Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-fineract
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..cfdff80
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,16 @@
+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.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9f2324c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,72 @@
+Apache Fineract: A Platform for Microfinance
+======
+
+The next evolution of fineract focussing being faster, lighter and cheaper to change (than existing mifos) so that it is more responsive to the needs of MFI’s and Integrators
+
+Build Status
+============
+
+Travis
+
+[![Build
+Status](https://travis-ci.org/openMF/mifosx.png?branch=master)](https://travis-ci.org/openMF/mifosx)
+
+Cloudbees Jenkins
+
+[![Build
+Status](https://openmf.ci.cloudbees.com/job/MIFOSX%20INTEGRATION%20TEST/badge/icon)](https://openmf.ci.cloudbees.com/job/MIFOSX%20INTEGRATION%20TEST/)
+
+<a target="_blank" href="https://openmf.ci.cloudbees.com/job/MIFOSX%20INTEGRATION%20TEST/"  title="Jenkins@CloudBees">Jenkins@CloudBees Unit + Integration Tests</a>
+
+
+Version
+==========
+
+The API for the fineract-platform (project named 'Apache Fineract')is documented in the api-docs under <b>Full API Matrix</b> and can be viewed <a target="_blank" href="https://demo.openmf.org/api-docs/apiLive.htm" title="API Documentation"> here
+</a>
+
+Latest stable release can always been viewed on master branch: <a target="_blank" href="https://github.com/openMF/mifosx/tree/master" title="Latest Release">Latest Release on Master</a>, <a target="_blank" href="https://github.com/openMF/mifosx/blob/master/CHANGELOG.md" title="Latest release change log">View change log</a>
+
+License
+=============
+
+This project is licensed under the open source MPL V2. See https://github.com/openMF/mifosx/blob/master/LICENSE.md
+
+Fineract Platform API
+=====================
+
+<a target="_blank" href="https://demo.openmf.org/api-docs/apiLive.htm" title="fineract platform api">API Documentation (Demo Server)</a>
+
+
+Online Demos
+=============================
+
+* <a target="_blank" href="https://demo.openmf.org" title="Reference Client App">Community App</a>
+* ~~<a target="_blank" href="https://demo.openmf.org/old/" title="Community App">Reference Client App (Deprecated)</a>~~
+
+Developers
+==========
+see https://mifosforge.jira.com/wiki/display/MIFOSX/MifosX+Technical - Developers Wiki Page
+
+see https://mifosforge.jira.com/wiki/display/MIFOSX/Getting+started+-+Contributing+to+MifosX  - Getting Started.
+
+see https://mifosforge.jira.com/wiki/display/MIFOSX/The+Basic+Design - Overview of Platform Implementation
+
+see https://github.com/openMF/mifosx/wiki/Screen-Based-Reporting for info around reporting
+
+see https://github.com/openMF/mifosx/wiki/Git-Usuage for info around using git
+
+see https://www.ohloh.net/p/mifosx for activity overview and basic code analysis.
+
+Roadmap
+==============
+
+<a target="_blank" href="http://goo.gl/IXS9Q" title="Community Roadmap (High Level)">Community Roadmap (High Level)</a>
+
+<a target="_blank" href="https://mifosforge.jira.com/browse/MIFOSX#selectedTab=com.atlassian.jira.plugin.system.project%3Aroadmap-panel" 
+   title="Project Release Roadmap on JIRA (Detailed View)">Project Release Roadmap on JIRA (Detailed View)</a>
+
+Video Demonstration
+===============
+
+Demonstration of first Prototype of this platform with browser App (April 2012) - http://www.youtube.com/watch?v=zN5Dn1Lc_js
diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
new file mode 100644
index 0000000..0eec1fd
--- /dev/null
+++ b/api-docs/apiLive.htm
@@ -0,0 +1,42924 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+<link rel="stylesheet" href="apidocs.css" type="text/css" media="screen" title="Apache Fineract API Docs" charset="utf-8" />
+<script type="text/javascript" src="jquery-1.7.min.js"></script>
+<title>Apache Fineract API Documentation</title>
+
+<script>
+	QueryParameters = (function() {
+		var result = {};
+		if (window.location.search) {
+			// split up the query string and store in an associative array
+			var params = window.location.search.slice(1).split("&");
+			for ( var i = 0; i < params.length; i++) {
+				var tmp = params[i].split("=");
+				result[tmp[0]] = unescape(tmp[1]);
+			}
+		}
+		return result;
+	}());
+
+	getLocation = function(href) {
+		var l = document.createElement("a");
+		l.href = href;
+		return l;
+	};
+
+	baseURL = "";
+	defQueryParams = "tenantIdentifier=default&pretty=true";
+
+	function clickAPILink(apiLink) {
+		var currentLink = baseURL + apiLink;
+		if (apiLink.indexOf("?") < 0)
+			currentLink += "?" + defQueryParams;
+		else
+			currentLink += "&" + defQueryParams;
+
+		if ((/MSIE (\d+\.\d+);/.test(navigator.userAgent))
+				|| (apiLink.indexOf("exportCSV") > -1)
+				|| (apiLink.indexOf("type=CSV") > -1)
+				|| (apiLink.indexOf("type=XLS") > -1)) { // test for MSIE (not a great browser for the api docs anyhow)  or a file download
+			location.href = currentLink;
+		} else
+			window.open(currentLink);
+	}
+
+	function clickAPILinkNotPretty(apiLink) {
+		var fixedQueryParams = "tenantIdentifier=default";
+		var currentLink = baseURL + apiLink;
+		if (apiLink.indexOf("?") < 0)
+			currentLink += "?" + fixedQueryParams;
+		else
+			currentLink += "&" + fixedQueryParams;
+
+		if ((/MSIE (\d+\.\d+);/.test(navigator.userAgent))
+				|| (apiLink.indexOf("exportCSV") > -1)
+				|| (apiLink.indexOf("type=CSV") > -1)
+				|| (apiLink.indexOf("type=XLS") > -1)) { // test for MSIE (not a great browser for the api docs anyhow)  or a file download
+			location.href = currentLink;
+		} else
+			window.open(currentLink);
+	}
+
+	function getBaseURL(docURL) {
+		var localhostUrl = "https://localhost:8443/fineract-provider/api/v1/";
+		var openmfDemoUrl = "/fineract-provider/api/v1/";
+
+		var baseUrl = "";
+
+		if (docURL.substring(0, 4).toLowerCase() == "http") {
+			var l = getLocation(docURL);
+
+			if (l.hostname == "demo.openmf.org") {
+				baseUrl = openmfDemoUrl;
+			} else {
+				baseUrl = "https://" + l.hostname
+						+ ":8443/fineract-provider/api/v1/"
+			}
+		} else {
+			//assume running locally
+			baseUrl = localhostUrl;
+		}
+
+		return baseUrl;
+	}
+</script>
+</head>
+
+<body>
+	<div id="page-wrapper">
+		<div id="flybar">
+			<div id="nav-logo">16.01.2.RELEASE</div>
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Overview</h2>
+				<div class="flybar-menu-overview">
+					<div class="toc-column1">
+						<div class="toc-section">
+<ul>
+	<li><a href="#top">Apache Fineract API Documentation</a></li>
+	<li><a href="#interact">Try The API From Your Browser</a></li>
+	<li><a href="#genopts">Generic Options</a></li>
+	<li><a href="#creates_and_updates">Creating and Updating</a></li>
+	<li><a href="#dates_and_numbers">Updating Dates and Numbers</a></li>
+	<li><a href="#field_descriptions">Field Descriptions</a></li>
+	<li><a href="#authentication_overview">Authentication Overview</a></li>
+	<li><a href="#authentication_basicauth">Authentication HTTP Basic</a></li>
+	<li><a href="#authentication_oauth">Authentication Oauth2</a></li>
+	<li><a href="#errors">Errors</a></li>
+	<li><a href="#batch_api">Batch API</a></li>
+	<li><a href="#fullapi_matrix">Full API Matrix</a></li>
+	<li><a href="#betaapi_matrix">Beta API Matrix</a></li>
+	<li><a href="#paymentapplicationlogic">Payment Application Logic / Transaction Processing Strategies</a></li>
+	<li><a href="#selfservice_overview">Self Service API</a></li>
+</ul>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Client</h2>
+				<div id="toc-menu-client" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#clients">Client</a></td>
+								<td>clients</td>
+								<td><a href="#clients_create">Create a Client</a></td>
+								<td><a href="#clients_list">List Clients</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}</td>
+								<td></td>
+								<td><a href="#clients_retrieve">Retrieve a Client</a></td>
+								<td><a href="#clients_update">Update a Client</a></td>
+								<td><a href="#clients_delete">Delete a Client</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=activate</td>
+								<td><a href="#clients_activate">Activate a Client</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=close</td>
+								<td><a href="#clients_close">Close a Client</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=reject</td>
+								<td><a href="#clients_reject">Reject a Client Application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=withdraw</td>
+								<td><a href="#clients_withdraw">Withdraw a Client Application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=reactivate</td>
+								<td><a href="#clients_reactivate">Reactivate a Client</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=assignStaff</td>
+								<td><a href="#clients_assignStaff">Assign Staff</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=unassignStaff</td>
+								<td><a href="#clients_unassignStaff">Unassign Staff</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=proposeTransfer</td>
+								<td><a href="#clients_propose_transfer">Propose a Client Transfer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=withdrawTransfer</td>
+								<td><a href="#clients_withdraw_transfer">Withdraw Client Transfer Proposal</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=acceptTransfer</td>
+								<td><a href="#clients_accept_transfer">Accept Client Transfer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=rejectTransfer</td>
+								<td><a href="#clients_reject_transfer">Reject Client Transfer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}?command=updateSavingsAccount</td>
+								<td><a href="#clients_updateSavingsAccount">Update Default Savings Account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td class="alt"></td>
+								<td>clients/{clientId}?command=proposeAndAcceptTransfer</td>
+								<td><a href="#clients_propose_and_accept_transfer">Propose and Accept a Client Transfer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}/accounts</td>
+								<td></td>
+								<td><a href="#clients_loansummary">Retrieve client accounts overview</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#client_identifiers">Client Identifiers</a></td>
+								<td>clients/{clientId}/identifiers</td>
+								<td><a href="#client_identifiers_create">Create an Identifier for a Client</a></td>
+								<td><a href="#client_identifiers_list">List all Identifiers for a Client</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}/identifiers/{identifierId}</td>
+								<td></td>
+								<td><a href="#client_identifiers_retrieve">Retrieve a Client Identifier</a></td>
+								<td><a href="#client_identifiers_update">Update a Client Identifier</a></td>
+								<td><a href="#client_identifiers_delete">Delete a Client Identifier</a></td>
+							</tr>
+							<tr>
+								<td><a href="#client_images">Client Images</a></td>
+								<td>clients/{clientId}/images</td>
+								<td><a href="#client_images_create">Upload an Image for a Client (as  DATA URI)</a></td>
+								<td><a href="#client_images_retrieve">Get Client Image (DATA URI)</a></td>
+								<td><a href="#client_images_update">Update Client Image (DATA URI)</a></td>
+								<td><a href="#client_images_delete">Delete Client Image</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td></td>
+								<td><a href="#client_images_create_form">
+									Upload an Image for a Client (Multi-part form data)</a>
+								</td>
+								<td><a href="#client_images_retrieve_binary">Get Client Image (Binary file)</a></td>
+								<td><a href="#client_images_update_form">
+									Update Client Image (Multi-part form data)</a>
+								</td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#standinginstruction">Standing Instructions</a></td>
+								<td>standinginstructions</td>
+								<td><a href="#standinginstruction_create">Create Standing Instruction</a></td>
+                                <td><a href="#standinginstructions_list">List Standing Instructions</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>standinginstructions/{standingInstructionId}</td>
+                                <td></td>
+								<td><a href="#standinginstructions_retrieve">Retrieve a Standing Instruction</a></td>
+                                <td><a href="#standinginstruction_update">Update Standing Instruction</a></td>
+								<td><a href="#standinginstruction_delete">Delete Standing Instruction(status change)</a></td>
+							</tr>
+                            <tr>
+                                <td></td>
+                                <td>standinginstructionrunhistory</td>
+                                <td></td>
+                                <td><a href="#standinginstructions_history">Standing Instructions Run History</a></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+							<tr>
+								<td><a href="#accounttransfers">Account Transfer</a></td>
+								<td>accounttransfers</td>
+								<td><a href="#accounttransfers_create">Create Account Transfer</a></td>
+                                <td><a href="#accounttransfers_list">List Account Transfer</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>accounttransfers/{accountTransferId}</td>
+								<td></td>
+                                <td><a href="#accounttransfers_retrieve">Retrieve a Account Transfer</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>accounttransfers/templateRefundByTransfer</td>
+								<td></td>
+                                <td><a href="#accounttransfers_retrieve_template_refund_by_transfer">Retrieve Refund of an Active Loan by Transfer Template</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>accounttransfers/refundByTransfer</td>
+                                <td><a href="#accounttransfers_refund_by_transfer">Refund an active loan by transfer</a></td>
+                                <td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#client_charge">Client Charge</a></td>
+								<td>clients/{clientId}/charges</td>
+								<td><a href="#add_clientCharge">Add Client Charge</a></td>
+								<td><a href="#list_clientCharges">List Client Charges</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}/charges/{clientChargeId}</td>
+								<td></td>
+								<td><a href="#clientcharges_retrieve">Retrieve a Client Charge</a></td>
+								<td></td>
+								<td><a href="#delete_clientCharge">Delete a Client Charge</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}/charges/{clientChargeId}?command=pay</td>
+								<td><a href="#pay_clientCharge">Pay a Client Charge</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}/charges/{clientChargeId}?command=waive</td>
+								<td><a href="#waive_clientCharge">Waive a Client Charge</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#client_transactions">Client Transactions</td>
+								<td>clients/{clientId}/transactions</td>
+								<td><a href="#list_clientTransactions">List Client Transactions</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>clients/{clientId}/transactions/{transactionId}?command=undo</td>
+								<td><a href="#revert_clientCharge">Undo a Client Transaction</a></td>
+								<td><a href="#clienttransactions_retrieve">Retrieve a Client Transaction</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+
+						</table>
+					</div>
+				</div>
+			</div>
+
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Savings</h2>
+				<div id="toc-menu-client" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#savingsaccounts">Savings Accounts</a></td>
+								<td>savingsaccounts</td>
+								<td><a href="#savingsaccounts_create">Submit new savings application</a></td>
+								<td><a href="#savingsaccounts_list">List savings application/accounts</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}</td>
+								<td></td>
+								<td><a href="#savingsaccounts_retrieve">Retrieve a savings application/account</a></td>
+								<td><a href="#savingsaccounts_update">Modify a savings application</a></td>
+								<td><a href="#savingsaccounts_delete">Delete a savings application</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=approve</td>
+								<td><a href="#savingsaccounts_approve">Approve a savings application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=undoApproval</td>
+								<td><a href="#savingsaccounts_undoapproval">Undo savings application approval</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=assignSavingsOfficer</td>
+								<td><a href="#savingsaccounts_assignSavingsOfficer">Assign Savings Officer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=unassignSavingsOfficer</td>
+								<td><a href="#savingsaccounts_unassignSavingsOfficer">Unassign Savings Officer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=reject</td>
+								<td><a href="#savingsaccounts_reject">Reject a savings application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=withdraw</td>
+								<td><a href="#savingsaccounts_withdrawbyapplicant">Withdraw savings application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=activate</td>
+								<td><a href="#savingsaccounts_activate">Activate a savings account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=close</td>
+								<td><a href="#savingsaccounts_close">Close a savings account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=calculateInterest</td>
+								<td><a href="#savingsaccounts_calculate_interest">Calculate interest on a savings account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}?command=postInterest</td>
+								<td><a href="#savingsaccounts_post_interest">Post interest on a savings account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#savingsaccounts_transactions">Savings Transactions</a></td>
+								<td>savingsaccounts/{accountId}/transactions?command=deposit</td>
+								<td><a href="#savingsaccounts_deposit">Make a deposit</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}/transactions?command=withdrawal</td>
+								<td><a href="#savingsaccounts_withdrawal">Make a withdrawal</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}/transactions/{transactionId}?command=undo</td>
+								<td><a href="#savingsaccounts_undotransaction">Undo transaction</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}/transactions/{transactionId}?command=modify</td>
+								<td><a href="#savingsaccounts_adjusttransaction">Adjust transaction</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}/transactions/template</td>
+								<td></td>
+								<td><a href="#savingsaccounts_transactions_template">Retrieve savings account transaction template</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}/transactions/{transactionId}</td>
+								<td></td>
+								<td><a href="#savingsaccounts_transaction">Retrieve savings account transaction</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#savings_charges">Savings Account Charges</a></td>
+								<td>savingsaccounts/{accountId}/charges</td>
+								<td><a href="#savings_charges_create">Add a Savings Account Charge</a></td>
+								<td><a href="#savings_charges_list">List Savings Account Charges</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}</td>
+								<td></td>
+								<td><a href="#savings_charges_retrieve">Retrieve a Savings Account Charge</a></td>
+								<td><a href="#savings_charges_update">Modify a Savings Account Charge</a></td>
+								<td><a href="#savings_charges_delete">Delete a Savings Account Charge</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=paycharge</td>
+								<td><a href="#savings_charges_pay">Pay a Savings Account Charge</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=waive</td>
+								<td><a href="#savings_charges_waive">Waive a Savings Account Charge</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=inactivate</td>
+								<td><a href="#savings_charges_inactivate">Inactivate a Savings Account Charge</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#fdaccounts">Fixed Deposit Accounts</a></td>
+								<td>fixeddepositaccounts</td>
+								<td><a href="#fdaccounts_create">Submit new fixed deposit application</a></td>
+								<td><a href="#fdaccounts_list">List fixed deposit application/accounts</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}</td>
+								<td></td>
+								<td><a href="#fdaccounts_retrieve">Retrieve a fixed deposit application/account</a></td>
+								<td><a href="#fdaccounts_update">Modify a fixed deposit application</a></td>
+								<td><a href="#fdaccounts_delete">Delete a fixed deposit application</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=approve</td>
+								<td><a href="#fdaccounts_approve">Approve a fixed deposit application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=undoApproval</td>
+								<td><a href="#fdaccounts_undoapproval">Undo fixed deposit application approval</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=reject</td>
+								<td><a href="#fdaccounts_reject">Reject a fixed deposit application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=withdraw</td>
+								<td><a href="#fdaccounts_withdrawbyapplicant">Withdraw fixed deposit application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=activate</td>
+								<td><a href="#fdaccounts_activate">Activate a fixed deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=close</td>
+								<td><a href="#fdaccounts_close">Close a fixed deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=prematureClose</td>
+								<td><a href="#fdaccounts_prematureclose">Premature Close a fixed deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=calculatePrematureAmount</td>
+								<td><a href="#fdaccounts_calculate_premature_amount">Calculate Premature amount on a fixed deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=calculateInterest</td>
+								<td><a href="#fdaccounts_calculate_interest">Calculate interest on a fixed deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositaccounts/{accountId}?command=postInterest</td>
+								<td><a href="#fdaccounts_post_interest">Post interest on a fixed deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#rdaccounts">Recurring Deposit Accounts</a></td>
+								<td>recurringdepositaccounts</td>
+								<td><a href="#rdaccounts_create">Submit new recurring deposit application</a></td>
+								<td><a href="#rdaccounts_list">List recurring deposit application/accounts</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}</td>
+								<td></td>
+								<td><a href="#rdaccounts_retrieve">Retrieve a recurring deposit application/account</a></td>
+								<td><a href="#rdaccounts_update">Modify a recurring deposit application</a></td>
+								<td><a href="#rdaccounts_delete">Delete a recurring deposit application</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=approve</td>
+								<td><a href="#rdaccounts_approve">Approve a recurring deposit application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=undoApproval</td>
+								<td><a href="#rdaccounts_undoapproval">Undo recurring deposit application approval</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=reject</td>
+								<td><a href="#rdaccounts_reject">Reject a recurring deposit application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=withdraw</td>
+								<td><a href="#rdaccounts_withdrawbyapplicant">Withdraw recurring deposit application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=activate</td>
+								<td><a href="#rdaccounts_activate">Activate a recurring deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=updateDepositAmount</td>
+								<td><a href="#rdaccounts_update_deposit_amount">Update recommended deposit amount</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=close</td>
+								<td><a href="#rdaccounts_close">Close a recurring deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=prematureClose</td>
+								<td><a href="#rdaccounts_prematureclose">Premature Close a recurring deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=calculatePrematureAmount</td>
+								<td><a href="#rdaccounts_calculate_premature_amount">Calculate Premature amount on a recurring deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=calculateInterest</td>
+								<td><a href="#rdaccounts_calculate_interest">Calculate interest on a recurring deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}?command=postInterest</td>
+								<td><a href="#rdaccounts_post_interest">Post interest on a recurring deposit account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#recurringdepositaccounts_transactions">Recurring Deposit Transactions</a></td>
+								<td>recurringdepositaccounts/{accountId}/transactions?command=deposit</td>
+								<td><a href="#recurringdepositaccounts_deposit">Make a deposit</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}/transactions?command=withdrawal</td>
+								<td><a href="#recurringdepositaccounts_withdrawal">Make a withdrawal</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}/transactions/{transactionId}?command=undo</td>
+								<td><a href="#recurringdepositaccounts_undotransaction">Undo transaction</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}/transactions/{transactionId}?command=modify</td>
+								<td><a href="#recurringdepositaccounts_adjusttransaction">Adjust transaction</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}/transactions/template</td>
+								<td></td>
+								<td><a href="#recurringdepositaccounts_transactions_template">Retrieve Deposit account transaction template</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositaccounts/{accountId}/transactions/{transactionId}</td>
+								<td></td>
+								<td><a href="#recurringdepositaccounts_transaction">Retrieve Deposit account transaction</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+						</table>
+					</div>
+				</div>
+			</div>
+
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Loan</h2>
+				<div id="toc-menu-loan" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#loans">Loans</a></td>
+								<td>loans?calculateLoanSchedule</td>
+								<td><a href="#loans_calculate">Calculate Loan Repayment Schedule</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans</td>
+								<td><a href="#loans_create">Submit a new Loan Application</a></td>
+								<td><a href="#loans_list">List Loans/Loan Applications</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}</td>
+								<td></td>
+								<td><a href="#loans_retrieve">Retrieve a Loan</a></td>
+								<td><a href="#loans_update">Update a Loan</a></td>
+								<td><a href="#loans_delete">Delete a Loan Application</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}?command=approve</td>
+								<td><a href="#loans_approve">Approve Loan Application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}?command=undoApproval</td>
+								<td><a href="#loans_approve_undo">Undo Loan Application Approval</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}?command=assignLoanOfficer</td>
+								<td><a href="#loans_assignLoanOfficer">Assign a Loan Officer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+								<tr>
+								<td></td>
+								<td>loans/{loanId}?command=unassignLoanOfficer</td>
+								<td><a href="#loans_unassignLoanOfficer">Unassign a Loan Officer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}?command=reject</td>
+								<td><a href="#loans_reject">Reject Loan Application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}?command=withdraw</td>
+								<td><a href="#loans_withdraw">Withdraw Loan Application</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}?command=disburse</td>
+								<td><a href="#loans_disburse">Disburse Loan</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}?command=disburseToSavings</td>
+								<td><a href="#loans_disburse_to_savings">Disburse Loan To Savings Account</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}?command=undoDisbursal</td>
+								<td><a href="#loans_disburse_undo">Undo Loan Application Disbursal</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}?command=recoverGuarantees</td>
+								<td><a href="#loans_recoverguarantee">Recover From Guarantors</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#loans_transactions">Loan Transactions</a></td>
+								<td>loans/{loanId}/transactions?command=repayment</td>
+								<td><a href="#loans_transaction_repayment">Enter a repayment</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}/transactions?command=waiveInterest</td>
+								<td><a href="#loans_transaction_waiveinterest">Waive Interest</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+                            <tr>
+                                <td></td>
+                                <td>loans/{loanId}/transactions?command=writeoff</td>
+                                <td><a href="#loans_transaction_write-off_loan">Write-off Loan</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>loans/{loanId}/transactions?command=undowriteoff</td>
+                                <td><a href="#loans_transaction_undo_write-off_loan">Undo Loan Write-off transaction</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+							 <tr>
+                                <td></td>
+                                <td>loans/{loanId}/transactions?command=prepayLoan</td>
+                                <td><a href="#loans_transaction_pre_close_loan">Loan Pre-Closure transaction template</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+							<tr>
+                                <td></td>
+                                <td>loans/{loanId}/transactions?command=recoverypayment</td>
+                                <td><a href="#loans_transaction_recovery_payment">Make a Recovery Payment</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}/transactions/{transactionId}</td>
+								<td></td>
+								<td><a href="#loans_transaction_retrieve">Retrieve a transactions details</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}/transactions/{transactionId}</td>
+								<td><a href="#loans_transaction_adjust">Adjust a Transaction</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+                                <td></td>
+                                <td>loans/{loanId}/transactions?command=refundByCash</td>
+                                <td><a href="#loans_transaction_refund_by_cash">Refund an Active Loan by Cash</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+							<tr>
+								<td><a href="#loans_charges">Loan Charges</a></td>
+								<td>loans/{loanId}/charges</td>
+								<td><a href="#loans_charges_create">Add a Loan Charge</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}/charges/{loanChargeId}</td>
+								<td><a href="#loans_charges_pay">Pay Loan Charge from Linked Savings</a></td>
+								<td><a href="#loans_charges_retrieve">Retrieve a Loan Charge</a></td>
+								<td><a href="#loans_charges_update">Modify a Loan Charge</a></td>
+								<td><a href="#loans_charges_delete">Delete a Loan Charge</a></td>
+							</tr>
+							<tr>
+								<td><a href="#guarantors">Loan Guarantors</a></td>
+								<td>loans/{loanId}/guarantors</td>
+								<td><a href="#guarantors_create">Create a Guarantor
+								</a></td>
+								<td><a href="#guarantors_list">List Guarantors
+								</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loans/{loanId}/guarantors/{guarantorId}</td>
+								<td></td>
+								<td><a href="#guarantors_retrieve">Retrieve a Guarantor</a></td>
+								<td><a href="#guarantors_update">Update a Guarantor</a></td>
+								<td><a href="#guarantors_delete">Delete a Guarantor</a></td>
+							</tr>
+							<tr>
+								<td><a href="#collaterals">Loan Collateral</a></td>
+								<td>loans/{loanId}/collaterals</td>
+								<td><a href="#collaterals_create">Create a Collateral
+								</a></td>
+								<td><a href="#collaterals_list">List collaterals
+								</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr  class="alt">
+								<td></td>
+								<td>loans/{loanId}/collaterals/{collateralId}</td>
+								<td></td>
+								<td><a href="#collaterals_retrieve">Retrieve a Collateral</a></td>
+								<td><a href="#collaterals_update">Update a Collateral</a></td>
+								<td><a href="#collaterals_delete">Delete a Collateral</a></td>
+							</tr>
+							<tr>
+								<td><a href="#loan_rescheduling">Loan Rescheduling</a></td>
+								<td>rescheduleloans</td>
+								<td><a href="#loan_reschedule_request_create">Create new loan reschedule request</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>rescheduleloans/{requestId}</td>
+								<td></td>
+								<td><a href="#loan_reschedule_request_retrieve">Retrieve a Loan Reschedule Request</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>rescheduleloans/{requestId}?command=previewLoanReschedule</td>
+								<td></td>
+								<td><a href="#loan_reschedule_preview_retrieve">Retrieve a Preview of The New Loan Repayment Schedule</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>rescheduleloans/{requestId}?command=reject</td>
+								<td><a href="#loan_reschedule_request_reject">Reject Loan Reschedule Request</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>rescheduleloans/{requestId}?command=approve</td>
+								<td><a href="#loan_reschedule_request_approve">Approve Loan Reschedule Request</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#loan_term_variations">Loan Term Variations</a></td>
+								<td>/loans/{loanId}/schedule?command=calculateLoanSchedule</td>
+								<td><a href="#loans_calculate_with_exceptions">Calculate Schedule with Loan Term Variations</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>/loans/{loanId}/schedule?command=addVariations</td>
+								<td><a href="#loans_update_variations">Create Loan Term Variations</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>/loans/{loanId}/schedule?command=deleteVariations</td>
+								<td><a href="#loans_delete_variations">Remove All Loan Term Variations</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+						</table>
+					</div>
+				</div>
+			</div>
+
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Groups</h2>
+				<div id="toc-menu-loan" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#groups">Group</a></td>
+								<td>groups</td>
+								<td><a href="#groups_create">Create a Group</a></td>
+								<td><a href="#groups_list">List Groups</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+			                    <td>groups/template</td>
+			                    <td></td>
+			                    <td><a href="#groups_template">Retrieve Group Template</a></td>
+			                    <td></td>
+								<td></td>
+			                </tr>
+							<tr>
+								<td></td>
+								<td>groups/{groupId}</td>
+								<td></td>
+								<td><a href="#groups_retrieve">Retrieve a Group</a></td>
+								<td><a href="#groups_update">Update a Group</a></td>
+								<td><a href="#groups_delete">Delete a Group</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>groups/{groupId}?command=activate</td>
+								<td><a href="#groups_activate">Activate a Group</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>groups/{groupId}?command=associateClients</td>
+								<td><a href="#groups_associate_clients">Associate Clients</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>groups/{groupId}?command=disassociateClients</td>
+								<td><a href="#groups_disassociate_clients">Disassociate Clients</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>groups/{groupId}/accounts</td>
+								<td></td>
+								<td><a href="#groups_accounts">Retrieve Group accounts summary</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>groups/{groupId}?command=transferClients</td>
+								<td><a href="#groups_transfer_clients">Bulk Transfer Clients across Groups</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+                            <tr>
+                                <td></td>
+                                <td>groups/{groupId}?command=generateCollectionSheet</td>
+                                <td><a href="#groups_generate_collectionsheet">Generate Collection Sheet</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>groups/{groupId}?command=saveCollectionSheet</td>
+                                <td><a href="#groups_save_collectionsheet">Save Collection Sheet</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>groups/{groupId}?command=unassignStaff</td>
+                                <td><a href="#groups_unassignStaff">Unassign Staff</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>groups/{groupId}?command=assignStaff</td>
+                                <td><a href="#groups_assignStaff">Assign Staff</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>groups/{groupId}?command=close</td>
+                                <td><a href="#groups_close">Close Group</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>groups/{groupId}?command=assignRole</td>
+                                <td><a href="#groups_assignRole">Assign Role</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>groups/{groupId}?command=unassignRole</td>
+                                <td><a href="#groups_unassignRole">Unassign Role</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>groups/{groupId}?command=updateRole</td>
+                                <td><a href="#groups_updateRole">Update Role</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td><a href="#centers">Center</a></td>
+                                <td>centers</td>
+                                <td><a href="#centers_create">Create a Center</a></td>
+                                <td><a href="#centers_list">List Centers</a></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>centers/template</td>
+                                <td></td>
+                                <td><a href="#centers_template">Retrieve Center Template</a></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>centers/{centerId}</td>
+                                <td></td>
+                                <td><a href="#centers_retrieve">Retrieve a Center</a></td>
+                                <td><a href="#centers_update">Update a Center</a></td>
+                                <td><a href="#centers_delete">Delete a Center</a></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>centers/{centerId}?command=activate</td>
+                                <td><a href="#centers_activate">Activate a Center</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>centers/{centerId}?command=close</td>
+                                <td><a href="#centers_close">Close Center</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+							<tr>
+								<td></td>
+								<td>centers/{centerId}?command=associateGroups</td>
+								<td><a href="#centers_associate_groups">Associate Groups</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>centers/{centerId}?command=disassociateGroups</td>
+								<td><a href="#centers_disassociate_groups">Disassociate Groups</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+                            <tr>
+															<td></td>
+															<td>centers/{centerId}/accounts</td>
+															<td></td>
+															<td><a href="#centers_accounts">Retrieve Center accounts summary</a></td>
+															<td></td>
+															<td></td>
+														</tr>
+                            <tr>
+                                <td></td>
+                                <td>centers/{centerId}?command=generateCollectionSheet</td>
+                                <td><a href="#centers_generate_collectionsheet">Generate Collection Sheet</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td>centers/{centerId}?command=saveCollectionSheet</td>
+                                <td><a href="#centers_save_collectionsheet">Save Collection Sheet</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+
+
+							<tr>
+                                <td><a href="#centers">Collection Sheet</a></td>
+                                <td>collectionsheet?command=generateCollectionSheet</td>
+                                <td><a href="#generate_individual_collection_sheet">Generate Collection Sheet</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+                           <tr>
+                                <td></td>
+                                <td>collectionsheet?command=saveCollectionSheet</td>
+                                <td><a href="#save_individual_collection_sheet">Save Collection Sheet</a></td>
+                                <td></td>
+                                <td></td>
+                                <td></td>
+                            </tr>
+						</table>
+					</div>
+				</div>
+			</div>
+
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Accounting</h2>
+				<div id="toc-menu-accounting" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#glaccounts">General Ledger Account</a></td>
+								<td>glaccounts</td>
+								<td><a href="#glaccounts_create">Create a New Ledger Account</a></td>
+								<td><a href="#glaccounts_list">List Ledger Accounts</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>glaccounts/{glaccountId}</td>
+								<td></td>
+								<td><a href="#glaccounts_retrieve">Retrieve a Ledger Account</a></td>
+								<td><a href="#glaccounts_update">Update a Ledger Account</a></td>
+								<td><a href="#glaccounts_delete">Delete a Ledger Account</a></td>
+							</tr>
+							<tr>
+								<td><a href="#glclosures">Accounting Closure</a></td>
+								<td>glclosures</td>
+								<td><a href="#glclosures_create">Create an Accounting Closure</a></td>
+								<td><a href="#glclosures_list">List Accounting Closures</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>glclosures/{glclosureId}</td>
+								<td></td>
+								<td><a href="#glclosures_retrieve">Retrieve an Accounting Closure</a></td>
+								<td><a href="#glclosures_update">Update an Accounting Closure</a></td>
+								<td><a href="#glclosures_delete">Delete an Accounting Closure</a></td>
+							</tr>
+							<tr>
+								<td><a href="#journalentries">Journal Entries</a></td>
+								<td>journalentries</td>
+								<td><a href="#journalentries_create">Create Journal Entries</a></td>
+								<td><a href="#journalentries_list">List Journal Entries</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>journalentries?command=updateRunningBalance</td>
+								<td><a href="#journalentries_updatebalance">Update Running Balance for Journal Entries</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>journalentries/{entryId}</td>
+								<td></td>
+								<td><a href="#journalentries_retrieve">Retrieve a single Entry</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>journalentries/{transactionId}/reversal</td>
+								<td><a href="#journalentries_reverse">Reverse Journal Entries</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#accountingrules">Accounting Rules</a></td>
+								<td>accountingrules</td>
+								<td><a href="#accountingrules_create">Create a Accounting Rule</a></td>
+								<td><a href="#accountingrules_list">List Accounting Rules</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>accountingrules/{accountingruleId}</td>
+								<td></td>
+								<td><a href="#accountingrules_retrieve">Retrieve a Accounting Rule</a></td>
+								<td><a href="#accountingrules_update">Update a Accounting Rule</a></td>
+								<td><a href="#accountingrules_delete">Delete a Accounting Rule</a></td>
+							</tr>
+							<tr>
+								<td><a href="#officeglaccount">Mapping Financial Activities to Accounts</a></td>
+								<td>financialactivityaccounts</td>
+								<td><a href="#financialactivityaccounts_create">Create Financial Activity to Account Mapping</a><br> </td>
+								<td><a href="#financialactivityaccounts_list">List Financial Activities to Accounts Mappings</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>financialactivityaccounts/{financialActivityAccountId}</td>
+								<td></td>
+								<td><a href="#financialactivityaccounts_retrieve">Retrieve a Financial Activity to Account Mapping</a></td>
+								<td><a href="#financialactivityaccounts_update">Update a Financial Activity to Account Mapping</a></td>
+								<td><a href="#financialactivityaccounts_delete">Delete a Financial Activity to Account Mapping</a></td>
+
+							</tr>
+							<tr>
+								<td><a href="#periodicaccrualaccounting">Periodic Accrual Accounting</a></td>
+								<td>accrualaccounting</td>
+								<td><a href="#periodicaccrualaccounting_run">Execute Periodic Accrual Accounting</a><br> </td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+
+							<tr>
+								<td><a href="#provisioningentries">Provisioning Entries</a></td>
+								<td>provisioningentries</td>
+								<td><a href="#provisioningentries_create">Create Provisioning Entry</a><br> </td>
+								<td><a href="#provisioningentries_list">List Provisioning Entries</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>provisioningentries/{entryId}</td>P
+								<td></td>
+								<td><a href="#provisioningentry_retrieve">Retrieve Provisioning Entry</a></td>
+								<td><a href="#provisioningentry_recreate">Recreates Provisionin Entry</a></td>
+								<td><a href="#provisioningentry_addjournals">Add Provisioning Journal Entries</a></td>
+								</tr>
+
+
+						</table>
+					</div>
+				</div>
+			</div>
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Org</h2>
+				<div id="toc-menu-org" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#offices">Office</a></td>
+								<td>offices</td>
+								<td><a href="#offices_create">Create an Office</a></td>
+								<td><a href="#offices_list">List Offices</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>offices/{officeId}</td>
+								<td></td>
+								<td><a href="#offices_retrieve">Retrieve an Office</a></td>
+								<td><a href="#offices_update">Update an Office</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#loanproducts">Loan Product</a></td>
+								<td>loanproducts</td>
+								<td><a href="#loanproducts_create">Create a Loan
+										Product</a></td>
+								<td><a href="#loanproducts_list">List Loan Products</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loanproducts/{productId}</td>
+								<td></td>
+								<td><a href="#loanproducts_retrieve">Retrieve a Loan
+										Product</a></td>
+								<td><a href="#loanproducts_update">Update a Loan
+										Product</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#savingsproducts">Savings Product</a></td>
+								<td>savingsproducts</td>
+								<td><a href="#savingsproducts_create">Create a Savings product</a></td>
+								<td><a href="#savingsproducts_list">List Savings products</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>savingsproducts/{productId}</td>
+								<td></td>
+								<td><a href="#savingsproducts_retrieve">Retrieve a savings product</a></td>
+								<td><a href="#savingsproducts_update">Update a savings product</a></td>
+								<td><a href="#savingsproducts_delete">Delete a savings product</a></td>
+							</tr>
+							<tr>
+								<td><a href="#fdproducts">Fixed Deposit Product</a></td>
+								<td>fixeddepositproducts</td>
+								<td><a href="#fdproducts_create">Create a Fixed Deposit product</a></td>
+								<td><a href="#fdproducts_list">List Fixed Deposit products</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>fixeddepositproducts/{productId}</td>
+								<td></td>
+								<td><a href="#fdproducts_retrieve">Retrieve a Fixed Deposit product</a></td>
+								<td><a href="#fdproducts_update">Update a Fixed Deposit product</a></td>
+								<td><a href="#fdproducts_delete">Delete a Fixed Deposit product</a></td>
+							</tr>
+							<tr>
+								<td><a href="#rdproducts">Recurring Deposit Product</a></td>
+								<td>recurringdepositproducts</td>
+								<td><a href="#rdproducts_create">Create a Recurring Deposit product</a></td>
+								<td><a href="#rdproducts_list">List Recurring Deposit products</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>recurringdepositproducts/{productId}</td>
+								<td></td>
+								<td><a href="#rdproducts_retrieve">Retrieve a Recurring Deposit product</a></td>
+								<td><a href="#rdproducts_update">Update a Recurring Deposit product</a></td>
+								<td><a href="#rdproducts_delete">Delete a Recurring Deposit product</a></td>
+							</tr>
+							<tr>
+								<td><a href="#configs">Currency</a></td>
+								<td>currencies</td>
+								<td></td>
+								<td><a href="#configs_currencyretrieve">Retrieve
+										Currency Configuration</a></td>
+								<td><a href="#configs_currencyupdate">Update Currency
+										Configuration</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#funds">Funds</a></td>
+								<td>funds</td>
+								<td><a href="#funds_create">Create a Fund</a></td>
+								<td><a href="#funds_retrieve">List Funds</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>funds/{fundId}</td>
+								<td></td>
+								<td><a href="#fund_retrieve">Retrieve a Fund</a></td>
+								<td><a href="#fund_update">Update a Fund</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#staff">Staff</a></td>
+								<td>staff</td>
+								<td><a href="#staff_create">Create a Staff Member</a></td>
+								<td><a href="#staff_list">List Staff</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>staff/{staffId}</td>
+								<td></td>
+								<td><a href="#staff_retrieve">Retrieve a Staff Member</a></td>
+								<td><a href="#staff_update">Update a Staff Member</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#client_images">Staff Images</a></td>
+								<td>staff/{staffId}/images</td>
+								<td><a href="#client_images_create">Upload an Image for a Staff Member (as  DATA URI)</a></td>
+								<td><a href="#client_images_retrieve">Get Staff Image (DATA URI)</a></td>
+								<td><a href="#client_images_update">Update Staff Image (DATA URI)</a></td>
+								<td><a href="#client_images_delete">Delete Staff Image</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td></td>
+								<td><a href="#client_images_create_form">
+									Upload an Image for a Staff Member (Multi-part form data)</a>
+								</td>
+								<td><a href="#client_images_retrieve_binary">Get Staff Image (Binary file)</a></td>
+								<td><a href="#client_images_update_form">
+									Update Staff Image (Multi-part form data)</a>
+								</td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#charges">Charges</a></td>
+								<td>charges</td>
+								<td><a href="#charges_create">Create a Charge</a></td>
+								<td><a href="#charges_list">List Charges</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>charges/{chargeId}</td>
+								<td></td>
+								<td><a href="#charges_retrieve">Retrieve a Charge</a></td>
+								<td><a href="#charges_update">Update a Charge</a></td>
+								<td><a href="#charges_delete">Delete a Charge</a></td>
+							</tr>
+							<tr>
+								<td><a href="#loanproductmix">Loan Product Mix</a></td>
+								<td>loanproducts?associations=productMixes</td>
+								<td></td>
+								<td><a href="#loanproductmix_list">List Loan Products Mix</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>loanproducts/{productId}/productmix</td>
+								<td><a href="#loanproductmix_create">Create a Loan Product Mix</a></td>
+								<td><a href="#loanproductmix_retrieve">Retrieve Loan Products Mix</a></td>
+								<td><a href="#loanproductmix_update">Update Loan Product Mix</a></td>
+								<td><a href="#loanproductmix_delete">Delete Loan Products Mix</a></td>
+							</tr>
+							<tr>
+								<td><a href="#holidays">Holidays</a></td>
+								<td>holidays</td>
+								<td><a href="#holidays_create">Create a Holiday</a></td>
+								<td><a href="#holidays_list">List Holidays</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>holidays/{holidayId}</td>
+								<td></td>
+								<td><a href="#holidays_retrieve">Retrieve a Holiday</a></td>
+								<td><a href="#holidays_update">Update a Holiday</a></td>
+								<td><a href="#holidays_delete">Delete a Holiday</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>holidays/{holidayId}?command=activate</td>
+								<td><a href="#holidays_activate">Activate a Holiday</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#workingdays">Working days</a></td>
+								<td>workingdays</td>
+								<td><a href="#workingdays_list">List workingdays</a></td>
+							    <td><a href="#workingdays_template">Working days template</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>workingdays/{workingdaysId}</td>
+								<td></td>
+								<td><a href="#workingdays_update">Update a workingdays</a></td>
+							</tr>
+							<tr>
+								<td><a href="#templates">User Generated Documents</a></td>
+								<td>templates</td>
+								<td><a href="#resources_addtemplate">Create a UGD</a></td>
+								<td><a href="#resource_templatelist">List UGDs</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>templates/{templateId}</td>
+								<td></td>
+								<td><a href="#resources_retrievetemplate">Retrieve a UGD</a></td>
+								<td><a href="#resources_updatetemplate">Update a UGD</a></td>
+								<td><a href="#resources_deletetemplate">Delete a UGD</a></td>
+							</tr>
+							<tr>
+								<td><a href="#interestratechart">Interest Rate Charts</a></td>
+								<td>charts</td>
+								<td><a href="#interestratechart_create">Create a Chart</a></td>
+								<td><a href="#interestratechart_list">List Charts</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>interestratecharts/{chartId}</td>
+								<td></td>
+								<td><a href="#interestratechart_retrieve">Retrieve a Chart</a></td>
+								<td><a href="#interestratechart_update">Update a Chart</a></td>
+								<td><a href="#interestratechart_delete">Delete a Chart</a></td>
+							</tr>
+							<tr>
+								<td><a href="#interestrateslab">Interest Rate Slabs</a></td>
+								<td>slabs</td>
+								<td><a href="#interestrateslab_create">Create a Slab</a></td>
+								<td><a href="#interestrateslab_list">List Slabs</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>interestrateslabs/{slabId}</td>
+								<td></td>
+								<td><a href="#interestrateslab_retrieve">Retrieve a Slab</a></td>
+								<td><a href="#interestrateslab_update">Update a Slab</a></td>
+								<td><a href="#interestrateslab_delete">Delete a Slab</a></td>
+							</tr>
+							<tr>
+								<td><a href="#tellercashmgmt">Teller Cash Management</a></td>
+								<td>tellers</td>
+								<td><a href="#createtellers">Create Teller</a></td>
+								<td><a href="#listtellers">List Tellers</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>tellers/{tellerId}</td>
+								<td></td>
+								<td><a href="#findtellers">Find Teller</a></td>
+								<td><a href="#updatetellers">Update Teller</a></td>
+								<td><a href="#deletetellers">Delete Teller</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>tellers/{tellerId}/cashiers</td>
+								<td><a href="#createcashier">Create Cashier</a></td>
+								<td><a href="#findallcashiers">Retrieve Cashier</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>tellers/{tellerId}/cashiers/{cashierId}</td>
+								<td></td>
+								<td><a href="#findaonecashier">Find Cashier</a></td>
+								<td><a href="#updatecashier">Update Cashier</a></td>
+								<td><a href="#deletecashier">Delete Cashier</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>tellers/{tellerId}/cashiers/template</td>
+								<td></td>
+								<td><a href="#updatecashier">Retrieve Cashier Template</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>tellers/{tellerId}/cashiers/{cashierId}/allocate</td>
+								<td><a href="#allocateCashToCashier">Allocate Cash To Cashier</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>tellers/{tellerId}/cashiers/{cashierId}/settle</td>
+								<td><a href="#settleCashFromCashier">Settle Cash From Cashier</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>tellers/{tellerId}/cashiers/{cashierId}/transactions</td>
+								<td></td>
+								<td><a href="#transactionsForCashier">Retrieve Cashier Transactions</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>tellers/{tellerId}/cashiers/{cashierId}/summaryandtransactions</td>
+								<td></td>
+								<td><a href="#getTransactionsWtihSummaryForCashier">Retrieve Cashier Transactions With Summary</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>tellers/{tellerId}/cashiers/{cashierId}/transactions/template</td>
+								<td></td>
+								<td><a href="#retrieveCashierTxnTemplate">Retrieve Cashier Transaction Template</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+
+							<tr>
+								<td><a href="#paymenttype">Payment Type</a></td>
+								<td>paymenttypes</td>
+								<td><a href="#create_paymenttype">Create Payment Type</a></td>
+								<td><a href="#paymenttype_list">List Payment Types</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>paymenttypes/{paymentTypeId}</td>
+								<td></td>
+								<td><a href="#paymenttype_retrieve">Retrieve Payment Type</a></td>
+								<td><a href="#paymenttype_update">Update Payment Type</a></td>
+								<td><a href="#paymnettype_delete">Delete Payment Type</a></td>
+							</tr>
+							<tr>
+								<td><a href="#provisioningcriteria">Provisioning Criteria</a></td>
+								<td>provisioningcriteria</td>
+								<td><a href="#create_provisioningcriteria">Create Provisioning Criteria</a></td>
+								<td><a href="#provisioningcriteria_list">List Provisioning Criteria</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>provisioningcriteria/{criteriaId}</td>
+								<td></td>
+								<td><a href="#retrieve_provisioningcriteria">Retrieve Provisioning Criteria</a></td>
+								<td><a href="#update_provisioningcriteria">Update Provisioning Criteria</a></td>
+								<td><a href="#delete_provisioningcriteria">Delete Provisioning Criteria</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#floatingrates">Floating Rates</a></td>
+								<td>floatingrates</td>
+								<td><a href="#create_floatingrates">Create Floating Rate</a></td>
+								<td><a href="#floatingrates_list">List Floating Rates</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>floatingrates/{floatingRateId}</td>
+								<td></td>
+								<td><a href="#retrieve_floatingrate">Retrieve Floating Rate</a></td>
+								<td><a href="#update_floatingrate">Update Floating Rate</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+						</table>
+					</div>
+				</div>
+			</div>
+			<div class="flybar-nav">
+				<h2 class="flybar-button">User</h2>
+				<div id="toc-menu-user" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#authenticationbasic">Authentication HTTP Basic</a></td>
+								<td>authentication</td>
+								<td><a href="#authenticate_request_basic">Verify Authentication</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#authenticationoauth">Authentication Oauth2</a></td>
+								<td>oauth/token</td>
+								<td><a href="#oauth">OAuth2 Access and refresh Token Request</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td></td>
+								<td><a href="#oauth_access_token_req">OAuth2 Access Token Request from refresh token</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>authenticated user</td>
+								<td></td>
+								<td><a href="#userdetails_request">Fetch Authenticated user details</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#users">User</a></td>
+								<td>users</td>
+								<td><a href="#users_create">Create a User</a></td>
+								<td><a href="#users_list">List Users</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>users/{userId}</td>
+								<td></td>
+								<td><a href="#users_retrieve">Retrieve a User</a></td>
+								<td><a href="#users_update">Update a User</a></td>
+								<td><a href="#users_delete">Delete a User</a></td>
+							</tr>
+							<tr>
+								<td><a href="#roles">Role</a></td>
+								<td>roles</td>
+								<td><a href="#roles_create">Create a New Role</a></td>
+								<td><a href="#roles_list">List Roles</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>roles/{roleId}</td>
+								<td></td>
+								<td><a href="#roles_retrieve">Retrieve a Role</a></td>
+								<td><a href="#roles_update">Update a Role</a></td>
+								<td><a href="#roles_delete">Delete Role</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>roles/{roleId}/permissions</td>
+								<td></td>
+								<td><a href="#rolespermissions_retrieve">Retrieve a
+										Role's Permissions</a></td>
+								<td><a href="#rolespermissions_update">Update a Role's
+										Permissions</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>roles/{roleId}?command=enable</td>
+								<td><a href="#roles_enable">Enable Role</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>roles/{roleId}?command=disable</td>
+								<td><a href="#roles_disable">Disable Role</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#permissions">Permission</a></td>
+								<td>permissions</td>
+								<td></td>
+								<td><a href="#permissions_list">List Application Permissions</a></td>
+								<td><a href="#permissions_update">Enable/Disable Permissions for Maker Checker</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#password_preferences">Password preferences</a></td>
+								<td>passwordpreferences</td>
+								<td><a href=""></a></td>
+								<td><a href="#password_preferences_list">Get Password Preferences</a></td>
+								<td><a href="#password_preferences_update">Update Password Preferences</a></td>
+								<td></td>
+							</tr>
+						</table>
+					</div>
+				</div>
+			</div>
+			<div class="flybar-nav">
+				<h2 class="flybar-button">System</h2>
+				<div id="toc-menu-system" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#configs_global">Global Configuration</a></td>
+								<td>configurations</td>
+								<td></td>
+								<td><a href="#configs_globalconfig_retrieve">List
+										Global Configuration</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#configs_global">Global Configuration</a></td>
+								<td>configurations/{configId}</td>
+								<td></td>
+								<td><a href="#configs_globalconfig_retrieve_one">Retrieve
+										a Global Configuration</a></td>
+								<td><a href="#configs_globalconfig_update">Update
+										Global Configuration</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#configs_hooks">Hooks</a></td>
+								<td>hooks</td>
+								<td><a href="#configs_hooks_create">Create a Hook</a></td>
+								<td><a href="#configs_hooks_retrieve">List Hooks</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>hooks/{hookId}</td>
+								<td></td>
+								<td><a href="#configs_hook_retrieve">Retrieve a Hook</a></td>
+								<td><a href="#configs_hook_update">Update a Hook</a></td>
+								<td><a href="#configs_hook_delete">Delete a Hook</a></td>
+							</tr>
+							<tr>
+								<td><a href="#accountnumberformats">Account number format</a></td>
+								<td>accountnumberformats</td>
+								<td><a href="#accountnumberformats_create">Create an Account number format</a></td>
+								<td><a href="#accountnumberformats_list">List Account number formats</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>accountnumberformats/{accountnumberformatId}</td>
+								<td></td>
+								<td><a href="#accountnumberformats_retrieve">Retrieve an Account number format</a></td>
+								<td><a href="#accountnumberformats_update">Update an Account number format</a></td>
+								<td><a href="#accountnumberformats_delete">Delete an Account number format</a></td>
+							</tr>
+							<tr>
+								<td><a href="#configs_codes">Codes</a></td>
+								<td>codes</td>
+								<td><a href="#configs_codes_create">Create a Code</a></td>
+								<td><a href="#configs_codes_retrieve">List Codes</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>codes/{codeId}</td>
+								<td></td>
+								<td><a href="#configs_code_retrieve">Retrieve a Code</a></td>
+								<td><a href="#configs_code_update">Update a Code</a></td>
+								<td><a href="#configs_code_delete">Delete a Code</a></td>
+							</tr>
+							<tr>
+								<td><a href="#configs_codes_codevalues">Code Values</a></td>
+								<td>codes/{codeId}/codevalues</td>
+								<td><a href="#configs_codes_codevalues_create">Create a Code Value</a></td>
+								<td><a href="#configs_codes_codevalues_list">List Code Values</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>codes/{codeId}/codevalues/{codevalueId}</td>
+								<td></td>
+								<td><a href="#configs_codes_codevalues_retrieve">Retrieve a Code Value</a></td>
+								<td><a href="#configs_codes_codevalues_update">Update a Code Value</a></td>
+								<td><a href="#configs_codes_codevalues_delete">Delete a Code Value</a></td>
+							</tr>
+							<tr>
+								<td><a href="#audits">Audits</a></td>
+								<td>audits</td>
+								<td></td>
+								<td><a href="#audits_list">List Audits</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>audits/{auditId}</td>
+								<td></td>
+								<td><a href="#audits_retrieve">Retrieve an Audit Entry</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#makercheckers">Makercheckers</a></td>
+								<td>makercheckers</td>
+								<td></td>
+								<td><a href="#makercheckers_list">List Maker Checker Entries</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>makercheckers/{auditId}</td>
+								<td></td>
+								<td></td>
+								<td></td>
+								<td><a href="#makercheckers_delete">Delete Maker Checker Entry</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>makercheckers/{auditId}?command=approve</td>
+								<td><a href="#makercheckers_approve">Approve Maker Checker Entry</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#scheduler_jobs">Scheduler jobs</a></td>
+								<td>jobs</td>
+								<td></td>
+								<td><a href="#scheduler_jobs_list">List Scheduler jobs</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>jobs/{jobId}</td>
+								<td></td>
+								<td><a href="#retrieve_scheduler_job">Retrieve a job</a></td>
+								<td><a href="#update_scheduler_job">Update a job</a></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>jobs/{jobId}?command=executeJob</td>
+								<td><a href="#run_job">Run a job</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>jobs/{jobid}/runhistory</td>
+								<td></td>
+								<td><a href="#retrieve_job_runhistory">Retrieve job history</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>scheduler</td>
+								<td></td>
+								<td><a href="#retrieve_scheduler_status">Retrieve scheduler status</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>scheduler?command=start</td>
+								<td><a href="#activate_scheduler">Activate a scheduler service</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>scheduler?command=stop</td>
+								<td><a href="#suspend_scheduler">Suspend a scheduler service</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#external_services">External Services</a></td>
+								<td>externalservice/{servicename}</td>
+								<td></td>
+								<td><a href="#configs_externalService_retrieve">Retrieve
+										a external service configuration</a></td>
+								<td><a href="#configs_externalService_update">Update
+										external service Configuration</a></td>
+								<td></td>
+							</tr>
+						</table>
+					</div>
+				</div>
+			</div>
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Non-Core</h2>
+				<div id="toc-menu-noncore" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#datatables">Data Table</a></td>
+								<td>datatables</td>
+                                <td><a href="#datatables_createTable">Create Data Table</a></td>
+								<td><a href="#datatables_list">List Data Tables</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+                                <td>datatables/{datatable}</td>
+                                <td></td>
+								<td><a href="#datatables_getTable">Retrieve Data Table Details</a></td>
+                                <td><a href="#datatables_updateTable">Update Data Table</a></td>
+                                <td><a href="#datatables_deleteTable">Delete Data Table</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>datatables/register/{datatable}/{apptable}</td>
+								<td><a href="#datatables_register">Register Data Table</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>datatables/deregister/{datatable}</td>
+								<td><a href="#datatables_deregister">Deregister Data
+										Table</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>datatables/{datatable}/{apptableId}</td>
+								<td><a href="#datatables_create">Create Entry in Data
+										Table</a></td>
+								<td><a href="#datatables_retrieve">Retrieve Entry(s)
+										from Data Table</a></td>
+								<td><a href="#datatables_update">Update Entry in Data
+										Table (One to One)</a></td>
+								<td><a href="#datatables_delete">Delete Entry(s) in
+										Data Table</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>datatables/{datatable}/{apptableId}/{datatableId}</td>
+								<td></td>
+								<td><a href="#datatables_update_1M">Update Entry in
+										Data Table (One to Many)</a></td>
+								<td></td>
+								<td><a href="#datatables_delete_1M">Delete Entry in
+										Data Table (One to Many)</a></td>
+							</tr>
+							<tr>
+								<td><a href="#notes">Notes</a></td>
+								<td>{resource}/{resourceId}/notes</td>
+								<td><a href="#resources_addnote">Add a Resource Note</a></td>
+								<td><a href="#resource_notelist">Retrieve a Resource's Notes</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>{resource}/{resourceId}/notes/{noteId}</td>
+								<td></td>
+								<td><a href="#resources_retrievenote">List All
+								Notes for a Resource</a></td>
+								<td><a href="#resources_updatenote">Update a Resource Note</a></td>
+								<td><a href="#resources_deletenote">Delete a Resource Note</a></td>
+							</tr>
+							<tr>
+								<td><a href="#documents">Documents</a></td>
+								<td>{entityType}/{entityId}/documents</td>
+								<td><a href="#documents_create">Create a Document</a></td>
+								<td><a href="#documents_list">List All Document Details</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>{entityType}/{entityId}/documents/{documentId}</td>
+								<td></td>
+								<td><a href="#documents_retrieve">Retrieve a Documents Details</a></td>
+								<td><a href="#documents_update">Update a Document</a></td>
+								<td><a href="#documents_delete">Delete a Document</a></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>{entityType}/{entityId}/documents/{documentId}/attachment</td>
+								<td></td>
+								<td><a href="#documents_retrieve_file">Retrieve
+									binary file associated with a
+									Document</a>
+								</td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#search">Search</a></td>
+								<td>search</td>
+								<td></td>
+								<td><a href="#search_resource">Search Resources</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#advance_search">AdHoc Search</a></td>
+								<td>search/advance</td>
+								<td><a href="#advance_search_resource">Advance Search</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+                                 <td><a href="#survey_retrieve">Survey</a></td>
+                                 <td>survey/</td>
+                                 <td></td>
+                                 <td><a href="#survey_retrieve">List surveys</a></td>
+                                 <td></td>
+                                 <td></td>
+                             </tr>
+                             <tr>
+                                 <td></td>
+                                 <td>survey/{surveyName}</td>
+                                 <td></td>
+                                 <td><a href="#survey_details">Retrieve Survey details</a></td>
+                                 <td></td>
+                                 <td></td>
+                             </tr>
+                             <tr>
+                                 <td></td>
+                                 <td>survey/{surveyName}/{clientId}</td>
+                                 <td><a href="#survey_create">Create Entry in the survey table</a></td>
+                                 <td></td>
+                                 <td></td>
+                                 <td></td>
+                             </tr>
+						</table>
+					</div>
+				</div>
+			</div>
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Report</h2>
+				<div id="toc-menu-report" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#reports">Report</a></td>
+								<td>reports</td>
+								<td><a href="#reports_create">Create a Report</a></td>
+								<td><a href="#reports_list">List Reports</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>reports/{id}</td>
+								<td></td>
+								<td><a href="#reports_retrieve">Retrieve a Report</a></td>
+								<td><a href="#reports_update">Update a Report</a></td>
+								<td><a href="#reports_delete">Delete a Report</a></td>
+							</tr>
+							<tr>
+								<td><a href="#runreports">Run Report</a></td>
+								<td>runreports/{reportName}</td>
+								<td></td>
+								<td><a href="#report_run">Run a Report</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+						</table>
+					</div>
+				</div>
+			</div>
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Template</h2>
+				<div id="toc-menu-template" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td>clients/template</td>
+								<td><a href="#clients_template">Retrieve Client Template</a></td>
+							</tr>
+							<tr>
+								<td>groups/template</td>
+								<td><a href="#groups_template">Retrieve Group Template</a></td>
+							</tr>
+							<tr >
+								<td>loanproducts/template</td>
+								<td><a href="#loanproducts_template">Retrieve Loan Product Template</a></td>
+							</tr>
+							<tr >
+								<td>loanproducts/template?isProductMixTemplate=true</td>
+								<td><a href="#loanproductmix_template">Retrieve Loan Product Mix Template</a></td>
+							</tr>
+							<tr>
+								<td>loans/template?clientId={clientId}</td>
+								<td><a href="#loans_template">Retrieve Loan Template</a></td>
+							</tr>
+							<tr>
+								<td>loans/{loanId}/transactions/template</td>
+								<td><a href="#loans_repaymenttemplate_etc">Retrieve Loan Transaction Template</a></td>
+							</tr>
+							<tr>
+								<td>charges/template</td>
+								<td><a href="#charges_template">Retrieve Charge Template</a></td>
+							</tr>
+							<tr>
+								<td>offices/template</td>
+								<td><a href="#offices_template">Retrieve Office Template</a></td>
+							</tr>
+							<tr>
+								<td>users/template</td>
+								<td><a href="#users_template">Retrieve User Template</a></td>
+							</tr>
+							<tr>
+								<td>hooks/template</td>
+								<td><a href="#hooks_template">Retrieve Hooks Template</a></td>
+							</tr>
+	                        <tr>
+	                                <td>audits/searchtemplate</td>
+	                                <td><a href="#audits_searchtemplate">Retrieve Audit Search Template</a></td>
+	                        </tr>
+	                        <tr>
+	                                <td>makercheckers/searchtemplate</td>
+	                                <td><a href="#makercheckers_searchtemplate">Retrieve Maker Checker Search Template</a></td>
+	                        </tr>
+	                        <tr>
+                                <td>reports/template</td>
+                                <td><a href="#reports_template">Retrieve Report Template</a></td>
+	                        </tr>
+	                        <tr>
+                                <td>accountingrules/template</td>
+                                <td><a href="#accountingrules_template">Retrieve Accounting Rule Template</a></td>
+	                        </tr>
+	                        <tr >
+								<td>savingproducts/template</td>
+								<td><a href="#savingsproducts_template">Retrieve Savings Product Template</a></td>
+							</tr>
+							<tr>
+								<td>savingsaccounts/template?clientId={clientId}</td>
+								<td><a href="#savingsaccounts_template">Retrieve savings template</a></td>
+							</tr>
+							<tr>
+								<td>savingsaccounts/{accountId}/transactions/template</td>
+								<td><a href="#savingsaccounts_transactions_template">Retrieve savings account transaction template</a></td>
+							</tr>
+                            <tr>
+                                <td>standinginstructions/template</td>
+                                <td><a href="#standinginstruction_template">Retrieve Standing Instruction Template</a></td>
+                            </tr>
+                            <tr>
+                                <td>accounttransfers/template</td>
+                                <td><a href="#accounttransfers_template">Retrieve Account Transfer Template</a></td>
+                            </tr>
+						</table>
+					</div>
+				</div>
+			</div>
+
+			<!-- API documentation for SPM Framework -->
+			<div class="flybar-nav">
+				<h2 class="flybar-button">SPM</h2>
+				<div id="toc-menu-report" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#surveys">Survey</a></td>
+								<td>surveys</td>
+								<td><a href="#surveys_create">Create a Survey</a></td>
+								<td><a href="#surveys_list">List Surveys</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>surveys/{id}</td>
+								<td></td>
+								<td><a href="#surveys_retrieve">Retrieve a Survey</a></td>
+								<td></td>
+								<td><a href="#surveys_delete">Deactivate a Survey</a></td>
+							</tr>
+							<tr>
+								<td><a href="#lookuptables">Lookup Table</a></td>
+								<td>surveys/{id}/lookuptables</td>
+								<td><a href="#lookuptables_create">Create a Lookup Table entry</a></td>
+								<td><a href="#lookoptables_list">List Lookup Table entries</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>surveys/{id}/lookuptables/{id}</td>
+								<td></td>
+								<td><a href="#lookuptables_retrieve">Retrieve an Entry</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#scorecards">Scorecard</a></td>
+								<td>surveys/{id}/scorecards</td>
+								<td><a href="#scorecards_create">Create a Scorecard entry</a></td>
+								<td><a href="#scorecards_list">List Scorecard entries</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+						</table>
+					</div>
+				</div>
+			</div>
+
+			<!-- API documentation for Customer Self Service APIs -->
+			<div class="flybar-nav">
+				<h2 class="flybar-button">Self Service</h2>
+				<div id="toc-menu-report" class="flybar-menu">
+					<div class="tocMatrix">
+						<table class=matrixHeading>
+							<tr class="matrixHeadingBG">
+								<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+										<table>
+											<tr>
+												<td>RESOURCES</td>
+											</tr>
+										</table>
+									</div></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">POST</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>create/complex
+													update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">GET</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>read</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">PUT</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>update</div></td>
+										</tr>
+									</table></td>
+								<td><table>
+										<tr>
+											<td><div class="matrixHeadingVerbs">DELETE</div></td>
+										</tr>
+										<tr>
+											<td><div class=matrixHeadingCommands>delete</div></td>
+										</tr>
+									</table></td>
+							</tr>
+							<tr>
+								<td><a href="#selfbasicauth">Authentication HTTP Basic</a></td>
+								<td>self/authentication</td>
+								<td></td>
+								<td><a href="#selfbasicauth">Verify Authentication</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#selfoauth">Authentication Oauth2</a></td>
+								<td>self/userdetails</td>
+								<td></td>
+								<td><a href="#selfoauth">Fetch Authenticated User Details</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#selflistclients">Clients</a></td>
+								<td>self/clients</td>
+								<td></td>
+								<td><a href="#selflistclients">List Clients</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/clients/{clientId}</td>
+								<td></td>
+								<td><a href="#selfclient">Retrieve Client</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/clients/{clientId}/accounts</td>
+								<td></td>
+								<td><a href="#selfclientsaccounts">Retrieve Client Accounts Overview</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/clients/{clientId}/images</td>
+								<td></td>
+								<td><a href="#selfclientsimages">Retrieve Client Image</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/clients/{clientId}/charges</td>
+								<td></td>
+								<td><a href="#selfclientscharges">List Client Charges</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/clients/{clientId}/charges/{chargeId}</td>
+								<td></td>
+								<td><a href="#selfclientscharge">Retrieve a Client Charge</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/clients/{clientId}/transactions</td>
+								<td></td>
+								<td><a href="#selfclienttransactions">List Client Transactions</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/clients/{clientId}/transactions/{transactionId}</td>
+								<td></td>
+								<td><a href="#selfclienttransaction">Retrieve a Client Transaction</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#selfloan">Loans</a></td>
+								<td>self/loans/{loanId}</td>
+								<td></td>
+								<td><a href="#selfloan">Retrieve a Loan</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/loans/{loanId}/transactions/{transactionId}</td>
+								<td></td>
+								<td><a href="#selfloantransaction">Retrieve a Loan Transaction Details</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/loans/{loanId}/charges</td>
+								<td></td>
+								<td><a href="#selfloancharges">Retrieve Loan Charges</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/loans/{loanId}/charges/{chargeId}</td>
+								<td></td>
+								<td><a href="#selfloancharge">Retrieve a Loan Charge Details</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#selfsavings">Savings</a></td>
+								<td>self/savingsaccounts/{accountId}</td>
+								<td></td>
+								<td><a href="#selfsavings">Retrieve a Savings Account</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/savingsaccounts/{accountId}/transactions/{transactionId}</td>
+								<td></td>
+								<td><a href="#selfsavingstransaction">Retrieve a Savings Transaction Details</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/savingsaccounts/{accountId}/charges</td>
+								<td></td>
+								<td><a href="#selfsavingscharges">Retrieve Savings Charges</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}</td>
+								<td></td>
+								<td><a href="#selfsavingscharge">Retrieve a Savings Charge Details</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td><a href="#selftransfertemplate">Own Account Transfers</a></td>
+								<td>self/accounttransfers/template</td>
+								<td></td>
+								<td><a href="#selftransfertemplate">Account Transfer Template</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/accounttransfers</td>
+								<td><a href="#selftransfer">Account Transfer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
+						</table>
+					</div>
+				</div>
+			</div>
+
+		</div>
+
+		<div id="main-content-wrapper">
+			<a id="top" name="top" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Apache Fineract API Documentation</h3>
+					<p>Apache Fineract is a secure, multi-tenanted microfinance platform.</p>
+					<p>
+						The goal of the Apache Fineract API is to empower developers to build apps
+						on top of the Apache Fineract Platform. The <a
+							href="https://demo.openmf.org" target="_blank">reference app</a>
+						(username: mifos, password: password) works on the same demo
+						'tenant' as the interactive links in this documentation.
+					</p>
+					<p>
+						The API is organized around <a
+							href="http://en.wikipedia.org/wiki/Representational_State_Transfer"
+							target="_blank">REST</a>.
+					</p>
+					<p>
+						The API is designed to have: <br> - predictable,
+						resource-oriented URLs<br> - to use HTTP response codes to
+						indicate API errors<br> - to use built-in HTTP features, like
+						HTTP authentication and HTTP verbs, which can be understood by
+						off-the-shelf HTTP clients.
+					</p>
+					<p>
+						<a href="http://www.json.org/" target="_blank">JSON</a> is
+						returned in all responses from the API, including errors.
+					</p>
+					<p>
+						Much of the API presentation and design ideas are owed to the
+						excellent <a
+							href="http://info.apigee.com/Portals/62317/docs/web%20api.pdf"
+							target="_blank">Apigee "Web API Design" eBook/PDF</a> and the
+						very good <a href="https://stripe.com/docs/api" target="_blank">Stripe
+							API reference</a>.
+					</p>
+				</div>
+			</div>
+
+			<a id="interact" name="interact" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Try The API From Your Browser</h3>
+					<p>GET (read) examples can be run directly from this
+						documentation. It is just a matter of clicking a link. Most
+						browsers display the output on the same page (or another tab if
+						you right click and select that option). Internet Explorer will
+						probably treat the output as if you wanted to download a file. In
+						that case just elect to open the output in a text editor.</p>
+					<p>
+						If you want to check out the POST, PUT and DELETE (update)
+						examples a good approach is to take a moment to install a REST
+						plugin for your browser e.g. <a
+							href="https://addons.mozilla.org/en-US/firefox/addon/restclient/"
+							target="_blank">RESTClient</a> for FireFox
+					</p>
+					The REST plugins will allow you to
+					<ul>
+						<li class=normalli>Select the "Verb" (e.g. POST)</li>
+						<li class=normalli>Enter the resource name (e.g. offices)</li>
+						<li class=normalli>Add a header to indicate you are sending
+							JSON data as part of the request body (Content-Type:
+							application/json)</li>
+						<li class=normalli>Add a header to indicate your 'tenant'
+							(X-Mifos-Platform-TenantId: default)</li>
+						<li class=normalli>Paste the example JSON into a Request Body</li>
+						<li class=normalli>Send the Request (and receive a Response)</li>
+					</ul>
+				</div>
+			</div>
+
+			<a id="genopts" name="genopts" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Generic Options</h3>
+					<h4>Convenience Templates</h4>
+					<p>There are a list of convenience resources (see Template
+						menu option). These resources end with "/template" and can be
+						useful when building maintenance user interface screens for client
+						applications. The template data returned may consist of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>
+						Also, many "Retrieve a" type resources (<a
+							href="#clients_retrieve">Retrieve a Client</a> for example) allow
+						the parameter option "template=true". This appends any "Allowed
+						Value Lists" which can be useful when building update
+						functionality.
+					</p>
+
+					<h4>Restrict Returned Fields</h4>
+					<p>Parameter "fields={fieldlist}" can be used on GET requests
+						to restrict the fields returned.</p>
+					<p>Normal Request:</p>
+					<div class=apiClickNotPretty>offices/1</div>
+					<p></p>
+					<p>Request (restricting fields returned):</p>
+					<div class=apiClick>offices/1?fields=id,name</div>
+					<p></p>
+					<h4>Pretty JSON Formatting</h4>
+					<p>Parameter "pretty=true" can be used to display JSON from GET
+						requests in an easy-to-read format. This parameter is used in this
+						documentation.</p>
+					<p>Easy-to-read JSON output for POSTs, PUTs and DELETEs will
+						available in the REST plugin you use e.g. RESTClient for FireFox</p>
+					<p>Normal Request (with pretty printing/formatting):</p>
+					<div class=apiClick>offices/1?pretty=true</div>
+					<p></p>
+				</div>
+
+			</div>
+
+			<a id="creates_and_updates" name="creates_and_updates"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Creating and Updating</h3>
+					<p>When you want to 'Create a ...' you have to at least supply
+						the mandatory fields. The mandatory fields are listed in this
+						documentation under the relevant 'Create a ...' heading.</p>
+
+					<p>When you want to 'Update a ...' you can update individual
+						fields or a combination of fields (subject to data integrity
+						rules).</p>
+				</div>
+
+			</div>
+
+
+			<a id="dates_and_numbers" name="dates_and_numbers"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Updating Dates and Numbers</h3>
+					<h4>Dates</h4>
+					<p>
+						Dates are returned in GET requests as an array e.g. [ 2007, 4,
+						11]. However, the API accepts them as strings in POST and PUT
+						requests. If there are any dates in your POST or PUT requests, you
+						need to provide the "locale" and "dateFormat". This can be any
+						date pattern supported by <a
+							href="http://joda-time.sourceforge.net/api-release/org/joda/time/format/DateTimeFormat.html">Joda-Time</a>.
+						This capability can help you when saving data in your client
+						application as you shouldn't need to do any date format conversion
+						prior to issuing your POST or PUT request.
+					</p>
+<code>JSON examples:
+{
+	"locale": "en_US",
+	"dateFormat": "dd MMMM yyyy",
+	"openingDate": "01 July 2007"
+}
+
+{
+	"locale": "en_US",
+	"dateFormat": "yyyy-MM-dd",
+	"openingDate": "2007-03-21"
+}
+</code>
+					<h4>Numbers</h4>
+					<p>You must provide a "locale" when updating numbers. Numbers
+						are not "Ids" or "Types" but are typically money amounts or
+						percentages that relate to loans. In any case, the API will send
+						back an error message if you forget.</p>
+<code>JSON examples:
+{
+	"locale": "en_US",
+	"principal": "240,400.88"
+}
+{
+	"locale": "fr_CH",
+	"principal": "240 400.88"
+}
+</code>
+				</div>
+
+			</div>
+
+			<a id="field_descriptions" name="field_descriptions"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Field Descriptions</h3>
+					<p>
+						Most fields are self-explanatory. Fields that aren't are described
+						under the relevant resource heading e.g. <a href="#authentication">AUTHENTICATION</a>
+					</p>
+
+				</div>
+
+			</div>
+
+			<a id="authentication_overview" name="authentication_overview"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Authentication Overview</h3>
+					<p>
+						Authentication to the API can be configured to be supported via <a
+							href="http://en.wikipedia.org/wiki/Basic_access_authentication">HTTP
+							Basic Auth</a> or <a href="http://tools.ietf.org/html/rfc6749">OAuth2</a>.
+					</p>
+
+					<p>
+						Default authentication is using HTTP Basic Auth. Oauth2 can be enabled by using <i>-Psecurity=oauth</i> option on gradle build command , refer
+						the platform setup <a href="https://github.com/openMF/mifosx/wiki/Launching-platform-server-locally-from-the-command-line#choosing-authentication-mechanism"> wiki</a> for additional details.
+					</p>
+					<p>
+						The platform has been configured to reject plain HTTP requests and
+						to expect all API requests to be made over <a
+							href="http://en.wikipedia.org/wiki/HTTP_Secure">HTTPS</a>. All
+						requests must be authenticated.
+					</p>
+				</div>
+				<div class="method-example">
+
+					</div>
+			</div>
+
+			<a id="authentication_basicauth" name="authentication_basicauth"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Authentication HTTP Basic</h3>
+					<p>
+						Authentication to the API occurs via <a
+							href="http://en.wikipedia.org/wiki/Basic_access_authentication">HTTP
+							Basic Auth</a>.
+					</p>
+
+				</div>
+				<div class="method-example">
+<code>
+// A Javascript/Jquery example of how to login to Apache Fineract and use its api.
+// Typically, the javascript application would
+// 1) Display a login page to retrieve the username and password.
+// 2) Send the username and password to a function
+//    such as setBasicAuthKey below which sets the HTTP Basic Auth key.
+// 3) The HTTP Basic Auth key is used in all subsequent requests
+// (see the function executeAjaxRequest below).
+
+function setBasicAuthKey(username, password) {
+
+	var jqxhr = $.ajax({
+		url : "authentication?username=" + username + "&password=" + password,
+		type : 'POST',
+		contentType : "application/json; charset=utf-8",
+		dataType : 'json',
+		data : "{}",
+		cache : false,
+		success : function(data, textStatus, jqXHR) {
+			basicAuthKey = data.base64EncodedAuthenticationKey;
+		},
+		error : function(jqXHR, textStatus, errorThrown) {
+			//error processing
+		}
+	});
+}
+
+function executeAjaxRequest(url, verbType, jsonData, basicAuthKey, successFunction, errorFunction) {
+	var jqxhr = $.ajax({
+		url : url,
+		type : verbType, //POST, GET, PUT or DELETE
+		contentType : "application/json; charset=utf-8",
+		dataType : 'json',
+		data : jsonData,
+		cache : false,
+		beforeSend : function(xhr) {
+			xhr.setRequestHeader("Authorization", "Basic " + basicAuthKey);
+		},
+		success : successFunction,
+		error : errorFunction
+	});
+}
+</code>
+					</div>
+			</div>
+
+			<a id="authentication_oauth" name="authentication_oauth"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Authentication Oauth2</h3>
+					<p>
+						Authentication to the API occurs via <a href="http://tools.ietf.org/html/rfc6749">OAuth2</a>. 'Resource Owner Password Credentials Grant' type is used.
+					</p>
+
+				</div>
+				<div class="method-example">
+<code>
+// A Javascript/Jquery example of how to login to Apache Fineract and use its api.
+// Typically, the javascript application would
+// 1) Display a login page to retrieve the username and password.
+// 2) Send the username, password, client_id , grant_type and client_secret to a function
+//    such as getOauthToken below which sets the HTTP bearer Auth key.
+// 3) The HTTP bearer Auth key is used in all subsequent requests
+// (see the function executeAjaxRequest below).
+
+function getOauthToken(username, password) {
+
+	var jqxhr = $.ajax({
+		url : "/fineract-provider/api/oauth/token?username=" + credentials.username + "&password=" + credentials.password +"&client_id=community-app&grant_type=password&client_secret=123,
+		type : 'POST',
+		contentType : "application/json; charset=utf-8",
+		dataType : 'json',
+		data : "{}",
+		cache : false,
+		success : function(data, textStatus, jqXHR) {
+			authKey = data.access_token;
+		},
+		error : function(jqXHR, textStatus, errorThrown) {
+			//error processing
+		}
+	});
+}
+
+function executeAjaxRequest(url, verbType, jsonData, authKey, successFunction, errorFunction) {
+	var jqxhr = $.ajax({
+		url : url,
+		type : verbType, //POST, GET, PUT or DELETE
+		contentType : "application/json; charset=utf-8",
+		dataType : 'json',
+		data : jsonData,
+		cache : false,
+		beforeSend : function(xhr) {
+			xhr.setRequestHeader("Authorization", "bearer " + authKey);
+		},
+		success : successFunction,
+		error : errorFunction
+	});
+}
+</code>
+					</div>
+			</div>
+
+			<a id="batch_api" name="batch_api" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Batch API</h3>
+
+					<p>
+						The Apache Fineract Batch API enables a consumer to access significant amounts of data in a single call or to make changes to several objects at once.
+						Batching allows a consumer to pass instructions for several operations in a  single HTTP request. A consumer can also specify dependencies between related operations. Once all operations have been completed, a consolidated response will be  passed back and the HTTP connection will be closed.
+					</p>
+
+					<p>
+						The Batch API takes in an array of logical HTTP requests represented as JSON arrays - each request has a requestId (the id of a request used to specify the sequence and as a dependency between requests), a method (corresponding to HTTP method GET/PUT/POST/DELETE etc.), a relativeUrl (the portion of the URL after https://example.org/api/v2/), optional headers array (corresponding to HTTP headers), optional reference parameter if a request is dependent on another request and an optional body (for POST and PUT requests).
+						The Batch API returns an array of logical HTTP responses represented as JSON arrays - each response has a requestId, a status code, an optional headers array and an optional body (which is a JSON encoded string).
+					</p>
+
+					<p>
+						Batch API uses <a href=https://code.google.com/p/json-path/>Json Path</a> to handle dependent
+						parameters. For example, if request '2' is referencing request '1' and in the "body" or in "relativeUrl" of request '2', there is a dependent parameter (which will look like "$.parameter_name"), then Batch API will internally substitute this dependent parameter from the response body of request '1'.
+					</p>
+
+					<p>
+						Batch API is able to handle deeply nested dependent requests as well nested parameters. As shown in the example, requests are dependent on each other as, 1&lt--2&lt--6, i.e a nested dependency, where request '6' is not directly dependent on request '1' but still it is one of the nested child of request '1'.
+						In the same way Batch API could handle a deeply nested dependent value, such as {..[..{..,$.parameter_name,..}..]}.
+					</p>
+
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/batches
+					</code>
+					<code class="method-request">
+POST batches
+Content-Type: application/json Request Body:
+[
+   {
+      "requestId":1,
+      "relativeUrl":"clients",
+      "method":"POST",
+      "headers":[
+         {
+            "name":"Content-type",
+            "value":"text/html"
+         }
+      ],
+      "body":"{
+	    \"officeId\": 1,
+	    \"firstname\": \"Petra\",
+	    \"lastname\": \"Yton\",
+	    \"externalId\": \"ex_externalId1\",
+	    \"dateFormat\": \"dd MMMM yyyy\",
+	    \"locale\": \"en\",
+	    \"active\": true,
+	    \"activationDate\": \"04 March 2009\",
+	    \"submittedOnDate\": \"04 March 2009\"
+   	 }"
+   },
+   {
+      "requestId":2,
+      "relativeUrl":"loans",
+      "method":"POST",
+      "headers":[
+         {
+            "name":"Content-type",
+            "value":"text/html"
+         }
+      ],
+      "reference":1,
+      "body":"{ \"dateFormat\": \"dd MMMM yyyy\",
+                \"locale\": \"en_GB\",
+                \"clientId\": \"$.clientId\",
+                \"productId\": 26,
+                \"principal\": \"10,000.00\",
+                \"loanTermFrequency\": 12,
+                \"loanTermFrequencyType\": 2,
+                \"loanType\": \"individual\",
+                \"numberOfRepayments\": 10,
+                \"repaymentEvery\": 1,
+                \"repaymentFrequencyType\": 2,
+                \"interestRatePerPeriod\": 10,
+                \"amortizationType\": 1,
+                \"interestType\": 0,
+                \"interestCalculationPeriodType\": 1,
+                \"transactionProcessingStrategyId\": 1,
+                \"expectedDisbursementDate\": \"10 Jun 2013\",
+                \"submittedOnDate\": \"10 Jun 2013\"
+              }"
+   },
+   {
+      "requestId":3,
+      "relativeUrl":"loans/$.loanId/charges",
+      "method":"POST",
+      "reference":2,
+      "headers":[
+         {
+            "name":"Content-type",
+            "value":"text/html"
+         }
+      ],
+      "body":"{
+              \"chargeId\": \"2\",
+              \"locale\": \"en\",
+              \"amount\": \"100\",
+              \"dateFormat\": \"dd MMMM yyyy\",
+              \"dueDate\": \"29 April 2013\"
+            }"
+   },
+   {
+      "requestId":4,
+      "relativeUrl":"loans/$.loanId/charges",
+      "method":"GET",
+      "reference":2,
+      "headers":[
+         {
+            "name":"Content-type",
+            "value":"text/html"
+         }
+      ],
+      "body":"{}"
+   }
+]
+					</code>
+					<code class="method-response">
+[
+   {
+      "requestId":1,
+      "statusCode":200,
+      "headers":[
+         {
+            "name":"Content-type",
+            "value":"text/html"
+         },
+         {
+            "name":"X-Mifos-Platform-TenantId",
+            "value":"text/html"
+         }
+      ],
+      "body":"{\"officeId\":1,\"clientId\":909,\"resourceId\":909}"
+   },
+   {
+      "requestId":2,
+      "statusCode":200,
+      "headers":[
+         {
+            "name":"Content-type",
+            "value":"text/html"
+         }
+      ],
+      "body":"{\"officeId\":1,\"clientId\":909,\"loanId\":212,\"resourceId\":212}"
+   },
+   {
+      "requestId":3,
+      "statusCode":200,
+      "headers":[
+         {
+            "name":"Content-type",
+            "value":"text/html"
+         }
+      ],
+      "body":"{\"officeId\":1,\"clientId\":909,\"loanId\":212,\"resourceId\":155}"
+   },
+   {
+      "requestId":4,
+      "statusCode":200,
+      "headers":[
+         {
+            "name":"Content-type",
+            "value":"text/html"
+         }
+      ],
+      "body":"[
+	   {
+	      \"id\":155,
+	      \"chargeId\":2,
+	      \"name\":\"Charge_Loans_GEQJC5\",
+	      \"chargeTimeType\":{
+	         \"id\":1,
+	         \"code\":\"chargeTimeType.disbursement\",
+	         \"value\":\"Disbursement\"
+	      },
+	      \"chargeCalculationType\":{
+	         \"id\":2,
+	         \"code\":\"chargeCalculationType.percent.of.amount\",
+	         \"value\":\"% Amount\"
+	      },
+	      \"percentage\":100.000000,
+	      \"amountPercentageAppliedTo\":10000.000000,
+	      \"currency\":{
+	         \"code\":\"USD\",
+	         \"name\":\"USDollar\",
+	         \"decimalPlaces\":2,
+	         \"displaySymbol\":\"$\",
+	         \"nameCode\":\"currency.USD\",
+	         \"displayLabel\":\"US Dollar ($)\"
+	      },
+	      \"amount\":10000.000000,
+	      \"amountPaid\":0,
+	      \"amountWaived\":0,
+	      \"amountWrittenOff\":0,
+	      \"amountOutstanding\":10000.000000,
+	      \"amountOrPercentage\":100.000000,
+	      \"penalty\":false,
+	      \"chargePaymentMode\":{
+	         \"id\":0,
+	         \"code\":\"chargepaymentmode.regular\",
+	         \"value\":\"Regular\"
+	      },
+	      \"paid\":false,
+	      \"waived\":false,
+	      \"chargePayable\":false
+	   }
+	]"
+   }
+]
+					</code>
+				</div>
+
+			</div>
+
+			<a class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Batch requests in a single transaction</h4>
+
+					<p>
+						The Apache Fineract Batch API is also capable of executing all the requests in a single transaction, by setting a Query Parameter, "enclosingTransaction=true". So, if one or more of the requests in a batch returns an erroneous response all of the Data base transactions made by other successful requests will be rolled back.
+					</p>
+
+					<p>
+						If there has been a rollback in a transaction then a single response will be provided, with a '400' status code and a body consisting of the error details of the first failed request.
+					</p>
+
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/batches?enclosingTransaction=true
+					</code>
+					<code class="method-request">
+POST batches
+Content-Type: application/json Request Body:
+[
+  {
+    "requestId":1,
+    "relativeUrl":"clients",
+    "method":"POST",
+    "headers":[
+      {
+        "name":"Content-type",
+        "value":"text/html"
+      },
+      {
+        "name":"X-Mifos-Platform-TenantId",
+        "value":"text/html"
+      }
+    ],
+    "body":"{
+    	 \"officeId\": 1,
+    	 \"firstname\": \"Petra\",
+    	 \"lastname\": \"Yton\",
+    	 \"externalId\": \"externalId_4\",
+    	 \"dateFormat\": \"dd MMMM yyyy\",
+    	 \"locale\": \"en\", \"active\": true,
+    	 \"activationDate\": \"04 March 2009\",
+    	 \"submittedOnDate\": \"04 March 2009\"
+    	 }"
+  },
+  {
+    "requestId":2,
+    "relativeUrl":"savingsaccounts",
+    "method":"POST",
+    "reference":1,
+    "headers":[
+      {
+        "name":"Content-type",
+        "value":"text/html"
+      }
+    ],
+    "body":"{
+              \"clientId\": \"$.clientId\",
+              \"productId\": 1,
+              \"locale\": \"en\",
+              \"dateFormat\": \"dd MMMM yyyy\",
+              \"submittedOnDate\": \"01 March 2011\"
+            }"
+  }
+]
+					</code>
+					<code class="method-response">
+Successful transaction response:
+[
+  {
+    "requestId":1,
+    "statusCode":200,
+    "headers":[
+      {
+        "name":"Content-type",
+        "value":"text/html"
+      },
+      {
+        "name":"X-Mifos-Platform-TenantId",
+        "value":"text/html"
+      }
+    ],
+    "body":"{\"officeId\":1,\"clientId\":922,\"resourceId\":922}"
+  },
+  {
+    "requestId":2,
+    "statusCode":200,
+    "headers":[
+      {
+        "name":"Content-type",
+        "value":"text/html"
+      }
+    ],
+    "body":"{\"officeId\":1,\"clientId\":922,\"savingsId\":116,\"resourceId\":116}"
+  }
+]
+					</code>
+
+				</div>
+
+			</div>
+
+			<a class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Batch API Errors</h4>
+
+					<p>
+						In Batch API without "enclosingTransaction=true", if one of the response is erroneous, then an appropriate status code will be set for that request and the error message will be returned in it's "body", while all other requests will return successful response with a status code of "200".
+					</p>
+
+				</div>
+			</div>
+
+			<a class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Available Command Strategies</h4>
+
+					<p>
+						These are the currently available command strategies within the Mifos Batch API. So, these listed operations can be executed using a Batch Request.
+					</p>
+
+				</div>
+				<div class="method-example">
+					<h6>Batch API - available commands</h6>
+					<ul>
+						<li><a href="#clients_create">Create a new Client</a>
+						<li><a href="#clients_update">Update an existing Client</a>
+						<li><a href="#loans_create">Apply a Loan to a Client</a>
+						<li><a href="#savingsaccounts_create">Apply Savings to a Client</a>
+						<li><a href="#loans_charges_create">Add a new Loan Charge</a>
+						<li><a href="#loans_charges_retrieve">Collect an existing Loan Charge</a>
+						<li><a href="#clients_activate">Activate a Pending Client</a>
+						<li><a href="#loans_approve">Approve a Pending Loan</a>
+						<li><a href="#loans_disburse">Disburse a Loan</a>
+					</ul>
+				</div>
+			</div>
+
+			<a id="errors" name="errors" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Errors</h3>
+
+					<p>All errors are returned in JSON.</p>
+
+				</div>
+				<div class="method-example">
+					<h6>HTTP Status Code Summary</h6>
+					<ul>
+						<li><strong>200</strong> OK - Everything Worked.</li>
+						<li><strong>400</strong> Bad Request - Invalid Parameter or
+							Data Integrity Issue.</li>
+						<li><strong>401</strong> Authentication Error.</li>
+						<li><strong>403</strong> Unauthorized Request.</li>
+						<li><strong>404</strong> Resource Not Found</li>
+						<li><strong>500</strong> Platform Internal Server Error.</li>
+					</ul>
+				</div>
+				<div class="method-example">
+<code class="method-response">
+<b>Error Message returned when attempting to create an Office without passing any parameters</b>
+{
+"developerMessage": "The request was invalid. This typically will happen due to validation errors which are provided.",
+"developerDocLink": "https://github.com/openMF/mifosx/wiki/HTTP-API-Error-codes",
+"httpStatusCode": "400",
+"defaultUserMessage": "Validation errors exist.",
+"userMessageGlobalisationCode": "validation.msg.validation.errors.exist",
+"errors": [
+	{
+	"developerMessage": "The parameter name cannot be blank.",
+	"defaultUserMessage": "The parameter name cannot be blank.",
+	"userMessageGlobalisationCode": "validation.msg.office.name.cannot.be.blank",
+	"parameterName": "name",
+	"value": null,
+	"args": []
+	},
+	{
+	"developerMessage": "The parameter openingDate cannot be blank.",
+	"defaultUserMessage": "The parameter openingDate cannot be blank.",
+	"userMessageGlobalisationCode": "validation.msg.office.openingDate.cannot.be.blank",
+	"parameterName": "openingDate", "value": null, "args": []
+	},
+	{
+	"developerMessage": "The parameter parentId cannot be blank.",
+	"defaultUserMessage": "The parameter parentId cannot be blank.",
+	"userMessageGlobalisationCode":
+	"validation.msg.office.parentId.cannot.be.blank", "parameterName":
+	"parentId", "value": null, "args": []
+	}
+]
+}
+</code>
+				</div>
+			</div>
+			<a id="selfservice_overview" name="selfservice_overview"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Self Service API Overview</h3>
+					<p>
+					Self Service APIs are a set of APIs with restricted data scope. Functional specifications and design can be viewed
+					<a href="https://mifosforge.jira.com/wiki/display/MIFOSX/Customer+Self+Service">here</a>.
+					</p>
+
+					<p>
+					While creating an <a href="#users_create">user</a>, user can be tagged as self service user. Also you can associate
+					clients that this user has access to. Data scope is restricted to these linked clients.
+					</p>
+					<p>
+					A self service user shall have access to only self service APIs. Self service APIs cannot be accessed by non-self service user. Vice-versa is also true.
+					</p>
+				</div>
+				<div class="method-example">
+
+					</div>
+			</div>
+			<!-- start of clients api docs -->
+			<a id="clients" name="clients" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Clients</h3>
+					<p>Clients are people and businesses that have applied (or may apply) to an MFI for loans.</p>
+					<p>Clients can be created in <b>Pending</b> or straight into <b>Active</b> state.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>accountNo</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>If provided during client creation, its
+								value is set as account no. for client account, otherwise an
+								auto generated account no. is put in place based on the
+								configured strategy.</td>
+						</tr>
+						<tr class=alt>
+							<td>externalId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A place to put an external reference for
+								this client e.g. The ID another system uses.<br> If
+								provided, it must be unique.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>active</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Indicates whether this client is to be created as <b>active</b> client. If active=true, then <b>activationDate</b> must be provided. If active=false, then the client is created as <b>pending</b>.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>activationDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The date on which the client became <b>active</b>.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>firstname</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Facility to break up name into parts
+								suitable for humans.</td>
+						</tr>
+						<tr class=alt>
+							<td>middlename</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Facility to break up name into parts
+								suitable for humans.</td>
+						</tr>
+						<tr class=alt>
+							<td>lastname</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Facility to break up name into parts
+								suitable for humans.</td>
+						</tr>
+						<tr class=alt>
+							<td>fullname</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Facility to set name of a client or
+								business that doesn't suit the firstname,middlename,lastname
+								structure.</td>
+						</tr>
+						<tr class=alt>
+							<td>mobileNo</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: unique mobile number that is used by SMS or Mobile Money functionality.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>staffId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The staffId of the staff member dealing with the client office. The staff member is not specifically the loan officer.</td>
+						</tr>
+						<tr class=alt>
+							<td>savingsProductId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Default overdraft savings account of client
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="clients_template" name="clients_template" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Client Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>officeId</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dt>staffInSelectedOfficeOnly</dt>
+						<dd>
+							Boolean <span>optional</span>
+						</dd>
+						<dd>Defaults to false if not provided. If <strong>staffInSelectedOfficeOnly=true</strong> only staff
+						who are associated with the selected branch are returned.</dd>
+						<dt>commandParam</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>If <strong>commandParam=close</strong> retrieves all closureReasons which are associated with
+						<strong>"ClientClosureReason"</strong> value.</dd>
+					</dl>
+					<p>Example Request:</p>
+					<div class=apiClick>clients/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/template
+					</code>
+					<code class="method-response">
+{
+	"activationDate":[2014,3,4],
+	"officeId":1,
+	"officeOptions":[{
+		"id":1,
+		"name":"Head Office",
+		"nameDecorated":"Head Office"
+	}],
+	"staffOptions":[{
+		"id":1,
+		"firstname":"xyz",
+		"lastname":"sjs",
+		"displayName":"sjs, xyz",
+		"officeId":1,
+		"officeName":"Head Office",
+		"isLoanOfficer":true,
+		"isActive":true
+	}],
+	"savingProductOptions":[{
+		"id":4,
+		"name":"account overdraft",
+		"withdrawalFeeForTransfers":false,
+		"allowOverdraft":false
+	}]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="clients_create" name="clients_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Client</h4>
+					<p>
+						<b>Note:</b> You can enter either:<br>
+						firstname/middlename/lastname - for a person (middlename is
+						optional) OR<br> fullname - for a business or organisation
+						(or person known by one name).<br>
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>firstname and lastname OR fullname,<br> officeId,<br> active=true and activationDate OR active=false,<br>
+							</td>
+						</tr>
+					</table>
+						<br />
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>groupId, externalId, accountNo, staffId, mobileNo, savingsProductId, genderId, clientTypeId, clientClassificationId</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/clients
+					</code>
+					<code class="method-request">
+POST clients
+Content-Type: application/json Request Body:
+{
+	"officeId": 1,
+	"firstname": "Petra",
+	"lastname": "Yton",
+	"externalId": "786YYH7",
+	"dateFormat": "dd MMMM yyyy",
+	"locale": "en",
+	"active": true,
+	"activationDate": "04 March 2009",
+    "submittedOnDate":"04 March 2009",
+    "savingsProductId" : 4
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "clientId": 1,
+    "resourceId": 1,
+    "savingsId": 10
+}
+					</code>
+				</div>
+				<div class="method-example">
+					<code class="method-request">
+POST clients
+Content-Type: application/json
+Request Body:
+{
+	"officeId": 1,
+	"fullname": "Client of group",
+	"groupId": 1,
+	"dateFormat": "dd MMMM yyyy",
+	"locale": "en",
+	"active": true,
+	"activationDate": "04 March 2009"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 1,
+  "clientId": 2,
+  "resourceId": 2
+}
+					</code>
+				</div>
+			</div>
+
+	<a id="clients_activate" name="clients_activate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Activate a Client</h4>
+	        <p>Clients can be created in a <i>Pending</i> state. This API exists to enable client activation (for when a client becomes an approved member of the financial Institution).</p>
+	        <p>If the client happens to be already active this API will result in an error.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=activate</code>
+	        <code class="method-request">POST clients/1?command=activate
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "activationDate": "01 March 2011"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="clients_close" name="clients_close" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Close a Client</h4>
+	        <p>Clients can be closed if they do not have any non-closed loans/savingsAccount. This API exists to close a client .</p>
+	        <p>If the client have any active loans/savingsAccount this API will result in an error.</p>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=close</code>
+	        <code class="method-request">POST clients/1?command=close
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat":"dd MMMM yyyy",
+  "locale":"en",
+  "closureDate":"25 June 2013",
+  "closureReasonId":"11"
+}
+			</code>
+	        <code class="method-response">
+{
+  "clientId":15,
+  "resourceId":15
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="clients_reject" name="clients_reject" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Reject a Client</h4>
+	        <p>Clients can be rejected when client is in pending for activation status.</p>
+	        <p>If the client is any other status, this API throws an error.</p>
+	        <table class=matrixHeading>
+				<tr class="matrixHeadingBG">
+					<td><div class="fineractHeading2">Mandatory Fields</div></td>
+				</tr>
+				<tr class=alt>
+					<td>rejectionDate, rejectionReasonId<br>
+					</td>
+				</tr>
+			</table>
+	    </div>
+
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=reject</code>
+	        <code class="method-request">POST clients/1?command=reject
+Content-Type: application/json
+Request Body:
+{
+"rejectionDate":"28 November 2014",
+"rejectionReasonId":16,
+"locale":"en",
+"dateFormat":"dd MMMM yyyy"
+}
+			</code>
+	        <code class="method-response">
+{
+  "clientId":15,
+  "resourceId":15
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="clients_withdraw" name="clients_withdraw" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Withdraw a Client</h4>
+	        <p>Client applications can be withdrawn when client is in a pending for activation status.</p>
+	        <p>If the client is any other status, this API throws an error.</p>
+	        <table class=matrixHeading>
+				<tr class="matrixHeadingBG">
+					<td><div class="fineractHeading2">Mandatory Fields</div></td>
+				</tr>
+				<tr class=alt>
+					<td>withdrawalDate, withdrawalReasonId<br>
+					</td>
+				</tr>
+			</table>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=withdraw</code>
+	        <code class="method-request">POST clients/1?command=withdraw
+Content-Type: application/json
+Request Body:
+{
+"withdrawalDate":"28 November 2014",
+"withdrawalReasonId":17,
+"locale":"en",
+"dateFormat":"dd MMMM yyyy"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId":1,
+  "clientId":15,
+  "resourceId":15
+}
+	        </code>
+	    </div>
+	</div>
+
+
+	<a id="clients_reactivate" name="clients_reactivate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Reactivate a Client</h4>
+	        <p>Clients can be reactivated after they have been closed.</p>
+	        <p>Trying to reactivate a client in any other state throws an error.</p>
+	         <table class=matrixHeading>
+				<tr class="matrixHeadingBG">
+					<td><div class="fineractHeading2">Mandatory Fields</div></td>
+				</tr>
+				<tr class=alt>
+					<td>reactivationDate<br>
+					</td>
+				</tr>
+			</table>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=reactivate</code>
+	        <code class="method-request">POST clients/1?command=reactivate
+Content-Type: application/json
+Request Body:
+{
+"reactivationDate":"28 November 2014",
+"locale":"en",
+"dateFormat":"dd MMMM yyyy"
+}
+			</code>
+	        <code class="method-response">
+{
+
+  "clientId":15,
+  "resourceId":15
+}
+	        </code>
+	    </div>
+	</div>
+		<a id="clients_assignStaff" name="clients_assignStaff" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Assign a Staff</h4>
+	        <p>Allows you to assign a <i>Staff</i> for existed Client.</p>
+	        <p>The selected Staff should belong to the same office (or an officer higher up in the hierarchy)
+	        as the Client he manages.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=assignStaff</code>
+	        <code class="method-request">POST clients/1?command=assignStaff
+Content-Type: application/json
+Request Body:
+{
+  "staffId": "1"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "resourceId": 1,
+  "changes": {"staffId":1}
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="clients_unassignStaff" name="clients_unassignStaff" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Unassign a Staff</h4>
+	        <p>Allows you to unassign the <i>Staff</i> assigned to a Client.</p>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=unassignStaff</code>
+	        <code class="method-request">POST clients/1?command=unassignStaff
+Content-Type: application/json
+Request Body:
+{
+  "staffId":"1"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "resourceId": 1,
+  "changes": {"staffId":1}
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="clients_updateSavingsAccount" name="clients_updateSavingsAccount" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Update Default Savings Account</h4>
+	        <p>Allows you to modify or assign a default savings account for an existing Client.</p>
+	        <p>The selected savings account should be one among the existing savings account for a particular customer.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=updateSavingsAccount</code>
+	        <code class="method-request">POST clients/1?command=updateSavingsAccount
+Content-Type: application/json
+Request Body:
+{
+  "savingsAccountId": "22"
+}
+			</code>
+	        <code class="method-response">
+{
+	"officeId":1,
+	"clientId":1,
+	"resourceId":1
+	,"changes":{
+		"savingsAccountId":22
+	}
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="clients_propose_transfer" name="clients_propose_transfer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Propose a Client Transfer</h4>
+	        <p>Allows you to propose the transfer of a <i>Client</i> to a different <i>Office</i>.</p>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=proposeTransfer</code>
+	        <code class="method-request">POST clients/1?command=proposeTransfer
+Content-Type: application/json
+Request Body:
+{
+  "destinationOfficeId":"2",
+  "note":"Client Relocating to Bangalore"
+}
+			</code>
+	        <code class="method-response">
+{
+   "clientId": 2,
+   "resourceId": 2
+}
+
+
+	        </code>
+	    </div>
+	</div>
+
+	<a id="clients_withdraw_transfer" name="clients_withdraw_transfer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Withdraw a Client Transfer</h4>
+	        <p>Allows you to withdraw the proposed transfer of a <i>Client</i> to a different <i>Office</i>.</p>
+	        <p>Withdrawal can happen only if the destination Branch (to which the transfer was
+	        <a href="#clients_propose_transfer">proposed</a>) has not already
+	         <a href="#clients_accept_transfer">accepted</a> the transfer proposal </p>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=withdrawTransfer</code>
+	        <code class="method-request">POST clients/1?command=withdrawTransfer
+Content-Type: application/json
+Request Body:
+{
+	"note":"Sorry, data entry error"
+}
+			</code>
+	        <code class="method-response">
+{
+   "clientId": 2,
+   "resourceId": 2
+}
+
+
+	        </code>
+	    </div>
+	</div>
+
+	<a id="clients_reject_transfer" name="clients_reject_transfer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Reject a Client Transfer</h4>
+	        <p>Allows the Destination Branch to reject the <a href="#clients_propose_transfer">proposed</a> Client Transfer.</p>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=rejectTransfer</code>
+	        <code class="method-request">POST clients/1?command=rejectTransfer
+Content-Type: application/json
+Request Body:
+{
+	"note":"We cannot accept tranfers of clients having loans with less than 1 repayment left"
+}
+			</code>
+	        <code class="method-response">
+{
+   "clientId": 2,
+   "resourceId": 2
+}
+
+
+	        </code>
+	    </div>
+	</div>
+
+	<a id="clients_accept_transfer" name="clients_accept_transfer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Accept a Client Transfer</h4>
+	        <p>Allows the Destination Branch to accept the <a href="#clients_propose_transfer">proposed</a> Client Transfer.</p>
+	        <p>The destination branch may also choose to link this client to a group (in which case, any existing active JLG loan of the client is rescheduled to match the meeting frequency of the group) and loan Officer at the time of accepting
+	        the transfer</p>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=acceptTransfer</code>
+	        <code class="method-request">POST clients/1?command=acceptTransfer
+Content-Type: application/json
+Request Body:
+{
+   "destinationGroupId":"13",
+   "staffId":"1",
+   "note":"Due Diligence done and all documents received"
+}
+			</code>
+	        <code class="method-response">
+{
+   "clientId": 2,
+   "resourceId": 2
+}
+
+
+	        </code>
+	    </div>
+	</div>
+
+
+	<a id="clients_propose_and_accept_transfer" name="clients_propose_and_accept_transfer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Propose and Accept a Client Transfer</h4>
+	        <p>Abstraction over the <a href="#clients_propose_transfer">Propose</a> and <a href="#clients_propose_transfer">Accept</a> <i>Client Transfer</i> API's which enable a user with Data Scope over both the Target and Destination Branches to directly
+	        transfer a Client to the destination <i>Office</i>.</p>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=proposeAndAcceptTransfer</code>
+	        <code class="method-request">POST clients/1?command=proposeTransfer
+Content-Type: application/json
+Request Body:
+{
+  "destinationOfficeId":"2",
+  "destinationGroupId":"13",
+  "staffId":"1",
+  "note":"Client Relocating to Bangalore"
+}
+			</code>
+	        <code class="method-response">
+{
+   "clientId": 2,
+   "resourceId": 2
+}
+
+
+	        </code>
+	    </div>
+	</div>
+
+
+			<a id="clients_retrieve" name="clients_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Client</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1</div>
+					<br>
+					<br>
+					<div class=apiClick>clients/1?template=true</div>
+					<br>
+					<br>
+					<div class=apiClick>clients/1?fields=id,displayName,officeName</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}
+					</code>
+					<code class="method-response">
+{
+  "id": 27,
+  "accountNo": "000000027",
+  "status": {
+    "id": 300,
+    "code": "clientStatusType.active",
+    "value": "Active"
+  },
+  "active": true,
+  "activationDate": [
+    2013,
+    1,
+    1
+  ],
+  "firstname": "savings",
+  "lastname": "test",
+  "displayName": "savings test",
+  "officeId": 1,
+  "officeName": "Head Office",
+  "timeline": {
+    "submittedOnDate": [
+      2013,
+      1,
+      1
+    ],
+    "submittedByUsername": "mifos",
+    "submittedByFirstname": "App",
+    "submittedByLastname": "Administrator",
+    "activatedOnDate": [
+      2013,
+      1,
+      1
+    ],
+    "activatedByUsername": "mifos",
+    "activatedByFirstname": "App",
+    "activatedByLastname": "Administrator"
+  },
+  "savingsProductId": 4,
+  "savingsProductName": "account overdraft",
+  "groups": []
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="client_list" name="clients_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Clients</h4>
+					<p>The <i>list</i> capability of clients can support <b>pagination</b> and <b>sorting</b>.</p>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>offset</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 0
+						</dd>
+						<dd>Indicates the result from which pagination starts</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+						<dt>orderBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>displayName, accountNo, officeId, officeName</span>
+						</dd>
+						<dd>Orders results by the indicated field.</dd>
+
+						<dt>sortBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>ASC, DESC</span>
+						</dd>
+						<dd>Indicates what way to order results if <i>orderBy</i> is used.</dd>
+
+						<dt>officeId</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dd>Provides the ability to restrict list of clients returned based on the office they are associated with.</dd>
+						<dt>underHierarchy</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use the office hierarchy string to return all clients under a given hierarchy.</dd>
+
+						<dt>displayName</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use displayName of clients to restrict results.</dd>
+
+						<dt>firstName</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use firstName of clients to restrict results.</dd>
+
+						<dt>lastName</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use lastName of clients to restrict results.</dd>
+
+						<dt>externalId</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use externalId of clients to restrict results.</dd>
+
+						<dt>sqlSearch</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use an sql fragment valid for the underlying client schema to filter results. e.g. display_name like %K%</dd>
+						<dt>orphansOnly</dt>
+						<dd>
+							Boolean <span>optional</span>, defaults to false
+						</dd>
+						<dd>Use orphansOnly as true to list clients which are not associated to any group/parent.</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients</div>
+					<br>
+					<div class=apiClick>clients?fields=displayName,officeName,timeline</div>
+					<br>
+					<div class=apiClick>clients?offset=10&limit=50</div>
+					<br>
+					<div class=apiClick>clients?orderBy=displayName&sortOrder=DESC</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients
+					</code>
+					<code class="method-response">
+{
+  "totalFilteredRecords": 2,
+  "pageItems": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "status": {
+        "id": 300,
+        "code": "clientStatusType.active",
+        "value": "Active"
+      },
+      "active": true,
+      "activationDate": [
+        2013,
+        3,
+        1
+      ],
+      "fullname": "Small shop",
+      "displayName": "Small shop",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 2,
+      "accountNo": "000000002",
+      "status": {
+        "id": 100,
+        "code": "clientStatusType.pending",
+        "value": "Pending"
+      },
+      "active": false,
+      "fullname": "Home Farm Produce",
+      "displayName": "Home Farm Produce",
+      "officeId": 1,
+      "officeName": "Head Office"
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="clients_update" name="clients_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Client</h4>
+					<p><b>Note:</b>You can update any of the basic attributes of a client (but not its associations) using this API. </p>
+					<p>Changing the relationship between a client and its office is not supported through this API. An API specific to handling <b>transfers</b> of clients between offices is available for the same.</p>
+					<p>The relationship between a client and a group must be removed through the <b>Groups</b> API.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/clients/{clientId}
+						</code>
+					<code class="method-request">
+PUT clients/1
+Content-Type: application/json
+Request Body:
+{
+    "externalId": "786444UUUYYH7"
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "clientId": 1,
+    "resourceId": 1,
+    "changes": {
+        "externalId": "786444UUUYYH7"
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="clients_delete" name="clients_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Client</h4>
+					<p>If a client is in <i>Pending</i> state, you are allowed to <b>Delete</b> it. The delete is a 'hard delete' and cannot be recovered from. Once clients become active or have loans or savings associated with them, you cannot delete the client but you may <b>Close</b> the client if they have left the program.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/clients/{clientId}
+						</code>
+					<code class="method-request">
+DELETE clients/3
+Content-Type: application/json
+Request Body:
+{
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 3,
+  "resourceId": 3
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="clients_loansummary" name="clients_loansummary" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve client accounts overview</h4>
+					<p>
+						An example of how a loan portfolio summary can be provided. This
+						is requested in a specific use case of the community application.<br> It is quite reasonable to add resources like this to simplify User Interface development.
+					</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/accounts</div>
+					<br>
+					<br>
+					<div class=apiClick>clients/1/accounts?fields=loanAccounts,savingsAccounts</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/accounts
+					</code>
+					<code class="method-response">
+{
+    "loanAccounts": [
+        {
+            "id": 1,
+            "accountNo": "000000001",
+            "externalId": "456",
+            "productId": 1,
+            "productName": "TestOne",
+            "status": {
+                "id": 300,
+                "code": "loanStatusType.active",
+                "value": "Active",
+                "pendingApproval": false,
+                "waitingForDisbursal": false,
+                "active": true,
+                "closedObligationsMet": false,
+                "closedWrittenOff": false,
+                "closedRescheduled": false,
+                "closed": false,
+                "overpaid": false
+            },
+            "loanType": {
+                "id": 1,
+                "code": "loanType.individual",
+                "value": "Individual"
+            },
+            "loanCycle": 1
+        }
+    ],
+    "savingsAccounts": [
+        {
+            "id": 7,
+            "accountNo": "000000007",
+            "productId": 2,
+            "productName": "Other product",
+            "status": {
+                "id": 100,
+                "code": "savingsAccountStatusType.submitted.and.pending.approval",
+                "value": "Submitted and pending approval",
+                "submittedAndPendingApproval": true,
+                "approved": false,
+                "rejected": false,
+                "withdrawnByApplicant": false,
+                "active": false,
+                "closed": false
+            },
+            "currency": {
+                "code": "USD",
+                "name": "US Dollar",
+                "decimalPlaces": 2,
+                "displaySymbol": "$",
+                "nameCode": "currency.USD",
+                "displayLabel": "US Dollar ($)"
+            }
+        },
+        {
+            "id": 6,
+            "accountNo": "000000006",
+            "productId": 1,
+            "productName": "Passbook Savings",
+            "status": {
+                "id": 300,
+                "code": "savingsAccountStatusType.active",
+                "value": "Active",
+                "submittedAndPendingApproval": false,
+                "approved": false,
+                "rejected": false,
+                "withdrawnByApplicant": false,
+                "active": true,
+                "closed": false
+            },
+            "currency": {
+                "code": "USD",
+                "name": "US Dollar",
+                "decimalPlaces": 2,
+                "displaySymbol": "$",
+                "nameCode": "currency.USD",
+                "displayLabel": "US Dollar ($)"
+            },
+            "accountBalance": 1828.03
+        },
+        {
+            "id": 5,
+            "accountNo": "000000005",
+            "productId": 1,
+            "productName": "Passbook Savings",
+            "status": {
+                "id": 400,
+                "code": "savingsAccountStatusType.withdrawn.by.applicant",
+                "value": "Withdrawn by applicant",
+                "submittedAndPendingApproval": false,
+                "approved": false,
+                "rejected": false,
+                "withdrawnByApplicant": true,
+                "active": false,
+                "closed": true
+            },
+            "currency": {
+                "code": "USD",
+                "name": "US Dollar",
+                "decimalPlaces": 2,
+                "displaySymbol": "$",
+                "nameCode": "currency.USD",
+                "displayLabel": "US Dollar ($)"
+            }
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- start of client Identifiers API docs-->
+			<a id="client_identifiers" name="client_identifiers" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Client Identifiers</h3>
+					<p>Client Identifiers refer to documents that are
+					used to uniquely identify a customer <br/>
+					Ex: Drivers License, Passport, Ration card etc
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>documentKey</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Number/String used to uniquely identify a particular
+								document (Driving License number for a driving license etc)</td>
+						</tr>
+						<tr class=alt>
+							<td>documentType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Type of the identification document
+								(License, Passport Etc)</td>
+						</tr>
+						<tr class=alt>
+							<td>description</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Any user comments to be associated
+								with the Client Identifier</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="client_identifiers_list" name="client_identifiers_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List all Identifiers for a Client</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/identifiers</div>
+					<br>
+					<br>
+					<div class=apiClick>clients/1/identifiers?fields=documentKey,documentType,description</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/identifiers
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 2,
+    "clientId": 1,
+    "documentType": {
+      "id": 3,
+      "name": "Drivers License"
+    },
+    "documentKey": "12345",
+    "description": "Issued in the year 2--7"
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="client_identifiers_template" name="client_identifiers_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Client Identifier Details Template</h4>
+					<p>This is a convenience resource useful for
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>clients/1/identifiers/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/identifiers/template
+					</code>
+					<code class="method-response">
+{
+  "allowedDocumentTypes": [
+    {
+      "id": 1,
+      "name": "Passport",
+      "position": 0
+    },
+    {
+      "id": 2,
+      "name": "Id",
+      "position": 0
+    },
+    {
+      "id": 3,
+      "name": "Drivers License",
+      "position": 0
+    },
+    {
+      "id": 4,
+      "name": "Any Other Id Type",
+      "position": 0
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="client_identifiers_retrieve" name="client_identifiers_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Client Identifier</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/identifier/2</div>
+					<br>
+					<br>
+					<div class=apiClick>clients/1/identifier/2?template=true</div>
+					<br>
+					<br>
+					<div class=apiClick>clients/1/identifiers/2?fields=documentKey,documentType,description</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/identifiers/{identifierId}
+					</code>
+					<code class="method-response">
+{
+  "id": 2,
+  "clientId": 1,
+  "documentType": {
+    "id": 3,
+    "name": "Drivers License"
+  },
+  "documentKey": "12345",
+  "description": "Issued in 2007"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="client_identifiers_create" name="client_identifiers_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create an Identifier for a Client</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>documentKey, documentTypeId
+							</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/clients/{clientId}/identifiers
+					</code>
+					<code class="method-request">
+POST clients/1/identifiers
+Content-Type: application/json
+Request Body:
+{
+"documentTypeId":"1",
+"documentKey":"KA-54677",
+"description":"Document has been verified"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "resourceId": 3
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="client_identifiers_update" name="client_identifiers_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Client Identifier</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/clients/{clientId}/identifiers/{identifierId}
+					</code>
+					<code class="method-request">
+PUT clients/1/identifiers/3
+Content-Type: application/json
+Request Body:
+{
+"documentTypeId":"4",
+"documentKey":"KA-94667",
+"description":"Document has been updated"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "resourceId": 3,
+  "changes": {
+    "documentTypeId": 4,
+    "documentKey": "KA-94667",
+    "description": "Document has been updated"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="client_identifiers_delete" name="client_identifiers_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Client Identifier</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/clients/{clientId}/identifiers/{identifierId}
+					</code>
+					<code class="method-request">
+DELETE clients/1/identifiers/3
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "resourceId": 3
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- start of Client Images API docs-->
+
+			<a id="client_images" name="client_images" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Images</h3>
+					<p>The current API provides support for the addition of a single
+					image for entities like Client (URL pattern <i>/clients</i>) and Staff (URL pattern <i>/staff</i>)<br/>
+					Allowed formats: JPEG (.jpg or .jpeg), GIF (.gif) and PNG (.png)
+					</p>
+					<p> The API supports two different Approaches for manipulating Images
+					 <ul>
+					 	<li><a href="http://en.wikipedia.org/wiki/Data_URI_scheme" target="_blank"> Data URI's: </a>
+					  For easier manipulation by Javascript clients etc in supported Browsers.
+					 	</li>
+					 	<li><a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2">
+						Multi-part form data:</a>
+					  Images can be uploaded using Multi part forms and downloaded as regular binary files
+					 	</li>
+					 <br/>
+				</div>
+			</div>
+
+			<a id="client_images_retrieve" name="client_images_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Get Entity Image (DATA URI)</h4>
+					<p>Optional arguments are identical to those of <a href="#client_images_retrieve_binary">Get Image associated with an Entity (Binary file)</a></p>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/images</div>
+					<div class=apiClick>staff/1/images</div>
+					<br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/images
+Accept: text/plain
+
+					</code>
+					<code class="method-response">
+data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
+bWFnZVJlYWR5ccllPAAAAJ1JREFUeNpi+P//PwMIA4E9EG8E4idQDGLbw+WhiiqA+D8OXAFVAzbp
+DxBvB2JLIGaGYkuoGEjOhhFIHAbij0BdPgxYACMj42ogJQpifwBiXSDeC8JIbt4LxSC5DyxQjTeB
++BeaYb+Q5EBOAVutCzMJHUNNPADzzDokiYdAfAmJvwLkGeTgWQfyKZICS6hYBTwc0QL8ORSjBDhA
+gAEAOg13B6R/SAgAAAAASUVORK5CYII=
+					</code>
+				</div>
+			</div>
+
+			<a id="client_images_retrieve_binary" name="client_images_retrieve_binary"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Get Image associated with an Entity (Binary file)</h4>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>output</dt>
+						<dd>
+							String <span>optional</span> one of <span>octet or inline_octet</span>
+						</dd>
+						<dd>The query parameter overrides the <span>"Accept" Header</span> and sets the Media Type to "application/octet-stream" <br/>
+						<span><b>octet :</b></span> Returns the image binary file as an attachment. The <span>Content-Disposition</span> header is set to <span>attachment;filename=somefile.ext</span> <br/>
+						<span><b>inline_octet :</b></span>The <span>Content-Disposition</span> header is set to <span>inline;filename=somefile.ext</span>
+						</dd>
+						<dt>maxWidth</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dd>Triggers resizing of the image to the defined width</dd>
+
+						<dt>maxHeight</dt>
+						<dd>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						</dd>
+						<dd>Triggers resizing of the image to the defined height</dd>
+
+
+					</dl>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/images
+Accept: application/octet-stream
+					</code>
+					<code class="method-request">
+GET clients/1/images
+Accept: application/octet-stream
+					</code>
+					<code class="method-response">
+<i>Not shown</i>: The corresponding binary (image) file
+					</code>
+				</div>
+			</div>
+
+			<a id="client_images_create" name="client_images_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Upload an Image for an Entity (Data URI)</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/clients/{clientId}/images
+					</code>
+					<code class="method-request">
+POST clients/1/images
+Content-Type: text/plain
+Request Body:
+data:image/png;base64,iVBORw0KGgoAA
+AANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l
+EQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6
+P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {},
+  "resourceIdentifier": "1"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="client_images_create_form" name="client_images_create_form" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Upload an Image for an Entity (Multi-part Form data)</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>file
+							</td>
+						</tr>
+					</table>
+					<p>
+						The form should contain a required named body part with the
+						name "file". <br/> <br/>
+						If you are using a HTML form, a snippet like
+						<small> &lt;input type="file" name="file"&gt;&lt;/input&gt; </small>
+						can be used for uploading the image file
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/clients/{clientId}/images
+					</code>
+					<code class="method-request">
+POST clients/1/images
+Content-Type: multipart/form-data
+Request Body: <i>Not shown</i>
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {},
+  "resourceIdentifier": "1"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="client_images_update" name="client_images_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Image associated with an Entity (Data URI)</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/clients/{clientId}/images
+					</code>
+					<code class="method-request">
+PUT clients/1/images
+Content-Type: text/plain
+Request Body:
+data:image/png;base64,iVBORw0KGgoAA
+AANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l
+EQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6
+P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {},
+  "resourceIdentifier": "1"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="client_images_update_form" name="client_images_update_form" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update an Entity's Image (Multi-part Form data)</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>file
+							</td>
+						</tr>
+					</table>
+					<p>
+						The form should contain a required named body part with the
+						name "file". <br/> <br/>
+						If you are using a HTML form, a snippet like
+						<small> &lt;input type="file" name="file"&gt;&lt;/input&gt; </small>
+						can be used for uploading the image file
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/clients/{clientId}/images
+					</code>
+					<code class="method-request">
+PUT clients/1/images
+Content-Type: multipart/form-data
+Request Body: <i>Not shown</i>
+					</code>
+				</div>
+			</div>
+
+			<a id="client_images_delete" name="client_images_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete an Entity's Image</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/clients/{clientId}/images
+					</code>
+					<code class="method-request">
+DELETE clients/1/images
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {},
+  "resourceIdentifier": "1"
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- Start of Administrative Units API-->
+			<a id="centers" name="centers" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Centers</h3>
+					<p><b>Centers</b> along with <b>Groups</b> are used to provided a distinctive banking distribution channel used in microfinance. Its common in areas such as Southern Asia to use Centers and Group as administrative units in <b>grameen style lending</b>. Typically groups will contain one to five people and centers themselves will be made of anywhere between 2-10 groups.
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>externalId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A place to put an external reference for this center e.g. The ID another system uses. If provided, it must be unique.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name given to the Center.</td>
+						</tr>
+						<tr class=alt>
+							<td>active</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Indicates whether this center is to be created as <b>active</b>. If active=true, then <b>activationDate</b> must be provided. If active=false, then the center is created as <b>pending</b>.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>activationDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The date on which the center became <b>active</b>.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>officeId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The officeId of the office/branch this center is administrated through.</td>
+						</tr>
+						<tr class=alt>
+							<td>staffId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The staffId of the staff member dealing with this center. The staff member is not specifically the loan officer.</td>
+						</tr>
+						<tr class=alt>
+							<td>groupMembers</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The array of groupIds to indicate what groups are part of this center.</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="centers_template" name="centers_template" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Center Template</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>centers/template</div>
+					<br/>
+					<div class=apiClick>centers/template?officeId=2</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/centers/template
+					</code>
+					<code class="method-response">
+{
+  "active": false,
+  "activationDate": [
+    2013,
+    4,
+    18
+  ],
+  "officeId": 1,
+  "officeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    },
+    {
+      "id": 2,
+      "name": "Branch Office 1",
+      "nameDecorated": "....Branch Office 1"
+    }
+  ],
+  "staffOptions": [
+    {
+      "id": 1,
+      "displayName": "C, Mike"
+    }
+  ],
+  "groupMembersOptions": [
+    {
+      "id": 1,
+      "name": "First Group",
+      "externalId": "000-1A",
+      "officeId": 1,
+      "officeName": "Head Office",
+      "hierarchy": ".1."
+    },
+    {
+      "id": 2,
+      "name": "Pending Group",
+      "officeId": 1,
+      "officeName": "Head Office",
+      "hierarchy": ".2."
+    }
+  ]
+}
+					</code>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/centers/template?officeId=2
+					</code>
+					<code class="method-response">
+{
+  "active": false,
+  "activationDate": [
+    2013,
+    4,
+    18
+  ],
+  "officeId": 2,
+  "officeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    },
+    {
+      "id": 2,
+      "name": "Branch Office 1",
+      "nameDecorated": "....Branch Office 1"
+    }
+  ],
+  "staffOptions": [
+    {
+      "id": 2,
+      "displayName": "D, Mary"
+    },
+    {
+      "id": 3,
+      "displayName": "P, Paul"
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="centers_create" name="centers_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Center</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name, officeId, active, activationDate (if active=true)</td>
+						</tr>
+					</table>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>externalId, staffId, groupMembers</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<p>Create a center as pending with no association to groupMembers.</p>
+					<code class="method-declaration">
+POST https://DomainName/fineract-provider/api/v1/centers
+					</code>
+					<code class="method-request">
+POST centers
+Content-Type: application/json Request Body:
+{
+ 	"name": "First Center (No groups)",
+ 	"officeId": 1,
+ 	"active": false
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 8,
+  "resourceId": 8
+}
+					</code>
+				</div>
+
+				<div class="method-example">
+					<p>Create a center as active with no association to groupMembers.</p>
+					<code class="method-declaration">
+POST https://DomainName/fineract-provider/api/v1/centers
+					</code>
+					<code class="method-request">
+POST centers
+Content-Type: application/json Request Body:
+{
+	"name": "centerwithgroup",
+	"officeId": 1,
+	"groupMembers": ["7"],
+	"locale": "en",
+	"dateFormat": "dd MMMM yyyy",
+	"active": true,
+	"activationDate": "01 March 2011"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 9,
+  "resourceId": 9
+}
+					</code>
+				</div>
+			</div>
+
+	<a id="centers_activate" name="centers_activate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Activate a Center</h2>
+	        <p>Centers can be created in a <i>Pending</i> state. This API exists to enable center activation.</p>
+	        <p>If the center happens to be already active, this API will result in an error.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/centers/{centerId}?command=activate</code>
+	        <code class="method-request">POST centers/1?command=activate
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "activationDate": "01 March 2011"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="centers_close" name="centers_close" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Close a Center</h2>
+	        <p>Centers can be closed if they don't have any non-closed groups or saving accounts.</p>
+	        <p>If the Center has any active groups or savings accounts, this API will result in an error.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/centers/{centerId}?command=close</code>
+	        <code class="method-request">POST centers/1?command=close
+Content-Type: application/json
+Request Body:
+{
+ "closureReasonId": 32,
+ "closureDate": "05 May 2014",
+ "locale": "en",
+ "dateFormat": "dd MMMM yyyy"
+}
+			</code>
+	        <code class="method-response">
+{
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="centers_associate_groups" name="centers_associate_groups" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Associate Groups</h2>
+	        <p>This API allows associating existing groups to a center.</p>
+	        <p>The groups are listed from the office to which the center is associated.</p>
+	        <p>If group(s) is already associated with a center, this API will result in an error.</p>
+	    </div>
+
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/centers/{centerId}?command=associateGroups</code>
+	        <code class="method-request">POST centers/1?command=associateGroups
+Content-Type: application/json
+Request Body:
+{
+  "grouptMembers":[1,2]
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 1,
+  "resourceId": 1,
+  "grouptMembers": [1,2]
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="centers_disassociate_groups" name="centers_disassociate_groups" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Disassociate Groups</h2>
+	        <p>This API allows to disassociate groups from a center.</p>
+	    </div>
+
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/centers/{centerId}?command=disassociateGroups</code>
+	        <code class="method-request">POST center/1?command=disassociateGroups
+Content-Type: application/json
+Request Body:
+{
+  "grouptMembers":[1,2]
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 1,
+  "resourceId": 1,
+  "grouptMembers": [1,2]
+}
+	        </code>
+	    </div>
+	</div>
+
+			<a id="centers_accounts" name="centers_accounts" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Center accounts overview</h4>
+					<p>
+						An example of how a savings summary for a Center can be provided. This
+						is requested in a specific use case of the reference application.<br> It is quite reasonable to add resources like this to simplify User Interface development.
+					</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>centers/9/accounts</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/centers/{centerId}/accounts
+					</code>
+					<code class="method-response">
+{
+ "savingsAccounts": [{
+  "id": 16,
+  "accountNo": "000000016",
+  "productId": 1,
+  "productName": "Voluntary savings",
+  "status": {
+   "id": 100,
+   "code": "savingsAccountStatusType.submitted.and.pending.approval",
+   "value": "Submitted and pending approval",
+   "submittedAndPendingApproval": true,
+   "approved": false,
+   "rejected": false,
+   "withdrawnByApplicant": false,
+   "active": false,
+   "closed": false,
+   "prematureClosed": false,
+   "transferInProgress": false,
+   "transferOnHold": false
+  },
+  "currency": {
+   "code": "USD",
+   "name": "US Dollar",
+   "decimalPlaces": 2,
+   "inMultiplesOf": 0,
+   "displaySymbol": "$",
+   "nameCode": "currency.USD",
+   "displayLabel": "US Dollar ($)"
+  },
+  "accountType": {
+   "id": 2,
+   "code": "accountType.group",
+   "value": "Group"
+  },
+  "timeline": {
+   "submittedOnDate": [2014,5,1],
+   "submittedByUsername": "mifos",
+   "submittedByFirstname": "App",
+   "submittedByLastname": "Administrator"
+  },
+  "depositType": {
+   "id": 100,
+   "code": "depositAccountType.savingsDeposit",
+   "value": "Savings"
+  }
+ }]
+}
+					</code>
+				</div>
+			</div>
+
+    <a id="centers_generate_collectionsheet" name="centers_generate_collectionsheet" class="old-syle-anchor">&nbsp;</a>
+        <div class="method-section">
+            <div class="method-description">
+              <h4>Generate Collection Sheet</h4>
+            <p>This Api retrieves repayment details of all jlg loans under a center as on a specified meeting date.</p>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">POST https://Domain Name/api/v1/centers/{centerId}?command=generateCollectionSheet</code>
+            <code class="method-request">POST centers/10?command=generateCollectionSheet
+Content-Type: application/json
+Request Body:
+{
+ "dateFormat":"dd MMMM yyyy",
+ "locale":"en",
+ "calendarId":6,
+ "transactionDate":"04 May 2014"
+}
+            </code>
+            <code class="method-response">
+{
+ "dueDate": [2014,5,4],
+ "loanProducts": [{
+  "id": 1,
+  "name": "IGL",
+  "includeInBorrowerCycle": false,
+  "useBorrowerCycle": false,
+  "currency": {
+   "code": "USD",
+   "name": "US Dollar",
+   "decimalPlaces": 2,
+   "inMultiplesOf": 0,
+   "displaySymbol": "$",
+   "nameCode": "currency.USD",
+   "displayLabel": "US Dollar ($)"
+  },
+  "principalVariationsForBorrowerCycle": [],
+  "interestRateVariationsForBorrowerCycle": [],
+  "numberOfRepaymentVariationsForBorrowerCycle": []
+ }],
+ "groups": [{
+  "groupId": 1,
+  "groupName": "Group 1",
+  "staffId": 1,
+  "staffName": "A, Aliya",
+  "levelId": 2,
+  "levelName": "Group",
+  "clients": [{
+   "clientId": 10,
+   "clientName": "saving acc",
+   "loans": [{
+    "loanId": 10,
+    "accountId": "000000010",
+    "accountStatusId": 300,
+    "productShortName": "IGL",
+    "productId": 1,
+    "currency": {
+     "code": "USD",
+     "name": "US Dollar",
+     "decimalPlaces": 2,
+     "inMultiplesOf": 0,
+     "displaySymbol": "$",
+     "nameCode": "currency.USD",
+     "displayLabel": "US Dollar ($)"
+    },
+    "principalDue": 1200.000000,
+    "principalPaid": 0.000000,
+    "interestDue": 21.360000,
+    "interestPaid": 0.000000,
+    "totalDue": 1221.360000
+   }],
+   "attendanceType": {
+    "id": 0,
+    "code": "attendanceType.invalid",
+    "value": "Invalid"
+   }
+  }]
+ }],
+ "attendanceTypeOptions": [{
+  "id": 1,
+  "code": "attendanceType.present",
+  "value": "Present"
+ },
+ {
+  "id": 2,
+  "code": "attendanceType.absent",
+  "value": "Absent"
+ },
+ {
+  "id": 3,
+  "code": "attendanceType.approved",
+  "value": "Approved"
+ },
+ {
+  "id": 4,
+  "code": "attendanceType.leave",
+  "value": "Leave"
+ },
+ {
+  "id": 5,
+  "code": "attendanceType.late",
+  "value": "Late"
+ }]
+}
+                    </code>
+                </div>
+            </div>
+
+			<a id="generate_individual_collection_sheet" name="generate_individual_collection_sheet" class="old-syle-anchor">&nbsp;</a>
+        <div class="method-section">
+            <div class="method-description">
+              <h4>Generate Individual Collection Sheet</h4>
+            <p>This Api retrieves repayment details of all individual loans under a office as on a specified meeting date.</p>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">POST https://Domain Name/api/v1/collectionsheet?command=generateCollectionSheet</code>
+            <code class="method-request">POST collectionsheet?command=generateCollectionSheet
+Content-Type: application/json
+Request Body:
+{
+   "dateFormat":"dd MMMM yyyy",
+   "locale":"en",
+   "transactionDate":"15 January 2015",
+   "officeId":3
+}
+            </code>
+            <code class="method-response">
+{
+  "dueDate": [
+    2015,
+    1,
+    15
+  ],
+  "clients": [
+    {
+      "clientId": 74,
+      "clientName": "guarantee test",
+      "loans": [
+        {
+          "loanId": 307,
+          "accountId": "000000307",
+          "accountStatusId": 300,
+          "productShortName": "pr",
+          "productId": 60,
+          "currency": {
+            "code": "USD",
+            "name": "US Dollar",
+            "decimalPlaces": 2,
+            "inMultiplesOf": 0,
+            "displaySymbol": "$",
+            "nameCode": "currency.USD",
+            "displayLabel": "US Dollar ($)"
+          },
+          "principalDue": 1126.23,
+          "principalPaid": 8873.77,
+          "interestDue": 0,
+          "interestPaid": 504.95,
+          "totalDue": 1126.23
+        }
+      ],
+      "savings": [
+        {
+          "savingsId": 213,
+          "accountId": "000000213",
+          "accountStatusId": 300,
+          "productName": "11",
+          "productId": 30,
+          "currency": {
+            "code": "USD",
+            "name": "US Dollar",
+            "decimalPlaces": 2,
+            "inMultiplesOf": 0,
+            "displaySymbol": "$",
+            "nameCode": "currency.USD",
+            "displayLabel": "US Dollar ($)"
+          },
+          "dueAmount": 2000
+        }
+      ]
+    }
+  ],
+  "paymentTypeOptions": [
+    {
+      "id": 19,
+      "name": "receipt",
+      "position": 1,
+      "description": "rec"
+    },
+    {
+      "id": 20,
+      "name": "check",
+      "position": 2,
+      "description": "che"
+    }
+  ]
+}
+                    </code>
+                </div>
+            </div>
+
+
+            <a id="centers_save_collectionsheet" name="centers_save_collectionsheet" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Save Collection Sheet</h4>
+                    <p>This Api allows the loan officer to perform bulk repayments of JLG loans for a center on a given meeting date.</p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/centers/{centerId}?command=saveCollectionSheet</code>
+                    <code class="method-request">POST centers/10?command=saveCollectionSheet
+{
+ "dateFormat": "dd MMMM yyyy",
+ "locale": "en",
+ "calendarId": 6,
+ "transactionDate": "04 May 2014",
+ "actualDisbursementDate": "04 May 2014",
+ "clientsAttendance": [],
+ "bulkDisbursementTransactions": [],
+ "bulkRepaymentTransactions": [{
+  "loanId": 10,
+  "transactionAmount": 1221.36
+ }]
+}
+                    </code>
+                    <code class="method-response">
+{
+ "groupId": 10,
+ "resourceId": 10,
+ "changes": {
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "loanTransactions": [10],
+  "SavingsTransactions": []
+ }
+}
+                    </code>
+                </div>
+            </div>
+
+			           <a id="save_individual_collection_sheet" name="save_individual_collection_sheet" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Save Collection Sheet</h4>
+                    <p>This Api allows the loan officer to perform bulk repayments of individual loans and deposit of mandatory savings on a given meeting date.</p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/collectionsheet?command=saveCollectionSheet</code>
+                    <code class="method-request">POST collectionsheet?command=saveCollectionSheet
+{
+ "dateFormat": "dd MMMM yyyy",
+ "locale": "en",
+ "transactionDate": "04 May 2014",
+ "actualDisbursementDate": "04 May 2014",
+ "bulkDisbursementTransactions": [],
+ "bulkRepaymentTransactions": [{
+  "loanId": 10,
+  "transactionAmount": 1221.36,
+  "paymentTypeId":19,
+  "receiptNumber":"1245356"
+ }],
+ "bulkSavingsDueTransactions":[]
+}
+                    </code>
+                    <code class="method-response">
+{
+ "groupId": 10,
+ "resourceId": 10,
+ "changes": {
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "loanTransactions": [15],
+  "SavingsTransactions": []
+
+ }
+}
+                    </code>
+                </div>
+            </div>
+
+
+			<a id="centers_update" name="centers_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Center</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/fineract-provider/api/v1/centers/{centerId}
+						</code>
+					<code class="method-request">
+PUT centers/8
+Content-Type: application/json
+Request Body:
+{
+	"name": "First Center (No groups)"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 8,
+  "resourceId": 8,
+  "changes": {
+    "name": "First Center (No groups) - modified"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="centers_delete" name="centers_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Center</h4>
+					<p>A Center can be deleted if it is in <i>pending</i> state and has no association - groups, loans or savings</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/fineract-provider/api/v1/centers/{centerId}
+						</code>
+					<code class="method-request">
+DELETE centers/8
+					</code>
+					<code class="method-response">
+{
+	"resourceId":1,
+	"changes":{}
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="centers_retrieve" name="centers_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Center</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>centers/1</div>
+					<br/>
+					<div class=apiClick>centers/1?associations=groupMembers</div>
+				</div>
+				<div class="method-example">
+					<p>Retrieve an existing center with no groups information.</p>
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/centers/8
+					</code>
+					<code class="method-response">
+{
+  "id": 8,
+  "status": {
+    "id": 100,
+    "code": "groupingStatusType.pending",
+    "value": "Pending"
+  },
+  "active": false,
+  "name": "First Center (No groups)",
+  "officeId": 1,
+  "officeName": "Head Office",
+  "hierarchy": ".8."
+}
+					</code>
+				</div>
+				<div class="method-example">
+					<p>Retrieve an existing center without the details of group associations.</p>
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/centers/9
+					</code>
+					<code class="method-response">
+{
+  "id": 9,
+  "status": {
+    "id": 300,
+    "code": "groupingStatusType.active",
+    "value": "Active"
+  },
+  "active": true,
+  "activationDate": [
+    2011,
+    3,
+    1
+  ],
+  "name": "centerwithgroup",
+  "officeId": 1,
+  "officeName": "Head Office",
+  "hierarchy": ".9."
+}
+					</code>
+				</div>
+			</div>
+
+		<a id="centers_list" name="centers_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Centers</h4>
+					<p>The default implementation supports <b>pagination</b> and <b>sorting</b> with the default pagination size set to 200 records. The parameter <b>limit</b> with value <b>-1</b> will return all entries.</p>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>paged</dt>
+						<dd>
+							Boolean <span>optional</span>, defaults to false
+						</dd>
+						<dd>If paged is <span>true</span> then results will be paginated.</dd>
+						<dt>offset</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 0
+						</dd>
+						<dd>Indicates from what result to start from.</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+						<dt>orderBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>displayName, accountNo, officeId, officeName</span>
+						</dd>
+						<dd>Orders the results by the field indicated.</dd>
+
+						<dt>sortBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>ASC, DESC</span>
+						</dd>
+						<dd>Indicates what way to order results if <i>orderBy</i> is used.</dd>
+
+						<dt>officeId</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dd>Provides ability to restrict list of centers returned based on the office there associated with.</dd>
+						<dt>underHierarchy</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use the office hierarchy string to return all centers under a given hierarchy.</dd>
+
+						<dt>name</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use name of centers to restrict results.</dd>
+
+						<dt>externalId</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use externalId of center to restrict results.</dd>
+
+						<dt>sqlSearch</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use an sql fragment valid for the underlying center schema to filter results. e.g. display_name like %K%</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>centers</div>
+					<br>
+					<div class=apiClick>centers?fields=name,officeName,joinedDate</div>
+					<br>
+					<div class=apiClick>centers?offset=10&limit=50</div>
+					<br>
+					<div class=apiClick>centers?orderBy=name&sortOrder=DESC</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/centers?paged=true
+					</code>
+					<code class="method-response">
+{
+  "totalFilteredRecords": 2,
+  "pageItems": [
+    {
+      "id": 2,
+      "status": {
+        "id": 100,
+        "code": "groupingStatusType.pending",
+        "value": "Pending"
+      },
+      "active": false,
+      "name": "Center 1",
+      "officeId": 1,
+      "officeName": "Head Office",
+      "hierarchy": ".2."
+    },
+    {
+      "id": 3,
+      "status": {
+        "id": 100,
+        "code": "groupingStatusType.pending",
+        "value": "Pending"
+      },
+      "active": false,
+      "name": "Center 2",
+      "officeId": 1,
+      "officeName": "Head Office",
+      "hierarchy": ".3."
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+			<!-- End of Centers API-->
+
+			<!-- start of Groups api docs -->
+			<a id="groups" name="groups" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Groups</h3>
+					<p><b>Groups</b> are used to provide a distinctive banking distribution channel used in microfinances throughout the world. The Group is an administrative unit. It can contain as few as 5 people or as many as 40 depending on how its used.</p>
+
+					<p>Different styles of group lending - Joint-Liability Group, Grameen Model (Center-Group), Self-Help Groups, Village/Communal Banks)</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name given to the Group.</td>
+						</tr>
+						<tr class=alt>
+							<td>externalId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A place to put an external reference for this group e.g. The ID another system uses. If provided, it must be unique.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>officeId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The officeId of the office/branch this group is administrated through.</td>
+						</tr>
+						<tr class=alt>
+							<td>active</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Indicates whether this group is to be created as <b>active</b>. If active=true, then <b>activationDate</b> must be provided. If active=false, then the group is created as <b>pending</b>.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>activationDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The date on which the group became <b>active</b>.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>staffId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The staffId of the staff member dealing with this group. The staff member is not specifically the loan officer. The staff member must be assigned to the same office as this group.</td>
+						</tr>
+						<tr class=alt>
+							<td>clientMembers</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The individual client members that make up the group. The clients must be assigned to the same office as this group.</td>
+						</tr>
+                        <tr class=alt>
+							<td>calendarId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The identifier of the calendar to which the transaction is linked with.</td>
+						</tr>
+						<tr class=alt>
+							<td>transactionDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The date on which the transaction took place.</td>
+						</tr>
+                        <tr class=alt>
+							<td>role</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The role to be assigned to a client.</td>
+						</tr>
+                        <tr class=alt>
+							<td>actualDisbursementDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The date on which the actual disbursement took place.</td>
+						</tr>
+                        <tr class=alt>
+							<td>clientsAttendance</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The clients attendance.</td>
+						</tr>
+                        <tr class=alt>
+							<td>bulkRepaymentTransaction </td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The details of any bulk repayment transactions.</td>
+						</tr>
+                        <tr class=alt>
+							<td>bulkDisbursementTransactions </td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The details of any bulk disbursement transactions.</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="groups_template" name="groups_template" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Group Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>officeId</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dt>centerId</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dt>staffInSelectedOfficeOnly</dt>
+						<dd>
+							Boolean <span>optional</span>
+						</dd>
+						<dd>Defaults to false if not provided. If <strong>staffInSelectedOfficeOnly=true</strong> only staff
+						who are associated with the selected branch are returned.</dd>
+					</dl>
+
+					<p>Example Requests:</p>
+					<div class=apiClick>groups/template</div>
+					<br/>
+					<div class=apiClick>groups/template?officeId=2</div>
+					<br/>
+					<div class=apiClick>groups/template?centerId=1</div>
+					<br/>
+					<div class=apiClick>groups/template?centerId=1&staffInSelectedOfficeOnly=true</div>
+					<br/>
+				</div>
+				<div class="method-example">
+					<p>Template to create a standard group</p>
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/groups/template
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "officeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    },
+    {
+      "id": 2,
+      "name": "Branch Office 1",
+      "nameDecorated": "....Branch Office 1"
+    }
+  ],
+  "staffOptions": [
+    {
+      "id": 1,
+      "displayName": "C, Mike"
+    }
+  ],
+  "clientOptions": [
+    {
+      "id": 1,
+      "displayName": "Petra Yton",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 2,
+      "displayName": "Small shop business",
+      "officeId": 1,
+      "officeName": "Head Office"
+    }
+  ]
+}
+					</code>
+				</div>
+				<div class="method-example">
+					<p>Template to create a standard group with specific office known. This will return only staffOptions and clientMembersOptions relevant for the chosen office.</p>
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/groups/template?officeId=2
+					</code>
+					<code class="method-response">
+{
+  "officeId": 2,
+  "officeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    },
+    {
+      "id": 2,
+      "name": "Branch Office 1",
+      "nameDecorated": "....Branch Office 1"
+    }
+  ],
+  "staffOptions": [
+    {
+      "id": 2,
+      "displayName": "D, Mary"
+    },
+    {
+      "id": 3,
+      "displayName": "P, Paul"
+    }
+  ]
+}
+					</code>
+				</div>
+				<div class="method-example">
+					<p>Template to create a group for an existing center. As an existing center will be assigned to an office, this will return only staffOptions and clientMembersOptions relevant for the chosen center/office.</p>
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/groups/template?centerId=1
+					</code>
+					<code class="method-response">
+
+					</code>
+				</div>
+			</div>
+
+			<a id="groups_create" name="groups_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Group</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name, officeId, active, activationDate (if active=true)</td>
+						</tr>
+					</table>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>externalId, staffId, clientMembers</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/fineract-provider/api/v1/groups
+					</code>
+					<code class="method-request">
+POST groups
+Content-Type: application/json Request Body:
+{
+	"officeId":"1",
+	"name":"Pending Group",
+	"active": false
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 2,
+  "resourceId": 2
+}
+					</code>
+					<code class="method-request">
+POST groups
+Content-Type: application/json Request Body:
+{
+	"officeId":"1",
+	"name":"First Group",
+	"externalId": "000-1A",
+	"clientMembers": ["1"],
+	"dateFormat": "dd MMMM yyyy",
+	"locale": "en",
+	"active": true,
+	"activationDate": "04 March 2009",
+      "submittedOnDate":"04 March 2009"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 1,
+  "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+	<a id="groups_activate" name="groups_activate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Activate a Group</h4>
+	        <p>Groups can be created in a <i>Pending</i> state. This API exists to enable group activation.</p>
+	        <p>If the group happens to be already active this API will result in an error.</p>
+            <table class=matrixHeading>
+				<tr class="matrixHeadingBG">
+                    <td><div class="fineractHeading2">Mandatory Fields</div></td>
+				</tr>
+                <tr class=alt>
+				    <td>activationDate<br>
+                    </td>
+                </tr>
+            </table>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=activate</code>
+	        <code class="method-request">POST groups/1?command=activate
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "activationDate": "01 March 2011"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="groups_associate_clients" name="groups_associate_clients" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Associate Clients</h4>
+	        <p>This API allows to associate existing clients to a group.</p>
+	        <p>The clients are listed from the office to which the group is associated.</p>
+	        <p>If client(s) is already associated with group then API will result in an error.</p>
+            <table class=matrixHeading>
+				<tr class="matrixHeadingBG">
+                    <td><div class="fineractHeading2">Mandatory Fields</div></td>
+				</tr>
+                <tr class=alt>
+				    <td>clientMembers<br>
+                    </td>
+                </tr>
+            </table>
+	    </div>
+
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=associateClients</code>
+	        <code class="method-request">POST groups/1?command=associateClients
+Content-Type: application/json
+Request Body:
+{
+  "clientMembers":[1,2]
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 1,
+  "resourceId": 1,
+  "clientMembers": [1,2]
+}
+	        </code>
+	    </div>
+	</div>
+	<a id="groups_disassociate_clients" name="groups_disassociate_clients" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Disassociate Clients</h4>
+	        <p>This API allows to disassociate clients from a group.</p>
+	        <p>Disassociating a client with active joint liability group loans results in an error.</p>
+            <table class=matrixHeading>
+				<tr class="matrixHeadingBG">
+                    <td><div class="fineractHeading2">Mandatory Fields</div></td>
+				</tr>
+                <tr class=alt>
+				    <td>clientMembers<br>
+                    </td>
+                </tr>
+            </table>
+	    </div>
+
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=disassociateClients</code>
+	        <code class="method-request">POST groups/1?command=disassociateClients
+Content-Type: application/json
+Request Body:
+{
+  "clientMembers":[1,2]
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 1,
+  "resourceId": 1,
+  "clientMembers": [1,2]
+}
+	        </code>
+	    </div>
+	</div>
+
+			<a id="groups_transfer_clients" name="groups_transfer_clients" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+			    <div class="method-description">
+			        <h4>Transfer Clients across groups</h4>
+			        <p>This API allows to transfer clients from one group to another</p>
+			        <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>destinationGroupId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The identifier of the group to which the clients are to be transferred (
+							has to be in the same branch as the source Group).</td>
+						</tr>
+						<tr class=alt>
+							<td>clients</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Identifiers of all clients who need to be transferred
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>inheritDestinationGroupLoanOfficer</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Flag specifies if the transferred clients (and all their active loans)
+							should be linked to the assigned loan officer for the group
+							</td>
+						</tr>
+					</table>
+					<br/>
+			        <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>destinationGroupId, clients</td>
+						</tr>
+					</table>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>inheritDestinationGroupLoanOfficer (defaults to true), transferActiveLoans (defaults to true)</td>
+						</tr>
+					</table>
+			    </div>
+
+
+			    <div class="method-example">
+
+			        <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=transferClients</code>
+			        <code class="method-request">POST groups/1?command=transferClients
+Content-Type: application/json
+Request Body:
+{
+  destinationGroupId:2,
+  clients:[{id:1}]
+}
+					</code>
+	        		<code class="method-response">
+{
+   "resourceId": 1
+}
+
+
+			  		</code>
+			    </div>
+			</div>
+
+            <a id="groups_generate_collectionsheet" name="groups_generate_collectionsheet" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Generate Collection Sheet</h4>
+                    <p>This API retrieves repayment details of all jlg loans of all members of a group on a specified meeting date.</p>
+                    <br>
+                    <table class=matrixHeading>
+				        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+				        </tr>
+                        <tr class=alt>
+				            <td>calendarId, transactionDate</td>
+                        </tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=generateCollectionSheet</code>
+                    <code class="method-request">POST groups/1?command=generateCollectionSheet
+Content-Type: application/json
+Request Body:
+{
+ "dateFormat":"dd MMMM yyyy",
+ "locale":"en",
+ "calendarId":6,
+ "transactionDate":"04 May 2014"
+}
+                    </code>
+                    <code class="method-response">
+{
+ "dueDate": [2014,5,4],
+ "loanProducts": [{
+  "id": 1,
+  "name": "IGL",
+  "includeInBorrowerCycle": false,
+  "useBorrowerCycle": false,
+  "currency": {
+   "code": "USD",
+   "name": "US Dollar",
+   "decimalPlaces": 2,
+   "inMultiplesOf": 0,
+   "displaySymbol": "$",
+   "nameCode": "currency.USD",
+   "displayLabel": "US Dollar ($)"
+  },
+  "principalVariationsForBorrowerCycle": [],
+  "interestRateVariationsForBorrowerCycle": [],
+  "numberOfRepaymentVariationsForBorrowerCycle": []
+ }],
+ "groups": [{
+  "groupId": 1,
+  "groupName": "Group 1",
+  "staffId": 1,
+  "staffName": "A, Aliya",
+  "levelId": 2,
+  "levelName": "Group",
+  "clients": [{
+   "clientId": 10,
+   "clientName": "saving acc",
+   "loans": [{
+    "loanId": 10,
+    "accountId": "000000010",
+    "accountStatusId": 300,
+    "productShortName": "IGL",
+    "productId": 1,
+    "currency": {
+     "code": "USD",
+     "name": "US Dollar",
+     "decimalPlaces": 2,
+     "inMultiplesOf": 0,
+     "displaySymbol": "$",
+     "nameCode": "currency.USD",
+     "displayLabel": "US Dollar ($)"
+    },
+    "principalDue": 1200.000000,
+    "principalPaid": 0.000000,
+    "interestDue": 21.360000,
+    "interestPaid": 0.000000,
+    "totalDue": 1221.360000
+   }],
+   "attendanceType": {
+    "id": 0,
+    "code": "attendanceType.invalid",
+    "value": "Invalid"
+   }
+  }]
+ }],
+ "attendanceTypeOptions": [{
+  "id": 1,
+  "code": "attendanceType.present",
+  "value": "Present"
+ },
+ {
+  "id": 2,
+  "code": "attendanceType.absent",
+  "value": "Absent"
+ },
+ {
+  "id": 3,
+  "code": "attendanceType.approved",
+  "value": "Approved"
+ },
+ {
+  "id": 4,
+  "code": "attendanceType.leave",
+  "value": "Leave"
+ },
+ {
+  "id": 5,
+  "code": "attendanceType.late",
+  "value": "Late"
+ }]
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="groups_save_collectionsheet" name="groups_save_collectionsheet" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Save Collection Sheet</h4>
+                    <p>This api allows the loan officer to perform bulk repayments of JLG loans for a group on its meeting date.</p>
+                    <table class=matrixHeading>
+				        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+				        </tr>
+                        <tr class=alt>
+				            <td>calendarId, transactionDate, actualDisbursementDate</td>
+                        </tr>
+                    </table>
+                    <br>
+                    <table class=matrixHeading>
+				        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Optional Fields</div></td>
+				        </tr>
+                        <tr class=alt>
+				            <td>clientsAttendance, bulkRepaymentTransaction, bulkDisbursementTransactions</td>
+                        </tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=saveCollectionSheet</code>
+                    <code class="method-request">POST groups/1?command=saveCollectionSheet
+{
+ "dateFormat": "dd MMMM yyyy",
+ "locale": "en",
+ "calendarId": 6,
+ "transactionDate": "04 May 2014",
+ "actualDisbursementDate": "04 May 2014",
+ "clientsAttendance": [],
+ "bulkDisbursementTransactions": [],
+ "bulkRepaymentTransactions": [{
+  "loanId": 10,
+  "transactionAmount": 1221.36
+ }]
+}
+                    </code>
+                    <code class="method-response">
+{
+ "groupId": 1,
+ "resourceId": 1,
+ "changes": {
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "bulkTransations": {
+   "loanId": 10,
+   "transactionAmount": 1221.36,
+   "transactionDate": [2014,5,4]
+  }
+ }
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="groups_unassignStaff" name="groups_unassignStaff" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Unassign a Staff</h4>
+                    <p>Allows you to unassign the <i>Staff</i>.</p>
+                    <table class=matrixHeading>
+				        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+				        </tr>
+                        <tr class=alt>
+				            <td>staffId</td>
+                        </tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=unassignStaff</code>
+                    <code class="method-request">POST groups/1?command=unassignStaff
+Content-Type: application/json
+Request Body:
+{
+ "staffId":"1"
+}
+                    </code>
+                    <code class="method-response">
+{
+ "officeId": 1,
+ "groupId": 1,
+ "resourceId": 1,
+ "changes": {}
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="groups_assignStaff" name="groups_assignStaff" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Assign a Staff</h4>
+                    <p>Allows you to assign <i>Staff</i> to an existing Group.</p>
+                    <p>The selected Staff should be belong to the same office (or an office higher up in the hierarchy) as this group</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>staffId</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>inheritStaffForClientAccounts</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Boolean if true all members of the group (i.e all clients with active loans and savings ) will inherit the staffId</td>
+						</tr>
+					</table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=assignStaff</code>
+                    <code class="method-request">POST groups/1?command=assignStaff
+Content-Type: application/json
+Request Body:
+{
+ "staffId": "1"
+}
+                    </code>
+                    <code class="method-response">
+{
+ "officeId": 1,
+ "groupId": 1,
+ "resourceId": 1,
+ "changes": {"staffId":1}
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="groups_close" name="groups_close" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Close a Group</h4>
+                    <p>This API exists to close a group. Groups can be closed if they don't have any non-closed clients/loans/savingsAccounts.</p>
+                    <p>If the group has any active clients/loans/savingsAccount, this API will result in an error.</p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=close</code>
+                    <code class="method-request">POST groups/1?command=close
+Content-Type: application/json
+Request Body:
+{
+ "dateFormat":"dd MMMM yyyy",
+ "locale":"en",
+ "closureDate":"25 June 2013",
+ "closureReasonId":"30"
+}
+                    </code>
+                    <code class="method-response">
+{
+ "groupId": 1,
+ "resourceId": 1
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="groups_assignRole" name="groups_assignRole" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Assign a Role</h4>
+                    <p>Allows you to assign a <i>Role</i> to an existing member of a group.</p>
+                    <p>We can define the different roles applicable to group members by adding code values to the pre-defined system code <b>GROUPROLE</b>. Example:Group leader etc.</p>
+                    <table class=matrixHeading>
+				        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+				        </tr>
+                        <tr class=alt>
+				            <td>clientId, role</td>
+                        </tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=assignRole</code>
+                    <code class="method-request">POST groups/1?command=assignRole
+Content-Type: application/json
+Request Body:
+{
+ "clientId": "1",
+ "role":30
+}
+                    </code>
+                    <code class="method-response">
+{
+ "clientId": 1,
+ "groupId": 1,
+ "resourceId": 1
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="groups_unassignRole" name="groups_unassignRole" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Unassign a Role</h4>
+                    <p>Allows you to unassign <i>Roles</i> associated tp Group members.</p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=unassignRole&roleId=1</code>
+                    <code class="method-request">POST groups/1?command=unassignRole&roleId=1
+Content-Type: application/json
+Request Body:
+{}
+                    </code>
+                    <code class="method-response">
+{
+ "clientId": 1,
+ "groupId": 1,
+ "resourceId": 1
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="groups_updateRole" name="groups_updateRole" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Update a Role</h4>
+                    <p>Allows you to update the member <i>Role</i>.</p>
+                    <table class=matrixHeading>
+				        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+				        </tr>
+                        <tr class=alt>
+				            <td>role</td>
+                        </tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">POST https://Domain Name/api/v1/groups/{groupId}?command=updateRole&roleId=2</code>
+                    <code class="method-request">POST groups/1?command=updateRole&roleId=2
+Content-Type: application/json
+Request Body:
+{
+ "role":"31"
+}
+                    </code>
+                    <code class="method-response">
+{
+ "groupId": 1,
+ "resourceId": 2,
+ "changes":{"role":31}
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="groups_retrieve" name="groups_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+                <div class="method-description">
+                    <h4>Retrieve a Group</h4>
+                    <p>Retrieve group information.</p>
+                    <dl class="argument-list">
+                        <dt>associations</dt>
+                        <dd>
+                            String <span>optional</span>
+                        </dd>
+                        <dd>
+                            One of, or comma separated of <span>clientMembers, activeClientMembers, groupRoles, calendars, collectionMeetingCalendar</span>
+                            <br>
+                            or <span>all</span> for all associations.
+                        </dd>
+                        <dt>staffInSelectedOfficeOnly</dt>
+                        <dd>
+                            Boolean <span>optional</span>, defaults to false
+                        </dd>
+                        <dd>If <strong>staffInSelectedOfficeOnly=true</strong> only staff
+                        who are associated with the selected branch are returned.</dd>
+                        <dt>roleId</dt>
+                        <dd>
+                            Long <span>optional</span>
+                        </dd>
+                        <dd>If present, will retrieve group role information</dd>
+                    </dl>
+                    <p>Example Requests:</p>
+                    <div class=apiClick>groups/1</div>
+                    <br/>
+                    <div class=apiClick>groups/1?associations=clientMembers</div>
+                </div>
+                <div class="method-example">
+                    <p>Group without any associations.</p>
+                    <code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/groups/1
+                    </code>
+                    <code class="method-response">
+{
+  "id": 1,
+  "name": "First Group",
+  "externalId": "000-1A",
+  "officeId": 1,
+  "officeName": "Head Office",
+  "hierarchy": ".1.",
+  "timeline": {
+    "activatedOnDate": [
+    2013,
+    11,
+    14
+    ],
+    "activatedByUsername": "mifos",
+    "activatedByFirstname": "App",
+    "activatedByLastname": "Administrator"
+    }
+}
+                    </code>
+                </div>
+                <div class="method-example">
+                    <p>Group along with all its Clients.</p>
+                    <code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/groups/1?associations=clientMembers
+                    </code>
+                    <code class="method-response">
+{
+  "id": 1,
+  "name": "First Group",
+  "externalId": "000-1A",
+  "officeId": 1,
+  "officeName": "Head Office",
+  "hierarchy": ".1.",
+  "timeline": {
+    "activatedOnDate": [
+    2013,
+    11,
+    14
+    ],
+    "activatedByUsername": "mifos",
+    "activatedByFirstname": "App",
+    "activatedByLastname": "Administrator"
+   },
+  "clientMembers": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "externalId": "786YYH7",
+      "activationDate": [
+        2009,
+        3,
+        4
+      ],
+      "firstname": "Petra",
+      "lastname": "Yton",
+      "displayName": "Petra Yton",
+      "officeId": 1,
+      "officeName": "Head Office"
+    }
+  ]
+}
+                    </code>
+                </div>
+            </div>
+
+<!--Start of Group account summary -->
+			<a id="groups_accounts" name="groups_accounts" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Group accounts overview</h4>
+					<p>
+						Retrieves details of all Loan and Savings accounts associated with this group.
+					</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>groups/1/accounts</div>
+					<br>
+					<br>
+					<div class=apiClick>groups/1/accounts?fields=loanAccounts,savingsAccounts,memberLoanAccounts,<br> memberSavingsAccounts</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/groups/{groupId}/accounts
+					</code>
+					<code class="method-response">
+{
+  "loanAccounts": [
+    {
+      "id": 3,
+      "accountNo": "000000003",
+      "productId": 3,
+      "productName": "daily product",
+      "status": {
+        "id": 100,
+        "code": "loanStatusType.submitted.and.pending.approval",
+        "value": "Submitted and pending approval",
+        "pendingApproval": true,
+        "waitingForDisbursal": false,
+        "active": false,
+        "closedObligationsMet": false,
+        "closedWrittenOff": false,
+        "closedRescheduled": false,
+        "closed": false,
+        "overpaid": false
+      },
+      "loanType": {
+        "id": 2,
+        "code": "accountType.group",
+        "value": "Group"
+      }
+    }
+  ],
+  "savingsAccounts": [
+    {
+      "id": 9,
+      "accountNo": "000000009",
+      "productId": 1,
+      "productName": "p_sav",
+      "status": {
+        "id": 100,
+        "code": "savingsAccountStatusType.submitted.and.pending.approval",
+        "value": "Submitted and pending approval",
+        "submittedAndPendingApproval": true,
+        "approved": false,
+        "rejected": false,
+        "withdrawnByApplicant": false,
+        "active": false,
+        "closed": false
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "accountType": {
+        "id": 2,
+        "code": "accountType.group",
+        "value": "Group"
+      }
+    },
+    {
+      "id": 4,
+      "accountNo": "000000004",
+      "productId": 1,
+      "productName": "p_sav",
+      "status": {
+        "id": 300,
+        "code": "savingsAccountStatusType.active",
+        "value": "Active",
+        "submittedAndPendingApproval": false,
+        "approved": false,
+        "rejected": false,
+        "withdrawnByApplicant": false,
+        "active": true,
+        "closed": false
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "accountType": {
+        "id": 2,
+        "code": "accountType.group",
+        "value": "Group"
+      }
+    }
+  ],
+  "memberLoanAccounts": [
+    {
+      "id": 4,
+      "accountNo": "000000004",
+      "productId": 1,
+      "productName": "testLoan",
+      "status": {
+        "id": 200,
+        "code": "loanStatusType.approved",
+        "value": "Approved",
+        "pendingApproval": false,
+        "waitingForDisbursal": true,
+        "active": false,
+        "closedObligationsMet": false,
+        "closedWrittenOff": false,
+        "closedRescheduled": false,
+        "closed": false,
+        "overpaid": false
+      },
+      "loanType": {
+        "id": 3,
+        "code": "accountType.jlg",
+        "value": "JLG"
+      }
+    },
+    {
+      "id": 7,
+      "accountNo": "000000007",
+      "productId": 2,
+      "productName": "weekly product",
+      "status": {
+        "id": 100,
+        "code": "loanStatusType.submitted.and.pending.approval",
+        "value": "Submitted and pending approval",
+        "pendingApproval": true,
+        "waitingForDisbursal": false,
+        "active": false,
+        "closedObligationsMet": false,
+        "closedWrittenOff": false,
+        "closedRescheduled": false,
+        "closed": false,
+        "overpaid": false
+      },
+      "loanType": {
+        "id": 3,
+        "code": "accountType.jlg",
+        "value": "JLG"
+      }
+    }
+  ],
+  "memberSavingsAccounts": [
+    {
+      "id": 3,
+      "accountNo": "000000003",
+      "productId": 1,
+      "productName": "p_sav",
+      "status": {
+        "id": 100,
+        "code": "savingsAccountStatusType.submitted.and.pending.approval",
+        "value": "Submitted and pending approval",
+        "submittedAndPendingApproval": true,
+        "approved": false,
+        "rejected": false,
+        "withdrawnByApplicant": false,
+        "active": false,
+        "closed": false
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "accountType": {
+        "id": 3,
+        "code": "accountType.jlg",
+        "value": "JLG"
+      }
+    },
+    {
+      "id": 10,
+      "accountNo": "000000010",
+      "productId": 1,
+      "productName": "p_sav",
+      "status": {
+        "id": 100,
+        "code": "savingsAccountStatusType.submitted.and.pending.approval",
+        "value": "Submitted and pending approval",
+        "submittedAndPendingApproval": true,
+        "approved": false,
+        "rejected": false,
+        "withdrawnByApplicant": false,
+        "active": false,
+        "closed": false
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "accountType": {
+        "id": 3,
+        "code": "accountType.jlg",
+        "value": "JLG"
+      }
+    }
+   ]
+}
+					</code>
+				</div>
+			</div>
+<!--End of Group account summary -->
+
+			<a id="groups_update" name="groups_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Group</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/fineract-provider/api/v1/groups/{groupid}
+						</code>
+					<code class="method-request">
+PUT groups/1
+Content-Type: application/json
+Request Body:
+{
+	"name": "First Group (changed)"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 1,
+  "resourceId": 1,
+  "changes": {
+    "name": "First Group (changed)"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="groups_delete" name="groups_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Group</h4>
+					<p>A group can be deleted if it is in <i>pending</i> state and has no associations - clients, loans or savings</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/fineract-provider/api/v1/groups/{groupid}
+						</code>
+					<code class="method-request">
+DELETE groups/2
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "groupId": 2,
+  "resourceId": 2
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="groups_list" name="groups_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Groups</h4>
+					<p>The default implementation of <i>listing Groups</i> returns 200 entries with support for <b>pagination</b> and <b>sorting</b>. Using the parameter <b>limit</b> with value <b>-1</b> returns all entries.</p>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>paged</dt>
+						<dd>
+							Boolean <span>optional</span>, defaults to false
+						</dd>
+						<dd>If paged is <span>true</span> then results will be paginated.</dd>
+						<dt>offset</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 0
+						</dd>
+						<dd>Indicates from what result to start from.</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+						<dt>orderBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>displayName, accountNo, officeId, officeName</span>
+						</dd>
+						<dd>Orders the results by the field indicated.</dd>
+
+						<dt>sortBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>ASC, DESC</span>
+						</dd>
+						<dd>Indicates what way to order results if <i>orderBy</i> is used.</dd>
+
+						<dt>officeId</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dd>Provides ability to restrict list of groups returned based on the office there associated with.</dd>
+						<dt>underHierarchy</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use the office hierarchy string to return all groups under a given hierarchy.</dd>
+
+						<dt>name</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use name of groups to restrict results.</dd>
+
+						<dt>externalId</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use externalId of groups to restrict results.</dd>
+
+						<dt>sqlSearch</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use an sql fragment valid for the underlying group schema to filter results. e.g. display_name like %K%</dd>
+						<dt>orphansOnly</dt>
+						<dd>
+							Boolean <span>optional</span>, defaults to false
+						</dd>
+						<dd>Use orphansOnly as true to list groups which are not associated to any center/parent.</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>groups</div>
+					<br>
+					<div class=apiClick>groups?fields=name,officeName,joinedDate</div>
+					<br>
+					<div class=apiClick>groups?offset=10&limit=50</div>
+					<br>
+					<div class=apiClick>groups?orderBy=name&sortOrder=DESC</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/groups?paged=true
+					</code>
+					<code class="method-response">
+{
+  "totalFilteredRecords": 2,
+  "pageItems": [
+    {
+      "id": 4,
+      "name": "AnotherGroup",
+      "status": {
+        "id": 100,
+        "code": "clientStatusType.pending",
+        "value": "Pending"
+      },
+      "active": false,
+      "officeId": 1,
+      "officeName": "Head Office",
+      "hierarchy": ".4."
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+			<!-- end of group api docs -->
+
+			<!-- start of loans api docs -->
+			<a id="loans" name="loans" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Loans</h3>
+					<p>The API concept of <b>loans</b> models the <b>loan application process</b> and the <b>loan contract/monitoring process</b>.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>accountNo</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The account no. associated with this loan. Is auto generated if not provided at loan application creation time.</td>
+						</tr>
+						<tr class=alt>
+							<td>externalId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A place to put an external reference for
+								this loan e.g. The ID another system uses.<br> If provided,
+								it must be unique.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>fundId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: For associating a loan with a given fund.</td>
+						</tr>
+						<tr class=alt>
+							<td>loanOfficerId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: For associating a loan with a given staff member who is a loan officer.</td>
+						</tr>
+						<tr class=alt>
+							<td>loanPurposeId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: For marking a loan with a given loan purpose option. Loan purposes are configurable and can be setup by system admin through code/code values screens.</td>
+						</tr>
+						<tr class=alt>
+							<td>principal</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The loan amount to be disbursed to through loan.</td>
+						</tr>
+						<tr class=alt>
+							<td>loanTermFrequency</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The length of loan term<br> Used like:
+								<b>loanTermFrequency</b> loanTermFrequencyType<br> e.g. <b>12</b> Months
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>loanTermFrequencyType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The loan term period to use. Used like:
+								loanTermFrequency <b>loanTermFrequencyType</b><br> e.g. 12 <b>Months</b>
+							<span>Example Values:</span> 0=Days, 1=Weeks, 2=Months, 3=Years
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>numberOfRepayments</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Number of installments to repay.<br>
+								Used like: <b>numberOfRepayments</b> Every <i>repaymentEvery</i>
+								<i>repaymentFrequencyType</i><br> e.g. <b>10</b> (repayments) Every 12 Weeks
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>repaymentEvery</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Used like: numberOfRepayments Every
+								<b>repaymentEvery</b> repaymentFrequencyType<br> e.g. 10
+								(repayments) Every <b>12</b> Weeks
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>repaymentFrequencyType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Used like: <i>numberOfRepayments</i> Every
+								repaymentEvery <b>repaymentFrequencyType</b><br> e.g. 10
+								(repayments) Every 12 <b>Weeks</b> <br>
+								<span>Example Values:</span> 0=Days, 1=Weeks, 2=Months
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>interestRatePerPeriod</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Interest Rate.<br> Used like:
+								<b>interestRatePerPeriod</b> % interestRateFrequencyType - interestType<br>
+								e.g. <b>12.0000</b>% Per year - Declining Balance
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>interestRateFrequencyType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Used like: interestRatePerPeriod%
+								interestRateFrequencyType - interestType<br> e.g. 12.0000%
+								<b>Per year</b> - Declining Balance <br>
+							<span>Example Values:</span> 2=Per month, 3=Per year
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnPrincipalPayment</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the principal component of a repayment period.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnInterestPayment</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the interest component of a repayment period. Interest is still calculated but offset to later repayment periods.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnInterestCharged</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Integer - represents the number of repayment periods that should be interest-free.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnArrearsAgeing</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Integer -  Used in Arrears calculation to only take into account loans that are more than graceOnArrearsAgeing days overdue.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>interestChargedFromDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Date - The date from with interest is to start being charged.</td>
+						</tr>
+
+						<tr class=alt>
+							<td>expectedDisbursementDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The proposed <b>disbursement date</b> of the loan so a proposed repayment schedule can be provided.</td>
+						</tr>
+						<tr class=alt>
+							<td>submittedOnDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The date the <b>loan application</b> was submitted by applicant.</td>
+						</tr>
+						<tr class=alt>
+							<td>linkAccountId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The Savings Account id for linking with loan account for payments.</td>
+						</tr>
+						<tr class=alt>
+							<td>amortizationType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc><span>Example Values:</span> 0=Equal
+								principle payments, 1=Equal installments</td>
+						</tr>
+						<tr class=alt>
+							<td>interestType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Used like: interestRatePerPeriod%
+								interestRateFrequencyType - interestType<br> e.g. 12.0000%
+								Per year - <b>Declining Balance</b> <br>
+							<span>Example Values:</span> 0=Declining Balance, 1=Flat
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>interestCalculationPeriodType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc><span>Example Values:</span> 0=Daily, 1=Same as repayment period</td>
+						</tr>
+						<tr class=alt>
+							<td>allowPartialPeriodInterestCalcualtion</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>This value will be supported along with interestCalculationPeriodType as Same as repayment period to calculate interest for partial periods.
+							<span>Example:</span> Interest charged from is 5th of April , Principal is 10000 and interest is 1% per month then the interest will be (10000 * 1%)* (25/30) , it calculates for the month first then calculates exact periods between start date and end date(can be a decimal)
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>inArrearsTolerance</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The amount that can be 'waived' at end
+								of all loan payments because it is too small to worry about.<br>
+								This is also the tolerance amount assessed when determining if a
+								loan is in arrears.
+							</td>
+						</tr>
+<tr class=alt>
+	<td>transactionProcessingStrategyId</td>
+</tr>
+<tr>
+	<td class=fielddesc>
+An enumeration that indicates the type of transaction processing strategy to be used. This relates to functionality that is also known as <b>Payment Application Logic</b>.
+
+<p>A number of out of the box approaches exist, some are custom to specific MFIs, some are more general and indicate the order in which payments are processed.</p>
+
+<p>Refer to the <a href="#paymentapplicationlogic">Payment Application Logic / Transaction Processing Strategy<a> section in the appendix for more detailed overview of each available <b>payment application logic</b> provided out of the box.</p>
+
+<i>List of current approaches</i>:<br/>
+<ul>
+	<li>1 = Mifos style (Similar to Old Mifos)</li>
+	<li>2 = Heavensfamily (Custom MFI approach)</li>
+	<li>3 = Creocore (Custom MFI approach)</li>
+	<li>4 = RBI (India)</li>
+	<li>5 = Principal Interest Penalties Fees Order</li>
+	<li>6 = Interest Principal Penalties Fees Order</li>
+    <li>7 = Early Payment Strategy</li>
+</ul>
+
+</ul>
+	</td>
+</tr>
+						<tr class=alt>
+							<td>loanType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>To represent different type of loans.<br>
+								At present there are three type of loans are supported.
+								<br>Available loan types:
+								<ol>
+									<li><b>individual</b>: Loan given to individual member</li>
+									<li><b>group</b>: Loan given to group as a whole</li>
+									<li><b>jlg</b>: Joint liability group loan given to members in a group  on individual basis. JLG loan can be given to one or more members in a group.</li>
+								</ol>
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationRestFrequencyDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies rest frequency start date for interest recalculation. This date must be before or equal to disbursement date
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationCompoundingFrequencyDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies compounding frequency start date for interest recalculation. This date must be equal to disbursement date
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="loans_template" name="loans_template" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Loan Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>templateType</dt>
+						<dd>
+							String <span>mandatory</span>, allowed values are <span>individual, group, jlg, jlgbulk</span>
+						</dd>
+						<dd>
+							<br>templateType value decides the required template data for creating a new loan application.
+
+						</dd>
+						<dd>
+							<br><b>'individual':</b> Loan template data for creating individual loans.
+						</dd>
+						<dd>
+							<b>'group':</b> Loan template data for creating a group loan (loan given to group as a whole).
+						</dd>
+						<dd>
+							<b>'jlg':</b> Loan template data for creating a Joint liability group loan for a client.
+						</dd>
+						<dd>
+							<b>'jlgbulk':</b> Loan template data for creating a Joint liability group loan for multiple clients at a time in a group.
+						</dd>
+						<dt>clientId</dt>
+						<dd>
+							Integer <span>mandatory</span>
+						</dd>
+					</dl>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>productId</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dd>If entered, productId, productName and selectedProduct
+							fields are returned.</dd>
+
+						<dt>staffInSelectedOfficeOnly</dt>
+						<dd>
+							Boolean <span>optional</span>
+						</dd>
+						<dd>Defaults to false if not provided. If <strong>staffInSelectedOfficeOnly=true</strong> only loan officers
+						who are associated with the selected branch are returned.</dd>
+						<dt>activeOnly</dt>
+						<dd>
+							Boolean <span>optional</span>
+						</dd>
+						<dd>Defaults to false if not provided. If <strong>activeOnly=true</strong> only active loan products are returned.</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>loans/template?templateType=individual&clientId=1</div>
+					<br>
+					<br>
+					<div class=apiClick>loans/template?templateType=individual&clientId=1&productId=1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1
+					</code>
+					<code class="method-response">
+{
+  "clientId": 1,
+  "clientName": "Kampala first Client",
+  "clientOfficeId": 2,
+  "timeline": {
+    "expectedDisbursementDate": [
+      2013,
+      3,
+      8
+    ]
+  },
+  "productOptions": [
+    {
+      "id": 1,
+      "name": "Kampala Product (with cash accounting)"
+    }
+  ]
+}
+					</code>
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1&productId=1
+					</code>
+					<code class="method-response">
+{
+  "clientId": 1,
+  "clientName": "Kampala first Client",
+  "clientOfficeId": 2,
+  "loanProductId": 1,
+  "loanProductName": "Kampala Product (with cash accounting)",
+  "loanProductDescription": "Typical Kampala loan product with cash accounting enabled for testing.",
+  "currency": {
+    "code": "UGX",
+    "name": "Uganda Shilling",
+    "decimalPlaces": 2,
+    "displaySymbol": "USh",
+    "nameCode": "currency.UGX",
+    "displayLabel": "Uganda Shilling (USh)"
+  },
+  "principal": 1000000,
+  "termFrequency": 12,
+  "termPeriodFrequencyType": {
+    "id": 2,
+    "code": "repaymentFrequency.periodFrequencyType.months",
+    "value": "Months"
+  },
+  "numberOfRepayments": 12,
+  "repaymentEvery": 1,
+  "repaymentFrequencyType": {
+    "id": 2,
+    "code": "repaymentFrequency.periodFrequencyType.months",
+    "value": "Months"
+  },
+  "interestRatePerPeriod": 24,
+  "interestRateFrequencyType": {
+    "id": 3,
+    "code": "interestRateFrequency.periodFrequencyType.years",
+    "value": "Per year"
+  },
+  "annualInterestRate": 24,
+  "amortizationType": {
+    "id": 1,
+    "code": "amortizationType.equal.installments",
+    "value": "Equal installments"
+  },
+  "interestType": {
+    "id": 1,
+    "code": "interestType.flat",
+    "value": "Flat"
+  },
+  "interestCalculationPeriodType": {
+    "id": 1,
+    "code": "interestCalculationPeriodType.same.as.repayment.period",
+    "value": "Same as repayment period"
+  },
+  "transactionProcessingStrategyId": 2,
+  "timeline": {
+    "expectedDisbursementDate": [
+      2013,
+      3,
+      8
+    ]
+  },
+  "daysInMonthType": {
+    "id": 30,
+    "code": "DaysInMonthType.days360",
+    "value": "30 Days"
+  },
+  "daysInYearType": {
+	"id": 360,
+	"code": "DaysInYearType.days360",
+	"value": "360 Days"
+  },
+  "isInterestRecalculationEnabled": true,
+  "interestRecalculationData": {
+    "interestRecalculationCompoundingType": {
+      "id": 2,
+      "code": "interestRecalculationCompoundingMethod.fee",
+      "value": "Fee"
+    },
+	"recalculationCompoundingFrequencyType": {
+		    "id":1,
+			"code":"interestRecalculationFrequencyType.same.as.repayment.period",
+			"value":"Same as repayment period"
+	},
+    "rescheduleStrategyType": {
+      "id": 2,
+      "code": "loanRescheduleStrategyMethod.reduce.number.of.installments",
+      "value": "Reduce number of installments"
+    },
+	"recalculationRestFrequencyType": {
+		    "id":1,
+			"code":"interestRecalculationFrequencyType.same.as.repayment.period",
+			"value":"Same as repayment period"
+	}
+  }
+  "charges": [],
+  "productOptions": [
+    {
+      "id": 1,
+      "name": "Kampala Product (with cash accounting)"
+    }
+  ],
+  "loanOfficerOptions": [
+    {
+      "id": 2,
+      "firstname": "Kampala",
+      "lastname": "LoanOfficer",
+      "displayName": "LoanOfficer, Kampala",
+      "officeId": 2,
+      "officeName": "Uganda (Kampala)",
+      "isLoanOfficer": true
+    }
+  ],
+  "loanPurposeOptions": [
+    {
+      "id": 20,
+      "name": "option.Agriculture",
+      "position": 1
+    },
+    {
+      "id": 21,
+      "name": "option.Manufacturing",
+      "position": 20
+    },
+    {
+      "id": 22,
+      "name": "option.HousingImprovement",
+      "position": 21
+    }
+  ],
+  "termFrequencyTypeOptions": [
+    {
+      "id": 0,
+      "code": "loanTermFrequency.periodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "loanTermFrequency.periodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "loanTermFrequency.periodFrequencyType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "loanTermFrequency.periodFrequencyType.years",
+      "value": "Years"
+    }
+  ],
+  "repaymentFrequencyTypeOptions": [
+    {
+      "id": 0,
+      "code": "repaymentFrequency.periodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "repaymentFrequency.periodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "repaymentFrequency.periodFrequencyType.months",
+      "value": "Months"
+    }
+  ],
+  "interestRateFrequencyTypeOptions": [
+    {
+      "id": 2,
+      "code": "interestRateFrequency.periodFrequencyType.months",
+      "value": "Per month"
+    },
+    {
+      "id": 3,
+      "code": "interestRateFrequency.periodFrequencyType.years",
+      "value": "Per year"
+    }
+  ],
+  "amortizationTypeOptions": [
+    {
+      "id": 1,
+      "code": "amortizationType.equal.installments",
+      "value": "Equal installments"
+    },
+    {
+      "id": 0,
+      "code": "amortizationType.equal.principal",
+      "value": "Equal principle payments"
+    }
+  ],
+  "interestTypeOptions": [
+    {
+      "id": 1,
+      "code": "interestType.flat",
+      "value": "Flat"
+    },
+    {
+      "id": 0,
+      "code": "interestType.declining.balance",
+      "value": "Declining Balance"
+    }
+  ],
+  "interestCalculationPeriodTypeOptions": [
+    {
+      "id": 0,
+      "code": "interestCalculationPeriodType.daily",
+      "value": "Daily"
+    },
+    {
+      "id": 1,
+      "code": "interestCalculationPeriodType.same.as.repayment.period",
+      "value": "Same as repayment period"
+    }
+  ],
+  "transactionProcessingStrategyOptions": [
+    {
+      "id": 2,
+      "code": "heavensfamily-strategy",
+      "name": "Heavensfamily"
+    }
+  ],
+  "chargeOptions": [
+    {
+      "id": 1,
+      "name": "Bank Fee (per installment)",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "UGX",
+        "name": "Uganda Shilling",
+        "decimalPlaces": 2,
+        "displaySymbol": "USh",
+        "nameCode": "currency.UGX",
+        "displayLabel": "Uganda Shilling (USh)"
+      },
+      "amount": 1500,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      }
+    }
+  ],
+  "loanCollateralOptions": [
+    {
+      "id": 17,
+      "name": "option.House",
+      "position": 1
+    },
+    {
+      "id": 18,
+      "name": "option.Television",
+      "position": 17
+    },
+    {
+      "id": 19,
+      "name": "option.Gold",
+      "position": 18
+    }
+  ],
+  "accountLinkingOptions":[
+  	{
+  		"id":1,
+  		"accountNo":"000000001",
+  		"clientId":1,
+  		"clientName":"pramod nuthakki",
+  		"productId":1,
+  		"productName":"pramod sav",
+  		"fieldOfficerId":0,
+  		"currency":{"code":"USD","name":"US Dollar","decimalPlaces":2,"displaySymbol":"$","nameCode":"currency.USD","displayLabel":"US Dollar ($)"}
+  	}
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_retrieve" name="loans_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Loan</h4>
+					<p>
+						<b>Note:</b> template=true parameter doesn't apply to this resource.
+					</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>associations</dt>
+						<dd>
+							optional, <span>Either 'all' or a comma separated list of
+								loan 'associations' (itemised below).</span>
+						</dd>
+						<dt>exclude</dt>
+						<dd>
+							optional, <span>'all' and a comma separated list of
+								loan associations (itemised below) as 'exclude'.</span>
+						</dd>
+						<dd>
+							<br>Associations are just extra pieces of data that you
+							might or might not want to retrieve.<br>
+							<br>
+						</dd>
+						<dd>
+							<b>'all':</b> Gets all association data.
+						</dd>
+						<dd>
+							<b>'repaymentSchedule':</b> Loan schedule data.
+						</dd>
+						<dd>
+							<b>'originalSchedule':</b> Loan schedule data without interest recalculations.
+						</dd>
+						<dd>
+							<b>'futureSchedule':</b> Loan schedule data from today date(will be displayed only for interest first repayment strategy processors)
+						</dd>
+						<dd>
+							<b>'transactions':</b> Loan transactions data.
+						</dd>
+						<dd>
+							<b>'charges':</b> Loan charges data.
+						</dd>
+						<dd>
+							<b>'guarantors':</b> Loan guarantors data.
+						</dd>
+						<dd>
+							<b>'collateral':</b> Loan collateral data.
+						</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>loans/1</div>
+					<br>
+					<br>
+					<div class=apiClick>loans/1?fields=id,principal,annualInterestRate</div>
+					<br>
+					<br>
+					<div class=apiClick>loans/1?associations=all</div>
+					<br>
+					<div class=apiClick>loans/1?associations=all&exclude=guarantors</div>
+					<br>
+					<br>
+					<div class=apiClick>loans/1?fields=id,principal,annualInterestRate&associations=repaymentSchedule,transactions</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "accountNo": "000000001",
+  "status": {
+    "id": 300,
+    "code": "loanStatusType.active",
+    "value": "Active",
+    "pendingApproval": false,
+    "waitingForDisbursal": false,
+    "active": true,
+    "closedObligationsMet": false,
+    "closedWrittenOff": false,
+    "closedRescheduled": false,
+    "closed": false,
+    "overpaid": false
+  },
+  "clientId": 1,
+  "clientName": "Kampala first Client",
+  "clientOfficeId": 2,
+  "loanProductId": 1,
+  "loanProductName": "Kampala Product (with cash accounting)",
+  "loanProductDescription": "Typical Kampala loan product with cash accounting enabled for testing.",
+  "loanPurposeId": 22,
+  "loanPurposeName": "option.HousingImprovement",
+  "loanOfficerId": 2,
+  "loanOfficerName": "LoanOfficer, Kampala",
+  "loanType": {
+    "id": 1,
+    "code": "loanType.individual",
+    "value": "Individual"
+  },
+  "currency": {
+    "code": "UGX",
+    "name": "Uganda Shilling",
+    "decimalPlaces": 2,
+    "displaySymbol": "USh",
+    "nameCode": "currency.UGX",
+    "displayLabel": "Uganda Shilling (USh)"
+  },
+  "principal": 1000000,
+  "termFrequency": 12,
+  "termPeriodFrequencyType": {
+    "id": 2,
+    "code": "termFrequency.periodFrequencyType.months",
+    "value": "Months"
+  },
+  "numberOfRepayments": 12,
+  "repaymentEvery": 1,
+  "repaymentFrequencyType": {
+    "id": 2,
+    "code": "repaymentFrequency.periodFrequencyType.months",
+    "value": "Months"
+  },
+  "interestRatePerPeriod": 24,
+  "interestRateFrequencyType": {
+    "id": 3,
+    "code": "interestRateFrequency.periodFrequencyType.years",
+    "value": "Per year"
+  },
+  "annualInterestRate": 24,
+  "amortizationType": {
+    "id": 1,
+    "code": "amortizationType.equal.installments",
+    "value": "Equal installments"
+  },
+  "interestType": {
+    "id": 1,
+    "code": "interestType.flat",
+    "value": "Flat"
+  },
+  "interestCalculationPeriodType": {
+    "id": 1,
+    "code": "interestCalculationPeriodType.same.as.repayment.period",
+    "value": "Same as repayment period"
+  },
+  "transactionProcessingStrategyId": 2,
+  "timeline": {
+    "submittedOnDate": [
+      2012,
+      4,
+      3
+    ],
+    "submittedByUsername": "admin",
+    "submittedByFirstname": "App",
+    "submittedByLastname": "Administrator",
+    "approvedOnDate": [
+      2012,
+      4,
+      3
+    ],
+    "approvedByUsername": "admin",
+    "approvedByFirstname": "App",
+    "approvedByLastname": "Administrator",
+    "expectedDisbursementDate": [
+      2012,
+      4,
+      10
+    ],
+    "actualDisbursementDate": [
+      2012,
+      4,
+      10
+    ],
+    "disbursedByUsername": "admin",
+    "disbursedByFirstname": "App",
+    "disbursedByLastname": "Administrator",
+    "expectedMaturityDate": [
+      2013,
+      4,
+      10
+    ]
+  },
+  "summary": {
+    "currency": {
+      "code": "UGX",
+      "name": "Uganda Shilling",
+      "decimalPlaces": 2,
+      "displaySymbol": "USh",
+      "nameCode": "currency.UGX",
+      "displayLabel": "Uganda Shilling (USh)"
+    },
+    "principalDisbursed": 1000000,
+    "principalPaid": 0,
+    "principalWrittenOff": 0,
+    "principalOutstanding": 1000000,
+    "principalOverdue": 833333.3,
+    "interestCharged": 240000,
+    "interestPaid": 0,
+    "interestWaived": 0,
+    "interestWrittenOff": 0,
+    "interestOutstanding": 240000,
+    "interestOverdue": 200000,
+    "feeChargesCharged": 18000,
+    "feeChargesDueAtDisbursementCharged": 0,
+    "feeChargesPaid": 0,
+    "feeChargesWaived": 0,
+    "feeChargesWrittenOff": 0,
+    "feeChargesOutstanding": 18000,
+    "feeChargesOverdue": 15000,
+    "penaltyChargesCharged": 0,
+    "penaltyChargesPaid": 0,
+    "penaltyChargesWaived": 0,
+    "penaltyChargesWrittenOff": 0,
+    "penaltyChargesOutstanding": 0,
+    "penaltyChargesOverdue": 0,
+    "totalExpectedRepayment": 1258000,
+    "totalRepayment": 0,
+    "totalExpectedCostOfLoan": 258000,
+    "totalCostOfLoan": 0,
+    "totalWaived": 0,
+    "totalWrittenOff": 0,
+    "totalOutstanding": 1258000,
+    "totalOverdue": 1048333.3,
+    "overdueSinceDate": [
+      2012,
+      5,
+      10
+    ],
+    "linkedAccount":{
+    	"id":1,
+    	"accountNo":"000000001"
+    },
+    "disbursementDetails":[{"id":71,"expectedDisbursementDate":[2013,11,1],"principal":22000.000000,"approvedPrincipal":22000.000000}],
+    "fixedEmiAmount":1100.000000,
+    "maxOutstandingLoanBalance":35000,
+    "canDisburse":false,
+    "emiAmountVariations": [],
+  "inArrears": true,
+  "isNPA":false,
+  "overdueCharges": [
+    {
+      "id": 20,
+      "name": "overdraft penality",
+      "active": true,
+      "penalty": true,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 3.000000,
+      "chargeTimeType": {
+        "id": 9,
+        "code": "chargeTimeType.overdueInstallment",
+        "value": "overdue fees"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 2,
+        "code": "chargeCalculationType.percent.of.amount",
+        "value": "% Amount"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      },
+      "feeInterval": 2,
+      "feeFrequency": {
+        "id": 1,
+        "code": "feeFrequencyperiodFrequencyType.weeks",
+        "value": "Weeks"
+      }
+    }
+  ]
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_list" name="loans_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Loans</h4>
+					<p>The <i>list</i> capability of loans can support <b>pagination</b> and <b>sorting</b>.</p>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>offset</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 0
+						</dd>
+						<dd>Indicates from what result to start from.</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+						<dt>orderBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>displayName, accountNo, officeId, officeName</span>
+						</dd>
+						<dd>Orders the results by the field indicated.</dd>
+
+						<dt>sortBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>ASC, DESC</span>
+						</dd>
+						<dd>Indicates what way to order results if <i>orderBy</i> is used.</dd>
+
+						<dt>officeId</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dd>Provides ability to restrict list of loans returned based on the office there associated with.</dd>
+
+						<dt>underHierarchy</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use the office hierarchy string to return all loans under a given hierarchy.</dd>
+
+						<dt>accountNo</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use account no. of loans to restrict results.</dd>
+
+						<dt>externalId</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use externalId of loan to restrict results.</dd>
+
+						<dt>sqlSearch</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use an sql fragment valid for the underlying loan schema to filter results. e.g. display_name like %K%</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>loans</div>
+					<br>
+					<div class=apiClick>loans?fields=accountNo</div>
+					<br>
+					<div class=apiClick>loans?offset=10&limit=50</div>
+					<br>
+					<div class=apiClick>loans?orderBy=accountNo&sortOrder=DESC</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/fineract-provider/api/v1/loans
+					</code>
+					<code class="method-response">
+{
+  "totalFilteredRecords": 1,
+  "pageItems": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "status": {
+        "id": 300,
+        "code": "loanStatusType.active",
+        "value": "Active",
+        "pendingApproval": false,
+        "waitingForDisbursal": false,
+        "active": true,
+        "closedObligationsMet": false,
+        "closedWrittenOff": false,
+        "closedRescheduled": false,
+        "closed": false,
+        "overpaid": false
+      },
+      "clientId": 1,
+      "clientName": "Change To IndividualName",
+      "clientOfficeId": 1,
+      "loanProductId": 1,
+      "loanProductName": "AgriCredit",
+      "loanProductDescription": "test",
+      "loanType": {
+        "id": 1,
+        "code": "loanType.individual",
+        "value": "Individual"
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "principal": 10000,
+      "termFrequency": 12,
+      "termPeriodFrequencyType": {
+        "id": 2,
+        "code": "termFrequency.periodFrequencyType.months",
+        "value": "Months"
+      },
+      "numberOfRepayments": 12,
+      "repaymentEvery": 1,
+      "repaymentFrequencyType": {
+        "id": 2,
+        "code": "repaymentFrequency.periodFrequencyType.months",
+        "value": "Months"
+      },
+      "interestRatePerPeriod": 2,
+      "interestRateFrequencyType": {
+        "id": 2,
+        "code": "interestRateFrequency.periodFrequencyType.months",
+        "value": "Per month"
+      },
+      "annualInterestRate": 24,
+      "amortizationType": {
+        "id": 1,
+        "code": "amortizationType.equal.installments",
+        "value": "Equal installments"
+      },
+      "interestType": {
+        "id": 0,
+        "code": "interestType.declining.balance",
+        "value": "Declining Balance"
+      },
+      "interestCalculationPeriodType": {
+        "id": 1,
+        "code": "interestCalculationPeriodType.same.as.repayment.period",
+        "value": "Same as repayment period"
+      },
+      "transactionProcessingStrategyId": 2,
+      "timeline": {
+        "submittedOnDate": [
+          2012,
+          6,
+          1
+        ],
+        "submittedByUsername": "mifos",
+        "submittedByFirstname": "App",
+        "submittedByLastname": "Administrator",
+        "approvedOnDate": [
+          2012,
+          6,
+          1
+        ],
+        "approvedByUsername": "mifos",
+        "approvedByFirstname": "App",
+        "approvedByLastname": "Administrator",
+        "expectedDisbursementDate": [
+          2012,
+          6,
+          1
+        ],
+        "actualDisbursementDate": [
+          2012,
+          6,
+          1
+        ],
+        "disbursedByUsername": "mifos",
+        "disbursedByFirstname": "App",
+        "disbursedByLastname": "Administrator",
+        "expectedMaturityDate": [
+          2013,
+          6,
+          1
+        ]
+      },
+      "linkedAccount":{
+    	"id":1,
+    	"accountNo":"000000001"
+      },
+      "summary": {
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        },
+        "principalDisbursed": 10000,
+        "principalPaid": 3073.07,
+        "principalWrittenOff": 0,
+        "principalOutstanding": 6926.93,
+        "principalOverdue": 5999.92,
+        "interestCharged": 1347.15,
+        "interestPaid": 709.33,
+        "interestWaived": 0,
+        "interestWrittenOff": 0,
+        "interestOutstanding": 637.82,
+        "interestOverdue": 619.28,
+        "feeChargesCharged": 0,
+        "feeChargesDueAtDisbursementCharged": 0,
+        "feeChargesPaid": 0,
+        "feeChargesWaived": 0,
+        "feeChargesWrittenOff": 0,
+        "feeChargesOutstanding": 0,
+        "feeChargesOverdue": 0,
+        "penaltyChargesCharged": 0,
+        "penaltyChargesPaid": 0,
+        "penaltyChargesWaived": 0,
+        "penaltyChargesWrittenOff": 0,
+        "penaltyChargesOutstanding": 0,
+        "penaltyChargesOverdue": 0,
+        "totalExpectedRepayment": 11347.15,
+        "totalRepayment": 3782.4,
+        "totalExpectedCostOfLoan": 1347.15,
+        "totalCostOfLoan": 709.33,
+        "totalWaived": 0,
+        "totalWrittenOff": 0,
+        "totalOutstanding": 7564.75,
+        "totalOverdue": 6619.2,
+        "overdueSinceDate": [
+          2012,
+          11,
+          1
+        ]
+      },
+      "feeChargesAtDisbursementCharged": 0,
+      "inArrears":false,
+      "isNPA":false
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_update" name="loans_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h2>Modify a loan application</h2>
+					<p>Loan application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">PUT https://Domain Name/api/v1/loans/{loanId}</code>
+					<code class="method-request">PUT loans/1
+Content-Type: application/json
+No Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "productId": 1,
+  "principal": "5000",
+  "loanTermFrequency": 10,
+  "loanTermFrequencyType": 0,
+  "numberOfRepayments": 10,
+  "repaymentEvery": 1,
+  "repaymentFrequencyType": 0,
+  "interestRatePerPeriod": 2,
+  "interestType": 0,
+  "interestCalculationPeriodType": 0,
+  "amortizationType": 1,
+  "expectedDisbursementDate": "04 March 2014",
+  "transactionProcessingStrategyId": 1
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 1,
+  "changes": {
+    "principal": 5000,
+    "locale": "en"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_calculate" name="loans_calculate" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Calculate loan repayment schedule</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>productId, principal, loanTermFrequency,
+								loanTermFrequencyType, numberOfRepayments, repaymentEvery,
+								repaymentFrequencyType, interestRatePerPeriod,
+								amortizationType, interestType,
+								interestCalculationPeriodType, expectedDisbursementDate,
+								transactionProcessingStrategyId</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans?command=calculateLoanSchedule
+					</code>
+					<code class="method-request">
+POST loans?command=calculateLoanSchedule
+Content-Type: application/json
+Request Body:
+{
+    "dateFormat": "dd MMMM yyyy",
+    "locale": "en_GB",
+    "productId": 1,
+    "principal": "100,000.00",
+    "loanTermFrequency": 12,
+    "loanTermFrequencyType": 2,
+    "numberOfRepayments": 12,
+    "repaymentEvery": 1,
+    "repaymentFrequencyType": 2,
+    "interestRatePerPeriod": 2,
+    "amortizationType": 1,
+    "interestType": 0,
+    "interestCalculationPeriodType": 1,
+    "expectedDisbursementDate": "20 September 2011",
+    "transactionProcessingStrategyId": 2
+}
+					</code>
+					<code class="method-response">
+{
+  "currency": {
+    "code": "UGX",
+    "name": "Uganda Shilling",
+    "decimalPlaces": 2,
+    "displaySymbol": "USh",
+    "nameCode": "currency.UGX",
+    "displayLabel": "Uganda Shilling (USh)"
+  },
+  "loanTermInDays": 366,
+  "totalPrincipalDisbursed": 100000,
+  "totalPrincipalExpected": 100000,
+  "totalPrincipalPaid": 0,
+  "totalInterestCharged": 13471.52,
+  "totalFeeChargesCharged": 0,
+  "totalPenaltyChargesCharged": 0,
+  "totalWaived": 0,
+  "totalWrittenOff": 0,
+  "totalRepaymentExpected": 113471.52,
+  "totalRepayment": 0,
+  "totalOutstanding": 0,
+  "periods": [
+    {
+      "period": 0,
+      "dueDate": [
+        2011,
+        9,
+        20
+      ],
+      "principalDisbursed": 100000,
+      "principalLoanBalanceOutstanding": 100000,
+      "feeChargesDue": 0,
+      "feeChargesOutstanding": 0,
+      "totalOriginalDueForPeriod": 0,
+      "totalDueForPeriod": 0,
+      "totalOutstandingForPeriod": 0,
+      "totalOverdue": 0,
+      "totalActualCostOfLoanForPeriod": 0
+    },
+    {
+      "period": 1,
+      "fromDate": [
+        2011,
+        9,
+        20
+      ],
+      "dueDate": [
+        2011,
+        10,
+        20
+      ],
+      "daysInPeriod": 30,
+      "principalOriginalDue": 7455.96,
+      "principalDue": 7455.96,
+      "principalOutstanding": 7455.96,
+      "principalLoanBalanceOutstanding": 92544.04,
+      "interestOriginalDue": 2000,
+      "interestDue": 2000,
+      "interestOutstanding": 2000,
+      "feeChargesDue": 0,
+      "penaltyChargesDue": 0,
+      "totalOriginalDueForPeriod": 9455.96,
+      "totalDueForPeriod": 9455.96,
+      "totalPaidForPeriod": 0,
+      "totalOutstandingForPeriod": 9455.96,
+      "totalOverdue": 9455.96,
+      "totalActualCostOfLoanForPeriod": 2000
+    },
+    ...
+    ...
+    {
+      "period": 12,
+      "fromDate": [
+        2012,
+        8,
+        20
+      ],
+      "dueDate": [
+        2012,
+        9,
+        20
+      ],
+      "daysInPeriod": 31,
+      "principalOriginalDue": 9270.56,
+      "principalDue": 9270.56,
+      "principalOutstanding": 9270.56,
+      "principalLoanBalanceOutstanding": 0,
+      "interestOriginalDue": 185.4,
+      "interestDue": 185.4,
+      "interestOutstanding": 185.4,
+      "feeChargesDue": 0,
+      "penaltyChargesDue": 0,
+      "totalOriginalDueForPeriod": 9455.96,
+      "totalDueForPeriod": 9455.96,
+      "totalPaidForPeriod": 0,
+      "totalOutstandingForPeriod": 9455.96,
+      "totalOverdue": 9455.96,
+      "totalActualCostOfLoanForPeriod": 185.4
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_create" name="loans_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Submit a new Loan Application</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>clientId, productId, principal, loanTermFrequency,
+								loanTermFrequencyType, loanType, numberOfRepayments, repaymentEvery,
+								repaymentFrequencyType, interestRatePerPeriod,
+								amortizationType, interestType,
+								interestCalculationPeriodType, transactionProcessingStrategyId,
+								expectedDisbursementDate, submittedOnDate, loanType</td>
+						</tr>
+					</table>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Mandatory Fields if interest recalculation is enabled for product and Rest frequency  not same as repayment period</div></td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationRestFrequencyDate</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Mandatory Fields if interest recalculation with interest/fee compounding is enabled for product and compounding frequency not same as repayment period</div></td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationCompoundingFrequencyDate</td>
+						</tr>
+					</table>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, linkAccountId, allowPartialPeriodInterestCalcualtion,
+								 fixedEmiAmount, maxOutstandingLoanBalance, disbursementData, graceOnArrearsAgeing, createStandingInstructionAtDisbursement (requires linkedAccountId if set to true)
+							</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans
+					</code>
+					<code class="method-request">
+POST loans
+Content-Type: application/json
+Request Body:
+{
+    "dateFormat": "dd MMMM yyyy",
+    "locale": "en_GB",
+    "clientId": 1,
+    "productId": 1,
+    "principal": "10,000.00",
+    "loanTermFrequency": 12,
+    "loanTermFrequencyType": 2,
+    "loanType": "individual",
+    "numberOfRepayments": 10,
+    "repaymentEvery": 1,
+    "repaymentFrequencyType": 2,
+    "interestRatePerPeriod": 10,
+    "amortizationType": 1,
+    "interestType": 0,
+    "interestCalculationPeriodType": 1,
+    "transactionProcessingStrategyId": 1,
+    "expectedDisbursementDate": "10 Jun 2013",
+    "submittedOnDate": "10 Jun 2013",
+    "linkAccountId" : "1",
+    "fixedEmiAmount":1100,
+    "maxOutstandingLoanBalance":"35000",
+    "disbursementData":[{"expectedDisbursementDate":"01 November 2013",
+    					"principal":22000,"approvedPrincipal":22000}]
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "clientId": 1,
+    "loanId": 1,
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_approve" name="loans_approve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Approve Loan Application</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>approvedOnDate</td>
+						</tr>
+					</table>
+				</br>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>approvedLoanAmount</td>
+						</tr>
+						<tr class=alt>
+							<td>expectedDisbursementDate</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}?command=approve
+					</code>
+					<code class="method-request">
+POST loans/1?command=approve
+Content-Type: application/json
+Request Body:
+{
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "approvedOnDate": "20 September 2011",
+    "expectedDisbursementDate" : "20 September 2011",
+    "note": "Loan approval note",
+    "disbursementData" : [{ id=226, principal="5", expectedDisbursementDate="20 September 2011"},
+    { id=227, principal="91", expectedDisbursementDate="21 September 2011"}]
+}
+</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 200,
+      "code": "loanStatusType.approved",
+      "value": "Approved",
+      "pendingApproval": false,
+      "waitingForDisbursal": true,
+      "active": false,
+      "closedObligationsMet": false,
+      "closedWrittenOff": false,
+      "closedRescheduled": false,
+      "closed": false,
+      "overpaid": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "approvedOnDate": "20 September 2011",
+    "note": "Loan approval note"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="loans_recoverguarantee" name="loans_recoverguarantee" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Recover Loan Guarantee </h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}?command=recoverGuarantees
+					</code>
+					<code class="method-request">
+POST loans/1?command=approve
+Content-Type: application/json
+No Request Body:
+</code>
+					<code class="method-response">
+{
+
+  "loanId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_approve_undo" name="loans_approve_undo" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Undo Loan Application Approval</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}?command=undoApproval
+					</code>
+					<code class="method-request">
+POST loans/1?command=undoApproval
+Content-Type: application/json
+Request Body:
+{
+  "note": "Loan undo approval note"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 100,
+      "code": "loanStatusType.submitted.and.pending.approval",
+      "value": "Submitted and pending approval",
+      "pendingApproval": true,
+      "waitingForDisbursal": false,
+      "active": false,
+      "closedObligationsMet": false,
+      "closedWrittenOff": false,
+      "closedRescheduled": false,
+      "closed": false,
+      "overpaid": false
+    },
+    "approvedOnDate": ""
+  }
+}
+					</code>
+				</div>
+			</div>
+
+        <a id="loans_assignLoanOfficer" name="loans_assignLoanOfficer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Assign a Loan Officer</h4>
+	        <p>Allows you to assign <i>Loan Officer</i> for existing Loan.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/loans/{loanId}?command=assignLoanOfficer</code>
+	        <code class="method-request">POST loans/1?command=assignLoanOfficer
+Content-Type: application/json
+Request Body:
+{
+	"toLoanOfficerId":2,
+	"assignmentDate":"02 September 2014",
+	"locale":"en",
+	"dateFormat":"dd MMMM yyyy",
+	"fromLoanOfficerId":""
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 6,
+  "loanId"	: 3,
+  "resourceId": 3
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="loans_unassignLoanOfficer" name="loans_unassignLoanOfficer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Unassign a Loan Officer</h4>
+	        <p>Allows you to unassign the <i>Loan Officer</i>.</p>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/loans/{loanId}?command=unassignLoanOfficer</code>
+	        <code class="method-request">POST clients/1?command=unassignLoanOfficer
+Content-Type: application/json
+Request Body:
+{
+	"unassignedDate":"15 September 2014",
+	"locale":"en",
+	"dateFormat":"dd MMMM yyyy"
+}
+			</code>
+	        <code class="method-response">
+{
+	"officeId":2,
+	"clientId":6,
+	"loanId":3,
+	"resourceId":3
+}
+	        </code>
+	    </div>
+	</div>
+
+			<a id="loans_reject" name="loans_reject" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Reject Loan Application</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>rejectedOnDate</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}?command=reject
+					</code>
+					<code class="method-request">
+POST loans/1?command=reject
+Content-Type: application/json
+Request Body:
+{
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "rejectedOnDate": "20 September 2011",
+    "note": "Loan rejection reason."
+}
+</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 500,
+      "code": "loanStatusType.rejected",
+      "value": "Rejected",
+      "pendingApproval": false,
+      "waitingForDisbursal": false,
+      "active": false,
+      "closedObligationsMet": false,
+      "closedWrittenOff": false,
+      "closedRescheduled": false,
+      "closed": false,
+      "overpaid": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "rejectedOnDate": "20 September 2011",
+    "closedOnDate": "20 September 2011"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_withdraw" name="loans_withdraw" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Applicant Withdraws from Loan Application</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>withdrawnOnDate</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}?command=withdrawnByApplicant
+					</code>
+					<code class="method-request">
+POST loans/1?command=withdrawnByApplicant
+Content-Type: application/json
+Request Body:
+{
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "withdrawnOnDate": "20 September 2011",
+    "note": "Reason loan applicant withdrew from application."
+}
+</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 2,
+  "resourceId": 2,
+  "changes": {
+    "status": {
+      "id": 400,
+      "code": "loanStatusType.withdrawn.by.client",
+      "value": "Withdrawn by applicant",
+      "pendingApproval": false,
+      "waitingForDisbursal": false,
+      "active": false,
+      "closedObligationsMet": false,
+      "closedWrittenOff": false,
+      "closedRescheduled": false,
+      "closed": false,
+      "overpaid": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "withdrawnOnDate": "20 September 2011",
+    "closedOnDate": "20 September 2011"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_disburse" name="loans_disburse" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Disburse Loan</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>actualDisbursementDate</td>
+						</tr>
+					</table>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>transactionAmount,fixedEmiAmount</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}?command=disburse
+					</code>
+					<code class="method-request">
+POST loans/1?command=disburse
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat": "dd MMMM yyyy",
+  "locale": "en",
+  "transactionAmount":10000,
+  "fixedEmiAmount""1100,
+  "actualDisbursementDate": "14 May 2013",
+  "paymentTypeId": "12",
+  "note": "",
+  "accountNumber": "accno123",
+  "checkNumber": "chec123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 1,
+  "changes": {
+    "accountNumber": "accno123",
+    "checkNumber": "chec123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123",
+    "status": {
+      "id": 300,
+      "code": "loanStatusType.active",
+      "value": "Active",
+      "pendingApproval": false,
+      "waitingForDisbursal": false,
+      "active": true,
+      "closedObligationsMet": false,
+      "closedWrittenOff": false,
+      "closedRescheduled": false,
+      "closed": false,
+      "overpaid": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "actualDisbursementDate": "14 May 2013",
+    "transactionAmount":10000
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_disburse_to_savings" name="loans_disburse_to_savings" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Disburse Loan To Savings Account</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>actualDisbursementDate</td>
+						</tr>
+					</table>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>transactionAmount,fixedEmiAmount</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}?command=disburseToSavings
+					</code>
+					<code class="method-request">
+POST loans/1?command=disburse
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat": "dd MMMM yyyy",
+  "locale": "en",
+  "transactionAmount":10000,
+  "fixedEmiAmount""1100,
+  "actualDisbursementDate": "14 May 2013",
+  "note": ""
+</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 300,
+      "code": "loanStatusType.active",
+      "value": "Active",
+      "pendingApproval": false,
+      "waitingForDisbursal": false,
+      "active": true,
+      "closedObligationsMet": false,
+      "closedWrittenOff": false,
+      "closedRescheduled": false,
+      "closed": false,
+      "overpaid": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "actualDisbursementDate": "14 May 2013",
+    "transactionAmount":10000
+  }
+}
+					</code>
+				</div>
+			</div>
+
+<a id="loans_disburse_undo" name="loans_disburse_undo" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Undo Loan Disbursal</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}?command=undoDisbursal
+					</code>
+					<code class="method-request">
+POST loans/1?command=undoDisbursal
+Content-Type: application/json
+{
+  "note": "Some comment"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 200,
+      "code": "loanStatusType.approved",
+      "value": "Approved",
+      "pendingApproval": false,
+      "waitingForDisbursal": true,
+      "active": false,
+      "closedObligationsMet": false,
+      "closedWrittenOff": false,
+      "closedRescheduled": false,
+      "closed": false,
+      "overpaid": false
+    },
+    "actualDisbursementDate": ""
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_delete" name="loans_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Loan Application</h4>
+					<p>
+						<b>Note:</b> Only loans in "Submitted and awaiting approval"
+						status can be deleted.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/loans/{loanId}
+					</code>
+					<code class="method-request">
+DELETE loans/1
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "clientId": 1,
+    "loanId": 1,
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- Loan Transactions API-->
+			<a id="loans_transactions" name="loans_transactions" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Loan Transactions</h3>
+					<p>Capabilities include loan repayment's, interest waivers and the ability to 'adjust' an existing transaction. An 'adjustment' of a transaction is really a 'reversal' of existing transaction followed by creation of a new transaction with the provided details.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>transactionAmount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The amount of the transaction.</td>
+						</tr>
+						<tr class=alt>
+							<td>transactionDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The date of the transaction.</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="loans_repaymenttemplate_etc"
+				name="loans_repaymenttemplate_etc" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Loan Transaction Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>command</dt>
+						<dd>
+							String<span> mandatory, case-insensitive</span>
+						</dd>
+						<dd>
+							<b>'repayment'</b><br> "date" is set to the date of the
+							first outstanding installment.<br> "total" "amount" is set
+							to the amount outstanding.
+						</dd>
+						<dd>
+							<b>'waiver'</b><br> "date" is set to the current date.<br>
+							"total" "amount" is set to the remaining loan principal
+							outstanding. The amount that can be waived is limited to the
+							loan's inArrearsTolerance
+						</dd>
+						<dd>
+							<b>'refundbycash'</b><br> "date" is set to the current date <br>
+							"total" "amount" is set
+							to the total amount paid in advance.
+						</dd>
+					</dl>
+					<p>Example Request:</p>
+					<div class=apiClick>loans/1/transactions/template?command=repayment</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/transactions/template"
+					</code>
+					<code class="method-response">
+{
+    "transactionType": {
+        "id": 2,
+        "code": "loanTransactionType.repayment",
+        "value": "Repayment"
+    },
+    "date": [
+        2009,
+        8,
+        1
+    ],
+    "total": {
+        "currencyCode": "XOF",
+        "digitsAfterDecimal": 0,
+        "inMultiplesOf": 0,
+        "amount": 471,
+        "defaultName": "CFA Franc BCEAO",
+        "nameCode": "currency.XOF",
+        "displaySymbol": "CFA",
+        "zero": false,
+        "greaterThanZero": true,
+        "displaySymbolValue": "471 CFA"
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_transaction_repayment" name="loans_transaction_repayment" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Make a Repayment </h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}/transactions?command=repayment
+					</code>
+					<code class="method-request">
+POST loans/5/transactions?command=repayment
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat": "dd MMMM yyyy",
+  "locale": "en",
+  "transactionDate": "14 May 2013",
+  "transactionAmount": "500.00",
+  "paymentTypeId": "12",
+  "note": "check payment",
+  "accountNumber": "acc123",
+  "checkNumber": "che123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 5,
+  "resourceId": 564,
+  "changes": {
+    "transactionDate": "14 May 2013",
+    "transactionAmount": "500.00",
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "note": "check payment",
+    "accountNumber": "acc123",
+    "checkNumber": "che123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_transaction_refund_by_cash" name="loans_transaction_refund_by_cash" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Make a Refund of an Active Loan by Cash </h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}/transactions?command=refundByCash
+					</code>
+					<code class="method-request">
+POST loans/5/transactions?command=refundByCash
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat": "dd MMMM yyyy",
+  "locale": "en",
+  "transactionDate": "14 May 2013",
+  "transactionAmount": "500.00",
+  "paymentTypeId": "12"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 5,
+  "resourceId": 564,
+  "changes": {
+    "transactionDate": "14 May 2013",
+    "transactionAmount": "500.00",
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy
+  }
+}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="loans_transaction_waiveinterest" name="loans_transaction_waiveinterest" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Waive Interest</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}/transactions?command=waiveInterest
+					</code>
+					<code class="method-request">
+POST loans/5/transactions?command=waiveInterest
+Content-Type: application/json
+Request Body:
+{
+	"locale": "en_GB",
+	"dateFormat": "dd MMMM yyyy",
+	"transactionDate": "14 May 2012",
+	"transactionAmount": "400",
+	"note": "Optional note related to the waiving of interest."
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 5,
+  "resourceId": 5,
+  "changes": {
+    "transactionDate": "14 May 2012",
+    "transactionAmount": "400",
+    "locale": "en_GB",
+    "dateFormat": "dd MMMM yyyy",
+    "note": "Optional note related to the interest waiver."
+  }
+}
+					</code>
+				</div>
+			</div>
+
+
+            <a id="loans_transaction_write-off_loan" name="loans_transaction_write-off_loan" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Write-off Loan</h4>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+                        POST https://DomainName/api/v1/loans/{loanId}/transactions?command=writeoff
+                    </code>
+                    <code class="method-request">
+                        POST loans/70/transactions?command=writeoff
+                        Content-Type: application/json
+                        Request Body:
+                        {
+                        "locale": "en_GB",
+                        "dateFormat": "dd MMMM yyyy",
+                        "transactionDate": "14 May 2012",
+                        "note": "Write-off note"
+                        }
+                    </code>
+                    <code class="method-response">
+                        {
+                        "officeId": 1,
+                        "clientId": 7,
+                        "loanId": 70,
+                        "resourceId": 442,
+                        "changes": {
+                        "transactionDate": "1 March 2012",
+                        "locale": "en",
+                        "dateFormat": "dd MMMM yyyy",
+                        "status": {
+                        "id": 601,
+                        "code": "loanStatusType.closed.written.off",
+                        "value": "Closed (written off)",
+                        "pendingApproval": false,
+                        "waitingForDisbursal": false,
+                        "active": false,
+                        "closedObligationsMet": false,
+                        "closedWrittenOff": true,
+                        "closedRescheduled": false,
+                        "closed": true,
+                        "overpaid": false
+                        },
+                        "closedOnDate": "1 March 2012",
+                        "writtenOffOnDate": "1 March 2012",
+                        "note": "Write-off note"
+                        }
+                        }
+
+                    </code>
+                </div>
+            </div>
+        <!------recovery payment -->
+		        <a id="loans_transaction_recovery_payment" name="loans_transaction_recovery_payment" class="old-syle-anchor">&nbsp;</a>
+	    		<div class="method-section">
+				<div class="method-description">
+					<h4>Make Recovery Payment </h4>
+					<p>This API allows collecting recovery payments for written-off loans</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}/transactions?command=recoverypayment
+					</code>
+					<code class="method-request">
+POST loans/5/transactions?command=recoverypayment
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat": "dd MMMM yyyy",
+  "locale": "en",
+  "transactionDate": "14 May 2013",
+  "transactionAmount": "500.00",
+  "paymentTypeId": "12",
+  "note": "check payment",
+  "accountNumber": "acc123",
+  "checkNumber": "che123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 5,
+  "resourceId": 564,
+  "changes": {
+    "transactionDate": "14 May 2013",
+    "transactionAmount": "500.00",
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "note": "check payment",
+    "accountNumber": "acc123",
+    "checkNumber": "che123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+
+		<!--- end of recovery payment -->
+
+            <a id="loans_transaction_undo_write-off_loan" name="loans_transaction_undo_write-off_loan" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Undo Loan Write-off Transaction</h4>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+                        POST https://DomainName/api/v1/loans/{loanId}/transactions?command=undowriteoff
+                    </code>
+                    <code class="method-request">
+                        POST loans/70/transactions?command=undowriteoff
+                        Content-Type: application/json
+                        Request Body:
+                        { }
+                    </code>
+                    <code class="method-response">
+
+		        {
+		          "officeId": 1,
+		          "clientId": 1,
+		          "loanId": 22
+		        }
+                    </code>
+                </div>
+            </div>
+
+
+			<a id="loans_transaction_pre_close_loan" name="loans_transaction_pre_close_loan" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+					<h4> Pre-Close template</h4>
+                    <p>This Api retrieves pre closure details of loan</p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+                        POST https://DomainName/api/v1/loans/{loanId}/transactions?command=prepayLoan   <b>or</b><br/>
+
+						https://DomainName/api/v1/loans/{loanId}/transactions?command=prepayLoan&dateFormat=dd+MMMM+yyyy&locale=en&transactionDate=02+April+2015
+                    </code>
+                    <code class="method-request">
+                        POST loans/70/transactions?command=prepayLoan
+                        Content-Type: application/json
+                        Request Body:
+                        { }
+                    </code>
+                    <code class="method-response">
+
+		        {
+		          "amount":7765.28,
+				  "principalPortion":7573.76,
+				  "interestPortion":191.52,
+				  "feeChargesPortion":0.00,
+				  "penaltyChargesPortion":0.00
+		        }
+                    </code>
+                </div>
+            </div>
+
+            <a id="loans_transaction_retrieve" name="loans_transaction_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Transaction Details</h4>
+					<p>Example Request:</p>
+					<div class=apiClick>loans/5/transactions/3</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/transactions/{transactionId}
+					</code>
+					<code class="method-response">
+{
+  "id": 3,
+  "type": {
+    "id": 2,
+    "code": "loanTransactionType.repayment",
+    "value": "Repayment",
+    "disbursement": false,
+    "repaymentAtDisbursement": false,
+    "repayment": true,
+    "contra": false,
+    "waiveInterest": false,
+    "waiveCharges": false,
+    "writeOff": false,
+    "recoveryRepayment": false
+  },
+  "date": [
+    2012,
+    5,
+    14
+  ],
+  "manuallyReversed": false,
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 559.88,
+  "interestPortion": 559.88
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_transaction_adjust" name="loans_transaction_adjust" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Adjust a Transaction</h4>
+					<p><b>Note:</b> there is no need to specify command={transactionType} parameter.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>transactionDate, transactionAmount</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}/transactions/{transactionId}
+					</code>
+					<code class="method-request">
+POST loans/1/transactions/2
+Content-Type: application/json
+Request Body:
+{
+    "locale": "en_GB",
+    "dateFormat": "dd MMMM yyyy",
+    "transactionDate": "25 May 2012",
+    "transactionAmount": "50,000.00",
+    "note": "An optional note about why your adjusting or changing the transaction."
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 16
+}
+					</code>
+				</div>
+			</div>
+
+			<!--Start of guarantors functionality-->
+
+			<a id="guarantors" name="guarantors" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Guarantors</h3>
+					<p>A person who guarantees to pay for someone else's debt
+					if he or she should default on a loan obligation. A guarantor acts as a co-signor of sorts.
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>guarantorType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Identifies if the guarantor is
+							an existing Client, Staff member or an external
+							individual <br/>
+							Refer <a href="#guarantors_template">Retrieve
+							Guarantor Details Template</a> for complete
+							details
+						</td>
+						</tr>
+						<tr class=alt>
+							<td>entityId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The identifier for guarantors
+							who are an existing Client of Staff member.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>firstname</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Guarantors first name
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>lastname</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Guarantors last name
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>officeName</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name of the office with which the internal guarantors are associated
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>addressLine1, addressLine2, city, state, zip,
+							country, mobileNumber, housePhoneNumber, comment,
+							dob</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Address, contact and date of
+							birth details of an external guarantor
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>savingsId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Guarantors Savings Account on which Amount will be blocked
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>amount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Guarantee amount which will be blocked on savings account.
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="guarantors_list" name="guarantors_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Guarantors</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>loans/1/guarantors</div>
+					<br>
+					<br>
+					<div class=apiClick>loans/1/guarantors?fields=firstname,lastname</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/guarantors
+					</code>
+					<code class="method-response">
+[
+    {
+        "id": 1,
+        "loanId": 1,
+        "guarantorType": {
+            "id": 1,
+            "code": "guarantor.existing.customer",
+            "value": "CUSTOMER"
+        },
+        "firstname": "Declan",
+        "lastname": "Browne",
+        "officeName": "Head Office",
+        "joinedDate": [
+            2009,
+            1,
+            4
+        ]
+    }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="guarantors_template" name="guarantors_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Guarantors Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>loans/1/guarantors/template</div>
+					<div class=apiClick>loans/1/guarantors/accounts/template?clientId=1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/guarantors/template
+					</code>
+					<code class="method-response">
+{
+    "guarantorType": {
+        "id": 1,
+        "code": "guarantor.existing.customer",
+        "value": "CUSTOMER"
+    },
+    "guarantorTypeOptions": [
+        {
+            "id": 1,
+            "code": "guarantor.existing.customer",
+            "value": "CUSTOMER"
+        },
+        {
+            "id": 2,
+            "code": "guarantor.staff",
+            "value": "STAFF"
+        },
+        {
+            "id": 3,
+            "code": "guarantor.external",
+            "value": "EXTERNAL"
+        }
+    ]
+}
+					</code>
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/guarantors/accounts/template?clientId={clientId}
+					</code>
+					<code class="method-response">
+{
+  "guarantorType": {
+    "id": 1,
+    "code": "guarantor.existing.customer",
+    "value": "CUSTOMER"
+  },
+  "status": false,
+  "accountLinkingOptions": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "clientId": 3,
+      "clientName": "Client_FirstName_UUV18 Client_LastName_9T2D",
+      "productId": 1,
+      "productName": "FIXED_DEPOSIT_PRODUCT_U0NS9T",
+      "fieldOfficerId": 0,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 4,
+        "inMultiplesOf": 100,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      }
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="guarantors_retrieve" name="guarantors_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Guarantor</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>/loans/1/guarantors/1</div>
+					<br>
+					<br>
+					<div class=apiClick>/loans/1/guarantors/1</div>
+					<br>
+					<br>
+					<div class=apiClick>/loans/1/guarantors/1?fields=firstname</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/guarantors/{guarantorId}
+					</code>
+					<code class="method-response">
+{
+    "id": 1,
+    "loanId": 1,
+    "guarantorType": {
+        "id": 1,
+        "code": "guarantor.existing.customer",
+        "value": "CUSTOMER"
+    },
+    "firstname": "Declan",
+    "officeName": "Head Office",
+    "joinedDate": [
+        2009,
+        1,
+        4
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="guarantors_create" name="guarantors_create" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Create a Guarantor</h4>
+                    <p>
+                        <b>Note:</b> You may associate any number of Guarantors
+                        to a Loan. The mandatory fields would vary based
+                        on the "guarantorType"<br>
+                    </p>
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields
+								for "internal" guarantors</div></td>
+						</tr>
+						<tr class=alt>
+							<td>guarantorTypeId, entityId</td>
+						</tr>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields for "external" guarantors</div></td>
+						</tr>
+						<tr class=alt>
+							<td>guarantorTypeId, firstname, lastname</td>
+						</tr>
+                    </table>
+					 <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields
+								for "internal" guarantors</div></td>
+						</tr>
+						<tr class=alt>
+							<td>savingsId, amount</td>
+						</tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1//loans/{loanId}}/guarantors
+                    </code>
+                    <code class="method-request">
+POST /loans/1/guarantors
+Content-Type: application/json
+Request Body:
+{
+  guarantorTypeId:3,
+  firstname:Lyndon,
+  lastname:Johnson
+}
+                    </code>
+                    <code class="method-response">
+{
+    "officeId": 2,
+    "loanId": 1,
+    "resourceId": 9
+}
+                    </code>
+                    <code class="method-request">
+POST /loans/1/guarantors
+Content-Type: application/json
+Request Body:
+{
+  guarantorTypeId:1,
+  entityId:2
+}
+                    </code>
+                    <code class="method-response">
+{
+    "officeId": 2,
+    "loanId": 1,
+    "resourceId": 10
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="guarantors_update" name="guarantors_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Guarantor</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/loans/{loanId}/guarantors/{guarantorId}
+					</code>
+					<code class="method-request">
+PUT loans/1/guarantors/1
+Content-Type: application/json
+Request Body:
+{
+  entityId:1
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 1,
+    "changes": {
+        "entityId": 1
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="guarantors_delete" name="guarantors_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Remove a Guarantor</h4>
+					<p>
+						<b>Note:</b> A guarantor can be removed only from loans that are not yet approved.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/loans/{loanId}/guarantors/{guarantorId}
+DELETE https://DomainName/api/v1/loans/{loanId}/guarantors/{guarantorId}?guarantorFundingId={guarantorFundingId}
+					</code>
+
+					<code class="method-request">
+DELETE loans/1/guarantors/1
+DELETE loans/1/guarantors/1?guarantorFundingId=1
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+    "officeId": 2,
+    "loanId": 1,
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+<!-- start of collaterals content -->
+			<a id="collaterals" name="collaterals" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3> Loan Collateral</h3>
+					<p>In lending agreements, collateral is a borrower's pledge of specific property to a lender,
+					to secure repayment of a loan. The collateral serves as protection for a lender against a
+					borrower's default - that is, any borrower failing to pay the principal and interest under
+					the terms of a loan obligation. If a borrower does default on a loan (due to insolvency
+					or other event), that borrower forfeits (gives up) the property pledged as collateral
+					- and the lender then becomes the owner of the collateral
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>type</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Identifies the type of a collateral <br/>
+							Ex: Gold, property etc (permitted types can be configured using
+							<a href="#configs_codes_codevalues">Code Values</a> for
+							the <a href="#configs_codes">Code</a> <i>LoanCollateral</i>) <br/>
+							Refer <a href="#collaterals_template">Retrieve
+							Collateral Details Template</a> for complete
+							details
+						</td>
+						</tr>
+						<tr class=alt>
+							<td>value</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The market value of a Collateral.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>description</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Description for the collateral
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>currency</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Details of the currency used
+								to estimate the value of a Collateral.
+								The currency defaults to the currency of the
+								Loan for which this Collateral serves as a
+								guarantee
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="collaterals_list" name="collaterals_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Loan Collaterals</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>loans/1/collaterals</div>
+					<br>
+					<br>
+					<div class=apiClick>loans/1/collaterals?fields=value,description</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/collaterals
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 12,
+    "type": {
+      "id": 8,
+      "name": "Gold"
+    },
+    "value": 50000,
+    "description": "24 Carat Gold chain weighing 12 grams",
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    }
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="collaterals_template" name="collaterals_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Collateral Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>loans/1/collaterals/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/collaterals/template
+					</code>
+					<code class="method-response">
+{
+  "allowedCollateralTypes": [
+    {
+      "id": 9,
+      "name": "Silver",
+      "position": 0
+    },
+    {
+      "id": 8,
+      "name": "Gold",
+      "position": 0
+    },
+    {
+      "id": 10,
+      "name": "Property",
+      "position": 0
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="collaterals_retrieve" name="collaterals_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Collateral</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>/loans/1/collaterals/1</div>
+					<br>
+					<br>
+					<div class=apiClick>/loans/1/collaterals/1?fields=value,description</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/collaterals/{guarantorId}
+					</code>
+					<code class="method-response">
+{
+  "id": 12,
+  "type": {
+    "id": 8,
+    "name": "Gold"
+  },
+  "value": 50000,
+  "description": "24 Carat Gold chain weighing 12 grams",
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  }
+}				</code>
+				</div>
+			</div>
+
+			<a id="collaterals_create" name="collaterals_create" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Create a Collateral</h4>
+                    <p>
+                        <b>Note:</b> Currently, Collaterals may be added only before a Loan
+                        is approved<br>
+                    </p>
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields
+								for "internal" collaterals</div></td>
+						</tr>
+						<tr class=alt>
+							<td>collateralTypeId</td>
+						</tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1//loans/{loanId}/collaterals
+                    </code>
+                    <code class="method-request">
+POST /loans/1/collaterals
+Content-Type: application/json
+Request Body:
+{
+  collateralTypeId:9,
+}
+                    </code>
+                    <code class="method-response">
+{
+  "loanId": 1,
+  "resourceId": 12
+}
+                    </code>
+                    <code class="method-request">
+POST /loans/1/collaterals
+Content-Type: application/json
+Request Body:
+{
+	"collateralTypeId":"8",
+	"value": "50000",
+	"dateFormat":"dd MMMM yyyy",
+	"locale": "en",
+	"description": "24 Carat Gold chain weighing 12 grams"
+}
+                    </code>
+                    <code class="method-response">
+{
+  "loanId": 1,
+  "resourceId": 13
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="collaterals_update" name="collaterals_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Collateral</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/loans/{loanId}/collaterals/{collateralId}
+					</code>
+					<code class="method-request">
+PUT loans/1/collaterals/12
+Content-Type: application/json
+Request Body:
+{
+"description": "22 Carat Gold chain weighing 12 grams"
+}
+					</code>
+					<code class="method-response">
+{
+  "loanId": 1,
+  "resourceId": 12,
+  "changes": {
+    "description": "22 Carat Gold chain weighing 12 grams"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="collaterals_delete" name="collaterals_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Remove a Collateral</h4>
+					<p>
+						<b>Note:</b> A collateral can only be removed from Loans that are not yet approved.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/loans/{loanId}/collaterals/{collateralId}
+					</code>
+					<code class="method-request">
+DELETE loans/1/collaterals/13
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+  "loanId": 1,
+  "resourceId": 13
+}
+					</code>
+				</div>
+			</div>
+
+<!-- start of Loan charges content-->
+			<a id="loans_charges" name="loans_charges" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Loan Charges</h3>
+					<p>Its typical for MFIs to add extra costs for their loan products. They
+					can be either Fees or Penalties.</p>
+					<p>Loan Charges are instances of <a href="#charges">Charges</a> and
+						represent either fees and penalties for loan products. Refer <a href="#charges">Charges</a>
+						for documentation of the various properties of a charge, Only additional properties (
+						specific to the context of a Charge being associated with a Loan) are
+						described here</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>amountPaid</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The Total amount which has been paid for this Charge
+						</td>
+						</tr>
+						<tr class=alt>
+							<td>amountWaived</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The Total amount that has been waived for this Charge
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>amountWrittenOff</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Total amount written off from this Charge
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>amountOutstanding</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The Total outstanding amount for this Charge
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="loans_charges_list" name="loans_charges_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Loan Charges</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>loans/1/charges</div>
+					<br>
+					<br>
+					<div class=apiClick>loans/1/charges?fields=name,amountOrPercentage</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/charges
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "chargeId": 1,
+    "name": "Loan Processing fee",
+    "chargeTimeType": {
+      "id": 1,
+      "code": "chargeTimeType.disbursement",
+      "value": "Disbursement"
+    },
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 100,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 100,
+    "amountOrPercentage": 100,
+    "penalty": false
+  },
+  {
+    "id": 7,
+    "chargeId": 2,
+    "name": "Collection Fee",
+    "chargeTimeType": {
+      "id": 2,
+      "code": "chargeTimeType.specifiedDueDate",
+      "value": "Specified due date"
+    },
+    "dueDate": [
+      2013,
+      3,
+      29
+    ],
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 100,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 100,
+    "amountOrPercentage": 100,
+    "penalty": false
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_charges_template" name="loans_charges_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Loan Charges Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>loans/1/charges/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/charges/template
+					</code>
+					<code class="method-response">
+{
+  "amountPaid": 0,
+  "amountWaived": 0,
+  "amountWrittenOff": 0,
+  "chargeOptions": [
+    {
+      "id": 2,
+      "name": "Collection Fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 100,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      }
+    },
+    {
+      "id": 3,
+      "name": "Late payment penalty",
+      "active": true,
+      "penalty": true,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 1,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      }
+    },
+    {
+      "id": 1,
+      "name": "Loan Processing fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 100,
+      "chargeTimeType": {
+        "id": 1,
+        "code": "chargeTimeType.disbursement",
+        "value": "Disbursement"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      }
+    }
+  ],
+  "penalty": false
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loans_charges_retrieve" name="loans_charges_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Loan Charge</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>/loans/1/charges/1</div>
+					<br>
+					<br>
+					<div class=apiClick>/loans/1/charges/1?fields=name,amountOrPercentage</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loans/{loanId}/charges/{chargeId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "chargeId": 1,
+  "name": "Loan Processing fee",
+  "chargeTimeType": {
+    "id": 1,
+    "code": "chargeTimeType.disbursement",
+    "value": "Disbursement"
+  },
+  "chargeCalculationType": {
+    "id": 1,
+    "code": "chargeCalculationType.flat",
+    "value": "Flat"
+  },
+  "percentage": 0,
+  "amountPercentageAppliedTo": 0,
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 100,
+  "amountPaid": 0,
+  "amountWaived": 0,
+  "amountWrittenOff": 0,
+  "amountOutstanding": 100,
+  "amountOrPercentage": 100,
+  "penalty": false
+}			</code>
+				</div>
+			</div>
+
+			<a id="loans_charges_create" name="loans_charges_create" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Create a Loan Charge</h4>
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields
+								for Loan Charges</div></td>
+						</tr>
+						<tr class=alt>
+							<td>chargeId, amount, dueDate</td>
+						</tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}/charges
+                    </code>
+                    <code class="method-request">
+POST /loans/1/charges
+Content-Type: application/json
+Request Body:
+{
+  "chargeId": "2",
+  "locale": "en",
+  "amount": "100",
+  "dateFormat": "dd MMMM yyyy",
+  "dueDate": "29 April 2013"
+}
+                    </code>
+                    <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 31
+}
+	                </code>
+                </div>
+            </div>
+
+            <a id="loans_charges_update" name="loans_charges_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Loan Charge</h4>
+					<p>Currently Loan Charges may be updated only if the Loan
+						is not yet approved
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/loans/{loanId}/charges/{chargeId}
+					</code>
+					<code class="method-request">
+PUT loans/1/charges/12
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat": "dd MMMM yyyy",
+  "locale": "en",
+  "amount": "60",
+  "dueDate": "27 March 2013"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 6,
+  "changes": {
+    "dueDate": "27 March 2013",
+    "dateFormat": "dd MMMM yyyy",
+    "locale": "en",
+    "amount": 60.0
+  }
+}
+					</code>
+				</div>
+			</div>
+
+            <a id="loans_charges_pay" name="loans_charges_pay"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Pay Loan Charge</h4>
+					<p>Loan Charge will be paid if the loan is linked with a savings account
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/{loanId}/charges/{chargeId}?command=pay
+					</code>
+					<code class="method-request">
+POST loans/1/charges/12?command=pay
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat": "dd MMMM yyyy",
+  "locale": "en",
+  "transactionDate": "19 September 2013"
+}
+					</code>
+					<code class="method-response">
+
+
+    {
+       "officeId": 1,
+       "clientId": 1,
+       "loanId": 6,
+       "savingsId": 1,
+       "resourceId": 12
+    }
+
+					</code>
+				</div>
+			</div>
+			<a id="loans_charges_delete" name="loans_charges_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Loan Charge</h4>
+					<p>
+						<b>Note:</b>Currently, A Loan Charge may only be removed from Loans that are not
+						 yet approved.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/loans/{loanId}/charges/{chargeId}
+					</code>
+					<code class="method-request">
+DELETE loans/1/charges/2
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "loanId": 1,
+  "resourceId": 2
+}
+					</code>
+				</div>
+			</div>
+
+<!-- Loan Rescheduling content -->
+<a id="loan_rescheduling" name="loan_rescheduling" class="old-syle-anchor">&nbsp;</a>
+<div class="method-section">
+				<div class="method-description">
+					<h3>Loan Rescheduling</h3>
+					<p>Loan rescheduling provides the ability to give clients extra grace periods, extend loan term by adding extra installments and adjust the interest rate of a loan.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>loanId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The ID of the loan to be rescheduled.</td>
+						</tr>
+						<tr class=alt>
+							<td>rescheduleFromDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Due date of the start point (installment) of the rescheduling.</td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnPrincipal</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Number of installments to be added with zero principal.</td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnInterest</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Number of installments to be added with zero interest.</td>
+						</tr>
+						<tr class=alt>
+							<td>extraTerms</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Number of installments to be added after the last loan repayment schedule installment.</td>
+						</tr>
+						<tr class=alt>
+							<td>recalculateInterest</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>If recalculateInterest="true", the total interest amount of the loan is recalculated. If recalculateInterest="false", the total interest amount remains the same.</td>
+						</tr>
+						<tr class=alt>
+							<td>newInterestRate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>New interest rate to be used in calculating the interest amount for each rescheduled period/installment.</td>
+						</tr>
+						<tr class=alt>
+							<td>adjustedDueDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>New due date for the start point (installment) of the rescheduling.</td>
+						</tr>
+						<tr class=alt>
+							<td>rescheduleReasonId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The ID of the code value that indicates the reason for rescheduling the loan.</td>
+						</tr>
+						<tr class=alt>
+							<td>rescheduleReasonComment</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Text provided as extra reason for the rescheduling of the loan.</td>
+						</tr>
+						<tr class=alt>
+							<td>submittedOnDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The date on which the loan reschedule request was made.</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="loan_reschedule_request_create" name="loan_reschedule_request_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Loan Reschedule Request</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>loanId, rescheduleFromDate, rescheduleReasonId, submittedOnDate,<br />
+							graceOnPrincipal OR graceOnInterest OR extraTerms OR newInterestRate OR adjustedDueDate
+							</td>
+						</tr>
+					</table>
+						<br />
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>recalculateInterest, rescheduleReasonComment</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/rescheduleloans
+					</code>
+					<code class="method-request">
+POST rescheduleloans
+Content-Type: application/json Request Body:
+{
+	"loanId": 1,
+	"graceOnPrincipal": 2,
+	"graceOnInterest": 3,
+	"extraTerms": 2,
+	"rescheduleFromDate": "04 December 2014",
+	"dateFormat": "dd MMMM yyyy",
+	"locale": "en",
+	"recalculateInterest": true,
+	"submittedOnDate": "04 September 2014",
+	"newInterestRate" : 28,
+	"rescheduleReasonId": 1
+}
+					</code>
+					<code class="method-response">
+{
+    "loanId": 1,
+    "resourceId": 1
+}
+					</code>
+				</div>
+				<div class="method-example">
+					<code class="method-request">
+POST rescheduleloans
+Content-Type: application/json Request Body:
+{
+	"loanId": 1,
+	"graceOnPrincipal": 2,
+	"rescheduleFromDate": "04 December 2014",
+	"adjustedDueDate": "20 December 2014",
+	"dateFormat": "dd MMMM yyyy",
+	"locale": "en",
+	"submittedOnDate": "04 September 2014",
+	"rescheduleReasonComment" : "Client has gone AWOL",
+	"rescheduleReasonId": 1
+}
+					</code>
+					<code class="method-response">
+{
+    "loanId": 1,
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+			<a id="loan_reschedule_request_retrieve" name="loan_reschedule_request_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Loan Reschedule Request</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>rescheduleloans/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/rescheduleloans/{requestId}
+					</code>
+					<code class="method-response">
+{
+  "id": 2,
+  "loanId": 17,
+  "clientId": 4,
+  "clientName": "Fernando Michael Torres",
+  "loanAccountNumber": "000000017",
+  "statusEnum": {
+    "id": 500,
+    "code": "loanStatusType.rejected",
+    "value": "Rejected",
+    "pendingApproval": false,
+    "approved": false,
+    "rejected": true
+  },
+  "rescheduleFromInstallment": 5,
+  "graceOnPrincipal": 3,
+  "rescheduleFromDate": [
+    2013,
+    12,
+    6
+  ],
+  "recalculateInterest": false,
+  "rescheduleReasonCodeValue": {
+    "id": 239,
+    "name": "client cannot pay on time"
+  },
+  "timeline": {
+    "submittedOnDate": [
+      2014,
+      8,
+      4
+    ],
+    "submittedByUsername": "mifos",
+    "submittedByFirstname": "App",
+    "submittedByLastname": "Administrator",
+    "rejectedOnDate": [
+      2014,
+      8,
+      5
+    ],
+    "rejectedByUsername": "mifos",
+    "rejectedByFirstname": "App",
+    "rejectedByLastname": "Administrator"
+  },
+  "rescheduleReasonComment": ""
+}
+					</code>
+				</div>
+			</div>
+			<a id="loan_reschedule_preview_retrieve" name="loan_reschedule_preview_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Preview of The New Loan Repayment Schedule</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>rescheduleloans/1?command=previewLoanReschedule</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/rescheduleloans/{requestId}?command=previewLoanReschedule
+					</code>
+					<code class="method-response">
+
+  "currency": {
+    "code": "KES",
+    "name": "Kenyan Shilling",
+    "decimalPlaces": 0,
+    "inMultiplesOf": 10,
+    "displaySymbol": "KSh",
+    "nameCode": "currency.KES",
+    "displayLabel": "Kenyan Shilling (KSh)"
+  },
+  "loanTermInDays": 182,
+  "totalPrincipalDisbursed": 10000,
+  "totalPrincipalExpected": 10000,
+  "totalPrincipalPaid": 900.000000,
+  "totalInterestCharged": 960,
+  "totalFeeChargesCharged": 0.000000,
+  "totalPenaltyChargesCharged": 2500.000000,
+  "totalRepaymentExpected": 13460,
+  "totalOutstanding": 12060.000000,
+  "periods": [
+    {
+      "period": 1,
+      "fromDate": [
+        2013,
+        11,
+        8
+      ],
+      "dueDate": [
+        2013,
+        11,
+        8
+      ],
+      "daysInPeriod": 0,
+      "principalOriginalDue": 0,
+      "principalDue": 0,
+      "principalOutstanding": 0,
+      "principalLoanBalanceOutstanding": 10000,
+      "interestOriginalDue": 0,
+      "interestDue": 0,
+      "interestOutstanding": 0,
+      "feeChargesDue": 0,
+      "penaltyChargesDue": 0,
+      "totalOriginalDueForPeriod": 0,
+      "totalDueForPeriod": 0,
+      "totalPaidForPeriod": 0,
+      "totalOutstandingForPeriod": 0,
+      "totalOverdue": 0,
+      "totalActualCostOfLoanForPeriod": 0
+    },
+    ...
+    ...
+    {
+      "period": 30,
+      "fromDate": [
+        2014,
+        5,
+        23
+      ],
+      "dueDate": [
+        2014,
+        5,
+        30
+      ],
+      "daysInPeriod": 7,
+      "principalOriginalDue": 300,
+      "principalDue": 300,
+      "principalOutstanding": 300,
+      "principalLoanBalanceOutstanding": 0,
+      "interestOriginalDue": 130,
+      "interestDue": 130,
+      "interestOutstanding": 130,
+      "feeChargesDue": 0,
+      "penaltyChargesDue": 0,
+      "totalOriginalDueForPeriod": 430,
+      "totalDueForPeriod": 430,
+      "totalPaidForPeriod": 0,
+      "totalOutstandingForPeriod": 430,
+      "totalOverdue": 430,
+      "totalActualCostOfLoanForPeriod": 130
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+			<a id="loan_reschedule_request_reject" name="loan_reschedule_request_reject" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Reject a Loan Reschedule Request</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">POST https://DomainName/api/v1/rescheduleloans/{requestId}?command=reject</code>
+					<code class="method-request">POST rescheduleloans/1?command=reject
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "rejectedOnDate": "11 September 2014"
+}
+			</code>
+			<code class="method-response">
+{
+  "loanId": 1,
+  "resourceId": 1,
+  "changes": {
+	"locale": "en",
+	"dateFormat": "dd MMMM yyyy",
+	"rejectedOnDate": "11 September 2014",
+	"rejectedByUserId": 1
+  }
+}
+					</code>
+				</div>
+			</div>
+			<a id="loan_reschedule_request_approve" name="loan_reschedule_request_approve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Approve a Loan Reschedule Request</h4>
+					<p>Rescheduling of a loan happens once a loan reschedule request is approved.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">POST https://DomainName/api/v1/rescheduleloans/{requestId}?command=approve</code>
+					<code class="method-request">POST rescheduleloans/1?command=approve
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "approvedOnDate": "11 September 2014"
+}
+			</code>
+			<code class="method-response">
+{
+  "loanId": 1,
+  "resourceId": 1,
+  "changes": {
+	"locale": "en",
+	"dateFormat": "dd MMMM yyyy",
+	"approvedOnDate": "11 September 2014",
+	"approvedByUserId": 1
+  }
+}
+					</code>
+				</div>
+			</div>
+
+
+<!-- start Loan term variations -->
+<a id="loan_term_variations" name="loan_term_variations" class="old-syle-anchor">&nbsp;</a>
+<div class="method-section">
+				<div class="method-description">
+					<h3>Loan Rescheduling</h3>
+					<p>Loan Term Variations provides the ability to change due dates, amounts and number of instalments before loan approval.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>loanId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The ID of the loan to be modified.</td>
+						</tr>
+						<tr class=alt>
+							<td>exceptions</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>contains exception data for variations(modifiedinstallments,newinstallments and deletedinstallments described below) </td>
+						</tr>
+
+					</table>
+					<p>Exception object fields</p>
+					<p>Modifiedinstallments Array fields</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>dueDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Schedule dueDate for which this exception should be applied.</td>
+						</tr>
+						<tr class=alt>
+							<td>modifiedDueDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc> this is the exception data to move the due date to specified  date for a instalment</td>
+						</tr>
+						<tr class=alt>
+							<td>installmentAmount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc> new instalment amount(principal+interest) for the specified instalment. supported only for interest method  declining  with equal instalments Amortization</td>
+						</tr>
+						<tr class=alt>
+							<td>principal</td>
+						</tr>
+						<tr>
+							<td class=fielddesc> new Principal amount for the specified instalment. supported only for interest method  declining  with equal instalments Amortization and for Flat loans.</td>
+						</tr>
+					</table>
+					<p>newinstallments Array fields</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>dueDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Schedule dueDate for inserting new instalment.</td>
+						</tr>
+						<tr class=alt>
+							<td>installmentAmount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc> new instalment amount(principal+interest) for the new instalment. supported only for interest method  declining  with equal instalments Amortization</td>
+						</tr>
+						<tr class=alt>
+							<td>principal</td>
+						</tr>
+						<tr>
+							<td class=fielddesc> new Principal amount for the new instalment. supported only for interest method  declining  with equal instalments Amortization and for Flat loans.</td>
+						</tr>
+					</table>
+					<p>deletedinstallments Array fields</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>dueDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Schedule dueDate for removing specific instalment.</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+
+
+
+			<a id="loans_calculate_with_exceptions" name="loans_calculate_with_exceptions" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Calculate loan repayment schedule based on Loan term variations</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>exceptions,locale,dateFormat</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/1/schedule?command=calculateLoanSchedule
+					</code>
+					<code class="method-request">
+POST loans/{loanIdd}/schedule?command=calculateLoanSchedule
+Content-Type: application/json
+Request Body:
+{
+	"locale": "en",
+	"dateFormat": "dd MMMM yyyy",
+	"exceptions": {
+		"modifiedinstallments": [{
+			"dueDate": '01 April 2016',
+			"modifiedDueDate": '05 April 2016'
+		},
+		{
+			"dueDate": '05 January 2016',
+			"modifiedDueDate": '01 January 2016'
+		}],
+		"deletedinstallments": [{
+			"dueDate": "01 October 2016"
+		}],
+		newinstallments: [{
+			dueDate: '10 October 2016',
+			installmentAmount: '1000'
+}]
+	}
+}
+					</code>
+					<code class="method-response">
+{
+	"currency": {
+		"code": "USD",
+		"name": "US Dollar",
+		"decimalPlaces": 2,
+		"displaySymbol": "$",
+		"nameCode": "currency.USD",
+		"displayLabel": "US Dollar ($)"
+	},
+	"loanTermInDays": 12,
+	"totalPrincipalDisbursed": 10000,
+	"totalPrincipalExpected": 10000,
+	"totalInterestCharged": 1340.98,
+	"totalFeeChargesCharged": 0,
+	"totalPenaltyChargesCharged": 0,
+	"totalRepaymentExpected": 11340.98,
+	"periods": [{
+		"period": 1,
+		"fromDate": [2015,
+		11,
+		1],
+		"dueDate": [2015,
+		12,
+		1],
+		"daysInPeriod": 30,
+		"principalOriginalDue": 745.6,
+		"principalDue": 745.6,
+		"principalOutstanding": 745.6,
+		"principalLoanBalanceOutstanding": 9254.4,
+		"interestOriginalDue": 200,
+		"interestDue": 200,
+		"interestOutstanding": 200,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 945.6,
+		"totalDueForPeriod": 945.6,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 945.6,
+		"totalOverdue": 945.6,
+		"totalActualCostOfLoanForPeriod": 200
+	},
+	{
+		"period": 2,
+		"fromDate": [2015,
+		12,
+		1],
+		"dueDate": [2016,
+		1,
+		1],
+		"daysInPeriod": 31,
+		"principalOriginalDue": 814.91,
+		"principalDue": 814.91,
+		"principalOutstanding": 814.91,
+		"principalLoanBalanceOutstanding": 8439.49,
+		"interestOriginalDue": 185.09,
+		"interestDue": 185.09,
+		"interestOutstanding": 185.09,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 1000,
+		"totalDueForPeriod": 1000,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 1000,
+		"totalActualCostOfLoanForPeriod": 185.09
+	},
+	{
+		"period": 3,
+		"fromDate": [2016,
+		1,
+		1],
+		"dueDate": [2016,
+		2,
+		1],
+		"daysInPeriod": 31,
+		"principalOriginalDue": 770.75,
+		"principalDue": 770.75,
+		"principalOutstanding": 770.75,
+		"principalLoanBalanceOutstanding": 7668.74,
+		"interestOriginalDue": 168.79,
+		"interestDue": 168.79,
+		"interestOutstanding": 168.79,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.54,
+		"totalDueForPeriod": 939.54,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.54,
+		"totalActualCostOfLoanForPeriod": 168.79
+	},
+	{
+		"period": 4,
+		"fromDate": [2016,
+		2,
+		1],
+		"dueDate": [2016,
+		3,
+		5],
+		"daysInPeriod": 33,
+		"principalOriginalDue": 786.17,
+		"principalDue": 786.17,
+		"principalOutstanding": 786.17,
+		"principalLoanBalanceOutstanding": 6882.57,
+		"interestOriginalDue": 153.37,
+		"interestDue": 153.37,
+		"interestOutstanding": 153.37,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.54,
+		"totalDueForPeriod": 939.54,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.54,
+		"totalActualCostOfLoanForPeriod": 153.37
+	},
+	{
+		"period": 5,
+		"fromDate": [2016,
+		3,
+		5],
+		"dueDate": [2016,
+		4,
+		5],
+		"daysInPeriod": 31,
+		"principalOriginalDue": 801.89,
+		"principalDue": 801.89,
+		"principalOutstanding": 801.89,
+		"principalLoanBalanceOutstanding": 6080.68,
+		"interestOriginalDue": 137.65,
+		"interestDue": 137.65,
+		"interestOutstanding": 137.65,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.54,
+		"totalDueForPeriod": 939.54,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.54,
+		"totalActualCostOfLoanForPeriod": 137.65
+	},
+	{
+		"period": 6,
+		"fromDate": [2016,
+		4,
+		5],
+		"dueDate": [2016,
+		5,
+		1],
+		"daysInPeriod": 26,
+		"principalOriginalDue": 817.93,
+		"principalDue": 817.93,
+		"principalOutstanding": 817.93,
+		"principalLoanBalanceOutstanding": 5262.75,
+		"interestOriginalDue": 121.61,
+		"interestDue": 121.61,
+		"interestOutstanding": 121.61,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.54,
+		"totalDueForPeriod": 939.54,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.54,
+		"totalActualCostOfLoanForPeriod": 121.61
+	},
+	{
+		"period": 7,
+		"fromDate": [2016,
+		5,
+		1],
+		"dueDate": [2016,
+		6,
+		1],
+		"daysInPeriod": 31,
+		"principalOriginalDue": 834.28,
+		"principalDue": 834.28,
+		"principalOutstanding": 834.28,
+		"principalLoanBalanceOutstanding": 4428.47,
+		"interestOriginalDue": 105.26,
+		"interestDue": 105.26,
+		"interestOutstanding": 105.26,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.54,
+		"totalDueForPeriod": 939.54,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.54,
+		"totalActualCostOfLoanForPeriod": 105.26
+	},
+	{
+		"period": 8,
+		"fromDate": [2016,
+		6,
+		1],
+		"dueDate": [2016,
+		7,
+		1],
+		"daysInPeriod": 30,
+		"principalOriginalDue": 850.97,
+		"principalDue": 850.97,
+		"principalOutstanding": 850.97,
+		"principalLoanBalanceOutstanding": 3577.5,
+		"interestOriginalDue": 88.57,
+		"interestDue": 88.57,
+		"interestOutstanding": 88.57,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.54,
+		"totalDueForPeriod": 939.54,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.54,
+		"totalActualCostOfLoanForPeriod": 88.57
+	},
+	{
+		"period": 9,
+		"fromDate": [2016,
+		7,
+		1],
+		"dueDate": [2016,
+		8,
+		1],
+		"daysInPeriod": 31,
+		"principalOriginalDue": 867.99,
+		"principalDue": 867.99,
+		"principalOutstanding": 867.99,
+		"principalLoanBalanceOutstanding": 2709.51,
+		"interestOriginalDue": 71.55,
+		"interestDue": 71.55,
+		"interestOutstanding": 71.55,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.54,
+		"totalDueForPeriod": 939.54,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.54,
+		"totalActualCostOfLoanForPeriod": 71.55
+	},
+	{
+		"period": 10,
+		"fromDate": [2016,
+		8,
+		1],
+		"dueDate": [2016,
+		9,
+		1],
+		"daysInPeriod": 31,
+		"principalOriginalDue": 885.35,
+		"principalDue": 885.35,
+		"principalOutstanding": 885.35,
+		"principalLoanBalanceOutstanding": 1824.16,
+		"interestOriginalDue": 54.19,
+		"interestDue": 54.19,
+		"interestOutstanding": 54.19,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.54,
+		"totalDueForPeriod": 939.54,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.54,
+		"totalActualCostOfLoanForPeriod": 54.19
+	},
+	{
+		"period": 11,
+		"fromDate": [2016,
+		9,
+		1],
+		"dueDate": [2016,
+		10,
+		10],
+		"daysInPeriod": 39,
+		"principalOriginalDue": 903.05,
+		"principalDue": 903.05,
+		"principalOutstanding": 903.05,
+		"principalLoanBalanceOutstanding": 921.11,
+		"interestOriginalDue": 36.48,
+		"interestDue": 36.48,
+		"interestOutstanding": 36.48,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.53,
+		"totalDueForPeriod": 939.53,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.53,
+		"totalActualCostOfLoanForPeriod": 36.48
+	},
+	{
+		"period": 12,
+		"fromDate": [2016,
+		10,
+		10],
+		"dueDate": [2016,
+		11,
+		1],
+		"daysInPeriod": 22,
+		"principalOriginalDue": 921.11,
+		"principalDue": 921.11,
+		"principalOutstanding": 921.11,
+		"principalLoanBalanceOutstanding": 0,
+		"interestOriginalDue": 18.42,
+		"interestDue": 18.42,
+		"interestOutstanding": 18.42,
+		"feeChargesDue": 0,
+		"penaltyChargesDue": 0,
+		"totalOriginalDueForPeriod": 939.53,
+		"totalDueForPeriod": 939.53,
+		"totalPaidForPeriod": 0,
+		"totalOutstandingForPeriod": 939.53,
+		"totalActualCostOfLoanForPeriod": 18.42
+	}]
+}
+					</code>
+				</div>
+			</div>
+
+		<a id="loans_update_variations" name="loans_update_variations" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Updates loan repayment schedule based on Loan term variations</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>exceptions,locale,dateFormat</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/1/schedule?command=addVariations
+					</code>
+					<code class="method-request">
+POST loans/{loanIdd}/schedule?command=addVariations
+Content-Type: application/json
+Request Body:
+{
+	"locale": "en",
+	"dateFormat": "dd MMMM yyyy",
+	"exceptions": {
+		"modifiedinstallments": [{
+			"dueDate": '01 April 2016',
+			"modifiedDueDate": '05 April 2016'
+		},
+		{
+			"dueDate": '05 January 2016',
+			"modifiedDueDate": '01 January 2016'
+		}],
+		"deletedinstallments": [{
+			"dueDate": "01 October 2016"
+		}],
+		newinstallments: [{
+			dueDate: '10 October 2016',
+			installmentAmount: '1000'
+}]
+	}
+}
+					</code>
+					<code class="method-response">
+{
+	"loanId": 1,
+	"changes": {
+		"loanTermVariations": [{
+			"id": 21,
+			"termType": {
+				"id": 4,
+				"code": "loanTermType.dueDate",
+				"value": "dueDate"
+			},
+			"termVariationApplicableFrom": [2016,
+			1,
+			1],
+			"dateValue": [2016,
+			1,
+			10],
+			"isSpecificToInstallment": true
+		},
+		{
+			"id": 22,
+			"termType": {
+				"id": 3,
+				"code": "loanTermType.principalAmount",
+				"value": "principalAmount"
+			},
+			"termVariationApplicableFrom": [2016,
+			1,
+			10],
+			"decimalValue": 1100,
+			"isSpecificToInstallment": true
+		},
+		{
+			"id": 23,
+			"termType": {
+				"id": 3,
+				"code": "loanTermType.principalAmount",
+				"value": "principalAmount"
+			},
+			"termVariationApplicableFrom": [2016,
+			8,
+			1],
+			"decimalValue": 1000,
+			"isSpecificToInstallment": true
+		},
+		{
+			"id": 24,
+			"termType": {
+				"id": 5,
+				"code": "loanTermType.dueDate",
+				"value": "insertInstallment"
+			},
+			"termVariationApplicableFrom": [2016,
+			8,
+			15],
+			"decimalValue": 900,
+			"isSpecificToInstallment": true
+		}]
+	}
+}
+					</code>
+				</div>
+			</div>
+
+
+		<a id="loans_delete_variations" name="loans_delete_variations" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Updates loan repayment schedule by removing Loan term variations</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td></td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loans/1/schedule?command=deleteVariations
+					</code>
+					<code class="method-request">
+POST loans/{loanIdd}/schedule?command=deleteVariations
+Content-Type: application/json
+Request Body:
+{}
+					</code>
+					<code class="method-response">
+{
+	"loanId": 1,
+	"changes": {
+		"removedEntityIds": [{
+			 21,22}]
+	}
+}
+					</code>
+				</div>
+			</div>
+
+
+<!-- End Loan Term variations-->
+<!-- start of offices content -->
+			<a id="offices" name="offices" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Offices</h3>
+					<p>Offices are used to model an MFIs structure. A hierarchical
+						representation of offices is supported. There will always be at
+						least one office (which represents the MFI or an MFIs head
+						office). All subsequent offices added must have a parent office.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>externalId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A place to put an external reference for
+								this office e.g. The ID another system uses for it.<br> If
+								provided, it must be unique.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>hierarchy</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>This field is a system generated
+								convenience field. It can't be set.<br> It uses "dot
+								notation" to define where an office stands in the office
+								hierarchy.<br> For example, the system default office (id =
+								1) has a hierarchy value of '.'<br> An office (id = 12)
+								placed directly underneath this default office has a hierarchy
+								value of '.12.'<br> And if a further office (id = 78) is
+								placed directly under office (id = 12) its hierarchy value is
+								'.12.78.'<br>
+							<br> The hierarchy value is useful when you need to display
+								offices in 'hierarchy' order (perhaps indented in a drop down
+								list box).
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="offices_list" name="offices_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Offices</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>offices</div>
+					<br>
+					<br>
+					<div class=apiClick>offices?fields=id,name,openingDate</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/offices
+					</code>
+					<code class="method-response">
+[
+    {
+        "id": 1,
+        "name": "Head Office",
+        "nameDecorated": "Head Office",
+        "externalId": "1",
+        "openingDate": [
+            2009,
+            1,
+            1
+        ],
+        "hierarchy": "."
+    }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="offices_template" name="offices_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Office Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>offices/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/offices/template
+					</code>
+					<code class="method-response">
+{
+    "openingDate": [
+        2013,
+        2,
+        4
+    ],
+    "allowedParents": [
+        {
+            "id": 1,
+            "name": "Head Office",
+            "nameDecorated": "Head Office"
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="offices_retrieve" name="offices_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve an Office</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>offices/1</div>
+					<br>
+					<br>
+					<div class=apiClick>offices/1?template=true</div>
+					<br>
+					<br>
+					<div class=apiClick>offices/1?fields=id,name,parentName</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/offices/1
+					</code>
+					<code class="method-response">
+{
+    "id": 1,
+    "name": "Head Office",
+    "nameDecorated": "Head Office",
+    "externalId": "1",
+    "openingDate": [
+        2009,
+        1,
+        1
+    ],
+    "hierarchy": "."
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="offices_create" name="offices_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create an Office</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name, openingDate, parentId</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/offices
+					</code>
+					<code class="method-request">
+POST offices
+Content-Type: application/json
+Request Body:
+{
+    "name": "Example New Branch",
+    "dateFormat": "dd MMMM yyyy",
+    "locale": "en",
+    "openingDate": "01 July 2007",
+    "parentId": 2,
+    "externalId": "SYS54-88"
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 3,
+    "resourceId": 3
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="offices_update" name="offices_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update Office</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/offices/{officeId}
+					</code>
+					<code class="method-request">
+PUT offices/1
+Content-Type: application/json
+Request Body:
+{
+	"name": "Name is updated"
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 1,
+    "changes": {
+        "name": "Name is updated"
+    }
+}
+					</code>
+				</div>
+			</div>
+			<!-- end of office api docs -->
+
+			<!-- start of loan products api docs -->
+			<a id="loanproducts" name="loanproducts" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Loan Products</h3>
+					<p>A Loan product is a template that is used when creating a
+						loan. Much of the template definition can be overridden during
+						loan creation.</p>
+
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name associated with loan product on system.</td>
+						</tr>
+						<tr class=alt>
+							<td>shortName</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Short name associated with a loan product. <br>
+								An abbreviated version of the name, used in reports or menus where space is limited, such as Collection Sheets.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>description</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>For providing helpful description of product offering.</td>
+						</tr>
+						<tr class=alt>
+							<td>fundId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>For associating a loan product with a given fund by default.</td>
+						</tr>
+						<tr class=alt>
+							<td>includeInBorrowerCycle</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>It is a flag, Used to denote whether the loans should include in loan cycle counter or not.</td>
+						</tr>
+						<tr class=alt>
+							<td>useBorrowerCycle</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>It is a flag, Used to denote whether the loans should depend on <br>borrower loan cycle counter or not.</td>
+						</tr>
+						<tr class=alt>
+							<td>currencyCode</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A three letter ISO code of currency.</td>
+						</tr>
+						<tr class=alt>
+							<td>digitsAfterDecimal</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Override the currency default value for digitsAfterDecimal.</td>
+						</tr>
+						<tr class=alt>
+							<td>inMultiplesOf</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Override the default value for rounding currency to multiples of value provided.</td>
+						</tr>
+						<tr class=alt>
+							<td>installmentAmountInMultiplesOf</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Override the default value for rounding instalment amount to multiples of value provided.</td>
+						</tr>
+
+						<tr class=alt>
+							<td>principal</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The loan amount to be disbursed to through loan.</td>
+						</tr>
+						<tr class=alt>
+							<td>numberOfRepayments</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Number of installments to repay.<br>
+								Used like: <b>numberOfRepayments</b> Every <i>repaymentEvery</i>
+								<i>repaymentFrequencyType</i><br> e.g. <b>10</b> (repayments) Every 12 Weeks
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>repaymentEvery</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Used like: numberOfRepayments Every
+								<b>repaymentEvery</b> repaymentFrequencyType<br> e.g. 10
+								(repayments) Every <b>12</b> Weeks
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>repaymentFrequencyType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Used like: <i>numberOfRepayments</i> Every
+								repaymentEvery <b>repaymentFrequencyType</b><br> e.g. 10
+								(repayments) Every 12 <b>Weeks</b> <br>
+								<span>Example Values:</span> 0=Days, 1=Weeks, 2=Months
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>interestRatePerPeriod</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Interest Rate.<br> Used like:
+								<b>interestRatePerPeriod</b> % interestRateFrequencyType - interestType<br>
+								e.g. <b>12.0000</b>% Per year - Declining Balance
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>interestRateFrequencyType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Used like: interestRatePerPeriod%
+								interestRateFrequencyType - interestType<br> e.g. 12.0000%
+								<b>Per year</b> - Declining Balance <br>
+							<span>Example Values:</span> 2=Per month, 3=Per year
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>amortizationType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc><span>Example Values:</span> 0=Equal
+								principle payments, 1=Equal installments</td>
+						</tr>
+						<tr class=alt>
+							<td>interestType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Used like: interestRatePerPeriod%
+								interestRateFrequencyType - interestType<br> e.g. 12.0000%
+								Per year - <b>Declining Balance</b> <br>
+							<span>Example Values:</span> 0=Declining Balance, 1=Flat
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>interestCalculationPeriodType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc><span>Example Values:</span> 0=Daily, 1=Same as repayment period</td>
+						</tr>
+						<tr class=alt>
+							<td>allowPartialPeriodInterestCalcualtion</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>This value will be supported along with interestCalculationPeriodType as Same as repayment period to calculate interest for partial periods.
+							<span>Example:</span> Interest charged from is 5th of April , Principal is 10000 and interest is 1% per month then the interest will be (10000 * 1%)* (25/30) , it calculates for the month first then calculates exact periods between start date and end date(can be a decimal)
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>inArrearsTolerance</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The amount that can be 'waived' at end
+								of all loan payments because it is too small to worry about.<br>
+								This is also the tolerance amount assessed when determining if a
+								loan is in arrears.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>principalVariationsForBorrowerCycle,interestRateVariationsForBorrowerCycle,<br>numberOfRepaymentVariationsForBorrowerCycle</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Variations for loan, based on borrower cycle number
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>minimumDaysBetweenDisbursalAndFirstRepayment</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The minimum number of days allowed between a Loan disbursal and its first repayment.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>principalThresholdForLastInstalment</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Field represents percentage of current instalment principal amount for comparing against principal outstanding to add another repayment instalment.  If the outstanding principal amount is less then calculated amount, remaining outstanding amount will be added to current instalment. Default value for multi disburse loan is 50% and non-multi disburse loan is 0%
+						</tr>
+						<tr class=alt>
+							<td>canDefineInstallmentAmount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>if provided as true, then fixed instalment amount can be provided from loan account.
+							</td>
+						</tr>
+
+
+
+<tr class=alt>
+	<td>transactionProcessingStrategyId</td>
+</tr>
+<tr>
+	<td class=fielddesc>
+An enumeration that indicates the type of transaction processing strategy to be used. This relates to functionality that is also known as <b>Payment Application Logic</b>.
+
+<p>A number of out of the box approaches exist, some are custom to specific MFIs, some are more general and indicate the order in which payments are processed.</p>
+
+<p>Refer to the <a href="#paymentapplicationlogic">Payment Application Logic / Transaction Processing Strategy<a> section in the appendix for more detailed overview of each available <b>payment application logic</b> provided out of the box.</p>
+
+<i>List of current approaches</i>:<br/>
+<ul>
+	<li>1 = Mifos style (Similar to Old Mifos)</li>
+	<li>2 = Heavensfamily (Custom MFI approach)</li>
+	<li>3 = Creocore (Custom MFI approach)</li>
+	<li>4 = RBI (India)</li>
+	<li>5 = Principal Interest Penalties Fees Order</li>
+	<li>6 = Interest Principal Penalties Fees Order</li>
+    <li>7 = Early Payment Strategy</li>
+</ul>
+	</td>
+</tr>
+						<tr class=alt>
+							<td>graceOnPrincipalPayment</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the principal component of a repayment period.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnInterestPayment</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the interest component of a repayment period. Interest is still calculated but offset to later repayment periods.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnInterestCharged</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Integer - represents the number of repayment periods that should be interest-free.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>graceOnArrearsAgeing</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Integer -  Used in Arrears calculation to only take into account loans that are more than graceOnArrearsAgeing days overdue.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>overdueDaysForNPA</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Integer - represents the maximum number of days a Loan may
+							be overdue before being classified as a NPA (non performing asset)
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>accountMovesOutOfNPAOnlyOnArrearsCompletion</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional: Boolean - if provided as true,  Loan Account moves out of NPA state only when all arrears are cleared
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>accountingRule</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies if accounting is enabled for the particular
+								product and the type of the accounting rule to be used
+								<span>Example Values:</span>1=NONE, 2=CASH_BASED, 3=ACCRUAL_PERIODIC, 4=ACCRUAL_UPFRONT</td>
+						</tr>
+						<tr class=alt>
+							<td>isInterestRecalculationEnabled</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>It is a flag, Used to denote whether interest recalculation is enabled or disabled for the particular product</td>
+						</tr>
+						<tr class=alt>
+							<td>daysInYearType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies the number of days in a year. <br>
+							<span>Example Values:</span>1=ACTUAL(Actual number of days in year), 360=360 DAYS, 364=364 DAYS(52 WEEKS), 365=365 DAYS</td>
+						</tr>
+						<tr class=alt>
+							<td>daysInMonthType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies the number of days in a month. <br>
+							<span>Example Values:</span>1=ACTUAL(Actual number of days in month), 30=30 DAYS</td>
+						</tr>
+						<tr class=alt>
+							<td>interestRecalculationCompoundingMethod</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies which amount portion should be added to principal for interest recalculation. <br>
+							<span>Example Values:</span>0=NONE(Only on principal), 1=INTEREST(Principal+Interest), 2=FEE(Principal+Fee), 3=FEE And INTEREST (Principal+Fee+Interest)</td>
+						</tr>
+						<tr class=alt>
+							<td>rescheduleStrategyMethod</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies what action should perform on loan repayment schedule for advance payments. <br>
+							<span>Example Values:</span>1=Reschedule next repayments, 2=Reduce number of installments, 3=Reduce EMI amount</td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationCompoundingFrequencyType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies effective date from which the compounding of interest or fee amounts will be considered in recalculation on late payment.<br>
+							<span>Example Values:</span>1=Same as repayment period, 2=Daily, 3=Weekly, 4=Monthly</td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationCompoundingFrequencyInterval</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies compounding frequency interval for interest recalculation.<br>
+						</tr>
+						<tr class=alt>
+							<td>recalculationCompoundingFrequencyDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies compounding frequency start date for interest recalculation.<br>
+						</tr>
+
+						<tr class=alt>
+							<td>recalculationRestFrequencyType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies effective date from which the late or advanced payment amounts will be considered in recalculation.<br>
+							<span>Example Values:</span>1=Same as repayment period, 2=Daily, 3=Weekly, 4=Monthly</td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationRestFrequencyInterval</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies rest frequency interval for interest recalculation.<br>
+						</tr>
+						<tr class=alt>
+							<td>recalculationRestFrequencyDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies rest frequency start date for interest recalculation.<br>
+						</tr>
+						<tr class=alt>
+							<td>preClosureInterestCalculationStrategy</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies applicable days for interest calculation on pre closure of a loan.<br>
+							<span>Example Values:</span>1=Calculate till pre closure date, 2=Calculate till rest frequency date</td>
+						</tr>
+						<tr class=alt>
+							<td>isArrearsBasedOnOriginalSchedule</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>If Specified as true, arrears will be identified based on original schedule.<br>
+						</tr>
+						<tr class=alt>
+							<td>allowAttributeOverrides</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Specifies if select attributes may be overridden for individual loan accounts.<br>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="loanproducts_list" name="loanproducts_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Loan Products</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>loanproducts</div>
+					<br>
+					<br>
+					<div class=apiClick>loanproducts?fields=name,description,interestRateFrequencyType,amortizationType</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loanproducts
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "name": "personal loan product",
+    "shortName": "pe1",
+    "includeInBorrowerCycle": false,
+    "useBorrowerCycle": false,
+    "startDate": [
+      2013,
+      9,
+      2
+    ],
+    "closeDate": [
+      2014,
+      2,
+      7
+    ],
+    "status": "loanProduct.active",
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "inMultiplesOf": 0,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "principal": 10000.000000,
+    "minPrincipal": 5000.000000,
+    "maxPrincipal": 15000.000000,
+    "numberOfRepayments": 10,
+    "minNumberOfRepayments": 5,
+    "maxNumberOfRepayments": 15,
+    "repaymentEvery": 7,
+    "repaymentFrequencyType": {
+      "id": 0,
+      "code": "repaymentFrequency.periodFrequencyType.days",
+      "value": "Days"
+    },
+    "interestRatePerPeriod": 15.000000,
+    "interestRateFrequencyType": {
+      "id": 3,
+      "code": "interestRateFrequency.periodFrequencyType.years",
+      "value": "Per year"
+    },
+    "annualInterestRate": 15.000000,
+    "amortizationType": {
+      "id": 1,
+      "code": "amortizationType.equal.installments",
+      "value": "Equal installments"
+    },
+    "interestType": {
+      "id": 1,
+      "code": "interestType.flat",
+      "value": "Flat"
+    },
+    "interestCalculationPeriodType": {
+      "id": 1,
+      "code": "interestCalculationPeriodType.same.as.repayment.period",
+      "value": "Same as repayment period"
+    },
+    "transactionProcessingStrategyId": 1,
+    "transactionProcessingStrategyName": "Mifos style",
+    "principalVariationsForBorrowerCycle": [],
+    "interestRateVariationsForBorrowerCycle": [],
+    "numberOfRepaymentVariationsForBorrowerCycle": [],
+    "daysInMonthType": {
+        "id": 30,
+        "code": "DaysInMonthType.days360",
+        "value": "30 Days"
+    },
+    "daysInYearType": {
+        "id": 360,
+        "code": "DaysInYearType.days360",
+        "value": "360 Days"
+    },
+    "isInterestRecalculationEnabled": true,
+    "interestRecalculationData": {
+        "id": 3,
+        "productId": 1,
+        "interestRecalculationCompoundingType": {
+            "id": 2,
+            "code": "interestRecalculationCompoundingMethod.fee",
+            "value": "Fee"
+        },
+		"recalculationCompoundingFrequencyType": {
+		    "id":1,
+			"code":"interestRecalculationFrequencyType.same.as.repayment.period",
+			"value":"Same as repayment period"
+		},
+        "rescheduleStrategyType": {
+            "id": 2,
+            "code": "loanRescheduleStrategyMethod.reduce.number.of.installments",
+            "value": "Reduce number of installments"
+        },
+		"recalculationRestFrequencyType": {
+		    "id":1,
+			"code":"interestRecalculationFrequencyType.same.as.repayment.period",
+			"value":"Same as repayment period"
+		},
+		"preClosureInterestCalculationStrategy": {
+		    "id":1,
+			"code":"loanPreClosureInterestCalculationStrategy.tillPreClosureDate",
+			"value":"Till preclose Date"
+		},
+		"isArrearsBasedOnOriginalSchedule" : true
+    },
+    "accountingRule": {
+      "id": 2,
+      "code": "accountingRuleType.cash",
+      "value": "CASH BASED"
+    },
+	"principalThresholdForLastInstalment":0
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="loanproducts_template" name="loanproducts_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Loan Product Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>loanproducts/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loanproducts/template
+					</code>
+					<code class="method-response">
+{
+  "includeInBorrowerCycle": false,
+  "useBorrowerCycle": false,
+  "currency": {
+    "code": "",
+    "name": "",
+    "decimalPlaces": 0,
+    "inMultiplesOf": 0,
+    "displaySymbol": "",
+    "nameCode": "",
+    "displayLabel": " []"
+  },
+  "repaymentFrequencyType": {
+    "id": 2,
+    "code": "repaymentFrequency.periodFrequencyType.months",
+    "value": "Months"
+  },
+  "interestRateFrequencyType": {
+    "id": 2,
+    "code": "interestRateFrequency.periodFrequencyType.months",
+    "value": "Per month"
+  },
+  "amortizationType": {
+    "id": 1,
+    "code": "amortizationType.equal.installments",
+    "value": "Equal installments"
+  },
+  "interestType": {
+    "id": 0,
+    "code": "interestType.declining.balance",
+    "value": "Declining Balance"
+  },
+  "interestCalculationPeriodType": {
+    "id": 1,
+    "code": "interestCalculationPeriodType.same.as.repayment.period",
+    "value": "Same as repayment period"
+  },
+  "principalVariationsForBorrowerCycle": [],
+  "interestRateVariationsForBorrowerCycle": [],
+  "numberOfRepaymentVariationsForBorrowerCycle": [],
+  "accountingRule": {
+    "id": 1,
+    "code": "accountingRuleType.none",
+    "value": "NONE"
+  },
+  "daysInMonthType": {
+        "id": 1,
+        "code": "DaysInMonthType.actual",
+        "value": "Actual"
+    },
+    "daysInYearType": {
+        "id": 1,
+        "code": "DaysInYearType.actual",
+        "value": "Actual"
+    },
+    "isInterestRecalculationEnabled": false,
+    "interestRecalculationData": {
+        "interestRecalculationCompoundingType": {
+            "id": 0,
+            "code": "interestRecalculationCompoundingMethod.none",
+            "value": "None"
+        },
+        "rescheduleStrategyType": {
+            "id": 3,
+            "code": "loanRescheduleStrategyMethod.reduce.emi.amount",
+            "value": "Reduce EMI amount"
+        },"preClosureInterestCalculationStrategy": {
+		    "id":1,
+			"code":"loanPreClosureInterestCalculationStrategy.tillPreClosureDate",
+			"value":"Till preclose Date"
+		}
+    },
+  "paymentTypeOptions": [
+    {
+      "id": 10,
+      "name": "check",
+      "position": 1
+    }
+  ],
+  "currencyOptions": [
+    {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    }
+  ],
+  "repaymentFrequencyTypeOptions": [
+    {
+      "id": 0,
+      "code": "repaymentFrequency.periodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "repaymentFrequency.periodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "repaymentFrequency.periodFrequencyType.months",
+      "value": "Months"
+    }
+  ],
+  "preClosureInterestCalculationStrategyOptions": [
+		{
+		    "id":1,
+			"code":"loanPreClosureInterestCalculationStrategy.tillPreClosureDate",
+			"value":"Till preclose Date"
+		},
+		{
+		    "id":2,
+			"code":"loanPreClosureInterestCalculationStrategy.tillRestFrequencyDate",
+			"value":"Till rest Frequency Date"
+		}
+	],
+  "interestRateFrequencyTypeOptions": [
+    {
+      "id": 2,
+      "code": "interestRateFrequency.periodFrequencyType.months",
+      "value": "Per month"
+    },
+    {
+      "id": 3,
+      "code": "interestRateFrequency.periodFrequencyType.years",
+      "value": "Per year"
+    }
+  ],
+  "amortizationTypeOptions": [
+    {
+      "id": 1,
+      "code": "amortizationType.equal.installments",
+      "value": "Equal installments"
+    },
+    {
+      "id": 0,
+      "code": "amortizationType.equal.principal",
+      "value": "Equal principal payments"
+    }
+  ],
+  "interestTypeOptions": [
+    {
+      "id": 1,
+      "code": "interestType.flat",
+      "value": "Flat"
+    },
+    {
+      "id": 0,
+      "code": "interestType.declining.balance",
+      "value": "Declining Balance"
+    }
+  ],
+  "interestCalculationPeriodTypeOptions": [
+    {
+      "id": 0,
+      "code": "interestCalculationPeriodType.daily",
+      "value": "Daily"
+    },
+    {
+      "id": 1,
+      "code": "interestCalculationPeriodType.same.as.repayment.period",
+      "value": "Same as repayment period"
+    }
+  ],
+  "transactionProcessingStrategyOptions": [
+    {
+      "id": 1,
+      "code": "mifos-standard-strategy",
+      "name": "Penalties, Fees, Interest, Principal order"
+    },
+    {
+      "id": 2,
+      "code": "heavensfamily-strategy",
+      "name": "HeavensFamily Unique"
+    },
+    {
+      "id": 3,
+      "code": "creocore-strategy",
+      "name": "Creocore Unique"
+    },
+    {
+      "id": 4,
+      "code": "rbi-india-strategy",
+      "name": "Overdue/Due Fee/Int,Principal"
+    },
+    {
+      "id": 5,
+      "code": "principal-interest-penalties-fees-order-strategy",
+      "name": "Principal Interest Penalties Fees Order"
+    },
+    {
+      "id": 6,
+      "code": "interest-principal-penalties-fees-order-strategy",
+      "name": "Interest Principal Penalties Fees Order"
+    }
+  ],
+  "chargeOptions": [
+    {
+      "id": 5,
+      "name": "des charge",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 100.000000,
+      "chargeTimeType": {
+        "id": 1,
+        "code": "chargeTimeType.disbursement",
+        "value": "Disbursement"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 1,
+      "name": "flat install",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 50.000000,
+      "chargeTimeType": {
+        "id": 8,
+        "code": "chargeTimeType.instalmentFee",
+        "value": "Instalment Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 9,
+      "name": "install amt+int",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 5.000000,
+      "chargeTimeType": {
+        "id": 8,
+        "code": "chargeTimeType.instalmentFee",
+        "value": "Instalment Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 3,
+        "code": "chargeCalculationType.percent.of.amount.and.interest",
+        "value": "% Loan Amount + Interest"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 10,
+      "name": "install int",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 5.000000,
+      "chargeTimeType": {
+        "id": 8,
+        "code": "chargeTimeType.instalmentFee",
+        "value": "Instalment Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 4,
+        "code": "chargeCalculationType.percent.of.interest",
+        "value": "% Interest"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 2,
+      "name": "install per",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 5.000000,
+      "chargeTimeType": {
+        "id": 8,
+        "code": "chargeTimeType.instalmentFee",
+        "value": "Instalment Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 2,
+        "code": "chargeCalculationType.percent.of.amount",
+        "value": "% Amount"
+      },
+      "chargePaymentMode": {
+        "id": 1,
+        "code": "chargepaymentmode.accounttransfer",
+        "value": "Account transfer"
+      }
+    },
+    {
+      "id": 8,
+      "name": "spe int",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 5.000000,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 4,
+        "code": "chargeCalculationType.percent.of.interest",
+        "value": "% Interest"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 4,
+      "name": "specific acc transfer",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 100.000000,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 1,
+        "code": "chargepaymentmode.accounttransfer",
+        "value": "Account transfer"
+      }
+    },
+    {
+      "id": 7,
+      "name": "specific amt+int",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 5.000000,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 3,
+        "code": "chargeCalculationType.percent.of.amount.and.interest",
+        "value": "% Loan Amount + Interest"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 3,
+      "name": "specific per",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 5.000000,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 2,
+        "code": "chargeCalculationType.percent.of.amount",
+        "value": "% Amount"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 6,
+      "name": "xyz",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 50.000000,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 1,
+        "code": "chargepaymentmode.accounttransfer",
+        "value": "Account transfer"
+      }
+    }
+  ],
+  "accountingRuleOptions": [
+    {
+      "id": 1,
+      "code": "accountingRuleType.none",
+      "value": "NONE"
+    },
+    {
+      "id": 2,
+      "code": "accountingRuleType.cash",
+      "value": "CASH BASED"
+    },
+    {
+      "id": 3,
+      "code": "accountingRuleType.accrual",
+      "value": "ACCRUAL BASED"
+    }
+  ],
+  "accountingMappingOptions": {
+    "liabilityAccountOptions": [
+      {
+        "id": 11,
+        "name": "over payment",
+        "glCode": "13",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 2,
+          "code": "accountType.liability",
+          "value": "LIABILITY"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "over payment",
+        "tagId": {
+          "id": 0
+        },
+        "organizationRunningBalance": 0
+      },
+    ],
+    "assetAccountOptions": [
+      {
+        "id": 1,
+        "name": "fund source",
+        "glCode": "01",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "fund source",
+        "tagId": {
+          "id": 0
+        },
+        "organizationRunningBalance": -60000
+      },
+      {
+        "id": 2,
+        "name": "Loan portfolio",
+        "glCode": "02",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "Loan portfolio",
+        "tagId": {
+          "id": 0
+        },
+        "organizationRunningBalance": 60000
+      },
+      {
+        "id": 3,
+        "name": "transfers",
+        "glCode": "03",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "transfers",
+        "tagId": {
+          "id": 0
+        },
+        "organizationRunningBalance": 0
+      },
+    ],
+    "expenseAccountOptions": [
+      {
+        "id": 10,
+        "name": "loans written off 2",
+        "glCode": "12",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 5,
+          "code": "accountType.expense",
+          "value": "EXPENSE"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "loans written off 2",
+        "tagId": {
+          "id": 0
+        },
+        "organizationRunningBalance": 0
+      }
+    ],
+    "incomeAccountOptions": [
+      {
+        "id": 4,
+        "name": "income from interest",
+        "glCode": "04",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 4,
+          "code": "accountType.income",
+          "value": "INCOME"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "income from interest",
+        "tagId": {
+          "id": 0
+        },
+        "organizationRunningBalance": 19
+      },
+      {
+        "id": 8,
+        "name": "income from fees 2",
+        "glCode": "10",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 4,
+          "code": "accountType.income",
+          "value": "INCOME"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "income from fees 2",
+        "tagId": {
+          "id": 0
+        },
+        "organizationRunningBalance": 0
+      },
+      {
+        "id": 9,
+        "name": "income from penalities 2",
+        "glCode": "11",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 4,
+          "code": "accountType.income",
+          "value": "INCOME"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "income from penalities 2",
+        "tagId": {
+          "id": 0
+        },
+        "organizationRunningBalance": 0
+      }
+    ]
+  },
+  "valueConditionTypeOptions": [
+    {
+      "id": 2,
+      "code": "LoanProductValueConditionType.equal",
+      "value": "equals"
+    },
+    {
+      "id": 3,
+      "code": "LoanProductValueConditionType.greterthan",
+      "value": "greter than"
+    }
+  ],
+  "daysInMonthTypeOptions": [{
+        "id": 1,
+        "code": "DaysInMonthType.actual",
+        "value": "Actual"
+    },
+    {
+        "id": 30,
+        "code": "DaysInMonthType.days360",
+        "value": "30 Days"
+    }],
+    "daysInYearTypeOptions": [{
+        "id": 1,
+        "code": "DaysInYearType.actual",
+        "value": "Actual"
+    },
+    {
+        "id": 360,
+        "code": "DaysInYearType.days360",
+        "value": "360 Days"
+    },
+    {
+        "id": 364,
+        "code": "DaysInYearType.days364",
+        "value": "364 Days"
+    },
+    {
+        "id": 365,
+        "code": "DaysInYearType.days365",
+        "value": "365 Days"
+    }],
+    "interestRecalculationCompoundingTypeOptions": [{
+        "id": 0,
+        "code": "interestRecalculationCompoundingMethod.none",
+        "value": "None"
+    },
+    {
+        "id": 2,
+        "code": "interestRecalculationCompoundingMethod.fee",
+        "value": "Fee"
+    },
+    {
+        "id": 1,
+        "code": "interestRecalculationCompoundingMethod.interest",
+        "value": "Interest"
+    },
+    {
+        "id": 3,
+        "code": "interestRecalculationCompoundingMethod.interest.and.fee",
+        "value": "Fee and Interest"
+    }],
+    "rescheduleStrategyTypeOptions": [{
+        "id": 3,
+        "code": "loanRescheduleStrategyMethod.reduce.emi.amount",
+        "value": "Reduce EMI amount"
+    },
+    {
+        "id": 2,
+        "code": "loanRescheduleStrategyMethod.reduce.number.of.installments",
+        "value": "Reduce number of installments"
+    },
+    {
+        "id": 1,
+        "code": "loanRescheduleStrategyMethod.reschedule.next.repayments",
+        "value": "Reschedule next repayments"
+    }],
+	"interestRecalculationFrequencyTypeOptions": [
+		{
+			"id":1,
+			"code":"interestRecalculationFrequencyType.same.as.repayment.period",
+			"value":"Same as repayment period"
+		},
+		{
+			"id":2,
+			"code":"interestRecalculationFrequencyType.daily",
+			"value":"Daily"
+		},
+		{
+			"id":3,
+			"code":"interestRecalculationFrequencyType.weekly",
+			"value":"Weekly"
+		},
+		{
+			"id":4,
+			"code":"interestRecalculationFrequencyType.monthly",
+			"value":"Monthly"
+		}]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loanproducts_retrieve" name="loanproducts_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Loan Product</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>loanproducts/1</div>
+					<br>
+					<br>
+					<div class=apiClick>loanproducts/1?template=true</div>
+					<br>
+					<br>
+					<div class=apiClick>loanproducts/1?fields=name,description,numberOfRepayments</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loanproducts/{productId}
+					</code>
+					<code class="method-response">
+{
+  "id": 11,
+  "name": "advanced accounting",
+  "shortName": "ad11",
+  "includeInBorrowerCycle": true,
+  "useBorrowerCycle": true,
+  "status": "loanProduct.active",
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "inMultiplesOf": 0,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "principal": 10000.000000,
+  "minPrincipal": 2000.000000,
+  "maxPrincipal": 15000.000000,
+  "numberOfRepayments": 7,
+  "repaymentEvery": 7,
+  "repaymentFrequencyType": {
+    "id": 0,
+    "code": "repaymentFrequency.periodFrequencyType.days",
+    "value": "Days"
+  },
+  "interestRatePerPeriod": 5.000000,
+  "interestRateFrequencyType": {
+    "id": 2,
+    "code": "interestRateFrequency.periodFrequencyType.months",
+    "value": "Per month"
+  },
+  "annualInterestRate": 60.000000,
+  "amortizationType": {
+    "id": 1,
+    "code": "amortizationType.equal.installments",
+    "value": "Equal installments"
+  },
+  "interestType": {
+    "id": 0,
+    "code": "interestType.declining.balance",
+    "value": "Declining Balance"
+  },
+  "interestCalculationPeriodType": {
+    "id": 1,
+    "code": "interestCalculationPeriodType.same.as.repayment.period",
+    "value": "Same as repayment period"
+  },
+  "transactionProcessingStrategyId": 1,
+  "transactionProcessingStrategyName": "Mifos style",
+  "charges": [],
+  "principalVariationsForBorrowerCycle": [
+    {
+      "id": 21,
+      "borrowerCycleNumber": 1,
+      "paramType": {
+        "id": 1,
+        "code": "LoanProductParamType.principal",
+        "value": "principal"
+      },
+      "valueConditionType": {
+        "id": 2,
+        "code": "LoanProductValueConditionType.equal",
+        "value": "equals"
+      },
+      "minValue": 2000.000000,
+      "maxValue": 20000.000000,
+      "defaultValue": 15000.000000
+    },
+    {
+      "id": 20,
+      "borrowerCycleNumber": 1,
+      "paramType": {
+        "id": 1,
+        "code": "LoanProductParamType.principal",
+        "value": "principal"
+      },
+      "valueConditionType": {
+        "id": 3,
+        "code": "LoanProductValueConditionType.greterthan",
+        "value": "greter than"
+      },
+      "minValue": 3000.000000,
+      "maxValue": 25000.000000,
+      "defaultValue": 20000.000000
+    }
+  ],
+  "interestRateVariationsForBorrowerCycle": [],
+  "numberOfRepaymentVariationsForBorrowerCycle": [],
+  "accountingRule": {
+    "id": 2,
+    "code": "accountingRuleType.cash",
+    "value": "CASH BASED"
+  },
+  "accountingMappings": {
+    "fundSourceAccount": {
+      "id": 1,
+      "name": "fund source",
+      "glCode": "01"
+    },
+    "loanPortfolioAccount": {
+      "id": 2,
+      "name": "Loan portfolio",
+      "glCode": "02"
+    },
+    "transfersInSuspenseAccount": {
+      "id": 3,
+      "name": "transfers",
+      "glCode": "03"
+    },
+    "interestOnLoanAccount": {
+      "id": 4,
+      "name": "income from interest",
+      "glCode": "04"
+    },
+    "incomeFromFeeAccount": {
+      "id": 8,
+      "name": "income from fees 2",
+      "glCode": "10"
+    },
+    "incomeFromPenaltyAccount": {
+      "id": 9,
+      "name": "income from penalities 2",
+      "glCode": "11"
+    },
+    "writeOffAccount": {
+      "id": 10,
+      "name": "loans written off 2",
+      "glCode": "12"
+    },
+    "overpaymentLiabilityAccount": {
+      "id": 11,
+      "name": "over payment",
+      "glCode": "13"
+    }
+  },
+  "paymentChannelToFundSourceMappings": [
+    {
+      "paymentType": {
+        "id": 10,
+        "name": "check"
+      },
+      "fundSourceAccount": {
+        "id": 1,
+        "name": "fund source",
+        "glCode": "01"
+      }
+    }
+  ],
+  "feeToIncomeAccountMappings": [
+    {
+      "charge": {
+        "id": 1,
+        "name": "flat install",
+        "active": false,
+        "penalty": false
+      },
+      "incomeAccount": {
+        "id": 8,
+        "name": "income from fees 2",
+        "glCode": "10"
+      }
+    },
+    {
+      "charge": {
+        "id": 2,
+        "name": "install per",
+        "active": false,
+        "penalty": false
+      },
+      "incomeAccount": {
+        "id": 4,
+        "name": "income from interest",
+        "glCode": "04"
+      }
+    },
+    {
+      "charge": {
+        "id": 5,
+        "name": "des charge",
+        "active": false,
+        "penalty": false
+      },
+      "incomeAccount": {
+        "id": 9,
+        "name": "income from penalities 2",
+        "glCode": "11"
+      }
+    },
+    "multiDisburseLoan":true,"maxTrancheCount":3,"outstandingLoanBalance":36000.000000,
+    "overdueDaysForNPA":2,
+	"principalThresholdForLastInstalment":50
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loanproducts_create" name="loanproducts_create"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Loan Product</h4>
+					<p>Depending of the Accounting Rule (accountingRule) selected,
+				     additional fields with details of the appropriate Ledger Account
+					 identifiers would need to be passed in.
+					</p>
+
+					</p>Refer <a href="https://mifosforge.jira.com/wiki/display/MIFOSX/A+Possible+accounting+Spec">
+					 MifosX Accounting Specs Draft</a> for more details regarding the
+					 significance of the selected accounting rule
+					</p>
+
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name, shortName, currencyCode, digitsAfterDecimal, inMultiplesOf, principal,
+								numberOfRepayments, repaymentEvery, repaymentFrequencyType,
+								interestRatePerPeriod, interestRateFrequencyType, amortizationType, interestType,
+								interestCalculationPeriodType, transactionProcessingStrategyId,
+								accountingRule, isInterestRecalculationEnabled, daysInYearType, daysInMonthType</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, includeInBorrowerCycle,
+							useBorrowerCycle,principalVariationsForBorrowerCycle,
+							numberOfRepaymentVariationsForBorrowerCycle,
+							interestRateVariationsForBorrowerCycle,
+							multiDisburseLoan,maxTrancheCount,
+							outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, allowAttributeOverrides, allowPartialPeriodInterestCalcualtion
+							</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Mandatory Fields for Cash(2) based accounting</div></td>
+						</tr>
+						<tr class=alt>
+							<td>fundSourceAccountId, loanPortfolioAccountId, interestOnLoanAccountId,
+								incomeFromFeeAccountId, incomeFromPenaltyAccountId,
+								writeOffAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Mandatory Fields for periodic (3) and upfront (4)accrual accounting</div></td>
+						</tr>
+						<tr class=alt>
+							<td>fundSourceAccountId, loanPortfolioAccountId, interestOnLoanAccountId,
+								incomeFromFeeAccountId, incomeFromPenaltyAccountId,
+								writeOffAccountId, receivableInterestAccountId, receivableFeeAccountId,
+								receivablePenaltyAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Mandatory Fields if interest recalculation is enabled(true)</div></td>
+						</tr>
+						<tr class=alt>
+							<td>interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Optional Fields if interest recalculation is enabled(true) </div></td>
+						</tr>
+						<tr class=alt>
+							<td>isArrearsBasedOnOriginalSchedule, preClosureInterestCalculationStrategy</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Optional Fields if interest recalculation is enabled(true) and recalculationRestFrequencyType is not same as repayment period</div></td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationRestFrequencyInterval, recalculationRestFrequencyDate</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Optional Fields if interest recalculation is enabled(true) and interestRecalculationCompoundingMethod is enabled</div></td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationCompoundingFrequencyType</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Optional Fields if interest recalculation is enabled(true) and interestRecalculationCompoundingMethod is enabled and
+							recalculationCompoundingFrequencyType is not same as repayment period</div></td>
+						</tr>
+						<tr class=alt>
+							<td>recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Mandatory Fields if Hold Guarantee funds is enabled(true)</div></td>
+						</tr>
+						<tr class=alt>
+							<td>mandatoryGuarantee</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Additional Optional Fields if Hold Guarantee funds is enabled(true)</div></td>
+						</tr>
+						<tr class=alt>
+							<td>minimumGuaranteeFromOwnFunds,minimumGuaranteeFromGuarantor</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loanproducts
+					</code>
+					<code class="method-request">
+POST loanproducts
+Content-Type: application/json
+Request Body:
+{"currencyCode":"USD",
+"includeInBorrowerCycle":"true",
+"useBorrowerCycle":true,
+"digitsAfterDecimal":"2",
+"inMultiplesOf":"0",
+"repaymentFrequencyType":0,
+"interestRateFrequencyType":2,
+"amortizationType":1,
+"interestType":0,
+"interestCalculationPeriodType":1,
+"transactionProcessingStrategyId":1,
+"principalVariationsForBorrowerCycle":[],
+"interestRateVariationsForBorrowerCycle":[],
+"numberOfRepaymentVariationsForBorrowerCycle":[
+	{
+		"valueConditionType":2,
+		"borrowerCycleNumber":"1",
+		"minValue":"5",
+		"defaultValue":"10",
+		"maxValue":"15"
+	},
+	{
+		"valueConditionType":3,
+		"borrowerCycleNumber":"1",
+		"minValue":"7",
+		"defaultValue":"15",
+		"maxValue":"20"
+	}
+],
+"allowAttributeOverrides":
+	{amortizationType : true,
+	 interestType : true,
+	 transactionProcessingStrategyId : false,
+	 interestCalculationPeriodType : true,
+	 inArrearsTolerance : false,
+	 repaymentEvery : true,
+	 graceOnPrincipalAndInterestPayment : true,
+	 graceOnArrearsAgeing : true},
+
+"accountingRule":"2",
+"name":"product 5",
+"shortName":"prd5",
+"principal":"5000",
+"numberOfRepayments":"7",
+"repaymentEvery":"7",
+"interestRatePerPeriod":"5",
+"paymentChannelToFundSourceMappings":[],
+"feeToIncomeAccountMappings":[],
+"penaltyToIncomeAccountMappings":[],
+"charges":[],
+"overdueDaysForNPA":2,
+"dateFormat":"dd MMMM yyyy",
+"locale":"en",
+"fundSourceAccountId":1,
+"loanPortfolioAccountId":2,
+"transfersInSuspenseAccountId":3,
+"interestOnLoanAccountId":4,
+"incomeFromFeeAccountId":8,
+"incomeFromPenaltyAccountId":9,
+"writeOffAccountId":10,
+"overpaymentLiabilityAccountId":11,
+"daysInMonthType":1,
+"daysInYearType":1,
+"isInterestRecalculationEnabled":"false",
+"holdGuaranteeFunds":"false",
+"principalThresholdForLastInstallment":20
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 1
+}
+					</code>
+					<code class="method-request">
+POST loanproducts
+Content-Type: application/json
+Request Body:
+{
+    "name": "LP Cash Accounting",
+    "shortName": "LPCA",
+    "currencyCode": "USD",
+    "locale": "en_GB",
+    "digitsAfterDecimal": "2",
+    "inMultiplesOf": 0,
+    "principal": "100,000.00",
+    "numberOfRepayments": "12",
+    "repaymentEvery": "1",
+    "repaymentFrequencyType": 2,
+    "transactionProcessingStrategyId": 1,
+    "interestRatePerPeriod": "1.75",
+    "interestRateFrequencyType": 2,
+    "amortizationType": 1,
+    "interestType": 0,
+    "interestCalculationPeriodType": 1,
+    "daysInMonthType":1,
+    "daysInYearType":1,
+    "isInterestRecalculationEnabled":"true",
+    "interestRecalculationCompoundingMethod":"0",
+    "rescheduleStrategyMethod":"3",
+	"recalculationRestFrequencyType":"1",
+	"preClosureInterestCalculationStrategy":1,
+    "accountingRule":"2",
+    "fundSourceAccountId":"4",
+    "loanPortfolioAccountId":"8",
+    "interestOnLoanAccountId":"34",
+    "incomeFromFeeAccountId":"37",
+    "incomeFromPenaltyAccountId":"35",
+    "writeOffAccountId":"41",
+    "transfersInSuspenseAccountId":"4",
+    "overpaymentLiabilityAccountId":"2",
+    "paymentChannelToFundSourceMappings": [
+    {
+      "paymentTypeId": "11",
+      "fundSourceAccountId": "11"
+    }
+  ],
+  "feeToIncomeAccountMappings": [
+    {
+      "chargeId": "1",
+      "incomeAccountId": "5"
+    },
+    {
+      "chargeId": "2",
+      "incomeAccountId": "8"
+    }
+  ],
+  "penaltyToIncomeAccountMappings": [
+    {
+      "chargeId": "6",
+      "incomeAccountId": "9"
+    }
+  ]
+
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 2
+}
+					</code>
+					<code class="method-request">
+POST loanproducts
+Content-Type: application/json
+Request Body:
+{
+    "name": "LP Accrual Accounting",
+    "shortName": "LPAA",
+    "currencyCode": "USD",
+    "locale": "en_GB",
+    "digitsAfterDecimal": "2",
+    "inMultiplesOf": 0,
+    "principal": "100,000.00",
+    "numberOfRepayments": "12",
+    "repaymentEvery": "1",
+    "repaymentFrequencyType": 2,
+    "transactionProcessingStrategyId": 1,
+    "interestRatePerPeriod": "1.75",
+    "interestRateFrequencyType": 2,
+    "amortizationType": 1,
+    "interestType": 0,
+    "interestCalculationPeriodType": 1,
+    "daysInMonthType":1,
+    "daysInYearType":1,
+    "isInterestRecalculationEnabled":"false",
+    "accountingRule":"3",
+    "fundSourceAccountId":"4",
+    "loanPortfolioAccountId":"8",
+    "receivableInterestAccountId":"9",
+    "receivableFeeAccountId":"11",
+    "receivablePenaltyAccountId":"10",
+    "interestOnLoanAccountId":"34",
+    "incomeFromFeeAccountId":"37",
+    "incomeFromPenaltyAccountId":"35",
+    "overpaymentLiabilityAccountId":"2",
+    "writeOffAccountId":"41"
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 3
+}
+					</code>
+
+				</div>
+			</div>
+
+			<a id="loanproducts_update" name="loanproducts_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Loan Product</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/loanproducts/{loanId}
+					</code>
+					<code class="method-request">
+PUT loanproducts/1
+Content-Type: application/json
+Request Body:
+{
+    "locale": "en_GB",
+    "principal": "70,000.00"
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1,
+    "changes": {
+        "principal": 70000,
+        "locale": "en_GB"
+    }
+}
+					</code>
+					<code class="method-request">
+PUT loanproducts/3
+Content-Type: application/json
+Request Body:
+{
+    "locale": "en",
+    "isInterestRecalculationEnabled":"true",
+    "interestRecalculationCompoundingMethod":"0",
+    "rescheduleStrategyMethod":"3",
+	"recalculationRestFrequencyType":"1"
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1,
+    "changes": {
+        "locale": "en",
+        "isInterestRecalculationEnabled":"true",
+        "interestRecalculationCompoundingMethod":"0",
+        "rescheduleStrategyMethod":"3",
+		"recalculationRestFrequencyType":"1"
+    }
+}
+					</code>
+				</div>
+			</div>
+			<!-- end of loan products api docs -->
+
+			<!-- start of loan products mix api docs -->
+			<a id="loanproductmix" name="loanproductmix" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Loan Products Mix</h3>
+					<p>If you have the appropriate permissions, you can decide which types of products a given client or group can mix. In this way, you can restrict clients from having active loans accounts of different products at the same time.</p>
+
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>restrictedProducts</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>List of productIds to create a product mix</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="loanproductmix_list" name="loanproductmix_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Loan Product Mix</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>loanproducts?associations=productMixes</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loanproducts?associations=productMixes
+					</code>
+					<code class="method-response">
+[
+  {
+    "productId": 1,
+    "productName": "Personal Loan",
+    "restrictedProducts": [
+      {
+        "id": 2,
+        "name": "Joint Loan",
+        "includeInBorrowerCycle": false
+      },
+      {
+        "id": 5,
+        "name": "Primary Loan",
+        "includeInBorrowerCycle": false
+      },
+      {
+        "id": 3,
+        "name": "Daily Loan",
+        "includeInBorrowerCycle": false
+      }
+    ],
+    "allowedProducts": [
+      {
+        "id": 1,
+        "name": "Personal Loan",
+        "includeInBorrowerCycle": false
+      },
+      {
+        "id": 6,
+        "name": "Personal Loan -2",
+        "includeInBorrowerCycle": false
+      },
+      {
+        "id": 4,
+        "name": "Weekly Loan",
+        "includeInBorrowerCycle": false
+      }
+    ]
+  },
+  {
+    "productId": 2,
+    "productName": "Joint Loan",
+    "restrictedProducts": [
+      {
+        "id": 1,
+        "name": "Personal Loan",
+        "includeInBorrowerCycle": false
+      },
+      {
+        "id": 6,
+        "name": "Personal Loan -2",
+        "includeInBorrowerCycle": false
+      }
+    ],
+    "allowedProducts": [
+      {
+        "id": 3,
+        "name": "Daily Loan",
+        "includeInBorrowerCycle": false
+      },
+      {
+        "id": 2,
+        "name": "Joint Loan",
+        "includeInBorrowerCycle": false
+      },
+      {
+        "id": 5,
+        "name": "Primary Loan",
+        "includeInBorrowerCycle": false
+      },
+      {
+        "id": 4,
+        "name": "Weekly Loan",
+        "includeInBorrowerCycle": false
+      }
+    ]
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="loanproductmix_template" name="loanproductmix_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Product Mix Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+          <p>This request gets all the products details for which productmix is not defined.</p>
+          <br/>
+					<div class=apiClick>loanproducts/template?isProductMixTemplate=true</div>
+          <br/><br/>
+          <p>This request gets the productmix details for the specific loanproduct.</p>
+          <br/>
+          <div class=apiClick>loanproducts/5/productmix</div>
+          <br/>
+          <div class=apiClick>loanproducts/5/productmix?template=true</div>
+
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/loanproducts/template?isProductMixTemplate=true
+					</code>
+					<code class="method-response">
+{
+  "productOptions": [
+    {
+      "id": 5,
+      "name": "Primary Loan"
+    },
+    {
+      "id": 4,
+      "name": "Weekly Loan"
+    }
+  ]
+}
+					</code>
+          <code class="method-declaration">
+GET https://DomainName/api/v1/loanproducts/5/productmix
+          </code>
+          <code class="method-response">
+{
+  "restrictedProducts": [
+    {
+      "id": 1,
+      "name": "Personal Loan",
+      "includeInBorrowerCycle": false
+    }
+  ],
+  "allowedProducts": [
+    {
+      "id": 3,
+      "name": "Daily Loan",
+      "includeInBorrowerCycle": false
+    },
+    {
+      "id": 2,
+      "name": "Joint Loan",
+      "includeInBorrowerCycle": false
+    },
+    {
+      "id": 6,
+      "name": "Personal Loan -2",
+      "includeInBorrowerCycle": false
+    },
+    {
+      "id": 5,
+      "name": "Primary Loan",
+      "includeInBorrowerCycle": false
+    },
+    {
+      "id": 4,
+      "name": "Weekly Loan",
+      "includeInBorrowerCycle": false
+    }
+  ]
+}
+          </code>
+				</div>
+			</div>
+
+      <a id="loanproductmix_retrieve" name="loanproductmix_retrieve"
+        class="old-syle-anchor">&nbsp;</a>
+      <div class="method-section">
+        <div class="method-description">
+          <h4>Retrieve Loan Product Mix</h4>
+          <p>Example Requests:</p>
+          <div class=apiClick>loanproducts/5/productmix</div>
+        </div>
+        <div class="method-example">
+          <code class="method-declaration">
+GET https://DomainName/api/v1/loanproducts/5/productmix
+          </code>
+          <code class="method-response">
+{
+  "restrictedProducts": [
+    {
+      "id": 1,
+      "name": "Personal Loan",
+      "includeInBorrowerCycle": false
+    }
+  ],
+  "allowedProducts": [
+    {
+      "id": 3,
+      "name": "Daily Loan",
+      "includeInBorrowerCycle": false
+    },
+    {
+      "id": 2,
+      "name": "Joint Loan",
+      "includeInBorrowerCycle": false
+    },
+    {
+      "id": 6,
+      "name": "Personal Loan -2",
+      "includeInBorrowerCycle": false
+    },
+    {
+      "id": 5,
+      "name": "Primary Loan",
+      "includeInBorrowerCycle": false
+    },
+    {
+      "id": 4,
+      "name": "Weekly Loan",
+      "includeInBorrowerCycle": false
+    }
+  ]
+}
+          </code>
+        </div>
+      </div>
+
+			<a id="loanproductmix_create" name="loanproductmix_create"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Loan Product Mix</h4>
+
+          <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>restrictedProducts</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/loanproducts/5/productmix
+					</code>
+					<code class="method-request">
+POST loanproducts/{productId}/productmix
+Content-Type: application/json
+Request Body:
+{"restrictedProducts":["1"]}
+					</code>
+					<code class="method-response">
+{
+  "changes":{
+    "restrictedProductsForMix":[1],
+    "removedProductsForMix":[]
+  },
+  "productId":5
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="loanproductmix_update" name="loanproductmix_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Loan Product Mix</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/loanproducts/{productId}/productmix
+					</code>
+					<code class="method-request">
+PUT loanproducts/5/productmix
+Content-Type: application/json
+Request Body:
+{
+    "restrictedProducts":["3"]
+}
+					</code>
+					<code class="method-response">
+{
+  "changes": {
+    "restrictedProductsForMix": [3],
+    "removedProductsForMix": [1]
+  },
+  "productId": 5
+}
+					</code>
+				</div>
+			</div>
+
+      <a id="loanproductmix_delete" name="loanproductmix_delete"
+        class="old-syle-anchor">&nbsp;</a>
+      <div class="method-section">
+        <div class="method-description">
+          <h4>Delete a Loan Product Mix</h4>
+        </div>
+        <div class="method-example">
+          <code class="method-declaration">
+DELETE https://DomainName/api/v1/loanproducts/{productId}/productmix
+          </code>
+          <code class="method-request">
+DELETE loanproducts/5/productmix
+          </code>
+          <code class="method-response">
+{
+  "changes": {
+    "removedProductsForMix": [3]
+  },
+  "productId": 5
+}
+          </code>
+        </div>
+      </div>
+			<!-- end of loan products mix api docs -->
+
+		<!-- start of holidays api docs -->
+		<a id="holidays" name="holidays" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h3>Holidays</h3>
+					<p>Some MFI's span large regions where different branch offices might observe different holidays. They need the ability to define holidays for specific branch offices and be able to set the repayment rule to follow during those holidays.
+					</p>
+					<p>
+						The reschedule of repayments to <b>repaymentsRescheduledTo</b> date during defined holidays is turned on/off by enabling/disabling <i>reschedule-repayments-on-holidays</i> in <a href="#configs_global">Global configurations</a>.
+					</p>
+					<p> Allow Repayment transactions on a defined holidays is turned on/off by enabling/disabling allow-transactions-on-holiday in <a href="#configs_global">Global configurations</a>.
+					</p>
+				<table class=matrixHeading>
+					<tr class="matrixHeadingBG">
+						<td><div class="fineractHeading2">Field Descriptions</div></td>
+					</tr>
+					<tr class=alt>
+						<td>name</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>Name of the holiday.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>fromDate</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>The date on holiday begins.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>toDate</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>The date on holiday ends.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>repaymentsRescheduledTo</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>Date to which repayments will be rescheduled when repayments date falls on a defined holiday. e.g. A holiday is defined on 25th of October 2013 with repaymentsRescheduledTo date to 26th of October 2013 and any loans with repayments date on 25th of October 2013 will be rescheduled to 26th of October 2013.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>officeId</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>The officeId represents the office to which holiday is applied.
+						</td>
+					</tr>
+				</table>
+			</div>
+		</div>
+
+		<a id="holidays_list" name="holidays_list" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h4>List Holidays</h4>
+				<p>Example Requests:</p>
+				<div class=apiClick>holidays?officeId=1</div>
+				<br>
+				<br>
+			</div>
+			<div class="method-example">
+				<code class="method-declaration">
+GET https://DomainName/api/v1/holidays?officeId=1
+				</code>
+				<code class="method-response">
+[
+{
+    "id": 1,
+    "name": "Good Friday",
+    "fromDate": [
+      2013,
+      10,
+      25
+    ],
+    "toDate": [
+      2013,
+      10,
+      25
+    ],
+    "repaymentsScheduleTo": [
+      2013,
+      10,
+      26
+    ],
+    "status": {
+      "id": 100,
+      "code": "holidayStatusType.pending.for.activation",
+      "value": "Pending for activation"
+    }
+}
+]
+				</code>
+			</div>
+		</div>
+
+		<a id="holidays_create" name="holiday_create" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h4>Create a Holiday</h4>
+				<table class=matrixHeading>
+					<tr class="matrixHeadingBG">
+						<td><div class="fineractHeading2">Mandatory Fields</div></td>
+					</tr>
+					<tr class=alt>
+						<td>name, description, fromDate, toDate, repaymentsRescheduledTo, offices</td>
+					</tr>
+				</table>
+			</div>
+			<div class="method-example">
+				<code class="method-declaration">
+POST https://DomainName/api/v1/holidays
+				</code>
+				<code class="method-request">
+POST holidays
+Content-Type: application/json
+Request Body:
+{
+"name": "Good Friday",
+"description": "Good Friday",
+"dateFormat": "dd MMMM yyyy",
+"locale": "en",
+"fromDate": "25 October 2013",
+"toDate": "25 October 2013",
+"repaymentsRescheduledTo": "26 October 2013",
+"offices": [
+	{"officeId":"1"},
+	{"officeId":"2"}
+]
+}
+				</code>
+				<code class="method-response">
+{
+"resourceId": 1
+}
+				</code>
+			</div>
+		</div>
+
+		<a id="holidays_activate" name="holidays_activate" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h4>Activate a Holiday</h4>
+				<p>Always Holidays are created in pending state. This API allows to activate a holiday.</p>
+				<p>Only the active holidays are considered for rescheduling the loan repayment.</p>
+			</div>
+			<div class="method-example">
+				<code class="method-declaration">
+POST https://DomainName/api/v1/holidays/{holidayId}?command=activate
+				</code>
+				<code class="method-request">
+POST holidays
+Content-Type: application/json
+Request Body:
+{
+}
+				</code>
+				<code class="method-response">
+{
+"resourceId": 1
+}
+				</code>
+			</div>
+		</div>
+
+		<a id="holidays_retrieve" name="holidays_retrieve"
+			class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h4>Retrieve a Holiday</h4>
+				<p>Example Requests:</p>
+				<div class=apiClick>holidays/1</div>
+				<br>
+			</div>
+			<div class="method-example">
+				<code class="method-declaration">
+GET https://DomainName/api/v1/holidays/{holidayId}
+				</code>
+				<code class="method-response">
+{
+  "id": 1,
+  "name": "Good Friday",
+  "fromDate": [
+    2013,
+    10,
+    25
+  ],
+  "toDate": [
+    2013,
+    10,
+    25
+  ],
+  "repaymentsRescheduledTo": [
+    2013,
+    10,
+    26
+  ],
+  "status": {
+	  "id": 100,
+	  "code": "holidayStatusType.active",
+	  "value": "Active"
+  }
+}
+				</code>
+			</div>
+		</div>
+
+		<a id="holidays_update" name="holidays_update" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h4>Update a Holiday</h4>
+				<p>If a holiday is in pending state (created and not activated) then all fields are allowed to modify. Once holidays become active only name and descriptions are allowed to modify.</p>
+			</div>
+			<div class="method-example">
+				<code class="method-declaration">
+PUT https://DomainName/api/v1/holidays/{holidayId}
+				</code>
+				<code class="method-request">
+PUT holidays/1
+Content-Type: application/json
+Request Body:
+{
+	name:"Independence day",
+	description: "Holiday for Independence day celebration"
+}
+				</code>
+				<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {
+    "name": "Independence day",
+    "description": "Holiday for Independence day celebration"
+  }
+}
+				</code>
+			</div>
+		</div>
+
+		<a id="holidays_delete" name="holidays_delete" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h4>Delete a Holiday</h4>
+				<p>This API allows to delete a holiday. This is a soft delete the deleted holiday status is marked as deleted.</p>
+			</div>
+			<div class="method-example">
+				<code class="method-declaration">
+DELETE https://DomainName/api/v1/holidays/{holidayId}
+				</code>
+				<code class="method-request">
+DELETE holidays/1
+Content-Type: application/json
+Request Body:
+{
+}
+				</code>
+				<code class="method-response">
+{
+  "resourceId": 1
+}
+				</code>
+			</div>
+		</div>
+		<!-- end of holidays api docs -->
+
+<!-- start of workingdays api docs -->
+		<a id="workingdays" name="workingdays" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h3>Working days</h3>
+					<p>The days of the week that are workdays.
+					</p>
+					<p>
+						Rescheduling of repayments when it falls on a non-working is turned on /off by enable/disable reschedule-future-repayments parameter in <a href="#configs_global">Global configurations</a>.
+					</p>
+					<p>
+						Allow transactions on non-working days is configurable by enabling/disbaling the allow-transactions-on-non_workingday parameter in <a href="#configs_global">Global configurations</a>.
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandotory Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>recurrence</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Recurrence pattern</td>
+						</tr>
+						<tr class=alt>
+							<td>repaymentRescheduleType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>repayment reschedule type .Options listed on the schedule for repayments on non-working days
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>extendTermForDailyRepayments</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Extend the term for loans following a daily repayment schedule.Expects boolean value true/false</td>
+						</tr>
+						<tr class=alt>
+							<td>locale</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>locale must be provided.</td>
+						</tr>
+
+					</table>
+				<table id="nw_repayment_schedule_rule" class=matrixHeading>
+					<tr class="matrixHeadingBG">
+						<td><div class="fineractHeading2">Schedule for repayments on non-working day</div></td>
+					</tr>
+					<tr class=alt>
+						<td>Same day</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>The repayments schedule for same day regardless of it's a non-working day.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>Next workday</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>The repayments reschedule to next working day. e.g. if a repayment falls on a non-working day will be rescheduled to next available working day.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>Previous workday</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>The repayments reschedule to previous working day. e.g. if a repayment falls on a non-working day will be rescheduled to previous available working day.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>Next scheduled meeting</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>The repayments reschduled to next scheduled meeting.
+						</td>
+					</tr>
+				</table>
+			</div>
+		</div>
+					<a id="workingdays_template" name="workingdays_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Working Days Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for working days.
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>workingdays/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/workingdays/template
+					</code>
+					<code class="method-response">
+{
+    "repaymentRescheduleOptions": [
+        {
+            "id": 1,
+            "code": "RepaymentRescheduleType.same.day",
+            "value": "same day"
+        },
+        {
+            "id": 4,
+            "code": "RepaymentRescheduleType.move.to.previous.working.day",
+            "value": "move to previous working day"
+        },
+        {
+            "id": 3,
+            "code": "RepaymentRescheduleType.move.to.next.repayment.meeting.day",
+            "value": "move to next repayment meeting day"
+        },
+        {
+            "id": 2,
+            "code": "RepaymentRescheduleType.move.to.next.working.day",
+            "value": "move to next working day"
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+		<a id="workingdays_list" name="workingdays_list" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h4>List Working days</h4>
+				<p>Example Requests:</p>
+				<div class=apiClick>workingdays</div>
+				<br>
+				<br>
+			</div>
+			<div class="method-example">
+				<code class="method-declaration">
+GET https://DomainName/api/v1/workingdays
+				</code>
+				<code class="method-response">
+[
+    {
+        "id": 1,
+        "recurrence": "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR",
+        "repaymentRescheduleType": {
+            "id": 4,
+            "code": "RepaymentRescheduleType.move.to.next.working.day",
+            "value": "move to next working day"
+        }
+        "extendTermForDailyRepayments" : true
+    }
+]
+				</code>
+			</div>
+		</div>
+				<a id="workingdays_update" name="workingdays_update" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h4>Update a Working Day</h4>
+				<table class=matrixHeading>
+					<tr class="matrixHeadingBG">
+						<td><div class="fineractHeading2">Mandatory Fields</div></td>
+					</tr>
+					<tr class=alt>
+						<td>recurrence,repaymentRescheduleType,extendTermForDailyRepayments,locale</td>
+					</tr>
+				</table>
+			</div>
+			<div class="method-example">
+				<code class="method-declaration">
+PUT https://DomainName/api/v1/Workingdays/1
+				</code>
+				<code class="method-request">
+PUT holidays
+Content-Type: application/json
+Request Body:
+{
+"recurrence": "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,SU",
+"locale": "en",
+"repaymentsRescheduledType": 4,
+"extendTermForDailyRepayments": false
+}
+				</code>
+				<code class="method-response">
+{
+"resourceId": 1
+}
+				</code>
+			</div>
+		</div>
+
+			<!-- end of workingdays api docs -->
+
+			<!-- start of configurations api docs -->
+			<a id="configs" name="configs" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Currency</h3>
+					<p>Application related configuration around viewing/updating
+						the currencies permitted for use within the MFI.</p>
+				</div>
+			</div>
+
+			<a id="configs_currencyretrieve" name="configs_currencyretrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Currency Configuration</h4>
+					<p>Returns the list of currencies permitted for use AND the
+						list of currencies not selected (but available for selection).</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>currencies</div>
+					<br>
+					<br>
+					<div class=apiClick>currencies?fields=selectedCurrencyOptions</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/currencies
+					</code>
+					<code class="method-response">
+{
+    "selectedCurrencyOptions": [
+        {
+            "code": "USD",
+            "name": "US Dollar",
+            "decimalPlaces": 2,
+            "displaySymbol": "$",
+            "nameCode": "currency.USD",
+            "displayLabel": "US Dollar ($)"
+        }
+    ],
+    "currencyOptions": [
+        {
+            "code": "AFN",
+            "name": "Afghanistan Afghani",
+            "decimalPlaces": 2,
+            "nameCode": "currency.AFN",
+            "displayLabel": "Afghanistan Afghani [AFN]"
+        },
+        {
+            "code": "ALL",
+            "name": "Albanian Lek",
+            "decimalPlaces": 2,
+            "nameCode": "currency.ALL",
+            "displayLabel": "Albanian Lek [ALL]"
+        },
+        {
+            "code": "ZWD",
+            "name": "Zimbabwe Dollar",
+            "decimalPlaces": 2,
+            "nameCode": "currency.ZWD",
+            "displayLabel": "Zimbabwe Dollar [ZWD]"
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_currencyupdate" name="configs_currencyupdate"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update Currency Configuration</h4>
+					<p>Updates the list of currencies permitted for use.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/currencies
+					</code>
+					<code class="method-request">
+PUT currencies
+Content-Type: application/json
+Request Body:
+{
+    "currencies": [
+        "KES",
+        "BND",
+        "LBP",
+        "GHC",
+        "USD",
+        "XOF",
+        "AED",
+        "AMD"
+    ]
+}
+					</code>
+					<code class="method-response">
+{
+    "currencies": [
+        "KES",
+        "BND",
+        "LBP",
+        "GHC",
+        "USD",
+        "XOF",
+        "AED",
+        "AMD"
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_global" name="configs_global" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Global Configuration</h3>
+					<p>Global configuration related to set of supported enable/disable configurations:</p>
+					<ol>
+						<li><b>maker-checker</b> - defaults to false - if true turns on maker-checker functionality</li>
+						<li><b>reschedule-future-repayments</b> - defaults to false - if true reschedules repayemnts which falls on a non-working day to configured repayment <a href="#nw_repayment_schedule_rule">rescheduling rule</a></li>
+						<li><b>allow-transactions-on-non_workingday</b> - defaults to false - if true allows transactions on non-working days</li>
+						<li><b>reschedule-repayments-on-holidays</b> - defaults to false - if true reschedules repayemnts which falls on a non-working day to defined <a href="#holidays">reschedule date</a></li>
+						<li><b>allow-transactions-on-holiday</b> - defaults to false - if true allows transactions on <a href="#holidays">holidays</a></li>
+						<li><b>savings-interest-posting-current-period-end</b> - Set it at the database level before any savings interest is posted. When set as false(default), interest will be posted on the first date of next period. If set as true, interest will be posted on last date of current period. There is no difference in the interest amount posted.</li>
+						<li><b>financial-year-beginning-month</b> - Set it at the database level before any savings interest is posted. Allowed values 1 - 12 (January - December). Interest posting periods are evaluated based on this configuration.</li>
+						<li><b>meetings-mandatory-for-jlg-loans</b> - if set to true, enforces all JLG loans to follow a meeting schedule belonging to either the parent group or Center.</li>
+					</ol>
+				</div>
+			</div>
+
+			<a id="configs_globalconfig_retrieve" name="configs_globalconfig_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Global Configuration</h4>
+					<p>Returns the list global enable/disable configurations.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>configurations</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/configurations
+					</code>
+					<code class="method-response">
+{
+  "globalConfiguration": [
+    {
+    "name": "maker-checker",
+    "enabled": true,
+    "value": 0,
+    "id": 1
+    },
+    {
+    "name": "amazon-S3",
+    "enabled": false,
+    "value": 0,
+    "id": 2
+    },
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+<a id="configs_globalconfig_retrieve_one" name="configs_globalconfig_retrieve_one" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Global Configuration</h4>
+					<p>Returns a global enable/disable configurations.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>configurations/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/configurations/1
+					</code>
+					<code class="method-response">
+    {
+    "name": "maker-checker",
+    "enabled": true,
+    "value": 0,
+    "id": 1
+    }
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_globalconfig_retrieve_survey" name="configs_globalconfig_retrieve_survey" class="old-syle-anchor">&nbsp;</a>
+             <div class="method-section">
+                 <div class="method-description">
+                     <h4>Retrieve Global Configuration for surveys</h4>
+                     <p>Returns the list global enable/disable survey configurations.</p>
+                     <p>Example Requests:</p>
+                     <div class=apiClick>configurations/survey</div>
+                 </div>
+                 <div class="method-example">
+                     <code class="method-declaration">
+                         GET https://DomainName/api/v1/configurations/survey
+                     </code>
+                     <code class="method-response">
+                         {
+                         "ppi_kenya_2005": [
+                         {
+                         "name": "maker-checker",
+                         "enabled": true,
+                         "value": 0,
+                         "id": 1
+                         },
+                         {
+                         "name": "ppi_tanzania_20012",
+                         "enabled": false,
+                         "value": 0,
+                         "id": 2
+                         },
+                         ]
+                         }
+                     </code>
+                 </div>
+             </div>
+
+			<a id="configs_globalconfig_update" name="configs_globalconfig_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update Global Configuration</h4>
+					<p>Updates an enable/disable global configuration item.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/configurations/9
+					</code>
+					<code class="method-request">
+PUT configurations
+Content-Type: application/json
+Request Body: {
+                "enabled":"true",
+                "value":2
+
+               }
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 9,
+    "changes": {
+    "enabled": true
+    }
+}				</code>
+				</div>
+			</div>
+
+	<a id="cache" name="cache" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+		<div class="method-description">
+			<h3>Cache</h3>
+			<p>The following settings are possible for cache:
+				<ul>
+					<li><b>No Caching</b>: caching turned off</li>
+					<li><b>Single node</b>: caching on for single instance deployments of platorm (works for multiple tenants but only one tomcat)</li>
+				</ul>
+			</p>
+			<p>By default caching is set to <i>No Caching</i>. Switching between caches results in the cache been clear e.g. from <i>Single node</i> to <i>No cache</i> and back again would clear down the single node cache.</p>
+		</div>
+	</div>
+
+	<a id="caches_list" name="caches_list" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+		<div class="method-description">
+			<h4>Retrieve Cache Types</h4>
+			<p>Returns the list of caches.</p>
+			<p>Example Requests:</p>
+			<div class=apiClick>caches</div>
+		</div>
+		<div class="method-example">
+			<code class="method-declaration">
+GET https://DomainName/api/v1/caches
+			</code>
+			<code class="method-response">
+[
+  {
+    "cacheType": {
+      "id": 1,
+      "code": "cacheType.noCache",
+      "value": "No cache"
+    },
+    "enabled": true
+  },
+  {
+    "cacheType": {
+      "id": 2,
+      "code": "cacheType.singleNode",
+      "value": "Single node"
+    },
+    "enabled": false
+  },
+  {
+    "cacheType": {
+      "id": 3,
+      "code": "cacheType.multiNode",
+      "value": "Multi node"
+    },
+    "enabled": false
+  }
+]
+			</code>
+		</div>
+	</div>
+
+	<a id="caches_switch" name="caches_switch"
+						class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+		<div class="method-description">
+			<h4>Switch Cache</h4>
+			<p>Switches the cache to chosen one.</p>
+		</div>
+		<div class="method-example">
+			<code class="method-declaration">
+PUT https://DomainName/api/v1/caches
+			</code>
+			<code class="method-request">
+PUT caches
+Content-Type: application/json
+Request Body:
+{
+	"cacheType": 2
+}
+			</code>
+			<code class="method-response">
+{
+  "changes": {
+    "cacheType": 2
+  }
+}
+			</code>
+		</div>
+	</div>
+			<a id="configs_hooks" name="configs_hooks" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Hooks</h3>
+					<p>Hooks are a mechanism to trigger custom code on the occurence of events.</p>
+					<p>Each template during hook creation represents custom behaviour on what actions should be taken on the triggering of a registered event for a hook. The action taken might be firing a HTTP request to another server or execute internal code.
+					<p />
+					<p>In order for MifosX to send webhook payloads, your server needs to be accessible from the Internet. MifosX will send along a few HTTP headers to differentiate between event types.</p>
+					<p>X-Mifos-Entity - The entity type that was triggered. <br/>
+					X-Mifos-Action - The action triggered on the entity type. <br/>
+				    X-Mifos-Platform-TenantId - The tenant which experienced the event.</p>
+				</div>
+			</div>
+
+			<a id="configs_hooks_create" name="configs_hooks_create"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Hook</h4>
+					<p>The following parameters can be passed for the creation of a hook :-
+						<ul>
+							<li> name - string - Required. The name of the template that is being called. (See /hooks/template for the list of valid hook names.)</li>
+							<li> isActive - boolean - Determines whether the hook is actually triggered.</li>
+							<li> events - array - Determines what events the hook is triggered for.
+</li>
+							<li> config - hash - Required. Key/value pairs to provide settings for this hook. These settings vary between the templates.</li>
+							<li> templateId - Optional. The UGD template ID associated with the same entity (client or loan).</li>
+						<ul></p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/hooks
+					</code>
+					<code class="method-request">
+POST hooks
+Content-Type: application/json
+Request Body:
+{
+  "name": "Web",
+  "isActive": true,
+  "displayName": "Kremlin",
+  "templateId": 1,
+  "events": [
+      {
+        "actionName": "DISBURSE",
+        "entityName": "LOAN"
+      },
+      {
+        "actionName": "REPAYMENT",
+        "entityName": "LOAN"
+      }
+  ],
+  "config": {
+    "Payload URL": "http://example.com/webhook",
+    "Content Type": "json"
+  }
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 4
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_hooks_retrieve" name="configs_hooks_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Hooks</h4>
+					<p>Returns the list of hooks.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>hooks</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/hooks
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "name": "Web",
+    "displayName": "Kremlin",
+    "isActive": true,
+    "createdAt": [2014, 9, 16],
+    "updatedAt": [2014, 9, 16],
+    "templateId": 1,
+    "templateName": "My UGD",
+    "events": [
+      {
+        "actionName": "DISBURSE",
+        "entityName": "LOAN"
+      },
+      {
+        "actionName": "REPAYMENT",
+        "entityName": "LOAN"
+      }
+    ],
+    "config": [
+      {
+      "fieldName": "Content Type",
+      "fieldValue": "json"
+      },
+      {
+      "fieldName": "Payload URL",
+      "fieldValue": "https://abc.com/api"
+      }
+    ]
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_hook_retrieve" name="configs_hook_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Hook</h4>
+					<p>Returns the details of a Hook.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>hooks/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/hooks/{hookId}
+					</code>
+					<code class="method-response">
+{
+    "id": 1,
+    "name": "Web",
+    "displayName": "Kremlin",
+    "isActive": true,
+    "createdAt": [2014, 9, 16],
+    "updatedAt": [2014, 9, 16],
+    "templateId": 1,
+    "templateName": "My UGD",
+    "events": [
+      {
+        "actionName": "DISBURSE",
+        "entityName": "LOAN"
+      },
+      {
+        "actionName": "REPAYMENT",
+        "entityName": "LOAN"
+      }
+    ],
+    "config": [
+      {
+      "fieldName": "Content Type",
+      "fieldValue": "json"
+      },
+      {
+      "fieldName": "Payload URL",
+      "fieldValue": "https://abc.com/api"
+      }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_hook_update" name="configs_hook_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Hook</h4>
+					<p>Updates the details of a hook.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/hooks/{hookId}
+					</code>
+					<code class="method-request">
+PUT hooks/4
+Content-Type: application/json
+Request Body:
+{
+  "name": "Web",
+  "isActive": true,
+  "displayName": "Local SMS Provider",
+  "events": [
+      {
+        "actionName": "DISBURSE",
+        "entityName": "LOAN"
+      }
+  ],
+  "config": {
+    "Payload URL": "http://changed.com/sms",
+    "Content Type": "json"
+  }
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 4,
+	"changes": {
+		"displayName": "Local SMS Provider",
+		"events": [
+			{
+				"actionName": "DISBURSE",
+				"entityName": "LOAN"
+			}
+		],
+		"config": {
+			"Payload URL": "http://changed.com/sms",
+			"Content Type": "json"
+		}
+	}
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_hook_delete" name="configs_hook_delete"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Hook</h4>
+					<p>Deletes a hook.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/hooks/{hookId}
+					</code>
+					<code class="method-request">
+DELETE hooks/4
+Content-Type: application/json
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 4
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="hooks_template" name="hooks_template" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Hooks Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>hooks/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/hooks/template
+					</code>
+					<code class="method-response">
+{
+    "templates": [
+       {
+         "name": "Web",
+         "schema": [
+            {
+                "fieldName": "Content Type",
+                "fieldType": "string",
+                "optional": true,
+                "placeholder": "json / form"
+            },
+            {
+                "fieldName": "Payload URL",
+                "fieldType": "string",
+                "optional": false
+            }
+         ]
+       },
+       {
+       },...
+    ],
+    "groupings": [
+        {
+          "name": "jobs",
+          "entities": [
+             {
+                "name": "SCHEDULER",
+                "actions": ["EXECUTEJOB", "UPDATE"]
+             },
+             {
+             },...
+          ]
+        },
+        {
+        },...
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_codes" name="configs_codes" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Codes</h3>
+					<p>Code and code values: Codes represent a specific category of
+						data, their code values are a specific instance of that category.</p>
+					<p>
+						Codes are mostly <b>system defined</b> which means the code itself
+						comes out of the box and cannot be modified however its code values
+						can be. e.g. 'Customer Identifier', it defaults to a code value of
+						'Passport' but could be 'Drivers License, National Id' etc
+					<p />
+				</div>
+			</div>
+
+			<a id="configs_codes_create" name="configs_codes_create"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Code</h4>
+					<p>Creates a code. Codes created through api are always 'user
+						defined' and so system defined is marked as false.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/codes
+					</code>
+					<code class="method-request">
+POST codes
+Content-Type: application/json
+Request Body:
+{
+	"name": "MyNewCode"
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 4
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_codes_retrieve" name="configs_codes_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Codes</h4>
+					<p>Returns the list of codes.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>codes</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/codes
+					</code>
+					<code class="method-response">
+[
+    {
+        "id": 1,
+        "name": "Customer Identifier",
+        "systemDefined": true
+    },
+    {
+        "id": 2,
+        "name": "Gender",
+        "systemDefined": true
+    },
+    {
+        "id": 3,
+        "name": "Education",
+        "systemDefined": true
+    }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_code_retrieve" name="configs_code_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Code</h4>
+					<p>Returns the details of a Code.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>codes/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/codes/{codeId}
+					</code>
+					<code class="method-response">
+{
+    "id": 1,
+    "name": "Customer Identifier",
+    "systemDefined": true
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_code_update" name="configs_codes_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Code</h4>
+					<p>Updates the details of a code if it is not system defined.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/codes/{codeId}
+					</code>
+					<code class="method-request">
+PUT codes/4
+Content-Type: application/json
+Request Body:
+{
+	"name": "MyNewCode(changed)"
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 4,
+    "changes": {
+        "name": "MyNewCode(changed)"
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_code_delete" name="configs_code_delete"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Code</h4>
+					<p>Deletes a code if it is not system defined.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/codes/{codeId}
+					</code>
+					<code class="method-request">
+DELETE codes/4
+Content-Type: application/json
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 4
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- Code Value starts here -->
+			<a id="configs_codes_codevalues" name="configs_codes_codevalues" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Code Values</h3>
+					<p>Code and code values: Codes represent a specific category of
+						data, their code values are a specific instance of that category.</p>
+					<p>
+						Codes are mostly <b>system defined</b> which means the code itself
+						comes out of the box and cannot be modified however its code values
+						can be. e.g. 'Customer Identifier', it defaults to a code value of
+						'Passport' but could be 'Drivers License, National Id' etc
+					<p />
+				</div>
+			</div>
+
+			<a id="configs_codes_codevalues_create" name="configs_codes_codevalues_create"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Code Value</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/codes/1/codevalues
+					</code>
+					<code class="method-request">
+POST codes/1/codevalues
+Content-Type: application/json
+Request Body:
+{
+	"name":"Ration Card",
+	"description: "Ration card information",
+	"position":"1"
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 4
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_codes_codevalues_list" name="configs_codes_codevalues_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Code Values</h4>
+					<p>Returns the list of Code Values for a given Code</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>codes/1/codevalues</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/codes/{codeId}/codevalues
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "name": "Passport",
+    "description: "Passport information",
+    "position": 0
+  },
+  {
+    "id": 2,
+    "name": "Id",
+    "description: "ID information",
+    "position": 0
+  },
+  {
+    "id": 3,
+    "name": "Drivers License",
+    "description: "Drivers License information",
+    "position": 0
+  },
+  {
+    "id": 4,
+    "name": "Any Other Id Type",
+    "description: "Any Other Id Type information",
+    "position": 0
+  },
+  {
+    "id": 5,
+    "name": "Ration Card",
+    "description: "Ration Card information",
+    "position": 1
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_codes_codevalues_retrieve" name="configs_codes_codevalues_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Code Value</h4>
+					<p>Returns the details of a Code Value</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>codes/1/codevalues/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/codes/{codeId}/codevalues/{codevalueId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "name": "Passport",
+  "description: "Passport information",
+  "position": 0
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_codes_codevalues_update" name="configs_codes_codevalues_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Code Value</h4>
+					<p>Updates the details of a code value.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/codes/{codeId}/codevalues/{codevalueId}
+					</code>
+					<code class="method-request">
+PUT codes/1/codevalues/1
+Content-Type: application/json
+Request Body:
+{
+	"name": "Indian Passport",
+	"description": "Indian Passport information",
+	"position":2
+}
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 1,
+  "changes":
+  {
+    "name": "Indian Passport",
+    "description": "Indian Passport information",
+    "position": 2
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="configs_codes_codevalues_delete" name="configs_codes_codevalues_delete"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Code Value</h4>
+					<p>Deletes a code value</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/codes/{codeId}/codevalues/{codevalueId}
+					</code>
+					<code class="method-request">
+DELETE codes/1/codevalues/5
+Content-Type: application/json
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 5
+}
+					</code>
+				</div>
+			</div>
+
+
+			<!-- Audit starts here -->
+			<a id="audits" name="audits" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Audits</h3>
+					<p>Every non-read Mifos API request is audited.  A fully processed request can
+                                        not be changed or deleted.  See maker checker api for situations where an audit is not
+                                        fully processed.<br><br>
+
+                                        Permissions: To search and look at audit entries a user needs to be attached to a role
+                                        that has one of the ALL_FUNCTIONS, ALL_FUNCTIONS_READ or READ_AUDIT permissions.<br><br>
+
+                                        Data Scope: A user can only see audits that are within their data scope.  However,
+                                        'head office' users can see all audits including those that aren't office/branch related
+                                        e.g. Loan Product changes.
+					<p/>
+				</div>
+			</div>
+
+			<a id="audits_list" name="audits_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Audits</h4>
+					<p>Get a 200 list of audits that match the criteria supplied and sorted by audit id in descending order,
+                                        and are within the requestors' data scope. Also it supports pagination and sorting</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+                                            <dt>actionName</dt>
+                                            <dd>optional</dd>
+                                            <dd>Examples: CREATE, UPDATE, DISBURSE</dd>
+
+                                            <dt>entityName</dt>
+                                            <dd>optional</dd>
+                                            <dd>Examples: CLIENT, LOAN, FUND</dd>
+
+					    <dt>resourceId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the entityName</span>
+                                            </dd>
+
+					    <dt>makerId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the application user creating the entry</span>
+                                            </dd>
+
+                                            <dt>makerDateTimeFrom</dt>
+                                            <dd>optional, <span>
+                                            Get entries created on or after this</span>
+                                            </dd>
+                                            <dd>Example: 2013-04-10 08:00:00</dd>
+
+                                            <dt>makerDateTimeTo</dt>
+                                            <dd>optional, <span>
+                                            Get entries created on or before this</span>
+                                            </dd>
+                                            <dd>Example: 2013-05-10 08:00:00</dd>
+
+					    <dt>checkerId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the application user that checked (approved) the entry</span>
+                                            </dd>
+
+                                            <dt>checkerDateTimeFrom</dt>
+                                            <dd>optional, <span>
+                                            Get entries checked on or after this</span>
+                                            </dd>
+                                            <dd>Example: 2013-04-10 08:00:00</dd>
+
+                                            <dt>checkerDateTimeTo</dt>
+                                            <dd>optional, <span>
+                                            Get entries checked on or before this</span>
+                                            </dd>
+                                            <dd>Example: 2013-05-10 18:00:00</dd>
+
+					    <dt>processingResult</dt>
+                                            <dd>optional, <span>
+                                            The enum value of the processing status</span>
+                                            </dd>
+                                            <dd>values:<br>
+                                            0: Invalid<br>
+                                            1: processed<br>
+                                            2: awaiting.approval<br>
+                                            3: rejected
+                                            </dd>
+
+					    <dt>officeId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the office/branch associated with the entry (if there is one)</span>
+                                            </dd>
+
+					    <dt>groupId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the group associated with the entry (if there is one).
+                                            A group is a general idea and may be a Center, a Group, a Communal Bank or other.</span>
+                                            </dd>
+
+					    <dt>clientId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the client associated with the entry (if there is one)</span>
+                                            </dd>
+
+					    <dt>loanId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the loan associated with the entry (if there is one)</span>
+                                            </dd>
+
+					    <dt>savingsAccountId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the savings account associated with the entry (if there is one)</span>
+                                            </dd>
+
+					    <dt>includeJson</dt>
+                                            <dd>optional, <span>
+                                            Values are true, false.  Default is false.</span>
+                                            </dd>
+                        <dt>paged</dt>
+								<dd>
+									Boolean <span>optional</span>, defaults to false
+								</dd>
+								<dd>If paged is true then results will be paginated.</dd>
+
+                        <dt>offset</dt>
+								<dd>
+									Integer <span>optional</span>, defaults to 0
+								</dd>
+								<dd>Indicates from what result to start from.</dd>
+
+						<dt>limit</dt>
+								<dd>
+									Integer <span>optional</span>, defaults to 200
+								</dd>
+								<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+						<dt>orderBy</dt>
+								<dd>
+									String <span>optional</span>, one of <span>id, actionName, entityName, resourceId, subresourceId, madeOnDate, checkedOnDate, officeName, groupName, clientName, loanAccountNo, savingsAccountNo</span>
+								</dd>
+								<dd>Orders the results by the field indicated.</dd>
+
+						<dt>sortBy</dt>
+								<dd>
+									String <span>optional</span>, one of <span>ASC, DESC</span>
+								</dd>
+								<dd>Indicates what way to order results if <i>orderBy</i> is used.</dd>
+
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>audits</div><br>
+					<div class=apiClick>audits?fields=madeOnDate,maker,processingResult</div><br>
+					<div class=apiClick>audits?makerDateTimeFrom=2013-03-25 08:00:00&makerDateTimeTo=2013-04-04 18:00:00</div><br>
+					<div class=apiClick>audits?officeId=1</div><br>
+					<div class=apiClick>audits?officeId=1&includeJson=true</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/audits
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 671,
+    "actionName": "PERMISSIONS",
+    "entityName": "ROLE",
+    "resourceId": 6,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365014262000,
+    "processingResult": "processed"
+  },
+  {
+    "id": 670,
+    "actionName": "CREATE",
+    "entityName": "CLIENTNOTE",
+    "resourceId": 287,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365014204000,
+    "processingResult": "awaiting.approval",
+    "officeName": "my office",
+    "clientName": "gg ggg"
+  },
+  {
+    "id": 668,
+    "actionName": "UPDATE",
+    "entityName": "PERMISSION",
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365014186000,
+    "processingResult": "processed"
+  },
+  {
+    "id": 667,
+    "actionName": "CREATE",
+    "entityName": "CLIENTNOTE",
+    "resourceId": 286,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365014169000,
+    "processingResult": "processed",
+    "officeName": "my office name",
+    "clientName": "gg ggg"
+  },
+  {
+    "id": 666,
+    "actionName": "CREATE",
+    "entityName": "CLIENT",
+    "resourceId": 363,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365012843000,
+    "processingResult": "awaiting.approval",
+    "officeName": "my office name"
+  },
+  {
+    "id": 657,
+    "actionName": "CREATE",
+    "entityName": "CLIENT",
+    "resourceId": 362,
+    "maker": "ii",
+    "madeOnDate": 1364953928000,
+    "checker": "ii",
+    "checkedOnDate": 1365010060000,
+    "processingResult": "processed",
+    "officeName": "my office name",
+    "clientName": "gg ggg"
+  },
+  {
+    "id": 645,
+    "actionName": "CREATE",
+    "entityName": "LOAN",
+    "resourceId": 373,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1364860260000,
+    "checker": "keithwoodlock",
+    "checkedOnDate": 1364861222000,
+    "processingResult": "processed",
+    "officeName": "another office",
+    "clientName": "another client",
+    "loanAccountNo": "000000373"
+  },...
+  ]
+					</code>
+					<code class="method-declaration">
+GET https://DomainName/api/v1/audits?paged=true&limit=5
+					</code>
+					<code class="method-response">
+{
+  "totalFilteredRecords": 30,
+  "pageItems": [
+  {
+    "id": 671,
+    "actionName": "PERMISSIONS",
+    "entityName": "ROLE",
+    "resourceId": 6,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365014262000,
+    "processingResult": "processed"
+  },
+  {
+    "id": 670,
+    "actionName": "CREATE",
+    "entityName": "CLIENTNOTE",
+    "resourceId": 287,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365014204000,
+    "processingResult": "awaiting.approval",
+    "officeName": "my office",
+    "clientName": "gg ggg"
+  },
+  {
+    "id": 668,
+    "actionName": "UPDATE",
+    "entityName": "PERMISSION",
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365014186000,
+    "processingResult": "processed"
+  },
+  {
+    "id": 667,
+    "actionName": "CREATE",
+    "entityName": "CLIENTNOTE",
+    "resourceId": 286,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365014169000,
+    "processingResult": "processed",
+    "officeName": "my office name",
+    "clientName": "gg ggg"
+  },
+  {
+    "id": 666,
+    "actionName": "CREATE",
+    "entityName": "CLIENT",
+    "resourceId": 363,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365012843000,
+    "processingResult": "awaiting.approval",
+    "officeName": "my office name"
+  }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="audits_searchtemplate" name="audits_searchtemplate"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Audit Search Template</h4>
+					<p>This is a convenience resource. It can be useful when
+                                        building an Audit Search UI. "appUsers" are data scoped to
+                                        the office/branch the requestor is associated with.
+					</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>audits/searchtemplate</div>
+					<div class=apiClick>audits/searchtemplate?fields=actionNames</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/audits/searchtemplate
+					</code>
+					<code class="method-response">
+{
+  "appUsers": [
+    {
+      "id": 30,
+      "username": "user 1"
+    },
+    {
+      "id": 28,
+      "username": "user 2"
+    },
+    {
+      "id": 35,
+      "username": "user 3"
+    },...
+  ],
+  "actionNames": [
+    "CREATE",
+    "DELETE",
+    "UPDATE",
+    "ACTIVATE",
+    "ADJUST",
+    "APPROVALUNDO",
+    "APPROVE",
+    "APPROVEINPAST",
+    "BULKREASSIGN",
+    "CALCULATEINTEREST",
+    "CLOSE",
+    "CLOSEASRESCHEDULED",
+    "CREATEHISTORIC",
+    "DEPOSIT",
+    "DEREGISTER",
+    "DISBURSALUNDO",
+    "DISBURSE",
+    "DISBURSEINPAST",
+    "INTEREST",
+    "PERMISSIONS",
+    "REGISTER",
+    "REJECT",...
+  ],
+  "entityNames": [
+    "CALENDAR",
+    "CENTER",
+    "CHARGE",
+    "CLIENT",
+    "CLIENTIDENTIFIER",
+    "CLIENTIMAGE",
+    "CLIENTNOTE",
+    "CODE",
+    "CODEVALUE",
+    "COLLATERAL",
+    "CONFIGURATION",
+    "CURRENCY",
+    "DATATABLE",
+    "DEPOSITACCOUNT",
+    "DEPOSITNOTE",
+    "DEPOSITPRODUCT",
+    "DOCUMENT",
+    "FUND",
+    "GLACCOUNT",
+    "GLCLOSURE",
+    "GROUP",
+    "GROUPNOTE",...
+  ],
+  "processingResults": [
+    {
+      "id": 0,
+      "processingResult": "invalid"
+    },
+    {
+      "id": 1,
+      "processingResult": "processed"
+    },
+    {
+      "id": 2,
+      "processingResult": "awaiting.approval"
+    },
+    {
+      "id": 3,
+      "processingResult": "rejected"
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="audits_retrieve" name="audits_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve an Audit Entry</h4>
+					<p></p>
+					<p>Example Requests:</p>
+					<div class=apiClick>audits/20</div>
+					<div class=apiClick>audits/20?fields=madeOnDate,maker,processingResult</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/audits
+					</code>
+					<code class="method-response">
+{
+  "id": 20,
+  "actionName": "REPAYMENT",
+  "entityName": "LOAN",
+  "resourceId": 868,
+  "maker": "dataentry1",
+  "madeOnDate": 1358449025000,
+  "processingResult": "processed",
+  "commandAsJson": "{\"transactionDate\":\"28 September 2012\",\"transactionAmount\":\"1,967.00\",\"locale\":\"en\",\"dateFormat\":\"dd MMMM yyyy\"}",
+  "officeName": "another office",
+  "clientName": "another client",
+  "loanAccountNo": "23"
+}
+					</code>
+				</div>
+			</div>
+
+			<!--Start of Account Number Format functionality-->
+
+			<a id="accountnumberformats" name="accountnumberformats" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Account number format</h3>
+					<p>Account number preferences are used to describe custom formats for account numbers associated with Customer, Loan and Savings accounts.
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>accountType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Identifies the type of the Account for which the Account number format applies <br/>
+							Refer <a href="#accountnumberformats_template">Retrieve
+							Account number formats Template</a> for complete
+							details
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>prefixType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Identifies a custom prefix for the account number
+							<br/>
+							Refer <a href="#accountnumberformats_template">Retrieve
+							Account number formats Template</a> for complete
+							details
+							</td>
+						</tr>
+
+					</table>
+				</div>
+			</div>
+
+			<a id="accountnumberformats_list" name="accountnumberformats_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Account number formats</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>accountnumberformats</div>
+					<br>
+					<br>
+					<div class=apiClick>accountnumberformats?fields=accountType,prefixType</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/accountnumberformats
+					</code>
+					<code class="method-response">
+[
+   {
+       "id": 2,
+       "accountType":
+       {
+           "id": 1,
+           "code": "accountType.client",
+           "value": "CLIENT"
+       },
+       "prefixType":
+       {
+           "id": 101,
+           "code": "accountNumberPrefixType.clientType",
+           "value": "CLIENT_TYPE"
+       }
+   },
+   {
+       "id": 3,
+       "accountType":
+       {
+           "id": 2,
+           "code": "accountType.loan",
+           "value": "LOAN"
+       },
+       "prefixType":
+       {
+           "id": 201,
+           "code": "accountNumberPrefixType.loanProductShortName",
+           "value": "LOAN_PRODUCT_SHORT_NAME"
+       }
+   }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="accountnumberformats_template" name="accountnumberformats_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Account number format Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>accountnumberformats/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/accountnumberformats/template
+					</code>
+					<code class="method-response">
+
+
+    {
+       "accountTypeOptions":
+       [
+           {
+               "id": 1,
+               "code": "accountType.client",
+               "value": "CLIENT"
+           },
+           {
+               "id": 2,
+               "code": "accountType.loan",
+               "value": "LOAN"
+           },
+           {
+               "id": 3,
+               "code": "accountType.savings",
+               "value": "SAVINGS"
+           },
+           {
+               "id": 4,
+               "code": "accountType.center",
+               "value": "CENTER"
+           },
+           {
+               "id": 5,
+               "code": "accountType.group",
+               "value": "GROUP"
+           }
+       ],
+       "prefixTypeOptions":
+       {
+           "accountType.loan":
+           [
+               {
+                   "id": 201,
+                   "code": "accountNumberPrefixType.loanProductShortName",
+                   "value": "LOAN_PRODUCT_SHORT_NAME"
+               },
+               {
+                   "id": 1,
+                   "code": "accountNumberPrefixType.officeName",
+                   "value": "OFFICE_NAME"
+               }
+           ],
+           "accountType.client":
+           [
+               {
+                   "id": 1,
+                   "code": "accountNumberPrefixType.officeName",
+                   "value": "OFFICE_NAME"
+               },
+               {
+                   "id": 101,
+                   "code": "accountNumberPrefixType.clientType",
+                   "value": "CLIENT_TYPE"
+               }
+           ],
+           "accountType.savings":
+           [
+               {
+                   "id": 301,
+                   "code": "accountNumberPrefixType.savingsProductShortName",
+                   "value": "SAVINGS_PRODUCT_SHORT_NAME"
+               },
+               {
+                   "id": 1,
+                   "code": "accountNumberPrefixType.officeName",
+                   "value": "OFFICE_NAME"
+               }
+           ],
+           "accountType.center":
+           [
+               {
+                   "id": 1,
+                   "code": "accountNumberPrefixType.officeName",
+                   "value": "OFFICE_NAME"
+               }
+           ],
+           "accountType.group":
+           [
+               {
+                   "id": 1,
+                   "code": "accountNumberPrefixType.officeName",
+                   "value": "OFFICE_NAME"
+               },
+           ]
+       }
+    }
+
+
+					</code>
+				</div>
+			</div>
+
+			<a id="accountnumberformats_retrieve" name="accountnumberformats_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve an Account number format</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>accountnumberformats/1</div>
+					<br>
+					<br>
+					<div class=apiClick>accountnumberformats/1?template=true</div>
+					<br>
+					<br>
+					<div class=apiClick>accountnumberformats/1?fields=accountType,prefixType</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/accountnumberformats/{accountnumberformatId}
+					</code>
+					<code class="method-response">
+
+{
+   "id": 2,
+   "accountType":
+   {
+       "id": 1,
+       "code": "accountType.client",
+       "value": "CLIENT"
+   },
+   "prefixType":
+   {
+       "id": 101,
+       "code": "accountNumberPrefixType.clientType",
+       "value": "CLIENT_TYPE"
+   }
+}
+
+					</code>
+				</div>
+			</div>
+
+			<a id="accountnumberformats_create" name="accountnumberformats_create" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Create an Account number format</h4>
+                    <p>
+                        <b>Note:</b> You may associate a single Account number format for a given account type<br>
+                    </p>
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields
+								for Account number formats</div></td>
+						</tr>
+						<tr class=alt>
+							<td>accountType</td>
+						</tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/accountnumberformats
+                    </code>
+                    <code class="method-request">
+POST /accountnumberformats
+Content-Type: application/json
+Request Body:
+{
+    accountType :1,
+    prefixType: 101
+}
+                    </code>
+                    <code class="method-response">
+{
+   "resourceId": 4
+}
+
+                    </code>
+                    <code class="method-request">
+POST /accountnumberformats
+Content-Type: application/json
+Request Body:
+{
+    accountType :2,
+    prefixType: 201
+}
+                    </code>
+                    <code class="method-response">
+{
+   "resourceId": 5
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="accountnumberformats_update" name="accountnumberformats_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update an Account number format</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/accountnumberformats/{accountnumberformatId}
+					</code>
+					<code class="method-request">
+PUT accountnumberformats/2
+Content-Type: application/json
+Request Body:
+{
+  prefixType:1
+}
+					</code>
+					<code class="method-response">
+{
+   "resourceId": 2,
+   "changes":
+   {
+       "prefixType": "OFFICE_NAME"
+   }
+}
+
+					</code>
+				</div>
+			</div>
+
+			<a id="accountnumberformats_delete" name="accountnumberformats_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete an Account number format</h4>
+					<p>
+						<b>Note:</b> Account numbers created while this format was active would remain unchanged.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/accountnumberformats/{accountnumberformatId}
+					</code>
+					<code class="method-request">
+DELETE accountnumberformats/2
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+   "resourceId": 2
+}
+
+					</code>
+				</div>
+			</div>
+
+			<!-- Maker Checker starts here -->
+			<a id="makercheckers" name="makercheckers" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Maker Checker (or 4-eye) functionality</h3>
+                                        <p>Apache Fineract Maker Checker functionality allows an MFI to define transactions
+                                        as having a maker and a checker phase.  One user enters, deletes or changes data.
+                                        Then, another user that has "Checker" rights for that transaction, can inspect
+                                        and approve the data.
+                                        </p>
+
+                                        <p>By default, Maker Checker functionality is disabled.
+                                        See <a href="#configs_globalconfig_update">Update Global Configuration</a> to enable/disable
+                                        Maker Checker functionality at a global level.
+                                        </p>
+
+                                        <p>
+                                        Additionally, Maker Checker functionality for each transaction (permission) is disabled by default.
+                                        see <a href="#permissions_update">Enable/Disable Permissions for Maker Checker</a> to enable/disable
+                                        Maker Checker functionality at a transaction (permission) level.
+                                        </p>
+
+                                        <p>
+                                        Finally, to give checking rights to a user (via a role associated with the user) see
+                                        <a href="#rolespermissions_update">Update a Role's Permissions</a>
+                                        </p>
+                                        <p>For example, <br>
+<code>
+{
+ "permissions":{
+    "CREATE_GUARANTOR_CHECKER":true,
+    "CREATE_CLIENT_CHECKER":true
+ }
+}
+</code>
+                                        <br>
+                                        will give checking rights for CREATE_GUARANTOR and CREATE_CLIENT</p>
+                                        <p>Alternatively, the special permissions ALL_FUNCTIONS or CHECKER_SUPER_USER will give blanket
+                                        checking rights.
+                                        </p>
+                                        <p>
+                                        When a user "makes" an entry that is enabled for Maker Checker, it is audited and the audit status
+                                        is set to a value of 2 (awaiting.approval).
+                                        </p>
+
+                                        Checkers can only Check and approve entries that they have been permitted to check.<br><br>
+
+                                        Checkers can only Check and approve entries that are within their data scope.  However,
+                                        'head office' Checkers can Check and approve all entries
+                                        including those that aren't office/branch related (as long as they have the Checker permissions)
+                                        e.g. Loan Product changes.
+					<p/>
+				</div>
+			</div>
+
+			<a id="makercheckers_list" name="makercheckers_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Maker Checker Entries</h4>
+					<p>Get a list of entries that can be checked by the requestor that match the criteria supplied.</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+                                            <dt>actionName</dt>
+                                            <dd>optional</dd>
+                                            <dd>Examples: CREATE, UPDATE, DISBURSE</dd>
+
+                                            <dt>entityName</dt>
+                                            <dd>optional</dd>
+                                            <dd>Examples: CLIENT, LOAN, FUND</dd>
+
+					    <dt>resourceId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the entityName</span>
+                                            </dd>
+
+					    <dt>makerId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the application user creating the entry</span>
+                                            </dd>
+
+                                            <dt>makerDateTimeFrom</dt>
+                                            <dd>optional, <span>
+                                            Get entries created on or after this</span>
+                                            </dd>
+                                            <dd>Example: 2013-04-10 08:00:00</dd>
+
+                                            <dt>makerDateTimeTo</dt>
+                                            <dd>optional, <span>
+                                            Get entries created on or before this</span>
+                                            </dd>
+                                            <dd>Example: 2013-05-10 08:00:00</dd>
+
+					    <dt>officeId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the office/branch associated with the entry (if there is one)</span>
+                                            </dd>
+
+					    <dt>groupId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the group associated with the entry (if there is one).
+                                            A group is a general idea and may be a Center, a Group, a Communal Bank or other.</span>
+                                            </dd>
+
+					    <dt>clientId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the client associated with the entry (if there is one)</span>
+                                            </dd>
+
+					    <dt>loanId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the loan associated with the entry (if there is one)</span>
+                                            </dd>
+
+					    <dt>savingsAccountId</dt>
+                                            <dd>optional, <span>
+                                            The id value of the savings account associated with the entry (if there is one)</span>
+                                            </dd>
+
+					    <dt>includeJson</dt>
+                                            <dd>optional, <span>
+                                            Values are true, false.  Default is false.</span>
+                                            </dd>
+
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>makercheckers</div><br>
+					<div class=apiClick>makercheckers?fields=madeOnDate,maker,processingResult</div><br>
+					<div class=apiClick>makercheckers?makerDateTimeFrom=2013-03-25 08:00:00&makerDateTimeTo=2013-04-04 18:00:00</div><br>
+					<div class=apiClick>makercheckers?officeId=1</div><br>
+					<div class=apiClick>makercheckers?officeId=1&includeJson=true</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/makercheckers
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 654,
+    "actionName": "CREATE",
+    "entityName": "LOANPRODUCT",
+    "resourceId": 15,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1364924512000,
+    "processingResult": "awaiting.approval"
+  },
+  {
+    "id": 666,
+    "actionName": "CREATE",
+    "entityName": "CLIENT",
+    "resourceId": 363,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365012843000,
+    "processingResult": "awaiting.approval",
+    "officeName": "my office name"
+  },
+  {
+    "id": 670,
+    "actionName": "CREATE",
+    "entityName": "CLIENTNOTE",
+    "resourceId": 287,
+    "maker": "keithwoodlock",
+    "madeOnDate": 1365014204000,
+    "processingResult": "awaiting.approval",
+    "officeName": "my office name",
+    "clientName": "gg ggg"
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="makercheckers_searchtemplate" name="makercheckers_searchtemplate"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Maker Checker Search Template</h4>
+					<p>This is a convenience resource. It can be useful when
+                                        building a Checker Inbox UI. "appUsers" are data scoped to
+                                        the office/branch the requestor is associated with.
+                                        "actionNames" and "entityNames" returned are those that the
+                                        requestor has Checker approval permissions for.
+					</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>makercheckers/searchtemplate</div>
+					<div class=apiClick>makercheckers/searchtemplate?fields=entityNames</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/audits/searchtemplate
+					</code>
+					<code class="method-response">
+{
+  "appUsers": [
+    {
+      "id": 30,
+      "username": "user 1"
+    },
+    {
+      "id": 28,
+      "username": "user 2"
+    },
+    {
+      "id": 35,
+      "username": "user 3"
+    },...
+  ],
+  "actionNames": [
+    "CREATE",
+    "DELETE",
+    "UPDATE",
+    "ACTIVATE",
+    "ADJUST",
+    "APPROVALUNDO",
+    "APPROVE",...
+  ],
+  "entityNames": [
+    "CALENDAR",
+    "CENTER",
+    "CHARGE",
+    "CLIENT",
+    "CLIENTIDENTIFIER",...
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="makercheckers_approve" name="makercheckers_approve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Approve Maker Checker Entry</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/makercheckers/{auditId}?command=approve
+					</code>
+					<code class="method-request">
+POST makercheckers/1?command=approve
+Content-Type: application/json
+					</code>
+					<code class="method-response">{ "auditId": 1 } </code>
+				</div>
+			</div>
+			<a id="makercheckers_reject" name="makercheckers_reject" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Reject Maker Checker Entry</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/makercheckers/{auditId}?command=reject
+					</code>
+					<code class="method-request">
+POST makercheckers/1?command=reject
+Content-Type: application/json
+					</code>
+					<code class="method-response">{ "auditId": 1 } </code>
+				</div>
+			</div>
+
+			<a id="makercheckers_delete" name="makercheckers_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete Maker Checker Entry</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/makercheckers/{auditId}
+					</code>
+					<code class="method-request">
+DELETE makercheckers/1
+Content-Type: application/json
+					</code>
+					<code class="method-response">{ "auditId": 1 } </code>
+				</div>
+			</div>
+
+			<!-- Maker Checker starts here -->
+			<a id="scheduler_jobs" name="scheduler_jobs" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>MIFOSX-BATCH JOBS</h3>
+						<p>Batch jobs (also known as cron jobs on Unix-based systems) are a series of back-end jobs executed on a computer at a particular time defined in job's cron expression.
+						</p>
+
+						<p>At any point, you can view the list of batch jobs scheduled to run along with other details specific to each job. Manually you can execute the jobs at any point of time.
+						</p>
+						<p>The scheduler status can be either "Active" or "Standby". If the scheduler status is Active, it indicates that all batch jobs are running/ will run as per the specified schedule.If the scheduler status is Standby, it will ensure all scheduled batch runs are suspended.
+						</p>
+				</div>
+			</div>
+
+			<a id="scheduler_jobs_list" name="scheduler_jobs_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Scheduler Jobs</h4>
+					<p>Returns the list of jobs.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>jobs</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/jobs
+					</code>
+					<code class="method-response">
+[
+  {
+    "jobId": 1,
+    "displayName": "Update loan Summary",
+    "cronExpression": "0 0 22 1/1 * ? *",
+    "active": false,
+    "currentlyRunning": false,
+    "lastRunHistory": {
+      "version": 17,
+      "jobRunStartTime": "Jul 26, 2013 1:38:26 PM",
+      "jobRunEndTime": "Jul 26, 2013 1:38:26 PM",
+      "status": "success",
+      "triggerType": "application"
+    }
+  },
+  {
+    "jobId": 2,
+    "displayName": "Update Loan Arrears Ageing",
+    "nextRunTime": "Jul 27, 2013 12:01:00 AM",
+    "cronExpression": "0 1 0 1/1 * ? *",
+    "active": true,
+    "currentlyRunning": false,
+    "lastRunHistory": {
+      "version": 18,
+      "jobRunStartTime": "Jul 26, 2013 4:05:32 PM",
+      "jobRunEndTime": "Jul 26, 2013 4:05:32 PM",
+      "status": "success",
+      "triggerType": "application"
+    }
+  },
+  {
+    "jobId": 3,
+    "displayName": "Update Loan Paid In Advance",
+    "nextRunTime": "Jul 27, 2013 12:05:00 AM",
+    "cronExpression": "0 5 0 1/1 * ? *",
+    "active": true,
+    "currentlyRunning": false,
+    "lastRunHistory": {
+      "version": 16,
+      "jobRunStartTime": "Jul 26, 2013 4:15:47 PM",
+      "jobRunEndTime": "Jul 26, 2013 4:15:47 PM",
+      "status": "success",
+      "triggerType": "application"
+    }
+  },
+  {
+    "jobId": 4,
+    "displayName": "Apply Annual Fee For Savings",
+    "nextRunTime": "Jul 26, 2013 10:20:00 PM",
+    "cronExpression": "0 20 22 1/1 * ? *",
+    "active": true,
+    "currentlyRunning": false,
+    "lastRunHistory": {
+      "version": 11,
+      "jobRunStartTime": "Jul 26, 2013 12:00:37 PM",
+      "jobRunEndTime": "Jul 26, 2013 12:00:38 PM",
+       "status": "success
+      "triggerType": "application"
+    }
+  },
+  {
+    "jobId": 5,
+    "displayName": "Apply Holidays To Loans",
+    "nextRunTime": "Jul 27, 2013 12:00:00 PM",
+    "cronExpression": "0 0 12 * * ?",
+    "active": true,
+    "currentlyRunning": false,
+    "lastRunHistory": {
+      "version": 16,
+      "jobRunStartTime": "Jul 26, 2013 4:31:54 PM",
+      "jobRunEndTime": "Jul 26, 2013 4:31:55 PM",
+      "status": "success",
+      "triggerType": "application"
+    }
+  }
+]
+
+					</code>
+				</div>
+			</div>
+
+			<a id="retrieve_scheduler_job" name="retrieve_scheduler_job"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Job</h4>
+					<p>Returns the details of a Job.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>jobs/5</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/jobs/{jobId}
+					</code>
+					<code class="method-response">
+https://localhost:8443/fineract-provider/api/v1/jobs/5
+{
+  "jobId": 5,
+  "displayName": "Apply Holidays To Loans",
+  "nextRunTime": "Jul 27, 2013 12:00:00 PM",
+  "cronExpression": "0 0 12 * * ?",
+  "active": true,
+  "currentlyRunning": false,
+  "lastRunHistory": {
+    "version": 16,
+    "jobRunStartTime": "Jul 26, 2013 4:31:54 PM",
+    "jobRunEndTime": "Jul 26, 2013 4:31:55 PM",
+    "status": "success",
+    "triggerType": "application"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="update_scheduler_job" name="update_scheduler_job"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Job</h4>
+					<p>Updates the details of a job.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/jobs/{jobId}
+					</code>
+					<code class="method-request">
+PUT jobs/1
+Content-Type: application/json
+{
+  "displayName":"Update loan Summary",
+  "cronExpression":"0 0 22 1/1 * ? *",
+  "active":"true"
+}					</code>
+				</div>
+			</div>
+
+			<a id="run_job" name="run_job"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Run a Job</h4>
+					<p>Manually Execute Specific Job.</p>
+				</div>
+					<div class="method-example">
+						<code class="method-declaration">
+POST https://DomainName/api/v1/jobs/{jobId}?command=executeJob
+						</code>
+						<code class="method-request">
+POST jobs/1?command=executeJob
+						</code>
+					</div>
+				</div>
+
+			<a id="retrieve_job_runhistory" name="retrieve_job_runhistory"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Job Run History</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>jobs/5/runhistory?offset=0&limit=200</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/jobs/{jobid}/runhistory?offset=0&limit=200
+					</code>
+					<code class="method-response">
+{
+  "totalFilteredRecords": 8,
+  "pageItems": [
+    {
+      "version": 1,
+      "jobRunStartTime": "Jul 16, 2013 12:00:00 PM",
+      "jobRunEndTime": "Jul 16, 2013 12:00:00 PM",
+      "status": "success",
+      "triggerType": "cron"
+    },
+    {
+      "version": 2,
+      "jobRunStartTime": "Jul 17, 2013 12:00:00 PM",
+      "jobRunEndTime": "Jul 17, 2013 12:00:00 PM",
+      "status": "success",
+      "triggerType": "cron"
+    },
+    {
+      "version": 3,
+      "jobRunStartTime": "Jul 18, 2013 12:00:00 PM",
+      "jobRunEndTime": "Jul 18, 2013 12:00:01 PM",
+      "status": "success",
+      "triggerType": "cron"
+    },
+    {
+      "version": 4,
+      "jobRunStartTime": "Jul 19, 2013 12:00:00 PM",
+      "jobRunEndTime": "Jul 19, 2013 12:00:00 PM",
+      "status": "success",
+      "triggerType": "cron"
+    },
+    {
+      "version": 5,
+      "jobRunStartTime": "Jul 20, 2013 11:16:07 AM",
+      "jobRunEndTime": "Jul 20, 2013 11:16:07 AM",
+      "status": "success",
+      "triggerType": "application"
+    },
+    {
+      "version": 6,
+      "jobRunStartTime": "Jul 20, 2013 11:35:05 AM",
+      "jobRunEndTime": "Jul 20, 2013 11:35:05 AM",
+      "status": "success",
+      "triggerType": "application"
+    },
+    {
+      "version": 7,
+      "jobRunStartTime": "Jul 20, 2013 12:00:00 PM",
+      "jobRunEndTime": "Jul 20, 2013 12:00:00 PM",
+      "status": "success",
+      "triggerType": "cron"
+    },
+    {
+      "version": 8,
+      "jobRunStartTime": "Jul 22, 2013 12:00:00 PM",
+      "jobRunEndTime": "Jul 22, 2013 12:00:00 PM",
+      "status": "success",
+      "triggerType": "cron"
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="retrieve_scheduler_status" name="retrieve_scheduler_status"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Scheduler Status</h4>
+					<p>Returns the scheduler status.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>scheduler</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/scheduler
+					</code>
+					<code class="method-response">
+https://localhost:8443/fineract-provider/api/v1/scheduler
+{
+  "active": true
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="activate_scheduler" name="activate_scheduler"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Activate Scheduler Jobs</h4>
+					<p>Activates the scheduler job service.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/scheduler?command=start
+					</code>
+					<code class="method-request">
+POST scheduler?command=start
+					</code>
+				</div>
+			</div>
+
+			<a id="suspend_scheduler" name="suspend_scheduler" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Suspend Scheduler Jobs</h4>
+					<p>Suspends the scheduler job service.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/scheduler?command=stop
+					</code>
+					<code class="method-request">
+POST scheduler?command=stop
+					</code>
+				</div>
+			</div>
+
+			<a id="external_services" name="external_services" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>External Services</h3>
+					<p>External Services Configuration related to set of supported configurations for third party services like Amazon S3 and SMTP:</p>
+					<ol>
+						<li><b>S3 (Amazon S3):</b></li>
+						<li><b>s3_access_key</b> - </li>
+						<li><b>s3_bucket_name</b> - </li>
+						<li><b>s3_secret_key</b> - </li>
+						<br><br>
+						<li><b>SMTP (Email Service):</b></li>
+						<li><b>username</b> - </li>
+						<li><b>password</b> - </li>
+						<li><b>host</b> - </li>
+						<li><b>port</b> - </li>
+						<li><b>useTLS</b> - </li>
+
+					</ol>
+				</div>
+			</div>
+
+			<a id="configs_externalService_retrieve" name="configs_externalService_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve External Services Configuration</h4>
+					<p>Returns a external Service configurations based on the Service Name.</p>
+					<p>Service Names supported are S3 and SMTP.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>externalservice/SMTP</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/externalservice/{serviceName}
+					</code>
+					<code class="method-response">
+
+
+    [
+        {
+            "name": "username",
+            "value": "test@mifos.com"
+        },
+        {
+            "name": "password",
+            "value": "XXXX"
+        },
+        {
+            "name": "host",
+            "value": "smtp.gmail.com"
+        },
+        {
+            "name": "port",
+            "value": "25"
+        },
+        {
+            "name": "useTLS",
+            "value": "true"
+        }
+    ]
+
+
+					</code>
+				</div>
+			</div>
+
+			<a id="config_externalService_update" name="config_externalService_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update External Service</h4>
+					<p>Updates the external Service Configuration for a Service Name.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/externalservice/{serviceName}
+					</code>
+					<code class="method-request">
+PUT externalservice/S3
+Content-Type: application/json
+{
+	"username" : "test@mifos.org"
+	"password" : "XXXX"
+}
+				</code>
+				</div>
+			</div>
+
+			<!-- Fund starts here -->
+			<a id="funds" name="funds" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Funds</h3>
+                                </div>
+                        </div>
+
+			<a id="funds_create" name="funds_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Fund</h4>
+					<p>Creates a fund.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/funds
+					</code>
+					<code class="method-request">
+POST funds
+Content-Type: application/json
+Request Body:
+{
+	"name": "EU Agri Fund"
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="funds_retrieve" name="funds_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Funds</h4>
+					<p>Returns the list of funds.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>funds</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/funds
+					</code>
+					<code class="method-response">
+[
+    {
+        "id": 1,
+        "name": "EU Agri Fund"
+    }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="fund_retrieve" name="fund_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Fund</h4>
+					<p>Returns the details of a Fund.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>funds/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/funds/{fundId}
+					</code>
+					<code class="method-response">
+{
+	"id": 1,
+	"name": "EU Agri Fund"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="fund_update" name="fund_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Fund</h4>
+					<p>Updates the details of a fund.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/funds/{fundId}
+					</code>
+					<code class="method-request">
+PUT funds/1
+Content-Type: application/json
+Request Body:
+{
+	"name": "EU Agri Fund (2010-2020)"
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1,
+    "changes": {
+        "name": "EU Agri Fund (2010-2020)"
+    }
+}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="staff" name="staff" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Staff</h3>
+					<p>Allows you to model staff members. At present the key role
+						of significance is whether this staff member is a loan officer or
+						not.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>firstname</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>First Name of the new Employee.</td>
+						</tr>
+						<tr class=alt>
+							<td>lastname</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Last Name of the new Employee.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>externalId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>ID to put an external reference for an Employee.</td>
+						</tr>
+						<tr class=alt>
+							<td>mobileNo</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Mobile number of an Employee.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>isLoanOfficer</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Indicates whether the employee account is to be created as <b>Loan Officer</b>. If isLoanOfficer=true, then the employee is <b>Loan Officer</b>. If isLoanOfficer=false, then the employee is
+								<b>not Loan Officer.</b>
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>isActive</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Indicates whether the employee account is to be created as <b>Active</b>. If isActive=true, then employee is active. If isActive=false, then employee is inactive.</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+
+			<a id="staff_create" name="staff_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a staff member</h4>
+					<p>Creates a staff member.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>officeId, firstname, lastname</td>
+						</tr>
+					</table>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>isLoanOfficer, isActive</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/staff
+					</code>
+					<code class="method-request">
+POST staff
+Content-Type: application/json
+Request Body:
+{
+    "officeId": 1,
+    "firstname": "John",
+    "lastname": "Doe",
+    "isLoanOfficer": "true",
+    "externalId": "17H",
+    "mobileNo": "+353851239876",
+    "isActive": "true",
+	"joiningDate": "01 January 2009",
+	"locale":"en",
+	"dateFormat":"dd MMMM yyyy"
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="staff_list" name="staff_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Staff</h4>
+					<p>Returns the list of staff members.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>staff</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/staff
+					</code>
+					<code class="method-response">
+[
+    {
+        "id": 1,
+        "firstname": "John",
+        "lastname": "Doe",
+        "displayName": "Doe, John",
+        "officeId": 1,
+        "officeName": "Head Office",
+        "isLoanOfficer": true,
+	"externalId": "17H",
+	"isActive": "true",
+	    "joiningDate":[2009,8,1]
+    }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="staff_retrieve" name="staff_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Staff Member</h4>
+					<p>Returns the details of a Staff Member.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>staff/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">GET https://DomainName/api/v1/staff/{staffId}</code>
+					<code class="method-response">
+{
+    "id": 1,
+    "firstname": "John",
+    "lastname": "Doe",
+    "displayName": "Doe, John",
+    "officeId": 1,
+    "officeName": "Head Office",
+    "isLoanOfficer": true,
+    "externalId": "17H",
+    "isActive": "true",
+	"joiningDate":[2009,8,1]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="staff_retrieve" name="staff_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Staff by status</h4>
+					<p>Returns the details of a Staff based on status.</p>
+					<p>By <b>default</b> it Returns all the </b>ACTIVE</b> Staff.</p>
+					<p>If status=INACTIVE, then it returns all <b>INACTIVE</b> Staff.</p>
+					<p>and for status=ALL, it Returns both <b>ACTIVE</b> and <b>INACTIVE</b> Staff.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>staff?status=active</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">GET https://DomainName/api/v1/staff?status={ACTIVE|INACTIVE|ALL}</code>
+					<code class="method-response">
+{
+    "id": 1,
+    "firstname": "John",
+    "lastname": "Doe",
+    "displayName": "Doe, John",
+    "officeId": 1,
+    "officeName": "Head Office",
+    "isLoanOfficer": true,
+    "externalId": "17H",
+    "isActive": "true",
+	"joiningDate":[2009,8,1]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="staff_update" name="staff_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Staff Member</h4>
+					<p>Updates the details of a staff member.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/staff/{staffId}
+					</code>
+					<code class="method-request">
+PUT staff/1
+Content-Type: application/json
+Request Body:
+{
+	"isLoanOfficer": "false",
+	"externalId": "17Hbb"
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 1,
+    "changes": {
+        "isLoanOfficer": false,
+	"externalId": "17Hbb"
+    }
+}
+					</code>
+				</div>
+			</div>
+
+
+<!--Charges section starts here-->
+<a id="charges" name="charges" class="old-syle-anchor">&nbsp;</a>
+<div class="method-section">
+	<div class="method-description">
+		<h3>Charges</h3>
+		<p>
+			Its typical for MFIs to add extra costs for their financial
+			products. These are typically <b>Fees</b> or <b>Penalties</b>.
+		</p>
+		<p>A <strong><i>Charge</i></strong> on fineract platform is what we use to model both <b>Fees</b> and <b>Penalties</b>.</p>
+		<p>At present we support defining charges for use with Client accounts and both loan and saving products.</p>
+		<table class=matrixHeading>
+			<tr class="matrixHeadingBG">
+				<td><div class="fineractHeading2">Field Descriptions</div></td>
+			</tr>
+			<tr class=alt>
+				<td>name</td>
+			</tr>
+			<tr>
+				<td class=fielddesc>Name associated with this charge.</td>
+			</tr>
+			<tr class=alt>
+				<td>chargeAppliesTo</td>
+			</tr>
+			<tr>
+				<td class=fielddesc>Enumeration for indicating whether charge is to be applicable for loans, savings or clients.<br/>
+					1=Loans,
+					2=Savings,
+					3=Client
+				<p>Once a charge definiton has been created, this attribute cannot be changed at any point.</p>
+				</td>
+			</tr>
+			<tr class=alt>
+				<td>active</td>
+			</tr>
+			<tr>
+				<td class=fielddesc>Boolean flag determines if the charge is currently active.</td>
+			</tr>
+			<tr class=alt>
+				<td>penalty</td>
+			</tr>
+			<tr>
+				<td class=fielddesc>Boolean flag determines if the charge is a <b>penalty</b>. If false the charge is considered a <b>Fee</b></td>
+			</tr>
+			<tr class=alt>
+				<td>currencyCode</td>
+			</tr>
+			<tr>
+				<td class=fielddesc>A three letter ISO code of currency.</td>
+			</tr>
+			<tr class=alt>
+				<td>chargeCalculationType</td>
+			</tr>
+			<tr>
+				<td class=fielddesc>Enumeration for indicating whether charge amount is <b>flat</b> or <b>percentage</b> based:<br/>
+					1=Flat,
+					2=% of Amount
+				<p>Used in combination with <i>amount</i> parameter e.g 1 <b>% of Amount</b> or 3.50 <b>Flat</b></p>
+				<p>For loans, % of Amount refers to the loan principal disbursed.</p>
+				<p>For savings, % of Amount can be used with 'withrawal fees' and refers to the amount withdrawn.</p>
+				</td>
+			</tr>
+			<tr class=alt>
+				<td>amount</td>
+			</tr>
+			<tr>
+				<td class=fielddesc>The charge amount. Used in combination with <i>chargeCalculationType</i> parameter. e.g <b>1</b> % of Amount or <b>3.50</b> Flat</td>
+			</tr>
+			<tr class=alt>
+				<td>minCap</td>
+            </tr>
+            <tr>
+				<td class=fielddesc><b>Optional</b>: Can be used when a '% of Amount' value is used for <i>chargeCalculationType</i>.
+				<p>Used to enforce a minimum charge amount in cases where the calculated amount is less than the minimum amount provided.</p>
+				<p>e.g. A 2% fee with a minimum cap of 500:</br>
+				In case of a 5000 loan, the 2% is 100, and therefore automatically 500 is used as the value of the charge.
+				</p>
+				</td>
+            </tr>
+            <tr class=alt>
+				<td>maxCap</td>
+            </tr>
+            <tr>
+            	<td class=fielddesc><b>Optional</b>: Can be used when a '% of Amount' value is used for <i>chargeCalculationType</i>.
+				<p>Used to enforce a maximum charge amount in cases where the calculated amount is greater than the maximum amount provided.</p>
+				<p>e.g. A 2% fee, with a maximum of 2000.:</br>
+				In case of a 150000 loan, the 2% adds up to 3000 charge, and therefore the maximum cap of 2000 will be used.
+				</p>
+				</td>
+            </tr>
+			<tr class=alt>
+				<td>chargeTimeType</td>
+			</tr>
+			<tr>
+				<td class=fielddesc>An enumeration indicating the time at which the charge becomes due:<br/>
+				<p>
+				1 = Disbursement : Applicable for loans and deducted at the time of loan disbursement.<br/>
+				2 = Specified Due Date : Adhoc charge can be applied for loans and savings on a specified due date.<br/>
+				3 = Savings Activation : This charge is applicable for savings account and charged at the time of account activation. This charge will be autodeducted once the account is activated with sufficient opening balance.<br/>
+				5 = Withdrawal fee : Charge applied to every withdrawal of savings account.<br/>
+				6 = Annual Fee : Recurring charge applied annually for savings on a specified Month and day. see <i>feeOnMonthDay</i><br/>
+				7 = Monthly Fee : Recurring charge applied on regular interval of months for savings. see <i>feeOnMonthDay</i> and <i>feeInterval</i>
+				</p>
+				</td>
+			</tr>
+			<tr class=alt>
+				<td>chargePaymentMode</td>
+			</tr>
+			<tr>
+				<td class=fielddesc><b>Applicable to loan charges only:</b> Enumeration for indicating whether charge is to be paid through an <i>Account Transfer</i> from savings or through <i>Regular</i> payment mode.<br/>
+					0=Regular,
+					1=Account Transfer
+				</td>
+			</tr>
+
+			<tr class=alt>
+				<td>feeOnMonthDay</td>
+			</tr>
+			<tr>
+            	<td class=fielddesc>Used along with <i>monthDayFormat</i> to indicate the recurring charge due date starting on a given day of the month and follow specified <i>feeInterval</i>. e.g. fee due date starting on <b>10 May</b> with <i>feeInterval</i> of two (2) months then a recurring fee on 10th of every two months is applied to savings account. <i>monthDayFormat</i> indicates day and month formatting used e.g. "dd MMM" for 10 May <br/>
+            	This field is mandatory if <i>chargeTimeType</i> is of type <b>Monthly Fee</b>
+            	</td>
+            </tr>
+            <tr class=alt>
+				<td>feeInterval</td>
+			</tr>
+			<tr>
+            	<td class=fielddesc>Fee to be applied on a specified interval.<br/>
+            		This field is mandatory if <i>chargeTimeType</i> is of type <b>Monthly Fee</b> or <i>feeFrequency<i> is selcted
+            	</td>
+            </tr>
+            <tr class=alt>
+				<td>feeFrequency</td>
+			</tr>
+            <tr>
+            	<td class=fielddesc>This field is Optional Used to indicate the recurring(days,weeks,months and Years) charge due date starting on system calculated date for overdue penalty.<br/></td>
+            </tr>
+            <tr class=alt>
+				<td>incomeAccount</td>
+			</tr>
+            <tr>
+            	<td class=fielddesc>This field is Optional and can be used only when a charge is applied to a Client. It indicates the Income or Liability account which gets
+            	credited when a payment is made on this charge<br/></td>
+            </tr>
+		</table>
+	</div>
+</div>
+
+	<a id="charges_template" name="charges_template" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+		<div class="method-description">
+			<h4>Retrieve Charge Template</h4>
+			<p>This is a convenience resource. It can be useful when
+				building maintenance user interface screens for client
+				applications. The template data returned consists of any or all
+				of:
+			<ul>
+				<li class=normalli>Field Defaults</li>
+				<li class=normalli>Allowed Value Lists</li>
+			</ul>
+			</p>
+			<p>Example Request:</p>
+			<div class=apiClick>charges/template</div>
+		</div>
+		<div class="method-example">
+			<code class="method-declaration">
+GET https://DomainName/api/v1/charges/template
+			</code>
+			<code class="method-response">
+{
+  "active": false,
+  "penalty": false,
+  "currencyOptions": [
+    {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    }
+  ],
+  "chargeCalculationTypeOptions": [
+    {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    {
+      "id": 2,
+      "code": "chargeCalculationType.percent.of.amount",
+      "value": "% Amount"
+    },
+    {
+      "id": 3,
+      "code": "chargeCalculationType.percent.of.amount.and.interest",
+      "value": "% Loan Amount + Interest"
+    },
+    {
+      "id": 4,
+      "code": "chargeCalculationType.percent.of.interest",
+      "value": "% Interest"
+    }
+  ],
+  "chargeAppliesToOptions": [
+    {
+      "id": 1,
+      "code": "chargeAppliesTo.loan",
+      "value": "Loan"
+    },
+    {
+      "id": 2,
+      "code": "chargeAppliesTo.savings",
+      "value": "Savings"
+    }
+  ],
+  "chargeTimeTypeOptions": [
+    {
+      "id": 1,
+      "code": "chargeTimeType.disbursement",
+      "value": "Disbursement"
+    },
+    {
+      "id": 2,
+      "code": "chargeTimeType.specifiedDueDate",
+      "value": "Specified due date"
+    },
+    {
+      "id": 3,
+      "code": "chargeTimeType.savingsActivation",
+      "value": "Savings Activation"
+    },
+    {
+      "id": 5,
+      "code": "chargeTimeType.withdrawalFee",
+      "value": "Withdrawal Fee"
+    },
+    {
+      "id": 6,
+      "code": "chargeTimeType.annualFee",
+      "value": "Annual Fee"
+    },
+    {
+      "id": 7,
+      "code": "chargeTimeType.monthlyFee",
+      "value": "Monthly Fee"
+    },
+    {
+      "id": 8,
+      "code": "chargeTimeType.instalmentFee",
+      "value": "Instalment Fee"
+    },
+    {
+      "id": 9,
+      "code": "chargeTimeType.overdueInstallment",
+      "value": "overdue fees"
+    },
+    {
+      "id": 10,
+      "code": "chargeTimeType.overdraftFee",
+      "value": "Overdraft Fee"
+    }
+  ],
+  "chargePaymetModeOptions": [
+    {
+      "id": 0,
+      "code": "chargepaymentmode.regular",
+      "value": "Regular"
+    },
+    {
+      "id": 1,
+      "code": "chargepaymentmode.accounttransfer",
+      "value": "Account transfer"
+    }
+  ],
+  "loanChargeCalculationTypeOptions": [
+    {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    {
+      "id": 2,
+      "code": "chargeCalculationType.percent.of.amount",
+      "value": "% Amount"
+    },
+    {
+      "id": 3,
+      "code": "chargeCalculationType.percent.of.amount.and.interest",
+      "value": "% Loan Amount + Interest"
+    },
+    {
+      "id": 4,
+      "code": "chargeCalculationType.percent.of.interest",
+      "value": "% Interest"
+    }
+  ],
+  "loanChargeTimeTypeOptions": [
+    {
+      "id": 1,
+      "code": "chargeTimeType.disbursement",
+      "value": "Disbursement"
+    },
+    {
+      "id": 2,
+      "code": "chargeTimeType.specifiedDueDate",
+      "value": "Specified due date"
+    },
+    {
+      "id": 8,
+      "code": "chargeTimeType.instalmentFee",
+      "value": "Instalment Fee"
+    },
+    {
+      "id": 9,
+      "code": "chargeTimeType.overdueInstallment",
+      "value": "overdue fees"
+    }
+  ],
+  "savingsChargeCalculationTypeOptions": [
+    {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    {
+      "id": 2,
+      "code": "chargeCalculationType.percent.of.amount",
+      "value": "% Amount"
+    }
+  ],
+  "savingsChargeTimeTypeOptions": [
+    {
+      "id": 2,
+      "code": "chargeTimeType.specifiedDueDate",
+      "value": "Specified due date"
+    },
+    {
+      "id": 3,
+      "code": "chargeTimeType.savingsActivation",
+      "value": "Savings Activation"
+    },
+    {
+      "id": 5,
+      "code": "chargeTimeType.withdrawalFee",
+      "value": "Withdrawal Fee"
+    },
+    {
+      "id": 6,
+      "code": "chargeTimeType.annualFee",
+      "value": "Annual Fee"
+    },
+    {
+      "id": 7,
+      "code": "chargeTimeType.monthlyFee",
+      "value": "Monthly Fee"
+    },
+    {
+      "id": 10,
+      "code": "chargeTimeType.overdraftFee",
+      "value": "Overdraft Fee"
+    }
+  ],
+  "feeFrequencyOptions": [
+    {
+      "id": 0,
+      "code": "loanTermFrequency.periodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "loanTermFrequency.periodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "loanTermFrequency.periodFrequencyType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "loanTermFrequency.periodFrequencyType.years",
+      "value": "Years"
+    }
+  ]
+}
+			</code>
+		</div>
+	</div>
+
+	<a id="charges_create" name="charges_create" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+		<div class="method-description">
+			<h4>Create/Define a Charge</h4>
+			<p>Define a new charge that can later be associated with loans and savings through their respective product definitions or directly on each account instance.</p>
+		</div>
+		<div class="method-example">
+			<code class="method-declaration">
+POST https://DomainName/api/v1/charges
+			</code>
+			<code class="method-request">
+POST charges
+Content-Type: application/json
+Request Body:
+{
+"name": "Loan service fee",
+"chargeAppliesTo": 1,
+"currencyCode": "USD",
+"locale": "en",
+"amount": "230.56",
+"chargeTimeType": "1",
+"chargeCalculationType": "1",
+"chargePaymentMode": "1",
+"active": true
+}
+			</code>
+			<code class="method-response">
+{
+"resourceId": 1
+}
+			</code>
+			<code class="method-request">
+POST charges
+Content-Type: application/json
+Request Body:
+{
+"locale": "en",
+"name": "Default Penalty",
+"amount": "2",
+"currencyCode": "USD",
+"chargeAppliesTo": "1",
+"chargeTimeType": "2",
+"chargeCalculationType": "2",
+"chargePaymentMode": "1",
+"active": "true",
+"penalty": "true",
+"minCap": 50.00,
+"maxCap": 100.00
+}
+			</code>
+			<code class="method-response">
+{
+"resourceId": 4
+}
+			</code>
+
+			<code class="method-request">
+POST charges
+Content-Type: application/json
+Request Body:
+{
+"locale": "en",
+"name": "Annuaul Fee",
+"amount": "20",
+"currencyCode": "USD",
+"chargeAppliesTo": "2",
+"chargeTimeType": "6",
+"chargeCalculationType": "1",
+"chargePaymentMode": "1",
+"active": "true",
+"penalty": "false"
+}
+			</code>
+			<code class="method-response">
+{
+"resourceId": 5
+}
+			</code>
+			<code class="method-request">
+POST charges
+Content-Type: application/json
+Request Body:
+{
+"locale": "en",
+"name": "Quarterly Fee",
+"amount": "5",
+"currencyCode": "USD",
+"chargeAppliesTo": "2",
+"chargeTimeType": "7",
+"feeOnMonthDay": "10 May",
+"monthDayFormat": "dd MMM",
+"feeInterval": "4",
+"chargeCalculationType": "1",
+"active": "true",
+"penalty": "false"
+}
+			</code>
+			<code class="method-response">
+{
+"resourceId": 6
+}
+			</code>
+			<code class="method-request">
+POST charges
+Content-Type: application/json
+Request Body:
+{
+	"chargeAppliesTo":1,
+	"feeFrequency":1,
+	"feeInterval":"2",
+	"name":"overdue charge",
+	"currencyCode":"USD",
+	"chargeTimeType":9,
+	"chargeCalculationType":1,
+	"chargePaymentMode":0,
+	"amount":"50",
+	"active":true,
+	"penalty":"true",
+	"locale":"en",
+	"monthDayFormat":"dd MMM"
+}
+			</code>
+			<code class="method-response">
+{
+"resourceId": 7
+}
+			</code>
+            <code class="method-request">
+POST charges
+Content-Type: application/json
+Request Body:
+{
+    "chargeAppliesTo": 2,
+    "name": "Weekly Fee",
+    "currencyCode": "USD",
+    "chargeTimeType": 11,
+    "chargeCalculationType": 1,
+    "feeInterval": "1",
+    "amount": "10",
+    "active": true,
+    "locale": "en"
+}
+            </code>
+            <code class="method-response">
+{
+    "resourceId": 8
+}
+            </code>
+        </div>
+	</div>
+
+			<a id="charges_list" name="charges_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Charges</h4>
+					<p>Returns the list of defined charges.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>charges</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/charges
+					</code>
+					<code class="method-response">
+[
+    {
+        "id": 1,
+        "name": "Loan service fee",
+        "active": true,
+        "penalty": false,
+        "currency": {
+            "code": "USD",
+            "name": "US Dollar",
+            "decimalPlaces": 2,
+            "displaySymbol": "$",
+            "nameCode": "currency.USD",
+            "displayLabel": "US Dollar ($)"
+        },
+        "amount": 230.56,
+        "chargeTimeType": {
+            "id": 1,
+            "code": "chargeTimeType.disbursement",
+            "value": "Disbursement"
+        },
+        "chargeAppliesTo": {
+            "id": 1,
+            "code": "chargeAppliesTo.loan",
+            "value": "Loan"
+        },
+        "chargeCalculationType": {
+            "id": 1,
+            "code": "chargeCalculationType.flat",
+            "value": "Flat"
+        },
+        "chargePaymentMode":{
+        	"id":1,
+        	"code":"chargepaymentmode.accounttransfer",
+        	"value":"Account transfer"
+        }
+    },
+	{
+    "id": 54,
+    "chargeId": 12,
+    "name": "Loan service fee 2",
+    "chargeTimeType": {
+      "id": 1,
+      "code": "chargeTimeType.disbursement",
+      "value": "Disbursement"
+    },
+    "chargeCalculationType": {
+      "id": 2,
+      "code": "chargeCalculationType.percent.of.amount",
+      "value": "% Amount"
+    },
+    "percentage": 2.000000,
+    "amountPercentageAppliedTo": 14000.000000,
+    "currency": {
+      "code": "KES",
+      "name": "Kenyan Shilling",
+      "decimalPlaces": 2,
+      "displaySymbol": "KSh",
+      "nameCode": "currency.KES",
+      "displayLabel": "Kenyan Shilling (KSh)"
+    },
+    "amount": 500.000000,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 500.000000,
+    "amountOrPercentage": 2.000000,
+    "penalty": false,
+    "chargePaymentMode": {
+      "id": 1,
+      "code": "chargepaymentmode.accounttransfer",
+      "value": "Account transfer"
+    },
+    "paid": false,
+    "waived": false,
+    "chargePayable": true,
+    "minCap": 500.000000,
+    "maxCap": 1000.000000
+  },
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="charges_retrieve" name="charges_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Charge</h4>
+					<p>Returns the details of a defined Charge.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>charges/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/charges/{chargeId}
+					</code>
+					<code class="method-response">
+{
+    "id": 1,
+    "name": "Loan service fee",
+    "active": true,
+    "penalty": false,
+    "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+    },
+    "amount": 230.56,
+    "chargeTimeType": {
+        "id": 1,
+        "code": "chargeTimeType.disbursement",
+        "value": "Disbursement"
+    },
+    "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+    },
+    "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+    },
+    "chargePaymentMode":{
+        	"id":1,
+        	"code":"chargepaymentmode.accounttransfer",
+        	"value":"Account transfer"
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="charges_update" name="charges_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Charge</h4>
+					<p>Updates the details of a Charge.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/charges/{chargeId}
+					</code>
+					<code class="method-request">
+PUT charges/1
+Content-Type: application/json
+Request Body:
+{
+	"name": "Loan service fee(changed)"
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1,
+    "changes": {
+        "name": "Loan service fee(changed)"
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="charges_delete" name="charges_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Charge</h4>
+					<p>Deletes a Charge.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/charges/{chargeId}
+					</code>
+					<code class="method-request">
+DELETE charges/1
+Content-Type: application/json
+					</code>
+					<code class="method-response">{ "resourceId": 1 } </code>
+				</div>
+			</div>
+
+<!--Accounting Rules section starts here-->
+			<a id="accountingrules" name="accountingrules" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Accounting Rules</h3>
+					<p>
+						It is typical scenario in MFI's that non accountants pass journal entries on a regular basis.
+						For Ex: A branch office might deposit their entire cash at hand to their Bank account at the end of a
+						working day. The branch office users might not understand enough of accounting to figure out which
+						account needs to get credited and which account needs to be debited to represent this transaction.
+					</p>
+					<p>Enter accounting rules, an abstraction on top of manual Journal entires for enabling
+					 simpler data entry.
+					 An accounting rule can define any of the following abstractions
+					<ul>
+					 	<li>A Simple journal entry where both the credit and debit account have been preselected</li>
+					 	<li>A Simple journal entry where either credit or debit accounts have been limited to a
+					 		pre-selected list of accounts (Ex: Debit account should be one of "Bank of America" of "JP Morgan"
+					 		and credit account should be "Cash")
+					 	</li>
+					 	<li>A Compound journal entry where multiple debits and / or multiple credits may be made
+					 		amongst a set of preselected list of accounts (Ex: Credit account should be either "Bank Of America"
+					 		or "Cash" and debit account can be "Employee Salary" and/or "Miscellenous Expenses")
+					 	</li>
+					</ul>
+
+					</p>
+
+					<p>An accounting rule can also be optionally associated with a branch, so that only a particular Branch's
+						users have access to the rule</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name of the accounting rule</td>
+						</tr>
+						<tr class=alt>
+							<td>description</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A description of the accounting rule.</td>
+						</tr>
+						<tr class=alt>
+							<td>officeId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>An Optional Office for this accounting rule is applicable.</td>
+						</tr>
+						<tr class=alt>
+							<td>accountToDebit</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>ID of the target account to be Debited</td>
+						</tr>
+						<tr class=alt>
+							<td>accountToCredit</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>ID of the target account to be Credited</td>
+						</tr>
+						<tr class=alt>
+							<td>debitTags</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A list of accounting Tags. Any Ledger account with this tag can serve as the
+								account to be debited.<br> This parameter is optional, if <b>accountToDebit</b> present in the request params</td>
+						</tr>
+						<tr class=alt>
+							<td>creditTags</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A list of accounting Tags. Any Ledger account with this tag can serve as the
+								account to be credited.<br> This parameter is optional, if <b>accountToCredit</b> present in the request params</td></td>
+						</tr>
+						<tr class=alt>
+							<td>allowMultipleDebitEntries</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Allows passing multiple debit entries for the accounting rule</td>
+						</tr>
+						<tr class=alt>
+							<td>allowMultipleCreditEntries</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Allows passing multiple credit entries for the accounting rule</td>
+						</tr>
+						<tr class=alt>
+							<td>paymentTypeId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Allows passing multiple credit entries for the accounting rule</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="accountingrules_template" name="accountingrules_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Accounting Rule Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>accountingrules/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/accountingrules/template
+					</code>
+					<code class="method-response">
+{
+	"systemDefined": false,
+	"allowedOffices": [{
+		"id": 1,
+		"name": "Head Office",
+		"nameDecorated": "Head Office"
+	}],
+	"allowedAccounts": [{
+		"id": 21,
+		"name": "Cash 2",
+		"parentId": 18,
+		"glCode": "20011",
+		"disabled": false,
+		"manualEntriesAllowed": true,
+		"type": {
+			"id": 1,
+			"code": "accountType.asset",
+			"value": "ASSET"
+		},
+		"usage": {
+			"id": 1,
+			"code": "accountUsage.detail",
+			"value": "DETAIL"
+		},
+		"description": "Cash",
+		"nameDecorated": "............Cash 2",
+		"tagId": {
+			"id": 10,
+			"name": "asset tag"
+		}
+	},
+	{
+		"id": 9,
+		"name": "Employee Salary",
+		"parentId": 8,
+		"glCode": "456674",
+		"disabled": false,
+		"manualEntriesAllowed": true,
+		"type": {
+			"id": 5,
+			"code": "accountType.expense",
+			"value": "EXPENSE"
+		},
+		"usage": {
+			"id": 1,
+			"code": "accountUsage.detail",
+			"value": "DETAIL"
+		},
+		"nameDecorated": "Employee Salary",
+		"tagId": {
+			"id": 14,
+			"name": "Expenses Tag"
+		}
+	}]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="accountingrules_create" name="accountingrules_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create/Define a Accounting rule</h4>
+					<p>Define a new Accounting rule.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name, officeId,<br> accountToDebit OR debitTags,<br> accountToCredit OR creditTags.<br>
+							</td>
+						</tr>
+					</table>
+						<br />
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>description</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/accountingrules
+					</code>
+					<code class="method-request">
+POST accountingrules
+Content-Type: application/json
+Request Body:
+{
+	"name": "test",
+	"officeId": "1",
+	"accountToDebit": "21",
+	"accountToCredit": "9",
+	"description": "Employee salary"
+}
+					</code>
+					<code class="method-response">
+{
+	"officeId":1,
+	"resourceId":1
+}
+					</code>
+					<code class="method-request">
+POST accountingrules
+Content-Type: application/json
+Request Body:
+{
+	"name": "fordocs",
+	"officeId": "1",
+	"debitTags": ["10","11"],
+	"allowMultipleDebitEntries": "true",
+	"creditTags": ["12","13"],
+	"allowMultipleCreditEntries": "true",
+	"description": "for api docs"
+}
+					</code>
+					<code class="method-response">
+{
+	"officeId":1,
+	"resourceId":2
+}
+					</code>
+					<code class="method-request">
+POST accountingrules
+Content-Type: application/json
+Request Body:
+{
+	"name": "test123",
+	"officeId": "1",
+	"accountToDebit": "21",
+	"creditTags": ["12", "13"],
+	"allowMultipleCreditEntries": "false",
+	"description": "Employee salary"
+}
+					</code>
+					<code class="method-response">
+{
+	"officeId":1,
+	"resourceId":3
+}
+					</code>
+					<code class="method-request">
+POST accountingrules
+Content-Type: application/json
+Request Body:
+{
+	"name": "fordocstest",
+	"officeId": "1",
+	"debitTags": ["10","11"],
+	"allowMultipleDebitEntries": "false",
+	"accountToCredit": "9",
+	"description": "for api docs"
+}
+					</code>
+					<code class="method-response">
+{
+	"officeId":1,
+	"resourceId":4
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="accountingrules_list" name="accountingrules_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Accounting Rules</h4>
+					<p>Returns the list of defined accounting rules.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>accountingrules</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/accountingrules
+					</code>
+					<code class="method-response">
+[{
+	"id": 1,
+	"officeId": 1,
+	"officeName": "Head Office",
+	"name": "test",
+	"description": "Employee salary",
+	"systemDefined": false,
+	"debitAccountHead": {
+		"id": 21,
+		"name": "Cash 2",
+		"glCode": "20011",
+		"disabled": false,
+		"manualEntriesAllowed": false
+	},
+	"creditAccountHead": {
+		"id": 9,
+		"name": "Employee Salary",
+		"glCode": "456674",
+		"disabled": false,
+		"manualEntriesAllowed": false
+	}
+},
+{
+	"id": 2,
+	"officeId": 1,
+	"officeName": "Head Office",
+	"name": "A L1",
+	"description": "aafff",
+	"systemDefined": false,
+	"debitAccountHead": {
+		"id": 3,
+		"name": "A L",
+		"glCode": "10003",
+		"disabled": false,
+		"manualEntriesAllowed": false
+	},
+	"creditAccountHead": {
+		"id": 4,
+		"name": "car loan",
+		"glCode": "10004",
+		"disabled": false,
+		"manualEntriesAllowed": false
+	}
+}]
+					</code>
+				</div>
+			</div>
+
+			<a id="accountingrules_retrieve" name="accountingrules_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Accounting rule</h4>
+					<p>Returns the details of a defined Accounting rule.</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>accountingrules/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/accountingrules/{accountingruleId}
+					</code>
+					<code class="method-response">
+{
+	"id": 1,
+	"officeId": 1,
+	"officeName": "Head Office",
+	"name": "test",
+	"description": "Employee salary",
+	"systemDefined": false,
+	"debitAccountHead": {
+		"id": 21,
+		"name": "Cash 2",
+		"glCode": "20011",
+		"disabled": false,
+		"manualEntriesAllowed": false
+	},
+	"creditAccountHead": {
+		"id": 9,
+		"name": "Employee Salary",
+		"glCode": "456674",
+		"disabled": false,
+		"manualEntriesAllowed": false
+	}
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="accountingrules_update" name="accountingrules_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Accounting Rule</h4>
+					<p>Updates the details of a Accounting rule.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/accountingrules/{accountingruleId}
+					</code>
+					<code class="method-request">
+PUT accountingrules/1
+Content-Type: application/json
+Request Body:
+{
+	"name": "Employee Salary posting rule"
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1,
+    "changes": {
+        "name": "Employee Salary posting rule"
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="accountingrules_delete" name="accountingrules_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Accounting Rule</h4>
+					<p>Deletes a Accounting rule.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/accountingrules/{accountingruleId}
+					</code>
+					<code class="method-request">
+DELETE accountingrules/1
+Content-Type: application/json
+					</code>
+					<code class="method-response">{ "resourceId": 1 } </code>
+				</div>
+			</div>
+
+<!-- start of saving products api docs -->
+	<a id="savingsproducts" name="savingsproducts" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h3>Savings Product:</h3>
+	        <p>An MFIs savings product offerings are modeled using this API.</p>
+	        <p>When creating savings accounts, the details from the savings product are used to auto fill details of the savings account application process.</p>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Field Descriptions</div></td></tr>
+
+				<tr class=alt><td>name</td></tr>
+	            <tr><td class=fielddesc>The name of the product offering.</td></tr>
+
+	            <tr class=alt><td>shortName</td></tr>
+	            <tr><td class=fielddesc>Shortname associated with a saving product. <br>
+	            	An abbreviated version of the name, used in reports or menus where space is limited.
+	            </td></tr>
+
+	            <tr class=alt><td>description</td></tr>
+	            <tr><td class=fielddesc>A description of the product offering.</td></tr>
+
+				<tr class=alt><td>currencyCode</td></tr>
+	            <tr><td class=fielddesc>Three letter ISO code representing currency.</td></tr>
+
+	            <tr class=alt><td>digitsAfterDecimal</td></tr>
+	            <tr><td class=fielddesc>Override the currency default value for digitsAfterDecimal.</td></tr>
+
+	            <tr class=alt><td>inMultiplesOf</td></tr>
+	            <tr><td class=fielddesc>Override the default value for rounding currency to multiples of provided value.</td></tr>
+
+				<tr class=alt><td>nominalAnnualInterestRate</td></tr>
+	            <tr><td class=fielddesc>The default interest rate set when creating savings accounts of this type of product. e.g. <b>5</b>% Per year - It number here is always expressed as the Nominal APR.</td></tr>
+
+				<tr class=alt><td>interestCompoundingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is compounded. 1=Daily, 4=Monthly (at end of month)</td></tr>
+
+	            <tr class=alt><td>interestPostingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is posted or credited to savings account. 4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec)</td></tr>
+
+	            <tr class=alt><td>interestCalculationType</td></tr>
+	            <tr><td class=fielddesc>The interest calculation method used: 1=Daily Balance or 2=Average Daily Balance</td></tr>
+
+				<tr class=alt><td>interestCalculationDaysInYearType</td></tr>
+	            <tr><td class=fielddesc>The setting for number of days in year to use: 360=360 Days, 365=365 Days</td></tr>
+
+				<tr class=alt><td>minRequiredOpeningBalance</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, sets the minimum deposit amount required to open a savings account e.g. <b>2,000</b></td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequency</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequencyType</i> to indicate the length of time that the savings account is 'locked in' and withdrawals are not allowed. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequencyType</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequency</i> to indicate the length of time that the savings account is 'locked in' and withdrawals are not allowed. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            e.g. 6 <b>Months</b></td></tr>
+
+	            <tr class=alt><td>withdrawalFeeForTransfers</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: Used along with <i>withdrawalFeeAmount</i> to indicate whether the withdrawal fee should be applied on the account for account transfers .</td></tr>
+
+				<tr class=alt><td>accountingRule</td></tr>
+	            <tr>
+					<td class=fielddesc>Specifies if accounting is enabled for the particular
+					product and the type of the accounting rule to be used
+					<span>Example Values:</span>1=NONE,2=CASH_BASED</td>
+				</tr>
+
+				<tr class=alt><td>allowOverdraft</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, depending on the value mark the savings account as overdraft account </td></tr>
+
+				<tr class=alt><td>overdraftLimit</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, sets the maximum allowed overdraft amount for a  savings account e.g. <b>5,000</b> else set the limit as zero</td></tr>
+
+				<tr class=alt><td>minBalanceForInterestCalculation</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, balance must be greater than provided value for calculation of interest  e.g. <b>5,000</b> </td></tr>
+
+	            <tr class=alt><td>enforceMinRequiredBalance</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If set to <span>true</span>, validates that the account balance does not
+	            fall below <span>minRequiredBalance</span> </td></tr>
+
+				<tr class=alt><td>minRequiredBalance</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, sets an indicator of the minimum required balance of the  savings account e.g. <b>5,000</b> else set the limit as zero. Note that <span>enforceMinRequiredBalance</span> determines if the minimum required balance is enforced</td></tr>
+
+	        </table>
+	    </div>
+	</div>
+
+	<a id="savingsproducts_template" name="savingsproducts_template" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Retrieve Savings Product Template</h4>
+            <p>This is a convenience resource.  It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:
+                <ul>
+                <li class=normalli>Field Defaults</li>
+                <li class=normalli>Allowed Value Lists</li>
+                </ul>
+            </p>
+            <p>Example Request: </p>
+            <div class=apiClick>savingsproducts/template</div>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">GET https://Domain Name/api/v1/savingsproducts/template</code>
+			<code class="method-response">
+{
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "interestCompoundingPeriodType": {
+    "id": 1,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+    "value": "Daily"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsInterestPostingPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "accountingRule": {
+    "id": 1,
+    "code": "accountingRuleType.none",
+    "value": "NONE"
+  },
+  "currencyOptions": [
+    {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    }
+  ],
+  "interestCompoundingPeriodTypeOptions": [
+    {
+      "id": 1,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+      "value": "Daily"
+    },
+    {
+      "id": 2,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.weekly",
+      "value": "Weekly"
+    },
+    {
+      "id": 3,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.biweekly",
+      "value": "Bi-Weekly"
+    },
+    {
+      "id": 4,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    {
+      "id": 5,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.quarterly",
+      "value": "Quarterly"
+    },
+    {
+      "id": 6,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.biannual",
+      "value": "Semi-Annual"
+    },
+    {
+      "id": 7,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.annual",
+      "value": "Annually"
+    },
+    {
+      "id": 8,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.nocompounding",
+      "value": "No Compounding - Simple Interest"
+    }
+  ],
+  "interestPostingPeriodTypeOptions": [
+    {
+      "id": 4,
+      "code": "savings.interest.posting.period.savingsInterestPostingPeriodType.monthly",
+      "value": "Monthly"
+    },
+    {
+      "id": 5,
+      "code": "savings.interest.posting.period.savingsInterestPostingPeriodType.quarterly",
+      "value": "Quarterly"
+    },
+    {
+      "id": 6,
+      "code": "savings.interest.posting.period.savingsInterestPostingPeriodType.biannual",
+      "value": "Semi-Annual"
+    },
+    {
+      "id": 7,
+      "code": "savings.interest.posting.period.savingsInterestPostingPeriodType.annual",
+      "value": "Annually"
+    }
+  ],
+  "interestCalculationTypeOptions": [
+    {
+      "id": 1,
+      "code": "savingsInterestCalculationType.dailybalance",
+      "value": "Daily Balance"
+    },
+    {
+      "id": 2,
+      "code": "savingsInterestCalculationType.averagedailybalance",
+      "value": "Average Daily Balance"
+    }
+  ],
+  "interestCalculationDaysInYearTypeOptions": [
+    {
+      "id": 360,
+      "code": "savingsInterestCalculationDaysInYearType.days360",
+      "value": "360 Days"
+    },
+    {
+      "id": 365,
+      "code": "savingsInterestCalculationDaysInYearType.days365",
+      "value": "365 Days"
+    }
+  ],
+  "lockinPeriodFrequencyTypeOptions": [
+    {
+      "id": 0,
+      "code": "savings.lockin.savingsPeriodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "savings.lockin.savingsPeriodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "savings.lockin.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "savings.lockin.savingsPeriodFrequencyType.years",
+      "value": "Years"
+    }
+  ],
+  "withdrawalFeeTypeOptions": [
+    {
+      "id": 1,
+      "code": "savingsWithdrawalFeesType.flat",
+      "value": "Flat"
+    },
+    {
+      "id": 2,
+      "code": "savingsWithdrawalFeesType.percent.of.amount",
+      "value": "% of Amount"
+    }
+  ],
+  "paymentTypeOptions": [
+    {
+      "id": 14,
+      "name": "Wire Transfer",
+      "position": 0
+    },
+    {
+      "id": 13,
+      "name": "Cash",
+      "position": 1
+    }
+  ],
+  "accountingRuleOptions": [
+    {
+      "id": 1,
+      "code": "accountingRuleType.none",
+      "value": "NONE"
+    },
+    {
+      "id": 2,
+      "code": "accountingRuleType.cash",
+      "value": "CASH BASED"
+    },
+    {
+      "id": 3,
+      "code": "accountingRuleType.accrual",
+      "value": "ACCRUAL BASED"
+    }
+  ],
+  "accountingMappingOptions": {
+    "liabilityAccountOptions": [
+      {
+        "id": 15,
+        "name": "Savings Control",
+        "glCode": "50001",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 2,
+          "code": "accountType.liability",
+          "value": "LIABILITY"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "Savings Control",
+        "tagId": {
+          "id": 0
+        }
+      }
+    ],
+    "assetAccountOptions": [
+      {
+        "id": 2,
+        "name": "Cash",
+        "glCode": "100001",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      },
+      {
+        "id": 16,
+        "name": "Savings Reference",
+        "glCode": "100007",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "Savings Reference",
+        "tagId": {
+          "id": 0
+        }
+      },
+      {
+        "id": 12,
+        "name": "HDFC Rajajinagar",
+        "glCode": "100015",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      },
+      {
+        "id": 1,
+        "name": "Loan Portfolio",
+        "glCode": "10003",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      },
+      {
+        "id": 7,
+        "name": "Interest Receivable",
+        "glCode": "10005",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      },
+      {
+        "id": 8,
+        "name": "Penalties Receivable",
+        "glCode": "10008",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      },
+      {
+        "id": 9,
+        "name": "Fee Receivable",
+        "glCode": "10009",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 1,
+          "code": "accountType.asset",
+          "value": "ASSET"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      }
+    ],
+    "expenseAccountOptions": [
+      {
+        "id": 6,
+        "name": "Write Off Expenses",
+        "glCode": "60001",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 5,
+          "code": "accountType.expense",
+          "value": "EXPENSE"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      },
+      {
+        "id": 13,
+        "name": "Employee Salary",
+        "glCode": "60002",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 5,
+          "code": "accountType.expense",
+          "value": "EXPENSE"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      },
+      {
+        "id": 18,
+        "name": "Interest On Savings",
+        "glCode": "60003",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 5,
+          "code": "accountType.expense",
+          "value": "EXPENSE"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "nameDecorated": "Interest On Savings",
+        "tagId": {
+          "id": 0
+        }
+      }
+    ],
+    "incomeAccountOptions": [
+      {
+        "id": 3,
+        "name": "Income from Interest",
+        "glCode": "40001",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 4,
+          "code": "accountType.income",
+          "value": "INCOME"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      },
+      {
+        "id": 4,
+        "name": "Income from Fees",
+        "glCode": "40002",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 4,
+          "code": "accountType.income",
+          "value": "INCOME"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      },
+      {
+        "id": 5,
+        "name": "Income from Penalties",
+        "glCode": "40004",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+          "id": 4,
+          "code": "accountType.income",
+          "value": "INCOME"
+        },
+        "usage": {
+          "id": 1,
+          "code": "accountUsage.detail",
+          "value": "DETAIL"
+        },
+        "tagId": {}
+      }
+    ]
+  },
+  "chargeOptions" : [ { "active" : true,
+        "amount" : 200,
+        "chargeAppliesTo" : { "code" : "chargeAppliesTo.savings",
+            "id" : 2,
+            "value" : "Savings"
+          },
+        "chargeCalculationType" : { "code" : "chargeCalculationType.flat",
+            "id" : 1,
+            "value" : "Flat"
+          },
+        "chargePaymentMode" : { "code" : "chargepaymentmode.regular",
+            "id" : 0,
+            "value" : "chargepaymentmode.regular"
+          },
+        "chargeTimeType" : { "code" : "chargeTimeType.specifiedDueDate",
+            "id" : 2,
+            "value" : "Specified due date"
+          },
+        "currency" : { "code" : "USD",
+            "decimalPlaces" : 2,
+            "displayLabel" : "US Dollar ($)",
+            "displaySymbol" : "$",
+            "name" : "US Dollar",
+            "nameCode" : "currency.USD"
+          },
+        "id" : 4,
+        "name" : "Savings charge 1",
+        "penalty" : false
+      },
+      { "active" : true,
+        "amount" : 300,
+        "chargeAppliesTo" : { "code" : "chargeAppliesTo.savings",
+            "id" : 2,
+            "value" : "Savings"
+          },
+        "chargeCalculationType" : { "code" : "chargeCalculationType.flat",
+            "id" : 1,
+            "value" : "Flat"
+          },
+        "chargePaymentMode" : { "code" : "chargepaymentmode.regular",
+            "id" : 0,
+            "value" : "chargepaymentmode.regular"
+          },
+        "chargeTimeType" : { "code" : "chargeTimeType.specifiedDueDate",
+            "id" : 2,
+            "value" : "Specified due date"
+          },
+        "currency" : { "code" : "USD",
+            "decimalPlaces" : 2,
+            "displayLabel" : "US Dollar ($)",
+            "displaySymbol" : "$",
+            "name" : "US Dollar",
+            "nameCode" : "currency.USD"
+          },
+        "id" : 5,
+        "name" : "Savings charge 2",
+        "penalty" : false
+      }
+    ]
+}
+			</code>
+        </div>
+    </div>
+
+    <a id="savingsproducts_create" name="savingsproducts_create" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Create a Savings Product</h4>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields</div></td></tr>
+                <tr class=alt>
+                	<td>name, shortName, description, currencyCode, digitsAfterDecimal,inMultiplesOf, nominalAnnualInterestRate,
+                		interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType,accountingRule</td>
+                </tr>
+            </table>
+            <br/>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields for Cash based accounting (accountingRule = 2)</div></td></tr>
+                <tr class=alt>
+                	<td>savingsReferenceAccountId, savingsControlAccountId, interestOnSavingsAccountId, incomeFromFeeAccountId, transfersInSuspenseAccountId, incomeFromPenaltyAccountId</td>
+                </tr>
+            </table>
+            <br/>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Optional Fields</div></td></tr>
+                <tr class=alt><td>minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, charges, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation</tr>
+            </table>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">POST https://Domain Name/api/v1/savingsproducts</code>
+			<code class="method-request">POST savingsproducts
+Content-Type: application/json
+Request Body:
+{
+  "name": "Passbook Savings",
+  "shortName": "PBSV",
+  "description": "Daily compounding using Daily Balance, 5% per year, 365 days in year",
+  "currencyCode": "USD",
+  "digitsAfterDecimal": 2,
+  "inMultiplesOf": 0,
+  "locale": "en",
+  "nominalAnnualInterestRate": "5.0",
+  "interestCompoundingPeriodType": 1,
+  "interestPostingPeriodType":4,
+  "interestCalculationType": 1,
+  "interestCalculationDaysInYearType": "365",
+  "accountingRule":"1",
+  "charges":[{"id":"1"}]
+}
+			</code>
+			<code class="method-response">
+{
+  "resourceId": 1
+}
+			</code>
+			<code class="method-request">POST savingsproducts
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "name": "New Passbook Savings ",
+  "shortName": "NPBS",
+  "description": "A savings product",
+  "currencyCode": "USD",
+  "digitsAfterDecimal": "2",
+  "inMultiplesOf": 0,
+  "nominalAnnualInterestRate": "15",
+  "interestCompoundingPeriodType": "1",
+  "interestPostingPeriodType": "4",
+  "interestCalculationType": "1",
+  "interestCalculationDaysInYearType": "365",
+  "minRequiredOpeningBalance": "50",
+  "allowOverdraft":true,
+  "overdraftLimit":5000,
+  "minBalanceForInterestCalculation":5000,
+  "minRequiredBalance":20,
+  "enforceMinRequiredBalance":true,
+  "lockinPeriodFrequency": "4",
+  "lockinPeriodFrequencyType": "1",
+  "withdrawalFeeForTransfers":false,
+  "accountingRule": "2",
+  "charges":[{"id":"1"},{"id":3}]
+  "savingsReferenceAccountId": "16",
+  "transfersInSuspenseAccountId": "16",
+  "savingsControlAccountId": "15",
+  "interestOnSavingsAccountId": "18",
+  "incomeFromFeeAccountId": "4",
+  "incomeFromPenaltyAccountId": "4",
+  "feeToIncomeAccountMappings":[
+      {
+         "chargeId":"6",
+         "incomeAccountId":"16"
+      }
+   ],
+   "penaltyToIncomeAccountMappings":[
+	  {
+	     "chargeId":"4",
+	     "incomeAccountId":"15"
+	  }
+  ],
+  "paymentChannelToFundSourceMappings": [
+    {
+      "paymentTypeId": "14",
+      "fundSourceAccountId": "2"
+    }
+  ]
+}
+			</code>
+			<code class="method-response">
+{
+  "resourceId": 1
+}
+			</code>
+        </div>
+    </div>
+
+    <a id="savingsproducts_retrieve" name="savingsproducts_retrieve" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Retrieve a Savings Product</h4>
+            <p>Example Requests: </p>
+            <div class=apiClick>savingsproducts/1</div>
+            <br><br>
+            <div class=apiClick>savingsproducts/1?template=true</div>
+             <br><br>
+            <div class=apiClick>savingsproducts/1?fields=name,description</div>
+        </div>
+        <div class="method-example">
+			<code class="method-declaration">GET https://Domain Name/api/v1/savingsproducts/1</code>
+			<code class="method-response">
+{
+  "id": 1,
+  "name": "savings product",
+  "shortName": "sa1",
+  "description": "gtasga",
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "nominalAnnualInterestRate": 5.000000,
+  "interestCompoundingPeriodType": {
+    "id": 1,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+    "value": "Daily"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "withdrawalFeeForTransfers": false,
+  "accountingRule": {
+    "id": 2,
+    "code": "accountingRuleType.cash",
+    "value": "CASH BASED"
+  },
+  "accountingMappings": {
+    "savingsReferenceAccount": {
+      "id": 12,
+      "name": "savings ref",
+      "glCode": "20"
+    },
+    "incomeFromFeeAccount": {
+      "id": 16,
+      "name": "income from savings fee",
+      "glCode": "24"
+    },
+    "incomeFromPenaltyAccount": {
+      "id": 17,
+      "name": "income from sav penalites",
+      "glCode": "25"
+    },
+    "interestOnSavingsAccount": {
+      "id": 15,
+      "name": "interest on savings",
+      "glCode": "23"
+    },
+    "savingsControlAccount": {
+      "id": 13,
+      "name": "savings ref tool kit",
+      "glCode": "21"
+    },
+    "transfersInSuspenseAccount": {
+      "id": 14,
+      "name": "saving transfers",
+      "glCode": "22"
+    }
+  },
+  "paymentChannelToFundSourceMappings": [
+    {
+      "paymentType": {
+        "id": 10,
+        "name": "check"
+      },
+      "fundSourceAccount": {
+        "id": 12,
+        "name": "savings ref",
+        "glCode": "20"
+      }
+    }
+  ],
+  "feeToIncomeAccountMappings": [
+    {
+      "charge": {
+        "id": 11,
+        "name": "sav charge",
+        "active": false,
+        "penalty": false
+      },
+      "incomeAccount": {
+        "id": 16,
+        "name": "income from savings fee",
+        "glCode": "24"
+      }
+    }
+  ],
+  "penaltyToIncomeAccountMappings": [
+    {
+      "charge": {
+        "id": 12,
+        "name": "sav 2",
+        "active": false,
+        "penalty": true
+      },
+      "incomeAccount": {
+        "id": 17,
+        "name": "income from sav penalites",
+        "glCode": "25"
+      }
+    }
+  ],
+  "charges": []
+}
+			</code>
+        </div>
+    </div>
+
+    <a id="savingsproducts_update" name="savingsproducts_update" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Update a Savings Product</h4>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">PUT https://Domain Name/api/v1/savingsproducts/{productId}</code>
+			<code class="method-request">POST savingsproducts/1
+Content-Type: application/json
+Request Body:
+{
+  "description": "Passbook Savings Lite.",
+  "locale": "en",
+  "interestRate": "5.73"
+}
+			</code>
+			<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {
+    "description": "Passbook Savings Lite.",
+    "interestRate": 5.73,
+    "locale": "en"
+  }
+}
+			</code>
+        </div>
+    </div>
+
+	<a id="savingsproducts_delete" name="savingsproducts_delete" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+		<div class="method-description">
+			<h4>Delete a Savings Product</h4>
+		</div>
+		<div class="method-example">
+			<code class="method-declaration">
+DELETE https://Domain Name/api/v1/savingsproducts/{productId}
+			</code>
+			<code class="method-request">
+DELETE savingsproducts/1
+Content-Type: application/json
+			</code>
+			<code class="method-response">
+{
+	"resourceId": 1
+}
+			</code>
+		</div>
+	</div>
+
+    <a id="savingsproducts_list" name="savingsproducts_list" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>List Savings Products</h4>
+            <p>Example Requests:</p>
+            <div class=apiClick>savingsproducts</div>
+            <br><br>
+            <div class=apiClick>savingsproducts?fields=name</div>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">GET https://Domain Name/api/v1/savingsproducts</code>
+            <code class="method-response">
+[
+  {
+    "id": 1,
+    "name": "Savings product",
+    "shortName": "sa1",
+    "description": "gtasga",
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "nominalAnnualInterestRate": 5.000000,
+    "interestCompoundingPeriodType": {
+      "id": 1,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+      "value": "Daily"
+    },
+    "interestPostingPeriodType": {
+      "id": 4,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    "interestCalculationType": {
+      "id": 1,
+      "code": "savingsInterestCalculationType.dailybalance",
+      "value": "Daily Balance"
+    },
+    "interestCalculationDaysInYearType": {
+      "id": 365,
+      "code": "savingsInterestCalculationDaysInYearType.days365",
+      "value": "365 Days"
+    },
+    "withdrawalFeeForTransfers": false,
+    "accountingRule": {
+      "id": 2,
+      "code": "accountingRuleType.cash",
+      "value": "CASH BASED"
+    }
+  }
+]			</code>
+        </div>
+    </div>
+<!-- end of saving products api docs -->
+
+<!-- start of fixed deposit products api docs -->
+	<a id="fdproducts" name="fdproducts" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h3>Fixed Deposit Product:</h3>
+	        <p>This is one of the advanced term deposit product offered by MFI's. The Fixed Deposit Products (aka FD) product offerings are modeled using this API.</p>
+	        <p>The FD products are deposit accounts which are held for a fixed term – like 1 year, 2 years etc.</p>
+	        <p>When creating fixed deposit accounts, the details from the fixed deposit product are used to auto fill details of the fixed deposit account application process.</p>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Field Descriptions</div></td></tr>
+
+				<tr class=alt><td>name</td></tr>
+	            <tr><td class=fielddesc>The name of the product offering.</td></tr>
+
+	            <tr class=alt><td>shortName</td></tr>
+	            <tr><td class=fielddesc>Shortname associated with a fixed deposit product. <br>
+	            	An abbreviated version of the name, used in reports or menus where space is limited.
+	            </td></tr>
+
+	            <tr class=alt><td>description</td></tr>
+	            <tr><td class=fielddesc>A description of the product offering.</td></tr>
+
+				<tr class=alt><td>currencyCode</td></tr>
+	            <tr><td class=fielddesc>Three letter ISO code representing currency.</td></tr>
+
+	            <tr class=alt><td>digitsAfterDecimal</td></tr>
+	            <tr><td class=fielddesc>Override the currency default value for digitsAfterDecimal.</td></tr>
+
+	            <tr class=alt><td>inMultiplesOf</td></tr>
+	            <tr><td class=fielddesc>Override the default value for rounding currency to multiples of provided value.</td></tr>
+
+				<tr class=alt><td>interestCompoundingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is compounded. 1=Daily, 4=Monthly (at end of month)</td></tr>
+
+	            <tr class=alt><td>interestPostingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is posted or credited to fixed deposit account. 4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec)</td></tr>
+
+	            <tr class=alt><td>interestCalculationType</td></tr>
+	            <tr><td class=fielddesc>The interest calculation method used: 1=Daily Balance or 2=Average Daily Balance</td></tr>
+
+				<tr class=alt><td>interestCalculationDaysInYearType</td></tr>
+	            <tr><td class=fielddesc>The setting for number of days in year to use: 360=360 Days, 365=365 Days</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequency</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequencyType</i> to indicate the length of time that the fixed deposit account is 'locked in' and premature closure is not allowed. e.g. <b>2</b> Months</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequencyType</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequency</i> to indicate the length of time that the fixed deposit account is 'locked in' and premature closure is not allowed. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            e.g. 2 <b>Months</b></td></tr>
+
+	            <tr class=alt><td>minDepositTerm</td></tr>
+	            <tr><td class=fielddesc>This is used along with <i>minDepositTermTypeId</i> to define allowed minimum deposit term for creating a fixed deposit account using this product. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>minDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc> Used along with <i>minDepositTerm</i> to define allowed minimum deposit term for creating a fixed deposit account using this product. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 6 <b>Months</b>
+	            	</td>
+	            </tr>
+
+	            <tr class=alt><td>maxDepositTerm</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>maxDepositTermTypeId</i> to define allowed maximum deposit term for creating a fixed deposit account using this product. e.g. <b>3</b> Years</td></tr>
+
+	            <tr class=alt><td>maxDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>maxDepositTerm</i> to define allowed maximum deposit term for creating a fixed deposit account using this product. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 3 <b>Years</b>
+	        		</td>
+	        	</tr>
+
+	            <tr class=alt><td>inMultiplesOfDepositTerm</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>inMultiplesOfDepositTermTypeId</i> to indicate the allowed deposit periods after minimum deposit period. e.g. If inMultiplesOfDepositTerm is <b>3</b> Months then the next allowed deposit period after minimum deposit of <b>6</b> Months should be <b>9</d> Months. </td>
+	            </tr>
+
+	            <tr class=alt><td>inMultiplesOfDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>inMultiplesOfDepositTerm</i> to indicate the allowed deposit periods after minimum deposit period. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. If inMultiplesOfDepositTerm is <b>3</b> Months then the next allowed deposit period after minimum deposit of <b>6</b> Months should be <b>9</d> Months.
+	        		</td>
+	        	</tr>
+
+	        	<tr class=alt><td>preClosurePenalApplicable</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: expects boolean value true/false, If provided as true then must provide <i>preClosurePenalInterest</i> and <i>preClosurePenalInterestOnTypeId</i>. </td>
+	            </tr>
+
+	            <tr class=alt><td>preClosurePenalInterest</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: Must provide when <i>preClosurePenalApplicable</i> is true. Used along with <i>preClosurePenalInterestOnTypeId</i> to apply a penalalty on top of  applicable interest rate for Pre-mature closure of accounts. e.g. “1%” means 1% less than the interest rate applicable.</td>
+	            </tr>
+
+	            <tr class=alt><td>preClosurePenalInterestOnTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: Must provide when <i>preClosurePenalApplicable</i> is true. Used along with <i>preClosurePenalInterest</i> to decide what should be the applicable interest rate to which penalalty can be applied on Pre-mature closure of accounts. 1=Whole Term, 2=Till Premature withdrawal e.g. “1%” means 1% less than the interest rate applicable till premature withdrawal.</td>
+	            </tr>
+
+				<tr class=alt><td>accountingRule</td></tr>
+	            <tr>
+					<td class=fielddesc>Specifies if accounting is enabled for the particular
+					product and the type of the accounting rule to be used
+					<span>Example Values:</span>1=NONE,2=CASH_BASED</td>
+				</tr>
+
+	        </table>
+	    </div>
+	</div>
+
+	<a id="fdproducts_create" name="fdproducts_create" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Create a Fixed Deposit Product</h4>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields</div></td></tr>
+                <tr class=alt>
+                	<td>name, shortName, description, currencyCode, digitsAfterDecimal,inMultiplesOf,
+                		interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minDepositTerm, minDepositTermTypeId, accountingRule</td>
+                </tr>
+            </table>
+            <br/>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields for Cash based accounting (accountingRule = 2)</div></td></tr>
+                <tr class=alt>
+                	<td>savingsReferenceAccountId, savingsControlAccountId, interestOnSavingsAccountId, incomeFromFeeAccountId, transfersInSuspenseAccountId, incomeFromPenaltyAccountId </td>
+                </tr>
+            </table>
+            <br/>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Optional Fields</div></td></tr>
+                <tr class=alt><td>lockinPeriodFrequency, lockinPeriodFrequencyType, maxDepositTerm, maxDepositTermTypeId, inMultiplesOfDepositTerm, inMultiplesOfDepositTermTypeId, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnTypeId, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, charges, charts</tr>
+            </table>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositproducts</code>
+			<code class="method-request">POST fixeddepositproducts
+Content-Type: application/json
+Request Body:
+{
+  "name": "Fixed deposit product",
+  "shortName": "FD01",
+  "description": "Daily compounding using Daily Balance, 5% per year, 365 days in year",
+  "currencyCode": "USD",
+  "digitsAfterDecimal": 2,
+  "inMultiplesOf": 0,
+  "locale": "en",
+  "interestCompoundingPeriodType": 1,
+  "interestPostingPeriodType":4,
+  "interestCalculationType": 1,
+  "interestCalculationDaysInYearType": "365",
+  "accountingRule":"1",
+  "preClosurePenalApplicable":"true",
+  "preClosurePenalInterest":"1.75",
+  "preClosurePenalInterestOnTypeId":1,
+  "minDepositTerm":1,
+  "minDepositTermTypeId":1,
+  "maxDepositTerm":5,
+  "maxDepositTermTypeId":3,
+  "charts":[
+      {
+         "fromDate": "01 Jan 2014",
+         "locale":"en",
+         "dateFormat":"dd MMMM yyyy",
+         "dateFormat":"dd MMMM yyyy",
+         "chartSlabs":[
+             {
+         	  "description":"from 0 to 90 days",
+         	  "periodType":"1",
+         	  "fromPeriod":"0",
+         	  "toPeriod":"90",
+         	  "annualInterestRate":"4.5"
+            }
+         ]
+      }
+  ]
+}
+			</code>
+			<code class="method-response">
+{
+  "resourceId": 1
+}
+			</code>
+        </div>
+    </div>
+
+    <a id="fdproducts_retrieve" name="fdproducts_retrieve" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Retrieve a Fixed Deposit Product</h4>
+            <p>Example Requests: </p>
+            <div class=apiClick>fixeddepositproducts/1</div>
+            <br><br>
+            <div class=apiClick>fixeddepositproducts/1?template=true</div>
+             <br><br>
+            <div class=apiClick>fixeddepositproducts/1?fields=name,description</div>
+        </div>
+        <div class="method-example">
+			<code class="method-declaration">GET https://Domain Name/api/v1/fixeddepositproducts/1</code>
+			<code class="method-response">
+{
+  "id": 1,
+  "name": "Fixed deposit product",
+  "shortName": "FD01",
+  "description": "Daily compounding using Daily Balance, 5% per year, 365 days in year",
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "interestCompoundingPeriodType": {
+    "id": 1,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+    "value": "Daily"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "accountingMappings": {
+    "savingsReferenceAccount": {
+      "id": 12,
+      "name": "savings ref",
+      "glCode": "20"
+    },
+    "incomeFromFeeAccount": {
+      "id": 16,
+      "name": "income from savings fee",
+      "glCode": "24"
+    },
+    "incomeFromPenaltyAccount": {
+      "id": 17,
+      "name": "income from sav penalites",
+      "glCode": "25"
+    },
+    "interestOnSavingsAccount": {
+      "id": 15,
+      "name": "interest on savings",
+      "glCode": "23"
+    },
+    "savingsControlAccount": {
+      "id": 13,
+      "name": "savings ref tool kit",
+      "glCode": "21"
+    },
+    "transfersInSuspenseAccount": {
+      "id": 14,
+      "name": "saving transfers",
+      "glCode": "22"
+    }
+  },
+  "feeToIncomeAccountMappings": [
+    {
+      "charge": {
+        "id": 11,
+        "name": "sav charge",
+        "active": false,
+        "penalty": false
+      },
+      "incomeAccount": {
+        "id": 16,
+        "name": "income from savings fee",
+        "glCode": "24"
+      }
+    }
+  ],
+  "penaltyToIncomeAccountMappings": [
+    {
+      "charge": {
+        "id": 12,
+        "name": "sav 2",
+        "active": false,
+        "penalty": true
+      },
+      "incomeAccount": {
+        "id": 17,
+        "name": "income from sav penalites",
+        "glCode": "25"
+      }
+    }
+  ],
+  "preClosurePenalApplicable":"true",
+  "preClosurePenalInterest":"1.75",
+  "preClosurePenalInterestOnType": {
+    "id": 1,
+    "code": "preClosurePenalInterestOnType.wholeTerm",
+    "value": "Whole term"
+  },
+  "minDepositTerm":1,
+  "minDepositTermType": {
+    "id": 1,
+    "code": "deposit.term.savingsPeriodFrequencyType.weeks",
+    "value": "Weeks"
+  },
+  "maxDepositTerm":5,
+  "maxDepositTermType": {
+    "id": 3,
+    "code": "deposit.term.savingsPeriodFrequencyType.years",
+    "value": "Years"
+  },
+  "activeChart": {
+    "id": 8,
+    "fromDate": [
+      2014,
+      1,
+      1
+    ],
+    "savingsProductId": 8,
+    "savingsProductName": "Fixed deposit product",
+    "chartSlabs": [
+      {
+        "id": 18,
+        "description": "from 0 to 90 days",
+        "periodType": {
+          "id": 1,
+          "code": "interestChartPeriodType.weeks",
+          "value": "Weeks"
+        },
+        "fromPeriod": 0,
+        "toPeriod": 90,
+        "annualInterestRate": 4.5,
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        }
+      }
+    ],
+    "periodTypes": [
+      {
+        "id": 0,
+        "code": "interestChartPeriodType.days",
+        "value": "Days"
+      },
+      {
+        "id": 1,
+        "code": "interestChartPeriodType.weeks",
+        "value": "Weeks"
+      },
+      {
+        "id": 2,
+        "code": "interestChartPeriodType.months",
+        "value": "Months"
+      },
+      {
+        "id": 3,
+        "code": "interestChartPeriodType.years",
+        "value": "Years"
+      }
+    ]
+  }
+}
+			</code>
+        </div>
+    </div>
+
+    <a id="fdproducts_update" name="fdproducts_update" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Update a Fixed Deposit Product</h4>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">PUT https://Domain Name/api/v1/fixeddepositproducts/{productId}</code>
+			<code class="method-request">POST fixeddepositproducts/1
+Content-Type: application/json
+Request Body:
+{
+  "description": "Fixed deposit product new offerings",
+  "locale": "en",
+  "minDepositTerm":5,
+  "minDepositTermTypeId":1,
+}
+			</code>
+			<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {
+    "description": "Fixed deposit product new offerings",
+    "minDepositTerm": 5
+  }
+}
+			</code>
+        </div>
+    </div>
+
+    <a id="fdproducts_delete" name="fdproducts_delete" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+		<div class="method-description">
+			<h4>Delete a Fixed Deposit Product</h4>
+		</div>
+		<div class="method-example">
+			<code class="method-declaration">
+DELETE https://Domain Name/api/v1/fixeddepositdproducts/{productId}
+			</code>
+			<code class="method-request">
+DELETE fixeddepositdproducts/1
+Content-Type: application/json
+			</code>
+			<code class="method-response">
+{
+	"resourceId": 1
+}
+			</code>
+		</div>
+	</div>
+
+	<a id="fdproducts_list" name="fdproducts_list" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>List Fixed Deposit Products</h4>
+            <p>Example Requests:</p>
+            <div class=apiClick>fixeddepositproducts</div>
+            <br><br>
+            <div class=apiClick>fixeddepositproducts?fields=name</div>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">GET https://Domain Name/api/v1/fixeddepositproducts</code>
+            <code class="method-response">
+[
+  {
+    "id": 3,
+    "name": "FD01",
+    "shortName": "FD01",
+    "description": "FD01",
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "inMultiplesOf": 1,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "preClosurePenalApplicable": false,
+    "minDepositTerm": 3,
+    "maxDepositTerm": 4,
+    "minDepositTermType": {
+      "id": 2,
+      "code": "deposit.term.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    "maxDepositTermType": {
+      "id": 3,
+      "code": "deposit.term.savingsPeriodFrequencyType.years",
+      "value": "Years"
+    },
+    "nominalAnnualInterestRate": 0,
+    "interestCompoundingPeriodType": {
+      "id": 4,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    "interestPostingPeriodType": {
+      "id": 4,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    "interestCalculationType": {
+      "id": 1,
+      "code": "savingsInterestCalculationType.dailybalance",
+      "value": "Daily Balance"
+    },
+    "interestCalculationDaysInYearType": {
+      "id": 365,
+      "code": "savingsInterestCalculationDaysInYearType.days365",
+      "value": "365 Days"
+    },
+    "accountingRule": {
+      "id": 1,
+      "code": "accountingRuleType.none",
+      "value": "NONE"
+    }
+  }
+]			</code>
+        </div>
+    </div>
+<!-- end of fixed deposit products api docs -->
+
+<!-- start of recurring deposit products api docs -->
+	<a id="rdproducts" name="rdproducts" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h3>Recurring Deposit Product:</h3>
+	        <p>Recurring Deposits are a special kind of Term Deposits offered by MFI's. The Recurring Deposit Products (aka RD) product offerings are modeled using this API.</p>
+	        <p>Recurring Deposits help people with regular incomes to deposit a fixed amount every month (specified recurring frequency) into their Recurring Deposit account.</p>
+	        <p>When creating recurring deposit accounts, the details from the recurring deposit product are used to auto fill details of the recurring deposit account application process.</p>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Field Descriptions</div></td></tr>
+
+				<tr class=alt><td>name</td></tr>
+	            <tr><td class=fielddesc>The name of the product offering.</td></tr>
+
+	            <tr class=alt><td>shortName</td></tr>
+	            <tr><td class=fielddesc>Shortname associated with a recurring deposit product. <br>
+	            	An abbreviated version of the name, used in reports or menus where space is limited.
+	            </td></tr>
+
+	            <tr class=alt><td>description</td></tr>
+	            <tr><td class=fielddesc>A description of the product offering.</td></tr>
+
+				<tr class=alt><td>currencyCode</td></tr>
+	            <tr><td class=fielddesc>Three letter ISO code representing currency.</td></tr>
+
+	            <tr class=alt><td>digitsAfterDecimal</td></tr>
+	            <tr><td class=fielddesc>Override the currency default value for digitsAfterDecimal.</td></tr>
+
+	            <tr class=alt><td>inMultiplesOf</td></tr>
+	            <tr><td class=fielddesc>Override the default value for rounding currency to multiples of provided value.</td></tr>
+
+				<tr class=alt><td>interestCompoundingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is compounded. 1=Daily, 4=Monthly (at end of month)</td></tr>
+
+	            <tr class=alt><td>interestPostingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is posted or credited to recurring deposit account. 4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec)</td></tr>
+
+	            <tr class=alt><td>interestCalculationType</td></tr>
+	            <tr><td class=fielddesc>The interest calculation method used: 1=Daily Balance or 2=Average Daily Balance</td></tr>
+
+				<tr class=alt><td>interestCalculationDaysInYearType</td></tr>
+	            <tr><td class=fielddesc>The setting for number of days in year to use: 360=360 Days, 365=365 Days</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequency</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequencyType</i> to indicate the length of time that the recurring deposit account is 'locked in' and premature closure is not allowed. e.g. <b>2</b> Months</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequencyType</td></tr>
+	            e.g. 2 <b>Months</b></td></tr>
+
+	            <tr class=alt><td>recurringDepositFrequency</td></tr>
+	            <tr><td class=fielddesc>The frequency for depositing a fixed amount to recurring deposit account.</td></tr>
+
+	            <tr class=alt><td>recurringDepositFrequencyTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc>Used along with <i>recurringDepositFrequency</i> to define frequency for depositing a fixed amount to recurring deposit account. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. deposit every 1 <b>Months</b>
+	            	</td>
+	            </tr>
+
+	            <tr class=alt><td>minDepositTerm</td></tr>
+	            <tr><td class=fielddesc>This is used along with <i>minDepositTermTypeId</i> to define allowed minimum deposit term for creating a recurring deposit account using this product. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>minDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc> Used along with <i>minDepositTerm</i> to define allowed minimum deposit term for creating a recurring deposit account using this product. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 6 <b>Months</b>
+	            	</td>
+	            </tr>
+
+	            <tr class=alt><td>maxDepositTerm</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>maxDepositTermTypeId</i> to define allowed maximum deposit term for creating a recurring deposit account using this product. e.g. <b>3</b> Years</td></tr>
+
+	            <tr class=alt><td>maxDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>maxDepositTerm</i> to define allowed maximum deposit term for creating a recurring deposit account using this product. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 3 <b>Years</b>
+	        		</td>
+	        	</tr>
+
+	            <tr class=alt><td>inMultiplesOfDepositTerm</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>inMultiplesOfDepositTermTypeId</i> to indicate the allowed deposit periods after minimum deposit period. e.g. If inMultiplesOfDepositTerm is <b>3</b> Months then the next allowed deposit period after minimum deposit of <b>6</b> Months should be <b>9</d> Months. </td>
+	            </tr>
+
+	            <tr class=alt><td>inMultiplesOfDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>inMultiplesOfDepositTerm</i> to indicate the allowed deposit periods after minimum deposit period. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. If inMultiplesOfDepositTerm is <b>3</b> Months then the next allowed deposit period after minimum deposit of <b>6</b> Months should be <b>9</d> Months.
+	        		</td>
+	        	</tr>
+
+	        	<tr class=alt><td>preClosurePenalApplicable</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: expects boolean value true/false, If provided as true then must provide <i>preClosurePenalInterest</i> and <i>preClosurePenalInterestOnTypeId</i>. </td>
+	            </tr>
+
+	            <tr class=alt><td>preClosurePenalInterest</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: Must provide when <i>preClosurePenalApplicable</i> is true. Used along with <i>preClosurePenalInterestOnTypeId</i> to apply a penalalty on top of  applicable interest rate for Pre-mature closure of accounts. e.g. “1%” means 1% less than the interest rate applicable.</td>
+	            </tr>
+
+	            <tr class=alt><td>preClosurePenalInterestOnTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: Must provide when <i>preClosurePenalApplicable</i> is true. Used along with <i>preClosurePenalInterest</i> to decide what should be the applicable interest rate to which penalalty can be applied on Pre-mature closure of accounts. 1=Whole Term, 2=Till Premature withdrawal e.g. “1%” means 1% less than the interest rate applicable till premature withdrawal.</td>
+	            </tr>
+
+				<tr class=alt><td>accountingRule</td></tr>
+	            <tr>
+					<td class=fielddesc>Specifies if accounting is enabled for the particular
+					product and the type of the accounting rule to be used
+					<span>Example Values:</span>1=NONE,2=CASH_BASED</td>
+				</tr>
+
+	        </table>
+	    </div>
+	</div>
+
+	<a id="rdproducts_create" name="rdproducts_create" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Create a Recurring Deposit Product</h4>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields</div></td></tr>
+                <tr class=alt>
+                	<td>name, shortName, description, currencyCode, digitsAfterDecimal,inMultiplesOf,
+                		interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minDepositTerm, minDepositTermTypeId, recurringDepositFrequency, recurringDepositFrequencyTypeId, accountingRule, depositAmount </td>
+                </tr>
+            </table>
+            <br/>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields for Cash based accounting (accountingRule = 2)</div></td></tr>
+                <tr class=alt>
+                	<td>savingsReferenceAccountId, savingsControlAccountId, interestOnSavingsAccountId, incomeFromFeeAccountId, transfersInSuspenseAccountId, incomeFromPenaltyAccountId</td>
+                </tr>
+            </table>
+            <br/>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Optional Fields</div></td></tr>
+                <tr class=alt><td>lockinPeriodFrequency, lockinPeriodFrequencyType, maxDepositTerm, maxDepositTermTypeId, inMultiplesOfDepositTerm, inMultiplesOfDepositTermTypeId, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnTypeId, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, charges, charts, minDepositAmount, maxDepositAmount</tr>
+            </table>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositproducts</code>
+			<code class="method-request">POST recurringdepositproducts
+Content-Type: application/json
+Request Body:
+{
+  "name": "Recurring deposit product",
+  "shortName": "RD01",
+  "description": "Daily compounding using Daily Balance, 5% per year, 365 days in year",
+  "currencyCode": "USD",
+  "digitsAfterDecimal": 2,
+  "inMultiplesOf": 0,
+  "locale": "en",
+  "interestCompoundingPeriodType": 1,
+  "interestPostingPeriodType":4,
+  "interestCalculationType": 1,
+  "interestCalculationDaysInYearType": "365",
+  "accountingRule":"1",
+  "recurringDepositFrequency":1,
+  "recurringDepositFrequencyTypeId":2,
+  "preClosurePenalApplicable":"true",
+  "preClosurePenalInterest":"1.75",
+  "preClosurePenalInterestOnTypeId":1,
+  "minDepositTerm":1,
+  "minDepositTermTypeId":1,
+  "maxDepositTerm":5,
+  "maxDepositTermTypeId":3,
+  depositAmount: "10000",
+  minDepositAmount: "100",
+  maxDepositAmount: "1000000",
+  "charts":[
+      {
+         "fromDate": "01 Jan 2014",
+         "locale":"en",
+         "dateFormat":"dd MMMM yyyy",
+         "dateFormat":"dd MMMM yyyy",
+         "chartSlabs":[
+             {
+         	  "description":"from 0 to 90 days",
+         	  "periodType":"1",
+         	  "fromPeriod":"0",
+         	  "toPeriod":"90",
+         	  "annualInterestRate":"4.5"
+            }
+         ]
+      }
+  ]
+}
+			</code>
+			<code class="method-response">
+{
+  "resourceId": 1
+}
+			</code>
+        </div>
+    </div>
+
+    <a id="rdproducts_retrieve" name="rdproducts_retrieve" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Retrieve a Recurring Deposit Product</h4>
+            <p>Example Requests: </p>
+            <div class=apiClick>recurringdepositproducts/1</div>
+            <br><br>
+            <div class=apiClick>recurringdepositproducts/1?template=true</div>
+             <br><br>
+            <div class=apiClick>recurringdepositproducts/1?fields=name,description</div>
+        </div>
+        <div class="method-example">
+			<code class="method-declaration">GET https://Domain Name/api/v1/recurringdepositproducts/1</code>
+			<code class="method-response">
+{
+  "id": 1,
+  "name": "Recurring deposit product",
+  "shortName": "RD01",
+  "description": "Daily compounding using Daily Balance, 5% per year, 365 days in year",
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "interestCompoundingPeriodType": {
+    "id": 1,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+    "value": "Daily"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "accountingMappings": {
+    "savingsReferenceAccount": {
+      "id": 12,
+      "name": "savings ref",
+      "glCode": "20"
+    },
+    "incomeFromFeeAccount": {
+      "id": 16,
+      "name": "income from savings fee",
+      "glCode": "24"
+    },
+    "incomeFromPenaltyAccount": {
+      "id": 17,
+      "name": "income from sav penalites",
+      "glCode": "25"
+    },
+    "interestOnSavingsAccount": {
+      "id": 15,
+      "name": "interest on savings",
+      "glCode": "23"
+    },
+    "savingsControlAccount": {
+      "id": 13,
+      "name": "savings ref tool kit",
+      "glCode": "21"
+    },
+    "transfersInSuspenseAccount": {
+      "id": 14,
+      "name": "saving transfers",
+      "glCode": "22"
+    }
+  },
+  "feeToIncomeAccountMappings": [
+    {
+      "charge": {
+        "id": 11,
+        "name": "sav charge",
+        "active": false,
+        "penalty": false
+      },
+      "incomeAccount": {
+        "id": 16,
+        "name": "income from savings fee",
+        "glCode": "24"
+      }
+    }
+  ],
+  "penaltyToIncomeAccountMappings": [
+    {
+      "charge": {
+        "id": 12,
+        "name": "sav 2",
+        "active": false,
+        "penalty": true
+      },
+      "incomeAccount": {
+        "id": 17,
+        "name": "income from sav penalites",
+        "glCode": "25"
+      }
+    }
+  ],
+  "recurringDepositFrequency":1,
+  "recurringDepositFrequencyType": {
+    "id": 2,
+    "code": "recurring.deposit.savingsPeriodFrequencyType.months",
+    "value": "Months"
+  },
+   "minDepositAmount":100.00,
+   "depositAmount":10000.00,
+   "maxDepositAmount":1000000.00,
+  "preClosurePenalApplicable":"true",
+  "preClosurePenalInterest":"1.75",
+  "preClosurePenalInterestOnType": {
+    "id": 1,
+    "code": "preClosurePenalInterestOnType.wholeTerm",
+    "value": "Whole term"
+  },
+  "minDepositTerm":1,
+  "minDepositTermType": {
+    "id": 1,
+    "code": "deposit.term.savingsPeriodFrequencyType.weeks",
+    "value": "Weeks"
+  },
+  "maxDepositTerm":5,
+  "maxDepositTermType": {
+    "id": 3,
+    "code": "deposit.term.savingsPeriodFrequencyType.years",
+    "value": "Years"
+  },
+  "activeChart": {
+    "id": 8,
+    "fromDate": [
+      2014,
+      1,
+      1
+    ],
+    "savingsProductId": 8,
+    "savingsProductName": "Fixed deposit product",
+    "chartSlabs": [
+      {
+        "id": 18,
+        "description": "from 0 to 90 days",
+        "periodType": {
+          "id": 1,
+          "code": "interestChartPeriodType.weeks",
+          "value": "Weeks"
+        },
+        "fromPeriod": 0,
+        "toPeriod": 90,
+        "annualInterestRate": 4.5,
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        }
+      }
+    ],
+    "periodTypes": [
+      {
+        "id": 0,
+        "code": "interestChartPeriodType.days",
+        "value": "Days"
+      },
+      {
+        "id": 1,
+        "code": "interestChartPeriodType.weeks",
+        "value": "Weeks"
+      },
+      {
+        "id": 2,
+        "code": "interestChartPeriodType.months",
+        "value": "Months"
+      },
+      {
+        "id": 3,
+        "code": "interestChartPeriodType.years",
+        "value": "Years"
+      }
+    ]
+  }
+}
+			</code>
+        </div>
+    </div>
+
+    <a id="rdproducts_update" name="rdproducts_update" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>Update a Recurring Deposit Product</h4>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">PUT https://Domain Name/api/v1/recurringdepositproducts/{productId}</code>
+			<code class="method-request">POST recurringdepositproducts/1
+Content-Type: application/json
+Request Body:
+{
+  "description": "Recurring deposit product new offerings",
+  "locale": "en",
+  "minDepositTerm":5,
+  "minDepositTermTypeId":1,
+}
+			</code>
+			<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {
+    "description": "Recurring deposit product new offerings",
+    "minDepositTerm": 5
+  }
+}
+			</code>
+        </div>
+    </div>
+
+    <a id="rdproducts_delete" name="rdproducts_delete" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+		<div class="method-description">
+			<h4>Delete a Recurring Deposit Product</h4>
+		</div>
+		<div class="method-example">
+			<code class="method-declaration">
+DELETE https://Domain Name/api/v1/recurringdepositdproducts/{productId}
+			</code>
+			<code class="method-request">
+DELETE recurringdepositdproducts/1
+Content-Type: application/json
+			</code>
+			<code class="method-response">
+{
+	"resourceId": 1
+}
+			</code>
+		</div>
+	</div>
+
+	<a id="rdproducts_list" name="rdproducts_list" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h4>List Recuring Deposit Products</h4>
+            <p>Example Requests:</p>
+            <div class=apiClick>recurringdepositproducts</div>
+            <br><br>
+            <div class=apiClick>recurringdepositproducts?fields=name</div>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">GET https://Domain Name/api/v1/recurringdepositproducts</code>
+            <code class="method-response">
+[
+  {
+    "id": 3,
+    "name": "RD01",
+    "shortName": "RD01",
+    "description": "RD01",
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "inMultiplesOf": 1,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "recurringDepositFrequency":1,
+    "recurringDepositFrequencyType": {
+      "id": 2,
+      "code": "recurring.deposit.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    "preClosurePenalApplicable": false,
+    "minDepositTerm": 3,
+    "maxDepositTerm": 4,
+    "minDepositTermType": {
+      "id": 2,
+      "code": "deposit.term.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    "maxDepositTermType": {
+      "id": 3,
+      "code": "deposit.term.savingsPeriodFrequencyType.years",
+      "value": "Years"
+    },
+    "interestCompoundingPeriodType": {
+      "id": 4,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    "interestPostingPeriodType": {
+      "id": 4,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    "interestCalculationType": {
+      "id": 1,
+      "code": "savingsInterestCalculationType.dailybalance",
+      "value": "Daily Balance"
+    },
+    "interestCalculationDaysInYearType": {
+      "id": 365,
+      "code": "savingsInterestCalculationDaysInYearType.days365",
+      "value": "365 Days"
+    },
+    "accountingRule": {
+      "id": 1,
+      "code": "accountingRuleType.none",
+      "value": "NONE"
+    }
+  }
+]			</code>
+        </div>
+    </div>
+<!-- end of fixed deposit products api docs -->
+
+<!-- start of savings accounts api -->
+	<a id="savingsaccounts" name="savingsaccounts" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Savings Account:</h2>
+	        <p>Savings accounts are instances of a praticular savings product created for an individual or group. An application process around the creation of accounts is also supported.</p>
+
+	        <table class=matrixHeading>
+	        	<thead>
+	        		<tr class="matrixHeadingBG">
+	        			<td class="fineractHeading2">Starting State</td>
+	        			<td class="fineractHeading2">Action</td>
+	        			<td class="fineractHeading2">Resultant State</td>
+	        		</tr>
+	        	</thead>
+	        	<tbody>
+	        		<tr>
+	        			<td>-</td>
+	        			<td>Submit</td>
+	        			<td>Submitted and pending approval</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Submitted and pending approval</td>
+	        			<td>Reject</td>
+	        			<td>Closed - Rejected</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Submitted and pending approval</td>
+	        			<td>Withdrawn by Applicant</td>
+	        			<td>Closed - Withdrawn by Applicant</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Submitted and pending approval</td>
+	        			<td>Approve</td>
+	        			<td>Approved</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Approved</td>
+	        			<td>Undo Approval</td>
+	        			<td>Submitted and pending approval</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Approved</td>
+	        			<td>Activate</td>
+	        			<td>Active</td>
+	        		</tr>
+	        	</tbody>
+	        </table>
+	        <br/>
+	      	<table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Field Descriptions</div></td></tr>
+
+				<tr class=alt><td>clientId</td></tr>
+				<tr><td class=fielddesc>The client you are creating the savings account for. Either <i>clientId</i> or <i>groupId</i> must be provided.</td></tr>
+
+				<tr class=alt><td>groupId</td></tr>
+				<tr><td class=fielddesc>The group you are creating the savings account for. Either <i>clientId</i> or <i>groupId</i> must be provided.</td></tr>
+
+				<tr class=alt><td>productId</td></tr>
+				<tr><td class=fielddesc>The id of the product used for this savings account. The savings account <b>inherits the selected currency</b> of the product and <b>possibly other details if not overridden</b> in the savings account creation request.</td></tr>
+
+        		<tr class=alt><td>accountNo</td></tr>
+				<tr><td class=fielddesc>The account no. associated with this loan. Is auto generated if not provided at creation time.</td></tr>
+
+				<tr class=alt><td>externalId</td></tr>
+				<tr><td class=fielddesc>A place to put an external reference for this savings account useful in migrations e.g. The ID another system uses. If provided, it must be unique.</td></tr>
+
+				<tr class=alt><td>submittedOnDate</td></tr>
+				<tr><td class=fielddesc><i>submittedOnDate</i> must be provided when initially creating savings account application. <i>locale</i> and <i>dateFormat</i> parameters must be provided with this.</td></tr>
+
+				<tr class=alt><td>nominalAnnualInterestRate</td></tr>
+	            <tr><td class=fielddesc>The interest rate set for savings account e.g. <b>5</b>% Per year - It is always expressed as the Nominal APR.</td></tr>
+
+	            <tr class=alt><td>interestCompoundingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is compounded. 1=Daily, 4=Monthly (at end of month)</td></tr>
+
+	            <tr class=alt><td>interestPostingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is posted or credited to savings account. The actual crediting or posting transaction is date as occurring on the day after the end of the period.  4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec), 7=Annually (at end of calendar year 31st Dec)</td></tr>
+
+	            <tr class=alt><td>interestCalculationType</td></tr>
+	            <tr><td class=fielddesc>The interest calculation method used: 1=Daily Balance or 2=Average Daily Balance</td></tr>
+
+				<tr class=alt><td>interestCalculationDaysInYearType</td></tr>
+	            <tr><td class=fielddesc>The setting for number of days in year to use: 360=360 Days, 365=365 Days</td></tr>
+
+				<tr class=alt><td>minRequiredOpeningBalance</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, sets the minimum deposit amount required to open a savings account e.g. <b>2,000</b></td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequency</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequencyType</i> to indicate the length of time that the savings account is 'locked in' and withdrawals are not allowed. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequencyType</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequency</i> to indicate the length of time that the savings account is 'locked in' and withdrawals are not allowed. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            e.g. 6 <b>Months</b></td></tr>
+
+	            <tr class=alt><td>withdrawalFeeForTransfers</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: Used along with <i>withdrawalFeeAmount</i> to indicate whether the withdrawal fee should be applied on the account for account transfers .</td></tr>
+
+	            <tr class=alt><td>allowOverdraft</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, depending on the value mark the savings account as overdraft account </td></tr>
+
+				<tr class=alt><td>overdraftLimit</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, sets the maximum allowed overdraft amount for a  savings account e.g. <b>5,000</b> else set the limit as zero</td></tr>
+
+	        </table>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_template" name="savingsaccounts_template" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Savings Account Template:</h2>
+	            <p>This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:
+	                <ul>
+	                <li class=normalli>Field Defaults</li>
+	                <li class=normalli>Allowed Value Lists</li>
+	                </ul>
+	            </p>
+	        <h4>Arguments</h4>
+	        <dl class="argument-list">
+	            <dt>clientId</dt>
+	            <dd>Integer <span>mandatory</span></dd>
+	            <dt>productId</dt>
+	            <dd>Integer <span>optional</span></dd>
+	            <dd>If entered, productId, productName and selectedProduct fields are returned.</dd>
+	        </dl>
+	        <p>Example Requests: </p>
+	        <div class=apiClick>savingsaccounts/template?clientId=1</div>
+	        <br><br>
+	        <div class=apiClick>savingsaccounts/template?clientId=1&productId=1</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/savingsaccounts/template?clientId={clientId}</code>
+			<code class="method-response">
+{
+  "clientId": 1,
+  "clientName": "small business",
+  "productOptions": [
+    {
+      "id": 1,
+      "name": "Passbook Savings"
+    }
+  ]
+}
+			</code>
+	    </div>
+
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/savingsaccounts/template?clientId={clientId}&productId={productId}</code>
+			<code class="method-response">
+{
+  "clientId": 1,
+  "clientName": "small business",
+  "savingsProductId": 1,
+  "savingsProductName": "Passbook Savings",
+  "timeline": {},
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "nominalAnnualInterestRate": 5,
+  "interestCompoundingPeriodType": {
+    "id": 1,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+    "value": "Daily"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "productOptions": [
+    {
+      "id": 1,
+      "name": "Passbook Savings"
+    }
+  ],
+  "fieldOfficerOptions": [
+    {
+      "id": 3,
+      "firstname": "Mrs.",
+      "lastname": "loanofficerB1",
+      "displayName": "loanofficerB1, Mrs.",
+      "officeId": 2,
+      "officeName": "branch 1",
+      "isLoanOfficer": true
+    },
+    {
+      "id": 1,
+      "firstname": "Mr.",
+      "lastname": "loanofficerHO",
+      "displayName": "loanofficerHO, Mr.",
+      "officeId": 1,
+      "officeName": "branch 1",
+      "isLoanOfficer": true
+    }
+  ],
+  "interestCompoundingPeriodTypeOptions": [
+    {
+      "id": 1,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+      "value": "Daily"
+    },
+    {
+      "id": 4,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+      "value": "Monthly"
+    }
+  ],
+  "interestPostingPeriodTypeOptions": [
+    {
+      "id": 4,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    {
+      "id": 5,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.quarterly",
+      "value": "Quarterly"
+    },
+    {
+      "id": 7,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.annual",
+      "value": "Annually"
+    }
+  ],
+  "interestCalculationTypeOptions": [
+    {
+      "id": 1,
+      "code": "savingsInterestCalculationType.dailybalance",
+      "value": "Daily Balance"
+    },
+    {
+      "id": 2,
+      "code": "savingsInterestCalculationType.averagedailybalance",
+      "value": "Average Daily Balance"
+    }
+  ],
+  "interestCalculationDaysInYearTypeOptions": [
+    {
+      "id": 360,
+      "code": "savingsInterestCalculationDaysInYearType.days360",
+      "value": "360 Days"
+    },
+    {
+      "id": 365,
+      "code": "savingsInterestCalculationDaysInYearType.days365",
+      "value": "365 Days"
+    }
+  ],
+  "lockinPeriodFrequencyTypeOptions": [
+    {
+      "id": 0,
+      "code": "savings.lockin.savingsPeriodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "savings.lockin.savingsPeriodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "savings.lockin.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "savings.lockin.savingsPeriodFrequencyType.years",
+      "value": "Years"
+    }
+  ],
+  "withdrawalFeeTypeOptions": [
+    {
+      "id": 1,
+      "code": "savingsWithdrawalFeesType.flat",
+      "value": "Flat"
+    },
+    {
+      "id": 2,
+      "code": "savingsWithdrawalFeesType.percent.of.amount",
+      "value": "% of Amount"
+    }
+  ],
+  "chargeOptions": [
+    {
+      "id": 4,
+      "name": "Savings charge 1",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 200,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "chargepaymentmode.regular"
+      }
+    },
+    {
+      "id": 5,
+      "name": "Savings charge 2",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 300,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "chargepaymentmode.regular"
+      }
+    },
+    {
+      "id": 6,
+      "name": "Annual fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 50,
+      "chargeTimeType": {
+        "id": 6,
+        "code": "chargeTimeType.annualFee",
+        "value": "Annual Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 7,
+      "name": "Quarterly fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 5,
+      "chargeTimeType": {
+        "id": 7,
+        "code": "chargeTimeType.monthlyFee",
+        "value": "Monthly Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      },
+      "feeOnMonthDay": [
+        10,
+        5
+      ],
+      "feeInterval": 4
+    }
+  ]
+}
+			</code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_create" name="savingsaccounts_create" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Submit new savings application</h2>
+	        <br/>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields</div></td></tr>
+	            <tr class=alt><td>clientId or groupId, productId, submittedOnDate</td></tr>
+	        </table>
+	        <br/>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Optional Fields</div></td></tr>
+	            <tr class=alt><td>accountNo, externalId, fieldOfficerId</td></tr>
+	        </table>
+	        <br/>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Inherited from Product (if not provided)</div></td></tr>
+	            <tr class=alt><td>nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, allowOverdraft, overdraftLimit</td>
+	       		</tr>
+	        </table>
+	    </div>
+	    <div class="method-example">
+	    	<p>Minimal request: accountNo auto generated, remaining details inherited from savings product.</p>
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts</code>
+	        <code class="method-request">POST savingsaccount
+Content-Type: application/json
+Request Body:
+{
+  "clientId": 1,
+  "productId": 1,
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "submittedOnDate": "01 March 2011"
+}
+			</code>
+			<p>Minimal request: accountNo provided (must be unique), remaining details inherited from savings product.</p>
+			<code class="method-request">POST savingsaccount
+Content-Type: application/json
+Request Body:
+{
+  "clientId": 1,
+  "productId": 1,
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "submittedOnDate": "01 March 2011",
+  "accountNo": "SA000023",
+  "externalId": "SYS-23"
+}
+			</code>
+			<p>Full request: accountNo provided (must be unique), remaining details override details from savings product (except currency).</p>
+			<code class="method-request">POST savingsaccount
+Content-Type: application/json
+Request Body:
+{
+  "clientId": 1,
+  "productId": 1,
+  "fieldOfficerId": 1,
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "submittedOnDate": "01 March 2013",
+  "accountNo": "SA000023",
+  "externalId": "SYS-23",
+  "nominalAnnualInterestRate": "5.65",
+  "interestCompoundingPeriodType": 1,
+  "interestPostingPeriodType": 4,
+  "interestCalculationType": 1,
+  "interestCalculationDaysInYearType": 365,
+  "minRequiredOpeningBalance": "1,000",
+  "lockinPeriodFrequency": 6,
+  "lockinPeriodFrequencyType": 2,
+  "allowOverdraft":true,
+  "overdraftLimit":5000,
+  "charges":[{"id":"1"}]
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_approve" name="savingsaccounts_approve" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Approve savings application</h2>
+	        <p>Approves savings application so long as its in 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=approve</code>
+	        <code class="method-request">POST savingsaccount/1?command=approve
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "approvedOnDate": "01 March 2013"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 200,
+      "code": "savingsAccountStatusType.approved",
+      "value": "Approved",
+      "submittedAndPendingApproval": false,
+      "approved": true,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "approvedOnDate": "02 March 2013"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_undoapproval" name="savingsaccounts_undoapproval" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Undo approval savings application</h2>
+	        <p>Will move 'approved' savings application back to 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=undoApproval</code>
+	        <code class="method-request">POST savingsaccount/1?command=undoApproval
+Content-Type: application/json
+Request Body:
+{
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 100,
+      "code": "savingsAccountStatusType.submitted.and.pending.approval",
+      "value": "Submitted and pending approval",
+      "submittedAndPendingApproval": true,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false
+    },
+    "approvedOnDate": ""
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+<a id="savingsaccounts_assignSavingsOfficer" name="savingsaccounts_assignSavingsOfficer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Assign Savings Officer</h4>
+	        <p>Allows you to assign <i>Savings Officer</i> for existing Savings Account.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=assignSavingsOfficer</code>
+	        <code class="method-request">POST savingsaccounts/1?command=assignSavingsOfficer
+Content-Type: application/json
+Request Body:
+{
+	assignmentDate : "04 September 2014",
+	dateFormat : "dd MMMM yyyy",
+	fromSavingsOfficerId : "",
+	locale : "en",
+	toSavingsOfficerId : 2
+}
+			</code>
+	        <code class="method-response">
+{
+	"officeId":2,
+	"savingsId":8,
+	"resourceId":8,
+	"changes":{"savingsOfficerId":2}
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_unassignSavingsOfficer" name="savingsaccounts_unassignSavingsOfficer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Unassign Savings Officer</h4>
+	        <p>Allows you to unassign the <i>Savings Officer</i>.</p>
+	    </div>
+		<div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=unassignSavingsOfficer</code>
+	        <code class="method-request">POST savingsaccounts/1?command=unassignSavingsOfficer
+Content-Type: application/json
+Request Body:
+{
+	dateFormat : "dd MMMM yyyy",
+	locale : "en",
+	unassignedDate : "05 September 2014"
+}
+			</code>
+	        <code class="method-response">
+{
+	"officeId":2,
+	"clientId":8,
+	"resourceId":8,
+	"changes":{}
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_reject" name="savingsaccounts_reject" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Reject savings application</h2>
+	        <p>Rejects savings application so long as its in 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=reject</code>
+	        <code class="method-request">POST savingsaccount/1?command=reject
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "rejectedOnDate": "03 March 2013"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 500,
+      "code": "savingsAccountStatusType.rejected",
+      "value": "Rejected",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": true,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": true
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "rejectedOnDate": "03 March 2013",
+    "closedOnDate": "03 March 2013"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_withdrawbyapplicant" name="savingsaccounts_withdrawbyapplicant" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Withdraw savings application</h2>
+	        <p>Used when an applicant withdraws from the savings application. It must be in 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=withdrawnByApplicant</code>
+	        <code class="method-request">POST savingsaccount/1?command=withdrawnByApplicant
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "withdrawnOnDate": "03 March 2013"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 400,
+      "code": "savingsAccountStatusType.withdrawn.by.applicant",
+      "value": "Withdrawn by applicant",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": true,
+      "active": false,
+      "closed": true
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "withdrawnOnDate": "03 March 2013",
+    "closedOnDate": "03 March 2013"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_activate" name="savingsaccounts_activate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Activate a savings account</h2>
+	        <p>Results in an approved savings application being converted into an 'active' savings account.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=activate</code>
+	        <code class="method-request">POST savingsaccount/1?command=activate
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "activatedOnDate": "01 March 2013"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 300,
+      "code": "savingsAccountStatusType.active",
+      "value": "Active",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": true,
+      "closed": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "activatedOnDate": "01 March 2013"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_close" name="savingsaccounts_close" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Close a savings account</h2>
+	        <p>Results in an Activated savings application being converted into an 'closed' savings account.</p>
+            <p><i>closedOnDate</i> is closure date of savings account</p>
+            <p><i>withdrawBalance</i> is a boolean value, true value of this field performs a withdrawal transaction with account's running balance.</p>
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG">
+                    <td><div class="fineractHeading2">Mandatory Fields</div></td>
+                </tr>
+                <tr class=alt>
+                    <td>dateFormat,locale,closedOnDate</td>
+                </tr>
+            </table>
+            <br />
+            <table class=matrixHeading>
+                <tr class="matrixHeadingBG">
+                    <td><div class="fineractHeading2">Optional Fields</div></td>
+                </tr>
+                <tr class=alt>
+                    <td>note, <i>withdrawBalance</i>, paymentTypeId, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber</td>
+                </tr>
+            </table>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=close</code>
+	        <code class="method-request">POST savingsaccount/5?command=close
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat":"dd MMMM yyyy",
+  "locale":"en",
+  "closedOnDate":"26 August 2013",
+  "note":"close note"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId":1,
+  "clientId":1,
+  "savingsId":5,
+  "resourceId":5,
+  "changes":{
+   "status":{
+    "id":600,
+    "code":"savingsAccountStatusType.closed",
+    "value":"Closed",
+    "submittedAndPendingApproval":false,
+    "approved":false,
+    "rejected":false,
+    "withdrawnByApplicant":false,
+    "active":false,
+    "closed":true
+    },
+  "locale":"en",
+  "dateFormat":"dd MMMM yyyy",
+  "closedOnDate":"26 August 2013",
+  "note":"close note"
+  }
+}
+	        </code>
+			<code class="method-request">POST savingsaccount/5?command=close
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat":"dd MMMM yyyy",
+  "locale":"en",
+  "closedOnDate":"26 August 2013",
+  "note":"close note",
+  "withdrawBalance":true,
+  "paymentTypeId": "14",
+  "accountNumber": "acc123",
+  "checkNumber": "che123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId":1,
+  "clientId":1,
+  "savingsId":5,
+  "resourceId":5,
+  "changes":{
+    "accountNumber": "acc123",
+    "checkNumber": "che123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123",
+    "status":{
+     "id":600,
+     "code":"savingsAccountStatusType.closed",
+     "value":"Closed",
+     "submittedAndPendingApproval":false,
+     "approved":false,
+     "rejected":false,
+     "withdrawnByApplicant":false,
+     "active":false,
+     "closed":true
+    },
+   "locale":"en",
+   "dateFormat":"dd MMMM yyyy",
+   "closedOnDate":"26 August 2013",
+   "note":"close note"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_calculate_interest" name="savingsaccounts_calculate_interest" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Calculate Interest on Savings Account</h2>
+	        <p>
+	        	Calculates interest earned on a savings account based on todays date. It does not attempt to <b>post or credit</b> the interest on the account. That is responsibility of the <a href="#savingsaccounts_post_interest" >Post Interest API</a> that will likely be called by overnight process.
+	        </p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=calculateInterest</code>
+	        <code class="method-request">POST savingsaccount/1?command=calculateInterest
+Content-Type: application/json
+Request Body:
+{
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_post_interest" name="savingsaccounts_post_interest" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Post Interest on Savings Account</h2>
+	        <p>
+	        	Calculates and Posts interest earned on a savings account based on todays date and whether an interest posting or crediting event is due.
+	        </p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=postInterest</code>
+	        <code class="method-request">POST savingsaccount/1?command=postInterest
+Content-Type: application/json
+Request Body:
+{
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_list" name="savingsaccounts_list" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h2>List savings applications/accounts</h2>
+            <p>Example Requests:</p>
+            <div class=apiClick>savingsaccounts</div>
+            <br><br>
+            <div class=apiClick>savingsaccounts?fields=name</div>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">GET https://Domain Name/api/v1/savingsaccounts</code>
+            <code class="method-response">
+{
+  "totalFilteredRecords": 1,
+  "pageItems": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "clientId": 1,
+      "clientName": "small business",
+      "savingsProductId": 1,
+      "savingsProductName": "Passbook Savings",
+      "fieldOfficerId": 0,
+      "status": {
+        "id": 100,
+        "code": "savingsAccountStatusType.submitted.and.pending.approval",
+        "value": "Submitted and pending approval",
+        "submittedAndPendingApproval": true,
+        "approved": false,
+        "rejected": false,
+        "withdrawnByApplicant": false,
+        "active": false,
+        "closed": false
+      },
+      "timeline": {
+        "submittedOnDate": [
+          2013,
+          3,
+          1
+        ]
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "nominalAnnualInterestRate": 5,
+      "interestCompoundingPeriodType": {
+        "id": 1,
+        "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+        "value": "Daily"
+      },
+      "interestPostingPeriodType": {
+        "id": 4,
+        "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+        "value": "Monthly"
+      },
+      "interestCalculationType": {
+        "id": 1,
+        "code": "savingsInterestCalculationType.dailybalance",
+        "value": "Daily Balance"
+      },
+      "interestCalculationDaysInYearType": {
+        "id": 365,
+        "code": "savingsInterestCalculationDaysInYearType.days365",
+        "value": "365 Days"
+      },
+      "summary": {
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        },
+        "accountBalance": 0
+      }
+    }
+  ]
+}
+			</code>
+        </div>
+    </div>
+
+	<a id="savingsaccounts_retrieve" name="savingsaccounts_retrieve" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve a savings application/account:</h2>
+	        <h4>Arguments</h4>
+	        <dl class="argument-list">
+	            <dt>associations</dt>
+	            <dd>optional, <span>Either 'all' or a comma separated list of savings 'associations' (itemized below).</span></dd>
+	            <dd><br>Associations are just extra pieces of data that you might or might not want to retrieve.<br><br></dd>
+	            <dd><b>'all':</b> Gets data related to all associations e.g. ?associations=all.</dd>
+				<dd><b>'transactions':</b> Gets data related to transactions on the account e.g. ?associations=transactions</dd>
+				<dd><b>'charges':</b>Savings Account charges data.</dd>
+	        </dl>
+	        <p>Example Requests : </p>
+	        <div class=apiClick>savingsaccounts/1</div>
+	        <br><br>
+	        <div class=apiClick>savingsaccounts/1?associations=all</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/savingsaccounts/{accountId}</code>
+	        <code class="method-response">
+{
+  "id": 1,
+  "accountNo": "000000001",
+  "clientId": 1,
+  "clientName": "small business",
+  "savingsProductId": 1,
+  "savingsProductName": "Passbook Savings",
+  "fieldOfficerId": 0,
+  "status": {
+    "id": 100,
+    "code": "savingsAccountStatusType.submitted.and.pending.approval",
+    "value": "Submitted and pending approval",
+    "submittedAndPendingApproval": true,
+    "approved": false,
+    "rejected": false,
+    "withdrawnByApplicant": false,
+    "active": false,
+    "closed": false
+  },
+  "timeline": {
+    "submittedOnDate": [
+      2013,
+      3,
+      1
+    ]
+  },
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "nominalAnnualInterestRate": 5,
+  "interestCompoundingPeriodType": {
+    "id": 1,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+    "value": "Daily"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "summary": {
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "accountBalance": 0
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_update" name="savingsaccounts_update" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Modify a savings application</h2>
+	        <p>Savings application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">PUT https://Domain Name/api/v1/savingsaccounts/{accountsId}</code>
+	        <code class="method-request">PUT savingsaccounts/1
+Content-Type: application/json
+No Request Body:
+{
+	"locale": "en",
+	"nominalAnnualInterestRate": "5.9999999999"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "nominalAnnualInterestRate": 5.9999999999,
+    "locale": "en"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_delete" name="savingsaccounts_delete" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Delete a savings application</h2>
+	        <p>At present we support <b>hard</b> delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">DELETE https://Domain Name/api/v1/savingsaccounts/{accountsId}</code>
+	        <code class="method-request">DELETE savingsaccounts/1
+Content-Type: application/json
+No Request Body:
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_transactions" name="savingsaccounts_transactions" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Savings Account Transactions:</h2>
+	        <p>Transactions possible on a savings account.</p>
+	      	<table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Field Descriptions</div></td></tr>
+
+				<tr class=alt><td>transactionDate</td></tr>
+				<tr><td class=fielddesc>The date of the transaction.</td></tr>
+
+				<tr class=alt><td>transactionAmount</td></tr>
+				<tr><td class=fielddesc>The amount of the transaction.</td></tr>
+	        </table>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_transactions_template" name="savingsaccounts_transactions_template" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Savings Account Transaction Template:</h2>
+	            <p>This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:
+	                <ul>
+	                <li class=normalli>Field Defaults</li>
+	                <li class=normalli>Allowed Value Lists</li>
+	                </ul>
+	            </p>
+	        <p>Example Requests: </p>
+	        <div class=apiClick>savingsaccounts/1/transactions/template</div>
+	        <br/>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/savingsaccounts/{accountId}/transactions/template
+	        </code>
+			<code class="method-response">
+{
+  "accountId": 1,
+  "accountNo": "000000001",
+  "date": [
+    2013,
+    5,
+    27
+  ],
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "paymentTypeOptions": [
+    {
+      "id": 14,
+      "name": "Wire Transfer",
+      "position": 0
+    },
+    {
+      "id": 13,
+      "name": "Cash",
+      "position": 1
+    }
+  ]
+}
+			</code>
+	    </div>
+	</div>
+
+<a id="savingsaccounts_transaction" name="savingsaccounts_transaction" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Savings Account Transaction:</h2>
+	            <p>This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:
+	                <ul>
+	                <li class=normalli>Field Defaults</li>
+	                <li class=normalli>Allowed Value Lists</li>
+	                </ul>
+	            </p>
+	        <p>Example Requests: </p>
+	        <div class=apiClick>savingsaccounts/1/transactions/1</div>
+	        <br/>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/savingsaccounts/{accountId}/transactions/{transactionId}
+	        </code>
+			<code class="method-response">
+{
+  "id": 1,
+  "transactionType": {
+    "id": 2,
+    "code": "savingsAccountTransactionType.withdrawal",
+    "value": "Withdrawal",
+    "deposit": false,
+    "withdrawal": true,
+    "interestPosting": false,
+    "feeDeduction": false
+  },
+  "accountId": 1,
+  "accountNo": "000000001",
+  "date": [
+    2013,
+    8,
+    7
+  ],
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "inMultiplesOf": 0,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "paymentDetailData": {
+    "id": 62,
+    "paymentType": {
+      "id": 11,
+      "name": "cash"
+    },
+    "accountNumber": "",
+    "checkNumber": "",
+    "routingCode": "",
+    "receiptNumber": "",
+    "bankNumber": ""
+  },
+  "amount": 5000,
+  "runningBalance": 0,
+  "reversed": true
+}
+</code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_deposit" name="savingsaccounts_deposit" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Deposit Transaction</h2>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{accountsId}/transactions?command=deposit</code>
+	        <code class="method-request">POST savingsaccounts/1/transactions?command=deposit
+Content-Type: application/json
+No Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "transactionDate": "27 May 2013",
+  "transactionAmount": "500",
+  "paymentTypeId": "14",
+  "accountNumber": "acc123",
+  "checkNumber": "che123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 2,
+  "savingsId": 1,
+  "resourceId": 47,
+  "changes": {
+    "accountNumber": "acc123",
+    "checkNumber": "che123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_withdrawal" name="savingsaccounts_withdrawal" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Withdrawal Transaction</h2>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{accountsId}/transactions?command=withdrawal</code>
+	        <code class="method-request">POST savingsaccounts/1/transactions?command=withdrawal
+Content-Type: application/json
+No Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "transactionDate": "27 May 2013",
+  "transactionAmount": "500",
+  "paymentTypeId": "14",
+  "accountNumber": "acc123",
+  "checkNumber": "che123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 2,
+  "savingsId": 1,
+  "resourceId": 48,
+  "changes": {
+    "accountNumber": "acc123",
+    "checkNumber": "che123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="savingsaccounts_adjusttransaction" name="savingsaccounts_adjusttransaction" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Adjust Transaction</h2>
+	        <p>This command modifies the given transaction.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{accountsId}/transactions/{transactionId}?command=modify</code>
+	        <code class="method-request">POST savingsaccounts/1/transactions/1?command=modify
+Content-Type: application/json
+No Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "transactionDate": "27 May 2013",
+  "transactionAmount": "500",
+  "paymentTypeId": "14",
+  "accountNumber": "acc123",
+  "checkNumber": "che123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 2,
+  "savingsId": 1,
+  "resourceId": 48,
+  "changes": {
+    "accountNumber": "acc123",
+    "checkNumber": "che123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+
+	<a id="savingsaccounts_undotransaction" name="savingsaccounts_undotransaction" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Undo transaction</h2>
+	        <p>This command reverses the given transaction.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{accountsId}/transactions/{transactionId}?command=undo</code>
+	        <code class="method-request">POST savingsaccounts/1/transactions/1?command=undo
+Content-Type: application/json
+No Request Body:
+{
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 2,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+<!-- end of savings accounts api -->
+
+<!-- start of Savings charges content-->
+			<a id="savings_charges" name="savings_charges" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Savings Charges</h3>
+					<p>Its typical for MFIs to add maintenance and operating charges. They can be either Fees or Penalties.
+					</p>
+					<p>Savings Charges are instances of <a href="#charges">Charges</a> and
+						represent either fees and penalties for savings products. Refer <a href="#charges">Charges</a>
+						for documentation of the various properties of a charge, Only additional properties (
+						specific to the context of a Charge being associated with a Savings account) are
+						described here</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>amountPaid</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The Total amount which has been paid for this Charge
+						</td>
+						</tr>
+						<tr class=alt>
+							<td>amountWaived</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The Total amount that has been waived for this Charge
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>amountWrittenOff</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Total amount written off from this Charge
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>amountOutstanding</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The Total outstanding amount for this Charge
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="savings_charges_list" name="savings_charges_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Savings Charges</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>savingsaccounts/1/charges</div>
+					<br>
+					<div class=apiClick>savingsaccounts/1/charges?chargeStatus=all</div>
+					<br>
+					<div class=apiClick>savingsaccounts/1/charges?chargeStatus=inactive</div>
+					<br>
+					<div class=apiClick>savingsaccounts/1/charges?chargeStatus=active</div>
+					<br>
+					<div class=apiClick>savingsaccounts/1/charges?fields=name,amountOrPercentage</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/savingsaccounts/{accountId}/charges
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "chargeId": 3,
+    "accountId": 57,
+    "name": "Savings account maintenance fee",
+    "chargeTimeType": {
+      "id": 1,
+      "code": "chargeTimeType.specifiedDueDate",
+      "value": "Specified due date"
+    },
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 100,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 100,
+    "amountOrPercentage": 100,
+    "penalty": false
+  },
+  {
+    "id": 2,
+    "chargeId": 4,
+    "accountId": 57,
+    "name": "Pass book Fee",
+    "chargeTimeType": {
+      "id": 2,
+      "code": "chargeTimeType.specifiedDueDate",
+      "value": "Specified due date"
+    },
+    "dueDate": [
+      2013,
+      3,
+      29
+    ],
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 100,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 100,
+    "amountOrPercentage": 100,
+    "penalty": false
+  },
+  {
+    "id": 9,
+    "chargeId": 4,
+    "accountId": 57,
+    "name": "Withdrawal fee percentage",
+    "chargeTimeType": {
+      "id": 5,
+      "code": "chargeTimeType.withdrawalFee",
+      "value": "Withdrawal Fee"
+    },
+    "chargeCalculationType": {
+      "id": 2,
+      "code": "chargeCalculationType.percent.of.amount",
+      "value": "% Amount"
+    },
+    "percentage": 0.25,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 0,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 0,
+    "amountOrPercentage": 0.25,
+    "penalty": false
+  },
+  {
+    "id": 10,
+    "chargeId": 6,
+    "accountId": 57,
+    "name": "Annual fee - INR",
+    "chargeTimeType": {
+      "id": 6,
+      "code": "chargeTimeType.annualFee",
+      "value": "Annual Fee"
+    },
+    "feeOnMonthDay": [
+      10,
+      9
+    ],
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 50,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 50,
+    "amountOrPercentage": 50,
+    "penalty": false
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="savings_charges_template" name="savings_charges_template"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Savings Charges Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>savingsaccounts/1/charges/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/savingsaccounts/{accountId}/charges/template
+					</code>
+					<code class="method-response">
+{
+  "amountPaid": 0,
+  "amountWaived": 0,
+  "amountWrittenOff": 0,
+  "chargeOptions": [
+    {
+      "id": 2,
+      "name": "Passbook Fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 100,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      }
+    },
+    {
+      "id": 3,
+      "name": "Chequebook fee",
+      "active": true,
+      "penalty": true,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 1,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      }
+    },
+    {
+      "id": 6,
+      "name": "Annual fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 50,
+      "chargeTimeType": {
+        "id": 6,
+        "code": "chargeTimeType.annualFee",
+        "value": "Annual Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    }
+  ],
+  "penalty": false
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="savings_charges_retrieve" name="savings_charges_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Savings account Charge</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>/savingsaccounts/1/charges/5</div>
+					<br>
+					<br>
+					<div class=apiClick>/savingsaccounts/1/charges/5?fields=name,amountOrPercentage</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "chargeId": 1,
+  "name": "Passbook fee",
+  "chargeTimeType": {
+    "id": 1,
+    "code": "chargeTimeType.specifiedDueDate",
+    "value": "Specified due date"
+  },
+  "chargeCalculationType": {
+    "id": 1,
+    "code": "chargeCalculationType.flat",
+    "value": "Flat"
+  },
+  "percentage": 0,
+  "amountPercentageAppliedTo": 0,
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 100,
+  "amountPaid": 0,
+  "amountWaived": 0,
+  "amountWrittenOff": 0,
+  "amountOutstanding": 100,
+  "amountOrPercentage": 100,
+  "penalty": false
+}			</code>
+				</div>
+			</div>
+
+			<a id="savings_charges_create" name="savings_charges_create" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Create a Savings account Charge</h4>
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields
+								for Savings account Charges</div></td>
+						</tr>
+						<tr class=alt>
+							<td>chargeId, amount</td>
+						</tr>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields
+								for Savings account Charges of Specified due date type</div></td>
+						</tr>
+						<tr class=alt>
+							<td>chargeId, amount, dueDate, dateFormat, locale</td>
+						</tr>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields
+								for Savings account Charges of Annual fee type</div></td>
+						</tr>
+						<tr class=alt>
+							<td>chargeId, amount, feeOnMonthDay, monthDayFormat, locale</td>
+						</tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/savingsaccounts/{accountId}/charges
+                    </code>
+                    <code class="method-request">
+POST /savingsaccounts/1/charges
+Content-Type: application/json
+Request Body:
+{
+  "chargeId": "2",
+  "locale": "en",
+  "amount": "100",
+  "dateFormat": "dd MMMM yyyy",
+  "dueDate": "29 April 2013"
+}
+                    </code>
+                    <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 6
+}
+	                </code>
+	                <code class="method-request">
+POST /savingsaccounts/1/charges
+Content-Type: application/json
+Request Body:
+{
+  "chargeId": "2",
+  "locale": "en",
+  "amount": "25",
+  "monthDayFormat": "dd MMMM",
+  "feeOnMonthDay": "09 October"
+}
+				</code>
+                <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 7
+}
+				</code>
+                </div>
+            </div>
+
+            <a id="savings_charges_update" name="savings_charges_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Savings account Charge</h4>
+					<p>Currently Savings account Charges may be updated only if the Savings account
+						is not yet approved.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}
+					</code>
+					<code class="method-request">
+PUT savingsaccounts/1/charges/6
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat": "dd MMMM yyyy",
+  "locale": "en",
+  "amount": "60",
+  "dueDate": "27 March 2013"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 6,
+  "changes": {
+    "dueDate": "27 March 2013",
+    "dateFormat": "dd MMMM yyyy",
+    "locale": "en",
+    "amount": 60.0
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="savings_charges_delete" name="savings_charges_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Savings account Charge</h4>
+					<p>
+						<b>Note:</b>Currently, A Savings account Charge may only be removed from Savings that are not
+						 yet approved.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}
+					</code>
+					<code class="method-request">
+DELETE savingsaccounts/1/charges/2
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 2
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="savings_charges_pay" name="savings_charges_pay" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Pay a Savings account Charge</h4>
+					<p>
+						An active charge will be paid when savings account is active and having sufficient balance.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=paycharge
+					</code>
+					<code class="method-request">
+POST savingsaccounts/1/charges/2?command=paycharge
+Content-Type: application/json
+Request Body:
+{
+  "dateFormat": "dd MMMM yyyy",
+  "locale": "en",
+  "amount": "60",
+  "dueDate": "12 September 2013"
+}
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 2
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="savings_charges_waive" name="savings_charges_waive" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Waive off a Savings account Charge</h4>
+					<p>
+						Outstanding charge amount will be waived off.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=waive
+					</code>
+					<code class="method-request">
+POST savingsaccounts/1/charges/2?command=waive
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 2
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="savings_charges_inactivate" name="savings_charges_inactivate" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Inactivate a Savings account Charge</h4>
+					<p>
+						A charge will be allowed to inactivate when savings account is active and not having any dues as of today. If charge is overpaid, corresponding charge payment transactions will be reversed.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=inactivate
+					</code>
+					<code class="method-request">
+POST savingsaccounts/1/charges/2?command=inactivate
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 2
+}
+					</code>
+				</div>
+			</div>
+
+<!-- End of Savings account charges -->
+
+<!-- start of fixed deposit accounts api -->
+	<a id="fdaccounts" name="fdaccounts" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Fixed Deposit Account:</h2>
+	        <p>Fixed Deposit accounts are instances of a praticular fixed deposit product created. An application process around the creation of accounts is also supported.</p>
+
+	        <table class=matrixHeading>
+	        	<thead>
+	        		<tr class="matrixHeadingBG">
+	        			<td class="fineractHeading2">Starting State</td>
+	        			<td class="fineractHeading2">Action</td>
+	        			<td class="fineractHeading2">Resultant State</td>
+	        		</tr>
+	        	</thead>
+	        	<tbody>
+	        		<tr>
+	        			<td>-</td>
+	        			<td>Submit</td>
+	        			<td>Submitted and pending approval</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Submitted and pending approval</td>
+	        			<td>Reject</td>
+	        			<td>Closed - Rejected</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Submitted and pending approval</td>
+	        			<td>Withdrawn by Applicant</td>
+	        			<td>Closed - Withdrawn by Applicant</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Submitted and pending approval</td>
+	        			<td>Approve</td>
+	        			<td>Approved</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Approved</td>
+	        			<td>Undo Approval</td>
+	        			<td>Submitted and pending approval</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Approved</td>
+	        			<td>Activate</td>
+	        			<td>Active</td>
+	        		</tr>
+	        	</tbody>
+	        </table>
+	        <br/>
+	      	<table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Field Descriptions</div></td></tr>
+
+				<tr class=alt><td>clientId</td></tr>
+				<tr><td class=fielddesc>The client you are creating the fixed deposit account for. Either <i>clientId</i> or <i>groupId</i> must be provided.</td></tr>
+
+				<tr class=alt><td>groupId</td></tr>
+				<tr><td class=fielddesc>The group you are creating the fixed deposit account for. Either <i>clientId</i> or <i>groupId</i> must be provided.</td></tr>
+
+				<tr class=alt><td>productId</td></tr>
+				<tr><td class=fielddesc>The id of the product used for this fixed deposit account. The fixed deposit account <b>inherits the selected currency</b> of the product and <b>possibly other details if not overridden</b> in the fixed deposit account creation request.</td></tr>
+
+        		<tr class=alt><td>accountNo</td></tr>
+				<tr><td class=fielddesc>The account no. associated with this fixed deposit. Is auto generated if not provided at creation time.</td></tr>
+
+				<tr class=alt><td>externalId</td></tr>
+				<tr><td class=fielddesc>A place to put an external reference for this fixed deposit account useful in migrations e.g. The ID another system uses. If provided, it must be unique.</td></tr>
+
+				<tr class=alt><td>submittedOnDate</td></tr>
+				<tr><td class=fielddesc><i>submittedOnDate</i> must be provided when initially creating fixed deposit account application. <i>locale</i> and <i>dateFormat</i> parameters must be provided with this.</td></tr>
+
+	            <tr class=alt><td>interestCompoundingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is compounded. 1=Daily, 4=Monthly (at end of month)</td></tr>
+
+	            <tr class=alt><td>interestPostingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is posted or credited to fixed deposit account. The actual crediting or posting transaction is date as occurring on the day after the end of the period.  4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec), 7=Annually (at end of calendar year 31st Dec)</td></tr>
+
+	            <tr class=alt><td>interestCalculationType</td></tr>
+	            <tr><td class=fielddesc>The interest calculation method used: 1=Daily Balance or 2=Average Daily Balance</td></tr>
+
+				<tr class=alt><td>interestCalculationDaysInYearType</td></tr>
+	            <tr><td class=fielddesc>The setting for number of days in year to use: 360=360 Days, 365=365 Days</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequency</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequencyType</i> to indicate the length of time that the fixed deposit account is 'locked in' and withdrawals are not allowed. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequencyType</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequency</i> to indicate the length of time that the fixed deposit account is 'locked in' and withdrawals are not allowed. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            e.g. 6 <b>Months</b></td></tr>
+
+	            <tr class=alt><td>depositAmount</td></tr>
+	            <tr><td class=fielddesc>The fixed deposit amount for which interest is provided on maturity.</td></tr>
+
+	            <tr class=alt><td>depositPeriod</td></tr>
+	            <tr><td class=fielddesc>Used along with <i>depositPeriodFrequencyId</i> to define term for which amount is deposited in fixed deposit. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>depositPeriodFrequencyId</td></tr>
+	            <tr>
+	            	<td class=fielddesc> Used along with <i>depositPeriod</i> to define term for which amount is deposited in fixed deposit. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 6 <b>Months</b>
+	            	</td>
+	            </tr>
+
+	            <tr class=alt><td>minDepositTerm</td></tr>
+	            <tr><td class=fielddesc>This is used along with <i>minDepositTermTypeId</i> to define allowed minimum deposit term for creating a fixed deposit account using this product. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>minDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc> Used along with <i>minDepositTerm</i> to define allowed minimum deposit term for creating a fixed deposit account using this product. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 6 <b>Months</b>
+	            	</td>
+	            </tr>
+
+	            <tr class=alt><td>maxDepositTerm</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>maxDepositTermTypeId</i> to define allowed maximum deposit term for creating a fixed deposit account using this product. e.g. <b>3</b> Years</td></tr>
+
+	            <tr class=alt><td>maxDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>maxDepositTerm</i> to define allowed maximum deposit term for creating a fixed deposit account using this product. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 3 <b>Years</b>
+	        		</td>
+	        	</tr>
+
+	            <tr class=alt><td>inMultiplesOfDepositTerm</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>inMultiplesOfDepositTermTypeId</i> to indicate the allowed deposit periods after minimum deposit period. e.g. If inMultiplesOfDepositTerm is <b>3</b> Months then the next allowed deposit period after minimum deposit of <b>6</b> Months should be <b>9</d> Months. </td>
+	            </tr>
+
+	            <tr class=alt><td>inMultiplesOfDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>inMultiplesOfDepositTerm</i> to indicate the allowed deposit periods after minimum deposit period. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. If inMultiplesOfDepositTerm is <b>3</b> Months then the next allowed deposit period after minimum deposit of <b>6</b> Months should be <b>9</d> Months.
+	        		</td>
+	        	</tr>
+
+	        	<tr class=alt><td>preClosurePenalApplicable</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: expects boolean value true/false, If provided as true then must provide <i>preClosurePenalInterest</i> and <i>preClosurePenalInterestOnTypeId</i>. </td>
+	            </tr>
+
+	            <tr class=alt><td>preClosurePenalInterest</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: Must provide when <i>preClosurePenalApplicable</i> is true. Used along with <i>preClosurePenalInterestOnTypeId</i> to apply a penalalty on top of  applicable interest rate for Pre-mature closure of accounts. e.g. “1%” means 1% less than the interest rate applicable.</td>
+	            </tr>
+
+	            <tr class=alt><td>preClosurePenalInterestOnTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: Must provide when <i>preClosurePenalApplicable</i> is true. Used along with <i>preClosurePenalInterest</i> to decide what should be the applicable interest rate to which penalalty can be applied on Pre-mature closure of accounts. 1=Whole Term, 2=Till Premature withdrawal e.g. “1%” means 1% less than the interest rate applicable till premature withdrawal.</td>
+	            </tr>
+
+	        </table>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_template" name="fdaccounts_template" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Fixed Deposit Account Template:</h2>
+	            <p>This is a convenience resource. It can be useful when building maintenance user interface screens for fixed deposit applications. The template data returned consists of any or all of:
+	                <ul>
+	                <li class=normalli>Field Defaults</li>
+	                <li class=normalli>Allowed Value Lists</li>
+	                </ul>
+	            </p>
+	        <h4>Arguments</h4>
+	        <dl class="argument-list">
+	            <dt>clientId</dt>
+	            <dd>Integer <span>mandatory</span></dd>
+	            <dt>productId</dt>
+	            <dd>Integer <span>optional</span></dd>
+	            <dd>If entered, productId, productName and selectedProduct fields are returned.</dd>
+	        </dl>
+	        <p>Example Requests: </p>
+	        <div class=apiClick>fixeddepositaccounts/template?clientId=1</div>
+	        <br><br>
+	        <div class=apiClick>fixeddepositaccounts/template?clientId=1&productId=1</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/fixeddepositaccounts/template?clientId={clientId}</code>
+			<code class="method-response">
+{
+  "clientId": 1,
+  "clientName": "small business",
+  "productOptions": [
+    {
+      "id": 1,
+      "name": "Passbook Savings"
+    }
+  ]
+}
+			</code>
+	    </div>
+
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/fixeddepositaccounts/template?clientId={clientId}&productId={productId}</code>
+			<code class="method-response">
+{
+  "clientId": 1,
+  "clientName": "small business",
+  "savingsProductId": 1,
+  "savingsProductName": "Passbook Savings",
+  "timeline": {},
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "nominalAnnualInterestRate": 5,
+  "interestCompoundingPeriodType": {
+    "id": 1,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+    "value": "Daily"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "productOptions": [
+    {
+      "id": 1,
+      "name": "Passbook Savings"
+    }
+  ],
+  "fieldOfficerOptions": [
+    {
+      "id": 3,
+      "firstname": "Mrs.",
+      "lastname": "loanofficerB1",
+      "displayName": "loanofficerB1, Mrs.",
+      "officeId": 2,
+      "officeName": "branch 1",
+      "isLoanOfficer": true
+    },
+    {
+      "id": 1,
+      "firstname": "Mr.",
+      "lastname": "loanofficerHO",
+      "displayName": "loanofficerHO, Mr.",
+      "officeId": 1,
+      "officeName": "branch 1",
+      "isLoanOfficer": true
+    }
+  ],
+  "interestCompoundingPeriodTypeOptions": [
+    {
+      "id": 1,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+      "value": "Daily"
+    },
+    {
+      "id": 4,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+      "value": "Monthly"
+    }
+  ],
+  "interestPostingPeriodTypeOptions": [
+    {
+      "id": 4,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    {
+      "id": 5,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.quarterly",
+      "value": "Quarterly"
+    },
+    {
+      "id": 7,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.annual",
+      "value": "Annually"
+    }
+  ],
+  "interestCalculationTypeOptions": [
+    {
+      "id": 1,
+      "code": "savingsInterestCalculationType.dailybalance",
+      "value": "Daily Balance"
+    },
+    {
+      "id": 2,
+      "code": "savingsInterestCalculationType.averagedailybalance",
+      "value": "Average Daily Balance"
+    }
+  ],
+  "interestCalculationDaysInYearTypeOptions": [
+    {
+      "id": 360,
+      "code": "savingsInterestCalculationDaysInYearType.days360",
+      "value": "360 Days"
+    },
+    {
+      "id": 365,
+      "code": "savingsInterestCalculationDaysInYearType.days365",
+      "value": "365 Days"
+    }
+  ],
+  "lockinPeriodFrequencyTypeOptions": [
+    {
+      "id": 0,
+      "code": "savings.lockin.savingsPeriodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "savings.lockin.savingsPeriodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "savings.lockin.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "savings.lockin.savingsPeriodFrequencyType.years",
+      "value": "Years"
+    }
+  ],
+  "withdrawalFeeTypeOptions": [
+    {
+      "id": 1,
+      "code": "savingsWithdrawalFeesType.flat",
+      "value": "Flat"
+    },
+    {
+      "id": 2,
+      "code": "savingsWithdrawalFeesType.percent.of.amount",
+      "value": "% of Amount"
+    }
+  ],
+  "chargeOptions": [
+    {
+      "id": 4,
+      "name": "Savings charge 1",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 200,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "chargepaymentmode.regular"
+      }
+    },
+    {
+      "id": 5,
+      "name": "Savings charge 2",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 300,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "chargepaymentmode.regular"
+      }
+    },
+    {
+      "id": 6,
+      "name": "Annual fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 50,
+      "chargeTimeType": {
+        "id": 6,
+        "code": "chargeTimeType.annualFee",
+        "value": "Annual Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 7,
+      "name": "Quarterly fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 5,
+      "chargeTimeType": {
+        "id": 7,
+        "code": "chargeTimeType.monthlyFee",
+        "value": "Monthly Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      },
+      "feeOnMonthDay": [
+        10,
+        5
+      ],
+      "feeInterval": 4
+    }
+  ]
+}
+			</code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_create" name="fdaccounts_create" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Submit new fixed deposit application</h2>
+	        <br/>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields</div></td></tr>
+	            <tr class=alt><td>clientId or groupId, productId, submittedOnDate, depositAmount, depositPeriod, depositPeriodFrequencyId</td></tr>
+	        </table>
+	        <br/>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Optional Fields</div></td></tr>
+	            <tr class=alt><td>accountNo, externalId, fieldOfficerId,linkAccountId(if provided initial deposit amount will be collected from this account),transferInterestToSavings(By enabling this flag all interest postings will be transferred to linked saving account )</td></tr>
+	        </table>
+	        <br/>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Inherited from Product (if not provided)</div></td></tr>
+	            <tr class=alt><td>interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, lockinPeriodFrequencyType, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnTypeId, charts</td>
+	       		</tr>
+	        </table>
+	    </div>
+	    <div class="method-example">
+	    	<p>Minimal request: accountNo auto generated, remaining details inherited from fixed deposit product.</p>
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts</code>
+	        <code class="method-request">POST fixeddepositaccount
+Content-Type: application/json
+Request Body:
+{
+  "clientId": 1,
+  "productId": 1,
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "submittedOnDate": "01 March 2014",
+  "depositAmount":"5000",
+  "depositPeriod":"6",
+  "depositPeriodFrequencyId":"2"
+}
+			</code>
+			<p>Minimal request: accountNo provided (must be unique), remaining details inherited from fixed deposit product.</p>
+			<code class="method-request">POST fixeddepositaccount
+Content-Type: application/json
+Request Body:
+{
+  "clientId": 1,
+  "productId": 1,
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "submittedOnDate": "01 March 2014",
+  "depositAmount":"5000",
+  "depositPeriod":"6",
+  "depositPeriodFrequencyId":"2",
+  "accountNo": "FD000023",
+  "externalId": "FD-23"
+}
+			</code>
+			<p>Full request: accountNo provided (must be unique), remaining details override details from fixed deposit product (except currency).</p>
+			<code class="method-request">POST savingsaccount
+Content-Type: application/json
+Request Body:
+{
+  "clientId": 1,
+  "productId": 1,
+  "fieldOfficerId": 1,
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "submittedOnDate": "01 March 2014",
+  "accountNo": "FD000024",
+  "externalId": "FD-24",
+  "interestCompoundingPeriodType": 1,
+  "interestPostingPeriodType": 4,
+  "interestCalculationType": 1,
+  "interestCalculationDaysInYearType": 365,
+  "depositAmount":"5000",
+  "depositPeriod":"6",
+  "depositPeriodFrequencyId":"2",
+  "lockinPeriodFrequency": 6,
+  "lockinPeriodFrequencyType": 2,
+  "charges":[{"id":"1"}]
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_approve" name="fdaccounts_approve" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Approve fixed deposit application</h2>
+	        <p>Approves fixed deposit application so long as its in 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{accountId}?command=approve</code>
+	        <code class="method-request">POST fixeddepositaccounts/1?command=approve
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "approvedOnDate": "01 March 2013"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 200,
+      "code": "savingsAccountStatusType.approved",
+      "value": "Approved",
+      "submittedAndPendingApproval": false,
+      "approved": true,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "approvedOnDate": "01 March 2013"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_undoapproval" name="fdaccounts_undoapproval" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Undo approval fixed deposit application</h2>
+	        <p>Will move 'approved' fixed deposit application back to 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{accountId}?command=undoApproval</code>
+	        <code class="method-request">POST fixeddepositaccounts/1?command=undoApproval
+Content-Type: application/json
+Request Body:
+{
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 100,
+      "code": "savingsAccountStatusType.submitted.and.pending.approval",
+      "value": "Submitted and pending approval",
+      "submittedAndPendingApproval": true,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false
+    },
+    "approvedOnDate": ""
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_reject" name="fdaccounts_reject" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Reject fixed deposit application</h2>
+	        <p>Rejects fixed deposit application so long as its in 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{accountId}?command=reject</code>
+	        <code class="method-request">POST fixeddepositaccounts/1?command=reject
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "rejectedOnDate": "03 March 2013"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 500,
+      "code": "savingsAccountStatusType.rejected",
+      "value": "Rejected",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": true,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": true
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "rejectedOnDate": "03 March 2013",
+    "closedOnDate": "03 March 2013"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_withdrawbyapplicant" name="fdaccounts_withdrawbyapplicant" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Withdraw fixed deposit application</h2>
+	        <p>Used when an applicant withdraws from the fixed deposit application. It must be in 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{savingsId}?command=withdrawnByApplicant</code>
+	        <code class="method-request">POST savingsaccount/1?command=withdrawnByApplicant
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "withdrawnOnDate": "03 March 2013"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 400,
+      "code": "savingsAccountStatusType.withdrawn.by.applicant",
+      "value": "Withdrawn by applicant",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": true,
+      "active": false,
+      "closed": true
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "withdrawnOnDate": "03 March 2013",
+    "closedOnDate": "03 March 2013"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_activate" name="fdaccounts_activate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Activate a fixed deposit account</h2>
+	        <p>Results in an approved fixed deposit application being converted into an 'active' fixed deposit account.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{accountId}?command=activate</code>
+	        <code class="method-request">POST savingsaccount/1?command=activate
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "activatedOnDate": "01 March 2013"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 300,
+      "code": "savingsAccountStatusType.active",
+      "value": "Active",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": true,
+      "closed": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "activatedOnDate": "01 March 2013"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_close" name="fdaccounts_close" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Close a fixed deposit account</h2>
+	        <p>Results in a Matured fixed deposit account being converted into a 'closed' fixed deposit account.</p> <p>On account close allowed actions are.</p>
+	         <table class=matrixHeading>
+	        	<thead>
+	        		<tr class="matrixHeadingBG">
+	        			<td class="fineractHeading2">Action on Close</td>
+	        			<td class="fineractHeading2">Result</td>
+	        		</tr>
+	        	</thead>
+	        	<tbody>
+	        		<tr>
+	        			<td>Withdraw Deposit &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+	        			<td>Matured amount withdrawn and paid to client</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Transfer to Savings</td>
+	        			<td>Matured amount transfered to specified savings account.</td>
+	        		</tr>
+	        		<tr>
+	        			<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+	        			<td>&nbsp;</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Re-Invest</td>
+	        			<td>Create new Fixed deposit application with matured amount as deposit amount.</td>
+	        		</tr>
+	        	</tbody>
+	        </table>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{accountId}?command=close</code>
+	        <code class="method-request">POST fixeddepositaccounts/1?command=close
+Content-Type: application/json
+Request Body:
+	{
+	"dateFormat":"dd MMMM yyyy",
+	"locale":"en",
+	"closedOnDate":"19 April 2014",
+	"note":"Closing and transfering amount to savings",
+	"onAccountClosureId":"200",
+	"toSavingsAccountId":1,
+	"transferDescription":"Transfered matured amount to savings account"
+	}
+			</code>
+	        <code class="method-response">
+{
+"officeId":1,
+"clientId":1,
+"savingsId":5,
+"resourceId":5,
+"changes":{
+	"status":{
+		"id":600,
+		"code":"savingsAccountStatusType.closed",
+		"value":"Closed",
+		"submittedAndPendingApproval":false,
+		"approved":false,
+		"rejected":false,
+		"withdrawnByApplicant":false,
+		"active":false,
+		"closed":true
+		},
+	"locale":"en",
+	"dateFormat":"dd MMMM yyyy",
+	"closedOnDate":"19 April 2014",
+	"note":"Closing and transfering amount to savings"
+	}
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_prematureclose" name="fdaccounts_prematureclose" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Premature Close a fixed deposit account</h2>
+	        <p>Results in an Active fixed deposit account being converted into a 'Premature Closed' fixed deposit account with options to withdraw prematured amount. (premature amount is calculated using interest rate chart applicable along with penal interest if any.)</p> <p>On account premature closure allowed actions are.</p>
+	         <table class=matrixHeading>
+	        	<thead>
+	        		<tr class="matrixHeadingBG">
+	        			<td class="fineractHeading2">Action on Premature Close</td>
+	        			<td class="fineractHeading2">Result</td>
+	        		</tr>
+	        	</thead>
+	        	<tbody>
+	        		<tr>
+	        			<td>Withdraw Deposit &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+	        			<td>Matured amount withdrawn and paid to client</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Transfer to Savings</td>
+	        			<td>Matured amount transfered to specified savings account.</td>
+	        		</tr>
+	        	</tbody>
+	        </table>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{accountId}?command=prematureClose</code>
+	        <code class="method-request">POST fixeddepositaccounts/1?command=prematureClose
+Content-Type: application/json
+Request Body:
+	{
+	"dateFormat":"dd MMMM yyyy",
+	"locale":"en",
+	"closedOnDate":"19 April 2014",
+	"note":"Close and transfer amount to savings",
+	"onAccountClosureId":"200",
+	"toSavingsAccountId":1,
+	"transferDescription":"Transfered matured amount to savings account"
+	}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 700,
+      "code": "savingsAccountStatusType.pre.mature.closure",
+      "value": "Premature Closed",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false,
+      "prematureClosed": true,
+      "transferInProgress": false,
+      "transferOnHold": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "closedOnDate": "19 April 2014",
+    "note": "Close and transfer amount to savings"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_calculate_premature_amount" name="fdaccounts_calculate_premature_amount" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Calculate Premature amount on Fixed deposit account</h2>
+	        <p>Calculate premature amount on fixed deposit account till premature close date. Premature amount is calculated based on interest chart and penal interest applicable.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{accountId}?command=calculatePrematureAmount</code>
+	        <code class="method-request">POST fixeddepositaccounts/1?command=calculatePrematureAmount
+Content-Type: application/json
+Request Body:
+	{
+	"dateFormat":"dd MMMM yyyy",
+	"locale":"en",
+	"closedOnDate":"19 April 2014"
+	}
+			</code>
+	        <code class="method-response">
+{
+  "maturityAmount": 100.65,
+  "savingsAccounts": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "clientId": 1,
+      "clientName": "Sangamesh N",
+      "savingsProductId": 1,
+      "savingsProductName": "FD-0001"
+    }
+  ],
+  "onAccountClosureOptions": [
+    {
+      "id": 100,
+      "code": "depositAccountClosureType.withdrawDeposit",
+      "value": "Withdra Deposit"
+    },
+    {
+      "id": 200,
+      "code": "depositAccountClosureType.transferToSavings",
+      "value": "Transfer to Savings"
+    },
+    {
+      "id": 300,
+      "code": "depositAccountClosureType.reinvest",
+      "value": "Re-Invest"
+    }
+  ],
+  "paymentTypeOptions": [],
+  "id": 12,
+  "depositType": {
+    "id": 300,
+    "code": "depositAccountType.recurringDeposit",
+    "value": "Recurring Deposit"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_calculate_interest" name="fdaccounts_calculate_interest" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Calculate Interest on Fixed Deposit Account</h2>
+	        <p>
+	        	Calculates interest earned on a fixed deposit account based on todays date. It does not attempt to <b>post or credit</b> the interest on the account. That is responsibility of the <a href="#fdaccounts_post_interest" >Post Interest API</a> that will likely be called by overnight process.
+	        </p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{accountId}?command=calculateInterest</code>
+	        <code class="method-request">POST fixeddepositaccount/1?command=calculateInterest
+Content-Type: application/json
+Request Body:
+{
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_post_interest" name="fdaccounts_post_interest" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Post Interest on Fixed Deposit Account</h2>
+	        <p>
+	        	Calculates and Posts interest earned on a fixed deposit account based on todays date and whether an interest posting or crediting event is due.
+	        </p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/fixeddepositaccounts/{accountId}?command=postInterest</code>
+	        <code class="method-request">POST fixeddepositaccount/1?command=postInterest
+Content-Type: application/json
+Request Body:
+{
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_list" name="fdaccounts_list" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h2>List Fixed deposit applications/accounts</h2>
+            <p>Example Requests:</p>
+            <div class=apiClick>fixeddepositaccounts</div>
+            <br><br>
+            <div class=apiClick>fixeddepositaccounts?fields=name</div>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">GET https://Domain Name/api/v1/fixeddepositaccounts</code>
+            <code class="method-response">
+[
+  {
+    "id": 1,
+    "accountNo": "000000001",
+    "clientId": 1,
+    "clientName": "Sangamesh N",
+    "savingsProductId": 3,
+    "savingsProductName": "FD01",
+    "fieldOfficerId": 0,
+    "status": {
+      "id": 100,
+      "code": "savingsAccountStatusType.submitted.and.pending.approval",
+      "value": "Submitted and pending approval",
+      "submittedAndPendingApproval": true,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false,
+      "prematureClosed": false,
+      "transferInProgress": false,
+      "transferOnHold": false
+    },
+    "timeline": {
+      "submittedOnDate": [
+        2014,
+        3,
+        1
+      ],
+      "submittedByUsername": "mifos",
+      "submittedByFirstname": "App",
+      "submittedByLastname": "Administrator"
+    },
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "inMultiplesOf": 1,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "interestCompoundingPeriodType": {
+      "id": 4,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    "interestPostingPeriodType": {
+      "id": 4,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    "interestCalculationType": {
+      "id": 1,
+      "code": "savingsInterestCalculationType.dailybalance",
+      "value": "Daily Balance"
+    },
+    "interestCalculationDaysInYearType": {
+      "id": 365,
+      "code": "savingsInterestCalculationDaysInYearType.days365",
+      "value": "365 Days"
+    },
+    "summary": {
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "inMultiplesOf": 1,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "accountBalance": 0
+    },
+    "interestFreePeriodApplicable": false,
+    "preClosurePenalApplicable": false,
+    "minDepositTerm": 3,
+    "maxDepositTerm": 4,
+    "minDepositTermType": {
+      "id": 2,
+      "code": "deposit.term.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    "maxDepositTermType": {
+      "id": 3,
+      "code": "deposit.term.savingsPeriodFrequencyType.years",
+      "value": "Years"
+    },
+    "depositAmount": 5000,
+    "maturityAmount": 5140.25,
+    "maturityDate": [
+      2014,
+      9,
+      1
+    ],
+    "depositPeriod": 6,
+    "depositPeriodFrequency": {
+      "id": 2,
+      "code": "deposit.period.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    }
+  }
+]
+
+			</code>
+        </div>
+    </div>
+
+    <a id="fdaccounts_retrieve" name="fdaccounts_retrieve" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve a fixed deposit application/account:</h2>
+	        <h4>Arguments</h4>
+	        <dl class="argument-list">
+	            <dt>associations</dt>
+	            <dd>optional, <span>Either 'all' or a comma separated list of fixed deposit 'associations' (itemized below).</span></dd>
+	            <dd><br>Associations are just extra pieces of data that you might or might not want to retrieve.<br><br></dd>
+	            <dd><b>'all':</b> Gets data related to all associations e.g. ?associations=all.</dd>
+				<dd><b>'transactions':</b> Gets data related to transactions on the account e.g. ?associations=transactions</dd>
+				<dd><b>'charges':</b>fixed deposit Account charges data.</dd>
+	        </dl>
+	        <p>Example Requests : </p>
+	        <div class=apiClick>fixeddepositaccounts/1</div>
+	        <br><br>
+	        <div class=apiClick>fixeddepositaccounts/1?associations=all</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/fixeddepositaccounts/{accountId}</code>
+	        <code class="method-response">
+{
+  "id": 1,
+  "accountNo": "FD000023",
+  "externalId": "FD-23",
+  "clientId": 1,
+  "clientName": "Sangamesh N",
+  "savingsProductId": 3,
+  "savingsProductName": "FD01",
+  "fieldOfficerId": 0,
+  "status": {
+    "id": 100,
+    "code": "savingsAccountStatusType.submitted.and.pending.approval",
+    "value": "Submitted and pending approval",
+    "submittedAndPendingApproval": true,
+    "approved": false,
+    "rejected": false,
+    "withdrawnByApplicant": false,
+    "active": false,
+    "closed": false,
+    "prematureClosed": false,
+    "transferInProgress": false,
+    "transferOnHold": false
+  },
+  "timeline": {
+    "submittedOnDate": [
+      2014,
+      3,
+      1
+    ],
+    "submittedByUsername": "mifos",
+    "submittedByFirstname": "App",
+    "submittedByLastname": "Administrator"
+  },
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "inMultiplesOf": 1,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "interestCompoundingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "interestFreePeriodApplicable": false,
+  "preClosurePenalApplicable": false,
+  "minDepositTerm": 3,
+  "maxDepositTerm": 4,
+  "minDepositTermType": {
+    "id": 2,
+    "code": "deposit.term.savingsPeriodFrequencyType.months",
+    "value": "Months"
+  },
+  "maxDepositTermType": {
+    "id": 3,
+    "code": "deposit.term.savingsPeriodFrequencyType.years",
+    "value": "Years"
+  },
+  "depositAmount": 5000,
+  "maturityAmount": 5140.25,
+  "maturityDate": [
+    2014,
+    9,
+    1
+  ],
+  "depositPeriod": 6,
+  "depositPeriodFrequency": {
+    "id": 2,
+    "code": "deposit.period.savingsPeriodFrequencyType.months",
+    "value": "Months"
+  },
+  "summary": {
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "inMultiplesOf": 1,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "accountBalance": 0
+  },
+  "accountChart": {
+    "id": 4,
+    "fromDate": [
+      2013,
+      10,
+      2
+    ],
+    "accountId": 5,
+    "accountNumber": "FD000023",
+    "chartSlabs": [
+      {
+        "id": 13,
+        "periodType": {
+          "id": 0,
+          "code": "interestChartPeriodType.days",
+          "value": "Days"
+        },
+        "fromPeriod": 181,
+        "toPeriod": 365,
+        "annualInterestRate": 5.5,
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        }
+      },
+      {
+        "id": 12,
+        "periodType": {
+          "id": 0,
+          "code": "interestChartPeriodType.days",
+          "value": "Days"
+        },
+        "fromPeriod": 1,
+        "toPeriod": 180,
+        "annualInterestRate": 5,
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        }
+      },
+      {
+        "id": 11,
+        "periodType": {
+          "id": 0,
+          "code": "interestChartPeriodType.days",
+          "value": "Days"
+        },
+        "fromPeriod": 366,
+        "annualInterestRate": 6,
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        }
+      }
+    ],
+    "periodTypes": [
+      {
+        "id": 0,
+        "code": "interestChartPeriodType.days",
+        "value": "Days"
+      },
+      {
+        "id": 1,
+        "code": "interestChartPeriodType.weeks",
+        "value": "Weeks"
+      },
+      {
+        "id": 2,
+        "code": "interestChartPeriodType.months",
+        "value": "Months"
+      },
+      {
+        "id": 3,
+        "code": "interestChartPeriodType.years",
+        "value": "Years"
+      }
+    ]
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_update" name="fdaccounts_update" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Modify a fixed deposit application</h2>
+	        <p>Fixed deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">PUT https://Domain Name/api/v1/fixeddepositaccounts/{accountId}</code>
+	        <code class="method-request">PUT fixeddepositaccounts/1
+Content-Type: application/json
+No Request Body:
+{
+	"locale": "en",
+	"depositAmount": 6000
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "depositAmount": 6000,
+    "locale": "en"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="fdaccounts_delete" name="fdaccounts_delete" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Delete a fixed deposit application</h2>
+	        <p>At present we support <b>hard</b> delete of fixed deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the fixed deposit account.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">DELETE https://Domain Name/api/v1/fixeddepositaccounts/{accountsId}</code>
+	        <code class="method-request">DELETE fixeddepositaccounts/1
+Content-Type: application/json
+No Request Body:
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+<!-- end of fixed deposit accounts api -->
+
+<!-- start of recurring deposit accounts api -->
+	<a id="rdaccounts" name="rdaccounts" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Recurring Deposit Account:</h2>
+	        <p>Recurring Deposit accounts are instances of a praticular recurring deposit product created. An application process around the creation of accounts is also supported.</p>
+
+	        <table class=matrixHeading>
+	        	<thead>
+	        		<tr class="matrixHeadingBG">
+	        			<td class="fineractHeading2">Starting State</td>
+	        			<td class="fineractHeading2">Action</td>
+	        			<td class="fineractHeading2">Resultant State</td>
+	        		</tr>
+	        	</thead>
+	        	<tbody>
+	        		<tr>
+	        			<td>-</td>
+	        			<td>Submit</td>
+	        			<td>Submitted and pending approval</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Submitted and pending approval</td>
+	        			<td>Reject</td>
+	        			<td>Closed - Rejected</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Submitted and pending approval</td>
+	        			<td>Withdrawn by Applicant</td>
+	        			<td>Closed - Withdrawn by Applicant</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Submitted and pending approval</td>
+	        			<td>Approve</td>
+	        			<td>Approved</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Approved</td>
+	        			<td>Undo Approval</td>
+	        			<td>Submitted and pending approval</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Approved</td>
+	        			<td>Activate</td>
+	        			<td>Active</td>
+	        		</tr>
+	        	</tbody>
+	        </table>
+	        <br/>
+	      	<table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Field Descriptions</div></td></tr>
+
+				<tr class=alt><td>clientId</td></tr>
+				<tr><td class=fielddesc>The client you are creating the recurring deposit account for. Either <i>clientId</i> or <i>groupId</i> must be provided.</td></tr>
+
+				<tr class=alt><td>groupId</td></tr>
+				<tr><td class=fielddesc>The group you are creating the recurring deposit account for. Either <i>clientId</i> or <i>groupId</i> must be provided.</td></tr>
+
+				<tr class=alt><td>productId</td></tr>
+				<tr><td class=fielddesc>The id of the product used for this recurring deposit account. The recurring deposit account <b>inherits the selected currency</b> of the product and <b>possibly other details if not overridden</b> in the recurring deposit account creation request.</td></tr>
+
+        		<tr class=alt><td>accountNo</td></tr>
+				<tr><td class=fielddesc>The account no. associated with this recurring deposit. Is auto generated if not provided at creation time.</td></tr>
+
+				<tr class=alt><td>externalId</td></tr>
+				<tr><td class=fielddesc>A place to put an external reference for this recurring deposit account useful in migrations e.g. The ID another system uses. If provided, it must be unique.</td></tr>
+
+				<tr class=alt><td>submittedOnDate</td></tr>
+				<tr><td class=fielddesc><i>submittedOnDate</i> must be provided when initially creating recurring deposit account application. <i>locale</i> and <i>dateFormat</i> parameters must be provided with this.</td></tr>
+
+	            <tr class=alt><td>interestCompoundingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is compounded. 1=Daily, 4=Monthly (at end of month)</td></tr>
+
+	            <tr class=alt><td>interestPostingPeriodType</td></tr>
+	            <tr><td class=fielddesc>The period at which interest rate is posted or credited to recurring deposit account. The actual crediting or posting transaction is date as occurring on the day after the end of the period.  4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec), 7=Annually (at end of calendar year 31st Dec)</td></tr>
+
+	            <tr class=alt><td>interestCalculationType</td></tr>
+	            <tr><td class=fielddesc>The interest calculation method used: 1=Daily Balance or 2=Average Daily Balance</td></tr>
+
+				<tr class=alt><td>interestCalculationDaysInYearType</td></tr>
+	            <tr><td class=fielddesc>The setting for number of days in year to use: 360=360 Days, 365=365 Days</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequency</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequencyType</i> to indicate the length of time that the recurring deposit account is 'locked in' and withdrawals are not allowed. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>lockinPeriodFrequencyType</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequency</i> to indicate the length of time that the recurring deposit account is 'locked in' and withdrawals are not allowed. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            e.g. 6 <b>Months</b></td></tr>
+
+	            <tr class=alt><td>recurringDepositAmount</td></tr>
+	            <tr><td class=fielddesc>The recurring deposit amount to be deposited regularly on a specified by deposit frequency.</td></tr>
+
+	            <tr class=alt><td>recurringDepositFrequency</td></tr>
+	            <tr><td class=fielddesc>Used along with <i>recurringDepositFrequencyTypeId</i> to define the frequency at which <i>recurringDepositAmount</i> to be deposited into RD account. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>recurringDepositFrequencyTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc> Used along with <i>recurringDepositFrequency</i> to define the frequency at which <i>recurringDepositAmount</i> to be deposited into RD account. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 6 <b>Months</b>
+	            	</td>
+	            </tr>
+
+	            <tr class=alt><td>depositPeriod</td></tr>
+	            <tr><td class=fielddesc>Used along with <i>depositPeriodFrequencyId</i> to define term for which amount is deposited in recurring deposit. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>depositPeriodFrequencyId</td></tr>
+	            <tr>
+	            	<td class=fielddesc> Used along with <i>depositPeriod</i> to define term for which amount is deposited in recurring deposit. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 6 <b>Months</b>
+	            	</td>
+	            </tr>
+
+	            <tr class=alt><td>minDepositTerm</td></tr>
+	            <tr><td class=fielddesc>This is used along with <i>minDepositTermTypeId</i> to define allowed minimum deposit term for creating a recurring deposit account using this product. e.g. <b>6</b> Months</td></tr>
+
+	            <tr class=alt><td>minDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc> Used along with <i>minDepositTerm</i> to define allowed minimum deposit term for creating a recurring deposit account using this product. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 6 <b>Months</b>
+	            	</td>
+	            </tr>
+
+	            <tr class=alt><td>maxDepositTerm</td></tr>
+	            <tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>maxDepositTermTypeId</i> to define allowed maximum deposit term for creating a recurring deposit account using this product. e.g. <b>3</b> Years</td></tr>
+
+	            <tr class=alt><td>maxDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>maxDepositTerm</i> to define allowed maximum deposit term for creating a recurring deposit account using this product. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. 3 <b>Years</b>
+	        		</td>
+	        	</tr>
+
+	            <tr class=alt><td>inMultiplesOfDepositTerm</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>inMultiplesOfDepositTermTypeId</i> to indicate the allowed deposit periods after minimum deposit period. e.g. If inMultiplesOfDepositTerm is <b>3</b> Months then the next allowed deposit period after minimum deposit of <b>6</b> Months should be <b>9</d> Months. </td>
+	            </tr>
+
+	            <tr class=alt><td>inMultiplesOfDepositTermTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: If provided, used along with <i>inMultiplesOfDepositTerm</i> to indicate the allowed deposit periods after minimum deposit period. 0=Days, 1=Weeks, 2=Months, 3=Years
+	            	e.g. If inMultiplesOfDepositTerm is <b>3</b> Months then the next allowed deposit period after minimum deposit of <b>6</b> Months should be <b>9</d> Months.
+	        		</td>
+	        	</tr>
+
+	        	<tr class=alt><td>preClosurePenalApplicable</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: expects boolean value true/false, If provided as true then must provide <i>preClosurePenalInterest</i> and <i>preClosurePenalInterestOnTypeId</i>. </td>
+	            </tr>
+
+	            <tr class=alt><td>preClosurePenalInterest</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: Must provide when <i>preClosurePenalApplicable</i> is true. Used along with <i>preClosurePenalInterestOnTypeId</i> to apply a penalalty on top of  applicable interest rate for Pre-mature closure of accounts. e.g. “1%” means 1% less than the interest rate applicable.</td>
+	            </tr>
+
+	            <tr class=alt><td>preClosurePenalInterestOnTypeId</td></tr>
+	            <tr>
+	            	<td class=fielddesc><b>Optional</b>: Must provide when <i>preClosurePenalApplicable</i> is true. Used along with <i>preClosurePenalInterest</i> to decide what should be the applicable interest rate to which penalalty can be applied on Pre-mature closure of accounts. 1=Whole Term, 2=Till Premature withdrawal e.g. “1%” means 1% less than the interest rate applicable till premature withdrawal.</td>
+	            </tr>
+
+	        </table>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_template" name="rdaccounts_template" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve recurring Deposit Account Template:</h2>
+	            <p>This is a convenience resource. It can be useful when building maintenance user interface screens for recurring deposit applications. The template data returned consists of any or all of:
+	                <ul>
+	                <li class=normalli>Field Defaults</li>
+	                <li class=normalli>Allowed Value Lists</li>
+	                </ul>
+	            </p>
+	        <h4>Arguments</h4>
+	        <dl class="argument-list">
+	            <dt>clientId</dt>
+	            <dd>Integer <span>mandatory</span></dd>
+	            <dt>productId</dt>
+	            <dd>Integer <span>optional</span></dd>
+	            <dd>If entered, productId, productName and selectedProduct fields are returned.</dd>
+	        </dl>
+	        <p>Example Requests: </p>
+	        <div class=apiClick>fixeddepositaccounts/template?clientId=1</div>
+	        <br><br>
+	        <div class=apiClick>fixeddepositaccounts/template?clientId=1&productId=1</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/fixeddepositaccounts/template?clientId={clientId}</code>
+			<code class="method-response">
+{
+  "clientId": 1,
+  "clientName": "small business",
+  "productOptions": [
+    {
+      "id": 1,
+      "name": "Passbook Savings"
+    }
+  ]
+}
+			</code>
+	    </div>
+
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/fixeddepositaccounts/template?clientId={clientId}&productId={productId}</code>
+			<code class="method-response">
+{
+  "clientId": 1,
+  "clientName": "small business",
+  "savingsProductId": 1,
+  "savingsProductName": "Passbook Savings",
+  "timeline": {},
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "nominalAnnualInterestRate": 5,
+  "interestCompoundingPeriodType": {
+    "id": 1,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+    "value": "Daily"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "productOptions": [
+    {
+      "id": 1,
+      "name": "Passbook Savings"
+    }
+  ],
+  "fieldOfficerOptions": [
+    {
+      "id": 3,
+      "firstname": "Mrs.",
+      "lastname": "loanofficerB1",
+      "displayName": "loanofficerB1, Mrs.",
+      "officeId": 2,
+      "officeName": "branch 1",
+      "isLoanOfficer": true
+    },
+    {
+      "id": 1,
+      "firstname": "Mr.",
+      "lastname": "loanofficerHO",
+      "displayName": "loanofficerHO, Mr.",
+      "officeId": 1,
+      "officeName": "branch 1",
+      "isLoanOfficer": true
+    }
+  ],
+  "interestCompoundingPeriodTypeOptions": [
+    {
+      "id": 1,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+      "value": "Daily"
+    },
+    {
+      "id": 4,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+      "value": "Monthly"
+    }
+  ],
+  "interestPostingPeriodTypeOptions": [
+    {
+      "id": 4,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    {
+      "id": 5,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.quarterly",
+      "value": "Quarterly"
+    },
+    {
+      "id": 7,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.annual",
+      "value": "Annually"
+    }
+  ],
+  "interestCalculationTypeOptions": [
+    {
+      "id": 1,
+      "code": "savingsInterestCalculationType.dailybalance",
+      "value": "Daily Balance"
+    },
+    {
+      "id": 2,
+      "code": "savingsInterestCalculationType.averagedailybalance",
+      "value": "Average Daily Balance"
+    }
+  ],
+  "interestCalculationDaysInYearTypeOptions": [
+    {
+      "id": 360,
+      "code": "savingsInterestCalculationDaysInYearType.days360",
+      "value": "360 Days"
+    },
+    {
+      "id": 365,
+      "code": "savingsInterestCalculationDaysInYearType.days365",
+      "value": "365 Days"
+    }
+  ],
+  "lockinPeriodFrequencyTypeOptions": [
+    {
+      "id": 0,
+      "code": "savings.lockin.savingsPeriodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "savings.lockin.savingsPeriodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "savings.lockin.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "savings.lockin.savingsPeriodFrequencyType.years",
+      "value": "Years"
+    }
+  ],
+  "withdrawalFeeTypeOptions": [
+    {
+      "id": 1,
+      "code": "savingsWithdrawalFeesType.flat",
+      "value": "Flat"
+    },
+    {
+      "id": 2,
+      "code": "savingsWithdrawalFeesType.percent.of.amount",
+      "value": "% of Amount"
+    }
+  ],
+  "chargeOptions": [
+    {
+      "id": 4,
+      "name": "Savings charge 1",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 200,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "chargepaymentmode.regular"
+      }
+    },
+    {
+      "id": 5,
+      "name": "Savings charge 2",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 300,
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "chargepaymentmode.regular"
+      }
+    },
+    {
+      "id": 6,
+      "name": "Annual fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 50,
+      "chargeTimeType": {
+        "id": 6,
+        "code": "chargeTimeType.annualFee",
+        "value": "Annual Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      }
+    },
+    {
+      "id": 7,
+      "name": "Quarterly fee",
+      "active": true,
+      "penalty": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 5,
+      "chargeTimeType": {
+        "id": 7,
+        "code": "chargeTimeType.monthlyFee",
+        "value": "Monthly Fee"
+      },
+      "chargeAppliesTo": {
+        "id": 2,
+        "code": "chargeAppliesTo.savings",
+        "value": "Savings"
+      },
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      },
+      "feeOnMonthDay": [
+        10,
+        5
+      ],
+      "feeInterval": 4
+    }
+  ]
+}
+			</code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_create" name="rdaccounts_create" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Submit new recurring deposit application</h2>
+	        <br/>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields</div></td></tr>
+	            <tr class=alt><td>clientId or groupId, productId, submittedOnDate, depositPeriod, depositPeriodFrequencyId, recurringFrequency, recurringFrequencyType, depositAmount,isCalendarInherited, mandatoryRecommendedDepositAmount</td></tr>
+	        </table>
+	        <br/>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Optional Fields</div></td></tr>
+	            <tr class=alt><td>accountNo, externalId, fieldOfficerId, expectedFirstDepositOnDate, allowWithdrawal, adjustAdvanceTowardsFuturePayments, isMandatoryDeposit</td></tr>
+	        </table>
+	        <br/>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Inherited from Product (if not provided)</div></td></tr>
+	            <tr class=alt><td>interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, lockinPeriodFrequencyType, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnTypeId, charts</td>
+	       		</tr>
+	        </table>
+	    </div>
+	    <div class="method-example">
+	    	<p>Minimal request: accountNo auto generated, remaining details inherited from recurring deposit product.</p>
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts</code>
+	        <code class="method-request">POST recurringdepositaccounts
+Content-Type: application/json
+Request Body:
+{
+  "clientId": 1,
+  "productId": 1,
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "submittedOnDate": "02 June 2014",
+  "depositPeriod":"20",
+  "depositPeriodFrequencyId":"1",
+  "depositAmount":10000,
+  "isCalendarInherited":false,
+  "recurringFrequency":"2",
+  "recurringFrequencyType":1,
+  "mandatoryRecommendedDepositAmount":"2000"
+}
+			</code>
+			<p>Minimal request: accountNo provided (must be unique), remaining details inherited from recurring deposit product.</p>
+			<code class="method-request">POST recurringdepositaccounts
+Content-Type: application/json
+Request Body:
+{
+  "clientId": 1,
+  "productId": 1,
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "submittedOnDate": "02 June 2014",
+  "depositPeriod":"20",
+  "depositPeriodFrequencyId":"1",
+  "depositAmount":10000,
+  "isCalendarInherited":false,
+  "recurringFrequency":"2",
+  "recurringFrequencyType":1,
+  "mandatoryRecommendedDepositAmount":"2000",
+  "accountNo": "RD000023",
+  "externalId": "RD-23",
+  "expectedFirstDepositOnDate": "02 June 2014",
+  "allowWithdrawal":false,
+  "adjustAdvanceTowardsFuturePayments":false,
+  "isMandatoryDeposit":false
+
+}
+			</code>
+			<p>Full request: accountNo provided (must be unique), remaining details override details from recurring deposit product (except currency).</p>
+			<code class="method-request">POST recurringdepositaccounts
+Content-Type: application/json
+Request Body:
+{
+  "clientId": 1,
+  "productId": 1,
+  "fieldOfficerId": 1,
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "submittedOnDate": "02 June 2014",
+  "depositPeriod":"20",
+  "depositPeriodFrequencyId":"1",
+  "depositAmount":10000,
+  "isCalendarInherited":false,
+  "recurringFrequency":"2",
+  "recurringFrequencyType":1,
+  "mandatoryRecommendedDepositAmount":"2000",
+  "accountNo": "RD000023",
+  "externalId": "RD-23",
+  "expectedFirstDepositOnDate": "02 June 2014",
+  "allowWithdrawal":false,
+  "adjustAdvanceTowardsFuturePayments":false,
+  "isMandatoryDeposit":false,
+  "interestCompoundingPeriodType": 1,
+  "interestPostingPeriodType": 4,
+  "interestCalculationType": 1,
+  "interestCalculationDaysInYearType": 365,
+  "lockinPeriodFrequency": 6,
+  "lockinPeriodFrequencyType": 2,
+  "charges":[{"id":"1"}]
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_approve" name="rdaccounts_approve" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Approve recurring deposit application</h2>
+	        <p>Approves recurring deposit application so long as its in 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=approve</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=approve
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "approvedOnDate": "01 March 2014"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 200,
+      "code": "savingsAccountStatusType.approved",
+      "value": "Approved",
+      "submittedAndPendingApproval": false,
+      "approved": true,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "approvedOnDate": "01 March 2014"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_undoapproval" name="rdaccounts_undoapproval" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Undo approval recurring deposit application</h2>
+	        <p>Will move 'approved' recurring deposit application back to 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=undoApproval</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=undoApproval
+Content-Type: application/json
+Request Body:
+{
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 100,
+      "code": "savingsAccountStatusType.submitted.and.pending.approval",
+      "value": "Submitted and pending approval",
+      "submittedAndPendingApproval": true,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false
+    },
+    "approvedOnDate": ""
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_reject" name="rdaccounts_reject" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Reject recurring deposit application</h2>
+	        <p>Rejects recurring deposit application so long as its in 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=reject</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=reject
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "rejectedOnDate": "03 March 2014"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 500,
+      "code": "savingsAccountStatusType.rejected",
+      "value": "Rejected",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": true,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": true
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "rejectedOnDate": "03 March 2014",
+    "closedOnDate": "03 March 2014"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_withdrawbyapplicant" name="rdaccounts_withdrawbyapplicant" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Withdraw recurring deposit application</h2>
+	        <p>Used when an applicant withdraws from the recurring deposit application. It must be in 'Submitted and pending approval' state.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{savingsId}?command=withdrawnByApplicant</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=withdrawnByApplicant
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "withdrawnOnDate": "03 March 2014"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 400,
+      "code": "savingsAccountStatusType.withdrawn.by.applicant",
+      "value": "Withdrawn by applicant",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": true,
+      "active": false,
+      "closed": true
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "withdrawnOnDate": "03 March 2014",
+    "closedOnDate": "03 March 2014"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_activate" name="rdaccounts_activate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Activate a recurring deposit account</h2>
+	        <p>Results in an approved recurring deposit application being converted into an 'active' recurring deposit account.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=activate</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=activate
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "activatedOnDate": "01 March 2013"
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 300,
+      "code": "savingsAccountStatusType.active",
+      "value": "Active",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": true,
+      "closed": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "activatedOnDate": "01 March 2013"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_update_deposit_amount" name="rdaccounts_update_deposit_amount" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Update the recommended deposit amount for a recurring deposit account</h2>
+	        <p>Updates the recommended deposit amount for a RD account as on the effective date.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=updateDepositAmount</code>
+	        <code class="method-request">POST recurringdepositaccounts/4?command=updateDepositAmount
+Content-Type: application/json
+Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "effectiveDate": "01 Dec 2014"
+  "mandatoryRecommendedDepositAmount": 398
+}
+			</code>
+	        <code class="method-response">
+
+{
+   "officeId": 1,
+   "clientId": 6,
+   "savingsId": 4,
+   "resourceId": 4,
+   "changes":
+   {
+       "mandatoryRecommendedDepositAmount": 398
+   }
+}
+
+	        </code>
+	    </div>
+	</div>
+
+
+	<a id="rdaccounts_close" name="rdaccounts_close" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Close a recurring deposit account</h2>
+	        <p>Results in a Matured recurring deposit account being converted into a 'closed' recurring deposit account.</p> <p>On account close allowed actions are.</p>
+	         <table class=matrixHeading>
+	        	<thead>
+	        		<tr class="matrixHeadingBG">
+	        			<td class="fineractHeading2">Action on Close</td>
+	        			<td class="fineractHeading2">Result</td>
+	        		</tr>
+	        	</thead>
+	        	<tbody>
+	        		<tr>
+	        			<td>Withdraw Deposit &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+	        			<td>Matured amount withdrawn and paid to client</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Transfer to Savings</td>
+	        			<td>Matured amount transfered to specified savings account.</td>
+	        		</tr>
+	        		<tr>
+	        			<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+	        			<td>&nbsp;</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Re-Invest</td>
+	        			<td>Create new Fixed deposit application with matured amount as deposit amount.</td>
+	        		</tr>
+	        	</tbody>
+	        </table>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=close</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=close
+Content-Type: application/json
+Request Body:
+	{
+	"dateFormat":"dd MMMM yyyy",
+	"locale":"en",
+	"closedOnDate":"19 April 2014",
+	"note":"Closing and transfering amount to savings",
+	"onAccountClosureId":"100",
+	"transferDescription":"Transfered matured amount to savings account"
+	}
+			</code>
+	        <code class="method-response">
+{
+"officeId":1,
+"clientId":1,
+"savingsId":5,
+"resourceId":5,
+"changes":{
+	"status":{
+		"id":600,
+		"code":"savingsAccountStatusType.closed",
+		"value":"Closed",
+		"submittedAndPendingApproval":false,
+		"approved":false,
+		"rejected":false,
+		"withdrawnByApplicant":false,
+		"active":false,
+		"closed":true
+		},
+	"locale":"en",
+	"dateFormat":"dd MMMM yyyy",
+	"closedOnDate":"19 April 2014",
+	"note":"Closing and transfering amount to savings"
+	}
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_prematureclose" name="rdaccounts_prematureclose" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Premature Close a recurring deposit account</h2>
+	        <p>Results in an Active recurring deposit account being converted into a 'Premature Closed' recurring deposit account with options to withdraw prematured amount. (premature amount is calculated using interest rate chart applicable along with penal interest if any.)</p> <p>On account premature closure allowed actions are.</p>
+	         <table class=matrixHeading>
+	        	<thead>
+	        		<tr class="matrixHeadingBG">
+	        			<td class="fineractHeading2">Action on Premature Close</td>
+	        			<td class="fineractHeading2">Result</td>
+	        		</tr>
+	        	</thead>
+	        	<tbody>
+	        		<tr>
+	        			<td>Withdraw Deposit &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+	        			<td>Matured amount withdrawn and paid to client</td>
+	        		</tr>
+	        		<tr>
+	        			<td>Transfer to Savings</td>
+	        			<td>Matured amount transfered to a savings account.</td>
+	        		</tr>
+	        	</tbody>
+	        </table>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=prematureClose</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=prematureClose
+Content-Type: application/json
+Request Body:
+	{
+	"dateFormat":"dd MMMM yyyy",
+	"locale":"en",
+	"closedOnDate":"19 April 2014",
+	"note":"Close and transfer amount to savings",
+	"onAccountClosureId":"200",
+	"toSavingsAccountId":1,
+	"transferDescription":"Transfered matured amount to savings account"
+	}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "status": {
+      "id": 700,
+      "code": "savingsAccountStatusType.pre.mature.closure",
+      "value": "Premature Closed",
+      "submittedAndPendingApproval": false,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false,
+      "prematureClosed": true,
+      "transferInProgress": false,
+      "transferOnHold": false
+    },
+    "locale": "en",
+    "dateFormat": "dd MMMM yyyy",
+    "closedOnDate": "19 April 2014",
+    "note": "Close and transfer amount to savings"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_calculate_premature_amount" name="rdaccounts_calculate_premature_amount" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Calculate Premature amount on Recurring deposit account</h2>
+	        <p>Calculate premature amount on recurring deposit till premature close date. Premature amount is calculated based on interest chart and penal interest applicable if any.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=calculatePrematureAmount</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=calculatePrematureAmount
+Content-Type: application/json
+Request Body:
+	{
+	"dateFormat":"dd MMMM yyyy",
+	"locale":"en",
+	"closedOnDate":"19 April 2014"
+	}
+			</code>
+	        <code class="method-response">
+{
+  "maturityAmount": 100.65,
+  "savingsAccounts": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "clientId": 1,
+      "clientName": "Sangamesh N",
+      "savingsProductId": 1,
+      "savingsProductName": "sa",
+      "fieldOfficerId": 0,
+      "status": {
+        "id": 300,
+        "code": "savingsAccountStatusType.active",
+        "value": "Active",
+        "submittedAndPendingApproval": false,
+        "approved": false,
+        "rejected": false,
+        "withdrawnByApplicant": false,
+        "active": true,
+        "closed": false,
+        "prematureClosed": false,
+        "transferInProgress": false,
+        "transferOnHold": false
+      },
+      "timeline": {
+        "submittedOnDate": [
+          2014,
+          1,
+          1
+        ],
+        "submittedByUsername": "mifos",
+        "submittedByFirstname": "App",
+        "submittedByLastname": "Administrator",
+        "approvedOnDate": [
+          2014,
+          1,
+          1
+        ],
+        "approvedByUsername": "mifos",
+        "approvedByFirstname": "App",
+        "approvedByLastname": "Administrator",
+        "activatedOnDate": [
+          2014,
+          1,
+          1
+        ]
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "inMultiplesOf": 1,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "nominalAnnualInterestRate": 5,
+      "interestCompoundingPeriodType": {
+        "id": 4,
+        "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+        "value": "Monthly"
+      },
+      "interestPostingPeriodType": {
+        "id": 4,
+        "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+        "value": "Monthly"
+      },
+      "interestCalculationType": {
+        "id": 1,
+        "code": "savingsInterestCalculationType.dailybalance",
+        "value": "Daily Balance"
+      },
+      "interestCalculationDaysInYearType": {
+        "id": 365,
+        "code": "savingsInterestCalculationDaysInYearType.days365",
+        "value": "365 Days"
+      },
+      "minRequiredOpeningBalance": 5000,
+      "withdrawalFeeForTransfers": false,
+      "allowOverdraft": false,
+      "summary": {
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "inMultiplesOf": 1,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        },
+        "totalDeposits": 13353.41,
+        "totalInterestEarned": 107.79,
+        "totalInterestPosted": 76.74,
+        "accountBalance": 13430.15
+      }
+    }
+  ],
+  "onAccountClosureOptions": [
+    {
+      "id": 100,
+      "code": "depositAccountClosureType.withdrawDeposit",
+      "value": "Withdra Deposit"
+    },
+    {
+      "id": 200,
+      "code": "depositAccountClosureType.transferToSavings",
+      "value": "Transfer to Savings"
+    },
+    {
+      "id": 300,
+      "code": "depositAccountClosureType.reinvest",
+      "value": "Re-Invest"
+    }
+  ],
+  "paymentTypeOptions": [],
+  "id": 12,
+  "depositType": {
+    "id": 300,
+    "code": "depositAccountType.recurringDeposit",
+    "value": "Recurring Deposit"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_calculate_interest" name="rdaccounts_calculate_interest" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Calculate Interest on recurring Deposit Account</h2>
+	        <p>
+	        	Calculates interest earned on a recurring deposit account based on todays date. It does not attempt to <b>post or credit</b> the interest on the account. That is responsibility of the <a href="#rdaccounts_post_interest" >Post Interest API</a> that will likely be called by overnight process.
+	        </p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=calculateInterest</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=calculateInterest
+Content-Type: application/json
+Request Body:
+{
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_post_interest" name="rdaccounts_post_interest" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Post Interest on recurring Deposit Account</h2>
+	        <p>
+	        	Calculates and Posts interest earned on a recurring deposit account based on todays date and whether an interest posting or crediting event is due.
+	        </p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountId}?command=postInterest</code>
+	        <code class="method-request">POST recurringdepositaccounts/1?command=postInterest
+Content-Type: application/json
+Request Body:
+{
+}
+			</code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_list" name="rdaccounts_list" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h2>List Fixed deposit applications/accounts</h2>
+            <p>Example Requests:</p>
+            <div class=apiClick>recurringdepositaccounts</div>
+            <br><br>
+            <div class=apiClick>recurringdepositaccounts?fields=name</div>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">GET https://Domain Name/api/v1/recurringdepositaccounts</code>
+            <code class="method-response">
+[
+  {
+    "id": 1,
+    "accountNo": "000000001",
+    "clientId": 1,
+    "clientName": "Sangamesh N",
+    "savingsProductId": 3,
+    "savingsProductName": "RD01",
+    "fieldOfficerId": 0,
+    "status": {
+      "id": 100,
+      "code": "savingsAccountStatusType.submitted.and.pending.approval",
+      "value": "Submitted and pending approval",
+      "submittedAndPendingApproval": true,
+      "approved": false,
+      "rejected": false,
+      "withdrawnByApplicant": false,
+      "active": false,
+      "closed": false,
+      "prematureClosed": false,
+      "transferInProgress": false,
+      "transferOnHold": false
+    },
+    "timeline": {
+      "submittedOnDate": [
+        2014,
+        3,
+        1
+      ],
+      "submittedByUsername": "mifos",
+      "submittedByFirstname": "App",
+      "submittedByLastname": "Administrator"
+    },
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "inMultiplesOf": 1,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "interestCompoundingPeriodType": {
+      "id": 4,
+      "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    "interestPostingPeriodType": {
+      "id": 4,
+      "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+      "value": "Monthly"
+    },
+    "interestCalculationType": {
+      "id": 1,
+      "code": "savingsInterestCalculationType.dailybalance",
+      "value": "Daily Balance"
+    },
+    "interestCalculationDaysInYearType": {
+      "id": 365,
+      "code": "savingsInterestCalculationDaysInYearType.days365",
+      "value": "365 Days"
+    },
+    "summary": {
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "inMultiplesOf": 1,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "accountBalance": 0
+    },
+    "depositAmount": 1150,
+    "maturityAmount": 252.59,
+    "maturityDate": [
+      2014,
+      4,
+      3
+    ],
+    "recurringDepositAmount": 100,
+    "recurringDepositFrequency": 1,
+    "recurringDepositFrequencyType": {
+      "id": 2,
+      "code": "recurring.deposit.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    "preClosurePenalApplicable": false,
+    "minDepositTerm": 3,
+    "maxDepositTerm": 4,
+    "minDepositTermType": {
+      "id": 2,
+      "code": "deposit.term.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+    "maxDepositTermType": {
+      "id": 3,
+      "code": "deposit.term.savingsPeriodFrequencyType.years",
+      "value": "Years"
+    },
+    "depositAmount": 5000,
+    "maturityAmount": 5140.25,
+    "maturityDate": [
+      2014,
+      9,
+      1
+    ],
+    "depositPeriod": 6,
+    "depositPeriodFrequency": {
+      "id": 2,
+      "code": "deposit.period.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    }
+  }
+]
+
+			</code>
+        </div>
+    </div>
+
+    <a id="rdaccounts_retrieve" name="rdaccounts_retrieve" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve a recurring deposit application/account:</h2>
+	        <h4>Arguments</h4>
+	        <dl class="argument-list">
+	            <dt>associations</dt>
+	            <dd>optional, <span>Either 'all' or a comma separated list of recurring deposit 'associations' (itemized below).</span></dd>
+	            <dd><br>Associations are just extra pieces of data that you might or might not want to retrieve.<br><br></dd>
+	            <dd><b>'all':</b> Gets data related to all associations e.g. ?associations=all.</dd>
+				<dd><b>'transactions':</b> Gets data related to transactions on the account e.g. ?associations=transactions</dd>
+				<dd><b>'charges':</b>recurring deposit Account charges data.</dd>
+	        </dl>
+	        <p>Example Requests : </p>
+	        <div class=apiClick>recurringdepositaccounts/1</div>
+	        <br><br>
+	        <div class=apiClick>recurringdepositaccounts/1?associations=all</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/recurringdepositaccounts/{accountId}</code>
+	        <code class="method-response">
+{
+  "id": 1,
+  "accountNo": "RD000023",
+  "externalId": "RD-23",
+  "clientId": 1,
+  "clientName": "Sangamesh N",
+  "savingsProductId": 3,
+  "savingsProductName": "RD01",
+  "fieldOfficerId": 0,
+  "status": {
+    "id": 100,
+    "code": "savingsAccountStatusType.submitted.and.pending.approval",
+    "value": "Submitted and pending approval",
+    "submittedAndPendingApproval": true,
+    "approved": false,
+    "rejected": false,
+    "withdrawnByApplicant": false,
+    "active": false,
+    "closed": false,
+    "prematureClosed": false,
+    "transferInProgress": false,
+    "transferOnHold": false
+  },
+  "timeline": {
+    "submittedOnDate": [
+      2014,
+      3,
+      1
+    ],
+    "submittedByUsername": "mifos",
+    "submittedByFirstname": "App",
+    "submittedByLastname": "Administrator"
+  },
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "inMultiplesOf": 1,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "interestCompoundingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "preClosurePenalApplicable": false,
+  "minDepositTerm": 3,
+  "maxDepositTerm": 4,
+  "minDepositTermType": {
+    "id": 2,
+    "code": "deposit.term.savingsPeriodFrequencyType.months",
+    "value": "Months"
+  },
+  "maxDepositTermType": {
+    "id": 3,
+    "code": "deposit.term.savingsPeriodFrequencyType.years",
+    "value": "Years"
+  },
+  "recurringDepositAmount": 100,
+  "recurringDepositFrequency": 1,
+  "expectedFirstDepositOnDate": [
+  	2014,
+  	4,
+   	2
+   ],
+  "recurringDepositFrequencyType": {
+      "id": 2,
+      "code": "recurring.deposit.savingsPeriodFrequencyType.months",
+      "value": "Months"
+    },
+  "depositPeriod": 6,
+  "depositPeriodFrequency": {
+    "id": 2,
+    "code": "deposit.period.savingsPeriodFrequencyType.months",
+    "value": "Months"
+  },
+  "summary": {
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "inMultiplesOf": 1,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "accountBalance": 0
+  },
+  "accountChart": {
+    "id": 4,
+    "fromDate": [
+      2013,
+      10,
+      2
+    ],
+    "accountId": 5,
+    "accountNumber": "RD000023",
+    "chartSlabs": [
+      {
+        "id": 13,
+        "periodType": {
+          "id": 0,
+          "code": "interestChartPeriodType.days",
+          "value": "Days"
+        },
+        "fromPeriod": 181,
+        "toPeriod": 365,
+        "annualInterestRate": 5.5,
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        }
+      },
+      {
+        "id": 12,
+        "periodType": {
+          "id": 0,
+          "code": "interestChartPeriodType.days",
+          "value": "Days"
+        },
+        "fromPeriod": 1,
+        "toPeriod": 180,
+        "annualInterestRate": 5,
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        }
+      },
+      {
+        "id": 11,
+        "periodType": {
+          "id": 0,
+          "code": "interestChartPeriodType.days",
+          "value": "Days"
+        },
+        "fromPeriod": 366,
+        "annualInterestRate": 6,
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        }
+      }
+    ],
+    "periodTypes": [
+      {
+        "id": 0,
+        "code": "interestChartPeriodType.days",
+        "value": "Days"
+      },
+      {
+        "id": 1,
+        "code": "interestChartPeriodType.weeks",
+        "value": "Weeks"
+      },
+      {
+        "id": 2,
+        "code": "interestChartPeriodType.months",
+        "value": "Months"
+      },
+      {
+        "id": 3,
+        "code": "interestChartPeriodType.years",
+        "value": "Years"
+      }
+    ]
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_update" name="rdaccounts_update" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Modify a recurring deposit application</h2>
+	        <p>Recurring deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">PUT https://Domain Name/api/v1/recurringdepositaccounts/{accountId}</code>
+	        <code class="method-request">PUT recurringdepositaccounts/1
+Content-Type: application/json
+No Request Body:
+{
+	"locale": "en",
+	"depositAmount": 6000
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 2,
+  "clientId": 1,
+  "savingsId": 1,
+  "resourceId": 1,
+  "changes": {
+    "depositAmount": 6000,
+    "locale": "en"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="rdaccounts_delete" name="rdaccounts_delete" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Delete a recurring deposit application</h2>
+	        <p>At present we support <b>hard</b> delete of recurring deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the recurring deposit account.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">DELETE https://Domain Name/api/v1/recurringdepositaccounts/{accountsId}</code>
+	        <code class="method-request">DELETE recurringdepositaccounts/1
+Content-Type: application/json
+No Request Body:
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+	<a id="recurringdepositaccounts_transactions" name="recurringdepositaccounts_transactions" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Recurring Deposit Account Transactions:</h2>
+	        <p>Transactions possible on a recurring deposit account.</p>
+	      	<table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Field Descriptions</div></td></tr>
+
+				<tr class=alt><td>transactionDate</td></tr>
+				<tr><td class=fielddesc>The date of the transaction.</td></tr>
+
+				<tr class=alt><td>transactionAmount</td></tr>
+				<tr><td class=fielddesc>The amount of the transaction.</td></tr>
+	        </table>
+	    </div>
+	</div>
+
+	<a id="recurringdepositaccounts_transactions_template" name="recurringdepositaccounts_transactions_template" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Recurring Deposit Account Transaction Template:</h2>
+	            <p>This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:
+	                <ul>
+	                <li class=normalli>Field Defaults</li>
+	                <li class=normalli>Allowed Value Lists</li>
+	                </ul>
+	            </p>
+	        <p>Example Requests: </p>
+	        <div class=apiClick>recurringdepositaccounts/1/transactions/template?command=deposit</div>
+	        <br/>
+			<div class=apiClick>recurringdepositaccounts/1/transactions/template?command=withdrawal</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/recurringdepositaccounts/{accountId}/transactions/template?command=deposit
+	        </code>
+			<code class="method-response">
+{
+  "id": 1,
+  "transactionType": {
+    "id": 1,
+    "code": "savingsAccountTransactionType.deposit",
+    "value": "Deposit",
+    "deposit": true,
+    "withdrawal": false,
+    "interestPosting": false,
+    "feeDeduction": false,
+    "initiateTransfer": false,
+    "approveTransfer": false,
+    "withdrawTransfer": false,
+    "rejectTransfer": false,
+    "overdraftInterest": false,
+    "writtenoff": false,
+    "overdraftFee": true
+  },
+  "accountId": 1,
+  "accountNo": "000000001",
+  "date": [
+    2014,
+    6,
+    25
+  ],
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 4,
+    "inMultiplesOf": 100,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 100000.000000,
+  "reversed": false,
+  "paymentTypeOptions": []
+}
+			</code>
+	    </div>
+	</div>
+
+<a id="recurringdepositaccounts_transaction" name="recurringdepositaccounts_transaction" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Recurring Deposit Account Transaction:</h2>
+	        <p>Example Requests: </p>
+	        <div class=apiClick>recurringdepositaccounts/1/transactions/1</div>
+	        <br/>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/recurringdepositaccounts/{accountId}/transactions/{transactionId}
+	        </code>
+			<code class="method-response">
+{
+  "id": 1,
+  "transactionType": {
+    "id": 2,
+    "code": "savingsAccountTransactionType.withdrawal",
+    "value": "Withdrawal",
+    "deposit": false,
+    "withdrawal": true,
+    "interestPosting": false,
+    "feeDeduction": false
+  },
+  "accountId": 1,
+  "accountNo": "000000001",
+  "date": [
+    2013,
+    8,
+    7
+  ],
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "inMultiplesOf": 0,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "paymentDetailData": {
+    "id": 62,
+    "paymentType": {
+      "id": 11,
+      "name": "cash"
+    },
+    "accountNumber": "",
+    "checkNumber": "",
+    "routingCode": "",
+    "receiptNumber": "",
+    "bankNumber": ""
+  },
+  "amount": 5000,
+  "runningBalance": 0,
+  "reversed": true
+}
+</code>
+	    </div>
+	</div>
+
+	<a id="recurringdepositaccounts_deposit" name="recurringdepositaccounts_deposit" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Deposit Transaction</h2>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountsId}/transactions?command=deposit</code>
+	        <code class="method-request">POST recurringdepositaccounts/1/transactions?command=deposit
+Content-Type: application/json
+No Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "transactionDate": "27 May 2013",
+  "transactionAmount": "500",
+  "paymentTypeId": "14",
+  "accountNumber": "acc123",
+  "checkNumber": "che123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 2,
+  "savingsId": 1,
+  "resourceId": 47,
+  "changes": {
+    "accountNumber": "acc123",
+    "checkNumber": "che123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="recurringdepositaccounts_withdrawal" name="recurringdepositaccounts_withdrawal" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Withdrawal Transaction</h2>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountsId}/transactions?command=withdrawal</code>
+	        <code class="method-request">POST recurringdepositaccounts/1/transactions?command=withdrawal
+Content-Type: application/json
+No Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "transactionDate": "27 May 2013",
+  "transactionAmount": "500",
+  "paymentTypeId": "14",
+  "accountNumber": "acc123",
+  "checkNumber": "che123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 2,
+  "savingsId": 1,
+  "resourceId": 48,
+  "changes": {
+    "accountNumber": "acc123",
+    "checkNumber": "che123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="recurringdepositaccounts_adjusttransaction" name="recurringdepositaccounts_adjusttransaction" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Adjust Transaction</h2>
+	        <p>This command modifies the given transaction.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountsId}/transactions/{transactionId}?command=modify</code>
+	        <code class="method-request">POST recurringdepositaccounts/1/transactions/1?command=modify
+Content-Type: application/json
+No Request Body:
+{
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "transactionDate": "27 May 2013",
+  "transactionAmount": "500",
+  "paymentTypeId": "14",
+  "accountNumber": "acc123",
+  "checkNumber": "che123",
+  "routingCode": "rou123",
+  "receiptNumber": "rec123",
+  "bankNumber": "ban123"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 2,
+  "savingsId": 1,
+  "resourceId": 48,
+  "changes": {
+    "accountNumber": "acc123",
+    "checkNumber": "che123",
+    "routingCode": "rou123",
+    "receiptNumber": "rec123",
+    "bankNumber": "ban123"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="recurringdepositaccounts_undotransaction" name="recurringdepositaccounts_undotransaction" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Undo transaction</h2>
+	        <p>This command reverses the given transaction.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/recurringdepositaccounts/{accountsId}/transactions/{transactionId}?command=undo</code>
+	        <code class="method-request">POST recurringdepositaccounts/1/transactions/1?command=undo
+Content-Type: application/json
+No Request Body:
+{
+}
+	        </code>
+	        <code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 2,
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+<!-- end of recurring deposit accounts api -->
+<!-- account transfers -->
+<a id="accounttransfers" name="accounttransfers" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Account Transfers:</h2>
+	        <p>Ability to be able to transfer monetary funds from one account to another.</p>
+	        <p>Note: At present only savings account to savings account transfers are supported.</p>
+	      	<table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Parameters</div></td></tr>
+
+	            <tr class=alt><td>fromOfficeId</td></tr>
+				<tr><td class=fielddesc>The id of the office/branch from which the transfer is made.</td></tr>
+
+				<tr class=alt><td>fromClientId</td></tr>
+				<tr><td class=fielddesc>The id of the client from which the transfer is made.</td></tr>
+
+				<tr class=alt><td>fromAccountType</td></tr>
+				<tr><td class=fielddesc>The type of account from which the transfer is made. 1=Loan Account, 2=Savings Account</td></tr>
+
+				<tr class=alt><td>fromAccountId</td></tr>
+				<tr><td class=fielddesc>The id of the account from which the transfer is made.</td></tr>
+
+				<tr class=alt><td>toOfficeId</td></tr>
+				<tr><td class=fielddesc>The id of the office/branch to which the transfer is made.</td></tr>
+
+				<tr class=alt><td>toClientId</td></tr>
+				<tr><td class=fielddesc>The id of the client to which the transfer is made.</td></tr>
+
+				<tr class=alt><td>toAccountType</td></tr>
+				<tr><td class=fielddesc>The type of account to which the transfer is made. 1=Loan Account, 2=Savings Account</td></tr>
+
+				<tr class=alt><td>toAccountId</td></tr>
+				<tr><td class=fielddesc>The id of the account to which the transfer is made. The accouont must be <i>active</i> and must have the same currency as that of the selected <b>fromAccountId</b>.</td></tr>
+
+				<tr class=alt><td>transferDate</td></tr>
+				<tr><td class=fielddesc>The date of the transfer. Requires <i>dateFormat</i> and <i>locale</i> parameters.</td></tr>
+
+				<tr class=alt><td>transferAmount</td></tr>
+				<tr><td class=fielddesc>The amount of the transfer. Requires <i>locale</i> parameter.</td></tr>
+
+				<tr class=alt><td>transferDescription</td></tr>
+				<tr><td class=fielddesc>Description of the transfer itself.</td></tr>
+	        </table>
+	    </div>
+	</div>
+
+	<a id="accounttransfers_template" name="accounttransfers_template" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Account Transfer Template:</h2>
+	            <p>This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:
+	                <ul>
+	                <li class=normalli>Field Defaults</li>
+	                <li class=normalli>Allowed Value Lists</li>
+	                </ul>
+	            </p>
+	        <p>Example Requests:</p>
+	        <div class=apiClick>accounttransfers/template?fromAccountType=2&fromOfficeId=1</div>
+	        <br/>
+	        <div class=apiClick>accounttransfers/template?fromAccountType=2&fromOfficeId=1&fromClientId=1</div>
+	        <br/>
+	        <div class=apiClick>accounttransfers/template?fromClientId=1&fromAccountType=2&fromAccountId=1</div>
+	        <br/>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/accounttransfers/template?fromAccountType=2&fromOfficeId=1
+	        </code>
+			<code class="method-response">
+{
+  "transferAmount": 0,
+  "transferDate": [
+    2013,
+    8,
+    15
+  ],
+  "fromOffice": {
+    "id": 1,
+    "name": "HO",
+    "nameDecorated": "HO",
+    "externalId": "1",
+    "openingDate": [
+      2009,
+      1,
+      1
+    ],
+    "hierarchy": "."
+  },
+  "fromAccountType": {
+    "id": 2,
+    "code": "accountType.savings",
+    "value": "Savings Account"
+  },
+  "fromOfficeOptions": [
+    {
+      "id": 1,
+      "name": "HO",
+      "nameDecorated": "HO"
+    },
+    {
+      "id": 2,
+      "name": "Branch 1",
+      "nameDecorated": "....Branch 1"
+    }
+  ],
+  "fromClientOptions": [
+    {
+      "id": 1,
+      "displayName": "Small shop",
+      "officeId": 1,
+      "officeName": "HO"
+    },
+    {
+      "id": 3,
+      "displayName": "Third client",
+      "officeId": 1,
+      "officeName": "HO"
+    }
+  ],
+  "fromAccountTypeOptions": [
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    }
+  ],
+  "toOfficeOptions": [
+    {
+      "id": 1,
+      "name": "HO",
+      "nameDecorated": "HO"
+    },
+    {
+      "id": 2,
+      "name": "Branch 1",
+      "nameDecorated": "....Branch 1"
+    }
+  ],
+  "toAccountTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountType.loan",
+      "value": "Loan Account"
+    },
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    }
+  ]
+}
+			</code>
+	    </div>
+
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/accounttransfers/template?fromAccountType=2&fromOfficeId=1&fromClientId=1
+	        </code>
+			<code class="method-response">
+{
+  "transferAmount": 0,
+  "transferDate": [
+    2013,
+    8,
+    15
+  ],
+  "fromOffice": {
+    "id": 1,
+    "name": "HO",
+    "nameDecorated": "HO",
+    "externalId": "1",
+    "openingDate": [
+      2009,
+      1,
+      1
+    ],
+    "hierarchy": "."
+  },
+  "fromClient": {
+    "id": 1,
+    "accountNo": "000000001",
+    "status": {
+      "id": 300,
+      "code": "clientStatusType.active",
+      "value": "Active"
+    },
+    "active": true,
+    "activationDate": [
+      2013,
+      3,
+      1
+    ],
+    "fullname": "Small shop",
+    "displayName": "Small shop",
+    "officeId": 1,
+    "officeName": "HO",
+    "groups": []
+  },
+  "fromAccountType": {
+    "id": 2,
+    "code": "accountType.savings",
+    "value": "Savings Account"
+  },
+  "fromOfficeOptions": [
+    {
+      "id": 1,
+      "name": "HO",
+      "nameDecorated": "HO"
+    },
+    {
+      "id": 2,
+      "name": "Branch 1",
+      "nameDecorated": "....Branch 1"
+    }
+  ],
+  "fromClientOptions": [
+    {
+      "id": 1,
+      "displayName": "Small shop",
+      "officeId": 1,
+      "officeName": "HO"
+    },
+    {
+      "id": 3,
+      "displayName": "Third client",
+      "officeId": 1,
+      "officeName": "HO"
+    }
+  ],
+  "fromAccountTypeOptions": [
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    }
+  ],
+  "fromAccountOptions": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "clientId": 1,
+      "clientName": "Small shop",
+      "productId": 1,
+      "productName": "Passbook",
+      "fieldOfficerId": 0,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      }
+    },
+    {
+      "id": 3,
+      "accountNo": "000000003",
+      "clientId": 1,
+      "clientName": "Small shop",
+      "productId": 2,
+      "productName": "Shilling product",
+      "fieldOfficerId": 0,
+      "currency": {
+        "code": "KES",
+        "name": "Kenyan Shilling",
+        "decimalPlaces": 0,
+        "inMultiplesOf": 0,
+        "displaySymbol": "KSh",
+        "nameCode": "currency.KES",
+        "displayLabel": "Kenyan Shilling (KSh)"
+      }
+    }
+  ],
+  "toOfficeOptions": [
+    {
+      "id": 1,
+      "name": "HO",
+      "nameDecorated": "HO"
+    },
+    {
+      "id": 2,
+      "name": "Branch 1",
+      "nameDecorated": "....Branch 1"
+    }
+  ],
+  "toAccountTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountType.loan",
+      "value": "Loan Account"
+    },
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    }
+  ]
+}
+			</code>
+	    </div>
+
+	   	<div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/accounttransfers/template?fromClientId=1&fromAccountType=2&fromAccountId=1
+	        </code>
+			<code class="method-response">
+{
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "transferAmount": 0,
+  "transferDate": [
+    2013,
+    8,
+    15
+  ],
+  "fromOffice": {
+    "id": 1,
+    "name": "HO",
+    "nameDecorated": "HO",
+    "externalId": "1",
+    "openingDate": [
+      2009,
+      1,
+      1
+    ],
+    "hierarchy": "."
+  },
+  "fromClient": {
+    "id": 1,
+    "accountNo": "000000001",
+    "status": {
+      "id": 300,
+      "code": "clientStatusType.active",
+      "value": "Active"
+    },
+    "active": true,
+    "activationDate": [
+      2013,
+      3,
+      1
+    ],
+    "fullname": "Small shop",
+    "displayName": "Small shop",
+    "officeId": 1,
+    "officeName": "HO",
+    "groups": []
+  },
+  "fromAccountType": {
+    "id": 2,
+    "code": "accountType.savings",
+    "value": "Savings Account"
+  },
+  "fromAccount": {
+    "id": 1,
+    "accountNo": "000000001",
+    "clientId": 1,
+    "clientName": "Small shop",
+    "productId": 1,
+    "productName": "Passbook",
+    "fieldOfficerId": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    }
+  },
+  "fromOfficeOptions": [
+    {
+      "id": 1,
+      "name": "HO",
+      "nameDecorated": "HO"
+    },
+    {
+      "id": 2,
+      "name": "Branch 1",
+      "nameDecorated": "....Branch 1"
+    }
+  ],
+  "fromClientOptions": [
+    {
+      "id": 1,
+      "displayName": "Small shop",
+      "officeId": 1,
+      "officeName": "HO"
+    },
+    {
+      "id": 3,
+      "displayName": "Third client",
+      "officeId": 1,
+      "officeName": "HO"
+    }
+  ],
+  "fromAccountTypeOptions": [
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    }
+  ],
+  "fromAccountOptions": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "clientId": 1,
+      "clientName": "Small shop",
+      "productId": 1,
+      "productName": "Passbook",
+      "fieldOfficerId": 0,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      }
+    },
+    {
+      "id": 3,
+      "accountNo": "000000003",
+      "clientId": 1,
+      "clientName": "Small shop",
+      "productId": 2,
+      "productName": "Shilling product",
+      "fieldOfficerId": 0,
+      "currency": {
+        "code": "KES",
+        "name": "Kenyan Shilling",
+        "decimalPlaces": 0,
+        "inMultiplesOf": 0,
+        "displaySymbol": "KSh",
+        "nameCode": "currency.KES",
+        "displayLabel": "Kenyan Shilling (KSh)"
+      }
+    }
+  ],
+  "toOfficeOptions": [
+    {
+      "id": 1,
+      "name": "HO",
+      "nameDecorated": "HO"
+    },
+    {
+      "id": 2,
+      "name": "Branch 1",
+      "nameDecorated": "....Branch 1"
+    }
+  ],
+  "toAccountTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountType.loan",
+      "value": "Loan Account"
+    },
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    }
+  ]
+}
+			</code>
+	    </div>
+
+	</div>
+
+	<a id="accounttransfers_create" name="accounttransfers_create" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Create new Transfer</h2>
+	        <p>Ability to create new transfer of monetary funds from one account to another.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/accounttransfers</code>
+	        <code class="method-request">POST accounttransfers/
+Content-Type: application/json
+No Request Body:
+{
+"fromOfficeId": 1,
+"fromClientId": 1,
+"fromAccountType": 2,
+"fromAccountId": 1,
+"toOfficeId": 1,
+"toClientId": 1,
+"toAccountType": 2,
+"toAccountId": 2,
+"dateFormat": "dd MMMM yyyy",
+"locale": "en",
+"transferDate": "01 August 2011",
+"transferAmount": "112.45",
+"transferDescription": "A description of the transfer"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="accounttransfers_list" name="accounttransfers_list" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h2>List account transfers</h2>
+            <p>Example Requests:</p>
+            <div class=apiClick>accounttransfers</div>
+            <br>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">GET https://Domain Name/api/v1/accounttransfers</code>
+            <code class="method-response">
+{
+  "totalFilteredRecords": 4,
+  "pageItems": [
+    {
+      "id": 1,
+      "reversed": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "transferAmount": 200,
+      "transferDate": [
+        2013,
+        4,
+        1
+      ],
+      "transferDescription": "pay off loan from savings.",
+      "fromOffice": {
+        "id": 1,
+        "name": "HO"
+      },
+      "fromClient": {
+        "id": 1,
+        "displayName": "Small shop",
+        "officeId": 1,
+        "officeName": "HO"
+      },
+      "fromAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "fromAccount": {
+        "id": 1,
+        "accountNo": "000000001"
+      },
+      "toOffice": {
+        "id": 1,
+        "name": "HO"
+      },
+      "toClient": {
+        "id": 1,
+        "displayName": "Small shop",
+        "officeId": 1,
+        "officeName": "HO"
+      },
+      "toAccountType": {
+        "id": 1,
+        "code": "accountType.loan",
+        "value": "Loan Account"
+      },
+      "toAccount": {
+        "id": 1,
+        "accountNo": "000000001"
+      }
+    },
+    {
+      "id": 2,
+      "reversed": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "transferAmount": 112.45,
+      "transferDate": [
+        2013,
+        6,
+        1
+      ],
+      "transferDescription": "A description of the transfer",
+      "fromOffice": {
+        "id": 1,
+        "name": "HO"
+      },
+      "fromClient": {
+        "id": 1,
+        "displayName": "Small shop",
+        "officeId": 1,
+        "officeName": "HO"
+      },
+      "fromAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "fromAccount": {
+        "id": 1,
+        "accountNo": "000000001"
+      },
+      "toOffice": {
+        "id": 1,
+        "name": "HO"
+      },
+      "toClient": {
+        "id": 1,
+        "displayName": "Small shop",
+        "officeId": 1,
+        "officeName": "HO"
+      },
+      "toAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "toAccount": {
+        "id": 3,
+        "accountNo": "000000003"
+      }
+    },
+    {
+      "id": 3,
+      "reversed": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "transferAmount": 112.45,
+      "transferDate": [
+        2013,
+        6,
+        1
+      ],
+      "transferDescription": "A description of the transfer",
+      "fromOffice": {
+        "id": 1,
+        "name": "HO"
+      },
+      "fromClient": {
+        "id": 1,
+        "displayName": "Small shop",
+        "officeId": 1,
+        "officeName": "HO"
+      },
+      "fromAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "fromAccount": {
+        "id": 1,
+        "accountNo": "000000001"
+      },
+      "toOffice": {
+        "id": 1,
+        "name": "HO"
+      },
+      "toClient": {
+        "id": 1,
+        "displayName": "Small shop",
+        "officeId": 1,
+        "officeName": "HO"
+      },
+      "toAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "toAccount": {
+        "id": 3,
+        "accountNo": "000000003"
+      }
+    },
+    {
+      "id": 4,
+      "reversed": false,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "transferAmount": 112.45,
+      "transferDate": [
+        2013,
+        6,
+        1
+      ],
+      "transferDescription": "A description of the transfer",
+      "fromOffice": {
+        "id": 1,
+        "name": "HO"
+      },
+      "fromClient": {
+        "id": 1,
+        "displayName": "Small shop",
+        "officeId": 1,
+        "officeName": "HO"
+      },
+      "fromAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "fromAccount": {
+        "id": 1,
+        "accountNo": "000000001"
+      },
+      "toOffice": {
+        "id": 1,
+        "name": "HO"
+      },
+      "toClient": {
+        "id": 1,
+        "displayName": "Small shop",
+        "officeId": 1,
+        "officeName": "HO"
+      },
+      "toAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "toAccount": {
+        "id": 3,
+        "accountNo": "000000003"
+      }
+    }
+  ]
+}
+			</code>
+        </div>
+    </div>
+
+	<a id="accounttransfers_retrieve" name="accounttransfers_retrieve" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve account transfer:</h2>
+	        <p>Example Requests : </p>
+	        <div class=apiClick>accounttransfers/1</div>
+	        <br>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/accounttransfers/{transferId}</code>
+	        <code class="method-response">
+{
+  "id": 1,
+  "reversed": false,
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "transferAmount": 200,
+  "transferDate": [
+    2013,
+    4,
+    1
+  ],
+  "transferDescription": "pay off loan from savings.",
+  "fromOffice": {
+    "id": 1,
+    "name": "HO"
+  },
+  "fromClient": {
+    "id": 1,
+    "displayName": "Small shop",
+    "officeId": 1,
+    "officeName": "HO"
+  },
+  "fromAccountType": {
+    "id": 2,
+    "code": "accountType.savings",
+    "value": "Savings Account"
+  },
+  "fromAccount": {
+    "id": 1,
+    "accountNo": "000000001"
+  },
+  "toOffice": {
+    "id": 1,
+    "name": "HO"
+  },
+  "toClient": {
+    "id": 1,
+    "displayName": "Small shop",
+    "officeId": 1,
+    "officeName": "HO"
+  },
+  "toAccountType": {
+    "id": 1,
+    "code": "accountType.loan",
+    "value": "Loan Account"
+  },
+  "toAccount": {
+    "id": 1,
+    "accountNo": "000000001"
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+		<a id="accounttransfers_retrieve_template_refund_by_transfer" name="accounttransfers_retrieve_template_refund_by_transfer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Refund of an Active Loan by Transfer Template:</h2>
+	        <p>Example Requests : </p>
+	        <div class=apiClick>accounttransfers/templateRefundByTransfer?fromAccountId=2&fromAccountType=1&
+	        toAccountId=1&toAccountType=2&toClientId=1&toOfficeId=1</div>
+	        <br>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/accounttransfers/templateRefundByTransfer</code>
+	        <code class="method-response">
+{
+   "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 0,
+      "inMultiplesOf": 0,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+   },
+   "transferAmount": 130,
+   "transferDate": [
+      2014,
+      11,
+      1
+   ],
+   "fromOffice": {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office",
+      "externalId": "1",
+      "openingDate": [
+         2009,
+         1,
+         1
+      ],
+      "hierarchy": "."
+   },
+   "fromClient": {
+      "id": 1,
+      "accountNo": "000000001",
+      "status": {
+         "id": 300,
+         "code": "clientStatusType.active",
+         "value": "Active"
+      },
+      "active": true,
+      "activationDate": [
+         2012,
+         2,
+         1
+      ],
+      "firstname": "Daniel",
+      "lastname": "Owusu",
+      "displayName": "Daniel Owusu",
+      "gender": {},
+      "clientType": {},
+      "clientClassification": {},
+      "officeId": 1,
+      "officeName": "Head Office",
+      "timeline": {
+         "submittedOnDate": [
+            2012,
+            2,
+            1
+         ],
+         "submittedByUsername": "mifos",
+         "submittedByFirstname": "App",
+         "submittedByLastname": "Administrator",
+         "activatedOnDate": [
+            2012,
+            2,
+            1
+         ],
+         "activatedByUsername": "mifos",
+         "activatedByFirstname": "App",
+         "activatedByLastname": "Administrator"
+      },
+      "groups": []
+   },
+   "fromAccountType": {
+      "id": 1,
+      "code": "accountType.loan",
+      "value": "Loan Account"
+   },
+   "fromAccount": {
+      "id": 2,
+      "accountNo": "000000002",
+      "clientId": 1,
+      "clientName": "Daniel Owusu",
+      "productId": 1,
+      "productName": "CTRL",
+      "fieldOfficerId": 0,
+      "currency": {
+         "code": "USD",
+         "name": "US Dollar",
+         "decimalPlaces": 0,
+         "inMultiplesOf": 0,
+         "displaySymbol": "$",
+         "nameCode": "currency.USD",
+         "displayLabel": "US Dollar ($)"
+      },
+      "amtForTransfer": 130
+   },
+   "toOffice": {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office",
+      "externalId": "1",
+      "openingDate": [
+         2009,
+         1,
+         1
+      ],
+      "hierarchy": "."
+   },
+   "toClient": {
+      "id": 1,
+      "displayName": "Daniel Owusu",
+      "officeId": 1,
+      "officeName": "Head Office"
+   },
+   "toAccountType": {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+   },
+   "toAccount": {
+      "id": 1,
+      "accountNo": "000000001",
+      "clientId": 1,
+      "clientName": "Daniel Owusu",
+      "productId": 1,
+      "productName": "TEST",
+      "fieldOfficerId": 0,
+      "currency": {
+         "code": "USD",
+         "name": "US Dollar",
+         "decimalPlaces": 0,
+         "inMultiplesOf": 0,
+         "displaySymbol": "$",
+         "nameCode": "currency.USD",
+         "displayLabel": "US Dollar ($)"
+      }
+   },
+   "fromOfficeOptions": [
+      {
+         "id": 1,
+         "name": "Head Office",
+         "nameDecorated": "Head Office"
+      }
+   ],
+   "fromClientOptions": [
+      {
+         "id": 1,
+         "displayName": "Daniel Owusu",
+         "officeId": 1,
+         "officeName": "Head Office"
+      }
+   ],
+   "fromAccountTypeOptions": [
+      {
+         "id": 2,
+         "code": "accountType.savings",
+         "value": "Savings Account"
+      },
+      {
+         "id": 1,
+         "code": "accountType.loan",
+         "value": "Loan Account"
+      }
+   ],
+   "fromAccountOptions": [
+      {
+         "id": 2,
+         "accountNo": "000000002",
+         "clientId": 1,
+         "clientName": "Daniel Owusu",
+         "productId": 1,
+         "productName": "CTRL",
+         "fieldOfficerId": 0,
+         "currency": {
+            "code": "USD",
+            "name": "US Dollar",
+            "decimalPlaces": 0,
+            "inMultiplesOf": 0,
+            "displaySymbol": "$",
+            "nameCode": "currency.USD",
+            "displayLabel": "US Dollar ($)"
+         }
+      }
+   ],
+   "toOfficeOptions": [
+      {
+         "id": 1,
+         "name": "Head Office",
+         "nameDecorated": "Head Office"
+      }
+   ],
+   "toClientOptions": [
+      {
+         "id": 1,
+         "displayName": "Daniel Owusu",
+         "officeId": 1,
+         "officeName": "Head Office"
+      }
+   ],
+   "toAccountTypeOptions": [
+      {
+         "id": 2,
+         "code": "accountType.savings",
+         "value": "Savings Account"
+      }
+   ],
+   "toAccountOptions": [
+      {
+         "id": 1,
+         "accountNo": "000000001",
+         "clientId": 1,
+         "clientName": "Daniel Owusu",
+         "productId": 1,
+         "productName": "TEST",
+         "fieldOfficerId": 0,
+         "currency": {
+            "code": "USD",
+            "name": "US Dollar",
+            "decimalPlaces": 0,
+            "inMultiplesOf": 0,
+            "displaySymbol": "$",
+            "nameCode": "currency.USD",
+            "displayLabel": "US Dollar ($)"
+         }
+      }
+   ]
+}
+	        </code>
+	    </div>
+	</div>
+
+
+		<a id="accounttransfers_refund_by_transfer" name="accounttransfers_refund_by_transfer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Refund of an Active Loan by Transfer</h2>
+	        <p>Ability to refund an active loan by transferring to a savings account.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/accounttransfers</code>
+	        <code class="method-request">POST refundByTransfer/
+Content-Type: application/json
+No Request Body:
+{
+   "fromAccountId": "2",
+   "fromAccountType": 1,
+   "toOfficeId": 1,
+   "toClientId": 1,
+   "toAccountType": 2,
+   "toAccountId": 1,
+   "transferAmount": 130,
+   "transferDate": "31 October 2014",
+   "transferDescription": "Transfer refund to my savings account",
+   "locale": "en",
+   "dateFormat": "dd MMMM yyyy",
+   "fromClientId": 1,
+   "fromOfficeId": 1
+
+}
+	        </code>
+	        <code class="method-response">
+{
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+<!-- end of acount transfers api -->
+
+<!-- Start Account Trasfer Standing Instrution-->
+
+<a id="standinginstruction" name="standinginstruction" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Standing Instructions</h2>
+	        <p>Standing instructions (or standing orders) refer to instructions a bank account holder ("the payer") gives to his or her bank to pay a set amount at regular intervals to another's ("the payee's") account.</p>
+	        <p>Note: At present only savings account to savings account and savings account to Loan account transfers are permitted.</p>
+	      	<table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Parameters</div></td></tr>
+
+				<tr class=alt><td>name</td></tr>
+				<tr><td class=fielddesc>A name for this Standing Instruction</td></tr>
+
+	            <tr class=alt><td>fromOfficeId</td></tr>
+				<tr><td class=fielddesc>The id of the office/branch from which the transfer is made.</td></tr>
+
+				<tr class=alt><td>fromClientId</td></tr>
+				<tr><td class=fielddesc>The id of the client (payer) who makes the transfer</td></tr>
+
+				<tr class=alt><td>fromAccountType</td></tr>
+				<tr><td class=fielddesc>The type of account from which the transfer is made. 1=Loan Account, 2=Savings Account</td></tr>
+
+				<tr class=alt><td>fromAccountId</td></tr>
+				<tr><td class=fielddesc>The id of the account from which the transfer is made.</td></tr>
+
+				<tr class=alt><td>toOfficeId</td></tr>
+				<tr><td class=fielddesc>The id of the office/branch to which the transfer is made.</td></tr>
+
+				<tr class=alt><td>toClientId</td></tr>
+				<tr><td class=fielddesc>The id of the client (payee), to whose account the transfer is made.</td></tr>
+
+				<tr class=alt><td>toAccountType</td></tr>
+				<tr><td class=fielddesc>The type of account to which the transfer is made. 1=Loan Account, 2=Savings Account</td></tr>
+
+				<tr class=alt><td>toAccountId</td></tr>
+				<tr><td class=fielddesc>The id of the account to which the transfer is made. The account must be <i>active</i> and must be in the same currency as that of the selected <b>fromAccountId</b>.</td></tr>
+
+				<tr class=alt><td>priority</td></tr>
+				<tr><td class=fielddesc>The priority of instruction while executing instructions. 1= URGENT,2 = HIGH, 3 = MEDIUM, 4 = LOW </td></tr>
+
+				<tr class=alt><td>transferType</td></tr>
+				<tr><td class=fielddesc>Identifies the source and destination account types. 1=Account Transfer(savings to savings), 2=Loan Repayment</td></tr>
+
+				<tr class=alt><td>instructionType</td></tr>
+				<tr><td class=fielddesc>Determines the amount to be transferred while executing a standing instruction. 1. FIXED, 2.DUES</td></tr>
+
+				<tr class=alt><td>status</td></tr>
+				<tr><td class=fielddesc>The Standing instruction state. 1. Active, 2.Disabled, 3.Deleted</td></tr>
+
+				<tr class=alt><td>recurrenceType</td></tr>
+				<tr><td class=fielddesc>Determines the recurrence of this standing instruction, can be either 1. Periodic or 2.As per dues</td></tr>
+
+				<tr class=alt><td>validFrom</td></tr>
+				<tr><td class=fielddesc>The Standing instruction's Start date.</td></tr>
+
+	        </table>
+	        <table class=matrixHeading>
+	            <tr class="matrixHeadingBG"><td><div class="fineractHeading2">Optional Parameters</div></td></tr>
+
+				<tr class=alt><td>amount</td></tr>
+				<tr><td class=fielddesc>Transfer amount while running the instruction. must be provided if the selected <b>instructionType</b> is fixed</td></tr>
+
+				<tr class=alt><td>validTill</td></tr>
+				<tr><td class=fielddesc>The Standing instruction's end date.</td></tr>
+
+				<tr class=alt><td>recurrenceFrequency</td></tr>
+				<tr><td class=fielddesc>The recurrence frequency of a Standing instruction's execution. Must be provided if the <b>recurrenceType</b> is Periodic. 0 = days, 1 = weekly, 2 = monthly, 3 = yearly</td></tr>
+
+				<tr class=alt><td>recurrenceInterval</td></tr>
+				<tr><td class=fielddesc>The recurrence interval of a standing instruction execution, determines the recurrence schedule when combined with <b>recurrenceFrequency</b>. Must be provided if the <b>recurrenceType</b> is periodic</td></tr>
+
+				<tr class=alt><td>recurrenceOnMonthDay</td></tr>
+				<tr><td class=fielddesc>The Month and Day of recurrence. Must be provided if the <b>recurrenceFrequency</b> is monthly or yearly</td></tr>
+
+
+			</table>
+	    </div>
+	</div>
+
+	<a id="standinginstruction_template" name="standinginstruction_template" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Standing Instruction Template:</h2>
+	            <p>This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:
+	                <ul>
+	                <li class=normalli>Field Defaults</li>
+	                <li class=normalli>Allowed Value Lists</li>
+	                </ul>
+	            </p>
+	        <p>Example Requests:</p>
+	        <div class=apiClick>standinginstructions/template?fromAccountType=2&fromOfficeId=1</div>
+	        <br/>
+	        <div class=apiClick>standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1</div>
+	        <br/>
+	        <div class=apiClick>standinginstructions/template?fromClientId=1&fromAccountType=2&fromAccountId=1&transferType=1</div>
+	        <br/>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/standinginstructions/template?fromAccountType=2&fromOfficeId=1
+	        </code>
+			<code class="method-response">
+				{
+  "fromOffice": {
+    "id": 1,
+    "name": "Head Office",
+    "nameDecorated": "Head Office",
+    "externalId": "1",
+    "openingDate": [
+      2009,
+      1,
+      1
+    ],
+    "hierarchy": "."
+  },
+  "fromAccountType": {
+    "id": 2,
+    "code": "accountType.savings",
+    "value": "Savings Account"
+  },
+  "fromOfficeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    }
+  ],
+  "fromClientOptions": [
+    {
+      "id": 1,
+      "displayName": "Client_FirstName_2VRAG Client_LastName_9QCY",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 20,
+      "displayName": "Client_FirstName_9KYLE Client_LastName_I0GJ",
+      "officeId": 1,
+      "officeName": "Head Office"
+    }
+  ],
+  "fromAccountTypeOptions": [
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    },
+    {
+      "id": 1,
+      "code": "accountType.loan",
+      "value": "Loan Account"
+    }
+  ],
+  "toOfficeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    }
+  ],
+  "toAccountTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountType.loan",
+      "value": "Loan Account"
+    },
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    }
+  ],
+  "transferTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountTransferType.account.transfer",
+      "value": "Account Transfer"
+    },
+    {
+      "id": 2,
+      "code": "accountTransferType.loan.repayment",
+      "value": "Loan Repayment"
+    }
+  ],
+  "statusOptions": [
+    {
+      "id": 1,
+      "code": "standingInstructionStatus.active",
+      "value": "Active"
+    },
+    {
+      "id": 2,
+      "code": "standingInstructionStatus.disabled",
+      "value": "Disabled"
+    }
+  ],
+  "instructionTypeOptions": [
+    {
+      "id": 1,
+      "code": "standingInstructionType.fixed",
+      "value": "Fixed"
+    },
+    {
+      "id": 2,
+      "code": "standingInstructionType.dues",
+      "value": "Dues"
+    }
+  ],
+  "priorityOptions": [
+    {
+      "id": 1,
+      "code": "standingInstructionPriority.urgent",
+      "value": "Urgent Priority"
+    },
+    {
+      "id": 2,
+      "code": "standingInstructionPriority.high",
+      "value": "High Priority"
+    },
+    {
+      "id": 3,
+      "code": "standingInstructionPriority.medium",
+      "value": "Medium Priority"
+    },
+    {
+      "id": 4,
+      "code": "standingInstructionPriority.low",
+      "value": "Low Priority"
+    }
+  ],
+  "recurrenceTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountTransferRecurrenceType.periodic",
+      "value": "Periodic Recurrence"
+    },
+    {
+      "id": 2,
+      "code": "accountTransferRecurrenceType.as.per.dues",
+      "value": "As Per Dues Recurrence"
+    }
+  ],
+  "recurrenceFrequencyOptions": [
+    {
+      "id": 0,
+      "code": "frequencyperiodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "frequencyperiodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "frequencyperiodFrequencyType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "frequencyperiodFrequencyType.years",
+      "value": "Years"
+    }
+  ]
+}
+			</code>
+	    </div>
+
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1
+	        </code>
+			<code class="method-response">
+				{
+  "fromOffice": {
+    "id": 1,
+    "name": "Head Office",
+    "nameDecorated": "Head Office",
+    "externalId": "1",
+    "openingDate": [
+      2009,
+      1,
+      1
+    ],
+    "hierarchy": "."
+  },
+  "fromClient": {
+    "id": 1,
+    "accountNo": "000000001",
+    "externalId": "ID_UTMYOEQ",
+    "status": {
+      "id": 300,
+      "code": "clientStatusType.active",
+      "value": "Active"
+    },
+    "active": true,
+    "activationDate": [
+      2011,
+      1,
+      1
+    ],
+    "firstname": "Client_FirstName_2VRAG",
+    "lastname": "Client_LastName_9QCY",
+    "displayName": "Client_FirstName_2VRAG Client_LastName_9QCY",
+    "officeId": 1,
+    "officeName": "Head Office",
+    "timeline": {
+      "submittedOnDate": [
+        2011,
+        1,
+        1
+      ],
+      "submittedByUsername": "mifos",
+      "submittedByFirstname": "App",
+      "submittedByLastname": "Administrator",
+      "activatedOnDate": [
+        2011,
+        1,
+        1
+      ],
+      "activatedByUsername": "mifos",
+      "activatedByFirstname": "App",
+      "activatedByLastname": "Administrator"
+    },
+    "groups": []
+  },
+  "fromAccountType": {
+    "id": 2,
+    "code": "accountType.savings",
+    "value": "Savings Account"
+  },
+  "fromOfficeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    }
+  ],
+  "fromClientOptions": [
+    {
+      "id": 1,
+      "displayName": "Client_FirstName_2VRAG Client_LastName_9QCY",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 2,
+      "displayName": "Client_FirstName_ZYDN2 Client_LastName_XVOP",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 3,
+      "displayName": "Client_FirstName_89LYT Client_LastName_4EY6",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 4,
+      "displayName": "Client_FirstName_PRCBG Client_LastName_JZU2",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 5,
+      "displayName": "Client_FirstName_J37GR Client_LastName_1T3X",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 6,
+      "displayName": "Client_FirstName_ZVHM2 Client_LastName_RUGS",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 7,
+      "displayName": "Client_FirstName_RBALP Client_LastName_437P",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 8,
+      "displayName": "Client_FirstName_R7M4V Client_LastName_Q5ED",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 9,
+      "displayName": "Client_FirstName_WIBDE Client_LastName_U91T",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 10,
+      "displayName": "Client_FirstName_26QJT Client_LastName_BEHD",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 11,
+      "displayName": "Client_FirstName_W071M Client_LastName_L7Z2",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 12,
+      "displayName": "Client_FirstName_QUHDJ Client_LastName_S4C5",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 13,
+      "displayName": "Client_FirstName_MNP4W Client_LastName_J8Y3",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 14,
+      "displayName": "Client_FirstName_TL6I8 Client_LastName_5YHG",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 15,
+      "displayName": "Client_FirstName_LUTBO Client_LastName_DITS",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 16,
+      "displayName": "Client_FirstName_UE39Z Client_LastName_PUWZ",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 17,
+      "displayName": "Client_FirstName_M8SD2 Client_LastName_J6QK",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 18,
+      "displayName": "Client_FirstName_SG8NF Client_LastName_BM1J",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 19,
+      "displayName": "Client_FirstName_BW0C8 Client_LastName_LGV9",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 20,
+      "displayName": "Client_FirstName_9KYLE Client_LastName_I0GJ",
+      "officeId": 1,
+      "officeName": "Head Office"
+    }
+  ],
+  "fromAccountTypeOptions": [
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    }
+  ],
+  "fromAccountOptions": [],
+  "toOfficeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    }
+  ],
+  "toAccountTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountType.loan",
+      "value": "Loan Account"
+    }
+  ],
+  "transferTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountTransferType.account.transfer",
+      "value": "Account Transfer"
+    },
+    {
+      "id": 2,
+      "code": "accountTransferType.loan.repayment",
+      "value": "Loan Repayment"
+    }
+  ],
+  "statusOptions": [
+    {
+      "id": 1,
+      "code": "standingInstructionStatus.active",
+      "value": "Active"
+    },
+    {
+      "id": 2,
+      "code": "standingInstructionStatus.disabled",
+      "value": "Disabled"
+    }
+  ],
+  "instructionTypeOptions": [
+    {
+      "id": 1,
+      "code": "standingInstructionType.fixed",
+      "value": "Fixed"
+    },
+    {
+      "id": 2,
+      "code": "standingInstructionType.dues",
+      "value": "Dues"
+    }
+  ],
+  "priorityOptions": [
+    {
+      "id": 1,
+      "code": "standingInstructionPriority.urgent",
+      "value": "Urgent Priority"
+    },
+    {
+      "id": 2,
+      "code": "standingInstructionPriority.high",
+      "value": "High Priority"
+    },
+    {
+      "id": 3,
+      "code": "standingInstructionPriority.medium",
+      "value": "Medium Priority"
+    },
+    {
+      "id": 4,
+      "code": "standingInstructionPriority.low",
+      "value": "Low Priority"
+    }
+  ],
+  "recurrenceTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountTransferRecurrenceType.periodic",
+      "value": "Periodic Recurrence"
+    },
+    {
+      "id": 2,
+      "code": "accountTransferRecurrenceType.as.per.dues",
+      "value": "As Per Dues Recurrence"
+    }
+  ],
+  "recurrenceFrequencyOptions": [
+    {
+      "id": 0,
+      "code": "frequencyperiodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "frequencyperiodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "frequencyperiodFrequencyType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "frequencyperiodFrequencyType.years",
+      "value": "Years"
+    }
+  ]
+}
+			</code>
+	    </div>
+
+	   	<div class="method-example">
+	        <code class="method-declaration">GET https://Domain Name/api/v1/standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=2&fromAccountId=1
+	        </code>
+			<code class="method-response">
+{
+  "fromOffice": {
+    "id": 1,
+    "name": "Head Office",
+    "nameDecorated": "Head Office",
+    "externalId": "1",
+    "openingDate": [
+      2009,
+      1,
+      1
+    ],
+    "hierarchy": "."
+  },
+  "fromClient": {
+    "id": 3,
+    "accountNo": "000000003",
+    "externalId": "ID_ECEAKAP",
+    "status": {
+      "id": 300,
+      "code": "clientStatusType.active",
+      "value": "Active"
+    },
+    "active": true,
+    "activationDate": [
+      2011,
+      3,
+      4
+    ],
+    "firstname": "Client_FirstName_89LYT",
+    "lastname": "Client_LastName_4EY6",
+    "displayName": "Client_FirstName_89LYT Client_LastName_4EY6",
+    "officeId": 1,
+    "officeName": "Head Office",
+    "timeline": {
+      "submittedOnDate": [
+        2011,
+        3,
+        4
+      ],
+      "submittedByUsername": "mifos",
+      "submittedByFirstname": "App",
+      "submittedByLastname": "Administrator",
+      "activatedOnDate": [
+        2011,
+        3,
+        4
+      ],
+      "activatedByUsername": "mifos",
+      "activatedByFirstname": "App",
+      "activatedByLastname": "Administrator"
+    },
+    "groups": []
+  },
+  "fromAccountType": {
+    "id": 2,
+    "code": "accountType.savings",
+    "value": "Savings Account"
+  },
+  "fromAccount": {
+    "id": 1,
+    "accountNo": "000000001",
+    "clientId": 3,
+    "clientName": "Client_FirstName_89LYT Client_LastName_4EY6",
+    "productId": 1,
+    "productName": "SAVINGS_PRODUCT_MVA619",
+    "fieldOfficerId": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 4,
+      "inMultiplesOf": 0,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    }
+  },
+  "fromOfficeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    }
+  ],
+  "fromClientOptions": [
+    {
+      "id": 1,
+      "displayName": "Client_FirstName_2VRAG Client_LastName_9QCY",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 2,
+      "displayName": "Client_FirstName_ZYDN2 Client_LastName_XVOP",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 3,
+      "displayName": "Client_FirstName_89LYT Client_LastName_4EY6",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 4,
+      "displayName": "Client_FirstName_PRCBG Client_LastName_JZU2",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 5,
+      "displayName": "Client_FirstName_J37GR Client_LastName_1T3X",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 6,
+      "displayName": "Client_FirstName_ZVHM2 Client_LastName_RUGS",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 7,
+      "displayName": "Client_FirstName_RBALP Client_LastName_437P",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 8,
+      "displayName": "Client_FirstName_R7M4V Client_LastName_Q5ED",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 9,
+      "displayName": "Client_FirstName_WIBDE Client_LastName_U91T",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 10,
+      "displayName": "Client_FirstName_26QJT Client_LastName_BEHD",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 11,
+      "displayName": "Client_FirstName_W071M Client_LastName_L7Z2",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 12,
+      "displayName": "Client_FirstName_QUHDJ Client_LastName_S4C5",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 13,
+      "displayName": "Client_FirstName_MNP4W Client_LastName_J8Y3",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 14,
+      "displayName": "Client_FirstName_TL6I8 Client_LastName_5YHG",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 15,
+      "displayName": "Client_FirstName_LUTBO Client_LastName_DITS",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 16,
+      "displayName": "Client_FirstName_UE39Z Client_LastName_PUWZ",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 17,
+      "displayName": "Client_FirstName_M8SD2 Client_LastName_J6QK",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 18,
+      "displayName": "Client_FirstName_SG8NF Client_LastName_BM1J",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 19,
+      "displayName": "Client_FirstName_BW0C8 Client_LastName_LGV9",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 20,
+      "displayName": "Client_FirstName_9KYLE Client_LastName_I0GJ",
+      "officeId": 1,
+      "officeName": "Head Office"
+    }
+  ],
+  "fromAccountTypeOptions": [
+    {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    }
+  ],
+  "fromAccountOptions": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "clientId": 3,
+      "clientName": "Client_FirstName_89LYT Client_LastName_4EY6",
+      "productId": 1,
+      "productName": "SAVINGS_PRODUCT_MVA619",
+      "fieldOfficerId": 0,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 4,
+        "inMultiplesOf": 0,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      }
+    }
+  ],
+  "toOfficeOptions": [
+    {
+      "id": 1,
+      "name": "Head Office",
+      "nameDecorated": "Head Office"
+    }
+  ],
+  "toAccountTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountType.loan",
+      "value": "Loan Account"
+    }
+  ],
+  "transferTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountTransferType.account.transfer",
+      "value": "Account Transfer"
+    },
+    {
+      "id": 2,
+      "code": "accountTransferType.loan.repayment",
+      "value": "Loan Repayment"
+    }
+  ],
+  "statusOptions": [
+    {
+      "id": 1,
+      "code": "standingInstructionStatus.active",
+      "value": "Active"
+    },
+    {
+      "id": 2,
+      "code": "standingInstructionStatus.disabled",
+      "value": "Disabled"
+    }
+  ],
+  "instructionTypeOptions": [
+    {
+      "id": 1,
+      "code": "standingInstructionType.fixed",
+      "value": "Fixed"
+    },
+    {
+      "id": 2,
+      "code": "standingInstructionType.dues",
+      "value": "Dues"
+    }
+  ],
+  "priorityOptions": [
+    {
+      "id": 1,
+      "code": "standingInstructionPriority.urgent",
+      "value": "Urgent Priority"
+    },
+    {
+      "id": 2,
+      "code": "standingInstructionPriority.high",
+      "value": "High Priority"
+    },
+    {
+      "id": 3,
+      "code": "standingInstructionPriority.medium",
+      "value": "Medium Priority"
+    },
+    {
+      "id": 4,
+      "code": "standingInstructionPriority.low",
+      "value": "Low Priority"
+    }
+  ],
+  "recurrenceTypeOptions": [
+    {
+      "id": 1,
+      "code": "accountTransferRecurrenceType.periodic",
+      "value": "Periodic Recurrence"
+    },
+    {
+      "id": 2,
+      "code": "accountTransferRecurrenceType.as.per.dues",
+      "value": "As Per Dues Recurrence"
+    }
+  ],
+  "recurrenceFrequencyOptions": [
+    {
+      "id": 0,
+      "code": "frequencyperiodFrequencyType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "frequencyperiodFrequencyType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "frequencyperiodFrequencyType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "frequencyperiodFrequencyType.years",
+      "value": "Years"
+    }
+  ]
+}
+			</code>
+	    </div>
+
+	</div>
+
+	<a id="standinginstruction_create" name="standinginstruction_create" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Create new Standing Instruction</h2>
+	        <p>Ability to create new instruction for transfer of monetary funds from one account to another.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://Domain Name/api/v1/standinginstructions</code>
+	        <code class="method-request">POST standinginstructions/
+Content-Type: application/json
+No Request Body:
+{
+	"fromOfficeId":1,
+	"fromClientId":1,
+	"fromAccountType":2,
+	"name":"standing instruction",
+	"transferType":1,
+	"priority":2,
+	"status":1,
+	"fromAccountId":1,
+	"toOfficeId":1,
+	"toClientId":1,
+	"toAccountType":2,
+	"toAccountId":3,
+	"instructionType":1,
+	"amount":"221",
+	"validFrom":"08 April 2014",
+	"recurrenceType":1,
+	"recurrenceInterval":"1",
+	"recurrenceFrequency":2,
+	"locale":"en",
+	"dateFormat":"dd MMMM yyyy",
+	"recurrenceOnMonthDay":"02 April",
+	"monthDayFormat":"dd MMMM"
+}
+	        </code>
+	        <code class="method-response">
+{
+
+"clientId":1,
+"resourceId":65
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="standinginstruction_update" name="standinginstruction_update" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Update Standing Instruction</h2>
+	        <p>Ability to modify existing instruction for transfer of monetary funds from one account to another. </p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">PUT https://Domain Name/api/v1/standinginstructions/1?command=update</code>
+	        <code class="method-request">PUT standinginstructions/1?command=update
+Content-Type: application/json
+No Request Body:
+{
+	"recurrenceInterval":"2"
+}
+	        </code>
+	        <code class="method-response">
+{
+	"resourceId":20,
+	"changes":{
+		"recurrenceInterval":2
+	}
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="standinginstruction_delete" name="standinginstruction_delete" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Delete Standing Instruction</h2>
+	        <p>Ability to modify existing instruction for transfer of monetary funds from one account to another. </p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">PUT https://Domain Name/api/v1/standinginstructions/1?command=delete</code>
+	        <code class="method-request">PUT standinginstructions/1?command=delete
+Content-Type: application/json
+No Request Body:
+{
+
+}
+	        </code>
+	        <code class="method-response">
+{
+	"resourceId":20,
+	"changes":{
+		"status":3
+	}
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="standinginstructions_list" name="standinginstructions_list" class="old-syle-anchor">&nbsp;</a>
+    <div class="method-section">
+        <div class="method-description">
+            <h2>List Standing Instructions</h2>
+            <p>Example Requests:</p>
+            <div class=apiClick>standinginstructions</div>
+            <br>
+        </div>
+        <div class="method-example">
+            <code class="method-declaration">GET https://Domain Name/api/v1/standinginstructions</code>
+            <code class="method-response">
+{
+  "totalFilteredRecords": 2,
+  "pageItems": [
+    {
+      "id": 1,
+      "accountDetailId": 6,
+      "name": "test standing",
+      "fromOffice": {
+        "id": 1,
+        "name": "Head Office"
+      },
+      "fromClient": {
+        "id": 1,
+        "displayName": "Test test",
+        "officeId": 1,
+        "officeName": "Head Office"
+      },
+      "fromAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "fromAccount": {
+        "id": 14,
+        "accountNo": "000000014",
+        "productId": 1,
+        "productName": "savings old"
+      },
+      "toOffice": {
+        "id": 1,
+        "name": "Head Office"
+      },
+      "toClient": {
+        "id": 1,
+        "displayName": "Test test",
+        "officeId": 1,
+        "officeName": "Head Office"
+      },
+      "toAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "toAccount": {
+        "id": 3,
+        "accountNo": "000000003",
+        "productId": 4,
+        "productName": "account overdraft"
+      },
+      "transferType": {
+        "id": 1,
+        "code": "accountTransferType.account.transfer",
+        "value": "Account Transfer"
+      },
+      "priority": {
+        "id": 3,
+        "code": "standingInstructionPriority.medium",
+        "value": "Medium Priority"
+      },
+      "instructionType": {
+        "id": 1,
+        "code": "standingInstructionType.fixed",
+        "value": "Fixed"
+      },
+      "status": {
+        "id": 3,
+        "code": "standingInstructionStatus.deleted",
+        "value": "Deleted"
+      },
+      "amount": 150.000000,
+      "validFrom": [
+        2014,
+        4,
+        3
+      ],
+      "recurrenceType": {
+        "id": 1,
+        "code": "accountTransferRecurrenceType.periodic",
+        "value": "Periodic Recurrence"
+      },
+      "recurrenceFrequency": {
+        "id": 2,
+        "code": "recurrenceperiodFrequencyType.months",
+        "value": "Months"
+      },
+      "recurrenceInterval": 1,
+      "recurrenceOnMonthDay": [
+        4,
+        3
+      ]
+    },
+    {
+      "id": 2,
+      "accountDetailId": 7,
+      "name": "test standing 2",
+      "fromOffice": {
+        "id": 1,
+        "name": "Head Office"
+      },
+      "fromClient": {
+        "id": 1,
+        "displayName": "Test test",
+        "officeId": 1,
+        "officeName": "Head Office"
+      },
+      "fromAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "fromAccount": {
+        "id": 14,
+        "accountNo": "000000014",
+        "productId": 1,
+        "productName": "savings old"
+      },
+      "toOffice": {
+        "id": 1,
+        "name": "Head Office"
+      },
+      "toClient": {
+        "id": 1,
+        "displayName": "Test test",
+        "officeId": 1,
+        "officeName": "Head Office"
+      },
+      "toAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "toAccount": {
+        "id": 3,
+        "accountNo": "000000003",
+        "productId": 4,
+        "productName": "account overdraft"
+      },
+      "transferType": {
+        "id": 1,
+        "code": "accountTransferType.account.transfer",
+        "value": "Account Transfer"
+      },
+      "priority": {
+        "id": 2,
+        "code": "standingInstructionPriority.high",
+        "value": "High Priority"
+      },
+      "instructionType": {
+        "id": 1,
+        "code": "standingInstructionType.fixed",
+        "value": "Fixed"
+      },
+      "status": {
+        "id": 3,
+        "code": "standingInstructionStatus.deleted",
+        "value": "Deleted"
+      },
+      "amount": 100.000000,
+      "validFrom": [
+        2014,
+        4,
+        3
+      ],
+      "recurrenceType": {
+        "id": 1,
+        "code": "accountTransferRecurrenceType.periodic",
+        "value": "Periodic Recurrence"
+      },
+      "recurrenceFrequency": {
+        "id": 2,
+        "code": "recurrenceperiodFrequencyType.months",
+        "value": "Months"
+      },
+      "recurrenceInterval": 1,
+      "recurrenceOnMonthDay": [
+        2,
+        1
+      ]
+    }
+  ]
+}			</code>
+        </div>
+    </div>
+
+	<a id="standinginstructions_retrieve" name="standinginstructions_retrieve" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Standing Instruction:</h2>
+	        <p>Example Requests : </p>
+	        <div class=apiClick>standinginstructions/1</div>
+	        <br>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/standinginstructions/{standingInstructionId}</code>
+	        <code class="method-response">
+{
+  "id": 1,
+  "accountDetailId": 6,
+  "name": "test standing",
+  "fromOffice": {
+    "id": 1,
+    "name": "Head Office"
+  },
+  "fromClient": {
+    "id": 1,
+    "displayName": "Test test",
+    "officeId": 1,
+    "officeName": "Head Office"
+  },
+  "fromAccountType": {
+    "id": 2,
+    "code": "accountType.savings",
+    "value": "Savings Account"
+  },
+  "fromAccount": {
+    "id": 14,
+    "accountNo": "000000014",
+    "productId": 1,
+    "productName": "savings old"
+  },
+  "toOffice": {
+    "id": 1,
+    "name": "Head Office"
+  },
+  "toClient": {
+    "id": 1,
+    "displayName": "Test test",
+    "officeId": 1,
+    "officeName": "Head Office"
+  },
+  "toAccountType": {
+    "id": 2,
+    "code": "accountType.savings",
+    "value": "Savings Account"
+  },
+  "toAccount": {
+    "id": 3,
+    "accountNo": "000000003",
+    "productId": 4,
+    "productName": "account overdraft"
+  },
+  "transferType": {
+    "id": 1,
+    "code": "accountTransferType.account.transfer",
+    "value": "Account Transfer"
+  },
+  "priority": {
+    "id": 3,
+    "code": "standingInstructionPriority.medium",
+    "value": "Medium Priority"
+  },
+  "instructionType": {
+    "id": 1,
+    "code": "standingInstructionType.fixed",
+    "value": "Fixed"
+  },
+  "status": {
+    "id": 3,
+    "code": "standingInstructionStatus.deleted",
+    "value": "Deleted"
+  },
+  "amount": 150.000000,
+  "validFrom": [
+    2014,
+    4,
+    3
+  ],
+  "recurrenceType": {
+    "id": 1,
+    "code": "accountTransferRecurrenceType.periodic",
+    "value": "Periodic Recurrence"
+  },
+  "recurrenceFrequency": {
+    "id": 2,
+    "code": "recurrenceperiodFrequencyType.months",
+    "value": "Months"
+  },
+  "recurrenceInterval": 1,
+  "recurrenceOnMonthDay": [
+    4,
+    3
+  ]
+}
+	        </code>
+	    </div>
+	</div>
+
+	<a id="standinginstructions_history" name="standinginstructions_history" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Standing Instructions Logged History:</h2>
+            <p>The <i>list</i> capability of history can support <b>pagination</b> and <b>sorting</b>.</p>
+            <h5>Optional Arguments</h5>
+            <dl class="argument-list">
+                <dt>offset</dt>
+                <dd>
+                    Integer <span>optional</span>, defaults to 0
+                </dd>
+                <dd>Indicates from what result to start from.</dd>
+
+                <dt>limit</dt>
+                <dd>
+                    Integer <span>optional</span>, defaults to 200
+                </dd>
+                <dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+                <dt>orderBy</dt>
+                <dd>
+                    String <span>optional</span>, one of <span>name,standingInstructionId</span>
+                </dd>
+                <dd>Orders the results by the field indicated.</dd>
+
+                <dt>sortBy</dt>
+                <dd>
+                    String <span>optional</span>, one of <span>ASC, DESC</span>
+                </dd>
+                <dd>Indicates what way to order results if <i>orderBy</i> is used.</dd>
+
+                <dt>clientId</dt>
+                <dd>
+                    Integer <span>optional</span>
+                </dd>
+                <dd>Use clientId of clients to restrict results.</dd>
+
+                <dt>clientName</dt>
+                <dd>
+                    String <span>optional</span>
+                </dd>
+                <dd>Use displayName of clients to restrict results.</dd>
+
+                <dt>fromAccountId</dt>
+                <dd>
+                    Integer <span>optional</span>
+                </dd>
+                <dd>Use fromAccountId of standing instruction transaction to restrict results. fromAccountId is id of <i>fromAccountType</i>.</dd>
+
+                <dt>fromAccountType</dt>
+                <dd>
+                    Integer <span>optional</span>
+                </dd>
+                <dd>Use fromAccountType of standing instruction transaction to restrict results. fromAccountType is enum value entity type Ex:Loan Account:1, Savings Account:2</dd>
+
+                <dt>transferType</dt>
+                <dd>
+                    Integer <span>optional</span>
+                </dd>
+                <dd>Use transferType of standing instruction transaction to restrict results. transferType is enum value transfer type Ex:Loan Repayment:2, Account Transfer:1</dd>
+
+                <dt>fromDate</dt>
+                <dd>
+                    Date<span>optional</span>
+                </dd>
+                <dd>Filters for transactions whose entry Date is greater than or equal to the passed in Date
+                </dd>
+                <dt>toDate</dt>
+                <dd>
+                    Date <span>optional</span>
+                </dd>
+                <dd>Filters for transactions whose entry Date is lesser than or equal to the passed in Date
+                </dd>
+
+                <dt>sqlSearch</dt>
+                <dd>
+                    String <span>optional</span>
+                </dd>
+                <dd>Use an sql fragment valid for the underlying standing instruction schema to filter results. e.g. name like %K%</dd>
+            </dl>
+	        <p>Example Requests : </p>
+	        <div class=apiClick>standinginstructionrunhistory</div>
+	        <br>
+            <div class=apiClick>standinginstructionrunhistory?orderBy=name&sortOrder=DESC</div>
+            <br>
+            <div class=apiClick>standinginstructionrunhistory?offset=10&limit=50</div>
+            <br>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/standinginstructionrunhistory</code>
+	        <code class="method-response">
+{
+  "totalFilteredRecords": 2,
+  "pageItems": [
+    {
+      "standingInstructionId": 1,
+      "name": "ACC Transfer",
+      "fromOffice": {
+        "id": 1,
+        "name": "Head Office"
+      },
+      "fromClient": {
+        "id": 1,
+        "displayName": "Test client",
+        "officeId": 1,
+        "officeName": "Head Office"
+      },
+      "fromAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "fromAccount": {
+        "id": 2,
+        "accountNo": "000000002",
+        "productId": 1,
+        "productName": "General Savings"
+      },
+      "toAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "toAccount": {
+        "id": 1,
+        "accountNo": "000000001",
+        "productId": 1,
+        "productName": "General Savings"
+      },
+      "toOffice": {
+        "id": 1,
+        "name": "Head Office"
+      },
+      "toClient": {
+        "id": 1,
+        "displayName": "Test client",
+        "officeId": 1,
+        "officeName": "Head Office"
+      },
+      "amount": 10,
+      "status": "success",
+      "executionTime": [
+        2014,
+        6,
+        30
+      ],
+      "errorLog": ""
+    },
+    {
+      "standingInstructionId": 2,
+      "name": "Pay overdues",
+      "fromOffice": {
+        "id": 1,
+        "name": "Head Office"
+      },
+      "fromClient": {
+        "id": 1,
+        "displayName": "Test client",
+        "officeId": 1,
+        "officeName": "Head Office"
+      },
+      "fromAccountType": {
+        "id": 2,
+        "code": "accountType.savings",
+        "value": "Savings Account"
+      },
+      "fromAccount": {
+        "id": 1,
+        "accountNo": "000000001",
+        "productId": 1,
+        "productName": "General Savings"
+      },
+      "toAccountType": {
+        "id": 1,
+        "code": "accountType.loan",
+        "value": "Loan Account"
+      },
+      "toAccount": {
+        "id": 1,
+        "accountNo": "000000001",
+        "productId": 1,
+        "productName": "Daily Loan"
+      },
+      "toOffice": {
+        "id": 1,
+        "name": "Head Office"
+      },
+      "toClient": {
+        "id": 1,
+        "displayName": "Test client",
+        "officeId": 1,
+        "officeName": "Head Office"
+      },
+      "amount": 7038.01,
+      "status": "success",
+      "executionTime": [
+        2014,
+        6,
+        30
+      ],
+      "errorLog": ""
+    }
+  ]
+}
+	        </code>
+	    </div>
+	</div>
+
+<!-- End Account Trasfer Standing Instrution-->
+
+<!-- start of datatables api docs -->
+			<a id="datatables" name="datatables" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Data Tables</h3>
+					<p>The datatables API allows you to plug-in your own tables
+						(MySql) that have a relationship to a Apache Fineract core table. For
+						example, you might want to add some extra client fields and record
+						information about each of the clients' family members. Via the API
+						you can create, read, update and delete entries for each
+						'plugged-in' table. The API checks for permission and for 'data
+						scoping' (only data within the users' office hierarchy can be
+						managed by the user).</p>
+					<p>The Apache Fineract Reference App uses a JQuery plug-in called
+						stretchydatatables (which in turn uses this datatables resource)
+						to provide a pretty flexible CRUD (Create, Read, Update, Delete)
+						User Interface.</p>
+					<p>
+						<a
+							href="https://mifosforge.jira.com/wiki/display/MIFOSX/Mifos+X+-+Plugging+In+Non-Core+and+User+Defined+Data">More
+							Documentation</a>
+					</p>
+				</div>
+			</div>
+
+			<a id="datatables_createTable" name="datatables_createTable"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create Data Table</h4>
+                    <p>Create a new data table and registers it with the Apache Fineract Core application
+                        table.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory - </strong> datatableName</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>The name of the Data Table.</td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory - </strong> apptableName</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>
+                                Application table name. Must be one of the following:
+                                <ul class="field">
+                                    <li>m_client</li>
+                                    <li>m_group</li>
+                                    <li>m_loan</li>
+                                    <li>m_office</li>
+                                    <li>m_saving_account</li>
+				    <li>m_product_loan</li>
+				    <li>m_savings_product</li>
+                                </ul>
+                            </td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory - </strong>columns</td>
+                        </tr>
+						<tr>
+                            <td class=fielddesc>An array of columns in the new Data Table.</td>
+						</tr>
+                        <tr class=alt>
+                            <td><em>Optional - </em>multiRow</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Allows to create multiple entries in the Data Table. Optional, defaults to <tt>false</tt>.
+                                If this property is not provided Data Table will allow only one entry.
+                            </td>
+                        </tr>
+                    </table>
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions - columns</div></td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory - </strong>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name of the created column. Can contain only alphanumeric characters, underscores and spaces, but cannot start with a number. Cannot start or end with an underscore or space.</td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory - </strong>type</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>
+                                Column type. Must be one of the following:
+                                <ul class="field">
+                                    <li>Boolean</li>
+                                    <li>Date</li>
+                                    <li>DateTime</li>
+                                    <li>Decimal</li>
+                                    <li>Dropdown</li>
+                                    <li>Number</li>
+                                    <li>String</li>
+                                    <li>Text</li>
+                                </ul>
+                            </td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory [type = Dropdown] - </strong>code</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>Used in Code Value fields. Column name becomes: <tt>code_cd_name</tt>. Mandatory if using type Dropdown, otherwise an error is returned.</td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional - </em>mandatory</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>Determines whether this column must have a value in every entry. Optional, defaults to <tt>false</tt>.</td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory [type = String] - </strong>length</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Length of the text field. Mandatory if type String is used, otherwise an error is returned.</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/datatables
+                    </code>
+                    <code class="method-request">
+POST https://DomainName/api/v1/datatables
+
+Content-Type: application/json
+Request Body:
+{
+    "datatableName": "extra_client_details",
+    "apptableName": "m_client",
+    "columns": [
+        {
+            "name": "Gender",
+            "type": "Dropdown",
+            "code": "Gender"
+        },
+        {
+            "name": "Some Decimal",
+            "type": "Decimal",
+            "mandatory": true
+        },
+        {
+            "name": "Birth Date",
+            "type": "Date"
+        },
+        {
+            "name": "Question 2",
+            "type": "String",
+            "length": 100,
+            "mandatory": false
+        }
+    ]
+}
+					</code>
+					<code class="method-response">
+{
+  "resourceIdentifier": "extra_client_details"
+}
+				</code>
+                <code class="method-request">
+POST https://DomainName/api/v1/datatables
+
+Content-Type: application/json
+Request Body:
+{
+    "datatableName": "client_address",
+    "apptableName": "m_client",
+    "multiRow":"true",
+    "columns": [
+        {
+            "name": "Address1",
+            "type": "String",
+            "length": 100,
+            "mandatory": true
+        },
+        {
+            "name": "Address2",
+            "length": 100,
+            "type": "String"
+        }
+    ]
+}
+                </code>
+                <code class="method-response">
+{
+    "resourceIdentifier": "client_address"
+}
+                </code>
+
+				</div>
+			</div>
+
+			<a id="datatables_list" name="datatables_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Data Tables</h4>
+					<p>Lists registered data tables and the Apache Fineract Core
+						application table they are registered to.</p>
+
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>apptable</dt>
+						<dd>
+							<span>optional</span>
+						</dd>
+						<dd>The Apache Fineract core application table.</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>datatables?apptable=m_client</div>
+					<br>
+					<br>
+					<div class=apiClick>datatables</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/datatables
+					</code>
+                    <code class="method-response">
+[
+  {
+    "applicationTableName": "m_client",
+    "registeredTableName": "extra_client_details",
+    "columnHeaderData": [
+      {
+        "columnName": "client_id",
+        "columnType": "bigint",
+        "columnLength": 0,
+        "columnDisplayType": "INTEGER",
+        "isColumnNullable": false,
+        "isColumnPrimaryKey": true,
+        "columnValues": []
+      },
+      {
+        "columnName": "Gender_cd_Question",
+        "columnType": "int",
+        "columnLength": 0,
+        "columnDisplayType": "INTEGER",
+        "isColumnNullable": true,
+        "isColumnPrimaryKey": false,
+        "columnValues": []
+      },
+      {
+        "columnName": "Some Decimal",
+        "columnType": "decimal",
+        "columnLength": 0,
+        "columnDisplayType": "DECIMAL",
+        "isColumnNullable": true,
+        "isColumnPrimaryKey": false,
+        "columnValues": []
+      },
+      {
+        "columnName": "Birth Date",
+        "columnType": "date",
+        "columnLength": 0,
+        "columnDisplayType": "DATE",
+        "isColumnNullable": true,
+        "isColumnPrimaryKey": false,
+        "columnValues": []
+      }
+    ]
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="datatables_getTable" name="datatables_getTable"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+                    <h4>Retrieve Data Table Details</h4>
+					<p>Lists a registered data table details and the Apache Fineract Core
+						application table they are registered to.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/datatables/{datatable}
+					</code>
+                    <code class="method-response">
+{
+   "applicationTableName": "m_client",
+   "registeredTableName": "extra_client_details",
+   "columnHeaderData": [
+      {
+        "columnName": "client_id",
+        "columnType": "bigint",
+        "columnLength": 0,
+        "columnDisplayType": "INTEGER",
+        "isColumnNullable": false,
+        "isColumnPrimaryKey": true,
+        "columnValues": []
+      },
+      {
+        "columnName": "Gender_cd_Question",
+        "columnType": "int",
+        "columnLength": 0,
+        "columnDisplayType": "INTEGER",
+        "isColumnNullable": true,
+        "isColumnPrimaryKey": false,
+        "columnValues": []
+      },
+      {
+        "columnName": "Some Decimal",
+        "columnType": "decimal",
+        "columnLength": 0,
+        "columnDisplayType": "DECIMAL",
+        "isColumnNullable": true,
+        "isColumnPrimaryKey": false,
+        "columnValues": []
+      },
+      {
+        "columnName": "Birth Date",
+        "columnType": "date",
+        "columnLength": 0,
+        "columnDisplayType": "DATE",
+        "isColumnNullable": true,
+        "isColumnPrimaryKey": false,
+        "columnValues": []
+      }
+   ]
+}
+				</code>
+				</div>
+			</div>
+
+			<a id="datatables_updateTable" name="datatables_updateTable"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update Data Table</h4>
+                    <p>Modifies fields of a data table. If the apptableName parameter is passed,
+                        data table is deregistered and registered with the new application table.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td><em>Optional - </em> apptableName</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>
+                                Application table name. Only necessary if changing the application table this Data Table is registered to. Must be one of the following:
+                                <ul class="field">
+                                    <li>m_client</li>
+                                    <li>m_group</li>
+                                    <li>m_loan</li>
+                                    <li>m_office</li>
+                                    <li>m_saving_account</li>
+                                </ul>
+                            </td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional - </em>dropColumns</td>
+                        </tr>
+						<tr>
+                            <td class=fielddesc>An array of columns to be deleted from the Data Table.</td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional - </em>addColumns</td>
+                        </tr>
+						<tr>
+                            <td class=fielddesc>An array of columns to be added to the Data Table.</td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional - </em>changeColumns</td>
+                        </tr>
+						<tr>
+                            <td class=fielddesc>An array of columns to be changed in the Data Table.</td>
+						</tr>
+                    </table>
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions - dropColumns</div></td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory - </strong>name</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>Requires a full name of the deleted column to be provided.</td>
+						</tr>
+					</table>
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions - addColumns</div></td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory - </strong>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name of the created column. Can contain only alphanumeric characters, underscores and spaces, but cannot start with a number. Cannot start or end with an underscore or space.</td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory - </strong>type</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>
+                                Column type. Must be one of the following:
+                                <ul class="field">
+                                    <li>Boolean</li>
+                                    <li>Date</li>
+                                    <li>DateTime</li>
+                                    <li>Decimal</li>
+                                    <li>Dropdown</li>
+                                    <li>Number</li>
+                                    <li>String</li>
+                                    <li>Text</li>
+                                </ul>
+                            </td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory [type = Dropdown] - </strong>code</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>Used in Code Value fields. Column name becomes: <tt>code_cd_name</tt>. Mandatory if using type Dropdown, otherwise an error is returned.</td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional - </em>mandatory</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>Determines whether this column must have a value in every entry. Optional, defaults to <tt>false</tt>.</td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory [type = String] - </strong>length</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Length of the text field. Mandatory if type String is used, otherwise an error is returned.</td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional - </em>after</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Only used when re-ordering Data Table columns. Requires a full column name to be provided.</td>
+						</tr>
+					</table>
+                    <table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions - changeColumns</div></td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory - </strong>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name of the created column. Can contain only alphanumeric characters, underscores and spaces, but cannot start with a number. Cannot start or end with an underscore or space.</td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional - </em>newName</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>New name of the created column. Can contain only alphanumeric characters, underscores and spaces, but cannot start with a number. Cannot start or end with an underscore or space.</td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory [type = Dropdown] - </strong>code</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>Used in Code Value fields. Column name becomes: <tt>code_cd_name</tt>. Mandatory if using type Dropdown, otherwise an error is returned.</td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional [type = Dropdown] - </em>newCode</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>Used in Code Value fields. Column name becomes: <tt>code_cd_name</tt>. Optional if using type Dropdown, otherwise an error is returned.</td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional - </em>mandatory</td>
+						</tr>
+						<tr>
+                            <td class=fielddesc>Determines whether this column must have a value in every entry. Optional, defaults to <tt>false</tt>.</td>
+						</tr>
+						<tr class=alt>
+                            <td><strong>Mandatory [type = String] - </strong>length</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Length of the text field. Mandatory if type String is used, otherwise an error is returned.</td>
+						</tr>
+						<tr class=alt>
+                            <td><em>Optional - </em>after</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Only used when re-ordering Data Table columns. Requires a full column name to be provided.</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+                    <code class="method-declaration">
+PUT https://DomainName/api/v1/datatables/{datatables}
+                    </code>
+                    <code class="method-request">
+PUT https://DomainName/api/v1/datatables/extra_client_details
+
+Content-Type: application/json
+Request Body:
+{
+    "apptableName": "m_client",
+    "dropColumns": [
+        {
+            "name": "Gender_cd_Question"
+        }
+    ],
+    "addColumns": [
+        {
+            "name": "Question",
+            "type": "Dropdown",
+            "code": "Gender",
+            "mandatory": true
+        },
+        {
+            "name": "Some Number",
+            "type": "Number",
+            "after": "Some Field"
+        }
+    ],
+    "changeColumns": [
+        {
+            "name": "Question",
+            "newName": "Question 2",
+            "mandatory": true,
+            "code": "Gender",
+            "newCode": "Gender2"
+        }
+    ]
+}
+					</code>
+					<code class="method-response">
+{
+  "resourceIdentifier": "extra_client_details"
+}
+				</code>
+				</div>
+			</div>
+
+			<a id="datatables_deleteTable" name="datatables_deleteTable"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete Data Table</h4>
+                    <p>Deletes a data table and deregisters it from the Apache Fineract Core
+                        application table.</p>
+				</div>
+				<div class="method-example">
+                    <code class="method-declaration">
+DELETE https://DomainName/api/v1/datatables/{datatables}
+                    </code>
+                    <code class="method-request">
+DELETE https://DomainName/api/v1/datatables/extra_client_details
+
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+{
+  "resourceIdentifier": "extra_client_details"
+}
+				</code>
+            </div>
+
+			</div>
+			<a id="datatables_register" name="datatables_register"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Register Data Table</h4>
+					<p>Registers a data table with the Apache Fineract Core application
+						table. This allows the data table to be maintained through the
+						API.
+						In case the datatable is a PPI (survey table), a parameter category should be pass along with the request.
+                        The API currently support one category (200)
+                    </p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/datatables/register/{datatable}/{apptable}
+					</code>
+					<code class="method-request">
+POST datatables/register/extra_client_details/m_client
+
+Content-Type: application/json
+Request Body:
+{}
+					</code>
+					<code class="method-response">
+{
+  "resourceIdentifier": "extra_client_details"
+}
+				</code>
+				</div>
+			</div>
+
+			<a id="datatables_deregister" name="datatables_deregister"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Deregister Data Table</h4>
+					<p>Deregisters a data table. It will no longer be available
+						through the API.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/datatables/deregister/{datatable}
+					</code>
+					<code class="method-request">
+POST datatables/deregister/extra_client_details
+
+Content-Type: application/json
+Request Body:
+{}
+					</code>
+					<code class="method-response">
+{
+  "resourceIdentifier": "extra_client_details"
+}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="datatables_create" name="datatables_create"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create Entry in Data Table</h4>
+					<p>Adds a row to the data table.</p>
+					<p>Note that the default datatable UI functionality converts
+						any field name containing spaces to underscores when using the
+						API. This means the field name "Business Description" is
+						considered the same as "Business_Description". So you shouldn't
+						have both "versions" in any data table.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/datatables/{datatable}/{apptableId}
+					</code>
+					<code class="method-request">
+POST datatables/extra_client_details/1
+
+Content-Type: application/json
+Request Body:
+{
+	"Business Description": "Livestock sales",
+	"Comment": "First	comment made",
+	"Education_cv": "Primary",
+	"Gender_cd": "6",
+	"Highest Rate Paid": "8.5",
+	"Next Visit": "01 October 2012",
+	"Years in Business": "5",
+	"dateFormat": "dd MMMM yyyy",
+	"locale": "en"
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="datatables_retrieve" name="datatables_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Entry(s) from Data Table</h4>
+					<p>
+						Gets the entry (if it exists) for data tables that are one to one
+						with the application table. <br> Gets the entries (if they
+						exist) for data tables that are one to many with the application
+						table.
+					</p>
+					<p>Note: The 'fields' parameter is not available for
+						datatables.</p>
+
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>order</dt>
+						<dd>
+							<span>optional</span>
+						</dd>
+						<dd>Specifies the order in which data is returned.</dd>
+						<dt>genericResultSet</dt>
+						<dd>
+							<span>optional, defaults to false</span>
+						</dd>
+						<dd>If 'true' an optimised JSON format is returned suitable for tabular display of data.
+							This format is used by the default data tables UI functionality.</dd>
+					</dl>
+
+					<p>Example Requests:</p>
+					<div class=apiClick>datatables/extra_client_details/1</div>
+					<br>
+					<br>
+					<div class=apiClick>datatables/extra_family_details/1?order=`Date of Birth` desc</div>
+					<br>
+					<br>
+					<div class=apiClick>datatables/extra_client_details/1?genericResultSet=true</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/datatables/{datatable}/{apptableId}?genericResultSet=true
+					</code>
+					<code class="method-response">
+{
+    "columnHeaders": [
+        {
+            "columnName": "client_id",
+            "columnType": "bigint",
+            "columnLength": 0,
+            "columnDisplayType": "INTEGER",
+            "isColumnNullable": false,
+            "isColumnPrimaryKey": true,
+            "columnValues": []
+        },
+        {
+            "columnName": "Business Description",
+            "columnType": "varchar",
+            "columnLength": 100,
+            "columnDisplayType": "STRING",
+            "isColumnNullable": true,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        },
+        {
+            "columnName": "Years in Business",
+            "columnType": "int",
+            "columnLength": 0,
+            "columnDisplayType": "INTEGER",
+            "isColumnNullable": true,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        },
+        {
+            "columnName": "Gender_cd",
+            "columnType": "int",
+            "columnLength": 0,
+            "columnDisplayType": "CODELOOKUP",
+            "isColumnNullable": true,
+            "isColumnPrimaryKey": false,
+            "columnValues": [
+                {
+                    "id": 5,
+                    "value": "option.Male"
+                },
+                {
+                    "id": 6,
+                    "value": "option.Female"
+                }
+            ]
+        },
+        {
+            "columnName": "Education_cv",
+            "columnType": "varchar",
+            "columnLength": 60,
+            "columnDisplayType": "CODEVALUE",
+            "isColumnNullable": true,
+            "isColumnPrimaryKey": false,
+            "columnValues": [
+                {
+                    "id": 9,
+                    "value": "Primary"
+                },
+                {
+                    "id": 10,
+                    "value": "Secondary"
+                },
+                {
+                    "id": 11,
+                    "value": "University"
+                }
+            ]
+        },
+        {
+            "columnName": "Next Visit",
+            "columnType": "date",
+            "columnLength": 0,
+            "columnDisplayType": "DATE",
+            "isColumnNullable": true,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        },
+        {
+            "columnName": "Highest Rate Paid",
+            "columnType": "decimal",
+            "columnLength": 0,
+            "columnDisplayType": "DECIMAL",
+            "isColumnNullable": true,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        },
+        {
+            "columnName": "Comment",
+            "columnType": "text",
+            "columnLength": 65535,
+            "columnDisplayType": "TEXT",
+            "isColumnNullable": true,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        }
+    ],
+    "data": [
+        {
+            "row": [
+                "1",
+                "Livestock sales",
+                "5",
+                "6",
+                "Primary",
+                "2012-10-01",
+                "8.500000",
+                "First\tcomment made"
+            ]
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="datatables_update" name="datatables_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update Entry in Data Table (One to One)</h4>
+					<p>Updates the row (if it exists) of the data table.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/datatables/{datatable}/{apptableId}
+					</code>
+					<code class="method-request">
+PUT datatables/extra_client_details/1
+
+Content-Type: application/json
+Request Body:
+{
+    "Business Description": "Livestock sales updated",
+}
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {
+    "Business Description": "Livestock sales updated"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="datatables_update_1M" name="datatables_update_1M"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update Entry in Data Table (One to Many)</h4>
+					<p>Updates the row (if it exists) of the data table.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/datatables/{datatable}/{apptableId}/{datatableId}
+					</code>
+					<code class="method-request">
+PUT datatables/Extra Family Details Data/1/2
+
+Content-Type: application/json
+Request Body:
+{
+	"Date of Birth": "01 June 1982",
+	Education_cdHighest: "5",
+	Name: "June",
+	"Other Notes": "More\nnotes",
+	"Points Score": "20",
+	dateFormat: "dd MMMM yyyy",
+	locale: "en"
+}
+					</code>
+					<code class="method-response">{ "resourceId": 1 } </code>
+				</div>
+			</div>
+
+			<a id="datatables_delete" name="datatables_delete"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete Entry(s) in Data Table</h4>
+					<p>
+						Deletes the entry (if it exists) for data tables that are one-to-one with the application table. <br> Deletes the entries (if they exist) for data tables that are one-to-many with the application table.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/datatables/{datatable}/{apptableId}
+					</code>
+					<code class="method-request">
+DELETE datatables/extra_client_details/1
+
+Content-Type: application/json
+Request Body:
+{}
+					 </code>
+					<code class="method-response">
+{
+	"resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="datatables_delete_1M" name="datatables_delete_1M"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete Entry in Datatable (One to Many)</h4>
+					<p>Deletes the entry (if it exists) for data tables that are
+						one to many with the application table.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/datatables/{datatable}/{apptableId}/{datatableId}
+					</code>
+					<code class="method-request">
+DELETE datatables/extra_family_details/1/2
+
+Content-Type: application/json
+Request Body:
+{}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+			<!-- end of datatables api docs -->
+
+			<!-- start of survey api docs -->
+
+             <a id="survey_retrieve" name="survey_retrieve"
+                class="old-syle-anchor">&nbsp;</a>
+             <div class="method-section">
+                 <div class="method-description">
+                     <h4>Retrieve surveys</h4>
+                     <p>Retrieve surveys. This allows to retrieve the list of survey tables registered .
+                     </p>
+                 </div>
+                 <div class="method-example">
+                     <code class="method-declaration">
+                         GET https://DomainName/api/v1/survey/
+                     </code>
+                     <code class="method-request">
+                         GET survey/
+
+                         Content-Type: application/json
+                         Request Body:
+                         {}
+                     </code>
+                     <code class="method-response">
+                     [
+                         {
+                         "datatableData": {
+                         "applicationTableName": "m_client",
+                         "registeredTableName": "ppi_kenya_2005",
+                         "columnHeaderData": [
+                             {
+                             "columnName": "id",
+                             "columnType": "bigint",
+                             "columnLength": 0,
+                             "columnDisplayType": "INTEGER",
+                             "isColumnNullable": false,
+                             "isColumnPrimaryKey": true,
+                             "columnValues": []
+                             },
+                             {
+                             "columnName": "client_id",
+                             "columnType": "int",
+                             "columnLength": 0,
+                             "columnDisplayType": "INTEGER",
+                             "isColumnNullable": false,
+                             "isColumnPrimaryKey": false,
+                             "columnValues": []
+                             },
+                             {
+                             "columnName": "ppi_household_members_cd_q1_householdmembers",
+                             "columnType": "int",
+                             "columnLength": 0,
+                             "columnDisplayType": "CODELOOKUP",
+                             "isColumnNullable": false,
+                             "isColumnPrimaryKey": false,
+                             "columnValues": [
+                             {
+                             "id": 167,
+                             "value": "Nine or More",
+                             "score": 0
+                             },
+                             {
+                             "id": 168,
+                             "value": "Seven or eight",
+                             "score": 5
+                             },
+                             {
+                             "id": 169,
+                             "value": "Six",
+                             "score": 8
+                             },
+
+                         ],
+                     "columnCode": "ppi_household_members"
+                     },
+                    {
+                     "columnName": "date",
+                     "columnType": "datetime",
+                     "columnLength": 0,
+                     "columnDisplayType": "DATETIME",
+                     "isColumnNullable": false,
+                     "isColumnPrimaryKey": false,
+                     "columnValues": []
+                     }
+                     ]
+                     },
+                     "enabled": false
+                     },
+                     {
+                     "datatableData": {
+                     "applicationTableName": "m_client",
+                     "registeredTableName": "ppi_tanzania_20012",
+                     "columnHeaderData": [
+                     {
+                     "columnName": "id",
+                     "columnType": "bigint",
+                     "columnLength": 0,
+                     "columnDisplayType": "INTEGER",
+                     "isColumnNullable": false,
+                     "isColumnPrimaryKey": true,
+                     "columnValues": []
+                     },
+                     {
+                     "columnName": "client_id",
+                     "columnType": "int",
+                     "columnLength": 0,
+                     "columnDisplayType": "INTEGER",
+                     "isColumnNullable": false,
+                     "isColumnPrimaryKey": false,
+                     "columnValues": []
+                     },
+                     {
+                     "columnName": "ppi_youngerthan17_cd_q1_youngerthan17",
+                     "columnType": "int",
+                     "columnLength": 0,
+                     "columnDisplayType": "CODELOOKUP",
+                     "isColumnNullable": false,
+                     "isColumnPrimaryKey": false,
+                     "columnValues": [
+                     {
+                     "id": 204,
+                     "value": "four or More",
+                     "score": 0
+                     },
+
+                     ],
+                     "columnCode": "ppi_youngerthan17"
+                     },
+                     ],
+                     "columnCode": "ppi_how_many_tables"
+                     },
+                      ]
+                     },
+                     "enabled": true
+                     }
+                     ]
+                     </code>
+                 </div>
+             </div>
+
+
+             <a id="survey_details" name="survey_details"
+                class="old-syle-anchor">&nbsp;</a>
+             <div class="method-section">
+                 <div class="method-description">
+                     <h4>Retrieve survey</h4>
+                     <p>Lists a registered survey table details and the Apache Fineract Core application table they are registered to.
+                     </p>
+                 </div>
+                 <div class="method-example">
+                     <code class="method-declaration">
+                         GET https://DomainName/api/v1/survey/ppi_kenya_2005
+                     </code>
+                     <code class="method-request">
+                         GET survey/{surveyName}
+
+                         Content-Type: application/json
+                         Request Body:
+                         {}
+                     </code>
+                     <code class="method-response">
+                         {
+                         "applicationTableName": "m_client",
+                         "registeredTableName": "extra_client_details",
+                         "columnHeaderData": [
+                         {
+                         "columnName": "client_id",
+                         "columnType": "bigint",
+                         "columnLength": 0,
+                         "columnDisplayType": "INTEGER",
+                         "isColumnNullable": false,
+                         "isColumnPrimaryKey": true,
+                         "columnValues": []
+                         },
+                         {
+                         "columnName": "Gender_cd_Question",
+                         "columnType": "int",
+                         "columnLength": 0,
+                         "columnDisplayType": "INTEGER",
+                         "isColumnNullable": true,
+                         "isColumnPrimaryKey": false,
+                         "columnValues": []
+                         },
+                         {
+                         "columnName": "Some Decimal",
+                         "columnType": "decimal",
+                         "columnLength": 0,
+                         "columnDisplayType": "DECIMAL",
+                         "isColumnNullable": true,
+                         "isColumnPrimaryKey": false,
+                         "columnValues": []
+                         },
+                         {
+                         "columnName": "Birth Date",
+                         "columnType": "date",
+                         "columnLength": 0,
+                         "columnDisplayType": "DATE",
+                         "isColumnNullable": true,
+                         "isColumnPrimaryKey": false,
+                         "columnValues": []
+                         }
+                         enabled:false
+                         ]
+                         }
+                     </code>
+                 </div>
+             </div>
+
+             <a id="survey_create" name="survey_create"
+                class="old-syle-anchor">&nbsp;</a>
+             <div class="method-section">
+                 <div class="method-description">
+                     <h4>Create an entry in the survey table</h4>
+                     <p>Insert and entry in a survey table (full fill the survey).
+
+                     </p>
+                 </div>
+                 <div class="method-example">
+                     <code class="method-declaration">
+                         POST https://DomainName/api/v1/survey/ppi_kenya_2005/87
+                     </code>
+                     <code class="method-request">
+                         POST survey/{surveyName}/{clientId}
+
+                         Content-Type: application/json
+                         Request Body:
+                         {
+
+                                 ppi_household_members_cd_q1_householdmembers : 167,
+                                 ppi_highestschool_cd_q2_highestschool : 174 ,
+                                 ppi_businessoccupation_cd_q3_businessoccupation : 180,
+                                 ppi_habitablerooms_cd_q4_habitablerooms :184,
+                                 ppi_floortype_cd_q5_floortype : 188,
+                                 ppi_lightingsource_cd_q6_lightingsource :190,
+                                 ppi_irons_cd_q7_irons:193,
+                                 ppi_mosquitonets_cd_q8_mosquitonets:195,
+                                 ppi_towels_cd_q9_towels:198,
+                                 ppi_fryingpans_cd_q10_fryingpans:201,
+                                 date:"2014-12-02 20:30:00",
+                                 dateFormat:"Y-m-d H:i:s",
+                                 locale:"en_GB"
+
+                         }
+                     </code>
+                     <code class="method-response">
+                         {
+                         "officeId": 2,
+                         "clientId": 87,
+                         "resourceId": 87
+                         }
+                     </code>
+                 </div>
+             </div>
+
+
+             <!-- end of Survey api docs -->
+
+			<!-- start of Notes api docs -->
+			<a id="notes" name="notes" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Notes</h3>
+					<p>Notes API allows to enter notes for supported resources.</p>
+
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>note</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A simple text note created for supported resources.</td>
+						</tr>
+					</table>
+					<br>
+					<br>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Supported Resources</div></td>
+						</tr>
+						<tr class=alt>
+							<td>
+								<a href="#clients">Client</a>
+								<br>
+								<a href="#loans">Loan</a>
+								<br>
+								<a href="#groups">Group</a>
+								<br>
+								<a href="#savingsaccounts">Savings Account</a>
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="resources_addnote" name="resources_addnote"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Add a Resource Note</h4>
+					<p>Adds a new note to a supported resource.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>note</td>
+						</tr>
+					</table>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/notes</div>
+					<br>
+					<br>
+					<div class=apiClick>groups/1/notes</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/{resource}/{resourceId}/notes
+					</code>
+					<code class="method-request">
+POST clients/1/notes
+Content-Type: application/json
+Request Body:
+{
+	"note": "a note about the client"
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "clientId": 1,
+    "resourceId": 76
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="resource_notelist" name="resource_notelist"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Resource's Notes</h4>
+					<p>
+						<b>Note:</b> Notes are returned in descending createOn order.
+					</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/2/notes</div>
+					<br>
+					<br>
+					<div class=apiClick>groups/2/notes?fields=note,createdOn,createdByUsername</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/{resource}/{resourceId}/notes
+					</code>
+					<code class="method-response">
+[
+    {
+        "id": 2,
+        "clientId": 1,
+        "noteType": {
+            "id": 100,
+            "code": "noteType.client",
+            "value": "Client note"
+        },
+        "note": "First note edited",
+        "createdById": 1,
+        "createdByUsername": "mifos",
+        "createdOn": 1342498505000,
+        "updatedById": 1,
+        "updatedByUsername": "mifos",
+        "updatedOn": 1342498517000
+    }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="resources_retrievenote" name="resources_retrievenote"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Resource Note</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/notes/76</div>
+					<br>
+					<br>
+					<div class=apiClick>groups/1/notes/20</div>
+					<br>
+					<br>
+					<div class=apiClick>clients/1/notes/76?fields=note,createdOn,createdByUsername</div>
+					<br>
+					<br>
+					<div class=apiClick>groups/1/notes/20?fields=note,createdOn,createdByUsername</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/{resource}/{resourceId}/notes/{noteId}
+					</code>
+					<code class="method-response">
+{
+    "id": 76,
+    "clientId": 1,
+    "noteType": {
+        "id": 100,
+        "code": "noteType.client",
+        "value": "Client note"
+    },
+    "note": "a note about the client",
+    "createdById": 1,
+    "createdByUsername": "mifos",
+    "createdOn": 1359463135000,
+    "updatedById": 1,
+    "updatedByUsername": "mifos",
+    "updatedOn": 1359463135000
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="resources_updatenote" name="resources_updatenote"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Resource Note</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/{resource}/{resourceId}/notes/{noteId}
+					</code>
+					<code class="method-request">
+PUT clients/1/notes/76
+Content-Type: application/json
+Request Body:
+{
+	"note": "and here the note is updated nicely."
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "clientId": 1,
+    "resourceId": 76,
+    "changes": {
+        "note": "and here the note is updated nicely."
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="resources_deletenote" name="resources_deletenote"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Resource Note</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/{resource}/{resourceId}/notes/{noteId}
+					</code>
+					<code class="method-request">
+DELETE clients/1/notes/76
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 76
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- end of Notes api docs -->
+
+			<!-- start of documents api docs -->
+			<a id="documents" name="documents" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Documents</h3>
+					<p>Multiple Documents (a combination of a name, description
+						and a file) may be attached to different Entities
+						like Clients, Groups, Staff, Loans, Savings and Client
+						Identifiers in the system
+					</p>
+					<p>Note: The currently allowed Entities are
+						<ul>
+							<li>Clients: URL Pattern as <i>clients</i></li>
+							<li>Staff: URL Pattern as <i>staff</i></li>
+							<li>Loans: URL Pattern as <i>loans</i></li>
+							<li>Savings: URL Pattern as <i>savings</i></li>
+							<li>Client Identifiers: URL Pattern as <i>client_identifiers</i></li>
+                                                        <li>Groups: URL Pattern as <i>groups</i></li>
+						</ul>
+					</p>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>parentEntityType</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The type of the Entity
+								with which this document is associated
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>parentEntityId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The ID of the entity (client,
+								loan etc) with which this document is
+								associated</td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>User Defined name for the
+								document, need not be the same as the name
+								of the file associated with the document
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>fileName</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The name of the file associated
+								with this document</td>
+						</tr>
+						<tr class=alt>
+							<td>size</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The size (in bytes) of the file
+								associated with this document</td>
+						</tr>
+						<tr class=alt>
+							<td>type</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Mime Type of the file
+								associated with this document
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>description</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A description of this document
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="documents_list" name="documents_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List documents</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/documents</div>
+					<br>
+					<div class=apiClick>client_identifiers/1/documents</div>
+					<br>
+					<div class=apiClick>loans/1/documents?fields=name,description</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/{entityType}/{entityId}/documents
+					</code>
+					<code class="method-response">
+[
+   {
+       "id": 1,
+       "parentEntityType": "clients",
+       "parentEntityId": 1,
+       "name": "Client Details Form ",
+       "fileName": "CGAP.pdf",
+       "size": 5246719,
+       "type": "application/pdf",
+       "description": "A signed form signed by new member"
+   }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="documents_retrieve" name="documents_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Document</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/documents/1</div>
+					<br>
+					<br>
+					<div class=apiClick>loans/1/documents/1</div>
+					<br>
+					<br>
+					<div class=apiClick>client_identifiers/1/documents/1?fields=name,description</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/documents/{clientId}
+					</code>
+					<code class="method-response">
+{
+   "id": 1,
+   "parentEntityType": "clients",
+   "parentEntityId": 1,
+   "name": "Client Details Form ",
+   "fileName": "CGAP.pdf",
+   "size": 5246719,
+   "type": "application/pdf",
+   "description": "A signed form signed by new member"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="documents_create" name="documents_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Document</h4>
+					<p>
+						<b>Note:</b> A document is created using a Multi-part form upload
+						<br>
+						<table class=matrixHeading>
+
+							<tr class="matrixHeadingBG">
+								<td><div class="fineractHeading2">Body Parts</div></td>
+							</tr>
+							<tr class=alt>
+								<td>name</td>
+							</tr>
+							<tr>
+								<td class=fielddesc>Name or summary of the document
+								</td>
+							</tr>
+							<tr class=alt>
+								<td>description</td>
+							</tr>
+							<tr>
+								<td class=fielddesc>Description of the document
+							</tr>
+							<tr class=alt>
+								<td>file</td>
+							</tr>
+							<tr>
+								<td class=fielddesc>The file to be uploaded
+								</td>
+							</tr>
+						</table>
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>file and description</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/{entityType}/{entityId}/documents
+					</code>
+					<code class="method-request">
+POST clients/1/documents
+Content-Type: multipart/form-data
+Request Body:
+<i>Not Shown (multi-part form data)</i>
+					</code>
+					<code class="method-response">
+{
+	"resourceId":3,
+	"resourceIdentifier":"3"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="documents_update" name="documents_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Document</h4>
+					<p>
+						<b>Note:</b> A document is updated using a Multi-part form upload
+						<br>
+						<table class=matrixHeading>
+
+							<tr class="matrixHeadingBG">
+								<td><div class="fineractHeading2">Body Parts</div></td>
+							</tr>
+							<tr class=alt>
+								<td>name</td>
+							</tr>
+							<tr>
+								<td class=fielddesc>Name or summary of the document
+								</td>
+							</tr>
+							<tr class=alt>
+								<td>description</td>
+							</tr>
+							<tr>
+								<td class=fielddesc>Description of the document
+							</tr>
+							<tr class=alt>
+								<td>file</td>
+							</tr>
+							<tr>
+								<td class=fielddesc>The file to be uploaded
+								</td>
+							</tr>
+						</table>
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/{entityType}/{entityId}/documents/{documentId}
+						</code>
+					<code class="method-request">
+PUT clients/1/documents/1
+Content-Type: multipart/form-data
+Request Body:
+<i>Not Shown (multi-part form data)</i>
+					</code>
+					<code class="method-response">
+{
+	"resourceId":3,
+	"changes":{},
+	"resourceIdentifier":"3"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="documents_retrieve_file" name="documents_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Binary File associated with Document</h4>
+					<p>Request used to download the file associated with the document</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/documents/1/attachment</div>
+					<br>
+					<br>
+					<div class=apiClick>loans/1/documents/1/attachment</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/{entityType}/{entityId}/documents/{documentId}/attachment
+					</code>
+					<code class="method-response">
+<i>Not Shown: The corresponding Binary file</i>
+					</code>
+				</div>
+			</div>
+
+			<a id="documents_delete" name="documents_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Remove a Document</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/{entityType}/{entityId}/documents/{documentId}
+					</code>
+					<code class="method-request">
+DELETE clients/1/documents/1
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+	"resourceId":1,
+	"changes":{},
+	"resourceIdentifier":"1"
+}
+					</code>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/{entityType}/{entityId}/documents/{documentId}
+					</code>
+					<code class="method-request">
+DELETE loans/1/documents/1
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+	"resourceId":1,
+	"changes":{},
+	"resourceIdentifier":"1"
+}
+					</code>
+				</div>
+
+			</div>
+
+
+			<!-- start of reports api docs -->
+			<a id="reports" name="reports" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Reports</h3>
+                                        <p>
+                                        Non-core reports can be added, updated and deleted.
+                                        </p>
+                                        <p>
+                                        Core reports (supplied at installation/upgrade time) can only have their "useReport" updated.
+                                        "useReport" is used, for example, in the reference UI report page to 'show'/'not show' reports.
+                                        Reports that have useReport set to false can still be run.
+                                        Reports only used for workflow purposes are examples of reports that would have their useReport set to false.
+                                        </p>
+                                        <p>
+                                        Placeholders can be put in the reportSql to act as parameters.  They have the format ${paramName}.
+                                        The runreports api will translate the value of any query parameter beginning R_ with the equivalent placeholder.<br>
+                                        e.g. query parameter R_myName=john will replace ${myName} with john
+                                        </p>
+                                        <p>
+                                        There is a special 'automatic' placeholder ${currentUserHierarchy} - if this is included in reportSql
+                                        it gets replaced by the requesting users' office hierarchy value.  This enables data scoping.<br>
+                                        Usage example "where o.hierarchy like CONCAT('${currentUserHierarchy}', '%')"
+                                        </p>
+                                        <p>
+                                        Note: <br>
+                                        The reports api allows parameters (not just placeholders in reportSql) to be associated with reports.
+                                        These associated parameters are only required to allow the reference UI reporting functionality implement
+                                        user-friendly parameter input.
+                                        </p>
+				</div>
+			</div>
+
+			<a id="reports_template" name="reports_template" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Report Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>reports/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/reports/template
+					</code>
+					<code class="method-response">
+{
+  "allowedReportTypes": [
+    "Table",
+    "Pentaho",
+    "Chart"
+  ],
+  "allowedReportSubTypes": [
+    "Bar",
+    "Pie"
+  ],
+  "allowedParameters": [
+    {
+      "id": 1,
+      "parameterName": "startDateSelect"
+    },
+    {
+      "id": 2,
+      "parameterName": "endDateSelect"
+    },
+    {
+      "id": 3,
+      "parameterName": "obligDateTypeSelect"
+    },
+    {
+      "id": 5,
+      "parameterName": "OfficeIdSelectOne"
+    },
+    {
+      "id": 6,
+      "parameterName": "loanOfficerIdSelectAll"
+    },
+    {
+      "id": 10,
+      "parameterName": "currencyIdSelectAll"
+    },
+    {
+      "id": 20,
+      "parameterName": "fundIdSelectAll"
+    },
+    {
+      "id": 25,
+      "parameterName": "loanProductIdSelectAll"
+    },
+    {
+      "id": 26,
+      "parameterName": "loanPurposeIdSelectAll"
+    },
+    {
+      "id": 100,
+      "parameterName": "parTypeSelect"
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="reports_create" name="reports_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Report</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/reports
+					</code>
+					<code class="method-request">
+POST reports
+Content-Type: application/json
+Request Body:
+{
+ "reportName":"Completely New Report",
+ "reportType":"Table",
+ "reportSubType":"",
+ "reportCategory":"Loan",
+ "useReport":"false",
+ "description":"Just\nAn\nExample",
+ "reportSql":"select 'very good sql' as AComment",
+ "reportParameters":[{"id":"","parameterId":"5","reportParameterName":""},{"id":"","parameterId":"6","reportParameterName":""}]
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 132
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="reports_list" name="reports_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Reports</h4>
+					<p>Lists all reports and their parameters. </p>
+
+					<p>Example Request:</p>
+					<div class=apiClick>reports</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/reports
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "reportName": "Client Listing",
+    "reportType": "Table",
+    "reportCategory": "Client",
+    "description": "Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \u0027one to one\u0027 additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\u0027d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\u0027t \n\nhave that client browser/memory impact).",
+    "coreReport": true,
+    "useReport": true,
+    "reportParameters": [
+      {
+        "id": 1,
+        "parameterId": 5,
+        "parameterName": "OfficeIdSelectOne"
+      }
+    ]
+  },
+  {
+    "id": 2,
+    "reportName": "Client Loans Listing",
+    "reportType": "Table",
+    "reportCategory": "Client",
+    "description": "Individual Client Report\n\nPretty \n\nwide report that lists the basic details of client loans.  \n\nCan be run for any size MFI but you\u0027d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\u0027t have that client browser/memory impact).",
+    "coreReport": false,
+    "useReport": true,
+    "reportParameters": [
+      {
+        "id": 2,
+        "parameterId": 5,
+        "parameterName": "OfficeIdSelectOne"
+      },
+      {
+        "id": 3,
+        "parameterId": 6,
+        "parameterName": "loanOfficerIdSelectAll"
+      },
+      {
+        "id": 4,
+        "parameterId": 10,
+        "parameterName": "currencyIdSelectAll"
+      },
+      {
+        "id": 5,
+        "parameterId": 20,
+        "parameterName": "fundIdSelectAll"
+      },
+      {
+        "id": 6,
+        "parameterId": 25,
+        "parameterName": "loanProductIdSelectAll"
+      },
+      {
+        "id": 7,
+        "parameterId": 26,
+        "parameterName": "loanPurposeIdSelectAll"
+      }
+    ]
+  },...
+  ]
+  </code>
+				</div>
+			</div>
+
+
+			<a id="reports_retrieve" name="reports_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Report</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>reports/1</div><br><br>
+					<div class=apiClick>reports/1?template=true</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/reports/{id}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "reportName": "Client Listing",
+  "reportType": "Table",
+  "reportCategory": "Client",
+  "description": "Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \u0027one to one\u0027 additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\u0027d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\u0027t \n\nhave that client browser/memory impact).",
+  "reportSql": "select \nconcat(repeat(\"..\",   \n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \u0027.\u0027, \u0027\u0027)) - 1))), ounder.`name`) as \"Office/Branch\",\n c.account_no as \"Client Account No.\",  \nc.display_name as \"Name\",  \nr.enum_message_property as \"Status\",\nc.activation_date as \"Activation\", c.external_id as \"External Id\"\nfrom m_office o \njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \u0027%\u0027)\nand ounder.hierarchy like concat(\u0027${currentUserHierarchy}\u0027, \u0027%\u0027)\njoin m_client c on c.office_id \u003d ounder.id\nleft join r_enum_value r on r.enum_name \u003d \u0027status_enum\u0027 and r.enum_id \u003d c.status_enum\nwhere o.id \u003d ${officeId}\norder by ounder.hierarchy, c.account_no",
+  "coreReport": true,
+  "useReport": true,
+  "reportParameters": [
+    {
+      "id": 1,
+      "parameterId": 5,
+      "parameterName": "OfficeIdSelectOne"
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="reports_update" name="reports_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Report</h4>
+                                        <p>
+                                        Only the useReport value can be updated for core reports.
+                                        </p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/reports/{id}
+					</code>
+					<code class="method-request">
+PUT reports/129
+Content-Type: application/json
+Request Body:
+{
+  "reportName": "New rpt name",
+  "reportParameters": [
+    {
+      "id": 194,
+      "parameterId": 5,
+      "reportParameterName": "m"
+    }
+  ]
+}
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 129,
+  "changes": {
+    "reportName": "New rpt name",
+    "reportParameters": "[{\"id\":194,\"parameterId\":5,\"reportParameterName\":\"m\"}]"
+  }
+}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="reports_delete" name="reports_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Report</h4>
+                                        <p>
+                                        Only non-core reports can be deleted.
+                                        </p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/reports/{id}
+					</code>
+					<code class="method-request">
+DELETE reports/100
+Content-Type: application/json
+Request Body:
+{}
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 100
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- start of runreports api docs -->
+			<a id="runreports" name="runreports" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Run Reports</h3>
+				</div>
+			</div>
+			<a id="report_run" name="report_run" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Running a Report</h4>
+					<p>This resource allows you to run and receive output from pre-defined Apache Fineract reports. </p>
+                                        <p>Reports can also be used to provide data for searching and workflow functionality.</p>
+                                        <p>
+                                                The default output is a JSON formatted "Generic Resultset". The Generic Resultset contains
+						Column Heading as well as Data information. However, you can
+						export to CSV format by simply adding "&exportCSV=true" to the end
+						of your URL.</p>
+					<p>If Pentaho reports have been pre-defined, they can also be
+						run through this resource. Pentaho reports can return HTML, PDF or
+						CSV formats.</p>
+					<p>The Apache Fineract reference application uses a
+						JQuery plugin called stretchyreporting which, itself, uses this
+						reports resource to provide a pretty flexible reporting User
+						Interface (UI).</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>R_'parameter names' ...</dt>
+						<dd>
+							optional, <span>No defaults</span>
+						</dd>
+						<dd>The number and names of the parameters depend on the
+							specific report and how it has been configured. R_officeId is an
+							example parameter name.</dd>
+						<dd>Note: the prefix R_ stands for Reporting</dd>
+						<dt>genericResultSet</dt>
+						<dd>
+							<span>optional, defaults to true</span>
+						</dd>
+						<dd>If 'true' an optimised JSON format is returned suitable for tabular display of data.
+						<dd>If 'false' a simple JSON format is returned.
+						<dt>parameterType</dt>
+						<dd>
+							optional, <span>The only valid value is 'true'. If any
+								other value is provided the argument will be ignored</span>
+						</dd>
+						<dd>Determines whether the request looks in the list of
+							reports or the list of parameters for its data. Doesn't apply to
+							Pentaho reports.</dd>
+						<dt>exportCSV</dt>
+						<dd>
+							optional, <span>The only valid value is 'true'. If any
+								other value is provided the argument will be ignored</span>
+						</dd>
+						<dd>Output will be delivered as a CSV file instead of JSON.
+							Doesn't apply to Pentaho reports.</dd>
+						<dt>output-type</dt>
+						<dd>
+							optional, <span>Defaults to HTML.</span>
+						</dd>
+						<dd>Valid Values are HTML, XLS, XSLX, CSV and PDF for html, Excel, Excel 2007+,
+							CSV and PDF formats respectively.</dd>
+						<dd>Only applies to Pentaho reports.</dd>
+						<dt>locale</dt>
+						<dd>
+							optional
+						</dd>
+						<dd>Any valid locale Ex: en_US, en_IN, fr_FR etc</dd>
+						<dd>Only applies to Pentaho reports.</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>runreports/Client%20Listing?R_officeId=1</div>
+					<br>
+					<br>
+					<div class=apiClick>runreports/Client%20Listing?R_officeId=1&exportCSV=true</div>
+					<br>
+					<br>
+					<div class=apiClick>runreports/OfficeIdSelectOne?R_officeId=1&ampparameterType=true</div>
+					<br>
+					<br>
+					<div class=apiClick>runreports/OfficeIdSelectOne?R_officeId=1&ampparameterType=true&exportCSV=true</div>
+					<br>
+					<br>
+					<div class=apiClick>runreports/Expected%20Payments%20By%20Date%20-%20Formatted?R_endDate=2013-04-30&R_loanOfficerId=-1&R_officeId=1&R_startDate=2013-04-16&output-type=HTML&R_officeId=1</div>
+					<br>
+					<br>
+					<div class=apiClick>runreports/Expected%20Payments%20By%20Date%20-%20Formatted?R_endDate=2013-04-30&R_loanOfficerId=-1&R_officeId=1&R_startDate=2013-04-16&output-type=XLS&R_officeId=1</div>
+					<br>
+					<br>
+					<div class=apiClick>runreports/Expected%20Payments%20By%20Date%20-%20Formatted?R_endDate=2013-04-30&R_loanOfficerId=-1&R_officeId=1&R_startDate=2013-04-16&output-type=CSV&R_officeId=1</div>
+					<br>
+					<br>
+					<div class=apiClick>runreports/Expected%20Payments%20By%20Date%20-%20Formatted?R_endDate=2013-04-30&R_loanOfficerId=-1&R_officeId=1&R_startDate=2013-04-16&output-type=PDF&R_officeId=1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/runreports/{reportName}
+					</code>
+					<code class="method-response">
+{
+    "columnHeaders": [
+        {
+            "columnName": "Office/Branch",
+            "columnType": "VARCHAR",
+            "isColumnNullable": false,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        },
+        {
+            "columnName": "Client Account No.",
+            "columnType": "VARCHAR",
+            "isColumnNullable": false,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        },
+        {
+            "columnName": "Name",
+            "columnType": "VARCHAR",
+            "isColumnNullable": false,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        },
+        {
+            "columnName": "Joined",
+            "columnType": "DATE",
+            "isColumnNullable": false,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        },
+        {
+            "columnName": "External Id",
+            "columnType": "VARCHAR",
+            "isColumnNullable": false,
+            "isColumnPrimaryKey": false,
+            "columnValues": []
+        }
+    ],
+    "data": [
+        {
+            "row": [
+                "Head Office",
+                "000000001",
+                "Petra Yton",
+                "2009-03-04",
+                "786YYH7"
+            ]
+        },
+        {
+            "row": [
+                "Head Office",
+                "000000002",
+                "Keith(changed) Yton",
+                "2009-03-04",
+                null
+            ]
+        },
+        {
+            "row": [
+                "Head Office",
+                "000000003",
+                "Jorge lastname",
+                "2013-02-05",
+                null
+            ]
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- start of authentication api docs -->
+			<a id="authenticationbasic" name="authenticationbasic" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Authentication HTTP Basic</h3>
+					<p>An API capability that allows client applications to verify
+						authentication details using HTTP Basic Authentication.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>base64EncodedAuthenticationKey</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>HTTP Basic Auth key. See <a
+								href="#authentication_overview">Authentication Overview</a> for
+								an example of its use.
+							</td>
+						</tr>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="authenticate_request_basic" name="authenticate_request_basic" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Verify authentication</h4>
+					<p>Authenticates the credentials provided and returns the set roles and permissions allowed.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/authentication?username={username}&password={password}
+					</code>
+					<code class="method-request">
+POST authentication?username=mifos&password=password
+Content-Type: application/json
+No Request Body
+					</code>
+					<p>Example response of autentication for user that is not linked with any staff.</p>
+					<code class="method-response">
+{
+    "username": "mifos",
+    "userId": 1,
+    "base64EncodedAuthenticationKey": "bWlmb3M6cGFzc3dvcmQ=",
+    "authenticated": true,
+    "officeId": 1,
+    "officeName": "Head Office",
+    "roles": [
+        {
+            "id": 1,
+            "name": "Super user",
+            "description": "This role provides all application permissions."
+        }
+    ],
+    "permissions": [
+        "ALL_FUNCTIONS"
+    ]
+}
+					</code>
+					<p>Example response of autentication for user that is linked with a staff member and role.</p>
+<code class="method-response">
+{
+    "username": "mifos",
+    "userId": 1,
+    "base64EncodedAuthenticationKey": "bWlmb3M6cGFzc3dvcmQ=",
+    "authenticated": true,
+    "officeId": 1,
+    "officeName": "Head Office",
+    "staffId": 1,
+    "staffDisplayName": "Director, Program",
+    "organisationalRole": {
+        "id": 100,
+        "code": "staffOrganisationalRoleType.programDirector",
+        "value": "Program Director"
+    },
+    "roles": [
+        {
+            "id": 1,
+            "name": "Super user",
+            "description": "This role provides all application permissions."
+        }
+    ],
+    "permissions": [
+        "ALL_FUNCTIONS"
+    ]
+}
+</code>
+
+
+
+
+
+					<code class="method-request">
+POST authentication?username=mifos&password=fail
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+{
+    "developerMessage": "Invalid authentication details were passed in api request.",
+    "developerDocLink": "https://github.com/openMF/mifosx/wiki/HTTP-API-Error-codes",
+    "httpStatusCode": "401",
+    "defaultUserMessage": "Unauthenticated. Please login.",
+    "userMessageGlobalisationCode": "error.msg.not.authenticated",
+    "errors": []
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="authenticationoauth" name="authenticationoauth" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Authentication Oauth2</h3>
+					<p>An API capability that allows client applications to fetch current user details details using Oauth2.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>accessToken</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>HTTP Auth bearer key. See <a
+								href="#authentication_overview">Authentication Overview</a> for
+								an example of its use.
+							</td>
+						</tr>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="oauth" name="oauth" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Authentication via OAuth2</h3>
+					<p>API for requesting OAuth2 access token and refresh token.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>grant_type</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Mandatory field.
+							Indicates the requested grant type. Supported values:
+							<span>password</span>.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>client_id</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Mandatory field.
+							Indicates the client application identity.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>client_secret</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional field.
+							Indicates the client application password.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>username</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Mandatory field.
+							Application User(resource) login name.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>password</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Mandatory field.
+							Application User(resource) password.
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="oauth_request" name="oauth_request" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>OAuth2 Refresh and Access Token Request</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/oauth/token?username={username}&password={password}&client_id={clientId}&grant_type={grant_type}&client_secret={client_secret}
+					</code>
+					<code class="method-request">
+POST api/oauth/token?username=mifos&password=password&client_id=community-app&grant_type=password&client_secret=123
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+{
+	""access_token": "b771987f-82fc-45ba-b521-bfe280c4e603",
+	"token_type": "bearer",
+	"refresh_token":"a2a89b23-8d22-4d90-8585-8f464db433b0",
+	"expires_in": 3599,
+	"scope": "all"
+}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="oauth_access_token_req" name="oauth_access_token_req" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Authentication via OAuth2</h3>
+					<p>API for requesting OAuth2 access tokens through oAuth2 refresh tokens.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>grant_type</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Mandatory field.
+							Indicates the requested grant type. Supported values:
+							<span>refresh_token</span>.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>client_id</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Mandatory field.
+							Indicates the client application identity.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>client_secret</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Optional field.
+							Indicates the client application password.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>refresh_token</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Mandatory field.
+							Application refresh token to generate access token.
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="oauth_request_with_refresh" name="oauth_request_with_refresh" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>OAuth2 Token Request through Refresh Token</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/oauth/token?refresh_token={refresh_token}&client_id={clientId}&grant_type={grant_type}&client_secret={client_secret}
+					</code>
+					<code class="method-request">
+POST api/oauth/token?client_id=community-app&grant_type=refresh_token&client_secret=123&refresh_token=a2a89b23-8d22-4d90-8585-8f464db433b0
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+{
+	""access_token": "b771987f-82fc-45ba-b521-bfe280c4e643",
+	"token_type": "bearer",
+	"refresh_token":"a2a89b23-8d22-4d90-8585-8f464db433b0",
+	"expires_in": 3599,
+	"scope": "all"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="userdetails_request" name="userdetails_request" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Fetch authenticated user details </h4>
+					<p>checks the Authentication and returns the set roles and permissions allowed.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/userdetails?access_token={access_token}
+					</code>
+					<code class="method-request">
+POST userdetails?access_token=bWlmb3M6cGFzc3dvcmQ=
+Content-Type: application/json
+No Request Body
+					</code>
+					<p>Example response of authenticated user user that is not linked with any staff.</p>
+					<code class="method-response">
+{
+    "username": "mifos",
+    "userId": 1,
+    "accessToken": "bWlmb3M6cGFzc3dvcmQ=",
+    "authenticated": true,
+    "officeId": 1,
+    "officeName": "Head Office",
+    "roles": [
+        {
+            "id": 1,
+            "name": "Super user",
+            "description": "This role provides all application permissions."
+        }
+    ],
+    "permissions": [
+        "ALL_FUNCTIONS"
+    ]
+}
+					</code>
+					<p>Example response of authenticated user that is linked with a staff member and role.</p>
+<code class="method-response">
+{
+    "username": "mifos",
+    "userId": 1,
+    "accessToken": "bWlmb3M6cGFzc3dvcmQ=",
+    "authenticated": true,
+    "officeId": 1,
+    "officeName": "Head Office",
+    "staffId": 1,
+    "staffDisplayName": "Director, Program",
+    "organisationalRole": {
+        "id": 100,
+        "code": "staffOrganisationalRoleType.programDirector",
+        "value": "Program Director"
+    },
+    "roles": [
+        {
+            "id": 1,
+            "name": "Super user",
+            "description": "This role provides all application permissions."
+        }
+    ],
+    "permissions": [
+        "ALL_FUNCTIONS"
+    ]
+}
+</code>
+
+
+
+
+
+					<code class="method-request">
+POST api/oauth/token?username=mifos&password=password&client_id=community-app&grant_type=password&client_secret=123
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+{
+    "developerMessage": "Invalid authentication details were passed in api request.",
+    "developerDocLink": "https://github.com/openMF/mifosx/wiki/HTTP-API-Error-codes",
+    "httpStatusCode": "401",
+    "defaultUserMessage": "Unauthenticated. Please login.",
+    "userMessageGlobalisationCode": "error.msg.not.authenticated",
+    "errors": []
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- start of users api docs -->
+			<a id="users" name="users" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Users</h3>
+					<p>An API capability to support administration of application users.</p>
+				</div>
+			</div>
+
+			<a id="users_list" name="users_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve list of users</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>users</div>
+					<br>
+					<br>
+					<div class=apiClick>users?fields=id,username,email,officeName</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/users
+					</code>
+					<code class="method-response">
+[
+    {
+        "id": 1,
+        "username": "mifos",
+        "officeId": 1,
+        "officeName": "Head Office",
+        "firstname": "App",
+        "lastname": "Administrator",
+        "email": "demomfi@mifos.org",
+        "passwordNeverExpires": false,
+        "staff": {
+		   "id": 1,
+		   "firstname": "Test",
+		   "lastname": "123",
+		   "displayName": "123, Test",
+		   "mobileNo": "12312312",
+		   "officeId": 1,
+		   "officeName": "Head Office",
+		   "isLoanOfficer": true,
+		   "isActive": true
+		}
+	"selectedRoles": [
+            {
+                "id": 1,
+                "name": "Super user",
+                "description": "This role provides all application permissions."
+            }
+        ]
+    }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="users_template" name="users_template" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve User Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for client
+						applications. The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+					<p>Example Request:</p>
+					<div class=apiClick>users/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/users/template
+					</code>
+					<code class="method-response">
+{
+    "allowedOffices": [
+        {
+            "id": 1,
+            "name": "Head Office",
+            "nameDecorated": "Head Office"
+        }
+    ],
+    "availableRoles": [
+        {
+            "id": 1,
+            "name": "Super user",
+            "description": "This role provides all application permissions."
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="users_retrieve" name="users_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a User</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>users/1</div>
+					<br>
+					<br>
+					<div class=apiClick>users/1?template=true</div>
+					<br>
+					<br>
+					<div class=apiClick>users/1?fields=username,officeName</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/users/{userId}
+					</code>
+					<code class="method-response">
+{
+    "id": 1,
+    "username": "mifos",
+    "officeId": 1,
+    "officeName": "Head Office",
+    "firstname": "App",
+    "lastname": "Administrator",
+    "email": "demomfi@mifos.org",
+    "passwordNeverExpires": true,
+    "staff": {
+	 "id": 1,
+	 "firstname": "Test",
+	 "lastname": "123",
+	 "displayName": "123, Test",
+	 "mobileNo": "12312312",
+	 "officeId": 1,
+	 "officeName": "Head Office",
+	 "isLoanOfficer": true,
+	 "isActive": true
+	}
+    "availableRoles": [],
+    "selectedRoles": [
+        {
+            "id": 1,
+            "name": "Super user",
+            "description": "This role provides all application permissions."
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="users_create" name="users_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a User</h4>
+					<p>Adds new application user.</p>
+					<p>
+						<b>Note:</b> Password information is not required (or processed).
+						Password details at present are auto-generated and then sent to
+						the email account given (which is why it can take a few seconds to
+						complete).
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr>
+							<td>username, firstname, lastname, email, officeId, roles, sendPasswordToEmail</td>
+						</tr>
+					</table>
+					<br>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr>
+							<td>staffId,passwordNeverExpires,isSelfServiceUser,clients</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/users
+					</code>
+					<code class="method-request">
+POST users
+Content-Type: application/json
+Request body:
+{
+    "username": "newuser",
+    "firstname": "Test",
+    "lastname": "User",
+    "email": "whatever@mifos.org",
+    "officeId": 1,
+    "staffId": 1,
+    "roles": [2,3],
+    "sendPasswordToEmail": true
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 11
+}
+					</code>
+					<code class="method-request">
+POST users
+Content-Type: application/json
+Request body:
+{
+    "username": "newuser",
+    "firstname": "Test",
+    "lastname": "User",
+    "email": "whatever@mifos.org",
+    "officeId": 1,
+    "staffId": 1,
+    "roles": [2,3],
+    "sendPasswordToEmail": false,
+    "password": "123",
+    "repeatPassword": "123"
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 12
+}
+					</code>
+					<code class="method-declaration">
+POST https://DomainName/api/v1/users
+					</code>
+					<code class="method-request">
+POST users
+Content-Type: application/json
+Request body:
+{
+    "username": "newuser",
+    "firstname": "Test",
+    "lastname": "User",
+    "email": "whatever@mifos.org",
+    "officeId": 1,
+    "staffId": 1,
+    "roles": [2,3],
+    "sendPasswordToEmail": true,
+    "isSelfServiceUser": true,
+    "clients": [1,2,3]
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 11
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="users_update" name="users_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a User</h4>
+					<p>
+						<b>Note:</b> When updating a password you must provide the
+						repeatPassword parameter also.
+					</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/users/{userId)
+					</code>
+					<code class="method-request">
+PUT users/3
+Content-Type: application/json
+Request body:
+{
+    "firstname": "Test",
+    "password": "window75",
+    "repeatPassword": "window75"
+}
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 3,
+    "changes": {
+        "firstname": "Test",
+        "passwordEncoded": "abc3326b1bb376351c7baeb4175f5e0504e33aadf6a158474a6d71de1befae51"
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="users_delete" name="users_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a User</h4>
+					<p>Removes the user and the associated roles and permissions.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/users/{userId}
+					</code>
+					<code class="method-request">
+DELETE users/20
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 20,
+    "changes": {}
+}
+					</code>
+				</div>
+			</div>
+			<!--  end of users api docs -->
+
+			<!-- start of roles api docs -->
+			<a id="roles" name="users" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Roles</h3>
+					<p>An API capability to support management of application roles for user administration.</p>
+				</div>
+			</div>
+
+			<a id="roles_list" name="roles_list" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Roles</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>roles</div>
+					<br>
+					<br>
+					<div class=apiClick>roles?fields=name</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/roles
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "name": "Super user",
+    "description": "This role provides all application permissions."
+  }
+]
+					</code>
+				</div>
+			</div>
+
+
+			<a id="roles_delete" name="roles_delete" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Role</h4>
+					<p>Description : Delete the role in case role is not associated with any users.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/roles/{roleId}
+					</code>
+					<code class="method-request">
+DELETE roles/20
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+{
+	"resourceId":1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="roles_retrieve" name="roles_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Role</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>roles/1</div>
+					<br>
+					<br>
+					<div class=apiClick>roles/1?fields=name</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/roles/{roleId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "name": "Super user",
+  "description": "This role provides all application permissions."
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="roles_create" name="roles_create" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a New Role</h4>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name, description</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/roles
+					</code>
+					<code class="method-request">
+POST roles
+Content-Type: application/json
+Request body:
+{
+    "name": "Another Role Name",
+    "description": "A description outlining the purpose of this role in relation to the application."
+}
+					</code>
+					<code class="method-response">{ "resourceId": 2} </code>
+				</div>
+			</div>
+
+			<a id="roles_update" name="roles_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Role</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/roles/{roleId}
+					</code>
+					<code class="method-request">
+PUT roles/1
+Content-Type: application/json
+Request body:
+{
+	"description": "some description(changed)"
+}
+					</code>
+					<code class="method-response">
+{
+	"resourceId": 1,
+	"changes": {
+		"description": "some description(changed)"
+	}
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="rolespermissions_retrieve" name="rolespermissions_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Role's Permissions</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>roles/1/permissions</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/roles/{roleId}/permissions
+					</code>
+					<code class="method-response">
+{
+    "id": 1,
+    "name": "Super user",
+    "description": "This role provides all application permissions.",
+    "permissionUsageData": [
+        {
+            "grouping": "authorisation",
+            "code": "READ_PERMISSION",
+            "entityName": "PERMISSION",
+            "actionName": "READ",
+            "selected": false
+        },
+        ...
+        {
+            "grouping": "transaction_loan",
+            "code": "WRITEOFF_LOAN_CHECKER",
+            "entityName": "LOAN",
+            "actionName": "WRITEOFF",
+            "selected": false
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="roles_enable" name="roles_enable" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Enable Role</h4>
+					<p>Description : Enable role in case role is disabled.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/roles/{roleId}?command=enable
+					</code>
+					<code class="method-request">
+POST roles/1?command=enable
+Content-Type: application/json
+No Request Body
+</code>
+					<code class="method-response">
+{
+	"resourceId":1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="roles_disable" name="roles_disable" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Disable Role</h4>
+					<p>Description : Disable the role in case role is not associated with any users.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/roles/{roleId}?command=disable
+					</code>
+					<code class="method-request">
+POST roles/1?command=disable
+Content-Type: application/json
+No Request Body
+</code>
+					<code class="method-response">
+{
+	"resourceId":1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="rolespermissions_update" name="rolespermissions_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Role's Permissions</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/roles/{roleId}/permissions
+					</code>
+					<code class="method-request">
+PUT roles/8
+Content-Type: application/json
+Request body:
+{
+    "permissions": {
+        "ALL_FUNCTIONS_READ": "true"
+    }
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 8,
+    "changes": {
+        "permissions": {
+            "ALL_FUNCTIONS_READ": true
+        }
+    }
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- start of permissions api docs -->
+			<a id="permissions" name="permissions" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Permissions</h3>
+					<p>An API capability to support management of application permissions for user administration.</p>
+                                        <p>There is no Apache Fineract functionality for creating or deleting permissions.  Permissions come pre-installed.</p>
+                                        <p>Permissions are not updated, except in the case of enabling or disabling non-read transactions for
+                                        Maker Checker functionality</p>
+				</div>
+			</div>
+
+			<a id="permissions_list" name="permissions_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Application Permissions</h4>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+					    <dt>makerCheckerable</dt>
+                                            <dd>optional, <span>
+                                            Values are true, false.  Default is false.</span>
+                                            <p>If makerCheckerable=false or not supplied then a list of application permissions is returned.  The "selected"
+                                            attribute is always true in this case.
+                                            </p>
+                                            <p>If makerCheckerable=true then the "selected" attribute shows whether the permission is
+                                            enabled for Maker Check functionality.
+                                            </p>
+                                            <p>
+                                            Note: Each Apache Fineract transaction is associated with a permission.
+                                            </p>
+                                            </dd>
+                                        </dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>permissions</div><br><br>
+					<div class=apiClick>permissions?makerCheckerable=true</div><br><br>
+					<div class=apiClick>permissions?fields=grouping,code</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/permissions
+					</code>
+					<code class="method-response">
+[
+  {
+    "grouping": "authorisation",
+    "code": "READ_PERMISSION",
+    "entityName": "PERMISSION",
+    "actionName": "READ",
+    "selected": true
+  },
+  ....
+  {
+    "grouping": "transaction_loan",
+    "code": "WRITEOFF_LOAN",
+    "entityName": "LOAN",
+    "actionName": "WRITEOFF",
+    "selected": true
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="permissions_update" name="permissions_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Enable/Disable Permissions for Maker Checker</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/permissions
+						</code>
+					<code class="method-request">
+PUT permissions
+Content-Type: application/json
+Request Body:
+{
+ "permissions":{
+    "CREATE_GUARANTOR":true,
+    "CREATE_CLIENT":true
+ }
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- start of password validation policy api docs -->
+			<a id="password_preferences" name="passwordpreferences" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Password preferences</h3>
+					<p>This API enables management of password policy for user administration.</p>
+					<p>There is no Apache Fineract functionality for creating a validation policy.  The validation policies come pre-installed.</p>
+					<p>Validation policies may be updated</p>
+				</div>
+			</div>
+
+			<a id="password_preferences_list" name="passwordValidationPolicy_list"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Application Password validation policies</h4>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+
+						</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>passwordpreferences</div><br><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/passwordpreferences/template
+					</code>
+					<code class="method-response">
+[
+{
+"id": 1,
+"description": "Password must be at least 1 character and not more that 50 characters long",
+"active": true,
+"key" : "simple"
+},
+{
+"id": 2,
+"description": "Password must be at least 6 characters, no more than 50 characters long, must include at least one upper case letter, one lower case letter, one numeric digit and no space",
+"active": false,
+"key" : "secure"
+}
+]
+					</code>
+				</div>
+
+							<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/passwordpreferences/
+					</code>
+					<code class="method-response">
+{
+"id": 1,
+"description": "Password most be at least 1 character and not more that 50 characters long",
+"active": true,
+"key" : "simple"
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="password_preferences_update" name="passwordValidationPolicy_update" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update password preferences</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/passwordpreferences/
+					</code>
+					<code class="method-request">
+PUT passwordpreferences
+Content-Type: application/json
+Request Body:
+{
+	"validationPolicyId" : 1,
+}
+					</code>
+				</div>
+			</div>
+			<!-- start of accounting api docs -->
+            <a id="glaccounts" name="glaccounts" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h3>General Ledger Account</h3>
+                    <p>Ledger accounts represent an Individual account within an Organizations Chart
+                       Of Accounts(COA) and are assigned a name and unique number by which they can
+                       be identified. <br/>
+                       All transactions relating to a company's assets, liabilities, owners' equity,
+                       revenue and expenses are recorded against these accounts
+                    </p>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Field Descriptions</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>name</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The name of the account</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>glCode</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The ledger code associated with the
+                                Account<br>These codes are mandatory and should be unique within
+                                an organization</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>disabled</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>A boolean flag that indicates whether an account
+                            	is currently in use</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>type</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Classifies the account into one of the following Types
+                            	<br/><b>Asset:</b> represent the different types of economic resources
+                            	owned or controlled by business, common examples of Asset accounts are cash,
+                            	cash in bank, building, inventory, prepaid rent, goodwill, accounts receivable<br/>
+								<b>Liability:</b> represent the different types of economic obligations
+								by a business, such as accounts payable, bank loan, bonds payable<br/>
+								<b>Income:</b> represent the company's gross earnings and common examples
+								include Interest Income, Sales and Service revenue<br/>
+								<b>Expense:</b> represent the company's expenditures to enable itself to
+								operate. Common examples are electricity and water, rentals, depreciation,
+								doubtful accounts, insurance.<br/>
+								<b>Equity:</b> represent the residual equity of a business (after
+								deducting from Assets all the liabilities) including Retained Earnings
+								and Appropriations <br/><br/>
+								The options are fully listed in <a href="#glaccounts_template">Retrieve
+								General Ledger Accounts Template</a>.
+                            </td>
+                        </tr>
+                        <tr class=alt>
+                            <td>usage</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Determines how the account shall be used<br>
+                            "Header" accounts specify the title of a group of accounts. They are used
+                            only for grouping together detail accounts that have a similar purpose; that is, detail accounts
+                            are assigned to specific header accounts
+                            <br/>
+                            "Detail" accounts may have transactions logged against them<br/><br/>
+                            The options are fully listed in <a href="#glaccounts_template">Retrieve
+							General Ledger Accounts Template</a>.
+                            </td>
+                        </tr>
+                        <tr class=alt>
+                            <td>manualEntriesAllowed</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Specifies if manual entries can be made
+                            against this account using the <a href="#journalentries_create">
+                            Create Journal Entries API</a> </td>
+                        </tr>
+                        <tr class=alt>
+                            <td>description</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Human understandable description for the Ledger Account
+                            </td>
+                        </tr>
+						<tr class=alt>
+                            <td>parentId</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>To assign a parent for this GLAccount
+                            </td>
+                        </tr><tr class=alt>
+                            <td>tagId</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Used for tagging the Account Heads, based on GLAccount types.
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+
+            <a id="glaccounts_list" name="glaccounts_list" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>List General Ledger Accounts</h4>
+                    <h5>Arguments</h5>
+                    <dl class="argument-list">
+                        <dt>type</dt>
+                        <dd>
+                            Integer <span>optional</span>
+                        </dd>
+                        <dt>manualEntriesAllowed</dt>
+                        <dd>
+                            boolean <span>optional</span>
+                        </dd>
+                        <dt>usage</dt>
+                        <dd>
+                            Integer <span>optional</span>
+                        </dd>
+                        <dt>disabled</dt>
+                        <dd>
+                            boolean <span>optional</span>
+                        </dd>
+						<dt>parentId</dt>
+                        <dd>
+                            Long <span>optional</span>
+                        </dd>
+						<dt>tagId</dt>
+                        <dd>
+                            Long <span>optional</span>
+                        </dd>
+                    </dl>
+                    <p>Example Requests:</p>
+                    <div class=apiClick>glaccounts</div>
+                    <br>
+                    <br>
+                    <div class=apiClick>glaccounts?type=1&manualEntriesAllowed=true&usage=1&disabled=false</div>
+                    <br>
+                    <div class=apiClick>glaccounts?fetchRunningBalance=true</div>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/glaccounts
+                    </code>
+                    <code class="method-response">
+[
+    {
+        "id": 16,
+        "name": "Cash",
+	"parentId": 1,
+        "glCode": "100001",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+            "id": 1,
+            "code": "accountType.asset",
+            "value": "ASSET"
+        },
+        "usage": {
+            "id": 1,
+            "code": "accountUsage.detail",
+            "value": "DETAIL"
+        },
+        "description": "Desc",
+	"nameDecorated": "....Cash",
+	"tagId": {
+	  "id": 10,
+	  "name": "asset tag"
+	},
+        "organizationRunningBalance": 118437,
+    },
+    {
+        "id": 15,
+        "name": "Fund Source For Loan",
+        "glCode": "100002",
+        "disabled": false,
+        "manualEntriesAllowed": true,
+        "type": {
+            "id": 1,
+            "code": "accountType.asset",
+            "value": "ASSET"
+        },
+        "usage": {
+            "id": 1,
+            "code": "accountUsage.detail",
+            "value": "DETAIL"
+        },
+        "description": "Desc",
+        "organizationRunningBalance": 118437,
+    }
+]
+                    </code>
+                </div>
+            </div>
+
+            <a id="glaccounts_template" name="glaccounts_template"
+                class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Retrieve GL Accounts Template</h4>
+                    <p>This is a convenience resource. It can be useful when
+                        building maintenance user interface screens for client
+                        applications. The template data returned consists of any or all
+                        of:
+                    <ul>
+                        <li class=normalli>Field Defaults</li>
+                        <li class=normalli>Allowed Value Lists</li>
+                    </ul>
+                    </p>
+                    <p>Example Request:</p>
+                    <div class=apiClick>glaccounts/template</div>
+                    <div class=apiClick>glaccounts/template?type=1</div>
+					<br>
+					type is optional and integer value from 1 to 5.<br>
+					<P><br>1.Assets
+					   <br>2.Liabilities
+					   <br>3.Equity
+					   <br>4.Income
+					   <br>5.Expenses</p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/glaccounts/template<br>
+GET https://DomainName/api/v1/glaccounts/template?type=1
+                    </code>
+                    <code class="method-response">
+{
+    "disabled": false,
+    "manualEntriesAllowed": true,
+    "type": {
+        "id": 1,
+        "code": "accountType.asset",
+        "value": "ASSET"
+    },
+    "usage": {
+        "id": 1,
+        "code": "accountUsage.detail",
+        "value": "DETAIL"
+    },
+    "accountTypeOptions": [
+        {
+            "id": 1,
+            "code": "accountType.asset",
+            "value": "ASSET"
+        },
+        {
+            "id": 2,
+            "code": "accountType.liability",
+            "value": "LIABILITY"
+        },
+        {
+            "id": 3,
+            "code": "accountType.equity",
+            "value": "EQUITY"
+        },
+        {
+            "id": 4,
+            "code": "accountType.income",
+            "value": "INCOME"
+        },
+        {
+            "id": 5,
+            "code": "accountType.expense",
+            "value": "EXPENSE"
+        }
+    ],
+    "usageOptions": [
+        {
+            "id": 1,
+            "code": "accountUsage.detail",
+            "value": "DETAIL"
+        },
+        {
+            "id": 2,
+            "code": "accountUsage.header",
+            "value": "HEADER"
+        }
+    ],
+  "assetHeaderAccountOptions": [
+    {
+      "id": 1,
+      "name": "Two wheeler loan",
+      "glCode": "10001",
+      "disabled": false,
+      "manualEntriesAllowed": true,
+      "type": {
+        "id": 1,
+        "code": "accountType.asset",
+        "value": "ASSET"
+      },
+      "usage": {
+        "id": 2,
+        "code": "accountUsage.header",
+        "value": "HEADER"
+      },
+      "nameDecorated": "Two wheeler loan",
+      "tagId": {
+        "id": 10,
+        "name": "asset tag"
+      }
+    },
+    {
+      "id": 2,
+      "name": "VEHICLE LOAN",
+      "parentId": 1,
+      "glCode": "10002",
+      "disabled": false,
+      "manualEntriesAllowed": true,
+      "type": {
+        "id": 1,
+        "code": "accountType.asset",
+        "value": "ASSET"
+      },
+      "usage": {
+        "id": 2,
+        "code": "accountUsage.header",
+        "value": "HEADER"
+      },
+      "nameDecorated": "....VEHICLE LOAN",
+      "tagId": {
+        "id": 10,
+        "name": "asset tag"
+      }
+    }
+  ],
+  "liabilityHeaderAccountOptions": [
+    {
+      "id": 15,
+      "name": "liabilitieschild",
+      "glCode": "ltchild",
+      "disabled": false,
+      "manualEntriesAllowed": true,
+      "type": {
+        "id": 2,
+        "code": "accountType.liability",
+        "value": "LIABILITY"
+      },
+      "usage": {
+        "id": 2,
+        "code": "accountUsage.header",
+        "value": "HEADER"
+      },
+      "nameDecorated": "liabilitieschild",
+      "tagId": {
+        "id": 11,
+        "name": "liability tag"
+      }
+    }
+  ],
+  "equityHeaderAccountOptions": [
+    {
+      "id": 13,
+      "name": "testajax",
+      "glCode": "12345678",
+      "disabled": false,
+      "manualEntriesAllowed": true,
+      "type": {
+        "id": 3,
+        "code": "accountType.equity",
+        "value": "EQUITY"
+      },
+      "usage": {
+        "id": 2,
+        "code": "accountUsage.header",
+        "value": "HEADER"
+      },
+      "nameDecorated": "testajax",
+      "tagId": {
+        "id": 12,
+        "name": "Equity tag"
+      }
+    }
+  ],
+  "expenseHeaderAccountOptions": [
+    {
+      "id": 8,
+      "name": "Salary",
+      "glCode": "450098",
+      "disabled": false,
+      "manualEntriesAllowed": true,
+      "type": {
+        "id": 5,
+        "code": "accountType.expense",
+        "value": "EXPENSE"
+      },
+      "usage": {
+        "id": 2,
+        "code": "accountUsage.header",
+        "value": "HEADER"
+      },
+      "nameDecorated": "Salary",
+      "tagId": {
+        "id": 14,
+        "name": "Expenses Tag"
+      }
+    }
+  ],
+  "allowedAssetsTagOptions": [
+    {
+      "id": 10,
+      "name": "asset tag",
+      "position": 0
+    }
+  ],
+  "allowedLiabilitiesTagOptions": [
+    {
+      "id": 11,
+      "name": "liability tag",
+      "position": 0
+    }
+  ],
+  "allowedEquityTagOptions": [
+    {
+      "id": 12,
+      "name": "Equity tag",
+      "position": 0
+    }
+  ],
+  "allowedIncomeTagOptions": [
+    {
+      "id": 13,
+      "name": "Income Tag",
+      "position": 0
+    }
+  ],
+  "allowedExpensesTagOptions": [
+    {
+      "id": 14,
+      "name": "Expenses Tag",
+      "position": 0
+    }
+  ]
+}
+                    </code>
+                </div>
+            </div>
+
+
+            <a id="glaccounts_retrieve" name="glaccounts_retrieve"
+                class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Retrieve a General Ledger Account</h4>
+                    <p>Example Requests:</p>
+                    <div class=apiClick>glaccounts/1</div>
+                    <br>
+                    <br>
+                    <div class=apiClick>glaccounts/1?template=true</div>
+                    <br>
+                    <br>
+                    <div class=apiClick>glaccounts/1?fields=name,glCode</div>
+                    <br>
+                    <br>
+                    <div class=apiClick>glaccounts/1?fetchRunningBalance=true</div>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/glaccounts/{glaccountsId}
+                    </code>
+                    <code class="method-response">
+{
+    "id": 1,
+    "name": "Cash",
+    "glCode": "100001",
+    "disabled": false,
+    "manualEntriesAllowed": true,
+    "type": {
+        "id": 1,
+        "code": "accountType.asset1",
+        "value": "ASSET"
+    },
+    "usage": {
+        "id": 1,
+        "code": "accountUsage.detail",
+        "value": "DETAIL"
+    },
+    "description": "Desc",
+    "organizationRunningBalance": 118437,
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="glaccounts_create" name="glaccounts_create" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Create a General Ledger Account</h4>
+                    <p>
+                        <b>Note:</b> You may optionally
+                        create Hierarchical Chart of Accounts by using the "parentId"
+                        property of an Account<br>
+                    </p>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>name, glCode, type, usage and manualEntriesAllowed
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/glaccounts
+                    </code>
+                    <code class="method-request">
+POST glaccounts
+Content-Type: application/json
+Request Body:
+{
+    "name": "Cash at Bangalore",
+    "glCode": "100017",
+    "manualEntriesAllowed": true,
+    "type": 1,
+    "tagId": "10",
+    "parentId": "18",
+    "usage": 1,
+    "description": "Cash at Bangalore Branch"
+}
+                    </code>
+                    <code class="method-response">
+{
+    "resourceId": 22
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="glaccounts_update" name="glaccounts_update" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Update a General Ledger Account</h4>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+PUT https://DomainName/api/v1/glaccounts/{glaccountId}
+                        </code>
+                    <code class="method-request">
+PUT glaccounts/22
+Content-Type: application/json
+Request Body:
+{
+    "name": "Cash at Bangalore rural"
+}
+                    </code>
+                    <code class="method-response">
+{
+    "resourceId": 22,
+    "changes": {
+        "name": "Cash at Bangalore rural"
+    }
+}
+                    </code>
+                </div>
+                <div class="method-example">
+                    <code class="method-request">
+PUT glaccounts/1
+Content-Type: application/json
+Request Body:
+{
+    "disabled": true
+}
+                    </code>
+                    <code class="method-response">
+{
+    "resourceId": 1,
+    "changes": {
+        "disabled": true
+    }
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="glaccounts_delete" name="glaccounts_delete" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Delete a General Ledger Account</h4>
+                    <p>
+                        <b>Note:</b> Only Ledger Accounts against which no transactions have been logged
+                        (either manually or by the loan or Savings portfolio) can be deleted.
+                    </p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+DELETE https://DomainName/api/v1/glaccounts/{glaccountId}
+                    </code>
+                    <code class="method-request">
+DELETE glaccounts/1
+Content-Type: application/json
+No Request Body:
+                    </code>
+                    <code class="method-response">
+{
+    "resourceId": 1,
+    "changes": {}
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="glclosures" name="glclosures" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h3>Accounting Closure</h3>
+                    <p>An accounting closure indicates that no more journal
+                    	entries may be logged (or reversed) in the system,
+                    	either manually or via the portfolio with an entry date
+                    	prior to the defined closure date
+                    </p>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Field Descriptions</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>closingDate</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The date for which the
+                            accounting closure is defined</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>officeId</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The identifer of the branch for
+                            	which accounting has been closed</td>
+                        </tr>
+                         <tr class=alt>
+                            <td>comments</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Description associated with an
+                            	Accounting closure</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+
+            <a id="glclosures_list" name="glclosures_list" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>List Accounting closures</h4>
+                    <p>Example Requests:</p>
+                    <div class=apiClick>glclosures</div>
+                    <br>
+                    <br>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/glclosures
+                    </code>
+                    <code class="method-response">
+[
+    {
+        "id": 7,
+        "officeId": 1,
+        "officeName": "Head Office",
+        "closingDate": [
+            2013,
+            1,
+            2
+        ],
+        "deleted": false,
+        "createdDate": [
+            2013,
+            1,
+            3
+        ],
+        "lastUpdatedDate": [
+            2013,
+            1,
+            3
+        ],
+        "createdByUserId": 1,
+        "createdByUsername": "mifos",
+        "lastUpdatedByUserId": 1,
+        "lastUpdatedByUsername": "mifos",
+        "comments": "closed",
+    },
+    {
+        "id": 6,
+        "officeId": 1,
+        "officeName": "Head Office",
+        "closingDate": [
+            2012,
+            12,
+            13
+        ],
+        "deleted": false,
+        "createdDate": [
+            2012,
+            12,
+            14
+        ],
+        "lastUpdatedDate": [
+            2012,
+            12,
+            14
+        ],
+        "createdByUserId": 1,
+        "createdByUsername": "mifos",
+        "lastUpdatedByUserId": 1,
+        "lastUpdatedByUsername": "mifos",
+        "comments": "hello",
+    }
+]
+                    </code>
+                </div>
+            </div>
+
+            <a id="glclosures_retrieve" name="glclosures_retrieve"
+                class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Retrieve an Accounting Closure</h4>
+                    <p>Example Requests:</p>
+                    <div class=apiClick>glclosures/1</div>
+                    <br>
+                    <br>
+                    <div class=apiClick>/glclosures/1?fields=officeName,closingDate</div>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/glclosures/{glclosureId}
+                    </code>
+                    <code class="method-response">
+{
+    "id": 7,
+    "officeId": 1,
+    "officeName": "Head Office",
+    "closingDate": [
+        2013,
+        1,
+        2
+    ],
+    "deleted": false,
+    "createdDate": [
+        2013,
+        1,
+        3
+    ],
+    "lastUpdatedDate": [
+        2013,
+        1,
+        3
+    ],
+    "createdByUserId": 1,
+    "createdByUsername": "mifos",
+    "lastUpdatedByUserId": 1,
+    "lastUpdatedByUsername": "mifos",
+    "comments": "closed",
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="glclosures_create" name="glclosures_create" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Create an Accounting Closure</h4>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>officeId,closingDate
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/glclosures
+                    </code>
+                    <code class="method-request">
+POST glclosures
+Content-Type: application/json
+Request Body:
+{
+    "officeId": 1,
+    "closingDate": "06 December 2012",
+    "comments": "The accountants are heading for a carribean vacation",
+ 	"locale": "en" ,
+  	"dateFormat": "dd MMMM yyyy"
+}
+                    </code>
+                    <code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 9
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="glclosures_update" name="glclosures_update" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Update an Accounting closure</h4>
+                    <p>Once an accounting closure is created, only the comments associated with it may be edited
+                    </p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+PUT https://DomainName/api/v1/glclosures/{glclosureId}
+                        </code>
+                    <code class="method-request">
+PUT glclosures/1
+Content-Type: application/json
+Request Body:
+{
+    "comments": "All transactions verified by Johnny Cash"
+}
+                    </code>
+                    <code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 1,
+    "changes": {
+        "comments": "All transactions verified by Johnny Cash"
+    }
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="glclosures_delete" name="glclosures_delete" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Delete an accounting closure</h4>
+                    <p>
+                        <b>Note:</b> Only the latest accounting closure
+                        associated with a branch may be deleted.
+                    </p>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+DELETE https://DomainName/api/v1/glclosures/{glclosureId}
+                    </code>
+                    <code class="method-request">
+DELETE glclosures/1
+Content-Type: application/json
+No Request Body:
+                    </code>
+                    <code class="method-response">
+{
+    "officeId": 1,
+    "resourceId": 1
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="journalentries" name="journalentries" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h3>Journal Entries</h3>
+                    <p>A journal entry refers to the logging of
+                    transactions against general ledger accounts. A journal
+                    entry may consist of several line items, each of which is either a "debit" or a "credit". The total amount of the
+                    debits must equal the total amount of the credits or the
+                    journal entry is said to be "unbalanced" <br/><br/>
+                    A journal entry directly changes the account balances on
+                    the general ledger
+                    </p>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Field Descriptions</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>officeId</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The identifier of the office (
+                            	cost center) at which the financial activity
+                            	occured</td>
+                        </tr>
+                        <tr class=alt>
+							<td>currencyCode</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>A three letter ISO code of currency.</td>
+						</tr>
+                        <tr class=alt>
+                            <td>glAccountId</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The identifier of the account (
+                            	Ledger Account) against which this journal entry
+                            	was made
+                            </td>
+                        </tr>
+                        <tr class=alt>
+                            <td>transactionDate</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The target date for which this
+                             entry was recorded</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>amount</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The Monetary amount associated
+                            with this entry
+                            </td>
+                        </tr>
+                        <tr class=alt>
+                            <td>comments</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>A description associated with
+                            this entry</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>entryType</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Either a Credit(1) or a Debit(2)</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>transactionId</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>A unique Identifier for a set
+                            	of related Credit and Debit entries that make up
+                            	a "balanced" jounral Entry. For a manual entry,
+                            	this feild is a unique string.<br/>
+                            	For a system generated entry, the combination
+                            	of transactionId and entityId is unique
+                            </td>
+                        </tr>
+                        <tr class=alt>
+                            <td>manualEntry</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Flag determines if an entry is
+                            generated by the portfolio (posted automatically by
+                            the system  during the lifecycle of loan or saving
+                            products) or manually created by using the <a href="
+                            journalentries_create">Create (Balanced) Journal
+                            Entries</a> API
+                        	</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>reversed</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Flag determines if this manual
+                            journal entry has been reversed using the <a href="
+                            journalentries_reverse">Reverse a Journal Entry</a>
+                            API. <br/>
+                            Note: A journal entry is reversed by logging debits
+                            for all credits that make up the Journal entry and
+                            vice-versa
+                        	</td>
+                        </tr>
+						<tr class=alt>
+                            <td>referenceNumber</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>An additional field that is used to store additional information about the entry (Ex: chequeNo)
+                        	</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>accountingRule</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Denotes the accounting rule id for posting journal entries.
+                        	</td>
+                        </tr>
+						<tr class=alt>
+                            <td>officeRunningBalance</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Describes office balance after the journal entry for the Ledger Account.
+                        	</td>
+                        </tr>
+						<tr class=alt>
+                            <td>organizationRunningBalance</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Describes complete organization balances after the journal entry for the Ledger Account.
+                        	</td>
+                        </tr>
+						<tr class=alt>
+                            <td>runningBalanceComputed</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Describes whether account balances computed for the journal entry.
+                        	</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>transactionDetails</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Additional details of transaction like payment details and notes .
+                        	</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>paymentTypeId</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Maps to a <a href="#configs_codes_codevalues">Code value</a> of a system defined  <a href="
+                            #configs_codes">Code</a> with the name "PaymentType". This is used to optionally identify the mode of payment (Ex: checks, Cash etc) associated with a Journal Entry
+                        	</td>
+                        </tr>
+                         <tr class=alt>
+                            <td>accountNumber, checkNumber, routingCode, receiptNumber, bankNumber</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Various properties associated with a payment type
+                        	</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+
+            <a id="journalentries_list" name="journalentries_list" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>List Journal Entries</h4>
+                    <p>The <i>list</i> capability of journal entries can support <b>pagination</b> and <b>sorting</b>.</p>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>offset</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 0
+						</dd>
+						<dd>Indicates from what result to start from.</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+						<dt>orderBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>displayName, accountNo, officeId, officeName</span>
+						</dd>
+						<dd>Orders the results by the field indicated.</dd>
+
+						<dt>sortBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>ASC, DESC</span>
+						</dd>
+						<dd>Indicates what way to order results if <i>orderBy</i> is used.</dd>
+
+						<dt>officeId</dt>
+						<dd>
+							Integer <span>optional</span>
+						</dd>
+						<dd>Provides ability to restrict list of centers returned based on the office there associated with.</dd>
+
+						<dt>glAccountId</dt>
+                        <dd>
+                            Integer <span>optional</span>
+                        </dd>
+                        <dt>manualEntriesOnly</dt>
+                        <dd>
+                            Boolean <span>optional</span>
+                        </dd>
+                        <dd>Flag determines if only manually created journal entries
+                        	are to be returned( journal entries created by
+                        	the system during lifecycle of loan or saving
+                        	products etc shall be excluded)
+                        	<br/>
+                        	Set to "false" by default if not passed in explicity
+                        </dd>
+                        <dt>fromDate</dt>
+                        <dd>
+                            Date<span>optional</span>
+                        </dd>
+                        <dd>Filters for Journal entries whose entry Date
+                        	is greater than or equal to the passed in Date
+                        </dd>
+                        <dt>toDate</dt>
+                        <dd>
+                            Date <span>optional</span>
+                        </dd>
+                        <dd>Filters for Journal entries whose entry Date
+                        	is lesser than or equal to the passed in Date
+                        </dd>
+                        <dt>transactionId</dt>
+                        <dd>
+                            String <span>optional</span>
+                        </dd>
+                        <dt>transactionDetails</dt>
+                        <dd>
+                            Boolean <span>optional</span>
+                        </dd>
+                        <dd>Flag determines if additional transactional details
+                        	to be returned(like payment details and notes).
+                        </dd>
+                        <dt>runningBalance</dt>
+                        <dd>
+                            Boolean <span>optional</span>
+                        </dd>
+                        <dd>Flag determines whether running balances
+                        	to be returned
+                        </dd>
+                        <dt>loanId</dt>
+                        <dd>
+                            Integer <span>optional</span>
+                        </dd>
+                        <dd>Provides ability to restrict journal entries based on the loan they are associated with
+                        </dd>
+                        <dt>savingsId</dt>
+                        <dd>
+                            Integer <span>optional</span>
+                        </dd>
+                        <dd>Provides ability to restrict journal entries based on the savings account they are associated with
+                        </dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>journalentries</div>
+					<br>
+					<div class=apiClick>journalentries?transactionId=PB37X8Y21EQUY4S</div>
+                    <br>
+                    <div class=apiClick>journalentries?officeId=1&manualEntriesOnly=true&fromDate=1 July 2013&toDate=15 July 2013&dateFormat=dd MMMM yyyy&locale=en</div>
+                    <br>
+					<div class=apiClick>journalentries?fields=officeName,glAccountName,transactionDate</div>
+					<br>
+					<div class=apiClick>journalentries?offset=10&limit=50</div>
+					<br>
+					<div class=apiClick>journalentries?orderBy=transactionId&sortOrder=DESC</div>
+					<br>
+					<div class=apiClick>journalentries?runningBalance=true</div>
+					<br>
+					<div class=apiClick>journalentries?transactionDetails=true</div>
+					<br>
+					<div class=apiClick>journalentries?loanId=12</div>
+					<br>
+					<div class=apiClick>journalentries?savingsId=24</div>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/journalentries?transactionId=PB37X8Y21EQUY4S
+                    </code>
+                    <code class="method-response">
+{
+  "totalFilteredRecords": 24,
+  "pageItems": [
+    {
+      "id": 1,
+      "officeId": 1,
+      "officeName": "Head Office",
+      "glAccountName": "ACCOUNT_NAME_WTYRB",
+      "glAccountId": 98,
+      "glAccountCode": "ASSET_C01367768735188",
+      "glAccountType": {
+        "id": 1,
+        "code": "accountType.asset",
+        "value": "ASSET"
+      },
+      "transactionDate": [
+        2011,
+        3,
+        4
+      ],
+      "entryType": {
+        "id": 2,
+        "code": "journalEntrytType.debit",
+        "value": "DEBIT"
+      },
+      "amount": 10000,
+      "transactionId": "33",
+      "manualEntry": false,
+      "entityType": {
+        "id": 1,
+        "code": "productType.loan",
+        "value": "LOAN"
+      },
+      "entityId": 7,
+      "createdByUserId": 1,
+      "createdDate": [
+        2013,
+        5,
+        5
+      ],
+      "createdByUserName": "mifos",
+      "reversed": false,
+    },
+    {
+      "id": 2,
+      "officeId": 1,
+      "officeName": "Head Office",
+      "glAccountName": "ACCOUNT_NAME_WTYRB",
+      "glAccountId": 98,
+      "glAccountCode": "ASSET_C01367768735188",
+      "glAccountType": {
+        "id": 1,
+        "code": "accountType.asset",
+        "value": "ASSET"
+      },
+      "transactionDate": [
+        2011,
+        3,
+        4
+      ],
+      "entryType": {
+        "id": 1,
+        "code": "journalEntryType.credit",
+        "value": "CREDIT"
+      },
+      "amount": 10000,
+      "transactionId": "33",
+      "manualEntry": false,
+      "entityType": {
+        "id": 1,
+        "code": "productType.loan",
+        "value": "LOAN"
+      },
+      "entityId": 7,
+      "createdByUserId": 1,
+      "createdDate": [
+        2013,
+        5,
+        5
+      ],
+      "createdByUserName": "mifos",
+      "reversed": false,
+
+    }
+  ]
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="journalentries_create" name="journalentries_create" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Create "Balanced" Journal Entries</h4>
+                    <p>
+                        <b>Note:</b> A Balanced (simple) Journal entry would
+                        have atleast one "Debit" and one "Credit" entry whose
+                        amounts are equal <br/>
+                        Compound Journal entries may have "n" debits and "m"
+                        credits where both "m" and "n" are greater than 0
+                        and the net sum or all debits and credits are equal
+                        <br>
+                    </p>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>officeId, transactionDate
+                            </td>
+                        </tr>
+                        <tr>
+                        	 <td class=fielddesc></td>
+                        </tr>
+                         <tr class=alt>
+                            <td>credits</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Details of the credits contained
+                            	in the journal entry. Each credit entry contains
+                            	the following items <br/>
+                            	glAccountId: Identifier of the general ledger
+                            	account against which the credit entry shall be
+                            	made <br/>
+                            	amount: Amount of money credited
+                            	<br/>
+                            	comments: Optional description associated with
+                            	a credit entry
+                            </a>.
+                            </td>
+                        </tr>
+                        <tr class=alt>
+                            <td>debits</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>Details of the debits contained
+                            	in the journal entry. Each debit entry contains the
+                            	following items <br/>
+                            	glAccountId: Identifier of the general ledger account
+                            	against which the debit entry shall be
+                            	made <br/>
+                            	amount: Amount of money debited
+                            	<br/>
+                            	comments: Optional description associated with
+                            	a debit entry
+                            </a>.
+                            </td>
+                        </tr>
+                    </table>
+                    <br />
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>paymentTypeId, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber</td>
+						</tr>
+					</table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/journalentries
+                    </code>
+                    <code class="method-request">
+POST journalentries
+Content-Type: application/json
+Request Body:
+{
+"officeId": 1,
+"transactionDate": "06 December 2012",
+"comments": "Gifts for staff",
+"locale": "en" ,
+"currencyCode": "USD",
+"dateFormat": "dd MMMM yyyy",
+"credits":[{"glAccountId":1,
+			"amount":5000},
+		   {"glAccountId":2,
+			"amount":5000}
+		  ],
+"debits":[{"glAccountId":3,
+			"amount":5000},
+		   {"glAccountId":4,
+			"amount":5000}
+		 ],
+"paymentTypeId": "12",
+"accountNumber": "accno123",
+"checkNumber": "chec123",
+"routingCode": "rou123",
+"receiptNumber": "rec123",
+"bankNumber": "ban123"
+}
+                    </code>
+                    <code class="method-response">
+{
+    "officeId": 1,
+    "transactionId": "DNEEMS2LPD0NJ9O"
+}
+                    </code>
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/journalentries
+                    </code>
+                    <code class="method-request">
+POST journalentries
+Content-Type: application/json
+Request Body:
+{
+	"locale": "en",
+	"dateFormat": "dd MMMM yyyy",
+	"officeId": "1",
+	"transactionDate": "23 May 2013",
+	"referenceNumber": "547",
+	"currencyCode": "USD",
+	"comments": "asdf",
+	"accountingRule": "2",
+	"amount": "547"
+}
+                    </code>
+                    <code class="method-response">
+{
+	"officeId":1,
+	"transactionId":"RS9MCISID4WK1ZM"
+}
+                    </code>
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/journalentries
+                    </code>
+                    <code class="method-request">
+POST journalentries
+Content-Type: application/json
+Request Body:
+{
+	"locale": "en",
+	"dateFormat": "dd MMMM yyyy",
+	"officeId": "1",
+	"transactionDate": "23 May 2013",
+	"referenceNumber": "547",
+	"comments": "asdf",
+	"accountingRule": "2",
+	"credits":[{"glAccountId":1,
+				"amount":5000},
+			   {"glAccountId":2,
+				"amount":5000}
+			  ],
+	"debits":[{"glAccountId":3,
+				"amount":5000},
+			   {"glAccountId":4,
+				"amount":5000}
+			 ]
+}
+                    </code>
+                    <code class="method-response">
+{
+	"officeId":1,
+	"transactionId":"RS9MCISID4WK1ZM"
+}
+                    </code>
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/journalentries
+                    </code>
+                    <code class="method-request">
+POST journalentries
+Content-Type: application/json
+Request Body:
+{
+	"locale": "en",
+	"dateFormat": "dd MMMM yyyy",
+	"officeId": "1",
+	"transactionDate": "23 May 2013",
+	"referenceNumber": "547",
+	"comments": "asdf",
+	"accountingRule": "3",
+	"credits":[{"glAccountId":1,
+				"amount":5000},
+			   {"glAccountId":2,
+				"amount":5000}
+			  ],
+	"amount": "10000"
+}
+                    </code>
+                    <code class="method-response">
+{
+	"officeId":1,
+	"transactionId":"RS9MCISID4WK1ZM"
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="journalentries_updatebalance" name="journalentries_updatebalance" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Update Running balances for Journal Entries</h4>
+                    <p>
+                        This API calculates the running balances for office. If office ID not provided this API calculates running balances for all offices.
+                        <br>
+                    </p>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>officeId
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/journalentries?command=updateRunningBalance
+                    </code>
+                    <code class="method-request">
+POST journalentries?command=updateRunningBalance
+Content-Type: application/json
+Request Body:
+{
+"officeId": 1,
+}
+                    </code>
+                    <code class="method-response">
+{
+    "officeId": 1,
+}
+                    </code>
+
+                </div>
+            </div>
+
+
+            <a id="journalentries_retrieve" name="journalentries_retrieve"
+                class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Retrieve a single Entry</h4>
+                    <p>Example Requests:</p>
+                    <div class=apiClick>journalentries/1</div>
+                    <br>
+                    <br>
+                    <br>
+                    <div class=apiClick>journalentries/1?fields=officeName,glAccountId,entryType,amount</div>
+                    <br>
+                    <div class=apiClick>journalentries/1?runningBalance=true</div>
+                    <br>
+                    <div class=apiClick>journalentries/1?transactionDetails=true</div>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/journalentries/{entryId}
+                    </code>
+                    <code class="method-response">
+{
+    "id": 1,
+    "officeId": 2,
+    "officeName": "sub branch 1",
+    "glAccountName": "Income from interest",
+    "glAccountId": 14,
+    "glAccountCode": "400001",
+    "glAccountType": {
+        "id": 4,
+        "code": "accountType.income",
+        "value": "INCOME"
+    },
+    "transactionDate": [
+        2012,
+        11,
+        2
+    ],
+    "entryType": {
+        "id": 1,
+        "code": "journalEntryType.credit",
+        "value": "CREDIT"
+    },
+    "amount": 900,
+    "transactionId": "13",
+    "manualEntry": true,
+    "createdByUserId": 1,
+    "createdDate": [
+        2012,
+        11,
+        2
+    ],
+    "createdByUserName": "mifos",
+    "reversed": false,
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="journalentries_reverse" name="journalentries_reverse"
+                class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Reverse a Journal Entry</h4>
+                    <p>
+                        <b>Note:</b> A journal entry is reversed by logging debits
+                        for all credits that constitute the Journal entry and
+                        vice-versa. The transactionId of the "reversal Entry" is returned
+                        as the methods response
+                    </p>
+                    <h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>command</dt>
+						<dd>
+							String<span> Mandatory, case-insensitive</span>
+						</dd>
+						<dd>'reverse' : Reverse the Journal Entry</dd>
+					</dl>
+                    <h5>Optional Arguments</h5>
+                    <dl class="argument-list">
+                        <dt>comments</dt>
+                        <dd>
+                            String <span>optional</span>
+                        </dd>
+                        <dd>Optional string to be applied as a comment for the created reversal entry,
+                        default comment is used if this field is not supplied</dd>
+                    </dl>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/journalentries/{transactionId}?command=reverse
+                    </code>
+                    <code class="method-request">
+POST journalentries/C1MAE935K0IAEYA?command=reverse
+Content-Type: application/json
+</code>
+                    <code class="method-response">
+{
+    "transactionId": "YFDYMPBVVI9TRJP"
+}
+                    </code>
+                </div>
+            </div>
+
+<!-- start office gl accout mappings-->
+
+<a id="officeglaccount" name="officeglaccount" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h3>Mapping Financial Activities to Accounts</h3>
+                    <p>
+                    	Organization Level Financial Activities like Asset and Liability Transfer can be mapped to GL Account. Integrated accounting takes these accounts into consideration when an Account transfer is made between a savings to loan/savings account and vice-versa
+                    </p>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Field Descriptions</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>financialActivityId</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The identifier of the Financial Activity
+                            	</td>
+                        </tr>
+                        <tr class=alt>
+                            <td>glAccountId</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>The identifier of a GL Account (
+                            	Ledger Account) which shall be used as the default account for the selected Financial Activity
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+
+            <a id="financialactivityaccounts_list" name="financialactivityaccounts_list" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>List Financial Activities to Accounts Mappings</h4>
+                   	<p>Example Requests:</p>
+					<div class=apiClick>financialactivityaccounts</div>
+				</div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/financialactivityaccounts
+                    </code>
+                    <code class="method-response">
+
+[
+  {
+    "id": 1,
+    "financialActivityData": {
+      "id": 200,
+      "name": "liabilityTransfer",
+      "mappedGLAccountType": "LIABILITY"
+    },
+    "glAccountData": {
+      "id": 55,
+      "name": "Liability Transfer (Temp)",
+      "glCode": "220004-Temp"
+    }
+  },
+  {
+    "id": 2,
+    "financialActivityData": {
+      "id": 100,
+      "name": "assetTransfer",
+      "mappedGLAccountType": "ASSET"
+    },
+    "glAccountData": {
+      "id": 33,
+      "name": "Petty Cash",
+      "glCode": "20302"
+    }
+  }
+]
+                    </code>
+                </div>
+            </div>
+
+            <a id="financialactivityaccounts_create" name="financialactivityaccounts_create" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Create a new Financial Activity to Accounts Mapping</h4>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>financialActivityId, glAccountId
+                            </td>
+                        </tr>
+
+                    </table>
+                    <br />
+
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/financialactivityaccounts
+                    </code>
+                    <code class="method-request">
+POST financialactivityaccounts
+Content-Type: application/json
+Request Body:
+{
+"financialActivityId": 200,
+"glAccountId":2
+}
+                    </code>
+                    <code class="method-response">
+{
+    "resourceId": 1
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="financialactivityaccounts_update" name="financialactivityaccounts_update" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Update a Financial Activity to Account Mapping</h4>
+                    <p>
+                        the API updates the Ledger account linked to a Financial Activity
+                        <br>
+                    </p>
+
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+PUT https://DomainName/api/v1/financialactivityaccounts/{financialactivityaccountId}
+                    </code>
+                    <code class="method-request">
+PUT financialactivityaccounts
+Content-Type: application/json
+Request Body:
+{
+"financialActivityId": 200,
+"glAccountId":3
+}
+                    </code>
+                    <code class="method-response">
+{
+  "resourceId": 2,
+  "changes": {
+    "glAccountId": 3
+  }
+}
+                    </code>
+
+                </div>
+            </div>
+
+
+            <a id="financialactivityaccounts_retrieve" name="financialactivityaccounts_retrieve"
+                class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Retrieve a Financial Activity to Account Mapping</h4>
+                    <p>Example Requests:</p>
+                    <div class=apiClick>financialactivityaccounts/1</div>
+                    <br>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+GET https://DomainName/api/v1/financialactivityaccounts/{financialactivityaccountId}
+                    </code>
+                    <code class="method-response">
+{
+  "id": 1,
+  "financialActivityData": {
+    "id": 200,
+    "name": "liabilityTransfer",
+    "mappedGLAccountType": "LIABILITY"
+  },
+  "glAccountData": {
+    "id": 55,
+    "name": "Liability Transfer (Temp)",
+    "glCode": "220004-Temp"
+  }
+}
+                    </code>
+                </div>
+            </div>
+
+            <a id="financialactivityaccounts_delete" name="financialactivityaccounts_delete"
+                class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Delete a Financial Activity to Account Mapping</h4>
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+DELETE https://DomainName/api/v1/financialactivityaccounts/{financialactivityaccountId}
+                    </code>
+                    <code class="method-request">
+DELETE financialactivityaccounts/1?
+Content-Type: application/json
+</code>
+                    <code class="method-response">
+{
+    "resourceId": "2"
+}
+                    </code>
+                </div>
+            </div>
+
+
+			<!-- start Periodic accrual API doc-->
+
+			<a id="periodicaccrualaccounting" name="periodicaccrualaccounting" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h3>Periodic Accrual Accounting</h3>
+                    <p>
+                    	Periodic Accrual is to accrue the loan income till the specific date or till batch job scheduled time.
+                    </p>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Field Descriptions</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>tillDate</td>
+                        </tr>
+                        <tr>
+                            <td class=fielddesc>which specifies periodic accruals should happen till the given Date
+                            	</td>
+                        </tr>
+
+                    </table>
+                </div>
+            </div>
+
+
+
+		<a id="periodicaccrualaccounting_run" name="periodicaccrualaccounting_run" class="old-syle-anchor">&nbsp;</a>
+            <div class="method-section">
+                <div class="method-description">
+                    <h4>Executes Periodic Accrual Accounting</h4>
+                    <table class=matrixHeading>
+                        <tr class="matrixHeadingBG">
+                            <td><div class="fineractHeading2">Mandatory Fields</div></td>
+                        </tr>
+                        <tr class=alt>
+                            <td>tillDate
+                            </td>
+                        </tr>
+
+                    </table>
+                    <br />
+
+                </div>
+                <div class="method-example">
+                    <code class="method-declaration">
+POST https://DomainName/api/v1/runaccruals
+                    </code>
+                    <code class="method-request">
+POST financialactivityaccounts
+Content-Type: application/json
+Request Body:
+{
+	"locale": "en",
+	"dateFormat": "dd MMMM yyyy",
+	"tillDate":"04 June 2014"
+}
+                    </code>
+                    <code class="method-response">
+                    </code>
+                </div>
+            </div>
+
+			<!-- Provisioning Entries -->
+			<a id="provisioningentries" name="provisioningentries" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Provisioning Entries</h3>
+					<p>This defines the Provisioning Entries for all active loan products
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>date</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Date on which day provisioning entries should be created
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>createjournalentries</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Boolean variable whether to add journal entries for generated provisioning entries
+							</td>
+						</tr>
+
+					</table>
+				</div>
+			</div>
+
+			<a id="provisioningentries_create" name="provisioningentries_create"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create new Provisioning Entries</h4>
+					<p>Creates a new Provisioning Entries</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>date</td>
+							</tr>
+						<tr class=alt>
+							<td>dateFormat</td>
+							</tr>
+						<tr class=alt>
+							<td>locale</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>createjournalentries</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					POST https://DomainName/api/v1/provisioningentries
+					</code>
+					<code class="method-request">
+					POST provisioningentries
+					Content-Type: application/json
+					Request Body:
+					{
+					"date":"16 October 2015",
+					"locale":"en",
+					"dateFormat":"dd MMMM yyyy",
+					"createjournalentries":true
+					}
+					</code>
+					<code class="method-response">
+					{
+					"resourceId": 1
+					}
+					</code>
+				</div>
+			</div>
+
+			<a id="provisioningentries_list" name="provisioningentries_list"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List all Provisioning Entries</h4>
+					<p>List all Provisioning Entries</p>
+
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					GET https://DomainName/api/v1/provisioningentries
+					</code>
+					<code class="method-request">
+					GET provisioningentries
+					Content-Type: application/json
+					Request Body:
+					{
+
+					}
+					</code>
+					<code class="method-response">
+					{[
+					{"id":1, "journalEntry":true, "createdUser":"mifos", "createdDate":"16 October 2014"},
+					{"id":2, "journalEntry":true, "createdUser":"mifos", "createdDate":"16 October 2015"}
+					]
+					}
+					</code>
+				</div>
+			</div>
+
+			<a id="provisioningentry_retrieve" name="provisioningentry_retrieve"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieves a Provisioning Entry</h4>
+					<p>Returns the details of a generated Provisioning Entry.</p>
+
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					GET https://DomainName/api/v1/provisioningentries/{privisioningEntryId}
+					</code>
+					<code class="method-request">
+					GET provisioningentries
+					Content-Type: application/json
+					Request Body:
+					{
+
+					}
+					</code>
+					<code class="method-response">
+					{
+					"id":1,
+					"journalEntry":true,
+					"createdUser":"mifos",
+					"createdDate":"16 October 2014",
+					"provisioningEntries":[
+					{"historyId":1,
+					"officeId:":1
+					"officeName:":"Head Office",
+					"currencyCode:":"USD",
+					"productId:":12,
+					"productName:":"Loan Product Name1",
+					"categoryId":4,
+					"categoryName":"LOSS",
+					"amountReserved":63098.29,
+					"liabilityAccount":4,
+					"liabilityCode":"Liability Account Name",
+					"expenseAccount":6,
+					"expenseCode":"Expense Account Name"
+					},
+					{"id":2,
+					"journalEntry":true,
+					"createdUser":"mifos",
+					"createdDate":"16 October 2014",
+					"provisioningEntries":[
+					{"historyId":1,
+					"officeId:":2
+					"officeName:":"Branch Office",
+					"currencyCode:":"USD",
+					"productId:":12,
+					"productName:":"Loan Product Name1",
+					"categoryId":2,
+					"categoryName":"SUB-STANDARD",
+					"amountReserved":63098.29,
+					"liabilityAccount":1,
+					"liabilityCode":"Liability Account Name",
+					"expenseAccount":2,
+					"expenseCode":"Expense Account Name"
+					}
+					]
+
+					}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="provisioningentry_recreate" name="provisioningentry_recreate"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Recreates Provisioning Entry</h4>
+					<p>Recreates Provisioning Entry.</p>
+
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					POST https://DomainName/api/v1/provisioningentries/{privisioningEntryId}?command=recreateprovisioningentry
+					</code>
+					<code class="method-request">
+					POST provisioningentries
+					Content-Type: application/json
+					Request Body:
+					{
+					"command":"recreateprovisioningentry"
+					}
+					</code>
+					<code class="method-response">
+					{
+					"resourceId": 1
+					}
+					</code>
+				</div>
+			</div>
+
+			<a id="provisioningentry_addjournals" name="provisioningentry_addjournals"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieves a Provisioning Entry</h4>
+					<p>Returns the details of a generated Provisioning Entry.</p>
+
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					POST https://DomainName/api/v1/provisioningentries/{privisioningEntryId}?command=createjournalentry
+					</code>
+					<code class="method-request">
+					POST provisioningentries
+					Content-Type: application/json
+					Request Body:
+					{
+						"command":"createjournalentry"
+					}
+					</code>
+
+					<code class="method-response">
+					{
+					"resourceId": 1
+					}
+					</code>
+				</div>
+			</div>
+
+
+			<!-- end Periodic accrual API doc-->
+
+			<!-- start of Search API doc -->
+		<a id="search" name="search" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Search</h3>
+					<p>Search API allows to search scoped resources clients, loans and groups on specified fields.</p>
+
+					<table class=matrixHeading id=scoped_resources>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Scoped Resources</div></td>
+							<td><div class="fineractHeading2">Scoped fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td rowspan="3"><a href="#clients">Client</a></td>
+							<td>Display Name</td>
+						</td>
+						<tr class=alt>
+							<td>Account Number</td>
+						</tr>
+						<tr class=alt>
+							<td>External Id</td>
+						</tr>
+						<tr>
+							<td><a href="#loans">Loan</a></td>
+							<td>Account Number</td>
+						</td>
+						<tr class=alt>
+							<td><a href="#client_identifiers">Client Identifiers</a></td>
+							<td>Document Key</td>
+						</td>
+						<tr>
+							<td rowspan=2><a href="#groups">Group</a></td>
+							<td>Name</td>
+						</td>
+						<tr class=alt>
+							<td>Account Number</td>
+						</tr>
+						<tr class=alt>
+							<td></td>
+							<td>External Id</td>
+						</td>
+					</table>
+				</div>
+			</div>
+			<a id="search_resource" name="search_resource"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Search Resources</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>search?query=000000001</div>
+					<br>
+					<div class=apiClick>search?query=Petra&resource=clients,groups</div>
+					<br>
+					<div class=apiClick>search?query=Petra&resource=clients,groups&exactMatch=true</div>
+					<br>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Parameters</div></td>
+							<td><div class="fineractHeading2">Description</div></td>
+						</tr>
+						<tr class=alt>
+							<td>
+								query
+								<br><i>(mandatory)</i>
+							</td>
+							<td class=fielddesc>
+								String which is searched on scoped resources
+							</td>
+						</tr>
+						<tr>
+							<td>
+								resource
+								<br><i>(non-mandatory)</i>
+							</td>
+							<td class=fielddesc>
+								Scoped resources on which search can be performed. If there is no resource parameter passed then search will be performed on all <a href="#search">scoped</a> resources.
+							</td>
+						</tr>
+						<tr>
+							<td>
+								exactMatch
+								<br><i>(non-mandatory)</i>
+							</td>
+							<td class=fielddesc>
+								Scoped resources on which search can be performed. If there is no exactMatch parameter passed then search will be performed for all partial matches <a href="#search">scoped</a> resources.
+							</td>
+						</tr>
+					</table>
+					<br>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Order of Search results</div></td>
+						</tr>
+						<tr class=alt>
+							<td class=fielddesc>
+								<b>List exact match results in following order</b>
+								<ol start=1>
+									<li>1. Clients</li>
+									<li>2. Loans</li>
+									<li>3. Client Identifiers</li>
+									<li>4. Groups</li>
+								</ol>
+							</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								<b>List partial match results in following order</b>
+								<ol start=1>
+									<li>1. Clients</li>
+									<li>2. Loans</li>
+									<li>3. Client Identifiers</li>
+									<li>4. Groups</li>
+								</ol>
+							</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/search?query=000000111
+					</code>
+					<code class="method-response">
+[
+  {
+    "entityId": 111,
+    "entityAccountNo": "000000111",
+    "entityName": "12-A Flat Loan",
+    "entityType": "LOAN",
+    "parentId": 65,
+    "parentName": "aaaa aaaaa",
+    "parentType": "client"
+  }
+]
+					</code>
+
+					<code class="method-declaration">
+GET https://DomainName/api/v1/search?query=000000001&exactMatch=true
+					</code>
+					<code class="method-response">
+[{
+	"entityId": 1,
+	"entityAccountNo": "000000001",
+	"entityExternalId": "ID_JKZGEXF",
+	"entityName": "Group_Name_HVCU5",
+	"entityType": "GROUP",
+	"parentId": 1,
+	"parentName": "Head Office",
+	"entityStatus": {
+		"id": 300,
+		"code": "groupingStatusType.active",
+		"value": "Active"
+	}
+},
+{
+	"entityId": 1,
+	"entityAccountNo": "000000001",
+	"entityExternalId": "ID_UOZAGPZ",
+	"entityName": "Client_FirstName_KJ5B6 Client_LastName_KDGX",
+	"entityType": "CLIENT",
+	"parentId": 1,
+	"parentName": "Head Office",
+	"entityStatus": {
+		"id": 300,
+		"code": "clientStatusType.active",
+		"value": "Active"
+	}
+}]
+					</code>
+				</div>
+			</div>
+			<a id="advance_search" name="advance_search" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Advance Search</h3>
+					<p>AdHoc Query search supported parameters and field descriptions</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>entities</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Entities to be included for search.(presently support added only for loans)<br></td>
+						</tr>
+						<tr class=alt>
+							<td>loanStatus</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Loan status values to be included for search.<br>
+								Supported loan status:["all","active","overpaid","closed","writeoff"]
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>loanProducts</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Array of loanproduct ids.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>offices</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Array of office ids.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>loanDateOption</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Date condition name for search query.<br>
+								Supported values "approvalDate" or "createdDate" or "disbursalDate".
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>loanFromDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Start date for 'loanDateOption' in 'yyyy-MM-dd' format. </td>
+						</tr>
+						<tr class=alt>
+							<td>loanToDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>End date for 'loanDateOption' in 'yyyy-MM-dd' format. </td>
+						</tr>
+						<tr class=alt>
+							<td>includeOutStandingAmountPercentage</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>It is a Boolean value, allows to search percentage of loan outstanding amount with in a given range</td>
+						</tr>
+						<tr class=alt>
+							<td>outStandingAmountPercentageCondition</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Condition type for outstanding amount percentage.
+								Supported values: between or &lt;= or &gt;= or &lt; or &gt; or = <br>
+								When 'outStandingAmountPercentageCondition' is <b>between</b> required fields <b>minOutStandingAmountPercentage</b> and <b>maxOutStandingAmountPercentage</b>
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>minOutStandingAmountPercentage</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Minimum percentage of outstanding amount. <br> Mandatory when 'outStandingAmountPercentageCondition' type is 'between' </td>
+						</tr>
+						<tr class=alt>
+							<td>maxOutStandingAmountPercentage </td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Maximum percentage of outstanding amount. <br> Mandatory when 'outStandingAmountPercentageCondition' type is 'between' </td>
+						</tr>
+						<tr class=alt>
+							<td>outStandingAmountPercentage</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Percentage of outstanding amount for search. <br> Mandatory when 'outStandingAmountPercentageCondition' type is otherthan 'between' </td>
+						</tr>
+						<tr class=alt>
+							<td>includeOutstandingAmount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Boolean value, allows user to search loan outstanding amount with in a given range</td>
+						</tr>
+						<tr class=alt>
+							<td>outstandingAmountCondition</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Condition type to search outstanding amount.
+								Supported values: between or &lt;= or &gt;= or &lt; or &gt; or = <br>
+								When 'outstandingAmountCondition' is <b>between</b> required fields <b>minOutStandingAmount</b> and <b>maxOutStandingAmount</b>
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>minOutStandingAmount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Minimum value of outstanding amount for outstandingAmountCondition type between. <br> Mandatory when 'outstandingAmountCondition' type is 'between' </td>
+						</tr>
+						<tr class=alt>
+							<td>maxOutStandingAmount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Maximum value of outstanding amount for outstandingAmountCondition type between. <br> Mandatory when 'outstandingAmountCondition' type is 'between' </td>
+						</tr>
+						<tr class=alt>
+							<td>outStandingAmount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Default value of outstanding amount for search. <br> Mandatory when 'outstandingAmountCondition' type is otherthan 'between' </td>
+						</tr>
+					</table>
+				</div>
+			</div>
+			<a id="advance_search_resource" name="advance_search_resource" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>AdHoc Query Search</h4>
+					<p>
+						AdHocQuery search has more search options, it is a <b>POST</b> request, it uses request body to send search parameters<br>
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>entities</td>
+						</tr>
+					</table>
+						<br />
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>
+								loanStatus, loanProducts, offices, loanDateOption, loanFromDate, loanToDate, <br>includeOutStandingAmountPercentage, outStandingAmountPercentageCondition, <br>
+								minOutStandingAmountPercentage and maxOutStandingAmountPercentage OR outStandingAmountPercentage, <br>includeOutstandingAmount, outstandingAmountCondition, <br>
+								minOutstandingAmount and maxOutstandingAmount OR outstandingAmount</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/search/advance
+					</code>
+					<code class="method-request">
+POST search/advance
+Content-Type: application/json Request Body:
+{
+	"locale": "en",
+	"dateFormat": "yyyy-MM-dd",
+	"entities": ["loans"],
+	"loanStatus": ["all","active","overpaid"],
+	"loanProducts": ["65","97","73","82"],
+	"offices": ["1","10","100"],
+	"loanDateOption": "approvalDate",
+	"loanFromDate": "2013-01-01",
+	"loanToDate": "2014-01-27",
+	"includeOutStandingAmountPercentage": true,
+	"outStandingAmountPercentageCondition": "<=",
+	"outStandingAmountPercentage": "80",
+	"includeOutstandingAmount": true,
+	"outstandingAmountCondition": "between",
+	"minOutstandingAmount": "100",
+	"maxOutstandingAmount": "10000"
+}
+					</code>
+					<code class="method-response">
+[{
+	"officeName": "HFC",
+	"loanProductName": "01 BC3M",
+	"count": 86,
+	"loanOutStanding": 5692.41,
+	"percentage": 76.4
+},
+{
+	"officeName": "keithoffice",
+	"loanProductName": "IGL",
+	"count": 3,
+	"loanOutStanding": 4168.23,
+	"percentage": 56.9
+},
+{
+	"officeName": "Rio",
+	"loanProductName": "IGL",
+	"count": 1,
+	"loanOutStanding": 1681.12,
+	"percentage": 55.56
+}]
+					</code>
+				</div>
+			</div>
+		<!-- ends Search API docs-->
+
+		<!-- start of interest rate chart API docs -->
+		<a id="interestratechart" name="interestratechart" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h3>Interest Rate Chart</h3>
+				<p>This defines an interest rate scheme that can be associated to a term deposit product. This will have a slab (band or range) of deposit periods and the associated interest rates applicable along with incentives for each band.</p>
+
+				<table class=matrixHeading>
+					<tr class="matrixHeadingBG">
+						<td><div class="fineractHeading2">Field Descriptions</div></td>
+					</tr>
+					<tr class=alt>
+						<td>name</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>Name of the interest rate chart.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>description</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>
+							Description of Interest rate chart.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>fromDate</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>
+							Date from when the chart is valid. The fromDate is mandatory and used to find out applicable interest rate chart for Accounts creation (FD & RD accounts). There should not be any overlapping of fromDate and endDate of charts for a product.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>endDate</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>
+							Validity end date of the chart. This is optional, if not provided means it is the current chart applicable.
+						</td>
+					</tr>
+				</table>
+			</div>
+		</div>
+
+		<a id="interestratechart_create" name="interestratechart_create"
+				class="old-syle-anchor">&nbsp;</a>
+				<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Chart</h4>
+					<p>Creats a new chart which can be attached to a term deposit products (FD or RD).</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>fromDate</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name, description and endDate</td>
+						</tr>
+					</table>
+
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/interestratecharts
+					</code>
+					<code class="method-request">
+POST interestratecharts
+Content-Type: application/json
+Request Body:
+{
+  "name": "Chart - 2014",
+  "description": "This chart is applicable for year 2014",
+  "type": "Document",
+  "locale": "en",
+  "dateFormat": "dd MMMM yyyy",
+  "fromDate": "01 Jan 2014"
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="interestratechart_list" name="interestratechart_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve all Charts</h4>
+					<p>Retrieve list of charts associated with a term deposit product(FD or RD).</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>productId</dt>
+						<dd>
+							Integer <span>mandatory</span>
+						</dd>
+						<dd>Retrieves Interest rate charts to a deposit product.</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>interestratecharts?productId=1</div>
+					<br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/interestratecharts?productId={productId)
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "fromDate": [
+      2014,
+      1,
+      1
+    ],
+    "savingsProductId": 1,
+    "savingsProductName": "Fixed Deposit Product 001",
+    "chartSlabs": [
+      {
+        "id": 1,
+        "periodType": {
+          "id": 0,
+          "code": "interestChartPeriodType.days",
+          "value": "Days"
+        },
+        "fromPeriod": 1,
+        "annualInterestRate": 6,
+        "currency": {
+          "code": "USD",
+          "name": "US Dollar",
+          "decimalPlaces": 2,
+          "displaySymbol": "$",
+          "nameCode": "currency.USD",
+          "displayLabel": "US Dollar ($)"
+        }
+      }
+    ]
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="interestratechart_retrieve" name="interestratechart_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Chart</h4>
+
+					<p>Example Requests:</p>
+					<div class=apiClick>interestratecharts/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/interestratecharts/{chartId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "fromDate": [
+    2014,
+    1,
+    1
+  ],
+  "savingsProductId": 1,
+  "savingsProductName": "Fixed Deposit Product 001",
+  "chartSlabs": [
+    {
+      "id": 1,
+      "periodType": {
+        "id": 0,
+        "code": "interestChartPeriodType.days",
+        "value": "Days"
+      },
+      "fromPeriod": 1,
+      "annualInterestRate": 6,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      }
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="interestratechart_update" name="interestratechart_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Chart</h4>
+					<p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/interestratecharts/{chartId}
+					</code>
+					<code class="method-request">
+PUT interestratecharts/1
+Content-Type: application/json
+Request Body:
+{
+  "name": "Interest rate chart for 2014",
+  "description": "Interest rate chart for 2014",
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="interestratechart_delete" name="interestratechart_delete"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Chart</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/interestratecharts/{chartId}
+					</code>
+					<code class="method-request">
+DELETE interestratecharts/1
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="interestratechart_template" name="interestratechart_template" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Chart Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for creating a chart.
+						The template data returned consists of any or all
+						of:
+					<ul>
+						<li class=normalli>Field Defaults</li>
+						<li class=normalli>Allowed Value Lists</li>
+					</ul>
+					</p>
+
+					<p>Example Request:</p>
+					<div class=apiClick>interestratecharts/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/interestratecharts/template
+					</code>
+					<code class="method-response">
+{
+  "periodTypes": [
+    {
+      "id": 0,
+      "code": "interestChartPeriodType.days",
+      "value": "Days"
+    },
+    {
+      "id": 1,
+      "code": "interestChartPeriodType.weeks",
+      "value": "Weeks"
+    },
+    {
+      "id": 2,
+      "code": "interestChartPeriodType.months",
+      "value": "Months"
+    },
+    {
+      "id": 3,
+      "code": "interestChartPeriodType.years",
+      "value": "Years"
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+		<!-- end of interest rate chart API docs-->
+
+		<!-- start of interest rate slabs API docs -->
+		<a id="interestrateslab" name="interestrateslab" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h3>Interest Rate Slab (A.K.A interest bands)</h3>
+				<p>The slabs a.k.a interest bands are associated with Interest Rate Chart. These bands allow to define different interest rates for different deposit term periods.</p>
+
+				<table class=matrixHeading>
+					<tr class="matrixHeadingBG">
+						<td><div class="fineractHeading2">Field Descriptions</div></td>
+					</tr>
+					<tr class=alt>
+						<td>periodType</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>The period type is used to define the different deposit term intervals. 0=Daily, 1=Weekly, 2=Monthly, 3=Yearly
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>fromPeriod</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>Start of the period for defining deposit term interval.
+							<br> e.g. for 1 day to 180 days applicable interest rate is 5%
+							<br> then startPeriod=1, endPeriod=180 and periodType=0
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>toPeriod</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>End of the period for defining deposit term interval.
+							<br> e.g. for 1 day to 180 days applicable interest rate is 5%
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>annualInterestRate</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>The applicable annual interest rate for defined term interval
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>description</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>
+							provide a description about the slab.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>incentives</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>
+							Represents incentives on interest for a perticular period.
+						</td>
+					</tr>
+				</table>
+			</div>
+		</div>
+
+		<a id="interestrateslab_create" name="interestrateslab_create"
+				class="old-syle-anchor">&nbsp;</a>
+				<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Slab</h4>
+					<p>Creats a new interest rate slab for an interest rate chart.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>periodType, fromPeriod, annualInterestRate</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>toPeriod and description</td>
+						</tr>
+					</table>
+					<p>Example Requests:</p>
+					<div class=apiClick>interestratecharts/1/chartslabs</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/interestratecharts/{chartId}/chartslabs
+					</code>
+					<code class="method-request">
+POST interestratecharts/{chartId}/chartslabs
+Content-Type: application/json
+Request Body:
+{
+  "periodType": "0",
+  "fromPeriod": "1",
+  "toPeriod": "180",
+  "annualInterestRate": "5",
+  "description": "5% interest from 1 day till 180 days of deposit",
+  "locale":"en",
+  "incentives":[{
+		"entityType":"2",
+		"attributeName":2,
+		"conditionType":2,
+		"attributeValue":11,
+		"incentiveType":3,
+		"amount":"-1"
+	}]
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="interestrateslab_list" name="interestrateslab_list"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve all Slabs</h4>
+					<p>Retrieve list of slabs associated with a chart </p>
+
+					<p>Example Requests:</p>
+					<div class=apiClick>interestratecharts/1/chartslabs</div>
+					<br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/interestratecharts/{chartId}/chartslabs
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "description": "5% interest from 1 day till 180 days of deposit",
+    "periodType": {
+      "id": 0,
+      "code": "interestChartPeriodType.days",
+      "value": "Days"
+    },
+    "fromPeriod": 1,
+    "toPeriod": 180,
+    "annualInterestRate": 5,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+	"incentives":[{
+	 "id":1,
+	 "entityType":{"id":2,"code":"InterestIncentiveEntityType.customer","value":"Customer"},
+	 "attributeName":{"id":2,"code":"InterestIncentiveAttributeName.gender","value":"Gender"}, "conditionType":{"id":2,"code":"incentiveConditionType.equal","value":"equal"},
+	 "attributeValue":"11","attributeValueDesc":"FEMALE",
+	 "incentiveType":{"id":3,"code":"InterestIncentiveType.incentive","value":"Incentive"},
+	 "amount":-1.000000
+	 }]
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="interestrateslab_retrieve" name="interestrateslab_retrieve"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Slab</h4>
+					<p>Retrieve a slab associated with an Interest rate chart</p>
+
+					<p>Example Requests:</p>
+					<div class=apiClick>interestratecharts/1/chartslabs/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/interestratecharts/{chartId}/chartslabs/{slabId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "description": "changed to 6% interest ",
+  "periodType": {
+    "id": 0,
+    "code": "interestChartPeriodType.days",
+    "value": "Days"
+  },
+  "fromPeriod": 1,
+  "toPeriod": 180,
+  "annualInterestRate": 6,
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "incentives":[{
+	 "id":1,
+	 "entityType":{"id":2,"code":"InterestIncentiveEntityType.customer","value":"Customer"},
+	 "attributeName":{"id":2,"code":"InterestIncentiveAttributeName.gender","value":"Gender"}, "conditionType":{"id":2,"code":"incentiveConditionType.equal","value":"equal"},
+	 "attributeValue":"11","attributeValueDesc":"FEMALE",
+	 "incentiveType":{"id":3,"code":"InterestIncentiveType.incentive","value":"Incentive"},
+	 "amount":-1.000000
+	 }]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="interestrateslab_update" name="interestrateslab_update"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Slab</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/interestratecharts/{chartId}/chartslabs/{slabId}
+					</code>
+					<code class="method-request">
+PUT interestratecharts/1/chartslabs/1
+Content-Type: application/json
+Request Body:
+{
+  "annualInterestRate": "6",
+  "description": "Interest rate changed to 6%",
+}
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 1,
+  "changes": {
+    "description": "Interest rate changed to 6%",
+    "annualInterestRate": 6
+  }
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="interestrateslab_delete" name="interestrateslab_delete"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Slab</h4>
+					<p>Delete a Slab from a chart</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/interestratecharts/{chartId}/chartslabs/{slabId}
+					</code>
+					<code class="method-request">
+DELETE interestratecharts/1/chartslabs/1
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+		<!-- end of interest rate slabs API docs-->
+
+		<!-- start of Teller Cash Management API docs -->
+			<a id="tellercashmgmt" name="tellercashmgmt" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Teller Cash Management</h3>
+					<p>Teller cash management which will allow an organization to manage their cash transactions at branches or head office more effectively.</p>
+				</div>
+			</div>
+			<a id="listtellers" name="listtellers"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List all tellers</h4>
+					<p>Retrieves list tellers.</p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						GET https://DomainName/api/v1/tellers
+					</code>
+					<code class="method-response">
+						[
+						{
+						"id": 3,
+						"officeId": 1,
+						"debitAccountId": 0,
+						"creditAccountId": 0,
+						"name": "Teller3",
+						"description": "cash handling",
+						"startDate": [
+						2015,
+						2,
+						1
+						],
+						"status": "ACTIVE",
+						"officeName": "Head Office"
+						},
+						{
+						"id": 2,
+						"officeId": 1,
+						"debitAccountId": 0,
+						"creditAccountId": 0,
+						"name": "Teller2",
+						"description": "abcc",
+						"startDate": [
+						2015,
+						2,
+						19
+						],
+						"endDate": [
+						2015,
+						3,
+						30
+						],
+						"status": "ACTIVE",
+						"officeName": "Head Office"
+						},
+						{
+						"id": 1,
+						"officeId": 1,
+						"debitAccountId": 0,
+						"creditAccountId": 0,
+						"name": "Teller1",
+						"description": "abc",
+						"startDate": [
+						2015,
+						2,
+						20
+						],
+						"status": "ACTIVE",
+						"officeName": "Head Office"
+						},
+						{
+						"id": 4,
+						"officeId": 2,
+						"debitAccountId": 0,
+						"creditAccountId": 0,
+						"name": "Teller4",
+						"description": "cash handling",
+						"startDate": [
+						2015,
+						2,
+						1
+						],
+						"status": "ACTIVE",
+						"officeName": "Office1"
+						}
+						]
+					</code>
+				</div>
+			</div>
+
+			<a id="createtellers" name="createtellers"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create teller</h4>
+					<p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>Teller name, OfficeId, Description, Start Date, Status</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>End Date</td>
+						</tr>
+					</table>
+					</p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						POST https://DomainName/api/v1/tellers
+					</code>
+					<code class="method-request">
+						POST tellers
+						Content-Type: application/json
+						Request Body:
+						{
+						"officeId":2,
+						"name":"Teller4",
+						"description":"cash handling",
+						"status":300,
+						"locale":"en",
+						"dateFormat":"dd MMMM yyyy",
+						"startDate":"01 February 2015"
+						}
+					</code>
+					<code class="method-response">
+						{
+						"officeId": 2,
+						"resourceId": 5
+						}
+					</code>
+				</div>
+			</div>
+
+			<a id="findtellers" name="findtellers"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve tellers</h4>
+					<p></p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						GET https://DomainName/api/v1/tellers/5
+					</code>
+					<code class="method-response">
+						{
+						"id": 5,
+						"officeId": 2,
+						"debitAccountId": 0,
+						"creditAccountId": 0,
+						"name": "Teller5",
+						"description": "cash handling",
+						"startDate": [
+						2015,
+						2,
+						1
+						],
+						"status": "ACTIVE",
+						"officeName": "Office1"
+						}
+					</code>
+				</div>
+			</div>
+
+			<a id="updatetellers" name="updatetellers"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update teller</h4>
+					<p></p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						PUT https://DomainName/api/v1/tellers/{tellerId}
+					</code>
+					<code class="method-request">
+						PUT tellers/5
+						Content-Type: application/json
+						Request Body:
+						{
+						"name":"Teller5",
+						"officeId":2,
+						"description":"teller cash handling",
+						"status":300,
+						"endDate":"28 February 2015",
+						"startDate":"01 February 2015",
+						"locale":"en",
+						"dateFormat":"dd MMMM yyyy"
+						}
+					</code>
+					<code class="method-response">
+						{
+						"officeId":2,
+						"resourceId":5,
+						"changes":{
+						"description":"teller cash handling",
+						"endDate":"28 February 2015",
+						"dateFormat":"dd MMMM yyyy",
+						"locale":"en"
+						}
+						}
+					</code>
+				</div>
+			</div>
+
+			<!--<a id="deletetellers" name="deletetellers"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete Teller</h4>
+					<p></p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/interestratecharts/{chartId}/chartslabs/{slabId}
+					</code>
+					<code class="method-request">
+DELETE interestratecharts/1/chartslabs/1
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+						{
+						"resourceId": 1
+						}
+					</code>
+				</div>
+			</div>
+-->
+			<a id="findallcashiers" name="findallcashiers"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Cashiers</h4>
+					<p></p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						GET https://DomainName/api/v1/tellers/{tellerId}/cashiers
+					</code>
+					<code class="method-response">
+						{
+						"tellerId": 1,
+						"tellerName": "Teller1",
+						"officeId": 1,
+						"officeName": "Head Office",
+						"cashiers": [
+						{
+						"id": 1,
+						"tellerId": 1,
+						"staffId": 1,
+						"description": "",
+						"startDate": "Feb 20, 2015 12:00:00 AM",
+						"endDate": "Feb 27, 2015 12:00:00 AM",
+						"isFullDay": true,
+						"startTime": "",
+						"endTime": "",
+						"tellerName": "Teller1",
+						"staffName": "Staff1, Test"
+						}
+						]
+						}
+					</code>
+				</div>
+			</div>
+
+			<a id="createcashier" name="createcashier"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create Cashiers</h4>
+					<p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>Cashier/staff, Fromm Date, To Date, Full Day or From time and To time</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>Description/Notes</td>
+						</tr>
+					</table>
+					</p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						POST https://DomainName/api/v1/tellers/{tellerId}/cashiers
+					</code>
+					<code class="method-request">
+						POST tellers/1/cashiers
+						{
+						"endDate":"28 February 2015",
+						"description":"Cashier created",
+						"isFullDay":true,
+						"staffId":3,
+						"locale":"en",
+						"dateFormat":"dd MMMM yyyy",
+						"startDate":"01 February 2015"
+						}
+					</code>
+					<code class="method-response">
+						{
+						"resourceId":1,
+						"subResourceId":2
+						}
+					</code>
+				</div>
+			</div>
+
+			<a id="findaonecashier" name="findonecashier"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a cashier</h4>
+					<p></p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						GET https://DomainName/api/v1/tellers/{tellerId}/cashiers/{cashierId}
+					</code>
+					<code class="method-response">
+						{
+						"id": 1,
+						"tellerId": 1,
+						"staffId": 1,
+						"description": "",
+						"startDate": "Feb 20, 2015 12:00:00 AM",
+						"endDate": "Feb 27, 2015 12:00:00 AM",
+						"isFullDay": true,
+						"startTime": "",
+						"endTime": "",
+						"tellerName": "Teller1",
+						"staffName": "Staff1, Test"
+						}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="updatecashier" name="updatecashier"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update Cashier</h4>
+					<p></p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						PUT https://DomainName/api/v1/tellers/{tellerId}/cashiers/{cashierId}
+					</code>
+
+					</code>
+					<code class="method-request">
+						PUT tellers/1/cashiers/1
+						{
+						"endDate":"25 February 2015",
+						"description":"Cashier updated.",
+						"isFullDay":true,
+						"staffId":4,
+						"locale":"en",
+						"dateFormat":"dd MMMM yyyy",
+						"startDate":"01 February 2015"
+						}
+					</code>
+					<code class="method-response">
+						{
+						"resourceId": 1,
+						"subResourceId": 1,
+						"changes": {
+						"description": "Cashier updated.",
+						"endDate": "25 February 2015",
+						"dateFormat": "dd MMMM yyyy",
+						"locale": "en"
+						}
+						}
+					</code>
+				</div>
+			</div>
+
+			<a id="deletecashier" name="deletecashier"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete Cashier</h4>
+					<p></p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						DELETE tellers/{tellerId}/cashiers/{cashierId}
+					</code>
+					<code class="method-request">
+						DELETE tellers/1/cashiers/3
+						Content-Type: application/json
+						Request Body:
+						{
+						}
+					</code>
+					<code class="method-response">
+						{
+						"resourceId": 3
+						}
+					</code>
+				</div>
+			</div>
+
+			<a id="cashiertemplate" name="cashiertemplate"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Find Cashiers</h4>
+					<p></p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						GET https://DomainName/api/v1/tellers/{tellerId}/cashiers/template
+					</code>
+					<code class="method-response">
+						{
+						"tellerId":1,
+						"officeId":1,
+						"officeName":"Head Office",
+						"tellerName":"Teller1",
+						"staffOptions":[
+						{
+						"id":1,
+						"displayName":"Staff1, Test"
+						},
+						{
+						"id":2,
+						"displayName":"Staff, 2"
+						},
+						{
+						"id":3,
+						"displayName":"Staff, 3"
+						}
+						]
+						}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="transactionsForCashier" name="transactionsForCashier"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Cashier Transaction</h4>
+					<p></p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						GET https://DomainName/api/v1/tellers/{tellerId}/cashiers/{cashierId}/transactions
+					</code>
+					<code class="method-response">
+						[
+						{
+						"id": 8,
+						"cashierId": 15,
+						"txnType": {
+						"id": 104,
+						"value": "Cash Out"
+						},
+						"txnAmount": 10000,
+						"txnDate": "Feb 25, 2015 12:00:00 AM",
+						"entityId": 2,
+						"entityType": "loans",
+						"txnNote": "Disbursement, Loan:2-000000002,Client:1-Test 1",
+						"createdDate": "Feb 25, 2015 12:00:00 AM",
+						"officeId": 1,
+						"officeName": "Head Office",
+						"tellerId": 0,
+						"cashierName": "B, Ramesh"
+						},
+						{
+						"id": 10,
+						"cashierId": 15,
+						"txnType": {
+						"id": 104,
+						"value": "Cash Out"
+						},
+						"txnAmount": 8500,
+						"txnDate": "Feb 25, 2015 12:00:00 AM",
+						"entityId": 3,
+						"entityType": "loans",
+						"txnNote": "Disbursement, Loan:3-000000003,Client:3-Client 1",
+						"createdDate": "Feb 25, 2015 12:00:00 AM",
+						"officeId": 1,
+						"officeName": "Head Office",
+						"tellerId": 0,
+						"cashierName": "B, Ramesh"
+						},
+						{
+						"id": 12,
+						"cashierId": 15,
+						"txnType": {
+						"id": 104,
+						"value": "Cash Out"
+						},
+						"txnAmount": 10000,
+						"txnDate": "Feb 1, 2015 12:00:00 AM",
+						"entityId": 4,
+						"entityType": "loans",
+						"txnNote": "Disbursement, Loan:4-000000004,Client:4-Client 2",
+						"createdDate": "Feb 25, 2015 12:00:00 AM",
+						"officeId": 1,
+						"officeName": "Head Office",
+						"tellerId": 0,
+						"cashierName": "B, Ramesh"
+						},
+						{
+						"id": 14,
+						"cashierId": 15,
+						"txnType": {
+						"id": 103,
+						"value": "Cash In"
+						},
+						"txnAmount": 1266.52,
+						"txnDate": "Feb 1, 2015 12:00:00 AM",
+						"entityId": 4,
+						"entityType": "loans",
+						"txnNote": "Repayment, Loan:4-000000004,Client:4-Client 2",
+						"createdDate": "Feb 25, 2015 12:00:00 AM",
+						"officeId": 1,
+						"officeName": "Head Office",
+						"tellerId": 0,
+						"cashierName": "B, Ramesh"
+						},
+						{
+						"id": 13,
+						"cashierId": 15,
+						"txnType": {
+						"id": 101,
+						"value": "Allocate Cash"
+						},
+						"txnAmount": 50000,
+						"txnDate": "Feb 1, 2015 12:00:00 AM",
+						"entityId": 0,
+						"entityType": "",
+						"txnNote": "cash allocated on 1st Feb 2015",
+						"createdDate": "Feb 25, 2015 12:00:00 AM",
+						"officeId": 1,
+						"officeName": "Head Office",
+						"tellerId": 10,
+						"tellerName": "Ramesh(Teller/Cashier)",
+						"cashierName": "B, Ramesh"
+						},
+						{
+						"id": 15,
+						"cashierId": 15,
+						"txnType": {
+						"id": 104,
+						"value": "Cash Out"
+						},
+						"txnAmount": 10000,
+						"txnDate": "Feb 1, 2015 12:00:00 AM",
+						"entityId": 5,
+						"entityType": "loans",
+						"txnNote": "Disbursement, Loan:5-000000005,Client:5-Suresh D",
+						"createdDate": "Feb 25, 2015 12:00:00 AM",
+						"officeId": 1,
+						"officeName": "Head Office",
+						"tellerId": 0,
+						"cashierName": "B, Ramesh"
+						},
+						{
+						"id": 17,
+						"cashierId": 15,
+						"txnType": {
+						"id": 103,
+						"value": "Cash In"
+						},
+						"txnAmount": 1266.52,
+						"txnDate": "Feb 1, 2015 12:00:00 AM",
+						"entityId": 5,
+						"entityType": "loans",
+						"txnNote": "Repayment, Loan:5-000000005,Client:5-Suresh D",
+						"createdDate": "Feb 25, 2015 12:00:00 AM",
+						"officeId": 1,
+						"officeName": "Head Office",
+						"tellerId": 0,
+						"cashierName": "B, Ramesh"
+						},
+						{
+						"id": 14,
+						"cashierId": 15,
+						"txnType": {
+						"id": 102,
+						"value": "Settle Cash"
+						},
+						"txnAmount": 10000,
+						"txnDate": "Feb 1, 2015 12:00:00 AM",
+						"entityId": 0,
+						"entityType": "",
+						"txnNote": "cash settlement on 1 feb  with 10k",
+						"createdDate": "Feb 25, 2015 12:00:00 AM",
+						"officeId": 1,
+						"officeName": "Head Office",
+						"tellerId": 10,
+						"tellerName": "Ramesh(Teller/Cashier)",
+						"cashierName": "B, Ramesh"
+						}
+						]
+					</code>
+				</div>
+			</div>
+
+			<!--
+                        <a id="cashiertemplate" name="cashiertemplate"
+                           class="old-syle-anchor">&nbsp;</a>
+                        <div class="method-section">
+                            <div class="method-description">
+                                <h4>Find Cashiers</h4>
+                                <p></p><br>
+                            </div>
+                            <div class="method-example">
+                                <code class="method-declaration">
+            GET https://DomainName/api/v1/tellers/{tellerId}/cashiers/template
+                                </code>
+                                <code class="method-response">
+            {
+            "tellerId":1,
+            "officeId":1,
+            "officeName":"Head Office",
+            "tellerName":"Teller1",
+            "staffOptions":[
+            {
+            "id":1,
+            "displayName":"Staff1, Test"
+            },
+            {
+            "id":2,
+            "displayName":"Staff, 2"
+            },
+            {
+            "id":3,
+            "displayName":"Staff, 3"
+            }
+            ]
+            }
+                                </code>
+                            </div>
+                        </div>-->
+
+			<a id="allocateCashToCashier" name="allocateCashToCashier"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Allocate Cash To Cashier</h4>
+					<p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>Date, Amount, Currency, Notes/Comments</td>
+						</tr>
+					</table>
+					</p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						POST https://DomainName/api/v1/tellers/{tellerId}/cashiers/{cashierId}/allocate
+					</code>
+					<code class="method-request">
+						POST tellers/1/cashiers/1/allocate?command=allocate
+						{
+						"currencyCode":"USD",
+						"txnAmount":"5000",
+						"txnNote":"allocating cash",
+						"locale":"en",
+						"dateFormat":"dd MMMM yyyy",
+						"txnDate":"01 February 2015"
+						}
+					</code>
+					<code class="method-response">
+						{
+						"resourceId":1,
+						"subResourceId":4
+						}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="settleCashFromCashier" name="settleCashFromCashier"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Settle Cash From Cashier</h4>
+					<p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>Date, Amount, Currency, Notes/Comments</td>
+						</tr>
+					</table>
+					</p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						POST https://DomainName/api/v1/tellers/{tellerId}/cashiers/{cashierId}/settle
+					</code>
+					<code class="method-request">
+						POST tellers/1/cashiers/1/settle?command=settle
+						{
+						"currencyCode":"USD",
+						"txnAmount":"2000",
+						"txnNote":"cash settlement",
+						"locale":"en",
+						"dateFormat":"dd MMMM yyyy",
+						"txnDate":"20 February 2015"
+						}
+					</code>
+					<code class="method-response">
+						{
+						"resourceId":1,
+						"subResourceId":5
+						}
+					</code>
+				</div>
+			</div>
+
+			<a id="getTransactionsWtihSummaryForCashier" name="getTransactionsWtihSummaryForCashier"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Transactions Wtih Summary For Cashier</h4>
+					<p></p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						GET https://DomainName/api/v1/tellers/{tellerId}/cashiers/{cashierId}/summaryandtransactions
+					</code>
+					<code class="method-response">
+						{
+						"sumCashAllocation":7000.000000,
+						"sumInwardCash":0,
+						"sumOutwardCash":0,
+						"sumCashSettlement":50.000000,
+						"netCash":6950.000000,
+						"officeName":"Head Office",
+						"tellerId":1,
+						"tellerName":"Teller1",
+						"cashierId":1,
+						"cashierName":"Staff1, Test",
+						"cashierTransactions":[
+						{
+						"id":2,
+						"cashierId":1,
+						"txnType":{
+						"id":101,
+						"value":"Allocate Cash"
+						},
+						"txnAmount":2000.000000,
+						"txnDate":"Feb 20, 2015 12:00:00 AM",
+						"entityId":0,
+						"entityType":"",
+						"txnNote":"aaaa",
+						"createdDate":"Feb 20, 2015 12:00:00 AM",
+						"officeId":1,
+						"officeName":"Head Office",
+						"tellerId":1,
+						"tellerName":"Teller1",
+						"cashierName":"Staff1, Test"
+						},
+						{
+						"id":3,
+						"cashierId":1,
+						"txnType":{
+						"id":102,
+						"value":"Settle Cash"
+						},
+						"txnAmount":50.000000,
+						"txnDate":"Feb 20, 2015 12:00:00 AM",
+						"entityId":0,
+						"entityType":"",
+						"txnNote":"bbbbbb",
+						"createdDate":"Feb 20, 2015 12:00:00 AM",
+						"officeId":1,
+						"officeName":"Head Office",
+						"tellerId":1,
+						"tellerName":"Teller1",
+						"cashierName":"Staff1, Test"
+						},
+						{
+						"id":4,
+						"cashierId":1,
+						"txnType":{
+						"id":101,
+						"value":"Allocate Cash"
+						},
+						"txnAmount":5000.000000,
+						"txnDate":"Feb 1, 2015 12:00:00 AM",
+						"entityId":0,
+						"entityType":"",
+						"txnNote":"allocating cash",
+						"createdDate":"Feb 23, 2015 12:00:00 AM",
+						"officeId":1,
+						"officeName":"Head Office",
+						"tellerId":1,
+						"tellerName":"Teller1",
+						"cashierName":"Staff1, Test"
+						}
+						]
+						}
+					</code>
+				</div>
+			</div>
+
+
+			<a id="retrieveCashierTxnTemplate" name="retrieveCashierTxnTemplate"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Cashier Transaction Template</h4>
+					<p></p><br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+						GET https://DomainName/api/v1/tellers/{tellerId}/cashiers/{cashierId}/transactions/template
+					</code>
+					<code class="method-response">
+						{
+						"cashierId":1,
+						"officeName":"Head Office",
+						"tellerId":1,
+						"tellerName":"Teller1",
+						"cashierName":"Staff1, Test",
+						"cashierData":{
+						"id":1,
+						"tellerId":1,
+						"staffId":1,
+						"description":"",
+						"startDate":"Feb 20, 2015 12:00:00 AM",
+						"endDate":"Feb 27, 2015 12:00:00 AM",
+						"isFullDay":true,
+						"startTime":"",
+						"endTime":"",
+						"tellerName":"Teller1",
+						"staffName":"Staff1, Test"
+						},
+						"startDate":"Feb 20, 2015 12:00:00 AM",
+						"endDate":"Feb 27, 2015 12:00:00 AM",
+						"currencyOptions":[
+						{
+						"code":"USD",
+						"name":"US Dollar",
+						"decimalPlaces":2,
+						"displaySymbol":"$",
+						"nameCode":"currency.USD",
+						"displayLabel":"US Dollar ($)"
+						}
+						]
+						}
+					</code>
+				</div>
+			</div>
+
+
+
+
+			<!-- end of Teller Cash Management API docs -->
+			<!--Start of Payment type API docs-->
+			<a id="paymenttype" name="paymenttype" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Payment Type</h3>
+					<p>This defines the payment type
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name the payment type
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>description</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Description of payment type
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>isCashPayment</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Determines weather the payment type is cash or not.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>position</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Can set the order in which the payment type should be displayed.
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+			<a id="create_paymenttype" name="create_paymenttype"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a Payment Type</h4>
+					<p>Creates a new Payment type</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>Description, isCashPayment,Position</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					POST https://DomainName/api/v1/paymenttypes
+					</code>
+					<code class="method-request">
+					POST paymenttype
+					Content-Type: application/json
+					Request Body:
+					{
+					"name":"cash",
+					"description":"cash payment type",
+					"isCashPayment":true,
+					"position":1
+					}
+					</code>
+					<code class="method-response">
+					{
+					"resourceId": 1
+					}
+					</code>
+				</div>
+			</div>
+			<a id="paymenttype_list" name="paymenttype_list"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve all Payment Types</h4>
+					<p>Retrieve list of payment types </p>
+
+
+					<br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					GET https://DomainName/api/v1/paymenttypes
+					</code>
+					<code class="method-response">
+					[
+					{
+					"id": 24,
+					"name": "PTC",
+					"description": "Cash",
+					"isCashPayment": true,
+					"position": 0
+					},
+					{
+					"id": 25,
+					"name": "mPay",
+					"description": "not chash payment",
+					"isCashPayment": false,
+					"position": 0
+					},
+					{
+					"id": 26,
+					"name": "mPesa",
+					"description": "non cash payment",
+					"isCashPayment": false,
+					"position": 0
+					},
+					{
+					"id": 27,
+					"name": "Mobile Money",
+					"description": "money transferred through mobile",
+					"isCashPayment": false,
+					"position": 0
+					},
+					{
+					"id": 13,
+					"name": "cash",
+					"description": "cash Payment",
+					"isCashPayment": true,
+					"position": 1
+					}
+					]
+					</code>
+				</div>
+			</div>
+			<a id="paymenttype_retrieve" name="paymenttype_retrieve"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Payment Type</h4>
+					<p>Retrieves a payment type </p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					GET https://DomainName/api/v1/paymenttypes/{paymentTypeId}
+					</code>
+					<code class="method-response">
+					{
+					"id": 13,
+					"name": "cash",
+					"description": "cash Payment",
+					"isCashPayment": true,
+					"position": 1
+					}
+					</code>
+				</div>
+			</div>
+			<a id="paymenttype_update" name="paymenttype_update"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a Payment Type</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					PUT https://DomainName/api/v1/paymenttypes/{paymnetTypeId}
+					</code>
+					<code class="method-request">
+					PUT /paymenttypes/13
+					{
+					"name":"mPay",
+					"description":"not a cash payment type",
+					"isCashPayment":false,
+					"position":3
+					}
+					</code>
+					<code class="method-response">
+					{
+					"resourceId": 13
+					}
+					</code>
+				</div>
+			</div>
+			<a id="paymnettype_delete" name="paymnettype_delete"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Payment Type</h4>
+					<p>Deletes payment type</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					DELETE https://DomainName/api/v1/paymenttypes/{paymentTypeId}
+					</code>
+					<code class="method-request">
+					DELETE paymnettypes/13
+					Content-Type: application/json
+					No Request Body:
+					</code>
+					<code class="method-response">
+					{
+					"resourceId": 13
+					}
+					</code>
+				</div>
+			</div>
+			<!-- Provisioning -->
+			<a id="provisioningcriteria" name="provisioningcriteria" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Provisioning Criteria</h3>
+					<p>This defines the Provisioning Criteria
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>Provisioning Criteria Name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name the Provisioning Criteria
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>Loan Products</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Select all loan products for which provisioning criteria to be associated
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>Provisioning Categories</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Define minimum, maximum, percentage, liability account, expense account for all provisioning categories
+							</td>
+						</tr>
+
+					</table>
+				</div>
+			</div>
+
+			<a id="create_provisioningcriteria" name="create_provisioningcriteria"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a new Provisioning Criteria</h4>
+					<p>Creates a new Provisioning Criteria</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>criteriaName</td>
+						</tr>
+						<tr class=alt>
+							<td>provisioningcriteria</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>loanProducts</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					POST https://DomainName/api/v1/provisioningcriteria
+					</code>
+					<code class="method-request">
+					POST provisioningcriteria
+					Content-Type: application/json
+					Request Body:
+					{
+					"criteriaName":"High Risk Products Criteria",
+					"loanProducts": [
+						{"id": 1,
+						"name": "LOAN_PRODUCT_3ODPK1",
+						"includeInBorrowerCycle": false},
+						{"id": 2,
+						"name": "LOAN_PRODUCT_BXW8NC",
+						includeInBorrowerCycle": false}
+						],
+					"provisioningcriteria": [
+						{"categoryId": 1,
+						"categoryName": "STANDARD",
+						"minAge": 0,
+						"maxAge": 3,
+						"provisioningPercentage": 1,
+						"liabilityAccount": 8,
+						"expenseAccount": 14},
+						{"categoryId": 2,
+						"categoryName": "SUB-STANDARD",
+						"minAge": 1,
+						"maxAge": 5,
+						"provisioningPercentage": 2,
+						"liabilityAccount": 13,
+						"expenseAccount": 13},
+						{"categoryId": 3,
+						"categoryName": "DOUBTFUL",
+						"minAge": 2,
+						"maxAge": 6,
+						"provisioningPercentage": 3,
+						"liabilityAccount": 9,
+						"expenseAccount": 10},
+						{"categoryId": 4,
+						"categoryName": "LOSS",
+						"minAge": 3,
+						"maxAge": 7,
+						"provisioningPercentage": 4,
+						"liabilityAccount": 10,
+						"expenseAccount": 9}]
+					}
+					</code>
+					<code class="method-response">
+					{
+					"resourceId": 1
+					}
+					</code>
+				</div>
+			</div>
+			<a id="provisioningcriteria_list" name="provisioningcriteria_list"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieves all created Provisioning Criterias</h4>
+					<p>Retrieves all created Provisioning Criterias</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					GET https://DomainName/api/v1/provisioningcriteria
+					</code>
+					<code class="method-request">
+					GET provisioningcriteria
+					Content-Type: application/json
+					Request Body:
+					{
+					}
+					</code>
+					<code class="method-response">
+					{
+					[
+					{"criteriaId":1,
+					 "criteriaName":"High Risk Products Criteria",
+					 "createdBy":"mifos"},
+					 {"criteriaId":2,
+					 "criteriaName":"Low Risk Products Criteria",
+					 "createdBy":"mifos"}
+					]
+					}
+					</code>
+				</div>
+			</div>
+			<a id="retrieve_provisioningcriteria" name="retrieve_provisioningcriteria"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieves a Provisioning Criteria</h4>
+					<p>Retrieves a Provisioning Criteria</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					GET https://DomainName/api/v1/provisioningcriteria/{criteriaId}
+					</code>
+					<code class="method-request">
+					GET provisioningcriteria/{criteriaId}
+					Content-Type: application/json
+					Request Body:
+					{
+					}
+					</code>
+					<code class="method-response">
+					{
+					"criteriaId":1
+					"criteriaName":"High Risk Products Criteria",
+					"loanProducts": [
+						{"id": 1,
+						"name": "LOAN_PRODUCT_3ODPK1",
+						"includeInBorrowerCycle": false},
+						{"id": 2,
+						"name": "LOAN_PRODUCT_BXW8NC",
+						includeInBorrowerCycle": false}
+						],
+					"provisioningcriteria": [
+						{"categoryId": 1,
+						"categoryName": "STANDARD",
+						"minAge": 0,
+						"maxAge": 3,
+						"provisioningPercentage": 1,
+						"liabilityAccount": 8,
+						"expenseAccount": 14},
+						{"categoryId": 2,
+						"categoryName": "SUB-STANDARD",
+						"minAge": 1,
+						"maxAge": 5,
+						"provisioningPercentage": 2,
+						"liabilityAccount": 13,
+						"expenseAccount": 13},
+						{"categoryId": 3,
+						"categoryName": "DOUBTFUL",
+						"minAge": 2,
+						"maxAge": 6,
+						"provisioningPercentage": 3,
+						"liabilityAccount": 9,
+						"expenseAccount": 10},
+						{"categoryId": 4,
+						"categoryName": "LOSS",
+						"minAge": 3,
+						"maxAge": 7,
+						"provisioningPercentage": 4,
+						"liabilityAccount": 10,
+						"expenseAccount": 9}]
+					}
+					</code>
+				</div>
+			</div>
+			<a id="update_provisioningcriteria" name="update_provisioningcriteria"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Updates a new Provisioning Criteria</h4>
+					<p>Updates a new Provisioning Criteria</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>criteriaName, loanProducts, provisioningcriteria</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					PUT https://DomainName/api/v1/provisioningcriteria/{criteriaId}
+					</code>
+					<code class="method-request">
+					PUT provisioningcriteria/{criteriaId}
+					Content-Type: application/json
+					Request Body:
+					{
+					"criteriaName":"High Risk Products Criteria1",
+					"loanProducts": [
+						{"id": 1,
+						"name": "LOAN_PRODUCT_3ODPK1",
+						"includeInBorrowerCycle": false},
+						{"id": 2,
+						"name": "LOAN_PRODUCT_BXW8NC",
+						includeInBorrowerCycle": false}
+						],
+					"provisioningcriteria": [
+						{"categoryId": 1,
+						"categoryName": "STANDARD",
+						"minAge": 0,
+						"maxAge": 3,
+						"provisioningPercentage": 1,
+						"liabilityAccount": 8,
+						"expenseAccount": 14},
+						{"categoryId": 2,
+						"categoryName": "SUB-STANDARD",
+						"minAge": 1,
+						"maxAge": 5,
+						"provisioningPercentage": 2,
+						"liabilityAccount": 13,
+						"expenseAccount": 13},
+						{"categoryId": 3,
+						"categoryName": "DOUBTFUL",
+						"minAge": 2,
+						"maxAge": 6,
+						"provisioningPercentage": 3,
+						"liabilityAccount": 9,
+						"expenseAccount": 10},
+						{"categoryId": 4,
+						"categoryName": "LOSS",
+						"minAge": 3,
+						"maxAge": 7,
+						"provisioningPercentage": 4,
+						"liabilityAccount": 10,
+						"expenseAccount": 9}]
+					}
+					</code>
+					<code class="method-response">
+					{
+					"resourceId": 1,
+					"changes": {
+					"criteriaName": "High Risk Products Criteria1"
+					}
+					}
+					</code>
+				</div>
+			</div>
+			<a id="delete_provisioningcriteria" name="delete_provisioningcriteria"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Deletes Provisioning Criteria</h4>
+					<p>Deletes Provisioning Criteria</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+				DELETE https://DomainName/api/v1/provisioningcriteria/{criteriaId}
+					</code>
+					<code class="method-request">
+					DELETE provisioningcriteria/{criteriaId}
+					Content-Type: application/json
+					Request Body:
+					{
+					}
+					</code>
+					<code class="method-response">
+					{
+					"resourceId": 1,
+					}
+					</code>
+				</div>
+			</div>
+		<!--End of Payment Type API docs-->
+			<!-- floatingrates -->
+			<a id="floatingrates" name="floatingrates" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Floating Rates</h3>
+					<p>This defines the Floating Rates</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Name of the Floating Rate, must be unique
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>isBaseLendingRate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Identifies the Floating Rate scheme to be Base Lending Rate. Only one scheme can be Base Lending Rate at any given point of time. default is false.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>isActive</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Identify if Floating Rate scheme is active or not. default is true.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>ratePeriods</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Array of ratePeriod JSON objects as defined in below section.
+							</td>
+						</tr>
+					</table>
+					<p>This defines the Floating Rates Periods</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions for ratePeriods</div></td>
+						</tr>
+						<tr class=alt>
+							<td>fromDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Start date from which this rate has to be considered. Should be a future date.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>interestRate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								Interest Rate applicable.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>isDifferentialToBaseLendingRate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>
+								If false, interestRate field is considered absolute.
+								If true, interestRate field is considered differential to Base Lending Rate as of the startDate.
+								Cannot be used if there is no scheme defined as Base Lending Rate.
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<a id="create_floatingrates" name="create_floatingrates"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Create a new Floating Rate</h4>
+					<p>Creates a new Floating Rate</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>isBaseLendingRate</td>
+						</tr>
+						<tr class=alt>
+							<td>isActive</td>
+						</tr>
+						<tr class=alt>
+							<td>ratePeriods</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					POST https://DomainName/api/v1/floatingrates
+					</code>
+					<code class="method-request">
+					POST floatingrates
+					Content-Type: application/json
+					Request Body:
+					{
+						"name":"Floating Rate 1",
+						"isBaseLendingRate":true,
+						"isActive":true,
+						"ratePeriods":[
+							{
+								"fromDate":"19 November 2015",
+								"interestRate":10,
+								"locale":"en",
+								"dateFormat":"dd MMMM yyyy"
+							},
+							{
+								"fromDate":"15 December 2015",
+								"interestRate":11,
+								"locale":"en",
+								"dateFormat":"dd MMMM yyyy"
+							}
+						]
+					}
+					</code>
+					<code class="method-response">
+					{
+						"resourceId": 1
+					}
+					</code>
+				</div>
+			</div>
+			<a id="floatingrates_list" name="floatingrates_list"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Floating Rates</h4>
+					<p>List Floating Rates</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					GET https://DomainName/api/v1/floatingrates
+					</code>
+					<code class="method-request">
+					GET floatingrates
+					Content-Type: application/json
+					</code>
+					<code class="method-response">
+					{
+						[
+							{
+								"id": 1,
+								"name": "Floating Rate 1",
+								"isBaseLendingRate": true,
+								"isActive": true,
+								"createdBy": "mifos",
+								"createdOn": "Nov 18, 2015",
+								"modifiedBy": "mifos",
+								"modifiedOn": "Nov 18, 2015"
+							},
+							{
+								"id": 2,
+								"name": "Floating Rate 2",
+								"isBaseLendingRate": false,
+								"isActive": true,
+								"createdBy": "mifos",
+								"createdOn": "Nov 18, 2015",
+								"modifiedBy": "mifos",
+								"modifiedOn": "Nov 18, 2015"
+							}
+						]
+					}
+					</code>
+				</div>
+			</div>
+			<a id="retrieve_floatingrate" name="retrieve_floatingrate"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Floating Rate</h4>
+					<p>Retrieve Floating Rate</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					GET https://DomainName/api/v1/floatingrates/1
+					</code>
+					<code class="method-request">
+					GET floatingrates
+					Content-Type: application/json
+					</code>
+					<code class="method-response">
+					{
+						"id": 1,
+						"name": "Floating Rate 1",
+						"isBaseLendingRate": true,
+						"isActive": true,
+						"createdBy": "mifos",
+						"createdOn": "Nov 18, 2015",
+						"modifiedBy": "mifos",
+						"modifiedOn": "Nov 18, 2015",
+						"ratePeriods":
+						[
+							{
+								"id": 1,
+								"fromDate": "Dec 15, 2015",
+								"interestRate": 11,
+								"isDifferentialToBaseLendingRate": false,
+								"isActive": true,
+								"createdBy": "mifos",
+								"createdOn": "Nov 18, 2015",
+								"modifiedBy": "mifos",
+								"modifiedOn": "Nov 18, 2015"
+							},
+							{
+								"id": 2,
+								"fromDate": "Nov 19, 2015",
+								"interestRate": 10,
+								"isDifferentialToBaseLendingRate": false,
+								"isActive": true,
+								"createdBy": "mifos",
+								"createdOn": "Nov 18, 2015",
+								"modifiedBy": "mifos",
+								"modifiedOn": "Nov 18, 2015"
+							}
+						]
+					}
+					</code>
+				</div>
+			</div>
+						<a id="update_floatingrate" name="update_floatingrate"
+			   class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update Floating Rate</h4>
+					<p>Updates new Floating Rate. Rate Periods in the past cannot be modified.
+					All the future rateperiods would be replaced with the new ratePeriods data sent.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+					PUT https://DomainName/api/v1/floatingrates/1
+					</code>
+					<code class="method-request">
+					PUT floatingrates
+					Content-Type: application/json
+					Request Body:
+					{
+						"name":"Floating Rate 1",
+						"isBaseLendingRate":true,
+						"isActive":true,
+						"ratePeriods":[
+							{
+								"fromDate":"19 November 2015",
+								"interestRate":10,
+								"locale":"en",
+								"dateFormat":"dd MMMM yyyy"
+							},
+							{
+								"fromDate":"15 December 2015",
+								"interestRate":11,
+								"locale":"en",
+								"dateFormat":"dd MMMM yyyy"
+							}
+						]
+					}
+					</code>
+					<code class="method-response">
+					{
+						"resourceId": 1,
+						"changes":
+						{
+							"ratePeriods": "[
+								{
+									"fromDate":"19 November 2015",
+									"interestRate":10,
+									"locale":"en",
+									"dateFormat":"dd MMMM yyyy"
+								},
+								{
+									"fromDate":"15 December 2015",
+									"interestRate":11,
+									"locale":"en",
+									"dateFormat":"dd MMMM yyyy"
+								}
+							]"
+						}
+					}
+					</code>
+				</div>
+			</div>
+			<!-- End of floatingrates -->
+
+		<!-- start of clients api docs -->
+		<a id="templates" name="clients" class="old-syle-anchor">&nbsp;</a>
+		<div class="method-section">
+			<div class="method-description">
+				<h3>User Generated Documents</h3>
+				<p>User Generated Documents(alternatively, Templates) are used for end-user features such as custom user defined document generation (AKA UGD).  They are based on <a href="http://mustache.github.io/">{{ moustache }} templates</a>. Think of them as a sort of built-in "mail merge" functionality.</p>
+
+				<p>User Generated Documents (and other types of templates) can aggregate data from several Apache Fineract back-end API calls via <b>mappers</b>.  Mappers can even access non-Apache Fineract REST services from other servers.  UGDs can render such data in tables, show images, etc. <i>TBD: Please have a look at some of the Example UGDs included in Apache Fineract (or <a href="https://mifosforge.jira.com/wiki/display/RES/UGD_FinalDoc">the Wiki page</a>, for now.).</i></p>
+
+				<p>UGDs can be assigned to an entity like <b>client</b> or <b>loan</b> and be of a type like <b>Document</b> or <b>SMS</b>.  The entity and type of a UGD is only there for the convenience of user agents (UIs), in order to know where to show UGDs for the user (i.e. which tab).  The Template Engine back-end runner does not actually need this metadata.</p>
+				<table class=matrixHeading>
+					<tr class="matrixHeadingBG">
+						<td><div class="fineractHeading2">Field Descriptions</div></td>
+					</tr>
+					<tr class=alt>
+						<td>name</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>Describes the document which will be created.
+							It must be unique and appears listed in a tab at the assigned entity. </td>
+					</tr>
+					<tr class=alt>
+						<td>type</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>
+							For now only the types Document and SMS are supported.
+							In a next version, UGDs may be created for e-mails as well.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>entity</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>
+							Indicates the primary resource reference.
+							UGDs may be filtered by entity and type so the relevant UGDs may be listed at the belonging position.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>text</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>
+							The actual UGD which may be any html-text containing mustache tags.
+						</td>
+					</tr>
+					<tr class=alt>
+						<td>mappers</td>
+					</tr>
+					<tr>
+						<td class=fielddesc>
+							By default one mapper is assigned to the UGD depending on the entity.
+							Mappers are used to create requests and get tags wich may be used in the UGD.
+							Also mappery are in order and may depend on the previous mappers.
+							For now mappers expect a response in JSON or plain/text.
+							JSON contains the keys and the values.
+							If the response is plain/text it can be accessed by {{$mapperskey.src}}
+						</td>
+					</tr>
+				</table>
+			</div>
+		</div>
+
+			<a id="clients_template" name="clients_template" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve UGD Details Template</h4>
+					<p>This is a convenience resource. It can be useful when
+						building maintenance user interface screens for UGDs.
+						The UGD data returned consists of any or all of:
+					</p>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>name</dt>
+						<dd>
+							String
+						</dd>
+						<dt>entity</dt>
+						<dd>
+							String
+						</dd>
+						<dt>type</dt>
+						<dd>
+							String
+						</dd>
+						<dt>text</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dt>mappers</dt>
+						<dd>
+							Mapper <span>optional</span>
+						</dd>
+					</dl>
+					<p>Example Request:</p>
+					<div class=apiClick>templates/template</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/templates/template
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "name": "Test",
+  "entity": 1,
+  "type": 0,
+  "text": "This is a loan for {{loan.clientName}}",
+  "mappers": [
+    {
+    "mappersorder": 0,
+    "mapperskey": "loan",
+    "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
+    "id": 1
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="resources_addtemplate" name="resources_addtemplate"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Add a UGD</h4>
+					<p>Adds a new UGD.</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name</td>
+						</tr>
+					</table>
+					<p>Example Requests:</p>
+					<div class=apiClick>templates/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/templates
+					</code>
+					<code class="method-request">
+POST templates
+Content-Type: application/json
+Request Body:
+{
+  "id": 1,
+  "name": "Test",
+  "entity": 1,
+  "type": 0,
+  "text": "This is a loan for {{loan.clientName}}",
+  "mappers": [
+    {
+      "mappersorder": 0,
+      "mapperskey": "loan",
+      "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
+      "id": 1
+    }
+  ]
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="resource_templatelist" name="resource_templatelist"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve all UGDs</h4>
+
+					<p>Example Requests:</p>
+					<div class=apiClick>templates</div>
+					<br>
+					<p>
+					It is also possible to get specific UGDs by entity and type:
+					</p>
+					<div class=apiClick>templates?type=0&entity=0</div>
+					<br>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Entity</div></td>
+							<td>Id</td>
+							<td><div class="fineractHeading2">Type</div></td>
+							<td>Id</td>
+						</tr>
+						<tr class=alt>
+							<td>client</td>
+							<td>0</td>
+							<td>Document</td>
+							<td>0</td>
+						</tr>
+						<tr>
+							<td>loan</td>
+							<td>1</td>
+							<td>E-Mail (not yet)</td>
+							<td>1</td>
+						</tr>
+						<tr>
+							<td></td>
+							<td></td>
+							<td>SMS</td>
+							<td>2</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/templates
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "name": "Test",
+    "entity": 1,
+    "type": 0,
+    "text": "This is a loan for {{loan.clientName}}",
+    "mappers": [
+      {
+        "mappersorder": 0,
+        "mapperskey": "loan",
+        "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
+        "id": 1
+      }
+    ]
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="resources_retrievetemplate" name="resources_retrievetemplate"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a UGD</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>templates/1</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/templates/{Id}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "name": "Test",
+  "entity": 1,
+  "type": 0,
+  "text": "This is a loan for {{loan.clientName}}",
+  "mappers": [
+    {
+      "mappersorder": 0,
+      "mapperskey": "loan",
+      "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
+      "id": 1
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="resources_updatetemplate" name="resources_updatetemplate"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Update a UGD</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+PUT https://DomainName/api/v1/templates/{templateId}
+					</code>
+					<code class="method-request">
+PUT templates/1
+Content-Type: application/json
+Request Body:
+{
+  "id": 1,
+  "name": "Test",
+  "entity": 1,
+  "type": 0,
+  "text": "This is a loan for {{loan.clientName}}",
+  "mappers": [
+    {
+      "mappersorder": 0,
+      "mapperskey": "loan",
+      "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
+      "id": 1
+    }
+  ]
+}
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+
+<!----------------Client charge API starts ------------>
+
+          <a id="client_charge" name="client_charge" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Client Charges</h3>
+					<p>It is typical for MFI's to directly associate charges with an implicit Client account. These
+					can be either fees or penalties</p>
+					<p>Client Charges are client specific instances of <a href="#charges">Charges</a>.  Refer <a href="#charges">Charges</a> for documentation of the various properties of a charge, Only additional properties (
+					specific to the context of a Charge being associated with a Client account) are
+					described here</p>
+
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>amountPaid</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The total amount which has been paid for this Charge
+						</td>
+						</tr>
+						<tr class=alt>
+							<td>amountWaived</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The total amount that has been waived for this Charge
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>amountOutstanding</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>The Total outstanding amount for this Charge
+							</td>
+						</tr>
+
+						<tr class=alt>
+							<td>dueDate</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>it specifies the due date</td>
+						</tr>
+
+					</table>
+				</div>
+			</div>
+
+			<a id="client_transaction" name="client_transaction" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Client Transaction</h3>
+					<p>Client Transactions refer to transactions made directly againt a Client's internal account. Currently, these transactions are only created as a result of charge payments/waivers. You are allowed to undo a transaction, however you cannot explicitly create one.
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Field Descriptions</div></td>
+						</tr>
+						<tr class=alt>
+							<td>id</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>This ia an unique Id associate with a Transaction.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>officeId</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Identifier of the office in which the transaction was made</td>
+						</tr>
+						<tr class=alt>
+							<td>reversed</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Boolean Flag that indicates if this transaction is reversed.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>amount</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Transaction amount</b>.
+							</td>
+						</tr>
+						<tr class=alt>
+							<td>currency</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Details of the currency involved with the Transaction. Currently defaults
+							to the currency of the Client Charge this transaction pays </td>
+						</tr>
+						<tr class=alt>
+							<td>date</td>
+						</tr>
+						<tr>
+							<td class=fielddesc>Effective date of the transaction</td>
+						</tr>
+
+					</table>
+				</div>
+			</div>
+			<a id="list_clientTransactions" name="list_clientTransactions" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Client Transactions</h4>
+					<p>The <i>list</i> capability of client transaction can support <b>pagination</b>.</p>
+					<h5>Mandatory Arguments</h5>
+					<dl class="argument-list">
+						<dt>offset</dt>
+						<dd>
+							Integer <span>Mandatory</span>, defaults to 0
+						</dd>
+						<dd>Indicates the result from which pagination starts</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>Mandatory</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+            		</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/189/transactions</div>
+					<div class=apiClick>clients/189/transactions?offset=10&limit=50</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/transactions?limit=5&offset=0
+					</code>
+					<code class="method-request">
+GET clients/226/transactions?limit=5&offset=0
+Content-Type: application/json
+No Request Body:
+
+			</code>
+			<code class="method-response">
+{
+  "totalFilteredRecords": 20,
+  "pageItems": [
+    {
+      "id": 226,
+      "officeId": 1,
+      "officeName": "Head Office",
+      "type": {
+        "id": 1,
+        "code": "clientTransactionType.payCharge",
+        "value": "PAY_CHARGE"
+      },
+      "date": [
+        2015,
+        9,
+        2
+      ],
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 22,
+      "submittedOnDate": [
+        2015,
+        9,
+        2
+      ],
+      "reversed": false
+    }
+  ]
+}
+</code>
+				</div>
+			</div>
+
+			<a id="clienttransactions_retrieve" name="clienttransactions_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Client Transaction</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/transactions/1</div>
+					<br>
+					<br>
+					<div class=apiClick>clients/1/transactions/1?fields=id,officeName</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/transaction/{transactionId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "officeId": 1,
+  "officeName": "Head Office",
+  "type": {
+    "id": 1,
+    "code": "clientTransactionType.payCharge",
+    "value": "PAY_CHARGE"
+  },
+  "date": [
+    2015,
+    8,
+    17
+  ],
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 60.000000,
+  "submittedOnDate": [
+    2015,
+    8,
+    19
+  ],
+  "reversed": true
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="add_clientCharge" name="add_clientCharge" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Add Client Charge</h4>
+					<p>
+						This API associates a Client charge with an implicit Client account
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>chargeId and dueDate </td>
+						</tr>
+					</table>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>amount</td>
+						</tr>
+					</table>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/clients/{clientId}/charges
+					</code>
+					<code class="method-request">
+POST clients/226/charges
+Content-Type: application/json
+Request Body:
+{
+    "amount" : "100",
+	"chargeId" : "226",
+	"dateFormat" : "dd MMMM yyyy",
+	"dueDate" : "01 September 2015",
+	"locale" : "en"
+}
+			</code>
+			<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 189,
+  "resourceId":164
+}
+					</code>
+				</div>
+			</div>
+			<a id="list_clientCharges" name="list_clientCharges" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Client Charges</h4>
+
+					<p>The <i>list</i> capability of client charges supports <b>pagination</b>.</p>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>offset</dt>
+						<dd>
+							Integer <span>Mandatory</span>, defaults to 0
+						</dd>
+						<dd>Indicates the result from which pagination starts</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>Mandatory</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+						<dt>pendingPayment</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd> Filters charges that are pending payment (neither paid or waived).</dd>
+          			</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/charges</div>
+					<div class=apiClick>clients/1/charges?offset=0&limit=5</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/charges?limit=5&offset=0
+					</code>
+					<code class="method-request">
+GET clients/1/charges?limit=5&offset=0
+Content-Type: application/json
+No Request Body
+            		</code>
+					<code class="method-response">
+{
+  "totalFilteredRecords": 4,
+  "pageItems": [
+    {
+      "id": 5,
+      "clientId": 1,
+      "chargeId": 6,
+      "name": "Client Fees 2",
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "dueDate": [
+        2015,
+        9,
+        1
+      ],
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 550.000000,
+      "amountPaid": 0.000000,
+      "amountWaived": 0.000000,
+      "amountWrittenOff": 0,
+      "amountOutstanding": 550.000000,
+      "penalty": false,
+      "isActive": true,
+      "isPaid": false,
+      "isWaived": false
+    },
+    {
+      "id": 4,
+      "clientId": 1,
+      "chargeId": 5,
+      "name": "Client Fee 1",
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "dueDate": [
+        2015,
+        8,
+        31
+      ],
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 120.000000,
+      "amountPaid": 0.000000,
+      "amountWaived": 120.000000,
+      "amountWrittenOff": 0,
+      "amountOutstanding": 120.000000,
+      "penalty": true,
+      "isActive": true,
+      "isPaid": false,
+      "isWaived": true
+    },
+    {
+      "id": 3,
+      "clientId": 1,
+      "chargeId": 5,
+      "name": "Client Fee 1",
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "dueDate": [
+        2015,
+        8,
+        17
+      ],
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 100.000000,
+      "amountPaid": 0.000000,
+      "amountWaived": 100.000000,
+      "amountWrittenOff": 0,
+      "amountOutstanding": 0.000000,
+      "penalty": true,
+      "isActive": true,
+      "isPaid": false,
+      "isWaived": true
+    },
+    {
+      "id": 2,
+      "clientId": 1,
+      "chargeId": 2,
+      "name": "Recurring savings Charge",
+      "chargeTimeType": {
+        "id": 7,
+        "code": "chargeTimeType.monthlyFee",
+        "value": "Monthly Fee"
+      },
+      "dueDate": [
+        2015,
+        8,
+        17
+      ],
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 100.000000,
+      "amountPaid": 0,
+      "amountWaived": 100.000000,
+      "amountWrittenOff": 0,
+      "amountOutstanding": 0.000000,
+      "penalty": false,
+      "isActive": true,
+      "isPaid": false,
+      "isWaived": true
+    }
+  ]
+}
+
+					</code>
+				</div>
+			</div>
+					<a id="clientcharges_retrieve" name="clientcharges_retrieve" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Client Charge</h4>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>associations</dt>
+						<dd>
+							optional, <span>Either 'all' or a comma separated list of
+								loan 'associations' (itemised below).</span>
+						</dd>
+						<dd>
+							<br>Associations are just extra pieces of data that you
+							might or might not want to retrieve.<br>
+							<br>
+						</dd>
+						<dd>
+							<b>'all':</b> Gets all association data.
+						</dd>
+						<dd>
+							<b>'transactions':</b> Retrieves all transactions made on this client charge.
+						</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>clients/1/charges/1</div>
+					<br>
+					<br>
+					<div class=apiClick>clients/1/charges/1?fields=name,id</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/clients/{clientId}/charges/{clientChargeId}
+					</code>
+					<code class="method-response">
+{
+  "id": 3,
+  "clientId": 1,
+  "chargeId": 5,
+  "name": "Client Fee 1",
+  "chargeTimeType": {
+    "id": 2,
+    "code": "chargeTimeType.specifiedDueDate",
+    "value": "Specified due date"
+  },
+  "dueDate": [
+    2015,
+    8,
+    17
+  ],
+  "chargeCalculationType": {
+    "id": 1,
+    "code": "chargeCalculationType.flat",
+    "value": "Flat"
+  },
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 100.000000,
+  "amountPaid": 0.000000,
+  "amountWaived": 100.000000,
+  "amountWrittenOff": 0,
+  "amountOutstanding": 0.000000,
+  "penalty": true,
+  "isActive": true,
+  "isPaid": false,
+  "isWaived": true
+}
+					</code>
+				</div>
+			</div>
+
+        <a id="delete_clientCharge" name="delete_clientCharge" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a Client Charge</h4>
+					<p>
+					Deletes a Client Charge on which no transactions have taken place (either payments or waivers).				</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE  https://DomainName/api/v1/clients/{clientId}/charges/{clientChargeId}
+					</code>
+					<code class="method-request">
+POST clients/189/charges/164
+Content-Type: application/json
+No Request Body
+            </code>
+			<code class="method-response">
+{
+  "officeId": 1,
+  "clientId": 189,
+  "resourceId":164
+}
+					</code>
+				</div>
+			</div>
+			<a id="pay_clientCharge" name="pay_clientCharge" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Pay a Client Charge</h4>
+					<p>
+						<br>Pay either a part of or the entire due amount for a charge.
+					</p>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr >
+						<td>
+							transactionDate and amount
+							</td>
+						</tr>
+					</table>
+						<br />
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/clients/{clientId}/charges/{clientChargeId}?command=paycharge
+					</code>
+					<code class="method-request">
+POST clients/189/charges/157?command=payCharge
+Content-Type: application/json
+Request Body:
+{
+
+ "amount":200,
+ "locale":"en",
+ "dateFormat":"dd MMMM yyyy",
+ "transactionDate":"01 September 2015"
+}
+			</code>
+			<code class="method-response">
+
+{
+  "officeId":1,
+  "clientId":189,
+  "resourceId":157,
+  "transactionId":"221"
+}
+					</code>
+				</div>
+			</div>
+          <a id="waive_clientCharge" name="waive_clientCharge" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Waive a Client Charge</h4>
+					<p>
+						<br>This API provides the facility of waiving off the remaining amount on a client charge
+					</p>
+
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/clients/{clientId}/charges/{clientChargeId}?command=waive
+					</code>
+					<code class="method-request">
+POST clients/189/charges/157?command=waive
+Content-Type: application/json
+No Request Body
+			</code>
+			<code class="method-response">
+
+{
+ 	"clientId":"189",
+	"resourceId":157
+}
+					</code>
+				</div>
+			</div>
+
+           <a id="revert_clientCharge" name="revert_clientCharge" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Undo a Client Transaction</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/clients/{clientId}/transactions/{transactionId}?command=undo
+					</code>
+					<code class="method-request">
+POST clients/189/transactions/222?command=undo
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+
+{
+"officeId":1,
+"clientId":189,
+"resourceId":222
+}
+					</code>
+				</div>
+			</div>
+<!-- End of Client Transactions -->
+			<a id="resources_deletetemplate" name="resources_deletetemplate"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Delete a UGD</h4>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+DELETE https://DomainName/api/v1/templates/{templateId}
+					</code>
+					<code class="method-request">
+DELETE templates/1
+Content-Type: application/json
+No Request Body:
+					</code>
+					<code class="method-response">
+{
+    "resourceId": 1
+}
+					</code>
+				</div>
+			</div>
+
+			<!-- end of UGD api docs -->
+					<!-- start of SPM API docs -->
+					<a id="surveys" name="surveys" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>SPM API</h3>
+							<p>
+								The Apache Fineract SPM API provides the ability to create custom surveys to collect social performance measurentment data or any additional questionnaire a financial institute want to collect.
+							</p>
+						</div>
+					</div>
+					<a id="surveys_create" name="surveys_create" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>Create a Survey</h3>
+							<p>Adds a new survey to collect client related data.</p>
+							<table class=matrixHeading>
+								<tr class="matrixHeadingBG">
+									<td><div class="fineractHeading2">Mandatory Fields</div></td>
+								</tr>
+								<tr class=alt>
+									<td>countryCode, key, name, questions, responses, sequenceNo, text, value</td>
+								</tr>
+							</table>
+						</div>
+						<div class="method-example">
+							<code class="method-declaration">
+POST https://DomainName/api/v1/surveys
+							</code>
+							<code class="method-request">
+POST surveys
+Content-Type: application/json
+Request Body:
+{
+  "key":"ppi-kenya-2010",
+  "name":"PPI Survey for Kenya, version 2010",
+  "description":null,
+  "countryCode":"KE",
+  "validFrom":null,
+  "validTo":null,
+  "componentDatas":
+  [
+    {
+      "key":"Household",
+      "text":"Information about the household.",
+      "description":null,
+      "sequenceNo":1
+    }
+  ],
+  "questionDatas":
+  [
+    {
+      "componentKey":"Household",
+      "key":"Familiy members",
+      "text":"How many persons live in the household?",
+      "description":null,
+      "sequenceNo":1,
+      "responseDatas":
+      [
+        {
+          "text":"1 to 3",
+          "value":1,
+          "sequenceNo":1
+        },
+        {
+          "text":"3 to 7",
+          "value":5,
+          "sequenceNo":2
+        },
+        {
+          "text":"more than 7",
+          "value":13,
+          "sequenceNo":3
+        }
+      ]
+    }
+  ]
+}
+							</code>
+							<code class="method-response">
+200 OK
+							</code>
+						</div>
+					</div>
+					<a id="surveys_list" name="surveys_list" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>List all Surveys</h3>
+						</div>
+						<div class="method-example">
+							<code class="method-declaration">
+GET https://DomainName/api/v1/surveys
+							</code>
+							<code class="method-request">
+GET surveys
+Content-Type: application/json
+No Request Body
+							</code>
+							<code class="method-response">
+[
+  {
+    "id": 1,
+    "componentDatas": [
+      {
+        "id": 1,
+        "key": "Household",
+        "text": "Information about the household.",
+        "description": null,
+        "sequenceNo": 1
+      }
+    ],
+    "questionDatas": [
+      {
+        "id": 1,
+        "responseDatas": [
+          {
+            "id": 1,
+            "text": "1 to 3",
+            "value": 1,
+            "sequenceNo": 1
+          },
+          {
+            "id": 2,
+            "text": "3 to 7",
+            "value": 5,
+            "sequenceNo": 2
+          },
+          {
+            "id": 3,
+            "text": "more than 7",
+            "value": 13,
+            "sequenceNo": 3
+          }
+        ],
+        "componentKey": "Household",
+        "key": "Familiy members",
+        "text": "How many persons live in the household?",
+        "description": null,
+        "sequenceNo": 1
+      }
+    ],
+    "key": "ppi-kenya-2010",
+    "name": "PPI Survey for Kenya, version 2010",
+    "description": null,
+    "countryCode": "KE",
+    "validFrom": 1450047600000,
+    "validTo": 4607276399000
+  }
+]
+							</code>
+						</div>
+					</div>
+					<a id="surveys_retrieve" name="surveys_retrieve" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>Retrieve a Survey</h3>
+						</div>
+						<div class="method-example">
+							<code class="method-declaration">
+GET https://DomainName/api/v1/surveys/1
+							</code>
+							<code class="method-request">
+GET surveys/1
+Content-Type: application/json
+No Request Body
+							</code>
+							<code class="method-response">
+{
+  "id": 1,
+  "componentDatas": [
+    {
+      "id": 1,
+      "key": "Household",
+      "text": "Information about the household.",
+      "description": null,
+      "sequenceNo": 1
+    }
+  ],
+  "questionDatas": [
+    {
+      "id": 1,
+      "responseDatas": [
+        {
+          "id": 1,
+          "text": "1 to 3",
+          "value": 1,
+          "sequenceNo": 1
+        },
+        {
+          "id": 2,
+          "text": "3 to 7",
+          "value": 5,
+          "sequenceNo": 2
+        },
+        {
+          "id": 3,
+          "text": "more than 7",
+          "value": 13,
+          "sequenceNo": 3
+        }
+      ],
+      "componentKey": "Household",
+      "key": "Familiy members",
+      "text": "How many persons live in the household?",
+      "description": null,
+      "sequenceNo": 1
+    }
+  ],
+  "key": "ppi-kenya-2010",
+  "name": "PPI Survey for Kenya, version 2010",
+  "description": null,
+  "countryCode": "KE",
+  "validFrom": 1450047600000,
+  "validTo": 4607276399000
+}
+							</code>
+						</div>
+					</div>
+					<a id="surveys_delete" name="surveys_delete" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>Deactivate Survey</h3>
+						</div>
+						<div class="method-example">
+							<code class="method-declaration">
+DELETE https://DomainName/api/v1/surveys/1
+							</code>
+							<code class="method-request">
+DELETE surveys/1
+Content-Type: application/json
+No Request Body
+							</code>
+							<code class="method-response">
+200 OK
+							</code>
+						</div>
+					</div>
+					<a id="lookuptables_create" name="lookuptables_create" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>Create a Lookup Table entry</h3>
+							<p>Add a new netry to a survey.</p>
+							<table class=matrixHeading>
+								<tr class="matrixHeadingBG">
+									<td><div class="fineractHeading2">Mandatory Fields</div></td>
+								</tr>
+								<tr class=alt>
+									<td>key, score, validFrom, validTo</td>
+								</tr>
+							</table>
+						</div>
+						<div class="method-example">
+							<code class="method-declaration">
+POST https://DomainName/api/v1/surveys/1/lookuptables
+							</code>
+							<code class="method-request">
+POST surveys/1/lookuptables
+Content-Type: application/json
+Request Body:
+{
+  "key": "test-table",
+  "description": null,
+  "entries": [
+    {
+      "valueFrom": 0,
+      "valueTo": 25,
+      "score": 100
+    },
+    {
+      "valueFrom": 26,
+      "valueTo": 50,
+      "score": 50
+    },
+    {
+      "valueFrom": 51,
+      "valueTo": 75,
+      "score": 25
+    },
+    {
+      "valueFrom": 76,
+      "valueTo": 100,
+      "score": 0
+    }
+  ]
+}
+              </code>
+							<code class="method-response">
+200 OK
+							</code>
+						</div>
+					</div>
+					<a id="lookuptables_list" name="lookuptables_list" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>List all Lookup Table entries</h3>
+							<p>List all Lookup Table entries for a survey.</p>
+						</div>
+						<div class="method-example">
+							<code class="method-declaration">
+GET https://DomainName/api/v1/surveys/1/lookuptables
+							</code>
+							<code class="method-request">
+GET surveys/1/lookuptables
+Content-Type: application/json
+No Request Body
+              </code>
+							<code class="method-response">
+[
+  {
+    "key": "test-table",
+    "description": null,
+    "entries": [
+      {
+        "valueFrom": 0,
+        "valueTo": 25,
+        "score": 100
+      },
+      {
+        "valueFrom": 26,
+        "valueTo": 50,
+        "score": 50
+      },
+      {
+        "valueFrom": 51,
+        "valueTo": 75,
+        "score": 25
+      },
+      {
+        "valueFrom": 76,
+        "valueTo": 100,
+        "score": 0
+      }
+    ]
+  }
+]
+							</code>
+						</div>
+					</div>
+					<a id="lookuptables_retrieve" name="lookuptables_retrieve" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>Retrieve a Lookup Table entry</h3>
+							<p>Retrieve a Lookup Table entry for a survey.</p>
+						</div>
+						<div class="method-example">
+							<code class="method-declaration">
+GET https://DomainName/api/v1/surveys/1/lookuptables/1
+							</code>
+							<code class="method-request">
+GET surveys/1/lookuptables/test-table
+Content-Type: application/json
+No Request Body
+              </code>
+							<code class="method-response">
+{
+  "key": "test-table",
+  "description": null,
+  "entries": [
+    {
+      "valueFrom": 0,
+      "valueTo": 25,
+      "score": 100
+    },
+    {
+      "valueFrom": 26,
+      "valueTo": 50,
+      "score": 50
+    },
+    {
+      "valueFrom": 51,
+      "valueTo": 75,
+      "score": 25
+    },
+    {
+      "valueFrom": 76,
+      "valueTo": 100,
+      "score": 0
+    }
+  ]
+}
+							</code>
+						</div>
+					</div>
+
+					<a id="scorecards_create" name="scorecards_create" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>Create a Scorecard entry</h3>
+							<p>Add a new netry to a survey.</p>
+							<table class=matrixHeading>
+								<tr class="matrixHeadingBG">
+									<td><div class="fineractHeading2">Mandatory Fields</div></td>
+								</tr>
+								<tr class=alt>
+									<td>clientId, createdOn, questionId, responseId, staffId</td>
+								</tr>
+							</table>
+						</div>
+						<div class="method-example">
+							<code class="method-declaration">
+POST https://DomainName/api/v1/surveys/1/scorecards
+							</code>
+							<code class="method-request">
+POST surveys/1/scorecards
+Content-Type: application/json
+Request Body:
+{
+  "userId": 1,
+  "clientId": 1,
+  "createdOn": 1451905784553,
+  "scorecardValues":
+  [
+    {
+      "questionId": 1,
+      "responseId": 1,
+      "value": 0
+    },
+    {
+      "questionId": 2,
+      "responseId": 8,
+      "value": 0
+    },
+    {
+      "questionId": 3,
+      "responseId": 12,
+      "value": 7
+    },
+    {
+      "questionId": 4,
+      "responseId": 16,
+      "value": 4
+    },
+    {
+      "questionId": 5,
+      "responseId": 21,
+      "value": 2
+    },
+    {
+      "questionId": 6,
+      "responseId": 25,
+      "value": 2
+    },
+    {
+      "questionId": 7,
+      "responseId": 28,
+      "value": 3
+    },
+    {
+      "questionId": 8,
+      "responseId": 30,
+      "value": 6
+    },
+    {
+      "questionId": 9,
+      "responseId": 33,
+      "value": 4
+    },
+    {
+      "questionId": 10,
+      "responseId": 37,
+      "value": 7
+    }
+  ]
+}              </code>
+							<code class="method-response">
+200 OK
+							</code>
+						</div>
+					</div>
+					<a id="scorecards_list" name="scorecards_list" class="old-syle-anchor">&nbsp;</a>
+					<div class="method-section">
+						<div class="method-description">
+							<h3>List all Scorecard entries</h3>
+							<p>List all Scorecard entries for a survey.</p>
+						</div>
+						<div class="method-example">
+							<code class="method-declaration">
+GET https://DomainName/api/v1/surveys/1/scorecards
+							</code>
+							<code class="method-request">
+GET surveys/1/scorecards
+Content-Type: application/json
+No Request Body
+              </code>
+							<code class="method-response">
+[
+	{
+	  "userId": 1,
+	  "clientId": 1,
+	  "createdOn": 1451905784553,
+	  "scorecardValues":
+	  [
+	    {
+	      "questionId": 1,
+	      "responseId": 1,
+	      "value": 0
+	    },
+	    {
+	      "questionId": 2,
+	      "responseId": 8,
+	      "value": 0
+	    },
+	    {
+	      "questionId": 3,
+	      "responseId": 12,
+	      "value": 7
+	    },
+	    {
+	      "questionId": 4,
+	      "responseId": 16,
+	      "value": 4
+	    },
+	    {
+	      "questionId": 5,
+	      "responseId": 21,
+	      "value": 2
+	    },
+	    {
+	      "questionId": 6,
+	      "responseId": 25,
+	      "value": 2
+	    },
+	    {
+	      "questionId": 7,
+	      "responseId": 28,
+	      "value": 3
+	    },
+	    {
+	      "questionId": 8,
+	      "responseId": 30,
+	      "value": 6
+	    },
+	    {
+	      "questionId": 9,
+	      "responseId": 33,
+	      "value": 4
+	    },
+	    {
+	      "questionId": 10,
+	      "responseId": 37,
+	      "value": 7
+	    }
+	  ]
+	}
+]
+							</code>
+						</div>
+					</div>
+					<!-- end of SPM API docs -->
+
+					<!-- Customer Self Service APIs-->
+			<div class="method-section">
+				<div class="method-description">
+					<h3>Self Service APIs</h3>
+				</div>
+			</div>
+			<a id="selfbasicauth" name="selfbasicauth" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Verify authentication</h4>
+					<p>Authenticates the credentials provided and returns the set roles and permissions allowed.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/self/authentication?username={username}&password={password}
+					</code>
+					<code class="method-request">
+POST self/authentication?username=mifos&password=password
+Content-Type: application/json
+No Request Body
+					</code>
+					<p>Example response of autentication for user</p>
+<code class="method-response">
+{
+    "username": "mifos",
+    "userId": 1,
+    "base64EncodedAuthenticationKey": "bWlmb3M6cGFzc3dvcmQ=",
+    "authenticated": true,
+    "officeId": 1,
+    "officeName": "Head Office",
+    "staffId": 1,
+    "staffDisplayName": "Director, Program",
+    "organisationalRole": {
+        "id": 100,
+        "code": "staffOrganisationalRoleType.programDirector",
+        "value": "Program Director"
+    },
+    "roles": [
+        {
+            "id": 1,
+            "name": "Super user",
+            "description": "This role provides all application permissions."
+        }
+    ],
+    "permissions": [
+        "ALL_FUNCTIONS"
+    ],
+    "isSelfServiceUser": true,
+    "clients": [1,2,3]
+}
+</code>
+
+					<code class="method-request">
+POST self/authentication?username=mifos&password=fail
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+{
+    "developerMessage": "Invalid authentication details were passed in api request.",
+    "developerDocLink": "https://github.com/openMF/mifosx/wiki/HTTP-API-Error-codes",
+    "httpStatusCode": "401",
+    "defaultUserMessage": "Unauthenticated. Please login.",
+    "userMessageGlobalisationCode": "error.msg.not.authenticated",
+    "errors": []
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="selfoauth" name="selfoauth" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Fetch authenticated user details </h4>
+					<p>checks the Authentication and returns the set roles and permissions allowed.</p>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+POST https://DomainName/api/v1/userdetails?access_token={access_token}
+					</code>
+					<code class="method-request">
+POST userdetails?access_token=bWlmb3M6cGFzc3dvcmQ=
+Content-Type: application/json
+No Request Body
+					</code>
+					<p>Example response of authenticated user.</p>
+<code class="method-response">
+{
+    "username": "mifos",
+    "userId": 1,
+    "base64EncodedAuthenticationKey": "bWlmb3M6cGFzc3dvcmQ=",
+    "authenticated": true,
+    "officeId": 1,
+    "officeName": "Head Office",
+    "staffId": 1,
+    "staffDisplayName": "Director, Program",
+    "organisationalRole": {
+        "id": 100,
+        "code": "staffOrganisationalRoleType.programDirector",
+        "value": "Program Director"
+    },
+    "roles": [
+        {
+            "id": 1,
+            "name": "Super user",
+            "description": "This role provides all application permissions."
+        }
+    ],
+    "permissions": [
+        "ALL_FUNCTIONS"
+    ],
+    "isSelfServiceUser": true,
+    "clients": [1,2,3]
+}
+</code>
+
+					<code class="method-request">
+POST api/oauth/token?username=mifos&password=password&client_id=community-app&grant_type=password&client_secret=123
+Content-Type: application/json
+No Request Body
+					</code>
+					<code class="method-response">
+{
+    "developerMessage": "Invalid authentication details were passed in api request.",
+    "developerDocLink": "https://github.com/openMF/mifosx/wiki/HTTP-API-Error-codes",
+    "httpStatusCode": "401",
+    "defaultUserMessage": "Unauthenticated. Please login.",
+    "userMessageGlobalisationCode": "error.msg.not.authenticated",
+    "errors": []
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="selflistclients" name="selflistclients" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Clients associated to the user</h4>
+					<p>The <i>list</i> capability of clients can support <b>pagination</b> and <b>sorting</b>.</p>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>offset</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 0
+						</dd>
+						<dd>Indicates the result from which pagination starts</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>optional</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+						<dt>orderBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>displayName, accountNo, officeId, officeName</span>
+						</dd>
+						<dd>Orders results by the indicated field.</dd>
+
+						<dt>sortBy</dt>
+						<dd>
+							String <span>optional</span>, one of <span>ASC, DESC</span>
+						</dd>
+						<dd>Indicates what way to order results if <i>orderBy</i> is used.</dd>
+
+						<dt>displayName</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use displayName of clients to restrict results.</dd>
+
+						<dt>firstName</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use firstName of clients to restrict results.</dd>
+
+						<dt>lastName</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd>Use lastName of clients to restrict results.</dd>
+
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/clients</div>
+					<br>
+					<div class=apiClick>self/clients?fields=displayName,officeName</div>
+					<br>
+					<div class=apiClick>self/clients?offset=10&limit=50</div>
+					<br>
+					<div class=apiClick>self/clients?orderBy=displayName&sortOrder=DESC</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/clients
+					</code>
+					<code class="method-response">
+{
+  "totalFilteredRecords": 2,
+  "pageItems": [
+    {
+      "id": 1,
+      "accountNo": "000000001",
+      "status": {
+        "id": 300,
+        "code": "clientStatusType.active",
+        "value": "Active"
+      },
+      "active": true,
+      "activationDate": [
+        2013,
+        3,
+        1
+      ],
+      "fullname": "Small shop",
+      "displayName": "Small shop",
+      "officeId": 1,
+      "officeName": "Head Office"
+    },
+    {
+      "id": 2,
+      "accountNo": "000000002",
+      "status": {
+        "id": 100,
+        "code": "clientStatusType.pending",
+        "value": "Pending"
+      },
+      "active": false,
+      "fullname": "Home Farm Produce",
+      "displayName": "Home Farm Produce",
+      "officeId": 1,
+      "officeName": "Head Office"
+    }
+  ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="selfclient" name="selfclient" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Client</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/clients/1</div>
+					<br>
+					<div class=apiClick>self/clients/1?fields=id,displayName,officeName</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/clients/{clientId}
+					</code>
+					<code class="method-response">
+{
+  "id": 27,
+  "accountNo": "000000027",
+  "status": {
+    "id": 300,
+    "code": "clientStatusType.active",
+    "value": "Active"
+  },
+  "active": true,
+  "activationDate": [
+    2013,
+    1,
+    1
+  ],
+  "firstname": "savings",
+  "lastname": "test",
+  "displayName": "savings test",
+  "officeId": 1,
+  "officeName": "Head Office",
+  "timeline": {
+    "submittedOnDate": [
+      2013,
+      1,
+      1
+    ],
+    "submittedByUsername": "mifos",
+    "submittedByFirstname": "App",
+    "submittedByLastname": "Administrator",
+    "activatedOnDate": [
+      2013,
+      1,
+      1
+    ],
+    "activatedByUsername": "mifos",
+    "activatedByFirstname": "App",
+    "activatedByLastname": "Administrator"
+  },
+  "savingsProductId": 4,
+  "savingsProductName": "account overdraft",
+  "groups": []
+}
+					</code>
+				</div>
+			</div>
+			<a id="selfclientsaccounts" name="selfclientsaccounts" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve client accounts overview</h4>
+					<p>
+						An example of how a loan portfolio summary can be provided. This
+						is requested in a specific use case of the community application.<br> It is quite reasonable to add resources like this to simplify User Interface development.
+					</p>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/clients/1/accounts</div>
+					<br>
+					<br>
+					<div class=apiClick>self/clients/1/accounts?fields=loanAccounts,savingsAccounts</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/clients/{clientId}/accounts
+					</code>
+					<code class="method-response">
+{
+    "loanAccounts": [
+        {
+            "id": 1,
+            "accountNo": "000000001",
+            "externalId": "456",
+            "productId": 1,
+            "productName": "TestOne",
+            "status": {
+                "id": 300,
+                "code": "loanStatusType.active",
+                "value": "Active",
+                "pendingApproval": false,
+                "waitingForDisbursal": false,
+                "active": true,
+                "closedObligationsMet": false,
+                "closedWrittenOff": false,
+                "closedRescheduled": false,
+                "closed": false,
+                "overpaid": false
+            },
+            "loanType": {
+                "id": 1,
+                "code": "loanType.individual",
+                "value": "Individual"
+            },
+            "loanCycle": 1
+        }
+    ],
+    "savingsAccounts": [
+        {
+            "id": 7,
+            "accountNo": "000000007",
+            "productId": 2,
+            "productName": "Other product",
+            "status": {
+                "id": 100,
+                "code": "savingsAccountStatusType.submitted.and.pending.approval",
+                "value": "Submitted and pending approval",
+                "submittedAndPendingApproval": true,
+                "approved": false,
+                "rejected": false,
+                "withdrawnByApplicant": false,
+                "active": false,
+                "closed": false
+            },
+            "currency": {
+                "code": "USD",
+                "name": "US Dollar",
+                "decimalPlaces": 2,
+                "displaySymbol": "$",
+                "nameCode": "currency.USD",
+                "displayLabel": "US Dollar ($)"
+            }
+        },
+        {
+            "id": 6,
+            "accountNo": "000000006",
+            "productId": 1,
+            "productName": "Passbook Savings",
+            "status": {
+                "id": 300,
+                "code": "savingsAccountStatusType.active",
+                "value": "Active",
+                "submittedAndPendingApproval": false,
+                "approved": false,
+                "rejected": false,
+                "withdrawnByApplicant": false,
+                "active": true,
+                "closed": false
+            },
+            "currency": {
+                "code": "USD",
+                "name": "US Dollar",
+                "decimalPlaces": 2,
+                "displaySymbol": "$",
+                "nameCode": "currency.USD",
+                "displayLabel": "US Dollar ($)"
+            },
+            "accountBalance": 1828.03
+        },
+        {
+            "id": 5,
+            "accountNo": "000000005",
+            "productId": 1,
+            "productName": "Passbook Savings",
+            "status": {
+                "id": 400,
+                "code": "savingsAccountStatusType.withdrawn.by.applicant",
+                "value": "Withdrawn by applicant",
+                "submittedAndPendingApproval": false,
+                "approved": false,
+                "rejected": false,
+                "withdrawnByApplicant": true,
+                "active": false,
+                "closed": true
+            },
+            "currency": {
+                "code": "USD",
+                "name": "US Dollar",
+                "decimalPlaces": 2,
+                "displaySymbol": "$",
+                "nameCode": "currency.USD",
+                "displayLabel": "US Dollar ($)"
+            }
+        }
+    ]
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="selfclientsimages" name="selfclientsimages"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve Client Image</h4>
+					<p>Optional arguments are identical to those of <a href="#client_images_retrieve_binary">Get Image associated with an Entity (Binary file)</a></p>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/clients/1/images</div>
+					<br>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/clients/{clientId}/images
+Accept: text/plain
+
+					</code>
+					<code class="method-response">
+data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
+bWFnZVJlYWR5ccllPAAAAJ1JREFUeNpi+P//PwMIA4E9EG8E4idQDGLbw+WhiiqA+D8OXAFVAzbp
+DxBvB2JLIGaGYkuoGEjOhhFIHAbij0BdPgxYACMj42ogJQpifwBiXSDeC8JIbt4LxSC5DyxQjTeB
++BeaYb+Q5EBOAVutCzMJHUNNPADzzDokiYdAfAmJvwLkGeTgWQfyKZICS6hYBTwc0QL8ORSjBDhA
+gAEAOg13B6R/SAgAAAAASUVORK5CYII=
+					</code>
+				</div>
+			</div>
+
+			<a id="selfclientscharges" name="selfclientscharges" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Client Charges</h4>
+
+					<p>The <i>list</i> capability of client charges supports <b>pagination</b>.</p>
+					<h5>Optional Arguments</h5>
+					<dl class="argument-list">
+						<dt>offset</dt>
+						<dd>
+							Integer <span>Mandatory</span>, defaults to 0
+						</dd>
+						<dd>Indicates the result from which pagination starts</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>Mandatory</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+
+						<dt>pendingPayment</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd> Filters charges that are pending payment (neither paid or waived).</dd>
+
+						<dt>chargeStatus</dt>
+						<dd>
+							String <span>optional</span>
+						</dd>
+						<dd> Filters charges according to the status.</dd>
+          			</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/clients/1/charges</div>
+					<div class=apiClick>self/clients/1/charges?offset=0&limit=5</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/clients/{clientId}/charges?limit=5&offset=0
+					</code>
+					<code class="method-request">
+GET self/clients/1/charges?limit=5&offset=0
+Content-Type: application/json
+No Request Body
+            		</code>
+					<code class="method-response">
+{
+  "totalFilteredRecords": 4,
+  "pageItems": [
+    {
+      "id": 5,
+      "clientId": 1,
+      "chargeId": 6,
+      "name": "Client Fees 2",
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "dueDate": [
+        2015,
+        9,
+        1
+      ],
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 550.000000,
+      "amountPaid": 0.000000,
+      "amountWaived": 0.000000,
+      "amountWrittenOff": 0,
+      "amountOutstanding": 550.000000,
+      "penalty": false,
+      "isActive": true,
+      "isPaid": false,
+      "isWaived": false
+    },
+    {
+      "id": 4,
+      "clientId": 1,
+      "chargeId": 5,
+      "name": "Client Fee 1",
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "dueDate": [
+        2015,
+        8,
+        31
+      ],
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 120.000000,
+      "amountPaid": 0.000000,
+      "amountWaived": 120.000000,
+      "amountWrittenOff": 0,
+      "amountOutstanding": 120.000000,
+      "penalty": true,
+      "isActive": true,
+      "isPaid": false,
+      "isWaived": true
+    },
+    {
+      "id": 3,
+      "clientId": 1,
+      "chargeId": 5,
+      "name": "Client Fee 1",
+      "chargeTimeType": {
+        "id": 2,
+        "code": "chargeTimeType.specifiedDueDate",
+        "value": "Specified due date"
+      },
+      "dueDate": [
+        2015,
+        8,
+        17
+      ],
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 100.000000,
+      "amountPaid": 0.000000,
+      "amountWaived": 100.000000,
+      "amountWrittenOff": 0,
+      "amountOutstanding": 0.000000,
+      "penalty": true,
+      "isActive": true,
+      "isPaid": false,
+      "isWaived": true
+    },
+    {
+      "id": 2,
+      "clientId": 1,
+      "chargeId": 2,
+      "name": "Recurring savings Charge",
+      "chargeTimeType": {
+        "id": 7,
+        "code": "chargeTimeType.monthlyFee",
+        "value": "Monthly Fee"
+      },
+      "dueDate": [
+        2015,
+        8,
+        17
+      ],
+      "chargeCalculationType": {
+        "id": 1,
+        "code": "chargeCalculationType.flat",
+        "value": "Flat"
+      },
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 100.000000,
+      "amountPaid": 0,
+      "amountWaived": 100.000000,
+      "amountWrittenOff": 0,
+      "amountOutstanding": 0.000000,
+      "penalty": false,
+      "isActive": true,
+      "isPaid": false,
+      "isWaived": true
+    }
+  ]
+}
+
+					</code>
+				</div>
+			</div>
+					<a id="selfclientscharge" name="selfclientscharge" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Client Charge</h4>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>associations</dt>
+						<dd>
+							optional, <span>a comma separated list of
+								'associations' (itemised below).</span>
+						</dd>
+						<dd>
+							<br>Associations are just extra pieces of data that you
+							might or might not want to retrieve.<br>
+							<br>
+						</dd>
+						<dd>
+							<b>'transactions':</b> Retrieves all transactions made on this client charge.
+						</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/clients/1/charges/1</div>
+					<br>
+					<br>
+					<div class=apiClick>self/clients/1/charges/1?fields=name,id</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/clients/{clientId}/charges/{clientChargeId}
+					</code>
+					<code class="method-response">
+{
+  "id": 3,
+  "clientId": 1,
+  "chargeId": 5,
+  "name": "Client Fee 1",
+  "chargeTimeType": {
+    "id": 2,
+    "code": "chargeTimeType.specifiedDueDate",
+    "value": "Specified due date"
+  },
+  "dueDate": [
+    2015,
+    8,
+    17
+  ],
+  "chargeCalculationType": {
+    "id": 1,
+    "code": "chargeCalculationType.flat",
+    "value": "Flat"
+  },
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 100.000000,
+  "amountPaid": 0.000000,
+  "amountWaived": 100.000000,
+  "amountWrittenOff": 0,
+  "amountOutstanding": 0.000000,
+  "penalty": true,
+  "isActive": true,
+  "isPaid": false,
+  "isWaived": true
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="selfclienttransactions" name="selfclienttransactions" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Client Transactions</h4>
+					<p>The <i>list</i> capability of client transaction can support <b>pagination</b>.</p>
+					<h5>Mandatory Arguments</h5>
+					<dl class="argument-list">
+						<dt>offset</dt>
+						<dd>
+							Integer <span>Mandatory</span>, defaults to 0
+						</dd>
+						<dd>Indicates the result from which pagination starts</dd>
+
+						<dt>limit</dt>
+						<dd>
+							Integer <span>Mandatory</span>, defaults to 200
+						</dd>
+						<dd>Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1</dd>
+            		</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/clients/189/transactions</div>
+					<div class=apiClick>self/clients/189/transactions?offset=10&limit=50</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/clients/{clientId}/transactions?limit=5&offset=0
+					</code>
+					<code class="method-request">
+GET self/clients/226/transactions?limit=5&offset=0
+Content-Type: application/json
+No Request Body:
+
+			</code>
+			<code class="method-response">
+{
+  "totalFilteredRecords": 20,
+  "pageItems": [
+    {
+      "id": 226,
+      "officeId": 1,
+      "officeName": "Head Office",
+      "type": {
+        "id": 1,
+        "code": "clientTransactionType.payCharge",
+        "value": "PAY_CHARGE"
+      },
+      "date": [
+        2015,
+        9,
+        2
+      ],
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 22,
+      "submittedOnDate": [
+        2015,
+        9,
+        2
+      ],
+      "reversed": false
+    }
+  ]
+}
+</code>
+				</div>
+			</div>
+
+			<a id="selfclienttransaction" name="selfclienttransaction" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Client Transaction</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/clients/1/transactions/1</div>
+					<br>
+					<br>
+					<div class=apiClick>self/clients/1/transactions/1?fields=id,officeName</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/clients/{clientId}/transaction/{transactionId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "officeId": 1,
+  "officeName": "Head Office",
+  "type": {
+    "id": 1,
+    "code": "clientTransactionType.payCharge",
+    "value": "PAY_CHARGE"
+  },
+  "date": [
+    2015,
+    8,
+    17
+  ],
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 60.000000,
+  "submittedOnDate": [
+    2015,
+    8,
+    19
+  ],
+  "reversed": true
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="selfloan" name="selfloan" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Loan</h4>
+					<h5>Arguments</h5>
+					<dl class="argument-list">
+						<dt>associations</dt>
+						<dd>
+							optional, <span>a comma separated list of
+								loan 'associations' (itemised below).</span>
+						</dd>
+						<dd>
+							<br>Associations are just extra pieces of data that you
+							might or might not want to retrieve.<br>
+							<br>
+						</dd>
+						<dd>
+							<b>'repaymentSchedule':</b> Loan schedule data.
+						</dd>
+						<dd>
+							<b>'originalSchedule':</b> Loan schedule data without interest recalculations.
+						</dd>
+						<dd>
+							<b>'futureSchedule':</b> Loan schedule data from today date(will be displayed only for interest first repayment strategy processors)
+						</dd>
+						<dd>
+							<b>'transactions':</b> Loan transactions data.
+						</dd>
+						<dd>
+							<b>'charges':</b> Loan charges data.
+						</dd>
+						<dd>
+							<b>'guarantors':</b> Loan guarantors data.
+						</dd>
+						<dd>
+							<b>'collateral':</b> Loan collateral data.
+						</dd>
+						<dd>
+							<b>'linkedAccount':</b> Account linked to loan.
+						</dd>
+						<dd>
+							<b>'multiDisburseDetails':</b> Loan multi-disbursement details.
+						</dd>
+					</dl>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/loans/1</div>
+					<br>
+					<br>
+					<div class=apiClick>self/loans/1?fields=id,principal,annualInterestRate</div>
+					<br>
+					<br>
+					<div class=apiClick>self/loans/1?fields=id,principal,annualInterestRate&associations=repaymentSchedule,transactions</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/loans/{loanId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "accountNo": "000000001",
+  "status": {
+    "id": 300,
+    "code": "loanStatusType.active",
+    "value": "Active",
+    "pendingApproval": false,
+    "waitingForDisbursal": false,
+    "active": true,
+    "closedObligationsMet": false,
+    "closedWrittenOff": false,
+    "closedRescheduled": false,
+    "closed": false,
+    "overpaid": false
+  },
+  "clientId": 1,
+  "clientName": "Kampala first Client",
+  "clientOfficeId": 2,
+  "loanProductId": 1,
+  "loanProductName": "Kampala Product (with cash accounting)",
+  "loanProductDescription": "Typical Kampala loan product with cash accounting enabled for testing.",
+  "loanPurposeId": 22,
+  "loanPurposeName": "option.HousingImprovement",
+  "loanOfficerId": 2,
+  "loanOfficerName": "LoanOfficer, Kampala",
+  "loanType": {
+    "id": 1,
+    "code": "loanType.individual",
+    "value": "Individual"
+  },
+  "currency": {
+    "code": "UGX",
+    "name": "Uganda Shilling",
+    "decimalPlaces": 2,
+    "displaySymbol": "USh",
+    "nameCode": "currency.UGX",
+    "displayLabel": "Uganda Shilling (USh)"
+  },
+  "principal": 1000000,
+  "termFrequency": 12,
+  "termPeriodFrequencyType": {
+    "id": 2,
+    "code": "termFrequency.periodFrequencyType.months",
+    "value": "Months"
+  },
+  "numberOfRepayments": 12,
+  "repaymentEvery": 1,
+  "repaymentFrequencyType": {
+    "id": 2,
+    "code": "repaymentFrequency.periodFrequencyType.months",
+    "value": "Months"
+  },
+  "interestRatePerPeriod": 24,
+  "interestRateFrequencyType": {
+    "id": 3,
+    "code": "interestRateFrequency.periodFrequencyType.years",
+    "value": "Per year"
+  },
+  "annualInterestRate": 24,
+  "amortizationType": {
+    "id": 1,
+    "code": "amortizationType.equal.installments",
+    "value": "Equal installments"
+  },
+  "interestType": {
+    "id": 1,
+    "code": "interestType.flat",
+    "value": "Flat"
+  },
+  "interestCalculationPeriodType": {
+    "id": 1,
+    "code": "interestCalculationPeriodType.same.as.repayment.period",
+    "value": "Same as repayment period"
+  },
+  "transactionProcessingStrategyId": 2,
+  "timeline": {
+    "submittedOnDate": [
+      2012,
+      4,
+      3
+    ],
+    "submittedByUsername": "admin",
+    "submittedByFirstname": "App",
+    "submittedByLastname": "Administrator",
+    "approvedOnDate": [
+      2012,
+      4,
+      3
+    ],
+    "approvedByUsername": "admin",
+    "approvedByFirstname": "App",
+    "approvedByLastname": "Administrator",
+    "expectedDisbursementDate": [
+      2012,
+      4,
+      10
+    ],
+    "actualDisbursementDate": [
+      2012,
+      4,
+      10
+    ],
+    "disbursedByUsername": "admin",
+    "disbursedByFirstname": "App",
+    "disbursedByLastname": "Administrator",
+    "expectedMaturityDate": [
+      2013,
+      4,
+      10
+    ]
+  },
+  "summary": {
+    "currency": {
+      "code": "UGX",
+      "name": "Uganda Shilling",
+      "decimalPlaces": 2,
+      "displaySymbol": "USh",
+      "nameCode": "currency.UGX",
+      "displayLabel": "Uganda Shilling (USh)"
+    },
+    "principalDisbursed": 1000000,
+    "principalPaid": 0,
+    "principalWrittenOff": 0,
+    "principalOutstanding": 1000000,
+    "principalOverdue": 833333.3,
+    "interestCharged": 240000,
+    "interestPaid": 0,
+    "interestWaived": 0,
+    "interestWrittenOff": 0,
+    "interestOutstanding": 240000,
+    "interestOverdue": 200000,
+    "feeChargesCharged": 18000,
+    "feeChargesDueAtDisbursementCharged": 0,
+    "feeChargesPaid": 0,
+    "feeChargesWaived": 0,
+    "feeChargesWrittenOff": 0,
+    "feeChargesOutstanding": 18000,
+    "feeChargesOverdue": 15000,
+    "penaltyChargesCharged": 0,
+    "penaltyChargesPaid": 0,
+    "penaltyChargesWaived": 0,
+    "penaltyChargesWrittenOff": 0,
+    "penaltyChargesOutstanding": 0,
+    "penaltyChargesOverdue": 0,
+    "totalExpectedRepayment": 1258000,
+    "totalRepayment": 0,
+    "totalExpectedCostOfLoan": 258000,
+    "totalCostOfLoan": 0,
+    "totalWaived": 0,
+    "totalWrittenOff": 0,
+    "totalOutstanding": 1258000,
+    "totalOverdue": 1048333.3,
+    "overdueSinceDate": [
+      2012,
+      5,
+      10
+    ],
+    "linkedAccount":{
+    	"id":1,
+    	"accountNo":"000000001"
+    },
+    "disbursementDetails":[{"id":71,"expectedDisbursementDate":[2013,11,1],"principal":22000.000000,"approvedPrincipal":22000.000000}],
+    "fixedEmiAmount":1100.000000,
+    "maxOutstandingLoanBalance":35000,
+    "canDisburse":false,
+    "emiAmountVariations": [],
+  "inArrears": true,
+  "isNPA":false,
+  "overdueCharges": [
+    {
+      "id": 20,
+      "name": "overdraft penality",
+      "active": true,
+      "penalty": true,
+      "currency": {
+        "code": "USD",
+        "name": "US Dollar",
+        "decimalPlaces": 2,
+        "displaySymbol": "$",
+        "nameCode": "currency.USD",
+        "displayLabel": "US Dollar ($)"
+      },
+      "amount": 3.000000,
+      "chargeTimeType": {
+        "id": 9,
+        "code": "chargeTimeType.overdueInstallment",
+        "value": "overdue fees"
+      },
+      "chargeAppliesTo": {
+        "id": 1,
+        "code": "chargeAppliesTo.loan",
+        "value": "Loan"
+      },
+      "chargeCalculationType": {
+        "id": 2,
+        "code": "chargeCalculationType.percent.of.amount",
+        "value": "% Amount"
+      },
+      "chargePaymentMode": {
+        "id": 0,
+        "code": "chargepaymentmode.regular",
+        "value": "Regular"
+      },
+      "feeInterval": 2,
+      "feeFrequency": {
+        "id": 1,
+        "code": "feeFrequencyperiodFrequencyType.weeks",
+        "value": "Weeks"
+      }
+    }
+  ]
+  }
+}
+					</code>
+				</div>
+			</div>
+
+            <a id="selfloantransaction" name="selfloantransaction" class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Loan Transaction Details</h4>
+					<p>Example Request:</p>
+					<div class=apiClick>self/loans/5/transactions/3</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/loans/{loanId}/transactions/{transactionId}
+					</code>
+					<code class="method-response">
+{
+  "id": 3,
+  "type": {
+    "id": 2,
+    "code": "loanTransactionType.repayment",
+    "value": "Repayment",
+    "disbursement": false,
+    "repaymentAtDisbursement": false,
+    "repayment": true,
+    "contra": false,
+    "waiveInterest": false,
+    "waiveCharges": false,
+    "writeOff": false,
+    "recoveryRepayment": false
+  },
+  "date": [
+    2012,
+    5,
+    14
+  ],
+  "manuallyReversed": false,
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 559.88,
+  "interestPortion": 559.88
+}
+					</code>
+				</div>
+			</div>
+
+			<a id="selfloancharges" name="selfloancharges"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Loan Charges</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/loans/1/charges</div>
+					<br>
+					<br>
+					<div class=apiClick>self/loans/1/charges?fields=name,amountOrPercentage</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/loans/{loanId}/charges
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "chargeId": 1,
+    "name": "Loan Processing fee",
+    "chargeTimeType": {
+      "id": 1,
+      "code": "chargeTimeType.disbursement",
+      "value": "Disbursement"
+    },
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 100,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 100,
+    "amountOrPercentage": 100,
+    "penalty": false
+  },
+  {
+    "id": 7,
+    "chargeId": 2,
+    "name": "Collection Fee",
+    "chargeTimeType": {
+      "id": 2,
+      "code": "chargeTimeType.specifiedDueDate",
+      "value": "Specified due date"
+    },
+    "dueDate": [
+      2013,
+      3,
+      29
+    ],
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 100,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 100,
+    "amountOrPercentage": 100,
+    "penalty": false
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="selfloancharge" name="selfloancharge"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Loan Charge</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/loans/1/charges/1</div>
+					<br>
+					<br>
+					<div class=apiClick>self/loans/1/charges/1?fields=name,amountOrPercentage</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/loans/{loanId}/charges/{chargeId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "chargeId": 1,
+  "name": "Loan Processing fee",
+  "chargeTimeType": {
+    "id": 1,
+    "code": "chargeTimeType.disbursement",
+    "value": "Disbursement"
+  },
+  "chargeCalculationType": {
+    "id": 1,
+    "code": "chargeCalculationType.flat",
+    "value": "Flat"
+  },
+  "percentage": 0,
+  "amountPercentageAppliedTo": 0,
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 100,
+  "amountPaid": 0,
+  "amountWaived": 0,
+  "amountWrittenOff": 0,
+  "amountOutstanding": 100,
+  "amountOrPercentage": 100,
+  "penalty": false
+}			</code>
+				</div>
+			</div>
+
+	<a id="selfsavings" name="selfsavings" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Retrieve a savings account:</h4>
+	        <h4>Arguments</h4>
+	        <dl class="argument-list">
+	            <dt>associations</dt>
+	            <dd>optional, <span>a comma separated list of savings 'associations' (itemized below).</span></dd>
+	            <dd><br>Associations are just extra pieces of data that you might or might not want to retrieve.<br><br></dd>
+				<dd><b>'transactions':</b> Gets data related to transactions on the account e.g. ?associations=transactions</dd>
+				<dd><b>'charges':</b>Savings Account charges data.</dd>
+	        </dl>
+	        <p>Example Requests : </p>
+	        <div class=apiClick>self/savingsaccounts/1</div>
+	        <br><br>
+	        <div class=apiClick>self/savingsaccounts/1?associations=transactions</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/self/savingsaccounts/{accountId}</code>
+	        <code class="method-response">
+{
+  "id": 1,
+  "accountNo": "000000001",
+  "clientId": 1,
+  "clientName": "small business",
+  "savingsProductId": 1,
+  "savingsProductName": "Passbook Savings",
+  "fieldOfficerId": 0,
+  "status": {
+    "id": 100,
+    "code": "savingsAccountStatusType.submitted.and.pending.approval",
+    "value": "Submitted and pending approval",
+    "submittedAndPendingApproval": true,
+    "approved": false,
+    "rejected": false,
+    "withdrawnByApplicant": false,
+    "active": false,
+    "closed": false
+  },
+  "timeline": {
+    "submittedOnDate": [
+      2013,
+      3,
+      1
+    ]
+  },
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "nominalAnnualInterestRate": 5,
+  "interestCompoundingPeriodType": {
+    "id": 1,
+    "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily",
+    "value": "Daily"
+  },
+  "interestPostingPeriodType": {
+    "id": 4,
+    "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly",
+    "value": "Monthly"
+  },
+  "interestCalculationType": {
+    "id": 1,
+    "code": "savingsInterestCalculationType.dailybalance",
+    "value": "Daily Balance"
+  },
+  "interestCalculationDaysInYearType": {
+    "id": 365,
+    "code": "savingsInterestCalculationDaysInYearType.days365",
+    "value": "365 Days"
+  },
+  "summary": {
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "accountBalance": 0
+  }
+}
+	        </code>
+	    </div>
+	</div>
+
+<a id="selfsavingstransaction" name="selfsavingstransaction" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h4>Retrieve Savings Account Transaction:</h4>
+	        <p>Example Requests: </p>
+	        <div class=apiClick>self/savingsaccounts/1/transactions/1</div>
+	        <br/>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/self/savingsaccounts/{accountId}/transactions/{transactionId}
+	        </code>
+			<code class="method-response">
+{
+  "id": 1,
+  "transactionType": {
+    "id": 2,
+    "code": "savingsAccountTransactionType.withdrawal",
+    "value": "Withdrawal",
+    "deposit": false,
+    "withdrawal": true,
+    "interestPosting": false,
+    "feeDeduction": false
+  },
+  "accountId": 1,
+  "accountNo": "000000001",
+  "date": [
+    2013,
+    8,
+    7
+  ],
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "inMultiplesOf": 0,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "paymentDetailData": {
+    "id": 62,
+    "paymentType": {
+      "id": 11,
+      "name": "cash"
+    },
+    "accountNumber": "",
+    "checkNumber": "",
+    "routingCode": "",
+    "receiptNumber": "",
+    "bankNumber": ""
+  },
+  "amount": 5000,
+  "runningBalance": 0,
+  "reversed": true
+}
+</code>
+	    </div>
+	</div>
+
+			<a id="selfsavingscharges" name="selfsavingscharges"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>List Savings Charges</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/savingsaccounts/1/charges</div>
+					<br>
+					<div class=apiClick>self/savingsaccounts/1/charges?chargeStatus=inactive</div>
+					<br>
+					<div class=apiClick>self/savingsaccounts/1/charges?fields=name,amountOrPercentage</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/savingsaccounts/{accountId}/charges
+					</code>
+					<code class="method-response">
+[
+  {
+    "id": 1,
+    "chargeId": 3,
+    "accountId": 57,
+    "name": "Savings account maintenance fee",
+    "chargeTimeType": {
+      "id": 1,
+      "code": "chargeTimeType.specifiedDueDate",
+      "value": "Specified due date"
+    },
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 100,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 100,
+    "amountOrPercentage": 100,
+    "penalty": false
+  },
+  {
+    "id": 2,
+    "chargeId": 4,
+    "accountId": 57,
+    "name": "Pass book Fee",
+    "chargeTimeType": {
+      "id": 2,
+      "code": "chargeTimeType.specifiedDueDate",
+      "value": "Specified due date"
+    },
+    "dueDate": [
+      2013,
+      3,
+      29
+    ],
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 100,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 100,
+    "amountOrPercentage": 100,
+    "penalty": false
+  },
+  {
+    "id": 9,
+    "chargeId": 4,
+    "accountId": 57,
+    "name": "Withdrawal fee percentage",
+    "chargeTimeType": {
+      "id": 5,
+      "code": "chargeTimeType.withdrawalFee",
+      "value": "Withdrawal Fee"
+    },
+    "chargeCalculationType": {
+      "id": 2,
+      "code": "chargeCalculationType.percent.of.amount",
+      "value": "% Amount"
+    },
+    "percentage": 0.25,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 0,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 0,
+    "amountOrPercentage": 0.25,
+    "penalty": false
+  },
+  {
+    "id": 10,
+    "chargeId": 6,
+    "accountId": 57,
+    "name": "Annual fee - INR",
+    "chargeTimeType": {
+      "id": 6,
+      "code": "chargeTimeType.annualFee",
+      "value": "Annual Fee"
+    },
+    "feeOnMonthDay": [
+      10,
+      9
+    ],
+    "chargeCalculationType": {
+      "id": 1,
+      "code": "chargeCalculationType.flat",
+      "value": "Flat"
+    },
+    "percentage": 0,
+    "amountPercentageAppliedTo": 0,
+    "currency": {
+      "code": "USD",
+      "name": "US Dollar",
+      "decimalPlaces": 2,
+      "displaySymbol": "$",
+      "nameCode": "currency.USD",
+      "displayLabel": "US Dollar ($)"
+    },
+    "amount": 50,
+    "amountPaid": 0,
+    "amountWaived": 0,
+    "amountWrittenOff": 0,
+    "amountOutstanding": 50,
+    "amountOrPercentage": 50,
+    "penalty": false
+  }
+]
+					</code>
+				</div>
+			</div>
+
+			<a id="selfsavingscharge" name="selfsavingscharge"
+				class="old-syle-anchor">&nbsp;</a>
+			<div class="method-section">
+				<div class="method-description">
+					<h4>Retrieve a Savings account Charge</h4>
+					<p>Example Requests:</p>
+					<div class=apiClick>self/savingsaccounts/1/charges/5</div>
+					<br>
+					<br>
+					<div class=apiClick>self/savingsaccounts/1/charges/5?fields=name,amountOrPercentage</div>
+				</div>
+				<div class="method-example">
+					<code class="method-declaration">
+GET https://DomainName/api/v1/self/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}
+					</code>
+					<code class="method-response">
+{
+  "id": 1,
+  "chargeId": 1,
+  "name": "Passbook fee",
+  "chargeTimeType": {
+    "id": 1,
+    "code": "chargeTimeType.specifiedDueDate",
+    "value": "Specified due date"
+  },
+  "chargeCalculationType": {
+    "id": 1,
+    "code": "chargeCalculationType.flat",
+    "value": "Flat"
+  },
+  "percentage": 0,
+  "amountPercentageAppliedTo": 0,
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "decimalPlaces": 2,
+    "displaySymbol": "$",
+    "nameCode": "currency.USD",
+    "displayLabel": "US Dollar ($)"
+  },
+  "amount": 100,
+  "amountPaid": 0,
+  "amountWaived": 0,
+  "amountWrittenOff": 0,
+  "amountOutstanding": 100,
+  "amountOrPercentage": 100,
+  "penalty": false
+}			</code>
+				</div>
+			</div>
+
+	<a id="selftransfertemplate" name="selftransfertemplate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Retrieve Account Transfer Template</h2>
+	        <p>Returns list of loan/savings accounts that can be used for account transfer</p>
+	        <p>Example Requests:</p>
+	        <div class=apiClick>self/accounttransfers/template</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/self/accounttransfers/template
+	        </code>
+			<code class="method-response">
+{
+  "accountOptions": [
+	{
+		"accountId": 1,
+		"accountNo": "00000001",
+		"accountType": {
+			"id": 2,
+			"code": "accountType.savings",
+			"value": "Savings Account"
+		},
+		"clientId": 1,
+		"clientName": "ABC",
+		"officeId": 1,
+		"officeName": "HEAD OFFICE"
+	},
+	{
+		"accountId": 5,
+		"accountNo": "00000005",
+		"accountType": {
+			"id": 1,
+			"code": "accountType.loan",
+			"value": "Loan Account"
+		},
+		"clientId": 2,
+		"clientName": "XYZ",
+		"officeId": 3,
+		"officeName": "REGIONAL OFFICE"
+	}
+  ]
+}
+			</code>
+	    </div>
+	</div>
+
+	<a id="selftransfer" name="selftransfer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Create new Transfer</h2>
+	        <p>Ability to create new transfer of monetary funds from one account to another.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://DomainName/api/v1/self/accounttransfers</code>
+	        <code class="method-request">POST self/accounttransfers/
+Content-Type: application/json
+No Request Body:
+{
+"fromOfficeId": 1,
+"fromClientId": 1,
+"fromAccountType": 2,
+"fromAccountId": 1,
+"toOfficeId": 1,
+"toClientId": 1,
+"toAccountType": 2,
+"toAccountId": 2,
+"dateFormat": "dd MMMM yyyy",
+"locale": "en",
+"transferDate": "01 August 2011",
+"transferAmount": "112.45",
+"transferDescription": "A description of the transfer"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
+
+			<!-- end of Customer Self Service APIs-->
+        </div>
+		<!-- main-content-wrapper -->
+
+		<a id="fullapi_matrix" name="fullapi_matrix" class="old-syle-anchor">&nbsp;</a>
+		<br>
+		<br>
+		<br>
+		<div class=fullMatrixOuter>
+			<table width="80%">
+				<tr>
+					<td><h3>
+							Full API Matrix
+							</h3></td>
+				</tr>
+			</table>
+			<table class=matrixHeading>
+				<tr class="matrixHeadingBG">
+					<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+							<table>
+								<tr>
+									<td>RESOURCES</td>
+								</tr>
+							</table>
+						</div></td>
+					<td><table>
+							<tr>
+								<td><div class="matrixHeadingVerbs">POST</div></td>
+							</tr>
+							<tr>
+								<td><div class=matrixHeadingCommands>create/complex update</div></td>
+							</tr>
+						</table></td>
+					<td><table>
+							<tr>
+								<td><div class="matrixHeadingVerbs">GET</div></td>
+							</tr>
+							<tr>
+								<td><div class=matrixHeadingCommands>read</div></td>
+							</tr>
+						</table></td>
+					<td><table>
+							<tr>
+								<td><div class="matrixHeadingVerbs">PUT</div></td>
+							</tr>
+							<tr>
+								<td><div class=matrixHeadingCommands>update</div></td>
+							</tr>
+						</table></td>
+					<td><table>
+							<tr>
+								<td><div class="matrixHeadingVerbs">DELETE</div></td>
+							</tr>
+							<tr>
+								<td><div class=matrixHeadingCommands>delete</div></td>
+							</tr>
+						</table></td>
+				</tr>
+				<tr>
+					<td><a href="#clients">Clients</a></td>
+					<td>clients</td>
+					<td><a href="#clients_create">Create a Client</a></td>
+					<td><a href="#clients_list">List Clients</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/template</td>
+					<td></td>
+					<td><a href="#clients_template">Retrieve Client Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/{clientId}</td>
+					<td></td>
+					<td><a href="#clients_retrieve">Retrieve a Client</a></td>
+					<td><a href="#clients_update">Update a Client</a></td>
+					<td><a href="#clients_delete">Delete a Client</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/{clientId}?command=activate</td>
+					<td><a href="#clients_activate">Activate a Client</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/{clientId}?command=close</td>
+					<td><a href="#clients_close">Close a Client</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/{clientId}?command=assignStaff</td>
+					<td><a href="#clients_assignStaff">Assign Staff</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/{clientId}?command=unassignStaff</td>
+					<td><a href="#clients_unassignStaff">Unassign Staff</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/{clientId}/accounts</td>
+					<td></td>
+					<td><a href="#clients_loansummary">Retrieve client accounts overview</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/{clientId}?command=transfer</td>
+					<td><a href="#clients_transfer">Transfer Client</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/{clientId}?command=updateSavingsAccount</td>
+					<td><a href="#clients_updateSavingsAccount">Update Default Savings Account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#client_identifiers">Client Identifiers</a></td>
+					<td>clients/{clientId}/identifiers</td>
+					<td><a href="#client_identifiers_create">Create an Identifier for a Client</a></td>
+					<td><a href="#client_identifiers_list">List all Identifiers for a Client</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>clients/{clientId}/identifiers/{identifierId}</td>
+					<td></td>
+					<td><a href="#client_identifiers_retrieve">Retrieve a Client Identifier</a></td>
+					<td><a href="#client_identifiers_update">Update a Client Identifier</a></td>
+					<td><a href="#client_identifiers_delete">Delete a Client Identifier</a></td>
+				</tr>
+				<tr>
+					<td><a href="#client_images">Client Images</a></td>
+					<td>clients/{clientId}/images</td>
+					<td><a href="#client_images_create">Upload an Image for a Client (as  DATA URI)</a></td>
+					<td><a href="#client_images_retrieve">Get Client Image (DATA URI)</a></td>
+					<td><a href="#client_images_update">Update Client Image (DATA URI)</a></td>
+					<td><a href="#client_images_delete">Delete Client Image</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td></td>
+					<td><a href="#client_images_create_form">
+						Upload an Image for a Client (Multi-part form data)</a>
+					</td>
+					<td><a href="#client_images_retrieve_binary">Get Client Image (Binary file)</a></td>
+					<td><a href="#client_images_update_form">
+						Update Client Image (Multi-part form data)</a>
+					</td>
+					<td></td>
+				</tr>
+                <tr>
+                    <td><a href="#standinginstruction">Standing Instructions</a></td>
+                    <td>standinginstructions</td>
+                    <td><a href="#standinginstruction_create">Create Standing Instruction</a></td>
+                    <td><a href="#standinginstructions_list">List Standing Instructions</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>standinginstructions/{standingInstructionId}</td>
+                    <td></td>
+                    <td><a href="#standinginstructions_retrieve">Retrive a Standing Instruction</a></td>
+                    <td><a href="#standinginstruction_update">Update Standing Instruction</a></td>
+                    <td><a href="#standinginstruction_delete">Delete Standing Instruction(status change)</a></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>standinginstructionrunhistory</td>
+                    <td></td>
+                    <td><a href="#standinginstructions_history">Standinginstructions History</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>standinginstructions/template</td>
+                    <td></td>
+                    <td><a href="#standinginstruction_template">Retrive Standing Instruction Template</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td><a href="#accounttransfers">Account Transfers</a></td>
+                    <td>accounttransfers/</td>
+                    <td><a href="#accounttransfers_create">Create new Transfer</a></td>
+                    <td><a href="#accounttransfers_list">List Transfers</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>accounttransfers/template</td>
+                    <td></td>
+                    <td><a href="#accounttransfers_template">Retrieve Template</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>accounttransfers/{transferId}</td>
+                    <td></td>
+                    <td><a href="#accounttransfers_retrieve">Retrieve Transfer</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+				<tr>
+					<td><a href="#groups">Group</a></td>
+					<td>groups</td>
+					<td><a href="#groups_create">Create a Group</a></td>
+					<td><a href="#groups_list">List Groups</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+                    <td>groups/template</td>
+                    <td></td>
+                    <td><a href="#groups_template">Retrieve Group Template</a></td>
+                    <td></td>
+					<td></td>
+                </tr>
+				<tr>
+					<td></td>
+					<td>groups/{groupId}</td>
+					<td></td>
+					<td><a href="#groups_retrieve">Retrieve a Group</a></td>
+					<td><a href="#groups_update">Update a Group</a></td>
+					<td><a href="#groups_delete">Delete a Group</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>groups/{groupId}?command=activate</td>
+					<td><a href="#groups_activate">Activate a Group</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>groups/{groupId}?command=associateClients</td>
+					<td><a href="#groups_associate_clients">Associate Clients</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>groups/{groupId}?command=disassociateClients</td>
+					<td><a href="#groups_disassociate_clients">Disassociate Clients</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>groups/{groupId}/accounts</td>
+					<td></td>
+					<td><a href="#groups_accounts">Retrive Group accounts summary</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>groups/{groupId}?command=transferClients</td>
+					<td><a href="#groups_transfer_clients">Bulk Transfer Clients across Groups</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+        <tr>
+            <td></td>
+            <td>groups/{groupId}?command=generateCollectionSheet</td>
+            <td><a href="#groups_generate_collectionsheet">Generate Collection Sheet</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td>groups/{groupId}?command=saveCollectionSheet</td>
+            <td><a href="#groups_save_collectionsheet">Save Collection Sheet</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td>groups/{groupId}?command=unassignStaff</td>
+            <td><a href="#groups_unassignStaff">Unassign Staff</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td>groups/{groupId}?command=assignStaff</td>
+            <td><a href="#groups_assignStaff">Assign Staff</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td>groups/{groupId}?command=close</td>
+            <td><a href="#groups_close">Close Group</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td>groups/{groupId}?command=assignRole</td>
+            <td><a href="#groups_assignRole">Assign Role</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td>groups/{groupId}?command=unassignRole</td>
+            <td><a href="#groups_unassignRole">Unassign Role</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td>groups/{groupId}?command=updateRole</td>
+            <td><a href="#groups_updateRole">Update Role</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+          <tr>
+              <td><a href="#centers">Center</a></td>
+              <td>centers</td>
+              <td><a href="#centers_create">Create a Center</a></td>
+              <td><a href="#centers_list">List Centers</a></td>
+              <td></td>
+              <td></td>
+          </tr>
+          <tr>
+              <td></td>
+              <td>centers/template</td>
+              <td></td>
+              <td><a href="#centers_template">Retrieve Center Template</a></td>
+              <td></td>
+              <td></td>
+          </tr>
+          <tr>
+              <td></td>
+              <td>centers/{centerId}</td>
+              <td></td>
+              <td><a href="#centers_retrieve">Retrieve a Center</a></td>
+              <td><a href="#centers_update">Update a Center</a></td>
+              <td><a href="#centers_delete">Delete a Center</a></td>
+          </tr>
+          <tr>
+              <td></td>
+              <td>centers/{centerId}?command=activate</td>
+              <td><a href="#centers_activate">Activate a Center</a></td>
+              <td></td>
+              <td></td>
+              <td></td>
+          </tr>
+          <tr>
+            <td></td>
+            <td>centers/{centerId}?command=close</td>
+            <td><a href="#centers_close">Close Center</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+		<tr>
+			<td></td>
+			<td>centers/{centerId}?command=associateGroups</td>
+			<td><a href="#centers_associate_groups">Associate Groups</a></td>
+			<td></td>
+			<td></td>
+			<td></td>
+		</tr>
+		<tr>
+			<td></td>
+			<td>centers/{centerId}?command=disassociateGroups</td>
+			<td><a href="#centers_disassociate_groups">Disassociate Groups</a></td>
+			<td></td>
+			<td></td>
+			<td></td>
+		</tr>
+        <tr>
+					<td></td>
+					<td>centers/{centerId}/accounts</td>
+					<td></td>
+					<td><a href="#centers_accounts">Retrive Center accounts summary</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+        <tr>
+            <td></td>
+            <td>centers/{centerId}?command=generateCollectionSheet</td>
+            <td><a href="#centers_generate_collectionsheet">Generate Collection Sheet</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td>centers/{centerId}?command=saveCollectionSheet</td>
+            <td><a href="#centers_save_collectionsheet">Save Collection Sheet</a></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+				<tr>
+					<td><a href="#loanproducts">Loan Product</a></td>
+					<td>loanproducts</td>
+					<td><a href="#loanproducts_create">Create a Loan Product</a></td>
+					<td><a href="#loanproducts_list">List Loan Products</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loanproducts/template</td>
+					<td></td>
+					<td><a href="#loanproducts_template">Retrieve Loan Product Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loanproducts/{productId}</td>
+					<td></td>
+					<td><a href="#loanproducts_retrieve">Retrieve a Loan Product</a></td>
+					<td><a href="#loanproducts_update">Update a Loan Product</a></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#loanproductmix">Loan Product Mix</a></td>
+					<td>loanproducts/template?isProductMixTemplate=true</td>
+					<td></td>
+					<td><a href="#loanproductmix_template">Retrieve Loan Product Mix Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loanproducts?associations=productMixes</td>
+					<td></td>
+					<td><a href="#loanproductmix_list">List Loan Products Mix</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loanproducts/{productId}/productmix</td>
+					<td><a href="#loanproductmix_create">Create a Loan Product Mix</a></td>
+					<td><a href="#loanproductmix_retrieve">Retrieve Loan Products Mix</a></td>
+					<td><a href="#loanproductmix_update">Update Loan Product Mix</a></td>
+					<td><a href="#loanproductmix_delete">Delete Loan Products Mix</a></td>
+				</tr>
+				<tr>
+					<td><a href="#loans">Loans</a></td>
+					<td>loans?calculateLoanSchedule</td>
+					<td><a href="#loans_calculate">Calculate Loan Repayment Schedule</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans</td>
+					<td><a href="#loans_create">Submit a new Loan Application</a></td>
+					<td><a href="#loans_list">List Loans/Loan Applications</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/template?clientId={clientId}</td>
+					<td></td>
+					<td><a href="#loans_template">Retrieve Loan Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}</td>
+					<td></td>
+					<td><a href="#loans_retrieve">Retrieve a Loan</a></td>
+					<td></td>
+					<td><a href="#loans_delete">Delete a Loan Application</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=approve</td>
+					<td><a href="#loans_approve">Approve Loan Application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=undoApproval</td>
+					<td><a href="#loans_approve_undo">Undo Loan Application Approval</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=assignLoanOfficer</td>
+					<td><a href="#loans_assignLoanOfficer">Assign a Loan Officer</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=unassignLoanOfficer</td>
+					<td><a href="#loans_unassignLoanOfficer">Unassign a Loan Officer</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=reject</td>
+					<td><a href="#loans_reject">Reject Loan Application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=withdraw</td>
+					<td><a href="#loans_withdraw">Withdraw Loan Application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=disburse</td>
+					<td><a href="#loans_disburse">Disburse Loan</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=disburseToSavings</td>
+					<td><a href="#loans_disburse_to_savings">Disburse Loan To Savings Account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=undoDisbursal</td>
+					<td><a href="#loans_disburse_undo">Undo Loan Application Disbursal</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}?command=recoverGuarantees</td>
+					<td><a href="#loans_recoverguarantee">Recover From Guarantors</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}/transactions/template</td>
+					<td></td>
+					<td><a href="#loans_repaymenttemplate_etc">Retrieve Loan Transaction Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#loans_transactions">Loan Transactions</a></td>
+					<td>loans/{loanId}/transactions?command=repayment</td>
+					<td><a href="#loans_transaction_repayment">Enter a repayment</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}/transactions?command=waiveInterest</td>
+					<td><a href="#loans_transaction_waiveinterest">Waive Interest</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr><tr>
+					<td></td>
+					<td>loans/{loanId}/transactions?command=writeoff</td>
+					<td><a href="#loans_transaction_write-off_loan">Write-off Loan</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				</tr><tr>
+					<td></td>
+					<td>loans/{loanId}/transactions?command=recoverypayment</td>
+					<td><a href="#loans_transaction_recovery_payment">Make Recovery Payment</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				</tr><tr>
+					<td></td>
+					<td>loans/{loanId}/transactions?command=undowriteoff</td>
+					<td><a href="#loans_transaction_undo_write-off_loan">Undo Loan Write-off Transaction</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}/transactions/{transactionId}</td>
+					<td></td>
+					<td><a href="#loans_transaction_retrieve">Retrieve a transactions details</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}/transactions/{transactionId}</td>
+					<td><a href="#loans_transaction_adjust">Adjust a Transaction</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#loans_charges">Loan Charges</a></td>
+					<td>loans/{loanId}/charges</td>
+					<td><a href="#loans_charges_create">Add a Loan Charge</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}/charges/{loanChargeId}</td>
+					<td><a href="#loans_charges_pay">Pay Loan Charge from Linked Savings</a></td>
+					<td><a href="#loans_charges_retrieve">Retrieve a Loan Charge</a></td>
+					<td><a href="#loans_charges_update">Modify a Loan Charge</a></td>
+					<td><a href="#loans_charges_delete">Delete a Loan Charge</a></td>
+				</tr>
+				<tr>
+					<td><a href="#guarantors">Loan Guarantors</a></td>
+					<td>loans/{loanId}/guarantors</td>
+					<td><a href="#guarantors_create">Create a Guarantor
+					</a></td>
+					<td><a href="#guarantors_list">List Guarantors
+					</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}/guarantors/{guarantorId}</td>
+					<td></td>
+					<td><a href="#guarantors_retrieve">Retrieve a Guarantor</a></td>
+					<td><a href="#guarantors_update">Update a Guarantor</a></td>
+					<td><a href="#guarantors_delete">Delete a Guarantor</a></td>
+				</tr>
+				<tr>
+					<td><a href="#collaterals">Loan Collateral</a></td>
+					<td>loans/{loanId}/collaterals</td>
+					<td><a href="#collaterals_create">Create a Collateral
+					</a></td>
+					<td><a href="#collaterals_list">List collaterals
+					</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>loans/{loanId}/collaterals/{collateralId}</td>
+					<td></td>
+					<td><a href="#collaterals_retrieve">Retrieve a Collateral</a></td>
+					<td><a href="#collaterals_update">Update a Collateral</a></td>
+					<td><a href="#collaterals_delete">Delete a Collateral</a></td>
+				</tr>
+				<tr>
+					<td><a href="#loan_rescheduling">Loan Rescheduling</a></td>
+					<td>rescheduleloans</td>
+					<td><a href="#loan_reschedule_request_create">Create a Loan Reschedule Request</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>rescheduleloans/{requestId}</td>
+					<td></td>
+					<td><a href="#loan_reschedule_request_retrieve">Retrieve a Loan Reschedule Request</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>rescheduleloans/{requestId}?command=previewLoanReschedule</td>
+					<td></td>
+					<td><a href="#loan_reschedule_preview_retrieve">Retrieve a Preview of The New Loan Repayment Schedule</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>rescheduleloans/{requestId}?command=reject</td>
+					<td><a href="#loan_reschedule_request_reject">Reject Loan Reschedule Request</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>rescheduleloans/{requestId}?command=approve</td>
+					<td><a href="#loan_reschedule_request_approve">Approve Loan Reschedule Request</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#loan_term_variations">Loan Term Variations</a></td>
+					<td>/loans/{loanId}/schedule?command=calculateLoanSchedule</td>
+					<td><a href="#loans_calculate_with_exceptions">Calculate Schedule with Loan Term Variations</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>/loans/{loanId}/schedule?command=addVariations</td>
+					<td><a href="#loans_update_variations">Create Loan Term Variations</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>/loans/{loanId}/schedule?command=deleteVariations</td>
+					<td><a href="#loans_delete_variations">Remove All Loan Term Variations</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#glaccounts">General Ledger Account</a></td>
+					<td>glaccounts</td>
+					<td><a href="#glaccounts_create">Create a New Ledger Account</a></td>
+					<td><a href="#glaccounts_list">List Ledger Accounts</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>glaccounts/{glaccountId}</td>
+					<td></td>
+					<td><a href="#glaccounts_retrieve">Retrieve a Ledger Account</a></td>
+					<td><a href="#glaccounts_update">Update a Ledger Account</a></td>
+					<td><a href="#glaccounts_delete">Delete a Ledger Account</a></td>
+				</tr>
+				<tr>
+					<td><a href="#glclosures">Accounting Closure</a></td>
+					<td>glclosures</td>
+					<td><a href="#glclosures_create">Create an Accounting Closure</a></td>
+					<td><a href="#glclosures_list">List Accounting Closures</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>glclosures/{glclosureId}</td>
+					<td></td>
+					<td><a href="#glclosures_retrieve">Retrieve an Accounting Closure</a></td>
+					<td><a href="#glclosures_update">Update an Accounting Closure</a></td>
+					<td><a href="#glclosures_delete">Delete an Accounting Closure</a></td>
+				</tr>
+				<tr>
+					<td><a href="#journalentries">Journal Entries</a></td>
+					<td>journalentries</td>
+					<td><a href="#journalentries_create">Create Journal Entries</a></td>
+					<td><a href="#journalentries_list">List Journal Entries</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>journalentries?command=updateRunningBalance</td>
+					<td><a href="#journalentries_updatebalance">Update Running Balance for Journal Entries</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>journalentries/{entryId}</td>
+					<td></td>
+					<td><a href="#journalentries_retrieve">Retrieve a single Entry</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>journalentries/{transactionId}/reversal</td>
+					<td><a href="#journalentries_reverse">Reverse Journal Entries</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#accountingrules">Accounting Rules</a></td>
+					<td>accountingrules</td>
+					<td><a href="#accountingrules_create">Create a Accounting Rule</a></td>
+					<td><a href="#accountingrules_list">List Accounting Rules</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>accountingrules/{accountingrulesId}</td>
+					<td></td>
+					<td><a href="#accountingrules_retrieve">Retrieve a Accounting Rule</a></td>
+					<td><a href="#accountingrules_update">Update a Accounting Rule</a></td>
+					<td><a href="#accountingrules_delete">Delete a Accounting Rule</a></td>
+				</tr>
+                <tr>
+                    <td><a href="#reports">Report</a></td>
+                    <td>reports</td>
+                    <td><a href="#reports_create">Create a Report</a></td>
+                    <td><a href="#reports_list">List Reports</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+				<tr>
+					<td></td>
+					<td>reports/template</td>
+					<td></td>
+					<td><a href="#reports_template">Retrieve Report Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+                <tr>
+                    <td></td>
+                    <td>reports/{id}</td>
+                    <td></td>
+                    <td><a href="#reports_retrieve">Retrieve a Report</a></td>
+                    <td><a href="#reports_update">Update a Report</a></td>
+                    <td><a href="#reports_delete">Delete a Report</a></td>
+                </tr>
+                <tr>
+                    <td><a href="#runreports">Run Report</a></td>
+                    <td>runreports/{reportName}</td>
+                    <td></td>
+                    <td><a href="#report_run">Run a Report</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+                <tr>
+					<td><a href="#search">Search</a></td>
+					<td>search</td>
+					<td></td>
+					<td><a href="#search_resource">Search Resources</td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#offices">Office</a></td>
+					<td>offices</td>
+					<td><a href="#offices_create">Create an Office</a></td>
+					<td><a href="#offices_list">List Offices</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>offices/template</td>
+					<td></td>
+					<td><a href="#offices_template">Retrieve Office Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>offices/{officeId}</td>
+					<td></td>
+					<td><a href="#offices_retrieve">Retrieve an Office</a></td>
+					<td><a href="#offices_update">Update an Office</a></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#configs">Currency</a></td>
+					<td>currencies</td>
+					<td></td>
+					<td><a href="#configs_currencyretrieve">Retrieve Currency
+							Configuration</a></td>
+					<td><a href="#configs_currencyupdate">Update Currency
+							Configuration</a></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#funds">Funds</a></td>
+					<td>funds</td>
+					<td><a href="#funds_create">Create a Fund</a></td>
+					<td><a href="#funds_retrieve">List Funds</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>funds/{fundId}</td>
+					<td></td>
+					<td><a href="#fund_retrieve">Retrieve a Fund</a></td>
+					<td><a href="#fund_update">Update a Fund</a></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#staff">Staff</a></td>
+					<td>staff</td>
+					<td><a href="#staff_create">Create a Staff</a></td>
+					<td><a href="#staff_list">List Staff</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>staff/{staffId}</td>
+					<td></td>
+					<td><a href="#staff_retrieve">Retrieve a Staff Member</a></td>
+					<td><a href="#staff_update">Update a Staff Member</a></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#client_images">Staff Images</a></td>
+					<td>staff/{staffId}/images</td>
+					<td><a href="#client_images_create">Upload an Image for a Staff Member (as  DATA URI)</a></td>
+					<td><a href="#client_images_retrieve">Get Staff Image (DATA URI)</a></td>
+					<td><a href="#client_images_update">Update Staff Image (DATA URI)</a></td>
+					<td><a href="#client_images_delete">Delete Staff Image</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td></td>
+					<td><a href="#client_images_create_form">
+						Upload an Image for a Staff Member (Multi-part form data)</a>
+					</td>
+					<td><a href="#client_images_retrieve_binary">Get Staff Image (Binary file)</a></td>
+					<td><a href="#client_images_update_form">
+						Update Staff Image (Multi-part form data)</a>
+					</td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#charges">Charges</a></td>
+					<td>charges</td>
+					<td><a href="#charges_create">Create a Charge</a></td>
+					<td><a href="#charges_list">List Charges</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>charges/{chargeId}</td>
+					<td></td>
+					<td><a href="#charges_retrieve">Retrieve a Charge</a></td>
+					<td><a href="#charges_update">Update a Charge</a></td>
+					<td><a href="#charges_delete">Delete a Charge</a></td>
+				</tr>
+				<tr>
+					<td><a href="#datatables">Data Table</a></td>
+					<td>datatables</td>
+                    <td><a href="#datatables_createTable">Create Data Table</a></td>
+					<td><a href="#datatables_list">List Data Tables</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+                <tr>
+                    <td></td>
+                    <td>datatables/{datatable}</td>
+                    <td></td>
+                    <td><a href="#datatables_getTable">Retrieve Data Table Details</a></td>
+                    <td><a href="#datatables_updateTable">Update Data Table</a></td>
+                    <td><a href="#datatables_deleteTable">Delete Data Table</a></td>
+                </tr>
+				<tr>
+					<td></td>
+					<td>datatables/register/{datatable}/{apptable}</td>
+					<td><a href="#datatables_register">Register Data Table</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>datatables/deregister/{datatable}</td>
+					<td><a href="#datatables_deregister">Deregister Data Table</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>datatables/{datatable}/{apptableId}</td>
+					<td><a href="#datatables_create">Create Entry in Data
+							Table</a></td>
+					<td><a href="#datatables_retrieve">Retrieve Entry(s) from
+							Data Table</a></td>
+					<td><a href="#datatables_update">Update Entry in Data
+							Table (One to One)</a></td>
+					<td><a href="#datatables_delete">Delete Entry(s) in Data
+							Table</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>datatables/{datatable}/{apptableId}/{datatableId}</td>
+					<td></td>
+					<td><a href="#datatables_update_1M">Update Entry in Data
+							Table (One to Many)</a></td>
+					<td></td>
+					<td><a href="#datatables_delete_1M">Delete Entry in Data
+							Table (One to Many)</a></td>
+				</tr>
+				<tr>
+					<td><a href="#documents">Documents</a></td>
+					<td>{entityType}/{entityId}/documents</td>
+					<td><a href="#documents_create">Create a Document</td>
+					<td><a href="#documents_list">List All Document Details</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>{entityType}/{entityId}/documents/{documentId}</td>
+					<td></td>
+					<td><a href="#documents_retrieve">Retrieve a Documents Details</a></td>
+					<td><a href="#documents_update">Update a Document</a></td>
+					<td><a href="#documents_delete">Delete a Document</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>{entityType}/{entityId}/documents/{documentId}/attachment</td>
+					<td></td>
+					<td><a href="#documents_retrieve_file">Retrieve
+						binary file associated with a
+						Document</a>
+					</td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#notes">Notes</a></td>
+					<td>{resource}/{resourceId}/notes</td>
+					<td><a href="#resources_addnote">Add a Resource Note</td>
+					<td><a href="#resource_notelist">Retrieve a Resource's Notes</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>{resource}/{resourceId}/notes/{noteId}</td>
+					<td></td>
+					<td><a href="#resources_retrievenote">List All
+					Notes for a Resource</a></td>
+					<td><a href="#resources_updatenote">Update a Resource Note</a></td>
+					<td><a href="#resources_deletenote">Delete a Resource Note</a></td>
+				</tr>
+				<tr>
+					<td><a href="#authentication">Authentication</a></td>
+					<td>authentication</td>
+					<td><a href="#authenticate_request">Verify Authentication</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#users">User</a></td>
+					<td>users</td>
+					<td><a href="#users_create">Create a User</a></td>
+					<td><a href="#users_list">List Users</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>users/template</td>
+					<td></td>
+					<td><a href="#users_template">Retrieve User Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>users/{userId}</td>
+					<td></td>
+					<td><a href="#users_retrieve">Retrieve a User</a></td>
+					<td><a href="#users_update">Update a User</a></td>
+					<td><a href="#users_delete">Delete a User</a></td>
+				</tr>
+				<tr>
+					<td><a href="#roles">Role</a></td>
+					<td>roles</td>
+					<td><a href="#roles_create">Create a New Role</a></td>
+					<td><a href="#roles_list">List Roles</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>roles/{roleId}</td>
+					<td></td>
+					<td><a href="#roles_retrieve">Retrieve a Role</a></td>
+					<td><a href="#roles_update">Update a Role</a></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>roles/{roleId}/permissions</td>
+					<td></td>
+					<td><a href="#rolespermissions_retrieve">Retrieve a Role's
+							Permissions</a></td>
+					<td><a href="#rolespermissions_update">Update a Role's
+							Permissions</a></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#permissions">Permission</a></td>
+					<td>permissions</td>
+					<td></td>
+					<td><a href="#permissions_list">List Application Permissions</a></td>
+					<td><a href="#permissions_update">Enable/Disable Permissions for Maker Checker</a></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#configs_global">Global Configuration</a></td>
+					<td>configurations</td>
+					<td></td>
+					<td><a href="#configs_globalconfig_retrieve">List Global Configuration</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>configurations/{configId}</td>
+					<td></td>
+					<td><a href="#configs_globalconfig_retrieve_one">Retrieve a Global Configuration</a></td>
+					<td><a href="#configs_globalconfig_update">Update Global Configuration</a></td>
+					<td></td>
+				</tr>
+				<tr class="alt">
+                     <td></td>
+                     <td></td>
+                     <td></td>
+                     <td><a href="#configs_globalconfig_retrieve_survey">Retrieve Surveys Global Configuration</a></td>
+                     <td></td>
+                     <td></td>
+                </tr>
+				<tr>
+					<td><a href="#configs_codes">Codes</a></td>
+					<td>codes</td>
+					<td><a href="#configs_codes_create">Create a Code</a></td>
+					<td><a href="#configs_codes_retrieve">List Codes</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>codes/{codeId}</td>
+					<td></td>
+					<td><a href="#configs_code_retrieve">Retrieve a Code</a></td>
+					<td><a href="#configs_code_update">Update a Code</a></td>
+					<td><a href="#configs_code_delete">Delete a Code</a></td>
+				</tr>
+				<tr>
+					<td><a href="#configs_codes_codevalues">Code Values</a></td>
+					<td>codes/{codeId}/codevalues</td>
+					<td><a href="#configs_codes_codevalues_create">Create a Code Value</a></td>
+					<td><a href="#configs_codes_codevalues_list">List Code Values</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr >
+					<td></td>
+					<td>codes/{codeId}/codevalues/{codevalueId}</td>
+					<td></td>
+					<td><a href="#configs_codes_codevalues_retrieve">Retrieve a Code Value</a></td>
+					<td><a href="#configs_codes_codevalues_update">Update a Code Value</a></td>
+					<td><a href="#configs_codes_codevalues_delete">Delete a Code Value</a></td>
+				</tr>
+                <tr>
+                    <td><a href="#audits">Audits</a></td>
+                    <td>audits</td>
+                    <td></td>
+                    <td><a href="#audits_list">List Audits</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+				<tr>
+					<td></td>
+					<td>audits/searchtemplate</td>
+					<td></td>
+					<td><a href="#audits_searchtemplate">Retrieve Audit Search Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+                <tr>
+                    <td></td>
+                    <td>audits/{auditId}</td>
+                    <td></td>
+                    <td><a href="#audits_retrieve">Retrieve an Audit Entry</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td><a href="#makercheckers">Makercheckers</a></td>
+                    <td>makercheckers</td>
+                    <td></td>
+                    <td><a href="#makercheckers_list">List Maker Checker Entries</a></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+				<tr>
+					<td></td>
+					<td>makercheckers/searchtemplate</td>
+					<td></td>
+					<td><a href="#makercheckers_searchtemplate">Retrieve Maker Checker Search Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+                <tr>
+                    <td></td>
+                    <td>makercheckers/{auditId}</td>
+                    <td></td>
+                    <td></td>
+                    <td></td>
+                    <td><a href="#makercheckers_delete">Delete Maker Checker Entry</a></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>makercheckers/{auditId}?command=approve</td>
+                    <td><a href="#makercheckers_approve">Approve Maker Checker Entry</a></td>
+                    <td></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+    			<tr>
+                    <td></td>
+                    <td>makercheckers/{auditId}?command=reject</td>
+                    <td><a href="#makercheckers_reject">Reject Maker Checker Entry</a></td>
+                    <td></td>
+                    <td></td>
+                    <td></td>
+                </tr>
+            <tr>
+				<td><a href="#scheduler_jobs">Scheduler jobs</a></td>
+				<td>jobs</td>
+				<td></td>
+				<td><a href="#scheduler_jobs_list">List Scheduler jobs</a></td>
+				<td></td>
+				<td></td>
+			</tr>
+			<tr>
+				<td></td>
+				<td>jobs/{jobId}</td>
+				<td></td>
+				<td><a href="#retrieve_scheduler_job">Retrieve a job</a></td>
+				<td><a href="#update_scheduler_job">Update a job</a></td>
+				<td></td>
+			</tr>
+			<tr>
+				<td></td>
+				<td>jobs/{jobId}?command=executeJob</td>
+				<td><a href="#run_job">Run a job</a></td>
+				<td></td>
+				<td></td>
+				<td></td>
+			</tr>
+			<tr>
+				<td></td>
+				<td>jobs/{jobid}/runhistory?offset=0&limit=200</td>
+				<td></td>
+				<td><a href="#retrieve_job_runhistory">Retrieve job history</a></td>
+				<td></td>
+				<td></td>
+			</tr>
+			<tr>
+				<td></td>
+				<td>scheduler</td>
+				<td></td>
+				<td><a href="#retrieve_scheduler_status">Retrieve scheduler status</a></td>
+				<td></td>
+				<td></td>
+			</tr>
+			<tr>
+				<td></td>
+				<td>scheduler?command=start</td>
+				<td><a href="#activate_scheduler">Activate a scheduler service</a></td>
+				<td></td>
+				<td></td>
+				<td></td>
+			</tr>
+			<tr>
+				<td></td>
+				<td>scheduler?command=stop</td>
+				<td><a href="#suspend_scheduler">Suspend a scheduler service</td>
+				<td></td>
+				<td></td>
+				<td></td>
+			</tr>
+				<tr>
+					<td><a href="#savingsproducts">Savings Product</a></td>
+					<td>savingsproducts</td>
+					<td><a href="#savingsproducts_create">Create a Savings product</a></td>
+					<td><a href="#savingsproducts_list">List Savings products</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingproducts/template</td>
+					<td></td>
+					<td><a href="#savingsproducts_template">Retrieve Savings Product Template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsproducts/{productId}</td>
+					<td></td>
+					<td><a href="#savingsproducts_retrieve">Retrieve a savings product</a></td>
+					<td><a href="#savingsproducts_update">Update a savings product</a></td>
+					<td><a href="#savingsproducts_delete">Delete a savings product</a></td>
+				</tr>
+				<tr>
+					<td><a href="#savingsaccounts">Savings Accounts</a></td>
+					<td>savingsaccounts</td>
+					<td><a href="#savingsaccounts_create">Submit new savings application</a></td>
+					<td><a href="#savingsaccounts_list">List savings application/accounts</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/template?clientId={clientId}</td>
+					<td></td>
+					<td><a href="#savingsaccounts_template">Retrieve savings template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}</td>
+					<td></td>
+					<td><a href="#savingsaccounts_retrieve">Retrieve a savings application/account</a></td>
+					<td><a href="#savingsaccounts_update">Modify a savings application</a></td>
+					<td><a href="#savingsaccounts_delete">Delete a savings application</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=approve</td>
+					<td><a href="#savingsaccounts_approve">Approve a savings application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=undoApproval</td>
+					<td><a href="#savingsaccounts_undoapproval">Undo savings application approval</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=assignSavingsOfficer</td>
+					<td><a href="#savingsaccounts_assignSavingsOfficer">Assign Savings Officer</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=unassignSavingsOfficer</td>
+					<td><a href="#savingsaccounts_unassignSavingsOfficer">Unassign Savings Officer</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=reject</td>
+					<td><a href="#savingsaccounts_reject">Reject a savings application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=withdraw</td>
+					<td><a href="#savingsaccounts_withdrawbyapplicant">Withdraw savings application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=activate</td>
+					<td><a href="#savingsaccounts_activate">Activate a savings account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=close</td>
+					<td><a href="#savingsaccounts_close">Close a savings account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=calculateInterest</td>
+					<td><a href="#savingsaccounts_calculate_interest">Calculate interest on a savings account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}?command=postInterest</td>
+					<td><a href="#savingsaccounts_post_interest">Post interest on a savings account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#savingsaccounts_transactions">Savings Transactions</a></td>
+					<td>savingsaccounts/{accountId}/transactions?command=deposit</td>
+					<td><a href="#savingsaccounts_deposit">Make a deposit</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}/transactions?command=withdrawal</td>
+					<td><a href="#savingsaccounts_withdrawal">Make a withdrawal</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}/transactions/{transactionId}?command=undo</td>
+					<td><a href="#savingsaccounts_undotransaction">Undo transaction</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}/transactions/{transactionId}?command=modify</td>
+					<td><a href="#savingsaccounts_adjusttransaction">Adjust transaction</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}/transactions/template</td>
+					<td></td>
+					<td><a href="#savingsaccounts_transactions_template">Retrieve savings account transaction template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}/transactions/{transactionId}</td>
+					<td></td>
+					<td><a href="#savingsaccounts_transaction">Retrieve savings account transaction</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#savings_charges">Savings Account Charges</a></td>
+					<td>savingsaccounts/{accountId}/charges</td>
+					<td><a href="#savings_charges_create">Add a Savings Account Charge</a></td>
+					<td><a href="#savings_charges_list">List Savings Account Charges</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}</td>
+					<td></td>
+					<td><a href="#savings_charges_retrieve">Retrieve a Savings Account Charge</a></td>
+					<td><a href="#savings_charges_update">Modify a Savings Account Charge</a></td>
+					<td><a href="#savings_charges_delete">Delete a Savings Account Charge</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=paycharge</td>
+					<td><a href="#savings_charges_pay">Pay a Savings Account Charge</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=waive</td>
+					<td><a href="#savings_charges_waive">Waive a Savings Account Charge</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=inactivate</td>
+					<td><a href="#savings_charges_inactivate">Inactivate a Savings Account Charge</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#fdproducts">Fixed Deposit Product</a></td>
+					<td>fixeddepositproducts</td>
+					<td><a href="#fdproducts_create">Create a Fixed Deposit product</a></td>
+					<td><a href="#fdproducts_list">List Fixed Deposit products</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositproducts/{productId}</td>
+					<td></td>
+					<td><a href="#fdproducts_retrieve">Retrieve a Fixed Deposit product</a></td>
+					<td><a href="#fdproducts_update">Update a Fixed Deposit product</a></td>
+					<td><a href="#fdproducts_delete">Delete a Fixed Deposit product</a></td>
+				</tr>
+				<tr>
+					<td><a href="#fdaccounts">Fixed Deposit Accounts</a></td>
+					<td>fixeddepositaccounts</td>
+					<td><a href="#fdaccounts_create">Submit new fixed deposit application</a></td>
+					<td><a href="#fdaccounts_list">List fixed deposit application/accounts</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}</td>
+					<td></td>
+					<td><a href="#fdaccounts_retrieve">Retrieve a fixed deposit application/account</a></td>
+					<td><a href="#fdaccounts_update">Modify a fixed deposit application</a></td>
+					<td><a href="#fdaccounts_delete">Delete a fixed deposit application</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=approve</td>
+					<td><a href="#fdaccounts_approve">Approve a fixed deposit application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=undoApproval</td>
+					<td><a href="#fdaccounts_undoapproval">Undo fixed deposit application approval</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=reject</td>
+					<td><a href="#fdaccounts_reject">Reject a fixed deposit application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=withdraw</td>
+					<td><a href="#fdaccounts_withdrawbyapplicant">Withdraw fixed deposit application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=activate</td>
+					<td><a href="#fdaccounts_activate">Activate a fixed deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=close</td>
+					<td><a href="#fdaccounts_close">Close a fixed deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=prematureClose</td>
+					<td><a href="#fdaccounts_prematureclose">Premature Close a fixed deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=calculatePrematureAmount</td>
+					<td><a href="#fdaccounts_calculate_premature_amount">Calculate Premature amount on a fixed deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=calculateInterest</td>
+					<td><a href="#fdaccounts_calculate_interest">Calculate interest on a fixed deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>fixeddepositaccounts/{accountId}?command=postInterest</td>
+					<td><a href="#fdaccounts_post_interest">Post interest on a fixed deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#rdproducts">Recurring Deposit Product</a></td>
+					<td>recurringdepositproducts</td>
+					<td><a href="#rdproducts_create">Create a Recurring Deposit product</a></td>
+					<td><a href="#rdproducts_list">List Recurring Deposit products</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositproducts/{productId}</td>
+					<td></td>
+					<td><a href="#rdproducts_retrieve">Retrieve a Recurring Deposit product</a></td>
+					<td><a href="#rdproducts_update">Update a Recurring Deposit product</a></td>
+					<td><a href="#rdproducts_delete">Delete a Recurring Deposit product</a></td>
+				</tr>
+				<tr>
+					<td><a href="#rdaccounts">Recurring Deposit Accounts</a></td>
+					<td>recurringdepositaccounts</td>
+					<td><a href="#rdaccounts_create">Submit new recurring deposit application</a></td>
+					<td><a href="#rdaccounts_list">List recurring deposit application/accounts</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}</td>
+					<td></td>
+					<td><a href="#rdaccounts_retrieve">Retrieve a recurring deposit application/account</a></td>
+					<td><a href="#rdaccounts_update">Modify a recurring deposit application</a></td>
+					<td><a href="#rdaccounts_delete">Delete a recurring deposit application</a></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=approve</td>
+					<td><a href="#rdaccounts_approve">Approve a recurring deposit application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=undoApproval</td>
+					<td><a href="#rdaccounts_undoapproval">Undo recurring deposit application approval</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=reject</td>
+					<td><a href="#rdaccounts_reject">Reject a recurring deposit application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=withdraw</td>
+					<td><a href="#rdaccounts_withdrawbyapplicant">Withdraw recurring deposit application</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=activate</td>
+					<td><a href="#rdaccounts_activate">Activate a recurring deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=close</td>
+					<td><a href="#rdaccounts_close">Close a recurring deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=prematureClose</td>
+					<td><a href="#rdaccounts_prematureclose">Premature Close a recurring deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=calculatePrematureAmount</td>
+					<td><a href="#rdaccounts_calculate_premature_amount">Calculate Premature amount on a recurring deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=calculateInterest</td>
+					<td><a href="#rdaccounts_calculate_interest">Calculate interest on a recurring deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}?command=postInterest</td>
+					<td><a href="#rdaccounts_post_interest">Post interest on a recurring deposit account</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td><a href="#recurringdepositaccounts_transactions">Recurring Deposit Transactions</a></td>
+					<td>recurringdepositaccounts/{accountId}/transactions?command=deposit</td>
+					<td><a href="#recurringdepositaccounts_deposit">Make a deposit</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}/transactions?command=withdrawal</td>
+					<td><a href="#recurringdepositaccounts_withdrawal">Make a withdrawal</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}/transactions/{transactionId}?command=undo</td>
+					<td><a href="#recurringdepositaccounts_undotransaction">Undo transaction</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}/transactions/{transactionId}?command=modify</td>
+					<td><a href="#recurringdepositaccounts_adjusttransaction">Adjust transaction</a></td>
+					<td></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}/transactions/template</td>
+					<td></td>
+					<td><a href="#recurringdepositaccounts_transactions_template">Retrieve Deposit account transaction template</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>recurringdepositaccounts/{accountId}/transactions/{transactionId}</td>
+					<td></td>
+					<td><a href="#recurringdepositaccounts_transaction">Retrieve Deposit account transaction</a></td>
+					<td></td>
+					<td></td>
+				</tr>
+			</table>
+		</div>
+
+	<a id="betaapi_matrix" name="betaapi_matrix" class="old-syle-anchor">&nbsp;</a>
+	<div class=fullMatrixOuter>
+		<table width="80%">
+			<tr>
+				<td><h3>Beta API Matrix</h3></td>
+			</tr>
+		</table>
+			<table class=matrixHeading>
+				<tr class="matrixHeadingBG">
+					<td colspan=2 valign="top"><div class="matrixHeadingVerbs">
+							<table>
+								<tr>
+									<td>RESOURCES</td>
+								</tr>
+							</table>
+						</div></td>
+					<td><table>
+							<tr>
+								<td><div class="matrixHeadingVerbs">POST</div></td>
+							</tr>
+							<tr>
+								<td><div class=matrixHeadingCommands>create/complex update</div></td>
+							</tr>
+						</table></td>
+					<td><table>
+							<tr>
+								<td><div class="matrixHeadingVerbs">GET</div></td>
+							</tr>
+							<tr>
+								<td><div class=matrixHeadingCommands>read</div></td>
+							</tr>
+						</table></td>
+					<td><table>
+							<tr>
+								<td><div class="matrixHeadingVerbs">PUT</div></td>
+							</tr>
+							<tr>
+								<td><div class=matrixHeadingCommands>update</div></td>
+							</tr>
+						</table></td>
+					<td><table>
+							<tr>
+								<td><div class="matrixHeadingVerbs">DELETE</div></td>
+							</tr>
+							<tr>
+								<td><div class=matrixHeadingCommands>delete</div></td>
+							</tr>
+						</table>
+					</td>
+				</tr>
+				<tr>
+					<td><a href="#cache">Cache</a></td>
+					<td>caches</td>
+					<td></td>
+					<td><a href="#caches_list">List Cache Types</a></td>
+					<td><a href="#caches_switch">Switch Cache</a></td>
+					<td></td>
+				</tr>
+			</table>
+		</div>
+
+<a 	id="paymentapplicationlogic"
+	name="paymentapplicationlogic"
+	class="old-syle-anchor">&nbsp;</a>
+<div class="fullMatrixOuter">
+	<h3>Payment Application Logic / Transaction Processing Strategy</h3>
+
+<p>The way payments (transactions) are handled and the effect they have on the loan is an area that can differ widely from MFI to MFI. Mifos allows for <strong>transaction processing strategies</strong> to be developed that are associated with a loan product. Several approaches are provided out-of-the-box. If these approaches dont meet exactly your needs a custom approach can be developed and released with the platform.</p>
+
+<p>In general the the following areas are handled by any given <strong>transaction processing strategy</strong>:
+	<ul>
+		<li><strong>Payment order</strong>: What component of a loan installment should be paid of frist interest, principal, fees, penalties?</li>
+		<li><strong>Early payment</strong>: What qualifies a payment as an early payment and what impact should this have on the loan schedule?</li>
+		<li><strong>On time payment</strong>: What qualifies a payment as on time?</li>
+		<li><strong>Late payment</strong>: What qualifies a payment as a late payment and what impact should this have on the loan schedule?</li>
+	</ul>
+</p>
+
+	<h4>Penalties, Fees, Interest, Principal order</h4>
+	<p>
+		<strong>Payment order</strong>:
+		<ol>
+			<li>Overdue and due penalties</li>
+			<li>Overdue and due fees</li>
+			<li>Overdue and due interest</li>
+			<li>Overdue and due principal</li>
+		</ol>
+	</p>
+	<p><strong>Early payment</strong>: Any payment that pays off an installment that occurs before the due date of the installment. There is no incentive/advantage to making an early payment as theres no decrease in cost of the loan.</p>
+
+	<p><strong>On time payment</strong>: Any payment that pays off an installment that occurs exactly on the due date of the installment.</p>
+
+	<p><strong>Late payment</strong>: Any payment that pays off an installment that occurs after the due date of the installment. There is no penalization for late payment. The total cost of the loan does not automatically increase, no penalties are automatically applied to the loan.</p>
+
+	<h4>Principal Interest Penalties Fees Order</h4>
+	<p>The key aspect of this strategy is the payment order.</p>
+	<p>
+		<strong>Payment order</strong>:
+		<ol>
+			<li>Overdue and due principal</li>
+			<li>Overdue and due interest</li>
+			<li>Overdue and due penalties</li>
+			<li>Overdue and due fees</li>
+		</ol>
+	</p>
+	<p><strong>Early payment</strong>: Any payment that pays off an installment that occurs before the due date of the installment. There is no incentive/advantage to making an early payment as theres no decrease in cost of the loan.</p>
+
+	<p><strong>On time payment</strong>: Any payment that pays off an installment that occurs exactly on the due date of the installment.</p>
+
+	<p><strong>Late payment</strong>: Any payment that pays off an installment that occurs after the due date of the installment. There is no penalization for late payment. The total cost of the loan does not automatically increase, no penalties are automatically applied to the loan.</p>
+
+	<h4>Interest Principal Penalties Fees Order</h4>
+	<p>The key aspect of this strategy is the payment order.</p>
+	<p>
+		<strong>Payment order</strong>:
+		<ol>
+			<li>Overdue and due interest</li>
+			<li>Overdue and due principal</li>
+			<li>Overdue and due penalties</li>
+			<li>Overdue and due fees</li>
+		</ol>
+	</p>
+	<p><strong>Early payment</strong>: Any payment that pays off an installment that occurs before the due date of the installment. There is no incentive/advantage to making an early payment as theres no decrease in cost of the loan.</p>
+
+	<p><strong>On time payment</strong>: Any payment that pays off an installment that occurs exactly on the due date of the installment.</p>
+
+	<p><strong>Late payment</strong>: Any payment that pays off an installment that occurs after the due date of the installment. There is no penalization for late payment. The total cost of the loan does not automatically increase, no penalties are automatically applied to the loan.</p>
+
+	<h4>RBI (India)</h4>
+	<p>Per RBI regulations, all interest must be paid (both current and overdue) before principal is paid.</p>
+	<p>For example taking a loan with two installments due (one current and one overdue) of 220 each (200 principal + 20 interest) - A partial payment of 40 results in payment been broken up as 20 interest on installment #1 (200 principal remaining) and 20 payment to interest on installment #2 (200 principal remaining)</p>
+	<p>
+		<strong>Payment order</strong>:
+		<ol>
+			<li>Overdue and due interest</li>
+			<li>Overdue and due principal</li>
+			<li>Overdue and due penalties</li>
+			<li>Overdue and due fees</li>
+		</ol>
+	</p>
+	<p><strong>Early payment</strong>: Any payment that pays off an installment that occurs before the due date of the installment. There is no incentive/advantage to making an early payment as theres no decrease in cost of the loan.</p>
+
+	<p><strong>On time payment</strong>: Any payment that pays off an installment that occurs exactly on the due date of the installment.</p>
+
+	<p><strong>Late payment</strong>: Any payment that pays off an installment that occurs after the due date of the installment. There is no penalization for late payment. The total cost of the loan does not automatically increase, no penalties are automatically applied to the loan.</p>
+
+        <h4>Early Payment Strategy</h4>
+	<p>This strategy works similar to the Interest Principal Penalties Fees Order for on-time and late payments, i.e</p>
+	<p>
+		<strong>Payment order</strong>:
+		<ol>
+			<li>Overdue and due interest</li>
+			<li>Overdue and due principal</li>
+			<li>Overdue and due penalties</li>
+			<li>Overdue and due fees</li>
+		</ol>
+	</p>
+	<p><strong>Early payment</strong>: Any payment that pays off an installment that occurs before the due date of the installment. There is no incentive/advantage to making an early payment as theres no decrease in cost of the loan. However, early payments are allocated only towards principal payments of future installments. Interest and fees/penalties of future installments are not paid and may be waived
+	manually if required</p>
+
+	<p><strong>On time payment</strong>: Any payment that pays off an installment that occurs exactly on the due date of the installment.</p>
+
+	<p><strong>Late payment</strong>: Any payment that pays off an installment that occurs after the due date of the installment. There is no penalization for late payment. The total cost of the loan does not automatically increase, no penalties are automatically applied to the loan.</p>
+
+
+
+
+</div>
+
+	<!-- end of page-wrapper div -->
+	</div>
+
+
+	<script>
+		$(document).ready(function() {
+
+			$('.apiClick').bind('click', function() {
+				clickAPILink($(this).text());
+			});
+
+			$('.apiClickNotPretty').bind('click', function() {
+				clickAPILinkNotPretty($(this).text());
+			});
+
+			baseURL = getBaseURL(window.location.href);
+
+		});
+	</script>
+</body>
+</html>
diff --git a/api-docs/apidocs.css b/api-docs/apidocs.css
new file mode 100644
index 0000000..38b8b9f
--- /dev/null
+++ b/api-docs/apidocs.css
@@ -0,0 +1,748 @@
+.apiClick {
+	text-decoration: underline;
+	cursor: hand; 
+	cursor: pointer;
+	color: blue;
+}
+
+.apiClickNotPretty {
+    text-decoration: underline;
+    cursor: hand; 
+    cursor: pointer;
+    color: blue;
+}
+
+#jpw {
+	text-decoration: underline;
+	cursor: hand; 
+	cursor: pointer;
+	color: blue;
+}
+
+
+.tocMatrix {
+	font-size: 85%;
+}
+.fullMatrixOuter {
+	margin-right: 20px;
+	margin-left: 20px;
+	margin-bottom: 20px;
+}
+
+.matrixHeading { 
+	font-family: Helvetica, Arial, sans-serif;
+	border-collapse:collapse;
+	border: 3px solid #6eb4db;
+	width:100%;
+}
+
+.matrixHeading td
+{ 
+	//padding:3px 7px 2px 7px;
+
+	padding-right:5px;
+
+	vertical-align:text-top;
+}
+
+.matrixHeading tr:not(:first-child):nth-child(even) {
+    color: #000;
+    background-color: rgb(240, 240, 255);
+}
+
+.matrixHeading tr:not(:first-child):nth-child(odd) {
+    color: #000;
+    background-color: rgba(255, 248, 218, 0.13);
+}
+
+.matrixHeading tr:first-child td table tr {
+    background-color: #6eb4db !important;
+}
+
+.matrixHeadingBG {
+    background-color: #6eb4db;
+}
+
+.matrixHeadingVerbs {
+	color: white;
+	font-weight: bold;
+	font-size: 125%
+}
+
+.matrixHeadingCommands {
+    color: rgb(240, 240, 255) !important;
+    background-color: #6eb4db !important;
+}
+
+.fineractHeading2 {
+	color: white;
+	font-weight: bold;
+	//font-size: 125%
+}
+
+.fieldtype  {
+	padding-right: 40px;
+	padding-left:40px;
+	padding-top:20px;
+	font-size: 85%;
+}
+
+.fielddesc span, .fieldtype {
+    font-style: italic;
+    font-weight: normal;
+}
+
+.fielddesc {
+	padding-bottom:10px;
+	padding-left:20px;
+}
+
+.fielddesc span {
+    font-style: italic;
+    font-weight: normal;
+}
+
+.normalli {list-style:disc outside none;}
+
+
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,tdjpw{margin:0;padding:0;}tablejpw{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
+
+a img { outline: none; border: 0; }
+h1 { font-size: 180%; font-weight: bold; }
+h2 { font-size: 165%; font-weight: bold; }
+h3 { font-size: 145%; font-weight: bold; }
+h4 { font-size: 120%; font-weight: bold; }
+h5 { font-size: 120%; font-weight: bold; }
+h6 { font-size: 110%; font-weight: bold; }
+h1, h2, h3, h4, h5, h6 { margin: 0 0 0.5em 0; line-height: 1.2em; }
+p { margin: 1em 0; }
+em {font-style: italic;}
+
+html {
+    background-color: rgb(240, 240, 255);
+}
+
+body {
+    padding: 0; margin: 0;
+    font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
+    font-size: 15px;
+    line-height: 22px;
+    min-width: 1000px;
+    background-color: gray;
+    border-top: 15px solid black;
+}
+
+#flybar {
+    font-size: 14px;
+    line-height: 20px;
+}
+
+.method-example h1, .method-example h2, .method-example h3, .method-example h4, .method-example h5, .method-example h6, #flybar, .footer {
+    font-family: Helvetica, Arial, sans-serif;
+}
+
+.method-example code {
+    font-family:  Monaco, Consolas, "Lucida Console", monospace;
+    font-size: 12px;
+    line-height: 18px;
+}
+
+#flybar {
+    position: fixed;
+    z-index: 100;
+    height: 40px;
+    min-width: 745px;
+    left: 0; right: 0; top: 0;
+    padding-left: 150px;
+    background: #eee;
+    background: -webkit-gradient(linear, left top, left bottom, from(#6eb4db), to(#dadada));
+    background: -moz-linear-gradient(top, #6eb4db, #dadada);
+    border-top: 3px solid black;
+    border-bottom: 1px solid #888;
+    -webkit-box-shadow: 0 3px 5px rgba(0,0,0,0.1);
+    -moz-box-shadow: 0 3px 5px rgba(0,0,0,0.1);
+    box-shadow: 0 3px 5px rgba(0,0,0,0.1);
+}
+
+#nav-logo {
+    display: block;
+    height: 30px;
+    position: absolute;
+    top: 10px;
+    left: 10px;
+    font-weight: bold;
+    color: black;
+    font-size: 16px;
+    text-decoration: none;
+}
+
+.flybar-button {
+    text-align: center;
+    text-transform: uppercase;
+    font-size: 12px;
+    padding: 0 0.5em;
+    line-height: 40px;
+    border-right: 1px solid transparent;
+    border-left: 1px solid transparent;
+    text-decoration: none;
+    font-weight: bold;
+    color: black;
+}
+
+.flybar-nav {
+    display: block;
+    float: left;
+    min-width: 80px;
+    position: relative;
+}
+
+.flybar-language {
+    display: block;
+    float: left;
+    position: relative;
+}
+
+.flybar-language .flybar-button {
+    float: left;
+    clear: none;
+    border-right: 1px solid transparent;
+    border-left: 1px solid transparent;
+    min-width: 80px;
+    margin-left: -1px;
+}
+
+.flybar-menu {
+    display: none;
+    position: absolute;
+    top: 41px;
+    max-height:550px;
+    overflow:auto;
+    width: 1000px;
+    background-color: rgba(249, 249, 249, 0.99);
+    border: 1px solid rgb(187, 187, 187);
+    border-top: 0;
+    padding: 1em;
+    border-bottom-left-radius: 8px;
+    border-bottom-right-radius: 8px;
+    -webkit-box-shadow: 0 3px 5px rgba(0,0,0,0.1);
+    -moz-box-shadow: 0 3px 5px rgba(0,0,0,0.1);
+    box-shadow: 0 3px 5px rgba(0,0,0,0.1);
+}
+
+.flybar-menu-overview {
+    display: none;
+    position: absolute;
+    top: 41px;
+
+    width: 300px;
+    background-color: rgba(249, 249, 249, 0.99);
+    border: 1px solid rgb(187, 187, 187);
+    border-top: 0;
+    padding: 1em;
+    border-bottom-left-radius: 8px;
+    border-bottom-right-radius: 8px;
+    -webkit-box-shadow: 0 3px 5px rgba(0,0,0,0.1);
+    -moz-box-shadow: 0 3px 5px rgba(0,0,0,0.1);
+    box-shadow: 0 3px 5px rgba(0,0,0,0.1);
+}
+
+
+
+#toc-menu-client {
+	position:absolute;
+	left:-50px;
+}
+#toc-menu-group {
+	position:absolute;
+	left:-130px;
+}
+#toc-menu-loan {
+	position:absolute;
+	left:-210px;
+}
+#toc-menu-savings {
+	position:absolute;
+	left:-290px;
+}
+#toc-menu-accounting {
+	position:absolute;
+	left:-370px;
+}
+#toc-menu-org {
+	position:absolute;
+	left:-450px;
+}
+#toc-menu-user {
+	position:absolute;
+	left:-530px;
+}
+#toc-menu-system {
+	position:absolute;
+	left:-610px;
+}
+#toc-menu-noncore {
+	position:absolute;
+	left:-690px;
+}
+#toc-menu-report {
+	position:absolute;
+	left:-770px;
+}
+#toc-menu-template {
+	position:absolute;
+	left:-850px;
+}
+
+#language-menu {
+    left: -25px;
+}
+
+#language-menu li {
+    display: inline-block;
+    margin-right: 25px;
+}
+
+.flybar-menu h3, .flybar-menu-overview h3, .flybar-menu-convenience h3 {
+    text-transform: uppercase;
+    font-size: 100%;
+}
+
+.flybar-menu h4, .flybar-menu-overview h4, .flybar-menu-convenience h4 {
+    text-transform: uppercase;
+    font-size: 90%;
+    color: rgb(155, 155, 155);
+    margin: 0.5em 0 0.5em 0;
+}
+
+.flybar-menu ul, .flybar-menu-overview ul, .flybar-menu-convenience ul {
+    margin: 0 0 1em 1em;
+}
+
+.flybar-nav:hover .flybar-menu, .flybar-nav:hover .flybar-menu-overview, .flybar-nav:hover .flybar-menu-convenience,
+.flybar-nav.active .flybar-menu, .flybar-nav.active .flybar-menu-overview, .flybar-nav.active .flybar-menu-convenience{
+    display: block;
+}
+
+.flybar-button:hover,
+.flybar-button.active,
+.flybar-nav:hover .flybar-button,
+.flybar-nav.active .flybar-button {
+  background: #eee;
+  background: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#6eb4db));
+  background: -moz-linear-gradient(top, #eee, #6eb4db);
+  border-left: 1px solid rgb(187, 187, 187);
+  border-right: 1px solid rgb(187, 187, 187);
+}
+
+.flybar-nav:hover .flybar-menu, .flybar-nav:hover .flybar-menu-overview, .flybar-nav:hover .flybar-menu-convenience,
+.flybar-button.active {
+  margin-right: -1px;
+}
+
+.flybar-nav:hover .flybar-button,
+.flybar-nav.active .flybar-button {
+    height: 51px;
+    color: #000;
+    background: -webkit-gradient(linear, left top, left bottom, from(#e5e5e5), to(#fff));
+    background: -moz-linear-gradient(top, #e5e5e5, #fff);
+}
+
+.toc-column1 {
+    width: 200px;
+}
+
+.toc-column2 {
+    width: 235px;
+}
+
+.toc-column3 {
+    width: 200px;
+}
+
+.toc-column1, .toc-column2, .toc-column3, .toc-column4, .toc-column5, .toc-column6 {
+    float: left;
+}
+
+.toc-section {
+    width: 235px;
+    font-size: 90%;
+}
+
+code {
+    white-space: pre;
+    white-space: -o-pre-wrap;
+    white-space: -moz-pre-wrap;
+    white-space: pre-wrap;
+    display: block;
+    margin: 1em 0;
+    line-height: 1.5em;
+}
+
+.method-section {
+    clear: left;
+    padding: 1em 0;
+    padding-top: 60px;
+    margin-left: -580px;
+}
+
+.method-section:after {
+	visibility: hidden;
+	display: block;
+	font-size: 0;
+	content: " ";
+	clear: both;
+	height: 0;
+}
+
+.method-description {
+    width: 540px;
+    float: left;
+    padding-right: 20px;
+    color: black;
+}
+
+.method-description p {
+}
+
+h1 {
+    text-transform: uppercase;
+}
+
+.method-description h4 {
+    /*font-family: monospace;*/
+}
+
+.method-description h5 {
+    font-size: 110%;
+    color: rgb(155, 155, 155);
+    text-transform: uppercase;
+    clear: both;
+    padding: 0.5em 0;
+}
+
+.method-example {
+    margin-left: 560px;
+    padding-left: 20px;
+    min-width: 380px;
+}
+
+.method-example code {
+    white-space: pre;
+    white-space: -o-pre-wrap;
+    white-space: -moz-pre-wrap;
+    white-space: pre-wrap;
+}
+
+.method-example {
+    color: rgb(105, 105, 125);
+}
+
+.method-example p {
+    font-style: italic;
+    color: rgb(105, 105, 105);
+}
+
+.method-error:before {
+    content: "EXAMPLE ERROR";
+    color: rgb(155, 155, 185);
+    display: block;
+}
+
+.method-declaration:before {
+    content: "DEFINITION";
+    color: rgb(155, 155, 185);
+    display: block;
+}
+
+.method-request:before {
+    content: "EXAMPLE REQUEST";
+    color: rgb(155, 155, 185);
+    display: block;
+}
+
+.method-response:before {
+    content: "EXAMPLE RESPONSE";
+    color: rgb(155, 155, 185);
+    display: block;
+}
+
+.expected-result:before {
+    content: "RESULT";
+    color: rgb(155, 155, 185);
+    display: block;
+}
+
+.method-response, .method-error {
+    font-size: 90%;
+}
+
+.method-example li {
+    font-family: monospace;
+    color: black;
+}
+
+.method-example ul {
+    margin: 1em 0;
+}
+
+.method-example table {
+    font-family: monospace;
+    color: black;
+}
+
+.method-example td {
+    padding-right: 20px;
+    padding-bottom: 5px;
+    vertical-align: top;
+}
+.method-example table th {
+    font-weight: bold;
+}
+
+.method-example code {
+    color: black;
+}
+
+.prompt:after {
+    content: '$ ';
+    color: gray;
+    display: block;
+}
+
+.prompt.curl:after { content: '$ '; }
+.prompt.ruby:after { content: '>> ' }
+.prompt.python:after { content: '>>> ' }
+.prompt.php:after { content: 'php > ' }
+.prompt.java:after { content: '' }
+
+.method-name {
+    font-family: monospace;
+    font-weight: bold;
+}
+
+.notification-request:before {
+    content: "EXAMPLE NOTIFICATION";
+    color: rgb(155, 155, 185);
+    display: block;
+}
+
+.notification-event:before {
+    content: "EVENT";
+    color: rgb(155, 155, 185);
+    display: block;
+}
+
+
+#page-wrapper {
+    background-color: white;
+    color: black;
+}
+
+#main-content-wrapper {
+    background-color: rgb(240, 240, 255);
+
+    margin-left: 579px;
+    padding: 0 20px 40px 20px;
+    border-left: 1px solid rgb(229, 229, 238);
+}
+
+#api-summary-example {
+    margin-top: 48px;
+}
+
+.argument-list {
+    font-size: 90%;
+    line-height: 1.5em;
+}
+
+.argument-list dl {
+    font-size: 95%;
+    padding: 1em 0;
+    line-height: 1.5em;
+}
+
+.argument-list:after {
+	visibility: hidden;
+	display: block;
+	font-size: 0;
+	content: " ";
+	clear: both;
+	height: 0;
+}
+
+.argument-list dl:after {
+	visibility: hidden;
+	display: block;
+	font-size: 0;
+	content: " ";
+	clear: both;
+	height: 0;
+}
+
+.argument-list dt {
+    clear:both;
+    width:34%;
+    float:left;
+    text-align:right;
+    font-weight: bold;
+    margin-top: 8px;
+}
+
+.event-types.argument-list dt {
+    width: 48%;
+}
+
+.event-types.argument-list dd {
+    width: 48%;
+}
+
+.argument-list dt:after {
+    content: ":";
+}
+
+.argument-list dd {
+   float:right;
+   width: 65%;
+}
+
+.argument-list dl dd {
+    width: 65%;
+}
+
+.argument-list dt + dd {
+    margin-top: 8px;
+    font-weight: bold;
+}
+
+.argument-list dd dl dd dl {
+    width: 95%;
+}
+
+.argument-list dd dl dd dl dd {
+    width: 65%;
+}
+.argument-list dd dl dd dl dt {
+    width:31%;
+}
+
+.argument-list dd span {
+    font-style: italic;
+    font-weight: normal;
+}
+
+.old-syle-anchor {display:block;height:0;width:0;}
+strong { font-weight: bold; }
+
+.footer {
+    position: fixed;
+    bottom: 0;
+    height: 30px;
+
+    border-top: 1px solid #ccc;
+    background: #eee;
+    background: -webkit-gradient(linear, left top, left bottom, from(#6eb4db), to(#dadada));
+    background: -moz-linear-gradient(top, #6eb4db, #dadada);
+
+    width: 100%;
+    opacity: 0.95;
+    font-size: 13px;
+}
+
+.footer input[type="text"] {
+    height: 14px;
+    padding: 3px;
+    border: 1px solid #aaa;
+    border-radius: 4px;
+}
+
+.suggestion-box {
+    float: right;
+    padding-top: 3px;
+    padding-right: 10px;
+}
+
+.footer input[type="submit"]:hover {
+    background: -webkit-gradient(
+        linear,
+        center top,
+        center bottom,
+        from(#fefefe),
+        to(#dbdbdb),
+        color-stop(80%, #eeeeee),
+        color-stop(0%, #eeeeee)
+    );
+    background: -moz-linear-gradient(
+        top,
+        #fff,
+        #fff
+    );
+}
+
+.footer input[type="submit"]:active {
+    background: -webkit-gradient(
+        linear,
+        center top,
+        center bottom,
+        from(#c0c0c0),
+        to(#fefefe),
+        color-stop(0%, #e1e1e1),
+        color-stop(30%, #eeeeee),
+        color-stop(80%, #fff)
+    );
+    background: -moz-linear-gradient(
+        top,
+        #c0c0c0,
+        #fefefe
+    );
+
+    border: 1px solid #a3a3a3;
+}
+
+.footer input[type="submit"] {
+    height: 22px;
+    padding: 3px;
+    font-size: 13px;
+
+    font-family: "proxima-nova-1","proxima-nova-2", Helvetica, Arial, sans-serif;
+    color: #454545;
+    text-shadow: 0px 1px 0px #fff;
+    text-align: center;
+
+    border: 1px solid #aaa;
+    background: -webkit-gradient(
+        linear,
+        center top,
+        center bottom,
+        from(#fefefe),
+        to(#dbdbdb),
+        color-stop(50%, #eeeeee)
+    );
+    background: -moz-linear-gradient(
+        top,
+        #fefefe,
+        #dbdbdb
+    );
+
+    border-radius: 5px;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    -webkit-background-clip: padding-box;
+    -webkit-backface-visibility: hidden;
+}
+
+.stripe-version {
+    float: left;
+}
+
+ul.field {
+    margin: 5px 10px;
+}
+
+ul.field li {
+    list-style: square inside none;
+    font-style: italic;
+}
+
+tt {
+    font-size: 9.5pt;
+}
\ No newline at end of file
diff --git a/api-docs/jquery-1.7.min.js b/api-docs/jquery-1.7.min.js
new file mode 100644
index 0000000..3ca5e0f
--- /dev/null
+++ b/api-docs/jquery-1.7.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7 jquery.com | jquery.org/license */
+(function(a,b){function cA(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cx(a){if(!cm[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cn||(cn=c.createElement("iframe"),cn.frameBorder=cn.width=cn.height=0),b.appendChild(cn);if(!co||!cn.createElement)co=(cn.contentWindow||cn.contentDocument).document,co.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),co.close();d=co.createElement(a),co.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cn)}cm[a]=e}return cm[a]}function cw(a,b){var c={};f.each(cs.concat.apply([],cs.slice(0,b)),function(){c[this]=a});return c}function cv(){ct=b}function cu(){setTimeout(cv,0);return ct=f.now()}function cl(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ck(){try{return new a.XMLHttpRequest}catch(b){}}function ce(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cd(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function cc(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bG.test(a)?d(a,e):cc(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)cc(a+"["+e+"]",b[e],c,d);else d(a,b)}function cb(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function ca(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bV,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=ca(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=ca(a,c,d,e,"*",g));return l}function b_(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bR),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bE(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bz:bA;if(d>0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bB(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function br(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bi,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bq(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bp(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bp)}function bp(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bo(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bn(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bm(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bl(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a){var b=Y.split(" "),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function W(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(R.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(){return!0}function M(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function K(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(K,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z]|[0-9])/ig,x=/^-ms-/,y=function(a,b){return(b+"").toUpperCase()},z=d.userAgent,A,B,C,D=Object.prototype.toString,E=Object.prototype.hasOwnProperty,F=Array.prototype.push,G=Array.prototype.slice,H=String.prototype.trim,I=Array.prototype.indexOf,J={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7",length:0,size:function(){return this.length},toArray:function(){return G.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?F.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),B.add(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:F,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;B.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!B){B=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",C,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",C),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&K()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return a!=null&&m.test(a)&&!isNaN(a)},type:function(a){return a==null?String(a):J[D.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!E.call(a,"constructor")&&!E.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||E.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(x,"ms-").replace(w,y)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:H?function(a){return a==null?"":H.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?F.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(I)return I.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=G.call(arguments,2),g=function(){return a.apply(c,f.concat(G.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){J["[object "+b+"]"]=b.toLowerCase()}),A=e.uaMatch(z),A.browser&&(e.browser[A.browser]=!0,e.browser.version=A.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?C=function(){c.removeEventListener("DOMContentLoaded",C,!1),e.ready()}:c.attachEvent&&(C=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",C),e.ready())}),typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return e});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){return i.done.apply(i,arguments).fail.apply(i,arguments)},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/><nav></nav>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,unknownElems:!!a.getElementsByTagName("nav").length,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",enctype:!!c.createElement("form").enctype,submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.lastChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},m&&f.extend(p,{position:"absolute",left:"-999px",top:"-999px"});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;f(function(){var a,b,d,e,g,h,i=1,j="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",l="visibility:hidden;border:0;",n="style='"+j+"border:5px solid #000;padding:0;'",p="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>";m=c.getElementsByTagName("body")[0];!m||(a=c.createElement("div"),a.style.cssText=l+"width:0;height:0;position:static;top:0;margin-top:"+i+"px",m.insertBefore(a,m.firstChild),o=c.createElement("div"),o.style.cssText=j+l,o.innerHTML=p,a.appendChild(o),b=o.firstChild,d=b.firstChild,g=b.nextSibling.firstChild.firstChild,h={doesNotAddBorder:d.offsetTop!==5,doesAddBorderForTableAndCells:g.offsetTop===5},d.style.position="fixed",d.style.top="20px",h.fixedPosition=d.offsetTop===20||d.offsetTop===15,d.style.position=d.style.top="",b.style.overflow="hidden",b.style.position="relative",h.subtractsBorderForOverflowNotVisible=d.offsetTop===-5,h.doesNotIncludeMarginInBodyOffset=m.offsetTop!==i,m.removeChild(a),o=a=null,f.extend(k,h))}),o.innerHTML="",n.removeChild(o),o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[f.expando]:a[f.expando]&&f.expando,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[f.expando]=n=++f.uuid:n=f.expando),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[f.expando]:f.expando;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)?b=b:b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" "));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[f.expando]:a.removeAttribute?a.removeAttribute(f.expando):a[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];if(!arguments.length){if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}return b}e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!a||j===3||j===8||j===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g},removeAttr:function(a,b){var c,d,e,g,h=0;if(a.nodeType===1){d=(b||"").split(p),g=d.length;for(;h<g;h++)e=d[h].toLowerCase(),c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1)}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return b;h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/\.(.*)$/,A=/^(?:textarea|input|select)$/i,B=/\./g,C=/ /g,D=/[^\w\s.|`]/g,E=/^([^\.]*)?(?:\.(.+))?$/,F=/\bhover(\.\S+)?/,G=/^key/,H=/^(?:mouse|contextmenu)|click/,I=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,J=function(a){var b=I.exec(a);b&&
+(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},K=function(a,b){return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||a.id===b[2])&&(!b[3]||b[3].test(a.className))},L=function(a){return f.event.special.hover?a:a.replace(F,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=L(c).split(" ");for(k=0;k<c.length;k++){l=E.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,namespace:n.join(".")},p),g&&(o.quick=J(g),!o.quick&&f.expr.match.POS.test(g)&&(o.isPositional=!0)),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d){var e=f.hasData(a)&&f._data(a),g,h,i,j,k,l,m,n,o,p,q;if(!!e&&!!(m=e.events)){b=L(b||"").split(" ");for(g=0;g<b.length;g++){h=E.exec(b[g])||[],i=h[1],j=h[2];if(!i){j=j?"."+j:"";for(l in m)f.event.remove(a,l+j,c,d);return}n=f.event.special[i]||{},i=(d?n.delegateType:n.bindType)||i,p=m[i]||[],k=p.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;if(c||j||d||n.remove)for(l=0;l<p.length;l++){q=p[l];if(!c||c.guid===q.guid)if(!j||j.test(q.namespace))if(!d||d===q.selector||d==="**"&&q.selector)p.splice(l--,1),q.selector&&p.delegateCount--,n.remove&&n.remove.call(a,q)}else p.length=0;p.length===0&&k!==p.length&&((!n.teardown||n.teardown.call(a,j)===!1)&&f.removeEvent(a,i,e.handle),delete m[i])}f.isEmptyObject(m)&&(o=e.handle,o&&(o.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"",(g||!e)&&c.preventDefault();if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,n=null;for(m=e.parentNode;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length;l++){m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d);if(c.isPropagationStopped())break}c.type=h,c.isDefaultPrevented()||(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=(f.event.special[c.type]||{}).handle,j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click"))for(m=c.target;m!=this;m=m.parentNode||this){o={},q=[];for(k=0;k<e;k++)r=d[k],s=r.selector,t=o[s],r.isPositional?t=(t||(o[s]=f(s))).index(m)>=0:t===b&&(t=o[s]=r.quick?K(m,r.quick):f(m).is(s)),t&&q.push(r);q.length&&j.push({elem:m,matches:q})}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){p=j[k],c.currentTarget=p.elem;for(l=0;l<p.matches.length&&!c.isImmediatePropagationStopped();l++){r=p.matches[l];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=(i||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement wheelDelta".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},focus:{delegateType:"focusin",noBubble:!0},blur:{delegateType:"focusout",noBubble:!0},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?N:M):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=N;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=N;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=N,this.stopPropagation()},isDefaultPrevented:M,isPropagationStopped:M,isImmediatePropagationStopped:M},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]=f.event.special[b]={delegateType:b,bindType:b,handle:function(a){var b=this,c=a.relatedTarget,d=a.handleObj,e=d.selector,g,h;if(!c||d.origType===a.type||c!==b&&!f.contains(b,c))g=a.type,a.type=d.origType,h=d.handler.apply(this,arguments),a.type=g;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(A.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;A.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return A.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=M;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=M);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),G.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),H.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw"Syntax error, unrecognized expression: "+a};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var O=/Until$/,P=/^(?:parents|prevUntil|prevAll)/,Q=/,/,R=/^.[^:#\[\.,]*$/,S=Array.prototype.slice,T=f.expr.match.POS,U={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(W(this,a,!1),"not",a)},filter:function(a){return this.pushStack(W(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?T.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",Z=/ jQuery\d+="(?:\d+|null)"/g,$=/^\s+/,_=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,ba=/<([\w:]+)/,bb=/<tbody/i,bc=/<|&#?\w+;/,bd=/<(?:script|style)/i,be=/<(?:script|object|embed|option|style)/i,bf=new RegExp("<(?:"+Y.replace(" ","|")+")","i"),bg=/checked\s*(?:[^=]|=\s*.checked.)/i,bh=/\/(java|ecma)script/i,bi=/^\s*<!(?:\[CDATA\[|\-\-)/,bj={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bk=X(c);bj.optgroup=bj.option,bj.tbody=bj.tfoot=bj.colgroup=bj.caption=bj.thead,bj.th=bj.td,f.support.htmlSerialize||(bj._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after"
+,arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Z,""):null;if(typeof a=="string"&&!bd.test(a)&&(f.support.leadingWhitespace||!$.test(a))&&!bj[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(_,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bg.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bl(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,br)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!be.test(j)&&(f.support.checkClone||!bg.test(j))&&!f.support.unknownElems&&bf.test(j)&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bn(a,d),e=bo(a),g=bo(d);for(h=0;e[h];++h)g[h]&&bn(e[h],g[h])}if(b){bm(a,d);if(c){e=bo(a),g=bo(d);for(h=0;e[h];++h)bm(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bc.test(k))k=b.createTextNode(k);else{k=k.replace(_,"<$1></$2>");var l=(ba.exec(k)||["",""])[1].toLowerCase(),m=bj[l]||bj._default,n=m[0],o=b.createElement("div");b===c?bk.appendChild(o):X(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=bb.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&$.test(k)&&o.insertBefore(b.createTextNode($.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bq(k[i]);else bq(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||bh.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bs=/alpha\([^)]*\)/i,bt=/opacity=([^)]*)/,bu=/([A-Z]|^ms)/g,bv=/^-?\d+(?:px)?$/i,bw=/^-?\d/,bx=/^([\-+])=([\-+.\de]+)/,by={position:"absolute",visibility:"hidden",display:"block"},bz=["Left","Right"],bA=["Top","Bottom"],bB,bC,bD;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bB(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bx.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bB)return bB(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bE(a,b,d);f.swap(a,by,function(){e=bE(a,b,d)});return e}},set:function(a,b){if(!bv.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bt.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bs,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bs.test(g)?g.replace(bs,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bB(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bC=function(a,c){var d,e,g;c=c.replace(bu,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bD=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bv.test(f)&&bw.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bB=bC||bD,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bF=/%20/g,bG=/\[\]$/,bH=/\r?\n/g,bI=/#.*$/,bJ=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bK=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bL=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bM=/^(?:GET|HEAD)$/,bN=/^\/\//,bO=/\?/,bP=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bQ=/^(?:select|textarea)/i,bR=/\s+/,bS=/([?&])_=[^&]*/,bT=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bU=f.fn.load,bV={},bW={},bX,bY,bZ=["*/"]+["*"];try{bX=e.href}catch(b$){bX=c.createElement("a"),bX.href="",bX=bX.href}bY=bT.exec(bX.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bU)return bU.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bP,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bQ.test(this.nodeName)||bK.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bH,"\r\n")}}):{name:b.name,value:c.replace(bH,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?cb(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),cb(a,b);return a},ajaxSettings:{url:bX,isLocal:bL.test(bY[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bZ},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:b_(bV),ajaxTransport:b_(bW),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cd(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=ce(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bJ.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bI,"").replace(bN,bY[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bR),d.crossDomain==null&&(r=bT.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bY[1]&&r[2]==bY[2]&&(r[3]||(r[1]==="http:"?80:443))==(bY[3]||(bY[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),ca(bV,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bM.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bO.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bS,"$1_="+x);d.url=y+(y===d.url?(bO.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bZ+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=ca(bW,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){s<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)cc(g,a[g],c,e);return d.join("&").replace(bF,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cf=f.now(),cg=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cf++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cg.test(b.url)||e&&cg.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cg,l),b.url===j&&(e&&(k=k.replace(cg,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ch=a.ActiveXObject?function(){for(var a in cj)cj[a](0,1)}:!1,ci=0,cj;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ck()||cl()}:ck,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ch&&delete cj[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++ci,ch&&(cj||(cj={},f(a).unload(ch)),cj[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cm={},cn,co,cp=/^(?:toggle|show|hide)$/,cq=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cr,cs=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],ct;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cw("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cx(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cw("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cw("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cx(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cp.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=cq.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cw("show",1),slideUp:cw("hide",1),slideToggle:cw("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=ct||cu(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cr&&(cr=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=ct||cu(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cr),cr=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now))}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cy=/^t(?:able|d|h)$/i,cz=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cA(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cy.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cz.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cz.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cA(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cA(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
diff --git a/build-cloudbees.sh b/build-cloudbees.sh
new file mode 100755
index 0000000..c608ea1
--- /dev/null
+++ b/build-cloudbees.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -e
+
+# https://developer.cloudbees.com/bin/view/DEV/Node+Builds
+curl -s -o use-node https://repository-cloudbees.forge.cloudbees.com/distributions/ci-addons/node/use-node
+NODE_VERSION=0.11.12 . ./use-node
+
+./build.sh
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..3b06f12
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# Exit the script if any command returns a non-true return value (http://www.davidpashley.com/articles/writing-robust-shell-scripts/)
+set -e
+
+git --version
+# TODO UNCOMMENT THIS once https://github.com/openMF/mifosx/pull/1291 is merged!
+# git pull
+# This does NOT get the most recent revision from the submodule repository.
+# Instead, it only gets the revision of the submodule that is recorded in the revision of the main repository.
+## NOT git submodule update --remote
+# The following is very important, because even if the main (API) repo is latest,
+# the sub-module initially will be a fixed old rev. and only this grabs real latest:
+git submodule foreach 'git checkout develop && git pull --ff-only origin develop'
+
+cd apps/community-app
+./build.sh
+cd ../../fineract-provider/
+./gradlew -Penv=dev clean dist
diff --git a/config/fineractdev-eclipse-preferences.epf b/config/fineractdev-eclipse-preferences.epf
new file mode 100644
index 0000000..373d3ef
--- /dev/null
+++ b/config/fineractdev-eclipse-preferences.epf
@@ -0,0 +1,110 @@
+#Fri Nov 23 23:01:54 GMT 2012
+/instance/org.eclipse.jdt.ui/cleanup.always_use_parentheses_in_expressions=false
+/instance/org.eclipse.jdt.ui/cleanup.make_parameters_final=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning
+/instance/org.eclipse.jdt.ui/cleanup.add_serial_version_id=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentSuffixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=disabled
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentPrefixes=
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.ondemandthreshold=99
+/instance/org.eclipse.jdt.ui/cleanup.always_use_blocks=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces=true
+/instance/org.eclipse.jdt.ui/cleanup.make_private_fields_final=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_override_annotations_interface_methods=true
+@org.eclipse.jdt.ui=3.7.1.r371_v20110824-0800
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_nls_tags=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.ignorelowercasenames=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_private_constructors=true
+/instance/org.eclipse.jdt.ui/cleanup.add_default_serial_version_id=true
+/instance/org.eclipse.jdt.ui/cleanup.use_blocks=true
+/instance/org.eclipse.jdt.ui/cleanup.convert_to_enhanced_for_loop=false
+file_export_version=3.0
+/instance/org.eclipse.jdt.ui/formatter_settings_version=12
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.exception.name=e
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_field_accesses_with_declaring_class=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=enabled
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.cleanupprofiles=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<profiles version\="2">\r\n<profile kind\="CleanUpProfile" name\="fineractdevprofile" version\="2">\r\n<setting id\="cleanup.remove_unused_private_fields" value\="true"/>\r\n<setting id\="cleanup.always_use_parentheses_in_expressions" value\="false"/>\r\n<setting id\="cleanup.never_use_blocks" value\="false"/>\r\n<setting id\="cleanup.remove_unused_private_methods" value\="true"/>\r\n<setting id\="cleanup.add_missing_deprecated_annotations" value\="true"/>\r\n<setting id\="cleanup.convert_to_enhanced_for_loop" value\="false"/>\r\n<setting id\="cleanup.remove_unnecessary_nls_tags" value\="true"/>\r\n<setting id\="cleanup.sort_members" value\="false"/>\r\n<setting id\="cleanup.remove_unused_local_variables" value\="false"/>\r\n<setting id\="cleanup.never_use_parentheses_in_expressions" value\="true"/>\r\n<setting id\="cleanup.remove_unused_private_members" value\="false"/>\r\n<setting id\="cleanup.remove_unnecessary_casts" value\="true"/>\r\n<setting id\="cleanup.make_parameters_final" value\="true"/>\r\n<setting id\="cleanup.use_this_for_non_static_field_access" value\="true"/>\r\n<setting id\="cleanup.remove_private_constructors" value\="true"/>\r\n<setting id\="cleanup.use_blocks" value\="true"/>\r\n<setting id\="cleanup.always_use_this_for_non_static_method_access" value\="false"/>\r\n<setting id\="cleanup.remove_trailing_whitespaces_all" value\="true"/>\r\n<setting id\="cleanup.always_use_this_for_non_static_field_access" value\="true"/>\r\n<setting id\="cleanup.use_this_for_non_static_field_access_only_if_necessary" value\="false"/>\r\n<setting id\="cleanup.add_default_serial_version_id" value\="true"/>\r\n<setting id\="cleanup.make_type_abstract_if_missing_method" value\="false"/>\r\n<setting id\="cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class" value\="true"/>\r\n<setting id\="cleanup.make_variable_declarations_final" value\="true"/>\r\n<setting id\="cleanup.add_missing_nls_tags" value\="false"/>\r\n<setting id\="cleanup.format_source_code" value\="true"/>\r\n<setting id\="cleanup.qualify_static_method_accesses_with_declaring_class" value\="false"/>\r\n<setting id\="cleanup.add_missing_override_annotations" value\="true"/>\r\n<setting id\="cleanup.remove_unused_private_types" value\="true"/>\r\n<setting id\="cleanup.add_missing_methods" value\="true"/>\r\n<setting id\="cleanup.make_local_variable_final" value\="true"/>\r\n<setting id\="cleanup.correct_indentation" value\="true"/>\r\n<setting id\="cleanup.add_missing_override_annotations_interface_methods" value\="true"/>\r\n<setting id\="cleanup.remove_unused_imports" value\="true"/>\r\n<setting id\="cleanup.remove_trailing_whitespaces_ignore_empty" value\="false"/>\r\n<setting id\="cleanup.make_private_fields_final" value\="true"/>\r\n<setting id\="cleanup.add_generated_serial_version_id" value\="false"/>\r\n<setting id\="cleanup.organize_imports" value\="true"/>\r\n<setting id\="cleanup.remove_trailing_whitespaces" value\="true"/>\r\n<setting id\="cleanup.sort_members_all" value\="false"/>\r\n<setting id\="cleanup.use_blocks_only_for_return_and_throw" value\="false"/>\r\n<setting id\="cleanup.add_missing_annotations" value\="true"/>\r\n<setting id\="cleanup.use_parentheses_in_expressions" value\="false"/>\r\n<setting id\="cleanup.qualify_static_field_accesses_with_declaring_class" value\="false"/>\r\n<setting id\="cleanup.use_this_for_non_static_method_access_only_if_necessary" value\="true"/>\r\n<setting id\="cleanup.use_this_for_non_static_method_access" value\="true"/>\r\n<setting id\="cleanup.qualify_static_member_accesses_through_instances_with_declaring_class" value\="true"/>\r\n<setting id\="cleanup.add_serial_version_id" value\="false"/>\r\n<setting id\="cleanup.format_source_code_changes_only" value\="false"/>\r\n<setting id\="cleanup.qualify_static_member_accesses_with_declaring_class" value\="true"/>\r\n<setting id\="cleanup.always_use_blocks" value\="true"/>\r\n</profile>\r\n</profiles>\r\n
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.importorder=java;javax;org;com;
+/instance/org.eclipse.jdt.ui/cleanup_profile=_fineractdevprofile
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_with_declaring_class=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+/instance/org.eclipse.jdt.ui/cleanup.use_parentheses_in_expressions=false
+\!/=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.compliance=1.6
+/instance/org.eclipse.jdt.ui/cleanup.sort_members=false
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.staticondemandthreshold=99
+/instance/org.eclipse.jdt.ui/cleanup.never_use_parentheses_in_expressions=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localSuffixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=enabled
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldSuffixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localPrefixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldPrefixes=
+/instance/org.eclipse.jdt.ui/cleanup.make_variable_declarations_final=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<profiles version\="12">\r\n<profile kind\="CodeFormatterProfile" name\="fineractdevprojectformatter" version\="12">\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.disabling_tag" value\="@formatter\:off"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_field" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.use_on_off_tags" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value\="80"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_after_package" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.continuation_indentation" value\="2"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_package" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.source" value\="1.7"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_line_comments" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.join_wrapped_lines" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.lineSplit" value\="140"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indentation.size" value\="8"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.enabling_tag" value\="@formatter\:on"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_assignment" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value\="error"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.tabulation.char" value\="space"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_method" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_switch" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value\="error"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_block" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.compact_else_if" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.tabulation.size" value\="4"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_empty_lines" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.compliance" value\="1.7"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value\="2"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value\="enabled"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.line_length" value\="80"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.join_lines_in_comments" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_html" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_source_code" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value\="1.7"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value\="80"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_header" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_block_comments" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value\="false"/>\r\n</profile>\r\n</profiles>\r\n
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=enabled
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.keywordthis=true
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_override_annotations=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unnecessary_nls_tags=true
+/instance/org.eclipse.jdt.ui/formatter_profile=_fineractdevprojectformatter
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+/instance/org.eclipse.jdt.ui/cleanup.organize_imports=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles.version=12
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+@org.eclipse.jdt.core=3.7.1.v_B76_R37x
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_types=true
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_methods=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_imports=true
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+/instance/org.eclipse.jdt.ui/cleanup.always_use_this_for_non_static_method_access=false
+/instance/org.eclipse.jdt.ui/cleanup.always_use_this_for_non_static_field_access=true
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_annotations=true
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_field_access=true
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces_all=true
+/instance/org.eclipse.jdt.ui/cleanup.never_use_blocks=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.gettersetter.use.is=true
+/instance/org.eclipse.jdt.ui/cleanup.format_source_code_changes_only=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_methods=true
+/instance/org.eclipse.jdt.ui/cleanup.add_generated_serial_version_id=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces_ignore_empty=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.source=1.6
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_method_access=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
+/instance/org.eclipse.jdt.ui/cleanup.sort_members_all=false
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_fields=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.overrideannotation=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_method_accesses_with_declaring_class=false
+/instance/org.eclipse.jdt.ui/cleanup_settings_version=2
+/instance/org.eclipse.jdt.ui/cleanup.make_type_abstract_if_missing_method=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_local_variables=false
+/instance/org.eclipse.jdt.ui/cleanup.make_local_variable_final=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
+/instance/org.eclipse.jdt.ui/cleanup.format_source_code=true
+/instance/org.eclipse.jdt.ui/cleanup.correct_indentation=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unnecessary_casts=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_members=false
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_deprecated_annotations=true
+/instance/org.eclipse.jdt.ui/cleanup.use_blocks_only_for_return_and_throw=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
diff --git a/docs/system-architecture/.gitattributes b/docs/system-architecture/.gitattributes
new file mode 100644
index 0000000..2125666
--- /dev/null
+++ b/docs/system-architecture/.gitattributes
@@ -0,0 +1 @@
+* text=auto
\ No newline at end of file
diff --git a/docs/system-architecture/.gitignore b/docs/system-architecture/.gitignore
new file mode 100644
index 0000000..16b2d7d
--- /dev/null
+++ b/docs/system-architecture/.gitignore
@@ -0,0 +1,2 @@
+# Include your project-specific ignores in this file
+# Read about how to use .gitignore: https://help.github.com/articles/ignoring-files
diff --git a/docs/system-architecture/.htaccess b/docs/system-architecture/.htaccess
new file mode 100644
index 0000000..64b43ad
--- /dev/null
+++ b/docs/system-architecture/.htaccess
@@ -0,0 +1,544 @@
+# Apache Configuration File
+
+# (!) Using `.htaccess` files slows down Apache, therefore, if you have access
+# to the main server config file (usually called `httpd.conf`), you should add
+# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
+
+# ##############################################################################
+# # CROSS-ORIGIN RESOURCE SHARING (CORS)                                       #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Cross-domain AJAX requests                                                 |
+# ------------------------------------------------------------------------------
+
+# Enable cross-origin AJAX requests.
+# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
+# http://enable-cors.org/
+
+# <IfModule mod_headers.c>
+#    Header set Access-Control-Allow-Origin "*"
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | CORS-enabled images                                                        |
+# ------------------------------------------------------------------------------
+
+# Send the CORS header for images when browsers request it.
+# https://developer.mozilla.org/en/CORS_Enabled_Image
+# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
+# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
+
+<IfModule mod_setenvif.c>
+    <IfModule mod_headers.c>
+        <FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
+            SetEnvIf Origin ":" IS_CORS
+            Header set Access-Control-Allow-Origin "*" env=IS_CORS
+        </FilesMatch>
+    </IfModule>
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Web fonts access                                                           |
+# ------------------------------------------------------------------------------
+
+# Allow access from all domains for web fonts
+
+<IfModule mod_headers.c>
+    <FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
+        Header set Access-Control-Allow-Origin "*"
+    </FilesMatch>
+</IfModule>
+
+
+# ##############################################################################
+# # ERRORS                                                                     #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | 404 error prevention for non-existing redirected folders                   |
+# ------------------------------------------------------------------------------
+
+# Prevent Apache from returning a 404 error for a rewrite if a directory
+# with the same name does not exist.
+# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
+# http://www.webmasterworld.com/apache/3808792.htm
+
+Options -MultiViews
+
+# ------------------------------------------------------------------------------
+# | Custom error messages / pages                                              |
+# ------------------------------------------------------------------------------
+
+# You can customize what Apache returns to the client in case of an error (see
+# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
+
+ErrorDocument 404 /404.html
+
+
+# ##############################################################################
+# # INTERNET EXPLORER                                                          #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Better website experience                                                  |
+# ------------------------------------------------------------------------------
+
+# Force IE to render pages in the highest available mode in the various
+# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
+# Use, if installed, Google Chrome Frame.
+
+<IfModule mod_headers.c>
+    Header set X-UA-Compatible "IE=edge,chrome=1"
+    # `mod_headers` can't match based on the content-type, however, we only
+    # want to send this header for HTML pages and not for the other resources
+    <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
+        Header unset X-UA-Compatible
+    </FilesMatch>
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Cookie setting from iframes                                                |
+# ------------------------------------------------------------------------------
+
+# Allow cookies to be set from iframes in IE.
+
+# <IfModule mod_headers.c>
+#   Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | Screen flicker                                                             |
+# ------------------------------------------------------------------------------
+
+# Stop screen flicker in IE on CSS rollovers (this only works in
+# combination with the `ExpiresByType` directives for images from below).
+
+# BrowserMatch "MSIE" brokenvary=1
+# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
+# BrowserMatch "Opera" !brokenvary
+# SetEnvIf brokenvary 1 force-no-vary
+
+
+# ##############################################################################
+# # MIME TYPES AND ENCODING                                                    #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Proper MIME types for all files                                            |
+# ------------------------------------------------------------------------------
+
+<IfModule mod_mime.c>
+
+  # Audio
+    AddType audio/mp4                                   m4a f4a f4b
+    AddType audio/ogg                                   oga ogg
+
+  # JavaScript
+    # Normalize to standard type (it's sniffed in IE anyways):
+    # http://tools.ietf.org/html/rfc4329#section-7.2
+    AddType application/javascript                      js jsonp
+    AddType application/json                            json
+
+  # Video
+    AddType video/mp4                                   mp4 m4v f4v f4p
+    AddType video/ogg                                   ogv
+    AddType video/webm                                  webm
+    AddType video/x-flv                                 flv
+
+  # Web fonts
+    AddType application/font-woff                       woff
+    AddType application/vnd.ms-fontobject               eot
+
+    # Browsers usually ignore the font MIME types and sniff the content,
+    # however, Chrome shows a warning if other MIME types are used for the
+    # following fonts.
+    AddType application/x-font-ttf                      ttc ttf
+    AddType font/opentype                               otf
+
+    # Make SVGZ fonts work on iPad:
+    # https://twitter.com/FontSquirrel/status/14855840545
+    AddType     image/svg+xml                           svg svgz
+    AddEncoding gzip                                    svgz
+
+  # Other
+    AddType application/octet-stream                    safariextz
+    AddType application/x-chrome-extension              crx
+    AddType application/x-opera-extension               oex
+    AddType application/x-shockwave-flash               swf
+    AddType application/x-web-app-manifest+json         webapp
+    AddType application/x-xpinstall                     xpi
+    AddType application/xml                             atom rdf rss xml
+    AddType image/webp                                  webp
+    AddType image/x-icon                                ico
+    AddType text/cache-manifest                         appcache manifest
+    AddType text/vtt                                    vtt
+    AddType text/x-component                            htc
+    AddType text/x-vcard                                vcf
+
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | UTF-8 encoding                                                             |
+# ------------------------------------------------------------------------------
+
+# Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
+AddDefaultCharset utf-8
+
+# Force UTF-8 for certain file formats.
+<IfModule mod_mime.c>
+    AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
+</IfModule>
+
+
+# ##############################################################################
+# # URL REWRITES                                                               #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Rewrite engine                                                             |
+# ------------------------------------------------------------------------------
+
+# Turning on the rewrite engine and enabling the `FollowSymLinks` option is
+# necessary for the following directives to work.
+
+# If your web host doesn't allow the `FollowSymlinks` option, you may need to
+# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
+# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
+
+# Also, some cloud hosting services require `RewriteBase` to be set:
+# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
+
+<IfModule mod_rewrite.c>
+    Options +FollowSymlinks
+  # Options +SymLinksIfOwnerMatch
+    RewriteEngine On
+  # RewriteBase /
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Suppressing / Forcing the "www." at the beginning of URLs                  |
+# ------------------------------------------------------------------------------
+
+# The same content should never be available under two different URLs especially
+# not with and without "www." at the beginning. This can cause SEO problems
+# (duplicate content), therefore, you should choose one of the alternatives and
+# redirect the other one.
+
+# By default option 1 (no "www.") is activated:
+# http://no-www.org/faq.php?q=class_b
+
+# If you'd prefer to use option 2, just comment out all the lines from option 1
+# and uncomment the ones from option 2.
+
+# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Option 1: rewrite www.example.com → example.com
+
+<IfModule mod_rewrite.c>
+    RewriteCond %{HTTPS} !=on
+    RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
+    RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
+</IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Option 2: rewrite example.com → www.example.com
+
+# Be aware that the following might not be a good idea if you use "real"
+# subdomains for certain parts of your website.
+
+# <IfModule mod_rewrite.c>
+#    RewriteCond %{HTTPS} !=on
+#    RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
+#    RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
+# </IfModule>
+
+
+# ##############################################################################
+# # SECURITY                                                                   #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Content Security Policy (CSP)                                              |
+# ------------------------------------------------------------------------------
+
+# You can mitigate the risk of cross-site scripting and other content-injection
+# attacks by setting a Content Security Policy which whitelists trusted sources
+# of content for your site.
+
+# The example header below allows ONLY scripts that are loaded from the current
+# site's origin (no inline scripts, no CDN, etc). This almost certainly won't
+# work as-is for your site!
+
+# To get all the details you'll need to craft a reasonable policy for your site,
+# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
+# see the specification: http://w3.org/TR/CSP).
+
+# <IfModule mod_headers.c>
+#    Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
+#    <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
+#        Header unset Content-Security-Policy
+#    </FilesMatch>
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | File access                                                                |
+# ------------------------------------------------------------------------------
+
+# Block access to directories without a default document.
+# Usually you should leave this uncommented because you shouldn't allow anyone
+# to surf through every directory on your server (which may includes rather
+# private places like the CMS's directories).
+
+<IfModule mod_autoindex.c>
+    Options -Indexes
+</IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Block access to hidden files and directories.
+# This includes directories used by version control systems such as Git and SVN.
+
+<IfModule mod_rewrite.c>
+    RewriteCond %{SCRIPT_FILENAME} -d [OR]
+    RewriteCond %{SCRIPT_FILENAME} -f
+    RewriteRule "(^|/)\." - [F]
+</IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Block access to backup and source files.
+# These files may be left by some text editors and can pose a great security
+# danger when anyone has access to them.
+
+<FilesMatch "(^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$">
+    Order allow,deny
+    Deny from all
+    Satisfy All
+</FilesMatch>
+
+# ------------------------------------------------------------------------------
+# | Secure Sockets Layer (SSL)                                                 |
+# ------------------------------------------------------------------------------
+
+# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
+# prevent `https://www.example.com` when your certificate only allows
+# `https://secure.example.com`.
+
+# <IfModule mod_rewrite.c>
+#    RewriteCond %{SERVER_PORT} !^443
+#    RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
+# </IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Force client-side SSL redirection.
+
+# If a user types "example.com" in his browser, the above rule will redirect him
+# to the secure version of the site. That still leaves a window of opportunity
+# (the initial HTTP connection) for an attacker to downgrade or redirect the
+# request. The following header ensures that browser will ONLY connect to your
+# server via HTTPS, regardless of what the users type in the address bar.
+# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
+
+# <IfModule mod_headers.c>
+#    Header set Strict-Transport-Security max-age=16070400;
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | Server software information                                                |
+# ------------------------------------------------------------------------------
+
+# Avoid displaying the exact Apache version number, the description of the
+# generic OS-type and the information about Apache's compiled-in modules.
+
+# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
+
+# ServerTokens Prod
+
+
+# ##############################################################################
+# # WEB PERFORMANCE                                                            #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Compression                                                                |
+# ------------------------------------------------------------------------------
+
+<IfModule mod_deflate.c>
+
+    # Force compression for mangled headers.
+    # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
+    <IfModule mod_setenvif.c>
+        <IfModule mod_headers.c>
+            SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
+            RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
+        </IfModule>
+    </IfModule>
+
+    # Compress all output labeled with one of the following MIME-types
+    # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
+    #  and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines
+    #  as `AddOutputFilterByType` is still in the core directives).
+    <IfModule mod_filter.c>
+        AddOutputFilterByType DEFLATE application/atom+xml \
+                                      application/javascript \
+                                      application/json \
+                                      application/rss+xml \
+                                      application/vnd.ms-fontobject \
+                                      application/x-font-ttf \
+                                      application/x-web-app-manifest+json \
+                                      application/xhtml+xml \
+                                      application/xml \
+                                      font/opentype \
+                                      image/svg+xml \
+                                      image/x-icon \
+                                      text/css \
+                                      text/html \
+                                      text/plain \
+                                      text/x-component \
+                                      text/xml
+    </IfModule>
+
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Content transformations                                                    |
+# ------------------------------------------------------------------------------
+
+# Prevent some of the mobile network providers from modifying the content of
+# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
+
+# <IfModule mod_headers.c>
+#    Header set Cache-Control "no-transform"
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | ETag removal                                                               |
+# ------------------------------------------------------------------------------
+
+# Since we're sending far-future expires headers (see below), ETags can
+# be removed: http://developer.yahoo.com/performance/rules.html#etags.
+
+# `FileETag None` is not enough for every server.
+<IfModule mod_headers.c>
+    Header unset ETag
+</IfModule>
+
+FileETag None
+
+# ------------------------------------------------------------------------------
+# | Expires headers (for better cache control)                                 |
+# ------------------------------------------------------------------------------
+
+# The following expires headers are set pretty far in the future. If you don't
+# control versioning with filename-based cache busting, consider lowering the
+# cache time for resources like CSS and JS to something like 1 week.
+
+<IfModule mod_expires.c>
+
+    ExpiresActive on
+    ExpiresDefault                                      "access plus 1 month"
+
+  # CSS
+    ExpiresByType text/css                              "access plus 1 year"
+
+  # Data interchange
+    ExpiresByType application/json                      "access plus 0 seconds"
+    ExpiresByType application/xml                       "access plus 0 seconds"
+    ExpiresByType text/xml                              "access plus 0 seconds"
+
+  # Favicon (cannot be renamed!)
+    ExpiresByType image/x-icon                          "access plus 1 week"
+
+  # HTML components (HTCs)
+    ExpiresByType text/x-component                      "access plus 1 month"
+
+  # HTML
+    ExpiresByType text/html                             "access plus 0 seconds"
+
+  # JavaScript
+    ExpiresByType application/javascript                "access plus 1 year"
+
+  # Manifest files
+    ExpiresByType application/x-web-app-manifest+json   "access plus 0 seconds"
+    ExpiresByType text/cache-manifest                   "access plus 0 seconds"
+
+  # Media
+    ExpiresByType audio/ogg                             "access plus 1 month"
+    ExpiresByType image/gif                             "access plus 1 month"
+    ExpiresByType image/jpeg                            "access plus 1 month"
+    ExpiresByType image/png                             "access plus 1 month"
+    ExpiresByType video/mp4                             "access plus 1 month"
+    ExpiresByType video/ogg                             "access plus 1 month"
+    ExpiresByType video/webm                            "access plus 1 month"
+
+  # Web feeds
+    ExpiresByType application/atom+xml                  "access plus 1 hour"
+    ExpiresByType application/rss+xml                   "access plus 1 hour"
+
+  # Web fonts
+    ExpiresByType application/font-woff                 "access plus 1 month"
+    ExpiresByType application/vnd.ms-fontobject         "access plus 1 month"
+    ExpiresByType application/x-font-ttf                "access plus 1 month"
+    ExpiresByType font/opentype                         "access plus 1 month"
+    ExpiresByType image/svg+xml                         "access plus 1 month"
+
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Filename-based cache busting                                               |
+# ------------------------------------------------------------------------------
+
+# If you're not using a build process to manage your filename version revving,
+# you might want to consider enabling the following directives to route all
+# requests such as `/css/style.12345.css` to `/css/style.css`.
+
+# To understand why this is important and a better idea than `*.css?v231`, read:
+# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
+
+# <IfModule mod_rewrite.c>
+#    RewriteCond %{REQUEST_FILENAME} !-f
+#    RewriteCond %{REQUEST_FILENAME} !-d
+#    RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | File concatenation                                                         |
+# ------------------------------------------------------------------------------
+
+# Allow concatenation from within specific CSS and JS files, e.g.:
+# Inside of `script.combined.js` you could have
+#   <!--#include file="libs/jquery.js" -->
+#   <!--#include file="plugins/jquery.idletimer.js" -->
+# and they would be included into this single file.
+
+# <IfModule mod_include.c>
+#    <FilesMatch "\.combined\.js$">
+#        Options +Includes
+#        AddOutputFilterByType INCLUDES application/javascript application/json
+#        SetOutputFilter INCLUDES
+#    </FilesMatch>
+#    <FilesMatch "\.combined\.css$">
+#        Options +Includes
+#        AddOutputFilterByType INCLUDES text/css
+#        SetOutputFilter INCLUDES
+#    </FilesMatch>
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | Persistent connections                                                     |
+# ------------------------------------------------------------------------------
+
+# Allow multiple requests to be sent over the same TCP connection:
+# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
+
+# Enable if you serve a lot of static content but, be aware of the
+# possible disadvantages!
+
+# <IfModule mod_headers.c>
+#    Header set Connection Keep-Alive
+# </IfModule>
diff --git a/docs/system-architecture/404.html b/docs/system-architecture/404.html
new file mode 100644
index 0000000..fdace4a
--- /dev/null
+++ b/docs/system-architecture/404.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>Page Not Found :(</title>
+        <style>
+            ::-moz-selection {
+                background: #b3d4fc;
+                text-shadow: none;
+            }
+
+            ::selection {
+                background: #b3d4fc;
+                text-shadow: none;
+            }
+
+            html {
+                padding: 30px 10px;
+                font-size: 20px;
+                line-height: 1.4;
+                color: #737373;
+                background: #f0f0f0;
+                -webkit-text-size-adjust: 100%;
+                -ms-text-size-adjust: 100%;
+            }
+
+            html,
+            input {
+                font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+            }
+
+            body {
+                max-width: 500px;
+                _width: 500px;
+                padding: 30px 20px 50px;
+                border: 1px solid #b3b3b3;
+                border-radius: 4px;
+                margin: 0 auto;
+                box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;
+                background: #fcfcfc;
+            }
+
+            h1 {
+                margin: 0 10px;
+                font-size: 50px;
+                text-align: center;
+            }
+
+            h1 span {
+                color: #bbb;
+            }
+
+            h3 {
+                margin: 1.5em 0 0.5em;
+            }
+
+            p {
+                margin: 1em 0;
+            }
+
+            ul {
+                padding: 0 0 0 40px;
+                margin: 1em 0;
+            }
+
+            .container {
+                max-width: 380px;
+                _width: 380px;
+                margin: 0 auto;
+            }
+
+            /* google search */
+
+            #goog-fixurl ul {
+                list-style: none;
+                padding: 0;
+                margin: 0;
+            }
+
+            #goog-fixurl form {
+                margin: 0;
+            }
+
+            #goog-wm-qt,
+            #goog-wm-sb {
+                border: 1px solid #bbb;
+                font-size: 16px;
+                line-height: normal;
+                vertical-align: top;
+                color: #444;
+                border-radius: 2px;
+            }
+
+            #goog-wm-qt {
+                width: 220px;
+                height: 20px;
+                padding: 5px;
+                margin: 5px 10px 0 0;
+                box-shadow: inset 0 1px 1px #ccc;
+            }
+
+            #goog-wm-sb {
+                display: inline-block;
+                height: 32px;
+                padding: 0 10px;
+                margin: 5px 0 0;
+                white-space: nowrap;
+                cursor: pointer;
+                background-color: #f5f5f5;
+                background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+                background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+                background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+                background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+                -webkit-appearance: none;
+                -moz-appearance: none;
+                appearance: none;
+                *overflow: visible;
+                *display: inline;
+                *zoom: 1;
+            }
+
+            #goog-wm-sb:hover,
+            #goog-wm-sb:focus {
+                border-color: #aaa;
+                box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+                background-color: #f8f8f8;
+            }
+
+            #goog-wm-qt:hover,
+            #goog-wm-qt:focus {
+                border-color: #105cb6;
+                outline: 0;
+                color: #222;
+            }
+
+            input::-moz-focus-inner {
+                padding: 0;
+                border: 0;
+            }
+        </style>
+    </head>
+    <body>
+        <div class="container">
+            <h1>Not found <span>:(</span></h1>
+            <p>Sorry, but the page you were trying to view does not exist.</p>
+            <p>It looks like this was the result of either:</p>
+            <ul>
+                <li>a mistyped address</li>
+                <li>an out-of-date link</li>
+            </ul>
+            <script>
+                var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host;
+            </script>
+            <script src="//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
+        </div>
+    </body>
+</html>
diff --git a/docs/system-architecture/CHANGELOG.md b/docs/system-architecture/CHANGELOG.md
new file mode 100644
index 0000000..c6ea125
--- /dev/null
+++ b/docs/system-architecture/CHANGELOG.md
@@ -0,0 +1,122 @@
+### HEAD
+
+### 4.2.0 (April 8, 2013)
+
+* Remove Google Analytics protocol check ([#1319](https://github.com/h5bp/html5-boilerplate/pull/1319)).
+* Update to Normalize.css 1.1.1.
+* Update Apache configurations to include the latest changes in the canonical
+  [`.htaccess`](https://github.com/h5bp/server-configs/blob/master/apache/.htaccess)
+  file.
+* Use a protocol relative URL for the 404 template script.
+* Update to jQuery 1.9.1.
+
+### 4.1.0 (January 21, 2013)
+
+* Update to Normalize.css 1.1.0.
+* Update to jQuery 1.9.0.
+
+### 4.0.3 (January 12, 2013)
+
+* Use 32x32 favicon.ico ([#1286](https://github.com/h5bp/html5-boilerplate/pull/1286)).
+* Remove named function expression in plugins.js ([#1280](https://github.com/h5bp/html5-boilerplate/pull/1280)).
+* Adjust CSS image-replacement code ([#1239](https://github.com/h5bp/html5-boilerplate/issues/1239)).
+* Update HiDPI example media query ([#1127](https://github.com/h5bp/html5-boilerplate/issues/1127)).
+
+### 4.0.2 (December 9, 2012)
+
+* Update placeholder icons.
+* Update to Normalize.css 1.0.2.
+* Update to jQuery 1.8.3.
+
+### 4.0.1 (October 20, 2012)
+
+* Further improvements to `console` method stubbing ([#1206](https://github.com/h5bp/html5-boilerplate/issues/1206), [#1229](https://github.com/h5bp/html5-boilerplate/pull/1229)).
+* Update to jQuery 1.8.2.
+* Update to Modernizr 2.6.2.
+* Minor additions to the documentation.
+
+### 4.0.0 (August 28, 2012)
+
+* Improve the Apache compression configuration ([#1012](https://github.com/h5bp/html5-boilerplate/issues/1012), [#1173](https://github.com/h5bp/html5-boilerplate/issues/1173)).
+* Add a HiDPI example media query ([#1127](https://github.com/h5bp/html5-boilerplate/issues/1127)).
+* Add bundled docs ([#1154](https://github.com/h5bp/html5-boilerplate/issues/1154)).
+* Add MIT license ([#1139](https://github.com/h5bp/html5-boilerplate/issues/1139)).
+* Update to Normalize.css 1.0.1.
+* Separate Normalize.css from the rest of the CSS ([#1160](https://github.com/h5bp/html5-boilerplate/issues/1160)).
+* Improve `console.log` protection ([#1107](https://github.com/h5bp/html5-boilerplate/issues/1107)).
+* Replace hot pink text selection color with a neutral color.
+* Change image replacement technique ([#1149](https://github.com/h5bp/html5-boilerplate/issues/1149)).
+* Code format and consistency changes ([#1112](https://github.com/h5bp/html5-boilerplate/issues/1112)).
+* Rename CSS file and rename JS files and subdirectories.
+* Update to jQuery 1.8 ([#1161](https://github.com/h5bp/html5-boilerplate/issues/1161)).
+* Update to Modernizr 2.6.1 ([#1086](https://github.com/h5bp/html5-boilerplate/issues/1086)).
+* Remove uncompressed jQuery ([#1153](https://github.com/h5bp/html5-boilerplate/issues/1153)).
+* Remove superfluous inline comments ([#1150](https://github.com/h5bp/html5-boilerplate/issues/1150)).
+
+### 3.0.2 (February 19, 2012)
+
+* Update to Modernizr 2.5.3.
+
+### 3.0.1 (February 08, 2012).
+
+* Update to Modernizr 2.5.2 (includes html5shiv 3.3).
+
+### 3.0.0 (February 06, 2012)
+
+* Improvements to `.htaccess`.
+* Improve 404 design.
+* Simplify JS folder structure.
+* Change `html` IE class names changed to target ranges rather than specific versions of IE.
+* Update CSS to include latest normalize.css changes and better typographic defaults ([#825](https://github.com/h5bp/html5-boilerplate/issues/825)).
+* Update to Modernizr 2.5 (includes yepnope 1.5 and html5shiv 3.2).
+* Update to jQuery 1.7.1.
+* Revert to async snippet for the Google Analytics script.
+* Remove the ant build script ([#826](https://github.com/h5bp/html5-boilerplate/issues/826)).
+* Remove Respond.js ([#816](https://github.com/h5bp/html5-boilerplate/issues/816)).
+* Remove the `demo/` directory ([#808](https://github.com/h5bp/html5-boilerplate/issues/808)).
+* Remove the `test/` directory ([#808](https://github.com/h5bp/html5-boilerplate/issues/808)).
+* Remove Google Chrome Frame script for IE6 users; replace with links to Chrome Frame and options for alternative browsers.
+* Remove `initial-scale=1` from the viewport `meta` ([#824](https://github.com/h5bp/html5-boilerplate/issues/824)).
+* Remove `defer` from all scripts to avoid legacy IE bugs.
+* Remove explicit Site Speed tracking for Google Analytics. It's now enabled by default.
+
+### 2.0.0 (August 10, 2011)
+
+* Change starting CSS to be based on normalize.css instead of reset.css ([#500](https://github.com/h5bp/html5-boilerplate/issues/500)).
+* Add Respond.js media query polyfill.
+* Add Google Chrome Frame script prompt for IE6 users.
+* Simplify the `html` conditional comments for modern browsers and add an `oldie` class.
+* Update clearfix to use "micro clearfix".
+* Add placeholder CSS MQs for mobile-first approach.
+* Add `textarea { resize: vertical; }` to only allow vertical resizing.
+* Add `img { max-width: 100%; }` to the print styles; prevents images being truncated.
+* Add Site Speed tracking for Google Analytics.
+* Update to jQuery 1.6.2 (and use minified by default).
+* Update to Modernizr 2.0 Complete, Production minified (includes yepnope, html5shiv, and Respond.js).
+* Use `Modernizr.load()` to load the Google Analytics script.
+* Much faster build process.
+* Add build script options for CSSLint, JSLint, JSHint tools.
+* Build script now compresses all images in subfolders.
+* Build script now versions files by SHA hash.
+* Many `.htaccess` improvements including: disable directory browsing, improved support for all versions of Apache, more robust and extensive HTTP compression rules.
+* Remove `handheld.css` as it has very poor device support.
+* Remove touch-icon `link` elements from the HTML and include improved touch-icon support.
+* Remove the cache-busting query paramaters from files references in the HTML.
+* Remove IE6 PNGFix.
+
+### 1.0.0 (March 21, 2011)
+
+* Rewrite build script to make it more customizable and flexible.
+* Add a humans.txt.
+* Numerous `.htaccess` improvements (including inline documentation).
+* Move the alternative server configurations to the H5BP server configs repo.
+* Use a protocol-relative url to reference jQuery and prevent mixed content warnings.
+* Optimize the Google Analytics snippet.
+* Use Eric Meyer's recent CSS reset update and the HTML5 Doctor reset.
+* More robust `sub`/`sup` CSS styles.
+* Add keyboard `.focusable` helper class that extends `.visuallyhidden`.
+* Print styles no longer print hash or JavaScript links.
+* Add a print reset for IE's proprietary filters.
+* Remove IE9-specific conditional class on the `html` element.
+* Remove margins from lists within `nav` elements.
+* Remove YUI profiling.
diff --git a/docs/system-architecture/CONTRIBUTING.md b/docs/system-architecture/CONTRIBUTING.md
new file mode 100644
index 0000000..6facbb6
--- /dev/null
+++ b/docs/system-architecture/CONTRIBUTING.md
@@ -0,0 +1,118 @@
+# Contributing to HTML5 Boilerplate
+
+♥ [HTML5 Boilerplate](http://html5boilerplate.com) and want to get involved?
+Thanks! There are plenty of ways you can help!
+
+
+## Bugs
+
+A bug is a _demonstrable problem_ that is caused by the code in the
+repository. Good bug reports are extremely helpful - thank you!
+
+Guidelines for bug reports:
+
+1. **Use the GitHub issue search** &mdash; check if the issue has already been
+   reported.
+
+2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
+   latest `master` branch in the repository.
+
+3. **Isolate the problem** &mdash; ideally create a [reduced test
+   case](http://css-tricks.com/6263-reduced-test-cases/) and a live example.
+
+A good bug report shouldn't leave others needing to chase you up for more
+information. Please try to be as detailed as possible in your report. What is
+your environment? What steps will reproduce the issue? What browser(s) and OS
+experience the problem? What would you expect to be the outcome? All these
+details will help people to fix any potential bugs.
+
+Example:
+
+> Short and descriptive example bug report title
+>
+> A summary of the issue and the browser/OS environment in which it occurs. If
+> suitable, include the steps required to reproduce the bug.
+>
+> 1. This is the first step
+> 2. This is the second step
+> 3. Further steps, etc.
+>
+> `<url>` (a link to the reduced test case)
+>
+> Any other information you want to share that is relevant to the issue being
+> reported. This might include the lines of code that you have identified as
+> causing the bug, and potential solutions (and your opinions on their
+> merits).
+
+**[File a bug report](https://github.com/h5bp/html5-boilerplate/issues/)**
+
+
+## Pull requests
+
+Good pull requests - patches, improvements, new features - are a fantastic
+help. They should remain focused in scope and avoid containing unrelated
+commits. If your contribution involves a significant amount of work or substantial
+changes to any part of the project, please open an issue to discuss it first.
+
+Make sure to adhere to the coding conventions used throughout a project
+(indentation, accurate comments, etc.). Please update any documentation that is
+relevant to the change you're making.
+
+Please follow this process; it's the best way to get your work included in the
+project:
+
+1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
+   and configure the remotes:
+
+   ```bash
+   # Clones your fork of the repo into the current directory in terminal
+   git clone https://github.com/<your-username>/html5-boilerplate.git
+   # Navigate to the newly cloned directory
+   cd html5-boilerplate
+   # Assigns the original repo to a remote called "upstream"
+   git remote add upstream https://github.com/h5bp/html5-boilerplate.git
+   ```
+
+2. If you cloned a while ago, get the latest changes from upstream:
+
+   ```bash
+   git checkout master
+   git pull upstream master
+   ```
+
+3. Create a new topic branch to contain your feature, change, or fix:
+
+   ```bash
+   git checkout -b <topic-branch-name>
+   ```
+
+4. Commit your changes in logical chunks. Please adhere to these [git commit
+   message
+   guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
+   or your pull request is unlikely be merged into the main project. Use git's
+   [interactive rebase](https://help.github.com/articles/interactive-rebase)
+   feature to tidy up your commits before making them public.
+
+5. Locally merge (or rebase) the upstream development branch into your topic branch:
+
+   ```bash
+   git pull [--rebase] upstream master
+   ```
+
+6. Push your topic branch up to your fork:
+
+   ```bash
+   git push origin <topic-branch-name>
+   ```
+
+10. [Open a Pull Request](https://help.github.com/articles/using-pull-requests) with a
+    clear title and description.
+
+
+## Do not…
+
+Please **do not** use the issue tracker for personal support requests (use
+[StackOverflow](http://stackoverflow.com/questions/tagged/html5boilerplate) or IRC).
+
+Please **do not** derail or troll issues. Keep the
+discussion on topic and respect the opinions of others.
diff --git a/docs/system-architecture/apple-touch-icon-114x114-precomposed.png b/docs/system-architecture/apple-touch-icon-114x114-precomposed.png
new file mode 100644
index 0000000..0fdf60e
--- /dev/null
+++ b/docs/system-architecture/apple-touch-icon-114x114-precomposed.png
Binary files differ
diff --git a/docs/system-architecture/apple-touch-icon-144x144-precomposed.png b/docs/system-architecture/apple-touch-icon-144x144-precomposed.png
new file mode 100644
index 0000000..257c77a
--- /dev/null
+++ b/docs/system-architecture/apple-touch-icon-144x144-precomposed.png
Binary files differ
diff --git a/docs/system-architecture/apple-touch-icon-57x57-precomposed.png b/docs/system-architecture/apple-touch-icon-57x57-precomposed.png
new file mode 100644
index 0000000..99b6054
--- /dev/null
+++ b/docs/system-architecture/apple-touch-icon-57x57-precomposed.png
Binary files differ
diff --git a/docs/system-architecture/apple-touch-icon-72x72-precomposed.png b/docs/system-architecture/apple-touch-icon-72x72-precomposed.png
new file mode 100644
index 0000000..8345cc3
--- /dev/null
+++ b/docs/system-architecture/apple-touch-icon-72x72-precomposed.png
Binary files differ
diff --git a/docs/system-architecture/apple-touch-icon-precomposed.png b/docs/system-architecture/apple-touch-icon-precomposed.png
new file mode 100644
index 0000000..99b6054
--- /dev/null
+++ b/docs/system-architecture/apple-touch-icon-precomposed.png
Binary files differ
diff --git a/docs/system-architecture/apple-touch-icon.png b/docs/system-architecture/apple-touch-icon.png
new file mode 100644
index 0000000..99b6054
--- /dev/null
+++ b/docs/system-architecture/apple-touch-icon.png
Binary files differ
diff --git a/docs/system-architecture/crossdomain.xml b/docs/system-architecture/crossdomain.xml
new file mode 100644
index 0000000..29a035d
--- /dev/null
+++ b/docs/system-architecture/crossdomain.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
+<cross-domain-policy>
+    <!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html -->
+
+    <!-- Most restrictive policy: -->
+    <site-control permitted-cross-domain-policies="none"/>
+
+    <!-- Least restrictive policy: -->
+    <!--
+    <site-control permitted-cross-domain-policies="all"/>
+    <allow-access-from domain="*" to-ports="*" secure="false"/>
+    <allow-http-request-headers-from domain="*" headers="*" secure="false"/>
+    -->
+</cross-domain-policy>
diff --git a/docs/system-architecture/css/bootstrap-3.0.0/bootstrap-theme.css b/docs/system-architecture/css/bootstrap-3.0.0/bootstrap-theme.css
new file mode 100644
index 0000000..ad11735
--- /dev/null
+++ b/docs/system-architecture/css/bootstrap-3.0.0/bootstrap-theme.css
@@ -0,0 +1,384 @@
+.btn-default,
+.btn-primary,
+.btn-success,
+.btn-info,
+.btn-warning,
+.btn-danger {
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.btn-default:active,
+.btn-primary:active,
+.btn-success:active,
+.btn-info:active,
+.btn-warning:active,
+.btn-danger:active,
+.btn-default.active,
+.btn-primary.active,
+.btn-success.active,
+.btn-info.active,
+.btn-warning.active,
+.btn-danger.active {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn:active,
+.btn.active {
+  background-image: none;
+}
+
+.btn-default {
+  text-shadow: 0 1px 0 #fff;
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6));
+  background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%);
+  background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%);
+  background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%);
+  background-repeat: repeat-x;
+  border-color: #e0e0e0;
+  border-color: #ccc;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+}
+
+.btn-default:active,
+.btn-default.active {
+  background-color: #e6e6e6;
+  border-color: #e0e0e0;
+}
+
+.btn-primary {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
+  background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
+  background-repeat: repeat-x;
+  border-color: #2d6ca2;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
+}
+
+.btn-primary:active,
+.btn-primary.active {
+  background-color: #3071a9;
+  border-color: #2d6ca2;
+}
+
+.btn-success {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44));
+  background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%);
+  background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+  background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
+  background-repeat: repeat-x;
+  border-color: #419641;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
+}
+
+.btn-success:active,
+.btn-success.active {
+  background-color: #449d44;
+  border-color: #419641;
+}
+
+.btn-warning {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f));
+  background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%);
+  background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+  background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
+  background-repeat: repeat-x;
+  border-color: #eb9316;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
+}
+
+.btn-warning:active,
+.btn-warning.active {
+  background-color: #ec971f;
+  border-color: #eb9316;
+}
+
+.btn-danger {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c));
+  background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%);
+  background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+  background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
+  background-repeat: repeat-x;
+  border-color: #c12e2a;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
+}
+
+.btn-danger:active,
+.btn-danger.active {
+  background-color: #c9302c;
+  border-color: #c12e2a;
+}
+
+.btn-info {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5));
+  background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%);
+  background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+  background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
+  background-repeat: repeat-x;
+  border-color: #2aabd2;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
+}
+
+.btn-info:active,
+.btn-info.active {
+  background-color: #31b0d5;
+  border-color: #2aabd2;
+}
+
+.thumbnail,
+.img-thumbnail {
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus,
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  background-color: #357ebd;
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
+  background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+}
+
+.navbar {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8));
+  background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%);
+  background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
+  background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
+  background-repeat: repeat-x;
+  border-radius: 4px;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
+}
+
+.navbar .navbar-nav > .active > a {
+  background-color: #f8f8f8;
+}
+
+.navbar-brand,
+.navbar-nav > li > a {
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
+}
+
+.navbar-inverse {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222));
+  background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%);
+  background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%);
+  background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
+}
+
+.navbar-inverse .navbar-nav > .active > a {
+  background-color: #222222;
+}
+
+.navbar-inverse .navbar-brand,
+.navbar-inverse .navbar-nav > li > a {
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.navbar-static-top,
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  border-radius: 0;
+}
+
+.alert {
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.alert-success {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc));
+  background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%);
+  background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+  background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
+  background-repeat: repeat-x;
+  border-color: #b2dba1;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
+}
+
+.alert-info {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0));
+  background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%);
+  background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+  background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
+  background-repeat: repeat-x;
+  border-color: #9acfea;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
+}
+
+.alert-warning {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0));
+  background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%);
+  background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+  background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
+  background-repeat: repeat-x;
+  border-color: #f5e79e;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
+}
+
+.alert-danger {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3));
+  background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%);
+  background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+  background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
+  background-repeat: repeat-x;
+  border-color: #dca7a7;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
+}
+
+.progress {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5));
+  background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%);
+  background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+  background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
+}
+
+.progress-bar {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
+  background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
+}
+
+.progress-bar-success {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44));
+  background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%);
+  background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+  background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
+}
+
+.progress-bar-info {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5));
+  background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%);
+  background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+  background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
+}
+
+.progress-bar-warning {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f));
+  background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%);
+  background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+  background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
+}
+
+.progress-bar-danger {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c));
+  background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%);
+  background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+  background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
+}
+
+.list-group {
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+}
+
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+  text-shadow: 0 -1px 0 #3071a9;
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3));
+  background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
+  background-repeat: repeat-x;
+  border-color: #3278b3;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
+}
+
+.panel {
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.panel-default > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8));
+  background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%);
+  background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+}
+
+.panel-primary > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
+  background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+}
+
+.panel-success > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6));
+  background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%);
+  background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+  background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
+}
+
+.panel-info > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3));
+  background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%);
+  background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+  background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
+}
+
+.panel-warning > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc));
+  background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%);
+  background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+  background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
+}
+
+.panel-danger > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc));
+  background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%);
+  background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+  background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
+}
+
+.well {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5));
+  background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%);
+  background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+  background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
+  background-repeat: repeat-x;
+  border-color: #dcdcdc;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
+          box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
+}
\ No newline at end of file
diff --git a/docs/system-architecture/css/bootstrap-3.0.0/bootstrap-theme.min.css b/docs/system-architecture/css/bootstrap-3.0.0/bootstrap-theme.min.css
new file mode 100644
index 0000000..cad36b4
--- /dev/null
+++ b/docs/system-architecture/css/bootstrap-3.0.0/bootstrap-theme.min.css
@@ -0,0 +1 @@
+.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,0%,#e6e6e6,100%);background-image:-moz-linear-gradient(top,#fff 0,#e6e6e6 100%);background-image:linear-gradient(to bottom,#fff 0,#e6e6e6 100%);background-repeat:repeat-x;border-color:#e0e0e0;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0)}.btn-default:active,.btn-default.active{background-color:#e6e6e6;border-color:#e0e0e0}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;border-color:#2d6ca2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.btn-primary:active,.btn-primary.active{background-color:#3071a9;border-color:#2d6ca2}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;border-color:#419641;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.btn-success:active,.btn-success.active{background-color:#449d44;border-color:#419641}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;border-color:#eb9316;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.btn-warning:active,.btn-warning.active{background-color:#ec971f;border-color:#eb9316}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;border-color:#c12e2a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.btn-danger:active,.btn-danger.active{background-color:#c9302c;border-color:#c12e2a}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;border-color:#2aabd2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.btn-info:active,.btn-info.active{background-color:#31b0d5;border-color:#2aabd2}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff,0%,#f8f8f8,100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar .navbar-nav>.active>a{background-color:#f8f8f8}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c,0%,#222,100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0)}.navbar-inverse .navbar-nav>.active>a{background-color:#222}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#c8e5bc,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#b9def0,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#f8efc0,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede,0%,#e7c3c3,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca,0%,#3278b3,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5,0%,#e8e8e8,100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#d0e9c6,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#c4e3f3,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#faf2cc,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede,0%,#ebcccc,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)}
\ No newline at end of file
diff --git a/docs/system-architecture/css/bootstrap-3.0.0/bootstrap.css b/docs/system-architecture/css/bootstrap-3.0.0/bootstrap.css
new file mode 100644
index 0000000..bbda4ee
--- /dev/null
+++ b/docs/system-architecture/css/bootstrap-3.0.0/bootstrap.css
@@ -0,0 +1,6805 @@
+/*!
+ * Bootstrap v3.0.0
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+/*! normalize.css v2.1.0 | MIT License | git.io/normalize */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+  display: block;
+}
+
+audio,
+canvas,
+video {
+  display: inline-block;
+}
+
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+
+[hidden] {
+  display: none;
+}
+
+html {
+  font-family: sans-serif;
+  -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+}
+
+body {
+  margin: 0;
+}
+
+a:focus {
+  outline: thin dotted;
+}
+
+a:active,
+a:hover {
+  outline: 0;
+}
+
+h1 {
+  margin: 0.67em 0;
+  font-size: 2em;
+}
+
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+
+b,
+strong {
+  font-weight: bold;
+}
+
+dfn {
+  font-style: italic;
+}
+
+hr {
+  height: 0;
+  -moz-box-sizing: content-box;
+       box-sizing: content-box;
+}
+
+mark {
+  color: #000;
+  background: #ff0;
+}
+
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, serif;
+  font-size: 1em;
+}
+
+pre {
+  white-space: pre-wrap;
+}
+
+q {
+  quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+small {
+  font-size: 80%;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+img {
+  border: 0;
+}
+
+svg:not(:root) {
+  overflow: hidden;
+}
+
+figure {
+  margin: 0;
+}
+
+fieldset {
+  padding: 0.35em 0.625em 0.75em;
+  margin: 0 2px;
+  border: 1px solid #c0c0c0;
+}
+
+legend {
+  padding: 0;
+  border: 0;
+}
+
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-family: inherit;
+  font-size: 100%;
+}
+
+button,
+input {
+  line-height: normal;
+}
+
+button,
+select {
+  text-transform: none;
+}
+
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  cursor: pointer;
+  -webkit-appearance: button;
+}
+
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+  padding: 0;
+  box-sizing: border-box;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+@media print {
+  * {
+    color: #000 !important;
+    text-shadow: none !important;
+    background: transparent !important;
+    box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  .ir a:after,
+  a[href^="javascript:"]:after,
+  a[href^="#"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  @page  {
+    margin: 2cm .5cm;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+  .navbar {
+    display: none;
+  }
+  .table td,
+  .table th {
+    background-color: #fff !important;
+  }
+  .btn > .caret,
+  .dropup > .btn > .caret {
+    border-top-color: #000 !important;
+  }
+  .label {
+    border: 1px solid #000;
+  }
+  .table {
+    border-collapse: collapse !important;
+  }
+  .table-bordered th,
+  .table-bordered td {
+    border: 1px solid #ddd !important;
+  }
+}
+
+*,
+*:before,
+*:after {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+html {
+  font-size: 62.5%;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #333333;
+  background-color: #ffffff;
+}
+
+input,
+button,
+select,
+textarea {
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+button,
+input,
+select[multiple],
+textarea {
+  background-image: none;
+}
+
+a {
+  color: #428bca;
+  text-decoration: none;
+}
+
+a:hover,
+a:focus {
+  color: #2a6496;
+  text-decoration: underline;
+}
+
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+img {
+  vertical-align: middle;
+}
+
+.img-responsive {
+  display: block;
+  height: auto;
+  max-width: 100%;
+}
+
+.img-rounded {
+  border-radius: 6px;
+}
+
+.img-thumbnail {
+  display: inline-block;
+  height: auto;
+  max-width: 100%;
+  padding: 4px;
+  line-height: 1.428571429;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-transition: all 0.2s ease-in-out;
+          transition: all 0.2s ease-in-out;
+}
+
+.img-circle {
+  border-radius: 50%;
+}
+
+hr {
+  margin-top: 20px;
+  margin-bottom: 20px;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+}
+
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0 0 0 0);
+  border: 0;
+}
+
+p {
+  margin: 0 0 10px;
+}
+
+.lead {
+  margin-bottom: 20px;
+  font-size: 16.099999999999998px;
+  font-weight: 200;
+  line-height: 1.4;
+}
+
+@media (min-width: 768px) {
+  .lead {
+    font-size: 21px;
+  }
+}
+
+small {
+  font-size: 85%;
+}
+
+cite {
+  font-style: normal;
+}
+
+.text-muted {
+  color: #999999;
+}
+
+.text-primary {
+  color: #428bca;
+}
+
+.text-warning {
+  color: #c09853;
+}
+
+.text-danger {
+  color: #b94a48;
+}
+
+.text-success {
+  color: #468847;
+}
+
+.text-info {
+  color: #3a87ad;
+}
+
+.text-left {
+  text-align: left;
+}
+
+.text-right {
+  text-align: right;
+}
+
+.text-center {
+  text-align: center;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-weight: 500;
+  line-height: 1.1;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small {
+  font-weight: normal;
+  line-height: 1;
+  color: #999999;
+}
+
+h1,
+h2,
+h3 {
+  margin-top: 20px;
+  margin-bottom: 10px;
+}
+
+h4,
+h5,
+h6 {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+h1,
+.h1 {
+  font-size: 36px;
+}
+
+h2,
+.h2 {
+  font-size: 30px;
+}
+
+h3,
+.h3 {
+  font-size: 24px;
+}
+
+h4,
+.h4 {
+  font-size: 18px;
+}
+
+h5,
+.h5 {
+  font-size: 14px;
+}
+
+h6,
+.h6 {
+  font-size: 12px;
+}
+
+h1 small,
+.h1 small {
+  font-size: 24px;
+}
+
+h2 small,
+.h2 small {
+  font-size: 18px;
+}
+
+h3 small,
+.h3 small,
+h4 small,
+.h4 small {
+  font-size: 14px;
+}
+
+.page-header {
+  padding-bottom: 9px;
+  margin: 40px 0 20px;
+  border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+  margin-top: 0;
+  margin-bottom: 10px;
+}
+
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+  margin-bottom: 0;
+}
+
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline > li {
+  display: inline-block;
+  padding-right: 5px;
+  padding-left: 5px;
+}
+
+dl {
+  margin-bottom: 20px;
+}
+
+dt,
+dd {
+  line-height: 1.428571429;
+}
+
+dt {
+  font-weight: bold;
+}
+
+dd {
+  margin-left: 0;
+}
+
+@media (min-width: 768px) {
+  .dl-horizontal dt {
+    float: left;
+    width: 160px;
+    overflow: hidden;
+    clear: left;
+    text-align: right;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .dl-horizontal dd {
+    margin-left: 180px;
+  }
+  .dl-horizontal dd:before,
+  .dl-horizontal dd:after {
+    display: table;
+    content: " ";
+  }
+  .dl-horizontal dd:after {
+    clear: both;
+  }
+  .dl-horizontal dd:before,
+  .dl-horizontal dd:after {
+    display: table;
+    content: " ";
+  }
+  .dl-horizontal dd:after {
+    clear: both;
+  }
+}
+
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+
+blockquote {
+  padding: 10px 20px;
+  margin: 0 0 20px;
+  border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+  font-size: 17.5px;
+  font-weight: 300;
+  line-height: 1.25;
+}
+
+blockquote p:last-child {
+  margin-bottom: 0;
+}
+
+blockquote small {
+  display: block;
+  line-height: 1.428571429;
+  color: #999999;
+}
+
+blockquote small:before {
+  content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid #eeeeee;
+  border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small {
+  text-align: right;
+}
+
+blockquote.pull-right small:before {
+  content: '';
+}
+
+blockquote.pull-right small:after {
+  content: '\00A0 \2014';
+}
+
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+  content: "";
+}
+
+address {
+  display: block;
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 1.428571429;
+}
+
+code,
+pre {
+  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+}
+
+code {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: #c7254e;
+  white-space: nowrap;
+  background-color: #f9f2f4;
+  border-radius: 4px;
+}
+
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 1.428571429;
+  color: #333333;
+  word-break: break-all;
+  word-wrap: break-word;
+  background-color: #f5f5f5;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+}
+
+pre.prettyprint {
+  margin-bottom: 20px;
+}
+
+pre code {
+  padding: 0;
+  font-size: inherit;
+  color: inherit;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border: 0;
+}
+
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+
+.container {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  content: " ";
+}
+
+.container:after {
+  clear: both;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  content: " ";
+}
+
+.container:after {
+  clear: both;
+}
+
+.row {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  content: " ";
+}
+
+.row:after {
+  clear: both;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  content: " ";
+}
+
+.row:after {
+  clear: both;
+}
+
+.col-xs-1,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.col-xs-10,
+.col-xs-11,
+.col-xs-12,
+.col-sm-1,
+.col-sm-2,
+.col-sm-3,
+.col-sm-4,
+.col-sm-5,
+.col-sm-6,
+.col-sm-7,
+.col-sm-8,
+.col-sm-9,
+.col-sm-10,
+.col-sm-11,
+.col-sm-12,
+.col-md-1,
+.col-md-2,
+.col-md-3,
+.col-md-4,
+.col-md-5,
+.col-md-6,
+.col-md-7,
+.col-md-8,
+.col-md-9,
+.col-md-10,
+.col-md-11,
+.col-md-12,
+.col-lg-1,
+.col-lg-2,
+.col-lg-3,
+.col-lg-4,
+.col-lg-5,
+.col-lg-6,
+.col-lg-7,
+.col-lg-8,
+.col-lg-9,
+.col-lg-10,
+.col-lg-11,
+.col-lg-12 {
+  position: relative;
+  min-height: 1px;
+  padding-right: 15px;
+  padding-left: 15px;
+}
+
+.col-xs-1,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.col-xs-10,
+.col-xs-11 {
+  float: left;
+}
+
+.col-xs-1 {
+  width: 8.333333333333332%;
+}
+
+.col-xs-2 {
+  width: 16.666666666666664%;
+}
+
+.col-xs-3 {
+  width: 25%;
+}
+
+.col-xs-4 {
+  width: 33.33333333333333%;
+}
+
+.col-xs-5 {
+  width: 41.66666666666667%;
+}
+
+.col-xs-6 {
+  width: 50%;
+}
+
+.col-xs-7 {
+  width: 58.333333333333336%;
+}
+
+.col-xs-8 {
+  width: 66.66666666666666%;
+}
+
+.col-xs-9 {
+  width: 75%;
+}
+
+.col-xs-10 {
+  width: 83.33333333333334%;
+}
+
+.col-xs-11 {
+  width: 91.66666666666666%;
+}
+
+.col-xs-12 {
+  width: 100%;
+}
+
+@media (min-width: 768px) {
+  .container {
+    max-width: 750px;
+  }
+  .col-sm-1,
+  .col-sm-2,
+  .col-sm-3,
+  .col-sm-4,
+  .col-sm-5,
+  .col-sm-6,
+  .col-sm-7,
+  .col-sm-8,
+  .col-sm-9,
+  .col-sm-10,
+  .col-sm-11 {
+    float: left;
+  }
+  .col-sm-1 {
+    width: 8.333333333333332%;
+  }
+  .col-sm-2 {
+    width: 16.666666666666664%;
+  }
+  .col-sm-3 {
+    width: 25%;
+  }
+  .col-sm-4 {
+    width: 33.33333333333333%;
+  }
+  .col-sm-5 {
+    width: 41.66666666666667%;
+  }
+  .col-sm-6 {
+    width: 50%;
+  }
+  .col-sm-7 {
+    width: 58.333333333333336%;
+  }
+  .col-sm-8 {
+    width: 66.66666666666666%;
+  }
+  .col-sm-9 {
+    width: 75%;
+  }
+  .col-sm-10 {
+    width: 83.33333333333334%;
+  }
+  .col-sm-11 {
+    width: 91.66666666666666%;
+  }
+  .col-sm-12 {
+    width: 100%;
+  }
+  .col-sm-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-sm-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-sm-push-3 {
+    left: 25%;
+  }
+  .col-sm-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-sm-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-sm-push-6 {
+    left: 50%;
+  }
+  .col-sm-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-sm-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-sm-push-9 {
+    left: 75%;
+  }
+  .col-sm-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-sm-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-sm-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-sm-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-sm-pull-3 {
+    right: 25%;
+  }
+  .col-sm-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-sm-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-sm-pull-6 {
+    right: 50%;
+  }
+  .col-sm-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-sm-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-sm-pull-9 {
+    right: 75%;
+  }
+  .col-sm-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-sm-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-sm-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-sm-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-sm-offset-3 {
+    margin-left: 25%;
+  }
+  .col-sm-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-sm-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-sm-offset-6 {
+    margin-left: 50%;
+  }
+  .col-sm-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-sm-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-sm-offset-9 {
+    margin-left: 75%;
+  }
+  .col-sm-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-sm-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+}
+
+@media (min-width: 992px) {
+  .container {
+    max-width: 970px;
+  }
+  .col-md-1,
+  .col-md-2,
+  .col-md-3,
+  .col-md-4,
+  .col-md-5,
+  .col-md-6,
+  .col-md-7,
+  .col-md-8,
+  .col-md-9,
+  .col-md-10,
+  .col-md-11 {
+    float: left;
+  }
+  .col-md-1 {
+    width: 8.333333333333332%;
+  }
+  .col-md-2 {
+    width: 16.666666666666664%;
+  }
+  .col-md-3 {
+    width: 25%;
+  }
+  .col-md-4 {
+    width: 33.33333333333333%;
+  }
+  .col-md-5 {
+    width: 41.66666666666667%;
+  }
+  .col-md-6 {
+    width: 50%;
+  }
+  .col-md-7 {
+    width: 58.333333333333336%;
+  }
+  .col-md-8 {
+    width: 66.66666666666666%;
+  }
+  .col-md-9 {
+    width: 75%;
+  }
+  .col-md-10 {
+    width: 83.33333333333334%;
+  }
+  .col-md-11 {
+    width: 91.66666666666666%;
+  }
+  .col-md-12 {
+    width: 100%;
+  }
+  .col-md-push-0 {
+    left: auto;
+  }
+  .col-md-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-md-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-md-push-3 {
+    left: 25%;
+  }
+  .col-md-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-md-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-md-push-6 {
+    left: 50%;
+  }
+  .col-md-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-md-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-md-push-9 {
+    left: 75%;
+  }
+  .col-md-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-md-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-md-pull-0 {
+    right: auto;
+  }
+  .col-md-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-md-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-md-pull-3 {
+    right: 25%;
+  }
+  .col-md-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-md-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-md-pull-6 {
+    right: 50%;
+  }
+  .col-md-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-md-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-md-pull-9 {
+    right: 75%;
+  }
+  .col-md-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-md-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-md-offset-0 {
+    margin-left: 0;
+  }
+  .col-md-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-md-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-md-offset-3 {
+    margin-left: 25%;
+  }
+  .col-md-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-md-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-md-offset-6 {
+    margin-left: 50%;
+  }
+  .col-md-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-md-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-md-offset-9 {
+    margin-left: 75%;
+  }
+  .col-md-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-md-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+}
+
+@media (min-width: 1200px) {
+  .container {
+    max-width: 1170px;
+  }
+  .col-lg-1,
+  .col-lg-2,
+  .col-lg-3,
+  .col-lg-4,
+  .col-lg-5,
+  .col-lg-6,
+  .col-lg-7,
+  .col-lg-8,
+  .col-lg-9,
+  .col-lg-10,
+  .col-lg-11 {
+    float: left;
+  }
+  .col-lg-1 {
+    width: 8.333333333333332%;
+  }
+  .col-lg-2 {
+    width: 16.666666666666664%;
+  }
+  .col-lg-3 {
+    width: 25%;
+  }
+  .col-lg-4 {
+    width: 33.33333333333333%;
+  }
+  .col-lg-5 {
+    width: 41.66666666666667%;
+  }
+  .col-lg-6 {
+    width: 50%;
+  }
+  .col-lg-7 {
+    width: 58.333333333333336%;
+  }
+  .col-lg-8 {
+    width: 66.66666666666666%;
+  }
+  .col-lg-9 {
+    width: 75%;
+  }
+  .col-lg-10 {
+    width: 83.33333333333334%;
+  }
+  .col-lg-11 {
+    width: 91.66666666666666%;
+  }
+  .col-lg-12 {
+    width: 100%;
+  }
+  .col-lg-push-0 {
+    left: auto;
+  }
+  .col-lg-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-lg-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-lg-push-3 {
+    left: 25%;
+  }
+  .col-lg-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-lg-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-lg-push-6 {
+    left: 50%;
+  }
+  .col-lg-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-lg-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-lg-push-9 {
+    left: 75%;
+  }
+  .col-lg-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-lg-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-lg-pull-0 {
+    right: auto;
+  }
+  .col-lg-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-lg-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-lg-pull-3 {
+    right: 25%;
+  }
+  .col-lg-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-lg-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-lg-pull-6 {
+    right: 50%;
+  }
+  .col-lg-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-lg-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-lg-pull-9 {
+    right: 75%;
+  }
+  .col-lg-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-lg-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-lg-offset-0 {
+    margin-left: 0;
+  }
+  .col-lg-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-lg-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-lg-offset-3 {
+    margin-left: 25%;
+  }
+  .col-lg-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-lg-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-lg-offset-6 {
+    margin-left: 50%;
+  }
+  .col-lg-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-lg-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-lg-offset-9 {
+    margin-left: 75%;
+  }
+  .col-lg-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-lg-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+}
+
+table {
+  max-width: 100%;
+  background-color: transparent;
+}
+
+th {
+  text-align: left;
+}
+
+.table {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.table thead > tr > th,
+.table tbody > tr > th,
+.table tfoot > tr > th,
+.table thead > tr > td,
+.table tbody > tr > td,
+.table tfoot > tr > td {
+  padding: 8px;
+  line-height: 1.428571429;
+  vertical-align: top;
+  border-top: 1px solid #dddddd;
+}
+
+.table thead > tr > th {
+  vertical-align: bottom;
+  border-bottom: 2px solid #dddddd;
+}
+
+.table caption + thead tr:first-child th,
+.table colgroup + thead tr:first-child th,
+.table thead:first-child tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child td {
+  border-top: 0;
+}
+
+.table tbody + tbody {
+  border-top: 2px solid #dddddd;
+}
+
+.table .table {
+  background-color: #ffffff;
+}
+
+.table-condensed thead > tr > th,
+.table-condensed tbody > tr > th,
+.table-condensed tfoot > tr > th,
+.table-condensed thead > tr > td,
+.table-condensed tbody > tr > td,
+.table-condensed tfoot > tr > td {
+  padding: 5px;
+}
+
+.table-bordered {
+  border: 1px solid #dddddd;
+}
+
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+  border: 1px solid #dddddd;
+}
+
+.table-bordered > thead > tr > th,
+.table-bordered > thead > tr > td {
+  border-bottom-width: 2px;
+}
+
+.table-striped > tbody > tr:nth-child(odd) > td,
+.table-striped > tbody > tr:nth-child(odd) > th {
+  background-color: #f9f9f9;
+}
+
+.table-hover > tbody > tr:hover > td,
+.table-hover > tbody > tr:hover > th {
+  background-color: #f5f5f5;
+}
+
+table col[class*="col-"] {
+  display: table-column;
+  float: none;
+}
+
+table td[class*="col-"],
+table th[class*="col-"] {
+  display: table-cell;
+  float: none;
+}
+
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+  background-color: #f5f5f5;
+}
+
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td {
+  background-color: #d0e9c6;
+  border-color: #c9e2b3;
+}
+
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td {
+  background-color: #ebcccc;
+  border-color: #e6c1c7;
+}
+
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+  background-color: #fcf8e3;
+  border-color: #fbeed5;
+}
+
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td {
+  background-color: #faf2cc;
+  border-color: #f8e5be;
+}
+
+@media (max-width: 768px) {
+  .table-responsive {
+    width: 100%;
+    margin-bottom: 15px;
+    overflow-x: scroll;
+    overflow-y: hidden;
+    border: 1px solid #dddddd;
+  }
+  .table-responsive > .table {
+    margin-bottom: 0;
+    background-color: #fff;
+  }
+  .table-responsive > .table > thead > tr > th,
+  .table-responsive > .table > tbody > tr > th,
+  .table-responsive > .table > tfoot > tr > th,
+  .table-responsive > .table > thead > tr > td,
+  .table-responsive > .table > tbody > tr > td,
+  .table-responsive > .table > tfoot > tr > td {
+    white-space: nowrap;
+  }
+  .table-responsive > .table-bordered {
+    border: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:first-child,
+  .table-responsive > .table-bordered > tbody > tr > th:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+  .table-responsive > .table-bordered > thead > tr > td:first-child,
+  .table-responsive > .table-bordered > tbody > tr > td:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+    border-left: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:last-child,
+  .table-responsive > .table-bordered > tbody > tr > th:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+  .table-responsive > .table-bordered > thead > tr > td:last-child,
+  .table-responsive > .table-bordered > tbody > tr > td:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+    border-right: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr:last-child > th,
+  .table-responsive > .table-bordered > tbody > tr:last-child > th,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+  .table-responsive > .table-bordered > thead > tr:last-child > td,
+  .table-responsive > .table-bordered > tbody > tr:last-child > td,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+    border-bottom: 0;
+  }
+}
+
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: inherit;
+  color: #333333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+label {
+  display: inline-block;
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9;
+  /* IE8-9 */
+
+  line-height: normal;
+}
+
+input[type="file"] {
+  display: block;
+}
+
+select[multiple],
+select[size] {
+  height: auto;
+}
+
+select optgroup {
+  font-family: inherit;
+  font-size: inherit;
+  font-style: inherit;
+}
+
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+  height: auto;
+}
+
+.form-control:-moz-placeholder {
+  color: #999999;
+}
+
+.form-control::-moz-placeholder {
+  color: #999999;
+}
+
+.form-control:-ms-input-placeholder {
+  color: #999999;
+}
+
+.form-control::-webkit-input-placeholder {
+  color: #999999;
+}
+
+.form-control {
+  display: block;
+  width: 100%;
+  height: 34px;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #555555;
+  vertical-align: middle;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+          transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+}
+
+.form-control:focus {
+  border-color: #66afe9;
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+  cursor: not-allowed;
+  background-color: #eeeeee;
+}
+
+textarea.form-control {
+  height: auto;
+}
+
+.form-group {
+  margin-bottom: 15px;
+}
+
+.radio,
+.checkbox {
+  display: block;
+  min-height: 20px;
+  padding-left: 20px;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  vertical-align: middle;
+}
+
+.radio label,
+.checkbox label {
+  display: inline;
+  margin-bottom: 0;
+  font-weight: normal;
+  cursor: pointer;
+}
+
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+  float: left;
+  margin-left: -20px;
+}
+
+.radio + .radio,
+.checkbox + .checkbox {
+  margin-top: -5px;
+}
+
+.radio-inline,
+.checkbox-inline {
+  display: inline-block;
+  padding-left: 20px;
+  margin-bottom: 0;
+  font-weight: normal;
+  vertical-align: middle;
+  cursor: pointer;
+}
+
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+  margin-top: 0;
+  margin-left: 10px;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+.radio[disabled],
+.radio-inline[disabled],
+.checkbox[disabled],
+.checkbox-inline[disabled],
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"],
+fieldset[disabled] .radio,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] .checkbox,
+fieldset[disabled] .checkbox-inline {
+  cursor: not-allowed;
+}
+
+.input-sm {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+select.input-sm {
+  height: 30px;
+  line-height: 30px;
+}
+
+textarea.input-sm {
+  height: auto;
+}
+
+.input-lg {
+  height: 45px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+select.input-lg {
+  height: 45px;
+  line-height: 45px;
+}
+
+textarea.input-lg {
+  height: auto;
+}
+
+.has-warning .help-block,
+.has-warning .control-label {
+  color: #c09853;
+}
+
+.has-warning .form-control {
+  border-color: #c09853;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-warning .form-control:focus {
+  border-color: #a47e3c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.has-warning .input-group-addon {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #c09853;
+}
+
+.has-error .help-block,
+.has-error .control-label {
+  color: #b94a48;
+}
+
+.has-error .form-control {
+  border-color: #b94a48;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-error .form-control:focus {
+  border-color: #953b39;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.has-error .input-group-addon {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #b94a48;
+}
+
+.has-success .help-block,
+.has-success .control-label {
+  color: #468847;
+}
+
+.has-success .form-control {
+  border-color: #468847;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-success .form-control:focus {
+  border-color: #356635;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.has-success .input-group-addon {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #468847;
+}
+
+.form-control-static {
+  padding-top: 7px;
+  margin-bottom: 0;
+}
+
+.help-block {
+  display: block;
+  margin-top: 5px;
+  margin-bottom: 10px;
+  color: #737373;
+}
+
+@media (min-width: 768px) {
+  .form-inline .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .form-control {
+    display: inline-block;
+  }
+  .form-inline .radio,
+  .form-inline .checkbox {
+    display: inline-block;
+    padding-left: 0;
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+  .form-inline .radio input[type="radio"],
+  .form-inline .checkbox input[type="checkbox"] {
+    float: none;
+    margin-left: 0;
+  }
+}
+
+.form-horizontal .control-label,
+.form-horizontal .radio,
+.form-horizontal .checkbox,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+  padding-top: 7px;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.form-horizontal .form-group {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  display: table;
+  content: " ";
+}
+
+.form-horizontal .form-group:after {
+  clear: both;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  display: table;
+  content: " ";
+}
+
+.form-horizontal .form-group:after {
+  clear: both;
+}
+
+@media (min-width: 768px) {
+  .form-horizontal .control-label {
+    text-align: right;
+  }
+}
+
+.btn {
+  display: inline-block;
+  padding: 6px 12px;
+  margin-bottom: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.428571429;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  cursor: pointer;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+       -o-user-select: none;
+          user-select: none;
+}
+
+.btn:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+.btn:hover,
+.btn:focus {
+  color: #333333;
+  text-decoration: none;
+}
+
+.btn:active,
+.btn.active {
+  background-image: none;
+  outline: 0;
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+  pointer-events: none;
+  cursor: not-allowed;
+  opacity: 0.65;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-default {
+  color: #333333;
+  background-color: #ffffff;
+  border-color: #cccccc;
+}
+
+.btn-default:hover,
+.btn-default:focus,
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+  color: #333333;
+  background-color: #ebebeb;
+  border-color: #adadad;
+}
+
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+  background-image: none;
+}
+
+.btn-default.disabled,
+.btn-default[disabled],
+fieldset[disabled] .btn-default,
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled:active,
+.btn-default[disabled]:active,
+fieldset[disabled] .btn-default:active,
+.btn-default.disabled.active,
+.btn-default[disabled].active,
+fieldset[disabled] .btn-default.active {
+  background-color: #ffffff;
+  border-color: #cccccc;
+}
+
+.btn-primary {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  color: #ffffff;
+  background-color: #3276b1;
+  border-color: #285e8e;
+}
+
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  background-image: none;
+}
+
+.btn-primary.disabled,
+.btn-primary[disabled],
+fieldset[disabled] .btn-primary,
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled:active,
+.btn-primary[disabled]:active,
+fieldset[disabled] .btn-primary:active,
+.btn-primary.disabled.active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary.active {
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+
+.btn-warning {
+  color: #ffffff;
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+  color: #ffffff;
+  background-color: #ed9c28;
+  border-color: #d58512;
+}
+
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+  background-image: none;
+}
+
+.btn-warning.disabled,
+.btn-warning[disabled],
+fieldset[disabled] .btn-warning,
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled:active,
+.btn-warning[disabled]:active,
+fieldset[disabled] .btn-warning:active,
+.btn-warning.disabled.active,
+.btn-warning[disabled].active,
+fieldset[disabled] .btn-warning.active {
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+
+.btn-danger {
+  color: #ffffff;
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+  color: #ffffff;
+  background-color: #d2322d;
+  border-color: #ac2925;
+}
+
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+  background-image: none;
+}
+
+.btn-danger.disabled,
+.btn-danger[disabled],
+fieldset[disabled] .btn-danger,
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled:active,
+.btn-danger[disabled]:active,
+fieldset[disabled] .btn-danger:active,
+.btn-danger.disabled.active,
+.btn-danger[disabled].active,
+fieldset[disabled] .btn-danger.active {
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+
+.btn-success {
+  color: #ffffff;
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+  color: #ffffff;
+  background-color: #47a447;
+  border-color: #398439;
+}
+
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+  background-image: none;
+}
+
+.btn-success.disabled,
+.btn-success[disabled],
+fieldset[disabled] .btn-success,
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled:active,
+.btn-success[disabled]:active,
+fieldset[disabled] .btn-success:active,
+.btn-success.disabled.active,
+.btn-success[disabled].active,
+fieldset[disabled] .btn-success.active {
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+
+.btn-info {
+  color: #ffffff;
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+  color: #ffffff;
+  background-color: #39b3d7;
+  border-color: #269abc;
+}
+
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+  background-image: none;
+}
+
+.btn-info.disabled,
+.btn-info[disabled],
+fieldset[disabled] .btn-info,
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled:active,
+.btn-info[disabled]:active,
+fieldset[disabled] .btn-info:active,
+.btn-info.disabled.active,
+.btn-info[disabled].active,
+fieldset[disabled] .btn-info.active {
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+
+.btn-link {
+  font-weight: normal;
+  color: #428bca;
+  cursor: pointer;
+  border-radius: 0;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+  background-color: transparent;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+  border-color: transparent;
+}
+
+.btn-link:hover,
+.btn-link:focus {
+  color: #2a6496;
+  text-decoration: underline;
+  background-color: transparent;
+}
+
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+  color: #999999;
+  text-decoration: none;
+}
+
+.btn-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+.btn-sm,
+.btn-xs {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-xs {
+  padding: 1px 5px;
+}
+
+.btn-block {
+  display: block;
+  width: 100%;
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity 0.15s linear;
+          transition: opacity 0.15s linear;
+}
+
+.fade.in {
+  opacity: 1;
+}
+
+.collapse {
+  display: none;
+}
+
+.collapse.in {
+  display: block;
+}
+
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition: height 0.35s ease;
+          transition: height 0.35s ease;
+}
+
+@font-face {
+  font-family: 'Glyphicons Halflings';
+  src: url('../fonts/glyphicons-halflings-regular.eot');
+  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
+}
+
+.glyphicon {
+  position: relative;
+  top: 1px;
+  display: inline-block;
+  font-family: 'Glyphicons Halflings';
+  -webkit-font-smoothing: antialiased;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+}
+
+.glyphicon-asterisk:before {
+  content: "\2a";
+}
+
+.glyphicon-plus:before {
+  content: "\2b";
+}
+
+.glyphicon-euro:before {
+  content: "\20ac";
+}
+
+.glyphicon-minus:before {
+  content: "\2212";
+}
+
+.glyphicon-cloud:before {
+  content: "\2601";
+}
+
+.glyphicon-envelope:before {
+  content: "\2709";
+}
+
+.glyphicon-pencil:before {
+  content: "\270f";
+}
+
+.glyphicon-glass:before {
+  content: "\e001";
+}
+
+.glyphicon-music:before {
+  content: "\e002";
+}
+
+.glyphicon-search:before {
+  content: "\e003";
+}
+
+.glyphicon-heart:before {
+  content: "\e005";
+}
+
+.glyphicon-star:before {
+  content: "\e006";
+}
+
+.glyphicon-star-empty:before {
+  content: "\e007";
+}
+
+.glyphicon-user:before {
+  content: "\e008";
+}
+
+.glyphicon-film:before {
+  content: "\e009";
+}
+
+.glyphicon-th-large:before {
+  content: "\e010";
+}
+
+.glyphicon-th:before {
+  content: "\e011";
+}
+
+.glyphicon-th-list:before {
+  content: "\e012";
+}
+
+.glyphicon-ok:before {
+  content: "\e013";
+}
+
+.glyphicon-remove:before {
+  content: "\e014";
+}
+
+.glyphicon-zoom-in:before {
+  content: "\e015";
+}
+
+.glyphicon-zoom-out:before {
+  content: "\e016";
+}
+
+.glyphicon-off:before {
+  content: "\e017";
+}
+
+.glyphicon-signal:before {
+  content: "\e018";
+}
+
+.glyphicon-cog:before {
+  content: "\e019";
+}
+
+.glyphicon-trash:before {
+  content: "\e020";
+}
+
+.glyphicon-home:before {
+  content: "\e021";
+}
+
+.glyphicon-file:before {
+  content: "\e022";
+}
+
+.glyphicon-time:before {
+  content: "\e023";
+}
+
+.glyphicon-road:before {
+  content: "\e024";
+}
+
+.glyphicon-download-alt:before {
+  content: "\e025";
+}
+
+.glyphicon-download:before {
+  content: "\e026";
+}
+
+.glyphicon-upload:before {
+  content: "\e027";
+}
+
+.glyphicon-inbox:before {
+  content: "\e028";
+}
+
+.glyphicon-play-circle:before {
+  content: "\e029";
+}
+
+.glyphicon-repeat:before {
+  content: "\e030";
+}
+
+.glyphicon-refresh:before {
+  content: "\e031";
+}
+
+.glyphicon-list-alt:before {
+  content: "\e032";
+}
+
+.glyphicon-flag:before {
+  content: "\e034";
+}
+
+.glyphicon-headphones:before {
+  content: "\e035";
+}
+
+.glyphicon-volume-off:before {
+  content: "\e036";
+}
+
+.glyphicon-volume-down:before {
+  content: "\e037";
+}
+
+.glyphicon-volume-up:before {
+  content: "\e038";
+}
+
+.glyphicon-qrcode:before {
+  content: "\e039";
+}
+
+.glyphicon-barcode:before {
+  content: "\e040";
+}
+
+.glyphicon-tag:before {
+  content: "\e041";
+}
+
+.glyphicon-tags:before {
+  content: "\e042";
+}
+
+.glyphicon-book:before {
+  content: "\e043";
+}
+
+.glyphicon-print:before {
+  content: "\e045";
+}
+
+.glyphicon-font:before {
+  content: "\e047";
+}
+
+.glyphicon-bold:before {
+  content: "\e048";
+}
+
+.glyphicon-italic:before {
+  content: "\e049";
+}
+
+.glyphicon-text-height:before {
+  content: "\e050";
+}
+
+.glyphicon-text-width:before {
+  content: "\e051";
+}
+
+.glyphicon-align-left:before {
+  content: "\e052";
+}
+
+.glyphicon-align-center:before {
+  content: "\e053";
+}
+
+.glyphicon-align-right:before {
+  content: "\e054";
+}
+
+.glyphicon-align-justify:before {
+  content: "\e055";
+}
+
+.glyphicon-list:before {
+  content: "\e056";
+}
+
+.glyphicon-indent-left:before {
+  content: "\e057";
+}
+
+.glyphicon-indent-right:before {
+  content: "\e058";
+}
+
+.glyphicon-facetime-video:before {
+  content: "\e059";
+}
+
+.glyphicon-picture:before {
+  content: "\e060";
+}
+
+.glyphicon-map-marker:before {
+  content: "\e062";
+}
+
+.glyphicon-adjust:before {
+  content: "\e063";
+}
+
+.glyphicon-tint:before {
+  content: "\e064";
+}
+
+.glyphicon-edit:before {
+  content: "\e065";
+}
+
+.glyphicon-share:before {
+  content: "\e066";
+}
+
+.glyphicon-check:before {
+  content: "\e067";
+}
+
+.glyphicon-move:before {
+  content: "\e068";
+}
+
+.glyphicon-step-backward:before {
+  content: "\e069";
+}
+
+.glyphicon-fast-backward:before {
+  content: "\e070";
+}
+
+.glyphicon-backward:before {
+  content: "\e071";
+}
+
+.glyphicon-play:before {
+  content: "\e072";
+}
+
+.glyphicon-pause:before {
+  content: "\e073";
+}
+
+.glyphicon-stop:before {
+  content: "\e074";
+}
+
+.glyphicon-forward:before {
+  content: "\e075";
+}
+
+.glyphicon-fast-forward:before {
+  content: "\e076";
+}
+
+.glyphicon-step-forward:before {
+  content: "\e077";
+}
+
+.glyphicon-eject:before {
+  content: "\e078";
+}
+
+.glyphicon-chevron-left:before {
+  content: "\e079";
+}
+
+.glyphicon-chevron-right:before {
+  content: "\e080";
+}
+
+.glyphicon-plus-sign:before {
+  content: "\e081";
+}
+
+.glyphicon-minus-sign:before {
+  content: "\e082";
+}
+
+.glyphicon-remove-sign:before {
+  content: "\e083";
+}
+
+.glyphicon-ok-sign:before {
+  content: "\e084";
+}
+
+.glyphicon-question-sign:before {
+  content: "\e085";
+}
+
+.glyphicon-info-sign:before {
+  content: "\e086";
+}
+
+.glyphicon-screenshot:before {
+  content: "\e087";
+}
+
+.glyphicon-remove-circle:before {
+  content: "\e088";
+}
+
+.glyphicon-ok-circle:before {
+  content: "\e089";
+}
+
+.glyphicon-ban-circle:before {
+  content: "\e090";
+}
+
+.glyphicon-arrow-left:before {
+  content: "\e091";
+}
+
+.glyphicon-arrow-right:before {
+  content: "\e092";
+}
+
+.glyphicon-arrow-up:before {
+  content: "\e093";
+}
+
+.glyphicon-arrow-down:before {
+  content: "\e094";
+}
+
+.glyphicon-share-alt:before {
+  content: "\e095";
+}
+
+.glyphicon-resize-full:before {
+  content: "\e096";
+}
+
+.glyphicon-resize-small:before {
+  content: "\e097";
+}
+
+.glyphicon-exclamation-sign:before {
+  content: "\e101";
+}
+
+.glyphicon-gift:before {
+  content: "\e102";
+}
+
+.glyphicon-leaf:before {
+  content: "\e103";
+}
+
+.glyphicon-eye-open:before {
+  content: "\e105";
+}
+
+.glyphicon-eye-close:before {
+  content: "\e106";
+}
+
+.glyphicon-warning-sign:before {
+  content: "\e107";
+}
+
+.glyphicon-plane:before {
+  content: "\e108";
+}
+
+.glyphicon-random:before {
+  content: "\e110";
+}
+
+.glyphicon-comment:before {
+  content: "\e111";
+}
+
+.glyphicon-magnet:before {
+  content: "\e112";
+}
+
+.glyphicon-chevron-up:before {
+  content: "\e113";
+}
+
+.glyphicon-chevron-down:before {
+  content: "\e114";
+}
+
+.glyphicon-retweet:before {
+  content: "\e115";
+}
+
+.glyphicon-shopping-cart:before {
+  content: "\e116";
+}
+
+.glyphicon-folder-close:before {
+  content: "\e117";
+}
+
+.glyphicon-folder-open:before {
+  content: "\e118";
+}
+
+.glyphicon-resize-vertical:before {
+  content: "\e119";
+}
+
+.glyphicon-resize-horizontal:before {
+  content: "\e120";
+}
+
+.glyphicon-hdd:before {
+  content: "\e121";
+}
+
+.glyphicon-bullhorn:before {
+  content: "\e122";
+}
+
+.glyphicon-certificate:before {
+  content: "\e124";
+}
+
+.glyphicon-thumbs-up:before {
+  content: "\e125";
+}
+
+.glyphicon-thumbs-down:before {
+  content: "\e126";
+}
+
+.glyphicon-hand-right:before {
+  content: "\e127";
+}
+
+.glyphicon-hand-left:before {
+  content: "\e128";
+}
+
+.glyphicon-hand-up:before {
+  content: "\e129";
+}
+
+.glyphicon-hand-down:before {
+  content: "\e130";
+}
+
+.glyphicon-circle-arrow-right:before {
+  content: "\e131";
+}
+
+.glyphicon-circle-arrow-left:before {
+  content: "\e132";
+}
+
+.glyphicon-circle-arrow-up:before {
+  content: "\e133";
+}
+
+.glyphicon-circle-arrow-down:before {
+  content: "\e134";
+}
+
+.glyphicon-globe:before {
+  content: "\e135";
+}
+
+.glyphicon-tasks:before {
+  content: "\e137";
+}
+
+.glyphicon-filter:before {
+  content: "\e138";
+}
+
+.glyphicon-fullscreen:before {
+  content: "\e140";
+}
+
+.glyphicon-dashboard:before {
+  content: "\e141";
+}
+
+.glyphicon-heart-empty:before {
+  content: "\e143";
+}
+
+.glyphicon-link:before {
+  content: "\e144";
+}
+
+.glyphicon-phone:before {
+  content: "\e145";
+}
+
+.glyphicon-usd:before {
+  content: "\e148";
+}
+
+.glyphicon-gbp:before {
+  content: "\e149";
+}
+
+.glyphicon-sort:before {
+  content: "\e150";
+}
+
+.glyphicon-sort-by-alphabet:before {
+  content: "\e151";
+}
+
+.glyphicon-sort-by-alphabet-alt:before {
+  content: "\e152";
+}
+
+.glyphicon-sort-by-order:before {
+  content: "\e153";
+}
+
+.glyphicon-sort-by-order-alt:before {
+  content: "\e154";
+}
+
+.glyphicon-sort-by-attributes:before {
+  content: "\e155";
+}
+
+.glyphicon-sort-by-attributes-alt:before {
+  content: "\e156";
+}
+
+.glyphicon-unchecked:before {
+  content: "\e157";
+}
+
+.glyphicon-expand:before {
+  content: "\e158";
+}
+
+.glyphicon-collapse-down:before {
+  content: "\e159";
+}
+
+.glyphicon-collapse-up:before {
+  content: "\e160";
+}
+
+.glyphicon-log-in:before {
+  content: "\e161";
+}
+
+.glyphicon-flash:before {
+  content: "\e162";
+}
+
+.glyphicon-log-out:before {
+  content: "\e163";
+}
+
+.glyphicon-new-window:before {
+  content: "\e164";
+}
+
+.glyphicon-record:before {
+  content: "\e165";
+}
+
+.glyphicon-save:before {
+  content: "\e166";
+}
+
+.glyphicon-open:before {
+  content: "\e167";
+}
+
+.glyphicon-saved:before {
+  content: "\e168";
+}
+
+.glyphicon-import:before {
+  content: "\e169";
+}
+
+.glyphicon-export:before {
+  content: "\e170";
+}
+
+.glyphicon-send:before {
+  content: "\e171";
+}
+
+.glyphicon-floppy-disk:before {
+  content: "\e172";
+}
+
+.glyphicon-floppy-saved:before {
+  content: "\e173";
+}
+
+.glyphicon-floppy-remove:before {
+  content: "\e174";
+}
+
+.glyphicon-floppy-save:before {
+  content: "\e175";
+}
+
+.glyphicon-floppy-open:before {
+  content: "\e176";
+}
+
+.glyphicon-credit-card:before {
+  content: "\e177";
+}
+
+.glyphicon-transfer:before {
+  content: "\e178";
+}
+
+.glyphicon-cutlery:before {
+  content: "\e179";
+}
+
+.glyphicon-header:before {
+  content: "\e180";
+}
+
+.glyphicon-compressed:before {
+  content: "\e181";
+}
+
+.glyphicon-earphone:before {
+  content: "\e182";
+}
+
+.glyphicon-phone-alt:before {
+  content: "\e183";
+}
+
+.glyphicon-tower:before {
+  content: "\e184";
+}
+
+.glyphicon-stats:before {
+  content: "\e185";
+}
+
+.glyphicon-sd-video:before {
+  content: "\e186";
+}
+
+.glyphicon-hd-video:before {
+  content: "\e187";
+}
+
+.glyphicon-subtitles:before {
+  content: "\e188";
+}
+
+.glyphicon-sound-stereo:before {
+  content: "\e189";
+}
+
+.glyphicon-sound-dolby:before {
+  content: "\e190";
+}
+
+.glyphicon-sound-5-1:before {
+  content: "\e191";
+}
+
+.glyphicon-sound-6-1:before {
+  content: "\e192";
+}
+
+.glyphicon-sound-7-1:before {
+  content: "\e193";
+}
+
+.glyphicon-copyright-mark:before {
+  content: "\e194";
+}
+
+.glyphicon-registration-mark:before {
+  content: "\e195";
+}
+
+.glyphicon-cloud-download:before {
+  content: "\e197";
+}
+
+.glyphicon-cloud-upload:before {
+  content: "\e198";
+}
+
+.glyphicon-tree-conifer:before {
+  content: "\e199";
+}
+
+.glyphicon-tree-deciduous:before {
+  content: "\e200";
+}
+
+.glyphicon-briefcase:before {
+  content: "\1f4bc";
+}
+
+.glyphicon-calendar:before {
+  content: "\1f4c5";
+}
+
+.glyphicon-pushpin:before {
+  content: "\1f4cc";
+}
+
+.glyphicon-paperclip:before {
+  content: "\1f4ce";
+}
+
+.glyphicon-camera:before {
+  content: "\1f4f7";
+}
+
+.glyphicon-lock:before {
+  content: "\1f512";
+}
+
+.glyphicon-bell:before {
+  content: "\1f514";
+}
+
+.glyphicon-bookmark:before {
+  content: "\1f516";
+}
+
+.glyphicon-fire:before {
+  content: "\1f525";
+}
+
+.glyphicon-wrench:before {
+  content: "\1f527";
+}
+
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  margin-left: 2px;
+  vertical-align: middle;
+  border-top: 4px solid #000000;
+  border-right: 4px solid transparent;
+  border-bottom: 0 dotted;
+  border-left: 4px solid transparent;
+  content: "";
+}
+
+.dropdown {
+  position: relative;
+}
+
+.dropdown-toggle:focus {
+  outline: 0;
+}
+
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0;
+  font-size: 14px;
+  list-style: none;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  border-radius: 4px;
+  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+          box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+  background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.dropdown-menu .divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+
+.dropdown-menu > li > a {
+  display: block;
+  padding: 3px 20px;
+  clear: both;
+  font-weight: normal;
+  line-height: 1.428571429;
+  color: #333333;
+  white-space: nowrap;
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #428bca;
+}
+
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #428bca;
+  outline: 0;
+}
+
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  color: #999999;
+}
+
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+  background-image: none;
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.open > .dropdown-menu {
+  display: block;
+}
+
+.open > a {
+  outline: 0;
+}
+
+.dropdown-header {
+  display: block;
+  padding: 3px 20px;
+  font-size: 12px;
+  line-height: 1.428571429;
+  color: #999999;
+}
+
+.dropdown-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 990;
+}
+
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  border-top: 0 dotted;
+  border-bottom: 4px solid #000000;
+  content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 1px;
+}
+
+@media (min-width: 768px) {
+  .navbar-right .dropdown-menu {
+    right: 0;
+    left: auto;
+  }
+}
+
+.btn-default .caret {
+  border-top-color: #333333;
+}
+
+.btn-primary .caret,
+.btn-success .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret {
+  border-top-color: #fff;
+}
+
+.dropup .btn-default .caret {
+  border-bottom-color: #333333;
+}
+
+.dropup .btn-primary .caret,
+.dropup .btn-success .caret,
+.dropup .btn-warning .caret,
+.dropup .btn-danger .caret,
+.dropup .btn-info .caret {
+  border-bottom-color: #fff;
+}
+
+.btn-group,
+.btn-group-vertical {
+  position: relative;
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+  position: relative;
+  float: left;
+}
+
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus,
+.btn-group > .btn:active,
+.btn-group-vertical > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn.active {
+  z-index: 2;
+}
+
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus {
+  outline: none;
+}
+
+.btn-group .btn + .btn,
+.btn-group .btn + .btn-group,
+.btn-group .btn-group + .btn,
+.btn-group .btn-group + .btn-group {
+  margin-left: -1px;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+  display: table;
+  content: " ";
+}
+
+.btn-toolbar:after {
+  clear: both;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+  display: table;
+  content: " ";
+}
+
+.btn-toolbar:after {
+  clear: both;
+}
+
+.btn-toolbar .btn-group {
+  float: left;
+}
+
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn-group + .btn,
+.btn-toolbar > .btn + .btn-group,
+.btn-toolbar > .btn-group + .btn-group {
+  margin-left: 5px;
+}
+
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+  border-radius: 0;
+}
+
+.btn-group > .btn:first-child {
+  margin-left: 0;
+}
+
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group > .btn-group {
+  float: left;
+}
+
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+
+.btn-group > .btn-group:first-child > .btn:last-child,
+.btn-group > .btn-group:first-child > .dropdown-toggle {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn-group:last-child > .btn:first-child {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+
+.btn-group-xs > .btn {
+  padding: 5px 10px;
+  padding: 1px 5px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-group-sm > .btn {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-group-lg > .btn {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+  padding-right: 8px;
+  padding-left: 8px;
+}
+
+.btn-group > .btn-lg + .dropdown-toggle {
+  padding-right: 12px;
+  padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn .caret {
+  margin-left: 0;
+}
+
+.btn-lg .caret {
+  border-width: 5px 5px 0;
+  border-bottom-width: 0;
+}
+
+.dropup .btn-lg .caret {
+  border-width: 0 5px 5px;
+}
+
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group {
+  display: block;
+  float: none;
+  width: 100%;
+  max-width: 100%;
+}
+
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after {
+  display: table;
+  content: " ";
+}
+
+.btn-group-vertical > .btn-group:after {
+  clear: both;
+}
+
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after {
+  display: table;
+  content: " ";
+}
+
+.btn-group-vertical > .btn-group:after {
+  clear: both;
+}
+
+.btn-group-vertical > .btn-group > .btn {
+  float: none;
+}
+
+.btn-group-vertical > .btn + .btn,
+.btn-group-vertical > .btn + .btn-group,
+.btn-group-vertical > .btn-group + .btn,
+.btn-group-vertical > .btn-group + .btn-group {
+  margin-top: -1px;
+  margin-left: 0;
+}
+
+.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+
+.btn-group-vertical > .btn:first-child:not(:last-child) {
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical > .btn:last-child:not(:first-child) {
+  border-top-right-radius: 0;
+  border-bottom-left-radius: 4px;
+  border-top-left-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:first-child > .btn:last-child,
+.btn-group-vertical > .btn-group:first-child > .dropdown-toggle {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:last-child > .btn:first-child {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group-justified {
+  display: table;
+  width: 100%;
+  border-collapse: separate;
+  table-layout: fixed;
+}
+
+.btn-group-justified .btn {
+  display: table-cell;
+  float: none;
+  width: 1%;
+}
+
+[data-toggle="buttons"] > .btn > input[type="radio"],
+[data-toggle="buttons"] > .btn > input[type="checkbox"] {
+  display: none;
+}
+
+.input-group {
+  position: relative;
+  display: table;
+  border-collapse: separate;
+}
+
+.input-group.col {
+  float: none;
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.input-group .form-control {
+  width: 100%;
+  margin-bottom: 0;
+}
+
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+  height: 45px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+select.input-group-lg > .form-control,
+select.input-group-lg > .input-group-addon,
+select.input-group-lg > .input-group-btn > .btn {
+  height: 45px;
+  line-height: 45px;
+}
+
+textarea.input-group-lg > .form-control,
+textarea.input-group-lg > .input-group-addon,
+textarea.input-group-lg > .input-group-btn > .btn {
+  height: auto;
+}
+
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+select.input-group-sm > .form-control,
+select.input-group-sm > .input-group-addon,
+select.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  line-height: 30px;
+}
+
+textarea.input-group-sm > .form-control,
+textarea.input-group-sm > .input-group-addon,
+textarea.input-group-sm > .input-group-btn > .btn {
+  height: auto;
+}
+
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+  display: table-cell;
+}
+
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+
+.input-group-addon,
+.input-group-btn {
+  width: 1%;
+  white-space: nowrap;
+  vertical-align: middle;
+}
+
+.input-group-addon {
+  padding: 6px 12px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1;
+  text-align: center;
+  background-color: #eeeeee;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+}
+
+.input-group-addon.input-sm {
+  padding: 5px 10px;
+  font-size: 12px;
+  border-radius: 3px;
+}
+
+.input-group-addon.input-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  border-radius: 6px;
+}
+
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+  margin-top: 0;
+}
+
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.input-group-addon:first-child {
+  border-right: 0;
+}
+
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child) {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.input-group-addon:last-child {
+  border-left: 0;
+}
+
+.input-group-btn {
+  position: relative;
+  white-space: nowrap;
+}
+
+.input-group-btn > .btn {
+  position: relative;
+}
+
+.input-group-btn > .btn + .btn {
+  margin-left: -4px;
+}
+
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:active {
+  z-index: 2;
+}
+
+.nav {
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+
+.nav:before,
+.nav:after {
+  display: table;
+  content: " ";
+}
+
+.nav:after {
+  clear: both;
+}
+
+.nav:before,
+.nav:after {
+  display: table;
+  content: " ";
+}
+
+.nav:after {
+  clear: both;
+}
+
+.nav > li {
+  position: relative;
+  display: block;
+}
+
+.nav > li > a {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+}
+
+.nav > li > a:hover,
+.nav > li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+
+.nav > li.disabled > a {
+  color: #999999;
+}
+
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+  color: #999999;
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+}
+
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+  background-color: #eeeeee;
+  border-color: #428bca;
+}
+
+.nav .nav-divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+
+.nav > li > a > img {
+  max-width: none;
+}
+
+.nav-tabs {
+  border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs > li {
+  float: left;
+  margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+  margin-right: 2px;
+  line-height: 1.428571429;
+  border: 1px solid transparent;
+  border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover {
+  border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+  color: #555555;
+  cursor: default;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-bottom-color: transparent;
+}
+
+.nav-tabs.nav-justified {
+  width: 100%;
+  border-bottom: 0;
+}
+
+.nav-tabs.nav-justified > li {
+  float: none;
+}
+
+.nav-tabs.nav-justified > li > a {
+  text-align: center;
+}
+
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+}
+
+.nav-tabs.nav-justified > li > a {
+  margin-right: 0;
+  border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs.nav-justified > .active > a {
+  border-bottom-color: #ffffff;
+}
+
+.nav-pills > li {
+  float: left;
+}
+
+.nav-pills > li > a {
+  border-radius: 5px;
+}
+
+.nav-pills > li + li {
+  margin-left: 2px;
+}
+
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+  color: #ffffff;
+  background-color: #428bca;
+}
+
+.nav-stacked > li {
+  float: none;
+}
+
+.nav-stacked > li + li {
+  margin-top: 2px;
+  margin-left: 0;
+}
+
+.nav-justified {
+  width: 100%;
+}
+
+.nav-justified > li {
+  float: none;
+}
+
+.nav-justified > li > a {
+  text-align: center;
+}
+
+@media (min-width: 768px) {
+  .nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+}
+
+.nav-tabs-justified {
+  border-bottom: 0;
+}
+
+.nav-tabs-justified > li > a {
+  margin-right: 0;
+  border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs-justified > .active > a {
+  border-bottom-color: #ffffff;
+}
+
+.tabbable:before,
+.tabbable:after {
+  display: table;
+  content: " ";
+}
+
+.tabbable:after {
+  clear: both;
+}
+
+.tabbable:before,
+.tabbable:after {
+  display: table;
+  content: " ";
+}
+
+.tabbable:after {
+  clear: both;
+}
+
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+  display: none;
+}
+
+.tab-content > .active,
+.pill-content > .active {
+  display: block;
+}
+
+.nav .caret {
+  border-top-color: #428bca;
+  border-bottom-color: #428bca;
+}
+
+.nav a:hover .caret {
+  border-top-color: #2a6496;
+  border-bottom-color: #2a6496;
+}
+
+.nav-tabs .dropdown-menu {
+  margin-top: -1px;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.navbar {
+  position: relative;
+  z-index: 1000;
+  min-height: 50px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+}
+
+.navbar:before,
+.navbar:after {
+  display: table;
+  content: " ";
+}
+
+.navbar:after {
+  clear: both;
+}
+
+.navbar:before,
+.navbar:after {
+  display: table;
+  content: " ";
+}
+
+.navbar:after {
+  clear: both;
+}
+
+@media (min-width: 768px) {
+  .navbar {
+    border-radius: 4px;
+  }
+}
+
+.navbar-header:before,
+.navbar-header:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-header:after {
+  clear: both;
+}
+
+.navbar-header:before,
+.navbar-header:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-header:after {
+  clear: both;
+}
+
+@media (min-width: 768px) {
+  .navbar-header {
+    float: left;
+  }
+}
+
+.navbar-collapse {
+  max-height: 340px;
+  padding-right: 15px;
+  padding-left: 15px;
+  overflow-x: visible;
+  border-top: 1px solid transparent;
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  -webkit-overflow-scrolling: touch;
+}
+
+.navbar-collapse:before,
+.navbar-collapse:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-collapse:after {
+  clear: both;
+}
+
+.navbar-collapse:before,
+.navbar-collapse:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-collapse:after {
+  clear: both;
+}
+
+.navbar-collapse.in {
+  overflow-y: auto;
+}
+
+@media (min-width: 768px) {
+  .navbar-collapse {
+    width: auto;
+    border-top: 0;
+    box-shadow: none;
+  }
+  .navbar-collapse.collapse {
+    display: block !important;
+    height: auto !important;
+    padding-bottom: 0;
+    overflow: visible !important;
+  }
+  .navbar-collapse.in {
+    overflow-y: visible;
+  }
+  .navbar-collapse .navbar-nav.navbar-left:first-child {
+    margin-left: -15px;
+  }
+  .navbar-collapse .navbar-nav.navbar-right:last-child {
+    margin-right: -15px;
+  }
+  .navbar-collapse .navbar-text:last-child {
+    margin-right: 0;
+  }
+}
+
+.container > .navbar-header,
+.container > .navbar-collapse {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+@media (min-width: 768px) {
+  .container > .navbar-header,
+  .container > .navbar-collapse {
+    margin-right: 0;
+    margin-left: 0;
+  }
+}
+
+.navbar-static-top {
+  border-width: 0 0 1px;
+}
+
+@media (min-width: 768px) {
+  .navbar-static-top {
+    border-radius: 0;
+  }
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  border-width: 0 0 1px;
+}
+
+@media (min-width: 768px) {
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    border-radius: 0;
+  }
+}
+
+.navbar-fixed-top {
+  top: 0;
+  z-index: 1030;
+}
+
+.navbar-fixed-bottom {
+  bottom: 0;
+  margin-bottom: 0;
+}
+
+.navbar-brand {
+  float: left;
+  padding: 15px 15px;
+  font-size: 18px;
+  line-height: 20px;
+}
+
+.navbar-brand:hover,
+.navbar-brand:focus {
+  text-decoration: none;
+}
+
+@media (min-width: 768px) {
+  .navbar > .container .navbar-brand {
+    margin-left: -15px;
+  }
+}
+
+.navbar-toggle {
+  position: relative;
+  float: right;
+  padding: 9px 10px;
+  margin-top: 8px;
+  margin-right: 15px;
+  margin-bottom: 8px;
+  background-color: transparent;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+
+.navbar-toggle .icon-bar {
+  display: block;
+  width: 22px;
+  height: 2px;
+  border-radius: 1px;
+}
+
+.navbar-toggle .icon-bar + .icon-bar {
+  margin-top: 4px;
+}
+
+@media (min-width: 768px) {
+  .navbar-toggle {
+    display: none;
+  }
+}
+
+.navbar-nav {
+  margin: 7.5px -15px;
+}
+
+.navbar-nav > li > a {
+  padding-top: 10px;
+  padding-bottom: 10px;
+  line-height: 20px;
+}
+
+@media (max-width: 767px) {
+  .navbar-nav .open .dropdown-menu {
+    position: static;
+    float: none;
+    width: auto;
+    margin-top: 0;
+    background-color: transparent;
+    border: 0;
+    box-shadow: none;
+  }
+  .navbar-nav .open .dropdown-menu > li > a,
+  .navbar-nav .open .dropdown-menu .dropdown-header {
+    padding: 5px 15px 5px 25px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a {
+    line-height: 20px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-nav .open .dropdown-menu > li > a:focus {
+    background-image: none;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-nav {
+    float: left;
+    margin: 0;
+  }
+  .navbar-nav > li {
+    float: left;
+  }
+  .navbar-nav > li > a {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-left {
+    float: left !important;
+  }
+  .navbar-right {
+    float: right !important;
+  }
+}
+
+.navbar-form {
+  padding: 10px 15px;
+  margin-top: 8px;
+  margin-right: -15px;
+  margin-bottom: 8px;
+  margin-left: -15px;
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+@media (min-width: 768px) {
+  .navbar-form .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .form-control {
+    display: inline-block;
+  }
+  .navbar-form .radio,
+  .navbar-form .checkbox {
+    display: inline-block;
+    padding-left: 0;
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+  .navbar-form .radio input[type="radio"],
+  .navbar-form .checkbox input[type="checkbox"] {
+    float: none;
+    margin-left: 0;
+  }
+}
+
+@media (max-width: 767px) {
+  .navbar-form .form-group {
+    margin-bottom: 5px;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-form {
+    width: auto;
+    padding-top: 0;
+    padding-bottom: 0;
+    margin-right: 0;
+    margin-left: 0;
+    border: 0;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+  }
+}
+
+.navbar-nav > li > .dropdown-menu {
+  margin-top: 0;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.navbar-nav.pull-right > li > .dropdown-menu,
+.navbar-nav > li > .dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.navbar-btn {
+  margin-top: 8px;
+  margin-bottom: 8px;
+}
+
+.navbar-text {
+  float: left;
+  margin-top: 15px;
+  margin-bottom: 15px;
+}
+
+@media (min-width: 768px) {
+  .navbar-text {
+    margin-right: 15px;
+    margin-left: 15px;
+  }
+}
+
+.navbar-default {
+  background-color: #f8f8f8;
+  border-color: #e7e7e7;
+}
+
+.navbar-default .navbar-brand {
+  color: #777777;
+}
+
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+  color: #5e5e5e;
+  background-color: transparent;
+}
+
+.navbar-default .navbar-text {
+  color: #777777;
+}
+
+.navbar-default .navbar-nav > li > a {
+  color: #777777;
+}
+
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+  color: #333333;
+  background-color: transparent;
+}
+
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+  color: #555555;
+  background-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .disabled > a,
+.navbar-default .navbar-nav > .disabled > a:hover,
+.navbar-default .navbar-nav > .disabled > a:focus {
+  color: #cccccc;
+  background-color: transparent;
+}
+
+.navbar-default .navbar-toggle {
+  border-color: #dddddd;
+}
+
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+  background-color: #dddddd;
+}
+
+.navbar-default .navbar-toggle .icon-bar {
+  background-color: #cccccc;
+}
+
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+  border-color: #e6e6e6;
+}
+
+.navbar-default .navbar-nav > .dropdown > a:hover .caret,
+.navbar-default .navbar-nav > .dropdown > a:focus .caret {
+  border-top-color: #333333;
+  border-bottom-color: #333333;
+}
+
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+  color: #555555;
+  background-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .open > a .caret,
+.navbar-default .navbar-nav > .open > a:hover .caret,
+.navbar-default .navbar-nav > .open > a:focus .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+
+.navbar-default .navbar-nav > .dropdown > a .caret {
+  border-top-color: #777777;
+  border-bottom-color: #777777;
+}
+
+@media (max-width: 767px) {
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+    color: #777777;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #333333;
+    background-color: transparent;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #555555;
+    background-color: #e7e7e7;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #cccccc;
+    background-color: transparent;
+  }
+}
+
+.navbar-default .navbar-link {
+  color: #777777;
+}
+
+.navbar-default .navbar-link:hover {
+  color: #333333;
+}
+
+.navbar-inverse {
+  background-color: #222222;
+  border-color: #080808;
+}
+
+.navbar-inverse .navbar-brand {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-text {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+  color: #ffffff;
+  background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+  color: #444444;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-toggle {
+  border-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+  background-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle .icon-bar {
+  background-color: #ffffff;
+}
+
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+  border-color: #101010;
+}
+
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+  color: #ffffff;
+  background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a:hover .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a .caret {
+  border-top-color: #999999;
+  border-bottom-color: #999999;
+}
+
+.navbar-inverse .navbar-nav > .open > a .caret,
+.navbar-inverse .navbar-nav > .open > a:hover .caret,
+.navbar-inverse .navbar-nav > .open > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+@media (max-width: 767px) {
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+    border-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+    color: #999999;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #ffffff;
+    background-color: transparent;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #ffffff;
+    background-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #444444;
+    background-color: transparent;
+  }
+}
+
+.navbar-inverse .navbar-link {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover {
+  color: #ffffff;
+}
+
+.breadcrumb {
+  padding: 8px 15px;
+  margin-bottom: 20px;
+  list-style: none;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+}
+
+.breadcrumb > li {
+  display: inline-block;
+}
+
+.breadcrumb > li + li:before {
+  padding: 0 5px;
+  color: #cccccc;
+  content: "/\00a0";
+}
+
+.breadcrumb > .active {
+  color: #999999;
+}
+
+.pagination {
+  display: inline-block;
+  padding-left: 0;
+  margin: 20px 0;
+  border-radius: 4px;
+}
+
+.pagination > li {
+  display: inline;
+}
+
+.pagination > li > a,
+.pagination > li > span {
+  position: relative;
+  float: left;
+  padding: 6px 12px;
+  margin-left: -1px;
+  line-height: 1.428571429;
+  text-decoration: none;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+}
+
+.pagination > li:first-child > a,
+.pagination > li:first-child > span {
+  margin-left: 0;
+  border-bottom-left-radius: 4px;
+  border-top-left-radius: 4px;
+}
+
+.pagination > li:last-child > a,
+.pagination > li:last-child > span {
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 4px;
+}
+
+.pagination > li > a:hover,
+.pagination > li > span:hover,
+.pagination > li > a:focus,
+.pagination > li > span:focus {
+  background-color: #eeeeee;
+}
+
+.pagination > .active > a,
+.pagination > .active > span,
+.pagination > .active > a:hover,
+.pagination > .active > span:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span:focus {
+  z-index: 2;
+  color: #ffffff;
+  cursor: default;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.pagination > .disabled > span,
+.pagination > .disabled > a,
+.pagination > .disabled > a:hover,
+.pagination > .disabled > a:focus {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #ffffff;
+  border-color: #dddddd;
+}
+
+.pagination-lg > li > a,
+.pagination-lg > li > span {
+  padding: 10px 16px;
+  font-size: 18px;
+}
+
+.pagination-lg > li:first-child > a,
+.pagination-lg > li:first-child > span {
+  border-bottom-left-radius: 6px;
+  border-top-left-radius: 6px;
+}
+
+.pagination-lg > li:last-child > a,
+.pagination-lg > li:last-child > span {
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+
+.pagination-sm > li > a,
+.pagination-sm > li > span {
+  padding: 5px 10px;
+  font-size: 12px;
+}
+
+.pagination-sm > li:first-child > a,
+.pagination-sm > li:first-child > span {
+  border-bottom-left-radius: 3px;
+  border-top-left-radius: 3px;
+}
+
+.pagination-sm > li:last-child > a,
+.pagination-sm > li:last-child > span {
+  border-top-right-radius: 3px;
+  border-bottom-right-radius: 3px;
+}
+
+.pager {
+  padding-left: 0;
+  margin: 20px 0;
+  text-align: center;
+  list-style: none;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  content: " ";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  content: " ";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager li {
+  display: inline;
+}
+
+.pager li > a,
+.pager li > span {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 15px;
+}
+
+.pager li > a:hover,
+.pager li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+
+.pager .next > a,
+.pager .next > span {
+  float: right;
+}
+
+.pager .previous > a,
+.pager .previous > span {
+  float: left;
+}
+
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #ffffff;
+}
+
+.label {
+  display: inline;
+  padding: .2em .6em .3em;
+  font-size: 75%;
+  font-weight: bold;
+  line-height: 1;
+  color: #ffffff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  border-radius: .25em;
+}
+
+.label[href]:hover,
+.label[href]:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.label:empty {
+  display: none;
+}
+
+.label-default {
+  background-color: #999999;
+}
+
+.label-default[href]:hover,
+.label-default[href]:focus {
+  background-color: #808080;
+}
+
+.label-primary {
+  background-color: #428bca;
+}
+
+.label-primary[href]:hover,
+.label-primary[href]:focus {
+  background-color: #3071a9;
+}
+
+.label-success {
+  background-color: #5cb85c;
+}
+
+.label-success[href]:hover,
+.label-success[href]:focus {
+  background-color: #449d44;
+}
+
+.label-info {
+  background-color: #5bc0de;
+}
+
+.label-info[href]:hover,
+.label-info[href]:focus {
+  background-color: #31b0d5;
+}
+
+.label-warning {
+  background-color: #f0ad4e;
+}
+
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+  background-color: #ec971f;
+}
+
+.label-danger {
+  background-color: #d9534f;
+}
+
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+  background-color: #c9302c;
+}
+
+.badge {
+  display: inline-block;
+  min-width: 10px;
+  padding: 3px 7px;
+  font-size: 12px;
+  font-weight: bold;
+  line-height: 1;
+  color: #ffffff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  background-color: #999999;
+  border-radius: 10px;
+}
+
+.badge:empty {
+  display: none;
+}
+
+a.badge:hover,
+a.badge:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+
+a.list-group-item.active > .badge,
+.nav-pills > .active > a > .badge {
+  color: #428bca;
+  background-color: #ffffff;
+}
+
+.nav-pills > li > a > .badge {
+  margin-left: 3px;
+}
+
+.jumbotron {
+  padding: 30px;
+  margin-bottom: 30px;
+  font-size: 21px;
+  font-weight: 200;
+  line-height: 2.1428571435;
+  color: inherit;
+  background-color: #eeeeee;
+}
+
+.jumbotron h1 {
+  line-height: 1;
+  color: inherit;
+}
+
+.jumbotron p {
+  line-height: 1.4;
+}
+
+.container .jumbotron {
+  border-radius: 6px;
+}
+
+@media screen and (min-width: 768px) {
+  .jumbotron {
+    padding-top: 48px;
+    padding-bottom: 48px;
+  }
+  .container .jumbotron {
+    padding-right: 60px;
+    padding-left: 60px;
+  }
+  .jumbotron h1 {
+    font-size: 63px;
+  }
+}
+
+.thumbnail {
+  display: inline-block;
+  display: block;
+  height: auto;
+  max-width: 100%;
+  padding: 4px;
+  line-height: 1.428571429;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-transition: all 0.2s ease-in-out;
+          transition: all 0.2s ease-in-out;
+}
+
+.thumbnail > img {
+  display: block;
+  height: auto;
+  max-width: 100%;
+}
+
+a.thumbnail:hover,
+a.thumbnail:focus {
+  border-color: #428bca;
+}
+
+.thumbnail > img {
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.thumbnail .caption {
+  padding: 9px;
+  color: #333333;
+}
+
+.alert {
+  padding: 15px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+
+.alert h4 {
+  margin-top: 0;
+  color: inherit;
+}
+
+.alert .alert-link {
+  font-weight: bold;
+}
+
+.alert > p,
+.alert > ul {
+  margin-bottom: 0;
+}
+
+.alert > p + p {
+  margin-top: 5px;
+}
+
+.alert-dismissable {
+  padding-right: 35px;
+}
+
+.alert-dismissable .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  color: inherit;
+}
+
+.alert-success {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.alert-success hr {
+  border-top-color: #c9e2b3;
+}
+
+.alert-success .alert-link {
+  color: #356635;
+}
+
+.alert-info {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.alert-info hr {
+  border-top-color: #a6e1ec;
+}
+
+.alert-info .alert-link {
+  color: #2d6987;
+}
+
+.alert-warning {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #fbeed5;
+}
+
+.alert-warning hr {
+  border-top-color: #f8e5be;
+}
+
+.alert-warning .alert-link {
+  color: #a47e3c;
+}
+
+.alert-danger {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+
+.alert-danger hr {
+  border-top-color: #e6c1c7;
+}
+
+.alert-danger .alert-link {
+  color: #953b39;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-moz-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-o-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+.progress {
+  height: 20px;
+  margin-bottom: 20px;
+  overflow: hidden;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress-bar {
+  float: left;
+  width: 0;
+  height: 100%;
+  font-size: 12px;
+  color: #ffffff;
+  text-align: center;
+  background-color: #428bca;
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-transition: width 0.6s ease;
+          transition: width 0.6s ease;
+}
+
+.progress-striped .progress-bar {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-size: 40px 40px;
+}
+
+.progress.active .progress-bar {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+     -moz-animation: progress-bar-stripes 2s linear infinite;
+      -ms-animation: progress-bar-stripes 2s linear infinite;
+       -o-animation: progress-bar-stripes 2s linear infinite;
+          animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-bar-success {
+  background-color: #5cb85c;
+}
+
+.progress-striped .progress-bar-success {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-info {
+  background-color: #5bc0de;
+}
+
+.progress-striped .progress-bar-info {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-warning {
+  background-color: #f0ad4e;
+}
+
+.progress-striped .progress-bar-warning {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-danger {
+  background-color: #d9534f;
+}
+
+.progress-striped .progress-bar-danger {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.media,
+.media-body {
+  overflow: hidden;
+  zoom: 1;
+}
+
+.media,
+.media .media {
+  margin-top: 15px;
+}
+
+.media:first-child {
+  margin-top: 0;
+}
+
+.media-object {
+  display: block;
+}
+
+.media-heading {
+  margin: 0 0 5px;
+}
+
+.media > .pull-left {
+  margin-right: 10px;
+}
+
+.media > .pull-right {
+  margin-left: 10px;
+}
+
+.media-list {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-group {
+  padding-left: 0;
+  margin-bottom: 20px;
+}
+
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+  margin-bottom: -1px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+}
+
+.list-group-item:first-child {
+  border-top-right-radius: 4px;
+  border-top-left-radius: 4px;
+}
+
+.list-group-item:last-child {
+  margin-bottom: 0;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+
+.list-group-item > .badge {
+  float: right;
+}
+
+.list-group-item > .badge + .badge {
+  margin-right: 5px;
+}
+
+a.list-group-item {
+  color: #555555;
+}
+
+a.list-group-item .list-group-item-heading {
+  color: #333333;
+}
+
+a.list-group-item:hover,
+a.list-group-item:focus {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+  z-index: 2;
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading {
+  color: inherit;
+}
+
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text {
+  color: #e1edf7;
+}
+
+.list-group-item-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+
+.list-group-item-text {
+  margin-bottom: 0;
+  line-height: 1.3;
+}
+
+.panel {
+  margin-bottom: 20px;
+  background-color: #ffffff;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.panel-body {
+  padding: 15px;
+}
+
+.panel-body:before,
+.panel-body:after {
+  display: table;
+  content: " ";
+}
+
+.panel-body:after {
+  clear: both;
+}
+
+.panel-body:before,
+.panel-body:after {
+  display: table;
+  content: " ";
+}
+
+.panel-body:after {
+  clear: both;
+}
+
+.panel > .list-group {
+  margin-bottom: 0;
+}
+
+.panel > .list-group .list-group-item {
+  border-width: 1px 0;
+}
+
+.panel > .list-group .list-group-item:first-child {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.panel > .list-group .list-group-item:last-child {
+  border-bottom: 0;
+}
+
+.panel-heading + .list-group .list-group-item:first-child {
+  border-top-width: 0;
+}
+
+.panel > .table {
+  margin-bottom: 0;
+}
+
+.panel > .panel-body + .table {
+  border-top: 1px solid #dddddd;
+}
+
+.panel-heading {
+  padding: 10px 15px;
+  border-bottom: 1px solid transparent;
+  border-top-right-radius: 3px;
+  border-top-left-radius: 3px;
+}
+
+.panel-title {
+  margin-top: 0;
+  margin-bottom: 0;
+  font-size: 16px;
+}
+
+.panel-title > a {
+  color: inherit;
+}
+
+.panel-footer {
+  padding: 10px 15px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #dddddd;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+
+.panel-group .panel {
+  margin-bottom: 0;
+  overflow: hidden;
+  border-radius: 4px;
+}
+
+.panel-group .panel + .panel {
+  margin-top: 5px;
+}
+
+.panel-group .panel-heading {
+  border-bottom: 0;
+}
+
+.panel-group .panel-heading + .panel-collapse .panel-body {
+  border-top: 1px solid #dddddd;
+}
+
+.panel-group .panel-footer {
+  border-top: 0;
+}
+
+.panel-group .panel-footer + .panel-collapse .panel-body {
+  border-bottom: 1px solid #dddddd;
+}
+
+.panel-default {
+  border-color: #dddddd;
+}
+
+.panel-default > .panel-heading {
+  color: #333333;
+  background-color: #f5f5f5;
+  border-color: #dddddd;
+}
+
+.panel-default > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #dddddd;
+}
+
+.panel-default > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #dddddd;
+}
+
+.panel-primary {
+  border-color: #428bca;
+}
+
+.panel-primary > .panel-heading {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.panel-primary > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #428bca;
+}
+
+.panel-primary > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #428bca;
+}
+
+.panel-success {
+  border-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #d6e9c6;
+}
+
+.panel-success > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #d6e9c6;
+}
+
+.panel-warning {
+  border-color: #fbeed5;
+}
+
+.panel-warning > .panel-heading {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #fbeed5;
+}
+
+.panel-warning > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #fbeed5;
+}
+
+.panel-warning > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #fbeed5;
+}
+
+.panel-danger {
+  border-color: #eed3d7;
+}
+
+.panel-danger > .panel-heading {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+
+.panel-danger > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #eed3d7;
+}
+
+.panel-danger > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #eed3d7;
+}
+
+.panel-info {
+  border-color: #bce8f1;
+}
+
+.panel-info > .panel-heading {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.panel-info > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #bce8f1;
+}
+
+.panel-info > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #bce8f1;
+}
+
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-lg {
+  padding: 24px;
+  border-radius: 6px;
+}
+
+.well-sm {
+  padding: 9px;
+  border-radius: 3px;
+}
+
+.close {
+  float: right;
+  font-size: 21px;
+  font-weight: bold;
+  line-height: 1;
+  color: #000000;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.2;
+  filter: alpha(opacity=20);
+}
+
+.close:hover,
+.close:focus {
+  color: #000000;
+  text-decoration: none;
+  cursor: pointer;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+button.close {
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+  -webkit-appearance: none;
+}
+
+.modal-open {
+  overflow: hidden;
+}
+
+body.modal-open,
+.modal-open .navbar-fixed-top,
+.modal-open .navbar-fixed-bottom {
+  margin-right: 15px;
+}
+
+.modal {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  display: none;
+  overflow: auto;
+  overflow-y: scroll;
+}
+
+.modal.fade .modal-dialog {
+  -webkit-transform: translate(0, -25%);
+      -ms-transform: translate(0, -25%);
+          transform: translate(0, -25%);
+  -webkit-transition: -webkit-transform 0.3s ease-out;
+     -moz-transition: -moz-transform 0.3s ease-out;
+       -o-transition: -o-transform 0.3s ease-out;
+          transition: transform 0.3s ease-out;
+}
+
+.modal.in .modal-dialog {
+  -webkit-transform: translate(0, 0);
+      -ms-transform: translate(0, 0);
+          transform: translate(0, 0);
+}
+
+.modal-dialog {
+  z-index: 1050;
+  width: auto;
+  padding: 10px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.modal-content {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #999999;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  outline: none;
+  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+          box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+  background-clip: padding-box;
+}
+
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1030;
+  background-color: #000000;
+}
+
+.modal-backdrop.fade {
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+
+.modal-backdrop.in {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.modal-header {
+  min-height: 16.428571429px;
+  padding: 15px;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+.modal-header .close {
+  margin-top: -2px;
+}
+
+.modal-title {
+  margin: 0;
+  line-height: 1.428571429;
+}
+
+.modal-body {
+  position: relative;
+  padding: 20px;
+}
+
+.modal-footer {
+  padding: 19px 20px 20px;
+  margin-top: 15px;
+  text-align: right;
+  border-top: 1px solid #e5e5e5;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer .btn + .btn {
+  margin-bottom: 0;
+  margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+
+@media screen and (min-width: 768px) {
+  .modal-dialog {
+    right: auto;
+    left: 50%;
+    width: 600px;
+    padding-top: 30px;
+    padding-bottom: 30px;
+  }
+  .modal-content {
+    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+  }
+}
+
+.tooltip {
+  position: absolute;
+  z-index: 1030;
+  display: block;
+  font-size: 12px;
+  line-height: 1.4;
+  opacity: 0;
+  filter: alpha(opacity=0);
+  visibility: visible;
+}
+
+.tooltip.in {
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+
+.tooltip.top {
+  padding: 5px 0;
+  margin-top: -3px;
+}
+
+.tooltip.right {
+  padding: 0 5px;
+  margin-left: 3px;
+}
+
+.tooltip.bottom {
+  padding: 5px 0;
+  margin-top: 3px;
+}
+
+.tooltip.left {
+  padding: 0 5px;
+  margin-left: -3px;
+}
+
+.tooltip-inner {
+  max-width: 200px;
+  padding: 3px 8px;
+  color: #ffffff;
+  text-align: center;
+  text-decoration: none;
+  background-color: #000000;
+  border-radius: 4px;
+}
+
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.top-left .tooltip-arrow {
+  bottom: 0;
+  left: 5px;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.top-right .tooltip-arrow {
+  right: 5px;
+  bottom: 0;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-right-color: #000000;
+  border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-left-color: #000000;
+  border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-left .tooltip-arrow {
+  top: 0;
+  left: 5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-right .tooltip-arrow {
+  top: 0;
+  right: 5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1010;
+  display: none;
+  max-width: 276px;
+  padding: 1px;
+  text-align: left;
+  white-space: normal;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  background-clip: padding-box;
+}
+
+.popover.top {
+  margin-top: -10px;
+}
+
+.popover.right {
+  margin-left: 10px;
+}
+
+.popover.bottom {
+  margin-top: 10px;
+}
+
+.popover.left {
+  margin-left: -10px;
+}
+
+.popover-title {
+  padding: 8px 14px;
+  margin: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 18px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+  padding: 9px 14px;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.popover .arrow {
+  border-width: 11px;
+}
+
+.popover .arrow:after {
+  border-width: 10px;
+  content: "";
+}
+
+.popover.top .arrow {
+  bottom: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-color: #999999;
+  border-top-color: rgba(0, 0, 0, 0.25);
+  border-bottom-width: 0;
+}
+
+.popover.top .arrow:after {
+  bottom: 1px;
+  margin-left: -10px;
+  border-top-color: #ffffff;
+  border-bottom-width: 0;
+  content: " ";
+}
+
+.popover.right .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-right-color: #999999;
+  border-right-color: rgba(0, 0, 0, 0.25);
+  border-left-width: 0;
+}
+
+.popover.right .arrow:after {
+  bottom: -10px;
+  left: 1px;
+  border-right-color: #ffffff;
+  border-left-width: 0;
+  content: " ";
+}
+
+.popover.bottom .arrow {
+  top: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-bottom-color: #999999;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+  border-top-width: 0;
+}
+
+.popover.bottom .arrow:after {
+  top: 1px;
+  margin-left: -10px;
+  border-bottom-color: #ffffff;
+  border-top-width: 0;
+  content: " ";
+}
+
+.popover.left .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-left-color: #999999;
+  border-left-color: rgba(0, 0, 0, 0.25);
+  border-right-width: 0;
+}
+
+.popover.left .arrow:after {
+  right: 1px;
+  bottom: -10px;
+  border-left-color: #ffffff;
+  border-right-width: 0;
+  content: " ";
+}
+
+.carousel {
+  position: relative;
+}
+
+.carousel-inner {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+
+.carousel-inner > .item {
+  position: relative;
+  display: none;
+  -webkit-transition: 0.6s ease-in-out left;
+          transition: 0.6s ease-in-out left;
+}
+
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  display: block;
+  height: auto;
+  max-width: 100%;
+  line-height: 1;
+}
+
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  display: block;
+}
+
+.carousel-inner > .active {
+  left: 0;
+}
+
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+
+.carousel-inner > .next {
+  left: 100%;
+}
+
+.carousel-inner > .prev {
+  left: -100%;
+}
+
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+  left: 0;
+}
+
+.carousel-inner > .active.left {
+  left: -100%;
+}
+
+.carousel-inner > .active.right {
+  left: 100%;
+}
+
+.carousel-control {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 15%;
+  font-size: 20px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.carousel-control.left {
+  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001)));
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%));
+  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+}
+
+.carousel-control.right {
+  right: 0;
+  left: auto;
+  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5)));
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%));
+  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+}
+
+.carousel-control:hover,
+.carousel-control:focus {
+  color: #ffffff;
+  text-decoration: none;
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+
+.carousel-control .icon-prev,
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  z-index: 5;
+  display: inline-block;
+}
+
+.carousel-control .icon-prev,
+.carousel-control .icon-next {
+  width: 20px;
+  height: 20px;
+  margin-top: -10px;
+  margin-left: -10px;
+  font-family: serif;
+}
+
+.carousel-control .icon-prev:before {
+  content: '\2039';
+}
+
+.carousel-control .icon-next:before {
+  content: '\203a';
+}
+
+.carousel-indicators {
+  position: absolute;
+  bottom: 10px;
+  left: 50%;
+  z-index: 15;
+  width: 60%;
+  padding-left: 0;
+  margin-left: -30%;
+  text-align: center;
+  list-style: none;
+}
+
+.carousel-indicators li {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  margin: 1px;
+  text-indent: -999px;
+  cursor: pointer;
+  border: 1px solid #ffffff;
+  border-radius: 10px;
+}
+
+.carousel-indicators .active {
+  width: 12px;
+  height: 12px;
+  margin: 0;
+  background-color: #ffffff;
+}
+
+.carousel-caption {
+  position: absolute;
+  right: 15%;
+  bottom: 20px;
+  left: 15%;
+  z-index: 10;
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+}
+
+.carousel-caption .btn {
+  text-shadow: none;
+}
+
+@media screen and (min-width: 768px) {
+  .carousel-control .icon-prev,
+  .carousel-control .icon-next {
+    width: 30px;
+    height: 30px;
+    margin-top: -15px;
+    margin-left: -15px;
+    font-size: 30px;
+  }
+  .carousel-caption {
+    right: 20%;
+    left: 20%;
+    padding-bottom: 30px;
+  }
+  .carousel-indicators {
+    bottom: 20px;
+  }
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: " ";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.pull-right {
+  float: right !important;
+}
+
+.pull-left {
+  float: left !important;
+}
+
+.hide {
+  display: none !important;
+}
+
+.show {
+  display: block !important;
+}
+
+.invisible {
+  visibility: hidden;
+}
+
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.affix {
+  position: fixed;
+}
+
+@-ms-viewport {
+  width: device-width;
+}
+
+@media screen and (max-width: 400px) {
+  @-ms-viewport {
+    width: 320px;
+  }
+}
+
+.hidden {
+  display: none !important;
+  visibility: hidden !important;
+}
+
+.visible-xs {
+  display: none !important;
+}
+
+tr.visible-xs {
+  display: none !important;
+}
+
+th.visible-xs,
+td.visible-xs {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-xs {
+    display: block !important;
+  }
+  tr.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-xs,
+  td.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-xs.visible-sm {
+    display: block !important;
+  }
+  tr.visible-xs.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-sm,
+  td.visible-xs.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-xs.visible-md {
+    display: block !important;
+  }
+  tr.visible-xs.visible-md {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-md,
+  td.visible-xs.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-xs.visible-lg {
+    display: block !important;
+  }
+  tr.visible-xs.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-lg,
+  td.visible-xs.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.visible-sm {
+  display: none !important;
+}
+
+tr.visible-sm {
+  display: none !important;
+}
+
+th.visible-sm,
+td.visible-sm {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-sm.visible-xs {
+    display: block !important;
+  }
+  tr.visible-sm.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-xs,
+  td.visible-sm.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm {
+    display: block !important;
+  }
+  tr.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-sm,
+  td.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-sm.visible-md {
+    display: block !important;
+  }
+  tr.visible-sm.visible-md {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-md,
+  td.visible-sm.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-sm.visible-lg {
+    display: block !important;
+  }
+  tr.visible-sm.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-lg,
+  td.visible-sm.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.visible-md {
+  display: none !important;
+}
+
+tr.visible-md {
+  display: none !important;
+}
+
+th.visible-md,
+td.visible-md {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-md.visible-xs {
+    display: block !important;
+  }
+  tr.visible-md.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-md.visible-xs,
+  td.visible-md.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-md.visible-sm {
+    display: block !important;
+  }
+  tr.visible-md.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-md.visible-sm,
+  td.visible-md.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md {
+    display: block !important;
+  }
+  tr.visible-md {
+    display: table-row !important;
+  }
+  th.visible-md,
+  td.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-md.visible-lg {
+    display: block !important;
+  }
+  tr.visible-md.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-md.visible-lg,
+  td.visible-md.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.visible-lg {
+  display: none !important;
+}
+
+tr.visible-lg {
+  display: none !important;
+}
+
+th.visible-lg,
+td.visible-lg {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-lg.visible-xs {
+    display: block !important;
+  }
+  tr.visible-lg.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-xs,
+  td.visible-lg.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-lg.visible-sm {
+    display: block !important;
+  }
+  tr.visible-lg.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-sm,
+  td.visible-lg.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-lg.visible-md {
+    display: block !important;
+  }
+  tr.visible-lg.visible-md {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-md,
+  td.visible-lg.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-lg {
+    display: block !important;
+  }
+  tr.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-lg,
+  td.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.hidden-xs {
+  display: block !important;
+}
+
+tr.hidden-xs {
+  display: table-row !important;
+}
+
+th.hidden-xs,
+td.hidden-xs {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-xs {
+    display: none !important;
+  }
+  tr.hidden-xs {
+    display: none !important;
+  }
+  th.hidden-xs,
+  td.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-xs.hidden-sm {
+    display: none !important;
+  }
+  tr.hidden-xs.hidden-sm {
+    display: none !important;
+  }
+  th.hidden-xs.hidden-sm,
+  td.hidden-xs.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-xs.hidden-md {
+    display: none !important;
+  }
+  tr.hidden-xs.hidden-md {
+    display: none !important;
+  }
+  th.hidden-xs.hidden-md,
+  td.hidden-xs.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-xs.hidden-lg {
+    display: none !important;
+  }
+  tr.hidden-xs.hidden-lg {
+    display: none !important;
+  }
+  th.hidden-xs.hidden-lg,
+  td.hidden-xs.hidden-lg {
+    display: none !important;
+  }
+}
+
+.hidden-sm {
+  display: block !important;
+}
+
+tr.hidden-sm {
+  display: table-row !important;
+}
+
+th.hidden-sm,
+td.hidden-sm {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-sm.hidden-xs {
+    display: none !important;
+  }
+  tr.hidden-sm.hidden-xs {
+    display: none !important;
+  }
+  th.hidden-sm.hidden-xs,
+  td.hidden-sm.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-sm {
+    display: none !important;
+  }
+  tr.hidden-sm {
+    display: none !important;
+  }
+  th.hidden-sm,
+  td.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-sm.hidden-md {
+    display: none !important;
+  }
+  tr.hidden-sm.hidden-md {
+    display: none !important;
+  }
+  th.hidden-sm.hidden-md,
+  td.hidden-sm.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-sm.hidden-lg {
+    display: none !important;
+  }
+  tr.hidden-sm.hidden-lg {
+    display: none !important;
+  }
+  th.hidden-sm.hidden-lg,
+  td.hidden-sm.hidden-lg {
+    display: none !important;
+  }
+}
+
+.hidden-md {
+  display: block !important;
+}
+
+tr.hidden-md {
+  display: table-row !important;
+}
+
+th.hidden-md,
+td.hidden-md {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-md.hidden-xs {
+    display: none !important;
+  }
+  tr.hidden-md.hidden-xs {
+    display: none !important;
+  }
+  th.hidden-md.hidden-xs,
+  td.hidden-md.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-md.hidden-sm {
+    display: none !important;
+  }
+  tr.hidden-md.hidden-sm {
+    display: none !important;
+  }
+  th.hidden-md.hidden-sm,
+  td.hidden-md.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-md {
+    display: none !important;
+  }
+  tr.hidden-md {
+    display: none !important;
+  }
+  th.hidden-md,
+  td.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-md.hidden-lg {
+    display: none !important;
+  }
+  tr.hidden-md.hidden-lg {
+    display: none !important;
+  }
+  th.hidden-md.hidden-lg,
+  td.hidden-md.hidden-lg {
+    display: none !important;
+  }
+}
+
+.hidden-lg {
+  display: block !important;
+}
+
+tr.hidden-lg {
+  display: table-row !important;
+}
+
+th.hidden-lg,
+td.hidden-lg {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-lg.hidden-xs {
+    display: none !important;
+  }
+  tr.hidden-lg.hidden-xs {
+    display: none !important;
+  }
+  th.hidden-lg.hidden-xs,
+  td.hidden-lg.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-lg.hidden-sm {
+    display: none !important;
+  }
+  tr.hidden-lg.hidden-sm {
+    display: none !important;
+  }
+  th.hidden-lg.hidden-sm,
+  td.hidden-lg.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-lg.hidden-md {
+    display: none !important;
+  }
+  tr.hidden-lg.hidden-md {
+    display: none !important;
+  }
+  th.hidden-lg.hidden-md,
+  td.hidden-lg.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-lg {
+    display: none !important;
+  }
+  tr.hidden-lg {
+    display: none !important;
+  }
+  th.hidden-lg,
+  td.hidden-lg {
+    display: none !important;
+  }
+}
+
+.visible-print {
+  display: none !important;
+}
+
+tr.visible-print {
+  display: none !important;
+}
+
+th.visible-print,
+td.visible-print {
+  display: none !important;
+}
+
+@media print {
+  .visible-print {
+    display: block !important;
+  }
+  tr.visible-print {
+    display: table-row !important;
+  }
+  th.visible-print,
+  td.visible-print {
+    display: table-cell !important;
+  }
+  .hidden-print {
+    display: none !important;
+  }
+  tr.hidden-print {
+    display: none !important;
+  }
+  th.hidden-print,
+  td.hidden-print {
+    display: none !important;
+  }
+}
\ No newline at end of file
diff --git a/docs/system-architecture/css/bootstrap-3.0.0/bootstrap.min.css b/docs/system-architecture/css/bootstrap-3.0.0/bootstrap.min.css
new file mode 100644
index 0000000..a553c4f
--- /dev/null
+++ b/docs/system-architecture/css/bootstrap-3.0.0/bootstrap.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap v3.0.0
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ *//*! normalize.css v2.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}mark{color:#000;background:#ff0}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid #c0c0c0}legend{padding:0;border:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:100%}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{padding:0;box-sizing:border-box}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:2cm .5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}button,input,select[multiple],textarea{background-image:none}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16.099999999999998px;font-weight:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-warning{color:#c09853}.text-danger{color:#b94a48}.text-success{color:#468847}.text-info{color:#3a87ad}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h4,h5,h6{margin-top:10px;margin-bottom:10px}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}h1 small,.h1 small{font-size:24px}h2 small,.h2 small{font-size:18px}h3 small,.h3 small,h4 small,.h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:bold}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small{display:block;line-height:1.428571429;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:1.428571429}code,pre{font-family:Monaco,Menlo,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.row{margin-right:-15px;margin-left:-15px}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11{float:left}.col-xs-1{width:8.333333333333332%}.col-xs-2{width:16.666666666666664%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333333333%}.col-xs-5{width:41.66666666666667%}.col-xs-6{width:50%}.col-xs-7{width:58.333333333333336%}.col-xs-8{width:66.66666666666666%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333333334%}.col-xs-11{width:91.66666666666666%}.col-xs-12{width:100%}@media(min-width:768px){.container{max-width:750px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11{float:left}.col-sm-1{width:8.333333333333332%}.col-sm-2{width:16.666666666666664%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333333333%}.col-sm-5{width:41.66666666666667%}.col-sm-6{width:50%}.col-sm-7{width:58.333333333333336%}.col-sm-8{width:66.66666666666666%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333333334%}.col-sm-11{width:91.66666666666666%}.col-sm-12{width:100%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-11{left:91.66666666666666%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-11{margin-left:91.66666666666666%}}@media(min-width:992px){.container{max-width:970px}.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11{float:left}.col-md-1{width:8.333333333333332%}.col-md-2{width:16.666666666666664%}.col-md-3{width:25%}.col-md-4{width:33.33333333333333%}.col-md-5{width:41.66666666666667%}.col-md-6{width:50%}.col-md-7{width:58.333333333333336%}.col-md-8{width:66.66666666666666%}.col-md-9{width:75%}.col-md-10{width:83.33333333333334%}.col-md-11{width:91.66666666666666%}.col-md-12{width:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.333333333333332%}.col-md-push-2{left:16.666666666666664%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333333333%}.col-md-push-5{left:41.66666666666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.333333333333336%}.col-md-push-8{left:66.66666666666666%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333333334%}.col-md-push-11{left:91.66666666666666%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-11{right:91.66666666666666%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-11{margin-left:91.66666666666666%}}@media(min-width:1200px){.container{max-width:1170px}.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11{float:left}.col-lg-1{width:8.333333333333332%}.col-lg-2{width:16.666666666666664%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333333333%}.col-lg-5{width:41.66666666666667%}.col-lg-6{width:50%}.col-lg-7{width:58.333333333333336%}.col-lg-8{width:66.66666666666666%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333333334%}.col-lg-11{width:91.66666666666666%}.col-lg-12{width:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-11{left:91.66666666666666%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-11{margin-left:91.66666666666666%}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #ddd}.table thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table caption+thead tr:first-child th,.table colgroup+thead tr:first-child th,.table thead:first-child tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed thead>tr>th,.table-condensed tbody>tr>th,.table-condensed tfoot>tr>th,.table-condensed thead>tr>td,.table-condensed tbody>tr>td,.table-condensed tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8;border-color:#d6e9c6}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td{background-color:#d0e9c6;border-color:#c9e2b3}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede;border-color:#eed3d7}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td{background-color:#ebcccc;border-color:#e6c1c7}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3;border-color:#fbeed5}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td{background-color:#faf2cc;border-color:#f8e5be}@media(max-width:768px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:scroll;overflow-y:hidden;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0;background-color:#fff}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>thead>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>thead>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}select[multiple],select[size]{height:auto}select optgroup{font-family:inherit;font-size:inherit;font-style:inherit}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}textarea.form-control{height:auto}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;padding-left:20px;margin-top:10px;margin-bottom:10px;vertical-align:middle}.radio label,.checkbox label{display:inline;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:normal;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm{height:auto}.input-lg{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:45px;line-height:45px}textarea.input-lg{height:auto}.has-warning .help-block,.has-warning .control-label{color:#c09853}.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.has-warning .input-group-addon{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.has-error .help-block,.has-error .control-label{color:#b94a48}.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.has-error .input-group-addon{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.has-success .help-block,.has-success .control-label{color:#468847}.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.has-success .input-group-addon{color:#468847;background-color:#dff0d8;border-color:#468847}.form-control-static{padding-top:7px;margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block}.form-inline .radio,.form-inline .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}@media(min-width:768px){.form-horizontal .control-label{text-align:right}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid transparent;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-link{font-weight:normal;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-xs{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';-webkit-font-smoothing:antialiased;font-style:normal;font-weight:normal;line-height:1}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-print:before{content:"\e045"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-briefcase:before{content:"\1f4bc"}.glyphicon-calendar:before{content:"\1f4c5"}.glyphicon-pushpin:before{content:"\1f4cc"}.glyphicon-paperclip:before{content:"\1f4ce"}.glyphicon-camera:before{content:"\1f4f7"}.glyphicon-lock:before{content:"\1f512"}.glyphicon-bell:before{content:"\1f514"}.glyphicon-bookmark:before{content:"\1f516"}.glyphicon-fire:before{content:"\1f525"}.glyphicon-wrench:before{content:"\1f527"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid #000;border-right:4px solid transparent;border-bottom:0 dotted;border-left:4px solid transparent;content:""}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#fff;text-decoration:none;background-color:#428bca}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0 dotted;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}}.btn-default .caret{border-top-color:#333}.btn-primary .caret,.btn-success .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret{border-top-color:#fff}.dropup .btn-default .caret{border-bottom-color:#333}.dropup .btn-primary .caret,.dropup .btn-success .caret,.dropup .btn-warning .caret,.dropup .btn-danger .caret,.dropup .btn-info .caret{border-bottom-color:#fff}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:5px 10px;padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-bottom-left-radius:4px;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child>.btn:last-child,.btn-group-vertical>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;border-collapse:separate;table-layout:fixed}.btn-group-justified .btn{display:table-cell;float:none;width:1%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group.col{float:none;padding-right:0;padding-left:0}.input-group .form-control{width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:45px;line-height:45px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-4px}.input-group-btn>.btn:hover,.input-group-btn>.btn:active{z-index:2}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}}.nav-tabs.nav-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs.nav-justified>.active>a{border-bottom-color:#fff}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:5px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs-justified>.active>a{border-bottom-color:#fff}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.nav .caret{border-top-color:#428bca;border-bottom-color:#428bca}.nav a:hover .caret{border-top-color:#2a6496;border-bottom-color:#2a6496}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;z-index:1000;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}@media(min-width:768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-collapse .navbar-nav.navbar-left:first-child{margin-left:-15px}.navbar-collapse .navbar-nav.navbar-right:last-child{margin-right:-15px}.navbar-collapse .navbar-text:last-child{margin-right:0}}.container>.navbar-header,.container>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.container>.navbar-header,.container>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;border-width:0 0 1px}@media(min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;z-index:1030}.navbar-fixed-bottom{bottom:0;margin-bottom:0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media(min-width:768px){.navbar>.container .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media(min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-text{float:left;margin-top:15px;margin-bottom:15px}@media(min-width:768px){.navbar-text{margin-right:15px;margin-left:15px}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#ccc}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e6e6e6}.navbar-default .navbar-nav>.dropdown>a:hover .caret,.navbar-default .navbar-nav>.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.open>a .caret,.navbar-default .navbar-nav>.open>a:hover .caret,.navbar-default .navbar-nav>.open>a:focus .caret{border-top-color:#555;border-bottom-color:#555}.navbar-default .navbar-nav>.dropdown>a .caret{border-top-color:#777;border-bottom-color:#777}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#eee}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#999;border-radius:10px}.badge:empty{display:none}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.btn .badge{position:relative;top:-1px}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eee}.jumbotron h1{line-height:1;color:inherit}.jumbotron p{line-height:1.4}.container .jumbotron{border-radius:6px}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1{font-size:63px}}.thumbnail{display:inline-block;display:block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img{display:block;height:auto;max-width:100%}a.thumbnail:hover,a.thumbnail:focus{border-color:#428bca}.thumbnail>img{margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#356635}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#2d6987}.alert-warning{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.alert-warning hr{border-top-color:#f8e5be}.alert-warning .alert-link{color:#a47e3c}.alert-danger{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger hr{border-top-color:#e6c1c7}.alert-danger .alert-link{color:#953b39}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0}.panel>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table{margin-bottom:0}.panel>.panel-body+.table{border-top:1px solid #ddd}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-title{margin-top:0;margin-bottom:0;font-size:16px}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-group .panel{margin-bottom:0;overflow:hidden;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-warning{border-color:#fbeed5}.panel-warning>.panel-heading{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#fbeed5}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#fbeed5}.panel-danger{border-color:#eed3d7}.panel-danger>.panel-heading{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#eed3d7}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#eed3d7}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}body.modal-open,.modal-open .navbar-fixed-top,.modal-open .navbar-fixed-bottom{margin-right:15px}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:auto;overflow-y:scroll}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{z-index:1050;width:auto;padding:10px;margin-right:auto;margin-left:auto}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{min-height:16.428571429px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{padding:19px 20px 20px;margin-top:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media screen and (min-width:768px){.modal-dialog{right:auto;left:50%;width:600px;padding-top:30px;padding-bottom:30px}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}}.tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0;content:" "}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0;content:" "}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0;content:" "}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0;content:" "}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;height:auto;max-width:100%;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);opacity:.5;filter:alpha(opacity=50)}.carousel-control.left{background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.5)),to(rgba(0,0,0,0.0001)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.5) 0),color-stop(rgba(0,0,0,0.0001) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1)}.carousel-control.right{right:0;left:auto;background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.0001)),to(rgba(0,0,0,0.5)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.0001) 0),color-stop(rgba(0,0,0,0.5) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1)}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;left:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix:after{clear:both}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.affix{position:fixed}@-ms-viewport{width:device-width}@media screen and (max-width:400px){@-ms-viewport{width:320px}}.hidden{display:none!important;visibility:hidden!important}.visible-xs{display:none!important}tr.visible-xs{display:none!important}th.visible-xs,td.visible-xs{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-xs.visible-sm{display:block!important}tr.visible-xs.visible-sm{display:table-row!important}th.visible-xs.visible-sm,td.visible-xs.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-xs.visible-md{display:block!important}tr.visible-xs.visible-md{display:table-row!important}th.visible-xs.visible-md,td.visible-xs.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-xs.visible-lg{display:block!important}tr.visible-xs.visible-lg{display:table-row!important}th.visible-xs.visible-lg,td.visible-xs.visible-lg{display:table-cell!important}}.visible-sm{display:none!important}tr.visible-sm{display:none!important}th.visible-sm,td.visible-sm{display:none!important}@media(max-width:767px){.visible-sm.visible-xs{display:block!important}tr.visible-sm.visible-xs{display:table-row!important}th.visible-sm.visible-xs,td.visible-sm.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-sm.visible-md{display:block!important}tr.visible-sm.visible-md{display:table-row!important}th.visible-sm.visible-md,td.visible-sm.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-sm.visible-lg{display:block!important}tr.visible-sm.visible-lg{display:table-row!important}th.visible-sm.visible-lg,td.visible-sm.visible-lg{display:table-cell!important}}.visible-md{display:none!important}tr.visible-md{display:none!important}th.visible-md,td.visible-md{display:none!important}@media(max-width:767px){.visible-md.visible-xs{display:block!important}tr.visible-md.visible-xs{display:table-row!important}th.visible-md.visible-xs,td.visible-md.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-md.visible-sm{display:block!important}tr.visible-md.visible-sm{display:table-row!important}th.visible-md.visible-sm,td.visible-md.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-md.visible-lg{display:block!important}tr.visible-md.visible-lg{display:table-row!important}th.visible-md.visible-lg,td.visible-md.visible-lg{display:table-cell!important}}.visible-lg{display:none!important}tr.visible-lg{display:none!important}th.visible-lg,td.visible-lg{display:none!important}@media(max-width:767px){.visible-lg.visible-xs{display:block!important}tr.visible-lg.visible-xs{display:table-row!important}th.visible-lg.visible-xs,td.visible-lg.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-lg.visible-sm{display:block!important}tr.visible-lg.visible-sm{display:table-row!important}th.visible-lg.visible-sm,td.visible-lg.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-lg.visible-md{display:block!important}tr.visible-lg.visible-md{display:table-row!important}th.visible-lg.visible-md,td.visible-lg.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-lg{display:block!important}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}.hidden-xs{display:block!important}tr.hidden-xs{display:table-row!important}th.hidden-xs,td.hidden-xs{display:table-cell!important}@media(max-width:767px){.hidden-xs{display:none!important}tr.hidden-xs{display:none!important}th.hidden-xs,td.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-xs.hidden-sm{display:none!important}tr.hidden-xs.hidden-sm{display:none!important}th.hidden-xs.hidden-sm,td.hidden-xs.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-xs.hidden-md{display:none!important}tr.hidden-xs.hidden-md{display:none!important}th.hidden-xs.hidden-md,td.hidden-xs.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-xs.hidden-lg{display:none!important}tr.hidden-xs.hidden-lg{display:none!important}th.hidden-xs.hidden-lg,td.hidden-xs.hidden-lg{display:none!important}}.hidden-sm{display:block!important}tr.hidden-sm{display:table-row!important}th.hidden-sm,td.hidden-sm{display:table-cell!important}@media(max-width:767px){.hidden-sm.hidden-xs{display:none!important}tr.hidden-sm.hidden-xs{display:none!important}th.hidden-sm.hidden-xs,td.hidden-sm.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}tr.hidden-sm{display:none!important}th.hidden-sm,td.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-sm.hidden-md{display:none!important}tr.hidden-sm.hidden-md{display:none!important}th.hidden-sm.hidden-md,td.hidden-sm.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-sm.hidden-lg{display:none!important}tr.hidden-sm.hidden-lg{display:none!important}th.hidden-sm.hidden-lg,td.hidden-sm.hidden-lg{display:none!important}}.hidden-md{display:block!important}tr.hidden-md{display:table-row!important}th.hidden-md,td.hidden-md{display:table-cell!important}@media(max-width:767px){.hidden-md.hidden-xs{display:none!important}tr.hidden-md.hidden-xs{display:none!important}th.hidden-md.hidden-xs,td.hidden-md.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-md.hidden-sm{display:none!important}tr.hidden-md.hidden-sm{display:none!important}th.hidden-md.hidden-sm,td.hidden-md.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}tr.hidden-md{display:none!important}th.hidden-md,td.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-md.hidden-lg{display:none!important}tr.hidden-md.hidden-lg{display:none!important}th.hidden-md.hidden-lg,td.hidden-md.hidden-lg{display:none!important}}.hidden-lg{display:block!important}tr.hidden-lg{display:table-row!important}th.hidden-lg,td.hidden-lg{display:table-cell!important}@media(max-width:767px){.hidden-lg.hidden-xs{display:none!important}tr.hidden-lg.hidden-xs{display:none!important}th.hidden-lg.hidden-xs,td.hidden-lg.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-lg.hidden-sm{display:none!important}tr.hidden-lg.hidden-sm{display:none!important}th.hidden-lg.hidden-sm,td.hidden-lg.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-lg.hidden-md{display:none!important}tr.hidden-lg.hidden-md{display:none!important}th.hidden-lg.hidden-md,td.hidden-lg.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg{display:none!important}tr.hidden-lg{display:none!important}th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print{display:none!important}tr.visible-print{display:none!important}th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}.hidden-print{display:none!important}tr.hidden-print{display:none!important}th.hidden-print,td.hidden-print{display:none!important}}
\ No newline at end of file
diff --git a/docs/system-architecture/css/fonts/glyphicons-halflings-regular.eot b/docs/system-architecture/css/fonts/glyphicons-halflings-regular.eot
new file mode 100644
index 0000000..87eaa43
--- /dev/null
+++ b/docs/system-architecture/css/fonts/glyphicons-halflings-regular.eot
Binary files differ
diff --git a/docs/system-architecture/css/fonts/glyphicons-halflings-regular.svg b/docs/system-architecture/css/fonts/glyphicons-halflings-regular.svg
new file mode 100644
index 0000000..5fee068
--- /dev/null
+++ b/docs/system-architecture/css/fonts/glyphicons-halflings-regular.svg
@@ -0,0 +1,228 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
+<font-face units-per-em="1200" ascent="960" descent="-240" />
+<missing-glyph horiz-adv-x="500" />
+<glyph />
+<glyph />
+<glyph unicode=" " />
+<glyph unicode="*" d="M1100 500h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200z" />
+<glyph unicode="+" d="M1100 400h-400v-400h-300v400h-400v300h400v400h300v-400h400v-300z" />
+<glyph unicode="&#xa0;" />
+<glyph unicode="&#x2000;" horiz-adv-x="652" />
+<glyph unicode="&#x2001;" horiz-adv-x="1304" />
+<glyph unicode="&#x2002;" horiz-adv-x="652" />
+<glyph unicode="&#x2003;" horiz-adv-x="1304" />
+<glyph unicode="&#x2004;" horiz-adv-x="434" />
+<glyph unicode="&#x2005;" horiz-adv-x="326" />
+<glyph unicode="&#x2006;" horiz-adv-x="217" />
+<glyph unicode="&#x2007;" horiz-adv-x="217" />
+<glyph unicode="&#x2008;" horiz-adv-x="163" />
+<glyph unicode="&#x2009;" horiz-adv-x="260" />
+<glyph unicode="&#x200a;" horiz-adv-x="72" />
+<glyph unicode="&#x202f;" horiz-adv-x="260" />
+<glyph unicode="&#x205f;" horiz-adv-x="326" />
+<glyph unicode="&#x20ac;" d="M800 500h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257 q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406z" />
+<glyph unicode="&#x2212;" d="M1100 700h-900v-300h900v300z" />
+<glyph unicode="&#x2601;" d="M178 300h750q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5q0 -80 56.5 -137t135.5 -57z" />
+<glyph unicode="&#x2709;" d="M1200 1100h-1200l600 -603zM300 600l-300 -300v600zM1200 900v-600l-300 300zM800 500l400 -400h-1200l400 400l200 -200z" />
+<glyph unicode="&#x270f;" d="M1101 889l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13l-94 -97zM401 189l614 614l-214 214l-614 -614zM-13 -13l333 112l-223 223z" />
+<glyph unicode="&#xe000;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#xe001;" d="M700 100h300v-100h-800v100h300v550l-500 550h1200l-500 -550v-550z" />
+<glyph unicode="&#xe002;" d="M1000 934v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q17 -55 85.5 -75.5t147.5 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7q-79 -25 -122.5 -82t-25.5 -112t86 -75.5t147 5.5 q65 21 109 69t44 90v606z" />
+<glyph unicode="&#xe003;" d="M913 432l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342t142 342t342 142t342 -142t142 -342q0 -142 -78 -261zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
+<glyph unicode="&#xe005;" d="M649 949q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5t-94 124.5t-33.5 117.5q0 64 28 123t73 100.5t104.5 64t119 20.5 t120 -38.5t104.5 -104.5z" />
+<glyph unicode="&#xe006;" d="M791 522l145 -449l-384 275l-382 -275l146 447l-388 280h479l146 400h2l146 -400h472zM168 71l2 1z" />
+<glyph unicode="&#xe007;" d="M791 522l145 -449l-384 275l-382 -275l146 447l-388 280h479l146 400h2l146 -400h472zM747 331l-74 229l193 140h-235l-77 211l-78 -211h-239l196 -142l-73 -226l192 140zM168 71l2 1z" />
+<glyph unicode="&#xe008;" d="M1200 143v-143h-1200v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100z" />
+<glyph unicode="&#xe009;" d="M1200 1100v-1100h-1200v1100h1200zM200 1000h-100v-100h100v100zM900 1000h-600v-400h600v400zM1100 1000h-100v-100h100v100zM200 800h-100v-100h100v100zM1100 800h-100v-100h100v100zM200 600h-100v-100h100v100zM1100 600h-100v-100h100v100zM900 500h-600v-400h600 v400zM200 400h-100v-100h100v100zM1100 400h-100v-100h100v100zM200 200h-100v-100h100v100zM1100 200h-100v-100h100v100z" />
+<glyph unicode="&#xe010;" d="M500 1050v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5zM1100 1050v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h400 q21 0 35.5 -14.5t14.5 -35.5zM500 450v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5zM1100 450v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5z" />
+<glyph unicode="&#xe011;" d="M300 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM700 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5zM1100 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM300 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM700 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1100 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM300 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM700 250v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1100 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5 t14.5 -35.5z" />
+<glyph unicode="&#xe012;" d="M300 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1200 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h700 q21 0 35.5 -14.5t14.5 -35.5zM300 450v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-200q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5zM1200 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5zM300 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1200 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5z" />
+<glyph unicode="&#xe013;" d="M448 34l818 820l-212 212l-607 -607l-206 207l-212 -212z" />
+<glyph unicode="&#xe014;" d="M882 106l-282 282l-282 -282l-212 212l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282z" />
+<glyph unicode="&#xe015;" d="M913 432l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342t142 342t342 142t342 -142t142 -342q0 -142 -78 -261zM507 363q137 0 233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5t-234 -97t-97 -233 t97 -233t234 -97zM600 800h100v-200h-100v-100h-200v100h-100v200h100v100h200v-100z" />
+<glyph unicode="&#xe016;" d="M913 432l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 299q-120 -77 -261 -77q-200 0 -342 142t-142 342t142 342t342 142t342 -142t142 -342q0 -141 -78 -262zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 801v-200h400v200h-400z" />
+<glyph unicode="&#xe017;" d="M700 750v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5zM800 975v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123 t-123 184t-45.5 224.5q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155z" />
+<glyph unicode="&#xe018;" d="M1200 1h-200v1200h200v-1200zM900 1h-200v800h200v-800zM600 1h-200v500h200v-500zM300 301h-200v-300h200v300z" />
+<glyph unicode="&#xe019;" d="M488 183l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5 q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39zM600 815q89 0 152 -63 t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152q0 88 63 151t152 63z" />
+<glyph unicode="&#xe020;" d="M900 1100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100zM800 1100v100h-300v-100h300zM200 900h900v-800q0 -41 -29.5 -71 t-70.5 -30h-700q-41 0 -70.5 30t-29.5 71v800zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
+<glyph unicode="&#xe021;" d="M1301 601h-200v-600h-300v400h-300v-400h-300v600h-200l656 644z" />
+<glyph unicode="&#xe022;" d="M600 700h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18v1150q0 11 7 18t18 7h475v-500zM1000 800h-300v300z" />
+<glyph unicode="&#xe023;" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM600 600h200 v-100h-300v400h100v-300z" />
+<glyph unicode="&#xe024;" d="M721 400h-242l-40 -400h-539l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538zM712 500l-27 300h-170l-27 -300h224z" />
+<glyph unicode="&#xe025;" d="M1100 400v-400h-1100v400h490l-290 300h200v500h300v-500h200l-290 -300h490zM988 300h-175v-100h175v100z" />
+<glyph unicode="&#xe026;" d="M600 1199q122 0 233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233t47.5 233t127.5 191t191 127.5t233 47.5zM600 1012q-170 0 -291 -121t-121 -291t121 -291t291 -121t291 121 t121 291t-121 291t-291 121zM700 600h150l-250 -300l-250 300h150v300h200v-300z" />
+<glyph unicode="&#xe027;" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM850 600h-150 v-300h-200v300h-150l250 300z" />
+<glyph unicode="&#xe028;" d="M0 500l200 700h800q199 -700 200 -700v-475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18v475zM903 1000h-606l-97 -500h200l50 -200h300l50 200h200z" />
+<glyph unicode="&#xe029;" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5zM797 598 l-297 -201v401z" />
+<glyph unicode="&#xe030;" d="M1177 600h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123t-123 -184t-45.5 -224.5t45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123 t123 184t45.5 224.5z" />
+<glyph unicode="&#xe031;" d="M700 800l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400zM500 400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122l-145 -145v400h400z" />
+<glyph unicode="&#xe032;" d="M100 1200v-1200h1100v1200h-1100zM1100 100h-900v900h900v-900zM400 800h-100v100h100v-100zM1000 800h-500v100h500v-100zM400 600h-100v100h100v-100zM1000 600h-500v100h500v-100zM400 400h-100v100h100v-100zM1000 400h-500v100h500v-100zM400 200h-100v100h100v-100 zM1000 300h-500v-100h500v100z" />
+<glyph unicode="&#xe034;" d="M200 0h-100v1100h100v-1100zM1100 600v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5z" />
+<glyph unicode="&#xe035;" d="M1200 275v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5t-49.5 -227v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50 q11 0 18 7t7 18zM400 480v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14zM1000 480v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14z" />
+<glyph unicode="&#xe036;" d="M0 800v-400h300l300 -200v800l-300 -200h-300zM971 600l141 -141l-71 -71l-141 141l-141 -141l-71 71l141 141l-141 141l71 71l141 -141l141 141l71 -71z" />
+<glyph unicode="&#xe037;" d="M0 800v-400h300l300 -200v800l-300 -200h-300zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
+<glyph unicode="&#xe038;" d="M974 186l6 8q142 178 142 405q0 230 -144 408l-6 8l-83 -64l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8zM300 801l300 200v-800l-300 200h-300v400h300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257z" />
+<glyph unicode="&#xe039;" d="M100 700h400v100h100v100h-100v300h-500v-600h100v100zM1200 700v500h-600v-200h100v-300h200v-300h300v200h-200v100h200zM100 1100h300v-300h-300v300zM800 800v300h300v-300h-300zM200 900h100v100h-100v-100zM900 1000h100v-100h-100v100zM300 600h-100v-100h-200 v-500h500v500h-200v100zM900 200v-100h-200v100h-100v100h100v200h-200v100h300v-300h200v-100h-100zM400 400v-300h-300v300h300zM300 200h-100v100h100v-100zM1100 300h100v-100h-100v100zM600 100h100v-100h-100v100zM1200 100v-100h-300v100h300z" />
+<glyph unicode="&#xe040;" d="M100 1200h-100v-1000h100v1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 1200v-1000h-200v1000h200zM400 100v-100h-300v100h300zM500 91h100v-91h-100v91zM700 91h100v-91h-100v91zM1100 91v-91h-200v91h200z " />
+<glyph unicode="&#xe041;" d="M1200 500l-500 -500l-699 700v475q0 10 7.5 17.5t17.5 7.5h474zM320 882q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71t29 -71q30 -30 71.5 -30t71.5 30z" />
+<glyph unicode="&#xe042;" d="M1201 500l-500 -500l-699 700v475q0 11 7 18t18 7h474zM1501 500l-500 -500l-50 50l450 450l-700 700h100zM320 882q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71t30 -71q29 -30 71 -30t71 30z" />
+<glyph unicode="&#xe043;" d="M1200 1200v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900v1025l175 175h925z" />
+<glyph unicode="&#xe045;" d="M947 829l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18l-94 -346l40 -124h592zM1200 800v-700h-200v200h-800v-200h-200v700h200l100 -200h600l100 200h200zM881 176l38 -152q2 -10 -3.5 -17t-15.5 -7h-600q-10 0 -15.5 7t-3.5 17l38 152q2 10 11.5 17t19.5 7 h500q10 0 19.5 -7t11.5 -17z" />
+<glyph unicode="&#xe047;" d="M1200 0v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417zM416 521l178 457l46 -140l116 -317 h-340z" />
+<glyph unicode="&#xe048;" d="M100 1199h471q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111t-162 -38.5h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21 t-29 14t-49 14.5v70zM400 1079v-379h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400z" />
+<glyph unicode="&#xe049;" d="M877 1200l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425z" />
+<glyph unicode="&#xe050;" d="M1150 1200h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49v300h150h700zM100 1000v-800h75l-125 -167l-125 167h75v800h-75l125 167 l125 -167h-75z" />
+<glyph unicode="&#xe051;" d="M950 1201h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50v300h150h700zM200 101h800v75l167 -125l-167 -125v75h-800v-75l-167 125l167 125 v-75z" />
+<glyph unicode="&#xe052;" d="M700 950v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35zM1100 650v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h1000 q21 0 35.5 15t14.5 35zM900 350v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35zM1200 50v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35 t35.5 -15h1100q21 0 35.5 15t14.5 35z" />
+<glyph unicode="&#xe053;" d="M1000 950v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35zM1200 650v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h1100 q21 0 35.5 15t14.5 35zM1000 350v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35zM1200 50v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35 t35.5 -15h1100q21 0 35.5 15t14.5 35z" />
+<glyph unicode="&#xe054;" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe055;" d="M0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe056;" d="M0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35zM0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe057;" d="M400 1100h-100v-1100h100v1100zM700 950v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35zM1100 650v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15 h500q20 0 35 15t15 35zM100 425v75h-201v100h201v75l166 -125zM900 350v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35zM1200 50v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5 v-100q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35z" />
+<glyph unicode="&#xe058;" d="M201 950v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35zM801 1100h100v-1100h-100v1100zM601 650v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15 h500q20 0 35 15t15 35zM1101 425v75h200v100h-200v75l-167 -125zM401 350v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35zM701 50v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5 v-100q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35z" />
+<glyph unicode="&#xe059;" d="M900 925v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53zM1200 300l-300 300l300 300v-600z" />
+<glyph unicode="&#xe060;" d="M1200 1056v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31zM1100 1000h-1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500zM476 750q0 -56 -39 -95t-95 -39t-95 39t-39 95t39 95t95 39t95 -39 t39 -95z" />
+<glyph unicode="&#xe062;" d="M600 1213q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262q0 124 60.5 231.5t165 172t226.5 64.5zM599 514q107 0 182.5 75.5t75.5 182.5t-75.5 182 t-182.5 75t-182 -75.5t-75 -181.5q0 -107 75.5 -182.5t181.5 -75.5z" />
+<glyph unicode="&#xe063;" d="M600 1199q122 0 233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233t47.5 233t127.5 191t191 127.5t233 47.5zM600 173v854q-176 0 -301.5 -125t-125.5 -302t125.5 -302t301.5 -125z " />
+<glyph unicode="&#xe064;" d="M554 1295q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 138.5t-64 210.5q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5zM455 296q-7 6 -18 17 t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156q14 -82 59.5 -136t136.5 -80z" />
+<glyph unicode="&#xe065;" d="M1108 902l113 113l-21 85l-92 28l-113 -113zM1100 625v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5 t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125zM436 341l161 50l412 412l-114 113l-405 -405z" />
+<glyph unicode="&#xe066;" d="M1100 453v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5z M813 431l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209z" />
+<glyph unicode="&#xe067;" d="M1100 569v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5h300q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69z M625 348l566 567l-136 137l-430 -431l-147 147l-136 -136z" />
+<glyph unicode="&#xe068;" d="M900 303v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198l-300 300l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296z" />
+<glyph unicode="&#xe069;" d="M900 0l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100z" />
+<glyph unicode="&#xe070;" d="M1200 0l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100z" />
+<glyph unicode="&#xe071;" d="M1200 0l-500 488v-488l-564 550l564 550v-487l500 487v-1100z" />
+<glyph unicode="&#xe072;" d="M1100 550l-900 550v-1100z" />
+<glyph unicode="&#xe073;" d="M500 150v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5zM900 150v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800q0 -21 14.5 -35.5t35.5 -14.5h200 q21 0 35.5 14.5t14.5 35.5z" />
+<glyph unicode="&#xe074;" d="M1100 150v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35z" />
+<glyph unicode="&#xe075;" d="M500 0v488l-500 -488v1100l500 -487v487l564 -550z" />
+<glyph unicode="&#xe076;" d="M1050 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488l-500 -488v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe077;" d="M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe078;" d="M650 1064l-550 -564h1100zM1200 350v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" />
+<glyph unicode="&#xe079;" d="M777 7l240 240l-353 353l353 353l-240 240l-592 -594z" />
+<glyph unicode="&#xe080;" d="M513 -46l-241 240l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1z" />
+<glyph unicode="&#xe081;" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM500 900v-200h-200v-200h200v-200h200v200h200v200h-200v200h-200z" />
+<glyph unicode="&#xe082;" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM300 700v-200h600v200h-600z" />
+<glyph unicode="&#xe083;" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM247 741l141 -141l-142 -141l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141 l-141 142z" />
+<glyph unicode="&#xe084;" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM546 623l-102 102l-174 -174l276 -277l411 411l-175 174z" />
+<glyph unicode="&#xe085;" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM500 500h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3 q-105 0 -172 -56t-67 -183h144q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5zM500 400v-100h200v100h-200z" />
+<glyph unicode="&#xe086;" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM500 900v-100h200v100h-200zM400 700v-100h100v-200h-100v-100h400v100h-100v300h-300z" />
+<glyph unicode="&#xe087;" d="M1200 700v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194v200h194q15 60 36 104.5t55.5 86t88 69t126.5 40.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203zM700 500v-206q149 48 201 206h-201v200h200 q-25 74 -76 127.5t-124 76.5v-204h-200v203q-75 -24 -130 -77.5t-79 -125.5h209v-200h-210q24 -73 79.5 -127.5t130.5 -78.5v206h200z" />
+<glyph unicode="&#xe088;" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM844 735 l-135 -135l135 -135l-109 -109l-135 135l-135 -135l-109 109l135 135l-135 135l109 109l135 -135l135 135z" />
+<glyph unicode="&#xe089;" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM896 654 l-346 -345l-228 228l141 141l87 -87l204 205z" />
+<glyph unicode="&#xe090;" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM248 385l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5q0 -115 62 -215zM955 809l-564 -564q97 -59 209 -59q171 0 292.5 121.5 t121.5 292.5q0 112 -59 209z" />
+<glyph unicode="&#xe091;" d="M1200 400h-600v-301l-600 448l600 453v-300h600v-300z" />
+<glyph unicode="&#xe092;" d="M600 400h-600v300h600v300l600 -453l-600 -448v301z" />
+<glyph unicode="&#xe093;" d="M1098 600h-298v-600h-300v600h-296l450 600z" />
+<glyph unicode="&#xe094;" d="M998 600l-449 -600l-445 600h296v600h300v-600h298z" />
+<glyph unicode="&#xe095;" d="M600 199v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453z" />
+<glyph unicode="&#xe096;" d="M1200 1200h-400l129 -129l-294 -294l142 -142l294 294l129 -129v400zM565 423l-294 -294l129 -129h-400v400l129 -129l294 294z" />
+<glyph unicode="&#xe097;" d="M871 730l129 -130h-400v400l129 -129l295 295l142 -141zM200 600h400v-400l-129 130l-295 -295l-142 141l295 295z" />
+<glyph unicode="&#xe101;" d="M600 1177q118 0 224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5t45.5 224.5t123 184t184 123t224.5 45.5zM686 549l58 302q4 20 -8 34.5t-33 14.5h-207q-20 0 -32 -14.5t-8 -34.5 l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5zM700 400h-200v-100h200v100z" />
+<glyph unicode="&#xe102;" d="M1200 900h-111v6t-1 15t-3 18l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6h-111v-100h100v-200h400v300h200v-300h400v200h100v100z M731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269zM481 900h-281q-3 0 14 48t35 96l18 47zM100 0h400v400h-400v-400zM700 400h400v-400h-400v400z" />
+<glyph unicode="&#xe103;" d="M0 121l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55l-201 -202 v143zM692 611q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5t86.5 76.5q55 66 367 234z" />
+<glyph unicode="&#xe105;" d="M1261 600l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30l-26 40l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5 t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30zM600 240q64 0 123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212 q0 85 46 158q-102 -87 -226 -258q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5zM484 762l-107 -106q49 -124 154 -191l105 105q-37 24 -75 72t-57 84z" />
+<glyph unicode="&#xe106;" d="M906 1200l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43l-26 40l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148zM1261 600l-26 -40q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5 t-124 -100t-146.5 -79l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52zM513 264l37 141q-107 18 -178.5 101.5t-71.5 193.5q0 85 46 158q-102 -87 -226 -258q210 -282 393 -336z M484 762l-107 -106q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68z" />
+<glyph unicode="&#xe107;" d="M-47 0h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 33 -48 36t-48 -29l-642 -1066q-21 -32 -7.5 -66t50.5 -34zM700 200v100h-200v-100h-345l445 723l445 -723h-345zM700 700h-200v-100l100 -300l100 300v100z" />
+<glyph unicode="&#xe108;" d="M800 711l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -21 -13 -29t-32 1l-94 78h-222l-94 -78q-19 -9 -32 -1t-13 29v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41q0 20 11 44.5t26 38.5 l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339z" />
+<glyph unicode="&#xe110;" d="M941 800l-600 -600h-341v200h259l600 600h241v198l300 -295l-300 -300v197h-159zM381 678l141 142l-181 180h-341v-200h259zM1100 598l300 -295l-300 -300v197h-241l-181 181l141 142l122 -123h159v198z" />
+<glyph unicode="&#xe111;" d="M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z" />
+<glyph unicode="&#xe112;" d="M400 900h-300v300h300v-300zM1100 900h-300v300h300v-300zM1100 800v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5t-58 109.5t-31.5 116t-15 104t-3 83v200h300v-250q0 -113 6 -145 q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300z" />
+<glyph unicode="&#xe113;" d="M902 184l226 227l-578 579l-580 -579l227 -227l352 353z" />
+<glyph unicode="&#xe114;" d="M650 218l578 579l-226 227l-353 -353l-352 353l-227 -227z" />
+<glyph unicode="&#xe115;" d="M1198 400v600h-796l215 -200h381v-400h-198l299 -283l299 283h-200zM-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196z" />
+<glyph unicode="&#xe116;" d="M1050 1200h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35 q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43l-100 475q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5z" />
+<glyph unicode="&#xe117;" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" />
+<glyph unicode="&#xe118;" d="M201 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000zM1501 700l-300 -700h-1200l300 700h1200z" />
+<glyph unicode="&#xe119;" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
+<glyph unicode="&#xe120;" d="M900 303v197h-600v-197l-300 297l300 298v-198h600v198l300 -298z" />
+<glyph unicode="&#xe121;" d="M31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM100 300h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM900 200h-100v-100h100v100z M1100 200h-100v-100h100v100z" />
+<glyph unicode="&#xe122;" d="M1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35zM325 800l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35q-56 337 -56 351v250v5 q0 13 0.5 18.5t2.5 13t8 10.5t15 3h200zM-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5z" />
+<glyph unicode="&#xe124;" d="M445 1180l-45 -233l-224 78l78 -225l-233 -44l179 -156l-179 -155l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180z" />
+<glyph unicode="&#xe125;" d="M700 1200h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400q0 -75 100 -75h61q123 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5zM700 925l-50 -225h450 v-125l-250 -375h-214l-136 100h-100v375l150 212l100 213h50v-175zM0 800v-600h200v600h-200z" />
+<glyph unicode="&#xe126;" d="M700 0h-50q-27 0 -51 20t-38 48l-96 198l-145 196q-20 26 -20 63v400q0 75 100 75h61q123 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5zM200 400h-200v600h200 v-600zM700 275l-50 225h450v125l-250 375h-214l-136 -100h-100v-375l150 -212l100 -213h50v175z" />
+<glyph unicode="&#xe127;" d="M364 873l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM408 792v-503 l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83zM208 200h-200v600h200v-600z" />
+<glyph unicode="&#xe128;" d="M475 1104l365 -230q7 -4 16.5 -10.5t26 -26t16.5 -36.5v-526q0 -13 -85.5 -93.5t-93.5 -80.5h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-84 0 -139 39t-55 111t54 110t139 37h302l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6zM370 946 l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100h222q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l106 89v502l-342 237zM1199 201h-200v600h200v-600z" />
+<glyph unicode="&#xe129;" d="M1100 473v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90zM911 400h-503l-236 339 l83 86l183 -146q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6v7.5v7v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294zM1000 200v-200h-600v200h600z" />
+<glyph unicode="&#xe130;" d="M305 1104v200h600v-200h-600zM605 310l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15l-230 -362q-15 -31 7 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85l-1 -302q0 -84 38.5 -138t110.5 -54t111 55t39 139v106z M905 804v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146l-83 86l237 339h503z" />
+<glyph unicode="&#xe131;" d="M603 1195q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM598 701h-298v-201h300l-2 -194l402 294l-402 298v-197z" />
+<glyph unicode="&#xe132;" d="M597 1195q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5zM200 600l400 -294v194h302v201h-300v197z" />
+<glyph unicode="&#xe133;" d="M603 1195q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM300 600h200v-300h200v300h200l-300 400z" />
+<glyph unicode="&#xe134;" d="M603 1195q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM500 900v-300h-200l300 -400l300 400h-200v300h-200z" />
+<glyph unicode="&#xe135;" d="M603 1195q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM627 1101q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6 q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55 t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q102 -2 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7 q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5 t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 41 1 44q31 -13 58.5 -14.5t39.5 3.5l11 4q6 36 -17 53.5t-64 28.5t-56 23q-19 -3 -37 0zM613 994q0 -18 8 -42.5t16.5 -44t9.5 -23.5q-9 2 -31 5t-36 5t-32 8t-30 14q3 12 16 30t16 25q10 -10 18.5 -10 t14 6t14.5 14.5t16 12.5z" />
+<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " />
+<glyph unicode="&#xe138;" d="M1100 1200v-100h-1000v100h1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
+<glyph unicode="&#xe140;" d="M329 729l142 142l-200 200l129 129h-400v-400l129 129zM1200 1200v-400l-129 129l-200 -200l-142 142l200 200l-129 129h400zM271 129l129 -129h-400v400l129 -129l200 200l142 -142zM1071 271l129 129v-400h-400l129 129l-200 200l142 142z" />
+<glyph unicode="&#xe141;" d="M596 1192q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM596 1010q-171 0 -292.5 -121.5t-121.5 -292.5q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5zM455 905 q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5t16 38.5t39 16.5zM708 821l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5 q0 32 20.5 56.5t51.5 29.5zM855 709q23 0 38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39q0 22 16 38t39 16zM345 709q23 0 39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39t15.5 38.5t38.5 15.5z" />
+<glyph unicode="&#xe143;" d="M649 54l-16 22q-90 125 -293 323q-71 70 -104.5 105.5t-77 89.5t-61 99t-17.5 91q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-203 -198 -293 -323zM844 524l12 12 q64 62 97.5 97t64.5 79t31 72q0 71 -48 119t-105 48q-74 0 -132 -82l-118 -171l-114 174q-51 79 -123 79q-60 0 -109.5 -49t-49.5 -118q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203z" />
+<glyph unicode="&#xe144;" d="M476 406l19 -17l105 105l-212 212l389 389l247 -247l-95 -96l18 -18q46 -46 77 -99l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159q0 -93 66 -159zM123 193l141 -141q66 -66 159 -66q95 0 159 66 l283 283q66 66 66 159t-66 159l-141 141q-12 12 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159q0 -94 66 -160z" />
+<glyph unicode="&#xe145;" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM900 1000h-600v-700h600v700zM600 46q43 0 73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5t-73.5 -30.5t-30.5 -73.5 t30.5 -73.5t73.5 -30.5z" />
+<glyph unicode="&#xe148;" d="M700 1029v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5h139q5 -77 48.5 -126.5t117.5 -64.5v335l-27 7q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5 t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5zM600 755v274q-61 -8 -97.5 -37.5t-36.5 -102.5q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3zM700 548 v-311q170 18 170 151q0 64 -44 99.5t-126 60.5z" />
+<glyph unicode="&#xe149;" d="M866 300l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5t-30 142.5h-221v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5 t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -11 2.5 -24.5t5.5 -24t9.5 -26.5t10.5 -25t14 -27.5t14 -25.5t15.5 -27t13.5 -24h242v-100h-197q8 -50 -2.5 -115t-31.5 -94 q-41 -59 -99 -113q35 11 84 18t70 7q32 1 102 -16t104 -17q76 0 136 30z" />
+<glyph unicode="&#xe150;" d="M300 0l298 300h-198v900h-200v-900h-198zM900 1200l298 -300h-198v-900h-200v900h-198z" />
+<glyph unicode="&#xe151;" d="M400 300h198l-298 -300l-298 300h198v900h200v-900zM1000 1200v-500h-100v100h-100v-100h-100v500h300zM901 1100h-100v-200h100v200zM700 500h300v-200h-99v-100h-100v100h99v100h-200v100zM800 100h200v-100h-300v200h100v-100z" />
+<glyph unicode="&#xe152;" d="M400 300h198l-298 -300l-298 300h198v900h200v-900zM1000 1200v-200h-99v-100h-100v100h99v100h-200v100h300zM800 800h200v-100h-300v200h100v-100zM700 500h300v-500h-100v100h-100v-100h-100v500zM801 200h100v200h-100v-200z" />
+<glyph unicode="&#xe153;" d="M300 0l298 300h-198v900h-200v-900h-198zM900 1100h-100v100h200v-500h-100v400zM1100 500v-500h-100v100h-200v400h300zM1001 400h-100v-200h100v200z" />
+<glyph unicode="&#xe154;" d="M300 0l298 300h-198v900h-200v-900h-198zM1100 1200v-500h-100v100h-200v400h300zM1001 1100h-100v-200h100v200zM900 400h-100v100h200v-500h-100v400z" />
+<glyph unicode="&#xe155;" d="M300 0l298 300h-198v900h-200v-900h-198zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" />
+<glyph unicode="&#xe156;" d="M300 0l298 300h-198v900h-200v-900h-198zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" />
+<glyph unicode="&#xe157;" d="M400 1100h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5 t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5z" />
+<glyph unicode="&#xe158;" d="M700 0h-300q-163 0 -281.5 117.5t-118.5 282.5v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5 t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5zM400 800v-500l333 250z" />
+<glyph unicode="&#xe159;" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM900 300v500q0 41 -29.5 70.5t-70.5 29.5h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5 t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5zM800 700h-500l250 -333z" />
+<glyph unicode="&#xe160;" d="M1100 700v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5zM900 300v500q0 41 -29.5 70.5t-70.5 29.5h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5 t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5zM550 733l-250 -333h500z" />
+<glyph unicode="&#xe161;" d="M500 1100h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200zM700 550l-400 -350v200h-300v300h300v200z" />
+<glyph unicode="&#xe162;" d="M403 2l9 -1q13 0 26 16l538 630q15 19 6 36q-8 18 -32 16h-300q1 4 78 219.5t79 227.5q2 17 -6 27l-8 8h-9q-16 0 -25 -15q-4 -5 -98.5 -111.5t-228 -257t-209.5 -238.5q-17 -19 -7 -40q10 -19 32 -19h302q-155 -438 -160 -458q-5 -21 4 -32z" />
+<glyph unicode="&#xe163;" d="M800 200h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185zM900 200v200h-300v300h300v200l400 -350z" />
+<glyph unicode="&#xe164;" d="M1200 700l-149 149l-342 -353l-213 213l353 342l-149 149h500v-500zM1022 571l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5v-300 q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98z" />
+<glyph unicode="&#xe165;" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM600 794 q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" />
+<glyph unicode="&#xe166;" d="M700 800v400h-300v-400h-300l445 -500l450 500h-295zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" />
+<glyph unicode="&#xe167;" d="M400 700v-300h300v300h295l-445 500l-450 -500h300zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" />
+<glyph unicode="&#xe168;" d="M405 400l596 596l-154 155l-442 -442l-150 151l-155 -155zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" />
+<glyph unicode="&#xe169;" d="M409 1103l-97 97l-212 -212l97 -98zM650 861l-149 149l-212 -212l149 -149l-238 -248h700v699zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" />
+<glyph unicode="&#xe170;" d="M539 950l-149 -149l212 -212l149 148l248 -237v700h-699zM297 709l-97 -97l212 -212l98 97zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" />
+<glyph unicode="&#xe171;" d="M1200 1199v-1079l-475 272l-310 -393v416h-392zM1166 1148l-672 -712v-226z" />
+<glyph unicode="&#xe172;" d="M1100 1000v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1200h-100v-200h100v200z" />
+<glyph unicode="&#xe173;" d="M578 500h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120zM700 1200h-100v-200h100v200zM1300 538l-475 -476l-244 244l123 123l120 -120l353 352z" />
+<glyph unicode="&#xe174;" d="M529 500h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170zM700 1200h-100v-200h100v200zM1167 6l-170 170l-170 -170l-127 127l170 170l-170 170l127 127l170 -170l170 170l127 -128 l-170 -169l170 -170z" />
+<glyph unicode="&#xe175;" d="M700 500h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200zM700 1000h-100v200h100v-200zM1000 600h-200v-300h-200l300 -300l300 300h-200v300z" />
+<glyph unicode="&#xe176;" d="M602 500h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200zM700 1000h-100v200h100v-200zM1000 300h200l-300 300l-300 -300h200v-300h200v300z" />
+<glyph unicode="&#xe177;" d="M1200 900v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h1200zM0 800v-550q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200zM100 500h400v-200h-400v200z" />
+<glyph unicode="&#xe178;" d="M500 1000h400v198l300 -298l-300 -298v198h-400v200zM100 800v200h100v-200h-100zM400 800h-100v200h100v-200zM700 300h-400v-198l-300 298l300 298v-198h400v-200zM800 500h100v-200h-100v200zM1000 500v-200h100v200h-100z" />
+<glyph unicode="&#xe179;" d="M1200 50v1106q0 31 -18 40.5t-44 -7.5l-276 -117q-25 -16 -43.5 -50.5t-18.5 -65.5v-359q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5zM550 1200l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447l-100 203v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300z" />
+<glyph unicode="&#xe180;" d="M1100 106v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394 q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5z" />
+<glyph unicode="&#xe181;" d="M675 1000l-100 100h-375l-100 -100h400l200 -200v-98l295 98h105v200h-425zM500 300v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5zM100 800h300v-200h-300v200zM700 565l400 133 v-163l-400 -133v163zM100 500h300v-200h-300v200zM805 300l295 98v-298h-425l-100 -100h-375l-100 100h400l200 200h105z" />
+<glyph unicode="&#xe182;" d="M179 1169l-162 -162q-1 -11 -0.5 -32.5t16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q16 17 13 40.5t-22 37.5l-192 136q-19 14 -45 12t-42 -19l-119 -118q-143 103 -267 227q-126 126 -227 268l118 118 q17 17 20 41.5t-11 44.5l-139 194q-14 19 -36.5 22t-40.5 -14z" />
+<glyph unicode="&#xe183;" d="M1200 712v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40t-53.5 -36.5t-31 -27.5l-9 -10v-200q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38 t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5zM800 650l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -15 -35.5t-35 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5 t30 -27.5t12 -24l1 -10v-50z" />
+<glyph unicode="&#xe184;" d="M175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250zM1200 100v-100h-1100v100h1100z" />
+<glyph unicode="&#xe185;" d="M600 1100h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300v1000q0 41 29.5 70.5t70.5 29.5zM1000 800h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300v700q0 41 29.5 70.5t70.5 29.5zM400 0v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400h300z" />
+<glyph unicode="&#xe186;" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM200 800v-300h200v-100h-200v-100h300v300h-200v100h200v100h-300zM800 800h-200v-500h200v100h100v300h-100 v100zM800 700v-300h-100v300h100z" />
+<glyph unicode="&#xe187;" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM400 600h-100v200h-100v-500h100v200h100v-200h100v500h-100v-200zM800 800h-200v-500h200v100h100v300h-100 v100zM800 700v-300h-100v300h100z" />
+<glyph unicode="&#xe188;" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM200 800v-500h300v100h-200v300h200v100h-300zM600 800v-500h300v100h-200v300h200v100h-300z" />
+<glyph unicode="&#xe189;" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM500 700l-300 -150l300 -150v300zM600 400l300 150l-300 150v-300z" />
+<glyph unicode="&#xe190;" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM900 800v-500h-700v500h700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM800 700h-130 q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300z" />
+<glyph unicode="&#xe191;" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM200 800v-300h200v-100h-200v-100h300v300h-200v100h200v100h-300zM800 300h100v500h-200v-100h100v-400z M601 300h100v100h-100v-100z" />
+<glyph unicode="&#xe192;" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM300 700v100h-100v-500h300v400h-200zM800 300h100v500h-200v-100h100v-400zM401 400h-100v200h100v-200z M601 300h100v100h-100v-100z" />
+<glyph unicode="&#xe193;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM1000 900h-900v-700h900v700zM400 700h-200v100h300v-300h-99v-100h-100v100h99v200zM800 700h-100v100h200v-500h-100v400zM201 400h100v-100 h-100v100zM701 300h-100v100h100v-100z" />
+<glyph unicode="&#xe194;" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM800 700h-300 v-200h300v-100h-300l-100 100v200l100 100h300v-100z" />
+<glyph unicode="&#xe195;" d="M596 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM596 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM800 700v-100 h-100v100h-200v-100h200v-100h-200v-100h-100v400h300zM800 400h-100v100h100v-100z" />
+<glyph unicode="&#xe197;" d="M800 300h128q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5q0 -80 56.5 -137t135.5 -57h222v300h400v-300zM700 200h200l-300 -300 l-300 300h200v300h200v-300z" />
+<glyph unicode="&#xe198;" d="M600 714l403 -403q94 26 154.5 104t60.5 178q0 121 -85 207.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5q0 -80 56.5 -137t135.5 -57h8zM700 -100h-200v300h-200l300 300 l300 -300h-200v-300z" />
+<glyph unicode="&#xe199;" d="M700 200h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-155l-75 -45h350l-75 45v155z" />
+<glyph unicode="&#xe200;" d="M700 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -12t1 -11q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5 q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350z" />
+<glyph unicode="&#x1f4bc;" d="M800 1000h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100zM500 1000h200v100h-200v-100zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" />
+<glyph unicode="&#x1f4c5;" d="M1100 900v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150h1100zM0 800v-750q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100zM100 600h100v-100h-100v100zM300 600h100v-100h-100v100z M500 600h100v-100h-100v100zM700 600h100v-100h-100v100zM900 600h100v-100h-100v100zM100 400h100v-100h-100v100zM300 400h100v-100h-100v100zM500 400h100v-100h-100v100zM700 400h100v-100h-100v100zM900 400h100v-100h-100v100zM100 200h100v-100h-100v100zM300 200 h100v-100h-100v100zM500 200h100v-100h-100v100zM700 200h100v-100h-100v100zM900 200h100v-100h-100v100z" />
+<glyph unicode="&#x1f4cc;" d="M902 1185l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207l-380 -303l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15z" />
+<glyph unicode="&#x1f4ce;" d="M518 119l69 -60l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163t35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84 t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -79.5 -17t-67.5 -51l-388 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348 q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256z" />
+<glyph unicode="&#x1f4f7;" d="M1200 200v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5z M1000 700h-100v100h100v-100zM844 500q0 -100 -72 -172t-172 -72t-172 72t-72 172t72 172t172 72t172 -72t72 -172zM706 500q0 44 -31 75t-75 31t-75 -31t-31 -75t31 -75t75 -31t75 31t31 75z" />
+<glyph unicode="&#x1f512;" d="M900 800h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
+<glyph unicode="&#x1f514;" d="M1062 400h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-22 -9 -63 -23t-167.5 -37t-251.5 -23t-245.5 20.5t-178.5 41.5l-58 20q-18 7 -31 27.5t-13 40.5q0 21 13.5 35.5t33.5 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94 q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327zM600 104q-54 0 -103 6q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6z" />
+<glyph unicode="&#x1f516;" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
+<glyph unicode="&#x1f525;" d="M400 755q2 -12 8 -41.5t8 -43t6 -39.5t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85t5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5 q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129 q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5z" />
+<glyph unicode="&#x1f527;" d="M948 778l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138z" />
+</font>
+</defs></svg> 
\ No newline at end of file
diff --git a/docs/system-architecture/css/fonts/glyphicons-halflings-regular.ttf b/docs/system-architecture/css/fonts/glyphicons-halflings-regular.ttf
new file mode 100644
index 0000000..be784dc
--- /dev/null
+++ b/docs/system-architecture/css/fonts/glyphicons-halflings-regular.ttf
Binary files differ
diff --git a/docs/system-architecture/css/fonts/glyphicons-halflings-regular.woff b/docs/system-architecture/css/fonts/glyphicons-halflings-regular.woff
new file mode 100644
index 0000000..2cc3e48
--- /dev/null
+++ b/docs/system-architecture/css/fonts/glyphicons-halflings-regular.woff
Binary files differ
diff --git a/docs/system-architecture/css/main.css b/docs/system-architecture/css/main.css
new file mode 100644
index 0000000..00a3e30
--- /dev/null
+++ b/docs/system-architecture/css/main.css
@@ -0,0 +1,300 @@
+/*
+ * HTML5 Boilerplate
+ *
+ * What follows is the result of much research on cross-browser styling.
+ * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
+ * Kroc Camen, and the H5BP dev community and team.
+ */
+
+/* ==========================================================================
+   Base styles: opinionated defaults
+   ========================================================================== */
+
+html,
+button,
+input,
+select,
+textarea {
+    color: #222;
+}
+
+body {
+    font-size: 1em;
+    line-height: 1.4;
+}
+
+/*
+ * Remove text-shadow in selection highlight: h5bp.com/i
+ * These selection rule sets have to be separate.
+ * Customize the background color to match your design.
+ */
+
+::-moz-selection {
+    background: #b3d4fc;
+    text-shadow: none;
+}
+
+::selection {
+    background: #b3d4fc;
+    text-shadow: none;
+}
+
+/*
+ * A better looking default horizontal rule
+ */
+
+hr {
+    display: block;
+    height: 1px;
+    border: 0;
+    border-top: 1px solid #ccc;
+    margin: 1em 0;
+    padding: 0;
+}
+
+/*
+ * Remove the gap between images and the bottom of their containers: h5bp.com/i/440
+ */
+
+img {
+    vertical-align: middle;
+}
+
+/*
+ * Remove default fieldset styles.
+ */
+
+fieldset {
+    border: 0;
+    margin: 0;
+    padding: 0;
+}
+
+/*
+ * Allow only vertical resizing of textareas.
+ */
+
+textarea {
+    resize: vertical;
+}
+
+/* ==========================================================================
+   Chrome Frame prompt
+   ========================================================================== */
+
+.chromeframe {
+    margin: 0.2em 0;
+    background: #ccc;
+    color: #000;
+    padding: 0.2em 0;
+}
+
+/* ==========================================================================
+   Author's custom styles
+   ========================================================================== */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* ==========================================================================
+   Helper classes
+   ========================================================================== */
+
+/*
+ * Image replacement
+ */
+
+.ir {
+    background-color: transparent;
+    border: 0;
+    overflow: hidden;
+    /* IE 6/7 fallback */
+    *text-indent: -9999px;
+}
+
+.ir:before {
+    content: "";
+    display: block;
+    width: 0;
+    height: 150%;
+}
+
+/*
+ * Hide from both screenreaders and browsers: h5bp.com/u
+ */
+
+.hidden {
+    display: none !important;
+    visibility: hidden;
+}
+
+/*
+ * Hide only visually, but have it available for screenreaders: h5bp.com/v
+ */
+
+.visuallyhidden {
+    border: 0;
+    clip: rect(0 0 0 0);
+    height: 1px;
+    margin: -1px;
+    overflow: hidden;
+    padding: 0;
+    position: absolute;
+    width: 1px;
+}
+
+/*
+ * Extends the .visuallyhidden class to allow the element to be focusable
+ * when navigated to via the keyboard: h5bp.com/p
+ */
+
+.visuallyhidden.focusable:active,
+.visuallyhidden.focusable:focus {
+    clip: auto;
+    height: auto;
+    margin: 0;
+    overflow: visible;
+    position: static;
+    width: auto;
+}
+
+/*
+ * Hide visually and from screenreaders, but maintain layout
+ */
+
+.invisible {
+    visibility: hidden;
+}
+
+/*
+ * Clearfix: contain floats
+ *
+ * For modern browsers
+ * 1. The space content is one way to avoid an Opera bug when the
+ *    `contenteditable` attribute is included anywhere else in the document.
+ *    Otherwise it causes space to appear at the top and bottom of elements
+ *    that receive the `clearfix` class.
+ * 2. The use of `table` rather than `block` is only necessary if using
+ *    `:before` to contain the top-margins of child elements.
+ */
+
+.clearfix:before,
+.clearfix:after {
+    content: " "; /* 1 */
+    display: table; /* 2 */
+}
+
+.clearfix:after {
+    clear: both;
+}
+
+/*
+ * For IE 6/7 only
+ * Include this rule to trigger hasLayout and contain floats.
+ */
+
+.clearfix {
+    *zoom: 1;
+}
+
+/* ==========================================================================
+   EXAMPLE Media Queries for Responsive Design.
+   These examples override the primary ('mobile first') styles.
+   Modify as content requires.
+   ========================================================================== */
+
+@media only screen and (min-width: 35em) {
+    /* Style adjustments for viewports that meet the condition */
+}
+
+@media print,
+       (-o-min-device-pixel-ratio: 5/4),
+       (-webkit-min-device-pixel-ratio: 1.25),
+       (min-resolution: 120dpi) {
+    /* Style adjustments for high resolution devices */
+}
+
+/* ==========================================================================
+   Print styles.
+   Inlined to avoid required HTTP connection: h5bp.com/r
+   ========================================================================== */
+
+@media print {
+    * {
+        background: transparent !important;
+        color: #000 !important; /* Black prints faster: h5bp.com/s */
+        box-shadow: none !important;
+        text-shadow: none !important;
+    }
+
+    a,
+    a:visited {
+        text-decoration: underline;
+    }
+
+    a[href]:after {
+        content: " (" attr(href) ")";
+    }
+
+    abbr[title]:after {
+        content: " (" attr(title) ")";
+    }
+
+    /*
+     * Don't show links for images, or javascript/internal links
+     */
+
+    .ir a:after,
+    a[href^="javascript:"]:after,
+    a[href^="#"]:after {
+        content: "";
+    }
+
+    pre,
+    blockquote {
+        border: 1px solid #999;
+        page-break-inside: avoid;
+    }
+
+    thead {
+        display: table-header-group; /* h5bp.com/t */
+    }
+
+    tr,
+    img {
+        page-break-inside: avoid;
+    }
+
+    img {
+        max-width: 100% !important;
+    }
+
+    @page {
+        margin: 0.5cm;
+    }
+
+    p,
+    h2,
+    h3 {
+        orphans: 3;
+        widows: 3;
+    }
+
+    h2,
+    h3 {
+        page-break-after: avoid;
+    }
+}
diff --git a/docs/system-architecture/css/normalize.css b/docs/system-architecture/css/normalize.css
new file mode 100644
index 0000000..8d57e3c
--- /dev/null
+++ b/docs/system-architecture/css/normalize.css
@@ -0,0 +1,533 @@
+/*! normalize.css v1.1.1 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+   HTML5 display definitions
+   ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+    display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+audio,
+canvas,
+video {
+    display: inline-block;
+    *display: inline;
+    *zoom: 1;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+    display: none;
+    height: 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
+ * Known issue: no IE 6 support.
+ */
+
+[hidden] {
+    display: none;
+}
+
+/* ==========================================================================
+   Base
+   ========================================================================== */
+
+/**
+ * 1. Prevent system color scheme's background color being used in Firefox, IE,
+ *    and Opera.
+ * 2. Prevent system color scheme's text color being used in Firefox, IE, and
+ *    Opera.
+ * 3. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
+ *    `em` units.
+ * 4. Prevent iOS text size adjust after orientation change, without disabling
+ *    user zoom.
+ */
+
+html {
+    background: #fff; /* 1 */
+    color: #000; /* 2 */
+    font-size: 100%; /* 3 */
+    -webkit-text-size-adjust: 100%; /* 4 */
+    -ms-text-size-adjust: 100%; /* 4 */
+}
+
+/**
+ * Address `font-family` inconsistency between `textarea` and other form
+ * elements.
+ */
+
+html,
+button,
+input,
+select,
+textarea {
+    font-family: sans-serif;
+}
+
+/**
+ * Address margins handled incorrectly in IE 6/7.
+ */
+
+body {
+    margin: 0;
+}
+
+/* ==========================================================================
+   Links
+   ========================================================================== */
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+    outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+    outline: 0;
+}
+
+/* ==========================================================================
+   Typography
+   ========================================================================== */
+
+/**
+ * Address font sizes and margins set differently in IE 6/7.
+ * Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
+ * and Chrome.
+ */
+
+h1 {
+    font-size: 2em;
+    margin: 0.67em 0;
+}
+
+h2 {
+    font-size: 1.5em;
+    margin: 0.83em 0;
+}
+
+h3 {
+    font-size: 1.17em;
+    margin: 1em 0;
+}
+
+h4 {
+    font-size: 1em;
+    margin: 1.33em 0;
+}
+
+h5 {
+    font-size: 0.83em;
+    margin: 1.67em 0;
+}
+
+h6 {
+    font-size: 0.67em;
+    margin: 2.33em 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+    border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
+ */
+
+b,
+strong {
+    font-weight: bold;
+}
+
+blockquote {
+    margin: 1em 40px;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+    font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ * Known issue: no IE 6/7 normalization.
+ */
+
+hr {
+    -moz-box-sizing: content-box;
+    box-sizing: content-box;
+    height: 0;
+}
+
+/**
+ * Address styling not present in IE 6/7/8/9.
+ */
+
+mark {
+    background: #ff0;
+    color: #000;
+}
+
+/**
+ * Address margins set differently in IE 6/7.
+ */
+
+p,
+pre {
+    margin: 1em 0;
+}
+
+/**
+ * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+    font-family: monospace, serif;
+    _font-family: 'courier new', monospace;
+    font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+    white-space: pre;
+    white-space: pre-wrap;
+    word-wrap: break-word;
+}
+
+/**
+ * Address CSS quotes not supported in IE 6/7.
+ */
+
+q {
+    quotes: none;
+}
+
+/**
+ * Address `quotes` property not supported in Safari 4.
+ */
+
+q:before,
+q:after {
+    content: '';
+    content: none;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+    font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+    font-size: 75%;
+    line-height: 0;
+    position: relative;
+    vertical-align: baseline;
+}
+
+sup {
+    top: -0.5em;
+}
+
+sub {
+    bottom: -0.25em;
+}
+
+/* ==========================================================================
+   Lists
+   ========================================================================== */
+
+/**
+ * Address margins set differently in IE 6/7.
+ */
+
+dl,
+menu,
+ol,
+ul {
+    margin: 1em 0;
+}
+
+dd {
+    margin: 0 0 0 40px;
+}
+
+/**
+ * Address paddings set differently in IE 6/7.
+ */
+
+menu,
+ol,
+ul {
+    padding: 0 0 0 40px;
+}
+
+/**
+ * Correct list images handled incorrectly in IE 7.
+ */
+
+nav ul,
+nav ol {
+    list-style: none;
+    list-style-image: none;
+}
+
+/* ==========================================================================
+   Embedded content
+   ========================================================================== */
+
+/**
+ * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
+ * 2. Improve image quality when scaled in IE 7.
+ */
+
+img {
+    border: 0; /* 1 */
+    -ms-interpolation-mode: bicubic; /* 2 */
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+    overflow: hidden;
+}
+
+/* ==========================================================================
+   Figures
+   ========================================================================== */
+
+/**
+ * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
+ */
+
+figure {
+    margin: 0;
+}
+
+/* ==========================================================================
+   Forms
+   ========================================================================== */
+
+/**
+ * Correct margin displayed oddly in IE 6/7.
+ */
+
+form {
+    margin: 0;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+    border: 1px solid #c0c0c0;
+    margin: 0 2px;
+    padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct color not being inherited in IE 6/7/8/9.
+ * 2. Correct text not wrapping in Firefox 3.
+ * 3. Correct alignment displayed oddly in IE 6/7.
+ */
+
+legend {
+    border: 0; /* 1 */
+    padding: 0;
+    white-space: normal; /* 2 */
+    *margin-left: -7px; /* 3 */
+}
+
+/**
+ * 1. Correct font size not being inherited in all browsers.
+ * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
+ *    and Chrome.
+ * 3. Improve appearance and consistency in all browsers.
+ */
+
+button,
+input,
+select,
+textarea {
+    font-size: 100%; /* 1 */
+    margin: 0; /* 2 */
+    vertical-align: baseline; /* 3 */
+    *vertical-align: middle; /* 3 */
+}
+
+/**
+ * Address Firefox 3+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+    line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+    text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ *    and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ *    `input` and others.
+ * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
+ *    Known issue: inner spacing remains in IE 6.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+    -webkit-appearance: button; /* 2 */
+    cursor: pointer; /* 3 */
+    *overflow: visible;  /* 4 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+    cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to content-box in IE 8/9.
+ * 2. Remove excess padding in IE 8/9.
+ * 3. Remove excess padding in IE 7.
+ *    Known issue: excess padding remains in IE 6.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+    box-sizing: border-box; /* 1 */
+    padding: 0; /* 2 */
+    *height: 13px; /* 3 */
+    *width: 13px; /* 3 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ *    (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+    -webkit-appearance: textfield; /* 1 */
+    -moz-box-sizing: content-box;
+    -webkit-box-sizing: content-box; /* 2 */
+    box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+    -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 3+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+    border: 0;
+    padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 6/7/8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+    overflow: auto; /* 1 */
+    vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+   Tables
+   ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+    border-collapse: collapse;
+    border-spacing: 0;
+}
diff --git a/docs/system-architecture/css/toc-0.1.2.zip b/docs/system-architecture/css/toc-0.1.2.zip
new file mode 100644
index 0000000..ea1d84e
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2.zip
Binary files differ
diff --git a/docs/system-architecture/css/toc-0.1.2/.gitignore b/docs/system-architecture/css/toc-0.1.2/.gitignore
new file mode 100644
index 0000000..70e542c
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/.gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+node_modules
+components
+reports
diff --git a/docs/system-architecture/css/toc-0.1.2/.gitmodules b/docs/system-architecture/css/toc-0.1.2/.gitmodules
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/.gitmodules
diff --git a/docs/system-architecture/css/toc-0.1.2/Gruntfile.js b/docs/system-architecture/css/toc-0.1.2/Gruntfile.js
new file mode 100644
index 0000000..8680b37
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/Gruntfile.js
@@ -0,0 +1,100 @@
+module.exports = function(grunt) {
+  grunt.initConfig({
+    info: grunt.file.readJSON('component.json'),
+    meta: {
+      banner: '/*!\n'+
+              ' * <%= info.name %> - <%= info.description %>\n'+
+              ' * v<%= info.version %>\n'+
+              ' * <%= info.homepage %>\n'+
+              ' * copyright <%= info.copyright %> <%= grunt.template.today("yyyy") %>\n'+
+              ' * <%= info.license %> License\n'+
+              '*/\n'
+    },
+    jshint: {
+      main: [
+        'grunt.js', 
+        'component.json',
+        'lib/**/*.js',
+        'test/*.js'
+      ]
+    },
+    concat: {
+      options: {
+        banner: '<%= meta.banner %>'
+      },
+      dist: {
+        src: 'lib/toc.js',
+        dest: 'dist/jquery.toc.js'
+      }
+    },
+    uglify: {
+      options: {
+        banner: '<%= meta.banner %>'
+      },
+      dist: {
+        src: 'dist/jquery.toc.js',
+        dest: 'dist/jquery.toc.min.js'
+      }
+    },
+    watch: {
+      main: {
+        files: '<%= jshint.main %>',
+        tasks: 'default' 
+      },
+      ci: {
+        files: [
+          '<%= jshint.main %>',
+          'test/index.html'
+        ],
+        tasks: ['default', 'mocha']
+      }
+    },
+    mocha: {
+      all: {
+        src: 'test/index.html',
+        options: {
+          run: true
+        }
+      }
+    },
+    plato: {
+      main: {
+        files: {
+          'reports': ['lib/*.js']
+        }
+      }
+    },
+    reloadr: {
+      test: [
+        'example/*',
+        'test/*',
+        'dist/*'
+      ]
+    },
+    connect: {
+      server:{
+        port: 8000,
+        base: '.'
+      },
+      plato: {
+        port: 8000,
+        base: 'reports',
+        options: {
+          keepalive: true
+        }
+      }
+    }
+  });
+  grunt.loadNpmTasks('grunt-contrib-jshint');
+  grunt.loadNpmTasks('grunt-contrib-concat');
+  grunt.loadNpmTasks('grunt-contrib-watch');
+  grunt.loadNpmTasks('grunt-contrib-uglify');
+  grunt.loadNpmTasks('grunt-contrib-connect');
+  grunt.loadNpmTasks('grunt-mocha');
+  grunt.loadNpmTasks('grunt-reloadr');
+  grunt.loadNpmTasks('grunt-plato');
+  grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
+  grunt.registerTask('dev', ['connect:server', 'reloadr', 'watch:main']);
+  grunt.registerTask('ci', ['connect:server', 'watch:ci']);
+  grunt.registerTask('reports', ['plato', 'connect:plato']);
+};
diff --git a/docs/system-architecture/css/toc-0.1.2/History.md b/docs/system-architecture/css/toc-0.1.2/History.md
new file mode 100644
index 0000000..1ff3bae
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/History.md
@@ -0,0 +1,46 @@
+
+0.1.2 / 2013-04-08 
+==================
+
+  * optimized scroll function.  fixes #15
+  * migrated to grunt for building
+  * fixed tabs on docs/index
+
+0.1.1 / 2012-10-05 
+==================
+
+  * updated example
+  * code cleanup, fixed onHighlight, removed console.log
+  * Add headerText and itemClass options
+
+0.1.0 / 2012-07-05 
+==================
+
+  * added smoosh to Makefile
+  * updated docs
+  * removed site submodule
+  * Added callback for when a new section is highlighted
+  * selected event
+  * adds another option to customize the header link text
+  * updated site
+
+0.0.3 / 2012-04-06 
+==================
+
+  * fixed smooth scrolling in firefox
+  * fixed copyright typo
+
+0.0.2 / 2012-03-09
+==================
+
+  * Issue [#2](https://github.com/jgallen23/toc/issues/2 "Issue #2")
+  * updated site
+  * 0.0.2 - updated docs
+
+0.0.1 / 2012-02-25 
+==================
+
+  * updated docs/site
+  * added readme
+  * got everything working
+  * initial commit
diff --git a/docs/system-architecture/css/toc-0.1.2/LICENSE b/docs/system-architecture/css/toc-0.1.2/LICENSE
new file mode 100644
index 0000000..ce5d8bc
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2013 Greg Allen
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/docs/system-architecture/css/toc-0.1.2/README.md b/docs/system-architecture/css/toc-0.1.2/README.md
new file mode 100644
index 0000000..176f0b9
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/README.md
@@ -0,0 +1,5 @@
+#TOC
+
+[TOC](http://projects.jga.me/toc/) is a jQuery plugin for automatically generating a table of contents. 
+
+For more information, check out the [documentation](http://projects.jga.me/toc/).
diff --git a/docs/system-architecture/css/toc-0.1.2/component.json b/docs/system-architecture/css/toc-0.1.2/component.json
new file mode 100644
index 0000000..b71fcd7
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/component.json
@@ -0,0 +1,14 @@
+{
+  "name": "toc",
+  "description": "jQuery Table of Contents Plugin",
+  "version": "0.1.2",
+  "homepage": "http://projects.jga.me/toc/",
+  "license": "MIT",
+  "copyright": "Greg Allen",
+  "main": "dist/toc.js",
+  "dependencies": {
+  },
+  "devDependencies": {
+    "assert": "*"
+  }
+}
diff --git a/docs/system-architecture/css/toc-0.1.2/dist/jquery.toc.js b/docs/system-architecture/css/toc-0.1.2/dist/jquery.toc.js
new file mode 100644
index 0000000..9fe5055
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/dist/jquery.toc.js
@@ -0,0 +1,107 @@
+/*!
+ * toc - jQuery Table of Contents Plugin
+ * v0.1.2
+ * http://projects.jga.me/toc/
+ * copyright Greg Allen 2013
+ * MIT License
+*/
+(function($) {
+$.fn.toc = function(options) {
+  var self = this;
+  var opts = $.extend({}, jQuery.fn.toc.defaults, options);
+
+  var container = $(opts.container);
+  var headings = $(opts.selectors, container);
+  var headingOffsets = [];
+  var activeClassName = opts.prefix+'-active';
+
+  var scrollTo = function(e) {
+    if (opts.smoothScrolling) {
+      e.preventDefault();
+      var elScrollTo = $(e.target).attr('href');
+      var $el = $(elScrollTo);
+
+      $('body,html').animate({ scrollTop: $el.offset().top }, 400, 'swing', function() {
+        location.hash = elScrollTo;
+      });
+    }
+    $('li', self).removeClass(activeClassName);
+    $(e.target).parent().addClass(activeClassName);
+  };
+
+  //highlight on scroll
+  var timeout;
+  var highlightOnScroll = function(e) {
+    if (timeout) {
+      clearTimeout(timeout);
+    }
+    timeout = setTimeout(function() {
+      var top = $(window).scrollTop(),
+        highlighted;
+      for (var i = 0, c = headingOffsets.length; i < c; i++) {
+        if (headingOffsets[i] >= top) {
+          $('li', self).removeClass(activeClassName);
+          highlighted = $('li:eq('+(i-1)+')', self).addClass(activeClassName);
+          opts.onHighlight(highlighted);
+          break;
+        }
+      }
+    }, 50);
+  };
+  if (opts.highlightOnScroll) {
+    $(window).bind('scroll', highlightOnScroll);
+    highlightOnScroll();
+  }
+
+  return this.each(function() {
+    //build TOC
+    var el = $(this);
+    var ul = $('<ul/>');
+    headings.each(function(i, heading) {
+      var $h = $(heading);
+      headingOffsets.push($h.offset().top - opts.highlightOffset);
+
+      //add anchor
+      var anchor = $('<span/>').attr('id', opts.anchorName(i, heading, opts.prefix)).insertBefore($h);
+
+      //build TOC item
+      var a = $('<a/>')
+        .text(opts.headerText(i, heading, $h))
+        .attr('href', '#' + opts.anchorName(i, heading, opts.prefix))
+        .bind('click', function(e) { 
+          scrollTo(e);
+          el.trigger('selected', $(this).attr('href'));
+        });
+
+      var li = $('<li/>')
+        .addClass(opts.itemClass(i, heading, $h, opts.prefix))
+        .append(a);
+
+      ul.append(li);
+    });
+    el.html(ul);
+  });
+};
+
+
+jQuery.fn.toc.defaults = {
+  container: 'body',
+  selectors: 'h1,h2,h3',
+  smoothScrolling: true,
+  prefix: 'toc',
+  onHighlight: function() {},
+  highlightOnScroll: true,
+  highlightOffset: 100,
+  anchorName: function(i, heading, prefix) {
+    return prefix+i;
+  },
+  headerText: function(i, heading, $heading) {
+    return $heading.text();
+  },
+  itemClass: function(i, heading, $heading, prefix) {
+    return prefix + '-' + $heading[0].tagName.toLowerCase();
+  }
+
+};
+
+})(jQuery);
diff --git a/docs/system-architecture/css/toc-0.1.2/dist/jquery.toc.min.js b/docs/system-architecture/css/toc-0.1.2/dist/jquery.toc.min.js
new file mode 100644
index 0000000..53a2c62
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/dist/jquery.toc.min.js
@@ -0,0 +1,8 @@
+/*!
+ * toc - jQuery Table of Contents Plugin
+ * v0.1.2
+ * http://projects.jga.me/toc/
+ * copyright Greg Allen 2013
+ * MIT License
+*/
+(function(t){t.fn.toc=function(e){var n,i=this,r=t.extend({},jQuery.fn.toc.defaults,e),o=t(r.container),a=t(r.selectors,o),l=[],h=r.prefix+"-active",s=function(e){if(r.smoothScrolling){e.preventDefault();var n=t(e.target).attr("href"),o=t(n);t("body,html").animate({scrollTop:o.offset().top},400,"swing",function(){location.hash=n})}t("li",i).removeClass(h),t(e.target).parent().addClass(h)},c=function(){n&&clearTimeout(n),n=setTimeout(function(){for(var e,n=t(window).scrollTop(),o=0,a=l.length;a>o;o++)if(l[o]>=n){t("li",i).removeClass(h),e=t("li:eq("+(o-1)+")",i).addClass(h),r.onHighlight(e);break}},50)};return r.highlightOnScroll&&(t(window).bind("scroll",c),c()),this.each(function(){var e=t(this),n=t("<ul/>");a.each(function(i,o){var a=t(o);l.push(a.offset().top-r.highlightOffset),t("<span/>").attr("id",r.anchorName(i,o,r.prefix)).insertBefore(a);var h=t("<a/>").text(r.headerText(i,o,a)).attr("href","#"+r.anchorName(i,o,r.prefix)).bind("click",function(n){s(n),e.trigger("selected",t(this).attr("href"))}),c=t("<li/>").addClass(r.itemClass(i,o,a,r.prefix)).append(h);n.append(c)}),e.html(n)})},jQuery.fn.toc.defaults={container:"body",selectors:"h1,h2,h3",smoothScrolling:!0,prefix:"toc",onHighlight:function(){},highlightOnScroll:!0,highlightOffset:100,anchorName:function(t,e,n){return n+t},headerText:function(t,e,n){return n.text()},itemClass:function(t,e,n,i){return i+"-"+n[0].tagName.toLowerCase()}}})(jQuery);
\ No newline at end of file
diff --git a/docs/system-architecture/css/toc-0.1.2/docs/index.md b/docs/system-architecture/css/toc-0.1.2/docs/index.md
new file mode 100644
index 0000000..06380d7
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/docs/index.md
@@ -0,0 +1,101 @@
+#TOC
+
+TOC is a jQuery plugin that will automatically generate a table of contents for your page. You can see an example of it on the left side of the page.
+
+##Features
+- Completely customizable
+- Click to smooth scroll to that spot on the page
+- Automatically highlight the current section
+- Extremely lightweight (744 bytes gzipped)
+- Can have multiple on a page
+
+##Download
+
+- [Production](https://raw.github.com/jgallen23/toc/master/dist/jquery.toc.min.js)
+- [Development](https://raw.github.com/jgallen23/toc/master/dist/jquery.toc.js)
+- [Source](http://github.com/jgallen23/toc)
+
+##Usage
+
+	`javascript
+	$('#toc').toc();
+
+###Options
+Defaults shown below
+
+	`javascript
+	$('#toc').toc({
+		'selectors': 'h1,h2,h3', //elements to use as headings
+		'container': 'body', //element to find all selectors in
+		'smoothScrolling': true, //enable or disable smooth scrolling on click
+		'prefix': 'toc', //prefix for anchor tags and class names
+		'onHighlight': function(el) {}, //called when a new section is highlighted 
+		'highlightOnScroll': true, //add class to heading that is currently in focus
+		'highlightOffset': 100, //offset to trigger the next headline
+		'anchorName': function(i, heading, prefix) { //custom function for anchor name
+			return prefix+i;
+		},
+		'headerText': function(i, heading, $heading) { //custom function building the header-item text
+			return $heading.text();
+		},
+		'itemClass': function(i, heading, $heading, prefix) { // custom function for item class
+			return $heading[0].tagName.toLowerCase();
+		}
+	});
+
+##Example CSS
+
+	`css
+	#toc {
+		top: 0px;
+		left: 0px;
+		height: 100%;
+		position: fixed;
+		background: #333;
+		box-shadow: inset -5px 0 5px 0px #000;
+		width: 150px;
+		padding-top: 20px;
+		color: #fff;
+	}
+
+	#toc ul {
+		margin: 0;
+		padding: 0;
+		list-style: none;
+	}
+
+	#toc li {
+		padding: 5px 10px;
+	}
+
+	#toc a {
+		color: #fff;
+		text-decoration: none;
+		display: block;
+	}
+
+	#toc .toc-h2 {
+		padding-left: 10px;
+	}
+
+	#toc .toc-h3 {
+		padding-left: 20px;
+	}
+
+	#toc .toc-active {
+		background: #336699;
+		box-shadow: inset -5px 0px 10px -5px #000;
+	}
+
+##History
+
+[View](https://raw.github.com/jgallen23/toc/master/History.md)
+
+##Future
+- Figure out how to handle headlines on bottom of page
+- Zepto.js support (should work, need to verify)
+- Ender support
+
+
+##Contributors
+- Greg Allen ([@jgaui](http://twitter.com/jgaui)) [jga.me](http://jga.me)
diff --git a/docs/system-architecture/css/toc-0.1.2/docs/jquery-deps.md b/docs/system-architecture/css/toc-0.1.2/docs/jquery-deps.md
new file mode 100644
index 0000000..cf9b01b
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/docs/jquery-deps.md
@@ -0,0 +1,10 @@
+#jQuery Dependencies
+- $.extend
+- bind
+- addClass
+- removeClass
+- animate
+- attr
+- text
+- append
+- createElement
diff --git a/docs/system-architecture/css/toc-0.1.2/example/index.html b/docs/system-architecture/css/toc-0.1.2/example/index.html
new file mode 100644
index 0000000..ff1b7cf
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/example/index.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title></title>
+    <script src="live.js"></script>
+    <style>
+      body {
+        font-family: Helvetica, Arial;
+      }
+      #wrapper { 
+        width: 980px;
+        margin: 0 auto;
+        margin-bottom: 300px;
+      }
+      .toc {
+        background: #fefefe;
+        width: 200px;
+        position: fixed;
+        border: 1px solid #ddd;
+        color: #333;
+      }
+      .toc a {
+        color: #333;
+      }
+      .toc .tocH2 {
+        margin-left: 10px
+      }
+      .toc .tocH3 {
+        margin-left: 20px
+      }
+      .toc-active {
+        color: #000;
+        font-weight: bold;
+      }
+      .toc.right {
+        right: 0
+      }
+    </style>
+  </head>
+  <body>
+    <div class="toc">
+    </div>
+    <div class="toc right">
+    </div>
+    <div id="wrapper">
+      <h1>Page Title</h1>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <h2>Sub Heading</h2>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <h2>Sub Heading</h2>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <h3>SubSub Heading</h3>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum fermentum ligula a augue sollicitudin a tincidunt felis tincidunt. Donec et urna augue, sed consectetur lacus. Maecenas tincidunt volutpat lorem. Suspendisse turpis tellus, sodales ac commodo id, rhoncus vel augue. Vestibulum nisl nibh, rutrum eu bibendum vitae, bibendum et libero. Suspendisse vel odio vitae leo commodo lacinia. Sed non lacinia nulla. Pellentesque faucibus euismod dictum. Suspendisse potenti.</p>
+    </div>
+    <script src="jquery.js"></script>
+    <script src="../lib/toc.js"></script>
+    <script>
+      $('.toc').toc({
+      });
+    </script>
+  </body>
+</html>
diff --git a/docs/system-architecture/css/toc-0.1.2/example/jquery.js b/docs/system-architecture/css/toc-0.1.2/example/jquery.js
new file mode 100644
index 0000000..8ccd0ea
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/example/jquery.js
@@ -0,0 +1,9266 @@
+/*!
+ * jQuery JavaScript Library v1.7.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Nov 21 21:11:03 2011 -0500
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+	navigator = window.navigator,
+	location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+		// The jQuery object is actually just the init constructor 'enhanced'
+		return new jQuery.fn.init( selector, context, rootjQuery );
+	},
+
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$,
+
+	// A central reference to the root jQuery(document)
+	rootjQuery,
+
+	// A simple way to check for HTML strings or ID strings
+	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+	quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+	// Check if a string has a non-whitespace character in it
+	rnotwhite = /\S/,
+
+	// Used for trimming whitespace
+	trimLeft = /^\s+/,
+	trimRight = /\s+$/,
+
+	// Match a standalone tag
+	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+	// JSON RegExp
+	rvalidchars = /^[\],:{}\s]*$/,
+	rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+	rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+	// Useragent RegExp
+	rwebkit = /(webkit)[ \/]([\w.]+)/,
+	ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+	rmsie = /(msie) ([\w.]+)/,
+	rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+	// Matches dashed string for camelizing
+	rdashAlpha = /-([a-z]|[0-9])/ig,
+	rmsPrefix = /^-ms-/,
+
+	// Used by jQuery.camelCase as callback to replace()
+	fcamelCase = function( all, letter ) {
+		return ( letter + "" ).toUpperCase();
+	},
+
+	// Keep a UserAgent string for use with jQuery.browser
+	userAgent = navigator.userAgent,
+
+	// For matching the engine and version of the browser
+	browserMatch,
+
+	// The deferred used on DOM ready
+	readyList,
+
+	// The ready event handler
+	DOMContentLoaded,
+
+	// Save a reference to some core methods
+	toString = Object.prototype.toString,
+	hasOwn = Object.prototype.hasOwnProperty,
+	push = Array.prototype.push,
+	slice = Array.prototype.slice,
+	trim = String.prototype.trim,
+	indexOf = Array.prototype.indexOf,
+
+	// [[Class]] -> type pairs
+	class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+	constructor: jQuery,
+	init: function( selector, context, rootjQuery ) {
+		var match, elem, ret, doc;
+
+		// Handle $(""), $(null), or $(undefined)
+		if ( !selector ) {
+			return this;
+		}
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this.context = this[0] = selector;
+			this.length = 1;
+			return this;
+		}
+
+		// The body element only exists once, optimize finding it
+		if ( selector === "body" && !context && document.body ) {
+			this.context = document;
+			this[0] = document.body;
+			this.selector = selector;
+			this.length = 1;
+			return this;
+		}
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			// Are we dealing with HTML string or an ID?
+			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+				// Assume that strings that start and end with <> are HTML and skip the regex check
+				match = [ null, selector, null ];
+
+			} else {
+				match = quickExpr.exec( selector );
+			}
+
+			// Verify a match, and that no context was specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] ) {
+					context = context instanceof jQuery ? context[0] : context;
+					doc = ( context ? context.ownerDocument || context : document );
+
+					// If a single string is passed in and it's a single tag
+					// just do a createElement and skip the rest
+					ret = rsingleTag.exec( selector );
+
+					if ( ret ) {
+						if ( jQuery.isPlainObject( context ) ) {
+							selector = [ document.createElement( ret[1] ) ];
+							jQuery.fn.attr.call( selector, context, true );
+
+						} else {
+							selector = [ doc.createElement( ret[1] ) ];
+						}
+
+					} else {
+						ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+						selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+					}
+
+					return jQuery.merge( this, selector );
+
+				// HANDLE: $("#id")
+				} else {
+					elem = document.getElementById( match[2] );
+
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document #6963
+					if ( elem && elem.parentNode ) {
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( elem.id !== match[2] ) {
+							return rootjQuery.find( selector );
+						}
+
+						// Otherwise, we inject the element directly into the jQuery object
+						this.length = 1;
+						this[0] = elem;
+					}
+
+					this.context = document;
+					this.selector = selector;
+					return this;
+				}
+
+			// HANDLE: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return ( context || rootjQuery ).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( context ).find( selector );
+			}
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) ) {
+			return rootjQuery.ready( selector );
+		}
+
+		if ( selector.selector !== undefined ) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return jQuery.makeArray( selector, this );
+	},
+
+	// Start with an empty selector
+	selector: "",
+
+	// The current version of jQuery being used
+	jquery: "1.7.1",
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+
+	toArray: function() {
+		return slice.call( this, 0 );
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == null ?
+
+			// Return a 'clean' array
+			this.toArray() :
+
+			// Return just the object
+			( num < 0 ? this[ this.length + num ] : this[ num ] );
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems, name, selector ) {
+		// Build a new jQuery matched element set
+		var ret = this.constructor();
+
+		if ( jQuery.isArray( elems ) ) {
+			push.apply( ret, elems );
+
+		} else {
+			jQuery.merge( ret, elems );
+		}
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		ret.context = this.context;
+
+		if ( name === "find" ) {
+			ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+		} else if ( name ) {
+			ret.selector = this.selector + "." + name + "(" + selector + ")";
+		}
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	ready: function( fn ) {
+		// Attach the listeners
+		jQuery.bindReady();
+
+		// Add the callback
+		readyList.add( fn );
+
+		return this;
+	},
+
+	eq: function( i ) {
+		i = +i;
+		return i === -1 ?
+			this.slice( i ) :
+			this.slice( i, i + 1 );
+	},
+
+	first: function() {
+		return this.eq( 0 );
+	},
+
+	last: function() {
+		return this.eq( -1 );
+	},
+
+	slice: function() {
+		return this.pushStack( slice.apply( this, arguments ),
+			"slice", slice.call(arguments).join(",") );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor(null);
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: push,
+	sort: [].sort,
+	splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+	var options, name, src, copy, copyIsArray, clone,
+		target = arguments[0] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+		target = {};
+	}
+
+	// extend jQuery itself if only one argument is passed
+	if ( length === i ) {
+		target = this;
+		--i;
+	}
+
+	for ( ; i < length; i++ ) {
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null ) {
+			// Extend the base object
+			for ( name in options ) {
+				src = target[ name ];
+				copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy ) {
+					continue;
+				}
+
+				// Recurse if we're merging plain objects or arrays
+				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+					if ( copyIsArray ) {
+						copyIsArray = false;
+						clone = src && jQuery.isArray(src) ? src : [];
+
+					} else {
+						clone = src && jQuery.isPlainObject(src) ? src : {};
+					}
+
+					// Never move original objects, clone them
+					target[ name ] = jQuery.extend( deep, clone, copy );
+
+				// Don't bring in undefined values
+				} else if ( copy !== undefined ) {
+					target[ name ] = copy;
+				}
+			}
+		}
+	}
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		if ( window.$ === jQuery ) {
+			window.$ = _$;
+		}
+
+		if ( deep && window.jQuery === jQuery ) {
+			window.jQuery = _jQuery;
+		}
+
+		return jQuery;
+	},
+
+	// Is the DOM ready to be used? Set to true once it occurs.
+	isReady: false,
+
+	// A counter to track how many items to wait for before
+	// the ready event fires. See #6781
+	readyWait: 1,
+
+	// Hold (or release) the ready event
+	holdReady: function( hold ) {
+		if ( hold ) {
+			jQuery.readyWait++;
+		} else {
+			jQuery.ready( true );
+		}
+	},
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+		// Either a released hold or an DOMready/load event and not yet ready
+		if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+			// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+			if ( !document.body ) {
+				return setTimeout( jQuery.ready, 1 );
+			}
+
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+
+			// If a normal DOM Ready event fired, decrement, and wait if need be
+			if ( wait !== true && --jQuery.readyWait > 0 ) {
+				return;
+			}
+
+			// If there are functions bound, to execute
+			readyList.fireWith( document, [ jQuery ] );
+
+			// Trigger any bound ready events
+			if ( jQuery.fn.trigger ) {
+				jQuery( document ).trigger( "ready" ).off( "ready" );
+			}
+		}
+	},
+
+	bindReady: function() {
+		if ( readyList ) {
+			return;
+		}
+
+		readyList = jQuery.Callbacks( "once memory" );
+
+		// Catch cases where $(document).ready() is called after the
+		// browser event has already occurred.
+		if ( document.readyState === "complete" ) {
+			// Handle it asynchronously to allow scripts the opportunity to delay ready
+			return setTimeout( jQuery.ready, 1 );
+		}
+
+		// Mozilla, Opera and webkit nightlies currently support this event
+		if ( document.addEventListener ) {
+			// Use the handy event callback
+			document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+			// A fallback to window.onload, that will always work
+			window.addEventListener( "load", jQuery.ready, false );
+
+		// If IE event model is used
+		} else if ( document.attachEvent ) {
+			// ensure firing before onload,
+			// maybe late but safe also for iframes
+			document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+			// A fallback to window.onload, that will always work
+			window.attachEvent( "onload", jQuery.ready );
+
+			// If IE and not a frame
+			// continually check to see if the document is ready
+			var toplevel = false;
+
+			try {
+				toplevel = window.frameElement == null;
+			} catch(e) {}
+
+			if ( document.documentElement.doScroll && toplevel ) {
+				doScrollCheck();
+			}
+		}
+	},
+
+	// See test/unit/core.js for details concerning isFunction.
+	// Since version 1.3, DOM methods and functions like alert
+	// aren't supported. They return false on IE (#2968).
+	isFunction: function( obj ) {
+		return jQuery.type(obj) === "function";
+	},
+
+	isArray: Array.isArray || function( obj ) {
+		return jQuery.type(obj) === "array";
+	},
+
+	// A crude way of determining if an object is a window
+	isWindow: function( obj ) {
+		return obj && typeof obj === "object" && "setInterval" in obj;
+	},
+
+	isNumeric: function( obj ) {
+		return !isNaN( parseFloat(obj) ) && isFinite( obj );
+	},
+
+	type: function( obj ) {
+		return obj == null ?
+			String( obj ) :
+			class2type[ toString.call(obj) ] || "object";
+	},
+
+	isPlainObject: function( obj ) {
+		// Must be an Object.
+		// Because of IE, we also have to check the presence of the constructor property.
+		// Make sure that DOM nodes and window objects don't pass through, as well
+		if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		try {
+			// Not own constructor property must be Object
+			if ( obj.constructor &&
+				!hasOwn.call(obj, "constructor") &&
+				!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+				return false;
+			}
+		} catch ( e ) {
+			// IE8,9 Will throw exceptions on certain host objects #9897
+			return false;
+		}
+
+		// Own properties are enumerated firstly, so to speed up,
+		// if last one is own, then all properties are own.
+
+		var key;
+		for ( key in obj ) {}
+
+		return key === undefined || hasOwn.call( obj, key );
+	},
+
+	isEmptyObject: function( obj ) {
+		for ( var name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	error: function( msg ) {
+		throw new Error( msg );
+	},
+
+	parseJSON: function( data ) {
+		if ( typeof data !== "string" || !data ) {
+			return null;
+		}
+
+		// Make sure leading/trailing whitespace is removed (IE can't handle it)
+		data = jQuery.trim( data );
+
+		// Attempt to parse using the native JSON parser first
+		if ( window.JSON && window.JSON.parse ) {
+			return window.JSON.parse( data );
+		}
+
+		// Make sure the incoming data is actual JSON
+		// Logic borrowed from http://json.org/json2.js
+		if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+			.replace( rvalidtokens, "]" )
+			.replace( rvalidbraces, "")) ) {
+
+			return ( new Function( "return " + data ) )();
+
+		}
+		jQuery.error( "Invalid JSON: " + data );
+	},
+
+	// Cross-browser xml parsing
+	parseXML: function( data ) {
+		var xml, tmp;
+		try {
+			if ( window.DOMParser ) { // Standard
+				tmp = new DOMParser();
+				xml = tmp.parseFromString( data , "text/xml" );
+			} else { // IE
+				xml = new ActiveXObject( "Microsoft.XMLDOM" );
+				xml.async = "false";
+				xml.loadXML( data );
+			}
+		} catch( e ) {
+			xml = undefined;
+		}
+		if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+		return xml;
+	},
+
+	noop: function() {},
+
+	// Evaluates a script in a global context
+	// Workarounds based on findings by Jim Driscoll
+	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+	globalEval: function( data ) {
+		if ( data && rnotwhite.test( data ) ) {
+			// We use execScript on Internet Explorer
+			// We use an anonymous function so that context is window
+			// rather than jQuery in Firefox
+			( window.execScript || function( data ) {
+				window[ "eval" ].call( window, data );
+			} )( data );
+		}
+	},
+
+	// Convert dashed to camelCase; used by the css and data modules
+	// Microsoft forgot to hump their vendor prefix (#9572)
+	camelCase: function( string ) {
+		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+	},
+
+	// args is for internal usage only
+	each: function( object, callback, args ) {
+		var name, i = 0,
+			length = object.length,
+			isObj = length === undefined || jQuery.isFunction( object );
+
+		if ( args ) {
+			if ( isObj ) {
+				for ( name in object ) {
+					if ( callback.apply( object[ name ], args ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( ; i < length; ) {
+					if ( callback.apply( object[ i++ ], args ) === false ) {
+						break;
+					}
+				}
+			}
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( isObj ) {
+				for ( name in object ) {
+					if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( ; i < length; ) {
+					if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+						break;
+					}
+				}
+			}
+		}
+
+		return object;
+	},
+
+	// Use native String.trim function wherever possible
+	trim: trim ?
+		function( text ) {
+			return text == null ?
+				"" :
+				trim.call( text );
+		} :
+
+		// Otherwise use our own trimming functionality
+		function( text ) {
+			return text == null ?
+				"" :
+				text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+		},
+
+	// results is for internal usage only
+	makeArray: function( array, results ) {
+		var ret = results || [];
+
+		if ( array != null ) {
+			// The window, strings (and functions) also have 'length'
+			// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+			var type = jQuery.type( array );
+
+			if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+				push.call( ret, array );
+			} else {
+				jQuery.merge( ret, array );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, array, i ) {
+		var len;
+
+		if ( array ) {
+			if ( indexOf ) {
+				return indexOf.call( array, elem, i );
+			}
+
+			len = array.length;
+			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+			for ( ; i < len; i++ ) {
+				// Skip accessing in sparse arrays
+				if ( i in array && array[ i ] === elem ) {
+					return i;
+				}
+			}
+		}
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		var i = first.length,
+			j = 0;
+
+		if ( typeof second.length === "number" ) {
+			for ( var l = second.length; j < l; j++ ) {
+				first[ i++ ] = second[ j ];
+			}
+
+		} else {
+			while ( second[j] !== undefined ) {
+				first[ i++ ] = second[ j++ ];
+			}
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var ret = [], retVal;
+		inv = !!inv;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			retVal = !!callback( elems[ i ], i );
+			if ( inv !== retVal ) {
+				ret.push( elems[ i ] );
+			}
+		}
+
+		return ret;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var value, key, ret = [],
+			i = 0,
+			length = elems.length,
+			// jquery objects are treated as arrays
+			isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+		// Go through the array, translating each of the items to their
+		if ( isArray ) {
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( key in elems ) {
+				value = callback( elems[ key ], key, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return ret.concat.apply( [], ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	// Bind a function to a context, optionally partially applying any
+	// arguments.
+	proxy: function( fn, context ) {
+		if ( typeof context === "string" ) {
+			var tmp = fn[ context ];
+			context = fn;
+			fn = tmp;
+		}
+
+		// Quick check to determine if target is callable, in the spec
+		// this throws a TypeError, but we will just return undefined.
+		if ( !jQuery.isFunction( fn ) ) {
+			return undefined;
+		}
+
+		// Simulated bind
+		var args = slice.call( arguments, 2 ),
+			proxy = function() {
+				return fn.apply( context, args.concat( slice.call( arguments ) ) );
+			};
+
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+		return proxy;
+	},
+
+	// Mutifunctional method to get and set values to a collection
+	// The value/s can optionally be executed if it's a function
+	access: function( elems, key, value, exec, fn, pass ) {
+		var length = elems.length;
+
+		// Setting many attributes
+		if ( typeof key === "object" ) {
+			for ( var k in key ) {
+				jQuery.access( elems, k, key[k], exec, fn, value );
+			}
+			return elems;
+		}
+
+		// Setting one attribute
+		if ( value !== undefined ) {
+			// Optionally, function values get executed if exec is true
+			exec = !pass && exec && jQuery.isFunction(value);
+
+			for ( var i = 0; i < length; i++ ) {
+				fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+			}
+
+			return elems;
+		}
+
+		// Getting an attribute
+		return length ? fn( elems[0], key ) : undefined;
+	},
+
+	now: function() {
+		return ( new Date() ).getTime();
+	},
+
+	// Use of jQuery.browser is frowned upon.
+	// More details: http://docs.jquery.com/Utilities/jQuery.browser
+	uaMatch: function( ua ) {
+		ua = ua.toLowerCase();
+
+		var match = rwebkit.exec( ua ) ||
+			ropera.exec( ua ) ||
+			rmsie.exec( ua ) ||
+			ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+			[];
+
+		return { browser: match[1] || "", version: match[2] || "0" };
+	},
+
+	sub: function() {
+		function jQuerySub( selector, context ) {
+			return new jQuerySub.fn.init( selector, context );
+		}
+		jQuery.extend( true, jQuerySub, this );
+		jQuerySub.superclass = this;
+		jQuerySub.fn = jQuerySub.prototype = this();
+		jQuerySub.fn.constructor = jQuerySub;
+		jQuerySub.sub = this.sub;
+		jQuerySub.fn.init = function init( selector, context ) {
+			if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+				context = jQuerySub( context );
+			}
+
+			return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+		};
+		jQuerySub.fn.init.prototype = jQuerySub.fn;
+		var rootjQuerySub = jQuerySub(document);
+		return jQuerySub;
+	},
+
+	browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+	jQuery.browser[ browserMatch.browser ] = true;
+	jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+	jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+	trimLeft = /^[\s\xA0]+/;
+	trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+	DOMContentLoaded = function() {
+		document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+		jQuery.ready();
+	};
+
+} else if ( document.attachEvent ) {
+	DOMContentLoaded = function() {
+		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+		if ( document.readyState === "complete" ) {
+			document.detachEvent( "onreadystatechange", DOMContentLoaded );
+			jQuery.ready();
+		}
+	};
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+	if ( jQuery.isReady ) {
+		return;
+	}
+
+	try {
+		// If IE is used, use the trick by Diego Perini
+		// http://javascript.nwbox.com/IEContentLoaded/
+		document.documentElement.doScroll("left");
+	} catch(e) {
+		setTimeout( doScrollCheck, 1 );
+		return;
+	}
+
+	// and execute any waiting functions
+	jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+	var object = flagsCache[ flags ] = {},
+		i, length;
+	flags = flags.split( /\s+/ );
+	for ( i = 0, length = flags.length; i < length; i++ ) {
+		object[ flags[i] ] = true;
+	}
+	return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ *	flags:	an optional list of space-separated flags that will change how
+ *			the callback list behaves
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ *	once:			will ensure the callback list can only be fired once (like a Deferred)
+ *
+ *	memory:			will keep track of previous values and will call any callback added
+ *					after the list has been fired right away with the latest "memorized"
+ *					values (like a Deferred)
+ *
+ *	unique:			will ensure a callback can only be added once (no duplicate in the list)
+ *
+ *	stopOnFalse:	interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( flags ) {
+
+	// Convert flags from String-formatted to Object-formatted
+	// (we check in cache first)
+	flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+	var // Actual callback list
+		list = [],
+		// Stack of fire calls for repeatable lists
+		stack = [],
+		// Last fire value (for non-forgettable lists)
+		memory,
+		// Flag to know if list is currently firing
+		firing,
+		// First callback to fire (used internally by add and fireWith)
+		firingStart,
+		// End of the loop when firing
+		firingLength,
+		// Index of currently firing callback (modified by remove if needed)
+		firingIndex,
+		// Add one or several callbacks to the list
+		add = function( args ) {
+			var i,
+				length,
+				elem,
+				type,
+				actual;
+			for ( i = 0, length = args.length; i < length; i++ ) {
+				elem = args[ i ];
+				type = jQuery.type( elem );
+				if ( type === "array" ) {
+					// Inspect recursively
+					add( elem );
+				} else if ( type === "function" ) {
+					// Add if not in unique mode and callback is not in
+					if ( !flags.unique || !self.has( elem ) ) {
+						list.push( elem );
+					}
+				}
+			}
+		},
+		// Fire callbacks
+		fire = function( context, args ) {
+			args = args || [];
+			memory = !flags.memory || [ context, args ];
+			firing = true;
+			firingIndex = firingStart || 0;
+			firingStart = 0;
+			firingLength = list.length;
+			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+				if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
+					memory = true; // Mark as halted
+					break;
+				}
+			}
+			firing = false;
+			if ( list ) {
+				if ( !flags.once ) {
+					if ( stack && stack.length ) {
+						memory = stack.shift();
+						self.fireWith( memory[ 0 ], memory[ 1 ] );
+					}
+				} else if ( memory === true ) {
+					self.disable();
+				} else {
+					list = [];
+				}
+			}
+		},
+		// Actual Callbacks object
+		self = {
+			// Add a callback or a collection of callbacks to the list
+			add: function() {
+				if ( list ) {
+					var length = list.length;
+					add( arguments );
+					// Do we need to add the callbacks to the
+					// current firing batch?
+					if ( firing ) {
+						firingLength = list.length;
+					// With memory, if we're not firing then
+					// we should call right away, unless previous
+					// firing was halted (stopOnFalse)
+					} else if ( memory && memory !== true ) {
+						firingStart = length;
+						fire( memory[ 0 ], memory[ 1 ] );
+					}
+				}
+				return this;
+			},
+			// Remove a callback from the list
+			remove: function() {
+				if ( list ) {
+					var args = arguments,
+						argIndex = 0,
+						argLength = args.length;
+					for ( ; argIndex < argLength ; argIndex++ ) {
+						for ( var i = 0; i < list.length; i++ ) {
+							if ( args[ argIndex ] === list[ i ] ) {
+								// Handle firingIndex and firingLength
+								if ( firing ) {
+									if ( i <= firingLength ) {
+										firingLength--;
+										if ( i <= firingIndex ) {
+											firingIndex--;
+										}
+									}
+								}
+								// Remove the element
+								list.splice( i--, 1 );
+								// If we have some unicity property then
+								// we only need to do this once
+								if ( flags.unique ) {
+									break;
+								}
+							}
+						}
+					}
+				}
+				return this;
+			},
+			// Control if a given callback is in the list
+			has: function( fn ) {
+				if ( list ) {
+					var i = 0,
+						length = list.length;
+					for ( ; i < length; i++ ) {
+						if ( fn === list[ i ] ) {
+							return true;
+						}
+					}
+				}
+				return false;
+			},
+			// Remove all callbacks from the list
+			empty: function() {
+				list = [];
+				return this;
+			},
+			// Have the list do nothing anymore
+			disable: function() {
+				list = stack = memory = undefined;
+				return this;
+			},
+			// Is it disabled?
+			disabled: function() {
+				return !list;
+			},
+			// Lock the list in its current state
+			lock: function() {
+				stack = undefined;
+				if ( !memory || memory === true ) {
+					self.disable();
+				}
+				return this;
+			},
+			// Is it locked?
+			locked: function() {
+				return !stack;
+			},
+			// Call all callbacks with the given context and arguments
+			fireWith: function( context, args ) {
+				if ( stack ) {
+					if ( firing ) {
+						if ( !flags.once ) {
+							stack.push( [ context, args ] );
+						}
+					} else if ( !( flags.once && memory ) ) {
+						fire( context, args );
+					}
+				}
+				return this;
+			},
+			// Call all the callbacks with the given arguments
+			fire: function() {
+				self.fireWith( this, arguments );
+				return this;
+			},
+			// To know if the callbacks have already been called at least once
+			fired: function() {
+				return !!memory;
+			}
+		};
+
+	return self;
+};
+
+
+
+
+var // Static reference to slice
+	sliceDeferred = [].slice;
+
+jQuery.extend({
+
+	Deferred: function( func ) {
+		var doneList = jQuery.Callbacks( "once memory" ),
+			failList = jQuery.Callbacks( "once memory" ),
+			progressList = jQuery.Callbacks( "memory" ),
+			state = "pending",
+			lists = {
+				resolve: doneList,
+				reject: failList,
+				notify: progressList
+			},
+			promise = {
+				done: doneList.add,
+				fail: failList.add,
+				progress: progressList.add,
+
+				state: function() {
+					return state;
+				},
+
+				// Deprecated
+				isResolved: doneList.fired,
+				isRejected: failList.fired,
+
+				then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+					deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
+					return this;
+				},
+				always: function() {
+					deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+					return this;
+				},
+				pipe: function( fnDone, fnFail, fnProgress ) {
+					return jQuery.Deferred(function( newDefer ) {
+						jQuery.each( {
+							done: [ fnDone, "resolve" ],
+							fail: [ fnFail, "reject" ],
+							progress: [ fnProgress, "notify" ]
+						}, function( handler, data ) {
+							var fn = data[ 0 ],
+								action = data[ 1 ],
+								returned;
+							if ( jQuery.isFunction( fn ) ) {
+								deferred[ handler ](function() {
+									returned = fn.apply( this, arguments );
+									if ( returned && jQuery.isFunction( returned.promise ) ) {
+										returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+									} else {
+										newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+									}
+								});
+							} else {
+								deferred[ handler ]( newDefer[ action ] );
+							}
+						});
+					}).promise();
+				},
+				// Get a promise for this deferred
+				// If obj is provided, the promise aspect is added to the object
+				promise: function( obj ) {
+					if ( obj == null ) {
+						obj = promise;
+					} else {
+						for ( var key in promise ) {
+							obj[ key ] = promise[ key ];
+						}
+					}
+					return obj;
+				}
+			},
+			deferred = promise.promise({}),
+			key;
+
+		for ( key in lists ) {
+			deferred[ key ] = lists[ key ].fire;
+			deferred[ key + "With" ] = lists[ key ].fireWith;
+		}
+
+		// Handle state
+		deferred.done( function() {
+			state = "resolved";
+		}, failList.disable, progressList.lock ).fail( function() {
+			state = "rejected";
+		}, doneList.disable, progressList.lock );
+
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+
+		// All done!
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( firstParam ) {
+		var args = sliceDeferred.call( arguments, 0 ),
+			i = 0,
+			length = args.length,
+			pValues = new Array( length ),
+			count = length,
+			pCount = length,
+			deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+				firstParam :
+				jQuery.Deferred(),
+			promise = deferred.promise();
+		function resolveFunc( i ) {
+			return function( value ) {
+				args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+				if ( !( --count ) ) {
+					deferred.resolveWith( deferred, args );
+				}
+			};
+		}
+		function progressFunc( i ) {
+			return function( value ) {
+				pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+				deferred.notifyWith( promise, pValues );
+			};
+		}
+		if ( length > 1 ) {
+			for ( ; i < length; i++ ) {
+				if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+					args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+				} else {
+					--count;
+				}
+			}
+			if ( !count ) {
+				deferred.resolveWith( deferred, args );
+			}
+		} else if ( deferred !== firstParam ) {
+			deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+		}
+		return promise;
+	}
+});
+
+
+
+
+jQuery.support = (function() {
+
+	var support,
+		all,
+		a,
+		select,
+		opt,
+		input,
+		marginDiv,
+		fragment,
+		tds,
+		events,
+		eventName,
+		i,
+		isSupported,
+		div = document.createElement( "div" ),
+		documentElement = document.documentElement;
+
+	// Preliminary tests
+	div.setAttribute("className", "t");
+	div.innerHTML = "   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+	all = div.getElementsByTagName( "*" );
+	a = div.getElementsByTagName( "a" )[ 0 ];
+
+	// Can't get basic test support
+	if ( !all || !all.length || !a ) {
+		return {};
+	}
+
+	// First batch of supports tests
+	select = document.createElement( "select" );
+	opt = select.appendChild( document.createElement("option") );
+	input = div.getElementsByTagName( "input" )[ 0 ];
+
+	support = {
+		// IE strips leading whitespace when .innerHTML is used
+		leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+		// Make sure that tbody elements aren't automatically inserted
+		// IE will insert them into empty tables
+		tbody: !div.getElementsByTagName("tbody").length,
+
+		// Make sure that link elements get serialized correctly by innerHTML
+		// This requires a wrapper element in IE
+		htmlSerialize: !!div.getElementsByTagName("link").length,
+
+		// Get the style information from getAttribute
+		// (IE uses .cssText instead)
+		style: /top/.test( a.getAttribute("style") ),
+
+		// Make sure that URLs aren't manipulated
+		// (IE normalizes it by default)
+		hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+		// Make sure that element opacity exists
+		// (IE uses filter instead)
+		// Use a regex to work around a WebKit issue. See #5145
+		opacity: /^0.55/.test( a.style.opacity ),
+
+		// Verify style float existence
+		// (IE uses styleFloat instead of cssFloat)
+		cssFloat: !!a.style.cssFloat,
+
+		// Make sure that if no value is specified for a checkbox
+		// that it defaults to "on".
+		// (WebKit defaults to "" instead)
+		checkOn: ( input.value === "on" ),
+
+		// Make sure that a selected-by-default option has a working selected property.
+		// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+		optSelected: opt.selected,
+
+		// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+		getSetAttribute: div.className !== "t",
+
+		// Tests for enctype support on a form(#6743)
+		enctype: !!document.createElement("form").enctype,
+
+		// Makes sure cloning an html5 element does not cause problems
+		// Where outerHTML is undefined, this still works
+		html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+		// Will be defined later
+		submitBubbles: true,
+		changeBubbles: true,
+		focusinBubbles: false,
+		deleteExpando: true,
+		noCloneEvent: true,
+		inlineBlockNeedsLayout: false,
+		shrinkWrapBlocks: false,
+		reliableMarginRight: true
+	};
+
+	// Make sure checked status is properly cloned
+	input.checked = true;
+	support.noCloneChecked = input.cloneNode( true ).checked;
+
+	// Make sure that the options inside disabled selects aren't marked as disabled
+	// (WebKit marks them as disabled)
+	select.disabled = true;
+	support.optDisabled = !opt.disabled;
+
+	// Test to see if it's possible to delete an expando from an element
+	// Fails in Internet Explorer
+	try {
+		delete div.test;
+	} catch( e ) {
+		support.deleteExpando = false;
+	}
+
+	if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+		div.attachEvent( "onclick", function() {
+			// Cloning a node shouldn't copy over any
+			// bound event handlers (IE does this)
+			support.noCloneEvent = false;
+		});
+		div.cloneNode( true ).fireEvent( "onclick" );
+	}
+
+	// Check if a radio maintains its value
+	// after being appended to the DOM
+	input = document.createElement("input");
+	input.value = "t";
+	input.setAttribute("type", "radio");
+	support.radioValue = input.value === "t";
+
+	input.setAttribute("checked", "checked");
+	div.appendChild( input );
+	fragment = document.createDocumentFragment();
+	fragment.appendChild( div.lastChild );
+
+	// WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	// Check if a disconnected checkbox will retain its checked
+	// value of true after appended to the DOM (IE6/7)
+	support.appendChecked = input.checked;
+
+	fragment.removeChild( input );
+	fragment.appendChild( div );
+
+	div.innerHTML = "";
+
+	// Check if div with explicit width and no margin-right incorrectly
+	// gets computed margin-right based on width of container. For more
+	// info see bug #3333
+	// Fails in WebKit before Feb 2011 nightlies
+	// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+	if ( window.getComputedStyle ) {
+		marginDiv = document.createElement( "div" );
+		marginDiv.style.width = "0";
+		marginDiv.style.marginRight = "0";
+		div.style.width = "2px";
+		div.appendChild( marginDiv );
+		support.reliableMarginRight =
+			( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+	}
+
+	// Technique from Juriy Zaytsev
+	// http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+	// We only care about the case where non-standard event systems
+	// are used, namely in IE. Short-circuiting here helps us to
+	// avoid an eval call (in setAttribute) which can cause CSP
+	// to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+	if ( div.attachEvent ) {
+		for( i in {
+			submit: 1,
+			change: 1,
+			focusin: 1
+		}) {
+			eventName = "on" + i;
+			isSupported = ( eventName in div );
+			if ( !isSupported ) {
+				div.setAttribute( eventName, "return;" );
+				isSupported = ( typeof div[ eventName ] === "function" );
+			}
+			support[ i + "Bubbles" ] = isSupported;
+		}
+	}
+
+	fragment.removeChild( div );
+
+	// Null elements to avoid leaks in IE
+	fragment = select = opt = marginDiv = div = input = null;
+
+	// Run tests that need a body at doc ready
+	jQuery(function() {
+		var container, outer, inner, table, td, offsetSupport,
+			conMarginTop, ptlm, vb, style, html,
+			body = document.getElementsByTagName("body")[0];
+
+		if ( !body ) {
+			// Return for frameset docs that don't have a body
+			return;
+		}
+
+		conMarginTop = 1;
+		ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;";
+		vb = "visibility:hidden;border:0;";
+		style = "style='" + ptlm + "border:5px solid #000;padding:0;'";
+		html = "<div " + style + "><div></div></div>" +
+			"<table " + style + " cellpadding='0' cellspacing='0'>" +
+			"<tr><td></td></tr></table>";
+
+		container = document.createElement("div");
+		container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+		body.insertBefore( container, body.firstChild );
+
+		// Construct the test element
+		div = document.createElement("div");
+		container.appendChild( div );
+
+		// Check if table cells still have offsetWidth/Height when they are set
+		// to display:none and there are still other visible table cells in a
+		// table row; if so, offsetWidth/Height are not reliable for use when
+		// determining if an element has been hidden directly using
+		// display:none (it is still safe to use offsets if a parent element is
+		// hidden; don safety goggles and see bug #4512 for more information).
+		// (only IE 8 fails this test)
+		div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+		tds = div.getElementsByTagName( "td" );
+		isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+		tds[ 0 ].style.display = "";
+		tds[ 1 ].style.display = "none";
+
+		// Check if empty table cells still have offsetWidth/Height
+		// (IE <= 8 fail this test)
+		support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+		// Figure out if the W3C box model works as expected
+		div.innerHTML = "";
+		div.style.width = div.style.paddingLeft = "1px";
+		jQuery.boxModel = support.boxModel = div.offsetWidth === 2;
+
+		if ( typeof div.style.zoom !== "undefined" ) {
+			// Check if natively block-level elements act like inline-block
+			// elements when setting their display to 'inline' and giving
+			// them layout
+			// (IE < 8 does this)
+			div.style.display = "inline";
+			div.style.zoom = 1;
+			support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );
+
+			// Check if elements with layout shrink-wrap their children
+			// (IE 6 does this)
+			div.style.display = "";
+			div.innerHTML = "<div style='width:4px;'></div>";
+			support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
+		}
+
+		div.style.cssText = ptlm + vb;
+		div.innerHTML = html;
+
+		outer = div.firstChild;
+		inner = outer.firstChild;
+		td = outer.nextSibling.firstChild.firstChild;
+
+		offsetSupport = {
+			doesNotAddBorder: ( inner.offsetTop !== 5 ),
+			doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+		};
+
+		inner.style.position = "fixed";
+		inner.style.top = "20px";
+
+		// safari subtracts parent border width here which is 5px
+		offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
+		inner.style.position = inner.style.top = "";
+
+		outer.style.overflow = "hidden";
+		outer.style.position = "relative";
+
+		offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
+		offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
+
+		body.removeChild( container );
+		div  = container = null;
+
+		jQuery.extend( support, offsetSupport );
+	});
+
+	return support;
+})();
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+	rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+	cache: {},
+
+	// Please use with caution
+	uuid: 0,
+
+	// Unique for each copy of jQuery on the page
+	// Non-digits removed to match rinlinejQuery
+	expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+	// The following elements throw uncatchable exceptions if you
+	// attempt to add expando properties to them.
+	noData: {
+		"embed": true,
+		// Ban all objects except for Flash (which handle expandos)
+		"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+		"applet": true
+	},
+
+	hasData: function( elem ) {
+		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+		return !!elem && !isEmptyDataObject( elem );
+	},
+
+	data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var privateCache, thisCache, ret,
+			internalKey = jQuery.expando,
+			getByName = typeof name === "string",
+
+			// We have to handle DOM nodes and JS objects differently because IE6-7
+			// can't GC object references properly across the DOM-JS boundary
+			isNode = elem.nodeType,
+
+			// Only DOM nodes need the global jQuery cache; JS object data is
+			// attached directly to the object so GC can occur automatically
+			cache = isNode ? jQuery.cache : elem,
+
+			// Only defining an ID for JS objects if its cache already exists allows
+			// the code to shortcut on the same path as a DOM node with no cache
+			id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
+			isEvents = name === "events";
+
+		// Avoid doing any more work than we need to when trying to get data on an
+		// object that has no data at all
+		if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+			return;
+		}
+
+		if ( !id ) {
+			// Only DOM nodes need a new unique ID for each element since their data
+			// ends up in the global cache
+			if ( isNode ) {
+				elem[ internalKey ] = id = ++jQuery.uuid;
+			} else {
+				id = internalKey;
+			}
+		}
+
+		if ( !cache[ id ] ) {
+			cache[ id ] = {};
+
+			// Avoids exposing jQuery metadata on plain JS objects when the object
+			// is serialized using JSON.stringify
+			if ( !isNode ) {
+				cache[ id ].toJSON = jQuery.noop;
+			}
+		}
+
+		// An object can be passed to jQuery.data instead of a key/value pair; this gets
+		// shallow copied over onto the existing cache
+		if ( typeof name === "object" || typeof name === "function" ) {
+			if ( pvt ) {
+				cache[ id ] = jQuery.extend( cache[ id ], name );
+			} else {
+				cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+			}
+		}
+
+		privateCache = thisCache = cache[ id ];
+
+		// jQuery data() is stored in a separate object inside the object's internal data
+		// cache in order to avoid key collisions between internal data and user-defined
+		// data.
+		if ( !pvt ) {
+			if ( !thisCache.data ) {
+				thisCache.data = {};
+			}
+
+			thisCache = thisCache.data;
+		}
+
+		if ( data !== undefined ) {
+			thisCache[ jQuery.camelCase( name ) ] = data;
+		}
+
+		// Users should not attempt to inspect the internal events object using jQuery.data,
+		// it is undocumented and subject to change. But does anyone listen? No.
+		if ( isEvents && !thisCache[ name ] ) {
+			return privateCache.events;
+		}
+
+		// Check for both converted-to-camel and non-converted data property names
+		// If a data property was specified
+		if ( getByName ) {
+
+			// First Try to find as-is property data
+			ret = thisCache[ name ];
+
+			// Test for null|undefined property data
+			if ( ret == null ) {
+
+				// Try to find the camelCased property
+				ret = thisCache[ jQuery.camelCase( name ) ];
+			}
+		} else {
+			ret = thisCache;
+		}
+
+		return ret;
+	},
+
+	removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var thisCache, i, l,
+
+			// Reference to internal data cache key
+			internalKey = jQuery.expando,
+
+			isNode = elem.nodeType,
+
+			// See jQuery.data for more information
+			cache = isNode ? jQuery.cache : elem,
+
+			// See jQuery.data for more information
+			id = isNode ? elem[ internalKey ] : internalKey;
+
+		// If there is already no cache entry for this object, there is no
+		// purpose in continuing
+		if ( !cache[ id ] ) {
+			return;
+		}
+
+		if ( name ) {
+
+			thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+			if ( thisCache ) {
+
+				// Support array or space separated string names for data keys
+				if ( !jQuery.isArray( name ) ) {
+
+					// try the string as a key before any manipulation
+					if ( name in thisCache ) {
+						name = [ name ];
+					} else {
+
+						// split the camel cased version by spaces unless a key with the spaces exists
+						name = jQuery.camelCase( name );
+						if ( name in thisCache ) {
+							name = [ name ];
+						} else {
+							name = name.split( " " );
+						}
+					}
+				}
+
+				for ( i = 0, l = name.length; i < l; i++ ) {
+					delete thisCache[ name[i] ];
+				}
+
+				// If there is no data left in the cache, we want to continue
+				// and let the cache object itself get destroyed
+				if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+					return;
+				}
+			}
+		}
+
+		// See jQuery.data for more information
+		if ( !pvt ) {
+			delete cache[ id ].data;
+
+			// Don't destroy the parent cache unless the internal data object
+			// had been the only thing left in it
+			if ( !isEmptyDataObject(cache[ id ]) ) {
+				return;
+			}
+		}
+
+		// Browsers that fail expando deletion also refuse to delete expandos on
+		// the window, but it will allow it on all other JS objects; other browsers
+		// don't care
+		// Ensure that `cache` is not a window object #10080
+		if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+			delete cache[ id ];
+		} else {
+			cache[ id ] = null;
+		}
+
+		// We destroyed the cache and need to eliminate the expando on the node to avoid
+		// false lookups in the cache for entries that no longer exist
+		if ( isNode ) {
+			// IE does not allow us to delete expando properties from nodes,
+			// nor does it have a removeAttribute function on Document nodes;
+			// we must handle all of these cases
+			if ( jQuery.support.deleteExpando ) {
+				delete elem[ internalKey ];
+			} else if ( elem.removeAttribute ) {
+				elem.removeAttribute( internalKey );
+			} else {
+				elem[ internalKey ] = null;
+			}
+		}
+	},
+
+	// For internal use only.
+	_data: function( elem, name, data ) {
+		return jQuery.data( elem, name, data, true );
+	},
+
+	// A method for determining if a DOM node can handle the data expando
+	acceptData: function( elem ) {
+		if ( elem.nodeName ) {
+			var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+			if ( match ) {
+				return !(match === true || elem.getAttribute("classid") !== match);
+			}
+		}
+
+		return true;
+	}
+});
+
+jQuery.fn.extend({
+	data: function( key, value ) {
+		var parts, attr, name,
+			data = null;
+
+		if ( typeof key === "undefined" ) {
+			if ( this.length ) {
+				data = jQuery.data( this[0] );
+
+				if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
+					attr = this[0].attributes;
+					for ( var i = 0, l = attr.length; i < l; i++ ) {
+						name = attr[i].name;
+
+						if ( name.indexOf( "data-" ) === 0 ) {
+							name = jQuery.camelCase( name.substring(5) );
+
+							dataAttr( this[0], name, data[ name ] );
+						}
+					}
+					jQuery._data( this[0], "parsedAttrs", true );
+				}
+			}
+
+			return data;
+
+		} else if ( typeof key === "object" ) {
+			return this.each(function() {
+				jQuery.data( this, key );
+			});
+		}
+
+		parts = key.split(".");
+		parts[1] = parts[1] ? "." + parts[1] : "";
+
+		if ( value === undefined ) {
+			data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+			// Try to fetch any internally stored data first
+			if ( data === undefined && this.length ) {
+				data = jQuery.data( this[0], key );
+				data = dataAttr( this[0], key, data );
+			}
+
+			return data === undefined && parts[1] ?
+				this.data( parts[0] ) :
+				data;
+
+		} else {
+			return this.each(function() {
+				var self = jQuery( this ),
+					args = [ parts[0], value ];
+
+				self.triggerHandler( "setData" + parts[1] + "!", args );
+				jQuery.data( this, key, value );
+				self.triggerHandler( "changeData" + parts[1] + "!", args );
+			});
+		}
+	},
+
+	removeData: function( key ) {
+		return this.each(function() {
+			jQuery.removeData( this, key );
+		});
+	}
+});
+
+function dataAttr( elem, key, data ) {
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+
+		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+		data = elem.getAttribute( name );
+
+		if ( typeof data === "string" ) {
+			try {
+				data = data === "true" ? true :
+				data === "false" ? false :
+				data === "null" ? null :
+				jQuery.isNumeric( data ) ? parseFloat( data ) :
+					rbrace.test( data ) ? jQuery.parseJSON( data ) :
+					data;
+			} catch( e ) {}
+
+			// Make sure we set the data so it isn't changed later
+			jQuery.data( elem, key, data );
+
+		} else {
+			data = undefined;
+		}
+	}
+
+	return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+	for ( var name in obj ) {
+
+		// if the public data object is empty, the private is still empty
+		if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+			continue;
+		}
+		if ( name !== "toJSON" ) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+	var deferDataKey = type + "defer",
+		queueDataKey = type + "queue",
+		markDataKey = type + "mark",
+		defer = jQuery._data( elem, deferDataKey );
+	if ( defer &&
+		( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
+		( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+		// Give room for hard-coded callbacks to fire first
+		// and eventually mark/queue something else on the element
+		setTimeout( function() {
+			if ( !jQuery._data( elem, queueDataKey ) &&
+				!jQuery._data( elem, markDataKey ) ) {
+				jQuery.removeData( elem, deferDataKey, true );
+				defer.fire();
+			}
+		}, 0 );
+	}
+}
+
+jQuery.extend({
+
+	_mark: function( elem, type ) {
+		if ( elem ) {
+			type = ( type || "fx" ) + "mark";
+			jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
+		}
+	},
+
+	_unmark: function( force, elem, type ) {
+		if ( force !== true ) {
+			type = elem;
+			elem = force;
+			force = false;
+		}
+		if ( elem ) {
+			type = type || "fx";
+			var key = type + "mark",
+				count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
+			if ( count ) {
+				jQuery._data( elem, key, count );
+			} else {
+				jQuery.removeData( elem, key, true );
+				handleQueueMarkDefer( elem, type, "mark" );
+			}
+		}
+	},
+
+	queue: function( elem, type, data ) {
+		var q;
+		if ( elem ) {
+			type = ( type || "fx" ) + "queue";
+			q = jQuery._data( elem, type );
+
+			// Speed up dequeue by getting out quickly if this is just a lookup
+			if ( data ) {
+				if ( !q || jQuery.isArray(data) ) {
+					q = jQuery._data( elem, type, jQuery.makeArray(data) );
+				} else {
+					q.push( data );
+				}
+			}
+			return q || [];
+		}
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			fn = queue.shift(),
+			hooks = {};
+
+		// If the fx queue is dequeued, always remove the progress sentinel
+		if ( fn === "inprogress" ) {
+			fn = queue.shift();
+		}
+
+		if ( fn ) {
+			// Add a progress sentinel to prevent the fx queue from being
+			// automatically dequeued
+			if ( type === "fx" ) {
+				queue.unshift( "inprogress" );
+			}
+
+			jQuery._data( elem, type + ".run", hooks );
+			fn.call( elem, function() {
+				jQuery.dequeue( elem, type );
+			}, hooks );
+		}
+
+		if ( !queue.length ) {
+			jQuery.removeData( elem, type + "queue " + type + ".run", true );
+			handleQueueMarkDefer( elem, type, "queue" );
+		}
+	}
+});
+
+jQuery.fn.extend({
+	queue: function( type, data ) {
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+		}
+
+		if ( data === undefined ) {
+			return jQuery.queue( this[0], type );
+		}
+		return this.each(function() {
+			var queue = jQuery.queue( this, type, data );
+
+			if ( type === "fx" && queue[0] !== "inprogress" ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	},
+	dequeue: function( type ) {
+		return this.each(function() {
+			jQuery.dequeue( this, type );
+		});
+	},
+	// Based off of the plugin by Clint Helfers, with permission.
+	// http://blindsignals.com/index.php/2009/07/jquery-delay/
+	delay: function( time, type ) {
+		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+		type = type || "fx";
+
+		return this.queue( type, function( next, hooks ) {
+			var timeout = setTimeout( next, time );
+			hooks.stop = function() {
+				clearTimeout( timeout );
+			};
+		});
+	},
+	clearQueue: function( type ) {
+		return this.queue( type || "fx", [] );
+	},
+	// Get a promise resolved when queues of a certain type
+	// are emptied (fx is the type by default)
+	promise: function( type, object ) {
+		if ( typeof type !== "string" ) {
+			object = type;
+			type = undefined;
+		}
+		type = type || "fx";
+		var defer = jQuery.Deferred(),
+			elements = this,
+			i = elements.length,
+			count = 1,
+			deferDataKey = type + "defer",
+			queueDataKey = type + "queue",
+			markDataKey = type + "mark",
+			tmp;
+		function resolve() {
+			if ( !( --count ) ) {
+				defer.resolveWith( elements, [ elements ] );
+			}
+		}
+		while( i-- ) {
+			if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+					( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+						jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+					jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+				count++;
+				tmp.add( resolve );
+			}
+		}
+		resolve();
+		return defer.promise();
+	}
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+	rspace = /\s+/,
+	rreturn = /\r/g,
+	rtype = /^(?:button|input)$/i,
+	rfocusable = /^(?:button|input|object|select|textarea)$/i,
+	rclickable = /^a(?:rea)?$/i,
+	rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+	getSetAttribute = jQuery.support.getSetAttribute,
+	nodeHook, boolHook, fixSpecified;
+
+jQuery.fn.extend({
+	attr: function( name, value ) {
+		return jQuery.access( this, name, value, true, jQuery.attr );
+	},
+
+	removeAttr: function( name ) {
+		return this.each(function() {
+			jQuery.removeAttr( this, name );
+		});
+	},
+
+	prop: function( name, value ) {
+		return jQuery.access( this, name, value, true, jQuery.prop );
+	},
+
+	removeProp: function( name ) {
+		name = jQuery.propFix[ name ] || name;
+		return this.each(function() {
+			// try/catch handles cases where IE balks (such as removing a property on window)
+			try {
+				this[ name ] = undefined;
+				delete this[ name ];
+			} catch( e ) {}
+		});
+	},
+
+	addClass: function( value ) {
+		var classNames, i, l, elem,
+			setClass, c, cl;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).addClass( value.call(this, j, this.className) );
+			});
+		}
+
+		if ( value && typeof value === "string" ) {
+			classNames = value.split( rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.nodeType === 1 ) {
+					if ( !elem.className && classNames.length === 1 ) {
+						elem.className = value;
+
+					} else {
+						setClass = " " + elem.className + " ";
+
+						for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+							if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+								setClass += classNames[ c ] + " ";
+							}
+						}
+						elem.className = jQuery.trim( setClass );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var classNames, i, l, elem, className, c, cl;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).removeClass( value.call(this, j, this.className) );
+			});
+		}
+
+		if ( (value && typeof value === "string") || value === undefined ) {
+			classNames = ( value || "" ).split( rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.nodeType === 1 && elem.className ) {
+					if ( value ) {
+						className = (" " + elem.className + " ").replace( rclass, " " );
+						for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+							className = className.replace(" " + classNames[ c ] + " ", " ");
+						}
+						elem.className = jQuery.trim( className );
+
+					} else {
+						elem.className = "";
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value,
+			isBool = typeof stateVal === "boolean";
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+			});
+		}
+
+		return this.each(function() {
+			if ( type === "string" ) {
+				// toggle individual class names
+				var className,
+					i = 0,
+					self = jQuery( this ),
+					state = stateVal,
+					classNames = value.split( rspace );
+
+				while ( (className = classNames[ i++ ]) ) {
+					// check each className given, space seperated list
+					state = isBool ? state : !self.hasClass( className );
+					self[ state ? "addClass" : "removeClass" ]( className );
+				}
+
+			} else if ( type === "undefined" || type === "boolean" ) {
+				if ( this.className ) {
+					// store className if set
+					jQuery._data( this, "__className__", this.className );
+				}
+
+				// toggle whole className
+				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+			}
+		});
+	},
+
+	hasClass: function( selector ) {
+		var className = " " + selector + " ",
+			i = 0,
+			l = this.length;
+		for ( ; i < l; i++ ) {
+			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+				return true;
+			}
+		}
+
+		return false;
+	},
+
+	val: function( value ) {
+		var hooks, ret, isFunction,
+			elem = this[0];
+
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
+
+				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				return typeof ret === "string" ?
+					// handle most common string cases
+					ret.replace(rreturn, "") :
+					// handle cases where value is null/undef or number
+					ret == null ? "" : ret;
+			}
+
+			return;
+		}
+
+		isFunction = jQuery.isFunction( value );
+
+		return this.each(function( i ) {
+			var self = jQuery(this), val;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call( this, i, self.val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+			} else if ( typeof val === "number" ) {
+				val += "";
+			} else if ( jQuery.isArray( val ) ) {
+				val = jQuery.map(val, function ( value ) {
+					return value == null ? "" : value + "";
+				});
+			}
+
+			hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	valHooks: {
+		option: {
+			get: function( elem ) {
+				// attributes.value is undefined in Blackberry 4.7 but
+				// uses .value. See #6932
+				var val = elem.attributes.value;
+				return !val || val.specified ? elem.value : elem.text;
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value, i, max, option,
+					index = elem.selectedIndex,
+					values = [],
+					options = elem.options,
+					one = elem.type === "select-one";
+
+				// Nothing was selected
+				if ( index < 0 ) {
+					return null;
+				}
+
+				// Loop through all the selected options
+				i = one ? index : 0;
+				max = one ? index + 1 : options.length;
+				for ( ; i < max; i++ ) {
+					option = options[ i ];
+
+					// Don't return options that are disabled or in a disabled optgroup
+					if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+							(!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+						// Get the specific value for the option
+						value = jQuery( option ).val();
+
+						// We don't need an array for one selects
+						if ( one ) {
+							return value;
+						}
+
+						// Multi-Selects return an array
+						values.push( value );
+					}
+				}
+
+				// Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+				if ( one && !values.length && options.length ) {
+					return jQuery( options[ index ] ).val();
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var values = jQuery.makeArray( value );
+
+				jQuery(elem).find("option").each(function() {
+					this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+				});
+
+				if ( !values.length ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	},
+
+	attrFn: {
+		val: true,
+		css: true,
+		html: true,
+		text: true,
+		data: true,
+		width: true,
+		height: true,
+		offset: true
+	},
+
+	attr: function( elem, name, value, pass ) {
+		var ret, hooks, notxml,
+			nType = elem.nodeType;
+
+		// don't get/set attributes on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		if ( pass && name in jQuery.attrFn ) {
+			return jQuery( elem )[ name ]( value );
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( typeof elem.getAttribute === "undefined" ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		// All attributes are lowercase
+		// Grab necessary hook if one is defined
+		if ( notxml ) {
+			name = name.toLowerCase();
+			hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+		}
+
+		if ( value !== undefined ) {
+
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+				return;
+
+			} else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				elem.setAttribute( name, "" + value );
+				return value;
+			}
+
+		} else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+			return ret;
+
+		} else {
+
+			ret = elem.getAttribute( name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret === null ?
+				undefined :
+				ret;
+		}
+	},
+
+	removeAttr: function( elem, value ) {
+		var propName, attrNames, name, l,
+			i = 0;
+
+		if ( value && elem.nodeType === 1 ) {
+			attrNames = value.toLowerCase().split( rspace );
+			l = attrNames.length;
+
+			for ( ; i < l; i++ ) {
+				name = attrNames[ i ];
+
+				if ( name ) {
+					propName = jQuery.propFix[ name ] || name;
+
+					// See #9699 for explanation of this approach (setting first, then removal)
+					jQuery.attr( elem, name, "" );
+					elem.removeAttribute( getSetAttribute ? name : propName );
+
+					// Set corresponding property to false for boolean attributes
+					if ( rboolean.test( name ) && propName in elem ) {
+						elem[ propName ] = false;
+					}
+				}
+			}
+		}
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				// We can't allow the type property to be changed (since it causes problems in IE)
+				if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+					jQuery.error( "type property can't be changed" );
+				} else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+					// Setting the type on a radio button after the value resets the value in IE6-9
+					// Reset value to it's default in case type is set after value
+					// This is for element creation
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		},
+		// Use the value property for back compat
+		// Use the nodeHook for button elements in IE6/7 (#1954)
+		value: {
+			get: function( elem, name ) {
+				if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+					return nodeHook.get( elem, name );
+				}
+				return name in elem ?
+					elem.value :
+					null;
+			},
+			set: function( elem, value, name ) {
+				if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+					return nodeHook.set( elem, value, name );
+				}
+				// Does not return so that setAttribute is also used
+				elem.value = value;
+			}
+		}
+	},
+
+	propFix: {
+		tabindex: "tabIndex",
+		readonly: "readOnly",
+		"for": "htmlFor",
+		"class": "className",
+		maxlength: "maxLength",
+		cellspacing: "cellSpacing",
+		cellpadding: "cellPadding",
+		rowspan: "rowSpan",
+		colspan: "colSpan",
+		usemap: "useMap",
+		frameborder: "frameBorder",
+		contenteditable: "contentEditable"
+	},
+
+	prop: function( elem, name, value ) {
+		var ret, hooks, notxml,
+			nType = elem.nodeType;
+
+		// don't get/set properties on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		if ( notxml ) {
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				return ( elem[ name ] = value );
+			}
+
+		} else {
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+				return ret;
+
+			} else {
+				return elem[ name ];
+			}
+		}
+	},
+
+	propHooks: {
+		tabIndex: {
+			get: function( elem ) {
+				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				var attributeNode = elem.getAttributeNode("tabindex");
+
+				return attributeNode && attributeNode.specified ?
+					parseInt( attributeNode.value, 10 ) :
+					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+						0 :
+						undefined;
+			}
+		}
+	}
+});
+
+// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
+jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+	get: function( elem, name ) {
+		// Align boolean attributes with corresponding properties
+		// Fall back to attribute presence where some booleans are not supported
+		var attrNode,
+			property = jQuery.prop( elem, name );
+		return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+			name.toLowerCase() :
+			undefined;
+	},
+	set: function( elem, value, name ) {
+		var propName;
+		if ( value === false ) {
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else {
+			// value is true since we know at this point it's type boolean and not false
+			// Set boolean attributes to the same name and set the DOM property
+			propName = jQuery.propFix[ name ] || name;
+			if ( propName in elem ) {
+				// Only set the IDL specifically if it already exists on the element
+				elem[ propName ] = true;
+			}
+
+			elem.setAttribute( name, name.toLowerCase() );
+		}
+		return name;
+	}
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+	fixSpecified = {
+		name: true,
+		id: true
+	};
+
+	// Use this for any attribute in IE6/7
+	// This fixes almost every IE6/7 issue
+	nodeHook = jQuery.valHooks.button = {
+		get: function( elem, name ) {
+			var ret;
+			ret = elem.getAttributeNode( name );
+			return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
+				ret.nodeValue :
+				undefined;
+		},
+		set: function( elem, value, name ) {
+			// Set the existing or create a new attribute node
+			var ret = elem.getAttributeNode( name );
+			if ( !ret ) {
+				ret = document.createAttribute( name );
+				elem.setAttributeNode( ret );
+			}
+			return ( ret.nodeValue = value + "" );
+		}
+	};
+
+	// Apply the nodeHook to tabindex
+	jQuery.attrHooks.tabindex.set = nodeHook.set;
+
+	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
+	// This is for removals
+	jQuery.each([ "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			set: function( elem, value ) {
+				if ( value === "" ) {
+					elem.setAttribute( name, "auto" );
+					return value;
+				}
+			}
+		});
+	});
+
+	// Set contenteditable to false on removals(#10429)
+	// Setting to empty string throws an error as an invalid value
+	jQuery.attrHooks.contenteditable = {
+		get: nodeHook.get,
+		set: function( elem, value, name ) {
+			if ( value === "" ) {
+				value = "false";
+			}
+			nodeHook.set( elem, value, name );
+		}
+	};
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+	jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			get: function( elem ) {
+				var ret = elem.getAttribute( name, 2 );
+				return ret === null ? undefined : ret;
+			}
+		});
+	});
+}
+
+if ( !jQuery.support.style ) {
+	jQuery.attrHooks.style = {
+		get: function( elem ) {
+			// Return undefined in the case of empty string
+			// Normalize to lowercase since IE uppercases css property names
+			return elem.style.cssText.toLowerCase() || undefined;
+		},
+		set: function( elem, value ) {
+			return ( elem.style.cssText = "" + value );
+		}
+	};
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+	jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+		get: function( elem ) {
+			var parent = elem.parentNode;
+
+			if ( parent ) {
+				parent.selectedIndex;
+
+				// Make sure that it also works with optgroups, see #5701
+				if ( parent.parentNode ) {
+					parent.parentNode.selectedIndex;
+				}
+			}
+			return null;
+		}
+	});
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+	jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+	jQuery.each([ "radio", "checkbox" ], function() {
+		jQuery.valHooks[ this ] = {
+			get: function( elem ) {
+				// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+				return elem.getAttribute("value") === null ? "on" : elem.value;
+			}
+		};
+	});
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+		set: function( elem, value ) {
+			if ( jQuery.isArray( value ) ) {
+				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+			}
+		}
+	});
+});
+
+
+
+
+var rformElems = /^(?:textarea|input|select)$/i,
+	rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+	rhoverHack = /\bhover(\.\S+)?\b/,
+	rkeyEvent = /^key/,
+	rmouseEvent = /^(?:mouse|contextmenu)|click/,
+	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+	rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
+	quickParse = function( selector ) {
+		var quick = rquickIs.exec( selector );
+		if ( quick ) {
+			//   0  1    2   3
+			// [ _, tag, id, class ]
+			quick[1] = ( quick[1] || "" ).toLowerCase();
+			quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
+		}
+		return quick;
+	},
+	quickIs = function( elem, m ) {
+		var attrs = elem.attributes || {};
+		return (
+			(!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
+			(!m[2] || (attrs.id || {}).value === m[2]) &&
+			(!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
+		);
+	},
+	hoverHack = function( events ) {
+		return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+	};
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+	add: function( elem, types, handler, data, selector ) {
+
+		var elemData, eventHandle, events,
+			t, tns, type, namespaces, handleObj,
+			handleObjIn, quick, handlers, special;
+
+		// Don't attach events to noData or text/comment nodes (allow plain objects tho)
+		if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+			return;
+		}
+
+		// Caller can pass in an object of custom data in lieu of the handler
+		if ( handler.handler ) {
+			handleObjIn = handler;
+			handler = handleObjIn.handler;
+		}
+
+		// Make sure that the handler has a unique ID, used to find/remove it later
+		if ( !handler.guid ) {
+			handler.guid = jQuery.guid++;
+		}
+
+		// Init the element's event structure and main handler, if this is the first
+		events = elemData.events;
+		if ( !events ) {
+			elemData.events = events = {};
+		}
+		eventHandle = elemData.handle;
+		if ( !eventHandle ) {
+			elemData.handle = eventHandle = function( e ) {
+				// Discard the second event of a jQuery.event.trigger() and
+				// when an event is called after a page has unloaded
+				return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+					undefined;
+			};
+			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+			eventHandle.elem = elem;
+		}
+
+		// Handle multiple events separated by a space
+		// jQuery(...).bind("mouseover mouseout", fn);
+		types = jQuery.trim( hoverHack(types) ).split( " " );
+		for ( t = 0; t < types.length; t++ ) {
+
+			tns = rtypenamespace.exec( types[t] ) || [];
+			type = tns[1];
+			namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+			// If event changes its type, use the special event handlers for the changed type
+			special = jQuery.event.special[ type ] || {};
+
+			// If selector defined, determine special event api type, otherwise given type
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+
+			// Update special based on newly reset type
+			special = jQuery.event.special[ type ] || {};
+
+			// handleObj is passed to all event handlers
+			handleObj = jQuery.extend({
+				type: type,
+				origType: tns[1],
+				data: data,
+				handler: handler,
+				guid: handler.guid,
+				selector: selector,
+				quick: quickParse( selector ),
+				namespace: namespaces.join(".")
+			}, handleObjIn );
+
+			// Init the event handler queue if we're the first
+			handlers = events[ type ];
+			if ( !handlers ) {
+				handlers = events[ type ] = [];
+				handlers.delegateCount = 0;
+
+				// Only use addEventListener/attachEvent if the special events handler returns false
+				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+					// Bind the global event handler to the element
+					if ( elem.addEventListener ) {
+						elem.addEventListener( type, eventHandle, false );
+
+					} else if ( elem.attachEvent ) {
+						elem.attachEvent( "on" + type, eventHandle );
+					}
+				}
+			}
+
+			if ( special.add ) {
+				special.add.call( elem, handleObj );
+
+				if ( !handleObj.handler.guid ) {
+					handleObj.handler.guid = handler.guid;
+				}
+			}
+
+			// Add to the element's handler list, delegates in front
+			if ( selector ) {
+				handlers.splice( handlers.delegateCount++, 0, handleObj );
+			} else {
+				handlers.push( handleObj );
+			}
+
+			// Keep track of which events have ever been used, for event optimization
+			jQuery.event.global[ type ] = true;
+		}
+
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function( elem, types, handler, selector, mappedTypes ) {
+
+		var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+			t, tns, type, origType, namespaces, origCount,
+			j, events, special, handle, eventType, handleObj;
+
+		if ( !elemData || !(events = elemData.events) ) {
+			return;
+		}
+
+		// Once for each type.namespace in types; type may be omitted
+		types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+		for ( t = 0; t < types.length; t++ ) {
+			tns = rtypenamespace.exec( types[t] ) || [];
+			type = origType = tns[1];
+			namespaces = tns[2];
+
+			// Unbind all events (on this namespace, if provided) for the element
+			if ( !type ) {
+				for ( type in events ) {
+					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+				}
+				continue;
+			}
+
+			special = jQuery.event.special[ type ] || {};
+			type = ( selector? special.delegateType : special.bindType ) || type;
+			eventType = events[ type ] || [];
+			origCount = eventType.length;
+			namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+
+			// Remove matching events
+			for ( j = 0; j < eventType.length; j++ ) {
+				handleObj = eventType[ j ];
+
+				if ( ( mappedTypes || origType === handleObj.origType ) &&
+					 ( !handler || handler.guid === handleObj.guid ) &&
+					 ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+					 ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+					eventType.splice( j--, 1 );
+
+					if ( handleObj.selector ) {
+						eventType.delegateCount--;
+					}
+					if ( special.remove ) {
+						special.remove.call( elem, handleObj );
+					}
+				}
+			}
+
+			// Remove generic event handler if we removed something and no more handlers exist
+			// (avoids potential for endless recursion during removal of special event handlers)
+			if ( eventType.length === 0 && origCount !== eventType.length ) {
+				if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+					jQuery.removeEvent( elem, type, elemData.handle );
+				}
+
+				delete events[ type ];
+			}
+		}
+
+		// Remove the expando if it's no longer used
+		if ( jQuery.isEmptyObject( events ) ) {
+			handle = elemData.handle;
+			if ( handle ) {
+				handle.elem = null;
+			}
+
+			// removeData also checks for emptiness and clears the expando if empty
+			// so use it instead of delete
+			jQuery.removeData( elem, [ "events", "handle" ], true );
+		}
+	},
+
+	// Events that are safe to short-circuit if no handlers are attached.
+	// Native DOM events should not be added, they may have inline handlers.
+	customEvent: {
+		"getData": true,
+		"setData": true,
+		"changeData": true
+	},
+
+	trigger: function( event, data, elem, onlyHandlers ) {
+		// Don't do events on text and comment nodes
+		if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+			return;
+		}
+
+		// Event object or event type
+		var type = event.type || event,
+			namespaces = [],
+			cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+
+		// focus/blur morphs to focusin/out; ensure we're not firing them right now
+		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+			return;
+		}
+
+		if ( type.indexOf( "!" ) >= 0 ) {
+			// Exclusive events trigger only for the exact event (no namespaces)
+			type = type.slice(0, -1);
+			exclusive = true;
+		}
+
+		if ( type.indexOf( "." ) >= 0 ) {
+			// Namespaced trigger; create a regexp to match event type in handle()
+			namespaces = type.split(".");
+			type = namespaces.shift();
+			namespaces.sort();
+		}
+
+		if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+			// No jQuery handlers for this event type, and it can't have inline handlers
+			return;
+		}
+
+		// Caller can pass in an Event, Object, or just an event type string
+		event = typeof event === "object" ?
+			// jQuery.Event object
+			event[ jQuery.expando ] ? event :
+			// Object literal
+			new jQuery.Event( type, event ) :
+			// Just the event type (string)
+			new jQuery.Event( type );
+
+		event.type = type;
+		event.isTrigger = true;
+		event.exclusive = exclusive;
+		event.namespace = namespaces.join( "." );
+		event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+		ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+		// Handle a global trigger
+		if ( !elem ) {
+
+			// TODO: Stop taunting the data cache; remove global events and always attach to document
+			cache = jQuery.cache;
+			for ( i in cache ) {
+				if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+					jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+				}
+			}
+			return;
+		}
+
+		// Clean up the event in case it is being reused
+		event.result = undefined;
+		if ( !event.target ) {
+			event.target = elem;
+		}
+
+		// Clone any incoming data and prepend the event, creating the handler arg list
+		data = data != null ? jQuery.makeArray( data ) : [];
+		data.unshift( event );
+
+		// Allow special events to draw outside the lines
+		special = jQuery.event.special[ type ] || {};
+		if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+			return;
+		}
+
+		// Determine event propagation path in advance, per W3C events spec (#9951)
+		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+		eventPath = [[ elem, special.bindType || type ]];
+		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+			bubbleType = special.delegateType || type;
+			cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+			old = null;
+			for ( ; cur; cur = cur.parentNode ) {
+				eventPath.push([ cur, bubbleType ]);
+				old = cur;
+			}
+
+			// Only add window if we got to document (e.g., not plain obj or detached DOM)
+			if ( old && old === elem.ownerDocument ) {
+				eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+			}
+		}
+
+		// Fire handlers on the event path
+		for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+			cur = eventPath[i][0];
+			event.type = eventPath[i][1];
+
+			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+			if ( handle ) {
+				handle.apply( cur, data );
+			}
+			// Note that this is a bare JS function and not a jQuery handler
+			handle = ontype && cur[ ontype ];
+			if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+				event.preventDefault();
+			}
+		}
+		event.type = type;
+
+		// If nobody prevented the default action, do it now
+		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+			if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+				!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+				// Call a native DOM method on the target with the same name name as the event.
+				// Can't use an .isFunction() check here because IE6/7 fails that test.
+				// Don't do default actions on window, that's where global variables be (#6170)
+				// IE<9 dies on focus/blur to hidden element (#1486)
+				if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+					// Don't re-trigger an onFOO event when we call its FOO() method
+					old = elem[ ontype ];
+
+					if ( old ) {
+						elem[ ontype ] = null;
+					}
+
+					// Prevent re-triggering of the same event, since we already bubbled it above
+					jQuery.event.triggered = type;
+					elem[ type ]();
+					jQuery.event.triggered = undefined;
+
+					if ( old ) {
+						elem[ ontype ] = old;
+					}
+				}
+			}
+		}
+
+		return event.result;
+	},
+
+	dispatch: function( event ) {
+
+		// Make a writable jQuery.Event from the native event object
+		event = jQuery.event.fix( event || window.event );
+
+		var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+			delegateCount = handlers.delegateCount,
+			args = [].slice.call( arguments, 0 ),
+			run_all = !event.exclusive && !event.namespace,
+			handlerQueue = [],
+			i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+
+		// Use the fix-ed jQuery.Event rather than the (read-only) native event
+		args[0] = event;
+		event.delegateTarget = this;
+
+		// Determine handlers that should run if there are delegated events
+		// Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861)
+		if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) {
+
+			// Pregenerate a single jQuery object for reuse with .is()
+			jqcur = jQuery(this);
+			jqcur.context = this.ownerDocument || this;
+
+			for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+				selMatch = {};
+				matches = [];
+				jqcur[0] = cur;
+				for ( i = 0; i < delegateCount; i++ ) {
+					handleObj = handlers[ i ];
+					sel = handleObj.selector;
+
+					if ( selMatch[ sel ] === undefined ) {
+						selMatch[ sel ] = (
+							handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
+						);
+					}
+					if ( selMatch[ sel ] ) {
+						matches.push( handleObj );
+					}
+				}
+				if ( matches.length ) {
+					handlerQueue.push({ elem: cur, matches: matches });
+				}
+			}
+		}
+
+		// Add the remaining (directly-bound) handlers
+		if ( handlers.length > delegateCount ) {
+			handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+		}
+
+		// Run delegates first; they may want to stop propagation beneath us
+		for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+			matched = handlerQueue[ i ];
+			event.currentTarget = matched.elem;
+
+			for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+				handleObj = matched.matches[ j ];
+
+				// Triggered event must either 1) be non-exclusive and have no namespace, or
+				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+				if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+					event.data = handleObj.data;
+					event.handleObj = handleObj;
+
+					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+							.apply( matched.elem, args );
+
+					if ( ret !== undefined ) {
+						event.result = ret;
+						if ( ret === false ) {
+							event.preventDefault();
+							event.stopPropagation();
+						}
+					}
+				}
+			}
+		}
+
+		return event.result;
+	},
+
+	// Includes some event props shared by KeyEvent and MouseEvent
+	// *** attrChange attrName relatedNode srcElement  are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+	props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+	fixHooks: {},
+
+	keyHooks: {
+		props: "char charCode key keyCode".split(" "),
+		filter: function( event, original ) {
+
+			// Add which for key events
+			if ( event.which == null ) {
+				event.which = original.charCode != null ? original.charCode : original.keyCode;
+			}
+
+			return event;
+		}
+	},
+
+	mouseHooks: {
+		props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+		filter: function( event, original ) {
+			var eventDoc, doc, body,
+				button = original.button,
+				fromElement = original.fromElement;
+
+			// Calculate pageX/Y if missing and clientX/Y available
+			if ( event.pageX == null && original.clientX != null ) {
+				eventDoc = event.target.ownerDocument || document;
+				doc = eventDoc.documentElement;
+				body = eventDoc.body;
+
+				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
+			}
+
+			// Add relatedTarget, if necessary
+			if ( !event.relatedTarget && fromElement ) {
+				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+			}
+
+			// Add which for click: 1 === left; 2 === middle; 3 === right
+			// Note: button is not normalized, so don't use it
+			if ( !event.which && button !== undefined ) {
+				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+			}
+
+			return event;
+		}
+	},
+
+	fix: function( event ) {
+		if ( event[ jQuery.expando ] ) {
+			return event;
+		}
+
+		// Create a writable copy of the event object and normalize some properties
+		var i, prop,
+			originalEvent = event,
+			fixHook = jQuery.event.fixHooks[ event.type ] || {},
+			copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+		event = jQuery.Event( originalEvent );
+
+		for ( i = copy.length; i; ) {
+			prop = copy[ --i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+		if ( !event.target ) {
+			event.target = originalEvent.srcElement || document;
+		}
+
+		// Target should not be a text node (#504, Safari)
+		if ( event.target.nodeType === 3 ) {
+			event.target = event.target.parentNode;
+		}
+
+		// For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
+		if ( event.metaKey === undefined ) {
+			event.metaKey = event.ctrlKey;
+		}
+
+		return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+	},
+
+	special: {
+		ready: {
+			// Make sure the ready event is setup
+			setup: jQuery.bindReady
+		},
+
+		load: {
+			// Prevent triggered image.load events from bubbling to window.load
+			noBubble: true
+		},
+
+		focus: {
+			delegateType: "focusin"
+		},
+		blur: {
+			delegateType: "focusout"
+		},
+
+		beforeunload: {
+			setup: function( data, namespaces, eventHandle ) {
+				// We only want to do this special case on windows
+				if ( jQuery.isWindow( this ) ) {
+					this.onbeforeunload = eventHandle;
+				}
+			},
+
+			teardown: function( namespaces, eventHandle ) {
+				if ( this.onbeforeunload === eventHandle ) {
+					this.onbeforeunload = null;
+				}
+			}
+		}
+	},
+
+	simulate: function( type, elem, event, bubble ) {
+		// Piggyback on a donor event to simulate a different one.
+		// Fake originalEvent to avoid donor's stopPropagation, but if the
+		// simulated event prevents default then we do the same on the donor.
+		var e = jQuery.extend(
+			new jQuery.Event(),
+			event,
+			{ type: type,
+				isSimulated: true,
+				originalEvent: {}
+			}
+		);
+		if ( bubble ) {
+			jQuery.event.trigger( e, null, elem );
+		} else {
+			jQuery.event.dispatch.call( elem, e );
+		}
+		if ( e.isDefaultPrevented() ) {
+			event.preventDefault();
+		}
+	}
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+	function( elem, type, handle ) {
+		if ( elem.removeEventListener ) {
+			elem.removeEventListener( type, handle, false );
+		}
+	} :
+	function( elem, type, handle ) {
+		if ( elem.detachEvent ) {
+			elem.detachEvent( "on" + type, handle );
+		}
+	};
+
+jQuery.Event = function( src, props ) {
+	// Allow instantiation without the 'new' keyword
+	if ( !(this instanceof jQuery.Event) ) {
+		return new jQuery.Event( src, props );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+			src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// Put explicitly provided properties onto the event object
+	if ( props ) {
+		jQuery.extend( this, props );
+	}
+
+	// Create a timestamp if incoming event doesn't have one
+	this.timeStamp = src && src.timeStamp || jQuery.now();
+
+	// Mark it as fixed
+	this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+	return false;
+}
+function returnTrue() {
+	return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	preventDefault: function() {
+		this.isDefaultPrevented = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+
+		// if preventDefault exists run it on the original event
+		if ( e.preventDefault ) {
+			e.preventDefault();
+
+		// otherwise set the returnValue property of the original event to false (IE)
+		} else {
+			e.returnValue = false;
+		}
+	},
+	stopPropagation: function() {
+		this.isPropagationStopped = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+		// if stopPropagation exists run it on the original event
+		if ( e.stopPropagation ) {
+			e.stopPropagation();
+		}
+		// otherwise set the cancelBubble property of the original event to true (IE)
+		e.cancelBubble = true;
+	},
+	stopImmediatePropagation: function() {
+		this.isImmediatePropagationStopped = returnTrue;
+		this.stopPropagation();
+	},
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+	mouseenter: "mouseover",
+	mouseleave: "mouseout"
+}, function( orig, fix ) {
+	jQuery.event.special[ orig ] = {
+		delegateType: fix,
+		bindType: fix,
+
+		handle: function( event ) {
+			var target = this,
+				related = event.relatedTarget,
+				handleObj = event.handleObj,
+				selector = handleObj.selector,
+				ret;
+
+			// For mousenter/leave call the handler if related is outside the target.
+			// NB: No relatedTarget if the mouse left/entered the browser window
+			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+				event.type = handleObj.origType;
+				ret = handleObj.handler.apply( this, arguments );
+				event.type = fix;
+			}
+			return ret;
+		}
+	};
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+	jQuery.event.special.submit = {
+		setup: function() {
+			// Only need this for delegated form submit events
+			if ( jQuery.nodeName( this, "form" ) ) {
+				return false;
+			}
+
+			// Lazy-add a submit handler when a descendant form may potentially be submitted
+			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+				// Node name check avoids a VML-related crash in IE (#9807)
+				var elem = e.target,
+					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+				if ( form && !form._submit_attached ) {
+					jQuery.event.add( form, "submit._submit", function( event ) {
+						// If form was submitted by the user, bubble the event up the tree
+						if ( this.parentNode && !event.isTrigger ) {
+							jQuery.event.simulate( "submit", this.parentNode, event, true );
+						}
+					});
+					form._submit_attached = true;
+				}
+			});
+			// return undefined since we don't need an event listener
+		},
+
+		teardown: function() {
+			// Only need this for delegated form submit events
+			if ( jQuery.nodeName( this, "form" ) ) {
+				return false;
+			}
+
+			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+			jQuery.event.remove( this, "._submit" );
+		}
+	};
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+	jQuery.event.special.change = {
+
+		setup: function() {
+
+			if ( rformElems.test( this.nodeName ) ) {
+				// IE doesn't fire change on a check/radio until blur; trigger it on click
+				// after a propertychange. Eat the blur-change in special.change.handle.
+				// This still fires onchange a second time for check/radio after blur.
+				if ( this.type === "checkbox" || this.type === "radio" ) {
+					jQuery.event.add( this, "propertychange._change", function( event ) {
+						if ( event.originalEvent.propertyName === "checked" ) {
+							this._just_changed = true;
+						}
+					});
+					jQuery.event.add( this, "click._change", function( event ) {
+						if ( this._just_changed && !event.isTrigger ) {
+							this._just_changed = false;
+							jQuery.event.simulate( "change", this, event, true );
+						}
+					});
+				}
+				return false;
+			}
+			// Delegated event; lazy-add a change handler on descendant inputs
+			jQuery.event.add( this, "beforeactivate._change", function( e ) {
+				var elem = e.target;
+
+				if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+					jQuery.event.add( elem, "change._change", function( event ) {
+						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+							jQuery.event.simulate( "change", this.parentNode, event, true );
+						}
+					});
+					elem._change_attached = true;
+				}
+			});
+		},
+
+		handle: function( event ) {
+			var elem = event.target;
+
+			// Swallow native change events from checkbox/radio, we already triggered them above
+			if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+				return event.handleObj.handler.apply( this, arguments );
+			}
+		},
+
+		teardown: function() {
+			jQuery.event.remove( this, "._change" );
+
+			return rformElems.test( this.nodeName );
+		}
+	};
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler while someone wants focusin/focusout
+		var attaches = 0,
+			handler = function( event ) {
+				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+			};
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				if ( attaches++ === 0 ) {
+					document.addEventListener( orig, handler, true );
+				}
+			},
+			teardown: function() {
+				if ( --attaches === 0 ) {
+					document.removeEventListener( orig, handler, true );
+				}
+			}
+		};
+	});
+}
+
+jQuery.fn.extend({
+
+	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+		var origFn, type;
+
+		// Types can be a map of types/handlers
+		if ( typeof types === "object" ) {
+			// ( types-Object, selector, data )
+			if ( typeof selector !== "string" ) {
+				// ( types-Object, data )
+				data = selector;
+				selector = undefined;
+			}
+			for ( type in types ) {
+				this.on( type, selector, data, types[ type ], one );
+			}
+			return this;
+		}
+
+		if ( data == null && fn == null ) {
+			// ( types, fn )
+			fn = selector;
+			data = selector = undefined;
+		} else if ( fn == null ) {
+			if ( typeof selector === "string" ) {
+				// ( types, selector, fn )
+				fn = data;
+				data = undefined;
+			} else {
+				// ( types, data, fn )
+				fn = data;
+				data = selector;
+				selector = undefined;
+			}
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		} else if ( !fn ) {
+			return this;
+		}
+
+		if ( one === 1 ) {
+			origFn = fn;
+			fn = function( event ) {
+				// Can use an empty set, since event contains the info
+				jQuery().off( event );
+				return origFn.apply( this, arguments );
+			};
+			// Use same guid so caller can remove using origFn
+			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+		}
+		return this.each( function() {
+			jQuery.event.add( this, types, fn, data, selector );
+		});
+	},
+	one: function( types, selector, data, fn ) {
+		return this.on.call( this, types, selector, data, fn, 1 );
+	},
+	off: function( types, selector, fn ) {
+		if ( types && types.preventDefault && types.handleObj ) {
+			// ( event )  dispatched jQuery.Event
+			var handleObj = types.handleObj;
+			jQuery( types.delegateTarget ).off(
+				handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type,
+				handleObj.selector,
+				handleObj.handler
+			);
+			return this;
+		}
+		if ( typeof types === "object" ) {
+			// ( types-object [, selector] )
+			for ( var type in types ) {
+				this.off( type, selector, types[ type ] );
+			}
+			return this;
+		}
+		if ( selector === false || typeof selector === "function" ) {
+			// ( types [, fn] )
+			fn = selector;
+			selector = undefined;
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		}
+		return this.each(function() {
+			jQuery.event.remove( this, types, fn, selector );
+		});
+	},
+
+	bind: function( types, data, fn ) {
+		return this.on( types, null, data, fn );
+	},
+	unbind: function( types, fn ) {
+		return this.off( types, null, fn );
+	},
+
+	live: function( types, data, fn ) {
+		jQuery( this.context ).on( types, this.selector, data, fn );
+		return this;
+	},
+	die: function( types, fn ) {
+		jQuery( this.context ).off( types, this.selector || "**", fn );
+		return this;
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.on( types, selector, data, fn );
+	},
+	undelegate: function( selector, types, fn ) {
+		// ( namespace ) or ( selector, types [, fn] )
+		return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+	},
+
+	trigger: function( type, data ) {
+		return this.each(function() {
+			jQuery.event.trigger( type, data, this );
+		});
+	},
+	triggerHandler: function( type, data ) {
+		if ( this[0] ) {
+			return jQuery.event.trigger( type, data, this[0], true );
+		}
+	},
+
+	toggle: function( fn ) {
+		// Save reference to arguments for access in closure
+		var args = arguments,
+			guid = fn.guid || jQuery.guid++,
+			i = 0,
+			toggler = function( event ) {
+				// Figure out which function to execute
+				var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+				jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+				// Make sure that clicks stop
+				event.preventDefault();
+
+				// and execute the function
+				return args[ lastToggle ].apply( this, arguments ) || false;
+			};
+
+		// link all the functions, so any of them can unbind this click handler
+		toggler.guid = guid;
+		while ( i < args.length ) {
+			args[ i++ ].guid = guid;
+		}
+
+		return this.click( toggler );
+	},
+
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	}
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+	// Handle event binding
+	jQuery.fn[ name ] = function( data, fn ) {
+		if ( fn == null ) {
+			fn = data;
+			data = null;
+		}
+
+		return arguments.length > 0 ?
+			this.on( name, null, data, fn ) :
+			this.trigger( name );
+	};
+
+	if ( jQuery.attrFn ) {
+		jQuery.attrFn[ name ] = true;
+	}
+
+	if ( rkeyEvent.test( name ) ) {
+		jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+	}
+
+	if ( rmouseEvent.test( name ) ) {
+		jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+	}
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ *  Copyright 2011, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+	expando = "sizcache" + (Math.random() + '').replace('.', ''),
+	done = 0,
+	toString = Object.prototype.toString,
+	hasDuplicate = false,
+	baseHasDuplicate = true,
+	rBackslash = /\\/g,
+	rReturn = /\r\n/g,
+	rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+//   Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+	baseHasDuplicate = false;
+	return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+	results = results || [];
+	context = context || document;
+
+	var origContext = context;
+
+	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+		return [];
+	}
+	
+	if ( !selector || typeof selector !== "string" ) {
+		return results;
+	}
+
+	var m, set, checkSet, extra, ret, cur, pop, i,
+		prune = true,
+		contextXML = Sizzle.isXML( context ),
+		parts = [],
+		soFar = selector;
+	
+	// Reset the position of the chunker regexp (start from head)
+	do {
+		chunker.exec( "" );
+		m = chunker.exec( soFar );
+
+		if ( m ) {
+			soFar = m[3];
+		
+			parts.push( m[1] );
+		
+			if ( m[2] ) {
+				extra = m[3];
+				break;
+			}
+		}
+	} while ( m );
+
+	if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+			set = posProcess( parts[0] + parts[1], context, seed );
+
+		} else {
+			set = Expr.relative[ parts[0] ] ?
+				[ context ] :
+				Sizzle( parts.shift(), context );
+
+			while ( parts.length ) {
+				selector = parts.shift();
+
+				if ( Expr.relative[ selector ] ) {
+					selector += parts.shift();
+				}
+				
+				set = posProcess( selector, set, seed );
+			}
+		}
+
+	} else {
+		// Take a shortcut and set the context if the root selector is an ID
+		// (but not if it'll be faster if the inner selector is an ID)
+		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+			ret = Sizzle.find( parts.shift(), context, contextXML );
+			context = ret.expr ?
+				Sizzle.filter( ret.expr, ret.set )[0] :
+				ret.set[0];
+		}
+
+		if ( context ) {
+			ret = seed ?
+				{ expr: parts.pop(), set: makeArray(seed) } :
+				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+			set = ret.expr ?
+				Sizzle.filter( ret.expr, ret.set ) :
+				ret.set;
+
+			if ( parts.length > 0 ) {
+				checkSet = makeArray( set );
+
+			} else {
+				prune = false;
+			}
+
+			while ( parts.length ) {
+				cur = parts.pop();
+				pop = cur;
+
+				if ( !Expr.relative[ cur ] ) {
+					cur = "";
+				} else {
+					pop = parts.pop();
+				}
+
+				if ( pop == null ) {
+					pop = context;
+				}
+
+				Expr.relative[ cur ]( checkSet, pop, contextXML );
+			}
+
+		} else {
+			checkSet = parts = [];
+		}
+	}
+
+	if ( !checkSet ) {
+		checkSet = set;
+	}
+
+	if ( !checkSet ) {
+		Sizzle.error( cur || selector );
+	}
+
+	if ( toString.call(checkSet) === "[object Array]" ) {
+		if ( !prune ) {
+			results.push.apply( results, checkSet );
+
+		} else if ( context && context.nodeType === 1 ) {
+			for ( i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+					results.push( set[i] );
+				}
+			}
+
+		} else {
+			for ( i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+					results.push( set[i] );
+				}
+			}
+		}
+
+	} else {
+		makeArray( checkSet, results );
+	}
+
+	if ( extra ) {
+		Sizzle( extra, origContext, results, seed );
+		Sizzle.uniqueSort( results );
+	}
+
+	return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+	if ( sortOrder ) {
+		hasDuplicate = baseHasDuplicate;
+		results.sort( sortOrder );
+
+		if ( hasDuplicate ) {
+			for ( var i = 1; i < results.length; i++ ) {
+				if ( results[i] === results[ i - 1 ] ) {
+					results.splice( i--, 1 );
+				}
+			}
+		}
+	}
+
+	return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+	return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+	return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+	var set, i, len, match, type, left;
+
+	if ( !expr ) {
+		return [];
+	}
+
+	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+		type = Expr.order[i];
+		
+		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+			left = match[1];
+			match.splice( 1, 1 );
+
+			if ( left.substr( left.length - 1 ) !== "\\" ) {
+				match[1] = (match[1] || "").replace( rBackslash, "" );
+				set = Expr.find[ type ]( match, context, isXML );
+
+				if ( set != null ) {
+					expr = expr.replace( Expr.match[ type ], "" );
+					break;
+				}
+			}
+		}
+	}
+
+	if ( !set ) {
+		set = typeof context.getElementsByTagName !== "undefined" ?
+			context.getElementsByTagName( "*" ) :
+			[];
+	}
+
+	return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+	var match, anyFound,
+		type, found, item, filter, left,
+		i, pass,
+		old = expr,
+		result = [],
+		curLoop = set,
+		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+	while ( expr && set.length ) {
+		for ( type in Expr.filter ) {
+			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+				filter = Expr.filter[ type ];
+				left = match[1];
+
+				anyFound = false;
+
+				match.splice(1,1);
+
+				if ( left.substr( left.length - 1 ) === "\\" ) {
+					continue;
+				}
+
+				if ( curLoop === result ) {
+					result = [];
+				}
+
+				if ( Expr.preFilter[ type ] ) {
+					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+					if ( !match ) {
+						anyFound = found = true;
+
+					} else if ( match === true ) {
+						continue;
+					}
+				}
+
+				if ( match ) {
+					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+						if ( item ) {
+							found = filter( item, match, i, curLoop );
+							pass = not ^ found;
+
+							if ( inplace && found != null ) {
+								if ( pass ) {
+									anyFound = true;
+
+								} else {
+									curLoop[i] = false;
+								}
+
+							} else if ( pass ) {
+								result.push( item );
+								anyFound = true;
+							}
+						}
+					}
+				}
+
+				if ( found !== undefined ) {
+					if ( !inplace ) {
+						curLoop = result;
+					}
+
+					expr = expr.replace( Expr.match[ type ], "" );
+
+					if ( !anyFound ) {
+						return [];
+					}
+
+					break;
+				}
+			}
+		}
+
+		// Improper expression
+		if ( expr === old ) {
+			if ( anyFound == null ) {
+				Sizzle.error( expr );
+
+			} else {
+				break;
+			}
+		}
+
+		old = expr;
+	}
+
+	return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+	throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+    var i, node,
+		nodeType = elem.nodeType,
+		ret = "";
+
+	if ( nodeType ) {
+		if ( nodeType === 1 || nodeType === 9 ) {
+			// Use textContent || innerText for elements
+			if ( typeof elem.textContent === 'string' ) {
+				return elem.textContent;
+			} else if ( typeof elem.innerText === 'string' ) {
+				// Replace IE's carriage returns
+				return elem.innerText.replace( rReturn, '' );
+			} else {
+				// Traverse it's children
+				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+					ret += getText( elem );
+				}
+			}
+		} else if ( nodeType === 3 || nodeType === 4 ) {
+			return elem.nodeValue;
+		}
+	} else {
+
+		// If no nodeType, this is expected to be an array
+		for ( i = 0; (node = elem[i]); i++ ) {
+			// Do not traverse comment nodes
+			if ( node.nodeType !== 8 ) {
+				ret += getText( node );
+			}
+		}
+	}
+	return ret;
+};
+
+var Expr = Sizzle.selectors = {
+	order: [ "ID", "NAME", "TAG" ],
+
+	match: {
+		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+	},
+
+	leftMatch: {},
+
+	attrMap: {
+		"class": "className",
+		"for": "htmlFor"
+	},
+
+	attrHandle: {
+		href: function( elem ) {
+			return elem.getAttribute( "href" );
+		},
+		type: function( elem ) {
+			return elem.getAttribute( "type" );
+		}
+	},
+
+	relative: {
+		"+": function(checkSet, part){
+			var isPartStr = typeof part === "string",
+				isTag = isPartStr && !rNonWord.test( part ),
+				isPartStrNotTag = isPartStr && !isTag;
+
+			if ( isTag ) {
+				part = part.toLowerCase();
+			}
+
+			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+				if ( (elem = checkSet[i]) ) {
+					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+						elem || false :
+						elem === part;
+				}
+			}
+
+			if ( isPartStrNotTag ) {
+				Sizzle.filter( part, checkSet, true );
+			}
+		},
+
+		">": function( checkSet, part ) {
+			var elem,
+				isPartStr = typeof part === "string",
+				i = 0,
+				l = checkSet.length;
+
+			if ( isPartStr && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+
+				for ( ; i < l; i++ ) {
+					elem = checkSet[i];
+
+					if ( elem ) {
+						var parent = elem.parentNode;
+						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+					}
+				}
+
+			} else {
+				for ( ; i < l; i++ ) {
+					elem = checkSet[i];
+
+					if ( elem ) {
+						checkSet[i] = isPartStr ?
+							elem.parentNode :
+							elem.parentNode === part;
+					}
+				}
+
+				if ( isPartStr ) {
+					Sizzle.filter( part, checkSet, true );
+				}
+			}
+		},
+
+		"": function(checkSet, part, isXML){
+			var nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+				nodeCheck = part;
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+		},
+
+		"~": function( checkSet, part, isXML ) {
+			var nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+				nodeCheck = part;
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+		}
+	},
+
+	find: {
+		ID: function( match, context, isXML ) {
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+				// Check parentNode to catch when Blackberry 4.6 returns
+				// nodes that are no longer in the document #6963
+				return m && m.parentNode ? [m] : [];
+			}
+		},
+
+		NAME: function( match, context ) {
+			if ( typeof context.getElementsByName !== "undefined" ) {
+				var ret = [],
+					results = context.getElementsByName( match[1] );
+
+				for ( var i = 0, l = results.length; i < l; i++ ) {
+					if ( results[i].getAttribute("name") === match[1] ) {
+						ret.push( results[i] );
+					}
+				}
+
+				return ret.length === 0 ? null : ret;
+			}
+		},
+
+		TAG: function( match, context ) {
+			if ( typeof context.getElementsByTagName !== "undefined" ) {
+				return context.getElementsByTagName( match[1] );
+			}
+		}
+	},
+	preFilter: {
+		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+			match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+			if ( isXML ) {
+				return match;
+			}
+
+			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+				if ( elem ) {
+					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+						if ( !inplace ) {
+							result.push( elem );
+						}
+
+					} else if ( inplace ) {
+						curLoop[i] = false;
+					}
+				}
+			}
+
+			return false;
+		},
+
+		ID: function( match ) {
+			return match[1].replace( rBackslash, "" );
+		},
+
+		TAG: function( match, curLoop ) {
+			return match[1].replace( rBackslash, "" ).toLowerCase();
+		},
+
+		CHILD: function( match ) {
+			if ( match[1] === "nth" ) {
+				if ( !match[2] ) {
+					Sizzle.error( match[0] );
+				}
+
+				match[2] = match[2].replace(/^\+|\s*/g, '');
+
+				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+				// calculate the numbers (first)n+(last) including if they are negative
+				match[2] = (test[1] + (test[2] || 1)) - 0;
+				match[3] = test[3] - 0;
+			}
+			else if ( match[2] ) {
+				Sizzle.error( match[0] );
+			}
+
+			// TODO: Move to normal caching system
+			match[0] = done++;
+
+			return match;
+		},
+
+		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+			var name = match[1] = match[1].replace( rBackslash, "" );
+			
+			if ( !isXML && Expr.attrMap[name] ) {
+				match[1] = Expr.attrMap[name];
+			}
+
+			// Handle if an un-quoted value was used
+			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+			if ( match[2] === "~=" ) {
+				match[4] = " " + match[4] + " ";
+			}
+
+			return match;
+		},
+
+		PSEUDO: function( match, curLoop, inplace, result, not ) {
+			if ( match[1] === "not" ) {
+				// If we're dealing with a complex expression, or a simple one
+				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+					match[3] = Sizzle(match[3], null, null, curLoop);
+
+				} else {
+					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+					if ( !inplace ) {
+						result.push.apply( result, ret );
+					}
+
+					return false;
+				}
+
+			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+				return true;
+			}
+			
+			return match;
+		},
+
+		POS: function( match ) {
+			match.unshift( true );
+
+			return match;
+		}
+	},
+	
+	filters: {
+		enabled: function( elem ) {
+			return elem.disabled === false && elem.type !== "hidden";
+		},
+
+		disabled: function( elem ) {
+			return elem.disabled === true;
+		},
+
+		checked: function( elem ) {
+			return elem.checked === true;
+		},
+		
+		selected: function( elem ) {
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			if ( elem.parentNode ) {
+				elem.parentNode.selectedIndex;
+			}
+			
+			return elem.selected === true;
+		},
+
+		parent: function( elem ) {
+			return !!elem.firstChild;
+		},
+
+		empty: function( elem ) {
+			return !elem.firstChild;
+		},
+
+		has: function( elem, i, match ) {
+			return !!Sizzle( match[3], elem ).length;
+		},
+
+		header: function( elem ) {
+			return (/h\d/i).test( elem.nodeName );
+		},
+
+		text: function( elem ) {
+			var attr = elem.getAttribute( "type" ), type = elem.type;
+			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 
+			// use getAttribute instead to test this case
+			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+		},
+
+		radio: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+		},
+
+		checkbox: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+		},
+
+		file: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+		},
+
+		password: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+		},
+
+		submit: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && "submit" === elem.type;
+		},
+
+		image: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+		},
+
+		reset: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && "reset" === elem.type;
+		},
+
+		button: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && "button" === elem.type || name === "button";
+		},
+
+		input: function( elem ) {
+			return (/input|select|textarea|button/i).test( elem.nodeName );
+		},
+
+		focus: function( elem ) {
+			return elem === elem.ownerDocument.activeElement;
+		}
+	},
+	setFilters: {
+		first: function( elem, i ) {
+			return i === 0;
+		},
+
+		last: function( elem, i, match, array ) {
+			return i === array.length - 1;
+		},
+
+		even: function( elem, i ) {
+			return i % 2 === 0;
+		},
+
+		odd: function( elem, i ) {
+			return i % 2 === 1;
+		},
+
+		lt: function( elem, i, match ) {
+			return i < match[3] - 0;
+		},
+
+		gt: function( elem, i, match ) {
+			return i > match[3] - 0;
+		},
+
+		nth: function( elem, i, match ) {
+			return match[3] - 0 === i;
+		},
+
+		eq: function( elem, i, match ) {
+			return match[3] - 0 === i;
+		}
+	},
+	filter: {
+		PSEUDO: function( elem, match, i, array ) {
+			var name = match[1],
+				filter = Expr.filters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+
+			} else if ( name === "contains" ) {
+				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+			} else if ( name === "not" ) {
+				var not = match[3];
+
+				for ( var j = 0, l = not.length; j < l; j++ ) {
+					if ( not[j] === elem ) {
+						return false;
+					}
+				}
+
+				return true;
+
+			} else {
+				Sizzle.error( name );
+			}
+		},
+
+		CHILD: function( elem, match ) {
+			var first, last,
+				doneName, parent, cache,
+				count, diff,
+				type = match[1],
+				node = elem;
+
+			switch ( type ) {
+				case "only":
+				case "first":
+					while ( (node = node.previousSibling) )	 {
+						if ( node.nodeType === 1 ) { 
+							return false; 
+						}
+					}
+
+					if ( type === "first" ) { 
+						return true; 
+					}
+
+					node = elem;
+
+				case "last":
+					while ( (node = node.nextSibling) )	 {
+						if ( node.nodeType === 1 ) { 
+							return false; 
+						}
+					}
+
+					return true;
+
+				case "nth":
+					first = match[2];
+					last = match[3];
+
+					if ( first === 1 && last === 0 ) {
+						return true;
+					}
+					
+					doneName = match[0];
+					parent = elem.parentNode;
+	
+					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+						count = 0;
+						
+						for ( node = parent.firstChild; node; node = node.nextSibling ) {
+							if ( node.nodeType === 1 ) {
+								node.nodeIndex = ++count;
+							}
+						} 
+
+						parent[ expando ] = doneName;
+					}
+					
+					diff = elem.nodeIndex - last;
+
+					if ( first === 0 ) {
+						return diff === 0;
+
+					} else {
+						return ( diff % first === 0 && diff / first >= 0 );
+					}
+			}
+		},
+
+		ID: function( elem, match ) {
+			return elem.nodeType === 1 && elem.getAttribute("id") === match;
+		},
+
+		TAG: function( elem, match ) {
+			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+		},
+		
+		CLASS: function( elem, match ) {
+			return (" " + (elem.className || elem.getAttribute("class")) + " ")
+				.indexOf( match ) > -1;
+		},
+
+		ATTR: function( elem, match ) {
+			var name = match[1],
+				result = Sizzle.attr ?
+					Sizzle.attr( elem, name ) :
+					Expr.attrHandle[ name ] ?
+					Expr.attrHandle[ name ]( elem ) :
+					elem[ name ] != null ?
+						elem[ name ] :
+						elem.getAttribute( name ),
+				value = result + "",
+				type = match[2],
+				check = match[4];
+
+			return result == null ?
+				type === "!=" :
+				!type && Sizzle.attr ?
+				result != null :
+				type === "=" ?
+				value === check :
+				type === "*=" ?
+				value.indexOf(check) >= 0 :
+				type === "~=" ?
+				(" " + value + " ").indexOf(check) >= 0 :
+				!check ?
+				value && result !== false :
+				type === "!=" ?
+				value !== check :
+				type === "^=" ?
+				value.indexOf(check) === 0 :
+				type === "$=" ?
+				value.substr(value.length - check.length) === check :
+				type === "|=" ?
+				value === check || value.substr(0, check.length + 1) === check + "-" :
+				false;
+		},
+
+		POS: function( elem, match, i, array ) {
+			var name = match[2],
+				filter = Expr.setFilters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+			}
+		}
+	}
+};
+
+var origPOS = Expr.match.POS,
+	fescape = function(all, num){
+		return "\\" + (num - 0 + 1);
+	};
+
+for ( var type in Expr.match ) {
+	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function( array, results ) {
+	array = Array.prototype.slice.call( array, 0 );
+
+	if ( results ) {
+		results.push.apply( results, array );
+		return results;
+	}
+	
+	return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+	makeArray = function( array, results ) {
+		var i = 0,
+			ret = results || [];
+
+		if ( toString.call(array) === "[object Array]" ) {
+			Array.prototype.push.apply( ret, array );
+
+		} else {
+			if ( typeof array.length === "number" ) {
+				for ( var l = array.length; i < l; i++ ) {
+					ret.push( array[i] );
+				}
+
+			} else {
+				for ( ; array[i]; i++ ) {
+					ret.push( array[i] );
+				}
+			}
+		}
+
+		return ret;
+	};
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+			return a.compareDocumentPosition ? -1 : 1;
+		}
+
+		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+	};
+
+} else {
+	sortOrder = function( a, b ) {
+		// The nodes are identical, we can exit early
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+
+		// Fallback to using sourceIndex (in IE) if it's available on both nodes
+		} else if ( a.sourceIndex && b.sourceIndex ) {
+			return a.sourceIndex - b.sourceIndex;
+		}
+
+		var al, bl,
+			ap = [],
+			bp = [],
+			aup = a.parentNode,
+			bup = b.parentNode,
+			cur = aup;
+
+		// If the nodes are siblings (or identical) we can do a quick check
+		if ( aup === bup ) {
+			return siblingCheck( a, b );
+
+		// If no parents were found then the nodes are disconnected
+		} else if ( !aup ) {
+			return -1;
+
+		} else if ( !bup ) {
+			return 1;
+		}
+
+		// Otherwise they're somewhere else in the tree so we need
+		// to build up a full list of the parentNodes for comparison
+		while ( cur ) {
+			ap.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		cur = bup;
+
+		while ( cur ) {
+			bp.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		al = ap.length;
+		bl = bp.length;
+
+		// Start walking down the tree looking for a discrepancy
+		for ( var i = 0; i < al && i < bl; i++ ) {
+			if ( ap[i] !== bp[i] ) {
+				return siblingCheck( ap[i], bp[i] );
+			}
+		}
+
+		// We ended someplace up the tree so do a sibling check
+		return i === al ?
+			siblingCheck( a, bp[i], -1 ) :
+			siblingCheck( ap[i], b, 1 );
+	};
+
+	siblingCheck = function( a, b, ret ) {
+		if ( a === b ) {
+			return ret;
+		}
+
+		var cur = a.nextSibling;
+
+		while ( cur ) {
+			if ( cur === b ) {
+				return -1;
+			}
+
+			cur = cur.nextSibling;
+		}
+
+		return 1;
+	};
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+	// We're going to inject a fake input element with a specified name
+	var form = document.createElement("div"),
+		id = "script" + (new Date()).getTime(),
+		root = document.documentElement;
+
+	form.innerHTML = "<a name='" + id + "'/>";
+
+	// Inject it into the root element, check its status, and remove it quickly
+	root.insertBefore( form, root.firstChild );
+
+	// The workaround has to do additional checks after a getElementById
+	// Which slows things down for other browsers (hence the branching)
+	if ( document.getElementById( id ) ) {
+		Expr.find.ID = function( match, context, isXML ) {
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+
+				return m ?
+					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+						[m] :
+						undefined :
+					[];
+			}
+		};
+
+		Expr.filter.ID = function( elem, match ) {
+			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+			return elem.nodeType === 1 && node && node.nodeValue === match;
+		};
+	}
+
+	root.removeChild( form );
+
+	// release memory in IE
+	root = form = null;
+})();
+
+(function(){
+	// Check to see if the browser returns only elements
+	// when doing getElementsByTagName("*")
+
+	// Create a fake element
+	var div = document.createElement("div");
+	div.appendChild( document.createComment("") );
+
+	// Make sure no comments are found
+	if ( div.getElementsByTagName("*").length > 0 ) {
+		Expr.find.TAG = function( match, context ) {
+			var results = context.getElementsByTagName( match[1] );
+
+			// Filter out possible comments
+			if ( match[1] === "*" ) {
+				var tmp = [];
+
+				for ( var i = 0; results[i]; i++ ) {
+					if ( results[i].nodeType === 1 ) {
+						tmp.push( results[i] );
+					}
+				}
+
+				results = tmp;
+			}
+
+			return results;
+		};
+	}
+
+	// Check to see if an attribute returns normalized href attributes
+	div.innerHTML = "<a href='#'></a>";
+
+	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+			div.firstChild.getAttribute("href") !== "#" ) {
+
+		Expr.attrHandle.href = function( elem ) {
+			return elem.getAttribute( "href", 2 );
+		};
+	}
+
+	// release memory in IE
+	div = null;
+})();
+
+if ( document.querySelectorAll ) {
+	(function(){
+		var oldSizzle = Sizzle,
+			div = document.createElement("div"),
+			id = "__sizzle__";
+
+		div.innerHTML = "<p class='TEST'></p>";
+
+		// Safari can't handle uppercase or unicode characters when
+		// in quirks mode.
+		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+			return;
+		}
+	
+		Sizzle = function( query, context, extra, seed ) {
+			context = context || document;
+
+			// Only use querySelectorAll on non-XML documents
+			// (ID selectors don't work in non-HTML documents)
+			if ( !seed && !Sizzle.isXML(context) ) {
+				// See if we find a selector to speed up
+				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+				
+				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+					// Speed-up: Sizzle("TAG")
+					if ( match[1] ) {
+						return makeArray( context.getElementsByTagName( query ), extra );
+					
+					// Speed-up: Sizzle(".CLASS")
+					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+						return makeArray( context.getElementsByClassName( match[2] ), extra );
+					}
+				}
+				
+				if ( context.nodeType === 9 ) {
+					// Speed-up: Sizzle("body")
+					// The body element only exists once, optimize finding it
+					if ( query === "body" && context.body ) {
+						return makeArray( [ context.body ], extra );
+						
+					// Speed-up: Sizzle("#ID")
+					} else if ( match && match[3] ) {
+						var elem = context.getElementById( match[3] );
+
+						// Check parentNode to catch when Blackberry 4.6 returns
+						// nodes that are no longer in the document #6963
+						if ( elem && elem.parentNode ) {
+							// Handle the case where IE and Opera return items
+							// by name instead of ID
+							if ( elem.id === match[3] ) {
+								return makeArray( [ elem ], extra );
+							}
+							
+						} else {
+							return makeArray( [], extra );
+						}
+					}
+					
+					try {
+						return makeArray( context.querySelectorAll(query), extra );
+					} catch(qsaError) {}
+
+				// qSA works strangely on Element-rooted queries
+				// We can work around this by specifying an extra ID on the root
+				// and working up from there (Thanks to Andrew Dupont for the technique)
+				// IE 8 doesn't work on object elements
+				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+					var oldContext = context,
+						old = context.getAttribute( "id" ),
+						nid = old || id,
+						hasParent = context.parentNode,
+						relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+					if ( !old ) {
+						context.setAttribute( "id", nid );
+					} else {
+						nid = nid.replace( /'/g, "\\$&" );
+					}
+					if ( relativeHierarchySelector && hasParent ) {
+						context = context.parentNode;
+					}
+
+					try {
+						if ( !relativeHierarchySelector || hasParent ) {
+							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+						}
+
+					} catch(pseudoError) {
+					} finally {
+						if ( !old ) {
+							oldContext.removeAttribute( "id" );
+						}
+					}
+				}
+			}
+		
+			return oldSizzle(query, context, extra, seed);
+		};
+
+		for ( var prop in oldSizzle ) {
+			Sizzle[ prop ] = oldSizzle[ prop ];
+		}
+
+		// release memory in IE
+		div = null;
+	})();
+}
+
+(function(){
+	var html = document.documentElement,
+		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+	if ( matches ) {
+		// Check to see if it's possible to do matchesSelector
+		// on a disconnected node (IE 9 fails this)
+		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+			pseudoWorks = false;
+
+		try {
+			// This should fail with an exception
+			// Gecko does not error, returns false instead
+			matches.call( document.documentElement, "[test!='']:sizzle" );
+	
+		} catch( pseudoError ) {
+			pseudoWorks = true;
+		}
+
+		Sizzle.matchesSelector = function( node, expr ) {
+			// Make sure that attribute selectors are quoted
+			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+			if ( !Sizzle.isXML( node ) ) {
+				try { 
+					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+						var ret = matches.call( node, expr );
+
+						// IE 9's matchesSelector returns false on disconnected nodes
+						if ( ret || !disconnectedMatch ||
+								// As well, disconnected nodes are said to be in a document
+								// fragment in IE 9, so check for that
+								node.document && node.document.nodeType !== 11 ) {
+							return ret;
+						}
+					}
+				} catch(e) {}
+			}
+
+			return Sizzle(expr, null, null, [node]).length > 0;
+		};
+	}
+})();
+
+(function(){
+	var div = document.createElement("div");
+
+	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+	// Opera can't find a second classname (in 9.6)
+	// Also, make sure that getElementsByClassName actually exists
+	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+		return;
+	}
+
+	// Safari caches class attributes, doesn't catch changes (in 3.2)
+	div.lastChild.className = "e";
+
+	if ( div.getElementsByClassName("e").length === 1 ) {
+		return;
+	}
+	
+	Expr.order.splice(1, 0, "CLASS");
+	Expr.find.CLASS = function( match, context, isXML ) {
+		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+			return context.getElementsByClassName(match[1]);
+		}
+	};
+
+	// release memory in IE
+	div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+
+		if ( elem ) {
+			var match = false;
+
+			elem = elem[dir];
+
+			while ( elem ) {
+				if ( elem[ expando ] === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 && !isXML ){
+					elem[ expando ] = doneName;
+					elem.sizset = i;
+				}
+
+				if ( elem.nodeName.toLowerCase() === cur ) {
+					match = elem;
+					break;
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+
+		if ( elem ) {
+			var match = false;
+			
+			elem = elem[dir];
+
+			while ( elem ) {
+				if ( elem[ expando ] === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 ) {
+					if ( !isXML ) {
+						elem[ expando ] = doneName;
+						elem.sizset = i;
+					}
+
+					if ( typeof cur !== "string" ) {
+						if ( elem === cur ) {
+							match = true;
+							break;
+						}
+
+					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+						match = elem;
+						break;
+					}
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+if ( document.documentElement.contains ) {
+	Sizzle.contains = function( a, b ) {
+		return a !== b && (a.contains ? a.contains(b) : true);
+	};
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+	Sizzle.contains = function( a, b ) {
+		return !!(a.compareDocumentPosition(b) & 16);
+	};
+
+} else {
+	Sizzle.contains = function() {
+		return false;
+	};
+}
+
+Sizzle.isXML = function( elem ) {
+	// documentElement is verified for cases where it doesn't yet exist
+	// (such as loading iframes in IE - #4833) 
+	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+	return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+	var match,
+		tmpSet = [],
+		later = "",
+		root = context.nodeType ? [context] : context;
+
+	// Position selectors must be done after the filter
+	// And so must :not(positional) so we move all PSEUDOs to the end
+	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+		later += match[0];
+		selector = selector.replace( Expr.match.PSEUDO, "" );
+	}
+
+	selector = Expr.relative[selector] ? selector + "*" : selector;
+
+	for ( var i = 0, l = root.length; i < l; i++ ) {
+		Sizzle( selector, root[i], tmpSet, seed );
+	}
+
+	return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+Sizzle.selectors.attrMap = {};
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+	rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+	// Note: This RegExp should be improved, or likely pulled from Sizzle
+	rmultiselector = /,/,
+	isSimple = /^.[^:#\[\.,]*$/,
+	slice = Array.prototype.slice,
+	POS = jQuery.expr.match.POS,
+	// methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.fn.extend({
+	find: function( selector ) {
+		var self = this,
+			i, l;
+
+		if ( typeof selector !== "string" ) {
+			return jQuery( selector ).filter(function() {
+				for ( i = 0, l = self.length; i < l; i++ ) {
+					if ( jQuery.contains( self[ i ], this ) ) {
+						return true;
+					}
+				}
+			});
+		}
+
+		var ret = this.pushStack( "", "find", selector ),
+			length, n, r;
+
+		for ( i = 0, l = this.length; i < l; i++ ) {
+			length = ret.length;
+			jQuery.find( selector, this[i], ret );
+
+			if ( i > 0 ) {
+				// Make sure that the results are unique
+				for ( n = length; n < ret.length; n++ ) {
+					for ( r = 0; r < length; r++ ) {
+						if ( ret[r] === ret[n] ) {
+							ret.splice(n--, 1);
+							break;
+						}
+					}
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	has: function( target ) {
+		var targets = jQuery( target );
+		return this.filter(function() {
+			for ( var i = 0, l = targets.length; i < l; i++ ) {
+				if ( jQuery.contains( this, targets[i] ) ) {
+					return true;
+				}
+			}
+		});
+	},
+
+	not: function( selector ) {
+		return this.pushStack( winnow(this, selector, false), "not", selector);
+	},
+
+	filter: function( selector ) {
+		return this.pushStack( winnow(this, selector, true), "filter", selector );
+	},
+
+	is: function( selector ) {
+		return !!selector && ( 
+			typeof selector === "string" ?
+				// If this is a positional selector, check membership in the returned set
+				// so $("p:first").is("p:last") won't return true for a doc with two "p".
+				POS.test( selector ) ? 
+					jQuery( selector, this.context ).index( this[0] ) >= 0 :
+					jQuery.filter( selector, this ).length > 0 :
+				this.filter( selector ).length > 0 );
+	},
+
+	closest: function( selectors, context ) {
+		var ret = [], i, l, cur = this[0];
+		
+		// Array (deprecated as of jQuery 1.7)
+		if ( jQuery.isArray( selectors ) ) {
+			var level = 1;
+
+			while ( cur && cur.ownerDocument && cur !== context ) {
+				for ( i = 0; i < selectors.length; i++ ) {
+
+					if ( jQuery( cur ).is( selectors[ i ] ) ) {
+						ret.push({ selector: selectors[ i ], elem: cur, level: level });
+					}
+				}
+
+				cur = cur.parentNode;
+				level++;
+			}
+
+			return ret;
+		}
+
+		// String
+		var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+				jQuery( selectors, context || this.context ) :
+				0;
+
+		for ( i = 0, l = this.length; i < l; i++ ) {
+			cur = this[i];
+
+			while ( cur ) {
+				if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+					ret.push( cur );
+					break;
+
+				} else {
+					cur = cur.parentNode;
+					if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+						break;
+					}
+				}
+			}
+		}
+
+		ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+		return this.pushStack( ret, "closest", selectors );
+	},
+
+	// Determine the position of an element within
+	// the matched set of elements
+	index: function( elem ) {
+
+		// No argument, return index in parent
+		if ( !elem ) {
+			return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+		}
+
+		// index in selector
+		if ( typeof elem === "string" ) {
+			return jQuery.inArray( this[0], jQuery( elem ) );
+		}
+
+		// Locate the position of the desired element
+		return jQuery.inArray(
+			// If it receives a jQuery object, the first element is used
+			elem.jquery ? elem[0] : elem, this );
+	},
+
+	add: function( selector, context ) {
+		var set = typeof selector === "string" ?
+				jQuery( selector, context ) :
+				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+			all = jQuery.merge( this.get(), set );
+
+		return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+			all :
+			jQuery.unique( all ) );
+	},
+
+	andSelf: function() {
+		return this.add( this.prevObject );
+	}
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+	return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+	parent: function( elem ) {
+		var parent = elem.parentNode;
+		return parent && parent.nodeType !== 11 ? parent : null;
+	},
+	parents: function( elem ) {
+		return jQuery.dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return jQuery.nth( elem, 2, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return jQuery.nth( elem, 2, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return jQuery.dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return jQuery.dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return jQuery.sibling( elem.parentNode.firstChild, elem );
+	},
+	children: function( elem ) {
+		return jQuery.sibling( elem.firstChild );
+	},
+	contents: function( elem ) {
+		return jQuery.nodeName( elem, "iframe" ) ?
+			elem.contentDocument || elem.contentWindow.document :
+			jQuery.makeArray( elem.childNodes );
+	}
+}, function( name, fn ) {
+	jQuery.fn[ name ] = function( until, selector ) {
+		var ret = jQuery.map( this, fn, until );
+
+		if ( !runtil.test( name ) ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			ret = jQuery.filter( selector, ret );
+		}
+
+		ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+		if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+			ret = ret.reverse();
+		}
+
+		return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+	};
+});
+
+jQuery.extend({
+	filter: function( expr, elems, not ) {
+		if ( not ) {
+			expr = ":not(" + expr + ")";
+		}
+
+		return elems.length === 1 ?
+			jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+			jQuery.find.matches(expr, elems);
+	},
+
+	dir: function( elem, dir, until ) {
+		var matched = [],
+			cur = elem[ dir ];
+
+		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+			if ( cur.nodeType === 1 ) {
+				matched.push( cur );
+			}
+			cur = cur[dir];
+		}
+		return matched;
+	},
+
+	nth: function( cur, result, dir, elem ) {
+		result = result || 1;
+		var num = 0;
+
+		for ( ; cur; cur = cur[dir] ) {
+			if ( cur.nodeType === 1 && ++num === result ) {
+				break;
+			}
+		}
+
+		return cur;
+	},
+
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				r.push( n );
+			}
+		}
+
+		return r;
+	}
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+	// Can't pass null or undefined to indexOf in Firefox 4
+	// Set to 0 to skip string check
+	qualifier = qualifier || 0;
+
+	if ( jQuery.isFunction( qualifier ) ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			var retVal = !!qualifier.call( elem, i, elem );
+			return retVal === keep;
+		});
+
+	} else if ( qualifier.nodeType ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			return ( elem === qualifier ) === keep;
+		});
+
+	} else if ( typeof qualifier === "string" ) {
+		var filtered = jQuery.grep(elements, function( elem ) {
+			return elem.nodeType === 1;
+		});
+
+		if ( isSimple.test( qualifier ) ) {
+			return jQuery.filter(qualifier, filtered, !keep);
+		} else {
+			qualifier = jQuery.filter( qualifier, filtered );
+		}
+	}
+
+	return jQuery.grep(elements, function( elem, i ) {
+		return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+	});
+}
+
+
+
+
+function createSafeFragment( document ) {
+	var list = nodeNames.split( "|" ),
+	safeFrag = document.createDocumentFragment();
+
+	if ( safeFrag.createElement ) {
+		while ( list.length ) {
+			safeFrag.createElement(
+				list.pop()
+			);
+		}
+	}
+	return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" +
+		"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+	rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+	rleadingWhitespace = /^\s+/,
+	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+	rtagName = /<([\w:]+)/,
+	rtbody = /<tbody/i,
+	rhtml = /<|&#?\w+;/,
+	rnoInnerhtml = /<(?:script|style)/i,
+	rnocache = /<(?:script|object|embed|option|style)/i,
+	rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"),
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptType = /\/(java|ecma)script/i,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+	wrapMap = {
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+		legend: [ 1, "<fieldset>", "</fieldset>" ],
+		thead: [ 1, "<table>", "</table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+		area: [ 1, "<map>", "</map>" ],
+		_default: [ 0, "", "" ]
+	},
+	safeFragment = createSafeFragment( document );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+	wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+	text: function( text ) {
+		if ( jQuery.isFunction(text) ) {
+			return this.each(function(i) {
+				var self = jQuery( this );
+
+				self.text( text.call(this, i, self.text()) );
+			});
+		}
+
+		if ( typeof text !== "object" && text !== undefined ) {
+			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+		}
+
+		return jQuery.text( this );
+	},
+
+	wrapAll: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapAll( html.call(this, i) );
+			});
+		}
+
+		if ( this[0] ) {
+			// The elements to wrap the target around
+			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+			if ( this[0].parentNode ) {
+				wrap.insertBefore( this[0] );
+			}
+
+			wrap.map(function() {
+				var elem = this;
+
+				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+					elem = elem.firstChild;
+				}
+
+				return elem;
+			}).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapInner( html.call(this, i) );
+			});
+		}
+
+		return this.each(function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		});
+	},
+
+	wrap: function( html ) {
+		var isFunction = jQuery.isFunction( html );
+
+		return this.each(function(i) {
+			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+		});
+	},
+
+	unwrap: function() {
+		return this.parent().each(function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		}).end();
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.appendChild( elem );
+			}
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.insertBefore( elem, this.firstChild );
+			}
+		});
+	},
+
+	before: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this );
+			});
+		} else if ( arguments.length ) {
+			var set = jQuery.clean( arguments );
+			set.push.apply( set, this.toArray() );
+			return this.pushStack( set, "before", arguments );
+		}
+	},
+
+	after: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			});
+		} else if ( arguments.length ) {
+			var set = this.pushStack( this, "after", arguments );
+			set.push.apply( set, jQuery.clean(arguments) );
+			return set;
+		}
+	},
+
+	// keepData is for internal use only--do not document
+	remove: function( selector, keepData ) {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+				if ( !keepData && elem.nodeType === 1 ) {
+					jQuery.cleanData( elem.getElementsByTagName("*") );
+					jQuery.cleanData( [ elem ] );
+				}
+
+				if ( elem.parentNode ) {
+					elem.parentNode.removeChild( elem );
+				}
+			}
+		}
+
+		return this;
+	},
+
+	empty: function() {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			// Remove element nodes and prevent memory leaks
+			if ( elem.nodeType === 1 ) {
+				jQuery.cleanData( elem.getElementsByTagName("*") );
+			}
+
+			// Remove any remaining nodes
+			while ( elem.firstChild ) {
+				elem.removeChild( elem.firstChild );
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function () {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		});
+	},
+
+	html: function( value ) {
+		if ( value === undefined ) {
+			return this[0] && this[0].nodeType === 1 ?
+				this[0].innerHTML.replace(rinlinejQuery, "") :
+				null;
+
+		// See if we can take a shortcut and just use innerHTML
+		} else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+			(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+			!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+			value = value.replace(rxhtmlTag, "<$1></$2>");
+
+			try {
+				for ( var i = 0, l = this.length; i < l; i++ ) {
+					// Remove element nodes and prevent memory leaks
+					if ( this[i].nodeType === 1 ) {
+						jQuery.cleanData( this[i].getElementsByTagName("*") );
+						this[i].innerHTML = value;
+					}
+				}
+
+			// If using innerHTML throws an exception, use the fallback method
+			} catch(e) {
+				this.empty().append( value );
+			}
+
+		} else if ( jQuery.isFunction( value ) ) {
+			this.each(function(i){
+				var self = jQuery( this );
+
+				self.html( value.call(this, i, self.html()) );
+			});
+
+		} else {
+			this.empty().append( value );
+		}
+
+		return this;
+	},
+
+	replaceWith: function( value ) {
+		if ( this[0] && this[0].parentNode ) {
+			// Make sure that the elements are removed from the DOM before they are inserted
+			// this can help fix replacing a parent with child elements
+			if ( jQuery.isFunction( value ) ) {
+				return this.each(function(i) {
+					var self = jQuery(this), old = self.html();
+					self.replaceWith( value.call( this, i, old ) );
+				});
+			}
+
+			if ( typeof value !== "string" ) {
+				value = jQuery( value ).detach();
+			}
+
+			return this.each(function() {
+				var next = this.nextSibling,
+					parent = this.parentNode;
+
+				jQuery( this ).remove();
+
+				if ( next ) {
+					jQuery(next).before( value );
+				} else {
+					jQuery(parent).append( value );
+				}
+			});
+		} else {
+			return this.length ?
+				this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+				this;
+		}
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, table, callback ) {
+		var results, first, fragment, parent,
+			value = args[0],
+			scripts = [];
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+			return this.each(function() {
+				jQuery(this).domManip( args, table, callback, true );
+			});
+		}
+
+		if ( jQuery.isFunction(value) ) {
+			return this.each(function(i) {
+				var self = jQuery(this);
+				args[0] = value.call(this, i, table ? self.html() : undefined);
+				self.domManip( args, table, callback );
+			});
+		}
+
+		if ( this[0] ) {
+			parent = value && value.parentNode;
+
+			// If we're in a fragment, just use that instead of building a new one
+			if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+				results = { fragment: parent };
+
+			} else {
+				results = jQuery.buildFragment( args, this, scripts );
+			}
+
+			fragment = results.fragment;
+
+			if ( fragment.childNodes.length === 1 ) {
+				first = fragment = fragment.firstChild;
+			} else {
+				first = fragment.firstChild;
+			}
+
+			if ( first ) {
+				table = table && jQuery.nodeName( first, "tr" );
+
+				for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+					callback.call(
+						table ?
+							root(this[i], first) :
+							this[i],
+						// Make sure that we do not leak memory by inadvertently discarding
+						// the original fragment (which might have attached data) instead of
+						// using it; in addition, use the original fragment object for the last
+						// item instead of first because it can end up being emptied incorrectly
+						// in certain situations (Bug #8070).
+						// Fragments from the fragment cache must always be cloned and never used
+						// in place.
+						results.cacheable || ( l > 1 && i < lastIndex ) ?
+							jQuery.clone( fragment, true, true ) :
+							fragment
+					);
+				}
+			}
+
+			if ( scripts.length ) {
+				jQuery.each( scripts, evalScript );
+			}
+		}
+
+		return this;
+	}
+});
+
+function root( elem, cur ) {
+	return jQuery.nodeName(elem, "table") ?
+		(elem.getElementsByTagName("tbody")[0] ||
+		elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+		elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+		return;
+	}
+
+	var type, i, l,
+		oldData = jQuery._data( src ),
+		curData = jQuery._data( dest, oldData ),
+		events = oldData.events;
+
+	if ( events ) {
+		delete curData.handle;
+		curData.events = {};
+
+		for ( type in events ) {
+			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+				jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
+			}
+		}
+	}
+
+	// make the cloned public data object a copy from the original
+	if ( curData.data ) {
+		curData.data = jQuery.extend( {}, curData.data );
+	}
+}
+
+function cloneFixAttributes( src, dest ) {
+	var nodeName;
+
+	// We do not need to do anything for non-Elements
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// clearAttributes removes the attributes, which we don't want,
+	// but also removes the attachEvent events, which we *do* want
+	if ( dest.clearAttributes ) {
+		dest.clearAttributes();
+	}
+
+	// mergeAttributes, in contrast, only merges back on the
+	// original attributes, not the events
+	if ( dest.mergeAttributes ) {
+		dest.mergeAttributes( src );
+	}
+
+	nodeName = dest.nodeName.toLowerCase();
+
+	// IE6-8 fail to clone children inside object elements that use
+	// the proprietary classid attribute value (rather than the type
+	// attribute) to identify the type of content to display
+	if ( nodeName === "object" ) {
+		dest.outerHTML = src.outerHTML;
+
+	} else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+		// IE6-8 fails to persist the checked state of a cloned checkbox
+		// or radio button. Worse, IE6-7 fail to give the cloned element
+		// a checked appearance if the defaultChecked value isn't also set
+		if ( src.checked ) {
+			dest.defaultChecked = dest.checked = src.checked;
+		}
+
+		// IE6-7 get confused and end up setting the value of a cloned
+		// checkbox/radio button to an empty string instead of "on"
+		if ( dest.value !== src.value ) {
+			dest.value = src.value;
+		}
+
+	// IE6-8 fails to return the selected option to the default selected
+	// state when cloning options
+	} else if ( nodeName === "option" ) {
+		dest.selected = src.defaultSelected;
+
+	// IE6-8 fails to set the defaultValue to the correct value when
+	// cloning other types of input fields
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+	}
+
+	// Event data gets referenced instead of copied if the expando
+	// gets copied too
+	dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+	var fragment, cacheable, cacheresults, doc,
+	first = args[ 0 ];
+
+	// nodes may contain either an explicit document object,
+	// a jQuery collection or context object.
+	// If nodes[0] contains a valid object to assign to doc
+	if ( nodes && nodes[0] ) {
+		doc = nodes[0].ownerDocument || nodes[0];
+	}
+
+	// Ensure that an attr object doesn't incorrectly stand in as a document object
+	// Chrome and Firefox seem to allow this to occur and will throw exception
+	// Fixes #8950
+	if ( !doc.createDocumentFragment ) {
+		doc = document;
+	}
+
+	// Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+	// Cloning options loses the selected state, so don't cache them
+	// IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+	// Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+	// Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+	if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
+		first.charAt(0) === "<" && !rnocache.test( first ) &&
+		(jQuery.support.checkClone || !rchecked.test( first )) &&
+		(jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+		cacheable = true;
+
+		cacheresults = jQuery.fragments[ first ];
+		if ( cacheresults && cacheresults !== 1 ) {
+			fragment = cacheresults;
+		}
+	}
+
+	if ( !fragment ) {
+		fragment = doc.createDocumentFragment();
+		jQuery.clean( args, doc, fragment, scripts );
+	}
+
+	if ( cacheable ) {
+		jQuery.fragments[ first ] = cacheresults ? fragment : 1;
+	}
+
+	return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = [],
+			insert = jQuery( selector ),
+			parent = this.length === 1 && this[0].parentNode;
+
+		if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+			insert[ original ]( this[0] );
+			return this;
+
+		} else {
+			for ( var i = 0, l = insert.length; i < l; i++ ) {
+				var elems = ( i > 0 ? this.clone(true) : this ).get();
+				jQuery( insert[i] )[ original ]( elems );
+				ret = ret.concat( elems );
+			}
+
+			return this.pushStack( ret, name, insert.selector );
+		}
+	};
+});
+
+function getAll( elem ) {
+	if ( typeof elem.getElementsByTagName !== "undefined" ) {
+		return elem.getElementsByTagName( "*" );
+
+	} else if ( typeof elem.querySelectorAll !== "undefined" ) {
+		return elem.querySelectorAll( "*" );
+
+	} else {
+		return [];
+	}
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+	if ( elem.type === "checkbox" || elem.type === "radio" ) {
+		elem.defaultChecked = elem.checked;
+	}
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+	var nodeName = ( elem.nodeName || "" ).toLowerCase();
+	if ( nodeName === "input" ) {
+		fixDefaultChecked( elem );
+	// Skip scripts, get other children
+	} else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) {
+		jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+	}
+}
+
+// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js
+function shimCloneNode( elem ) {
+	var div = document.createElement( "div" );
+	safeFragment.appendChild( div );
+
+	div.innerHTML = elem.outerHTML;
+	return div.firstChild;
+}
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var srcElements,
+			destElements,
+			i,
+			// IE<=8 does not properly clone detached, unknown element nodes
+			clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ?
+				elem.cloneNode( true ) :
+				shimCloneNode( elem );
+
+		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+			// IE copies events bound via attachEvent when using cloneNode.
+			// Calling detachEvent on the clone will also remove the events
+			// from the original. In order to get around this, we use some
+			// proprietary methods to clear the events. Thanks to MooTools
+			// guys for this hotness.
+
+			cloneFixAttributes( elem, clone );
+
+			// Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+			srcElements = getAll( elem );
+			destElements = getAll( clone );
+
+			// Weird iteration because IE will replace the length property
+			// with an element if you are cloning the body and one of the
+			// elements on the page has a name or id of "length"
+			for ( i = 0; srcElements[i]; ++i ) {
+				// Ensure that the destination node is not null; Fixes #9587
+				if ( destElements[i] ) {
+					cloneFixAttributes( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			cloneCopyEvent( elem, clone );
+
+			if ( deepDataAndEvents ) {
+				srcElements = getAll( elem );
+				destElements = getAll( clone );
+
+				for ( i = 0; srcElements[i]; ++i ) {
+					cloneCopyEvent( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		srcElements = destElements = null;
+
+		// Return the cloned set
+		return clone;
+	},
+
+	clean: function( elems, context, fragment, scripts ) {
+		var checkScriptType;
+
+		context = context || document;
+
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if ( typeof context.createElement === "undefined" ) {
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+		}
+
+		var ret = [], j;
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( typeof elem === "number" ) {
+				elem += "";
+			}
+
+			if ( !elem ) {
+				continue;
+			}
+
+			// Convert html string into DOM nodes
+			if ( typeof elem === "string" ) {
+				if ( !rhtml.test( elem ) ) {
+					elem = context.createTextNode( elem );
+				} else {
+					// Fix "XHTML"-style tags in all browsers
+					elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+					// Trim whitespace, otherwise indexOf won't work as expected
+					var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
+						wrap = wrapMap[ tag ] || wrapMap._default,
+						depth = wrap[0],
+						div = context.createElement("div");
+
+					// Append wrapper element to unknown element safe doc fragment
+					if ( context === document ) {
+						// Use the fragment we've already created for this document
+						safeFragment.appendChild( div );
+					} else {
+						// Use a fragment created with the owner document
+						createSafeFragment( context ).appendChild( div );
+					}
+
+					// Go to html and back, then peel off extra wrappers
+					div.innerHTML = wrap[1] + elem + wrap[2];
+
+					// Move to the right depth
+					while ( depth-- ) {
+						div = div.lastChild;
+					}
+
+					// Remove IE's autoinserted <tbody> from table fragments
+					if ( !jQuery.support.tbody ) {
+
+						// String was a <table>, *may* have spurious <tbody>
+						var hasBody = rtbody.test(elem),
+							tbody = tag === "table" && !hasBody ?
+								div.firstChild && div.firstChild.childNodes :
+
+								// String was a bare <thead> or <tfoot>
+								wrap[1] === "<table>" && !hasBody ?
+									div.childNodes :
+									[];
+
+						for ( j = tbody.length - 1; j >= 0 ; --j ) {
+							if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+								tbody[ j ].parentNode.removeChild( tbody[ j ] );
+							}
+						}
+					}
+
+					// IE completely kills leading whitespace when innerHTML is used
+					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+						div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+					}
+
+					elem = div.childNodes;
+				}
+			}
+
+			// Resets defaultChecked for any radios and checkboxes
+			// about to be appended to the DOM in IE 6/7 (#8060)
+			var len;
+			if ( !jQuery.support.appendChecked ) {
+				if ( elem[0] && typeof (len = elem.length) === "number" ) {
+					for ( j = 0; j < len; j++ ) {
+						findInputs( elem[j] );
+					}
+				} else {
+					findInputs( elem );
+				}
+			}
+
+			if ( elem.nodeType ) {
+				ret.push( elem );
+			} else {
+				ret = jQuery.merge( ret, elem );
+			}
+		}
+
+		if ( fragment ) {
+			checkScriptType = function( elem ) {
+				return !elem.type || rscriptType.test( elem.type );
+			};
+			for ( i = 0; ret[i]; i++ ) {
+				if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+					scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+
+				} else {
+					if ( ret[i].nodeType === 1 ) {
+						var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );
+
+						ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+					}
+					fragment.appendChild( ret[i] );
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	cleanData: function( elems ) {
+		var data, id,
+			cache = jQuery.cache,
+			special = jQuery.event.special,
+			deleteExpando = jQuery.support.deleteExpando;
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+				continue;
+			}
+
+			id = elem[ jQuery.expando ];
+
+			if ( id ) {
+				data = cache[ id ];
+
+				if ( data && data.events ) {
+					for ( var type in data.events ) {
+						if ( special[ type ] ) {
+							jQuery.event.remove( elem, type );
+
+						// This is a shortcut to avoid jQuery.event.remove's overhead
+						} else {
+							jQuery.removeEvent( elem, type, data.handle );
+						}
+					}
+
+					// Null the DOM reference to avoid IE6/7/8 leak (#7054)
+					if ( data.handle ) {
+						data.handle.elem = null;
+					}
+				}
+
+				if ( deleteExpando ) {
+					delete elem[ jQuery.expando ];
+
+				} else if ( elem.removeAttribute ) {
+					elem.removeAttribute( jQuery.expando );
+				}
+
+				delete cache[ id ];
+			}
+		}
+	}
+});
+
+function evalScript( i, elem ) {
+	if ( elem.src ) {
+		jQuery.ajax({
+			url: elem.src,
+			async: false,
+			dataType: "script"
+		});
+	} else {
+		jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+	}
+
+	if ( elem.parentNode ) {
+		elem.parentNode.removeChild( elem );
+	}
+}
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+	ropacity = /opacity=([^)]*)/,
+	// fixed for IE9, see #8346
+	rupper = /([A-Z]|^ms)/g,
+	rnumpx = /^-?\d+(?:px)?$/i,
+	rnum = /^-?\d/,
+	rrelNum = /^([\-+])=([\-+.\de]+)/,
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssWidth = [ "Left", "Right" ],
+	cssHeight = [ "Top", "Bottom" ],
+	curCSS,
+
+	getComputedStyle,
+	currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+	// Setting 'undefined' is a no-op
+	if ( arguments.length === 2 && value === undefined ) {
+		return this;
+	}
+
+	return jQuery.access( this, name, value, true, function( elem, name, value ) {
+		return value !== undefined ?
+			jQuery.style( elem, name, value ) :
+			jQuery.css( elem, name );
+	});
+};
+
+jQuery.extend({
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity", "opacity" );
+					return ret === "" ? "1" : ret;
+
+				} else {
+					return elem.style.opacity;
+				}
+			}
+		}
+	},
+
+	// Exclude the following css properties to add px
+	cssNumber: {
+		"fillOpacity": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		// normalize float css property
+		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, origName = jQuery.camelCase( name ),
+			style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+		name = jQuery.cssProps[ origName ] || origName;
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// convert relative number strings (+= or -=) to relative numbers. #7345
+			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+				value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that NaN and null values aren't set. See: #7116
+			if ( value == null || type === "number" && isNaN( value ) ) {
+				return;
+			}
+
+			// If a number was passed in, add 'px' to the (except for certain CSS properties)
+			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+				value += "px";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+				// Fixes bug #5509
+				try {
+					style[ name ] = value;
+				} catch(e) {}
+			}
+
+		} else {
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra ) {
+		var ret, hooks;
+
+		// Make sure that we're working with the right name
+		name = jQuery.camelCase( name );
+		hooks = jQuery.cssHooks[ name ];
+		name = jQuery.cssProps[ name ] || name;
+
+		// cssFloat needs a special treatment
+		if ( name === "cssFloat" ) {
+			name = "float";
+		}
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+			return ret;
+
+		// Otherwise, if a way to get the computed value exists, use that
+		} else if ( curCSS ) {
+			return curCSS( elem, name );
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {};
+
+		// Remember the old values, and insert the new ones
+		for ( var name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		callback.call( elem );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+	}
+});
+
+// DEPRECATED, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+jQuery.each(["height", "width"], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			var val;
+
+			if ( computed ) {
+				if ( elem.offsetWidth !== 0 ) {
+					return getWH( elem, name, extra );
+				} else {
+					jQuery.swap( elem, cssShow, function() {
+						val = getWH( elem, name, extra );
+					});
+				}
+
+				return val;
+			}
+		},
+
+		set: function( elem, value ) {
+			if ( rnumpx.test( value ) ) {
+				// ignore negative width and height values #1599
+				value = parseFloat( value );
+
+				if ( value >= 0 ) {
+					return value + "px";
+				}
+
+			} else {
+				return value;
+			}
+		}
+	};
+});
+
+if ( !jQuery.support.opacity ) {
+	jQuery.cssHooks.opacity = {
+		get: function( elem, computed ) {
+			// IE uses filters for opacity
+			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+				( parseFloat( RegExp.$1 ) / 100 ) + "" :
+				computed ? "1" : "";
+		},
+
+		set: function( elem, value ) {
+			var style = elem.style,
+				currentStyle = elem.currentStyle,
+				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+				filter = currentStyle && currentStyle.filter || style.filter || "";
+
+			// IE has trouble with opacity if it does not have layout
+			// Force it by setting the zoom level
+			style.zoom = 1;
+
+			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+			if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+
+				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+				// if "filter:" is present at all, clearType is disabled, we want to avoid this
+				// style.removeAttribute is IE Only, but so apparently is this code path...
+				style.removeAttribute( "filter" );
+
+				// if there there is no filter style applied in a css rule, we are done
+				if ( currentStyle && !currentStyle.filter ) {
+					return;
+				}
+			}
+
+			// otherwise, set new filter values
+			style.filter = ralpha.test( filter ) ?
+				filter.replace( ralpha, opacity ) :
+				filter + " " + opacity;
+		}
+	};
+}
+
+jQuery(function() {
+	// This hook cannot be added until DOM ready because the support test
+	// for it is not run until after DOM ready
+	if ( !jQuery.support.reliableMarginRight ) {
+		jQuery.cssHooks.marginRight = {
+			get: function( elem, computed ) {
+				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+				// Work around by temporarily setting element display to inline-block
+				var ret;
+				jQuery.swap( elem, { "display": "inline-block" }, function() {
+					if ( computed ) {
+						ret = curCSS( elem, "margin-right", "marginRight" );
+					} else {
+						ret = elem.style.marginRight;
+					}
+				});
+				return ret;
+			}
+		};
+	}
+});
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+	getComputedStyle = function( elem, name ) {
+		var ret, defaultView, computedStyle;
+
+		name = name.replace( rupper, "-$1" ).toLowerCase();
+
+		if ( (defaultView = elem.ownerDocument.defaultView) &&
+				(computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+			ret = computedStyle.getPropertyValue( name );
+			if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+				ret = jQuery.style( elem, name );
+			}
+		}
+
+		return ret;
+	};
+}
+
+if ( document.documentElement.currentStyle ) {
+	currentStyle = function( elem, name ) {
+		var left, rsLeft, uncomputed,
+			ret = elem.currentStyle && elem.currentStyle[ name ],
+			style = elem.style;
+
+		// Avoid setting ret to empty string here
+		// so we don't default to auto
+		if ( ret === null && style && (uncomputed = style[ name ]) ) {
+			ret = uncomputed;
+		}
+
+		// From the awesome hack by Dean Edwards
+		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+		// If we're not dealing with a regular pixel number
+		// but a number that has a weird ending, we need to convert it to pixels
+		if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+
+			// Remember the original values
+			left = style.left;
+			rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+			// Put in the new values to get a computed value out
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = elem.currentStyle.left;
+			}
+			style.left = name === "fontSize" ? "1em" : ( ret || 0 );
+			ret = style.pixelLeft + "px";
+
+			// Revert the changed values
+			style.left = left;
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = rsLeft;
+			}
+		}
+
+		return ret === "" ? "auto" : ret;
+	};
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWH( elem, name, extra ) {
+
+	// Start with offset property
+	var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		which = name === "width" ? cssWidth : cssHeight,
+		i = 0,
+		len = which.length;
+
+	if ( val > 0 ) {
+		if ( extra !== "border" ) {
+			for ( ; i < len; i++ ) {
+				if ( !extra ) {
+					val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
+				}
+				if ( extra === "margin" ) {
+					val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
+				} else {
+					val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
+				}
+			}
+		}
+
+		return val + "px";
+	}
+
+	// Fall back to computed then uncomputed css if necessary
+	val = curCSS( elem, name, name );
+	if ( val < 0 || val == null ) {
+		val = elem.style[ name ] || 0;
+	}
+	// Normalize "", auto, and prepare for extra
+	val = parseFloat( val ) || 0;
+
+	// Add padding, border, margin
+	if ( extra ) {
+		for ( ; i < len; i++ ) {
+			val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
+			if ( extra !== "padding" ) {
+				val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
+			}
+			if ( extra === "margin" ) {
+				val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
+			}
+		}
+	}
+
+	return val + "px";
+}
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.hidden = function( elem ) {
+		var width = elem.offsetWidth,
+			height = elem.offsetHeight;
+
+		return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+	};
+
+	jQuery.expr.filters.visible = function( elem ) {
+		return !jQuery.expr.filters.hidden( elem );
+	};
+}
+
+
+
+
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rhash = /#.*$/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+	rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rquery = /\?/,
+	rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+	rselectTextarea = /^(?:select|textarea)/i,
+	rspacesAjax = /\s+/,
+	rts = /([?&])_=[^&]*/,
+	rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+	// Keep a copy of the old load method
+	_load = jQuery.fn.load,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Document location
+	ajaxLocation,
+
+	// Document location segments
+	ajaxLocParts,
+
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+	ajaxLocation = location.href;
+} catch( e ) {
+	// Use the href attribute of an A element
+	// since IE will modify it given document.location
+	ajaxLocation = document.createElement( "a" );
+	ajaxLocation.href = "";
+	ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		if ( jQuery.isFunction( func ) ) {
+			var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+				i = 0,
+				length = dataTypes.length,
+				dataType,
+				list,
+				placeBefore;
+
+			// For each dataType in the dataTypeExpression
+			for ( ; i < length; i++ ) {
+				dataType = dataTypes[ i ];
+				// We control if we're asked to add before
+				// any existing element
+				placeBefore = /^\+/.test( dataType );
+				if ( placeBefore ) {
+					dataType = dataType.substr( 1 ) || "*";
+				}
+				list = structure[ dataType ] = structure[ dataType ] || [];
+				// then we add to the structure accordingly
+				list[ placeBefore ? "unshift" : "push" ]( func );
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+		dataType /* internal */, inspected /* internal */ ) {
+
+	dataType = dataType || options.dataTypes[ 0 ];
+	inspected = inspected || {};
+
+	inspected[ dataType ] = true;
+
+	var list = structure[ dataType ],
+		i = 0,
+		length = list ? list.length : 0,
+		executeOnly = ( structure === prefilters ),
+		selection;
+
+	for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+		selection = list[ i ]( options, originalOptions, jqXHR );
+		// If we got redirected to another dataType
+		// we try there if executing only and not done already
+		if ( typeof selection === "string" ) {
+			if ( !executeOnly || inspected[ selection ] ) {
+				selection = undefined;
+			} else {
+				options.dataTypes.unshift( selection );
+				selection = inspectPrefiltersOrTransports(
+						structure, options, originalOptions, jqXHR, selection, inspected );
+			}
+		}
+	}
+	// If we're only executing or nothing was selected
+	// we try the catchall dataType if not done already
+	if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+		selection = inspectPrefiltersOrTransports(
+				structure, options, originalOptions, jqXHR, "*", inspected );
+	}
+	// unnecessary when only executing (prefilters)
+	// but it'll be ignored by the caller in that case
+	return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var key, deep,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+	for ( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+}
+
+jQuery.fn.extend({
+	load: function( url, params, callback ) {
+		if ( typeof url !== "string" && _load ) {
+			return _load.apply( this, arguments );
+
+		// Don't do a request if no elements are being requested
+		} else if ( !this.length ) {
+			return this;
+		}
+
+		var off = url.indexOf( " " );
+		if ( off >= 0 ) {
+			var selector = url.slice( off, url.length );
+			url = url.slice( 0, off );
+		}
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params ) {
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = undefined;
+
+			// Otherwise, build a param string
+			} else if ( typeof params === "object" ) {
+				params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+				type = "POST";
+			}
+		}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			// Complete callback (responseText is used internally)
+			complete: function( jqXHR, status, responseText ) {
+				// Store the response as specified by the jqXHR object
+				responseText = jqXHR.responseText;
+				// If successful, inject the HTML into all the matched elements
+				if ( jqXHR.isResolved() ) {
+					// #4825: Get the actual response in case
+					// a dataFilter is present in ajaxSettings
+					jqXHR.done(function( r ) {
+						responseText = r;
+					});
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(responseText.replace(rscript, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						responseText );
+				}
+
+				if ( callback ) {
+					self.each( callback, [ responseText, status, jqXHR ] );
+				}
+			}
+		});
+
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+
+	serializeArray: function() {
+		return this.map(function(){
+			return this.elements ? jQuery.makeArray( this.elements ) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled &&
+				( this.checked || rselectTextarea.test( this.nodeName ) ||
+					rinput.test( this.type ) );
+		})
+		.map(function( i, elem ){
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val, i ){
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+	jQuery.fn[ o ] = function( f ){
+		return this.on( o, f );
+	};
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		return jQuery.ajax({
+			type: method,
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	};
+});
+
+jQuery.extend({
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		if ( settings ) {
+			// Building a settings object
+			ajaxExtend( target, jQuery.ajaxSettings );
+		} else {
+			// Extending ajaxSettings
+			settings = target;
+			target = jQuery.ajaxSettings;
+		}
+		ajaxExtend( target, settings );
+		return target;
+	},
+
+	ajaxSettings: {
+		url: ajaxLocation,
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+		global: true,
+		type: "GET",
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			text: "text/plain",
+			json: "application/json, text/javascript",
+			"*": allTypes
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText"
+		},
+
+		// List of data converters
+		// 1) key format is "source_type destination_type" (a single space in-between)
+		// 2) the catchall symbol "*" can be used for source_type
+		converters: {
+
+			// Convert anything to text
+			"* text": window.String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			context: true,
+			url: true
+		}
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var // Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events
+			// It's the callbackContext if one was provided in the options
+			// and if it's a DOM node or a jQuery collection
+			globalEventContext = callbackContext !== s &&
+				( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+						jQuery( callbackContext ) : jQuery.event,
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery.Callbacks( "once memory" ),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// ifModified key
+			ifModifiedKey,
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+			// transport
+			transport,
+			// timeout handle
+			timeoutTimer,
+			// Cross-domain detection vars
+			parts,
+			// The jqXHR state
+			state = 0,
+			// To know if global events are to be dispatched
+			fireGlobals,
+			// Loop variable
+			i,
+			// Fake xhr
+			jqXHR = {
+
+				readyState: 0,
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					if ( !state ) {
+						var lname = name.toLowerCase();
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match === undefined ? null : match;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					statusText = statusText || "abort";
+					if ( transport ) {
+						transport.abort( statusText );
+					}
+					done( 0, statusText );
+					return this;
+				}
+			};
+
+		// Callback for when everything is done
+		// It is defined here because jslint complains if it is declared
+		// at the end of the function (which would be more logical and readable)
+		function done( status, nativeStatusText, responses, headers ) {
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			var isSuccess,
+				success,
+				error,
+				statusText = nativeStatusText,
+				response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+				lastModified,
+				etag;
+
+			// If successful, handle type chaining
+			if ( status >= 200 && status < 300 || status === 304 ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+
+					if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+						jQuery.lastModified[ ifModifiedKey ] = lastModified;
+					}
+					if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+						jQuery.etag[ ifModifiedKey ] = etag;
+					}
+				}
+
+				// If not modified
+				if ( status === 304 ) {
+
+					statusText = "notmodified";
+					isSuccess = true;
+
+				// If we have data
+				} else {
+
+					try {
+						success = ajaxConvert( s, response );
+						statusText = "success";
+						isSuccess = true;
+					} catch(e) {
+						// We have a parsererror
+						statusText = "parsererror";
+						error = e;
+					}
+				}
+			} else {
+				// We extract error from statusText
+				// then normalize statusText and status for non-aborts
+				error = statusText;
+				if ( !statusText || status ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+						[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger( "ajaxStop" );
+				}
+			}
+		}
+
+		// Attach deferreds
+		deferred.promise( jqXHR );
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+		jqXHR.complete = completeDeferred.add;
+
+		// Status-dependent callbacks
+		jqXHR.statusCode = function( map ) {
+			if ( map ) {
+				var tmp;
+				if ( state < 2 ) {
+					for ( tmp in map ) {
+						statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+					}
+				} else {
+					tmp = map[ jqXHR.status ];
+					jqXHR.then( tmp, tmp );
+				}
+			}
+			return this;
+		};
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+		// Determine if a cross-domain request is in order
+		if ( s.crossDomain == null ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefiler, stop there
+		if ( state === 2 ) {
+			return false;
+		}
+
+		// We can fire global events as of now if asked to
+		fireGlobals = s.global;
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger( "ajaxStart" );
+		}
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Get ifModifiedKey before adding the anti-cache parameter
+			ifModifiedKey = s.url;
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+
+				var ts = jQuery.now(),
+					// try replacing _= if it is there
+					ret = s.url.replace( rts, "$1_=" + ts );
+
+				// if nothing was replaced, add timestamp to the end
+				s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			ifModifiedKey = ifModifiedKey || s.url;
+			if ( jQuery.lastModified[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+			}
+			if ( jQuery.etag[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+			}
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+				// Abort if not done already
+				jqXHR.abort();
+				return false;
+
+		}
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout( function(){
+					jqXHR.abort( "timeout" );
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch (e) {
+				// Propagate exception as error if not done
+				if ( state < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					throw e;
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a, traditional ) {
+		var s = [],
+			add = function( key, value ) {
+				// If value is a function, invoke it and return its value
+				value = jQuery.isFunction( value ) ? value() : value;
+				s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+			};
+
+		// Set traditional to true for jQuery <= 1.3.2 behavior.
+		if ( traditional === undefined ) {
+			traditional = jQuery.ajaxSettings.traditional;
+		}
+
+		// If an array was passed in, assume that it is an array of form elements.
+		if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+			// Serialize the form elements
+			jQuery.each( a, function() {
+				add( this.name, this.value );
+			});
+
+		} else {
+			// If traditional, encode the "old" way (the way 1.3.2 or older
+			// did it), otherwise encode params recursively.
+			for ( var prefix in a ) {
+				buildParams( prefix, a[ prefix ], traditional, add );
+			}
+		}
+
+		// Return the resulting serialization
+		return s.join( "&" ).replace( r20, "+" );
+	}
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+	if ( jQuery.isArray( obj ) ) {
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+				// If array item is non-scalar (array or object), encode its
+				// numeric index to resolve deserialization ambiguity issues.
+				// Note that rack (as of 1.0.0) can't currently deserialize
+				// nested arrays properly, and attempting to do so may cause
+				// a server error. Possible fixes are to modify rack's
+				// deserialization algorithm or to provide an option or flag
+				// to force array serialization to be shallow.
+				buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
+			}
+		});
+
+	} else if ( !traditional && obj != null && typeof obj === "object" ) {
+		// Serialize object item.
+		for ( var name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var contents = s.contents,
+		dataTypes = s.dataTypes,
+		responseFields = s.responseFields,
+		ct,
+		type,
+		finalDataType,
+		firstDataType;
+
+	// Fill responseXXX fields
+	for ( type in responseFields ) {
+		if ( type in responses ) {
+			jqXHR[ responseFields[type] ] = responses[ type ];
+		}
+	}
+
+	// Remove auto dataType and get content-type in the process
+	while( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+	// Apply the dataFilter if provided
+	if ( s.dataFilter ) {
+		response = s.dataFilter( response, s.dataType );
+	}
+
+	var dataTypes = s.dataTypes,
+		converters = {},
+		i,
+		key,
+		length = dataTypes.length,
+		tmp,
+		// Current and previous dataTypes
+		current = dataTypes[ 0 ],
+		prev,
+		// Conversion expression
+		conversion,
+		// Conversion function
+		conv,
+		// Conversion functions (transitive conversion)
+		conv1,
+		conv2;
+
+	// For each dataType in the chain
+	for ( i = 1; i < length; i++ ) {
+
+		// Create converters map
+		// with lowercased keys
+		if ( i === 1 ) {
+			for ( key in s.converters ) {
+				if ( typeof key === "string" ) {
+					converters[ key.toLowerCase() ] = s.converters[ key ];
+				}
+			}
+		}
+
+		// Get the dataTypes
+		prev = current;
+		current = dataTypes[ i ];
+
+		// If current is auto dataType, update it to prev
+		if ( current === "*" ) {
+			current = prev;
+		// If no auto and dataTypes are actually different
+		} else if ( prev !== "*" && prev !== current ) {
+
+			// Get the converter
+			conversion = prev + " " + current;
+			conv = converters[ conversion ] || converters[ "* " + current ];
+
+			// If there is no direct converter, search transitively
+			if ( !conv ) {
+				conv2 = undefined;
+				for ( conv1 in converters ) {
+					tmp = conv1.split( " " );
+					if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+						conv2 = converters[ tmp[1] + " " + current ];
+						if ( conv2 ) {
+							conv1 = converters[ conv1 ];
+							if ( conv1 === true ) {
+								conv = conv2;
+							} else if ( conv2 === true ) {
+								conv = conv1;
+							}
+							break;
+						}
+					}
+				}
+			}
+			// If we found no converter, dispatch an error
+			if ( !( conv || conv2 ) ) {
+				jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+			}
+			// If found converter is not an equivalence
+			if ( conv !== true ) {
+				// Convert with 1 or 2 converters accordingly
+				response = conv ? conv( response ) : conv2( conv1(response) );
+			}
+		}
+	}
+	return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+	jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		return jQuery.expando + "_" + ( jsc++ );
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var inspectData = s.contentType === "application/x-www-form-urlencoded" &&
+		( typeof s.data === "string" );
+
+	if ( s.dataTypes[ 0 ] === "jsonp" ||
+		s.jsonp !== false && ( jsre.test( s.url ) ||
+				inspectData && jsre.test( s.data ) ) ) {
+
+		var responseContainer,
+			jsonpCallback = s.jsonpCallback =
+				jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+			previous = window[ jsonpCallback ],
+			url = s.url,
+			data = s.data,
+			replace = "$1" + jsonpCallback + "$2";
+
+		if ( s.jsonp !== false ) {
+			url = url.replace( jsre, replace );
+			if ( s.url === url ) {
+				if ( inspectData ) {
+					data = data.replace( jsre, replace );
+				}
+				if ( s.data === data ) {
+					// Add callback manually
+					url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+				}
+			}
+		}
+
+		s.url = url;
+		s.data = data;
+
+		// Install callback
+		window[ jsonpCallback ] = function( response ) {
+			responseContainer = [ response ];
+		};
+
+		// Clean-up function
+		jqXHR.always(function() {
+			// Set callback back to previous value
+			window[ jsonpCallback ] = previous;
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( previous ) ) {
+				window[ jsonpCallback ]( responseContainer[ 0 ] );
+			}
+		});
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( jsonpCallback + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Delegate to script
+		return "script";
+	}
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /javascript|ecmascript/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+		s.global = false;
+	}
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+
+		var script,
+			head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+		return {
+
+			send: function( _, callback ) {
+
+				script = document.createElement( "script" );
+
+				script.async = "async";
+
+				if ( s.scriptCharset ) {
+					script.charset = s.scriptCharset;
+				}
+
+				script.src = s.url;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+						// Handle memory leak in IE
+						script.onload = script.onreadystatechange = null;
+
+						// Remove the script
+						if ( head && script.parentNode ) {
+							head.removeChild( script );
+						}
+
+						// Dereference the script
+						script = undefined;
+
+						// Callback if not abort
+						if ( !isAbort ) {
+							callback( 200, "success" );
+						}
+					}
+				};
+				// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+				// This arises when a base node is used (#2709 and #4378).
+				head.insertBefore( script, head.firstChild );
+			},
+
+			abort: function() {
+				if ( script ) {
+					script.onload( 0, 1 );
+				}
+			}
+		};
+	}
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+	xhrOnUnloadAbort = window.ActiveXObject ? function() {
+		// Abort all pending requests
+		for ( var key in xhrCallbacks ) {
+			xhrCallbacks[ key ]( 0, 1 );
+		}
+	} : false,
+	xhrId = 0,
+	xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch( e ) {}
+}
+
+function createActiveXHR() {
+	try {
+		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+	} catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+	/* Microsoft failed to properly
+	 * implement the XMLHttpRequest in IE7 (can't request local files),
+	 * so we use the ActiveXObject when it is available
+	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+	 * we need a fallback.
+	 */
+	function() {
+		return !this.isLocal && createStandardXHR() || createActiveXHR();
+	} :
+	// For all other browsers, use the standard XMLHttpRequest object
+	createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+	jQuery.extend( jQuery.support, {
+		ajax: !!xhr,
+		cors: !!xhr && ( "withCredentials" in xhr )
+	});
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+	jQuery.ajaxTransport(function( s ) {
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( !s.crossDomain || jQuery.support.cors ) {
+
+			var callback;
+
+			return {
+				send: function( headers, complete ) {
+
+					// Get a new xhr
+					var xhr = s.xhr(),
+						handle,
+						i;
+
+					// Open the socket
+					// Passing null username, generates a login popup on Opera (#2865)
+					if ( s.username ) {
+						xhr.open( s.type, s.url, s.async, s.username, s.password );
+					} else {
+						xhr.open( s.type, s.url, s.async );
+					}
+
+					// Apply custom fields if provided
+					if ( s.xhrFields ) {
+						for ( i in s.xhrFields ) {
+							xhr[ i ] = s.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( s.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( s.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+						headers[ "X-Requested-With" ] = "XMLHttpRequest";
+					}
+
+					// Need an extra try/catch for cross domain requests in Firefox 3
+					try {
+						for ( i in headers ) {
+							xhr.setRequestHeader( i, headers[ i ] );
+						}
+					} catch( _ ) {}
+
+					// Do send the request
+					// This may raise an exception which is actually
+					// handled in jQuery.ajax (so no try/catch here)
+					xhr.send( ( s.hasContent && s.data ) || null );
+
+					// Listener
+					callback = function( _, isAbort ) {
+
+						var status,
+							statusText,
+							responseHeaders,
+							responses,
+							xml;
+
+						// Firefox throws exceptions when accessing properties
+						// of an xhr when a network error occured
+						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+						try {
+
+							// Was never called and is aborted or complete
+							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+								// Only called once
+								callback = undefined;
+
+								// Do not keep as active anymore
+								if ( handle ) {
+									xhr.onreadystatechange = jQuery.noop;
+									if ( xhrOnUnloadAbort ) {
+										delete xhrCallbacks[ handle ];
+									}
+								}
+
+								// If it's an abort
+								if ( isAbort ) {
+									// Abort it manually if needed
+									if ( xhr.readyState !== 4 ) {
+										xhr.abort();
+									}
+								} else {
+									status = xhr.status;
+									responseHeaders = xhr.getAllResponseHeaders();
+									responses = {};
+									xml = xhr.responseXML;
+
+									// Construct response list
+									if ( xml && xml.documentElement /* #4958 */ ) {
+										responses.xml = xml;
+									}
+									responses.text = xhr.responseText;
+
+									// Firefox throws an exception when accessing
+									// statusText for faulty cross-domain requests
+									try {
+										statusText = xhr.statusText;
+									} catch( e ) {
+										// We normalize with Webkit giving an empty statusText
+										statusText = "";
+									}
+
+									// Filter status for non standard behaviors
+
+									// If the request is local and we have data: assume a success
+									// (success with no data won't get notified, that's the best we
+									// can do given current implementations)
+									if ( !status && s.isLocal && !s.crossDomain ) {
+										status = responses.text ? 200 : 404;
+									// IE - #1450: sometimes returns 1223 when it should be 204
+									} else if ( status === 1223 ) {
+										status = 204;
+									}
+								}
+							}
+						} catch( firefoxAccessException ) {
+							if ( !isAbort ) {
+								complete( -1, firefoxAccessException );
+							}
+						}
+
+						// Call complete if needed
+						if ( responses ) {
+							complete( status, statusText, responses, responseHeaders );
+						}
+					};
+
+					// if we're in sync mode or it's in cache
+					// and has been retrieved directly (IE6 & IE7)
+					// we need to manually fire the callback
+					if ( !s.async || xhr.readyState === 4 ) {
+						callback();
+					} else {
+						handle = ++xhrId;
+						if ( xhrOnUnloadAbort ) {
+							// Create the active xhrs callbacks list if needed
+							// and attach the unload handler
+							if ( !xhrCallbacks ) {
+								xhrCallbacks = {};
+								jQuery( window ).unload( xhrOnUnloadAbort );
+							}
+							// Add to list of active xhrs callbacks
+							xhrCallbacks[ handle ] = callback;
+						}
+						xhr.onreadystatechange = callback;
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback(0,1);
+					}
+				}
+			};
+		}
+	});
+}
+
+
+
+
+var elemdisplay = {},
+	iframe, iframeDoc,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+	timerId,
+	fxAttrs = [
+		// height animations
+		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+		// width animations
+		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+		// opacity animations
+		[ "opacity" ]
+	],
+	fxNow;
+
+jQuery.fn.extend({
+	show: function( speed, easing, callback ) {
+		var elem, display;
+
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("show", 3), speed, easing, callback );
+
+		} else {
+			for ( var i = 0, j = this.length; i < j; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					// Reset the inline display of this element to learn if it is
+					// being hidden by cascaded rules or not
+					if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+						display = elem.style.display = "";
+					}
+
+					// Set elements which have been overridden with display: none
+					// in a stylesheet to whatever the default browser style is
+					// for such an element
+					if ( display === "" && jQuery.css(elem, "display") === "none" ) {
+						jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+					}
+				}
+			}
+
+			// Set the display of most of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					if ( display === "" || display === "none" ) {
+						elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
+					}
+				}
+			}
+
+			return this;
+		}
+	},
+
+	hide: function( speed, easing, callback ) {
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("hide", 3), speed, easing, callback);
+
+		} else {
+			var elem, display,
+				i = 0,
+				j = this.length;
+
+			for ( ; i < j; i++ ) {
+				elem = this[i];
+				if ( elem.style ) {
+					display = jQuery.css( elem, "display" );
+
+					if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
+						jQuery._data( elem, "olddisplay", display );
+					}
+				}
+			}
+
+			// Set the display of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				if ( this[i].style ) {
+					this[i].style.display = "none";
+				}
+			}
+
+			return this;
+		}
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+
+	toggle: function( fn, fn2, callback ) {
+		var bool = typeof fn === "boolean";
+
+		if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+			this._toggle.apply( this, arguments );
+
+		} else if ( fn == null || bool ) {
+			this.each(function() {
+				var state = bool ? fn : jQuery(this).is(":hidden");
+				jQuery(this)[ state ? "show" : "hide" ]();
+			});
+
+		} else {
+			this.animate(genFx("toggle", 3), fn, fn2, callback);
+		}
+
+		return this;
+	},
+
+	fadeTo: function( speed, to, easing, callback ) {
+		return this.filter(":hidden").css("opacity", 0).show().end()
+					.animate({opacity: to}, speed, easing, callback);
+	},
+
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed( speed, easing, callback );
+
+		if ( jQuery.isEmptyObject( prop ) ) {
+			return this.each( optall.complete, [ false ] );
+		}
+
+		// Do not change referenced properties as per-property easing will be lost
+		prop = jQuery.extend( {}, prop );
+
+		function doAnimation() {
+			// XXX 'this' does not always have a nodeName when running the
+			// test suite
+
+			if ( optall.queue === false ) {
+				jQuery._mark( this );
+			}
+
+			var opt = jQuery.extend( {}, optall ),
+				isElement = this.nodeType === 1,
+				hidden = isElement && jQuery(this).is(":hidden"),
+				name, val, p, e,
+				parts, start, end, unit,
+				method;
+
+			// will store per property easing and be used to determine when an animation is complete
+			opt.animatedProperties = {};
+
+			for ( p in prop ) {
+
+				// property name normalization
+				name = jQuery.camelCase( p );
+				if ( p !== name ) {
+					prop[ name ] = prop[ p ];
+					delete prop[ p ];
+				}
+
+				val = prop[ name ];
+
+				// easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+				if ( jQuery.isArray( val ) ) {
+					opt.animatedProperties[ name ] = val[ 1 ];
+					val = prop[ name ] = val[ 0 ];
+				} else {
+					opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+				}
+
+				if ( val === "hide" && hidden || val === "show" && !hidden ) {
+					return opt.complete.call( this );
+				}
+
+				if ( isElement && ( name === "height" || name === "width" ) ) {
+					// Make sure that nothing sneaks out
+					// Record all 3 overflow attributes because IE does not
+					// change the overflow attribute when overflowX and
+					// overflowY are set to the same value
+					opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+					// Set display property to inline-block for height/width
+					// animations on inline elements that are having width/height animated
+					if ( jQuery.css( this, "display" ) === "inline" &&
+							jQuery.css( this, "float" ) === "none" ) {
+
+						// inline-level elements accept inline-block;
+						// block-level elements need to be inline with layout
+						if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
+							this.style.display = "inline-block";
+
+						} else {
+							this.style.zoom = 1;
+						}
+					}
+				}
+			}
+
+			if ( opt.overflow != null ) {
+				this.style.overflow = "hidden";
+			}
+
+			for ( p in prop ) {
+				e = new jQuery.fx( this, opt, p );
+				val = prop[ p ];
+
+				if ( rfxtypes.test( val ) ) {
+
+					// Tracks whether to show or hide based on private
+					// data attached to the element
+					method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
+					if ( method ) {
+						jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
+						e[ method ]();
+					} else {
+						e[ val ]();
+					}
+
+				} else {
+					parts = rfxnum.exec( val );
+					start = e.cur();
+
+					if ( parts ) {
+						end = parseFloat( parts[2] );
+						unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+						// We need to compute starting value
+						if ( unit !== "px" ) {
+							jQuery.style( this, p, (end || 1) + unit);
+							start = ( (end || 1) / e.cur() ) * start;
+							jQuery.style( this, p, start + unit);
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] ) {
+							end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
+						}
+
+						e.custom( start, end, unit );
+
+					} else {
+						e.custom( start, val, "" );
+					}
+				}
+			}
+
+			// For JS strict compliance
+			return true;
+		}
+
+		return optall.queue === false ?
+			this.each( doAnimation ) :
+			this.queue( optall.queue, doAnimation );
+	},
+
+	stop: function( type, clearQueue, gotoEnd ) {
+		if ( typeof type !== "string" ) {
+			gotoEnd = clearQueue;
+			clearQueue = type;
+			type = undefined;
+		}
+		if ( clearQueue && type !== false ) {
+			this.queue( type || "fx", [] );
+		}
+
+		return this.each(function() {
+			var index,
+				hadTimers = false,
+				timers = jQuery.timers,
+				data = jQuery._data( this );
+
+			// clear marker counters if we know they won't be
+			if ( !gotoEnd ) {
+				jQuery._unmark( true, this );
+			}
+
+			function stopQueue( elem, data, index ) {
+				var hooks = data[ index ];
+				jQuery.removeData( elem, index, true );
+				hooks.stop( gotoEnd );
+			}
+
+			if ( type == null ) {
+				for ( index in data ) {
+					if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
+						stopQueue( this, data, index );
+					}
+				}
+			} else if ( data[ index = type + ".run" ] && data[ index ].stop ){
+				stopQueue( this, data, index );
+			}
+
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+					if ( gotoEnd ) {
+
+						// force the next step to be the last
+						timers[ index ]( true );
+					} else {
+						timers[ index ].saveState();
+					}
+					hadTimers = true;
+					timers.splice( index, 1 );
+				}
+			}
+
+			// start the next in the queue if the last step wasn't forced
+			// timers currently will call their complete callbacks, which will dequeue
+			// but only if they were gotoEnd
+			if ( !( gotoEnd && hadTimers ) ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	}
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	setTimeout( clearFxNow, 0 );
+	return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+	fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, num ) {
+	var obj = {};
+
+	jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
+		obj[ this ] = type;
+	});
+
+	return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx( "show", 1 ),
+	slideUp: genFx( "hide", 1 ),
+	slideToggle: genFx( "toggle", 1 ),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+});
+
+jQuery.extend({
+	speed: function( speed, easing, fn ) {
+		var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+			complete: fn || !fn && easing ||
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+		};
+
+		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+			opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+		// normalize opt.queue - true/undefined/null -> "fx"
+		if ( opt.queue == null || opt.queue === true ) {
+			opt.queue = "fx";
+		}
+
+		// Queueing
+		opt.old = opt.complete;
+
+		opt.complete = function( noUnmark ) {
+			if ( jQuery.isFunction( opt.old ) ) {
+				opt.old.call( this );
+			}
+
+			if ( opt.queue ) {
+				jQuery.dequeue( this, opt.queue );
+			} else if ( noUnmark !== false ) {
+				jQuery._unmark( this );
+			}
+		};
+
+		return opt;
+	},
+
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum;
+		}
+	},
+
+	timers: [],
+
+	fx: function( elem, options, prop ) {
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		options.orig = options.orig || {};
+	}
+
+});
+
+jQuery.fx.prototype = {
+	// Simple function for setting a style value
+	update: function() {
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
+	},
+
+	// Get the current size
+	cur: function() {
+		if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
+			return this.elem[ this.prop ];
+		}
+
+		var parsed,
+			r = jQuery.css( this.elem, this.prop );
+		// Empty strings, null, undefined and "auto" are converted to 0,
+		// complex values such as "rotate(1rad)" are returned as is,
+		// simple values such as "10px" are parsed to Float.
+		return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+	},
+
+	// Start an animation from one number to another
+	custom: function( from, to, unit ) {
+		var self = this,
+			fx = jQuery.fx;
+
+		this.startTime = fxNow || createFxNow();
+		this.end = to;
+		this.now = this.start = from;
+		this.pos = this.state = 0;
+		this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+
+		function t( gotoEnd ) {
+			return self.step( gotoEnd );
+		}
+
+		t.queue = this.options.queue;
+		t.elem = this.elem;
+		t.saveState = function() {
+			if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
+				jQuery._data( self.elem, "fxshow" + self.prop, self.start );
+			}
+		};
+
+		if ( t() && jQuery.timers.push(t) && !timerId ) {
+			timerId = setInterval( fx.tick, fx.interval );
+		}
+	},
+
+	// Simple 'show' function
+	show: function() {
+		var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
+
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		// Make sure that we start at a small width/height to avoid any flash of content
+		if ( dataShow !== undefined ) {
+			// This show is picking up where a previous hide or show left off
+			this.custom( this.cur(), dataShow );
+		} else {
+			this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
+		}
+
+		// Start by showing the element
+		jQuery( this.elem ).show();
+	},
+
+	// Simple 'hide' function
+	hide: function() {
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom( this.cur(), 0 );
+	},
+
+	// Each step of an animation
+	step: function( gotoEnd ) {
+		var p, n, complete,
+			t = fxNow || createFxNow(),
+			done = true,
+			elem = this.elem,
+			options = this.options;
+
+		if ( gotoEnd || t >= options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			options.animatedProperties[ this.prop ] = true;
+
+			for ( p in options.animatedProperties ) {
+				if ( options.animatedProperties[ p ] !== true ) {
+					done = false;
+				}
+			}
+
+			if ( done ) {
+				// Reset the overflow
+				if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+					jQuery.each( [ "", "X", "Y" ], function( index, value ) {
+						elem.style[ "overflow" + value ] = options.overflow[ index ];
+					});
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( options.hide ) {
+					jQuery( elem ).hide();
+				}
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( options.hide || options.show ) {
+					for ( p in options.animatedProperties ) {
+						jQuery.style( elem, p, options.orig[ p ] );
+						jQuery.removeData( elem, "fxshow" + p, true );
+						// Toggle data is no longer needed
+						jQuery.removeData( elem, "toggle" + p, true );
+					}
+				}
+
+				// Execute the complete function
+				// in the event that the complete function throws an exception
+				// we must ensure it won't be called twice. #5684
+
+				complete = options.complete;
+				if ( complete ) {
+
+					options.complete = false;
+					complete.call( elem );
+				}
+			}
+
+			return false;
+
+		} else {
+			// classical easing cannot be used with an Infinity duration
+			if ( options.duration == Infinity ) {
+				this.now = t;
+			} else {
+				n = t - this.startTime;
+				this.state = n / options.duration;
+
+				// Perform the easing function, defaults to swing
+				this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
+				this.now = this.start + ( (this.end - this.start) * this.pos );
+			}
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+};
+
+jQuery.extend( jQuery.fx, {
+	tick: function() {
+		var timer,
+			timers = jQuery.timers,
+			i = 0;
+
+		for ( ; i < timers.length; i++ ) {
+			timer = timers[ i ];
+			// Checks the timer has not already been removed
+			if ( !timer() && timers[ i ] === timer ) {
+				timers.splice( i--, 1 );
+			}
+		}
+
+		if ( !timers.length ) {
+			jQuery.fx.stop();
+		}
+	},
+
+	interval: 13,
+
+	stop: function() {
+		clearInterval( timerId );
+		timerId = null;
+	},
+
+	speeds: {
+		slow: 600,
+		fast: 200,
+		// Default speed
+		_default: 400
+	},
+
+	step: {
+		opacity: function( fx ) {
+			jQuery.style( fx.elem, "opacity", fx.now );
+		},
+
+		_default: function( fx ) {
+			if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+				fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+			} else {
+				fx.elem[ fx.prop ] = fx.now;
+			}
+		}
+	}
+});
+
+// Adds width/height step functions
+// Do not set anything below 0
+jQuery.each([ "width", "height" ], function( i, prop ) {
+	jQuery.fx.step[ prop ] = function( fx ) {
+		jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
+	};
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.animated = function( elem ) {
+		return jQuery.grep(jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		}).length;
+	};
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+	if ( !elemdisplay[ nodeName ] ) {
+
+		var body = document.body,
+			elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+			display = elem.css( "display" );
+		elem.remove();
+
+		// If the simple way fails,
+		// get element's real default display by attaching it to a temp iframe
+		if ( display === "none" || display === "" ) {
+			// No iframe to use yet, so create it
+			if ( !iframe ) {
+				iframe = document.createElement( "iframe" );
+				iframe.frameBorder = iframe.width = iframe.height = 0;
+			}
+
+			body.appendChild( iframe );
+
+			// Create a cacheable copy of the iframe document on first call.
+			// IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+			// document to it; WebKit & Firefox won't allow reusing the iframe document.
+			if ( !iframeDoc || !iframe.createElement ) {
+				iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+				iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" );
+				iframeDoc.close();
+			}
+
+			elem = iframeDoc.createElement( nodeName );
+
+			iframeDoc.body.appendChild( elem );
+
+			display = jQuery.css( elem, "display" );
+			body.removeChild( iframe );
+		}
+
+		// Store the correct default display
+		elemdisplay[ nodeName ] = display;
+	}
+
+	return elemdisplay[ nodeName ];
+}
+
+
+
+
+var rtable = /^t(?:able|d|h)$/i,
+	rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+	jQuery.fn.offset = function( options ) {
+		var elem = this[0], box;
+
+		if ( options ) {
+			return this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+		}
+
+		if ( !elem || !elem.ownerDocument ) {
+			return null;
+		}
+
+		if ( elem === elem.ownerDocument.body ) {
+			return jQuery.offset.bodyOffset( elem );
+		}
+
+		try {
+			box = elem.getBoundingClientRect();
+		} catch(e) {}
+
+		var doc = elem.ownerDocument,
+			docElem = doc.documentElement;
+
+		// Make sure we're not dealing with a disconnected DOM node
+		if ( !box || !jQuery.contains( docElem, elem ) ) {
+			return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+		}
+
+		var body = doc.body,
+			win = getWindow(doc),
+			clientTop  = docElem.clientTop  || body.clientTop  || 0,
+			clientLeft = docElem.clientLeft || body.clientLeft || 0,
+			scrollTop  = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop,
+			scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+			top  = box.top  + scrollTop  - clientTop,
+			left = box.left + scrollLeft - clientLeft;
+
+		return { top: top, left: left };
+	};
+
+} else {
+	jQuery.fn.offset = function( options ) {
+		var elem = this[0];
+
+		if ( options ) {
+			return this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+		}
+
+		if ( !elem || !elem.ownerDocument ) {
+			return null;
+		}
+
+		if ( elem === elem.ownerDocument.body ) {
+			return jQuery.offset.bodyOffset( elem );
+		}
+
+		var computedStyle,
+			offsetParent = elem.offsetParent,
+			prevOffsetParent = elem,
+			doc = elem.ownerDocument,
+			docElem = doc.documentElement,
+			body = doc.body,
+			defaultView = doc.defaultView,
+			prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+			top = elem.offsetTop,
+			left = elem.offsetLeft;
+
+		while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+			if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+				break;
+			}
+
+			computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+			top  -= elem.scrollTop;
+			left -= elem.scrollLeft;
+
+			if ( elem === offsetParent ) {
+				top  += elem.offsetTop;
+				left += elem.offsetLeft;
+
+				if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+					top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+					left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+				}
+
+				prevOffsetParent = offsetParent;
+				offsetParent = elem.offsetParent;
+			}
+
+			if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+				top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+				left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+			}
+
+			prevComputedStyle = computedStyle;
+		}
+
+		if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+			top  += body.offsetTop;
+			left += body.offsetLeft;
+		}
+
+		if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+			top  += Math.max( docElem.scrollTop, body.scrollTop );
+			left += Math.max( docElem.scrollLeft, body.scrollLeft );
+		}
+
+		return { top: top, left: left };
+	};
+}
+
+jQuery.offset = {
+
+	bodyOffset: function( body ) {
+		var top = body.offsetTop,
+			left = body.offsetLeft;
+
+		if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+			top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+			left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+		}
+
+		return { top: top, left: left };
+	},
+
+	setOffset: function( elem, options, i ) {
+		var position = jQuery.css( elem, "position" );
+
+		// set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		var curElem = jQuery( elem ),
+			curOffset = curElem.offset(),
+			curCSSTop = jQuery.css( elem, "top" ),
+			curCSSLeft = jQuery.css( elem, "left" ),
+			calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+			props = {}, curPosition = {}, curTop, curLeft;
+
+		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+			options = options.call( elem, i, curOffset );
+		}
+
+		if ( options.top != null ) {
+			props.top = ( options.top - curOffset.top ) + curTop;
+		}
+		if ( options.left != null ) {
+			props.left = ( options.left - curOffset.left ) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+
+jQuery.fn.extend({
+
+	position: function() {
+		if ( !this[0] ) {
+			return null;
+		}
+
+		var elem = this[0],
+
+		// Get *real* offsetParent
+		offsetParent = this.offsetParent(),
+
+		// Get correct offsets
+		offset       = this.offset(),
+		parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+		// Subtract element margins
+		// note: when an element has margin: auto the offsetLeft and marginLeft
+		// are the same in Safari causing offset.left to incorrectly be 0
+		offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+		offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+		// Add offsetParent borders
+		parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+		parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+		// Subtract the two offsets
+		return {
+			top:  offset.top  - parentOffset.top,
+			left: offset.left - parentOffset.left
+		};
+	},
+
+	offsetParent: function() {
+		return this.map(function() {
+			var offsetParent = this.offsetParent || document.body;
+			while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+			return offsetParent;
+		});
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+	var method = "scroll" + name;
+
+	jQuery.fn[ method ] = function( val ) {
+		var elem, win;
+
+		if ( val === undefined ) {
+			elem = this[ 0 ];
+
+			if ( !elem ) {
+				return null;
+			}
+
+			win = getWindow( elem );
+
+			// Return the scroll offset
+			return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+				jQuery.support.boxModel && win.document.documentElement[ method ] ||
+					win.document.body[ method ] :
+				elem[ method ];
+		}
+
+		// Set the scroll offset
+		return this.each(function() {
+			win = getWindow( this );
+
+			if ( win ) {
+				win.scrollTo(
+					!i ? val : jQuery( win ).scrollLeft(),
+					 i ? val : jQuery( win ).scrollTop()
+				);
+
+			} else {
+				this[ method ] = val;
+			}
+		});
+	};
+});
+
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ?
+		elem :
+		elem.nodeType === 9 ?
+			elem.defaultView || elem.parentWindow :
+			false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+	var type = name.toLowerCase();
+
+	// innerHeight and innerWidth
+	jQuery.fn[ "inner" + name ] = function() {
+		var elem = this[0];
+		return elem ?
+			elem.style ?
+			parseFloat( jQuery.css( elem, type, "padding" ) ) :
+			this[ type ]() :
+			null;
+	};
+
+	// outerHeight and outerWidth
+	jQuery.fn[ "outer" + name ] = function( margin ) {
+		var elem = this[0];
+		return elem ?
+			elem.style ?
+			parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
+			this[ type ]() :
+			null;
+	};
+
+	jQuery.fn[ type ] = function( size ) {
+		// Get window width or height
+		var elem = this[0];
+		if ( !elem ) {
+			return size == null ? null : this;
+		}
+
+		if ( jQuery.isFunction( size ) ) {
+			return this.each(function( i ) {
+				var self = jQuery( this );
+				self[ type ]( size.call( this, i, self[ type ]() ) );
+			});
+		}
+
+		if ( jQuery.isWindow( elem ) ) {
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			// 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+			var docElemProp = elem.document.documentElement[ "client" + name ],
+				body = elem.document.body;
+			return elem.document.compatMode === "CSS1Compat" && docElemProp ||
+				body && body[ "client" + name ] || docElemProp;
+
+		// Get document width or height
+		} else if ( elem.nodeType === 9 ) {
+			// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+			return Math.max(
+				elem.documentElement["client" + name],
+				elem.body["scroll" + name], elem.documentElement["scroll" + name],
+				elem.body["offset" + name], elem.documentElement["offset" + name]
+			);
+
+		// Get or set width or height on the element
+		} else if ( size === undefined ) {
+			var orig = jQuery.css( elem, type ),
+				ret = parseFloat( orig );
+
+			return jQuery.isNumeric( ret ) ? ret : orig;
+
+		// Set the width or height on the element (default to pixels if value is unitless)
+		} else {
+			return this.css( type, typeof size === "string" ? size : size + "px" );
+		}
+	};
+
+});
+
+
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+	define( "jquery", [], function () { return jQuery; } );
+}
+
+
+
+})( window );
diff --git a/docs/system-architecture/css/toc-0.1.2/example/live.js b/docs/system-architecture/css/toc-0.1.2/example/live.js
new file mode 100644
index 0000000..e48917b
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/example/live.js
@@ -0,0 +1,233 @@
+/*
+  Live.js - One script closer to Designing in the Browser
+  Written for Handcraft.com by Martin Kool (@mrtnkl).
+
+  Version 4.
+  Recent change: Made stylesheet and mimetype checks case insensitive.
+
+  http://livejs.com
+  http://livejs.com/license (MIT)  
+  @livejs
+
+  Include live.js#css to monitor css changes only.
+  Include live.js#js to monitor js changes only.
+  Include live.js#html to monitor html changes only.
+  Mix and match to monitor a preferred combination such as live.js#html,css  
+
+  By default, just include live.js to monitor all css, js and html changes.
+  
+  Live.js can also be loaded as a bookmarklet. It is best to only use it for CSS then,
+  as a page reload due to a change in html or css would not re-include the bookmarklet.
+  To monitor CSS and be notified that it has loaded, include it as: live.js#css,notify
+*/
+(function () {
+
+  var headers = { "Etag": 1, "Last-Modified": 1, "Content-Length": 1, "Content-Type": 1 },
+      resources = {},
+      pendingRequests = {},
+      currentLinkElements = {},
+      oldLinkElements = {},
+      interval = 1000,
+      loaded = false,
+      active = { "html": 1, "css": 1, "js": 1 };
+
+  var Live = {
+
+    // performs a cycle per interval
+    heartbeat: function () {      
+      if (document.body) {        
+        // make sure all resources are loaded on first activation
+        if (!loaded) Live.loadresources();
+        Live.checkForChanges();
+      }
+      setTimeout(Live.heartbeat, interval);
+    },
+
+    // loads all local css and js resources upon first activation
+    loadresources: function () {
+
+      // helper method to assert if a given url is local
+      function isLocal(url) {
+        var loc = document.location,
+            reg = new RegExp("^\\.|^\/(?!\/)|^[\\w]((?!://).)*$|" + loc.protocol + "//" + loc.host);
+        return url.match(reg);
+      }
+
+      // gather all resources
+      var scripts = document.getElementsByTagName("script"),
+          links = document.getElementsByTagName("link"),
+          uris = [];
+
+      // track local js urls
+      for (var i = 0; i < scripts.length; i++) {
+        var script = scripts[i], src = script.getAttribute("src");
+        if (src && isLocal(src))
+          uris.push(src);
+        if (src && src.match(/\blive.js#/)) {
+          for (var type in active)
+            active[type] = src.match("[#,|]" + type) != null
+          if (src.match("notify")) 
+            alert("Live.js is loaded.");
+        }
+      }
+      if (!active.js) uris = [];
+      if (active.html) uris.push(document.location.href);
+
+      // track local css urls
+      for (var i = 0; i < links.length && active.css; i++) {
+        var link = links[i], rel = link.getAttribute("rel"), href = link.getAttribute("href", 2);
+        if (href && rel && rel.match(new RegExp("stylesheet", "i")) && isLocal(href)) {
+          uris.push(href);
+          currentLinkElements[href] = link;
+        }
+      }
+
+      // initialize the resources info
+      for (var i = 0; i < uris.length; i++) {
+        var url = uris[i];
+        Live.getHead(url, function (url, info) {
+          resources[url] = info;
+        });
+      }
+
+      // add rule for morphing between old and new css files
+      var head = document.getElementsByTagName("head")[0],
+          style = document.createElement("style"),
+          rule = "transition: all .3s ease-out;"
+      css = [".livejs-loading * { ", rule, " -webkit-", rule, "-moz-", rule, "-o-", rule, "}"].join('');
+      style.setAttribute("type", "text/css");
+      head.appendChild(style);
+      style.styleSheet ? style.styleSheet.cssText = css : style.appendChild(document.createTextNode(css));
+
+      // yep
+      loaded = true;
+    },
+
+    // check all tracking resources for changes
+    checkForChanges: function () {
+      for (var url in resources) {
+        if (pendingRequests[url])
+          continue;
+
+        Live.getHead(url, function (url, newInfo) {
+          var oldInfo = resources[url],
+              hasChanged = false;
+          resources[url] = newInfo;
+          for (var header in oldInfo) {
+            // do verification based on the header type
+            var oldValue = oldInfo[header],
+                newValue = newInfo[header],
+                contentType = newInfo["Content-Type"];
+            switch (header.toLowerCase()) {
+              case "etag":
+                if (!newValue) break;
+                // fall through to default
+              default:
+                hasChanged = oldValue != newValue;
+                break;
+            }
+            // if changed, act
+            if (hasChanged) {
+              Live.refreshResource(url, contentType);
+              break;
+            }
+          }
+        });
+      }
+    },
+
+    // act upon a changed url of certain content type
+    refreshResource: function (url, type) {
+      switch (type.toLowerCase()) {
+        // css files can be reloaded dynamically by replacing the link element                               
+        case "text/css":
+          var link = currentLinkElements[url],
+              html = document.body.parentNode,
+              head = link.parentNode,
+              next = link.nextSibling,
+              newLink = document.createElement("link");
+
+          html.className = html.className.replace(/\s*livejs\-loading/gi, '') + ' livejs-loading';
+          newLink.setAttribute("type", "text/css");
+          newLink.setAttribute("rel", "stylesheet");
+          newLink.setAttribute("href", url + "?now=" + new Date() * 1);
+          next ? head.insertBefore(newLink, next) : head.appendChild(newLink);
+          currentLinkElements[url] = newLink;
+          oldLinkElements[url] = link;
+
+          // schedule removal of the old link
+          Live.removeoldLinkElements();
+          break;
+
+        // check if an html resource is our current url, then reload                               
+        case "text/html":
+          if (url != document.location.href)
+            return;
+
+          // local javascript changes cause a reload as well
+        case "text/javascript":
+        case "application/javascript":
+        case "application/x-javascript":
+          document.location.reload();
+      }
+    },
+
+    // removes the old stylesheet rules only once the new one has finished loading
+    removeoldLinkElements: function () {
+      var pending = 0;
+      for (var url in oldLinkElements) {
+        // if this sheet has any cssRules, delete the old link
+        try {
+          var link = currentLinkElements[url],
+              oldLink = oldLinkElements[url],
+              html = document.body.parentNode,
+              sheet = link.sheet || link.styleSheet,
+              rules = sheet.rules || sheet.cssRules;
+          if (rules.length >= 0) {
+            oldLink.parentNode.removeChild(oldLink);
+            delete oldLinkElements[url];
+            setTimeout(function () {
+              html.className = html.className.replace(/\s*livejs\-loading/gi, '');
+            }, 100);
+          }
+        } catch (e) {
+          pending++;
+        }
+        if (pending) setTimeout(Live.removeoldLinkElements, 50);
+      }
+    },
+
+    // performs a HEAD request and passes the header info to the given callback
+    getHead: function (url, callback) {
+      pendingRequests[url] = true;
+      var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XmlHttp");
+      xhr.open("HEAD", url, true);
+      xhr.onreadystatechange = function () {
+        delete pendingRequests[url];
+        if (xhr.readyState == 4 && xhr.status != 304) {
+          xhr.getAllResponseHeaders();
+          var info = {};
+          for (var h in headers) {
+            var value = xhr.getResponseHeader(h);
+            // adjust the simple Etag variant to match on its significant part
+            if (h.toLowerCase() == "etag" && value) value = value.replace(/^W\//, '');
+            if (h.toLowerCase() == "content-type" && value) value = value.replace(/^(.*?);.*?$/i, "$1");
+            info[h] = value;
+          }
+          callback(url, info);
+        }
+      }
+      xhr.send();
+    }
+  };
+
+  // start listening
+  if (document.location.protocol != "file:") {
+    if (!window.liveJsLoaded)
+      Live.heartbeat();
+
+    window.liveJsLoaded = true;
+  }
+  else if (window.console)
+    console.log("Live.js doesn't support the file protocol. It needs http.");    
+})();
\ No newline at end of file
diff --git a/docs/system-architecture/css/toc-0.1.2/lib/copyright.js b/docs/system-architecture/css/toc-0.1.2/lib/copyright.js
new file mode 100644
index 0000000..54b73bd
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/lib/copyright.js
@@ -0,0 +1,7 @@
+/*!
+  * jquery.toc.js - A jQuery plugin that will automatically generate a table of contents. 
+  * v0.1.1
+  * https://github.com/jgallen23/toc
+  * copyright JGA 2012
+  * MIT License
+  */
diff --git a/docs/system-architecture/css/toc-0.1.2/lib/toc.js b/docs/system-architecture/css/toc-0.1.2/lib/toc.js
new file mode 100644
index 0000000..ef8f0c0
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/lib/toc.js
@@ -0,0 +1,100 @@
+(function($) {
+$.fn.toc = function(options) {
+  var self = this;
+  var opts = $.extend({}, jQuery.fn.toc.defaults, options);
+
+  var container = $(opts.container);
+  var headings = $(opts.selectors, container);
+  var headingOffsets = [];
+  var activeClassName = opts.prefix+'-active';
+
+  var scrollTo = function(e) {
+    if (opts.smoothScrolling) {
+      e.preventDefault();
+      var elScrollTo = $(e.target).attr('href');
+      var $el = $(elScrollTo);
+
+      $('body,html').animate({ scrollTop: $el.offset().top }, 400, 'swing', function() {
+        location.hash = elScrollTo;
+      });
+    }
+    $('li', self).removeClass(activeClassName);
+    $(e.target).parent().addClass(activeClassName);
+  };
+
+  //highlight on scroll
+  var timeout;
+  var highlightOnScroll = function(e) {
+    if (timeout) {
+      clearTimeout(timeout);
+    }
+    timeout = setTimeout(function() {
+      var top = $(window).scrollTop(),
+        highlighted;
+      for (var i = 0, c = headingOffsets.length; i < c; i++) {
+        if (headingOffsets[i] >= top) {
+          $('li', self).removeClass(activeClassName);
+          highlighted = $('li:eq('+(i-1)+')', self).addClass(activeClassName);
+          opts.onHighlight(highlighted);
+          break;
+        }
+      }
+    }, 50);
+  };
+  if (opts.highlightOnScroll) {
+    $(window).bind('scroll', highlightOnScroll);
+    highlightOnScroll();
+  }
+
+  return this.each(function() {
+    //build TOC
+    var el = $(this);
+    var ul = $('<ul/>');
+    headings.each(function(i, heading) {
+      var $h = $(heading);
+      headingOffsets.push($h.offset().top - opts.highlightOffset);
+
+      //add anchor
+      var anchor = $('<span/>').attr('id', opts.anchorName(i, heading, opts.prefix)).insertBefore($h);
+
+      //build TOC item
+      var a = $('<a/>')
+        .text(opts.headerText(i, heading, $h))
+        .attr('href', '#' + opts.anchorName(i, heading, opts.prefix))
+        .bind('click', function(e) { 
+          scrollTo(e);
+          el.trigger('selected', $(this).attr('href'));
+        });
+
+      var li = $('<li/>')
+        .addClass(opts.itemClass(i, heading, $h, opts.prefix))
+        .append(a);
+
+      ul.append(li);
+    });
+    el.html(ul);
+  });
+};
+
+
+jQuery.fn.toc.defaults = {
+  container: 'body',
+  selectors: 'h1,h2,h3',
+  smoothScrolling: true,
+  prefix: 'toc',
+  onHighlight: function() {},
+  highlightOnScroll: true,
+  highlightOffset: 100,
+  anchorName: function(i, heading, prefix) {
+    return prefix+i;
+  },
+  headerText: function(i, heading, $heading) {
+    return $heading.text();
+  },
+  itemClass: function(i, heading, $heading, prefix) {
+    return prefix + '-' + $heading[0].tagName.toLowerCase();
+  }
+
+};
+
+})(jQuery);
diff --git a/docs/system-architecture/css/toc-0.1.2/package.json b/docs/system-architecture/css/toc-0.1.2/package.json
new file mode 100644
index 0000000..9bec26b
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "toc",
+  "version": "0.0.0",
+  "private": true,
+  "devDependencies": {
+    "mocha": "~1.8.2",
+    "grunt": "~0.4.1",
+    "grunt-reloadr": "~0.1.2",
+    "grunt-mocha": "~0.3.0",
+    "grunt-contrib-concat": "~0.1.3",
+    "grunt-contrib-uglify": "~0.2.0",
+    "grunt-contrib-jshint": "~0.3.0",
+    "grunt-contrib-connect": "~0.2.0",
+    "grunt-contrib-watch": "~0.3.1",
+    "grunt-plato": "~0.1.5"
+  }
+}
diff --git a/docs/system-architecture/css/toc-0.1.2/test/index.html b/docs/system-architecture/css/toc-0.1.2/test/index.html
new file mode 100644
index 0000000..e9e41c2
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/test/index.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>toc Tests</title>
+  <link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
+  <script src="../components/assert/assert.js"></script>
+  <script src="../node_modules/mocha/mocha.js"></script>
+  <!--<script src="../components/blanket/dist/qunit/blanket.min.js" data-cover-adapter="../components/blanket/src/adapters/mocha-blanket.js"></script>-->
+  <script>mocha.setup('tdd')</script>
+</head>
+<body>
+  <div id="mocha"></div>
+  <div id="fixture" style="display: none">
+
+  </div>
+  <script src="../dist/toc.js" data-cover></script>
+  <script src="toc.test.js"></script>
+  <script>
+    if (navigator.userAgent.indexOf('PhantomJS') < 0) {
+      mocha.run().globals(['LiveReload']);
+    }
+  </script>
+</body>
+</html>
diff --git a/docs/system-architecture/css/toc-0.1.2/test/toc.test.js b/docs/system-architecture/css/toc-0.1.2/test/toc.test.js
new file mode 100644
index 0000000..6171947
--- /dev/null
+++ b/docs/system-architecture/css/toc-0.1.2/test/toc.test.js
@@ -0,0 +1,4 @@
+
+suite('toc', function() {
+
+});
diff --git a/docs/system-architecture/diagrams/command-query.png b/docs/system-architecture/diagrams/command-query.png
new file mode 100644
index 0000000..4de18ec
--- /dev/null
+++ b/docs/system-architecture/diagrams/command-query.png
Binary files differ
diff --git a/docs/system-architecture/diagrams/command-query.xml b/docs/system-architecture/diagrams/command-query.xml
new file mode 100644
index 0000000..f244192
--- /dev/null
+++ b/docs/system-architecture/diagrams/command-query.xml
@@ -0,0 +1 @@
+<mxGraphModel dx="800" dy="800" grid="1" guides="1" tooltips="1" connect="1" fold="1" page="1" pageScale="1" pageWidth="826" pageHeight="1169" style="default-style2"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="47" value="Mifos X Platform &amp; API" style="verticalAlign=top;align=left;spacingTop=8;spacingLeft=2;spacingRight=12;shape=cube;size=10;direction=south;fontStyle=4;fillColor=#CCCCCC" parent="1" vertex="1"><mxGeometry x="120" y="60" width="590" height="440" as="geometry"/></mxCell><mxCell id="93" value="Oracle Mysql RDMS" style="shape=cylinder;whiteSpace=wrap" parent="1" vertex="1"><mxGeometry x="730" y="59.5" width="60" height="450.5" as="geometry"/></mxCell><mxCell id="95" value="Command" style="shape=message;whiteSpace=wrap" parent="1" vertex="1"><mxGeometry x="20" y="370" width="60" height="40" as="geometry"/></mxCell><mxCell id="96" value="Query" style="shape=message;whiteSpace=wrap" parent="1" vertex="1"><mxGeometry x="20" y="140" width="60" height="40" as="geometry"/></mxCell><mxCell id="97" value="" style="edgeStyle=none;exitX=1;exitY=0.5" parent="1" edge="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="80" y="384" as="sourcePoint"/><mxPoint x="120" y="384" as="targetPoint"/></mxGeometry></mxCell><mxCell id="98" value="" style="edgeStyle=none;exitX=1;exitY=0.5" parent="1" edge="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="80" y="150" as="sourcePoint"/><mxPoint x="120" y="150" as="targetPoint"/></mxGeometry></mxCell><mxCell id="99" value="" style="edgeStyle=none;exitX=1;exitY=0.5" parent="1" edge="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="120" y="400" as="sourcePoint"/><mxPoint x="80" y="400" as="targetPoint"/></mxGeometry></mxCell><mxCell id="100" value="" style="edgeStyle=none;exitX=1;exitY=0.5" parent="1" edge="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="120" y="170" as="sourcePoint"/><mxPoint x="80" y="170" as="targetPoint"/></mxGeometry></mxCell><mxCell id="106" value="" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;" vertex="1" parent="1"><mxGeometry x="134" y="280" width="546" height="205" as="geometry"/></mxCell><mxCell id="109" value="HTTPS API" style="rounded=1;whiteSpace=wrap;fillColor=#007FFF;rotation=-90" vertex="1" parent="1"><mxGeometry x="100" y="390" width="124" height="30" as="geometry"/></mxCell><mxCell id="108" value="" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;" vertex="1" parent="1"><mxGeometry x="130" y="92.5" width="550" height="175" as="geometry"/></mxCell><mxCell id="52" value="HTTPS API" style="rounded=1;whiteSpace=wrap;fillColor=#007FFF;rotation=-90" parent="1" vertex="1"><mxGeometry x="100" y="165" width="124" height="30" as="geometry"/></mxCell><mxCell id="112" value="1. Check Permissions" style="" vertex="1" parent="1"><mxGeometry x="210" y="120" width="170" height="30" as="geometry"/></mxCell><mxCell id="113" value="2. Fetch Data (SQL Query)" style="" vertex="1" parent="1"><mxGeometry x="240" y="165" width="170" height="30" as="geometry"/></mxCell><mxCell id="114" value="3. Covert To JSON response" style="" vertex="1" parent="1"><mxGeometry x="210" y="205" width="183" height="30" as="geometry"/></mxCell><mxCell id="115" value="1. Check Permissions" style="" vertex="1" parent="1"><mxGeometry x="200" y="310" width="170" height="30" as="geometry"/></mxCell><mxCell id="116" value="2. Locate Command Handler" style="" vertex="1" parent="1"><mxGeometry x="216.5" y="352.5" width="170" height="30" as="geometry"/></mxCell><mxCell id="117" value="3. Command Handler&#xa;Uses Domain Services&#xa;Model + ORM to perist changes&#xa;All changes passed back" style="" vertex="1" parent="1"><mxGeometry x="410" y="345" width="250" height="125" as="geometry"/></mxCell><mxCell id="118" value="4.Updated with &#xa;changes for Audit" style="" vertex="1" parent="1"><mxGeometry x="200" y="405" width="170" height="30" as="geometry"/></mxCell><mxCell id="119" value="5. Covert To JSON response" style="" vertex="1" parent="1"><mxGeometry x="200" y="440" width="183" height="30" as="geometry"/></mxCell></root></mxGraphModel>
\ No newline at end of file
diff --git a/docs/system-architecture/diagrams/platform-categories.png b/docs/system-architecture/diagrams/platform-categories.png
new file mode 100644
index 0000000..6c46464
--- /dev/null
+++ b/docs/system-architecture/diagrams/platform-categories.png
Binary files differ
diff --git a/docs/system-architecture/diagrams/platform-categories.xml b/docs/system-architecture/diagrams/platform-categories.xml
new file mode 100644
index 0000000..f63b817
--- /dev/null
+++ b/docs/system-architecture/diagrams/platform-categories.xml
@@ -0,0 +1 @@
+<mxGraphModel dx="800" dy="800" grid="1" guides="1" tooltips="1" connect="1" fold="1" page="1" pageScale="1" pageWidth="826" pageHeight="1169" style="default-style2"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="47" value="Mifos X Platform &amp; API" style="verticalAlign=top;align=left;spacingTop=8;spacingLeft=2;spacingRight=12;shape=cube;size=10;direction=south;fontStyle=4;fillColor=#CCCCCC" vertex="1" parent="1"><mxGeometry x="30" y="59.5" width="780" height="260.5" as="geometry"/></mxCell><mxCell id="48" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="118.5" y="22" width="25" height="42" as="geometry"/></mxCell><mxCell id="49" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="46" y="22" width="25" height="42" as="geometry"/></mxCell><mxCell id="50" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="757.5" y="22" width="25" height="42" as="geometry"/></mxCell><mxCell id="51" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="675" y="22" width="25" height="42" as="geometry"/></mxCell><mxCell id="52" value="HTTPS API" style="rounded=1;whiteSpace=wrap;fillColor=#007FFF" vertex="1" parent="1"><mxGeometry x="46" y="99.5" width="744" height="30" as="geometry"/></mxCell><mxCell id="77" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="191" y="22" width="25" height="42" as="geometry"/></mxCell><mxCell id="78" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="252.5" y="22" width="25" height="42" as="geometry"/></mxCell><mxCell id="79" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="382.5" y="22" width="25" height="42" as="geometry"/></mxCell><mxCell id="80" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="487.5" y="22" width="25" height="42" as="geometry"/></mxCell><mxCell id="86" value="Infrastructure" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;rotation=0" vertex="1" parent="1"><mxGeometry x="46" y="150" width="151.5" height="70" as="geometry"/></mxCell><mxCell id="87" value="User Administration" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;rotation=0" vertex="1" parent="1"><mxGeometry x="46" y="230" width="151.5" height="70" as="geometry"/></mxCell><mxCell id="88" value="Organisation Modelling" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;rotation=0" vertex="1" parent="1"><mxGeometry x="231" y="230" width="151.5" height="70" as="geometry"/></mxCell><mxCell id="89" value="Product Configuration" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;rotation=0" vertex="1" parent="1"><mxGeometry x="231" y="150" width="151.5" height="70" as="geometry"/></mxCell><mxCell id="90" value="Client Data" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;rotation=0" vertex="1" parent="1"><mxGeometry x="440" y="150" width="151.5" height="70" as="geometry"/></mxCell><mxCell id="91" value="Portfolio Management" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;rotation=0" vertex="1" parent="1"><mxGeometry x="624.25" y="150" width="151.5" height="70" as="geometry"/></mxCell><mxCell id="92" value="GL Account Management" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;rotation=0" vertex="1" parent="1"><mxGeometry x="523.5" y="230" width="151.5" height="70" as="geometry"/></mxCell></root></mxGraphModel>
\ No newline at end of file
diff --git a/docs/system-architecture/diagrams/platform-systemview.png b/docs/system-architecture/diagrams/platform-systemview.png
new file mode 100644
index 0000000..aa66031
--- /dev/null
+++ b/docs/system-architecture/diagrams/platform-systemview.png
Binary files differ
diff --git a/docs/system-architecture/diagrams/platform-systemview.xml b/docs/system-architecture/diagrams/platform-systemview.xml
new file mode 100644
index 0000000..9fba161
--- /dev/null
+++ b/docs/system-architecture/diagrams/platform-systemview.xml
@@ -0,0 +1 @@
+<mxGraphModel dx="800" dy="800" grid="1" guides="1" tooltips="1" connect="1" fold="1" page="1" pageScale="1" pageWidth="826" pageHeight="1169" style="default-style2"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="47" value="Mifos X Platform &amp; API" style="verticalAlign=top;align=left;spacingTop=8;spacingLeft=2;spacingRight=12;shape=cube;size=10;direction=south;fontStyle=4;fillColor=#CCCCCC" vertex="1" parent="1"><mxGeometry x="30" y="842" width="780" height="100" as="geometry"/></mxCell><mxCell id="48" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="118.5" y="804.5" width="25" height="42" as="geometry"/></mxCell><mxCell id="49" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="46" y="804.5" width="25" height="42" as="geometry"/></mxCell><mxCell id="50" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="757.5" y="804.5" width="25" height="42" as="geometry"/></mxCell><mxCell id="51" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="675" y="804.5" width="25" height="42" as="geometry"/></mxCell><mxCell id="52" value="HTTPS API" style="rounded=1;whiteSpace=wrap;fillColor=#007FFF" vertex="1" parent="1"><mxGeometry x="46" y="882" width="744" height="30" as="geometry"/></mxCell><mxCell id="53" value="Customer" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top" vertex="1" parent="1"><mxGeometry x="252.5" y="390" width="30" height="70" as="geometry"/></mxCell><mxCell id="54" value="Back-Office Apps" style="rounded=1;whiteSpace=wrap;rotation=-90" vertex="1" parent="1"><mxGeometry x="72.5" y="720" width="120" height="50" as="geometry"/></mxCell><mxCell id="55" value="Field Officer Apps" style="rounded=1;whiteSpace=wrap;rotation=-90" vertex="1" parent="1"><mxGeometry x="207.5" y="720" width="120" height="50" as="geometry"/></mxCell><mxCell id="56" value="Front Office / Telller Apps" style="rounded=1;whiteSpace=wrap;rotation=-90" vertex="1" parent="1"><mxGeometry x="143.5" y="720" width="120" height="50" as="geometry"/></mxCell><mxCell id="57" value="Public Facing Information Apps" style="rounded=1;whiteSpace=wrap;rotation=-90" vertex="1" parent="1"><mxGeometry x="-1.5" y="719" width="120" height="50" as="geometry"/></mxCell><mxCell id="58" value="Mobile Money Apps" style="rounded=1;whiteSpace=wrap;rotation=-90" vertex="1" parent="1"><mxGeometry x="440" y="720" width="120" height="50" as="geometry"/></mxCell><mxCell id="59" value="ATM / Card Services Apps" style="rounded=1;whiteSpace=wrap;rotation=-90" vertex="1" parent="1"><mxGeometry x="335" y="720" width="120" height="50" as="geometry"/></mxCell><mxCell id="60" value="ATM / POS / Card Services" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top" vertex="1" parent="1"><mxGeometry x="380" y="530" width="30" height="70" as="geometry"/></mxCell><mxCell id="61" value="Social Performance App" style="rounded=1;whiteSpace=wrap;rotation=-90" vertex="1" parent="1"><mxGeometry x="645" y="720" width="120" height="50" as="geometry"/></mxCell><mxCell id="62" value="Reporting (third parties e.g. Mix)" style="rounded=1;whiteSpace=wrap;rotation=-90" vertex="1" parent="1"><mxGeometry x="710" y="720" width="120" height="50" as="geometry"/></mxCell><mxCell id="63" value="Agent&#xa;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top" vertex="1" parent="1"><mxGeometry x="615" y="540" width="30" height="70" as="geometry"/></mxCell><mxCell id="64" value="Third Parties / Industry" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top" vertex="1" parent="1"><mxGeometry x="742.5" y="390" width="30" height="70" as="geometry"/></mxCell><mxCell id="65" value="MFI Staff" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top" vertex="1" parent="1"><mxGeometry x="140" y="530" width="30" height="70" as="geometry"/></mxCell><mxCell id="66" value="" style="edgeStyle=none" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="240" y="470" as="sourcePoint"/><mxPoint x="170" y="520" as="targetPoint"/></mxGeometry></mxCell><mxCell id="67" value="" style="edgeStyle=none" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="300" y="470" as="sourcePoint"/><mxPoint x="380" y="520" as="targetPoint"/></mxGeometry></mxCell><mxCell id="68" value="" style="edgeStyle=none" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="290" y="420" as="sourcePoint"/><mxPoint x="630" y="420" as="targetPoint"/></mxGeometry></mxCell><mxCell id="69" value="" style="edgeStyle=none;entryX=1;entryY=0.5" edge="1" target="58" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="500" y="420" as="sourcePoint"/><mxPoint x="650" y="430" as="targetPoint"/></mxGeometry></mxCell><mxCell id="70" value="" style="edgeStyle=none;" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="629" y="420" as="sourcePoint"/><mxPoint x="630" y="530" as="targetPoint"/></mxGeometry></mxCell><mxCell id="71" value="Agent Apps" style="rounded=1;whiteSpace=wrap;rotation=-90" vertex="1" parent="1"><mxGeometry x="570" y="720" width="120" height="50" as="geometry"/></mxCell><mxCell id="72" value="" style="edgeStyle=none;" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="629" y="635" as="sourcePoint"/><mxPoint x="630" y="680" as="targetPoint"/></mxGeometry></mxCell><mxCell id="73" value="" style="edgeStyle=none;" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="394.5" y="620" as="sourcePoint"/><mxPoint x="395.5" y="665" as="targetPoint"/></mxGeometry></mxCell><mxCell id="74" value="" style="edgeStyle=none;" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="770" y="680" as="sourcePoint"/><mxPoint x="770" y="490" as="targetPoint"/></mxGeometry></mxCell><mxCell id="75" value="" style="edgeStyle=none;" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="58.5" y="680" as="sourcePoint"/><mxPoint x="60" y="420" as="targetPoint"/></mxGeometry></mxCell><mxCell id="76" value="" style="edgeStyle=none" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="58.5" y="420" as="sourcePoint"/><mxPoint x="240" y="420" as="targetPoint"/></mxGeometry></mxCell><mxCell id="77" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="191" y="804.5" width="25" height="42" as="geometry"/></mxCell><mxCell id="78" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="252.5" y="804.5" width="25" height="42" as="geometry"/></mxCell><mxCell id="79" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="382.5" y="804.5" width="25" height="42" as="geometry"/></mxCell><mxCell id="80" value="" style="shape=lollipop;direction=south;rotation=-90" vertex="1" parent="1"><mxGeometry x="487.5" y="804.5" width="25" height="42" as="geometry"/></mxCell><mxCell id="81" value="" style="edgeStyle=none" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="130" y="640" as="sourcePoint"/><mxPoint x="270" y="640" as="targetPoint"/></mxGeometry></mxCell><mxCell id="82" value="" style="edgeStyle=none;" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="130" y="640" as="sourcePoint"/><mxPoint x="131" y="685" as="targetPoint"/></mxGeometry></mxCell><mxCell id="83" value="" style="edgeStyle=none;" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="264.5" y="640" as="sourcePoint"/><mxPoint x="265.5" y="685" as="targetPoint"/></mxGeometry></mxCell><mxCell id="84" value="" style="edgeStyle=none;" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="207" y="640" as="sourcePoint"/><mxPoint x="208" y="685" as="targetPoint"/></mxGeometry></mxCell><mxCell id="85" value="" style="edgeStyle=none;" edge="1" parent="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="180" y="600" as="sourcePoint"/><mxPoint x="210" y="640" as="targetPoint"/></mxGeometry></mxCell></root></mxGraphModel>
\ No newline at end of file
diff --git a/docs/system-architecture/favicon.ico b/docs/system-architecture/favicon.ico
new file mode 100644
index 0000000..be74abd
--- /dev/null
+++ b/docs/system-architecture/favicon.ico
Binary files differ
diff --git a/docs/system-architecture/humans.txt b/docs/system-architecture/humans.txt
new file mode 100644
index 0000000..d9e1bb9
--- /dev/null
+++ b/docs/system-architecture/humans.txt
@@ -0,0 +1,15 @@
+# humanstxt.org/
+# The humans responsible & technology colophon
+
+# TEAM
+
+    <name> -- <role> -- <twitter>
+
+# THANKS
+
+    <name>
+
+# TECHNOLOGY COLOPHON
+
+    HTML5, CSS3
+    Normalize.css, jQuery, Modernizr
diff --git a/docs/system-architecture/img/mifos-icon.png b/docs/system-architecture/img/mifos-icon.png
new file mode 100644
index 0000000..bb02523
--- /dev/null
+++ b/docs/system-architecture/img/mifos-icon.png
Binary files differ
diff --git a/docs/system-architecture/index.html b/docs/system-architecture/index.html
new file mode 100644
index 0000000..fdf12d4
--- /dev/null
+++ b/docs/system-architecture/index.html
@@ -0,0 +1,587 @@
+<!doctype html>
+<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+        <title>Mifos Platform Docs: System Architecture</title>
+        <meta name="description" content="mifos platform documentation">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+        <link rel="stylesheet" href="css/bootstrap-3.0.0/bootstrap.min.css">
+        <link href="css/bootstrap-3.0.0/bootstrap-theme.min.css" rel="stylesheet">
+<style>
+#wrapper {
+    margin: 0 20px 0 220px;
+}
+a {
+    color: #336699;
+}
+body {
+    font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
+    font-size: 16px;
+    line-height: 24px;
+    margin: 0;
+    padding: 0;
+}
+h1, h2, h3, #toc a {
+    font-family: 'Voces',sans-serif;
+}
+p, ul {
+    margin-bottom: 24px;
+    margin-top: 0;
+}
+small {
+    font-size: 10px;
+}
+code {
+    word-wrap: break-word;
+}
+.repo-stats iframe {
+    float: right;
+    margin-left: 5px;
+}
+#toc {
+    background: none repeat scroll 0 0 #333333;
+    box-shadow: -5px 0 5px 0 #000000 inset;
+    color: #FFFFFF;
+    height: 100%;
+    left: 0;
+    position: fixed;
+    top: 0;
+    width: 215px;
+}
+#toc ul {
+    list-style: none outside none;
+    margin: 0;
+    padding: 0;
+}
+#toc li {
+    padding: 5px 10px;
+}
+#toc a {
+    color: #FFFFFF;
+    display: block;
+    text-decoration: none;
+}
+#toc .toc-h2 {
+    padding-left: 10px;
+}
+#toc .toc-h3 {
+    padding-left: 20px;
+}
+#toc .toc-active {
+    background: none repeat scroll 0 0 #336699;
+    box-shadow: -5px 0 10px -5px #000000 inset;
+}
+pre {
+    background: none repeat scroll 0 0 #333333;
+    color: #F8F8F8;
+    display: block;
+    padding: 0.5em;
+}
+pre .shebang, pre .comment, pre .template_comment, pre .javadoc {
+    color: #7C7C7C;
+}
+pre .keyword, pre .tag, pre .ruby .function .keyword, pre .tex .command {
+    color: #96CBFE;
+}
+pre .function .keyword, pre .sub .keyword, pre .method, pre .list .title {
+    color: #FFFFB6;
+}
+pre .string, pre .tag .value, pre .cdata, pre .filter .argument, pre .attr_selector, pre .apache .cbracket, pre .date {
+    color: #A8FF60;
+}
+pre .subst {
+    color: #DAEFA3;
+}
+pre .regexp {
+    color: #E9C062;
+}
+pre .function .title, pre .sub .identifier, pre .pi, pre .decorator, pre .ini .title, pre .tex .special {
+    color: #FFFFB6;
+}
+pre .class .title, pre .haskell .label, pre .constant, pre .smalltalk .class, pre .javadoctag, pre .yardoctag, pre .phpdoc, pre .nginx .built_in {
+    color: #FFFFB6;
+}
+pre .symbol, pre .ruby .symbol .string, pre .ruby .symbol .keyword, pre .ruby .symbol .keymethods, pre .number, pre .variable, pre .vbscript, pre .literal {
+    color: #C6C5FE;
+}
+pre .css .tag {
+    color: #96CBFE;
+}
+pre .css .rules .property, pre .css .id {
+    color: #FFFFB6;
+}
+pre .css .class {
+    color: #FFFFFF;
+}
+pre .hexcolor {
+    color: #C6C5FE;
+}
+pre .number {
+    color: #FF73FD;
+}
+pre .tex .formula {
+    opacity: 0.7;
+}
+.github-repo {
+    background: url("images/Octocat.png") no-repeat scroll right bottom rgba(0, 0, 0, 0);
+    border: 1px solid #CCCCCC;
+    border-radius: 5px;
+    box-shadow: 0 0 3px #333333;
+    font-family: Helvetica,Arial,sans-serif;
+    font-size: 14px;
+    letter-spacing: 1px;
+    position: relative;
+}
+.github-repo a {
+    text-decoration: none;
+}
+.github-repo a:hover {
+    text-decoration: underline;
+}
+.github-repo .repo-header {
+    background: none repeat scroll 0 0 rgba(240, 240, 240, 0.7);
+}
+.github-repo .repo-header .repo-stats {
+    float: right;
+    font-size: 12px;
+    line-height: 20px;
+    margin: 5px 10px 0 0;
+}
+.github-repo .repo-header .repo-stats span, .github-repo .repo-header .repo-stats a {
+    color: #000000;
+    padding: 0 5px 0 25px;
+}
+.github-repo .repo-header .repo-stats .repo-watchers {
+    background: url("images/repostat.png") no-repeat scroll 5px -5px rgba(0, 0, 0, 0);
+}
+.github-repo .repo-header .repo-stats .repo-forks {
+    background: url("images/repostat.png") no-repeat scroll 5px -55px rgba(0, 0, 0, 0);
+}
+.github-repo .repo-header .repo-stats .repo-twitter {
+    float: right;
+    margin-left: 5px;
+}
+.github-repo .repo-header .repo-name {
+    display: block;
+    font-size: 18px;
+    letter-spacing: 2px;
+    padding: 5px 10px;
+}
+.github-repo .repo-commit {
+    padding: 10px;
+}
+.github-repo .repo-commit .repo-commit-message {
+    color: #000000;
+}
+.github-repo .repo-commit .repo-commit-date {
+    color: #333333;
+    display: block;
+    font-size: 12px;
+    font-style: italic;
+    letter-spacing: 0;
+    margin-top: 10px;
+}
+</style>
+
+        <script src="js/vendor/modernizr-2.6.2.min.js"></script>
+
+        <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+        <!--[if lt IE 9]>
+        <script src="js/vendor/bootstrap-3.0.0/assets/html5shiv.js"></script>
+        <script src="js/vendor/bootstrap-3.0.0/assets/respond.min.js"></script>
+        <![endif]-->
+    </head>
+    <body>
+        <!--[if lt IE 7]>
+            <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
+        <![endif]-->
+<div id="toc">
+</div>
+<div id="wrapper">
+<aside>
+    <h1>Mifos Platform Software Architecture Document</h1>
+    <section>
+        <h2>Summary</h2>
+        <p>
+            This document captures the major architectural decisions in platform. The purpose of the document is to provide a guide to the overall structure of the platform; where it fits in the overall context of an MIS solution and its internals so that contributors can more effectively understand how changes that they are considering can be made, and the consequences of those changes.
+        </p>
+        <p>
+            The target audience for this report is both system integrators (who will use the document to gain an understanding of the structure of the platform and its design rationale) and platform contributors who will use the document to reason about future changes and who will update the document as the system evolves.
+        </p>
+    </section>
+
+  <section>
+    <h2>Introduction</h2>
+    <section>
+        <h4>The Idea</h4>
+        <p>Mifos was an idea born out of a wish to create and deploy technology that allows the microfinance industry to scale. The goal is to:
+            <ul>
+                <li>Produce a gold standard management information system suitable for microfinance operations</li>
+                <li>Acts as the basis of a <strong>platform for microfinance</strong></li>
+                <li>Open source, owned and driven by member organisations in the community</li>
+                <li>Enabling potential for eco-system of providers located near to MFIs</li>
+            </ul>
+        </p>
+
+        <h4>History</h4>
+        <p>
+            <ul>
+                <li>2006: Project intiated by <a href="http://www.grameenfoundation.org/" target="_blank">Grameen Foundation</a></li>
+                <li>Late 2011: Grameen Foundation handed over full responsibility to open source community.</li>
+                <li>2012: Mifos X platform started. Previous members of project come together under the name of Community for Open Source Microfinance (COSM / <a href="http://www.openmf.org" target="_blank">OpenMF</a>)</li>
+                <li>2013: COSM / OpenMF officially rebranded to <strong>Mifos Initiative</strong> and receive US 501c3 status.</li>
+            </ul>
+        </p>
+
+        <h4>Project Related</h4>
+        <p>
+            <ul>
+                <li>Project url is <a href="https://github.com/openMF/mifosx" target="_blank">https://github.com/openMF/mifosx</a>
+                </li>
+                <li>Download from <a href="https://sourceforge.net/projects/mifos/" target="_blank">https://sourceforge.net/projects/mifos/</a>
+                </li>
+                <li><a href="https://sourceforge.net/projects/mifos/files/Mifos%20X/stats/timeline?dates=2012-04-25+to+2013-11-16" target="_blank">Download stats</a>
+                </li>
+            </ul>
+        </p>
+    </section>
+  </section>
+
+    <section>
+        <h2>System Overview</h2>
+        <img src="diagrams/platform-systemview.png" alt="System view diagram" />
+        <p>
+            Financial institutions deliver their services to customers through a variety of means today.
+            <ul>
+                <li>Customers can call direct into branches (teller model)</li>
+                <li>Customers can organise into groups (or centers) and agree to meetup at a location and time with FI staff (traditional microfinance).</li>
+                <li>An FI might have a public facing information portal that customers can use for variety of reasons including account management (online banking).</li>
+                <li>An FI might be integrated into a ATM/POS/Card services network that the customer can use.</li>
+                <li>An FI might be integrated with a mobile money operator and support mobile money services for customer (present/future microfinance).</li>
+                <li>An FI might use third party agents to sell on products/services from other banks/FIs.</li>
+            </ul>
+        </p>
+        <p>
+            As illustrated in the above diagram, the various stakeholders leverage <strong>business apps</strong> to perform specific customer or FI related actions. The functionality contained in these business apps can be bundled up and packaged in any way. In the diagram, several of the apps may be combined into one app or any one of the blocks representing an app could be further broken up as needed.
+        </p>
+        <p>
+            The platform is the <strong>core engine of the MIS</strong>. It hides alot of the complexity that exists in the business and technical domains needed for an MIS in FIs behind a relatively simple API. It is this API that <strong>frees up app developers to innovate and produce apps</strong> that can be as general or as bespoke as FIs need them to be.
+        </p>
+    </section>
+
+    <section>
+        <h2>Functional Overview</h2>
+        <p>As ALL capabilities of the platform are exposed through an API, The API docs are the best place to view a detailed breakdown of what the platform does. See <a href="https://demo.openmf.org/api-docs/apiLive.htm" target="_blank">online API Documentation</a>.
+        </p>
+        <img    src="diagrams/platform-categories.png" 
+                alt="platform functionality view" />
+        <p>At a higher level though we see the capabilities fall into the following categories:
+            <ul>
+                <li>
+                    Infrastructure
+                    <ul>
+                        <li>Codes</li>
+                        <li>Extensible Data Tables</li>
+                        <li>Reporting</li>
+                    </ul>
+                </li>
+                <li>
+                    User Administration
+                    <ul>
+                        <li>Users</li>
+                        <li>Roles</li>
+                        <li>Permissions</li>
+                    </ul>
+                </li>
+                <li>Organisation Modelling
+                    <ul>
+                        <li>Offices</li>
+                        <li>Staff</li>
+                        <li>Currency</li>
+                    </ul>
+                </li>
+                <li>Product Configuration
+                    <ul>
+                        <li>Charges</li>
+                        <li>Loan Products</li>
+                        <li>Deposit Products</li>
+                    </ul>
+                </li>
+                <li>
+                    Client Data
+                     <ul>
+                        <li>Know Your Client (KYC)</li>
+                    </ul>
+                </li>
+                <li>Portfolio Management
+                    <ul>
+                        <li>Loan Accounts</li>
+                        <li>Deposit Accounts</li>
+                        <li>Client/Groups</li>
+                    </ul>
+                </li>
+                <li>GL Account Management 
+                    <ul>
+                        <li>Chart of Accounts</li>
+                        <li>General Ledger</li>
+                    </ul>
+                </li>
+            </ul>
+        </p>
+    </section>
+
+    <section>
+        <h2>Technology</h2>
+        <p>
+            <ul>
+                <li>Java 7: <a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html" target="_blank">http://www.oracle.com/technetwork/java/javase/downloads/index.html</a></li>
+
+                <li>JAX-RS 1.0: <a href="https://wikis.oracle.com/display/Jersey/Overview+of+JAX-RS+1.0+Features" target="_blank">using Jersey (1.17.x)</a></li>
+
+                <li><a href="http://www.json.org/" target="_blank">JSON</a> using <a href="http://code.google.com/p/google-gson/" target="_blank">Google GSON</a></li>
+
+                <li>
+                    Spring I/O Platform: <a href="http://spring.io/platform" target="_blank">http://spring.io/platform</a>
+                    <ul>
+                        <li><a  href="http://projects.spring.io/spring-framework"
+                                target="_blank">Spring Framework</a></li>
+                        <li><a  href="http://projects.spring.io/spring-security"
+                                target="_blank">Spring Security</a></li>
+                        <li><a  href="http://projects.spring.io/spring-data/"
+                                target="_blank">Spring Data (JPA) backed by Hibernate</a></li>
+                    </ul>
+                </li>
+                
+                <li>MySQL: <a href="http://www.oracle.com/us/products/mysql/overview/index.html" target="_blank">http://www.oracle.com/us/products/mysql/overview/index.html</a></li>
+            </ul>
+        </p>
+    </section>
+
+    <section>
+        <h2>Principals</h2>
+        <h3>RESTful API</h3>
+        <p>
+            The platform exposes all its functionality via a <strong>practically-RESTful API</strong>, that communicates using JSON.<p>
+        <p>
+            We use the term <strong>practically-RESTful</strong> in order to make it clear we are not trying to be <a href="http://en.wikipedia.org/wiki/Representational_state_transfer" target="_blank">fully REST compliant</a> but still maintain important RESTful attributes like:
+            <ul>
+                <li>
+                    Stateless: platform maintains no conversational or session-based state. The result of this is ability to scale horizontally with ease.
+                </li>
+                <li>
+                    Resource-oriented: API is focussed around set of resources using HTTP vocabulary and conventions e.g GET, PUT, POST, DELETE, HTTP status codes. This results in a simple and consistent API for clients.
+                </li>
+            </ul>
+
+            See <a href="https://demo.openmf.org/api-docs/apiLive.htm" target="_blank">online API Documentation</a> for more detail.
+        </p>
+        <h3>Multi-tenanted</h3>
+        <p>
+            The mifos platform has been developed with support for multi-tenancy at the core of its design. This means that it is just as easy to use the platform for Software-as-a-Service (SaaS) type offerings as it is for local installations.
+        </p>
+        <p>
+            The platform uses an approach that isolates an FIs data per database/schema (<a href="http://msdn.microsoft.com/en-us/library/aa479086.aspx#mlttntda_topic2" target="_blank">See Separate Databases and Shared Database, Separate Schemas</a>). 
+        </p>
+        <h3>Extensible</h3>
+        <p>
+            Whilst each tenant will have a set of core tables, the platform tables can be extended in different ways for each tenant through the use of <a href="#datatables">Data tables</a> functionality.
+        </p>
+
+        <h3>Command Query Seperation</h3>
+        <p>We seperate <strong>commands</strong> (that change data) from <strong>queries</strong> (that read data).</p>
+        <p>Why? There are numerous reasons for choosing this approach which at present is not an attempt at full blown CQRS. The main advantages at present are:
+            <ul>
+                <li>State changing commands are persisted providing an audit of all state changes.</li>
+                <li>Used to support a general approach to <strong>maker-checker</strong>.</li>
+                <li>State changing commands use the Object-Oriented paradign (and hence ORM) whilst querys can stay in the data paradigm.</li>
+            </ul>
+        </p>
+
+        <h3>Maker-Checker</h3>
+        <p>Also known as <strong>four-eyes principal</strong>. Enables apps to support a maker-checker style workflow process. Commands that pass validation will be persisted. Maker-checker can be enabled/disabled at fine-grained level for any state changing API.</p>
+
+        <h3>Fine grained access control</h3>
+        <p>A fine grained permission is associated with each API. Administrators have fine grained control over what roles or users have access to.</p>
+    </section>
+
+    <section>
+        <h2>Code Packaging</h2>
+        <p>
+            The intention is for platform code to be packaged in a vertical slice way (as opposed to layers).<br>
+            Source code starts from <a href="https://github.com/openMF/mifosx/tree/master/mifosng-provider/src/main/java/org/mifosplatform">mifosng-provider/src/main/java/org/mifosplatform</a><br>
+            
+            <ul><strong>org.mifosplatform.</strong>
+                <li>accounting</li>
+                <li>useradministration</li>
+                <li>infrastructure</li>
+                <li>portfolio
+                     <ul>
+                        <li>charge</li>
+                        <li>client</li>
+                        <li>fund</li>
+                        <li>loanaccount</li>
+                    </ul>
+                </li>
+                <li>accounting</li>
+            </ul>
+
+            Within each vertical slice is some common packaging structure:
+
+             <ul><strong>org.mifosplatform.useradministration.</strong>
+                <li>api - XXXApiResource.java - REST api implementation files</li>
+                <li>handler - XXXCommandHandler.java - specific handlers invoked</li>
+                <li>service - contains read + write services for functional area</li>
+                <li>domain - OO concepts for the functional area</li>
+                <li>data - Data concepts for the area</li>
+                <li>serialization - ability to convert from/to API JSON for functional area</li>
+            </ul>
+        </p>
+    </section>
+
+    <section>
+        <h2>Design Overview</h2>
+        <p><strong>Note</strong>: The implementation of the platform code to process commands through handlers whilst supporting maker-checker and authorisation checks is a little bit convoluted at present and is an area pin-pointed for clean up to make it easier to on board new platform developers. In the mean time below content is used to explain its workings at present.</p>
+        <img src="diagrams/command-query.png" alt="Command-Query Overview" />
+        <p>
+            Taking into account example shown above for the <strong>users</strong> resource.
+            <ol>
+                <li>Query: GET /users</li>
+                <li>HTTPS API: retrieveAll method on org.mifosplatform.useradministration.api.UsersApiResource invoked</li>
+                <li>UsersApiResource.retrieveAll: Check user has permission to access this resources data.</li>
+                <li>UsersApiResource.retrieveAll: Use 'read service' to fetch all users data ('read services' execute simple SQL queries against Database using JDBC)</li>
+                <li>UsersApiResource.retrieveAll: Data returned to coverted into JSON response</li>
+            </ol>
+
+            <ol>
+                <li>Command: POST /users (Note: data passed in request body)</li>
+                <li>HTTPS API: <strong>create</strong> method on <strong>org.mifosplatform.useradministration.api.UsersApiResource</strong> invoked</li>
+            </ol>
+
+<pre><code>@POST
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+public String create(final String apiRequestBodyAsJson) {
+
+    final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+            .createUser() //
+            .withJson(apiRequestBodyAsJson) //
+            .build();
+
+    final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+    return this.toApiJsonSerializer.serialize(result);
+}
+</code></pre>
+<p><em>Description:</em> Create a CommandWrapper object that represents this create user command and JSON request body. Pass off responsiblity for processing to <strong>PortfolioCommandSourceWritePlatformService.logCommandSource</strong>.</p>
+
+<pre><code>
+@Override
+public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) {
+
+    boolean isApprovedByChecker = false;
+    // check if is update of own account details
+    if (wrapper.isUpdateOfOwnUserDetails(this.context.authenticatedUser().getId())) {
+        // then allow this operation to proceed.
+        // maker checker doesnt mean anything here.
+        isApprovedByChecker = true; // set to true in case permissions have
+                                    // been maker-checker enabled by
+                                    // accident.
+    } else {
+        // if not user changing their own details - check user has
+        // permission to perform specific task.
+        this.context.authenticatedUser().validateHasPermissionTo(wrapper.getTaskPermissionName());
+    }
+    validateIsUpdateAllowed();
+
+    final String json = wrapper.getJson();
+    CommandProcessingResult result = null;
+    try {
+        final JsonElement parsedCommand = this.fromApiJsonHelper.parse(json);
+        final JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(),
+                wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(),
+                wrapper.getSavingsId(), wrapper.getCodeId(), wrapper.getSupportedEntityType(), wrapper.getSupportedEntityId(),
+                wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId());
+
+        result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
+    } catch (final RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
+
+        result = this.processAndLogCommandService.logCommand(e.getCommandSourceResult());
+    }
+
+    return result;
+}
+</code></pre>
+<p><em>Description:</em> check user has permission for this action. if ok, a) parse the json request body, b) create a JsonCommand object to wrap the command details, c) use <strong>CommandProcessingService</strong> to handle command.</p>
+<p><strong>Note</strong>: if a RollbackTransactionAsCommandIsNotApprovedByCheckerException occurs at this point. The original transaction will of been aborted and we only log an entry for the command in the audit table setting its status as 'Pending'.</p>
+
+<pre><code>@Transactional
+@Override
+public CommandProcessingResult processAndLogCommand(final CommandWrapper wrapper, final JsonCommand command, final boolean isApprovedByChecker) {
+
+    final boolean rollbackTransaction = this.configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName())
+            && !isApprovedByChecker;
+
+    final NewCommandSourceHandler handler = findCommandHandler(wrapper);
+    final CommandProcessingResult result = handler.processCommand(command);
+
+    final AppUser maker = this.context.authenticatedUser();
+
+    CommandSource commandSourceResult = null;
+    if (command.commandId() != null) {
+        commandSourceResult = this.commandSourceRepository.findOne(command.commandId());
+        commandSourceResult.markAsChecked(maker, DateTime.now());
+    } else {
+        commandSourceResult = CommandSource.fullEntryFrom(wrapper, command, maker);
+    }
+    commandSourceResult.updateResourceId(result.resourceId());
+    commandSourceResult.updateForAudit(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(),
+            result.getSavingsId(), result.getProductId());
+
+    String changesOnlyJson = null;
+    if (result.hasChanges()) {
+        changesOnlyJson = this.toApiJsonSerializer.serializeResult(result.getChanges());
+        commandSourceResult.updateJsonTo(changesOnlyJson);
+    }
+
+    if (!result.hasChanges() && wrapper.isUpdateOperation() && !wrapper.isUpdateDatatable()) {
+        commandSourceResult.updateJsonTo(null);
+    }
+
+    if (commandSourceResult.hasJson()) {
+        this.commandSourceRepository.save(commandSourceResult);
+    }
+
+    if (rollbackTransaction) { throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(commandSourceResult); }
+
+    return result;
+}
+</code></pre>
+        <ol>
+            <li>Check that if maker-checker configuration enabled for this action. If yes and this is not a 'checker' approving the command - rollback at the end. We rollback at the end in order to test if the command will pass 'domain validation' which requires commit to database for full check.</li>
+            <li>findCommandHandler - Find the correct <strong>Hanlder</strong> to process this command.</li>
+            <li>Process command using handler (In transactional scope).</li>
+            <li>CommandSource object created/updated with all details for logging to 'm_portfolio_command_source' table.</li>
+            <li>In update scenario, we check to see if there where really any changes/updates. If so only JSON for changes is stored in audit log.</li>
+        </ol>
+        </p>
+    </section>
+
+</aside>
+<!-- end of wrapper -->
+</div>
+
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+    <script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.1.min.js"><\/script>')</script>
+    <script src="js/plugins.js"></script>
+    <script src="js/vendor/bootstrap-3.0.0/bootstrap.min.js"></script>
+    <script src="js/vendor/toc-0.1.2/jquery.toc.min.js"></script>
+    <script>
+        $('#toc').toc();
+    </script>
+    </body>
+</html>
\ No newline at end of file
diff --git a/docs/system-architecture/js/plugins.js b/docs/system-architecture/js/plugins.js
new file mode 100644
index 0000000..9683075
--- /dev/null
+++ b/docs/system-architecture/js/plugins.js
@@ -0,0 +1,23 @@
+// Avoid `console` errors in browsers that lack a console.
+(function() {
+    var method;
+    var noop = function () {};
+    var methods = [
+        'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
+        'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
+        'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
+        'timeStamp', 'trace', 'warn'
+    ];
+    var length = methods.length;
+    var console = (window.console = window.console || {});
+
+    while (length--) {
+        method = methods[length];
+
+        // Only stub undefined methods.
+        if (!console[method]) {
+            console[method] = noop;
+        }
+    }
+}());
+// Place any jQuery/helper plugins in here.
\ No newline at end of file
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/application.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/application.js
new file mode 100644
index 0000000..81b644b
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/application.js
@@ -0,0 +1,83 @@
+// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
+// IT'S ALL JUST JUNK FOR OUR DOCS!
+// ++++++++++++++++++++++++++++++++++++++++++
+
+!function ($) {
+
+  $(function(){
+
+    var $window = $(window)
+    var $body   = $(document.body)
+
+    var navHeight = $('.navbar').outerHeight(true) + 10
+
+    $body.scrollspy({
+      target: '.bs-sidebar',
+      offset: navHeight
+    })
+
+    $window.on('load', function () {
+      $body.scrollspy('refresh')
+    })
+
+    $('.bs-docs-container [href=#]').click(function (e) {
+      e.preventDefault()
+    })
+
+    // back to top
+    setTimeout(function () {
+      var $sideBar = $('.bs-sidebar')
+
+      $sideBar.affix({
+        offset: {
+          top: function () {
+            var offsetTop      = $sideBar.offset().top
+            var sideBarMargin  = parseInt($sideBar.children(0).css('margin-top'), 10)
+            var navOuterHeight = $('.bs-docs-nav').height()
+
+            return (this.top = offsetTop - navOuterHeight - sideBarMargin)
+          }
+        , bottom: function () {
+            return (this.bottom = $('.bs-footer').outerHeight(true))
+          }
+        }
+      })
+    }, 100)
+
+    setTimeout(function () {
+      $('.bs-top').affix()
+    }, 100)
+
+    // tooltip demo
+    $('.tooltip-demo').tooltip({
+      selector: "[data-toggle=tooltip]",
+      container: "body"
+    })
+
+    $('.tooltip-test').tooltip()
+    $('.popover-test').popover()
+
+    $('.bs-docs-navbar').tooltip({
+      selector: "a[data-toggle=tooltip]",
+      container: ".bs-docs-navbar .nav"
+    })
+
+    // popover demo
+    $("[data-toggle=popover]")
+      .popover()
+
+    // button state demo
+    $('#fat-btn')
+      .click(function () {
+        var btn = $(this)
+        btn.button('loading')
+        setTimeout(function () {
+          btn.button('reset')
+        }, 3000)
+      })
+
+    // carousel demo
+    $('.bs-docs-carousel-example').carousel()
+})
+
+}(window.jQuery)
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/customizer.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/customizer.js
new file mode 100644
index 0000000..5abfe42
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/customizer.js
@@ -0,0 +1,290 @@
+window.onload = function () { // wait for load in a dumb way because B-0
+  var cw = '/*!\n * Bootstrap v3.0.0\n *\n * Copyright 2013 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world @twitter by @mdo and @fat.\n */\n\n'
+
+  function showError(msg, err) {
+    $('<div id="bsCustomizerAlert" class="bs-customizer-alert">\
+        <div class="container">\
+          <a href="#bsCustomizerAlert" data-dismiss="alert" class="close pull-right">&times;</a>\
+          <p class="bs-customizer-alert-text"><span class="glyphicon glyphicon-warning-sign"></span>' + msg + '</p>' +
+          (err.extract ? '<pre class="bs-customizer-alert-extract">' + err.extract.join('\n') + '</pre>' : '') + '\
+        </div>\
+      </div>').appendTo('body').alert()
+    throw err
+  }
+
+  function showCallout(msg, showUpTop) {
+    var callout = $('<div class="bs-callout bs-callout-danger">\
+       <h4>Attention!</h4>\
+      <p>' + msg + '</p>\
+    </div>')
+
+    if (showUpTop) {
+      callout.appendTo('.bs-docs-container')
+    } else {
+      callout.insertAfter('.bs-customize-download')
+    }
+  }
+
+  function getQueryParam(key) {
+    key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars
+    var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)"));
+    return match && decodeURIComponent(match[1].replace(/\+/g, " "));
+  }
+
+  function createGist(configData) {
+    var data = {
+      "description": "Bootstrap Customizer Config",
+      "public": true,
+      "files": {
+        "config.json": {
+          "content": JSON.stringify(configData, null, 2)
+        }
+      }
+    }
+    $.ajax({
+      url: 'https://api.github.com/gists',
+      type: 'POST',
+      dataType: 'json',
+      data: JSON.stringify(data)
+    })
+    .success(function(result) {
+      history.replaceState(false, document.title, window.location.origin + window.location.pathname + '?id=' + result.id)
+    })
+    .error(function(err) {
+      showError('<strong>Ruh roh!</strong> Could not save gist file, configuration not saved.', err)
+    })
+  }
+
+  function getCustomizerData() {
+    var vars = {}
+
+    $('#less-variables-section input')
+        .each(function () {
+          $(this).val() && (vars[ $(this).prev().text() ] = $(this).val())
+        })
+
+    var data = {
+      vars: vars,
+      css: $('#less-section input:checked')  .map(function () { return this.value }).toArray(),
+      js:  $('#plugin-section input:checked').map(function () { return this.value }).toArray()
+    }
+
+    if ($.isEmptyObject(data.vars) && !data.css.length && !data.js.length) return
+
+    return data
+  }
+
+  function parseUrl() {
+    var id = getQueryParam('id')
+
+    if (!id) return
+
+    $.ajax({
+      url: 'https://api.github.com/gists/' + id,
+      type: 'GET',
+      dataType: 'json'
+    })
+    .success(function(result) {
+      var data = JSON.parse(result.files['config.json'].content)
+      if (data.js) {
+        $('#plugin-section input').each(function () {
+          $(this).prop('checked', ~$.inArray(this.value, data.js))
+        })
+      }
+      if (data.css) {
+        $('#less-section input').each(function () {
+          $(this).prop('checked', ~$.inArray(this.value, data.css))
+        })
+      }
+      if (data.vars) {
+        for (var i in data.vars) {
+          $('input[data-var="' + i + '"]').val(data.vars[i])
+        }
+      }
+    })
+    .error(function(err) {
+      showError('Error fetching bootstrap config file', err)
+    })
+  }
+
+  function generateZip(css, js, fonts, complete) {
+    if (!css && !js) return showError('<strong>Ruh roh!</strong> No Bootstrap files selected.', new Error('no Bootstrap'))
+
+    var zip = new JSZip()
+
+    if (css) {
+      var cssFolder = zip.folder('css')
+      for (var fileName in css) {
+        cssFolder.file(fileName, css[fileName])
+      }
+    }
+
+    if (js) {
+      var jsFolder = zip.folder('js')
+      for (var fileName in js) {
+        jsFolder.file(fileName, js[fileName])
+      }
+    }
+
+    if (fonts) {
+      var fontsFolder = zip.folder('fonts')
+      for (var fileName in fonts) {
+        fontsFolder.file(fileName, fonts[fileName])
+      }
+    }
+
+    var content = zip.generate({type:"blob"})
+
+    complete(content)
+  }
+
+  function generateCustomCSS(vars) {
+    var result = ''
+
+    for (var key in vars) {
+      result += key + ': ' + vars[key] + ';\n'
+    }
+
+    return result + '\n\n'
+  }
+
+  function generateFonts() {
+    var glyphicons = $('#less-section [value="glyphicons.less"]:checked')
+    if (glyphicons.length) {
+      return __fonts
+    }
+  }
+
+  function generateCSS() {
+    var $checked = $('#less-section input:checked')
+
+    if (!$checked.length) return false
+
+    var result = {}
+    var vars = {}
+    var css = ''
+
+    $('#less-variables-section input')
+        .each(function () {
+          $(this).val() && (vars[ $(this).prev().text() ] = $(this).val())
+        })
+
+    css += __less['variables.less']
+    if (vars) css += generateCustomCSS(vars)
+    css += __less['mixins.less']
+    css += __less['normalize.less']
+    css += __less['scaffolding.less']
+    css += $checked
+      .map(function () { return __less[this.value] })
+      .toArray()
+      .join('\n')
+
+    css = css.replace(/@import[^\n]*/gi, '') //strip any imports
+
+    try {
+      var parser = new less.Parser({
+          paths: ['variables.less', 'mixins.less']
+        , optimization: 0
+        , filename: 'bootstrap.css'
+      }).parse(css, function (err, tree) {
+        if (err) {
+          return showError('<strong>Ruh roh!</strong> Could not parse less files.', err)
+        }
+        result = {
+          'bootstrap.css'     : cw + tree.toCSS(),
+          'bootstrap.min.css' : cw + tree.toCSS({ compress: true })
+        }
+      })
+    } catch (err) {
+      return showError('<strong>Ruh roh!</strong> Could not parse less files.', err)
+    }
+
+    return result
+  }
+
+  function generateJavascript() {
+    var $checked = $('#plugin-section input:checked')
+    if (!$checked.length) return false
+
+    var js = $checked
+      .map(function () { return __js[this.value] })
+      .toArray()
+      .join('\n')
+
+    return {
+      'bootstrap.js': js,
+      'bootstrap.min.js': cw + uglify(js)
+    }
+  }
+
+  var inputsComponent = $('#less-section input')
+  var inputsPlugin    = $('#plugin-section input')
+  var inputsVariables = $('#less-variables-section input')
+
+  $('#less-section .toggle').on('click', function (e) {
+    e.preventDefault()
+    inputsComponent.prop('checked', !inputsComponent.is(':checked'))
+  })
+
+  $('#plugin-section .toggle').on('click', function (e) {
+    e.preventDefault()
+    inputsPlugin.prop('checked', !inputsPlugin.is(':checked'))
+  })
+
+  $('#less-variables-section .toggle').on('click', function (e) {
+    e.preventDefault()
+    inputsVariables.val('')
+  })
+
+  $('[data-dependencies]').on('click', function () {
+    if (!$(this).is(':checked')) return
+    var dependencies = this.getAttribute('data-dependencies')
+    if (!dependencies) return
+    dependencies = dependencies.split(',')
+    for (var i = 0; i < dependencies.length; i++) {
+      var dependency = $('[value="' + dependencies[i] + '"]')
+      dependency && dependency.prop('checked', true)
+    }
+  })
+
+  $('[data-dependents]').on('click', function () {
+    if ($(this).is(':checked')) return
+    var dependents = this.getAttribute('data-dependents')
+    if (!dependents) return
+    dependents = dependents.split(',')
+    for (var i = 0; i < dependents.length; i++) {
+      var dependent = $('[value="' + dependents[i] + '"]')
+      dependent && dependent.prop('checked', false)
+    }
+  })
+
+  var $compileBtn = $('#btn-compile')
+  var $downloadBtn = $('#btn-download')
+
+  $compileBtn.on('click', function (e) {
+    e.preventDefault()
+
+    $compileBtn.attr('disabled', 'disabled')
+
+    generateZip(generateCSS(), generateJavascript(), generateFonts(), function (blob) {
+      $compileBtn.removeAttr('disabled')
+      saveAs(blob, "bootstrap.zip")
+      createGist(getCustomizerData())
+    })
+  })
+
+  // browser support alerts
+  if (!window.URL && navigator.userAgent.toLowerCase().indexOf('safari') != -1) {
+    showCallout("Looks like you're using safari, which sadly doesn't have the best support\
+                 for HTML5 blobs. Because of this your file will be downloaded with the name <code>\"untitled\"</code>.\
+                 However, if you check your downloads folder, just rename this <code>\"untitled\"</code> file\
+                 to <code>\"bootstrap.zip\"</code> and you should be good to go!")
+  } else if (!window.URL && !window.webkitURL) {
+    $('.bs-docs-section, .bs-sidebar').css('display', 'none')
+
+    showCallout("Looks like your current browser doesn't support the Bootstrap Customizer. Please take a second\
+                to <a href=\"https://www.google.com/intl/en/chrome/browser/\"> upgrade to a more modern browser</a>.", true)
+  }
+
+  parseUrl()
+}
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/filesaver.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/filesaver.js
new file mode 100644
index 0000000..adecc88
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/filesaver.js
@@ -0,0 +1,169 @@
+/* Blob.js
+ * A Blob implementation.
+ * 2013-06-20
+ *
+ * By Eli Grey, http://eligrey.com
+ * By Devin Samarin, https://github.com/eboyjr
+ * License: X11/MIT
+ *   See LICENSE.md
+ */
+
+/*global self, unescape */
+/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
+  plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
+
+if (typeof Blob !== "function" || typeof URL === "undefined")
+if (typeof Blob === "function" && typeof webkitURL !== "undefined") self.URL = webkitURL;
+else var Blob = (function (view) {
+  "use strict";
+
+  var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) {
+    var
+        get_class = function(object) {
+        return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
+      }
+      , FakeBlobBuilder = function BlobBuilder() {
+        this.data = [];
+      }
+      , FakeBlob = function Blob(data, type, encoding) {
+        this.data = data;
+        this.size = data.length;
+        this.type = type;
+        this.encoding = encoding;
+      }
+      , FBB_proto = FakeBlobBuilder.prototype
+      , FB_proto = FakeBlob.prototype
+      , FileReaderSync = view.FileReaderSync
+      , FileException = function(type) {
+        this.code = this[this.name = type];
+      }
+      , file_ex_codes = (
+          "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
+        + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
+      ).split(" ")
+      , file_ex_code = file_ex_codes.length
+      , real_URL = view.URL || view.webkitURL || view
+      , real_create_object_URL = real_URL.createObjectURL
+      , real_revoke_object_URL = real_URL.revokeObjectURL
+      , URL = real_URL
+      , btoa = view.btoa
+      , atob = view.atob
+
+      , ArrayBuffer = view.ArrayBuffer
+      , Uint8Array = view.Uint8Array
+    ;
+    FakeBlob.fake = FB_proto.fake = true;
+    while (file_ex_code--) {
+      FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
+    }
+    if (!real_URL.createObjectURL) {
+      URL = view.URL = {};
+    }
+    URL.createObjectURL = function(blob) {
+      var
+          type = blob.type
+        , data_URI_header
+      ;
+      if (type === null) {
+        type = "application/octet-stream";
+      }
+      if (blob instanceof FakeBlob) {
+        data_URI_header = "data:" + type;
+        if (blob.encoding === "base64") {
+          return data_URI_header + ";base64," + blob.data;
+        } else if (blob.encoding === "URI") {
+          return data_URI_header + "," + decodeURIComponent(blob.data);
+        } if (btoa) {
+          return data_URI_header + ";base64," + btoa(blob.data);
+        } else {
+          return data_URI_header + "," + encodeURIComponent(blob.data);
+        }
+      } else if (real_create_object_URL) {
+        return real_create_object_URL.call(real_URL, blob);
+      }
+    };
+    URL.revokeObjectURL = function(object_URL) {
+      if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
+        real_revoke_object_URL.call(real_URL, object_URL);
+      }
+    };
+    FBB_proto.append = function(data/*, endings*/) {
+      var bb = this.data;
+      // decode data to a binary string
+      if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
+        var
+            str = ""
+          , buf = new Uint8Array(data)
+          , i = 0
+          , buf_len = buf.length
+        ;
+        for (; i < buf_len; i++) {
+          str += String.fromCharCode(buf[i]);
+        }
+        bb.push(str);
+      } else if (get_class(data) === "Blob" || get_class(data) === "File") {
+        if (FileReaderSync) {
+          var fr = new FileReaderSync;
+          bb.push(fr.readAsBinaryString(data));
+        } else {
+          // async FileReader won't work as BlobBuilder is sync
+          throw new FileException("NOT_READABLE_ERR");
+        }
+      } else if (data instanceof FakeBlob) {
+        if (data.encoding === "base64" && atob) {
+          bb.push(atob(data.data));
+        } else if (data.encoding === "URI") {
+          bb.push(decodeURIComponent(data.data));
+        } else if (data.encoding === "raw") {
+          bb.push(data.data);
+        }
+      } else {
+        if (typeof data !== "string") {
+          data += ""; // convert unsupported types to strings
+        }
+        // decode UTF-16 to binary string
+        bb.push(unescape(encodeURIComponent(data)));
+      }
+    };
+    FBB_proto.getBlob = function(type) {
+      if (!arguments.length) {
+        type = null;
+      }
+      return new FakeBlob(this.data.join(""), type, "raw");
+    };
+    FBB_proto.toString = function() {
+      return "[object BlobBuilder]";
+    };
+    FB_proto.slice = function(start, end, type) {
+      var args = arguments.length;
+      if (args < 3) {
+        type = null;
+      }
+      return new FakeBlob(
+          this.data.slice(start, args > 1 ? end : this.data.length)
+        , type
+        , this.encoding
+      );
+    };
+    FB_proto.toString = function() {
+      return "[object Blob]";
+    };
+    return FakeBlobBuilder;
+  }(view));
+
+  return function Blob(blobParts, options) {
+    var type = options ? (options.type || "") : "";
+    var builder = new BlobBuilder();
+    if (blobParts) {
+      for (var i = 0, len = blobParts.length; i < len; i++) {
+        builder.append(blobParts[i]);
+      }
+    }
+    return builder.getBlob(type);
+  };
+}(self));
+
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+var saveAs=saveAs||(navigator.msSaveOrOpenBlob&&navigator.msSaveOrOpenBlob.bind(navigator))||(function(h){"use strict";var r=h.document,l=function(){return h.URL||h.webkitURL||h},e=h.URL||h.webkitURL||h,n=r.createElementNS("http://www.w3.org/1999/xhtml","a"),g=!h.externalHost&&"download" in n,j=function(t){var s=r.createEvent("MouseEvents");s.initMouseEvent("click",true,false,h,0,0,0,0,0,false,false,false,false,0,null);t.dispatchEvent(s)},o=h.webkitRequestFileSystem,p=h.requestFileSystem||o||h.mozRequestFileSystem,m=function(s){(h.setImmediate||h.setTimeout)(function(){throw s},0)},c="application/octet-stream",k=0,b=[],i=function(){var t=b.length;while(t--){var s=b[t];if(typeof s==="string"){e.revokeObjectURL(s)}else{s.remove()}}b.length=0},q=function(t,s,w){s=[].concat(s);var v=s.length;while(v--){var x=t["on"+s[v]];if(typeof x==="function"){try{x.call(t,w||t)}catch(u){m(u)}}}},f=function(t,u){var v=this,B=t.type,E=false,x,w,s=function(){var F=l().createObjectURL(t);b.push(F);return F},A=function(){q(v,"writestart progress write writeend".split(" "))},D=function(){if(E||!x){x=s(t)}if(w){w.location.href=x}else{window.open(x,"_blank")}v.readyState=v.DONE;A()},z=function(F){return function(){if(v.readyState!==v.DONE){return F.apply(this,arguments)}}},y={create:true,exclusive:false},C;v.readyState=v.INIT;if(!u){u="download"}if(g){x=s(t);n.href=x;n.download=u;j(n);v.readyState=v.DONE;A();return}if(h.chrome&&B&&B!==c){C=t.slice||t.webkitSlice;t=C.call(t,0,t.size,c);E=true}if(o&&u!=="download"){u+=".download"}if(B===c||o){w=h}if(!p){D();return}k+=t.size;p(h.TEMPORARY,k,z(function(F){F.root.getDirectory("saved",y,z(function(G){var H=function(){G.getFile(u,y,z(function(I){I.createWriter(z(function(J){J.onwriteend=function(K){w.location.href=I.toURL();b.push(I);v.readyState=v.DONE;q(v,"writeend",K)};J.onerror=function(){var K=J.error;if(K.code!==K.ABORT_ERR){D()}};"writestart progress write abort".split(" ").forEach(function(K){J["on"+K]=v["on"+K]});J.write(t);v.abort=function(){J.abort();v.readyState=v.DONE};v.readyState=v.WRITING}),D)}),D)};G.getFile(u,{create:false},z(function(I){I.remove();H()}),z(function(I){if(I.code===I.NOT_FOUND_ERR){H()}else{D()}}))}),D)}),D)},d=f.prototype,a=function(s,t){return new f(s,t)};d.abort=function(){var s=this;s.readyState=s.DONE;q(s,"abort")};d.readyState=d.INIT=0;d.WRITING=1;d.DONE=2;d.error=d.onwritestart=d.onprogress=d.onwrite=d.onabort=d.onerror=d.onwriteend=null;h.addEventListener("unload",i,false);return a}(self));
\ No newline at end of file
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/holder.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/holder.js
new file mode 100644
index 0000000..f717054
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/holder.js
@@ -0,0 +1,419 @@
+/*
+
+Holder - 2.0 - client side image placeholders
+(c) 2012-2013 Ivan Malopinsky / http://imsky.co
+
+Provided under the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
+Commercial use requires attribution.
+
+*/
+
+var Holder = Holder || {};
+(function (app, win) {
+
+var preempted = false,
+fallback = false,
+canvas = document.createElement('canvas');
+
+//getElementsByClassName polyfill
+document.getElementsByClassName||(document.getElementsByClassName=function(e){var t=document,n,r,i,s=[];if(t.querySelectorAll)return t.querySelectorAll("."+e);if(t.evaluate){r=".//*[contains(concat(' ', @class, ' '), ' "+e+" ')]",n=t.evaluate(r,t,null,0,null);while(i=n.iterateNext())s.push(i)}else{n=t.getElementsByTagName("*"),r=new RegExp("(^|\\s)"+e+"(\\s|$)");for(i=0;i<n.length;i++)r.test(n[i].className)&&s.push(n[i])}return s})
+
+//getComputedStyle polyfill
+window.getComputedStyle||(window.getComputedStyle=function(e,t){return this.el=e,this.getPropertyValue=function(t){var n=/(\-([a-z]){1})/g;return t=="float"&&(t="styleFloat"),n.test(t)&&(t=t.replace(n,function(){return arguments[2].toUpperCase()})),e.currentStyle[t]?e.currentStyle[t]:null},this})
+
+//http://javascript.nwbox.com/ContentLoaded by Diego Perini with modifications
+function contentLoaded(n,t){var l="complete",s="readystatechange",u=!1,h=u,c=!0,i=n.document,a=i.documentElement,e=i.addEventListener?"addEventListener":"attachEvent",v=i.addEventListener?"removeEventListener":"detachEvent",f=i.addEventListener?"":"on",r=function(e){(e.type!=s||i.readyState==l)&&((e.type=="load"?n:i)[v](f+e.type,r,u),!h&&(h=!0)&&t.call(n,null))},o=function(){try{a.doScroll("left")}catch(n){setTimeout(o,50);return}r("poll")};if(i.readyState==l)t.call(n,"lazy");else{if(i.createEventObject&&a.doScroll){try{c=!n.frameElement}catch(y){}c&&o()}i[e](f+"DOMContentLoaded",r,u),i[e](f+s,r,u),n[e](f+"load",r,u)}};
+
+//https://gist.github.com/991057 by Jed Schmidt with modifications
+function selector(a){
+	a=a.match(/^(\W)?(.*)/);var b=document["getElement"+(a[1]?a[1]=="#"?"ById":"sByClassName":"sByTagName")](a[2]);
+	var ret=[];	b!=null&&(b.length?ret=b:b.length==0?ret=b:ret=[b]);	return ret;
+}
+
+//shallow object property extend
+function extend(a,b){var c={};for(var d in a)c[d]=a[d];for(var e in b)c[e]=b[e];return c}
+
+//hasOwnProperty polyfill
+if (!Object.prototype.hasOwnProperty)
+	Object.prototype.hasOwnProperty = function(prop) {
+		var proto = this.__proto__ || this.constructor.prototype;
+		return (prop in this) && (!(prop in proto) || proto[prop] !== this[prop]);
+	}
+
+function text_size(width, height, template) {
+	height = parseInt(height,10);
+	width = parseInt(width,10);
+	var bigSide = Math.max(height, width)
+	var smallSide = Math.min(height, width)
+	var scale = 1 / 12;
+	var newHeight = Math.min(smallSide * 0.75, 0.75 * bigSide * scale);
+	return {
+		height: Math.round(Math.max(template.size, newHeight))
+	}
+}
+
+function draw(ctx, dimensions, template, ratio) {
+	var ts = text_size(dimensions.width, dimensions.height, template);
+	var text_height = ts.height;
+	var width = dimensions.width * ratio,
+		height = dimensions.height * ratio;
+	var font = template.font ? template.font : "sans-serif";
+	canvas.width = width;
+	canvas.height = height;
+	ctx.textAlign = "center";
+	ctx.textBaseline = "middle";
+	ctx.fillStyle = template.background;
+	ctx.fillRect(0, 0, width, height);
+	ctx.fillStyle = template.foreground;
+	ctx.font = "bold " + text_height + "px " + font;
+	var text = template.text ? template.text : (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height));
+	var text_width = ctx.measureText(text).width;
+	if (text_width / width >= 0.75) {
+		text_height = Math.floor(text_height * 0.75 * (width/text_width));
+	}
+	//Resetting font size if necessary
+	ctx.font = "bold " + (text_height * ratio) + "px " + font;
+	ctx.fillText(text, (width / 2), (height / 2), width);
+	return canvas.toDataURL("image/png");
+}
+
+function render(mode, el, holder, src) {
+	var dimensions = holder.dimensions,
+		theme = holder.theme,
+		text = holder.text ? decodeURIComponent(holder.text) : holder.text;
+	var dimensions_caption = dimensions.width + "x" + dimensions.height;
+	theme = (text ? extend(theme, {
+		text: text
+	}) : theme);
+	theme = (holder.font ? extend(theme, {
+		font: holder.font
+	}) : theme);
+	if (mode == "image") {
+		el.setAttribute("data-src", src);
+		el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption);
+		if (fallback || !holder.auto) {
+			el.style.width = dimensions.width + "px";
+			el.style.height = dimensions.height + "px";
+		}
+		if (fallback) {
+			el.style.backgroundColor = theme.background;
+		} else {
+			el.setAttribute("src", draw(ctx, dimensions, theme, ratio));
+		}
+	} else if (mode == "background") {
+		if (!fallback) {
+			el.style.backgroundImage = "url(" + draw(ctx, dimensions, theme, ratio) + ")";
+			el.style.backgroundSize = dimensions.width + "px " + dimensions.height + "px";
+		}
+	} else if (mode == "fluid") {
+		el.setAttribute("data-src", src);
+		el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption);
+		if (dimensions.height.substr(-1) == "%") {
+			el.style.height = dimensions.height
+		} else {
+			el.style.height = dimensions.height + "px"
+		}
+		if (dimensions.width.substr(-1) == "%") {
+			el.style.width = dimensions.width
+		} else {
+			el.style.width = dimensions.width + "px"
+		}
+		if (el.style.display == "inline" || el.style.display == "") {
+			el.style.display = "block";
+		}
+		if (fallback) {
+			el.style.backgroundColor = theme.background;
+		} else {
+			el.holderData = holder;
+			fluid_images.push(el);
+			fluid_update(el);
+		}
+	}
+};
+
+function fluid_update(element) {
+	var images;
+	if (element.nodeType == null) {
+		images = fluid_images;
+	} else {
+		images = [element]
+	}
+	for (i in images) {
+		var el = images[i]
+		if (el.holderData) {
+			var holder = el.holderData;
+			el.setAttribute("src", draw(ctx, {
+				height: el.clientHeight,
+				width: el.clientWidth
+			}, holder.theme, ratio));
+		}
+	}
+}
+
+function parse_flags(flags, options) {
+
+	var ret = {
+		theme: settings.themes.gray
+	}, render = false;
+
+	for (sl = flags.length, j = 0; j < sl; j++) {
+		var flag = flags[j];
+		if (app.flags.dimensions.match(flag)) {
+			render = true;
+			ret.dimensions = app.flags.dimensions.output(flag);
+		} else if (app.flags.fluid.match(flag)) {
+			render = true;
+			ret.dimensions = app.flags.fluid.output(flag);
+			ret.fluid = true;
+		} else if (app.flags.colors.match(flag)) {
+			ret.theme = app.flags.colors.output(flag);
+		} else if (options.themes[flag]) {
+			//If a theme is specified, it will override custom colors
+			ret.theme = options.themes[flag];
+		} else if (app.flags.text.match(flag)) {
+			ret.text = app.flags.text.output(flag);
+		} else if (app.flags.font.match(flag)) {
+			ret.font = app.flags.font.output(flag);
+		} else if (app.flags.auto.match(flag)) {
+			ret.auto = true;
+		}
+	}
+
+	return render ? ret : false;
+
+};
+
+
+
+if (!canvas.getContext) {
+	fallback = true;
+} else {
+	if (canvas.toDataURL("image/png")
+		.indexOf("data:image/png") < 0) {
+		//Android doesn't support data URI
+		fallback = true;
+	} else {
+		var ctx = canvas.getContext("2d");
+	}
+}
+
+var dpr = 1, bsr = 1;
+	
+if(!fallback){
+    dpr = window.devicePixelRatio || 1,
+    bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
+}
+
+var ratio = dpr / bsr;
+
+var fluid_images = [];
+
+var settings = {
+	domain: "holder.js",
+	images: "img",
+	bgnodes: ".holderjs",
+	themes: {
+		"gray": {
+			background: "#eee",
+			foreground: "#aaa",
+			size: 12
+		},
+		"social": {
+			background: "#3a5a97",
+			foreground: "#fff",
+			size: 12
+		},
+		"industrial": {
+			background: "#434A52",
+			foreground: "#C2F200",
+			size: 12
+		}
+	},
+	stylesheet: ".holderjs-fluid {font-size:16px;font-weight:bold;text-align:center;font-family:sans-serif;margin:0}"
+};
+
+
+app.flags = {
+	dimensions: {
+		regex: /^(\d+)x(\d+)$/,
+		output: function (val) {
+			var exec = this.regex.exec(val);
+			return {
+				width: +exec[1],
+				height: +exec[2]
+			}
+		}
+	},
+	fluid: {
+		regex: /^([0-9%]+)x([0-9%]+)$/,
+		output: function (val) {
+			var exec = this.regex.exec(val);
+			return {
+				width: exec[1],
+				height: exec[2]
+			}
+		}
+	},
+	colors: {
+		regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i,
+		output: function (val) {
+			var exec = this.regex.exec(val);
+			return {
+				size: settings.themes.gray.size,
+				foreground: "#" + exec[2],
+				background: "#" + exec[1]
+			}
+		}
+	},
+	text: {
+		regex: /text\:(.*)/,
+		output: function (val) {
+			return this.regex.exec(val)[1];
+		}
+	},
+	font: {
+		regex: /font\:(.*)/,
+		output: function (val) {
+			return this.regex.exec(val)[1];
+		}
+	},
+	auto: {
+		regex: /^auto$/
+	}
+}
+
+for (var flag in app.flags) {
+	if (!app.flags.hasOwnProperty(flag)) continue;
+	app.flags[flag].match = function (val) {
+		return val.match(this.regex)
+	}
+}
+
+app.add_theme = function (name, theme) {
+	name != null && theme != null && (settings.themes[name] = theme);
+	return app;
+};
+
+app.add_image = function (src, el) {
+	var node = selector(el);
+	if (node.length) {
+		for (var i = 0, l = node.length; i < l; i++) {
+			var img = document.createElement("img")
+			img.setAttribute("data-src", src);
+			node[i].appendChild(img);
+		}
+	}
+	return app;
+};
+
+app.run = function (o) {
+	var options = extend(settings, o),
+	    images = [], imageNodes = [], bgnodes = [];
+	    
+	if(typeof(options.images) == "string"){
+	    imageNodes = selector(options.images);
+	}
+	else if (window.NodeList && options.images instanceof window.NodeList) {
+		imageNodes = options.images;
+	} else if (window.Node && options.images instanceof window.Node) {
+		imageNodes = [options.images];
+	}
+
+	if(typeof(options.bgnodes) == "string"){
+	    bgnodes = selector(options.bgnodes);
+	} else if (window.NodeList && options.elements instanceof window.NodeList) {
+		bgnodes = options.bgnodes;
+	} else if (window.Node && options.bgnodes instanceof window.Node) {
+		bgnodes = [options.bgnodes];
+	}
+
+	preempted = true;
+
+	for (i = 0, l = imageNodes.length; i < l; i++) images.push(imageNodes[i]);
+
+	var holdercss = document.getElementById("holderjs-style");
+	if (!holdercss) {
+		holdercss = document.createElement("style");
+		holdercss.setAttribute("id", "holderjs-style");
+		holdercss.type = "text/css";
+		document.getElementsByTagName("head")[0].appendChild(holdercss);
+	}
+	
+	if (!options.nocss) {
+	    if (holdercss.styleSheet) {
+		    holdercss.styleSheet.cssText += options.stylesheet;
+	    } else {
+		    holdercss.appendChild(document.createTextNode(options.stylesheet));
+	    }
+	}
+
+	var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)");
+
+	for (var l = bgnodes.length, i = 0; i < l; i++) {
+		var src = window.getComputedStyle(bgnodes[i], null)
+			.getPropertyValue("background-image");
+		var flags = src.match(cssregex);
+		var bgsrc = bgnodes[i].getAttribute("data-background-src");
+
+		if (flags) {
+			var holder = parse_flags(flags[1].split("/"), options);
+			if (holder) {
+				render("background", bgnodes[i], holder, src);
+			}
+		}
+		else if(bgsrc != null){
+		    var holder = parse_flags(bgsrc.substr(bgsrc.lastIndexOf(options.domain) + options.domain.length + 1)
+				.split("/"), options);
+		    if(holder){
+			render("background", bgnodes[i], holder, src);
+		    }
+		}
+	}
+
+	for (l = images.length, i = 0; i < l; i++) {
+	    
+		var attr_src = attr_data_src = src = null;
+		
+		try{
+		    attr_src = images[i].getAttribute("src");
+		    attr_datasrc = images[i].getAttribute("data-src");
+		}catch(e){}
+				
+		if (attr_datasrc == null && !! attr_src && attr_src.indexOf(options.domain) >= 0) {
+			src = attr_src;
+		} else if ( !! attr_datasrc && attr_datasrc.indexOf(options.domain) >= 0) {
+			src = attr_datasrc;
+		}
+		
+		if (src) {
+			var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1)
+				.split("/"), options);
+			if (holder) {
+				if (holder.fluid) {
+					render("fluid", images[i], holder, src)
+				} else {
+					render("image", images[i], holder, src);
+				}
+			}
+		}
+	}
+	return app;
+};
+
+contentLoaded(win, function () {
+	if (window.addEventListener) {
+		window.addEventListener("resize", fluid_update, false);
+		window.addEventListener("orientationchange", fluid_update, false);
+	} else {
+		window.attachEvent("onresize", fluid_update)
+	}
+	preempted || app.run();
+});
+
+if (typeof define === "function" && define.amd) {
+	define("Holder", [], function () {
+		return app;
+	});
+}
+
+})(Holder, window);
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/html5shiv.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/html5shiv.js
new file mode 100644
index 0000000..784f221
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/html5shiv.js
@@ -0,0 +1,8 @@
+/*
+ HTML5 Shiv v3.6.2pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
+*/
+(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
+a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}</style>";
+c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
+"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
+for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/jquery.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/jquery.js
new file mode 100644
index 0000000..76d21a4
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/jquery.js
@@ -0,0 +1,6 @@
+/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery-1.10.2.min.map
+*/
+(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav></:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t
+}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Ct=/^(?:checkbox|radio)$/i,Nt=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle);
+u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=un(e,t),Pt.detach()),Gt[e]=n),n}function un(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,n){x.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(x.css(e,"display"))?x.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x.support.opacity||(x.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=x.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===x.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,n){return n?x.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,n){x.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?x(e).position()[n]+"px":r):t}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!x.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||x.css(e,"display"))},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(x.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Ct.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),x.param=function(e,n){var r,i=[],o=function(e,t){t=x.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var mn,yn,vn=x.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Cn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Nn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=x.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=o.href}catch(Ln){yn=a.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(T)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(l){var u;return o[l]=!0,x.each(e[l]||[],function(e,l){var c=l(n,r,i);return"string"!=typeof c||a||o[c]?a?!(u=c):t:(n.dataTypes.unshift(c),s(c),!1)}),u}return s(n.dataTypes[0])||!o["*"]&&s("*")}function _n(e,n){var r,i,o=x.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,l=e.indexOf(" ");return l>=0&&(i=e.slice(l,e.length),e=e.slice(0,l)),x.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&x.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?x("<div>").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
\ No newline at end of file
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/json2.js.htm b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/json2.js.htm
new file mode 100644
index 0000000..ff9313e
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/json2.js.htm
@@ -0,0 +1,1171 @@
+
+
+
+<!DOCTYPE html>
+<html>
+  <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# githubog: http://ogp.me/ns/fb/githubog#">
+    <meta charset='utf-8'>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <title>JSON-js/json2.js at master · douglascrockford/JSON-js</title>
+    <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub" />
+    <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub" />
+    <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-114.png" />
+    <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114.png" />
+    <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-144.png" />
+    <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144.png" />
+    <link rel="logo" type="image/svg" href="https://github-media-downloads.s3.amazonaws.com/github-logo.svg" />
+    <meta property="og:image" content="https://github.global.ssl.fastly.net/images/modules/logos_page/Octocat.png">
+    <meta name="hostname" content="github-fe128-cp1-prd.iad.github.net">
+    <meta name="ruby" content="ruby 1.9.3p194-tcs-github-tcmalloc (2012-05-25, TCS patched 2012-05-27, GitHub v1.0.36) [x86_64-linux]">
+    <link rel="assets" href="https://github.global.ssl.fastly.net/">
+    <link rel="xhr-socket" href="/_sockets" />
+    
+    
+
+
+    <meta name="msapplication-TileImage" content="/windows-tile.png" />
+    <meta name="msapplication-TileColor" content="#ffffff" />
+    <meta name="selected-link" value="repo_source" data-pjax-transient />
+    <meta content="collector.githubapp.com" name="octolytics-host" /><meta content="github" name="octolytics-app-id" /><meta content="e2f86eeb-559e-407b-8639-44e195876131" name="octolytics-dimension-request_id" /><meta content="1131294" name="octolytics-actor-id" /><meta content="keithwoodlock" name="octolytics-actor-login" /><meta content="c6488d14c2dcaeb3bdf0928f03bee95a636030b9df88e5d33a400b4d96bb9417" name="octolytics-actor-hash" />
+    
+
+    
+    
+    <link rel="icon" type="image/x-icon" href="/favicon.ico" />
+
+    <meta content="authenticity_token" name="csrf-param" />
+<meta content="0zjVjO5n9NwMnMOEQRpCSBtTXf0LBDGgN0xSPTeqIRQ=" name="csrf-token" />
+
+    <link href="https://github.global.ssl.fastly.net/assets/github-2acccfdeaa48ea23ecdc42addc34669f048ecab8.css" media="all" rel="stylesheet" type="text/css" />
+    <link href="https://github.global.ssl.fastly.net/assets/github2-d75c750a6b14571dc070b6570d9224acd7b6795e.css" media="all" rel="stylesheet" type="text/css" />
+    
+
+
+      <script src="https://github.global.ssl.fastly.net/assets/frameworks-f86a2975a82dceee28e5afe598d1ebbfd7109d79.js" type="text/javascript"></script>
+      <script src="https://github.global.ssl.fastly.net/assets/github-be76400731d26146fc825df9f879385b669bd2fc.js" type="text/javascript"></script>
+      
+      <meta http-equiv="x-pjax-version" content="d7f2425c90b626cc14a2574d53f2c301">
+
+        <link data-pjax-transient rel='permalink' href='/douglascrockford/JSON-js/blob/e39db4b7e6249f04a195e7dd0840e610cc9e941e/json2.js'>
+  <meta property="og:title" content="JSON-js"/>
+  <meta property="og:type" content="githubog:gitrepository"/>
+  <meta property="og:url" content="https://github.com/douglascrockford/JSON-js"/>
+  <meta property="og:image" content="https://github.global.ssl.fastly.net/images/gravatars/gravatar-user-420.png"/>
+  <meta property="og:site_name" content="GitHub"/>
+  <meta property="og:description" content="JSON-js - JSON in JavaScript"/>
+
+  <meta name="description" content="JSON-js - JSON in JavaScript" />
+
+  <meta content="262641" name="octolytics-dimension-user_id" /><meta content="douglascrockford" name="octolytics-dimension-user_login" /><meta content="1092277" name="octolytics-dimension-repository_id" /><meta content="douglascrockford/JSON-js" name="octolytics-dimension-repository_nwo" /><meta content="true" name="octolytics-dimension-repository_public" /><meta content="false" name="octolytics-dimension-repository_is_fork" /><meta content="1092277" name="octolytics-dimension-repository_network_root_id" /><meta content="douglascrockford/JSON-js" name="octolytics-dimension-repository_network_root_nwo" />
+  <link href="https://github.com/douglascrockford/JSON-js/commits/master.atom" rel="alternate" title="Recent Commits to JSON-js:master" type="application/atom+xml" />
+
+  </head>
+
+
+  <body class="logged_in  env-production windows vis-public page-blob">
+    <div class="wrapper">
+      
+      
+      
+
+
+      <div class="header header-logged-in true">
+  <div class="container clearfix">
+
+    <a class="header-logo-invertocat" href="https://github.com/organizations/openMF">
+  <span class="mega-octicon octicon-mark-github"></span>
+</a>
+
+    <div class="divider-vertical"></div>
+
+    
+    <a href="/notifications" class="notification-indicator tooltipped downwards" data-gotokey="n" title="You have unread notifications">
+        <span class="mail-status unread"></span>
+</a>    <div class="divider-vertical"></div>
+
+
+      <div class="command-bar js-command-bar  in-repository">
+          <form accept-charset="UTF-8" action="/search" class="command-bar-form" id="top_search_form" method="get">
+
+<input type="text" data-hotkey=" s" name="q" id="js-command-bar-field" placeholder="Search or type a command" tabindex="1" autocapitalize="off"
+    
+    data-username="keithwoodlock"
+      data-repo="douglascrockford/JSON-js"
+      data-branch="master"
+      data-sha="ca9b65d1e9e0bc104ca6e4fa9047cc035f27a637"
+  >
+
+    <input type="hidden" name="nwo" value="douglascrockford/JSON-js" />
+
+    <div class="select-menu js-menu-container js-select-menu search-context-select-menu">
+      <span class="minibutton select-menu-button js-menu-target">
+        <span class="js-select-button">This repository</span>
+      </span>
+
+      <div class="select-menu-modal-holder js-menu-content js-navigation-container">
+        <div class="select-menu-modal">
+
+          <div class="select-menu-item js-navigation-item js-this-repository-navigation-item selected">
+            <span class="select-menu-item-icon octicon octicon-check"></span>
+            <input type="radio" class="js-search-this-repository" name="search_target" value="repository" checked="checked" />
+            <div class="select-menu-item-text js-select-button-text">This repository</div>
+          </div> <!-- /.select-menu-item -->
+
+          <div class="select-menu-item js-navigation-item js-all-repositories-navigation-item">
+            <span class="select-menu-item-icon octicon octicon-check"></span>
+            <input type="radio" name="search_target" value="global" />
+            <div class="select-menu-item-text js-select-button-text">All repositories</div>
+          </div> <!-- /.select-menu-item -->
+
+        </div>
+      </div>
+    </div>
+
+  <span class="octicon help tooltipped downwards" title="Show command bar help">
+    <span class="octicon octicon-question"></span>
+  </span>
+
+
+  <input type="hidden" name="ref" value="cmdform">
+
+</form>
+        <ul class="top-nav">
+          <li class="explore"><a href="/explore">Explore</a></li>
+            <li><a href="https://gist.github.com">Gist</a></li>
+            <li><a href="/blog">Blog</a></li>
+          <li><a href="https://help.github.com">Help</a></li>
+        </ul>
+      </div>
+
+    
+
+
+  <ul id="user-links">
+    <li>
+      <a href="/keithwoodlock" class="name">
+        <img height="20" src="https://2.gravatar.com/avatar/7cb9ea82bf508b3cb43f39d9a3f1c8a3?d=https%3A%2F%2Fidenticons.github.com%2F5d64743571857aa2599a532187a6aa02.png&amp;s=140" width="20" /> keithwoodlock
+      </a>
+    </li>
+
+      <li>
+        <a href="/new" id="new_repo" class="tooltipped downwards" title="Create a new repo" aria-label="Create a new repo">
+          <span class="octicon octicon-repo-create"></span>
+        </a>
+      </li>
+
+      <li>
+        <a href="/settings/profile" id="account_settings"
+          class="tooltipped downwards"
+          aria-label="Account settings "
+          title="Account settings ">
+          <span class="octicon octicon-tools"></span>
+        </a>
+      </li>
+      <li>
+        <a class="tooltipped downwards" href="/logout" data-method="post" id="logout" title="Sign out" aria-label="Sign out">
+          <span class="octicon octicon-log-out"></span>
+        </a>
+      </li>
+
+  </ul>
+
+<div class="js-new-dropdown-contents hidden">
+  
+
+<ul class="dropdown-menu">
+  <li>
+    <a href="/new"><span class="octicon octicon-repo-create"></span> New repository</a>
+  </li>
+  <li>
+    <a href="/organizations/new"><span class="octicon octicon-organization"></span> New organization</a>
+  </li>
+
+
+
+    <li class="section-title">
+      <span title="douglascrockford/JSON-js">This repository</span>
+    </li>
+    <li>
+      <a href="/douglascrockford/JSON-js/issues/new"><span class="octicon octicon-issue-opened"></span> New issue</a>
+    </li>
+</ul>
+
+</div>
+
+
+    
+  </div>
+</div>
+
+      
+
+      
+
+
+
+
+          <div class="site" itemscope itemtype="http://schema.org/WebPage">
+    
+    <div class="pagehead repohead instapaper_ignore readability-menu">
+      <div class="container">
+        
+
+<ul class="pagehead-actions">
+
+    <li class="subscription">
+      <form accept-charset="UTF-8" action="/notifications/subscribe" class="js-social-container" data-autosubmit="true" data-remote="true" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="0zjVjO5n9NwMnMOEQRpCSBtTXf0LBDGgN0xSPTeqIRQ=" /></div>  <input id="repository_id" name="repository_id" type="hidden" value="1092277" />
+
+    <div class="select-menu js-menu-container js-select-menu">
+        <a class="social-count js-social-count" href="/douglascrockford/JSON-js/watchers">
+          303
+        </a>
+      <span class="minibutton select-menu-button with-count js-menu-target" role="button" tabindex="0">
+        <span class="js-select-button">
+          <span class="octicon octicon-eye-watch"></span>
+          Watch
+        </span>
+      </span>
+
+      <div class="select-menu-modal-holder">
+        <div class="select-menu-modal subscription-menu-modal js-menu-content">
+          <div class="select-menu-header">
+            <span class="select-menu-title">Notification status</span>
+            <span class="octicon octicon-remove-close js-menu-close"></span>
+          </div> <!-- /.select-menu-header -->
+
+          <div class="select-menu-list js-navigation-container" role="menu">
+
+            <div class="select-menu-item js-navigation-item selected" role="menuitem" tabindex="0">
+              <span class="select-menu-item-icon octicon octicon-check"></span>
+              <div class="select-menu-item-text">
+                <input checked="checked" id="do_included" name="do" type="radio" value="included" />
+                <h4>Not watching</h4>
+                <span class="description">You only receive notifications for discussions in which you participate or are @mentioned.</span>
+                <span class="js-select-button-text hidden-select-button-text">
+                  <span class="octicon octicon-eye-watch"></span>
+                  Watch
+                </span>
+              </div>
+            </div> <!-- /.select-menu-item -->
+
+            <div class="select-menu-item js-navigation-item " role="menuitem" tabindex="0">
+              <span class="select-menu-item-icon octicon octicon octicon-check"></span>
+              <div class="select-menu-item-text">
+                <input id="do_subscribed" name="do" type="radio" value="subscribed" />
+                <h4>Watching</h4>
+                <span class="description">You receive notifications for all discussions in this repository.</span>
+                <span class="js-select-button-text hidden-select-button-text">
+                  <span class="octicon octicon-eye-unwatch"></span>
+                  Unwatch
+                </span>
+              </div>
+            </div> <!-- /.select-menu-item -->
+
+            <div class="select-menu-item js-navigation-item " role="menuitem" tabindex="0">
+              <span class="select-menu-item-icon octicon octicon-check"></span>
+              <div class="select-menu-item-text">
+                <input id="do_ignore" name="do" type="radio" value="ignore" />
+                <h4>Ignoring</h4>
+                <span class="description">You do not receive any notifications for discussions in this repository.</span>
+                <span class="js-select-button-text hidden-select-button-text">
+                  <span class="octicon octicon-mute"></span>
+                  Stop ignoring
+                </span>
+              </div>
+            </div> <!-- /.select-menu-item -->
+
+          </div> <!-- /.select-menu-list -->
+
+        </div> <!-- /.select-menu-modal -->
+      </div> <!-- /.select-menu-modal-holder -->
+    </div> <!-- /.select-menu -->
+
+</form>
+    </li>
+
+  <li>
+  
+<div class="js-toggler-container js-social-container starring-container ">
+  <a href="/douglascrockford/JSON-js/unstar" class="minibutton with-count js-toggler-target star-button starred upwards" title="Unstar this repo" data-remote="true" data-method="post" rel="nofollow">
+    <span class="octicon octicon-star-delete"></span><span class="text">Unstar</span>
+  </a>
+  <a href="/douglascrockford/JSON-js/star" class="minibutton with-count js-toggler-target star-button unstarred upwards" title="Star this repo" data-remote="true" data-method="post" rel="nofollow">
+    <span class="octicon octicon-star"></span><span class="text">Star</span>
+  </a>
+  <a class="social-count js-social-count" href="/douglascrockford/JSON-js/stargazers">3,637</a>
+</div>
+
+  </li>
+
+
+        <li>
+          <a href="/douglascrockford/JSON-js/fork" class="minibutton with-count js-toggler-target fork-button lighter upwards" title="Fork this repo" rel="facebox nofollow">
+            <span class="octicon octicon-git-branch-create"></span><span class="text">Fork</span>
+          </a>
+          <a href="/douglascrockford/JSON-js/network" class="social-count">1,962</a>
+        </li>
+
+
+</ul>
+
+        <h1 itemscope itemtype="http://data-vocabulary.org/Breadcrumb" class="entry-title public">
+          <span class="repo-label"><span>public</span></span>
+          <span class="mega-octicon octicon-repo"></span>
+          <span class="author">
+            <a href="/douglascrockford" class="url fn" itemprop="url" rel="author"><span itemprop="title">douglascrockford</span></a></span
+          ><span class="repohead-name-divider">/</span><strong
+          ><a href="/douglascrockford/JSON-js" class="js-current-repository js-repo-home-link">JSON-js</a></strong>
+
+          <span class="page-context-loader">
+            <img alt="Octocat-spinner-32" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+          </span>
+
+        </h1>
+      </div><!-- /.container -->
+    </div><!-- /.repohead -->
+
+    <div class="container">
+
+      <div class="repository-with-sidebar repo-container ">
+
+        <div class="repository-sidebar">
+            
+
+<div class="repo-nav repo-nav-full js-repository-container-pjax js-octicon-loaders">
+  <div class="repo-nav-contents">
+    <ul class="repo-menu">
+      <li class="tooltipped leftwards" title="Code">
+        <a href="/douglascrockford/JSON-js" aria-label="Code" class="js-selected-navigation-item selected" data-gotokey="c" data-pjax="true" data-selected-links="repo_source repo_downloads repo_commits repo_tags repo_branches /douglascrockford/JSON-js">
+          <span class="octicon octicon-code"></span> <span class="full-word">Code</span>
+          <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+
+
+      <li class="tooltipped leftwards" title="Pull Requests"><a href="/douglascrockford/JSON-js/pulls" aria-label="Pull Requests" class="js-selected-navigation-item js-disable-pjax" data-gotokey="p" data-selected-links="repo_pulls /douglascrockford/JSON-js/pulls">
+            <span class="octicon octicon-git-pull-request"></span> <span class="full-word">Pull Requests</span>
+            <span class='counter'>0</span>
+            <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+
+
+        <li class="tooltipped leftwards" title="Wiki">
+          <a href="/douglascrockford/JSON-js/wiki" aria-label="Wiki" class="js-selected-navigation-item " data-pjax="true" data-selected-links="repo_wiki /douglascrockford/JSON-js/wiki">
+            <span class="octicon octicon-book"></span> <span class="full-word">Wiki</span>
+            <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>        </li>
+    </ul>
+    <div class="repo-menu-separator"></div>
+    <ul class="repo-menu">
+
+      <li class="tooltipped leftwards" title="Pulse">
+        <a href="/douglascrockford/JSON-js/pulse" aria-label="Pulse" class="js-selected-navigation-item " data-pjax="true" data-selected-links="pulse /douglascrockford/JSON-js/pulse">
+          <span class="octicon octicon-pulse"></span> <span class="full-word">Pulse</span>
+          <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+
+      <li class="tooltipped leftwards" title="Graphs">
+        <a href="/douglascrockford/JSON-js/graphs" aria-label="Graphs" class="js-selected-navigation-item " data-pjax="true" data-selected-links="repo_graphs repo_contributors /douglascrockford/JSON-js/graphs">
+          <span class="octicon octicon-graph"></span> <span class="full-word">Graphs</span>
+          <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+
+      <li class="tooltipped leftwards" title="Network">
+        <a href="/douglascrockford/JSON-js/network" aria-label="Network" class="js-selected-navigation-item js-disable-pjax" data-selected-links="repo_network /douglascrockford/JSON-js/network">
+          <span class="octicon octicon-git-branch"></span> <span class="full-word">Network</span>
+          <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+    </ul>
+
+
+  </div>
+</div>
+
+            <div class="only-with-full-nav">
+              
+
+  
+
+<div class="clone-url open"
+  data-protocol-type="http"
+  data-url="/users/set_protocol?protocol_selector=http&amp;protocol_type=clone">
+  <h3><strong>HTTPS</strong> clone URL</h3>
+
+  <div class="clone-url-box">
+    <input type="text" class="clone js-url-field"
+           value="https://github.com/douglascrockford/JSON-js.git" readonly="readonly">
+
+    <span class="js-zeroclipboard url-box-clippy minibutton zeroclipboard-button" data-clipboard-text="https://github.com/douglascrockford/JSON-js.git" data-copied-hint="copied!" title="copy to clipboard"><span class="octicon octicon-clippy"></span></span>
+  </div>
+</div>
+
+  
+
+<div class="clone-url "
+  data-protocol-type="ssh"
+  data-url="/users/set_protocol?protocol_selector=ssh&amp;protocol_type=clone">
+  <h3><strong>SSH</strong> clone URL</h3>
+
+  <div class="clone-url-box">
+    <input type="text" class="clone js-url-field"
+           value="git@github.com:douglascrockford/JSON-js.git" readonly="readonly">
+
+    <span class="js-zeroclipboard url-box-clippy minibutton zeroclipboard-button" data-clipboard-text="git@github.com:douglascrockford/JSON-js.git" data-copied-hint="copied!" title="copy to clipboard"><span class="octicon octicon-clippy"></span></span>
+  </div>
+</div>
+
+  
+
+<div class="clone-url "
+  data-protocol-type="subversion"
+  data-url="/users/set_protocol?protocol_selector=subversion&amp;protocol_type=clone">
+  <h3><strong>Subversion</strong> checkout URL</h3>
+
+  <div class="clone-url-box">
+    <input type="text" class="clone js-url-field"
+           value="https://github.com/douglascrockford/JSON-js" readonly="readonly">
+
+    <span class="js-zeroclipboard url-box-clippy minibutton zeroclipboard-button" data-clipboard-text="https://github.com/douglascrockford/JSON-js" data-copied-hint="copied!" title="copy to clipboard"><span class="octicon octicon-clippy"></span></span>
+  </div>
+</div>
+
+
+
+<p class="clone-options">You can clone with
+    <a href="#" class="js-clone-selector" data-protocol="http">HTTPS</a>,
+    <a href="#" class="js-clone-selector" data-protocol="ssh">SSH</a>,
+    <a href="#" class="js-clone-selector" data-protocol="subversion">Subversion</a>,
+  and <a href="https://help.github.com/articles/which-remote-url-should-i-use">other methods.</a>
+</p>
+
+
+  <a href="http://windows.github.com" class="minibutton sidebar-button">
+    <span class="octicon octicon-device-desktop"></span>
+    Clone in Desktop
+  </a>
+
+                <a href="/douglascrockford/JSON-js/archive/master.zip"
+                   class="minibutton sidebar-button"
+                   title="Download this repository as a zip file"
+                   rel="nofollow">
+                  <span class="octicon octicon-cloud-download"></span>
+                  Download ZIP
+                </a>
+            </div>
+        </div><!-- /.repository-sidebar -->
+
+        <div id="js-repo-pjax-container" class="repository-content context-loader-container" data-pjax-container>
+          
+
+
+<!-- blob contrib key: blob_contributors:v21:5be543a0fd9a5d2d52ee291de4bb4b6a -->
+<!-- blob contrib frag key: views10/v8/blob_contributors:v21:5be543a0fd9a5d2d52ee291de4bb4b6a -->
+
+<p title="This is a placeholder element" class="js-history-link-replace hidden"></p>
+
+<a href="/douglascrockford/JSON-js/find/master" data-pjax data-hotkey="t" style="display:none">Show File Finder</a>
+
+<div class="file-navigation">
+  
+
+
+<div class="select-menu js-menu-container js-select-menu" >
+  <span class="minibutton select-menu-button js-menu-target" data-hotkey="w"
+    data-master-branch="master"
+    data-ref="master"
+    role="button" aria-label="Switch branches or tags" tabindex="0">
+    <span class="octicon octicon-git-branch"></span>
+    <i>branch:</i>
+    <span class="js-select-button">master</span>
+  </span>
+
+  <div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax>
+
+    <div class="select-menu-modal">
+      <div class="select-menu-header">
+        <span class="select-menu-title">Switch branches/tags</span>
+        <span class="octicon octicon-remove-close js-menu-close"></span>
+      </div> <!-- /.select-menu-header -->
+
+      <div class="select-menu-filters">
+        <div class="select-menu-text-filter">
+          <input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="js-filterable-field js-navigation-enable" placeholder="Filter branches/tags">
+        </div>
+        <div class="select-menu-tabs">
+          <ul>
+            <li class="select-menu-tab">
+              <a href="#" data-tab-filter="branches" class="js-select-menu-tab">Branches</a>
+            </li>
+            <li class="select-menu-tab">
+              <a href="#" data-tab-filter="tags" class="js-select-menu-tab">Tags</a>
+            </li>
+          </ul>
+        </div><!-- /.select-menu-tabs -->
+      </div><!-- /.select-menu-filters -->
+
+      <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches">
+
+        <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
+
+
+            <div class="select-menu-item js-navigation-item selected">
+              <span class="select-menu-item-icon octicon octicon-check"></span>
+              <a href="/douglascrockford/JSON-js/blob/master/json2.js" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="master" data-skip-pjax="true" rel="nofollow" title="master">master</a>
+            </div> <!-- /.select-menu-item -->
+        </div>
+
+          <div class="select-menu-no-results">Nothing to show</div>
+      </div> <!-- /.select-menu-list -->
+
+      <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags">
+        <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
+
+
+        </div>
+
+        <div class="select-menu-no-results">Nothing to show</div>
+      </div> <!-- /.select-menu-list -->
+
+    </div> <!-- /.select-menu-modal -->
+  </div> <!-- /.select-menu-modal-holder -->
+</div> <!-- /.select-menu -->
+
+  <div class="breadcrumb">
+    <span class='repo-root js-repo-root'><span itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb"><a href="/douglascrockford/JSON-js" data-branch="master" data-direction="back" data-pjax="true" itemscope="url"><span itemprop="title">JSON-js</span></a></span></span><span class="separator"> / </span><strong class="final-path">json2.js</strong> <span class="js-zeroclipboard minibutton zeroclipboard-button" data-clipboard-text="json2.js" data-copied-hint="copied!" title="copy to clipboard"><span class="octicon octicon-clippy"></span></span>
+  </div>
+</div>
+
+
+  
+  <div class="commit file-history-tease">
+    <img class="main-avatar" height="24" src="https://secure.gravatar.com/avatar/e56473600eaf21d901269f09f63bd7db?s=140&amp;d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="24" />
+    <span class="author"><span rel="author">Douglas Crockford</span></span>
+    <time class="js-relative-date" datetime="2013-05-26T07:58:04-07:00" title="2013-05-26 07:58:04">May 26, 2013</time>
+    <div class="commit-title">
+        <a href="/douglascrockford/JSON-js/commit/e39db4b7e6249f04a195e7dd0840e610cc9e941e" class="message" data-pjax="true" title="hygiene">hygiene</a>
+    </div>
+
+    <div class="participation">
+      <p class="quickstat"><a href="#blob_contributors_box" rel="facebox"><strong>1</strong> contributor</a></p>
+      
+    </div>
+    <div id="blob_contributors_box" style="display:none">
+      <h2 class="facebox-header">Users who have contributed to this file</h2>
+      <ul class="facebox-user-list">
+        <li class="facebox-user-list-item">
+          <img height="24" src="https://secure.gravatar.com/avatar/b871de839c31ddb1d9db8e33e0cb88a6?s=140&amp;d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="24" />
+          <a href="/douglascrockford">douglascrockford</a>
+        </li>
+      </ul>
+    </div>
+  </div>
+
+
+<div id="files" class="bubble">
+  <div class="file">
+    <div class="meta">
+      <div class="info">
+        <span class="icon"><b class="octicon octicon-file-text"></b></span>
+        <span class="mode" title="File Mode">file</span>
+          <span>487 lines (369 sloc)</span>
+        <span>17.524 kb</span>
+      </div>
+      <div class="actions">
+        <div class="button-group">
+                <a class="minibutton tooltipped leftwards"
+                   title="Clicking this button will automatically fork this project so you can edit the file"
+                   href="/douglascrockford/JSON-js/edit/master/json2.js"
+                   data-method="post" rel="nofollow">Edit</a>
+          <a href="/douglascrockford/JSON-js/raw/master/json2.js" class="button minibutton " id="raw-url">Raw</a>
+            <a href="/douglascrockford/JSON-js/blame/master/json2.js" class="button minibutton ">Blame</a>
+          <a href="/douglascrockford/JSON-js/commits/master/json2.js" class="button minibutton " rel="nofollow">History</a>
+        </div><!-- /.button-group -->
+            <a class="minibutton danger empty-icon tooltipped downwards"
+               href="/douglascrockford/JSON-js/delete/master/json2.js"
+               title="Fork this project and delete file"
+               data-method="post" data-test-id="delete-blob-file" rel="nofollow">
+            Delete
+          </a>
+      </div><!-- /.actions -->
+
+    </div>
+        <div class="blob-wrapper data type-javascript js-blob-data">
+        <table class="file-code file-diff">
+          <tr class="file-code-line">
+            <td class="blob-line-nums">
+              <span id="L1" rel="#L1">1</span>
+<span id="L2" rel="#L2">2</span>
+<span id="L3" rel="#L3">3</span>
+<span id="L4" rel="#L4">4</span>
+<span id="L5" rel="#L5">5</span>
+<span id="L6" rel="#L6">6</span>
+<span id="L7" rel="#L7">7</span>
+<span id="L8" rel="#L8">8</span>
+<span id="L9" rel="#L9">9</span>
+<span id="L10" rel="#L10">10</span>
+<span id="L11" rel="#L11">11</span>
+<span id="L12" rel="#L12">12</span>
+<span id="L13" rel="#L13">13</span>
+<span id="L14" rel="#L14">14</span>
+<span id="L15" rel="#L15">15</span>
+<span id="L16" rel="#L16">16</span>
+<span id="L17" rel="#L17">17</span>
+<span id="L18" rel="#L18">18</span>
+<span id="L19" rel="#L19">19</span>
+<span id="L20" rel="#L20">20</span>
+<span id="L21" rel="#L21">21</span>
+<span id="L22" rel="#L22">22</span>
+<span id="L23" rel="#L23">23</span>
+<span id="L24" rel="#L24">24</span>
+<span id="L25" rel="#L25">25</span>
+<span id="L26" rel="#L26">26</span>
+<span id="L27" rel="#L27">27</span>
+<span id="L28" rel="#L28">28</span>
+<span id="L29" rel="#L29">29</span>
+<span id="L30" rel="#L30">30</span>
+<span id="L31" rel="#L31">31</span>
+<span id="L32" rel="#L32">32</span>
+<span id="L33" rel="#L33">33</span>
+<span id="L34" rel="#L34">34</span>
+<span id="L35" rel="#L35">35</span>
+<span id="L36" rel="#L36">36</span>
+<span id="L37" rel="#L37">37</span>
+<span id="L38" rel="#L38">38</span>
+<span id="L39" rel="#L39">39</span>
+<span id="L40" rel="#L40">40</span>
+<span id="L41" rel="#L41">41</span>
+<span id="L42" rel="#L42">42</span>
+<span id="L43" rel="#L43">43</span>
+<span id="L44" rel="#L44">44</span>
+<span id="L45" rel="#L45">45</span>
+<span id="L46" rel="#L46">46</span>
+<span id="L47" rel="#L47">47</span>
+<span id="L48" rel="#L48">48</span>
+<span id="L49" rel="#L49">49</span>
+<span id="L50" rel="#L50">50</span>
+<span id="L51" rel="#L51">51</span>
+<span id="L52" rel="#L52">52</span>
+<span id="L53" rel="#L53">53</span>
+<span id="L54" rel="#L54">54</span>
+<span id="L55" rel="#L55">55</span>
+<span id="L56" rel="#L56">56</span>
+<span id="L57" rel="#L57">57</span>
+<span id="L58" rel="#L58">58</span>
+<span id="L59" rel="#L59">59</span>
+<span id="L60" rel="#L60">60</span>
+<span id="L61" rel="#L61">61</span>
+<span id="L62" rel="#L62">62</span>
+<span id="L63" rel="#L63">63</span>
+<span id="L64" rel="#L64">64</span>
+<span id="L65" rel="#L65">65</span>
+<span id="L66" rel="#L66">66</span>
+<span id="L67" rel="#L67">67</span>
+<span id="L68" rel="#L68">68</span>
+<span id="L69" rel="#L69">69</span>
+<span id="L70" rel="#L70">70</span>
+<span id="L71" rel="#L71">71</span>
+<span id="L72" rel="#L72">72</span>
+<span id="L73" rel="#L73">73</span>
+<span id="L74" rel="#L74">74</span>
+<span id="L75" rel="#L75">75</span>
+<span id="L76" rel="#L76">76</span>
+<span id="L77" rel="#L77">77</span>
+<span id="L78" rel="#L78">78</span>
+<span id="L79" rel="#L79">79</span>
+<span id="L80" rel="#L80">80</span>
+<span id="L81" rel="#L81">81</span>
+<span id="L82" rel="#L82">82</span>
+<span id="L83" rel="#L83">83</span>
+<span id="L84" rel="#L84">84</span>
+<span id="L85" rel="#L85">85</span>
+<span id="L86" rel="#L86">86</span>
+<span id="L87" rel="#L87">87</span>
+<span id="L88" rel="#L88">88</span>
+<span id="L89" rel="#L89">89</span>
+<span id="L90" rel="#L90">90</span>
+<span id="L91" rel="#L91">91</span>
+<span id="L92" rel="#L92">92</span>
+<span id="L93" rel="#L93">93</span>
+<span id="L94" rel="#L94">94</span>
+<span id="L95" rel="#L95">95</span>
+<span id="L96" rel="#L96">96</span>
+<span id="L97" rel="#L97">97</span>
+<span id="L98" rel="#L98">98</span>
+<span id="L99" rel="#L99">99</span>
+<span id="L100" rel="#L100">100</span>
+<span id="L101" rel="#L101">101</span>
+<span id="L102" rel="#L102">102</span>
+<span id="L103" rel="#L103">103</span>
+<span id="L104" rel="#L104">104</span>
+<span id="L105" rel="#L105">105</span>
+<span id="L106" rel="#L106">106</span>
+<span id="L107" rel="#L107">107</span>
+<span id="L108" rel="#L108">108</span>
+<span id="L109" rel="#L109">109</span>
+<span id="L110" rel="#L110">110</span>
+<span id="L111" rel="#L111">111</span>
+<span id="L112" rel="#L112">112</span>
+<span id="L113" rel="#L113">113</span>
+<span id="L114" rel="#L114">114</span>
+<span id="L115" rel="#L115">115</span>
+<span id="L116" rel="#L116">116</span>
+<span id="L117" rel="#L117">117</span>
+<span id="L118" rel="#L118">118</span>
+<span id="L119" rel="#L119">119</span>
+<span id="L120" rel="#L120">120</span>
+<span id="L121" rel="#L121">121</span>
+<span id="L122" rel="#L122">122</span>
+<span id="L123" rel="#L123">123</span>
+<span id="L124" rel="#L124">124</span>
+<span id="L125" rel="#L125">125</span>
+<span id="L126" rel="#L126">126</span>
+<span id="L127" rel="#L127">127</span>
+<span id="L128" rel="#L128">128</span>
+<span id="L129" rel="#L129">129</span>
+<span id="L130" rel="#L130">130</span>
+<span id="L131" rel="#L131">131</span>
+<span id="L132" rel="#L132">132</span>
+<span id="L133" rel="#L133">133</span>
+<span id="L134" rel="#L134">134</span>
+<span id="L135" rel="#L135">135</span>
+<span id="L136" rel="#L136">136</span>
+<span id="L137" rel="#L137">137</span>
+<span id="L138" rel="#L138">138</span>
+<span id="L139" rel="#L139">139</span>
+<span id="L140" rel="#L140">140</span>
+<span id="L141" rel="#L141">141</span>
+<span id="L142" rel="#L142">142</span>
+<span id="L143" rel="#L143">143</span>
+<span id="L144" rel="#L144">144</span>
+<span id="L145" rel="#L145">145</span>
+<span id="L146" rel="#L146">146</span>
+<span id="L147" rel="#L147">147</span>
+<span id="L148" rel="#L148">148</span>
+<span id="L149" rel="#L149">149</span>
+<span id="L150" rel="#L150">150</span>
+<span id="L151" rel="#L151">151</span>
+<span id="L152" rel="#L152">152</span>
+<span id="L153" rel="#L153">153</span>
+<span id="L154" rel="#L154">154</span>
+<span id="L155" rel="#L155">155</span>
+<span id="L156" rel="#L156">156</span>
+<span id="L157" rel="#L157">157</span>
+<span id="L158" rel="#L158">158</span>
+<span id="L159" rel="#L159">159</span>
+<span id="L160" rel="#L160">160</span>
+<span id="L161" rel="#L161">161</span>
+<span id="L162" rel="#L162">162</span>
+<span id="L163" rel="#L163">163</span>
+<span id="L164" rel="#L164">164</span>
+<span id="L165" rel="#L165">165</span>
+<span id="L166" rel="#L166">166</span>
+<span id="L167" rel="#L167">167</span>
+<span id="L168" rel="#L168">168</span>
+<span id="L169" rel="#L169">169</span>
+<span id="L170" rel="#L170">170</span>
+<span id="L171" rel="#L171">171</span>
+<span id="L172" rel="#L172">172</span>
+<span id="L173" rel="#L173">173</span>
+<span id="L174" rel="#L174">174</span>
+<span id="L175" rel="#L175">175</span>
+<span id="L176" rel="#L176">176</span>
+<span id="L177" rel="#L177">177</span>
+<span id="L178" rel="#L178">178</span>
+<span id="L179" rel="#L179">179</span>
+<span id="L180" rel="#L180">180</span>
+<span id="L181" rel="#L181">181</span>
+<span id="L182" rel="#L182">182</span>
+<span id="L183" rel="#L183">183</span>
+<span id="L184" rel="#L184">184</span>
+<span id="L185" rel="#L185">185</span>
+<span id="L186" rel="#L186">186</span>
+<span id="L187" rel="#L187">187</span>
+<span id="L188" rel="#L188">188</span>
+<span id="L189" rel="#L189">189</span>
+<span id="L190" rel="#L190">190</span>
+<span id="L191" rel="#L191">191</span>
+<span id="L192" rel="#L192">192</span>
+<span id="L193" rel="#L193">193</span>
+<span id="L194" rel="#L194">194</span>
+<span id="L195" rel="#L195">195</span>
+<span id="L196" rel="#L196">196</span>
+<span id="L197" rel="#L197">197</span>
+<span id="L198" rel="#L198">198</span>
+<span id="L199" rel="#L199">199</span>
+<span id="L200" rel="#L200">200</span>
+<span id="L201" rel="#L201">201</span>
+<span id="L202" rel="#L202">202</span>
+<span id="L203" rel="#L203">203</span>
+<span id="L204" rel="#L204">204</span>
+<span id="L205" rel="#L205">205</span>
+<span id="L206" rel="#L206">206</span>
+<span id="L207" rel="#L207">207</span>
+<span id="L208" rel="#L208">208</span>
+<span id="L209" rel="#L209">209</span>
+<span id="L210" rel="#L210">210</span>
+<span id="L211" rel="#L211">211</span>
+<span id="L212" rel="#L212">212</span>
+<span id="L213" rel="#L213">213</span>
+<span id="L214" rel="#L214">214</span>
+<span id="L215" rel="#L215">215</span>
+<span id="L216" rel="#L216">216</span>
+<span id="L217" rel="#L217">217</span>
+<span id="L218" rel="#L218">218</span>
+<span id="L219" rel="#L219">219</span>
+<span id="L220" rel="#L220">220</span>
+<span id="L221" rel="#L221">221</span>
+<span id="L222" rel="#L222">222</span>
+<span id="L223" rel="#L223">223</span>
+<span id="L224" rel="#L224">224</span>
+<span id="L225" rel="#L225">225</span>
+<span id="L226" rel="#L226">226</span>
+<span id="L227" rel="#L227">227</span>
+<span id="L228" rel="#L228">228</span>
+<span id="L229" rel="#L229">229</span>
+<span id="L230" rel="#L230">230</span>
+<span id="L231" rel="#L231">231</span>
+<span id="L232" rel="#L232">232</span>
+<span id="L233" rel="#L233">233</span>
+<span id="L234" rel="#L234">234</span>
+<span id="L235" rel="#L235">235</span>
+<span id="L236" rel="#L236">236</span>
+<span id="L237" rel="#L237">237</span>
+<span id="L238" rel="#L238">238</span>
+<span id="L239" rel="#L239">239</span>
+<span id="L240" rel="#L240">240</span>
+<span id="L241" rel="#L241">241</span>
+<span id="L242" rel="#L242">242</span>
+<span id="L243" rel="#L243">243</span>
+<span id="L244" rel="#L244">244</span>
+<span id="L245" rel="#L245">245</span>
+<span id="L246" rel="#L246">246</span>
+<span id="L247" rel="#L247">247</span>
+<span id="L248" rel="#L248">248</span>
+<span id="L249" rel="#L249">249</span>
+<span id="L250" rel="#L250">250</span>
+<span id="L251" rel="#L251">251</span>
+<span id="L252" rel="#L252">252</span>
+<span id="L253" rel="#L253">253</span>
+<span id="L254" rel="#L254">254</span>
+<span id="L255" rel="#L255">255</span>
+<span id="L256" rel="#L256">256</span>
+<span id="L257" rel="#L257">257</span>
+<span id="L258" rel="#L258">258</span>
+<span id="L259" rel="#L259">259</span>
+<span id="L260" rel="#L260">260</span>
+<span id="L261" rel="#L261">261</span>
+<span id="L262" rel="#L262">262</span>
+<span id="L263" rel="#L263">263</span>
+<span id="L264" rel="#L264">264</span>
+<span id="L265" rel="#L265">265</span>
+<span id="L266" rel="#L266">266</span>
+<span id="L267" rel="#L267">267</span>
+<span id="L268" rel="#L268">268</span>
+<span id="L269" rel="#L269">269</span>
+<span id="L270" rel="#L270">270</span>
+<span id="L271" rel="#L271">271</span>
+<span id="L272" rel="#L272">272</span>
+<span id="L273" rel="#L273">273</span>
+<span id="L274" rel="#L274">274</span>
+<span id="L275" rel="#L275">275</span>
+<span id="L276" rel="#L276">276</span>
+<span id="L277" rel="#L277">277</span>
+<span id="L278" rel="#L278">278</span>
+<span id="L279" rel="#L279">279</span>
+<span id="L280" rel="#L280">280</span>
+<span id="L281" rel="#L281">281</span>
+<span id="L282" rel="#L282">282</span>
+<span id="L283" rel="#L283">283</span>
+<span id="L284" rel="#L284">284</span>
+<span id="L285" rel="#L285">285</span>
+<span id="L286" rel="#L286">286</span>
+<span id="L287" rel="#L287">287</span>
+<span id="L288" rel="#L288">288</span>
+<span id="L289" rel="#L289">289</span>
+<span id="L290" rel="#L290">290</span>
+<span id="L291" rel="#L291">291</span>
+<span id="L292" rel="#L292">292</span>
+<span id="L293" rel="#L293">293</span>
+<span id="L294" rel="#L294">294</span>
+<span id="L295" rel="#L295">295</span>
+<span id="L296" rel="#L296">296</span>
+<span id="L297" rel="#L297">297</span>
+<span id="L298" rel="#L298">298</span>
+<span id="L299" rel="#L299">299</span>
+<span id="L300" rel="#L300">300</span>
+<span id="L301" rel="#L301">301</span>
+<span id="L302" rel="#L302">302</span>
+<span id="L303" rel="#L303">303</span>
+<span id="L304" rel="#L304">304</span>
+<span id="L305" rel="#L305">305</span>
+<span id="L306" rel="#L306">306</span>
+<span id="L307" rel="#L307">307</span>
+<span id="L308" rel="#L308">308</span>
+<span id="L309" rel="#L309">309</span>
+<span id="L310" rel="#L310">310</span>
+<span id="L311" rel="#L311">311</span>
+<span id="L312" rel="#L312">312</span>
+<span id="L313" rel="#L313">313</span>
+<span id="L314" rel="#L314">314</span>
+<span id="L315" rel="#L315">315</span>
+<span id="L316" rel="#L316">316</span>
+<span id="L317" rel="#L317">317</span>
+<span id="L318" rel="#L318">318</span>
+<span id="L319" rel="#L319">319</span>
+<span id="L320" rel="#L320">320</span>
+<span id="L321" rel="#L321">321</span>
+<span id="L322" rel="#L322">322</span>
+<span id="L323" rel="#L323">323</span>
+<span id="L324" rel="#L324">324</span>
+<span id="L325" rel="#L325">325</span>
+<span id="L326" rel="#L326">326</span>
+<span id="L327" rel="#L327">327</span>
+<span id="L328" rel="#L328">328</span>
+<span id="L329" rel="#L329">329</span>
+<span id="L330" rel="#L330">330</span>
+<span id="L331" rel="#L331">331</span>
+<span id="L332" rel="#L332">332</span>
+<span id="L333" rel="#L333">333</span>
+<span id="L334" rel="#L334">334</span>
+<span id="L335" rel="#L335">335</span>
+<span id="L336" rel="#L336">336</span>
+<span id="L337" rel="#L337">337</span>
+<span id="L338" rel="#L338">338</span>
+<span id="L339" rel="#L339">339</span>
+<span id="L340" rel="#L340">340</span>
+<span id="L341" rel="#L341">341</span>
+<span id="L342" rel="#L342">342</span>
+<span id="L343" rel="#L343">343</span>
+<span id="L344" rel="#L344">344</span>
+<span id="L345" rel="#L345">345</span>
+<span id="L346" rel="#L346">346</span>
+<span id="L347" rel="#L347">347</span>
+<span id="L348" rel="#L348">348</span>
+<span id="L349" rel="#L349">349</span>
+<span id="L350" rel="#L350">350</span>
+<span id="L351" rel="#L351">351</span>
+<span id="L352" rel="#L352">352</span>
+<span id="L353" rel="#L353">353</span>
+<span id="L354" rel="#L354">354</span>
+<span id="L355" rel="#L355">355</span>
+<span id="L356" rel="#L356">356</span>
+<span id="L357" rel="#L357">357</span>
+<span id="L358" rel="#L358">358</span>
+<span id="L359" rel="#L359">359</span>
+<span id="L360" rel="#L360">360</span>
+<span id="L361" rel="#L361">361</span>
+<span id="L362" rel="#L362">362</span>
+<span id="L363" rel="#L363">363</span>
+<span id="L364" rel="#L364">364</span>
+<span id="L365" rel="#L365">365</span>
+<span id="L366" rel="#L366">366</span>
+<span id="L367" rel="#L367">367</span>
+<span id="L368" rel="#L368">368</span>
+<span id="L369" rel="#L369">369</span>
+<span id="L370" rel="#L370">370</span>
+<span id="L371" rel="#L371">371</span>
+<span id="L372" rel="#L372">372</span>
+<span id="L373" rel="#L373">373</span>
+<span id="L374" rel="#L374">374</span>
+<span id="L375" rel="#L375">375</span>
+<span id="L376" rel="#L376">376</span>
+<span id="L377" rel="#L377">377</span>
+<span id="L378" rel="#L378">378</span>
+<span id="L379" rel="#L379">379</span>
+<span id="L380" rel="#L380">380</span>
+<span id="L381" rel="#L381">381</span>
+<span id="L382" rel="#L382">382</span>
+<span id="L383" rel="#L383">383</span>
+<span id="L384" rel="#L384">384</span>
+<span id="L385" rel="#L385">385</span>
+<span id="L386" rel="#L386">386</span>
+<span id="L387" rel="#L387">387</span>
+<span id="L388" rel="#L388">388</span>
+<span id="L389" rel="#L389">389</span>
+<span id="L390" rel="#L390">390</span>
+<span id="L391" rel="#L391">391</span>
+<span id="L392" rel="#L392">392</span>
+<span id="L393" rel="#L393">393</span>
+<span id="L394" rel="#L394">394</span>
+<span id="L395" rel="#L395">395</span>
+<span id="L396" rel="#L396">396</span>
+<span id="L397" rel="#L397">397</span>
+<span id="L398" rel="#L398">398</span>
+<span id="L399" rel="#L399">399</span>
+<span id="L400" rel="#L400">400</span>
+<span id="L401" rel="#L401">401</span>
+<span id="L402" rel="#L402">402</span>
+<span id="L403" rel="#L403">403</span>
+<span id="L404" rel="#L404">404</span>
+<span id="L405" rel="#L405">405</span>
+<span id="L406" rel="#L406">406</span>
+<span id="L407" rel="#L407">407</span>
+<span id="L408" rel="#L408">408</span>
+<span id="L409" rel="#L409">409</span>
+<span id="L410" rel="#L410">410</span>
+<span id="L411" rel="#L411">411</span>
+<span id="L412" rel="#L412">412</span>
+<span id="L413" rel="#L413">413</span>
+<span id="L414" rel="#L414">414</span>
+<span id="L415" rel="#L415">415</span>
+<span id="L416" rel="#L416">416</span>
+<span id="L417" rel="#L417">417</span>
+<span id="L418" rel="#L418">418</span>
+<span id="L419" rel="#L419">419</span>
+<span id="L420" rel="#L420">420</span>
+<span id="L421" rel="#L421">421</span>
+<span id="L422" rel="#L422">422</span>
+<span id="L423" rel="#L423">423</span>
+<span id="L424" rel="#L424">424</span>
+<span id="L425" rel="#L425">425</span>
+<span id="L426" rel="#L426">426</span>
+<span id="L427" rel="#L427">427</span>
+<span id="L428" rel="#L428">428</span>
+<span id="L429" rel="#L429">429</span>
+<span id="L430" rel="#L430">430</span>
+<span id="L431" rel="#L431">431</span>
+<span id="L432" rel="#L432">432</span>
+<span id="L433" rel="#L433">433</span>
+<span id="L434" rel="#L434">434</span>
+<span id="L435" rel="#L435">435</span>
+<span id="L436" rel="#L436">436</span>
+<span id="L437" rel="#L437">437</span>
+<span id="L438" rel="#L438">438</span>
+<span id="L439" rel="#L439">439</span>
+<span id="L440" rel="#L440">440</span>
+<span id="L441" rel="#L441">441</span>
+<span id="L442" rel="#L442">442</span>
+<span id="L443" rel="#L443">443</span>
+<span id="L444" rel="#L444">444</span>
+<span id="L445" rel="#L445">445</span>
+<span id="L446" rel="#L446">446</span>
+<span id="L447" rel="#L447">447</span>
+<span id="L448" rel="#L448">448</span>
+<span id="L449" rel="#L449">449</span>
+<span id="L450" rel="#L450">450</span>
+<span id="L451" rel="#L451">451</span>
+<span id="L452" rel="#L452">452</span>
+<span id="L453" rel="#L453">453</span>
+<span id="L454" rel="#L454">454</span>
+<span id="L455" rel="#L455">455</span>
+<span id="L456" rel="#L456">456</span>
+<span id="L457" rel="#L457">457</span>
+<span id="L458" rel="#L458">458</span>
+<span id="L459" rel="#L459">459</span>
+<span id="L460" rel="#L460">460</span>
+<span id="L461" rel="#L461">461</span>
+<span id="L462" rel="#L462">462</span>
+<span id="L463" rel="#L463">463</span>
+<span id="L464" rel="#L464">464</span>
+<span id="L465" rel="#L465">465</span>
+<span id="L466" rel="#L466">466</span>
+<span id="L467" rel="#L467">467</span>
+<span id="L468" rel="#L468">468</span>
+<span id="L469" rel="#L469">469</span>
+<span id="L470" rel="#L470">470</span>
+<span id="L471" rel="#L471">471</span>
+<span id="L472" rel="#L472">472</span>
+<span id="L473" rel="#L473">473</span>
+<span id="L474" rel="#L474">474</span>
+<span id="L475" rel="#L475">475</span>
+<span id="L476" rel="#L476">476</span>
+<span id="L477" rel="#L477">477</span>
+<span id="L478" rel="#L478">478</span>
+<span id="L479" rel="#L479">479</span>
+<span id="L480" rel="#L480">480</span>
+<span id="L481" rel="#L481">481</span>
+<span id="L482" rel="#L482">482</span>
+<span id="L483" rel="#L483">483</span>
+<span id="L484" rel="#L484">484</span>
+<span id="L485" rel="#L485">485</span>
+<span id="L486" rel="#L486">486</span>
+
+            </td>
+            <td class="blob-line-code">
+                    <div class="highlight"><pre><div class='line' id='LC1'><span class="cm">/*</span></div><div class='line' id='LC2'><span class="cm">    json2.js</span></div><div class='line' id='LC3'><span class="cm">    2013-05-26</span></div><div class='line' id='LC4'><br/></div><div class='line' id='LC5'><span class="cm">    Public Domain.</span></div><div class='line' id='LC6'><br/></div><div class='line' id='LC7'><span class="cm">    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.</span></div><div class='line' id='LC8'><br/></div><div class='line' id='LC9'><span class="cm">    See http://www.JSON.org/js.html</span></div><div class='line' id='LC10'><br/></div><div class='line' id='LC11'><br/></div><div class='line' id='LC12'><span class="cm">    This code should be minified before deployment.</span></div><div class='line' id='LC13'><span class="cm">    See http://javascript.crockford.com/jsmin.html</span></div><div class='line' id='LC14'><br/></div><div class='line' id='LC15'><span class="cm">    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO</span></div><div class='line' id='LC16'><span class="cm">    NOT CONTROL.</span></div><div class='line' id='LC17'><br/></div><div class='line' id='LC18'><br/></div><div class='line' id='LC19'><span class="cm">    This file creates a global JSON object containing two methods: stringify</span></div><div class='line' id='LC20'><span class="cm">    and parse.</span></div><div class='line' id='LC21'><br/></div><div class='line' id='LC22'><span class="cm">        JSON.stringify(value, replacer, space)</span></div><div class='line' id='LC23'><span class="cm">            value       any JavaScript value, usually an object or array.</span></div><div class='line' id='LC24'><br/></div><div class='line' id='LC25'><span class="cm">            replacer    an optional parameter that determines how object</span></div><div class='line' id='LC26'><span class="cm">                        values are stringified for objects. It can be a</span></div><div class='line' id='LC27'><span class="cm">                        function or an array of strings.</span></div><div class='line' id='LC28'><br/></div><div class='line' id='LC29'><span class="cm">            space       an optional parameter that specifies the indentation</span></div><div class='line' id='LC30'><span class="cm">                        of nested structures. If it is omitted, the text will</span></div><div class='line' id='LC31'><span class="cm">                        be packed without extra whitespace. If it is a number,</span></div><div class='line' id='LC32'><span class="cm">                        it will specify the number of spaces to indent at each</span></div><div class='line' id='LC33'><span class="cm">                        level. If it is a string (such as &#39;\t&#39; or &#39;&amp;nbsp;&#39;),</span></div><div class='line' id='LC34'><span class="cm">                        it contains the characters used to indent at each level.</span></div><div class='line' id='LC35'><br/></div><div class='line' id='LC36'><span class="cm">            This method produces a JSON text from a JavaScript value.</span></div><div class='line' id='LC37'><br/></div><div class='line' id='LC38'><span class="cm">            When an object value is found, if the object contains a toJSON</span></div><div class='line' id='LC39'><span class="cm">            method, its toJSON method will be called and the result will be</span></div><div class='line' id='LC40'><span class="cm">            stringified. A toJSON method does not serialize: it returns the</span></div><div class='line' id='LC41'><span class="cm">            value represented by the name/value pair that should be serialized,</span></div><div class='line' id='LC42'><span class="cm">            or undefined if nothing should be serialized. The toJSON method</span></div><div class='line' id='LC43'><span class="cm">            will be passed the key associated with the value, and this will be</span></div><div class='line' id='LC44'><span class="cm">            bound to the value</span></div><div class='line' id='LC45'><br/></div><div class='line' id='LC46'><span class="cm">            For example, this would serialize Dates as ISO strings.</span></div><div class='line' id='LC47'><br/></div><div class='line' id='LC48'><span class="cm">                Date.prototype.toJSON = function (key) {</span></div><div class='line' id='LC49'><span class="cm">                    function f(n) {</span></div><div class='line' id='LC50'><span class="cm">                        // Format integers to have at least two digits.</span></div><div class='line' id='LC51'><span class="cm">                        return n &lt; 10 ? &#39;0&#39; + n : n;</span></div><div class='line' id='LC52'><span class="cm">                    }</span></div><div class='line' id='LC53'><br/></div><div class='line' id='LC54'><span class="cm">                    return this.getUTCFullYear()   + &#39;-&#39; +</span></div><div class='line' id='LC55'><span class="cm">                         f(this.getUTCMonth() + 1) + &#39;-&#39; +</span></div><div class='line' id='LC56'><span class="cm">                         f(this.getUTCDate())      + &#39;T&#39; +</span></div><div class='line' id='LC57'><span class="cm">                         f(this.getUTCHours())     + &#39;:&#39; +</span></div><div class='line' id='LC58'><span class="cm">                         f(this.getUTCMinutes())   + &#39;:&#39; +</span></div><div class='line' id='LC59'><span class="cm">                         f(this.getUTCSeconds())   + &#39;Z&#39;;</span></div><div class='line' id='LC60'><span class="cm">                };</span></div><div class='line' id='LC61'><br/></div><div class='line' id='LC62'><span class="cm">            You can provide an optional replacer method. It will be passed the</span></div><div class='line' id='LC63'><span class="cm">            key and value of each member, with this bound to the containing</span></div><div class='line' id='LC64'><span class="cm">            object. The value that is returned from your method will be</span></div><div class='line' id='LC65'><span class="cm">            serialized. If your method returns undefined, then the member will</span></div><div class='line' id='LC66'><span class="cm">            be excluded from the serialization.</span></div><div class='line' id='LC67'><br/></div><div class='line' id='LC68'><span class="cm">            If the replacer parameter is an array of strings, then it will be</span></div><div class='line' id='LC69'><span class="cm">            used to select the members to be serialized. It filters the results</span></div><div class='line' id='LC70'><span class="cm">            such that only members with keys listed in the replacer array are</span></div><div class='line' id='LC71'><span class="cm">            stringified.</span></div><div class='line' id='LC72'><br/></div><div class='line' id='LC73'><span class="cm">            Values that do not have JSON representations, such as undefined or</span></div><div class='line' id='LC74'><span class="cm">            functions, will not be serialized. Such values in objects will be</span></div><div class='line' id='LC75'><span class="cm">            dropped; in arrays they will be replaced with null. You can use</span></div><div class='line' id='LC76'><span class="cm">            a replacer function to replace those with JSON values.</span></div><div class='line' id='LC77'><span class="cm">            JSON.stringify(undefined) returns undefined.</span></div><div class='line' id='LC78'><br/></div><div class='line' id='LC79'><span class="cm">            The optional space parameter produces a stringification of the</span></div><div class='line' id='LC80'><span class="cm">            value that is filled with line breaks and indentation to make it</span></div><div class='line' id='LC81'><span class="cm">            easier to read.</span></div><div class='line' id='LC82'><br/></div><div class='line' id='LC83'><span class="cm">            If the space parameter is a non-empty string, then that string will</span></div><div class='line' id='LC84'><span class="cm">            be used for indentation. If the space parameter is a number, then</span></div><div class='line' id='LC85'><span class="cm">            the indentation will be that many spaces.</span></div><div class='line' id='LC86'><br/></div><div class='line' id='LC87'><span class="cm">            Example:</span></div><div class='line' id='LC88'><br/></div><div class='line' id='LC89'><span class="cm">            text = JSON.stringify([&#39;e&#39;, {pluribus: &#39;unum&#39;}]);</span></div><div class='line' id='LC90'><span class="cm">            // text is &#39;[&quot;e&quot;,{&quot;pluribus&quot;:&quot;unum&quot;}]&#39;</span></div><div class='line' id='LC91'><br/></div><div class='line' id='LC92'><br/></div><div class='line' id='LC93'><span class="cm">            text = JSON.stringify([&#39;e&#39;, {pluribus: &#39;unum&#39;}], null, &#39;\t&#39;);</span></div><div class='line' id='LC94'><span class="cm">            // text is &#39;[\n\t&quot;e&quot;,\n\t{\n\t\t&quot;pluribus&quot;: &quot;unum&quot;\n\t}\n]&#39;</span></div><div class='line' id='LC95'><br/></div><div class='line' id='LC96'><span class="cm">            text = JSON.stringify([new Date()], function (key, value) {</span></div><div class='line' id='LC97'><span class="cm">                return this[key] instanceof Date ?</span></div><div class='line' id='LC98'><span class="cm">                    &#39;Date(&#39; + this[key] + &#39;)&#39; : value;</span></div><div class='line' id='LC99'><span class="cm">            });</span></div><div class='line' id='LC100'><span class="cm">            // text is &#39;[&quot;Date(---current time---)&quot;]&#39;</span></div><div class='line' id='LC101'><br/></div><div class='line' id='LC102'><br/></div><div class='line' id='LC103'><span class="cm">        JSON.parse(text, reviver)</span></div><div class='line' id='LC104'><span class="cm">            This method parses a JSON text to produce an object or array.</span></div><div class='line' id='LC105'><span class="cm">            It can throw a SyntaxError exception.</span></div><div class='line' id='LC106'><br/></div><div class='line' id='LC107'><span class="cm">            The optional reviver parameter is a function that can filter and</span></div><div class='line' id='LC108'><span class="cm">            transform the results. It receives each of the keys and values,</span></div><div class='line' id='LC109'><span class="cm">            and its return value is used instead of the original value.</span></div><div class='line' id='LC110'><span class="cm">            If it returns what it received, then the structure is not modified.</span></div><div class='line' id='LC111'><span class="cm">            If it returns undefined then the member is deleted.</span></div><div class='line' id='LC112'><br/></div><div class='line' id='LC113'><span class="cm">            Example:</span></div><div class='line' id='LC114'><br/></div><div class='line' id='LC115'><span class="cm">            // Parse the text. Values that look like ISO date strings will</span></div><div class='line' id='LC116'><span class="cm">            // be converted to Date objects.</span></div><div class='line' id='LC117'><br/></div><div class='line' id='LC118'><span class="cm">            myData = JSON.parse(text, function (key, value) {</span></div><div class='line' id='LC119'><span class="cm">                var a;</span></div><div class='line' id='LC120'><span class="cm">                if (typeof value === &#39;string&#39;) {</span></div><div class='line' id='LC121'><span class="cm">                    a =</span></div><div class='line' id='LC122'><span class="cm">/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);</span></div><div class='line' id='LC123'><span class="cm">                    if (a) {</span></div><div class='line' id='LC124'><span class="cm">                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],</span></div><div class='line' id='LC125'><span class="cm">                            +a[5], +a[6]));</span></div><div class='line' id='LC126'><span class="cm">                    }</span></div><div class='line' id='LC127'><span class="cm">                }</span></div><div class='line' id='LC128'><span class="cm">                return value;</span></div><div class='line' id='LC129'><span class="cm">            });</span></div><div class='line' id='LC130'><br/></div><div class='line' id='LC131'><span class="cm">            myData = JSON.parse(&#39;[&quot;Date(09/09/2001)&quot;]&#39;, function (key, value) {</span></div><div class='line' id='LC132'><span class="cm">                var d;</span></div><div class='line' id='LC133'><span class="cm">                if (typeof value === &#39;string&#39; &amp;&amp;</span></div><div class='line' id='LC134'><span class="cm">                        value.slice(0, 5) === &#39;Date(&#39; &amp;&amp;</span></div><div class='line' id='LC135'><span class="cm">                        value.slice(-1) === &#39;)&#39;) {</span></div><div class='line' id='LC136'><span class="cm">                    d = new Date(value.slice(5, -1));</span></div><div class='line' id='LC137'><span class="cm">                    if (d) {</span></div><div class='line' id='LC138'><span class="cm">                        return d;</span></div><div class='line' id='LC139'><span class="cm">                    }</span></div><div class='line' id='LC140'><span class="cm">                }</span></div><div class='line' id='LC141'><span class="cm">                return value;</span></div><div class='line' id='LC142'><span class="cm">            });</span></div><div class='line' id='LC143'><br/></div><div class='line' id='LC144'><br/></div><div class='line' id='LC145'><span class="cm">    This is a reference implementation. You are free to copy, modify, or</span></div><div class='line' id='LC146'><span class="cm">    redistribute.</span></div><div class='line' id='LC147'><span class="cm">*/</span></div><div class='line' id='LC148'><br/></div><div class='line' id='LC149'><span class="cm">/*jslint evil: true, regexp: true */</span></div><div class='line' id='LC150'><br/></div><div class='line' id='LC151'><span class="cm">/*members &quot;&quot;, &quot;\b&quot;, &quot;\t&quot;, &quot;\n&quot;, &quot;\f&quot;, &quot;\r&quot;, &quot;\&quot;&quot;, JSON, &quot;\\&quot;, apply,</span></div><div class='line' id='LC152'><span class="cm">    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,</span></div><div class='line' id='LC153'><span class="cm">    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,</span></div><div class='line' id='LC154'><span class="cm">    lastIndex, length, parse, prototype, push, replace, slice, stringify,</span></div><div class='line' id='LC155'><span class="cm">    test, toJSON, toString, valueOf</span></div><div class='line' id='LC156'><span class="cm">*/</span></div><div class='line' id='LC157'><br/></div><div class='line' id='LC158'><br/></div><div class='line' id='LC159'><span class="c1">// Create a JSON object only if one does not already exist. We create the</span></div><div class='line' id='LC160'><span class="c1">// methods in a closure to avoid creating global variables.</span></div><div class='line' id='LC161'><br/></div><div class='line' id='LC162'><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">JSON</span> <span class="o">!==</span> <span class="s1">&#39;object&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC163'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">JSON</span> <span class="o">=</span> <span class="p">{};</span></div><div class='line' id='LC164'><span class="p">}</span></div><div class='line' id='LC165'><br/></div><div class='line' id='LC166'><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span></div><div class='line' id='LC167'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;use strict&#39;</span><span class="p">;</span></div><div class='line' id='LC168'><br/></div><div class='line' id='LC169'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">function</span> <span class="nx">f</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC170'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="c1">// Format integers to have at least two digits.</span></div><div class='line' id='LC171'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nx">n</span> <span class="o">&lt;</span> <span class="mi">10</span> <span class="o">?</span> <span class="s1">&#39;0&#39;</span> <span class="o">+</span> <span class="nx">n</span> <span class="o">:</span> <span class="nx">n</span><span class="p">;</span></div><div class='line' id='LC172'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC173'><br/></div><div class='line' id='LC174'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toJSON</span> <span class="o">!==</span> <span class="s1">&#39;function&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC175'><br/></div><div class='line' id='LC176'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">Date</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toJSON</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span></div><div class='line' id='LC177'><br/></div><div class='line' id='LC178'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nb">isFinite</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">valueOf</span><span class="p">())</span></div><div class='line' id='LC179'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">?</span> <span class="k">this</span><span class="p">.</span><span class="nx">getUTCFullYear</span><span class="p">()</span>     <span class="o">+</span> <span class="s1">&#39;-&#39;</span> <span class="o">+</span></div><div class='line' id='LC180'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">f</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">getUTCMonth</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;-&#39;</span> <span class="o">+</span></div><div class='line' id='LC181'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">f</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">getUTCDate</span><span class="p">())</span>      <span class="o">+</span> <span class="s1">&#39;T&#39;</span> <span class="o">+</span></div><div class='line' id='LC182'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">f</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">getUTCHours</span><span class="p">())</span>     <span class="o">+</span> <span class="s1">&#39;:&#39;</span> <span class="o">+</span></div><div class='line' id='LC183'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">f</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">getUTCMinutes</span><span class="p">())</span>   <span class="o">+</span> <span class="s1">&#39;:&#39;</span> <span class="o">+</span></div><div class='line' id='LC184'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">f</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">getUTCSeconds</span><span class="p">())</span>   <span class="o">+</span> <span class="s1">&#39;Z&#39;</span></div><div class='line' id='LC185'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">:</span> <span class="kc">null</span><span class="p">;</span></div><div class='line' id='LC186'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">};</span></div><div class='line' id='LC187'><br/></div><div class='line' id='LC188'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toJSON</span>      <span class="o">=</span></div><div class='line' id='LC189'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">Number</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toJSON</span>  <span class="o">=</span></div><div class='line' id='LC190'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">Boolean</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toJSON</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span></div><div class='line' id='LC191'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">valueOf</span><span class="p">();</span></div><div class='line' id='LC192'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">};</span></div><div class='line' id='LC193'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC194'><br/></div><div class='line' id='LC195'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">var</span> <span class="nx">cx</span> <span class="o">=</span> <span class="sr">/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g</span><span class="p">,</span></div><div class='line' id='LC196'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">escapable</span> <span class="o">=</span> <span class="sr">/[\\\&quot;\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g</span><span class="p">,</span></div><div class='line' id='LC197'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">gap</span><span class="p">,</span></div><div class='line' id='LC198'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">indent</span><span class="p">,</span></div><div class='line' id='LC199'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">meta</span> <span class="o">=</span> <span class="p">{</span>    <span class="c1">// table of character substitutions</span></div><div class='line' id='LC200'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;\b&#39;</span><span class="o">:</span> <span class="s1">&#39;\\b&#39;</span><span class="p">,</span></div><div class='line' id='LC201'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;\t&#39;</span><span class="o">:</span> <span class="s1">&#39;\\t&#39;</span><span class="p">,</span></div><div class='line' id='LC202'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;\n&#39;</span><span class="o">:</span> <span class="s1">&#39;\\n&#39;</span><span class="p">,</span></div><div class='line' id='LC203'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;\f&#39;</span><span class="o">:</span> <span class="s1">&#39;\\f&#39;</span><span class="p">,</span></div><div class='line' id='LC204'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;\r&#39;</span><span class="o">:</span> <span class="s1">&#39;\\r&#39;</span><span class="p">,</span></div><div class='line' id='LC205'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;&quot;&#39;</span> <span class="o">:</span> <span class="s1">&#39;\\&quot;&#39;</span><span class="p">,</span></div><div class='line' id='LC206'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;\\&#39;</span><span class="o">:</span> <span class="s1">&#39;\\\\&#39;</span></div><div class='line' id='LC207'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">},</span></div><div class='line' id='LC208'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">rep</span><span class="p">;</span></div><div class='line' id='LC209'><br/></div><div class='line' id='LC210'><br/></div><div class='line' id='LC211'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">function</span> <span class="nx">quote</span><span class="p">(</span><span class="nx">string</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC212'><br/></div><div class='line' id='LC213'><span class="c1">// If the string contains no control characters, no quote characters, and no</span></div><div class='line' id='LC214'><span class="c1">// backslash characters, then we can safely slap some quotes around it.</span></div><div class='line' id='LC215'><span class="c1">// Otherwise we must also replace the offending characters with safe escape</span></div><div class='line' id='LC216'><span class="c1">// sequences.</span></div><div class='line' id='LC217'><br/></div><div class='line' id='LC218'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">escapable</span><span class="p">.</span><span class="nx">lastIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span></div><div class='line' id='LC219'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nx">escapable</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">string</span><span class="p">)</span> <span class="o">?</span> <span class="s1">&#39;&quot;&#39;</span> <span class="o">+</span> <span class="nx">string</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="nx">escapable</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC220'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">var</span> <span class="nx">c</span> <span class="o">=</span> <span class="nx">meta</span><span class="p">[</span><span class="nx">a</span><span class="p">];</span></div><div class='line' id='LC221'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="k">typeof</span> <span class="nx">c</span> <span class="o">===</span> <span class="s1">&#39;string&#39;</span></div><div class='line' id='LC222'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">?</span> <span class="nx">c</span></div><div class='line' id='LC223'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">:</span> <span class="s1">&#39;\\u&#39;</span> <span class="o">+</span> <span class="p">(</span><span class="s1">&#39;0000&#39;</span> <span class="o">+</span> <span class="nx">a</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="mi">0</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="mi">16</span><span class="p">)).</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">4</span><span class="p">);</span></div><div class='line' id='LC224'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">})</span> <span class="o">+</span> <span class="s1">&#39;&quot;&#39;</span> <span class="o">:</span> <span class="s1">&#39;&quot;&#39;</span> <span class="o">+</span> <span class="nx">string</span> <span class="o">+</span> <span class="s1">&#39;&quot;&#39;</span><span class="p">;</span></div><div class='line' id='LC225'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC226'><br/></div><div class='line' id='LC227'><br/></div><div class='line' id='LC228'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">function</span> <span class="nx">str</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">holder</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC229'><br/></div><div class='line' id='LC230'><span class="c1">// Produce a string from holder[key].</span></div><div class='line' id='LC231'><br/></div><div class='line' id='LC232'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">var</span> <span class="nx">i</span><span class="p">,</span>          <span class="c1">// The loop counter.</span></div><div class='line' id='LC233'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">k</span><span class="p">,</span>          <span class="c1">// The member key.</span></div><div class='line' id='LC234'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">v</span><span class="p">,</span>          <span class="c1">// The member value.</span></div><div class='line' id='LC235'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">length</span><span class="p">,</span></div><div class='line' id='LC236'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">mind</span> <span class="o">=</span> <span class="nx">gap</span><span class="p">,</span></div><div class='line' id='LC237'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">partial</span><span class="p">,</span></div><div class='line' id='LC238'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">value</span> <span class="o">=</span> <span class="nx">holder</span><span class="p">[</span><span class="nx">key</span><span class="p">];</span></div><div class='line' id='LC239'><br/></div><div class='line' id='LC240'><span class="c1">// If the value has a toJSON method, call it to obtain a replacement value.</span></div><div class='line' id='LC241'><br/></div><div class='line' id='LC242'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nx">value</span> <span class="o">&amp;&amp;</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="s1">&#39;object&#39;</span> <span class="o">&amp;&amp;</span></div><div class='line' id='LC243'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">typeof</span> <span class="nx">value</span><span class="p">.</span><span class="nx">toJSON</span> <span class="o">===</span> <span class="s1">&#39;function&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC244'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">.</span><span class="nx">toJSON</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span></div><div class='line' id='LC245'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC246'><br/></div><div class='line' id='LC247'><span class="c1">// If we were called with a replacer function, then call the replacer to</span></div><div class='line' id='LC248'><span class="c1">// obtain a replacement value.</span></div><div class='line' id='LC249'><br/></div><div class='line' id='LC250'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">rep</span> <span class="o">===</span> <span class="s1">&#39;function&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC251'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">value</span> <span class="o">=</span> <span class="nx">rep</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">holder</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">);</span></div><div class='line' id='LC252'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC253'><br/></div><div class='line' id='LC254'><span class="c1">// What happens next depends on the value&#39;s type.</span></div><div class='line' id='LC255'><br/></div><div class='line' id='LC256'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">switch</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC257'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">case</span> <span class="s1">&#39;string&#39;</span><span class="o">:</span></div><div class='line' id='LC258'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nx">quote</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span></div><div class='line' id='LC259'><br/></div><div class='line' id='LC260'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">case</span> <span class="s1">&#39;number&#39;</span><span class="o">:</span></div><div class='line' id='LC261'><br/></div><div class='line' id='LC262'><span class="c1">// JSON numbers must be finite. Encode non-finite numbers as null.</span></div><div class='line' id='LC263'><br/></div><div class='line' id='LC264'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nb">isFinite</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">?</span> <span class="nb">String</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">:</span> <span class="s1">&#39;null&#39;</span><span class="p">;</span></div><div class='line' id='LC265'><br/></div><div class='line' id='LC266'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">case</span> <span class="s1">&#39;boolean&#39;</span><span class="o">:</span></div><div class='line' id='LC267'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">case</span> <span class="s1">&#39;null&#39;</span><span class="o">:</span></div><div class='line' id='LC268'><br/></div><div class='line' id='LC269'><span class="c1">// If the value is a boolean or null, convert it to a string. Note:</span></div><div class='line' id='LC270'><span class="c1">// typeof null does not produce &#39;null&#39;. The case is included here in</span></div><div class='line' id='LC271'><span class="c1">// the remote chance that this gets fixed someday.</span></div><div class='line' id='LC272'><br/></div><div class='line' id='LC273'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nb">String</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span></div><div class='line' id='LC274'><br/></div><div class='line' id='LC275'><span class="c1">// If the type is &#39;object&#39;, we might be dealing with an object or an array or</span></div><div class='line' id='LC276'><span class="c1">// null.</span></div><div class='line' id='LC277'><br/></div><div class='line' id='LC278'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">case</span> <span class="s1">&#39;object&#39;</span><span class="o">:</span></div><div class='line' id='LC279'><br/></div><div class='line' id='LC280'><span class="c1">// Due to a specification blunder in ECMAScript, typeof null is &#39;object&#39;,</span></div><div class='line' id='LC281'><span class="c1">// so watch out for that case.</span></div><div class='line' id='LC282'><br/></div><div class='line' id='LC283'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC284'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="s1">&#39;null&#39;</span><span class="p">;</span></div><div class='line' id='LC285'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC286'><br/></div><div class='line' id='LC287'><span class="c1">// Make an array to hold the partial results of stringifying this object value.</span></div><div class='line' id='LC288'><br/></div><div class='line' id='LC289'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">gap</span> <span class="o">+=</span> <span class="nx">indent</span><span class="p">;</span></div><div class='line' id='LC290'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">partial</span> <span class="o">=</span> <span class="p">[];</span></div><div class='line' id='LC291'><br/></div><div class='line' id='LC292'><span class="c1">// Is the value an array?</span></div><div class='line' id='LC293'><br/></div><div class='line' id='LC294'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toString</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">===</span> <span class="s1">&#39;[object Array]&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC295'><br/></div><div class='line' id='LC296'><span class="c1">// The value is an array. Stringify every element. Use null as a placeholder</span></div><div class='line' id='LC297'><span class="c1">// for non-JSON values.</span></div><div class='line' id='LC298'><br/></div><div class='line' id='LC299'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">length</span> <span class="o">=</span> <span class="nx">value</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span></div><div class='line' id='LC300'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC301'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">partial</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">str</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="o">||</span> <span class="s1">&#39;null&#39;</span><span class="p">;</span></div><div class='line' id='LC302'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC303'><br/></div><div class='line' id='LC304'><span class="c1">// Join all of the elements together, separated with commas, and wrap them in</span></div><div class='line' id='LC305'><span class="c1">// brackets.</span></div><div class='line' id='LC306'><br/></div><div class='line' id='LC307'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">v</span> <span class="o">=</span> <span class="nx">partial</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span></div><div class='line' id='LC308'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">?</span> <span class="s1">&#39;[]&#39;</span></div><div class='line' id='LC309'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">:</span> <span class="nx">gap</span></div><div class='line' id='LC310'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">?</span> <span class="s1">&#39;[\n&#39;</span> <span class="o">+</span> <span class="nx">gap</span> <span class="o">+</span> <span class="nx">partial</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s1">&#39;,\n&#39;</span> <span class="o">+</span> <span class="nx">gap</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;\n&#39;</span> <span class="o">+</span> <span class="nx">mind</span> <span class="o">+</span> <span class="s1">&#39;]&#39;</span></div><div class='line' id='LC311'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">:</span> <span class="s1">&#39;[&#39;</span> <span class="o">+</span> <span class="nx">partial</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s1">&#39;,&#39;</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;]&#39;</span><span class="p">;</span></div><div class='line' id='LC312'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">gap</span> <span class="o">=</span> <span class="nx">mind</span><span class="p">;</span></div><div class='line' id='LC313'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nx">v</span><span class="p">;</span></div><div class='line' id='LC314'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC315'><br/></div><div class='line' id='LC316'><span class="c1">// If the replacer is an array, use it to select the members to be stringified.</span></div><div class='line' id='LC317'><br/></div><div class='line' id='LC318'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nx">rep</span> <span class="o">&amp;&amp;</span> <span class="k">typeof</span> <span class="nx">rep</span> <span class="o">===</span> <span class="s1">&#39;object&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC319'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">length</span> <span class="o">=</span> <span class="nx">rep</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span></div><div class='line' id='LC320'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC321'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">rep</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">===</span> <span class="s1">&#39;string&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC322'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">k</span> <span class="o">=</span> <span class="nx">rep</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span></div><div class='line' id='LC323'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">v</span> <span class="o">=</span> <span class="nx">str</span><span class="p">(</span><span class="nx">k</span><span class="p">,</span> <span class="nx">value</span><span class="p">);</span></div><div class='line' id='LC324'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nx">v</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC325'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">partial</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">quote</span><span class="p">(</span><span class="nx">k</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="nx">gap</span> <span class="o">?</span> <span class="s1">&#39;: &#39;</span> <span class="o">:</span> <span class="s1">&#39;:&#39;</span><span class="p">)</span> <span class="o">+</span> <span class="nx">v</span><span class="p">);</span></div><div class='line' id='LC326'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC327'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC328'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC329'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span> <span class="k">else</span> <span class="p">{</span></div><div class='line' id='LC330'><br/></div><div class='line' id='LC331'><span class="c1">// Otherwise, iterate through all of the keys in the object.</span></div><div class='line' id='LC332'><br/></div><div class='line' id='LC333'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">for</span> <span class="p">(</span><span class="nx">k</span> <span class="k">in</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC334'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">hasOwnProperty</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">k</span><span class="p">))</span> <span class="p">{</span></div><div class='line' id='LC335'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">v</span> <span class="o">=</span> <span class="nx">str</span><span class="p">(</span><span class="nx">k</span><span class="p">,</span> <span class="nx">value</span><span class="p">);</span></div><div class='line' id='LC336'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nx">v</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC337'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">partial</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">quote</span><span class="p">(</span><span class="nx">k</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="nx">gap</span> <span class="o">?</span> <span class="s1">&#39;: &#39;</span> <span class="o">:</span> <span class="s1">&#39;:&#39;</span><span class="p">)</span> <span class="o">+</span> <span class="nx">v</span><span class="p">);</span></div><div class='line' id='LC338'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC339'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC340'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC341'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC342'><br/></div><div class='line' id='LC343'><span class="c1">// Join all of the member texts together, separated with commas,</span></div><div class='line' id='LC344'><span class="c1">// and wrap them in braces.</span></div><div class='line' id='LC345'><br/></div><div class='line' id='LC346'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">v</span> <span class="o">=</span> <span class="nx">partial</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span></div><div class='line' id='LC347'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">?</span> <span class="s1">&#39;{}&#39;</span></div><div class='line' id='LC348'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">:</span> <span class="nx">gap</span></div><div class='line' id='LC349'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">?</span> <span class="s1">&#39;{\n&#39;</span> <span class="o">+</span> <span class="nx">gap</span> <span class="o">+</span> <span class="nx">partial</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s1">&#39;,\n&#39;</span> <span class="o">+</span> <span class="nx">gap</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;\n&#39;</span> <span class="o">+</span> <span class="nx">mind</span> <span class="o">+</span> <span class="s1">&#39;}&#39;</span></div><div class='line' id='LC350'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">:</span> <span class="s1">&#39;{&#39;</span> <span class="o">+</span> <span class="nx">partial</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s1">&#39;,&#39;</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;}&#39;</span><span class="p">;</span></div><div class='line' id='LC351'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">gap</span> <span class="o">=</span> <span class="nx">mind</span><span class="p">;</span></div><div class='line' id='LC352'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nx">v</span><span class="p">;</span></div><div class='line' id='LC353'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC354'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC355'><br/></div><div class='line' id='LC356'><span class="c1">// If the JSON object does not yet have a stringify method, give it one.</span></div><div class='line' id='LC357'><br/></div><div class='line' id='LC358'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span> <span class="o">!==</span> <span class="s1">&#39;function&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC359'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">replacer</span><span class="p">,</span> <span class="nx">space</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC360'><br/></div><div class='line' id='LC361'><span class="c1">// The stringify method takes a value and an optional replacer, and an optional</span></div><div class='line' id='LC362'><span class="c1">// space parameter, and returns a JSON text. The replacer can be a function</span></div><div class='line' id='LC363'><span class="c1">// that can replace values, or an array of strings that will select the keys.</span></div><div class='line' id='LC364'><span class="c1">// A default replacer method can be provided. Use of the space parameter can</span></div><div class='line' id='LC365'><span class="c1">// produce text that is more easily readable.</span></div><div class='line' id='LC366'><br/></div><div class='line' id='LC367'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">var</span> <span class="nx">i</span><span class="p">;</span></div><div class='line' id='LC368'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">gap</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span></div><div class='line' id='LC369'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">indent</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span></div><div class='line' id='LC370'><br/></div><div class='line' id='LC371'><span class="c1">// If the space parameter is a number, make an indent string containing that</span></div><div class='line' id='LC372'><span class="c1">// many spaces.</span></div><div class='line' id='LC373'><br/></div><div class='line' id='LC374'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">space</span> <span class="o">===</span> <span class="s1">&#39;number&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC375'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">space</span><span class="p">;</span> <span class="nx">i</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC376'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">indent</span> <span class="o">+=</span> <span class="s1">&#39; &#39;</span><span class="p">;</span></div><div class='line' id='LC377'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC378'><br/></div><div class='line' id='LC379'><span class="c1">// If the space parameter is a string, it will be used as the indent string.</span></div><div class='line' id='LC380'><br/></div><div class='line' id='LC381'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">space</span> <span class="o">===</span> <span class="s1">&#39;string&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC382'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">indent</span> <span class="o">=</span> <span class="nx">space</span><span class="p">;</span></div><div class='line' id='LC383'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC384'><br/></div><div class='line' id='LC385'><span class="c1">// If there is a replacer, it must be a function or an array.</span></div><div class='line' id='LC386'><span class="c1">// Otherwise, throw an error.</span></div><div class='line' id='LC387'><br/></div><div class='line' id='LC388'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">rep</span> <span class="o">=</span> <span class="nx">replacer</span><span class="p">;</span></div><div class='line' id='LC389'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nx">replacer</span> <span class="o">&amp;&amp;</span> <span class="k">typeof</span> <span class="nx">replacer</span> <span class="o">!==</span> <span class="s1">&#39;function&#39;</span> <span class="o">&amp;&amp;</span></div><div class='line' id='LC390'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">(</span><span class="k">typeof</span> <span class="nx">replacer</span> <span class="o">!==</span> <span class="s1">&#39;object&#39;</span> <span class="o">||</span></div><div class='line' id='LC391'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">typeof</span> <span class="nx">replacer</span><span class="p">.</span><span class="nx">length</span> <span class="o">!==</span> <span class="s1">&#39;number&#39;</span><span class="p">))</span> <span class="p">{</span></div><div class='line' id='LC392'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s1">&#39;JSON.stringify&#39;</span><span class="p">);</span></div><div class='line' id='LC393'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC394'><br/></div><div class='line' id='LC395'><span class="c1">// Make a fake root object containing our value under the key of &#39;&#39;.</span></div><div class='line' id='LC396'><span class="c1">// Return the result of stringifying the value.</span></div><div class='line' id='LC397'><br/></div><div class='line' id='LC398'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nx">str</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="p">{</span><span class="s1">&#39;&#39;</span><span class="o">:</span> <span class="nx">value</span><span class="p">});</span></div><div class='line' id='LC399'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">};</span></div><div class='line' id='LC400'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC401'><br/></div><div class='line' id='LC402'><br/></div><div class='line' id='LC403'><span class="c1">// If the JSON object does not yet have a parse method, give it one.</span></div><div class='line' id='LC404'><br/></div><div class='line' id='LC405'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span> <span class="o">!==</span> <span class="s1">&#39;function&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC406'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">text</span><span class="p">,</span> <span class="nx">reviver</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC407'><br/></div><div class='line' id='LC408'><span class="c1">// The parse method takes a text and an optional reviver function, and returns</span></div><div class='line' id='LC409'><span class="c1">// a JavaScript value if the text is a valid JSON text.</span></div><div class='line' id='LC410'><br/></div><div class='line' id='LC411'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">var</span> <span class="nx">j</span><span class="p">;</span></div><div class='line' id='LC412'><br/></div><div class='line' id='LC413'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">function</span> <span class="nx">walk</span><span class="p">(</span><span class="nx">holder</span><span class="p">,</span> <span class="nx">key</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC414'><br/></div><div class='line' id='LC415'><span class="c1">// The walk method is used to recursively walk the resulting structure so</span></div><div class='line' id='LC416'><span class="c1">// that modifications can be made.</span></div><div class='line' id='LC417'><br/></div><div class='line' id='LC418'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="kd">var</span> <span class="nx">k</span><span class="p">,</span> <span class="nx">v</span><span class="p">,</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">holder</span><span class="p">[</span><span class="nx">key</span><span class="p">];</span></div><div class='line' id='LC419'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nx">value</span> <span class="o">&amp;&amp;</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="s1">&#39;object&#39;</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC420'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">for</span> <span class="p">(</span><span class="nx">k</span> <span class="k">in</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC421'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">hasOwnProperty</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">k</span><span class="p">))</span> <span class="p">{</span></div><div class='line' id='LC422'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">v</span> <span class="o">=</span> <span class="nx">walk</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">k</span><span class="p">);</span></div><div class='line' id='LC423'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nx">v</span> <span class="o">!==</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC424'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">value</span><span class="p">[</span><span class="nx">k</span><span class="p">]</span> <span class="o">=</span> <span class="nx">v</span><span class="p">;</span></div><div class='line' id='LC425'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span> <span class="k">else</span> <span class="p">{</span></div><div class='line' id='LC426'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">delete</span> <span class="nx">value</span><span class="p">[</span><span class="nx">k</span><span class="p">];</span></div><div class='line' id='LC427'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC428'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC429'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC430'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC431'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="nx">reviver</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">holder</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">);</span></div><div class='line' id='LC432'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC433'><br/></div><div class='line' id='LC434'><br/></div><div class='line' id='LC435'><span class="c1">// Parsing happens in four stages. In the first stage, we replace certain</span></div><div class='line' id='LC436'><span class="c1">// Unicode characters with escape sequences. JavaScript handles many characters</span></div><div class='line' id='LC437'><span class="c1">// incorrectly, either silently deleting them, or treating them as line endings.</span></div><div class='line' id='LC438'><br/></div><div class='line' id='LC439'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">text</span> <span class="o">=</span> <span class="nb">String</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span></div><div class='line' id='LC440'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">cx</span><span class="p">.</span><span class="nx">lastIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span></div><div class='line' id='LC441'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nx">cx</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">text</span><span class="p">))</span> <span class="p">{</span></div><div class='line' id='LC442'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">text</span> <span class="o">=</span> <span class="nx">text</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="nx">cx</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span></div><div class='line' id='LC443'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="s1">&#39;\\u&#39;</span> <span class="o">+</span></div><div class='line' id='LC444'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">(</span><span class="s1">&#39;0000&#39;</span> <span class="o">+</span> <span class="nx">a</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="mi">0</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="mi">16</span><span class="p">)).</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">4</span><span class="p">);</span></div><div class='line' id='LC445'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">});</span></div><div class='line' id='LC446'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC447'><br/></div><div class='line' id='LC448'><span class="c1">// In the second stage, we run the text against regular expressions that look</span></div><div class='line' id='LC449'><span class="c1">// for non-JSON patterns. We are especially concerned with &#39;()&#39; and &#39;new&#39;</span></div><div class='line' id='LC450'><span class="c1">// because they can cause invocation, and &#39;=&#39; because it can cause mutation.</span></div><div class='line' id='LC451'><span class="c1">// But just to be safe, we want to reject all unexpected forms.</span></div><div class='line' id='LC452'><br/></div><div class='line' id='LC453'><span class="c1">// We split the second stage into 4 regexp operations in order to work around</span></div><div class='line' id='LC454'><span class="c1">// crippling inefficiencies in IE&#39;s and Safari&#39;s regexp engines. First we</span></div><div class='line' id='LC455'><span class="c1">// replace the JSON backslash pairs with &#39;@&#39; (a non-JSON character). Second, we</span></div><div class='line' id='LC456'><span class="c1">// replace all simple value tokens with &#39;]&#39; characters. Third, we delete all</span></div><div class='line' id='LC457'><span class="c1">// open brackets that follow a colon or comma or that begin the text. Finally,</span></div><div class='line' id='LC458'><span class="c1">// we look to see that the remaining characters are only whitespace or &#39;]&#39; or</span></div><div class='line' id='LC459'><span class="c1">// &#39;,&#39; or &#39;:&#39; or &#39;{&#39; or &#39;}&#39;. If that is so, then the text is safe for eval.</span></div><div class='line' id='LC460'><br/></div><div class='line' id='LC461'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="sr">/^[\],:{}\s]*$/</span></div><div class='line' id='LC462'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">text</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\\(?:[&quot;\\\/bfnrt]|u[0-9a-fA-F]{4})/g</span><span class="p">,</span> <span class="s1">&#39;@&#39;</span><span class="p">)</span></div><div class='line' id='LC463'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/&quot;[^&quot;\\\n\r]*&quot;|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g</span><span class="p">,</span> <span class="s1">&#39;]&#39;</span><span class="p">)</span></div><div class='line' id='LC464'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/(?:^|:|,)(?:\s*\[)+/g</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)))</span> <span class="p">{</span></div><div class='line' id='LC465'><br/></div><div class='line' id='LC466'><span class="c1">// In the third stage we use the eval function to compile the text into a</span></div><div class='line' id='LC467'><span class="c1">// JavaScript structure. The &#39;{&#39; operator is subject to a syntactic ambiguity</span></div><div class='line' id='LC468'><span class="c1">// in JavaScript: it can begin a block or an object literal. We wrap the text</span></div><div class='line' id='LC469'><span class="c1">// in parens to eliminate the ambiguity.</span></div><div class='line' id='LC470'><br/></div><div class='line' id='LC471'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nx">j</span> <span class="o">=</span> <span class="nb">eval</span><span class="p">(</span><span class="s1">&#39;(&#39;</span> <span class="o">+</span> <span class="nx">text</span> <span class="o">+</span> <span class="s1">&#39;)&#39;</span><span class="p">);</span></div><div class='line' id='LC472'><br/></div><div class='line' id='LC473'><span class="c1">// In the optional fourth stage, we recursively walk the new structure, passing</span></div><div class='line' id='LC474'><span class="c1">// each name/value pair to a reviver function for possible transformation.</span></div><div class='line' id='LC475'><br/></div><div class='line' id='LC476'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="k">typeof</span> <span class="nx">reviver</span> <span class="o">===</span> <span class="s1">&#39;function&#39;</span></div><div class='line' id='LC477'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">?</span> <span class="nx">walk</span><span class="p">({</span><span class="s1">&#39;&#39;</span><span class="o">:</span> <span class="nx">j</span><span class="p">},</span> <span class="s1">&#39;&#39;</span><span class="p">)</span></div><div class='line' id='LC478'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="o">:</span> <span class="nx">j</span><span class="p">;</span></div><div class='line' id='LC479'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC480'><br/></div><div class='line' id='LC481'><span class="c1">// If the text is not JSON parseable, then a SyntaxError is thrown.</span></div><div class='line' id='LC482'><br/></div><div class='line' id='LC483'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">throw</span> <span class="k">new</span> <span class="nx">SyntaxError</span><span class="p">(</span><span class="s1">&#39;JSON.parse&#39;</span><span class="p">);</span></div><div class='line' id='LC484'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">};</span></div><div class='line' id='LC485'>&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span></div><div class='line' id='LC486'><span class="p">}());</span></div></pre></div>
+            </td>
+          </tr>
+        </table>
+  </div>
+
+  </div>
+</div>
+
+<a href="#jump-to-line" rel="facebox[.linejump]" data-hotkey="l" class="js-jump-to-line" style="display:none">Jump to Line</a>
+<div id="jump-to-line" style="display:none">
+  <form accept-charset="UTF-8" class="js-jump-to-line-form">
+    <input class="linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line&hellip;" autofocus>
+    <button type="submit" class="button">Go</button>
+  </form>
+</div>
+
+        </div>
+
+      </div><!-- /.repo-container -->
+      <div class="modal-backdrop"></div>
+    </div><!-- /.container -->
+  </div><!-- /.site -->
+
+
+    </div><!-- /.wrapper -->
+
+      <div class="container">
+  <div class="site-footer">
+    <ul class="site-footer-links right">
+      <li><a href="https://status.github.com/">Status</a></li>
+      <li><a href="http://developer.github.com">API</a></li>
+      <li><a href="http://training.github.com">Training</a></li>
+      <li><a href="http://shop.github.com">Shop</a></li>
+      <li><a href="/blog">Blog</a></li>
+      <li><a href="/about">About</a></li>
+
+    </ul>
+
+    <a href="/">
+      <span class="mega-octicon octicon-mark-github"></span>
+    </a>
+
+    <ul class="site-footer-links">
+      <li>&copy; 2013 <span title="0.55060s from github-fe128-cp1-prd.iad.github.net">GitHub</span>, Inc.</li>
+        <li><a href="/site/terms">Terms</a></li>
+        <li><a href="/site/privacy">Privacy</a></li>
+        <li><a href="/security">Security</a></li>
+        <li><a href="/contact">Contact</a></li>
+    </ul>
+  </div><!-- /.site-footer -->
+</div><!-- /.container -->
+
+
+    <div class="fullscreen-overlay js-fullscreen-overlay" id="fullscreen_overlay">
+  <div class="fullscreen-container js-fullscreen-container">
+    <div class="textarea-wrap">
+      <textarea name="fullscreen-contents" id="fullscreen-contents" class="js-fullscreen-contents" placeholder="" data-suggester="fullscreen_suggester"></textarea>
+          <div class="suggester-container">
+              <div class="suggester fullscreen-suggester js-navigation-container" id="fullscreen_suggester"
+                 data-url="/douglascrockford/JSON-js/suggestions/commit">
+              </div>
+          </div>
+    </div>
+  </div>
+  <div class="fullscreen-sidebar">
+    <a href="#" class="exit-fullscreen js-exit-fullscreen tooltipped leftwards" title="Exit Zen Mode">
+      <span class="mega-octicon octicon-screen-normal"></span>
+    </a>
+    <a href="#" class="theme-switcher js-theme-switcher tooltipped leftwards"
+      title="Switch themes">
+      <span class="octicon octicon-color-mode"></span>
+    </a>
+  </div>
+</div>
+
+
+
+    <div id="ajax-error-message" class="flash flash-error">
+      <span class="octicon octicon-alert"></span>
+      <a href="#" class="octicon octicon-remove-close close ajax-error-dismiss"></a>
+      Something went wrong with that request. Please try again.
+    </div>
+
+    
+  </body>
+</html>
+
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/jszip.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/jszip.js
new file mode 100644
index 0000000..0378189
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/jszip.js
@@ -0,0 +1,1425 @@
+/**
+
+JSZip - A Javascript class for generating and reading zip files
+<http://stuartk.com/jszip>
+
+(c) 2009-2012 Stuart Knightley <stuart [at] stuartk.com>
+Dual licenced under the MIT license or GPLv3. See LICENSE.markdown.
+
+Usage:
+   zip = new JSZip();
+   zip.file("hello.txt", "Hello, World!").file("tempfile", "nothing");
+   zip.folder("images").file("smile.gif", base64Data, {base64: true});
+   zip.file("Xmas.txt", "Ho ho ho !", {date : new Date("December 25, 2007 00:00:01")});
+   zip.remove("tempfile");
+
+   base64zip = zip.generate();
+
+**/
+"use strict";
+
+/**
+ * Representation a of zip file in js
+ * @constructor
+ * @param {String=|ArrayBuffer=|Uint8Array=|Buffer=} data the data to load, if any (optional).
+ * @param {Object=} options the options for creating this objects (optional).
+ */
+var JSZip = function(data, options) {
+   // object containing the files :
+   // {
+   //   "folder/" : {...},
+   //   "folder/data.txt" : {...}
+   // }
+   this.files = {};
+
+   // Where we are in the hierarchy
+   this.root = "";
+
+   if (data) {
+      this.load(data, options);
+   }
+};
+
+JSZip.signature = {
+   LOCAL_FILE_HEADER : "\x50\x4b\x03\x04",
+   CENTRAL_FILE_HEADER : "\x50\x4b\x01\x02",
+   CENTRAL_DIRECTORY_END : "\x50\x4b\x05\x06",
+   ZIP64_CENTRAL_DIRECTORY_LOCATOR : "\x50\x4b\x06\x07",
+   ZIP64_CENTRAL_DIRECTORY_END : "\x50\x4b\x06\x06",
+   DATA_DESCRIPTOR : "\x50\x4b\x07\x08"
+};
+
+// Default properties for a new file
+JSZip.defaults = {
+   base64: false,
+   binary: false,
+   dir: false,
+   date: null,
+   compression: null
+};
+
+
+JSZip.prototype = (function () {
+
+   /**
+    * Returns the raw data of a ZipObject, decompress the content if necessary.
+    * @param {ZipObject} file the file to use.
+    * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
+    */
+   var getRawData = function (file) {
+      if (file._data instanceof JSZip.CompressedObject) {
+         file._data = file._data.getContent();
+         file.options.binary = true;
+         file.options.base64 = false;
+
+         if (JSZip.utils.getTypeOf(file._data) === "uint8array") {
+            var copy = file._data;
+            // when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array.
+            // if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file).
+            file._data = new Uint8Array(copy.length);
+            // with an empty Uint8Array, Opera fails with a "Offset larger than array size"
+            if (copy.length !== 0) {
+               file._data.set(copy, 0);
+            }
+         }
+      }
+      return file._data;
+   };
+
+   /**
+    * Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it.
+    * @param {ZipObject} file the file to use.
+    * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
+    */
+   var getBinaryData = function (file) {
+      var result = getRawData(file), type = JSZip.utils.getTypeOf(result);
+      if (type === "string") {
+         if (!file.options.binary) {
+            // unicode text !
+            // unicode string => binary string is a painful process, check if we can avoid it.
+            if (JSZip.support.uint8array && typeof TextEncoder === "function") {
+               return TextEncoder("utf-8").encode(result);
+            }
+            if (JSZip.support.nodebuffer) {
+               return new Buffer(result, "utf-8");
+            }
+         }
+         return file.asBinary();
+      }
+      return result;
+   }
+
+   /**
+    * Transform this._data into a string.
+    * @param {function} filter a function String -> String, applied if not null on the result.
+    * @return {String} the string representing this._data.
+    */
+   var dataToString = function (asUTF8) {
+      var result = getRawData(this);
+      if (result === null || typeof result === "undefined") {
+         return "";
+      }
+      // if the data is a base64 string, we decode it before checking the encoding !
+      if (this.options.base64) {
+         result = JSZip.base64.decode(result);
+      }
+      if (asUTF8 && this.options.binary) {
+         // JSZip.prototype.utf8decode supports arrays as input
+         // skip to array => string step, utf8decode will do it.
+         result = JSZip.prototype.utf8decode(result);
+      } else {
+         // no utf8 transformation, do the array => string step.
+         result = JSZip.utils.transformTo("string", result);
+      }
+
+      if (!asUTF8 && !this.options.binary) {
+         result = JSZip.prototype.utf8encode(result);
+      }
+      return result;
+   };
+   /**
+    * A simple object representing a file in the zip file.
+    * @constructor
+    * @param {string} name the name of the file
+    * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
+    * @param {Object} options the options of the file
+    */
+   var ZipObject = function (name, data, options) {
+      this.name = name;
+      this._data = data;
+      this.options = options;
+   };
+
+   ZipObject.prototype = {
+      /**
+       * Return the content as UTF8 string.
+       * @return {string} the UTF8 string.
+       */
+      asText : function () {
+         return dataToString.call(this, true);
+      },
+      /**
+       * Returns the binary content.
+       * @return {string} the content as binary.
+       */
+      asBinary : function () {
+         return dataToString.call(this, false);
+      },
+      /**
+       * Returns the content as a nodejs Buffer.
+       * @return {Buffer} the content as a Buffer.
+       */
+      asNodeBuffer : function () {
+         var result = getBinaryData(this);
+         return JSZip.utils.transformTo("nodebuffer", result);
+      },
+      /**
+       * Returns the content as an Uint8Array.
+       * @return {Uint8Array} the content as an Uint8Array.
+       */
+      asUint8Array : function () {
+         var result = getBinaryData(this);
+         return JSZip.utils.transformTo("uint8array", result);
+      },
+      /**
+       * Returns the content as an ArrayBuffer.
+       * @return {ArrayBuffer} the content as an ArrayBufer.
+       */
+      asArrayBuffer : function () {
+         return this.asUint8Array().buffer;
+      }
+   };
+
+   /**
+    * Transform an integer into a string in hexadecimal.
+    * @private
+    * @param {number} dec the number to convert.
+    * @param {number} bytes the number of bytes to generate.
+    * @returns {string} the result.
+    */
+   var decToHex = function(dec, bytes) {
+      var hex = "", i;
+      for(i = 0; i < bytes; i++) {
+         hex += String.fromCharCode(dec&0xff);
+         dec=dec>>>8;
+      }
+      return hex;
+   };
+
+   /**
+    * Merge the objects passed as parameters into a new one.
+    * @private
+    * @param {...Object} var_args All objects to merge.
+    * @return {Object} a new object with the data of the others.
+    */
+   var extend = function () {
+      var result = {}, i, attr;
+      for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
+         for (attr in arguments[i]) {
+            if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
+               result[attr] = arguments[i][attr];
+            }
+         }
+      }
+      return result;
+   };
+
+   /**
+    * Transforms the (incomplete) options from the user into the complete
+    * set of options to create a file.
+    * @private
+    * @param {Object} o the options from the user.
+    * @return {Object} the complete set of options.
+    */
+   var prepareFileAttrs = function (o) {
+      o = o || {};
+      if (o.base64 === true && o.binary == null) {
+         o.binary = true;
+      }
+      o = extend(o, JSZip.defaults);
+      o.date = o.date || new Date();
+      if (o.compression !== null) o.compression = o.compression.toUpperCase();
+
+      return o;
+   };
+
+   /**
+    * Add a file in the current folder.
+    * @private
+    * @param {string} name the name of the file
+    * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
+    * @param {Object} o the options of the file
+    * @return {Object} the new file.
+    */
+   var fileAdd = function (name, data, o) {
+      // be sure sub folders exist
+      var parent = parentFolder(name), dataType = JSZip.utils.getTypeOf(data);
+      if (parent) {
+         folderAdd.call(this, parent);
+      }
+
+      o = prepareFileAttrs(o);
+
+      if (o.dir || data === null || typeof data === "undefined") {
+         o.base64 = false;
+         o.binary = false;
+         data = null;
+      } else if (dataType === "string") {
+         if (o.binary && !o.base64) {
+            // optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask
+            if (o.optimizedBinaryString !== true) {
+               // this is a string, not in a base64 format.
+               // Be sure that this is a correct "binary string"
+               data = JSZip.utils.string2binary(data);
+            }
+         }
+      } else { // arraybuffer, uint8array, ...
+         o.base64 = false;
+         o.binary = true;
+
+         if (!dataType && !(data instanceof JSZip.CompressedObject)) {
+            throw new Error("The data of '" + name + "' is in an unsupported format !");
+         }
+
+         // special case : it's way easier to work with Uint8Array than with ArrayBuffer
+         if (dataType === "arraybuffer") {
+            data = JSZip.utils.transformTo("uint8array", data);
+         }
+      }
+
+      return this.files[name] = new ZipObject(name, data, o);
+   };
+
+
+   /**
+    * Find the parent folder of the path.
+    * @private
+    * @param {string} path the path to use
+    * @return {string} the parent folder, or ""
+    */
+   var parentFolder = function (path) {
+      if (path.slice(-1) == '/') {
+         path = path.substring(0, path.length - 1);
+      }
+      var lastSlash = path.lastIndexOf('/');
+      return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
+   };
+
+   /**
+    * Add a (sub) folder in the current folder.
+    * @private
+    * @param {string} name the folder's name
+    * @return {Object} the new folder.
+    */
+   var folderAdd = function (name) {
+      // Check the name ends with a /
+      if (name.slice(-1) != "/") {
+         name += "/"; // IE doesn't like substr(-1)
+      }
+
+      // Does this folder already exist?
+      if (!this.files[name]) {
+         fileAdd.call(this, name, null, {dir:true});
+      }
+      return this.files[name];
+   };
+
+   /**
+    * Generate a JSZip.CompressedObject for a given zipOject.
+    * @param {ZipObject} file the object to read.
+    * @param {JSZip.compression} compression the compression to use.
+    * @return {JSZip.CompressedObject} the compressed result.
+    */
+   var generateCompressedObjectFrom = function (file, compression) {
+      var result = new JSZip.CompressedObject(), content;
+
+      // the data has not been decompressed, we might reuse things !
+      if (file._data instanceof JSZip.CompressedObject) {
+         result.uncompressedSize = file._data.uncompressedSize;
+         result.crc32 = file._data.crc32;
+
+         if (result.uncompressedSize === 0 || file.options.dir) {
+            compression = JSZip.compressions['STORE'];
+            result.compressedContent = "";
+            result.crc32 = 0;
+         } else if (file._data.compressionMethod === compression.magic) {
+            result.compressedContent = file._data.getCompressedContent();
+         } else {
+            content = file._data.getContent()
+            // need to decompress / recompress
+            result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
+         }
+      } else {
+         // have uncompressed data
+         content = getBinaryData(file);
+         if (!content || content.length === 0 || file.options.dir) {
+            compression = JSZip.compressions['STORE'];
+            content = "";
+         }
+         result.uncompressedSize = content.length;
+         result.crc32 = this.crc32(content);
+         result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
+      }
+
+      result.compressedSize = result.compressedContent.length;
+      result.compressionMethod = compression.magic;
+
+      return result;
+   };
+
+   /**
+    * Generate the various parts used in the construction of the final zip file.
+    * @param {string} name the file name.
+    * @param {ZipObject} file the file content.
+    * @param {JSZip.CompressedObject} compressedObject the compressed object.
+    * @param {number} offset the current offset from the start of the zip file.
+    * @return {object} the zip parts.
+    */
+   var generateZipParts = function(name, file, compressedObject, offset) {
+      var data = compressedObject.compressedContent,
+          utfEncodedFileName = this.utf8encode(file.name),
+          useUTF8 = utfEncodedFileName !== file.name,
+          o       = file.options,
+          dosTime,
+          dosDate;
+
+      // date
+      // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
+      // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
+      // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
+
+      dosTime = o.date.getHours();
+      dosTime = dosTime << 6;
+      dosTime = dosTime | o.date.getMinutes();
+      dosTime = dosTime << 5;
+      dosTime = dosTime | o.date.getSeconds() / 2;
+
+      dosDate = o.date.getFullYear() - 1980;
+      dosDate = dosDate << 4;
+      dosDate = dosDate | (o.date.getMonth() + 1);
+      dosDate = dosDate << 5;
+      dosDate = dosDate | o.date.getDate();
+
+
+      var header = "";
+
+      // version needed to extract
+      header += "\x0A\x00";
+      // general purpose bit flag
+      // set bit 11 if utf8
+      header += useUTF8 ? "\x00\x08" : "\x00\x00";
+      // compression method
+      header += compressedObject.compressionMethod;
+      // last mod file time
+      header += decToHex(dosTime, 2);
+      // last mod file date
+      header += decToHex(dosDate, 2);
+      // crc-32
+      header += decToHex(compressedObject.crc32, 4);
+      // compressed size
+      header += decToHex(compressedObject.compressedSize, 4);
+      // uncompressed size
+      header += decToHex(compressedObject.uncompressedSize, 4);
+      // file name length
+      header += decToHex(utfEncodedFileName.length, 2);
+      // extra field length
+      header += "\x00\x00";
+
+
+      var fileRecord = JSZip.signature.LOCAL_FILE_HEADER + header + utfEncodedFileName;
+
+      var dirRecord = JSZip.signature.CENTRAL_FILE_HEADER +
+      // version made by (00: DOS)
+      "\x14\x00" +
+      // file header (common to file and central directory)
+      header +
+      // file comment length
+      "\x00\x00" +
+      // disk number start
+      "\x00\x00" +
+      // internal file attributes TODO
+      "\x00\x00" +
+      // external file attributes
+      (file.options.dir===true?"\x10\x00\x00\x00":"\x00\x00\x00\x00")+
+      // relative offset of local header
+      decToHex(offset, 4) +
+      // file name
+      utfEncodedFileName;
+
+
+      return {
+         fileRecord : fileRecord,
+         dirRecord : dirRecord,
+         compressedObject : compressedObject
+      };
+   };
+
+   /**
+    * An object to write any content to a string.
+    * @constructor
+    */
+   var StringWriter = function () {
+      this.data = [];
+   };
+   StringWriter.prototype = {
+      /**
+       * Append any content to the current string.
+       * @param {Object} input the content to add.
+       */
+      append : function (input) {
+         input = JSZip.utils.transformTo("string", input);
+         this.data.push(input);
+      },
+      /**
+       * Finalize the construction an return the result.
+       * @return {string} the generated string.
+       */
+      finalize : function () {
+         return this.data.join("");
+      }
+   };
+   /**
+    * An object to write any content to an Uint8Array.
+    * @constructor
+    * @param {number} length The length of the array.
+    */
+   var Uint8ArrayWriter = function (length) {
+      this.data = new Uint8Array(length);
+      this.index = 0;
+   };
+   Uint8ArrayWriter.prototype = {
+      /**
+       * Append any content to the current array.
+       * @param {Object} input the content to add.
+       */
+      append : function (input) {
+         if (input.length !== 0) {
+            // with an empty Uint8Array, Opera fails with a "Offset larger than array size"
+            input = JSZip.utils.transformTo("uint8array", input);
+            this.data.set(input, this.index);
+            this.index += input.length;
+         }
+      },
+      /**
+       * Finalize the construction an return the result.
+       * @return {Uint8Array} the generated array.
+       */
+      finalize : function () {
+         return this.data;
+      }
+   };
+
+   // return the actual prototype of JSZip
+   return {
+      /**
+       * Read an existing zip and merge the data in the current JSZip object.
+       * The implementation is in jszip-load.js, don't forget to include it.
+       * @param {String|ArrayBuffer|Uint8Array|Buffer} stream  The stream to load
+       * @param {Object} options Options for loading the stream.
+       *  options.base64 : is the stream in base64 ? default : false
+       * @return {JSZip} the current JSZip object
+       */
+      load : function (stream, options) {
+         throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
+      },
+
+      /**
+       * Filter nested files/folders with the specified function.
+       * @param {Function} search the predicate to use :
+       * function (relativePath, file) {...}
+       * It takes 2 arguments : the relative path and the file.
+       * @return {Array} An array of matching elements.
+       */
+      filter : function (search) {
+         var result = [], filename, relativePath, file, fileClone;
+         for (filename in this.files) {
+            if ( !this.files.hasOwnProperty(filename) ) { continue; }
+            file = this.files[filename];
+            // return a new object, don't let the user mess with our internal objects :)
+            fileClone = new ZipObject(file.name, file._data, extend(file.options));
+            relativePath = filename.slice(this.root.length, filename.length);
+            if (filename.slice(0, this.root.length) === this.root && // the file is in the current root
+                search(relativePath, fileClone)) { // and the file matches the function
+               result.push(fileClone);
+            }
+         }
+         return result;
+      },
+
+      /**
+       * Add a file to the zip file, or search a file.
+       * @param   {string|RegExp} name The name of the file to add (if data is defined),
+       * the name of the file to find (if no data) or a regex to match files.
+       * @param   {String|ArrayBuffer|Uint8Array|Buffer} data  The file data, either raw or base64 encoded
+       * @param   {Object} o     File options
+       * @return  {JSZip|Object|Array} this JSZip object (when adding a file),
+       * a file (when searching by string) or an array of files (when searching by regex).
+       */
+      file : function(name, data, o) {
+         if (arguments.length === 1) {
+            if (name instanceof RegExp) {
+               var regexp = name;
+               return this.filter(function(relativePath, file) {
+                  return !file.options.dir && regexp.test(relativePath);
+               });
+            } else { // text
+               return this.filter(function (relativePath, file) {
+                  return !file.options.dir && relativePath === name;
+               })[0]||null;
+            }
+         } else { // more than one argument : we have data !
+            name = this.root+name;
+            fileAdd.call(this, name, data, o);
+         }
+         return this;
+      },
+
+      /**
+       * Add a directory to the zip file, or search.
+       * @param   {String|RegExp} arg The name of the directory to add, or a regex to search folders.
+       * @return  {JSZip} an object with the new directory as the root, or an array containing matching folders.
+       */
+      folder : function(arg) {
+         if (!arg) {
+            return this;
+         }
+
+         if (arg instanceof RegExp) {
+            return this.filter(function(relativePath, file) {
+               return file.options.dir && arg.test(relativePath);
+            });
+         }
+
+         // else, name is a new folder
+         var name = this.root + arg;
+         var newFolder = folderAdd.call(this, name);
+
+         // Allow chaining by returning a new object with this folder as the root
+         var ret = this.clone();
+         ret.root = newFolder.name;
+         return ret;
+      },
+
+      /**
+       * Delete a file, or a directory and all sub-files, from the zip
+       * @param {string} name the name of the file to delete
+       * @return {JSZip} this JSZip object
+       */
+      remove : function(name) {
+         name = this.root + name;
+         var file = this.files[name];
+         if (!file) {
+            // Look for any folders
+            if (name.slice(-1) != "/") {
+               name += "/";
+            }
+            file = this.files[name];
+         }
+
+         if (file) {
+            if (!file.options.dir) {
+               // file
+               delete this.files[name];
+            } else {
+               // folder
+               var kids = this.filter(function (relativePath, file) {
+                  return file.name.slice(0, name.length) === name;
+               });
+               for (var i = 0; i < kids.length; i++) {
+                  delete this.files[kids[i].name];
+               }
+            }
+         }
+
+         return this;
+      },
+
+      /**
+       * Generate the complete zip file
+       * @param {Object} options the options to generate the zip file :
+       * - base64, (deprecated, use type instead) true to generate base64.
+       * - compression, "STORE" by default.
+       * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
+       * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
+       */
+      generate : function(options) {
+         options = extend(options || {}, {
+            base64 : true,
+            compression : "STORE",
+            type : "base64"
+         });
+
+         JSZip.utils.checkSupport(options.type);
+
+         var zipData = [], localDirLength = 0, centralDirLength = 0, writer, i;
+
+
+         // first, generate all the zip parts.
+         for (var name in this.files) {
+            if ( !this.files.hasOwnProperty(name) ) { continue; }
+            var file = this.files[name];
+
+            var compressionName = file.compression || options.compression.toUpperCase();
+            var compression = JSZip.compressions[compressionName];
+            if (!compression) {
+               throw new Error(compressionName + " is not a valid compression method !");
+            }
+
+            var compressedObject = generateCompressedObjectFrom.call(this, file, compression);
+
+            var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength);
+            localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
+            centralDirLength += zipPart.dirRecord.length;
+            zipData.push(zipPart);
+         }
+
+         var dirEnd = "";
+
+         // end of central dir signature
+         dirEnd = JSZip.signature.CENTRAL_DIRECTORY_END +
+         // number of this disk
+         "\x00\x00" +
+         // number of the disk with the start of the central directory
+         "\x00\x00" +
+         // total number of entries in the central directory on this disk
+         decToHex(zipData.length, 2) +
+         // total number of entries in the central directory
+         decToHex(zipData.length, 2) +
+         // size of the central directory   4 bytes
+         decToHex(centralDirLength, 4) +
+         // offset of start of central directory with respect to the starting disk number
+         decToHex(localDirLength, 4) +
+         // .ZIP file comment length
+         "\x00\x00";
+
+
+         // we have all the parts (and the total length)
+         // time to create a writer !
+         switch(options.type.toLowerCase()) {
+            case "uint8array" :
+            case "arraybuffer" :
+            case "blob" :
+            case "nodebuffer" :
+               writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
+               break;
+            case "base64" :
+            default : // case "string" :
+               writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
+               break;
+         }
+
+         for (i = 0; i < zipData.length; i++) {
+            writer.append(zipData[i].fileRecord);
+            writer.append(zipData[i].compressedObject.compressedContent);
+         }
+         for (i = 0; i < zipData.length; i++) {
+            writer.append(zipData[i].dirRecord);
+         }
+
+         writer.append(dirEnd);
+
+         var zip = writer.finalize();
+
+
+
+         switch(options.type.toLowerCase()) {
+            // case "zip is an Uint8Array"
+            case "uint8array" :
+            case "arraybuffer" :
+            case "nodebuffer" :
+               return JSZip.utils.transformTo(options.type.toLowerCase(), zip);
+            case "blob" :
+               return JSZip.utils.arrayBuffer2Blob(JSZip.utils.transformTo("arraybuffer", zip));
+
+            // case "zip is a string"
+            case "base64" :
+               return (options.base64) ? JSZip.base64.encode(zip) : zip;
+            default : // case "string" :
+               return zip;
+         }
+      },
+
+      /**
+       *
+       *  Javascript crc32
+       *  http://www.webtoolkit.info/
+       *
+       */
+      crc32 : function crc32(input, crc) {
+         if (typeof input === "undefined" || !input.length) {
+            return 0;
+         }
+
+         var isArray = JSZip.utils.getTypeOf(input) !== "string";
+
+         var table = [
+            0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
+            0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
+            0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
+            0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+            0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
+            0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+            0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
+            0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
+            0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
+            0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
+            0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
+            0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+            0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
+            0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+            0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
+            0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
+            0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
+            0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+            0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
+            0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
+            0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
+            0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
+            0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
+            0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+            0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
+            0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
+            0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
+            0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+            0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
+            0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+            0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
+            0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
+            0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
+            0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+            0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
+            0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+            0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
+            0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
+            0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
+            0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
+            0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
+            0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+            0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
+            0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+            0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
+            0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
+            0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
+            0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+            0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
+            0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
+            0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
+            0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
+            0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
+            0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+            0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
+            0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+            0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
+            0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
+            0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
+            0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+            0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
+            0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
+            0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
+            0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+         ];
+
+         if (typeof(crc) == "undefined") { crc = 0; }
+         var x = 0;
+         var y = 0;
+         var byte = 0;
+
+         crc = crc ^ (-1);
+         for( var i = 0, iTop = input.length; i < iTop; i++ ) {
+            byte = isArray ? input[i] : input.charCodeAt(i);
+            y = ( crc ^ byte ) & 0xFF;
+            x = table[y];
+            crc = ( crc >>> 8 ) ^ x;
+         }
+
+         return crc ^ (-1);
+      },
+
+      // Inspired by http://my.opera.com/GreyWyvern/blog/show.dml/1725165
+      clone : function() {
+         var newObj = new JSZip();
+         for (var i in this) {
+            if (typeof this[i] !== "function") {
+               newObj[i] = this[i];
+            }
+         }
+         return newObj;
+      },
+
+
+      /**
+       * http://www.webtoolkit.info/javascript-utf8.html
+       */
+      utf8encode : function (string) {
+         // TextEncoder + Uint8Array to binary string is faster than checking every bytes on long strings.
+         // http://jsperf.com/utf8encode-vs-textencoder
+         // On short strings (file names for example), the TextEncoder API is (currently) slower.
+         if (JSZip.support.uint8array && typeof TextEncoder === "function") {
+            var u8 = TextEncoder("utf-8").encode(string);
+            return JSZip.utils.transformTo("string", u8);
+         }
+         if (JSZip.support.nodebuffer) {
+            return JSZip.utils.transformTo("string", new Buffer(string, "utf-8"));
+         }
+
+         // array.join may be slower than string concatenation but generates less objects (less time spent garbage collecting).
+         // See also http://jsperf.com/array-direct-assignment-vs-push/31
+         var result = [], resIndex = 0;
+
+         for (var n = 0; n < string.length; n++) {
+
+            var c = string.charCodeAt(n);
+
+            if (c < 128) {
+               result[resIndex++] = String.fromCharCode(c);
+            } else if ((c > 127) && (c < 2048)) {
+               result[resIndex++] = String.fromCharCode((c >> 6) | 192);
+               result[resIndex++] = String.fromCharCode((c & 63) | 128);
+            } else {
+               result[resIndex++] = String.fromCharCode((c >> 12) | 224);
+               result[resIndex++] = String.fromCharCode(((c >> 6) & 63) | 128);
+               result[resIndex++] = String.fromCharCode((c & 63) | 128);
+            }
+
+         }
+
+         return result.join("");
+      },
+
+      /**
+       * http://www.webtoolkit.info/javascript-utf8.html
+       */
+      utf8decode : function (input) {
+         var result = [], resIndex = 0;
+         var type = JSZip.utils.getTypeOf(input);
+         var isArray = type !== "string";
+         var i = 0;
+         var c = 0, c1 = 0, c2 = 0, c3 = 0;
+
+         // check if we can use the TextDecoder API
+         // see http://encoding.spec.whatwg.org/#api
+         if (JSZip.support.uint8array && typeof TextDecoder === "function") {
+            return TextDecoder("utf-8").decode(
+               JSZip.utils.transformTo("uint8array", input)
+            );
+         }
+         if (JSZip.support.nodebuffer) {
+            return JSZip.utils.transformTo("nodebuffer", input).toString("utf-8");
+         }
+
+         while ( i < input.length ) {
+
+            c = isArray ? input[i] : input.charCodeAt(i);
+
+            if (c < 128) {
+               result[resIndex++] = String.fromCharCode(c);
+               i++;
+            } else if ((c > 191) && (c < 224)) {
+               c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
+               result[resIndex++] = String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+               i += 2;
+            } else {
+               c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
+               c3 = isArray ? input[i+2] : input.charCodeAt(i+2);
+               result[resIndex++] = String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+               i += 3;
+            }
+
+         }
+
+         return result.join("");
+      }
+   };
+}());
+
+/*
+ * Compression methods
+ * This object is filled in as follow :
+ * name : {
+ *    magic // the 2 bytes indentifying the compression method
+ *    compress // function, take the uncompressed content and return it compressed.
+ *    uncompress // function, take the compressed content and return it uncompressed.
+ *    compressInputType // string, the type accepted by the compress method. null to accept everything.
+ *    uncompressInputType // string, the type accepted by the uncompress method. null to accept everything.
+ * }
+ *
+ * STORE is the default compression method, so it's included in this file.
+ * Other methods should go to separated files : the user wants modularity.
+ */
+JSZip.compressions = {
+   "STORE" : {
+      magic : "\x00\x00",
+      compress : function (content) {
+         return content; // no compression
+      },
+      uncompress : function (content) {
+         return content; // no compression
+      },
+      compressInputType : null,
+      uncompressInputType : null
+   }
+};
+
+/*
+ * List features that require a modern browser, and if the current browser support them.
+ */
+JSZip.support = {
+   // contains true if JSZip can read/generate ArrayBuffer, false otherwise.
+   arraybuffer : (function(){
+      return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
+   })(),
+   // contains true if JSZip can read/generate nodejs Buffer, false otherwise.
+   nodebuffer : (function(){
+      return typeof Buffer !== "undefined";
+   })(),
+   // contains true if JSZip can read/generate Uint8Array, false otherwise.
+   uint8array : (function(){
+      return typeof Uint8Array !== "undefined";
+   })(),
+   // contains true if JSZip can read/generate Blob, false otherwise.
+   blob : (function(){
+      // the spec started with BlobBuilder then replaced it with a construtor for Blob.
+      // Result : we have browsers that :
+      // * know the BlobBuilder (but with prefix)
+      // * know the Blob constructor
+      // * know about Blob but not about how to build them
+      // About the "=== 0" test : if given the wrong type, it may be converted to a string.
+      // Instead of an empty content, we will get "[object Uint8Array]" for example.
+      if (typeof ArrayBuffer === "undefined") {
+         return false;
+      }
+      var buffer = new ArrayBuffer(0);
+      try {
+         return new Blob([buffer], { type: "application/zip" }).size === 0;
+      }
+      catch(e) {}
+
+      try {
+         var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
+                            window.MozBlobBuilder || window.MSBlobBuilder)();
+         builder.append(buffer);
+         return builder.getBlob('application/zip').size === 0;
+      }
+      catch(e) {}
+
+      return false;
+   })()
+};
+
+(function () {
+   JSZip.utils = {
+      /**
+       * Convert a string to a "binary string" : a string containing only char codes between 0 and 255.
+       * @param {string} str the string to transform.
+       * @return {String} the binary string.
+       */
+      string2binary : function (str) {
+         var result = "";
+         for (var i = 0; i < str.length; i++) {
+            result += String.fromCharCode(str.charCodeAt(i) & 0xff);
+         }
+         return result;
+      },
+      /**
+       * Create a Uint8Array from the string.
+       * @param {string} str the string to transform.
+       * @return {Uint8Array} the typed array.
+       * @throws {Error} an Error if the browser doesn't support the requested feature.
+       * @deprecated : use JSZip.utils.transformTo instead.
+       */
+      string2Uint8Array : function (str) {
+         return JSZip.utils.transformTo("uint8array", str);
+      },
+
+      /**
+       * Create a string from the Uint8Array.
+       * @param {Uint8Array} array the array to transform.
+       * @return {string} the string.
+       * @throws {Error} an Error if the browser doesn't support the requested feature.
+       * @deprecated : use JSZip.utils.transformTo instead.
+       */
+      uint8Array2String : function (array) {
+         return JSZip.utils.transformTo("string", array);
+      },
+      /**
+       * Create a blob from the given ArrayBuffer.
+       * @param {ArrayBuffer} buffer the buffer to transform.
+       * @return {Blob} the result.
+       * @throws {Error} an Error if the browser doesn't support the requested feature.
+       */
+      arrayBuffer2Blob : function (buffer) {
+         JSZip.utils.checkSupport("blob");
+
+         try {
+            // Blob constructor
+            return new Blob([buffer], { type: "application/zip" });
+         }
+         catch(e) {}
+
+         try {
+            // deprecated, browser only, old way
+            var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
+                               window.MozBlobBuilder || window.MSBlobBuilder)();
+            builder.append(buffer);
+            return builder.getBlob('application/zip');
+         }
+         catch(e) {}
+
+         // well, fuck ?!
+         throw new Error("Bug : can't construct the Blob.");
+      },
+      /**
+       * Create a blob from the given string.
+       * @param {string} str the string to transform.
+       * @return {Blob} the result.
+       * @throws {Error} an Error if the browser doesn't support the requested feature.
+       */
+      string2Blob : function (str) {
+         var buffer = JSZip.utils.transformTo("arraybuffer", str);
+         return JSZip.utils.arrayBuffer2Blob(buffer);
+      }
+   };
+
+   /**
+    * The identity function.
+    * @param {Object} input the input.
+    * @return {Object} the same input.
+    */
+   function identity(input) {
+      return input;
+   };
+
+   /**
+    * Fill in an array with a string.
+    * @param {String} str the string to use.
+    * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated).
+    * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array.
+    */
+   function stringToArrayLike(str, array) {
+      for (var i = 0; i < str.length; ++i) {
+         array[i] = str.charCodeAt(i) & 0xFF;
+      }
+      return array;
+   };
+
+   /**
+    * Transform an array-like object to a string.
+    * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
+    * @return {String} the result.
+    */
+   function arrayLikeToString(array) {
+      // Performances notes :
+      // --------------------
+      // String.fromCharCode.apply(null, array) is the fastest, see
+      // see http://jsperf.com/converting-a-uint8array-to-a-string/2
+      // but the stack is limited (and we can get huge arrays !).
+      //
+      // result += String.fromCharCode(array[i]); generate too many strings !
+      //
+      // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
+      var chunk = 65536;
+      var result = [], len = array.length, type = JSZip.utils.getTypeOf(array), k = 0;
+
+      while (k < len && chunk > 1) {
+         try {
+            if (type === "array" || type === "nodebuffer") {
+               result.push(String.fromCharCode.apply(null, array.slice(k, Math.max(k + chunk, len))));
+            } else {
+               result.push(String.fromCharCode.apply(null, array.subarray(k, k + chunk)));
+            }
+            k += chunk;
+         } catch (e) {
+            chunk = Math.floor(chunk / 2);
+         }
+      }
+      return result.join("");
+   };
+
+   /**
+    * Copy the data from an array-like to an other array-like.
+    * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array.
+    * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated.
+    * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array.
+    */
+   function arrayLikeToArrayLike(arrayFrom, arrayTo) {
+      for(var i = 0; i < arrayFrom.length; i++) {
+         arrayTo[i] = arrayFrom[i];
+      }
+      return arrayTo;
+   };
+
+   // a matrix containing functions to transform everything into everything.
+   var transform = {};
+
+   // string to ?
+   transform["string"] = {
+      "string" : identity,
+      "array" : function (input) {
+         return stringToArrayLike(input, new Array(input.length));
+      },
+      "arraybuffer" : function (input) {
+         return transform["string"]["uint8array"](input).buffer;
+      },
+      "uint8array" : function (input) {
+         return stringToArrayLike(input, new Uint8Array(input.length));
+      },
+      "nodebuffer" : function (input) {
+         return stringToArrayLike(input, new Buffer(input.length));
+      }
+   };
+
+   // array to ?
+   transform["array"] = {
+      "string" : arrayLikeToString,
+      "array" : identity,
+      "arraybuffer" : function (input) {
+         return (new Uint8Array(input)).buffer;
+      },
+      "uint8array" : function (input) {
+         return new Uint8Array(input);
+      },
+      "nodebuffer" : function (input) {
+         return new Buffer(input);
+      }
+   };
+
+   // arraybuffer to ?
+   transform["arraybuffer"] = {
+      "string" : function (input) {
+         return arrayLikeToString(new Uint8Array(input));
+      },
+      "array" : function (input) {
+         return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
+      },
+      "arraybuffer" : identity,
+      "uint8array" : function (input) {
+         return new Uint8Array(input);
+      },
+      "nodebuffer" : function (input) {
+         return new Buffer(new Uint8Array(input));
+      }
+   };
+
+   // uint8array to ?
+   transform["uint8array"] = {
+      "string" : arrayLikeToString,
+      "array" : function (input) {
+         return arrayLikeToArrayLike(input, new Array(input.length));
+      },
+      "arraybuffer" : function (input) {
+         return input.buffer;
+      },
+      "uint8array" : identity,
+      "nodebuffer" : function(input) {
+         return new Buffer(input);
+      }
+   };
+
+   // nodebuffer to ?
+   transform["nodebuffer"] = {
+      "string" : arrayLikeToString,
+      "array" : function (input) {
+         return arrayLikeToArrayLike(input, new Array(input.length));
+      },
+      "arraybuffer" : function (input) {
+         return transform["nodebuffer"]["uint8array"](input).buffer;
+      },
+      "uint8array" : function (input) {
+         return arrayLikeToArrayLike(input, new Uint8Array(input.length));
+      },
+      "nodebuffer" : identity
+   };
+
+   /**
+    * Transform an input into any type.
+    * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer.
+    * If no output type is specified, the unmodified input will be returned.
+    * @param {String} outputType the output type.
+    * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert.
+    * @throws {Error} an Error if the browser doesn't support the requested output type.
+    */
+   JSZip.utils.transformTo = function (outputType, input) {
+      if (!input) {
+         // undefined, null, etc
+         // an empty string won't harm.
+         input = "";
+      }
+      if (!outputType) {
+         return input;
+      }
+      JSZip.utils.checkSupport(outputType);
+      var inputType = JSZip.utils.getTypeOf(input);
+      var result = transform[inputType][outputType](input);
+      return result;
+   };
+
+   /**
+    * Return the type of the input.
+    * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer.
+    * @param {Object} input the input to identify.
+    * @return {String} the (lowercase) type of the input.
+    */
+   JSZip.utils.getTypeOf = function (input) {
+      if (typeof input === "string") {
+         return "string";
+      }
+      if (input instanceof Array) {
+         return "array";
+      }
+      if (JSZip.support.nodebuffer && Buffer.isBuffer(input)) {
+         return "nodebuffer";
+      }
+      if (JSZip.support.uint8array && input instanceof Uint8Array) {
+         return "uint8array";
+      }
+      if (JSZip.support.arraybuffer && input instanceof ArrayBuffer) {
+         return "arraybuffer";
+      }
+   };
+
+   /**
+    * Throw an exception if the type is not supported.
+    * @param {String} type the type to check.
+    * @throws {Error} an Error if the browser doesn't support the requested type.
+    */
+   JSZip.utils.checkSupport = function (type) {
+      var supported = true;
+      switch (type.toLowerCase()) {
+         case "uint8array":
+            supported = JSZip.support.uint8array;
+         break;
+         case "arraybuffer":
+            supported = JSZip.support.arraybuffer;
+         break;
+         case "nodebuffer":
+            supported = JSZip.support.nodebuffer;
+         break;
+         case "blob":
+            supported = JSZip.support.blob;
+         break;
+      }
+      if (!supported) {
+         throw new Error(type + " is not supported by this browser");
+      }
+   };
+
+
+})();
+
+(function (){
+   /**
+    * Represents an entry in the zip.
+    * The content may or may not be compressed.
+    * @constructor
+    */
+   JSZip.CompressedObject = function () {
+         this.compressedSize = 0;
+         this.uncompressedSize = 0;
+         this.crc32 = 0;
+         this.compressionMethod = null;
+         this.compressedContent = null;
+   };
+
+   JSZip.CompressedObject.prototype = {
+      /**
+       * Return the decompressed content in an unspecified format.
+       * The format will depend on the decompressor.
+       * @return {Object} the decompressed content.
+       */
+      getContent : function () {
+         return null; // see implementation
+      },
+      /**
+       * Return the compressed content in an unspecified format.
+       * The format will depend on the compressed conten source.
+       * @return {Object} the compressed content.
+       */
+      getCompressedContent : function () {
+         return null; // see implementation
+      }
+   };
+})();
+
+/**
+ *
+ *  Base64 encode / decode
+ *  http://www.webtoolkit.info/
+ *
+ *  Hacked so that it doesn't utf8 en/decode everything
+ **/
+JSZip.base64 = (function() {
+   // private property
+   var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+
+   return {
+      // public method for encoding
+      encode : function(input, utf8) {
+         var output = "";
+         var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+         var i = 0;
+
+         while (i < input.length) {
+
+            chr1 = input.charCodeAt(i++);
+            chr2 = input.charCodeAt(i++);
+            chr3 = input.charCodeAt(i++);
+
+            enc1 = chr1 >> 2;
+            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+            enc4 = chr3 & 63;
+
+            if (isNaN(chr2)) {
+               enc3 = enc4 = 64;
+            } else if (isNaN(chr3)) {
+               enc4 = 64;
+            }
+
+            output = output +
+               _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
+               _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
+
+         }
+
+         return output;
+      },
+
+      // public method for decoding
+      decode : function(input, utf8) {
+         var output = "";
+         var chr1, chr2, chr3;
+         var enc1, enc2, enc3, enc4;
+         var i = 0;
+
+         input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+         while (i < input.length) {
+
+            enc1 = _keyStr.indexOf(input.charAt(i++));
+            enc2 = _keyStr.indexOf(input.charAt(i++));
+            enc3 = _keyStr.indexOf(input.charAt(i++));
+            enc4 = _keyStr.indexOf(input.charAt(i++));
+
+            chr1 = (enc1 << 2) | (enc2 >> 4);
+            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+            chr3 = ((enc3 & 3) << 6) | enc4;
+
+            output = output + String.fromCharCode(chr1);
+
+            if (enc3 != 64) {
+               output = output + String.fromCharCode(chr2);
+            }
+            if (enc4 != 64) {
+               output = output + String.fromCharCode(chr3);
+            }
+
+         }
+
+         return output;
+
+      }
+   };
+}());
+
+// enforcing Stuk's coding style
+// vim: set shiftwidth=3 softtabstop=3:
\ No newline at end of file
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/less.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/less.js
new file mode 100644
index 0000000..f4aa7cb
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/less.js
@@ -0,0 +1,9 @@
+//
+// LESS - Leaner CSS v1.3.3
+// http://lesscss.org
+//
+// Copyright (c) 2009-2013, Alexis Sellier
+// Licensed under the Apache 2.0 License.
+//
+(function(e,t){function n(t){return e.less[t.split("/")[1]]}function f(){r.env==="development"?(r.optimization=0,r.watchTimer=setInterval(function(){r.watchMode&&g(function(e,t,n,r,i){t&&S(t.toCSS(),r,i.lastModified)})},r.poll)):r.optimization=3}function m(){var e=document.getElementsByTagName("style");for(var t=0;t<e.length;t++)e[t].type.match(p)&&(new r.Parser({filename:document.location.href.replace(/#.*$/,""),dumpLineNumbers:r.dumpLineNumbers})).parse(e[t].innerHTML||"",function(n,r){var i=r.toCSS(),s=e[t];s.type="text/css",s.styleSheet?s.styleSheet.cssText=i:s.innerHTML=i})}function g(e,t){for(var n=0;n<r.sheets.length;n++)w(r.sheets[n],e,t,r.sheets.length-(n+1))}function y(e,t){var n=b(e),r=b(t),i,s,o,u,a="";if(n.hostPart!==r.hostPart)return"";s=Math.max(r.directories.length,n.directories.length);for(i=0;i<s;i++)if(r.directories[i]!==n.directories[i])break;u=r.directories.slice(i),o=n.directories.slice(i);for(i=0;i<u.length-1;i++)a+="../";for(i=0;i<o.length-1;i++)a+=o[i]+"/";return a}function b(e,t){var n=/^((?:[a-z-]+:)?\/\/(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/,r=e.match(n),i={},s=[],o,u;if(!r)throw new Error("Could not parse sheet href - '"+e+"'");if(!r[1]||r[2]){u=t.match(n);if(!u)throw new Error("Could not parse page url - '"+t+"'");r[1]=u[1],r[2]||(r[3]=u[3]+r[3])}if(r[3]){s=r[3].replace("\\","/").split("/");for(o=0;o<s.length;o++)s[o]===".."&&o>0&&(s.splice(o-1,2),o-=2)}return i.hostPart=r[1],i.directories=s,i.path=r[1]+s.join("/"),i.fileUrl=i.path+(r[4]||""),i.url=i.fileUrl+(r[5]||""),i}function w(t,n,i,s){var o=t.contents||{},u=t.files||{},a=b(t.href,e.location.href),f=a.url,c=l&&l.getItem(f),h=l&&l.getItem(f+":timestamp"),p={css:c,timestamp:h},d;r.relativeUrls?r.rootpath?t.entryPath?d=b(r.rootpath+y(a.path,t.entryPath)).path:d=r.rootpath:d=a.path:r.rootpath?d=r.rootpath:t.entryPath?d=t.entryPath:d=a.path,x(f,t.type,function(e,l){v+=e.replace(/@import .+?;/ig,"");if(!i&&p&&l&&(new Date(l)).valueOf()===(new Date(p.timestamp)).valueOf())S(p.css,t),n(null,null,e,t,{local:!0,remaining:s},f);else try{o[f]=e,(new r.Parser({optimization:r.optimization,paths:[a.path],entryPath:t.entryPath||a.path,mime:t.type,filename:f,rootpath:d,relativeUrls:t.relativeUrls,contents:o,files:u,dumpLineNumbers:r.dumpLineNumbers})).parse(e,function(r,i){if(r)return k(r,f);try{n(r,i,e,t,{local:!1,lastModified:l,remaining:s},f),N(document.getElementById("less-error-message:"+E(f)))}catch(r){k(r,f)}})}catch(c){k(c,f)}},function(e,t){throw new Error("Couldn't load "+t+" ("+e+")")})}function E(e){return e.replace(/^[a-z]+:\/\/?[^\/]+/,"").replace(/^\//,"").replace(/\.[a-zA-Z]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function S(e,t,n){var r,i=t.href||"",s="less:"+(t.title||E(i));if((r=document.getElementById(s))===null){r=document.createElement("style"),r.type="text/css",t.media&&(r.media=t.media),r.id=s;var o=t&&t.nextSibling||null;(o||document.getElementsByTagName("head")[0]).parentNode.insertBefore(r,o)}if(r.styleSheet)try{r.styleSheet.cssText=e}catch(u){throw new Error("Couldn't reassign styleSheet.cssText.")}else(function(e){r.childNodes.length>0?r.firstChild.nodeValue!==e.nodeValue&&r.replaceChild(e,r.firstChild):r.appendChild(e)})(document.createTextNode(e));if(n&&l){C("saving "+i+" to cache.");try{l.setItem(i,e),l.setItem(i+":timestamp",n)}catch(u){C("failed to save")}}}function x(e,t,n,i){function a(t,n,r){t.status>=200&&t.status<300?n(t.responseText,t.getResponseHeader("Last-Modified")):typeof r=="function"&&r(t.status,e)}var s=T(),u=o?r.fileAsync:r.async;typeof s.overrideMimeType=="function"&&s.overrideMimeType("text/css"),s.open("GET",e,u),s.setRequestHeader("Accept",t||"text/x-less, text/css; q=0.9, */*; q=0.5"),s.send(null),o&&!r.fileAsync?s.status===0||s.status>=200&&s.status<300?n(s.responseText):i(s.status,e):u?s.onreadystatechange=function(){s.readyState==4&&a(s,n,i)}:a(s,n,i)}function T(){if(e.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(t){return C("browser doesn't support AJAX."),null}}function N(e){return e&&e.parentNode.removeChild(e)}function C(e){r.env=="development"&&typeof console!="undefined"&&console.log("less: "+e)}function k(e,t){var n="less-error-message:"+E(t),i='<li><label>{line}</label><pre class="{class}">{content}</pre></li>',s=document.createElement("div"),o,u,a=[],f=e.filename||t,l=f.match(/([^\/]+(\?.*)?)$/)[1];s.id=n,s.className="less-error-message",u="<h3>"+(e.message||"There is an error in your .less file")+"</h3>"+'<p>in <a href="'+f+'">'+l+"</a> ";var c=function(e,t,n){e.extract[t]&&a.push(i.replace(/\{line\}/,parseInt(e.line)+(t-1)).replace(/\{class\}/,n).replace(/\{content\}/,e.extract[t]))};e.stack?u+="<br/>"+e.stack.split("\n").slice(1).join("<br/>"):e.extract&&(c(e,0,""),c(e,1,"line"),c(e,2,""),u+="on line "+e.line+", column "+(e.column+1)+":</p>"+"<ul>"+a.join("")+"</ul>"),s.innerHTML=u,S([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),s.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),r.env=="development"&&(o=setInterval(function(){document.body&&(document.getElementById(n)?document.body.replaceChild(s,document.getElementById(n)):document.body.insertBefore(s,document.body.firstChild),clearInterval(o))},10))}Array.isArray||(Array.isArray=function(e){return Object.prototype.toString.call(e)==="[object Array]"||e instanceof Array}),Array.prototype.forEach||(Array.prototype.forEach=function(e,t){var n=this.length>>>0;for(var r=0;r<n;r++)r in this&&e.call(t,this[r],r,this)}),Array.prototype.map||(Array.prototype.map=function(e){var t=this.length>>>0,n=new Array(t),r=arguments[1];for(var i=0;i<t;i++)i in this&&(n[i]=e.call(r,this[i],i,this));return n}),Array.prototype.filter||(Array.prototype.filter=function(e){var t=[],n=arguments[1];for(var r=0;r<this.length;r++)e.call(n,this[r])&&t.push(this[r]);return t}),Array.prototype.reduce||(Array.prototype.reduce=function(e){var t=this.length>>>0,n=0;if(t===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n<t;n++)n in this&&(r=e.call(null,r,this[n],n,this));return r}),Array.prototype.indexOf||(Array.prototype.indexOf=function(e){var t=this.length,n=arguments[1]||0;if(!t)return-1;if(n>=t)return-1;n<0&&(n+=t);for(;n<t;n++){if(!Object.prototype.hasOwnProperty.call(this,n))continue;if(e===this[n])return n}return-1}),Object.keys||(Object.keys=function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return t}),String.prototype.trim||(String.prototype.trim=function(){return String(this).replace(/^\s\s*/,"").replace(/\s\s*$/,"")});var r,i,s;typeof environment=="object"&&{}.toString.call(environment)==="[object Environment]"?(typeof e=="undefined"?r={}:r=e.less={},i=r.tree={},r.mode="rhino"):typeof e=="undefined"?(r=exports,i=n("./tree"),r.mode="node"):(typeof e.less=="undefined"&&(e.less={}),r=e.less,i=e.less.tree={},r.mode="browser"),r.Parser=function(t){function g(){a=c[u],f=o,h=o}function y(){c[u]=a,o=f,h=o}function b(){o>h&&(c[u]=c[u].slice(o-h),h=o)}function w(e){var t=e.charCodeAt(0);return t===32||t===10||t===9}function E(e){var t,n,r,i,a;if(e instanceof Function)return e.call(p.parsers);if(typeof e=="string")t=s.charAt(o)===e?e:null,r=1,b();else{b();if(!(t=e.exec(c[u])))return null;r=t[0].length}if(t)return S(r),typeof t=="string"?t:t.length===1?t[0]:t}function S(e){var t=o,n=u,r=o+c[u].length,i=o+=e;while(o<r){if(!w(s.charAt(o)))break;o++}return c[u]=c[u].slice(e+(o-i)),h=o,c[u].length===0&&u<c.length-1&&u++,t!==o||n!==u}function x(e,t){var n=E(e);if(!!n)return n;T(t||(typeof e=="string"?"expected '"+e+"' got '"+s.charAt(o)+"'":"unexpected token"))}function T(e,t){var n=new Error(e);throw n.index=o,n.type=t||"Syntax",n}function N(e){return typeof e=="string"?s.charAt(o)===e:e.test(c[u])?!0:!1}function C(e,t){return e.filename&&t.filename&&e.filename!==t.filename?p.imports.contents[e.filename]:s}function k(e,t){for(var n=e,r=-1;n>=0&&t.charAt(n)!=="\n";n--)r++;return{line:typeof e=="number"?(t.slice(0,e).match(/\n/g)||"").length:null,column:r}}function L(e){return r.mode==="browser"||r.mode==="rhino"?e.filename:n("path").resolve(e.filename)}function A(e,t,n){return{lineNumber:k(e,t).line+1,fileName:L(n)}}function O(e,t){var n=C(e,t),r=k(e.index,n),i=r.line,s=r.column,o=n.split("\n");this.type=e.type||"Syntax",this.message=e.message,this.filename=e.filename||t.filename,this.index=e.index,this.line=typeof i=="number"?i+1:null,this.callLine=e.call&&k(e.call,n).line+1,this.callExtract=o[k(e.call,n).line],this.stack=e.stack,this.column=s,this.extract=[o[i-1],o[i],o[i+1]]}var s,o,u,a,f,l,c,h,p,d=this,t=t||{};t.contents||(t.contents={}),t.rootpath=t.rootpath||"",t.files||(t.files={});var v=function(){},m=this.imports={paths:t.paths||[],queue:[],files:t.files,contents:t.contents,mime:t.mime,error:null,push:function(e,n){var i=this;this.queue.push(e),r.Parser.importer(e,this.paths,function(t,r,s){i.queue.splice(i.queue.indexOf(e),1);var o=s in i.files;i.files[s]=r,t&&!i.error&&(i.error=t),n(t,r,o),i.queue.length===0&&v(i.error)},t)}};return this.env=t=t||{},this.optimization="optimization"in this.env?this.env.optimization:1,this.env.filename=this.env.filename||null,p={imports:m,parse:function(e,a){var f,d,m,g,y,b,w=[],S,x=null;o=u=h=l=0,s=e.replace(/\r\n/g,"\n"),s=s.replace(/^\uFEFF/,""),c=function(e){var n=0,r=/(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,i=/\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,o=/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,u=0,a,f=e[0],l;for(var c=0,h,p;c<s.length;){r.lastIndex=c,(a=r.exec(s))&&a.index===c&&(c+=a[0].length,f.push(a[0])),h=s.charAt(c),i.lastIndex=o.lastIndex=c;if(a=o.exec(s))if(a.index===c){c+=a[0].length,f.push(a[0]);continue}if(!l&&h==="/"){p=s.charAt(c+1);if(p==="/"||p==="*")if(a=i.exec(s))if(a.index===c){c+=a[0].length,f.push(a[0]);continue}}switch(h){case"{":if(!l){u++,f.push(h);break};case"}":if(!l){u--,f.push(h),e[++n]=f=[];break};case"(":if(!l){l=!0,f.push(h);break};case")":if(l){l=!1,f.push(h);break};default:f.push(h)}c++}return u!=0&&(x=new O({index:c-1,type:"Parse",message:u>0?"missing closing `}`":"missing opening `{`",filename:t.filename},t)),e.map(function(e){return e.join("")})}([[]]);if(x)return a(x,t);try{f=new i.Ruleset([],E(this.parsers.primary)),f.root=!0}catch(T){return a(new O(T,t))}f.toCSS=function(e){var s,o,u;return function(s,o){var u=[],a;s=s||{},typeof o=="object"&&!Array.isArray(o)&&(o=Object.keys(o).map(function(e){var t=o[e];return t instanceof i.Value||(t instanceof i.Expression||(t=new i.Expression([t])),t=new i.Value([t])),new i.Rule("@"+e,t,!1,0)}),u=[new i.Ruleset(null,o)]);try{var f=e.call(this,{frames:u}).toCSS([],{compress:s.compress||!1,dumpLineNumbers:t.dumpLineNumbers})}catch(l){throw new O(l,t)}if(a=p.imports.error)throw a instanceof O?a:new O(a,t);return s.yuicompress&&r.mode==="node"?n("ycssmin").cssmin(f):s.compress?f.replace(/(\s)+/g,"$1"):f}}(f.eval);if(o<s.length-1){o=l,b=s.split("\n"),y=(s.slice(0,o).match(/\n/g)||"").length+1;for(var N=o,C=-1;N>=0&&s.charAt(N)!=="\n";N--)C++;x={type:"Parse",message:"Syntax Error on line "+y,index:o,filename:t.filename,line:y,column:C,extract:[b[y-2],b[y-1],b[y]]}}this.imports.queue.length>0?v=function(e){e=x||e,e?a(e):a(null,f)}:a(x,f)},parsers:{primary:function(){var e,t=[];while((e=E(this.mixin.definition)||E(this.rule)||E(this.ruleset)||E(this.mixin.call)||E(this.comment)||E(this.directive))||E(/^[\s\n]+/)||E(/^;+/))e&&t.push(e);return t},comment:function(){var e;if(s.charAt(o)!=="/")return;if(s.charAt(o+1)==="/")return new i.Comment(E(/^\/\/.*/),!0);if(e=E(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new i.Comment(e)},entities:{quoted:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=='"'&&s.charAt(t)!=="'")return;n&&E("~");if(e=E(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/))return new i.Quoted(e[0],e[1]||e[2],n)},keyword:function(){var e;if(e=E(/^[_A-Za-z-][_A-Za-z0-9-]*/))return i.colors.hasOwnProperty(e)?new i.Color(i.colors[e].slice(1)):new i.Keyword(e)},call:function(){var e,n,r,s,a=o;if(!(e=/^([\w-]+|%|progid:[\w\.]+)\(/.exec(c[u])))return;e=e[1],n=e.toLowerCase();if(n==="url")return null;o+=e.length;if(n==="alpha"){s=E(this.alpha);if(typeof s!="undefined")return s}E("("),r=E(this.entities.arguments);if(!E(")"))return;if(e)return new i.Call(e,r,a,t.filename)},arguments:function(){var e=[],t;while(t=E(this.entities.assignment)||E(this.expression)){e.push(t);if(!E(","))break}return e},literal:function(){return E(this.entities.ratio)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.quoted)||E(this.entities.unicodeDescriptor)},assignment:function(){var e,t;if((e=E(/^\w+(?=\s?=)/i))&&E("=")&&(t=E(this.entity)))return new i.Assignment(e,t)},url:function(){var e;if(s.charAt(o)!=="u"||!E(/^url\(/))return;return e=E(this.entities.quoted)||E(this.entities.variable)||E(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/)||"",x(")"),new i.URL(e.value!=null||e instanceof i.Variable?e:new i.Anonymous(e),t.rootpath)},variable:function(){var e,n=o;if(s.charAt(o)==="@"&&(e=E(/^@@?[\w-]+/)))return new i.Variable(e,n,t.filename)},variableCurly:function(){var e,n,r=o;if(s.charAt(o)==="@"&&(n=E(/^@\{([\w-]+)\}/)))return new i.Variable("@"+n[1],r,t.filename)},color:function(){var e;if(s.charAt(o)==="#"&&(e=E(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/)))return new i.Color(e[1])},dimension:function(){var e,t=s.charCodeAt(o);if(t>57||t<43||t===47||t==44)return;if(e=E(/^([+-]?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn|dpi|dpcm|dppx|rem|vw|vh|vmin|vm|ch)?/))return new i.Dimension(e[1],e[2])},ratio:function(){var e,t=s.charCodeAt(o);if(t>57||t<48)return;if(e=E(/^(\d+\/\d+)/))return new i.Ratio(e[1])},unicodeDescriptor:function(){var e;if(e=E(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/))return new i.UnicodeDescriptor(e[0])},javascript:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=="`")return;n&&E("~");if(e=E(/^`([^`]*)`/))return new i.JavaScript(e[1],o,n)}},variable:function(){var e;if(s.charAt(o)==="@"&&(e=E(/^(@[\w-]+)\s*:/)))return e[1]},shorthand:function(){var e,t;if(!N(/^[@\w.%-]+\/[@\w.-]+/))return;g();if((e=E(this.entity))&&E("/")&&(t=E(this.entity)))return new i.Shorthand(e,t);y()},mixin:{call:function(){var e=[],n,r,u=[],a=[],f,l,c,h,p,d,v,m=o,b=s.charAt(o),w,S,C=!1;if(b!=="."&&b!=="#")return;g();while(n=E(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/))e.push(new i.Element(r,n,o)),r=E(">");if(E("(")){p=[];while(c=E(this.expression)){h=null,S=c;if(c.value.length==1){var k=c.value[0];k instanceof i.Variable&&E(":")&&(p.length>0&&(d&&T("Cannot mix ; and , as delimiter types"),v=!0),S=x(this.expression),h=w=k.name)}p.push(S),a.push({name:h,value:S});if(E(","))continue;if(E(";")||d)v&&T("Cannot mix ; and , as delimiter types"),d=!0,p.length>1&&(S=new i.Value(p)),u.push({name:w,value:S}),w=null,p=[],v=!1}x(")")}f=d?u:a,E(this.important)&&(C=!0);if(e.length>0&&(E(";")||N("}")))return new i.mixin.Call(e,f,m,t.filename,C);y()},definition:function(){var e,t=[],n,r,u,a,f,c=!1;if(s.charAt(o)!=="."&&s.charAt(o)!=="#"||N(/^[^{]*\}/))return;g();if(n=E(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){e=n[1];do{E(this.comment);if(s.charAt(o)==="."&&E(/^\.{3}/)){c=!0,t.push({variadic:!0});break}if(!(u=E(this.entities.variable)||E(this.entities.literal)||E(this.entities.keyword)))break;if(u instanceof i.Variable)if(E(":"))a=x(this.expression,"expected expression"),t.push({name:u.name,value:a});else{if(E(/^\.{3}/)){t.push({name:u.name,variadic:!0}),c=!0;break}t.push({name:u.name})}else t.push({value:u})}while(E(",")||E(";"));E(")")||(l=o,y()),E(this.comment),E(/^when/)&&(f=x(this.conditions,"expected condition")),r=E(this.block);if(r)return new i.mixin.Definition(e,t,r,f,c);y()}}},entity:function(){return E(this.entities.literal)||E(this.entities.variable)||E(this.entities.url)||E(this.entities.call)||E(this.entities.keyword)||E(this.entities.javascript)||E(this.comment)},end:function(){return E(";")||N("}")},alpha:function(){var e;if(!E(/^\(opacity=/i))return;if(e=E(/^\d+/)||E(this.entities.variable))return x(")"),new i.Alpha(e)},element:function(){var e,t,n,r;n=E(this.combinator),e=E(/^(?:\d+\.\d+|\d+)%/)||E(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||E("*")||E("&")||E(this.attribute)||E(/^\([^()@]+\)/)||E(/^[\.#](?=@)/)||E(this.entities.variableCurly),e||E("(")&&(r=E(this.entities.variableCurly)||E(this.entities.variable)||E(this.selector))&&E(")")&&(e=new i.Paren(r));if(e)return new i.Element(n,e,o)},combinator:function(){var e,t=s.charAt(o);if(t===">"||t==="+"||t==="~"||t==="|"){o++;while(s.charAt(o).match(/\s/))o++;return new i.Combinator(t)}return s.charAt(o-1).match(/\s/)?new i.Combinator(" "):new i.Combinator(null)},selector:function(){var e,t,n=[],r,u;if(E("("))return e=E(this.entity),E(")")?new i.Selector([new i.Element("",e,o)]):null;while(t=E(this.element)){r=s.charAt(o),n.push(t);if(r==="{"||r==="}"||r===";"||r===","||r===")")break}if(n.length>0)return new i.Selector(n)},attribute:function(){var e="",t,n,r;if(!E("["))return;if(t=E(/^(?:[_A-Za-z0-9-]|\\.)+/)||E(this.entities.quoted))(r=E(/^[|~*$^]?=/))&&(n=E(this.entities.quoted)||E(/^[\w-]+/))?e=[t,r,n.toCSS?n.toCSS():n].join(""):e=t;if(!E("]"))return;if(e)return"["+e+"]"},block:function(){var e;if(E("{")&&(e=E(this.primary))&&E("}"))return e},ruleset:function(){var e=[],n,r,u,a;g(),t.dumpLineNumbers&&(a=A(o,s,t));while(n=E(this.selector)){e.push(n),E(this.comment);if(!E(","))break;E(this.comment)}if(e.length>0&&(r=E(this.block))){var f=new i.Ruleset(e,r,t.strictImports);return t.dumpLineNumbers&&(f.debugInfo=a),f}l=o,y()},rule:function(){var e,t,n=s.charAt(o),r,a;g();if(n==="."||n==="#"||n==="&")return;if(e=E(this.variable)||E(this.property)){e.charAt(0)!="@"&&(a=/^([^@+\/'"*`(;{}-]*);/.exec(c[u]))?(o+=a[0].length-1,t=new i.Anonymous(a[1])):e==="font"?t=E(this.font):t=E(this.value),r=E(this.important);if(t&&E(this.end))return new i.Rule(e,t,r,f);l=o,y()}},"import":function(){var e,n,r=o;g();var s=E(/^@import(?:-(once))?\s+/);if(s&&(e=E(this.entities.quoted)||E(this.entities.url))){n=E(this.mediaFeatures);if(E(";"))return new i.Import(e,m,n,s[1]==="once",r,t.rootpath)}y()},mediaFeature:function(){var e,t,n=[];do if(e=E(this.entities.keyword))n.push(e);else if(E("(")){t=E(this.property),e=E(this.entity);if(!E(")"))return null;if(t&&e)n.push(new i.Paren(new i.Rule(t,e,null,o,!0)));else{if(!e)return null;n.push(new i.Paren(e))}}while(e);if(n.length>0)return new i.Expression(n)},mediaFeatures:function(){var e,t=[];do if(e=E(this.mediaFeature)){t.push(e);if(!E(","))break}else if(e=E(this.entities.variable)){t.push(e);if(!E(","))break}while(e);return t.length>0?t:null},media:function(){var e,n,r,u;t.dumpLineNumbers&&(u=A(o,s,t));if(E(/^@media/)){e=E(this.mediaFeatures);if(n=E(this.block))return r=new i.Media(n,e),t.dumpLineNumbers&&(r.debugInfo=u),r}},directive:function(){var e,n,r,u,a,f,l,c,h,p;if(s.charAt(o)!=="@")return;if(n=E(this["import"])||E(this.media))return n;g(),e=E(/^@[a-z-]+/);if(!e)return;l=e,e.charAt(1)=="-"&&e.indexOf("-",2)>0&&(l="@"+e.slice(e.indexOf("-",2)+1));switch(l){case"@font-face":c=!0;break;case"@viewport":case"@top-left":case"@top-left-corner":case"@top-center":case"@top-right":case"@top-right-corner":case"@bottom-left":case"@bottom-left-corner":case"@bottom-center":case"@bottom-right":case"@bottom-right-corner":case"@left-top":case"@left-middle":case"@left-bottom":case"@right-top":case"@right-middle":case"@right-bottom":c=!0;break;case"@page":case"@document":case"@supports":case"@keyframes":c=!0,h=!0;break;case"@namespace":p=!0}h&&(e+=" "+(E(/^[^{]+/)||"").trim());if(c){if(r=E(this.block))return new i.Directive(e,r)}else if((n=p?E(this.expression):E(this.entity))&&E(";")){var d=new i.Directive(e,n);return t.dumpLineNumbers&&(d.debugInfo=A(o,s,t)),d}y()},font:function(){var e=[],t=[],n,r,s,o;while(o=E(this.shorthand)||E(this.entity))t.push(o);e.push(new i.Expression(t));if(E(","))while(o=E(this.expression)){e.push(o);if(!E(","))break}return new i.Value(e)},value:function(){var e,t=[],n;while(e=E(this.expression)){t.push(e);if(!E(","))break}if(t.length>0)return new i.Value(t)},important:function(){if(s.charAt(o)==="!")return E(/^! *important/)},sub:function(){var e;if(E("(")&&(e=E(this.expression))&&E(")"))return e},multiplication:function(){var e,t,n,r;if(e=E(this.operand)){while(!N(/^\/[*\/]/)&&(n=E("/")||E("*"))&&(t=E(this.operand)))r=new i.Operation(n,[r||e,t]);return r||e}},addition:function(){var e,t,n,r;if(e=E(this.multiplication)){while((n=E(/^[-+]\s+/)||!w(s.charAt(o-1))&&(E("+")||E("-")))&&(t=E(this.multiplication)))r=new i.Operation(n,[r||e,t]);return r||e}},conditions:function(){var e,t,n=o,r;if(e=E(this.condition)){while(E(",")&&(t=E(this.condition)))r=new i.Condition("or",r||e,t,n);return r||e}},condition:function(){var e,t,n,r,s=o,u=!1;E(/^not/)&&(u=!0),x("(");if(e=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))return(r=E(/^(?:>=|=<|[<=>])/))?(t=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))?n=new i.Condition(r,e,t,s,u):T("expected expression"):n=new i.Condition("=",e,new i.Keyword("true"),s,u),x(")"),E(/^and/)?new i.Condition("and",n,E(this.condition)):n},operand:function(){var e,t=s.charAt(o+1);s.charAt(o)==="-"&&(t==="@"||t==="(")&&(e=E("-"));var n=E(this.sub)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.variable)||E(this.entities.call);return e?new i.Operation("*",[new i.Dimension(-1),n]):n},expression:function(){var e,t,n=[],r;while(e=E(this.addition)||E(this.entity))n.push(e);if(n.length>0)return new i.Expression(n)},property:function(){var e;if(e=E(/^(\*?-?[_a-z0-9-]+)\s*:/))return e[1]}}}};if(r.mode==="browser"||r.mode==="rhino")r.Parser.importer=function(e,t,n,r){!/^([a-z-]+:)?\//.test(e)&&t.length>0&&(e=t[0]+e),w({href:e,title:e,type:r.mime,contents:r.contents,files:r.files,rootpath:r.rootpath,entryPath:r.entryPath,relativeUrls:r.relativeUrls},function(e,i,s,o,u,a){e&&typeof r.errback=="function"?r.errback.call(null,a,t,n,r):n.call(null,e,i,a)},!0)};(function(e){function t(t){return e.functions.hsla(t.h,t.s,t.l,t.a)}function n(t,n){return t instanceof e.Dimension&&t.unit=="%"?parseFloat(t.value*n/100):r(t)}function r(t){if(t instanceof e.Dimension)return parseFloat(t.unit=="%"?t.value/100:t.value);if(typeof t=="number")return t;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function i(e){return Math.min(1,Math.max(0,e))}e.functions={rgb:function(e,t,n){return this.rgba(e,t,n,1)},rgba:function(t,i,s,o){var u=[t,i,s].map(function(e){return n(e,256)});return o=r(o),new e.Color(u,o)},hsl:function(e,t,n){return this.hsla(e,t,n,1)},hsla:function(e,t,n,i){function u(e){return e=e<0?e+1:e>1?e-1:e,e*6<1?o+(s-o)*e*6:e*2<1?s:e*3<2?o+(s-o)*(2/3-e)*6:o}e=r(e)%360/360,t=r(t),n=r(n),i=r(i);var s=n<=.5?n*(t+1):n+t-n*t,o=n*2-s;return this.rgba(u(e+1/3)*255,u(e)*255,u(e-1/3)*255,i)},hsv:function(e,t,n){return this.hsva(e,t,n,1)},hsva:function(e,t,n,i){e=r(e)%360/360*360,t=r(t),n=r(n),i=r(i);var s,o;s=Math.floor(e/60%6),o=e/60-s;var u=[n,n*(1-t),n*(1-o*t),n*(1-(1-o)*t)],a=[[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]];return this.rgba(u[a[s][0]]*255,u[a[s][1]]*255,u[a[s][2]]*255,i)},hue:function(t){return new e.Dimension(Math.round(t.toHSL().h))},saturation:function(t){return new e.Dimension(Math.round(t.toHSL().s*100),"%")},lightness:function(t){return new e.Dimension(Math.round(t.toHSL().l*100),"%")},red:function(t){return new e.Dimension(t.rgb[0])},green:function(t){return new e.Dimension(t.rgb[1])},blue:function(t){return new e.Dimension(t.rgb[2])},alpha:function(t){return new e.Dimension(t.toHSL().a)},luma:function(t){return new e.Dimension(Math.round((.2126*(t.rgb[0]/255)+.7152*(t.rgb[1]/255)+.0722*(t.rgb[2]/255))*t.alpha*100),"%")},saturate:function(e,n){var r=e.toHSL();return r.s+=n.value/100,r.s=i(r.s),t(r)},desaturate:function(e,n){var r=e.toHSL();return r.s-=n.value/100,r.s=i(r.s),t(r)},lighten:function(e,n){var r=e.toHSL();return r.l+=n.value/100,r.l=i(r.l),t(r)},darken:function(e,n){var r=e.toHSL();return r.l-=n.value/100,r.l=i(r.l),t(r)},fadein:function(e,n){var r=e.toHSL();return r.a+=n.value/100,r.a=i(r.a),t(r)},fadeout:function(e,n){var r=e.toHSL();return r.a-=n.value/100,r.a=i(r.a),t(r)},fade:function(e,n){var r=e.toHSL();return r.a=n.value/100,r.a=i(r.a),t(r)},spin:function(e,n){var r=e.toHSL(),i=(r.h+n.value)%360;return r.h=i<0?360+i:i,t(r)},mix:function(t,n,r){r||(r=new e.Dimension(50));var i=r.value/100,s=i*2-1,o=t.toHSL().a-n.toHSL().a,u=((s*o==-1?s:(s+o)/(1+s*o))+1)/2,a=1-u,f=[t.rgb[0]*u+n.rgb[0]*a,t.rgb[1]*u+n.rgb[1]*a,t.rgb[2]*u+n.rgb[2]*a],l=t.alpha*i+n.alpha*(1-i);return new e.Color(f,l)},greyscale:function(t){return this.desaturate(t,new e.Dimension(100))},contrast:function(e,t,n,r){return e.rgb?(typeof n=="undefined"&&(n=this.rgba(255,255,255,1)),typeof t=="undefined"&&(t=this.rgba(0,0,0,1)),typeof r=="undefined"?r=.43:r=r.value,(.2126*(e.rgb[0]/255)+.7152*(e.rgb[1]/255)+.0722*(e.rgb[2]/255))*e.alpha<r?n:t):null},e:function(t){return new e.Anonymous(t instanceof e.JavaScript?t.evaluated:t)},escape:function(t){return new e.Anonymous(encodeURI(t.value).replace(/=/g,"%3D").replace(/:/g,"%3A").replace(/#/g,"%23").replace(/;/g,"%3B").replace(/\(/g,"%28").replace(/\)/g,"%29"))},"%":function(t){var n=Array.prototype.slice.call(arguments,1),r=t.value;for(var i=0;i<n.length;i++)r=r.replace(/%[sda]/i,function(e){var t=e.match(/s/i)?n[i].value:n[i].toCSS();return e.match(/[A-Z]$/)?encodeURIComponent(t):t});return r=r.replace(/%%/g,"%"),new e.Quoted('"'+r+'"',r)},unit:function(t,n){return new e.Dimension(t.value,n?n.toCSS():"")},round:function(e,t){var n=typeof t=="undefined"?0:t.value;return this._math(function(e){return e.toFixed(n)},e)},ceil:function(e){return this._math(Math.ceil,e)},floor:function(e){return this._math(Math.floor,e)},_math:function(t,n){if(n instanceof e.Dimension)return new e.Dimension(t(parseFloat(n.value)),n.unit);if(typeof n=="number")return t(n);throw{type:"Argument",message:"argument must be a number"}},argb:function(t){return new e.Anonymous(t.toARGB())},percentage:function(t){return new e.Dimension(t.value*100,"%")},color:function(t){if(t instanceof e.Quoted)return new e.Color(t.value.slice(1));throw{type:"Argument",message:"argument must be a string"}},iscolor:function(t){return this._isa(t,e.Color)},isnumber:function(t){return this._isa(t,e.Dimension)},isstring:function(t){return this._isa(t,e.Quoted)},iskeyword:function(t){return this._isa(t,e.Keyword)},isurl:function(t){return this._isa(t,e.URL)},ispixel:function(t){return t instanceof e.Dimension&&t.unit==="px"?e.True:e.False},ispercentage:function(t){return t instanceof e.Dimension&&t.unit==="%"?e.True:e.False},isem:function(t){return t instanceof e.Dimension&&t.unit==="em"?e.True:e.False},_isa:function(t,n){return t instanceof n?e.True:e.False},multiply:function(e,t){var n=e.rgb[0]*t.rgb[0]/255,r=e.rgb[1]*t.rgb[1]/255,i=e.rgb[2]*t.rgb[2]/255;return this.rgb(n,r,i)},screen:function(e,t){var n=255-(255-e.rgb[0])*(255-t.rgb[0])/255,r=255-(255-e.rgb[1])*(255-t.rgb[1])/255,i=255-(255-e.rgb[2])*(255-t.rgb[2])/255;return this.rgb(n,r,i)},overlay:function(e,t){var n=e.rgb[0]<128?2*e.rgb[0]*t.rgb[0]/255:255-2*(255-e.rgb[0])*(255-t.rgb[0])/255,r=e.rgb[1]<128?2*e.rgb[1]*t.rgb[1]/255:255-2*(255-e.rgb[1])*(255-t.rgb[1])/255,i=e.rgb[2]<128?2*e.rgb[2]*t.rgb[2]/255:255-2*(255-e.rgb[2])*(255-t.rgb[2])/255;return this.rgb(n,r,i)},softlight:function(e,t){var n=t.rgb[0]*e.rgb[0]/255,r=n+e.rgb[0]*(255-(255-e.rgb[0])*(255-t.rgb[0])/255-n)/255;n=t.rgb[1]*e.rgb[1]/255;var i=n+e.rgb[1]*(255-(255-e.rgb[1])*(255-t.rgb[1])/255-n)/255;n=t.rgb[2]*e.rgb[2]/255;var s=n+e.rgb[2]*(255-(255-e.rgb[2])*(255-t.rgb[2])/255-n)/255;return this.rgb(r,i,s)},hardlight:function(e,t){var n=t.rgb[0]<128?2*t.rgb[0]*e.rgb[0]/255:255-2*(255-t.rgb[0])*(255-e.rgb[0])/255,r=t.rgb[1]<128?2*t.rgb[1]*e.rgb[1]/255:255-2*(255-t.rgb[1])*(255-e.rgb[1])/255,i=t.rgb[2]<128?2*t.rgb[2]*e.rgb[2]/255:255-2*(255-t.rgb[2])*(255-e.rgb[2])/255;return this.rgb(n,r,i)},difference:function(e,t){var n=Math.abs(e.rgb[0]-t.rgb[0]),r=Math.abs(e.rgb[1]-t.rgb[1]),i=Math.abs(e.rgb[2]-t.rgb[2]);return this.rgb(n,r,i)},exclusion:function(e,t){var n=e.rgb[0]+t.rgb[0]*(255-e.rgb[0]-e.rgb[0])/255,r=e.rgb[1]+t.rgb[1]*(255-e.rgb[1]-e.rgb[1])/255,i=e.rgb[2]+t.rgb[2]*(255-e.rgb[2]-e.rgb[2])/255;return this.rgb(n,r,i)},average:function(e,t){var n=(e.rgb[0]+t.rgb[0])/2,r=(e.rgb[1]+t.rgb[1])/2,i=(e.rgb[2]+t.rgb[2])/2;return this.rgb(n,r,i)},negation:function(e,t){var n=255-Math.abs(255-t.rgb[0]-e.rgb[0]),r=255-Math.abs(255-t.rgb[1]-e.rgb[1]),i=255-Math.abs(255-t.rgb[2]-e.rgb[2]);return this.rgb(n,r,i)},tint:function(e,t){return this.mix(this.rgb(255,255,255),e,t)},shade:function(e,t){return this.mix(this.rgb(0,0,0),e,t)}}})(n("./tree")),function(e){e.colors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen
+:"#9acd32"}}(n("./tree")),function(e){e.Alpha=function(e){this.value=e},e.Alpha.prototype={toCSS:function(){return"alpha(opacity="+(this.value.toCSS?this.value.toCSS():this.value)+")"},eval:function(e){return this.value.eval&&(this.value=this.value.eval(e)),this}}}(n("../tree")),function(e){e.Anonymous=function(e){this.value=e.value||e},e.Anonymous.prototype={toCSS:function(){return this.value},eval:function(){return this},compare:function(e){if(!e.toCSS)return-1;var t=this.toCSS(),n=e.toCSS();return t===n?0:t<n?-1:1}}}(n("../tree")),function(e){e.Assignment=function(e,t){this.key=e,this.value=t},e.Assignment.prototype={toCSS:function(){return this.key+"="+(this.value.toCSS?this.value.toCSS():this.value)},eval:function(t){return this.value.eval?new e.Assignment(this.key,this.value.eval(t)):this}}}(n("../tree")),function(e){e.Call=function(e,t,n,r){this.name=e,this.args=t,this.index=n,this.filename=r},e.Call.prototype={eval:function(t){var n=this.args.map(function(e){return e.eval(t)}),r;if(this.name in e.functions)try{r=e.functions[this.name].apply(e.functions,n);if(r!=null)return r}catch(i){throw{type:i.type||"Runtime",message:"error evaluating function `"+this.name+"`"+(i.message?": "+i.message:""),index:this.index,filename:this.filename}}return new e.Anonymous(this.name+"("+n.map(function(e){return e.toCSS(t)}).join(", ")+")")},toCSS:function(e){return this.eval(e).toCSS()}}}(n("../tree")),function(e){e.Color=function(e,t){Array.isArray(e)?this.rgb=e:e.length==6?this.rgb=e.match(/.{2}/g).map(function(e){return parseInt(e,16)}):this.rgb=e.split("").map(function(e){return parseInt(e+e,16)}),this.alpha=typeof t=="number"?t:1},e.Color.prototype={eval:function(){return this},toCSS:function(){return this.alpha<1?"rgba("+this.rgb.map(function(e){return Math.round(e)}).concat(this.alpha).join(", ")+")":"#"+this.rgb.map(function(e){return e=Math.round(e),e=(e>255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},operate:function(t,n){var r=[];n instanceof e.Color||(n=n.toColor());for(var i=0;i<3;i++)r[i]=e.operate(t,this.rgb[i],n.rgb[i]);return new e.Color(r,this.alpha+n.alpha)},toHSL:function(){var e=this.rgb[0]/255,t=this.rgb[1]/255,n=this.rgb[2]/255,r=this.alpha,i=Math.max(e,t,n),s=Math.min(e,t,n),o,u,a=(i+s)/2,f=i-s;if(i===s)o=u=0;else{u=a>.5?f/(2-i-s):f/(i+s);switch(i){case e:o=(t-n)/f+(t<n?6:0);break;case t:o=(n-e)/f+2;break;case n:o=(e-t)/f+4}o/=6}return{h:o*360,s:u,l:a,a:r}},toARGB:function(){var e=[Math.round(this.alpha*255)].concat(this.rgb);return"#"+e.map(function(e){return e=Math.round(e),e=(e>255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},compare:function(e){return e.rgb?e.rgb[0]===this.rgb[0]&&e.rgb[1]===this.rgb[1]&&e.rgb[2]===this.rgb[2]&&e.alpha===this.alpha?0:-1:-1}}}(n("../tree")),function(e){e.Comment=function(e,t){this.value=e,this.silent=!!t},e.Comment.prototype={toCSS:function(e){return e.compress?"":this.value},eval:function(){return this}}}(n("../tree")),function(e){e.Condition=function(e,t,n,r,i){this.op=e.trim(),this.lvalue=t,this.rvalue=n,this.index=r,this.negate=i},e.Condition.prototype.eval=function(e){var t=this.lvalue.eval(e),n=this.rvalue.eval(e),r=this.index,i,i=function(e){switch(e){case"and":return t&&n;case"or":return t||n;default:if(t.compare)i=t.compare(n);else{if(!n.compare)throw{type:"Type",message:"Unable to perform comparison",index:r};i=n.compare(t)}switch(i){case-1:return e==="<"||e==="=<";case 0:return e==="="||e===">="||e==="=<";case 1:return e===">"||e===">="}}}(this.op);return this.negate?!i:i}}(n("../tree")),function(e){e.Dimension=function(e,t){this.value=parseFloat(e),this.unit=t||null},e.Dimension.prototype={eval:function(){return this},toColor:function(){return new e.Color([this.value,this.value,this.value])},toCSS:function(){var e=this.value+this.unit;return e},operate:function(t,n){return new e.Dimension(e.operate(t,this.value,n.value),this.unit||n.unit)},compare:function(t){return t instanceof e.Dimension?t.value>this.value?-1:t.value<this.value?1:t.unit&&this.unit!==t.unit?-1:0:-1}}}(n("../tree")),function(e){e.Directive=function(t,n){this.name=t,Array.isArray(n)?(this.ruleset=new e.Ruleset([],n),this.ruleset.allowImports=!0):this.value=n},e.Directive.prototype={toCSS:function(e,t){return this.ruleset?(this.ruleset.root=!0,this.name+(t.compress?"{":" {\n  ")+this.ruleset.toCSS(e,t).trim().replace(/\n/g,"\n  ")+(t.compress?"}":"\n}\n")):this.name+" "+this.value.toCSS()+";\n"},eval:function(t){var n=this;return this.ruleset&&(t.frames.unshift(this),n=new e.Directive(this.name),n.ruleset=this.ruleset.eval(t),t.frames.shift()),n},variable:function(t){return e.Ruleset.prototype.variable.call(this.ruleset,t)},find:function(){return e.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return e.Ruleset.prototype.rulesets.apply(this.ruleset)}}}(n("../tree")),function(e){e.Element=function(t,n,r){this.combinator=t instanceof e.Combinator?t:new e.Combinator(t),typeof n=="string"?this.value=n.trim():n?this.value=n:this.value="",this.index=r},e.Element.prototype.eval=function(t){return new e.Element(this.combinator,this.value.eval?this.value.eval(t):this.value,this.index)},e.Element.prototype.toCSS=function(e){var t=this.value.toCSS?this.value.toCSS(e):this.value;return t==""&&this.combinator.value.charAt(0)=="&"?"":this.combinator.toCSS(e||{})+t},e.Combinator=function(e){e===" "?this.value=" ":this.value=e?e.trim():""},e.Combinator.prototype.toCSS=function(e){return{"":""," ":" ",":":" :","+":e.compress?"+":" + ","~":e.compress?"~":" ~ ",">":e.compress?">":" > ","|":e.compress?"|":" | "}[this.value]}}(n("../tree")),function(e){e.Expression=function(e){this.value=e},e.Expression.prototype={eval:function(t){return this.value.length>1?new e.Expression(this.value.map(function(e){return e.eval(t)})):this.value.length===1?this.value[0].eval(t):this},toCSS:function(e){return this.value.map(function(t){return t.toCSS?t.toCSS(e):""}).join(" ")}}}(n("../tree")),function(e){e.Import=function(t,n,r,i,s,o){var u=this;this.once=i,this.index=s,this._path=t,this.features=r&&new e.Value(r),this.rootpath=o,t instanceof e.Quoted?this.path=/(\.[a-z]*$)|([\?;].*)$/.test(t.value)?t.value:t.value+".less":this.path=t.value.value||t.value,this.css=/css([\?;].*)?$/.test(this.path),this.css||n.push(this.path,function(t,n,r){t&&(t.index=s),r&&u.once&&(u.skip=r),u.root=n||new e.Ruleset([],[])})},e.Import.prototype={toCSS:function(e){var t=this.features?" "+this.features.toCSS(e):"";return this.css?(typeof this._path.value=="string"&&!/^(?:[a-z-]+:|\/)/.test(this._path.value)&&(this._path.value=this.rootpath+this._path.value),"@import "+this._path.toCSS()+t+";\n"):""},eval:function(t){var n,r=this.features&&this.features.eval(t);return this.skip?[]:this.css?this:(n=new e.Ruleset([],this.root.rules.slice(0)),n.evalImports(t),this.features?new e.Media(n.rules,this.features.value):n.rules)}}}(n("../tree")),function(e){e.JavaScript=function(e,t,n){this.escaped=n,this.expression=e,this.index=t},e.JavaScript.prototype={eval:function(t){var n,r=this,i={},s=this.expression.replace(/@\{([\w-]+)\}/g,function(n,i){return e.jsify((new e.Variable("@"+i,r.index)).eval(t))});try{s=new Function("return ("+s+")")}catch(o){throw{message:"JavaScript evaluation error: `"+s+"`",index:this.index}}for(var u in t.frames[0].variables())i[u.slice(1)]={value:t.frames[0].variables()[u].value,toJS:function(){return this.value.eval(t).toCSS()}};try{n=s.call(i)}catch(o){throw{message:"JavaScript evaluation error: '"+o.name+": "+o.message+"'",index:this.index}}return typeof n=="string"?new e.Quoted('"'+n+'"',n,this.escaped,this.index):Array.isArray(n)?new e.Anonymous(n.join(", ")):new e.Anonymous(n)}}}(n("../tree")),function(e){e.Keyword=function(e){this.value=e},e.Keyword.prototype={eval:function(){return this},toCSS:function(){return this.value},compare:function(t){return t instanceof e.Keyword?t.value===this.value?0:1:-1}},e.True=new e.Keyword("true"),e.False=new e.Keyword("false")}(n("../tree")),function(e){e.Media=function(t,n){var r=this.emptySelectors();this.features=new e.Value(n),this.ruleset=new e.Ruleset(r,t),this.ruleset.allowImports=!0},e.Media.prototype={toCSS:function(e,t){var n=this.features.toCSS(t);return this.ruleset.root=e.length===0||e[0].multiMedia,"@media "+n+(t.compress?"{":" {\n  ")+this.ruleset.toCSS(e,t).trim().replace(/\n/g,"\n  ")+(t.compress?"}":"\n}\n")},eval:function(t){t.mediaBlocks||(t.mediaBlocks=[],t.mediaPath=[]);var n=new e.Media([],[]);return this.debugInfo&&(this.ruleset.debugInfo=this.debugInfo,n.debugInfo=this.debugInfo),n.features=this.features.eval(t),t.mediaPath.push(n),t.mediaBlocks.push(n),t.frames.unshift(this.ruleset),n.ruleset=this.ruleset.eval(t),t.frames.shift(),t.mediaPath.pop(),t.mediaPath.length===0?n.evalTop(t):n.evalNested(t)},variable:function(t){return e.Ruleset.prototype.variable.call(this.ruleset,t)},find:function(){return e.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return e.Ruleset.prototype.rulesets.apply(this.ruleset)},emptySelectors:function(){var t=new e.Element("","&",0);return[new e.Selector([t])]},evalTop:function(t){var n=this;if(t.mediaBlocks.length>1){var r=this.emptySelectors();n=new e.Ruleset(r,t.mediaBlocks),n.multiMedia=!0}return delete t.mediaBlocks,delete t.mediaPath,n},evalNested:function(t){var n,r,i=t.mediaPath.concat([this]);for(n=0;n<i.length;n++)r=i[n].features instanceof e.Value?i[n].features.value:i[n].features,i[n]=Array.isArray(r)?r:[r];return this.features=new e.Value(this.permute(i).map(function(t){t=t.map(function(t){return t.toCSS?t:new e.Anonymous(t)});for(n=t.length-1;n>0;n--)t.splice(n,0,new e.Anonymous("and"));return new e.Expression(t)})),new e.Ruleset([],[])},permute:function(e){if(e.length===0)return[];if(e.length===1)return e[0];var t=[],n=this.permute(e.slice(1));for(var r=0;r<n.length;r++)for(var i=0;i<e[0].length;i++)t.push([e[0][i]].concat(n[r]));return t},bubbleSelectors:function(t){this.ruleset=new e.Ruleset(t.slice(0),[this.ruleset])}}}(n("../tree")),function(e){e.mixin={},e.mixin.Call=function(t,n,r,i,s){this.selector=new e.Selector(t),this.arguments=n,this.index=r,this.filename=i,this.important=s},e.mixin.Call.prototype={eval:function(t){var n,r,i,s=[],o=!1,u,a,f,l,c;i=this.arguments&&this.arguments.map(function(e){return{name:e.name,value:e.value.eval(t)}});for(u=0;u<t.frames.length;u++)if((n=t.frames[u].find(this.selector)).length>0){c=!0;for(a=0;a<n.length;a++){r=n[a],l=!1;for(f=0;f<t.frames.length;f++)if(!(r instanceof e.mixin.Definition)&&r===(t.frames[f].originalRuleset||t.frames[f])){l=!0;break}if(l)continue;if(r.matchArgs(i,t)){if(!r.matchCondition||r.matchCondition(i,t))try{Array.prototype.push.apply(s,r.eval(t,i,this.important).rules)}catch(h){throw{message:h.message,index:this.index,filename:this.filename,stack:h.stack}}o=!0}}if(o)return s}throw c?{type:"Runtime",message:"No matching definition was found for `"+this.selector.toCSS().trim()+"("+(i?i.map(function(e){var t="";return e.name&&(t+=e.name+":"),e.value.toCSS?t+=e.value.toCSS():t+="???",t}).join(", "):"")+")`",index:this.index,filename:this.filename}:{type:"Name",message:this.selector.toCSS().trim()+" is undefined",index:this.index,filename:this.filename}}},e.mixin.Definition=function(t,n,r,i,s){this.name=t,this.selectors=[new e.Selector([new e.Element(null,t)])],this.params=n,this.condition=i,this.variadic=s,this.arity=n.length,this.rules=r,this._lookups={},this.required=n.reduce(function(e,t){return!t.name||t.name&&!t.value?e+1:e},0),this.parent=e.Ruleset.prototype,this.frames=[]},e.mixin.Definition.prototype={toCSS:function(){return""},variable:function(e){return this.parent.variable.call(this,e)},variables:function(){return this.parent.variables.call(this)},find:function(){return this.parent.find.apply(this,arguments)},rulesets:function(){return this.parent.rulesets.apply(this)},evalParams:function(t,n,r,i){var s=new e.Ruleset(null,[]),o,u,a=this.params.slice(0),f,l,c,h,p,d;if(r){r=r.slice(0);for(f=0;f<r.length;f++){u=r[f];if(h=u&&u.name){p=!1;for(l=0;l<a.length;l++)if(!i[l]&&h===a[l].name){i[l]=u.value.eval(t),s.rules.unshift(new e.Rule(h,u.value.eval(t))),p=!0;break}if(p){r.splice(f,1),f--;continue}throw{type:"Runtime",message:"Named argument for "+this.name+" "+r[f].name+" not found"}}}}d=0;for(f=0;f<a.length;f++){if(i[f])continue;u=r&&r[d];if(h=a[f].name)if(a[f].variadic&&r){o=[];for(l=d;l<r.length;l++)o.push(r[l].value.eval(t));s.rules.unshift(new e.Rule(h,(new e.Expression(o)).eval(t)))}else{c=u&&u.value;if(c)c=c.eval(t);else{if(!a[f].value)throw{type:"Runtime",message:"wrong number of arguments for "+this.name+" ("+r.length+" for "+this.arity+")"};c=a[f].value.eval(n)}s.rules.unshift(new e.Rule(h,c)),i[f]=c}if(a[f].variadic&&r)for(l=d;l<r.length;l++)i[l]=r[l].value.eval(t);d++}return s},eval:function(t,n,r){var i=[],s=this.frames.concat(t.frames),o=this.evalParams(t,{frames:s},n,i),u,a,f,l;return o.rules.unshift(new e.Rule("@arguments",(new e.Expression(i)).eval(t))),a=r?this.parent.makeImportant.apply(this).rules:this.rules.slice(0),l=(new e.Ruleset(null,a)).eval({frames:[this,o].concat(s)}),l.originalRuleset=this,l},matchCondition:function(e,t){return this.condition&&!this.condition.eval({frames:[this.evalParams(t,{frames:this.frames.concat(t.frames)},e,[])].concat(t.frames)})?!1:!0},matchArgs:function(e,t){var n=e&&e.length||0,r,i;if(!this.variadic){if(n<this.required)return!1;if(n>this.params.length)return!1;if(this.required>0&&n>this.params.length)return!1}r=Math.min(n,this.arity);for(var s=0;s<r;s++)if(!this.params[s].name&&!this.params[s].variadic&&e[s].value.eval(t).toCSS()!=this.params[s].value.eval(t).toCSS())return!1;return!0}}}(n("../tree")),function(e){e.Operation=function(e,t){this.op=e.trim(),this.operands=t},e.Operation.prototype.eval=function(t){var n=this.operands[0].eval(t),r=this.operands[1].eval(t),i;if(n instanceof e.Dimension&&r instanceof e.Color){if(this.op!=="*"&&this.op!=="+")throw{name:"OperationError",message:"Can't substract or divide a color from a number"};i=r,r=n,n=i}if(!n.operate)throw{name:"OperationError",message:"Operation on an invalid type"};return n.operate(this.op,r)},e.operate=function(e,t,n){switch(e){case"+":return t+n;case"-":return t-n;case"*":return t*n;case"/":return t/n}}}(n("../tree")),function(e){e.Paren=function(e){this.value=e},e.Paren.prototype={toCSS:function(e){return"("+this.value.toCSS(e)+")"},eval:function(t){return new e.Paren(this.value.eval(t))}}}(n("../tree")),function(e){e.Quoted=function(e,t,n,r){this.escaped=n,this.value=t||"",this.quote=e.charAt(0),this.index=r},e.Quoted.prototype={toCSS:function(){return this.escaped?this.value:this.quote+this.value+this.quote},eval:function(t){var n=this,r=this.value.replace(/`([^`]+)`/g,function(r,i){return(new e.JavaScript(i,n.index,!0)).eval(t).value}).replace(/@\{([\w-]+)\}/g,function(r,i){var s=(new e.Variable("@"+i,n.index)).eval(t);return s instanceof e.Quoted?s.value:s.toCSS()});return new e.Quoted(this.quote+r+this.quote,r,this.escaped,this.index)},compare:function(e){if(!e.toCSS)return-1;var t=this.toCSS(),n=e.toCSS();return t===n?0:t<n?-1:1}}}(n("../tree")),function(e){e.Ratio=function(e){this.value=e},e.Ratio.prototype={toCSS:function(e){return this.value},eval:function(){return this}}}(n("../tree")),function(e){e.Rule=function(t,n,r,i,s){this.name=t,this.value=n instanceof e.Value?n:new e.Value([n]),this.important=r?" "+r.trim():"",this.index=i,this.inline=s||!1,t.charAt(0)==="@"?this.variable=!0:this.variable=!1},e.Rule.prototype.toCSS=function(e){return this.variable?"":this.name+(e.compress?":":": ")+this.value.toCSS(e)+this.important+(this.inline?"":";")},e.Rule.prototype.eval=function(t){return new e.Rule(this.name,this.value.eval(t),this.important,this.index,this.inline)},e.Rule.prototype.makeImportant=function(){return new e.Rule(this.name,this.value,"!important",this.index,this.inline)},e.Shorthand=function(e,t){this.a=e,this.b=t},e.Shorthand.prototype={toCSS:function(e){return this.a.toCSS(e)+"/"+this.b.toCSS(e)},eval:function(){return this}}}(n("../tree")),function(e){e.Ruleset=function(e,t,n){this.selectors=e,this.rules=t,this._lookups={},this.strictImports=n},e.Ruleset.prototype={eval:function(t){var n=this.selectors&&this.selectors.map(function(e){return e.eval(t)}),r=new e.Ruleset(n,this.rules.slice(0),this.strictImports),i;r.originalRuleset=this,r.root=this.root,r.allowImports=this.allowImports,this.debugInfo&&(r.debugInfo=this.debugInfo),t.frames.unshift(r),(r.root||r.allowImports||!r.strictImports)&&r.evalImports(t);for(var s=0;s<r.rules.length;s++)r.rules[s]instanceof e.mixin.Definition&&(r.rules[s].frames=t.frames.slice(0));var o=t.mediaBlocks&&t.mediaBlocks.length||0;for(var s=0;s<r.rules.length;s++)r.rules[s]instanceof e.mixin.Call&&(i=r.rules[s].eval(t),r.rules.splice.apply(r.rules,[s,1].concat(i)),s+=i.length-1,r.resetCache());for(var s=0,u;s<r.rules.length;s++)u=r.rules[s],u instanceof e.mixin.Definition||(r.rules[s]=u.eval?u.eval(t):u);t.frames.shift();if(t.mediaBlocks)for(var s=o;s<t.mediaBlocks.length;s++)t.mediaBlocks[s].bubbleSelectors(n);return r},evalImports:function(t){var n,r;for(n=0;n<this.rules.length;n++)this.rules[n]instanceof e.Import&&(r=this.rules[n].eval(t),typeof r.length=="number"?(this.rules.splice.apply(this.rules,[n,1].concat(r)),n+=r.length-1):this.rules.splice(n,1,r),this.resetCache())},makeImportant:function(){return new e.Ruleset(this.selectors,this.rules.map(function(e){return e.makeImportant?e.makeImportant():e}),this.strictImports)},matchArgs:function(e){return!e||e.length===0},resetCache:function(){this._rulesets=null,this._variables=null,this._lookups={}},variables:function(){return this._variables?this._variables:this._variables=this.rules.reduce(function(t,n){return n instanceof e.Rule&&n.variable===!0&&(t[n.name]=n),t},{})},variable:function(e){return this.variables()[e]},rulesets:function(){return this._rulesets?this._rulesets:this._rulesets=this.rules.filter(function(t){return t instanceof e.Ruleset||t instanceof e.mixin.Definition})},find:function(t,n){n=n||this;var r=[],i,s,o=t.toCSS();return o in this._lookups?this._lookups[o]:(this.rulesets().forEach(function(i){if(i!==n)for(var o=0;o<i.selectors.length;o++)if(s=t.match(i.selectors[o])){t.elements.length>i.selectors[o].elements.length?Array.prototype.push.apply(r,i.find(new e.Selector(t.elements.slice(1)),n)):r.push(i);break}}),this._lookups[o]=r)},toCSS:function(t,n){var r=[],i=[],s=[],o=[],u=[],a,f,l;this.root||this.joinSelectors(u,t,this.selectors);for(var c=0;c<this.rules.length;c++){l=this.rules[c];if(l.rules||l instanceof e.Media)o.push(l.toCSS(u,n));else if(l instanceof e.Directive){var h=l.toCSS(u,n);if(l.name==="@charset"){if(n.charset){l.debugInfo&&(o.push(e.debugInfo(n,l)),o.push((new e.Comment("/* "+h.replace(/\n/g,"")+" */\n")).toCSS(n)));continue}n.charset=!0}o.push(h)}else l instanceof e.Comment?l.silent||(this.root?o.push(l.toCSS(n)):i.push(l.toCSS(n))):l.toCSS&&!l.variable?i.push(l.toCSS(n)):l.value&&!l.variable&&i.push(l.value.toString())}o=o.join("");if(this.root)r.push(i.join(n.compress?"":"\n"));else if(i.length>0){f=e.debugInfo(n,this),a=u.map(function(e){return e.map(function(e){return e.toCSS(n)}).join("").trim()}).join(n.compress?",":",\n");for(var c=i.length-1;c>=0;c--)s.indexOf(i[c])===-1&&s.unshift(i[c]);i=s,r.push(f+a+(n.compress?"{":" {\n  ")+i.join(n.compress?"":"\n  ")+(n.compress?"}":"\n}\n"))}return r.push(o),r.join("")+(n.compress?"\n":"")},joinSelectors:function(e,t,n){for(var r=0;r<n.length;r++)this.joinSelector(e,t,n[r])},joinSelector:function(t,n,r){var i,s,o,u,a,f,l,c,h,p,d,v,m,g,y;for(i=0;i<r.elements.length;i++)f=r.elements[i],f.value==="&"&&(u=!0);if(!u){if(n.length>0)for(i=0;i<n.length;i++)t.push(n[i].concat(r));else t.push([r]);return}g=[],a=[[]];for(i=0;i<r.elements.length;i++){f=r.elements[i];if(f.value!=="&")g.push(f);else{y=[],g.length>0&&this.mergeElementsOnToSelectors(g,a);for(s=0;s<a.length;s++){l=a[s];if(n.length==0)l.length>0&&(l[0].elements=l[0].elements.slice(0),l[0].elements.push(new e.Element(f.combinator,"",0))),y.push(l);else for(o=0;o<n.length;o++)c=n[o],h=[],p=[],v=!0,l.length>0?(h=l.slice(0),m=h.pop(),d=new e.Selector(m.elements.slice(0)),v=!1):d=new e.Selector([]),c.length>1&&(p=p.concat(c.slice(1))),c.length>0&&(v=!1,d.elements.push(new e.Element(f.combinator,c[0].elements[0].value,0)),d.elements=d.elements.concat(c[0].elements.slice(1))),v||h.push(d),h=h.concat(p),y.push(h)}a=y,g=[]}}g.length>0&&this.mergeElementsOnToSelectors(g,a);for(i=0;i<a.length;i++)t.push(a[i])},mergeElementsOnToSelectors:function(t,n){var r,i;if(n.length==0){n.push([new e.Selector(t)]);return}for(r=0;r<n.length;r++)i=n[r],i.length>0?i[i.length-1]=new e.Selector(i[i.length-1].elements.concat(t)):i.push(new e.Selector(t))}}}(n("../tree")),function(e){e.Selector=function(e){this.elements=e},e.Selector.prototype.match=function(e){var t=this.elements,n=t.length,r,i,s,o;r=e.elements.slice(e.elements.length&&e.elements[0].value==="&"?1:0),i=r.length,s=Math.min(n,i);if(i===0||n<i)return!1;for(o=0;o<s;o++)if(t[o].value!==r[o].value)return!1;return!0},e.Selector.prototype.eval=function(t){return new e.Selector(this.elements.map(function(e){return e.eval(t)}))},e.Selector.prototype.toCSS=function(e){return this._css?this._css:(this.elements[0].combinator.value===""?this._css=" ":this._css="",this._css+=this.elements.map(function(t){return typeof t=="string"?" "+t.trim():t.toCSS(e)}).join(""),this._css)}}(n("../tree")),function(e){e.UnicodeDescriptor=function(e){this.value=e},e.UnicodeDescriptor.prototype={toCSS:function(e){return this.value},eval:function(){return this}}}(n("../tree")),function(e){e.URL=function(e,t){this.value=e,this.rootpath=t},e.URL.prototype={toCSS:function(){return"url("+this.value.toCSS()+")"},eval:function(t){var n=this.value.eval(t),r;return typeof n.value=="string"&&!/^(?:[a-z-]+:|\/)/.test(n.value)&&(r=this.rootpath,n.quote||(r=r.replace(/[\(\)'"\s]/g,function(e){return"\\"+e})),n.value=r+n.value),new e.URL(n,this.rootpath)}}}(n("../tree")),function(e){e.Value=function(e){this.value=e,this.is="value"},e.Value.prototype={eval:function(t){return this.value.length===1?this.value[0].eval(t):new e.Value(this.value.map(function(e){return e.eval(t)}))},toCSS:function(e){return this.value.map(function(t){return t.toCSS(e)}).join(e.compress?",":", ")}}}(n("../tree")),function(e){e.Variable=function(e,t,n){this.name=e,this.index=t,this.file=n},e.Variable.prototype={eval:function(t){var n,r,i=this.name;i.indexOf("@@")==0&&(i="@"+(new e.Variable(i.slice(1))).eval(t).value);if(this.evaluating)throw{type:"Name",message:"Recursive variable definition for "+i,filename:this.file,index:this.index};this.evaluating=!0;if(n=e.find(t.frames,function(e){if(r=e.variable(i))return r.value.eval(t)}))return this.evaluating=!1,n;throw{type:"Name",message:"variable "+i+" is undefined",filename:this.file,index:this.index}}}}(n("../tree")),function(e){e.debugInfo=function(t,n){var r="";if(t.dumpLineNumbers&&!t.compress)switch(t.dumpLineNumbers){case"comments":r=e.debugInfo.asComment(n);break;case"mediaquery":r=e.debugInfo.asMediaQuery(n);break;case"all":r=e.debugInfo.asComment(n)+e.debugInfo.asMediaQuery(n)}return r},e.debugInfo.asComment=function(e){return"/* line "+e.debugInfo.lineNumber+", "+e.debugInfo.fileName+" */\n"},e.debugInfo.asMediaQuery=function(e){return"@media -sass-debug-info{filename{font-family:"+("file://"+e.debugInfo.fileName).replace(/[\/:.]/g,"\\$&")+"}line{font-family:\\00003"+e.debugInfo.lineNumber+"}}\n"},e.find=function(e,t){for(var n=0,r;n<e.length;n++)if(r=t.call(e,e[n]))return r;return null},e.jsify=function(e){return Array.isArray(e.value)&&e.value.length>1?"["+e.value.map(function(e){return e.toCSS(!1)}).join(", ")+"]":e.toCSS(!1)}}(n("./tree"));var o=/^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);r.env=r.env||(location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname=="localhost"||location.port.length>0||o?"development":"production"),r.async=r.async||!1,r.fileAsync=r.fileAsync||!1,r.poll=r.poll||(o?1e3:1500);if(r.functions)for(var u in r.functions)r.tree.functions[u]=r.functions[u];var a=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);a&&(r.dumpLineNumbers=a[1]),r.watch=function(){return r.watchMode||(r.env="development",f()),this.watchMode=!0},r.unwatch=function(){return clearInterval(r.watchTimer),this.watchMode=!1},/!watch/.test(location.hash)&&r.watch();var l=null;if(r.env!="development")try{l=typeof e.localStorage=="undefined"?null:e.localStorage}catch(c){}var h=document.getElementsByTagName("link"),p=/^text\/(x-)?less$/;r.sheets=[];for(var d=0;d<h.length;d++)(h[d].rel==="stylesheet/less"||h[d].rel.match(/stylesheet/)&&h[d].type.match(p))&&r.sheets.push(h[d]);var v="";r.modifyVars=function(e){var t=v;for(name in e)t+=(name.slice(0,1)==="@"?"":"@")+name+": "+(e[name].slice(-1)===";"?e[name]:e[name]+";");(new r.Parser).parse(t,function(e,t){S(t.toCSS(),r.sheets[r.sheets.length-1])})},r.refresh=function(e){var t,n;t=n=new Date,g(function(e,r,i,s,o){o.local?C("loading "+s.href+" from cache."):(C("parsed "+s.href+" successfully."),S(r.toCSS(),s,o.lastModified)),C("css for "+s.href+" generated in "+(new Date-n)+"ms"),o.remaining===0&&C("css generated in "+(new Date-t)+"ms"),n=new Date},e),m()},r.refreshStyles=m,r.refresh(r.env==="development"),typeof define=="function"&&define.amd&&define("less",[],function(){return r})})(window);
\ No newline at end of file
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/raw-files.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/raw-files.js
new file mode 100644
index 0000000..4c34c2f
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/raw-files.js
@@ -0,0 +1,3 @@
+var __js = {"affix.js":"/* ========================================================================\n * Bootstrap: affix.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#affix\n * ========================================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // AFFIX CLASS DEFINITION\n  // ======================\n\n  var Affix = function (element, options) {\n    this.options = $.extend({}, Affix.DEFAULTS, options)\n    this.$window = $(window)\n      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))\n      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))\n\n    this.$element = $(element)\n    this.affixed  =\n    this.unpin    = null\n\n    this.checkPosition()\n  }\n\n  Affix.RESET = 'affix affix-top affix-bottom'\n\n  Affix.DEFAULTS = {\n    offset: 0\n  }\n\n  Affix.prototype.checkPositionWithEventLoop = function () {\n    setTimeout($.proxy(this.checkPosition, this), 1)\n  }\n\n  Affix.prototype.checkPosition = function () {\n    if (!this.$element.is(':visible')) return\n\n    var scrollHeight = $(document).height()\n    var scrollTop    = this.$window.scrollTop()\n    var position     = this.$element.offset()\n    var offset       = this.options.offset\n    var offsetTop    = offset.top\n    var offsetBottom = offset.bottom\n\n    if (typeof offset != 'object')         offsetBottom = offsetTop = offset\n    if (typeof offsetTop == 'function')    offsetTop    = offset.top()\n    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()\n\n    var affix = this.unpin   != null && (scrollTop + this.unpin <= position.top) ? false :\n                offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :\n                offsetTop    != null && (scrollTop <= offsetTop) ? 'top' : false\n\n    if (this.affixed === affix) return\n    if (this.unpin) this.$element.css('top', '')\n\n    this.affixed = affix\n    this.unpin   = affix == 'bottom' ? position.top - scrollTop : null\n\n    this.$element.removeClass(Affix.RESET).addClass('affix' + (affix ? '-' + affix : ''))\n\n    if (affix == 'bottom') {\n      this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() })\n    }\n  }\n\n\n  // AFFIX PLUGIN DEFINITION\n  // =======================\n\n  var old = $.fn.affix\n\n  $.fn.affix = function (option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.affix')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.affix.Constructor = Affix\n\n\n  // AFFIX NO CONFLICT\n  // =================\n\n  $.fn.affix.noConflict = function () {\n    $.fn.affix = old\n    return this\n  }\n\n\n  // AFFIX DATA-API\n  // ==============\n\n  $(window).on('load', function () {\n    $('[data-spy=\"affix\"]').each(function () {\n      var $spy = $(this)\n      var data = $spy.data()\n\n      data.offset = data.offset || {}\n\n      if (data.offsetBottom) data.offset.bottom = data.offsetBottom\n      if (data.offsetTop)    data.offset.top    = data.offsetTop\n\n      $spy.affix(data)\n    })\n  })\n\n}(window.jQuery);\n","alert.js":"/* ========================================================================\n * Bootstrap: alert.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#alerts\n * ========================================================================\n * Copyright 2013 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // ALERT CLASS DEFINITION\n  // ======================\n\n  var dismiss = '[data-dismiss=\"alert\"]'\n  var Alert   = function (el) {\n    $(el).on('click', dismiss, this.close)\n  }\n\n  Alert.prototype.close = function (e) {\n    var $this    = $(this)\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = $(selector)\n\n    if (e) e.preventDefault()\n\n    if (!$parent.length) {\n      $parent = $this.hasClass('alert') ? $this : $this.parent()\n    }\n\n    $parent.trigger(e = $.Event('close.bs.alert'))\n\n    if (e.isDefaultPrevented()) return\n\n    $parent.removeClass('in')\n\n    function removeElement() {\n      $parent.trigger('closed.bs.alert').remove()\n    }\n\n    $.support.transition && $parent.hasClass('fade') ?\n      $parent\n        .one($.support.transition.end, removeElement)\n        .emulateTransitionEnd(150) :\n      removeElement()\n  }\n\n\n  // ALERT PLUGIN DEFINITION\n  // =======================\n\n  var old = $.fn.alert\n\n  $.fn.alert = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.alert')\n\n      if (!data) $this.data('bs.alert', (data = new Alert(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  $.fn.alert.Constructor = Alert\n\n\n  // ALERT NO CONFLICT\n  // =================\n\n  $.fn.alert.noConflict = function () {\n    $.fn.alert = old\n    return this\n  }\n\n\n  // ALERT DATA-API\n  // ==============\n\n  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)\n\n}(window.jQuery);\n","button.js":"/* ========================================================================\n * Bootstrap: button.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#buttons\n * ========================================================================\n * Copyright 2013 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // BUTTON PUBLIC CLASS DEFINITION\n  // ==============================\n\n  var Button = function (element, options) {\n    this.$element = $(element)\n    this.options  = $.extend({}, Button.DEFAULTS, options)\n  }\n\n  Button.DEFAULTS = {\n    loadingText: 'loading...'\n  }\n\n  Button.prototype.setState = function (state) {\n    var d    = 'disabled'\n    var $el  = this.$element\n    var val  = $el.is('input') ? 'val' : 'html'\n    var data = $el.data()\n\n    state = state + 'Text'\n\n    if (!data.resetText) $el.data('resetText', $el[val]())\n\n    $el[val](data[state] || this.options[state])\n\n    // push to event loop to allow forms to submit\n    setTimeout(function () {\n      state == 'loadingText' ?\n        $el.addClass(d).attr(d, d) :\n        $el.removeClass(d).removeAttr(d);\n    }, 0)\n  }\n\n  Button.prototype.toggle = function () {\n    var $parent = this.$element.closest('[data-toggle=\"buttons\"]')\n\n    if ($parent.length) {\n      var $input = this.$element.find('input')\n        .prop('checked', !this.$element.hasClass('active'))\n        .trigger('change')\n      if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active')\n    }\n\n    this.$element.toggleClass('active')\n  }\n\n\n  // BUTTON PLUGIN DEFINITION\n  // ========================\n\n  var old = $.fn.button\n\n  $.fn.button = function (option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.button')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.button', (data = new Button(this, options)))\n\n      if (option == 'toggle') data.toggle()\n      else if (option) data.setState(option)\n    })\n  }\n\n  $.fn.button.Constructor = Button\n\n\n  // BUTTON NO CONFLICT\n  // ==================\n\n  $.fn.button.noConflict = function () {\n    $.fn.button = old\n    return this\n  }\n\n\n  // BUTTON DATA-API\n  // ===============\n\n  $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {\n    var $btn = $(e.target)\n    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')\n    $btn.button('toggle')\n    e.preventDefault()\n  })\n\n}(window.jQuery);\n","carousel.js":"/* ========================================================================\n * Bootstrap: carousel.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#carousel\n * ========================================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // CAROUSEL CLASS DEFINITION\n  // =========================\n\n  var Carousel = function (element, options) {\n    this.$element    = $(element)\n    this.$indicators = this.$element.find('.carousel-indicators')\n    this.options     = options\n    this.paused      =\n    this.sliding     =\n    this.interval    =\n    this.$active     =\n    this.$items      = null\n\n    this.options.pause == 'hover' && this.$element\n      .on('mouseenter', $.proxy(this.pause, this))\n      .on('mouseleave', $.proxy(this.cycle, this))\n  }\n\n  Carousel.DEFAULTS = {\n    interval: 5000\n  , pause: 'hover'\n  , wrap: true\n  }\n\n  Carousel.prototype.cycle =  function (e) {\n    e || (this.paused = false)\n\n    this.interval && clearInterval(this.interval)\n\n    this.options.interval\n      && !this.paused\n      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))\n\n    return this\n  }\n\n  Carousel.prototype.getActiveIndex = function () {\n    this.$active = this.$element.find('.item.active')\n    this.$items  = this.$active.parent().children()\n\n    return this.$items.index(this.$active)\n  }\n\n  Carousel.prototype.to = function (pos) {\n    var that        = this\n    var activeIndex = this.getActiveIndex()\n\n    if (pos > (this.$items.length - 1) || pos < 0) return\n\n    if (this.sliding)       return this.$element.one('slid', function () { that.to(pos) })\n    if (activeIndex == pos) return this.pause().cycle()\n\n    return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))\n  }\n\n  Carousel.prototype.pause = function (e) {\n    e || (this.paused = true)\n\n    if (this.$element.find('.next, .prev').length && $.support.transition.end) {\n      this.$element.trigger($.support.transition.end)\n      this.cycle(true)\n    }\n\n    this.interval = clearInterval(this.interval)\n\n    return this\n  }\n\n  Carousel.prototype.next = function () {\n    if (this.sliding) return\n    return this.slide('next')\n  }\n\n  Carousel.prototype.prev = function () {\n    if (this.sliding) return\n    return this.slide('prev')\n  }\n\n  Carousel.prototype.slide = function (type, next) {\n    var $active   = this.$element.find('.item.active')\n    var $next     = next || $active[type]()\n    var isCycling = this.interval\n    var direction = type == 'next' ? 'left' : 'right'\n    var fallback  = type == 'next' ? 'first' : 'last'\n    var that      = this\n\n    if (!$next.length) {\n      if (!this.options.wrap) return\n      $next = this.$element.find('.item')[fallback]()\n    }\n\n    this.sliding = true\n\n    isCycling && this.pause()\n\n    var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })\n\n    if ($next.hasClass('active')) return\n\n    if (this.$indicators.length) {\n      this.$indicators.find('.active').removeClass('active')\n      this.$element.one('slid', function () {\n        var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])\n        $nextIndicator && $nextIndicator.addClass('active')\n      })\n    }\n\n    if ($.support.transition && this.$element.hasClass('slide')) {\n      this.$element.trigger(e)\n      if (e.isDefaultPrevented()) return\n      $next.addClass(type)\n      $next[0].offsetWidth // force reflow\n      $active.addClass(direction)\n      $next.addClass(direction)\n      $active\n        .one($.support.transition.end, function () {\n          $next.removeClass([type, direction].join(' ')).addClass('active')\n          $active.removeClass(['active', direction].join(' '))\n          that.sliding = false\n          setTimeout(function () { that.$element.trigger('slid') }, 0)\n        })\n        .emulateTransitionEnd(600)\n    } else {\n      this.$element.trigger(e)\n      if (e.isDefaultPrevented()) return\n      $active.removeClass('active')\n      $next.addClass('active')\n      this.sliding = false\n      this.$element.trigger('slid')\n    }\n\n    isCycling && this.cycle()\n\n    return this\n  }\n\n\n  // CAROUSEL PLUGIN DEFINITION\n  // ==========================\n\n  var old = $.fn.carousel\n\n  $.fn.carousel = function (option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.carousel')\n      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)\n      var action  = typeof option == 'string' ? option : options.slide\n\n      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))\n      if (typeof option == 'number') data.to(option)\n      else if (action) data[action]()\n      else if (options.interval) data.pause().cycle()\n    })\n  }\n\n  $.fn.carousel.Constructor = Carousel\n\n\n  // CAROUSEL NO CONFLICT\n  // ====================\n\n  $.fn.carousel.noConflict = function () {\n    $.fn.carousel = old\n    return this\n  }\n\n\n  // CAROUSEL DATA-API\n  // =================\n\n  $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {\n    var $this   = $(this), href\n    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '')) //strip for ie7\n    var options = $.extend({}, $target.data(), $this.data())\n    var slideIndex = $this.attr('data-slide-to')\n    if (slideIndex) options.interval = false\n\n    $target.carousel(options)\n\n    if (slideIndex = $this.attr('data-slide-to')) {\n      $target.data('bs.carousel').to(slideIndex)\n    }\n\n    e.preventDefault()\n  })\n\n  $(window).on('load', function () {\n    $('[data-ride=\"carousel\"]').each(function () {\n      var $carousel = $(this)\n      $carousel.carousel($carousel.data())\n    })\n  })\n\n}(window.jQuery);\n","collapse.js":"/* ========================================================================\n * Bootstrap: collapse.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#collapse\n * ========================================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // COLLAPSE PUBLIC CLASS DEFINITION\n  // ================================\n\n  var Collapse = function (element, options) {\n    this.$element      = $(element)\n    this.options       = $.extend({}, Collapse.DEFAULTS, options)\n    this.transitioning = null\n\n    if (this.options.parent) this.$parent = $(this.options.parent)\n    if (this.options.toggle) this.toggle()\n  }\n\n  Collapse.DEFAULTS = {\n    toggle: true\n  }\n\n  Collapse.prototype.dimension = function () {\n    var hasWidth = this.$element.hasClass('width')\n    return hasWidth ? 'width' : 'height'\n  }\n\n  Collapse.prototype.show = function () {\n    if (this.transitioning || this.$element.hasClass('in')) return\n\n    var startEvent = $.Event('show.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    var actives = this.$parent && this.$parent.find('> .panel > .in')\n\n    if (actives && actives.length) {\n      var hasData = actives.data('bs.collapse')\n      if (hasData && hasData.transitioning) return\n      actives.collapse('hide')\n      hasData || actives.data('bs.collapse', null)\n    }\n\n    var dimension = this.dimension()\n\n    this.$element\n      .removeClass('collapse')\n      .addClass('collapsing')\n      [dimension](0)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.$element\n        .removeClass('collapsing')\n        .addClass('in')\n        [dimension]('auto')\n      this.transitioning = 0\n      this.$element.trigger('shown.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    var scrollSize = $.camelCase(['scroll', dimension].join('-'))\n\n    this.$element\n      .one($.support.transition.end, $.proxy(complete, this))\n      .emulateTransitionEnd(350)\n      [dimension](this.$element[0][scrollSize])\n  }\n\n  Collapse.prototype.hide = function () {\n    if (this.transitioning || !this.$element.hasClass('in')) return\n\n    var startEvent = $.Event('hide.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    var dimension = this.dimension()\n\n    this.$element\n      [dimension](this.$element[dimension]())\n      [0].offsetHeight\n\n    this.$element\n      .addClass('collapsing')\n      .removeClass('collapse')\n      .removeClass('in')\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.transitioning = 0\n      this.$element\n        .trigger('hidden.bs.collapse')\n        .removeClass('collapsing')\n        .addClass('collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    this.$element\n      [dimension](0)\n      .one($.support.transition.end, $.proxy(complete, this))\n      .emulateTransitionEnd(350)\n  }\n\n  Collapse.prototype.toggle = function () {\n    this[this.$element.hasClass('in') ? 'hide' : 'show']()\n  }\n\n\n  // COLLAPSE PLUGIN DEFINITION\n  // ==========================\n\n  var old = $.fn.collapse\n\n  $.fn.collapse = function (option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.collapse')\n      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.collapse.Constructor = Collapse\n\n\n  // COLLAPSE NO CONFLICT\n  // ====================\n\n  $.fn.collapse.noConflict = function () {\n    $.fn.collapse = old\n    return this\n  }\n\n\n  // COLLAPSE DATA-API\n  // =================\n\n  $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) {\n    var $this   = $(this), href\n    var target  = $this.attr('data-target')\n        || e.preventDefault()\n        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '') //strip for ie7\n    var $target = $(target)\n    var data    = $target.data('bs.collapse')\n    var option  = data ? 'toggle' : $this.data()\n    var parent  = $this.attr('data-parent')\n    var $parent = parent && $(parent)\n\n    if (!data || !data.transitioning) {\n      if ($parent) $parent.find('[data-toggle=collapse][data-parent=\"' + parent + '\"]').not($this).addClass('collapsed')\n      $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')\n    }\n\n    $target.collapse(option)\n  })\n\n}(window.jQuery);\n","dropdown.js":"/* ========================================================================\n * Bootstrap: dropdown.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#dropdowns\n * ========================================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // DROPDOWN CLASS DEFINITION\n  // =========================\n\n  var backdrop = '.dropdown-backdrop'\n  var toggle   = '[data-toggle=dropdown]'\n  var Dropdown = function (element) {\n    var $el = $(element).on('click.bs.dropdown', this.toggle)\n  }\n\n  Dropdown.prototype.toggle = function (e) {\n    var $this = $(this)\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    clearMenus()\n\n    if (!isActive) {\n      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {\n        // if mobile we we use a backdrop because click events don't delegate\n        $('<div class=\"dropdown-backdrop\"/>').insertAfter($(this)).on('click', clearMenus)\n      }\n\n      $parent.trigger(e = $.Event('show.bs.dropdown'))\n\n      if (e.isDefaultPrevented()) return\n\n      $parent\n        .toggleClass('open')\n        .trigger('shown.bs.dropdown')\n\n      $this.focus()\n    }\n\n    return false\n  }\n\n  Dropdown.prototype.keydown = function (e) {\n    if (!/(38|40|27)/.test(e.keyCode)) return\n\n    var $this = $(this)\n\n    e.preventDefault()\n    e.stopPropagation()\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    if (!isActive || (isActive && e.keyCode == 27)) {\n      if (e.which == 27) $parent.find(toggle).focus()\n      return $this.click()\n    }\n\n    var $items = $('[role=menu] li:not(.divider):visible a', $parent)\n\n    if (!$items.length) return\n\n    var index = $items.index($items.filter(':focus'))\n\n    if (e.keyCode == 38 && index > 0)                 index--                        // up\n    if (e.keyCode == 40 && index < $items.length - 1) index++                        // down\n    if (!~index)                                      index=0\n\n    $items.eq(index).focus()\n  }\n\n  function clearMenus() {\n    $(backdrop).remove()\n    $(toggle).each(function (e) {\n      var $parent = getParent($(this))\n      if (!$parent.hasClass('open')) return\n      $parent.trigger(e = $.Event('hide.bs.dropdown'))\n      if (e.isDefaultPrevented()) return\n      $parent.removeClass('open').trigger('hidden.bs.dropdown')\n    })\n  }\n\n  function getParent($this) {\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\\s]*$)/, '') //strip for ie7\n    }\n\n    var $parent = selector && $(selector)\n\n    return $parent && $parent.length ? $parent : $this.parent()\n  }\n\n\n  // DROPDOWN PLUGIN DEFINITION\n  // ==========================\n\n  var old = $.fn.dropdown\n\n  $.fn.dropdown = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('dropdown')\n\n      if (!data) $this.data('dropdown', (data = new Dropdown(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  $.fn.dropdown.Constructor = Dropdown\n\n\n  // DROPDOWN NO CONFLICT\n  // ====================\n\n  $.fn.dropdown.noConflict = function () {\n    $.fn.dropdown = old\n    return this\n  }\n\n\n  // APPLY TO STANDARD DROPDOWN ELEMENTS\n  // ===================================\n\n  $(document)\n    .on('click.bs.dropdown.data-api', clearMenus)\n    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })\n    .on('click.bs.dropdown.data-api'  , toggle, Dropdown.prototype.toggle)\n    .on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)\n\n}(window.jQuery);\n","modal.js":"/* ========================================================================\n * Bootstrap: modal.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#modals\n * ========================================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // MODAL CLASS DEFINITION\n  // ======================\n\n  var Modal = function (element, options) {\n    this.options   = options\n    this.$element  = $(element)\n    this.$backdrop =\n    this.isShown   = null\n\n    if (this.options.remote) this.$element.load(this.options.remote)\n  }\n\n  Modal.DEFAULTS = {\n      backdrop: true\n    , keyboard: true\n    , show: true\n  }\n\n  Modal.prototype.toggle = function (_relatedTarget) {\n    return this[!this.isShown ? 'show' : 'hide'](_relatedTarget)\n  }\n\n  Modal.prototype.show = function (_relatedTarget) {\n    var that = this\n    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })\n\n    this.$element.trigger(e)\n\n    if (this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = true\n\n    this.escape()\n\n    this.$element.on('click.dismiss.modal', '[data-dismiss=\"modal\"]', $.proxy(this.hide, this))\n\n    this.backdrop(function () {\n      var transition = $.support.transition && that.$element.hasClass('fade')\n\n      if (!that.$element.parent().length) {\n        that.$element.appendTo(document.body) // don't move modals dom position\n      }\n\n      that.$element.show()\n\n      if (transition) {\n        that.$element[0].offsetWidth // force reflow\n      }\n\n      that.$element\n        .addClass('in')\n        .attr('aria-hidden', false)\n\n      that.enforceFocus()\n\n      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })\n\n      transition ?\n        that.$element.find('.modal-dialog') // wait for modal to slide in\n          .one($.support.transition.end, function () {\n            that.$element.focus().trigger(e)\n          })\n          .emulateTransitionEnd(300) :\n        that.$element.focus().trigger(e)\n    })\n  }\n\n  Modal.prototype.hide = function (e) {\n    if (e) e.preventDefault()\n\n    e = $.Event('hide.bs.modal')\n\n    this.$element.trigger(e)\n\n    if (!this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = false\n\n    this.escape()\n\n    $(document).off('focusin.bs.modal')\n\n    this.$element\n      .removeClass('in')\n      .attr('aria-hidden', true)\n      .off('click.dismiss.modal')\n\n    $.support.transition && this.$element.hasClass('fade') ?\n      this.$element\n        .one($.support.transition.end, $.proxy(this.hideModal, this))\n        .emulateTransitionEnd(300) :\n      this.hideModal()\n  }\n\n  Modal.prototype.enforceFocus = function () {\n    $(document)\n      .off('focusin.bs.modal') // guard against infinite focus loop\n      .on('focusin.bs.modal', $.proxy(function (e) {\n        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {\n          this.$element.focus()\n        }\n      }, this))\n  }\n\n  Modal.prototype.escape = function () {\n    if (this.isShown && this.options.keyboard) {\n      this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {\n        e.which == 27 && this.hide()\n      }, this))\n    } else if (!this.isShown) {\n      this.$element.off('keyup.dismiss.bs.modal')\n    }\n  }\n\n  Modal.prototype.hideModal = function () {\n    var that = this\n    this.$element.hide()\n    this.backdrop(function () {\n      that.removeBackdrop()\n      that.$element.trigger('hidden.bs.modal')\n    })\n  }\n\n  Modal.prototype.removeBackdrop = function () {\n    this.$backdrop && this.$backdrop.remove()\n    this.$backdrop = null\n  }\n\n  Modal.prototype.backdrop = function (callback) {\n    var that    = this\n    var animate = this.$element.hasClass('fade') ? 'fade' : ''\n\n    if (this.isShown && this.options.backdrop) {\n      var doAnimate = $.support.transition && animate\n\n      this.$backdrop = $('<div class=\"modal-backdrop ' + animate + '\" />')\n        .appendTo(document.body)\n\n      this.$element.on('click.dismiss.modal', $.proxy(function (e) {\n        if (e.target !== e.currentTarget) return\n        this.options.backdrop == 'static'\n          ? this.$element[0].focus.call(this.$element[0])\n          : this.hide.call(this)\n      }, this))\n\n      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow\n\n      this.$backdrop.addClass('in')\n\n      if (!callback) return\n\n      doAnimate ?\n        this.$backdrop\n          .one($.support.transition.end, callback)\n          .emulateTransitionEnd(150) :\n        callback()\n\n    } else if (!this.isShown && this.$backdrop) {\n      this.$backdrop.removeClass('in')\n\n      $.support.transition && this.$element.hasClass('fade')?\n        this.$backdrop\n          .one($.support.transition.end, callback)\n          .emulateTransitionEnd(150) :\n        callback()\n\n    } else if (callback) {\n      callback()\n    }\n  }\n\n\n  // MODAL PLUGIN DEFINITION\n  // =======================\n\n  var old = $.fn.modal\n\n  $.fn.modal = function (option, _relatedTarget) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.modal')\n      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))\n      if (typeof option == 'string') data[option](_relatedTarget)\n      else if (options.show) data.show(_relatedTarget)\n    })\n  }\n\n  $.fn.modal.Constructor = Modal\n\n\n  // MODAL NO CONFLICT\n  // =================\n\n  $.fn.modal.noConflict = function () {\n    $.fn.modal = old\n    return this\n  }\n\n\n  // MODAL DATA-API\n  // ==============\n\n  $(document).on('click.bs.modal.data-api', '[data-toggle=\"modal\"]', function (e) {\n    var $this   = $(this)\n    var href    = $this.attr('href')\n    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\\s]+$)/, ''))) //strip for ie7\n    var option  = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())\n\n    e.preventDefault()\n\n    $target\n      .modal(option, this)\n      .one('hide', function () {\n        $this.is(':visible') && $this.focus()\n      })\n  })\n\n  $(document)\n    .on('show.bs.modal',  '.modal', function () { $(document.body).addClass('modal-open') })\n    .on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })\n\n}(window.jQuery);\n","popover.js":"/* ========================================================================\n * Bootstrap: popover.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#popovers\n * ========================================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // POPOVER PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Popover = function (element, options) {\n    this.init('popover', element, options)\n  }\n\n  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')\n\n  Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, {\n    placement: 'right'\n  , trigger: 'click'\n  , content: ''\n  , template: '<div class=\"popover\"><div class=\"arrow\"></div><h3 class=\"popover-title\"></h3><div class=\"popover-content\"></div></div>'\n  })\n\n\n  // NOTE: POPOVER EXTENDS tooltip.js\n  // ================================\n\n  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)\n\n  Popover.prototype.constructor = Popover\n\n  Popover.prototype.getDefaults = function () {\n    return Popover.DEFAULTS\n  }\n\n  Popover.prototype.setContent = function () {\n    var $tip    = this.tip()\n    var title   = this.getTitle()\n    var content = this.getContent()\n\n    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)\n    $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)\n\n    $tip.removeClass('fade top bottom left right in')\n\n    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do\n    // this manually by checking the contents.\n    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()\n  }\n\n  Popover.prototype.hasContent = function () {\n    return this.getTitle() || this.getContent()\n  }\n\n  Popover.prototype.getContent = function () {\n    var $e = this.$element\n    var o  = this.options\n\n    return $e.attr('data-content')\n      || (typeof o.content == 'function' ?\n            o.content.call($e[0]) :\n            o.content)\n  }\n\n  Popover.prototype.arrow = function () {\n    return this.$arrow = this.$arrow || this.tip().find('.arrow')\n  }\n\n  Popover.prototype.tip = function () {\n    if (!this.$tip) this.$tip = $(this.options.template)\n    return this.$tip\n  }\n\n\n  // POPOVER PLUGIN DEFINITION\n  // =========================\n\n  var old = $.fn.popover\n\n  $.fn.popover = function (option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.popover')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.popover.Constructor = Popover\n\n\n  // POPOVER NO CONFLICT\n  // ===================\n\n  $.fn.popover.noConflict = function () {\n    $.fn.popover = old\n    return this\n  }\n\n}(window.jQuery);\n","scrollspy.js":"/* ========================================================================\n * Bootstrap: scrollspy.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#scrollspy\n * ========================================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // SCROLLSPY CLASS DEFINITION\n  // ==========================\n\n  function ScrollSpy(element, options) {\n    var href\n    var process  = $.proxy(this.process, this)\n\n    this.$element       = $(element).is('body') ? $(window) : $(element)\n    this.$body          = $('body')\n    this.$scrollElement = this.$element.on('scroll.bs.scroll-spy.data-api', process)\n    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)\n    this.selector       = (this.options.target\n      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '')) //strip for ie7\n      || '') + ' .nav li > a'\n    this.offsets        = $([])\n    this.targets        = $([])\n    this.activeTarget   = null\n\n    this.refresh()\n    this.process()\n  }\n\n  ScrollSpy.DEFAULTS = {\n    offset: 10\n  }\n\n  ScrollSpy.prototype.refresh = function () {\n    var offsetMethod = this.$element[0] == window ? 'offset' : 'position'\n\n    this.offsets = $([])\n    this.targets = $([])\n\n    var self     = this\n    var $targets = this.$body\n      .find(this.selector)\n      .map(function () {\n        var $el   = $(this)\n        var href  = $el.data('target') || $el.attr('href')\n        var $href = /^#\\w/.test(href) && $(href)\n\n        return ($href\n          && $href.length\n          && [[ $href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]]) || null\n      })\n      .sort(function (a, b) { return a[0] - b[0] })\n      .each(function () {\n        self.offsets.push(this[0])\n        self.targets.push(this[1])\n      })\n  }\n\n  ScrollSpy.prototype.process = function () {\n    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset\n    var scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight\n    var maxScroll    = scrollHeight - this.$scrollElement.height()\n    var offsets      = this.offsets\n    var targets      = this.targets\n    var activeTarget = this.activeTarget\n    var i\n\n    if (scrollTop >= maxScroll) {\n      return activeTarget != (i = targets.last()[0]) && this.activate(i)\n    }\n\n    for (i = offsets.length; i--;) {\n      activeTarget != targets[i]\n        && scrollTop >= offsets[i]\n        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])\n        && this.activate( targets[i] )\n    }\n  }\n\n  ScrollSpy.prototype.activate = function (target) {\n    this.activeTarget = target\n\n    $(this.selector)\n      .parents('.active')\n      .removeClass('active')\n\n    var selector = this.selector\n      + '[data-target=\"' + target + '\"],'\n      + this.selector + '[href=\"' + target + '\"]'\n\n    var active = $(selector)\n      .parents('li')\n      .addClass('active')\n\n    if (active.parent('.dropdown-menu').length)  {\n      active = active\n        .closest('li.dropdown')\n        .addClass('active')\n    }\n\n    active.trigger('activate')\n  }\n\n\n  // SCROLLSPY PLUGIN DEFINITION\n  // ===========================\n\n  var old = $.fn.scrollspy\n\n  $.fn.scrollspy = function (option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.scrollspy')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.scrollspy.Constructor = ScrollSpy\n\n\n  // SCROLLSPY NO CONFLICT\n  // =====================\n\n  $.fn.scrollspy.noConflict = function () {\n    $.fn.scrollspy = old\n    return this\n  }\n\n\n  // SCROLLSPY DATA-API\n  // ==================\n\n  $(window).on('load', function () {\n    $('[data-spy=\"scroll\"]').each(function () {\n      var $spy = $(this)\n      $spy.scrollspy($spy.data())\n    })\n  })\n\n}(window.jQuery);\n","tab.js":"/* ========================================================================\n * Bootstrap: tab.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#tabs\n * ========================================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // TAB CLASS DEFINITION\n  // ====================\n\n  var Tab = function (element) {\n    this.element = $(element)\n  }\n\n  Tab.prototype.show = function () {\n    var $this    = this.element\n    var $ul      = $this.closest('ul:not(.dropdown-menu)')\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') //strip for ie7\n    }\n\n    if ($this.parent('li').hasClass('active')) return\n\n    var previous = $ul.find('.active:last a')[0]\n    var e        = $.Event('show.bs.tab', {\n      relatedTarget: previous\n    })\n\n    $this.trigger(e)\n\n    if (e.isDefaultPrevented()) return\n\n    var $target = $(selector)\n\n    this.activate($this.parent('li'), $ul)\n    this.activate($target, $target.parent(), function () {\n      $this.trigger({\n        type: 'shown.bs.tab'\n      , relatedTarget: previous\n      })\n    })\n  }\n\n  Tab.prototype.activate = function (element, container, callback) {\n    var $active    = container.find('> .active')\n    var transition = callback\n      && $.support.transition\n      && $active.hasClass('fade')\n\n    function next() {\n      $active\n        .removeClass('active')\n        .find('> .dropdown-menu > .active')\n        .removeClass('active')\n\n      element.addClass('active')\n\n      if (transition) {\n        element[0].offsetWidth // reflow for transition\n        element.addClass('in')\n      } else {\n        element.removeClass('fade')\n      }\n\n      if (element.parent('.dropdown-menu')) {\n        element.closest('li.dropdown').addClass('active')\n      }\n\n      callback && callback()\n    }\n\n    transition ?\n      $active\n        .one($.support.transition.end, next)\n        .emulateTransitionEnd(150) :\n      next()\n\n    $active.removeClass('in')\n  }\n\n\n  // TAB PLUGIN DEFINITION\n  // =====================\n\n  var old = $.fn.tab\n\n  $.fn.tab = function ( option ) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.tab')\n\n      if (!data) $this.data('bs.tab', (data = new Tab(this)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.tab.Constructor = Tab\n\n\n  // TAB NO CONFLICT\n  // ===============\n\n  $.fn.tab.noConflict = function () {\n    $.fn.tab = old\n    return this\n  }\n\n\n  // TAB DATA-API\n  // ============\n\n  $(document).on('click.bs.tab.data-api', '[data-toggle=\"tab\"], [data-toggle=\"pill\"]', function (e) {\n    e.preventDefault()\n    $(this).tab('show')\n  })\n\n}(window.jQuery);\n","tooltip.js":"/* ========================================================================\n * Bootstrap: tooltip.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#tooltip\n * Inspired by the original jQuery.tipsy by Jason Frame\n * ========================================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // TOOLTIP PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Tooltip = function (element, options) {\n    this.type       =\n    this.options    =\n    this.enabled    =\n    this.timeout    =\n    this.hoverState =\n    this.$element   = null\n\n    this.init('tooltip', element, options)\n  }\n\n  Tooltip.DEFAULTS = {\n    animation: true\n  , placement: 'top'\n  , selector: false\n  , template: '<div class=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>'\n  , trigger: 'hover focus'\n  , title: ''\n  , delay: 0\n  , html: false\n  , container: false\n  }\n\n  Tooltip.prototype.init = function (type, element, options) {\n    this.enabled  = true\n    this.type     = type\n    this.$element = $(element)\n    this.options  = this.getOptions(options)\n\n    var triggers = this.options.trigger.split(' ')\n\n    for (var i = triggers.length; i--;) {\n      var trigger = triggers[i]\n\n      if (trigger == 'click') {\n        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))\n      } else if (trigger != 'manual') {\n        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focus'\n        var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'\n\n        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))\n        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))\n      }\n    }\n\n    this.options.selector ?\n      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :\n      this.fixTitle()\n  }\n\n  Tooltip.prototype.getDefaults = function () {\n    return Tooltip.DEFAULTS\n  }\n\n  Tooltip.prototype.getOptions = function (options) {\n    options = $.extend({}, this.getDefaults(), this.$element.data(), options)\n\n    if (options.delay && typeof options.delay == 'number') {\n      options.delay = {\n        show: options.delay\n      , hide: options.delay\n      }\n    }\n\n    return options\n  }\n\n  Tooltip.prototype.getDelegateOptions = function () {\n    var options  = {}\n    var defaults = this.getDefaults()\n\n    this._options && $.each(this._options, function (key, value) {\n      if (defaults[key] != value) options[key] = value\n    })\n\n    return options\n  }\n\n  Tooltip.prototype.enter = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'in'\n\n    if (!self.options.delay || !self.options.delay.show) return self.show()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'in') self.show()\n    }, self.options.delay.show)\n  }\n\n  Tooltip.prototype.leave = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'out'\n\n    if (!self.options.delay || !self.options.delay.hide) return self.hide()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'out') self.hide()\n    }, self.options.delay.hide)\n  }\n\n  Tooltip.prototype.show = function () {\n    var e = $.Event('show.bs.'+ this.type)\n\n    if (this.hasContent() && this.enabled) {\n      this.$element.trigger(e)\n\n      if (e.isDefaultPrevented()) return\n\n      var $tip = this.tip()\n\n      this.setContent()\n\n      if (this.options.animation) $tip.addClass('fade')\n\n      var placement = typeof this.options.placement == 'function' ?\n        this.options.placement.call(this, $tip[0], this.$element[0]) :\n        this.options.placement\n\n      var autoToken = /\\s?auto?\\s?/i\n      var autoPlace = autoToken.test(placement)\n      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'\n\n      $tip\n        .detach()\n        .css({ top: 0, left: 0, display: 'block' })\n        .addClass(placement)\n\n      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)\n\n      var pos          = this.getPosition()\n      var actualWidth  = $tip[0].offsetWidth\n      var actualHeight = $tip[0].offsetHeight\n\n      if (autoPlace) {\n        var $parent = this.$element.parent()\n\n        var orgPlacement = placement\n        var docScroll    = document.documentElement.scrollTop || document.body.scrollTop\n        var parentWidth  = this.options.container == 'body' ? window.innerWidth  : $parent.outerWidth()\n        var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()\n        var parentLeft   = this.options.container == 'body' ? 0 : $parent.offset().left\n\n        placement = placement == 'bottom' && pos.top   + pos.height  + actualHeight - docScroll > parentHeight  ? 'top'    :\n                    placement == 'top'    && pos.top   - docScroll   - actualHeight < 0                         ? 'bottom' :\n                    placement == 'right'  && pos.right + actualWidth > parentWidth                              ? 'left'   :\n                    placement == 'left'   && pos.left  - actualWidth < parentLeft                               ? 'right'  :\n                    placement\n\n        $tip\n          .removeClass(orgPlacement)\n          .addClass(placement)\n      }\n\n      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)\n\n      this.applyPlacement(calculatedOffset, placement)\n      this.$element.trigger('shown.bs.' + this.type)\n    }\n  }\n\n  Tooltip.prototype.applyPlacement = function(offset, placement) {\n    var replace\n    var $tip   = this.tip()\n    var width  = $tip[0].offsetWidth\n    var height = $tip[0].offsetHeight\n\n    // manually read margins because getBoundingClientRect includes difference\n    var marginTop = parseInt($tip.css('margin-top'), 10)\n    var marginLeft = parseInt($tip.css('margin-left'), 10)\n\n    // we must check for NaN for ie 8/9\n    if (isNaN(marginTop))  marginTop  = 0\n    if (isNaN(marginLeft)) marginLeft = 0\n\n    offset.top  = offset.top  + marginTop\n    offset.left = offset.left + marginLeft\n\n    $tip\n      .offset(offset)\n      .addClass('in')\n\n    // check to see if placing tip in new offset caused the tip to resize itself\n    var actualWidth  = $tip[0].offsetWidth\n    var actualHeight = $tip[0].offsetHeight\n\n    if (placement == 'top' && actualHeight != height) {\n      replace = true\n      offset.top = offset.top + height - actualHeight\n    }\n\n    if (/bottom|top/.test(placement)) {\n      var delta = 0\n\n      if (offset.left < 0) {\n        delta       = offset.left * -2\n        offset.left = 0\n\n        $tip.offset(offset)\n\n        actualWidth  = $tip[0].offsetWidth\n        actualHeight = $tip[0].offsetHeight\n      }\n\n      this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')\n    } else {\n      this.replaceArrow(actualHeight - height, actualHeight, 'top')\n    }\n\n    if (replace) $tip.offset(offset)\n  }\n\n  Tooltip.prototype.replaceArrow = function(delta, dimension, position) {\n    this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + \"%\") : '')\n  }\n\n  Tooltip.prototype.setContent = function () {\n    var $tip  = this.tip()\n    var title = this.getTitle()\n\n    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)\n    $tip.removeClass('fade in top bottom left right')\n  }\n\n  Tooltip.prototype.hide = function () {\n    var that = this\n    var $tip = this.tip()\n    var e    = $.Event('hide.bs.' + this.type)\n\n    function complete() {\n      if (that.hoverState != 'in') $tip.detach()\n    }\n\n    this.$element.trigger(e)\n\n    if (e.isDefaultPrevented()) return\n\n    $tip.removeClass('in')\n\n    $.support.transition && this.$tip.hasClass('fade') ?\n      $tip\n        .one($.support.transition.end, complete)\n        .emulateTransitionEnd(150) :\n      complete()\n\n    this.$element.trigger('hidden.bs.' + this.type)\n\n    return this\n  }\n\n  Tooltip.prototype.fixTitle = function () {\n    var $e = this.$element\n    if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {\n      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')\n    }\n  }\n\n  Tooltip.prototype.hasContent = function () {\n    return this.getTitle()\n  }\n\n  Tooltip.prototype.getPosition = function () {\n    var el = this.$element[0]\n    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {\n      width: el.offsetWidth\n    , height: el.offsetHeight\n    }, this.$element.offset())\n  }\n\n  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {\n    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :\n           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :\n           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :\n        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }\n  }\n\n  Tooltip.prototype.getTitle = function () {\n    var title\n    var $e = this.$element\n    var o  = this.options\n\n    title = $e.attr('data-original-title')\n      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)\n\n    return title\n  }\n\n  Tooltip.prototype.tip = function () {\n    return this.$tip = this.$tip || $(this.options.template)\n  }\n\n  Tooltip.prototype.arrow = function () {\n    return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')\n  }\n\n  Tooltip.prototype.validate = function () {\n    if (!this.$element[0].parentNode) {\n      this.hide()\n      this.$element = null\n      this.options  = null\n    }\n  }\n\n  Tooltip.prototype.enable = function () {\n    this.enabled = true\n  }\n\n  Tooltip.prototype.disable = function () {\n    this.enabled = false\n  }\n\n  Tooltip.prototype.toggleEnabled = function () {\n    this.enabled = !this.enabled\n  }\n\n  Tooltip.prototype.toggle = function (e) {\n    var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this\n    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)\n  }\n\n  Tooltip.prototype.destroy = function () {\n    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)\n  }\n\n\n  // TOOLTIP PLUGIN DEFINITION\n  // =========================\n\n  var old = $.fn.tooltip\n\n  $.fn.tooltip = function (option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.tooltip')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.tooltip.Constructor = Tooltip\n\n\n  // TOOLTIP NO CONFLICT\n  // ===================\n\n  $.fn.tooltip.noConflict = function () {\n    $.fn.tooltip = old\n    return this\n  }\n\n}(window.jQuery);\n","transition.js":"/* ========================================================================\n * Bootstrap: transition.js v3.0.0\n * http://twbs.github.com/bootstrap/javascript.html#transitions\n * ========================================================================\n * Copyright 2013 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================================== */\n\n\n+function ($) { \"use strict\";\n\n  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)\n  // ============================================================\n\n  function transitionEnd() {\n    var el = document.createElement('bootstrap')\n\n    var transEndEventNames = {\n      'WebkitTransition' : 'webkitTransitionEnd'\n    , 'MozTransition'    : 'transitionend'\n    , 'OTransition'      : 'oTransitionEnd otransitionend'\n    , 'transition'       : 'transitionend'\n    }\n\n    for (var name in transEndEventNames) {\n      if (el.style[name] !== undefined) {\n        return { end: transEndEventNames[name] }\n      }\n    }\n  }\n\n  // http://blog.alexmaccaw.com/css-transitions\n  $.fn.emulateTransitionEnd = function (duration) {\n    var called = false, $el = this\n    $(this).one($.support.transition.end, function () { called = true })\n    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }\n    setTimeout(callback, duration)\n    return this\n  }\n\n  $(function () {\n    $.support.transition = transitionEnd()\n  })\n\n}(window.jQuery);\n"}
+var __less = {"alerts.less":"//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n  padding: @alert-padding;\n  margin-bottom: @line-height-computed;\n  border: 1px solid transparent;\n  border-radius: @alert-border-radius;\n\n  // Headings for larger alerts\n  h4 {\n    margin-top: 0;\n    // Specified for the h4 to prevent conflicts of changing @headingsColor\n    color: inherit;\n  }\n  // Provide class for links that match alerts\n  .alert-link {\n    font-weight: @alert-link-font-weight;\n  }\n\n  // Improve alignment and spacing of inner content\n  > p,\n  > ul {\n    margin-bottom: 0;\n  }\n  > p + p {\n    margin-top: 5px;\n  }\n}\n\n// Dismissable alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable {\n padding-right: (@alert-padding + 20);\n\n  // Adjust close link position\n  .close {\n    position: relative;\n    top: -2px;\n    right: -21px;\n    color: inherit;\n  }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n  .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n.alert-info {\n  .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n.alert-warning {\n  .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n.alert-danger {\n  .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","badges.less":"//\n// Badges\n// --------------------------------------------------\n\n\n// Base classes\n.badge {\n  display: inline-block;\n  min-width: 10px;\n  padding: 3px 7px;\n  font-size: @font-size-small;\n  font-weight: @badge-font-weight;\n  color: @badge-color;\n  line-height: @badge-line-height;\n  vertical-align: baseline;\n  white-space: nowrap;\n  text-align: center;\n  background-color: @badge-bg;\n  border-radius: @badge-border-radius;\n\n  // Empty badges collapse automatically (not available in IE8)\n  &:empty {\n    display: none;\n  }\n}\n\n// Hover state, but only for links\na.badge {\n  &:hover,\n  &:focus {\n    color: @badge-link-hover-color;\n    text-decoration: none;\n    cursor: pointer;\n  }\n}\n\n// Quick fix for labels/badges in buttons\n.btn .badge {\n  position: relative;\n  top: -1px;\n}\n\n// Account for counters in navs\na.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n  color: @badge-active-color;\n  background-color: @badge-active-bg;\n}\n.nav-pills > li > a > .badge {\n  margin-left: 3px;\n}\n","bootstrap.less":"/*!\n * Bootstrap v3.0.0\n *\n * Copyright 2013 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world by @mdo and @fat.\n */\n\n// Core variables and mixins\n@import \"variables.less\";\n@import \"mixins.less\";\n\n// Reset\n@import \"normalize.less\";\n@import \"print.less\";\n\n// Core CSS\n@import \"scaffolding.less\";\n@import \"type.less\";\n@import \"code.less\";\n@import \"grid.less\";\n@import \"tables.less\";\n@import \"forms.less\";\n@import \"buttons.less\";\n\n// Components\n@import \"component-animations.less\";\n@import \"glyphicons.less\";\n@import \"dropdowns.less\";\n@import \"button-groups.less\";\n@import \"input-groups.less\";\n@import \"navs.less\";\n@import \"navbar.less\";\n@import \"breadcrumbs.less\";\n@import \"pagination.less\";\n@import \"pager.less\";\n@import \"labels.less\";\n@import \"badges.less\";\n@import \"jumbotron.less\";\n@import \"thumbnails.less\";\n@import \"alerts.less\";\n@import \"progress-bars.less\";\n@import \"media.less\";\n@import \"list-group.less\";\n@import \"panels.less\";\n@import \"wells.less\";\n@import \"close.less\";\n\n// Components w/ JavaScript\n@import \"modals.less\";\n@import \"tooltip.less\";\n@import \"popovers.less\";\n@import \"carousel.less\";\n\n// Utility classes\n@import \"utilities.less\";\n@import \"responsive-utilities.less\";\n","breadcrumbs.less":"//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n  padding: 8px 15px;\n  margin-bottom: @line-height-computed;\n  list-style: none;\n  background-color: @breadcrumb-bg;\n  border-radius: @border-radius-base;\n  > li {\n    display: inline-block;\n    &+li:before {\n      content: \"/\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n      padding: 0 5px;\n      color: @breadcrumb-color;\n    }\n  }\n  > .active {\n    color: @breadcrumb-active-color;\n  }\n}\n","button-groups.less":"//\n// Button groups\n// --------------------------------------------------\n\n// Button carets\n//\n// Match the button text color to the arrow/caret for indicating dropdown-ness.\n\n.caret {\n  .btn-default & {\n    border-top-color: @btn-default-color;\n  }\n  .btn-primary &,\n  .btn-success &,\n  .btn-warning &,\n  .btn-danger &,\n  .btn-info & {\n    border-top-color: #fff;\n  }\n}\n.dropup {\n  & .btn-default .caret {\n    border-bottom-color: @btn-default-color;\n  }\n  .btn-primary,\n  .btn-success,\n  .btn-warning,\n  .btn-danger,\n  .btn-info {\n   .caret {\n      border-bottom-color: #fff;\n    }\n  }\n}\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-block;\n  vertical-align: middle; // match .btn alignment given font-size hack above\n  > .btn {\n    position: relative;\n    float: left;\n    // Bring the \"active\" button to the front\n    &:hover,\n    &:focus,\n    &:active,\n    &.active {\n      z-index: 2;\n    }\n    &:focus {\n      // Remove focus outline when dropdown JS adds it after closing the menu\n      outline: none;\n    }\n  }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n  .btn + .btn,\n  .btn + .btn-group,\n  .btn-group + .btn,\n  .btn-group + .btn-group {\n    margin-left: -1px;\n  }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n  .clearfix();\n\n  .btn-group {\n    float: left;\n  }\n  // Space out series of button groups\n  > .btn,\n  > .btn-group {\n    + .btn,\n    + .btn-group {\n      margin-left: 5px;\n    }\n  }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n  border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n  margin-left: 0;\n  &:not(:last-child):not(.dropdown-toggle) {\n    .border-right-radius(0);\n  }\n}\n// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n  .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n  float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group > .btn-group:first-child {\n  > .btn:last-child,\n  > .dropdown-toggle {\n    .border-right-radius(0);\n  }\n}\n.btn-group > .btn-group:last-child > .btn:first-child {\n  .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n  outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { .btn-xs(); }\n.btn-group-sm > .btn { .btn-sm(); }\n.btn-group-lg > .btn { .btn-lg(); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n  padding-left: 8px;\n  padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n  padding-left: 12px;\n  padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n  .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n}\n\n\n// Reposition the caret\n.btn .caret {\n  margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n  border-width: @caret-width-large @caret-width-large 0;\n  border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n  border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n  > .btn,\n  > .btn-group {\n    display: block;\n    float: none;\n    width: 100%;\n    max-width: 100%;\n  }\n\n  // Clear floats so dropdown menus can be properly placed\n  > .btn-group {\n    .clearfix();\n    > .btn {\n      float: none;\n    }\n  }\n\n  > .btn + .btn,\n  > .btn + .btn-group,\n  > .btn-group + .btn,\n  > .btn-group + .btn-group {\n    margin-top: -1px;\n    margin-left: 0;\n  }\n}\n\n.btn-group-vertical > .btn {\n  &:not(:first-child):not(:last-child) {\n    border-radius: 0;\n  }\n  &:first-child:not(:last-child) {\n    border-top-right-radius: @border-radius-base;\n    .border-bottom-radius(0);\n  }\n  &:last-child:not(:first-child) {\n    border-bottom-left-radius: @border-radius-base;\n    .border-top-radius(0);\n  }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child {\n  > .btn:last-child,\n  > .dropdown-toggle {\n    .border-bottom-radius(0);\n  }\n}\n.btn-group-vertical > .btn-group:last-child > .btn:first-child {\n  .border-top-radius(0);\n}\n\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n  display: table;\n  width: 100%;\n  table-layout: fixed;\n  border-collapse: separate;\n  .btn {\n    float: none;\n    display: table-cell;\n    width: 1%;\n  }\n}\n\n\n// Checkbox and radio options\n[data-toggle=\"buttons\"] > .btn > input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn > input[type=\"checkbox\"] {\n  display: none;\n}\n","buttons.less":"//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n// Core styles\n.btn {\n  display: inline-block;\n  padding: @padding-base-vertical @padding-base-horizontal;\n  margin-bottom: 0; // For input.btn\n  font-size: @font-size-base;\n  font-weight: @btn-font-weight;\n  line-height: @line-height-base;\n  text-align: center;\n  vertical-align: middle;\n  cursor: pointer;\n  border: 1px solid transparent;\n  border-radius: @border-radius-base;\n  white-space: nowrap;\n  .user-select(none);\n\n  &:focus {\n    .tab-focus();\n  }\n\n  &:hover,\n  &:focus {\n    color: @btn-default-color;\n    text-decoration: none;\n  }\n\n  &:active,\n  &.active {\n    outline: 0;\n    background-image: none;\n    .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n  }\n\n  &.disabled,\n  &[disabled],\n  fieldset[disabled] & {\n    cursor: not-allowed;\n    pointer-events: none; // Future-proof disabling of clicks\n    .opacity(.65);\n    .box-shadow(none);\n  }\n\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n  .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n  .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Warning appears as orange\n.btn-warning {\n  .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n  .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n// Success appears as green\n.btn-success {\n  .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n  .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n  color: @link-color;\n  font-weight: normal;\n  cursor: pointer;\n  border-radius: 0;\n\n  &,\n  &:active,\n  &[disabled],\n  fieldset[disabled] & {\n    background-color: transparent;\n    .box-shadow(none);\n  }\n  &,\n  &:hover,\n  &:focus,\n  &:active {\n    border-color: transparent;\n  }\n  &:hover,\n  &:focus {\n    color: @link-hover-color;\n    text-decoration: underline;\n    background-color: transparent;\n  }\n  &[disabled],\n  fieldset[disabled] & {\n    &:hover,\n    &:focus {\n      color: @btn-link-disabled-color;\n      text-decoration: none;\n    }\n  }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n  // line-height: ensure even-numbered height of button next to large input\n  .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n.btn-sm,\n.btn-xs {\n  // line-height: ensure proper height of button next to small input\n  .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n.btn-xs {\n  padding: 1px 5px;\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n  display: block;\n  width: 100%;\n  padding-left: 0;\n  padding-right: 0;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n  margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n  &.btn-block {\n    width: 100%;\n  }\n}\n","carousel.less":"//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n  position: relative;\n}\n\n.carousel-inner {\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n\n  > .item {\n    display: none;\n    position: relative;\n    .transition(.6s ease-in-out left);\n\n    // Account for jankitude on images\n    > img,\n    > a > img {\n      .img-responsive();\n      line-height: 1;\n    }\n  }\n\n  > .active,\n  > .next,\n  > .prev { display: block; }\n\n  > .active {\n    left: 0;\n  }\n\n  > .next,\n  > .prev {\n    position: absolute;\n    top: 0;\n    width: 100%;\n  }\n\n  > .next {\n    left: 100%;\n  }\n  > .prev {\n    left: -100%;\n  }\n  > .next.left,\n  > .prev.right {\n    left: 0;\n  }\n\n  > .active.left {\n    left: -100%;\n  }\n  > .active.right {\n    left: 100%;\n  }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n  position: absolute;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  width: @carousel-control-width;\n  .opacity(@carousel-control-opacity);\n  font-size: @carousel-control-font-size;\n  color: @carousel-control-color;\n  text-align: center;\n  text-shadow: @carousel-text-shadow;\n  // We can't have this transition here because webkit cancels the carousel\n  // animation if you trip this while in the middle of another animation.\n\n  // Set gradients for backgrounds\n  &.left {\n    #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n  }\n  &.right {\n    left: auto;\n    right: 0;\n    #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n  }\n\n  // Hover/focus state\n  &:hover,\n  &:focus {\n    color: @carousel-control-color;\n    text-decoration: none;\n    .opacity(.9);\n  }\n\n  // Toggles\n  .icon-prev,\n  .icon-next,\n  .glyphicon-chevron-left,\n  .glyphicon-chevron-right {\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    z-index: 5;\n    display: inline-block;\n  }\n  .icon-prev,\n  .icon-next {\n    width:  20px;\n    height: 20px;\n    margin-top: -10px;\n    margin-left: -10px;\n    font-family: serif;\n  }\n\n  .icon-prev {\n    &:before {\n      content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n    }\n  }\n  .icon-next {\n    &:before {\n      content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n    }\n  }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n  position: absolute;\n  bottom: 10px;\n  left: 50%;\n  z-index: 15;\n  width: 60%;\n  margin-left: -30%;\n  padding-left: 0;\n  list-style: none;\n  text-align: center;\n\n  li {\n    display: inline-block;\n    width:  10px;\n    height: 10px;\n    margin: 1px;\n    text-indent: -999px;\n    border: 1px solid @carousel-indicator-border-color;\n    border-radius: 10px;\n    cursor: pointer;\n  }\n  .active {\n    margin: 0;\n    width:  12px;\n    height: 12px;\n    background-color: @carousel-indicator-active-bg;\n  }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n  position: absolute;\n  left: 15%;\n  right: 15%;\n  bottom: 20px;\n  z-index: 10;\n  padding-top: 20px;\n  padding-bottom: 20px;\n  color: @carousel-caption-color;\n  text-align: center;\n  text-shadow: @carousel-text-shadow;\n  & .btn {\n    text-shadow: none; // No shadow for button elements in carousel-caption\n  }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-tablet) {\n\n  // Scale up the controls a smidge\n  .carousel-control .icon-prev,\n  .carousel-control .icon-next {\n    width: 30px;\n    height: 30px;\n    margin-top: -15px;\n    margin-left: -15px;\n    font-size: 30px;\n  }\n\n  // Show and left align the captions\n  .carousel-caption {\n    left: 20%;\n    right: 20%;\n    padding-bottom: 30px;\n  }\n\n  // Move up the indicators\n  .carousel-indicators {\n    bottom: 20px;\n  }\n}\n","close.less":"//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n  float: right;\n  font-size: (@font-size-base * 1.5);\n  font-weight: @close-font-weight;\n  line-height: 1;\n  color: @close-color;\n  text-shadow: @close-text-shadow;\n  .opacity(.2);\n\n  &:hover,\n  &:focus {\n    color: @close-color;\n    text-decoration: none;\n    cursor: pointer;\n    .opacity(.5);\n  }\n\n  // Additional properties for button version\n  // iOS requires the button element instead of an anchor tag.\n  // If you want the anchor version, it requires `href=\"#\"`.\n  button& {\n    padding: 0;\n    cursor: pointer;\n    background: transparent;\n    border: 0;\n    -webkit-appearance: none;\n  }\n}\n","code.less":"//\n// Code (inline and blocK)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\npre {\n  font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: @code-color;\n  background-color: @code-bg;\n  white-space: nowrap;\n  border-radius: @border-radius-base;\n}\n\n// Blocks of code\npre {\n  display: block;\n  padding: ((@line-height-computed - 1) / 2);\n  margin: 0 0 (@line-height-computed / 2);\n  font-size: (@font-size-base - 1); // 14px to 13px\n  line-height: @line-height-base;\n  word-break: break-all;\n  word-wrap: break-word;\n  color: @pre-color;\n  background-color: @pre-bg;\n  border: 1px solid @pre-border-color;\n  border-radius: @border-radius-base;\n\n  // Make prettyprint styles more spaced out for readability\n  &.prettyprint {\n    margin-bottom: @line-height-computed;\n  }\n\n  // Account for some code outputs that place code tags in pre tags\n  code {\n    padding: 0;\n    font-size: inherit;\n    color: inherit;\n    white-space: pre-wrap;\n    background-color: transparent;\n    border: 0;\n  }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n  max-height: @pre-scrollable-max-height;\n  overflow-y: scroll;\n}\n","component-animations.less":"//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552.\n\n.fade {\n  opacity: 0;\n  .transition(opacity .15s linear);\n  &.in {\n    opacity: 1;\n  }\n}\n\n.collapse {\n  display: none;\n  &.in {\n    display: block;\n  }\n}\n.collapsing {\n  position: relative;\n  height: 0;\n  overflow: hidden;\n  .transition(height .35s ease);\n}\n","dropdowns.less":"//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n  display: inline-block;\n  width: 0;\n  height: 0;\n  margin-left: 2px;\n  vertical-align: middle;\n  border-top:   @caret-width-base solid @dropdown-caret-color;\n  border-right: @caret-width-base solid transparent;\n  border-left:  @caret-width-base solid transparent;\n  // Firefox fix for https://github.com/twbs/bootstrap/issues/9538. Once fixed,\n  // we can just straight up remove this.\n  border-bottom: 0 dotted;\n  content: \"\";\n}\n\n// The dropdown wrapper (div)\n.dropdown {\n  position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n  outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  z-index: @zindex-dropdown;\n  display: none; // none by default, but block on \"open\" of the menu\n  float: left;\n  min-width: 160px;\n  padding: 5px 0;\n  margin: 2px 0 0; // override default ul\n  list-style: none;\n  font-size: @font-size-base;\n  background-color: @dropdown-bg;\n  border: 1px solid @dropdown-fallback-border; // IE8 fallback\n  border: 1px solid @dropdown-border;\n  border-radius: @border-radius-base;\n  .box-shadow(0 6px 12px rgba(0,0,0,.175));\n  background-clip: padding-box;\n\n  // Aligns the dropdown menu to right\n  &.pull-right {\n    right: 0;\n    left: auto;\n  }\n\n  // Dividers (basically an hr) within the dropdown\n  .divider {\n    .nav-divider(@dropdown-divider-bg);\n  }\n\n  // Links within the dropdown menu\n  > li > a {\n    display: block;\n    padding: 3px 20px;\n    clear: both;\n    font-weight: normal;\n    line-height: @line-height-base;\n    color: @dropdown-link-color;\n    white-space: nowrap; // prevent links from randomly breaking onto new lines\n  }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n  &:hover,\n  &:focus {\n    text-decoration: none;\n    color: @dropdown-link-hover-color;\n    background-color: @dropdown-link-hover-bg;\n  }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n  &,\n  &:hover,\n  &:focus {\n    color: @dropdown-link-active-color;\n    text-decoration: none;\n    outline: 0;\n    background-color: @dropdown-link-active-bg;\n  }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n  &,\n  &:hover,\n  &:focus {\n    color: @dropdown-link-disabled-color;\n  }\n}\n// Nuke hover/focus effects\n.dropdown-menu > .disabled > a {\n  &:hover,\n  &:focus {\n    text-decoration: none;\n    background-color: transparent;\n    background-image: none; // Remove CSS gradient\n    .reset-filter();\n    cursor: not-allowed;\n  }\n}\n\n// Open state for the dropdown\n.open {\n  // Show the menu\n  > .dropdown-menu {\n    display: block;\n  }\n\n  // Remove the outline when :focus is triggered\n  > a {\n    outline: 0;\n  }\n}\n\n// Dropdown section headers\n.dropdown-header {\n  display: block;\n  padding: 3px 20px;\n  font-size: @font-size-small;\n  line-height: @line-height-base;\n  color: @dropdown-header-color;\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n  position: fixed;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  top: 0;\n  z-index: @zindex-dropdown - 10;\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n  right: 0;\n  left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n  // Reverse the caret\n  .caret {\n    // Firefox fix for https://github.com/twbs/bootstrap/issues/9538. Once this\n    // gets fixed, restore `border-top: 0;`.\n    border-top: 0 dotted;\n    border-bottom: 4px solid @dropdown-caret-color;\n    content: \"\";\n  }\n  // Different positioning for bottom up menu\n  .dropdown-menu {\n    top: auto;\n    bottom: 100%;\n    margin-bottom: 1px;\n  }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n  .navbar-right {\n    .dropdown-menu {\n      .pull-right > .dropdown-menu();\n    }\n  }\n}\n\n","forms.less":"//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\n\nlegend {\n  display: block;\n  width: 100%;\n  padding: 0;\n  margin-bottom: @line-height-computed;\n  font-size: (@font-size-base * 1.5);\n  line-height: inherit;\n  color: @legend-color;\n  border: 0;\n  border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n  display: inline-block;\n  margin-bottom: 5px;\n  font-weight: bold;\n}\n\n\n// Normalize form controls\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n  .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  margin: 4px 0 0;\n  margin-top: 1px \\9; /* IE8-9 */\n  line-height: normal;\n}\n\n// Set the height of select and file controls to match text inputs\ninput[type=\"file\"] {\n  display: block;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n  height: auto;\n}\n\n// Fix optgroup Firefox bug per https://github.com/twbs/bootstrap/issues/7611\nselect optgroup {\n  font-size: inherit;\n  font-style: inherit;\n  font-family: inherit;\n}\n\n// Focus for select, file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n  .tab-focus();\n}\n\n// Fix for Chrome number input\n// Setting certain font-sizes causes the `I` bar to appear on hover of the bottom increment button.\n// See https://github.com/twbs/bootstrap/issues/8350 for more.\ninput[type=\"number\"] {\n  &::-webkit-outer-spin-button,\n  &::-webkit-inner-spin-button {\n    height: auto;\n  }\n}\n\n\n// Placeholder\n//\n// Placeholder text gets special styles because when browsers invalidate entire\n// lines if it doesn't understand a selector/\n.form-control {\n  .placeholder();\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n  display: block;\n  width: 100%;\n  height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n  padding: @padding-base-vertical @padding-base-horizontal;\n  font-size: @font-size-base;\n  line-height: @line-height-base;\n  color: @input-color;\n  vertical-align: middle;\n  background-color: @input-bg;\n  border: 1px solid @input-border;\n  border-radius: @input-border-radius;\n  .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n  .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n  // Customize the `:focus` state to imitate native WebKit styles.\n  .form-control-focus();\n\n  // Disabled and read-only inputs\n  // Note: HTML5 says that controls under a fieldset > legend:first-child won't\n  // be disabled if the fieldset is disabled. Due to implementation difficulty,\n  // we don't honor that edge case; we style them as disabled anyway.\n  &[disabled],\n  &[readonly],\n  fieldset[disabled] & {\n    cursor: not-allowed;\n    background-color: @input-bg-disabled;\n  }\n\n  // Reset height for `textarea`s\n  textarea& {\n    height: auto;\n  }\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n  margin-bottom: 15px;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n  display: block;\n  min-height: @line-height-computed; // clear the floating input if there is no label text\n  margin-top: 10px;\n  margin-bottom: 10px;\n  padding-left: 20px;\n  vertical-align: middle;\n  label {\n    display: inline;\n    margin-bottom: 0;\n    font-weight: normal;\n    cursor: pointer;\n  }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n  float: left;\n  margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n  margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n  display: inline-block;\n  padding-left: 20px;\n  margin-bottom: 0;\n  vertical-align: middle;\n  font-weight: normal;\n  cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n  margin-top: 0;\n  margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n//\n// Note: Neither radios nor checkboxes can be readonly.\ninput[type=\"radio\"],\ninput[type=\"checkbox\"],\n.radio,\n.radio-inline,\n.checkbox,\n.checkbox-inline {\n  &[disabled],\n  fieldset[disabled] & {\n    cursor: not-allowed;\n  }\n}\n\n// Form control sizing\n.input-sm {\n  .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n\n.input-lg {\n  .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n\n\n// Form control feedback states\n//\n// Apply contextual and semantic states to individual form controls.\n\n// Warning\n.has-warning {\n  .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);\n}\n// Error\n.has-error {\n  .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);\n}\n// Success\n.has-success {\n  .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);\n}\n\n\n// Static form control text\n//\n// Apply class to a `p` element to make any string of text align with labels in\n// a horizontal form layout.\n\n.form-control-static {\n  margin-bottom: 0; // Remove default margin from `p`\n  padding-top: (@padding-base-vertical + 1);\n}\n\n\n// Help text\n//\n// Apply to any element you wish to create light text for placement immediately\n// below a form control. Use for general help, formatting, or instructional text.\n\n.help-block {\n  display: block; // account for any element using help-block\n  margin-top: 5px;\n  margin-bottom: 10px;\n  color: lighten(@text-color, 25%); // lighten the text some for contrast\n}\n\n\n\n// Inline forms\n//\n// Make forms appear inline(-block) by adding the `.form-inline` class. Inline\n// forms begin stacked on extra small (mobile) devices and then go inline when\n// viewports reach <768px.\n//\n// Requires wrapping inputs and labels with `.form-group` for proper display of\n// default HTML form controls and our custom form controls (e.g., input groups).\n//\n// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.\n\n.form-inline {\n\n  // Kick in the inline\n  @media (min-width: @screen-tablet) {\n    // Inline-block all the things for \"inline\"\n    .form-group  {\n      display: inline-block;\n      margin-bottom: 0;\n      vertical-align: middle;\n    }\n\n    // In navbar-form, allow folks to *not* use `.form-group`\n    .form-control {\n      display: inline-block;\n    }\n\n    // Remove default margin on radios/checkboxes that were used for stacking, and\n    // then undo the floating of radios and checkboxes to match (which also avoids\n    // a bug in WebKit: https://github.com/twbs/bootstrap/issues/1969).\n    .radio,\n    .checkbox {\n      display: inline-block;\n      margin-top: 0;\n      margin-bottom: 0;\n      padding-left: 0;\n    }\n    .radio input[type=\"radio\"],\n    .checkbox input[type=\"checkbox\"] {\n      float: none;\n      margin-left: 0;\n    }\n  }\n}\n\n\n// Horizontal forms\n//\n// Horizontal forms are built on grid classes and allow you to create forms with\n// labels on the left and inputs on the right.\n\n.form-horizontal {\n\n  // Consistent vertical alignment of labels, radios, and checkboxes\n  .control-label,\n  .radio,\n  .checkbox,\n  .radio-inline,\n  .checkbox-inline {\n    margin-top: 0;\n    margin-bottom: 0;\n    padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n  }\n\n  // Make form groups behave like rows\n  .form-group {\n    .make-row();\n  }\n\n  // Only right align form labels here when the columns stop stacking\n  @media (min-width: @screen-tablet) {\n    .control-label {\n      text-align: right;\n    }\n  }\n}\n","glyphicons.less":"//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// <a href=\"#\"><span class=\"glyphicon glyphicon-star\"></span> Star</a>\n\n// Import the fonts\n@font-face {\n  font-family: 'Glyphicons Halflings';\n  src: url('@{icon-font-path}@{icon-font-name}.eot');\n  src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n       url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n       url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n       url('@{icon-font-path}@{icon-font-name}.svg#glyphicons-halflingsregular') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n  position: relative;\n  top: 1px;\n  display: inline-block;\n  font-family: 'Glyphicons Halflings';\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased;\n}\n\n// Individual icons\n.glyphicon-asterisk               { &:before { content: \"\\2a\"; } }\n.glyphicon-plus                   { &:before { content: \"\\2b\"; } }\n.glyphicon-euro                   { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus                  { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud                  { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope               { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil                 { &:before { content: \"\\270f\"; } }\n.glyphicon-glass                  { &:before { content: \"\\e001\"; } }\n.glyphicon-music                  { &:before { content: \"\\e002\"; } }\n.glyphicon-search                 { &:before { content: \"\\e003\"; } }\n.glyphicon-heart                  { &:before { content: \"\\e005\"; } }\n.glyphicon-star                   { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty             { &:before { content: \"\\e007\"; } }\n.glyphicon-user                   { &:before { content: \"\\e008\"; } }\n.glyphicon-film                   { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large               { &:before { content: \"\\e010\"; } }\n.glyphicon-th                     { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list                { &:before { content: \"\\e012\"; } }\n.glyphicon-ok                     { &:before { content: \"\\e013\"; } }\n.glyphicon-remove                 { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in                { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out               { &:before { content: \"\\e016\"; } }\n.glyphicon-off                    { &:before { content: \"\\e017\"; } }\n.glyphicon-signal                 { &:before { content: \"\\e018\"; } }\n.glyphicon-cog                    { &:before { content: \"\\e019\"; } }\n.glyphicon-trash                  { &:before { content: \"\\e020\"; } }\n.glyphicon-home                   { &:before { content: \"\\e021\"; } }\n.glyphicon-file                   { &:before { content: \"\\e022\"; } }\n.glyphicon-time                   { &:before { content: \"\\e023\"; } }\n.glyphicon-road                   { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt           { &:before { content: \"\\e025\"; } }\n.glyphicon-download               { &:before { content: \"\\e026\"; } }\n.glyphicon-upload                 { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox                  { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle            { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat                 { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh                { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt               { &:before { content: \"\\e032\"; } }\n.glyphicon-flag                   { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones             { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off             { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down            { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up              { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode                 { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode                { &:before { content: \"\\e040\"; } }\n.glyphicon-tag                    { &:before { content: \"\\e041\"; } }\n.glyphicon-tags                   { &:before { content: \"\\e042\"; } }\n.glyphicon-book                   { &:before { content: \"\\e043\"; } }\n.glyphicon-print                  { &:before { content: \"\\e045\"; } }\n.glyphicon-font                   { &:before { content: \"\\e047\"; } }\n.glyphicon-bold                   { &:before { content: \"\\e048\"; } }\n.glyphicon-italic                 { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height            { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width             { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left             { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center           { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right            { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify          { &:before { content: \"\\e055\"; } }\n.glyphicon-list                   { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left            { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right           { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video         { &:before { content: \"\\e059\"; } }\n.glyphicon-picture                { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker             { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust                 { &:before { content: \"\\e063\"; } }\n.glyphicon-tint                   { &:before { content: \"\\e064\"; } }\n.glyphicon-edit                   { &:before { content: \"\\e065\"; } }\n.glyphicon-share                  { &:before { content: \"\\e066\"; } }\n.glyphicon-check                  { &:before { content: \"\\e067\"; } }\n.glyphicon-move                   { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward          { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward          { &:before { content: \"\\e070\"; } }\n.glyphicon-backward               { &:before { content: \"\\e071\"; } }\n.glyphicon-play                   { &:before { content: \"\\e072\"; } }\n.glyphicon-pause                  { &:before { content: \"\\e073\"; } }\n.glyphicon-stop                   { &:before { content: \"\\e074\"; } }\n.glyphicon-forward                { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward           { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward           { &:before { content: \"\\e077\"; } }\n.glyphicon-eject                  { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left           { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right          { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign              { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign             { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign            { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign                { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign          { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign              { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot             { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle          { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle              { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle             { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left             { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right            { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up               { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down             { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt              { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full            { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small           { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign       { &:before { content: \"\\e101\"; } }\n.glyphicon-gift                   { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf                   { &:before { content: \"\\e103\"; } }\n.glyphicon-eye-open               { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close              { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign           { &:before { content: \"\\e107\"; } }\n.glyphicon-plane                  { &:before { content: \"\\e108\"; } }\n.glyphicon-random                 { &:before { content: \"\\e110\"; } }\n.glyphicon-comment                { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet                 { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up             { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down           { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet                { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart          { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close           { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open            { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical        { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal      { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd                    { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn               { &:before { content: \"\\e122\"; } }\n.glyphicon-certificate            { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up              { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down            { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right             { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left              { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up                { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down              { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right     { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left      { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up        { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down      { &:before { content: \"\\e134\"; } }\n.glyphicon-globe                  { &:before { content: \"\\e135\"; } }\n.glyphicon-tasks                  { &:before { content: \"\\e137\"; } }\n.glyphicon-filter                 { &:before { content: \"\\e138\"; } }\n.glyphicon-fullscreen             { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard              { &:before { content: \"\\e141\"; } }\n.glyphicon-heart-empty            { &:before { content: \"\\e143\"; } }\n.glyphicon-link                   { &:before { content: \"\\e144\"; } }\n.glyphicon-phone                  { &:before { content: \"\\e145\"; } }\n.glyphicon-usd                    { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp                    { &:before { content: \"\\e149\"; } }\n.glyphicon-sort                   { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet       { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt   { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order          { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt      { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes     { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked              { &:before { content: \"\\e157\"; } }\n.glyphicon-expand                 { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down          { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up            { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in                 { &:before { content: \"\\e161\"; } }\n.glyphicon-flash                  { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out                { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window             { &:before { content: \"\\e164\"; } }\n.glyphicon-record                 { &:before { content: \"\\e165\"; } }\n.glyphicon-save                   { &:before { content: \"\\e166\"; } }\n.glyphicon-open                   { &:before { content: \"\\e167\"; } }\n.glyphicon-saved                  { &:before { content: \"\\e168\"; } }\n.glyphicon-import                 { &:before { content: \"\\e169\"; } }\n.glyphicon-export                 { &:before { content: \"\\e170\"; } }\n.glyphicon-send                   { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk            { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved           { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove          { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save            { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open            { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card            { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer               { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery                { &:before { content: \"\\e179\"; } }\n.glyphicon-header                 { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed             { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone               { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt              { &:before { content: \"\\e183\"; } }\n.glyphicon-tower                  { &:before { content: \"\\e184\"; } }\n.glyphicon-stats                  { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video               { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video               { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles              { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo           { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby            { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1              { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1              { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1              { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark         { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark      { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download         { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload           { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer           { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous         { &:before { content: \"\\e200\"; } }\n.glyphicon-briefcase              { &:before { content: \"\\1f4bc\"; } }\n.glyphicon-calendar               { &:before { content: \"\\1f4c5\"; } }\n.glyphicon-pushpin                { &:before { content: \"\\1f4cc\"; } }\n.glyphicon-paperclip              { &:before { content: \"\\1f4ce\"; } }\n.glyphicon-camera                 { &:before { content: \"\\1f4f7\"; } }\n.glyphicon-lock                   { &:before { content: \"\\1f512\"; } }\n.glyphicon-bell                   { &:before { content: \"\\1f514\"; } }\n.glyphicon-bookmark               { &:before { content: \"\\1f516\"; } }\n.glyphicon-fire                   { &:before { content: \"\\1f525\"; } }\n.glyphicon-wrench                 { &:before { content: \"\\1f527\"; } }\n","grid.less":"//\n// Grid system\n// --------------------------------------------------\n\n\n// Set the container width, and override it for fixed navbars in media queries\n.container {\n  .container-fixed();\n}\n\n// mobile first defaults\n.row {\n  .make-row();\n}\n\n// Common styles for small and large grid columns\n.col-xs-1,\n.col-xs-2,\n.col-xs-3,\n.col-xs-4,\n.col-xs-5,\n.col-xs-6,\n.col-xs-7,\n.col-xs-8,\n.col-xs-9,\n.col-xs-10,\n.col-xs-11,\n.col-xs-12,\n.col-sm-1,\n.col-sm-2,\n.col-sm-3,\n.col-sm-4,\n.col-sm-5,\n.col-sm-6,\n.col-sm-7,\n.col-sm-8,\n.col-sm-9,\n.col-sm-10,\n.col-sm-11,\n.col-sm-12,\n.col-md-1,\n.col-md-2,\n.col-md-3,\n.col-md-4,\n.col-md-5,\n.col-md-6,\n.col-md-7,\n.col-md-8,\n.col-md-9,\n.col-md-10,\n.col-md-11,\n.col-md-12,\n.col-lg-1,\n.col-lg-2,\n.col-lg-3,\n.col-lg-4,\n.col-lg-5,\n.col-lg-6,\n.col-lg-7,\n.col-lg-8,\n.col-lg-9,\n.col-lg-10,\n.col-lg-11,\n.col-lg-12 {\n  position: relative;\n  // Prevent columns from collapsing when empty\n  min-height: 1px;\n  // Inner gutter via padding\n  padding-left:  (@grid-gutter-width / 2);\n  padding-right: (@grid-gutter-width / 2);\n}\n\n\n// Extra small grid\n//\n// Grid classes for extra small devices like smartphones. No offset, push, or\n// pull classes are present here due to the size of the target.\n//\n// Note that `.col-xs-12` doesn't get floated on purpose—there's no need since\n// it's full-width.\n\n.col-xs-1,\n.col-xs-2,\n.col-xs-3,\n.col-xs-4,\n.col-xs-5,\n.col-xs-6,\n.col-xs-7,\n.col-xs-8,\n.col-xs-9,\n.col-xs-10,\n.col-xs-11 {\n  float: left;\n}\n.col-xs-1  { width: percentage((1 / @grid-columns)); }\n.col-xs-2  { width: percentage((2 / @grid-columns)); }\n.col-xs-3  { width: percentage((3 / @grid-columns)); }\n.col-xs-4  { width: percentage((4 / @grid-columns)); }\n.col-xs-5  { width: percentage((5 / @grid-columns)); }\n.col-xs-6  { width: percentage((6 / @grid-columns)); }\n.col-xs-7  { width: percentage((7 / @grid-columns)); }\n.col-xs-8  { width: percentage((8 / @grid-columns)); }\n.col-xs-9  { width: percentage((9 / @grid-columns)); }\n.col-xs-10 { width: percentage((10/ @grid-columns)); }\n.col-xs-11 { width: percentage((11/ @grid-columns)); }\n.col-xs-12 { width: 100%; }\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n//\n// Note that `.col-sm-12` doesn't get floated on purpose—there's no need since\n// it's full-width.\n\n@media (min-width: @screen-tablet) {\n  .container {\n    max-width: @container-tablet;\n  }\n\n  .col-sm-1,\n  .col-sm-2,\n  .col-sm-3,\n  .col-sm-4,\n  .col-sm-5,\n  .col-sm-6,\n  .col-sm-7,\n  .col-sm-8,\n  .col-sm-9,\n  .col-sm-10,\n  .col-sm-11 {\n    float: left;\n  }\n  .col-sm-1  { width: percentage((1 / @grid-columns)); }\n  .col-sm-2  { width: percentage((2 / @grid-columns)); }\n  .col-sm-3  { width: percentage((3 / @grid-columns)); }\n  .col-sm-4  { width: percentage((4 / @grid-columns)); }\n  .col-sm-5  { width: percentage((5 / @grid-columns)); }\n  .col-sm-6  { width: percentage((6 / @grid-columns)); }\n  .col-sm-7  { width: percentage((7 / @grid-columns)); }\n  .col-sm-8  { width: percentage((8 / @grid-columns)); }\n  .col-sm-9  { width: percentage((9 / @grid-columns)); }\n  .col-sm-10 { width: percentage((10/ @grid-columns)); }\n  .col-sm-11 { width: percentage((11/ @grid-columns)); }\n  .col-sm-12 { width: 100%; }\n\n  // Push and pull columns for source order changes\n  .col-sm-push-1  { left: percentage((1 / @grid-columns)); }\n  .col-sm-push-2  { left: percentage((2 / @grid-columns)); }\n  .col-sm-push-3  { left: percentage((3 / @grid-columns)); }\n  .col-sm-push-4  { left: percentage((4 / @grid-columns)); }\n  .col-sm-push-5  { left: percentage((5 / @grid-columns)); }\n  .col-sm-push-6  { left: percentage((6 / @grid-columns)); }\n  .col-sm-push-7  { left: percentage((7 / @grid-columns)); }\n  .col-sm-push-8  { left: percentage((8 / @grid-columns)); }\n  .col-sm-push-9  { left: percentage((9 / @grid-columns)); }\n  .col-sm-push-10 { left: percentage((10/ @grid-columns)); }\n  .col-sm-push-11 { left: percentage((11/ @grid-columns)); }\n\n  .col-sm-pull-1  { right: percentage((1 / @grid-columns)); }\n  .col-sm-pull-2  { right: percentage((2 / @grid-columns)); }\n  .col-sm-pull-3  { right: percentage((3 / @grid-columns)); }\n  .col-sm-pull-4  { right: percentage((4 / @grid-columns)); }\n  .col-sm-pull-5  { right: percentage((5 / @grid-columns)); }\n  .col-sm-pull-6  { right: percentage((6 / @grid-columns)); }\n  .col-sm-pull-7  { right: percentage((7 / @grid-columns)); }\n  .col-sm-pull-8  { right: percentage((8 / @grid-columns)); }\n  .col-sm-pull-9  { right: percentage((9 / @grid-columns)); }\n  .col-sm-pull-10 { right: percentage((10/ @grid-columns)); }\n  .col-sm-pull-11 { right: percentage((11/ @grid-columns)); }\n\n  // Offsets\n  .col-sm-offset-1  { margin-left: percentage((1 / @grid-columns)); }\n  .col-sm-offset-2  { margin-left: percentage((2 / @grid-columns)); }\n  .col-sm-offset-3  { margin-left: percentage((3 / @grid-columns)); }\n  .col-sm-offset-4  { margin-left: percentage((4 / @grid-columns)); }\n  .col-sm-offset-5  { margin-left: percentage((5 / @grid-columns)); }\n  .col-sm-offset-6  { margin-left: percentage((6 / @grid-columns)); }\n  .col-sm-offset-7  { margin-left: percentage((7 / @grid-columns)); }\n  .col-sm-offset-8  { margin-left: percentage((8 / @grid-columns)); }\n  .col-sm-offset-9  { margin-left: percentage((9 / @grid-columns)); }\n  .col-sm-offset-10 { margin-left: percentage((10/ @grid-columns)); }\n  .col-sm-offset-11 { margin-left: percentage((11/ @grid-columns)); }\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n//\n// Note that `.col-md-12` doesn't get floated on purpose—there's no need since\n// it's full-width.\n\n@media (min-width: @screen-desktop) {\n  .container {\n    max-width: @container-desktop;\n  }\n  .col-md-1,\n  .col-md-2,\n  .col-md-3,\n  .col-md-4,\n  .col-md-5,\n  .col-md-6,\n  .col-md-7,\n  .col-md-8,\n  .col-md-9,\n  .col-md-10,\n  .col-md-11 {\n    float: left;\n  }\n  .col-md-1  { width: percentage((1 / @grid-columns)); }\n  .col-md-2  { width: percentage((2 / @grid-columns)); }\n  .col-md-3  { width: percentage((3 / @grid-columns)); }\n  .col-md-4  { width: percentage((4 / @grid-columns)); }\n  .col-md-5  { width: percentage((5 / @grid-columns)); }\n  .col-md-6  { width: percentage((6 / @grid-columns)); }\n  .col-md-7  { width: percentage((7 / @grid-columns)); }\n  .col-md-8  { width: percentage((8 / @grid-columns)); }\n  .col-md-9  { width: percentage((9 / @grid-columns)); }\n  .col-md-10 { width: percentage((10/ @grid-columns)); }\n  .col-md-11 { width: percentage((11/ @grid-columns)); }\n  .col-md-12 { width: 100%; }\n\n  // Push and pull columns for source order changes\n  .col-md-push-0  { left: auto; }\n  .col-md-push-1  { left: percentage((1 / @grid-columns)); }\n  .col-md-push-2  { left: percentage((2 / @grid-columns)); }\n  .col-md-push-3  { left: percentage((3 / @grid-columns)); }\n  .col-md-push-4  { left: percentage((4 / @grid-columns)); }\n  .col-md-push-5  { left: percentage((5 / @grid-columns)); }\n  .col-md-push-6  { left: percentage((6 / @grid-columns)); }\n  .col-md-push-7  { left: percentage((7 / @grid-columns)); }\n  .col-md-push-8  { left: percentage((8 / @grid-columns)); }\n  .col-md-push-9  { left: percentage((9 / @grid-columns)); }\n  .col-md-push-10 { left: percentage((10/ @grid-columns)); }\n  .col-md-push-11 { left: percentage((11/ @grid-columns)); }\n\n  .col-md-pull-0  { right: auto; }\n  .col-md-pull-1  { right: percentage((1 / @grid-columns)); }\n  .col-md-pull-2  { right: percentage((2 / @grid-columns)); }\n  .col-md-pull-3  { right: percentage((3 / @grid-columns)); }\n  .col-md-pull-4  { right: percentage((4 / @grid-columns)); }\n  .col-md-pull-5  { right: percentage((5 / @grid-columns)); }\n  .col-md-pull-6  { right: percentage((6 / @grid-columns)); }\n  .col-md-pull-7  { right: percentage((7 / @grid-columns)); }\n  .col-md-pull-8  { right: percentage((8 / @grid-columns)); }\n  .col-md-pull-9  { right: percentage((9 / @grid-columns)); }\n  .col-md-pull-10 { right: percentage((10/ @grid-columns)); }\n  .col-md-pull-11 { right: percentage((11/ @grid-columns)); }\n\n  // Offsets\n  .col-md-offset-0  { margin-left: 0; }\n  .col-md-offset-1  { margin-left: percentage((1 / @grid-columns)); }\n  .col-md-offset-2  { margin-left: percentage((2 / @grid-columns)); }\n  .col-md-offset-3  { margin-left: percentage((3 / @grid-columns)); }\n  .col-md-offset-4  { margin-left: percentage((4 / @grid-columns)); }\n  .col-md-offset-5  { margin-left: percentage((5 / @grid-columns)); }\n  .col-md-offset-6  { margin-left: percentage((6 / @grid-columns)); }\n  .col-md-offset-7  { margin-left: percentage((7 / @grid-columns)); }\n  .col-md-offset-8  { margin-left: percentage((8 / @grid-columns)); }\n  .col-md-offset-9  { margin-left: percentage((9 / @grid-columns)); }\n  .col-md-offset-10 { margin-left: percentage((10/ @grid-columns)); }\n  .col-md-offset-11 { margin-left: percentage((11/ @grid-columns)); }\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n//\n// Note that `.col-lg-12` doesn't get floated on purpose—there's no need since\n// it's full-width.\n\n@media (min-width: @screen-lg-desktop) {\n  .container {\n    max-width: @container-lg-desktop;\n  }\n\n  .col-lg-1,\n  .col-lg-2,\n  .col-lg-3,\n  .col-lg-4,\n  .col-lg-5,\n  .col-lg-6,\n  .col-lg-7,\n  .col-lg-8,\n  .col-lg-9,\n  .col-lg-10,\n  .col-lg-11 {\n    float: left;\n  }\n  .col-lg-1  { width: percentage((1 / @grid-columns)); }\n  .col-lg-2  { width: percentage((2 / @grid-columns)); }\n  .col-lg-3  { width: percentage((3 / @grid-columns)); }\n  .col-lg-4  { width: percentage((4 / @grid-columns)); }\n  .col-lg-5  { width: percentage((5 / @grid-columns)); }\n  .col-lg-6  { width: percentage((6 / @grid-columns)); }\n  .col-lg-7  { width: percentage((7 / @grid-columns)); }\n  .col-lg-8  { width: percentage((8 / @grid-columns)); }\n  .col-lg-9  { width: percentage((9 / @grid-columns)); }\n  .col-lg-10 { width: percentage((10/ @grid-columns)); }\n  .col-lg-11 { width: percentage((11/ @grid-columns)); }\n  .col-lg-12 { width: 100%; }\n\n  // Push and pull columns for source order changes\n  .col-lg-push-0  { left: auto; }\n  .col-lg-push-1  { left: percentage((1 / @grid-columns)); }\n  .col-lg-push-2  { left: percentage((2 / @grid-columns)); }\n  .col-lg-push-3  { left: percentage((3 / @grid-columns)); }\n  .col-lg-push-4  { left: percentage((4 / @grid-columns)); }\n  .col-lg-push-5  { left: percentage((5 / @grid-columns)); }\n  .col-lg-push-6  { left: percentage((6 / @grid-columns)); }\n  .col-lg-push-7  { left: percentage((7 / @grid-columns)); }\n  .col-lg-push-8  { left: percentage((8 / @grid-columns)); }\n  .col-lg-push-9  { left: percentage((9 / @grid-columns)); }\n  .col-lg-push-10 { left: percentage((10/ @grid-columns)); }\n  .col-lg-push-11 { left: percentage((11/ @grid-columns)); }\n\n  .col-lg-pull-0  { right: auto; }\n  .col-lg-pull-1  { right: percentage((1 / @grid-columns)); }\n  .col-lg-pull-2  { right: percentage((2 / @grid-columns)); }\n  .col-lg-pull-3  { right: percentage((3 / @grid-columns)); }\n  .col-lg-pull-4  { right: percentage((4 / @grid-columns)); }\n  .col-lg-pull-5  { right: percentage((5 / @grid-columns)); }\n  .col-lg-pull-6  { right: percentage((6 / @grid-columns)); }\n  .col-lg-pull-7  { right: percentage((7 / @grid-columns)); }\n  .col-lg-pull-8  { right: percentage((8 / @grid-columns)); }\n  .col-lg-pull-9  { right: percentage((9 / @grid-columns)); }\n  .col-lg-pull-10 { right: percentage((10/ @grid-columns)); }\n  .col-lg-pull-11 { right: percentage((11/ @grid-columns)); }\n\n  // Offsets\n  .col-lg-offset-0  { margin-left: 0; }\n  .col-lg-offset-1  { margin-left: percentage((1 / @grid-columns)); }\n  .col-lg-offset-2  { margin-left: percentage((2 / @grid-columns)); }\n  .col-lg-offset-3  { margin-left: percentage((3 / @grid-columns)); }\n  .col-lg-offset-4  { margin-left: percentage((4 / @grid-columns)); }\n  .col-lg-offset-5  { margin-left: percentage((5 / @grid-columns)); }\n  .col-lg-offset-6  { margin-left: percentage((6 / @grid-columns)); }\n  .col-lg-offset-7  { margin-left: percentage((7 / @grid-columns)); }\n  .col-lg-offset-8  { margin-left: percentage((8 / @grid-columns)); }\n  .col-lg-offset-9  { margin-left: percentage((9 / @grid-columns)); }\n  .col-lg-offset-10 { margin-left: percentage((10/ @grid-columns)); }\n  .col-lg-offset-11 { margin-left: percentage((11/ @grid-columns)); }\n}\n","input-groups.less":"//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n  position: relative; // For dropdowns\n  display: table;\n  border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n  // Undo padding and float of grid classes\n  &.col {\n    float: none;\n    padding-left: 0;\n    padding-right: 0;\n  }\n\n  .form-control {\n    width: 100%;\n    margin-bottom: 0;\n  }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn { .input-lg(); }\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn { .input-sm(); }\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n  display: table-cell;\n\n  &:not(:first-child):not(:last-child) {\n    border-radius: 0;\n  }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n  width: 1%;\n  white-space: nowrap;\n  vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n  padding: @padding-base-vertical @padding-base-horizontal;\n  font-size: @font-size-base;\n  font-weight: normal;\n  line-height: 1;\n  text-align: center;\n  background-color: @input-group-addon-bg;\n  border: 1px solid @input-group-addon-border-color;\n  border-radius: @border-radius-base;\n\n  // Sizing\n  &.input-sm {\n    padding: @padding-small-vertical @padding-small-horizontal;\n    font-size: @font-size-small;\n    border-radius: @border-radius-small;\n  }\n  &.input-lg {\n    padding: @padding-large-vertical @padding-large-horizontal;\n    font-size: @font-size-large;\n    border-radius: @border-radius-large;\n  }\n\n  // Nuke default margins from checkboxes and radios to vertically center within.\n  input[type=\"radio\"],\n  input[type=\"checkbox\"] {\n    margin-top: 0;\n  }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {\n  .border-right-radius(0);\n}\n.input-group-addon:first-child {\n  border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child) {\n  .border-left-radius(0);\n}\n.input-group-addon:last-child {\n  border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n  position: relative;\n  white-space: nowrap;\n}\n.input-group-btn > .btn {\n  position: relative;\n  // Jankily prevent input button groups from wrapping\n  + .btn {\n    margin-left: -4px;\n  }\n  // Bring the \"active\" button to the front\n  &:hover,\n  &:active {\n    z-index: 2;\n  }\n}\n","jumbotron.less":"//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n  padding: @jumbotron-padding;\n  margin-bottom: @jumbotron-padding;\n  font-size: (@font-size-base * 1.5);\n  font-weight: 200;\n  line-height: (@line-height-base * 1.5);\n  color: @jumbotron-color;\n  background-color: @jumbotron-bg;\n\n  h1 {\n    line-height: 1;\n    color: @jumbotron-heading-color;\n  }\n  p {\n    line-height: 1.4;\n  }\n\n  .container & {\n    border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n  }\n\n  @media screen and (min-width: @screen-tablet) {\n    padding-top:    (@jumbotron-padding * 1.6);\n    padding-bottom: (@jumbotron-padding * 1.6);\n\n    .container & {\n      padding-left:  (@jumbotron-padding * 2);\n      padding-right: (@jumbotron-padding * 2);\n    }\n\n    h1 {\n      font-size: (@font-size-base * 4.5);\n    }\n  }\n}\n","labels.less":"//\n// Labels\n// --------------------------------------------------\n\n.label {\n  display: inline;\n  padding: .2em .6em .3em;\n  font-size: 75%;\n  font-weight: bold;\n  line-height: 1;\n  color: @label-color;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  border-radius: .25em;\n\n  // Add hover effects, but only for links\n  &[href] {\n    &:hover,\n    &:focus {\n      color: @label-link-hover-color;\n      text-decoration: none;\n      cursor: pointer;\n    }\n  }\n\n  // Empty labels collapse automatically (not available in IE8)\n  &:empty {\n    display: none;\n  }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n  .label-variant(@label-default-bg);\n}\n\n.label-primary {\n  .label-variant(@label-primary-bg);\n}\n\n.label-success {\n  .label-variant(@label-success-bg);\n}\n\n.label-info {\n  .label-variant(@label-info-bg);\n}\n\n.label-warning {\n  .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n  .label-variant(@label-danger-bg);\n}\n","list-group.less":"//\n// List groups\n// --------------------------------------------------\n\n// Base class\n//\n// Easily usable on <ul>, <ol>, or <div>.\n.list-group {\n  // No need to set list-style: none; since .list-group-item is block level\n  margin-bottom: 20px;\n  padding-left: 0; // reset padding because ul and ol\n}\n\n// Individual list items\n// -------------------------\n\n.list-group-item {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n  // Place the border on the list items and negative margin up for better styling\n  margin-bottom: -1px;\n  background-color: @list-group-bg;\n  border: 1px solid @list-group-border;\n\n  // Round the first and last items\n  &:first-child {\n    .border-top-radius(@list-group-border-radius);\n  }\n  &:last-child {\n    margin-bottom: 0;\n    .border-bottom-radius(@list-group-border-radius);\n  }\n\n  // Align badges within list items\n  > .badge {\n    float: right;\n  }\n  > .badge + .badge {\n    margin-right: 5px;\n  }\n\n  // Linked list items\n  a& {\n    color: @list-group-link-color;\n\n    .list-group-item-heading {\n      color: @list-group-link-heading-color;\n    }\n\n    // Hover state\n    &:hover,\n    &:focus {\n      text-decoration: none;\n      background-color: @list-group-hover-bg;\n    }\n  }\n\n  // Active class on item itself, not parent\n  &.active,\n  &.active:hover,\n  &.active:focus {\n    z-index: 2; // Place active items above their siblings for proper border styling\n    color: @list-group-active-color;\n    background-color: @list-group-active-bg;\n    border-color: @list-group-active-border;\n\n    // Force color to inherit for custom content\n    .list-group-item-heading {\n      color: inherit;\n    }\n    .list-group-item-text {\n      color: lighten(@list-group-active-bg, 40%);\n    }\n  }\n}\n\n// Custom content options\n// -------------------------\n\n.list-group-item-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.list-group-item-text {\n  margin-bottom: 0;\n  line-height: 1.3;\n}\n","media.less":"// Media objects\n// Source: http://stubbornella.org/content/?p=497\n// --------------------------------------------------\n\n\n// Common styles\n// -------------------------\n\n// Clear the floats\n.media,\n.media-body {\n  overflow: hidden;\n  zoom: 1;\n}\n\n// Proper spacing between instances of .media\n.media,\n.media .media {\n  margin-top: 15px;\n}\n.media:first-child {\n  margin-top: 0;\n}\n\n// For images and videos, set to block\n.media-object {\n  display: block;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n  margin: 0 0 5px;\n}\n\n\n// Media image alignment\n// -------------------------\n\n.media {\n  > .pull-left {\n    margin-right: 10px;\n  }\n  > .pull-right {\n    margin-left: 10px;\n  }\n}\n\n\n// Media list variation\n// -------------------------\n\n// Undo default ul/ol styles\n.media-list {\n  padding-left: 0;\n  list-style: none;\n}\n","mixins.less":"//\n// Mixins\n// --------------------------------------------------\n\n\n// Utilities\n// -------------------------\n\n// Clearfix\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n//    contenteditable attribute is included anywhere else in the document.\n//    Otherwise it causes space to appear at the top and bottom of elements\n//    that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n//    `:before` to contain the top-margins of child elements.\n.clearfix() {\n  &:before,\n  &:after {\n    content: \" \"; /* 1 */\n    display: table; /* 2 */\n  }\n  &:after {\n    clear: both;\n  }\n}\n\n// Webkit-style focus\n.tab-focus() {\n  // Default\n  outline: thin dotted #333;\n  // Webkit\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\n\n// Center-align a block level element\n.center-block() {\n  display: block;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n// Sizing shortcuts\n.size(@width; @height) {\n  width: @width;\n  height: @height;\n}\n.square(@size) {\n  .size(@size; @size);\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n  &:-moz-placeholder            { color: @color; } // Firefox 4-18\n  &::-moz-placeholder           { color: @color; } // Firefox 19+\n  &:-ms-input-placeholder       { color: @color; } // Internet Explorer 10+\n  &::-webkit-input-placeholder  { color: @color; } // Safari and Chrome\n}\n\n// Text overflow\n// Requires inline-block or block for proper styling\n.text-overflow() {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n// CSS image replacement\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n.hide-text() {\n  font: ~\"0/0\" a;\n  color: transparent;\n  text-shadow: none;\n  background-color: transparent;\n  border: 0;\n}\n\n\n\n// CSS3 PROPERTIES\n// --------------------------------------------------\n\n// Single side border-radius\n.border-top-radius(@radius) {\n  border-top-right-radius: @radius;\n   border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n  border-bottom-right-radius: @radius;\n     border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n  border-bottom-right-radius: @radius;\n   border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n  border-bottom-left-radius: @radius;\n     border-top-left-radius: @radius;\n}\n\n// Drop shadows\n.box-shadow(@shadow) {\n  -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n          box-shadow: @shadow;\n}\n\n// Transitions\n.transition(@transition) {\n  -webkit-transition: @transition;\n          transition: @transition;\n}\n.transition-delay(@transition-delay) {\n  -webkit-transition-delay: @transition-delay;\n          transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n  -webkit-transition-duration: @transition-duration;\n          transition-duration: @transition-duration;\n}\n.transition-transform(@transition) {\n  -webkit-transition: -webkit-transform @transition;\n     -moz-transition: -moz-transform @transition;\n       -o-transition: -o-transform @transition;\n          transition: transform @transition;\n}\n\n// Transformations\n.rotate(@degrees) {\n  -webkit-transform: rotate(@degrees);\n      -ms-transform: rotate(@degrees); // IE9+\n          transform: rotate(@degrees);\n}\n.scale(@ratio) {\n  -webkit-transform: scale(@ratio);\n      -ms-transform: scale(@ratio); // IE9+\n          transform: scale(@ratio);\n}\n.translate(@x; @y) {\n  -webkit-transform: translate(@x, @y);\n      -ms-transform: translate(@x, @y); // IE9+\n          transform: translate(@x, @y);\n}\n.skew(@x; @y) {\n  -webkit-transform: skew(@x, @y);\n      -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n          transform: skew(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n  -webkit-transform: translate3d(@x, @y, @z);\n          transform: translate3d(@x, @y, @z);\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples\n.backface-visibility(@visibility){\n  -webkit-backface-visibility: @visibility;\n     -moz-backface-visibility: @visibility;\n          backface-visibility: @visibility;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n  -webkit-box-sizing: @boxmodel;\n     -moz-box-sizing: @boxmodel;\n          box-sizing: @boxmodel;\n}\n\n// User select\n// For selecting text on the page\n.user-select(@select) {\n  -webkit-user-select: @select;\n     -moz-user-select: @select;\n      -ms-user-select: @select; // IE10+\n       -o-user-select: @select;\n          user-select: @select;\n}\n\n// Resize anything\n.resizable(@direction) {\n  resize: @direction; // Options: horizontal, vertical, both\n  overflow: auto; // Safari fix\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n  -webkit-column-count: @column-count;\n     -moz-column-count: @column-count;\n          column-count: @column-count;\n  -webkit-column-gap: @column-gap;\n     -moz-column-gap: @column-gap;\n          column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n  word-wrap: break-word;\n  -webkit-hyphens: @mode;\n     -moz-hyphens: @mode;\n      -ms-hyphens: @mode; // IE10+\n       -o-hyphens: @mode;\n          hyphens: @mode;\n}\n\n// Opacity\n.opacity(@opacity) {\n  opacity: @opacity;\n  // IE8 filter\n  @opacity-ie: (@opacity * 100);\n  filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n\n\n\n// GRADIENTS\n// --------------------------------------------------\n\n#gradient {\n\n  // Horizontal gradient, from left to right\n  //\n  // Creates two color stops, start and end, by specifying a color and position for each color stop.\n  // Color stops are not available in IE9 and below.\n  .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n    background-image: -webkit-gradient(linear, @start-percent top, @end-percent top, from(@start-color), to(@end-color)); // Safari 4+, Chrome 2+\n    background-image: -webkit-linear-gradient(left, color-stop(@start-color @start-percent), color-stop(@end-color @end-percent)); // Safari 5.1+, Chrome 10+\n    background-image: -moz-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // FF 3.6+\n    background-image:  linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10\n    background-repeat: repeat-x;\n    filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n  }\n\n  // Vertical gradient, from top to bottom\n  //\n  // Creates two color stops, start and end, by specifying a color and position for each color stop.\n  // Color stops are not available in IE9 and below.\n  .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n    background-image: -webkit-gradient(linear, left @start-percent, left @end-percent, from(@start-color), to(@end-color)); // Safari 4+, Chrome 2+\n    background-image: -webkit-linear-gradient(top, @start-color, @start-percent, @end-color, @end-percent); // Safari 5.1+, Chrome 10+\n    background-image:  -moz-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // FF 3.6+\n    background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10\n    background-repeat: repeat-x;\n    filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n  }\n\n  .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n    background-repeat: repeat-x;\n    background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1+, Chrome 10+\n    background-image: -moz-linear-gradient(@deg, @start-color, @end-color); // FF 3.6+\n    background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10\n  }\n  .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n    background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@start-color), color-stop(@color-stop, @mid-color), to(@end-color));\n    background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n    background-image: -moz-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n    background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n    background-repeat: no-repeat;\n    filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n  }\n  .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@start-color), color-stop(@color-stop, @mid-color), to(@end-color));\n    background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n    background-image: -moz-linear-gradient(top, @start-color, @mid-color @color-stop, @end-color);\n    background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n    background-repeat: no-repeat;\n    filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n  }\n  .radial(@inner-color: #555; @outer-color: #333) {\n    background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@inner-color), to(@outer-color));\n    background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n    background-image: -moz-radial-gradient(circle, @inner-color, @outer-color);\n    background-image: radial-gradient(circle, @inner-color, @outer-color);\n    background-repeat: no-repeat;\n  }\n  .striped(@color: #555; @angle: 45deg) {\n    background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent));\n    background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);\n    background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);\n    background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);\n  }\n}\n\n// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n.reset-filter() {\n  filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n\n\n\n// Retina images\n//\n// Short retina mixin for setting background-image and -size\n\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n  background-image: url(\"@{file-1x}\");\n\n  @media\n  only screen and (-webkit-min-device-pixel-ratio: 2),\n  only screen and (   min--moz-device-pixel-ratio: 2),\n  only screen and (     -o-min-device-pixel-ratio: 2/1),\n  only screen and (        min-device-pixel-ratio: 2),\n  only screen and (                min-resolution: 192dpi),\n  only screen and (                min-resolution: 2dppx) {\n    background-image: url(\"@{file-2x}\");\n    background-size: @width-1x @height-1x;\n  }\n}\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n\n.img-responsive(@display: block;) {\n  display: @display;\n  max-width: 100%; // Part 1: Set a maximum relative to the parent\n  height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// COMPONENT MIXINS\n// --------------------------------------------------\n\n// Horizontal dividers\n// -------------------------\n// Dividers (basically an hr) within dropdowns and nav lists\n.nav-divider(@color: #e5e5e5) {\n  height: 1px;\n  margin: ((@line-height-computed / 2) - 1) 0;\n  overflow: hidden;\n  background-color: @color;\n}\n\n// Panels\n// -------------------------\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border;) {\n  border-color: @border;\n  & > .panel-heading {\n    color: @heading-text-color;\n    background-color: @heading-bg-color;\n    border-color: @heading-border;\n    + .panel-collapse .panel-body {\n      border-top-color: @border;\n    }\n  }\n  & > .panel-footer {\n    + .panel-collapse .panel-body {\n      border-bottom-color: @border;\n    }\n  }\n}\n\n// Alerts\n// -------------------------\n.alert-variant(@background; @border; @text-color) {\n  background-color: @background;\n  border-color: @border;\n  color: @text-color;\n  hr {\n    border-top-color: darken(@border, 5%);\n  }\n  .alert-link {\n    color: darken(@text-color, 10%);\n  }\n}\n\n// Tables\n// -------------------------\n.table-row-variant(@state; @background; @border) {\n  // Exact selectors below required to override `.table-striped` and prevent\n  // inheritance to nested tables.\n  .table > thead > tr,\n  .table > tbody > tr,\n  .table > tfoot > tr {\n    > td.@{state},\n    > th.@{state},\n    &.@{state} > td,\n    &.@{state} > th {\n      background-color: @background;\n      border-color: @border;\n    }\n  }\n\n  // Hover states for `.table-hover`\n  // Note: this is not available for cells or rows within `thead` or `tfoot`.\n  .table-hover > tbody > tr {\n    > td.@{state}:hover,\n    > th.@{state}:hover,\n    &.@{state}:hover > td {\n      background-color: darken(@background, 5%);\n      border-color: darken(@border, 5%);\n    }\n  }\n}\n\n// Button variants\n// -------------------------\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n.button-variant(@color; @background; @border) {\n  color: @color;\n  background-color: @background;\n  border-color: @border;\n\n  &:hover,\n  &:focus,\n  &:active,\n  &.active,\n  .open .dropdown-toggle& {\n    color: @color;\n    background-color: darken(@background, 8%);\n        border-color: darken(@border, 12%);\n  }\n  &:active,\n  &.active,\n  .open .dropdown-toggle& {\n    background-image: none;\n  }\n  &.disabled,\n  &[disabled],\n  fieldset[disabled] & {\n    &,\n    &:hover,\n    &:focus,\n    &:active,\n    &.active {\n      background-color: @background;\n          border-color: @border\n    }\n  }\n}\n\n// Button sizes\n// -------------------------\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n  padding: @padding-vertical @padding-horizontal;\n  font-size: @font-size;\n  line-height: @line-height;\n  border-radius: @border-radius;\n}\n\n// Pagination\n// -------------------------\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {\n  > li {\n    > a,\n    > span {\n      padding: @padding-vertical @padding-horizontal;\n      font-size: @font-size;\n    }\n    &:first-child {\n      > a,\n      > span {\n        .border-left-radius(@border-radius);\n      }\n    }\n    &:last-child {\n      > a,\n      > span {\n        .border-right-radius(@border-radius);\n      }\n    }\n  }\n}\n\n// Labels\n// -------------------------\n.label-variant(@color) {\n  background-color: @color;\n  &[href] {\n    &:hover,\n    &:focus {\n      background-color: darken(@color, 10%);\n    }\n  }\n}\n\n// Navbar vertical align\n// -------------------------\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n.navbar-vertical-align(@element-height) {\n  margin-top: ((@navbar-height - @element-height) / 2);\n  margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n\n// Progress bars\n// -------------------------\n.progress-bar-variant(@color) {\n  background-color: @color;\n  .progress-striped & {\n    #gradient > .striped(@color);\n  }\n}\n\n// Responsive utilities\n// -------------------------\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n  display: block !important;\n  tr& { display: table-row !important; }\n  th&,\n  td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n  display: none !important;\n  tr& { display: none !important; }\n  th&,\n  td& { display: none !important; }\n}\n\n// Grid System\n// -----------\n\n// Centered container element\n.container-fixed() {\n  margin-right: auto;\n  margin-left: auto;\n  padding-left:  (@grid-gutter-width / 2);\n  padding-right: (@grid-gutter-width / 2);\n  .clearfix();\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n  margin-left:  (@gutter / -2);\n  margin-right: (@gutter / -2);\n  .clearfix();\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n  position: relative;\n  float: left;\n  width: percentage((@columns / @grid-columns));\n  // Prevent columns from collapsing when empty\n  min-height: 1px;\n  // Inner gutter via padding\n  padding-left:  (@gutter / 2);\n  padding-right: (@gutter / 2);\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n  position: relative;\n  // Prevent columns from collapsing when empty\n  min-height: 1px;\n  // Inner gutter via padding\n  padding-left:  (@gutter / 2);\n  padding-right: (@gutter / 2);\n\n  // Calculate width based on number of columns available\n  @media (min-width: @screen-sm) {\n    float: left;\n    width: percentage((@columns / @grid-columns));\n  }\n}\n\n// Generate the small column offsets\n.make-sm-column-offset(@columns) {\n  @media (min-width: @screen-sm) {\n    margin-left: percentage((@columns / @grid-columns));\n  }\n}\n.make-sm-column-push(@columns) {\n  @media (min-width: @screen-sm) {\n    left: percentage((@columns / @grid-columns));\n  }\n}\n.make-sm-column-pull(@columns) {\n  @media (min-width: @screen-sm) {\n    right: percentage((@columns / @grid-columns));\n  }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n  position: relative;\n  // Prevent columns from collapsing when empty\n  min-height: 1px;\n  // Inner gutter via padding\n  padding-left:  (@gutter / 2);\n  padding-right: (@gutter / 2);\n\n  // Calculate width based on number of columns available\n  @media (min-width: @screen-md) {\n    float: left;\n    width: percentage((@columns / @grid-columns));\n  }\n}\n\n// Generate the large column offsets\n.make-md-column-offset(@columns) {\n  @media (min-width: @screen-md) {\n    margin-left: percentage((@columns / @grid-columns));\n  }\n}\n.make-md-column-push(@columns) {\n  @media (min-width: @screen-md) {\n    left: percentage((@columns / @grid-columns));\n  }\n}\n.make-md-column-pull(@columns) {\n  @media (min-width: @screen-md) {\n    right: percentage((@columns / @grid-columns));\n  }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n  position: relative;\n  // Prevent columns from collapsing when empty\n  min-height: 1px;\n  // Inner gutter via padding\n  padding-left:  (@gutter / 2);\n  padding-right: (@gutter / 2);\n\n  // Calculate width based on number of columns available\n  @media (min-width: @screen-lg) {\n    float: left;\n    width: percentage((@columns / @grid-columns));\n  }\n}\n\n// Generate the large column offsets\n.make-lg-column-offset(@columns) {\n  @media (min-width: @screen-lg) {\n    margin-left: percentage((@columns / @grid-columns));\n  }\n}\n.make-lg-column-push(@columns) {\n  @media (min-width: @screen-lg) {\n    left: percentage((@columns / @grid-columns));\n  }\n}\n.make-lg-column-pull(@columns) {\n  @media (min-width: @screen-lg) {\n    right: percentage((@columns / @grid-columns));\n  }\n}\n\n\n// Form validation states\n//\n// Used in forms.less to generate the form validation CSS for warnings, errors,\n// and successes.\n\n.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {\n  // Color the label and help text\n  .help-block,\n  .control-label {\n    color: @text-color;\n  }\n  // Set the border and box shadow on specific inputs to match\n  .form-control {\n    border-color: @border-color;\n    .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work\n    &:focus {\n      border-color: darken(@border-color, 10%);\n      @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);\n      .box-shadow(@shadow);\n    }\n  }\n  // Set validation states also for addons\n  .input-group-addon {\n    color: @text-color;\n    border-color: @border-color;\n    background-color: @background-color;\n  }\n}\n\n// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `@input-focus-border` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n\n.form-control-focus(@color: @input-border-focus) {\n  @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);\n  &:focus {\n    border-color: @color;\n    outline: 0;\n    .box-shadow(~\"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}\");\n  }\n}\n\n// Form control sizing\n//\n// Relative text size, padding, and border-radii changes for form controls. For\n// horizontal sizing, wrap controls in the predefined grid classes. `<select>`\n// element gets special love because it's special, and that's a fact!\n\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n  height: @input-height;\n  padding: @padding-vertical @padding-horizontal;\n  font-size: @font-size;\n  line-height: @line-height;\n  border-radius: @border-radius;\n\n  select& {\n    height: @input-height;\n    line-height: @input-height;\n  }\n\n  textarea& {\n    height: auto;\n  }\n}\n","modals.less":"//\n// Modals\n// --------------------------------------------------\n\n// .modal-open      - body class for killing the scroll\n// .modal           - container to scroll within\n// .modal-dialog    - positioning shell for the actual modal\n// .modal-content   - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n  overflow: hidden;\n\n\n  // Account for hiding of scrollbar\n  body&,\n  .navbar-fixed-top,\n  .navbar-fixed-bottom {\n    margin-right: 15px\n  }\n}\n\n// Container that the modal scrolls within\n.modal {\n  display: none;\n  overflow: auto;\n  overflow-y: scroll;\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: @zindex-modal-background;\n\n  // When fading in the modal, animate it to slide down\n  &.fade .modal-dialog {\n    .translate(0, -25%);\n    .transition-transform(~\"0.3s ease-out\");\n  }\n  &.in .modal-dialog { .translate(0, 0)}\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n  margin-left: auto;\n  margin-right: auto;\n  width: auto;\n  padding: 10px;\n  z-index: (@zindex-modal-background + 10);\n}\n\n// Actual modal\n.modal-content {\n  position: relative;\n  background-color: @modal-content-bg;\n  border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n  border: 1px solid @modal-content-border-color;\n  border-radius: @border-radius-large;\n  .box-shadow(0 3px 9px rgba(0,0,0,.5));\n  background-clip: padding-box;\n  // Remove focus outline from opened modal\n  outline: none;\n}\n\n// Modal background\n.modal-backdrop {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: (@zindex-modal-background - 10);\n  background-color: @modal-backdrop-bg;\n  // Fade for backdrop\n  &.fade { .opacity(0); }\n  &.in { .opacity(.5); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n  padding: @modal-title-padding;\n  border-bottom: 1px solid @modal-header-border-color;\n  min-height: (@modal-title-padding + @modal-title-line-height);\n}\n// Close icon\n.modal-header .close {\n  margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n  margin: 0;\n  line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n  position: relative;\n  padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n  margin-top: 15px;\n  padding: (@modal-inner-padding - 1) @modal-inner-padding @modal-inner-padding;\n  text-align: right; // right align buttons\n  border-top: 1px solid @modal-footer-border-color;\n  .clearfix(); // clear it in case folks use .pull-* classes on buttons\n\n  // Properly space out buttons\n  .btn + .btn {\n    margin-left: 5px;\n    margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n  }\n  // but override that for button groups\n  .btn-group .btn + .btn {\n    margin-left: -1px;\n  }\n  // and override it for block buttons as well\n  .btn-block + .btn-block {\n    margin-left: 0;\n  }\n}\n\n// Scale up the modal\n@media screen and (min-width: @screen-tablet) {\n\n  .modal-dialog {\n    left: 50%;\n    right: auto;\n    width: 600px;\n    padding-top: 30px;\n    padding-bottom: 30px;\n  }\n  .modal-content {\n    .box-shadow(0 5px 15px rgba(0,0,0,.5));\n  }\n\n}\n","navbar.less":"//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n  position: relative;\n  z-index: @zindex-navbar;\n  min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n  margin-bottom: @navbar-margin-bottom;\n  border: 1px solid transparent;\n\n  // Prevent floats from breaking the navbar\n  .clearfix();\n\n  @media (min-width: @grid-float-breakpoint) {\n    border-radius: @navbar-border-radius;\n  }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n  .clearfix();\n\n  @media (min-width: @grid-float-breakpoint) {\n    float: left;\n  }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n  max-height: 340px;\n  overflow-x: visible;\n  padding-right: @navbar-padding-horizontal;\n  padding-left:  @navbar-padding-horizontal;\n  border-top: 1px solid transparent;\n  box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n  .clearfix();\n  -webkit-overflow-scrolling: touch;\n\n  &.in {\n    overflow-y: auto;\n  }\n\n  @media (min-width: @grid-float-breakpoint) {\n    width: auto;\n    border-top: 0;\n    box-shadow: none;\n\n    &.collapse {\n      display: block !important;\n      height: auto !important;\n      padding-bottom: 0; // Override default setting\n      overflow: visible !important;\n    }\n\n    &.in {\n      overflow-y: visible;\n    }\n\n    // Account for first and last children spacing\n    .navbar-nav.navbar-left:first-child {\n      margin-left: -@navbar-padding-horizontal;\n    }\n    .navbar-nav.navbar-right:last-child {\n      margin-right: -@navbar-padding-horizontal;\n    }\n    .navbar-text:last-child {\n      margin-right: 0;\n    }\n  }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container > .navbar-header,\n.container > .navbar-collapse {\n  margin-right: -@navbar-padding-horizontal;\n  margin-left:  -@navbar-padding-horizontal;\n\n  @media (min-width: @grid-float-breakpoint) {\n    margin-right: 0;\n    margin-left:  0;\n  }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirity of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n  border-width: 0 0 1px;\n  @media (min-width: @grid-float-breakpoint) {\n    border-radius: 0;\n  }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  position: fixed;\n  right: 0;\n  left: 0;\n  border-width: 0 0 1px;\n\n  // Undo the rounded corners\n  @media (min-width: @grid-float-breakpoint) {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top {\n  z-index: @zindex-navbar-fixed;\n  top: 0;\n}\n.navbar-fixed-bottom {\n  bottom: 0;\n  margin-bottom: 0; // override .navbar defaults\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n  float: left;\n  padding: @navbar-padding-vertical @navbar-padding-horizontal;\n  font-size: @font-size-large;\n  line-height: @line-height-computed;\n  &:hover,\n  &:focus {\n    text-decoration: none;\n  }\n\n  @media (min-width: @grid-float-breakpoint) {\n    .navbar > .container & {\n      margin-left: -@navbar-padding-horizontal;\n    }\n  }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n  position: relative;\n  float: right;\n  margin-right: @navbar-padding-horizontal;\n  padding: 9px 10px;\n  .navbar-vertical-align(34px);\n  background-color: transparent;\n  border: 1px solid transparent;\n  border-radius: @border-radius-base;\n\n  // Bars\n  .icon-bar {\n    display: block;\n    width: 22px;\n    height: 2px;\n    border-radius: 1px;\n  }\n  .icon-bar + .icon-bar {\n    margin-top: 4px;\n  }\n\n  @media (min-width: @grid-float-breakpoint) {\n    display: none;\n  }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with it's own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n  margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n  > li > a {\n    padding-top:    10px;\n    padding-bottom: 10px;\n    line-height: @line-height-computed;\n  }\n\n  @media (max-width: @screen-xs-max) {\n    // Dropdowns get custom display when collapsed\n    .open .dropdown-menu {\n      position: static;\n      float: none;\n      width: auto;\n      margin-top: 0;\n      background-color: transparent;\n      border: 0;\n      box-shadow: none;\n      > li > a,\n      .dropdown-header {\n        padding: 5px 15px 5px 25px;\n      }\n      > li > a {\n        line-height: @line-height-computed;\n        &:hover,\n        &:focus {\n          background-image: none;\n        }\n      }\n    }\n  }\n\n  // Uncollapse the nav\n  @media (min-width: @grid-float-breakpoint) {\n    float: left;\n    margin: 0;\n\n    > li {\n      float: left;\n      > a {\n        padding-top: ((@navbar-height - @line-height-computed) / 2);\n        padding-bottom: ((@navbar-height - @line-height-computed) / 2);\n      }\n    }\n  }\n\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specifity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n\n@media (min-width: @grid-float-breakpoint) {\n  .navbar-left  { .pull-left(); }\n  .navbar-right { .pull-right(); }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n  margin-left: -@navbar-padding-horizontal;\n  margin-right: -@navbar-padding-horizontal;\n  padding: 10px @navbar-padding-horizontal;\n  border-top: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n  .box-shadow(@shadow);\n\n  // Mixin behavior for optimum display\n  .form-inline();\n\n  .form-group {\n    @media (max-width: @screen-xs-max) {\n      margin-bottom: 5px;\n    }\n  }\n\n  // Vertically center in expanded, horizontal navbar\n  .navbar-vertical-align(@input-height-base);\n\n  // Undo 100% width for pull classes\n  @media (min-width: @grid-float-breakpoint) {\n    width: auto;\n    border: 0;\n    margin-left: 0;\n    margin-right: 0;\n    padding-top: 0;\n    padding-bottom: 0;\n    .box-shadow(none);\n  }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n  margin-top: 0;\n  .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n  .border-bottom-radius(0);\n}\n\n// Right aligned menus need alt position\n.navbar-nav.pull-right > li > .dropdown-menu,\n.navbar-nav > li > .dropdown-menu.pull-right {\n  left: auto;\n  right: 0;\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n  .navbar-vertical-align(@input-height-base);\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n  float: left;\n  .navbar-vertical-align(@line-height-computed);\n\n  @media (min-width: @grid-float-breakpoint) {\n    margin-left: @navbar-padding-horizontal;\n    margin-right: @navbar-padding-horizontal;\n  }\n}\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n  background-color: @navbar-default-bg;\n  border-color: @navbar-default-border;\n\n  .navbar-brand {\n    color: @navbar-default-brand-color;\n    &:hover,\n    &:focus {\n      color: @navbar-default-brand-hover-color;\n      background-color: @navbar-default-brand-hover-bg;\n    }\n  }\n\n  .navbar-text {\n    color: @navbar-default-color;\n  }\n\n  .navbar-nav {\n    > li > a {\n      color: @navbar-default-link-color;\n\n      &:hover,\n      &:focus {\n        color: @navbar-default-link-hover-color;\n        background-color: @navbar-default-link-hover-bg;\n      }\n    }\n    > .active > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @navbar-default-link-active-color;\n        background-color: @navbar-default-link-active-bg;\n      }\n    }\n    > .disabled > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @navbar-default-link-disabled-color;\n        background-color: @navbar-default-link-disabled-bg;\n      }\n    }\n  }\n\n  .navbar-toggle {\n    border-color: @navbar-default-toggle-border-color;\n    &:hover,\n    &:focus {\n      background-color: @navbar-default-toggle-hover-bg;\n    }\n    .icon-bar {\n      background-color: @navbar-default-toggle-icon-bar-bg;\n    }\n  }\n\n  .navbar-collapse,\n  .navbar-form {\n    border-color: darken(@navbar-default-bg, 7%);\n  }\n\n  // Dropdown menu items and carets\n  .navbar-nav {\n    // Caret should match text color on hover\n    > .dropdown > a:hover .caret,\n    > .dropdown > a:focus .caret {\n      border-top-color: @navbar-default-link-hover-color;\n      border-bottom-color: @navbar-default-link-hover-color;\n    }\n\n    // Remove background color from open dropdown\n    > .open > a {\n      &,\n      &:hover,\n      &:focus {\n        background-color: @navbar-default-link-active-bg;\n        color: @navbar-default-link-active-color;\n        .caret {\n          border-top-color: @navbar-default-link-active-color;\n          border-bottom-color: @navbar-default-link-active-color;\n        }\n      }\n    }\n    > .dropdown > a .caret {\n      border-top-color: @navbar-default-link-color;\n      border-bottom-color: @navbar-default-link-color;\n    }\n\n\n    @media (max-width: @screen-xs-max) {\n      // Dropdowns get custom display when collapsed\n      .open .dropdown-menu {\n        > li > a {\n          color: @navbar-default-link-color;\n          &:hover,\n          &:focus {\n            color: @navbar-default-link-hover-color;\n            background-color: @navbar-default-link-hover-bg;\n          }\n        }\n        > .active > a {\n          &,\n          &:hover,\n          &:focus {\n            color: @navbar-default-link-active-color;\n            background-color: @navbar-default-link-active-bg;\n          }\n        }\n        > .disabled > a {\n          &,\n          &:hover,\n          &:focus {\n            color: @navbar-default-link-disabled-color;\n            background-color: @navbar-default-link-disabled-bg;\n          }\n        }\n      }\n    }\n  }\n\n\n  // Links in navbars\n  //\n  // Add a class to ensure links outside the navbar nav are colored correctly.\n\n  .navbar-link {\n    color: @navbar-default-link-color;\n    &:hover {\n      color: @navbar-default-link-hover-color;\n    }\n  }\n\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n  background-color: @navbar-inverse-bg;\n  border-color: @navbar-inverse-border;\n\n  .navbar-brand {\n    color: @navbar-inverse-brand-color;\n    &:hover,\n    &:focus {\n      color: @navbar-inverse-brand-hover-color;\n      background-color: @navbar-inverse-brand-hover-bg;\n    }\n  }\n\n  .navbar-text {\n    color: @navbar-inverse-color;\n  }\n\n  .navbar-nav {\n    > li > a {\n      color: @navbar-inverse-link-color;\n\n      &:hover,\n      &:focus {\n        color: @navbar-inverse-link-hover-color;\n        background-color: @navbar-inverse-link-hover-bg;\n      }\n    }\n    > .active > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @navbar-inverse-link-active-color;\n        background-color: @navbar-inverse-link-active-bg;\n      }\n    }\n    > .disabled > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @navbar-inverse-link-disabled-color;\n        background-color: @navbar-inverse-link-disabled-bg;\n      }\n    }\n  }\n\n  // Darken the responsive nav toggle\n  .navbar-toggle {\n    border-color: @navbar-inverse-toggle-border-color;\n    &:hover,\n    &:focus {\n      background-color: @navbar-inverse-toggle-hover-bg;\n    }\n    .icon-bar {\n      background-color: @navbar-inverse-toggle-icon-bar-bg;\n    }\n  }\n\n  .navbar-collapse,\n  .navbar-form {\n    border-color: darken(@navbar-inverse-bg, 7%);\n  }\n\n  // Dropdowns\n  .navbar-nav {\n    > .open > a {\n      &,\n      &:hover,\n      &:focus {\n        background-color: @navbar-inverse-link-active-bg;\n        color: @navbar-inverse-link-active-color;\n      }\n    }\n    > .dropdown > a:hover .caret {\n      border-top-color: @navbar-inverse-link-hover-color;\n      border-bottom-color: @navbar-inverse-link-hover-color;\n    }\n    > .dropdown > a .caret {\n      border-top-color: @navbar-inverse-link-color;\n      border-bottom-color: @navbar-inverse-link-color;\n    }\n    > .open > a {\n      &,\n      &:hover,\n      &:focus {\n        .caret {\n          border-top-color: @navbar-inverse-link-active-color;\n          border-bottom-color: @navbar-inverse-link-active-color;\n        }\n      }\n    }\n\n    @media (max-width: @screen-xs-max) {\n      // Dropdowns get custom display\n      .open .dropdown-menu {\n        > .dropdown-header {\n          border-color: @navbar-inverse-border;\n        }\n        > li > a {\n          color: @navbar-inverse-link-color;\n          &:hover,\n          &:focus {\n            color: @navbar-inverse-link-hover-color;\n            background-color: @navbar-inverse-link-hover-bg;\n          }\n        }\n        > .active > a {\n          &,\n          &:hover,\n          &:focus {\n            color: @navbar-inverse-link-active-color;\n            background-color: @navbar-inverse-link-active-bg;\n          }\n        }\n        > .disabled > a {\n          &,\n          &:hover,\n          &:focus {\n            color: @navbar-inverse-link-disabled-color;\n            background-color: @navbar-inverse-link-disabled-bg;\n          }\n        }\n      }\n    }\n  }\n\n  .navbar-link {\n    color: @navbar-inverse-link-color;\n    &:hover {\n      color: @navbar-inverse-link-hover-color;\n    }\n  }\n\n}\n","navs.less":"//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n  margin-bottom: 0;\n  padding-left: 0; // Override default ul/ol\n  list-style: none;\n  .clearfix();\n\n  > li {\n    position: relative;\n    display: block;\n\n    > a {\n      position: relative;\n      display: block;\n      padding: @nav-link-padding;\n      &:hover,\n      &:focus {\n        text-decoration: none;\n        background-color: @nav-link-hover-bg;\n      }\n    }\n\n    // Disabled state sets text to gray and nukes hover/tab effects\n    &.disabled > a {\n      color: @nav-disabled-link-color;\n\n      &:hover,\n      &:focus {\n        color: @nav-disabled-link-hover-color;\n        text-decoration: none;\n        background-color: transparent;\n        cursor: not-allowed;\n      }\n    }\n  }\n\n  // Open dropdowns\n  .open > a {\n    &,\n    &:hover,\n    &:focus {\n      background-color: @nav-link-hover-bg;\n      border-color: @link-color;\n    }\n  }\n\n  // Dividers (basically an hr) within the dropdown\n  .nav-divider {\n    .nav-divider();\n  }\n\n  // Prevent IE8 from misplacing imgs\n  // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n  > li > a > img {\n    max-width: none;\n  }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n  border-bottom: 1px solid @nav-tabs-border-color;\n  > li {\n    float: left;\n    // Make the list-items overlay the bottom border\n    margin-bottom: -1px;\n\n    // Actual tabs (as links)\n    > a {\n      margin-right: 2px;\n      line-height: @line-height-base;\n      border: 1px solid transparent;\n      border-radius: @border-radius-base @border-radius-base 0 0;\n      &:hover {\n        border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n      }\n    }\n\n    // Active state, and it's :hover to override normal :hover\n    &.active > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @nav-tabs-active-link-hover-color;\n        background-color: @nav-tabs-active-link-hover-bg;\n        border: 1px solid @nav-tabs-active-link-hover-border-color;\n        border-bottom-color: transparent;\n        cursor: default;\n      }\n    }\n  }\n  // pulling this in mainly for less shorthand\n  &.nav-justified {\n    .nav-justified();\n    .nav-tabs-justified();\n  }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n  > li {\n    float: left;\n\n    // Links rendered as pills\n    > a {\n      border-radius: 5px;\n    }\n    + li {\n      margin-left: 2px;\n    }\n\n    // Active state\n    &.active > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @nav-pills-active-link-hover-color;\n        background-color: @nav-pills-active-link-hover-bg;\n      }\n    }\n  }\n}\n\n\n// Stacked pills\n.nav-stacked {\n  > li {\n    float: none;\n    + li {\n      margin-top: 2px;\n      margin-left: 0; // no need for this gap between nav items\n    }\n  }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n  width: 100%;\n\n  > li {\n    float: none;\n     > a {\n      text-align: center;\n    }\n  }\n\n  @media (min-width: @screen-sm) {\n    > li {\n      display: table-cell;\n      width: 1%;\n    }\n  }\n}\n\n// Move borders to anchors instead of bottom of list\n.nav-tabs-justified {\n  border-bottom: 0;\n  > li > a {\n    border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n\n    // Override margin from .nav-tabs\n    margin-right: 0;\n  }\n  > .active > a {\n    border-bottom-color: @nav-tabs-justified-active-link-border-color;\n  }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Clear any floats\n.tabbable {\n  .clearfix();\n}\n\n// Show/hide tabbable areas\n.tab-content > .tab-pane,\n.pill-content > .pill-pane {\n  display: none;\n}\n.tab-content,\n.pill-content {\n  > .active {\n    display: block;\n  }\n}\n\n\n\n// Dropdowns\n// -------------------------\n\n// Make dropdown carets use link color in navs\n.nav .caret {\n  border-top-color: @link-color;\n  border-bottom-color: @link-color;\n}\n.nav a:hover .caret {\n  border-top-color: @link-hover-color;\n  border-bottom-color: @link-hover-color;\n}\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n  // make dropdown border overlap tab border\n  margin-top: -1px;\n  // Remove the top rounded corners here since there is a hard edge above the menu\n  .border-top-radius(0);\n}\n","normalize.less":"/*! normalize.css v2.1.0 | MIT License | git.io/normalize */\n\n// ==========================================================================\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined in IE 8/9.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nnav,\nsection,\nsummary {\n  display: block;\n}\n\n//\n// Correct `inline-block` display not defined in IE 8/9.\n//\n\naudio,\ncanvas,\nvideo {\n  display: inline-block;\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\n[hidden] {\n  display: none;\n}\n\n// ==========================================================================\n// Base\n// ==========================================================================\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS text size adjust after orientation change, without disabling\n//    user zoom.\n//\n\nhtml {\n  font-family: sans-serif; // 1\n  -webkit-text-size-adjust: 100%; // 2\n  -ms-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n  margin: 0;\n}\n\n// ==========================================================================\n// Links\n// ==========================================================================\n\n//\n// Address `outline` inconsistency between Chrome and other browsers.\n//\n\na:focus {\n  outline: thin dotted;\n}\n\n//\n// Improve readability when focused and also mouse hovered in all browsers.\n//\n\na:active,\na:hover {\n  outline: 0;\n}\n\n// ==========================================================================\n// Typography\n// ==========================================================================\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari 5, and Chrome.\n//\n\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9, Safari 5, and Chrome.\n//\n\nabbr[title] {\n  border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.\n//\n\nb,\nstrong {\n  font-weight: bold;\n}\n\n//\n// Address styling not present in Safari 5 and Chrome.\n//\n\ndfn {\n  font-style: italic;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n  -moz-box-sizing: content-box;\n  box-sizing: content-box;\n  height: 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n  background: #ff0;\n  color: #000;\n}\n\n//\n// Correct font family set oddly in Safari 5 and Chrome.\n//\n\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, serif;\n  font-size: 1em;\n}\n\n//\n// Improve readability of pre-formatted text in all browsers.\n//\n\npre {\n  white-space: pre-wrap;\n}\n\n//\n// Set consistent quote types.\n//\n\nq {\n  quotes: \"\\201C\" \"\\201D\" \"\\2018\" \"\\2019\";\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n  font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsup {\n  top: -0.5em;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\n// ==========================================================================\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9.\n//\n\nimg {\n  border: 0;\n}\n\n//\n// Correct overflow displayed oddly in IE 9.\n//\n\nsvg:not(:root) {\n  overflow: hidden;\n}\n\n// ==========================================================================\n// Figures\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari 5.\n//\n\nfigure {\n  margin: 0;\n}\n\n// ==========================================================================\n// Forms\n// ==========================================================================\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n  border: 1px solid #c0c0c0;\n  margin: 0 2px;\n  padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n  border: 0; // 1\n  padding: 0; // 2\n}\n\n//\n// 1. Correct font family not being inherited in all browsers.\n// 2. Correct font size not being inherited in all browsers.\n// 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.\n//\n\nbutton,\ninput,\nselect,\ntextarea {\n  font-family: inherit; // 1\n  font-size: 100%; // 2\n  margin: 0; // 3\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\nbutton,\ninput {\n  line-height: normal;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.\n// Correct `select` style inheritance in Firefox 4+ and Opera.\n//\n\nbutton,\nselect {\n  text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n//    and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n//    `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button; // 2\n  cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n  cursor: default;\n}\n\n//\n// 1. Address box sizing set to `content-box` in IE 8/9.\n// 2. Remove excess padding in IE 8/9.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  box-sizing: border-box; // 1\n  padding: 0; // 2\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome\n//    (include `-moz` to future-proof).\n//\n\ninput[type=\"search\"] {\n  -webkit-appearance: textfield; // 1\n  -moz-box-sizing: content-box;\n  -webkit-box-sizing: content-box; // 2\n  box-sizing: content-box;\n}\n\n//\n// Remove inner padding and search cancel button in Safari 5 and Chrome\n// on OS X.\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  border: 0;\n  padding: 0;\n}\n\n//\n// 1. Remove default vertical scrollbar in IE 8/9.\n// 2. Improve readability and alignment in all browsers.\n//\n\ntextarea {\n  overflow: auto; // 1\n  vertical-align: top; // 2\n}\n\n// ==========================================================================\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n","pager.less":"//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n  padding-left: 0;\n  margin: @line-height-computed 0;\n  list-style: none;\n  text-align: center;\n  .clearfix();\n  li {\n    display: inline;\n    > a,\n    > span {\n      display: inline-block;\n      padding: 5px 14px;\n      background-color: @pagination-bg;\n      border: 1px solid @pagination-border;\n      border-radius: @pager-border-radius;\n    }\n\n    > a:hover,\n    > a:focus {\n      text-decoration: none;\n      background-color: @pagination-hover-bg;\n    }\n  }\n\n  .next {\n    > a,\n    > span {\n      float: right;\n    }\n  }\n\n  .previous {\n    > a,\n    > span {\n      float: left;\n    }\n  }\n\n  .disabled {\n    > a,\n    > a:hover,\n    > a:focus,\n    > span {\n      color: @pager-disabled-color;\n      background-color: @pagination-bg;\n      cursor: not-allowed;\n    }\n  }\n\n}\n","pagination.less":"//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n  display: inline-block;\n  padding-left: 0;\n  margin: @line-height-computed 0;\n  border-radius: @border-radius-base;\n\n  > li {\n    display: inline; // Remove list-style and block-level defaults\n    > a,\n    > span {\n      position: relative;\n      float: left; // Collapse white-space\n      padding: @padding-base-vertical @padding-base-horizontal;\n      line-height: @line-height-base;\n      text-decoration: none;\n      background-color: @pagination-bg;\n      border: 1px solid @pagination-border;\n      margin-left: -1px;\n    }\n    &:first-child {\n      > a,\n      > span {\n        margin-left: 0;\n        .border-left-radius(@border-radius-base);\n      }\n    }\n    &:last-child {\n      > a,\n      > span {\n        .border-right-radius(@border-radius-base);\n      }\n    }\n  }\n\n  > li > a,\n  > li > span {\n    &:hover,\n    &:focus {\n      background-color: @pagination-hover-bg;\n    }\n  }\n\n  > .active > a,\n  > .active > span {\n    &,\n    &:hover,\n    &:focus {\n      z-index: 2;\n      color: @pagination-active-color;\n      background-color: @pagination-active-bg;\n      border-color: @pagination-active-bg;\n      cursor: default;\n    }\n  }\n\n  > .disabled {\n    > span,\n    > a,\n    > a:hover,\n    > a:focus {\n      color: @pagination-disabled-color;\n      background-color: @pagination-bg;\n      border-color: @pagination-border;\n      cursor: not-allowed;\n    }\n  }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n  .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n  .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);\n}\n","panels.less":"//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n  margin-bottom: @line-height-computed;\n  background-color: @panel-bg;\n  border: 1px solid transparent;\n  border-radius: @panel-border-radius;\n  .box-shadow(0 1px 1px rgba(0,0,0,.05));\n}\n\n// Panel contents\n.panel-body {\n  padding: 15px;\n  .clearfix();\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n  > .list-group {\n    margin-bottom: 0;\n\n    .list-group-item {\n      border-width: 1px 0;\n\n      // Remove border radius for top one\n      &:first-child {\n        .border-top-radius(0);\n      }\n      // But keep it for the last one\n      &:last-child {\n        border-bottom: 0;\n      }\n    }\n  }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n  .list-group-item:first-child {\n    border-top-width: 0;\n  }\n}\n\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n  > .table {\n    margin-bottom: 0;\n  }\n  > .panel-body + .table {\n    border-top: 1px solid @table-border-color;\n  }\n}\n\n\n// Optional heading\n.panel-heading {\n  padding: 10px 15px;\n  border-bottom: 1px solid transparent;\n  .border-top-radius(@panel-border-radius - 1);\n}\n\n// Within heading, strip any `h*` tag of it's default margins for spacing.\n.panel-title {\n  margin-top: 0;\n  margin-bottom: 0;\n  font-size: ceil((@font-size-base * 1.125));\n  > a {\n    color: inherit;\n  }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n  padding: 10px 15px;\n  background-color: @panel-footer-bg;\n  border-top: 1px solid @panel-inner-border;\n  .border-bottom-radius(@panel-border-radius - 1);\n}\n\n\n// Collapsable panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n  // Tighten up margin so it's only between panels\n  .panel {\n    margin-bottom: 0;\n    border-radius: @panel-border-radius;\n    overflow: hidden; // crop contents when collapsed\n    + .panel {\n      margin-top: 5px;\n    }\n  }\n\n  .panel-heading {\n    border-bottom: 0;\n    + .panel-collapse .panel-body {\n      border-top: 1px solid @panel-inner-border;\n    }\n  }\n  .panel-footer {\n    border-top: 0;\n    + .panel-collapse .panel-body {\n      border-bottom: 1px solid @panel-inner-border;\n    }\n  }\n\n  // New subcomponent for wrapping collapsable content for proper animations\n  .panel-collapse {\n\n  }\n}\n\n\n// Contextual variations\n.panel-default {\n  .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n  .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n  .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-warning {\n  .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n  .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n.panel-info {\n  .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n","popovers.less":"//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: @zindex-popover;\n  display: none;\n  max-width: @popover-max-width;\n  padding: 1px;\n  text-align: left; // Reset given new insertion method\n  background-color: @popover-bg;\n  background-clip: padding-box;\n  border: 1px solid @popover-fallback-border-color;\n  border: 1px solid @popover-border-color;\n  border-radius: @border-radius-large;\n  .box-shadow(0 5px 10px rgba(0,0,0,.2));\n\n  // Overrides for proper insertion\n  white-space: normal;\n\n  // Offset the popover to account for the popover arrow\n  &.top     { margin-top: -10px; }\n  &.right   { margin-left: 10px; }\n  &.bottom  { margin-top: 10px; }\n  &.left    { margin-left: -10px; }\n}\n\n.popover-title {\n  margin: 0; // reset heading margin\n  padding: 8px 14px;\n  font-size: @font-size-base;\n  font-weight: normal;\n  line-height: 18px;\n  background-color: @popover-title-bg;\n  border-bottom: 1px solid darken(@popover-title-bg, 5%);\n  border-radius: 5px 5px 0 0;\n}\n\n.popover-content {\n  padding: 9px 14px;\n}\n\n// Arrows\n//\n// .arrow is outer, .arrow:after is inner\n\n.popover .arrow {\n  &,\n  &:after {\n    position: absolute;\n    display: block;\n    width: 0;\n    height: 0;\n    border-color: transparent;\n    border-style: solid;\n  }\n}\n.popover .arrow {\n  border-width: @popover-arrow-outer-width;\n}\n.popover .arrow:after {\n  border-width: @popover-arrow-width;\n  content: \"\";\n}\n\n.popover {\n  &.top .arrow {\n    left: 50%;\n    margin-left: -@popover-arrow-outer-width;\n    border-bottom-width: 0;\n    border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n    border-top-color: @popover-arrow-outer-color;\n    bottom: -@popover-arrow-outer-width;\n    &:after {\n      content: \" \";\n      bottom: 1px;\n      margin-left: -@popover-arrow-width;\n      border-bottom-width: 0;\n      border-top-color: @popover-arrow-color;\n    }\n  }\n  &.right .arrow {\n    top: 50%;\n    left: -@popover-arrow-outer-width;\n    margin-top: -@popover-arrow-outer-width;\n    border-left-width: 0;\n    border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n    border-right-color: @popover-arrow-outer-color;\n    &:after {\n      content: \" \";\n      left: 1px;\n      bottom: -@popover-arrow-width;\n      border-left-width: 0;\n      border-right-color: @popover-arrow-color;\n    }\n  }\n  &.bottom .arrow {\n    left: 50%;\n    margin-left: -@popover-arrow-outer-width;\n    border-top-width: 0;\n    border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n    border-bottom-color: @popover-arrow-outer-color;\n    top: -@popover-arrow-outer-width;\n    &:after {\n      content: \" \";\n      top: 1px;\n      margin-left: -@popover-arrow-width;\n      border-top-width: 0;\n      border-bottom-color: @popover-arrow-color;\n    }\n  }\n\n  &.left .arrow {\n    top: 50%;\n    right: -@popover-arrow-outer-width;\n    margin-top: -@popover-arrow-outer-width;\n    border-right-width: 0;\n    border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n    border-left-color: @popover-arrow-outer-color;\n    &:after {\n      content: \" \";\n      right: 1px;\n      border-right-width: 0;\n      border-left-color: @popover-arrow-color;\n      bottom: -@popover-arrow-width;\n    }\n  }\n\n}\n","print.less":"//\n// Basic print styles\n// --------------------------------------------------\n// Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css\n\n@media print {\n\n  * {\n    text-shadow: none !important;\n    color: #000 !important; // Black prints faster: h5bp.com/s\n    background: transparent !important;\n    box-shadow: none !important;\n  }\n\n  a,\n  a:visited {\n    text-decoration: underline;\n  }\n\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n\n  abbr[title]:after {\n    content: \" (\" attr(title) \")\";\n  }\n\n  // Don't show links for images, or javascript/internal links\n  .ir a:after,\n  a[href^=\"javascript:\"]:after,\n  a[href^=\"#\"]:after {\n    content: \"\";\n  }\n\n  pre,\n  blockquote {\n    border: 1px solid #999;\n    page-break-inside: avoid;\n  }\n\n  thead {\n    display: table-header-group; // h5bp.com/t\n  }\n\n  tr,\n  img {\n    page-break-inside: avoid;\n  }\n\n  img {\n    max-width: 100% !important;\n  }\n\n  @page {\n    margin: 2cm .5cm;\n  }\n\n  p,\n  h2,\n  h3 {\n    orphans: 3;\n    widows: 3;\n  }\n\n  h2,\n  h3 {\n    page-break-after: avoid;\n  }\n\n  // Bootstrap components\n  .navbar {\n    display: none;\n  }\n  .table {\n    td,\n    th {\n      background-color: #fff !important;\n    }\n  }\n  .btn,\n  .dropup > .btn {\n    > .caret {\n      border-top-color: #000 !important;\n    }\n  }\n  .label {\n    border: 1px solid #000;\n  }\n\n  .table {\n    border-collapse: collapse !important;\n  }\n  .table-bordered {\n    th,\n    td {\n      border: 1px solid #ddd !important;\n    }\n  }\n\n}\n","progress-bars.less":"//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// Webkit\n@-webkit-keyframes progress-bar-stripes {\n  from  { background-position: 40px 0; }\n  to    { background-position: 0 0; }\n}\n\n// Firefox\n@-moz-keyframes progress-bar-stripes {\n  from  { background-position: 40px 0; }\n  to    { background-position: 0 0; }\n}\n\n// Opera\n@-o-keyframes progress-bar-stripes {\n  from  { background-position: 0 0; }\n  to    { background-position: 40px 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n  from  { background-position: 40px 0; }\n  to    { background-position: 0 0; }\n}\n\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n  overflow: hidden;\n  height: @line-height-computed;\n  margin-bottom: @line-height-computed;\n  background-color: @progress-bg;\n  border-radius: @border-radius-base;\n  .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n  float: left;\n  width: 0%;\n  height: 100%;\n  font-size: @font-size-small;\n  color: @progress-bar-color;\n  text-align: center;\n  background-color: @progress-bar-bg;\n  .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n  .transition(width .6s ease);\n}\n\n// Striped bars\n.progress-striped .progress-bar {\n  #gradient > .striped(@progress-bar-bg);\n  background-size: 40px 40px;\n}\n\n// Call animation for the active one\n.progress.active .progress-bar {\n  -webkit-animation: progress-bar-stripes 2s linear infinite;\n     -moz-animation: progress-bar-stripes 2s linear infinite;\n      -ms-animation: progress-bar-stripes 2s linear infinite;\n       -o-animation: progress-bar-stripes 2s linear infinite;\n          animation: progress-bar-stripes 2s linear infinite;\n}\n\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n  .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n  .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n  .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n  .progress-bar-variant(@progress-bar-danger-bg);\n}\n","responsive-utilities.less":"//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 Metro responsive\n// Required for Windows 8 Metro split-screen snapping with IE10\n//\n// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n@-ms-viewport{\n  width: device-width;\n}\n\n// IE10 on Windows Phone 8\n// IE10 on WP8 doesn't report CSS pixels, but actual device pixels. In\n// other words, say on a Lumia, you'll get 768px as the device width,\n// meaning users will see the tablet styles and not phone styles.\n//\n// Alternatively you can override this with JS (see source below), but\n// we won't be doing that here given our limited scope.\n//\n// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/\n@media screen and (max-width: 400px) {\n  @-ms-viewport{\n    width: 320px;\n  }\n}\n\n// Hide from screenreaders and browsers\n// Credit: HTML5 Boilerplate\n.hidden {\n  display: none !important;\n  visibility: hidden !important;\n}\n\n// Visibility utilities\n\n.visible-xs {\n  .responsive-invisibility();\n  @media (max-width: @screen-xs-max) {\n    .responsive-visibility();\n  }\n  &.visible-sm {\n    @media (min-width: @screen-sm) and (max-width: @screen-sm-max) {\n      .responsive-visibility();\n    }\n  }\n  &.visible-md {\n    @media (min-width: @screen-md) and (max-width: @screen-md-max) {\n      .responsive-visibility();\n    }    \n  }\n  &.visible-lg {\n    @media (min-width: @screen-lg) {\n      .responsive-visibility();\n    }    \n  }\n}\n.visible-sm {\n  .responsive-invisibility();\n  &.visible-xs {\n    @media (max-width: @screen-xs-max) {\n      .responsive-visibility();\n    }    \n  }\n  @media (min-width: @screen-sm) and (max-width: @screen-sm-max) {\n    .responsive-visibility();\n  }\n  &.visible-md {\n    @media (min-width: @screen-md) and (max-width: @screen-md-max) {\n      .responsive-visibility();\n    }    \n  }\n  &.visible-lg {\n    @media (min-width: @screen-lg) {\n      .responsive-visibility();\n    }    \n  }\n}\n.visible-md {\n  .responsive-invisibility();\n  &.visible-xs {\n    @media (max-width: @screen-xs-max) {\n      .responsive-visibility();\n    }    \n  }\n  &.visible-sm {\n    @media (min-width: @screen-sm) and (max-width: @screen-sm-max) {\n      .responsive-visibility();\n    }\n  }\n  @media (min-width: @screen-md) and (max-width: @screen-md-max) {\n    .responsive-visibility();\n  }\n  &.visible-lg {\n    @media (min-width: @screen-lg) {\n      .responsive-visibility();\n    }    \n  }\n}\n.visible-lg {\n  .responsive-invisibility();\n  &.visible-xs {\n    @media (max-width: @screen-xs-max) {\n      .responsive-visibility();\n    }    \n  }\n  &.visible-sm {\n    @media (min-width: @screen-sm) and (max-width: @screen-sm-max) {\n      .responsive-visibility();\n    }\n  }\n  &.visible-md {\n    @media (min-width: @screen-md) and (max-width: @screen-md-max) {\n      .responsive-visibility();\n    }    \n  }\n  @media (min-width: @screen-lg) {\n    .responsive-visibility();\n  }\n}\n\n.hidden-xs {\n  .responsive-visibility();\n  @media (max-width: @screen-xs-max) {\n    .responsive-invisibility();\n  }\n  &.hidden-sm {\n    @media (min-width: @screen-sm) and (max-width: @screen-sm-max) {\n      .responsive-invisibility();\n    }    \n  }\n  &.hidden-md {\n    @media (min-width: @screen-md) and (max-width: @screen-md-max) {\n      .responsive-invisibility();\n    }    \n  }\n  &.hidden-lg {\n    @media (min-width: @screen-lg) {\n      .responsive-invisibility();\n    }    \n  }\n}\n.hidden-sm {\n  .responsive-visibility();\n  &.hidden-xs {\n    @media (max-width: @screen-xs-max) {\n      .responsive-invisibility();\n    }\n  }\n  @media (min-width: @screen-sm) and (max-width: @screen-sm-max) {\n    .responsive-invisibility();\n  }\n  &.hidden-md {\n    @media (min-width: @screen-md) and (max-width: @screen-md-max) {\n      .responsive-invisibility();\n    }    \n  }\n  &.hidden-lg {\n    @media (min-width: @screen-lg) {\n      .responsive-invisibility();\n    }    \n  }\n}\n.hidden-md {\n  .responsive-visibility();\n  &.hidden-xs {\n    @media (max-width: @screen-xs-max) {\n      .responsive-invisibility();\n    }    \n  }\n  &.hidden-sm {\n    @media (min-width: @screen-sm) and (max-width: @screen-sm-max) {\n      .responsive-invisibility();\n    }    \n  }\n  @media (min-width: @screen-md) and (max-width: @screen-md-max) {\n    .responsive-invisibility();\n  }\n  &.hidden-lg {\n    @media (min-width: @screen-lg) {\n      .responsive-invisibility();\n    }    \n  }\n}\n.hidden-lg {\n  .responsive-visibility();\n  &.hidden-xs {\n    @media (max-width: @screen-xs-max) {\n      .responsive-invisibility();\n    }    \n  }\n  &.hidden-sm {\n    @media (min-width: @screen-sm) and (max-width: @screen-sm-max) {\n      .responsive-invisibility();\n    }    \n  }\n  &.hidden-md {\n    @media (min-width: @screen-md) and (max-width: @screen-md-max) {\n      .responsive-invisibility();\n    }    \n  }\n  @media (min-width: @screen-lg) {\n    .responsive-invisibility();\n  }\n}\n\n// Print utilities\n.visible-print {\n  .responsive-invisibility();\n}\n\n@media print {\n  .visible-print {\n    .responsive-visibility();\n  }\n  .hidden-print {\n    .responsive-invisibility();\n  }\n}\n","scaffolding.less":"//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n\n*,\n*:before,\n*:after {\n  .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n  font-size: 62.5%;\n  -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n  font-family: @font-family-base;\n  font-size: @font-size-base;\n  line-height: @line-height-base;\n  color: @text-color;\n  background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\n\n// Reset unusual Firefox-on-Android default style.\n//\n// See https://github.com/necolas/normalize.css/issues/214\n\nbutton,\ninput,\nselect[multiple],\ntextarea {\n  background-image: none;\n}\n\n\n// Links\n\na {\n  color: @link-color;\n  text-decoration: none;\n\n  &:hover,\n  &:focus {\n    color: @link-hover-color;\n    text-decoration: underline;\n  }\n\n  &:focus {\n    .tab-focus();\n  }\n}\n\n\n// Images\n\nimg {\n  vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n  .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n  border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n  padding: @thumbnail-padding;\n  line-height: @line-height-base;\n  background-color: @thumbnail-bg;\n  border: 1px solid @thumbnail-border;\n  border-radius: @thumbnail-border-radius;\n  .transition(all .2s ease-in-out);\n\n  // Keep them at most 100% wide\n  .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n  border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n  margin-top:    @line-height-computed;\n  margin-bottom: @line-height-computed;\n  border: 0;\n  border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content/\n\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  margin: -1px;\n  padding: 0;\n  overflow: hidden;\n  clip: rect(0 0 0 0);\n  border: 0;\n}\n","tables.less":"//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n  max-width: 100%;\n  background-color: @table-bg;\n}\nth {\n  text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n  width: 100%;\n  margin-bottom: @line-height-computed;\n  // Cells\n  thead,\n  tbody,\n  tfoot {\n    > tr {\n      > th,\n      > td {\n        padding: @table-cell-padding;\n        line-height: @line-height-base;\n        vertical-align: top;\n        border-top: 1px solid @table-border-color;\n      }\n    }\n  }\n  // Bottom align for column headings\n  thead > tr > th {\n    vertical-align: bottom;\n    border-bottom: 2px solid @table-border-color;\n  }\n  // Remove top border from thead by default\n  caption + thead,\n  colgroup + thead,\n  thead:first-child {\n    tr:first-child {\n      th, td {\n        border-top: 0;\n      }\n    }\n  }\n  // Account for multiple tbody instances\n  tbody + tbody {\n    border-top: 2px solid @table-border-color;\n  }\n\n  // Nesting\n  .table {\n    background-color: @body-bg;\n  }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n  thead,\n  tbody,\n  tfoot {\n    > tr {\n      > th,\n      > td {\n        padding: @table-condensed-cell-padding;\n      }\n    }\n  }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n  border: 1px solid @table-border-color;\n  > thead,\n  > tbody,\n  > tfoot {\n    > tr {\n      > th,\n      > td {\n        border: 1px solid @table-border-color;\n      }\n    }\n  }\n  > thead {\n    > tr {\n      > th,\n      > td {\n        border-bottom-width: 2px;\n      }\n    }\n  }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n  > tbody {\n    > tr:nth-child(odd) {\n      > td,\n      > th {\n        background-color: @table-bg-accent;\n      }\n    }\n  }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n  > tbody {\n    > tr:hover {\n      > td,\n      > th {\n        background-color: @table-bg-hover;\n      }\n    }\n  }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n  float: none;\n  display: table-column;\n}\ntable {\n  td,\n  th {\n    &[class*=\"col-\"] {\n      float: none;\n      display: table-cell;\n    }\n  }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n.table > thead > tr,\n.table > tbody > tr,\n.table > tfoot > tr {\n  > td.active,\n  > th.active,\n  &.active > td,\n  &.active > th  {\n    background-color: @table-bg-active;\n  }\n}\n\n// Generate the contextual variants\n.table-row-variant(success; @state-success-bg; @state-success-border);\n.table-row-variant(danger; @state-danger-bg; @state-danger-border);\n.table-row-variant(warning; @state-warning-bg; @state-warning-border);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-scrollable` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n@media (max-width: @screen-sm) {\n  .table-responsive {\n    width: 100%;\n    margin-bottom: 15px;\n    overflow-y: hidden;\n    overflow-x: scroll;\n    border: 1px solid @table-border-color;\n\n    // Tighten up spacing and give a background color\n    > .table {\n      margin-bottom: 0;\n      background-color: #fff;\n\n      // Ensure the content doesn't wrap\n      > thead,\n      > tbody,\n      > tfoot {\n        > tr {\n          > th,\n          > td {\n            white-space: nowrap;\n          }\n        }\n      }\n    }\n\n    // Special overrides for the bordered tables\n    > .table-bordered {\n      border: 0;\n\n      // Nuke the appropriate borders so that the parent can handle them\n      > thead,\n      > tbody,\n      > tfoot {\n        > tr {\n          > th:first-child,\n          > td:first-child {\n            border-left: 0;\n          }\n          > th:last-child,\n          > td:last-child {\n            border-right: 0;\n          }\n        }\n        > tr:last-child {\n          > th,\n          > td {\n            border-bottom: 0;\n          }\n        }\n      }\n    }\n  }\n}\n","theme.less":"\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n  text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n  @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n  .box-shadow(@shadow);\n\n  // Reset the shadow\n  &:active,\n  &.active {\n    .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n  }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555;) {\n  #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 10%));\n  border-color: darken(@btn-color, 12%);\n\n  &:active,\n  &.active {\n    background-color: darken(@btn-color, 10%);\n    border-color: darken(@btn-color, 12%);\n  }\n}\n\n// Common styles\n.btn {\n  // Remove the gradient for the pressed/active state\n  &:active,\n  &.active {\n    background-image: none;\n  }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg;); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger  { .btn-styles(@btn-danger-bg); }\n.btn-info    { .btn-styles(@btn-info-bg); }\n\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n  .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus,\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n  background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Basic navbar\n.navbar {\n  #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg;);\n  border-radius: @navbar-border-radius;\n  @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n  .box-shadow(@shadow);\n\n  .navbar-nav > .active > a {\n    background-color: @navbar-default-bg;\n  }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n  text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n  #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg;);\n\n  .navbar-nav > .active > a {\n    background-color: @navbar-inverse-bg;\n  }\n\n  .navbar-brand,\n  .navbar-nav > li > a {\n    text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n  }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  border-radius: 0;\n}\n\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n  text-shadow: 0 1px 0 rgba(255,255,255,.2);\n  @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n  .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n  #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n  border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success    { .alert-styles(@alert-success-bg); }\n.alert-info       { .alert-styles(@alert-info-bg); }\n.alert-warning    { .alert-styles(@alert-warning-bg); }\n.alert-danger     { .alert-styles(@alert-danger-bg); }\n\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n  #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg;)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n  #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar            { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success    { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info       { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning    { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger     { .progress-bar-styles(@progress-bar-danger-bg); }\n\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n  border-radius: @border-radius-base;\n  .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n  #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n  border-color: darken(@list-group-active-border, 7.5%);\n}\n\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n  .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n  #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading   { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading   { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading   { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading      { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading   { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading    { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n  #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg;);\n  border-color: darken(@well-bg, 10%);\n  @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n  .box-shadow(@shadow);\n}\n","thumbnails.less":"//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n  .img-thumbnail();\n  display: block; // Override the inline-block from `.img-thumbnail`\n\n  > img {\n    .img-responsive();\n  }\n}\n\n\n// Add a hover state for linked versions only\na.thumbnail:hover,\na.thumbnail:focus {\n  border-color: @link-color;\n}\n\n// Images and captions\n.thumbnail > img {\n  margin-left: auto;\n  margin-right: auto;\n}\n.thumbnail .caption {\n  padding: @thumbnail-caption-padding;\n  color: @thumbnail-caption-color;\n}\n","tooltip.less":"//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n  position: absolute;\n  z-index: @zindex-tooltip;\n  display: block;\n  visibility: visible;\n  font-size: @font-size-small;\n  line-height: 1.4;\n  .opacity(0);\n\n  &.in     { .opacity(.9); }\n  &.top    { margin-top:  -3px; padding: 5px 0; }\n  &.right  { margin-left:  3px; padding: 0 5px; }\n  &.bottom { margin-top:   3px; padding: 5px 0; }\n  &.left   { margin-left: -3px; padding: 0 5px; }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n  max-width: @tooltip-max-width;\n  padding: 3px 8px;\n  color: @tooltip-color;\n  text-align: center;\n  text-decoration: none;\n  background-color: @tooltip-bg;\n  border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n  position: absolute;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.tooltip {\n  &.top .tooltip-arrow {\n    bottom: 0;\n    left: 50%;\n    margin-left: -@tooltip-arrow-width;\n    border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n    border-top-color: @tooltip-arrow-color;\n  }\n  &.top-left .tooltip-arrow {\n    bottom: 0;\n    left: 5px;\n    border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n    border-top-color: @tooltip-arrow-color;\n  }\n  &.top-right .tooltip-arrow {\n    bottom: 0;\n    right: 5px;\n    border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n    border-top-color: @tooltip-arrow-color;\n  }\n  &.right .tooltip-arrow {\n    top: 50%;\n    left: 0;\n    margin-top: -@tooltip-arrow-width;\n    border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n    border-right-color: @tooltip-arrow-color;\n  }\n  &.left .tooltip-arrow {\n    top: 50%;\n    right: 0;\n    margin-top: -@tooltip-arrow-width;\n    border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n    border-left-color: @tooltip-arrow-color;\n  }\n  &.bottom .tooltip-arrow {\n    top: 0;\n    left: 50%;\n    margin-left: -@tooltip-arrow-width;\n    border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n    border-bottom-color: @tooltip-arrow-color;\n  }\n  &.bottom-left .tooltip-arrow {\n    top: 0;\n    left: 5px;\n    border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n    border-bottom-color: @tooltip-arrow-color;\n  }\n  &.bottom-right .tooltip-arrow {\n    top: 0;\n    right: 5px;\n    border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n    border-bottom-color: @tooltip-arrow-color;\n  }\n}\n","type.less":"//\n// Typography\n// --------------------------------------------------\n\n\n// Body text\n// -------------------------\n\np {\n  margin: 0 0 (@line-height-computed / 2);\n}\n.lead {\n  margin-bottom: @line-height-computed;\n  font-size: (@font-size-base * 1.15);\n  font-weight: 200;\n  line-height: 1.4;\n\n  @media (min-width: 768px) {\n    font-size: (@font-size-base * 1.5);\n  }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: 14px base font * 85% = about 12px\nsmall   { font-size: 85%; }\n\n// Undo browser default styling\ncite    { font-style: normal; }\n\n// Contextual emphasis\n.text-muted          { color: @text-muted; }\n.text-primary        { color: @brand-primary; }\n.text-warning        { color: @state-warning-text; }\n.text-danger         { color: @state-danger-text; }\n.text-success        { color: @state-success-text; }\n.text-info           { color: @state-info-text; }\n\n// Alignment\n.text-left           { text-align: left; }\n.text-right          { text-align: right; }\n.text-center         { text-align: center; }\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n  font-family: @headings-font-family;\n  font-weight: @headings-font-weight;\n  line-height: @headings-line-height;\n  small {\n    font-weight: normal;\n    line-height: 1;\n    color: @headings-small-color;\n  }\n}\n\nh1,\nh2,\nh3 {\n  margin-top: @line-height-computed;\n  margin-bottom: (@line-height-computed / 2);\n}\nh4,\nh5,\nh6 {\n  margin-top: (@line-height-computed / 2);\n  margin-bottom: (@line-height-computed / 2);\n}\n\nh1, .h1 { font-size: floor(@font-size-base * 2.60); } // ~36px\nh2, .h2 { font-size: floor(@font-size-base * 2.15); } // ~30px\nh3, .h3 { font-size: ceil(@font-size-base * 1.70); } // ~24px\nh4, .h4 { font-size: ceil(@font-size-base * 1.25); } // ~18px\nh5, .h5 { font-size:  @font-size-base; }\nh6, .h6 { font-size: ceil(@font-size-base * 0.85); } // ~12px\n\nh1 small, .h1 small { font-size: ceil(@font-size-base * 1.70); } // ~24px\nh2 small, .h2 small { font-size: ceil(@font-size-base * 1.25); } // ~18px\nh3 small, .h3 small,\nh4 small, .h4 small { font-size: @font-size-base; }\n\n\n// Page header\n// -------------------------\n\n.page-header {\n  padding-bottom: ((@line-height-computed / 2) - 1);\n  margin: (@line-height-computed * 2) 0 @line-height-computed;\n  border-bottom: 1px solid @page-header-border-color;\n}\n\n\n\n// Lists\n// --------------------------------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n  margin-top: 0;\n  margin-bottom: (@line-height-computed / 2);\n  ul,\n  ol{\n    margin-bottom: 0;\n  }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n  padding-left: 0;\n  list-style: none;\n}\n// Inline turns list items into inline-block\n.list-inline {\n  .list-unstyled();\n  > li {\n    display: inline-block;\n    padding-left: 5px;\n    padding-right: 5px;\n  }\n}\n\n// Description Lists\ndl {\n  margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n  line-height: @line-height-base;\n}\ndt {\n  font-weight: bold;\n}\ndd {\n  margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n@media (min-width: @grid-float-breakpoint) {\n  .dl-horizontal {\n    dt {\n      float: left;\n      width: (@component-offset-horizontal - 20);\n      clear: left;\n      text-align: right;\n      .text-overflow();\n    }\n    dd {\n      margin-left: @component-offset-horizontal;\n      .clearfix(); // Clear the floated `dt` if an empty `dd` is present\n    }\n  }\n}\n\n// MISC\n// ----\n\n// Abbreviations and acronyms\nabbr[title],\n// Added data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n  cursor: help;\n  border-bottom: 1px dotted @abbr-border-color;\n}\nabbr.initialism {\n  font-size: 90%;\n  text-transform: uppercase;\n}\n\n// Blockquotes\nblockquote {\n  padding: (@line-height-computed / 2) @line-height-computed;\n  margin: 0 0 @line-height-computed;\n  border-left: 5px solid @blockquote-border-color;\n  p {\n    font-size: (@font-size-base * 1.25);\n    font-weight: 300;\n    line-height: 1.25;\n  }\n  p:last-child {\n    margin-bottom: 0;\n  }\n  small {\n    display: block;\n    line-height: @line-height-base;\n    color: @blockquote-small-color;\n    &:before {\n      content: '\\2014 \\00A0';// EM DASH, NBSP\n    }\n  }\n\n  // Float right with text-align: right\n  &.pull-right {\n    padding-right: 15px;\n    padding-left: 0;\n    border-right: 5px solid @blockquote-border-color;\n    border-left: 0;\n    p,\n    small {\n      text-align: right;\n    }\n    small {\n      &:before {\n        content: '';\n      }\n      &:after {\n        content: '\\00A0 \\2014';// NBSP, EM DASH\n      }\n    }\n  }\n}\n\n// Quotes\nq:before,\nq:after,\nblockquote:before,\nblockquote:after {\n  content: \"\";\n}\n\n// Addresses\naddress {\n  display: block;\n  margin-bottom: @line-height-computed;\n  font-style: normal;\n  line-height: @line-height-base;\n}\n","utilities.less":"//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n  .clearfix();\n}\n.pull-right {\n  float: right !important;\n}\n.pull-left {\n  float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n.hide {\n  display: none !important;\n}\n.show {\n  display: block !important;\n}\n.invisible {\n  visibility: hidden;\n}\n.text-hide {\n  .hide-text();\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n  position: fixed;\n}\n","variables.less":"//\n// Variables\n// --------------------------------------------------\n\n\n// Global values\n// --------------------------------------------------\n\n// Grays\n// -------------------------\n\n@gray-darker:            lighten(#000, 13.5%); // #222\n@gray-dark:              lighten(#000, 20%);   // #333\n@gray:                   lighten(#000, 33.5%); // #555\n@gray-light:             lighten(#000, 60%);   // #999\n@gray-lighter:           lighten(#000, 93.5%); // #eee\n\n// Brand colors\n// -------------------------\n\n@brand-primary:         #428bca;\n@brand-success:         #5cb85c;\n@brand-warning:         #f0ad4e;\n@brand-danger:          #d9534f;\n@brand-info:            #5bc0de;\n\n// Scaffolding\n// -------------------------\n\n@body-bg:               #fff;\n@text-color:            @gray-dark;\n\n// Links\n// -------------------------\n\n@link-color:            @brand-primary;\n@link-hover-color:      darken(@link-color, 15%);\n\n// Typography\n// -------------------------\n\n@font-family-sans-serif:  \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n@font-family-serif:       Georgia, \"Times New Roman\", Times, serif;\n@font-family-monospace:   Monaco, Menlo, Consolas, \"Courier New\", monospace;\n@font-family-base:        @font-family-sans-serif;\n\n@font-size-base:          14px;\n@font-size-large:         ceil(@font-size-base * 1.25); // ~18px\n@font-size-small:         ceil(@font-size-base * 0.85); // ~12px\n\n@line-height-base:        1.428571429; // 20/14\n@line-height-computed:    floor(@font-size-base * @line-height-base); // ~20px\n\n@headings-font-family:    @font-family-base;\n@headings-font-weight:    500;\n@headings-line-height:    1.1;\n\n// Iconography\n// -------------------------\n\n@icon-font-path:          \"../fonts/\";\n@icon-font-name:          \"glyphicons-halflings-regular\";\n\n\n// Components\n// -------------------------\n// Based on 14px font-size and 1.428 line-height (~20px to start)\n\n@padding-base-vertical:          6px;\n@padding-base-horizontal:        12px;\n\n@padding-large-vertical:         10px;\n@padding-large-horizontal:       16px;\n\n@padding-small-vertical:         5px;\n@padding-small-horizontal:       10px;\n\n@line-height-large:              1.33;\n@line-height-small:              1.5;\n\n@border-radius-base:             4px;\n@border-radius-large:            6px;\n@border-radius-small:            3px;\n\n@component-active-bg:            @brand-primary;\n\n@caret-width-base:               4px;\n@caret-width-large:              5px;\n\n// Tables\n// -------------------------\n\n@table-cell-padding:                 8px;\n@table-condensed-cell-padding:       5px;\n\n@table-bg:                           transparent; // overall background-color\n@table-bg-accent:                    #f9f9f9; // for striping\n@table-bg-hover:                     #f5f5f5;\n@table-bg-active:                    @table-bg-hover;\n\n@table-border-color:                 #ddd; // table and cell border\n\n\n// Buttons\n// -------------------------\n\n@btn-font-weight:                normal;\n\n@btn-default-color:              #333;\n@btn-default-bg:                 #fff;\n@btn-default-border:             #ccc;\n\n@btn-primary-color:              #fff;\n@btn-primary-bg:                 @brand-primary;\n@btn-primary-border:             darken(@btn-primary-bg, 5%);\n\n@btn-success-color:              #fff;\n@btn-success-bg:                 @brand-success;\n@btn-success-border:             darken(@btn-success-bg, 5%);\n\n@btn-warning-color:              #fff;\n@btn-warning-bg:                 @brand-warning;\n@btn-warning-border:             darken(@btn-warning-bg, 5%);\n\n@btn-danger-color:               #fff;\n@btn-danger-bg:                  @brand-danger;\n@btn-danger-border:              darken(@btn-danger-bg, 5%);\n\n@btn-info-color:                 #fff;\n@btn-info-bg:                    @brand-info;\n@btn-info-border:                darken(@btn-info-bg, 5%);\n\n@btn-link-disabled-color:        @gray-light;\n\n\n// Forms\n// -------------------------\n\n@input-bg:                       #fff;\n@input-bg-disabled:              @gray-lighter;\n\n@input-color:                    @gray;\n@input-border:                   #ccc;\n@input-border-radius:            @border-radius-base;\n@input-border-focus:             #66afe9;\n\n@input-color-placeholder:        @gray-light;\n\n@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);\n@input-height-large:             (floor(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);\n@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);\n\n@legend-color:                   @gray-dark;\n@legend-border-color:            #e5e5e5;\n\n@input-group-addon-bg:           @gray-lighter;\n@input-group-addon-border-color: @input-border;\n\n\n// Dropdowns\n// -------------------------\n\n@dropdown-bg:                    #fff;\n@dropdown-border:                rgba(0,0,0,.15);\n@dropdown-fallback-border:       #ccc;\n@dropdown-divider-bg:            #e5e5e5;\n\n@dropdown-link-active-color:     #fff;\n@dropdown-link-active-bg:        @component-active-bg;\n\n@dropdown-link-color:            @gray-dark;\n@dropdown-link-hover-color:      #fff;\n@dropdown-link-hover-bg:         @dropdown-link-active-bg;\n\n@dropdown-link-disabled-color:   @gray-light;\n\n@dropdown-header-color:          @gray-light;\n\n@dropdown-caret-color:           #000;\n\n\n// COMPONENT VARIABLES\n// --------------------------------------------------\n\n\n// Z-index master list\n// -------------------------\n// Used for a bird's eye view of components dependent on the z-axis\n// Try to avoid customizing these :)\n\n@zindex-navbar:            1000;\n@zindex-dropdown:          1000;\n@zindex-popover:           1010;\n@zindex-tooltip:           1030;\n@zindex-navbar-fixed:      1030;\n@zindex-modal-background:  1040;\n@zindex-modal:             1050;\n\n// Media queries breakpoints\n// --------------------------------------------------\n\n// Extra small screen / phone\n@screen-xs:                  480px;\n@screen-phone:               @screen-xs;\n\n// Small screen / tablet\n@screen-sm:                  768px;\n@screen-tablet:              @screen-sm;\n\n// Medium screen / desktop\n@screen-md:                  992px;\n@screen-desktop:             @screen-md;\n\n// Large screen / wide desktop\n@screen-lg:                  1200px;\n@screen-lg-desktop:          @screen-lg;\n\n// So media queries don't overlap when required, provide a maximum\n@screen-xs-max:              (@screen-sm - 1);\n@screen-sm-max:              (@screen-md - 1);\n@screen-md-max:              (@screen-lg - 1);\n\n\n// Grid system\n// --------------------------------------------------\n\n// Number of columns in the grid system\n@grid-columns:              12;\n// Padding, to be divided by two and applied to the left and right of all columns\n@grid-gutter-width:         30px;\n// Point at which the navbar stops collapsing\n@grid-float-breakpoint:     @screen-tablet;\n\n\n// Navbar\n// -------------------------\n\n// Basics of a navbar\n@navbar-height:                    50px;\n@navbar-margin-bottom:             @line-height-computed;\n@navbar-default-color:             #777;\n@navbar-default-bg:                #f8f8f8;\n@navbar-default-border:            darken(@navbar-default-bg, 6.5%);\n@navbar-border-radius:             @border-radius-base;\n@navbar-padding-horizontal:        floor(@grid-gutter-width / 2);\n@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);\n\n// Navbar links\n@navbar-default-link-color:                #777;\n@navbar-default-link-hover-color:          #333;\n@navbar-default-link-hover-bg:             transparent;\n@navbar-default-link-active-color:         #555;\n@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);\n@navbar-default-link-disabled-color:       #ccc;\n@navbar-default-link-disabled-bg:          transparent;\n\n// Navbar brand label\n@navbar-default-brand-color:               @navbar-default-link-color;\n@navbar-default-brand-hover-color:         darken(@navbar-default-link-color, 10%);\n@navbar-default-brand-hover-bg:            transparent;\n\n// Navbar toggle\n@navbar-default-toggle-hover-bg:           #ddd;\n@navbar-default-toggle-icon-bar-bg:        #ccc;\n@navbar-default-toggle-border-color:       #ddd;\n\n\n// Inverted navbar\n//\n// Reset inverted navbar basics\n@navbar-inverse-color:                      @gray-light;\n@navbar-inverse-bg:                         #222;\n@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);\n\n// Inverted navbar links\n@navbar-inverse-link-color:                 @gray-light;\n@navbar-inverse-link-hover-color:           #fff;\n@navbar-inverse-link-hover-bg:              transparent;\n@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;\n@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);\n@navbar-inverse-link-disabled-color:        #444;\n@navbar-inverse-link-disabled-bg:           transparent;\n\n// Inverted navbar brand label\n@navbar-inverse-brand-color:                @navbar-inverse-link-color;\n@navbar-inverse-brand-hover-color:          #fff;\n@navbar-inverse-brand-hover-bg:             transparent;\n\n// Inverted navbar search\n// Normal navbar needs no special styles or vars\n@navbar-inverse-search-bg:                  lighten(@navbar-inverse-bg, 25%);\n@navbar-inverse-search-bg-focus:            #fff;\n@navbar-inverse-search-border:              @navbar-inverse-bg;\n@navbar-inverse-search-placeholder-color:   #ccc;\n\n// Inverted navbar toggle\n@navbar-inverse-toggle-hover-bg:            #333;\n@navbar-inverse-toggle-icon-bar-bg:         #fff;\n@navbar-inverse-toggle-border-color:        #333;\n\n\n// Navs\n// -------------------------\n\n@nav-link-padding:                          10px 15px;\n@nav-link-hover-bg:                         @gray-lighter;\n\n@nav-disabled-link-color:                   @gray-light;\n@nav-disabled-link-hover-color:             @gray-light;\n\n@nav-open-link-hover-color:                 #fff;\n@nav-open-caret-border-color:               #fff;\n\n// Tabs\n@nav-tabs-border-color:                     #ddd;\n\n@nav-tabs-link-hover-border-color:          @gray-lighter;\n\n@nav-tabs-active-link-hover-bg:             @body-bg;\n@nav-tabs-active-link-hover-color:          @gray;\n@nav-tabs-active-link-hover-border-color:   #ddd;\n\n@nav-tabs-justified-link-border-color:            #ddd;\n@nav-tabs-justified-active-link-border-color:     @body-bg;\n\n// Pills\n@nav-pills-active-link-hover-bg:            @component-active-bg;\n@nav-pills-active-link-hover-color:         #fff;\n\n\n// Pagination\n// -------------------------\n\n@pagination-bg:                        #fff;\n@pagination-border:                    #ddd;\n\n@pagination-hover-bg:                  @gray-lighter;\n\n@pagination-active-bg:                 @brand-primary;\n@pagination-active-color:              #fff;\n\n@pagination-disabled-color:            @gray-light;\n\n\n// Pager\n// -------------------------\n\n@pager-border-radius:                  15px;\n@pager-disabled-color:                 @gray-light;\n\n\n// Jumbotron\n// -------------------------\n\n@jumbotron-padding:              30px;\n@jumbotron-color:                inherit;\n@jumbotron-bg:                   @gray-lighter;\n\n@jumbotron-heading-color:        inherit;\n\n\n// Form states and alerts\n// -------------------------\n\n@state-warning-text:             #c09853;\n@state-warning-bg:               #fcf8e3;\n@state-warning-border:           darken(spin(@state-warning-bg, -10), 3%);\n\n@state-danger-text:              #b94a48;\n@state-danger-bg:                #f2dede;\n@state-danger-border:            darken(spin(@state-danger-bg, -10), 3%);\n\n@state-success-text:             #468847;\n@state-success-bg:               #dff0d8;\n@state-success-border:           darken(spin(@state-success-bg, -10), 5%);\n\n@state-info-text:                #3a87ad;\n@state-info-bg:                  #d9edf7;\n@state-info-border:              darken(spin(@state-info-bg, -10), 7%);\n\n\n// Tooltips\n// -------------------------\n@tooltip-max-width:           200px;\n@tooltip-color:               #fff;\n@tooltip-bg:                  #000;\n\n@tooltip-arrow-width:         5px;\n@tooltip-arrow-color:         @tooltip-bg;\n\n\n// Popovers\n// -------------------------\n@popover-bg:                          #fff;\n@popover-max-width:                   276px;\n@popover-border-color:                rgba(0,0,0,.2);\n@popover-fallback-border-color:       #ccc;\n\n@popover-title-bg:                    darken(@popover-bg, 3%);\n\n@popover-arrow-width:                 10px;\n@popover-arrow-color:                 #fff;\n\n@popover-arrow-outer-width:           (@popover-arrow-width + 1);\n@popover-arrow-outer-color:           rgba(0,0,0,.25);\n@popover-arrow-outer-fallback-color:  #999;\n\n\n// Labels\n// -------------------------\n\n@label-default-bg:            @gray-light;\n@label-primary-bg:            @brand-primary;\n@label-success-bg:            @brand-success;\n@label-info-bg:               @brand-info;\n@label-warning-bg:            @brand-warning;\n@label-danger-bg:             @brand-danger;\n\n@label-color:                 #fff;\n@label-link-hover-color:      #fff;\n\n\n// Modals\n// -------------------------\n@modal-inner-padding:         20px;\n\n@modal-title-padding:         15px;\n@modal-title-line-height:     @line-height-base;\n\n@modal-content-bg:                             #fff;\n@modal-content-border-color:                   rgba(0,0,0,.2);\n@modal-content-fallback-border-color:          #999;\n\n@modal-backdrop-bg:           #000;\n@modal-header-border-color:   #e5e5e5;\n@modal-footer-border-color:   @modal-header-border-color;\n\n\n// Alerts\n// -------------------------\n@alert-padding:               15px;\n@alert-border-radius:         @border-radius-base;\n@alert-link-font-weight:      bold;\n\n@alert-success-bg:            @state-success-bg;\n@alert-success-text:          @state-success-text;\n@alert-success-border:        @state-success-border;\n\n@alert-info-bg:               @state-info-bg;\n@alert-info-text:             @state-info-text;\n@alert-info-border:           @state-info-border;\n\n@alert-warning-bg:            @state-warning-bg;\n@alert-warning-text:          @state-warning-text;\n@alert-warning-border:        @state-warning-border;\n\n@alert-danger-bg:             @state-danger-bg;\n@alert-danger-text:           @state-danger-text;\n@alert-danger-border:         @state-danger-border;\n\n\n// Progress bars\n// -------------------------\n@progress-bg:                 #f5f5f5;\n@progress-bar-color:          #fff;\n\n@progress-bar-bg:             @brand-primary;\n@progress-bar-success-bg:     @brand-success;\n@progress-bar-warning-bg:     @brand-warning;\n@progress-bar-danger-bg:      @brand-danger;\n@progress-bar-info-bg:        @brand-info;\n\n\n// List group\n// -------------------------\n@list-group-bg:               #fff;\n@list-group-border:           #ddd;\n@list-group-border-radius:    @border-radius-base;\n\n@list-group-hover-bg:         #f5f5f5;\n@list-group-active-color:     #fff;\n@list-group-active-bg:        @component-active-bg;\n@list-group-active-border:    @list-group-active-bg;\n\n@list-group-link-color:          #555;\n@list-group-link-heading-color:  #333;\n\n\n// Panels\n// -------------------------\n@panel-bg:                    #fff;\n@panel-inner-border:          #ddd;\n@panel-border-radius:         @border-radius-base;\n@panel-footer-bg:             #f5f5f5;\n\n@panel-default-text:          @gray-dark;\n@panel-default-border:        #ddd;\n@panel-default-heading-bg:    #f5f5f5;\n\n@panel-primary-text:          #fff;\n@panel-primary-border:        @brand-primary;\n@panel-primary-heading-bg:    @brand-primary;\n\n@panel-success-text:          @state-success-text;\n@panel-success-border:        @state-success-border;\n@panel-success-heading-bg:    @state-success-bg;\n\n@panel-warning-text:          @state-warning-text;\n@panel-warning-border:        @state-warning-border;\n@panel-warning-heading-bg:    @state-warning-bg;\n\n@panel-danger-text:           @state-danger-text;\n@panel-danger-border:         @state-danger-border;\n@panel-danger-heading-bg:     @state-danger-bg;\n\n@panel-info-text:             @state-info-text;\n@panel-info-border:           @state-info-border;\n@panel-info-heading-bg:       @state-info-bg;\n\n\n// Thumbnails\n// -------------------------\n@thumbnail-padding:           4px;\n@thumbnail-bg:                @body-bg;\n@thumbnail-border:            #ddd;\n@thumbnail-border-radius:     @border-radius-base;\n\n@thumbnail-caption-color:     @text-color;\n@thumbnail-caption-padding:   9px;\n\n\n// Wells\n// -------------------------\n@well-bg:                     #f5f5f5;\n\n\n// Badges\n// -------------------------\n@badge-color:                 #fff;\n@badge-link-hover-color:      #fff;\n@badge-bg:                    @gray-light;\n\n@badge-active-color:          @link-color;\n@badge-active-bg:             #fff;\n\n@badge-font-weight:           bold;\n@badge-line-height:           1;\n@badge-border-radius:         10px;\n\n\n// Breadcrumbs\n// -------------------------\n@breadcrumb-bg:               #f5f5f5;\n@breadcrumb-color:            #ccc;\n@breadcrumb-active-color:     @gray-light;\n\n\n// Carousel\n// ------------------------\n\n@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);\n\n@carousel-control-color:                      #fff;\n@carousel-control-width:                      15%;\n@carousel-control-opacity:                    .5;\n@carousel-control-font-size:                  20px;\n\n@carousel-indicator-active-bg:                #fff;\n@carousel-indicator-border-color:             #fff;\n\n@carousel-caption-color:                      #fff;\n\n\n// Close\n// ------------------------\n@close-color:                 #000;\n@close-font-weight:           bold;\n@close-text-shadow:           0 1px 0 #fff;\n\n\n// Code\n// ------------------------\n@code-color:                  #c7254e;\n@code-bg:                     #f9f2f4;\n\n@pre-bg:                      #f5f5f5;\n@pre-color:                   @gray-dark;\n@pre-border-color:            #ccc;\n@pre-scrollable-max-height:   340px;\n\n// Type\n// ------------------------\n@text-muted:                  @gray-light;\n@abbr-border-color:           @gray-light;\n@headings-small-color:        @gray-light;\n@blockquote-small-color:      @gray-light;\n@blockquote-border-color:     @gray-lighter;\n@page-header-border-color:    @gray-lighter;\n\n// Miscellaneous\n// -------------------------\n\n// Hr border color\n@hr-border:                   @gray-lighter;\n\n// Horizontal forms & lists\n@component-offset-horizontal: 180px;\n\n\n// Container sizes\n// --------------------------------------------------\n\n// Small screen / tablet\n@container-tablet:            ((720px + @grid-gutter-width));\n\n// Medium screen / desktop\n@container-desktop:           ((940px + @grid-gutter-width));\n\n// Large screen / wide desktop\n@container-lg-desktop:        ((1140px + @grid-gutter-width));\n","wells.less":"//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n  min-height: 20px;\n  padding: 19px;\n  margin-bottom: 20px;\n  background-color: @well-bg;\n  border: 1px solid darken(@well-bg, 7%);\n  border-radius: @border-radius-base;\n  .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));\n  blockquote {\n    border-color: #ddd;\n    border-color: rgba(0,0,0,.15);\n  }\n}\n\n// Sizes\n.well-lg {\n  padding: 24px;\n  border-radius: @border-radius-large;\n}\n.well-sm {\n  padding: 9px;\n  border-radius: @border-radius-small;\n}\n"}
+var __fonts = {"glyphicons-halflings-regular.eot":"�6\u0000\u0000�5\u0000\u0000\u0002\u0000\u0002\u0000\u0004\u0000\u0000\u0000\u0000\u0000\u0005\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0000�\u0001\u0000\u0000\u0004\u0000LP\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000��\u0018\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000(\u0000G\u0000L\u0000Y\u0000P\u0000H\u0000I\u0000C\u0000O\u0000N\u0000S\u0000 \u0000H\u0000a\u0000l\u0000f\u0000l\u0000i\u0000n\u0000g\u0000s\u0000\u0000\u0000\u000e\u0000R\u0000e\u0000g\u0000u\u0000l\u0000a\u0000r\u0000\u0000\u0000x\u0000V\u0000e\u0000r\u0000s\u0000i\u0000o\u0000n\u0000 \u00001\u0000.\u00000\u00000\u00001\u0000;\u0000P\u0000S\u0000 \u00000\u00000\u00001\u0000.\u00000\u00000\u00001\u0000;\u0000h\u0000o\u0000t\u0000c\u0000o\u0000n\u0000v\u0000 \u00001\u0000.\u00000\u0000.\u00007\u00000\u0000;\u0000m\u0000a\u0000k\u0000e\u0000o\u0000t\u0000f\u0000.\u0000l\u0000i\u0000b\u00002\u0000.\u00005\u0000.\u00005\u00008\u00003\u00002\u00009\u0000\u0000\u00008\u0000G\u0000L\u0000Y\u0000P\u0000H\u0000I\u0000C\u0000O\u0000N\u0000S\u0000 \u0000H\u0000a\u0000l\u0000f\u0000l\u0000i\u0000n\u0000g\u0000s\u0000 \u0000R\u0000e\u0000g\u0000u\u0000l\u0000a\u0000r\u0000\u0000\u0000\u0000\u0000BSGP\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0003\u0000w�\u00005s\u00005y\u0000-�\u0012�����\u0011՟(tۊK��\u0010D��'P\u000b�M�\n�\u0000B�j\u001e���beJ2cc��LF\u001e1+�WEeuJ\u0019��e�~m��%���W��*�IzI\u0017€�\u0004sL��\tx\n\n��4�xїPS��-Uu�T\fí²˜E��F�\u0003\u0003��?ͬ\tԯ�4�\rʨg��\u001cq �e�$�{��-+�\u0012\u00021u\u0001�\u0006���{���S��\"!E�B&/\r���L��\u0002�E�\u0005�K�\u0015�7f��Ү�|�'�=����j��p��E�A_B�@��*�\u001d?~���\r�\u00169�&\u0005�I���v���@\u0017e��r��\u0001�>Mo<�LX���\u0004�%���a>˒ �\u0002�jp\u001d?P�;�_��\b��iƶ� <��}LbX�Ue1\u0012�L\u0011\f\t�!�;���\f\f�\u0006�\tD\u000b�^%� �\tPP��\u0015A�@��\u0002�5\u000e����K\u000ea�lӴğ�a!\u0010������|Zh�\u0010��FFI�0�E�F\"����'Mո�\u00069\u0004�@�)�\f1A�����ȶ���р���<\u0001�\f�\u001e\u0015��,�@��A�\tpA��Cϲ�\\�.�\u0000\u0010mȤ�Ix���\u0006��sS\n\b\"�r�r}�n\u0019N�����~lQ��o�`>�t��\u0016��鰅\u0006��׶������\u00125����\fX`\n­\u0001g\u00129K��\u0003� �g\u0002�\u0005]�s �z\u0005T0%j��kT���g̙0\"�\u0004Y\u001cVg��\u0013@\u000e��)��\u0001>aF\bc\u0000!t\u0000z�\u0000��\u0014�=\u0006V��k���@op^v�?�J��=f���0[7��@��}�t\u0001��\\��\u0015��}��\u001cv\\|f\u0019\u000f{�y�?=\u001f�1\"f����\u000fx���Hxx�i�\u000f8�k�PY�8��\u0007~��\u001d�\u000e�7@�(:3v�𰬨X�X+(\u000b\u0017�\u0002�����F\u000bj\u001a�\u0016�19�\b�\u0013�'(m�}��\f�\u000f�e\u000e�\u000b��C��Œ�=��\u0004&�\f�\u0014�e\ny\u0006\u0019��E\u0006��B\u0000�(�2���E?\u0001xv5�ki{P\"�I�G�kXLP�ŀE�>G��[�|q!�c�+o0�\na�\u0004�~�\\��\u0019;��!\u0011�!�+�\u001d�uX1�H�\"����������\b�U94%��`����)��y�\u001f\u00061�*����/\u0018��:>\\g�@�<H��HܶW��~}�����:�\u0015�h\u000bf\u001b���lZ�4�Y��\u000b���?\u001czU��\u0002���AUq:�>x�����Z��.]�\u0006�A��\u0004��ҁ�K��/\n�{[\u00178�]�b�.q~���2�D�oR�zK��oGl���{E��\u0001\u001a��\u000b9��\t�E\r�ZR�\u0013}3\u0012QO��Е6��C�).�R���%\bOy\u000e����r\n�#\u0015��Q��V?l��G�I��V�\tT.�R���t\r\u001c\u0011�ދ����mݯ�mb�v\u0001\u0000�vtO\u000fn#<�u\u001c�9��\u0017p3:��\u0011�)�%�\u001e�Tz��\u0003�\u0012\u0016\u001a��c\u001a��\fB\u0004W�c\u000b������\ncFvEZ�O�Ik��\u0010M\u0010\u0014�Y�u\u000fj%[�(\u00110i�\u0011\u0003�>)�aQy}�vb2��'*�b�G��\r���~\t�5�s�'����\u0002�å�vx\u001e\u00118\u0007)#7J\f��\r\u0002���@IƠ�\u000b\u0016�`�S\u0017(E�˖\u0016B��!\u000e\u0001\u0002$<\u0015�\u0000sm���{э� �����!`x뚢\"ZaB3����\u0004x��W��[\u0004׃���j\u0010\u0006\u0002ƃh�%pt\u0004K\u0001�[�d���\bC$U\u0014C@\\)�A~\u0014q�\u0005����\u0013�\u0019/�\u0016@\u0005\u0018\u0001zn�2\u0011�\u0016e?9�ڟ�N�ظQ�91��Ba����a�0�#�\u001cژ.3\u000e���\u0018����������+z%�\u0010Ԇ�\b��\u001a\u001fEڔs�A\u0017\u0012\u001a\u001c\u000b\r�\u0012U�|\u001a�h��@��إs5S8*v0\u0018\u001cD�����U�\u001dYm\u0007���I���s���:��Dt��Xr�/���0[L\r\u001b\u0006N�\t���\u0005�I���Ċ$a����pp���\u0003MNÊ¢7��tD�I'al�Y\"4\u0017��X\t�r��[�{\u001d_�\u0012MW� \u0015X1�<��c��)�L\u0019|���S\u0000�Q��͖�V�\u0019��F��\u0002F��\u0004\u0016\u0001t\u001bu��4d��r\u0018-Mv�M�u�S���\u00178�91c���\nÒ`�\fAI��#\\�*p�X�׼�)���4%>��\u001a�bm�]�5��5�N\u0017=G���\u0000\u0001#�r���96#R\u0003���\u0015\nYi���\f�\u0006+\u0007\u000bf\u0000'F5\f8@\u0004�x�4���K���.��- 2��:�Q\u0003\t�4<y\nʃ9i\u0004��\u001f�V3:��<\u00167>܏\u0004�\u00175\u0001\f�2\u001c\u001a�X\u001eC�7\u0010`M-��j�\n\u0000>;~�L��5�n�H\u0013��HF4�K߹�Y�p��r�\u0002ws�4�݌F�۝��F�:�\u00070Ó¢\u0001�L[f|��\u0006��\u0006��J�����֠:\u0013h�$�I\u0001��ry(*�u&\u001d~K�e��`�\f��73)\u0010�W�$ %ض����p�@\u0013C�\\�r|S�\u0012\"�\u001a�E\"\u0018���0i�r\u0002O�Dh�md#$4E\u001b��@J��\"����T\u0017�\u001f(��L�9ٴ�N�CxM�����\r�}D�-�BI��rx�\u001c\b�)\\)\u0001�gU�J�i����u�O�\u00151T\u0007>�|��>J��D��bs�-�@\u0016�\u0010E�@�\u0013]�8�������B��𴼅�ږ��\u000b�X�_3\u0010�U\n\b�\f�dD�ǁ�_� ��\u0005���\f\u0004�t��\u0001��K�4\u0010n,�D�\u001f���\"6��@\u0005���뇂\u0005� ��F\u001e�m�`Y�\u0012\u0004�6�\u0016'3¨�\u001f�\u00162l�\u0004p\u000fM\u0003�`<�hW�\"��P�@����\u0019\t(J(�^�3��O�y�Lq����]\u0012(D��R�r\u0019c�{e����\\3P\u0016!}\u0010\"��ǼVt�)w���\u00194?��u~�D��\u001a��U2]�\u00190�OVF�6-���M��)���eY�l.H��Rψ_�A��J�\u000bÊ»\u0018Y�\u000e咓oIF��Zt:\u0013�٬��\u0013·�����h\"\u0012\u0006B������&���\"�ny󘋉A��-���x#�~6�շ��hX�����{�9A�\"�5io�rj�{�H8�)��IZ^м\u0004�ij@.@�ΓF\u001e.���\u001c\u001c\u0019R�K�A�D �d\u0011��\u0001�Q\u0010�,�\f���a\u0004[\u001aq�g�@�\t�D_���#E�u�)�R1��B]F\n�u�V\u0003�����G{\u000e�����\u001a�\u0004��x\f�\u001e]ε(@\u0018\u0001m�\rl\u0011���f�����P�:�H���\u0000s�,i\u0014�����Q>2�\"d[\r(�\u0010� ��\f/�uҏ�bS�L\u0011˳�.\u001b�\"�:�\u0018A�U@�p��ֽR\u0001�E���{�\u001f�\u001bB\u001b��H���ƥ�!\u0005I���a�\u000b-w\"�)w%���᜙�����\u001brw��ͳ�\u000er�6��!�1�Y��\u0000˘\u0016LT�p��x��\u0018�r�\u0006�8\u0014]\u0005�\u0011>҈\u0004��S<h�yj�C\u0013�\u0015���]��K�ŀĬ&\\v�C`�b�#a���[]�I���F\\k൥�X]��`�e�MX�\u0013dT�2��f���}����^n�N��$��@�ٚ��z��R\u0012:�q\"S��Y�\b�0��J�/��թY3\u001a�ę�Ů�#�q��\u001fY\u0015\u0018�s�����|HJܢ�ǔ0�ق�\u0004�v�8�4�����Q\u0011\u001bv�y��PÑ¡C�;�Μ{\u0002x8`3G��fN����[V�߾\u000eP�\t�U�\u0013\u0006�]]�JO��6\u000e��r�_t~o��>\"�#^�m�ꤾ�\u0014�B\\\r��i�&U�=��Q\u001d�1ğ,?�t&r,@l�5Aی�J��6\\��\u0007�w��x7I�\u0013\\`n�sKpÊ´\b�݀׼\u0014\f�2\u000bL�\u0005\u0000��\b'�M3\b��\u000f���Ki[v&V\u0002�tF�TH�\nrx�\u0001��TUY�Ee�}\u0001���\u0005$\u0016W�\u0007=�\u0013\u0001X/�}%�K�D\u001a\u0013��*��:�D\u0000=K\u0012�_�����`�D�#�[��J�Q�l�$\t\u0006��z�\u0000s/�\u0002��\u001f\u001e�q��J����I+\u0010�X��,�$\t�g�f`��%�Q�6�\"\u0018@t��N\"9\u001e()y\u0015�\u001b.B��� L��*�6�o^4�4Y��e-ҫ��z\u0005_�|���~�nM7�\u000e�ڌ�Tg�\r\n�`�΋d�\u000e\u0007kbr�ؐ����\u0018�\u000f�f�\u0013v\u001c��/$}%)�-ė��R�\n\u0007�&\rH|&�X�$\u000e\t\r;c�+s-�A�Ј>>;_�?-�1hG*\u0010��\u0016@��Urȳ�ˊ73�6h)����\u0018�� O���\u0000.�M2�>j\u0000j=B¦�1�M��XB`L�Ѱ\u000b>x\u0005���\rd���b\u0016N\u0012\t�X�\u0011K�\u0012�n,\u0013ཫW{퓙��;�ߴ�p[�j)^��`z�\b�WI֮�\u0007����p�h�4?Ϳ��eS�ڃ\f��\u00184+v\u001f{�1��X�PBX���\u000bq��EsG�\u001b��p?g\u0006���i{Ә�M�t����ꛖ���\f�c��8;$<�\\3Q���\u001eZ\u0005�\rld\u0001v��f܆\"�\u0001��c�\u0001�ž��\b��\\\"Z�\u0018�T)U��Z�l��U\u0016A'h�E�,ԁ�.A=��\u0005t���\u0010F�DRl�s�\u000b�)ș�`/\";�\f.��2��\u001b\u001d]b�\u0010P�cB��ro�*[��\t\u0000��B� K|e��40\u001f��M\n�2B�\u0014�f1���1\u0004y��������R<\"�F�I�\u0002'�V���P��\u0012�N�ɣ���\nPF\u00158\b`�j\"jm�����\u0016\u000f� gxD�A\t�ڝH�u*W�X_����9q�[��L\u0006\u0005\bi�u\fN�9v@pkvmR��sS��(�\u0001�\u0002\u001c@ص\u0003\rۙ\u0010\u0014��\u0018��bqL�\u0016$� \u001b�Ũ!\u001a�\u0019�g\u0005�X\u0002Y�d�� ?ȩ�\u001a�D|'���C�#ru�\b\u0011*(n�o�5\u001b\u0016\u0004������\"x�c\u0006!B���i��2��쫳\u0018������\u001f�D\\C�\u0015\u0006�E�=���z��J��k��)\u0015��\u0013gY�]�e���0��#��\u0016`�P\"�{\u0000��\u0019�����Q]b\u000fڝPb��\u001ajB�!�\u0001Zlv;�\"禥N��[\",�\u001a0yԺ�#u-\r�\u0019\r��\u0001\u001fG�\u001d���!����\u0001�.��\u0001�\u0011n�b���FX\t�&�\u001b�t�\u001c!2�]6H�GH\u000f<\u0012��\u0000\u0014\u0004\u001ehC��K4N\u0004��\"\u0019��7�\t�|2�~�����'h��\u0013�A$�\u0007\u000bVTv����Y����\n=<�\u0012I�\u0016\u0003##\u0012��!L$�����[%��'R���F�!.Ŝ��|��m9�('\u0018�%�\nt�P+�l�w\u00118D�1\u0013\u001d����g\u001a� �\fWޏ�Y1\u0016��lb�\t��\u001f�(���5V\u001a�zk���1m�\u0017����ֻ\u0000\u0010-���\r�I�����6�\t\u001a\u0010A9\u001ex���?;gHɬ�{)\u0006\r �G�)�� ��Np�Ȁ�\nO\u0002\u00199•S���%<�E)\u0018��<O�L�BLx\u001c�A���01\u0002}¬h\u0000-=�n�\u0010e\u0016C\b�#0c��k���-\u0000\u001b��b�!:f�\u001dT3,(mС�q��=؃�c�����\u0007��1�*T\u000bilO\u001b�)`�\u0016h{�\u0011M]N�%�\n��~*&\u001e�W�$�=7\u001d8�MÆ´\u0017�\u0013d\n\u00114\u0014tZ\u0000��3Ք�3jđ�\u001d\u0014\u0014�\u0005an*��P�\u00146�\u0011�¢2J$Ò¢(LP@c��R\u0000#@$��\u0014�h7Nzpq@ព�;ac�\u0002�H[�\u0014w\u000bh�v\"c�\u0003����3s\u001d\u0017�+\u0019֒\b�` \u001c�6S�v� ~\u000f\tTt�r�\r�HS1eRO܁\u0013�\u0012(X�\u0015��`]\u0015��eC\u0016�\u0019\u001f 4x�h �@���\u000bE�\u0011r@�x�\u000bJX��Y%�47�d@FX�Q����(�ٓ{A\u0001<�!\u0004^!F�y�\u000e ��%=�!\b��b\u0010��k8���\u0002�P�y\u001dW\u0013\u001arrqKyË»E\\��ث��ո��T\u001d�B��h�\u0001\tCwI�!\u0000|Dj7\\��i\u000e�U�*\u0003��J��wR\n�G�:��R�M��b�H��\u0010n\u0003|�wi�\u0013�ڠ\u0013\u0006��*���g*~\\���G���SvY��T�j����@\u001dV\b\u0001\u0017�q\u0016T��f�*��ER��\u0018T��\u0003P��\fnt8\u0001�,�ά\u000e��e�A��2bP\f�����-���\rU00\u0005�f4\u0013\u0004O�\u0017N8\t�0\u0001'C�#��\u0017�\u001f\u0006'\"\u0007Wv;�\u001a�� �L\n�]C0Y\u001a��>�Ly�|w\u0017�x.���\u001e�3#\u0004\"V�\u0019�\u0013�ʵKI�Z �a��\u0014\u001eh��[P�Ȃ=�̘Aa���%�[\u0010\u0019(�P�'R\u0013\u0011�D�8\u0004c�\r\u001dPMn+jc�\u0010�̛�3sh�[�t��8(\u0010C�3<#`e�\u001f�h�up#��5�A\u0014\u0010a\u000bυ�)&\u0000DO����֐��/ޘZ�)��=)���D'n_\u001f�߼�BG\u0003��Y�\\lZ����@�0\u001b!\u001d�ӕ!C��DFi�`(\"\u0014ؽ5�\u001c����9ZGG4�?��W\u001eあ�\u0013M��'i�?9���l*���FV�H\u001c}��P�׊JM�Η[|�bdM\u001fL(L\u000eԕ�(��И_�\u0004\\\u0016.��2\t��B\b���\r��I?\u0002w\u0000��\u0007Tw\u0011�\u001d����2\u0004g�`�\u0001\u0018\u0000�:\u000f\u0000\u0001\u0004��\u0000�kQ��}\u0003$�U�\u001a��\u0007�\u0003z��\u0018�>�\u0002\u0016/H\nP@f}�\u0018��2(A\f����\u0011\n\u0005ߚ�%��ꑓ,q3)�Ί\u0007�\u0011���jh�����hw\u0006�V�v�\u0007\u0018�VQ\u0012���\\!` tX5\u0014���^��B\u0013/\u000e�')�0�t3�~�\u0001\f\u0012�����$�\u001b\t����4��\u000fwV\t2�(�5�\u0004[��\f�\u0007��R�\u0015u\nſ��\u0010\u000e\u0001[s~-b|�P\u001f�Q{\u001a�?˺JT�1��[�5k���> ZN��w&�|�r�>��d\u0017��\u0013;Ũ8��^V �\u0011���0ú\u0016Z\u0013\"d&[,�nY\"}\u0012|)�\u0006�n6����(4S������u\u0011\b|[‚�L6���\u0001\u0005t�c\u0006\fDD��K���i�\nK��t\u001d�\u001e2\b*�\n�2���eZ�\u0000\u0011\f͗7�4\tQ�M��\u0005iq��HT�\u0018y�Y�\u001c�(9�T�����a-kpx�d\r[\fN�y�~O�q�^\u0007�lqQ\u0017�0Z�\u000b��\u001a�\u0019d��a-�1�\u0000s� �#����\u001c�#̨g\f�|]��\u00126�\u0019�$\bdnj�?~tM\u0012�\r�� 褺�\u001c\u0003���ܵ��6cƳ5e�����\u001f�\u001cXU\u001b�8GE�\u0016\u0012���v�?ch�V�K'��C\u001b�$�ΧS�A��`�n��\u000fJw�Z��'��\u0013'ztO\u0001��G\u0013\u0012�\u001cl\u0005\u0003\u001b\u0011\u0003\u001dĈ\u0003��[D��Q�+�q\n�B�\n\u001e9\u0002\u0001Ô�<\u0014�\u0013\u0014\u0016?l\\幠�$�s\bsb'\u001e���<\u0017��W�1�Œ�0DL�\u00196�+\u0004Ak���\u0013=���m\u001e\r�\u0016\u0010�_G��v�\u00125p�,�s�n\u001c\u000e���\u0006.����Q\u0011aR�PD+<�3�lM\b�5_BԆA��N��p\u001a��H\n�\u0000��<�.\u0003\u0000�������|[P\u000e\u0012\tZ��m�\u001a�X\u000f%-6�t��g�\u0000@@\u001b��z�&���mL��\u0001�3���<Q@ͷ�n�H\u0001�Ɏ�q����4����Q\b�;&:��\n�=�bU\u001a`�[�MJo��,�� @I�|'e��M�^H�cf\u0007���$�P\u0001�\u0004d \u0011lǧC&�ڦw�2\u0005w�pu�l��^n�ƣ�Šp��T�\fm\u0011��˰��\u0003��\\߀\u0006\u0015\u0011�hb=rA�.us�4���O\u0011�i�;�P�\u0011�m9Jؕ0���I\u000b�}�\u0000u)DŽ��\b�\u0013�rT:B�!�\t3��(\n��*\u001c�l�U\u001d/8\n=�>\u001a'\u0019t]�\u0016���]�i�\u000e�ulz�\u001f$��\u0010|�H\u001c\u000b\u0011�n3�0�v?�yU�t{Ċ�)¶\u0001U�0��~\u0005O[}\u0007�\u0000\"��\u0015\u0004�\u0010)����\u0010 ٓ�'j�\u000e�k��MCX�\u0011<-�\u0003�����/p����G�?\u000bl�\n�9 4�F\u000e��~���u\u0011�u�h+�+�cH�\u0014���녙Q�&�t֢�x�q�t+�_Q$T\u0003\u0012^\u0003؇��`u�O��d�Qp]����� �!H\u0019舰�\u001cŶ�SJT��J�y��g\u0018�xh\u001f!\u0019�8G\u000eb*�<\u0006���\u0003T\u000b�h}���\"�N4bx�#$�\t|�͑�a���P�l�I\u0002\f��:W`��\bL�U�\fMK�\u0007������bTs(S��\u000e����\b�p�~�U1�p�.7���\n\u0013N���\u0010٨��D�ޛ�h�AXa���\n@+\u0016Ά\u0013�-'����\u000b�6O]�TU<1�\u0012����q��\u0011�\u001ew�$������=Ò¼L��\u0016����3�\u0016fi�+���M�\u001f\u0010p�6�œ6�����{L���󿒎E�\u001b;0�\u0014�~pN\u0006\u0019���\u0000lZ��$��N�������b���i�)��y��^M�\u0003\\\u001d�\u000f\u0003�D\u0016����\ndּ��\u0003Pe�\u0018#6��AX4�̆�[��;�Ek�7�l-\u0002��bG��\u001fV��0������\u0019r�)*r���\u000e�:�Gw6�U�\u001dI>j�\u0017���#q��R��H{�U곎�!BR\"����3x\"3��\u0019�ޗm0>=�&����\u000fB@�J��\n��k��̪ts0p~\tT�a�h�B����!�s[\u0007\r1yκ \t�VJ3\u0016!\u001dz���\tTP\u0005\b�\u001a\b�LI:�‹\f\u001a���q�\u0019�s/�C��O��-�\u0019�\u001d��\u0012!�ñ…º€ï¿½ï¿½L�21��y\u0014���؂�F��\f䤣['��'��V��Dz��\u001bd\u0014�Ϗӆ��\u0006�p�\u0012��.��Ek\u001a\u000f��ʤI$����|�e�\u0013��\u001c�\\\u0017��T���\u001a'��H>;Y\n��\u0001�\u0015:+UC�7\u000e����c�\u0011u�@ʸ�.��x�~�J\u0019�+I�\"\u0002F�H��8��N���k���\u0000\"�u\u0002p���Mo\u0003z��\u0006Gä°¦r{;���Dz\u000b���Uw�R�pRr`��{l��\u0016�s>˲$@��)��.\u0003x-�t���\u001e�a\"�N�\f�^��~&@᧧��<�K�\u0003������,���R��\u0010\u0010��P\u0014ږ��\u0012�鯌Ԭk\r\n���9��P\u001eS4�ͳ\f\u001ab�n�\b�\u001cZq��d\u0010�#\u001d-���C\t�\u001e�xz\u001b�W6>\u001d�\u0001/��\u001b�-�Q���N����k�s��wl^��Ў���FV\u0003���\u0006�Y�������\u0005��'C?\u0003���xB�o܉�Q\nM�?�~��U\u0014j�a�\u001fSӯ��\u001f�s5�\t�{Y�2\u0002�aF܈\u0018:\u001cJ\u001e�\u0012s�Ҵ@W�\u0006�i[\u0014�'O���vW�B�\u00156�}{I�5����\u001a��k�r�(�4\u0016�u-�0\u000f�7\bH�X�\t%$�هAd�?[��h\\<\u0012�\bn�\u000b��_\u001bÊ©X�V\u0006�3/�\u00132sE�\u001e�u9�����\u001b\u0016�>�\b^O��5��QkF��&%Y0c[\u0019���kO�����&��\u0004c�f�hi�1����3_���3�}wa\u001cjiA$t�\u0018��\u0013��\u000f�ۀ�z\u0002��'\b$\u0010�0�,N����R\u0010�HJ(�%�B�ܼ�v5�|�̱Xx,��L��@H�^���G#������q;�_�\u0003�uJ��g�*�W���\n��f#�;0//Z�\u0015���U�~�n6�+��$zv}\u000e.�\u00030.\u0003=\u001d�\n�&�p\u000b�����ῼ|��f&\u0003s܇�b\b��9��.\u0005�A��=�`�\u001a��\u0010D\u0015��\u0018���\u0005��`*\u0000�\u0010y�R���p\u0004���.2�謅�?�\u001a�0t���hZ3Ë·e5�,�i��޾0�Qe���;�\u0003���,\u0019b��\u0013����� �т�\u001f��)Q@�.և�@��(ښz.�1E�>������ J|W[+\u0019U\"�\"A\u000b�\u0012�*�\u0001�w�]yS�AH>�>ȟ�˩ѠF,\u0004\u001e\u000e�#�/즊\u000e�w�\n��k~Y��H��\u0010l�eu�H\u0000�\u0019x\u0003}+\u000b\u0019p8'[���U��ܥ��\u0012V�\u0001�\u000e#\u0002��\u00136����\u0001�0(Δr���XS��6YB���m%\u000b\u001dM��ӃY�\u000f67��x�-nh�'*�,R�*����\t\u001a\u0017;\u001f\u0013��|�6\r%U�C\u0017r�c��2�\u0003�*y�E�Ǟ+��xk�\u000f�*�\u0003�Z��^0N�A�o���*ZY�\u001e5u���C?\u00010 0\r\u000f2�����P��\u0001\f\u0018G�\b�X2z6P3��\u0002Դ�1�S��`�\u000b�|&|\u0005ӀiE�����A��)���k��0N\u0006�\u0000\u001c���j�D�܉� �@��������}�~\u0014�w\u0017��U�,rq\u0000t\u0015�¤S>^0$rI)\u001fV�rqú��\u0012�y\u0014���7���\u0005E��n\u0019\fU�wi��!u%b����\u0002���^��I�O�\u0000\u0000��ǐ��Ǒ��cI���Ѿd��C����N];\"T��\u0010�\u001aB�)�8$�V� �Fk?�\u001d\u001f\u0010��2�HO\u0012\u001e\u0016�>��Ќ�\t����c&���ȗ�%�\u0014Z�:�h�\n���>��p%��\u0019H�$zd��fn(=\"\u001b��\u0005\u001e�\u0010������^��Ӊ�Y�+D�\u0004<�*;�\u001c{�+Zy\fl���B\u00120���ȁ\u001b!���\tޝ��O\u0012d�\"R����g�a��\u0017H����\fIÒ¶ig����u�*�x\u0013�x���\u0010\\�\u0012����j&>��\u0003c�5�〸��\u0003͔\u0015?�M\u0012l��n�,\u001d㳅\u0007z�8\u0014��jpZ{c,�\nS9<�,\tЬS·�����Ҕ�gP37\u0012B�xW\u0005_�\\�M���b���\n���:-\b��\u0018!��\u0004�� ]1�\u0010D�EԦ��<�`讏�\u0011F�v����L\ny|�e�2&}���ڛ��\u0017\u0012��R��\b��{I�~�|�\u000e�\u00021\u001dN���5ß®N\u0006h��(f\f1Hl\u0011��V8@\u001b\u0003E�\u0017)+�eeR\n���h�Z\t\u0015�[��V�PgR��fU�\f�i\u0011��\u001eE(\u00014\"�c�l�\"E�B�����`�2��|\u000bÆ@�#�*qS��&�\n\u0002�\u0011@�7�\u001e\u0001[|��<�\t��j-�H���t*/��I����\b6q��8Z`��b[���Y'P�H�\r�W�\b\u001f'4D딴\u00135��F�c�P��j�פ���\u000ed��\u0012\"Å©RXrg\u0010I�7π\u0012�r���7ϔ0�F��\r�\"\u0004\u0002�p2h���\u0000ADy�p���P��\n�\\'�\u000f\u001a�T\u0004\u000b�\u0005�&�\u0017\u0001���\u0015sv\u000f9.+.���\u0015L�pi�ߤv��Hbȩ��E�T��fe�閹�\u0010D\u0010Y��� �+��ΰ�H\u0012Û¹\u001a\n�\u0005��\u0006#K4xB�2�(D�?�}\u0000iS|�\u0007fVWx�����\u0015����e������J�Ε\u0010p+g��ز���x��C�A�e��xT��Hq�ggл�^#\u0000�i�ov�\u0018W���G�\u0010�k���\n�2��F�٨)�1�?��t����\u0000\n��p��t�?�د�\"�\rIV~W�g�7\u001bb�t*\n̯��C�R�r\t�D�~�*��\u0006\u0002G�P\u0012�%Fo�1�(\u001dH��%\bY�\b��Z���H�\u001e^X V�r�(�\u000bd��G���;�4$�t*��e�b�<L�\u0002N.�\u0002�\u000b���\u0004��C\u0000EЪʂ�\u001d�~]�3��J\u001a��o\\TXo\f\u0006�����@< PރHD��?�?��?��\u0011��Pn\u0013/�\b\u0001\r\u0002\rA�\u001f��)(\"\u0007!�JP \u001a�\u0003y�\u000e\\ �6U|I)��x��{pN\u0019K���\nO���\u0005�t�,��a#�7�\u00046B�_d��\u000e��'Rr;�]ʉ��H�Q4?\u0016\u0004M�\b���\b�X?~�\u000f��\"�Rը�<��+˙�7\nu�xS\b�����Qy{C\u001b0\u0016�*j��\u0010\"���ݼ��ŶQi1�F0���e�R�\u0011X�� ɂ�QB�J�<��e�_�M\tE��?O�\u0004a�\u0017�[>���m\\E�\u0002l��&\u001a���{J��\u0018��\u0017\u0003\u0005A�\r�\b��j\u001c�\u001e�.�#�<2j}�ҕJ:B�ZK?7\u0011�6�\u0011�D\u0011D�&�B_a/�\u0004\u001f#��Q�\u0018\u0003��\u001b�M`���N�`�\u0018P�o\u0001��\u0016�$_\f\b�q�V�\u00079�K1��\u0000=\u0010��9)\u0013�~7\u001f>��Go�TX���\u0010\nTc�*����!��a\u0013���?\u001d�X��X\u0003�\u001fC O�4�\"�!�\u001e�j��\u0014i���աE;�3��\u0000�XM\u0018\ta�HԞ]\tĽ�͵E�\u0011\u0002�湔L�D�&��\r\"\u001c^\u0014�}�\u0013�)\u0014�a*�+����Vt@�jg9a�����C�}��\r%X*+�A\u0002��&\u000b\f��>�U��R�)�c�\u001a|M+�-��#@<b��&-떨C\u0019Am��\u00034�$ye2J\u001f[6Ψ�Q��k����*���\u0018���U\b��᡾}Đ'Uz��\u0006�\u0004�x�*\t�\u001e\u000fW'���p(�J\u001e\u0014�z��dX�$����Ȣ�Q�&0n��\u0005EYs�(n\u0006����*�%\f��\u0003���L�=�>�\\��\u001f�\u0004�\u001f\u0002+Ӕ�W\u0000�l1��ԑ5�S���\u0003:ȓ/�\u0015d\u001cd\u00032�@#FU\u0006�2\u0011�ɺ\"��47]�83�\u001f\b��\u0018dl���\u0004X��f�24��g�\u0013�(%\u0010����\u0016[\n}5��\"�\u0007�5��'u�\u000bK��\tR*#�x�ۙX#G!�t�bH���w�\u0011�\u001d�v��Ѱ���t��B�T�\u0019�t\b���>����F\u0002�\u0014]\rp^2�����e\u0012p@X�w9\u0004a�n�X��)\u0002@F��\u0002@� �h>J&mAع!�u=���}T\u0011\u001d\u0011�QW��3A�M@�C����\\���\u0013\"���F�����d+����\u0002�O�Q�\u0015\u0012\u0017�B�����q&��ռ\b�W�\u0016���:5�sz%)�\t\u0017y�R#�\u000b����|Rƾr%\u0017�fr�_\u0007\u0006\r��yE�-\u0006�\u0019�p&�_�7D���4��w@�\"Ý°0�U�Yb\u001f�!�(�epQ�~\"�V\nvQ�߷�q\\y�6\u0013\u0001�G\u0019YR�_\n\u0016�U�\u0012������ݐ���\u000eoP؁cZ&�\n�4\u0019\u0010�\u0002Rf{`��P\u001ah��;N�\u000b�\u0010�H(�\u0016T����\b7^��ΕZ�\r��n�[��\u0011������\u0004x�.��_3��O���D\u001b���\rE�F\"D��(�33��tu��\u0016ᒯu�t\u0000\u0016\u0011��\u0015`Rh��a�Z�(DJ\u001d�$\u0004l�0���A\t��H2�\"�^H\"�y1���e\u0011�@-�oo��j��]�\u001c�~!�H��\u000f�l\u0014]S�3���3V�\u0006YX�\u0010�\u000b\u000e�C�g�~� AG�I6�\u000e����ם�3\u0006B�Zc���ĉ\u0001��\u0005��#Lv�-C�P,V�@���+�ԘáQL�\u0019s\n�$�E�ϖa,A\u0003�#R\u0010�\u000b���\u0001�fW�d�R�60�\u0019�C\u0001���\t�i\t�d\u0019f����T:H\u0001����'�\tg�\"P�4� I9��U^�@�%2E.���h�\u0004�\u0012E\b\u000f�:��h\u001e\u0010�\u0000|�\n\u0014��հ����!O:��:f��Ŏ��\f��hs\u0018\u001f��>\f2�E<qIΒ�\u0016F�w\n��^h\u001e�\u0012\u0002�cHd���HE�[\u000bM��t)�Z9�\u000bDT=�L�cN�XJ\u0010�D˴�����4\f��#~%4z�K�\u0000����o)݉ ��۩0]�P-�\u0006�+�p���2g<�ߧ�}�,`�n�I‡��{�_�'F\u000e���\u0003�c5\u00008̩�\u0014��@ܲ|\u0016��\\N\u001b4T����X������%�c\u0010�kx\b6D,\u0013fXU\u0011h\\\nw��9�R5`h��2�ܶ��b�\u0018�\u0005\u0018v��O���3�Q=�ԛ�\u0012\nQ>���}�\u0013���vn�rQ�V��l\u0010�\u0005\u0005֎�F��\u0003�\u0005e\u0007�J���\u0019K���(d�l����u0����@U�^\u001d���_~��B\u0003\u0015�r��.�\u0011�*h�E{\u0000�X\u000fI�������x9>��g\u0018�c�����}���~�\u000f>nN�\u0012Ì¥Z��d�d\u000e�a��+3D�6wp;\u0000�a�O��\u0000E�~*�Mͪ�\u001d\\��������G�G]>lyN��a�D�JI%B,G\u0002\u0007�,���K��͸3�t��2��4\u0001�l�G@���\u0001{������\u0003k\u001c���\u0000\u0018�\u0000d?�\u001c�$.�\u0010CD���0���\u0000��7d:[�����>\u000e}��\u001b\f�\u0014)_蒾3˒]�\t��[\u0003\u0018��ӝ7���\u000e\f\u0003��#;�>�\u0005PO��f\u0007\r�c\u0015���&�\u000fa\u0007ul��h\b=�w\u0017\u0013�!�X�œ@w�/�z�]��Yaw\u0012�[�t�f���$��E��\\�.��-�vW�!��\u0000��^�J����26\u001e\u001f�U����in����h����%���\u0014+\u0006\bW�;���n\"1\u0014]�C�?`��\u001c�,i(�����\u0018-����+G�օ1�L����;��3QÍ¿s'\u00029�@��ѭ�N>�]aӡ�&�6\t�G\u0007\r2��`\u0002QIB\u0010�N�\u0003\u0000�R\tw�\u0001c�\u001a\u000b4\u001fR��>\u000f\u0012\u0011�ʚ�\u0010M�v\u0011��.�u�'3��ʉ\u000b:�o\u0011\u0003\"��Zg?�%��h�\u001f�����~�Hg\u0005���٬$%���&]�\u0006�'gFOa�̓q4nx<\u001b�>\"J\u0019�r��\u0007Ï¢u��G^��}�\u0012����\u0012�A�����5c��B��Z�X�b�\u0019��'�nQi0�3�anL'�u�e[\u0006\u0007�<Hm%./��1K0�m��[ک�\f\u00163��;=\\@�:xh �f�M>1�Q/u\u0018\u0002�0G����\fab�-�_j�!7\u0013Ƿ�2Gng���u\u0001\"�#��GRc�\u0019�����+�+�I�\u0010��2�;�W���]n)�\u0016D��\u001d&���3=�!ض�7�yș>\f�\u0011ߊ��:�W/-\u0017\u001c�\u0015Q6qW,WEn�\tVL�_N\u0019�|YkكZ�z��T�n,P<8x���9��A�I\u0015I��\u001e�-b(<�i��.I�;\u0000��\u000e�m\u0000I�^�\u000f��/0\u001c\u0000�ñ€„}ۂ{P�E7.���3{�H��[^`<J�/S���\u0014��R�!�\u0004F�R��;�8��0wO\r���\u0016U.��\u0000g�#T��<\u0013ɭ����o\u0000��@_��E����N��\u000eG�L��}.8�v�\u001aC�u�M�L��a�3Ӣ���I\u001d8�\u001d�HZ�u�������@1\u000bF��\u0003�H&L�$�\t&��\u0011�\"�0��3R)�(�\r\u0012\u0011��]�p\t*\u0006�t:\u0017�\u000f<��\u001f��1�&�w'\u001f�\r�S���\u000f\u0006L�}07M��\bp�۠r\u001b�\u0014�;^�\r��g_�-\u0000\u000fhŜ���6��+\r�\u0006\u0013[��\b\u0017.����S!Ò»K\\u��EQ`\nbW8\u0001%�l�\tB�x��\u00038$;K\u0006{k�B�\u001dc\"��`gr:;)e��M�����\u0007ၢ%W����Sv$j�Z�?9\u0000d�\u000e���$-m2�p��+��ě\u0011�\u0005>Qu5q�|�2\u0000��?\u000f�\\�**!��� �S�%YiQC��U|\r�&gy�V\u001e��\\\u000ba[u��Щ\u00135��W\b\fݖ�����\u0004���@�����=���=�t\u00059(Z\u0015�*\u000b<�U��D���j)�\u000b\u001d\\j\u0017�\u000b�C��B/�ts�|U�v��zUL\u001b�EB�\"��\u000bz`{8)\f���Z�q(�E��\u0007\"uE��e\u001c\u0005E��\u0016�\u0010[�\u001af�\u0005�p)��K�L�R���M�A�+lG\u0015۔$�\u0014%�լ��5��,���\u000bZ�@*Y�\u0007���ͦ\u0014��P�f��\u00161\u0006F�t\u00012\u0011�Imm�\u0005����~��M�\u0013�w`\u000bxJ�\u000e# ���\u0000�ErH2t��F�.�\u0012\u0000�VPEl�*O\u0005�ɪ;\u0010\u0007�\u0014ai<��(*�\"RF�'��~�UO*W�e��pچ6K\u0012|��Рv2�{��.6\u001e�jNq��\t�\u0006:I���y\u000eg\u0019��L�\u0019.�������\u0010�:�\u0007\\���,���`\u000e0\u000bx���`)\u0016S �\u0014��\u001b@zw\u0016QE�n�^/\u0017!�\u0018?\u000f�\u000b�ƅ�Q��\u001ae\u0016r\n���\u0013��� ��*\u000b;����&�\"T�H�ϥ�\u0007hP \u0010��CQĎ4ON\u0006=����\u001e�zc�\u001c\"~\u0013\u000e\u001at�E&X!u\u0005%�r���E�\u0011�A�02t�\u0016\bs�\u0002F\u0002)\u00106E8����\u0010�c=\u000e�\u001b\u0010��j0 ��j�\f6���\u001e\u0004׊\u001a#�X\u0001:�H\u0013hD�)�\u0005\u0012�!����\u0016\u0000V0�C@\b\u00187 ���\u0006�\u001e\u0000S�\u001c�c\u000e�4\u0000�\u0007�#�[@�ac\u0004|\u0013�'��@m��\u000107\u0005�\u000fP\u0015p6�\u001d\u0000�\u0006�\u0014�`A�\u0006X\u000e\u0001-���\n0m�c(E\u001e��\u0001�:\u0000��*\u0019��w��8X\u0001u��T\u0002�Aʤ\u0015�\u0000\u0004�\u001c�\u0000<�~ E18v��n�\b�?$\u0010�X(\\n�^\u001f�\t��3�Zp\u0001��<\\4�\u0002D�u\u0012\t��e�\u0000���q(�8mn\u001c)6\b�p\u0001�P\u0001s0\u0001\u0013 \u0000��\u0002��\u0004�\u0000\u0007Հ\u000f+\u0000\u0018R\u0000(�\u0000I(\u0000�P\u0000�`\u0001'�;\u000f\u0000s\u001c\u0000�0\u0001�T9|T9|L9dL9TD9>@8� \u0018Y\r\f$Ɔ\u000f��@``�0@�0@V,@\"(@\u0004$C|H���\f��\u000b����c\n�@\u0015b�)\u0005\u0000c�\u0000�\f\u0018�\n\u0018�\n\u0018�\u0002\u0018��1+�b\u001b��\u000fA�'A��\u0003`��\u0001��\u0002�@\u0003:�y�\u0001٘\u0006�`\u001a\u0019�n�\u0001��\u0006~ \u0018X�`�\u0001c�\u0005n\u0000\u0014X\u0000I`\u0001\u000fp\u0003f�\u0005Ý°\u0015v@MY\u0000�d\u0002�p\tU�\u001a�\u0001���6/�2\f��\u001f\u001cGA\n�D\u001f/q\u001bb���\u001e��j\u001b�m�L�=\u0013\u0018�L]L��\u0013;/R@�\u001c\u000eq@�\u0014���5C1�\u0019���d�b\rه�f\u001c\u001d�gvؼ�\u0005� ,90�����\u0002/�~}�O�o�淊��#;�#�\u001dڜs��g\u0014\u0017����a]��{0\u0012ـ��\u0006� 4/\u0001�x\u000bK�P^\u0002r�\u0010\u0017�t�\u0002Õ c�\u0003\u001dH\u0016�@�R\u0005b�%ԁ\u001a�\b��=#\u0001�\b\u0010H@yB\u0003��\u001bπ�l\u0006\u0013`.�\u0001T�\n%�E.\u0000��\u0001��}�\u0003ب\u001c�@�*\u0004Di�ɘ�$\u0019�1\u0004w��yv�ݳV�8\u0003�\u0010�07�\u0013�F�H�.���\u0018�Ɵ8��J�\u001f@\u0000\u0000\u0001\u0003\u0001`\u0000\u0000\u0015�'��\u0010�`\u0004\\LT���\u0002A\u0018p\u0012Bs�)r�!�\n�(\n\u0003�\u0004i�`","glyphicons-halflings-regular.svg":"<?xml version=\"1.0\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\" >\n<svg xmlns=\"http://www.w3.org/2000/svg\">\n<metadata></metadata>\n<defs>\n<font id=\"glyphicons_halflingsregular\" horiz-adv-x=\"1200\" >\n<font-face units-per-em=\"1200\" ascent=\"960\" descent=\"-240\" />\n<missing-glyph horiz-adv-x=\"500\" />\n<glyph />\n<glyph />\n<glyph unicode=\" \" />\n<glyph unicode=\"*\" d=\"M1100 500h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200z\" />\n<glyph unicode=\"+\" d=\"M1100 400h-400v-400h-300v400h-400v300h400v400h300v-400h400v-300z\" />\n<glyph unicode=\"&#xa0;\" />\n<glyph unicode=\"&#x2000;\" horiz-adv-x=\"652\" />\n<glyph unicode=\"&#x2001;\" horiz-adv-x=\"1304\" />\n<glyph unicode=\"&#x2002;\" horiz-adv-x=\"652\" />\n<glyph unicode=\"&#x2003;\" horiz-adv-x=\"1304\" />\n<glyph unicode=\"&#x2004;\" horiz-adv-x=\"434\" />\n<glyph unicode=\"&#x2005;\" horiz-adv-x=\"326\" />\n<glyph unicode=\"&#x2006;\" horiz-adv-x=\"217\" />\n<glyph unicode=\"&#x2007;\" horiz-adv-x=\"217\" />\n<glyph unicode=\"&#x2008;\" horiz-adv-x=\"163\" />\n<glyph unicode=\"&#x2009;\" horiz-adv-x=\"260\" />\n<glyph unicode=\"&#x200a;\" horiz-adv-x=\"72\" />\n<glyph unicode=\"&#x202f;\" horiz-adv-x=\"260\" />\n<glyph unicode=\"&#x205f;\" horiz-adv-x=\"326\" />\n<glyph unicode=\"&#x20ac;\" d=\"M800 500h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257 q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406z\" />\n<glyph unicode=\"&#x2212;\" d=\"M1100 700h-900v-300h900v300z\" />\n<glyph unicode=\"&#x2601;\" d=\"M178 300h750q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5q0 -80 56.5 -137t135.5 -57z\" />\n<glyph unicode=\"&#x2709;\" d=\"M1200 1100h-1200l600 -603zM300 600l-300 -300v600zM1200 900v-600l-300 300zM800 500l400 -400h-1200l400 400l200 -200z\" />\n<glyph unicode=\"&#x270f;\" d=\"M1101 889l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13l-94 -97zM401 189l614 614l-214 214l-614 -614zM-13 -13l333 112l-223 223z\" />\n<glyph unicode=\"&#xe000;\" horiz-adv-x=\"500\" d=\"M0 0z\" />\n<glyph unicode=\"&#xe001;\" d=\"M700 100h300v-100h-800v100h300v550l-500 550h1200l-500 -550v-550z\" />\n<glyph unicode=\"&#xe002;\" d=\"M1000 934v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q17 -55 85.5 -75.5t147.5 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7q-79 -25 -122.5 -82t-25.5 -112t86 -75.5t147 5.5 q65 21 109 69t44 90v606z\" />\n<glyph unicode=\"&#xe003;\" d=\"M913 432l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342t142 342t342 142t342 -142t142 -342q0 -142 -78 -261zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z\" />\n<glyph unicode=\"&#xe005;\" d=\"M649 949q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5t-94 124.5t-33.5 117.5q0 64 28 123t73 100.5t104.5 64t119 20.5 t120 -38.5t104.5 -104.5z\" />\n<glyph unicode=\"&#xe006;\" d=\"M791 522l145 -449l-384 275l-382 -275l146 447l-388 280h479l146 400h2l146 -400h472zM168 71l2 1z\" />\n<glyph unicode=\"&#xe007;\" d=\"M791 522l145 -449l-384 275l-382 -275l146 447l-388 280h479l146 400h2l146 -400h472zM747 331l-74 229l193 140h-235l-77 211l-78 -211h-239l196 -142l-73 -226l192 140zM168 71l2 1z\" />\n<glyph unicode=\"&#xe008;\" d=\"M1200 143v-143h-1200v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100z\" />\n<glyph unicode=\"&#xe009;\" d=\"M1200 1100v-1100h-1200v1100h1200zM200 1000h-100v-100h100v100zM900 1000h-600v-400h600v400zM1100 1000h-100v-100h100v100zM200 800h-100v-100h100v100zM1100 800h-100v-100h100v100zM200 600h-100v-100h100v100zM1100 600h-100v-100h100v100zM900 500h-600v-400h600 v400zM200 400h-100v-100h100v100zM1100 400h-100v-100h100v100zM200 200h-100v-100h100v100zM1100 200h-100v-100h100v100z\" />\n<glyph unicode=\"&#xe010;\" d=\"M500 1050v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5zM1100 1050v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h400 q21 0 35.5 -14.5t14.5 -35.5zM500 450v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5zM1100 450v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5z\" />\n<glyph unicode=\"&#xe011;\" d=\"M300 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM700 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5zM1100 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM300 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM700 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1100 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM300 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM700 250v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1100 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5 t14.5 -35.5z\" />\n<glyph unicode=\"&#xe012;\" d=\"M300 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1200 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h700 q21 0 35.5 -14.5t14.5 -35.5zM300 450v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-200q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5zM1200 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5zM300 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1200 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5z\" />\n<glyph unicode=\"&#xe013;\" d=\"M448 34l818 820l-212 212l-607 -607l-206 207l-212 -212z\" />\n<glyph unicode=\"&#xe014;\" d=\"M882 106l-282 282l-282 -282l-212 212l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282z\" />\n<glyph unicode=\"&#xe015;\" d=\"M913 432l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342t142 342t342 142t342 -142t142 -342q0 -142 -78 -261zM507 363q137 0 233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5t-234 -97t-97 -233 t97 -233t234 -97zM600 800h100v-200h-100v-100h-200v100h-100v200h100v100h200v-100z\" />\n<glyph unicode=\"&#xe016;\" d=\"M913 432l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 299q-120 -77 -261 -77q-200 0 -342 142t-142 342t142 342t342 142t342 -142t142 -342q0 -141 -78 -262zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 801v-200h400v200h-400z\" />\n<glyph unicode=\"&#xe017;\" d=\"M700 750v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5zM800 975v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123 t-123 184t-45.5 224.5q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155z\" />\n<glyph unicode=\"&#xe018;\" d=\"M1200 1h-200v1200h200v-1200zM900 1h-200v800h200v-800zM600 1h-200v500h200v-500zM300 301h-200v-300h200v300z\" />\n<glyph unicode=\"&#xe019;\" d=\"M488 183l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5 q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39zM600 815q89 0 152 -63 t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152q0 88 63 151t152 63z\" />\n<glyph unicode=\"&#xe020;\" d=\"M900 1100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100zM800 1100v100h-300v-100h300zM200 900h900v-800q0 -41 -29.5 -71 t-70.5 -30h-700q-41 0 -70.5 30t-29.5 71v800zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z\" />\n<glyph unicode=\"&#xe021;\" d=\"M1301 601h-200v-600h-300v400h-300v-400h-300v600h-200l656 644z\" />\n<glyph unicode=\"&#xe022;\" d=\"M600 700h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18v1150q0 11 7 18t18 7h475v-500zM1000 800h-300v300z\" />\n<glyph unicode=\"&#xe023;\" d=\"M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM600 600h200 v-100h-300v400h100v-300z\" />\n<glyph unicode=\"&#xe024;\" d=\"M721 400h-242l-40 -400h-539l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538zM712 500l-27 300h-170l-27 -300h224z\" />\n<glyph unicode=\"&#xe025;\" d=\"M1100 400v-400h-1100v400h490l-290 300h200v500h300v-500h200l-290 -300h490zM988 300h-175v-100h175v100z\" />\n<glyph unicode=\"&#xe026;\" d=\"M600 1199q122 0 233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233t47.5 233t127.5 191t191 127.5t233 47.5zM600 1012q-170 0 -291 -121t-121 -291t121 -291t291 -121t291 121 t121 291t-121 291t-291 121zM700 600h150l-250 -300l-250 300h150v300h200v-300z\" />\n<glyph unicode=\"&#xe027;\" d=\"M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM850 600h-150 v-300h-200v300h-150l250 300z\" />\n<glyph unicode=\"&#xe028;\" d=\"M0 500l200 700h800q199 -700 200 -700v-475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18v475zM903 1000h-606l-97 -500h200l50 -200h300l50 200h200z\" />\n<glyph unicode=\"&#xe029;\" d=\"M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5zM797 598 l-297 -201v401z\" />\n<glyph unicode=\"&#xe030;\" d=\"M1177 600h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123t-123 -184t-45.5 -224.5t45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123 t123 184t45.5 224.5z\" />\n<glyph unicode=\"&#xe031;\" d=\"M700 800l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400zM500 400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122l-145 -145v400h400z\" />\n<glyph unicode=\"&#xe032;\" d=\"M100 1200v-1200h1100v1200h-1100zM1100 100h-900v900h900v-900zM400 800h-100v100h100v-100zM1000 800h-500v100h500v-100zM400 600h-100v100h100v-100zM1000 600h-500v100h500v-100zM400 400h-100v100h100v-100zM1000 400h-500v100h500v-100zM400 200h-100v100h100v-100 zM1000 300h-500v-100h500v100z\" />\n<glyph unicode=\"&#xe034;\" d=\"M200 0h-100v1100h100v-1100zM1100 600v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5z\" />\n<glyph unicode=\"&#xe035;\" d=\"M1200 275v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5t-49.5 -227v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50 q11 0 18 7t7 18zM400 480v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14zM1000 480v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14z\" />\n<glyph unicode=\"&#xe036;\" d=\"M0 800v-400h300l300 -200v800l-300 -200h-300zM971 600l141 -141l-71 -71l-141 141l-141 -141l-71 71l141 141l-141 141l71 71l141 -141l141 141l71 -71z\" />\n<glyph unicode=\"&#xe037;\" d=\"M0 800v-400h300l300 -200v800l-300 -200h-300zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z\" />\n<glyph unicode=\"&#xe038;\" d=\"M974 186l6 8q142 178 142 405q0 230 -144 408l-6 8l-83 -64l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8zM300 801l300 200v-800l-300 200h-300v400h300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257z\" />\n<glyph unicode=\"&#xe039;\" d=\"M100 700h400v100h100v100h-100v300h-500v-600h100v100zM1200 700v500h-600v-200h100v-300h200v-300h300v200h-200v100h200zM100 1100h300v-300h-300v300zM800 800v300h300v-300h-300zM200 900h100v100h-100v-100zM900 1000h100v-100h-100v100zM300 600h-100v-100h-200 v-500h500v500h-200v100zM900 200v-100h-200v100h-100v100h100v200h-200v100h300v-300h200v-100h-100zM400 400v-300h-300v300h300zM300 200h-100v100h100v-100zM1100 300h100v-100h-100v100zM600 100h100v-100h-100v100zM1200 100v-100h-300v100h300z\" />\n<glyph unicode=\"&#xe040;\" d=\"M100 1200h-100v-1000h100v1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 1200v-1000h-200v1000h200zM400 100v-100h-300v100h300zM500 91h100v-91h-100v91zM700 91h100v-91h-100v91zM1100 91v-91h-200v91h200z \" />\n<glyph unicode=\"&#xe041;\" d=\"M1200 500l-500 -500l-699 700v475q0 10 7.5 17.5t17.5 7.5h474zM320 882q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71t29 -71q30 -30 71.5 -30t71.5 30z\" />\n<glyph unicode=\"&#xe042;\" d=\"M1201 500l-500 -500l-699 700v475q0 11 7 18t18 7h474zM1501 500l-500 -500l-50 50l450 450l-700 700h100zM320 882q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71t30 -71q29 -30 71 -30t71 30z\" />\n<glyph unicode=\"&#xe043;\" d=\"M1200 1200v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900v1025l175 175h925z\" />\n<glyph unicode=\"&#xe045;\" d=\"M947 829l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18l-94 -346l40 -124h592zM1200 800v-700h-200v200h-800v-200h-200v700h200l100 -200h600l100 200h200zM881 176l38 -152q2 -10 -3.5 -17t-15.5 -7h-600q-10 0 -15.5 7t-3.5 17l38 152q2 10 11.5 17t19.5 7 h500q10 0 19.5 -7t11.5 -17z\" />\n<glyph unicode=\"&#xe047;\" d=\"M1200 0v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417zM416 521l178 457l46 -140l116 -317 h-340z\" />\n<glyph unicode=\"&#xe048;\" d=\"M100 1199h471q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111t-162 -38.5h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21 t-29 14t-49 14.5v70zM400 1079v-379h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400z\" />\n<glyph unicode=\"&#xe049;\" d=\"M877 1200l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425z\" />\n<glyph unicode=\"&#xe050;\" d=\"M1150 1200h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49v300h150h700zM100 1000v-800h75l-125 -167l-125 167h75v800h-75l125 167 l125 -167h-75z\" />\n<glyph unicode=\"&#xe051;\" d=\"M950 1201h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50v300h150h700zM200 101h800v75l167 -125l-167 -125v75h-800v-75l-167 125l167 125 v-75z\" />\n<glyph unicode=\"&#xe052;\" d=\"M700 950v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35zM1100 650v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h1000 q21 0 35.5 15t14.5 35zM900 350v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35zM1200 50v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35 t35.5 -15h1100q21 0 35.5 15t14.5 35z\" />\n<glyph unicode=\"&#xe053;\" d=\"M1000 950v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35zM1200 650v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h1100 q21 0 35.5 15t14.5 35zM1000 350v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35zM1200 50v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35 t35.5 -15h1100q21 0 35.5 15t14.5 35z\" />\n<glyph unicode=\"&#xe054;\" d=\"M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z\" />\n<glyph unicode=\"&#xe055;\" d=\"M0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z\" />\n<glyph unicode=\"&#xe056;\" d=\"M0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35zM0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z\" />\n<glyph unicode=\"&#xe057;\" d=\"M400 1100h-100v-1100h100v1100zM700 950v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35zM1100 650v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15 h500q20 0 35 15t15 35zM100 425v75h-201v100h201v75l166 -125zM900 350v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35zM1200 50v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5 v-100q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35z\" />\n<glyph unicode=\"&#xe058;\" d=\"M201 950v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35zM801 1100h100v-1100h-100v1100zM601 650v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15 h500q20 0 35 15t15 35zM1101 425v75h200v100h-200v75l-167 -125zM401 350v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35zM701 50v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5 v-100q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35z\" />\n<glyph unicode=\"&#xe059;\" d=\"M900 925v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53zM1200 300l-300 300l300 300v-600z\" />\n<glyph unicode=\"&#xe060;\" d=\"M1200 1056v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31zM1100 1000h-1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500zM476 750q0 -56 -39 -95t-95 -39t-95 39t-39 95t39 95t95 39t95 -39 t39 -95z\" />\n<glyph unicode=\"&#xe062;\" d=\"M600 1213q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262q0 124 60.5 231.5t165 172t226.5 64.5zM599 514q107 0 182.5 75.5t75.5 182.5t-75.5 182 t-182.5 75t-182 -75.5t-75 -181.5q0 -107 75.5 -182.5t181.5 -75.5z\" />\n<glyph unicode=\"&#xe063;\" d=\"M600 1199q122 0 233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233t47.5 233t127.5 191t191 127.5t233 47.5zM600 173v854q-176 0 -301.5 -125t-125.5 -302t125.5 -302t301.5 -125z \" />\n<glyph unicode=\"&#xe064;\" d=\"M554 1295q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 138.5t-64 210.5q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5zM455 296q-7 6 -18 17 t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156q14 -82 59.5 -136t136.5 -80z\" />\n<glyph unicode=\"&#xe065;\" d=\"M1108 902l113 113l-21 85l-92 28l-113 -113zM1100 625v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5 t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125zM436 341l161 50l412 412l-114 113l-405 -405z\" />\n<glyph unicode=\"&#xe066;\" d=\"M1100 453v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5z M813 431l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209z\" />\n<glyph unicode=\"&#xe067;\" d=\"M1100 569v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5h300q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69z M625 348l566 567l-136 137l-430 -431l-147 147l-136 -136z\" />\n<glyph unicode=\"&#xe068;\" d=\"M900 303v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198l-300 300l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296z\" />\n<glyph unicode=\"&#xe069;\" d=\"M900 0l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100z\" />\n<glyph unicode=\"&#xe070;\" d=\"M1200 0l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100z\" />\n<glyph unicode=\"&#xe071;\" d=\"M1200 0l-500 488v-488l-564 550l564 550v-487l500 487v-1100z\" />\n<glyph unicode=\"&#xe072;\" d=\"M1100 550l-900 550v-1100z\" />\n<glyph unicode=\"&#xe073;\" d=\"M500 150v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5zM900 150v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800q0 -21 14.5 -35.5t35.5 -14.5h200 q21 0 35.5 14.5t14.5 35.5z\" />\n<glyph unicode=\"&#xe074;\" d=\"M1100 150v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35z\" />\n<glyph unicode=\"&#xe075;\" d=\"M500 0v488l-500 -488v1100l500 -487v487l564 -550z\" />\n<glyph unicode=\"&#xe076;\" d=\"M1050 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488l-500 -488v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5z\" />\n<glyph unicode=\"&#xe077;\" d=\"M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5z\" />\n<glyph unicode=\"&#xe078;\" d=\"M650 1064l-550 -564h1100zM1200 350v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z\" />\n<glyph unicode=\"&#xe079;\" d=\"M777 7l240 240l-353 353l353 353l-240 240l-592 -594z\" />\n<glyph unicode=\"&#xe080;\" d=\"M513 -46l-241 240l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1z\" />\n<glyph unicode=\"&#xe081;\" d=\"M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM500 900v-200h-200v-200h200v-200h200v200h200v200h-200v200h-200z\" />\n<glyph unicode=\"&#xe082;\" d=\"M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM300 700v-200h600v200h-600z\" />\n<glyph unicode=\"&#xe083;\" d=\"M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM247 741l141 -141l-142 -141l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141 l-141 142z\" />\n<glyph unicode=\"&#xe084;\" d=\"M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM546 623l-102 102l-174 -174l276 -277l411 411l-175 174z\" />\n<glyph unicode=\"&#xe085;\" d=\"M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM500 500h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3 q-105 0 -172 -56t-67 -183h144q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5zM500 400v-100h200v100h-200z\" />\n<glyph unicode=\"&#xe086;\" d=\"M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM500 900v-100h200v100h-200zM400 700v-100h100v-200h-100v-100h400v100h-100v300h-300z\" />\n<glyph unicode=\"&#xe087;\" d=\"M1200 700v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194v200h194q15 60 36 104.5t55.5 86t88 69t126.5 40.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203zM700 500v-206q149 48 201 206h-201v200h200 q-25 74 -76 127.5t-124 76.5v-204h-200v203q-75 -24 -130 -77.5t-79 -125.5h209v-200h-210q24 -73 79.5 -127.5t130.5 -78.5v206h200z\" />\n<glyph unicode=\"&#xe088;\" d=\"M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM844 735 l-135 -135l135 -135l-109 -109l-135 135l-135 -135l-109 109l135 135l-135 135l109 109l135 -135l135 135z\" />\n<glyph unicode=\"&#xe089;\" d=\"M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM896 654 l-346 -345l-228 228l141 141l87 -87l204 205z\" />\n<glyph unicode=\"&#xe090;\" d=\"M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM248 385l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5q0 -115 62 -215zM955 809l-564 -564q97 -59 209 -59q171 0 292.5 121.5 t121.5 292.5q0 112 -59 209z\" />\n<glyph unicode=\"&#xe091;\" d=\"M1200 400h-600v-301l-600 448l600 453v-300h600v-300z\" />\n<glyph unicode=\"&#xe092;\" d=\"M600 400h-600v300h600v300l600 -453l-600 -448v301z\" />\n<glyph unicode=\"&#xe093;\" d=\"M1098 600h-298v-600h-300v600h-296l450 600z\" />\n<glyph unicode=\"&#xe094;\" d=\"M998 600l-449 -600l-445 600h296v600h300v-600h298z\" />\n<glyph unicode=\"&#xe095;\" d=\"M600 199v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453z\" />\n<glyph unicode=\"&#xe096;\" d=\"M1200 1200h-400l129 -129l-294 -294l142 -142l294 294l129 -129v400zM565 423l-294 -294l129 -129h-400v400l129 -129l294 294z\" />\n<glyph unicode=\"&#xe097;\" d=\"M871 730l129 -130h-400v400l129 -129l295 295l142 -141zM200 600h400v-400l-129 130l-295 -295l-142 141l295 295z\" />\n<glyph unicode=\"&#xe101;\" d=\"M600 1177q118 0 224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5t45.5 224.5t123 184t184 123t224.5 45.5zM686 549l58 302q4 20 -8 34.5t-33 14.5h-207q-20 0 -32 -14.5t-8 -34.5 l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5zM700 400h-200v-100h200v100z\" />\n<glyph unicode=\"&#xe102;\" d=\"M1200 900h-111v6t-1 15t-3 18l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6h-111v-100h100v-200h400v300h200v-300h400v200h100v100z M731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269zM481 900h-281q-3 0 14 48t35 96l18 47zM100 0h400v400h-400v-400zM700 400h400v-400h-400v400z\" />\n<glyph unicode=\"&#xe103;\" d=\"M0 121l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55l-201 -202 v143zM692 611q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5t86.5 76.5q55 66 367 234z\" />\n<glyph unicode=\"&#xe105;\" d=\"M1261 600l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30l-26 40l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5 t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30zM600 240q64 0 123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212 q0 85 46 158q-102 -87 -226 -258q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5zM484 762l-107 -106q49 -124 154 -191l105 105q-37 24 -75 72t-57 84z\" />\n<glyph unicode=\"&#xe106;\" d=\"M906 1200l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43l-26 40l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148zM1261 600l-26 -40q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5 t-124 -100t-146.5 -79l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52zM513 264l37 141q-107 18 -178.5 101.5t-71.5 193.5q0 85 46 158q-102 -87 -226 -258q210 -282 393 -336z M484 762l-107 -106q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68z\" />\n<glyph unicode=\"&#xe107;\" d=\"M-47 0h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 33 -48 36t-48 -29l-642 -1066q-21 -32 -7.5 -66t50.5 -34zM700 200v100h-200v-100h-345l445 723l445 -723h-345zM700 700h-200v-100l100 -300l100 300v100z\" />\n<glyph unicode=\"&#xe108;\" d=\"M800 711l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -21 -13 -29t-32 1l-94 78h-222l-94 -78q-19 -9 -32 -1t-13 29v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41q0 20 11 44.5t26 38.5 l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339z\" />\n<glyph unicode=\"&#xe110;\" d=\"M941 800l-600 -600h-341v200h259l600 600h241v198l300 -295l-300 -300v197h-159zM381 678l141 142l-181 180h-341v-200h259zM1100 598l300 -295l-300 -300v197h-241l-181 181l141 142l122 -123h159v198z\" />\n<glyph unicode=\"&#xe111;\" d=\"M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z\" />\n<glyph unicode=\"&#xe112;\" d=\"M400 900h-300v300h300v-300zM1100 900h-300v300h300v-300zM1100 800v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5t-58 109.5t-31.5 116t-15 104t-3 83v200h300v-250q0 -113 6 -145 q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300z\" />\n<glyph unicode=\"&#xe113;\" d=\"M902 184l226 227l-578 579l-580 -579l227 -227l352 353z\" />\n<glyph unicode=\"&#xe114;\" d=\"M650 218l578 579l-226 227l-353 -353l-352 353l-227 -227z\" />\n<glyph unicode=\"&#xe115;\" d=\"M1198 400v600h-796l215 -200h381v-400h-198l299 -283l299 283h-200zM-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196z\" />\n<glyph unicode=\"&#xe116;\" d=\"M1050 1200h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35 q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43l-100 475q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5z\" />\n<glyph unicode=\"&#xe117;\" d=\"M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z\" />\n<glyph unicode=\"&#xe118;\" d=\"M201 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000zM1501 700l-300 -700h-1200l300 700h1200z\" />\n<glyph unicode=\"&#xe119;\" d=\"M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z\" />\n<glyph unicode=\"&#xe120;\" d=\"M900 303v197h-600v-197l-300 297l300 298v-198h600v198l300 -298z\" />\n<glyph unicode=\"&#xe121;\" d=\"M31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM100 300h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM900 200h-100v-100h100v100z M1100 200h-100v-100h100v100z\" />\n<glyph unicode=\"&#xe122;\" d=\"M1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35zM325 800l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35q-56 337 -56 351v250v5 q0 13 0.5 18.5t2.5 13t8 10.5t15 3h200zM-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5z\" />\n<glyph unicode=\"&#xe124;\" d=\"M445 1180l-45 -233l-224 78l78 -225l-233 -44l179 -156l-179 -155l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180z\" />\n<glyph unicode=\"&#xe125;\" d=\"M700 1200h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400q0 -75 100 -75h61q123 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5zM700 925l-50 -225h450 v-125l-250 -375h-214l-136 100h-100v375l150 212l100 213h50v-175zM0 800v-600h200v600h-200z\" />\n<glyph unicode=\"&#xe126;\" d=\"M700 0h-50q-27 0 -51 20t-38 48l-96 198l-145 196q-20 26 -20 63v400q0 75 100 75h61q123 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5zM200 400h-200v600h200 v-600zM700 275l-50 225h450v125l-250 375h-214l-136 -100h-100v-375l150 -212l100 -213h50v175z\" />\n<glyph unicode=\"&#xe127;\" d=\"M364 873l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM408 792v-503 l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83zM208 200h-200v600h200v-600z\" />\n<glyph unicode=\"&#xe128;\" d=\"M475 1104l365 -230q7 -4 16.5 -10.5t26 -26t16.5 -36.5v-526q0 -13 -85.5 -93.5t-93.5 -80.5h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-84 0 -139 39t-55 111t54 110t139 37h302l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6zM370 946 l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100h222q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l106 89v502l-342 237zM1199 201h-200v600h200v-600z\" />\n<glyph unicode=\"&#xe129;\" d=\"M1100 473v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90zM911 400h-503l-236 339 l83 86l183 -146q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6v7.5v7v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294zM1000 200v-200h-600v200h600z\" />\n<glyph unicode=\"&#xe130;\" d=\"M305 1104v200h600v-200h-600zM605 310l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15l-230 -362q-15 -31 7 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85l-1 -302q0 -84 38.5 -138t110.5 -54t111 55t39 139v106z M905 804v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146l-83 86l237 339h503z\" />\n<glyph unicode=\"&#xe131;\" d=\"M603 1195q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM598 701h-298v-201h300l-2 -194l402 294l-402 298v-197z\" />\n<glyph unicode=\"&#xe132;\" d=\"M597 1195q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5zM200 600l400 -294v194h302v201h-300v197z\" />\n<glyph unicode=\"&#xe133;\" d=\"M603 1195q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM300 600h200v-300h200v300h200l-300 400z\" />\n<glyph unicode=\"&#xe134;\" d=\"M603 1195q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM500 900v-300h-200l300 -400l300 400h-200v300h-200z\" />\n<glyph unicode=\"&#xe135;\" d=\"M603 1195q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM627 1101q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6 q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55 t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q102 -2 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7 q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5 t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 41 1 44q31 -13 58.5 -14.5t39.5 3.5l11 4q6 36 -17 53.5t-64 28.5t-56 23q-19 -3 -37 0zM613 994q0 -18 8 -42.5t16.5 -44t9.5 -23.5q-9 2 -31 5t-36 5t-32 8t-30 14q3 12 16 30t16 25q10 -10 18.5 -10 t14 6t14.5 14.5t16 12.5z\" />\n<glyph unicode=\"&#xe137;\" horiz-adv-x=\"1220\" d=\"M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z \" />\n<glyph unicode=\"&#xe138;\" d=\"M1100 1200v-100h-1000v100h1000zM150 1000h900l-350 -500v-300l-200 -200v500z\" />\n<glyph unicode=\"&#xe140;\" d=\"M329 729l142 142l-200 200l129 129h-400v-400l129 129zM1200 1200v-400l-129 129l-200 -200l-142 142l200 200l-129 129h400zM271 129l129 -129h-400v400l129 -129l200 200l142 -142zM1071 271l129 129v-400h-400l129 129l-200 200l142 142z\" />\n<glyph unicode=\"&#xe141;\" d=\"M596 1192q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM596 1010q-171 0 -292.5 -121.5t-121.5 -292.5q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5zM455 905 q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5t16 38.5t39 16.5zM708 821l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5 q0 32 20.5 56.5t51.5 29.5zM855 709q23 0 38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39q0 22 16 38t39 16zM345 709q23 0 39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39t15.5 38.5t38.5 15.5z\" />\n<glyph unicode=\"&#xe143;\" d=\"M649 54l-16 22q-90 125 -293 323q-71 70 -104.5 105.5t-77 89.5t-61 99t-17.5 91q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-203 -198 -293 -323zM844 524l12 12 q64 62 97.5 97t64.5 79t31 72q0 71 -48 119t-105 48q-74 0 -132 -82l-118 -171l-114 174q-51 79 -123 79q-60 0 -109.5 -49t-49.5 -118q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203z\" />\n<glyph unicode=\"&#xe144;\" d=\"M476 406l19 -17l105 105l-212 212l389 389l247 -247l-95 -96l18 -18q46 -46 77 -99l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159q0 -93 66 -159zM123 193l141 -141q66 -66 159 -66q95 0 159 66 l283 283q66 66 66 159t-66 159l-141 141q-12 12 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159q0 -94 66 -160z\" />\n<glyph unicode=\"&#xe145;\" d=\"M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM900 1000h-600v-700h600v700zM600 46q43 0 73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5t-73.5 -30.5t-30.5 -73.5 t30.5 -73.5t73.5 -30.5z\" />\n<glyph unicode=\"&#xe148;\" d=\"M700 1029v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5h139q5 -77 48.5 -126.5t117.5 -64.5v335l-27 7q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5 t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5zM600 755v274q-61 -8 -97.5 -37.5t-36.5 -102.5q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3zM700 548 v-311q170 18 170 151q0 64 -44 99.5t-126 60.5z\" />\n<glyph unicode=\"&#xe149;\" d=\"M866 300l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5t-30 142.5h-221v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5 t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -11 2.5 -24.5t5.5 -24t9.5 -26.5t10.5 -25t14 -27.5t14 -25.5t15.5 -27t13.5 -24h242v-100h-197q8 -50 -2.5 -115t-31.5 -94 q-41 -59 -99 -113q35 11 84 18t70 7q32 1 102 -16t104 -17q76 0 136 30z\" />\n<glyph unicode=\"&#xe150;\" d=\"M300 0l298 300h-198v900h-200v-900h-198zM900 1200l298 -300h-198v-900h-200v900h-198z\" />\n<glyph unicode=\"&#xe151;\" d=\"M400 300h198l-298 -300l-298 300h198v900h200v-900zM1000 1200v-500h-100v100h-100v-100h-100v500h300zM901 1100h-100v-200h100v200zM700 500h300v-200h-99v-100h-100v100h99v100h-200v100zM800 100h200v-100h-300v200h100v-100z\" />\n<glyph unicode=\"&#xe152;\" d=\"M400 300h198l-298 -300l-298 300h198v900h200v-900zM1000 1200v-200h-99v-100h-100v100h99v100h-200v100h300zM800 800h200v-100h-300v200h100v-100zM700 500h300v-500h-100v100h-100v-100h-100v500zM801 200h100v200h-100v-200z\" />\n<glyph unicode=\"&#xe153;\" d=\"M300 0l298 300h-198v900h-200v-900h-198zM900 1100h-100v100h200v-500h-100v400zM1100 500v-500h-100v100h-200v400h300zM1001 400h-100v-200h100v200z\" />\n<glyph unicode=\"&#xe154;\" d=\"M300 0l298 300h-198v900h-200v-900h-198zM1100 1200v-500h-100v100h-200v400h300zM1001 1100h-100v-200h100v200zM900 400h-100v100h200v-500h-100v400z\" />\n<glyph unicode=\"&#xe155;\" d=\"M300 0l298 300h-198v900h-200v-900h-198zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z\" />\n<glyph unicode=\"&#xe156;\" d=\"M300 0l298 300h-198v900h-200v-900h-198zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z\" />\n<glyph unicode=\"&#xe157;\" d=\"M400 1100h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5 t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5z\" />\n<glyph unicode=\"&#xe158;\" d=\"M700 0h-300q-163 0 -281.5 117.5t-118.5 282.5v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5 t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5zM400 800v-500l333 250z\" />\n<glyph unicode=\"&#xe159;\" d=\"M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM900 300v500q0 41 -29.5 70.5t-70.5 29.5h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5 t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5zM800 700h-500l250 -333z\" />\n<glyph unicode=\"&#xe160;\" d=\"M1100 700v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5zM900 300v500q0 41 -29.5 70.5t-70.5 29.5h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5 t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5zM550 733l-250 -333h500z\" />\n<glyph unicode=\"&#xe161;\" d=\"M500 1100h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200zM700 550l-400 -350v200h-300v300h300v200z\" />\n<glyph unicode=\"&#xe162;\" d=\"M403 2l9 -1q13 0 26 16l538 630q15 19 6 36q-8 18 -32 16h-300q1 4 78 219.5t79 227.5q2 17 -6 27l-8 8h-9q-16 0 -25 -15q-4 -5 -98.5 -111.5t-228 -257t-209.5 -238.5q-17 -19 -7 -40q10 -19 32 -19h302q-155 -438 -160 -458q-5 -21 4 -32z\" />\n<glyph unicode=\"&#xe163;\" d=\"M800 200h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185zM900 200v200h-300v300h300v200l400 -350z\" />\n<glyph unicode=\"&#xe164;\" d=\"M1200 700l-149 149l-342 -353l-213 213l353 342l-149 149h500v-500zM1022 571l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5v-300 q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98z\" />\n<glyph unicode=\"&#xe165;\" d=\"M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM600 794 q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z\" />\n<glyph unicode=\"&#xe166;\" d=\"M700 800v400h-300v-400h-300l445 -500l450 500h-295zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z\" />\n<glyph unicode=\"&#xe167;\" d=\"M400 700v-300h300v300h295l-445 500l-450 -500h300zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z\" />\n<glyph unicode=\"&#xe168;\" d=\"M405 400l596 596l-154 155l-442 -442l-150 151l-155 -155zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z\" />\n<glyph unicode=\"&#xe169;\" d=\"M409 1103l-97 97l-212 -212l97 -98zM650 861l-149 149l-212 -212l149 -149l-238 -248h700v699zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z\" />\n<glyph unicode=\"&#xe170;\" d=\"M539 950l-149 -149l212 -212l149 148l248 -237v700h-699zM297 709l-97 -97l212 -212l98 97zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z\" />\n<glyph unicode=\"&#xe171;\" d=\"M1200 1199v-1079l-475 272l-310 -393v416h-392zM1166 1148l-672 -712v-226z\" />\n<glyph unicode=\"&#xe172;\" d=\"M1100 1000v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1200h-100v-200h100v200z\" />\n<glyph unicode=\"&#xe173;\" d=\"M578 500h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120zM700 1200h-100v-200h100v200zM1300 538l-475 -476l-244 244l123 123l120 -120l353 352z\" />\n<glyph unicode=\"&#xe174;\" d=\"M529 500h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170zM700 1200h-100v-200h100v200zM1167 6l-170 170l-170 -170l-127 127l170 170l-170 170l127 127l170 -170l170 170l127 -128 l-170 -169l170 -170z\" />\n<glyph unicode=\"&#xe175;\" d=\"M700 500h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200zM700 1000h-100v200h100v-200zM1000 600h-200v-300h-200l300 -300l300 300h-200v300z\" />\n<glyph unicode=\"&#xe176;\" d=\"M602 500h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200zM700 1000h-100v200h100v-200zM1000 300h200l-300 300l-300 -300h200v-300h200v300z\" />\n<glyph unicode=\"&#xe177;\" d=\"M1200 900v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h1200zM0 800v-550q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200zM100 500h400v-200h-400v200z\" />\n<glyph unicode=\"&#xe178;\" d=\"M500 1000h400v198l300 -298l-300 -298v198h-400v200zM100 800v200h100v-200h-100zM400 800h-100v200h100v-200zM700 300h-400v-198l-300 298l300 298v-198h400v-200zM800 500h100v-200h-100v200zM1000 500v-200h100v200h-100z\" />\n<glyph unicode=\"&#xe179;\" d=\"M1200 50v1106q0 31 -18 40.5t-44 -7.5l-276 -117q-25 -16 -43.5 -50.5t-18.5 -65.5v-359q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5zM550 1200l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447l-100 203v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300z\" />\n<glyph unicode=\"&#xe180;\" d=\"M1100 106v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394 q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5z\" />\n<glyph unicode=\"&#xe181;\" d=\"M675 1000l-100 100h-375l-100 -100h400l200 -200v-98l295 98h105v200h-425zM500 300v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5zM100 800h300v-200h-300v200zM700 565l400 133 v-163l-400 -133v163zM100 500h300v-200h-300v200zM805 300l295 98v-298h-425l-100 -100h-375l-100 100h400l200 200h105z\" />\n<glyph unicode=\"&#xe182;\" d=\"M179 1169l-162 -162q-1 -11 -0.5 -32.5t16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q16 17 13 40.5t-22 37.5l-192 136q-19 14 -45 12t-42 -19l-119 -118q-143 103 -267 227q-126 126 -227 268l118 118 q17 17 20 41.5t-11 44.5l-139 194q-14 19 -36.5 22t-40.5 -14z\" />\n<glyph unicode=\"&#xe183;\" d=\"M1200 712v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40t-53.5 -36.5t-31 -27.5l-9 -10v-200q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38 t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5zM800 650l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -15 -35.5t-35 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5 t30 -27.5t12 -24l1 -10v-50z\" />\n<glyph unicode=\"&#xe184;\" d=\"M175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250zM1200 100v-100h-1100v100h1100z\" />\n<glyph unicode=\"&#xe185;\" d=\"M600 1100h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300v1000q0 41 29.5 70.5t70.5 29.5zM1000 800h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300v700q0 41 29.5 70.5t70.5 29.5zM400 0v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400h300z\" />\n<glyph unicode=\"&#xe186;\" d=\"M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM200 800v-300h200v-100h-200v-100h300v300h-200v100h200v100h-300zM800 800h-200v-500h200v100h100v300h-100 v100zM800 700v-300h-100v300h100z\" />\n<glyph unicode=\"&#xe187;\" d=\"M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM400 600h-100v200h-100v-500h100v200h100v-200h100v500h-100v-200zM800 800h-200v-500h200v100h100v300h-100 v100zM800 700v-300h-100v300h100z\" />\n<glyph unicode=\"&#xe188;\" d=\"M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM200 800v-500h300v100h-200v300h200v100h-300zM600 800v-500h300v100h-200v300h200v100h-300z\" />\n<glyph unicode=\"&#xe189;\" d=\"M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM500 700l-300 -150l300 -150v300zM600 400l300 150l-300 150v-300z\" />\n<glyph unicode=\"&#xe190;\" d=\"M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM900 800v-500h-700v500h700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM800 700h-130 q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300z\" />\n<glyph unicode=\"&#xe191;\" d=\"M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM200 800v-300h200v-100h-200v-100h300v300h-200v100h200v100h-300zM800 300h100v500h-200v-100h100v-400z M601 300h100v100h-100v-100z\" />\n<glyph unicode=\"&#xe192;\" d=\"M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM300 700v100h-100v-500h300v400h-200zM800 300h100v500h-200v-100h100v-400zM401 400h-100v200h100v-200z M601 300h100v100h-100v-100z\" />\n<glyph unicode=\"&#xe193;\" d=\"M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM1000 900h-900v-700h900v700zM400 700h-200v100h300v-300h-99v-100h-100v100h99v200zM800 700h-100v100h200v-500h-100v400zM201 400h100v-100 h-100v100zM701 300h-100v100h100v-100z\" />\n<glyph unicode=\"&#xe194;\" d=\"M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM800 700h-300 v-200h300v-100h-300l-100 100v200l100 100h300v-100z\" />\n<glyph unicode=\"&#xe195;\" d=\"M596 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM596 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM800 700v-100 h-100v100h-200v-100h200v-100h-200v-100h-100v400h300zM800 400h-100v100h100v-100z\" />\n<glyph unicode=\"&#xe197;\" d=\"M800 300h128q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5q0 -80 56.5 -137t135.5 -57h222v300h400v-300zM700 200h200l-300 -300 l-300 300h200v300h200v-300z\" />\n<glyph unicode=\"&#xe198;\" d=\"M600 714l403 -403q94 26 154.5 104t60.5 178q0 121 -85 207.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5q0 -80 56.5 -137t135.5 -57h8zM700 -100h-200v300h-200l300 300 l300 -300h-200v-300z\" />\n<glyph unicode=\"&#xe199;\" d=\"M700 200h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-155l-75 -45h350l-75 45v155z\" />\n<glyph unicode=\"&#xe200;\" d=\"M700 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -12t1 -11q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5 q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350z\" />\n<glyph unicode=\"&#x1f4bc;\" d=\"M800 1000h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100zM500 1000h200v100h-200v-100zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z\" />\n<glyph unicode=\"&#x1f4c5;\" d=\"M1100 900v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150h1100zM0 800v-750q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100zM100 600h100v-100h-100v100zM300 600h100v-100h-100v100z M500 600h100v-100h-100v100zM700 600h100v-100h-100v100zM900 600h100v-100h-100v100zM100 400h100v-100h-100v100zM300 400h100v-100h-100v100zM500 400h100v-100h-100v100zM700 400h100v-100h-100v100zM900 400h100v-100h-100v100zM100 200h100v-100h-100v100zM300 200 h100v-100h-100v100zM500 200h100v-100h-100v100zM700 200h100v-100h-100v100zM900 200h100v-100h-100v100z\" />\n<glyph unicode=\"&#x1f4cc;\" d=\"M902 1185l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207l-380 -303l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15z\" />\n<glyph unicode=\"&#x1f4ce;\" d=\"M518 119l69 -60l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163t35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84 t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -79.5 -17t-67.5 -51l-388 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348 q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256z\" />\n<glyph unicode=\"&#x1f4f7;\" d=\"M1200 200v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5z M1000 700h-100v100h100v-100zM844 500q0 -100 -72 -172t-172 -72t-172 72t-72 172t72 172t172 72t172 -72t72 -172zM706 500q0 44 -31 75t-75 31t-75 -31t-31 -75t31 -75t75 -31t75 31t31 75z\" />\n<glyph unicode=\"&#x1f512;\" d=\"M900 800h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z\" />\n<glyph unicode=\"&#x1f514;\" d=\"M1062 400h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-22 -9 -63 -23t-167.5 -37t-251.5 -23t-245.5 20.5t-178.5 41.5l-58 20q-18 7 -31 27.5t-13 40.5q0 21 13.5 35.5t33.5 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94 q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327zM600 104q-54 0 -103 6q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6z\" />\n<glyph unicode=\"&#x1f516;\" d=\"M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z\" />\n<glyph unicode=\"&#x1f525;\" d=\"M400 755q2 -12 8 -41.5t8 -43t6 -39.5t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85t5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5 q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129 q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5z\" />\n<glyph unicode=\"&#x1f527;\" d=\"M948 778l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138z\" />\n</font>\n</defs></svg> ","glyphicons-halflings-regular.ttf":"\u0000\u0001\u0000\u0000\u0000\u000f\u0000�\u0000\u0003\u0000pFFTMh+�\r\u0000\u0000\u0000�\u0000\u0000\u0000\u001cGDEF\u0001\b\u0000\u0004\u0000\u0000\u0001\u0018\u0000\u0000\u0000 OS/2i\u001el�\u0000\u0000\u00018\u0000\u0000\u0000`cmap�/V�\u0000\u0000\u0001�\u0000\u0000\u0005.cvt \u0000(\u0002�\u0000\u0000\u0006�\u0000\u0000\u0000\u0004gasp��\u0000\u0003\u0000\u0000\u0006�\u0000\u0000\u0000\bglyf\u0001��\u0016\u0000\u0000\u0006�\u0000\u0000[Xhead\u00008=�\u0000\u0000b,\u0000\u0000\u00006hhea\n�\u0004x\u0000\u0000bd\u0000\u0000\u0000$hmtx�\u000e\u0012p\u0000\u0000b�\u0000\u0000\u0002�loca���@\u0000\u0000ep\u0000\u0000\u0001�maxp\u0001.\u0000�\u0000\u0000g(\u0000\u0000\u0000 nameԖ��\u0000\u0000gH\u0000\u0000\u0003|post�cQw\u0000\u0000j�\u0000\u0000\bywebfK)Q�\u0000\u0000s@\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000�=��\u0000\u0000\u0000\u0000��\u0017�\u0000\u0000\u0000\u0000����\u0000\u0001\u0000\u0000\u0000\u000e\u0000\u0000\u0000\u0018\u0000\u0000\u0000\u0000\u0000\u0002\u0000\u0001\u0000\u0001\u0000�\u0000\u0001\u0000\u0004\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0003\u0004�\u0001�\u0000\u0005\u0000\u0004\u0003\f\u0002�\u0000\u0000\u0000Z\u0003\f\u0002�\u0000\u0000\u0001�\u00002\u0002�\u0000\u0000\u0000\u0000\u0005\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000UKWN\u0000@\u0000 ��\u0005x��\u0000\u001c\u0005�\u0000\f\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000 \u0000\u0001\u0000\u0000\u0000\u0005\u0000\u0000\u0000\u0003\u0000\u0000\u0000,\u0000\u0000\u0000\n\u0000\u0000\u0001�\u0000\u0001\u0000\u0000\u0000\u0000\u0004(\u0000\u0003\u0000\u0001\u0000\u0000\u0000,\u0000\u0003\u0000\n\u0000\u0000\u0001�\u0000\u0004\u0001p\u0000\u0000\u0000X\u0000@\u0000\u0005\u0000\u0018\u0000 \u0000+\u0000� \n / _ �\"\u0012&\u0001'\t'\u000f�\u0003�\t�\u0019�)�2�9�C�E�I�Y�`�i�y�����\u0003�\b�\u0019�\"�)�5�8�A�E�I�Y�i�y�������\u0000��\u0000\u0000\u0000 \u0000*\u0000� \u0000 / _ �\"\u0012&\u0001'\t'\u000f�\u0000�\u0005�\u0010� �0�4�@�E�G�P�`�b�p�����\u0001�\u0005�\u0010� �$�0�7�@�C�H�P�`�p�������\u0000�������f�\u0007��ߴ�h�\u0003�\u0015�\u000e�\t \u0019 \u0018 \u0012 \f \u0006 \u0005\u001f�\u001f�\u001f�\u001f�\u001f�\u001f�\u001f�\u001f�\u001f�\u001fu\u001ft\u001fm\u001fg\u001ff\u001f`\u001f_\u001fX\u001fW\u001fU\u001fO\u001fI\u001fC\u001f=\u001f7\u001f6\u001e�\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\f\u0000\u0000\u0000\u0000\u0002�\u0000\u0000\u0000\u0000\u0000\u0000\u00005\u0000\u0000\u0000 \u0000\u0000\u0000 \u0000\u0000\u0000\u0003\u0000\u0000\u0000*\u0000\u0000\u0000+\u0000\u0000\u0000\u0004\u0000\u0000\u0000�\u0000\u0000\u0000�\u0000\u0000\u0000\u0006\u0000\u0000 \u0000\u0000\u0000 \n\u0000\u0000\u0000\u0007\u0000\u0000 /\u0000\u0000 /\u0000\u0000\u0000\u0012\u0000\u0000 _\u0000\u0000 _\u0000\u0000\u0000\u0013\u0000\u0000 �\u0000\u0000 �\u0000\u0000\u0000\u0014\u0000\u0000\"\u0012\u0000\u0000\"\u0012\u0000\u0000\u0000\u0015\u0000\u0000&\u0001\u0000\u0000&\u0001\u0000\u0000\u0000\u0016\u0000\u0000'\t\u0000\u0000'\t\u0000\u0000\u0000\u0017\u0000\u0000'\u000f\u0000\u0000'\u000f\u0000\u0000\u0000\u0018\u0000\u0000�\u0000\u0000\u0000�\u0003\u0000\u0000\u0000\u0019\u0000\u0000�\u0005\u0000\u0000�\t\u0000\u0000\u0000\u001d\u0000\u0000�\u0010\u0000\u0000�\u0019\u0000\u0000\u0000\"\u0000\u0000� \u0000\u0000�)\u0000\u0000\u0000,\u0000\u0000�0\u0000\u0000�2\u0000\u0000\u00006\u0000\u0000�4\u0000\u0000�9\u0000\u0000\u00009\u0000\u0000�@\u0000\u0000�C\u0000\u0000\u0000?\u0000\u0000�E\u0000\u0000�E\u0000\u0000\u0000C\u0000\u0000�G\u0000\u0000�I\u0000\u0000\u0000D\u0000\u0000�P\u0000\u0000�Y\u0000\u0000\u0000G\u0000\u0000�`\u0000\u0000�`\u0000\u0000\u0000Q\u0000\u0000�b\u0000\u0000�i\u0000\u0000\u0000R\u0000\u0000�p\u0000\u0000�y\u0000\u0000\u0000Z\u0000\u0000��\u0000\u0000��\u0000\u0000\u0000d\u0000\u0000��\u0000\u0000��\u0000\u0000\u0000n\u0000\u0000�\u0001\u0000\u0000�\u0003\u0000\u0000\u0000v\u0000\u0000�\u0005\u0000\u0000�\b\u0000\u0000\u0000y\u0000\u0000�\u0010\u0000\u0000�\u0019\u0000\u0000\u0000}\u0000\u0000� \u0000\u0000�\"\u0000\u0000\u0000�\u0000\u0000�$\u0000\u0000�)\u0000\u0000\u0000�\u0000\u0000�0\u0000\u0000�5\u0000\u0000\u0000�\u0000\u0000�7\u0000\u0000�8\u0000\u0000\u0000�\u0000\u0000�@\u0000\u0000�A\u0000\u0000\u0000�\u0000\u0000�C\u0000\u0000�E\u0000\u0000\u0000�\u0000\u0000�H\u0000\u0000�I\u0000\u0000\u0000�\u0000\u0000�P\u0000\u0000�Y\u0000\u0000\u0000�\u0000\u0000�`\u0000\u0000�i\u0000\u0000\u0000�\u0000\u0000�p\u0000\u0000�y\u0000\u0000\u0000�\u0000\u0000��\u0000\u0000��\u0000\u0000\u0000�\u0000\u0000��\u0000\u0000��\u0000\u0000\u0000�\u0000\u0000��\u0000\u0000��\u0000\u0000\u0000�\u0000\u0000�\u0000\u0000\u0000�\u0000\u0000\u0000\u0000�\u0000\u0001��\u0000\u0001��\u0000\u0000\u0000�\u0000\u0001��\u0000\u0001��\u0000\u0000\u0000�\u0000\u0001��\u0000\u0001��\u0000\u0000\u0000�\u0000\u0001��\u0000\u0001��\u0000\u0000\u0000�\u0000\u0001��\u0000\u0001��\u0000\u0000\u0000�\u0000\u0001�\u0012\u0000\u0001�\u0012\u0000\u0000\u0000�\u0000\u0001�\u0014\u0000\u0001�\u0014\u0000\u0000\u0000�\u0000\u0001�\u0016\u0000\u0001�\u0016\u0000\u0000\u0000�\u0000\u0001�%\u0000\u0001�%\u0000\u0000\u0000�\u0000\u0001�'\u0000\u0001�'\u0000\u0000\u0000�\u0000\u0000\u0001\u0006\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0002\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0004\u0005\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0014\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000(\u0002�\u0000\u0000\u0000\u0001��\u0000\u0002\u0000\u0002\u0000(\u0000\u0000\u0001h\u0003 \u0000\u0003\u0000\u0007\u0000.�\u0001\u0000/<�\u0007\u0004\u0000�2�\u0006\u0005�<�\u0003\u0002\u0000�2\u0000�\u0003\u0000/<�\u0005\u0004\u0000�2�\u0007\u0006\u0001�<�\u0001\u0002\u0000�23\u0011!\u0011%3\u0011#(\u0001@����\u0003 ��(\u0002�\u0000\u0001\u0000d\u0000d\u0004L\u0004L\u0000\u0017\u0000\u0000\u0001!\u0017\u0007'\u0011#\u0011\u0007'7!5!'7\u0017\u00113\u00117\u0017\u0007!\u0004L�����ȷ����\u0001\u0003���ȷ��\u0001\u0003\u0001𷍷��\u0001\u0003���ȷ��\u0001\u0003�����\u0000\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0004L\u0004L\u0000\u000b\u0000\u0000\u0001!\u0011!\u0011!\u0011!\u0011!\u0011!\u0004L�p���p\u0001�\u0001,\u0001�\u0001��p\u0001�\u0001,\u0001��p\u0000\u0001\u0000d\u0000\u0005\u0004�\u0004�\u00007\u0000\u0000\u0001!\u001e\u000432>\u000253\u0006\u0007\u0006#\"'.\u0001'#7347#7367632\u0017\u0016\u0017#4.\u0002#\"\u000e\u0002\u0007!\u0007!\u0006\u0015!\u0003 ��\t09C3\u0015\u001dJL3�\u001fak��w$B\f�dq\u0005�d�%Ku��p<\u0006�3LJ\u001e\u00189D?\u0013\u0001{d��\u0006\u0001�\u0001�JtB+\u000f\u001a0W5�ju�.�xd/5d�Z��gj7X0\u0019\u0014,Z>d.6\u0000\u0001\u0000�\u0001�\u0004L\u0002�\u0000\u0003\u0000\u0000\u0001!\u0011!\u0004L�|\u0003�\u0002���\u0000\u0001��\u0001,\u0004�\u0004A\u0000\u0016\u0000\u0000\u0013!2654&#\"\u0007.\u0001#\"\u0006\u0015\u0014\u0017\u000e\u0001\u0015\u0014\u0016�\u0002�x��x.,,�n��\u0002BUq\u0001,�zx�\u000eawי\u0019\f\u000ekEPr\u0000\u0000\u0000\u0000\u0004\u0000\u0000\u0000d\u0004�\u0004L\u0000\u0002\u0000\u0005\u0000\b\u0000\r\u0000\u0000\u0001!\u0001%\u0001\u0011!\u0011\u0001\u0007\u0001!\u0001\u0017\u0004��P\u0002X����\u0004���d\u0001��P\u0001��\u0004L��g��\u0002X��\u0001,d�p\u0001��\u0000\u0000\u0003����\u0004�\u0004�\u0000\t\u0000\r\u0000\u0010\u0000\u0000\u0001764/\u0001&\"\u000f\u0001\t\u0001'\u0001\u0003%'\u0004Mc\r\r�\u000f$\u000f^�\u001a\u0002f����\u0001M�\u0003y\\\r'\u000e�\r\ra�n\u0002f����`p�\u0000\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00001\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\t\u0000\u0000%!\u0015!5!\u0011\u0001!\u0001\u0002�\u0001,��\u0001,�\f\u0004��\fddd\u0002&\u0002&��\u0000\u0000\u0001\u0000\u000e\u0000\b\u0004L\u0004�\u0000 \u0000\u0000\u0001\u0011&\u0007\u000e\u0001\u0017\u001e\u00017>\u00015\u00114&\u0007\u0005\u000e\u0001\u0015\u0011&\u0007\u000e\u0001\u001e\u00017>\u00015\u0011\u0003�@JOW\u0012\u0011�OFS\u000e\n�\u0010\n\u000e@JOW$�OAX\u0003���\u0010\u0017\u001ar67)\u0019\u0017Q7\u0003q\n\u000b\u0003�\u0003\u0013\n�O\u0011\u0018\u0019rn)\u001a\u0015`*\u0002^\u0000\u0002\u0000\u0017��\u0004�\u0004�\u0000\u0013\u0000\u001b\u0000\u0000\t\u0001\u0016\u0014\u000f\u0001\u0006\"'\u0001\u0006#\"\u0000\u0010\u0000 \u0000\u0015\u0014\u0000\u0010\u0016 6\u0010& \u0003�\u0001,\u0007\u0007m\b\u0014\b��w����\u0001\u001c\u0001�\u0001\u001c���\u0001\u0012����\u0001���\b\u0014\bm\u0007\u0007\u0001,N\u0001\u001c\u0001�\u0001\u001c��Ȏ\u0001\u0016����\u0001\u0012�\u0000\u0001\u0000d\u0000X\u0004�\u0004D\u0000\u0019\u0000\u0000\u0001>\u0002\u001e\u0002\u0015\u0014\u000e\u0003\u0007.\u000454>\u0002\u001e\u0001\u0002�0{xuX6Cy��>>��yC8Zwwy\u0003�EH\u0004-Sv@9y��UU��y9@vS-\u0004I\u0000\u0000\u0000\u0002��\u0000G\u0004�\u0004�\u0000\n\u0000\f\u0000\u0000\u0001\u0013\t\u0001\u0013\u0001!\u00133\u0013!\u00017\u0003\u0017�������|\u0001ߒ\u0002�\u0001��\u0013\u0002\u0002\n�?\u0001\u0013��\u0001�\u0001\u0018\u0001��p�'\u0001\u0000\u0003��\u0000G\u0004�\u0004�\u0000\n\u0000\u0014\u0000\u0016\u0000\u0000\u0001\u0013\t\u0001\u0013\u0001!\u00133\u0013!\u0001'7#'\u0007#\u0017\u00077\u00017\u0003\u0017�������|\u0001ߒ\u0002�\u0001��VJ��MN��I���\u0002\u0002\n�?\u0001\u0013��\u0001�\u0001\u0018\u0001��p�+���ӎ���o\u0001\u0000\u0001\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u0013\u0000\u0000%\u0015!5\u00015\"&=\u0001462\u0016\u001d\u0001\u0014\u0006#\u0015\u0004��P\u0001�%?���?%���\u0001\u0001d�3�|��|�3�d\u0000\r\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u0003\u0000\u0007\u0000\u000b\u0000\u000f\u0000\u0013\u0000\u0017\u0000\u001b\u0000\u001f\u0000#\u0000'\u0000+\u0000/\u00003\u0000\u0000\u0001\u0011!\u0011\u0017#\u00153%!\u0011!\u0013#\u00153\u0005#\u00153%#\u00153\u0005#\u00153%#\u00153#!\u0011!\u0001#\u00153%#\u00153\u0005#\u00153%#\u00153\u0004��P�dd\u0002���\u0002X�dd�|dd\u0003�dd�|dd\u0003�dd���\u0002X�Ddd\u0003�dd�|dd\u0003�dd\u0004L��\u0004Lddd�p\u0001�ddddddddd�p\u0001,ddddddd\u0000\u0004\u0000\u0000\u0000\u0000\u0004L\u0004L\u0000\u000f\u0000\u001f\u0000/\u0000?\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0005\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0005\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0001�\u001d\u0015�p\u0015\u001d\u001d\u0015\u0001�\u0015\u001d\u0002X\u001d\u0015�p\u0015\u001d\u001d\u0015\u0001�\u0015\u001d��\u001d\u0015�p\u0015\u001d\u001d\u0015\u0001�\u0015\u001d\u0002X\u001d\u0015�p\u0015\u001d\u001d\u0015\u0001�\u0015\u001d\u0004\u001a�p\u0015\u001d\u001d\u0015\u0001�\u0015\u001d\u001d\u0015�p\u0015\u001d\u001d\u0015\u0001�\u0015\u001d\u001d���p\u0015\u001d\u001d\u0015\u0001�\u0015\u001d\u001d\u0015�p\u0015\u001d\u001d\u0015\u0001�\u0015\u001d\u001d\u0000\u0000\u0000\t\u0000\u0000\u0000\u0000\u0004L\u0004L\u0000\u000f\u0000\u001f\u0000/\u0000?\u0000O\u0000_\u0000o\u0000\u0000�\u0000\u0000\u0001\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0005\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0005\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0001\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0005\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0005\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0001\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0005\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0005\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0001,\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0001�\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0001�\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d��\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0001�\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0001�\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d��\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0001�\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0001�\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0004\u001a�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d�[�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d�[�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u001f\u0000/\u0000?\u0000O\u0000_\u0000\u0000\u0001\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0005\u0015\u0014\u0006#!\"&=\u0001463!2\u0016\u000154&+\u0001\"\u0006\u001d\u0001\u0014\u0016;\u000126%\u0015\u0014\u0006#!\"&=\u0001463!2\u0016\u0001\u0015\u0014\u0006+\u0001\"&=\u000146;\u00012\u0016\u0005\u0015\u0014\u0006#!\"&=\u0001463!2\u0016\u0001,\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0003�\u001d\u0015�D\u0015\u001d\u001d\u0015\u0002�\u0015\u001d�|\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0003�\u001d\u0015�D\u0015\u001d\u001d\u0015\u0002�\u0015\u001d�|\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0003�\u001d\u0015�D\u0015\u001d\u001d\u0015\u0002�\u0015\u001d\u0004\u001a�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d���\u0015\u001d\u001d\u0015�\u0015\u001d\u001d��\u0015\u001d\u001d\u0015�\u0015\u001d\u001d�[�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u001d\u0000\u0000\u0000\u0000\u0001\u0000\u001d\u0000\"\u0004�\u0004*\u0000\u0005\u0000\u0000%\u0001'\u0001'\u0007\u0001�\u00032�����\"\u00034�����\u0000\u0000\u0000\u0000\u0001\u0000j\u0000j\u0004F\u0004F\u0000\u000b\u0000\u0000%\t\u0001'\t\u00017\t\u0001\u0017\t\u0001\u0003r�����\u0001\u001a���\u0001\u001a\u0001\u001a���\u0001\u001aj\u0001\u001a���\u0001\u001a\u0001\u001a���\u0001\u001a�����\u0000\u0000\u0003\u0000\u0017��\u0004�\u0004�\u0000\u0013\u0000\u001b\u0000'\u0000\u0000\t\u0001\u0016\u0014\u000f\u0001\u0006\"'\u0001\u0006#\"\u0000\u0010\u0000 \u0000\u0015\u0014\u0004 6\u0010& \u0006\u0010%3\u0015#\u0015#5#5353\u0003�\u0001,\u0007\u0007m\b\u0014\b��w����\u0001\u001c\u0001�\u0001\u001c��\u0001\u0012�����\u0001�dd�dd�\u0001���\b\u0014\bm\u0007\u0007\u0001,N\u0001\u001c\u0001�\u0001\u001c��Ȏ��\u0001\u0012������dd�d\u0000\u0000\u0003\u0000\u0017��\u0004�\u0004�\u0000\u0013\u0000\u001b\u0000\u001f\u0000\u0000\t\u0001\u0016\u0014\u000f\u0001\u0006\"'\u0001\u0006#\"\u0000\u0010\u0000 \u0000\u0015\u0014\u0000\u0010\u0016 6\u0010& \u0007\u0015!5\u0003�\u0001,\u0007\u0007m\b\u0014\b��x����\u0001\u001c\u0001�\u0001\u001c���\u0001\u0012����F\u0001�\u0001���\u0007\u0016\u0007m\b\b\u0001+M\u0001\u001c\u0001�\u0001\u001c��ȍ\u0001\u0015����\u0001\u0010����\u0000\u0002\u0000\u0017\u0000\u0017\u0004�\u0004�\u0000\u000f\u0000+\u0000\u0000\u0001\u00114&+\u0001\"\u0006\u0015\u0011\u0014\u0016;\u00012675\u0016\u0012\u0015\u0014\u000e\u0002\".\u000254\u00127\u0015\u000e\u0001\u0015\u0014\u0016 654&\u0002�\u001d\u0015d\u0015\u001d\u001d\u0015d\u0015\u001dd��[���՛[Ò§g|�\u0001b�|\u0002�\u0001�\u0015\u001d\u001d\u0015�p\u0015\u001d\u001d��>�طv՛[[��v�\u0001(>�7�x����x�\u0000\u0000\u0000\u0004\u0000d\u0000\u0001\u0004�\u0004�\u0000\u0003\u0000\u0007\u0000\u000b\u0000\u000f\u0000\u0000%#\u00113\u0001#\u00113\u0001#\u00113\u0005#\u00113\u0004���������������\u0001\u0004��P\u0003 ��\u0001����\u0000\u0000\u0000\u0002\u0000\u001a\u0000\u001b\u0004�\u0004�\u0000G\u0000Q\u0000\u0000%\u0017\u001632?\u00026?\u0001\u001767'76?\u0002654/\u0002&/\u00017&'\u0007'&/\u0002&#\"\u000f\u0002\u0006\u000f\u0001'\u0006\u0007\u0017\u0007\u0006\u000f\u0002\u0006\u0015\u0014\u001f\u0002\u0016\u001f\u0001\u0007\u0016\u00177\u0017\u0016\u0017\u00122\u0016\u0015\u0014\u0006\"&54\u0001�&(\"\u001b/&\u0006./\u0005�80P\u0003\u0018\u000f\u0001�\u0005\u0005�\u0001\u0010\u0017\u0003P,<�\u0005-0\u0006&(\"\u001b/&\u00052,\u0005�;.P\u0003\u0019\r\u0002�\u0006\u0006�\u0002\u000e\u0018\u0003P-<�\u0005-1\u001c�~~�~��\u0005\u0005�\u0002\r\u001a\u0003Q,=�\u0005,1\u0006&(\"\u001c-&\u00063*\u0005�:/Q\u0003\u0019\u000e\u0001�\u0005\u0005�\u0001\u000e\u0019\u0003Q/:�\u0005/.\u0006&0\u0019!)&\u00061,\u0005�;.Q\u0003\u001a\r\u0002v~XY~~YX\u0000\u0000\u0007\u0000d��\u0004�\u0005\u0014\u0000\u0019\u0000\u001d\u0000'\u0000+\u0000/\u00003\u00007\u0000\u0000\u0001!2\u0016\u001d\u0001\u0014\u0006#!\"&=\u0001463!5463!2\u0016\u0015\u00075!\u0015\u0005!\u0011\u0014\u0006#!\"&5;\u0001\u0011#\u00133\u0011#\u00133\u0011#\u00133\u0011#\u0003�\u0001\u0013\n\u000f\u000e\u000b��\u000b\u000e\u000f\n\u0001\u0013;)\u0001,);d����\u0003�;)�D);ddd�dd�dd�dd\u0004L\u000f\n2\u000b\u000e\u000e\u000b2\n\u000fd);;)ddd���)<<)\u0002��D\u0002��D\u0002��D\u0002�\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0001\u0005\u0015\u0004�\u0000\n\u0000\u0000\u0001#\u0011!\u0011!\u0011!\u0011#\u0001\u0005\u0015��������\u0002�\u0002Y��\u0001��p\u0002X\u0002�\u0000\u0000\u0000\u0000\u0002\u0000d\u0000\u0000\u0003�\u0004�\u0000\u000e\u0000\u0011\u0000\u0000\u0001!\u0011\u0014\u0006#!\"&5\u0011463!\u0001!\u0011\u0002X\u0001�\u000e\u000b��\u000b\u000e\u000e\u000b\u0001�\u0001���\u0002��]\u000b\u000e\u000e\u000b\u0004~\u000b\u000e�p\u0001,\u0000\u0003\u0000\u0004\u0000\u0004\u0004�\u0004�\u0000\u000b\u0000\u0013\u0000\u0019\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0004 \u0006\u0010\u0016 6\u0010\u00053\u0015!\u00113\u0001�\u0001D\u0001\u0012�������\u0002_����\u0001V��b���d\u0004�������\u0001\u0012\u0001D\u0001\u0012\u0016�����\u0001V�d\u0001�\u0000\u0002��\u0000\u0000\u0005\u0014\u0004�\u0000\u000b\u0000\u000f\u0000\u0000\u0001#\u0003!\u00013\u00033\u00033\u0001!\u000b\u0001#\u0003\u0002��(��\u0001��\u0015�\u0014�\u0001���2\u001b�\u001b\u0001��p\u0004���\u0001,�P\u0001�\u0001,��\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004L\u0004�\u0000\u000b\u0000\u000f\u0000\u0000\u0001\u0011!\u0011!\u00013\u0011!\u00113\u0001\u0005#\u00153\u0004L��\u0001����\u0001,���\u0001z��\u0001��p\u0001�\u0001,\u0001��\f��dd\u0000\u0000\u0000\u0003\u0000\u0001\u0000\u0001\u0004�\u0004�\u0000\u000f\u0000\u0017\u0000\u001e\u0000\u0000\u00002\u001e\u0002\u0014\u000e\u0002\".\u00024>\u0001\u0004 \u0006\u0010\u0016 6\u0010\u00053\u000b\u00013\u00113\u0001��ޠ__���ޠ__�\u0002\u0002����\u0001T��Ȗ����\u0004�_���ޠ__���ޠ\\�����\u0001T���\u0001,\u0001,\u0000\u0000\u0003\u0000\u0004\u0000\u0004\u0004�\u0004�\u0000\u000b\u0000\u0013\u0000\u001a\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0004 \u0006\u0010\u0016 6\u0010\u0007#\u0011#\u0011#\u0013\u0001�\u0001D\u0001\u0012�������\u0002_����\u0001V���Ȗ�\u0004�������\u0001\u0012\u0001D\u0001\u0012\u0016�����\u0001V���\u0001,\u0001,\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\f\u0000\u0014\u0000\u0000\u0011\u0013!\u00123\u0011\u0014\u0006#!\"&5\u0001!\u00033\u0017!73�\u0003 �\u0001\u000e\u000b��\u000b\u000e\u0003���a�2\u0001,2�\u0001�\u0002��D�%\u000b\u000e\u000e\u000b\u0003��\f��\u0000\u0000\u0000\u0000\u0003\u0000\u0004\u0000\u0004\u0004�\u0004�\u0000\u000b\u0000\u0015\u0000\u0018\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0004 \u0006\u0015\u0014\u0016 654\u0007\u0005\u0011\u0001�\u0001D\u0001\u0012�������\u0002_����\u0001V����\u0004�������\u0001\u0012\u0001D\u0001\u0012\u0016����ò¬«­ï¿½\u0001�\u0000\u0001\u0000\u0017\u0000\u0017\u0004�\u0004�\u0000\u001c\u0000\u0000\u0001#\u0014\u0006 &\u0010632\u0017\u0007!\u0011\u0007&#\"\u000e\u0002\u0014\u001e\u00022>\u0002\u0004���������n�\u0001����v՛[[���՛[\u0002X���\u0001b�Q�\u0001��z[���՛[[��\u0000\u0002\u0000\u0017\u0000\u0000\u0004�\u0004�\u0000\u0010\u0000!\u0000\u0000\u00017&#\"\u0006\u0015#4>\u000232\u00177\u0011\u0001\u0007\u001632653\u0014\u000e\u0002#\"'\u0007\u0011\u0002��p����[��vƝ����p����[��vƝ�\u0003 �P��v՛[z��p�p�P��v՛[z�\u0001�\u0000\u0000\u0000\n\u0000d\u0000\u0000\u0004�\u0004�\u0000\u0003\u0000\u0007\u0000\u000b\u0000\u000f\u0000\u0013\u0000\u0017\u0000\u001b\u0000\u001f\u0000#\u0000'\u0000\u0000\u0013\u0011!\u0011\u0003!\u0011!\u0005#53\u0005!5!\u0001#53\u0005!5!\u0001#53\u0005!5!\u0001#53)\u0001\u0015!d\u0004Ld�|\u0003��Ddd\u0002X�\f\u0001���dd\u0002X�\f\u0001���dd\u0002X�\f\u0001���dd\u0002X�\f\u0001�\u0004��P\u0004���\u0003��ddd��ddd��ddd��dd\u0000\u0002\u0000d\u0000\u0000\u0004L\u0004L\u0000\u0003\u0000\u0015\u0000\u00003#\u00113\u0001\u0011\u000e\u0001.\u0003\u0006\u0007\u0011>\u0001\u001e\u0002>\u0001�dd\u0003�({���tZ\u0014<�x|rjd\u0004L�\f\u0001�QE\n((\nEQ�\f<0\r!\u001b\u0005O\u0000\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000!\u00001\u0000A\u0000\u0000\u0001\u00114.\u0002\"\u000e\u0002\u0015\u0011\u0014\u0016;\u0001265\u00114>\u0001 \u001e\u0001\u0015\u0011\u0014\u0016;\u000126%\u0011\u0014\u0006+\u0001\"&5\u001146;\u00012\u0016\u0005\u0011\u0014\u0006+\u0001\"&5\u001146;\u00012\u0016\u0004�c���ޣc\u000e\u000b2\u000b\u000e��\u0001\u0006��\u000e\u000b2\u000b\u000e��\f\b�\b\f\f\b�\b\f\u0002X\f\b�\b\f\f\b�\b\f\u0001\u0013\u0001,tÞ£cc��t��\u000b\u000e\u000e\u000b\u0001,�rr���\u000b\u000e\u000e��4\b\f\f\b\u0001�\b\f\f\b�4\b\f\f\b\u0001�\b\f\f\u0000\u0000\u0000\u0002\u0000\u0000\u0000�\u0004X\u0003�\u0000\u0005\u0000\u0011\u0000\u0000\u0019\u0001!\u0005\u0011\r\u0001\u0017\u0007'\u0007'7'7\u00177\u0017\u0001,\u0001,��\u0002��G��G��G��G\u0003 �p�\u0003 �ȍG��G��G��G\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000�\u0003p\u0003�\u0000\u0005\u0000\u000f\u0000\u0000\u0019\u0001!\u0005\u0011\u0005%7\u0016\u0015\u0014\u0007'654\u0001,\u0001,��\u0001�EojCV\u0003 �p�\u0003 �95����6n��\u0000\u0000\u0003\u0000\u0000\u0000�\u0004b\u0003�\u0000\r\u0000\u0013\u0000\u001d\u0000\u0000%7654/\u0001\u0007\u0017\u0016\u0015\u0014\u000f\u0001\u0001%\u0011%!\u0011%7\u0016\u0015\u0014\u0007'654\u0003�\u0006��\u0006S\u0007{w\u0007��\u0001,����\u0002�EojCV�\b����\b@\b����\b\u0002%����\u0001�95����7n��\u0000\u0000\u0000\r\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\t\u0000\u0015\u0000\u0019\u0000\u001d\u0000!\u0000%\u0000-\u0000;\u0000?\u0000C\u0000G\u0000K\u0000O\u0000\u0000\u0013!535#\u0011!\u00113%\u0011!\u00153\u00113\u0011!5#5\u0001!\u0011)\u0001\u0011!\u0011%35#!3\u0015#\u0001#\u0015#\u0011!\u0011#\u0001\u0015#5#535#5!\u00113\u0015%\u0011!\u0011\u0017#53!3\u0015#\u00053\u0015#%\u0015!5d\u0001�dd�\fd\u0004L��d�\u0001,��|\u0001,��\u0002�\u0001,�|dd\u0002�dd��d�\u0001��\u0002X�dd�\u0001,������dd\u0003 dd�\fdd\u0002X��\u0002�dd\u0001,��d\u0001�������d\u0001���\u0001,��ddd��d�\f\u0001���ddd�d��d���\u0001,�ddddddd\u0000\u0000\t\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u0003\u0000\u0007\u0000\u000b\u0000\u000f\u0000\u0013\u0000\u0017\u0000\u001b\u0000\u001f\u0000#\u0000\u0000\u0013#\u0011;\u0001#\u00113\u0001#\u00113\u0013#\u00113!\u0011#\u0011\u0001\u0015!5\u00053\u0015#73\u0015#%\u0015#5ddd�dd\u0001����dd\u0001,�����\u0001�dd�dd\u0001��\u0004��\u0018\u0003��\u0018\u0003��\u0018\u0003��\u0018\u0003���dd\t[[[[[[\u0000\u0000\u0000\u0000\u0002\u0000\u0001\u0000\u0000\u0004�\u0004�\u0000\u0007\u0000\u0013\u0000\u0000\t\u0002\u0011463!\u000364'&\"\u0007\u0006\u0014\u0017\u00162\u0004��\f�E\u000f\n\u0001Ú´\u001d\u001d\u001eS\u001e\u001d\u001d\u001eS\u0001��\f\u0002�\u0001�\n\u000f��\u001dT\u001d\u001e\u001e\u001dT\u001d\u001e\u0000\u0003\u0000\u0002\u0000\u0000\u0005�\u0004�\u0000\u0007\u0000\r\u0000\u0019\u0000\u0000\t\u0002\u0011463!\t\u0001'\t\u00013\u000164'&\"\u0007\u0006\u0014\u0017\u00162\u0004��\f�E\u000e\u000b\u0001�\u0003��\f2\u0001��Dd�\u001f\u001e\u001e\u001dT\u001d\u001e\u001e\u001dT\u0001��\f\u0002�\u0001�\u000b\u000e�D�\f2\u0001�\u0002���\u001dT\u001d\u001e\u001e\u001dT\u001d\u001e\u0000\u0001\u0000d\u0000\u0000\u0004�\u0004�\u0000\n\u0000\u0000\u0001\u0011\u0007\u0011!\u0007!\u0011!\u00117\u0004�d�\u0012d\u0002��|�\u0004��\u0018d\u0003�d�\u0018\u0004\u0001�\u0000\u0000\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u000b\u0000\u0017\u0000'\u0000\u0000\u0001\u0003.\u0001#!\"\u0006\u0007\u0003\u0017!%\u0011#5!\u0015#\u00113\u0017!7\u0003\u0017\u0016\u0006#!\"&?\u0001>\u00013!2\u0016\u0003�^\u0002\u0010\n�>\n\u0010\u0002^(\u0002P\u0001;�����d\u0002Xdw&\u0002\u000b\n��\n\u000b\u0002&\u0002\u0013\n\u0001�\n\u0013\u0003=\u0001Z\u000b\u000e\u000e\u000b��|_�D��\u0002������\n\u000e\u000e\n�\n\u000e\u000e\u0000\u0000\u0000\u0000\u0002\u00005\u0000\u0000\u0004�\u0004�\u0000\u001e\u0000\"\u0000\u0000!5&'.\u0001/\u0001\u0001#\u0001\u0006\u0007\u000e\u0001\u000f\u0001\u0015!5\".\u0001?\u0001!\u0017\u0016\u0006#\u0015\u0001\u0013\u0017\u0013\u0004�\"(\u0012\u001e\u0006\u0006�]�q\u0018\u001c\f*\u000f\u000f\u0001m)>$\u0013\\\u0001�R\u0010+5���.tB\u0001*\u0013.\u000e\r\u0003��\u00120\u001b\f\u001a\u0007\u0007BB\u00166,��-WB\u0002\t\u0001Ɍ��\u0000\u0000\u0000\u0003\u0000d\u0000\u0000\u0003�\u0004�\u0000 \u0000(\u00001\u0000\u0000\u0013!2\u0016\u0015\u0014\u000e\u0002\u000f\u0001\u001e\u0004\u0015\u0014\u000e\u0001#!5>\u00015\u00114.\u0003'\u0005\u001132654&\u000332654&+\u0001d\u0001�x�\u0017!\"\u000b\f\b\u001bE4+v�O�\f);\u0002\t\u0016$\u001f\u0001,�Ll���Y�}^�\u0004���7]7(\b\u0007\u0003\f3AvFT�MY\u00073(\u0003;\u001c\u0017\u001d\r\u000f\u00072��{MRa��aTZ�\u0000\u0000\u0000\u0001\u0000�\u0000\u0000\u0003o\u0004�\u0000\u0019\u0000\u0000\u0001\u0017\u000e\u0003\u000f\u0001\u0003\u0006\u0016\u0017\u0015!5>\u00017\u00136&'.\u0001'5\u0003m\u0002!:\"\u0019\u0005\u0005�\n0G�\fMs\b�\n(G\u0006\t\u0005\u0004�9\b#'%\f\f��4<\u000699\u0007C/\u0003Q8$\u0013\u0001\u0003\u00019\u0000\u0000\u0002��\u0000\u0000\u0005\u0014\u0004�\u0000\u001b\u0000%\u0000\u0000\u00013\u0011#4.\u0003+\u0001\u0011\u0017\u0015!57\u0011#\"\u000e\u0003\u0015#\u00113\u0005\u00113\u0007'3\u0011#7\u0017\u0004~�2\u0010\u0015.!\"�d�pd�\"!/\u0014\u00111���K}}KK}}\u0004���\u001d'\u0015\t\u0002��2dd2\u0003R\u0002\t\u0015'\u001d\u0001,��১\u0003 ��\u0000\u0000\u0000\u0002\u0000!��\u0004�\u0004�\u0000\u001b\u0000%\u0000\u0000\u00013\u0011#4.\u0003+\u0001\u0011\u0017\u0015!57\u0011#\"\u000e\u0003\u0015#\u00113\u0003!5\u0017\u00075!\u0015'7\u0003��2\u0010\u0014/!\"�d�pd�\"!.\u0015\u00102�2\u0003 ���১\u0004���\u001d'\u0015\t\u0002�v2dd2\u0002�\u0002\t\u0015'\u001d\u0001,��K}}KK}}\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u001f\u0000/\u0000?\u0000\u0000\u000154&#!\"\u0006\u001d\u0001\u0014\u00163!26\u000154&#!\"\u0006\u001d\u0001\u0014\u00163!26\u000354&#!\"\u0006\u001d\u0001\u0014\u00163!26\u000154&#!\"\u0006\u001d\u0001\u0014\u00163!26\u0002�\u001d\u0015��\u0015\u001d\u001d\u0015\u0002X\u0015\u001d\u0001�\u001d\u0015�\u0018\u0015\u001d\u001d\u0015\u0003�\u0015\u001d�\u001d\u0015��\u0015\u001d\u001d\u0015\u0003 \u0015\u001d\u0001,\u001d\u0015��\u0015\u001d\u001d\u0015\u0004L\u0015\u001d\u0003�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0000\u0000\u0004\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u001f\u0000/\u0000?\u0000\u0000\u000154&#!\"\u0006\u001d\u0001\u0014\u00163!26\u001354&#!\"\u0006\u001d\u0001\u0014\u00163!26\u000354&#!\"\u0006\u001d\u0001\u0014\u00163!26\u001354&#!\"\u0006\u001d\u0001\u0014\u00163!26\u0003�\u001d\u0015�D\u0015\u001d\u001d\u0015\u0002�\u0015\u001d�\u001d\u0015��\u0015\u001d\u001d\u0015\u0004L\u0015\u001d�\u001d\u0015�D\u0015\u001d\u001d\u0015\u0002�\u0015\u001d�\u001d\u0015��\u0015\u001d\u001d\u0015\u0004L\u0015\u001d\u0003�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0000\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u001f\u0000/\u0000?\u0000\u0000\u00015463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u00015463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u00135463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u00015463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u0001�\u001d\u0015\u0002X\u0015\u001d\u001d\u0015��\u0015\u001d�p\u001d\u0015\u0003�\u0015\u001d\u001d\u0015�\u0018\u0015\u001d�\u001d\u0015\u0003 \u0015\u001d\u001d\u0015��\u0015\u001d��\u001d\u0015\u0004L\u0015\u001d\u001d\u0015��\u0015\u001d\u0003�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0000\u0000\u0004\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u001f\u0000/\u0000?\u0000\u0000\u00115463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u00115463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u00115463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u00115463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u001d\u0015\u0004L\u0015\u001d\u001d\u0015��\u0015\u001d\u001d\u0015\u0004L\u0015\u001d\u001d\u0015��\u0015\u001d\u001d\u0015\u0004L\u0015\u001d\u001d\u0015��\u0015\u001d\u001d\u0015\u0004L\u0015\u001d\u001d\u0015��\u0015\u001d\u0003�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0000\b\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u001f\u0000/\u0000?\u0000O\u0000_\u0000o\u0000\u0000\u0000\u0011546;\u00012\u0016\u001d\u0001\u0014\u0006+\u0001\"&%5463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u0001546;\u00012\u0016\u001d\u0001\u0014\u0006+\u0001\"&%5463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u0001546;\u00012\u0016\u001d\u0001\u0014\u0006+\u0001\"&%5463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u0001546;\u00012\u0016\u001d\u0001\u0014\u0006+\u0001\"&%5463!2\u0016\u001d\u0001\u0014\u0006#!\"&\u001d\u0015d\u0015\u001d\u001d\u0015d\u0015\u001d\u0001,\u001d\u0015\u0003 \u0015\u001d\u001d\u0015��\u0015\u001d��\u001d\u0015d\u0015\u001d\u001d\u0015d\u0015\u001d\u0001,\u001d\u0015\u0003 \u0015\u001d\u001d\u0015��\u0015\u001d��\u001d\u0015d\u0015\u001d\u001d\u0015d\u0015\u001d\u0001,\u001d\u0015\u0003 \u0015\u001d\u001d\u0015��\u0015\u001d��\u001d\u0015d\u0015\u001d\u001d\u0015d\u0015\u001d\u0001,\u001d\u0015\u0003 \u0015\u001d\u001d\u0015��\u0015\u001d\u0003�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0014d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0014d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0014d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0014d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0000\u0000\u0000\u0006��\u0000\u0000\u0004�\u0004L\u0000\u0003\u0000\u0013\u0000#\u0000*\u0000:\u0000J\u0000\u0000\u0001#\u00113\u000154&+\u0001\"\u0006\u001d\u0001\u0014\u0016;\u000126\u000154&#!\"\u0006\u001d\u0001\u0014\u00163!26\u00055#535\u0017\u000554&#!\"\u0006\u001d\u0001\u0014\u00163!26\u000154&#!\"\u0006\u001d\u0001\u0014\u00163!26\u0001�dd\u0001,\u001e\u0014d\u0015\u001d\u001d\u0015d\u0014\u001e\u0001�\u001e\u0014�\f\u0015\u001d\u001d\u0015\u0001�\u0014\u001e�\u0018�ɦ\u0002z\u001e\u0014��\u0015\u001d\u001d\u0015\u0001,\u0014\u001e\u0001,\u001e\u0014��\u0015\u001d\u001d\u0015\u0002X\u0014\u001e\u0004L��\u0003�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e�KdK}�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0000\u0000\u0000\u0006\u0000\u0001\u0000\u0000\u0005\u0015\u0004L\u0000\u000f\u0000\u0013\u0000#\u0000*\u0000:\u0000J\u0000\u0000\u001354&+\u0001\"\u0006\u001d\u0001\u0014\u0016;\u000126%3\u0011#\u000354&#!\"\u0006\u001d\u0001\u0014\u00163!26\u0005535#5\u0007\u000554&#!\"\u0006\u001d\u0001\u0014\u00163!26\u000154&#!\"\u0006\u001d\u0001\u0014\u00163!26�\u001e\u0014d\u0015\u001d\u001d\u0015d\u0014\u001e\u0002Xdd�\u001e\u0014�\f\u0015\u001d\u001d\u0015\u0001�\u0014\u001e\u0001��ȧ��\u001e\u0014��\u0015\u001d\u001d\u0015\u0001,\u0014\u001e\u0001,\u001e\u0014��\u0015\u001d\u001d\u0015\u0002X\u0014\u001e\u0003�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e���\u0002�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e�KdK}�d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e��d\u0015\u001d\u001d\u0015d\u0014\u001e\u001e\u0000\u0000\u0002\u0000\u0000\u0000�\u0004�\u0003�\u0000\u000f\u0000\u0012\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\t\u0002\u0003�,\u001f�\u0012\u001f,,\u001f\u0002�\u001f,\u0001,��\u0001,\u0003��v\u001f,,\u001f\u0002�\u001f,,�p\u0001,\u0001,\u0000\u0000\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u0017\u0000\u001f\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0007!\u00117\u0005'\u0001\u0013\u0000\u0014\u0006\"&462\u0004�\u0019\u0013��\u0012\u001a\u001a\u0012\u0004X\u0013\u0019d�\u0018�\u0001*J\u0001%���NpNNp\u0004 �\f\u0012\u001a\u001a\u0012\u0003�\u0012\u001a\u001aJ�\u001f���\u0001>��\u00012pNNpN\u0000\u0000\u0002\u0000���\u0004\u001c\u0004�\u0000\u0014\u0000\u001e\u0000\u0000\u00012\u001e\u0001\u0014\u0007\u000e\u0001\u000f\u0001.\u0004'&54>\u0001\u0013264&\"\u0006\u0015\u0014\u0016\u0002X{�yII�99\n\"c]s+?y�yk��֖�\u0004�~���r�BB\t\"ko�K�{|ׁ�E�֖�jk�\u0000\u0002\u0000\u0001\u0000\u0001\u0004�\u0004�\u0000\u000f\u0000\u0015\u0000\u0000\u00002\u001e\u0002\u0014\u000e\u0002\".\u00024>\u0001\u0001\u0011\"\u0006\u0010\u0016\u0001��ޠ__���ޠ__�\u0001X���\u0004�_���ޠ__���ޠ�]\u0003V����\u0000\u0000\u0000\u0002\u0000u\u0000\u0004\u0003�\u0005\u000f\u0000\u0016\u0000%\u0000\u0000\u0001\u001e\u0006\u0015\u0014\u000e\u0002\u0007.\u000254>\u0003\u0003.\u0002'&6?\u0001\u000e\u0001\u0017\u001e\u0001\u0017\u0002*\u0015IOWM?%N~�OrÀDmssE\u0007\u0016.\n\u000f\u0002\t\t\\7\u000f\u000e[[\u0005\u000fG�vwsu�EY�d;\u0004\u0006^�w^�����y\u0006\u0016J(I�43n�QRl\u001a\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004�\u0004h\u0000\u0004\u0000!\u0000&\u0000\u0000\u00017/\u0001\u0007\u0013\u0015\u0014\u0006#!\"&5\u0011463\u0004\u0017\u0007!\"\u0006\u0015\u0011\u0014\u00163!26=\u0001\u00057\u0001'\u0001\u0004Tq\u0015\\qi���ԥ���\u0001n\u001f���);;)\u0001�);�0�\u0001�r�k\u0003�qU\u001cq�z�����\u0001,��\u0006\b�;)�\f);;)}T2\u0001�q�k\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u001c\u0000.\u0000\u0000\u0001\u0015\u0014\u0006#!\"&5\u0011463!\u0017\u0006\u0007#\"\u0006\u0015\u0011\u0014\u00163!265'\t\u0001\u0015\"\u000e\u0005\u0007>\u0003\u001f\u0001\u0004L���ԥ���\u0001\u0005\u0002�U�);;)\u0001�);W\u0001h��\u0007\u0018HCVC9\u000b\u001egg_\u001e\u001d\u0001�5����\u0001,��P X;)�\f);;)�\u0001D\u0001>�\u0002\u000e\u00173CmC&4\u0013\t\u0001\u0001\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u001d\u0000#\u0000\u0000\u0001\u0015\u0014\u0006#!\"&5\u0011463!2\u0017\u0007!\"\u0006\u0015\u0011\u0014\u00163!26=\u0001\u0005\u0001'\u0001'\u0007\u0004L���ԥ���\u0001,<C���);;)\u0001�);��\u00026��R��\u00029�����\u0001,��\u0017�;)�\f);;)E\u0015\u00027��Q��\u0000\u0001\u0000\u0000\u0000\u0001\u0004�\u0004�\u0000\u0017\u0000\u0000\u00015#\u00153\t\u000135#\u0015\t\u0001\u001535#\t\u0001#\u001535\u0001\u0003�����������\u0001,��\u0001,\u0001'��\u0001,\u0001/����\u0001,��\u0001,\u0001(��\u0001,������\u0000\u0000\u0000\u0001\u0000�\u0000\u0000\u0003�\u0004L\u0000\u0013\u0000\u0000!\u0001\u0011\u0014\u0006+\u0001\"&5\u001146;\u00012\u0016\u0015\u0011\u0001\u0003��\f\u001d\u0015d\u0015\u001d\u001d\u0015d\u0015\u001d\u0001�\u0001��J\u0015\u001d\u001d\u0015\u0003�\u0015\u001d\u001d\u0015�K\u0001�\u0000\u0001\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u0017\u0000\u0000!\u0001\u0011\u0001\u0011\u0014\u0006+\u0001\"&5\u001146;\u00012\u0016\u0015\u0011\u0001\u0011\u0001\u0004��\f�\f\u001d\u0015d\u0015\u001d\u001d\u0015d\u0015\u001d\u0001�\u0001�\u0001��\u0018\u0001��J\u0015\u001d\u001d\u0015\u0003�\u0015\u001d\u001d\u0015�K\u0001��\u0019\u0001�\u0000\u0001\u0000�\u0000\u0000\u0004�\u0004L\u0000\u0006\u0000\u0000!\u0001\u0011\t\u0001\u0011\u0001\u0004��\f��\u00024\u0001�\u0001��\u0018\u0002&\u0002&�\u0019\u0001�\u0000\u0000\u0001\u0000�\u0000\u0000\u0004L\u0004L\u0000\u0002\u0000\u0000\t\u0001\u0011\u0004L�|\u0002&\u0002&��\u0000\u0000\u0002\u0000�\u0000d\u0003�\u0003�\u0000\u000f\u0000\u001f\u0000\u0000%\u00114&+\u0001\"\u0006\u0015\u0011\u0014\u0016;\u000126%\u00114&+\u0001\"\u0006\u0015\u0011\u0014\u0016;\u000126\u0001�\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d\u0001�\u001d\u0015�\u0015\u001d\u001d\u0015�\u0015\u001d�\u0003 \u0015\u001d\u001d\u0015��\u0015\u001d\u001d\u0015\u0003 \u0015\u001d\u001d\u0015��\u0015\u001d\u001d\u0000\u0001\u0000�\u0000d\u0004L\u0003�\u0000\u000f\u0000\u0000%\u00114&#!\"\u0006\u0015\u0011\u0014\u00163!26\u0004L\u001d\u0015��\u0015\u001d\u001d\u0015\u0003 \u0015\u001d�\u0003 \u0015\u001d\u001d\u0015��\u0014\u001e\u001e\u0000\u0001\u0000\u0000\u0000\u0000\u0004(\u0004L\u0000\u0006\u0000\u0000!\u0011\u0001\u0011\u0001\u0011\u0001\u0001��\f\u0001�\u00024\u0001��\u0018\u0004L�\u0019\u0001���\u0000\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u0017\u0000\u0000\u000132\u0016\u0015\u0011\u0014\u0006+\u0001\"&5\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u001146\u0004\u001ad\u0015\u001d\u001d\u0015d\u0015\u001d�\f�\f\u0001�\u0001�\u001d\u0004L\u001d\u0015�\u0018\u0015\u001d\u001d\u0015\u0001��\u0018\u0001��\u0018\u0004L�\u0019\u0001��\u0019\u0001�\u0015\u001d\u0000\u0000\u0001\u0001,\u0000\u0000\u0003�\u0004L\u0000\u0013\u0000\u0000\u000132\u0016\u0015\u0011\u0014\u0006+\u0001\"&5\u0011\u0001\u0011\u0001\u001146\u0003Rd\u0015\u001d\u001d\u0015d\u0015\u001d�\f\u0001�\u001d\u0004L\u001d\u0015�\u0018\u0015\u001d\u001d\u0015\u0001��\u0018\u0004L�\u0019\u0001�\u0015\u001d\u0000\u0000\u0002\u0000d\u0000�\u0004�\u0004(\u0000\u0002\u0000\u0012\u0000\u0000\t\u0001!\u001d\u0001\u0014\u0006#!\"&=\u0001463!2\u0016\u0002���\u0004L\u001d\u0015�\u0018\u0015\u001d\u001d\u0015\u0003�\u0015\u001d\u0004(�̖d\u0015\u001d\u001d\u0015d\u0015\u001d\u001d\u0000\u0000\u0001\u0000�\u0000\u0007\u0003�\u0004�\u0000\u0005\u0000\u0000%7\t\u0001'\u0001\u0003\t���\u0001a���\u0007�\u0001a\u0001a���\u0000\u0000\u0001\u0001\u0010��\u0004R\u0004t\u0000\b\u0000\u0000\u0005'\t\u00017\u0001\u0017\u0007\u0015\u0002\u0001�\u0001a���\u0002<\u0015\u0001.�\u0001a\u0001a���\u0016\u0001\u0001\u0000\u0000\u0002\u0000\u0003\u0000\u0003\u0004�\u0004�\u0000\u000b\u0000\u0017\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0005\u0015#\u00153\u00153535#5\u0001�\u0001D\u0001\u0013ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001Q�����\u0004ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001\u0013\u0001D\u0001\u0013�������\u0000\u0000\u0002\u0000\u0003\u0000\u0003\u0004�\u0004�\u0000\u000b\u0000\u000f\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0013\u0015!5\u0001�\u0001D\u0001\u0013ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  ï¿½\u0002X\u0004ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001\u0013\u0001D\u0001\u0013����\u0000\u0002\u0000\u0003\u0000\u0003\u0004�\u0004�\u0000\u000b\u0000\u0017\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0013\u0017\u0007\u00177\u00177'7'\u0007'\u0001�\u0001D\u0001\u0013ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  T��Ս�Վ�Ս�\u0004ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001\u0013\u0001D\u0001\u0013�؍�Վ�Ս�ԍ�\u0000\u0000\u0000\u0002\u0000\u0003\u0000\u0003\u0004�\u0004�\u0000\u000b\u0000\u0011\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0001'\u0007\t\u0001'\u0001�\u0001D\u0001\u0013ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001f�\u0001\u0014\u0001��\u0004ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001\u0013\u0001D\u0001\u0013�bf���\u0001��\u0000\u0000\u0000\u0000\u0003\u0000\u0003\u0000\u0003\u0004�\u0004�\u0000\u000b\u00006\u0000:\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u00013>\u000454.\u0003#\"\u0006\u001532\u0016264>\u0005:\u000132\u0016\u0015\u0014\u0006\u0007\u000e\u0004\u0017\u001535\u0001�\u0001D\u0001\u0013ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001Q�\u0005\u0012-\"\u001c#1D1\u001bi��\u0004\u000f\u0007\u0006\u0002\u0005\u0002\t\u0004\u000e\u0004\u0013\u0003\u0013\u0016\b\u0017\u0005\u000f'\u001d\u0018\u0001�\u0004ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001\u0013\u0001D\u0001\u0013��\u0003\n)2X23L(\u0018\u0006p\u0002\u0006\f\n\u0007\u0005\u0003\u0002\u0001\u0014\u0010\u0016\f\u0010\u0001\u0004\u0017\u001f=�dd\u0000\u0000\u0003\u0000\u0003\u0000\u0003\u0004�\u0004�\u0000\u000b\u0000\u000f\u0000\u0019\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0005\u001535\u0005\u00153\u0015#\u0015!5#\u0011\u0001�\u0001D\u0001\u0013ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001Q���dd\u0001�d\u0004ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001\u0013\u0001D\u0001\u0013�dd�d�dd\u0001,\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u001a\u00001\u0000\u0000\u0001\u0015#\u000e\u0001\u0007\u0015#5.\u0001'#53>\u0003753\u0015\u001e\u0002\u0017\u0005\u001567#53.\u0001'\u0015#5\u000e\u0001\u00073\u0015#\u001e\u0001\u00175\u0004��\u0019�YȌ�\u001e��\u000f*EkI�6vk\u0012�ו4��\u0019fI�Kn\u0018��\u0018oK\u0002��f�!��\u001b�}�<YS7\r��\u0014P�E��0��Jk\u0017��\u0018kH�Im\u0018�\u0000\u0003\u0000\u0004\u0000\u0004\u0004�\u0004�\u0000\u000b\u0000\u0013\u0000\u001f\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0004 \u0006\u0010\u0016 6\u0010\u000f\u0001\u0017\u0007'\u0007'7'7\u00177\u0001�\u0001D\u0001\u0012�������\u0002_����\u0001V󪇇m��m��m��\u0004�������\u0001\u0012\u0001D\u0001\u0012\u0016�����\u0001V$��m��m��m��\u0000\u0003\u0000\u0004\u0000\u0004\u0004�\u0004�\u0000\u000b\u0000\u0013\u0000\u0019\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0004 \u0006\u0010\u0016 6\u0010\u0007\u0001'7\u00177\u0001�\u0001D\u0001\u0012�������\u0002_����\u0001V�v����W�\u0004�������\u0001\u0012\u0001D\u0001\u0012\u0016�����\u0001Vu����W�\u0000\u0003\u0000\u0004\u0000\u0004\u0004�\u0004�\u0000\u000b\u0000\u0013\u0000\u001b\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0013\u0001&#\"\u0006\u0015\u0014\t\u0001\u001632654\u0001�\u0001D\u0001\u0012�������T\u00028dt��\u0003\u0001��ap��\u0004�������\u0001\u0012\u0001D\u0001\u0012�u\u00027>��s\u0001D��;��p\u0000\u0000\u0000\u0001\u0000\u0000\u0000c\u0004�\u0003�\u0000\u0006\u0000\u0000\u0001!\u0011\t\u0001\u0011!\u0004�����\u0002X\u0002X\u0001���\u0001�\u0001���\u0000\u0000\u0000\u0000\u0001\u0000\u0000\u0000c\u0004�\u0003�\u0000\u0006\u0000\u0000\u0001!\u0011!\u0011\t\u0001\u0002X��\u0002X\u0002X��\u0001�\u0001,\u0001,�;�@\u0000\u0000\u0000\u0000\u0001\u0000�\u0000\u0000\u0004J\u0004�\u0000\u0006\u0000\u0000\u0001!\u0011!\u0011!\u0001\u0004J������\u0001�\u0002X��\u0002X\u0002X\u0000\u0000\u0001\u0000h\u0000\u0000\u0003�\u0004�\u0000\u0006\u0000\u0000\t\u0002!\u0011!\u0011\u0003��?�C\u0001(\u0001,\u0002X��\u0002X\u0002X��\u0000\u0001\u0000\u0000\u0000�\u0004�\u0004L\u0000\r\u0000\u0000%\u0011\u000e\u0003\u0007>\u00037\u0011\u0001\u0002X_���#\u0006F���\u0002X�\u0001-\u0002$DuM�խg\b\u0001\u000f�;\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u0006\u0000\r\u0000\u0000\u0001!\u0017\u0001\u0017\u0001\u0017\t\u0001\u0017!\u0011\u0017\u0001\u0004��p��ڎ\u0001&����ځ�p�\u0001&\u0004���ڎ\u0001&����ځ\u0001��\u0001&\u0000\u0000\u0000\u0002\u0000\"\u0000#\u0004�\u0004�\u0000\u0006\u0000\r\u0000\u0000\u0001\u0017!\u0011\u0017\u0001\u0017\u0001!\u0011'\u0001'\u0001\u0003g��p�\u0001'��:\u0001���َ\u0001'\u0002ڂ\u0001��\u0001'��W�p��ٍ\u0001'\u0000\u0003\u0000\u0017\u0000\u0017\u0004�\u0004�\u0000\u000f\u0000\u001f\u0000#\u0000\u0000\u00002\u001e\u0002\u0014\u000e\u0002\".\u00024>\u0001\u0001\u00136&+\u0001\"\u0006\u0017\u0013\u001e\u0001;\u000126\u0017#\u00153\u0001��՛[[���՛[[�\u0001�:\u0004\u0018\u0015�\u0014\u0018\u0004:\u0004#\u00146\u0014#\u0012��\u0004�[���՛[[���՛��\u0001.\u0014\u001d\u001d\u0014��\u0014\u001d\u001d�d\u0000\u0005\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000&\u0000-\u00003\u00007\u0000;\u0000\u0000\u0001#<\u0001&/\u0001.\u0001#\"\u000f\u0001\u0006\u0007&/\u0001&#\"\u0006\u000f\u0001\u000e\u0001\u0014\u0015#\u00153\u0015!\u00113\u0011!53%7\u001e\u0003#)\u0001\"6?\u0001\u0003!\u0011)\u0002\u0011!\u0004�o\u0002\u0002\"\u000b=' \u001d�\u0016\u0012\u0013\u0015�!\u001d'=\n#\u0002\u0002od\u0001��\u0001�d�+�\u0005\u000e \u0012\u0002����\u0003\"\u0012\u0012�\u0001��p\u0002X\u0001��p\u0003�\u0001\n\u0014\b�'0\u0011�\r\u0016\u0018\f�\u0012.&�\b\u0014\n\u0001d�\u0001,���d�\f)W9`0/��\u0001��p\u0000\u0000\u0000\u0000\u0002\u0000\u0000��\u0004�\u0004�\u0000\u001b\u00002\u0000\u000057.\u0002>\u00017>\u00057\u0014\u0002\u000e\u0004.\u0002#\u000f\u0001\u0001>\u00037>\u0001'&\"\u0006\u0007\u000e\u0001\u000f\u0001\u0004\u0007\u0006\u001676�\t\b\u0003\u00158./ie���h,Jhq�x{\\S\u000fc�\u0002�Fak[)\u0016\u0004\b\u0007\u0014!\u0011#�==��Y0'C7y�5<�b�;<U3-\u001e9\u001e���ЛU3\t\u0006\u0013\u000f7�\u0002y&?_�T2\u0014\t\u0005\u001d\u00193s  ��o\rSB\u0000\u0000\u0000\u0003��\u0000}\u0004�\u00043\u0000!\u0000?\u0000G\u0000\u0000\u0001\u0007\u000e\u0006\".\u0005/\u00017>\u00062\u001e\u0005\u0017\u00002>\u00047.\u0004'\u0016\u0015\u0014\u0006\"&547\u0006\u0007\u001e\u0004\u0013\u0007\u0016\u00177.\u0001'\u0004�\u001a\u0006\u001cFOsv���vsOF\u001c\u0006\u001a\u001a\u0006\u001cFOsv���vsOF\u001c\u0006�E�wRY,H\u000b\u00017\u001d:9\u001e1���.f|\u0007C-[TFk1ii%L\u0013\u0002X(\n(WT`G//G`TW(\n((\n(WT`G//G`TW(\n�p(3\\;h\u000e\u0001I%E:\u0019JY|��|UIW�\n`=^8\u0001�j|Ci\u0018`$\u0000\u0004��\u0000\u0000\u0004�\u0004�\u0000\u0016\u0000.\u00008\u0000A\u0000\u0000\t\u0001#7.\u0004/\u00017>\u000632\u00177\u0001\u0007\u000e\u0004\u00077>\u00067&'7\u001e\u0002\u0017\u00017.\u0001547\u0006\u0007\u0012\u0013\u0007\u0016\u0017?\u0001.\u0001'\u0003��Ɣ%R�ri'\n\u001a\u001a\u0006\u001cFOsv�H=<%\u0001�\u001a\u0007%Ze�I&\u0016-/\"0\u0013/\u0001a+'C�.\r�.%k�.f|Қk1i/\u0017\u001e:\u000f\u0004��P�\u0015egy8\u000f((\n(WT`G/\u0011���(\f4kbf\u001c�\u000f&2&?\u0018@\u00020�6�@\u0014���\u0012�nUIW���\u0001�j|C/W\u001cR\u001b\u0000\u0000\u0000\u0003��\u0000\u0000\u0005\u0012\u0004�\u0000\u000b\u0000\u0012\u0000\u0017\u0000\u0000#!26'\u0001.\u0001\u0007\u0001\u0006\u0016%5#\u0015!\t\u0002#\u0015\u001b\u0001/\u0005\u000e%\u001b\u0015�~\u00148\u0014�~\u0015\u001b\u0003\u0010���\u0001�\u0001����ddG \u0004 !\u0006 �� D�dd\u0002��-\u0001�d��\u0001,\u0000\u0000\u0001\u0000d\u0000\u0015\u0004�\u0004�\u0000)\u0000\u0000\t\u0001\u001e\u0001\u001d\u0001\u0014\u0006'%\u0011\u0016\u001d\u0001\u0014\u0006/\u0001#\u0007\u0006&=\u000147\u0011\u0005\u0006&=\u0001467\u0001\u0011462\u0016\u0015\u0003 \u0001k\u000f\u0016\u0018\u0011��d\u001a\u0013^�^\u0013\u001ad��\u0011\u0018\u0016\u000f\u0001kX|X\u0002���\u000e1\u0014)\u0014\r\f���[\u0016@\u0015\u0010\tNN\t\u0010\u0015@\u0016[\u0001\u0007�\f\r\u0014)\u00141\u000e\u0001E\u0001S>XX>\u0000\u0003\u0000\u0000\u0000\u0003\u0005x\u0004�\u0000\n\u0000\u0010\u0000\u0019\u0000\u0000\t\u0001!5!\u000135\t\u00015\u00057'!\u0015!\u0005\t\u00015#'7\u00173\u0003�����\u0001\u0003\u0002X�\u0001,���1����\u0001\u0003\u0003I\u0001,�����z�\u0003 ���\u0002X������z��������ŵ�{\u0000\u0001\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u0012\u0000\u0000\u0013!2\u0016\u0015\u0011\u0014\u0006#!\u0001\u0011#\"&5\u001146d\u0003�);;)����d);;\u0004L;)��);��\u0001,;)\u0002X);\u0000\u0000\u0000\u0003\u0000d\u0000\u0000\u0004L\u0004�\u0000\u0003\u0000\u0007\u0000-\u0000\u0000\u0001!\u0011!\u0001!\u0011!\u0011\u0015\u0014\u000e\u0005\".\u0005=\u0001!\u0015\u0014\u0017\u0016\u0017\u001632>\u0006'4=\u0001\u0001���\u0001,\u0002���\u0001,\u0006\u0018'Me���eM'\u0018\u0006\u0001,\u0006\u0011U'5%;)\u001f\u0011\u000b\u0003\u0002\u0001\u0003�\u0001,��\u0001,�p�*R~jqP33Pqj~R*��q \\\u0019\u000b\u000b\u0014\u001c#(,.\u0018\u0011\b�\u0000\u0000\u0001��\u0000�\u0004h\u0003�\u0000\u0005\u0000\u0000%7\t\u0001\u0017\u0001\u0003�������\u0001`��\u0002C���\u0001a\u0000\u0000\u0001\u0000F\u0000�\u0004�\u0004\u0000\u0000\u0005\u0000\u0000%\u0001'\t\u0001\u0007\u0002�\u0002B�������\u0002C���\u0001a�\u0000\u0000\u0002�:\u0000d\u0005v\u0003�\u0000\b\u0000\u0011\u0000\u0000\u0001\u0011!\u0017!\u0011#\t\u0004#\u0011!\u0017!\u0011\u0004����\u0001}�\u0001+\u0001+��\u0001+\u0001,�\u0001����\u0001�\u0002X��p��\u0001\u001b\u0001,\u0001\u001b���p�\u0002X\u0000\u0000\u0000\u0000\u0001\u0000\u0012\u0000\u0000\u0004�\u0004�\u00002\u0000\u0000\u000132\u0016\u0014\u0006+\u0001\u0003\u000e\u0002+\u0001\u0015\u0014\u0006\"&=\u0001!\u0015\u0014\u0006\"&=\u0001#\"&5463!7!\"&'\u0003&763!7>\u0001\u0004\u001a^\u0014\u001e\u001e\u00146�\u0002\b\u001e\u0012\u001f\u001d*\u001d��\u001d*\u001d2\u0015\u001d\u001d\u0015\u0002\u00170�� -\u0005d\u0005\u000f\u000e\u0019\u0003�&\u0005\u001b\u0004�\u001d*\u001d�?\u0004\r\u00162\u0014\u001e\u001e\u001422\u0014\u001e\u001e\u00142\u001e\u0014\u0015\u001d�*\u0016\u0001�\u0018\u0012\u0013�\u0011\u0015\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u000b\u0000\u000f\u0000\u0000\u0001\u0015!53463!2\u0016\u0015\u0005!\u0011!\u0004��P�;)\u0001,);�D\u0004��P\u0003�dd);;)���\u0000\u0002\u0000\u0001\u0000\u0000\u0005�\u0004L\u0000\f\u0000\u0010\u0000\u0000\u0013\u0003\u00113463!2\u0016\u0015!\u0015\u0005\u0001!\u0001���;)\u0001,);\u0001�\u0001,���P\u0001,\u0003 �p\u0002X);;)�d�D\u0002�\u0000\u0001\u0001.\u0000\u0000\u0003�\u0004�\u0000\t\u0000\u0000\u00013\u0011#\t\u0001#\u00113\u0001\u0001.��\u0001*\u0001*����\u0001,\u0002X\u0001,������\u0000\u0001\u0000\u0000\u0001/\u0004�\u0003�\u0000\t\u0000\u0000\u00015!\u0015\t\u0001\u0015!5\u0001\u0003�����\u0001,\u0002X\u0001,\u0001/��\u0001)\u0001*����\u0000\u0004\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\t\u0000\u0019\u0000\u001d\u0000!\u0000\u0000\u001b\u0001>\u00013!2\u0016\u0017\u0013\u0005!2\u0016\u001d\u0001\u0014\u0006#!\"&=\u000146\u0005#\u001537#\u00153\u001f�\u0005$\u0014\u0002�\u0013%\u0005���\u0003�);;)�\u0018);;\u0003Idd�dd\u0001�\u0002�\u0016'-\u0017�$d;)d);;)d);dddd\u0000\u0000\u0000\u0003��\u0000d\u0004�\u0004L\u0000\r\u0000'\u00003\u0000\u0000%\u00114632\u0016\u0015\u0011\u0014\u0006#\"&\u0001%\u0011%#\u0013\u0016\u000e\u0001#\"+\u0001\"&'\u0002=\u0001454>\u00023\u0007546?\u0001\u0015.\u0004\u0004L\u001d\u0015\u0014\u001e\u001e\u0014\u0015\u001d��\u0002��]&/\u0002\n\f\u000f\u0005\u0003S\u0014\u001d\u00048\u0001\u0004\f\t�2\u0019\u0019\u0004\u000e\"\u001a\u0016�\u0003R\u0015\u001d\u001d\u0015��\u0014\u001e\u001e\u0002l������\f\u000b\u0001\u001c\u0015\u0001Q\u000e�\u0002\u0003\r\u000b\u000f\u0006�2\u00182\r\r�\u0002\u0007\u0015\u0016!\u0000\u0000\u0001\u0000\u0015\u0000\u0015\u0004�\u0004�\u0000\u0017\u0000\u0000\u0001\u0007'\u0017\u0007\u0017\u0007\u0017\u00077\u00177\u00177\u0017'7'7'7\u0007'\u0007\u0001�-�N鳳�N�-��-�N괴�N�-�\u0004��N�,��-�N鳳�N�-��,�N��\u0000\u0003\u0000\u0000\u0000d\u0004�\u0004�\u0000\u001e\u0000*\u0000.\u0000\u0000\u0001#\"\u0006\u000f\u0002\u0006\u0015\u0011\u0014;\u0001\u0016;\u000127\u00136=\u00014&#!6=\u00014&\u0003\u0007!\u0015\u0003#'#\u0011?\u00013\u0001\u00113\u0011\u0002�2\u001b0\u000e`�\u0014d={\u0010�.%�\u001d='��\u001c='2\u0001��ֈd�d2�D�\u0004�(\u001c��\u001a%�pKd9\u0001X\u001f+d,Qv\u0007�,Q���}��d\u0001w������\u0002X\u0000\u0003\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\u001e\u0000\"\u0000.\u0000\u0000!#\"&/\u0002&5\u00114;\u00016;\u00012\u0017\u0013\u0016\u001d\u0001\u0014\u0006#!\u0016\u001d\u0001\u0014\u0006\u0001#\u00113\u0001'!5\u0003#\u0007#\u0011\u001f\u00013\u0002�2\u001b0\u000e`�\u0014d={\u0010�.%�\u001d='��\u001c=����\u0001�2\u0001��ֈd�d2(\u001c��\u001a%\u0001�Kd9��\u001f+d,Qv\u0007�,Q\u0001�\u0002X�+�}\u0001wd����\u0000\u0000\u0000\u0003\u0000\b\u0000d\u0005\u0015\u0004U\u0000\u001e\u0000=\u0000A\u0000\u0000\u0001%632\u001f\u0001\u0016\u0015\u0014\u000f\u0001!2\u0016\u0014\u0006+\u0001\u0003\u000e\u0001#!\"&5\u0011467\u0017\u0011\u0017!\u0013>\u0001;\u00012654&#!*\u0002.\u0004'&54?\u0001'\u0001#\u00113\u0001l\u0001j\u000e\u000b\u0011\fm\u000e\u000bU\u0001.UkmTk�\u0007\u001b\u000f��\u0007�\u001c\u000e:d\u0001%�\u0006\u001b\u000f�\u0010\u0012\u0012\u0010�7\u0001\u000b\u0004\t\u0003\u0007\u0004\u0004\u0002\u0005\n�V����\u0003i�\u0006\fp\u000e\u0014\u0012\u000eyL�N��\u0016'�\r\u0002\r\u0011%\nH�\tY\u0001S\u0015(\u001e\u0015\u0014\u001d\u0001\u0001\u0002\u0003\u0005\u0003\f\b\u000e\r�S��\u0002X\u0000\u0000\u0000\u0003��\u0000e\u0004�\u0004V\u0000\u001e\u00008\u0000<\u0000\u0000\u0001\u0005\u001e\u0002\u0015\u0011\u0014\u0006#!\"&'\u0003#\"&46\u0017!'&54?\u0001632\u0007\u0017\u0016\u0015\u0014\u0007\u000e\u0005*\u0001#!\u001532\u0016\u0017\u0013!7\u0011%\u0001#\u00113\u0001�\u0001m\u0007\u0013!�\b��\u000f\u001b\u0006�jTnlU\u0001.U\u000b\u000em\r\u0010\u000b[�\n\u0005\u0001\u0005\u0003\u0007\u0004\t\u0004\u000b\u0001�$�\u000f\u001b\u0006�\u0001%j��\u0002���\u0004P�\u0004\r'\u0011��\r�(\u0015\u0001SN�L\u0001y\u0010\u0010\u0015\rq\f��\u000b\u000f\u000b\t\u0003\u0005\u0003\u0002\u0001\u0001d)\u0014��Y\u0001����\u0002X\u0000\u0000\u0003\u0000a\u0000\u0000\u0004L\u0005\u000e\u0000\u001b\u00006\u0000:\u0000\u0000\u0001\u00114&'%54&\"\u0006\u0015\u0011'&\u0006\u000f\u0001\u0006\u0017\u0013\u001e\u00023!26\u0007!\u00037\u0017\u00167>\u0005<\u00015\u001146\u0016\u001d\u0001\u0014\u0016\u0017\u0005\u0019\u0001\u0015!5\u0004L(\u0015��N�Ly\u000f%\u000ep\u0016\u0010�\u0004\r'\u0011\u0002\r\r���\t�S�\u0016\u0019\u0003\u0005\u0003\u0002\u0001\u000122(\u0015\u0001S��\u0001�\u0001V\u000f\u001b\u0006�jTnkU��T\f\u0001\rn\u0016\u001f��\u0006\u0013\u001f�B\u0001SV�\u0012\r\u0001\u0005\u0003\u0007\u0004\t\u0003\f\u0001\u0001�\u0016\u0012\u0013\u0015�\u0010\u001b\u0006�������\u0000\u0003\u0000\u0001\u0000\n\u0003�\u0005\u0018\u0000\u0003\u0000\u001f\u00006\u0000\u0000\u00015!\u0015\u0001%>\u00015\u00114&#!\"\u0006\u000f\u0001\u0003\u0006\u001f\u0001\u001e\u0001?\u0001\u0003\u0014\u0016265\u0001\u0011\u0005\u000e\u0001\u001d\u0001\u0014\u0006&5\u0011<\u0001.\u0001'&\u000f\u0001'\u0013!\u00011\u0002X��\u0001S\u0014)�\r��\u0011%\n\n�\u000f\u0016p\r&\u000fy\u0001M�N\u0001,��\u0014(22\u0003\u0007\u0006\u0018\u0016�S�\u0001�\u0004P�����\u0006\u001b\u000f\u0001V\u0007�\u001c\u000e\u000f��\u001f\u0016n\r\u0001\u000bU��TlnT\u0002X�ڂ\u0006\u001c\u000f�\u0016\u0012\u0012\u0016\u0001�\u000b\u0007\u0010\b\u0003\r\u0012�V\u0001S\u0000\u0000\u0002\u0000\u0005\u0000\u0000\u0004�\u0004�\u0000\u000e\u0000\u0015\u0000\u0000\u00012\u0004\u0012\u0010\u0002\u0004 $\u000254>\u0002\u0013!\u0015!\u0007\t\u0001\u0002[�\u0001\u0013���������_��u��\u0001,\u0002\u0001��n\u0004ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½í  \u0001\u0013�zÝ _�\u0012��\u0001&\u0001*\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u0010\u0000\u0017\u0000\u0000\u00012\u001e\u0002\u0015\u0014\u0002\u0004 $\u000254>\u0002\t\u00015!5!5\u0002UzÝ _��������_����\u0001�\u0001.��\u0004�_��zï¿½ï¿½í  \u0001\u0013�zÝ _�������\u0000\u0002\u0000\u0005\u0000\u0000\u0004�\u0004�\u0000\u0010\u0000\u0017\u0000\u0000\u00012\u001e\u0002\u0015\u0014\u0002\u0004 $\u000254>\u0002\u00033\u00113\u00113\u0001\u0002[yÝ _��������_�ݵ�����\u0004�_��zï¿½ï¿½í  \u0001\u0013�zÝ _����\u0001,\u0001�\u0000\u0000\u0002\u0000\u0005\u0000\u0000\u0004�\u0004�\u0000\u0010\u0000\u0017\u0000\u0000\u00012\u001e\u0002\u0015\u0014\u0002\u0004 $\u000254>\u0002\u0013\u0011#\t\u0001#\u0011\u0002[yÝ _��������_��\u0013�\u0001,\u0001,�\u0004�_��zï¿½ï¿½í  \u0001\u0013�zÝ _�����p\u0001�\u0001,\u0000\u0000\u0000\u0003\u0000\u0005\u0000\u0000\u0004�\u0004�\u0000\u0010\u0000�\u0000�\u0000\u0000\u00012\u001e\u0002\u0015\u0014\u0002\u0004 $\u000254>\u0002\u0017\u000e\u0003\u0007\u0006&\u0007\u000e\u0001\u0007\u0006\u0016\u0007\u000e\u0001\u0007\u0006\u0016\u0007\u0014\u0016\u00072\u001e\u0001\u0017\u0016\u0017\u001e\u00027\u0016\u0006\u0017\u0016\u0017\u0014\u000e\u0001\u0017\u00167>\u00027.\u0001'.\u0001'\"\u000e\u0002\u0007\u0006'&65.\u0001'6.\u0001\u0007\u0006'&767\u001e\u0002\u0017\u001e\u0001\u001f\u000146'&67>\u00037&72\u0016267.\u0003'6'\u001e\u0001?\u00016.\u0001'\u0006\u0007\u0014\u001e\u0001\u0017.\u0003'>\u00017\u00162>\u0001\u0002[yÝ _��������_�ݒ\u000f+\u001a>\b\u000f=\u000f\u0015>\u0003\u0003\u0013\u0001\u00031\u0005\b\u001b\u0006\"\u0001\f\u0016\u0018\b\u0018T\u0016>9\u001d\b.\u0003*-\u0006\u0001\u0005fw\u001e\"#\u0003\u000e.\r\u000eF\u0011\t= .\u00102\u0010\u0004\u0001\u0006)\u0004\u0002\b\u0019\u001a\u0017\u0013\u0013\u000b\u0006\u0010\u0006(\u001b\u0006\f(\u000e\u000e\u0013\u0004\u0004%\u0004\u0005\n\u0007\u0018\u0016\u0006\u0010\b\u001f\u0012\u0017\t\n)#?\f\u000b\n\u001f7\f\u000b\u0006.R\u000f\u0013 \u0010\u0011\u0001\t,\u001c$\f\u0003\u001a\u0003\n\u0011\u000b\u0012\u0004�_��zï¿½ï¿½í  \u0001\u0013�zÝ _^\f\u0012\u0006\n\u0001\u0003\u0007\u0006\u0007'\u000f\u000b\u0017\u0007\"q\u0017!w\u001c\tF\u0019\u000b\u0013\u0004\f\u001e\b/\u001e\u0004\u0012J\u0014G\t\u0006\u0013\n\f\u0002r\u001d$>\u001f\t\u0001\u0007\u0007\u0010\u000b\u0001\u0002\u000b\u000b#\u0017\u0002/\u0002\r\b\u0003\u0016&\u0012\u001d\u0019\u001d\u001c\u001e\u0010\u0006\u0001\u0001\u0007\n\u0013%\t\b\u0003I\u0015\u0017+\n\u000e*\u0014\u0019\t\u0012\u0013\u0003\t\u000b\u0017'\u0015 \u0007)\u0003\r\u0003\u0005\u0004$#\u0016\f\u0003h\u00121'\u0004\u0002\u0006\u0004\f\b\f$\u0007\n\f\u0011\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000`\u0004�\u0004�\u0000\u000f\u0000\u0013\u0000#\u0000'\u00007\u0000;\u0000\u0000\u0013!2\u0016\u001d\u0001\u0014\u0006#!\"&=\u000146\u0005#\u00153\u0005!2\u0016\u001d\u0001\u0014\u0006#!\"&=\u000146\u0005!\u0015!\u0005!2\u0016\u001d\u0001\u0014\u0006#!\"&=\u000146\u0005!\u0015!d\u0003�);;)�\u0018);;\u0004\u0011���\u0018\u0003�);;)�\u0018);;\u0004\u0011�\f\u0001��\u0018\u0003�);;)�\u0018);;\u0004\u0011��\u0001,\u0004�;)d);;)d);dd�;)d);;)d);dd�;)d);;)d);dd\u0000\u0000\u0000\u0002\u0000d\u0000\u0000\u0004L\u0004�\u0000\u0003\u0000\t\u0000\u0000\u0001\u0015!5\u0017!\u0001\u0011\u0007\u0011\u0004L�\u00182\u0003����\u0004�dd��\f���\u0001�\u0000\u0004\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u0006\u0000\r\u0000\u0014\u0000\u001b\u0000\u0000\u00017'7!\u00117\u0001\u0011'\u0007'7'\u0001\u0017!\u0011\u00177\u0017\u00057\u0011!7'7\u0001I�ȁ�p�\u0004/�Ȏȁ����p�Ȏ\u0002X��p�Ȏ\u0002َȁ�p�\u0001\u000f�p�Ȏȁ�с\u0001��Ȏ:��p�Ȏ\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u000b\u0000\u0015\u0000\u001f\u00008\u0000B\u0000L\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0004 \u0006\u0015\u0014\u0016 654%2\u0016\u0014\u0006#\"&46\u00057&54632\u0016\u0014\u0006#\"'\u0007\u0016\u0015\u0014\u0006\"&546762\u0016\u0014\u0006#\"&54$2\u0016\u0015\u0014\u0006#\"&4\u0001�\u0001D\u0001\u0012�������\u0002_����\u0001V���\u0016  \u0016\u0017  \u0001\u0014\u0001\t \u0017\u0016  \u0016\u000e\u000f7\u00113H3)\u001f�.\u001f \u0016\u0017 �\".  \u0017\u0016 \u0004�������\u0001\u0012\u0001D\u0001\u0012\u0016����ò¬«Š . !,!T\u0001\u000e\u000e\u0016! . \n�\u0016\u001d$33$ 1\u0005\u000e\u001f.  \u0017\u0016  \u0016\u0017  .\u0000\u0000\u0002\u0000P\u00006\u0004�\u0004X\u0000\u0019\u00003\u0000\u0000%'&'.\u000354632\u0017632\u0016\u0015\u0014\u000e\u0002\u0007\u0006\u0007\u00137>\u000254&#\"\u000f\u0001'&#\"\u0006\u0015\u0014\u001e\u0001\u001f\u0001\u0016\u00176\u0002�\u0010Z�GCW#ń�bg���#WCG�Z�\f@C>`9J:vr3H<c=>@\u0016]aR6\u0016}�FGlZ.�Ł�Ń.ZlGF�}\u0001�\f>FX\u001cG`R��ObE\u001bVA>\u0016Zo\\\u0000\u0000\u0000\u0002\u00009��\u0004w\u0004�\u0000\u0019\u00002\u0000\u0000\u0001\u00177'\u0001\u0017\u0007\u0017\u0016\u00177>\u000154/\u0001&#\"\u0007\u0001\u0006\u0015\u0014\u0017\u0003\u0017\u0016327\u000164/\u0001&'\u0007\u0017\u0001'7'&'\u0007\u0006\u0015\u0014\u0001�\u0013i�\u0001��_\u0012.\u001f\u001d#7B�B]_@��BBԍB]_@\u0001\u001bBB�\f\u0007i��{�_\u0012.\u001d7B\u0001�\u0011i�\u0001��`\u0012.5\u001d#j+]B�BB��@_]B���BB\u0001\u001bB�B�\f\u0005i��{�_\u0012-87B]^\u0000\u0000\u0000\u0000\u0003\u0000�\u0000\u0000\u0003�\u0004�\u0000\u0011\u0000\u0015\u0000\u001d\u0000\u00007\u00114>\u00022\u001e\u0002\u0015\u0011\u0014\u0006#!\"&\u0001!\u0011!\u0004264&\"\u0006\u0014�<f���d:;)��);\u0002���\u0002X��V==V=d\u0003�\u00152.\u001e\u001e.2\u0015�G);;\u0003��D�=V==V\u0000\u0000\u0003\u0001'\u0000\u0012\u0004\t\u0004�\u0000/\u0000;\u0000A\u0000\u0000\u0001\u0011\u0017\u001e\u0004\u0015\u0014\u0006\u0007\u0015#5&'.\u0001'3\u001e\u0001\u0017\u0011'.\u000454>\u0001753\u0015\u001e\u0004\u0017#.\u0001\u0003\u0011\u000e\u0001\u0015\u0014\u001e\u0003\u0017\u0016\u0017\u0011654&\u0002�@\"<P7(��d�U(\u0019\u0003�\u0005WJ\u001b.BN/!X�Od&ER<+\u0003�\b6�=I\u0010\u0011*\u0014\u001c\u0007h�X\u0004\u0005��\u000e\u0007\u0013,<e>��\u000bMN\u0011W(kVMc\u000f\u0001O\u0007\u000e\u0019/9X7\\�C\u0007NO\u0004\u0013,?iBHK��\u0001\u0012\b;I\u001d,\u0018\u0015\u0006\u0007\u0002���\u0012�@G\u0000\u0001\u0000d\u0000f\u0003�\u0004�\u0000H\u0000\u0000\u0001\u0017\u000e\u0002#\"&\u0007\u000e\u0001\u000f\u0001'>\u00057>\u0001'#53&'.\u0001>\u00017632\u0016\u0015#4.\u0001#\"\u0006\u0007\u0006\u0015\u0014\u001e\u0006\u00173\u0015#\u0016\u0006\u0007\u0006\u0007>\u000136\u001632\u0003b2)O'*\u0017�2'V\u0017\u00187\u0006\u0015\n\u0011\f\u0011\t0\f$ݦ\u0018\u0014\n\t\u0003/-a��ʙDP$%T\u0014)\u0005\u0006\r\b\u0014\b\u0017\u0002��\b\u0015\u0015):#b\u0015 �\"L\u0001,�\u0019\u0017\u0003B\u0004\u0004\u001a\f\u000b�\u0004\u000e\u0006\r\u000b\u0011\n7�Gd17\u001a;V^(X�w4K\u001c\u001d\u0015,9\u000b\u001b\u0015 \u0012%\u000e(\u0004d2�\u001d;6\u000b\u000e\u0001\"\u0000\u0000\u0000\u0000\u0002\u0000\u0002\u0000\u0000\u0004�\u0004�\u0000\u0006\u0000\r\u0000\u0000!\u0001#\u0011#\u0011#\t\u0001#\u0011#\u0011#\u0001,\u0001*���\u0003�\u0001*���\u0001,\u0003��|\u0003����|\u0003�\u0000\u0000\u0000\u0005\u0000\u0002\u0000\u0000\u0003�\u0004�\u0000\u0006\u0000\u000e\u0000\u0012\u0000\u001c\u0000\"\u0000\u0000\u00013\t\u00013\u00113!\u0011#5#\u0015#\u0011\u0017#\u00153\u0003!\u0015#\u0015#535#\u00133\u0015!53\u0001��������\u0002Xddd�dd�\u0001,cdc�d���d\u0001,��\u0001,\u0003��\fdd\u0001�d��p�ddd��d�\u0000\u0005\u0000\u0002\u0000\u0000\u0003�\u0004�\u0000\u0006\u0000\u0010\u0000\u0016\u0000\u001e\u0000\"\u0000\u0000\u00013\t\u00013\u00113!\u0015#\u0015#535#5\u00133\u0015!53\u0003!\u0011#5#\u0015#735#\u0001��������\u0002Xcdc�d���dd\u0001,dddedd\u0001,��\u0001,\u0003��dddd�pd��p�\fdd��\u0000\u0000\u0004\u0000\u0002\u0000\u0000\u0004L\u0004�\u0000\u0006\u0000\f\u0000\u0012\u0000\u0016\u0000\u0000!\u0001#\u0011#\u0011#\u0001#53\u0011#\u0017\u0011#5#\u0011\u0017#\u00153\u0001,\u0001*���\u0003�d�d�d��dd\u0001,\u0003��|\u0003 d�\f��\fd\u0001�d�\u0000\u0004\u0000\u0002\u0000\u0000\u0004L\u0004�\u0000\u0006\u0000\f\u0000\u0010\u0000\u0016\u0000\u0000!\u0001#\u0011#\u0011#\u0001\u0011#5#\u0011\u0017#\u00153\u0003#53\u0011#\u0001,\u0001*���\u0004Jd��dded�d\u0001,\u0003��|\u0003��\fd\u0001�d��\fd�\f\u0000\u0000\u0000\u0000\u0005\u0000\u0002\u0000\u0000\u0004�\u0004�\u0000\u0006\u0000\n\u0000\u000e\u0000\u0012\u0000\u0016\u0000\u0000!\u0001#\u0011#\u0011#\u0001#53\u0013!5!\u0013!5!\u0013!5!\u0001,\u0001*���\u0003���d��\u0001,d�p\u0001�d�\f\u0001�\u0001,\u0003��|\u0002���\f��\f��\f�\u0000\u0000\u0000\u0000\u0005\u0000\u0002\u0000\u0000\u0004�\u0004�\u0000\u0006\u0000\n\u0000\u000e\u0000\u0012\u0000\u0016\u0000\u0000!\u0001#\u0011#\u0011#\u0001!5!\u0003!5!\u0003!5!\u0003#53\u0001,\u0001*���\u0004��\f\u0001�d�p\u0001�d��\u0001,d��\u0001,\u0003��|\u0002���\f��\f��\f�\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004L\u0004L\u0000\u000f\u0000\u001f\u0000\u0000\u0001!2\u0016\u0015\u0011\u0014\u0006#!\"&5\u001146\u0005!\"\u0006\u0015\u0011\u0014\u00163!265\u00114&\u0001�\u0001,�����ԥ��\u00025�\f);;)\u0001�);;\u0004L���ԥ���\u0001,���;)�\f);;)\u0001�);\u0000\u0000\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004L\u0004L\u0000\u000f\u0000\u001f\u0000\"\u0000\u0000)\u0001\"&5\u0011463!2\u0016\u0015\u0011\u0014\u0006\u0003!\"\u0006\u0015\u0011\u0014\u00163!265\u00114&\u0005\u0011%\u0002��ԣ���\u0001,���A�\f);;)\u0001�);;�G\u0001M��\u0001,�����ԥ�\u0003�;)�\f);;)\u0001�);d�\f�\u0000\u0003\u0000\u0000\u0000\u0000\u0004L\u0004L\u0000\u000f\u0000\u001f\u0000\"\u0000\u0000\u0019\u0001463!2\u0016\u0015\u0011\u0014\u0006#!\"&%\u00114&#!\"\u0006\u0015\u0011\u0014\u00163!26\u0003!\u0013��\u0001,�����ԥ�\u0003�;)�\f);;)\u0001�);d�\f�\u0001�\u0001,�����ԥ��A\u0001�);;)�\f);;\u0001���\u0000\u0003\u0000\u0000\u0000\u0000\u0004L\u0004L\u0000\u000f\u0000\u001f\u0000\"\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0003\u00114&#!\"\u0006\u0015\u0011\u0014\u00163!26\u0001\u0003!\u0004L���ԥ���\u0001,���;)�\f);;)\u0001�);���\u0001�\u0002��Ԣ���\u0001,�����\u0001�);;)�\f);;\u0001���\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0005\u0014\u0004L\u0000\u0013\u0000\u001a\u0000\u0000\u0001!2\u0016\u0015\u0011\u0014\u0006#!5!265\u00114&#!\u0013\u00015!\u0011!5\u0001�\u0001������p\u0001�);;)�\f��p��\u0001,\u0004L���ԥ��;)\u0001�);�����\u0001,�\u0000\u0000\u0001\u0000�\u0000\u0001\u0003�\u0004�\u0000\u001f\u0000\u0000%\u001727\u00016'&\u0007!6\u001276/\u0001#\"\u0007\u000e\u0001\u0000\u0007\u0006\u0017\u00163!\u0002\u0007\u0006\u0017\u0001�\t\r\r\u0002\u001a\u000f\t\b\u0018��\u0001�\u0002\u0002\b\b\t\u0010\t\u0004���L\u0011\n\n\u0016\u0001.�\u0005\u0005\t\u0002\u0001\u0010\u0002v\u0013\u0011\u0012\u0002\u0004\u0001�\f\u0011\n\b\u000f\u0005���X\u0013\u0015\u0013�J\u0014\u0015\u000b\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0005\u0014\u0004L\u0000\u0018\u0000\u001f\u0000\u0000%!\"&5\u0011463!5.\u0001/\u0001\"\u0006\u0015\u0011\u0014\u00163!2?\u00015!\u0011!5\u0001\u0003 �\f);;)\u0001�\u000e�]]����\u0001,/5d��\u0001,\u0001��;)\u0001�);�\u0004\u0007\u0002\u0002���ԥ�\u000f��\u0001,���\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u0006\u0000$\u0000\u0000\u0001'\u0001'\u0001'!\u0003\u0007\u0015\u0014\u0006#!\"&5\u001146;\u00017'#\"\u0006\u0015\u0011\u0014\u00163!26=\u0001\u0004�����\u0001a�\u0001��z;)�\f);;)�vJd����\u0001,��\u0002�����\u0001V���{�);;)\u0001�);zN���ԥ���b\u0000\u0000\u0000\u0003\u0000\u0004\u0000\u0004\u0004�\u0004�\u0000\u000b\u0000\u0013\u0000\u001b\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0004 \u0006\u0010\u0016 6\u0010$2\u0016\u0014\u0006\"&4\u0001�\u0001D\u0001\u0012�������\u0002_����\u0001V��\u0012�rr�r\u0004�������\u0001\u0012\u0001D\u0001\u0012\u0016�����\u0001V\u0017r�rr�\u0000\u0000\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004L\u0004�\u0000\u0006\u0000\u0010\u0000\u0014\u0000\u0000\u0001\u0011!\u0011!\t\u0002!2\u0016\u0015\u0011!\u001146\u0005#\u00153\u0002�����\u0001�\u0001��6\u0004\u0018\u000b\u0010��\u000e\u0003�dd\u0003 \u0001��p�\f\u0001��\f\u000f\n��\u0001\u0013\u000b\u000ed2\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004L\u0004�\u0000\u0006\u0000\u0010\u0000\u0014\u0000\u0000\u0001\u0011!\u0011!\t\u0001\u0003!2\u0016\u0015\u0011!\u001146\u0005#\u00153\u0001�\u0001,\u0001'�C�>K\u0004\u0018\u000b\u0010��\u000e\u0003�dd\u0002���\u0001,\u0001��\f�p\u000f\n��\u0001\u0013\u000b\u000ed2\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004L\u0004\u0000\u0005\u0000\u000f\u0000\u0013\u0000\u0000\t\u0001'\u0001'\u0007\u0003!2\u0016\u0015\u0011!\u001146\u0005#\u00153\u0001�\u0002T��F��K\u0004\u0018\u000b\u0010��\u000e\u0003�dd\u0001�\u0002T��F���k\u000f\n��\u0001\u0013\u000b\u000ed2\u0000\u0004\u0000\u0000\u0000\u0000\u0004L\u0004�\u0000\u0003\u0000\n\u0000\u0014\u0000\u0018\u0000\u0000\u0001'\u0007\u0017\u0005'\u0007\u0017\u0007!\u0011\u0001!2\u0016\u0015\u0011!\u001146\u0005#\u00153\u0001�a�a\u0001ŕԕ�\u0002���\u0004\u0018\u000b\u0010��\u000e\u0003�dd\u0004Oa�b\u001d�ԕ�\u0002���\u000f\n��\u0001\u0013\u000b\u000ed2\u0000\u0000\u0004\u0000\u0000\u0000\u0000\u0004L\u0004�\u0000\u0006\u0000\n\u0000\u0014\u0000\u0018\u0000\u0000\u0001\u0007\u00177\u0017\u0011!\u0003\u0007\u00177\u0005!2\u0016\u0015\u0011!\u001146\u0005#\u00153\u0002\u001b�ԕ��E\u0003a�b�\u001b\u0004\u0018\u000b\u0010��\u000e\u0003�dd\u0003��Ԕ�\u0002��\u0015a�a�\u000f\n��\u0001\u0013\u000b\u000ed2\u0000\u0000\u0000\u0002\u0000\u0017��\u0004�\u0004�\u0000\u0005\u0000\b\u0000\u0000\u0001\u0011\t\u0001\u0011!\t\u0001\u0015\u0004��%���x\u0004w�`\u0004���\u0001\u0010�w\u0001�\u0002��8�\u0000\u0002\u0000\u0000\u0000d\u0004L\u0004�\u0000\u0015\u0000\u0019\u0000\u0000\u0001\u0011\u0014\u0006+\u0001\u0011!\u0011#\"&5\u001146;\u0001\u0011!\u00113+\u0001\u00153\u0004L\u001e\u0014��D�\u0015\u001d\u001d\u0015�\u0001�d�dd\u0003���\u0015\u001d\u0001��p\u001d\u0015\u0003�\u0014\u001e��\u0001,�\u0000\u0000\u0000\u0003\u0000\u0000\u0000>\u0005\u0014\u0004�\u0000\u0013\u0000\u0017\u0000\u001d\u0000\u0000\u0001!\u0011#\"&5\u001146;\u0001\u0011!\u00113\u0017\u0015\u0001'\u0003#\u00153\t\u0001'7\u0017\u0001\u0002B���\u0015\u001d\u001d\u0015�\u0001�d���x\u0004dd\u0002X�%�{x\u0001a\u0001��p\u001d\u0015\u0003�\u0014\u001e��\u0001,����x\u0002=��2�$�{x\u0001`\u0000\u0003\u0000\u0000\u0000\u0006\u0005\u000e\u0004�\u0000\u0013\u0000\u0017\u0000#\u0000\u0000\u0001!\u0011#\"&5\u001146;\u0001\u0011!\u00113\u0017\u0011\u0007'\u0003#\u00153\u0001'\u0007'7'7\u00177\u0017\u0007\u0017\u0002\u0011���\u0015\u001d\u001d\u0015�\u0001�d�g�dd\u0001Ӫ�������\u0001��p\u001d\u0015\u0003�\u0014\u001e��\u0001,���g�\u0001���\u001e���������\u0000\u0000\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u0012\u0000\u0016\u0000\u001d\u0000\u0000\u0001!\u0011#\"&5\u001146;\u0001\u0011!\u00113\u0017\u0011!\u0011#53\u0001#\u0011#\t\u0001#\u0002��\f�\u0015\u001d\u001d\u0015�\u0001�d��pdd\u0001,��\u0001,\u0001,�\u0001��p\u001d\u0015\u0003�\u0014\u001e��\u0001,���\u0001,�������\u0001,\u0000\u0000\u0003\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u0012\u0000\u0016\u0000\u001d\u0000\u0000\u0001!\u0011#\"&5\u001146;\u0001\u0011!\u00113\u0017\u0011/\u0001#53\u00013\t\u00013\u00113\u0002Z�n�\u0015\u001d\u001d\u0015�\u0001�d���dd\u0001,�������\u0001��p\u001d\u0015\u0003�\u0014\u001e��\u0001,��n����|\u0001,����\u0000\u0000\u0003\u0000\u0000\u0000�\u0004�\u0004L\u0000\t\u0000\u0013\u0000\u0017\u0000\u0000\u000154&#!\"\u0006\u001d\u0002\u0011\u0014\u00163!265\u0011\u0001!\u0015!\u0004�\u001d\u0015��\u0015\u001d\u001d\u0015\u0004L\u0015\u001d��\u0001��p\u0003��\u0015\u001d\u001d\u0015�d��\u0015\u001d\u001d\u0015\u0002&���\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000f\u0004�\u0004�\u0000\u0006\u0000\n\u0000\u000e\u0000\u0015\u0000\u0019\u0000\u001d\u0000\u0000\u0001!5\t\u00015)\u000153\u00153#53\u0001!\u0015\t\u0001\u0015!;\u0001\u0015#7\u001535\u0001�\u0001�\u0001,���p�pd�dd\u0001,�p��\u0001,\u0001�ddd�d\u0003�����������D�\u0001*\u0001*�����\u0000\u0002\u0000d\u0000\u0000\u0004�\u0004�\u0000\u0016\u0000/\u0000\u0000%\u00114&\u0007\u0005\u000e\u0001\u0015\u0011\u0014\u001e\u0002\u001f\u0001\u0011\u0014\u0016;\u000126\u0001\u0017\u0011\u0007\u0011\u0014\u0006+\u0001\"&5\u0011'\u00117\u0017\u00113\u00117\u0017\u00113\u0011\u0004�$\u001a��\u0019%\u0015\u001d\u001d\u000b\n\u001d\u0015�\u0015\u001d�v2d\u001d\u0015�\u0015\u001dd22d22d2\u0004R\u001f\u0013\u0011u\u0010E\u001f��\u001d5!\u0018\u0006\u0005�s\u0015\u001d\u001d\u0004�d�p��A\u0015\u001d\u001d\u0015\u0001��\u0001�dd��\u0001,dd��\u0001,\u0000\u0000\u0000\u0000\u0001\u0000d\u0000\u0000\u0004�\u0004L\u00003\u0000\u0000%\u001146?\u00015!\u00152\u0016\u0015\u0011!\u00114635!\u00152\u001e\u0003\u0015\u0011\u0014\u0006\u000f\u0001\u0015!5\"&5\u0011!\u0011\u0014\u0006#\u0015!5\".\u0003\u0004L2\u0019\u0019�pK\u0019�\f\u0019K�p\u0004\u000e\"\u001a\u00162\u0019\u0019\u0001�K\u0019\u0001�\u0019K\u0001�\u0004\u000e\"\u001a\u0016j\u0003x\u0016\u0019\u0001\u000288\f&�v\u0001�&\f88\u0001\u0005\t\u0015\u000e��\u0016\u0019\u0001\u000288\f&\u0001��v&\f88\u0001\u0005\t\u0015\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0004L\u0004L\u0000\t\u0000\u0019\u0000\u001d\u0000!\u0000%\u0000.\u0000\u0000\u0001'!\u0007!\u0017\u0015%35\u0001\u00114&#!\"\u0006\u0015\u0011\u0014\u00163!26\u0001!\u0015!\u0005%\u0015\u0005%!\u0015)\u0001%\u0011!\u0007!'!7\u0002�d��d\u0001��\u0001'i��;)��);;)\u0001,);�p\u0001,��\u0002X\u0001��p��\u0001,��\u0002�\u0001'�Wd��d\u0001��\u0003�dd�bb��D\u0001�);;)�\f);;\u0002\u001d�#���b�b��dd�\u0000\u0001\u0000\u0010\u0000\u0010\u0004�\u0004�\u0000!\u0000\u0000\u0013\u0007\u0006\u001e\u0003\u0017\u001e\u00033?\u00016&/\u0001&\u0006\u000f\u0001&'&'7>\u0001/\u0001.\u0001��\u0001\u0001\u001f>�fgї{\u001f\u001f�\u0010\u0006\u0013�\u00134\u0010w�|~ev\u0011\u0006\u000e�\u000e-\u0004��\u000b+���fg�=!\u0001�\u0011/\u000e�\u000e\u0004\u0011vg|~�v\u00111\u0014�\u0013\u0006\u0000\u0002\u0000\u0000\u0000\u0000\u0004�\u0004L\u0000\"\u0000@\u0000\u0000\u00015.\u0004#\"\u000e\u0004\u000f\u0001\u0015\u0014\u0016?\u0001>\u0001=\u00016 \u0017\u0015\u0014\u0016\u001f\u0001\u00166\u0005\u0001\u001e\u0001\u001d\u0001\u0014\u0006#!\"&=\u0001467\u000154>\u00032\u001e\u0002\u001f\u0001\u0004�\u0006\u001ad|�~\\�ud?,\t\t\u001d\u0014�\u0014\u001d�\u0001>�\u001d\u0014�\u0014\u001d�p\u0001m\u000e\u0015\u001e\u0014��\u0015\u001d\u0015\u000e\u0001m\u0002\u0016&RpR&\u0016\u0001\u0001\u0002��\b\u0019A1)\u0015!((!\u000b\n�\u0015\u0019\u0004!\u0004\"\u0015�\u0018\u0018�\u0015\"\u0004!\u0004\u0019)��\u000e3\u0014�\u0015\u001d\u001d\u0015�\u00143\u000e\u0001/2\u0004\r \u0019\u0015\u0014\u001b\u001c\n\n\u0000\u0000\u0000\u0000\u0002\u0000d\u0000\u0000\u0004�\u0004L\u0000\u0015\u0000\u0019\u0000\u00007!'57\u0011#\u0015#5#\u0015#5#\u0015#5#\u0011\u0017\u0015\u0005\u0015!5�\u0003�}ddd�d�ddd\u0003���Ȗ�d\u0001��������pd��dd\u0000\u0000\u0000\u0003\u0000d\u0000\u0000\u0004�\u0004L\u0000\t\u0000\u0013\u0000\u001d\u0000\u0000\u000132\u0016\u0015\u0011!\u001146\u000132\u0016\u0015\u0011!\u001146\u0001\u00114&+\u0001\"\u0006\u0015\u0011\u0002Xd);��;\u0001�d);��;��;)d);\u0004L;)�\u0018\u0003�);��;)�D\u0002�);��\u0001�);;)�p\u0000\u0000\u0000\u0000\u0005��\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u0013\u0000\u001f\u0000'\u0000+\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0007!\u0011!\u0001\u00113\u0015#\u0015!\u0011#535!#\u0011353\u0011#1\u0011#\u0011\u0004��|�D|��|\u0002�|���|\u0003�����\u0001,��\u0001,��ddd\u0003 �\f|��|\u0001�|��\u0018�D\u0002X��dd\u0001,dd�\fd\u0001,��\u0001,\u0000\u0000\u0005��\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u0013\u0000\u001f\u0000'\u0000+\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0007!\u0011!\u0001#5#\u0011353\u00153\u0011#!#\u0011353\u0011#1\u0011#\u0011\u0004��|�D|��|\u0002�|���|\u0003���dddddd\u0001���ddd\u0003 �\f|��|\u0001�|��\u0018�D\u0001���\f��\u0001��\fd\u0001,��\u0001,\u0000\u0000\u0000\u0004��\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u0013\u0000\u001b\u0000#\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0007!\u0011!\u0001\u0011!5#\u0011353\u0011!5#\u001135\u0004��|�D|��|\u0002�|���|\u0003���\u0001,��d\u0001,��\u0003 �\f|��|\u0001�|��\u0018�D\u0002X�\fd\u0001,d�\fd\u0001,d\u0000\u0004��\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u0013\u0000\u0016\u0000\u0019\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0007!\u0011!\u0001\r\u00013-\u0001\u0004��|�D|��|\u0002�|���|\u0003��\f��\u0001,d\u0001,��\u0003 �\f|��|\u0001�|��\u0018�D\u0001𖖖�\u0000\u0000\u0000\u0005��\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u0013\u0000\u0017\u0000\u001f\u0000'\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0007!\u0011!\u0003\u0011!\u0011\u00133264&+\u0001!#\"\u0006\u0014\u0016;\u0001\u0004��|�D|��|\u0002�|���|\u0003�d�Dd�)69&�\u0001��&96)�\u0003 �\f|��|\u0001�|��\u0018�D\u0002X�\f\u0001��pT�VV�T\u0000\u0000\u0000\u0000\u0005��\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u0013\u0000\u001f\u0000%\u0000)\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0007!\u0011!\u0001\u00113\u0015#\u0015!\u0011#535\u00013\u0011#\u00153\u000335#\u0004��|�D|��|\u0002�|���|\u0003�����\u0001,��\u0001,d�d�dd\u0003 �\f|��|\u0001�|��\u0018�D\u0002X��dd\u0001,dd�\f\u0001�d�pd\u0000\u0000\u0000\u0000\u0006��\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u0013\u0000\u0019\u0000\u001f\u0000#\u0000'\u0000\u0000\u0001\u0011\u0014\u0006#!\"&5\u0011463!2\u0016\u0007!\u0011!\u00015#\u0011!\u0011\u00013\u0011#\u00153\u0001#53\u001335#\u0004��|�D|��|\u0002�|���|\u0003��Dd\u0001,\u0001,d�d�qdd�dd\u0003 �\f|��|\u0001�|��\u0018�D\u0001�d�\f\u0001��p\u0001�d�����d\u0000\u0000\u0000\u0006��\u0000\u0000\u0004�\u0004L\u0000\u000f\u0000\u0013\u0000\u001d\u0000#\u0000'\u0000+\u0000\u0000\u0013!2\u0016\u0015\u0011\u0014\u0006#!\"&5\u001146\u0005!\u0011!\u0001#5!\u0011#\u0015#53%#53\u0011#%3\u0015#!#53�\u0002�|��|�D|��\u0003��|\u0003����\u0001,cdc\u0001�d�d��dd\u0001�dd\u0004L�|�\f|��|\u0001�|���D\u0001�d��dd�d�\fddd\u0000\u0000\u0000\u0003\u0000\u0004\u0000\u0004\u0004�\u0004�\u0000\u000b\u0000\u0013\u0000\u001d\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0004 \u0006\u0010\u0016 6\u0010\u0007!\u0015!\u0015!'57!\u0001�\u0001D\u0001\u0012�������\u0002_����\u0001V����\u0001,��dd\u0001,\u0004�������\u0001\u0012\u0001D\u0001\u0012\u0016�����\u0001VG�dd�d\u0000\u0000\u0004\u0000\u0000\u0000\u0004\u0004�\u0004�\u0000\u000b\u0000\u0013\u0000 \u0000$\u0000\u0000\u0000 \u0004\u0012\u0010\u0002\u0004 $\u0002\u0010\u0012\u0004 \u0006\u0010\u0016 6\u0010\u0007\u0015#5#\u00153\u0015#\u0015#\u0011!\u0013#53\u0001�\u0001D\u0001\u0012�������\u0002_����\u0001V��d���d\u0001,ddd\u0004�������\u0001\u0012\u0001D\u0001\u0012\u0016�����\u0001VGddddd\u0001��pd\u0000\u0000\u0000\u0000\u0002����\u0004�\u0004A\u0000\u001a\u0000!\u0000\u0000\u000132654&#\"\u0007.\u0001#\"\u0006\u0015\u0014\u0017\u000e\u0001\u0015\u0014\u0016;\u0001\u0011!\u00033\t\u00013\u00113\u0003 �x��x.,,�n��\u0002BUqO�\u0001�d�������\u0001,�zx�\u000eawי\u0019\f\u000ekEPr\u0001,�p��\u0001,\u0001,\u0000\u0002����\u0004�\u0004A\u0000\u0018\u0000\u001f\u0000\u0000\t\u0001>\u000154&#\"\u0007.\u0001#\"\u0006\u0015\u0014\u0017\u000e\u0001\u0015\u0014\u0016;\u0001\u0001#\u0011#\t\u0001#\u0002X\u0001�^y�x.,,�n��\u0002BUqO\b\u0002\u0002��\u0001,\u0001,�\u0002��m\u001a�dy�\u000eawי\u0019\f\u000ekEPr�p\u0001,\u0001,��\u0000\u0000\u0001\u0000d\u0000\u0000\u0004L\u0004m\u0000\u0010\u0000\u0000%!\u00013\u00013\t\u00013\u00013\u0001!\u0015\u0007!'\u0002�\u0001����������Ԫ�����\u0001�K\u0001^K�\u0001,\u0001,\u0001M�����ԛ--\u0000\u0000\u0000\u0000\u0001\u0000y\u0000\u0000\u00047\u0004�\u0000)\u0000\u0000%\u0011\u001632654'>\u000154&'.\u0001#\"\u0006\u0015\u0014\u0016\u0015&#\"\u0006\u0015\u0014\u0016\u0017\u0006\u0015\u0014\u0016327\u0011\u0007!\u0002�.6Ji\t2;{Y\u001a�^t�\u0002\u000e\tJi9/\u0004iJ8,K\u0001^-\u00012\u001eiJ\u0018\u001f f=Z�\u0006Yq�t\u0004\u0010\u0003\u0002iJ5X\u0015\u0010\u0016Ji\u001e��-\u0000\u0000\u0000\u0003\u0000\u0000\u0000d\u0004�\u0004�\u0000\u0017\u0000\u001b\u0000%\u0000\u0000\u0001!2\u0016\u0015\u0011!5#\u0015!\u0011463!546;\u00012\u0016\u0015\u000535#\u0001\u0015\u0014\u0006#!\"&=\u0001\u0003 \u0001,);�\f��\f;)\u0001,;)�);����\u0002�;)�\u0018);\u0003�;)�pdd\u0001�);d);;)dd�D�);;)�\u0000\u0000\u0011\u0000\u0000\u0000\u0000\u0004L\u0004�\u0000\u0011\u0000\u001b\u0000\u001f\u0000#\u0000'\u0000+\u0000/\u00003\u00007\u0000;\u0000?\u0000C\u0000G\u0000K\u0000O\u0000S\u0000W\u0000\u0000\u000154&+\u00015#\u0015!5#\u0015#\"\u0006\u001d\u0002\u0011\u0014\u00163!265\u0011\u00053\u0015#73\u0015#73\u0015#73\u0015#73\u0015#\u00053\u0015#73\u0015#73\u0015#73\u0015#73\u0015#\u00053\u0015#73\u0015#73\u0015#73\u0015#73\u0015#\u0004L\u001d\u0015�d�\fd�\u0015\u001d\u001d\u0015\u0003�\u0015\u001d�\u0018dd�dd�dd�dd�dd��dd�dd�dd�dd�dd��dd�dd�dd�dd�dd\u0003��\u0015\u001ddddd\u001d\u0015�d�\u0012\u0014\u001e\u001e\u0014\u0002��ddddddddddddddddddddddddddddd\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0004�\u0004�\u0000\u0018\u0000\u0000\t\u0001\u0016\u0014\u0007\u0006\"/\u0001\u0001\u0011'\u0007\t\u00017'!\u0001'&4762\u0003�\u0001\u001b\u000f\u000f\u000e*\u000e$������\u0001/��\u0001,\u0001\u000b#\u000f\u000f\u000e*\u0004���\u000f*\u000e\u000f\u000f#��������\u0001|��\u0001\u0017$\u000e*\u000e\u000f\u0000\u0000\u0000\u0000\u0001��\u0000;\u0004�\u0004�\u0000O\u0000\u0000%\u0017\u0001>\u0001'.\u0001'&#\"\u0007\u0006\u0007\u0001\u000e\u0001\u0017\u001e\u0001327>\u0002767\u0001>\u0001'&'&#\"\u0006\u0007\u0001\u0007\u0017\u000167632\u0017\u0016\u0007\u0001\u0006#\"&'&>\u0002767\u0001>\u000232\u0017\u001e\u0001\u0007\u0006\u000f\u0001\u0003\u0002\u0006E\u0002\u0005C8\u0011\u0010fOESkZ(G�\u0000D\u001a0#vF?8!@)'(\u0011\u0001�#\u0018\u000f\u001bZ\u0014\t.C\"�|\u0007E\u0001y\u0014\u0013\u0017\u001b&\u000f\u0010$��4I7Z\t\u0005\u000f0$&\u0018\u0014\u0001\\4=k6\u0019\u0017_v\b\u0007[��w<\u0001�C�]W�$!7\u0018G�\u0000D�N9@\u001c\u00101*+,\u0011\u0001�#b/W\u0011\u0002\"\"�t\u0007C\u0001u\u0016\u0010\u0017$'$��4B?#>@$$\u0015\u0014\u0001\\475\u0004\u0011�be[��\u0000\u0000\u0000\u0004\u0000\u0000\u0000d\u0004�\u0004L\u0000\u001d\u0000!\u0000)\u00001\u0000\u0000%\u00114&+\u0001.\u0004+\u0001\"\u000e\u0002\u000f\u0001#\"\u0006\u0015\u0011\u0014\u00163!26\u0003#53\u0006\u0014\u0006\"&462\u00124&\"\u0006\u0014\u00162\u0004�;)�\u0004\u000f37S*�)R:.\u000b\f�);;)\u0003�);�dd��Ȑ��\u0006>X>>X�\u0002X);\b\u001bE5+);;\u0015\u0014;)��);;\u0002\u001dd�Ȑ�Ȑ��X>>X>\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000\u0004L\u0004�\u0000\u0019\u0000#\u0000\u0000\u000132\u0016\u0015\u0011\u0014\u0006#!\"&5\u001146;\u00015463!2\u0016\u0015\u0005!54&+\u0001\"\u0006\u0015\u0003�d);;)�|);;)dvR\u0001,Rv�\f\u0001,\u001d\u0015�\u0015\u001d\u0003 ;)��);;)\u0002X);�RvvRȖ\u0015\u001d\u001d\u0015\u0000\u0000\u0000\u0002\u0000J\u0000\u0000\u0004f\u0004�\u0000'\u0000/\u0000\u0000\u000132\u0016\u0015\u0014\u0006\u0007\u000e\u0002\"&/\u0001.\u0001546;\u00017\u0013>\u00017'&6;\u00012\u0016\u000f\u0001\u001e\u0001\u0017\u0013\u0000\"'\u001e\u0001267\u0004&\u0011\u0014\u001b\u001a\u0012\u0016R���::\u0012\u001a\u001b\u0014\u0011v?\u000fzS\u0012\u0006\u0012\u0014^\u0014\u0012\u0006\u0012Sz\u000f?��l1\f8F8\u000b\u0001�\u001d\u0015\u0014(\u0007\t\u001c.)\u0015\u0014\u0007)\u0014\u0015\u001d�\u0001GM~\u0014 \u0013\u001a\u0015\u0013%\u0013M���+\u00061==1\u0000\u0000\u0001\u0000�\u0000\u0000\u0004L\u0004�\u0000\n\u0000\u00003\t\u0001\u00114&#!\"\u0006\u0015�\u0001�\u0001�\u001d\u0015��\u0015\u001d\u0001��E\u0004~\u0014\u001e\u001e\u0014\u0000\u0000\u0000\u0001\u0000o\u0000\f\u0004D\u0004�\u0000H\u0000\u0000\u0001\u001e\u0003\u000e\u0004\u0007\u0006.\u00027\u000e\u0002\u0014\u0017\u001e\u0001\u0017\u0016>\u00037>\u0001'\u001e\u0001\u0007\u000e\u0001\u0007\u000e\u0004\u001e\u0001>\u00017>\u000476\u0002'\u0016\u0017\u0016'&'.\u00027\u000e\u0004\u0001�\u0002\f\u0004\b\u0001\u0001\u000b\u0010\u001a\u0012&:\u0017\u0007\u000e4?\u000f\u0005\tFF\u001fB:8(\u000f \u000e\u0014OV\u0011\u0005\u001f\u0016\n\t\u000f\u0003\u0003\b\u000e\u0019$\u00189DkC@\u000f&��\u0016\u0015'G\u000f\u0012OS\u00053\r*gJ.\u0002�\f;\u001b4\u001b(\u0017\u0019\u0010\u0004\n.MV .nhB\u001e8-\u0015\n%>=\u001eB�'P�d!I,\u0013\u0014 \u000f\u0017\b\u000b\u0001\u0004\u0006\u0014\u001c=CnC�\u0001Sm,U�\u0005\u0002\u0007!�ٕ\b\u001ffm�\u0000\u0001\u0000\u0000\u0000\u0002\u0004�\u0004�\u0000\u0014\u0000\u0000\u00017\u0016\u0007\u0006'\u0001\u0006\"/\u0001&47\u0001&676\u0017\u0005\u0003��\r�{���\u000f+\u000fo\u000f\u000f\u0002X!N`����\u0003\n~�\\F/��\u0010\u0010n\u000f+\u0010\u0002We�6\\e�\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0001\u0000A\u0002(��_\u000f<�\u0000\u001f\u0004�\u0000\u0000\u0000\u0000����\u0000\u0000\u0000\u0000�����:��\u0005�\u0005\u0018\u0000\u0000\u0000\b\u0000\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0000\u0000\u0005���\u0000\u0000\u0005\u0018�:��\u0005�\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000�\u0001�\u0000(\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000d\u0004�\u0000\u0000\u0004�\u0000\u0000\u0002�\u0000\u0000\u0005\u0018\u0000\u0000\u0002�\u0000\u0000\u0005\u0018\u0000\u0000\u0001�\u0000\u0000\u0001F\u0000\u0000\u0000�\u0000\u0000\u0000�\u0000\u0000\u0000�\u0000\u0000\u0001\u0004\u0000\u0000\u0000H\u0000\u0000\u0001\u0004\u0000\u0000\u0001F\u0000\u0000\u0004�\u0000d\u0004�\u0000�\u0004���\u0004�\u0000\u0000\u0004���\u0001�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u000e\u0004�\u0000\u0017\u0004�\u0000d\u0004���\u0004���\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u001d\u0004�\u0000j\u0004�\u0000\u0017\u0004�\u0000\u0017\u0004�\u0000\u0017\u0004�\u0000d\u0004�\u0000\u001a\u0004�\u0000d\u0004�\u0000\u0001\u0004�\u0000d\u0004�\u0000\u0004\u0004���\u0004�\u0000\u0000\u0004�\u0000\u0001\u0004�\u0000\u0004\u0004�\u0000\u0000\u0004�\u0000\u0004\u0004�\u0000\u0017\u0004�\u0000\u0017\u0004�\u0000d\u0004�\u0000d\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0001\u0004�\u0000\u0002\u0004�\u0000d\u0004�\u0000\u0000\u0004�\u00005\u0004�\u0000d\u0004�\u0000�\u0004���\u0004�\u0000!\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004���\u0004�\u0000\u0001\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000�\u0004�\u0000\u0001\u0004�\u0000u\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000�\u0004�\u0000\u0000\u0004�\u0000�\u0004�\u0000�\u0004�\u0000�\u0004�\u0000�\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0001,\u0004�\u0000d\u0004�\u0000�\u0004�\u0001\u0010\u0004�\u0000\u0003\u0004�\u0000\u0003\u0004�\u0000\u0003\u0004�\u0000\u0003\u0004�\u0000\u0003\u0004�\u0000\u0003\u0004�\u0000\u0000\u0004�\u0000\u0004\u0004�\u0000\u0004\u0004�\u0000\u0004\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000�\u0004�\u0000h\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\"\u0004�\u0000\u0017\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004���\u0004���\u0004���\u0004�\u0000d\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000d\u0004���\u0004�\u0000F\u0004��:\u0004�\u0000\u0012\u0004�\u0000\u0000\u0004�\u0000\u0001\u0004�\u0001.\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004���\u0004�\u0000\u0015\u0004�\u0000\u0000\u0004�\u0000\u0000\u0004�\u0000\b\u0004���\u0004�\u0000a\u0004�\u0000\u0001\u0004�\u0000\u0005\u0004�\u0000\u0000\u0004�\u0000\u0005\u0004�\u0000\u0005\u0004�\u0000\u0005\u0004�\u0000\u0000\u0004�\u0000d\u0000\u0000\u0000\u0000\u0000P\u00009\u0000�\u0001'\u0000d\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000�\u0000\u0000\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0017\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000d\u0000d\u0000\u0000\u0000\u0010\u0000\u0000\u0000d\u0000d����������������\u0000\u0004\u0000\u0000����\u0000d\u0000y\u0000\u0000\u0000\u0000\u0000\u0000��\u0000\u0000\u0000\u0000\u0000J\u0000�\u0000o\u0000\u0000\u0000\u0000\u0000\u0000\u0000*\u0000*\u0000*\u0000*\u0000V\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000p\u0000�\u0000�\u0000�\u0001\u001a\u0001B\u0001J\u0001b\u0001�\u0001�\u0001�\u0002\u001c\u0002N\u0002n\u0002�\u0003&\u0003�\u0004f\u0004z\u0004�\u0004�\u0005\u001c\u0005^\u0005�\u0005�\u0006P\u0006j\u0006�\u0006�\u0006�\u0007\u0006\u0007<\u0007r\u0007�\u0007�\u0007�\b0\bz\b�\t\u0000\t&\tF\t|\t�\n8\n^\n�\n�\n�\u000b.\u000bx\u000b�\u000b�\f\u001a\fv\f�\r.\r�\u000e2\u000e�\u000f\b\u000f.\u000ff\u000f�\u000f�\u000f�\u0010>\u0010�\u0010�\u0010�\u0011\u000e\u00118\u0011N\u0011\\\u0011�\u0011�\u0011�\u0011�\u0012\u000e\u00120\u0012D\u0012\\\u0012�\u0012�\u0012�\u0013\n\u0013b\u0013�\u0013�\u0014\u001a\u0014P\u0014�\u0014�\u0014�\u0014�\u0014�\u0014�\u0015\u001c\u0015>\u0015x\u0015�\u0016&\u0016�\u0016�\u0017$\u0017f\u0017�\u0017�\u0018\u0000\u0018\u0014\u0018(\u0018P\u0018�\u0018�\u0018�\u0018�\u0019\n\u0019B\u0019�\u0019�\u001a\u0004\u001aL\u001a�\u001b\n\u001bd\u001b�\u001b�\u001c\u0018\u001cD\u001cr\u001dX\u001d�\u001d�\u001e\u0002\u001ex\u001e�\u001f\u001c\u001fN\u001f� \u0016 4 l � � �!$!R!�!�!�\"0\"^\"�\"�#\b#@#j#�#�#�$\u001c$6$`$�$�%\b%<%f%�%�&2&�&�'\u001c'D'x'�(\u0000(:(l(�(�)6)|)�)�*.*d*�*�+\u0002+�+�,2,|,�,�-\u0016-�-�\u0000\u0001\u0000\u0000\u0000�\u0000�\u0000\u0011\u0000\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0000@\u0000.\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000f\u0000�\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u0013\u0000\u0012\u0000\u0000\u0000\u0003\u0000\u0001\u0004\t\u0000\u0000\u0000j\u0000\u0012\u0000\u0003\u0000\u0001\u0004\t\u0000\u0001\u0000(\u0000|\u0000\u0003\u0000\u0001\u0004\t\u0000\u0002\u0000\u000e\u0000�\u0000\u0003\u0000\u0001\u0004\t\u0000\u0003\u0000L\u0000�\u0000\u0003\u0000\u0001\u0004\t\u0000\u0004\u00008\u0000�\u0000\u0003\u0000\u0001\u0004\t\u0000\u0005\u0000x\u00016\u0000\u0003\u0000\u0001\u0004\t\u0000\u0006\u00006\u0001�\u0000\u0003\u0000\u0001\u0004\t\u0000\b\u0000\u0016\u0001�\u0000\u0003\u0000\u0001\u0004\t\u0000\t\u0000\u0016\u0001�\u0000\u0003\u0000\u0001\u0004\t\u0000\u000b\u0000$\u0002\u0010\u0000\u0003\u0000\u0001\u0004\t\u0000\f\u0000$\u00024\u0000\u0003\u0000\u0001\u0004\t\u0000\u0013\u0000$\u0002X\u0000\u0003\u0000\u0001\u0004\t\u0000�\u0000\u0016\u0002|\u0000\u0003\u0000\u0001\u0004\t\u0000�\u00000\u0002�www.glyphicons.com\u0000C\u0000o\u0000p\u0000y\u0000r\u0000i\u0000g\u0000h\u0000t\u0000 \u0000�\u0000 \u00002\u00000\u00001\u00003\u0000 \u0000b\u0000y\u0000 \u0000J\u0000a\u0000n\u0000 \u0000K\u0000o\u0000v\u0000a\u0000r\u0000i\u0000k\u0000.\u0000 \u0000A\u0000l\u0000l\u0000 \u0000r\u0000i\u0000g\u0000h\u0000t\u0000s\u0000 \u0000r\u0000e\u0000s\u0000e\u0000r\u0000v\u0000e\u0000d\u0000.\u0000G\u0000L\u0000Y\u0000P\u0000H\u0000I\u0000C\u0000O\u0000N\u0000S\u0000 \u0000H\u0000a\u0000l\u0000f\u0000l\u0000i\u0000n\u0000g\u0000s\u0000R\u0000e\u0000g\u0000u\u0000l\u0000a\u0000r\u00001\u0000.\u00000\u00000\u00001\u0000;\u0000U\u0000K\u0000W\u0000N\u0000;\u0000G\u0000L\u0000Y\u0000P\u0000H\u0000I\u0000C\u0000O\u0000N\u0000S\u0000H\u0000a\u0000l\u0000f\u0000l\u0000i\u0000n\u0000g\u0000s\u0000-\u0000R\u0000e\u0000g\u0000u\u0000l\u0000a\u0000r\u0000G\u0000L\u0000Y\u0000P\u0000H\u0000I\u0000C\u0000O\u0000N\u0000S\u0000 \u0000H\u0000a\u0000l\u0000f\u0000l\u0000i\u0000n\u0000g\u0000s\u0000 \u0000R\u0000e\u0000g\u0000u\u0000l\u0000a\u0000r\u0000V\u0000e\u0000r\u0000s\u0000i\u0000o\u0000n\u0000 \u00001\u0000.\u00000\u00000\u00001\u0000;\u0000P\u0000S\u0000 \u00000\u00000\u00001\u0000.\u00000\u00000\u00001\u0000;\u0000h\u0000o\u0000t\u0000c\u0000o\u0000n\u0000v\u0000 \u00001\u0000.\u00000\u0000.\u00007\u00000\u0000;\u0000m\u0000a\u0000k\u0000e\u0000o\u0000t\u0000f\u0000.\u0000l\u0000i\u0000b\u00002\u0000.\u00005\u0000.\u00005\u00008\u00003\u00002\u00009\u0000G\u0000L\u0000Y\u0000P\u0000H\u0000I\u0000C\u0000O\u0000N\u0000S\u0000H\u0000a\u0000l\u0000f\u0000l\u0000i\u0000n\u0000g\u0000s\u0000-\u0000R\u0000e\u0000g\u0000u\u0000l\u0000a\u0000r\u0000J\u0000a\u0000n\u0000 \u0000K\u0000o\u0000v\u0000a\u0000r\u0000i\u0000k\u0000J\u0000a\u0000n\u0000 \u0000K\u0000o\u0000v\u0000a\u0000r\u0000i\u0000k\u0000w\u0000w\u0000w\u0000.\u0000g\u0000l\u0000y\u0000p\u0000h\u0000i\u0000c\u0000o\u0000n\u0000s\u0000.\u0000c\u0000o\u0000m\u0000w\u0000w\u0000w\u0000.\u0000g\u0000l\u0000y\u0000p\u0000h\u0000i\u0000c\u0000o\u0000n\u0000s\u0000.\u0000c\u0000o\u0000m\u0000w\u0000w\u0000w\u0000.\u0000g\u0000l\u0000y\u0000p\u0000h\u0000i\u0000c\u0000o\u0000n\u0000s\u0000.\u0000c\u0000o\u0000m\u0000W\u0000e\u0000b\u0000f\u0000o\u0000n\u0000t\u0000 \u00001\u0000.\u00000\u0000M\u0000o\u0000n\u0000 \u0000J\u0000u\u0000l\u0000 \u0000 \u00001\u0000 \u00000\u00005\u0000:\u00002\u00006\u0000:\u00000\u00000\u0000 \u00002\u00000\u00001\u00003\u0000\u0002\u0000\u0000\u0000\u0000\u0000\u0000��\u00002\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000�\u0000\u0000\u0001\u0002\u0001\u0003\u0000\u0003\u0000\r\u0000\u000e\u0001\u0004\u0001\u0005\u0001\u0006\u0001\u0007\u0001\b\u0001\t\u0001\n\u0001\u000b\u0001\f\u0001\r\u0001\u000e\u0001\u000f\u0001\u0010\u0001\u0011\u0001\u0012\u0000�\u0001\u0013\u0001\u0014\u0001\u0015\u0001\u0016\u0001\u0017\u0001\u0018\u0001\u0019\u0001\u001a\u0001\u001b\u0001\u001c\u0001\u001d\u0001\u001e\u0001\u001f\u0001 \u0001!\u0001\"\u0001#\u0001$\u0001%\u0001&\u0001'\u0001(\u0001)\u0001*\u0001+\u0001,\u0001-\u0001.\u0001/\u00010\u00011\u00012\u00013\u00014\u00015\u00016\u00017\u00018\u00019\u0001:\u0001;\u0001<\u0001=\u0001>\u0001?\u0001@\u0001A\u0001B\u0001C\u0001D\u0001E\u0001F\u0001G\u0001H\u0001I\u0001J\u0001K\u0001L\u0001M\u0001N\u0001O\u0001P\u0001Q\u0001R\u0001S\u0001T\u0001U\u0001V\u0001W\u0001X\u0001Y\u0001Z\u0001[\u0001\\\u0001]\u0001^\u0001_\u0001`\u0001a\u0001b\u0001c\u0001d\u0001e\u0001f\u0001g\u0001h\u0001i\u0001j\u0001k\u0001l\u0001m\u0001n\u0001o\u0001p\u0001q\u0001r\u0001s\u0001t\u0001u\u0001v\u0001w\u0001x\u0001y\u0001z\u0001{\u0001|\u0001}\u0001~\u0001\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0001�\u0006glyph1\u0006glyph2\u0007uni00A0\u0007uni2000\u0007uni2001\u0007uni2002\u0007uni2003\u0007uni2004\u0007uni2005\u0007uni2006\u0007uni2007\u0007uni2008\u0007uni2009\u0007uni200A\u0007uni202F\u0007uni205F\u0004Euro\u0007uni2601\u0007uni2709\u0007uni270F\u0007uniE000\u0007uniE001\u0007uniE002\u0007uniE003\u0007uniE005\u0007uniE006\u0007uniE007\u0007uniE008\u0007uniE009\u0007uniE010\u0007uniE011\u0007uniE012\u0007uniE013\u0007uniE014\u0007uniE015\u0007uniE016\u0007uniE017\u0007uniE018\u0007uniE019\u0007uniE020\u0007uniE021\u0007uniE022\u0007uniE023\u0007uniE024\u0007uniE025\u0007uniE026\u0007uniE027\u0007uniE028\u0007uniE029\u0007uniE030\u0007uniE031\u0007uniE032\u0007uniE034\u0007uniE035\u0007uniE036\u0007uniE037\u0007uniE038\u0007uniE039\u0007uniE040\u0007uniE041\u0007uniE042\u0007uniE043\u0007uniE045\u0007uniE047\u0007uniE048\u0007uniE049\u0007uniE050\u0007uniE051\u0007uniE052\u0007uniE053\u0007uniE054\u0007uniE055\u0007uniE056\u0007uniE057\u0007uniE058\u0007uniE059\u0007uniE060\u0007uniE062\u0007uniE063\u0007uniE064\u0007uniE065\u0007uniE066\u0007uniE067\u0007uniE068\u0007uniE069\u0007uniE070\u0007uniE071\u0007uniE072\u0007uniE073\u0007uniE074\u0007uniE075\u0007uniE076\u0007uniE077\u0007uniE078\u0007uniE079\u0007uniE080\u0007uniE081\u0007uniE082\u0007uniE083\u0007uniE084\u0007uniE085\u0007uniE086\u0007uniE087\u0007uniE088\u0007uniE089\u0007uniE090\u0007uniE091\u0007uniE092\u0007uniE093\u0007uniE094\u0007uniE095\u0007uniE096\u0007uniE097\u0007uniE101\u0007uniE102\u0007uniE103\u0007uniE105\u0007uniE106\u0007uniE107\u0007uniE108\u0007uniE110\u0007uniE111\u0007uniE112\u0007uniE113\u0007uniE114\u0007uniE115\u0007uniE116\u0007uniE117\u0007uniE118\u0007uniE119\u0007uniE120\u0007uniE121\u0007uniE122\u0007uniE124\u0007uniE125\u0007uniE126\u0007uniE127\u0007uniE128\u0007uniE129\u0007uniE130\u0007uniE131\u0007uniE132\u0007uniE133\u0007uniE134\u0007uniE135\u0007uniE137\u0007uniE138\u0007uniE140\u0007uniE141\u0007uniE143\u0007uniE144\u0007uniE145\u0007uniE148\u0007uniE149\u0007uniE150\u0007uniE151\u0007uniE152\u0007uniE153\u0007uniE154\u0007uniE155\u0007uniE156\u0007uniE157\u0007uniE158\u0007uniE159\u0007uniE160\u0007uniE161\u0007uniE162\u0007uniE163\u0007uniE164\u0007uniE165\u0007uniE166\u0007uniE167\u0007uniE168\u0007uniE169\u0007uniE170\u0007uniE171\u0007uniE172\u0007uniE173\u0007uniE174\u0007uniE175\u0007uniE176\u0007uniE177\u0007uniE178\u0007uniE179\u0007uniE180\u0007uniE181\u0007uniE182\u0007uniE183\u0007uniE184\u0007uniE185\u0007uniE186\u0007uniE187\u0007uniE188\u0007uniE189\u0007uniE190\u0007uniE191\u0007uniE192\u0007uniE193\u0007uniE194\u0007uniE195\u0007uniE197\u0007uniE198\u0007uniE199\u0007uniE200\u0006u1F4BC\u0006u1F4C5\u0006u1F4CC\u0006u1F4CE\u0006u1F4F7\u0006u1F512\u0006u1F514\u0006u1F516\u0006u1F525\u0006u1F527\u0000\u0000\u0000\u0000\u0001Q�K(\u0000\u0000","glyphicons-halflings-regular.woff":"wOFF\u0000\u0001\u0000\u0000\u0000\u0000@@\u0000\u000f\u0000\u0000\u0000\u0000sH\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000FFTM\u0000\u0000\u0001X\u0000\u0000\u0000\u001c\u0000\u0000\u0000\u001ch+�\rGDEF\u0000\u0000\u0001t\u0000\u0000\u0000\u001e\u0000\u0000\u0000 \u0001\b\u0000\u0004OS/2\u0000\u0000\u0001�\u0000\u0000\u0000F\u0000\u0000\u0000`i\u001el�cmap\u0000\u0000\u0001�\u0000\u0000\u0002~\u0000\u0000\u0005.�/V�cvt \u0000\u0000\u0004\\\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0004\u0000(\u0002�gasp\u0000\u0000\u0004`\u0000\u0000\u0000\b\u0000\u0000\u0000\b��\u0000\u0003glyf\u0000\u0000\u0004h\u0000\u00003�\u0000\u0000[X\u0001��\u0016head\u0000\u00008,\u0000\u0000\u00004\u0000\u0000\u00006\u00008=�hhea\u0000\u00008`\u0000\u0000\u0000\u001f\u0000\u0000\u0000$\n�\u0004xhmtx\u0000\u00008�\u0000\u0000\u0001\u0013\u0000\u0000\u0002��\u000e\u0012ploca\u0000\u00009�\u0000\u0000\u0001�\u0000\u0000\u0001����@maxp\u0000\u0000;@\u0000\u0000\u0000 \u0000\u0000\u0000 \u0001.\u0000�name\u0000\u0000;`\u0000\u0000\u0001�\u0000\u0000\u0003|Ԗ��post\u0000\u0000<�\u0000\u0000\u0003R\u0000\u0000\by�cQwwebf\u0000\u0000@8\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0006K)Q�\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000�=��\u0000\u0000\u0000\u0000��\u0017�\u0000\u0000\u0000\u0000����x�c`d``�\u0003b\t\u0006\u0010`b`\u0004�[@�\u0002�1\u0000\u0000\r�\u0001\r\u0000\u0000x�c`fid��������t���!\nB3.a0b�\u0001�\u0003�\u0010�\t�\u001d�\u001d�������?k��/\f2�S\u0018x�ŒHJ\u0014\u0018\u0018\u0001A�\u000b�\u0000\u0000x�͓?LSQ\u0018�ϣ-PB���t�{� \u00150�\u0010�0\u0018K*�N��\u0011\\h�A\u00194\fj����`\u001cX\u00101\u001aYM�t�Ѹ����\u0000�\u0007NN�h����n]�\\L|ɯ��n��;}�+�\u0018�\b9J���\u001e\u0002&D�p5\b�#��h0��q�xM\u000b,:�b�l�����d[��g\u001a$\"qII��Ƞ�eX\n2!E����˒F�VS��v��\u0001\u001d�a-���hI�uQ��P.�\u000e\u0016\u001d�\u0019{v��$!V��Wr�yDƸ���ʜ,h�1M��V�֬�4��:�E��9]�%�sy��^������Ǜ\u00177\"�Mk���6e[l���j\u001b3e���4�f�|3_�\u0017�a��+撹`�M�L�qs֜1'M���1�5��^���O�z�Qu�r�\u0017>\u0019�{_\u001d�ӽ(�R���\u0015�/��]\u001e$�I\u000f�ӫ\u001e4\u0002��\u0007M@[�A3��{p�n���C(q�Oќ\u001dq�9H'\bא�YB��LI7�a��{� 3�\u001c�3\u001f��=์�\u0002�\tz�L0���\u0007��)2�|��LJ��\u001c=G����\u000bd��2��M�:_��Yk�y������ivVv�\r����7i�W�칟f�\u0000�m��u��\u000e����ߥG���fu�\u001f�쮮�C���u~D����Oiw�E��4��2�K`\u000b\u001epÚ¾?\t\u0001���B����\u0010�\u001d�*\u0004xO��\u0000\u001f\u0010�H�\u0000\u001f��\u0010�\u0013�\u001c\u0002|�\u000f�\u0000kt&\u0004X�����gԃ*7�{�5�c��h\f��Q�w��o��\r�\u0014�H\u0000\u0000\u0000(\u0002�\u0000\u0000\u0000\u0001��\u0000\u0002xÚµ|\t`Tչ�=w��Y2���m&���L\u0002�\u0004f23@H�a\u0013\b�\u0016Q\u0011pD\u0001��\u001a@\u0010\u0015qm� \u0005�`��\n\u0005�HQ�`_��^Ô¶j�U�����V���k+d.���;�L\u0016���{���9�;���wη�˰L%Ð[�2�cD&�\u001ca�G\u001c\u0015y�|�9���#�r,\\2�q�X��GE\u0003�\u001cq����Cq\u0004b\u000e%\u0019����_���\u000f*��\u0019¤�\u0014��712�Vd1��;�PR�+����9����MZ��M/���҄�\\\u0012�\\����c�\u001f�\u0003�kF����\u0000�E��ZH\u001b\tï-{��P\\\u0004���a&����X����\f��_\u001e���?\u0019�I�!�LĢ�G��DX��\u0015\u0015Q1H\nW�u\u0018\u0007\u000f\u001b\u0015��\u0013�b�K\u0017,���U\u0015��wS+�3��\u0002\u0013�<�i�\b����\t��a�\u001b\\dmJ�0�\u001d�„;\u001a\u0007ڊ\u0006ω\u001fZr�7\"\u0007W����}s7�t��d��Bwxn}*�\u0000�T��7�ǁ��O|S�:n#{\\�`��OI�?�_�x\u0018Æ¥D\u0013ñš ¿\\�\u0010�Ar�V\"�=G�OV\u001f8�:\u0012\u000e?���ﰍ�V����V?k]���;\u000b-Ö¥c���d�_�o\u0007ڱ���1��\u001a\t\u0010 \u001f\u0011�J��/Me���\u001d|�֑\"m���6�oJ�f��mN�'�\u0014�Ue\u0018��g�?�_�_b�\u0000�\u000e����j\u0012,�\u0011#\t\u0011.\u0010�'ݘ���Va��\u0015���N?��I���\\�\u001f����_й�>�����k\\���dǹ�o\u0007�L@��o\u001c�\"{��;? a�\u0002\u0018ZR�\u0014\u001bd��3X���\u0001_\u001ca���\u0011\u0014�D��d=�;j��\u0000t��2��s#'L��t<2e�\f�)m7Y����)W5sϤ�a��V&�U���$��d�^�\\��\u0014GA���UE��\u0001�<��|�c���N��\u00143��x�6b(\u000f\u0011�) G\u0019#�\u0019��,a\u000f�q[IX\u0014������U�U�\u000f�\u0004X����\u0013�����'\u0004h\r��D��'�e�\u001f��ģ����P��o3�\u001f�\u0014B��Y\u001f+���\u0018��5pC�G\u0006�]}gsbԚ�����m\\3j��U��pϏ��\u001f4�u��5��st֬��ܻf���\u0019���@Z����q�v����\u0000T����⊹\u0014����ڽچǴu䷏���w:],k�\u001a�K;O��\u0014��J�\b�@7\f7�g.�P�\u001f\u0012����\u0007�6{��\u001fM��_?\u001d�c��>������~���_�n#9|�\u0002>\u00006 ��`\u001d�ID=^�6�%�Y�\u0016hh�g{C`˖-�����u������\u0014�8}���mf�\u0006pd\u0018�R�τ��L5\u0013C~Q\u001c�_�\u0005`���,�u��\u000f�I�gØ®\n�w<��m���u�\u0014���������\u000f��K��&`[�E��?�\tg.�\u001c��7\u001b�X�4 v�O�<\b���)Q����K�/x%�E�z%�&y��ܻ��+��E�׹Ͻ�mW(\u0000ԍ}���������l��Hn�@��_-\u0001L�����~�+��=\u001cH[�ם\u001f���ዲW�\u001e�k�'�\fc���\\��Kq�\r���\u0001�\u0004�\r0�<P�\b�.���s(�m�J��x�\u001e\u0007����K��Qz[��{_L\u000f]�x�r�S~\u0000H8&\u0000�($�\u001fsю�7�Q�����\u001dz�%�\u0012~,?\u0016��\u0000�,#I\u001a�l$�J��v��\u0014�\u0003)���%=o;�\n��\u001e2 Ô¿\u0014��\f0�\u00031�/���x,\u001e��DHo�\u0005�\t�\u001f�\u000f���d8�R����ϰN�\u001b�'(\u001e�_,�DXCs�_���D\u001a\u000bw��[�\u0007�䑁����D$\u0010J'���oU\u0015e #�;am��J\n���K�C��d��\u0004AŖG�x�3)Q]�\f\u0015\u0018��WJ���!���kw���[��}s���.��\u0017ױ���,�^{��V(�j�/���g����.^|n�Ju�\u0014C����Zߙ��\u0011#��\u0000?�]U���\u001f��\u001b�Tr\u0001�Q\u00162EL1�����c����'\u0016m`\u0013\rDN$C�\u0004\\�k��`5I\u0006Cb(\b��r\u001bk���A�E\u0003\\J�R�SJD��\u0004��\u0019��4�\u0005���\u000bV�\u0017W\u0007\r�j�����r\u00056�uA�:������\u0007�A�\rz\r!\u001a\u0016\u001e��L�\n���\r��Yk\u00017uЈ\u0007�ACJ��_t���\u000b��l~\u00117-\\��\u0010\u001e���\f\n\u001ab\u0003�\u0007�WO�\n�:dk!7�z��Bu�\u0010\u001c\\�T\u0005\rC�ƒ��i\\Q>ۺ������if�V�˗�v�\r:��K\u0012R�8#Y�fy\\���\u0018W$A�\n�Z���b�?n#q�lV���f��D\\�U$\\U�Be��X[�\u001e\r7:W�?��f���Vs�dKU��Vaa�\u0007U#FT�H\u001d���s\u0015�\u000b\u0012�\u001e�\u0019\f�����ᑊ�aL�6�\u001a�NAy`�ٍ\f\u001dS�\u0018��7���jÕ¹B\u0011n�f�f5w\u001eF\u001c�o��\u000eh�z��כ�(���x��\u000fñ‡€¯\\�{1e����e\u0015����\u0019%�$\u0005x�\u00052�8���>юk����ޠ\u001d��32�3m!���\u000fu�\u0010'��|��\u001f\u0004%\u001bt��\f#�\u0001Wʿ��)$������}����\u001fɑ7����ɑ��h��b�+*�$\f��\u0005P�;�Ϻ���A2\u0011��b\u0004�\u0016T>�_��U\u0012V���]G�tYI\u00174\u000b`\t\u0014�>\u0003�A�<\u0002\u0010d�\u0007O�>V��5����f\u0000\u001c#�_x�\r7�ɜXV;���d槚����\u001d*��\bO�}�\u0017\u001f@�����[�?}E?\u0018�~�\u0015\t��\u001d�ؕ��m���#-��2\u000e���eY�(\\LV�1�+�\u0019\u0001\u000e�`�r\u000f��.P�$\u001cU�\u0005dD-����R������]b\n���Y�D�qE���~�\u000f�\u0007�F�\u000e>{�l��Ȯ�%�\u0015nCYЎ\u0016��8� \u001a�n\u001f\u000b�,�s�E�\u001b� >��1Ҷ��S��\u0012�T�\u0019VJXS�a�]��p�k7Ca�\u0019\u0005�� \u001a�~0(��$؃�\u0006&�1\u0018y��E\u0007{|[�C�]܁k��'������ʶM��\u001c6{�V��[�\u0019���\f����\u0019\u0017p(\u0007�*�l\u0014��#��������B�\u0018�j�R-�Bz�\u0017]�2\u000f�c�F\u0015���܃�>P=���cb(/\u001cV\u0012�\f�����z���^�vc�Cw�u�xh���K\u0000\u0003\u0004<m����4f�f\u001918_)\u0016�P~���q��\u0010�**\r#,�QV\u001a��SO�|${\u001fp��\u0015.HT��uϷ�������FX\"��M �?l���\u001fX���Y��m��\u0006�+|\u0007ԇ���:��\u0016�獕+߸�޽��`E�\u001a\u001es�\u0019}n�|3w\u000et&X+\u000b�\"8��,��\u0003�\u0007\"\r�\u0010��Om\u001a�)�Ǖi-0_T5�\u0019��ŵPX�\f,!�\u0004�(�`>��Hۘۖ����1,�Б�<�X��c:\u001d�/��\u0001��\u000b$\u0013\u0013H��%�\f l�\u0004\u001c`�u��~a��f�!�]%�� h@�G\u0014�\u000f��~x�h�ȼ�_~iG\u001e\u001b\u0000ᢒ6l��\u0003���n�v�s|\f\u0012��\n\u0013`\u00061��=�\u0002]`\"�ЌK\u0001�\reN,�P$X�\u001c\n(q @�\b�|��\nhv`\u001d��Lʨx�8� \u0005�]\u0019�a\r�\u0013~�_Sh�i\u0016`%�T\\�;�Q��cc\u001dړ�\u0014-��R�\u0012k����\u0003n,�/\"ww`5\u0012��T��b�`\bHgpdSz�❊Ej����h��G��w�f�'��A�\u001c0������\u001f�\u00059iSU<�\bb���|{g\u0001w.�w�X*e����y�d�\u0017Ѫ7�T&s��P�\\4�eO\u0014�;�1�F�9���f���B\n��\u001b�M;������\u0000=\u0000�\u0013ޣ��QFg�Q�>Fra>�0aV���i�(9\u0001��V���C\u0016��\n�=\u0016��s�!]k\u0018� \u000eѡ���$��Tڙb?�\\w\u0004���Υ:\u000bxr��Y\u0013P\u001e˸�\u0011.B@�\u0018DNV\u0002\u000e�\u0011`\u001a�<�졒���\u0013T����c�&��dg�U�SI-2�\n\u0003�Z\u0015dͦ�~��\r�.\u0013�`rqud.Nv��u7�G�*{\\U�m_7Y�&<���S<��</g\u0018%\u001e\fEH5!~b\u0010��[\u001e!\rDA4$��]|{y��g0h�\\��((�\f��Ȳ��\n�u�����qm���\u001d�d�+b���v:\u0007\u0017[�D��ѓ\b���As\u001aY#9�U�'�\u0006������*ѱ�BU\u0012Ċ��x8\u0003=��I\u0017�B�#F���~\u001aHR�׫ +�fK^ñ˜šï¿½ï¿½L�,U���SQJ�_i�u��o^s����⏴5y}�2O�,��Z���:�\u001a1V�Ֆ��|�\u0018�\u001eX;i��έ\u000bf�����\r�\u0005�i���\u0011���%�$�J uBqn\u0019�\f//\u0014�{M��i�I��=k�\u001cg0\n|��<(`�t��f�a�0qT57mh��pd\u0018�՞��r�0�%��k\u0003�\u0003�'\u001d �9\u001ct��\u0018���̯�\u0011�K\u0011�\u001cfjKJ-W�ݎ!;��\u0013��{\"�P���$#�y8�JE���Q\nyq��`�>�l�>:���\u0017�-`\u0003}a��\u0012�Q�\u000f%�\u0017�Mwuw�\u0011�\u001e�\u0011Ep\b\u0016f��f�\u0015�d\u001fÍ´y�X\u0006��/����A_\u0002r7�\u0012��\u0013���/)G�0���\u0002�u�Ig\u0001�q�$�\n7\u001fЛ2(\n{�K���o���\u000b�)����s_r�/���\u0012������a����W,����\u0012��0�Zk��]_R�~A��Nu�E��NuU��Nu�\u0006������ގ^x������+��\u0016�y����I�F�\f�@M-�\u000f}�/Ë»}&8\u0001z\u000e��^I\u000e���Q���!��pyW�\u001f�\u0005z�\u0000f83�Z���>���Q�\u001a�,|���\n\b\tw�J�|n�B}�\u0017ܾ΂ӧ�a��G\u001d�Q\u0018*@��z���u�?��|bj���\u0015&�\u0001u\u0019A����/Wo�*zH��\u000b�D���u��K�\u0000���^����Kt�^�����c���R�t{�\u001dtx\u001b��\u001b0���pi�Y\u001a\u000e�����\u001a}�{2�J�<\n�t�nuw�@MԟP�\u0017\u001a�QI!D\\\fz�j\u0012��\u0015�.�w\u0016\u00159�fW!(S� \u0003&������-�'��e�\u0016,�.�qB����w�z�\u0003\u0012��Ɉ��.Ɨ�/��\u000f]!Q\u001fqS%'‡�q0�\\�DM\u0010c�\u001e�y��kƏ���a��\u001b��}`Ú7�,}���w<���o_���Fc����L���u��/=\u0006��,}���Y\u001f���CB\u001c�\u0006���c�4�_���\u001b�y=7\u001b�v*��dx��P��3��$�۴��\"l(�h 4�)�\u0003��S�Lj\bL^�Ĕ�?�w���o\u001f#z\"&\u001bk4^��Y��V���Һ��;\u001f\u001cs��T-o����y����';�\u0018<\u0013*�o��-�1m��EÙ±9��\u0002���\u0004�\u000b\u0000��K�\u0019 ^\u0016\u0015ݣ��XG�$\t\u0011~�\n��\u0015�?������>�6Y^�\u0003�4��ȅ�Zm�7����R��\u0015�JVhw��V\t�#C�\u000fj�P���{f��^�-e�~���\u001f%LD�Wts�l\u0010�ݭ�A��ʭ�Xϕ\u0012���y�}`��l�s�-�N���Q�G\r3�n��\u0006����gq�Z֜��~2�Կ�Z�بe��5.#!L\u0017>�\u0000\u001f/ZC=����\u0006�/��\u0012\u001e1���t\u0017)����\u001ef�}'���\\\u0016�1\u0012�|D���a\u001a��}ؘ+\u0011�K10]�\u0004݅�\u0011#�q�mT�����A�\u0003���~���S��Sp]yJ�٨�����s7B_��RH/\u0007��\u0000��%G\u000e\\ ��\tY��+i\u0013ɟ���4�\u0003`�\u0003�A�\\�\u0005\ta\u0015���\u0015R�\u000fg`\u001a(Lc\u0006D�5�F\u0011�����\u0007��a1����\b,�t\f�JeR�F�N��\u0012�\u0015�}��N��\u001d��/W\u0010zi�`�S�\u0014�?g����W�`�\u0002�Ӭ2�?$���yI.@��w|\u0013�\r�\u0016���%1$h��$��&�\u0017e)�tF�zi�T=&/PBg���y�\u000b�����>�}��$���0����(\u001c���,�+�ʉ#��\f\u000eD=���3�o�P����vd�#.��\u0019����\u000e��%i~����Sd�_���_�\u0002�8�8�/��O��@-L��\u001c�E�%#\u000b���Ǝ�H$S��\u0007g+\u000b+\u0018�?�?K��\u001e\u001eXA�I\u0003�\u0006%,u������kǵ�{��i*����~��\u001a���jO���p]���\u000b�#ls\u001fX�\u0011\u001a\u001f�\"~.Y��r2�\f����fn��֦Mom�c_��d�:6mfz����\u0006,MH�޽�g�a�&���\u0005�p�a�#���.���&@��\t9V������\u0019X\u000b$j�0�2�� Zy\u0019׫�D\u0017���K�CF\u000f)^�`\u001bo\u0013\r��\u001ay+��\\�<Y���\u0005�﨤�ę����XSe���\u001e�`1�\u0002�\u0012��c�\u0013^.�{4�ꉯ�w�F\u0000��\u0007<����~PCW\u0019��}Y\u0002}Z���-\u001bПSDS�$����?�)d�X=���$\u001f+\u000bR\"\t��\u0014Ê \u0006�\u000b�;η�Z��5�W\u001f��8a\u001b0f�x5Ѻԩ�z{�i�p�xu���7�,�m\"{\\]�_9y�����\u0011��H櫪{��c�_\f��:a��ګ\u0005K�V�/+�E��Ii��\u0013[�#󊡈\u0003\u000f=�,�w�XJEN�-6&�/l�U{�\u000f���v�\u0006���A��{�U�k&\u0011\u001a�p\u001b\t�X��nm&;4u���8�~mA����4���M�v�v2:�Z�g\u0007[2k��T�5��!�\u001d�oO�nj\u001e\f\u001bj�\"?&'�\u000e���X\u001b3��&F$�ûV\u001b��}\r�g\u0002�O��B�\t��Z��\u000e9�}\u000b��\u0002\f}V�gd�\"wVk�F�J\u0012�\u0006N��\u0019�\u001d�(��\u001chFI\u00074C��6�\rc\u001f��n��gd\u0010[1��I\u001b�z��<b�js�ؠ�\u0000�D��\u0007Yq�(�[���l&���\u000fhg��;\u0012��3����\u0011i�Gt>�3~~3�9\u0003��/C�B���L_\rm�\u001c\u000e��w7�\u0010{f\u0003�\u001aڤ��Z6h�n\"!�]�1��T��{kÓ®\u0004\u0015���GP�c�\u0016������kw�o\u000e�\u000b�_�\u000b���ߝp�������t�)�'\u0012q{�n�M8ޗB)���\u0019D�޵h�� �j���6b\u0010�\u0012��\u0006z���\u0012�z�c����W�r��9�Q�\"g�Ʋ���P���<  >Q��:��eoCg8�*��W\u0004k��M���+w:��qj<r\u001b�ɝw(4�і�)��9#����&��ո�IK՜a�\u0007W_z�&��k�忀����(�ē\u0011��$녤���\u0011�o#\u0004�����ơv\u000f/\u001a<��;�<N\u001a\u001a�^|�\u0013���%<��\u0015��^{�\fۍ��cc\u0017,������n����N�r��Ш䚗�#�]��vĬ� �0�K�k���\u00153\u001a\\��+��`�\r�Ό����0v{Y���n˟шr��O����|\f,�\u0006f\u001c�U�\u001a�#B5 i��\u0004����I0Ʋ)\u0012I���]�9\u0001\u000b&��P2v����ڶ��>el�������{WM�&|��$������=�h�8jе3�.\u001d�xq���6W�*�̜?��z���s*1Z��^k��]W{���\u000f�\u0019^8�\u001a�\u0016�5~�A���yC�o��\u001b��`~\u0005�C_\u0018�K;�e\u0011f(F�@\u0019\u0007�ig0N\n��E�N\u0006CIX�\t���ȉ\u001dj\u0000f\nq�j��\u0016�����!SW_��\u001b\u0011 �(\u0012\u0003so�6>�\u0019T]>�UM\u0016\f\f��/���\u0004�n�.��\u0004t�Z�\r�a s�t��k�ںz�؜�_i�Y�pQ�V[0\u001al(\u0018��3��Ė�n��M�}˱?�Yr\u001c:S=�dz1\u001d�o�^��\u000f�:�D��\u000fzl\b�\u0014��\u0013\u0000�C1�~��T\u000b�@��^�\u001e�N���9���#/���} \nǕ�e�����e�1��W�A�B\n\r\u0010��H$:���R>��b(��\u001e�j�\u0017\r�4&\u001d\u0002='���������6O�Cۙ*r�{��(��t\u0014xldi��f�g�\u000f�C�U�|�����zFJv���F�4�s-\u0011?��C�\u0010+\u0019Cf�77�S��\u0013V��\u0019\u0013c�� �!�X�H�B2�H�\u0000W~�K1���~� �����lK\u000f��<�r���o�o��)�,�_e�O��u�����W���������LL��5�ï�F)�\u001c\u001ay�C�����7��~0\t�P�UlsUm66B�50\u000e7H�\tT.��/���#���=��To\b��\u0011=ȇ��$l(\bM���\u001f?yÓ¤P�\u0001n\u001d�B�@mU��\f�\u0011,,�+���\u0003��_�bj,6uŒ��\u0007�\u0017W�]Wh6�K���H�#�\"4{�����[�����L�\u0007��Q���d��>dG�_��,��\u001d˜�_��Ln�����l�ﵧ�=\u001f�aG}�*��\u0018\u0003\u0019Τ�V��y4�ơ�d�\u001by?��\u000fw�����Sd \u0019��O�\u0018~�����\u000fH\u001bÛ¬j-�\u001fI1\b�b폀;JCl\u0017h�\u001f\u0000:Eu[\u0006-\u0019��\u000e$t\r�T�g�=\u001a%I0OB\\0����/�����i6��,�\u000e@��\u0000o��������\u0006\t)�f-��\r\n�|;�u6���(�\u0014Տ>��U\u0007x�o\n���\u000e)G\u0017l��ڀf\u0019��I\t�����z\nTz4�7;��iNj�\u0007z�Sx\u000fÞµ`漋sd_U$�(䴚yUO��4��1T�\\\u0003\u0000R4+��\b0�\u0006=?\u001e#>F�J%�S��\u00002��)�m�\u0015(c��}\u0004P�\u001a��\r�~\\A炂^\u0005\u001a��5I�ɓ�J��o����8S��N�.�W�\u0018f\u0017%�WzH�p�{\\\u0001�Х_�\u0013��\u0000���Ll���\u0013\u001a$�+R�Uz�\u0017M\n�yG�we�&�3!��\u000e��%3�ʃ\u0018���<V\u0018b4bC,4���1\u0011\u0006��H\u0011\u001eMP\u001c+���9�t��`5k��\u0004n���\u000f%����ha!o-/���tj�\u001f����\u0017;\u000f���\u00163)��4�E��7�\fj� ���\u0015%\u000f&�0\u0012�k����F\u0017C`��L�HʨX���a��\u0006}0������'0h�.��˱c�k~��'�.�{WN����&��\u0018]�R��>f\u0000�~���������\"�J@?�~��8Q�8��h 1\u0002�\b{<Z<�:�;U��~1\u0012��[\u0017Ò¾WR\u0017��\u0013\u0017�~8�#\u0015M�\u0006۽���O�\u0002Z���0�\\:0\u0015��*�\bO����n��\u0014Y��\u0016�\bL�n_-Ɖ\u0001\u001f\u0005(_��zVK��#�2�NO��BJ���~G)�]\t��\u001fU�\\�FJLJ�\u0001>��n|p��\u0007��n�*�=��\u0016]\u001f�`\u001d��Y�O\u001dÍ©\t\u0000K�\u0012�\t�d�\u0001���%e���ꩢH}�\u0003X���\u0000�(�z+Yb5;,ˬ�Y$2k鲙K7��6���L�ux�\u0004�7\u0014�^�;�v-I̼�\u0013y�\u0015L�fc\u0007��g\r�\u0016��i]��6Y{�\u0013ڛ��;\u0002��5�5d�T���^BXN�,y��\u0017gt��.^��7��6\u001b�0�\u0019\u0001}\u0010|�������\u000e�$dE�\u0012:G�\\D�0\u0000z����R��\u0000��7d��R\u000e�i\u0007lņ���\\~+��l]�o7_��$\u0010\u0001\u00106�f�U��5H`�v�=\u000b��Գ|~ȑ�4���\u0012�1�����ۥ�\u0015�o}�l3\u001b94�I�ʭ={\r��y\u001dq�Y��I���I���\r\u0005��w�\b\u0005A3F��EW��pI�\u0003J�\b\f�'�9<�P�\u000b\f�T)i�B�kl\u0001k��N1a�����\u0019?����Po<\u001aE��^z����n�,�͙\u0016���S�m7�J�i$3f?��׻�Y\bQQ�~�^lؠ�A�(�]4q��\u0002�*\u0005��:G\u0002�\u001e ��a��\u0014�\u0012���\u001e`\u0012�\u0010�TI\u0001�\u0019��Z�FB.�\f�\u0014\u001b2�]�7?�\u0019�����Ӓ\u001f��!��&Òz�]\u0019�r�����\u0019��?����y\u0016���F^�i;J=���\u0019�p���\u0000��\u0006C��\u0015���!�Y��q���f�\u0019\f݉�s� c�X\u0002�f-[\\�\\���`O^���_���\u001b��w']��c�r��\u001e����b�]���As�>A�d@��:\b��t���ơ.��\u0006�f\u0014gg�[Y(\u0000\\�\u0018�ֈ��\u001f������N?��9q�d\u000e����0G*\u0006H��p��~^E�u�P1G���\u0012�.]����B'xX�\u000b\u0018u5=���%Ý°\u001ffv��.��l�\u001db`.e�n�\u0018�\u0011P�|l�c���J��Y�T�\u000fw\b\u001aB�\u0004�\u0012�D�u2�f����P��$�b�$:Ȓ\u0011.�\b!�A͐At\u0003`x\u0004��'ZߗV��\u0006\u0016����lR=ǹ\b7D�+6�\u0013�� �`��~�7/�\r\u0018d ¢U�r?g��[�:�ue\u0011{��\u0013C\u0015��\u0015\u0016�.��`7T\u0016\u001b,�V���\u0003�`\u0012\u000b<\u0006{^�S6���\r\u0016��4i1\u001b\"�m�2��\u0018�%\u0015\u0016��39�ξ\u0004�gq\u001aL\u0004�$�lfY,_!+�J�c\u000b�.��˫���\t�q` �,�JoE})臢�LX��/��l~\u001e�\t:���\u0012��@�hr\u0005�*\u001d/�\u0003M�\u0001�B���\u0019�rH*\u0013��|N�+�\u001e\u000bw�sH�g\r�%�R!�,���{]��X\u001e��\\C�\u0005��G+飩�\u0001�ϳT���;`:\u0017�x�.�ޏ�i�C�z�zśl>zF�7����`\u001e\u0019��Y\u0010�6j{AR�k��\r�2G�Bߊ\u001b�^\u0004\u0014\r�\u0001V\u0013u�QoKR\u0016�\u000e\u0005����U������p��/�Q7�͙��z1��[��Ko�\u0017G�<\\/��\u001f�����̥ �\u001a��+e5\u0007P�R�%$�ƭ߇ĬQ�H&2U�5\u0015�-����+8\u0002�oy��<rY\u0019q\u0013c��7V[�\u0011�:VU��H)\u0016i��2,��_!c�ѲH�\u0012Vf\u0012�գ��i��[\u0011�U�\r\u0011������\b��T&���o\u0006�\u00165�P0\u0014�h7d�rb�� �`��Ͷ \u0013t\u0017#�k��`\u001f��}uܨ9��\u001b�,�y�}'�sF�{u�Q��Q���M\u0018Þº2v��\u001b��Gz�_0=�����q�΍����N�\u001f�{븱��&?�ԏm.\u00197���S\u0016�)�}U�g�m�Q>\u0019v�S~\u0015�#�\r�\u001d\u0019G\u001a�\u0002\u0019���P�\u0014X�`\u0015rh\u0015&\t�t\u001b\u0012e\u0002,\u0001g(!��Z�A\u001e��\r�H�ןl��x�\r#�?66v�+R�ظ�\".���b\u0015o���p�\u0017�;#q������F��#o��Q�\u0006\\�\u001b?�W\u0004|��78\u0007\rM6^?O�\u0011��=\u0016\u000e�\u0019/�$\u001d��F��\u001b��Y����\u0011�v\u001fx\"5\\���m��wf��ͮKqߗ�\u0011�/\u0012�:ǡ��lz�V�E\u0000��\u0018'�5�c�a�Ӽj\u0019\u0013\u0007\r�x��z1XJ\u001d!�\u0019�P\u000f</�#�s�v#\u001fPIv�[�F�����<�+��Y���W�9\u0013�#�����\rSR�1�G\f���K\u001c�\u001bow\fp���\u001ch�\u0005��V�\u0015\u001eqS�W\u000e�'MvÌ©\\:{ҍ62E�\u0016V\u000fkN^��(q�\u0014�\u0015nX�x�D�\u0012q�Վ��\u000b$�Ⱦ��v>0r\u001c��,���?�\\Ms\rY�\b�/\u0017�\u0017�M��ǰ\u0017�:e=\r�ٍ��3�1���k\u0011��\u0004�6�0Z5%4@~3\u001a�-\u0017$\r��aq\u0018\u0007[*�{��m2rՃ\u0016<s�+;GO�\b�tW\t��<w��~z2O����\u0017Je_-o\"�m�2���E\u0016�V�j�7;L�-�RC�E���U6\u001f]U3��+�����2g�Zɧ�\u001b��\t�����G�*��Yß±B��\f�~\u0002A<��z��@O`�n�|}�\u0003�\fz\r�\f�\u0018@�q2%��J0�Mӌ��C�\u001e^N�[�bq�+�v5i\u0003�T{����/���G�7�nT1�9��>و���\u0002<j�d��=Ú³3\u001e=�5�^����\u0006�A \t�r��j&E��ޔ�jO�w�b��4�\ryJ�&ڞ\u0005����\u000fnap�el����P4���F��Xe)�\u00022\u0000�Qj/x�\\x]�8\n9\u0003��@a�\u00040�������:M2r�DG�'�.�\t��.,U�z��]�4\u0000M���U�-�})l��e�N\u0016��\b.\u0003\u0018���`�=�\u0015�]jRN&��\u0013�Gm\u001f�������e�q=�\u0002s2j���S���f�1��;_)�\u000e�K\u0015��U�\u0016�^-\t�\u0000���>��^\u0004��U9mi��$l���z�\bn��.\u0006J^��^!�n\u000b{�;��S\\_\f\u0012����N^E��k��k���^�|,�W{�S��d�ދ�?J�@b���_�m�\f4���হ\bE�c\u0017����\u0018q(q��6l\u0007�!\u0003\u0004�\u0017jZ�\u001c\u001b5�,�K���o�\u001d�po�O�\f\u0010\u0019%b((*\tg2Q\r+),��\b�{L\u0001�.�m��|��f�+\u0000�O�l^��n�_����a2yHd� \u0018Ybg[]\u000e'˓#\u0016�)�&�����%��\tn��ŋ�?\u0005��.��1���t\r�>qeYzY�����X\u001dO�VO�C��E�ջh�>ݠ��g��\u0002��a��‰99I��\u0014�sS����ځ�Ȃ���ѻ�#��uBJo�������So����_Y���0�59�´�a�8ޚ��V���J�\u0015w>:��\\�g�\u0015���J,ϝs�:���\\4��O\u0014���#�\u0001�^\"':\u0013|��~阕;��b�'#p���f��\u0013�ٚ�^\u0011&ps.P\u001a�\ri����90u�:�\u0014��\u001f�{��&ܹB��z����|B\u001b�cW.D���ܥ�}|��4\u0007 ߅#G��Q�c,�\u0002�D��I\u000f�;\u0017t, '�wl��=Þ¹=\u00076?eA�B/\u0014���a�\u0007���94��Ǵ\u0013\u0007���\u0014z��\u0018!��p\u0000L+�%�\u000b�;�\u0006\\�I���\\��\u001b(���\u0012��\fC\u0013���\u0012ß®\u0005�W�����|�ȥ�Į�\"{���COy:Eq���\u000b���;����;�\u0006\u0012ܭ�s�H�ށ�ڋ(�\u0001�s��1uKOUw�h�[����\u000b����q�\u001e�d��8�V\u0017Jʄm�\u001e�\u0006�}���{�\u0002\u0017Ö®&\u000b`�s���ίf�T-�U`�|ږA�f���ӖC�m���s��:�\u0017�[���=�\u0011�:p���_�F��n>@\u001eS;}��{�s��~\"����\u0013\t\u0019Ç­[����Yr��B�j�Β�\rvd6q�q���j�\u00152�\u0014;W[��NfK\u0018ݔ֧���+��n�=��yư�\u001a)5�wb�]\u0002��qη�&�_:�G�i�;R�34�\u0013�沾�E\u0000�pF\u0007���A\u000f0\u0000XE�>�!�z����‡�J\u0017p!@o\u0011*tT�kїg�Ӎ;��\u0007U5=Z\u000f��D1�kO�\u0007�\u0013�!�i��cKI6�\u0010\u0018�+�.��|�\n\u000e|{E��qa\u0000�7�h\"\"n��\u0017�h����K]�;�cJ��޸R`\u0010�ۡ6�\r��W��h���J�\u0013�ꔉ�2]{՚�q\u0014�DRv����ǡ\u0004Ö·~\u0001^tw<�\u0006��M��B�eb�f)���Ј\t<!m\u0013\u000bɅ‰��>Y­�\u0014\u0012v�PKPk%�\u0006-C�\u0012�(Y;\u001f�>&�j���Lֿ�D�:��\u0012#\u001e!ETd)\u0000c�[�@OM@\u0012\u0002�TE\u0002��.�$Ù§S\u0018�hSIhqz?��\u000e*� \u0011��]�4�\u0002�S\u0005�/��>'S\u001f�o�…0~9�\t�U�\u000f<��Bu��6\u0014\u0003�쌝�\u0006�\u0014F�\\�\u0001�G\u001f\u0017k \tL\u0006\u0001b\u0005��\u0006�\r�D�������\u0016����kKK��\u001b\\?v��WmY���V���\u0015� ~�^��G�t,���:��uT[\u001f���֛׭���\u0018�>�2��\u0010˙���\u0011�_n�m��\f�\nXG\u0012e2\\�\u0012OBȄӳN�$���(2\u001a�n(J�;���g�L5��F��\u0015�w\u0013��D/�0ˬ�ύ\u0013H��e�'8�ez�C\b��y�W\r����J�lR�B^�˥�\n\n\u001e��ᲰJ{�\u001asӝ\u0005\u001dTG���B�]\\b2u}c��O���\u0012��f\u0012����C�\u0004`�#�\u000bw�2�t)n��cꎋ��Q�\t��.^�|{ \u000bϨ����V�is���z�\u001aĀ:���9�\u0006u�a�]����*\f�V�bt�\u000e;Í¡\u0011�{\u000b�=\t�7\u0017Ò¿^�g_�B\u001c4g\u0010�Ը�w���\u001a\u0002�\f�޾.=\u001a�F����U��p���������a9�\u0000�\u0002@�9kC�A\u0016�g꿎\u0007�2�k���%x��w����\u0007\u000fx�_0@ݦ\u000b\r���G1�j�RC��Ȝ��\u0002�?\u001e��\u00006��?Õ«MOV\u0017��f>�\r\"���~R��}���\u000b;�_�q�)��m�\u0003�r��Q5�(�N���O����Ԇ�İ�\u0006raCpX�jC�D�7s���\u001bf��k\u0001̍�2^�x>�����\u000b9\r��Ͼ�ըi��r�\u0007\u001e��o/��\t����@]\u0002��\u0001�@]\u0002w��������\u0018�'F0A\b�(}��f�–�A���)�+t9�\u0005��\u0003������(d��v�L�R�\u0010z:R��PG\u0010(����\u0011Ru|:��xK&砇���B\u000e+�!\tW6劖��ٝ�\u0018\u0011���3.��&��y~��2���m�����Q�I:�X��;��\\�;u��3KB�\u0017�˟^�M�)YD��\u0011�󫒨�q��Ǖ���\u0003�SÞ§^%]�����.�)��V��e�z�o[Y���l���gSy,�Q[�W�eE�Skz4���\u001f���=M�2̪\t(DWP\tj{�=�ǁ\u0000�\u001e�?@��?\u0005��̛H��I�w�`× A�\u000e�\u0006`%�]8_\u0003\u000e=�8D�\u000feP�HzX�#�!\u0016M:D�=\u001eILXl�֮��hÛ¼;�f��\t��U��'\f\rCS�HÔ·xBAi٢���\u001b�Y��\u001d��c\u0017O�7Kvτ�>�\u0017�2z��\u0017\"gvt��\u001af�92_d�{o\u0004tVv}�\rLaT��e�:Qm��\"\u0010�\b�\u0007��s(\u0000�\u000f�j3�\\J�ViN\u0011C�\u000e趢���\u0011�=��1�����\u0003q�\u0012��g?\u0013���W�a\n�\u000e��;�;I:\u000br>\u0019�\u001fY�����U��N-\u0003'�\u0003����/���~k�@�v�h(�&\u0018g3�dH!�`M2\u0011�\u001e$�6�u��B�s���FR��7�m�~|�S;k\u001b`����k\u001do�R{��{���\u0015P�F���;L-�\u0003hc\n��`\u001a`�\u0002'�A���\u0001\u0003Õ³IP�0y�F�D��I棴\"~�\r\u001eg��Wz|DČ\u0006�0�\u0015F\ru�\u0017M\u00193c���q\u001a3�h��ul�PedU��A\u001e�\u0017؊纍�Q��:q\fY�v��A��\"�^���\\�`\u001b\\\u0011,p��j��&\n�\u001bZ��k;Ó«F�ˣ�]?��\n%Y�@4y��\u0012��\u0001\u0003�\bra�\u001c\u0007[^��!�\"wz�rE\b�56��GVTH\b,\u0019�\u001d�\u0016�t���{|{\u0016u�*�?��ϊ����� r�X� 6d��:i�\b?\tR[���Œ3\u0006�UӇG̖\u001dȺ���\u0000�nS��TC}s}}���ry�c�\u0003����\u0003N���B\tuÚ´\u000f�R=����B���zʠ���yIP2�y%P\u0002h\fx\u001d�?��Ixz+\b9�+�\\Y�-���Nom��RV�mM���2�yU�i\u000b7˰�4���褫\u001e�-H��\r�}�Ŕ�|�%=>�p\u0017\u00179=��\u0017�6|�����hm��5�ip����8�.[����C,C�\u000e5��6w�h,�TIn��-y�%�&�w���$W�u�$���@��!��������r���Tr���w����\u0018~=�%:Wnc,�h�O4��㬼h��I+����\u001e=�\u001c8ӊ��>�k���\u0004\u001b��\u001e\f�BM���\u0016>�\u0010���\u0019\u001c.�֚\u0006�`\u001c;��q��J[��=e�C(���6�˳\u0016V\u0014\f\u001b�t�H[����H�q6��\u0019B,��\u0013\"�g�����J��Λ\"�f�E����\u001bz� �\u0014���5�\u000fMݟRƇ]�2��g&��]R7j��CdƲ���\u0004VT\u001e|w{^��e��\u001a��G�\u0007��0�\u0011\r!��\u0000L}\u0012�9(\u000bܱK��Z�!}�6�v���6+��k���L��\\7�:}�n_n\u001bhg��t:q�M�2��\u0003�\u0016��x�c`d```dpdҘ11���+�<�\u0006�\b��o�����V����e�\u0000r9\u0018�@�\u0000��\u000e�x�c`d``���\u000b���o��2�]\u0006�\b\n�\u0001\u0000��\u00070\u0000x�c�������\u0001\t���L=\f\f�\u0012\b�q\u0013\u0010�100܄��@>\u000b���� 9��\u0013,\u001b�����3�\u0017��|@,\u000eQ�\u0007\u0004��\rò@�\u0005U+��� \u0005�\u0019�4\u000bЌ9P=�\u0010>�͂�/\u0005�=0}LHjL���\u0015H+b��6T/Ll\n�_�E�\t(�\u0001e�@\u0012\u0003bF\u001d��;�l\u0001 ͌\u0003��Ƃ�W\u0010>\u0003�\u0019H|%��an=\f����\u0002\u0014\u000f���\u001b��\u0002�B�0a�C��(�>\u000e�X\"�ϬPqV8>\u00021�\u0001\u0004\u0002\u0018,\u0019N0�\u0003yL(\u0010\u0001n�I\u0016$\u0011q\u0006T�\u00026K\u0000��?\u0007\u0015�t����\u0013P�\u0012��?�D/�\u0013\f�\f\f\u0000t\f��\u0000x�c``Ђ�0�\u0002��\u0000�9�/�R�N�^�I��\u0018/0�b�a�c�c:Ƭ���%���e\u000e�\u0003V\u0019�8�\u0006�_l\u0001lYl=l\u0007؞���۰\u0017��b?���À��c\u0011'\u0003�\u001a�\u001bg\r�\u000f.\u000b�8�I\\k�>p�qWp/�~�#�S�s�W���ψo\u001e?\u0007�\u001e\u001a�\f�\u0003�\u0004�\u0004�\u0004\u000e\b�\u0011�\u0013�\u0010�\u0013�\u0011�\u0011\\!�O��\u0010�����P�P��\u001a�{�\\�I�S���H�\u0004�t��\u0013�\"rB���\u000fQ\u0019Q;�\n�kbjb=b_�U���g���`�\u0010�А\b��%�C���'I.I'�\t�{�X�|��IsI�H��~##!�\"S$\u001b!�I��\u001c�\\��1y\u0019y?�u\nb\n&\n9\nK\u0014�(|STQ\fRlSܧ�M�@)Ni��\u0019e\u000ee\u0007�,�)�{�ߨȨ��$�LS��ʡj���:Gñ…š‘Z��\u001eu\u0019u\u0017�\n�=\u001a\f\u001aV\u001a9\u001a�4>i�i�hn�������եuB�I�A{���N��&��b�M�k\u0000c�yi\u0000\u0000\u0000\u0000\u0001\u0000\u0000\u0000�\u0000�\u0000\u0011\u0000\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0000@\u0000.\u0000\u0000\u0000\u0000xڭ��N�@\u0014�O\u000b\u001a�F�$��p�Ʀ� �ʸ\u0010��h\u0014]\nB�\u0014Jl���\u0014>�\u001b7.\\�\u0004�\u001e>�\u000b��aD�,��ff��=�̝[\u0000��\u0019\u001a�/�\u0005�\u0011h�\u0018�K���a\t��u��^q\u0004�xR\u001cE\u0016\u001f�'p�e\u0014O\"�=*�BB{S\u001c#�+�Ƣ>�x���8N>U����U�+,�.\fC�v{��S�ھY�Z؄�\u000ez��\u0003\u001b\r\u00040�����$VH\u0015�5P�9�\\w��\t��\r�|�!\u0007_�j\\k\\��/�����PB\u0001�<�\u0000�8��@/\u0017u\u000e��6s\u000e��q͈8%�LK֒�1O/3/?�������o+0F�N�=|��d\u000f�k*I���;Ú 2@U껃\f\u0013k��hѵIO��3*N���&�rd��\u0014r���/5>\u001a��d�˯�aݎ��gTP��4eVYa�\"\u001a\fz��zZ��\u001b2.���:{��,z:�?~\u0002�m�{x�m�Ւ�U\u0000E�^IH�\u0004ww���ҍ'C\u000f��N\u0012\bI\b\f\u0010,����[pww��!x\u0004 �k���]]�g�3U_Mk\\k����V����_�\u0016�\u0018�\u001aߚܚ�\u0004\u0016c\"�X�%X��X��La\u0019�e9�o��\n��J��*��j��\u001a��Z��:��z��\u0006l�Fl�&l�fl�\u0016l�Vl�6�\t\u0005\u001dJ*j\u001a��ؖ�؞\u001dؑ�ؙ�Lc�]�3̮����������þ���\u001c��\u001c��\u001c¡\u001c��\u001c��\u001c��\u001cñ\u001c�������ɜ�tf0�S9�Y��l�0�3�Ǚ��\bgs\u000e�2��8�\u000b���X��\\Â¥\\��\\��\\��\\õ\\���������­����������ý���<��<��<£<��<��<��<ó<�������˼«���,�\r��-��\u001d��=��\u0003>�#>�\u0013>�3>�\u000b��+��\u001b��;��\u0007~�'~�\u0017~�7~�\u000f��8}���32�b���������-ï¿½í±[�\u001d�t+�v\u001b���ܩ�-�\u0007[\rO��̛3�����\u001f7��\u001f�}D�G�}D�G�����}/�{y����؉�؉����؋�؋��^a��W�+�\u0015�\n{���^a�c�c�c�c�c�c�c�c�c��S�)픾��Sz��|������+�W��|Oe��=���^e��W۩��vj;���Nm����i|Wc����k�5�\u001a{���^�^�^�^�^�^�^�^�^�^�^�^�^�^�^�^�^�^oЋ����;����\u001e�����:������:�����d�3�;���:������9z�����9z����]������9:���ߔ��qʱ�{�~���7��~���7��~���7�=\u001dG��qt\u001c\u001dG��qt\u001c\u001dG��qt\u001c\u001dG��qt\u001c\u001dG��qt\u001c\u001dG��qt\u001c\u001dG��qt\u001c\u001dG��qt\u001c\u001dG�\u0019�۳�\u001bt��o?q$�崡�\u0019�\u0006����\f7��J1�r0��\u0014�`�\u0001\n�-|\u0000\u0000\u0000\u0001Q�K(\u0000\u0000"}
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/respond.min.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/respond.min.js
new file mode 100644
index 0000000..56418a2
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/respond.min.js
@@ -0,0 +1,6 @@
+/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
+/*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
+window.matchMedia=window.matchMedia||function(a){"use strict";var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='&shy;<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(document);
+
+/*! Respond.js v1.1.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs  */
+(function(a){"use strict";function x(){u(!0)}var b={};a.respond=b,b.update=function(){},b.mediaQueriesSupported=a.matchMedia&&a.matchMedia("only all").matches,b.mediaQueriesSupported;var q,r,t,c=a.document,d=c.documentElement,e=[],f=[],g=[],h={},i=30,j=c.getElementsByTagName("head")[0]||d,k=c.getElementsByTagName("base")[0],l=j.getElementsByTagName("link"),m=[],n=function(){for(var b=0;l.length>b;b++){var c=l[b],d=c.href,e=c.media,f=c.rel&&"stylesheet"===c.rel.toLowerCase();d&&f&&!h[d]&&(c.styleSheet&&c.styleSheet.rawCssText?(p(c.styleSheet.rawCssText,d,e),h[d]=!0):(!/^([a-zA-Z:]*\/\/)/.test(d)&&!k||d.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&m.push({href:d,media:e}))}o()},o=function(){if(m.length){var a=m.shift();v(a.href,function(b){p(b,a.href,a.media),h[a.href]=!0,setTimeout(function(){o()},0)})}},p=function(a,b,c){var d=a.match(/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi),g=d&&d.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+b+"$2$3")},i=!g&&c;b.length&&(b+="/"),i&&(g=1);for(var j=0;g>j;j++){var k,l,m,n;i?(k=c,f.push(h(a))):(k=d[j].match(/@media *([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,f.push(RegExp.$2&&h(RegExp.$2))),m=k.split(","),n=m.length;for(var o=0;n>o;o++)l=m[o],e.push({media:l.split("(")[0].match(/(only\s+)?([a-zA-Z]+)\s?/)&&RegExp.$2||"all",rules:f.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},s=function(){var a,b=c.createElement("div"),e=c.body,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",e||(e=f=c.createElement("body"),e.style.background="none"),e.appendChild(b),d.insertBefore(e,d.firstChild),a=b.offsetWidth,f?d.removeChild(e):e.removeChild(b),a=t=parseFloat(a)},u=function(a){var b="clientWidth",h=d[b],k="CSS1Compat"===c.compatMode&&h||c.body[b]||h,m={},n=l[l.length-1],o=(new Date).getTime();if(a&&q&&i>o-q)return clearTimeout(r),r=setTimeout(u,i),void 0;q=o;for(var p in e)if(e.hasOwnProperty(p)){var v=e[p],w=v.minw,x=v.maxw,y=null===w,z=null===x,A="em";w&&(w=parseFloat(w)*(w.indexOf(A)>-1?t||s():1)),x&&(x=parseFloat(x)*(x.indexOf(A)>-1?t||s():1)),v.hasquery&&(y&&z||!(y||k>=w)||!(z||x>=k))||(m[v.media]||(m[v.media]=[]),m[v.media].push(f[v.rules]))}for(var B in g)g.hasOwnProperty(B)&&g[B]&&g[B].parentNode===j&&j.removeChild(g[B]);for(var C in m)if(m.hasOwnProperty(C)){var D=c.createElement("style"),E=m[C].join("\n");D.type="text/css",D.media=C,j.insertBefore(D,n.nextSibling),D.styleSheet?D.styleSheet.cssText=E:D.appendChild(c.createTextNode(E)),g.push(D)}},v=function(a,b){var c=w();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},w=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}();n(),b.update=n,a.addEventListener?a.addEventListener("resize",x,!1):a.attachEvent&&a.attachEvent("onresize",x)})(this);
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/uglify.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/uglify.js
new file mode 100644
index 0000000..5235dea
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/assets/uglify.js
@@ -0,0 +1,14 @@
+/** @license uglifyweb Copyright (c) 2011, The Dojo Foundation All Rights Reserved.
+ * The parts that are unique to this repo (not much, just some wrapper code) are
+ * released under the new BSD and MIT licenses.
+ *
+ * This file includes UglifyJS and some parts of es5-shim, both which have
+ * their own licenses:
+ *
+ * https://github.com/mishoo/UglifyJS (BSD)
+ * https://github.com/kriskowal/es5-shim (MIT)
+ *
+ * More info on the project: https://github.com/jrburke/uglifyweb
+ */
+
+(function(){var a=Object.prototype.toString,b="a"[0]!="a",c=function(a){if(a==null)throw new TypeError;return b&&typeof a=="string"&&a?a.split(""):Object(a)};Array.prototype.forEach||(Array.prototype.forEach=function(a){var b=c(this),d=arguments[1],e=0,f=b.length>>>0;while(e<f)e in b&&a.call(d,b[e],e,b),e++}),Array.prototype.reduce||(Array.prototype.reduce=function(a){var b=c(this),d=b.length>>>0;if(!d&&arguments.length==1)throw new TypeError;var e=0,f;if(arguments.length<2){do{if(e in b){f=b[e++];break}if(++e>=d)throw new TypeError}while(!0)}else f=arguments[1];for(;e<d;e++)e in b&&(f=a.call(void 0,f,b[e],e,b));return f});var d,e,f;(function(){function g(a,b){if(a&&a.charAt(0)==="."&&b){b=b.split("/"),b=b.slice(0,b.length-1),a=b.concat(a.split("/"));var c,d;for(c=0;d=a[c];c++)if(d===".")a.splice(c,1),c-=1;else if(d==="..")if(c!==1||a[2]!==".."&&a[0]!=="..")c>0&&(a.splice(c-1,2),c-=2);else break;a=a.join("/")}return a}function h(a,d){return function(){return c.apply(null,b.call(arguments,0).concat([a,d]))}}function i(a){return function(b){return g(b,a)}}function j(b){return function(c){a[b]=c}}function k(b,c){var d,e,f=b.indexOf("!");return f!==-1?(d=g(b.slice(0,f),c),b=b.slice(f+1),e=a[d],e&&e.normalize?b=e.normalize(b,i(c)):b=g(b,c)):b=g(b,c),{f:d?d+"!"+b:b,n:b,p:e}}function l(b,c,d,e){var f=[],g,i,l,m,n,o;e||(e=b);if(typeof d=="function"){if(c)for(m=0;m<c.length;m++)o=k(c[m],e),l=o.f,l==="require"?f[m]=h(b):l==="exports"?(f[m]=a[b]={},g=!0):l==="module"?i=f[m]={id:b,uri:"",exports:a[b]}:l in a?f[m]=a[l]:o.p&&(o.p.load(o.n,h(e,!0),j(l),{}),f[m]=a[l]);n=d.apply(a[b],f),b&&(i&&i.exports!==undefined?a[b]=i.exports:g||(a[b]=n))}else b&&(a[b]=d)}var a={},b=[].slice,c;if(typeof f=="function")return;d=c=function(b,d,e,f){return typeof b=="string"?a[k(b,d).f]:(b.splice||(d.splice?(b=d,d=arguments[2]):b=[]),f?l(null,b,d,e):setTimeout(function(){l(null,b,d,e)},15),c)},c.config=function(){return c},e||(e=c),f=function(a,b,c){b.splice||(c=b,b=[]),l(a,b,c)},f.amd={}})(),f("almond",function(){}),f("lib/parse-js",["require","exports","module"],function(a,b,c){function r(a){return q.letter.test(a)}function s(a){return a=a.charCodeAt(0),a>=48&&a<=57}function t(a){return s(a)||r(a)}function u(a){return q.non_spacing_mark.test(a)||q.space_combining_mark.test(a)}function v(a){return q.connector_punctuation.test(a)}function w(a){return a=="$"||a=="_"||r(a)}function x(a){return w(a)||u(a)||s(a)||v(a)||a=="‌"||a=="‍"}function y(a){if(i.test(a))return parseInt(a.substr(2),16);if(j.test(a))return parseInt(a.substr(1),8);if(k.test(a))return parseFloat(a)}function z(a,b,c,d){this.message=a,this.line=b,this.col=c,this.pos=d,this.stack=(new Error).stack}function A(a,b,c,d){throw new z(a,b,c,d)}function B(a,b,c){return a.type==b&&(c==null||a.value==c)}function D(a){function c(){return b.text.charAt(b.pos)}function e(a,c){var d=b.text.charAt(b.pos++);if(a&&!d)throw C;return d=="\n"?(b.newline_before=b.newline_before||!c,++b.line,b.col=0):++b.col,d}function i(){return!b.peek()}function j(a,c){var d=b.text.indexOf(a,b.pos);if(c&&d==-1)throw C;return d}function k(){b.tokline=b.line,b.tokcol=b.col,b.tokpos=b.pos}function p(a,c,d){b.regex_allowed=a=="operator"&&!S(F,c)||a=="keyword"&&S(f,c)||a=="punc"&&S(n,c);var e={type:a,value:c,line:b.tokline,col:b.tokcol,pos:b.tokpos,nlb:b.newline_before};return d||(e.comments_before=b.comments_before,b.comments_before=[]),b.newline_before=!1,e}function q(){while(S(m,c()))e()}function r(a){var b="",d=c(),f=0;while(d&&a(d,f++))b+=e(),d=c();return b}function u(a){A(a,b.tokline,b.tokcol,b.tokpos)}function v(a){var b=!1,c=!1,d=!1,e=a==".",f=r(function(f,g){return f=="x"||f=="X"?d?!1:d=!0:!!d||f!="E"&&f!="e"?f=="-"?c||g==0&&!a?!0:!1:f=="+"?c:(c=!1,f=="."?!e&&!d?e=!0:!1:t(f)):b?!1:b=c=!0});a&&(f=a+f);var g=y(f);if(!isNaN(g))return p("num",g);u("Invalid syntax: "+f)}function z(a){var b=e(!0,a);switch(b){case"n":return"\n";case"r":return"\r";case"t":return"\t";case"b":return"\b";case"v":return"";case"f":return"\f";case"0":return"\0";case"x":return String.fromCharCode(B(2));case"u":return String.fromCharCode(B(4));case"\n":return"";default:return b}}function B(a){var b=0;for(;a>0;--a){var c=parseInt(e(!0),16);isNaN(c)&&u("Invalid hex-character pattern in string"),b=b<<4|c}return b}function D(){return N("Unterminated string constant",function(){var a=e(),b="";for(;;){var c=e(!0);if(c=="\\"){var d=0,f=null;c=r(function(a){if(a>="0"&&a<="7"){if(!f)return f=a,++d;if(f<="3"&&d<=2)return++d;if(f>="4"&&d<=1)return++d}return!1}),d>0?c=String.fromCharCode(parseInt(c,8)):c=z(!0)}else if(c==a)break;b+=c}return p("string",b)})}function E(){e();var a=j("\n"),c;return a==-1?(c=b.text.substr(b.pos),b.pos=b.text.length):(c=b.text.substring(b.pos,a),b.pos=a),p("comment1",c,!0)}function G(){return e(),N("Unterminated multiline comment",function(){var a=j("*/",!0),c=b.text.substring(b.pos,a),d=p("comment2",c,!0);return b.pos=a+2,b.line+=c.split("\n").length-1,b.newline_before=c.indexOf("\n")>=0,/^@cc_on/i.test(c)&&(T("WARNING: at line "+b.line),T('*** Found "conditional comment": '+c),T("*** UglifyJS DISCARDS ALL COMMENTS.  This means your code might no longer work properly in Internet Explorer.")),d})}function H(){var a=!1,b="",d;while((d=c())!=null)if(!a)if(d=="\\")a=!0,e();else if(x(d))b+=e();else break;else d!="u"&&u("Expecting UnicodeEscapeSequence -- uXXXX"),d=z(),x(d)||u("Unicode char: "+d.charCodeAt(0)+" is not valid in identifier"),b+=d,a=!1;return b}function I(a){return N("Unterminated regular expression",function(){var b=!1,c,d=!1;while(c=e(!0))if(b)a+="\\"+c,b=!1;else if(c=="[")d=!0,a+=c;else if(c=="]"&&d)d=!1,a+=c;else{if(c=="/"&&!d)break;c=="\\"?b=!0:a+=c}var f=H();return p("regexp",[a,f])})}function J(a){function b(a){if(!c())return a;var d=a+c();return S(l,d)?(e(),b(d)):a}return p("operator",b(a||e()))}function K(){e();var a=b.regex_allowed;switch(c()){case"/":return b.comments_before.push(E()),b.regex_allowed=a,O();case"*":return b.comments_before.push(G()),b.regex_allowed=a,O()}return b.regex_allowed?I(""):J("/")}function L(){return e(),s(c())?v("."):p("punc",".")}function M(){var a=H();return S(d,a)?S(l,a)?p("operator",a):S(g,a)?p("atom",a):p("keyword",a):p("name",a)}function N(a,b){try{return b()}catch(c){if(c===C)u(a);else throw c}}function O(a){if(a!=null)return I(a);q(),k();var b=c();if(!b)return p("eof");if(s(b))return v();if(b=='"'||b=="'")return D();if(S(o,b))return p("punc",e());if(b==".")return L();if(b=="/")return K();if(S(h,b))return J();if(b=="\\"||w(b))return M();u("Unexpected character '"+b+"'")}var b={text:a.replace(/\r\n?|[\n\u2028\u2029]/g,"\n").replace(/^\uFEFF/,""),pos:0,tokpos:0,line:0,tokline:0,col:0,tokcol:0,newline_before:!1,regex_allowed:!1,comments_before:[]};return O.context=function(a){return a&&(b=a),b},O}function K(a,b,c){this.name=a,this.start=b,this.end=c}function L(a,b,c){function e(a,b){return B(d.token,a,b)}function f(){return d.peeked||(d.peeked=d.input())}function g(){return d.prev=d.token,d.peeked?(d.token=d.peeked,d.peeked=null):d.token=d.input(),d.token}function h(){return d.prev}function i(a,b,c,e){var f=d.input.context();A(a,b!=null?b:f.tokline,c!=null?c:f.tokcol,e!=null?e:f.tokpos)}function j(a,b){i(b,a.line,a.col)}function k(a){a==null&&(a=d.token),j(a,"Unexpected token: "+a.type+" ("+a.value+")")}function l(a,b){if(e(a,b))return g();j(d.token,"Unexpected token "+d.token.type+", expected "+a)}function m(a){return l("punc",a)}function n(){return!b&&(d.token.nlb||e("eof")||e("punc","}"))}function o(){e("punc",";")?g():n()||k()}function p(){return P(arguments)}function q(){m("(");var a=bk();return m(")"),a}function r(a,b,c){return a instanceof K?a:new K(a,b,c)}function s(a){return c?function(){var b=d.token,c=a.apply(this,arguments);return c[0]=r(c[0],b,h()),c}:a}function u(a){d.labels.push(a);var c=d.token,e=t();return b&&!S(I,e[0])&&k(c),d.labels.pop(),p("label",a,e)}function v(){return p("stat",N(bk,o))}function w(a){var b;return n()||(b=e("name")?d.token.value:null),b!=null?(g(),R(b,d.labels)||i("Label "+b+" without matching loop or statement")):d.in_loop==0&&i(a+" not inside a loop or switch"),o(),p(a,b)}function x(){m("(");var a=null;if(!e("punc",";")){a=e("keyword","var")?(g(),V(!0)):bk(!0,!0);if(e("operator","in"))return z(a)}return y(a)}function y(a){m(";");var b=e("punc",";")?null:bk();m(";");var c=e("punc",")")?null:bk();return m(")"),p("for",a,b,c,bl(t))}function z(a){var b=a[0]=="var"?p("name",a[1][0]):a;g();var c=bk();return m(")"),p("for-in",a,b,c,bl(t))}function L(){var a=q(),b=t(),c;return e("keyword","else")&&(g(),c=t()),p("if",a,b,c)}function O(){m("{");var a=[];while(!e("punc","}"))e("eof")&&k(),a.push(t());return g(),a}function T(){var a=O(),b,c;if(e("keyword","catch")){g(),m("("),e("name")||i("Name expected");var f=d.token.value;g(),m(")"),b=[f,O()]}return e("keyword","finally")&&(g(),c=O()),!b&&!c&&i("Missing catch/finally blocks"),p("try",a,b,c)}function U(a){var b=[];for(;;){e("name")||k();var c=d.token.value;g(),e("operator","=")?(g(),b.push([c,bk(!1,a)])):b.push([c]);if(!e("punc",","))break;g()}return b}function V(a){return p("var",U(a))}function W(){return p("const",U())}function X(){var a=Y(!1),b;return e("punc","(")?(g(),b=Z(")")):b=[],bc(p("new",a,b),!0)}function Z(a,b,c){var d=!0,f=[];while(!e("punc",a)){d?d=!1:m(",");if(b&&e("punc",a))break;e("punc",",")&&c?f.push(["atom","undefined"]):f.push(bk(!1))}return g(),f}function $(){return p("array",Z("]",!b,!0))}function _(){var a=!0,c=[];while(!e("punc","}")){a?a=!1:m(",");if(!b&&e("punc","}"))break;var f=d.token.type,h=ba();f!="name"||h!="get"&&h!="set"||!!e("punc",":")?(m(":"),c.push([h,bk(!1)])):c.push([bb(),C(!1),h])}return g(),p("object",c)}function ba(){switch(d.token.type){case"num":case"string":return N(d.token.value,g)}return bb()}function bb(){switch(d.token.type){case"name":case"operator":case"keyword":case"atom":return N(d.token.value,g);default:k()}}function bc(a,b){return e("punc",".")?(g(),bc(p("dot",a,bb()),b)):e("punc","[")?(g(),bc(p("sub",a,N(bk,M(m,"]"))),b)):b&&e("punc","(")?(g(),bc(p("call",a,Z(")")),!0)):a}function bd(a){if(e("operator")&&S(E,d.token.value))return be("unary-prefix",N(d.token.value,g),bd(a));var b=Y(a);while(e("operator")&&S(F,d.token.value)&&!d.token.nlb)b=be("unary-postfix",d.token.value,b),g();return b}function be(a,b,c){return(b=="++"||b=="--")&&!bi(c)&&i("Invalid use of "+b+" operator"),p(a,b,c)}function bf(a,b,c){var f=e("operator")?d.token.value:null;f&&f=="in"&&c&&(f=null);var h=f!=null?H[f]:null;if(h!=null&&h>b){g();var i=bf(bd(!0),h,c);return bf(p("binary",f,a,i),b,c)}return a}function bg(a){return bf(bd(!0),0,a)}function bh(a){var b=bg(a);if(e("operator","?")){g();var c=bk(!1);return m(":"),p("conditional",b,c,bk(!1,a))}return b}function bi(a){if(!b)return!0;switch(a[0]+""){case"dot":case"sub":case"new":case"call":return!0;case"name":return a[1]!="this"}}function bj(a){var b=bh(a),c=d.token.value;if(e("operator")&&S(G,c)){if(bi(b))return g(),p("assign",G[c],b,bj(a));i("Invalid assignment")}return b}function bl(a){try{return++d.in_loop,a()}finally{--d.in_loop}}var d={input:typeof a=="string"?D(a,!0):a,token:null,prev:null,peeked:null,in_function:0,in_loop:0,labels:[]};d.token=g();var t=s(function(){if(e("operator","/")||e("operator","/="))d.peeked=null,d.token=d.input(d.token.value.substr(1));switch(d.token.type){case"num":case"string":case"regexp":case"operator":case"atom":return v();case"name":return B(f(),"punc",":")?u(N(d.token.value,g,g)):v();case"punc":switch(d.token.value){case"{":return p("block",O());case"[":case"(":return v();case";":return g(),p("block");default:k()};case"keyword":switch(N(d.token.value,g)){case"break":return w("break");case"continue":return w("continue");case"debugger":return o(),p("debugger");case"do":return function(a){return l("keyword","while"),p("do",N(q,o),a)}(bl(t));case"for":return x();case"function":return C(!0);case"if":return L();case"return":return d.in_function==0&&i("'return' outside of function"),p("return",e("punc",";")?(g(),null):n()?null:N(bk,o));case"switch":return p("switch",q(),Q());case"throw":return d.token.nlb&&i("Illegal newline after 'throw'"),p("throw",N(bk,o));case"try":return T();case"var":return N(V,o);case"const":return N(W,o);case"while":return p("while",q(),bl(t));case"with":return p("with",q(),t());default:k()}}}),C=s(function(a){var b=e("name")?N(d.token.value,g):null;return a&&!b&&k(),m("("),p(a?"defun":"function",b,function(a,b){while(!e("punc",")"))a?a=!1:m(","),e("name")||k(),b.push(d.token.value),g();return g(),b}(!0,[]),function(){++d.in_function;var a=d.in_loop;d.in_loop=0;var b=O();return--d.in_function,d.in_loop=a,b}())}),Q=M(bl,function(){m("{");var a=[],b=null;while(!e("punc","}"))e("eof")&&k(),e("keyword","case")?(g(),b=[],a.push([bk(),b]),m(":")):e("keyword","default")?(g(),m(":"),b=[],a.push([null,b])):(b||k(),b.push(t()));return g(),a}),Y=s(function(a){if(e("operator","new"))return g(),X();if(e("punc")){switch(d.token.value){case"(":return g(),bc(N(bk,M(m,")")),a);case"[":return g(),bc($(),a);case"{":return g(),bc(_(),a)}k()}if(e("keyword","function"))return g(),bc(C(!1),a);if(S(J,d.token.type)){var b=d.token.type=="regexp"?p("regexp",d.token.value[0],d.token.value[1]):p(d.token.type,d.token.value);return bc(N(b,g),a)}k()}),bk=s(function(a,b){arguments.length==0&&(a=!0);var c=bj(b);return a&&e("punc",",")?(g(),p("seq",c,bk(!0,b))):c});return p("toplevel",function(a){while(!e("eof"))a.push(t());return a}([]))}function M(a){var b=P(arguments,1);return function(){return a.apply(this,b.concat(P(arguments)))}}function N(a){a instanceof Function&&(a=a());for(var b=1,c=arguments.length;--c>0;++b)arguments[b]();return a}function O(a){var b={};for(var c=0;c<a.length;++c)b[a[c]]=!0;return b}function P(a,b){return Array.prototype.slice.call(a,b||0)}function Q(a){return a.split("")}function R(a,b){for(var c=b.length;--c>=0;)if(b[c]===a)return!0;return!1}function S(a,b){return Object.prototype.hasOwnProperty.call(a,b)}var d=O(["break","case","catch","const","continue","default","delete","do","else","finally","for","function","if","in","instanceof","new","return","switch","throw","try","typeof","var","void","while","with"]),e=O(["abstract","boolean","byte","char","class","debugger","double","enum","export","extends","final","float","goto","implements","import","int","interface","long","native","package","private","protected","public","short","static","super","synchronized","throws","transient","volatile"]),f=O(["return","new","delete","throw","else","case"]),g=O(["false","null","true","undefined"]),h=O(Q("+-*&%=<>!?|~^")),i=/^0x[0-9a-f]+$/i,j=/^0[0-7]+$/,k=/^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i,l=O(["in","instanceof","typeof","new","void","delete","++","--","+","-","!","~","&","|","^","*","/","%",">>","<<",">>>","<",">","<=",">=","==","===","!=","!==","?","=","+=","-=","/=","*=","%=",">>=","<<=",">>>=","|=","^=","&=","&&","||"]),m=O(Q("  \n\r\t\f​᠎              ")),n=O(Q("[{}(,.;:")),o=O(Q("[]{}(),;:")),p=O(Q("gmsiy")),q={letter:new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"),non_spacing_mark:new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"),space_combining_mark:new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"),connector_punctuation:new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]")};z.prototype.toString=function(){return this.message+" (line: "+this.line+", col: "+this.col+", pos: "+this.pos+")"+"\n\n"+this.stack};var C={},E=O(["typeof","void","delete","--","++","!","~","-","+"]),F=O(["--","++"]),G=function(a,b,c){while(c<a.length)b[a[c]]=a[c].substr(0,a[c].length-1),c++;return b}(["+=","-=","/=","*=","%=",">>=","<<=",">>>=","|=","^=","&="],{"=":!0},0),H=function(a,b){for(var c=0,d=1;c<a.length;++c,++d){var e=a[c];for(var f=0;f<e.length;++f)b[e[f]]=d}return b}([["||"],["&&"],["|"],["^"],["&"],["==","===","!=","!=="],["<",">","<=",">=","in","instanceof"],[">>","<<",">>>"],["+","-"],["*","/","%"]],{}),I=O(["for","do","while","switch"]),J=O(["atom","num","string","regexp","name"]);K.prototype.toString=function(){return this.name};var T=function(){};b.tokenizer=D,b.parse=L,b.slice=P,b.curry=M,b.member=R,b.array_to_hash=O,b.PRECEDENCE=H,b.KEYWORDS_ATOM=g,b.RESERVED_WORDS=e,b.KEYWORDS=d,b.ATOMIC_START_TOKEN=J,b.OPERATORS=l,b.is_alphanumeric_char=t,b.set_logger=function(a){T=a}}),f("lib/process",["require","exports","module","./parse-js"],function(a,b,c){function i(){function a(a){return[this[0],K(a,function(a){var b=[a[0]];return a.length>1&&(b[1]=g(a[1])),b})]}function b(a){var b=[this[0]];return a!=null&&b.push(K(a,g)),b}function g(a){if(a==null)return null;try{f.push(a);var b=a[0],e=d[b];if(e){var g=e.apply(a,a.slice(1));if(g!=null)return g}return e=c[b],e.apply(a,a.slice(1))}finally{f.pop()}}function h(a){if(a==null)return null;try{return f.push(a),c[a[0]].apply(a,a.slice(1))}finally{f.pop()}}function i(a,b){var c={},e;for(e in a)J(a,e)&&(c[e]=d[e],d[e]=a[e]);var f=b();for(e in c)J(c,e)&&(c[e]?d[e]=c[e]:delete d[e]);return f}var c={string:function(a){return[this[0],a]},num:function(a){return[this[0],a]},name:function(a){return[this[0],a]},toplevel:function(a){return[this[0],K(a,g)]},block:b,splice:b,"var":a,"const":a,"try":function(a,b,c){return[this[0],K(a,g),b!=null?[b[0],K(b[1],g)]:null,c!=null?K(c,g):null]},"throw":function(a){return[this[0],g(a)]},"new":function(a,b){return[this[0],g(a),K(b,g)]},"switch":function(a,b){return[this[0],g(a),K(b,function(a){return[a[0]?g(a[0]):null,K(a[1],g)]})]},"break":function(a){return[this[0],a]},"continue":function(a){return[this[0],a]},conditional:function(a,b,c){return[this[0],g(a),g(b),g(c)]},assign:function(a,b,c){return[this[0],a,g(b),g(c)]},dot:function(a){return[this[0],g(a)].concat(e(arguments,1))},call:function(a,b){return[this[0],g(a),K(b,g)]},"function":function(a,b,c){return[this[0],a,b.slice(),K(c,g)]},defun:function(a,b,c){return[this[0],a,b.slice(),K(c,g)]},"if":function(a,b,c){return[this[0],g(a),g(b),g(c)]},"for":function(a,b,c,d){return[this[0],g(a),g(b),g(c),g(d)]},"for-in":function(a,b,c,d){return[this[0],g(a),g(b),g(c),g(d)]},"while":function(a,b){return[this[0],g(a),g(b)]},"do":function(a,b){return[this[0],g(a),g(b)]},"return":function(a){return[this[0],g(a)]},binary:function(a,b,c){return[this[0],a,g(b),g(c)]},"unary-prefix":function(a,b){return[this[0],a,g(b)]},"unary-postfix":function(a,b){return[this[0],a,g(b)]},sub:function(a,b){return[this[0],g(a),g(b)]},object:function(a){return[this[0],K(a,function(a){return a.length==2?[a[0],g(a[1])]:[a[0],g(a[1]),a[2]]})]},regexp:function(a,b){return[this[0],a,b]},array:function(a){return[this[0],K(a,g)]},stat:function(a){return[this[0],g(a)]},seq:function(){return[this[0]].concat(K(e(arguments),g))},label:function(a,b){return[this[0],a,g(b)]},"with":function(a,b){return[this[0],g(a),g(b)]},atom:function(a){return[this[0],a]}},d={},f=[];return{walk:g,dive:h,with_walkers:i,parent:function(){return f[f.length-2]},stack:function(){return f}}}function j(a){this.names={},this.mangled={},this.rev_mangled={},this.cname=-1,this.refs={},this.uses_with=!1,this.uses_eval=!1,this.parent=a,this.children=[],a?(this.level=a.level+1,a.children.push(this)):this.level=0}function l(a){function f(a){b=new j(b);var c=b.body=a();return c.scope=b,b=b.parent,c}function g(a,c){return b.define(a,c)}function h(a){b.refs[a]=!0}function k(a,b,c){var e=this[0]=="defun";return[this[0],e?g(a,"defun"):a,b,f(function(){return e||g(a,"lambda"),K(b,function(a){g(a,"arg")}),K(c,d)})]}function l(a){return function(b){K(b,function(b){g(b[0],a),b[1]&&h(b[0])})}}var b=null,c=i(),d=c.walk,e=[];return f(function(){function i(a,b){for(b=a.children.length;--b>=0;)i(a.children[b]);for(b in a.refs)if(J(a.refs,b))for(var c=a.has(b),d=a;d;d=d.parent){d.refs[b]=c;if(d===c)break}}var f=c.with_walkers({"function":k,defun:k,label:function(a,b){g(a,"label")},"break":function(a){a&&h(a)},"continue":function(a){a&&h(a)},"with":function(a,c){for(var d=b;d;d=d.parent)d.uses_with=!0},"var":l("var"),"const":l("const"),"try":function(a,b,c){if(b!=null)return[this[0],K(a,d),[g(b[0],"catch"),K(b[1],d)],c!=null?K(c,d):null]},name:function(a){a=="eval"&&e.push(b),h(a)}},function(){return d(a)});return K(e,function(a){if(!a.has("eval"))while(a)a.uses_eval=!0,a=a.parent}),i(b),f})}function m(a,b){function g(a,c){return!b.toplevel&&!e.parent?a:b.except&&f(a,b.except)?a:e.get_mangled(a,c)}function h(a){if(b.defines)return!e.has(a)&&J(b.defines,a)?b.defines[a]:null}function j(a,b,c){var f=this[0]=="defun",h;return a&&(f?a=g(a):(h={},!e.uses_eval&&!e.uses_with?a=h[a]=e.next_mangled():h[a]=a)),c=k(c.scope,function(){return b=K(b,function(a){return g(a)}),K(c,d)},h),[this[0],a,b,c]}function k(a,b,c){var d=e;e=a;if(c)for(var f in c)J(c,f)&&a.set_mangle(f,c[f]);for(var f in a.names)J(a.names,f)&&g(f,!0);var h=b();return h.scope=a,e=d,h}function m(a){return[this[0],K(a,function(a){return[g(a[0]),d(a[1])]})]}var c=i(),d=c.walk,e;return b=b||{},c.with_walkers({"function":j,defun:function(){var a=j.apply(this,arguments);switch(c.parent()[0]){case"toplevel":case"function":case"defun":return K.at_top(a)}return a},label:function(a,b){return[this[0],g(a),d(b)]},"break":function(a){if(a)return[this[0],g(a)]},"continue":function(a){if(a)return[this[0],g(a)]},"var":m,"const":m,name:function(a){return h(a)||[this[0],g(a)]},"try":function(a,b,c){return[this[0],K(a,d),b!=null?[g(b[0]),K(b[1],d)]:null,c!=null?K(c,d):null]},toplevel:function(a){var b=this;return k(b.scope,function(){return[b[0],K(a,d)]})}},function(){return d(l(a))})}function o(a,b){return E(a).length>E(b[0]=="stat"?b[1]:b).length?b:a}function p(a){return a[0]=="block"&&a[1]&&a[1].length>0?a[1][a[1].length-1]:a}function q(a){if(a)switch(p(a)[0]){case"return":case"break":case"continue":case"throw":return!0}}function r(a){return a[0]=="unary-prefix"&&f(a[1],["!","delete"])||a[0]=="binary"&&f(a[1],["in","instanceof","==","!=","===","!==","<","<=",">=",">"])||a[0]=="binary"&&f(a[1],["&&","||"])&&r(a[2])&&r(a[3])||a[0]=="conditional"&&r(a[2])&&r(a[3])||a[0]=="assign"&&a[1]===!0&&r(a[3])||a[0]=="seq"&&r(a[a.length-1])}function s(a){return!a||a[0]=="block"&&(!a[1]||a[1].length==0)}function t(a){return a[0]=="string"||a[0]=="unary-prefix"&&a[1]=="typeof"||a[0]=="binary"&&a[1]=="+"&&(t(a[2])||t(a[3]))}function v(a){s(a)||n("Dropping unreachable code: "+E(a,!0))}function w(a){function d(a){a=K(a,c);for(var b=0;b<a.length;++b){var e=a[b];if(e[0]!="if")continue;if(e[3]&&c(e[3]))continue;var f=c(e[2]);if(!q(f))continue;var g=c(e[1]),h=a.slice(b+1),i=h.length==1?h[0]:["block",h],j=a.slice(0,b).concat([[e[0],g,f,i]]);return d(j)}return a}function e(a,b,c){return c=d(c),[this[0],a,b,c]}function f(a){return[this[0],a!=null?d(a):null]}var b=i(),c=b.walk;return b.with_walkers({defun:e,"function":e,block:f,splice:f,toplevel:function(a){return[this[0],d(a)]},"try":function(a,b,c){return[this[0],d(a),b!=null?[b[0],d(b[1])]:null,c!=null?d(c):null]}},function(){return c(a)})}function x(a,b){function g(){throw e}function h(){throw f}function j(){return b.call(this,this,c,g,h)}function k(a){if(a=="++"||a=="--")return j.apply(this,arguments)}var c=i(),d=c.walk,e={},f={};return c.with_walkers({"try":j,"throw":j,"return":j,"new":j,"switch":j,"break":j,"continue":j,assign:j,call:j,"if":j,"for":j,"for-in":j,"while":j,"do":j,"return":j,"unary-prefix":k,"unary-postfix":k,defun:j},function(){for(;;)try{d(a);break}catch(b){if(b===e)break;if(b===f)continue;throw b}})}function y(a){function e(a,b){var e=d;d=b,a=K(a,c);var f={},g=K(b.names,function(a,c){return a!="var"?K.skip:b.references(c)?(f[c]=!0,[c]):K.skip});return g.length>0&&(x(["block",a],function(a,b,c,d){if(a[0]=="assign"&&a[1]===!0&&a[2][0]=="name"&&J(f,a[2][1])){for(var e=g.length;--e>=0;)if(g[e][0]==a[2][1]){g[e][1]&&c(),g[e][1]=a[3],g.push(g.splice(e,1)[0]);break}var h=b.parent();if(h[0]=="seq"){var i=h[2];i.unshift(0,h.length),h.splice.apply(h,i)}else h[0]=="stat"?h.splice(0,h.length,"block"):c();d()}c()}),a.unshift(["var",g])),d=e,a}function f(a){var c=null;for(var d=a.length;--d>=0;){var e=a[d];if(!e[1])continue;e=["assign",!0,["name",e[0]],e[1]],c==null?c=e:c=["seq",e,c]}return c==null?b.parent()[0]=="for-in"?["name",a[0][0]]:K.skip:["stat",c]}function g(a){return[this[0],e(a,this.scope)]}var b=i(),c=b.walk,d;return b.with_walkers({"function":function(a,b,c){for(var d=b.length;--d>=0&&!c.scope.references(b[d]);)b.pop();return c.scope.references(a)||(a=null),[this[0],a,b,e(c,c.scope)]},defun:function(a,b,c){if(!d.references(a))return K.skip;for(var f=b.length;--f>=0&&!c.scope.references(b[f]);)b.pop();return[this[0],a,b,e(c,c.scope)]},"var":f,toplevel:g},function(){return c(l(a))})}function z(a,b){function h(a){var c=["unary-prefix","!",a];switch(a[0]){case"unary-prefix":return a[1]=="!"&&r(a[2])?a[2]:c;case"seq":return a=e(a),a[a.length-1]=h(a[a.length-1]),a;case"conditional":return o(c,["conditional",a[1],h(a[2]),h(a[3])]);case"binary":var d=a[1],f=a[2],g=a[3];if(!b.keep_comps)switch(d){case"<=":return["binary",">",f,g];case"<":return["binary",">=",f,g];case">=":return["binary","<",f,g];case">":return["binary","<=",f,g]}switch(d){case"==":return["binary","!=",f,g];case"!=":return["binary","==",f,g];case"===":return["binary","!==",f,g];case"!==":return["binary","===",f,g];case"&&":return o(c,["binary","||",h(f),h(g)]);case"||":return o(c,["binary","&&",h(f),h(g)])}}return c}function j(a,b,c){var d=function(){return a[0]=="unary-prefix"&&a[1]=="!"?c?["conditional",a[2],c,b]:["binary","||",a[2],b]:c?o(["conditional",a,b,c],["conditional",h(a),c,b]):["binary","&&",a,b]};return u(a,function(a,d){return v(d?c:b),d?b:c},d)}function k(a,b){var c=g;g=a;var d=b();return d.scope=a,g=c,d}function m(a){return a!=null&&a[0]=="block"&&a[1]&&(a[1].length==1?a=a[1][0]:a[1].length==0&&(a=["block"])),a}function p(a,b,c){var d=this[0]=="defun";return c=k(c.scope,function(){var b=t(c,"lambda");return!d&&a&&!g.references(a)&&(a=null),b}),[this[0],a,b,c]}function t(a,c){return a=K(a,d),a=a.reduce(function(a,b){return b[0]=="block"?b[1]&&a.push.apply(a,b[1]):a.push(b),a},[]),a=function(b,c){return a.forEach(function(a){c&&(a[0]=="var"&&c[0]=="var"||a[0]=="const"&&c[0]=="const")?c[1]=c[1].concat(a[1]):(b.push(a),c=a)}),b}([]),b.dead_code&&(a=function(c,d){return a.forEach(function(a){d?a[0]=="function"||a[0]=="defun"?c.push(a):a[0]=="var"||a[0]=="const"?(b.no_warnings||n("Variables declared in unreachable code"),a[1]=K(a[1],function(a){return a[1]&&!b.no_warnings&&v(["assign",!0,["name",a[0]],a[1]]),[a[0]]}),c.push(a)):b.no_warnings||v(a):(c.push(a),f(a[0],["return","throw","break","continue"])&&(d=!0))}),c}([])),b.make_seqs&&(a=function(b,c){return a.forEach(function(a){c&&c[0]=="stat"&&a[0]=="stat"?c[1]=["seq",c[1],a[1]]:(b.push(a),c=a)}),b.length>=2&&b[b.length-2][0]=="stat"&&(b[b.length-1][0]=="return"||b[b.length-1][0]=="throw")&&b[b.length-1][1]&&b.splice(b.length-2,2,[b[b.length-1][0],["seq",b[b.length-2][1],b[b.length-1][1]]]),b}([])),a}function x(a,b,c){return u(a,function(a,e){return e?(b=d(b),v(c),b||["block"]):(c=d(c),v(b),c||["block"])},function(){return y(a,b,c)})}function y(a,b,c){a=d(a),b=d(b),c=d(c),s(b)?(a=h(a),b=c,c=null):s(c)?c=null:function(){var d=E(a),e=h(a),f=E(e);if(f.length<d.length){var g=b;b=c,c=g,a=e}}();if(s(c)&&s(b))return["stat",a];var e=["if",a,b,c];return b[0]=="if"&&s(b[3])&&s(c)?e=o(e,d(["if",["binary","&&",a,b[1]],b[2]])):b[0]=="stat"?c?c[0]=="stat"&&(e=o(e,["stat",j(a,b[1],c[1])])):e=o(e,["stat",j(a,b[1])]):c&&b[0]==c[0]&&(b[0]=="return"||b[0]=="throw")&&b[1]&&c[1]?e=o(e,[b[0],j(a,b[1],c[1])]):c&&q(b)?(e=[["if",a,b]],c[0]=="block"?c[1]&&(e=e.concat(c[1])):e.push(c),e=d(["block",e])):b&&q(c)&&(e=[["if",h(a),c]],b[0]=="block"?b[1]&&(e=e.concat(b[1])):e.push(b),e=d(["block",e])),e}function z(a,b){return u(a,function(a,c){return c?["for",null,null,null,d(b)]:(v(b),["block"])})}b=H(b,{make_seqs:!0,dead_code:!0,no_warnings:!1,keep_comps:!0});var c=i(),d=c.walk,g;return c.with_walkers({sub:function(a,b){if(b[0]=="string"){var c=b[1];if(I(c))return["dot",d(a),c];if(/^[1-9][0-9]*$/.test(c)||c==="0")return["sub",d(a),["num",parseInt(c,10)]]}},"if":x,toplevel:function(a){return["toplevel",k(this.scope,function(){return t(a)})]},"switch":function(a,b){var c=b.length-1;return["switch",d(a),K(b,function(a,b){var e=t(a[1]);if(b==c&&e.length>0){var f=e[e.length-1];f[0]=="break"&&!f[1]&&e.pop()}return[a[0]?d(a[0]):null,e]})]},"function":p,defun:p,block:function(a){if(a)return m(["block",t(a)])},binary:function(a,b,c){return u(["binary",a,d(b),d(c)],function(a){return o(d(a),this)},function(){return function(){if(a!="=="&&a!="!=")return;var e=d(b),f=d(c);return e&&e[0]=="unary-prefix"&&e[1]=="!"&&e[2][0]=="num"?b=["num",+!e[2][1]]:f&&f[0]=="unary-prefix"&&f[1]=="!"&&f[2][0]=="num"&&(c=["num",+!f[2][1]]),["binary",a,b,c]}()||this})},conditional:function(a,b,c){return j(d(a),d(b),d(c))},"try":function(a,b,c){return["try",t(a),b!=null?[b[0],t(b[1])]:null,c!=null?t(c):null]},"unary-prefix":function(a,b){b=d(b);var c=["unary-prefix",a,b];return a=="!"&&(c=o(c,h(b))),u(c,function(a,b){return d(a)},function(){return c})},name:function(a){switch(a){case"true":return["unary-prefix","!",["num",0]];case"false":return["unary-prefix","!",["num",1]]}},"while":z,assign:function(a,b,c){b=d(b),c=d(c);var e=["+","-","/","*","%",">>","<<",">>>","|","^","&"];return a===!0&&b[0]==="name"&&c[0]==="binary"&&~e.indexOf(c[1])&&c[2][0]==="name"&&c[2][1]===b[1]?[this[0],c[1],b,c[3]]:[this[0],a,b,c]}},function(){for(var b=0;b<2;++b)a=w(a),a=l(a),a=d(a);return a})}function B(a,b){var c=0,d=0;return a=a.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g,function(a){switch(a){case"\\":return"\\\\";case"\b":return"\\b";case"\f":return"\\f";case"\n":return"\\n";case"\r":return"\\r";case"\t":return"\\t";case"\u2028":return"\\u2028";case"\u2029":return"\\u2029";case'"':return++c,'"';case"'":return++d,"'";case"\0":return"\\0"}return a}),b&&(a=C(a)),c>d?"'"+a.replace(/\x27/g,"\\'")+"'":'"'+a.replace(/\x22/g,'\\"')+'"'}function C(a){return a.replace(/[\u0080-\uffff]/g,function(a){var b=a.charCodeAt(0).toString(16);while(b.length<4)b="0"+b;return"\\u"+b})}function E(a,b){function m(a){var c=B(a,b.ascii_only);return b.inline_script&&(c=c.replace(/<\x2fscript([>/\t\n\f\r ])/gi,"<\\/script$1")),c}function n(a){return a=a.toString(),b.ascii_only&&(a=C(a)),a}function o(a){return a==null&&(a=""),c&&(a=G(" ",b.indent_start+j*b.indent_level)+a),a}function p(a,b){b==null&&(b=1),j+=b;try{return a.apply(null,e(arguments,1))}finally{j-=b}}function q(a){if(c)return a.join(" ");var b=[];for(var d=0;d<a.length;++d){var e=a[d+1];b.push(a[d]),e&&(/[a-z0-9_\x24]$/i.test(a[d].toString())&&/^[a-z0-9_\x24]/i.test(e.toString())||/[\+\-]$/.test(a[d].toString())&&/^[\+\-]/.test(e.toString()))&&b.push(" ")}return b.join("")}function r(a){return a.join(","+l)}function t(a){var b=y(a);for(var c=1;c<arguments.length;++c){var d=arguments[c];if(d instanceof Function&&d(a)||a[0]==d)return"("+b+")"}return b}function u(a){if(a.length==1)return a[0];if(a.length==2){var b=a[1];return a=a[0],a.length>b.length?b:a}return u([a[0],u(a.slice(1))])}function v(a){if(a[0]=="function"||a[0]=="object"){var b=e(x.stack()),c=b.pop(),d=b.pop();while(d){if(d[0]=="stat")return!0;if((d[0]=="seq"||d[0]=="call"||d[0]=="dot"||d[0]=="sub"||d[0]=="conditional")&&d[1]===c||(d[0]=="binary"||d[0]=="assign"||d[0]=="unary-postfix")&&d[2]===c)c=d,d=b.pop();else return!1}}return!J(A,a[0])}function w(a){var b=a.toString(10),c=[b.replace(/^0\./,".")],d;return Math.floor(a)===a?(a<0?c.push("-0x"+(-a).toString(16).toLowerCase(),"-0"+(-a).toString(8)):c.push("0x"+a.toString(16).toLowerCase(),"0"+a.toString(8)),(d=/^(.*?)(0+)$/.exec(a))&&c.push(d[1]+"e"+d[2].length)):(d=/^0?\.(0+)(.*)$/.exec(a))&&c.push(d[2]+"e-"+(d[1].length+d[2].length),b.substr(b.indexOf("."))),u(c)}function z(a){if(a==null)return";";if(a[0]=="do")return N([a]);var b=a;for(;;){var c=b[0];if(c=="if"){if(!b[3])return y(["block",[a]]);b=b[3]}else if(c=="while"||c=="do")b=b[2];else if(c=="for"||c=="for-in")b=b[4];else break}return y(a)}function E(a,b,c,d){var e=d||"function";return a&&(e+=" "+n(a)),e+="("+r(K(b,n))+")",e=q([e,N(c)]),v(this)?"("+e+")":e}function F(a){switch(a[0]){case"with":case"while":return s(a[2]);case"for":case"for-in":return s(a[4]);case"if":if(s(a[2])&&!a[3])return!0;if(a[3])return s(a[3])?!0:F(a[3]);return F(a[2])}}function L(a,b){for(var d=[],e=a.length-1,f=0;f<=e;++f){var g=a[f],h=y(g);h!=";"&&(!c&&f==e&&!F(g)&&(h=h.replace(/;+\s*$/,"")),d.push(h))}return b?d:K(d,o)}function M(a){var b=a.length;return b==0?"{}":"{"+k+K(a,function(a,d){var e=a[1].length>0,f=p(function(){return o(a[0]?q(["case",y(a[0])+":"]):"default:")},.5)+(e?k+p(function(){return L(a[1]).join(k)}):"");return!c&&e&&d<b-1&&(f+=";"),f}).join(k)+k+o("}")}function N(a){return a?a.length==0?"{}":"{"+k+p(function(){return L(a).join(k)})+k+o("}"):";"}function O(a){var b=a[0],c=a[1];return c!=null&&(b=q([n(b),"=",t(c,"seq")])),b}b=H(b,{indent_start:0,indent_level:4,quote_keys:!1,space_colon:!1,beautify:!1,ascii_only:!1,inline_script:!1});var c=!!b.beautify,j=0,k=c?"\n":"",l=c?" ":"",x=i(),y=x.walk;return x.with_walkers({string:m,num:w,name:n,toplevel:function(a){return L(a).join(k+k)},splice:function(a){var b=x.parent();return J(D,b)?N.apply(this,arguments):K(L(a,!0),function(a,b){return b>0?o(a):a}).join(k)},block:N,"var":function(a){return"var "+r(K(a,O))+";"},"const":function(a){return"const "+r(K(a,O))+";"},"try":function(a,b,c){var d=["try",N(a)];return b&&d.push("catch","("+b[0]+")",N(b[1])),c&&d.push("finally",N(c)),q(d)},"throw":function(a){return q(["throw",y(a)])+";"},"new":function(a,b){return b=b.length>0?"("+r(K(b,function(a){return t(a,"seq")}))+")":"",q(["new",t(a,"seq","binary","conditional","assign",function(a){var b=i(),c={};try{b.with_walkers({call:function(){throw c},"function":function(){return this}},function(){b.walk(a)})}catch(d){if(d===c)return!0;throw d}})+b])},"switch":function(a,b){return q(["switch","("+y(a)+")",M(b)])},"break":function(a){var b="break";return a!=null&&(b+=" "+n(a)),b+";"},"continue":function(a){var b="continue";return a!=null&&(b+=" "+n(a)),b+";"},conditional:function(a,b,c){return q([t(a,"assign","seq","conditional"),"?",t(b,"seq"),":",t(c,"seq")])},assign:function(a,b,c){return a&&a!==!0?a+="=":a="=",q([y(b),a,t(c,"seq")])},dot:function(a){var b=y(a),c=1;a[0]=="num"?/\./.test(a[1])||(b+="."):v(a)&&(b="("+b+")");while(c<arguments.length)b+="."+n(arguments[c++]);return b},call:function(a,b){var c=y(a);return c.charAt(0)!="("&&v(a)&&(c="("+c+")"),c+"("+r(K(b,function(a){return t(a,"seq")}))+")"},"function":E,defun:E,"if":function(a,b,c){var d=["if","("+y(a)+")",c?z(b):y(b)];return c&&d.push("else",y(c)),q(d)},"for":function(a,b,c,d){var e=["for"];a=(a!=null?y(a):"").replace(/;*\s*$/,";"+l),b=(b!=null?y(b):"").replace(/;*\s*$/,";"+l),c=(c!=null?y(c):"").replace(/;*\s*$/,"");var f=a+b+c;return f=="; ; "&&(f=";;"),e.push("("+f+")",y(d)),q(e)},"for-in":function(a,b,c,d){return q(["for","("+(a?y(a).replace(/;+$/,""):y(b)),"in",y(c)+")",y(d)])},"while":function(a,b){return q(["while","("+y(a)+")",y(b)])},"do":function(a,b){return q(["do",y(b),"while","("+y(a)+")"])+";"},"return":function(a){var b=["return"];return a!=null&&b.push(y(a)),q(b)+";"},binary:function(a,d,e){var h=y(d),i=y(e);if(f(d[0],["assign","conditional","seq"])||d[0]=="binary"&&g[a]>g[d[1]]||d[0]=="function"&&v(this))h="("+h+")";return f(e[0],["assign","conditional","seq"])||e[0]=="binary"&&g[a]>=g[e[1]]&&(e[1]!=a||!f(a,["&&","||","*"]))?i="("+i+")":!c&&b.inline_script&&(a=="<"||a=="<<")&&e[0]=="regexp"&&/^script/i.test(e[1])&&(i=" "+i),q([h,a,i])},"unary-prefix":function(a,b){var c=y(b);return b[0]=="num"||b[0]=="unary-prefix"&&!J(h,a+b[1])||!v(b)||(c="("+c+")"),a+(d.is_alphanumeric_char(a.charAt(0))?" ":"")+c},"unary-postfix":function(a,b){var c=y(b);return b[0]=="num"||b[0]=="unary-postfix"&&!J(h,a+b[1])||!v(b)||(c="("+c+")"),c+a},sub:function(a,b){var c=y(a);return v(a)&&(c="("+c+")"),c+"["+y(b)+"]"},object:function(a){var d=v(this);if(a.length==0)return d?"({})":"{}";var e="{"+k+p(function(){return K(a,function(a){if(a.length==3)return o(E(a[0],a[1][2],a[1][3],a[2]));var d=a[0],e=t(a[1],"seq");return b.quote_keys?d=m(d):(typeof d=="number"||!c&&+d+""==d)&&parseFloat(d)>=0?d=w(+d):I(d)||(d=m(d)),o(q(c&&b.space_colon?[d,":",e]:[d+":",e]))}).join(","+k)})+k+o("}");return d?"("+e+")":e},regexp:function(a,b){return"/"+a+"/"+b},array:function(a){return a.length==0?"[]":q(["[",r(K(a,function(b,d){return!c&&b[0]=="atom"&&b[1]=="undefined"?d===a.length-1?",":"":t(b,"seq")})),"]"])},stat:function(a){return y(a).replace(/;*\s*$/,";")},seq:function(){return r(K(e(arguments),y))},label:function(a,b){return q([n(a),":",y(b)])},"with":function(a,b){return q(["with","("+y(a)+")",y(b)])},atom:function(a){return n(a)}},function(){return y(a)})}function F(a,b){var c=[0];return d.parse(function(){function h(a){return a.pos-f}function i(a){f=a.pos,c.push(f)}function j(){var a=e.apply(this,arguments);c:{if(g&&g.type=="keyword")break c;if(h(a)>b)switch(a.type){case"keyword":case"atom":case"name":case"punc":i(a);break c}}return g=a,a}var e=d.tokenizer(a),f=0,g;return j.context=function(){return e.context.apply(this,arguments)},j}()),c.map(function(b,d){return a.substring(b,c[d+1]||a.length)}).join("\n")}function G(a,b){if(b>0){if(b==1)return a;var c=G(a,b>>1);return c+=c,b&1&&(c+=a),c}return""}function H(a,b){var c={};a===!0&&(a={});for(var d in b)J(b,d)&&(c[d]=a&&J(a,d)?a[d]:b[d]);return c}function I(a){return/^[a-z_$][a-z0-9_$]*$/i.test(a)&&a!="this"&&!J(d.KEYWORDS_ATOM,a)&&!J(d.RESERVED_WORDS,a)&&!J(d.KEYWORDS,a)}function J(a,b){return Object.prototype.hasOwnProperty.call(a,b)}var d=a("./parse-js"),e=d.slice,f=d.member,g=d.PRECEDENCE,h=d.OPERATORS,k=function(){var a="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";return function(b){var c="";do c=a.charAt(b%54)+c,b=Math.floor(b/54);while(b>0);return c}}();j.prototype={has:function(a){for(var b=this;b;b=b.parent)if(J(b.names,a))return b},has_mangled:function(a){for(var b=this;b;b=b.parent)if(J(b.rev_mangled,a))return b},toJSON:function(){return{names:this.names,uses_eval:this.uses_eval,uses_with:this.uses_with}},next_mangled:function(){for(;;){var a=k(++this.cname),b;b=this.has_mangled(a);if(b&&this.refs[b.rev_mangled[a]]===b)continue;b=this.has(a);if(b&&b!==this&&this.refs[a]===b&&!b.has_mangled(a))continue;if(J(this.refs,a)&&this.refs[a]==null)continue;if(!I(a))continue;return a}},set_mangle:function(a,b){return this.rev_mangled[b]=a,this.mangled[a]=b},get_mangled:function(a,b){if(this.uses_eval||this.uses_with)return a;var c=this.has(a);return c?J(c.mangled,a)?c.mangled[a]:b?c.set_mangle(a,c.next_mangled()):a:a},references:function(a){return a&&!this.parent||this.uses_with||this.uses_eval||this.refs[a]},define:function(a,b){if(a!=null){if(b=="var"||!J(this.names,a))this.names[a]=b||"var";return a}}};var n=function(){},u=function(){function b(c){switch(c[0]){case"string":case"num":return c[1];case"name":case"atom":switch(c[1]){case"true":return!0;case"false":return!1;case"null":return null}break;case"unary-prefix":switch(c[1]){case"!":return!b(c[2]);case"typeof":return typeof b(c[2]);case"~":return~b(c[2]);case"-":return-b(c[2]);case"+":return+b(c[2])}break;case"binary":var d=c[2],e=c[3];switch(c[1]){case"&&":return b(d)&&b(e);case"||":return b(d)||b(e);case"|":return b(d)|b(e);case"&":return b(d)&b(e);case"^":return b(d)^b(e);case"+":return b(d)+b(e);case"*":return b(d)*b(e);case"/":return b(d)/b(e);case"%":return b(d)%b(e);case"-":return b(d)-b(e);case"<<":return b(d)<<b(e);case">>":return b(d)>>b(e);case">>>":return b(d)>>>b(e);case"==":return b(d)==b(e);case"===":return b(d)===b(e);case"!=":return b(d)!=b(e);case"!==":return b(d)!==b(e);case"<":return b(d)<b(e);case"<=":return b(d)<=b(e);case">":return b(d)>b(e);case">=":return b(d)>=b(e);case"in":return b(d)in b(e);case"instanceof":return b(d)instanceof b(e)}}throw a}var a={};return function(c,d,e){try{var f=b(c),g;switch(typeof f){case"string":g=["string",f];break;case"number":g=["num",f];break;case"boolean":g=["name",String(f)];break;default:throw new Error("Can't handle constant of type: "+typeof f)}return d.call(c,g,f)}catch(h){if(h===a){if(c[0]!="binary"||c[1]!="==="&&c[1]!="!=="||!(t(c[2])&&t(c[3])||r(c[2])&&r(c[3]))){if(e&&c[0]=="binary"&&(c[1]=="||"||c[1]=="&&"))try{var i=b(c[2]);c=c[1]=="&&"&&(i?c[3]:i)||c[1]=="||"&&(i?i:c[3])||c}catch(j){}}else c[1]=c[1].substr(0,2);return e?e.call(c,c):null}throw h}}}(),A=d.array_to_hash(["name","array","object","string","dot","sub","call","regexp","defun"]),D=d.array_to_hash(["if","while","do","for","for-in","with"]),K;(function(){function b(a){this.v=a}function c(a){this.v=a}K=function(d,e,f){function j(){var j=e.call(f,d[i],i);j instanceof b?(j=j.v,j instanceof c?h.push.apply(h,j.v):h.push(j)):j!=a&&(j instanceof c?g.push.apply(g,j.v):g.push(j))}var g=[],h=[],i;if(d instanceof Array)for(i=0;i<d.length;++i)j();else for(i in d)J(d,i)&&j();return h.concat(g)},K.at_top=function(a){return new b(a)},K.splice=function(a){return new c(a)};var a=K.skip={}})(),b.ast_walker=i,b.ast_mangle=m,b.ast_squeeze=z,b.ast_lift_variables=y,b.gen_code=E,b.ast_add_scope=l,b.set_logger=function(a){n=a},b.make_string=B,b.split_lines=F,b.MAP=K}),f("uglify-js",["require","exports","module","./lib/parse-js","./lib/process"],function(a,b,c){function d(a,b){b||(b={});var c=d.parser,e=d.uglify,f=c.parse(a,b.strict_semicolons);f=e.ast_mangle(f,b.mangle_options),f=e.ast_squeeze(f,b.squeeze_options);var g=e.gen_code(f,b.gen_options);return g}d.parser=a("./lib/parse-js"),d.uglify=a("./lib/process"),c.exports=d}),f("lib/squeeze-more",["require","exports","module","./parse-js","./process"],function(a,b,c){function l(a){function f(a,b){var c=d,e;return d=a,e=b(),d=c,e}function g(a,b,d){return[this[0],a,b,f(d.scope,h(i,d,c))]}var b=e.ast_walker(),c=b.walk,d;return b.with_walkers({toplevel:function(a){return[this[0],f(this.scope,h(i,a,c))]},"function":g,defun:g,"new":function(a,b){if(a[0]=="name"&&a[1]=="Array"&&!d.has("Array"))return b.length!=1?["array",b]:c(["call",["name","Array"],b])},call:function(a,b){if(a[0]=="dot"&&a[2]=="toString"&&b.length==0)return["binary","+",a[1],["string",""]];if(a[0]=="name"&&a[1]=="Array"&&b.length!=1&&!d.has("Array"))return["array",b]}},function(){return c(e.ast_add_scope(a))})}var d=a("./parse-js"),e=a("./process"),f=d.slice,g=d.member,h=d.curry,i=e.MAP,j=d.PRECEDENCE,k=d.OPERATORS;b.ast_squeeze_more=l});if(!this.uglify){var g=this;e(["uglify-js","lib/process","lib/squeeze-more"],function(a,b,c){b.ast_squeeze_more=c.ast_squeeze_more,g.uglify=a;var d=g.define;typeof d=="function"&&d.amd&&d("uglifyweb",function(){return a})},null,!0)}})()
\ No newline at end of file
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/bootstrap.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/bootstrap.js
new file mode 100644
index 0000000..2c64257
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/bootstrap.js
@@ -0,0 +1,1999 @@
+/**
+* bootstrap.js v3.0.0 by @fat and @mdo
+* Copyright 2013 Twitter Inc.
+* http://www.apache.org/licenses/LICENSE-2.0
+*/
+if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#transitions
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+  // ============================================================
+
+  function transitionEnd() {
+    var el = document.createElement('bootstrap')
+
+    var transEndEventNames = {
+      'WebkitTransition' : 'webkitTransitionEnd'
+    , 'MozTransition'    : 'transitionend'
+    , 'OTransition'      : 'oTransitionEnd otransitionend'
+    , 'transition'       : 'transitionend'
+    }
+
+    for (var name in transEndEventNames) {
+      if (el.style[name] !== undefined) {
+        return { end: transEndEventNames[name] }
+      }
+    }
+  }
+
+  // http://blog.alexmaccaw.com/css-transitions
+  $.fn.emulateTransitionEnd = function (duration) {
+    var called = false, $el = this
+    $(this).one($.support.transition.end, function () { called = true })
+    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+    setTimeout(callback, duration)
+    return this
+  }
+
+  $(function () {
+    $.support.transition = transitionEnd()
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#alerts
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // ALERT CLASS DEFINITION
+  // ======================
+
+  var dismiss = '[data-dismiss="alert"]'
+  var Alert   = function (el) {
+    $(el).on('click', dismiss, this.close)
+  }
+
+  Alert.prototype.close = function (e) {
+    var $this    = $(this)
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = $(selector)
+
+    if (e) e.preventDefault()
+
+    if (!$parent.length) {
+      $parent = $this.hasClass('alert') ? $this : $this.parent()
+    }
+
+    $parent.trigger(e = $.Event('close.bs.alert'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      $parent.trigger('closed.bs.alert').remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent
+        .one($.support.transition.end, removeElement)
+        .emulateTransitionEnd(150) :
+      removeElement()
+  }
+
+
+  // ALERT PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.alert
+
+  $.fn.alert = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.alert')
+
+      if (!data) $this.data('bs.alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.alert.Constructor = Alert
+
+
+  // ALERT NO CONFLICT
+  // =================
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+  // ALERT DATA-API
+  // ==============
+
+  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#buttons
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // BUTTON PUBLIC CLASS DEFINITION
+  // ==============================
+
+  var Button = function (element, options) {
+    this.$element = $(element)
+    this.options  = $.extend({}, Button.DEFAULTS, options)
+  }
+
+  Button.DEFAULTS = {
+    loadingText: 'loading...'
+  }
+
+  Button.prototype.setState = function (state) {
+    var d    = 'disabled'
+    var $el  = this.$element
+    var val  = $el.is('input') ? 'val' : 'html'
+    var data = $el.data()
+
+    state = state + 'Text'
+
+    if (!data.resetText) $el.data('resetText', $el[val]())
+
+    $el[val](data[state] || this.options[state])
+
+    // push to event loop to allow forms to submit
+    setTimeout(function () {
+      state == 'loadingText' ?
+        $el.addClass(d).attr(d, d) :
+        $el.removeClass(d).removeAttr(d);
+    }, 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+    if ($parent.length) {
+      var $input = this.$element.find('input')
+        .prop('checked', !this.$element.hasClass('active'))
+        .trigger('change')
+      if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active')
+    }
+
+    this.$element.toggleClass('active')
+  }
+
+
+  // BUTTON PLUGIN DEFINITION
+  // ========================
+
+  var old = $.fn.button
+
+  $.fn.button = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.button')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  $.fn.button.Constructor = Button
+
+
+  // BUTTON NO CONFLICT
+  // ==================
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+  // BUTTON DATA-API
+  // ===============
+
+  $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {
+    var $btn = $(e.target)
+    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+    $btn.button('toggle')
+    e.preventDefault()
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#carousel
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // CAROUSEL CLASS DEFINITION
+  // =========================
+
+  var Carousel = function (element, options) {
+    this.$element    = $(element)
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options     = options
+    this.paused      =
+    this.sliding     =
+    this.interval    =
+    this.$active     =
+    this.$items      = null
+
+    this.options.pause == 'hover' && this.$element
+      .on('mouseenter', $.proxy(this.pause, this))
+      .on('mouseleave', $.proxy(this.cycle, this))
+  }
+
+  Carousel.DEFAULTS = {
+    interval: 5000
+  , pause: 'hover'
+  , wrap: true
+  }
+
+  Carousel.prototype.cycle =  function (e) {
+    e || (this.paused = false)
+
+    this.interval && clearInterval(this.interval)
+
+    this.options.interval
+      && !this.paused
+      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+    return this
+  }
+
+  Carousel.prototype.getActiveIndex = function () {
+    this.$active = this.$element.find('.item.active')
+    this.$items  = this.$active.parent().children()
+
+    return this.$items.index(this.$active)
+  }
+
+  Carousel.prototype.to = function (pos) {
+    var that        = this
+    var activeIndex = this.getActiveIndex()
+
+    if (pos > (this.$items.length - 1) || pos < 0) return
+
+    if (this.sliding)       return this.$element.one('slid', function () { that.to(pos) })
+    if (activeIndex == pos) return this.pause().cycle()
+
+    return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+  }
+
+  Carousel.prototype.pause = function (e) {
+    e || (this.paused = true)
+
+    if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+      this.$element.trigger($.support.transition.end)
+      this.cycle(true)
+    }
+
+    this.interval = clearInterval(this.interval)
+
+    return this
+  }
+
+  Carousel.prototype.next = function () {
+    if (this.sliding) return
+    return this.slide('next')
+  }
+
+  Carousel.prototype.prev = function () {
+    if (this.sliding) return
+    return this.slide('prev')
+  }
+
+  Carousel.prototype.slide = function (type, next) {
+    var $active   = this.$element.find('.item.active')
+    var $next     = next || $active[type]()
+    var isCycling = this.interval
+    var direction = type == 'next' ? 'left' : 'right'
+    var fallback  = type == 'next' ? 'first' : 'last'
+    var that      = this
+
+    if (!$next.length) {
+      if (!this.options.wrap) return
+      $next = this.$element.find('.item')[fallback]()
+    }
+
+    this.sliding = true
+
+    isCycling && this.pause()
+
+    var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })
+
+    if ($next.hasClass('active')) return
+
+    if (this.$indicators.length) {
+      this.$indicators.find('.active').removeClass('active')
+      this.$element.one('slid', function () {
+        var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
+        $nextIndicator && $nextIndicator.addClass('active')
+      })
+    }
+
+    if ($.support.transition && this.$element.hasClass('slide')) {
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+      $next.addClass(type)
+      $next[0].offsetWidth // force reflow
+      $active.addClass(direction)
+      $next.addClass(direction)
+      $active
+        .one($.support.transition.end, function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () { that.$element.trigger('slid') }, 0)
+        })
+        .emulateTransitionEnd(600)
+    } else {
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+      $active.removeClass('active')
+      $next.addClass('active')
+      this.sliding = false
+      this.$element.trigger('slid')
+    }
+
+    isCycling && this.cycle()
+
+    return this
+  }
+
+
+  // CAROUSEL PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.carousel
+
+  $.fn.carousel = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.carousel')
+      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+      var action  = typeof option == 'string' ? option : options.slide
+
+      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  $.fn.carousel.Constructor = Carousel
+
+
+  // CAROUSEL NO CONFLICT
+  // ====================
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+
+  // CAROUSEL DATA-API
+  // =================
+
+  $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+    var $this   = $(this), href
+    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+    var options = $.extend({}, $target.data(), $this.data())
+    var slideIndex = $this.attr('data-slide-to')
+    if (slideIndex) options.interval = false
+
+    $target.carousel(options)
+
+    if (slideIndex = $this.attr('data-slide-to')) {
+      $target.data('bs.carousel').to(slideIndex)
+    }
+
+    e.preventDefault()
+  })
+
+  $(window).on('load', function () {
+    $('[data-ride="carousel"]').each(function () {
+      var $carousel = $(this)
+      $carousel.carousel($carousel.data())
+    })
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#collapse
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // COLLAPSE PUBLIC CLASS DEFINITION
+  // ================================
+
+  var Collapse = function (element, options) {
+    this.$element      = $(element)
+    this.options       = $.extend({}, Collapse.DEFAULTS, options)
+    this.transitioning = null
+
+    if (this.options.parent) this.$parent = $(this.options.parent)
+    if (this.options.toggle) this.toggle()
+  }
+
+  Collapse.DEFAULTS = {
+    toggle: true
+  }
+
+  Collapse.prototype.dimension = function () {
+    var hasWidth = this.$element.hasClass('width')
+    return hasWidth ? 'width' : 'height'
+  }
+
+  Collapse.prototype.show = function () {
+    if (this.transitioning || this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('show.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var actives = this.$parent && this.$parent.find('> .panel > .in')
+
+    if (actives && actives.length) {
+      var hasData = actives.data('bs.collapse')
+      if (hasData && hasData.transitioning) return
+      actives.collapse('hide')
+      hasData || actives.data('bs.collapse', null)
+    }
+
+    var dimension = this.dimension()
+
+    this.$element
+      .removeClass('collapse')
+      .addClass('collapsing')
+      [dimension](0)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.$element
+        .removeClass('collapsing')
+        .addClass('in')
+        [dimension]('auto')
+      this.transitioning = 0
+      this.$element.trigger('shown.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+    this.$element
+      .one($.support.transition.end, $.proxy(complete, this))
+      .emulateTransitionEnd(350)
+      [dimension](this.$element[0][scrollSize])
+  }
+
+  Collapse.prototype.hide = function () {
+    if (this.transitioning || !this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('hide.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var dimension = this.dimension()
+
+    this.$element
+      [dimension](this.$element[dimension]())
+      [0].offsetHeight
+
+    this.$element
+      .addClass('collapsing')
+      .removeClass('collapse')
+      .removeClass('in')
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.transitioning = 0
+      this.$element
+        .trigger('hidden.bs.collapse')
+        .removeClass('collapsing')
+        .addClass('collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    this.$element
+      [dimension](0)
+      .one($.support.transition.end, $.proxy(complete, this))
+      .emulateTransitionEnd(350)
+  }
+
+  Collapse.prototype.toggle = function () {
+    this[this.$element.hasClass('in') ? 'hide' : 'show']()
+  }
+
+
+  // COLLAPSE PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.collapse
+
+  $.fn.collapse = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.collapse')
+      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.collapse.Constructor = Collapse
+
+
+  // COLLAPSE NO CONFLICT
+  // ====================
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+  // COLLAPSE DATA-API
+  // =================
+
+  $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) {
+    var $this   = $(this), href
+    var target  = $this.attr('data-target')
+        || e.preventDefault()
+        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+    var $target = $(target)
+    var data    = $target.data('bs.collapse')
+    var option  = data ? 'toggle' : $this.data()
+    var parent  = $this.attr('data-parent')
+    var $parent = parent && $(parent)
+
+    if (!data || !data.transitioning) {
+      if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed')
+      $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+    }
+
+    $target.collapse(option)
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#dropdowns
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // DROPDOWN CLASS DEFINITION
+  // =========================
+
+  var backdrop = '.dropdown-backdrop'
+  var toggle   = '[data-toggle=dropdown]'
+  var Dropdown = function (element) {
+    var $el = $(element).on('click.bs.dropdown', this.toggle)
+  }
+
+  Dropdown.prototype.toggle = function (e) {
+    var $this = $(this)
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    clearMenus()
+
+    if (!isActive) {
+      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+        // if mobile we we use a backdrop because click events don't delegate
+        $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
+      }
+
+      $parent.trigger(e = $.Event('show.bs.dropdown'))
+
+      if (e.isDefaultPrevented()) return
+
+      $parent
+        .toggleClass('open')
+        .trigger('shown.bs.dropdown')
+
+      $this.focus()
+    }
+
+    return false
+  }
+
+  Dropdown.prototype.keydown = function (e) {
+    if (!/(38|40|27)/.test(e.keyCode)) return
+
+    var $this = $(this)
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    if (!isActive || (isActive && e.keyCode == 27)) {
+      if (e.which == 27) $parent.find(toggle).focus()
+      return $this.click()
+    }
+
+    var $items = $('[role=menu] li:not(.divider):visible a', $parent)
+
+    if (!$items.length) return
+
+    var index = $items.index($items.filter(':focus'))
+
+    if (e.keyCode == 38 && index > 0)                 index--                        // up
+    if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
+    if (!~index)                                      index=0
+
+    $items.eq(index).focus()
+  }
+
+  function clearMenus() {
+    $(backdrop).remove()
+    $(toggle).each(function (e) {
+      var $parent = getParent($(this))
+      if (!$parent.hasClass('open')) return
+      $parent.trigger(e = $.Event('hide.bs.dropdown'))
+      if (e.isDefaultPrevented()) return
+      $parent.removeClass('open').trigger('hidden.bs.dropdown')
+    })
+  }
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    var $parent = selector && $(selector)
+
+    return $parent && $parent.length ? $parent : $this.parent()
+  }
+
+
+  // DROPDOWN PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('dropdown')
+
+      if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  // DROPDOWN NO CONFLICT
+  // ====================
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  // APPLY TO STANDARD DROPDOWN ELEMENTS
+  // ===================================
+
+  $(document)
+    .on('click.bs.dropdown.data-api', clearMenus)
+    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.bs.dropdown.data-api'  , toggle, Dropdown.prototype.toggle)
+    .on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#modals
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // MODAL CLASS DEFINITION
+  // ======================
+
+  var Modal = function (element, options) {
+    this.options   = options
+    this.$element  = $(element)
+    this.$backdrop =
+    this.isShown   = null
+
+    if (this.options.remote) this.$element.load(this.options.remote)
+  }
+
+  Modal.DEFAULTS = {
+      backdrop: true
+    , keyboard: true
+    , show: true
+  }
+
+  Modal.prototype.toggle = function (_relatedTarget) {
+    return this[!this.isShown ? 'show' : 'hide'](_relatedTarget)
+  }
+
+  Modal.prototype.show = function (_relatedTarget) {
+    var that = this
+    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+    this.$element.trigger(e)
+
+    if (this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = true
+
+    this.escape()
+
+    this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+    this.backdrop(function () {
+      var transition = $.support.transition && that.$element.hasClass('fade')
+
+      if (!that.$element.parent().length) {
+        that.$element.appendTo(document.body) // don't move modals dom position
+      }
+
+      that.$element.show()
+
+      if (transition) {
+        that.$element[0].offsetWidth // force reflow
+      }
+
+      that.$element
+        .addClass('in')
+        .attr('aria-hidden', false)
+
+      that.enforceFocus()
+
+      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+      transition ?
+        that.$element.find('.modal-dialog') // wait for modal to slide in
+          .one($.support.transition.end, function () {
+            that.$element.focus().trigger(e)
+          })
+          .emulateTransitionEnd(300) :
+        that.$element.focus().trigger(e)
+    })
+  }
+
+  Modal.prototype.hide = function (e) {
+    if (e) e.preventDefault()
+
+    e = $.Event('hide.bs.modal')
+
+    this.$element.trigger(e)
+
+    if (!this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = false
+
+    this.escape()
+
+    $(document).off('focusin.bs.modal')
+
+    this.$element
+      .removeClass('in')
+      .attr('aria-hidden', true)
+      .off('click.dismiss.modal')
+
+    $.support.transition && this.$element.hasClass('fade') ?
+      this.$element
+        .one($.support.transition.end, $.proxy(this.hideModal, this))
+        .emulateTransitionEnd(300) :
+      this.hideModal()
+  }
+
+  Modal.prototype.enforceFocus = function () {
+    $(document)
+      .off('focusin.bs.modal') // guard against infinite focus loop
+      .on('focusin.bs.modal', $.proxy(function (e) {
+        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+          this.$element.focus()
+        }
+      }, this))
+  }
+
+  Modal.prototype.escape = function () {
+    if (this.isShown && this.options.keyboard) {
+      this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
+        e.which == 27 && this.hide()
+      }, this))
+    } else if (!this.isShown) {
+      this.$element.off('keyup.dismiss.bs.modal')
+    }
+  }
+
+  Modal.prototype.hideModal = function () {
+    var that = this
+    this.$element.hide()
+    this.backdrop(function () {
+      that.removeBackdrop()
+      that.$element.trigger('hidden.bs.modal')
+    })
+  }
+
+  Modal.prototype.removeBackdrop = function () {
+    this.$backdrop && this.$backdrop.remove()
+    this.$backdrop = null
+  }
+
+  Modal.prototype.backdrop = function (callback) {
+    var that    = this
+    var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+    if (this.isShown && this.options.backdrop) {
+      var doAnimate = $.support.transition && animate
+
+      this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+        .appendTo(document.body)
+
+      this.$element.on('click.dismiss.modal', $.proxy(function (e) {
+        if (e.target !== e.currentTarget) return
+        this.options.backdrop == 'static'
+          ? this.$element[0].focus.call(this.$element[0])
+          : this.hide.call(this)
+      }, this))
+
+      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+      this.$backdrop.addClass('in')
+
+      if (!callback) return
+
+      doAnimate ?
+        this.$backdrop
+          .one($.support.transition.end, callback)
+          .emulateTransitionEnd(150) :
+        callback()
+
+    } else if (!this.isShown && this.$backdrop) {
+      this.$backdrop.removeClass('in')
+
+      $.support.transition && this.$element.hasClass('fade')?
+        this.$backdrop
+          .one($.support.transition.end, callback)
+          .emulateTransitionEnd(150) :
+        callback()
+
+    } else if (callback) {
+      callback()
+    }
+  }
+
+
+  // MODAL PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.modal
+
+  $.fn.modal = function (option, _relatedTarget) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.modal')
+      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option](_relatedTarget)
+      else if (options.show) data.show(_relatedTarget)
+    })
+  }
+
+  $.fn.modal.Constructor = Modal
+
+
+  // MODAL NO CONFLICT
+  // =================
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+  // MODAL DATA-API
+  // ==============
+
+  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this   = $(this)
+    var href    = $this.attr('href')
+    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+    var option  = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+    e.preventDefault()
+
+    $target
+      .modal(option, this)
+      .one('hide', function () {
+        $this.is(':visible') && $this.focus()
+      })
+  })
+
+  $(document)
+    .on('show.bs.modal',  '.modal', function () { $(document.body).addClass('modal-open') })
+    .on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // TOOLTIP PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Tooltip = function (element, options) {
+    this.type       =
+    this.options    =
+    this.enabled    =
+    this.timeout    =
+    this.hoverState =
+    this.$element   = null
+
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.DEFAULTS = {
+    animation: true
+  , placement: 'top'
+  , selector: false
+  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+  , trigger: 'hover focus'
+  , title: ''
+  , delay: 0
+  , html: false
+  , container: false
+  }
+
+  Tooltip.prototype.init = function (type, element, options) {
+    this.enabled  = true
+    this.type     = type
+    this.$element = $(element)
+    this.options  = this.getOptions(options)
+
+    var triggers = this.options.trigger.split(' ')
+
+    for (var i = triggers.length; i--;) {
+      var trigger = triggers[i]
+
+      if (trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (trigger != 'manual') {
+        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focus'
+        var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
+
+        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+    }
+
+    this.options.selector ?
+      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+      this.fixTitle()
+  }
+
+  Tooltip.prototype.getDefaults = function () {
+    return Tooltip.DEFAULTS
+  }
+
+  Tooltip.prototype.getOptions = function (options) {
+    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+    if (options.delay && typeof options.delay == 'number') {
+      options.delay = {
+        show: options.delay
+      , hide: options.delay
+      }
+    }
+
+    return options
+  }
+
+  Tooltip.prototype.getDelegateOptions = function () {
+    var options  = {}
+    var defaults = this.getDefaults()
+
+    this._options && $.each(this._options, function (key, value) {
+      if (defaults[key] != value) options[key] = value
+    })
+
+    return options
+  }
+
+  Tooltip.prototype.enter = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'in'
+
+    if (!self.options.delay || !self.options.delay.show) return self.show()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'in') self.show()
+    }, self.options.delay.show)
+  }
+
+  Tooltip.prototype.leave = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'out'
+
+    if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'out') self.hide()
+    }, self.options.delay.hide)
+  }
+
+  Tooltip.prototype.show = function () {
+    var e = $.Event('show.bs.'+ this.type)
+
+    if (this.hasContent() && this.enabled) {
+      this.$element.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      var $tip = this.tip()
+
+      this.setContent()
+
+      if (this.options.animation) $tip.addClass('fade')
+
+      var placement = typeof this.options.placement == 'function' ?
+        this.options.placement.call(this, $tip[0], this.$element[0]) :
+        this.options.placement
+
+      var autoToken = /\s?auto?\s?/i
+      var autoPlace = autoToken.test(placement)
+      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+      $tip
+        .detach()
+        .css({ top: 0, left: 0, display: 'block' })
+        .addClass(placement)
+
+      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+      var pos          = this.getPosition()
+      var actualWidth  = $tip[0].offsetWidth
+      var actualHeight = $tip[0].offsetHeight
+
+      if (autoPlace) {
+        var $parent = this.$element.parent()
+
+        var orgPlacement = placement
+        var docScroll    = document.documentElement.scrollTop || document.body.scrollTop
+        var parentWidth  = this.options.container == 'body' ? window.innerWidth  : $parent.outerWidth()
+        var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
+        var parentLeft   = this.options.container == 'body' ? 0 : $parent.offset().left
+
+        placement = placement == 'bottom' && pos.top   + pos.height  + actualHeight - docScroll > parentHeight  ? 'top'    :
+                    placement == 'top'    && pos.top   - docScroll   - actualHeight < 0                         ? 'bottom' :
+                    placement == 'right'  && pos.right + actualWidth > parentWidth                              ? 'left'   :
+                    placement == 'left'   && pos.left  - actualWidth < parentLeft                               ? 'right'  :
+                    placement
+
+        $tip
+          .removeClass(orgPlacement)
+          .addClass(placement)
+      }
+
+      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+      this.applyPlacement(calculatedOffset, placement)
+      this.$element.trigger('shown.bs.' + this.type)
+    }
+  }
+
+  Tooltip.prototype.applyPlacement = function(offset, placement) {
+    var replace
+    var $tip   = this.tip()
+    var width  = $tip[0].offsetWidth
+    var height = $tip[0].offsetHeight
+
+    // manually read margins because getBoundingClientRect includes difference
+    var marginTop = parseInt($tip.css('margin-top'), 10)
+    var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+    // we must check for NaN for ie 8/9
+    if (isNaN(marginTop))  marginTop  = 0
+    if (isNaN(marginLeft)) marginLeft = 0
+
+    offset.top  = offset.top  + marginTop
+    offset.left = offset.left + marginLeft
+
+    $tip
+      .offset(offset)
+      .addClass('in')
+
+    // check to see if placing tip in new offset caused the tip to resize itself
+    var actualWidth  = $tip[0].offsetWidth
+    var actualHeight = $tip[0].offsetHeight
+
+    if (placement == 'top' && actualHeight != height) {
+      replace = true
+      offset.top = offset.top + height - actualHeight
+    }
+
+    if (/bottom|top/.test(placement)) {
+      var delta = 0
+
+      if (offset.left < 0) {
+        delta       = offset.left * -2
+        offset.left = 0
+
+        $tip.offset(offset)
+
+        actualWidth  = $tip[0].offsetWidth
+        actualHeight = $tip[0].offsetHeight
+      }
+
+      this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
+    } else {
+      this.replaceArrow(actualHeight - height, actualHeight, 'top')
+    }
+
+    if (replace) $tip.offset(offset)
+  }
+
+  Tooltip.prototype.replaceArrow = function(delta, dimension, position) {
+    this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
+  }
+
+  Tooltip.prototype.setContent = function () {
+    var $tip  = this.tip()
+    var title = this.getTitle()
+
+    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+    $tip.removeClass('fade in top bottom left right')
+  }
+
+  Tooltip.prototype.hide = function () {
+    var that = this
+    var $tip = this.tip()
+    var e    = $.Event('hide.bs.' + this.type)
+
+    function complete() {
+      if (that.hoverState != 'in') $tip.detach()
+    }
+
+    this.$element.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    $tip.removeClass('in')
+
+    $.support.transition && this.$tip.hasClass('fade') ?
+      $tip
+        .one($.support.transition.end, complete)
+        .emulateTransitionEnd(150) :
+      complete()
+
+    this.$element.trigger('hidden.bs.' + this.type)
+
+    return this
+  }
+
+  Tooltip.prototype.fixTitle = function () {
+    var $e = this.$element
+    if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+    }
+  }
+
+  Tooltip.prototype.hasContent = function () {
+    return this.getTitle()
+  }
+
+  Tooltip.prototype.getPosition = function () {
+    var el = this.$element[0]
+    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
+      width: el.offsetWidth
+    , height: el.offsetHeight
+    }, this.$element.offset())
+  }
+
+  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }
+  }
+
+  Tooltip.prototype.getTitle = function () {
+    var title
+    var $e = this.$element
+    var o  = this.options
+
+    title = $e.attr('data-original-title')
+      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+    return title
+  }
+
+  Tooltip.prototype.tip = function () {
+    return this.$tip = this.$tip || $(this.options.template)
+  }
+
+  Tooltip.prototype.arrow = function () {
+    return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
+  }
+
+  Tooltip.prototype.validate = function () {
+    if (!this.$element[0].parentNode) {
+      this.hide()
+      this.$element = null
+      this.options  = null
+    }
+  }
+
+  Tooltip.prototype.enable = function () {
+    this.enabled = true
+  }
+
+  Tooltip.prototype.disable = function () {
+    this.enabled = false
+  }
+
+  Tooltip.prototype.toggleEnabled = function () {
+    this.enabled = !this.enabled
+  }
+
+  Tooltip.prototype.toggle = function (e) {
+    var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
+    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+  }
+
+  Tooltip.prototype.destroy = function () {
+    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
+  }
+
+
+  // TOOLTIP PLUGIN DEFINITION
+  // =========================
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.tooltip')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tooltip.Constructor = Tooltip
+
+
+  // TOOLTIP NO CONFLICT
+  // ===================
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#popovers
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // POPOVER PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+  Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, {
+    placement: 'right'
+  , trigger: 'click'
+  , content: ''
+  , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+  })
+
+
+  // NOTE: POPOVER EXTENDS tooltip.js
+  // ================================
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+  Popover.prototype.constructor = Popover
+
+  Popover.prototype.getDefaults = function () {
+    return Popover.DEFAULTS
+  }
+
+  Popover.prototype.setContent = function () {
+    var $tip    = this.tip()
+    var title   = this.getTitle()
+    var content = this.getContent()
+
+    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+    $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+    $tip.removeClass('fade top bottom left right in')
+
+    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+    // this manually by checking the contents.
+    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+  }
+
+  Popover.prototype.hasContent = function () {
+    return this.getTitle() || this.getContent()
+  }
+
+  Popover.prototype.getContent = function () {
+    var $e = this.$element
+    var o  = this.options
+
+    return $e.attr('data-content')
+      || (typeof o.content == 'function' ?
+            o.content.call($e[0]) :
+            o.content)
+  }
+
+  Popover.prototype.arrow = function () {
+    return this.$arrow = this.$arrow || this.tip().find('.arrow')
+  }
+
+  Popover.prototype.tip = function () {
+    if (!this.$tip) this.$tip = $(this.options.template)
+    return this.$tip
+  }
+
+
+  // POPOVER PLUGIN DEFINITION
+  // =========================
+
+  var old = $.fn.popover
+
+  $.fn.popover = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.popover')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.popover.Constructor = Popover
+
+
+  // POPOVER NO CONFLICT
+  // ===================
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#scrollspy
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // SCROLLSPY CLASS DEFINITION
+  // ==========================
+
+  function ScrollSpy(element, options) {
+    var href
+    var process  = $.proxy(this.process, this)
+
+    this.$element       = $(element).is('body') ? $(window) : $(element)
+    this.$body          = $('body')
+    this.$scrollElement = this.$element.on('scroll.bs.scroll-spy.data-api', process)
+    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
+    this.selector       = (this.options.target
+      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      || '') + ' .nav li > a'
+    this.offsets        = $([])
+    this.targets        = $([])
+    this.activeTarget   = null
+
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.DEFAULTS = {
+    offset: 10
+  }
+
+  ScrollSpy.prototype.refresh = function () {
+    var offsetMethod = this.$element[0] == window ? 'offset' : 'position'
+
+    this.offsets = $([])
+    this.targets = $([])
+
+    var self     = this
+    var $targets = this.$body
+      .find(this.selector)
+      .map(function () {
+        var $el   = $(this)
+        var href  = $el.data('target') || $el.attr('href')
+        var $href = /^#\w/.test(href) && $(href)
+
+        return ($href
+          && $href.length
+          && [[ $href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]]) || null
+      })
+      .sort(function (a, b) { return a[0] - b[0] })
+      .each(function () {
+        self.offsets.push(this[0])
+        self.targets.push(this[1])
+      })
+  }
+
+  ScrollSpy.prototype.process = function () {
+    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
+    var scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+    var maxScroll    = scrollHeight - this.$scrollElement.height()
+    var offsets      = this.offsets
+    var targets      = this.targets
+    var activeTarget = this.activeTarget
+    var i
+
+    if (scrollTop >= maxScroll) {
+      return activeTarget != (i = targets.last()[0]) && this.activate(i)
+    }
+
+    for (i = offsets.length; i--;) {
+      activeTarget != targets[i]
+        && scrollTop >= offsets[i]
+        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+        && this.activate( targets[i] )
+    }
+  }
+
+  ScrollSpy.prototype.activate = function (target) {
+    this.activeTarget = target
+
+    $(this.selector)
+      .parents('.active')
+      .removeClass('active')
+
+    var selector = this.selector
+      + '[data-target="' + target + '"],'
+      + this.selector + '[href="' + target + '"]'
+
+    var active = $(selector)
+      .parents('li')
+      .addClass('active')
+
+    if (active.parent('.dropdown-menu').length)  {
+      active = active
+        .closest('li.dropdown')
+        .addClass('active')
+    }
+
+    active.trigger('activate')
+  }
+
+
+  // SCROLLSPY PLUGIN DEFINITION
+  // ===========================
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.scrollspy')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+
+  // SCROLLSPY NO CONFLICT
+  // =====================
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+  // SCROLLSPY DATA-API
+  // ==================
+
+  $(window).on('load', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      $spy.scrollspy($spy.data())
+    })
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#tabs
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // TAB CLASS DEFINITION
+  // ====================
+
+  var Tab = function (element) {
+    this.element = $(element)
+  }
+
+  Tab.prototype.show = function () {
+    var $this    = this.element
+    var $ul      = $this.closest('ul:not(.dropdown-menu)')
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    if ($this.parent('li').hasClass('active')) return
+
+    var previous = $ul.find('.active:last a')[0]
+    var e        = $.Event('show.bs.tab', {
+      relatedTarget: previous
+    })
+
+    $this.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    var $target = $(selector)
+
+    this.activate($this.parent('li'), $ul)
+    this.activate($target, $target.parent(), function () {
+      $this.trigger({
+        type: 'shown.bs.tab'
+      , relatedTarget: previous
+      })
+    })
+  }
+
+  Tab.prototype.activate = function (element, container, callback) {
+    var $active    = container.find('> .active')
+    var transition = callback
+      && $.support.transition
+      && $active.hasClass('fade')
+
+    function next() {
+      $active
+        .removeClass('active')
+        .find('> .dropdown-menu > .active')
+        .removeClass('active')
+
+      element.addClass('active')
+
+      if (transition) {
+        element[0].offsetWidth // reflow for transition
+        element.addClass('in')
+      } else {
+        element.removeClass('fade')
+      }
+
+      if (element.parent('.dropdown-menu')) {
+        element.closest('li.dropdown').addClass('active')
+      }
+
+      callback && callback()
+    }
+
+    transition ?
+      $active
+        .one($.support.transition.end, next)
+        .emulateTransitionEnd(150) :
+      next()
+
+    $active.removeClass('in')
+  }
+
+
+  // TAB PLUGIN DEFINITION
+  // =====================
+
+  var old = $.fn.tab
+
+  $.fn.tab = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.tab')
+
+      if (!data) $this.data('bs.tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tab.Constructor = Tab
+
+
+  // TAB NO CONFLICT
+  // ===============
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+  // TAB DATA-API
+  // ============
+
+  $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+    e.preventDefault()
+    $(this).tab('show')
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#affix
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // AFFIX CLASS DEFINITION
+  // ======================
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, Affix.DEFAULTS, options)
+    this.$window = $(window)
+      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))
+
+    this.$element = $(element)
+    this.affixed  =
+    this.unpin    = null
+
+    this.checkPosition()
+  }
+
+  Affix.RESET = 'affix affix-top affix-bottom'
+
+  Affix.DEFAULTS = {
+    offset: 0
+  }
+
+  Affix.prototype.checkPositionWithEventLoop = function () {
+    setTimeout($.proxy(this.checkPosition, this), 1)
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var scrollHeight = $(document).height()
+    var scrollTop    = this.$window.scrollTop()
+    var position     = this.$element.offset()
+    var offset       = this.options.offset
+    var offsetTop    = offset.top
+    var offsetBottom = offset.bottom
+
+    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function')    offsetTop    = offset.top()
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+    var affix = this.unpin   != null && (scrollTop + this.unpin <= position.top) ? false :
+                offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
+                offsetTop    != null && (scrollTop <= offsetTop) ? 'top' : false
+
+    if (this.affixed === affix) return
+    if (this.unpin) this.$element.css('top', '')
+
+    this.affixed = affix
+    this.unpin   = affix == 'bottom' ? position.top - scrollTop : null
+
+    this.$element.removeClass(Affix.RESET).addClass('affix' + (affix ? '-' + affix : ''))
+
+    if (affix == 'bottom') {
+      this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() })
+    }
+  }
+
+
+  // AFFIX PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.affix
+
+  $.fn.affix = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.affix')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.affix.Constructor = Affix
+
+
+  // AFFIX NO CONFLICT
+  // =================
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+  // AFFIX DATA-API
+  // ==============
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+      var data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      if (data.offsetBottom) data.offset.bottom = data.offsetBottom
+      if (data.offsetTop)    data.offset.top    = data.offsetTop
+
+      $spy.affix(data)
+    })
+  })
+
+}(window.jQuery);
diff --git a/docs/system-architecture/js/vendor/bootstrap-3.0.0/bootstrap.min.js b/docs/system-architecture/js/vendor/bootstrap-3.0.0/bootstrap.min.js
new file mode 100644
index 0000000..1765631
--- /dev/null
+++ b/docs/system-architecture/js/vendor/bootstrap-3.0.0/bootstrap.min.js
@@ -0,0 +1,6 @@
+/**
+* bootstrap.js v3.0.0 by @fat and @mdo
+* Copyright 2013 Twitter Inc.
+* http://www.apache.org/licenses/LICENSE-2.0
+*/
+if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b),f.trigger(d=a.Event("show.bs.dropdown")),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown"),e.focus()}return!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=a("[role=menu] li:not(.divider):visible a",f);if(h.length){var i=h.index(h.filter(":focus"));38==b.keyCode&&i>0&&i--,40==b.keyCode&&i<h.length-1&&i++,~i||(i=0),h.eq(i).focus()}}}};var g=a.fn.dropdown;a.fn.dropdown=function(b){return this.each(function(){var c=a(this),d=c.data("dropdown");d||c.data("dropdown",d=new f(this)),"string"==typeof b&&d[b].call(c)})},a.fn.dropdown.Constructor=f,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=g,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",e,f.prototype.toggle).on("keydown.bs.dropdown.data-api",e+", [role=menu]",f.prototype.keydown)}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.options=c,this.$element=a(b),this.$backdrop=this.isShown=null,this.options.remote&&this.$element.load(this.options.remote)};b.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},b.prototype.toggle=function(a){return this[this.isShown?"hide":"show"](a)},b.prototype.show=function(b){var c=this,d=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(d),this.isShown||d.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.$element.on("click.dismiss.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.backdrop(function(){var d=a.support.transition&&c.$element.hasClass("fade");c.$element.parent().length||c.$element.appendTo(document.body),c.$element.show(),d&&c.$element[0].offsetWidth,c.$element.addClass("in").attr("aria-hidden",!1),c.enforceFocus();var e=a.Event("shown.bs.modal",{relatedTarget:b});d?c.$element.find(".modal-dialog").one(a.support.transition.end,function(){c.$element.focus().trigger(e)}).emulateTransitionEnd(300):c.$element.focus().trigger(e)}))},b.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one(a.support.transition.end,a.proxy(this.hideModal,this)).emulateTransitionEnd(300):this.hideModal())},b.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.focus()},this))},b.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keyup.dismiss.bs.modal")},b.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden.bs.modal")})},b.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},b.prototype.backdrop=function(b){var c=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var d=a.support.transition&&c;if(this.$backdrop=a('<div class="modal-backdrop '+c+'" />').appendTo(document.body),this.$element.on("click.dismiss.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},b.DEFAULTS,e.data(),"object"==typeof c&&c);f||e.data("bs.modal",f=new b(this,g)),"string"==typeof c?f[c](d):g.show&&f.show(d)})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f,this).one("hide",function(){c.is(":visible")&&c.focus()})}),a(document).on("show.bs.modal",".modal",function(){a(document.body).addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){a(document.body).removeClass("modal-open")})}(window.jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focus",i="hover"==g?"mouseleave":"blur";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},b.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show),void 0):c.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide),void 0):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this.tip();this.setContent(),this.options.animation&&c.addClass("fade");var d="function"==typeof this.options.placement?this.options.placement.call(this,c[0],this.$element[0]):this.options.placement,e=/\s?auto?\s?/i,f=e.test(d);f&&(d=d.replace(e,"")||"top"),c.detach().css({top:0,left:0,display:"block"}).addClass(d),this.options.container?c.appendTo(this.options.container):c.insertAfter(this.$element);var g=this.getPosition(),h=c[0].offsetWidth,i=c[0].offsetHeight;if(f){var j=this.$element.parent(),k=d,l=document.documentElement.scrollTop||document.body.scrollTop,m="body"==this.options.container?window.innerWidth:j.outerWidth(),n="body"==this.options.container?window.innerHeight:j.outerHeight(),o="body"==this.options.container?0:j.offset().left;d="bottom"==d&&g.top+g.height+i-l>n?"top":"top"==d&&g.top-l-i<0?"bottom":"right"==d&&g.right+h>m?"left":"left"==d&&g.left-h<o?"right":d,c.removeClass(k).addClass(d)}var p=this.getCalculatedOffset(d,g,h,i);this.applyPlacement(p,d),this.$element.trigger("shown.bs."+this.type)}},b.prototype.applyPlacement=function(a,b){var c,d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),a.top=a.top+g,a.left=a.left+h,d.offset(a).addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;if("top"==b&&j!=f&&(c=!0,a.top=a.top+f-j),/bottom|top/.test(b)){var k=0;a.left<0&&(k=-2*a.left,a.left=0,d.offset(a),i=d[0].offsetWidth,j=d[0].offsetHeight),this.replaceArrow(k-e+i,i,"left")}else this.replaceArrow(j-f,j,"top");c&&d.offset(a)},b.prototype.replaceArrow=function(a,b,c){this.arrow().css(c,a?50*(1-a/b)+"%":"")},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},b.prototype.hide=function(){function b(){"in"!=c.hoverState&&d.detach()}var c=this,d=this.tip(),e=a.Event("hide.bs."+this.type);return this.$element.trigger(e),e.isDefaultPrevented()?void 0:(d.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d.one(a.support.transition.end,b).emulateTransitionEnd(150):b(),this.$element.trigger("hidden.bs."+this.type),this)},b.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},b.prototype.hasContent=function(){return this.getTitle()},b.prototype.getPosition=function(){var b=this.$element[0];return a.extend({},"function"==typeof b.getBoundingClientRect?b.getBoundingClientRect():{width:b.offsetWidth,height:b.offsetHeight},this.$element.offset())},b.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},b.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},b.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},b.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},b.prototype.enable=function(){this.enabled=!0},b.prototype.disable=function(){this.enabled=!1},b.prototype.toggleEnabled=function(){this.enabled=!this.enabled},b.prototype.toggle=function(b){var c=b?a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type):this;c.tip().hasClass("in")?c.leave(c):c.enter(c)},b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var c=a.fn.tooltip;a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof c&&c;e||d.data("bs.tooltip",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=c,this}}(window.jQuery),+function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");b.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery);
\ No newline at end of file
diff --git a/docs/system-architecture/js/vendor/jquery-1.9.1.min.js b/docs/system-architecture/js/vendor/jquery-1.9.1.min.js
new file mode 100644
index 0000000..32d50cb
--- /dev/null
+++ b/docs/system-architecture/js/vendor/jquery-1.9.1.min.js
@@ -0,0 +1,5 @@
+/*! jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery.min.map
+*/(function(e,t){var n,r,i=typeof t,o=e.document,a=e.location,s=e.jQuery,u=e.$,l={},c=[],p="1.9.1",f=c.concat,d=c.push,h=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,b=function(e,t){return new b.fn.init(e,t,r)},x=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?b.extend(e,r):r}},i={};return r.pipe=r.then,b.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*"),r=d.getElementsByTagName("a")[0],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav></:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},b.Event=function(e,n){return this instanceof b.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;
+return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,b(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){b.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="<a name='"+x+"'></a><div name='"+x+"'></div>",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="<input type='hidden' i=''/>",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}o=st.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||st.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&&gt(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.inArray(e,t)>=0===n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Nt=/^(?:checkbox|radio)$/i,Ct=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:b.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return b.clone(this,e,t)})},html:function(e){return b.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!b.support.htmlSerialize&&mt.test(e)||!b.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l)
+}b.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===b.css(e,"display")||!b.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||b.isNumeric(o)?o||0:a):a},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=ln(e,t),Pt.detach()),Gt[e]=n),n}function ln(e,t){var n=b(t.createElement(e)).appendTo(t.body),r=b.css(n[0],"display");return n.remove(),r}b.each(["height","width"],function(e,n){b.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(b.css(e,"display"))?b.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,i),i):0)}}}),b.support.opacity||(b.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=b.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===b.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),b(function(){b.support.reliableMarginRight||(b.cssHooks.marginRight={get:function(e,n){return n?b.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!b.support.pixelPosition&&b.fn.position&&b.each(["top","left"],function(e,n){b.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?b(e).position()[n]+"px":r):t}}})}),b.expr&&b.expr.filters&&(b.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!b.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||b.css(e,"display"))},b.expr.filters.visible=function(e){return!b.expr.filters.hidden(e)}),b.each({margin:"",padding:"",border:"Width"},function(e,t){b.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(b.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;b.fn.extend({serialize:function(){return b.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=b.prop(this,"elements");return e?b.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!b(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Nt.test(e))}).map(function(e,t){var n=b(this).val();return null==n?null:b.isArray(n)?b.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),b.param=function(e,n){var r,i=[],o=function(e,t){t=b.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=b.ajaxSettings&&b.ajaxSettings.traditional),b.isArray(e)||e.jquery&&!b.isPlainObject(e))b.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(b.isArray(t))b.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==b.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}b.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){b.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),b.fn.hover=function(e,t){return this.mouseenter(e).mouseleave(t||e)};var mn,yn,vn=b.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Nn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Cn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=b.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=a.href}catch(Ln){yn=o.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(w)||[];if(b.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(u){var l;return o[u]=!0,b.each(e[u]||[],function(e,u){var c=u(n,r,i);return"string"!=typeof c||a||o[c]?a?!(l=c):t:(n.dataTypes.unshift(c),s(c),!1)}),l}return s(n.dataTypes[0])||!o["*"]&&s("*")}function Mn(e,n){var r,i,o=b.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&b.extend(!0,e,r),e}b.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,u=e.indexOf(" ");return u>=0&&(i=e.slice(u,e.length),e=e.slice(0,u)),b.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&b.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?b("<div>").append(b.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},b.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){b.fn[t]=function(e){return this.on(t,e)}}),b.each(["get","post"],function(e,n){b[n]=function(e,r,i,o){return b.isFunction(r)&&(o=o||i,i=r,r=t),b.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),b.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Nn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":b.parseJSON,"text xml":b.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Mn(Mn(e,b.ajaxSettings),t):Mn(b.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,u,l,c,p=b.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?b(f):b.event,h=b.Deferred(),g=b.Callbacks("once memory"),m=p.statusCode||{},y={},v={},x=0,T="canceled",N={readyState:0,getResponseHeader:function(e){var t;if(2===x){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===x?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)m[t]=[m[t],e[t]];else N.always(e[N.status]);return this},abort:function(e){var t=e||T;return l&&l.abort(t),k(0,t),this}};if(h.promise(N).complete=g.add,N.success=N.done,N.error=N.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=b.trim(p.dataType||"*").toLowerCase().match(w)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?80:443))==(mn[3]||("http:"===mn[1]?80:443)))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=b.param(p.data,p.traditional)),qn(An,p,n,N),2===x)return N;u=p.global,u&&0===b.active++&&b.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Cn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(b.lastModified[o]&&N.setRequestHeader("If-Modified-Since",b.lastModified[o]),b.etag[o]&&N.setRequestHeader("If-None-Match",b.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&N.setRequestHeader("Content-Type",p.contentType),N.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)N.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,N,p)===!1||2===x))return N.abort();T="abort";for(i in{success:1,error:1,complete:1})N[i](p[i]);if(l=qn(jn,p,n,N)){N.readyState=1,u&&d.trigger("ajaxSend",[N,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){N.abort("timeout")},p.timeout));try{x=1,l.send(y,k)}catch(C){if(!(2>x))throw C;k(-1,C)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,C=n;2!==x&&(x=2,s&&clearTimeout(s),l=t,a=i||"",N.readyState=e>0?4:0,r&&(w=_n(p,N,r)),e>=200&&300>e||304===e?(p.ifModified&&(T=N.getResponseHeader("Last-Modified"),T&&(b.lastModified[o]=T),T=N.getResponseHeader("etag"),T&&(b.etag[o]=T)),204===e?(c=!0,C="nocontent"):304===e?(c=!0,C="notmodified"):(c=Fn(p,w),C=c.state,y=c.data,v=c.error,c=!v)):(v=C,(e||!C)&&(C="error",0>e&&(e=0))),N.status=e,N.statusText=(n||C)+"",c?h.resolveWith(f,[y,C,N]):h.rejectWith(f,[N,C,v]),N.statusCode(m),m=t,u&&d.trigger(c?"ajaxSuccess":"ajaxError",[N,p,c?y:v]),g.fireWith(f,[N,C]),u&&(d.trigger("ajaxComplete",[N,p]),--b.active||b.event.trigger("ajaxStop")))}return N},getScript:function(e,n){return b.get(e,t,n,"script")},getJSON:function(e,t,n){return b.get(e,t,n,"json")}});function _n(e,n,r){var i,o,a,s,u=e.contents,l=e.dataTypes,c=e.responseFields;for(s in c)s in r&&(n[c[s]]=r[s]);while("*"===l[0])l.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in u)if(u[s]&&u[s].test(o)){l.unshift(s);break}if(l[0]in r)a=l[0];else{for(s in r){if(!l[0]||e.converters[s+" "+l[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==l[0]&&l.unshift(a),r[a]):t}function Fn(e,t){var n,r,i,o,a={},s=0,u=e.dataTypes.slice(),l=u[0];if(e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u[1])for(i in e.converters)a[i.toLowerCase()]=e.converters[i];for(;r=u[++s];)if("*"!==r){if("*"!==l&&l!==r){if(i=a[l+" "+r]||a["* "+r],!i)for(n in a)if(o=n.split(" "),o[1]===r&&(i=a[l+" "+o[0]]||a["* "+o[0]])){i===!0?i=a[n]:a[n]!==!0&&(r=o[0],u.splice(s--,0,r));break}if(i!==!0)if(i&&e["throws"])t=i(t);else try{t=i(t)}catch(c){return{state:"parsererror",error:i?c:"No conversion from "+l+" to "+r}}}l=r}return{state:"success",data:t}}b.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return b.globalEval(e),e}}}),b.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),b.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=o.head||b("head")[0]||o.documentElement;return{send:function(t,i){n=o.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var On=[],Bn=/(=)\?(?=&|$)|\?\?/;b.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=On.pop()||b.expando+"_"+vn++;return this[e]=!0,e}}),b.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=b.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||b.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,On.push(o)),s&&b.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}b.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=b.ajaxSettings.xhr(),b.support.cors=!!Rn&&"withCredentials"in Rn,Rn=b.support.ajax=!!Rn,Rn&&b.ajaxTransport(function(n){if(!n.crossDomain||b.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,p;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=b.noop,$n&&delete Pn[a]),i)4!==u.readyState&&u.abort();else{p={},s=u.status,l=u.getAllResponseHeaders(),"string"==typeof u.responseText&&(p.text=u.responseText);try{c=u.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,l)},n.async?4===u.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},b(e).unload($n)),Pn[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+x+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n,r,i=this.createTween(e,t),o=Yn.exec(t),a=i.cur(),s=+a||0,u=1,l=20;if(o){if(n=+o[2],r=o[3]||(b.cssNumber[e]?"":"px"),"px"!==r&&s){s=b.css(i.elem,e,!0)||n||1;do u=u||".5",s/=u,b.style(i.elem,e,s+r);while(u!==(u=i.cur()/a)&&1!==u&&--l)}i.unit=r,i.start=s,i.end=o[1]?s+(o[1]+1)*n:n}return i}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=b.now()}function Zn(e,t){b.each(t,function(t,n){var r=(Qn[t]||[]).concat(Qn["*"]),i=0,o=r.length;for(;o>i;i++)if(r[i].call(e,t,n))return})}function er(e,t,n){var r,i,o=0,a=Gn.length,s=b.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;for(;u>a;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),1>o&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:b.extend({},t),opts:b.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=b.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?s.resolveWith(e,[l,t]):s.rejectWith(e,[l,t]),this}}),c=l.props;for(tr(c,l.opts.specialEasing);a>o;o++)if(r=Gn[o].call(l,e,c,l.opts))return r;return Zn(l,c),b.isFunction(l.opts.start)&&l.opts.start.call(e,l),b.fx.timer(b.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function tr(e,t){var n,r,i,o,a;for(i in e)if(r=b.camelCase(i),o=t[r],n=e[i],b.isArray(n)&&(o=n[1],n=e[i]=n[0]),i!==r&&(e[r]=n,delete e[i]),a=b.cssHooks[r],a&&"expand"in a){n=a.expand(n),delete e[r];for(i in n)i in e||(e[i]=n[i],t[i]=o)}else t[r]=o}b.Animation=b.extend(er,{tweener:function(e,t){b.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,u,l,c,p,f=this,d=e.style,h={},g=[],m=e.nodeType&&nn(e);n.queue||(c=b._queueHooks(e,"fx"),null==c.unqueued&&(c.unqueued=0,p=c.empty.fire,c.empty.fire=function(){c.unqueued||p()}),c.unqueued++,f.always(function(){f.always(function(){c.unqueued--,b.queue(e,"fx").length||c.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],"inline"===b.css(e,"display")&&"none"===b.css(e,"float")&&(b.support.inlineBlockNeedsLayout&&"inline"!==un(e.nodeName)?d.zoom=1:d.display="inline-block")),n.overflow&&(d.overflow="hidden",b.support.shrinkWrapBlocks||f.always(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(i in t)if(a=t[i],Vn.exec(a)){if(delete t[i],u=u||"toggle"===a,a===(m?"hide":"show"))continue;g.push(i)}if(o=g.length){s=b._data(e,"fxshow")||b._data(e,"fxshow",{}),"hidden"in s&&(m=s.hidden),u&&(s.hidden=!m),m?b(e).show():f.done(function(){b(e).hide()}),f.done(function(){var t;b._removeData(e,"fxshow");for(t in h)b.style(e,t,h[t])});for(i=0;o>i;i++)r=g[i],l=f.createTween(r,m?s[r]:0),h[r]=s[r]||b.style(e,r),r in s||(s[r]=l.start,m&&(l.end=l.start,l.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}b.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(b.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?b.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=b.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){b.fx.step[e.prop]?b.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[b.cssProps[e.prop]]||b.cssHooks[e.prop])?b.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},b.each(["toggle","show","hide"],function(e,t){var n=b.fn[t];b.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),b.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=b.isEmptyObject(e),o=b.speed(t,n,r),a=function(){var t=er(this,b.extend({},e),o);a.finish=function(){t.stop(!0)},(i||b._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=b.timers,a=b._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&b.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=b._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=b.timers,a=r?r.length:0;for(n.finish=!0,b.queue(this,e,[]),i&&i.cur&&i.cur.finish&&i.cur.finish.call(this),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}b.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){b.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),b.speed=function(e,t,n){var r=e&&"object"==typeof e?b.extend({},e):{complete:n||!n&&t||b.isFunction(e)&&e,duration:e,easing:n&&t||t&&!b.isFunction(t)&&t};return r.duration=b.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in b.fx.speeds?b.fx.speeds[r.duration]:b.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){b.isFunction(r.old)&&r.old.call(this),r.queue&&b.dequeue(this,r.queue)},r},b.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},b.timers=[],b.fx=rr.prototype.init,b.fx.tick=function(){var e,n=b.timers,r=0;for(Xn=b.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||b.fx.stop(),Xn=t},b.fx.timer=function(e){e()&&b.timers.push(e)&&b.fx.start()},b.fx.interval=13,b.fx.start=function(){Un||(Un=setInterval(b.fx.tick,b.fx.interval))},b.fx.stop=function(){clearInterval(Un),Un=null},b.fx.speeds={slow:600,fast:200,_default:400},b.fx.step={},b.expr&&b.expr.filters&&(b.expr.filters.animated=function(e){return b.grep(b.timers,function(t){return e===t.elem}).length}),b.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){b.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,b.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},b.offset={setOffset:function(e,t,n){var r=b.css(e,"position");"static"===r&&(e.style.position="relative");var i=b(e),o=i.offset(),a=b.css(e,"top"),s=b.css(e,"left"),u=("absolute"===r||"fixed"===r)&&b.inArray("auto",[a,s])>-1,l={},c={},p,f;u?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),b.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(l.top=t.top-o.top+p),null!=t.left&&(l.left=t.left-o.left+f),"using"in t?t.using.call(e,l):i.css(l)}},b.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===b.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),b.nodeName(e[0],"html")||(n=e.offset()),n.top+=b.css(e[0],"borderTopWidth",!0),n.left+=b.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-b.css(r,"marginTop",!0),left:t.left-n.left-b.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||o.documentElement;while(e&&!b.nodeName(e,"html")&&"static"===b.css(e,"position"))e=e.offsetParent;return e||o.documentElement})}}),b.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);b.fn[e]=function(i){return b.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?b(a).scrollLeft():o,r?o:b(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return b.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}b.each({Height:"height",Width:"width"},function(e,n){b.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){b.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return b.access(this,function(n,r,i){var o;return b.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?b.css(n,r,s):b.style(n,r,i,s)},n,a?i:t,a,null)}})}),e.jQuery=e.$=b,"function"==typeof define&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return b})})(window);
diff --git a/docs/system-architecture/js/vendor/modernizr-2.6.2.min.js b/docs/system-architecture/js/vendor/modernizr-2.6.2.min.js
new file mode 100644
index 0000000..f65d479
--- /dev/null
+++ b/docs/system-architecture/js/vendor/modernizr-2.6.2.min.js
@@ -0,0 +1,4 @@
+/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
+ * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
+ */
+;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d<e;d++)u[c[d]]=c[d]in k;return u.list&&(u.list=!!b.createElement("datalist")&&!!a.HTMLDataListElement),u}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),e.inputtypes=function(a){for(var d=0,e,f,h,i=a.length;d<i;d++)k.setAttribute("type",f=a[d]),e=k.type!=="text",e&&(k.value=l,k.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(f)&&k.style.WebkitAppearance!==c?(g.appendChild(k),h=b.defaultView,e=h.getComputedStyle&&h.getComputedStyle(k,null).WebkitAppearance!=="textfield"&&k.offsetHeight!==0,g.removeChild(k)):/^(search|tel)$/.test(f)||(/^(url|email)$/.test(f)?e=k.checkValidity&&k.checkValidity()===!1:e=k.value!=l)),t[a[d]]=!!e;return t}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k=b.createElement("input"),l=":)",m={}.toString,n=" -webkit- -moz- -o- -ms- ".split(" "),o="Webkit Moz O ms",p=o.split(" "),q=o.toLowerCase().split(" "),r={svg:"http://www.w3.org/2000/svg"},s={},t={},u={},v=[],w=v.slice,x,y=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="<svg/>",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function p(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return r.shivMethods?n(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+l().join().replace(/\w+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(r,b.frag)}function q(a){a||(a=b);var c=m(a);return r.shivCSS&&!f&&!c.hasCSS&&(c.hasCSS=!!k(a,"article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}")),j||p(a,c),a}var c=a.html5||{},d=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,e=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,f,g="_html5shiv",h=0,i={},j;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))};
diff --git a/docs/system-architecture/js/vendor/toc-0.1.2/jquery.toc.min.js b/docs/system-architecture/js/vendor/toc-0.1.2/jquery.toc.min.js
new file mode 100644
index 0000000..53a2c62
--- /dev/null
+++ b/docs/system-architecture/js/vendor/toc-0.1.2/jquery.toc.min.js
@@ -0,0 +1,8 @@
+/*!
+ * toc - jQuery Table of Contents Plugin
+ * v0.1.2
+ * http://projects.jga.me/toc/
+ * copyright Greg Allen 2013
+ * MIT License
+*/
+(function(t){t.fn.toc=function(e){var n,i=this,r=t.extend({},jQuery.fn.toc.defaults,e),o=t(r.container),a=t(r.selectors,o),l=[],h=r.prefix+"-active",s=function(e){if(r.smoothScrolling){e.preventDefault();var n=t(e.target).attr("href"),o=t(n);t("body,html").animate({scrollTop:o.offset().top},400,"swing",function(){location.hash=n})}t("li",i).removeClass(h),t(e.target).parent().addClass(h)},c=function(){n&&clearTimeout(n),n=setTimeout(function(){for(var e,n=t(window).scrollTop(),o=0,a=l.length;a>o;o++)if(l[o]>=n){t("li",i).removeClass(h),e=t("li:eq("+(o-1)+")",i).addClass(h),r.onHighlight(e);break}},50)};return r.highlightOnScroll&&(t(window).bind("scroll",c),c()),this.each(function(){var e=t(this),n=t("<ul/>");a.each(function(i,o){var a=t(o);l.push(a.offset().top-r.highlightOffset),t("<span/>").attr("id",r.anchorName(i,o,r.prefix)).insertBefore(a);var h=t("<a/>").text(r.headerText(i,o,a)).attr("href","#"+r.anchorName(i,o,r.prefix)).bind("click",function(n){s(n),e.trigger("selected",t(this).attr("href"))}),c=t("<li/>").addClass(r.itemClass(i,o,a,r.prefix)).append(h);n.append(c)}),e.html(n)})},jQuery.fn.toc.defaults={container:"body",selectors:"h1,h2,h3",smoothScrolling:!0,prefix:"toc",onHighlight:function(){},highlightOnScroll:!0,highlightOffset:100,anchorName:function(t,e,n){return n+t},headerText:function(t,e,n){return n.text()},itemClass:function(t,e,n,i){return i+"-"+n[0].tagName.toLowerCase()}}})(jQuery);
\ No newline at end of file
diff --git a/docs/system-architecture/robots.txt b/docs/system-architecture/robots.txt
new file mode 100644
index 0000000..ee2cc21
--- /dev/null
+++ b/docs/system-architecture/robots.txt
@@ -0,0 +1,3 @@
+# robotstxt.org/
+
+User-agent: *
diff --git a/fineract-db/mifospltaform-tenants-first-time-install.sql b/fineract-db/mifospltaform-tenants-first-time-install.sql
new file mode 100644
index 0000000..38f4c89
--- /dev/null
+++ b/fineract-db/mifospltaform-tenants-first-time-install.sql
@@ -0,0 +1,140 @@
+-- MySQL dump 10.13  Distrib 5.1.60, for Win32 (ia32)
+--
+-- Host: localhost    Database: mifosplatform-tenants
+-- ------------------------------------------------------
+-- Server version	5.1.60-community
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `schema_version`
+--
+
+DROP TABLE IF EXISTS `schema_version`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `schema_version` (
+  `version_rank` int(11) NOT NULL,
+  `installed_rank` int(11) NOT NULL,
+  `version` varchar(50) NOT NULL,
+  `description` varchar(200) NOT NULL,
+  `type` varchar(20) NOT NULL,
+  `script` varchar(1000) NOT NULL,
+  `checksum` int(11) DEFAULT NULL,
+  `installed_by` varchar(100) NOT NULL,
+  `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `execution_time` int(11) NOT NULL,
+  `success` tinyint(1) NOT NULL,
+  PRIMARY KEY (`version`),
+  KEY `schema_version_vr_idx` (`version_rank`),
+  KEY `schema_version_ir_idx` (`installed_rank`),
+  KEY `schema_version_s_idx` (`success`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `schema_version`
+--
+
+LOCK TABLES `schema_version` WRITE;
+/*!40000 ALTER TABLE `schema_version` DISABLE KEYS */;
+INSERT INTO `schema_version` (`version_rank`, `installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES
+  (1, 1, '1', 'mifos-platform-shared-tenants', 'SQL', 'V1__mifos-platform-shared-tenants.sql', -486745552, 'root', '2014-10-12 22:13:50', 896, 1),
+  (2, 2, '2', 'externalize-connection-properties', 'SQL', 'V2__externalize-connection-properties.sql', 210473669, 'root', '2014-10-12 22:13:51', 661, 1);/*!40000 ALTER TABLE `schema_version` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `tenants`
+--
+
+DROP TABLE IF EXISTS `tenants`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tenants` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `identifier` varchar(100) NOT NULL,
+  `name` varchar(100) NOT NULL,
+  `schema_name` varchar(100) NOT NULL,
+  `timezone_id` varchar(100) NOT NULL,
+  `country_id` int(11) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `schema_server` varchar(100) NOT NULL DEFAULT 'localhost',
+  `schema_server_port` varchar(10) NOT NULL DEFAULT '3306',
+  `schema_username` varchar(100) NOT NULL DEFAULT 'root',
+  `schema_password` varchar(100) NOT NULL DEFAULT 'mysql',
+  `auto_update` tinyint(1) NOT NULL DEFAULT '1',
+  `pool_initial_size` int(5) DEFAULT 5,
+  `pool_validation_interval` int(11) DEFAULT 30000,
+  `pool_remove_abandoned` tinyint(1) DEFAULT 1,
+  `pool_remove_abandoned_timeout` int(5) DEFAULT 60,
+  `pool_log_abandoned` tinyint(1) DEFAULT 1,
+  `pool_abandon_when_percentage_full` int(5) DEFAULT 50,
+  `pool_test_on_borrow` tinyint(1) DEFAULT 1,
+  `pool_max_active` int(5) DEFAULT 40,
+  `pool_min_idle` int(5) DEFAULT 20,
+  `pool_max_idle` int(5) DEFAULT 10,
+  `pool_suspect_timeout` int(5) DEFAULT 60,
+  `pool_time_between_eviction_runs_millis` int(11) DEFAULT 34000,
+  `pool_min_evictable_idle_time_millis` int(11) DEFAULT 60000,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `tenants`
+--
+
+LOCK TABLES `tenants` WRITE;
+/*!40000 ALTER TABLE `tenants` DISABLE KEYS */;
+INSERT INTO `tenants` VALUES 
+(1,'default','default','mifostenant-default','Asia/Kolkata',NULL,NULL,NULL,NULL,'localhost','3306','root','mysql',1,5,30000,1,60,1,50,1,40,20,10,60,34000,60000);
+/*!40000 ALTER TABLE `tenants` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `timezones`
+--
+
+DROP TABLE IF EXISTS `timezones`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `timezones` (
+  `id` int(3) NOT NULL AUTO_INCREMENT,
+  `country_code` varchar(2) NOT NULL,
+  `timezonename` varchar(100) NOT NULL,
+  `comments` varchar(150) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=416 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `timezones`
+--
+
+LOCK TABLES `timezones` WRITE;
+/*!40000 ALTER TABLE `timezones` DISABLE KEYS */;
+INSERT INTO `timezones` VALUES (1,'AD','Europe/Andorra',NULL),(2,'AE','Asia/Dubai',''),(3,'AF','Asia/Kabul',''),(4,'AG','America/Antigua',''),(5,'AI','America/Anguilla',''),(6,'AL','Europe/Tirane',''),(7,'AM','Asia/Yerevan',''),(8,'AO','Africa/Luanda',''),(9,'AQ','Antarctica/McMurdo','McMurdo Station, Ross Island'),(10,'AQ','Antarctica/South_Pole','Amundsen-Scott Station, South Pole'),(11,'AQ','Antarctica/Rothera','Rothera Station, Adelaide Island'),(12,'AQ','Antarctica/Palmer','Palmer Station, Anvers Island'),(13,'AQ','Antarctica/Mawson','Mawson Station, Holme Bay'),(14,'AQ','Antarctica/Davis','Davis Station, Vestfold Hills'),(15,'AQ','Antarctica/Casey','Casey Station, Bailey Peninsula'),(16,'AQ','Antarctica/Vostok','Vostok Station, Lake Vostok'),(17,'AQ','Antarctica/DumontDUrville','Dumont-dUrville Station, Terre Adelie'),(18,'AQ','Antarctica/Syowa','Syowa Station, E Ongul I'),(19,'AQ','Antarctica/Macquarie','Macquarie Island Station, Macquarie Island'),(20,'AR','America/Argentina/Buenos_Aires','Buenos Aires (BA, CF)'),(21,'AR','America/Argentina/Cordoba','most locations (CB, CC, CN, ER, FM, MN, SE, SF)'),(22,'AR','America/Argentina/Salta','(SA, LP, NQ, RN)'),(23,'AR','America/Argentina/Jujuy','Jujuy (JY)'),(24,'AR','America/Argentina/Tucuman','Tucuman (TM)'),(25,'AR','America/Argentina/Catamarca','Catamarca (CT), Chubut (CH)'),(26,'AR','America/Argentina/La_Rioja','La Rioja (LR)'),(27,'AR','America/Argentina/San_Juan','San Juan (SJ)'),(28,'AR','America/Argentina/Mendoza','Mendoza (MZ)'),(29,'AR','America/Argentina/San_Luis','San Luis (SL)'),(30,'AR','America/Argentina/Rio_Gallegos','Santa Cruz (SC)'),(31,'AR','America/Argentina/Ushuaia','Tierra del Fuego (TF)'),(32,'AS','Pacific/Pago_Pago',''),(33,'AT','Europe/Vienna',''),(34,'AU','Australia/Lord_Howe','Lord Howe Island'),(35,'AU','Australia/Hobart','Tasmania - most locations'),(36,'AU','Australia/Currie','Tasmania - King Island'),(37,'AU','Australia/Melbourne','Victoria'),(38,'AU','Australia/Sydney','New South Wales - most locations'),(39,'AU','Australia/Broken_Hill','New South Wales - Yancowinna'),(40,'AU','Australia/Brisbane','Queensland - most locations'),(41,'AU','Australia/Lindeman','Queensland - Holiday Islands'),(42,'AU','Australia/Adelaide','South Australia'),(43,'AU','Australia/Darwin','Northern Territory'),(44,'AU','Australia/Perth','Western Australia - most locations'),(45,'AU','Australia/Eucla','Western Australia - Eucla area'),(46,'AW','America/Aruba',''),(47,'AX','Europe/Mariehamn',''),(48,'AZ','Asia/Baku',''),(49,'BA','Europe/Sarajevo',''),(50,'BB','America/Barbados',''),(51,'BD','Asia/Dhaka',''),(52,'BE','Europe/Brussels',''),(53,'BF','Africa/Ouagadougou',''),(54,'BG','Europe/Sofia',''),(55,'BH','Asia/Bahrain',''),(56,'BI','Africa/Bujumbura',''),(57,'BJ','Africa/Porto-Novo',''),(58,'BL','America/St_Barthelemy',''),(59,'BM','Atlantic/Bermuda',''),(60,'BN','Asia/Brunei',''),(61,'BO','America/La_Paz',''),(62,'BQ','America/Kralendijk',''),(63,'BR','America/Noronha','Atlantic islands'),(64,'BR','America/Belem','Amapa, E Para'),(65,'BR','America/Fortaleza','NE Brazil (MA, PI, CE, RN, PB)'),(66,'BR','America/Recife','Pernambuco'),(67,'BR','America/Araguaina','Tocantins'),(68,'BR','America/Maceio','Alagoas, Sergipe'),(69,'BR','America/Bahia','Bahia'),(70,'BR','America/Sao_Paulo','S & SE Brazil (GO, DF, MG, ES, RJ, SP, PR, SC, RS)'),(71,'BR','America/Campo_Grande','Mato Grosso do Sul'),(72,'BR','America/Cuiaba','Mato Grosso'),(73,'BR','America/Santarem','W Para'),(74,'BR','America/Porto_Velho','Rondonia'),(75,'BR','America/Boa_Vista','Roraima'),(76,'BR','America/Manaus','E Amazonas'),(77,'BR','America/Eirunepe','W Amazonas'),(78,'BR','America/Rio_Branco','Acre'),(79,'BS','America/Nassau',''),(80,'BT','Asia/Thimphu',''),(81,'BW','Africa/Gaborone',''),(82,'BY','Europe/Minsk',''),(83,'BZ','America/Belize',''),(84,'CA','America/St_Johns','Newfoundland Time, including SE Labrador'),(85,'CA','America/Halifax','Atlantic Time - Nova Scotia (most places), PEI'),(86,'CA','America/Glace_Bay','Atlantic Time - Nova Scotia - places that did not observe DST 1966-1971'),(87,'CA','America/Moncton','Atlantic Time - New Brunswick'),(88,'CA','America/Goose_Bay','Atlantic Time - Labrador - most locations'),(89,'CA','America/Blanc-Sablon','Atlantic Standard Time - Quebec - Lower North Shore'),(90,'CA','America/Montreal','Eastern Time - Quebec - most locations'),(91,'CA','America/Toronto','Eastern Time - Ontario - most locations'),(92,'CA','America/Nipigon','Eastern Time - Ontario & Quebec - places that did not observe DST 1967-1973'),(93,'CA','America/Thunder_Bay','Eastern Time - Thunder Bay, Ontario'),(94,'CA','America/Iqaluit','Eastern Time - east Nunavut - most locations'),(95,'CA','America/Pangnirtung','Eastern Time - Pangnirtung, Nunavut'),(96,'CA','America/Resolute','Central Standard Time - Resolute, Nunavut'),(97,'CA','America/Atikokan','Eastern Standard Time - Atikokan, Ontario and Southampton I, Nunavut'),(98,'CA','America/Rankin_Inlet','Central Time - central Nunavut'),(99,'CA','America/Winnipeg','Central Time - Manitoba & west Ontario'),(100,'CA','America/Rainy_River','Central Time - Rainy River & Fort Frances, Ontario'),(101,'CA','America/Regina','Central Standard Time - Saskatchewan - most locations'),(102,'CA','America/Swift_Current','Central Standard Time - Saskatchewan - midwest'),(103,'CA','America/Edmonton','Mountain Time - Alberta, east British Columbia & west Saskatchewan'),(104,'CA','America/Cambridge_Bay','Mountain Time - west Nunavut'),(105,'CA','America/Yellowknife','Mountain Time - central Northwest Territories'),(106,'CA','America/Inuvik','Mountain Time - west Northwest Territories'),(107,'CA','America/Creston','Mountain Standard Time - Creston, British Columbia'),(108,'CA','America/Dawson_Creek','Mountain Standard Time - Dawson Creek & Fort Saint John, British Columbia'),(109,'CA','America/Vancouver','Pacific Time - west British Columbia'),(110,'CA','America/Whitehorse','Pacific Time - south Yukon'),(111,'CA','America/Dawson','Pacific Time - north Yukon'),(112,'CC','Indian/Cocos',''),(113,'CD','Africa/Kinshasa','west Dem. Rep. of Congo'),(114,'CD','Africa/Lubumbashi','east Dem. Rep. of Congo'),(115,'CF','Africa/Bangui',''),(116,'CG','Africa/Brazzaville',''),(117,'CH','Europe/Zurich',''),(118,'CI','Africa/Abidjan',''),(119,'CK','Pacific/Rarotonga',''),(120,'CL','America/Santiago','most locations'),(121,'CL','Pacific/Easter','Easter Island & Sala y Gomez'),(122,'CM','Africa/Douala',''),(123,'CN','Asia/Shanghai','east China - Beijing, Guangdong, Shanghai, etc.'),(124,'CN','Asia/Harbin','Heilongjiang (except Mohe), Jilin'),(125,'CN','Asia/Chongqing','central China - Sichuan, Yunnan, Guangxi, Shaanxi, Guizhou, etc.'),(126,'CN','Asia/Urumqi','most of Tibet & Xinjiang'),(127,'CN','Asia/Kashgar','west Tibet & Xinjiang'),(128,'CO','America/Bogota',''),(129,'CR','America/Costa_Rica',''),(130,'CU','America/Havana',''),(131,'CV','Atlantic/Cape_Verde',''),(132,'CW','America/Curacao',''),(133,'CX','Indian/Christmas',''),(134,'CY','Asia/Nicosia',''),(135,'CZ','Europe/Prague',''),(136,'DE','Europe/Berlin',''),(137,'DJ','Africa/Djibouti',''),(138,'DK','Europe/Copenhagen',''),(139,'DM','America/Dominica',''),(140,'DO','America/Santo_Domingo',''),(141,'DZ','Africa/Algiers',''),(142,'EC','America/Guayaquil','mainland'),(143,'EC','Pacific/Galapagos','Galapagos Islands'),(144,'EE','Europe/Tallinn',''),(145,'EG','Africa/Cairo',''),(146,'EH','Africa/El_Aaiun',''),(147,'ER','Africa/Asmara',''),(148,'ES','Europe/Madrid','mainland'),(149,'ES','Africa/Ceuta','Ceuta & Melilla'),(150,'ES','Atlantic/Canary','Canary Islands'),(151,'ET','Africa/Addis_Ababa',''),(152,'FI','Europe/Helsinki',''),(153,'FJ','Pacific/Fiji',''),(154,'FK','Atlantic/Stanley',''),(155,'FM','Pacific/Chuuk','Chuuk (Truk) and Yap'),(156,'FM','Pacific/Pohnpei','Pohnpei (Ponape)'),(157,'FM','Pacific/Kosrae','Kosrae'),(158,'FO','Atlantic/Faroe',''),(159,'FR','Europe/Paris',''),(160,'GA','Africa/Libreville',''),(161,'GB','Europe/London',''),(162,'GD','America/Grenada',''),(163,'GE','Asia/Tbilisi',''),(164,'GF','America/Cayenne',''),(165,'GG','Europe/Guernsey',''),(166,'GH','Africa/Accra',''),(167,'GI','Europe/Gibraltar',''),(168,'GL','America/Godthab','most locations'),(169,'GL','America/Danmarkshavn','east coast, north of Scoresbysund'),(170,'GL','America/Scoresbysund','Scoresbysund / Ittoqqortoormiit'),(171,'GL','America/Thule','Thule / Pituffik'),(172,'GM','Africa/Banjul',''),(173,'GN','Africa/Conakry',''),(174,'GP','America/Guadeloupe',''),(175,'GQ','Africa/Malabo',''),(176,'GR','Europe/Athens',''),(177,'GS','Atlantic/South_Georgia',''),(178,'GT','America/Guatemala',''),(179,'GU','Pacific/Guam',''),(180,'GW','Africa/Bissau',''),(181,'GY','America/Guyana',''),(182,'HK','Asia/Hong_Kong',''),(183,'HN','America/Tegucigalpa',''),(184,'HR','Europe/Zagreb',''),(185,'HT','America/Port-au-Prince',''),(186,'HU','Europe/Budapest',''),(187,'ID','Asia/Jakarta','Java & Sumatra'),(188,'ID','Asia/Pontianak','west & central Borneo'),(189,'ID','Asia/Makassar','east & south Borneo, Sulawesi (Celebes), Bali, Nusa Tengarra, west Timor'),(190,'ID','Asia/Jayapura','west New Guinea (Irian Jaya) & Malukus (Moluccas)'),(191,'IE','Europe/Dublin',''),(192,'IL','Asia/Jerusalem',''),(193,'IM','Europe/Isle_of_Man',''),(194,'IN','Asia/Kolkata',''),(195,'IO','Indian/Chagos',''),(196,'IQ','Asia/Baghdad',''),(197,'IR','Asia/Tehran',''),(198,'IS','Atlantic/Reykjavik',''),(199,'IT','Europe/Rome',''),(200,'JE','Europe/Jersey',''),(201,'JM','America/Jamaica',''),(202,'JO','Asia/Amman',''),(203,'JP','Asia/Tokyo',''),(204,'KE','Africa/Nairobi',''),(205,'KG','Asia/Bishkek',''),(206,'KH','Asia/Phnom_Penh',''),(207,'KI','Pacific/Tarawa','Gilbert Islands'),(208,'KI','Pacific/Enderbury','Phoenix Islands'),(209,'KI','Pacific/Kiritimati','Line Islands'),(210,'KM','Indian/Comoro',''),(211,'KN','America/St_Kitts',''),(212,'KP','Asia/Pyongyang',''),(213,'KR','Asia/Seoul',''),(214,'KW','Asia/Kuwait',''),(215,'KY','America/Cayman',''),(216,'KZ','Asia/Almaty','most locations'),(217,'KZ','Asia/Qyzylorda','Qyzylorda (Kyzylorda, Kzyl-Orda)'),(218,'KZ','Asia/Aqtobe','Aqtobe (Aktobe)'),(219,'KZ','Asia/Aqtau','Atyrau (Atirau, Guryev), Mangghystau (Mankistau)'),(220,'KZ','Asia/Oral','West Kazakhstan'),(221,'LA','Asia/Vientiane',''),(222,'LB','Asia/Beirut',''),(223,'LC','America/St_Lucia',''),(224,'LI','Europe/Vaduz',''),(225,'LK','Asia/Colombo',''),(226,'LR','Africa/Monrovia',''),(227,'LS','Africa/Maseru',''),(228,'LT','Europe/Vilnius',''),(229,'LU','Europe/Luxembourg',''),(230,'LV','Europe/Riga',''),(231,'LY','Africa/Tripoli',''),(232,'MA','Africa/Casablanca',''),(233,'MC','Europe/Monaco',''),(234,'MD','Europe/Chisinau',''),(235,'ME','Europe/Podgorica',''),(236,'MF','America/Marigot',''),(237,'MG','Indian/Antananarivo',''),(238,'MH','Pacific/Majuro','most locations'),(239,'MH','Pacific/Kwajalein','Kwajalein'),(240,'MK','Europe/Skopje',''),(241,'ML','Africa/Bamako',''),(242,'MM','Asia/Rangoon',''),(243,'MN','Asia/Ulaanbaatar','most locations'),(244,'MN','Asia/Hovd','Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan'),(245,'MN','Asia/Choibalsan','Dornod, Sukhbaatar'),(246,'MO','Asia/Macau',''),(247,'MP','Pacific/Saipan',''),(248,'MQ','America/Martinique',''),(249,'MR','Africa/Nouakchott',''),(250,'MS','America/Montserrat',''),(251,'MT','Europe/Malta',''),(252,'MU','Indian/Mauritius',''),(253,'MV','Indian/Maldives',''),(254,'MW','Africa/Blantyre',''),(255,'MX','America/Mexico_City','Central Time - most locations'),(256,'MX','America/Cancun','Central Time - Quintana Roo'),(257,'MX','America/Merida','Central Time - Campeche, Yucatan'),(258,'MX','America/Monterrey','Mexican Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas away from US border'),(259,'MX','America/Matamoros','US Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas near US border'),(260,'MX','America/Mazatlan','Mountain Time - S Baja, Nayarit, Sinaloa'),(261,'MX','America/Chihuahua','Mexican Mountain Time - Chihuahua away from US border'),(262,'MX','America/Ojinaga','US Mountain Time - Chihuahua near US border'),(263,'MX','America/Hermosillo','Mountain Standard Time - Sonora'),(264,'MX','America/Tijuana','US Pacific Time - Baja California near US border'),(265,'MX','America/Santa_Isabel','Mexican Pacific Time - Baja California away from US border'),(266,'MX','America/Bahia_Banderas','Mexican Central Time - Bahia de Banderas'),(267,'MY','Asia/Kuala_Lumpur','peninsular Malaysia'),(268,'MY','Asia/Kuching','Sabah & Sarawak'),(269,'MZ','Africa/Maputo',''),(270,'NA','Africa/Windhoek',''),(271,'NC','Pacific/Noumea',''),(272,'NE','Africa/Niamey',''),(273,'NF','Pacific/Norfolk',''),(274,'NG','Africa/Lagos',''),(275,'NI','America/Managua',''),(276,'NL','Europe/Amsterdam',''),(277,'NO','Europe/Oslo',''),(278,'NP','Asia/Kathmandu',''),(279,'NR','Pacific/Nauru',''),(280,'NU','Pacific/Niue',''),(281,'NZ','Pacific/Auckland','most locations'),(282,'NZ','Pacific/Chatham','Chatham Islands'),(283,'OM','Asia/Muscat',''),(284,'PA','America/Panama',''),(285,'PE','America/Lima',''),(286,'PF','Pacific/Tahiti','Society Islands'),(287,'PF','Pacific/Marquesas','Marquesas Islands'),(288,'PF','Pacific/Gambier','Gambier Islands'),(289,'PG','Pacific/Port_Moresby',''),(290,'PH','Asia/Manila',''),(291,'PK','Asia/Karachi',''),(292,'PL','Europe/Warsaw',''),(293,'PM','America/Miquelon',''),(294,'PN','Pacific/Pitcairn',''),(295,'PR','America/Puerto_Rico',''),(296,'PS','Asia/Gaza','Gaza Strip'),(297,'PS','Asia/Hebron','West Bank'),(298,'PT','Europe/Lisbon','mainland'),(299,'PT','Atlantic/Madeira','Madeira Islands'),(300,'PT','Atlantic/Azores','Azores'),(301,'PW','Pacific/Palau',''),(302,'PY','America/Asuncion',''),(303,'QA','Asia/Qatar',''),(304,'RE','Indian/Reunion',''),(305,'RO','Europe/Bucharest',''),(306,'RS','Europe/Belgrade',''),(307,'RU','Europe/Kaliningrad','Moscow-01 - Kaliningrad'),(308,'RU','Europe/Moscow','Moscow+00 - west Russia'),(309,'RU','Europe/Volgograd','Moscow+00 - Caspian Sea'),(310,'RU','Europe/Samara','Moscow+00 - Samara, Udmurtia'),(311,'RU','Asia/Yekaterinburg','Moscow+02 - Urals'),(312,'RU','Asia/Omsk','Moscow+03 - west Siberia'),(313,'RU','Asia/Novosibirsk','Moscow+03 - Novosibirsk'),(314,'RU','Asia/Novokuznetsk','Moscow+03 - Novokuznetsk'),(315,'RU','Asia/Krasnoyarsk','Moscow+04 - Yenisei River'),(316,'RU','Asia/Irkutsk','Moscow+05 - Lake Baikal'),(317,'RU','Asia/Yakutsk','Moscow+06 - Lena River'),(318,'RU','Asia/Vladivostok','Moscow+07 - Amur River'),(319,'RU','Asia/Sakhalin','Moscow+07 - Sakhalin Island'),(320,'RU','Asia/Magadan','Moscow+08 - Magadan'),(321,'RU','Asia/Kamchatka','Moscow+08 - Kamchatka'),(322,'RU','Asia/Anadyr','Moscow+08 - Bering Sea'),(323,'RW','Africa/Kigali',''),(324,'SA','Asia/Riyadh',''),(325,'SB','Pacific/Guadalcanal',''),(326,'SC','Indian/Mahe',''),(327,'SD','Africa/Khartoum',''),(328,'SE','Europe/Stockholm',''),(329,'SG','Asia/Singapore',''),(330,'SH','Atlantic/St_Helena',''),(331,'SI','Europe/Ljubljana',''),(332,'SJ','Arctic/Longyearbyen',''),(333,'SK','Europe/Bratislava',''),(334,'SL','Africa/Freetown',''),(335,'SM','Europe/San_Marino',''),(336,'SN','Africa/Dakar',''),(337,'SO','Africa/Mogadishu',''),(338,'SR','America/Paramaribo',''),(339,'SS','Africa/Juba',''),(340,'ST','Africa/Sao_Tome',''),(341,'SV','America/El_Salvador',''),(342,'SX','America/Lower_Princes',''),(343,'SY','Asia/Damascus',''),(344,'SZ','Africa/Mbabane',''),(345,'TC','America/Grand_Turk',''),(346,'TD','Africa/Ndjamena',''),(347,'TF','Indian/Kerguelen',''),(348,'TG','Africa/Lome',''),(349,'TH','Asia/Bangkok',''),(350,'TJ','Asia/Dushanbe',''),(351,'TK','Pacific/Fakaofo',''),(352,'TL','Asia/Dili',''),(353,'TM','Asia/Ashgabat',''),(354,'TN','Africa/Tunis',''),(355,'TO','Pacific/Tongatapu',''),(356,'TR','Europe/Istanbul',''),(357,'TT','America/Port_of_Spain',''),(358,'TV','Pacific/Funafuti',''),(359,'TW','Asia/Taipei',''),(360,'TZ','Africa/Dar_es_Salaam',''),(361,'UA','Europe/Kiev','most locations'),(362,'UA','Europe/Uzhgorod','Ruthenia'),(363,'UA','Europe/Zaporozhye','Zaporozhye, E Lugansk / Zaporizhia, E Luhansk'),(364,'UA','Europe/Simferopol','central Crimea'),(365,'UG','Africa/Kampala',''),(366,'UM','Pacific/Johnston','Johnston Atoll'),(367,'UM','Pacific/Midway','Midway Islands'),(368,'UM','Pacific/Wake','Wake Island'),(369,'US','America/New_York','Eastern Time'),(370,'US','America/Detroit','Eastern Time - Michigan - most locations'),(371,'US','America/Kentucky/Louisville','Eastern Time - Kentucky - Louisville area'),(372,'US','America/Kentucky/Monticello','Eastern Time - Kentucky - Wayne County'),(373,'US','America/Indiana/Indianapolis','Eastern Time - Indiana - most locations'),(374,'US','America/Indiana/Vincennes','Eastern Time - Indiana - Daviess, Dubois, Knox & Martin Counties'),(375,'US','America/Indiana/Winamac','Eastern Time - Indiana - Pulaski County'),(376,'US','America/Indiana/Marengo','Eastern Time - Indiana - Crawford County'),(377,'US','America/Indiana/Petersburg','Eastern Time - Indiana - Pike County'),(378,'US','America/Indiana/Vevay','Eastern Time - Indiana - Switzerland County'),(379,'US','America/Chicago','Central Time'),(380,'US','America/Indiana/Tell_City','Central Time - Indiana - Perry County'),(381,'US','America/Indiana/Knox','Central Time - Indiana - Starke County'),(382,'US','America/Menominee','Central Time - Michigan - Dickinson, Gogebic, Iron & Menominee Counties'),(383,'US','America/North_Dakota/Center','Central Time - North Dakota - Oliver County'),(384,'US','America/North_Dakota/New_Salem','Central Time - North Dakota - Morton County (except Mandan area)'),(385,'US','America/North_Dakota/Beulah','Central Time - North Dakota - Mercer County'),(386,'US','America/Denver','Mountain Time'),(387,'US','America/Boise','Mountain Time - south Idaho & east Oregon'),(388,'US','America/Shiprock','Mountain Time - Navajo'),(389,'US','America/Phoenix','Mountain Standard Time - Arizona'),(390,'US','America/Los_Angeles','Pacific Time'),(391,'US','America/Anchorage','Alaska Time'),(392,'US','America/Juneau','Alaska Time - Alaska panhandle'),(393,'US','America/Sitka','Alaska Time - southeast Alaska panhandle'),(394,'US','America/Yakutat','Alaska Time - Alaska panhandle neck'),(395,'US','America/Nome','Alaska Time - west Alaska'),(396,'US','America/Adak','Aleutian Islands'),(397,'US','America/Metlakatla','Metlakatla Time - Annette Island'),(398,'US','Pacific/Honolulu','Hawaii'),(399,'UY','America/Montevideo',''),(400,'UZ','Asia/Samarkand','west Uzbekistan'),(401,'UZ','Asia/Tashkent','east Uzbekistan'),(402,'VA','Europe/Vatican',''),(403,'VC','America/St_Vincent',''),(404,'VE','America/Caracas',''),(405,'VG','America/Tortola',''),(406,'VI','America/St_Thomas',''),(407,'VN','Asia/Ho_Chi_Minh',''),(408,'VU','Pacific/Efate',''),(409,'WF','Pacific/Wallis',''),(410,'WS','Pacific/Apia',''),(411,'YE','Asia/Aden',''),(412,'YT','Indian/Mayotte',''),(413,'ZA','Africa/Johannesburg',''),(414,'ZM','Africa/Lusaka',''),(415,'ZW','Africa/Harare','');
+/*!40000 ALTER TABLE `timezones` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-05-07 12:22:12
diff --git a/fineract-db/multi-tenant-demo-backups/0001-mifos-platform-shared-tenants.sql b/fineract-db/multi-tenant-demo-backups/0001-mifos-platform-shared-tenants.sql
new file mode 100644
index 0000000..922e3f3
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/0001-mifos-platform-shared-tenants.sql
@@ -0,0 +1,89 @@
+-- MySQL dump 10.13  Distrib 5.1.60, for Win32 (ia32)
+--
+-- Host: localhost    Database: mifosplatform-tenants
+-- ------------------------------------------------------
+-- Server version	5.1.60-community
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `tenants`
+--
+
+DROP TABLE IF EXISTS `tenants`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tenants` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `identifier` varchar(100) NOT NULL,
+  `name` varchar(100) NOT NULL,
+  `schema_name` varchar(100) NOT NULL,
+  `timezone_id` varchar(100) NOT NULL,
+  `country_id` int(11) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `schema_server` varchar(100) NOT NULL DEFAULT 'localhost',
+  `schema_server_port` varchar(10) NOT NULL DEFAULT '3306',
+  `schema_username` varchar(100) NOT NULL DEFAULT 'root',
+  `schema_password` varchar(100) NOT NULL DEFAULT 'mysql',
+  `auto_update` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `tenants`
+--
+
+LOCK TABLES `tenants` WRITE;
+/*!40000 ALTER TABLE `tenants` DISABLE KEYS */;
+INSERT INTO `tenants` VALUES (1,'default','Default Demo Tenant','mifostenant-default','Asia/Kolkata',NULL,NULL,NULL,NULL,'localhost','3306','root','mysql',1);
+/*!40000 ALTER TABLE `tenants` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `timezones`
+--
+
+DROP TABLE IF EXISTS `timezones`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `timezones` (
+  `id` int(3) NOT NULL AUTO_INCREMENT,
+  `country_code` varchar(2) NOT NULL,
+  `timezonename` varchar(100) NOT NULL,
+  `comments` varchar(150) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=416 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `timezones`
+--
+
+LOCK TABLES `timezones` WRITE;
+/*!40000 ALTER TABLE `timezones` DISABLE KEYS */;
+INSERT INTO `timezones` VALUES (1,'AD','Europe/Andorra',NULL),(2,'AE','Asia/Dubai',''),(3,'AF','Asia/Kabul',''),(4,'AG','America/Antigua',''),(5,'AI','America/Anguilla',''),(6,'AL','Europe/Tirane',''),(7,'AM','Asia/Yerevan',''),(8,'AO','Africa/Luanda',''),(9,'AQ','Antarctica/McMurdo','McMurdo Station, Ross Island'),(10,'AQ','Antarctica/South_Pole','Amundsen-Scott Station, South Pole'),(11,'AQ','Antarctica/Rothera','Rothera Station, Adelaide Island'),(12,'AQ','Antarctica/Palmer','Palmer Station, Anvers Island'),(13,'AQ','Antarctica/Mawson','Mawson Station, Holme Bay'),(14,'AQ','Antarctica/Davis','Davis Station, Vestfold Hills'),(15,'AQ','Antarctica/Casey','Casey Station, Bailey Peninsula'),(16,'AQ','Antarctica/Vostok','Vostok Station, Lake Vostok'),(17,'AQ','Antarctica/DumontDUrville','Dumont-dUrville Station, Terre Adelie'),(18,'AQ','Antarctica/Syowa','Syowa Station, E Ongul I'),(19,'AQ','Antarctica/Macquarie','Macquarie Island Station, Macquarie Island'),(20,'AR','America/Argentina/Buenos_Aires','Buenos Aires (BA, CF)'),(21,'AR','America/Argentina/Cordoba','most locations (CB, CC, CN, ER, FM, MN, SE, SF)'),(22,'AR','America/Argentina/Salta','(SA, LP, NQ, RN)'),(23,'AR','America/Argentina/Jujuy','Jujuy (JY)'),(24,'AR','America/Argentina/Tucuman','Tucuman (TM)'),(25,'AR','America/Argentina/Catamarca','Catamarca (CT), Chubut (CH)'),(26,'AR','America/Argentina/La_Rioja','La Rioja (LR)'),(27,'AR','America/Argentina/San_Juan','San Juan (SJ)'),(28,'AR','America/Argentina/Mendoza','Mendoza (MZ)'),(29,'AR','America/Argentina/San_Luis','San Luis (SL)'),(30,'AR','America/Argentina/Rio_Gallegos','Santa Cruz (SC)'),(31,'AR','America/Argentina/Ushuaia','Tierra del Fuego (TF)'),(32,'AS','Pacific/Pago_Pago',''),(33,'AT','Europe/Vienna',''),(34,'AU','Australia/Lord_Howe','Lord Howe Island'),(35,'AU','Australia/Hobart','Tasmania - most locations'),(36,'AU','Australia/Currie','Tasmania - King Island'),(37,'AU','Australia/Melbourne','Victoria'),(38,'AU','Australia/Sydney','New South Wales - most locations'),(39,'AU','Australia/Broken_Hill','New South Wales - Yancowinna'),(40,'AU','Australia/Brisbane','Queensland - most locations'),(41,'AU','Australia/Lindeman','Queensland - Holiday Islands'),(42,'AU','Australia/Adelaide','South Australia'),(43,'AU','Australia/Darwin','Northern Territory'),(44,'AU','Australia/Perth','Western Australia - most locations'),(45,'AU','Australia/Eucla','Western Australia - Eucla area'),(46,'AW','America/Aruba',''),(47,'AX','Europe/Mariehamn',''),(48,'AZ','Asia/Baku',''),(49,'BA','Europe/Sarajevo',''),(50,'BB','America/Barbados',''),(51,'BD','Asia/Dhaka',''),(52,'BE','Europe/Brussels',''),(53,'BF','Africa/Ouagadougou',''),(54,'BG','Europe/Sofia',''),(55,'BH','Asia/Bahrain',''),(56,'BI','Africa/Bujumbura',''),(57,'BJ','Africa/Porto-Novo',''),(58,'BL','America/St_Barthelemy',''),(59,'BM','Atlantic/Bermuda',''),(60,'BN','Asia/Brunei',''),(61,'BO','America/La_Paz',''),(62,'BQ','America/Kralendijk',''),(63,'BR','America/Noronha','Atlantic islands'),(64,'BR','America/Belem','Amapa, E Para'),(65,'BR','America/Fortaleza','NE Brazil (MA, PI, CE, RN, PB)'),(66,'BR','America/Recife','Pernambuco'),(67,'BR','America/Araguaina','Tocantins'),(68,'BR','America/Maceio','Alagoas, Sergipe'),(69,'BR','America/Bahia','Bahia'),(70,'BR','America/Sao_Paulo','S & SE Brazil (GO, DF, MG, ES, RJ, SP, PR, SC, RS)'),(71,'BR','America/Campo_Grande','Mato Grosso do Sul'),(72,'BR','America/Cuiaba','Mato Grosso'),(73,'BR','America/Santarem','W Para'),(74,'BR','America/Porto_Velho','Rondonia'),(75,'BR','America/Boa_Vista','Roraima'),(76,'BR','America/Manaus','E Amazonas'),(77,'BR','America/Eirunepe','W Amazonas'),(78,'BR','America/Rio_Branco','Acre'),(79,'BS','America/Nassau',''),(80,'BT','Asia/Thimphu',''),(81,'BW','Africa/Gaborone',''),(82,'BY','Europe/Minsk',''),(83,'BZ','America/Belize',''),(84,'CA','America/St_Johns','Newfoundland Time, including SE Labrador'),(85,'CA','America/Halifax','Atlantic Time - Nova Scotia (most places), PEI'),(86,'CA','America/Glace_Bay','Atlantic Time - Nova Scotia - places that did not observe DST 1966-1971'),(87,'CA','America/Moncton','Atlantic Time - New Brunswick'),(88,'CA','America/Goose_Bay','Atlantic Time - Labrador - most locations'),(89,'CA','America/Blanc-Sablon','Atlantic Standard Time - Quebec - Lower North Shore'),(90,'CA','America/Montreal','Eastern Time - Quebec - most locations'),(91,'CA','America/Toronto','Eastern Time - Ontario - most locations'),(92,'CA','America/Nipigon','Eastern Time - Ontario & Quebec - places that did not observe DST 1967-1973'),(93,'CA','America/Thunder_Bay','Eastern Time - Thunder Bay, Ontario'),(94,'CA','America/Iqaluit','Eastern Time - east Nunavut - most locations'),(95,'CA','America/Pangnirtung','Eastern Time - Pangnirtung, Nunavut'),(96,'CA','America/Resolute','Central Standard Time - Resolute, Nunavut'),(97,'CA','America/Atikokan','Eastern Standard Time - Atikokan, Ontario and Southampton I, Nunavut'),(98,'CA','America/Rankin_Inlet','Central Time - central Nunavut'),(99,'CA','America/Winnipeg','Central Time - Manitoba & west Ontario'),(100,'CA','America/Rainy_River','Central Time - Rainy River & Fort Frances, Ontario'),(101,'CA','America/Regina','Central Standard Time - Saskatchewan - most locations'),(102,'CA','America/Swift_Current','Central Standard Time - Saskatchewan - midwest'),(103,'CA','America/Edmonton','Mountain Time - Alberta, east British Columbia & west Saskatchewan'),(104,'CA','America/Cambridge_Bay','Mountain Time - west Nunavut'),(105,'CA','America/Yellowknife','Mountain Time - central Northwest Territories'),(106,'CA','America/Inuvik','Mountain Time - west Northwest Territories'),(107,'CA','America/Creston','Mountain Standard Time - Creston, British Columbia'),(108,'CA','America/Dawson_Creek','Mountain Standard Time - Dawson Creek & Fort Saint John, British Columbia'),(109,'CA','America/Vancouver','Pacific Time - west British Columbia'),(110,'CA','America/Whitehorse','Pacific Time - south Yukon'),(111,'CA','America/Dawson','Pacific Time - north Yukon'),(112,'CC','Indian/Cocos',''),(113,'CD','Africa/Kinshasa','west Dem. Rep. of Congo'),(114,'CD','Africa/Lubumbashi','east Dem. Rep. of Congo'),(115,'CF','Africa/Bangui',''),(116,'CG','Africa/Brazzaville',''),(117,'CH','Europe/Zurich',''),(118,'CI','Africa/Abidjan',''),(119,'CK','Pacific/Rarotonga',''),(120,'CL','America/Santiago','most locations'),(121,'CL','Pacific/Easter','Easter Island & Sala y Gomez'),(122,'CM','Africa/Douala',''),(123,'CN','Asia/Shanghai','east China - Beijing, Guangdong, Shanghai, etc.'),(124,'CN','Asia/Harbin','Heilongjiang (except Mohe), Jilin'),(125,'CN','Asia/Chongqing','central China - Sichuan, Yunnan, Guangxi, Shaanxi, Guizhou, etc.'),(126,'CN','Asia/Urumqi','most of Tibet & Xinjiang'),(127,'CN','Asia/Kashgar','west Tibet & Xinjiang'),(128,'CO','America/Bogota',''),(129,'CR','America/Costa_Rica',''),(130,'CU','America/Havana',''),(131,'CV','Atlantic/Cape_Verde',''),(132,'CW','America/Curacao',''),(133,'CX','Indian/Christmas',''),(134,'CY','Asia/Nicosia',''),(135,'CZ','Europe/Prague',''),(136,'DE','Europe/Berlin',''),(137,'DJ','Africa/Djibouti',''),(138,'DK','Europe/Copenhagen',''),(139,'DM','America/Dominica',''),(140,'DO','America/Santo_Domingo',''),(141,'DZ','Africa/Algiers',''),(142,'EC','America/Guayaquil','mainland'),(143,'EC','Pacific/Galapagos','Galapagos Islands'),(144,'EE','Europe/Tallinn',''),(145,'EG','Africa/Cairo',''),(146,'EH','Africa/El_Aaiun',''),(147,'ER','Africa/Asmara',''),(148,'ES','Europe/Madrid','mainland'),(149,'ES','Africa/Ceuta','Ceuta & Melilla'),(150,'ES','Atlantic/Canary','Canary Islands'),(151,'ET','Africa/Addis_Ababa',''),(152,'FI','Europe/Helsinki',''),(153,'FJ','Pacific/Fiji',''),(154,'FK','Atlantic/Stanley',''),(155,'FM','Pacific/Chuuk','Chuuk (Truk) and Yap'),(156,'FM','Pacific/Pohnpei','Pohnpei (Ponape)'),(157,'FM','Pacific/Kosrae','Kosrae'),(158,'FO','Atlantic/Faroe',''),(159,'FR','Europe/Paris',''),(160,'GA','Africa/Libreville',''),(161,'GB','Europe/London',''),(162,'GD','America/Grenada',''),(163,'GE','Asia/Tbilisi',''),(164,'GF','America/Cayenne',''),(165,'GG','Europe/Guernsey',''),(166,'GH','Africa/Accra',''),(167,'GI','Europe/Gibraltar',''),(168,'GL','America/Godthab','most locations'),(169,'GL','America/Danmarkshavn','east coast, north of Scoresbysund'),(170,'GL','America/Scoresbysund','Scoresbysund / Ittoqqortoormiit'),(171,'GL','America/Thule','Thule / Pituffik'),(172,'GM','Africa/Banjul',''),(173,'GN','Africa/Conakry',''),(174,'GP','America/Guadeloupe',''),(175,'GQ','Africa/Malabo',''),(176,'GR','Europe/Athens',''),(177,'GS','Atlantic/South_Georgia',''),(178,'GT','America/Guatemala',''),(179,'GU','Pacific/Guam',''),(180,'GW','Africa/Bissau',''),(181,'GY','America/Guyana',''),(182,'HK','Asia/Hong_Kong',''),(183,'HN','America/Tegucigalpa',''),(184,'HR','Europe/Zagreb',''),(185,'HT','America/Port-au-Prince',''),(186,'HU','Europe/Budapest',''),(187,'ID','Asia/Jakarta','Java & Sumatra'),(188,'ID','Asia/Pontianak','west & central Borneo'),(189,'ID','Asia/Makassar','east & south Borneo, Sulawesi (Celebes), Bali, Nusa Tengarra, west Timor'),(190,'ID','Asia/Jayapura','west New Guinea (Irian Jaya) & Malukus (Moluccas)'),(191,'IE','Europe/Dublin',''),(192,'IL','Asia/Jerusalem',''),(193,'IM','Europe/Isle_of_Man',''),(194,'IN','Asia/Kolkata',''),(195,'IO','Indian/Chagos',''),(196,'IQ','Asia/Baghdad',''),(197,'IR','Asia/Tehran',''),(198,'IS','Atlantic/Reykjavik',''),(199,'IT','Europe/Rome',''),(200,'JE','Europe/Jersey',''),(201,'JM','America/Jamaica',''),(202,'JO','Asia/Amman',''),(203,'JP','Asia/Tokyo',''),(204,'KE','Africa/Nairobi',''),(205,'KG','Asia/Bishkek',''),(206,'KH','Asia/Phnom_Penh',''),(207,'KI','Pacific/Tarawa','Gilbert Islands'),(208,'KI','Pacific/Enderbury','Phoenix Islands'),(209,'KI','Pacific/Kiritimati','Line Islands'),(210,'KM','Indian/Comoro',''),(211,'KN','America/St_Kitts',''),(212,'KP','Asia/Pyongyang',''),(213,'KR','Asia/Seoul',''),(214,'KW','Asia/Kuwait',''),(215,'KY','America/Cayman',''),(216,'KZ','Asia/Almaty','most locations'),(217,'KZ','Asia/Qyzylorda','Qyzylorda (Kyzylorda, Kzyl-Orda)'),(218,'KZ','Asia/Aqtobe','Aqtobe (Aktobe)'),(219,'KZ','Asia/Aqtau','Atyrau (Atirau, Guryev), Mangghystau (Mankistau)'),(220,'KZ','Asia/Oral','West Kazakhstan'),(221,'LA','Asia/Vientiane',''),(222,'LB','Asia/Beirut',''),(223,'LC','America/St_Lucia',''),(224,'LI','Europe/Vaduz',''),(225,'LK','Asia/Colombo',''),(226,'LR','Africa/Monrovia',''),(227,'LS','Africa/Maseru',''),(228,'LT','Europe/Vilnius',''),(229,'LU','Europe/Luxembourg',''),(230,'LV','Europe/Riga',''),(231,'LY','Africa/Tripoli',''),(232,'MA','Africa/Casablanca',''),(233,'MC','Europe/Monaco',''),(234,'MD','Europe/Chisinau',''),(235,'ME','Europe/Podgorica',''),(236,'MF','America/Marigot',''),(237,'MG','Indian/Antananarivo',''),(238,'MH','Pacific/Majuro','most locations'),(239,'MH','Pacific/Kwajalein','Kwajalein'),(240,'MK','Europe/Skopje',''),(241,'ML','Africa/Bamako',''),(242,'MM','Asia/Rangoon',''),(243,'MN','Asia/Ulaanbaatar','most locations'),(244,'MN','Asia/Hovd','Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan'),(245,'MN','Asia/Choibalsan','Dornod, Sukhbaatar'),(246,'MO','Asia/Macau',''),(247,'MP','Pacific/Saipan',''),(248,'MQ','America/Martinique',''),(249,'MR','Africa/Nouakchott',''),(250,'MS','America/Montserrat',''),(251,'MT','Europe/Malta',''),(252,'MU','Indian/Mauritius',''),(253,'MV','Indian/Maldives',''),(254,'MW','Africa/Blantyre',''),(255,'MX','America/Mexico_City','Central Time - most locations'),(256,'MX','America/Cancun','Central Time - Quintana Roo'),(257,'MX','America/Merida','Central Time - Campeche, Yucatan'),(258,'MX','America/Monterrey','Mexican Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas away from US border'),(259,'MX','America/Matamoros','US Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas near US border'),(260,'MX','America/Mazatlan','Mountain Time - S Baja, Nayarit, Sinaloa'),(261,'MX','America/Chihuahua','Mexican Mountain Time - Chihuahua away from US border'),(262,'MX','America/Ojinaga','US Mountain Time - Chihuahua near US border'),(263,'MX','America/Hermosillo','Mountain Standard Time - Sonora'),(264,'MX','America/Tijuana','US Pacific Time - Baja California near US border'),(265,'MX','America/Santa_Isabel','Mexican Pacific Time - Baja California away from US border'),(266,'MX','America/Bahia_Banderas','Mexican Central Time - Bahia de Banderas'),(267,'MY','Asia/Kuala_Lumpur','peninsular Malaysia'),(268,'MY','Asia/Kuching','Sabah & Sarawak'),(269,'MZ','Africa/Maputo',''),(270,'NA','Africa/Windhoek',''),(271,'NC','Pacific/Noumea',''),(272,'NE','Africa/Niamey',''),(273,'NF','Pacific/Norfolk',''),(274,'NG','Africa/Lagos',''),(275,'NI','America/Managua',''),(276,'NL','Europe/Amsterdam',''),(277,'NO','Europe/Oslo',''),(278,'NP','Asia/Kathmandu',''),(279,'NR','Pacific/Nauru',''),(280,'NU','Pacific/Niue',''),(281,'NZ','Pacific/Auckland','most locations'),(282,'NZ','Pacific/Chatham','Chatham Islands'),(283,'OM','Asia/Muscat',''),(284,'PA','America/Panama',''),(285,'PE','America/Lima',''),(286,'PF','Pacific/Tahiti','Society Islands'),(287,'PF','Pacific/Marquesas','Marquesas Islands'),(288,'PF','Pacific/Gambier','Gambier Islands'),(289,'PG','Pacific/Port_Moresby',''),(290,'PH','Asia/Manila',''),(291,'PK','Asia/Karachi',''),(292,'PL','Europe/Warsaw',''),(293,'PM','America/Miquelon',''),(294,'PN','Pacific/Pitcairn',''),(295,'PR','America/Puerto_Rico',''),(296,'PS','Asia/Gaza','Gaza Strip'),(297,'PS','Asia/Hebron','West Bank'),(298,'PT','Europe/Lisbon','mainland'),(299,'PT','Atlantic/Madeira','Madeira Islands'),(300,'PT','Atlantic/Azores','Azores'),(301,'PW','Pacific/Palau',''),(302,'PY','America/Asuncion',''),(303,'QA','Asia/Qatar',''),(304,'RE','Indian/Reunion',''),(305,'RO','Europe/Bucharest',''),(306,'RS','Europe/Belgrade',''),(307,'RU','Europe/Kaliningrad','Moscow-01 - Kaliningrad'),(308,'RU','Europe/Moscow','Moscow+00 - west Russia'),(309,'RU','Europe/Volgograd','Moscow+00 - Caspian Sea'),(310,'RU','Europe/Samara','Moscow+00 - Samara, Udmurtia'),(311,'RU','Asia/Yekaterinburg','Moscow+02 - Urals'),(312,'RU','Asia/Omsk','Moscow+03 - west Siberia'),(313,'RU','Asia/Novosibirsk','Moscow+03 - Novosibirsk'),(314,'RU','Asia/Novokuznetsk','Moscow+03 - Novokuznetsk'),(315,'RU','Asia/Krasnoyarsk','Moscow+04 - Yenisei River'),(316,'RU','Asia/Irkutsk','Moscow+05 - Lake Baikal'),(317,'RU','Asia/Yakutsk','Moscow+06 - Lena River'),(318,'RU','Asia/Vladivostok','Moscow+07 - Amur River'),(319,'RU','Asia/Sakhalin','Moscow+07 - Sakhalin Island'),(320,'RU','Asia/Magadan','Moscow+08 - Magadan'),(321,'RU','Asia/Kamchatka','Moscow+08 - Kamchatka'),(322,'RU','Asia/Anadyr','Moscow+08 - Bering Sea'),(323,'RW','Africa/Kigali',''),(324,'SA','Asia/Riyadh',''),(325,'SB','Pacific/Guadalcanal',''),(326,'SC','Indian/Mahe',''),(327,'SD','Africa/Khartoum',''),(328,'SE','Europe/Stockholm',''),(329,'SG','Asia/Singapore',''),(330,'SH','Atlantic/St_Helena',''),(331,'SI','Europe/Ljubljana',''),(332,'SJ','Arctic/Longyearbyen',''),(333,'SK','Europe/Bratislava',''),(334,'SL','Africa/Freetown',''),(335,'SM','Europe/San_Marino',''),(336,'SN','Africa/Dakar',''),(337,'SO','Africa/Mogadishu',''),(338,'SR','America/Paramaribo',''),(339,'SS','Africa/Juba',''),(340,'ST','Africa/Sao_Tome',''),(341,'SV','America/El_Salvador',''),(342,'SX','America/Lower_Princes',''),(343,'SY','Asia/Damascus',''),(344,'SZ','Africa/Mbabane',''),(345,'TC','America/Grand_Turk',''),(346,'TD','Africa/Ndjamena',''),(347,'TF','Indian/Kerguelen',''),(348,'TG','Africa/Lome',''),(349,'TH','Asia/Bangkok',''),(350,'TJ','Asia/Dushanbe',''),(351,'TK','Pacific/Fakaofo',''),(352,'TL','Asia/Dili',''),(353,'TM','Asia/Ashgabat',''),(354,'TN','Africa/Tunis',''),(355,'TO','Pacific/Tongatapu',''),(356,'TR','Europe/Istanbul',''),(357,'TT','America/Port_of_Spain',''),(358,'TV','Pacific/Funafuti',''),(359,'TW','Asia/Taipei',''),(360,'TZ','Africa/Dar_es_Salaam',''),(361,'UA','Europe/Kiev','most locations'),(362,'UA','Europe/Uzhgorod','Ruthenia'),(363,'UA','Europe/Zaporozhye','Zaporozhye, E Lugansk / Zaporizhia, E Luhansk'),(364,'UA','Europe/Simferopol','central Crimea'),(365,'UG','Africa/Kampala',''),(366,'UM','Pacific/Johnston','Johnston Atoll'),(367,'UM','Pacific/Midway','Midway Islands'),(368,'UM','Pacific/Wake','Wake Island'),(369,'US','America/New_York','Eastern Time'),(370,'US','America/Detroit','Eastern Time - Michigan - most locations'),(371,'US','America/Kentucky/Louisville','Eastern Time - Kentucky - Louisville area'),(372,'US','America/Kentucky/Monticello','Eastern Time - Kentucky - Wayne County'),(373,'US','America/Indiana/Indianapolis','Eastern Time - Indiana - most locations'),(374,'US','America/Indiana/Vincennes','Eastern Time - Indiana - Daviess, Dubois, Knox & Martin Counties'),(375,'US','America/Indiana/Winamac','Eastern Time - Indiana - Pulaski County'),(376,'US','America/Indiana/Marengo','Eastern Time - Indiana - Crawford County'),(377,'US','America/Indiana/Petersburg','Eastern Time - Indiana - Pike County'),(378,'US','America/Indiana/Vevay','Eastern Time - Indiana - Switzerland County'),(379,'US','America/Chicago','Central Time'),(380,'US','America/Indiana/Tell_City','Central Time - Indiana - Perry County'),(381,'US','America/Indiana/Knox','Central Time - Indiana - Starke County'),(382,'US','America/Menominee','Central Time - Michigan - Dickinson, Gogebic, Iron & Menominee Counties'),(383,'US','America/North_Dakota/Center','Central Time - North Dakota - Oliver County'),(384,'US','America/North_Dakota/New_Salem','Central Time - North Dakota - Morton County (except Mandan area)'),(385,'US','America/North_Dakota/Beulah','Central Time - North Dakota - Mercer County'),(386,'US','America/Denver','Mountain Time'),(387,'US','America/Boise','Mountain Time - south Idaho & east Oregon'),(388,'US','America/Shiprock','Mountain Time - Navajo'),(389,'US','America/Phoenix','Mountain Standard Time - Arizona'),(390,'US','America/Los_Angeles','Pacific Time'),(391,'US','America/Anchorage','Alaska Time'),(392,'US','America/Juneau','Alaska Time - Alaska panhandle'),(393,'US','America/Sitka','Alaska Time - southeast Alaska panhandle'),(394,'US','America/Yakutat','Alaska Time - Alaska panhandle neck'),(395,'US','America/Nome','Alaska Time - west Alaska'),(396,'US','America/Adak','Aleutian Islands'),(397,'US','America/Metlakatla','Metlakatla Time - Annette Island'),(398,'US','Pacific/Honolulu','Hawaii'),(399,'UY','America/Montevideo',''),(400,'UZ','Asia/Samarkand','west Uzbekistan'),(401,'UZ','Asia/Tashkent','east Uzbekistan'),(402,'VA','Europe/Vatican',''),(403,'VC','America/St_Vincent',''),(404,'VE','America/Caracas',''),(405,'VG','America/Tortola',''),(406,'VI','America/St_Thomas',''),(407,'VN','Asia/Ho_Chi_Minh',''),(408,'VU','Pacific/Efate',''),(409,'WF','Pacific/Wallis',''),(410,'WS','Pacific/Apia',''),(411,'YE','Asia/Aden',''),(412,'YT','Indian/Mayotte',''),(413,'ZA','Africa/Johannesburg',''),(414,'ZM','Africa/Lusaka',''),(415,'ZW','Africa/Harare','');
+/*!40000 ALTER TABLE `timezones` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-04-03 18:11:34
diff --git a/fineract-db/multi-tenant-demo-backups/bare-bones-demo/README.md b/fineract-db/multi-tenant-demo-backups/bare-bones-demo/README.md
new file mode 100644
index 0000000..7a0d8a7
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/bare-bones-demo/README.md
@@ -0,0 +1,21 @@
+Bare Bones Demo
+======
+
+This demo database contains:
+
+- DDL of latest schema
+- Minimum reference data required for deployment of platform which is:
+  -  Its mandatory to have one selected currency so we default to US Dollar
+  -  Its mandatory to have one root or head office, so we have one created by default called a 'Head Office'
+  -  Permissions supported/needed by latest release of software are setup
+  -  Its mandatory to have at least one role when creating new users so we have one role created by default called 'Super user' which has the special permission 'Full Authorisation'. Any user with this role can do anything in the system.
+  -  Its required to have at least one application user setup so remaining setup can be done through ui so we have on application user created by default with username 'mifos' with a password of 'password'. Application users must be associated with an office and a role so this user is associated with 'Head office' and 'Super user' role allowing this user to do any operation in any office(branch).
+- Configuration
+  - No 'additional data' through the 'datatables' approach is setup
+  - One 'code' is setup called 'Client Identifier' with default values of {'Passport number'} - (required for Client Identity Document functionalty)
+  - Enable/Disable configuration has one entry named 'maker-checker' to allow people to enable disable this feature at global level. It is off or disabled by default.
+- No products (loans, deposit, savings) are setup
+- No Charges (fees or penalties) are setup
+- No Staff (employees) are setup (loan officers are optional when submiting new loan application)
+- No Portfolio data (no clients, groups, loan accounts, deposit accounts, savings accounts)
+- No Accounting data (no chart of accounts is setup, by default accounting with respect to portfolio items is off unless enabled when creating a loan product.)
diff --git a/fineract-db/multi-tenant-demo-backups/bare-bones-demo/bk_bare_bones_demo.sql b/fineract-db/multi-tenant-demo-backups/bare-bones-demo/bk_bare_bones_demo.sql
new file mode 100644
index 0000000..fab47a1
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/bare-bones-demo/bk_bare_bones_demo.sql
@@ -0,0 +1,1933 @@
+-- MySQL dump 10.13  Distrib 5.1.60, for Win32 (ia32)
+--
+-- Host: localhost    Database: mifostenant-default
+-- ------------------------------------------------------
+-- Server version	5.1.60-community
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `acc_gl_account`
+--
+
+DROP TABLE IF EXISTS `acc_gl_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_account`
+--
+
+LOCK TABLES `acc_gl_account` WRITE;
+/*!40000 ALTER TABLE `acc_gl_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_closure`
+--
+
+DROP TABLE IF EXISTS `acc_gl_closure`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_closure`
+--
+
+LOCK TABLES `acc_gl_closure` WRITE;
+/*!40000 ALTER TABLE `acc_gl_closure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_closure` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_journal_entry`
+--
+
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_entry` tinyint(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type_enum` smallint(5) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_journal_entry`
+--
+
+LOCK TABLES `acc_gl_journal_entry` WRITE;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_product_mapping`
+--
+
+DROP TABLE IF EXISTS `acc_product_mapping`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_product_mapping`
+--
+
+LOCK TABLES `acc_product_mapping` WRITE;
+/*!40000 ALTER TABLE `acc_product_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_product_mapping` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `c_configuration`
+--
+
+DROP TABLE IF EXISTS `c_configuration`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `c_configuration`
+--
+
+LOCK TABLES `c_configuration` WRITE;
+/*!40000 ALTER TABLE `c_configuration` DISABLE KEYS */;
+INSERT INTO `c_configuration` VALUES (1,'maker-checker',0);
+/*!40000 ALTER TABLE `c_configuration` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `client additional data`
+--
+
+DROP TABLE IF EXISTS `client additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `client additional data` (
+  `client_id` bigint(20) NOT NULL,
+  `Gender_cd` int(11) NOT NULL,
+  `Date of Birth` date NOT NULL,
+  `Home address` text NOT NULL,
+  `Telephone number` varchar(20) NOT NULL,
+  `Telephone number (2nd)` varchar(20) NOT NULL,
+  `Email address` varchar(50) NOT NULL,
+  `EducationLevel_cd` int(11) NOT NULL,
+  `MaritalStatus_cd` int(11) NOT NULL,
+  `Number of children` int(11) NOT NULL,
+  `Citizenship` varchar(50) NOT NULL,
+  `PovertyStatus_cd` int(11) NOT NULL,
+  `YesNo_cd_Employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of employment` int(11) DEFAULT NULL,
+  `Employer name` varchar(50) DEFAULT NULL,
+  `Number of years` int(11) DEFAULT NULL,
+  `Monthly salary` decimal(19,6) DEFAULT NULL,
+  `YesNo_cd_Self employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of self-employment` int(11) DEFAULT NULL,
+  `Business address` text,
+  `Number of employees` int(11) DEFAULT NULL,
+  `Monthly salaries paid` decimal(19,6) DEFAULT NULL,
+  `Monthly net income of business activity` decimal(19,6) DEFAULT NULL,
+  `Monthly rent` decimal(19,6) DEFAULT NULL,
+  `Other income generating activities` varchar(100) DEFAULT NULL,
+  `YesNo_cd_Bookkeeping` int(11) DEFAULT NULL,
+  `YesNo_cd_Loans with other institutions` int(11) NOT NULL,
+  `From whom` varchar(100) DEFAULT NULL,
+  `Amount` decimal(19,6) DEFAULT NULL,
+  `Interest rate pa` decimal(19,6) DEFAULT NULL,
+  `Number of people depending on overal income` int(11) NOT NULL,
+  `YesNo_cd_Bank account` int(11) NOT NULL,
+  `YesNo_cd_Business plan provided` int(11) NOT NULL,
+  `YesNo_cd_Access to internet` int(11) DEFAULT NULL,
+  `Introduced by` varchar(100) DEFAULT NULL,
+  `Known to introducer since` varchar(100) NOT NULL,
+  `Last visited by` varchar(100) DEFAULT NULL,
+  `Last visited on` date NOT NULL,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_client_additional_data` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `client additional data`
+--
+
+LOCK TABLES `client additional data` WRITE;
+/*!40000 ALTER TABLE `client additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `client additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_client_details`
+--
+
+DROP TABLE IF EXISTS `extra_client_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_client_details` (
+  `client_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_extra_client_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_client_details`
+--
+
+LOCK TABLES `extra_client_details` WRITE;
+/*!40000 ALTER TABLE `extra_client_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_client_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_family_details`
+--
+
+DROP TABLE IF EXISTS `extra_family_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_family_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `Name` varchar(40) DEFAULT NULL,
+  `Date of Birth` date DEFAULT NULL,
+  `Points Score` int(11) DEFAULT NULL,
+  `Education_cd_Highest` int(11) DEFAULT NULL,
+  `Other Notes` text,
+  PRIMARY KEY (`id`),
+  KEY `FK_Extra Family Details Data_1` (`client_id`),
+  CONSTRAINT `FK_family_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_family_details`
+--
+
+LOCK TABLES `extra_family_details` WRITE;
+/*!40000 ALTER TABLE `extra_family_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_family_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_loan_details`
+--
+
+DROP TABLE IF EXISTS `extra_loan_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_loan_details` (
+  `loan_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_extra_loan_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_loan_details`
+--
+
+LOCK TABLES `extra_loan_details` WRITE;
+/*!40000 ALTER TABLE `extra_loan_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_loan_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `impact measurement`
+--
+
+DROP TABLE IF EXISTS `impact measurement`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `impact measurement` (
+  `loan_id` bigint(20) NOT NULL,
+  `YesNo_cd_RepaidOnSchedule` int(11) NOT NULL,
+  `ReasonNotRepaidOnSchedule` text,
+  `How was Loan Amount Invested` text NOT NULL,
+  `Additional Income Generated` decimal(19,6) NOT NULL,
+  `Additional Income Used For` text NOT NULL,
+  `YesNo_cd_NewJobsCreated` int(11) NOT NULL,
+  `Number of Jobs Created` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_impact measurement` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `impact measurement`
+--
+
+LOCK TABLES `impact measurement` WRITE;
+/*!40000 ALTER TABLE `impact measurement` DISABLE KEYS */;
+/*!40000 ALTER TABLE `impact measurement` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `loan additional data`
+--
+
+DROP TABLE IF EXISTS `loan additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `loan additional data` (
+  `loan_id` bigint(20) NOT NULL,
+  `PurposeOfLoan_cd` int(11) NOT NULL,
+  `CollateralType_cd` int(11) NOT NULL,
+  `Collateral notes` text NOT NULL,
+  `YesNo_cd_Guarantor` int(11) NOT NULL,
+  `Guarantor name` varchar(100) DEFAULT NULL,
+  `Guarantor relation` varchar(100) DEFAULT NULL,
+  `Guarantor address` varchar(100) DEFAULT NULL,
+  `Guarantor telephone number` varchar(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_loan_additional_data` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `loan additional data`
+--
+
+LOCK TABLES `loan additional data` WRITE;
+/*!40000 ALTER TABLE `loan additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `loan additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser`
+--
+
+DROP TABLE IF EXISTS `m_appuser`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser`
+--
+
+LOCK TABLES `m_appuser` WRITE;
+/*!40000 ALTER TABLE `m_appuser` DISABLE KEYS */;
+INSERT INTO `m_appuser` VALUES (1,0,1,'mifos','App','Administrator','5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a','demomfi@mifos.org','\0','','','','');
+/*!40000 ALTER TABLE `m_appuser` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser_role`
+--
+
+DROP TABLE IF EXISTS `m_appuser_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser_role`
+--
+
+LOCK TABLES `m_appuser_role` WRITE;
+/*!40000 ALTER TABLE `m_appuser_role` DISABLE KEYS */;
+INSERT INTO `m_appuser_role` VALUES (1,1);
+/*!40000 ALTER TABLE `m_appuser_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar`
+--
+
+DROP TABLE IF EXISTS `m_calendar`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `title` varchar(50) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar`
+--
+
+LOCK TABLES `m_calendar` WRITE;
+/*!40000 ALTER TABLE `m_calendar` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar_instance`
+--
+
+DROP TABLE IF EXISTS `m_calendar_instance`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar_instance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `entity_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar_instance`
+--
+
+LOCK TABLES `m_calendar_instance` WRITE;
+/*!40000 ALTER TABLE `m_calendar_instance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar_instance` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_charge`
+--
+
+DROP TABLE IF EXISTS `m_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_charge`
+--
+
+LOCK TABLES `m_charge` WRITE;
+/*!40000 ALTER TABLE `m_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client`
+--
+
+DROP TABLE IF EXISTS `m_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `image_key` varchar(500) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client`
+--
+
+LOCK TABLES `m_client` WRITE;
+/*!40000 ALTER TABLE `m_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client_identifier`
+--
+
+DROP TABLE IF EXISTS `m_client_identifier`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client_identifier`
+--
+
+LOCK TABLES `m_client_identifier` WRITE;
+/*!40000 ALTER TABLE `m_client_identifier` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_identifier` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code`
+--
+
+DROP TABLE IF EXISTS `m_code`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code`
+--
+
+LOCK TABLES `m_code` WRITE;
+/*!40000 ALTER TABLE `m_code` DISABLE KEYS */;
+INSERT INTO `m_code` VALUES (1,'Customer Identifier',1),(2,'LoanCollateral',1),(3,'LoanPurpose',1),(4,'Gender',1),(5,'YesNo',1);
+/*!40000 ALTER TABLE `m_code` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code_value`
+--
+
+DROP TABLE IF EXISTS `m_code_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code_value`
+--
+
+LOCK TABLES `m_code_value` WRITE;
+/*!40000 ALTER TABLE `m_code_value` DISABLE KEYS */;
+INSERT INTO `m_code_value` VALUES (1,1,'Passport',1),(2,1,'Id',1),(3,1,'Drivers License',2),(4,1,'Any Other Id Type',3);
+/*!40000 ALTER TABLE `m_code_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_currency`
+--
+
+DROP TABLE IF EXISTS `m_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_currency`
+--
+
+LOCK TABLES `m_currency` WRITE;
+/*!40000 ALTER TABLE `m_currency` DISABLE KEYS */;
+INSERT INTO `m_currency` VALUES (1,'AED',2,NULL,'UAE Dirham','currency.AED'),(2,'AFN',2,NULL,'Afghanistan Afghani','currency.AFN'),(3,'ALL',2,NULL,'Albanian Lek','currency.ALL'),(4,'AMD',2,NULL,'Armenian Dram','currency.AMD'),(5,'ANG',2,NULL,'Netherlands Antillian Guilder','currency.ANG'),(6,'AOA',2,NULL,'Angolan Kwanza','currency.AOA'),(7,'ARS',2,'$','Argentine Peso','currency.ARS'),(8,'AUD',2,'A$','Australian Dollar','currency.AUD'),(9,'AWG',2,NULL,'Aruban Guilder','currency.AWG'),(10,'AZM',2,NULL,'Azerbaijanian Manat','currency.AZM'),(11,'BAM',2,NULL,'Bosnia and Herzegovina Convertible Marks','currency.BAM'),(12,'BBD',2,NULL,'Barbados Dollar','currency.BBD'),(13,'BDT',2,NULL,'Bangladesh Taka','currency.BDT'),(14,'BGN',2,NULL,'Bulgarian Lev','currency.BGN'),(15,'BHD',3,NULL,'Bahraini Dinar','currency.BHD'),(16,'BIF',0,NULL,'Burundi Franc','currency.BIF'),(17,'BMD',2,NULL,'Bermudian Dollar','currency.BMD'),(18,'BND',2,'B$','Brunei Dollar','currency.BND'),(19,'BOB',2,'Bs.','Bolivian Boliviano','currency.BOB'),(20,'BRL',2,'R$','Brazilian Real','currency.BRL'),(21,'BSD',2,NULL,'Bahamian Dollar','currency.BSD'),(22,'BTN',2,NULL,'Bhutan Ngultrum','currency.BTN'),(23,'BWP',2,NULL,'Botswana Pula','currency.BWP'),(24,'BYR',0,NULL,'Belarussian Ruble','currency.BYR'),(25,'BZD',2,'BZ$','Belize Dollar','currency.BZD'),(26,'CAD',2,NULL,'Canadian Dollar','currency.CAD'),(27,'CDF',2,NULL,'Franc Congolais','currency.CDF'),(28,'CHF',2,NULL,'Swiss Franc','currency.CHF'),(29,'CLP',0,'$','Chilean Peso','currency.CLP'),(30,'CNY',2,NULL,'Chinese Yuan Renminbi','currency.CNY'),(31,'COP',2,'$','Colombian Peso','currency.COP'),(32,'CRC',2,'₡','Costa Rican Colon','currency.CRC'),(33,'CSD',2,NULL,'Serbian Dinar','currency.CSD'),(34,'CUP',2,'$MN','Cuban Peso','currency.CUP'),(35,'CVE',2,NULL,'Cape Verde Escudo','currency.CVE'),(36,'CYP',2,NULL,'Cyprus Pound','currency.CYP'),(37,'CZK',2,NULL,'Czech Koruna','currency.CZK'),(38,'DJF',0,NULL,'Djibouti Franc','currency.DJF'),(39,'DKK',2,NULL,'Danish Krone','currency.DKK'),(40,'DOP',2,'RD$','Dominican Peso','currency.DOP'),(41,'DZD',2,NULL,'Algerian Dinar','currency.DZD'),(42,'EEK',2,NULL,'Estonian Kroon','currency.EEK'),(43,'EGP',2,NULL,'Egyptian Pound','currency.EGP'),(44,'ERN',2,NULL,'Eritrea Nafka','currency.ERN'),(45,'ETB',2,NULL,'Ethiopian Birr','currency.ETB'),(46,'EUR',2,'€','Euro','currency.EUR'),(47,'FJD',2,NULL,'Fiji Dollar','currency.FJD'),(48,'FKP',2,NULL,'Falkland Islands Pound','currency.FKP'),(49,'GBP',2,NULL,'Pound Sterling','currency.GBP'),(50,'GEL',2,NULL,'Georgian Lari','currency.GEL'),(51,'GHC',2,'GHc','Ghana Cedi','currency.GHC'),(52,'GIP',2,NULL,'Gibraltar Pound','currency.GIP'),(53,'GMD',2,NULL,'Gambian Dalasi','currency.GMD'),(54,'GNF',0,NULL,'Guinea Franc','currency.GNF'),(55,'GTQ',2,'Q','Guatemala Quetzal','currency.GTQ'),(56,'GYD',2,NULL,'Guyana Dollar','currency.GYD'),(57,'HKD',2,NULL,'Hong Kong Dollar','currency.HKD'),(58,'HNL',2,'L','Honduras Lempira','currency.HNL'),(59,'HRK',2,NULL,'Croatian Kuna','currency.HRK'),(60,'HTG',2,'G','Haiti Gourde','currency.HTG'),(61,'HUF',2,NULL,'Hungarian Forint','currency.HUF'),(62,'IDR',2,NULL,'Indonesian Rupiah','currency.IDR'),(63,'ILS',2,NULL,'New Israeli Shekel','currency.ILS'),(64,'INR',2,'₹','Indian Rupee','currency.INR'),(65,'IQD',3,NULL,'Iraqi Dinar','currency.IQD'),(66,'IRR',2,NULL,'Iranian Rial','currency.IRR'),(67,'ISK',0,NULL,'Iceland Krona','currency.ISK'),(68,'JMD',2,NULL,'Jamaican Dollar','currency.JMD'),(69,'JOD',3,NULL,'Jordanian Dinar','currency.JOD'),(70,'JPY',0,NULL,'Japanese Yen','currency.JPY'),(71,'KES',2,'KSh','Kenyan Shilling','currency.KES'),(72,'KGS',2,NULL,'Kyrgyzstan Som','currency.KGS'),(73,'KHR',2,NULL,'Cambodia Riel','currency.KHR'),(74,'KMF',0,NULL,'Comoro Franc','currency.KMF'),(75,'KPW',2,NULL,'North Korean Won','currency.KPW'),(76,'KRW',0,NULL,'Korean Won','currency.KRW'),(77,'KWD',3,NULL,'Kuwaiti Dinar','currency.KWD'),(78,'KYD',2,NULL,'Cayman Islands Dollar','currency.KYD'),(79,'KZT',2,NULL,'Kazakhstan Tenge','currency.KZT'),(80,'LAK',2,NULL,'Lao Kip','currency.LAK'),(81,'LBP',2,'L£','Lebanese Pound','currency.LBP'),(82,'LKR',2,NULL,'Sri Lanka Rupee','currency.LKR'),(83,'LRD',2,NULL,'Liberian Dollar','currency.LRD'),(84,'LSL',2,NULL,'Lesotho Loti','currency.LSL'),(85,'LTL',2,NULL,'Lithuanian Litas','currency.LTL'),(86,'LVL',2,NULL,'Latvian Lats','currency.LVL'),(87,'LYD',3,NULL,'Libyan Dinar','currency.LYD'),(88,'MAD',2,NULL,'Moroccan Dirham','currency.MAD'),(89,'MDL',2,NULL,'Moldovan Leu','currency.MDL'),(90,'MGA',2,NULL,'Malagasy Ariary','currency.MGA'),(91,'MKD',2,NULL,'Macedonian Denar','currency.MKD'),(92,'MMK',2,'K','Myanmar Kyat','currency.MMK'),(93,'MNT',2,NULL,'Mongolian Tugrik','currency.MNT'),(94,'MOP',2,NULL,'Macau Pataca','currency.MOP'),(95,'MRO',2,NULL,'Mauritania Ouguiya','currency.MRO'),(96,'MTL',2,NULL,'Maltese Lira','currency.MTL'),(97,'MUR',2,NULL,'Mauritius Rupee','currency.MUR'),(98,'MVR',2,NULL,'Maldives Rufiyaa','currency.MVR'),(99,'MWK',2,NULL,'Malawi Kwacha','currency.MWK'),(100,'MXN',2,'$','Mexican Peso','currency.MXN'),(101,'MYR',2,NULL,'Malaysian Ringgit','currency.MYR'),(102,'MZM',2,NULL,'Mozambique Metical','currency.MZM'),(103,'NAD',2,NULL,'Namibia Dollar','currency.NAD'),(104,'NGN',2,NULL,'Nigerian Naira','currency.NGN'),(105,'NIO',2,'C$','Nicaragua Cordoba Oro','currency.NIO'),(106,'NOK',2,NULL,'Norwegian Krone','currency.NOK'),(107,'NPR',2,NULL,'Nepalese Rupee','currency.NPR'),(108,'NZD',2,NULL,'New Zealand Dollar','currency.NZD'),(109,'OMR',3,NULL,'Rial Omani','currency.OMR'),(110,'PAB',2,'B/.','Panama Balboa','currency.PAB'),(111,'PEN',2,'S/.','Peruvian Nuevo Sol','currency.PEN'),(112,'PGK',2,NULL,'Papua New Guinea Kina','currency.PGK'),(113,'PHP',2,NULL,'Philippine Peso','currency.PHP'),(114,'PKR',2,NULL,'Pakistan Rupee','currency.PKR'),(115,'PLN',2,NULL,'Polish Zloty','currency.PLN'),(116,'PYG',0,'₲','Paraguayan Guarani','currency.PYG'),(117,'QAR',2,NULL,'Qatari Rial','currency.QAR'),(118,'RON',2,NULL,'Romanian Leu','currency.RON'),(119,'RUB',2,NULL,'Russian Ruble','currency.RUB'),(120,'RWF',0,NULL,'Rwanda Franc','currency.RWF'),(121,'SAR',2,NULL,'Saudi Riyal','currency.SAR'),(122,'SBD',2,NULL,'Solomon Islands Dollar','currency.SBD'),(123,'SCR',2,NULL,'Seychelles Rupee','currency.SCR'),(124,'SDD',2,NULL,'Sudanese Dinar','currency.SDD'),(125,'SEK',2,NULL,'Swedish Krona','currency.SEK'),(126,'SGD',2,NULL,'Singapore Dollar','currency.SGD'),(127,'SHP',2,NULL,'St Helena Pound','currency.SHP'),(128,'SIT',2,NULL,'Slovenian Tolar','currency.SIT'),(129,'SKK',2,NULL,'Slovak Koruna','currency.SKK'),(130,'SLL',2,NULL,'Sierra Leone Leone','currency.SLL'),(131,'SOS',2,NULL,'Somali Shilling','currency.SOS'),(132,'SRD',2,NULL,'Surinam Dollar','currency.SRD'),(133,'STD',2,NULL,'Sao Tome and Principe Dobra','currency.STD'),(134,'SVC',2,NULL,'El Salvador Colon','currency.SVC'),(135,'SYP',2,NULL,'Syrian Pound','currency.SYP'),(136,'SZL',2,NULL,'Swaziland Lilangeni','currency.SZL'),(137,'THB',2,NULL,'Thai Baht','currency.THB'),(138,'TJS',2,NULL,'Tajik Somoni','currency.TJS'),(139,'TMM',2,NULL,'Turkmenistan Manat','currency.TMM'),(140,'TND',3,'DT','Tunisian Dinar','currency.TND'),(141,'TOP',2,NULL,'Tonga Pa\'anga','currency.TOP'),(142,'TRY',2,NULL,'Turkish Lira','currency.TRY'),(143,'TTD',2,NULL,'Trinidad and Tobago Dollar','currency.TTD'),(144,'TWD',2,NULL,'New Taiwan Dollar','currency.TWD'),(145,'TZS',2,NULL,'Tanzanian Shilling','currency.TZS'),(146,'UAH',2,NULL,'Ukraine Hryvnia','currency.UAH'),(147,'UGX',2,'USh','Uganda Shilling','currency.UGX'),(148,'USD',2,'$','US Dollar','currency.USD'),(149,'UYU',2,'$U','Peso Uruguayo','currency.UYU'),(150,'UZS',2,NULL,'Uzbekistan Sum','currency.UZS'),(151,'VEB',2,'Bs.F.','Venezuelan Bolivar','currency.VEB'),(152,'VND',2,NULL,'Vietnamese Dong','currency.VND'),(153,'VUV',0,NULL,'Vanuatu Vatu','currency.VUV'),(154,'WST',2,NULL,'Samoa Tala','currency.WST'),(155,'XAF',0,NULL,'CFA Franc BEAC','currency.XAF'),(156,'XCD',2,NULL,'East Caribbean Dollar','currency.XCD'),(157,'XDR',5,NULL,'SDR (Special Drawing Rights)','currency.XDR'),(158,'XOF',0,'CFA','CFA Franc BCEAO','currency.XOF'),(159,'XPF',0,NULL,'CFP Franc','currency.XPF'),(160,'YER',2,NULL,'Yemeni Rial','currency.YER'),(161,'ZAR',2,'R','South African Rand','currency.ZAR'),(162,'ZMK',2,NULL,'Zambian Kwacha','currency.ZMK'),(163,'ZWD',2,NULL,'Zimbabwe Dollar','currency.ZWD');
+/*!40000 ALTER TABLE `m_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_document`
+--
+
+DROP TABLE IF EXISTS `m_document`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(50) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_document`
+--
+
+LOCK TABLES `m_document` WRITE;
+/*!40000 ALTER TABLE `m_document` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_document` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_fund`
+--
+
+DROP TABLE IF EXISTS `m_fund`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_fund`
+--
+
+LOCK TABLES `m_fund` WRITE;
+/*!40000 ALTER TABLE `m_fund` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_fund` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group`
+--
+
+DROP TABLE IF EXISTS `m_group`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `level_Id` int(11) NOT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`,`level_Id`),
+  UNIQUE KEY `external_id` (`external_id`,`level_Id`),
+  KEY `office_id` (`office_id`),
+  KEY `staff_id` (`staff_id`),
+  KEY `Parent_Id_reference` (`parent_id`),
+  KEY `FK_m_group_level` (`level_Id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_Id`) REFERENCES `m_group_level` (`id`),
+  CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group`
+--
+
+LOCK TABLES `m_group` WRITE;
+/*!40000 ALTER TABLE `m_group` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_client`
+--
+
+DROP TABLE IF EXISTS `m_group_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_client`
+--
+
+LOCK TABLES `m_group_client` WRITE;
+/*!40000 ALTER TABLE `m_group_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_level`
+--
+
+DROP TABLE IF EXISTS `m_group_level`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_level` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) DEFAULT NULL,
+  `super_parent` tinyint(1) NOT NULL,
+  `level_name` varchar(100) NOT NULL,
+  `recursable` tinyint(1) NOT NULL,
+  `can_have_clients` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `Parent_levelId_reference` (`parent_id`),
+  CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_level`
+--
+
+LOCK TABLES `m_group_level` WRITE;
+/*!40000 ALTER TABLE `m_group_level` DISABLE KEYS */;
+INSERT INTO `m_group_level` VALUES (1,NULL,1,'Center',1,0),(2,1,0,'Group',0,1);
+/*!40000 ALTER TABLE `m_group_level` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_guarantor`
+--
+
+DROP TABLE IF EXISTS `m_guarantor`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_guarantor` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `dob` date DEFAULT NULL,
+  `address_line_1` varchar(500) DEFAULT NULL,
+  `address_line_2` varchar(500) DEFAULT NULL,
+  `city` varchar(50) DEFAULT NULL,
+  `state` varchar(50) DEFAULT NULL,
+  `country` varchar(50) DEFAULT NULL,
+  `zip` varchar(20) DEFAULT NULL,
+  `house_phone_number` varchar(20) DEFAULT NULL,
+  `mobile_number` varchar(20) DEFAULT NULL,
+  `comment` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_m_loan` (`loan_id`),
+  CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_guarantor`
+--
+
+LOCK TABLES `m_guarantor` WRITE;
+/*!40000 ALTER TABLE `m_guarantor` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan`
+--
+
+DROP TABLE IF EXISTS `m_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `loanpurpose_cv_id` int(11) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `disbursedon_userid` bigint(20) DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `principal_disbursed_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `rescheduledon_date` date DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `writtenoffon_date` date DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loanpurpose_codevalue` (`loanpurpose_cv_id`),
+  KEY `FK_submittedon_userid` (`submittedon_userid`),
+  KEY `FK_approvedon_userid` (`approvedon_userid`),
+  KEY `FK_rejectedon_userid` (`rejectedon_userid`),
+  KEY `FK_withdrawnon_userid` (`withdrawnon_userid`),
+  KEY `FK_disbursedon_userid` (`disbursedon_userid`),
+  KEY `FK_closedon_userid` (`closedon_userid`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_approvedon_userid` FOREIGN KEY (`approvedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_closedon_userid` FOREIGN KEY (`closedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_disbursedon_userid` FOREIGN KEY (`disbursedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loanpurpose_codevalue` FOREIGN KEY (`loanpurpose_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_rejectedon_userid` FOREIGN KEY (`rejectedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_submittedon_userid` FOREIGN KEY (`submittedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_withdrawnon_userid` FOREIGN KEY (`withdrawnon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan`
+--
+
+LOCK TABLES `m_loan` WRITE;
+/*!40000 ALTER TABLE `m_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_arrears_aging`
+--
+
+DROP TABLE IF EXISTS `m_loan_arrears_aging`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_arrears_aging` (
+  `loan_id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `principal_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `overdue_since_date_derived` date DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_arrears_aging_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_arrears_aging`
+--
+
+LOCK TABLES `m_loan_arrears_aging` WRITE;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_charge`
+--
+
+LOCK TABLES `m_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_collateral`
+--
+
+DROP TABLE IF EXISTS `m_loan_collateral`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_collateral` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_cv_id` int(11) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_collateral_m_loan` (`loan_id`),
+  KEY `FK_collateral_code_value` (`type_cv_id`),
+  CONSTRAINT `FK_collateral_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_collateral_code_value` FOREIGN KEY (`type_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_collateral`
+--
+
+LOCK TABLES `m_loan_collateral` WRITE;
+/*!40000 ALTER TABLE `m_loan_collateral` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_collateral` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_officer_assignment_history`
+--
+
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_officer_assignment_history`
+--
+
+LOCK TABLES `m_loan_officer_assignment_history` WRITE;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_repayment_schedule`
+--
+
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_repayment_schedule`
+--
+
+LOCK TABLES `m_loan_repayment_schedule` WRITE;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_transaction`
+--
+
+DROP TABLE IF EXISTS `m_loan_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_transaction`
+--
+
+LOCK TABLES `m_loan_transaction` WRITE;
+/*!40000 ALTER TABLE `m_loan_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_note`
+--
+
+DROP TABLE IF EXISTS `m_note`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK_m_note_m_group` (`group_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_note_m_group` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_note`
+--
+
+LOCK TABLES `m_note` WRITE;
+/*!40000 ALTER TABLE `m_note` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_note` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office`
+--
+
+DROP TABLE IF EXISTS `m_office`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office`
+--
+
+LOCK TABLES `m_office` WRITE;
+/*!40000 ALTER TABLE `m_office` DISABLE KEYS */;
+INSERT INTO `m_office` VALUES (1,NULL,'.','1','Head Office','2009-01-01');
+/*!40000 ALTER TABLE `m_office` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office_transaction`
+--
+
+DROP TABLE IF EXISTS `m_office_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office_transaction`
+--
+
+LOCK TABLES `m_office_transaction` WRITE;
+/*!40000 ALTER TABLE `m_office_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_office_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_organisation_currency`
+--
+
+DROP TABLE IF EXISTS `m_organisation_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_organisation_currency`
+--
+
+LOCK TABLES `m_organisation_currency` WRITE;
+/*!40000 ALTER TABLE `m_organisation_currency` DISABLE KEYS */;
+INSERT INTO `m_organisation_currency` VALUES (21,'USD',2,'US Dollar','$','currency.USD');
+/*!40000 ALTER TABLE `m_organisation_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_permission`
+--
+
+DROP TABLE IF EXISTS `m_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=287 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_permission`
+--
+
+LOCK TABLES `m_permission` WRITE;
+/*!40000 ALTER TABLE `m_permission` DISABLE KEYS */;
+INSERT INTO `m_permission` VALUES (1,'special','ALL_FUNCTIONS',NULL,NULL,0),(2,'special','ALL_FUNCTIONS_READ',NULL,NULL,0),(3,'special','CHECKER_SUPER_USER',NULL,NULL,0),(4,'special','REPORTING_SUPER_USER',NULL,NULL,0),(5,'authorisation','READ_PERMISSION','PERMISSION','READ',0),(6,'authorisation','PERMISSIONS_ROLE','ROLE','PERMISSIONS',0),(7,'authorisation','CREATE_ROLE','ROLE','CREATE',0),(8,'authorisation','CREATE_ROLE_CHECKER','ROLE','CREATE',0),(9,'authorisation','READ_ROLE','ROLE','READ',0),(10,'authorisation','UPDATE_ROLE','ROLE','UPDATE',0),(11,'authorisation','UPDATE_ROLE_CHECKER','ROLE','UPDATE',0),(12,'authorisation','DELETE_ROLE','ROLE','DELETE',0),(13,'authorisation','DELETE_ROLE_CHECKER','ROLE','DELETE',0),(14,'authorisation','CREATE_USER','USER','CREATE',0),(15,'authorisation','CREATE_USER_CHECKER','USER','CREATE',0),(16,'authorisation','READ_USER','USER','READ',0),(17,'authorisation','UPDATE_USER','USER','UPDATE',0),(18,'authorisation','UPDATE_USER_CHECKER','USER','UPDATE',0),(19,'authorisation','DELETE_USER','USER','DELETE',0),(20,'authorisation','DELETE_USER_CHECKER','USER','DELETE',0),(21,'configuration','READ_CONFIGURATION','CONFIGURATION','READ',0),(22,'configuration','UPDATE_CONFIGURATION','CONFIGURATION','UPDATE',0),(23,'configuration','UPDATE_CONFIGURATION_CHECKER','CONFIGURATION','UPDATE',0),(24,'configuration','READ_CODE','CODE','READ',0),(25,'configuration','CREATE_CODE','CODE','CREATE',0),(26,'configuration','CREATE_CODE_CHECKER','CODE','CREATE',0),(27,'configuration','UPDATE_CODE','CODE','UPDATE',0),(28,'configuration','UPDATE_CODE_CHECKER','CODE','UPDATE',0),(29,'configuration','DELETE_CODE','CODE','DELETE',0),(30,'configuration','DELETE_CODE_CHECKER','CODE','DELETE',0),(31,'configuration','READ_CODEVALUE','CODEVALUE','READ',0),(32,'configuration','CREATE_CODEVALUE','CODEVALUE','CREATE',0),(33,'configuration','CREATE_CODEVALUE_CHECKER','CODEVALUE','CREATE',0),(34,'configuration','UPDATE_CODEVALUE','CODEVALUE','UPDATE',0),(35,'configuration','UPDATE_CODEVALUE_CHECKER','CODEVALUE','UPDATE',0),(36,'configuration','DELETE_CODEVALUE','CODEVALUE','DELETE',0),(37,'configuration','DELETE_CODEVALUE_CHECKER','CODEVALUE','DELETE',0),(38,'configuration','READ_CURRENCY','CURRENCY','READ',0),(39,'configuration','UPDATE_CURRENCY','CURRENCY','UPDATE',0),(40,'configuration','UPDATE_CURRENCY_CHECKER','CURRENCY','UPDATE',0),(41,'configuration','UPDATE_PERMISSION','PERMISSION','UPDATE',0),(42,'configuration','UPDATE_PERMISSION_CHECKER','PERMISSION','UPDATE',0),(43,'configuration','READ_DATATABLE','DATATABLE','READ',0),(44,'configuration','REGISTER_DATATABLE','DATATABLE','REGISTER',0),(45,'configuration','REGISTER_DATATABLE_CHECKER','DATATABLE','REGISTER',0),(46,'configuration','DEREGISTER_DATATABLE','DATATABLE','DEREGISTER',0),(47,'configuration','DEREGISTER_DATATABLE_CHECKER','DATATABLE','DEREGISTER',0),(48,'configuration','READ_AUDIT','AUDIT','READ',0),(49,'configuration','CREATE_CALENDAR','CALENDAR','CREATE',0),(50,'configuration','READ_CALENDAR','CALENDAR','READ',0),(51,'configuration','UPDATE_CALENDAR','CALENDAR','UPDATE',0),(52,'configuration','DELETE_CALENDAR','CALENDAR','DELETE',0),(53,'configuration','CREATE_CALENDAR_CHECKER','CALENDAR','CREATE',0),(54,'configuration','UPDATE_CALENDAR_CHECKER','CALENDAR','UPDATE',0),(55,'configuration','DELETE_CALENDAR_CHECKER','CALENDAR','DELETE',0),(56,'organisation','READ_MAKERCHECKER','MAKERCHECKER','READ',0),(57,'organisation','READ_CHARGE','CHARGE','READ',0),(58,'organisation','CREATE_CHARGE','CHARGE','CREATE',0),(59,'organisation','CREATE_CHARGE_CHECKER','CHARGE','CREATE',0),(60,'organisation','UPDATE_CHARGE','CHARGE','UPDATE',0),(61,'organisation','UPDATE_CHARGE_CHECKER','CHARGE','UPDATE',0),(62,'organisation','DELETE_CHARGE','CHARGE','DELETE',0),(63,'organisation','DELETE_CHARGE_CHECKER','CHARGE','DELETE',0),(64,'organisation','READ_FUND','FUND','READ',0),(65,'organisation','CREATE_FUND','FUND','CREATE',0),(66,'organisation','CREATE_FUND_CHECKER','FUND','CREATE',0),(67,'organisation','UPDATE_FUND','FUND','UPDATE',0),(68,'organisation','UPDATE_FUND_CHECKER','FUND','UPDATE',0),(69,'organisation','DELETE_FUND','FUND','DELETE',0),(70,'organisation','DELETE_FUND_CHECKER','FUND','DELETE',0),(71,'organisation','READ_LOANPRODUCT','LOANPRODUCT','READ',0),(72,'organisation','CREATE_LOANPRODUCT','LOANPRODUCT','CREATE',0),(73,'organisation','CREATE_LOANPRODUCT_CHECKER','LOANPRODUCT','CREATE',0),(74,'organisation','UPDATE_LOANPRODUCT','LOANPRODUCT','UPDATE',0),(75,'organisation','UPDATE_LOANPRODUCT_CHECKER','LOANPRODUCT','UPDATE',0),(76,'organisation','DELETE_LOANPRODUCT','LOANPRODUCT','DELETE',0),(77,'organisation','DELETE_LOANPRODUCT_CHECKER','LOANPRODUCT','DELETE',0),(78,'organisation','READ_OFFICE','OFFICE','READ',0),(79,'organisation','CREATE_OFFICE','OFFICE','CREATE',0),(80,'organisation','CREATE_OFFICE_CHECKER','OFFICE','CREATE',0),(81,'organisation','UPDATE_OFFICE','OFFICE','UPDATE',0),(82,'organisation','UPDATE_OFFICE_CHECKER','OFFICE','UPDATE',0),(83,'organisation','READ_OFFICETRANSACTION','OFFICETRANSACTION','READ',0),(84,'organisation','DELETE_OFFICE_CHECKER','OFFICE','DELETE',0),(85,'organisation','CREATE_OFFICETRANSACTION','OFFICETRANSACTION','CREATE',0),(86,'organisation','CREATE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','CREATE',0),(87,'organisation','DELETE_OFFICETRANSACTION','OFFICETRANSACTION','DELETE',0),(88,'organisation','DELETE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','DELETE',0),(89,'organisation','READ_STAFF','STAFF','READ',0),(90,'organisation','CREATE_STAFF','STAFF','CREATE',0),(91,'organisation','CREATE_STAFF_CHECKER','STAFF','CREATE',0),(92,'organisation','UPDATE_STAFF','STAFF','UPDATE',0),(93,'organisation','UPDATE_STAFF_CHECKER','STAFF','UPDATE',0),(94,'organisation','DELETE_STAFF','STAFF','DELETE',0),(95,'organisation','DELETE_STAFF_CHECKER','STAFF','DELETE',0),(96,'organisation','READ_SAVINGSPRODUCT','SAVINGSPRODUCT','READ',0),(97,'organisation','CREATE_SAVINGSPRODUCT','SAVINGSPRODUCT','CREATE',0),(98,'organisation','CREATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','CREATE',0),(99,'organisation','UPDATE_SAVINGSPRODUCT','SAVINGSPRODUCT','UPDATE',0),(100,'organisation','UPDATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','UPDATE',0),(101,'organisation','DELETE_SAVINGSPRODUCT','SAVINGSPRODUCT','DELETE',0),(102,'organisation','DELETE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','DELETE',0),(103,'portfolio','READ_LOAN','LOAN','READ',0),(104,'portfolio','CREATE_LOAN','LOAN','CREATE',0),(105,'portfolio','CREATE_LOAN_CHECKER','LOAN','CREATE',0),(106,'portfolio','UPDATE_LOAN','LOAN','UPDATE',0),(107,'portfolio','UPDATE_LOAN_CHECKER','LOAN','UPDATE',0),(108,'portfolio','DELETE_LOAN','LOAN','DELETE',0),(109,'portfolio','DELETE_LOAN_CHECKER','LOAN','DELETE',0),(110,'portfolio','READ_CLIENT','CLIENT','READ',0),(111,'portfolio','CREATE_CLIENT','CLIENT','CREATE',0),(112,'portfolio','CREATE_CLIENT_CHECKER','CLIENT','CREATE',0),(113,'portfolio','UPDATE_CLIENT','CLIENT','UPDATE',0),(114,'portfolio','UPDATE_CLIENT_CHECKER','CLIENT','UPDATE',0),(115,'portfolio','DELETE_CLIENT','CLIENT','DELETE',0),(116,'portfolio','DELETE_CLIENT_CHECKER','CLIENT','DELETE',0),(117,'portfolio','READ_CLIENTIMAGE','CLIENTIMAGE','READ',0),(118,'portfolio','CREATE_CLIENTIMAGE','CLIENTIMAGE','CREATE',0),(119,'portfolio','CREATE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','CREATE',0),(120,'portfolio','DELETE_CLIENTIMAGE','CLIENTIMAGE','DELETE',0),(121,'portfolio','DELETE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','DELETE',0),(122,'portfolio','READ_CLIENTNOTE','CLIENTNOTE','READ',0),(123,'portfolio','CREATE_CLIENTNOTE','CLIENTNOTE','CREATE',0),(124,'portfolio','CREATE_CLIENTNOTE_CHECKER','CLIENTNOTE','CREATE',0),(125,'portfolio','UPDATE_CLIENTNOTE','CLIENTNOTE','UPDATE',0),(126,'portfolio','UPDATE_CLIENTNOTE_CHECKER','CLIENTNOTE','UPDATE',0),(127,'portfolio','DELETE_CLIENTNOTE','CLIENTNOTE','DELETE',0),(128,'portfolio','DELETE_CLIENTNOTE_CHECKER','CLIENTNOTE','DELETE',0),(129,'portfolio','READ_GROUPNOTE','GROUPNOTE','READ',0),(130,'portfolio','CREATE_GROUPNOTE','GROUPNOTE','CREATE',0),(131,'portfolio','UPDATE_GROUPNOTE','GROUPNOTE','UPDATE',0),(132,'portfolio','DELETE_GROUPNOTE','GROUPNOTE','DELETE',0),(133,'portfolio','CREATE_GROUPNOTE_CHECKER','GROUPNOTE','CREATE',0),(134,'portfolio','UPDATE_GROUPNOTE_CHECKER','GROUPNOTE','UPDATE',0),(135,'portfolio','DELETE_GROUPNOTE_CHECKER','GROUPNOTE','DELETE',0),(136,'portfolio','READ_LOANNOTE','LOANNOTE','READ',0),(137,'portfolio','CREATE_LOANNOTE','LOANNOTE','CREATE',0),(138,'portfolio','UPDATE_LOANNOTE','LOANNOTE','UPDATE',0),(139,'portfolio','DELETE_LOANNOTE','LOANNOTE','DELETE',0),(140,'portfolio','CREATE_LOANNOTE_CHECKER','LOANNOTE','CREATE',0),(141,'portfolio','UPDATE_LOANNOTE_CHECKER','LOANNOTE','UPDATE',0),(142,'portfolio','DELETE_LOANNOTE_CHECKER','LOANNOTE','DELETE',0),(143,'portfolio','READ_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','READ',0),(144,'portfolio','CREATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','CREATE',0),(145,'portfolio','UPDATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','UPDATE',0),(146,'portfolio','DELETE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','DELETE',0),(147,'portfolio','CREATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','CREATE',0),(148,'portfolio','UPDATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','UPDATE',0),(149,'portfolio','DELETE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','DELETE',0),(150,'portfolio','READ_SAVINGNOTE','SAVINGNOTE','READ',0),(151,'portfolio','CREATE_SAVINGNOTE','SAVINGNOTE','CREATE',0),(152,'portfolio','UPDATE_SAVINGNOTE','SAVINGNOTE','UPDATE',0),(153,'portfolio','DELETE_SAVINGNOTE','SAVINGNOTE','DELETE',0),(154,'portfolio','CREATE_SAVINGNOTE_CHECKER','SAVINGNOTE','CREATE',0),(155,'portfolio','UPDATE_SAVINGNOTE_CHECKER','SAVINGNOTE','UPDATE',0),(156,'portfolio','DELETE_SAVINGNOTE_CHECKER','SAVINGNOTE','DELETE',0),(157,'portfolio','READ_CLIENTIDENTIFIER','CLIENTIDENTIFIER','READ',0),(158,'portfolio','CREATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','CREATE',0),(159,'portfolio','CREATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','CREATE',0),(160,'portfolio','UPDATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','UPDATE',0),(161,'portfolio','UPDATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','UPDATE',0),(162,'portfolio','DELETE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','DELETE',0),(163,'portfolio','DELETE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','DELETE',0),(164,'portfolio','READ_DOCUMENT','DOCUMENT','READ',0),(165,'portfolio','CREATE_DOCUMENT','DOCUMENT','CREATE',0),(166,'portfolio','CREATE_DOCUMENT_CHECKER','DOCUMENT','CREATE',0),(167,'portfolio','UPDATE_DOCUMENT','DOCUMENT','UPDATE',0),(168,'portfolio','UPDATE_DOCUMENT_CHECKER','DOCUMENT','UPDATE',0),(169,'portfolio','DELETE_DOCUMENT','DOCUMENT','DELETE',0),(170,'portfolio','DELETE_DOCUMENT_CHECKER','DOCUMENT','DELETE',0),(171,'portfolio','READ_GROUP','GROUP','READ',0),(172,'portfolio','CREATE_GROUP','GROUP','CREATE',0),(173,'portfolio','CREATE_GROUP_CHECKER','GROUP','CREATE',0),(174,'portfolio','UPDATE_GROUP','GROUP','UPDATE',0),(175,'portfolio','UPDATE_GROUP_CHECKER','GROUP','UPDATE',0),(176,'portfolio','DELETE_GROUP','GROUP','DELETE',0),(177,'portfolio','DELETE_GROUP_CHECKER','GROUP','DELETE',0),(178,'portfolio','UNASSIGNSTAFF_GROUP','GROUP','UNASSIGNSTAFF',0),(179,'portfolio','UNASSIGNSTAFF_GROUP_CHECKER','GROUP','UNASSIGNSTAFF',0),(180,'portfolio','CREATE_LOANCHARGE','LOANCHARGE','CREATE',0),(181,'portfolio','CREATE_LOANCHARGE_CHECKER','LOANCHARGE','CREATE',0),(182,'portfolio','UPDATE_LOANCHARGE','LOANCHARGE','UPDATE',0),(183,'portfolio','UPDATE_LOANCHARGE_CHECKER','LOANCHARGE','UPDATE',0),(184,'portfolio','DELETE_LOANCHARGE','LOANCHARGE','DELETE',0),(185,'portfolio','DELETE_LOANCHARGE_CHECKER','LOANCHARGE','DELETE',0),(186,'portfolio','WAIVE_LOANCHARGE','LOANCHARGE','WAIVE',0),(187,'portfolio','WAIVE_LOANCHARGE_CHECKER','LOANCHARGE','WAIVE',0),(188,'portfolio','READ_SAVINGSACCOUNT','SAVINGSACCOUNT','READ',0),(189,'portfolio','CREATE_SAVINGSACCOUNT','SAVINGSACCOUNT','CREATE',0),(190,'portfolio','CREATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','CREATE',0),(191,'portfolio','UPDATE_SAVINGSACCOUNT','SAVINGSACCOUNT','UPDATE',0),(192,'portfolio','UPDATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','UPDATE',0),(193,'portfolio','DELETE_SAVINGSACCOUNT','SAVINGSACCOUNT','DELETE',0),(194,'portfolio','DELETE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DELETE',0),(195,'portfolio','READ_GUARANTOR','GUARANTOR','READ',0),(196,'portfolio','CREATE_GUARANTOR','GUARANTOR','CREATE',0),(197,'portfolio','CREATE_GUARANTOR_CHECKER','GUARANTOR','CREATE',0),(198,'portfolio','UPDATE_GUARANTOR','GUARANTOR','UPDATE',0),(199,'portfolio','UPDATE_GUARANTOR_CHECKER','GUARANTOR','UPDATE',0),(200,'portfolio','DELETE_GUARANTOR','GUARANTOR','DELETE',0),(201,'portfolio','DELETE_GUARANTOR_CHECKER','GUARANTOR','DELETE',0),(202,'transaction_loan','APPROVE_LOAN','LOAN','APPROVE',0),(203,'transaction_loan','APPROVEINPAST_LOAN','LOAN','APPROVEINPAST',0),(204,'transaction_loan','REJECT_LOAN','LOAN','REJECT',0),(205,'transaction_loan','REJECTINPAST_LOAN','LOAN','REJECTINPAST',0),(206,'transaction_loan','WITHDRAW_LOAN','LOAN','WITHDRAW',0),(207,'transaction_loan','WITHDRAWINPAST_LOAN','LOAN','WITHDRAWINPAST',0),(208,'transaction_loan','APPROVALUNDO_LOAN','LOAN','APPROVALUNDO',0),(209,'transaction_loan','DISBURSE_LOAN','LOAN','DISBURSE',0),(210,'transaction_loan','DISBURSEINPAST_LOAN','LOAN','DISBURSEINPAST',0),(211,'transaction_loan','DISBURSALUNDO_LOAN','LOAN','DISBURSALUNDO',0),(212,'transaction_loan','REPAYMENT_LOAN','LOAN','REPAYMENT',0),(213,'transaction_loan','REPAYMENTINPAST_LOAN','LOAN','REPAYMENTINPAST',0),(214,'transaction_loan','ADJUST_LOAN','LOAN','ADJUST',0),(215,'transaction_loan','WAIVEINTERESTPORTION_LOAN','LOAN','WAIVEINTERESTPORTION',0),(216,'transaction_loan','WRITEOFF_LOAN','LOAN','WRITEOFF',0),(217,'transaction_loan','CLOSE_LOAN','LOAN','CLOSE',0),(218,'transaction_loan','CLOSEASRESCHEDULED_LOAN','LOAN','CLOSEASRESCHEDULED',0),(219,'transaction_loan','UPDATELOANOFFICER_LOAN','LOAN','UPDATELOANOFFICER',0),(220,'transaction_loan','UPDATELOANOFFICER_LOAN_CHECKER','LOAN','UPDATELOANOFFICER',0),(221,'transaction_loan','REMOVELOANOFFICER_LOAN','LOAN','REMOVELOANOFFICER',0),(222,'transaction_loan','REMOVELOANOFFICER_LOAN_CHECKER','LOAN','REMOVELOANOFFICER',0),(223,'transaction_loan','BULKREASSIGN_LOAN','LOAN','BULKREASSIGN',0),(224,'transaction_loan','BULKREASSIGN_LOAN_CHECKER','LOAN','BULKREASSIGN',0),(225,'transaction_loan','APPROVE_LOAN_CHECKER','LOAN','APPROVE',0),(226,'transaction_loan','APPROVEINPAST_LOAN_CHECKER','LOAN','APPROVEINPAST',0),(227,'transaction_loan','REJECT_LOAN_CHECKER','LOAN','REJECT',0),(228,'transaction_loan','REJECTINPAST_LOAN_CHECKER','LOAN','REJECTINPAST',0),(229,'transaction_loan','WITHDRAW_LOAN_CHECKER','LOAN','WITHDRAW',0),(230,'transaction_loan','WITHDRAWINPAST_LOAN_CHECKER','LOAN','WITHDRAWINPAST',0),(231,'transaction_loan','APPROVALUNDO_LOAN_CHECKER','LOAN','APPROVALUNDO',0),(232,'transaction_loan','DISBURSE_LOAN_CHECKER','LOAN','DISBURSE',0),(233,'transaction_loan','DISBURSEINPAST_LOAN_CHECKER','LOAN','DISBURSEINPAST',0),(234,'transaction_loan','DISBURSALUNDO_LOAN_CHECKER','LOAN','DISBURSALUNDO',0),(235,'transaction_loan','REPAYMENT_LOAN_CHECKER','LOAN','REPAYMENT',0),(236,'transaction_loan','REPAYMENTINPAST_LOAN_CHECKER','LOAN','REPAYMENTINPAST',0),(237,'transaction_loan','ADJUST_LOAN_CHECKER','LOAN','ADJUST',0),(238,'transaction_loan','WAIVEINTERESTPORTION_LOAN_CHECKER','LOAN','WAIVEINTERESTPORTION',0),(239,'transaction_loan','WRITEOFF_LOAN_CHECKER','LOAN','WRITEOFF',0),(240,'transaction_loan','CLOSE_LOAN_CHECKER','LOAN','CLOSE',0),(241,'transaction_loan','CLOSEASRESCHEDULED_LOAN_CHECKER','LOAN','CLOSEASRESCHEDULED',0),(242,'transaction_savings','DEPOSIT_SAVINGSACCOUNT','SAVINGSACCOUNT','DEPOSIT',0),(243,'transaction_savings','DEPOSIT_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DEPOSIT',0),(244,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT','SAVINGSACCOUNT','WITHDRAWAL',0),(245,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','WITHDRAWAL',0),(246,'transaction_savings','ACTIVATE_SAVINGSACCOUNT','SAVINGSACCOUNT','ACTIVATE',0),(247,'transaction_savings','ACTIVATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','ACTIVATE',0),(248,'accounting','CREATE_GLACCOUNT','GLACCOUNT','CREATE',0),(249,'accounting','UPDATE_GLACCOUNT','GLACCOUNT','UPDATE',0),(250,'accounting','DELETE_GLACCOUNT','GLACCOUNT','DELETE',0),(251,'accounting','CREATE_GLCLOSURE','GLCLOSURE','CREATE',0),(252,'accounting','UPDATE_GLCLOSURE','GLCLOSURE','UPDATE',0),(253,'accounting','DELETE_GLCLOSURE','GLCLOSURE','DELETE',0),(254,'accounting','CREATE_JOURNALENTRY','JOURNALENTRY','CREATE',0),(255,'accounting','REVERSE_JOURNALENTRY','JOURNALENTRY','REVERSE',0),(256,'report','READ_Active Loans - Details','Active Loans - Details','READ',0),(257,'report','READ_Active Loans - Summary','Active Loans - Summary','READ',0),(258,'report','READ_Active Loans by Disbursal Period','Active Loans by Disbursal Period','READ',0),(259,'report','READ_Active Loans in last installment','Active Loans in last installment','READ',0),(260,'report','READ_Active Loans in last installment Summary','Active Loans in last installment Summary','READ',0),(261,'report','READ_Active Loans Passed Final Maturity','Active Loans Passed Final Maturity','READ',0),(262,'report','READ_Active Loans Passed Final Maturity Summary','Active Loans Passed Final Maturity Summary','READ',0),(263,'report','READ_Aging Detail','Aging Detail','READ',0),(264,'report','READ_Aging Summary (Arrears in Months)','Aging Summary (Arrears in Months)','READ',0),(265,'report','READ_Aging Summary (Arrears in Weeks)','Aging Summary (Arrears in Weeks)','READ',0),(266,'report','READ_Balance Sheet','Balance Sheet','READ',0),(267,'report','READ_Branch Expected Cash Flow','Branch Expected Cash Flow','READ',0),(268,'report','READ_Client Listing','Client Listing','READ',0),(269,'report','READ_Client Loans Listing','Client Loans Listing','READ',0),(270,'report','READ_Expected Payments By Date - Basic','Expected Payments By Date - Basic','READ',0),(271,'report','READ_Expected Payments By Date - Formatted','Expected Payments By Date - Formatted','READ',0),(272,'report','READ_Funds Disbursed Between Dates Summary','Funds Disbursed Between Dates Summary','READ',0),(273,'report','READ_Funds Disbursed Between Dates Summary by Office','Funds Disbursed Between Dates Summary by Office','READ',0),(274,'report','READ_Income Statement','Income Statement','READ',0),(275,'report','READ_Loan Account Schedule','Loan Account Schedule','READ',0),(276,'report','READ_Loans Awaiting Disbursal','Loans Awaiting Disbursal','READ',0),(277,'report','READ_Loans Awaiting Disbursal Summary','Loans Awaiting Disbursal Summary','READ',0),(278,'report','READ_Loans Awaiting Disbursal Summary by Month','Loans Awaiting Disbursal Summary by Month','READ',0),(279,'report','READ_Loans Pending Approval','Loans Pending Approval','READ',0),(280,'report','READ_Obligation Met Loans Details','Obligation Met Loans Details','READ',0),(281,'report','READ_Obligation Met Loans Summary','Obligation Met Loans Summary','READ',0),(282,'report','READ_Portfolio at Risk','Portfolio at Risk','READ',0),(283,'report','READ_Portfolio at Risk by Branch','Portfolio at Risk by Branch','READ',0),(284,'report','READ_Rescheduled Loans','Rescheduled Loans','READ',0),(285,'report','READ_Trial Balance','Trial Balance','READ',0),(286,'report','READ_Written-Off Loans','Written-Off Loans','READ',0);
+/*!40000 ALTER TABLE `m_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_portfolio_command_source`
+--
+
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `subresource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_portfolio_command_source`
+--
+
+LOCK TABLES `m_portfolio_command_source` WRITE;
+/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_portfolio_command_source` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan`
+--
+
+DROP TABLE IF EXISTS `m_product_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan`
+--
+
+LOCK TABLES `m_product_loan` WRITE;
+/*!40000 ALTER TABLE `m_product_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan_charge`
+--
+
+LOCK TABLES `m_product_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_product_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role`
+--
+
+DROP TABLE IF EXISTS `m_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role`
+--
+
+LOCK TABLES `m_role` WRITE;
+/*!40000 ALTER TABLE `m_role` DISABLE KEYS */;
+INSERT INTO `m_role` VALUES (1,'Super user','This role provides all application permissions.');
+/*!40000 ALTER TABLE `m_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role_permission`
+--
+
+DROP TABLE IF EXISTS `m_role_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role_permission`
+--
+
+LOCK TABLES `m_role_permission` WRITE;
+/*!40000 ALTER TABLE `m_role_permission` DISABLE KEYS */;
+INSERT INTO `m_role_permission` VALUES (1,1);
+/*!40000 ALTER TABLE `m_role_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account`
+--
+
+DROP TABLE IF EXISTS `m_savings_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `status_enum` smallint(5) NOT NULL DEFAULT '300',
+  `activation_date` date DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `lockedin_until_date_derived` date DEFAULT NULL,
+  `total_deposits_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
+  `account_balance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sa_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `sa_externalid_UNIQUE` (`external_id`),
+  KEY `FKSA00000000000001` (`client_id`),
+  KEY `FKSA00000000000002` (`group_id`),
+  KEY `FKSA00000000000003` (`product_id`),
+  CONSTRAINT `FKSA00000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA00000000000002` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSA00000000000003` FOREIGN KEY (`product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account`
+--
+
+LOCK TABLES `m_savings_account` WRITE;
+/*!40000 ALTER TABLE `m_savings_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account_transaction`
+--
+
+DROP TABLE IF EXISTS `m_savings_account_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account_transaction`
+--
+
+LOCK TABLES `m_savings_account_transaction` WRITE;
+/*!40000 ALTER TABLE `m_savings_account_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_product`
+--
+
+DROP TABLE IF EXISTS `m_savings_product`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_product` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sp_unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_product`
+--
+
+LOCK TABLES `m_savings_product` WRITE;
+/*!40000 ALTER TABLE `m_savings_product` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_product` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_staff`
+--
+
+DROP TABLE IF EXISTS `m_staff`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_staff`
+--
+
+LOCK TABLES `m_staff` WRITE;
+/*!40000 ALTER TABLE `m_staff` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_staff` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `r_enum_value`
+--
+
+DROP TABLE IF EXISTS `r_enum_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `r_enum_value`
+--
+
+LOCK TABLES `r_enum_value` WRITE;
+/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
+INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
+/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `ref_loan_transaction_processing_strategy`
+--
+
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `ref_loan_transaction_processing_strategy`
+--
+
+LOCK TABLES `ref_loan_transaction_processing_strategy` WRITE;
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` DISABLE KEYS */;
+INSERT INTO `ref_loan_transaction_processing_strategy` VALUES (1,'mifos-standard-strategy','Mifos style',NULL,NULL,NULL,NULL),(2,'heavensfamily-strategy','Heavensfamily',NULL,NULL,NULL,NULL),(3,'creocore-strategy','Creocore',NULL,NULL,NULL,NULL),(4,'rbi-india-strategy','RBI (India)',NULL,NULL,NULL,NULL);
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `rpt_sequence`
+--
+
+DROP TABLE IF EXISTS `rpt_sequence`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `rpt_sequence`
+--
+
+LOCK TABLES `rpt_sequence` WRITE;
+/*!40000 ALTER TABLE `rpt_sequence` DISABLE KEYS */;
+/*!40000 ALTER TABLE `rpt_sequence` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_parameter` (
+  `parameter_id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_parameter_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`parameter_id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`),
+  KEY `fk_stretchy_parameter_0001_idx` (`parent_parameter_id`),
+  CONSTRAINT `fk_stretchy_parameter_0001` FOREIGN KEY (`parent_parameter_id`) REFERENCES `stretchy_parameter` (`parameter_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_parameter`
+--
+
+LOCK TABLES `stretchy_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_parameter` VALUES (1,'startDateSelect','startDate','startDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(2,'endDateSelect','endDate','endDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(3,'obligDateTypeSelect','obligDateType','obligDateType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Closed\" as `name` union all\r\nselect 2, \"Disbursal\" ) x\r\norder by x.`id`',NULL),(5,'OfficeIdSelectOne','officeId','Office','select','number','0',NULL,'Y',NULL,'select id, \r\nconcat(substring(\"........................................\", 1, \r\n   \n\n((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat\n\n(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy',NULL),(6,'loanOfficerIdSelectAll','loanOfficerId','Loan Officer','select','number','0',NULL,NULL,'Y','(select lo.id, lo.display_name as `Name` \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\njoin m_staff lo on lo.office_id = ounder.id\r\nwhere lo.is_loan_officer = true\r\nand o.id = ${officeId})\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',5),(10,'currencyIdSelectAll','currencyId','Currency','select','number','0',NULL,NULL,'Y','select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`',NULL),(20,'fundIdSelectAll','fundId','Fund','select','number','0',NULL,NULL,'Y','(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',NULL),(25,'loanProductIdSelectAll','loanProductId','Product','select','number','0',NULL,NULL,'Y','select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere p.currency_code = \'${currencyId}\'\r\norder by 2',10),(26,'loanPurposeIdSelectAll','loanPurposeId','Loan Purpose','select','number','0',NULL,NULL,'Y','select -10 as id, \'-\' as code_value\r\nunion all\r\nselect * from (select v.id, v.code_value\r\nfrom m_code c\r\njoin m_code_value v on v.code_id = c.id\r\nwhere c.code_name = \"loanPurpose\"\r\norder by v.order_position)  x',NULL),(100,'parTypeSelect','parType','parType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Principal Only\" as `name` union all\r\nselect 2, \"Principal + Interest\" union all\r\nselect 3, \"Principal + Interest + Fees\" union all\r\nselect 4, \"Principal + Interest + Fees + Penalties\") x\r\norder by x.`id`',NULL),(1001,'FullReportList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\r\n  \n\nrp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on rp.report_id = r.report_id\r\n  \n\nleft join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \n\n\r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on rp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where \n\nur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by \n\nr.report_category, r.report_name, rp.parameter_id',NULL),(1002,'FullParameterList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r\nsp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r\nfrom stretchy_parameter sp\r\nleft join stretchy_parameter spp on spp.parameter_id = sp.parent_parameter_id\r\nwhere sp.special is null\r\nand exists \r\n	(select \'f\' \r\n	from stretchy_report sr\r\n	join stretchy_report_parameter srp on srp.report_id = sr.report_id\r\n	where sr.report_name in(${reportListing})\r\n	and srp.parameter_id = sp.parameter_id\r\n	)\r\norder by sp.parameter_id',NULL),(1003,'reportCategoryList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, \n\nr.report_category,\r\n  rp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on \n\nrp.report_id = r.report_id\r\n  left join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.report_category = \'${reportCategory}\'\r\n  and \n\nr.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on \n\nrp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where ur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \n\n\'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by r.report_category, r.report_name, rp.parameter_id',NULL);
+/*!40000 ALTER TABLE `stretchy_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report`
+--
+
+DROP TABLE IF EXISTS `stretchy_report`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report` (
+  `report_id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`report_id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report`
+--
+
+LOCK TABLES `stretchy_report` WRITE;
+/*!40000 ALTER TABLE `stretchy_report` DISABLE KEYS */;
+INSERT INTO `stretchy_report` VALUES (1,'Client Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n c.account_no as \"Client Account No.\",  \r\nc.display_name as \"Name\",  \n\nc.joined_date as \"Joined\", c.external_id as \"External Id\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand \n\nounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\nwhere o.id = ${officeId}\r\nand c.is_deleted=0\r\n\n\norder by ounder.hierarchy, c.account_no','Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t \n\nhave that client browser/memory impact).',1,1),(2,'Client Loans Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client \n\nAccount No.\", \r\nc.display_name as \"Name\", \r\nlo.display_name as \"Loan Officer\", l.account_no as \"Loan Account No.\", l.external_id as \"External Id\", \r\n\n\np.name as Loan, st.enum_message_property as \"Status\",  \r\nf.`name` as Fund, purp.code_value as \"Loan Purpose\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount,\n\n\r\nl.arrearstolerance_amount as \"Arrears Tolerance Amount\",\r\nl.number_of_repayments as \"Expected No. Repayments\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\nl.nominal_interest_rate_per_period as \"Nominal Interest Rate Per Period\",\r\n\r\nipf.enum_message_property as \"Interest Rate Frequency\n\n\",\r\nim.enum_message_property as \"Interest Method\",\r\nicp.enum_message_property as \"Interest Calculated in Period\",\r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.repay_every as \"Repayment Frequency\",\r\nrf.enum_message_property as \"Repayment Frequency Period\",\n\n\r\nam.enum_message_property as \"Amortization\",\r\n\r\nl.total_charges_due_at_disbursement_derived as \"Total Charges Due At Disbursement\",\r\n\r\ndate( \n\nl.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As \"Expected Disbursal\",\r\ndate(l.expected_firstrepaymenton_date) as \n\n\"Expected First Repayment\", date(l.interest_calculated_from_date) as \"Interest Calculated From\" ,\r\ndate(l.disbursedon_date) as Disbursed, date\n\n(l.expected_maturedon_date) \"Expected Maturity\",\r\ndate(l.maturedon_date) as \"Matured On\", date(l.closedon_date) as Closed,\r\ndate(l.rejectedon_date) as \n\nRejected, date(l.rescheduledon_date) as Rescheduled, \r\ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) \"Written Off\"\r\nfrom m_office o \r\njoin \n\nm_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on \n\nc.office_id = ounder.id\r\nleft join m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_product_loan p on p.id = \n\nl.product_id\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join r_enum_value st on st.enum_name = \"loan_status_id\" and st.enum_id = l.loan_status_id\r\nleft join \n\nr_enum_value ipf on ipf.enum_name = \"interest_period_frequency_enum\" and ipf.enum_id = l.interest_period_frequency_enum\r\nleft join r_enum_value im on im.enum_name \n\n= \"interest_method_enum\" and im.enum_id = l.interest_method_enum\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = \n\nl.term_period_frequency_enum\r\nleft join r_enum_value icp on icp.enum_name = \"interest_calculated_in_period_enum\" and icp.enum_id = \n\nl.interest_calculated_in_period_enum\r\nleft join r_enum_value rf on rf.enum_name = \"repayment_period_frequency_enum\" and rf.enum_id = \n\nl.repayment_period_frequency_enum\r\nleft join r_enum_value am on am.enum_name = \"amortization_method_enum\" and am.enum_id = l.amortization_method_enum\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\n\r\nleft \n\njoin m_currency cur on cur.code = l.currency_code\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand \n\n(l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \n\n\"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\norder by ounder.hierarchy, 2 , l.id','Individual Client Report\r\n\r\nPretty \n\nwide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).',1,1),(5,'Loans Awaiting Disbursal','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No\", c.display_name as \"Name\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund, ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\",\r\ndate(l.approvedon_date) \"Approved\",\r\ndatediff(l.expected_disbursedon_date, curdate()) as \"Days to Disbursal\",\r\ndate(l.expected_disbursedon_date) \"Expected Disbursal\",\r\npurp.code_value as \"Loan Purpose\",\r\n lo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, datediff(l.expected_disbursedon_date, curdate()),  c.account_no','Individual Client Report',1,1),(6,'Loans Awaiting Disbursal Summary','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`','Individual Client Report',1,1),(7,'Loans Awaiting Disbursal Summary by Month','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as \"Year\", \r\nmonthname(l.expected_disbursedon_date) as \"Month\",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)','Individual Client Report',1,1),(8,'Loans Pending Approval','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client Name\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  pl.`name` as \"Product\", \r\nl.account_no as \"Loan Account No.\", \r\nl.principal_amount as \"Loan Amount\", \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\ndatediff(curdate(), l.submittedon_date) \"Days Pending Approval\", \r\npurp.code_value as \"Loan Purpose\",\r\nlo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 100 /*Submitted and awaiting approval */\r\norder by ounder.hierarchy, l.submittedon_date,  l.account_no','Individual Client Report',1,1),(11,'Active Loans - Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. loans_in_arrears_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(if(laa.loan_id is not null,  l.id, null)  )) as loans_in_arrears_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(laa.principal_overdue_derived) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(laa.interest_overdue_derived) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(laa.fee_charges_overdue_derived) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(laa.penalty_charges_overdue_derived) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(12,'Active Loans - Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\npenalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(13,'Obligation Met Loans Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client\",\r\nl.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.total_repayment_derived  as \"Total Repaid\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", \r\ndate(l.closedon_date) as \"Closed\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(14,'Obligation Met Loans Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as \"No. of Clients\",\r\ncount(distinct(l.id)) as \"No. of Loans\",\r\nsum(l.principal_amount) as \"Total Loan Amount\", \r\nsum(l.principal_repaid_derived) as \"Total Principal Repaid\",\r\nsum(l.interest_repaid_derived) as \"Total Interest Repaid\",\r\nsum(l.fee_charges_repaid_derived) as \"Total Fees Repaid\",\r\nsum(l.penalty_charges_repaid_derived) as \"Total Penalties Repaid\",\r\nsum(l.interest_waived_derived) as \"Total Interest Waived\",\r\nsum(l.fee_charges_waived_derived) as \"Total Fees Waived\",\r\nsum(l.penalty_charges_waived_derived) as \"Total Penalties Waived\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code','Individual Client \n\nReport',1,1),(15,'Portfolio at Risk','Table',NULL,'Loan','select x.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from \r\n(select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code) x','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(16,'Portfolio at Risk by Branch','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\",\r\nx.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select  ounder.id as \"branch\", ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\n\r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(20,'Funds Disbursed Between Dates Summary','Table',NULL,'Fund','select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, \r\nround(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(21,'Funds Disbursed Between Dates Summary by Office','Table',NULL,'Fund','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n \n\nifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\n\n\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c \n\non c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\n\n\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand \n\n(l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, \n\nl.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(48,'Balance Sheet','Pentaho',NULL,'Accounting',NULL,'Balance Sheet',1,1),(49,'Income Statement','Pentaho',NULL,'Accounting',NULL,'Profit and Loss Statement',1,1),(50,'Trial Balance','Pentaho',NULL,'Accounting',NULL,'Trial Balance Report',1,1),(51,'Written-Off Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Written-Off Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Written-Off Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Written-Off Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Written-Off Penalties\',\r\nn.note AS \'Reason For Write-Off\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 6 /*write-off */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=601\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\") \r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Written Off Loans',1,1),(52,'Aging Detail','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as \"Client Account No.\",\r\n 	mc.display_name AS \"Client Name\",\r\n 	ml.account_no AS \"Account Number\",\r\n 	ml.principal_amount AS \"Loan Amount\",\r\n ml.principal_disbursed_derived AS \"Original Principal\",\r\n ml.interest_charged_derived AS \"Original Interest\",\r\n ml.principal_repaid_derived AS \"Principal Paid\",\r\n ml.interest_repaid_derived AS \"Interest Paid\",\r\n laa.principal_overdue_derived AS \"Principal Overdue\",\r\n laa.interest_overdue_derived AS \"Interest Overdue\",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as \"Days in Arrears\",\r\n\r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS \"Weeks In Arrears Band\",\r\n\r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n				 \'> 360\'))))) AS \"Days in Arrears Band\"\r\n\r\n	FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n	        AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n	    INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n	    INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n	WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Loan arrears aging (Weeks)',1,1),(53,'Aging Summary (Arrears in Weeks)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Weeks In Arrears (Up To)\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'1\',2 UNION\r\n		SELECT \'2\',3 UNION\r\n		SELECT \'3\',4 UNION\r\n		SELECT \'4\',5 UNION\r\n		SELECT \'5\',6 UNION\r\n		SELECT \'6\',7 UNION\r\n		SELECT \'7\',8 UNION\r\n		SELECT \'8\',9 UNION\r\n		SELECT \'9\',10 UNION\r\n		SELECT \'10\',11 UNION\r\n		SELECT \'11\',12 UNION\r\n		SELECT \'12\',13 UNION\r\n		SELECT \'12+\',14) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<8, \'1\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<15, \'2\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<22, \'3\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<29, \'4\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<36, \'5\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<43, \'6\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<50, \'7\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<57, \'8\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<64, \'9\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<71, \'10\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<78, \'11\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<85, \'12\',\r\n				 \'12+\'))))))))))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(54,'Rescheduled Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Rescheduled Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Rescheduled Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Rescheduled Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Rescheduled Penalties\',\r\nn.note AS \'Reason For Rescheduling\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 7 /*marked for rescheduling */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=602\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Rescheduled Loans.  The ability to reschedule (or mark that you have rescheduled the loan elsewhere) is a legacy of the older Mifos product.  Needed for migration.',1,1),(55,'Active Loans Passed Final Maturity','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(56,'Active Loans Passed Final Maturity Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(57,'Active Loans in last installment','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(lastInstallment.`hierarchy`) - LENGTH(REPLACE(lastInstallment.`hierarchy`, \'.\', \'\')) - 1))), lastInstallment.branch) as \"Office/Branch\",\r\nlastInstallment.Currency,\r\nlastInstallment.`Loan Officer`, \r\nlastInstallment.`Client Account No`, lastInstallment.`Client`, \r\nlastInstallment.`Loan Account No`, lastInstallment.`Product`, \r\nlastInstallment.`Fund`,  lastInstallment.`Loan Amount`, \r\nlastInstallment.`Annual Nominal Interest Rate`, \r\nlastInstallment.`Disbursed`, lastInstallment.`Expected Matured On` ,\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\norder by lastInstallment.hierarchy, lastInstallment.Currency, lastInstallment.`Client Account No`, lastInstallment.`Loan Account No`','Individual Client \n\nReport',1,1),(58,'Active Loans in last installment Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select lastInstallment.branchId as branchId,\r\nlastInstallment.Currency,\r\ncount(distinct(lastInstallment.clientId)) as client_count, \r\ncount(distinct(lastInstallment.loanId)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id as branchId, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.id as clientId, c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\ngroup by lastInstallment.branchId) x on x.branchId = mo.id\r\norder by mo.hierarchy, x.Currency','Individual Client \n\nReport',1,1),(59,'Active Loans by Disbursal Period','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No\", c.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Principal Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\n\r\nl.total_expected_repayment_derived as \"Total Loan (P+I+F+Pen)\",\r\nl.total_repayment_derived as \"Total Repaid (P+I+F+Pen)\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(61,'Aging Summary (Arrears in Months)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Days In Arrears\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'0 - 30\',2 UNION\r\n		SELECT \'30 - 60\',3 UNION\r\n		SELECT \'60 - 90\',4 UNION\r\n		SELECT \'90 - 180\',5 UNION\r\n		SELECT \'180 - 360\',6 UNION\r\n		SELECT \'> 360\',7 ) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<361, \'180 - 360\', \r\n				 \'> 360\')))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(91,'Loan Account Schedule','Pentaho',NULL,'Loan',NULL,NULL,1,0),(92,'Branch Expected Cash Flow','Pentaho',NULL,'Loan',NULL,NULL,1,1),(93,'Expected Payments By Date - Basic','Table',NULL,'Loan','SELECT \r\n      ounder.name \'Office\', \r\n      IFNULL(ms.display_name,\'-\') \'Loan Officer\',\r\n	  mc.account_no \'Client Account Number\',\r\n	  mc.display_name \'Name\',\r\n	  mp.name \'Product\',\r\n	  ml.account_no \'Loan Account Number\',\r\n	  mr.duedate \'Due Date\',\r\n	  mr.installment \'Installment\',\r\n	  cu.display_symbol \'Currency\',\r\n	  mr.principal_amount- IFNULL(mr.principal_completed_derived,0) \'Principal Due\',\r\n	  mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0) \'Interest Due\', \r\n	  IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0) \'Fees Due\', \r\n	  IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0) \'Penalty Due\',\r\n      (mr.principal_amount- IFNULL(mr.principal_completed_derived,0)) +\r\n       (mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0)) + \r\n       (IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0)) + \r\n       (IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0)) \'Total Due\', \r\n     mlaa.total_overdue_derived \'Total Overdue\'\r\n										 \r\n FROM m_office mo\r\n  JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\n  \r\n  AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n	\r\n  LEFT JOIN m_client mc ON mc.office_id=ounder.id\r\n  LEFT JOIN m_loan ml ON ml.client_id=mc.id AND ml.loan_status_id=300\r\n  LEFT JOIN m_loan_arrears_aging mlaa ON mlaa.loan_id=ml.id\r\n  LEFT JOIN m_loan_repayment_schedule mr ON mr.loan_id=ml.id AND mr.completed_derived=0\r\n  LEFT JOIN m_product_loan mp ON mp.id=ml.product_id\r\n  LEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\n  LEFT JOIN m_currency cu ON cu.code=ml.currency_code\r\n WHERE mo.id=${officeId}\r\n AND (IFNULL(ml.loan_officer_id, -10) = \"${loanOfficerId}\" OR \"-1\" = \"${loanOfficerId}\")\r\n AND mr.duedate BETWEEN \'${startDate}\' AND \'${endDate}\'\r\n ORDER BY ounder.id,mr.duedate,ml.account_no','Test',1,1),(94,'Expected Payments By Date - Formatted','Pentaho',NULL,'Loan',NULL,NULL,1,1);
+/*!40000 ALTER TABLE `stretchy_report` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report_parameter` (
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`report_id`,`parameter_id`),
+  UNIQUE KEY `report_id_name_UNIQUE` (`report_id`,`report_parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report_parameter`
+--
+
+LOCK TABLES `stretchy_report_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_report_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_report_parameter` VALUES (1,5,NULL),(2,5,NULL),(2,6,NULL),(2,10,NULL),(2,20,NULL),(2,25,NULL),(2,26,NULL),(5,5,NULL),(5,6,NULL),(5,10,NULL),(5,20,NULL),(5,25,NULL),(5,26,NULL),(6,5,NULL),(6,6,NULL),(6,10,NULL),(6,20,NULL),(6,25,NULL),(6,26,NULL),(7,5,NULL),(7,6,NULL),(7,10,NULL),(7,20,NULL),(7,25,NULL),(7,26,NULL),(8,5,NULL),(8,6,NULL),(8,10,NULL),(8,25,NULL),(8,26,NULL),(11,5,NULL),(11,6,NULL),(11,10,NULL),(11,20,NULL),(11,25,NULL),(11,26,NULL),(11,100,NULL),(12,5,NULL),(12,6,NULL),(12,10,NULL),(12,20,NULL),(12,25,NULL),(12,26,NULL),(13,1,NULL),(13,2,NULL),(13,3,NULL),(13,5,NULL),(13,6,NULL),(13,10,NULL),(13,20,NULL),(13,25,NULL),(13,26,NULL),(14,1,NULL),(14,2,NULL),(14,3,NULL),(14,5,NULL),(14,6,NULL),(14,10,NULL),(14,20,NULL),(14,25,NULL),(14,26,NULL),(15,5,NULL),(15,6,NULL),(15,10,NULL),(15,20,NULL),(15,25,NULL),(15,26,NULL),(15,100,NULL),(16,5,NULL),(16,6,NULL),(16,10,NULL),(16,20,NULL),(16,25,NULL),(16,26,NULL),(16,100,NULL),(20,1,NULL),(20,2,NULL),(20,10,NULL),(20,20,NULL),(21,1,NULL),(21,2,NULL),(21,5,NULL),(21,10,NULL),(21,20,NULL),(48,5,'branch'),(48,2,'date'),(49,5,'branch'),(49,1,'fromDate'),(49,2,'toDate'),(50,5,'branch'),(50,1,'fromDate'),(50,2,'toDate'),(51,1,NULL),(51,2,NULL),(51,5,NULL),(51,10,NULL),(51,25,NULL),(52,5,NULL),(53,5,NULL),(53,10,NULL),(54,1,NULL),(54,2,NULL),(54,5,NULL),(54,10,NULL),(54,25,NULL),(55,5,NULL),(55,6,NULL),(55,10,NULL),(55,20,NULL),(55,25,NULL),(55,26,NULL),(56,5,NULL),(56,6,NULL),(56,10,NULL),(56,20,NULL),(56,25,NULL),(56,26,NULL),(56,100,NULL),(57,5,NULL),(57,6,NULL),(57,10,NULL),(57,20,NULL),(57,25,NULL),(57,26,NULL),(58,5,NULL),(58,6,NULL),(58,10,NULL),(58,20,NULL),(58,25,NULL),(58,26,NULL),(58,100,NULL),(59,1,NULL),(59,2,NULL),(59,5,NULL),(59,6,NULL),(59,10,NULL),(59,20,NULL),(59,25,NULL),(59,26,NULL),(61,5,NULL),(61,10,NULL),(92,1,'fromDate'),(92,5,'selectOffice'),(92,2,'toDate'),(93,1,NULL),(93,2,NULL),(93,5,NULL),(93,6,NULL),(94,2,'endDate'),(94,6,'loanOfficerId'),(94,5,'officeId'),(94,1,'startDate');
+/*!40000 ALTER TABLE `stretchy_report_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `x_registered_table`
+--
+
+DROP TABLE IF EXISTS `x_registered_table`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `x_registered_table`
+--
+
+LOCK TABLES `x_registered_table` WRITE;
+/*!40000 ALTER TABLE `x_registered_table` DISABLE KEYS */;
+/*!40000 ALTER TABLE `x_registered_table` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-03-25 17:46:07
diff --git a/fineract-db/multi-tenant-demo-backups/bk_mifostenant_default.sql b/fineract-db/multi-tenant-demo-backups/bk_mifostenant_default.sql
new file mode 100644
index 0000000..306570a
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/bk_mifostenant_default.sql
@@ -0,0 +1,1935 @@
+-- MySQL dump 10.13  Distrib 5.1.60, for Win32 (ia32)
+--
+-- Host: localhost    Database: mifostenant-default
+-- ------------------------------------------------------
+-- Server version	5.1.60-community
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `acc_gl_account`
+--
+
+DROP TABLE IF EXISTS `acc_gl_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=98 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_account`
+--
+
+LOCK TABLES `acc_gl_account` WRITE;
+/*!40000 ALTER TABLE `acc_gl_account` DISABLE KEYS */;
+INSERT INTO `acc_gl_account` VALUES (1,'Petty Cash Balances',NULL,'11100',0,1,2,1,NULL),(2,'Cash in Valut 1',NULL,'11101',0,1,1,1,NULL),(3,'Bank Balances',NULL,'11200',0,1,2,1,NULL),(4,'Centenary Opening Account',NULL,'11201',0,1,1,1,NULL),(5,'Centenary Expense Account',NULL,'11202',0,1,1,1,NULL),(6,'Centenary USD Account',NULL,'11203',0,1,1,1,NULL),(7,'Loans and Advances',NULL,'13100',0,1,2,1,NULL),(8,'Loans to Clients',NULL,'13101',0,1,1,1,NULL),(9,'Outstanding Interest',NULL,'13102',0,1,1,1,NULL),(10,'Outstanding Late Payment Interest',NULL,'13103',0,1,1,1,NULL),(11,'Outstanding Bank Fees to be collected',NULL,'13104',0,1,1,1,NULL),(12,'WriteOff Accounts',NULL,'13200',0,1,2,1,NULL),(13,'Write-offs (use for funds coming in)',NULL,'13201',0,1,1,1,NULL),(14,'Write-offs outstanding principal',NULL,'13202',0,1,1,1,NULL),(15,'Write-offs outstanding interest',NULL,'13203',0,1,1,1,NULL),(16,'Write-offs collected bank fees',NULL,'13204',0,1,1,1,NULL),(17,'Write-offs hardware/furniture',NULL,'13205',0,1,1,1,NULL),(18,'Fixed Assets',NULL,'14100',0,1,2,1,NULL),(19,'Office Equipment',NULL,'14101',0,1,1,1,NULL),(20,'Suspense Items (unidentified deposits)',NULL,'15000',0,1,2,1,NULL),(21,'Assets',NULL,'10000',0,1,2,1,NULL),(22,'Liabilities',NULL,'20000',0,1,2,2,NULL),(23,'Shares Account',NULL,'26100',0,1,2,2,NULL),(24,'Shares Captial',NULL,'26101',0,1,1,2,NULL),(25,'Donated Equity',NULL,'26300',0,1,2,2,NULL),(26,'Donated Equity Ameropa Foundation',NULL,'26301',0,1,1,2,NULL),(27,'Donated Equity e.h',NULL,'26302',0,1,1,2,NULL),(28,'Overpaid Amounts',NULL,'27000',0,1,2,2,NULL),(29,'Loss Provision',NULL,'28000',0,1,2,2,NULL),(30,'Provision Outstanding Principal',NULL,'28001',0,1,1,2,NULL),(31,'Provision Oustanding Interest',NULL,'28002',0,1,1,2,NULL),(32,'Income',NULL,'30000',0,1,2,4,NULL),(33,'Interest Income from Loans',NULL,'31100',0,1,2,4,NULL),(34,'Interest on Loans',NULL,'31101',0,1,1,4,NULL),(35,'Late Payment Interest',NULL,'31102',0,1,1,4,NULL),(36,'Income from Micro credit & Lending Activities',NULL,'31300',0,1,2,4,NULL),(37,'Collected Bank Fees Receivable',NULL,'6201',0,1,1,4,NULL),(38,'Deposits from Loans Write Off',NULL,'31400',0,1,2,4,NULL),(39,'Expenditure',NULL,'40000',0,1,2,5,NULL),(40,'Office Expenditure Account',NULL,'42100',0,1,2,5,NULL),(41,'Water Charges',NULL,'42102',0,1,1,5,NULL),(42,'Electricity Charges',NULL,'42103',0,1,1,5,NULL),(43,'Printing and Stationary',NULL,'42105',0,1,1,5,NULL),(44,'Office Rent',NULL,'42107',0,1,1,5,NULL),(45,'Marketing Expense',NULL,'42109',0,1,1,5,NULL),(46,'Office utilities',NULL,'42112',0,1,1,5,'(supplies, toiletries, kitchen)'),(47,'Furniture',NULL,'42113',0,1,1,5,NULL),(48,'CEDA Meeting Expense',NULL,'42114',0,1,1,5,NULL),(49,'Employee Personal Expsense Account',NULL,'42200',0,1,2,5,NULL),(50,'Salary Alice',NULL,'42201',0,1,1,5,NULL),(51,'Salary Irene',NULL,'42202',0,1,1,5,NULL),(52,'Salary Richard',NULL,'42203',0,1,1,5,NULL),(53,'Salary Loan Officer TBA',NULL,'42204',0,1,1,5,NULL),(54,'Medical Insurance Alice & Family',NULL,'42205',0,1,1,5,NULL),(55,'Medical Insurance Irene',NULL,'42206',0,1,1,5,NULL),(56,'Medical Insurance Richard',NULL,'42207',0,1,1,5,NULL),(57,'Medical Insurance Loan Officer TBA',NULL,'42208',0,1,1,5,NULL),(58,'PAYE all employees',NULL,'42209',0,1,1,5,NULL),(59,'NSSF all employees',NULL,'42210',0,1,1,5,NULL),(60,'Lunch Allowances all employees',NULL,'42211',0,1,1,5,NULL),(61,'IT software and maintenance',NULL,'42300',0,1,2,5,NULL),(62,'Mifos maintenance contract 12 months',NULL,'42301',0,1,1,5,NULL),(63,'VPS Contract 12 months',NULL,'42302',0,1,1,5,NULL),(64,'Bulk SMS Service',NULL,'42303',0,1,1,5,NULL),(65,'Support Accounting Software',NULL,'42304',0,1,1,5,NULL),(66,'Mifos Instance Setup',NULL,'42305',0,1,1,5,NULL),(67,'Misc support expense',NULL,'42306',0,1,1,5,NULL),(68,'Warid Mobile Line',NULL,'42307',0,1,1,5,NULL),(69,'Landline',NULL,'42308',0,1,1,5,NULL),(70,'Modem Alice',NULL,'42309',0,1,1,5,NULL),(71,'Modem Irene',NULL,'42310',0,1,1,5,NULL),(72,'Modem Richard',NULL,'42311',0,1,1,5,NULL),(73,'Repairs',NULL,'42312',0,1,1,5,NULL),(74,'Airtime Expenses',NULL,'42400',0,1,2,5,NULL),(75,'Airtime Alice',NULL,'42401',0,1,1,5,NULL),(76,'Airtime Richard',NULL,'42402',0,1,1,5,NULL),(77,'Airtime Loan Office TBA',NULL,'42403',0,1,1,5,NULL),(78,'Special Airtime Alice',NULL,'42404',0,1,1,5,NULL),(79,'Transportation',NULL,'42500',0,1,2,5,NULL),(80,'Flat monthly transportation cost',NULL,'42501',0,1,1,5,NULL),(81,'Faciliation cost for Richard',NULL,'42502',0,1,1,5,NULL),(82,'Faciliation cost for Loan Officer TBA',NULL,'42503',0,1,1,5,NULL),(83,'Consultancy Expenses',NULL,'42600',0,1,2,5,NULL),(84,'Audit Fees',NULL,'42601',0,1,1,5,NULL),(85,'Legal Fees',NULL,'42602',0,1,1,5,NULL),(86,'Miscellaneous Expenses Account',NULL,'42700',0,1,2,5,NULL),(87,'MFI License',NULL,'42703',0,1,1,5,NULL),(88,'Sundy Expenses',NULL,'42704',0,1,1,5,NULL),(89,'Bank Fees',NULL,'42800',0,1,2,5,NULL),(90,'Bank Charges Operating Account',NULL,'42801',0,1,1,5,NULL),(91,'Bank Charges Expense Account',NULL,'42802',0,1,1,5,NULL),(92,'E.H Account',NULL,'42900',0,1,2,5,NULL),(93,'Airtime',NULL,'42901',0,1,1,5,NULL),(94,'Modem',NULL,'42902',0,1,1,5,NULL),(95,'Meals',NULL,'42903',0,1,1,5,NULL),(96,'Transportation',NULL,'42904',0,1,1,5,NULL),(97,'Miscellaneous',NULL,'42905',0,1,1,5,NULL);
+/*!40000 ALTER TABLE `acc_gl_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_closure`
+--
+
+DROP TABLE IF EXISTS `acc_gl_closure`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_closure`
+--
+
+LOCK TABLES `acc_gl_closure` WRITE;
+/*!40000 ALTER TABLE `acc_gl_closure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_closure` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_journal_entry`
+--
+
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_entry` tinyint(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type_enum` smallint(5) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_journal_entry`
+--
+
+LOCK TABLES `acc_gl_journal_entry` WRITE;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_product_mapping`
+--
+
+DROP TABLE IF EXISTS `acc_product_mapping`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_product_mapping`
+--
+
+LOCK TABLES `acc_product_mapping` WRITE;
+/*!40000 ALTER TABLE `acc_product_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_product_mapping` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `c_configuration`
+--
+
+DROP TABLE IF EXISTS `c_configuration`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `c_configuration`
+--
+
+LOCK TABLES `c_configuration` WRITE;
+/*!40000 ALTER TABLE `c_configuration` DISABLE KEYS */;
+INSERT INTO `c_configuration` VALUES (1,'maker-checker',0);
+/*!40000 ALTER TABLE `c_configuration` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `client additional data`
+--
+
+DROP TABLE IF EXISTS `client additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `client additional data` (
+  `client_id` bigint(20) NOT NULL,
+  `Gender_cd` int(11) NOT NULL,
+  `Date of Birth` date NOT NULL,
+  `Home address` text NOT NULL,
+  `Telephone number` varchar(20) NOT NULL,
+  `Telephone number (2nd)` varchar(20) NOT NULL,
+  `Email address` varchar(50) NOT NULL,
+  `EducationLevel_cd` int(11) NOT NULL,
+  `MaritalStatus_cd` int(11) NOT NULL,
+  `Number of children` int(11) NOT NULL,
+  `Citizenship` varchar(50) NOT NULL,
+  `PovertyStatus_cd` int(11) NOT NULL,
+  `YesNo_cd_Employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of employment` int(11) DEFAULT NULL,
+  `Employer name` varchar(50) DEFAULT NULL,
+  `Number of years` int(11) DEFAULT NULL,
+  `Monthly salary` decimal(19,6) DEFAULT NULL,
+  `YesNo_cd_Self employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of self-employment` int(11) DEFAULT NULL,
+  `Business address` text,
+  `Number of employees` int(11) DEFAULT NULL,
+  `Monthly salaries paid` decimal(19,6) DEFAULT NULL,
+  `Monthly net income of business activity` decimal(19,6) DEFAULT NULL,
+  `Monthly rent` decimal(19,6) DEFAULT NULL,
+  `Other income generating activities` varchar(100) DEFAULT NULL,
+  `YesNo_cd_Bookkeeping` int(11) DEFAULT NULL,
+  `YesNo_cd_Loans with other institutions` int(11) NOT NULL,
+  `From whom` varchar(100) DEFAULT NULL,
+  `Amount` decimal(19,6) DEFAULT NULL,
+  `Interest rate pa` decimal(19,6) DEFAULT NULL,
+  `Number of people depending on overal income` int(11) NOT NULL,
+  `YesNo_cd_Bank account` int(11) NOT NULL,
+  `YesNo_cd_Business plan provided` int(11) NOT NULL,
+  `YesNo_cd_Access to internet` int(11) DEFAULT NULL,
+  `Introduced by` varchar(100) DEFAULT NULL,
+  `Known to introducer since` varchar(100) NOT NULL,
+  `Last visited by` varchar(100) DEFAULT NULL,
+  `Last visited on` date NOT NULL,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_client_additional_data` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `client additional data`
+--
+
+LOCK TABLES `client additional data` WRITE;
+/*!40000 ALTER TABLE `client additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `client additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_client_details`
+--
+
+DROP TABLE IF EXISTS `extra_client_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_client_details` (
+  `client_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_extra_client_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_client_details`
+--
+
+LOCK TABLES `extra_client_details` WRITE;
+/*!40000 ALTER TABLE `extra_client_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_client_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_family_details`
+--
+
+DROP TABLE IF EXISTS `extra_family_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_family_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `Name` varchar(40) DEFAULT NULL,
+  `Date of Birth` date DEFAULT NULL,
+  `Points Score` int(11) DEFAULT NULL,
+  `Education_cd_Highest` int(11) DEFAULT NULL,
+  `Other Notes` text,
+  PRIMARY KEY (`id`),
+  KEY `FK_Extra Family Details Data_1` (`client_id`),
+  CONSTRAINT `FK_family_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_family_details`
+--
+
+LOCK TABLES `extra_family_details` WRITE;
+/*!40000 ALTER TABLE `extra_family_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_family_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_loan_details`
+--
+
+DROP TABLE IF EXISTS `extra_loan_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_loan_details` (
+  `loan_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_extra_loan_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_loan_details`
+--
+
+LOCK TABLES `extra_loan_details` WRITE;
+/*!40000 ALTER TABLE `extra_loan_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_loan_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `impact measurement`
+--
+
+DROP TABLE IF EXISTS `impact measurement`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `impact measurement` (
+  `loan_id` bigint(20) NOT NULL,
+  `YesNo_cd_RepaidOnSchedule` int(11) NOT NULL,
+  `ReasonNotRepaidOnSchedule` text,
+  `How was Loan Amount Invested` text NOT NULL,
+  `Additional Income Generated` decimal(19,6) NOT NULL,
+  `Additional Income Used For` text NOT NULL,
+  `YesNo_cd_NewJobsCreated` int(11) NOT NULL,
+  `Number of Jobs Created` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_impact measurement` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `impact measurement`
+--
+
+LOCK TABLES `impact measurement` WRITE;
+/*!40000 ALTER TABLE `impact measurement` DISABLE KEYS */;
+/*!40000 ALTER TABLE `impact measurement` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `loan additional data`
+--
+
+DROP TABLE IF EXISTS `loan additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `loan additional data` (
+  `loan_id` bigint(20) NOT NULL,
+  `PurposeOfLoan_cd` int(11) NOT NULL,
+  `CollateralType_cd` int(11) NOT NULL,
+  `Collateral notes` text NOT NULL,
+  `YesNo_cd_Guarantor` int(11) NOT NULL,
+  `Guarantor name` varchar(100) DEFAULT NULL,
+  `Guarantor relation` varchar(100) DEFAULT NULL,
+  `Guarantor address` varchar(100) DEFAULT NULL,
+  `Guarantor telephone number` varchar(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_loan_additional_data` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `loan additional data`
+--
+
+LOCK TABLES `loan additional data` WRITE;
+/*!40000 ALTER TABLE `loan additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `loan additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser`
+--
+
+DROP TABLE IF EXISTS `m_appuser`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser`
+--
+
+LOCK TABLES `m_appuser` WRITE;
+/*!40000 ALTER TABLE `m_appuser` DISABLE KEYS */;
+INSERT INTO `m_appuser` VALUES (1,0,1,'mifos','App','Administrator','5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a','demomfi@mifos.org','\0','','','','');
+/*!40000 ALTER TABLE `m_appuser` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser_role`
+--
+
+DROP TABLE IF EXISTS `m_appuser_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser_role`
+--
+
+LOCK TABLES `m_appuser_role` WRITE;
+/*!40000 ALTER TABLE `m_appuser_role` DISABLE KEYS */;
+INSERT INTO `m_appuser_role` VALUES (1,1);
+/*!40000 ALTER TABLE `m_appuser_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar`
+--
+
+DROP TABLE IF EXISTS `m_calendar`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `title` varchar(50) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar`
+--
+
+LOCK TABLES `m_calendar` WRITE;
+/*!40000 ALTER TABLE `m_calendar` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar_instance`
+--
+
+DROP TABLE IF EXISTS `m_calendar_instance`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar_instance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `entity_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar_instance`
+--
+
+LOCK TABLES `m_calendar_instance` WRITE;
+/*!40000 ALTER TABLE `m_calendar_instance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar_instance` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_charge`
+--
+
+DROP TABLE IF EXISTS `m_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_charge`
+--
+
+LOCK TABLES `m_charge` WRITE;
+/*!40000 ALTER TABLE `m_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client`
+--
+
+DROP TABLE IF EXISTS `m_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `image_key` varchar(500) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client`
+--
+
+LOCK TABLES `m_client` WRITE;
+/*!40000 ALTER TABLE `m_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client_identifier`
+--
+
+DROP TABLE IF EXISTS `m_client_identifier`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client_identifier`
+--
+
+LOCK TABLES `m_client_identifier` WRITE;
+/*!40000 ALTER TABLE `m_client_identifier` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_identifier` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code`
+--
+
+DROP TABLE IF EXISTS `m_code`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code`
+--
+
+LOCK TABLES `m_code` WRITE;
+/*!40000 ALTER TABLE `m_code` DISABLE KEYS */;
+INSERT INTO `m_code` VALUES (1,'Customer Identifier',1),(2,'LoanCollateral',1),(3,'LoanPurpose',1),(4,'Gender',1),(5,'YesNo',1),(6,'Education',1);
+/*!40000 ALTER TABLE `m_code` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code_value`
+--
+
+DROP TABLE IF EXISTS `m_code_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code_value`
+--
+
+LOCK TABLES `m_code_value` WRITE;
+/*!40000 ALTER TABLE `m_code_value` DISABLE KEYS */;
+INSERT INTO `m_code_value` VALUES (1,1,'Passport',1),(2,1,'Id',1),(3,1,'Drivers License',2),(4,1,'Any Other Id Type',3),(5,4,'option.Male',1),(6,4,'option.Female',5),(7,5,'option.Yes',1),(8,5,'option.No',7),(9,6,'Primary',1),(10,6,'Secondary',9),(11,6,'University',10);
+/*!40000 ALTER TABLE `m_code_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_currency`
+--
+
+DROP TABLE IF EXISTS `m_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_currency`
+--
+
+LOCK TABLES `m_currency` WRITE;
+/*!40000 ALTER TABLE `m_currency` DISABLE KEYS */;
+INSERT INTO `m_currency` VALUES (1,'AED',2,NULL,'UAE Dirham','currency.AED'),(2,'AFN',2,NULL,'Afghanistan Afghani','currency.AFN'),(3,'ALL',2,NULL,'Albanian Lek','currency.ALL'),(4,'AMD',2,NULL,'Armenian Dram','currency.AMD'),(5,'ANG',2,NULL,'Netherlands Antillian Guilder','currency.ANG'),(6,'AOA',2,NULL,'Angolan Kwanza','currency.AOA'),(7,'ARS',2,'$','Argentine Peso','currency.ARS'),(8,'AUD',2,'A$','Australian Dollar','currency.AUD'),(9,'AWG',2,NULL,'Aruban Guilder','currency.AWG'),(10,'AZM',2,NULL,'Azerbaijanian Manat','currency.AZM'),(11,'BAM',2,NULL,'Bosnia and Herzegovina Convertible Marks','currency.BAM'),(12,'BBD',2,NULL,'Barbados Dollar','currency.BBD'),(13,'BDT',2,NULL,'Bangladesh Taka','currency.BDT'),(14,'BGN',2,NULL,'Bulgarian Lev','currency.BGN'),(15,'BHD',3,NULL,'Bahraini Dinar','currency.BHD'),(16,'BIF',0,NULL,'Burundi Franc','currency.BIF'),(17,'BMD',2,NULL,'Bermudian Dollar','currency.BMD'),(18,'BND',2,'B$','Brunei Dollar','currency.BND'),(19,'BOB',2,'Bs.','Bolivian Boliviano','currency.BOB'),(20,'BRL',2,'R$','Brazilian Real','currency.BRL'),(21,'BSD',2,NULL,'Bahamian Dollar','currency.BSD'),(22,'BTN',2,NULL,'Bhutan Ngultrum','currency.BTN'),(23,'BWP',2,NULL,'Botswana Pula','currency.BWP'),(24,'BYR',0,NULL,'Belarussian Ruble','currency.BYR'),(25,'BZD',2,'BZ$','Belize Dollar','currency.BZD'),(26,'CAD',2,NULL,'Canadian Dollar','currency.CAD'),(27,'CDF',2,NULL,'Franc Congolais','currency.CDF'),(28,'CHF',2,NULL,'Swiss Franc','currency.CHF'),(29,'CLP',0,'$','Chilean Peso','currency.CLP'),(30,'CNY',2,NULL,'Chinese Yuan Renminbi','currency.CNY'),(31,'COP',2,'$','Colombian Peso','currency.COP'),(32,'CRC',2,'₡','Costa Rican Colon','currency.CRC'),(33,'CSD',2,NULL,'Serbian Dinar','currency.CSD'),(34,'CUP',2,'$MN','Cuban Peso','currency.CUP'),(35,'CVE',2,NULL,'Cape Verde Escudo','currency.CVE'),(36,'CYP',2,NULL,'Cyprus Pound','currency.CYP'),(37,'CZK',2,NULL,'Czech Koruna','currency.CZK'),(38,'DJF',0,NULL,'Djibouti Franc','currency.DJF'),(39,'DKK',2,NULL,'Danish Krone','currency.DKK'),(40,'DOP',2,'RD$','Dominican Peso','currency.DOP'),(41,'DZD',2,NULL,'Algerian Dinar','currency.DZD'),(42,'EEK',2,NULL,'Estonian Kroon','currency.EEK'),(43,'EGP',2,NULL,'Egyptian Pound','currency.EGP'),(44,'ERN',2,NULL,'Eritrea Nafka','currency.ERN'),(45,'ETB',2,NULL,'Ethiopian Birr','currency.ETB'),(46,'EUR',2,'€','Euro','currency.EUR'),(47,'FJD',2,NULL,'Fiji Dollar','currency.FJD'),(48,'FKP',2,NULL,'Falkland Islands Pound','currency.FKP'),(49,'GBP',2,NULL,'Pound Sterling','currency.GBP'),(50,'GEL',2,NULL,'Georgian Lari','currency.GEL'),(51,'GHC',2,'GHc','Ghana Cedi','currency.GHC'),(52,'GIP',2,NULL,'Gibraltar Pound','currency.GIP'),(53,'GMD',2,NULL,'Gambian Dalasi','currency.GMD'),(54,'GNF',0,NULL,'Guinea Franc','currency.GNF'),(55,'GTQ',2,'Q','Guatemala Quetzal','currency.GTQ'),(56,'GYD',2,NULL,'Guyana Dollar','currency.GYD'),(57,'HKD',2,NULL,'Hong Kong Dollar','currency.HKD'),(58,'HNL',2,'L','Honduras Lempira','currency.HNL'),(59,'HRK',2,NULL,'Croatian Kuna','currency.HRK'),(60,'HTG',2,'G','Haiti Gourde','currency.HTG'),(61,'HUF',2,NULL,'Hungarian Forint','currency.HUF'),(62,'IDR',2,NULL,'Indonesian Rupiah','currency.IDR'),(63,'ILS',2,NULL,'New Israeli Shekel','currency.ILS'),(64,'INR',2,'₹','Indian Rupee','currency.INR'),(65,'IQD',3,NULL,'Iraqi Dinar','currency.IQD'),(66,'IRR',2,NULL,'Iranian Rial','currency.IRR'),(67,'ISK',0,NULL,'Iceland Krona','currency.ISK'),(68,'JMD',2,NULL,'Jamaican Dollar','currency.JMD'),(69,'JOD',3,NULL,'Jordanian Dinar','currency.JOD'),(70,'JPY',0,NULL,'Japanese Yen','currency.JPY'),(71,'KES',2,'KSh','Kenyan Shilling','currency.KES'),(72,'KGS',2,NULL,'Kyrgyzstan Som','currency.KGS'),(73,'KHR',2,NULL,'Cambodia Riel','currency.KHR'),(74,'KMF',0,NULL,'Comoro Franc','currency.KMF'),(75,'KPW',2,NULL,'North Korean Won','currency.KPW'),(76,'KRW',0,NULL,'Korean Won','currency.KRW'),(77,'KWD',3,NULL,'Kuwaiti Dinar','currency.KWD'),(78,'KYD',2,NULL,'Cayman Islands Dollar','currency.KYD'),(79,'KZT',2,NULL,'Kazakhstan Tenge','currency.KZT'),(80,'LAK',2,NULL,'Lao Kip','currency.LAK'),(81,'LBP',2,'L£','Lebanese Pound','currency.LBP'),(82,'LKR',2,NULL,'Sri Lanka Rupee','currency.LKR'),(83,'LRD',2,NULL,'Liberian Dollar','currency.LRD'),(84,'LSL',2,NULL,'Lesotho Loti','currency.LSL'),(85,'LTL',2,NULL,'Lithuanian Litas','currency.LTL'),(86,'LVL',2,NULL,'Latvian Lats','currency.LVL'),(87,'LYD',3,NULL,'Libyan Dinar','currency.LYD'),(88,'MAD',2,NULL,'Moroccan Dirham','currency.MAD'),(89,'MDL',2,NULL,'Moldovan Leu','currency.MDL'),(90,'MGA',2,NULL,'Malagasy Ariary','currency.MGA'),(91,'MKD',2,NULL,'Macedonian Denar','currency.MKD'),(92,'MMK',2,'K','Myanmar Kyat','currency.MMK'),(93,'MNT',2,NULL,'Mongolian Tugrik','currency.MNT'),(94,'MOP',2,NULL,'Macau Pataca','currency.MOP'),(95,'MRO',2,NULL,'Mauritania Ouguiya','currency.MRO'),(96,'MTL',2,NULL,'Maltese Lira','currency.MTL'),(97,'MUR',2,NULL,'Mauritius Rupee','currency.MUR'),(98,'MVR',2,NULL,'Maldives Rufiyaa','currency.MVR'),(99,'MWK',2,NULL,'Malawi Kwacha','currency.MWK'),(100,'MXN',2,'$','Mexican Peso','currency.MXN'),(101,'MYR',2,NULL,'Malaysian Ringgit','currency.MYR'),(102,'MZM',2,NULL,'Mozambique Metical','currency.MZM'),(103,'NAD',2,NULL,'Namibia Dollar','currency.NAD'),(104,'NGN',2,NULL,'Nigerian Naira','currency.NGN'),(105,'NIO',2,'C$','Nicaragua Cordoba Oro','currency.NIO'),(106,'NOK',2,NULL,'Norwegian Krone','currency.NOK'),(107,'NPR',2,NULL,'Nepalese Rupee','currency.NPR'),(108,'NZD',2,NULL,'New Zealand Dollar','currency.NZD'),(109,'OMR',3,NULL,'Rial Omani','currency.OMR'),(110,'PAB',2,'B/.','Panama Balboa','currency.PAB'),(111,'PEN',2,'S/.','Peruvian Nuevo Sol','currency.PEN'),(112,'PGK',2,NULL,'Papua New Guinea Kina','currency.PGK'),(113,'PHP',2,NULL,'Philippine Peso','currency.PHP'),(114,'PKR',2,NULL,'Pakistan Rupee','currency.PKR'),(115,'PLN',2,NULL,'Polish Zloty','currency.PLN'),(116,'PYG',0,'₲','Paraguayan Guarani','currency.PYG'),(117,'QAR',2,NULL,'Qatari Rial','currency.QAR'),(118,'RON',2,NULL,'Romanian Leu','currency.RON'),(119,'RUB',2,NULL,'Russian Ruble','currency.RUB'),(120,'RWF',0,NULL,'Rwanda Franc','currency.RWF'),(121,'SAR',2,NULL,'Saudi Riyal','currency.SAR'),(122,'SBD',2,NULL,'Solomon Islands Dollar','currency.SBD'),(123,'SCR',2,NULL,'Seychelles Rupee','currency.SCR'),(124,'SDD',2,NULL,'Sudanese Dinar','currency.SDD'),(125,'SEK',2,NULL,'Swedish Krona','currency.SEK'),(126,'SGD',2,NULL,'Singapore Dollar','currency.SGD'),(127,'SHP',2,NULL,'St Helena Pound','currency.SHP'),(128,'SIT',2,NULL,'Slovenian Tolar','currency.SIT'),(129,'SKK',2,NULL,'Slovak Koruna','currency.SKK'),(130,'SLL',2,NULL,'Sierra Leone Leone','currency.SLL'),(131,'SOS',2,NULL,'Somali Shilling','currency.SOS'),(132,'SRD',2,NULL,'Surinam Dollar','currency.SRD'),(133,'STD',2,NULL,'Sao Tome and Principe Dobra','currency.STD'),(134,'SVC',2,NULL,'El Salvador Colon','currency.SVC'),(135,'SYP',2,NULL,'Syrian Pound','currency.SYP'),(136,'SZL',2,NULL,'Swaziland Lilangeni','currency.SZL'),(137,'THB',2,NULL,'Thai Baht','currency.THB'),(138,'TJS',2,NULL,'Tajik Somoni','currency.TJS'),(139,'TMM',2,NULL,'Turkmenistan Manat','currency.TMM'),(140,'TND',3,'DT','Tunisian Dinar','currency.TND'),(141,'TOP',2,NULL,'Tonga Pa\'anga','currency.TOP'),(142,'TRY',2,NULL,'Turkish Lira','currency.TRY'),(143,'TTD',2,NULL,'Trinidad and Tobago Dollar','currency.TTD'),(144,'TWD',2,NULL,'New Taiwan Dollar','currency.TWD'),(145,'TZS',2,NULL,'Tanzanian Shilling','currency.TZS'),(146,'UAH',2,NULL,'Ukraine Hryvnia','currency.UAH'),(147,'UGX',2,'USh','Uganda Shilling','currency.UGX'),(148,'USD',2,'$','US Dollar','currency.USD'),(149,'UYU',2,'$U','Peso Uruguayo','currency.UYU'),(150,'UZS',2,NULL,'Uzbekistan Sum','currency.UZS'),(151,'VEB',2,'Bs.F.','Venezuelan Bolivar','currency.VEB'),(152,'VND',2,NULL,'Vietnamese Dong','currency.VND'),(153,'VUV',0,NULL,'Vanuatu Vatu','currency.VUV'),(154,'WST',2,NULL,'Samoa Tala','currency.WST'),(155,'XAF',0,NULL,'CFA Franc BEAC','currency.XAF'),(156,'XCD',2,NULL,'East Caribbean Dollar','currency.XCD'),(157,'XDR',5,NULL,'SDR (Special Drawing Rights)','currency.XDR'),(158,'XOF',0,'CFA','CFA Franc BCEAO','currency.XOF'),(159,'XPF',0,NULL,'CFP Franc','currency.XPF'),(160,'YER',2,NULL,'Yemeni Rial','currency.YER'),(161,'ZAR',2,'R','South African Rand','currency.ZAR'),(162,'ZMK',2,NULL,'Zambian Kwacha','currency.ZMK'),(163,'ZWD',2,NULL,'Zimbabwe Dollar','currency.ZWD');
+/*!40000 ALTER TABLE `m_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_document`
+--
+
+DROP TABLE IF EXISTS `m_document`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(50) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_document`
+--
+
+LOCK TABLES `m_document` WRITE;
+/*!40000 ALTER TABLE `m_document` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_document` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_fund`
+--
+
+DROP TABLE IF EXISTS `m_fund`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_fund`
+--
+
+LOCK TABLES `m_fund` WRITE;
+/*!40000 ALTER TABLE `m_fund` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_fund` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group`
+--
+
+DROP TABLE IF EXISTS `m_group`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `level_Id` int(11) NOT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`,`level_Id`),
+  UNIQUE KEY `external_id` (`external_id`,`level_Id`),
+  KEY `office_id` (`office_id`),
+  KEY `staff_id` (`staff_id`),
+  KEY `Parent_Id_reference` (`parent_id`),
+  KEY `FK_m_group_level` (`level_Id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_Id`) REFERENCES `m_group_level` (`id`),
+  CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group`
+--
+
+LOCK TABLES `m_group` WRITE;
+/*!40000 ALTER TABLE `m_group` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_client`
+--
+
+DROP TABLE IF EXISTS `m_group_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_client`
+--
+
+LOCK TABLES `m_group_client` WRITE;
+/*!40000 ALTER TABLE `m_group_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_level`
+--
+
+DROP TABLE IF EXISTS `m_group_level`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_level` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) DEFAULT NULL,
+  `super_parent` tinyint(1) NOT NULL,
+  `level_name` varchar(100) NOT NULL,
+  `recursable` tinyint(1) NOT NULL,
+  `can_have_clients` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `Parent_levelId_reference` (`parent_id`),
+  CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_level`
+--
+
+LOCK TABLES `m_group_level` WRITE;
+/*!40000 ALTER TABLE `m_group_level` DISABLE KEYS */;
+INSERT INTO `m_group_level` VALUES (1,NULL,1,'Center',1,0),(2,1,0,'Group',0,1);
+/*!40000 ALTER TABLE `m_group_level` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_guarantor`
+--
+
+DROP TABLE IF EXISTS `m_guarantor`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_guarantor` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `dob` date DEFAULT NULL,
+  `address_line_1` varchar(500) DEFAULT NULL,
+  `address_line_2` varchar(500) DEFAULT NULL,
+  `city` varchar(50) DEFAULT NULL,
+  `state` varchar(50) DEFAULT NULL,
+  `country` varchar(50) DEFAULT NULL,
+  `zip` varchar(20) DEFAULT NULL,
+  `house_phone_number` varchar(20) DEFAULT NULL,
+  `mobile_number` varchar(20) DEFAULT NULL,
+  `comment` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_m_loan` (`loan_id`),
+  CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_guarantor`
+--
+
+LOCK TABLES `m_guarantor` WRITE;
+/*!40000 ALTER TABLE `m_guarantor` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan`
+--
+
+DROP TABLE IF EXISTS `m_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `loanpurpose_cv_id` int(11) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `disbursedon_userid` bigint(20) DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `principal_disbursed_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `rescheduledon_date` date DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `writtenoffon_date` date DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loanpurpose_codevalue` (`loanpurpose_cv_id`),
+  KEY `FK_submittedon_userid` (`submittedon_userid`),
+  KEY `FK_approvedon_userid` (`approvedon_userid`),
+  KEY `FK_rejectedon_userid` (`rejectedon_userid`),
+  KEY `FK_withdrawnon_userid` (`withdrawnon_userid`),
+  KEY `FK_disbursedon_userid` (`disbursedon_userid`),
+  KEY `FK_closedon_userid` (`closedon_userid`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_approvedon_userid` FOREIGN KEY (`approvedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_closedon_userid` FOREIGN KEY (`closedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_disbursedon_userid` FOREIGN KEY (`disbursedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loanpurpose_codevalue` FOREIGN KEY (`loanpurpose_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_rejectedon_userid` FOREIGN KEY (`rejectedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_submittedon_userid` FOREIGN KEY (`submittedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_withdrawnon_userid` FOREIGN KEY (`withdrawnon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan`
+--
+
+LOCK TABLES `m_loan` WRITE;
+/*!40000 ALTER TABLE `m_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_arrears_aging`
+--
+
+DROP TABLE IF EXISTS `m_loan_arrears_aging`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_arrears_aging` (
+  `loan_id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `principal_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `overdue_since_date_derived` date DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_arrears_aging_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_arrears_aging`
+--
+
+LOCK TABLES `m_loan_arrears_aging` WRITE;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_charge`
+--
+
+LOCK TABLES `m_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_collateral`
+--
+
+DROP TABLE IF EXISTS `m_loan_collateral`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_collateral` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_cv_id` int(11) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_collateral_m_loan` (`loan_id`),
+  KEY `FK_collateral_code_value` (`type_cv_id`),
+  CONSTRAINT `FK_collateral_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_collateral_code_value` FOREIGN KEY (`type_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_collateral`
+--
+
+LOCK TABLES `m_loan_collateral` WRITE;
+/*!40000 ALTER TABLE `m_loan_collateral` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_collateral` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_officer_assignment_history`
+--
+
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_officer_assignment_history`
+--
+
+LOCK TABLES `m_loan_officer_assignment_history` WRITE;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_repayment_schedule`
+--
+
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_repayment_schedule`
+--
+
+LOCK TABLES `m_loan_repayment_schedule` WRITE;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_transaction`
+--
+
+DROP TABLE IF EXISTS `m_loan_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_transaction`
+--
+
+LOCK TABLES `m_loan_transaction` WRITE;
+/*!40000 ALTER TABLE `m_loan_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_note`
+--
+
+DROP TABLE IF EXISTS `m_note`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK_m_note_m_group` (`group_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_note_m_group` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_note`
+--
+
+LOCK TABLES `m_note` WRITE;
+/*!40000 ALTER TABLE `m_note` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_note` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office`
+--
+
+DROP TABLE IF EXISTS `m_office`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office`
+--
+
+LOCK TABLES `m_office` WRITE;
+/*!40000 ALTER TABLE `m_office` DISABLE KEYS */;
+INSERT INTO `m_office` VALUES (1,NULL,'.','1','Head Office','2009-01-01');
+/*!40000 ALTER TABLE `m_office` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office_transaction`
+--
+
+DROP TABLE IF EXISTS `m_office_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office_transaction`
+--
+
+LOCK TABLES `m_office_transaction` WRITE;
+/*!40000 ALTER TABLE `m_office_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_office_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_organisation_currency`
+--
+
+DROP TABLE IF EXISTS `m_organisation_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_organisation_currency`
+--
+
+LOCK TABLES `m_organisation_currency` WRITE;
+/*!40000 ALTER TABLE `m_organisation_currency` DISABLE KEYS */;
+INSERT INTO `m_organisation_currency` VALUES (21,'USD',2,'US Dollar','$','currency.USD');
+/*!40000 ALTER TABLE `m_organisation_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_permission`
+--
+
+DROP TABLE IF EXISTS `m_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=299 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_permission`
+--
+
+LOCK TABLES `m_permission` WRITE;
+/*!40000 ALTER TABLE `m_permission` DISABLE KEYS */;
+INSERT INTO `m_permission` VALUES (1,'special','ALL_FUNCTIONS',NULL,NULL,0),(2,'special','ALL_FUNCTIONS_READ',NULL,NULL,0),(3,'special','CHECKER_SUPER_USER',NULL,NULL,0),(4,'special','REPORTING_SUPER_USER',NULL,NULL,0),(5,'authorisation','READ_PERMISSION','PERMISSION','READ',0),(6,'authorisation','PERMISSIONS_ROLE','ROLE','PERMISSIONS',0),(7,'authorisation','CREATE_ROLE','ROLE','CREATE',0),(8,'authorisation','CREATE_ROLE_CHECKER','ROLE','CREATE',0),(9,'authorisation','READ_ROLE','ROLE','READ',0),(10,'authorisation','UPDATE_ROLE','ROLE','UPDATE',0),(11,'authorisation','UPDATE_ROLE_CHECKER','ROLE','UPDATE',0),(12,'authorisation','DELETE_ROLE','ROLE','DELETE',0),(13,'authorisation','DELETE_ROLE_CHECKER','ROLE','DELETE',0),(14,'authorisation','CREATE_USER','USER','CREATE',0),(15,'authorisation','CREATE_USER_CHECKER','USER','CREATE',0),(16,'authorisation','READ_USER','USER','READ',0),(17,'authorisation','UPDATE_USER','USER','UPDATE',0),(18,'authorisation','UPDATE_USER_CHECKER','USER','UPDATE',0),(19,'authorisation','DELETE_USER','USER','DELETE',0),(20,'authorisation','DELETE_USER_CHECKER','USER','DELETE',0),(21,'configuration','READ_CONFIGURATION','CONFIGURATION','READ',0),(22,'configuration','UPDATE_CONFIGURATION','CONFIGURATION','UPDATE',0),(23,'configuration','UPDATE_CONFIGURATION_CHECKER','CONFIGURATION','UPDATE',0),(24,'configuration','READ_CODE','CODE','READ',0),(25,'configuration','CREATE_CODE','CODE','CREATE',0),(26,'configuration','CREATE_CODE_CHECKER','CODE','CREATE',0),(27,'configuration','UPDATE_CODE','CODE','UPDATE',0),(28,'configuration','UPDATE_CODE_CHECKER','CODE','UPDATE',0),(29,'configuration','DELETE_CODE','CODE','DELETE',0),(30,'configuration','DELETE_CODE_CHECKER','CODE','DELETE',0),(31,'configuration','READ_CODEVALUE','CODEVALUE','READ',0),(32,'configuration','CREATE_CODEVALUE','CODEVALUE','CREATE',0),(33,'configuration','CREATE_CODEVALUE_CHECKER','CODEVALUE','CREATE',0),(34,'configuration','UPDATE_CODEVALUE','CODEVALUE','UPDATE',0),(35,'configuration','UPDATE_CODEVALUE_CHECKER','CODEVALUE','UPDATE',0),(36,'configuration','DELETE_CODEVALUE','CODEVALUE','DELETE',0),(37,'configuration','DELETE_CODEVALUE_CHECKER','CODEVALUE','DELETE',0),(38,'configuration','READ_CURRENCY','CURRENCY','READ',0),(39,'configuration','UPDATE_CURRENCY','CURRENCY','UPDATE',0),(40,'configuration','UPDATE_CURRENCY_CHECKER','CURRENCY','UPDATE',0),(41,'configuration','UPDATE_PERMISSION','PERMISSION','UPDATE',0),(42,'configuration','UPDATE_PERMISSION_CHECKER','PERMISSION','UPDATE',0),(43,'configuration','READ_DATATABLE','DATATABLE','READ',0),(44,'configuration','REGISTER_DATATABLE','DATATABLE','REGISTER',0),(45,'configuration','REGISTER_DATATABLE_CHECKER','DATATABLE','REGISTER',0),(46,'configuration','DEREGISTER_DATATABLE','DATATABLE','DEREGISTER',0),(47,'configuration','DEREGISTER_DATATABLE_CHECKER','DATATABLE','DEREGISTER',0),(48,'configuration','READ_AUDIT','AUDIT','READ',0),(49,'configuration','CREATE_CALENDAR','CALENDAR','CREATE',0),(50,'configuration','READ_CALENDAR','CALENDAR','READ',0),(51,'configuration','UPDATE_CALENDAR','CALENDAR','UPDATE',0),(52,'configuration','DELETE_CALENDAR','CALENDAR','DELETE',0),(53,'configuration','CREATE_CALENDAR_CHECKER','CALENDAR','CREATE',0),(54,'configuration','UPDATE_CALENDAR_CHECKER','CALENDAR','UPDATE',0),(55,'configuration','DELETE_CALENDAR_CHECKER','CALENDAR','DELETE',0),(56,'organisation','READ_MAKERCHECKER','MAKERCHECKER','READ',0),(57,'organisation','READ_CHARGE','CHARGE','READ',0),(58,'organisation','CREATE_CHARGE','CHARGE','CREATE',0),(59,'organisation','CREATE_CHARGE_CHECKER','CHARGE','CREATE',0),(60,'organisation','UPDATE_CHARGE','CHARGE','UPDATE',0),(61,'organisation','UPDATE_CHARGE_CHECKER','CHARGE','UPDATE',0),(62,'organisation','DELETE_CHARGE','CHARGE','DELETE',0),(63,'organisation','DELETE_CHARGE_CHECKER','CHARGE','DELETE',0),(64,'organisation','READ_FUND','FUND','READ',0),(65,'organisation','CREATE_FUND','FUND','CREATE',0),(66,'organisation','CREATE_FUND_CHECKER','FUND','CREATE',0),(67,'organisation','UPDATE_FUND','FUND','UPDATE',0),(68,'organisation','UPDATE_FUND_CHECKER','FUND','UPDATE',0),(69,'organisation','DELETE_FUND','FUND','DELETE',0),(70,'organisation','DELETE_FUND_CHECKER','FUND','DELETE',0),(71,'organisation','READ_LOANPRODUCT','LOANPRODUCT','READ',0),(72,'organisation','CREATE_LOANPRODUCT','LOANPRODUCT','CREATE',0),(73,'organisation','CREATE_LOANPRODUCT_CHECKER','LOANPRODUCT','CREATE',0),(74,'organisation','UPDATE_LOANPRODUCT','LOANPRODUCT','UPDATE',0),(75,'organisation','UPDATE_LOANPRODUCT_CHECKER','LOANPRODUCT','UPDATE',0),(76,'organisation','DELETE_LOANPRODUCT','LOANPRODUCT','DELETE',0),(77,'organisation','DELETE_LOANPRODUCT_CHECKER','LOANPRODUCT','DELETE',0),(78,'organisation','READ_OFFICE','OFFICE','READ',0),(79,'organisation','CREATE_OFFICE','OFFICE','CREATE',0),(80,'organisation','CREATE_OFFICE_CHECKER','OFFICE','CREATE',0),(81,'organisation','UPDATE_OFFICE','OFFICE','UPDATE',0),(82,'organisation','UPDATE_OFFICE_CHECKER','OFFICE','UPDATE',0),(83,'organisation','READ_OFFICETRANSACTION','OFFICETRANSACTION','READ',0),(84,'organisation','DELETE_OFFICE_CHECKER','OFFICE','DELETE',0),(85,'organisation','CREATE_OFFICETRANSACTION','OFFICETRANSACTION','CREATE',0),(86,'organisation','CREATE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','CREATE',0),(87,'organisation','DELETE_OFFICETRANSACTION','OFFICETRANSACTION','DELETE',0),(88,'organisation','DELETE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','DELETE',0),(89,'organisation','READ_STAFF','STAFF','READ',0),(90,'organisation','CREATE_STAFF','STAFF','CREATE',0),(91,'organisation','CREATE_STAFF_CHECKER','STAFF','CREATE',0),(92,'organisation','UPDATE_STAFF','STAFF','UPDATE',0),(93,'organisation','UPDATE_STAFF_CHECKER','STAFF','UPDATE',0),(94,'organisation','DELETE_STAFF','STAFF','DELETE',0),(95,'organisation','DELETE_STAFF_CHECKER','STAFF','DELETE',0),(96,'organisation','READ_SAVINGSPRODUCT','SAVINGSPRODUCT','READ',0),(97,'organisation','CREATE_SAVINGSPRODUCT','SAVINGSPRODUCT','CREATE',0),(98,'organisation','CREATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','CREATE',0),(99,'organisation','UPDATE_SAVINGSPRODUCT','SAVINGSPRODUCT','UPDATE',0),(100,'organisation','UPDATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','UPDATE',0),(101,'organisation','DELETE_SAVINGSPRODUCT','SAVINGSPRODUCT','DELETE',0),(102,'organisation','DELETE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','DELETE',0),(103,'portfolio','READ_LOAN','LOAN','READ',0),(104,'portfolio','CREATE_LOAN','LOAN','CREATE',0),(105,'portfolio','CREATE_LOAN_CHECKER','LOAN','CREATE',0),(106,'portfolio','UPDATE_LOAN','LOAN','UPDATE',0),(107,'portfolio','UPDATE_LOAN_CHECKER','LOAN','UPDATE',0),(108,'portfolio','DELETE_LOAN','LOAN','DELETE',0),(109,'portfolio','DELETE_LOAN_CHECKER','LOAN','DELETE',0),(110,'portfolio','READ_CLIENT','CLIENT','READ',0),(111,'portfolio','CREATE_CLIENT','CLIENT','CREATE',0),(112,'portfolio','CREATE_CLIENT_CHECKER','CLIENT','CREATE',0),(113,'portfolio','UPDATE_CLIENT','CLIENT','UPDATE',0),(114,'portfolio','UPDATE_CLIENT_CHECKER','CLIENT','UPDATE',0),(115,'portfolio','DELETE_CLIENT','CLIENT','DELETE',0),(116,'portfolio','DELETE_CLIENT_CHECKER','CLIENT','DELETE',0),(117,'portfolio','READ_CLIENTIMAGE','CLIENTIMAGE','READ',0),(118,'portfolio','CREATE_CLIENTIMAGE','CLIENTIMAGE','CREATE',0),(119,'portfolio','CREATE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','CREATE',0),(120,'portfolio','DELETE_CLIENTIMAGE','CLIENTIMAGE','DELETE',0),(121,'portfolio','DELETE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','DELETE',0),(122,'portfolio','READ_CLIENTNOTE','CLIENTNOTE','READ',0),(123,'portfolio','CREATE_CLIENTNOTE','CLIENTNOTE','CREATE',0),(124,'portfolio','CREATE_CLIENTNOTE_CHECKER','CLIENTNOTE','CREATE',0),(125,'portfolio','UPDATE_CLIENTNOTE','CLIENTNOTE','UPDATE',0),(126,'portfolio','UPDATE_CLIENTNOTE_CHECKER','CLIENTNOTE','UPDATE',0),(127,'portfolio','DELETE_CLIENTNOTE','CLIENTNOTE','DELETE',0),(128,'portfolio','DELETE_CLIENTNOTE_CHECKER','CLIENTNOTE','DELETE',0),(129,'portfolio','READ_GROUPNOTE','GROUPNOTE','READ',0),(130,'portfolio','CREATE_GROUPNOTE','GROUPNOTE','CREATE',0),(131,'portfolio','UPDATE_GROUPNOTE','GROUPNOTE','UPDATE',0),(132,'portfolio','DELETE_GROUPNOTE','GROUPNOTE','DELETE',0),(133,'portfolio','CREATE_GROUPNOTE_CHECKER','GROUPNOTE','CREATE',0),(134,'portfolio','UPDATE_GROUPNOTE_CHECKER','GROUPNOTE','UPDATE',0),(135,'portfolio','DELETE_GROUPNOTE_CHECKER','GROUPNOTE','DELETE',0),(136,'portfolio','READ_LOANNOTE','LOANNOTE','READ',0),(137,'portfolio','CREATE_LOANNOTE','LOANNOTE','CREATE',0),(138,'portfolio','UPDATE_LOANNOTE','LOANNOTE','UPDATE',0),(139,'portfolio','DELETE_LOANNOTE','LOANNOTE','DELETE',0),(140,'portfolio','CREATE_LOANNOTE_CHECKER','LOANNOTE','CREATE',0),(141,'portfolio','UPDATE_LOANNOTE_CHECKER','LOANNOTE','UPDATE',0),(142,'portfolio','DELETE_LOANNOTE_CHECKER','LOANNOTE','DELETE',0),(143,'portfolio','READ_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','READ',0),(144,'portfolio','CREATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','CREATE',0),(145,'portfolio','UPDATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','UPDATE',0),(146,'portfolio','DELETE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','DELETE',0),(147,'portfolio','CREATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','CREATE',0),(148,'portfolio','UPDATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','UPDATE',0),(149,'portfolio','DELETE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','DELETE',0),(150,'portfolio','READ_SAVINGNOTE','SAVINGNOTE','READ',0),(151,'portfolio','CREATE_SAVINGNOTE','SAVINGNOTE','CREATE',0),(152,'portfolio','UPDATE_SAVINGNOTE','SAVINGNOTE','UPDATE',0),(153,'portfolio','DELETE_SAVINGNOTE','SAVINGNOTE','DELETE',0),(154,'portfolio','CREATE_SAVINGNOTE_CHECKER','SAVINGNOTE','CREATE',0),(155,'portfolio','UPDATE_SAVINGNOTE_CHECKER','SAVINGNOTE','UPDATE',0),(156,'portfolio','DELETE_SAVINGNOTE_CHECKER','SAVINGNOTE','DELETE',0),(157,'portfolio','READ_CLIENTIDENTIFIER','CLIENTIDENTIFIER','READ',0),(158,'portfolio','CREATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','CREATE',0),(159,'portfolio','CREATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','CREATE',0),(160,'portfolio','UPDATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','UPDATE',0),(161,'portfolio','UPDATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','UPDATE',0),(162,'portfolio','DELETE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','DELETE',0),(163,'portfolio','DELETE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','DELETE',0),(164,'portfolio','READ_DOCUMENT','DOCUMENT','READ',0),(165,'portfolio','CREATE_DOCUMENT','DOCUMENT','CREATE',0),(166,'portfolio','CREATE_DOCUMENT_CHECKER','DOCUMENT','CREATE',0),(167,'portfolio','UPDATE_DOCUMENT','DOCUMENT','UPDATE',0),(168,'portfolio','UPDATE_DOCUMENT_CHECKER','DOCUMENT','UPDATE',0),(169,'portfolio','DELETE_DOCUMENT','DOCUMENT','DELETE',0),(170,'portfolio','DELETE_DOCUMENT_CHECKER','DOCUMENT','DELETE',0),(171,'portfolio','READ_GROUP','GROUP','READ',0),(172,'portfolio','CREATE_GROUP','GROUP','CREATE',0),(173,'portfolio','CREATE_GROUP_CHECKER','GROUP','CREATE',0),(174,'portfolio','UPDATE_GROUP','GROUP','UPDATE',0),(175,'portfolio','UPDATE_GROUP_CHECKER','GROUP','UPDATE',0),(176,'portfolio','DELETE_GROUP','GROUP','DELETE',0),(177,'portfolio','DELETE_GROUP_CHECKER','GROUP','DELETE',0),(178,'portfolio','UNASSIGNSTAFF_GROUP','GROUP','UNASSIGNSTAFF',0),(179,'portfolio','UNASSIGNSTAFF_GROUP_CHECKER','GROUP','UNASSIGNSTAFF',0),(180,'portfolio','CREATE_LOANCHARGE','LOANCHARGE','CREATE',0),(181,'portfolio','CREATE_LOANCHARGE_CHECKER','LOANCHARGE','CREATE',0),(182,'portfolio','UPDATE_LOANCHARGE','LOANCHARGE','UPDATE',0),(183,'portfolio','UPDATE_LOANCHARGE_CHECKER','LOANCHARGE','UPDATE',0),(184,'portfolio','DELETE_LOANCHARGE','LOANCHARGE','DELETE',0),(185,'portfolio','DELETE_LOANCHARGE_CHECKER','LOANCHARGE','DELETE',0),(186,'portfolio','WAIVE_LOANCHARGE','LOANCHARGE','WAIVE',0),(187,'portfolio','WAIVE_LOANCHARGE_CHECKER','LOANCHARGE','WAIVE',0),(188,'portfolio','READ_SAVINGSACCOUNT','SAVINGSACCOUNT','READ',0),(189,'portfolio','CREATE_SAVINGSACCOUNT','SAVINGSACCOUNT','CREATE',0),(190,'portfolio','CREATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','CREATE',0),(191,'portfolio','UPDATE_SAVINGSACCOUNT','SAVINGSACCOUNT','UPDATE',0),(192,'portfolio','UPDATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','UPDATE',0),(193,'portfolio','DELETE_SAVINGSACCOUNT','SAVINGSACCOUNT','DELETE',0),(194,'portfolio','DELETE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DELETE',0),(195,'portfolio','READ_GUARANTOR','GUARANTOR','READ',0),(196,'portfolio','CREATE_GUARANTOR','GUARANTOR','CREATE',0),(197,'portfolio','CREATE_GUARANTOR_CHECKER','GUARANTOR','CREATE',0),(198,'portfolio','UPDATE_GUARANTOR','GUARANTOR','UPDATE',0),(199,'portfolio','UPDATE_GUARANTOR_CHECKER','GUARANTOR','UPDATE',0),(200,'portfolio','DELETE_GUARANTOR','GUARANTOR','DELETE',0),(201,'portfolio','DELETE_GUARANTOR_CHECKER','GUARANTOR','DELETE',0),(202,'transaction_loan','APPROVE_LOAN','LOAN','APPROVE',0),(203,'transaction_loan','APPROVEINPAST_LOAN','LOAN','APPROVEINPAST',0),(204,'transaction_loan','REJECT_LOAN','LOAN','REJECT',0),(205,'transaction_loan','REJECTINPAST_LOAN','LOAN','REJECTINPAST',0),(206,'transaction_loan','WITHDRAW_LOAN','LOAN','WITHDRAW',0),(207,'transaction_loan','WITHDRAWINPAST_LOAN','LOAN','WITHDRAWINPAST',0),(208,'transaction_loan','APPROVALUNDO_LOAN','LOAN','APPROVALUNDO',0),(209,'transaction_loan','DISBURSE_LOAN','LOAN','DISBURSE',0),(210,'transaction_loan','DISBURSEINPAST_LOAN','LOAN','DISBURSEINPAST',0),(211,'transaction_loan','DISBURSALUNDO_LOAN','LOAN','DISBURSALUNDO',0),(212,'transaction_loan','REPAYMENT_LOAN','LOAN','REPAYMENT',0),(213,'transaction_loan','REPAYMENTINPAST_LOAN','LOAN','REPAYMENTINPAST',0),(214,'transaction_loan','ADJUST_LOAN','LOAN','ADJUST',0),(215,'transaction_loan','WAIVEINTERESTPORTION_LOAN','LOAN','WAIVEINTERESTPORTION',0),(216,'transaction_loan','WRITEOFF_LOAN','LOAN','WRITEOFF',0),(217,'transaction_loan','CLOSE_LOAN','LOAN','CLOSE',0),(218,'transaction_loan','CLOSEASRESCHEDULED_LOAN','LOAN','CLOSEASRESCHEDULED',0),(219,'transaction_loan','UPDATELOANOFFICER_LOAN','LOAN','UPDATELOANOFFICER',0),(220,'transaction_loan','UPDATELOANOFFICER_LOAN_CHECKER','LOAN','UPDATELOANOFFICER',0),(221,'transaction_loan','REMOVELOANOFFICER_LOAN','LOAN','REMOVELOANOFFICER',0),(222,'transaction_loan','REMOVELOANOFFICER_LOAN_CHECKER','LOAN','REMOVELOANOFFICER',0),(223,'transaction_loan','BULKREASSIGN_LOAN','LOAN','BULKREASSIGN',0),(224,'transaction_loan','BULKREASSIGN_LOAN_CHECKER','LOAN','BULKREASSIGN',0),(225,'transaction_loan','APPROVE_LOAN_CHECKER','LOAN','APPROVE',0),(226,'transaction_loan','APPROVEINPAST_LOAN_CHECKER','LOAN','APPROVEINPAST',0),(227,'transaction_loan','REJECT_LOAN_CHECKER','LOAN','REJECT',0),(228,'transaction_loan','REJECTINPAST_LOAN_CHECKER','LOAN','REJECTINPAST',0),(229,'transaction_loan','WITHDRAW_LOAN_CHECKER','LOAN','WITHDRAW',0),(230,'transaction_loan','WITHDRAWINPAST_LOAN_CHECKER','LOAN','WITHDRAWINPAST',0),(231,'transaction_loan','APPROVALUNDO_LOAN_CHECKER','LOAN','APPROVALUNDO',0),(232,'transaction_loan','DISBURSE_LOAN_CHECKER','LOAN','DISBURSE',0),(233,'transaction_loan','DISBURSEINPAST_LOAN_CHECKER','LOAN','DISBURSEINPAST',0),(234,'transaction_loan','DISBURSALUNDO_LOAN_CHECKER','LOAN','DISBURSALUNDO',0),(235,'transaction_loan','REPAYMENT_LOAN_CHECKER','LOAN','REPAYMENT',0),(236,'transaction_loan','REPAYMENTINPAST_LOAN_CHECKER','LOAN','REPAYMENTINPAST',0),(237,'transaction_loan','ADJUST_LOAN_CHECKER','LOAN','ADJUST',0),(238,'transaction_loan','WAIVEINTERESTPORTION_LOAN_CHECKER','LOAN','WAIVEINTERESTPORTION',0),(239,'transaction_loan','WRITEOFF_LOAN_CHECKER','LOAN','WRITEOFF',0),(240,'transaction_loan','CLOSE_LOAN_CHECKER','LOAN','CLOSE',0),(241,'transaction_loan','CLOSEASRESCHEDULED_LOAN_CHECKER','LOAN','CLOSEASRESCHEDULED',0),(242,'transaction_savings','DEPOSIT_SAVINGSACCOUNT','SAVINGSACCOUNT','DEPOSIT',0),(243,'transaction_savings','DEPOSIT_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DEPOSIT',0),(244,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT','SAVINGSACCOUNT','WITHDRAWAL',0),(245,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','WITHDRAWAL',0),(246,'transaction_savings','ACTIVATE_SAVINGSACCOUNT','SAVINGSACCOUNT','ACTIVATE',0),(247,'transaction_savings','ACTIVATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','ACTIVATE',0),(248,'accounting','CREATE_GLACCOUNT','GLACCOUNT','CREATE',0),(249,'accounting','UPDATE_GLACCOUNT','GLACCOUNT','UPDATE',0),(250,'accounting','DELETE_GLACCOUNT','GLACCOUNT','DELETE',0),(251,'accounting','CREATE_GLCLOSURE','GLCLOSURE','CREATE',0),(252,'accounting','UPDATE_GLCLOSURE','GLCLOSURE','UPDATE',0),(253,'accounting','DELETE_GLCLOSURE','GLCLOSURE','DELETE',0),(254,'accounting','CREATE_JOURNALENTRY','JOURNALENTRY','CREATE',0),(255,'accounting','REVERSE_JOURNALENTRY','JOURNALENTRY','REVERSE',0),(256,'report','READ_Active Loans - Details','Active Loans - Details','READ',0),(257,'report','READ_Active Loans - Summary','Active Loans - Summary','READ',0),(258,'report','READ_Active Loans by Disbursal Period','Active Loans by Disbursal Period','READ',0),(259,'report','READ_Active Loans in last installment','Active Loans in last installment','READ',0),(260,'report','READ_Active Loans in last installment Summary','Active Loans in last installment Summary','READ',0),(261,'report','READ_Active Loans Passed Final Maturity','Active Loans Passed Final Maturity','READ',0),(262,'report','READ_Active Loans Passed Final Maturity Summary','Active Loans Passed Final Maturity Summary','READ',0),(263,'report','READ_Aging Detail','Aging Detail','READ',0),(264,'report','READ_Aging Summary (Arrears in Months)','Aging Summary (Arrears in Months)','READ',0),(265,'report','READ_Aging Summary (Arrears in Weeks)','Aging Summary (Arrears in Weeks)','READ',0),(266,'report','READ_Balance Sheet','Balance Sheet','READ',0),(267,'report','READ_Branch Expected Cash Flow','Branch Expected Cash Flow','READ',0),(268,'report','READ_Client Listing','Client Listing','READ',0),(269,'report','READ_Client Loans Listing','Client Loans Listing','READ',0),(270,'report','READ_Expected Payments By Date - Basic','Expected Payments By Date - Basic','READ',0),(271,'report','READ_Expected Payments By Date - Formatted','Expected Payments By Date - Formatted','READ',0),(272,'report','READ_Funds Disbursed Between Dates Summary','Funds Disbursed Between Dates Summary','READ',0),(273,'report','READ_Funds Disbursed Between Dates Summary by Office','Funds Disbursed Between Dates Summary by Office','READ',0),(274,'report','READ_Income Statement','Income Statement','READ',0),(275,'report','READ_Loan Account Schedule','Loan Account Schedule','READ',0),(276,'report','READ_Loans Awaiting Disbursal','Loans Awaiting Disbursal','READ',0),(277,'report','READ_Loans Awaiting Disbursal Summary','Loans Awaiting Disbursal Summary','READ',0),(278,'report','READ_Loans Awaiting Disbursal Summary by Month','Loans Awaiting Disbursal Summary by Month','READ',0),(279,'report','READ_Loans Pending Approval','Loans Pending Approval','READ',0),(280,'report','READ_Obligation Met Loans Details','Obligation Met Loans Details','READ',0),(281,'report','READ_Obligation Met Loans Summary','Obligation Met Loans Summary','READ',0),(282,'report','READ_Portfolio at Risk','Portfolio at Risk','READ',0),(283,'report','READ_Portfolio at Risk by Branch','Portfolio at Risk by Branch','READ',0),(284,'report','READ_Rescheduled Loans','Rescheduled Loans','READ',0),(285,'report','READ_Trial Balance','Trial Balance','READ',0),(286,'report','READ_Written-Off Loans','Written-Off Loans','READ',0),(287,'datatable','CREATE_extra_client_details','extra_client_details','CREATE',1),(288,'datatable','CREATE_extra_family_details','extra_family_details','CREATE',1),(289,'datatable','CREATE_extra_loan_details','extra_loan_details','CREATE',1),(290,'datatable','READ_extra_client_details','extra_client_details','READ',1),(291,'datatable','READ_extra_family_details','extra_family_details','READ',1),(292,'datatable','READ_extra_loan_details','extra_loan_details','READ',1),(293,'datatable','UPDATE_extra_client_details','extra_client_details','UPDATE',1),(294,'datatable','UPDATE_extra_family_details','extra_family_details','UPDATE',1),(295,'datatable','UPDATE_extra_loan_details','extra_loan_details','UPDATE',1),(296,'datatable','DELETE_extra_client_details','extra_client_details','DELETE',1),(297,'datatable','DELETE_extra_family_details','extra_family_details','DELETE',1),(298,'datatable','DELETE_extra_loan_details','extra_loan_details','DELETE',1);
+/*!40000 ALTER TABLE `m_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_portfolio_command_source`
+--
+
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `subresource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_portfolio_command_source`
+--
+
+LOCK TABLES `m_portfolio_command_source` WRITE;
+/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_portfolio_command_source` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan`
+--
+
+DROP TABLE IF EXISTS `m_product_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan`
+--
+
+LOCK TABLES `m_product_loan` WRITE;
+/*!40000 ALTER TABLE `m_product_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan_charge`
+--
+
+LOCK TABLES `m_product_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_product_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role`
+--
+
+DROP TABLE IF EXISTS `m_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role`
+--
+
+LOCK TABLES `m_role` WRITE;
+/*!40000 ALTER TABLE `m_role` DISABLE KEYS */;
+INSERT INTO `m_role` VALUES (1,'Super user','This role provides all application permissions.');
+/*!40000 ALTER TABLE `m_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role_permission`
+--
+
+DROP TABLE IF EXISTS `m_role_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role_permission`
+--
+
+LOCK TABLES `m_role_permission` WRITE;
+/*!40000 ALTER TABLE `m_role_permission` DISABLE KEYS */;
+INSERT INTO `m_role_permission` VALUES (1,1);
+/*!40000 ALTER TABLE `m_role_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account`
+--
+
+DROP TABLE IF EXISTS `m_savings_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `status_enum` smallint(5) NOT NULL DEFAULT '300',
+  `activation_date` date DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `lockedin_until_date_derived` date DEFAULT NULL,
+  `total_deposits_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
+  `account_balance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sa_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `sa_externalid_UNIQUE` (`external_id`),
+  KEY `FKSA00000000000001` (`client_id`),
+  KEY `FKSA00000000000002` (`group_id`),
+  KEY `FKSA00000000000003` (`product_id`),
+  CONSTRAINT `FKSA00000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA00000000000002` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSA00000000000003` FOREIGN KEY (`product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account`
+--
+
+LOCK TABLES `m_savings_account` WRITE;
+/*!40000 ALTER TABLE `m_savings_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account_transaction`
+--
+
+DROP TABLE IF EXISTS `m_savings_account_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account_transaction`
+--
+
+LOCK TABLES `m_savings_account_transaction` WRITE;
+/*!40000 ALTER TABLE `m_savings_account_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_product`
+--
+
+DROP TABLE IF EXISTS `m_savings_product`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_product` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sp_unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_product`
+--
+
+LOCK TABLES `m_savings_product` WRITE;
+/*!40000 ALTER TABLE `m_savings_product` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_product` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_staff`
+--
+
+DROP TABLE IF EXISTS `m_staff`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_staff`
+--
+
+LOCK TABLES `m_staff` WRITE;
+/*!40000 ALTER TABLE `m_staff` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_staff` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `r_enum_value`
+--
+
+DROP TABLE IF EXISTS `r_enum_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `r_enum_value`
+--
+
+LOCK TABLES `r_enum_value` WRITE;
+/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
+INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
+/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `ref_loan_transaction_processing_strategy`
+--
+
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `ref_loan_transaction_processing_strategy`
+--
+
+LOCK TABLES `ref_loan_transaction_processing_strategy` WRITE;
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` DISABLE KEYS */;
+INSERT INTO `ref_loan_transaction_processing_strategy` VALUES (1,'mifos-standard-strategy','Mifos style',NULL,NULL,NULL,NULL),(2,'heavensfamily-strategy','Heavensfamily',NULL,NULL,NULL,NULL),(3,'creocore-strategy','Creocore',NULL,NULL,NULL,NULL),(4,'rbi-india-strategy','RBI (India)',NULL,NULL,NULL,NULL);
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `rpt_sequence`
+--
+
+DROP TABLE IF EXISTS `rpt_sequence`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `rpt_sequence`
+--
+
+LOCK TABLES `rpt_sequence` WRITE;
+/*!40000 ALTER TABLE `rpt_sequence` DISABLE KEYS */;
+/*!40000 ALTER TABLE `rpt_sequence` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_parameter` (
+  `parameter_id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_parameter_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`parameter_id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`),
+  KEY `fk_stretchy_parameter_0001_idx` (`parent_parameter_id`),
+  CONSTRAINT `fk_stretchy_parameter_0001` FOREIGN KEY (`parent_parameter_id`) REFERENCES `stretchy_parameter` (`parameter_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_parameter`
+--
+
+LOCK TABLES `stretchy_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_parameter` VALUES (1,'startDateSelect','startDate','startDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(2,'endDateSelect','endDate','endDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(3,'obligDateTypeSelect','obligDateType','obligDateType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Closed\" as `name` union all\r\nselect 2, \"Disbursal\" ) x\r\norder by x.`id`',NULL),(5,'OfficeIdSelectOne','officeId','Office','select','number','0',NULL,'Y',NULL,'select id, \r\nconcat(substring(\"........................................\", 1, \r\n   \n\n((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat\n\n(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy',NULL),(6,'loanOfficerIdSelectAll','loanOfficerId','Loan Officer','select','number','0',NULL,NULL,'Y','(select lo.id, lo.display_name as `Name` \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\njoin m_staff lo on lo.office_id = ounder.id\r\nwhere lo.is_loan_officer = true\r\nand o.id = ${officeId})\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',5),(10,'currencyIdSelectAll','currencyId','Currency','select','number','0',NULL,NULL,'Y','select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`',NULL),(20,'fundIdSelectAll','fundId','Fund','select','number','0',NULL,NULL,'Y','(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',NULL),(25,'loanProductIdSelectAll','loanProductId','Product','select','number','0',NULL,NULL,'Y','select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere p.currency_code = \'${currencyId}\'\r\norder by 2',10),(26,'loanPurposeIdSelectAll','loanPurposeId','Loan Purpose','select','number','0',NULL,NULL,'Y','select -10 as id, \'-\' as code_value\r\nunion all\r\nselect * from (select v.id, v.code_value\r\nfrom m_code c\r\njoin m_code_value v on v.code_id = c.id\r\nwhere c.code_name = \"loanPurpose\"\r\norder by v.order_position)  x',NULL),(100,'parTypeSelect','parType','parType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Principal Only\" as `name` union all\r\nselect 2, \"Principal + Interest\" union all\r\nselect 3, \"Principal + Interest + Fees\" union all\r\nselect 4, \"Principal + Interest + Fees + Penalties\") x\r\norder by x.`id`',NULL),(1001,'FullReportList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\r\n  \n\nrp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on rp.report_id = r.report_id\r\n  \n\nleft join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \n\n\r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on rp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where \n\nur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by \n\nr.report_category, r.report_name, rp.parameter_id',NULL),(1002,'FullParameterList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r\nsp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r\nfrom stretchy_parameter sp\r\nleft join stretchy_parameter spp on spp.parameter_id = sp.parent_parameter_id\r\nwhere sp.special is null\r\nand exists \r\n	(select \'f\' \r\n	from stretchy_report sr\r\n	join stretchy_report_parameter srp on srp.report_id = sr.report_id\r\n	where sr.report_name in(${reportListing})\r\n	and srp.parameter_id = sp.parameter_id\r\n	)\r\norder by sp.parameter_id',NULL),(1003,'reportCategoryList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, \n\nr.report_category,\r\n  rp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on \n\nrp.report_id = r.report_id\r\n  left join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.report_category = \'${reportCategory}\'\r\n  and \n\nr.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on \n\nrp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where ur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \n\n\'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by r.report_category, r.report_name, rp.parameter_id',NULL);
+/*!40000 ALTER TABLE `stretchy_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report`
+--
+
+DROP TABLE IF EXISTS `stretchy_report`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report` (
+  `report_id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`report_id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report`
+--
+
+LOCK TABLES `stretchy_report` WRITE;
+/*!40000 ALTER TABLE `stretchy_report` DISABLE KEYS */;
+INSERT INTO `stretchy_report` VALUES (1,'Client Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n c.account_no as \"Client Account No.\",  \r\nc.display_name as \"Name\",  \n\nc.joined_date as \"Joined\", c.external_id as \"External Id\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand \n\nounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\nwhere o.id = ${officeId}\r\nand c.is_deleted=0\r\n\n\norder by ounder.hierarchy, c.account_no','Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t \n\nhave that client browser/memory impact).',1,1),(2,'Client Loans Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client \n\nAccount No.\", \r\nc.display_name as \"Name\", \r\nlo.display_name as \"Loan Officer\", l.account_no as \"Loan Account No.\", l.external_id as \"External Id\", \r\n\n\np.name as Loan, st.enum_message_property as \"Status\",  \r\nf.`name` as Fund, purp.code_value as \"Loan Purpose\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount,\n\n\r\nl.arrearstolerance_amount as \"Arrears Tolerance Amount\",\r\nl.number_of_repayments as \"Expected No. Repayments\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\nl.nominal_interest_rate_per_period as \"Nominal Interest Rate Per Period\",\r\n\r\nipf.enum_message_property as \"Interest Rate Frequency\n\n\",\r\nim.enum_message_property as \"Interest Method\",\r\nicp.enum_message_property as \"Interest Calculated in Period\",\r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.repay_every as \"Repayment Frequency\",\r\nrf.enum_message_property as \"Repayment Frequency Period\",\n\n\r\nam.enum_message_property as \"Amortization\",\r\n\r\nl.total_charges_due_at_disbursement_derived as \"Total Charges Due At Disbursement\",\r\n\r\ndate( \n\nl.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As \"Expected Disbursal\",\r\ndate(l.expected_firstrepaymenton_date) as \n\n\"Expected First Repayment\", date(l.interest_calculated_from_date) as \"Interest Calculated From\" ,\r\ndate(l.disbursedon_date) as Disbursed, date\n\n(l.expected_maturedon_date) \"Expected Maturity\",\r\ndate(l.maturedon_date) as \"Matured On\", date(l.closedon_date) as Closed,\r\ndate(l.rejectedon_date) as \n\nRejected, date(l.rescheduledon_date) as Rescheduled, \r\ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) \"Written Off\"\r\nfrom m_office o \r\njoin \n\nm_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on \n\nc.office_id = ounder.id\r\nleft join m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_product_loan p on p.id = \n\nl.product_id\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join r_enum_value st on st.enum_name = \"loan_status_id\" and st.enum_id = l.loan_status_id\r\nleft join \n\nr_enum_value ipf on ipf.enum_name = \"interest_period_frequency_enum\" and ipf.enum_id = l.interest_period_frequency_enum\r\nleft join r_enum_value im on im.enum_name \n\n= \"interest_method_enum\" and im.enum_id = l.interest_method_enum\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = \n\nl.term_period_frequency_enum\r\nleft join r_enum_value icp on icp.enum_name = \"interest_calculated_in_period_enum\" and icp.enum_id = \n\nl.interest_calculated_in_period_enum\r\nleft join r_enum_value rf on rf.enum_name = \"repayment_period_frequency_enum\" and rf.enum_id = \n\nl.repayment_period_frequency_enum\r\nleft join r_enum_value am on am.enum_name = \"amortization_method_enum\" and am.enum_id = l.amortization_method_enum\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\n\r\nleft \n\njoin m_currency cur on cur.code = l.currency_code\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand \n\n(l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \n\n\"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\norder by ounder.hierarchy, 2 , l.id','Individual Client Report\r\n\r\nPretty \n\nwide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).',1,1),(5,'Loans Awaiting Disbursal','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No\", c.display_name as \"Name\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund, ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\",\r\ndate(l.approvedon_date) \"Approved\",\r\ndatediff(l.expected_disbursedon_date, curdate()) as \"Days to Disbursal\",\r\ndate(l.expected_disbursedon_date) \"Expected Disbursal\",\r\npurp.code_value as \"Loan Purpose\",\r\n lo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, datediff(l.expected_disbursedon_date, curdate()),  c.account_no','Individual Client Report',1,1),(6,'Loans Awaiting Disbursal Summary','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`','Individual Client Report',1,1),(7,'Loans Awaiting Disbursal Summary by Month','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as \"Year\", \r\nmonthname(l.expected_disbursedon_date) as \"Month\",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)','Individual Client Report',1,1),(8,'Loans Pending Approval','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client Name\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  pl.`name` as \"Product\", \r\nl.account_no as \"Loan Account No.\", \r\nl.principal_amount as \"Loan Amount\", \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\ndatediff(curdate(), l.submittedon_date) \"Days Pending Approval\", \r\npurp.code_value as \"Loan Purpose\",\r\nlo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 100 /*Submitted and awaiting approval */\r\norder by ounder.hierarchy, l.submittedon_date,  l.account_no','Individual Client Report',1,1),(11,'Active Loans - Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. loans_in_arrears_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(if(laa.loan_id is not null,  l.id, null)  )) as loans_in_arrears_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(laa.principal_overdue_derived) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(laa.interest_overdue_derived) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(laa.fee_charges_overdue_derived) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(laa.penalty_charges_overdue_derived) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(12,'Active Loans - Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\npenalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(13,'Obligation Met Loans Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client\",\r\nl.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.total_repayment_derived  as \"Total Repaid\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", \r\ndate(l.closedon_date) as \"Closed\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(14,'Obligation Met Loans Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as \"No. of Clients\",\r\ncount(distinct(l.id)) as \"No. of Loans\",\r\nsum(l.principal_amount) as \"Total Loan Amount\", \r\nsum(l.principal_repaid_derived) as \"Total Principal Repaid\",\r\nsum(l.interest_repaid_derived) as \"Total Interest Repaid\",\r\nsum(l.fee_charges_repaid_derived) as \"Total Fees Repaid\",\r\nsum(l.penalty_charges_repaid_derived) as \"Total Penalties Repaid\",\r\nsum(l.interest_waived_derived) as \"Total Interest Waived\",\r\nsum(l.fee_charges_waived_derived) as \"Total Fees Waived\",\r\nsum(l.penalty_charges_waived_derived) as \"Total Penalties Waived\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code','Individual Client \n\nReport',1,1),(15,'Portfolio at Risk','Table',NULL,'Loan','select x.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from \r\n(select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code) x','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(16,'Portfolio at Risk by Branch','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\",\r\nx.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select  ounder.id as \"branch\", ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\n\r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(20,'Funds Disbursed Between Dates Summary','Table',NULL,'Fund','select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, \r\nround(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(21,'Funds Disbursed Between Dates Summary by Office','Table',NULL,'Fund','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n \n\nifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\n\n\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c \n\non c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\n\n\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand \n\n(l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, \n\nl.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(48,'Balance Sheet','Pentaho',NULL,'Accounting',NULL,'Balance Sheet',1,1),(49,'Income Statement','Pentaho',NULL,'Accounting',NULL,'Profit and Loss Statement',1,1),(50,'Trial Balance','Pentaho',NULL,'Accounting',NULL,'Trial Balance Report',1,1),(51,'Written-Off Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Written-Off Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Written-Off Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Written-Off Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Written-Off Penalties\',\r\nn.note AS \'Reason For Write-Off\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 6 /*write-off */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=601\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\") \r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Written Off Loans',1,1),(52,'Aging Detail','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as \"Client Account No.\",\r\n 	mc.display_name AS \"Client Name\",\r\n 	ml.account_no AS \"Account Number\",\r\n 	ml.principal_amount AS \"Loan Amount\",\r\n ml.principal_disbursed_derived AS \"Original Principal\",\r\n ml.interest_charged_derived AS \"Original Interest\",\r\n ml.principal_repaid_derived AS \"Principal Paid\",\r\n ml.interest_repaid_derived AS \"Interest Paid\",\r\n laa.principal_overdue_derived AS \"Principal Overdue\",\r\n laa.interest_overdue_derived AS \"Interest Overdue\",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as \"Days in Arrears\",\r\n\r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS \"Weeks In Arrears Band\",\r\n\r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n				 \'> 360\'))))) AS \"Days in Arrears Band\"\r\n\r\n	FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n	        AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n	    INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n	    INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n	WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Loan arrears aging (Weeks)',1,1),(53,'Aging Summary (Arrears in Weeks)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Weeks In Arrears (Up To)\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'1\',2 UNION\r\n		SELECT \'2\',3 UNION\r\n		SELECT \'3\',4 UNION\r\n		SELECT \'4\',5 UNION\r\n		SELECT \'5\',6 UNION\r\n		SELECT \'6\',7 UNION\r\n		SELECT \'7\',8 UNION\r\n		SELECT \'8\',9 UNION\r\n		SELECT \'9\',10 UNION\r\n		SELECT \'10\',11 UNION\r\n		SELECT \'11\',12 UNION\r\n		SELECT \'12\',13 UNION\r\n		SELECT \'12+\',14) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<8, \'1\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<15, \'2\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<22, \'3\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<29, \'4\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<36, \'5\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<43, \'6\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<50, \'7\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<57, \'8\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<64, \'9\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<71, \'10\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<78, \'11\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<85, \'12\',\r\n				 \'12+\'))))))))))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(54,'Rescheduled Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Rescheduled Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Rescheduled Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Rescheduled Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Rescheduled Penalties\',\r\nn.note AS \'Reason For Rescheduling\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 7 /*marked for rescheduling */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=602\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Rescheduled Loans.  The ability to reschedule (or mark that you have rescheduled the loan elsewhere) is a legacy of the older Mifos product.  Needed for migration.',1,1),(55,'Active Loans Passed Final Maturity','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(56,'Active Loans Passed Final Maturity Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(57,'Active Loans in last installment','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(lastInstallment.`hierarchy`) - LENGTH(REPLACE(lastInstallment.`hierarchy`, \'.\', \'\')) - 1))), lastInstallment.branch) as \"Office/Branch\",\r\nlastInstallment.Currency,\r\nlastInstallment.`Loan Officer`, \r\nlastInstallment.`Client Account No`, lastInstallment.`Client`, \r\nlastInstallment.`Loan Account No`, lastInstallment.`Product`, \r\nlastInstallment.`Fund`,  lastInstallment.`Loan Amount`, \r\nlastInstallment.`Annual Nominal Interest Rate`, \r\nlastInstallment.`Disbursed`, lastInstallment.`Expected Matured On` ,\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\norder by lastInstallment.hierarchy, lastInstallment.Currency, lastInstallment.`Client Account No`, lastInstallment.`Loan Account No`','Individual Client \n\nReport',1,1),(58,'Active Loans in last installment Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select lastInstallment.branchId as branchId,\r\nlastInstallment.Currency,\r\ncount(distinct(lastInstallment.clientId)) as client_count, \r\ncount(distinct(lastInstallment.loanId)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id as branchId, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.id as clientId, c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\ngroup by lastInstallment.branchId) x on x.branchId = mo.id\r\norder by mo.hierarchy, x.Currency','Individual Client \n\nReport',1,1),(59,'Active Loans by Disbursal Period','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No\", c.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Principal Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\n\r\nl.total_expected_repayment_derived as \"Total Loan (P+I+F+Pen)\",\r\nl.total_repayment_derived as \"Total Repaid (P+I+F+Pen)\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(61,'Aging Summary (Arrears in Months)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Days In Arrears\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'0 - 30\',2 UNION\r\n		SELECT \'30 - 60\',3 UNION\r\n		SELECT \'60 - 90\',4 UNION\r\n		SELECT \'90 - 180\',5 UNION\r\n		SELECT \'180 - 360\',6 UNION\r\n		SELECT \'> 360\',7 ) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<361, \'180 - 360\', \r\n				 \'> 360\')))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(91,'Loan Account Schedule','Pentaho',NULL,'Loan',NULL,NULL,1,0),(92,'Branch Expected Cash Flow','Pentaho',NULL,'Loan',NULL,NULL,1,1),(93,'Expected Payments By Date - Basic','Table',NULL,'Loan','SELECT \r\n      ounder.name \'Office\', \r\n      IFNULL(ms.display_name,\'-\') \'Loan Officer\',\r\n	  mc.account_no \'Client Account Number\',\r\n	  mc.display_name \'Name\',\r\n	  mp.name \'Product\',\r\n	  ml.account_no \'Loan Account Number\',\r\n	  mr.duedate \'Due Date\',\r\n	  mr.installment \'Installment\',\r\n	  cu.display_symbol \'Currency\',\r\n	  mr.principal_amount- IFNULL(mr.principal_completed_derived,0) \'Principal Due\',\r\n	  mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0) \'Interest Due\', \r\n	  IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0) \'Fees Due\', \r\n	  IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0) \'Penalty Due\',\r\n      (mr.principal_amount- IFNULL(mr.principal_completed_derived,0)) +\r\n       (mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0)) + \r\n       (IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0)) + \r\n       (IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0)) \'Total Due\', \r\n     mlaa.total_overdue_derived \'Total Overdue\'\r\n										 \r\n FROM m_office mo\r\n  JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\n  \r\n  AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n	\r\n  LEFT JOIN m_client mc ON mc.office_id=ounder.id\r\n  LEFT JOIN m_loan ml ON ml.client_id=mc.id AND ml.loan_status_id=300\r\n  LEFT JOIN m_loan_arrears_aging mlaa ON mlaa.loan_id=ml.id\r\n  LEFT JOIN m_loan_repayment_schedule mr ON mr.loan_id=ml.id AND mr.completed_derived=0\r\n  LEFT JOIN m_product_loan mp ON mp.id=ml.product_id\r\n  LEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\n  LEFT JOIN m_currency cu ON cu.code=ml.currency_code\r\n WHERE mo.id=${officeId}\r\n AND (IFNULL(ml.loan_officer_id, -10) = \"${loanOfficerId}\" OR \"-1\" = \"${loanOfficerId}\")\r\n AND mr.duedate BETWEEN \'${startDate}\' AND \'${endDate}\'\r\n ORDER BY ounder.id,mr.duedate,ml.account_no','Test',1,1),(94,'Expected Payments By Date - Formatted','Pentaho',NULL,'Loan',NULL,NULL,1,1);
+/*!40000 ALTER TABLE `stretchy_report` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report_parameter` (
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`report_id`,`parameter_id`),
+  UNIQUE KEY `report_id_name_UNIQUE` (`report_id`,`report_parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report_parameter`
+--
+
+LOCK TABLES `stretchy_report_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_report_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_report_parameter` VALUES (1,5,NULL),(2,5,NULL),(2,6,NULL),(2,10,NULL),(2,20,NULL),(2,25,NULL),(2,26,NULL),(5,5,NULL),(5,6,NULL),(5,10,NULL),(5,20,NULL),(5,25,NULL),(5,26,NULL),(6,5,NULL),(6,6,NULL),(6,10,NULL),(6,20,NULL),(6,25,NULL),(6,26,NULL),(7,5,NULL),(7,6,NULL),(7,10,NULL),(7,20,NULL),(7,25,NULL),(7,26,NULL),(8,5,NULL),(8,6,NULL),(8,10,NULL),(8,25,NULL),(8,26,NULL),(11,5,NULL),(11,6,NULL),(11,10,NULL),(11,20,NULL),(11,25,NULL),(11,26,NULL),(11,100,NULL),(12,5,NULL),(12,6,NULL),(12,10,NULL),(12,20,NULL),(12,25,NULL),(12,26,NULL),(13,1,NULL),(13,2,NULL),(13,3,NULL),(13,5,NULL),(13,6,NULL),(13,10,NULL),(13,20,NULL),(13,25,NULL),(13,26,NULL),(14,1,NULL),(14,2,NULL),(14,3,NULL),(14,5,NULL),(14,6,NULL),(14,10,NULL),(14,20,NULL),(14,25,NULL),(14,26,NULL),(15,5,NULL),(15,6,NULL),(15,10,NULL),(15,20,NULL),(15,25,NULL),(15,26,NULL),(15,100,NULL),(16,5,NULL),(16,6,NULL),(16,10,NULL),(16,20,NULL),(16,25,NULL),(16,26,NULL),(16,100,NULL),(20,1,NULL),(20,2,NULL),(20,10,NULL),(20,20,NULL),(21,1,NULL),(21,2,NULL),(21,5,NULL),(21,10,NULL),(21,20,NULL),(48,5,'branch'),(48,2,'date'),(49,5,'branch'),(49,1,'fromDate'),(49,2,'toDate'),(50,5,'branch'),(50,1,'fromDate'),(50,2,'toDate'),(51,1,NULL),(51,2,NULL),(51,5,NULL),(51,10,NULL),(51,25,NULL),(52,5,NULL),(53,5,NULL),(53,10,NULL),(54,1,NULL),(54,2,NULL),(54,5,NULL),(54,10,NULL),(54,25,NULL),(55,5,NULL),(55,6,NULL),(55,10,NULL),(55,20,NULL),(55,25,NULL),(55,26,NULL),(56,5,NULL),(56,6,NULL),(56,10,NULL),(56,20,NULL),(56,25,NULL),(56,26,NULL),(56,100,NULL),(57,5,NULL),(57,6,NULL),(57,10,NULL),(57,20,NULL),(57,25,NULL),(57,26,NULL),(58,5,NULL),(58,6,NULL),(58,10,NULL),(58,20,NULL),(58,25,NULL),(58,26,NULL),(58,100,NULL),(59,1,NULL),(59,2,NULL),(59,5,NULL),(59,6,NULL),(59,10,NULL),(59,20,NULL),(59,25,NULL),(59,26,NULL),(61,5,NULL),(61,10,NULL),(92,1,'fromDate'),(92,5,'selectOffice'),(92,2,'toDate'),(93,1,NULL),(93,2,NULL),(93,5,NULL),(93,6,NULL),(94,2,'endDate'),(94,6,'loanOfficerId'),(94,5,'officeId'),(94,1,'startDate');
+/*!40000 ALTER TABLE `stretchy_report_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `x_registered_table`
+--
+
+DROP TABLE IF EXISTS `x_registered_table`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `x_registered_table`
+--
+
+LOCK TABLES `x_registered_table` WRITE;
+/*!40000 ALTER TABLE `x_registered_table` DISABLE KEYS */;
+INSERT INTO `x_registered_table` VALUES ('extra_client_details','m_client'),('extra_family_details','m_client'),('extra_loan_details','m_loan');
+/*!40000 ALTER TABLE `x_registered_table` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-03-25 18:17:29
diff --git a/fineract-db/multi-tenant-demo-backups/ceda/README.md b/fineract-db/multi-tenant-demo-backups/ceda/README.md
new file mode 100644
index 0000000..6424923
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/ceda/README.md
@@ -0,0 +1,9 @@
+CEDA Microfinance (Kampala, Uganda)
+======
+
+This demo database contains:
+
+- DDL of latest schema
+- Minimum reference data required for deployment of platform
+- Customisatons for CEDA on m_code/m_code_value tables, extra datatables for client and loan data capture
+- Extra reports over and beyond core reports customisation for CEDA operations
\ No newline at end of file
diff --git a/fineract-db/multi-tenant-demo-backups/ceda/bk_ceda_trial.sql b/fineract-db/multi-tenant-demo-backups/ceda/bk_ceda_trial.sql
new file mode 100644
index 0000000..6031508
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/ceda/bk_ceda_trial.sql
@@ -0,0 +1,1909 @@
+-- MySQL dump 10.13  Distrib 5.1.60, for Win32 (ia32)
+--
+-- Host: localhost    Database: mifostenant-ceda
+-- ------------------------------------------------------
+-- Server version	5.1.60-community
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `acc_gl_account`
+--
+
+DROP TABLE IF EXISTS `acc_gl_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=98 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_account`
+--
+
+LOCK TABLES `acc_gl_account` WRITE;
+/*!40000 ALTER TABLE `acc_gl_account` DISABLE KEYS */;
+INSERT INTO `acc_gl_account` VALUES (1,'Petty Cash Balances',NULL,'11100',0,1,2,1,NULL),(2,'Cash in Valut 1',NULL,'11101',0,1,1,1,NULL),(3,'Bank Balances',NULL,'11200',0,1,2,1,NULL),(4,'Centenary Opening Account',NULL,'11201',0,1,1,1,NULL),(5,'Centenary Expense Account',NULL,'11202',0,1,1,1,NULL),(6,'Centenary USD Account',NULL,'11203',0,1,1,1,NULL),(7,'Loans and Advances',NULL,'13100',0,1,2,1,NULL),(8,'Loans to Clients',NULL,'13101',0,1,1,1,NULL),(9,'Outstanding Interest',NULL,'13102',0,1,1,1,NULL),(10,'Outstanding Late Payment Interest',NULL,'13103',0,1,1,1,NULL),(11,'Outstanding Bank Fees to be collected',NULL,'13104',0,1,1,1,NULL),(12,'WriteOff Accounts',NULL,'13200',0,1,2,1,NULL),(13,'Write-offs (use for funds coming in)',NULL,'13201',0,1,1,1,NULL),(14,'Write-offs outstanding principal',NULL,'13202',0,1,1,1,NULL),(15,'Write-offs outstanding interest',NULL,'13203',0,1,1,1,NULL),(16,'Write-offs collected bank fees',NULL,'13204',0,1,1,1,NULL),(17,'Write-offs hardware/furniture',NULL,'13205',0,1,1,1,NULL),(18,'Fixed Assets',NULL,'14100',0,1,2,1,NULL),(19,'Office Equipment',NULL,'14101',0,1,1,1,NULL),(20,'Suspense Items (unidentified deposits)',NULL,'15000',0,1,2,1,NULL),(21,'Assets',NULL,'10000',0,1,2,1,NULL),(22,'Liabilities',NULL,'20000',0,1,2,2,NULL),(23,'Shares Account',NULL,'26100',0,1,2,2,NULL),(24,'Shares Captial',NULL,'26101',0,1,1,2,NULL),(25,'Donated Equity',NULL,'26300',0,1,2,2,NULL),(26,'Donated Equity Ameropa Foundation',NULL,'26301',0,1,1,2,NULL),(27,'Donated Equity e.h',NULL,'26302',0,1,1,2,NULL),(28,'Overpaid Amounts',NULL,'27000',0,1,2,2,NULL),(29,'Loss Provision',NULL,'28000',0,1,2,2,NULL),(30,'Provision Outstanding Principal',NULL,'28001',0,1,1,2,NULL),(31,'Provision Oustanding Interest',NULL,'28002',0,1,1,2,NULL),(32,'Income',NULL,'30000',0,1,2,4,NULL),(33,'Interest Income from Loans',NULL,'31100',0,1,2,4,NULL),(34,'Interest on Loans',NULL,'31101',0,1,1,4,NULL),(35,'Late Payment Interest',NULL,'31102',0,1,1,4,NULL),(36,'Income from Micro credit & Lending Activities',NULL,'31300',0,1,2,4,NULL),(37,'Collected Bank Fees Receivable',NULL,'6201',0,1,1,4,NULL),(38,'Deposits from Loans Write Off',NULL,'31400',0,1,2,4,NULL),(39,'Expenditure',NULL,'40000',0,1,2,5,NULL),(40,'Office Expenditure Account',NULL,'42100',0,1,2,5,NULL),(41,'Water Charges',NULL,'42102',0,1,1,5,NULL),(42,'Electricity Charges',NULL,'42103',0,1,1,5,NULL),(43,'Printing and Stationary',NULL,'42105',0,1,1,5,NULL),(44,'Office Rent',NULL,'42107',0,1,1,5,NULL),(45,'Marketing Expense',NULL,'42109',0,1,1,5,NULL),(46,'Office utilities',NULL,'42112',0,1,1,5,'(supplies, toiletries, kitchen)'),(47,'Furniture',NULL,'42113',0,1,1,5,NULL),(48,'CEDA Meeting Expense',NULL,'42114',0,1,1,5,NULL),(49,'Employee Personal Expsense Account',NULL,'42200',0,1,2,5,NULL),(50,'Salary Alice',NULL,'42201',0,1,1,5,NULL),(51,'Salary Irene',NULL,'42202',0,1,1,5,NULL),(52,'Salary Richard',NULL,'42203',0,1,1,5,NULL),(53,'Salary Loan Officer TBA',NULL,'42204',0,1,1,5,NULL),(54,'Medical Insurance Alice & Family',NULL,'42205',0,1,1,5,NULL),(55,'Medical Insurance Irene',NULL,'42206',0,1,1,5,NULL),(56,'Medical Insurance Richard',NULL,'42207',0,1,1,5,NULL),(57,'Medical Insurance Loan Officer TBA',NULL,'42208',0,1,1,5,NULL),(58,'PAYE all employees',NULL,'42209',0,1,1,5,NULL),(59,'NSSF all employees',NULL,'42210',0,1,1,5,NULL),(60,'Lunch Allowances all employees',NULL,'42211',0,1,1,5,NULL),(61,'IT software and maintenance',NULL,'42300',0,1,2,5,NULL),(62,'Mifos maintenance contract 12 months',NULL,'42301',0,1,1,5,NULL),(63,'VPS Contract 12 months',NULL,'42302',0,1,1,5,NULL),(64,'Bulk SMS Service',NULL,'42303',0,1,1,5,NULL),(65,'Support Accounting Software',NULL,'42304',0,1,1,5,NULL),(66,'Mifos Instance Setup',NULL,'42305',0,1,1,5,NULL),(67,'Misc support expense',NULL,'42306',0,1,1,5,NULL),(68,'Warid Mobile Line',NULL,'42307',0,1,1,5,NULL),(69,'Landline',NULL,'42308',0,1,1,5,NULL),(70,'Modem Alice',NULL,'42309',0,1,1,5,NULL),(71,'Modem Irene',NULL,'42310',0,1,1,5,NULL),(72,'Modem Richard',NULL,'42311',0,1,1,5,NULL),(73,'Repairs',NULL,'42312',0,1,1,5,NULL),(74,'Airtime Expenses',NULL,'42400',0,1,2,5,NULL),(75,'Airtime Alice',NULL,'42401',0,1,1,5,NULL),(76,'Airtime Richard',NULL,'42402',0,1,1,5,NULL),(77,'Airtime Loan Office TBA',NULL,'42403',0,1,1,5,NULL),(78,'Special Airtime Alice',NULL,'42404',0,1,1,5,NULL),(79,'Transportation',NULL,'42500',0,1,2,5,NULL),(80,'Flat monthly transportation cost',NULL,'42501',0,1,1,5,NULL),(81,'Faciliation cost for Richard',NULL,'42502',0,1,1,5,NULL),(82,'Faciliation cost for Loan Officer TBA',NULL,'42503',0,1,1,5,NULL),(83,'Consultancy Expenses',NULL,'42600',0,1,2,5,NULL),(84,'Audit Fees',NULL,'42601',0,1,1,5,NULL),(85,'Legal Fees',NULL,'42602',0,1,1,5,NULL),(86,'Miscellaneous Expenses Account',NULL,'42700',0,1,2,5,NULL),(87,'MFI License',NULL,'42703',0,1,1,5,NULL),(88,'Sundy Expenses',NULL,'42704',0,1,1,5,NULL),(89,'Bank Fees',NULL,'42800',0,1,2,5,NULL),(90,'Bank Charges Operating Account',NULL,'42801',0,1,1,5,NULL),(91,'Bank Charges Expense Account',NULL,'42802',0,1,1,5,NULL),(92,'E.H Account',NULL,'42900',0,1,2,5,NULL),(93,'Airtime',NULL,'42901',0,1,1,5,NULL),(94,'Modem',NULL,'42902',0,1,1,5,NULL),(95,'Meals',NULL,'42903',0,1,1,5,NULL),(96,'Transportation',NULL,'42904',0,1,1,5,NULL),(97,'Miscellaneous',NULL,'42905',0,1,1,5,NULL);
+/*!40000 ALTER TABLE `acc_gl_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_closure`
+--
+
+DROP TABLE IF EXISTS `acc_gl_closure`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_closure`
+--
+
+LOCK TABLES `acc_gl_closure` WRITE;
+/*!40000 ALTER TABLE `acc_gl_closure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_closure` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_journal_entry`
+--
+
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_entry` tinyint(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type_enum` smallint(5) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_journal_entry`
+--
+
+LOCK TABLES `acc_gl_journal_entry` WRITE;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_product_mapping`
+--
+
+DROP TABLE IF EXISTS `acc_product_mapping`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_product_mapping`
+--
+
+LOCK TABLES `acc_product_mapping` WRITE;
+/*!40000 ALTER TABLE `acc_product_mapping` DISABLE KEYS */;
+INSERT INTO `acc_product_mapping` VALUES (1,4,1,1,1),(2,8,1,1,2),(3,34,1,1,3),(4,37,1,1,4),(5,35,1,1,5),(6,97,1,1,6);
+/*!40000 ALTER TABLE `acc_product_mapping` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `c_configuration`
+--
+
+DROP TABLE IF EXISTS `c_configuration`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `c_configuration`
+--
+
+LOCK TABLES `c_configuration` WRITE;
+/*!40000 ALTER TABLE `c_configuration` DISABLE KEYS */;
+INSERT INTO `c_configuration` VALUES (1,'maker-checker',0);
+/*!40000 ALTER TABLE `c_configuration` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `client additional data`
+--
+
+DROP TABLE IF EXISTS `client additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `client additional data` (
+  `client_id` bigint(20) NOT NULL,
+  `Gender_cd` int(11) NOT NULL,
+  `Date of Birth` date NOT NULL,
+  `Home address` text NOT NULL,
+  `Telephone number` varchar(20) NOT NULL,
+  `Telephone number (2nd)` varchar(20) NOT NULL,
+  `Email address` varchar(50) NOT NULL,
+  `EducationLevel_cd` int(11) NOT NULL,
+  `MaritalStatus_cd` int(11) NOT NULL,
+  `Number of children` int(11) NOT NULL,
+  `Citizenship` varchar(50) NOT NULL,
+  `PovertyStatus_cd` int(11) NOT NULL,
+  `YesNo_cd_Employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of employment` int(11) DEFAULT NULL,
+  `Employer name` varchar(50) DEFAULT NULL,
+  `Number of years` int(11) DEFAULT NULL,
+  `Monthly salary` decimal(19,6) DEFAULT NULL,
+  `YesNo_cd_Self employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of self-employment` int(11) DEFAULT NULL,
+  `Business address` text,
+  `Number of employees` int(11) DEFAULT NULL,
+  `Monthly salaries paid` decimal(19,6) DEFAULT NULL,
+  `Monthly net income of business activity` decimal(19,6) DEFAULT NULL,
+  `Monthly rent` decimal(19,6) DEFAULT NULL,
+  `Other income generating activities` varchar(100) DEFAULT NULL,
+  `YesNo_cd_Bookkeeping` int(11) DEFAULT NULL,
+  `YesNo_cd_Loans with other institutions` int(11) NOT NULL,
+  `From whom` varchar(100) DEFAULT NULL,
+  `Amount` decimal(19,6) DEFAULT NULL,
+  `Interest rate pa` decimal(19,6) DEFAULT NULL,
+  `Number of people depending on overal income` int(11) NOT NULL,
+  `YesNo_cd_Bank account` int(11) NOT NULL,
+  `YesNo_cd_Business plan provided` int(11) NOT NULL,
+  `YesNo_cd_Access to internet` int(11) DEFAULT NULL,
+  `Introduced by` varchar(100) DEFAULT NULL,
+  `Known to introducer since` varchar(100) NOT NULL,
+  `Last visited by` varchar(100) DEFAULT NULL,
+  `Last visited on` date NOT NULL,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_client_additional_data` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `client additional data`
+--
+
+LOCK TABLES `client additional data` WRITE;
+/*!40000 ALTER TABLE `client additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `client additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `impact measurement`
+--
+
+DROP TABLE IF EXISTS `impact measurement`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `impact measurement` (
+  `loan_id` bigint(20) NOT NULL,
+  `YesNo_cd_RepaidOnSchedule` int(11) NOT NULL,
+  `ReasonNotRepaidOnSchedule` text,
+  `How was Loan Amount Invested` text NOT NULL,
+  `Additional Income Generated` decimal(19,6) NOT NULL,
+  `Additional Income Used For` text NOT NULL,
+  `YesNo_cd_NewJobsCreated` int(11) NOT NULL,
+  `Number of Jobs Created` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_impact measurement` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `impact measurement`
+--
+
+LOCK TABLES `impact measurement` WRITE;
+/*!40000 ALTER TABLE `impact measurement` DISABLE KEYS */;
+/*!40000 ALTER TABLE `impact measurement` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `loan additional data`
+--
+
+DROP TABLE IF EXISTS `loan additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `loan additional data` (
+  `loan_id` bigint(20) NOT NULL,
+  `PurposeOfLoan_cd` int(11) NOT NULL,
+  `CollateralType_cd` int(11) NOT NULL,
+  `Collateral notes` text NOT NULL,
+  `YesNo_cd_Guarantor` int(11) NOT NULL,
+  `Guarantor name` varchar(100) DEFAULT NULL,
+  `Guarantor relation` varchar(100) DEFAULT NULL,
+  `Guarantor address` varchar(100) DEFAULT NULL,
+  `Guarantor telephone number` varchar(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_loan_additional_data` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `loan additional data`
+--
+
+LOCK TABLES `loan additional data` WRITE;
+/*!40000 ALTER TABLE `loan additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `loan additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser`
+--
+
+DROP TABLE IF EXISTS `m_appuser`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser`
+--
+
+LOCK TABLES `m_appuser` WRITE;
+/*!40000 ALTER TABLE `m_appuser` DISABLE KEYS */;
+INSERT INTO `m_appuser` VALUES (1,0,1,'admin','App','Administrator','5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a','demomfi@mifos.org','\0','','','',''),(2,0,1,'keithwoodlock','Keith','Woodlock','4f607e9b6cffbe7d3db92d4bfa3391c7aa751727b4ea29d08fddf9dd72e6e7e3','keithwoodlock@gmail.com','\0','','','','');
+/*!40000 ALTER TABLE `m_appuser` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser_role`
+--
+
+DROP TABLE IF EXISTS `m_appuser_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser_role`
+--
+
+LOCK TABLES `m_appuser_role` WRITE;
+/*!40000 ALTER TABLE `m_appuser_role` DISABLE KEYS */;
+INSERT INTO `m_appuser_role` VALUES (1,1),(2,1);
+/*!40000 ALTER TABLE `m_appuser_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar`
+--
+
+DROP TABLE IF EXISTS `m_calendar`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `title` varchar(50) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar`
+--
+
+LOCK TABLES `m_calendar` WRITE;
+/*!40000 ALTER TABLE `m_calendar` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar_instance`
+--
+
+DROP TABLE IF EXISTS `m_calendar_instance`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar_instance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `entity_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar_instance`
+--
+
+LOCK TABLES `m_calendar_instance` WRITE;
+/*!40000 ALTER TABLE `m_calendar_instance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar_instance` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_charge`
+--
+
+DROP TABLE IF EXISTS `m_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_charge`
+--
+
+LOCK TABLES `m_charge` WRITE;
+/*!40000 ALTER TABLE `m_charge` DISABLE KEYS */;
+INSERT INTO `m_charge` VALUES (1,'Bank Fee (per installment)','UGX',1,2,1,'1500.000000',0,1,0);
+/*!40000 ALTER TABLE `m_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client`
+--
+
+DROP TABLE IF EXISTS `m_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `image_key` varchar(500) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client`
+--
+
+LOCK TABLES `m_client` WRITE;
+/*!40000 ALTER TABLE `m_client` DISABLE KEYS */;
+INSERT INTO `m_client` VALUES (1,'000000001',2,NULL,'Test',NULL,'One',NULL,'Test One',NULL,'2011-02-01',0);
+/*!40000 ALTER TABLE `m_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client_identifier`
+--
+
+DROP TABLE IF EXISTS `m_client_identifier`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client_identifier`
+--
+
+LOCK TABLES `m_client_identifier` WRITE;
+/*!40000 ALTER TABLE `m_client_identifier` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_identifier` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code`
+--
+
+DROP TABLE IF EXISTS `m_code`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code`
+--
+
+LOCK TABLES `m_code` WRITE;
+/*!40000 ALTER TABLE `m_code` DISABLE KEYS */;
+INSERT INTO `m_code` VALUES (1,'Customer Identifier',1),(2,'LoanCollateral',1),(3,'LoanPurpose',1),(4,'Gender',1),(5,'YesNo',1),(6,'GuarantorRelationship',1),(7,'FieldOfEmployment',0),(8,'EducationLevel',0),(9,'MaritalStatus',0),(10,'PovertyStatus',0);
+/*!40000 ALTER TABLE `m_code` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code_value`
+--
+
+DROP TABLE IF EXISTS `m_code_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code_value`
+--
+
+LOCK TABLES `m_code_value` WRITE;
+/*!40000 ALTER TABLE `m_code_value` DISABLE KEYS */;
+INSERT INTO `m_code_value` VALUES (1,1,'Passport',1),(2,1,'Id',2),(3,1,'Drivers License',3),(4,1,'Any Other Id Type',4),(5,6,'Spouse',0),(6,6,'Parent',0),(7,6,'Sibling',0),(8,6,'Business Associate',0),(9,6,'Other',0),(10,7,'option.Banker',1),(11,7,'option.SoftwareDeveloper',10),(12,8,'option.University',1),(13,8,'option.Secondary',12),(14,8,'option.Primary',13),(15,9,'option.Married',1),(16,9,'option.Single',15),(17,9,'option.Divorced',16),(18,9,'option.Widow',17),(19,10,'option.PovertyStatus.Band1',1),(20,10,'option.PovertyStatus.Band2',19),(21,10,'option.PovertyStatus.Band3',20),(22,2,'option.House',1),(23,2,'option.Television',22),(24,2,'option.Gold',23),(25,3,'option.Agriculture',1),(26,3,'option.Manufacturing',25),(27,3,'option.HousingImprovement',26),(28,4,'option.Male',1),(29,4,'option.Female',28),(30,5,'option.Yes',1),(31,5,'option.No',30);
+/*!40000 ALTER TABLE `m_code_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_currency`
+--
+
+DROP TABLE IF EXISTS `m_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_currency`
+--
+
+LOCK TABLES `m_currency` WRITE;
+/*!40000 ALTER TABLE `m_currency` DISABLE KEYS */;
+INSERT INTO `m_currency` VALUES (1,'AED',2,NULL,'UAE Dirham','currency.AED'),(2,'AFN',2,NULL,'Afghanistan Afghani','currency.AFN'),(3,'ALL',2,NULL,'Albanian Lek','currency.ALL'),(4,'AMD',2,NULL,'Armenian Dram','currency.AMD'),(5,'ANG',2,NULL,'Netherlands Antillian Guilder','currency.ANG'),(6,'AOA',2,NULL,'Angolan Kwanza','currency.AOA'),(7,'ARS',2,'$','Argentine Peso','currency.ARS'),(8,'AUD',2,'A$','Australian Dollar','currency.AUD'),(9,'AWG',2,NULL,'Aruban Guilder','currency.AWG'),(10,'AZM',2,NULL,'Azerbaijanian Manat','currency.AZM'),(11,'BAM',2,NULL,'Bosnia and Herzegovina Convertible Marks','currency.BAM'),(12,'BBD',2,NULL,'Barbados Dollar','currency.BBD'),(13,'BDT',2,NULL,'Bangladesh Taka','currency.BDT'),(14,'BGN',2,NULL,'Bulgarian Lev','currency.BGN'),(15,'BHD',3,NULL,'Bahraini Dinar','currency.BHD'),(16,'BIF',0,NULL,'Burundi Franc','currency.BIF'),(17,'BMD',2,NULL,'Bermudian Dollar','currency.BMD'),(18,'BND',2,'B$','Brunei Dollar','currency.BND'),(19,'BOB',2,'Bs.','Bolivian Boliviano','currency.BOB'),(20,'BRL',2,'R$','Brazilian Real','currency.BRL'),(21,'BSD',2,NULL,'Bahamian Dollar','currency.BSD'),(22,'BTN',2,NULL,'Bhutan Ngultrum','currency.BTN'),(23,'BWP',2,NULL,'Botswana Pula','currency.BWP'),(24,'BYR',0,NULL,'Belarussian Ruble','currency.BYR'),(25,'BZD',2,'BZ$','Belize Dollar','currency.BZD'),(26,'CAD',2,NULL,'Canadian Dollar','currency.CAD'),(27,'CDF',2,NULL,'Franc Congolais','currency.CDF'),(28,'CHF',2,NULL,'Swiss Franc','currency.CHF'),(29,'CLP',0,'$','Chilean Peso','currency.CLP'),(30,'CNY',2,NULL,'Chinese Yuan Renminbi','currency.CNY'),(31,'COP',2,'$','Colombian Peso','currency.COP'),(32,'CRC',2,'₡','Costa Rican Colon','currency.CRC'),(33,'CSD',2,NULL,'Serbian Dinar','currency.CSD'),(34,'CUP',2,'$MN','Cuban Peso','currency.CUP'),(35,'CVE',2,NULL,'Cape Verde Escudo','currency.CVE'),(36,'CYP',2,NULL,'Cyprus Pound','currency.CYP'),(37,'CZK',2,NULL,'Czech Koruna','currency.CZK'),(38,'DJF',0,NULL,'Djibouti Franc','currency.DJF'),(39,'DKK',2,NULL,'Danish Krone','currency.DKK'),(40,'DOP',2,'RD$','Dominican Peso','currency.DOP'),(41,'DZD',2,NULL,'Algerian Dinar','currency.DZD'),(42,'EEK',2,NULL,'Estonian Kroon','currency.EEK'),(43,'EGP',2,NULL,'Egyptian Pound','currency.EGP'),(44,'ERN',2,NULL,'Eritrea Nafka','currency.ERN'),(45,'ETB',2,NULL,'Ethiopian Birr','currency.ETB'),(46,'EUR',2,'€','Euro','currency.EUR'),(47,'FJD',2,NULL,'Fiji Dollar','currency.FJD'),(48,'FKP',2,NULL,'Falkland Islands Pound','currency.FKP'),(49,'GBP',2,NULL,'Pound Sterling','currency.GBP'),(50,'GEL',2,NULL,'Georgian Lari','currency.GEL'),(51,'GHC',2,'GHc','Ghana Cedi','currency.GHC'),(52,'GIP',2,NULL,'Gibraltar Pound','currency.GIP'),(53,'GMD',2,NULL,'Gambian Dalasi','currency.GMD'),(54,'GNF',0,NULL,'Guinea Franc','currency.GNF'),(55,'GTQ',2,'Q','Guatemala Quetzal','currency.GTQ'),(56,'GYD',2,NULL,'Guyana Dollar','currency.GYD'),(57,'HKD',2,NULL,'Hong Kong Dollar','currency.HKD'),(58,'HNL',2,'L','Honduras Lempira','currency.HNL'),(59,'HRK',2,NULL,'Croatian Kuna','currency.HRK'),(60,'HTG',2,'G','Haiti Gourde','currency.HTG'),(61,'HUF',2,NULL,'Hungarian Forint','currency.HUF'),(62,'IDR',2,NULL,'Indonesian Rupiah','currency.IDR'),(63,'ILS',2,NULL,'New Israeli Shekel','currency.ILS'),(64,'INR',2,'₹','Indian Rupee','currency.INR'),(65,'IQD',3,NULL,'Iraqi Dinar','currency.IQD'),(66,'IRR',2,NULL,'Iranian Rial','currency.IRR'),(67,'ISK',0,NULL,'Iceland Krona','currency.ISK'),(68,'JMD',2,NULL,'Jamaican Dollar','currency.JMD'),(69,'JOD',3,NULL,'Jordanian Dinar','currency.JOD'),(70,'JPY',0,NULL,'Japanese Yen','currency.JPY'),(71,'KES',2,'KSh','Kenyan Shilling','currency.KES'),(72,'KGS',2,NULL,'Kyrgyzstan Som','currency.KGS'),(73,'KHR',2,NULL,'Cambodia Riel','currency.KHR'),(74,'KMF',0,NULL,'Comoro Franc','currency.KMF'),(75,'KPW',2,NULL,'North Korean Won','currency.KPW'),(76,'KRW',0,NULL,'Korean Won','currency.KRW'),(77,'KWD',3,NULL,'Kuwaiti Dinar','currency.KWD'),(78,'KYD',2,NULL,'Cayman Islands Dollar','currency.KYD'),(79,'KZT',2,NULL,'Kazakhstan Tenge','currency.KZT'),(80,'LAK',2,NULL,'Lao Kip','currency.LAK'),(81,'LBP',2,'L£','Lebanese Pound','currency.LBP'),(82,'LKR',2,NULL,'Sri Lanka Rupee','currency.LKR'),(83,'LRD',2,NULL,'Liberian Dollar','currency.LRD'),(84,'LSL',2,NULL,'Lesotho Loti','currency.LSL'),(85,'LTL',2,NULL,'Lithuanian Litas','currency.LTL'),(86,'LVL',2,NULL,'Latvian Lats','currency.LVL'),(87,'LYD',3,NULL,'Libyan Dinar','currency.LYD'),(88,'MAD',2,NULL,'Moroccan Dirham','currency.MAD'),(89,'MDL',2,NULL,'Moldovan Leu','currency.MDL'),(90,'MGA',2,NULL,'Malagasy Ariary','currency.MGA'),(91,'MKD',2,NULL,'Macedonian Denar','currency.MKD'),(92,'MMK',2,'K','Myanmar Kyat','currency.MMK'),(93,'MNT',2,NULL,'Mongolian Tugrik','currency.MNT'),(94,'MOP',2,NULL,'Macau Pataca','currency.MOP'),(95,'MRO',2,NULL,'Mauritania Ouguiya','currency.MRO'),(96,'MTL',2,NULL,'Maltese Lira','currency.MTL'),(97,'MUR',2,NULL,'Mauritius Rupee','currency.MUR'),(98,'MVR',2,NULL,'Maldives Rufiyaa','currency.MVR'),(99,'MWK',2,NULL,'Malawi Kwacha','currency.MWK'),(100,'MXN',2,'$','Mexican Peso','currency.MXN'),(101,'MYR',2,NULL,'Malaysian Ringgit','currency.MYR'),(102,'MZM',2,NULL,'Mozambique Metical','currency.MZM'),(103,'NAD',2,NULL,'Namibia Dollar','currency.NAD'),(104,'NGN',2,NULL,'Nigerian Naira','currency.NGN'),(105,'NIO',2,'C$','Nicaragua Cordoba Oro','currency.NIO'),(106,'NOK',2,NULL,'Norwegian Krone','currency.NOK'),(107,'NPR',2,NULL,'Nepalese Rupee','currency.NPR'),(108,'NZD',2,NULL,'New Zealand Dollar','currency.NZD'),(109,'OMR',3,NULL,'Rial Omani','currency.OMR'),(110,'PAB',2,'B/.','Panama Balboa','currency.PAB'),(111,'PEN',2,'S/.','Peruvian Nuevo Sol','currency.PEN'),(112,'PGK',2,NULL,'Papua New Guinea Kina','currency.PGK'),(113,'PHP',2,NULL,'Philippine Peso','currency.PHP'),(114,'PKR',2,NULL,'Pakistan Rupee','currency.PKR'),(115,'PLN',2,NULL,'Polish Zloty','currency.PLN'),(116,'PYG',0,'₲','Paraguayan Guarani','currency.PYG'),(117,'QAR',2,NULL,'Qatari Rial','currency.QAR'),(118,'RON',2,NULL,'Romanian Leu','currency.RON'),(119,'RUB',2,NULL,'Russian Ruble','currency.RUB'),(120,'RWF',0,NULL,'Rwanda Franc','currency.RWF'),(121,'SAR',2,NULL,'Saudi Riyal','currency.SAR'),(122,'SBD',2,NULL,'Solomon Islands Dollar','currency.SBD'),(123,'SCR',2,NULL,'Seychelles Rupee','currency.SCR'),(124,'SDD',2,NULL,'Sudanese Dinar','currency.SDD'),(125,'SEK',2,NULL,'Swedish Krona','currency.SEK'),(126,'SGD',2,NULL,'Singapore Dollar','currency.SGD'),(127,'SHP',2,NULL,'St Helena Pound','currency.SHP'),(128,'SIT',2,NULL,'Slovenian Tolar','currency.SIT'),(129,'SKK',2,NULL,'Slovak Koruna','currency.SKK'),(130,'SLL',2,NULL,'Sierra Leone Leone','currency.SLL'),(131,'SOS',2,NULL,'Somali Shilling','currency.SOS'),(132,'SRD',2,NULL,'Surinam Dollar','currency.SRD'),(133,'STD',2,NULL,'Sao Tome and Principe Dobra','currency.STD'),(134,'SVC',2,NULL,'El Salvador Colon','currency.SVC'),(135,'SYP',2,NULL,'Syrian Pound','currency.SYP'),(136,'SZL',2,NULL,'Swaziland Lilangeni','currency.SZL'),(137,'THB',2,NULL,'Thai Baht','currency.THB'),(138,'TJS',2,NULL,'Tajik Somoni','currency.TJS'),(139,'TMM',2,NULL,'Turkmenistan Manat','currency.TMM'),(140,'TND',3,'DT','Tunisian Dinar','currency.TND'),(141,'TOP',2,NULL,'Tonga Pa\'anga','currency.TOP'),(142,'TRY',2,NULL,'Turkish Lira','currency.TRY'),(143,'TTD',2,NULL,'Trinidad and Tobago Dollar','currency.TTD'),(144,'TWD',2,NULL,'New Taiwan Dollar','currency.TWD'),(145,'TZS',2,NULL,'Tanzanian Shilling','currency.TZS'),(146,'UAH',2,NULL,'Ukraine Hryvnia','currency.UAH'),(147,'UGX',2,'USh','Uganda Shilling','currency.UGX'),(148,'USD',2,'$','US Dollar','currency.USD'),(149,'UYU',2,'$U','Peso Uruguayo','currency.UYU'),(150,'UZS',2,NULL,'Uzbekistan Sum','currency.UZS'),(151,'VEB',2,'Bs.F.','Venezuelan Bolivar','currency.VEB'),(152,'VND',2,NULL,'Vietnamese Dong','currency.VND'),(153,'VUV',0,NULL,'Vanuatu Vatu','currency.VUV'),(154,'WST',2,NULL,'Samoa Tala','currency.WST'),(155,'XAF',0,NULL,'CFA Franc BEAC','currency.XAF'),(156,'XCD',2,NULL,'East Caribbean Dollar','currency.XCD'),(157,'XDR',5,NULL,'SDR (Special Drawing Rights)','currency.XDR'),(158,'XOF',0,'CFA','CFA Franc BCEAO','currency.XOF'),(159,'XPF',0,NULL,'CFP Franc','currency.XPF'),(160,'YER',2,NULL,'Yemeni Rial','currency.YER'),(161,'ZAR',2,'R','South African Rand','currency.ZAR'),(162,'ZMK',2,NULL,'Zambian Kwacha','currency.ZMK'),(163,'ZWD',2,NULL,'Zimbabwe Dollar','currency.ZWD');
+/*!40000 ALTER TABLE `m_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_document`
+--
+
+DROP TABLE IF EXISTS `m_document`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(50) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_document`
+--
+
+LOCK TABLES `m_document` WRITE;
+/*!40000 ALTER TABLE `m_document` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_document` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_fund`
+--
+
+DROP TABLE IF EXISTS `m_fund`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_fund`
+--
+
+LOCK TABLES `m_fund` WRITE;
+/*!40000 ALTER TABLE `m_fund` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_fund` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group`
+--
+
+DROP TABLE IF EXISTS `m_group`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `level_Id` int(11) NOT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`,`level_Id`),
+  UNIQUE KEY `external_id` (`external_id`,`level_Id`),
+  KEY `office_id` (`office_id`),
+  KEY `staff_id` (`staff_id`),
+  KEY `Parent_Id_reference` (`parent_id`),
+  KEY `FK_m_group_level` (`level_Id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_Id`) REFERENCES `m_group_level` (`id`),
+  CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group`
+--
+
+LOCK TABLES `m_group` WRITE;
+/*!40000 ALTER TABLE `m_group` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_client`
+--
+
+DROP TABLE IF EXISTS `m_group_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_client`
+--
+
+LOCK TABLES `m_group_client` WRITE;
+/*!40000 ALTER TABLE `m_group_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_level`
+--
+
+DROP TABLE IF EXISTS `m_group_level`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_level` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) DEFAULT NULL,
+  `super_parent` tinyint(1) NOT NULL,
+  `level_name` varchar(100) NOT NULL,
+  `recursable` tinyint(1) NOT NULL,
+  `can_have_clients` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `Parent_levelId_reference` (`parent_id`),
+  CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_level`
+--
+
+LOCK TABLES `m_group_level` WRITE;
+/*!40000 ALTER TABLE `m_group_level` DISABLE KEYS */;
+INSERT INTO `m_group_level` VALUES (1,NULL,1,'Center',1,0),(2,1,0,'Group',0,1);
+/*!40000 ALTER TABLE `m_group_level` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_guarantor`
+--
+
+DROP TABLE IF EXISTS `m_guarantor`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_guarantor` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `client_reln_cv_id` int(11) DEFAULT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `dob` date DEFAULT NULL,
+  `address_line_1` varchar(500) DEFAULT NULL,
+  `address_line_2` varchar(500) DEFAULT NULL,
+  `city` varchar(50) DEFAULT NULL,
+  `state` varchar(50) DEFAULT NULL,
+  `country` varchar(50) DEFAULT NULL,
+  `zip` varchar(20) DEFAULT NULL,
+  `house_phone_number` varchar(20) DEFAULT NULL,
+  `mobile_number` varchar(20) DEFAULT NULL,
+  `comment` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_m_loan` (`loan_id`),
+  KEY `FK_m_guarantor_m_code_value` (`client_reln_cv_id`),
+  CONSTRAINT `FK_m_guarantor_m_code_value` FOREIGN KEY (`client_reln_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_guarantor`
+--
+
+LOCK TABLES `m_guarantor` WRITE;
+/*!40000 ALTER TABLE `m_guarantor` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan`
+--
+
+DROP TABLE IF EXISTS `m_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `loanpurpose_cv_id` int(11) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `min_principal_amount` decimal(19,6) NOT NULL,
+  `max_principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `disbursedon_userid` bigint(20) DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `principal_disbursed_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `rescheduledon_date` date DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `writtenoffon_date` date DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loanpurpose_codevalue` (`loanpurpose_cv_id`),
+  KEY `FK_submittedon_userid` (`submittedon_userid`),
+  KEY `FK_approvedon_userid` (`approvedon_userid`),
+  KEY `FK_rejectedon_userid` (`rejectedon_userid`),
+  KEY `FK_withdrawnon_userid` (`withdrawnon_userid`),
+  KEY `FK_disbursedon_userid` (`disbursedon_userid`),
+  KEY `FK_closedon_userid` (`closedon_userid`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_approvedon_userid` FOREIGN KEY (`approvedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_closedon_userid` FOREIGN KEY (`closedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_disbursedon_userid` FOREIGN KEY (`disbursedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loanpurpose_codevalue` FOREIGN KEY (`loanpurpose_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_rejectedon_userid` FOREIGN KEY (`rejectedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_submittedon_userid` FOREIGN KEY (`submittedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_withdrawnon_userid` FOREIGN KEY (`withdrawnon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan`
+--
+
+LOCK TABLES `m_loan` WRITE;
+/*!40000 ALTER TABLE `m_loan` DISABLE KEYS */;
+INSERT INTO `m_loan` VALUES (1,'000000001',NULL,1,NULL,1,NULL,2,25,200,'UGX',2,'1000000.000000','0.000000','1000000000000.000000',NULL,'24.000000',3,'24.000000',1,1,12,2,1,2,12,1,'2011-04-01',1,'2011-04-01',1,'2011-04-01',NULL,NULL,NULL,NULL,'2012-04-01','2012-04-01',NULL,NULL,'0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000',NULL,NULL,NULL,NULL,NULL,NULL,2);
+/*!40000 ALTER TABLE `m_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_arrears_aging`
+--
+
+DROP TABLE IF EXISTS `m_loan_arrears_aging`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_arrears_aging` (
+  `loan_id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `principal_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `overdue_since_date_derived` date DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_arrears_aging_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_arrears_aging`
+--
+
+LOCK TABLES `m_loan_arrears_aging` WRITE;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_charge`
+--
+
+LOCK TABLES `m_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_loan_charge` DISABLE KEYS */;
+INSERT INTO `m_loan_charge` VALUES (1,1,1,0,2,'2011-06-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(2,1,1,0,2,'2011-12-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(3,1,1,0,2,'2011-09-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(4,1,1,0,2,'2011-08-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(5,1,1,0,2,'2011-11-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(6,1,1,0,2,'2011-05-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(7,1,1,0,2,'2012-03-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(8,1,1,0,2,'2012-02-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(9,1,1,0,2,'2012-04-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(10,1,1,0,2,'2012-01-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(11,1,1,0,2,'2011-10-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(12,1,1,0,2,'2011-07-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0);
+/*!40000 ALTER TABLE `m_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_collateral`
+--
+
+DROP TABLE IF EXISTS `m_loan_collateral`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_collateral` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_cv_id` int(11) NOT NULL,
+  `value` decimal(19,6) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_collateral_m_loan` (`loan_id`),
+  KEY `FK_collateral_code_value` (`type_cv_id`),
+  CONSTRAINT `FK_collateral_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_collateral_code_value` FOREIGN KEY (`type_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_collateral`
+--
+
+LOCK TABLES `m_loan_collateral` WRITE;
+/*!40000 ALTER TABLE `m_loan_collateral` DISABLE KEYS */;
+INSERT INTO `m_loan_collateral` VALUES (1,1,23,NULL,' small description.');
+/*!40000 ALTER TABLE `m_loan_collateral` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_officer_assignment_history`
+--
+
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_officer_assignment_history`
+--
+
+LOCK TABLES `m_loan_officer_assignment_history` WRITE;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` DISABLE KEYS */;
+INSERT INTO `m_loan_officer_assignment_history` VALUES (1,1,2,'2011-04-01',NULL,1,'2013-04-05 12:42:09','2013-04-05 12:42:09',1);
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_repayment_schedule`
+--
+
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_repayment_schedule`
+--
+
+LOCK TABLES `m_loan_repayment_schedule` WRITE;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` DISABLE KEYS */;
+INSERT INTO `m_loan_repayment_schedule` VALUES (1,1,'2011-04-01','2011-05-01',1,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(2,1,'2011-05-01','2011-06-01',2,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(3,1,'2011-06-01','2011-07-01',3,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(4,1,'2011-07-01','2011-08-01',4,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(5,1,'2011-08-01','2011-09-01',5,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(6,1,'2011-09-01','2011-10-01',6,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(7,1,'2011-10-01','2011-11-01',7,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(8,1,'2011-11-01','2011-12-01',8,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(9,1,'2011-12-01','2012-01-01',9,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(10,1,'2012-01-01','2012-02-01',10,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(11,1,'2012-02-01','2012-03-01',11,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(12,1,'2012-03-01','2012-04-01',12,'83333.370000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL);
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_transaction`
+--
+
+DROP TABLE IF EXISTS `m_loan_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_transaction`
+--
+
+LOCK TABLES `m_loan_transaction` WRITE;
+/*!40000 ALTER TABLE `m_loan_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_note`
+--
+
+DROP TABLE IF EXISTS `m_note`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK_m_note_m_group` (`group_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_note_m_group` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_note`
+--
+
+LOCK TABLES `m_note` WRITE;
+/*!40000 ALTER TABLE `m_note` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_note` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office`
+--
+
+DROP TABLE IF EXISTS `m_office`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office`
+--
+
+LOCK TABLES `m_office` WRITE;
+/*!40000 ALTER TABLE `m_office` DISABLE KEYS */;
+INSERT INTO `m_office` VALUES (1,NULL,'.','1','CEDA Microfinance Ltd.','2009-01-01'),(2,1,'.2.','2','Uganda (Kampala)','2009-01-01');
+/*!40000 ALTER TABLE `m_office` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office_transaction`
+--
+
+DROP TABLE IF EXISTS `m_office_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office_transaction`
+--
+
+LOCK TABLES `m_office_transaction` WRITE;
+/*!40000 ALTER TABLE `m_office_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_office_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_organisation_currency`
+--
+
+DROP TABLE IF EXISTS `m_organisation_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_organisation_currency`
+--
+
+LOCK TABLES `m_organisation_currency` WRITE;
+/*!40000 ALTER TABLE `m_organisation_currency` DISABLE KEYS */;
+INSERT INTO `m_organisation_currency` VALUES (22,'UGX',2,'Uganda Shilling','USh','currency.UGX');
+/*!40000 ALTER TABLE `m_organisation_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_permission`
+--
+
+DROP TABLE IF EXISTS `m_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=308 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_permission`
+--
+
+LOCK TABLES `m_permission` WRITE;
+/*!40000 ALTER TABLE `m_permission` DISABLE KEYS */;
+INSERT INTO `m_permission` VALUES (1,'special','ALL_FUNCTIONS',NULL,NULL,0),(2,'special','ALL_FUNCTIONS_READ',NULL,NULL,0),(3,'special','CHECKER_SUPER_USER',NULL,NULL,0),(4,'special','REPORTING_SUPER_USER',NULL,NULL,0),(5,'authorisation','READ_PERMISSION','PERMISSION','READ',0),(6,'authorisation','PERMISSIONS_ROLE','ROLE','PERMISSIONS',0),(7,'authorisation','CREATE_ROLE','ROLE','CREATE',0),(8,'authorisation','CREATE_ROLE_CHECKER','ROLE','CREATE',0),(9,'authorisation','READ_ROLE','ROLE','READ',0),(10,'authorisation','UPDATE_ROLE','ROLE','UPDATE',0),(11,'authorisation','UPDATE_ROLE_CHECKER','ROLE','UPDATE',0),(12,'authorisation','DELETE_ROLE','ROLE','DELETE',0),(13,'authorisation','DELETE_ROLE_CHECKER','ROLE','DELETE',0),(14,'authorisation','CREATE_USER','USER','CREATE',0),(15,'authorisation','CREATE_USER_CHECKER','USER','CREATE',0),(16,'authorisation','READ_USER','USER','READ',0),(17,'authorisation','UPDATE_USER','USER','UPDATE',0),(18,'authorisation','UPDATE_USER_CHECKER','USER','UPDATE',0),(19,'authorisation','DELETE_USER','USER','DELETE',0),(20,'authorisation','DELETE_USER_CHECKER','USER','DELETE',0),(21,'configuration','READ_CONFIGURATION','CONFIGURATION','READ',0),(22,'configuration','UPDATE_CONFIGURATION','CONFIGURATION','UPDATE',0),(23,'configuration','UPDATE_CONFIGURATION_CHECKER','CONFIGURATION','UPDATE',0),(24,'configuration','READ_CODE','CODE','READ',0),(25,'configuration','CREATE_CODE','CODE','CREATE',0),(26,'configuration','CREATE_CODE_CHECKER','CODE','CREATE',0),(27,'configuration','UPDATE_CODE','CODE','UPDATE',0),(28,'configuration','UPDATE_CODE_CHECKER','CODE','UPDATE',0),(29,'configuration','DELETE_CODE','CODE','DELETE',0),(30,'configuration','DELETE_CODE_CHECKER','CODE','DELETE',0),(31,'configuration','READ_CODEVALUE','CODEVALUE','READ',0),(32,'configuration','CREATE_CODEVALUE','CODEVALUE','CREATE',0),(33,'configuration','CREATE_CODEVALUE_CHECKER','CODEVALUE','CREATE',0),(34,'configuration','UPDATE_CODEVALUE','CODEVALUE','UPDATE',0),(35,'configuration','UPDATE_CODEVALUE_CHECKER','CODEVALUE','UPDATE',0),(36,'configuration','DELETE_CODEVALUE','CODEVALUE','DELETE',0),(37,'configuration','DELETE_CODEVALUE_CHECKER','CODEVALUE','DELETE',0),(38,'configuration','READ_CURRENCY','CURRENCY','READ',0),(39,'configuration','UPDATE_CURRENCY','CURRENCY','UPDATE',0),(40,'configuration','UPDATE_CURRENCY_CHECKER','CURRENCY','UPDATE',0),(41,'configuration','UPDATE_PERMISSION','PERMISSION','UPDATE',0),(42,'configuration','UPDATE_PERMISSION_CHECKER','PERMISSION','UPDATE',0),(43,'configuration','READ_DATATABLE','DATATABLE','READ',0),(44,'configuration','REGISTER_DATATABLE','DATATABLE','REGISTER',0),(45,'configuration','REGISTER_DATATABLE_CHECKER','DATATABLE','REGISTER',0),(46,'configuration','DEREGISTER_DATATABLE','DATATABLE','DEREGISTER',0),(47,'configuration','DEREGISTER_DATATABLE_CHECKER','DATATABLE','DEREGISTER',0),(48,'configuration','READ_AUDIT','AUDIT','READ',0),(49,'configuration','CREATE_CALENDAR','CALENDAR','CREATE',0),(50,'configuration','READ_CALENDAR','CALENDAR','READ',0),(51,'configuration','UPDATE_CALENDAR','CALENDAR','UPDATE',0),(52,'configuration','DELETE_CALENDAR','CALENDAR','DELETE',0),(53,'configuration','CREATE_CALENDAR_CHECKER','CALENDAR','CREATE',0),(54,'configuration','UPDATE_CALENDAR_CHECKER','CALENDAR','UPDATE',0),(55,'configuration','DELETE_CALENDAR_CHECKER','CALENDAR','DELETE',0),(57,'organisation','READ_CHARGE','CHARGE','READ',0),(58,'organisation','CREATE_CHARGE','CHARGE','CREATE',0),(59,'organisation','CREATE_CHARGE_CHECKER','CHARGE','CREATE',0),(60,'organisation','UPDATE_CHARGE','CHARGE','UPDATE',0),(61,'organisation','UPDATE_CHARGE_CHECKER','CHARGE','UPDATE',0),(62,'organisation','DELETE_CHARGE','CHARGE','DELETE',0),(63,'organisation','DELETE_CHARGE_CHECKER','CHARGE','DELETE',0),(64,'organisation','READ_FUND','FUND','READ',0),(65,'organisation','CREATE_FUND','FUND','CREATE',0),(66,'organisation','CREATE_FUND_CHECKER','FUND','CREATE',0),(67,'organisation','UPDATE_FUND','FUND','UPDATE',0),(68,'organisation','UPDATE_FUND_CHECKER','FUND','UPDATE',0),(69,'organisation','DELETE_FUND','FUND','DELETE',0),(70,'organisation','DELETE_FUND_CHECKER','FUND','DELETE',0),(71,'organisation','READ_LOANPRODUCT','LOANPRODUCT','READ',0),(72,'organisation','CREATE_LOANPRODUCT','LOANPRODUCT','CREATE',0),(73,'organisation','CREATE_LOANPRODUCT_CHECKER','LOANPRODUCT','CREATE',0),(74,'organisation','UPDATE_LOANPRODUCT','LOANPRODUCT','UPDATE',0),(75,'organisation','UPDATE_LOANPRODUCT_CHECKER','LOANPRODUCT','UPDATE',0),(76,'organisation','DELETE_LOANPRODUCT','LOANPRODUCT','DELETE',0),(77,'organisation','DELETE_LOANPRODUCT_CHECKER','LOANPRODUCT','DELETE',0),(78,'organisation','READ_OFFICE','OFFICE','READ',0),(79,'organisation','CREATE_OFFICE','OFFICE','CREATE',0),(80,'organisation','CREATE_OFFICE_CHECKER','OFFICE','CREATE',0),(81,'organisation','UPDATE_OFFICE','OFFICE','UPDATE',0),(82,'organisation','UPDATE_OFFICE_CHECKER','OFFICE','UPDATE',0),(83,'organisation','READ_OFFICETRANSACTION','OFFICETRANSACTION','READ',0),(84,'organisation','DELETE_OFFICE_CHECKER','OFFICE','DELETE',0),(85,'organisation','CREATE_OFFICETRANSACTION','OFFICETRANSACTION','CREATE',0),(86,'organisation','CREATE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','CREATE',0),(87,'organisation','DELETE_OFFICETRANSACTION','OFFICETRANSACTION','DELETE',0),(88,'organisation','DELETE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','DELETE',0),(89,'organisation','READ_STAFF','STAFF','READ',0),(90,'organisation','CREATE_STAFF','STAFF','CREATE',0),(91,'organisation','CREATE_STAFF_CHECKER','STAFF','CREATE',0),(92,'organisation','UPDATE_STAFF','STAFF','UPDATE',0),(93,'organisation','UPDATE_STAFF_CHECKER','STAFF','UPDATE',0),(94,'organisation','DELETE_STAFF','STAFF','DELETE',0),(95,'organisation','DELETE_STAFF_CHECKER','STAFF','DELETE',0),(96,'organisation','READ_SAVINGSPRODUCT','SAVINGSPRODUCT','READ',0),(97,'organisation','CREATE_SAVINGSPRODUCT','SAVINGSPRODUCT','CREATE',0),(98,'organisation','CREATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','CREATE',0),(99,'organisation','UPDATE_SAVINGSPRODUCT','SAVINGSPRODUCT','UPDATE',0),(100,'organisation','UPDATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','UPDATE',0),(101,'organisation','DELETE_SAVINGSPRODUCT','SAVINGSPRODUCT','DELETE',0),(102,'organisation','DELETE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','DELETE',0),(103,'portfolio','READ_LOAN','LOAN','READ',0),(104,'portfolio','CREATE_LOAN','LOAN','CREATE',0),(105,'portfolio','CREATE_LOAN_CHECKER','LOAN','CREATE',0),(106,'portfolio','UPDATE_LOAN','LOAN','UPDATE',0),(107,'portfolio','UPDATE_LOAN_CHECKER','LOAN','UPDATE',0),(108,'portfolio','DELETE_LOAN','LOAN','DELETE',0),(109,'portfolio','DELETE_LOAN_CHECKER','LOAN','DELETE',0),(110,'portfolio','READ_CLIENT','CLIENT','READ',0),(111,'portfolio','CREATE_CLIENT','CLIENT','CREATE',0),(112,'portfolio','CREATE_CLIENT_CHECKER','CLIENT','CREATE',0),(113,'portfolio','UPDATE_CLIENT','CLIENT','UPDATE',0),(114,'portfolio','UPDATE_CLIENT_CHECKER','CLIENT','UPDATE',0),(115,'portfolio','DELETE_CLIENT','CLIENT','DELETE',0),(116,'portfolio','DELETE_CLIENT_CHECKER','CLIENT','DELETE',0),(117,'portfolio','READ_CLIENTIMAGE','CLIENTIMAGE','READ',0),(118,'portfolio','CREATE_CLIENTIMAGE','CLIENTIMAGE','CREATE',0),(119,'portfolio','CREATE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','CREATE',0),(120,'portfolio','DELETE_CLIENTIMAGE','CLIENTIMAGE','DELETE',0),(121,'portfolio','DELETE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','DELETE',0),(122,'portfolio','READ_CLIENTNOTE','CLIENTNOTE','READ',0),(123,'portfolio','CREATE_CLIENTNOTE','CLIENTNOTE','CREATE',0),(124,'portfolio','CREATE_CLIENTNOTE_CHECKER','CLIENTNOTE','CREATE',0),(125,'portfolio','UPDATE_CLIENTNOTE','CLIENTNOTE','UPDATE',0),(126,'portfolio','UPDATE_CLIENTNOTE_CHECKER','CLIENTNOTE','UPDATE',0),(127,'portfolio','DELETE_CLIENTNOTE','CLIENTNOTE','DELETE',0),(128,'portfolio','DELETE_CLIENTNOTE_CHECKER','CLIENTNOTE','DELETE',0),(129,'portfolio','READ_GROUPNOTE','GROUPNOTE','READ',0),(130,'portfolio','CREATE_GROUPNOTE','GROUPNOTE','CREATE',0),(131,'portfolio','UPDATE_GROUPNOTE','GROUPNOTE','UPDATE',0),(132,'portfolio','DELETE_GROUPNOTE','GROUPNOTE','DELETE',0),(133,'portfolio','CREATE_GROUPNOTE_CHECKER','GROUPNOTE','CREATE',0),(134,'portfolio','UPDATE_GROUPNOTE_CHECKER','GROUPNOTE','UPDATE',0),(135,'portfolio','DELETE_GROUPNOTE_CHECKER','GROUPNOTE','DELETE',0),(136,'portfolio','READ_LOANNOTE','LOANNOTE','READ',0),(137,'portfolio','CREATE_LOANNOTE','LOANNOTE','CREATE',0),(138,'portfolio','UPDATE_LOANNOTE','LOANNOTE','UPDATE',0),(139,'portfolio','DELETE_LOANNOTE','LOANNOTE','DELETE',0),(140,'portfolio','CREATE_LOANNOTE_CHECKER','LOANNOTE','CREATE',0),(141,'portfolio','UPDATE_LOANNOTE_CHECKER','LOANNOTE','UPDATE',0),(142,'portfolio','DELETE_LOANNOTE_CHECKER','LOANNOTE','DELETE',0),(143,'portfolio','READ_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','READ',0),(144,'portfolio','CREATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','CREATE',0),(145,'portfolio','UPDATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','UPDATE',0),(146,'portfolio','DELETE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','DELETE',0),(147,'portfolio','CREATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','CREATE',0),(148,'portfolio','UPDATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','UPDATE',0),(149,'portfolio','DELETE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','DELETE',0),(150,'portfolio','READ_SAVINGNOTE','SAVINGNOTE','READ',0),(151,'portfolio','CREATE_SAVINGNOTE','SAVINGNOTE','CREATE',0),(152,'portfolio','UPDATE_SAVINGNOTE','SAVINGNOTE','UPDATE',0),(153,'portfolio','DELETE_SAVINGNOTE','SAVINGNOTE','DELETE',0),(154,'portfolio','CREATE_SAVINGNOTE_CHECKER','SAVINGNOTE','CREATE',0),(155,'portfolio','UPDATE_SAVINGNOTE_CHECKER','SAVINGNOTE','UPDATE',0),(156,'portfolio','DELETE_SAVINGNOTE_CHECKER','SAVINGNOTE','DELETE',0),(157,'portfolio','READ_CLIENTIDENTIFIER','CLIENTIDENTIFIER','READ',0),(158,'portfolio','CREATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','CREATE',0),(159,'portfolio','CREATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','CREATE',0),(160,'portfolio','UPDATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','UPDATE',0),(161,'portfolio','UPDATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','UPDATE',0),(162,'portfolio','DELETE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','DELETE',0),(163,'portfolio','DELETE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','DELETE',0),(164,'portfolio','READ_DOCUMENT','DOCUMENT','READ',0),(165,'portfolio','CREATE_DOCUMENT','DOCUMENT','CREATE',0),(166,'portfolio','CREATE_DOCUMENT_CHECKER','DOCUMENT','CREATE',0),(167,'portfolio','UPDATE_DOCUMENT','DOCUMENT','UPDATE',0),(168,'portfolio','UPDATE_DOCUMENT_CHECKER','DOCUMENT','UPDATE',0),(169,'portfolio','DELETE_DOCUMENT','DOCUMENT','DELETE',0),(170,'portfolio','DELETE_DOCUMENT_CHECKER','DOCUMENT','DELETE',0),(171,'portfolio','READ_GROUP','GROUP','READ',0),(172,'portfolio','CREATE_GROUP','GROUP','CREATE',0),(173,'portfolio','CREATE_GROUP_CHECKER','GROUP','CREATE',0),(174,'portfolio','UPDATE_GROUP','GROUP','UPDATE',0),(175,'portfolio','UPDATE_GROUP_CHECKER','GROUP','UPDATE',0),(176,'portfolio','DELETE_GROUP','GROUP','DELETE',0),(177,'portfolio','DELETE_GROUP_CHECKER','GROUP','DELETE',0),(178,'portfolio','UNASSIGNSTAFF_GROUP','GROUP','UNASSIGNSTAFF',0),(179,'portfolio','UNASSIGNSTAFF_GROUP_CHECKER','GROUP','UNASSIGNSTAFF',0),(180,'portfolio','CREATE_LOANCHARGE','LOANCHARGE','CREATE',0),(181,'portfolio','CREATE_LOANCHARGE_CHECKER','LOANCHARGE','CREATE',0),(182,'portfolio','UPDATE_LOANCHARGE','LOANCHARGE','UPDATE',0),(183,'portfolio','UPDATE_LOANCHARGE_CHECKER','LOANCHARGE','UPDATE',0),(184,'portfolio','DELETE_LOANCHARGE','LOANCHARGE','DELETE',0),(185,'portfolio','DELETE_LOANCHARGE_CHECKER','LOANCHARGE','DELETE',0),(186,'portfolio','WAIVE_LOANCHARGE','LOANCHARGE','WAIVE',0),(187,'portfolio','WAIVE_LOANCHARGE_CHECKER','LOANCHARGE','WAIVE',0),(188,'portfolio','READ_SAVINGSACCOUNT','SAVINGSACCOUNT','READ',0),(189,'portfolio','CREATE_SAVINGSACCOUNT','SAVINGSACCOUNT','CREATE',0),(190,'portfolio','CREATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','CREATE',0),(191,'portfolio','UPDATE_SAVINGSACCOUNT','SAVINGSACCOUNT','UPDATE',0),(192,'portfolio','UPDATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','UPDATE',0),(193,'portfolio','DELETE_SAVINGSACCOUNT','SAVINGSACCOUNT','DELETE',0),(194,'portfolio','DELETE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DELETE',0),(195,'portfolio','READ_GUARANTOR','GUARANTOR','READ',0),(196,'portfolio','CREATE_GUARANTOR','GUARANTOR','CREATE',0),(197,'portfolio','CREATE_GUARANTOR_CHECKER','GUARANTOR','CREATE',0),(198,'portfolio','UPDATE_GUARANTOR','GUARANTOR','UPDATE',0),(199,'portfolio','UPDATE_GUARANTOR_CHECKER','GUARANTOR','UPDATE',0),(200,'portfolio','DELETE_GUARANTOR','GUARANTOR','DELETE',0),(201,'portfolio','DELETE_GUARANTOR_CHECKER','GUARANTOR','DELETE',0),(202,'portfolio','READ_COLLATERAL','COLLATERAL','READ',0),(203,'portfolio','CREATE_COLLATERAL','COLLATERAL','CREATE',0),(204,'portfolio','UPDATE_COLLATERAL','COLLATERAL','UPDATE',0),(205,'portfolio','DELETE_COLLATERAL','COLLATERAL','DELETE',0),(206,'portfolio','CREATE_COLLATERAL_CHECKER','COLLATERAL','CREATE',0),(207,'portfolio','UPDATE_COLLATERAL_CHECKER','COLLATERAL','UPDATE',0),(208,'portfolio','DELETE_COLLATERAL_CHECKER','COLLATERAL','DELETE',0),(209,'transaction_loan','APPROVE_LOAN','LOAN','APPROVE',0),(210,'transaction_loan','APPROVEINPAST_LOAN','LOAN','APPROVEINPAST',0),(211,'transaction_loan','REJECT_LOAN','LOAN','REJECT',0),(212,'transaction_loan','REJECTINPAST_LOAN','LOAN','REJECTINPAST',0),(213,'transaction_loan','WITHDRAW_LOAN','LOAN','WITHDRAW',0),(214,'transaction_loan','WITHDRAWINPAST_LOAN','LOAN','WITHDRAWINPAST',0),(215,'transaction_loan','APPROVALUNDO_LOAN','LOAN','APPROVALUNDO',0),(216,'transaction_loan','DISBURSE_LOAN','LOAN','DISBURSE',0),(217,'transaction_loan','DISBURSEINPAST_LOAN','LOAN','DISBURSEINPAST',0),(218,'transaction_loan','DISBURSALUNDO_LOAN','LOAN','DISBURSALUNDO',0),(219,'transaction_loan','REPAYMENT_LOAN','LOAN','REPAYMENT',0),(220,'transaction_loan','REPAYMENTINPAST_LOAN','LOAN','REPAYMENTINPAST',0),(221,'transaction_loan','ADJUST_LOAN','LOAN','ADJUST',0),(222,'transaction_loan','WAIVEINTERESTPORTION_LOAN','LOAN','WAIVEINTERESTPORTION',0),(223,'transaction_loan','WRITEOFF_LOAN','LOAN','WRITEOFF',0),(224,'transaction_loan','CLOSE_LOAN','LOAN','CLOSE',0),(225,'transaction_loan','CLOSEASRESCHEDULED_LOAN','LOAN','CLOSEASRESCHEDULED',0),(226,'transaction_loan','UPDATELOANOFFICER_LOAN','LOAN','UPDATELOANOFFICER',0),(227,'transaction_loan','UPDATELOANOFFICER_LOAN_CHECKER','LOAN','UPDATELOANOFFICER',0),(228,'transaction_loan','REMOVELOANOFFICER_LOAN','LOAN','REMOVELOANOFFICER',0),(229,'transaction_loan','REMOVELOANOFFICER_LOAN_CHECKER','LOAN','REMOVELOANOFFICER',0),(230,'transaction_loan','BULKREASSIGN_LOAN','LOAN','BULKREASSIGN',0),(231,'transaction_loan','BULKREASSIGN_LOAN_CHECKER','LOAN','BULKREASSIGN',0),(232,'transaction_loan','APPROVE_LOAN_CHECKER','LOAN','APPROVE',0),(233,'transaction_loan','APPROVEINPAST_LOAN_CHECKER','LOAN','APPROVEINPAST',0),(234,'transaction_loan','REJECT_LOAN_CHECKER','LOAN','REJECT',0),(235,'transaction_loan','REJECTINPAST_LOAN_CHECKER','LOAN','REJECTINPAST',0),(236,'transaction_loan','WITHDRAW_LOAN_CHECKER','LOAN','WITHDRAW',0),(237,'transaction_loan','WITHDRAWINPAST_LOAN_CHECKER','LOAN','WITHDRAWINPAST',0),(238,'transaction_loan','APPROVALUNDO_LOAN_CHECKER','LOAN','APPROVALUNDO',0),(239,'transaction_loan','DISBURSE_LOAN_CHECKER','LOAN','DISBURSE',0),(240,'transaction_loan','DISBURSEINPAST_LOAN_CHECKER','LOAN','DISBURSEINPAST',0),(241,'transaction_loan','DISBURSALUNDO_LOAN_CHECKER','LOAN','DISBURSALUNDO',0),(242,'transaction_loan','REPAYMENT_LOAN_CHECKER','LOAN','REPAYMENT',0),(243,'transaction_loan','REPAYMENTINPAST_LOAN_CHECKER','LOAN','REPAYMENTINPAST',0),(244,'transaction_loan','ADJUST_LOAN_CHECKER','LOAN','ADJUST',0),(245,'transaction_loan','WAIVEINTERESTPORTION_LOAN_CHECKER','LOAN','WAIVEINTERESTPORTION',0),(246,'transaction_loan','WRITEOFF_LOAN_CHECKER','LOAN','WRITEOFF',0),(247,'transaction_loan','CLOSE_LOAN_CHECKER','LOAN','CLOSE',0),(248,'transaction_loan','CLOSEASRESCHEDULED_LOAN_CHECKER','LOAN','CLOSEASRESCHEDULED',0),(249,'transaction_savings','DEPOSIT_SAVINGSACCOUNT','SAVINGSACCOUNT','DEPOSIT',0),(250,'transaction_savings','DEPOSIT_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DEPOSIT',0),(251,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT','SAVINGSACCOUNT','WITHDRAWAL',0),(252,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','WITHDRAWAL',0),(253,'transaction_savings','ACTIVATE_SAVINGSACCOUNT','SAVINGSACCOUNT','ACTIVATE',0),(254,'transaction_savings','ACTIVATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','ACTIVATE',0),(255,'transaction_savings','CALCULATEINTEREST_SAVINGSACCOUNT','SAVINGSACCOUNT','CALCULATEINTEREST',0),(256,'transaction_savings','CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','CALCULATEINTEREST',0),(257,'accounting','CREATE_GLACCOUNT','GLACCOUNT','CREATE',0),(258,'accounting','UPDATE_GLACCOUNT','GLACCOUNT','UPDATE',0),(259,'accounting','DELETE_GLACCOUNT','GLACCOUNT','DELETE',0),(260,'accounting','CREATE_GLCLOSURE','GLCLOSURE','CREATE',0),(261,'accounting','UPDATE_GLCLOSURE','GLCLOSURE','UPDATE',0),(262,'accounting','DELETE_GLCLOSURE','GLCLOSURE','DELETE',0),(263,'accounting','CREATE_JOURNALENTRY','JOURNALENTRY','CREATE',0),(264,'accounting','REVERSE_JOURNALENTRY','JOURNALENTRY','REVERSE',0),(265,'report','READ_Active Loans - Details','Active Loans - Details','READ',0),(266,'report','READ_Active Loans - Summary','Active Loans - Summary','READ',0),(267,'report','READ_Active Loans by Disbursal Period','Active Loans by Disbursal Period','READ',0),(268,'report','READ_Active Loans in last installment','Active Loans in last installment','READ',0),(269,'report','READ_Active Loans in last installment Summary','Active Loans in last installment Summary','READ',0),(270,'report','READ_Active Loans Passed Final Maturity','Active Loans Passed Final Maturity','READ',0),(271,'report','READ_Active Loans Passed Final Maturity Summary','Active Loans Passed Final Maturity Summary','READ',0),(272,'report','READ_Aging Detail','Aging Detail','READ',0),(273,'report','READ_Aging Summary (Arrears in Months)','Aging Summary (Arrears in Months)','READ',0),(274,'report','READ_Aging Summary (Arrears in Weeks)','Aging Summary (Arrears in Weeks)','READ',0),(275,'report','READ_Balance Sheet','Balance Sheet','READ',0),(276,'report','READ_Branch Expected Cash Flow','Branch Expected Cash Flow','READ',0),(277,'report','READ_Client Listing','Client Listing','READ',0),(278,'report','READ_Client Loans Listing','Client Loans Listing','READ',0),(279,'report','READ_Expected Payments By Date - Basic','Expected Payments By Date - Basic','READ',0),(280,'report','READ_Expected Payments By Date - Formatted','Expected Payments By Date - Formatted','READ',0),(281,'report','READ_Funds Disbursed Between Dates Summary','Funds Disbursed Between Dates Summary','READ',0),(282,'report','READ_Funds Disbursed Between Dates Summary by Office','Funds Disbursed Between Dates Summary by Office','READ',0),(283,'report','READ_Income Statement','Income Statement','READ',0),(284,'report','READ_Loan Account Schedule','Loan Account Schedule','READ',0),(285,'report','READ_Loans Awaiting Disbursal','Loans Awaiting Disbursal','READ',0),(286,'report','READ_Loans Awaiting Disbursal Summary','Loans Awaiting Disbursal Summary','READ',0),(287,'report','READ_Loans Awaiting Disbursal Summary by Month','Loans Awaiting Disbursal Summary by Month','READ',0),(288,'report','READ_Loans Pending Approval','Loans Pending Approval','READ',0),(289,'report','READ_Obligation Met Loans Details','Obligation Met Loans Details','READ',0),(290,'report','READ_Obligation Met Loans Summary','Obligation Met Loans Summary','READ',0),(291,'report','READ_Portfolio at Risk','Portfolio at Risk','READ',0),(292,'report','READ_Portfolio at Risk by Branch','Portfolio at Risk by Branch','READ',0),(293,'report','READ_Rescheduled Loans','Rescheduled Loans','READ',0),(294,'report','READ_Trial Balance','Trial Balance','READ',0),(295,'report','READ_Written-Off Loans','Written-Off Loans','READ',0),(296,'datatable','CREATE_client additional data','client additional data','CREATE',1),(297,'datatable','CREATE_impact measurement','impact measurement','CREATE',1),(298,'datatable','CREATE_loan additional data','loan additional data','CREATE',1),(299,'datatable','READ_client additional data','client additional data','READ',1),(300,'datatable','READ_impact measurement','impact measurement','READ',1),(301,'datatable','READ_loan additional data','loan additional data','READ',1),(302,'datatable','UPDATE_client additional data','client additional data','UPDATE',1),(303,'datatable','UPDATE_impact measurement','impact measurement','UPDATE',1),(304,'datatable','UPDATE_loan additional data','loan additional data','UPDATE',1),(305,'datatable','DELETE_client additional data','client additional data','DELETE',1),(306,'datatable','DELETE_impact measurement','impact measurement','DELETE',1),(307,'datatable','DELETE_loan additional data','loan additional data','DELETE',1);
+/*!40000 ALTER TABLE `m_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_portfolio_command_source`
+--
+
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `subresource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_portfolio_command_source`
+--
+
+LOCK TABLES `m_portfolio_command_source` WRITE;
+/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
+INSERT INTO `m_portfolio_command_source` VALUES (1,'CREATE','CLIENT',2,NULL,1,NULL,NULL,'/clients/template',1,NULL,'{\"officeId\":\"2\",\"firstname\":\"Test\",\"middlename\":\"\",\"lastname\":\"One\",\"fullname\":\"\",\"externalId\":\"\",\"dateFormat\":\"yyyy-MM-dd\",\"locale\":\"en\",\"joinedDate\":\"2011-02-01\"}',1,'2013-04-05 12:38:05',NULL,NULL,1),(2,'CREATE','LOAN',2,NULL,1,1,NULL,'/loans',1,NULL,'{\"clientId\":\"1\",\"dateFormat\":\"yyyy-MM-dd\",\"locale\":\"en\",\"productId\":\"1\",\"loanOfficerId\":\"2\",\"submittedOnDate\":\"2011-04-01\",\"loanPurposeId\":\"25\",\"principal\":\"1,000,000.00\",\"loanTermFrequency\":\"12\",\"loanTermFrequencyType\":\"2\",\"numberOfRepayments\":\"12\",\"repaymentEvery\":\"1\",\"repaymentFrequencyType\":\"2\",\"interestRatePerPeriod\":\"24\",\"interestRateFrequencyType\":\"3\",\"expectedDisbursementDate\":\"2011-04-01\",\"amortizationType\":\"1\",\"interestType\":\"1\",\"interestCalculationPeriodType\":\"1\",\"inArrearsTolerance\":\"\",\"transactionProcessingStrategyId\":\"2\",\"repaymentsStartingFromDate\":\"\",\"interestChargedFromDate\":\"\",\"charges\":[{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-05-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-06-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-07-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-08-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-09-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-10-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-11-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-12-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2012-01-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2012-02-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2012-03-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2012-04-01\"}],\"collateral\":[{\"id\":\"\",\"type\":\"23\",\"description\":\" small description.\",\"value\":\"\"}]}',1,'2013-04-05 12:41:57',NULL,NULL,1),(3,'APPROVE','LOAN',2,NULL,1,1,NULL,'/loans/1',1,NULL,'{\"status\":{\"id\":200,\"code\":\"loanStatusType.approved\",\"value\":\"Approved\",\"pendingApproval\":false,\"waitingForDisbursal\":true,\"active\":false,\"closedObligationsMet\":false,\"closedWrittenOff\":false,\"closedRescheduled\":false,\"closed\":false,\"overpaid\":false},\"locale\":\"en\",\"dateFormat\":\"yyyy-MM-dd\",\"approvedOnDate\":\"2011-04-01\"}',1,'2013-04-05 12:42:09',NULL,NULL,1);
+/*!40000 ALTER TABLE `m_portfolio_command_source` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan`
+--
+
+DROP TABLE IF EXISTS `m_product_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `min_principal_amount` decimal(19,6) NOT NULL,
+  `max_principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan`
+--
+
+LOCK TABLES `m_product_loan` WRITE;
+/*!40000 ALTER TABLE `m_product_loan` DISABLE KEYS */;
+INSERT INTO `m_product_loan` VALUES (1,'UGX',2,'1000000.000000','0.000000','1000000000000.000000',NULL,'Kampala Product (with cash accounting)','Typical Kampala loan product with cash accounting enabled for testing.',NULL,'24.000000',3,'24.000000',1,1,1,2,12,1,2,2);
+/*!40000 ALTER TABLE `m_product_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan_charge`
+--
+
+LOCK TABLES `m_product_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_product_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role`
+--
+
+DROP TABLE IF EXISTS `m_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role`
+--
+
+LOCK TABLES `m_role` WRITE;
+/*!40000 ALTER TABLE `m_role` DISABLE KEYS */;
+INSERT INTO `m_role` VALUES (1,'Super user','This role provides all application permissions.');
+/*!40000 ALTER TABLE `m_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role_permission`
+--
+
+DROP TABLE IF EXISTS `m_role_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role_permission`
+--
+
+LOCK TABLES `m_role_permission` WRITE;
+/*!40000 ALTER TABLE `m_role_permission` DISABLE KEYS */;
+INSERT INTO `m_role_permission` VALUES (1,1);
+/*!40000 ALTER TABLE `m_role_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account`
+--
+
+DROP TABLE IF EXISTS `m_savings_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `status_enum` smallint(5) NOT NULL DEFAULT '300',
+  `activation_date` date DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_annual_interest_rate` decimal(19,6) NOT NULL,
+  `interest_compounding_period_enum` smallint(5) NOT NULL,
+  `interest_calculation_type_enum` smallint(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `lockedin_until_date_derived` date DEFAULT NULL,
+  `total_deposits_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_earned_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
+  `account_balance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sa_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `sa_externalid_UNIQUE` (`external_id`),
+  KEY `FKSA00000000000001` (`client_id`),
+  KEY `FKSA00000000000002` (`group_id`),
+  KEY `FKSA00000000000003` (`product_id`),
+  CONSTRAINT `FKSA00000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA00000000000002` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSA00000000000003` FOREIGN KEY (`product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account`
+--
+
+LOCK TABLES `m_savings_account` WRITE;
+/*!40000 ALTER TABLE `m_savings_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account_transaction`
+--
+
+DROP TABLE IF EXISTS `m_savings_account_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `running_balance_derived` decimal(19,6) DEFAULT NULL,
+  `balance_number_of_days_derived` int(11) DEFAULT NULL,
+  `balance_end_date_derived` date DEFAULT NULL,
+  `cumulative_balance_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account_transaction`
+--
+
+LOCK TABLES `m_savings_account_transaction` WRITE;
+/*!40000 ALTER TABLE `m_savings_account_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_product`
+--
+
+DROP TABLE IF EXISTS `m_savings_product`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_product` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_annual_interest_rate` decimal(19,6) NOT NULL,
+  `interest_compounding_period_enum` smallint(5) NOT NULL,
+  `interest_calculation_type_enum` smallint(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sp_unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_product`
+--
+
+LOCK TABLES `m_savings_product` WRITE;
+/*!40000 ALTER TABLE `m_savings_product` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_product` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_staff`
+--
+
+DROP TABLE IF EXISTS `m_staff`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_staff`
+--
+
+LOCK TABLES `m_staff` WRITE;
+/*!40000 ALTER TABLE `m_staff` DISABLE KEYS */;
+INSERT INTO `m_staff` VALUES (1,1,1,'CEDA HO','LoanOfficer','LoanOfficer, CEDA HO'),(2,1,2,'Kampala','LoanOfficer','LoanOfficer, Kampala');
+/*!40000 ALTER TABLE `m_staff` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `r_enum_value`
+--
+
+DROP TABLE IF EXISTS `r_enum_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `r_enum_value`
+--
+
+LOCK TABLES `r_enum_value` WRITE;
+/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
+INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
+/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `ref_loan_transaction_processing_strategy`
+--
+
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `ref_loan_transaction_processing_strategy`
+--
+
+LOCK TABLES `ref_loan_transaction_processing_strategy` WRITE;
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` DISABLE KEYS */;
+INSERT INTO `ref_loan_transaction_processing_strategy` VALUES (2,'heavensfamily-strategy','Heavensfamily',NULL,NULL,NULL,NULL);
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `rpt_sequence`
+--
+
+DROP TABLE IF EXISTS `rpt_sequence`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `rpt_sequence`
+--
+
+LOCK TABLES `rpt_sequence` WRITE;
+/*!40000 ALTER TABLE `rpt_sequence` DISABLE KEYS */;
+/*!40000 ALTER TABLE `rpt_sequence` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `schema_version`
+--
+
+DROP TABLE IF EXISTS `schema_version`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `schema_version` (
+  `version_rank` int(11) NOT NULL,
+  `installed_rank` int(11) NOT NULL,
+  `version` varchar(50) NOT NULL,
+  `description` varchar(200) NOT NULL,
+  `type` varchar(20) NOT NULL,
+  `script` varchar(1000) NOT NULL,
+  `checksum` int(11) DEFAULT NULL,
+  `installed_by` varchar(100) NOT NULL,
+  `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `execution_time` int(11) NOT NULL,
+  `success` tinyint(1) NOT NULL,
+  PRIMARY KEY (`version`),
+  KEY `schema_version_vr_idx` (`version_rank`),
+  KEY `schema_version_ir_idx` (`installed_rank`),
+  KEY `schema_version_s_idx` (`success`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `schema_version`
+--
+
+LOCK TABLES `schema_version` WRITE;
+/*!40000 ALTER TABLE `schema_version` DISABLE KEYS */;
+INSERT INTO `schema_version` VALUES (1,1,'1','mifosplatform-core-ddl-latest','SQL','V1__mifosplatform-core-ddl-latest.sql',-1957145051,'root','2013-04-05 11:23:24',365,1),(2,2,'2','mifosx-base-reference-data-utf8','SQL','V2__mifosx-base-reference-data-utf8.sql',1316484475,'root','2013-04-05 11:23:24',26,1),(3,3,'3','mifosx-permissions-and-authorisation-utf8','SQL','V3__mifosx-permissions-and-authorisation-utf8.sql',1922951887,'root','2013-04-05 11:23:24',26,1),(4,4,'4','mifosx-core-reports-utf8','SQL','V4__mifosx-core-reports-utf8.sql',-934709187,'root','2013-04-05 11:23:24',52,1),(5,5,'5','update-savings-product-and-account-tables','SQL','V5__update-savings-product-and-account-tables.sql',1171300485,'root','2013-04-05 11:23:24',22,1),(6,6,'6','add min max principal column to loan','SQL','V6__add_min_max_principal_column_to_loan.sql',21414779,'root','2013-04-05 11:23:24',25,1),(7,7,'7','remove read makerchecker permission','SQL','V7__remove_read_makerchecker_permission.sql',-335430825,'root','2013-04-05 11:23:24',3,1),(8,8,'8','deposit-transaction-permissions-if-they-exist','SQL','V8__deposit-transaction-permissions-if-they-exist.sql',-1507997551,'root','2013-04-05 11:23:24',2,1);
+/*!40000 ALTER TABLE `schema_version` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_parameter` (
+  `parameter_id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_parameter_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`parameter_id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`),
+  KEY `fk_stretchy_parameter_0001_idx` (`parent_parameter_id`),
+  CONSTRAINT `fk_stretchy_parameter_0001` FOREIGN KEY (`parent_parameter_id`) REFERENCES `stretchy_parameter` (`parameter_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_parameter`
+--
+
+LOCK TABLES `stretchy_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_parameter` VALUES (1,'startDateSelect','startDate','startDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(2,'endDateSelect','endDate','endDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(3,'obligDateTypeSelect','obligDateType','obligDateType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Closed\" as `name` union all\r\nselect 2, \"Disbursal\" ) x\r\norder by x.`id`',NULL),(5,'OfficeIdSelectOne','officeId','Office','select','number','0',NULL,'Y',NULL,'select id, \r\nconcat(substring(\"........................................\", 1, \r\n   \n\n((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat\n\n(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy',NULL),(6,'loanOfficerIdSelectAll','loanOfficerId','Loan Officer','select','number','0',NULL,NULL,'Y','(select lo.id, lo.display_name as `Name` \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\njoin m_staff lo on lo.office_id = ounder.id\r\nwhere lo.is_loan_officer = true\r\nand o.id = ${officeId})\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',5),(10,'currencyIdSelectAll','currencyId','Currency','select','number','0',NULL,NULL,'Y','select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`',NULL),(20,'fundIdSelectAll','fundId','Fund','select','number','0',NULL,NULL,'Y','(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',NULL),(25,'loanProductIdSelectAll','loanProductId','Product','select','number','0',NULL,NULL,'Y','select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere p.currency_code = \'${currencyId}\'\r\norder by 2',10),(26,'loanPurposeIdSelectAll','loanPurposeId','Loan Purpose','select','number','0',NULL,NULL,'Y','select -10 as id, \'-\' as code_value\r\nunion all\r\nselect * from (select v.id, v.code_value\r\nfrom m_code c\r\njoin m_code_value v on v.code_id = c.id\r\nwhere c.code_name = \"loanPurpose\"\r\norder by v.order_position)  x',NULL),(100,'parTypeSelect','parType','parType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Principal Only\" as `name` union all\r\nselect 2, \"Principal + Interest\" union all\r\nselect 3, \"Principal + Interest + Fees\" union all\r\nselect 4, \"Principal + Interest + Fees + Penalties\") x\r\norder by x.`id`',NULL),(1001,'FullReportList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\r\n  \n\nrp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on rp.report_id = r.report_id\r\n  \n\nleft join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \n\n\r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on rp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where \n\nur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by \n\nr.report_category, r.report_name, rp.parameter_id',NULL),(1002,'FullParameterList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r\nsp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r\nfrom stretchy_parameter sp\r\nleft join stretchy_parameter spp on spp.parameter_id = sp.parent_parameter_id\r\nwhere sp.special is null\r\nand exists \r\n	(select \'f\' \r\n	from stretchy_report sr\r\n	join stretchy_report_parameter srp on srp.report_id = sr.report_id\r\n	where sr.report_name in(${reportListing})\r\n	and srp.parameter_id = sp.parameter_id\r\n	)\r\norder by sp.parameter_id',NULL),(1003,'reportCategoryList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, \n\nr.report_category,\r\n  rp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on \n\nrp.report_id = r.report_id\r\n  left join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.report_category = \'${reportCategory}\'\r\n  and \n\nr.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on \n\nrp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where ur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \n\n\'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by r.report_category, r.report_name, rp.parameter_id',NULL);
+/*!40000 ALTER TABLE `stretchy_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report`
+--
+
+DROP TABLE IF EXISTS `stretchy_report`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report` (
+  `report_id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`report_id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report`
+--
+
+LOCK TABLES `stretchy_report` WRITE;
+/*!40000 ALTER TABLE `stretchy_report` DISABLE KEYS */;
+INSERT INTO `stretchy_report` VALUES (1,'Client Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n c.account_no as \"Client Account No.\",  \r\nc.display_name as \"Name\",  \n\nc.joined_date as \"Joined\", c.external_id as \"External Id\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand \n\nounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\nwhere o.id = ${officeId}\r\nand c.is_deleted=0\r\n\n\norder by ounder.hierarchy, c.account_no','Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t \n\nhave that client browser/memory impact).',1,1),(2,'Client Loans Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client \n\nAccount No.\", \r\nc.display_name as \"Name\", \r\nlo.display_name as \"Loan Officer\", l.account_no as \"Loan Account No.\", l.external_id as \"External Id\", \r\n\n\np.name as Loan, st.enum_message_property as \"Status\",  \r\nf.`name` as Fund, purp.code_value as \"Loan Purpose\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount,\n\n\r\nl.arrearstolerance_amount as \"Arrears Tolerance Amount\",\r\nl.number_of_repayments as \"Expected No. Repayments\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\nl.nominal_interest_rate_per_period as \"Nominal Interest Rate Per Period\",\r\n\r\nipf.enum_message_property as \"Interest Rate Frequency\n\n\",\r\nim.enum_message_property as \"Interest Method\",\r\nicp.enum_message_property as \"Interest Calculated in Period\",\r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.repay_every as \"Repayment Frequency\",\r\nrf.enum_message_property as \"Repayment Frequency Period\",\n\n\r\nam.enum_message_property as \"Amortization\",\r\n\r\nl.total_charges_due_at_disbursement_derived as \"Total Charges Due At Disbursement\",\r\n\r\ndate( \n\nl.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As \"Expected Disbursal\",\r\ndate(l.expected_firstrepaymenton_date) as \n\n\"Expected First Repayment\", date(l.interest_calculated_from_date) as \"Interest Calculated From\" ,\r\ndate(l.disbursedon_date) as Disbursed, date\n\n(l.expected_maturedon_date) \"Expected Maturity\",\r\ndate(l.maturedon_date) as \"Matured On\", date(l.closedon_date) as Closed,\r\ndate(l.rejectedon_date) as \n\nRejected, date(l.rescheduledon_date) as Rescheduled, \r\ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) \"Written Off\"\r\nfrom m_office o \r\njoin \n\nm_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on \n\nc.office_id = ounder.id\r\nleft join m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_product_loan p on p.id = \n\nl.product_id\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join r_enum_value st on st.enum_name = \"loan_status_id\" and st.enum_id = l.loan_status_id\r\nleft join \n\nr_enum_value ipf on ipf.enum_name = \"interest_period_frequency_enum\" and ipf.enum_id = l.interest_period_frequency_enum\r\nleft join r_enum_value im on im.enum_name \n\n= \"interest_method_enum\" and im.enum_id = l.interest_method_enum\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = \n\nl.term_period_frequency_enum\r\nleft join r_enum_value icp on icp.enum_name = \"interest_calculated_in_period_enum\" and icp.enum_id = \n\nl.interest_calculated_in_period_enum\r\nleft join r_enum_value rf on rf.enum_name = \"repayment_period_frequency_enum\" and rf.enum_id = \n\nl.repayment_period_frequency_enum\r\nleft join r_enum_value am on am.enum_name = \"amortization_method_enum\" and am.enum_id = l.amortization_method_enum\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\n\r\nleft \n\njoin m_currency cur on cur.code = l.currency_code\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand \n\n(l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \n\n\"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\norder by ounder.hierarchy, 2 , l.id','Individual Client Report\r\n\r\nPretty \n\nwide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).',1,1),(5,'Loans Awaiting Disbursal','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No\", c.display_name as \"Name\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund, ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\",\r\ndate(l.approvedon_date) \"Approved\",\r\ndatediff(l.expected_disbursedon_date, curdate()) as \"Days to Disbursal\",\r\ndate(l.expected_disbursedon_date) \"Expected Disbursal\",\r\npurp.code_value as \"Loan Purpose\",\r\n lo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, datediff(l.expected_disbursedon_date, curdate()),  c.account_no','Individual Client Report',1,1),(6,'Loans Awaiting Disbursal Summary','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`','Individual Client Report',1,1),(7,'Loans Awaiting Disbursal Summary by Month','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as \"Year\", \r\nmonthname(l.expected_disbursedon_date) as \"Month\",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)','Individual Client Report',1,1),(8,'Loans Pending Approval','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client Name\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  pl.`name` as \"Product\", \r\nl.account_no as \"Loan Account No.\", \r\nl.principal_amount as \"Loan Amount\", \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\ndatediff(curdate(), l.submittedon_date) \"Days Pending Approval\", \r\npurp.code_value as \"Loan Purpose\",\r\nlo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 100 /*Submitted and awaiting approval */\r\norder by ounder.hierarchy, l.submittedon_date,  l.account_no','Individual Client Report',1,1),(11,'Active Loans - Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. loans_in_arrears_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(if(laa.loan_id is not null,  l.id, null)  )) as loans_in_arrears_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(laa.principal_overdue_derived) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(laa.interest_overdue_derived) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(laa.fee_charges_overdue_derived) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(laa.penalty_charges_overdue_derived) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(12,'Active Loans - Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\npenalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(13,'Obligation Met Loans Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client\",\r\nl.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.total_repayment_derived  as \"Total Repaid\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", \r\ndate(l.closedon_date) as \"Closed\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(14,'Obligation Met Loans Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as \"No. of Clients\",\r\ncount(distinct(l.id)) as \"No. of Loans\",\r\nsum(l.principal_amount) as \"Total Loan Amount\", \r\nsum(l.principal_repaid_derived) as \"Total Principal Repaid\",\r\nsum(l.interest_repaid_derived) as \"Total Interest Repaid\",\r\nsum(l.fee_charges_repaid_derived) as \"Total Fees Repaid\",\r\nsum(l.penalty_charges_repaid_derived) as \"Total Penalties Repaid\",\r\nsum(l.interest_waived_derived) as \"Total Interest Waived\",\r\nsum(l.fee_charges_waived_derived) as \"Total Fees Waived\",\r\nsum(l.penalty_charges_waived_derived) as \"Total Penalties Waived\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code','Individual Client \n\nReport',1,1),(15,'Portfolio at Risk','Table',NULL,'Loan','select x.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from \r\n(select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code) x','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(16,'Portfolio at Risk by Branch','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\",\r\nx.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select  ounder.id as \"branch\", ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\n\r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(20,'Funds Disbursed Between Dates Summary','Table',NULL,'Fund','select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, \r\nround(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(21,'Funds Disbursed Between Dates Summary by Office','Table',NULL,'Fund','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n \n\nifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\n\n\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c \n\non c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\n\n\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand \n\n(l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, \n\nl.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(48,'Balance Sheet','Pentaho',NULL,'Accounting',NULL,'Balance Sheet',1,1),(49,'Income Statement','Pentaho',NULL,'Accounting',NULL,'Profit and Loss Statement',1,1),(50,'Trial Balance','Pentaho',NULL,'Accounting',NULL,'Trial Balance Report',1,1),(51,'Written-Off Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Written-Off Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Written-Off Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Written-Off Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Written-Off Penalties\',\r\nn.note AS \'Reason For Write-Off\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 6 /*write-off */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=601\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\") \r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Written Off Loans',1,1),(52,'Aging Detail','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as \"Client Account No.\",\r\n 	mc.display_name AS \"Client Name\",\r\n 	ml.account_no AS \"Account Number\",\r\n 	ml.principal_amount AS \"Loan Amount\",\r\n ml.principal_disbursed_derived AS \"Original Principal\",\r\n ml.interest_charged_derived AS \"Original Interest\",\r\n ml.principal_repaid_derived AS \"Principal Paid\",\r\n ml.interest_repaid_derived AS \"Interest Paid\",\r\n laa.principal_overdue_derived AS \"Principal Overdue\",\r\n laa.interest_overdue_derived AS \"Interest Overdue\",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as \"Days in Arrears\",\r\n\r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS \"Weeks In Arrears Band\",\r\n\r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n				 \'> 360\'))))) AS \"Days in Arrears Band\"\r\n\r\n	FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n	        AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n	    INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n	    INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n	WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Loan arrears aging (Weeks)',1,1),(53,'Aging Summary (Arrears in Weeks)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Weeks In Arrears (Up To)\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'1\',2 UNION\r\n		SELECT \'2\',3 UNION\r\n		SELECT \'3\',4 UNION\r\n		SELECT \'4\',5 UNION\r\n		SELECT \'5\',6 UNION\r\n		SELECT \'6\',7 UNION\r\n		SELECT \'7\',8 UNION\r\n		SELECT \'8\',9 UNION\r\n		SELECT \'9\',10 UNION\r\n		SELECT \'10\',11 UNION\r\n		SELECT \'11\',12 UNION\r\n		SELECT \'12\',13 UNION\r\n		SELECT \'12+\',14) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<8, \'1\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<15, \'2\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<22, \'3\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<29, \'4\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<36, \'5\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<43, \'6\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<50, \'7\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<57, \'8\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<64, \'9\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<71, \'10\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<78, \'11\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<85, \'12\',\r\n				 \'12+\'))))))))))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(54,'Rescheduled Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Rescheduled Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Rescheduled Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Rescheduled Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Rescheduled Penalties\',\r\nn.note AS \'Reason For Rescheduling\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 7 /*marked for rescheduling */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=602\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Rescheduled Loans.  The ability to reschedule (or mark that you have rescheduled the loan elsewhere) is a legacy of the older Mifos product.  Needed for migration.',1,1),(55,'Active Loans Passed Final Maturity','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(56,'Active Loans Passed Final Maturity Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(57,'Active Loans in last installment','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(lastInstallment.`hierarchy`) - LENGTH(REPLACE(lastInstallment.`hierarchy`, \'.\', \'\')) - 1))), lastInstallment.branch) as \"Office/Branch\",\r\nlastInstallment.Currency,\r\nlastInstallment.`Loan Officer`, \r\nlastInstallment.`Client Account No`, lastInstallment.`Client`, \r\nlastInstallment.`Loan Account No`, lastInstallment.`Product`, \r\nlastInstallment.`Fund`,  lastInstallment.`Loan Amount`, \r\nlastInstallment.`Annual Nominal Interest Rate`, \r\nlastInstallment.`Disbursed`, lastInstallment.`Expected Matured On` ,\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\norder by lastInstallment.hierarchy, lastInstallment.Currency, lastInstallment.`Client Account No`, lastInstallment.`Loan Account No`','Individual Client \n\nReport',1,1),(58,'Active Loans in last installment Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select lastInstallment.branchId as branchId,\r\nlastInstallment.Currency,\r\ncount(distinct(lastInstallment.clientId)) as client_count, \r\ncount(distinct(lastInstallment.loanId)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id as branchId, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.id as clientId, c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\ngroup by lastInstallment.branchId) x on x.branchId = mo.id\r\norder by mo.hierarchy, x.Currency','Individual Client \n\nReport',1,1),(59,'Active Loans by Disbursal Period','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No\", c.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Principal Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\n\r\nl.total_expected_repayment_derived as \"Total Loan (P+I+F+Pen)\",\r\nl.total_repayment_derived as \"Total Repaid (P+I+F+Pen)\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(61,'Aging Summary (Arrears in Months)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Days In Arrears\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'0 - 30\',2 UNION\r\n		SELECT \'30 - 60\',3 UNION\r\n		SELECT \'60 - 90\',4 UNION\r\n		SELECT \'90 - 180\',5 UNION\r\n		SELECT \'180 - 360\',6 UNION\r\n		SELECT \'> 360\',7 ) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<361, \'180 - 360\', \r\n				 \'> 360\')))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(91,'Loan Account Schedule','Pentaho',NULL,'Loan',NULL,NULL,1,0),(92,'Branch Expected Cash Flow','Pentaho',NULL,'Loan',NULL,NULL,1,1),(93,'Expected Payments By Date - Basic','Table',NULL,'Loan','SELECT \r\n      ounder.name \'Office\', \r\n      IFNULL(ms.display_name,\'-\') \'Loan Officer\',\r\n	  mc.account_no \'Client Account Number\',\r\n	  mc.display_name \'Name\',\r\n	  mp.name \'Product\',\r\n	  ml.account_no \'Loan Account Number\',\r\n	  mr.duedate \'Due Date\',\r\n	  mr.installment \'Installment\',\r\n	  cu.display_symbol \'Currency\',\r\n	  mr.principal_amount- IFNULL(mr.principal_completed_derived,0) \'Principal Due\',\r\n	  mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0) \'Interest Due\', \r\n	  IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0) \'Fees Due\', \r\n	  IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0) \'Penalty Due\',\r\n      (mr.principal_amount- IFNULL(mr.principal_completed_derived,0)) +\r\n       (mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0)) + \r\n       (IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0)) + \r\n       (IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0)) \'Total Due\', \r\n     mlaa.total_overdue_derived \'Total Overdue\'\r\n										 \r\n FROM m_office mo\r\n  JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\n  \r\n  AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n	\r\n  LEFT JOIN m_client mc ON mc.office_id=ounder.id\r\n  LEFT JOIN m_loan ml ON ml.client_id=mc.id AND ml.loan_status_id=300\r\n  LEFT JOIN m_loan_arrears_aging mlaa ON mlaa.loan_id=ml.id\r\n  LEFT JOIN m_loan_repayment_schedule mr ON mr.loan_id=ml.id AND mr.completed_derived=0\r\n  LEFT JOIN m_product_loan mp ON mp.id=ml.product_id\r\n  LEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\n  LEFT JOIN m_currency cu ON cu.code=ml.currency_code\r\n WHERE mo.id=${officeId}\r\n AND (IFNULL(ml.loan_officer_id, -10) = \"${loanOfficerId}\" OR \"-1\" = \"${loanOfficerId}\")\r\n AND mr.duedate BETWEEN \'${startDate}\' AND \'${endDate}\'\r\n ORDER BY ounder.id,mr.duedate,ml.account_no','Test',1,1),(94,'Expected Payments By Date - Formatted','Pentaho',NULL,'Loan',NULL,NULL,1,1);
+/*!40000 ALTER TABLE `stretchy_report` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report_parameter` (
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`report_id`,`parameter_id`),
+  UNIQUE KEY `report_id_name_UNIQUE` (`report_id`,`report_parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report_parameter`
+--
+
+LOCK TABLES `stretchy_report_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_report_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_report_parameter` VALUES (1,5,NULL),(2,5,NULL),(2,6,NULL),(2,10,NULL),(2,20,NULL),(2,25,NULL),(2,26,NULL),(5,5,NULL),(5,6,NULL),(5,10,NULL),(5,20,NULL),(5,25,NULL),(5,26,NULL),(6,5,NULL),(6,6,NULL),(6,10,NULL),(6,20,NULL),(6,25,NULL),(6,26,NULL),(7,5,NULL),(7,6,NULL),(7,10,NULL),(7,20,NULL),(7,25,NULL),(7,26,NULL),(8,5,NULL),(8,6,NULL),(8,10,NULL),(8,25,NULL),(8,26,NULL),(11,5,NULL),(11,6,NULL),(11,10,NULL),(11,20,NULL),(11,25,NULL),(11,26,NULL),(11,100,NULL),(12,5,NULL),(12,6,NULL),(12,10,NULL),(12,20,NULL),(12,25,NULL),(12,26,NULL),(13,1,NULL),(13,2,NULL),(13,3,NULL),(13,5,NULL),(13,6,NULL),(13,10,NULL),(13,20,NULL),(13,25,NULL),(13,26,NULL),(14,1,NULL),(14,2,NULL),(14,3,NULL),(14,5,NULL),(14,6,NULL),(14,10,NULL),(14,20,NULL),(14,25,NULL),(14,26,NULL),(15,5,NULL),(15,6,NULL),(15,10,NULL),(15,20,NULL),(15,25,NULL),(15,26,NULL),(15,100,NULL),(16,5,NULL),(16,6,NULL),(16,10,NULL),(16,20,NULL),(16,25,NULL),(16,26,NULL),(16,100,NULL),(20,1,NULL),(20,2,NULL),(20,10,NULL),(20,20,NULL),(21,1,NULL),(21,2,NULL),(21,5,NULL),(21,10,NULL),(21,20,NULL),(48,5,'branch'),(48,2,'date'),(49,5,'branch'),(49,1,'fromDate'),(49,2,'toDate'),(50,5,'branch'),(50,1,'fromDate'),(50,2,'toDate'),(51,1,NULL),(51,2,NULL),(51,5,NULL),(51,10,NULL),(51,25,NULL),(52,5,NULL),(53,5,NULL),(53,10,NULL),(54,1,NULL),(54,2,NULL),(54,5,NULL),(54,10,NULL),(54,25,NULL),(55,5,NULL),(55,6,NULL),(55,10,NULL),(55,20,NULL),(55,25,NULL),(55,26,NULL),(56,5,NULL),(56,6,NULL),(56,10,NULL),(56,20,NULL),(56,25,NULL),(56,26,NULL),(56,100,NULL),(57,5,NULL),(57,6,NULL),(57,10,NULL),(57,20,NULL),(57,25,NULL),(57,26,NULL),(58,5,NULL),(58,6,NULL),(58,10,NULL),(58,20,NULL),(58,25,NULL),(58,26,NULL),(58,100,NULL),(59,1,NULL),(59,2,NULL),(59,5,NULL),(59,6,NULL),(59,10,NULL),(59,20,NULL),(59,25,NULL),(59,26,NULL),(61,5,NULL),(61,10,NULL),(92,1,'fromDate'),(92,5,'selectOffice'),(92,2,'toDate'),(93,1,NULL),(93,2,NULL),(93,5,NULL),(93,6,NULL),(94,2,'endDate'),(94,6,'loanOfficerId'),(94,5,'officeId'),(94,1,'startDate');
+/*!40000 ALTER TABLE `stretchy_report_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `x_registered_table`
+--
+
+DROP TABLE IF EXISTS `x_registered_table`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `x_registered_table`
+--
+
+LOCK TABLES `x_registered_table` WRITE;
+/*!40000 ALTER TABLE `x_registered_table` DISABLE KEYS */;
+INSERT INTO `x_registered_table` VALUES ('client additional data','m_client'),('impact measurement','m_loan'),('loan additional data','m_loan');
+/*!40000 ALTER TABLE `x_registered_table` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-04-05 12:42:52
diff --git a/fineract-db/multi-tenant-demo-backups/ceda/bk_core_with_custom_and_coa.sql b/fineract-db/multi-tenant-demo-backups/ceda/bk_core_with_custom_and_coa.sql
new file mode 100644
index 0000000..fea7164
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/ceda/bk_core_with_custom_and_coa.sql
@@ -0,0 +1,1909 @@
+-- MySQL dump 10.13  Distrib 5.1.60, for Win32 (ia32)
+--
+-- Host: localhost    Database: mifostenant-ceda
+-- ------------------------------------------------------
+-- Server version	5.1.60-community
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `acc_gl_account`
+--
+
+DROP TABLE IF EXISTS `acc_gl_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=98 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_account`
+--
+
+LOCK TABLES `acc_gl_account` WRITE;
+/*!40000 ALTER TABLE `acc_gl_account` DISABLE KEYS */;
+INSERT INTO `acc_gl_account` VALUES (1,'Petty Cash Balances',NULL,'11100',0,1,2,1,NULL),(2,'Cash in Valut 1',NULL,'11101',0,1,1,1,NULL),(3,'Bank Balances',NULL,'11200',0,1,2,1,NULL),(4,'Centenary Opening Account',NULL,'11201',0,1,1,1,NULL),(5,'Centenary Expense Account',NULL,'11202',0,1,1,1,NULL),(6,'Centenary USD Account',NULL,'11203',0,1,1,1,NULL),(7,'Loans and Advances',NULL,'13100',0,1,2,1,NULL),(8,'Loans to Clients',NULL,'13101',0,1,1,1,NULL),(9,'Outstanding Interest',NULL,'13102',0,1,1,1,NULL),(10,'Outstanding Late Payment Interest',NULL,'13103',0,1,1,1,NULL),(11,'Outstanding Bank Fees to be collected',NULL,'13104',0,1,1,1,NULL),(12,'WriteOff Accounts',NULL,'13200',0,1,2,1,NULL),(13,'Write-offs (use for funds coming in)',NULL,'13201',0,1,1,1,NULL),(14,'Write-offs outstanding principal',NULL,'13202',0,1,1,1,NULL),(15,'Write-offs outstanding interest',NULL,'13203',0,1,1,1,NULL),(16,'Write-offs collected bank fees',NULL,'13204',0,1,1,1,NULL),(17,'Write-offs hardware/furniture',NULL,'13205',0,1,1,1,NULL),(18,'Fixed Assets',NULL,'14100',0,1,2,1,NULL),(19,'Office Equipment',NULL,'14101',0,1,1,1,NULL),(20,'Suspense Items (unidentified deposits)',NULL,'15000',0,1,2,1,NULL),(21,'Assets',NULL,'10000',0,1,2,1,NULL),(22,'Liabilities',NULL,'20000',0,1,2,2,NULL),(23,'Shares Account',NULL,'26100',0,1,2,2,NULL),(24,'Shares Captial',NULL,'26101',0,1,1,2,NULL),(25,'Donated Equity',NULL,'26300',0,1,2,2,NULL),(26,'Donated Equity Ameropa Foundation',NULL,'26301',0,1,1,2,NULL),(27,'Donated Equity e.h',NULL,'26302',0,1,1,2,NULL),(28,'Overpaid Amounts',NULL,'27000',0,1,2,2,NULL),(29,'Loss Provision',NULL,'28000',0,1,2,2,NULL),(30,'Provision Outstanding Principal',NULL,'28001',0,1,1,2,NULL),(31,'Provision Oustanding Interest',NULL,'28002',0,1,1,2,NULL),(32,'Income',NULL,'30000',0,1,2,4,NULL),(33,'Interest Income from Loans',NULL,'31100',0,1,2,4,NULL),(34,'Interest on Loans',NULL,'31101',0,1,1,4,NULL),(35,'Late Payment Interest',NULL,'31102',0,1,1,4,NULL),(36,'Income from Micro credit & Lending Activities',NULL,'31300',0,1,2,4,NULL),(37,'Collected Bank Fees Receivable',NULL,'6201',0,1,1,4,NULL),(38,'Deposits from Loans Write Off',NULL,'31400',0,1,2,4,NULL),(39,'Expenditure',NULL,'40000',0,1,2,5,NULL),(40,'Office Expenditure Account',NULL,'42100',0,1,2,5,NULL),(41,'Water Charges',NULL,'42102',0,1,1,5,NULL),(42,'Electricity Charges',NULL,'42103',0,1,1,5,NULL),(43,'Printing and Stationary',NULL,'42105',0,1,1,5,NULL),(44,'Office Rent',NULL,'42107',0,1,1,5,NULL),(45,'Marketing Expense',NULL,'42109',0,1,1,5,NULL),(46,'Office utilities',NULL,'42112',0,1,1,5,'(supplies, toiletries, kitchen)'),(47,'Furniture',NULL,'42113',0,1,1,5,NULL),(48,'CEDA Meeting Expense',NULL,'42114',0,1,1,5,NULL),(49,'Employee Personal Expsense Account',NULL,'42200',0,1,2,5,NULL),(50,'Salary Alice',NULL,'42201',0,1,1,5,NULL),(51,'Salary Irene',NULL,'42202',0,1,1,5,NULL),(52,'Salary Richard',NULL,'42203',0,1,1,5,NULL),(53,'Salary Loan Officer TBA',NULL,'42204',0,1,1,5,NULL),(54,'Medical Insurance Alice & Family',NULL,'42205',0,1,1,5,NULL),(55,'Medical Insurance Irene',NULL,'42206',0,1,1,5,NULL),(56,'Medical Insurance Richard',NULL,'42207',0,1,1,5,NULL),(57,'Medical Insurance Loan Officer TBA',NULL,'42208',0,1,1,5,NULL),(58,'PAYE all employees',NULL,'42209',0,1,1,5,NULL),(59,'NSSF all employees',NULL,'42210',0,1,1,5,NULL),(60,'Lunch Allowances all employees',NULL,'42211',0,1,1,5,NULL),(61,'IT software and maintenance',NULL,'42300',0,1,2,5,NULL),(62,'Mifos maintenance contract 12 months',NULL,'42301',0,1,1,5,NULL),(63,'VPS Contract 12 months',NULL,'42302',0,1,1,5,NULL),(64,'Bulk SMS Service',NULL,'42303',0,1,1,5,NULL),(65,'Support Accounting Software',NULL,'42304',0,1,1,5,NULL),(66,'Mifos Instance Setup',NULL,'42305',0,1,1,5,NULL),(67,'Misc support expense',NULL,'42306',0,1,1,5,NULL),(68,'Warid Mobile Line',NULL,'42307',0,1,1,5,NULL),(69,'Landline',NULL,'42308',0,1,1,5,NULL),(70,'Modem Alice',NULL,'42309',0,1,1,5,NULL),(71,'Modem Irene',NULL,'42310',0,1,1,5,NULL),(72,'Modem Richard',NULL,'42311',0,1,1,5,NULL),(73,'Repairs',NULL,'42312',0,1,1,5,NULL),(74,'Airtime Expenses',NULL,'42400',0,1,2,5,NULL),(75,'Airtime Alice',NULL,'42401',0,1,1,5,NULL),(76,'Airtime Richard',NULL,'42402',0,1,1,5,NULL),(77,'Airtime Loan Office TBA',NULL,'42403',0,1,1,5,NULL),(78,'Special Airtime Alice',NULL,'42404',0,1,1,5,NULL),(79,'Transportation',NULL,'42500',0,1,2,5,NULL),(80,'Flat monthly transportation cost',NULL,'42501',0,1,1,5,NULL),(81,'Faciliation cost for Richard',NULL,'42502',0,1,1,5,NULL),(82,'Faciliation cost for Loan Officer TBA',NULL,'42503',0,1,1,5,NULL),(83,'Consultancy Expenses',NULL,'42600',0,1,2,5,NULL),(84,'Audit Fees',NULL,'42601',0,1,1,5,NULL),(85,'Legal Fees',NULL,'42602',0,1,1,5,NULL),(86,'Miscellaneous Expenses Account',NULL,'42700',0,1,2,5,NULL),(87,'MFI License',NULL,'42703',0,1,1,5,NULL),(88,'Sundy Expenses',NULL,'42704',0,1,1,5,NULL),(89,'Bank Fees',NULL,'42800',0,1,2,5,NULL),(90,'Bank Charges Operating Account',NULL,'42801',0,1,1,5,NULL),(91,'Bank Charges Expense Account',NULL,'42802',0,1,1,5,NULL),(92,'E.H Account',NULL,'42900',0,1,2,5,NULL),(93,'Airtime',NULL,'42901',0,1,1,5,NULL),(94,'Modem',NULL,'42902',0,1,1,5,NULL),(95,'Meals',NULL,'42903',0,1,1,5,NULL),(96,'Transportation',NULL,'42904',0,1,1,5,NULL),(97,'Miscellaneous',NULL,'42905',0,1,1,5,NULL);
+/*!40000 ALTER TABLE `acc_gl_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_closure`
+--
+
+DROP TABLE IF EXISTS `acc_gl_closure`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_closure`
+--
+
+LOCK TABLES `acc_gl_closure` WRITE;
+/*!40000 ALTER TABLE `acc_gl_closure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_closure` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_journal_entry`
+--
+
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_entry` tinyint(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type_enum` smallint(5) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_journal_entry`
+--
+
+LOCK TABLES `acc_gl_journal_entry` WRITE;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_product_mapping`
+--
+
+DROP TABLE IF EXISTS `acc_product_mapping`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_product_mapping`
+--
+
+LOCK TABLES `acc_product_mapping` WRITE;
+/*!40000 ALTER TABLE `acc_product_mapping` DISABLE KEYS */;
+INSERT INTO `acc_product_mapping` VALUES (1,4,1,1,1),(2,8,1,1,2),(3,34,1,1,3),(4,37,1,1,4),(5,35,1,1,5),(6,97,1,1,6);
+/*!40000 ALTER TABLE `acc_product_mapping` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `c_configuration`
+--
+
+DROP TABLE IF EXISTS `c_configuration`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `c_configuration`
+--
+
+LOCK TABLES `c_configuration` WRITE;
+/*!40000 ALTER TABLE `c_configuration` DISABLE KEYS */;
+INSERT INTO `c_configuration` VALUES (1,'maker-checker',0);
+/*!40000 ALTER TABLE `c_configuration` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `client additional data`
+--
+
+DROP TABLE IF EXISTS `client additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `client additional data` (
+  `client_id` bigint(20) NOT NULL,
+  `Gender_cd` int(11) NOT NULL,
+  `Date of Birth` date NOT NULL,
+  `Home address` text NOT NULL,
+  `Telephone number` varchar(20) NOT NULL,
+  `Telephone number (2nd)` varchar(20) NOT NULL,
+  `Email address` varchar(50) NOT NULL,
+  `EducationLevel_cd` int(11) NOT NULL,
+  `MaritalStatus_cd` int(11) NOT NULL,
+  `Number of children` int(11) NOT NULL,
+  `Citizenship` varchar(50) NOT NULL,
+  `PovertyStatus_cd` int(11) NOT NULL,
+  `YesNo_cd_Employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of employment` int(11) DEFAULT NULL,
+  `Employer name` varchar(50) DEFAULT NULL,
+  `Number of years` int(11) DEFAULT NULL,
+  `Monthly salary` decimal(19,6) DEFAULT NULL,
+  `YesNo_cd_Self employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of self-employment` int(11) DEFAULT NULL,
+  `Business address` text,
+  `Number of employees` int(11) DEFAULT NULL,
+  `Monthly salaries paid` decimal(19,6) DEFAULT NULL,
+  `Monthly net income of business activity` decimal(19,6) DEFAULT NULL,
+  `Monthly rent` decimal(19,6) DEFAULT NULL,
+  `Other income generating activities` varchar(100) DEFAULT NULL,
+  `YesNo_cd_Bookkeeping` int(11) DEFAULT NULL,
+  `YesNo_cd_Loans with other institutions` int(11) NOT NULL,
+  `From whom` varchar(100) DEFAULT NULL,
+  `Amount` decimal(19,6) DEFAULT NULL,
+  `Interest rate pa` decimal(19,6) DEFAULT NULL,
+  `Number of people depending on overal income` int(11) NOT NULL,
+  `YesNo_cd_Bank account` int(11) NOT NULL,
+  `YesNo_cd_Business plan provided` int(11) NOT NULL,
+  `YesNo_cd_Access to internet` int(11) DEFAULT NULL,
+  `Introduced by` varchar(100) DEFAULT NULL,
+  `Known to introducer since` varchar(100) NOT NULL,
+  `Last visited by` varchar(100) DEFAULT NULL,
+  `Last visited on` date NOT NULL,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_client_additional_data` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `client additional data`
+--
+
+LOCK TABLES `client additional data` WRITE;
+/*!40000 ALTER TABLE `client additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `client additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `impact measurement`
+--
+
+DROP TABLE IF EXISTS `impact measurement`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `impact measurement` (
+  `loan_id` bigint(20) NOT NULL,
+  `YesNo_cd_RepaidOnSchedule` int(11) NOT NULL,
+  `ReasonNotRepaidOnSchedule` text,
+  `How was Loan Amount Invested` text NOT NULL,
+  `Additional Income Generated` decimal(19,6) NOT NULL,
+  `Additional Income Used For` text NOT NULL,
+  `YesNo_cd_NewJobsCreated` int(11) NOT NULL,
+  `Number of Jobs Created` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_impact measurement` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `impact measurement`
+--
+
+LOCK TABLES `impact measurement` WRITE;
+/*!40000 ALTER TABLE `impact measurement` DISABLE KEYS */;
+/*!40000 ALTER TABLE `impact measurement` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `loan additional data`
+--
+
+DROP TABLE IF EXISTS `loan additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `loan additional data` (
+  `loan_id` bigint(20) NOT NULL,
+  `PurposeOfLoan_cd` int(11) NOT NULL,
+  `CollateralType_cd` int(11) NOT NULL,
+  `Collateral notes` text NOT NULL,
+  `YesNo_cd_Guarantor` int(11) NOT NULL,
+  `Guarantor name` varchar(100) DEFAULT NULL,
+  `Guarantor relation` varchar(100) DEFAULT NULL,
+  `Guarantor address` varchar(100) DEFAULT NULL,
+  `Guarantor telephone number` varchar(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_loan_additional_data` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `loan additional data`
+--
+
+LOCK TABLES `loan additional data` WRITE;
+/*!40000 ALTER TABLE `loan additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `loan additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser`
+--
+
+DROP TABLE IF EXISTS `m_appuser`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser`
+--
+
+LOCK TABLES `m_appuser` WRITE;
+/*!40000 ALTER TABLE `m_appuser` DISABLE KEYS */;
+INSERT INTO `m_appuser` VALUES (1,0,1,'admin','App','Administrator','5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a','demomfi@mifos.org','\0','','','',''),(2,0,1,'keithwoodlock','Keith','Woodlock','4f607e9b6cffbe7d3db92d4bfa3391c7aa751727b4ea29d08fddf9dd72e6e7e3','keithwoodlock@gmail.com','\0','','','','');
+/*!40000 ALTER TABLE `m_appuser` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser_role`
+--
+
+DROP TABLE IF EXISTS `m_appuser_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser_role`
+--
+
+LOCK TABLES `m_appuser_role` WRITE;
+/*!40000 ALTER TABLE `m_appuser_role` DISABLE KEYS */;
+INSERT INTO `m_appuser_role` VALUES (1,1),(2,1);
+/*!40000 ALTER TABLE `m_appuser_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar`
+--
+
+DROP TABLE IF EXISTS `m_calendar`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `title` varchar(50) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar`
+--
+
+LOCK TABLES `m_calendar` WRITE;
+/*!40000 ALTER TABLE `m_calendar` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar_instance`
+--
+
+DROP TABLE IF EXISTS `m_calendar_instance`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar_instance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `entity_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar_instance`
+--
+
+LOCK TABLES `m_calendar_instance` WRITE;
+/*!40000 ALTER TABLE `m_calendar_instance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar_instance` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_charge`
+--
+
+DROP TABLE IF EXISTS `m_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_charge`
+--
+
+LOCK TABLES `m_charge` WRITE;
+/*!40000 ALTER TABLE `m_charge` DISABLE KEYS */;
+INSERT INTO `m_charge` VALUES (1,'Bank Fee (per installment)','UGX',1,2,1,'1500.000000',0,1,0);
+/*!40000 ALTER TABLE `m_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client`
+--
+
+DROP TABLE IF EXISTS `m_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `image_key` varchar(500) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client`
+--
+
+LOCK TABLES `m_client` WRITE;
+/*!40000 ALTER TABLE `m_client` DISABLE KEYS */;
+INSERT INTO `m_client` VALUES (1,'000000001',2,NULL,'Test',NULL,'One',NULL,'Test One',NULL,'2011-02-01',0);
+/*!40000 ALTER TABLE `m_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client_identifier`
+--
+
+DROP TABLE IF EXISTS `m_client_identifier`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client_identifier`
+--
+
+LOCK TABLES `m_client_identifier` WRITE;
+/*!40000 ALTER TABLE `m_client_identifier` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_identifier` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code`
+--
+
+DROP TABLE IF EXISTS `m_code`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code`
+--
+
+LOCK TABLES `m_code` WRITE;
+/*!40000 ALTER TABLE `m_code` DISABLE KEYS */;
+INSERT INTO `m_code` VALUES (1,'Customer Identifier',1),(2,'LoanCollateral',1),(3,'LoanPurpose',1),(4,'Gender',1),(5,'YesNo',1),(6,'GuarantorRelationship',1),(7,'FieldOfEmployment',0),(8,'EducationLevel',0),(9,'MaritalStatus',0),(10,'PovertyStatus',0);
+/*!40000 ALTER TABLE `m_code` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code_value`
+--
+
+DROP TABLE IF EXISTS `m_code_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code_value`
+--
+
+LOCK TABLES `m_code_value` WRITE;
+/*!40000 ALTER TABLE `m_code_value` DISABLE KEYS */;
+INSERT INTO `m_code_value` VALUES (1,1,'Passport',1),(2,1,'Id',2),(3,1,'Drivers License',3),(4,1,'Any Other Id Type',4),(5,6,'Spouse',0),(6,6,'Parent',0),(7,6,'Sibling',0),(8,6,'Business Associate',0),(9,6,'Other',0),(10,7,'option.Banker',1),(11,7,'option.SoftwareDeveloper',10),(12,8,'option.University',1),(13,8,'option.Secondary',12),(14,8,'option.Primary',13),(15,9,'option.Married',1),(16,9,'option.Single',15),(17,9,'option.Divorced',16),(18,9,'option.Widow',17),(19,10,'option.PovertyStatus.Band1',1),(20,10,'option.PovertyStatus.Band2',19),(21,10,'option.PovertyStatus.Band3',20),(22,2,'option.House',1),(23,2,'option.Television',22),(24,2,'option.Gold',23),(25,3,'option.Agriculture',1),(26,3,'option.Manufacturing',25),(27,3,'option.HousingImprovement',26),(28,4,'option.Male',1),(29,4,'option.Female',28),(30,5,'option.Yes',1),(31,5,'option.No',30);
+/*!40000 ALTER TABLE `m_code_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_currency`
+--
+
+DROP TABLE IF EXISTS `m_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_currency`
+--
+
+LOCK TABLES `m_currency` WRITE;
+/*!40000 ALTER TABLE `m_currency` DISABLE KEYS */;
+INSERT INTO `m_currency` VALUES (1,'AED',2,NULL,'UAE Dirham','currency.AED'),(2,'AFN',2,NULL,'Afghanistan Afghani','currency.AFN'),(3,'ALL',2,NULL,'Albanian Lek','currency.ALL'),(4,'AMD',2,NULL,'Armenian Dram','currency.AMD'),(5,'ANG',2,NULL,'Netherlands Antillian Guilder','currency.ANG'),(6,'AOA',2,NULL,'Angolan Kwanza','currency.AOA'),(7,'ARS',2,'$','Argentine Peso','currency.ARS'),(8,'AUD',2,'A$','Australian Dollar','currency.AUD'),(9,'AWG',2,NULL,'Aruban Guilder','currency.AWG'),(10,'AZM',2,NULL,'Azerbaijanian Manat','currency.AZM'),(11,'BAM',2,NULL,'Bosnia and Herzegovina Convertible Marks','currency.BAM'),(12,'BBD',2,NULL,'Barbados Dollar','currency.BBD'),(13,'BDT',2,NULL,'Bangladesh Taka','currency.BDT'),(14,'BGN',2,NULL,'Bulgarian Lev','currency.BGN'),(15,'BHD',3,NULL,'Bahraini Dinar','currency.BHD'),(16,'BIF',0,NULL,'Burundi Franc','currency.BIF'),(17,'BMD',2,NULL,'Bermudian Dollar','currency.BMD'),(18,'BND',2,'B$','Brunei Dollar','currency.BND'),(19,'BOB',2,'Bs.','Bolivian Boliviano','currency.BOB'),(20,'BRL',2,'R$','Brazilian Real','currency.BRL'),(21,'BSD',2,NULL,'Bahamian Dollar','currency.BSD'),(22,'BTN',2,NULL,'Bhutan Ngultrum','currency.BTN'),(23,'BWP',2,NULL,'Botswana Pula','currency.BWP'),(24,'BYR',0,NULL,'Belarussian Ruble','currency.BYR'),(25,'BZD',2,'BZ$','Belize Dollar','currency.BZD'),(26,'CAD',2,NULL,'Canadian Dollar','currency.CAD'),(27,'CDF',2,NULL,'Franc Congolais','currency.CDF'),(28,'CHF',2,NULL,'Swiss Franc','currency.CHF'),(29,'CLP',0,'$','Chilean Peso','currency.CLP'),(30,'CNY',2,NULL,'Chinese Yuan Renminbi','currency.CNY'),(31,'COP',2,'$','Colombian Peso','currency.COP'),(32,'CRC',2,'₡','Costa Rican Colon','currency.CRC'),(33,'CSD',2,NULL,'Serbian Dinar','currency.CSD'),(34,'CUP',2,'$MN','Cuban Peso','currency.CUP'),(35,'CVE',2,NULL,'Cape Verde Escudo','currency.CVE'),(36,'CYP',2,NULL,'Cyprus Pound','currency.CYP'),(37,'CZK',2,NULL,'Czech Koruna','currency.CZK'),(38,'DJF',0,NULL,'Djibouti Franc','currency.DJF'),(39,'DKK',2,NULL,'Danish Krone','currency.DKK'),(40,'DOP',2,'RD$','Dominican Peso','currency.DOP'),(41,'DZD',2,NULL,'Algerian Dinar','currency.DZD'),(42,'EEK',2,NULL,'Estonian Kroon','currency.EEK'),(43,'EGP',2,NULL,'Egyptian Pound','currency.EGP'),(44,'ERN',2,NULL,'Eritrea Nafka','currency.ERN'),(45,'ETB',2,NULL,'Ethiopian Birr','currency.ETB'),(46,'EUR',2,'€','Euro','currency.EUR'),(47,'FJD',2,NULL,'Fiji Dollar','currency.FJD'),(48,'FKP',2,NULL,'Falkland Islands Pound','currency.FKP'),(49,'GBP',2,NULL,'Pound Sterling','currency.GBP'),(50,'GEL',2,NULL,'Georgian Lari','currency.GEL'),(51,'GHC',2,'GHc','Ghana Cedi','currency.GHC'),(52,'GIP',2,NULL,'Gibraltar Pound','currency.GIP'),(53,'GMD',2,NULL,'Gambian Dalasi','currency.GMD'),(54,'GNF',0,NULL,'Guinea Franc','currency.GNF'),(55,'GTQ',2,'Q','Guatemala Quetzal','currency.GTQ'),(56,'GYD',2,NULL,'Guyana Dollar','currency.GYD'),(57,'HKD',2,NULL,'Hong Kong Dollar','currency.HKD'),(58,'HNL',2,'L','Honduras Lempira','currency.HNL'),(59,'HRK',2,NULL,'Croatian Kuna','currency.HRK'),(60,'HTG',2,'G','Haiti Gourde','currency.HTG'),(61,'HUF',2,NULL,'Hungarian Forint','currency.HUF'),(62,'IDR',2,NULL,'Indonesian Rupiah','currency.IDR'),(63,'ILS',2,NULL,'New Israeli Shekel','currency.ILS'),(64,'INR',2,'₹','Indian Rupee','currency.INR'),(65,'IQD',3,NULL,'Iraqi Dinar','currency.IQD'),(66,'IRR',2,NULL,'Iranian Rial','currency.IRR'),(67,'ISK',0,NULL,'Iceland Krona','currency.ISK'),(68,'JMD',2,NULL,'Jamaican Dollar','currency.JMD'),(69,'JOD',3,NULL,'Jordanian Dinar','currency.JOD'),(70,'JPY',0,NULL,'Japanese Yen','currency.JPY'),(71,'KES',2,'KSh','Kenyan Shilling','currency.KES'),(72,'KGS',2,NULL,'Kyrgyzstan Som','currency.KGS'),(73,'KHR',2,NULL,'Cambodia Riel','currency.KHR'),(74,'KMF',0,NULL,'Comoro Franc','currency.KMF'),(75,'KPW',2,NULL,'North Korean Won','currency.KPW'),(76,'KRW',0,NULL,'Korean Won','currency.KRW'),(77,'KWD',3,NULL,'Kuwaiti Dinar','currency.KWD'),(78,'KYD',2,NULL,'Cayman Islands Dollar','currency.KYD'),(79,'KZT',2,NULL,'Kazakhstan Tenge','currency.KZT'),(80,'LAK',2,NULL,'Lao Kip','currency.LAK'),(81,'LBP',2,'L£','Lebanese Pound','currency.LBP'),(82,'LKR',2,NULL,'Sri Lanka Rupee','currency.LKR'),(83,'LRD',2,NULL,'Liberian Dollar','currency.LRD'),(84,'LSL',2,NULL,'Lesotho Loti','currency.LSL'),(85,'LTL',2,NULL,'Lithuanian Litas','currency.LTL'),(86,'LVL',2,NULL,'Latvian Lats','currency.LVL'),(87,'LYD',3,NULL,'Libyan Dinar','currency.LYD'),(88,'MAD',2,NULL,'Moroccan Dirham','currency.MAD'),(89,'MDL',2,NULL,'Moldovan Leu','currency.MDL'),(90,'MGA',2,NULL,'Malagasy Ariary','currency.MGA'),(91,'MKD',2,NULL,'Macedonian Denar','currency.MKD'),(92,'MMK',2,'K','Myanmar Kyat','currency.MMK'),(93,'MNT',2,NULL,'Mongolian Tugrik','currency.MNT'),(94,'MOP',2,NULL,'Macau Pataca','currency.MOP'),(95,'MRO',2,NULL,'Mauritania Ouguiya','currency.MRO'),(96,'MTL',2,NULL,'Maltese Lira','currency.MTL'),(97,'MUR',2,NULL,'Mauritius Rupee','currency.MUR'),(98,'MVR',2,NULL,'Maldives Rufiyaa','currency.MVR'),(99,'MWK',2,NULL,'Malawi Kwacha','currency.MWK'),(100,'MXN',2,'$','Mexican Peso','currency.MXN'),(101,'MYR',2,NULL,'Malaysian Ringgit','currency.MYR'),(102,'MZM',2,NULL,'Mozambique Metical','currency.MZM'),(103,'NAD',2,NULL,'Namibia Dollar','currency.NAD'),(104,'NGN',2,NULL,'Nigerian Naira','currency.NGN'),(105,'NIO',2,'C$','Nicaragua Cordoba Oro','currency.NIO'),(106,'NOK',2,NULL,'Norwegian Krone','currency.NOK'),(107,'NPR',2,NULL,'Nepalese Rupee','currency.NPR'),(108,'NZD',2,NULL,'New Zealand Dollar','currency.NZD'),(109,'OMR',3,NULL,'Rial Omani','currency.OMR'),(110,'PAB',2,'B/.','Panama Balboa','currency.PAB'),(111,'PEN',2,'S/.','Peruvian Nuevo Sol','currency.PEN'),(112,'PGK',2,NULL,'Papua New Guinea Kina','currency.PGK'),(113,'PHP',2,NULL,'Philippine Peso','currency.PHP'),(114,'PKR',2,NULL,'Pakistan Rupee','currency.PKR'),(115,'PLN',2,NULL,'Polish Zloty','currency.PLN'),(116,'PYG',0,'₲','Paraguayan Guarani','currency.PYG'),(117,'QAR',2,NULL,'Qatari Rial','currency.QAR'),(118,'RON',2,NULL,'Romanian Leu','currency.RON'),(119,'RUB',2,NULL,'Russian Ruble','currency.RUB'),(120,'RWF',0,NULL,'Rwanda Franc','currency.RWF'),(121,'SAR',2,NULL,'Saudi Riyal','currency.SAR'),(122,'SBD',2,NULL,'Solomon Islands Dollar','currency.SBD'),(123,'SCR',2,NULL,'Seychelles Rupee','currency.SCR'),(124,'SDD',2,NULL,'Sudanese Dinar','currency.SDD'),(125,'SEK',2,NULL,'Swedish Krona','currency.SEK'),(126,'SGD',2,NULL,'Singapore Dollar','currency.SGD'),(127,'SHP',2,NULL,'St Helena Pound','currency.SHP'),(128,'SIT',2,NULL,'Slovenian Tolar','currency.SIT'),(129,'SKK',2,NULL,'Slovak Koruna','currency.SKK'),(130,'SLL',2,NULL,'Sierra Leone Leone','currency.SLL'),(131,'SOS',2,NULL,'Somali Shilling','currency.SOS'),(132,'SRD',2,NULL,'Surinam Dollar','currency.SRD'),(133,'STD',2,NULL,'Sao Tome and Principe Dobra','currency.STD'),(134,'SVC',2,NULL,'El Salvador Colon','currency.SVC'),(135,'SYP',2,NULL,'Syrian Pound','currency.SYP'),(136,'SZL',2,NULL,'Swaziland Lilangeni','currency.SZL'),(137,'THB',2,NULL,'Thai Baht','currency.THB'),(138,'TJS',2,NULL,'Tajik Somoni','currency.TJS'),(139,'TMM',2,NULL,'Turkmenistan Manat','currency.TMM'),(140,'TND',3,'DT','Tunisian Dinar','currency.TND'),(141,'TOP',2,NULL,'Tonga Pa\'anga','currency.TOP'),(142,'TRY',2,NULL,'Turkish Lira','currency.TRY'),(143,'TTD',2,NULL,'Trinidad and Tobago Dollar','currency.TTD'),(144,'TWD',2,NULL,'New Taiwan Dollar','currency.TWD'),(145,'TZS',2,NULL,'Tanzanian Shilling','currency.TZS'),(146,'UAH',2,NULL,'Ukraine Hryvnia','currency.UAH'),(147,'UGX',2,'USh','Uganda Shilling','currency.UGX'),(148,'USD',2,'$','US Dollar','currency.USD'),(149,'UYU',2,'$U','Peso Uruguayo','currency.UYU'),(150,'UZS',2,NULL,'Uzbekistan Sum','currency.UZS'),(151,'VEB',2,'Bs.F.','Venezuelan Bolivar','currency.VEB'),(152,'VND',2,NULL,'Vietnamese Dong','currency.VND'),(153,'VUV',0,NULL,'Vanuatu Vatu','currency.VUV'),(154,'WST',2,NULL,'Samoa Tala','currency.WST'),(155,'XAF',0,NULL,'CFA Franc BEAC','currency.XAF'),(156,'XCD',2,NULL,'East Caribbean Dollar','currency.XCD'),(157,'XDR',5,NULL,'SDR (Special Drawing Rights)','currency.XDR'),(158,'XOF',0,'CFA','CFA Franc BCEAO','currency.XOF'),(159,'XPF',0,NULL,'CFP Franc','currency.XPF'),(160,'YER',2,NULL,'Yemeni Rial','currency.YER'),(161,'ZAR',2,'R','South African Rand','currency.ZAR'),(162,'ZMK',2,NULL,'Zambian Kwacha','currency.ZMK'),(163,'ZWD',2,NULL,'Zimbabwe Dollar','currency.ZWD');
+/*!40000 ALTER TABLE `m_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_document`
+--
+
+DROP TABLE IF EXISTS `m_document`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(50) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_document`
+--
+
+LOCK TABLES `m_document` WRITE;
+/*!40000 ALTER TABLE `m_document` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_document` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_fund`
+--
+
+DROP TABLE IF EXISTS `m_fund`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_fund`
+--
+
+LOCK TABLES `m_fund` WRITE;
+/*!40000 ALTER TABLE `m_fund` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_fund` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group`
+--
+
+DROP TABLE IF EXISTS `m_group`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `level_Id` int(11) NOT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`,`level_Id`),
+  UNIQUE KEY `external_id` (`external_id`,`level_Id`),
+  KEY `office_id` (`office_id`),
+  KEY `staff_id` (`staff_id`),
+  KEY `Parent_Id_reference` (`parent_id`),
+  KEY `FK_m_group_level` (`level_Id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_Id`) REFERENCES `m_group_level` (`id`),
+  CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group`
+--
+
+LOCK TABLES `m_group` WRITE;
+/*!40000 ALTER TABLE `m_group` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_client`
+--
+
+DROP TABLE IF EXISTS `m_group_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_client`
+--
+
+LOCK TABLES `m_group_client` WRITE;
+/*!40000 ALTER TABLE `m_group_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_level`
+--
+
+DROP TABLE IF EXISTS `m_group_level`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_level` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) DEFAULT NULL,
+  `super_parent` tinyint(1) NOT NULL,
+  `level_name` varchar(100) NOT NULL,
+  `recursable` tinyint(1) NOT NULL,
+  `can_have_clients` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `Parent_levelId_reference` (`parent_id`),
+  CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_level`
+--
+
+LOCK TABLES `m_group_level` WRITE;
+/*!40000 ALTER TABLE `m_group_level` DISABLE KEYS */;
+INSERT INTO `m_group_level` VALUES (1,NULL,1,'Center',1,0),(2,1,0,'Group',0,1);
+/*!40000 ALTER TABLE `m_group_level` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_guarantor`
+--
+
+DROP TABLE IF EXISTS `m_guarantor`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_guarantor` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `client_reln_cv_id` int(11) DEFAULT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `dob` date DEFAULT NULL,
+  `address_line_1` varchar(500) DEFAULT NULL,
+  `address_line_2` varchar(500) DEFAULT NULL,
+  `city` varchar(50) DEFAULT NULL,
+  `state` varchar(50) DEFAULT NULL,
+  `country` varchar(50) DEFAULT NULL,
+  `zip` varchar(20) DEFAULT NULL,
+  `house_phone_number` varchar(20) DEFAULT NULL,
+  `mobile_number` varchar(20) DEFAULT NULL,
+  `comment` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_m_loan` (`loan_id`),
+  KEY `FK_m_guarantor_m_code_value` (`client_reln_cv_id`),
+  CONSTRAINT `FK_m_guarantor_m_code_value` FOREIGN KEY (`client_reln_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_guarantor`
+--
+
+LOCK TABLES `m_guarantor` WRITE;
+/*!40000 ALTER TABLE `m_guarantor` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan`
+--
+
+DROP TABLE IF EXISTS `m_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `loanpurpose_cv_id` int(11) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `min_principal_amount` decimal(19,6) NOT NULL,
+  `max_principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `disbursedon_userid` bigint(20) DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `principal_disbursed_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `rescheduledon_date` date DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `writtenoffon_date` date DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loanpurpose_codevalue` (`loanpurpose_cv_id`),
+  KEY `FK_submittedon_userid` (`submittedon_userid`),
+  KEY `FK_approvedon_userid` (`approvedon_userid`),
+  KEY `FK_rejectedon_userid` (`rejectedon_userid`),
+  KEY `FK_withdrawnon_userid` (`withdrawnon_userid`),
+  KEY `FK_disbursedon_userid` (`disbursedon_userid`),
+  KEY `FK_closedon_userid` (`closedon_userid`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_approvedon_userid` FOREIGN KEY (`approvedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_closedon_userid` FOREIGN KEY (`closedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_disbursedon_userid` FOREIGN KEY (`disbursedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loanpurpose_codevalue` FOREIGN KEY (`loanpurpose_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_rejectedon_userid` FOREIGN KEY (`rejectedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_submittedon_userid` FOREIGN KEY (`submittedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_withdrawnon_userid` FOREIGN KEY (`withdrawnon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan`
+--
+
+LOCK TABLES `m_loan` WRITE;
+/*!40000 ALTER TABLE `m_loan` DISABLE KEYS */;
+INSERT INTO `m_loan` VALUES (1,'000000001',NULL,1,NULL,1,NULL,2,25,200,'UGX',2,'1000000.000000','0.000000','1000000000000.000000',NULL,'24.000000',3,'24.000000',1,1,12,2,1,2,12,1,'2011-04-01',1,'2011-04-01',1,'2011-04-01',NULL,NULL,NULL,NULL,'2012-04-01','2012-04-01',NULL,NULL,'0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000','0.000000',NULL,NULL,NULL,NULL,NULL,NULL,2);
+/*!40000 ALTER TABLE `m_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_arrears_aging`
+--
+
+DROP TABLE IF EXISTS `m_loan_arrears_aging`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_arrears_aging` (
+  `loan_id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `principal_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `overdue_since_date_derived` date DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_arrears_aging_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_arrears_aging`
+--
+
+LOCK TABLES `m_loan_arrears_aging` WRITE;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_charge`
+--
+
+LOCK TABLES `m_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_loan_charge` DISABLE KEYS */;
+INSERT INTO `m_loan_charge` VALUES (1,1,1,0,2,'2011-06-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(2,1,1,0,2,'2011-12-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(3,1,1,0,2,'2011-09-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(4,1,1,0,2,'2011-08-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(5,1,1,0,2,'2011-11-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(6,1,1,0,2,'2011-05-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(7,1,1,0,2,'2012-03-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(8,1,1,0,2,'2012-02-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(9,1,1,0,2,'2012-04-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(10,1,1,0,2,'2012-01-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(11,1,1,0,2,'2011-10-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0),(12,1,1,0,2,'2011-07-01',1,NULL,NULL,'1500.000000','0.000000',NULL,NULL,'1500.000000',0,0);
+/*!40000 ALTER TABLE `m_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_collateral`
+--
+
+DROP TABLE IF EXISTS `m_loan_collateral`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_collateral` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_cv_id` int(11) NOT NULL,
+  `value` decimal(19,6) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_collateral_m_loan` (`loan_id`),
+  KEY `FK_collateral_code_value` (`type_cv_id`),
+  CONSTRAINT `FK_collateral_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_collateral_code_value` FOREIGN KEY (`type_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_collateral`
+--
+
+LOCK TABLES `m_loan_collateral` WRITE;
+/*!40000 ALTER TABLE `m_loan_collateral` DISABLE KEYS */;
+INSERT INTO `m_loan_collateral` VALUES (1,1,23,NULL,' small description.');
+/*!40000 ALTER TABLE `m_loan_collateral` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_officer_assignment_history`
+--
+
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_officer_assignment_history`
+--
+
+LOCK TABLES `m_loan_officer_assignment_history` WRITE;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` DISABLE KEYS */;
+INSERT INTO `m_loan_officer_assignment_history` VALUES (1,1,2,'2011-04-01',NULL,1,'2013-04-05 12:42:09','2013-04-05 12:42:09',1);
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_repayment_schedule`
+--
+
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_repayment_schedule`
+--
+
+LOCK TABLES `m_loan_repayment_schedule` WRITE;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` DISABLE KEYS */;
+INSERT INTO `m_loan_repayment_schedule` VALUES (1,1,'2011-04-01','2011-05-01',1,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(2,1,'2011-05-01','2011-06-01',2,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(3,1,'2011-06-01','2011-07-01',3,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(4,1,'2011-07-01','2011-08-01',4,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(5,1,'2011-08-01','2011-09-01',5,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(6,1,'2011-09-01','2011-10-01',6,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(7,1,'2011-10-01','2011-11-01',7,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(8,1,'2011-11-01','2011-12-01',8,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(9,1,'2011-12-01','2012-01-01',9,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(10,1,'2012-01-01','2012-02-01',10,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(11,1,'2012-02-01','2012-03-01',11,'83333.330000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL),(12,1,'2012-03-01','2012-04-01',12,'83333.370000',NULL,NULL,'20000.000000',NULL,NULL,'1500.000000',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'\0',1,'2013-04-05 12:41:57','2013-04-05 12:41:57',1,NULL);
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_transaction`
+--
+
+DROP TABLE IF EXISTS `m_loan_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_transaction`
+--
+
+LOCK TABLES `m_loan_transaction` WRITE;
+/*!40000 ALTER TABLE `m_loan_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_note`
+--
+
+DROP TABLE IF EXISTS `m_note`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK_m_note_m_group` (`group_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_note_m_group` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_note`
+--
+
+LOCK TABLES `m_note` WRITE;
+/*!40000 ALTER TABLE `m_note` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_note` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office`
+--
+
+DROP TABLE IF EXISTS `m_office`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office`
+--
+
+LOCK TABLES `m_office` WRITE;
+/*!40000 ALTER TABLE `m_office` DISABLE KEYS */;
+INSERT INTO `m_office` VALUES (1,NULL,'.','1','CEDA Microfinance Ltd.','2009-01-01'),(2,1,'.2.','2','Uganda (Kampala)','2009-01-01');
+/*!40000 ALTER TABLE `m_office` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office_transaction`
+--
+
+DROP TABLE IF EXISTS `m_office_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office_transaction`
+--
+
+LOCK TABLES `m_office_transaction` WRITE;
+/*!40000 ALTER TABLE `m_office_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_office_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_organisation_currency`
+--
+
+DROP TABLE IF EXISTS `m_organisation_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_organisation_currency`
+--
+
+LOCK TABLES `m_organisation_currency` WRITE;
+/*!40000 ALTER TABLE `m_organisation_currency` DISABLE KEYS */;
+INSERT INTO `m_organisation_currency` VALUES (22,'UGX',2,'Uganda Shilling','USh','currency.UGX');
+/*!40000 ALTER TABLE `m_organisation_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_permission`
+--
+
+DROP TABLE IF EXISTS `m_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=308 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_permission`
+--
+
+LOCK TABLES `m_permission` WRITE;
+/*!40000 ALTER TABLE `m_permission` DISABLE KEYS */;
+INSERT INTO `m_permission` VALUES (1,'special','ALL_FUNCTIONS',NULL,NULL,0),(2,'special','ALL_FUNCTIONS_READ',NULL,NULL,0),(3,'special','CHECKER_SUPER_USER',NULL,NULL,0),(4,'special','REPORTING_SUPER_USER',NULL,NULL,0),(5,'authorisation','READ_PERMISSION','PERMISSION','READ',0),(6,'authorisation','PERMISSIONS_ROLE','ROLE','PERMISSIONS',0),(7,'authorisation','CREATE_ROLE','ROLE','CREATE',0),(8,'authorisation','CREATE_ROLE_CHECKER','ROLE','CREATE',0),(9,'authorisation','READ_ROLE','ROLE','READ',0),(10,'authorisation','UPDATE_ROLE','ROLE','UPDATE',0),(11,'authorisation','UPDATE_ROLE_CHECKER','ROLE','UPDATE',0),(12,'authorisation','DELETE_ROLE','ROLE','DELETE',0),(13,'authorisation','DELETE_ROLE_CHECKER','ROLE','DELETE',0),(14,'authorisation','CREATE_USER','USER','CREATE',0),(15,'authorisation','CREATE_USER_CHECKER','USER','CREATE',0),(16,'authorisation','READ_USER','USER','READ',0),(17,'authorisation','UPDATE_USER','USER','UPDATE',0),(18,'authorisation','UPDATE_USER_CHECKER','USER','UPDATE',0),(19,'authorisation','DELETE_USER','USER','DELETE',0),(20,'authorisation','DELETE_USER_CHECKER','USER','DELETE',0),(21,'configuration','READ_CONFIGURATION','CONFIGURATION','READ',0),(22,'configuration','UPDATE_CONFIGURATION','CONFIGURATION','UPDATE',0),(23,'configuration','UPDATE_CONFIGURATION_CHECKER','CONFIGURATION','UPDATE',0),(24,'configuration','READ_CODE','CODE','READ',0),(25,'configuration','CREATE_CODE','CODE','CREATE',0),(26,'configuration','CREATE_CODE_CHECKER','CODE','CREATE',0),(27,'configuration','UPDATE_CODE','CODE','UPDATE',0),(28,'configuration','UPDATE_CODE_CHECKER','CODE','UPDATE',0),(29,'configuration','DELETE_CODE','CODE','DELETE',0),(30,'configuration','DELETE_CODE_CHECKER','CODE','DELETE',0),(31,'configuration','READ_CODEVALUE','CODEVALUE','READ',0),(32,'configuration','CREATE_CODEVALUE','CODEVALUE','CREATE',0),(33,'configuration','CREATE_CODEVALUE_CHECKER','CODEVALUE','CREATE',0),(34,'configuration','UPDATE_CODEVALUE','CODEVALUE','UPDATE',0),(35,'configuration','UPDATE_CODEVALUE_CHECKER','CODEVALUE','UPDATE',0),(36,'configuration','DELETE_CODEVALUE','CODEVALUE','DELETE',0),(37,'configuration','DELETE_CODEVALUE_CHECKER','CODEVALUE','DELETE',0),(38,'configuration','READ_CURRENCY','CURRENCY','READ',0),(39,'configuration','UPDATE_CURRENCY','CURRENCY','UPDATE',0),(40,'configuration','UPDATE_CURRENCY_CHECKER','CURRENCY','UPDATE',0),(41,'configuration','UPDATE_PERMISSION','PERMISSION','UPDATE',0),(42,'configuration','UPDATE_PERMISSION_CHECKER','PERMISSION','UPDATE',0),(43,'configuration','READ_DATATABLE','DATATABLE','READ',0),(44,'configuration','REGISTER_DATATABLE','DATATABLE','REGISTER',0),(45,'configuration','REGISTER_DATATABLE_CHECKER','DATATABLE','REGISTER',0),(46,'configuration','DEREGISTER_DATATABLE','DATATABLE','DEREGISTER',0),(47,'configuration','DEREGISTER_DATATABLE_CHECKER','DATATABLE','DEREGISTER',0),(48,'configuration','READ_AUDIT','AUDIT','READ',0),(49,'configuration','CREATE_CALENDAR','CALENDAR','CREATE',0),(50,'configuration','READ_CALENDAR','CALENDAR','READ',0),(51,'configuration','UPDATE_CALENDAR','CALENDAR','UPDATE',0),(52,'configuration','DELETE_CALENDAR','CALENDAR','DELETE',0),(53,'configuration','CREATE_CALENDAR_CHECKER','CALENDAR','CREATE',0),(54,'configuration','UPDATE_CALENDAR_CHECKER','CALENDAR','UPDATE',0),(55,'configuration','DELETE_CALENDAR_CHECKER','CALENDAR','DELETE',0),(57,'organisation','READ_CHARGE','CHARGE','READ',0),(58,'organisation','CREATE_CHARGE','CHARGE','CREATE',0),(59,'organisation','CREATE_CHARGE_CHECKER','CHARGE','CREATE',0),(60,'organisation','UPDATE_CHARGE','CHARGE','UPDATE',0),(61,'organisation','UPDATE_CHARGE_CHECKER','CHARGE','UPDATE',0),(62,'organisation','DELETE_CHARGE','CHARGE','DELETE',0),(63,'organisation','DELETE_CHARGE_CHECKER','CHARGE','DELETE',0),(64,'organisation','READ_FUND','FUND','READ',0),(65,'organisation','CREATE_FUND','FUND','CREATE',0),(66,'organisation','CREATE_FUND_CHECKER','FUND','CREATE',0),(67,'organisation','UPDATE_FUND','FUND','UPDATE',0),(68,'organisation','UPDATE_FUND_CHECKER','FUND','UPDATE',0),(69,'organisation','DELETE_FUND','FUND','DELETE',0),(70,'organisation','DELETE_FUND_CHECKER','FUND','DELETE',0),(71,'organisation','READ_LOANPRODUCT','LOANPRODUCT','READ',0),(72,'organisation','CREATE_LOANPRODUCT','LOANPRODUCT','CREATE',0),(73,'organisation','CREATE_LOANPRODUCT_CHECKER','LOANPRODUCT','CREATE',0),(74,'organisation','UPDATE_LOANPRODUCT','LOANPRODUCT','UPDATE',0),(75,'organisation','UPDATE_LOANPRODUCT_CHECKER','LOANPRODUCT','UPDATE',0),(76,'organisation','DELETE_LOANPRODUCT','LOANPRODUCT','DELETE',0),(77,'organisation','DELETE_LOANPRODUCT_CHECKER','LOANPRODUCT','DELETE',0),(78,'organisation','READ_OFFICE','OFFICE','READ',0),(79,'organisation','CREATE_OFFICE','OFFICE','CREATE',0),(80,'organisation','CREATE_OFFICE_CHECKER','OFFICE','CREATE',0),(81,'organisation','UPDATE_OFFICE','OFFICE','UPDATE',0),(82,'organisation','UPDATE_OFFICE_CHECKER','OFFICE','UPDATE',0),(83,'organisation','READ_OFFICETRANSACTION','OFFICETRANSACTION','READ',0),(84,'organisation','DELETE_OFFICE_CHECKER','OFFICE','DELETE',0),(85,'organisation','CREATE_OFFICETRANSACTION','OFFICETRANSACTION','CREATE',0),(86,'organisation','CREATE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','CREATE',0),(87,'organisation','DELETE_OFFICETRANSACTION','OFFICETRANSACTION','DELETE',0),(88,'organisation','DELETE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','DELETE',0),(89,'organisation','READ_STAFF','STAFF','READ',0),(90,'organisation','CREATE_STAFF','STAFF','CREATE',0),(91,'organisation','CREATE_STAFF_CHECKER','STAFF','CREATE',0),(92,'organisation','UPDATE_STAFF','STAFF','UPDATE',0),(93,'organisation','UPDATE_STAFF_CHECKER','STAFF','UPDATE',0),(94,'organisation','DELETE_STAFF','STAFF','DELETE',0),(95,'organisation','DELETE_STAFF_CHECKER','STAFF','DELETE',0),(96,'organisation','READ_SAVINGSPRODUCT','SAVINGSPRODUCT','READ',0),(97,'organisation','CREATE_SAVINGSPRODUCT','SAVINGSPRODUCT','CREATE',0),(98,'organisation','CREATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','CREATE',0),(99,'organisation','UPDATE_SAVINGSPRODUCT','SAVINGSPRODUCT','UPDATE',0),(100,'organisation','UPDATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','UPDATE',0),(101,'organisation','DELETE_SAVINGSPRODUCT','SAVINGSPRODUCT','DELETE',0),(102,'organisation','DELETE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','DELETE',0),(103,'portfolio','READ_LOAN','LOAN','READ',0),(104,'portfolio','CREATE_LOAN','LOAN','CREATE',0),(105,'portfolio','CREATE_LOAN_CHECKER','LOAN','CREATE',0),(106,'portfolio','UPDATE_LOAN','LOAN','UPDATE',0),(107,'portfolio','UPDATE_LOAN_CHECKER','LOAN','UPDATE',0),(108,'portfolio','DELETE_LOAN','LOAN','DELETE',0),(109,'portfolio','DELETE_LOAN_CHECKER','LOAN','DELETE',0),(110,'portfolio','READ_CLIENT','CLIENT','READ',0),(111,'portfolio','CREATE_CLIENT','CLIENT','CREATE',0),(112,'portfolio','CREATE_CLIENT_CHECKER','CLIENT','CREATE',0),(113,'portfolio','UPDATE_CLIENT','CLIENT','UPDATE',0),(114,'portfolio','UPDATE_CLIENT_CHECKER','CLIENT','UPDATE',0),(115,'portfolio','DELETE_CLIENT','CLIENT','DELETE',0),(116,'portfolio','DELETE_CLIENT_CHECKER','CLIENT','DELETE',0),(117,'portfolio','READ_CLIENTIMAGE','CLIENTIMAGE','READ',0),(118,'portfolio','CREATE_CLIENTIMAGE','CLIENTIMAGE','CREATE',0),(119,'portfolio','CREATE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','CREATE',0),(120,'portfolio','DELETE_CLIENTIMAGE','CLIENTIMAGE','DELETE',0),(121,'portfolio','DELETE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','DELETE',0),(122,'portfolio','READ_CLIENTNOTE','CLIENTNOTE','READ',0),(123,'portfolio','CREATE_CLIENTNOTE','CLIENTNOTE','CREATE',0),(124,'portfolio','CREATE_CLIENTNOTE_CHECKER','CLIENTNOTE','CREATE',0),(125,'portfolio','UPDATE_CLIENTNOTE','CLIENTNOTE','UPDATE',0),(126,'portfolio','UPDATE_CLIENTNOTE_CHECKER','CLIENTNOTE','UPDATE',0),(127,'portfolio','DELETE_CLIENTNOTE','CLIENTNOTE','DELETE',0),(128,'portfolio','DELETE_CLIENTNOTE_CHECKER','CLIENTNOTE','DELETE',0),(129,'portfolio','READ_GROUPNOTE','GROUPNOTE','READ',0),(130,'portfolio','CREATE_GROUPNOTE','GROUPNOTE','CREATE',0),(131,'portfolio','UPDATE_GROUPNOTE','GROUPNOTE','UPDATE',0),(132,'portfolio','DELETE_GROUPNOTE','GROUPNOTE','DELETE',0),(133,'portfolio','CREATE_GROUPNOTE_CHECKER','GROUPNOTE','CREATE',0),(134,'portfolio','UPDATE_GROUPNOTE_CHECKER','GROUPNOTE','UPDATE',0),(135,'portfolio','DELETE_GROUPNOTE_CHECKER','GROUPNOTE','DELETE',0),(136,'portfolio','READ_LOANNOTE','LOANNOTE','READ',0),(137,'portfolio','CREATE_LOANNOTE','LOANNOTE','CREATE',0),(138,'portfolio','UPDATE_LOANNOTE','LOANNOTE','UPDATE',0),(139,'portfolio','DELETE_LOANNOTE','LOANNOTE','DELETE',0),(140,'portfolio','CREATE_LOANNOTE_CHECKER','LOANNOTE','CREATE',0),(141,'portfolio','UPDATE_LOANNOTE_CHECKER','LOANNOTE','UPDATE',0),(142,'portfolio','DELETE_LOANNOTE_CHECKER','LOANNOTE','DELETE',0),(143,'portfolio','READ_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','READ',0),(144,'portfolio','CREATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','CREATE',0),(145,'portfolio','UPDATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','UPDATE',0),(146,'portfolio','DELETE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','DELETE',0),(147,'portfolio','CREATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','CREATE',0),(148,'portfolio','UPDATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','UPDATE',0),(149,'portfolio','DELETE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','DELETE',0),(150,'portfolio','READ_SAVINGNOTE','SAVINGNOTE','READ',0),(151,'portfolio','CREATE_SAVINGNOTE','SAVINGNOTE','CREATE',0),(152,'portfolio','UPDATE_SAVINGNOTE','SAVINGNOTE','UPDATE',0),(153,'portfolio','DELETE_SAVINGNOTE','SAVINGNOTE','DELETE',0),(154,'portfolio','CREATE_SAVINGNOTE_CHECKER','SAVINGNOTE','CREATE',0),(155,'portfolio','UPDATE_SAVINGNOTE_CHECKER','SAVINGNOTE','UPDATE',0),(156,'portfolio','DELETE_SAVINGNOTE_CHECKER','SAVINGNOTE','DELETE',0),(157,'portfolio','READ_CLIENTIDENTIFIER','CLIENTIDENTIFIER','READ',0),(158,'portfolio','CREATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','CREATE',0),(159,'portfolio','CREATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','CREATE',0),(160,'portfolio','UPDATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','UPDATE',0),(161,'portfolio','UPDATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','UPDATE',0),(162,'portfolio','DELETE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','DELETE',0),(163,'portfolio','DELETE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','DELETE',0),(164,'portfolio','READ_DOCUMENT','DOCUMENT','READ',0),(165,'portfolio','CREATE_DOCUMENT','DOCUMENT','CREATE',0),(166,'portfolio','CREATE_DOCUMENT_CHECKER','DOCUMENT','CREATE',0),(167,'portfolio','UPDATE_DOCUMENT','DOCUMENT','UPDATE',0),(168,'portfolio','UPDATE_DOCUMENT_CHECKER','DOCUMENT','UPDATE',0),(169,'portfolio','DELETE_DOCUMENT','DOCUMENT','DELETE',0),(170,'portfolio','DELETE_DOCUMENT_CHECKER','DOCUMENT','DELETE',0),(171,'portfolio','READ_GROUP','GROUP','READ',0),(172,'portfolio','CREATE_GROUP','GROUP','CREATE',0),(173,'portfolio','CREATE_GROUP_CHECKER','GROUP','CREATE',0),(174,'portfolio','UPDATE_GROUP','GROUP','UPDATE',0),(175,'portfolio','UPDATE_GROUP_CHECKER','GROUP','UPDATE',0),(176,'portfolio','DELETE_GROUP','GROUP','DELETE',0),(177,'portfolio','DELETE_GROUP_CHECKER','GROUP','DELETE',0),(178,'portfolio','UNASSIGNSTAFF_GROUP','GROUP','UNASSIGNSTAFF',0),(179,'portfolio','UNASSIGNSTAFF_GROUP_CHECKER','GROUP','UNASSIGNSTAFF',0),(180,'portfolio','CREATE_LOANCHARGE','LOANCHARGE','CREATE',0),(181,'portfolio','CREATE_LOANCHARGE_CHECKER','LOANCHARGE','CREATE',0),(182,'portfolio','UPDATE_LOANCHARGE','LOANCHARGE','UPDATE',0),(183,'portfolio','UPDATE_LOANCHARGE_CHECKER','LOANCHARGE','UPDATE',0),(184,'portfolio','DELETE_LOANCHARGE','LOANCHARGE','DELETE',0),(185,'portfolio','DELETE_LOANCHARGE_CHECKER','LOANCHARGE','DELETE',0),(186,'portfolio','WAIVE_LOANCHARGE','LOANCHARGE','WAIVE',0),(187,'portfolio','WAIVE_LOANCHARGE_CHECKER','LOANCHARGE','WAIVE',0),(188,'portfolio','READ_SAVINGSACCOUNT','SAVINGSACCOUNT','READ',0),(189,'portfolio','CREATE_SAVINGSACCOUNT','SAVINGSACCOUNT','CREATE',0),(190,'portfolio','CREATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','CREATE',0),(191,'portfolio','UPDATE_SAVINGSACCOUNT','SAVINGSACCOUNT','UPDATE',0),(192,'portfolio','UPDATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','UPDATE',0),(193,'portfolio','DELETE_SAVINGSACCOUNT','SAVINGSACCOUNT','DELETE',0),(194,'portfolio','DELETE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DELETE',0),(195,'portfolio','READ_GUARANTOR','GUARANTOR','READ',0),(196,'portfolio','CREATE_GUARANTOR','GUARANTOR','CREATE',0),(197,'portfolio','CREATE_GUARANTOR_CHECKER','GUARANTOR','CREATE',0),(198,'portfolio','UPDATE_GUARANTOR','GUARANTOR','UPDATE',0),(199,'portfolio','UPDATE_GUARANTOR_CHECKER','GUARANTOR','UPDATE',0),(200,'portfolio','DELETE_GUARANTOR','GUARANTOR','DELETE',0),(201,'portfolio','DELETE_GUARANTOR_CHECKER','GUARANTOR','DELETE',0),(202,'portfolio','READ_COLLATERAL','COLLATERAL','READ',0),(203,'portfolio','CREATE_COLLATERAL','COLLATERAL','CREATE',0),(204,'portfolio','UPDATE_COLLATERAL','COLLATERAL','UPDATE',0),(205,'portfolio','DELETE_COLLATERAL','COLLATERAL','DELETE',0),(206,'portfolio','CREATE_COLLATERAL_CHECKER','COLLATERAL','CREATE',0),(207,'portfolio','UPDATE_COLLATERAL_CHECKER','COLLATERAL','UPDATE',0),(208,'portfolio','DELETE_COLLATERAL_CHECKER','COLLATERAL','DELETE',0),(209,'transaction_loan','APPROVE_LOAN','LOAN','APPROVE',0),(210,'transaction_loan','APPROVEINPAST_LOAN','LOAN','APPROVEINPAST',0),(211,'transaction_loan','REJECT_LOAN','LOAN','REJECT',0),(212,'transaction_loan','REJECTINPAST_LOAN','LOAN','REJECTINPAST',0),(213,'transaction_loan','WITHDRAW_LOAN','LOAN','WITHDRAW',0),(214,'transaction_loan','WITHDRAWINPAST_LOAN','LOAN','WITHDRAWINPAST',0),(215,'transaction_loan','APPROVALUNDO_LOAN','LOAN','APPROVALUNDO',0),(216,'transaction_loan','DISBURSE_LOAN','LOAN','DISBURSE',0),(217,'transaction_loan','DISBURSEINPAST_LOAN','LOAN','DISBURSEINPAST',0),(218,'transaction_loan','DISBURSALUNDO_LOAN','LOAN','DISBURSALUNDO',0),(219,'transaction_loan','REPAYMENT_LOAN','LOAN','REPAYMENT',0),(220,'transaction_loan','REPAYMENTINPAST_LOAN','LOAN','REPAYMENTINPAST',0),(221,'transaction_loan','ADJUST_LOAN','LOAN','ADJUST',0),(222,'transaction_loan','WAIVEINTERESTPORTION_LOAN','LOAN','WAIVEINTERESTPORTION',0),(223,'transaction_loan','WRITEOFF_LOAN','LOAN','WRITEOFF',0),(224,'transaction_loan','CLOSE_LOAN','LOAN','CLOSE',0),(225,'transaction_loan','CLOSEASRESCHEDULED_LOAN','LOAN','CLOSEASRESCHEDULED',0),(226,'transaction_loan','UPDATELOANOFFICER_LOAN','LOAN','UPDATELOANOFFICER',0),(227,'transaction_loan','UPDATELOANOFFICER_LOAN_CHECKER','LOAN','UPDATELOANOFFICER',0),(228,'transaction_loan','REMOVELOANOFFICER_LOAN','LOAN','REMOVELOANOFFICER',0),(229,'transaction_loan','REMOVELOANOFFICER_LOAN_CHECKER','LOAN','REMOVELOANOFFICER',0),(230,'transaction_loan','BULKREASSIGN_LOAN','LOAN','BULKREASSIGN',0),(231,'transaction_loan','BULKREASSIGN_LOAN_CHECKER','LOAN','BULKREASSIGN',0),(232,'transaction_loan','APPROVE_LOAN_CHECKER','LOAN','APPROVE',0),(233,'transaction_loan','APPROVEINPAST_LOAN_CHECKER','LOAN','APPROVEINPAST',0),(234,'transaction_loan','REJECT_LOAN_CHECKER','LOAN','REJECT',0),(235,'transaction_loan','REJECTINPAST_LOAN_CHECKER','LOAN','REJECTINPAST',0),(236,'transaction_loan','WITHDRAW_LOAN_CHECKER','LOAN','WITHDRAW',0),(237,'transaction_loan','WITHDRAWINPAST_LOAN_CHECKER','LOAN','WITHDRAWINPAST',0),(238,'transaction_loan','APPROVALUNDO_LOAN_CHECKER','LOAN','APPROVALUNDO',0),(239,'transaction_loan','DISBURSE_LOAN_CHECKER','LOAN','DISBURSE',0),(240,'transaction_loan','DISBURSEINPAST_LOAN_CHECKER','LOAN','DISBURSEINPAST',0),(241,'transaction_loan','DISBURSALUNDO_LOAN_CHECKER','LOAN','DISBURSALUNDO',0),(242,'transaction_loan','REPAYMENT_LOAN_CHECKER','LOAN','REPAYMENT',0),(243,'transaction_loan','REPAYMENTINPAST_LOAN_CHECKER','LOAN','REPAYMENTINPAST',0),(244,'transaction_loan','ADJUST_LOAN_CHECKER','LOAN','ADJUST',0),(245,'transaction_loan','WAIVEINTERESTPORTION_LOAN_CHECKER','LOAN','WAIVEINTERESTPORTION',0),(246,'transaction_loan','WRITEOFF_LOAN_CHECKER','LOAN','WRITEOFF',0),(247,'transaction_loan','CLOSE_LOAN_CHECKER','LOAN','CLOSE',0),(248,'transaction_loan','CLOSEASRESCHEDULED_LOAN_CHECKER','LOAN','CLOSEASRESCHEDULED',0),(249,'transaction_savings','DEPOSIT_SAVINGSACCOUNT','SAVINGSACCOUNT','DEPOSIT',0),(250,'transaction_savings','DEPOSIT_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DEPOSIT',0),(251,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT','SAVINGSACCOUNT','WITHDRAWAL',0),(252,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','WITHDRAWAL',0),(253,'transaction_savings','ACTIVATE_SAVINGSACCOUNT','SAVINGSACCOUNT','ACTIVATE',0),(254,'transaction_savings','ACTIVATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','ACTIVATE',0),(255,'transaction_savings','CALCULATEINTEREST_SAVINGSACCOUNT','SAVINGSACCOUNT','CALCULATEINTEREST',0),(256,'transaction_savings','CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','CALCULATEINTEREST',0),(257,'accounting','CREATE_GLACCOUNT','GLACCOUNT','CREATE',0),(258,'accounting','UPDATE_GLACCOUNT','GLACCOUNT','UPDATE',0),(259,'accounting','DELETE_GLACCOUNT','GLACCOUNT','DELETE',0),(260,'accounting','CREATE_GLCLOSURE','GLCLOSURE','CREATE',0),(261,'accounting','UPDATE_GLCLOSURE','GLCLOSURE','UPDATE',0),(262,'accounting','DELETE_GLCLOSURE','GLCLOSURE','DELETE',0),(263,'accounting','CREATE_JOURNALENTRY','JOURNALENTRY','CREATE',0),(264,'accounting','REVERSE_JOURNALENTRY','JOURNALENTRY','REVERSE',0),(265,'report','READ_Active Loans - Details','Active Loans - Details','READ',0),(266,'report','READ_Active Loans - Summary','Active Loans - Summary','READ',0),(267,'report','READ_Active Loans by Disbursal Period','Active Loans by Disbursal Period','READ',0),(268,'report','READ_Active Loans in last installment','Active Loans in last installment','READ',0),(269,'report','READ_Active Loans in last installment Summary','Active Loans in last installment Summary','READ',0),(270,'report','READ_Active Loans Passed Final Maturity','Active Loans Passed Final Maturity','READ',0),(271,'report','READ_Active Loans Passed Final Maturity Summary','Active Loans Passed Final Maturity Summary','READ',0),(272,'report','READ_Aging Detail','Aging Detail','READ',0),(273,'report','READ_Aging Summary (Arrears in Months)','Aging Summary (Arrears in Months)','READ',0),(274,'report','READ_Aging Summary (Arrears in Weeks)','Aging Summary (Arrears in Weeks)','READ',0),(275,'report','READ_Balance Sheet','Balance Sheet','READ',0),(276,'report','READ_Branch Expected Cash Flow','Branch Expected Cash Flow','READ',0),(277,'report','READ_Client Listing','Client Listing','READ',0),(278,'report','READ_Client Loans Listing','Client Loans Listing','READ',0),(279,'report','READ_Expected Payments By Date - Basic','Expected Payments By Date - Basic','READ',0),(280,'report','READ_Expected Payments By Date - Formatted','Expected Payments By Date - Formatted','READ',0),(281,'report','READ_Funds Disbursed Between Dates Summary','Funds Disbursed Between Dates Summary','READ',0),(282,'report','READ_Funds Disbursed Between Dates Summary by Office','Funds Disbursed Between Dates Summary by Office','READ',0),(283,'report','READ_Income Statement','Income Statement','READ',0),(284,'report','READ_Loan Account Schedule','Loan Account Schedule','READ',0),(285,'report','READ_Loans Awaiting Disbursal','Loans Awaiting Disbursal','READ',0),(286,'report','READ_Loans Awaiting Disbursal Summary','Loans Awaiting Disbursal Summary','READ',0),(287,'report','READ_Loans Awaiting Disbursal Summary by Month','Loans Awaiting Disbursal Summary by Month','READ',0),(288,'report','READ_Loans Pending Approval','Loans Pending Approval','READ',0),(289,'report','READ_Obligation Met Loans Details','Obligation Met Loans Details','READ',0),(290,'report','READ_Obligation Met Loans Summary','Obligation Met Loans Summary','READ',0),(291,'report','READ_Portfolio at Risk','Portfolio at Risk','READ',0),(292,'report','READ_Portfolio at Risk by Branch','Portfolio at Risk by Branch','READ',0),(293,'report','READ_Rescheduled Loans','Rescheduled Loans','READ',0),(294,'report','READ_Trial Balance','Trial Balance','READ',0),(295,'report','READ_Written-Off Loans','Written-Off Loans','READ',0),(296,'datatable','CREATE_client additional data','client additional data','CREATE',1),(297,'datatable','CREATE_impact measurement','impact measurement','CREATE',1),(298,'datatable','CREATE_loan additional data','loan additional data','CREATE',1),(299,'datatable','READ_client additional data','client additional data','READ',1),(300,'datatable','READ_impact measurement','impact measurement','READ',1),(301,'datatable','READ_loan additional data','loan additional data','READ',1),(302,'datatable','UPDATE_client additional data','client additional data','UPDATE',1),(303,'datatable','UPDATE_impact measurement','impact measurement','UPDATE',1),(304,'datatable','UPDATE_loan additional data','loan additional data','UPDATE',1),(305,'datatable','DELETE_client additional data','client additional data','DELETE',1),(306,'datatable','DELETE_impact measurement','impact measurement','DELETE',1),(307,'datatable','DELETE_loan additional data','loan additional data','DELETE',1);
+/*!40000 ALTER TABLE `m_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_portfolio_command_source`
+--
+
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `subresource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_portfolio_command_source`
+--
+
+LOCK TABLES `m_portfolio_command_source` WRITE;
+/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
+INSERT INTO `m_portfolio_command_source` VALUES (1,'CREATE','CLIENT',2,NULL,1,NULL,NULL,'/clients/template',1,NULL,'{\"officeId\":\"2\",\"firstname\":\"Test\",\"middlename\":\"\",\"lastname\":\"One\",\"fullname\":\"\",\"externalId\":\"\",\"dateFormat\":\"yyyy-MM-dd\",\"locale\":\"en\",\"joinedDate\":\"2011-02-01\"}',1,'2013-04-05 12:38:05',NULL,NULL,1),(2,'CREATE','LOAN',2,NULL,1,1,NULL,'/loans',1,NULL,'{\"clientId\":\"1\",\"dateFormat\":\"yyyy-MM-dd\",\"locale\":\"en\",\"productId\":\"1\",\"loanOfficerId\":\"2\",\"submittedOnDate\":\"2011-04-01\",\"loanPurposeId\":\"25\",\"principal\":\"1,000,000.00\",\"loanTermFrequency\":\"12\",\"loanTermFrequencyType\":\"2\",\"numberOfRepayments\":\"12\",\"repaymentEvery\":\"1\",\"repaymentFrequencyType\":\"2\",\"interestRatePerPeriod\":\"24\",\"interestRateFrequencyType\":\"3\",\"expectedDisbursementDate\":\"2011-04-01\",\"amortizationType\":\"1\",\"interestType\":\"1\",\"interestCalculationPeriodType\":\"1\",\"inArrearsTolerance\":\"\",\"transactionProcessingStrategyId\":\"2\",\"repaymentsStartingFromDate\":\"\",\"interestChargedFromDate\":\"\",\"charges\":[{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-05-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-06-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-07-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-08-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-09-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-10-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-11-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2011-12-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2012-01-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2012-02-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2012-03-01\"},{\"chargeId\":\"1\",\"amount\":\"1,500\",\"dueDate\":\"2012-04-01\"}],\"collateral\":[{\"id\":\"\",\"type\":\"23\",\"description\":\" small description.\",\"value\":\"\"}]}',1,'2013-04-05 12:41:57',NULL,NULL,1),(3,'APPROVE','LOAN',2,NULL,1,1,NULL,'/loans/1',1,NULL,'{\"status\":{\"id\":200,\"code\":\"loanStatusType.approved\",\"value\":\"Approved\",\"pendingApproval\":false,\"waitingForDisbursal\":true,\"active\":false,\"closedObligationsMet\":false,\"closedWrittenOff\":false,\"closedRescheduled\":false,\"closed\":false,\"overpaid\":false},\"locale\":\"en\",\"dateFormat\":\"yyyy-MM-dd\",\"approvedOnDate\":\"2011-04-01\"}',1,'2013-04-05 12:42:09',NULL,NULL,1);
+/*!40000 ALTER TABLE `m_portfolio_command_source` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan`
+--
+
+DROP TABLE IF EXISTS `m_product_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `min_principal_amount` decimal(19,6) NOT NULL,
+  `max_principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan`
+--
+
+LOCK TABLES `m_product_loan` WRITE;
+/*!40000 ALTER TABLE `m_product_loan` DISABLE KEYS */;
+INSERT INTO `m_product_loan` VALUES (1,'UGX',2,'1000000.000000','0.000000','1000000000000.000000',NULL,'Kampala Product (with cash accounting)','Typical Kampala loan product with cash accounting enabled for testing.',NULL,'24.000000',3,'24.000000',1,1,1,2,12,1,2,2);
+/*!40000 ALTER TABLE `m_product_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan_charge`
+--
+
+LOCK TABLES `m_product_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_product_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role`
+--
+
+DROP TABLE IF EXISTS `m_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role`
+--
+
+LOCK TABLES `m_role` WRITE;
+/*!40000 ALTER TABLE `m_role` DISABLE KEYS */;
+INSERT INTO `m_role` VALUES (1,'Super user','This role provides all application permissions.');
+/*!40000 ALTER TABLE `m_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role_permission`
+--
+
+DROP TABLE IF EXISTS `m_role_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role_permission`
+--
+
+LOCK TABLES `m_role_permission` WRITE;
+/*!40000 ALTER TABLE `m_role_permission` DISABLE KEYS */;
+INSERT INTO `m_role_permission` VALUES (1,1);
+/*!40000 ALTER TABLE `m_role_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account`
+--
+
+DROP TABLE IF EXISTS `m_savings_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `status_enum` smallint(5) NOT NULL DEFAULT '300',
+  `activation_date` date DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_annual_interest_rate` decimal(19,6) NOT NULL,
+  `interest_compounding_period_enum` smallint(5) NOT NULL,
+  `interest_calculation_type_enum` smallint(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `lockedin_until_date_derived` date DEFAULT NULL,
+  `total_deposits_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_earned_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
+  `account_balance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sa_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `sa_externalid_UNIQUE` (`external_id`),
+  KEY `FKSA00000000000001` (`client_id`),
+  KEY `FKSA00000000000002` (`group_id`),
+  KEY `FKSA00000000000003` (`product_id`),
+  CONSTRAINT `FKSA00000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA00000000000002` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSA00000000000003` FOREIGN KEY (`product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account`
+--
+
+LOCK TABLES `m_savings_account` WRITE;
+/*!40000 ALTER TABLE `m_savings_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account_transaction`
+--
+
+DROP TABLE IF EXISTS `m_savings_account_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `running_balance_derived` decimal(19,6) DEFAULT NULL,
+  `balance_number_of_days_derived` int(11) DEFAULT NULL,
+  `balance_end_date_derived` date DEFAULT NULL,
+  `cumulative_balance_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account_transaction`
+--
+
+LOCK TABLES `m_savings_account_transaction` WRITE;
+/*!40000 ALTER TABLE `m_savings_account_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_product`
+--
+
+DROP TABLE IF EXISTS `m_savings_product`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_product` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_annual_interest_rate` decimal(19,6) NOT NULL,
+  `interest_compounding_period_enum` smallint(5) NOT NULL,
+  `interest_calculation_type_enum` smallint(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sp_unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_product`
+--
+
+LOCK TABLES `m_savings_product` WRITE;
+/*!40000 ALTER TABLE `m_savings_product` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_product` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_staff`
+--
+
+DROP TABLE IF EXISTS `m_staff`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_staff`
+--
+
+LOCK TABLES `m_staff` WRITE;
+/*!40000 ALTER TABLE `m_staff` DISABLE KEYS */;
+INSERT INTO `m_staff` VALUES (1,1,1,'CEDA HO','LoanOfficer','LoanOfficer, CEDA HO'),(2,1,2,'Kampala','LoanOfficer','LoanOfficer, Kampala');
+/*!40000 ALTER TABLE `m_staff` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `r_enum_value`
+--
+
+DROP TABLE IF EXISTS `r_enum_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `r_enum_value`
+--
+
+LOCK TABLES `r_enum_value` WRITE;
+/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
+INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
+/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `ref_loan_transaction_processing_strategy`
+--
+
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `ref_loan_transaction_processing_strategy`
+--
+
+LOCK TABLES `ref_loan_transaction_processing_strategy` WRITE;
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` DISABLE KEYS */;
+INSERT INTO `ref_loan_transaction_processing_strategy` VALUES (2,'heavensfamily-strategy','Heavensfamily',NULL,NULL,NULL,NULL);
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `rpt_sequence`
+--
+
+DROP TABLE IF EXISTS `rpt_sequence`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `rpt_sequence`
+--
+
+LOCK TABLES `rpt_sequence` WRITE;
+/*!40000 ALTER TABLE `rpt_sequence` DISABLE KEYS */;
+/*!40000 ALTER TABLE `rpt_sequence` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `schema_version`
+--
+
+DROP TABLE IF EXISTS `schema_version`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `schema_version` (
+  `version_rank` int(11) NOT NULL,
+  `installed_rank` int(11) NOT NULL,
+  `version` varchar(50) NOT NULL,
+  `description` varchar(200) NOT NULL,
+  `type` varchar(20) NOT NULL,
+  `script` varchar(1000) NOT NULL,
+  `checksum` int(11) DEFAULT NULL,
+  `installed_by` varchar(100) NOT NULL,
+  `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `execution_time` int(11) NOT NULL,
+  `success` tinyint(1) NOT NULL,
+  PRIMARY KEY (`version`),
+  KEY `schema_version_vr_idx` (`version_rank`),
+  KEY `schema_version_ir_idx` (`installed_rank`),
+  KEY `schema_version_s_idx` (`success`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `schema_version`
+--
+
+LOCK TABLES `schema_version` WRITE;
+/*!40000 ALTER TABLE `schema_version` DISABLE KEYS */;
+INSERT INTO `schema_version` VALUES (1,1,'1','mifosplatform-core-ddl-latest','SQL','V1__mifosplatform-core-ddl-latest.sql',-1957145051,'root','2013-04-05 11:23:24',365,1),(2,2,'2','mifosx-base-reference-data-utf8','SQL','V2__mifosx-base-reference-data-utf8.sql',1316484475,'root','2013-04-05 11:23:24',26,1),(3,3,'3','mifosx-permissions-and-authorisation-utf8','SQL','V3__mifosx-permissions-and-authorisation-utf8.sql',1922951887,'root','2013-04-05 11:23:24',26,1),(4,4,'4','mifosx-core-reports-utf8','SQL','V4__mifosx-core-reports-utf8.sql',-934709187,'root','2013-04-05 11:23:24',52,1),(5,5,'5','update-savings-product-and-account-tables','SQL','V5__update-savings-product-and-account-tables.sql',1171300485,'root','2013-04-05 11:23:24',22,1),(6,6,'6','add min max principal column to loan','SQL','V6__add_min_max_principal_column_to_loan.sql',21414779,'root','2013-04-05 11:23:24',25,1),(7,7,'7','remove read makerchecker permission','SQL','V7__remove_read_makerchecker_permission.sql',-335430825,'root','2013-04-05 11:23:24',3,1),(8,8,'8','deposit-transaction-permissions-if-they-exist','SQL','V8__deposit-transaction-permissions-if-they-exist.sql',-1507997551,'root','2013-04-05 11:23:24',2,1);
+/*!40000 ALTER TABLE `schema_version` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_parameter` (
+  `parameter_id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_parameter_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`parameter_id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`),
+  KEY `fk_stretchy_parameter_0001_idx` (`parent_parameter_id`),
+  CONSTRAINT `fk_stretchy_parameter_0001` FOREIGN KEY (`parent_parameter_id`) REFERENCES `stretchy_parameter` (`parameter_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_parameter`
+--
+
+LOCK TABLES `stretchy_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_parameter` VALUES (1,'startDateSelect','startDate','startDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(2,'endDateSelect','endDate','endDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(3,'obligDateTypeSelect','obligDateType','obligDateType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Closed\" as `name` union all\r\nselect 2, \"Disbursal\" ) x\r\norder by x.`id`',NULL),(5,'OfficeIdSelectOne','officeId','Office','select','number','0',NULL,'Y',NULL,'select id, \r\nconcat(substring(\"........................................\", 1, \r\n   \n\n((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat\n\n(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy',NULL),(6,'loanOfficerIdSelectAll','loanOfficerId','Loan Officer','select','number','0',NULL,NULL,'Y','(select lo.id, lo.display_name as `Name` \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\njoin m_staff lo on lo.office_id = ounder.id\r\nwhere lo.is_loan_officer = true\r\nand o.id = ${officeId})\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',5),(10,'currencyIdSelectAll','currencyId','Currency','select','number','0',NULL,NULL,'Y','select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`',NULL),(20,'fundIdSelectAll','fundId','Fund','select','number','0',NULL,NULL,'Y','(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',NULL),(25,'loanProductIdSelectAll','loanProductId','Product','select','number','0',NULL,NULL,'Y','select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere p.currency_code = \'${currencyId}\'\r\norder by 2',10),(26,'loanPurposeIdSelectAll','loanPurposeId','Loan Purpose','select','number','0',NULL,NULL,'Y','select -10 as id, \'-\' as code_value\r\nunion all\r\nselect * from (select v.id, v.code_value\r\nfrom m_code c\r\njoin m_code_value v on v.code_id = c.id\r\nwhere c.code_name = \"loanPurpose\"\r\norder by v.order_position)  x',NULL),(100,'parTypeSelect','parType','parType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Principal Only\" as `name` union all\r\nselect 2, \"Principal + Interest\" union all\r\nselect 3, \"Principal + Interest + Fees\" union all\r\nselect 4, \"Principal + Interest + Fees + Penalties\") x\r\norder by x.`id`',NULL),(1001,'FullReportList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\r\n  \n\nrp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on rp.report_id = r.report_id\r\n  \n\nleft join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \n\n\r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on rp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where \n\nur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by \n\nr.report_category, r.report_name, rp.parameter_id',NULL),(1002,'FullParameterList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r\nsp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r\nfrom stretchy_parameter sp\r\nleft join stretchy_parameter spp on spp.parameter_id = sp.parent_parameter_id\r\nwhere sp.special is null\r\nand exists \r\n	(select \'f\' \r\n	from stretchy_report sr\r\n	join stretchy_report_parameter srp on srp.report_id = sr.report_id\r\n	where sr.report_name in(${reportListing})\r\n	and srp.parameter_id = sp.parameter_id\r\n	)\r\norder by sp.parameter_id',NULL),(1003,'reportCategoryList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, \n\nr.report_category,\r\n  rp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on \n\nrp.report_id = r.report_id\r\n  left join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.report_category = \'${reportCategory}\'\r\n  and \n\nr.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on \n\nrp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where ur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \n\n\'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by r.report_category, r.report_name, rp.parameter_id',NULL);
+/*!40000 ALTER TABLE `stretchy_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report`
+--
+
+DROP TABLE IF EXISTS `stretchy_report`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report` (
+  `report_id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`report_id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report`
+--
+
+LOCK TABLES `stretchy_report` WRITE;
+/*!40000 ALTER TABLE `stretchy_report` DISABLE KEYS */;
+INSERT INTO `stretchy_report` VALUES (1,'Client Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n c.account_no as \"Client Account No.\",  \r\nc.display_name as \"Name\",  \n\nc.joined_date as \"Joined\", c.external_id as \"External Id\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand \n\nounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\nwhere o.id = ${officeId}\r\nand c.is_deleted=0\r\n\n\norder by ounder.hierarchy, c.account_no','Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t \n\nhave that client browser/memory impact).',1,1),(2,'Client Loans Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client \n\nAccount No.\", \r\nc.display_name as \"Name\", \r\nlo.display_name as \"Loan Officer\", l.account_no as \"Loan Account No.\", l.external_id as \"External Id\", \r\n\n\np.name as Loan, st.enum_message_property as \"Status\",  \r\nf.`name` as Fund, purp.code_value as \"Loan Purpose\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount,\n\n\r\nl.arrearstolerance_amount as \"Arrears Tolerance Amount\",\r\nl.number_of_repayments as \"Expected No. Repayments\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\nl.nominal_interest_rate_per_period as \"Nominal Interest Rate Per Period\",\r\n\r\nipf.enum_message_property as \"Interest Rate Frequency\n\n\",\r\nim.enum_message_property as \"Interest Method\",\r\nicp.enum_message_property as \"Interest Calculated in Period\",\r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.repay_every as \"Repayment Frequency\",\r\nrf.enum_message_property as \"Repayment Frequency Period\",\n\n\r\nam.enum_message_property as \"Amortization\",\r\n\r\nl.total_charges_due_at_disbursement_derived as \"Total Charges Due At Disbursement\",\r\n\r\ndate( \n\nl.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As \"Expected Disbursal\",\r\ndate(l.expected_firstrepaymenton_date) as \n\n\"Expected First Repayment\", date(l.interest_calculated_from_date) as \"Interest Calculated From\" ,\r\ndate(l.disbursedon_date) as Disbursed, date\n\n(l.expected_maturedon_date) \"Expected Maturity\",\r\ndate(l.maturedon_date) as \"Matured On\", date(l.closedon_date) as Closed,\r\ndate(l.rejectedon_date) as \n\nRejected, date(l.rescheduledon_date) as Rescheduled, \r\ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) \"Written Off\"\r\nfrom m_office o \r\njoin \n\nm_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on \n\nc.office_id = ounder.id\r\nleft join m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_product_loan p on p.id = \n\nl.product_id\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join r_enum_value st on st.enum_name = \"loan_status_id\" and st.enum_id = l.loan_status_id\r\nleft join \n\nr_enum_value ipf on ipf.enum_name = \"interest_period_frequency_enum\" and ipf.enum_id = l.interest_period_frequency_enum\r\nleft join r_enum_value im on im.enum_name \n\n= \"interest_method_enum\" and im.enum_id = l.interest_method_enum\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = \n\nl.term_period_frequency_enum\r\nleft join r_enum_value icp on icp.enum_name = \"interest_calculated_in_period_enum\" and icp.enum_id = \n\nl.interest_calculated_in_period_enum\r\nleft join r_enum_value rf on rf.enum_name = \"repayment_period_frequency_enum\" and rf.enum_id = \n\nl.repayment_period_frequency_enum\r\nleft join r_enum_value am on am.enum_name = \"amortization_method_enum\" and am.enum_id = l.amortization_method_enum\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\n\r\nleft \n\njoin m_currency cur on cur.code = l.currency_code\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand \n\n(l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \n\n\"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\norder by ounder.hierarchy, 2 , l.id','Individual Client Report\r\n\r\nPretty \n\nwide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).',1,1),(5,'Loans Awaiting Disbursal','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No\", c.display_name as \"Name\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund, ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\",\r\ndate(l.approvedon_date) \"Approved\",\r\ndatediff(l.expected_disbursedon_date, curdate()) as \"Days to Disbursal\",\r\ndate(l.expected_disbursedon_date) \"Expected Disbursal\",\r\npurp.code_value as \"Loan Purpose\",\r\n lo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, datediff(l.expected_disbursedon_date, curdate()),  c.account_no','Individual Client Report',1,1),(6,'Loans Awaiting Disbursal Summary','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`','Individual Client Report',1,1),(7,'Loans Awaiting Disbursal Summary by Month','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as \"Year\", \r\nmonthname(l.expected_disbursedon_date) as \"Month\",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)','Individual Client Report',1,1),(8,'Loans Pending Approval','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client Name\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  pl.`name` as \"Product\", \r\nl.account_no as \"Loan Account No.\", \r\nl.principal_amount as \"Loan Amount\", \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\ndatediff(curdate(), l.submittedon_date) \"Days Pending Approval\", \r\npurp.code_value as \"Loan Purpose\",\r\nlo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 100 /*Submitted and awaiting approval */\r\norder by ounder.hierarchy, l.submittedon_date,  l.account_no','Individual Client Report',1,1),(11,'Active Loans - Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. loans_in_arrears_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(if(laa.loan_id is not null,  l.id, null)  )) as loans_in_arrears_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(laa.principal_overdue_derived) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(laa.interest_overdue_derived) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(laa.fee_charges_overdue_derived) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(laa.penalty_charges_overdue_derived) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(12,'Active Loans - Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\npenalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(13,'Obligation Met Loans Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client\",\r\nl.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.total_repayment_derived  as \"Total Repaid\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", \r\ndate(l.closedon_date) as \"Closed\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(14,'Obligation Met Loans Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as \"No. of Clients\",\r\ncount(distinct(l.id)) as \"No. of Loans\",\r\nsum(l.principal_amount) as \"Total Loan Amount\", \r\nsum(l.principal_repaid_derived) as \"Total Principal Repaid\",\r\nsum(l.interest_repaid_derived) as \"Total Interest Repaid\",\r\nsum(l.fee_charges_repaid_derived) as \"Total Fees Repaid\",\r\nsum(l.penalty_charges_repaid_derived) as \"Total Penalties Repaid\",\r\nsum(l.interest_waived_derived) as \"Total Interest Waived\",\r\nsum(l.fee_charges_waived_derived) as \"Total Fees Waived\",\r\nsum(l.penalty_charges_waived_derived) as \"Total Penalties Waived\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code','Individual Client \n\nReport',1,1),(15,'Portfolio at Risk','Table',NULL,'Loan','select x.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from \r\n(select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code) x','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(16,'Portfolio at Risk by Branch','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\",\r\nx.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select  ounder.id as \"branch\", ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\n\r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(20,'Funds Disbursed Between Dates Summary','Table',NULL,'Fund','select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, \r\nround(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(21,'Funds Disbursed Between Dates Summary by Office','Table',NULL,'Fund','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n \n\nifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\n\n\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c \n\non c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\n\n\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand \n\n(l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, \n\nl.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(48,'Balance Sheet','Pentaho',NULL,'Accounting',NULL,'Balance Sheet',1,1),(49,'Income Statement','Pentaho',NULL,'Accounting',NULL,'Profit and Loss Statement',1,1),(50,'Trial Balance','Pentaho',NULL,'Accounting',NULL,'Trial Balance Report',1,1),(51,'Written-Off Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Written-Off Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Written-Off Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Written-Off Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Written-Off Penalties\',\r\nn.note AS \'Reason For Write-Off\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 6 /*write-off */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=601\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\") \r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Written Off Loans',1,1),(52,'Aging Detail','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as \"Client Account No.\",\r\n 	mc.display_name AS \"Client Name\",\r\n 	ml.account_no AS \"Account Number\",\r\n 	ml.principal_amount AS \"Loan Amount\",\r\n ml.principal_disbursed_derived AS \"Original Principal\",\r\n ml.interest_charged_derived AS \"Original Interest\",\r\n ml.principal_repaid_derived AS \"Principal Paid\",\r\n ml.interest_repaid_derived AS \"Interest Paid\",\r\n laa.principal_overdue_derived AS \"Principal Overdue\",\r\n laa.interest_overdue_derived AS \"Interest Overdue\",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as \"Days in Arrears\",\r\n\r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS \"Weeks In Arrears Band\",\r\n\r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n				 \'> 360\'))))) AS \"Days in Arrears Band\"\r\n\r\n	FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n	        AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n	    INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n	    INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n	WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Loan arrears aging (Weeks)',1,1),(53,'Aging Summary (Arrears in Weeks)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Weeks In Arrears (Up To)\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'1\',2 UNION\r\n		SELECT \'2\',3 UNION\r\n		SELECT \'3\',4 UNION\r\n		SELECT \'4\',5 UNION\r\n		SELECT \'5\',6 UNION\r\n		SELECT \'6\',7 UNION\r\n		SELECT \'7\',8 UNION\r\n		SELECT \'8\',9 UNION\r\n		SELECT \'9\',10 UNION\r\n		SELECT \'10\',11 UNION\r\n		SELECT \'11\',12 UNION\r\n		SELECT \'12\',13 UNION\r\n		SELECT \'12+\',14) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<8, \'1\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<15, \'2\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<22, \'3\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<29, \'4\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<36, \'5\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<43, \'6\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<50, \'7\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<57, \'8\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<64, \'9\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<71, \'10\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<78, \'11\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<85, \'12\',\r\n				 \'12+\'))))))))))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(54,'Rescheduled Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Rescheduled Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Rescheduled Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Rescheduled Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Rescheduled Penalties\',\r\nn.note AS \'Reason For Rescheduling\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 7 /*marked for rescheduling */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=602\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Rescheduled Loans.  The ability to reschedule (or mark that you have rescheduled the loan elsewhere) is a legacy of the older Mifos product.  Needed for migration.',1,1),(55,'Active Loans Passed Final Maturity','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(56,'Active Loans Passed Final Maturity Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(57,'Active Loans in last installment','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(lastInstallment.`hierarchy`) - LENGTH(REPLACE(lastInstallment.`hierarchy`, \'.\', \'\')) - 1))), lastInstallment.branch) as \"Office/Branch\",\r\nlastInstallment.Currency,\r\nlastInstallment.`Loan Officer`, \r\nlastInstallment.`Client Account No`, lastInstallment.`Client`, \r\nlastInstallment.`Loan Account No`, lastInstallment.`Product`, \r\nlastInstallment.`Fund`,  lastInstallment.`Loan Amount`, \r\nlastInstallment.`Annual Nominal Interest Rate`, \r\nlastInstallment.`Disbursed`, lastInstallment.`Expected Matured On` ,\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\norder by lastInstallment.hierarchy, lastInstallment.Currency, lastInstallment.`Client Account No`, lastInstallment.`Loan Account No`','Individual Client \n\nReport',1,1),(58,'Active Loans in last installment Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select lastInstallment.branchId as branchId,\r\nlastInstallment.Currency,\r\ncount(distinct(lastInstallment.clientId)) as client_count, \r\ncount(distinct(lastInstallment.loanId)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id as branchId, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.id as clientId, c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\ngroup by lastInstallment.branchId) x on x.branchId = mo.id\r\norder by mo.hierarchy, x.Currency','Individual Client \n\nReport',1,1),(59,'Active Loans by Disbursal Period','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No\", c.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Principal Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\n\r\nl.total_expected_repayment_derived as \"Total Loan (P+I+F+Pen)\",\r\nl.total_repayment_derived as \"Total Repaid (P+I+F+Pen)\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(61,'Aging Summary (Arrears in Months)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Days In Arrears\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'0 - 30\',2 UNION\r\n		SELECT \'30 - 60\',3 UNION\r\n		SELECT \'60 - 90\',4 UNION\r\n		SELECT \'90 - 180\',5 UNION\r\n		SELECT \'180 - 360\',6 UNION\r\n		SELECT \'> 360\',7 ) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<361, \'180 - 360\', \r\n				 \'> 360\')))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(91,'Loan Account Schedule','Pentaho',NULL,'Loan',NULL,NULL,1,0),(92,'Branch Expected Cash Flow','Pentaho',NULL,'Loan',NULL,NULL,1,1),(93,'Expected Payments By Date - Basic','Table',NULL,'Loan','SELECT \r\n      ounder.name \'Office\', \r\n      IFNULL(ms.display_name,\'-\') \'Loan Officer\',\r\n	  mc.account_no \'Client Account Number\',\r\n	  mc.display_name \'Name\',\r\n	  mp.name \'Product\',\r\n	  ml.account_no \'Loan Account Number\',\r\n	  mr.duedate \'Due Date\',\r\n	  mr.installment \'Installment\',\r\n	  cu.display_symbol \'Currency\',\r\n	  mr.principal_amount- IFNULL(mr.principal_completed_derived,0) \'Principal Due\',\r\n	  mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0) \'Interest Due\', \r\n	  IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0) \'Fees Due\', \r\n	  IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0) \'Penalty Due\',\r\n      (mr.principal_amount- IFNULL(mr.principal_completed_derived,0)) +\r\n       (mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0)) + \r\n       (IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0)) + \r\n       (IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0)) \'Total Due\', \r\n     mlaa.total_overdue_derived \'Total Overdue\'\r\n										 \r\n FROM m_office mo\r\n  JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\n  \r\n  AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n	\r\n  LEFT JOIN m_client mc ON mc.office_id=ounder.id\r\n  LEFT JOIN m_loan ml ON ml.client_id=mc.id AND ml.loan_status_id=300\r\n  LEFT JOIN m_loan_arrears_aging mlaa ON mlaa.loan_id=ml.id\r\n  LEFT JOIN m_loan_repayment_schedule mr ON mr.loan_id=ml.id AND mr.completed_derived=0\r\n  LEFT JOIN m_product_loan mp ON mp.id=ml.product_id\r\n  LEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\n  LEFT JOIN m_currency cu ON cu.code=ml.currency_code\r\n WHERE mo.id=${officeId}\r\n AND (IFNULL(ml.loan_officer_id, -10) = \"${loanOfficerId}\" OR \"-1\" = \"${loanOfficerId}\")\r\n AND mr.duedate BETWEEN \'${startDate}\' AND \'${endDate}\'\r\n ORDER BY ounder.id,mr.duedate,ml.account_no','Test',1,1),(94,'Expected Payments By Date - Formatted','Pentaho',NULL,'Loan',NULL,NULL,1,1);
+/*!40000 ALTER TABLE `stretchy_report` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report_parameter` (
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`report_id`,`parameter_id`),
+  UNIQUE KEY `report_id_name_UNIQUE` (`report_id`,`report_parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report_parameter`
+--
+
+LOCK TABLES `stretchy_report_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_report_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_report_parameter` VALUES (1,5,NULL),(2,5,NULL),(2,6,NULL),(2,10,NULL),(2,20,NULL),(2,25,NULL),(2,26,NULL),(5,5,NULL),(5,6,NULL),(5,10,NULL),(5,20,NULL),(5,25,NULL),(5,26,NULL),(6,5,NULL),(6,6,NULL),(6,10,NULL),(6,20,NULL),(6,25,NULL),(6,26,NULL),(7,5,NULL),(7,6,NULL),(7,10,NULL),(7,20,NULL),(7,25,NULL),(7,26,NULL),(8,5,NULL),(8,6,NULL),(8,10,NULL),(8,25,NULL),(8,26,NULL),(11,5,NULL),(11,6,NULL),(11,10,NULL),(11,20,NULL),(11,25,NULL),(11,26,NULL),(11,100,NULL),(12,5,NULL),(12,6,NULL),(12,10,NULL),(12,20,NULL),(12,25,NULL),(12,26,NULL),(13,1,NULL),(13,2,NULL),(13,3,NULL),(13,5,NULL),(13,6,NULL),(13,10,NULL),(13,20,NULL),(13,25,NULL),(13,26,NULL),(14,1,NULL),(14,2,NULL),(14,3,NULL),(14,5,NULL),(14,6,NULL),(14,10,NULL),(14,20,NULL),(14,25,NULL),(14,26,NULL),(15,5,NULL),(15,6,NULL),(15,10,NULL),(15,20,NULL),(15,25,NULL),(15,26,NULL),(15,100,NULL),(16,5,NULL),(16,6,NULL),(16,10,NULL),(16,20,NULL),(16,25,NULL),(16,26,NULL),(16,100,NULL),(20,1,NULL),(20,2,NULL),(20,10,NULL),(20,20,NULL),(21,1,NULL),(21,2,NULL),(21,5,NULL),(21,10,NULL),(21,20,NULL),(48,5,'branch'),(48,2,'date'),(49,5,'branch'),(49,1,'fromDate'),(49,2,'toDate'),(50,5,'branch'),(50,1,'fromDate'),(50,2,'toDate'),(51,1,NULL),(51,2,NULL),(51,5,NULL),(51,10,NULL),(51,25,NULL),(52,5,NULL),(53,5,NULL),(53,10,NULL),(54,1,NULL),(54,2,NULL),(54,5,NULL),(54,10,NULL),(54,25,NULL),(55,5,NULL),(55,6,NULL),(55,10,NULL),(55,20,NULL),(55,25,NULL),(55,26,NULL),(56,5,NULL),(56,6,NULL),(56,10,NULL),(56,20,NULL),(56,25,NULL),(56,26,NULL),(56,100,NULL),(57,5,NULL),(57,6,NULL),(57,10,NULL),(57,20,NULL),(57,25,NULL),(57,26,NULL),(58,5,NULL),(58,6,NULL),(58,10,NULL),(58,20,NULL),(58,25,NULL),(58,26,NULL),(58,100,NULL),(59,1,NULL),(59,2,NULL),(59,5,NULL),(59,6,NULL),(59,10,NULL),(59,20,NULL),(59,25,NULL),(59,26,NULL),(61,5,NULL),(61,10,NULL),(92,1,'fromDate'),(92,5,'selectOffice'),(92,2,'toDate'),(93,1,NULL),(93,2,NULL),(93,5,NULL),(93,6,NULL),(94,2,'endDate'),(94,6,'loanOfficerId'),(94,5,'officeId'),(94,1,'startDate');
+/*!40000 ALTER TABLE `stretchy_report_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `x_registered_table`
+--
+
+DROP TABLE IF EXISTS `x_registered_table`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `x_registered_table`
+--
+
+LOCK TABLES `x_registered_table` WRITE;
+/*!40000 ALTER TABLE `x_registered_table` DISABLE KEYS */;
+INSERT INTO `x_registered_table` VALUES ('client additional data','m_client'),('impact measurement','m_loan'),('loan additional data','m_loan');
+/*!40000 ALTER TABLE `x_registered_table` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-04-05 12:42:54
diff --git a/fineract-db/multi-tenant-demo-backups/ceda/ceda-schema-customisations.sql b/fineract-db/multi-tenant-demo-backups/ceda/ceda-schema-customisations.sql
new file mode 100644
index 0000000..8a8fab8
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/ceda/ceda-schema-customisations.sql
@@ -0,0 +1,344 @@
+DELETE FROM `ref_loan_transaction_processing_strategy` WHERE id in (1, 3, 4);
+
+INSERT INTO `m_code` (`code_name`, `is_system_defined`)
+VALUES
+('FieldOfEmployment', '0'),
+('EducationLevel', '0'),
+('MaritalStatus', '0'),
+('PovertyStatus', '0');
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Banker', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "FieldOfEmployment";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.SoftwareDeveloper', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "FieldOfEmployment";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.University', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "EducationLevel";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Secondary', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "EducationLevel";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Primary', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "EducationLevel";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Married', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "MaritalStatus";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Single', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "MaritalStatus";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Divorced', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "MaritalStatus";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Widow', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "MaritalStatus";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.PovertyStatus.Band1', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "PovertyStatus";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.PovertyStatus.Band2', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "PovertyStatus";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.PovertyStatus.Band3', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "PovertyStatus";
+
+-- ======= ADD in loan purspose and collateral values to enable this functionality
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.House', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "LoanCollateral";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Television', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "LoanCollateral";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Gold', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "LoanCollateral";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Agriculture', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "LoanPurpose";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Manufacturing', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "LoanPurpose";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.HousingImprovement', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "LoanPurpose";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Male', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "Gender";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Female', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "Gender";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Yes', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "YesNo";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.No', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "YesNo";
+
+
+
+DROP TABLE IF EXISTS `client additional data`;
+CREATE TABLE `client additional data` (
+  `client_id` bigint(20) NOT NULL,
+  `Gender_cd` int(11) NOT NULL,
+  `Date of Birth` date NOT NULL,
+  `Home address` text NOT NULL,
+  `Telephone number` varchar(20) NOT NULL,
+  `Telephone number (2nd)` varchar(20) NOT NULL,
+  `Email address` varchar(50) NOT NULL,
+  `EducationLevel_cd` int(11) NOT NULL,
+  `MaritalStatus_cd` int(11) NOT NULL,
+  `Number of children` int(11) NOT NULL,
+  `Citizenship` varchar(50) NOT NULL,
+  `PovertyStatus_cd` int(11) NOT NULL,
+  `YesNo_cd_Employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of employment` int(11) DEFAULT NULL,
+  `Employer name` varchar(50) DEFAULT NULL,
+  `Number of years` int(11) DEFAULT NULL,
+  `Monthly salary` decimal(19,6) DEFAULT NULL,
+  `YesNo_cd_Self employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of self-employment` int(11) DEFAULT NULL,
+  `Business address` text,
+  `Number of employees` int(11) DEFAULT NULL,
+  `Monthly salaries paid` decimal(19,6) DEFAULT NULL,
+  `Monthly net income of business activity` decimal(19,6) DEFAULT NULL,
+  `Monthly rent` decimal(19,6) DEFAULT NULL,
+  `Other income generating activities` varchar(100) DEFAULT NULL,
+  `YesNo_cd_Bookkeeping` int(11) DEFAULT NULL,
+  `YesNo_cd_Loans with other institutions` int(11) NOT NULL,
+  `From whom` varchar(100) DEFAULT NULL,
+  `Amount` decimal(19,6) DEFAULT NULL,
+  `Interest rate pa` decimal(19,6) DEFAULT NULL,
+  `Number of people depending on overal income` int(11) NOT NULL,
+  `YesNo_cd_Bank account` int(11) NOT NULL,
+  `YesNo_cd_Business plan provided` int(11) NOT NULL,
+  `YesNo_cd_Access to internet` int(11) DEFAULT NULL,
+  `Introduced by` varchar(100) DEFAULT NULL,
+  `Known to introducer since` varchar(100) NOT NULL,
+  `Last visited by` varchar(100) DEFAULT NULL,
+  `Last visited on` date NOT NULL,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_client_additional_data` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `impact measurement`;
+CREATE TABLE `impact measurement` (
+  `loan_id` bigint(20) NOT NULL,
+  `YesNo_cd_RepaidOnSchedule` int(11) NOT NULL,
+  `ReasonNotRepaidOnSchedule` text DEFAULT NULL,
+  `How was Loan Amount Invested` text NOT NULL,
+  `Additional Income Generated` decimal(19,6) NOT NULL,
+  `Additional Income Used For` text NOT NULL,
+  `YesNo_cd_NewJobsCreated` int(11) NOT NULL,
+  `Number of Jobs Created` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_impact measurement` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `loan additional data`;
+CREATE TABLE `loan additional data` (
+  `loan_id` bigint(20) NOT NULL,
+  `PurposeOfLoan_cd` int(11) NOT NULL,
+  `CollateralType_cd` int(11) NOT NULL,
+  `Collateral notes` text NOT NULL,
+  `YesNo_cd_Guarantor` int(11) NOT NULL,
+  `Guarantor name` varchar(100) DEFAULT NULL,
+  `Guarantor relation` varchar(100) DEFAULT NULL,
+  `Guarantor address` varchar(100) DEFAULT NULL,
+  `Guarantor telephone number` varchar(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_loan_additional_data` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- datatables mapping
+INSERT INTO `x_registered_table`
+(`registered_table_name`,
+`application_table_name`)
+VALUES
+('client additional data', 'm_client'),
+('impact measurement', 'm_loan'),
+('loan additional data', 'm_loan');
+
+-- make sure permissions created for registered datatables
+/* add a create, read, update and delete permission for each registered datatable */
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('CREATE_', r.registered_table_name), r.registered_table_name, 'CREATE'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('READ_', r.registered_table_name), r.registered_table_name, 'READ'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('UPDATE_', r.registered_table_name), r.registered_table_name, 'UPDATE'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('DELETE_', r.registered_table_name), r.registered_table_name, 'DELETE'
+from x_registered_table r;
+
+
+-- ==== Chart of Accounts =====
+truncate `acc_gl_account`;
+INSERT INTO `acc_gl_account` VALUES 
+(1,'Petty Cash Balances',NULL,'11100',0,1,2,1,NULL),
+(2,'Cash in Valut 1',NULL,'11101',0,1,1,1,NULL),
+(3,'Bank Balances',NULL,'11200',0,1,2,1,NULL),
+(4,'Centenary Opening Account',NULL,'11201',0,1,1,1,NULL),
+(5,'Centenary Expense Account',NULL,'11202',0,1,1,1,NULL),
+(6,'Centenary USD Account',NULL,'11203',0,1,1,1,NULL),
+(7,'Loans and Advances',NULL,'13100',0,1,2,1,NULL),
+(8,'Loans to Clients',NULL,'13101',0,1,1,1,NULL),
+(9,'Outstanding Interest',NULL,'13102',0,1,1,1,NULL),
+(10,'Outstanding Late Payment Interest',NULL,'13103',0,1,1,1,NULL),
+(11,'Outstanding Bank Fees to be collected',NULL,'13104',0,1,1,1,NULL),
+(12,'WriteOff Accounts',NULL,'13200',0,1,2,1,NULL),
+(13,'Write-offs (use for funds coming in)',NULL,'13201',0,1,1,1,NULL),
+(14,'Write-offs outstanding principal',NULL,'13202',0,1,1,1,NULL),
+(15,'Write-offs outstanding interest',NULL,'13203',0,1,1,1,NULL),
+(16,'Write-offs collected bank fees',NULL,'13204',0,1,1,1,NULL),
+(17,'Write-offs hardware/furniture',NULL,'13205',0,1,1,1,NULL),
+(18,'Fixed Assets',NULL,'14100',0,1,2,1,NULL),
+(19,'Office Equipment',NULL,'14101',0,1,1,1,NULL),
+(20,'Suspense Items (unidentified deposits)',NULL,'15000',0,1,2,1,NULL),
+(21,'Assets',NULL,'10000',0,1,2,1,NULL),
+(22,'Liabilities',NULL,'20000',0,1,2,2,NULL),
+(23,'Shares Account',NULL,'26100',0,1,2,2,NULL),
+(24,'Shares Captial',NULL,'26101',0,1,1,2,NULL),
+(25,'Donated Equity',NULL,'26300',0,1,2,2,NULL),
+(26,'Donated Equity Ameropa Foundation',NULL,'26301',0,1,1,2,NULL),
+(27,'Donated Equity e.h',NULL,'26302',0,1,1,2,NULL),
+(28,'Overpaid Amounts',NULL,'27000',0,1,2,2,NULL),
+(29,'Loss Provision',NULL,'28000',0,1,2,2,NULL),
+(30,'Provision Outstanding Principal',NULL,'28001',0,1,1,2,NULL),
+(31,'Provision Oustanding Interest',NULL,'28002',0,1,1,2,NULL),(32,'Income',NULL,'30000',0,1,2,4,NULL),
+(33,'Interest Income from Loans',NULL,'31100',0,1,2,4,NULL),
+(34,'Interest on Loans',NULL,'31101',0,1,1,4,NULL),
+(35,'Late Payment Interest',NULL,'31102',0,1,1,4,NULL),
+(36,'Income from Micro credit & Lending Activities',NULL,'31300',0,1,2,4,NULL),
+(37,'Collected Bank Fees Receivable',NULL,'6201',0,1,1,4,NULL),
+(38,'Deposits from Loans Write Off',NULL,'31400',0,1,2,4,NULL),
+(39,'Expenditure',NULL,'40000',0,1,2,5,NULL),
+(40,'Office Expenditure Account',NULL,'42100',0,1,2,5,NULL),
+(41,'Water Charges',NULL,'42102',0,1,1,5,NULL),
+(42,'Electricity Charges',NULL,'42103',0,1,1,5,NULL),
+(43,'Printing and Stationary',NULL,'42105',0,1,1,5,NULL),
+(44,'Office Rent',NULL,'42107',0,1,1,5,NULL),
+(45,'Marketing Expense',NULL,'42109',0,1,1,5,NULL),
+(46,'Office utilities',NULL,'42112',0,1,1,5,'(supplies, toiletries, kitchen)'),
+(47,'Furniture',NULL,'42113',0,1,1,5,NULL),
+(48,'CEDA Meeting Expense',NULL,'42114',0,1,1,5,NULL),
+(49,'Employee Personal Expsense Account',NULL,'42200',0,1,2,5,NULL),
+(50,'Salary Alice',NULL,'42201',0,1,1,5,NULL),
+(51,'Salary Irene',NULL,'42202',0,1,1,5,NULL),
+(52,'Salary Richard',NULL,'42203',0,1,1,5,NULL),
+(53,'Salary Loan Officer TBA',NULL,'42204',0,1,1,5,NULL),
+(54,'Medical Insurance Alice & Family',NULL,'42205',0,1,1,5,NULL),
+(55,'Medical Insurance Irene',NULL,'42206',0,1,1,5,NULL),
+(56,'Medical Insurance Richard',NULL,'42207',0,1,1,5,NULL),
+(57,'Medical Insurance Loan Officer TBA',NULL,'42208',0,1,1,5,NULL),
+(58,'PAYE all employees',NULL,'42209',0,1,1,5,NULL),
+(59,'NSSF all employees',NULL,'42210',0,1,1,5,NULL),
+(60,'Lunch Allowances all employees',NULL,'42211',0,1,1,5,NULL),
+(61,'IT software and maintenance',NULL,'42300',0,1,2,5,NULL),
+(62,'Mifos maintenance contract 12 months',NULL,'42301',0,1,1,5,NULL),
+(63,'VPS Contract 12 months',NULL,'42302',0,1,1,5,NULL),
+(64,'Bulk SMS Service',NULL,'42303',0,1,1,5,NULL),
+(65,'Support Accounting Software',NULL,'42304',0,1,1,5,NULL),
+(66,'Mifos Instance Setup',NULL,'42305',0,1,1,5,NULL),
+(67,'Misc support expense',NULL,'42306',0,1,1,5,NULL),
+(68,'Warid Mobile Line',NULL,'42307',0,1,1,5,NULL),
+(69,'Landline',NULL,'42308',0,1,1,5,NULL),
+(70,'Modem Alice',NULL,'42309',0,1,1,5,NULL),
+(71,'Modem Irene',NULL,'42310',0,1,1,5,NULL),
+(72,'Modem Richard',NULL,'42311',0,1,1,5,NULL),
+(73,'Repairs',NULL,'42312',0,1,1,5,NULL),
+(74,'Airtime Expenses',NULL,'42400',0,1,2,5,NULL),
+(75,'Airtime Alice',NULL,'42401',0,1,1,5,NULL),
+(76,'Airtime Richard',NULL,'42402',0,1,1,5,NULL),
+(77,'Airtime Loan Office TBA',NULL,'42403',0,1,1,5,NULL),
+(78,'Special Airtime Alice',NULL,'42404',0,1,1,5,NULL),
+(79,'Transportation',NULL,'42500',0,1,2,5,NULL),
+(80,'Flat monthly transportation cost',NULL,'42501',0,1,1,5,NULL),
+(81,'Faciliation cost for Richard',NULL,'42502',0,1,1,5,NULL),
+(82,'Faciliation cost for Loan Officer TBA',NULL,'42503',0,1,1,5,NULL),
+(83,'Consultancy Expenses',NULL,'42600',0,1,2,5,NULL),
+(84,'Audit Fees',NULL,'42601',0,1,1,5,NULL),
+(85,'Legal Fees',NULL,'42602',0,1,1,5,NULL),
+(86,'Miscellaneous Expenses Account',NULL,'42700',0,1,2,5,NULL),
+(87,'MFI License',NULL,'42703',0,1,1,5,NULL),
+(88,'Sundy Expenses',NULL,'42704',0,1,1,5,NULL),
+(89,'Bank Fees',NULL,'42800',0,1,2,5,NULL),
+(90,'Bank Charges Operating Account',NULL,'42801',0,1,1,5,NULL),
+(91,'Bank Charges Expense Account',NULL,'42802',0,1,1,5,NULL),
+(92,'E.H Account',NULL,'42900',0,1,2,5,NULL),
+(93,'Airtime',NULL,'42901',0,1,1,5,NULL),
+(94,'Modem',NULL,'42902',0,1,1,5,NULL),
+(95,'Meals',NULL,'42903',0,1,1,5,NULL),
+(96,'Transportation',NULL,'42904',0,1,1,5,NULL),
+(97,'Miscellaneous',NULL,'42905',0,1,1,5,NULL);
\ No newline at end of file
diff --git a/fineract-db/multi-tenant-demo-backups/ceda/ceda-user-office-product-setup.sql b/fineract-db/multi-tenant-demo-backups/ceda/ceda-user-office-product-setup.sql
new file mode 100644
index 0000000..0590448
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/ceda/ceda-user-office-product-setup.sql
@@ -0,0 +1,104 @@
+DELETE FROM `m_organisation_currency` WHERE id>0;
+
+INSERT INTO `m_organisation_currency`
+(
+`code`,
+`decimal_places`,
+`name`,
+`display_symbol`,
+`internationalized_name_code`)
+VALUES
+('UGX', 2, 'Uganda Shilling', 'USh', 'currency.UGX');
+
+
+UPDATE `m_appuser` SET `username` = 'admin' WHERE id=1;
+
+INSERT INTO `m_appuser`
+(`is_deleted`,
+`office_id`,
+`username`,
+`firstname`,
+`lastname`,
+`password`,
+`email`,
+`firsttime_login_remaining`,
+`nonexpired`,
+`nonlocked`,
+`nonexpired_credentials`,
+`enabled`)
+VALUES
+(0, 1, 'keithwoodlock', 'Keith', 'Woodlock', 
+'4f607e9b6cffbe7d3db92d4bfa3391c7aa751727b4ea29d08fddf9dd72e6e7e3', 'keithwoodlock@gmail.com', 0, 1, 1, 1, 1);
+
+INSERT INTO `m_appuser_role`
+(`appuser_id`,`role_id`) VALUES (2,1);
+
+UPDATE `m_office` SET `name` = 'CEDA Microfinance Ltd.' WHERE id=1;
+
+INSERT INTO `m_office`
+(
+`parent_id`,
+`hierarchy`,
+`external_id`,
+`name`,
+`opening_date`)
+VALUES 
+(1, '.2.', 2, 'Uganda (Kampala)', '2009-01-01');
+
+
+INSERT INTO `m_staff`
+(
+`is_loan_officer`,
+`office_id`,
+`firstname`,
+`lastname`,
+`display_name`)
+VALUES
+(1, 1, 'CEDA HO', 'LoanOfficer', 'LoanOfficer, CEDA HO'),
+(1, 2, 'Kampala', 'LoanOfficer', 'LoanOfficer, Kampala');
+
+
+INSERT INTO `m_charge`
+(
+`name`,
+`currency_code`,
+`charge_applies_to_enum`,
+`charge_time_enum`,
+`charge_calculation_enum`,
+`amount`,
+`is_penalty`,
+`is_active`,
+`is_deleted`)
+VALUES ('Bank Fee (per installment)', 'UGX', 1, 2, 1, 1500.000000, 0, 1, 0);
+
+
+INSERT INTO `m_product_loan`
+(
+`currency_code`,
+`currency_digits`,
+`principal_amount`,
+`min_principal_amount`,
+`max_principal_amount`,
+`arrearstolerance_amount`,
+`name`,
+`description`,
+`fund_id`,
+`nominal_interest_rate_per_period`,
+`interest_period_frequency_enum`,
+`annual_nominal_interest_rate`,
+`interest_method_enum`,
+`interest_calculated_in_period_enum`,
+`repay_every`,
+`repayment_period_frequency_enum`,
+`number_of_repayments`,
+`amortization_method_enum`,
+`accounting_type`,
+`loan_transaction_strategy_id`)
+VALUES
+('UGX', 2, 1000000.000000, 0, 1000000000000.000000, null , 'Kampala Product (with cash accounting)', 
+'Typical Kampala loan product with cash accounting enabled for testing.', null , 
+24.000000, 3, 24.000000, 1, 1, 1, 2, 12, 1, 2, 2);
+
+-- mapping of that loan product to GL Accounts
+INSERT INTO `acc_product_mapping` 
+VALUES (1,4,1,1,1),(2,8,1,1,2),(3,34,1,1,3),(4,37,1,1,4),(5,35,1,1,5),(6,97,1,1,6);
\ No newline at end of file
diff --git a/fineract-db/multi-tenant-demo-backups/default-demo/README.md b/fineract-db/multi-tenant-demo-backups/default-demo/README.md
new file mode 100644
index 0000000..6cdf3ec
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/default-demo/README.md
@@ -0,0 +1,15 @@
+default Demo
+======
+
+This demo database contains:
+
+- DDL of latest schema
+
+- Minimum reference data required for deployment of platform which is:
+  -  Its mandatory to have one selected currency so we default to several of latin-america currencies
+  -  Its mandatory to have one root or head office, so we have one created by default called a 'Latam HO'
+  -  Permissions supported/needed by latest release of software are setup
+  -  Its mandatory to have at least one role when creating new users so we have one role created by default called 'Super user' which has the special permission 'Full Authorisation'. Any user with this role can do anything in the system.
+  -  Its required to have at least one application user setup so remaining setup can be done through ui so we have on application user created by default with username 'quipo' with a password of 'quipo'. Application users must be associated with an office and a role so this user is associated with 'Latam HO' and 'Super user' role allowing this user to do any operation in any office(branch).
+
+Minimum amount of data to support api-docs interaction.
\ No newline at end of file
diff --git a/fineract-db/multi-tenant-demo-backups/default-demo/bk_mifostenant-default.sql b/fineract-db/multi-tenant-demo-backups/default-demo/bk_mifostenant-default.sql
new file mode 100644
index 0000000..306570a
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/default-demo/bk_mifostenant-default.sql
@@ -0,0 +1,1935 @@
+-- MySQL dump 10.13  Distrib 5.1.60, for Win32 (ia32)
+--
+-- Host: localhost    Database: mifostenant-default
+-- ------------------------------------------------------
+-- Server version	5.1.60-community
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `acc_gl_account`
+--
+
+DROP TABLE IF EXISTS `acc_gl_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=98 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_account`
+--
+
+LOCK TABLES `acc_gl_account` WRITE;
+/*!40000 ALTER TABLE `acc_gl_account` DISABLE KEYS */;
+INSERT INTO `acc_gl_account` VALUES (1,'Petty Cash Balances',NULL,'11100',0,1,2,1,NULL),(2,'Cash in Valut 1',NULL,'11101',0,1,1,1,NULL),(3,'Bank Balances',NULL,'11200',0,1,2,1,NULL),(4,'Centenary Opening Account',NULL,'11201',0,1,1,1,NULL),(5,'Centenary Expense Account',NULL,'11202',0,1,1,1,NULL),(6,'Centenary USD Account',NULL,'11203',0,1,1,1,NULL),(7,'Loans and Advances',NULL,'13100',0,1,2,1,NULL),(8,'Loans to Clients',NULL,'13101',0,1,1,1,NULL),(9,'Outstanding Interest',NULL,'13102',0,1,1,1,NULL),(10,'Outstanding Late Payment Interest',NULL,'13103',0,1,1,1,NULL),(11,'Outstanding Bank Fees to be collected',NULL,'13104',0,1,1,1,NULL),(12,'WriteOff Accounts',NULL,'13200',0,1,2,1,NULL),(13,'Write-offs (use for funds coming in)',NULL,'13201',0,1,1,1,NULL),(14,'Write-offs outstanding principal',NULL,'13202',0,1,1,1,NULL),(15,'Write-offs outstanding interest',NULL,'13203',0,1,1,1,NULL),(16,'Write-offs collected bank fees',NULL,'13204',0,1,1,1,NULL),(17,'Write-offs hardware/furniture',NULL,'13205',0,1,1,1,NULL),(18,'Fixed Assets',NULL,'14100',0,1,2,1,NULL),(19,'Office Equipment',NULL,'14101',0,1,1,1,NULL),(20,'Suspense Items (unidentified deposits)',NULL,'15000',0,1,2,1,NULL),(21,'Assets',NULL,'10000',0,1,2,1,NULL),(22,'Liabilities',NULL,'20000',0,1,2,2,NULL),(23,'Shares Account',NULL,'26100',0,1,2,2,NULL),(24,'Shares Captial',NULL,'26101',0,1,1,2,NULL),(25,'Donated Equity',NULL,'26300',0,1,2,2,NULL),(26,'Donated Equity Ameropa Foundation',NULL,'26301',0,1,1,2,NULL),(27,'Donated Equity e.h',NULL,'26302',0,1,1,2,NULL),(28,'Overpaid Amounts',NULL,'27000',0,1,2,2,NULL),(29,'Loss Provision',NULL,'28000',0,1,2,2,NULL),(30,'Provision Outstanding Principal',NULL,'28001',0,1,1,2,NULL),(31,'Provision Oustanding Interest',NULL,'28002',0,1,1,2,NULL),(32,'Income',NULL,'30000',0,1,2,4,NULL),(33,'Interest Income from Loans',NULL,'31100',0,1,2,4,NULL),(34,'Interest on Loans',NULL,'31101',0,1,1,4,NULL),(35,'Late Payment Interest',NULL,'31102',0,1,1,4,NULL),(36,'Income from Micro credit & Lending Activities',NULL,'31300',0,1,2,4,NULL),(37,'Collected Bank Fees Receivable',NULL,'6201',0,1,1,4,NULL),(38,'Deposits from Loans Write Off',NULL,'31400',0,1,2,4,NULL),(39,'Expenditure',NULL,'40000',0,1,2,5,NULL),(40,'Office Expenditure Account',NULL,'42100',0,1,2,5,NULL),(41,'Water Charges',NULL,'42102',0,1,1,5,NULL),(42,'Electricity Charges',NULL,'42103',0,1,1,5,NULL),(43,'Printing and Stationary',NULL,'42105',0,1,1,5,NULL),(44,'Office Rent',NULL,'42107',0,1,1,5,NULL),(45,'Marketing Expense',NULL,'42109',0,1,1,5,NULL),(46,'Office utilities',NULL,'42112',0,1,1,5,'(supplies, toiletries, kitchen)'),(47,'Furniture',NULL,'42113',0,1,1,5,NULL),(48,'CEDA Meeting Expense',NULL,'42114',0,1,1,5,NULL),(49,'Employee Personal Expsense Account',NULL,'42200',0,1,2,5,NULL),(50,'Salary Alice',NULL,'42201',0,1,1,5,NULL),(51,'Salary Irene',NULL,'42202',0,1,1,5,NULL),(52,'Salary Richard',NULL,'42203',0,1,1,5,NULL),(53,'Salary Loan Officer TBA',NULL,'42204',0,1,1,5,NULL),(54,'Medical Insurance Alice & Family',NULL,'42205',0,1,1,5,NULL),(55,'Medical Insurance Irene',NULL,'42206',0,1,1,5,NULL),(56,'Medical Insurance Richard',NULL,'42207',0,1,1,5,NULL),(57,'Medical Insurance Loan Officer TBA',NULL,'42208',0,1,1,5,NULL),(58,'PAYE all employees',NULL,'42209',0,1,1,5,NULL),(59,'NSSF all employees',NULL,'42210',0,1,1,5,NULL),(60,'Lunch Allowances all employees',NULL,'42211',0,1,1,5,NULL),(61,'IT software and maintenance',NULL,'42300',0,1,2,5,NULL),(62,'Mifos maintenance contract 12 months',NULL,'42301',0,1,1,5,NULL),(63,'VPS Contract 12 months',NULL,'42302',0,1,1,5,NULL),(64,'Bulk SMS Service',NULL,'42303',0,1,1,5,NULL),(65,'Support Accounting Software',NULL,'42304',0,1,1,5,NULL),(66,'Mifos Instance Setup',NULL,'42305',0,1,1,5,NULL),(67,'Misc support expense',NULL,'42306',0,1,1,5,NULL),(68,'Warid Mobile Line',NULL,'42307',0,1,1,5,NULL),(69,'Landline',NULL,'42308',0,1,1,5,NULL),(70,'Modem Alice',NULL,'42309',0,1,1,5,NULL),(71,'Modem Irene',NULL,'42310',0,1,1,5,NULL),(72,'Modem Richard',NULL,'42311',0,1,1,5,NULL),(73,'Repairs',NULL,'42312',0,1,1,5,NULL),(74,'Airtime Expenses',NULL,'42400',0,1,2,5,NULL),(75,'Airtime Alice',NULL,'42401',0,1,1,5,NULL),(76,'Airtime Richard',NULL,'42402',0,1,1,5,NULL),(77,'Airtime Loan Office TBA',NULL,'42403',0,1,1,5,NULL),(78,'Special Airtime Alice',NULL,'42404',0,1,1,5,NULL),(79,'Transportation',NULL,'42500',0,1,2,5,NULL),(80,'Flat monthly transportation cost',NULL,'42501',0,1,1,5,NULL),(81,'Faciliation cost for Richard',NULL,'42502',0,1,1,5,NULL),(82,'Faciliation cost for Loan Officer TBA',NULL,'42503',0,1,1,5,NULL),(83,'Consultancy Expenses',NULL,'42600',0,1,2,5,NULL),(84,'Audit Fees',NULL,'42601',0,1,1,5,NULL),(85,'Legal Fees',NULL,'42602',0,1,1,5,NULL),(86,'Miscellaneous Expenses Account',NULL,'42700',0,1,2,5,NULL),(87,'MFI License',NULL,'42703',0,1,1,5,NULL),(88,'Sundy Expenses',NULL,'42704',0,1,1,5,NULL),(89,'Bank Fees',NULL,'42800',0,1,2,5,NULL),(90,'Bank Charges Operating Account',NULL,'42801',0,1,1,5,NULL),(91,'Bank Charges Expense Account',NULL,'42802',0,1,1,5,NULL),(92,'E.H Account',NULL,'42900',0,1,2,5,NULL),(93,'Airtime',NULL,'42901',0,1,1,5,NULL),(94,'Modem',NULL,'42902',0,1,1,5,NULL),(95,'Meals',NULL,'42903',0,1,1,5,NULL),(96,'Transportation',NULL,'42904',0,1,1,5,NULL),(97,'Miscellaneous',NULL,'42905',0,1,1,5,NULL);
+/*!40000 ALTER TABLE `acc_gl_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_closure`
+--
+
+DROP TABLE IF EXISTS `acc_gl_closure`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_closure`
+--
+
+LOCK TABLES `acc_gl_closure` WRITE;
+/*!40000 ALTER TABLE `acc_gl_closure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_closure` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_journal_entry`
+--
+
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_entry` tinyint(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type_enum` smallint(5) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_journal_entry`
+--
+
+LOCK TABLES `acc_gl_journal_entry` WRITE;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_product_mapping`
+--
+
+DROP TABLE IF EXISTS `acc_product_mapping`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_product_mapping`
+--
+
+LOCK TABLES `acc_product_mapping` WRITE;
+/*!40000 ALTER TABLE `acc_product_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_product_mapping` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `c_configuration`
+--
+
+DROP TABLE IF EXISTS `c_configuration`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `c_configuration`
+--
+
+LOCK TABLES `c_configuration` WRITE;
+/*!40000 ALTER TABLE `c_configuration` DISABLE KEYS */;
+INSERT INTO `c_configuration` VALUES (1,'maker-checker',0);
+/*!40000 ALTER TABLE `c_configuration` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `client additional data`
+--
+
+DROP TABLE IF EXISTS `client additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `client additional data` (
+  `client_id` bigint(20) NOT NULL,
+  `Gender_cd` int(11) NOT NULL,
+  `Date of Birth` date NOT NULL,
+  `Home address` text NOT NULL,
+  `Telephone number` varchar(20) NOT NULL,
+  `Telephone number (2nd)` varchar(20) NOT NULL,
+  `Email address` varchar(50) NOT NULL,
+  `EducationLevel_cd` int(11) NOT NULL,
+  `MaritalStatus_cd` int(11) NOT NULL,
+  `Number of children` int(11) NOT NULL,
+  `Citizenship` varchar(50) NOT NULL,
+  `PovertyStatus_cd` int(11) NOT NULL,
+  `YesNo_cd_Employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of employment` int(11) DEFAULT NULL,
+  `Employer name` varchar(50) DEFAULT NULL,
+  `Number of years` int(11) DEFAULT NULL,
+  `Monthly salary` decimal(19,6) DEFAULT NULL,
+  `YesNo_cd_Self employed` int(11) NOT NULL,
+  `FieldOfEmployment_cd_Field of self-employment` int(11) DEFAULT NULL,
+  `Business address` text,
+  `Number of employees` int(11) DEFAULT NULL,
+  `Monthly salaries paid` decimal(19,6) DEFAULT NULL,
+  `Monthly net income of business activity` decimal(19,6) DEFAULT NULL,
+  `Monthly rent` decimal(19,6) DEFAULT NULL,
+  `Other income generating activities` varchar(100) DEFAULT NULL,
+  `YesNo_cd_Bookkeeping` int(11) DEFAULT NULL,
+  `YesNo_cd_Loans with other institutions` int(11) NOT NULL,
+  `From whom` varchar(100) DEFAULT NULL,
+  `Amount` decimal(19,6) DEFAULT NULL,
+  `Interest rate pa` decimal(19,6) DEFAULT NULL,
+  `Number of people depending on overal income` int(11) NOT NULL,
+  `YesNo_cd_Bank account` int(11) NOT NULL,
+  `YesNo_cd_Business plan provided` int(11) NOT NULL,
+  `YesNo_cd_Access to internet` int(11) DEFAULT NULL,
+  `Introduced by` varchar(100) DEFAULT NULL,
+  `Known to introducer since` varchar(100) NOT NULL,
+  `Last visited by` varchar(100) DEFAULT NULL,
+  `Last visited on` date NOT NULL,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_client_additional_data` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `client additional data`
+--
+
+LOCK TABLES `client additional data` WRITE;
+/*!40000 ALTER TABLE `client additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `client additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_client_details`
+--
+
+DROP TABLE IF EXISTS `extra_client_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_client_details` (
+  `client_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_extra_client_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_client_details`
+--
+
+LOCK TABLES `extra_client_details` WRITE;
+/*!40000 ALTER TABLE `extra_client_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_client_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_family_details`
+--
+
+DROP TABLE IF EXISTS `extra_family_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_family_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `Name` varchar(40) DEFAULT NULL,
+  `Date of Birth` date DEFAULT NULL,
+  `Points Score` int(11) DEFAULT NULL,
+  `Education_cd_Highest` int(11) DEFAULT NULL,
+  `Other Notes` text,
+  PRIMARY KEY (`id`),
+  KEY `FK_Extra Family Details Data_1` (`client_id`),
+  CONSTRAINT `FK_family_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_family_details`
+--
+
+LOCK TABLES `extra_family_details` WRITE;
+/*!40000 ALTER TABLE `extra_family_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_family_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_loan_details`
+--
+
+DROP TABLE IF EXISTS `extra_loan_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_loan_details` (
+  `loan_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_extra_loan_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_loan_details`
+--
+
+LOCK TABLES `extra_loan_details` WRITE;
+/*!40000 ALTER TABLE `extra_loan_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_loan_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `impact measurement`
+--
+
+DROP TABLE IF EXISTS `impact measurement`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `impact measurement` (
+  `loan_id` bigint(20) NOT NULL,
+  `YesNo_cd_RepaidOnSchedule` int(11) NOT NULL,
+  `ReasonNotRepaidOnSchedule` text,
+  `How was Loan Amount Invested` text NOT NULL,
+  `Additional Income Generated` decimal(19,6) NOT NULL,
+  `Additional Income Used For` text NOT NULL,
+  `YesNo_cd_NewJobsCreated` int(11) NOT NULL,
+  `Number of Jobs Created` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_impact measurement` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `impact measurement`
+--
+
+LOCK TABLES `impact measurement` WRITE;
+/*!40000 ALTER TABLE `impact measurement` DISABLE KEYS */;
+/*!40000 ALTER TABLE `impact measurement` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `loan additional data`
+--
+
+DROP TABLE IF EXISTS `loan additional data`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `loan additional data` (
+  `loan_id` bigint(20) NOT NULL,
+  `PurposeOfLoan_cd` int(11) NOT NULL,
+  `CollateralType_cd` int(11) NOT NULL,
+  `Collateral notes` text NOT NULL,
+  `YesNo_cd_Guarantor` int(11) NOT NULL,
+  `Guarantor name` varchar(100) DEFAULT NULL,
+  `Guarantor relation` varchar(100) DEFAULT NULL,
+  `Guarantor address` varchar(100) DEFAULT NULL,
+  `Guarantor telephone number` varchar(20) DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_loan_additional_data` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `loan additional data`
+--
+
+LOCK TABLES `loan additional data` WRITE;
+/*!40000 ALTER TABLE `loan additional data` DISABLE KEYS */;
+/*!40000 ALTER TABLE `loan additional data` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser`
+--
+
+DROP TABLE IF EXISTS `m_appuser`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser`
+--
+
+LOCK TABLES `m_appuser` WRITE;
+/*!40000 ALTER TABLE `m_appuser` DISABLE KEYS */;
+INSERT INTO `m_appuser` VALUES (1,0,1,'mifos','App','Administrator','5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a','demomfi@mifos.org','\0','','','','');
+/*!40000 ALTER TABLE `m_appuser` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser_role`
+--
+
+DROP TABLE IF EXISTS `m_appuser_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser_role`
+--
+
+LOCK TABLES `m_appuser_role` WRITE;
+/*!40000 ALTER TABLE `m_appuser_role` DISABLE KEYS */;
+INSERT INTO `m_appuser_role` VALUES (1,1);
+/*!40000 ALTER TABLE `m_appuser_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar`
+--
+
+DROP TABLE IF EXISTS `m_calendar`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `title` varchar(50) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar`
+--
+
+LOCK TABLES `m_calendar` WRITE;
+/*!40000 ALTER TABLE `m_calendar` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_calendar_instance`
+--
+
+DROP TABLE IF EXISTS `m_calendar_instance`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_calendar_instance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `entity_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_calendar_instance`
+--
+
+LOCK TABLES `m_calendar_instance` WRITE;
+/*!40000 ALTER TABLE `m_calendar_instance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar_instance` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_charge`
+--
+
+DROP TABLE IF EXISTS `m_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_charge`
+--
+
+LOCK TABLES `m_charge` WRITE;
+/*!40000 ALTER TABLE `m_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client`
+--
+
+DROP TABLE IF EXISTS `m_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `image_key` varchar(500) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client`
+--
+
+LOCK TABLES `m_client` WRITE;
+/*!40000 ALTER TABLE `m_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client_identifier`
+--
+
+DROP TABLE IF EXISTS `m_client_identifier`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client_identifier`
+--
+
+LOCK TABLES `m_client_identifier` WRITE;
+/*!40000 ALTER TABLE `m_client_identifier` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_identifier` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code`
+--
+
+DROP TABLE IF EXISTS `m_code`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code`
+--
+
+LOCK TABLES `m_code` WRITE;
+/*!40000 ALTER TABLE `m_code` DISABLE KEYS */;
+INSERT INTO `m_code` VALUES (1,'Customer Identifier',1),(2,'LoanCollateral',1),(3,'LoanPurpose',1),(4,'Gender',1),(5,'YesNo',1),(6,'Education',1);
+/*!40000 ALTER TABLE `m_code` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code_value`
+--
+
+DROP TABLE IF EXISTS `m_code_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code_value`
+--
+
+LOCK TABLES `m_code_value` WRITE;
+/*!40000 ALTER TABLE `m_code_value` DISABLE KEYS */;
+INSERT INTO `m_code_value` VALUES (1,1,'Passport',1),(2,1,'Id',1),(3,1,'Drivers License',2),(4,1,'Any Other Id Type',3),(5,4,'option.Male',1),(6,4,'option.Female',5),(7,5,'option.Yes',1),(8,5,'option.No',7),(9,6,'Primary',1),(10,6,'Secondary',9),(11,6,'University',10);
+/*!40000 ALTER TABLE `m_code_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_currency`
+--
+
+DROP TABLE IF EXISTS `m_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_currency`
+--
+
+LOCK TABLES `m_currency` WRITE;
+/*!40000 ALTER TABLE `m_currency` DISABLE KEYS */;
+INSERT INTO `m_currency` VALUES (1,'AED',2,NULL,'UAE Dirham','currency.AED'),(2,'AFN',2,NULL,'Afghanistan Afghani','currency.AFN'),(3,'ALL',2,NULL,'Albanian Lek','currency.ALL'),(4,'AMD',2,NULL,'Armenian Dram','currency.AMD'),(5,'ANG',2,NULL,'Netherlands Antillian Guilder','currency.ANG'),(6,'AOA',2,NULL,'Angolan Kwanza','currency.AOA'),(7,'ARS',2,'$','Argentine Peso','currency.ARS'),(8,'AUD',2,'A$','Australian Dollar','currency.AUD'),(9,'AWG',2,NULL,'Aruban Guilder','currency.AWG'),(10,'AZM',2,NULL,'Azerbaijanian Manat','currency.AZM'),(11,'BAM',2,NULL,'Bosnia and Herzegovina Convertible Marks','currency.BAM'),(12,'BBD',2,NULL,'Barbados Dollar','currency.BBD'),(13,'BDT',2,NULL,'Bangladesh Taka','currency.BDT'),(14,'BGN',2,NULL,'Bulgarian Lev','currency.BGN'),(15,'BHD',3,NULL,'Bahraini Dinar','currency.BHD'),(16,'BIF',0,NULL,'Burundi Franc','currency.BIF'),(17,'BMD',2,NULL,'Bermudian Dollar','currency.BMD'),(18,'BND',2,'B$','Brunei Dollar','currency.BND'),(19,'BOB',2,'Bs.','Bolivian Boliviano','currency.BOB'),(20,'BRL',2,'R$','Brazilian Real','currency.BRL'),(21,'BSD',2,NULL,'Bahamian Dollar','currency.BSD'),(22,'BTN',2,NULL,'Bhutan Ngultrum','currency.BTN'),(23,'BWP',2,NULL,'Botswana Pula','currency.BWP'),(24,'BYR',0,NULL,'Belarussian Ruble','currency.BYR'),(25,'BZD',2,'BZ$','Belize Dollar','currency.BZD'),(26,'CAD',2,NULL,'Canadian Dollar','currency.CAD'),(27,'CDF',2,NULL,'Franc Congolais','currency.CDF'),(28,'CHF',2,NULL,'Swiss Franc','currency.CHF'),(29,'CLP',0,'$','Chilean Peso','currency.CLP'),(30,'CNY',2,NULL,'Chinese Yuan Renminbi','currency.CNY'),(31,'COP',2,'$','Colombian Peso','currency.COP'),(32,'CRC',2,'₡','Costa Rican Colon','currency.CRC'),(33,'CSD',2,NULL,'Serbian Dinar','currency.CSD'),(34,'CUP',2,'$MN','Cuban Peso','currency.CUP'),(35,'CVE',2,NULL,'Cape Verde Escudo','currency.CVE'),(36,'CYP',2,NULL,'Cyprus Pound','currency.CYP'),(37,'CZK',2,NULL,'Czech Koruna','currency.CZK'),(38,'DJF',0,NULL,'Djibouti Franc','currency.DJF'),(39,'DKK',2,NULL,'Danish Krone','currency.DKK'),(40,'DOP',2,'RD$','Dominican Peso','currency.DOP'),(41,'DZD',2,NULL,'Algerian Dinar','currency.DZD'),(42,'EEK',2,NULL,'Estonian Kroon','currency.EEK'),(43,'EGP',2,NULL,'Egyptian Pound','currency.EGP'),(44,'ERN',2,NULL,'Eritrea Nafka','currency.ERN'),(45,'ETB',2,NULL,'Ethiopian Birr','currency.ETB'),(46,'EUR',2,'€','Euro','currency.EUR'),(47,'FJD',2,NULL,'Fiji Dollar','currency.FJD'),(48,'FKP',2,NULL,'Falkland Islands Pound','currency.FKP'),(49,'GBP',2,NULL,'Pound Sterling','currency.GBP'),(50,'GEL',2,NULL,'Georgian Lari','currency.GEL'),(51,'GHC',2,'GHc','Ghana Cedi','currency.GHC'),(52,'GIP',2,NULL,'Gibraltar Pound','currency.GIP'),(53,'GMD',2,NULL,'Gambian Dalasi','currency.GMD'),(54,'GNF',0,NULL,'Guinea Franc','currency.GNF'),(55,'GTQ',2,'Q','Guatemala Quetzal','currency.GTQ'),(56,'GYD',2,NULL,'Guyana Dollar','currency.GYD'),(57,'HKD',2,NULL,'Hong Kong Dollar','currency.HKD'),(58,'HNL',2,'L','Honduras Lempira','currency.HNL'),(59,'HRK',2,NULL,'Croatian Kuna','currency.HRK'),(60,'HTG',2,'G','Haiti Gourde','currency.HTG'),(61,'HUF',2,NULL,'Hungarian Forint','currency.HUF'),(62,'IDR',2,NULL,'Indonesian Rupiah','currency.IDR'),(63,'ILS',2,NULL,'New Israeli Shekel','currency.ILS'),(64,'INR',2,'₹','Indian Rupee','currency.INR'),(65,'IQD',3,NULL,'Iraqi Dinar','currency.IQD'),(66,'IRR',2,NULL,'Iranian Rial','currency.IRR'),(67,'ISK',0,NULL,'Iceland Krona','currency.ISK'),(68,'JMD',2,NULL,'Jamaican Dollar','currency.JMD'),(69,'JOD',3,NULL,'Jordanian Dinar','currency.JOD'),(70,'JPY',0,NULL,'Japanese Yen','currency.JPY'),(71,'KES',2,'KSh','Kenyan Shilling','currency.KES'),(72,'KGS',2,NULL,'Kyrgyzstan Som','currency.KGS'),(73,'KHR',2,NULL,'Cambodia Riel','currency.KHR'),(74,'KMF',0,NULL,'Comoro Franc','currency.KMF'),(75,'KPW',2,NULL,'North Korean Won','currency.KPW'),(76,'KRW',0,NULL,'Korean Won','currency.KRW'),(77,'KWD',3,NULL,'Kuwaiti Dinar','currency.KWD'),(78,'KYD',2,NULL,'Cayman Islands Dollar','currency.KYD'),(79,'KZT',2,NULL,'Kazakhstan Tenge','currency.KZT'),(80,'LAK',2,NULL,'Lao Kip','currency.LAK'),(81,'LBP',2,'L£','Lebanese Pound','currency.LBP'),(82,'LKR',2,NULL,'Sri Lanka Rupee','currency.LKR'),(83,'LRD',2,NULL,'Liberian Dollar','currency.LRD'),(84,'LSL',2,NULL,'Lesotho Loti','currency.LSL'),(85,'LTL',2,NULL,'Lithuanian Litas','currency.LTL'),(86,'LVL',2,NULL,'Latvian Lats','currency.LVL'),(87,'LYD',3,NULL,'Libyan Dinar','currency.LYD'),(88,'MAD',2,NULL,'Moroccan Dirham','currency.MAD'),(89,'MDL',2,NULL,'Moldovan Leu','currency.MDL'),(90,'MGA',2,NULL,'Malagasy Ariary','currency.MGA'),(91,'MKD',2,NULL,'Macedonian Denar','currency.MKD'),(92,'MMK',2,'K','Myanmar Kyat','currency.MMK'),(93,'MNT',2,NULL,'Mongolian Tugrik','currency.MNT'),(94,'MOP',2,NULL,'Macau Pataca','currency.MOP'),(95,'MRO',2,NULL,'Mauritania Ouguiya','currency.MRO'),(96,'MTL',2,NULL,'Maltese Lira','currency.MTL'),(97,'MUR',2,NULL,'Mauritius Rupee','currency.MUR'),(98,'MVR',2,NULL,'Maldives Rufiyaa','currency.MVR'),(99,'MWK',2,NULL,'Malawi Kwacha','currency.MWK'),(100,'MXN',2,'$','Mexican Peso','currency.MXN'),(101,'MYR',2,NULL,'Malaysian Ringgit','currency.MYR'),(102,'MZM',2,NULL,'Mozambique Metical','currency.MZM'),(103,'NAD',2,NULL,'Namibia Dollar','currency.NAD'),(104,'NGN',2,NULL,'Nigerian Naira','currency.NGN'),(105,'NIO',2,'C$','Nicaragua Cordoba Oro','currency.NIO'),(106,'NOK',2,NULL,'Norwegian Krone','currency.NOK'),(107,'NPR',2,NULL,'Nepalese Rupee','currency.NPR'),(108,'NZD',2,NULL,'New Zealand Dollar','currency.NZD'),(109,'OMR',3,NULL,'Rial Omani','currency.OMR'),(110,'PAB',2,'B/.','Panama Balboa','currency.PAB'),(111,'PEN',2,'S/.','Peruvian Nuevo Sol','currency.PEN'),(112,'PGK',2,NULL,'Papua New Guinea Kina','currency.PGK'),(113,'PHP',2,NULL,'Philippine Peso','currency.PHP'),(114,'PKR',2,NULL,'Pakistan Rupee','currency.PKR'),(115,'PLN',2,NULL,'Polish Zloty','currency.PLN'),(116,'PYG',0,'₲','Paraguayan Guarani','currency.PYG'),(117,'QAR',2,NULL,'Qatari Rial','currency.QAR'),(118,'RON',2,NULL,'Romanian Leu','currency.RON'),(119,'RUB',2,NULL,'Russian Ruble','currency.RUB'),(120,'RWF',0,NULL,'Rwanda Franc','currency.RWF'),(121,'SAR',2,NULL,'Saudi Riyal','currency.SAR'),(122,'SBD',2,NULL,'Solomon Islands Dollar','currency.SBD'),(123,'SCR',2,NULL,'Seychelles Rupee','currency.SCR'),(124,'SDD',2,NULL,'Sudanese Dinar','currency.SDD'),(125,'SEK',2,NULL,'Swedish Krona','currency.SEK'),(126,'SGD',2,NULL,'Singapore Dollar','currency.SGD'),(127,'SHP',2,NULL,'St Helena Pound','currency.SHP'),(128,'SIT',2,NULL,'Slovenian Tolar','currency.SIT'),(129,'SKK',2,NULL,'Slovak Koruna','currency.SKK'),(130,'SLL',2,NULL,'Sierra Leone Leone','currency.SLL'),(131,'SOS',2,NULL,'Somali Shilling','currency.SOS'),(132,'SRD',2,NULL,'Surinam Dollar','currency.SRD'),(133,'STD',2,NULL,'Sao Tome and Principe Dobra','currency.STD'),(134,'SVC',2,NULL,'El Salvador Colon','currency.SVC'),(135,'SYP',2,NULL,'Syrian Pound','currency.SYP'),(136,'SZL',2,NULL,'Swaziland Lilangeni','currency.SZL'),(137,'THB',2,NULL,'Thai Baht','currency.THB'),(138,'TJS',2,NULL,'Tajik Somoni','currency.TJS'),(139,'TMM',2,NULL,'Turkmenistan Manat','currency.TMM'),(140,'TND',3,'DT','Tunisian Dinar','currency.TND'),(141,'TOP',2,NULL,'Tonga Pa\'anga','currency.TOP'),(142,'TRY',2,NULL,'Turkish Lira','currency.TRY'),(143,'TTD',2,NULL,'Trinidad and Tobago Dollar','currency.TTD'),(144,'TWD',2,NULL,'New Taiwan Dollar','currency.TWD'),(145,'TZS',2,NULL,'Tanzanian Shilling','currency.TZS'),(146,'UAH',2,NULL,'Ukraine Hryvnia','currency.UAH'),(147,'UGX',2,'USh','Uganda Shilling','currency.UGX'),(148,'USD',2,'$','US Dollar','currency.USD'),(149,'UYU',2,'$U','Peso Uruguayo','currency.UYU'),(150,'UZS',2,NULL,'Uzbekistan Sum','currency.UZS'),(151,'VEB',2,'Bs.F.','Venezuelan Bolivar','currency.VEB'),(152,'VND',2,NULL,'Vietnamese Dong','currency.VND'),(153,'VUV',0,NULL,'Vanuatu Vatu','currency.VUV'),(154,'WST',2,NULL,'Samoa Tala','currency.WST'),(155,'XAF',0,NULL,'CFA Franc BEAC','currency.XAF'),(156,'XCD',2,NULL,'East Caribbean Dollar','currency.XCD'),(157,'XDR',5,NULL,'SDR (Special Drawing Rights)','currency.XDR'),(158,'XOF',0,'CFA','CFA Franc BCEAO','currency.XOF'),(159,'XPF',0,NULL,'CFP Franc','currency.XPF'),(160,'YER',2,NULL,'Yemeni Rial','currency.YER'),(161,'ZAR',2,'R','South African Rand','currency.ZAR'),(162,'ZMK',2,NULL,'Zambian Kwacha','currency.ZMK'),(163,'ZWD',2,NULL,'Zimbabwe Dollar','currency.ZWD');
+/*!40000 ALTER TABLE `m_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_document`
+--
+
+DROP TABLE IF EXISTS `m_document`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(50) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_document`
+--
+
+LOCK TABLES `m_document` WRITE;
+/*!40000 ALTER TABLE `m_document` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_document` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_fund`
+--
+
+DROP TABLE IF EXISTS `m_fund`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_fund`
+--
+
+LOCK TABLES `m_fund` WRITE;
+/*!40000 ALTER TABLE `m_fund` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_fund` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group`
+--
+
+DROP TABLE IF EXISTS `m_group`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `level_Id` int(11) NOT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`,`level_Id`),
+  UNIQUE KEY `external_id` (`external_id`,`level_Id`),
+  KEY `office_id` (`office_id`),
+  KEY `staff_id` (`staff_id`),
+  KEY `Parent_Id_reference` (`parent_id`),
+  KEY `FK_m_group_level` (`level_Id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_Id`) REFERENCES `m_group_level` (`id`),
+  CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group`
+--
+
+LOCK TABLES `m_group` WRITE;
+/*!40000 ALTER TABLE `m_group` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_client`
+--
+
+DROP TABLE IF EXISTS `m_group_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_client`
+--
+
+LOCK TABLES `m_group_client` WRITE;
+/*!40000 ALTER TABLE `m_group_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_level`
+--
+
+DROP TABLE IF EXISTS `m_group_level`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_level` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) DEFAULT NULL,
+  `super_parent` tinyint(1) NOT NULL,
+  `level_name` varchar(100) NOT NULL,
+  `recursable` tinyint(1) NOT NULL,
+  `can_have_clients` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `Parent_levelId_reference` (`parent_id`),
+  CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_level`
+--
+
+LOCK TABLES `m_group_level` WRITE;
+/*!40000 ALTER TABLE `m_group_level` DISABLE KEYS */;
+INSERT INTO `m_group_level` VALUES (1,NULL,1,'Center',1,0),(2,1,0,'Group',0,1);
+/*!40000 ALTER TABLE `m_group_level` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_guarantor`
+--
+
+DROP TABLE IF EXISTS `m_guarantor`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_guarantor` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `dob` date DEFAULT NULL,
+  `address_line_1` varchar(500) DEFAULT NULL,
+  `address_line_2` varchar(500) DEFAULT NULL,
+  `city` varchar(50) DEFAULT NULL,
+  `state` varchar(50) DEFAULT NULL,
+  `country` varchar(50) DEFAULT NULL,
+  `zip` varchar(20) DEFAULT NULL,
+  `house_phone_number` varchar(20) DEFAULT NULL,
+  `mobile_number` varchar(20) DEFAULT NULL,
+  `comment` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_m_loan` (`loan_id`),
+  CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_guarantor`
+--
+
+LOCK TABLES `m_guarantor` WRITE;
+/*!40000 ALTER TABLE `m_guarantor` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan`
+--
+
+DROP TABLE IF EXISTS `m_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `loanpurpose_cv_id` int(11) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `disbursedon_userid` bigint(20) DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `principal_disbursed_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `rescheduledon_date` date DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `writtenoffon_date` date DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loanpurpose_codevalue` (`loanpurpose_cv_id`),
+  KEY `FK_submittedon_userid` (`submittedon_userid`),
+  KEY `FK_approvedon_userid` (`approvedon_userid`),
+  KEY `FK_rejectedon_userid` (`rejectedon_userid`),
+  KEY `FK_withdrawnon_userid` (`withdrawnon_userid`),
+  KEY `FK_disbursedon_userid` (`disbursedon_userid`),
+  KEY `FK_closedon_userid` (`closedon_userid`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_approvedon_userid` FOREIGN KEY (`approvedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_closedon_userid` FOREIGN KEY (`closedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_disbursedon_userid` FOREIGN KEY (`disbursedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loanpurpose_codevalue` FOREIGN KEY (`loanpurpose_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_rejectedon_userid` FOREIGN KEY (`rejectedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_submittedon_userid` FOREIGN KEY (`submittedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_withdrawnon_userid` FOREIGN KEY (`withdrawnon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan`
+--
+
+LOCK TABLES `m_loan` WRITE;
+/*!40000 ALTER TABLE `m_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_arrears_aging`
+--
+
+DROP TABLE IF EXISTS `m_loan_arrears_aging`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_arrears_aging` (
+  `loan_id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `principal_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `overdue_since_date_derived` date DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_arrears_aging_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_arrears_aging`
+--
+
+LOCK TABLES `m_loan_arrears_aging` WRITE;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_charge`
+--
+
+LOCK TABLES `m_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_collateral`
+--
+
+DROP TABLE IF EXISTS `m_loan_collateral`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_collateral` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_cv_id` int(11) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_collateral_m_loan` (`loan_id`),
+  KEY `FK_collateral_code_value` (`type_cv_id`),
+  CONSTRAINT `FK_collateral_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_collateral_code_value` FOREIGN KEY (`type_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_collateral`
+--
+
+LOCK TABLES `m_loan_collateral` WRITE;
+/*!40000 ALTER TABLE `m_loan_collateral` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_collateral` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_officer_assignment_history`
+--
+
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_officer_assignment_history`
+--
+
+LOCK TABLES `m_loan_officer_assignment_history` WRITE;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_repayment_schedule`
+--
+
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_repayment_schedule`
+--
+
+LOCK TABLES `m_loan_repayment_schedule` WRITE;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_transaction`
+--
+
+DROP TABLE IF EXISTS `m_loan_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_transaction`
+--
+
+LOCK TABLES `m_loan_transaction` WRITE;
+/*!40000 ALTER TABLE `m_loan_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_note`
+--
+
+DROP TABLE IF EXISTS `m_note`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK_m_note_m_group` (`group_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_note_m_group` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_note`
+--
+
+LOCK TABLES `m_note` WRITE;
+/*!40000 ALTER TABLE `m_note` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_note` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office`
+--
+
+DROP TABLE IF EXISTS `m_office`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office`
+--
+
+LOCK TABLES `m_office` WRITE;
+/*!40000 ALTER TABLE `m_office` DISABLE KEYS */;
+INSERT INTO `m_office` VALUES (1,NULL,'.','1','Head Office','2009-01-01');
+/*!40000 ALTER TABLE `m_office` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office_transaction`
+--
+
+DROP TABLE IF EXISTS `m_office_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office_transaction`
+--
+
+LOCK TABLES `m_office_transaction` WRITE;
+/*!40000 ALTER TABLE `m_office_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_office_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_organisation_currency`
+--
+
+DROP TABLE IF EXISTS `m_organisation_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_organisation_currency`
+--
+
+LOCK TABLES `m_organisation_currency` WRITE;
+/*!40000 ALTER TABLE `m_organisation_currency` DISABLE KEYS */;
+INSERT INTO `m_organisation_currency` VALUES (21,'USD',2,'US Dollar','$','currency.USD');
+/*!40000 ALTER TABLE `m_organisation_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_permission`
+--
+
+DROP TABLE IF EXISTS `m_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=299 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_permission`
+--
+
+LOCK TABLES `m_permission` WRITE;
+/*!40000 ALTER TABLE `m_permission` DISABLE KEYS */;
+INSERT INTO `m_permission` VALUES (1,'special','ALL_FUNCTIONS',NULL,NULL,0),(2,'special','ALL_FUNCTIONS_READ',NULL,NULL,0),(3,'special','CHECKER_SUPER_USER',NULL,NULL,0),(4,'special','REPORTING_SUPER_USER',NULL,NULL,0),(5,'authorisation','READ_PERMISSION','PERMISSION','READ',0),(6,'authorisation','PERMISSIONS_ROLE','ROLE','PERMISSIONS',0),(7,'authorisation','CREATE_ROLE','ROLE','CREATE',0),(8,'authorisation','CREATE_ROLE_CHECKER','ROLE','CREATE',0),(9,'authorisation','READ_ROLE','ROLE','READ',0),(10,'authorisation','UPDATE_ROLE','ROLE','UPDATE',0),(11,'authorisation','UPDATE_ROLE_CHECKER','ROLE','UPDATE',0),(12,'authorisation','DELETE_ROLE','ROLE','DELETE',0),(13,'authorisation','DELETE_ROLE_CHECKER','ROLE','DELETE',0),(14,'authorisation','CREATE_USER','USER','CREATE',0),(15,'authorisation','CREATE_USER_CHECKER','USER','CREATE',0),(16,'authorisation','READ_USER','USER','READ',0),(17,'authorisation','UPDATE_USER','USER','UPDATE',0),(18,'authorisation','UPDATE_USER_CHECKER','USER','UPDATE',0),(19,'authorisation','DELETE_USER','USER','DELETE',0),(20,'authorisation','DELETE_USER_CHECKER','USER','DELETE',0),(21,'configuration','READ_CONFIGURATION','CONFIGURATION','READ',0),(22,'configuration','UPDATE_CONFIGURATION','CONFIGURATION','UPDATE',0),(23,'configuration','UPDATE_CONFIGURATION_CHECKER','CONFIGURATION','UPDATE',0),(24,'configuration','READ_CODE','CODE','READ',0),(25,'configuration','CREATE_CODE','CODE','CREATE',0),(26,'configuration','CREATE_CODE_CHECKER','CODE','CREATE',0),(27,'configuration','UPDATE_CODE','CODE','UPDATE',0),(28,'configuration','UPDATE_CODE_CHECKER','CODE','UPDATE',0),(29,'configuration','DELETE_CODE','CODE','DELETE',0),(30,'configuration','DELETE_CODE_CHECKER','CODE','DELETE',0),(31,'configuration','READ_CODEVALUE','CODEVALUE','READ',0),(32,'configuration','CREATE_CODEVALUE','CODEVALUE','CREATE',0),(33,'configuration','CREATE_CODEVALUE_CHECKER','CODEVALUE','CREATE',0),(34,'configuration','UPDATE_CODEVALUE','CODEVALUE','UPDATE',0),(35,'configuration','UPDATE_CODEVALUE_CHECKER','CODEVALUE','UPDATE',0),(36,'configuration','DELETE_CODEVALUE','CODEVALUE','DELETE',0),(37,'configuration','DELETE_CODEVALUE_CHECKER','CODEVALUE','DELETE',0),(38,'configuration','READ_CURRENCY','CURRENCY','READ',0),(39,'configuration','UPDATE_CURRENCY','CURRENCY','UPDATE',0),(40,'configuration','UPDATE_CURRENCY_CHECKER','CURRENCY','UPDATE',0),(41,'configuration','UPDATE_PERMISSION','PERMISSION','UPDATE',0),(42,'configuration','UPDATE_PERMISSION_CHECKER','PERMISSION','UPDATE',0),(43,'configuration','READ_DATATABLE','DATATABLE','READ',0),(44,'configuration','REGISTER_DATATABLE','DATATABLE','REGISTER',0),(45,'configuration','REGISTER_DATATABLE_CHECKER','DATATABLE','REGISTER',0),(46,'configuration','DEREGISTER_DATATABLE','DATATABLE','DEREGISTER',0),(47,'configuration','DEREGISTER_DATATABLE_CHECKER','DATATABLE','DEREGISTER',0),(48,'configuration','READ_AUDIT','AUDIT','READ',0),(49,'configuration','CREATE_CALENDAR','CALENDAR','CREATE',0),(50,'configuration','READ_CALENDAR','CALENDAR','READ',0),(51,'configuration','UPDATE_CALENDAR','CALENDAR','UPDATE',0),(52,'configuration','DELETE_CALENDAR','CALENDAR','DELETE',0),(53,'configuration','CREATE_CALENDAR_CHECKER','CALENDAR','CREATE',0),(54,'configuration','UPDATE_CALENDAR_CHECKER','CALENDAR','UPDATE',0),(55,'configuration','DELETE_CALENDAR_CHECKER','CALENDAR','DELETE',0),(56,'organisation','READ_MAKERCHECKER','MAKERCHECKER','READ',0),(57,'organisation','READ_CHARGE','CHARGE','READ',0),(58,'organisation','CREATE_CHARGE','CHARGE','CREATE',0),(59,'organisation','CREATE_CHARGE_CHECKER','CHARGE','CREATE',0),(60,'organisation','UPDATE_CHARGE','CHARGE','UPDATE',0),(61,'organisation','UPDATE_CHARGE_CHECKER','CHARGE','UPDATE',0),(62,'organisation','DELETE_CHARGE','CHARGE','DELETE',0),(63,'organisation','DELETE_CHARGE_CHECKER','CHARGE','DELETE',0),(64,'organisation','READ_FUND','FUND','READ',0),(65,'organisation','CREATE_FUND','FUND','CREATE',0),(66,'organisation','CREATE_FUND_CHECKER','FUND','CREATE',0),(67,'organisation','UPDATE_FUND','FUND','UPDATE',0),(68,'organisation','UPDATE_FUND_CHECKER','FUND','UPDATE',0),(69,'organisation','DELETE_FUND','FUND','DELETE',0),(70,'organisation','DELETE_FUND_CHECKER','FUND','DELETE',0),(71,'organisation','READ_LOANPRODUCT','LOANPRODUCT','READ',0),(72,'organisation','CREATE_LOANPRODUCT','LOANPRODUCT','CREATE',0),(73,'organisation','CREATE_LOANPRODUCT_CHECKER','LOANPRODUCT','CREATE',0),(74,'organisation','UPDATE_LOANPRODUCT','LOANPRODUCT','UPDATE',0),(75,'organisation','UPDATE_LOANPRODUCT_CHECKER','LOANPRODUCT','UPDATE',0),(76,'organisation','DELETE_LOANPRODUCT','LOANPRODUCT','DELETE',0),(77,'organisation','DELETE_LOANPRODUCT_CHECKER','LOANPRODUCT','DELETE',0),(78,'organisation','READ_OFFICE','OFFICE','READ',0),(79,'organisation','CREATE_OFFICE','OFFICE','CREATE',0),(80,'organisation','CREATE_OFFICE_CHECKER','OFFICE','CREATE',0),(81,'organisation','UPDATE_OFFICE','OFFICE','UPDATE',0),(82,'organisation','UPDATE_OFFICE_CHECKER','OFFICE','UPDATE',0),(83,'organisation','READ_OFFICETRANSACTION','OFFICETRANSACTION','READ',0),(84,'organisation','DELETE_OFFICE_CHECKER','OFFICE','DELETE',0),(85,'organisation','CREATE_OFFICETRANSACTION','OFFICETRANSACTION','CREATE',0),(86,'organisation','CREATE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','CREATE',0),(87,'organisation','DELETE_OFFICETRANSACTION','OFFICETRANSACTION','DELETE',0),(88,'organisation','DELETE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','DELETE',0),(89,'organisation','READ_STAFF','STAFF','READ',0),(90,'organisation','CREATE_STAFF','STAFF','CREATE',0),(91,'organisation','CREATE_STAFF_CHECKER','STAFF','CREATE',0),(92,'organisation','UPDATE_STAFF','STAFF','UPDATE',0),(93,'organisation','UPDATE_STAFF_CHECKER','STAFF','UPDATE',0),(94,'organisation','DELETE_STAFF','STAFF','DELETE',0),(95,'organisation','DELETE_STAFF_CHECKER','STAFF','DELETE',0),(96,'organisation','READ_SAVINGSPRODUCT','SAVINGSPRODUCT','READ',0),(97,'organisation','CREATE_SAVINGSPRODUCT','SAVINGSPRODUCT','CREATE',0),(98,'organisation','CREATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','CREATE',0),(99,'organisation','UPDATE_SAVINGSPRODUCT','SAVINGSPRODUCT','UPDATE',0),(100,'organisation','UPDATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','UPDATE',0),(101,'organisation','DELETE_SAVINGSPRODUCT','SAVINGSPRODUCT','DELETE',0),(102,'organisation','DELETE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','DELETE',0),(103,'portfolio','READ_LOAN','LOAN','READ',0),(104,'portfolio','CREATE_LOAN','LOAN','CREATE',0),(105,'portfolio','CREATE_LOAN_CHECKER','LOAN','CREATE',0),(106,'portfolio','UPDATE_LOAN','LOAN','UPDATE',0),(107,'portfolio','UPDATE_LOAN_CHECKER','LOAN','UPDATE',0),(108,'portfolio','DELETE_LOAN','LOAN','DELETE',0),(109,'portfolio','DELETE_LOAN_CHECKER','LOAN','DELETE',0),(110,'portfolio','READ_CLIENT','CLIENT','READ',0),(111,'portfolio','CREATE_CLIENT','CLIENT','CREATE',0),(112,'portfolio','CREATE_CLIENT_CHECKER','CLIENT','CREATE',0),(113,'portfolio','UPDATE_CLIENT','CLIENT','UPDATE',0),(114,'portfolio','UPDATE_CLIENT_CHECKER','CLIENT','UPDATE',0),(115,'portfolio','DELETE_CLIENT','CLIENT','DELETE',0),(116,'portfolio','DELETE_CLIENT_CHECKER','CLIENT','DELETE',0),(117,'portfolio','READ_CLIENTIMAGE','CLIENTIMAGE','READ',0),(118,'portfolio','CREATE_CLIENTIMAGE','CLIENTIMAGE','CREATE',0),(119,'portfolio','CREATE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','CREATE',0),(120,'portfolio','DELETE_CLIENTIMAGE','CLIENTIMAGE','DELETE',0),(121,'portfolio','DELETE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','DELETE',0),(122,'portfolio','READ_CLIENTNOTE','CLIENTNOTE','READ',0),(123,'portfolio','CREATE_CLIENTNOTE','CLIENTNOTE','CREATE',0),(124,'portfolio','CREATE_CLIENTNOTE_CHECKER','CLIENTNOTE','CREATE',0),(125,'portfolio','UPDATE_CLIENTNOTE','CLIENTNOTE','UPDATE',0),(126,'portfolio','UPDATE_CLIENTNOTE_CHECKER','CLIENTNOTE','UPDATE',0),(127,'portfolio','DELETE_CLIENTNOTE','CLIENTNOTE','DELETE',0),(128,'portfolio','DELETE_CLIENTNOTE_CHECKER','CLIENTNOTE','DELETE',0),(129,'portfolio','READ_GROUPNOTE','GROUPNOTE','READ',0),(130,'portfolio','CREATE_GROUPNOTE','GROUPNOTE','CREATE',0),(131,'portfolio','UPDATE_GROUPNOTE','GROUPNOTE','UPDATE',0),(132,'portfolio','DELETE_GROUPNOTE','GROUPNOTE','DELETE',0),(133,'portfolio','CREATE_GROUPNOTE_CHECKER','GROUPNOTE','CREATE',0),(134,'portfolio','UPDATE_GROUPNOTE_CHECKER','GROUPNOTE','UPDATE',0),(135,'portfolio','DELETE_GROUPNOTE_CHECKER','GROUPNOTE','DELETE',0),(136,'portfolio','READ_LOANNOTE','LOANNOTE','READ',0),(137,'portfolio','CREATE_LOANNOTE','LOANNOTE','CREATE',0),(138,'portfolio','UPDATE_LOANNOTE','LOANNOTE','UPDATE',0),(139,'portfolio','DELETE_LOANNOTE','LOANNOTE','DELETE',0),(140,'portfolio','CREATE_LOANNOTE_CHECKER','LOANNOTE','CREATE',0),(141,'portfolio','UPDATE_LOANNOTE_CHECKER','LOANNOTE','UPDATE',0),(142,'portfolio','DELETE_LOANNOTE_CHECKER','LOANNOTE','DELETE',0),(143,'portfolio','READ_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','READ',0),(144,'portfolio','CREATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','CREATE',0),(145,'portfolio','UPDATE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','UPDATE',0),(146,'portfolio','DELETE_LOANTRANSACTIONNOTE','LOANTRANSACTIONNOTE','DELETE',0),(147,'portfolio','CREATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','CREATE',0),(148,'portfolio','UPDATE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','UPDATE',0),(149,'portfolio','DELETE_LOANTRANSACTIONNOTE_CHECKER','LOANTRANSACTIONNOTE','DELETE',0),(150,'portfolio','READ_SAVINGNOTE','SAVINGNOTE','READ',0),(151,'portfolio','CREATE_SAVINGNOTE','SAVINGNOTE','CREATE',0),(152,'portfolio','UPDATE_SAVINGNOTE','SAVINGNOTE','UPDATE',0),(153,'portfolio','DELETE_SAVINGNOTE','SAVINGNOTE','DELETE',0),(154,'portfolio','CREATE_SAVINGNOTE_CHECKER','SAVINGNOTE','CREATE',0),(155,'portfolio','UPDATE_SAVINGNOTE_CHECKER','SAVINGNOTE','UPDATE',0),(156,'portfolio','DELETE_SAVINGNOTE_CHECKER','SAVINGNOTE','DELETE',0),(157,'portfolio','READ_CLIENTIDENTIFIER','CLIENTIDENTIFIER','READ',0),(158,'portfolio','CREATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','CREATE',0),(159,'portfolio','CREATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','CREATE',0),(160,'portfolio','UPDATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','UPDATE',0),(161,'portfolio','UPDATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','UPDATE',0),(162,'portfolio','DELETE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','DELETE',0),(163,'portfolio','DELETE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','DELETE',0),(164,'portfolio','READ_DOCUMENT','DOCUMENT','READ',0),(165,'portfolio','CREATE_DOCUMENT','DOCUMENT','CREATE',0),(166,'portfolio','CREATE_DOCUMENT_CHECKER','DOCUMENT','CREATE',0),(167,'portfolio','UPDATE_DOCUMENT','DOCUMENT','UPDATE',0),(168,'portfolio','UPDATE_DOCUMENT_CHECKER','DOCUMENT','UPDATE',0),(169,'portfolio','DELETE_DOCUMENT','DOCUMENT','DELETE',0),(170,'portfolio','DELETE_DOCUMENT_CHECKER','DOCUMENT','DELETE',0),(171,'portfolio','READ_GROUP','GROUP','READ',0),(172,'portfolio','CREATE_GROUP','GROUP','CREATE',0),(173,'portfolio','CREATE_GROUP_CHECKER','GROUP','CREATE',0),(174,'portfolio','UPDATE_GROUP','GROUP','UPDATE',0),(175,'portfolio','UPDATE_GROUP_CHECKER','GROUP','UPDATE',0),(176,'portfolio','DELETE_GROUP','GROUP','DELETE',0),(177,'portfolio','DELETE_GROUP_CHECKER','GROUP','DELETE',0),(178,'portfolio','UNASSIGNSTAFF_GROUP','GROUP','UNASSIGNSTAFF',0),(179,'portfolio','UNASSIGNSTAFF_GROUP_CHECKER','GROUP','UNASSIGNSTAFF',0),(180,'portfolio','CREATE_LOANCHARGE','LOANCHARGE','CREATE',0),(181,'portfolio','CREATE_LOANCHARGE_CHECKER','LOANCHARGE','CREATE',0),(182,'portfolio','UPDATE_LOANCHARGE','LOANCHARGE','UPDATE',0),(183,'portfolio','UPDATE_LOANCHARGE_CHECKER','LOANCHARGE','UPDATE',0),(184,'portfolio','DELETE_LOANCHARGE','LOANCHARGE','DELETE',0),(185,'portfolio','DELETE_LOANCHARGE_CHECKER','LOANCHARGE','DELETE',0),(186,'portfolio','WAIVE_LOANCHARGE','LOANCHARGE','WAIVE',0),(187,'portfolio','WAIVE_LOANCHARGE_CHECKER','LOANCHARGE','WAIVE',0),(188,'portfolio','READ_SAVINGSACCOUNT','SAVINGSACCOUNT','READ',0),(189,'portfolio','CREATE_SAVINGSACCOUNT','SAVINGSACCOUNT','CREATE',0),(190,'portfolio','CREATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','CREATE',0),(191,'portfolio','UPDATE_SAVINGSACCOUNT','SAVINGSACCOUNT','UPDATE',0),(192,'portfolio','UPDATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','UPDATE',0),(193,'portfolio','DELETE_SAVINGSACCOUNT','SAVINGSACCOUNT','DELETE',0),(194,'portfolio','DELETE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DELETE',0),(195,'portfolio','READ_GUARANTOR','GUARANTOR','READ',0),(196,'portfolio','CREATE_GUARANTOR','GUARANTOR','CREATE',0),(197,'portfolio','CREATE_GUARANTOR_CHECKER','GUARANTOR','CREATE',0),(198,'portfolio','UPDATE_GUARANTOR','GUARANTOR','UPDATE',0),(199,'portfolio','UPDATE_GUARANTOR_CHECKER','GUARANTOR','UPDATE',0),(200,'portfolio','DELETE_GUARANTOR','GUARANTOR','DELETE',0),(201,'portfolio','DELETE_GUARANTOR_CHECKER','GUARANTOR','DELETE',0),(202,'transaction_loan','APPROVE_LOAN','LOAN','APPROVE',0),(203,'transaction_loan','APPROVEINPAST_LOAN','LOAN','APPROVEINPAST',0),(204,'transaction_loan','REJECT_LOAN','LOAN','REJECT',0),(205,'transaction_loan','REJECTINPAST_LOAN','LOAN','REJECTINPAST',0),(206,'transaction_loan','WITHDRAW_LOAN','LOAN','WITHDRAW',0),(207,'transaction_loan','WITHDRAWINPAST_LOAN','LOAN','WITHDRAWINPAST',0),(208,'transaction_loan','APPROVALUNDO_LOAN','LOAN','APPROVALUNDO',0),(209,'transaction_loan','DISBURSE_LOAN','LOAN','DISBURSE',0),(210,'transaction_loan','DISBURSEINPAST_LOAN','LOAN','DISBURSEINPAST',0),(211,'transaction_loan','DISBURSALUNDO_LOAN','LOAN','DISBURSALUNDO',0),(212,'transaction_loan','REPAYMENT_LOAN','LOAN','REPAYMENT',0),(213,'transaction_loan','REPAYMENTINPAST_LOAN','LOAN','REPAYMENTINPAST',0),(214,'transaction_loan','ADJUST_LOAN','LOAN','ADJUST',0),(215,'transaction_loan','WAIVEINTERESTPORTION_LOAN','LOAN','WAIVEINTERESTPORTION',0),(216,'transaction_loan','WRITEOFF_LOAN','LOAN','WRITEOFF',0),(217,'transaction_loan','CLOSE_LOAN','LOAN','CLOSE',0),(218,'transaction_loan','CLOSEASRESCHEDULED_LOAN','LOAN','CLOSEASRESCHEDULED',0),(219,'transaction_loan','UPDATELOANOFFICER_LOAN','LOAN','UPDATELOANOFFICER',0),(220,'transaction_loan','UPDATELOANOFFICER_LOAN_CHECKER','LOAN','UPDATELOANOFFICER',0),(221,'transaction_loan','REMOVELOANOFFICER_LOAN','LOAN','REMOVELOANOFFICER',0),(222,'transaction_loan','REMOVELOANOFFICER_LOAN_CHECKER','LOAN','REMOVELOANOFFICER',0),(223,'transaction_loan','BULKREASSIGN_LOAN','LOAN','BULKREASSIGN',0),(224,'transaction_loan','BULKREASSIGN_LOAN_CHECKER','LOAN','BULKREASSIGN',0),(225,'transaction_loan','APPROVE_LOAN_CHECKER','LOAN','APPROVE',0),(226,'transaction_loan','APPROVEINPAST_LOAN_CHECKER','LOAN','APPROVEINPAST',0),(227,'transaction_loan','REJECT_LOAN_CHECKER','LOAN','REJECT',0),(228,'transaction_loan','REJECTINPAST_LOAN_CHECKER','LOAN','REJECTINPAST',0),(229,'transaction_loan','WITHDRAW_LOAN_CHECKER','LOAN','WITHDRAW',0),(230,'transaction_loan','WITHDRAWINPAST_LOAN_CHECKER','LOAN','WITHDRAWINPAST',0),(231,'transaction_loan','APPROVALUNDO_LOAN_CHECKER','LOAN','APPROVALUNDO',0),(232,'transaction_loan','DISBURSE_LOAN_CHECKER','LOAN','DISBURSE',0),(233,'transaction_loan','DISBURSEINPAST_LOAN_CHECKER','LOAN','DISBURSEINPAST',0),(234,'transaction_loan','DISBURSALUNDO_LOAN_CHECKER','LOAN','DISBURSALUNDO',0),(235,'transaction_loan','REPAYMENT_LOAN_CHECKER','LOAN','REPAYMENT',0),(236,'transaction_loan','REPAYMENTINPAST_LOAN_CHECKER','LOAN','REPAYMENTINPAST',0),(237,'transaction_loan','ADJUST_LOAN_CHECKER','LOAN','ADJUST',0),(238,'transaction_loan','WAIVEINTERESTPORTION_LOAN_CHECKER','LOAN','WAIVEINTERESTPORTION',0),(239,'transaction_loan','WRITEOFF_LOAN_CHECKER','LOAN','WRITEOFF',0),(240,'transaction_loan','CLOSE_LOAN_CHECKER','LOAN','CLOSE',0),(241,'transaction_loan','CLOSEASRESCHEDULED_LOAN_CHECKER','LOAN','CLOSEASRESCHEDULED',0),(242,'transaction_savings','DEPOSIT_SAVINGSACCOUNT','SAVINGSACCOUNT','DEPOSIT',0),(243,'transaction_savings','DEPOSIT_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DEPOSIT',0),(244,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT','SAVINGSACCOUNT','WITHDRAWAL',0),(245,'transaction_savings','WITHDRAWAL_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','WITHDRAWAL',0),(246,'transaction_savings','ACTIVATE_SAVINGSACCOUNT','SAVINGSACCOUNT','ACTIVATE',0),(247,'transaction_savings','ACTIVATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','ACTIVATE',0),(248,'accounting','CREATE_GLACCOUNT','GLACCOUNT','CREATE',0),(249,'accounting','UPDATE_GLACCOUNT','GLACCOUNT','UPDATE',0),(250,'accounting','DELETE_GLACCOUNT','GLACCOUNT','DELETE',0),(251,'accounting','CREATE_GLCLOSURE','GLCLOSURE','CREATE',0),(252,'accounting','UPDATE_GLCLOSURE','GLCLOSURE','UPDATE',0),(253,'accounting','DELETE_GLCLOSURE','GLCLOSURE','DELETE',0),(254,'accounting','CREATE_JOURNALENTRY','JOURNALENTRY','CREATE',0),(255,'accounting','REVERSE_JOURNALENTRY','JOURNALENTRY','REVERSE',0),(256,'report','READ_Active Loans - Details','Active Loans - Details','READ',0),(257,'report','READ_Active Loans - Summary','Active Loans - Summary','READ',0),(258,'report','READ_Active Loans by Disbursal Period','Active Loans by Disbursal Period','READ',0),(259,'report','READ_Active Loans in last installment','Active Loans in last installment','READ',0),(260,'report','READ_Active Loans in last installment Summary','Active Loans in last installment Summary','READ',0),(261,'report','READ_Active Loans Passed Final Maturity','Active Loans Passed Final Maturity','READ',0),(262,'report','READ_Active Loans Passed Final Maturity Summary','Active Loans Passed Final Maturity Summary','READ',0),(263,'report','READ_Aging Detail','Aging Detail','READ',0),(264,'report','READ_Aging Summary (Arrears in Months)','Aging Summary (Arrears in Months)','READ',0),(265,'report','READ_Aging Summary (Arrears in Weeks)','Aging Summary (Arrears in Weeks)','READ',0),(266,'report','READ_Balance Sheet','Balance Sheet','READ',0),(267,'report','READ_Branch Expected Cash Flow','Branch Expected Cash Flow','READ',0),(268,'report','READ_Client Listing','Client Listing','READ',0),(269,'report','READ_Client Loans Listing','Client Loans Listing','READ',0),(270,'report','READ_Expected Payments By Date - Basic','Expected Payments By Date - Basic','READ',0),(271,'report','READ_Expected Payments By Date - Formatted','Expected Payments By Date - Formatted','READ',0),(272,'report','READ_Funds Disbursed Between Dates Summary','Funds Disbursed Between Dates Summary','READ',0),(273,'report','READ_Funds Disbursed Between Dates Summary by Office','Funds Disbursed Between Dates Summary by Office','READ',0),(274,'report','READ_Income Statement','Income Statement','READ',0),(275,'report','READ_Loan Account Schedule','Loan Account Schedule','READ',0),(276,'report','READ_Loans Awaiting Disbursal','Loans Awaiting Disbursal','READ',0),(277,'report','READ_Loans Awaiting Disbursal Summary','Loans Awaiting Disbursal Summary','READ',0),(278,'report','READ_Loans Awaiting Disbursal Summary by Month','Loans Awaiting Disbursal Summary by Month','READ',0),(279,'report','READ_Loans Pending Approval','Loans Pending Approval','READ',0),(280,'report','READ_Obligation Met Loans Details','Obligation Met Loans Details','READ',0),(281,'report','READ_Obligation Met Loans Summary','Obligation Met Loans Summary','READ',0),(282,'report','READ_Portfolio at Risk','Portfolio at Risk','READ',0),(283,'report','READ_Portfolio at Risk by Branch','Portfolio at Risk by Branch','READ',0),(284,'report','READ_Rescheduled Loans','Rescheduled Loans','READ',0),(285,'report','READ_Trial Balance','Trial Balance','READ',0),(286,'report','READ_Written-Off Loans','Written-Off Loans','READ',0),(287,'datatable','CREATE_extra_client_details','extra_client_details','CREATE',1),(288,'datatable','CREATE_extra_family_details','extra_family_details','CREATE',1),(289,'datatable','CREATE_extra_loan_details','extra_loan_details','CREATE',1),(290,'datatable','READ_extra_client_details','extra_client_details','READ',1),(291,'datatable','READ_extra_family_details','extra_family_details','READ',1),(292,'datatable','READ_extra_loan_details','extra_loan_details','READ',1),(293,'datatable','UPDATE_extra_client_details','extra_client_details','UPDATE',1),(294,'datatable','UPDATE_extra_family_details','extra_family_details','UPDATE',1),(295,'datatable','UPDATE_extra_loan_details','extra_loan_details','UPDATE',1),(296,'datatable','DELETE_extra_client_details','extra_client_details','DELETE',1),(297,'datatable','DELETE_extra_family_details','extra_family_details','DELETE',1),(298,'datatable','DELETE_extra_loan_details','extra_loan_details','DELETE',1);
+/*!40000 ALTER TABLE `m_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_portfolio_command_source`
+--
+
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `subresource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_portfolio_command_source`
+--
+
+LOCK TABLES `m_portfolio_command_source` WRITE;
+/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_portfolio_command_source` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan`
+--
+
+DROP TABLE IF EXISTS `m_product_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan`
+--
+
+LOCK TABLES `m_product_loan` WRITE;
+/*!40000 ALTER TABLE `m_product_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan_charge`
+--
+
+LOCK TABLES `m_product_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_product_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role`
+--
+
+DROP TABLE IF EXISTS `m_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role`
+--
+
+LOCK TABLES `m_role` WRITE;
+/*!40000 ALTER TABLE `m_role` DISABLE KEYS */;
+INSERT INTO `m_role` VALUES (1,'Super user','This role provides all application permissions.');
+/*!40000 ALTER TABLE `m_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role_permission`
+--
+
+DROP TABLE IF EXISTS `m_role_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role_permission`
+--
+
+LOCK TABLES `m_role_permission` WRITE;
+/*!40000 ALTER TABLE `m_role_permission` DISABLE KEYS */;
+INSERT INTO `m_role_permission` VALUES (1,1);
+/*!40000 ALTER TABLE `m_role_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account`
+--
+
+DROP TABLE IF EXISTS `m_savings_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `status_enum` smallint(5) NOT NULL DEFAULT '300',
+  `activation_date` date DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `lockedin_until_date_derived` date DEFAULT NULL,
+  `total_deposits_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
+  `account_balance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sa_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `sa_externalid_UNIQUE` (`external_id`),
+  KEY `FKSA00000000000001` (`client_id`),
+  KEY `FKSA00000000000002` (`group_id`),
+  KEY `FKSA00000000000003` (`product_id`),
+  CONSTRAINT `FKSA00000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA00000000000002` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSA00000000000003` FOREIGN KEY (`product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account`
+--
+
+LOCK TABLES `m_savings_account` WRITE;
+/*!40000 ALTER TABLE `m_savings_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_account_transaction`
+--
+
+DROP TABLE IF EXISTS `m_savings_account_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_account_transaction`
+--
+
+LOCK TABLES `m_savings_account_transaction` WRITE;
+/*!40000 ALTER TABLE `m_savings_account_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_savings_product`
+--
+
+DROP TABLE IF EXISTS `m_savings_product`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_savings_product` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sp_unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_savings_product`
+--
+
+LOCK TABLES `m_savings_product` WRITE;
+/*!40000 ALTER TABLE `m_savings_product` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_product` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_staff`
+--
+
+DROP TABLE IF EXISTS `m_staff`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_staff`
+--
+
+LOCK TABLES `m_staff` WRITE;
+/*!40000 ALTER TABLE `m_staff` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_staff` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `r_enum_value`
+--
+
+DROP TABLE IF EXISTS `r_enum_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `r_enum_value`
+--
+
+LOCK TABLES `r_enum_value` WRITE;
+/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
+INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
+/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `ref_loan_transaction_processing_strategy`
+--
+
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `ref_loan_transaction_processing_strategy`
+--
+
+LOCK TABLES `ref_loan_transaction_processing_strategy` WRITE;
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` DISABLE KEYS */;
+INSERT INTO `ref_loan_transaction_processing_strategy` VALUES (1,'mifos-standard-strategy','Mifos style',NULL,NULL,NULL,NULL),(2,'heavensfamily-strategy','Heavensfamily',NULL,NULL,NULL,NULL),(3,'creocore-strategy','Creocore',NULL,NULL,NULL,NULL),(4,'rbi-india-strategy','RBI (India)',NULL,NULL,NULL,NULL);
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `rpt_sequence`
+--
+
+DROP TABLE IF EXISTS `rpt_sequence`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `rpt_sequence`
+--
+
+LOCK TABLES `rpt_sequence` WRITE;
+/*!40000 ALTER TABLE `rpt_sequence` DISABLE KEYS */;
+/*!40000 ALTER TABLE `rpt_sequence` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_parameter` (
+  `parameter_id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_parameter_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`parameter_id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`),
+  KEY `fk_stretchy_parameter_0001_idx` (`parent_parameter_id`),
+  CONSTRAINT `fk_stretchy_parameter_0001` FOREIGN KEY (`parent_parameter_id`) REFERENCES `stretchy_parameter` (`parameter_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_parameter`
+--
+
+LOCK TABLES `stretchy_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_parameter` VALUES (1,'startDateSelect','startDate','startDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(2,'endDateSelect','endDate','endDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(3,'obligDateTypeSelect','obligDateType','obligDateType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Closed\" as `name` union all\r\nselect 2, \"Disbursal\" ) x\r\norder by x.`id`',NULL),(5,'OfficeIdSelectOne','officeId','Office','select','number','0',NULL,'Y',NULL,'select id, \r\nconcat(substring(\"........................................\", 1, \r\n   \n\n((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat\n\n(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy',NULL),(6,'loanOfficerIdSelectAll','loanOfficerId','Loan Officer','select','number','0',NULL,NULL,'Y','(select lo.id, lo.display_name as `Name` \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\njoin m_staff lo on lo.office_id = ounder.id\r\nwhere lo.is_loan_officer = true\r\nand o.id = ${officeId})\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',5),(10,'currencyIdSelectAll','currencyId','Currency','select','number','0',NULL,NULL,'Y','select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`',NULL),(20,'fundIdSelectAll','fundId','Fund','select','number','0',NULL,NULL,'Y','(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',NULL),(25,'loanProductIdSelectAll','loanProductId','Product','select','number','0',NULL,NULL,'Y','select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere p.currency_code = \'${currencyId}\'\r\norder by 2',10),(26,'loanPurposeIdSelectAll','loanPurposeId','Loan Purpose','select','number','0',NULL,NULL,'Y','select -10 as id, \'-\' as code_value\r\nunion all\r\nselect * from (select v.id, v.code_value\r\nfrom m_code c\r\njoin m_code_value v on v.code_id = c.id\r\nwhere c.code_name = \"loanPurpose\"\r\norder by v.order_position)  x',NULL),(100,'parTypeSelect','parType','parType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Principal Only\" as `name` union all\r\nselect 2, \"Principal + Interest\" union all\r\nselect 3, \"Principal + Interest + Fees\" union all\r\nselect 4, \"Principal + Interest + Fees + Penalties\") x\r\norder by x.`id`',NULL),(1001,'FullReportList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\r\n  \n\nrp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on rp.report_id = r.report_id\r\n  \n\nleft join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \n\n\r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on rp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where \n\nur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by \n\nr.report_category, r.report_name, rp.parameter_id',NULL),(1002,'FullParameterList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r\nsp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r\nfrom stretchy_parameter sp\r\nleft join stretchy_parameter spp on spp.parameter_id = sp.parent_parameter_id\r\nwhere sp.special is null\r\nand exists \r\n	(select \'f\' \r\n	from stretchy_report sr\r\n	join stretchy_report_parameter srp on srp.report_id = sr.report_id\r\n	where sr.report_name in(${reportListing})\r\n	and srp.parameter_id = sp.parameter_id\r\n	)\r\norder by sp.parameter_id',NULL),(1003,'reportCategoryList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, \n\nr.report_category,\r\n  rp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on \n\nrp.report_id = r.report_id\r\n  left join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.report_category = \'${reportCategory}\'\r\n  and \n\nr.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on \n\nrp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where ur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \n\n\'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by r.report_category, r.report_name, rp.parameter_id',NULL);
+/*!40000 ALTER TABLE `stretchy_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report`
+--
+
+DROP TABLE IF EXISTS `stretchy_report`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report` (
+  `report_id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`report_id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report`
+--
+
+LOCK TABLES `stretchy_report` WRITE;
+/*!40000 ALTER TABLE `stretchy_report` DISABLE KEYS */;
+INSERT INTO `stretchy_report` VALUES (1,'Client Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n c.account_no as \"Client Account No.\",  \r\nc.display_name as \"Name\",  \n\nc.joined_date as \"Joined\", c.external_id as \"External Id\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand \n\nounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\nwhere o.id = ${officeId}\r\nand c.is_deleted=0\r\n\n\norder by ounder.hierarchy, c.account_no','Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t \n\nhave that client browser/memory impact).',1,1),(2,'Client Loans Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client \n\nAccount No.\", \r\nc.display_name as \"Name\", \r\nlo.display_name as \"Loan Officer\", l.account_no as \"Loan Account No.\", l.external_id as \"External Id\", \r\n\n\np.name as Loan, st.enum_message_property as \"Status\",  \r\nf.`name` as Fund, purp.code_value as \"Loan Purpose\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount,\n\n\r\nl.arrearstolerance_amount as \"Arrears Tolerance Amount\",\r\nl.number_of_repayments as \"Expected No. Repayments\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\nl.nominal_interest_rate_per_period as \"Nominal Interest Rate Per Period\",\r\n\r\nipf.enum_message_property as \"Interest Rate Frequency\n\n\",\r\nim.enum_message_property as \"Interest Method\",\r\nicp.enum_message_property as \"Interest Calculated in Period\",\r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.repay_every as \"Repayment Frequency\",\r\nrf.enum_message_property as \"Repayment Frequency Period\",\n\n\r\nam.enum_message_property as \"Amortization\",\r\n\r\nl.total_charges_due_at_disbursement_derived as \"Total Charges Due At Disbursement\",\r\n\r\ndate( \n\nl.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As \"Expected Disbursal\",\r\ndate(l.expected_firstrepaymenton_date) as \n\n\"Expected First Repayment\", date(l.interest_calculated_from_date) as \"Interest Calculated From\" ,\r\ndate(l.disbursedon_date) as Disbursed, date\n\n(l.expected_maturedon_date) \"Expected Maturity\",\r\ndate(l.maturedon_date) as \"Matured On\", date(l.closedon_date) as Closed,\r\ndate(l.rejectedon_date) as \n\nRejected, date(l.rescheduledon_date) as Rescheduled, \r\ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) \"Written Off\"\r\nfrom m_office o \r\njoin \n\nm_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on \n\nc.office_id = ounder.id\r\nleft join m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_product_loan p on p.id = \n\nl.product_id\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join r_enum_value st on st.enum_name = \"loan_status_id\" and st.enum_id = l.loan_status_id\r\nleft join \n\nr_enum_value ipf on ipf.enum_name = \"interest_period_frequency_enum\" and ipf.enum_id = l.interest_period_frequency_enum\r\nleft join r_enum_value im on im.enum_name \n\n= \"interest_method_enum\" and im.enum_id = l.interest_method_enum\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = \n\nl.term_period_frequency_enum\r\nleft join r_enum_value icp on icp.enum_name = \"interest_calculated_in_period_enum\" and icp.enum_id = \n\nl.interest_calculated_in_period_enum\r\nleft join r_enum_value rf on rf.enum_name = \"repayment_period_frequency_enum\" and rf.enum_id = \n\nl.repayment_period_frequency_enum\r\nleft join r_enum_value am on am.enum_name = \"amortization_method_enum\" and am.enum_id = l.amortization_method_enum\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\n\r\nleft \n\njoin m_currency cur on cur.code = l.currency_code\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand \n\n(l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \n\n\"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\norder by ounder.hierarchy, 2 , l.id','Individual Client Report\r\n\r\nPretty \n\nwide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).',1,1),(5,'Loans Awaiting Disbursal','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No\", c.display_name as \"Name\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund, ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\",\r\ndate(l.approvedon_date) \"Approved\",\r\ndatediff(l.expected_disbursedon_date, curdate()) as \"Days to Disbursal\",\r\ndate(l.expected_disbursedon_date) \"Expected Disbursal\",\r\npurp.code_value as \"Loan Purpose\",\r\n lo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, datediff(l.expected_disbursedon_date, curdate()),  c.account_no','Individual Client Report',1,1),(6,'Loans Awaiting Disbursal Summary','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`','Individual Client Report',1,1),(7,'Loans Awaiting Disbursal Summary by Month','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as \"Year\", \r\nmonthname(l.expected_disbursedon_date) as \"Month\",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)','Individual Client Report',1,1),(8,'Loans Pending Approval','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client Name\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  pl.`name` as \"Product\", \r\nl.account_no as \"Loan Account No.\", \r\nl.principal_amount as \"Loan Amount\", \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\ndatediff(curdate(), l.submittedon_date) \"Days Pending Approval\", \r\npurp.code_value as \"Loan Purpose\",\r\nlo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 100 /*Submitted and awaiting approval */\r\norder by ounder.hierarchy, l.submittedon_date,  l.account_no','Individual Client Report',1,1),(11,'Active Loans - Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. loans_in_arrears_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(if(laa.loan_id is not null,  l.id, null)  )) as loans_in_arrears_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(laa.principal_overdue_derived) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(laa.interest_overdue_derived) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(laa.fee_charges_overdue_derived) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(laa.penalty_charges_overdue_derived) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(12,'Active Loans - Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\npenalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(13,'Obligation Met Loans Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client\",\r\nl.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.total_repayment_derived  as \"Total Repaid\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", \r\ndate(l.closedon_date) as \"Closed\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(14,'Obligation Met Loans Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as \"No. of Clients\",\r\ncount(distinct(l.id)) as \"No. of Loans\",\r\nsum(l.principal_amount) as \"Total Loan Amount\", \r\nsum(l.principal_repaid_derived) as \"Total Principal Repaid\",\r\nsum(l.interest_repaid_derived) as \"Total Interest Repaid\",\r\nsum(l.fee_charges_repaid_derived) as \"Total Fees Repaid\",\r\nsum(l.penalty_charges_repaid_derived) as \"Total Penalties Repaid\",\r\nsum(l.interest_waived_derived) as \"Total Interest Waived\",\r\nsum(l.fee_charges_waived_derived) as \"Total Fees Waived\",\r\nsum(l.penalty_charges_waived_derived) as \"Total Penalties Waived\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code','Individual Client \n\nReport',1,1),(15,'Portfolio at Risk','Table',NULL,'Loan','select x.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from \r\n(select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code) x','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(16,'Portfolio at Risk by Branch','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\",\r\nx.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select  ounder.id as \"branch\", ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\n\r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(20,'Funds Disbursed Between Dates Summary','Table',NULL,'Fund','select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, \r\nround(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(21,'Funds Disbursed Between Dates Summary by Office','Table',NULL,'Fund','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n \n\nifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\n\n\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c \n\non c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\n\n\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand \n\n(l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, \n\nl.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(48,'Balance Sheet','Pentaho',NULL,'Accounting',NULL,'Balance Sheet',1,1),(49,'Income Statement','Pentaho',NULL,'Accounting',NULL,'Profit and Loss Statement',1,1),(50,'Trial Balance','Pentaho',NULL,'Accounting',NULL,'Trial Balance Report',1,1),(51,'Written-Off Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Written-Off Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Written-Off Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Written-Off Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Written-Off Penalties\',\r\nn.note AS \'Reason For Write-Off\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 6 /*write-off */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=601\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\") \r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Written Off Loans',1,1),(52,'Aging Detail','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as \"Client Account No.\",\r\n 	mc.display_name AS \"Client Name\",\r\n 	ml.account_no AS \"Account Number\",\r\n 	ml.principal_amount AS \"Loan Amount\",\r\n ml.principal_disbursed_derived AS \"Original Principal\",\r\n ml.interest_charged_derived AS \"Original Interest\",\r\n ml.principal_repaid_derived AS \"Principal Paid\",\r\n ml.interest_repaid_derived AS \"Interest Paid\",\r\n laa.principal_overdue_derived AS \"Principal Overdue\",\r\n laa.interest_overdue_derived AS \"Interest Overdue\",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as \"Days in Arrears\",\r\n\r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS \"Weeks In Arrears Band\",\r\n\r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n				 \'> 360\'))))) AS \"Days in Arrears Band\"\r\n\r\n	FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n	        AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n	    INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n	    INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n	WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Loan arrears aging (Weeks)',1,1),(53,'Aging Summary (Arrears in Weeks)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Weeks In Arrears (Up To)\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'1\',2 UNION\r\n		SELECT \'2\',3 UNION\r\n		SELECT \'3\',4 UNION\r\n		SELECT \'4\',5 UNION\r\n		SELECT \'5\',6 UNION\r\n		SELECT \'6\',7 UNION\r\n		SELECT \'7\',8 UNION\r\n		SELECT \'8\',9 UNION\r\n		SELECT \'9\',10 UNION\r\n		SELECT \'10\',11 UNION\r\n		SELECT \'11\',12 UNION\r\n		SELECT \'12\',13 UNION\r\n		SELECT \'12+\',14) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<8, \'1\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<15, \'2\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<22, \'3\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<29, \'4\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<36, \'5\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<43, \'6\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<50, \'7\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<57, \'8\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<64, \'9\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<71, \'10\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<78, \'11\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<85, \'12\',\r\n				 \'12+\'))))))))))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(54,'Rescheduled Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Rescheduled Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Rescheduled Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Rescheduled Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Rescheduled Penalties\',\r\nn.note AS \'Reason For Rescheduling\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 7 /*marked for rescheduling */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=602\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Rescheduled Loans.  The ability to reschedule (or mark that you have rescheduled the loan elsewhere) is a legacy of the older Mifos product.  Needed for migration.',1,1),(55,'Active Loans Passed Final Maturity','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(56,'Active Loans Passed Final Maturity Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(57,'Active Loans in last installment','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(lastInstallment.`hierarchy`) - LENGTH(REPLACE(lastInstallment.`hierarchy`, \'.\', \'\')) - 1))), lastInstallment.branch) as \"Office/Branch\",\r\nlastInstallment.Currency,\r\nlastInstallment.`Loan Officer`, \r\nlastInstallment.`Client Account No`, lastInstallment.`Client`, \r\nlastInstallment.`Loan Account No`, lastInstallment.`Product`, \r\nlastInstallment.`Fund`,  lastInstallment.`Loan Amount`, \r\nlastInstallment.`Annual Nominal Interest Rate`, \r\nlastInstallment.`Disbursed`, lastInstallment.`Expected Matured On` ,\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\norder by lastInstallment.hierarchy, lastInstallment.Currency, lastInstallment.`Client Account No`, lastInstallment.`Loan Account No`','Individual Client \n\nReport',1,1),(58,'Active Loans in last installment Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select lastInstallment.branchId as branchId,\r\nlastInstallment.Currency,\r\ncount(distinct(lastInstallment.clientId)) as client_count, \r\ncount(distinct(lastInstallment.loanId)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id as branchId, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.id as clientId, c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\ngroup by lastInstallment.branchId) x on x.branchId = mo.id\r\norder by mo.hierarchy, x.Currency','Individual Client \n\nReport',1,1),(59,'Active Loans by Disbursal Period','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No\", c.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Principal Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\n\r\nl.total_expected_repayment_derived as \"Total Loan (P+I+F+Pen)\",\r\nl.total_repayment_derived as \"Total Repaid (P+I+F+Pen)\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(61,'Aging Summary (Arrears in Months)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Days In Arrears\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'0 - 30\',2 UNION\r\n		SELECT \'30 - 60\',3 UNION\r\n		SELECT \'60 - 90\',4 UNION\r\n		SELECT \'90 - 180\',5 UNION\r\n		SELECT \'180 - 360\',6 UNION\r\n		SELECT \'> 360\',7 ) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<361, \'180 - 360\', \r\n				 \'> 360\')))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(91,'Loan Account Schedule','Pentaho',NULL,'Loan',NULL,NULL,1,0),(92,'Branch Expected Cash Flow','Pentaho',NULL,'Loan',NULL,NULL,1,1),(93,'Expected Payments By Date - Basic','Table',NULL,'Loan','SELECT \r\n      ounder.name \'Office\', \r\n      IFNULL(ms.display_name,\'-\') \'Loan Officer\',\r\n	  mc.account_no \'Client Account Number\',\r\n	  mc.display_name \'Name\',\r\n	  mp.name \'Product\',\r\n	  ml.account_no \'Loan Account Number\',\r\n	  mr.duedate \'Due Date\',\r\n	  mr.installment \'Installment\',\r\n	  cu.display_symbol \'Currency\',\r\n	  mr.principal_amount- IFNULL(mr.principal_completed_derived,0) \'Principal Due\',\r\n	  mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0) \'Interest Due\', \r\n	  IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0) \'Fees Due\', \r\n	  IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0) \'Penalty Due\',\r\n      (mr.principal_amount- IFNULL(mr.principal_completed_derived,0)) +\r\n       (mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0)) + \r\n       (IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0)) + \r\n       (IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0)) \'Total Due\', \r\n     mlaa.total_overdue_derived \'Total Overdue\'\r\n										 \r\n FROM m_office mo\r\n  JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\n  \r\n  AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n	\r\n  LEFT JOIN m_client mc ON mc.office_id=ounder.id\r\n  LEFT JOIN m_loan ml ON ml.client_id=mc.id AND ml.loan_status_id=300\r\n  LEFT JOIN m_loan_arrears_aging mlaa ON mlaa.loan_id=ml.id\r\n  LEFT JOIN m_loan_repayment_schedule mr ON mr.loan_id=ml.id AND mr.completed_derived=0\r\n  LEFT JOIN m_product_loan mp ON mp.id=ml.product_id\r\n  LEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\n  LEFT JOIN m_currency cu ON cu.code=ml.currency_code\r\n WHERE mo.id=${officeId}\r\n AND (IFNULL(ml.loan_officer_id, -10) = \"${loanOfficerId}\" OR \"-1\" = \"${loanOfficerId}\")\r\n AND mr.duedate BETWEEN \'${startDate}\' AND \'${endDate}\'\r\n ORDER BY ounder.id,mr.duedate,ml.account_no','Test',1,1),(94,'Expected Payments By Date - Formatted','Pentaho',NULL,'Loan',NULL,NULL,1,1);
+/*!40000 ALTER TABLE `stretchy_report` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report_parameter` (
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`report_id`,`parameter_id`),
+  UNIQUE KEY `report_id_name_UNIQUE` (`report_id`,`report_parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report_parameter`
+--
+
+LOCK TABLES `stretchy_report_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_report_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_report_parameter` VALUES (1,5,NULL),(2,5,NULL),(2,6,NULL),(2,10,NULL),(2,20,NULL),(2,25,NULL),(2,26,NULL),(5,5,NULL),(5,6,NULL),(5,10,NULL),(5,20,NULL),(5,25,NULL),(5,26,NULL),(6,5,NULL),(6,6,NULL),(6,10,NULL),(6,20,NULL),(6,25,NULL),(6,26,NULL),(7,5,NULL),(7,6,NULL),(7,10,NULL),(7,20,NULL),(7,25,NULL),(7,26,NULL),(8,5,NULL),(8,6,NULL),(8,10,NULL),(8,25,NULL),(8,26,NULL),(11,5,NULL),(11,6,NULL),(11,10,NULL),(11,20,NULL),(11,25,NULL),(11,26,NULL),(11,100,NULL),(12,5,NULL),(12,6,NULL),(12,10,NULL),(12,20,NULL),(12,25,NULL),(12,26,NULL),(13,1,NULL),(13,2,NULL),(13,3,NULL),(13,5,NULL),(13,6,NULL),(13,10,NULL),(13,20,NULL),(13,25,NULL),(13,26,NULL),(14,1,NULL),(14,2,NULL),(14,3,NULL),(14,5,NULL),(14,6,NULL),(14,10,NULL),(14,20,NULL),(14,25,NULL),(14,26,NULL),(15,5,NULL),(15,6,NULL),(15,10,NULL),(15,20,NULL),(15,25,NULL),(15,26,NULL),(15,100,NULL),(16,5,NULL),(16,6,NULL),(16,10,NULL),(16,20,NULL),(16,25,NULL),(16,26,NULL),(16,100,NULL),(20,1,NULL),(20,2,NULL),(20,10,NULL),(20,20,NULL),(21,1,NULL),(21,2,NULL),(21,5,NULL),(21,10,NULL),(21,20,NULL),(48,5,'branch'),(48,2,'date'),(49,5,'branch'),(49,1,'fromDate'),(49,2,'toDate'),(50,5,'branch'),(50,1,'fromDate'),(50,2,'toDate'),(51,1,NULL),(51,2,NULL),(51,5,NULL),(51,10,NULL),(51,25,NULL),(52,5,NULL),(53,5,NULL),(53,10,NULL),(54,1,NULL),(54,2,NULL),(54,5,NULL),(54,10,NULL),(54,25,NULL),(55,5,NULL),(55,6,NULL),(55,10,NULL),(55,20,NULL),(55,25,NULL),(55,26,NULL),(56,5,NULL),(56,6,NULL),(56,10,NULL),(56,20,NULL),(56,25,NULL),(56,26,NULL),(56,100,NULL),(57,5,NULL),(57,6,NULL),(57,10,NULL),(57,20,NULL),(57,25,NULL),(57,26,NULL),(58,5,NULL),(58,6,NULL),(58,10,NULL),(58,20,NULL),(58,25,NULL),(58,26,NULL),(58,100,NULL),(59,1,NULL),(59,2,NULL),(59,5,NULL),(59,6,NULL),(59,10,NULL),(59,20,NULL),(59,25,NULL),(59,26,NULL),(61,5,NULL),(61,10,NULL),(92,1,'fromDate'),(92,5,'selectOffice'),(92,2,'toDate'),(93,1,NULL),(93,2,NULL),(93,5,NULL),(93,6,NULL),(94,2,'endDate'),(94,6,'loanOfficerId'),(94,5,'officeId'),(94,1,'startDate');
+/*!40000 ALTER TABLE `stretchy_report_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `x_registered_table`
+--
+
+DROP TABLE IF EXISTS `x_registered_table`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `x_registered_table`
+--
+
+LOCK TABLES `x_registered_table` WRITE;
+/*!40000 ALTER TABLE `x_registered_table` DISABLE KEYS */;
+INSERT INTO `x_registered_table` VALUES ('extra_client_details','m_client'),('extra_family_details','m_client'),('extra_loan_details','m_loan');
+/*!40000 ALTER TABLE `x_registered_table` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-03-25 18:17:29
diff --git a/fineract-db/multi-tenant-demo-backups/default-demo/extra-datatables-and-code-values.sql b/fineract-db/multi-tenant-demo-backups/default-demo/extra-datatables-and-code-values.sql
new file mode 100644
index 0000000..7c13cb3
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/default-demo/extra-datatables-and-code-values.sql
@@ -0,0 +1,221 @@
+-- add code and code values for datatables dropdowns
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Male', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "Gender";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Female', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "Gender";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.Yes', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "YesNo";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'option.No', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "YesNo";
+
+INSERT INTO `m_code`
+(`code_name`, `is_system_defined`) 
+VALUES 
+('Education',1);
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Primary', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "Education";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Secondary', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "Education";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'University', ifnull(max(mv.id), 1)
+from m_code mc
+join m_code_value mv on mv.code_id = mc.id
+where mc.`code_name` = "Education";
+
+-- ========= datatables=======
+
+DROP TABLE IF EXISTS `extra_client_details`;
+CREATE TABLE `extra_client_details` (
+  `client_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_extra_client_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `extra_family_details`;
+CREATE TABLE `extra_family_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `Name` varchar(40) DEFAULT NULL,
+  `Date of Birth` date DEFAULT NULL,
+  `Points Score` int(11) DEFAULT NULL,
+  `Education_cd_Highest` int(11) DEFAULT NULL,
+  `Other Notes` text,
+  PRIMARY KEY (`id`),
+  KEY `FK_Extra Family Details Data_1` (`client_id`),
+  CONSTRAINT `FK_family_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `extra_loan_details`;
+CREATE TABLE `extra_loan_details` (
+  `loan_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_extra_loan_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- datatables mapping
+INSERT INTO `x_registered_table`
+(`registered_table_name`,`application_table_name`)
+VALUES
+('extra_client_details', 'm_client'),
+('extra_family_details', 'm_client'),
+('extra_loan_details', 'm_loan');
+
+
+-- make sure permissions created for registered datatables
+/* add a create, read, update and delete permission for each registered datatable */
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('CREATE_', r.registered_table_name), r.registered_table_name, 'CREATE'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('READ_', r.registered_table_name), r.registered_table_name, 'READ'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('UPDATE_', r.registered_table_name), r.registered_table_name, 'UPDATE'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('DELETE_', r.registered_table_name), r.registered_table_name, 'DELETE'
+from x_registered_table r;
+
+
+
+-- ==== Chart of Accounts =====
+truncate `acc_gl_account`;
+INSERT INTO `acc_gl_account` VALUES 
+(1,'Petty Cash Balances',NULL,'11100',0,1,2,1,NULL),
+(2,'Cash in Valut 1',NULL,'11101',0,1,1,1,NULL),
+(3,'Bank Balances',NULL,'11200',0,1,2,1,NULL),
+(4,'Centenary Opening Account',NULL,'11201',0,1,1,1,NULL),
+(5,'Centenary Expense Account',NULL,'11202',0,1,1,1,NULL),
+(6,'Centenary USD Account',NULL,'11203',0,1,1,1,NULL),
+(7,'Loans and Advances',NULL,'13100',0,1,2,1,NULL),
+(8,'Loans to Clients',NULL,'13101',0,1,1,1,NULL),
+(9,'Outstanding Interest',NULL,'13102',0,1,1,1,NULL),
+(10,'Outstanding Late Payment Interest',NULL,'13103',0,1,1,1,NULL),
+(11,'Outstanding Bank Fees to be collected',NULL,'13104',0,1,1,1,NULL),
+(12,'WriteOff Accounts',NULL,'13200',0,1,2,1,NULL),
+(13,'Write-offs (use for funds coming in)',NULL,'13201',0,1,1,1,NULL),
+(14,'Write-offs outstanding principal',NULL,'13202',0,1,1,1,NULL),
+(15,'Write-offs outstanding interest',NULL,'13203',0,1,1,1,NULL),
+(16,'Write-offs collected bank fees',NULL,'13204',0,1,1,1,NULL),
+(17,'Write-offs hardware/furniture',NULL,'13205',0,1,1,1,NULL),
+(18,'Fixed Assets',NULL,'14100',0,1,2,1,NULL),
+(19,'Office Equipment',NULL,'14101',0,1,1,1,NULL),
+(20,'Suspense Items (unidentified deposits)',NULL,'15000',0,1,2,1,NULL),
+(21,'Assets',NULL,'10000',0,1,2,1,NULL),
+(22,'Liabilities',NULL,'20000',0,1,2,2,NULL),
+(23,'Shares Account',NULL,'26100',0,1,2,2,NULL),
+(24,'Shares Captial',NULL,'26101',0,1,1,2,NULL),
+(25,'Donated Equity',NULL,'26300',0,1,2,2,NULL),
+(26,'Donated Equity Ameropa Foundation',NULL,'26301',0,1,1,2,NULL),
+(27,'Donated Equity e.h',NULL,'26302',0,1,1,2,NULL),
+(28,'Overpaid Amounts',NULL,'27000',0,1,2,2,NULL),
+(29,'Loss Provision',NULL,'28000',0,1,2,2,NULL),
+(30,'Provision Outstanding Principal',NULL,'28001',0,1,1,2,NULL),
+(31,'Provision Oustanding Interest',NULL,'28002',0,1,1,2,NULL),(32,'Income',NULL,'30000',0,1,2,4,NULL),
+(33,'Interest Income from Loans',NULL,'31100',0,1,2,4,NULL),
+(34,'Interest on Loans',NULL,'31101',0,1,1,4,NULL),
+(35,'Late Payment Interest',NULL,'31102',0,1,1,4,NULL),
+(36,'Income from Micro credit & Lending Activities',NULL,'31300',0,1,2,4,NULL),
+(37,'Collected Bank Fees Receivable',NULL,'6201',0,1,1,4,NULL),
+(38,'Deposits from Loans Write Off',NULL,'31400',0,1,2,4,NULL),
+(39,'Expenditure',NULL,'40000',0,1,2,5,NULL),
+(40,'Office Expenditure Account',NULL,'42100',0,1,2,5,NULL),
+(41,'Water Charges',NULL,'42102',0,1,1,5,NULL),
+(42,'Electricity Charges',NULL,'42103',0,1,1,5,NULL),
+(43,'Printing and Stationary',NULL,'42105',0,1,1,5,NULL),
+(44,'Office Rent',NULL,'42107',0,1,1,5,NULL),
+(45,'Marketing Expense',NULL,'42109',0,1,1,5,NULL),
+(46,'Office utilities',NULL,'42112',0,1,1,5,'(supplies, toiletries, kitchen)'),
+(47,'Furniture',NULL,'42113',0,1,1,5,NULL),
+(48,'CEDA Meeting Expense',NULL,'42114',0,1,1,5,NULL),
+(49,'Employee Personal Expsense Account',NULL,'42200',0,1,2,5,NULL),
+(50,'Salary Alice',NULL,'42201',0,1,1,5,NULL),
+(51,'Salary Irene',NULL,'42202',0,1,1,5,NULL),
+(52,'Salary Richard',NULL,'42203',0,1,1,5,NULL),
+(53,'Salary Loan Officer TBA',NULL,'42204',0,1,1,5,NULL),
+(54,'Medical Insurance Alice & Family',NULL,'42205',0,1,1,5,NULL),
+(55,'Medical Insurance Irene',NULL,'42206',0,1,1,5,NULL),
+(56,'Medical Insurance Richard',NULL,'42207',0,1,1,5,NULL),
+(57,'Medical Insurance Loan Officer TBA',NULL,'42208',0,1,1,5,NULL),
+(58,'PAYE all employees',NULL,'42209',0,1,1,5,NULL),
+(59,'NSSF all employees',NULL,'42210',0,1,1,5,NULL),
+(60,'Lunch Allowances all employees',NULL,'42211',0,1,1,5,NULL),
+(61,'IT software and maintenance',NULL,'42300',0,1,2,5,NULL),
+(62,'Mifos maintenance contract 12 months',NULL,'42301',0,1,1,5,NULL),
+(63,'VPS Contract 12 months',NULL,'42302',0,1,1,5,NULL),
+(64,'Bulk SMS Service',NULL,'42303',0,1,1,5,NULL),
+(65,'Support Accounting Software',NULL,'42304',0,1,1,5,NULL),
+(66,'Mifos Instance Setup',NULL,'42305',0,1,1,5,NULL),
+(67,'Misc support expense',NULL,'42306',0,1,1,5,NULL),
+(68,'Warid Mobile Line',NULL,'42307',0,1,1,5,NULL),
+(69,'Landline',NULL,'42308',0,1,1,5,NULL),
+(70,'Modem Alice',NULL,'42309',0,1,1,5,NULL),
+(71,'Modem Irene',NULL,'42310',0,1,1,5,NULL),
+(72,'Modem Richard',NULL,'42311',0,1,1,5,NULL),
+(73,'Repairs',NULL,'42312',0,1,1,5,NULL),
+(74,'Airtime Expenses',NULL,'42400',0,1,2,5,NULL),
+(75,'Airtime Alice',NULL,'42401',0,1,1,5,NULL),
+(76,'Airtime Richard',NULL,'42402',0,1,1,5,NULL),
+(77,'Airtime Loan Office TBA',NULL,'42403',0,1,1,5,NULL),
+(78,'Special Airtime Alice',NULL,'42404',0,1,1,5,NULL),
+(79,'Transportation',NULL,'42500',0,1,2,5,NULL),
+(80,'Flat monthly transportation cost',NULL,'42501',0,1,1,5,NULL),
+(81,'Faciliation cost for Richard',NULL,'42502',0,1,1,5,NULL),
+(82,'Faciliation cost for Loan Officer TBA',NULL,'42503',0,1,1,5,NULL),
+(83,'Consultancy Expenses',NULL,'42600',0,1,2,5,NULL),
+(84,'Audit Fees',NULL,'42601',0,1,1,5,NULL),
+(85,'Legal Fees',NULL,'42602',0,1,1,5,NULL),
+(86,'Miscellaneous Expenses Account',NULL,'42700',0,1,2,5,NULL),
+(87,'MFI License',NULL,'42703',0,1,1,5,NULL),
+(88,'Sundy Expenses',NULL,'42704',0,1,1,5,NULL),
+(89,'Bank Fees',NULL,'42800',0,1,2,5,NULL),
+(90,'Bank Charges Operating Account',NULL,'42801',0,1,1,5,NULL),
+(91,'Bank Charges Expense Account',NULL,'42802',0,1,1,5,NULL),
+(92,'E.H Account',NULL,'42900',0,1,2,5,NULL),
+(93,'Airtime',NULL,'42901',0,1,1,5,NULL),
+(94,'Modem',NULL,'42902',0,1,1,5,NULL),
+(95,'Meals',NULL,'42903',0,1,1,5,NULL),
+(96,'Transportation',NULL,'42904',0,1,1,5,NULL),
+(97,'Miscellaneous',NULL,'42905',0,1,1,5,NULL);
\ No newline at end of file
diff --git a/fineract-db/multi-tenant-demo-backups/gk-maarg/0001b-gk-datatables.sql b/fineract-db/multi-tenant-demo-backups/gk-maarg/0001b-gk-datatables.sql
new file mode 100644
index 0000000..63d463b
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/gk-maarg/0001b-gk-datatables.sql
@@ -0,0 +1,39 @@
+DROP TABLE IF EXISTS `risk_analysis`;
+CREATE TABLE `risk_analysis` (
+  `client_id` bigint(20) NOT NULL,
+  `proposed_loan_amount` decimal(19,6) DEFAULT NULL,
+  `assets_cash` decimal(19,6) DEFAULT NULL,
+  `assets_bank_accounts` decimal(19,6) DEFAULT NULL,
+  `assets_accounts_receivable` decimal(19,6) DEFAULT NULL,
+  `assets_inventory` decimal(19,6) DEFAULT NULL,
+  `assets_total_fixed_business` decimal(19,6) DEFAULT NULL,
+  `assets_total_business` decimal(19,6) DEFAULT NULL,
+  `assets_total_household` decimal(19,6) DEFAULT NULL,
+  `liabilities_accounts_payable` decimal(19,6) DEFAULT NULL,
+  `liabilities_business_debts` decimal(19,6) DEFAULT NULL,
+  `liabilities_total_business` decimal(19,6) DEFAULT NULL,
+  `liabilities_equity_working_capital` decimal(19,6) DEFAULT NULL,
+  `liabilities_total_household` decimal(19,6) DEFAULT NULL,
+  `liabilities_household_equity` decimal(19,6) DEFAULT NULL,
+  `cashflow_cash_sales` decimal(19,6) DEFAULT NULL,
+  `cashflow_cash_sales2` decimal(19,6) DEFAULT NULL,
+  `cashflow_cost_goods_sold` decimal(19,6) DEFAULT NULL,
+  `cashflow_cost_goods_sold2` decimal(19,6) DEFAULT NULL,
+  `cashflow_gross_profit` decimal(19,6) DEFAULT NULL,
+  `cashflow_other_income1` decimal(19,6) DEFAULT NULL,
+  `cashflow_total_income2` decimal(19,6) DEFAULT NULL,
+  `cashflow_household_expense` decimal(19,6) DEFAULT NULL,
+  `cashflow_payments_to_savings` decimal(19,6) DEFAULT NULL,
+  `cashflow_operational_expenses` decimal(19,6) DEFAULT NULL,
+  `cashflow_disposable_income` decimal(19,6) DEFAULT NULL,
+  `cashflow_amount_loan_installment` decimal(19,6) DEFAULT NULL,
+  `cashflow_available_surplus` decimal(19,6) DEFAULT NULL,
+  `fi_inventory_turnover` decimal(19,6) DEFAULT NULL,
+  `fi_gross_margin` decimal(19,6) DEFAULT NULL,
+  `fi_indebtedness` decimal(19,6) DEFAULT NULL,
+  `fi_loan_recommendation` decimal(19,6) DEFAULT NULL,
+  `fi_roe` decimal(19,6) DEFAULT NULL,
+  `fi_repayment_capacity` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_risk_analysis_1` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
\ No newline at end of file
diff --git a/fineract-db/multi-tenant-demo-backups/latam-demo/README.md b/fineract-db/multi-tenant-demo-backups/latam-demo/README.md
new file mode 100644
index 0000000..f75fffe
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/latam-demo/README.md
@@ -0,0 +1,16 @@
+latam Demo
+======
+
+This demo database contains:
+
+- DDL of latest schema
+- Minimum reference data required for deployment of platform which is:
+  -  Its mandatory to have one selected currency so we default to several of latin-america currencies
+  -  Its mandatory to have one root or head office, so we have one created by default called a 'Latam HO'
+  -  Permissions supported/needed by latest release of software are setup
+  -  Its mandatory to have at least one role when creating new users so we have one role created by default called 'Super user' which has the special permission 'Full Authorisation'. Any user with this role can do anything in the system.
+  -  Its required to have at least one application user setup so remaining setup can be done through ui so we have on application user created by default with username 'quipo' with a password of 'quipo'. Application users must be associated with an office and a role so this user is associated with 'Latam HO' and 'Super user' role allowing this user to do any operation in any office(branch).
+- Configuration
+  - No 'additional data' through the 'datatables' approach is setup
+  - One 'code' is setup called 'Client Identifier' with default values of {'Passport number'} - (required for Client Identity Document functionalty)
+  - Enable/Disable configuration has one entry named 'maker-checker' to allow people to enable disable this feature at global level. It is off or disabled by default.
\ No newline at end of file
diff --git a/fineract-db/multi-tenant-demo-backups/latam-demo/bk_latam.sql b/fineract-db/multi-tenant-demo-backups/latam-demo/bk_latam.sql
new file mode 100644
index 0000000..770877b
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/latam-demo/bk_latam.sql
@@ -0,0 +1,1847 @@
+-- MySQL dump 10.13  Distrib 5.1.60, for Win32 (ia32)
+--
+-- Host: localhost    Database: mifostenant-default
+-- ------------------------------------------------------
+-- Server version	5.1.60-community
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `acc_gl_account`
+--
+
+DROP TABLE IF EXISTS `acc_gl_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_account`
+--
+
+LOCK TABLES `acc_gl_account` WRITE;
+/*!40000 ALTER TABLE `acc_gl_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_closure`
+--
+
+DROP TABLE IF EXISTS `acc_gl_closure`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_closure`
+--
+
+LOCK TABLES `acc_gl_closure` WRITE;
+/*!40000 ALTER TABLE `acc_gl_closure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_closure` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_gl_journal_entry`
+--
+
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `portfolio_generated` tinyint(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(50) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type` varchar(50) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_gl_journal_entry`
+--
+
+LOCK TABLES `acc_gl_journal_entry` WRITE;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `acc_product_mapping`
+--
+
+DROP TABLE IF EXISTS `acc_product_mapping`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `acc_product_mapping`
+--
+
+LOCK TABLES `acc_product_mapping` WRITE;
+/*!40000 ALTER TABLE `acc_product_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_product_mapping` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `c_configuration`
+--
+
+DROP TABLE IF EXISTS `c_configuration`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `c_configuration`
+--
+
+LOCK TABLES `c_configuration` WRITE;
+/*!40000 ALTER TABLE `c_configuration` DISABLE KEYS */;
+INSERT INTO `c_configuration` VALUES (1,'maker-checker',0);
+/*!40000 ALTER TABLE `c_configuration` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_client_details`
+--
+
+DROP TABLE IF EXISTS `extra_client_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_client_details` (
+  `client_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_latam_extra_client_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_client_details`
+--
+
+LOCK TABLES `extra_client_details` WRITE;
+/*!40000 ALTER TABLE `extra_client_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_client_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_family_details`
+--
+
+DROP TABLE IF EXISTS `extra_family_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_family_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `Name` varchar(40) DEFAULT NULL,
+  `Date of Birth` date DEFAULT NULL,
+  `Points Score` int(11) DEFAULT NULL,
+  `Education_cd_Highest` int(11) DEFAULT NULL,
+  `Other Notes` text,
+  PRIMARY KEY (`id`),
+  KEY `FK_Extra Family Details Data_1` (`client_id`),
+  CONSTRAINT `FK_latam_family_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_family_details`
+--
+
+LOCK TABLES `extra_family_details` WRITE;
+/*!40000 ALTER TABLE `extra_family_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_family_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `extra_loan_details`
+--
+
+DROP TABLE IF EXISTS `extra_loan_details`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `extra_loan_details` (
+  `loan_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_latam_extra_loan_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `extra_loan_details`
+--
+
+LOCK TABLES `extra_loan_details` WRITE;
+/*!40000 ALTER TABLE `extra_loan_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `extra_loan_details` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser`
+--
+
+DROP TABLE IF EXISTS `m_appuser`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser`
+--
+
+LOCK TABLES `m_appuser` WRITE;
+/*!40000 ALTER TABLE `m_appuser` DISABLE KEYS */;
+INSERT INTO `m_appuser` VALUES (1,0,1,'mifos','App','Administrator','5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a','demomfi@mifos.org','\0','','','','');
+/*!40000 ALTER TABLE `m_appuser` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_appuser_role`
+--
+
+DROP TABLE IF EXISTS `m_appuser_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_appuser_role`
+--
+
+LOCK TABLES `m_appuser_role` WRITE;
+/*!40000 ALTER TABLE `m_appuser_role` DISABLE KEYS */;
+INSERT INTO `m_appuser_role` VALUES (1,1);
+/*!40000 ALTER TABLE `m_appuser_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_charge`
+--
+
+DROP TABLE IF EXISTS `m_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_charge`
+--
+
+LOCK TABLES `m_charge` WRITE;
+/*!40000 ALTER TABLE `m_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client`
+--
+
+DROP TABLE IF EXISTS `m_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `image_key` varchar(500) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client`
+--
+
+LOCK TABLES `m_client` WRITE;
+/*!40000 ALTER TABLE `m_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_client_identifier`
+--
+
+DROP TABLE IF EXISTS `m_client_identifier`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_client_identifier`
+--
+
+LOCK TABLES `m_client_identifier` WRITE;
+/*!40000 ALTER TABLE `m_client_identifier` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_identifier` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code`
+--
+
+DROP TABLE IF EXISTS `m_code`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code`
+--
+
+LOCK TABLES `m_code` WRITE;
+/*!40000 ALTER TABLE `m_code` DISABLE KEYS */;
+INSERT INTO `m_code` VALUES (1,'Customer Identifier',1),(2,'Gender',1),(3,'Education',1);
+/*!40000 ALTER TABLE `m_code` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_code_value`
+--
+
+DROP TABLE IF EXISTS `m_code_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_code_value`
+--
+
+LOCK TABLES `m_code_value` WRITE;
+/*!40000 ALTER TABLE `m_code_value` DISABLE KEYS */;
+INSERT INTO `m_code_value` VALUES (1,1,'Passport number',0),(2,2,'Male',1),(3,2,'Female',2),(4,3,'Primary',1),(5,3,'Secondary',2),(6,3,'University',3);
+/*!40000 ALTER TABLE `m_code_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_currency`
+--
+
+DROP TABLE IF EXISTS `m_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_currency`
+--
+
+LOCK TABLES `m_currency` WRITE;
+/*!40000 ALTER TABLE `m_currency` DISABLE KEYS */;
+INSERT INTO `m_currency` VALUES (1,'AED',2,NULL,'UAE Dirham','currency.AED'),(2,'AFN',2,NULL,'Afghanistan Afghani','currency.AFN'),(3,'ALL',2,NULL,'Albanian Lek','currency.ALL'),(4,'AMD',2,NULL,'Armenian Dram','currency.AMD'),(5,'ANG',2,NULL,'Netherlands Antillian Guilder','currency.ANG'),(6,'AOA',2,NULL,'Angolan Kwanza','currency.AOA'),(7,'ARS',2,'$','Argentine Peso','currency.ARS'),(8,'AUD',2,'A$','Australian Dollar','currency.AUD'),(9,'AWG',2,NULL,'Aruban Guilder','currency.AWG'),(10,'AZM',2,NULL,'Azerbaijanian Manat','currency.AZM'),(11,'BAM',2,NULL,'Bosnia and Herzegovina Convertible Marks','currency.BAM'),(12,'BBD',2,NULL,'Barbados Dollar','currency.BBD'),(13,'BDT',2,NULL,'Bangladesh Taka','currency.BDT'),(14,'BGN',2,NULL,'Bulgarian Lev','currency.BGN'),(15,'BHD',3,NULL,'Bahraini Dinar','currency.BHD'),(16,'BIF',0,NULL,'Burundi Franc','currency.BIF'),(17,'BMD',2,NULL,'Bermudian Dollar','currency.BMD'),(18,'BND',2,'B$','Brunei Dollar','currency.BND'),(19,'BOB',2,'Bs.','Bolivian Boliviano','currency.BOB'),(20,'BRL',2,'R$','Brazilian Real','currency.BRL'),(21,'BSD',2,NULL,'Bahamian Dollar','currency.BSD'),(22,'BTN',2,NULL,'Bhutan Ngultrum','currency.BTN'),(23,'BWP',2,NULL,'Botswana Pula','currency.BWP'),(24,'BYR',0,NULL,'Belarussian Ruble','currency.BYR'),(25,'BZD',2,'BZ$','Belize Dollar','currency.BZD'),(26,'CAD',2,NULL,'Canadian Dollar','currency.CAD'),(27,'CDF',2,NULL,'Franc Congolais','currency.CDF'),(28,'CHF',2,NULL,'Swiss Franc','currency.CHF'),(29,'CLP',0,'$','Chilean Peso','currency.CLP'),(30,'CNY',2,NULL,'Chinese Yuan Renminbi','currency.CNY'),(31,'COP',2,'$','Colombian Peso','currency.COP'),(32,'CRC',2,'₡','Costa Rican Colon','currency.CRC'),(33,'CSD',2,NULL,'Serbian Dinar','currency.CSD'),(34,'CUP',2,'$MN','Cuban Peso','currency.CUP'),(35,'CVE',2,NULL,'Cape Verde Escudo','currency.CVE'),(36,'CYP',2,NULL,'Cyprus Pound','currency.CYP'),(37,'CZK',2,NULL,'Czech Koruna','currency.CZK'),(38,'DJF',0,NULL,'Djibouti Franc','currency.DJF'),(39,'DKK',2,NULL,'Danish Krone','currency.DKK'),(40,'DOP',2,'RD$','Dominican Peso','currency.DOP'),(41,'DZD',2,NULL,'Algerian Dinar','currency.DZD'),(42,'EEK',2,NULL,'Estonian Kroon','currency.EEK'),(43,'EGP',2,NULL,'Egyptian Pound','currency.EGP'),(44,'ERN',2,NULL,'Eritrea Nafka','currency.ERN'),(45,'ETB',2,NULL,'Ethiopian Birr','currency.ETB'),(46,'EUR',2,'€','Euro','currency.EUR'),(47,'FJD',2,NULL,'Fiji Dollar','currency.FJD'),(48,'FKP',2,NULL,'Falkland Islands Pound','currency.FKP'),(49,'GBP',2,NULL,'Pound Sterling','currency.GBP'),(50,'GEL',2,NULL,'Georgian Lari','currency.GEL'),(51,'GHC',2,'GHc','Ghana Cedi','currency.GHC'),(52,'GIP',2,NULL,'Gibraltar Pound','currency.GIP'),(53,'GMD',2,NULL,'Gambian Dalasi','currency.GMD'),(54,'GNF',0,NULL,'Guinea Franc','currency.GNF'),(55,'GTQ',2,'Q','Guatemala Quetzal','currency.GTQ'),(56,'GYD',2,NULL,'Guyana Dollar','currency.GYD'),(57,'HKD',2,NULL,'Hong Kong Dollar','currency.HKD'),(58,'HNL',2,'L','Honduras Lempira','currency.HNL'),(59,'HRK',2,NULL,'Croatian Kuna','currency.HRK'),(60,'HTG',2,'G','Haiti Gourde','currency.HTG'),(61,'HUF',2,NULL,'Hungarian Forint','currency.HUF'),(62,'IDR',2,NULL,'Indonesian Rupiah','currency.IDR'),(63,'ILS',2,NULL,'New Israeli Shekel','currency.ILS'),(64,'INR',2,'₹','Indian Rupee','currency.INR'),(65,'IQD',3,NULL,'Iraqi Dinar','currency.IQD'),(66,'IRR',2,NULL,'Iranian Rial','currency.IRR'),(67,'ISK',0,NULL,'Iceland Krona','currency.ISK'),(68,'JMD',2,NULL,'Jamaican Dollar','currency.JMD'),(69,'JOD',3,NULL,'Jordanian Dinar','currency.JOD'),(70,'JPY',0,NULL,'Japanese Yen','currency.JPY'),(71,'KES',2,'KSh','Kenyan Shilling','currency.KES'),(72,'KGS',2,NULL,'Kyrgyzstan Som','currency.KGS'),(73,'KHR',2,NULL,'Cambodia Riel','currency.KHR'),(74,'KMF',0,NULL,'Comoro Franc','currency.KMF'),(75,'KPW',2,NULL,'North Korean Won','currency.KPW'),(76,'KRW',0,NULL,'Korean Won','currency.KRW'),(77,'KWD',3,NULL,'Kuwaiti Dinar','currency.KWD'),(78,'KYD',2,NULL,'Cayman Islands Dollar','currency.KYD'),(79,'KZT',2,NULL,'Kazakhstan Tenge','currency.KZT'),(80,'LAK',2,NULL,'Lao Kip','currency.LAK'),(81,'LBP',2,'L£','Lebanese Pound','currency.LBP'),(82,'LKR',2,NULL,'Sri Lanka Rupee','currency.LKR'),(83,'LRD',2,NULL,'Liberian Dollar','currency.LRD'),(84,'LSL',2,NULL,'Lesotho Loti','currency.LSL'),(85,'LTL',2,NULL,'Lithuanian Litas','currency.LTL'),(86,'LVL',2,NULL,'Latvian Lats','currency.LVL'),(87,'LYD',3,NULL,'Libyan Dinar','currency.LYD'),(88,'MAD',2,NULL,'Moroccan Dirham','currency.MAD'),(89,'MDL',2,NULL,'Moldovan Leu','currency.MDL'),(90,'MGA',2,NULL,'Malagasy Ariary','currency.MGA'),(91,'MKD',2,NULL,'Macedonian Denar','currency.MKD'),(92,'MMK',2,'K','Myanmar Kyat','currency.MMK'),(93,'MNT',2,NULL,'Mongolian Tugrik','currency.MNT'),(94,'MOP',2,NULL,'Macau Pataca','currency.MOP'),(95,'MRO',2,NULL,'Mauritania Ouguiya','currency.MRO'),(96,'MTL',2,NULL,'Maltese Lira','currency.MTL'),(97,'MUR',2,NULL,'Mauritius Rupee','currency.MUR'),(98,'MVR',2,NULL,'Maldives Rufiyaa','currency.MVR'),(99,'MWK',2,NULL,'Malawi Kwacha','currency.MWK'),(100,'MXN',2,'$','Mexican Peso','currency.MXN'),(101,'MYR',2,NULL,'Malaysian Ringgit','currency.MYR'),(102,'MZM',2,NULL,'Mozambique Metical','currency.MZM'),(103,'NAD',2,NULL,'Namibia Dollar','currency.NAD'),(104,'NGN',2,NULL,'Nigerian Naira','currency.NGN'),(105,'NIO',2,'C$','Nicaragua Cordoba Oro','currency.NIO'),(106,'NOK',2,NULL,'Norwegian Krone','currency.NOK'),(107,'NPR',2,NULL,'Nepalese Rupee','currency.NPR'),(108,'NZD',2,NULL,'New Zealand Dollar','currency.NZD'),(109,'OMR',3,NULL,'Rial Omani','currency.OMR'),(110,'PAB',2,'B/.','Panama Balboa','currency.PAB'),(111,'PEN',2,'S/.','Peruvian Nuevo Sol','currency.PEN'),(112,'PGK',2,NULL,'Papua New Guinea Kina','currency.PGK'),(113,'PHP',2,NULL,'Philippine Peso','currency.PHP'),(114,'PKR',2,NULL,'Pakistan Rupee','currency.PKR'),(115,'PLN',2,NULL,'Polish Zloty','currency.PLN'),(116,'PYG',0,'₲','Paraguayan Guarani','currency.PYG'),(117,'QAR',2,NULL,'Qatari Rial','currency.QAR'),(118,'RON',2,NULL,'Romanian Leu','currency.RON'),(119,'RUB',2,NULL,'Russian Ruble','currency.RUB'),(120,'RWF',0,NULL,'Rwanda Franc','currency.RWF'),(121,'SAR',2,NULL,'Saudi Riyal','currency.SAR'),(122,'SBD',2,NULL,'Solomon Islands Dollar','currency.SBD'),(123,'SCR',2,NULL,'Seychelles Rupee','currency.SCR'),(124,'SDD',2,NULL,'Sudanese Dinar','currency.SDD'),(125,'SEK',2,NULL,'Swedish Krona','currency.SEK'),(126,'SGD',2,NULL,'Singapore Dollar','currency.SGD'),(127,'SHP',2,NULL,'St Helena Pound','currency.SHP'),(128,'SIT',2,NULL,'Slovenian Tolar','currency.SIT'),(129,'SKK',2,NULL,'Slovak Koruna','currency.SKK'),(130,'SLL',2,NULL,'Sierra Leone Leone','currency.SLL'),(131,'SOS',2,NULL,'Somali Shilling','currency.SOS'),(132,'SRD',2,NULL,'Surinam Dollar','currency.SRD'),(133,'STD',2,NULL,'Sao Tome and Principe Dobra','currency.STD'),(134,'SVC',2,NULL,'El Salvador Colon','currency.SVC'),(135,'SYP',2,NULL,'Syrian Pound','currency.SYP'),(136,'SZL',2,NULL,'Swaziland Lilangeni','currency.SZL'),(137,'THB',2,NULL,'Thai Baht','currency.THB'),(138,'TJS',2,NULL,'Tajik Somoni','currency.TJS'),(139,'TMM',2,NULL,'Turkmenistan Manat','currency.TMM'),(140,'TND',3,'DT','Tunisian Dinar','currency.TND'),(141,'TOP',2,NULL,'Tonga Pa\'anga','currency.TOP'),(142,'TRY',2,NULL,'Turkish Lira','currency.TRY'),(143,'TTD',2,NULL,'Trinidad and Tobago Dollar','currency.TTD'),(144,'TWD',2,NULL,'New Taiwan Dollar','currency.TWD'),(145,'TZS',2,NULL,'Tanzanian Shilling','currency.TZS'),(146,'UAH',2,NULL,'Ukraine Hryvnia','currency.UAH'),(147,'UGX',2,'USh','Uganda Shilling','currency.UGX'),(148,'USD',2,'$','US Dollar','currency.USD'),(149,'UYU',2,'$U','Peso Uruguayo','currency.UYU'),(150,'UZS',2,NULL,'Uzbekistan Sum','currency.UZS'),(151,'VEB',2,'Bs.F.','Venezuelan Bolivar','currency.VEB'),(152,'VND',2,NULL,'Vietnamese Dong','currency.VND'),(153,'VUV',0,NULL,'Vanuatu Vatu','currency.VUV'),(154,'WST',2,NULL,'Samoa Tala','currency.WST'),(155,'XAF',0,NULL,'CFA Franc BEAC','currency.XAF'),(156,'XCD',2,NULL,'East Caribbean Dollar','currency.XCD'),(157,'XDR',5,NULL,'SDR (Special Drawing Rights)','currency.XDR'),(158,'XOF',0,'CFA','CFA Franc BCEAO','currency.XOF'),(159,'XPF',0,NULL,'CFP Franc','currency.XPF'),(160,'YER',2,NULL,'Yemeni Rial','currency.YER'),(161,'ZAR',2,'R','South African Rand','currency.ZAR'),(162,'ZMK',2,NULL,'Zambian Kwacha','currency.ZMK'),(163,'ZWD',2,NULL,'Zimbabwe Dollar','currency.ZWD');
+/*!40000 ALTER TABLE `m_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_deposit_account`
+--
+
+DROP TABLE IF EXISTS `m_deposit_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_deposit_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `status_enum` smallint(5) NOT NULL DEFAULT '0',
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) NOT NULL,
+  `product_id` bigint(20) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `deposit_amount` decimal(19,6) DEFAULT NULL,
+  `maturity_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `tenure_months` int(11) NOT NULL,
+  `interest_compounded_every` smallint(5) NOT NULL DEFAULT '1',
+  `interest_compounded_every_period_enum` smallint(5) NOT NULL DEFAULT '2',
+  `projected_commencement_date` date NOT NULL,
+  `actual_commencement_date` date DEFAULT NULL,
+  `matures_on_date` datetime DEFAULT NULL,
+  `projected_interest_accrued_on_maturity` decimal(19,6) NOT NULL,
+  `actual_interest_accrued` decimal(19,6) DEFAULT NULL,
+  `projected_total_maturity_amount` decimal(19,6) NOT NULL,
+  `actual_total_amount` decimal(19,6) DEFAULT NULL,
+  `is_compounding_interest_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `interest_paid` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_interest_withdrawable` tinyint(1) NOT NULL DEFAULT '0',
+  `available_interest` decimal(19,6) DEFAULT '0.000000',
+  `interest_posted_amount` decimal(19,6) DEFAULT '0.000000',
+  `last_interest_posted_date` date DEFAULT NULL,
+  `next_interest_posting_date` date DEFAULT NULL,
+  `is_renewal_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `renewed_account_id` bigint(20) DEFAULT NULL,
+  `is_preclosure_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `pre_closure_interest_rate` decimal(19,6) NOT NULL,
+  `is_lock_in_period_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `lock_in_period` bigint(20) DEFAULT NULL,
+  `lock_in_period_type` smallint(5) NOT NULL DEFAULT '2',
+  `withdrawnon_date` datetime DEFAULT NULL,
+  `rejectedon_date` datetime DEFAULT NULL,
+  `closedon_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `deposit_acc_external_id` (`external_id`),
+  KEY `FKKW0000000000001` (`client_id`),
+  KEY `FKKW0000000000002` (`product_id`),
+  KEY `FKKW0000000000003` (`renewed_account_id`),
+  CONSTRAINT `FKKW0000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKKW0000000000002` FOREIGN KEY (`product_id`) REFERENCES `m_product_deposit` (`id`),
+  CONSTRAINT `FKKW0000000000003` FOREIGN KEY (`renewed_account_id`) REFERENCES `m_deposit_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_deposit_account`
+--
+
+LOCK TABLES `m_deposit_account` WRITE;
+/*!40000 ALTER TABLE `m_deposit_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_deposit_account_transaction`
+--
+
+DROP TABLE IF EXISTS `m_deposit_account_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_deposit_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `deposit_account_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `contra_id` bigint(20) DEFAULT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `interest` decimal(19,6) NOT NULL,
+  `total` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKKW00000000000005` (`deposit_account_id`),
+  KEY `FKKW00000000000006` (`contra_id`),
+  CONSTRAINT `FKKW00000000000005` FOREIGN KEY (`deposit_account_id`) REFERENCES `m_deposit_account` (`id`),
+  CONSTRAINT `FKKW00000000000006` FOREIGN KEY (`contra_id`) REFERENCES `m_deposit_account_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_deposit_account_transaction`
+--
+
+LOCK TABLES `m_deposit_account_transaction` WRITE;
+/*!40000 ALTER TABLE `m_deposit_account_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_account_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_document`
+--
+
+DROP TABLE IF EXISTS `m_document`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(50) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_document`
+--
+
+LOCK TABLES `m_document` WRITE;
+/*!40000 ALTER TABLE `m_document` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_document` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_fund`
+--
+
+DROP TABLE IF EXISTS `m_fund`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_fund`
+--
+
+LOCK TABLES `m_fund` WRITE;
+/*!40000 ALTER TABLE `m_fund` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_fund` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group`
+--
+
+DROP TABLE IF EXISTS `m_group`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `name` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `office_id` (`office_id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group`
+--
+
+LOCK TABLES `m_group` WRITE;
+/*!40000 ALTER TABLE `m_group` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_group_client`
+--
+
+DROP TABLE IF EXISTS `m_group_client`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_group_client`
+--
+
+LOCK TABLES `m_group_client` WRITE;
+/*!40000 ALTER TABLE `m_group_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_guarantor`
+--
+
+DROP TABLE IF EXISTS `m_guarantor`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_guarantor` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `dob` date DEFAULT NULL,
+  `address_line_1` varchar(500) DEFAULT NULL,
+  `address_line_2` varchar(500) DEFAULT NULL,
+  `city` varchar(50) DEFAULT NULL,
+  `state` varchar(50) DEFAULT NULL,
+  `country` varchar(50) DEFAULT NULL,
+  `zip` varchar(20) DEFAULT NULL,
+  `house_phone_number` varchar(20) DEFAULT NULL,
+  `mobile_number` varchar(20) DEFAULT NULL,
+  `comment` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_m_loan` (`loan_id`),
+  CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_guarantor`
+--
+
+LOCK TABLES `m_guarantor` WRITE;
+/*!40000 ALTER TABLE `m_guarantor` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan`
+--
+
+DROP TABLE IF EXISTS `m_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `guarantor_id` bigint(20) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `submittedon_date` datetime DEFAULT NULL,
+  `approvedon_date` datetime DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` datetime DEFAULT NULL,
+  `rejectedon_date` datetime DEFAULT NULL,
+  `rescheduledon_date` datetime DEFAULT NULL,
+  `withdrawnon_date` datetime DEFAULT NULL,
+  `writtenoffon_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loan_guarantor` (`guarantor_id`),
+  CONSTRAINT `FK_m_loan_guarantor` FOREIGN KEY (`guarantor_id`) REFERENCES `m_guarantor` (`id`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan`
+--
+
+LOCK TABLES `m_loan` WRITE;
+/*!40000 ALTER TABLE `m_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_charge`
+--
+
+LOCK TABLES `m_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_officer_assignment_history`
+--
+
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_officer_assignment_history`
+--
+
+LOCK TABLES `m_loan_officer_assignment_history` WRITE;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_repayment_schedule`
+--
+
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_repayment_schedule`
+--
+
+LOCK TABLES `m_loan_repayment_schedule` WRITE;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_loan_transaction`
+--
+
+DROP TABLE IF EXISTS `m_loan_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `contra_id` bigint(20) DEFAULT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  KEY `FKCFCEA426FC69F3F1` (`contra_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FKCFCEA426FC69F3F1` FOREIGN KEY (`contra_id`) REFERENCES `m_loan_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_loan_transaction`
+--
+
+LOCK TABLES `m_loan_transaction` WRITE;
+/*!40000 ALTER TABLE `m_loan_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_note`
+--
+
+DROP TABLE IF EXISTS `m_note`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `deposit_account_id` bigint(20) DEFAULT NULL,
+  `saving_account_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  KEY `FK_m_note_m_deposit_account` (`deposit_account_id`),
+  KEY `FK_m_note_m_saving_account` (`saving_account_id`),
+  CONSTRAINT `FK_m_note_m_saving_account` FOREIGN KEY (`saving_account_id`) REFERENCES `m_saving_account` (`id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_note_m_deposit_account` FOREIGN KEY (`deposit_account_id`) REFERENCES `m_deposit_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_note`
+--
+
+LOCK TABLES `m_note` WRITE;
+/*!40000 ALTER TABLE `m_note` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_note` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office`
+--
+
+DROP TABLE IF EXISTS `m_office`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office`
+--
+
+LOCK TABLES `m_office` WRITE;
+/*!40000 ALTER TABLE `m_office` DISABLE KEYS */;
+INSERT INTO `m_office` VALUES (1,NULL,'.','1','Head Office','2009-01-01');
+/*!40000 ALTER TABLE `m_office` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_office_transaction`
+--
+
+DROP TABLE IF EXISTS `m_office_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_office_transaction`
+--
+
+LOCK TABLES `m_office_transaction` WRITE;
+/*!40000 ALTER TABLE `m_office_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_office_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_organisation_currency`
+--
+
+DROP TABLE IF EXISTS `m_organisation_currency`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_organisation_currency`
+--
+
+LOCK TABLES `m_organisation_currency` WRITE;
+/*!40000 ALTER TABLE `m_organisation_currency` DISABLE KEYS */;
+INSERT INTO `m_organisation_currency` VALUES (21,'USD',2,'US Dollar','$','currency.USD');
+/*!40000 ALTER TABLE `m_organisation_currency` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_permission`
+--
+
+DROP TABLE IF EXISTS `m_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=237 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_permission`
+--
+
+LOCK TABLES `m_permission` WRITE;
+/*!40000 ALTER TABLE `m_permission` DISABLE KEYS */;
+INSERT INTO `m_permission` VALUES (1,'special','ALL_FUNCTIONS',NULL,NULL,0),(2,'special','ALL_FUNCTIONS_READ',NULL,NULL,0),(3,'special','CHECKER_SUPER_USER',NULL,NULL,0),(4,'special','REPORTING_SUPER_USER',NULL,NULL,0),(5,'authorisation','READ_PERMISSION','PERMISSION','READ',0),(6,'authorisation','PERMISSIONS_ROLE','ROLE','PERMISSIONS',1),(7,'authorisation','CREATE_ROLE','ROLE','CREATE',1),(8,'authorisation','CREATE_ROLE_CHECKER','ROLE','CREATE',1),(9,'authorisation','READ_ROLE','ROLE','READ',0),(10,'authorisation','UPDATE_ROLE','ROLE','UPDATE',1),(11,'authorisation','UPDATE_ROLE_CHECKER','ROLE','UPDATE',1),(12,'authorisation','DELETE_ROLE','ROLE','DELETE',1),(13,'authorisation','DELETE_ROLE_CHECKER','ROLE','DELETE',1),(14,'authorisation','CREATE_USER','USER','CREATE',1),(15,'authorisation','CREATE_USER_CHECKER','USER','CREATE',1),(16,'authorisation','READ_USER','USER','READ',0),(17,'authorisation','UPDATE_USER','USER','UPDATE',1),(18,'authorisation','UPDATE_USER_CHECKER','USER','UPDATE',1),(19,'authorisation','DELETE_USER','USER','DELETE',1),(20,'authorisation','DELETE_USER_CHECKER','USER','DELETE',1),(21,'configuration','READ_CONFIGURATION','CONFIGURATION','READ',1),(22,'configuration','UPDATE_CONFIGURATION','CONFIGURATION','UPDATE',1),(23,'configuration','UPDATE_CONFIGURATION_CHECKER','CONFIGURATION','UPDATE',1),(24,'configuration','READ_CODE','CODE','READ',0),(25,'configuration','CREATE_CODE','CODE','CREATE',1),(26,'configuration','CREATE_CODE_CHECKER','CODE','CREATE',1),(27,'configuration','UPDATE_CODE','CODE','UPDATE',1),(28,'configuration','UPDATE_CODE_CHECKER','CODE','UPDATE',1),(29,'configuration','DELETE_CODE','CODE','DELETE',1),(30,'configuration','DELETE_CODE_CHECKER','CODE','DELETE',1),(31,'configuration','READ_CURRENCY','CURRENCY','READ',0),(32,'configuration','UPDATE_CURRENCY','CURRENCY','UPDATE',1),(33,'configuration','UPDATE_CURRENCY_CHECKER','CURRENCY','UPDATE',1),(34,'configuration','UPDATE_PERMISSION','PERMISSION','UPDATE',1),(35,'configuration','UPDATE_PERMISSION_CHECKER','PERMISSION','UPDATE',1),(36,'configuration','READ_DATATABLE','DATATABLE','READ',0),(37,'configuration','REGISTER_DATATABLE','DATATABLE','REGISTER',1),(38,'configuration','REGISTER_DATATABLE_CHECKER','DATATABLE','REGISTER',1),(39,'configuration','DEREGISTER_DATATABLE','DATATABLE','DEREGISTER',1),(40,'configuration','DEREGISTER_DATATABLE_CHECKER','DATATABLE','DEREGISTER',1),(41,'configuration','READ_AUDIT','AUDIT','READ',0),(42,'organisation','READ_MAKERCHECKER','MAKERCHECKER','READ',0),(43,'organisation','READ_CHARGE','CHARGE','READ',0),(44,'organisation','CREATE_CHARGE','CHARGE','CREATE',1),(45,'organisation','CREATE_CHARGE_CHECKER','CHARGE','CREATE',1),(46,'organisation','UPDATE_CHARGE','CHARGE','UPDATE',1),(47,'organisation','UPDATE_CHARGE_CHECKER','CHARGE','UPDATE',1),(48,'organisation','DELETE_CHARGE','CHARGE','DELETE',1),(49,'organisation','DELETE_CHARGE_CHECKER','CHARGE','DELETE',1),(50,'organisation','READ_FUND','FUND','READ',0),(51,'organisation','CREATE_FUND','FUND','CREATE',1),(52,'organisation','CREATE_FUND_CHECKER','FUND','CREATE',1),(53,'organisation','UPDATE_FUND','FUND','UPDATE',1),(54,'organisation','UPDATE_FUND_CHECKER','FUND','UPDATE',1),(55,'organisation','DELETE_FUND','FUND','DELETE',1),(56,'organisation','DELETE_FUND_CHECKER','FUND','DELETE',1),(57,'organisation','READ_LOANPRODUCT','LOANPRODUCT','READ',0),(58,'organisation','CREATE_LOANPRODUCT','LOANPRODUCT','CREATE',1),(59,'organisation','CREATE_LOANPRODUCT_CHECKER','LOANPRODUCT','CREATE',1),(60,'organisation','UPDATE_LOANPRODUCT','LOANPRODUCT','UPDATE',1),(61,'organisation','UPDATE_LOANPRODUCT_CHECKER','LOANPRODUCT','UPDATE',1),(62,'organisation','DELETE_LOANPRODUCT','LOANPRODUCT','DELETE',1),(63,'organisation','DELETE_LOANPRODUCT_CHECKER','LOANPRODUCT','DELETE',1),(64,'organisation','READ_OFFICE','OFFICE','READ',0),(65,'organisation','CREATE_OFFICE','OFFICE','CREATE',1),(66,'organisation','CREATE_OFFICE_CHECKER','OFFICE','CREATE',1),(67,'organisation','UPDATE_OFFICE','OFFICE','UPDATE',1),(68,'organisation','UPDATE_OFFICE_CHECKER','OFFICE','UPDATE',1),(69,'organisation','READ_OFFICETRANSACTION','OFFICETRANSACTION','READ',0),(70,'organisation','DELETE_OFFICE_CHECKER','OFFICE','DELETE',1),(71,'organisation','CREATE_OFFICETRANSACTION','OFFICETRANSACTION','CREATE',1),(72,'organisation','CREATE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','CREATE',1),(73,'organisation','DELETE_OFFICETRANSACTION','OFFICETRANSACTION','DELETE',1),(74,'organisation','DELETE_OFFICETRANSACTION_CHECKER','OFFICETRANSACTION','DELETE',1),(75,'organisation','READ_STAFF','STAFF','READ',0),(76,'organisation','CREATE_STAFF','STAFF','CREATE',1),(77,'organisation','CREATE_STAFF_CHECKER','STAFF','CREATE',1),(78,'organisation','UPDATE_STAFF','STAFF','UPDATE',1),(79,'organisation','UPDATE_STAFF_CHECKER','STAFF','UPDATE',1),(80,'organisation','DELETE_STAFF','STAFF','DELETE',1),(81,'organisation','DELETE_STAFF_CHECKER','STAFF','DELETE',1),(82,'organisation','READ_SAVINGSPRODUCT','SAVINGSPRODUCT','READ',0),(83,'organisation','CREATE_SAVINGSPRODUCT','SAVINGSPRODUCT','CREATE',1),(84,'organisation','CREATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','CREATE',1),(85,'organisation','UPDATE_SAVINGSPRODUCT','SAVINGSPRODUCT','UPDATE',1),(86,'organisation','UPDATE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','UPDATE',1),(87,'organisation','DELETE_SAVINGSPRODUCT','SAVINGSPRODUCT','DELETE',1),(88,'organisation','DELETE_SAVINGSPRODUCT_CHECKER','SAVINGSPRODUCT','DELETE',1),(89,'organisation','READ_DEPOSITPRODUCT','DEPOSITPRODUCT','READ',0),(90,'organisation','CREATE_DEPOSITPRODUCT','DEPOSITPRODUCT','CREATE',1),(91,'organisation','CREATE_DEPOSITPRODUCT_CHECKER','DEPOSITPRODUCT','CREATE',1),(92,'organisation','UPDATE_DEPOSITPRODUCT','DEPOSITPRODUCT','UPDATE',1),(93,'organisation','UPDATE_DEPOSITPRODUCT_CHECKER','DEPOSITPRODUCT','UPDATE',1),(94,'organisation','DELETE_DEPOSITPRODUCT','DEPOSITPRODUCT','DELETE',1),(95,'organisation','DELETE_DEPOSITPRODUCT_CHECKER','DEPOSITPRODUCT','DELETE',1),(96,'portfolio','READ_LOAN','LOAN','READ',0),(97,'portfolio','CREATE_LOAN','LOAN','CREATE',1),(98,'portfolio','CREATE_LOAN_CHECKER','LOAN','CREATE',1),(99,'portfolio','UPDATE_LOAN','LOAN','UPDATE',1),(100,'portfolio','UPDATE_LOAN_CHECKER','LOAN','UPDATE',1),(101,'portfolio','DELETE_LOAN','LOAN','DELETE',1),(102,'portfolio','DELETE_LOAN_CHECKER','LOAN','DELETE',1),(103,'portfolio','READ_CLIENT','CLIENT','READ',0),(104,'portfolio','CREATE_CLIENT','CLIENT','CREATE',1),(105,'portfolio','CREATE_CLIENT_CHECKER','CLIENT','CREATE',1),(106,'portfolio','UPDATE_CLIENT','CLIENT','UPDATE',1),(107,'portfolio','UPDATE_CLIENT_CHECKER','CLIENT','UPDATE',1),(108,'portfolio','DELETE_CLIENT','CLIENT','DELETE',1),(109,'portfolio','DELETE_CLIENT_CHECKER','CLIENT','DELETE',1),(110,'portfolio','READ_CLIENTIMAGE','CLIENTIMAGE','READ',0),(111,'portfolio','CREATE_CLIENTIMAGE','CLIENTIMAGE','CREATE',1),(112,'portfolio','CREATE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','CREATE',1),(113,'portfolio','DELETE_CLIENTIMAGE','CLIENTIMAGE','DELETE',1),(114,'portfolio','DELETE_CLIENTIMAGE_CHECKER','CLIENTIMAGE','DELETE',1),(115,'portfolio','READ_CLIENTNOTE','CLIENTNOTE','READ',0),(116,'portfolio','CREATE_CLIENTNOTE','CLIENTNOTE','CREATE',1),(117,'portfolio','CREATE_CLIENTNOTE_CHECKER','CLIENTNOTE','CREATE',1),(118,'portfolio','UPDATE_CLIENTNOTE','CLIENTNOTE','UPDATE',1),(119,'portfolio','UPDATE_CLIENTNOTE_CHECKER','CLIENTNOTE','UPDATE',1),(120,'portfolio','DELETE_CLIENTNOTE','CLIENTNOTE','DELETE',1),(121,'portfolio','DELETE_CLIENTNOTE_CHECKER','CLIENTNOTE','DELETE',1),(122,'portfolio','READ_CLIENTIDENTIFIER','CLIENTIDENTIFIER','READ',0),(123,'portfolio','CREATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','CREATE',1),(124,'portfolio','CREATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','CREATE',1),(125,'portfolio','UPDATE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','UPDATE',1),(126,'portfolio','UPDATE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','UPDATE',1),(127,'portfolio','DELETE_CLIENTIDENTIFIER','CLIENTIDENTIFIER','DELETE',1),(128,'portfolio','DELETE_CLIENTIDENTIFIER_CHECKER','CLIENTIDENTIFIER','DELETE',1),(129,'portfolio','READ_DOCUMENT','DOCUMENT','READ',0),(130,'portfolio','CREATE_DOCUMENT','DOCUMENT','CREATE',1),(131,'portfolio','CREATE_DOCUMENT_CHECKER','DOCUMENT','CREATE',1),(132,'portfolio','UPDATE_DOCUMENT','DOCUMENT','UPDATE',1),(133,'portfolio','UPDATE_DOCUMENT_CHECKER','DOCUMENT','UPDATE',1),(134,'portfolio','DELETE_DOCUMENT','DOCUMENT','DELETE',1),(135,'portfolio','DELETE_DOCUMENT_CHECKER','DOCUMENT','DELETE',1),(136,'portfolio','READ_GROUP','GROUP','READ',0),(137,'portfolio','CREATE_GROUP','GROUP','CREATE',1),(138,'portfolio','CREATE_GROUP_CHECKER','GROUP','CREATE',1),(139,'portfolio','UPDATE_GROUP','GROUP','UPDATE',1),(140,'portfolio','UPDATE_GROUP_CHECKER','GROUP','UPDATE',1),(141,'portfolio','DELETE_GROUP','GROUP','DELETE',1),(142,'portfolio','DELETE_GROUP_CHECKER','GROUP','DELETE',1),(143,'portfolio','CREATE_LOANCHARGE','LOANCHARGE','CREATE',1),(144,'portfolio','CREATE_LOANCHARGE_CHECKER','LOANCHARGE','CREATE',1),(145,'portfolio','UPDATE_LOANCHARGE','LOANCHARGE','UPDATE',1),(146,'portfolio','UPDATE_LOANCHARGE_CHECKER','LOANCHARGE','UPDATE',1),(147,'portfolio','DELETE_LOANCHARGE','LOANCHARGE','DELETE',1),(148,'portfolio','DELETE_LOANCHARGE_CHECKER','LOANCHARGE','DELETE',1),(149,'portfolio','WAIVE_LOANCHARGE','LOANCHARGE','WAIVE',1),(150,'portfolio','WAIVE_LOANCHARGE_CHECKER','LOANCHARGE','WAIVE',1),(151,'portfolio','READ_DEPOSITACCOUNT','DEPOSITACCOUNT','READ',0),(152,'portfolio','CREATE_DEPOSITACCOUNT','DEPOSITACCOUNT','CREATE',1),(153,'portfolio','CREATE_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','CREATE',1),(154,'portfolio','UPDATE_DEPOSITACCOUNT','DEPOSITACCOUNT','UPDATE',1),(155,'portfolio','UPDATE_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','UPDATE',1),(156,'portfolio','DELETE_DEPOSITACCOUNT','DEPOSITACCOUNT','DELETE',1),(157,'portfolio','DELETE_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','DELETE',1),(158,'portfolio','READ_SAVINGSACCOUNT','SAVINGSACCOUNT','READ',0),(159,'portfolio','CREATE_SAVINGSACCOUNT','SAVINGSACCOUNT','CREATE',1),(160,'portfolio','CREATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','CREATE',1),(161,'portfolio','UPDATE_SAVINGSACCOUNT','SAVINGSACCOUNT','UPDATE',1),(162,'portfolio','UPDATE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','UPDATE',1),(163,'portfolio','DELETE_SAVINGSACCOUNT','SAVINGSACCOUNT','DELETE',1),(164,'portfolio','DELETE_SAVINGSACCOUNT_CHECKER','SAVINGSACCOUNT','DELETE',1),(165,'portfolio','READ_GUARANTOR','GUARANTOR','READ',0),(166,'portfolio','CREATE_GUARANTOR','GUARANTOR','CREATE',1),(167,'portfolio','CREATE_GUARANTOR_CHECKER','GUARANTOR','CREATE',1),(168,'portfolio','UPDATE_GUARANTOR','GUARANTOR','UPDATE',1),(169,'portfolio','UPDATE_GUARANTOR_CHECKER','GUARANTOR','UPDATE',1),(170,'portfolio','DELETE_GUARANTOR','GUARANTOR','DELETE',1),(171,'portfolio','DELETE_GUARANTOR_CHECKER','GUARANTOR','DELETE',1),(172,'transaction_loan','APPROVE_LOAN','LOAN','APPROVE',1),(173,'transaction_loan','APPROVEINPAST_LOAN','LOAN','APPROVEINPAST',1),(174,'transaction_loan','REJECT_LOAN','LOAN','REJECT',1),(175,'transaction_loan','REJECTINPAST_LOAN','LOAN','REJECTINPAST',1),(176,'transaction_loan','WITHDRAW_LOAN','LOAN','WITHDRAW',1),(177,'transaction_loan','WITHDRAWINPAST_LOAN','LOAN','WITHDRAWINPAST',1),(178,'transaction_loan','APPROVALUNDO_LOAN','LOAN','APPROVALUNDO',1),(179,'transaction_loan','DISBURSE_LOAN','LOAN','DISBURSE',1),(180,'transaction_loan','DISBURSEINPAST_LOAN','LOAN','DISBURSEINPAST',1),(181,'transaction_loan','DISBURSALUNDO_LOAN','LOAN','DISBURSALUNDO',1),(182,'transaction_loan','REPAYMENT_LOAN','LOAN','REPAYMENT',1),(183,'transaction_loan','REPAYMENTINPAST_LOAN','LOAN','REPAYMENTINPAST',1),(184,'transaction_loan','BULKREASSIGN_LOAN','LOAN','BULKREASSIGN',1),(185,'transaction_loan','ADJUST_LOAN','LOAN','ADJUST',1),(186,'transaction_loan','WAIVEINTERESTPORTION_LOAN','LOAN','WAIVEINTERESTPORTION',1),(187,'transaction_loan','WRITEOFF_LOAN','LOAN','WRITEOFF',1),(188,'transaction_loan','CLOSE_LOAN','LOAN','CLOSE',1),(189,'transaction_loan','CLOSEASRESCHEDULED_LOAN','LOAN','CLOSEASRESCHEDULED',1),(190,'transaction_loan','APPROVE_LOAN_CHECKER','LOAN','APPROVE',1),(191,'transaction_loan','APPROVEINPAST_LOAN_CHECKER','LOAN','APPROVEINPAST',1),(192,'transaction_loan','REJECT_LOAN_CHECKER','LOAN','REJECT',1),(193,'transaction_loan','REJECTINPAST_LOAN_CHECKER','LOAN','REJECTINPAST',1),(194,'transaction_loan','WITHDRAW_LOAN_CHECKER','LOAN','WITHDRAW',1),(195,'transaction_loan','WITHDRAWINPAST_LOAN_CHECKER','LOAN','WITHDRAWINPAST',1),(196,'transaction_loan','APPROVALUNDO_LOAN_CHECKER','LOAN','APPROVALUNDO',1),(197,'transaction_loan','DISBURSE_LOAN_CHECKER','LOAN','DISBURSE',1),(198,'transaction_loan','DISBURSEINPAST_LOAN_CHECKER','LOAN','DISBURSEINPAST',1),(199,'transaction_loan','DISBURSALUNDO_LOAN_CHECKER','LOAN','DISBURSALUNDO',1),(200,'transaction_loan','REPAYMENT_LOAN_CHECKER','LOAN','REPAYMENT',1),(201,'transaction_loan','REPAYMENTINPAST_LOAN_CHECKER','LOAN','REPAYMENTINPAST',1),(202,'transaction_loan','BULKREASSIGN_LOAN_CHECKER','LOAN','BULKREASSIGN',1),(203,'transaction_loan','ADJUST_LOAN_CHECKER','LOAN','ADJUST',1),(204,'transaction_loan','WAIVEINTERESTPORTION_LOAN_CHECKER','LOAN','WAIVEINTERESTPORTION',1),(205,'transaction_loan','WRITEOFF_LOAN_CHECKER','LOAN','WRITEOFF',1),(206,'transaction_loan','CLOSE_LOAN_CHECKER','LOAN','CLOSE',1),(207,'transaction_loan','CLOSEASRESCHEDULED_LOAN_CHECKER','LOAN','CLOSEASRESCHEDULED',1),(208,'transaction_deposit','APPROVE_DEPOSITACCOUNT','DEPOSITACCOUNT','APPROVE',1),(209,'transaction_deposit','REJECT_DEPOSITACCOUNT','DEPOSITACCOUNT','REJECT',1),(210,'transaction_deposit','WITHDRAW_DEPOSITACCOUNT','DEPOSITACCOUNT','WITHDRAW',1),(211,'transaction_deposit','APPROVALUNDO_DEPOSITACCOUNT','DEPOSITACCOUNT','APPROVALUNDO',1),(212,'transaction_deposit','WITHDRAWAL_DEPOSITACCOUNT','DEPOSITACCOUNT','WITHDRAWAL',1),(213,'transaction_deposit','INTEREST_DEPOSITACCOUNT','DEPOSITACCOUNT','INTEREST',1),(214,'transaction_deposit','RENEW_DEPOSITACCOUNT','DEPOSITACCOUNT','RENEW',1),(215,'transaction_deposit','APPROVE_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','APPROVE',1),(216,'transaction_deposit','REJECT_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','REJECT',1),(217,'transaction_deposit','WITHDRAW_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','WITHDRAW',1),(218,'transaction_deposit','APPROVALUNDO_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','APPROVALUNDO',1),(219,'transaction_deposit','WITHDRAWAL_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','WITHDRAWAL',1),(220,'transaction_deposit','INTEREST_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','INTEREST',1),(221,'transaction_deposit','RENEW_DEPOSITACCOUNT_CHECKER','DEPOSITACCOUNT','RENEW',1),(222,'report','READ_Active Loans Portfolio Status','Active Loans Portfolio Status','READ',1),(223,'report','READ_Active Loans Summary per Branch','Active Loans Summary per Branch','READ',1),(224,'report','READ_Balance Sheet','Balance Sheet','READ',1),(225,'report','READ_Client Listing','Client Listing','READ',1),(226,'report','READ_Client Loans Listing','Client Loans Listing','READ',1),(227,'report','READ_Funds Disbursed Between Dates Summary','Funds Disbursed Between Dates Summary','READ',1),(228,'report','READ_Funds Disbursed Between Dates Summary by Office','Funds Disbursed Between Dates Summary by Office','READ',1),(229,'report','READ_Income Statement','Income Statement','READ',1),(230,'report','READ_Loans Awaiting Disbursal','Loans Awaiting Disbursal','READ',1),(231,'report','READ_Loans Awaiting Disbursal Summary','Loans Awaiting Disbursal Summary','READ',1),(232,'report','READ_Loans Awaiting Disbursal Summary by Month','Loans Awaiting Disbursal Summary by Month','READ',1),(233,'report','READ_Portfolio at Risk','Portfolio at Risk','READ',1),(234,'report','READ_Portfolio at Risk by Branch','Portfolio at Risk by Branch','READ',1),(235,'report','READ_Trial Balance','Trial Balance','READ',1);
+/*!40000 ALTER TABLE `m_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_portfolio_command_source`
+--
+
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_portfolio_command_source`
+--
+
+LOCK TABLES `m_portfolio_command_source` WRITE;
+/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_portfolio_command_source` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_deposit`
+--
+
+DROP TABLE IF EXISTS `m_product_deposit`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_deposit` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `external_id` varchar(100) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `minimum_balance` decimal(19,6) DEFAULT NULL,
+  `maximum_balance` decimal(19,6) DEFAULT NULL,
+  `tenure_months` int(11) NOT NULL,
+  `interest_compounded_every` smallint(5) NOT NULL DEFAULT '1',
+  `interest_compounded_every_period_enum` smallint(5) NOT NULL DEFAULT '2',
+  `maturity_default_interest_rate` decimal(19,6) NOT NULL,
+  `maturity_min_interest_rate` decimal(19,6) NOT NULL,
+  `maturity_max_interest_rate` decimal(19,6) NOT NULL,
+  `is_compounding_interest_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `is_renewal_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `is_preclosure_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `pre_closure_interest_rate` decimal(19,6) NOT NULL,
+  `is_lock_in_period_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `lock_in_period` bigint(20) DEFAULT NULL,
+  `lock_in_period_type` smallint(5) NOT NULL DEFAULT '2',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_deposit_product` (`name`),
+  UNIQUE KEY `externalid_deposit_product` (`external_id`),
+  KEY `FKJPW0000000000003` (`createdby_id`),
+  KEY `FKJPW0000000000004` (`lastmodifiedby_id`),
+  CONSTRAINT `FKJPX0000000000003` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FKJPX0000000000004` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_deposit`
+--
+
+LOCK TABLES `m_product_deposit` WRITE;
+/*!40000 ALTER TABLE `m_product_deposit` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_deposit` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan`
+--
+
+DROP TABLE IF EXISTS `m_product_loan`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan`
+--
+
+LOCK TABLES `m_product_loan` WRITE;
+/*!40000 ALTER TABLE `m_product_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_loan_charge`
+--
+
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_loan_charge`
+--
+
+LOCK TABLES `m_product_loan_charge` WRITE;
+/*!40000 ALTER TABLE `m_product_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_charge` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_product_savings`
+--
+
+DROP TABLE IF EXISTS `m_product_savings`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_product_savings` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `currency_code` varchar(3) DEFAULT NULL,
+  `currency_digits` smallint(5) DEFAULT NULL,
+  `interest_rate` decimal(19,6) DEFAULT NULL,
+  `min_interest_rate` decimal(19,6) DEFAULT NULL,
+  `max_interest_rate` decimal(19,6) DEFAULT NULL,
+  `savings_deposit_amount` decimal(19,6) NOT NULL,
+  `savings_product_type` smallint(5) DEFAULT NULL,
+  `tenure_type` smallint(5) DEFAULT NULL,
+  `deposit_every` bigint(20) DEFAULT NULL,
+  `tenure` int(11) DEFAULT NULL,
+  `frequency` int(11) DEFAULT NULL,
+  `interest_type` smallint(5) DEFAULT NULL,
+  `interest_calculation_method` smallint(5) DEFAULT NULL,
+  `min_bal_for_withdrawal` decimal(19,6) NOT NULL,
+  `is_partial_deposit_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `is_lock_in_period_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `lock_in_period` bigint(20) DEFAULT NULL,
+  `lock_in_period_type` smallint(5) NOT NULL DEFAULT '1',
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKJPW0000000000003` (`createdby_id`),
+  KEY `FKJPW0000000000004` (`lastmodifiedby_id`),
+  CONSTRAINT `FKJPW0000000000003` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FKJPW0000000000004` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_product_savings`
+--
+
+LOCK TABLES `m_product_savings` WRITE;
+/*!40000 ALTER TABLE `m_product_savings` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_savings` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role`
+--
+
+DROP TABLE IF EXISTS `m_role`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role`
+--
+
+LOCK TABLES `m_role` WRITE;
+/*!40000 ALTER TABLE `m_role` DISABLE KEYS */;
+INSERT INTO `m_role` VALUES (1,'Super user','This role provides all application permissions.');
+/*!40000 ALTER TABLE `m_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_role_permission`
+--
+
+DROP TABLE IF EXISTS `m_role_permission`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_role_permission`
+--
+
+LOCK TABLES `m_role_permission` WRITE;
+/*!40000 ALTER TABLE `m_role_permission` DISABLE KEYS */;
+INSERT INTO `m_role_permission` VALUES (1,1);
+/*!40000 ALTER TABLE `m_role_permission` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_saving_account`
+--
+
+DROP TABLE IF EXISTS `m_saving_account`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_saving_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `status_enum` smallint(5) NOT NULL DEFAULT '0',
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) NOT NULL,
+  `product_id` bigint(20) NOT NULL,
+  `deposit_amount_per_period` decimal(19,6) NOT NULL,
+  `savings_product_type` smallint(5) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `total_deposit_amount` decimal(19,6) NOT NULL,
+  `reccuring_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `regular_saving_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `tenure` int(11) NOT NULL,
+  `tenure_type` smallint(5) DEFAULT NULL,
+  `deposit_every` bigint(20) DEFAULT NULL,
+  `frequency` int(11) DEFAULT NULL,
+  `interest_posting_every` int(11) DEFAULT NULL,
+  `interest_posting_frequency` int(11) DEFAULT NULL,
+  `interest_type` smallint(5) DEFAULT NULL,
+  `interest_calculation_method` smallint(5) DEFAULT NULL,
+  `projected_commencement_date` date NOT NULL,
+  `actual_commencement_date` date DEFAULT NULL,
+  `matures_on_date` datetime DEFAULT NULL,
+  `projected_interest_accrued_on_maturity` decimal(19,6) NOT NULL,
+  `actual_interest_accrued` decimal(19,6) DEFAULT NULL,
+  `projected_total_maturity_amount` decimal(19,6) NOT NULL,
+  `actual_total_amount` decimal(19,6) DEFAULT NULL,
+  `is_preclosure_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `pre_closure_interest_rate` decimal(19,6) NOT NULL,
+  `outstanding_amount` decimal(19,6) NOT NULL,
+  `interest_posted_amount` decimal(19,6) DEFAULT '0.000000',
+  `last_interest_posted_date` date DEFAULT NULL,
+  `next_interest_posting_date` date DEFAULT NULL,
+  `is_lock_in_period_allowed` tinyint(1) NOT NULL DEFAULT '0',
+  `lock_in_period` bigint(20) DEFAULT NULL,
+  `lock_in_period_type` smallint(5) NOT NULL DEFAULT '1',
+  `withdrawnon_date` datetime DEFAULT NULL,
+  `rejectedon_date` datetime DEFAULT NULL,
+  `closedon_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `deposit_acc_external_id` (`external_id`),
+  KEY `FKSA0000000000001` (`client_id`),
+  KEY `FKSA0000000000002` (`product_id`),
+  CONSTRAINT `FKSA0000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA0000000000002` FOREIGN KEY (`product_id`) REFERENCES `m_product_savings` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_saving_account`
+--
+
+LOCK TABLES `m_saving_account` WRITE;
+/*!40000 ALTER TABLE `m_saving_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_saving_account` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_saving_account_transaction`
+--
+
+DROP TABLE IF EXISTS `m_saving_account_transaction`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_saving_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `saving_account_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `contra_id` bigint(20) DEFAULT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`saving_account_id`),
+  KEY `FKSAT0000000002` (`contra_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`saving_account_id`) REFERENCES `m_saving_account` (`id`),
+  CONSTRAINT `FKSAT0000000002` FOREIGN KEY (`contra_id`) REFERENCES `m_saving_account_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_saving_account_transaction`
+--
+
+LOCK TABLES `m_saving_account_transaction` WRITE;
+/*!40000 ALTER TABLE `m_saving_account_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_saving_account_transaction` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_saving_schedule`
+--
+
+DROP TABLE IF EXISTS `m_saving_schedule`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_saving_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `saving_account_id` bigint(20) NOT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `deposit` decimal(21,4) NOT NULL,
+  `payment_date` date DEFAULT NULL,
+  `deposit_paid` decimal(21,4) DEFAULT NULL,
+  `interest_accured` decimal(21,4) DEFAULT '0.0000',
+  `completed_derived` bit(1) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSS00000000001` (`saving_account_id`),
+  CONSTRAINT `FKSS00000000001` FOREIGN KEY (`saving_account_id`) REFERENCES `m_saving_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_saving_schedule`
+--
+
+LOCK TABLES `m_saving_schedule` WRITE;
+/*!40000 ALTER TABLE `m_saving_schedule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_saving_schedule` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `m_staff`
+--
+
+DROP TABLE IF EXISTS `m_staff`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `m_staff`
+--
+
+LOCK TABLES `m_staff` WRITE;
+/*!40000 ALTER TABLE `m_staff` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_staff` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `r_enum_value`
+--
+
+DROP TABLE IF EXISTS `r_enum_value`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `r_enum_value`
+--
+
+LOCK TABLES `r_enum_value` WRITE;
+/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
+INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years');
+/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `ref_loan_transaction_processing_strategy`
+--
+
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `ref_loan_transaction_processing_strategy`
+--
+
+LOCK TABLES `ref_loan_transaction_processing_strategy` WRITE;
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` DISABLE KEYS */;
+INSERT INTO `ref_loan_transaction_processing_strategy` VALUES (1,'mifos-standard-strategy','Mifos style',NULL,NULL,NULL,NULL),(2,'heavensfamily-strategy','Heavensfamily',NULL,NULL,NULL,NULL),(3,'creocore-strategy','Creocore',NULL,NULL,NULL,NULL),(4,'rbi-india-strategy','RBI (India)',NULL,NULL,NULL,NULL);
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `rpt_sequence`
+--
+
+DROP TABLE IF EXISTS `rpt_sequence`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `rpt_sequence`
+--
+
+LOCK TABLES `rpt_sequence` WRITE;
+/*!40000 ALTER TABLE `rpt_sequence` DISABLE KEYS */;
+/*!40000 ALTER TABLE `rpt_sequence` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_parameter` (
+  `parameter_id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  PRIMARY KEY (`parameter_id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_parameter`
+--
+
+LOCK TABLES `stretchy_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_parameter` VALUES (1,'FullReportList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\r\n  rp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on rp.report_id = r.report_id\r\n  left join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on rp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where ur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by r.report_category, r.report_name, rp.parameter_id'),(2,'FullParameterList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select parameter_name, parameter_variable, parameter_label, parameter_displayType, \r\nparameter_FormatType, parameter_default, selectOne,  selectAll\r\nfrom stretchy_parameter p\r\nwhere special is null\r\norder by parameter_id'),(3,'reportCategoryList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\r\n  rp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on rp.report_id = r.report_id\r\n  left join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.report_category = \'${reportCategory}\'\r\n  and r.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on rp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where ur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by r.report_category, r.report_name, rp.parameter_id'),(5,'OfficeIdSelectOne','officeId','Office','select','number','0',NULL,'Y',NULL,'select id, \r\nconcat(substring(\"........................................\", 1, \r\n   ((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy'),(6,'loanOfficerIdSelectAll','loanOfficerId','Loan Officer','select','number','0',NULL,'Y','Y','(select id, display_name as `Name` from m_staff\nwhere is_loan_officer = true)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2'),(10,'currencyIdSelectAll','currencyId','Currency','select','number','0',NULL,'Y','Y','select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`'),(11,'currencyIdSelectOne','currencyId','Currency','select','number','0',NULL,'Y',NULL,'select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`'),(20,'fundIdSelectAll','fundId','Fund','select','number','0',NULL,'Y','Y','(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2'),(25,'loanProductIdSelectAll','loanProductId','Product','select','number','0',NULL,'Y','Y','select id, `name`\r\nfrom m_product_loan\r\norder by 2'),(40,'startDateSelect','startDate','startDate','date','date','today',NULL,NULL,NULL,NULL),(41,'endDateSelect','endDate','endDate','date','date','today',NULL,NULL,NULL,NULL);
+/*!40000 ALTER TABLE `stretchy_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report`
+--
+
+DROP TABLE IF EXISTS `stretchy_report`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report` (
+  `report_id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`report_id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report`
+--
+
+LOCK TABLES `stretchy_report` WRITE;
+/*!40000 ALTER TABLE `stretchy_report` DISABLE KEYS */;
+INSERT INTO `stretchy_report` VALUES (1,'Client Listing','Table',NULL,'Client','select ounder.`name` as \"Office/Branch\", c.account_no as \"Client Account No.\",  \r\nc.display_name as \"Name\",  c.joined_date as \"Joined\", c.external_id as \"External Id\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\nwhere o.id = ${officeId}\r\nand c.is_deleted=0\r\norder by ounder.hierarchy, c.account_no','Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this report and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).',1,1),(2,'Client Loans Listing','Table',NULL,'Client','select ounder.`name` as \"Office/Branch\", c.account_no as \"Client Account No.\", \r\nc.display_name as \"Name\", \r\nlo.display_name as \"Loan Officer\", l.account_no as \"Loan Account No.\", l.external_id as \"External Id\", \r\np.name as Loan, st.enum_message_property as \"Status\",  \r\nf.`name` as Fund,\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount,\r\nl.arrearstolerance_amount as \"Arrears Tolerance Amount\",\r\nl.number_of_repayments as \"Expected No. Repayments\",\r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\nl.nominal_interest_rate_per_period as \"Nominal Interest Rate Per Period\",\r\n\r\nipf.enum_message_property as \"Interest Rate Frequency\",\r\nim.enum_message_property as \"Interest Method\",\r\nicp.enum_message_property as \"Interest Calculated in Period\",\r\nl.term_frequency as \"Term Frequency\",\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.repay_every as \"Repayment Frequency\",\r\nrf.enum_message_property as \"Repayment Frequency Period\",\r\nam.enum_message_property as \"Amortization\",\r\n\r\nl.total_charges_due_at_disbursement_derived as \"Total Charges Due At Disbursement\",\r\n\r\ndate( l.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As \"Expected Disbursal\",\r\ndate(l.expected_firstrepaymenton_date) as \"Expected First Repayment\", date(l.interest_calculated_from_date) as \"Interest Calculated From\" ,\r\ndate(l.disbursedon_date) as Disbursed, date(l.expected_maturedon_date) \"Expected Maturity\",\r\ndate(l.maturedon_date) as \"Matured On\", date(l.closedon_date) as Closed,\r\ndate(l.rejectedon_date) as Rejected, date(l.rescheduledon_date) as Rescheduled, \r\ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) \"Written Off\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\nleft join m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join r_enum_value st on st.enum_name = \"loan_status_id\" and st.enum_id = l.loan_status_id\r\nleft join r_enum_value ipf on ipf.enum_name = \"interest_period_frequency_enum\" and ipf.enum_id = l.interest_period_frequency_enum\r\nleft join r_enum_value im on im.enum_name = \"interest_method_enum\" and im.enum_id = l.interest_method_enum\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nleft join r_enum_value icp on icp.enum_name = \"interest_calculated_in_period_enum\" and icp.enum_id = l.interest_calculated_in_period_enum\r\nleft join r_enum_value rf on rf.enum_name = \"repayment_period_frequency_enum\" and rf.enum_id = l.repayment_period_frequency_enum\r\nleft join r_enum_value am on am.enum_name = \"amortization_method_enum\" and am.enum_id = l.amortization_method_enum\r\n\r\nleft join m_currency cur on cur.code = l.currency_code\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\norder by ounder.hierarchy, 2 , l.id','Individual Client Report\r\n\r\nPretty wide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  There is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).',1,1),(5,'Loans Awaiting Disbursal','Table',NULL,'Loan Portfolio','SELECT ounder.`name` as \"Office/Branch\", lo.display_name as \"Loan Officer\", c.display_name as \"Name\", \r\nl.account_no as \"Loan Account No.\", pl.`name` as \"Product\",  f.`name` as Fund,\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\ndate(l.approvedon_date) \"Approved\", l.expected_disbursedon_date \"Expected Disbursal\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, l.expected_disbursedon_date,  c.display_name','Individual Client Report',1,1),(6,'Loans Awaiting Disbursal Summary','Table',NULL,'Loan Portfolio','SELECT ounder.`name` as \"Office/Branch\",  pl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`','Individual Client Report',1,1),(7,'Loans Awaiting Disbursal Summary by Month','Table',NULL,'Loan Portfolio','SELECT ounder.`name` as \"Office/Branch\",  pl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as \"Year\", monthname(l.expected_disbursedon_date) as \"Month\",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)','Individual Client Report',1,1),(10,'Active Loans Portfolio Status','Table',NULL,'Loan','select ounder.`name` as \"Office/Branch\", lo.display_name as \"Loan Officer\", c.display_name as \"Name\", \r\np.`name` as Loan, f.`name` as Fund, l.account_no as \"Loan Account No\",\r\nl.disbursedon_date as Disbursed, ifnull(cur.display_symbol, l.currency_code) as Currency,\r\nsum(r.principal_amount - ifnull(r.principal_completed_derived, 0)) as \"Principal Outstanding\",\r\nsum(r.interest_amount - ifnull(r.interest_completed_derived, 0)) as \"Interest Outstanding\",\r\n\r\nif(datediff(curdate(), min(r.duedate)) < 0, 0, datediff(curdate(), min(r.duedate))) as \"Days Overdue\",   \r\nmin(r.installment) as \"First Overdue Installment\",\r\nmin(r.duedate) as \"First Overdue Installment Date\",\r\nsum(if(r.duedate <= curdate(), \r\n        (r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 0)) as \"Principal Overdue\",\r\nsum(if(r.duedate <= curdate(), \r\n        (ifnull(r.interest_amount, 0) - ifnull(r.interest_completed_derived, 0))\r\n            , 0)) as \"Interest Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\n                                        and r.completed_derived is false\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, p.`name`, l.currency_code, c.display_name,  l.account_no','Individual Client Report',1,1),(11,'Active Loans Summary per Branch','Table',NULL,'Loan Portfolio','select ounder.`name` as \"Office/Branch\", ifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as \"No. of Clients\", count(distinct(l.id)) as \"No. of Active Loans\",\r\ncount(distinct(\r\n		  if(r.duedate <= curdate(), \r\n			    if(r.principal_amount - ifnull(r.principal_completed_derived, 0) > 0, l.id, null), null)\r\n			  )) as \"No. of Loans in Arrears\",\r\n\r\nsum(l.principal_amount) as \"Total Loans Disbursed\",\r\nsum(ifnull(r.principal_completed_derived, 0)) as \"Total Principal Repaid\",\r\nsum(ifnull(r.interest_completed_derived, 0)) as \"Total Interest Repaid\",\r\nsum(r.principal_amount - ifnull(r.principal_completed_derived, 0)) as \"Total Principal Outstanding\",\r\nsum(ifnull(r.interest_amount, 0) - ifnull(r.interest_completed_derived, 0)) as \"Total Interest Outstanding\",\r\nsum(if(r.duedate <= curdate(), \r\n        (r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 0)) as \"Total Principal in Arrears\",\r\ncast(round(\r\n    (sum(if(r.duedate <= curdate(), \r\n        (r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 0)) * 100) / \r\n            sum(r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 2) as char)\r\n            as \"Portfolio at Risk %\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand l.loan_status_id = 300\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code',NULL,1,1),(15,'Portfolio at Risk','Table',NULL,'Loan Portfolio','select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(r.principal_amount - ifnull(r.principal_completed_derived, 0)) as \"Principal Outstanding\",\r\nsum(if(r.duedate <= curdate(), \r\n        (r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 0)) as \"Principal Overdue\",\r\n            \r\n    cast(round(\r\n    (sum(if(r.duedate <= curdate(), \r\n        (r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 0)) * 100) / \r\n            sum(r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 2) as char)\r\n            as \"Portfolio at Risk %\"\r\n            \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\n                                        and r.completed_derived is false\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code',NULL,1,1),(16,'Portfolio at Risk by Branch','Table',NULL,'Loan Portfolio','select  concat(substring(\"........................................\", 1, \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(r.principal_amount - ifnull(r.principal_completed_derived, 0)) as \"Principal Outstanding\",\r\nsum(if(r.duedate <= curdate(), \r\n        (r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 0)) as \"Principal Overdue\",\r\n            \r\n    cast(round(\r\n    (sum(if(r.duedate <= curdate(), \r\n        (r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 0)) * 100) / \r\n            sum(r.principal_amount - ifnull(r.principal_completed_derived, 0))\r\n            , 2) as char)\r\n            as \"Portfolio at Risk %\"\r\n            \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\n                                        and r.completed_derived is false\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code',NULL,1,1),(20,'Funds Disbursed Between Dates Summary','Table',NULL,'Fund','select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(21,'Funds Disbursed Between Dates Summary by Office','Table',NULL,'Fund','select ounder.`name` as \"Office/Branch\", ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(48,'Balance Sheet','Pentaho',NULL,'Accounting',NULL,'Balance Sheet',1,0),(49,'Income Statement','Pentaho',NULL,'Accounting',NULL,'Profit and Loss Statement',1,0),(50,'Trial Balance','Pentaho',NULL,'Accounting',NULL,'Trial Balance Report',1,0);
+/*!40000 ALTER TABLE `stretchy_report` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `stretchy_report_parameter`
+--
+
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stretchy_report_parameter` (
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`report_id`,`parameter_id`),
+  UNIQUE KEY `report_id_name_UNIQUE` (`report_id`,`report_parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `stretchy_report_parameter`
+--
+
+LOCK TABLES `stretchy_report_parameter` WRITE;
+/*!40000 ALTER TABLE `stretchy_report_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_report_parameter` VALUES (1,5,NULL),(2,5,NULL),(2,6,NULL),(2,10,NULL),(2,20,NULL),(2,25,NULL),(5,5,NULL),(5,6,NULL),(5,10,NULL),(5,20,NULL),(5,25,NULL),(6,5,NULL),(6,6,NULL),(6,10,NULL),(6,20,NULL),(6,25,NULL),(7,5,NULL),(7,6,NULL),(7,10,NULL),(7,20,NULL),(7,25,NULL),(10,5,NULL),(10,6,NULL),(10,10,NULL),(10,20,NULL),(10,25,NULL),(11,5,NULL),(11,10,NULL),(15,5,NULL),(15,6,NULL),(15,10,NULL),(15,20,NULL),(15,25,NULL),(16,5,NULL),(16,6,NULL),(16,10,NULL),(16,20,NULL),(16,25,NULL),(20,10,NULL),(20,20,NULL),(20,40,NULL),(20,41,NULL),(21,5,NULL),(21,10,NULL),(21,20,NULL),(21,40,NULL),(21,41,NULL),(48,5,'branch'),(48,41,'date'),(49,5,'branch'),(49,40,'fromDate'),(49,41,'toDate'),(50,5,'branch'),(50,40,'fromDate'),(50,41,'toDate');
+/*!40000 ALTER TABLE `stretchy_report_parameter` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `x_registered_table`
+--
+
+DROP TABLE IF EXISTS `x_registered_table`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `x_registered_table`
+--
+
+LOCK TABLES `x_registered_table` WRITE;
+/*!40000 ALTER TABLE `x_registered_table` DISABLE KEYS */;
+/*!40000 ALTER TABLE `x_registered_table` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-02-05 12:37:59
diff --git a/fineract-db/multi-tenant-demo-backups/latam-demo/datatables-on-latam-demo.sql b/fineract-db/multi-tenant-demo-backups/latam-demo/datatables-on-latam-demo.sql
new file mode 100644
index 0000000..e3e5259
--- /dev/null
+++ b/fineract-db/multi-tenant-demo-backups/latam-demo/datatables-on-latam-demo.sql
@@ -0,0 +1,49 @@
+DROP TABLE IF EXISTS `latam_extra_client_details`;
+CREATE TABLE `latam_extra_client_details` (
+  `client_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`client_id`),
+  CONSTRAINT `FK_latam_extra_client_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `latam_family_details`;
+CREATE TABLE `latam_family_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `Name` varchar(40) DEFAULT NULL,
+  `Date of Birth` date DEFAULT NULL,
+  `Points Score` int(11) DEFAULT NULL,
+  `Education_cd_Highest` int(11) DEFAULT NULL,
+  `Other Notes` text,
+  PRIMARY KEY (`id`),
+  KEY `FK_Extra Family Details Data_1` (`client_id`),
+  CONSTRAINT `FK_latam_family_details` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `latam_extra_loan_details`;
+CREATE TABLE `latam_extra_loan_details` (
+  `loan_id` bigint(20) NOT NULL,
+  `Business Description` varchar(100) DEFAULT NULL,
+  `Years in Business` int(11) DEFAULT NULL,
+  `Gender_cd` int(11) DEFAULT NULL,
+  `Education_cv` varchar(60) DEFAULT NULL,
+  `Next Visit` date DEFAULT NULL,
+  `Highest Rate Paid` decimal(19,6) DEFAULT NULL,
+  `Comment` text,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `FK_latam_extra_loan_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+INSERT INTO `mifostenant-latam`.`m_code` (`code_name`, `is_system_defined`) VALUES ('Gender', 1);
+INSERT INTO `mifostenant-latam`.`m_code_value` (`code_id`, `code_value`, `order_position`) VALUES (2, 'Male', 1); INSERT INTO `mifostenant-latam`.`m_code_value` (`code_id`, `code_value`, `order_position`) VALUES (2, 'Female', 2);
+
+
+INSERT INTO `mifostenant-latam`.`m_code` (`code_name`, `is_system_defined`) VALUES ('Education', 1);
+INSERT INTO `mifostenant-latam`.`m_code_value` (`code_id`, `code_value`, `order_position`) VALUES (3, 'Primary', 1); INSERT INTO `mifostenant-latam`.`m_code_value` (`code_id`, `code_value`, `order_position`) VALUES (3, 'Secondary', 2); INSERT INTO `mifostenant-latam`.`m_code_value` (`code_id`, `code_value`, `order_position`) VALUES (3, 'University', 3);
\ No newline at end of file
diff --git a/fineract-db/old-schema-files/0001a-mifosplatform-core-ddl-latest.sql b/fineract-db/old-schema-files/0001a-mifosplatform-core-ddl-latest.sql
new file mode 100644
index 0000000..6615c90
--- /dev/null
+++ b/fineract-db/old-schema-files/0001a-mifosplatform-core-ddl-latest.sql
@@ -0,0 +1,947 @@
+-- drop tables in base-schema
+SET foreign_key_checks = 0;
+
+-- drop accounting subsystem
+DROP TABLE IF EXISTS `acc_gl_account`;
+DROP TABLE IF EXISTS `acc_gl_closure`;
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+DROP TABLE IF EXISTS `acc_product_mapping`;
+
+-- drop portfolio subsystem
+DROP TABLE IF EXISTS `c_configuration`;
+DROP TABLE IF EXISTS `m_appuser`;
+DROP TABLE IF EXISTS `m_appuser_role`;
+DROP TABLE IF EXISTS `m_calendar`;
+DROP TABLE IF EXISTS `m_calendar_instance`;
+DROP TABLE IF EXISTS `m_charge`;
+DROP TABLE IF EXISTS `m_client`;
+DROP TABLE IF EXISTS `m_client_identifier`;
+DROP TABLE IF EXISTS `m_code`;
+DROP TABLE IF EXISTS `m_code_value`;
+DROP TABLE IF EXISTS `m_currency`;
+DROP TABLE IF EXISTS `m_deposit_account`;
+DROP TABLE IF EXISTS `m_deposit_account_transaction`;
+DROP TABLE IF EXISTS `m_document`;
+DROP TABLE IF EXISTS `m_fund`;
+DROP TABLE IF EXISTS `m_group`;
+DROP TABLE IF EXISTS `m_group_level`;
+DROP TABLE IF EXISTS `m_group_client`;
+DROP TABLE IF EXISTS `m_guarantor`;
+DROP TABLE IF EXISTS `m_loan`;
+DROP TABLE IF EXISTS `m_loan_charge`;
+DROP TABLE IF EXISTS `m_loan_arrears_aging`;
+DROP TABLE IF EXISTS `m_loan_collateral`;
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+DROP TABLE IF EXISTS `m_loan_transaction`;
+DROP TABLE IF EXISTS `m_note`;
+DROP TABLE IF EXISTS `m_office`;
+DROP TABLE IF EXISTS `m_office_transaction`;
+DROP TABLE IF EXISTS `m_organisation_currency`;
+DROP TABLE IF EXISTS `m_permission`;
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+DROP TABLE IF EXISTS `m_product_deposit`;
+DROP TABLE IF EXISTS `m_product_loan`;
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+DROP TABLE IF EXISTS `m_role`;
+DROP TABLE IF EXISTS `m_role_permission`;
+DROP TABLE IF EXISTS `m_savings_account`;
+DROP TABLE IF EXISTS `m_savings_account_transaction`;
+DROP TABLE IF EXISTS `m_savings_product`;
+DROP TABLE IF EXISTS `m_staff`;
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+DROP TABLE IF EXISTS `x_registered_table`;
+
+-- drop reporting related tables
+DROP TABLE IF EXISTS `r_enum_value`;
+DROP TABLE IF EXISTS `rpt_sequence`;
+DROP TABLE IF EXISTS `stretchy_parameter`;
+DROP TABLE IF EXISTS `stretchy_report`;
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+
+SET foreign_key_checks = 1;
+
+-- DDL for reference/lookup tables
+CREATE TABLE `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` TINYINT(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(50) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/*not a major table - just intended for database reporting use for enums and values that would be hidden in java*/
+CREATE TABLE `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* used to link MySql tables to Mifos X application tables for additional data needs */
+CREATE TABLE `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `m_calendar` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `title` varchar(50) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `m_calendar_instance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `entity_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ================= end of reference/lookup tables =============
+
+-- DDL for office related tables
+CREATE TABLE `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ============ end of office related tables ==========
+
+-- DDL for admin tables
+CREATE TABLE `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ================ end of user admin tables ===============
+
+-- DDL for organisation wide related concepts
+CREATE TABLE `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `subresource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `joining_date` date,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ============ end of organisation wide related tables ===========
+
+-- DDL client/group related tables
+CREATE TABLE `m_group_level` (
+`id` INT(11) NOT NULL AUTO_INCREMENT,
+`parent_id` INT(11) NULL DEFAULT NULL,
+`super_parent` TINYINT(1) NOT NULL,
+`level_name` VARCHAR(100) NOT NULL,
+`recursable` TINYINT(1) NOT NULL,
+`can_have_clients` TINYINT(1) NOT NULL,
+PRIMARY KEY (`id`),
+INDEX `Parent_levelId_reference` (`parent_id`),
+CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
+)ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `staff_id` BIGINT(20) DEFAULT NULL,
+  `parent_id` BIGINT(20) NULL DEFAULT NULL,
+  `level_Id` INT(11) NOT NULL,
+  `hierarchy` VARCHAR(100) NULL DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`, `level_id`),
+  UNIQUE KEY `external_id` (`external_id`, `level_Id`),
+  KEY `office_id` (`office_id`),
+  KEY `staff_id` (`staff_id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_Id`) REFERENCES `m_group_level` (`id`),
+  CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `image_key` varchar(500) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ==== end of client/group related tables ==========
+
+-- DDL for loan and loan related tables
+CREATE TABLE `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `loanpurpose_cv_id` int(11) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `disbursedon_userid` bigint(20) DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `principal_disbursed_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `rescheduledon_date` date DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `writtenoffon_date` date DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loanpurpose_codevalue` (`loanpurpose_cv_id`),
+  KEY `FK_submittedon_userid` (`submittedon_userid`),
+  KEY `FK_approvedon_userid` (`approvedon_userid`),
+  KEY `FK_rejectedon_userid` (`rejectedon_userid`),
+  KEY `FK_withdrawnon_userid` (`withdrawnon_userid`),
+  KEY `FK_disbursedon_userid` (`disbursedon_userid`),
+  KEY `FK_closedon_userid` (`closedon_userid`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_approvedon_userid` FOREIGN KEY (`approvedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_closedon_userid` FOREIGN KEY (`closedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_disbursedon_userid` FOREIGN KEY (`disbursedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loanpurpose_codevalue` FOREIGN KEY (`loanpurpose_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_rejectedon_userid` FOREIGN KEY (`rejectedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_submittedon_userid` FOREIGN KEY (`submittedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_withdrawnon_userid` FOREIGN KEY (`withdrawnon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_arrears_aging` (
+  `loan_id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `principal_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `overdue_since_date_derived` date DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_arrears_aging_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_guarantor` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_id` BIGINT(20) NOT NULL,
+        `client_reln_cv_id` INT(11) DEFAULT NULL,
+	`type_enum` SMALLINT(5) NOT NULL,
+	`entity_id` BIGINT(20) NULL DEFAULT NULL,
+	`firstname` VARCHAR(50) NULL DEFAULT NULL,
+	`lastname` VARCHAR(50) NULL DEFAULT NULL,
+	`dob` DATE NULL DEFAULT NULL,
+	`address_line_1` VARCHAR(500) NULL DEFAULT NULL,
+	`address_line_2` VARCHAR(500) NULL DEFAULT NULL,
+	`city` VARCHAR(50) NULL DEFAULT NULL,
+	`state` VARCHAR(50) NULL DEFAULT NULL,
+	`country` VARCHAR(50) NULL DEFAULT NULL,
+	`zip` VARCHAR(20) NULL DEFAULT NULL,
+	`house_phone_number` VARCHAR(20) NULL DEFAULT NULL,
+	`mobile_number` VARCHAR(20) NULL DEFAULT NULL,
+	`comment` VARCHAR(500) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_guarantor_m_loan` (`loan_id`),
+        CONSTRAINT `FK_m_guarantor_m_code_value` FOREIGN KEY (`client_reln_cv_id`) REFERENCES `m_code_value` (`id`)
+	CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_collateral` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_cv_id` int(11) NOT NULL,
+  `value` DECIMAL(19,6) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_collateral_m_loan` (`loan_id`),
+  KEY `FK_collateral_code_value` (`type_cv_id`),
+  CONSTRAINT `FK_collateral_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_collateral_code_value` FOREIGN KEY (`type_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `is_reversed` TINYINT(1) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- ======== end of loan related tables ==========
+
+CREATE TABLE `m_savings_product` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `interest_period_enum` SMALLINT(5) NOT NULL,
+  `interest_calculation_type_enum` SMALLINT(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` SMALLINT(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sp_unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_savings_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `status_enum` SMALLINT(5) NOT NULL DEFAULT 300,
+  `activation_date` DATE DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_period_enum` SMALLINT(5) NOT NULL,
+  `interest_calculation_type_enum` SMALLINT(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` SMALLINT(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `lockedin_until_date_derived` DATE DEFAULT NULL,
+  `total_deposits_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_earned_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
+  `account_balance_derived` decimal(19,6) NOT NULL DEFAULT 0,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sa_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `sa_externalid_UNIQUE` (`external_id`),
+  KEY `FKSA00000000000001` (`client_id`),
+  KEY `FKSA00000000000002` (`group_id`),
+  KEY `FKSA00000000000003` (`product_id`),
+  CONSTRAINT `FKSA00000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA00000000000002` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSA00000000000003` FOREIGN KEY (`product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_savings_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `running_balance_derived` DECIMAL(19,6) NULL,
+  `balance_number_of_days_derived` INT NULL,
+  `balance_end_date_derived` DATE NULL,
+  `cumulative_balance_derived` DECIMAL(19,6) NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- end of savings account related tables
+
+-- DDL for notes associated with all client/group and financial accounts
+CREATE TABLE `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK_m_note_m_group` (`group_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_note_m_group` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- DDL for accounting sub system related tables
+CREATE TABLE `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_entry` TINYINT(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type_enum` smallint(5) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- =========== end of accounting related tables ==========
+
+-- DDL for reporting related tables
+CREATE TABLE `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `stretchy_parameter` (
+  `parameter_id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_parameter_id` int(11) NULL DEFAULT NULL,
+  PRIMARY KEY (`parameter_id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`),
+  INDEX `fk_stretchy_parameter_0001_idx` (`parent_parameter_id`),
+  CONSTRAINT `fk_stretchy_parameter_0001` FOREIGN KEY (`parent_parameter_id`) REFERENCES `stretchy_parameter` (`parameter_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `stretchy_report` (
+  `report_id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`report_id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `stretchy_report_parameter` (
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`report_id`,`parameter_id`),
+  UNIQUE KEY `report_id_name_UNIQUE` (`report_id`,`report_parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- =========== end of reporting related tables ============
\ No newline at end of file
diff --git a/fineract-db/old-schema-files/0002-mifosx-base-reference-data-utf8.sql b/fineract-db/old-schema-files/0002-mifosx-base-reference-data-utf8.sql
new file mode 100644
index 0000000..1393ea9
--- /dev/null
+++ b/fineract-db/old-schema-files/0002-mifosx-base-reference-data-utf8.sql
@@ -0,0 +1,321 @@
+-- currency symbols may not apply through command line on windows so use a different client like mysql workbench
+
+INSERT INTO `ref_loan_transaction_processing_strategy`
+(`id`,`code`,`name`)
+VALUES
+(1, 'mifos-standard-strategy', 'Mifos style'),
+(2, 'heavensfamily-strategy', 'Heavensfamily'),
+(3, 'creocore-strategy', 'Creocore'),
+(4, 'rbi-india-strategy', 'RBI (India)');
+
+INSERT INTO `c_configuration`
+(`name`, `enabled`)
+VALUES 
+('maker-checker', 0);
+
+INSERT INTO `r_enum_value` 
+VALUES 
+('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),
+('amortization_method_enum',1,'Equal installments','Equal installments'),
+('interest_calculated_in_period_enum',0,'Daily','Daily'),
+('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),
+('interest_method_enum',0,'Declining Balance','Declining Balance'),
+('interest_method_enum',1,'Flat','Flat'),
+('interest_period_frequency_enum',2,'Per month','Per month'),
+('interest_period_frequency_enum',3,'Per year','Per year'),
+('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),
+('loan_status_id',200,'Approved','Approved'),
+('loan_status_id',300,'Active','Active'),
+('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),
+('loan_status_id',500,'Rejected','Rejected'),
+('loan_status_id',600,'Closed','Closed'),
+('loan_status_id',601,'Written-Off','Written-Off'),
+('loan_status_id',602,'Rescheduled','Rescheduled'),
+('loan_status_id',700,'Overpaid','Overpaid'),
+('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),
+('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),
+('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),
+('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),
+('processing_result_enum',0,'invalid','Invalid'),
+('processing_result_enum',1,'processed','Processed'),
+('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),
+('processing_result_enum',3,'rejected','Rejected'),
+('repayment_period_frequency_enum',0,'Days','Days'),
+('repayment_period_frequency_enum',1,'Weeks','Weeks'),
+('repayment_period_frequency_enum',2,'Months','Months'),
+('term_period_frequency_enum',0,'Days','Days'),
+('term_period_frequency_enum',1,'Weeks','Weeks'),
+('term_period_frequency_enum',2,'Months','Months'),
+('term_period_frequency_enum',3,'Years','Years');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '1', 'Disbursement', 'Disbursement');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '2', 'Repayment', 'Repayment');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '3', 'Contra', 'Contra');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '4', 'Waive Interest', 'Waive Interest');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '5', 'Repayment At Disbursement', 'Repayment At Disbursement');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '6', 'Write-Off', 'Write-Off');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '7', 'Marked for Rescheduling', 'Marked for Rescheduling');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '8', 'Recovery Repayment', 'Recovery Repayment');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '9', 'Waive Charges', 'Waive Charges');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '10', 'Apply Charges', 'Apply Charges');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) 
+VALUES ('transaction_type_enum', '11', 'Apply Interest', 'Apply Interest');
+
+INSERT INTO `m_currency`
+(`id`,`code`,`decimal_places`,`display_symbol`,`name`, `internationalized_name_code`)
+VALUES 
+(1,'AED',2,NULL,'UAE Dirham','currency.AED'),
+(2,'AFN',2,NULL,'Afghanistan Afghani','currency.AFN'),
+(3,'ALL',2,NULL,'Albanian Lek','currency.ALL'),
+(4,'AMD',2,NULL,'Armenian Dram','currency.AMD'),
+(5,'ANG',2,NULL,'Netherlands Antillian Guilder','currency.ANG'),
+(6,'AOA',2,NULL,'Angolan Kwanza','currency.AOA'),
+(7,'ARS',2,'$','Argentine Peso','currency.ARS'),
+(8,'AUD',2,'A$','Australian Dollar','currency.AUD'),
+(9,'AWG',2,NULL,'Aruban Guilder','currency.AWG'),
+(10,'AZM',2,NULL,'Azerbaijanian Manat','currency.AZM'),
+(11,'BAM',2,NULL,'Bosnia and Herzegovina Convertible Marks','currency.BAM'),
+(12,'BBD',2,NULL,'Barbados Dollar','currency.BBD'),
+(13,'BDT',2,NULL,'Bangladesh Taka','currency.BDT'),
+(14,'BGN',2,NULL,'Bulgarian Lev','currency.BGN'),
+(15,'BHD',3,NULL,'Bahraini Dinar','currency.BHD'),
+(16,'BIF',0,NULL,'Burundi Franc','currency.BIF'),
+(17,'BMD',2,NULL,'Bermudian Dollar','currency.BMD'),
+(18,'BND',2,'B$','Brunei Dollar','currency.BND'),
+(19,'BOB',2,'Bs.','Bolivian Boliviano','currency.BOB'),
+(20,'BRL',2,'R$','Brazilian Real','currency.BRL'),
+(21,'BSD',2,NULL,'Bahamian Dollar','currency.BSD'),
+(22,'BTN',2,NULL,'Bhutan Ngultrum','currency.BTN'),
+(23,'BWP',2,NULL,'Botswana Pula','currency.BWP'),
+(24,'BYR',0,NULL,'Belarussian Ruble','currency.BYR'),
+(25,'BZD',2,'BZ$','Belize Dollar','currency.BZD'),
+(26,'CAD',2,NULL,'Canadian Dollar','currency.CAD'),
+(27,'CDF',2,NULL,'Franc Congolais','currency.CDF'),
+(28,'CHF',2,NULL,'Swiss Franc','currency.CHF'),
+(29,'CLP',0,'$','Chilean Peso','currency.CLP'),
+(30,'CNY',2,NULL,'Chinese Yuan Renminbi','currency.CNY'),
+(31,'COP',2,'$','Colombian Peso','currency.COP'),
+(32,'CRC',2,'₡','Costa Rican Colon','currency.CRC'),
+(33,'CSD',2,NULL,'Serbian Dinar','currency.CSD'),
+(34,'CUP',2,'$MN','Cuban Peso','currency.CUP'),
+(35,'CVE',2,NULL,'Cape Verde Escudo','currency.CVE'),
+(36,'CYP',2,NULL,'Cyprus Pound','currency.CYP'),
+(37,'CZK',2,NULL,'Czech Koruna','currency.CZK'),
+(38,'DJF',0,NULL,'Djibouti Franc','currency.DJF'),
+(39,'DKK',2,NULL,'Danish Krone','currency.DKK'),
+(40,'DOP',2,'RD$','Dominican Peso','currency.DOP'),
+(41,'DZD',2,NULL,'Algerian Dinar','currency.DZD'),
+(42,'EEK',2,NULL,'Estonian Kroon','currency.EEK'),
+(43,'EGP',2,NULL,'Egyptian Pound','currency.EGP'),
+(44,'ERN',2,NULL,'Eritrea Nafka','currency.ERN'),
+(45,'ETB',2,NULL,'Ethiopian Birr','currency.ETB'),
+(46,'EUR',2,'€','Euro','currency.EUR'),
+(47,'FJD',2,NULL,'Fiji Dollar','currency.FJD'),
+(48,'FKP',2,NULL,'Falkland Islands Pound','currency.FKP'),
+(49,'GBP',2,NULL,'Pound Sterling','currency.GBP'),
+(50,'GEL',2,NULL,'Georgian Lari','currency.GEL'),
+(51,'GHC',2,'GHc','Ghana Cedi','currency.GHC'),
+(52,'GIP',2,NULL,'Gibraltar Pound','currency.GIP'),
+(53,'GMD',2,NULL,'Gambian Dalasi','currency.GMD'),
+(54,'GNF',0,NULL,'Guinea Franc','currency.GNF'),
+(55,'GTQ',2,'Q','Guatemala Quetzal','currency.GTQ'),
+(56,'GYD',2,NULL,'Guyana Dollar','currency.GYD'),
+(57,'HKD',2,NULL,'Hong Kong Dollar','currency.HKD'),
+(58,'HNL',2,'L','Honduras Lempira','currency.HNL'),
+(59,'HRK',2,NULL,'Croatian Kuna','currency.HRK'),
+(60,'HTG',2,'G','Haiti Gourde','currency.HTG'),
+(61,'HUF',2,NULL,'Hungarian Forint','currency.HUF'),
+(62,'IDR',2,NULL,'Indonesian Rupiah','currency.IDR'),
+(63,'ILS',2,NULL,'New Israeli Shekel','currency.ILS'),
+(64,'INR',2,'₹','Indian Rupee','currency.INR'),
+(65,'IQD',3,NULL,'Iraqi Dinar','currency.IQD'),
+(66,'IRR',2,NULL,'Iranian Rial','currency.IRR'),
+(67,'ISK',0,NULL,'Iceland Krona','currency.ISK'),
+(68,'JMD',2,NULL,'Jamaican Dollar','currency.JMD'),
+(69,'JOD',3,NULL,'Jordanian Dinar','currency.JOD'),
+(70,'JPY',0,NULL,'Japanese Yen','currency.JPY'),
+(71,'KES',2,'KSh','Kenyan Shilling','currency.KES'),
+(72,'KGS',2,NULL,'Kyrgyzstan Som','currency.KGS'),
+(73,'KHR',2,NULL,'Cambodia Riel','currency.KHR'),
+(74,'KMF',0,NULL,'Comoro Franc','currency.KMF'),
+(75,'KPW',2,NULL,'North Korean Won','currency.KPW'),
+(76,'KRW',0,NULL,'Korean Won','currency.KRW'),
+(77,'KWD',3,NULL,'Kuwaiti Dinar','currency.KWD'),
+(78,'KYD',2,NULL,'Cayman Islands Dollar','currency.KYD'),
+(79,'KZT',2,NULL,'Kazakhstan Tenge','currency.KZT'),
+(80,'LAK',2,NULL,'Lao Kip','currency.LAK'),
+(81,'LBP',2,'L£','Lebanese Pound','currency.LBP'),
+(82,'LKR',2,NULL,'Sri Lanka Rupee','currency.LKR'),
+(83,'LRD',2,NULL,'Liberian Dollar','currency.LRD'),
+(84,'LSL',2,NULL,'Lesotho Loti','currency.LSL'),
+(85,'LTL',2,NULL,'Lithuanian Litas','currency.LTL'),
+(86,'LVL',2,NULL,'Latvian Lats','currency.LVL'),
+(87,'LYD',3,NULL,'Libyan Dinar','currency.LYD'),
+(88,'MAD',2,NULL,'Moroccan Dirham','currency.MAD'),
+(89,'MDL',2,NULL,'Moldovan Leu','currency.MDL'),
+(90,'MGA',2,NULL,'Malagasy Ariary','currency.MGA'),
+(91,'MKD',2,NULL,'Macedonian Denar','currency.MKD'),
+(92,'MMK',2,'K','Myanmar Kyat','currency.MMK'),
+(93,'MNT',2,NULL,'Mongolian Tugrik','currency.MNT'),
+(94,'MOP',2,NULL,'Macau Pataca','currency.MOP'),
+(95,'MRO',2,NULL,'Mauritania Ouguiya','currency.MRO'),
+(96,'MTL',2,NULL,'Maltese Lira','currency.MTL'),
+(97,'MUR',2,NULL,'Mauritius Rupee','currency.MUR'),
+(98,'MVR',2,NULL,'Maldives Rufiyaa','currency.MVR'),
+(99,'MWK',2,NULL,'Malawi Kwacha','currency.MWK'),
+(100,'MXN',2,'$','Mexican Peso','currency.MXN'),
+(101,'MYR',2,NULL,'Malaysian Ringgit','currency.MYR'),
+(102,'MZM',2,NULL,'Mozambique Metical','currency.MZM'),
+(103,'NAD',2,NULL,'Namibia Dollar','currency.NAD'),
+(104,'NGN',2,NULL,'Nigerian Naira','currency.NGN'),
+(105,'NIO',2,'C$','Nicaragua Cordoba Oro','currency.NIO'),
+(106,'NOK',2,NULL,'Norwegian Krone','currency.NOK'),
+(107,'NPR',2,NULL,'Nepalese Rupee','currency.NPR'),
+(108,'NZD',2,NULL,'New Zealand Dollar','currency.NZD'),
+(109,'OMR',3,NULL,'Rial Omani','currency.OMR'),
+(110,'PAB',2,'B/.','Panama Balboa','currency.PAB'),
+(111,'PEN',2,'S/.','Peruvian Nuevo Sol','currency.PEN'),
+(112,'PGK',2,NULL,'Papua New Guinea Kina','currency.PGK'),
+(113,'PHP',2,NULL,'Philippine Peso','currency.PHP'),
+(114,'PKR',2,NULL,'Pakistan Rupee','currency.PKR'),
+(115,'PLN',2,NULL,'Polish Zloty','currency.PLN'),
+(116,'PYG',0,'₲','Paraguayan Guarani','currency.PYG'),
+(117,'QAR',2,NULL,'Qatari Rial','currency.QAR'),
+(118,'RON',2,NULL,'Romanian Leu','currency.RON'),
+(119,'RUB',2,NULL,'Russian Ruble','currency.RUB'),
+(120,'RWF',0,NULL,'Rwanda Franc','currency.RWF'),
+(121,'SAR',2,NULL,'Saudi Riyal','currency.SAR'),
+(122,'SBD',2,NULL,'Solomon Islands Dollar','currency.SBD'),
+(123,'SCR',2,NULL,'Seychelles Rupee','currency.SCR'),
+(124,'SDD',2,NULL,'Sudanese Dinar','currency.SDD'),
+(125,'SEK',2,NULL,'Swedish Krona','currency.SEK'),
+(126,'SGD',2,NULL,'Singapore Dollar','currency.SGD'),
+(127,'SHP',2,NULL,'St Helena Pound','currency.SHP'),
+(128,'SIT',2,NULL,'Slovenian Tolar','currency.SIT'),
+(129,'SKK',2,NULL,'Slovak Koruna','currency.SKK'),
+(130,'SLL',2,NULL,'Sierra Leone Leone','currency.SLL'),
+(131,'SOS',2,NULL,'Somali Shilling','currency.SOS'),
+(132,'SRD',2,NULL,'Surinam Dollar','currency.SRD'),
+(133,'STD',2,NULL,'Sao Tome and Principe Dobra','currency.STD'),
+(134,'SVC',2,NULL,'El Salvador Colon','currency.SVC'),
+(135,'SYP',2,NULL,'Syrian Pound','currency.SYP'),
+(136,'SZL',2,NULL,'Swaziland Lilangeni','currency.SZL'),
+(137,'THB',2,NULL,'Thai Baht','currency.THB'),
+(138,'TJS',2,NULL,'Tajik Somoni','currency.TJS'),
+(139,'TMM',2,NULL,'Turkmenistan Manat','currency.TMM'),
+(140,'TND',3,'DT','Tunisian Dinar','currency.TND'),
+(141,'TOP',2,NULL,'Tonga Pa\'anga','currency.TOP'),
+(142,'TRY',2,NULL,'Turkish Lira','currency.TRY'),
+(143,'TTD',2,NULL,'Trinidad and Tobago Dollar','currency.TTD'),
+(144,'TWD',2,NULL,'New Taiwan Dollar','currency.TWD'),
+(145,'TZS',2,NULL,'Tanzanian Shilling','currency.TZS'),
+(146,'UAH',2,NULL,'Ukraine Hryvnia','currency.UAH'),
+(147,'UGX',2,'USh','Uganda Shilling','currency.UGX'),
+(148,'USD',2,'$','US Dollar','currency.USD'),
+(149,'UYU',2,'$U','Peso Uruguayo','currency.UYU'),
+(150,'UZS',2,NULL,'Uzbekistan Sum','currency.UZS'),
+(151,'VEB',2,'Bs.F.','Venezuelan Bolivar','currency.VEB'),
+(152,'VND',2,NULL,'Vietnamese Dong','currency.VND'),
+(153,'VUV',0,NULL,'Vanuatu Vatu','currency.VUV'),
+(154,'WST',2,NULL,'Samoa Tala','currency.WST'),
+(155,'XAF',0,NULL,'CFA Franc BEAC','currency.XAF'),
+(156,'XCD',2,NULL,'East Caribbean Dollar','currency.XCD'),
+(157,'XDR',5,NULL,'SDR (Special Drawing Rights)','currency.XDR'),
+(158,'XOF',0, 'CFA','CFA Franc BCEAO','currency.XOF'),
+(159,'XPF',0,NULL,'CFP Franc','currency.XPF'),
+(160,'YER',2,NULL,'Yemeni Rial','currency.YER'),
+(161,'ZAR',2, 'R','South African Rand','currency.ZAR'),
+(162,'ZMK',2,NULL,'Zambian Kwacha','currency.ZMK'),
+(163,'ZWD',2,NULL,'Zimbabwe Dollar','currency.ZWD');
+-- ======== end of currencies ==
+
+INSERT INTO `m_organisation_currency` (`id`, `code`, `decimal_places`, `name`, `display_symbol`, `internationalized_name_code`) 
+VALUES (21,'USD',2,'US Dollar','$','currency.USD');
+
+INSERT INTO `m_office` (`id`, `parent_id`, `hierarchy`, `external_id`, `name`, `opening_date`) 
+VALUES 
+(1,NULL,'.','1','Head Office','2009-01-01');
+
+INSERT INTO `m_group_level` (`id`, `parent_id`, `super_parent`, `level_name`, `recursable`, `can_have_clients`) 
+VALUES (1, NULL, 1, 'Center', 1, 0);
+INSERT INTO `m_group_level` (`id`, `parent_id`, `super_parent`, `level_name`, `recursable`, `can_have_clients`) 
+VALUES (2, 1, 0, 'Group', 0, 1);
+
+
+
+-- create single code and code value for client identifiers
+INSERT INTO `m_code`
+(`code_name`, `is_system_defined`) 
+VALUES 
+('Customer Identifier',1),
+('LoanCollateral',1),
+('LoanPurpose',1),
+('Gender',1),
+('YesNo',1),
+('GuarantorRelationship',1);
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Passport', 1
+from m_code mc
+where mc.`code_name` = "Customer Identifier";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Id', 2
+from m_code mc
+where mc.`code_name` = "Customer Identifier";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Drivers License', 3
+from m_code mc
+where mc.`code_name` = "Customer Identifier";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Any Other Id Type', 4
+from m_code mc
+where mc.`code_name` = "Customer Identifier";
+
+-- Adding a few Default Guarantor Relationships
+insert into m_code_value (code_id,code_value,order_position) 
+	select id,"Spouse",0 
+	from m_code 
+	where m_code.code_name="GuarantorRelationship";
+
+insert into m_code_value (code_id,code_value,order_position) 
+	select id,"Parent",0 
+	from m_code 
+	where m_code.code_name="GuarantorRelationship";
+
+insert into m_code_value (code_id,code_value,order_position) 
+	select id,"Sibling",0 
+	from m_code 
+	where m_code.code_name="GuarantorRelationship";
+
+insert into m_code_value (code_id,code_value,order_position) 
+	select id,"Business Associate",0 
+	from m_code 
+	where m_code.code_name="GuarantorRelationship";
+
+insert into m_code_value (code_id,code_value,order_position) 
+	select id,"Other",0 
+	from m_code 
+	where m_code.code_name="GuarantorRelationship";
\ No newline at end of file
diff --git a/fineract-db/old-schema-files/0003-mifosx-permissions-and-authorisation-utf8.sql b/fineract-db/old-schema-files/0003-mifosx-permissions-and-authorisation-utf8.sql
new file mode 100644
index 0000000..aaf726a
--- /dev/null
+++ b/fineract-db/old-schema-files/0003-mifosx-permissions-and-authorisation-utf8.sql
@@ -0,0 +1,334 @@
+
+-- ========= roles and permissions =========
+
+/*
+this scripts removes all current m_role_permission and m_permission entries
+and then inserts new m_permission entries and just one m_role_permission entry
+which gives the role (id 1 - super user) an ALL_FUNCTIONS permission
+
+If you had other roles set up with specific permissions you will have to set up their permissions again.
+*/
+
+-- truncate `m_role_permission`;
+-- truncate `m_permission`;
+-- truncate `x_registered_table`;
+
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) VALUES 
+('special','ALL_FUNCTIONS',NULL,NULL,0),
+('special','ALL_FUNCTIONS_READ',NULL,NULL,0),
+('special', 'CHECKER_SUPER_USER', NULL, NULL, '0'),
+('special','REPORTING_SUPER_USER',NULL,NULL,0),
+('authorisation','READ_PERMISSION','PERMISSION','READ',0),
+('authorisation','PERMISSIONS_ROLE','ROLE','PERMISSIONS',1),
+('authorisation','CREATE_ROLE','ROLE','CREATE',1),
+('authorisation','CREATE_ROLE_CHECKER','ROLE','CREATE',0),
+('authorisation','READ_ROLE','ROLE','READ',0),
+('authorisation','UPDATE_ROLE','ROLE','UPDATE',1),
+('authorisation','UPDATE_ROLE_CHECKER','ROLE','UPDATE',0),
+('authorisation','DELETE_ROLE','ROLE','DELETE',1),
+('authorisation','DELETE_ROLE_CHECKER','ROLE','DELETE',0),
+('authorisation','CREATE_USER','USER','CREATE',1),
+('authorisation','CREATE_USER_CHECKER','USER','CREATE',0),
+('authorisation','READ_USER','USER','READ',0),
+('authorisation','UPDATE_USER','USER','UPDATE',1),
+('authorisation','UPDATE_USER_CHECKER','USER','UPDATE',0),
+('authorisation','DELETE_USER','USER','DELETE',1),
+('authorisation','DELETE_USER_CHECKER','USER','DELETE',0),
+('configuration','READ_CONFIGURATION','CONFIGURATION','READ',1),
+('configuration','UPDATE_CONFIGURATION','CONFIGURATION','UPDATE',1),
+('configuration','UPDATE_CONFIGURATION_CHECKER','CONFIGURATION','UPDATE',0),
+('configuration','READ_CODE','CODE','READ',0),
+('configuration','CREATE_CODE','CODE','CREATE',1),
+('configuration','CREATE_CODE_CHECKER','CODE','CREATE',0),
+('configuration','UPDATE_CODE','CODE','UPDATE',1),
+('configuration','UPDATE_CODE_CHECKER','CODE','UPDATE',0),
+('configuration','DELETE_CODE','CODE','DELETE',1),
+('configuration','DELETE_CODE_CHECKER','CODE','DELETE',0),
+('configuration', 'READ_CODEVALUE', 'CODEVALUE', 'READ', '0'),
+('configuration', 'CREATE_CODEVALUE', 'CODEVALUE', 'CREATE', '1'),
+('configuration', 'CREATE_CODEVALUE_CHECKER', 'CODEVALUE', 'CREATE', '0'),
+('configuration', 'UPDATE_CODEVALUE', 'CODEVALUE', 'UPDATE', '1'),
+('configuration', 'UPDATE_CODEVALUE_CHECKER', 'CODEVALUE', 'UPDATE', '0'),
+('configuration', 'DELETE_CODEVALUE', 'CODEVALUE', 'DELETE', '1'),
+('configuration', 'DELETE_CODEVALUE_CHECKER', 'CODEVALUE', 'DELETE', '0'),
+('configuration','READ_CURRENCY','CURRENCY','READ',0),
+('configuration','UPDATE_CURRENCY','CURRENCY','UPDATE',1),
+('configuration','UPDATE_CURRENCY_CHECKER','CURRENCY','UPDATE',0),
+('configuration', 'UPDATE_PERMISSION', 'PERMISSION', 'UPDATE', '1'),
+('configuration', 'UPDATE_PERMISSION_CHECKER', 'PERMISSION', 'UPDATE', '0'),
+('configuration', 'READ_DATATABLE', 'DATATABLE', 'READ', '0'),
+('configuration', 'REGISTER_DATATABLE', 'DATATABLE', 'REGISTER', '1'),
+('configuration', 'REGISTER_DATATABLE_CHECKER', 'DATATABLE', 'REGISTER', '0'),
+('configuration', 'DEREGISTER_DATATABLE', 'DATATABLE', 'DEREGISTER', '1'),
+('configuration', 'DEREGISTER_DATATABLE_CHECKER', 'DATATABLE', 'DEREGISTER', '0'),
+('configuration', 'READ_AUDIT', 'AUDIT', 'READ', '0'),
+('configuration', 'CREATE_CALENDAR', 'CALENDAR', 'CREATE', '0'),
+('configuration', 'READ_CALENDAR', 'CALENDAR', 'READ', '0'),
+('configuration', 'UPDATE_CALENDAR', 'CALENDAR', 'UPDATE', '0'),
+('configuration', 'DELETE_CALENDAR', 'CALENDAR', 'DELETE', '0'),
+('configuration', 'CREATE_CALENDAR_CHECKER', 'CALENDAR', 'CREATE', '0'),
+('configuration', 'UPDATE_CALENDAR_CHECKER', 'CALENDAR', 'UPDATE', '0'),
+('configuration', 'DELETE_CALENDAR_CHECKER', 'CALENDAR', 'DELETE', '0'),
+('organisation', 'READ_MAKERCHECKER', 'MAKERCHECKER', 'READ', '0'),
+('organisation', 'READ_CHARGE', 'CHARGE', 'READ', '0'),
+('organisation', 'CREATE_CHARGE', 'CHARGE', 'CREATE', '1'),
+('organisation', 'CREATE_CHARGE_CHECKER', 'CHARGE', 'CREATE', '0'),
+('organisation', 'UPDATE_CHARGE', 'CHARGE', 'UPDATE', '1'),
+('organisation', 'UPDATE_CHARGE_CHECKER', 'CHARGE', 'UPDATE', '0'),
+('organisation', 'DELETE_CHARGE', 'CHARGE', 'DELETE', '1'),
+('organisation', 'DELETE_CHARGE_CHECKER', 'CHARGE', 'DELETE', '0'),
+('organisation', 'READ_FUND', 'FUND', 'READ', '0'),
+('organisation', 'CREATE_FUND', 'FUND', 'CREATE', '1'),
+('organisation', 'CREATE_FUND_CHECKER', 'FUND', 'CREATE', '0'),
+('organisation', 'UPDATE_FUND', 'FUND', 'UPDATE', '1'),
+('organisation', 'UPDATE_FUND_CHECKER', 'FUND', 'UPDATE', '0'),
+('organisation', 'DELETE_FUND', 'FUND', 'DELETE', '1'),
+('organisation', 'DELETE_FUND_CHECKER', 'FUND', 'DELETE', '0'),
+('organisation', 'READ_LOANPRODUCT', 'LOANPRODUCT', 'READ', '0'),
+('organisation', 'CREATE_LOANPRODUCT', 'LOANPRODUCT', 'CREATE', '1'),
+('organisation', 'CREATE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'CREATE', '0'),
+('organisation', 'UPDATE_LOANPRODUCT', 'LOANPRODUCT', 'UPDATE', '1'),
+('organisation', 'UPDATE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'UPDATE', '0'),
+('organisation', 'DELETE_LOANPRODUCT', 'LOANPRODUCT', 'DELETE', '1'),
+('organisation', 'DELETE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'DELETE', '0'),
+('organisation', 'READ_OFFICE', 'OFFICE', 'READ', '0'),
+('organisation', 'CREATE_OFFICE', 'OFFICE', 'CREATE', '1'),
+('organisation', 'CREATE_OFFICE_CHECKER', 'OFFICE', 'CREATE', '0'),
+('organisation', 'UPDATE_OFFICE', 'OFFICE', 'UPDATE', '1'),
+('organisation', 'UPDATE_OFFICE_CHECKER', 'OFFICE', 'UPDATE', '0'),
+('organisation', 'READ_OFFICETRANSACTION', 'OFFICETRANSACTION', 'READ', '0'),
+('organisation', 'DELETE_OFFICE_CHECKER', 'OFFICE', 'DELETE', '0'),
+('organisation', 'CREATE_OFFICETRANSACTION', 'OFFICETRANSACTION', 'CREATE', '1'),
+('organisation', 'CREATE_OFFICETRANSACTION_CHECKER', 'OFFICETRANSACTION', 'CREATE', '0'),
+('organisation', 'DELETE_OFFICETRANSACTION', 'OFFICETRANSACTION', 'DELETE', 1),
+('organisation', 'DELETE_OFFICETRANSACTION_CHECKER', 'OFFICETRANSACTION', 'DELETE', 0),
+('organisation', 'READ_STAFF', 'STAFF', 'READ', '0'),
+('organisation', 'CREATE_STAFF', 'STAFF', 'CREATE', '1'),
+('organisation', 'CREATE_STAFF_CHECKER', 'STAFF', 'CREATE', '0'),
+('organisation', 'UPDATE_STAFF', 'STAFF', 'UPDATE', '1'),
+('organisation', 'UPDATE_STAFF_CHECKER', 'STAFF', 'UPDATE', '0'),
+('organisation', 'DELETE_STAFF', 'STAFF', 'DELETE', '1'),
+('organisation', 'DELETE_STAFF_CHECKER', 'STAFF', 'DELETE', '0'),
+('organisation', 'READ_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'READ', '0'),
+('organisation', 'CREATE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'CREATE', '1'),
+('organisation', 'CREATE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'CREATE', '0'),
+('organisation', 'UPDATE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'UPDATE', '1'),
+('organisation', 'UPDATE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'UPDATE', '0'),
+('organisation', 'DELETE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'DELETE', '1'),
+('organisation', 'DELETE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'DELETE', '0'),
+('portfolio', 'READ_LOAN', 'LOAN', 'READ', '0'),
+('portfolio', 'CREATE_LOAN', 'LOAN', 'CREATE', '1'),
+('portfolio', 'CREATE_LOAN_CHECKER', 'LOAN', 'CREATE', '0'),
+('portfolio', 'UPDATE_LOAN', 'LOAN', 'UPDATE', '1'),
+('portfolio', 'UPDATE_LOAN_CHECKER', 'LOAN', 'UPDATE', '0'),
+('portfolio', 'DELETE_LOAN', 'LOAN', 'DELETE', '1'),
+('portfolio', 'DELETE_LOAN_CHECKER', 'LOAN', 'DELETE', '0'),
+-- ('portfolio', 'CREATEHISTORIC_LOAN', 'LOAN', 'CREATEHISTORIC', '1'),
+-- ('portfolio', 'CREATEHISTORIC_LOAN_CHECKER', 'LOAN', 'CREATEHISTORIC', '0'),
+-- ('portfolio', 'UPDATEHISTORIC_LOAN', 'LOAN', 'UPDATEHISTORIC', '1'),
+-- ('portfolio', 'UPDATEHISTORIC_LOAN_CHECKER', 'LOAN', 'UPDATEHISTORIC', '0'),
+('portfolio', 'READ_CLIENT', 'CLIENT', 'READ', '0'),
+('portfolio', 'CREATE_CLIENT', 'CLIENT', 'CREATE', '1'),
+('portfolio', 'CREATE_CLIENT_CHECKER', 'CLIENT', 'CREATE', '0'),
+('portfolio', 'UPDATE_CLIENT', 'CLIENT', 'UPDATE', '1'),
+('portfolio', 'UPDATE_CLIENT_CHECKER', 'CLIENT', 'UPDATE', '0'),
+('portfolio', 'DELETE_CLIENT', 'CLIENT', 'DELETE', '1'),
+('portfolio', 'DELETE_CLIENT_CHECKER', 'CLIENT', 'DELETE', '0'),
+('portfolio', 'READ_CLIENTIMAGE', 'CLIENTIMAGE', 'READ', '0'),
+('portfolio', 'CREATE_CLIENTIMAGE', 'CLIENTIMAGE', 'CREATE', '1'),
+('portfolio', 'CREATE_CLIENTIMAGE_CHECKER', 'CLIENTIMAGE', 'CREATE', '0'),
+('portfolio', 'DELETE_CLIENTIMAGE', 'CLIENTIMAGE', 'DELETE', '1'),
+('portfolio', 'DELETE_CLIENTIMAGE_CHECKER', 'CLIENTIMAGE', 'DELETE', '0'),
+('portfolio', 'READ_CLIENTNOTE', 'CLIENTNOTE', 'READ', '0'),
+('portfolio', 'CREATE_CLIENTNOTE', 'CLIENTNOTE', 'CREATE', '1'),
+('portfolio', 'CREATE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_CLIENTNOTE', 'CLIENTNOTE', 'UPDATE', '1'),
+('portfolio', 'UPDATE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_CLIENTNOTE', 'CLIENTNOTE', 'DELETE', '1'),
+('portfolio', 'DELETE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'DELETE', '0'),
+('portfolio', 'READ_GROUPNOTE', 'GROUPNOTE', 'READ', '0'),
+('portfolio', 'CREATE_GROUPNOTE', 'GROUPNOTE', 'CREATE', '1'),
+('portfolio', 'UPDATE_GROUPNOTE', 'GROUPNOTE', 'UPDATE', '1'),
+('portfolio', 'DELETE_GROUPNOTE', 'GROUPNOTE', 'DELETE', '1'),
+('portfolio', 'CREATE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'DELETE', '0'),
+('portfolio', 'READ_LOANNOTE', 'LOANNOTE', 'READ', '0'),
+('portfolio', 'CREATE_LOANNOTE', 'LOANNOTE', 'CREATE', '1'),
+('portfolio', 'UPDATE_LOANNOTE', 'LOANNOTE', 'UPDATE', '1'),
+('portfolio', 'DELETE_LOANNOTE', 'LOANNOTE', 'DELETE', '1'),
+('portfolio', 'CREATE_LOANNOTE_CHECKER', 'LOANNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_LOANNOTE_CHECKER', 'LOANNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_LOANNOTE_CHECKER', 'LOANNOTE', 'DELETE', '0'),
+('portfolio', 'READ_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'READ', '0'),
+('portfolio', 'CREATE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'CREATE', '1'),
+('portfolio', 'UPDATE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'UPDATE', '1'),
+('portfolio', 'DELETE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'DELETE', '1'),
+('portfolio', 'CREATE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'DELETE', '0'),
+('portfolio', 'READ_SAVINGNOTE', 'SAVINGNOTE', 'READ', '0'),
+('portfolio', 'CREATE_SAVINGNOTE', 'SAVINGNOTE', 'CREATE', '1'),
+('portfolio', 'UPDATE_SAVINGNOTE', 'SAVINGNOTE', 'UPDATE', '1'),
+('portfolio', 'DELETE_SAVINGNOTE', 'SAVINGNOTE', 'DELETE', '1'),
+('portfolio', 'CREATE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'DELETE', '0'),
+('portfolio', 'READ_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'READ', '0'),
+('portfolio', 'CREATE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'CREATE', '1'),
+('portfolio', 'CREATE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'CREATE', '0'),
+('portfolio', 'UPDATE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'UPDATE', '1'),
+('portfolio', 'UPDATE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'UPDATE', '0'),
+('portfolio', 'DELETE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'DELETE', '1'),
+('portfolio', 'DELETE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'DELETE', '0'),
+('portfolio', 'READ_DOCUMENT', 'DOCUMENT', 'READ', '0'),
+('portfolio', 'CREATE_DOCUMENT', 'DOCUMENT', 'CREATE', '1'),
+('portfolio', 'CREATE_DOCUMENT_CHECKER', 'DOCUMENT', 'CREATE', '0'),
+('portfolio', 'UPDATE_DOCUMENT', 'DOCUMENT', 'UPDATE', '1'),
+('portfolio', 'UPDATE_DOCUMENT_CHECKER', 'DOCUMENT', 'UPDATE', '0'),
+('portfolio', 'DELETE_DOCUMENT', 'DOCUMENT', 'DELETE', '1'),
+('portfolio', 'DELETE_DOCUMENT_CHECKER', 'DOCUMENT', 'DELETE', '0'),
+('portfolio', 'READ_GROUP', 'GROUP', 'READ', '0'),
+('portfolio', 'CREATE_GROUP', 'GROUP', 'CREATE', '1'),
+('portfolio', 'CREATE_GROUP_CHECKER', 'GROUP', 'CREATE', '0'),
+('portfolio', 'UPDATE_GROUP', 'GROUP', 'UPDATE', '1'),
+('portfolio', 'UPDATE_GROUP_CHECKER', 'GROUP', 'UPDATE', '0'),
+('portfolio', 'DELETE_GROUP', 'GROUP', 'DELETE', '1'),
+('portfolio', 'DELETE_GROUP_CHECKER', 'GROUP', 'DELETE', '0'),
+('portfolio', 'UNASSIGNSTAFF_GROUP', 'GROUP', 'UNASSIGNSTAFF', 1),
+('portfolio', 'UNASSIGNSTAFF_GROUP_CHECKER', 'GROUP', 'UNASSIGNSTAFF', 0),
+('portfolio', 'CREATE_LOANCHARGE', 'LOANCHARGE', 'CREATE', '1'),
+('portfolio', 'CREATE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'CREATE', '0'),
+('portfolio', 'UPDATE_LOANCHARGE', 'LOANCHARGE', 'UPDATE', '1'),
+('portfolio', 'UPDATE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'UPDATE', '0'),
+('portfolio', 'DELETE_LOANCHARGE', 'LOANCHARGE', 'DELETE', '1'),
+('portfolio', 'DELETE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'DELETE', '0'),
+('portfolio', 'WAIVE_LOANCHARGE', 'LOANCHARGE', 'WAIVE', '1'),
+('portfolio', 'WAIVE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'WAIVE', '0'),
+('portfolio', 'READ_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'READ', '0'),
+('portfolio', 'CREATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CREATE', '1'),
+('portfolio', 'CREATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CREATE', '0'),
+('portfolio', 'UPDATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UPDATE', '1'),
+('portfolio', 'UPDATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UPDATE', '0'),
+('portfolio', 'DELETE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DELETE', '1'),
+('portfolio', 'DELETE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DELETE', '0'),
+('portfolio', 'READ_GUARANTOR', 'GUARANTOR', 'READ', 0),
+('portfolio', 'CREATE_GUARANTOR', 'GUARANTOR', 'CREATE', 1),
+('portfolio', 'CREATE_GUARANTOR_CHECKER', 'GUARANTOR', 'CREATE', 0),
+('portfolio', 'UPDATE_GUARANTOR', 'GUARANTOR', 'UPDATE', 1),
+('portfolio', 'UPDATE_GUARANTOR_CHECKER', 'GUARANTOR', 'UPDATE', 0),
+('portfolio', 'DELETE_GUARANTOR', 'GUARANTOR', 'DELETE', 1),
+('portfolio', 'DELETE_GUARANTOR_CHECKER', 'GUARANTOR', 'DELETE', 0),
+('portfolio', 'READ_COLLATERAL', 'COLLATERAL', 'READ', '0'),
+('portfolio', 'CREATE_COLLATERAL', 'COLLATERAL', 'CREATE', '1'),
+('portfolio', 'UPDATE_COLLATERAL', 'COLLATERAL', 'UPDATE', '1'),
+('portfolio', 'DELETE_COLLATERAL', 'COLLATERAL', 'DELETE', '1'),
+('portfolio', 'CREATE_COLLATERAL_CHECKER', 'COLLATERAL', 'CREATE', '0'),
+('portfolio', 'UPDATE_COLLATERAL_CHECKER', 'COLLATERAL', 'UPDATE', '0'),
+('portfolio', 'DELETE_COLLATERAL_CHECKER', 'COLLATERAL', 'DELETE', '0'),
+('transaction_loan', 'APPROVE_LOAN', 'LOAN', 'APPROVE', '1'),
+('transaction_loan', 'APPROVEINPAST_LOAN', 'LOAN', 'APPROVEINPAST', '1'),
+('transaction_loan', 'REJECT_LOAN', 'LOAN', 'REJECT', '1'),
+('transaction_loan', 'REJECTINPAST_LOAN', 'LOAN', 'REJECTINPAST', '1'),
+('transaction_loan', 'WITHDRAW_LOAN', 'LOAN', 'WITHDRAW', '1'),
+('transaction_loan', 'WITHDRAWINPAST_LOAN', 'LOAN', 'WITHDRAWINPAST', '1'),
+('transaction_loan', 'APPROVALUNDO_LOAN', 'LOAN', 'APPROVALUNDO', '1'),
+('transaction_loan', 'DISBURSE_LOAN', 'LOAN', 'DISBURSE', '1'),
+('transaction_loan', 'DISBURSEINPAST_LOAN', 'LOAN', 'DISBURSEINPAST', '1'),
+('transaction_loan', 'DISBURSALUNDO_LOAN', 'LOAN', 'DISBURSALUNDO', '1'),
+('transaction_loan', 'REPAYMENT_LOAN', 'LOAN', 'REPAYMENT', '1'),
+('transaction_loan', 'REPAYMENTINPAST_LOAN', 'LOAN', 'REPAYMENTINPAST', '1'),
+('transaction_loan', 'ADJUST_LOAN', 'LOAN', 'ADJUST', '1'),
+('transaction_loan', 'WAIVEINTERESTPORTION_LOAN', 'LOAN', 'WAIVEINTERESTPORTION', '1'),
+('transaction_loan', 'WRITEOFF_LOAN', 'LOAN', 'WRITEOFF', '1'),
+('transaction_loan', 'CLOSE_LOAN', 'LOAN', 'CLOSE', '1'),
+('transaction_loan', 'CLOSEASRESCHEDULED_LOAN', 'LOAN', 'CLOSEASRESCHEDULED', '1'),
+('transaction_loan', 'UPDATELOANOFFICER_LOAN', 'LOAN', 'UPDATELOANOFFICER', 1),
+('transaction_loan', 'UPDATELOANOFFICER_LOAN_CHECKER', 'LOAN', 'UPDATELOANOFFICER', 0),
+('transaction_loan', 'REMOVELOANOFFICER_LOAN', 'LOAN', 'REMOVELOANOFFICER', 1),
+('transaction_loan', 'REMOVELOANOFFICER_LOAN_CHECKER', 'LOAN', 'REMOVELOANOFFICER', 0),
+('transaction_loan', 'BULKREASSIGN_LOAN', 'LOAN', 'BULKREASSIGN', '1'),
+('transaction_loan', 'BULKREASSIGN_LOAN_CHECKER', 'LOAN', 'BULKREASSIGN', '0'),
+('transaction_loan', 'APPROVE_LOAN_CHECKER', 'LOAN', 'APPROVE', '0'),
+('transaction_loan', 'APPROVEINPAST_LOAN_CHECKER', 'LOAN', 'APPROVEINPAST', '0'),
+('transaction_loan', 'REJECT_LOAN_CHECKER', 'LOAN', 'REJECT', '0'),
+('transaction_loan', 'REJECTINPAST_LOAN_CHECKER', 'LOAN', 'REJECTINPAST', '0'),
+('transaction_loan', 'WITHDRAW_LOAN_CHECKER', 'LOAN', 'WITHDRAW', '0'),
+('transaction_loan', 'WITHDRAWINPAST_LOAN_CHECKER', 'LOAN', 'WITHDRAWINPAST', '0'),
+('transaction_loan', 'APPROVALUNDO_LOAN_CHECKER', 'LOAN', 'APPROVALUNDO', '0'),
+('transaction_loan', 'DISBURSE_LOAN_CHECKER', 'LOAN', 'DISBURSE', '0'),
+('transaction_loan', 'DISBURSEINPAST_LOAN_CHECKER', 'LOAN', 'DISBURSEINPAST', '0'),
+('transaction_loan', 'DISBURSALUNDO_LOAN_CHECKER', 'LOAN', 'DISBURSALUNDO', '0'),
+('transaction_loan', 'REPAYMENT_LOAN_CHECKER', 'LOAN', 'REPAYMENT', '0'),
+('transaction_loan', 'REPAYMENTINPAST_LOAN_CHECKER', 'LOAN', 'REPAYMENTINPAST', '0'),
+('transaction_loan', 'ADJUST_LOAN_CHECKER', 'LOAN', 'ADJUST', '0'),
+('transaction_loan', 'WAIVEINTERESTPORTION_LOAN_CHECKER', 'LOAN', 'WAIVEINTERESTPORTION', '0'),
+('transaction_loan', 'WRITEOFF_LOAN_CHECKER', 'LOAN', 'WRITEOFF', '0'),
+('transaction_loan', 'CLOSE_LOAN_CHECKER', 'LOAN', 'CLOSE', '0'),
+('transaction_loan', 'CLOSEASRESCHEDULED_LOAN_CHECKER', 'LOAN', 'CLOSEASRESCHEDULED', '0'),
+('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DEPOSIT', '1'),
+('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DEPOSIT', '0'),
+('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAWAL', '1'),
+('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAWAL', '0'),
+('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ACTIVATE', '1'),
+('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE', '0'),
+('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '1'),
+('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '0');
+
+-- == accounting related permissions
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES 
+('accounting', 'CREATE_GLACCOUNT', 'GLACCOUNT', 'CREATE', 1),
+('accounting', 'UPDATE_GLACCOUNT', 'GLACCOUNT', 'UPDATE', 1),
+('accounting', 'DELETE_GLACCOUNT', 'GLACCOUNT', 'DELETE', 1),
+('accounting', 'CREATE_GLCLOSURE', 'GLCLOSURE', 'CREATE', 1),
+('accounting', 'UPDATE_GLCLOSURE', 'GLCLOSURE', 'UPDATE', 1),
+('accounting', 'DELETE_GLCLOSURE', 'GLCLOSURE', 'DELETE', 1), 
+('accounting', 'CREATE_JOURNALENTRY', 'JOURNALENTRY', 'CREATE', 1),
+('accounting', 'REVERSE_JOURNALENTRY', 'JOURNALENTRY', 'REVERSE', 1);
+
+
+INSERT INTO `m_role` (`id`, `name`, `description`) 
+VALUES 
+(1,'Super user','This role provides all application permissions.');
+
+/* role 1 is super user, give it ALL_FUNCTIONS */
+INSERT INTO m_role_permission(role_id, permission_id)
+select 1, id
+from m_permission
+where code = 'ALL_FUNCTIONS';
+
+INSERT INTO `m_appuser` (`id`, `office_id`, `username`, `firstname`, `lastname`, `password`, `email`, 
+`firsttime_login_remaining`, `nonexpired`, `nonlocked`, `nonexpired_credentials`, `enabled`) 
+VALUES 
+(1,1,'mifos','App','Administrator','5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a','demomfi@mifos.org','\0','','','','');
+
+
+INSERT INTO `m_appuser_role` (`appuser_id`, `role_id`) VALUES (1,1);
+
+
+-- Add in permissions for any special datatables added in base reference data
+-- This needs to always happen at end of the script
+
+/* add a create, read, update and delete permission for each registered datatable */
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('CREATE_', r.registered_table_name), r.registered_table_name, 'CREATE'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('READ_', r.registered_table_name), r.registered_table_name, 'READ'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('UPDATE_', r.registered_table_name), r.registered_table_name, 'UPDATE'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('DELETE_', r.registered_table_name), r.registered_table_name, 'DELETE'
+from x_registered_table r;
+
+
+/* regardless of inserted permission settings above, no permissions (transactions) are preselected as being part of the maker-checker process
+so, just set the flag to false... the end-user can decide which permissions should be maker-checkerable
+*/
+update m_permission set can_maker_checker = false;
\ No newline at end of file
diff --git a/fineract-db/old-schema-files/0004-mifosx-core-reports-utf8.sql b/fineract-db/old-schema-files/0004-mifosx-core-reports-utf8.sql
new file mode 100644
index 0000000..b6a728b
--- /dev/null
+++ b/fineract-db/old-schema-files/0004-mifosx-core-reports-utf8.sql
@@ -0,0 +1,12 @@
+truncate table stretchy_report;
+truncate table stretchy_parameter;
+truncate table stretchy_report_parameter;
+
+INSERT INTO `stretchy_report` VALUES (1,'Client Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n c.account_no as \"Client Account No.\",  \r\nc.display_name as \"Name\",  \n\nc.joined_date as \"Joined\", c.external_id as \"External Id\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand \n\nounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\nwhere o.id = ${officeId}\r\nand c.is_deleted=0\r\n\n\norder by ounder.hierarchy, c.account_no','Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t \n\nhave that client browser/memory impact).',1,1),(2,'Client Loans Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client \n\nAccount No.\", \r\nc.display_name as \"Name\", \r\nlo.display_name as \"Loan Officer\", l.account_no as \"Loan Account No.\", l.external_id as \"External Id\", \r\n\n\np.name as Loan, st.enum_message_property as \"Status\",  \r\nf.`name` as Fund, purp.code_value as \"Loan Purpose\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount,\n\n\r\nl.arrearstolerance_amount as \"Arrears Tolerance Amount\",\r\nl.number_of_repayments as \"Expected No. Repayments\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\nl.nominal_interest_rate_per_period as \"Nominal Interest Rate Per Period\",\r\n\r\nipf.enum_message_property as \"Interest Rate Frequency\n\n\",\r\nim.enum_message_property as \"Interest Method\",\r\nicp.enum_message_property as \"Interest Calculated in Period\",\r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.repay_every as \"Repayment Frequency\",\r\nrf.enum_message_property as \"Repayment Frequency Period\",\n\n\r\nam.enum_message_property as \"Amortization\",\r\n\r\nl.total_charges_due_at_disbursement_derived as \"Total Charges Due At Disbursement\",\r\n\r\ndate( \n\nl.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As \"Expected Disbursal\",\r\ndate(l.expected_firstrepaymenton_date) as \n\n\"Expected First Repayment\", date(l.interest_calculated_from_date) as \"Interest Calculated From\" ,\r\ndate(l.disbursedon_date) as Disbursed, date\n\n(l.expected_maturedon_date) \"Expected Maturity\",\r\ndate(l.maturedon_date) as \"Matured On\", date(l.closedon_date) as Closed,\r\ndate(l.rejectedon_date) as \n\nRejected, date(l.rescheduledon_date) as Rescheduled, \r\ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) \"Written Off\"\r\nfrom m_office o \r\njoin \n\nm_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on \n\nc.office_id = ounder.id\r\nleft join m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_product_loan p on p.id = \n\nl.product_id\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join r_enum_value st on st.enum_name = \"loan_status_id\" and st.enum_id = l.loan_status_id\r\nleft join \n\nr_enum_value ipf on ipf.enum_name = \"interest_period_frequency_enum\" and ipf.enum_id = l.interest_period_frequency_enum\r\nleft join r_enum_value im on im.enum_name \n\n= \"interest_method_enum\" and im.enum_id = l.interest_method_enum\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = \n\nl.term_period_frequency_enum\r\nleft join r_enum_value icp on icp.enum_name = \"interest_calculated_in_period_enum\" and icp.enum_id = \n\nl.interest_calculated_in_period_enum\r\nleft join r_enum_value rf on rf.enum_name = \"repayment_period_frequency_enum\" and rf.enum_id = \n\nl.repayment_period_frequency_enum\r\nleft join r_enum_value am on am.enum_name = \"amortization_method_enum\" and am.enum_id = l.amortization_method_enum\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\n\r\nleft \n\njoin m_currency cur on cur.code = l.currency_code\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand \n\n(l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \n\n\"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\norder by ounder.hierarchy, 2 , l.id','Individual Client Report\r\n\r\nPretty \n\nwide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).',1,1),(5,'Loans Awaiting Disbursal','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No\", c.display_name as \"Name\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund, ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\",\r\ndate(l.approvedon_date) \"Approved\",\r\ndatediff(l.expected_disbursedon_date, curdate()) as \"Days to Disbursal\",\r\ndate(l.expected_disbursedon_date) \"Expected Disbursal\",\r\npurp.code_value as \"Loan Purpose\",\r\n lo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, datediff(l.expected_disbursedon_date, curdate()),  c.account_no','Individual Client Report',1,1),(6,'Loans Awaiting Disbursal Summary','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`','Individual Client Report',1,1),(7,'Loans Awaiting Disbursal Summary by Month','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as \"Year\", \r\nmonthname(l.expected_disbursedon_date) as \"Month\",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)','Individual Client Report',1,1),(8,'Loans Pending Approval','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client Name\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  pl.`name` as \"Product\", \r\nl.account_no as \"Loan Account No.\", \r\nl.principal_amount as \"Loan Amount\", \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\ndatediff(curdate(), l.submittedon_date) \"Days Pending Approval\", \r\npurp.code_value as \"Loan Purpose\",\r\nlo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 100 /*Submitted and awaiting approval */\r\norder by ounder.hierarchy, l.submittedon_date,  l.account_no','Individual Client Report',1,1),(11,'Active Loans - Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. loans_in_arrears_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(if(laa.loan_id is not null,  l.id, null)  )) as loans_in_arrears_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(laa.principal_overdue_derived) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(laa.interest_overdue_derived) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(laa.fee_charges_overdue_derived) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(laa.penalty_charges_overdue_derived) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(12,'Active Loans - Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\npenalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(13,'Obligation Met Loans Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client\",\r\nl.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.total_repayment_derived  as \"Total Repaid\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", \r\ndate(l.closedon_date) as \"Closed\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(14,'Obligation Met Loans Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as \"No. of Clients\",\r\ncount(distinct(l.id)) as \"No. of Loans\",\r\nsum(l.principal_amount) as \"Total Loan Amount\", \r\nsum(l.principal_repaid_derived) as \"Total Principal Repaid\",\r\nsum(l.interest_repaid_derived) as \"Total Interest Repaid\",\r\nsum(l.fee_charges_repaid_derived) as \"Total Fees Repaid\",\r\nsum(l.penalty_charges_repaid_derived) as \"Total Penalties Repaid\",\r\nsum(l.interest_waived_derived) as \"Total Interest Waived\",\r\nsum(l.fee_charges_waived_derived) as \"Total Fees Waived\",\r\nsum(l.penalty_charges_waived_derived) as \"Total Penalties Waived\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code','Individual Client \n\nReport',1,1),(15,'Portfolio at Risk','Table',NULL,'Loan','select x.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from \r\n(select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code) x','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(16,'Portfolio at Risk by Branch','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\",\r\nx.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select  ounder.id as \"branch\", ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\n\r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(20,'Funds Disbursed Between Dates Summary','Table',NULL,'Fund','select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, \r\nround(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(21,'Funds Disbursed Between Dates Summary by Office','Table',NULL,'Fund','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n \n\nifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\n\n\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c \n\non c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\n\n\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand \n\n(l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, \n\nl.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(48,'Balance Sheet','Pentaho',NULL,'Accounting',NULL,'Balance Sheet',1,1),(49,'Income Statement','Pentaho',NULL,'Accounting',NULL,'Profit and Loss Statement',1,1),(50,'Trial Balance','Pentaho',NULL,'Accounting',NULL,'Trial Balance Report',1,1),(51,'Written-Off Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Written-Off Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Written-Off Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Written-Off Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Written-Off Penalties\',\r\nn.note AS \'Reason For Write-Off\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 6 /*write-off */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=601\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\") \r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Written Off Loans',1,1),(52,'Aging Detail','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as \"Client Account No.\",\r\n 	mc.display_name AS \"Client Name\",\r\n 	ml.account_no AS \"Account Number\",\r\n 	ml.principal_amount AS \"Loan Amount\",\r\n ml.principal_disbursed_derived AS \"Original Principal\",\r\n ml.interest_charged_derived AS \"Original Interest\",\r\n ml.principal_repaid_derived AS \"Principal Paid\",\r\n ml.interest_repaid_derived AS \"Interest Paid\",\r\n laa.principal_overdue_derived AS \"Principal Overdue\",\r\n laa.interest_overdue_derived AS \"Interest Overdue\",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as \"Days in Arrears\",\r\n\r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS \"Weeks In Arrears Band\",\r\n\r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n				 \'> 360\'))))) AS \"Days in Arrears Band\"\r\n\r\n	FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n	        AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n	    INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n	    INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n	WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Loan arrears aging (Weeks)',1,1),(53,'Aging Summary (Arrears in Weeks)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Weeks In Arrears (Up To)\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'1\',2 UNION\r\n		SELECT \'2\',3 UNION\r\n		SELECT \'3\',4 UNION\r\n		SELECT \'4\',5 UNION\r\n		SELECT \'5\',6 UNION\r\n		SELECT \'6\',7 UNION\r\n		SELECT \'7\',8 UNION\r\n		SELECT \'8\',9 UNION\r\n		SELECT \'9\',10 UNION\r\n		SELECT \'10\',11 UNION\r\n		SELECT \'11\',12 UNION\r\n		SELECT \'12\',13 UNION\r\n		SELECT \'12+\',14) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<8, \'1\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<15, \'2\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<22, \'3\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<29, \'4\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<36, \'5\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<43, \'6\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<50, \'7\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<57, \'8\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<64, \'9\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<71, \'10\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<78, \'11\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<85, \'12\',\r\n				 \'12+\'))))))))))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(54,'Rescheduled Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Rescheduled Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Rescheduled Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Rescheduled Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Rescheduled Penalties\',\r\nn.note AS \'Reason For Rescheduling\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 7 /*marked for rescheduling */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=602\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Rescheduled Loans.  The ability to reschedule (or mark that you have rescheduled the loan elsewhere) is a legacy of the older Mifos product.  Needed for migration.',1,1),(55,'Active Loans Passed Final Maturity','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(56,'Active Loans Passed Final Maturity Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(57,'Active Loans in last installment','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(lastInstallment.`hierarchy`) - LENGTH(REPLACE(lastInstallment.`hierarchy`, \'.\', \'\')) - 1))), lastInstallment.branch) as \"Office/Branch\",\r\nlastInstallment.Currency,\r\nlastInstallment.`Loan Officer`, \r\nlastInstallment.`Client Account No`, lastInstallment.`Client`, \r\nlastInstallment.`Loan Account No`, lastInstallment.`Product`, \r\nlastInstallment.`Fund`,  lastInstallment.`Loan Amount`, \r\nlastInstallment.`Annual Nominal Interest Rate`, \r\nlastInstallment.`Disbursed`, lastInstallment.`Expected Matured On` ,\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\norder by lastInstallment.hierarchy, lastInstallment.Currency, lastInstallment.`Client Account No`, lastInstallment.`Loan Account No`','Individual Client \n\nReport',1,1),(58,'Active Loans in last installment Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select lastInstallment.branchId as branchId,\r\nlastInstallment.Currency,\r\ncount(distinct(lastInstallment.clientId)) as client_count, \r\ncount(distinct(lastInstallment.loanId)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id as branchId, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.id as clientId, c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\ngroup by lastInstallment.branchId) x on x.branchId = mo.id\r\norder by mo.hierarchy, x.Currency','Individual Client \n\nReport',1,1),(59,'Active Loans by Disbursal Period','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No\", c.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Principal Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\n\r\nl.total_expected_repayment_derived as \"Total Loan (P+I+F+Pen)\",\r\nl.total_repayment_derived as \"Total Repaid (P+I+F+Pen)\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(61,'Aging Summary (Arrears in Months)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Days In Arrears\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'0 - 30\',2 UNION\r\n		SELECT \'30 - 60\',3 UNION\r\n		SELECT \'60 - 90\',4 UNION\r\n		SELECT \'90 - 180\',5 UNION\r\n		SELECT \'180 - 360\',6 UNION\r\n		SELECT \'> 360\',7 ) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<361, \'180 - 360\', \r\n				 \'> 360\')))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(91,'Loan Account Schedule','Pentaho',NULL,'Loan',NULL,NULL,1,0),(92,'Branch Expected Cash Flow','Pentaho',NULL,'Loan',NULL,NULL,1,1),(93,'Expected Payments By Date - Basic','Table',NULL,'Loan','SELECT \r\n      ounder.name \'Office\', \r\n      IFNULL(ms.display_name,\'-\') \'Loan Officer\',\r\n	  mc.account_no \'Client Account Number\',\r\n	  mc.display_name \'Name\',\r\n	  mp.name \'Product\',\r\n	  ml.account_no \'Loan Account Number\',\r\n	  mr.duedate \'Due Date\',\r\n	  mr.installment \'Installment\',\r\n	  cu.display_symbol \'Currency\',\r\n	  mr.principal_amount- IFNULL(mr.principal_completed_derived,0) \'Principal Due\',\r\n	  mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0) \'Interest Due\', \r\n	  IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0) \'Fees Due\', \r\n	  IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0) \'Penalty Due\',\r\n      (mr.principal_amount- IFNULL(mr.principal_completed_derived,0)) +\r\n       (mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0)) + \r\n       (IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0)) + \r\n       (IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0)) \'Total Due\', \r\n     mlaa.total_overdue_derived \'Total Overdue\'\r\n										 \r\n FROM m_office mo\r\n  JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\n  \r\n  AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n	\r\n  LEFT JOIN m_client mc ON mc.office_id=ounder.id\r\n  LEFT JOIN m_loan ml ON ml.client_id=mc.id AND ml.loan_status_id=300\r\n  LEFT JOIN m_loan_arrears_aging mlaa ON mlaa.loan_id=ml.id\r\n  LEFT JOIN m_loan_repayment_schedule mr ON mr.loan_id=ml.id AND mr.completed_derived=0\r\n  LEFT JOIN m_product_loan mp ON mp.id=ml.product_id\r\n  LEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\n  LEFT JOIN m_currency cu ON cu.code=ml.currency_code\r\n WHERE mo.id=${officeId}\r\n AND (IFNULL(ml.loan_officer_id, -10) = \"${loanOfficerId}\" OR \"-1\" = \"${loanOfficerId}\")\r\n AND mr.duedate BETWEEN \'${startDate}\' AND \'${endDate}\'\r\n ORDER BY ounder.id,mr.duedate,ml.account_no','Test',1,1),(94,'Expected Payments By Date - Formatted','Pentaho',NULL,'Loan',NULL,NULL,1,1);
+INSERT INTO `stretchy_parameter` VALUES (1,'startDateSelect','startDate','startDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(2,'endDateSelect','endDate','endDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(3,'obligDateTypeSelect','obligDateType','obligDateType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Closed\" as `name` union all\r\nselect 2, \"Disbursal\" ) x\r\norder by x.`id`',NULL),(5,'OfficeIdSelectOne','officeId','Office','select','number','0',NULL,'Y',NULL,'select id, \r\nconcat(substring(\"........................................\", 1, \r\n   \n\n((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat\n\n(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy',NULL),(6,'loanOfficerIdSelectAll','loanOfficerId','Loan Officer','select','number','0',NULL,NULL,'Y','(select lo.id, lo.display_name as `Name` \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\njoin m_staff lo on lo.office_id = ounder.id\r\nwhere lo.is_loan_officer = true\r\nand o.id = ${officeId})\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',5),(10,'currencyIdSelectAll','currencyId','Currency','select','number','0',NULL,NULL,'Y','select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`',NULL),(20,'fundIdSelectAll','fundId','Fund','select','number','0',NULL,NULL,'Y','(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',NULL),(25,'loanProductIdSelectAll','loanProductId','Product','select','number','0',NULL,NULL,'Y','select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere p.currency_code = \'${currencyId}\'\r\norder by 2',10),(26,'loanPurposeIdSelectAll','loanPurposeId','Loan Purpose','select','number','0',NULL,NULL,'Y','select -10 as id, \'-\' as code_value\r\nunion all\r\nselect * from (select v.id, v.code_value\r\nfrom m_code c\r\njoin m_code_value v on v.code_id = c.id\r\nwhere c.code_name = \"loanPurpose\"\r\norder by v.order_position)  x',NULL),(100,'parTypeSelect','parType','parType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Principal Only\" as `name` union all\r\nselect 2, \"Principal + Interest\" union all\r\nselect 3, \"Principal + Interest + Fees\" union all\r\nselect 4, \"Principal + Interest + Fees + Penalties\") x\r\norder by x.`id`',NULL),(1001,'FullReportList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\r\n  \n\nrp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on rp.report_id = r.report_id\r\n  \n\nleft join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \n\n\r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on rp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where \n\nur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by \n\nr.report_category, r.report_name, rp.parameter_id',NULL),(1002,'FullParameterList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r\nsp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r\nfrom stretchy_parameter sp\r\nleft join stretchy_parameter spp on spp.parameter_id = sp.parent_parameter_id\r\nwhere sp.special is null\r\nand exists \r\n	(select \'f\' \r\n	from stretchy_report sr\r\n	join stretchy_report_parameter srp on srp.report_id = sr.report_id\r\n	where sr.report_name in(${reportListing})\r\n	and srp.parameter_id = sp.parameter_id\r\n	)\r\norder by sp.parameter_id',NULL),(1003,'reportCategoryList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, \n\nr.report_category,\r\n  rp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on \n\nrp.report_id = r.report_id\r\n  left join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.report_category = \'${reportCategory}\'\r\n  and \n\nr.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on \n\nrp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where ur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \n\n\'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by r.report_category, r.report_name, rp.parameter_id',NULL);
+INSERT INTO `stretchy_report_parameter` VALUES (1,5,NULL),(2,5,NULL),(2,6,NULL),(2,10,NULL),(2,20,NULL),(2,25,NULL),(2,26,NULL),(5,5,NULL),(5,6,NULL),(5,10,NULL),(5,20,NULL),(5,25,NULL),(5,26,NULL),(6,5,NULL),(6,6,NULL),(6,10,NULL),(6,20,NULL),(6,25,NULL),(6,26,NULL),(7,5,NULL),(7,6,NULL),(7,10,NULL),(7,20,NULL),(7,25,NULL),(7,26,NULL),(8,5,NULL),(8,6,NULL),(8,10,NULL),(8,25,NULL),(8,26,NULL),(11,5,NULL),(11,6,NULL),(11,10,NULL),(11,20,NULL),(11,25,NULL),(11,26,NULL),(11,100,NULL),(12,5,NULL),(12,6,NULL),(12,10,NULL),(12,20,NULL),(12,25,NULL),(12,26,NULL),(13,1,NULL),(13,2,NULL),(13,3,NULL),(13,5,NULL),(13,6,NULL),(13,10,NULL),(13,20,NULL),(13,25,NULL),(13,26,NULL),(14,1,NULL),(14,2,NULL),(14,3,NULL),(14,5,NULL),(14,6,NULL),(14,10,NULL),(14,20,NULL),(14,25,NULL),(14,26,NULL),(15,5,NULL),(15,6,NULL),(15,10,NULL),(15,20,NULL),(15,25,NULL),(15,26,NULL),(15,100,NULL),(16,5,NULL),(16,6,NULL),(16,10,NULL),(16,20,NULL),(16,25,NULL),(16,26,NULL),(16,100,NULL),(20,1,NULL),(20,2,NULL),(20,10,NULL),(20,20,NULL),(21,1,NULL),(21,2,NULL),(21,5,NULL),(21,10,NULL),(21,20,NULL),(48,5,'branch'),(48,2,'date'),(49,5,'branch'),(49,1,'fromDate'),(49,2,'toDate'),(50,5,'branch'),(50,1,'fromDate'),(50,2,'toDate'),(51,1,NULL),(51,2,NULL),(51,5,NULL),(51,10,NULL),(51,25,NULL),(52,5,NULL),(53,5,NULL),(53,10,NULL),(54,1,NULL),(54,2,NULL),(54,5,NULL),(54,10,NULL),(54,25,NULL),(55,5,NULL),(55,6,NULL),(55,10,NULL),(55,20,NULL),(55,25,NULL),(55,26,NULL),(56,5,NULL),(56,6,NULL),(56,10,NULL),(56,20,NULL),(56,25,NULL),(56,26,NULL),(56,100,NULL),(57,5,NULL),(57,6,NULL),(57,10,NULL),(57,20,NULL),(57,25,NULL),(57,26,NULL),(58,5,NULL),(58,6,NULL),(58,10,NULL),(58,20,NULL),(58,25,NULL),(58,26,NULL),(58,100,NULL),(59,1,NULL),(59,2,NULL),(59,5,NULL),(59,6,NULL),(59,10,NULL),(59,20,NULL),(59,25,NULL),(59,26,NULL),(61,5,NULL),(61,10,NULL),(92,1,'fromDate'),(92,5,'selectOffice'),(92,2,'toDate'),(93,1,NULL),(93,2,NULL),(93,5,NULL),(93,6,NULL),(94,2,'endDate'),(94,6,'loanOfficerId'),(94,5,'officeId'),(94,1,'startDate');
+
+insert into m_permission(grouping, `code`, entity_name, action_name, can_maker_checker)
+select 'report', concat('READ_', r.report_name), r.report_name, 'READ', false
+from stretchy_report r;
+
diff --git a/fineract-provider/.gitignore b/fineract-provider/.gitignore
new file mode 100644
index 0000000..8e9e830
--- /dev/null
+++ b/fineract-provider/.gitignore
@@ -0,0 +1,11 @@
+bin
+out/
+build
+repos
+.classpath
+.project
+.settings
+.gradle
+*.log
+!gradle/wrapper/gradle-wrapper.jar
+/gradle
diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle
new file mode 100644
index 0000000..f915b18
--- /dev/null
+++ b/fineract-provider/build.gradle
@@ -0,0 +1,346 @@
+description = '''\
+Run as:
+gradle clean tomcatrunwar
+'''
+
+buildscript {
+  repositories {
+  	 jcenter()
+  }
+
+  dependencies {
+     classpath 'org.gradle.api.plugins:gradle-tomcat-plugin:1.0',
+               'nl.javadude.gradle.plugins:license-gradle-plugin:0.11.0',
+               'org.zeroturnaround:gradle-jrebel-plugin:1.1.2',
+               'org.springframework.boot:spring-boot-gradle-plugin:1.1.6.RELEASE' // also change springDataJpaVersion below
+  }
+}
+
+apply plugin: 'rebel'
+apply plugin: 'license'
+apply plugin: 'war'
+apply plugin: 'spring-boot'
+apply plugin: 'eclipse'
+apply plugin: 'idea'
+apply plugin: 'tomcat'
+apply plugin: 'project-report'
+apply plugin: 'java'
+
+/* define the valid syntax level for source files */
+sourceCompatibility = JavaVersion.VERSION_1_7
+/* define binary compatibility version */
+targetCompatibility = JavaVersion.VERSION_1_7
+
+project.ext.springBootVersion = '1.1.6.RELEASE'
+project.ext.springVersion = '4.0.7.RELEASE'
+project.ext.springOauthVersion = '2.0.4.RELEASE'
+project.ext.jerseyVersion = '1.17'
+project.ext.springDataJpaVersion = '1.7.0.RELEASE' // also change spring-boot-gradle-plugin version above
+
+project.ext.mysqlUser='root'
+project.ext.mysqlPassword='mysql'
+
+
+group = 'org.apache.fineract'
+
+repositories {
+	jcenter()
+	// mavenLocal() // useful for local dev using MariaDB4j SNAPSHOTs (not needed for real-world non-SNAPHOT builds)
+}
+
+configurations {
+	providedRuntime // needed for Spring Boot executable WAR
+    providedCompile
+	compile
+	runtime
+	all*.exclude group: 'commons-logging'
+}
+/* Pick up dependencies based on the environemnt, defaults to production */
+if (project.hasProperty('env') && project.getProperty('env') == 'dev') {
+    apply from:  'dev-dependencies.gradle'
+}  else {
+    apply from: 'dependencies.gradle'
+}
+
+/* Enable Oauth2 authentication based on environment, default to HTTP basic auth */
+if (project.hasProperty('security') && project.getProperty('security') == 'oauth') {
+	copy {
+	    from './properties/oauth/'
+	    into 'src/main/resources/'
+	    include '*.properties'
+	}
+}  else {
+	copy {
+	    from './properties/basicauth/'
+	    into 'src/main/resources/'
+	    include '*.properties'
+	}
+}
+
+task dist(type:Zip){
+	baseName = 'fineractplatform'
+	version = qualifyVersionIfNecessary(releaseVersion)
+    includeEmptyDirs = true
+	from('../') {
+		fileMode = 0755
+		include '*.md'
+	}
+    from('src/main/dist') {
+        fileMode = 0755
+        include '*'
+    }
+	from('../apps') {
+		fileMode = 0755
+        include '**/*'
+		into('apps')
+	}
+    from('../api-docs/') {
+        fileMode = 0755
+        include '*'
+        into('api-docs')
+    }
+    from('../fineract-db/') {
+        fileMode = 0755
+        include '*.sql'
+        into('database')
+    }
+    from('src/main/resources/sql/migrations') {
+        fileMode = 0755
+        include '**/*'
+        into('database/migrations')
+    }
+    
+    from war.outputs.files
+    into(baseName + '-' + version)
+}
+
+war {
+    war.finalizedBy(bootRepackage)
+}
+
+license {
+    header rootProject.file('../LICENSE.md')
+    excludes(["**/*.html", "**/*.mustache", "**/*.sql", "**/package-info.java", "**/keystore.jks"])
+    strictCheck true
+}
+
+tomcatRun {
+    httpPort = 8080
+    httpsPort = 8443
+    stopPort = 8081
+    stopKey=   'stopKey'
+    enableSSL = true
+    configFile = file('src/test/resources/META-INF/context.xml')
+}
+
+tomcatRunWar {
+    httpPort = 8080
+    httpsPort = 8443
+    stopPort = 8081
+    stopKey=   'stopKey'
+    enableSSL = true
+    keystoreFile = file('src/main/resources/keystore.jks')
+    keystorePass = 'openmf'
+    configFile = file('src/test/resources/META-INF/context.xml')
+}
+
+/* http://stackoverflow.com/questions/19653311/jpa-repository-works-in-idea-and-production-but-not-in-gradle */
+sourceSets.main.output.resourcesDir = sourceSets.main.output.classesDir
+sourceSets.test.output.resourcesDir = sourceSets.test.output.classesDir
+
+/* Exclude maria db and embedded tomcat related files for non dev builds */
+if (!(project.hasProperty('env') && project.getProperty('env') == 'dev')) {
+sourceSets {
+    main {
+        java {
+            exclude '**/Server*'
+            exclude '**/MariaDB4j*'
+            exclude '**/EmbeddedTomcatWithSSLConfiguration.java'
+        }
+    }
+    test {
+    	java {
+    		exclude '**/core/boot/tests/**'
+    	}
+    }
+}
+}
+
+sourceSets {
+ integrationTest {
+    	compileClasspath += main.output + test.output
+        runtimeClasspath += main.output + test.output
+    }
+}
+
+configurations {
+    integrationTestCompile.extendsFrom testCompile
+    integrationTestRuntime.extendsFrom testRuntime
+}
+
+task integrationTest(type:Test){
+    description = "Run integration tests (located in src/integrationTest/java). Starts tomcat in daemon mode before executing the tests."
+    it.dependsOn war
+    doFirst {
+        tomcatRunWar.daemon = true
+        tomcatRunWar.execute()
+    }
+
+
+    testClassesDir = project.sourceSets.integrationTest.output.classesDir
+    classpath = project.sourceSets.integrationTest.runtimeClasspath
+}
+
+
+import groovy.sql.Sql
+
+repositories {
+    mavenCentral()
+}
+configurations {
+    driver
+}
+dependencies {
+    driver 'mysql:mysql-connector-java:5.1.16'
+}
+
+URLClassLoader loader = GroovyObject.class.classLoader
+configurations.driver.each {File file ->
+    loader.addURL(file.toURL())
+}
+
+task createDB<<{
+    description= "Creates the Database. Needs database name to be passed (like: -PdbName=someDBname)"
+    sql = Sql.newInstance( 'jdbc:mysql://localhost:3306/', mysqlUser, mysqlPassword, 'com.mysql.jdbc.Driver' )
+    sql.execute( 'create database '+"`$dbName`" )
+}
+
+task dropDB<<{
+    description= "Drops the specified database. The database name has to be passed (like: -PdbName=someDBname)"
+    sql = Sql.newInstance( 'jdbc:mysql://localhost:3306/', mysqlUser, mysqlPassword, 'com.mysql.jdbc.Driver' )
+    sql.execute( 'DROP DATABASE '+"`$dbName`")
+}
+task setBlankPassword<<{
+    sql = Sql.newInstance( 'jdbc:mysql://localhost:3306/', mysqlUser, mysqlPassword, 'com.mysql.jdbc.Driver' )
+    sql.execute('USE `mifosplatform-tenants`')
+    sql.execute('UPDATE mifosplatform-tenants.tenants SET schema_server = \'localhost\', schema_server_port = \'3306\', schema_username = \'mifos\', schema_password = \'mysql\' WHERE id=1;')
+}
+
+
+apply plugin: 'flyway'
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath "org.flywaydb:flyway-gradle-plugin:3.0" // version upgraded during Spring Boot & MariaDB4j work, as prev. used v0.2 didn't work well after *.sql moved from fineract-db to fineract-provider/src/main/resources (new version also has clearer errors, e.g. in case of missing DB)
+        classpath 'mysql:mysql-connector-java:5.1.22'
+    }
+}
+
+
+flyway {
+    url = "jdbc:mysql://localhost:3306/mifostenant-default"
+    user = mysqlUser
+    password = mysqlPassword
+}
+
+task migrateTenantDB<<{
+    description="Migrates a Tenant DB. Optionally can pass dbName. Defaults to 'mifostenant-default' (Example: -PdbName=someTenantDBname)"
+
+	def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/core_db'
+	def tenantDbName = 'mifostenant-default';
+    if (rootProject.hasProperty("dbName")) {
+		tenantDbName = rootProject.getProperty("dbName")
+	}
+	
+    flyway.url= "jdbc:mysql://localhost:3306/$tenantDbName"
+    flyway.locations= [filePath]
+    /**We use ${ as the prefix for strecthy reporting, do not want them to be interpreted by Flyway**/
+    flyway.placeholderPrefix = "\$\${"
+    flywayMigrate.execute()
+}
+
+task showTenantDBInfo<<{
+    description="Shows the migration info for a Tenant DB. Optionally can pass dbName. Defaults to 'mifostenant-default' (Example: -PdbName=someTenantDBname)"
+    
+	def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/core_db'
+    def tenantDbName = 'mifostenant-default';
+    if (rootProject.hasProperty("dbName")) {
+		tenantDbName = rootProject.getProperty("dbName")
+	}
+	
+    flyway.url= "jdbc:mysql://localhost:3306/$tenantDbName"
+    flyway.locations= [filePath]
+    flywayInfo.execute()
+}
+
+
+task migrateTenantListDB<<{
+    description="Migrates a Tenant List DB. Optionally can pass dbName. Defaults to 'mifosplatform-tenants' (Example: -PdbName=someDBname)"
+
+	def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/list_db'
+ 	def tenantsDbName = 'mifosplatform-tenants';
+    if (rootProject.hasProperty("dbName")) {
+		tenantsDbName = rootProject.getProperty("dbName")
+	}
+	
+    flyway.url= "jdbc:mysql://localhost:3306/$tenantsDbName"
+    flyway.locations= [filePath]
+
+    flywayMigrate.execute()
+}
+
+task showTenantListDBInfo<<{
+    description="Shows the migration info for a List DB. Optionally can pass dbName. Defaults to 'mifosplatform-tenants' (Example: -PdbName=someDBname)"
+    
+	def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/list_db'
+    def tenantsDbName = 'mifosplatform-tenants';
+    if (rootProject.hasProperty("dbName")) {
+		tenantsDbName = rootProject.getProperty("dbName")
+	}
+    
+    flyway.url= "jdbc:mysql://localhost:3306/$tenantsDbName"
+    flyway.locations= [filePath]
+    flywayInfo.execute()
+}
+
+task repairTenantDB<<{
+    description="repair migrate"
+    
+	def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/list_db'
+    def tenantsDbName = 'mifosplatform-tenants';
+    if (rootProject.hasProperty("dbName")) {
+        tenantsDbName = rootProject.getProperty("dbName")
+    }
+    
+    flyway.url= "jdbc:mysql://localhost:3306/$tenantsDbName"
+    flyway.locations= [filePath]
+    flywayRepair.execute()
+}
+
+/*
+* Support publication of artifacts versioned by topic branch.
+* CI builds supply `-P BRANCH_NAME=<TOPIC>` to gradle at build time.
+* If <TOPIC> starts with 'MIFOSX-', change version
+* from BUILD-SNAPSHOT => <TOPIC>-SNAPSHOT
+* e.g. 1.1.0.BUILD-SNAPSHOT => 1.0.0.MIFOSX-1234-SNAPSHOT
+*/
+def qualifyVersionIfNecessary(version) {
+
+	if (rootProject.hasProperty("BRANCH_NAME")) {
+		def qualifier = rootProject.getProperty("BRANCH_NAME")
+		if (qualifier.startsWith("MIFOSX-")) {
+			return version.replace('BUILD', qualifier)
+		}
+	}
+	return version
+}
+
+springBoot {
+    mainClass = 'org.apache.fineract.ServerWithMariaDB4jApplication'
+}
+bootRepackage {
+    mainClass = 'org.apache.fineract.ServerWithMariaDB4jApplication'
+}
diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle
new file mode 100644
index 0000000..e40a330
--- /dev/null
+++ b/fineract-provider/dependencies.gradle
@@ -0,0 +1,80 @@
+dependencies {
+        def tomcatVersion = '7.0.54'
+        tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
+               "org.apache.tomcat.embed:tomcat-embed-logging-log4j:${tomcatVersion}" // NOT tomcat-embed-logging-juli (http://stackoverflow.com/questions/23963049/classcircularityerror-java-util-logging-logrecord-running-gradle-webapp-with-ja)
+        tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
+            exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
+        }
+        tomcat "org.apache.tomcat:tomcat-dbcp:${tomcatVersion}"
+
+    providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
+
+    providedCompile(
+//              [group: 'javax.servlet', name: 'servlet-api', version: '2.5'],
+            )
+
+    compile(
+               // [group: 'ch.vorburger.mariaDB4j', name: 'mariaDB4j', version: '2.1.3'],
+
+                [group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion],
+                [group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: springBootVersion],
+                [group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: springBootVersion],
+
+                [group: 'org.springframework', name: 'spring-context-support', version: springVersion],
+				
+	       		[group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: springOauthVersion],
+
+                [group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'],
+                [group: 'com.sun.jersey', name: 'jersey-core', version: jerseyVersion],
+                [group: 'com.sun.jersey', name: 'jersey-servlet', version: jerseyVersion],
+                [group: 'com.sun.jersey', name: 'jersey-server', version: jerseyVersion],
+                [group: 'com.sun.jersey', name: 'jersey-json', version: jerseyVersion],
+                [group: 'com.sun.jersey.contribs', name: 'jersey-spring', version: jerseyVersion],
+                [group: 'com.sun.jersey.contribs', name: 'jersey-multipart', version: jerseyVersion],
+
+                [group: 'com.squareup.retrofit', name: 'retrofit', version: '1.6.1'],
+                [group: 'com.squareup.okhttp', name: 'okhttp', version: '2.0.0'],
+                [group: 'com.squareup.okhttp', name: 'okhttp-urlconnection', version: '2.0.0'],
+
+
+                [group: 'com.google.code.gson', name: 'gson', version: '2.2.4'],
+                [group: 'com.google.guava', name: 'guava', version: '15.0'],
+
+                [group: 'joda-time', name: 'joda-time', version: '2.4'],
+                [group: 'net.sourceforge.javacsv', name: 'javacsv', version: '2.0'],
+                [group: 'org.apache.commons', name: 'commons-email', version: '1.3.3'],
+                [group: 'org.apache.commons', name: 'commons-lang3', version: '3.3.2'],
+
+                // no slf4j & logback here (anymore), as spring-boot-starter-logging already brings this now, better assembled (log4j-over-slf4j was originally forgotten here)
+
+                [group: 'mysql', name: 'mysql-connector-java', version: '5.1.27'],
+               // [group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: tomcatVersion],
+
+
+                [group: 'org.apache.poi',name: 'poi', version: '3.9'],
+                [group: 'org.apache.poi',name: 'poi-ooxml', version: '3.9'],
+                [group: 'org.apache.poi',name: 'poi-ooxml-schemas', version: '3.9'],
+
+                [group: 'com.lowagie', name: 'itext', version: '2.1.7'],
+                [group: 'com.lowagie', name: 'itext-rtf', version: '2.1.7'],
+                [group: 'org.mnode.ical4j', name: 'ical4j', version: '1.0.4'],
+                [group: 'com.googlecode.flyway', name: 'flyway-core', version: '2.1.1'],
+                [group: 'org.quartz-scheduler', name: 'quartz', version: '2.1.7'],
+                [group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.2.1'],
+                [group: 'net.sf.ehcache', name: 'ehcache', version: '2.7.2'],
+                [group: 'com.github.spullara.mustache.java', name: 'compiler', version: '0.8.12'],
+                [group: 'com.jayway.jsonpath', name: 'json-path', version: '0.9.1'],
+
+                // Although fineract (at the time of writing) doesn't have any compile time dep. on this,
+                // it's useful to have this for the Spring Boot TestRestTemplate http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rest-templates-test-utility
+                [group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.5'],
+                // Once we've switched to Java 8 this dep can be removed.
+                [group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.0']
+     )
+     testCompile 'junit:junit:4.11',
+                 'junit:junit-dep:4.11',
+                 'org.mockito:mockito-core:1.9.5',
+                 'com.jayway.restassured:rest-assured:2.3.3',
+                 [group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion]
+
+}
diff --git a/fineract-provider/dev-dependencies.gradle b/fineract-provider/dev-dependencies.gradle
new file mode 100644
index 0000000..83039f7
--- /dev/null
+++ b/fineract-provider/dev-dependencies.gradle
@@ -0,0 +1,79 @@
+dependencies {
+        def tomcatVersion = '7.0.54'
+        tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
+               "org.apache.tomcat.embed:tomcat-embed-logging-log4j:${tomcatVersion}" // NOT tomcat-embed-logging-juli (http://stackoverflow.com/questions/23963049/classcircularityerror-java-util-logging-logrecord-running-gradle-webapp-with-ja)
+        tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
+            exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
+        }
+        tomcat "org.apache.tomcat:tomcat-dbcp:${tomcatVersion}"
+
+    providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
+
+    providedCompile(
+//              [group: 'javax.servlet', name: 'servlet-api', version: '2.5'],
+            )
+
+    compile(
+                [group: 'ch.vorburger.mariaDB4j', name: 'mariaDB4j', version: '2.1.3'],
+
+                [group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion],
+                [group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: springBootVersion],
+                [group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: springBootVersion],
+
+                [group: 'org.springframework', name: 'spring-context-support', version: springVersion],
+				[group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: springOauthVersion],
+
+                [group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'],
+                [group: 'com.sun.jersey', name: 'jersey-core', version: jerseyVersion],
+                [group: 'com.sun.jersey', name: 'jersey-servlet', version: jerseyVersion],
+                [group: 'com.sun.jersey', name: 'jersey-server', version: jerseyVersion],
+                [group: 'com.sun.jersey', name: 'jersey-json', version: jerseyVersion],
+                [group: 'com.sun.jersey.contribs', name: 'jersey-spring', version: jerseyVersion],
+                [group: 'com.sun.jersey.contribs', name: 'jersey-multipart', version: jerseyVersion],
+
+                [group: 'com.squareup.retrofit', name: 'retrofit', version: '1.6.1'],
+                [group: 'com.squareup.okhttp', name: 'okhttp', version: '2.0.0'],
+                [group: 'com.squareup.okhttp', name: 'okhttp-urlconnection', version: '2.0.0'],
+
+
+                [group: 'com.google.code.gson', name: 'gson', version: '2.2.4'],
+                [group: 'com.google.guava', name: 'guava', version: '15.0'],
+
+                [group: 'joda-time', name: 'joda-time', version: '2.4'],
+                [group: 'net.sourceforge.javacsv', name: 'javacsv', version: '2.0'],
+                [group: 'org.apache.commons', name: 'commons-email', version: '1.3.3'],
+                [group: 'org.apache.commons', name: 'commons-lang3', version: '3.3.2'],
+
+                // no slf4j & logback here (anymore), as spring-boot-starter-logging already brings this now, better assembled (log4j-over-slf4j was originally forgotten here)
+
+                [group: 'mysql', name: 'mysql-connector-java', version: '5.1.27'],
+                [group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: tomcatVersion],
+
+               
+                [group: 'org.apache.poi',name: 'poi', version: '3.9'],
+                [group: 'org.apache.poi',name: 'poi-ooxml', version: '3.9'],
+                [group: 'org.apache.poi',name: 'poi-ooxml-schemas', version: '3.9'],
+
+                [group: 'com.lowagie', name: 'itext', version: '2.1.7'],
+                [group: 'com.lowagie', name: 'itext-rtf', version: '2.1.7'],
+                [group: 'org.mnode.ical4j', name: 'ical4j', version: '1.0.4'],
+                [group: 'com.googlecode.flyway', name: 'flyway-core', version: '2.1.1'],
+                [group: 'org.quartz-scheduler', name: 'quartz', version: '2.1.7'],
+                [group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.2.1'],
+                [group: 'net.sf.ehcache', name: 'ehcache', version: '2.7.2'],
+                [group: 'com.github.spullara.mustache.java', name: 'compiler', version: '0.8.12'],
+                [group: 'com.jayway.jsonpath', name: 'json-path', version: '0.9.1'],
+
+                // Although fineract (at the time of writing) doesn't have any compile time dep. on this,
+                // it's useful to have this for the Spring Boot TestRestTemplate http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rest-templates-test-utility
+                [group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.5'],
+                // Once we've switched to Java 8 this dep can be removed.
+                [group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.0']
+     )
+     testCompile 'junit:junit:4.11',
+                 'junit:junit-dep:4.11',
+                 'org.mockito:mockito-core:1.9.5',
+                 'com.jayway.restassured:rest-assured:2.3.3',
+                 [group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion]
+
+}
diff --git a/fineract-provider/gradle.properties b/fineract-provider/gradle.properties
new file mode 100644
index 0000000..706f6ee
--- /dev/null
+++ b/fineract-provider/gradle.properties
@@ -0,0 +1,3 @@
+releaseVersion=16.01.2.RELEASE
+
+
diff --git a/fineract-provider/gradle/wrapper/gradle-wrapper.jar b/fineract-provider/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..faa569a
--- /dev/null
+++ b/fineract-provider/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/fineract-provider/gradle/wrapper/gradle-wrapper.properties b/fineract-provider/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..2b85462
--- /dev/null
+++ b/fineract-provider/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Oct 22 19:36:27 IST 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.8-bin.zip
diff --git a/fineract-provider/gradlew b/fineract-provider/gradlew
new file mode 100755
index 0000000..210ae59
--- /dev/null
+++ b/fineract-provider/gradlew
@@ -0,0 +1,167 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8005,server=y,suspend=n"
+
+CATALINA_OPTS="-Xms512m -Xmx2048m"
+JAVA_OPTS="-Xms512m -Xmx2048m -XX:PermSize=256m -XX:MaxPermSize=512m"
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/fineract-provider/gradlew.bat b/fineract-provider/gradlew.bat
new file mode 100644
index 0000000..af905cc
--- /dev/null
+++ b/fineract-provider/gradlew.bat
@@ -0,0 +1,93 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8005,server=y,suspend=n

+

+set CATALINA_OPTS=-Xms512m -Xmx512m

+set JAVA_OPTS=-Xms512m -Xmx512m -XX:PermSize=256m -XX:MaxPermSize=512m

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/fineract-provider/properties/basicauth/application.properties b/fineract-provider/properties/basicauth/application.properties
new file mode 100644
index 0000000..adbef74
--- /dev/null
+++ b/fineract-provider/properties/basicauth/application.properties
@@ -0,0 +1,2 @@
+
+spring.profiles.default=basicauth
\ No newline at end of file
diff --git a/fineract-provider/properties/oauth/application.properties b/fineract-provider/properties/oauth/application.properties
new file mode 100644
index 0000000..05ef979
--- /dev/null
+++ b/fineract-provider/properties/oauth/application.properties
@@ -0,0 +1,3 @@
+
+spring.profiles.default=basicauth
+spring.profiles.active=oauth
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountNumberPreferencesTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountNumberPreferencesTest.java
new file mode 100644
index 0000000..97c3fac
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountNumberPreferencesTest.java
@@ -0,0 +1,486 @@
+/**
+ * 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.integrationtests;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.CenterDomain;
+import org.apache.fineract.integrationtests.common.CenterHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GroupHelper;
+import org.apache.fineract.integrationtests.common.OfficeHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.system.AccountNumberPreferencesHelper;
+import org.apache.fineract.integrationtests.common.system.CodeHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class AccountNumberPreferencesTest {
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseValidationError;
+    private ResponseSpecification responseNotFoundError;
+    private ResponseSpecification responseForbiddenError;
+    private Integer clientId;
+    private Integer loanProductId;
+    private Integer loanId;
+    private Integer savingsProductId;
+    private Integer savingsId;
+    private final String loanPrincipalAmount = "100000.00";
+    private final String numberOfRepayments = "12";
+    private final String interestRatePerPeriod = "18";
+    private final String dateString = "4 September 2014";
+    private final String minBalanceForInterestCalculation = null;
+    private final String minRequiredBalance = null;
+    private final String enforceMinRequiredBalance = "false";
+    private LoanTransactionHelper loanTransactionHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+    private AccountNumberPreferencesHelper accountNumberPreferencesHelper;
+    private Integer clientAccountNumberPreferenceId;
+    private Integer loanAccountNumberPreferenceId;
+    private Integer savingsAccountNumberPreferenceId;
+    private Integer groupsAccountNumberPreferenceId;
+    private Integer centerAccountNumberPreferenceId;
+    private final String MINIMUM_OPENING_BALANCE = "1000.0";
+    private final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+    private Boolean isAccountPreferenceSetUp = false;
+    private Integer clientTypeCodeId;
+    private String clientCodeValueName;
+    private Integer clientCodeValueId;
+    private final String clientTypeName = "CLIENT_TYPE";
+    private final String officeName = "OFFICE_NAME";
+    private final String loanShortName = "LOAN_PRODUCT_SHORT_NAME";
+    private final String savingsShortName = "SAVINGS_PRODUCT_SHORT_NAME";
+    private Integer groupID;
+    private Integer centerId;
+    private String groupAccountNo;
+    
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseValidationError = new ResponseSpecBuilder().expectStatusCode(400).build();
+        this.responseNotFoundError = new ResponseSpecBuilder().expectStatusCode(404).build();
+        this.responseForbiddenError = new ResponseSpecBuilder().expectStatusCode(403).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.accountNumberPreferencesHelper = new AccountNumberPreferencesHelper(this.requestSpec, this.responseSpec);
+
+    }
+
+    @Test
+    public void testAccountNumberPreferences() {
+
+        /* Create Loan and Savings Product */
+        this.createLoanAndSavingsProduct();
+
+        /* Ensure no account number preferences are present in the system */
+        this.deleteAllAccountNumberPreferences();
+
+        /*
+         * Validate the default account number generation rules for clients,
+         * loans and savings accounts.
+         */
+        this.validateDefaultAccountNumberGeneration();
+
+        /* Create and Validate account number preferences */
+        this.createAccountNumberPreference();
+
+        /*
+         * Validate account number preference rules apply to Clients,Loans and
+         * Saving Accounts
+         */
+        this.validateAccountNumberGenerationWithPreferences();
+
+        /* Validate account number preferences Updation */
+        this.updateAccountNumberPreference();
+
+        /*
+         * Validate account number preference rules apply to Clients,Loans and
+         * Saving Accounts after Updation
+         */
+        this.validateAccountNumberGenerationWithPreferences();
+
+        /* Delete all account number preferences */
+        this.deleteAllAccountNumberPreferences();
+
+    }
+
+    private void createLoanAndSavingsProduct() {
+        this.createLoanProduct();
+        this.createSavingsProduct();
+    }
+
+    private void deleteAllAccountNumberPreferences() {
+        ArrayList<HashMap<String, Object>> preferenceIds = this.accountNumberPreferencesHelper.getAllAccountNumberPreferences();
+        /* Deletion of valid account preference ID */
+        for (HashMap<String, Object> preferenceId : preferenceIds) {
+            Integer id = (Integer) preferenceId.get("id");
+            HashMap<String, Object> delResponse = this.accountNumberPreferencesHelper.deleteAccountNumberPreference(id, this.responseSpec,
+                    "");
+            System.out.println("Successfully deleted account number preference (ID: " + delResponse.get("resourceId") + ")");
+        }
+        /* Deletion of invalid account preference ID should fail */
+        System.out
+                .println("---------------------------------DELETING ACCOUNT NUMBER PREFERENCE WITH INVALID ID------------------------------------------");
+
+        HashMap<String, Object> deletionError = this.accountNumberPreferencesHelper.deleteAccountNumberPreference(10,
+                this.responseNotFoundError, "");
+        Assert.assertEquals("error.msg.resource.not.found", deletionError.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+    }
+
+    private void validateDefaultAccountNumberGeneration() {
+        this.createAndValidateClientEntity(this.isAccountPreferenceSetUp);
+        this.createAndValidateLoanEntity(this.isAccountPreferenceSetUp);
+        this.createAndValidateSavingsEntity(this.isAccountPreferenceSetUp);
+        this.createAndValidateGroup(this.isAccountPreferenceSetUp);
+        this.createAndValidateCenter(this.isAccountPreferenceSetUp);
+    }
+
+    private void validateAccountNumberGenerationWithPreferences() {
+        this.isAccountPreferenceSetUp = true;
+        this.createAndValidateClientEntity(this.isAccountPreferenceSetUp);
+        this.createAndValidateLoanEntity(this.isAccountPreferenceSetUp);
+        this.createAndValidateSavingsEntity(this.isAccountPreferenceSetUp);
+        this.createAndValidateGroup(this.isAccountPreferenceSetUp);
+        this.createAndValidateCenter(this.isAccountPreferenceSetUp);
+    }
+
+    private void createAccountNumberPreference() {
+        this.clientAccountNumberPreferenceId = (Integer) this.accountNumberPreferencesHelper.createClientAccountNumberPreference(
+                this.responseSpec, "resourceId");
+        System.out.println("Successfully created account number preferences for Client (ID: " + this.clientAccountNumberPreferenceId);
+
+        this.loanAccountNumberPreferenceId = (Integer) this.accountNumberPreferencesHelper.createLoanAccountNumberPreference(
+                this.responseSpec, "resourceId");
+        System.out.println("Successfully created account number preferences for Loan (ID: " + this.loanAccountNumberPreferenceId);
+
+        this.savingsAccountNumberPreferenceId = (Integer) this.accountNumberPreferencesHelper.createSavingsAccountNumberPreference(
+                this.responseSpec, "resourceId");
+        System.out.println("Successfully created account number preferences for Savings (ID: " + this.savingsAccountNumberPreferenceId);
+        
+        this.groupsAccountNumberPreferenceId = (Integer) this.accountNumberPreferencesHelper.createGroupsAccountNumberPreference(
+                this.responseSpec, "resourceId");
+        System.out.println("Successfully created account number preferences for Groups (ID: " + this.groupsAccountNumberPreferenceId);
+        
+        this.centerAccountNumberPreferenceId = (Integer) this.accountNumberPreferencesHelper.createCenterAccountNumberPreference(
+                this.responseSpec, "resourceId");
+        System.out.println("Successfully created account number preferences for Center (ID: " + this.centerAccountNumberPreferenceId);
+
+        this.accountNumberPreferencesHelper.verifyCreationOfAccountNumberPreferences(this.clientAccountNumberPreferenceId,
+                this.loanAccountNumberPreferenceId, this.savingsAccountNumberPreferenceId, this.groupsAccountNumberPreferenceId, 
+                this.centerAccountNumberPreferenceId, this.responseSpec, this.requestSpec);
+
+        this.createAccountNumberPreferenceInvalidData("1000", "1001");
+        this.createAccountNumberPreferenceDuplicateData("1", "101");
+
+    }
+
+    private void createAccountNumberPreferenceDuplicateData(final String accountType, final String prefixType) {
+        /* Creating account Preference with duplicate data should fail */
+        System.out
+                .println("---------------------------------CREATING ACCOUNT NUMBER PREFERENCE WITH DUPLICATE DATA------------------------------------------");
+
+        HashMap<String, Object> creationError = this.accountNumberPreferencesHelper.createAccountNumberPreferenceWithInvalidData(
+                this.responseForbiddenError, accountType, prefixType, "");
+
+        Assert.assertEquals("error.msg.account.number.format.duplicate.account.type",
+                creationError.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    private void createAccountNumberPreferenceInvalidData(final String accountType, final String prefixType) {
+
+        /* Creating account Preference with invalid data should fail */
+        System.out
+                .println("---------------------------------CREATING ACCOUNT NUMBER PREFERENCE WITH INVALID DATA------------------------------------------");
+
+        HashMap<String, Object> creationError = this.accountNumberPreferencesHelper.createAccountNumberPreferenceWithInvalidData(
+                this.responseValidationError, accountType, prefixType, "");
+
+        if (creationError.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE).equals(
+                "validation.msg.accountNumberFormat.accountType.is.not.within.expected.range")) {
+            Assert.assertEquals("validation.msg.accountNumberFormat.accountType.is.not.within.expected.range",
+                    creationError.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+        } else if (creationError.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE).equals(
+                "validation.msg.accountNumberFormat.prefixType.is.not.one.of.expected.enumerations")) {
+            Assert.assertEquals("validation.msg.accountNumberFormat.prefixType.is.not.one.of.expected.enumerations",
+                    creationError.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+        }
+    }
+
+    private void updateAccountNumberPreference() {
+        HashMap<String, Object> accountNumberPreferences = this.accountNumberPreferencesHelper.updateAccountNumberPreference(
+                this.clientAccountNumberPreferenceId, "101", this.responseSpec, "");
+
+        System.out.println("--------------------------UPDATION SUCCESSFUL FOR ACCOUNT NUMBER PREFERENCE ID "
+                + accountNumberPreferences.get("resourceId"));
+
+        this.accountNumberPreferencesHelper.verifyUpdationOfAccountNumberPreferences((Integer) accountNumberPreferences.get("resourceId"),
+                this.responseSpec, this.requestSpec);
+
+        /* Update invalid account preference id should fail */
+        System.out
+                .println("---------------------------------UPDATING ACCOUNT NUMBER PREFERENCE WITH INVALID DATA------------------------------------------");
+
+        /* Invalid Account Type */
+        HashMap<String, Object> updationError = this.accountNumberPreferencesHelper.updateAccountNumberPreference(9999, "101",
+                this.responseNotFoundError, "");
+        if (updationError.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE).equals("error.msg.resource.not.found")) {
+            Assert.assertEquals("error.msg.resource.not.found", updationError.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+        }
+        /* Invalid Prefix Type */
+        HashMap<String, Object> updationError1 = this.accountNumberPreferencesHelper.updateAccountNumberPreference(
+                this.clientAccountNumberPreferenceId, "103", this.responseValidationError, "");
+
+        Assert.assertEquals("validation.msg.validation.errors.exist", updationError1.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    private void createAndValidateClientEntity(Boolean isAccountPreferenceSetUp) {
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        if (isAccountPreferenceSetUp) {
+            this.createAndValidateClientBasedOnAccountPreference();
+        } else {
+            this.createAndValidateClientWithoutAccountPreference();
+        }
+    }
+    
+    private void createAndValidateGroup(Boolean isAccountPreferenceSetUp) {
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec);
+        GroupHelper.verifyGroupCreatedOnServer(this.requestSpec, this.responseSpec, groupID);
+
+        this.groupID = GroupHelper.activateGroup(this.requestSpec, this.responseSpec, groupID.toString());
+        GroupHelper.verifyGroupActivatedOnServer(this.requestSpec, this.responseSpec, groupID, true);
+        
+        final String GROUP_URL = "/fineract-provider/api/v1/groups/" + this.groupID + "?" + Utils.TENANT_IDENTIFIER;
+        this.groupAccountNo = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "accountNo");
+        
+        if (isAccountPreferenceSetUp) {
+        	String groupsPrefixName = (String) this.accountNumberPreferencesHelper.getAccountNumberPreference(
+                    this.groupsAccountNumberPreferenceId, "prefixType.value");
+        	
+            if (groupsPrefixName.equals(this.officeName)) {
+            	
+                final String groupOfficeName = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "officeName");
+                
+                this.validateAccountNumberLengthAndStartsWithPrefix(this.groupAccountNo, groupOfficeName);
+            }
+        } else {
+            validateAccountNumberLengthAndStartsWithPrefix(this.groupAccountNo, null);
+        }
+    }
+    
+    private void createAndValidateCenter(Boolean isAccountPreferenceSetUp) {
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        Integer officeId = new OfficeHelper(requestSpec, responseSpec).createOffice("01 July 2007");
+
+        String name = "CenterCreation" + new Timestamp(new java.util.Date().getTime());
+        this.centerId = CenterHelper.createCenter(name, officeId, requestSpec, responseSpec);
+        CenterDomain center = CenterHelper.retrieveByID(centerId, requestSpec, responseSpec);
+        Assert.assertNotNull(center);
+        Assert.assertTrue(center.getName().equals(name));
+        
+        if (isAccountPreferenceSetUp) {
+        	String centerPrefixName = (String) this.accountNumberPreferencesHelper.getAccountNumberPreference(
+                    this.centerAccountNumberPreferenceId, "prefixType.value");
+            final String CENTER_URL = "/fineract-provider/api/v1/centers/" + this.centerId + "?" + Utils.TENANT_IDENTIFIER;
+        	
+            if (centerPrefixName.equals(this.officeName)) {
+                final String centerOfficeName = Utils.performServerGet(requestSpec, responseSpec, CENTER_URL, "officeName");  
+                this.validateAccountNumberLengthAndStartsWithPrefix(center.getAccountNo(), centerOfficeName);
+            }
+        } else {	
+            validateAccountNumberLengthAndStartsWithPrefix(center.getAccountNo(), null);
+        }
+    }
+    
+
+    private void createAndValidateClientWithoutAccountPreference() {
+        this.clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(this.clientId);
+        String clientAccountNo = (String) ClientHelper.getClient(requestSpec, responseSpec, this.clientId.toString(), "accountNo");
+        validateAccountNumberLengthAndStartsWithPrefix(clientAccountNo, null);
+    }
+
+    private void createAndValidateClientBasedOnAccountPreference() {
+        final String codeName = "ClientType";
+        String clientAccountNo = null;
+        String clientPrefixName = (String) this.accountNumberPreferencesHelper.getAccountNumberPreference(
+                this.clientAccountNumberPreferenceId, "prefixType.value");
+        if (clientPrefixName.equals(this.clientTypeName)) {
+
+            /* Retrieve Code id for the Code "ClientType" */
+            HashMap<String, Object> code = CodeHelper.getCodeByName(this.requestSpec, this.responseSpec, codeName);
+            this.clientTypeCodeId = (Integer) code.get("id");
+
+            /* Retrieve/Create Code Values for the Code "ClientType" */
+            HashMap<String, Object> codeValue = CodeHelper.retrieveOrCreateCodeValue(this.clientTypeCodeId, this.requestSpec,
+                    this.responseSpec);
+
+            this.clientCodeValueName = (String) codeValue.get("name");
+            this.clientCodeValueId = (Integer) codeValue.get("id");
+
+            /* Create Client with Client Type */
+            this.clientId = ClientHelper.createClientForAccountPreference(this.requestSpec, this.responseSpec, this.clientCodeValueId,
+                    "clientId");
+
+            Assert.assertNotNull(clientId);
+
+            clientAccountNo = (String) ClientHelper.getClient(this.requestSpec, this.responseSpec, this.clientId.toString(), "accountNo");
+            this.validateAccountNumberLengthAndStartsWithPrefix(clientAccountNo, this.clientCodeValueName);
+
+        } else if (clientPrefixName.equals(this.officeName)) {
+            this.clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+            Assert.assertNotNull(clientId);
+            clientAccountNo = (String) ClientHelper.getClient(requestSpec, responseSpec, this.clientId.toString(), "accountNo");
+            String officeName = (String) ClientHelper.getClient(requestSpec, responseSpec, this.clientId.toString(), "officeName");
+            this.validateAccountNumberLengthAndStartsWithPrefix(clientAccountNo, officeName);
+        }
+    }
+
+    private void validateAccountNumberLengthAndStartsWithPrefix(final String accountNumber, final String prefix) {
+        if (prefix != null) {
+            Assert.assertEquals(accountNumber.length(), prefix.length() + 9);
+            Assert.assertTrue(accountNumber.startsWith(prefix));
+        } else {
+            Assert.assertEquals(accountNumber.length(), 9);
+        }
+    }
+
+    private void createLoanProduct() {
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        System.out.println("---------------------------------CREATING LOAN PRODUCT------------------------------------------");
+
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount)
+                .withNumberOfRepayments(numberOfRepayments).withinterestRatePerPeriod(interestRatePerPeriod)
+                .withInterestRateFrequencyTypeAsYear().build(null);
+
+        this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+        System.out.println("Successfully created loan product  (ID: " + this.loanProductId + ")");
+    }
+
+    private void createAndValidateLoanEntity(Boolean isAccountPreferenceSetUp) {
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        System.out.println("---------------------------------NEW LOAN APPLICATION------------------------------------------");
+
+        final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount)
+                .withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsMonths().withNumberOfRepayments(numberOfRepayments)
+                .withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments()
+                .withInterestCalculationPeriodTypeAsDays().withInterestRatePerPeriod(interestRatePerPeriod).withLoanTermFrequencyAsMonths()
+                .withSubmittedOnDate(dateString).withExpectedDisbursementDate(dateString).withPrincipalGrace("2").withInterestGrace("2")
+                .build(this.clientId.toString(), this.loanProductId.toString(), null);
+
+        System.out.println("Loan Application :" + loanApplicationJSON);
+
+        this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+        String loanAccountNo = (String) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, this.loanId,
+                "accountNo");
+
+        if (isAccountPreferenceSetUp) {
+            String loanPrefixName = (String) this.accountNumberPreferencesHelper.getAccountNumberPreference(
+                    this.loanAccountNumberPreferenceId, "prefixType.value");
+            if (loanPrefixName.equals(this.officeName)) {
+                String loanOfficeName = (String) ClientHelper.getClient(requestSpec, responseSpec, this.clientId.toString(), "officeName");
+                this.validateAccountNumberLengthAndStartsWithPrefix(loanAccountNo, loanOfficeName);
+            } else if (loanPrefixName.equals(this.loanShortName)) {
+                String loanShortName = (String) this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec,
+                        this.loanProductId, "shortName");
+                this.validateAccountNumberLengthAndStartsWithPrefix(loanAccountNo, loanShortName);
+            }
+            System.out.println("SUCCESSFULLY CREATED LOAN APPLICATION BASED ON ACCOUNT PREFERENCES (ID: " + this.loanId + ")");
+        } else {
+            this.validateAccountNumberLengthAndStartsWithPrefix(loanAccountNo, null);
+            System.out.println("SUCCESSFULLY CREATED LOAN APPLICATION (ID: " + loanId + ")");
+        }
+    }
+
+    private void createSavingsProduct() {
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+
+        SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+
+        final String savingsProductJSON = savingsProductHelper
+                //
+                .withInterestCompoundingPeriodTypeAsDaily()
+                //
+                .withInterestPostingPeriodTypeAsMonthly()
+                //
+                .withInterestCalculationPeriodTypeAsDailyBalance()
+                //
+                .withMinBalanceForInterestCalculation(minBalanceForInterestCalculation)
+                //
+                .withMinRequiredBalance(minRequiredBalance).withEnforceMinRequiredBalance(enforceMinRequiredBalance)
+                .withMinimumOpenningBalance(this.MINIMUM_OPENING_BALANCE).build();
+        this.savingsProductId = SavingsProductHelper.createSavingsProduct(savingsProductJSON, this.requestSpec, this.responseSpec);
+        System.out.println("Sucessfully created savings product (ID: " + this.savingsProductId + ")");
+
+    }
+
+    private void createAndValidateSavingsEntity(Boolean isAccountPreferenceSetUp) {
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        this.savingsId = this.savingsAccountHelper
+                .applyForSavingsApplication(this.clientId, this.savingsProductId, ACCOUNT_TYPE_INDIVIDUAL);
+
+        String savingsAccountNo = (String) this.savingsAccountHelper.getSavingsAccountDetail(this.savingsId, "accountNo");
+
+        if (isAccountPreferenceSetUp) {
+            String savingsPrefixName = (String) this.accountNumberPreferencesHelper.getAccountNumberPreference(
+                    this.savingsAccountNumberPreferenceId, "prefixType.value");
+
+            if (savingsPrefixName.equals(this.officeName)) {
+                String savingsOfficeName = (String) ClientHelper.getClient(requestSpec, responseSpec, this.clientId.toString(),
+                        "officeName");
+                this.validateAccountNumberLengthAndStartsWithPrefix(savingsAccountNo, savingsOfficeName);
+            } else if (savingsPrefixName.equals(this.savingsShortName)) {
+                String loanShortName = (String) this.savingsAccountHelper.getSavingsAccountDetail(this.savingsId, "shortName");
+                this.validateAccountNumberLengthAndStartsWithPrefix(savingsAccountNo, loanShortName);
+            }
+            System.out.println("SUCCESSFULLY CREATED SAVINGS APPLICATION BASED ON ACCOUNT PREFERENCES (ID: " + this.loanId + ")");
+        } else {
+            this.validateAccountNumberLengthAndStartsWithPrefix(savingsAccountNo, null);
+            System.out.println("SUCCESSFULLY CREATED SAVINGS APPLICATION (ID: " + this.savingsId + ")");
+        }
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountTransferTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountTransferTest.java
new file mode 100644
index 0000000..01399c2
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountTransferTest.java
@@ -0,0 +1,492 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.OfficeHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import org.apache.fineract.integrationtests.common.accounting.Account.AccountType;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.savings.AccountTransferHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * JUnit Test Cases for Account Transfer for.
+ */
+@SuppressWarnings({ "rawtypes", "unused" })
+public class AccountTransferTest {
+
+    public static final String MINIMUM_OPENING_BALANCE = "30000.0";
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+    public static final String ACCOUNT_TRANSFER_AMOUNT = "15000.0";
+    public static final String ACCOUNT_TRANSFER_AMOUNT_ADJUST = "3000.0";
+    public static final String FROM_LOAN_ACCOUNT_TYPE = "1";
+    public static final String FROM_SAVINGS_ACCOUNT_TYPE = "2";
+    public static final String TO_LOAN_ACCOUNT_TYPE = "1";
+    public static final String TO_SAVINGS_ACCOUNT_TYPE = "2";
+
+    public static final String LOAN_APPROVAL_DATE = "01 March 2013";
+    public static final String LOAN_APPROVAL_DATE_PLUS_ONE = "02 March 2013";
+    public static final String LOAN_DISBURSAL_DATE = "01 March 2013";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsAccountHelper savingsAccountHelper;
+    private AccountTransferHelper accountTransferHelper;
+    private LoanTransactionHelper loanTransactionHelper;
+    private AccountHelper accountHelper;
+    private JournalEntryHelper journalEntryHelper;
+
+    Float TRANSFER_AMOUNT = new Float(ACCOUNT_TRANSFER_AMOUNT);
+    Float TRANSFER_AMOUNT_ADJUST = new Float(ACCOUNT_TRANSFER_AMOUNT_ADJUST);
+
+    private FinancialActivityAccountHelper financialActivityAccountHelper;
+    private Integer financialActivityAccountId;
+    private Account liabilityTransferAccount;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.financialActivityAccountHelper = new FinancialActivityAccountHelper(this.requestSpec);
+
+        List<HashMap> financialActivities = this.financialActivityAccountHelper.getAllFinancialActivityAccounts(this.responseSpec);
+        if (financialActivities.isEmpty()) {
+            /** Setup liability transfer account **/
+            /** Create a Liability and an Asset Transfer Account **/
+            liabilityTransferAccount = accountHelper.createLiabilityAccount();
+            Assert.assertNotNull(liabilityTransferAccount);
+
+            /*** Create A Financial Activity to Account Mapping **/
+            financialActivityAccountId = (Integer) financialActivityAccountHelper.createFinancialActivityAccount(
+                    FinancialActivityAccountsTest.liabilityTransferFinancialActivityId, liabilityTransferAccount.getAccountID(),
+                    responseSpec, CommonConstants.RESPONSE_RESOURCE_ID);
+            Assert.assertNotNull(financialActivityAccountId);
+        } else {
+            for (HashMap financialActivity : financialActivities) {
+                HashMap financialActivityData = (HashMap) financialActivity.get("financialActivityData");
+                if (financialActivityData.get("id").equals(FinancialActivityAccountsTest.liabilityTransferFinancialActivityId)) {
+                    HashMap glAccountData = (HashMap) financialActivity.get("glAccountData");
+                    liabilityTransferAccount = new Account((Integer) glAccountData.get("id"), AccountType.LIABILITY);
+                    financialActivityAccountId = (Integer) financialActivity.get("id");
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the Liability transfer account
+     */
+    @After
+    public void tearDown() {
+        Integer deletedFinancialActivityAccountId = financialActivityAccountHelper.deleteFinancialActivityAccount(
+                financialActivityAccountId, responseSpec, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(deletedFinancialActivityAccountId);
+        Assert.assertEquals(financialActivityAccountId, deletedFinancialActivityAccountId);
+    }
+
+    @Test
+    public void testFromSavingsToSavingsAccountTransfer() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.accountTransferHelper = new AccountTransferHelper(this.requestSpec, this.responseSpec);
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        OfficeHelper officeHelper = new OfficeHelper(this.requestSpec, this.responseSpec);
+        Integer toOfficeId = officeHelper.createOffice("01 January 2011");
+        Assert.assertNotNull(toOfficeId);
+
+        // Creating Savings Account to which fund to be Transferred
+        final Integer toClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2011",
+                String.valueOf(toOfficeId));
+        Assert.assertNotNull(toClientID);
+
+        final Integer toSavingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, assetAccount,
+                incomeAccount, expenseAccount, liabilityAccount);
+        Assert.assertNotNull(toSavingsProductID);
+
+        final Integer toSavingsID = this.savingsAccountHelper.applyForSavingsApplication(toClientID, toSavingsProductID,
+                ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(toSavingsProductID);
+
+        HashMap toSavingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, toSavingsID);
+        SavingsStatusChecker.verifySavingsIsPending(toSavingsStatusHashMap);
+
+        toSavingsStatusHashMap = this.savingsAccountHelper.approveSavings(toSavingsID);
+        SavingsStatusChecker.verifySavingsIsApproved(toSavingsStatusHashMap);
+
+        toSavingsStatusHashMap = this.savingsAccountHelper.activateSavings(toSavingsID);
+        SavingsStatusChecker.verifySavingsIsActive(toSavingsStatusHashMap);
+
+        final HashMap toSavingsSummaryBefore = this.savingsAccountHelper.getSavingsSummary(toSavingsID);
+
+        Integer fromOfficeId = officeHelper.createOffice("01 January 2011");
+        Assert.assertNotNull(fromOfficeId);
+
+        // Creating Savings Account from which the Fund has to be Transferred
+        final Integer fromClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2011",
+                String.valueOf(fromOfficeId));
+        Assert.assertNotNull(fromClientID);
+
+        final Integer fromSavingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                assetAccount, incomeAccount, expenseAccount, liabilityAccount);
+        Assert.assertNotNull(fromSavingsProductID);
+
+        final Integer fromSavingsID = this.savingsAccountHelper.applyForSavingsApplication(fromClientID, fromSavingsProductID,
+                ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(fromSavingsID);
+
+        HashMap fromSavingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, fromSavingsID);
+        SavingsStatusChecker.verifySavingsIsPending(fromSavingsStatusHashMap);
+
+        fromSavingsStatusHashMap = this.savingsAccountHelper.approveSavings(fromSavingsID);
+        SavingsStatusChecker.verifySavingsIsApproved(fromSavingsStatusHashMap);
+
+        fromSavingsStatusHashMap = this.savingsAccountHelper.activateSavings(fromSavingsID);
+        SavingsStatusChecker.verifySavingsIsActive(fromSavingsStatusHashMap);
+
+        final HashMap fromSavingsSummaryBefore = this.savingsAccountHelper.getSavingsSummary(fromSavingsID);
+
+        Float fromSavingsBalance = new Float(MINIMUM_OPENING_BALANCE);
+        Float toSavingsBalance = new Float(MINIMUM_OPENING_BALANCE);
+
+        this.accountTransferHelper.accountTransfer(fromClientID, fromSavingsID, fromClientID, toSavingsID, FROM_SAVINGS_ACCOUNT_TYPE,
+                TO_SAVINGS_ACCOUNT_TYPE, ACCOUNT_TRANSFER_AMOUNT);
+
+        fromSavingsBalance -= new Float(ACCOUNT_TRANSFER_AMOUNT);
+        toSavingsBalance += new Float(ACCOUNT_TRANSFER_AMOUNT);
+
+        HashMap fromSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(fromSavingsID);
+        assertEquals("Verifying From Savings Account Balance after Account Transfer", fromSavingsBalance,
+                fromSavingsSummaryAfter.get("accountBalance"));
+
+        HashMap toSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(toSavingsID);
+        assertEquals("Verifying To Savings Account Balance after Account Transfer", toSavingsBalance,
+                toSavingsSummaryAfter.get("accountBalance"));
+        final JournalEntry[] office1LiabilityEntries = { new JournalEntry(new Float(ACCOUNT_TRANSFER_AMOUNT),
+                JournalEntry.TransactionType.CREDIT) };
+        final JournalEntry[] office2LiabilityEntries = { new JournalEntry(new Float(ACCOUNT_TRANSFER_AMOUNT),
+                JournalEntry.TransactionType.DEBIT) };
+
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(fromOfficeId, liabilityTransferAccount,
+                AccountTransferHelper.ACCOUNT_TRANSFER_DATE, office1LiabilityEntries);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(toOfficeId, liabilityTransferAccount,
+                AccountTransferHelper.ACCOUNT_TRANSFER_DATE, office2LiabilityEntries);
+
+    }
+
+    @Test
+    public void testFromSavingsToLoanAccountTransfer() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        final Account loanAssetAccount = this.accountHelper.createAssetAccount();
+        final Account loanIncomeAccount = this.accountHelper.createIncomeAccount();
+        final Account loanExpenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.accountTransferHelper = new AccountTransferHelper(this.requestSpec, this.responseSpec);
+
+        OfficeHelper officeHelper = new OfficeHelper(this.requestSpec, this.responseSpec);
+        Integer toOfficeId = officeHelper.createOffice("01 January 2011");
+        Assert.assertNotNull(toOfficeId);
+
+        // Creating Loan Account to which fund to be Transferred
+        final Integer toClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2011",
+                String.valueOf(toOfficeId));
+        Assert.assertNotNull(toClientID);
+
+        Account toTransferAccount = accountHelper.createLiabilityAccount();
+        Assert.assertNotNull(toTransferAccount);
+
+        final Integer toLoanProductID = createLoanProduct(loanAssetAccount, loanIncomeAccount, loanExpenseAccount, overpaymentAccount);
+        Assert.assertNotNull(toLoanProductID);
+
+        final Integer toLoanID = applyForLoanApplication(toClientID, toLoanProductID);
+        Assert.assertNotNull(toLoanID);
+
+        HashMap toLoanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, toLoanID);
+        LoanStatusChecker.verifyLoanIsPending(toLoanStatusHashMap);
+
+        toLoanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_APPROVAL_DATE, toLoanID);
+        LoanStatusChecker.verifyLoanIsApproved(toLoanStatusHashMap);
+
+        toLoanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSAL_DATE, toLoanID);
+        LoanStatusChecker.verifyLoanIsActive(toLoanStatusHashMap);
+
+        Integer fromOfficeId = officeHelper.createOffice("01 January 2011");
+        Assert.assertNotNull(fromOfficeId);
+
+        // Creating Savings Account from which the Fund has to be Transferred
+        final Integer fromClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2011",
+                String.valueOf(fromOfficeId));
+        Assert.assertNotNull(fromClientID);
+
+        final Integer fromSavingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                assetAccount, incomeAccount, expenseAccount, liabilityAccount);
+        Assert.assertNotNull(fromSavingsProductID);
+
+        final Integer fromSavingsID = this.savingsAccountHelper.applyForSavingsApplication(fromClientID, fromSavingsProductID,
+                ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(fromSavingsID);
+
+        HashMap fromSavingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, fromSavingsID);
+        SavingsStatusChecker.verifySavingsIsPending(fromSavingsStatusHashMap);
+
+        fromSavingsStatusHashMap = this.savingsAccountHelper.approveSavings(fromSavingsID);
+        SavingsStatusChecker.verifySavingsIsApproved(fromSavingsStatusHashMap);
+
+        fromSavingsStatusHashMap = this.savingsAccountHelper.activateSavings(fromSavingsID);
+        SavingsStatusChecker.verifySavingsIsActive(fromSavingsStatusHashMap);
+
+        final HashMap fromSavingsSummaryBefore = this.savingsAccountHelper.getSavingsSummary(fromSavingsID);
+
+        Float fromSavingsBalance = new Float(MINIMUM_OPENING_BALANCE);
+
+        this.accountTransferHelper.accountTransfer(fromClientID, fromSavingsID, toClientID, toLoanID, FROM_SAVINGS_ACCOUNT_TYPE,
+                TO_LOAN_ACCOUNT_TYPE, ACCOUNT_TRANSFER_AMOUNT_ADJUST);
+
+        fromSavingsBalance -= TRANSFER_AMOUNT_ADJUST;
+
+        HashMap fromSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(fromSavingsID);
+        assertEquals("Verifying From Savings Account Balance after Account Transfer", fromSavingsBalance,
+                fromSavingsSummaryAfter.get("accountBalance"));
+
+        HashMap toLoanSummaryAfter = this.loanTransactionHelper.getLoanSummary(requestSpec, responseSpec, toLoanID);
+        assertEquals("Verifying To Loan Repayment Amount after Account Transfer", TRANSFER_AMOUNT_ADJUST,
+                toLoanSummaryAfter.get("totalRepayment"));
+
+        final JournalEntry[] office1LiabilityEntries = { new JournalEntry(new Float(ACCOUNT_TRANSFER_AMOUNT_ADJUST),
+                JournalEntry.TransactionType.CREDIT) };
+        final JournalEntry[] office2LiabilityEntries = { new JournalEntry(new Float(ACCOUNT_TRANSFER_AMOUNT_ADJUST),
+                JournalEntry.TransactionType.DEBIT) };
+
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(fromOfficeId, liabilityTransferAccount,
+                AccountTransferHelper.ACCOUNT_TRANSFER_DATE, office1LiabilityEntries);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(toOfficeId, liabilityTransferAccount,
+                AccountTransferHelper.ACCOUNT_TRANSFER_DATE, office2LiabilityEntries);
+    }
+
+    @Test
+    public void testFromLoanToSavingsAccountTransfer() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        final Account loanAssetAccount = this.accountHelper.createAssetAccount();
+        final Account loanIncomeAccount = this.accountHelper.createIncomeAccount();
+        final Account loanExpenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.accountTransferHelper = new AccountTransferHelper(this.requestSpec, this.responseSpec);
+
+        OfficeHelper officeHelper = new OfficeHelper(this.requestSpec, this.responseSpec);
+        Integer toOfficeId = officeHelper.createOffice("01 January 2011");
+        Assert.assertNotNull(toOfficeId);
+
+        // Creating Loan Account to which fund to be Transferred
+        final Integer toClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2011",
+                String.valueOf(toOfficeId));
+        Assert.assertNotNull(toClientID);
+
+        final Integer toSavingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, assetAccount,
+                incomeAccount, expenseAccount, liabilityAccount);
+        Assert.assertNotNull(toSavingsProductID);
+
+        final Integer toSavingsID = this.savingsAccountHelper.applyForSavingsApplication(toClientID, toSavingsProductID,
+                ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(toSavingsID);
+
+        HashMap toSavingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, toSavingsID);
+        SavingsStatusChecker.verifySavingsIsPending(toSavingsStatusHashMap);
+
+        toSavingsStatusHashMap = this.savingsAccountHelper.approveSavings(toSavingsID);
+        SavingsStatusChecker.verifySavingsIsApproved(toSavingsStatusHashMap);
+
+        toSavingsStatusHashMap = this.savingsAccountHelper.activateSavings(toSavingsID);
+        SavingsStatusChecker.verifySavingsIsActive(toSavingsStatusHashMap);
+
+        Integer fromOfficeId = officeHelper.createOffice("01 January 2011");
+        Assert.assertNotNull(fromOfficeId);
+
+        // Creating Savings Account from which the Fund has to be Transferred
+        final Integer fromClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2011",
+                String.valueOf(fromOfficeId));
+        Assert.assertNotNull(fromClientID);
+
+        final Integer loanProductID = createLoanProduct(loanAssetAccount, loanIncomeAccount, loanExpenseAccount, overpaymentAccount);
+        Assert.assertNotNull(loanProductID);
+
+        final Integer loanID = applyForLoanApplication(fromClientID, loanProductID);
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_APPROVAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final Integer fromSavingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                assetAccount, incomeAccount, expenseAccount, liabilityAccount);
+        Assert.assertNotNull(fromSavingsProductID);
+
+        final Integer fromSavingsID = this.savingsAccountHelper.applyForSavingsApplication(fromClientID, fromSavingsProductID,
+                ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(fromSavingsID);
+
+        HashMap fromSavingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, fromSavingsID);
+        SavingsStatusChecker.verifySavingsIsPending(fromSavingsStatusHashMap);
+
+        fromSavingsStatusHashMap = this.savingsAccountHelper.approveSavings(fromSavingsID);
+        SavingsStatusChecker.verifySavingsIsApproved(fromSavingsStatusHashMap);
+
+        fromSavingsStatusHashMap = this.savingsAccountHelper.activateSavings(fromSavingsID);
+        SavingsStatusChecker.verifySavingsIsActive(fromSavingsStatusHashMap);
+
+        final HashMap toSavingsSummaryBefore = this.savingsAccountHelper.getSavingsSummary(toSavingsID);
+
+        Float fromSavingsBalance = new Float(MINIMUM_OPENING_BALANCE);
+
+        this.accountTransferHelper.accountTransfer(fromClientID, fromSavingsID, fromClientID, loanID, FROM_SAVINGS_ACCOUNT_TYPE,
+                TO_LOAN_ACCOUNT_TYPE, ACCOUNT_TRANSFER_AMOUNT);
+
+        fromSavingsBalance -= TRANSFER_AMOUNT;
+
+        HashMap fromSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(fromSavingsID);
+
+        // Verifying fromSavings Account Balance after Account Transfer
+        assertEquals("Verifying From Savings Account Balance after Account Transfer", fromSavingsBalance,
+                fromSavingsSummaryAfter.get("accountBalance"));
+
+        Float toSavingsBalance = new Float(MINIMUM_OPENING_BALANCE);
+
+        this.accountTransferHelper.accountTransfer(fromClientID, loanID, toClientID, toSavingsID, FROM_LOAN_ACCOUNT_TYPE,
+                TO_SAVINGS_ACCOUNT_TYPE, ACCOUNT_TRANSFER_AMOUNT_ADJUST);
+
+        toSavingsBalance += TRANSFER_AMOUNT_ADJUST;
+
+        HashMap toSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(toSavingsID);
+
+        // Verifying toSavings Account Balance after Account Transfer
+        assertEquals("Verifying From Savings Account Balance after Account Transfer", toSavingsBalance,
+                toSavingsSummaryAfter.get("accountBalance"));
+
+        final JournalEntry[] office1LiabilityEntries = { new JournalEntry(new Float(ACCOUNT_TRANSFER_AMOUNT_ADJUST),
+                JournalEntry.TransactionType.CREDIT) };
+        final JournalEntry[] office2LiabilityEntries = { new JournalEntry(new Float(ACCOUNT_TRANSFER_AMOUNT_ADJUST),
+                JournalEntry.TransactionType.DEBIT) };
+
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(fromOfficeId, liabilityTransferAccount,
+                AccountTransferHelper.ACCOUNT_TRANSFER_DATE, office1LiabilityEntries);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(toOfficeId, liabilityTransferAccount,
+                AccountTransferHelper.ACCOUNT_TRANSFER_DATE, office2LiabilityEntries);
+
+    }
+
+    private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String minOpenningBalance, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+        SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+        final String savingsProductJSON = savingsProductHelper //
+                .withInterestCompoundingPeriodTypeAsDaily() //
+                .withInterestPostingPeriodTypeAsMonthly() //
+                .withInterestCalculationPeriodTypeAsDailyBalance() //
+                .withMinimumOpenningBalance(minOpenningBalance).withAccountingRuleAsCashBased(accounts).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    private Integer createLoanProduct(final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("8,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withAccountingRuleAsCashBased(accounts)//
+                .build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal("8,000.00") //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("10 January 2013") //
+                .withSubmittedOnDate("10 January 2013") //
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java
new file mode 100644
index 0000000..ef020bc
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java
@@ -0,0 +1,1078 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import org.apache.fineract.integrationtests.common.accounting.PeriodicAccrualAccountingHelper;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositAccountHelper;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositAccountStatusChecker;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositProductHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.recurringdeposit.RecurringDepositAccountHelper;
+import org.apache.fineract.integrationtests.common.recurringdeposit.RecurringDepositAccountStatusChecker;
+import org.apache.fineract.integrationtests.common.recurringdeposit.RecurringDepositProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes", "unchecked", "static-access" })
+public class AccountingScenarioIntegrationTest {
+
+    private static RequestSpecification requestSpec;
+    private static ResponseSpecification responseSpec;
+
+    private final String DATE_OF_JOINING = "01 January 2011";
+
+    private final Float LP_PRINCIPAL = 10000.0f;
+    private final String LP_REPAYMENTS = "5";
+    private final String LP_REPAYMENT_PERIOD = "2";
+    private final String LP_INTEREST_RATE = "1";
+    private final String EXPECTED_DISBURSAL_DATE = "04 March 2011";
+    private final String LOAN_APPLICATION_SUBMISSION_DATE = "3 March 2011";
+    private final String TRANSACTION_DATE = "01 March 2013";
+    private final String LOAN_TERM_FREQUENCY = "10";
+    private final String INDIVIDUAL_LOAN = "individual";
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+    public static final String MINIMUM_OPENING_BALANCE = "1000.0";
+    public static final String DEPOSIT_AMOUNT = "7000";
+    public static final String WITHDRAWAL_AMOUNT = "3000";
+    public static final String WITHDRAWAL_AMOUNT_ADJUSTED = "2000";
+
+    Float SP_BALANCE = new Float(MINIMUM_OPENING_BALANCE);
+    Float SP_DEPOSIT_AMOUNT = new Float(DEPOSIT_AMOUNT);
+    Float SP_WITHDRAWAL_AMOUNT = new Float(WITHDRAWAL_AMOUNT);
+    Float SP_WITHDRAWAL_AMOUNT_ADJUSTED = new Float(WITHDRAWAL_AMOUNT_ADJUSTED);
+
+    private final String REPAYMENT_DATE[] = { "", "04 May 2011", "04 July 2011", "04 September 2011", "04 November 2011", "04 January 2012" };
+    private final Float REPAYMENT_AMOUNT[] = { .0f, 2200.0f, 3000.0f, 900.0f, 2000.0f, 2500.0f };
+
+    private final Float AMOUNT_TO_BE_WAIVE = 400.0f;
+    private LoanTransactionHelper loanTransactionHelper;
+    private AccountHelper accountHelper;
+    private JournalEntryHelper journalEntryHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+    private FixedDepositProductHelper fixedDepositProductHelper;
+    private FixedDepositAccountHelper fixedDepositAccountHelper;
+    private RecurringDepositProductHelper recurringDepositProductHelper;
+    private RecurringDepositAccountHelper recurringDepositAccountHelper;
+    private SchedulerJobHelper schedulerJobHelper;
+    private PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        this.periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void checkUpfrontAccrualAccountingFlow() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProductWithUpfrontAccrualAccountingEnabled(assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // CHECK ACCOUNT ENTRIES
+        System.out.println("Entries ......");
+        final float PRINCIPAL_VALUE_FOR_EACH_PERIOD = 2000.0f;
+        final float TOTAL_INTEREST = 1000.0f;
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(TOTAL_INTEREST, JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.DEBIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.EXPECTED_DISBURSAL_DATE, assetAccountInitialEntry);
+        System.out.println("CHECKING INCOME: ******************************************");
+        final JournalEntry incomeJournalEntry = new JournalEntry(TOTAL_INTEREST, JournalEntry.TransactionType.CREDIT);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, this.EXPECTED_DISBURSAL_DATE, incomeJournalEntry);
+
+        // MAKE 1
+        System.out.println("Repayment 1 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[1], this.REPAYMENT_AMOUNT[1], loanID);
+        final float FIRST_INTEREST = 200.0f;
+        final float FIRST_PRINCIPAL = 2000.0f;
+        float expected_value = this.LP_PRINCIPAL - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, expected_value, loanID);
+        final JournalEntry[] assetAccountFirstEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[1], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(FIRST_INTEREST, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(FIRST_PRINCIPAL, JournalEntry.TransactionType.CREDIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[1], assetAccountFirstEntry);
+        System.out.println("Repayment 1 Done......");
+
+        // REPAYMENT 2
+        System.out.println("Repayment 2 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[2], this.REPAYMENT_AMOUNT[2], loanID);
+        final float SECOND_AND_THIRD_INTEREST = 400.0f;
+        final float SECOND_PRINCIPAL = this.REPAYMENT_AMOUNT[2] - SECOND_AND_THIRD_INTEREST;
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(2, expected_value, loanID);
+        final JournalEntry[] assetAccountSecondEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[2], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(SECOND_AND_THIRD_INTEREST, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(SECOND_PRINCIPAL, JournalEntry.TransactionType.CREDIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[2], assetAccountSecondEntry);
+        System.out.println("Repayment 2 Done ......");
+
+        // WAIVE INTEREST
+        System.out.println("Waive Interest  ......");
+        this.loanTransactionHelper.waiveInterest(this.REPAYMENT_DATE[4], this.AMOUNT_TO_BE_WAIVE.toString(), loanID);
+
+        final JournalEntry waivedEntry = new JournalEntry(this.AMOUNT_TO_BE_WAIVE, JournalEntry.TransactionType.CREDIT);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[4], waivedEntry);
+
+        final JournalEntry expenseJournalEntry = new JournalEntry(this.AMOUNT_TO_BE_WAIVE, JournalEntry.TransactionType.DEBIT);
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, this.REPAYMENT_DATE[4], expenseJournalEntry);
+        System.out.println("Waive Interest Done......");
+
+        // REPAYMENT 3
+        System.out.println("Repayment 3 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[3], this.REPAYMENT_AMOUNT[3], loanID);
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        final JournalEntry[] assetAccountThirdEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[3], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.REPAYMENT_AMOUNT[3], JournalEntry.TransactionType.CREDIT) };
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(3, expected_value, loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[3], assetAccountThirdEntry);
+        System.out.println("Repayment 3 Done ......");
+
+        // REPAYMENT 4
+        System.out.println("Repayment 4 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[4], this.REPAYMENT_AMOUNT[4], loanID);
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(4, expected_value, loanID);
+        final JournalEntry[] assetAccountFourthEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[4], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.REPAYMENT_AMOUNT[4], JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[4], assetAccountFourthEntry);
+        System.out.println("Repayment 4 Done  ......");
+
+        // Repayment 5
+        System.out.println("Repayment 5 ......");
+        final JournalEntry[] assetAccountFifthEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[5], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.REPAYMENT_AMOUNT[5], JournalEntry.TransactionType.CREDIT) };
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[5], this.REPAYMENT_AMOUNT[5], loanID);
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(5, expected_value, loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[5], assetAccountFifthEntry);
+        System.out.println("Repayment 5 Done  ......");
+    }
+
+    private Integer createLoanProductWithUpfrontAccrualAccountingEnabled(final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(this.LP_PRINCIPAL.toString()).withRepaymentTypeAsMonth()
+                .withRepaymentAfterEvery(this.LP_REPAYMENT_PERIOD).withNumberOfRepayments(this.LP_REPAYMENTS).withRepaymentTypeAsMonth()
+                .withinterestRatePerPeriod(this.LP_INTEREST_RATE).withInterestRateFrequencyTypeAsMonths()
+                .withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRuleUpfrontAccrual(accounts)
+                .build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(this.LP_PRINCIPAL.toString())
+                .withLoanTermFrequency(this.LOAN_TERM_FREQUENCY).withLoanTermFrequencyAsMonths().withNumberOfRepayments(this.LP_REPAYMENTS)
+                .withRepaymentEveryAfter(this.LP_REPAYMENT_PERIOD).withRepaymentFrequencyTypeAsMonths()
+                .withInterestRatePerPeriod(this.LP_INTEREST_RATE).withInterestTypeAsFlatBalance()
+                .withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
+                .withExpectedDisbursementDate(this.EXPECTED_DISBURSAL_DATE).withSubmittedOnDate(this.LOAN_APPLICATION_SUBMISSION_DATE)
+                .withLoanType(this.INDIVIDUAL_LOAN).build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    @Test
+    public void checkAccountingWithSavingsFlow() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer savingsProductID = createSavingsProduct(MINIMUM_OPENING_BALANCE, assetAccount, incomeAccount, expenseAccount,
+                liabilityAccount);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        final Integer savingsID = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsID);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsID);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsID);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        // Checking initial Account entries.
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(this.SP_BALANCE, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(this.SP_BALANCE, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.TRANSACTION_DATE, assetAccountInitialEntry);
+        this.journalEntryHelper
+                .checkJournalEntryForLiabilityAccount(liabilityAccount, this.TRANSACTION_DATE, liablilityAccountInitialEntry);
+
+        // First Transaction-Deposit
+        this.savingsAccountHelper.depositToSavingsAccount(savingsID, DEPOSIT_AMOUNT, SavingsAccountHelper.TRANSACTION_DATE,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+        Float balance = SP_BALANCE + SP_DEPOSIT_AMOUNT;
+        HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsID);
+        assertEquals("Verifying Balance after Deposit", balance, summary.get("accountBalance"));
+
+        System.out.println("----------------------Verifying Journal Entry after the Transaction Deposit----------------------------");
+        final JournalEntry[] assetAccountFirstTransactionEntry = { new JournalEntry(this.SP_DEPOSIT_AMOUNT,
+                JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liabililityAccountFirstTransactionEntry = { new JournalEntry(this.SP_DEPOSIT_AMOUNT,
+                JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.TRANSACTION_DATE, assetAccountFirstTransactionEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, this.TRANSACTION_DATE,
+                liabililityAccountFirstTransactionEntry);
+
+        // Second Transaction-Withdrawal
+        this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsID, WITHDRAWAL_AMOUNT, SavingsAccountHelper.TRANSACTION_DATE,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+        balance -= SP_WITHDRAWAL_AMOUNT;
+        summary = this.savingsAccountHelper.getSavingsSummary(savingsID);
+        assertEquals("Verifying Balance after Withdrawal", balance, summary.get("accountBalance"));
+
+        System.out.println("-------------------Verifying Journal Entry after the Transaction Withdrawal----------------------");
+        final JournalEntry[] assetAccountSecondTransactionEntry = { new JournalEntry(this.SP_WITHDRAWAL_AMOUNT,
+                JournalEntry.TransactionType.CREDIT) };
+        final JournalEntry[] liabililityAccountSecondTransactionEntry = { new JournalEntry(this.SP_WITHDRAWAL_AMOUNT,
+                JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.TRANSACTION_DATE, assetAccountSecondTransactionEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, this.TRANSACTION_DATE,
+                liabililityAccountSecondTransactionEntry);
+
+        // Third Transaction-Add Charges for Withdrawal Fee
+        final Integer withdrawalChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsWithdrawalFeeJSON());
+        Assert.assertNotNull(withdrawalChargeId);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsID, withdrawalChargeId);
+        ArrayList<HashMap> chargesPendingState = this.savingsAccountHelper.getSavingsCharges(savingsID);
+        Assert.assertEquals(1, chargesPendingState.size());
+        HashMap savingsChargeForPay = chargesPendingState.get(0);
+        HashMap paidCharge = this.savingsAccountHelper.getSavingsCharge(savingsID, (Integer) savingsChargeForPay.get("id"));
+        Float chargeAmount = (Float) paidCharge.get("amount");
+
+        // Withdrawal after adding Charge of type Withdrawal Fee
+        this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsID, WITHDRAWAL_AMOUNT_ADJUSTED,
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        summary = this.savingsAccountHelper.getSavingsSummary(savingsID);
+        balance = balance - SP_WITHDRAWAL_AMOUNT_ADJUSTED - chargeAmount;
+
+        final JournalEntry[] liabililityAccountThirdTransactionEntry = {
+                new JournalEntry(chargeAmount, JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.SP_WITHDRAWAL_AMOUNT_ADJUSTED, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] assetAccountThirdTransactionEntry = { new JournalEntry(this.SP_WITHDRAWAL_AMOUNT_ADJUSTED,
+                JournalEntry.TransactionType.CREDIT) };
+        final JournalEntry[] incomeAccountThirdTransactionEntry = { new JournalEntry(chargeAmount, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.TRANSACTION_DATE, assetAccountThirdTransactionEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, this.TRANSACTION_DATE,
+                liabililityAccountThirdTransactionEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, this.TRANSACTION_DATE, incomeAccountThirdTransactionEntry);
+
+        // Verifying Balance after applying Charge for Withdrawal Fee
+        assertEquals("Verifying Balance", balance, summary.get("accountBalance"));
+    }
+
+    @Test
+    public void testFixedDepositAccountingFlow() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1;
+        todaysDate.add(Calendar.DATE, numberOfDaysLeft);
+        final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, assetAccount, incomeAccount, expenseAccount,
+                liabilityAccount);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, FixedDepositTest.WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+
+        HashMap accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
+
+        Float depositAmount = (Float) accountSummary.get("totalDeposits");
+
+        // Checking initial Journal entries after Activation.
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, ACTIVATION_DATE, assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, ACTIVATION_DATE, liablilityAccountInitialEntry);
+
+        Integer transactionIdForPostInterest = this.fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId);
+        Assert.assertNotNull(transactionIdForPostInterest);
+
+        accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
+        Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted");
+
+        // Checking initial Journal entries after Interest Posting.
+        final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry);
+
+    }
+
+    @Test
+    public void testRecurringDepositAccountingFlow() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1;
+        todaysDate.add(Calendar.DATE, numberOfDaysLeft);
+        final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, assetAccount, liabilityAccount,
+                incomeAccount, expenseAccount);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, RecurringDepositTest.WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+
+        Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                depositAmount, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(depositTransactionId);
+
+        // Checking initial Journal entries after Activation.
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE, assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE,
+                liablilityAccountInitialEntry);
+
+        Integer interestPostingTransactionId = this.recurringDepositAccountHelper
+                .postInterestForRecurringDeposit(recurringDepositAccountId);
+        Assert.assertNotNull(interestPostingTransactionId);
+
+        HashMap accountSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted");
+
+        // Checking initial Journal entries after Interest Posting.
+        final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry);
+
+    }
+
+    public static Integer createSavingsProduct(final String minOpenningBalance, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+        final String savingsProductJSON = new SavingsProductHelper().withInterestCompoundingPeriodTypeAsDaily() //
+                .withInterestPostingPeriodTypeAsQuarterly() //
+                .withInterestCalculationPeriodTypeAsDailyBalance() //
+                .withMinimumOpenningBalance(minOpenningBalance).withAccountingRuleAsCashBased(accounts).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    private Integer createFixedDepositProduct(final String validFrom, final String validTo, Account... accounts) {
+        System.out.println("------------------------------CREATING NEW FIXED DEPOSIT PRODUCT ---------------------------------------");
+        FixedDepositProductHelper fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        final String fixedDepositProductJSON = fixedDepositProductHelper //
+                .withAccountingRuleAsCashBased(accounts).build(validFrom, validTo);
+        return FixedDepositProductHelper.createFixedDepositProduct(fixedDepositProductJSON, requestSpec, responseSpec);
+    }
+
+    private Integer applyForFixedDepositApplication(final String clientID, final String productID, final String validFrom,
+            final String validTo, final String submittedOnDate, final String penalInterestType) {
+        System.out.println("--------------------------------APPLYING FOR FIXED DEPOSIT ACCOUNT --------------------------------");
+        final String fixedDepositApplicationJSON = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec) //
+                .withSubmittedOnDate(submittedOnDate).build(clientID, productID, validFrom, validTo, penalInterestType);
+        return this.fixedDepositAccountHelper
+                .applyFixedDepositApplication(fixedDepositApplicationJSON, this.requestSpec, this.responseSpec);
+    }
+
+    private Integer createRecurringDepositProduct(final String validFrom, final String validTo, Account... accounts) {
+        System.out.println("------------------------------CREATING NEW RECURRING DEPOSIT PRODUCT ---------------------------------------");
+        RecurringDepositProductHelper recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        final String recurringDepositProductJSON = recurringDepositProductHelper //
+                .withAccountingRuleAsCashBased(accounts).build(validFrom, validTo);
+        return RecurringDepositProductHelper.createRecurringDepositProduct(recurringDepositProductJSON, requestSpec, responseSpec);
+    }
+
+    private Integer applyForRecurringDepositApplication(final String clientID, final String productID, final String validFrom,
+            final String validTo, final String submittedOnDate, final String penalInterestType, final String expectedFirstDepositOnDate) {
+        System.out.println("--------------------------------APPLYING FOR RECURRING DEPOSIT ACCOUNT --------------------------------");
+        final String recurringDepositApplicationJSON = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec)
+                //
+                .withSubmittedOnDate(submittedOnDate).withExpectedFirstDepositOnDate(expectedFirstDepositOnDate)
+                .build(clientID, productID, validFrom, validTo, penalInterestType);
+        return this.recurringDepositAccountHelper.applyRecurringDepositApplication(recurringDepositApplicationJSON, this.requestSpec,
+                this.responseSpec);
+    }
+
+    @Test
+    public void checkPeriodicAccrualAccountingFlow() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingEnabled(assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // CHECK ACCOUNT ENTRIES
+        System.out.println("Entries ......");
+        final float PRINCIPAL_VALUE_FOR_EACH_PERIOD = 2000.0f;
+        final float TOTAL_INTEREST = 1000.0f;
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.DEBIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.EXPECTED_DISBURSAL_DATE, assetAccountInitialEntry);
+
+        final String jobName = "Add Accrual Transactions";
+        try {
+            this.schedulerJobHelper.executeJob(jobName);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        // MAKE 1
+        System.out.println("Repayment 1 ......");
+        final float FIRST_INTEREST = 200.0f;
+        final float FIRST_PRINCIPAL = 2000.0f;
+        final float FEE_PORTION = 0.0f;
+        final float PENALTY_PORTION = 0.0f;
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(this.REPAYMENT_DATE[1]), FIRST_INTEREST,
+                FEE_PORTION, PENALTY_PORTION, loanID);
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[1], this.REPAYMENT_AMOUNT[1], loanID);
+        float expected_value = this.LP_PRINCIPAL - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, expected_value, loanID);
+        final JournalEntry[] assetAccountFirstEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[1], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(FIRST_INTEREST, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(FIRST_PRINCIPAL, JournalEntry.TransactionType.CREDIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[1], assetAccountFirstEntry);
+        System.out.println("Repayment 1 Done......");
+
+        // REPAYMENT 2
+        System.out.println("Repayment 2 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[2], this.REPAYMENT_AMOUNT[2], loanID);
+        final float SECOND_AND_THIRD_INTEREST = 400.0f;
+        final float SECOND_PRINCIPAL = this.REPAYMENT_AMOUNT[2] - SECOND_AND_THIRD_INTEREST;
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(this.REPAYMENT_DATE[2]), FIRST_INTEREST,
+                FEE_PORTION, PENALTY_PORTION, loanID);
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(this.REPAYMENT_DATE[3]), FIRST_INTEREST,
+                FEE_PORTION, PENALTY_PORTION, loanID);
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(2, expected_value, loanID);
+        final JournalEntry[] assetAccountSecondEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[2], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(SECOND_AND_THIRD_INTEREST, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(SECOND_PRINCIPAL, JournalEntry.TransactionType.CREDIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[2], assetAccountSecondEntry);
+        System.out.println("Repayment 2 Done ......");
+
+        // WAIVE INTEREST
+        System.out.println("Waive Interest  ......");
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(this.REPAYMENT_DATE[4]), FIRST_INTEREST,
+                FEE_PORTION, PENALTY_PORTION, loanID);
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(this.REPAYMENT_DATE[5]), FIRST_INTEREST,
+                FEE_PORTION, PENALTY_PORTION, loanID);
+        this.loanTransactionHelper.waiveInterest(this.REPAYMENT_DATE[4], this.AMOUNT_TO_BE_WAIVE.toString(), loanID);
+
+        final JournalEntry waivedEntry = new JournalEntry(this.AMOUNT_TO_BE_WAIVE, JournalEntry.TransactionType.CREDIT);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[4], waivedEntry);
+
+        final JournalEntry expenseJournalEntry = new JournalEntry(this.AMOUNT_TO_BE_WAIVE, JournalEntry.TransactionType.DEBIT);
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, this.REPAYMENT_DATE[4], expenseJournalEntry);
+        System.out.println("Waive Interest Done......");
+
+        // REPAYMENT 3
+        System.out.println("Repayment 3 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[3], this.REPAYMENT_AMOUNT[3], loanID);
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        final JournalEntry[] assetAccountThirdEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[3], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.REPAYMENT_AMOUNT[3], JournalEntry.TransactionType.CREDIT) };
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(3, expected_value, loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[3], assetAccountThirdEntry);
+        System.out.println("Repayment 3 Done ......");
+
+        // REPAYMENT 4
+        System.out.println("Repayment 4 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[4], this.REPAYMENT_AMOUNT[4], loanID);
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(4, expected_value, loanID);
+        final JournalEntry[] assetAccountFourthEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[4], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.REPAYMENT_AMOUNT[4], JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[4], assetAccountFourthEntry);
+        System.out.println("Repayment 4 Done  ......");
+
+        // Repayment 5
+        System.out.println("Repayment 5 ......");
+        final JournalEntry[] assetAccountFifthEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[5], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.REPAYMENT_AMOUNT[5], JournalEntry.TransactionType.CREDIT) };
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[5], this.REPAYMENT_AMOUNT[5], loanID);
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(5, expected_value, loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[5], assetAccountFifthEntry);
+        System.out.println("Repayment 5 Done  ......");
+    }
+
+    @Test
+    public void checkPeriodicAccrualAccountingFlow_OVER_PAYMENT() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingEnabled(assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // CHECK ACCOUNT ENTRIES
+        System.out.println("Entries ......");
+        final float PRINCIPAL_VALUE_FOR_EACH_PERIOD = 2000.0f;
+        final float TOTAL_INTEREST = 1000.0f;
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.DEBIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.EXPECTED_DISBURSAL_DATE, assetAccountInitialEntry);
+
+        final String jobName = "Add Accrual Transactions";
+        try {
+            this.schedulerJobHelper.executeJob(jobName);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        // MAKE 1
+        System.out.println("Repayment 1 ......");
+        final float FIRST_INTEREST = 200.0f;
+        final float FIRST_PRINCIPAL = 2000.0f;
+        final float FEE_PORTION = 0.0f;
+        final float PENALTY_PORTION = 0.0f;
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(this.REPAYMENT_DATE[1]), FIRST_INTEREST,
+                FEE_PORTION, PENALTY_PORTION, loanID);
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[1], 15000f, loanID);
+        float expected_value = this.LP_PRINCIPAL - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, expected_value, loanID);
+        final JournalEntry[] assetAccountEntry = { new JournalEntry(15000f, JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(1000f, JournalEntry.TransactionType.CREDIT), new JournalEntry(10000f, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[1], assetAccountEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, this.REPAYMENT_DATE[1], new JournalEntry(4000f,
+                JournalEntry.TransactionType.CREDIT));
+        System.out.println("Repayment  Done......");
+
+    }
+
+    @Test
+    public void checkPeriodicAccrualAccountingTillCurrentDateFlow() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingEnabled(assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+        final float FEE_PORTION = 50.0f;
+        final float PENALTY_PORTION = 100.0f;
+        Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(FEE_PORTION), false));
+        Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateJSON(
+                ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(PENALTY_PORTION), true));
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+
+        Calendar todayDate = Calendar.getInstance();
+        final String currentDate = dateFormat.format(todayDate.getTime());
+
+        todayDate.add(Calendar.DATE, -4);
+
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todayDate.getTime());
+
+        todayDate.add(Calendar.MONTH, 2);
+        final String FIRST_REPAYMENT_DATE = dateFormat.format(todayDate.getTime());
+
+        todayDate = Calendar.getInstance();
+        todayDate.add(Calendar.DATE, -2);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDate),
+                        dateFormat.format(todayDate.getTime()), String.valueOf(PENALTY_PORTION)));
+        todayDate.add(Calendar.DATE, 1);
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flat), dateFormat.format(todayDate.getTime()), String.valueOf(FEE_PORTION)));
+
+        // CHECK ACCOUNT ENTRIES
+        System.out.println("Entries ......");
+        final float PRINCIPAL_VALUE_FOR_EACH_PERIOD = 2000.0f;
+        final float TOTAL_INTEREST = 1000.0f;
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.DEBIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, LOAN_DISBURSEMENT_DATE, assetAccountInitialEntry);
+
+        final String jobName = "Add Periodic Accrual Transactions";
+        try {
+            this.schedulerJobHelper.executeJob(jobName);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        final ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec,
+                loanID);
+        // MAKE 1
+        List fromDateList = (List) loanSchedule.get(1).get("fromDate");
+        LocalDate fromDateLocal = LocalDate.now();
+        fromDateLocal = fromDateLocal.withYear((int) fromDateList.get(0));
+        fromDateLocal = fromDateLocal.withMonthOfYear((int) fromDateList.get(1));
+        fromDateLocal = fromDateLocal.withDayOfMonth((int) fromDateList.get(2));
+
+        List dueDateList = (List) loanSchedule.get(1).get("dueDate");
+        LocalDate dueDateLocal = LocalDate.now();
+        dueDateLocal = dueDateLocal.withYear((int) dueDateList.get(0));
+        dueDateLocal = dueDateLocal.withMonthOfYear((int) dueDateList.get(1));
+        dueDateLocal = dueDateLocal.withDayOfMonth((int) dueDateList.get(2));
+
+        int totalDaysInPeriod = Days.daysBetween(fromDateLocal, dueDateLocal).getDays();
+
+        float totalInterest = (float) loanSchedule.get(1).get("interestOriginalDue");
+        DecimalFormat numberFormat = new DecimalFormat("#.00", new DecimalFormatSymbols(Locale.US));
+        float INTEREST_4_DAYS = totalInterest / totalDaysInPeriod * 4;
+        INTEREST_4_DAYS = new Float(numberFormat.format(INTEREST_4_DAYS));
+
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(currentDate), INTEREST_4_DAYS, FEE_PORTION,
+                PENALTY_PORTION, loanID);
+
+    }
+
+    @Test
+    public void checkPeriodicAccrualAccountingAPIFlow() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingEnabled(assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+        final float FEE_PORTION = 50.0f;
+        final float PENALTY_PORTION = 100.0f;
+        final float NEXT_FEE_PORTION = 55.0f;
+        final float NEXT_PENALTY_PORTION = 105.0f;
+
+        Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(FEE_PORTION), false));
+        Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateJSON(
+                ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(PENALTY_PORTION), true));
+
+        Integer flatNext = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateJSON(
+                ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(NEXT_FEE_PORTION), false));
+        Integer flatSpecifiedDueDateNext = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
+                .getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(NEXT_PENALTY_PORTION), true));
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+
+        Calendar todayDate = Calendar.getInstance();
+        final String currentDate = dateFormat.format(todayDate.getTime());
+
+        todayDate.add(Calendar.DATE, -4);
+
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todayDate.getTime());
+
+        todayDate.add(Calendar.MONTH, 2);
+        final String FIRST_REPAYMENT_DATE = dateFormat.format(todayDate.getTime());
+
+        todayDate = Calendar.getInstance();
+        todayDate.add(Calendar.DATE, -2);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDate),
+                        dateFormat.format(todayDate.getTime()), String.valueOf(PENALTY_PORTION)));
+        todayDate.add(Calendar.DATE, 1);
+        String runOndate = dateFormat.format(todayDate.getTime());
+
+        this.loanTransactionHelper
+                .addChargesForLoan(
+                        loanID,
+                        LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flat), runOndate,
+                                String.valueOf(FEE_PORTION)));
+
+        todayDate.add(Calendar.DATE, 1);
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDateNext),
+                        dateFormat.format(todayDate.getTime()), String.valueOf(NEXT_PENALTY_PORTION)));
+
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatNext),
+                        dateFormat.format(todayDate.getTime()), String.valueOf(NEXT_FEE_PORTION)));
+
+        // CHECK ACCOUNT ENTRIES
+        System.out.println("Entries ......");
+        final float PRINCIPAL_VALUE_FOR_EACH_PERIOD = 2000.0f;
+        final float TOTAL_INTEREST = 1000.0f;
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.DEBIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, LOAN_DISBURSEMENT_DATE, assetAccountInitialEntry);
+
+        this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOndate);
+
+        final ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec,
+                loanID);
+        // MAKE 1
+        List fromDateList = (List) loanSchedule.get(1).get("fromDate");
+        LocalDate fromDateLocal = LocalDate.now();
+        fromDateLocal = fromDateLocal.withYear((int) fromDateList.get(0));
+        fromDateLocal = fromDateLocal.withMonthOfYear((int) fromDateList.get(1));
+        fromDateLocal = fromDateLocal.withDayOfMonth((int) fromDateList.get(2));
+
+        List dueDateList = (List) loanSchedule.get(1).get("dueDate");
+        LocalDate dueDateLocal = LocalDate.now();
+        dueDateLocal = dueDateLocal.withYear((int) dueDateList.get(0));
+        dueDateLocal = dueDateLocal.withMonthOfYear((int) dueDateList.get(1));
+        dueDateLocal = dueDateLocal.withDayOfMonth((int) dueDateList.get(2));
+
+        int totalDaysInPeriod = Days.daysBetween(fromDateLocal, dueDateLocal).getDays();
+
+        float totalInterest = (float) loanSchedule.get(1).get("interestOriginalDue");
+        DecimalFormat numberFormat = new DecimalFormat("#.00", new DecimalFormatSymbols(Locale.US));
+        float INTEREST_3_DAYS = totalInterest / totalDaysInPeriod * 3;
+        INTEREST_3_DAYS = new Float(numberFormat.format(INTEREST_3_DAYS));
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(runOndate), INTEREST_3_DAYS, FEE_PORTION,
+                PENALTY_PORTION, loanID);
+
+        runOndate = dateFormat.format(todayDate.getTime());
+
+        this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOndate);
+        float interestPerDay = (totalInterest / totalDaysInPeriod * 4) - INTEREST_3_DAYS;
+        interestPerDay = new Float(numberFormat.format(interestPerDay));
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(runOndate), interestPerDay, NEXT_FEE_PORTION,
+                NEXT_PENALTY_PORTION, loanID);
+
+    }
+
+    private Integer createLoanProductWithPeriodicAccrualAccountingEnabled(final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(this.LP_PRINCIPAL.toString()).withRepaymentTypeAsMonth()
+                .withRepaymentAfterEvery(this.LP_REPAYMENT_PERIOD).withNumberOfRepayments(this.LP_REPAYMENTS).withRepaymentTypeAsMonth()
+                .withinterestRatePerPeriod(this.LP_INTEREST_RATE).withInterestRateFrequencyTypeAsMonths()
+                .withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
+                .withDaysInMonth("30").withDaysInYear("365").build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    @Test
+    public void checkCashBasedAccountingFlow() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProductWithCashBasedAccountingEnabled(assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // CHECK ACCOUNT ENTRIES
+        System.out.println("Entries ......");
+        final float PRINCIPAL_VALUE_FOR_EACH_PERIOD = 2000.0f;
+        final float TOTAL_INTEREST = 1000.0f;
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.EXPECTED_DISBURSAL_DATE, assetAccountInitialEntry);
+
+        // MAKE 1
+        System.out.println("Repayment 1 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[1], this.REPAYMENT_AMOUNT[1], loanID);
+        final float FIRST_INTEREST = 200.0f;
+        final float FIRST_PRINCIPAL = 2000.0f;
+        float expected_value = this.LP_PRINCIPAL - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, expected_value, loanID);
+        final JournalEntry[] assetAccountFirstEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[1], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(FIRST_PRINCIPAL, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[1], assetAccountFirstEntry);
+        System.out.println("CHECKING INCOME: ******************************************");
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, this.REPAYMENT_DATE[1], new JournalEntry(FIRST_INTEREST,
+                JournalEntry.TransactionType.CREDIT));
+        System.out.println("Repayment 1 Done......");
+
+        // REPAYMENT 2
+        System.out.println("Repayment 2 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[2], this.REPAYMENT_AMOUNT[2], loanID);
+        final float SECOND_AND_THIRD_INTEREST = 400.0f;
+        final float SECOND_PRINCIPAL = this.REPAYMENT_AMOUNT[2] - SECOND_AND_THIRD_INTEREST;
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(2, expected_value, loanID);
+        final JournalEntry[] assetAccountSecondEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[2], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(SECOND_PRINCIPAL, JournalEntry.TransactionType.CREDIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[2], assetAccountSecondEntry);
+        System.out.println("CHECKING INCOME: ******************************************");
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, this.REPAYMENT_DATE[2], new JournalEntry(
+                SECOND_AND_THIRD_INTEREST, JournalEntry.TransactionType.CREDIT));
+        System.out.println("Repayment 2 Done ......");
+
+        // WAIVE INTEREST
+        System.out.println("Waive Interest  ......");
+        Integer transactionId = this.loanTransactionHelper.waiveInterestAndReturnTransactionId(this.REPAYMENT_DATE[4],
+                this.AMOUNT_TO_BE_WAIVE.toString(), loanID);
+        // waive of fees and interest are not considered in cash based
+        // accounting,
+        this.journalEntryHelper.ensureNoAccountingTransactionsWithTransactionId("L" + transactionId);
+
+        // REPAYMENT 3
+        System.out.println("Repayment 3 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[3], this.REPAYMENT_AMOUNT[3], loanID);
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        final JournalEntry[] assetAccountThirdEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[3], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.REPAYMENT_AMOUNT[3], JournalEntry.TransactionType.CREDIT) };
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(3, expected_value, loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[3], assetAccountThirdEntry);
+        System.out.println("Repayment 3 Done ......");
+
+        // REPAYMENT 4
+        System.out.println("Repayment 4 ......");
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[4], this.REPAYMENT_AMOUNT[4], loanID);
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(4, expected_value, loanID);
+        final JournalEntry[] assetAccountFourthEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[4], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.REPAYMENT_AMOUNT[4], JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[4], assetAccountFourthEntry);
+        System.out.println("Repayment 4 Done  ......");
+
+        // Repayment 5
+        System.out.println("Repayment 5 ......");
+        final JournalEntry[] assetAccountFifthEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[5], JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(this.REPAYMENT_AMOUNT[5], JournalEntry.TransactionType.CREDIT) };
+        expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD;
+        this.loanTransactionHelper.makeRepayment(this.REPAYMENT_DATE[5], this.REPAYMENT_AMOUNT[5], loanID);
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(5, expected_value, loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[5], assetAccountFifthEntry);
+        System.out.println("Repayment 5 Done  ......");
+    }
+
+    private Integer createLoanProductWithCashBasedAccountingEnabled(final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(this.LP_PRINCIPAL.toString()).withRepaymentTypeAsMonth()
+                .withRepaymentAfterEvery(this.LP_REPAYMENT_PERIOD).withNumberOfRepayments(this.LP_REPAYMENTS).withRepaymentTypeAsMonth()
+                .withinterestRatePerPeriod(this.LP_INTEREST_RATE).withInterestRateFrequencyTypeAsMonths()
+                .withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRuleAsCashBased(accounts).build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private LocalDate getDateAsLocalDate(String dateAsString) {
+        LocalDate date = null;
+        try {
+            DateFormat df = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+            date = new LocalDate(df.parse(dateAsString));
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return date;
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/BatchApiTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/BatchApiTest.java
new file mode 100644
index 0000000..25a4644
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/BatchApiTest.java
@@ -0,0 +1,425 @@
+/**
+ * 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.integrationtests;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.integrationtests.common.BatchHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Test class for
+ * {@link org.apache.fineract.batch.command.CommandStrategyProvider}. This tests
+ * the response provided by commandStrategy by injecting it with a
+ * {@code BatchRequest}.
+ * 
+ * @author RishabhShukla
+ * 
+ * @see org.apache.fineract.integrationtests.common.BatchHelper
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ */
+public class BatchApiTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    public BatchApiTest() {
+        super();
+    }
+
+    /**
+     * Sets up the essential settings for the TEST like contentType,
+     * expectedStatusCode. It uses the '@Before' annotation provided by jUnit.
+     */
+    @Before
+    public void setup() {
+
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    /**
+     * Tests for the unimplemented command Strategies by returning 501 status
+     * code. For a unknownRequest a statusCode 501 is returned back with
+     * response.
+     * 
+     * @see org.apache.fineract.batch.command.internal.UnknownCommandStrategy
+     */
+    @Test
+    public void shouldReturnStatusNotImplementedUnknownCommand() {
+
+        final BatchRequest br = new BatchRequest();
+        br.setRequestId(4711L);
+        br.setRelativeUrl("/nirvana");
+        br.setMethod("POST");
+
+        final List<BatchResponse> response = BatchHelper.postWithSingleRequest(this.requestSpec, this.responseSpec, br);
+
+        // Verify that only 501 is returned as the status code
+        for (BatchResponse resp : response) {
+            Assert.assertEquals("Verify Status code 501", (long) 501, (long) resp.getStatusCode());
+        }
+    }
+
+    /**
+     * Tests for the successful response for a createClient request from
+     * createClientCommand. A successful response with statusCode '200' is
+     * returned back.
+     * 
+     * @see org.apache.fineract.batch.command.internal.CreateClientCommandStrategy
+     */
+    @Test
+    public void shouldReturnOkStatusForCreateClientCommand() {
+
+        final BatchRequest br = BatchHelper.createClientRequest(4712L, "");
+
+        final List<BatchResponse> response = BatchHelper.postWithSingleRequest(this.requestSpec, this.responseSpec, br);
+
+        // Verify that a 200 response is returned as the status code
+        for (BatchResponse resp : response) {
+            Assert.assertEquals("Verify Status code 200", (long) 200, (long) resp.getStatusCode());
+        }
+    }
+
+    /**
+     * Tests for an erroneous response with statusCode '501' if transaction
+     * fails. If Query Parameter 'enclosingTransaction' is set to 'true' and if
+     * one of the request in BatchRequest fails then all transactions are rolled
+     * back.
+     * 
+     * @see org.apache.fineract.batch.command.internal.CreateClientCommandStrategy
+     * @see org.apache.fineract.batch.api.BatchApiResource
+     * @see org.apache.fineract.batch.service.BatchApiService
+     */
+    @Test
+    public void shouldRollBackAllTransactionsOnFailure() {
+
+        // Create first client request
+        final BatchRequest br1 = BatchHelper.createClientRequest(4713L, "TestExtId11");
+
+        // Create second client request
+        final BatchRequest br2 = BatchHelper.createClientRequest(4714L, "TestExtId12");
+
+        // Create third client request, having same externalID as second client,
+        // hence cause of error
+        final BatchRequest br3 = BatchHelper.createClientRequest(4715L, "TestExtId11");
+
+        final List<BatchRequest> batchRequests = new ArrayList<>();
+
+        batchRequests.add(br1);
+        batchRequests.add(br2);
+        batchRequests.add(br3);
+
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+        final List<BatchResponse> response = BatchHelper.postBatchRequestsWithEnclosingTransaction(this.requestSpec, this.responseSpec,
+                jsonifiedRequest);
+
+        // Verifies that none of the client in BatchRequest is created on the
+        // server
+        BatchHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, "TestExtId11");
+        BatchHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, "TestExtId12");
+
+        // Asserts that all the transactions have been successfully rolled back
+        Assert.assertEquals(response.size(), 1);
+        Assert.assertEquals("Verify Status code 400", (long) 400, (long) response.get(0).getStatusCode());
+    }
+
+    /**
+     * Tests that a client information was successfully updated through
+     * updateClientCommand. A 'changes' parameter is returned in the response
+     * after successful update of client information.
+     * 
+     * @see org.apache.fineract.batch.command.internal.UpdateClientCommandStrategy
+     */
+    @Test
+    public void shouldReflectChangesOnClientUpdate() {
+
+        // Create a createClient Request
+        final BatchRequest br1 = BatchHelper.createClientRequest(4716L, "");
+
+        // Create a clientUpdate Request
+        final BatchRequest br2 = BatchHelper.updateClientRequest(4717L, 4716L);
+
+        final List<BatchRequest> batchRequests = new ArrayList<>();
+
+        batchRequests.add(br1);
+        batchRequests.add(br2);
+
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+        final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
+                jsonifiedRequest);
+
+        // Get the changes parameter from updateClient Response
+        final JsonObject changes = new FromJsonHelper().parse(response.get(1).getBody()).getAsJsonObject().get("changes").getAsJsonObject();
+
+        // Asserts the client information is successfully updated
+        Assert.assertEquals("Verify Firstname", "TestFirstName", changes.get("firstname").getAsString());
+        Assert.assertEquals("Verify Lastname", "TestLastName", changes.get("lastname").getAsString());
+    }
+
+    /**
+     * Tests that a ApplyLoanCommand was successfully executed and returned a
+     * 200(OK) status. It creates a new client and apply a loan to that client.
+     * This also verifies the successful resolution of dependencies among two
+     * requests.
+     * 
+     * @see org.apache.fineract.batch.command.internal.ApplyLoanCommandStrategy
+     */
+    @Test
+    public void shouldReturnOkStatusForApplyLoanCommand() {
+
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("10000000.00") //
+                .withNumberOfRepayments("24") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("2") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualPrincipalPayment() //
+                .withInterestTypeAsDecliningBalance() //
+                .currencyDetails("0", "100").build(null);
+
+        final Integer productId = new LoanTransactionHelper(this.requestSpec, this.responseSpec).getLoanProductId(loanProductJSON);
+
+        // Create a createClient Request
+        final BatchRequest br1 = BatchHelper.createClientRequest(4718L, "");
+
+        // Create a activateClient Request
+        final BatchRequest br2 = BatchHelper.activateClientRequest(4719L, 4718L);
+
+        // Create a ApplyLoan Request
+        final BatchRequest br3 = BatchHelper.applyLoanRequest(4720L, 4719L, productId);
+
+        final List<BatchRequest> batchRequests = new ArrayList<>();
+
+        batchRequests.add(br1);
+        batchRequests.add(br2);
+        batchRequests.add(br3);
+
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+        final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
+                jsonifiedRequest);
+
+        // Get the clientId parameter from createClient Response
+        final JsonElement clientId = new FromJsonHelper().parse(response.get(0).getBody()).getAsJsonObject().get("clientId");
+
+        Assert.assertEquals("Verify Status Code 200" + clientId.getAsString(), 200L, (long) response.get(1).getStatusCode());
+    }
+
+    /**
+     * Tests that a new savings accounts was applied to an existing client and a
+     * 200(OK) status was returned. It first creates a new client and a savings
+     * product, then uses the cliendId and ProductId to apply a savings account.
+     * 
+     * @see org.apache.fineract.batch.command.internal.ApplySavingsCommandStrategy
+     */
+    @Test
+    public void shouldReturnOkStatusForApplySavingsCommand() {
+
+        final SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+        final String savingsProductJSON = savingsProductHelper //
+                .withInterestCompoundingPeriodTypeAsDaily() //
+                .withInterestPostingPeriodTypeAsMonthly() //
+                .withInterestCalculationPeriodTypeAsDailyBalance() //
+                .withMinimumOpenningBalance("5000").build();
+
+        final Integer productId = SavingsProductHelper.createSavingsProduct(savingsProductJSON, this.requestSpec, this.responseSpec);
+
+        // Create a createClient Request
+        final BatchRequest br1 = BatchHelper.createClientRequest(4720L, "");
+
+        // Create a activateClient Request
+        final BatchRequest br2 = BatchHelper.activateClientRequest(4721L, 4720L);
+
+        // Create a applySavings Request
+        final BatchRequest br3 = BatchHelper.applySavingsRequest(4722L, 4721L, productId);
+
+        final List<BatchRequest> batchRequests = new ArrayList<>();
+
+        batchRequests.add(br1);
+        batchRequests.add(br2);
+        batchRequests.add(br3);
+
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+        final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
+                jsonifiedRequest);
+
+        Assert.assertEquals("Verify Status Code 200", 200L, (long) response.get(1).getStatusCode());
+    }
+
+    /**
+     * Tests that a new charge was added to a newly created loan and charges are
+     * Collected properly 200(OK) status was returned for successful responses.
+     * It first creates a new client and apply a loan, then creates a new charge
+     * for the create loan and then fetches all the applied charges
+     * 
+     * @see org.apache.fineract.batch.command.internal.CollectChargesCommandStrategy
+     * @see org.apache.fineract.batch.command.internal.CreateChargeCommandStrategy
+     */
+    @Test
+    public void shouldReturnOkStatusForCollectChargesCommand() {
+
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("10000000.00") //
+                .withNumberOfRepayments("24") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("2") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualPrincipalPayment() //
+                .withInterestTypeAsDecliningBalance() //
+                .currencyDetails("0", "100").build(null);
+
+        final Integer productId = new LoanTransactionHelper(this.requestSpec, this.responseSpec).getLoanProductId(loanProductJSON);
+
+        // Create a createClient Request
+        final BatchRequest br1 = BatchHelper.createClientRequest(4722L, "");
+
+        // Create a activateClient Request
+        final BatchRequest br2 = BatchHelper.activateClientRequest(4723L, 4722L);
+
+        // Create a ApplyLoan Request
+        final BatchRequest br3 = BatchHelper.applyLoanRequest(4724L, 4723L, productId);
+
+        // Create a Collect Charges Request
+        final BatchRequest br4 = BatchHelper.collectChargesRequest(4725L, 4724L);
+
+        final List<BatchRequest> batchRequests = new ArrayList<>();
+
+        batchRequests.add(br1);
+        batchRequests.add(br2);
+        batchRequests.add(br3);
+        batchRequests.add(br4);
+
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+        final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
+                jsonifiedRequest);
+
+        Assert.assertEquals("Verify Status Code 200 for Create Loan Charge", 200L, (long) response.get(3).getStatusCode());
+    }
+
+    /**
+     * Test for the successful activation of a pending client using
+     * 'ActivateClientCommandStrategy'. A '200' status code is expected on
+     * successful activation.
+     * 
+     * @see org.apache.fineract.batch.command.internal.ActivateClientCommandStrategy
+     */
+    @Test
+    public void shouldReturnOkStatusOnSuccessfulClientActivation() {
+
+        // Create a createClient Request
+        final BatchRequest br1 = BatchHelper.createClientRequest(4726L, "");
+
+        // Create a activateClient Request
+        final BatchRequest br2 = BatchHelper.activateClientRequest(4727L, 4726L);
+
+        final List<BatchRequest> batchRequests = new ArrayList<>();
+
+        batchRequests.add(br1);
+        batchRequests.add(br2);
+
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+        final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
+                jsonifiedRequest);
+
+        Assert.assertEquals("Verify Status Code 200 for Create Client", 200L, (long) response.get(0).getStatusCode());
+        Assert.assertEquals("Verify Status Code 200 for Activate Client", 200L, (long) response.get(1).getStatusCode());
+    }
+
+    /**
+     * Test for the successful approval and disbursal of a loan using
+     * 'ApproveLoanCommandStrategy' and 'DisburseLoanCommandStrategy'. A '200'
+     * status code is expected on successful activation.
+     * 
+     * @see org.apache.fineract.batch.command.internal.ApproveLoanCommandStrategy
+     * @see org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy
+     */
+    @Test
+    public void shouldReturnOkStatusOnSuccessfulLoanApprovalAndDisburse() {
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("10000000.00") //
+                .withNumberOfRepayments("24") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("2") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualPrincipalPayment() //
+                .withInterestTypeAsDecliningBalance() //
+                .currencyDetails("0", "100").build(null);
+
+        final Integer productId = new LoanTransactionHelper(this.requestSpec, this.responseSpec).getLoanProductId(loanProductJSON);
+
+        // Create a createClient Request
+        final BatchRequest br1 = BatchHelper.createClientRequest(4730L, "");
+
+        // Create a activateClient Request
+        final BatchRequest br2 = BatchHelper.activateClientRequest(4731L, 4730L);
+
+        // Create a ApplyLoan Request
+        final BatchRequest br3 = BatchHelper.applyLoanRequest(4732L, 4731L, productId);
+
+        // Create a approveLoan Request
+        final BatchRequest br4 = BatchHelper.approveLoanRequest(4733L, 4732L);
+
+        // Create a disburseLoan Request
+        final BatchRequest br5 = BatchHelper.disburseLoanRequest(4734L, 4733L);
+
+        final List<BatchRequest> batchRequests = new ArrayList<>();
+
+        batchRequests.add(br1);
+        batchRequests.add(br2);
+        batchRequests.add(br3);
+        batchRequests.add(br4);
+        batchRequests.add(br5);
+
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+        final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
+                jsonifiedRequest);
+
+        Assert.assertEquals("Verify Status Code 200 for Approve Loan", 200L, (long) response.get(3).getStatusCode());
+        Assert.assertEquals("Verify Status Code 200 for Disburse Loan", 200L, (long) response.get(4).getStatusCode());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/BatchRequestsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/BatchRequestsIntegrationTest.java
new file mode 100644
index 0000000..ad1f61b
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/BatchRequestsIntegrationTest.java
@@ -0,0 +1,140 @@
+/**
+ * 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.integrationtests;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.integrationtests.common.BatchHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GroupHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Test class for testing the integration of Batch API with custom batch
+ * requests and various user defined workflow. Like in the case of mifos
+ * community-app
+ * 
+ * @author Rishabh Shukla
+ */
+public class BatchRequestsIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    public BatchRequestsIntegrationTest() {
+        super();
+    }
+
+    /**
+     * Sets up the essential settings for the TEST like contentType,
+     * expectedStatusCode. It uses the '@Before' annotation provided by jUnit.
+     */
+    @Before
+    public void setup() {
+
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    /**
+     * Tests that a loan is successfully applied to client members of a group. 
+     * Firstly, it'll create a few new clients and then will add those clients
+     * to the group. Then a few loans will be created and one of those loans
+     * will be chosen at random and similarily a few of the created clients will
+     * be chosen on random. Now, the selected loan will be applied to these
+     * clients through Batch - API ApplyLoanCommandStrategy.  
+     */
+    public void shouldReturnOkStatusForLoansAppliedToSelectedClients() {
+
+        // Generate a random count of number of clients to be created
+        final Integer clientsCount = (int) Math.ceil(Math.random() * 7) + 3;
+        final Integer[] clientIDs = new Integer[clientsCount];
+
+        // Create a new group and get its groupId
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
+
+        // Create new clients and add those to this group
+        for (Integer i = 0; i < clientsCount; i++) {
+            clientIDs[i] = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+            groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientIDs[i].toString());
+            System.out.println("client " + clientIDs[i] + " has been added to the group " + groupID);
+        }
+
+        // Generate a random count of number of new loan products to be created
+        final Integer loansCount = (int) Math.ceil(Math.random() * 4) + 1;
+        final Integer[] loanProducts = new Integer[loansCount];
+
+        // Create new loan Products
+        for (Integer i = 0; i < loansCount; i++) {
+            final String loanProductJSON = new LoanProductTestBuilder() //
+                    .withPrincipal(String.valueOf(10000.00 + Math.ceil(Math.random() * 1000000.00))) //
+                    .withNumberOfRepayments(String.valueOf(2 + (int) Math.ceil(Math.random() * 36))) //
+                    .withRepaymentAfterEvery(String.valueOf(1 + (int) Math.ceil(Math.random() * 3))) //
+                    .withRepaymentTypeAsMonth() //
+                    .withinterestRatePerPeriod(String.valueOf(1 + (int) Math.ceil(Math.random() * 4))) //
+                    .withInterestRateFrequencyTypeAsMonths() //
+                    .withAmortizationTypeAsEqualPrincipalPayment() //
+                    .withInterestTypeAsDecliningBalance() //
+                    .currencyDetails("0", "100").build(null);
+
+            loanProducts[i] = new LoanTransactionHelper(this.requestSpec, this.responseSpec).getLoanProductId(loanProductJSON);
+        }
+
+        // Select anyone of the loan products at random
+        final Integer loanProductID = loanProducts[(int) Math.floor(Math.random() * (loansCount - 1))];
+
+        final List<BatchRequest> batchRequests = new ArrayList<>();
+
+        // Select a few clients from created group at random
+        Integer selClientsCount = (int) Math.ceil(Math.random() * clientsCount) + 2;
+        for (int i = 0; i < selClientsCount; i++) {
+            BatchRequest br = BatchHelper.applyLoanRequest((long) selClientsCount, null, loanProductID);
+            br.setBody(br.getBody().replace("$.clientId", String.valueOf(clientIDs[(int) Math.floor(Math.random() * (clientsCount - 1))])));
+            batchRequests.add(br);
+        }
+
+        // Send the request to Batch - API
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+        final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
+                jsonifiedRequest);
+
+        // Verify that each loan has been applied successfully
+        for (BatchResponse res : response) {
+            Assert.assertEquals("Verify Status Code 200", 200L, (long) res.getStatusCode());
+        }
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/CenterIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/CenterIntegrationTest.java
new file mode 100644
index 0000000..d923ee5
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/CenterIntegrationTest.java
@@ -0,0 +1,261 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.CenterDomain;
+import org.apache.fineract.integrationtests.common.CenterHelper;
+import org.apache.fineract.integrationtests.common.OfficeHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class CenterIntegrationTest {
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void testBasicCenterCreation() {
+        int officeId = new OfficeHelper(requestSpec, responseSpec).createOffice("01 July 2007");
+
+        String name = "TestBasicCreation" + new Timestamp(new java.util.Date().getTime());
+        int resourceId = CenterHelper.createCenter(name, officeId, requestSpec, responseSpec);
+        CenterDomain center = CenterHelper.retrieveByID(resourceId, requestSpec, responseSpec);
+
+        Assert.assertNotNull(center);
+        Assert.assertTrue(center.getName().equals(name));
+        Assert.assertTrue(center.getOfficeId() == officeId);
+        Assert.assertTrue(center.isActive() == false);
+
+        // Test retrieval by listing all centers
+        int id = CenterHelper.listCenters(requestSpec, responseSpec).get(0).getId();
+        Assert.assertTrue(id > 0);
+
+        CenterDomain retrievedCenter = CenterHelper.retrieveByID(id, requestSpec, responseSpec);
+        Assert.assertNotNull(retrievedCenter);
+        Assert.assertNotNull(retrievedCenter.getName());
+        Assert.assertNotNull(retrievedCenter.getHierarchy());
+        Assert.assertNotNull(retrievedCenter.getOfficeName());
+
+    }
+
+    @Test
+    public void testFullCenterCreation() {
+
+        int officeId = new OfficeHelper(requestSpec, responseSpec).createOffice("01 July 2007");
+        String name = "TestFullCreation" + new Timestamp(new java.util.Date().getTime());
+        String externalId = Utils.randomStringGenerator("ID_", 7, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+        int staffId = StaffHelper.createStaff(requestSpec, responseSpec);
+        int[] groupMembers = generateGroupMembers(3, officeId);
+        int resourceId = CenterHelper.createCenter(name, officeId, externalId, staffId, groupMembers, requestSpec, responseSpec);
+        CenterDomain center = CenterHelper.retrieveByID(resourceId, requestSpec, responseSpec);
+
+        Assert.assertNotNull(center);
+        Assert.assertTrue(center.getName().equals(name));
+        Assert.assertTrue(center.getOfficeId() == officeId);
+        Assert.assertTrue(center.getExternalId().equals(externalId));
+        Assert.assertTrue(center.getStaffId() == staffId);
+        Assert.assertTrue(center.isActive() == false);
+        Assert.assertArrayEquals(center.getGroupMembers(), groupMembers);
+    }
+
+    @Test
+    public void testListCenters() {
+        ArrayList<CenterDomain> paginatedList = CenterHelper.paginatedListCenters(requestSpec, responseSpec);
+        ArrayList<CenterDomain> list = CenterHelper.listCenters(requestSpec, responseSpec);
+
+        Assert.assertNotNull(paginatedList);
+        Assert.assertNotNull(list);
+        Assert.assertTrue(Arrays.equals(paginatedList.toArray(new CenterDomain[paginatedList.size()]),
+                list.toArray(new CenterDomain[list.size()])));
+    }
+
+    @Test
+    public void testVoidCenterRetrieval() {
+        ArrayList<CenterDomain> arr = CenterHelper.listCenters(requestSpec, responseSpec);
+        int id = arr.get(arr.size() - 1).getId() + 1;
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(404).build();
+        CenterDomain center = CenterHelper.retrieveByID(id, requestSpec, responseSpec);
+        Assert.assertNotNull(center);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testCenterUpdate() {
+        int officeId = new OfficeHelper(requestSpec, responseSpec).createOffice("01 July 2007");
+        String name = "TestFullCreation" + new Timestamp(new java.util.Date().getTime());
+        String externalId = Utils.randomStringGenerator("ID_", 7, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+        int staffId = StaffHelper.createStaff(requestSpec, responseSpec);
+        int[] groupMembers = generateGroupMembers(3, officeId);
+        int resourceId = CenterHelper.createCenter(name, officeId, externalId, staffId, groupMembers, requestSpec, responseSpec);
+
+        String newName = "TestCenterUpdateNew" + new Timestamp(new java.util.Date().getTime());
+        String newExternalId = Utils.randomStringGenerator("newID_", 7, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+        int newStaffId = StaffHelper.createStaff(requestSpec, responseSpec);
+        int[] associateGroupMembers = generateGroupMembers(2, officeId);
+
+        int[] associateResponse = CenterHelper.associateGroups(resourceId, associateGroupMembers, requestSpec, responseSpec);
+        Arrays.sort(associateResponse);
+        Arrays.sort(associateGroupMembers);
+        Assert.assertArrayEquals(associateResponse, associateGroupMembers);
+
+        int[] newGroupMembers = new int[5];
+        for (int i = 0; i < 5; i++) {
+            if (i < 3) {
+                newGroupMembers[i] = groupMembers[i];
+            } else {
+                newGroupMembers[i] = associateGroupMembers[i % 3];
+            }
+        }
+
+        HashMap request = new HashMap();
+        request.put("name", newName);
+        request.put("externalId", newExternalId);
+        request.put("staffId", newStaffId);
+        HashMap response = CenterHelper.updateCenter(resourceId, request, requestSpec, responseSpec);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(newName, response.get("name"));
+        Assert.assertEquals(newExternalId, response.get("externalId"));
+        Assert.assertEquals(newStaffId, response.get("staffId"));
+
+        CenterDomain center = CenterHelper.retrieveByID(resourceId, requestSpec, responseSpec);
+        Assert.assertNotNull(center);
+        Assert.assertEquals(newName, center.getName());
+        Assert.assertEquals(newExternalId, center.getExternalId());
+        Assert.assertEquals((Integer)newStaffId, center.getStaffId());
+        Assert.assertArrayEquals(newGroupMembers, center.getGroupMembers());
+    }
+
+    @Test
+    public void testCenterDeletion() {
+        int officeId = new OfficeHelper(requestSpec, responseSpec).createOffice("01 July 2007");
+        String name = "TestBasicCreation" + new Timestamp(new java.util.Date().getTime());
+        int resourceId = CenterHelper.createCenter(name, officeId, requestSpec, responseSpec);
+
+        CenterHelper.deleteCenter(resourceId, requestSpec, responseSpec);
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(404).build();
+        CenterDomain center = CenterHelper.retrieveByID(resourceId, requestSpec, responseSpec);
+        Assert.assertNotNull(center);
+    }
+
+    private int[] generateGroupMembers(int size, int officeId) {
+        int[] groupMembers = new int[size];
+        for (int i = 0; i < groupMembers.length; i++) {
+            final HashMap<String, String> map = new HashMap<>();
+            map.put("officeId", "" + officeId);
+            map.put("name", Utils.randomStringGenerator("Group_Name_", 5));
+            map.put("externalId", Utils.randomStringGenerator("ID_", 7, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+            map.put("dateFormat", "dd MMMM yyyy");
+            map.put("locale", "en");
+            map.put("active", "true");
+            map.put("activationDate", "04 March 2011");
+
+            groupMembers[i] = Utils.performServerPost(requestSpec, responseSpec, "/fineract-provider/api/v1/groups?"
+                    + Utils.TENANT_IDENTIFIER, new Gson().toJson(map), "groupId");
+        }
+        return groupMembers;
+    }
+
+    @Test
+    public void testStaffAssignmentDuringCenterCreation() {
+
+        Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        System.out.println("--------------creating first staff with id-------------" + staffId);
+        Assert.assertNotNull(staffId);
+
+        int centerWithStaffId = CenterHelper.createCenterWithStaffId(this.requestSpec, this.responseSpec, staffId);
+        CenterDomain center = CenterHelper.retrieveByID(centerWithStaffId, requestSpec, responseSpec);
+        Assert.assertNotNull(center);
+        Assert.assertTrue(center.getId() == centerWithStaffId);
+        Assert.assertTrue(center.getStaffId() == staffId);
+        Assert.assertTrue(center.isActive() == true);
+    }
+
+    @Test
+    public void testAssignStaffToCenter() {
+        Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        System.out.println("--------------creating first staff with id-------------" + staffId);
+        Assert.assertNotNull(staffId);
+
+        Integer groupID = CenterHelper.createCenter(this.requestSpec, this.responseSpec);
+        CenterHelper.verifyCenterCreatedOnServer(this.requestSpec, this.responseSpec, groupID);
+
+        HashMap assignStaffToCenterResponseMap = (HashMap) CenterHelper.assignStaff(this.requestSpec, this.responseSpec, groupID.toString(),
+                staffId.longValue());
+        assertEquals("Verify assigned staff id is the same as id sent", assignStaffToCenterResponseMap.get("staffId"), staffId);
+
+        CenterDomain center = CenterHelper.retrieveByID(groupID, requestSpec, responseSpec);
+        Assert.assertNotNull(center);
+        Assert.assertTrue(center.getId() == groupID);
+        Assert.assertTrue(center.getStaffId() == staffId);
+
+    }
+
+    @Test
+    public void testUnassignStaffToCenter() {
+        Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        System.out.println("--------------creating first staff with id-------------" + staffId);
+        Assert.assertNotNull(staffId);
+
+        Integer groupID = CenterHelper.createCenter(this.requestSpec, this.responseSpec);
+        CenterHelper.verifyCenterCreatedOnServer(this.requestSpec, this.responseSpec, groupID);
+        
+        HashMap assignStaffToCenterResponseMap = (HashMap) CenterHelper.assignStaff(this.requestSpec, this.responseSpec, groupID.toString(),
+                staffId.longValue());
+        assertEquals("Verify assigned staff id is the same as id sent", assignStaffToCenterResponseMap.get("staffId"), staffId);
+        CenterDomain centerWithStaffAssigned = CenterHelper.retrieveByID(groupID, requestSpec, responseSpec);
+        Assert.assertNotNull(centerWithStaffAssigned);
+        Assert.assertTrue(centerWithStaffAssigned.getId() == groupID);
+        Assert.assertTrue(centerWithStaffAssigned.getStaffId() == staffId);
+        
+        HashMap unassignStaffToCenterResponseMap = (HashMap) CenterHelper.unassignStaff(this.requestSpec, this.responseSpec, groupID.toString(),
+                staffId.longValue());
+        assertEquals("Verify staffId is null after unassigning ", unassignStaffToCenterResponseMap.get("staffId"), null);
+        CenterDomain centerWithStaffUnssigned = CenterHelper.retrieveByID(groupID, requestSpec, responseSpec);
+        Assert.assertNotNull(centerWithStaffUnssigned);
+        Assert.assertTrue(centerWithStaffUnssigned.getId() == groupID);
+        Assert.assertTrue(centerWithStaffUnssigned.getStaffId() == null);
+        
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ChargesTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ChargesTest.java
new file mode 100644
index 0000000..d441401
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ChargesTest.java
@@ -0,0 +1,326 @@
+/**
+ * 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.integrationtests;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes" })
+public class ChargesTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void testChargesForLoans() {
+
+        // Retrieving all Charges
+        ArrayList<HashMap> allChargesData = ChargesHelper.getCharges(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(allChargesData);
+
+        // Testing Creation, Updation and Deletion of Disbursement Charge
+        final Integer disbursementChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getLoanDisbursementJSON());
+        Assert.assertNotNull(disbursementChargeId);
+
+        // Updating Charge Amount
+        HashMap changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, disbursementChargeId,
+                ChargesHelper.getModifyChargeJSON());
+
+        HashMap chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, disbursementChargeId);
+        Assert.assertEquals("Verifying Charge after Modification", chargeDataAfterChanges.get("amount"), changes.get("amount"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, disbursementChargeId,
+                ChargesHelper.getModifyChargeAsPecentageAmountJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, disbursementChargeId);
+
+        HashMap chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargePaymentMode");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargePaymentMode"));
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, disbursementChargeId,
+                ChargesHelper.getModifyChargeAsPecentageLoanAmountWithInterestJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, disbursementChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, disbursementChargeId,
+                ChargesHelper.getModifyChargeAsPercentageInterestJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, disbursementChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        Integer chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, disbursementChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", disbursementChargeId, chargeIdAfterDeletion);
+
+        // Testing Creation, Updation and Deletion of Specified due date Charge
+        final Integer specifiedDueDateChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON());
+        Assert.assertNotNull(specifiedDueDateChargeId);
+
+        // Updating Charge Amount
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, specifiedDueDateChargeId,
+                ChargesHelper.getModifyChargeJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, specifiedDueDateChargeId);
+        Assert.assertEquals("Verifying Charge after Modification", chargeDataAfterChanges.get("amount"), changes.get("amount"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, specifiedDueDateChargeId,
+                ChargesHelper.getModifyChargeAsPecentageAmountJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, specifiedDueDateChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargePaymentMode");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargePaymentMode"));
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, specifiedDueDateChargeId,
+                ChargesHelper.getModifyChargeAsPecentageLoanAmountWithInterestJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, specifiedDueDateChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, specifiedDueDateChargeId,
+                ChargesHelper.getModifyChargeAsPercentageInterestJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, specifiedDueDateChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, specifiedDueDateChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", specifiedDueDateChargeId, chargeIdAfterDeletion);
+
+        // Testing Creation, Updation and Deletion of Installment Fee Charge
+        final Integer installmentFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getLoanInstallmentFeeJSON());
+
+        // Updating Charge Amount
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, installmentFeeChargeId,
+                ChargesHelper.getModifyChargeJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, installmentFeeChargeId);
+        Assert.assertEquals("Verifying Charge after Modification", chargeDataAfterChanges.get("amount"), changes.get("amount"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, installmentFeeChargeId,
+                ChargesHelper.getModifyChargeAsPecentageAmountJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, installmentFeeChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargePaymentMode");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargePaymentMode"));
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, installmentFeeChargeId,
+                ChargesHelper.getModifyChargeAsPecentageLoanAmountWithInterestJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, installmentFeeChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, installmentFeeChargeId,
+                ChargesHelper.getModifyChargeAsPercentageInterestJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, installmentFeeChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, installmentFeeChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", installmentFeeChargeId, chargeIdAfterDeletion);
+
+        // Testing Creation, Updation and Deletion of Overdue Installment Fee
+        // Charge
+        final Integer overdueFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getLoanOverdueFeeJSON());
+        Assert.assertNotNull(overdueFeeChargeId);
+
+        // Updating Charge Amount
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, overdueFeeChargeId, ChargesHelper.getModifyChargeJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, overdueFeeChargeId);
+        Assert.assertEquals("Verifying Charge after Modification", chargeDataAfterChanges.get("amount"), changes.get("amount"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, overdueFeeChargeId,
+                ChargesHelper.getModifyChargeAsPecentageAmountJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, overdueFeeChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargePaymentMode");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargePaymentMode"));
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, overdueFeeChargeId,
+                ChargesHelper.getModifyChargeAsPecentageLoanAmountWithInterestJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, overdueFeeChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, overdueFeeChargeId,
+                ChargesHelper.getModifyChargeAsPercentageInterestJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, overdueFeeChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, overdueFeeChargeId,
+                ChargesHelper.getModifyChargeFeeFrequencyAsYearsJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, overdueFeeChargeId);
+
+        chargeChangedData = (HashMap) chargeDataAfterChanges.get("feeFrequency");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("feeFrequency"));
+
+        chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, overdueFeeChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", overdueFeeChargeId, chargeIdAfterDeletion);
+    }
+
+    @Test
+    public void testChargesForSavings() {
+
+        // Testing Creation, Updation and Deletion of Specified due date Charge
+        final Integer specifiedDueDateChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsSpecifiedDueDateJSON());
+        Assert.assertNotNull(specifiedDueDateChargeId);
+
+        // Updating Charge Amount
+        HashMap changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, specifiedDueDateChargeId,
+                ChargesHelper.getModifyChargeJSON());
+
+        HashMap chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, specifiedDueDateChargeId);
+        Assert.assertEquals("Verifying Charge after Modification", chargeDataAfterChanges.get("amount"), changes.get("amount"));
+
+        Integer chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, specifiedDueDateChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", specifiedDueDateChargeId, chargeIdAfterDeletion);
+
+        // Testing Creation, Updation and Deletion of Savings Activation Charge
+        final Integer savingsActivationChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsActivationFeeJSON());
+        Assert.assertNotNull(savingsActivationChargeId);
+
+        // Updating Charge Amount
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, savingsActivationChargeId,
+                ChargesHelper.getModifyChargeJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, savingsActivationChargeId);
+        Assert.assertEquals("Verifying Charge after Modification", chargeDataAfterChanges.get("amount"), changes.get("amount"));
+
+        chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, savingsActivationChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", savingsActivationChargeId, chargeIdAfterDeletion);
+
+        // Testing Creation, Updation and Deletion of Charge for Withdrawal Fee
+        final Integer withdrawalFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsWithdrawalFeeJSON());
+        Assert.assertNotNull(withdrawalFeeChargeId);
+
+        // Updating Charge-Calculation-Type to Withdrawal-Fee
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, withdrawalFeeChargeId,
+                ChargesHelper.getModifyWithdrawalFeeSavingsChargeJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, withdrawalFeeChargeId);
+
+        HashMap chargeChangedData = (HashMap) chargeDataAfterChanges.get("chargeCalculationType");
+        Assert.assertEquals("Verifying Charge after Modification", chargeChangedData.get("id"), changes.get("chargeCalculationType"));
+
+        chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, withdrawalFeeChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", withdrawalFeeChargeId, chargeIdAfterDeletion);
+
+        // Testing Creation, Updation and Deletion of Charge for Annual Fee
+        final Integer annualFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsAnnualFeeJSON());
+        Assert.assertNotNull(annualFeeChargeId);
+
+        // Updating Charge Amount
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, annualFeeChargeId, ChargesHelper.getModifyChargeJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, annualFeeChargeId);
+        Assert.assertEquals("Verifying Charge after Modification", chargeDataAfterChanges.get("amount"), changes.get("amount"));
+
+        chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, annualFeeChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", annualFeeChargeId, chargeIdAfterDeletion);
+
+        // Testing Creation, Updation and Deletion of Charge for Monthly Fee
+        final Integer monthlyFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsMonthlyFeeJSON());
+        Assert.assertNotNull(monthlyFeeChargeId);
+
+        // Updating Charge Amount
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, monthlyFeeChargeId, ChargesHelper.getModifyChargeJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, monthlyFeeChargeId);
+        Assert.assertEquals("Verifying Charge after Modification", chargeDataAfterChanges.get("amount"), changes.get("amount"));
+
+        chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, monthlyFeeChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", monthlyFeeChargeId, chargeIdAfterDeletion);
+
+        // Testing Creation, Updation and Deletion of Charge for Overdraft Fee
+        final Integer overdraftFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsOverdraftFeeJSON());
+        Assert.assertNotNull(overdraftFeeChargeId);
+
+        // Updating Charge Amount
+        changes = ChargesHelper.updateCharges(this.requestSpec, this.responseSpec, overdraftFeeChargeId,
+                ChargesHelper.getModifyChargeJSON());
+
+        chargeDataAfterChanges = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, overdraftFeeChargeId);
+        Assert.assertEquals("Verifying Charge after Modification", chargeDataAfterChanges.get("amount"), changes.get("amount"));
+
+        chargeIdAfterDeletion = ChargesHelper.deleteCharge(this.responseSpec, this.requestSpec, overdraftFeeChargeId);
+        Assert.assertEquals("Verifying Charge ID after deletion", overdraftFeeChargeId, chargeIdAfterDeletion);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
new file mode 100644
index 0000000..c27abc3
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -0,0 +1,5051 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import org.apache.fineract.integrationtests.common.accounting.PeriodicAccrualAccountingHelper;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.savings.AccountTransferHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.joda.time.LocalDate;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.JsonObject;
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.path.json.JsonPath;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Client Loan Integration Test for checking Loan Application Repayments
+ * Schedule, loan charges, penalties, loan repayments and verifying accounting
+ * transactions
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class ClientLoanIntegrationTest {
+
+    public static final String MINIMUM_OPENING_BALANCE = "1000.0";
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private static final String NONE = "1";
+    private static final String CASH_BASED = "2";
+    private static final String ACCRUAL_PERIODIC = "3";
+    private static final String ACCRUAL_UPFRONT = "4";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private JournalEntryHelper journalEntryHelper;
+    private AccountHelper accountHelper;
+    private SchedulerJobHelper schedulerJobHelper;
+    private PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+    private AccountTransferHelper accountTransferHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void checkClientLoanCreateAndDisburseFlow() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct(false, NONE);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, null, null, "12,000.00");
+        final ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec,
+                loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+    }
+
+    @Test
+    public void testLoanCharges_DISBURSEMENT_FEE() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct(false, NONE);
+
+        List<HashMap> charges = new ArrayList<>();
+        Integer flatDisbursement = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanDisbursementJSON());
+
+        Integer amountPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1"));
+        addCharges(charges, amountPercentage, "1", null);
+        Integer amountPlusInterestPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1"));
+        addCharges(charges, amountPlusInterestPercentage, "1", null);
+        Integer interestPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST, "1"));
+        addCharges(charges, interestPercentage, "1", null);
+
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap disbursementDetail = loanSchedule.get(0);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+
+        validateCharge(amountPercentage, loanCharges, "1.0", "120.0", "0.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "1.0", "6.06", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "1.0", "126.06", "0.0", "0.0");
+
+        validateNumberForEqual("252.12", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getDisbursementChargesForLoanAsJSON(String.valueOf(flatDisbursement)));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        disbursementDetail = loanSchedule.get(0);
+
+        validateCharge(flatDisbursement, loanCharges, "100.0", "100.0", "0.0", "0.0");
+        validateNumberForEqual("352.12", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(amountPercentage, loanCharges).get("id"),
+                LoanTransactionHelper.getUpdateChargesForLoanAsJSON("2"));
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(interestPercentage, loanCharges).get("id"),
+                LoanTransactionHelper.getUpdateChargesForLoanAsJSON("2"));
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(amountPlusInterestPercentage, loanCharges)
+                .get("id"), LoanTransactionHelper.getUpdateChargesForLoanAsJSON("2"));
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(flatDisbursement, loanCharges).get("id"),
+                LoanTransactionHelper.getUpdateChargesForLoanAsJSON("150"));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        disbursementDetail = loanSchedule.get(0);
+        validateCharge(amountPercentage, loanCharges, "2.0", "240.0", "0.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "2.0", "12.12", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "2.0", "252.12", "0.0", "0.0");
+        validateCharge(flatDisbursement, loanCharges, "150.0", "150.0", "0.0", "0.0");
+        validateNumberForEqual("654.24", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        this.loanTransactionHelper.updateLoan(loanID,
+                updateLoanJson(clientID, loanProductID, copyChargesForUpdate(loanCharges, null, null), null));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        disbursementDetail = loanSchedule.get(0);
+        validateCharge(amountPercentage, loanCharges, "2.0", "200.0", "0.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "2.0", "10.1", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "2.0", "210.1", "0.0", "0.0");
+        validateCharge(flatDisbursement, loanCharges, "150.0", "150.0", "0.0", "0.0");
+        validateNumberForEqual("570.2", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        this.loanTransactionHelper.updateLoan(loanID,
+                updateLoanJson(clientID, loanProductID, copyChargesForUpdate(loanCharges, flatDisbursement, "1"), null));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        disbursementDetail = loanSchedule.get(0);
+        validateCharge(amountPercentage, loanCharges, "1.0", "100.0", "0.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "1.0", "5.05", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "1.0", "105.05", "0.0", "0.0");
+        validateNumberForEqual("210.1", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        charges.clear();
+        addCharges(charges, flatDisbursement, "100", null);
+        this.loanTransactionHelper.updateLoan(loanID, updateLoanJson(clientID, loanProductID, charges, null));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        disbursementDetail = loanSchedule.get(0);
+        validateCharge(flatDisbursement, loanCharges, "100.0", "100.0", "0.0", "0.0");
+        validateNumberForEqual("100.0", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        this.loanTransactionHelper.deleteChargesForLoan(loanID, (Integer) getloanCharge(flatDisbursement, loanCharges).get("id"));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        disbursementDetail = loanSchedule.get(0);
+        Assert.assertEquals(0, loanCharges.size());
+        validateNumberForEqual("0.0", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+    }
+
+    @Test
+    public void testLoanCharges_DISBURSEMENT_FEE_WITH_AMOUNT_CHANGE() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct(false, NONE);
+
+        List<HashMap> charges = new ArrayList<>();
+        Integer amountPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1"));
+        addCharges(charges, amountPercentage, "1", null);
+        Integer amountPlusInterestPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1"));
+        addCharges(charges, amountPlusInterestPercentage, "1", null);
+        Integer interestPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST, "1"));
+        addCharges(charges, interestPercentage, "1", null);
+
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap disbursementDetail = loanSchedule.get(0);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+
+        validateCharge(amountPercentage, loanCharges, "1.0", "120.0", "0.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "1.0", "6.06", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "1.0", "126.06", "0.0", "0.0");
+        validateNumberForEqual("252.12", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        // DISBURSE
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID, "10000");
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        disbursementDetail = loanSchedule.get(0);
+
+        validateCharge(amountPercentage, loanCharges, "1.0", "0.0", "100.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "1.0", "0.0", "5.05", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "1.0", "0.0", "105.05", "0.0");
+        validateNumberForEqual("210.1", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+    }
+
+    @Test
+    public void testLoanCharges_SPECIFIED_DUE_DATE_FEE() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct(false, NONE);
+
+        List<HashMap> charges = new ArrayList<>();
+        Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", false));
+        Integer flatAccTransfer = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateWithAccountTransferJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", false));
+
+        Integer amountPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", false));
+        addCharges(charges, amountPercentage, "1", "29 September 2011");
+        Integer amountPlusInterestPercentage = ChargesHelper
+                .createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateJSON(
+                        ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1", false));
+        addCharges(charges, amountPlusInterestPercentage, "1", "29 September 2011");
+        Integer interestPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST, "1", false));
+        addCharges(charges, interestPercentage, "1", "29 September 2011");
+
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap firstInstallment = loanSchedule.get(1);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+
+        validateCharge(amountPercentage, loanCharges, "1.0", "120.0", "0.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "1.0", "6.06", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "1.0", "126.06", "0.0", "0.0");
+
+        validateNumberForEqual("252.12", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flat), "29 September 2011", "100"));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+
+        validateCharge(flat, loanCharges, "100.0", "100.0", "0.0", "0.0");
+        validateNumberForEqual("352.12", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(amountPercentage, loanCharges).get("id"),
+                LoanTransactionHelper.getUpdateChargesForLoanAsJSON("2"));
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(interestPercentage, loanCharges).get("id"),
+                LoanTransactionHelper.getUpdateChargesForLoanAsJSON("2"));
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(amountPlusInterestPercentage, loanCharges)
+                .get("id"), LoanTransactionHelper.getUpdateChargesForLoanAsJSON("2"));
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(flat, loanCharges).get("id"),
+                LoanTransactionHelper.getUpdateChargesForLoanAsJSON("150"));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateCharge(amountPercentage, loanCharges, "2.0", "240.0", "0.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "2.0", "12.12", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "2.0", "252.12", "0.0", "0.0");
+        validateCharge(flat, loanCharges, "150.0", "150.0", "0.0", "0.0");
+        validateNumberForEqual("654.24", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        final Integer savingsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID,
+                MINIMUM_OPENING_BALANCE);
+        this.loanTransactionHelper.updateLoan(loanID,
+                updateLoanJson(clientID, loanProductID, copyChargesForUpdate(loanCharges, null, null), String.valueOf(savingsId)));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateCharge(amountPercentage, loanCharges, "2.0", "200.0", "0.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "2.0", "10.1", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "2.0", "210.1", "0.0", "0.0");
+        validateCharge(flat, loanCharges, "150.0", "150.0", "0.0", "0.0");
+        validateNumberForEqual("570.2", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        this.loanTransactionHelper.updateLoan(loanID,
+                updateLoanJson(clientID, loanProductID, copyChargesForUpdate(loanCharges, flat, "1"), null));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateCharge(amountPercentage, loanCharges, "1.0", "100.0", "0.0", "0.0");
+        validateCharge(interestPercentage, loanCharges, "1.0", "5.05", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentage, loanCharges, "1.0", "105.05", "0.0", "0.0");
+        validateNumberForEqual("210.1", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        charges.clear();
+        addCharges(charges, flat, "100", "29 September 2011");
+        this.loanTransactionHelper.updateLoan(loanID, updateLoanJson(clientID, loanProductID, charges, null));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateCharge(flat, loanCharges, "100.0", "100.0", "0.0", "0.0");
+        validateNumberForEqual("100.0", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        this.loanTransactionHelper.deleteChargesForLoan(loanID, (Integer) getloanCharge(flat, loanCharges).get("id"));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        Assert.assertEquals(0, loanCharges.size());
+        validateNumberForEqual("0", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatAccTransfer), "29 September 2011", "100"));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateCharge(flatAccTransfer, loanCharges, "100.0", "100.0", "0.0", "0.0");
+        validateNumberForEqual("100.0", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // DISBURSE
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(amountPercentage), "29 September 2011", "1"));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateCharge(amountPercentage, loanCharges, "1.0", "100.0", "0.0", "0.0");
+        validateCharge(flatAccTransfer, loanCharges, "100.0", "100.0", "0.0", "0.0");
+        validateNumberForEqual("200.0", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, (Integer) getloanCharge(amountPercentage, loanCharges).get("id"), "");
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateCharge(amountPercentage, loanCharges, "1.0", "0.0", "0.0", "100.0");
+        validateCharge(flatAccTransfer, loanCharges, "100.0", "100.0", "0.0", "0.0");
+        validateNumberForEqual("200.0", String.valueOf(firstInstallment.get("feeChargesDue")));
+        validateNumberForEqual("100.0", String.valueOf(firstInstallment.get("feeChargesOutstanding")));
+        validateNumberForEqual("100.0", String.valueOf(firstInstallment.get("feeChargesWaived")));
+
+        this.loanTransactionHelper.payChargesForLoan(loanID, (Integer) getloanCharge(flatAccTransfer, loanCharges).get("id"),
+                LoanTransactionHelper.getPayChargeJSON(SavingsAccountHelper.TRANSACTION_DATE, null));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateCharge(amountPercentage, loanCharges, "1.0", "0.0", "0.0", "100.0");
+        validateCharge(flatAccTransfer, loanCharges, "100.0", "0.0", "100.0", "0.0");
+        validateNumberForEqual("200.0", String.valueOf(firstInstallment.get("feeChargesDue")));
+        validateNumberForEqual("100.0", String.valueOf(firstInstallment.get("feeChargesWaived")));
+        validateNumberForEqual("100.0", String.valueOf(firstInstallment.get("feeChargesPaid")));
+        validateNumberForEqual("0.0", String.valueOf(firstInstallment.get("feeChargesOutstanding")));
+    }
+
+    @Test
+    public void testLoanCharges_INSTALMENT_FEE() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct(false, NONE);
+
+        List<HashMap> charges = new ArrayList<>();
+        Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+        Integer flatAccTransfer = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentWithAccountTransferJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+
+        Integer amountPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", false));
+        addCharges(charges, amountPercentage, "1", "29 September 2011");
+        Integer amountPlusInterestPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1", false));
+        addCharges(charges, amountPlusInterestPercentage, "1", "29 September 2011");
+        Integer interestPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST, "1", false));
+        addCharges(charges, interestPercentage, "1", "29 September 2011");
+
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+
+        Float totalPerOfAmout = 0F;
+        Float totalPerOfAmoutPlusInt = 0F;
+        Float totalPerOfint = 0F;
+        for (HashMap installment : loanSchedule) {
+            Float principalDue = (Float) installment.get("principalDue");
+            Float interestDue = (Float) installment.get("interestDue");
+            Float principalFee = principalDue / 100;
+            Float interestFee = interestDue / 100;
+            Float totalInstallmentFee = (principalFee * 2) + (interestFee * 2);
+            validateNumberForEqualExcludePrecission(String.valueOf(totalInstallmentFee), String.valueOf(installment.get("feeChargesDue")));
+            totalPerOfAmout = totalPerOfAmout + principalFee;
+            totalPerOfAmoutPlusInt = totalPerOfAmoutPlusInt + principalFee + interestFee;
+            totalPerOfint = totalPerOfint + interestFee;
+        }
+
+        validateChargeExcludePrecission(amountPercentage, loanCharges, "1.0", String.valueOf(totalPerOfAmout), "0.0", "0.0");
+        validateChargeExcludePrecission(interestPercentage, loanCharges, "1.0", String.valueOf(totalPerOfint), "0.0", "0.0");
+        validateChargeExcludePrecission(amountPlusInterestPercentage, loanCharges, "1.0", String.valueOf(totalPerOfAmoutPlusInt), "0.0",
+                "0.0");
+
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getInstallmentChargesForLoanAsJSON(String.valueOf(flat), "50"));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        totalPerOfAmout = 0F;
+        totalPerOfAmoutPlusInt = 0F;
+        totalPerOfint = 0F;
+        for (HashMap installment : loanSchedule) {
+            Float principalDue = (Float) installment.get("principalDue");
+            Float interestDue = (Float) installment.get("interestDue");
+            Float principalFee = principalDue / 100;
+            Float interestFee = interestDue / 100;
+            Float totalInstallmentFee = (principalFee * 2) + (interestFee * 2) + 50;
+            validateNumberForEqualExcludePrecission(String.valueOf(totalInstallmentFee), String.valueOf(installment.get("feeChargesDue")));
+            totalPerOfAmout = totalPerOfAmout + principalFee;
+            totalPerOfAmoutPlusInt = totalPerOfAmoutPlusInt + principalFee + interestFee;
+            totalPerOfint = totalPerOfint + interestFee;
+        }
+
+        validateChargeExcludePrecission(amountPercentage, loanCharges, "1.0", String.valueOf(totalPerOfAmout), "0.0", "0.0");
+        validateChargeExcludePrecission(interestPercentage, loanCharges, "1.0", String.valueOf(totalPerOfint), "0.0", "0.0");
+        validateChargeExcludePrecission(amountPlusInterestPercentage, loanCharges, "1.0", String.valueOf(totalPerOfAmoutPlusInt), "0.0",
+                "0.0");
+        validateChargeExcludePrecission(flat, loanCharges, "50.0", "200", "0.0", "0.0");
+
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(amountPercentage, loanCharges).get("id"),
+                LoanTransactionHelper.getUpdateChargesForLoanAsJSON("2"));
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(interestPercentage, loanCharges).get("id"),
+                LoanTransactionHelper.getUpdateChargesForLoanAsJSON("2"));
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(amountPlusInterestPercentage, loanCharges)
+                .get("id"), LoanTransactionHelper.getUpdateChargesForLoanAsJSON("2"));
+        this.loanTransactionHelper.updateChargesForLoan(loanID, (Integer) getloanCharge(flat, loanCharges).get("id"),
+                LoanTransactionHelper.getUpdateChargesForLoanAsJSON("100"));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        totalPerOfAmout = 0F;
+        totalPerOfAmoutPlusInt = 0F;
+        totalPerOfint = 0F;
+        for (HashMap installment : loanSchedule) {
+            Float principalDue = (Float) installment.get("principalDue");
+            Float interestDue = (Float) installment.get("interestDue");
+            Float principalFee = principalDue * 2 / 100;
+            Float interestFee = interestDue * 2 / 100;
+            Float totalInstallmentFee = (principalFee * 2) + (interestFee * 2) + 100;
+            validateNumberForEqualExcludePrecission(String.valueOf(totalInstallmentFee), String.valueOf(installment.get("feeChargesDue")));
+            totalPerOfAmout = totalPerOfAmout + principalFee;
+            totalPerOfAmoutPlusInt = totalPerOfAmoutPlusInt + principalFee + interestFee;
+            totalPerOfint = totalPerOfint + interestFee;
+        }
+
+        validateChargeExcludePrecission(amountPercentage, loanCharges, "2.0", String.valueOf(totalPerOfAmout), "0.0", "0.0");
+        validateChargeExcludePrecission(interestPercentage, loanCharges, "2.0", String.valueOf(totalPerOfint), "0.0", "0.0");
+        validateChargeExcludePrecission(amountPlusInterestPercentage, loanCharges, "2.0", String.valueOf(totalPerOfAmoutPlusInt), "0.0",
+                "0.0");
+        validateChargeExcludePrecission(flat, loanCharges, "100.0", "400", "0.0", "0.0");
+
+        final Integer savingsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID,
+                MINIMUM_OPENING_BALANCE);
+        this.loanTransactionHelper.updateLoan(loanID,
+                updateLoanJson(clientID, loanProductID, copyChargesForUpdate(loanCharges, null, null), String.valueOf(savingsId)));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        totalPerOfAmout = 0F;
+        totalPerOfAmoutPlusInt = 0F;
+        totalPerOfint = 0F;
+        for (HashMap installment : loanSchedule) {
+            Float principalDue = (Float) installment.get("principalDue");
+            Float interestDue = (Float) installment.get("interestDue");
+            Float principalFee = principalDue * 2 / 100;
+            Float interestFee = interestDue * 2 / 100;
+            Float totalInstallmentFee = (principalFee * 2) + (interestFee * 2) + 100;
+            validateNumberForEqualExcludePrecission(String.valueOf(totalInstallmentFee), String.valueOf(installment.get("feeChargesDue")));
+            totalPerOfAmout = totalPerOfAmout + principalFee;
+            totalPerOfAmoutPlusInt = totalPerOfAmoutPlusInt + principalFee + interestFee;
+            totalPerOfint = totalPerOfint + interestFee;
+        }
+
+        validateChargeExcludePrecission(amountPercentage, loanCharges, "2.0", String.valueOf(totalPerOfAmout), "0.0", "0.0");
+        validateChargeExcludePrecission(interestPercentage, loanCharges, "2.0", String.valueOf(totalPerOfint), "0.0", "0.0");
+        validateChargeExcludePrecission(amountPlusInterestPercentage, loanCharges, "2.0", String.valueOf(totalPerOfAmoutPlusInt), "0.0",
+                "0.0");
+        validateChargeExcludePrecission(flat, loanCharges, "100.0", "400", "0.0", "0.0");
+
+        this.loanTransactionHelper.updateLoan(loanID,
+                updateLoanJson(clientID, loanProductID, copyChargesForUpdate(loanCharges, flat, "1"), null));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        totalPerOfAmout = 0F;
+        totalPerOfAmoutPlusInt = 0F;
+        totalPerOfint = 0F;
+        for (HashMap installment : loanSchedule) {
+            Float principalDue = (Float) installment.get("principalDue");
+            Float interestDue = (Float) installment.get("interestDue");
+            Float principalFee = principalDue / 100;
+            Float interestFee = interestDue / 100;
+            Float totalInstallmentFee = (principalFee * 2) + (interestFee * 2);
+            validateNumberForEqualExcludePrecission(String.valueOf(totalInstallmentFee), String.valueOf(installment.get("feeChargesDue")));
+            totalPerOfAmout = totalPerOfAmout + principalFee;
+            totalPerOfAmoutPlusInt = totalPerOfAmoutPlusInt + principalFee + interestFee;
+            totalPerOfint = totalPerOfint + interestFee;
+        }
+
+        validateChargeExcludePrecission(amountPercentage, loanCharges, "1.0", String.valueOf(totalPerOfAmout), "0.0", "0.0");
+        validateChargeExcludePrecission(interestPercentage, loanCharges, "1.0", String.valueOf(totalPerOfint), "0.0", "0.0");
+        validateChargeExcludePrecission(amountPlusInterestPercentage, loanCharges, "1.0", String.valueOf(totalPerOfAmoutPlusInt), "0.0",
+                "0.0");
+
+        charges.clear();
+        addCharges(charges, flat, "50", "29 September 2011");
+        this.loanTransactionHelper.updateLoan(loanID, updateLoanJson(clientID, loanProductID, charges, null));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        for (HashMap installment : loanSchedule) {
+            validateNumberForEqualExcludePrecission("50", String.valueOf(installment.get("feeChargesDue")));
+        }
+        validateChargeExcludePrecission(flat, loanCharges, "50.0", "200", "0.0", "0.0");
+
+        this.loanTransactionHelper.deleteChargesForLoan(loanID, (Integer) getloanCharge(flat, loanCharges).get("id"));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        for (HashMap installment : loanSchedule) {
+            validateNumberForEqualExcludePrecission("0", String.valueOf(installment.get("feeChargesDue")));
+        }
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getInstallmentChargesForLoanAsJSON(String.valueOf(flatAccTransfer), "100"));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        for (HashMap installment : loanSchedule) {
+            validateNumberForEqualExcludePrecission("100", String.valueOf(installment.get("feeChargesDue")));
+        }
+        validateChargeExcludePrecission(flatAccTransfer, loanCharges, "100.0", "400", "0.0", "0.0");
+
+        // DISBURSE
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getInstallmentChargesForLoanAsJSON(String.valueOf(flat), "50"));
+
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        for (HashMap installment : loanSchedule) {
+            validateNumberForEqualExcludePrecission("150", String.valueOf(installment.get("feeChargesDue")));
+        }
+        validateChargeExcludePrecission(flatAccTransfer, loanCharges, "100.0", "400", "0.0", "0.0");
+        validateChargeExcludePrecission(flat, loanCharges, "50.0", "200", "0.0", "0.0");
+
+        Integer waivePeriodnum = 1;
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, (Integer) getloanCharge(flat, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(waivePeriodnum)));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        for (HashMap installment : loanSchedule) {
+            validateNumberForEqualExcludePrecission("150", String.valueOf(installment.get("feeChargesDue")));
+            if (waivePeriodnum == installment.get("period")) {
+                validateNumberForEqualExcludePrecission("100.0", String.valueOf(installment.get("feeChargesOutstanding")));
+                validateNumberForEqualExcludePrecission("50.0", String.valueOf(installment.get("feeChargesWaived")));
+            } else {
+                validateNumberForEqualExcludePrecission("150.0", String.valueOf(installment.get("feeChargesOutstanding")));
+                validateNumberForEqualExcludePrecission("0.0", String.valueOf(installment.get("feeChargesWaived")));
+
+            }
+        }
+        validateChargeExcludePrecission(flatAccTransfer, loanCharges, "100.0", "400", "0.0", "0.0");
+        validateChargeExcludePrecission(flat, loanCharges, "50.0", "150", "0.0", "50.0");
+
+        Integer payPeriodnum = 2;
+        this.loanTransactionHelper.payChargesForLoan(loanID, (Integer) getloanCharge(flatAccTransfer, loanCharges).get("id"),
+                LoanTransactionHelper.getPayChargeJSON(SavingsAccountHelper.TRANSACTION_DATE, String.valueOf(payPeriodnum)));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        for (HashMap installment : loanSchedule) {
+            validateNumberForEqualExcludePrecission("150", String.valueOf(installment.get("feeChargesDue")));
+            if (payPeriodnum == installment.get("period")) {
+                validateNumberForEqualExcludePrecission("50.0", String.valueOf(installment.get("feeChargesOutstanding")));
+                validateNumberForEqualExcludePrecission("100.0", String.valueOf(installment.get("feeChargesPaid")));
+            } else if (waivePeriodnum == installment.get("period")) {
+                validateNumberForEqualExcludePrecission("100.0", String.valueOf(installment.get("feeChargesOutstanding")));
+                validateNumberForEqualExcludePrecission("50.0", String.valueOf(installment.get("feeChargesWaived")));
+            } else {
+                validateNumberForEqualExcludePrecission("150.0", String.valueOf(installment.get("feeChargesOutstanding")));
+                validateNumberForEqualExcludePrecission("0.0", String.valueOf(installment.get("feeChargesPaid")));
+
+            }
+        }
+        validateChargeExcludePrecission(flatAccTransfer, loanCharges, "100.0", "300", "100.0", "0.0");
+        validateChargeExcludePrecission(flat, loanCharges, "50.0", "150", "0.0", "50.0");
+
+    }
+
+    @Test
+    public void testLoanCharges_DISBURSEMENT_TO_SAVINGS() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        SavingsAccountHelper savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct(false, NONE);
+
+        final Integer savingsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID,
+                MINIMUM_OPENING_BALANCE);
+
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, null, savingsId.toString(), "12,000.00");
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        HashMap summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        Float balance = new Float(MINIMUM_OPENING_BALANCE);
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+        // DISBURSE
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoanToSavings(SavingsAccountHelper.TRANSACTION_DATE, loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        balance = new Float(MINIMUM_OPENING_BALANCE) + new Float("12000");
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+        loanStatusHashMap = this.loanTransactionHelper.undoDisbursal(loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        balance = new Float(MINIMUM_OPENING_BALANCE);
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+    }
+
+    @Test
+    public void testLoanCharges_DISBURSEMENT_WITH_TRANCHES() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct(true, NONE);
+
+        List<HashMap> tranches = new ArrayList<>();
+        tranches.add(createTrancheDetail("1 March 2014", "25000"));
+        tranches.add(createTrancheDetail("23 April 2014", "20000"));
+
+        final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, null, null, "45,000.00", tranches);
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("1 March 2014", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        // DISBURSE first Tranche
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("1 March 2014", loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // DISBURSE Second Tranche
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("23 April 2014", loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.undoDisbursal(loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+    }
+
+    @Test
+    public void testLoanCharges_DISBURSEMENT_TO_SAVINGS_WITH_TRANCHES() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        SavingsAccountHelper savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct(true, NONE);
+
+        final Integer savingsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID,
+                MINIMUM_OPENING_BALANCE);
+
+        List<HashMap> tranches = new ArrayList<>();
+        tranches.add(createTrancheDetail("1 March 2014", "25000"));
+        tranches.add(createTrancheDetail("23 April 2014", "20000"));
+
+        final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, null, savingsId.toString(), "45,000.00",
+                tranches);
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("1 March 2014", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        HashMap summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        Float balance = new Float(MINIMUM_OPENING_BALANCE);
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+        // DISBURSE first Tranche
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoanToSavings("1 March 2014", loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        balance = new Float(MINIMUM_OPENING_BALANCE) + new Float("25000");
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+        // DISBURSE Second Tranche
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoanToSavings("23 April 2014", loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        balance = new Float(MINIMUM_OPENING_BALANCE) + new Float("25000") + new Float("20000");
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+        loanStatusHashMap = this.loanTransactionHelper.undoDisbursal(loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        balance = new Float(MINIMUM_OPENING_BALANCE);
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+    }
+
+    private void validateCharge(Integer amountPercentage, final List<HashMap> loanCharges, final String amount, final String outstanding,
+            String amountPaid, String amountWaived) {
+        HashMap chargeDetail = getloanCharge(amountPercentage, loanCharges);
+        Assert.assertTrue(new Float(amount).compareTo(new Float(String.valueOf(chargeDetail.get("amountOrPercentage")))) == 0);
+        Assert.assertTrue(new Float(outstanding).compareTo(new Float(String.valueOf(chargeDetail.get("amountOutstanding")))) == 0);
+        Assert.assertTrue(new Float(amountPaid).compareTo(new Float(String.valueOf(chargeDetail.get("amountPaid")))) == 0);
+        Assert.assertTrue(new Float(amountWaived).compareTo(new Float(String.valueOf(chargeDetail.get("amountWaived")))) == 0);
+    }
+
+    private void validateChargeExcludePrecission(Integer amountPercentage, final List<HashMap> loanCharges, final String amount,
+            final String outstanding, String amountPaid, String amountWaived) {
+        DecimalFormat twoDForm = new DecimalFormat("#");
+        HashMap chargeDetail = getloanCharge(amountPercentage, loanCharges);
+        Assert.assertTrue(new Float(twoDForm.format(new Float(amount))).compareTo(new Float(twoDForm.format(new Float(String
+                .valueOf(chargeDetail.get("amountOrPercentage")))))) == 0);
+        Assert.assertTrue(new Float(twoDForm.format(new Float(outstanding))).compareTo(new Float(twoDForm.format(new Float(String
+                .valueOf(chargeDetail.get("amountOutstanding")))))) == 0);
+        Assert.assertTrue(new Float(twoDForm.format(new Float(amountPaid))).compareTo(new Float(twoDForm.format(new Float(String
+                .valueOf(chargeDetail.get("amountPaid")))))) == 0);
+        Assert.assertTrue(new Float(twoDForm.format(new Float(amountWaived))).compareTo(new Float(twoDForm.format(new Float(String
+                .valueOf(chargeDetail.get("amountWaived")))))) == 0);
+    }
+
+    public void validateNumberForEqual(String val, String val2) {
+        Assert.assertTrue(new Float(val).compareTo(new Float(val2)) == 0);
+    }
+
+    public void validateNumberForEqualWithMsg(String msg, String val, String val2) {
+        Assert.assertTrue(msg + "expected " + val + " but was " + val2, new Float(val).compareTo(new Float(val2)) == 0);
+    }
+
+    public void validateNumberForEqualExcludePrecission(String val, String val2) {
+        DecimalFormat twoDForm = new DecimalFormat("#");
+        Assert.assertTrue(new Float(twoDForm.format(new Float(val))).compareTo(new Float(twoDForm.format(new Float(val2)))) == 0);
+    }
+
+    private Integer createLoanProduct(final boolean multiDisburseLoan, final String accountingRule, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        LoanProductTestBuilder builder = new LoanProductTestBuilder() //
+                .withPrincipal("12,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withTranches(multiDisburseLoan) //
+                .withAccounting(accountingRule, accounts);
+        if (multiDisburseLoan) {
+            builder = builder.withInterestCalculationPeriodTypeAsRepaymentPeriod(true);
+        }
+        final String loanProductJSON = builder.build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer createLoanProduct(final String inMultiplesOf, final String digitsAfterDecimal, final String repaymentStrategy) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("10000000.00") //
+                .withNumberOfRepayments("24") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("2") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withRepaymentStrategy(repaymentStrategy) //
+                .withAmortizationTypeAsEqualPrincipalPayment() //
+                .withInterestTypeAsDecliningBalance() //
+                .currencyDetails(digitsAfterDecimal, inMultiplesOf).build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer createLoanProduct(final String inMultiplesOf, final String digitsAfterDecimal, final String repaymentStrategy,
+            final String accountingRule, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("10000000.00") //
+                .withNumberOfRepayments("24") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("2") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withRepaymentStrategy(repaymentStrategy) //
+                .withAmortizationTypeAsEqualPrincipalPayment() //
+                .withInterestTypeAsDecliningBalance() //
+                .currencyDetails(digitsAfterDecimal, inMultiplesOf).withAccounting(accountingRule, accounts).build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String graceOnPrincipalPayment) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal("10000000.00") //
+                .withLoanTermFrequency("24") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("24") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualPrincipalPayments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withPrincipalGrace(graceOnPrincipalPayment).withExpectedDisbursementDate("2 June 2014") //
+                .withSubmittedOnDate("2 June 2014") //
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
+            final String savingsId, String principal) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2011") //
+                .withSubmittedOnDate("20 September 2011") //
+                .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
+            final String savingsId, String principal, List<HashMap> tranches) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("1 March 2014") //
+                .withTranches(tranches) //
+                .withSubmittedOnDate("1 March 2014") //
+
+                .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private String updateLoanJson(final Integer clientID, final Integer loanProductID, List<HashMap> charges, String savingsId) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal("10,000.00") //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2011") //
+                .withSubmittedOnDate("20 September 2011") //
+                .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+        return loanApplicationJSON;
+    }
+
+    private Integer applyForLoanApplicationWithPaymentStrategy(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
+            final String savingsId, String principal, final String repaymentStrategy) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2011") //
+                .withSubmittedOnDate("20 September 2011") //
+                .withwithRepaymentStrategy(repaymentStrategy) //
+                .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private Integer applyForLoanApplicationWithPaymentStrategyAndPastMonth(final Integer clientID, final Integer loanProductID,
+            List<HashMap> charges, final String savingsId, String principal, final String repaymentStrategy, final int month) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+
+        Calendar fourMonthsfromNowCalendar = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, month);
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+        String fourMonthsfromNow = dateFormat.format(fourMonthsfromNowCalendar.getTime());
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("6") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("6") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsFlatBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate(fourMonthsfromNow) //
+                .withSubmittedOnDate(fourMonthsfromNow) //
+                .withwithRepaymentStrategy(repaymentStrategy) //
+                .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private void verifyLoanRepaymentSchedule(final ArrayList<HashMap> loanSchedule) {
+        System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------");
+
+        assertEquals("Checking for Due Date for 1st Month", new ArrayList<>(Arrays.asList(2011, 10, 20)), loanSchedule.get(1)
+                .get("dueDate"));
+        assertEquals("Checking for Principal Due for 1st Month", new Float("2911.49"), loanSchedule.get(1).get("principalOriginalDue"));
+        assertEquals("Checking for Interest Due for 1st Month", new Float("240.00"), loanSchedule.get(1).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 2nd Month", new ArrayList<>(Arrays.asList(2011, 11, 20)), loanSchedule.get(2)
+                .get("dueDate"));
+        assertEquals("Checking for Principal Due for 2nd Month", new Float("2969.72"), loanSchedule.get(2).get("principalDue"));
+        assertEquals("Checking for Interest Due for 2nd Month", new Float("181.77"), loanSchedule.get(2).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 3rd Month", new ArrayList<>(Arrays.asList(2011, 12, 20)), loanSchedule.get(3)
+                .get("dueDate"));
+        assertEquals("Checking for Principal Due for 3rd Month", new Float("3029.11"), loanSchedule.get(3).get("principalDue"));
+        assertEquals("Checking for Interest Due for 3rd Month", new Float("122.38"), loanSchedule.get(3).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 4th Month", new ArrayList<>(Arrays.asList(2012, 1, 20)), loanSchedule.get(4).get("dueDate"));
+        assertEquals("Checking for Principal Due for 4th Month", new Float("3089.68"), loanSchedule.get(4).get("principalDue"));
+        assertEquals("Checking for Interest Due for 4th Month", new Float("61.79"), loanSchedule.get(4).get("interestOriginalDue"));
+    }
+
+    private void verifyLoanRepaymentScheduleForEqualPrincipal(final ArrayList<HashMap> loanSchedule) {
+        System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------");
+
+        assertEquals("Checking for Due Date for 1st Month", new ArrayList<>(Arrays.asList(2014, 7, 2)), loanSchedule.get(1).get("dueDate"));
+        assertEquals("Checking for Principal Due for 1st Month", new Float("416700"), loanSchedule.get(1).get("principalOriginalDue"));
+        assertEquals("Checking for Interest Due for 1st Month", new Float("200000"), loanSchedule.get(1).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 2nd Month", new ArrayList<>(Arrays.asList(2014, 8, 2)), loanSchedule.get(2).get("dueDate"));
+        assertEquals("Checking for Principal Due for 2nd Month", new Float("416700"), loanSchedule.get(2).get("principalDue"));
+        assertEquals("Checking for Interest Due for 2nd Month", new Float("191700"), loanSchedule.get(2).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 3rd Month", new ArrayList<>(Arrays.asList(2014, 9, 2)), loanSchedule.get(3).get("dueDate"));
+        assertEquals("Checking for Principal Due for 3rd Month", new Float("416700"), loanSchedule.get(3).get("principalDue"));
+        assertEquals("Checking for Interest Due for 3rd Month", new Float("183300"), loanSchedule.get(3).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 4th Month", new ArrayList<>(Arrays.asList(2014, 10, 2)), loanSchedule.get(4).get("dueDate"));
+        assertEquals("Checking for Principal Due for 4th Month", new Float("416700"), loanSchedule.get(4).get("principalDue"));
+        assertEquals("Checking for Interest Due for 4th Month", new Float("175000"), loanSchedule.get(4).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 5th Month", new ArrayList<>(Arrays.asList(2014, 11, 2)), loanSchedule.get(5).get("dueDate"));
+        assertEquals("Checking for Principal Due for 5th Month", new Float("416700"), loanSchedule.get(5).get("principalDue"));
+        assertEquals("Checking for Interest Due for 5th Month", new Float("166700"), loanSchedule.get(5).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 6th Month", new ArrayList<>(Arrays.asList(2014, 12, 2)), loanSchedule.get(6).get("dueDate"));
+        assertEquals("Checking for Principal Due for 6th Month", new Float("416700"), loanSchedule.get(6).get("principalDue"));
+        assertEquals("Checking for Interest Due for 6th Month", new Float("158300"), loanSchedule.get(6).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 10th Month", new ArrayList<>(Arrays.asList(2015, 4, 2)), loanSchedule.get(10)
+                .get("dueDate"));
+        assertEquals("Checking for Principal Due for 10th Month", new Float("416700"), loanSchedule.get(10).get("principalDue"));
+        assertEquals("Checking for Interest Due for 10th Month", new Float("125000"), loanSchedule.get(10).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 20th Month", new ArrayList<>(Arrays.asList(2016, 2, 2)), loanSchedule.get(20)
+                .get("dueDate"));
+        assertEquals("Checking for Principal Due for 20th Month", new Float("416700"), loanSchedule.get(20).get("principalDue"));
+        assertEquals("Checking for Interest Due for 20th Month", new Float("41700"), loanSchedule.get(20).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 24th Month", new ArrayList<>(Arrays.asList(2016, 6, 2)), loanSchedule.get(24)
+                .get("dueDate"));
+        assertEquals("Checking for Principal Due for 24th Month", new Float("415900"), loanSchedule.get(24).get("principalDue"));
+        assertEquals("Checking for Interest Due for 24th Month", new Float("8300"), loanSchedule.get(24).get("interestOriginalDue"));
+
+    }
+
+    private void verifyLoanRepaymentScheduleForEqualPrincipalWithGrace(final ArrayList<HashMap> loanSchedule) {
+        System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------");
+
+        assertEquals("Checking for Due Date for 1st Month", new ArrayList<>(Arrays.asList(2014, 7, 2)), loanSchedule.get(1).get("dueDate"));
+        validateNumberForEqualWithMsg("Checking for Principal Due for 1st Month", "0.0",
+                String.valueOf(loanSchedule.get(1).get("principalOriginalDue")));
+        assertEquals("Checking for Interest Due for 1st Month", new Float("200000"), loanSchedule.get(1).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 2nd Month", new ArrayList<>(Arrays.asList(2014, 8, 2)), loanSchedule.get(2).get("dueDate"));
+        validateNumberForEqualWithMsg("Checking for Principal Due for 2nd Month", "0.0",
+                String.valueOf(loanSchedule.get(2).get("principalOriginalDue")));
+        assertEquals("Checking for Interest Due for 2nd Month", new Float("200000"), loanSchedule.get(2).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 3rd Month", new ArrayList<>(Arrays.asList(2014, 9, 2)), loanSchedule.get(3).get("dueDate"));
+        validateNumberForEqualWithMsg("Checking for Principal Due for 3rd Month", "0.0",
+                String.valueOf(loanSchedule.get(3).get("principalDue")));
+        assertEquals("Checking for Interest Due for 3rd Month", new Float("200000"), loanSchedule.get(3).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 4th Month", new ArrayList<>(Arrays.asList(2014, 10, 2)), loanSchedule.get(4).get("dueDate"));
+        validateNumberForEqualWithMsg("Checking for Principal Due for 4th Month", "0",
+                String.valueOf(loanSchedule.get(4).get("principalDue")));
+        assertEquals("Checking for Interest Due for 4th Month", new Float("200000"), loanSchedule.get(4).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 5th Month", new ArrayList<>(Arrays.asList(2014, 11, 2)), loanSchedule.get(5).get("dueDate"));
+        validateNumberForEqualWithMsg("Checking for Principal Due for 5th Month", "0",
+                String.valueOf(loanSchedule.get(5).get("principalDue")));
+        assertEquals("Checking for Interest Due for 5th Month", new Float("200000"), loanSchedule.get(5).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 6th Month", new ArrayList<>(Arrays.asList(2014, 12, 2)), loanSchedule.get(6).get("dueDate"));
+        assertEquals("Checking for Principal Due for 6th Month", new Float("526300"), loanSchedule.get(6).get("principalDue"));
+        assertEquals("Checking for Interest Due for 6th Month", new Float("200000"), loanSchedule.get(6).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 7th Month", new ArrayList<>(Arrays.asList(2015, 1, 2)), loanSchedule.get(7).get("dueDate"));
+        assertEquals("Checking for Principal Due for 7th Month", new Float("526300"), loanSchedule.get(7).get("principalDue"));
+        assertEquals("Checking for Interest Due for 7th Month", new Float("189500"), loanSchedule.get(7).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 10th Month", new ArrayList<>(Arrays.asList(2015, 4, 2)), loanSchedule.get(10)
+                .get("dueDate"));
+        assertEquals("Checking for Principal Due for 10th Month", new Float("526300"), loanSchedule.get(10).get("principalDue"));
+        assertEquals("Checking for Interest Due for 10th Month", new Float("157900"), loanSchedule.get(10).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 20th Month", new ArrayList<>(Arrays.asList(2016, 2, 2)), loanSchedule.get(20)
+                .get("dueDate"));
+        assertEquals("Checking for Principal Due for 20th Month", new Float("526300"), loanSchedule.get(20).get("principalDue"));
+        assertEquals("Checking for Interest Due for 20th Month", new Float("52600"), loanSchedule.get(20).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 24th Month", new ArrayList<>(Arrays.asList(2016, 6, 2)), loanSchedule.get(24)
+                .get("dueDate"));
+        assertEquals("Checking for Principal Due for 24th Month", new Float("526600"), loanSchedule.get(24).get("principalDue"));
+        assertEquals("Checking for Interest Due for 24th Month", new Float("10500"), loanSchedule.get(24).get("interestOriginalDue"));
+
+    }
+
+    private void addCharges(List<HashMap> charges, Integer chargeId, String amount, String duedate) {
+        charges.add(charges(chargeId, amount, duedate));
+    }
+
+    private HashMap charges(Integer chargeId, String amount, String duedate) {
+        HashMap charge = new HashMap(2);
+        charge.put("chargeId", chargeId.toString());
+        charge.put("amount", amount);
+        if (duedate != null) {
+            charge.put("dueDate", duedate);
+        }
+        return charge;
+    }
+
+    private HashMap getloanCharge(Integer chargeId, List<HashMap> charges) {
+        HashMap charge = null;
+        for (HashMap loancharge : charges) {
+            if (loancharge.get("chargeId").equals(chargeId)) {
+                charge = loancharge;
+            }
+        }
+        return charge;
+    }
+
+    private List<HashMap> copyChargesForUpdate(List<HashMap> charges, Integer deleteWithChargeId, String amount) {
+        List<HashMap> loanCharges = new ArrayList<>();
+        for (HashMap charge : charges) {
+            if (!charge.get("chargeId").equals(deleteWithChargeId)) {
+                loanCharges.add(copyForUpdate(charge, amount));
+            }
+        }
+        return loanCharges;
+    }
+
+    private HashMap copyForUpdate(HashMap charge, String amount) {
+        HashMap map = new HashMap();
+        map.put("id", charge.get("id"));
+        if (amount == null) {
+            map.put("amount", charge.get("amountOrPercentage"));
+        } else {
+            map.put("amount", amount);
+        }
+        if (charge.get("dueDate") != null) {
+            map.put("dueDate", charge.get("dueDate"));
+        }
+        map.put("chargeId", charge.get("chargeId"));
+        return map;
+    }
+
+    private HashMap createTrancheDetail(final String date, final String amount) {
+        HashMap detail = new HashMap();
+        detail.put("expectedDisbursementDate", date);
+        detail.put("principal", amount);
+
+        return detail;
+    }
+
+    /***
+     * Test case for checking CashBasedAccounting functionality adding charges
+     * with calculation type flat
+     */
+    @Test
+    public void loanWithFlatCahargesAndCashBasedAccountingEnabled() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+        Integer flatDisbursement = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanDisbursementJSON());
+        addCharges(charges, flatDisbursement, "100", null);
+        Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", false));
+        addCharges(charges, flatSpecifiedDueDate, "100", "29 September 2011");
+        Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+        addCharges(charges, flatInstallmentFee, "50", null);
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct(false, CASH_BASED, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatDisbursement, loanCharges, "100", "100.00", "0.0", "0.0");
+        validateCharge(flatSpecifiedDueDate, loanCharges, "100", "100.00", "0.0", "0.0");
+        validateCharge(flatInstallmentFee, loanCharges, "50", "200.00", "0.0", "0.0");
+
+        // check for disbursement fee
+        HashMap disbursementDetail = loanSchedule.get(0);
+        validateNumberForEqual("100.00", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        // check for charge at specified date and installment fee
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("150.00", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // check for installment fee
+        HashMap secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("50.00", String.valueOf(secondInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.CREDIT));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatDisbursement, loanCharges, "100", "0.00", "100.0", "0.0");
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3301.49"), loanID);
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatDisbursement, loanCharges, "100", "0.00", "100.0", "0.0");
+        validateCharge(flatSpecifiedDueDate, loanCharges, "100", "0.00", "100.0", "0.0");
+        validateCharge(flatInstallmentFee, loanCharges, "50", "150.00", "50.0", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2911.49"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 October 2011",
+                new JournalEntry(Float.valueOf("150.00"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240.00"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatSpecifiedDueDate), "29 October 2011", "100"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("150.00", String.valueOf(secondInstallment.get("feeChargesDue")));
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, (Integer) getloanCharge(flatInstallmentFee, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatInstallmentFee, loanCharges, "50", "100.00", "50.0", "50.0");
+
+        System.out.println("----------Make repayment 2------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3251.49"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3251.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2969.72"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("181.77"),
+                        JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("--------------Waive interest---------------");
+        this.loanTransactionHelper.waiveInterest("20 December 2011", String.valueOf(61.79), loanID);
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("60.59", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        Integer flatPenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", true));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatPenaltySpecifiedDueDate), "29 September 2011", "100"));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatPenaltySpecifiedDueDate, loanCharges, "100", "0.00", "100.0", "0.0");
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("100", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2811.49"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 October 2011",
+                new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("150.00"),
+                        JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("----------Make repayment 3 advance------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.49"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3129.11"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("50.00"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("122.38"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatPenaltySpecifiedDueDate), "10 January 2012", "100"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("100", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3239.68", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Pay applied penalty ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("100"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(Float.valueOf("100"),
+                JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 January 2012",
+                new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("0", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3139.68", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Make repayment 4 ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3139.68"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("3139.68"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3089.68"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("50.00"), JournalEntry.TransactionType.CREDIT));
+    }
+
+    /***
+     * Test case for checking CashBasedAccounting functionality adding charges
+     * with calculation type percentage of amount
+     */
+    @Test
+    public void loanWithCahargesOfTypeAmountPercentageAndCashBasedAccountingEnabled() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+        Integer percentageDisbursementCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1"));
+        addCharges(charges, percentageDisbursementCharge, "1", null);
+
+        Integer percentageSpecifiedDueDateCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", false));
+        addCharges(charges, percentageSpecifiedDueDateCharge, "1", "29 September 2011");
+
+        Integer percentageInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", false));
+        addCharges(charges, percentageInstallmentFee, "1", "29 September 2011");
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct(false, CASH_BASED, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageDisbursementCharge, loanCharges, "1", "120.00", "0.0", "0.0");
+        validateCharge(percentageSpecifiedDueDateCharge, loanCharges, "1", "120.00", "0.0", "0.0");
+        validateCharge(percentageInstallmentFee, loanCharges, "1", "120.00", "0.0", "0.0");
+
+        // check for disbursement fee
+        HashMap disbursementDetail = loanSchedule.get(0);
+        validateNumberForEqual("120.00", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        // check for charge at specified date and installment fee
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("149.11", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // check for installment fee
+        HashMap secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("29.70", String.valueOf(secondInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.CREDIT));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageDisbursementCharge, loanCharges, "1", "0.0", "120.00", "0.0");
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3300.60"), loanID);
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageDisbursementCharge, loanCharges, "1", "0.00", "120.00", "0.0");
+        validateCharge(percentageSpecifiedDueDateCharge, loanCharges, "1", "0.00", "120.0", "0.0");
+        validateCharge(percentageInstallmentFee, loanCharges, "1", "90.89", "29.11", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2911.49"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 October 2011",
+                new JournalEntry(Float.valueOf("149.11"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240.00"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(percentageSpecifiedDueDateCharge), "29 October 2011", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("149.70", String.valueOf(secondInstallment.get("feeChargesDue")));
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, (Integer) getloanCharge(percentageInstallmentFee, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageInstallmentFee, loanCharges, "1", "61.19", "29.11", "29.70");
+
+        System.out.println("----------Make repayment 2------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3271.49"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3271.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2969.72"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("181.77"),
+                        JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("--------------Waive interest---------------");
+        this.loanTransactionHelper.waiveInterest("20 December 2011", String.valueOf(61.79), loanID);
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("60.59", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        Integer percentagePenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", true));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(percentagePenaltySpecifiedDueDate), "29 September 2011", "1"));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentagePenaltySpecifiedDueDate, loanCharges, "1", "0.00", "120.0", "0.0");
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("120", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2791.49"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 October 2011",
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("149.11"),
+                        JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("----------Make repayment 3 advance------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.78"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3301.78"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3149.11"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("30.29"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("122.38"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(percentagePenaltySpecifiedDueDate), "10 January 2012", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("120", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3240.58", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Pay applied penalty ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("120"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 January 2012",
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("0", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3120.58", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Make repayment 4 ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3120.58"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("3120.58"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3089.68"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("30.90"), JournalEntry.TransactionType.CREDIT));
+    }
+
+    /***
+     * Test case for checking CashBasedAccounting functionality adding charges
+     * with calculation type percentage of amount plus interest
+     */
+    @Test
+    public void loanWithCahargesOfTypeAmountPlusInterestPercentageAndCashBasedAccountingEnabled() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+        Integer amountPlusInterestPercentageDisbursementCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1"));
+        addCharges(charges, amountPlusInterestPercentageDisbursementCharge, "1", null);
+
+        Integer amountPlusInterestPercentageSpecifiedDueDateCharge = ChargesHelper
+                .createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateJSON(
+                        ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1", false));
+        addCharges(charges, amountPlusInterestPercentageSpecifiedDueDateCharge, "1", "29 September 2011");
+
+        Integer amountPlusInterestPercentageInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1", false));
+        addCharges(charges, amountPlusInterestPercentageInstallmentFee, "1", "29 September 2011");
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct(false, CASH_BASED, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageDisbursementCharge, loanCharges, "1", "126.06", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentageSpecifiedDueDateCharge, loanCharges, "1", "126.06", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "126.04", "0.0", "0.0");
+
+        // check for disbursement fee
+        HashMap disbursementDetail = loanSchedule.get(0);
+        validateNumberForEqual("126.06", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        // check for charge at specified date and installment fee
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("157.57", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // check for installment fee
+        HashMap secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("31.51", String.valueOf(secondInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("126.06"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("126.06"), JournalEntry.TransactionType.CREDIT));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageDisbursementCharge, loanCharges, "1", "0.0", "126.06", "0.0");
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3309.06"), loanID);
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageDisbursementCharge, loanCharges, "1", "0.00", "126.06", "0.0");
+        validateCharge(amountPlusInterestPercentageSpecifiedDueDateCharge, loanCharges, "1", "0.00", "126.06", "0.0");
+        validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "94.53", "31.51", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2911.49"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 October 2011",
+                new JournalEntry(Float.valueOf("157.57"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240.00"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentageSpecifiedDueDateCharge), "29 October 2011", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("157.57", String.valueOf(secondInstallment.get("feeChargesDue")));
+        this.loanTransactionHelper.waiveChargesForLoan(loanID,
+                (Integer) getloanCharge(amountPlusInterestPercentageInstallmentFee, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "63.02", "31.51", "31.51");
+
+        System.out.println("----------Make repayment 2------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3277.55"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3277.55"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2969.72"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("126.06"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("181.77"),
+                        JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("--------------Waive interest---------------");
+        this.loanTransactionHelper.waiveInterest("20 December 2011", String.valueOf(61.79), loanID);
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("60.59", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        Integer amountPlusInterestPercentagePenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", true));
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentagePenaltySpecifiedDueDate), "29 September 2011", "1"));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentagePenaltySpecifiedDueDate, loanCharges, "1", "0.0", "120.0", "0.0");
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("120", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2791.49"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 October 2011",
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("157.57"),
+                        JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("----------Make repayment 3 advance------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3303"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", new JournalEntry(Float.valueOf("3303"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3149.11"), JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("31.51"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("122.38"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentagePenaltySpecifiedDueDate), "10 January 2012", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("120", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3241.19", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Pay applied penalty ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("120"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 January 2012",
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("0", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3121.19", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Make repayment 4 ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3121.19"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("3121.19"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3089.68"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("31.51"), JournalEntry.TransactionType.CREDIT));
+    }
+
+    /***
+     * Test case for checking AccuralUpfrontAccounting functionality adding
+     * charges with calculation type flat
+     */
+    @Test
+    public void loanWithFlatCahargesAndUpfrontAccrualAccountingEnabled() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+        Integer flatDisbursement = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanDisbursementJSON());
+        addCharges(charges, flatDisbursement, "100", null);
+        Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", false));
+
+        Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+        addCharges(charges, flatInstallmentFee, "50", null);
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct(false, ACCRUAL_UPFRONT, assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatDisbursement, loanCharges, "100", "100.00", "0.0", "0.0");
+        validateCharge(flatInstallmentFee, loanCharges, "50", "200.00", "0.0", "0.0");
+
+        // check for disbursement fee
+        HashMap disbursementDetail = loanSchedule.get(0);
+        validateNumberForEqual("100.00", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        // check for charge at specified date and installment fee
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("50.00", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // check for installment fee
+        HashMap secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("50.00", String.valueOf(secondInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("605.94"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("200.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("605.94"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("100.00"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("200.00"), JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatSpecifiedDueDate), "29 September 2011", "100"));
+
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatDisbursement, loanCharges, "100", "0.00", "100.0", "0.0");
+        validateCharge(flatSpecifiedDueDate, loanCharges, "100", "100.00", "0.0", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "29 September 2011",
+                new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "29 September 2011",
+                new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3301.49"), loanID);
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatDisbursement, loanCharges, "100", "0.00", "100.0", "0.0");
+        validateCharge(flatSpecifiedDueDate, loanCharges, "100", "0.00", "100.0", "0.0");
+        validateCharge(flatInstallmentFee, loanCharges, "50", "150.00", "50.0", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("150.00"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatSpecifiedDueDate), "29 October 2011", "100"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("150.00", String.valueOf(secondInstallment.get("feeChargesDue")));
+        System.out.println("----------- Waive installment charge for 2nd installment ---------");
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, (Integer) getloanCharge(flatInstallmentFee, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatInstallmentFee, loanCharges, "50", "100.00", "50.0", "50.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", new JournalEntry(Float.valueOf("50.0"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("50.0"), JournalEntry.TransactionType.DEBIT));
+
+        System.out.println("----------Make repayment 2------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3251.49"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3251.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("--------------Waive interest---------------");
+        this.loanTransactionHelper.waiveInterest("20 December 2011", String.valueOf(61.79), loanID);
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("60.59", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 December 2011", new JournalEntry(Float.valueOf("61.79"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 December 2011",
+                new JournalEntry(Float.valueOf("61.79"), JournalEntry.TransactionType.DEBIT));
+
+        Integer flatPenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", true));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatPenaltySpecifiedDueDate), "29 September 2011", "100"));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatPenaltySpecifiedDueDate, loanCharges, "100", "0.00", "100.0", "0.0");
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("100", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        // checking the journal entry as applied penalty has been collected
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("150"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2811.49"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("----------Make repayment 3 advance------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.49"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50.00"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3129.11"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatPenaltySpecifiedDueDate), "10 January 2012", "100"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("100", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3239.68", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Pay applied penalty ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("100"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(Float.valueOf("100"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"), JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("0", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3139.68", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Make over payment for repayment 4 ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3220.60"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("3220.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50.00"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, "20 January 2012", new JournalEntry(80.92f,
+                JournalEntry.TransactionType.CREDIT));
+        loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status");
+        LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
+    }
+
+    /***
+     * Test case for checking AccuralUpfrontAccounting functionality adding
+     * charges with calculation type percentage of amount
+     */
+    @Test
+    public void loanWithCahargesAndUpfrontAccrualAccountingEnabled() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+        Integer percentageDisbursementCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1"));
+        addCharges(charges, percentageDisbursementCharge, "1", null);
+
+        Integer percentageSpecifiedDueDateCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", false));
+        addCharges(charges, percentageSpecifiedDueDateCharge, "1", "29 September 2011");
+
+        Integer percentageInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", false));
+        addCharges(charges, percentageInstallmentFee, "1", "29 September 2011");
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct(false, ACCRUAL_UPFRONT, assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageDisbursementCharge, loanCharges, "1", "120.00", "0.0", "0.0");
+        validateCharge(percentageSpecifiedDueDateCharge, loanCharges, "1", "120.00", "0.0", "0.0");
+        validateCharge(percentageInstallmentFee, loanCharges, "1", "120.00", "0.0", "0.0");
+
+        // check for disbursement fee
+        HashMap disbursementDetail = loanSchedule.get(0);
+        validateNumberForEqual("120.00", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        // check for charge at specified date and installment fee
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("149.11", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // check for installment fee
+        HashMap secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("29.70", String.valueOf(secondInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("605.94"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("605.94"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("120.00"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("120.00"),
+                        JournalEntry.TransactionType.CREDIT));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageDisbursementCharge, loanCharges, "1", "0.0", "120.00", "0.0");
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3300.60"), loanID);
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageDisbursementCharge, loanCharges, "1", "0.00", "120.00", "0.0");
+        validateCharge(percentageSpecifiedDueDateCharge, loanCharges, "1", "0.00", "120.0", "0.0");
+        validateCharge(percentageInstallmentFee, loanCharges, "1", "90.89", "29.11", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("149.11"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(percentageSpecifiedDueDateCharge), "29 October 2011", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("149.70", String.valueOf(secondInstallment.get("feeChargesDue")));
+        System.out.println("----------- Waive installment charge for 2nd installment ---------");
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, (Integer) getloanCharge(percentageInstallmentFee, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageInstallmentFee, loanCharges, "1", "61.19", "29.11", "29.70");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", new JournalEntry(Float.valueOf("29.7"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("29.7"), JournalEntry.TransactionType.DEBIT));
+
+        System.out.println("----------Make repayment 2------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3271.49"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3271.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("--------------Waive interest---------------");
+        this.loanTransactionHelper.waiveInterest("20 December 2011", String.valueOf(61.79), loanID);
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("60.59", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 December 2011", new JournalEntry(Float.valueOf("61.79"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 December 2011",
+                new JournalEntry(Float.valueOf("61.79"), JournalEntry.TransactionType.DEBIT));
+
+        Integer percentagePenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", true));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(percentagePenaltySpecifiedDueDate), "29 September 2011", "1"));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentagePenaltySpecifiedDueDate, loanCharges, "1", "0.00", "120.0", "0.0");
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("120", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        // checking the journal entry as applied penalty has been collected
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("149.11"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2791.49"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("----------Make repayment 3 advance------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.78"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3301.78"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("30.29"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3149.11"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(percentagePenaltySpecifiedDueDate), "10 January 2012", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("120", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3240.58", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Pay applied penalty ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("120"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("0", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3120.58", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Make over payment for repayment 4 ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3220.58"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("3220.58"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("30.90"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, "20 January 2012", new JournalEntry(100f,
+                JournalEntry.TransactionType.CREDIT));
+        loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status");
+        LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
+    }
+
+    /***
+     * Test case for checking AccuralUpfrontAccounting functionality adding
+     * charges with calculation type percentage of amount plus interest
+     */
+    @Test
+    public void loanWithCahargesOfTypeAmountPlusInterestPercentageAndUpfrontAccrualAccountingEnabled() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+        Integer amountPlusInterestPercentageDisbursementCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1"));
+        addCharges(charges, amountPlusInterestPercentageDisbursementCharge, "1", null);
+
+        Integer amountPlusInterestPercentageSpecifiedDueDateCharge = ChargesHelper
+                .createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateJSON(
+                        ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1", false));
+
+        Integer amountPlusInterestPercentageInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1", false));
+        addCharges(charges, amountPlusInterestPercentageInstallmentFee, "1", "29 September 2011");
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct(false, ACCRUAL_UPFRONT, assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageDisbursementCharge, loanCharges, "1", "126.06", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "126.04", "0.0", "0.0");
+
+        // check for disbursement fee
+        HashMap disbursementDetail = loanSchedule.get(0);
+        validateNumberForEqual("126.06", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        // check for charge at specified date and installment fee
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("31.51", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // check for installment fee
+        HashMap secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("31.51", String.valueOf(secondInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("605.94"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("126.06"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("126.04"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("605.94"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("126.06"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("126.04"), JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentageSpecifiedDueDateCharge), "29 September 2011", "1"));
+
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageDisbursementCharge, loanCharges, "1", "0.0", "126.06", "0.0");
+        validateCharge(amountPlusInterestPercentageSpecifiedDueDateCharge, loanCharges, "1", "126.06", "0.0", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "29 September 2011",
+                new JournalEntry(Float.valueOf("126.06"), JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "29 September 2011",
+                new JournalEntry(Float.valueOf("126.06"), JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3309.06"), loanID);
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageDisbursementCharge, loanCharges, "1", "0.00", "126.06", "0.0");
+        validateCharge(amountPlusInterestPercentageSpecifiedDueDateCharge, loanCharges, "1", "0.00", "126.06", "0.0");
+        validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "94.53", "31.51", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("157.57"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentageSpecifiedDueDateCharge), "29 October 2011", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("157.57", String.valueOf(secondInstallment.get("feeChargesDue")));
+        System.out.println("----------- Waive installment charge for 2nd installment ---------");
+        this.loanTransactionHelper.waiveChargesForLoan(loanID,
+                (Integer) getloanCharge(amountPlusInterestPercentageInstallmentFee, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "63.02", "31.51", "31.51");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", new JournalEntry(
+                Float.valueOf("31.51"), JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("31.51"), JournalEntry.TransactionType.DEBIT));
+
+        System.out.println("----------Make repayment 2------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3277.55"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3277.55"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("126.06"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("--------------Waive interest---------------");
+        this.loanTransactionHelper.waiveInterest("20 December 2011", String.valueOf(61.79), loanID);
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("60.59", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 December 2011", new JournalEntry(Float.valueOf("61.79"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 December 2011",
+                new JournalEntry(Float.valueOf("61.79"), JournalEntry.TransactionType.DEBIT));
+
+        Integer amountPlusInterestPercentagePenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", true));
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentagePenaltySpecifiedDueDate), "29 September 2011", "1"));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentagePenaltySpecifiedDueDate, loanCharges, "1", "0.0", "120.0", "0.0");
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("120", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        // checking the journal entry as applied penalty has been collected
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("157.57"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2791.49"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("----------Make repayment 3 advance------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3303"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", new JournalEntry(Float.valueOf("3303"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("31.51"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3149.11"),
+                        JournalEntry.TransactionType.CREDIT));
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentagePenaltySpecifiedDueDate), "10 January 2012", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("120", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3241.19", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Pay applied penalty ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("120"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("0", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3121.19", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Make over payment for repayment 4 ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3221.61"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("3221.61"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("31.51"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, "20 January 2012", new JournalEntry(100.42f,
+                JournalEntry.TransactionType.CREDIT));
+        loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status");
+        LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
+    }
+
+    /***
+     * Test case for checking AccuralPeriodicAccounting functionality adding
+     * charges with calculation type flat
+     */
+    @Test
+    public void loanWithFlatCahargesAndPeriodicAccrualAccountingEnabled() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+        Integer flatDisbursement = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanDisbursementJSON());
+        addCharges(charges, flatDisbursement, "100", null);
+        Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", false));
+        addCharges(charges, flatSpecifiedDueDate, "100", "29 September 2011");
+        Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+        addCharges(charges, flatInstallmentFee, "50", null);
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct(false, ACCRUAL_PERIODIC, assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatDisbursement, loanCharges, "100", "100.00", "0.0", "0.0");
+        validateCharge(flatSpecifiedDueDate, loanCharges, "100", "100.00", "0.0", "0.0");
+        validateCharge(flatInstallmentFee, loanCharges, "50", "200.00", "0.0", "0.0");
+
+        // check for disbursement fee
+        HashMap disbursementDetail = loanSchedule.get(0);
+        validateNumberForEqual("100.00", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        // check for charge at specified date and installment fee
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("150.00", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // check for installment fee
+        HashMap secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("50.00", String.valueOf(secondInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.CREDIT));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatDisbursement, loanCharges, "100", "0.00", "100.0", "0.0");
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3301.49"), loanID);
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatDisbursement, loanCharges, "100", "0.00", "100.0", "0.0");
+        validateCharge(flatSpecifiedDueDate, loanCharges, "100", "0.00", "100.0", "0.0");
+        validateCharge(flatInstallmentFee, loanCharges, "50", "150.00", "50.0", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("150.00"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatSpecifiedDueDate), "29 October 2011", "100"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("150.00", String.valueOf(secondInstallment.get("feeChargesDue")));
+        System.out.println("----------- Waive installment charge for 2nd installment ---------");
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, (Integer) getloanCharge(flatInstallmentFee, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatInstallmentFee, loanCharges, "50", "100.00", "50.0", "50.0");
+
+        /*
+         * this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
+         * "20 September 2011", new JournalEntry(Float.valueOf("50.0"),
+         * JournalEntry.TransactionType.CREDIT));
+         * this.journalEntryHelper.checkJournalEntryForExpenseAccount
+         * (expenseAccount, "20 September 2011", new
+         * JournalEntry(Float.valueOf("50.0"),
+         * JournalEntry.TransactionType.DEBIT));
+         */
+        final String jobName = "Add Accrual Transactions";
+        try {
+            this.schedulerJobHelper.executeJob(jobName);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        checkAccrualTransactions(loanSchedule, loanID);
+
+        System.out.println("----------Make repayment 2------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3251.49"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3251.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100.00"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("--------------Waive interest---------------");
+        this.loanTransactionHelper.waiveInterest("20 December 2011", String.valueOf(61.79), loanID);
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("60.59", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 December 2011", new JournalEntry(Float.valueOf("61.79"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 December 2011",
+                new JournalEntry(Float.valueOf("61.79"), JournalEntry.TransactionType.DEBIT));
+
+        Integer flatPenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", true));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatPenaltySpecifiedDueDate), "29 September 2011", "100"));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(flatPenaltySpecifiedDueDate, loanCharges, "100", "0.00", "100.0", "0.0");
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("100", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        // checking the journal entry as applied penalty has been collected
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("150"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2811.49"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("----------Make repayment 3 advance------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.49"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3129.11"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(flatPenaltySpecifiedDueDate), "10 January 2012", "100"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("100", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3239.68", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Pay applied penalty ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("100"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(Float.valueOf("100"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"), JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("0", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3139.68", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Make repayment 4 ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3139.68"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("3139.68"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50.00"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT));
+        loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status");
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+    }
+
+    /**
+     * Test case for checking AccuralPeriodicAccounting functionality adding
+     * charges with calculation type percentage of amount
+     */
+    @Test
+    public void loanWithCahargesOfTypeAmountPercentageAndPeriodicAccrualAccountingEnabled() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+        Integer percentageDisbursementCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1"));
+        addCharges(charges, percentageDisbursementCharge, "1", null);
+
+        Integer percentageSpecifiedDueDateCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", false));
+        addCharges(charges, percentageSpecifiedDueDateCharge, "1", "29 September 2011");
+
+        Integer percentageInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", false));
+        addCharges(charges, percentageInstallmentFee, "1", "29 September 2011");
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct(false, ACCRUAL_PERIODIC, assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageDisbursementCharge, loanCharges, "1", "120.00", "0.0", "0.0");
+        validateCharge(percentageSpecifiedDueDateCharge, loanCharges, "1", "120.00", "0.0", "0.0");
+        validateCharge(percentageInstallmentFee, loanCharges, "1", "120.00", "0.0", "0.0");
+
+        // check for disbursement fee
+        HashMap disbursementDetail = loanSchedule.get(0);
+        validateNumberForEqual("120.00", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        // check for charge at specified date and installment fee
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("149.11", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // check for installment fee
+        HashMap secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("29.70", String.valueOf(secondInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("120.00"), JournalEntry.TransactionType.CREDIT));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageDisbursementCharge, loanCharges, "1", "0.0", "120.00", "0.0");
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3300.60"), loanID);
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageDisbursementCharge, loanCharges, "1", "0.00", "120.00", "0.0");
+        validateCharge(percentageSpecifiedDueDateCharge, loanCharges, "1", "0.00", "120.0", "0.0");
+        validateCharge(percentageInstallmentFee, loanCharges, "1", "90.89", "29.11", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("149.11"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(percentageSpecifiedDueDateCharge), "29 October 2011", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("149.70", String.valueOf(secondInstallment.get("feeChargesDue")));
+        System.out.println("----------- Waive installment charge for 2nd installment ---------");
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, (Integer) getloanCharge(percentageInstallmentFee, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentageInstallmentFee, loanCharges, "1", "61.19", "29.11", "29.70");
+
+        /*
+         * this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
+         * "20 September 2011", new JournalEntry(Float.valueOf("29.7"),
+         * JournalEntry.TransactionType.CREDIT));
+         * this.journalEntryHelper.checkJournalEntryForExpenseAccount
+         * (expenseAccount, "20 September 2011", new
+         * JournalEntry(Float.valueOf("29.7"),
+         * JournalEntry.TransactionType.DEBIT));
+         */
+
+        final String jobName = "Add Accrual Transactions";
+        try {
+            this.schedulerJobHelper.executeJob(jobName);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        checkAccrualTransactions(loanSchedule, loanID);
+
+        System.out.println("----------Make repayment 2------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3271.49"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3271.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("--------------Waive interest---------------");
+        this.loanTransactionHelper.waiveInterest("20 December 2011", String.valueOf(61.79), loanID);
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("60.59", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 December 2011", new JournalEntry(Float.valueOf("61.79"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 December 2011",
+                new JournalEntry(Float.valueOf("61.79"), JournalEntry.TransactionType.DEBIT));
+
+        Integer percentagePenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", true));
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(percentagePenaltySpecifiedDueDate), "29 September 2011", "1"));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(percentagePenaltySpecifiedDueDate, loanCharges, "1", "0.00", "120.0", "0.0");
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("120", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        // checking the journal entry as applied penalty has been collected
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("149.11"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2791.49"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("----------Make repayment 3 advance------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.78"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3301.78"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("30.29"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3149.11"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                String.valueOf(percentagePenaltySpecifiedDueDate), "10 January 2012", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("120", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3240.58", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Pay applied penalty ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("120"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("0", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3120.58", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Make repayment 4 ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3120.58"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("3120.58"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("30.90"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT));
+        loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status");
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+    }
+
+    /***
+     * Test case for checking AccuralPeriodicAccounting functionality adding
+     * charges with calculation type percentage of amount and interest
+     */
+    @Test
+    public void loanWithCahargesOfTypeAmountPlusInterestPercentageAndPeriodicAccrualAccountingEnabled() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+        Integer amountPlusInterestPercentageDisbursementCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1"));
+        addCharges(charges, amountPlusInterestPercentageDisbursementCharge, "1", null);
+
+        Integer amountPlusInterestPercentageSpecifiedDueDateCharge = ChargesHelper
+                .createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateJSON(
+                        ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1", false));
+        addCharges(charges, amountPlusInterestPercentageSpecifiedDueDateCharge, "1", "29 September 2011");
+
+        Integer amountPlusInterestPercentageInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST, "1", false));
+        addCharges(charges, amountPlusInterestPercentageInstallmentFee, "1", "29 September 2011");
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct(false, ACCRUAL_PERIODIC, assetAccount, incomeAccount, expenseAccount,
+                overpaymentAccount);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+        List<HashMap> loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageDisbursementCharge, loanCharges, "1", "126.06", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentageSpecifiedDueDateCharge, loanCharges, "1", "126.06", "0.0", "0.0");
+        validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "126.04", "0.0", "0.0");
+
+        // check for disbursement fee
+        HashMap disbursementDetail = loanSchedule.get(0);
+        validateNumberForEqual("126.06", String.valueOf(disbursementDetail.get("feeChargesDue")));
+
+        // check for charge at specified date and installment fee
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("157.57", String.valueOf(firstInstallment.get("feeChargesDue")));
+
+        // check for installment fee
+        HashMap secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("31.51", String.valueOf(secondInstallment.get("feeChargesDue")));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("126.06"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "20 September 2011",
+                new JournalEntry(Float.valueOf("126.06"), JournalEntry.TransactionType.CREDIT));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageDisbursementCharge, loanCharges, "1", "0.0", "126.06", "0.0");
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3309.06"), loanID);
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageDisbursementCharge, loanCharges, "1", "0.00", "126.06", "0.0");
+        validateCharge(amountPlusInterestPercentageSpecifiedDueDateCharge, loanCharges, "1", "0.00", "126.06", "0.0");
+        validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "94.53", "31.51", "0.0");
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("157.57"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentageSpecifiedDueDateCharge), "29 October 2011", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("157.57", String.valueOf(secondInstallment.get("feeChargesDue")));
+        System.out.println("----------- Waive installment charge for 2nd installment ---------");
+        this.loanTransactionHelper.waiveChargesForLoan(loanID,
+                (Integer) getloanCharge(amountPlusInterestPercentageInstallmentFee, loanCharges).get("id"),
+                LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "63.02", "31.51", "31.51");
+
+        /*
+         * this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
+         * "20 September 2011", new JournalEntry( Float.valueOf("31.51"),
+         * JournalEntry.TransactionType.CREDIT));
+         * this.journalEntryHelper.checkJournalEntryForExpenseAccount
+         * (expenseAccount, "20 September 2011", new
+         * JournalEntry(Float.valueOf("31.51"),
+         * JournalEntry.TransactionType.DEBIT));
+         */
+
+        final String jobName = "Add Accrual Transactions";
+        try {
+            this.schedulerJobHelper.executeJob(jobName);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        checkAccrualTransactions(loanSchedule, loanID);
+
+        System.out.println("----------Make repayment 2------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3277.55"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011",
+                new JournalEntry(Float.valueOf("3277.55"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("126.06"),
+                        JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("--------------Waive interest---------------");
+        this.loanTransactionHelper.waiveInterest("20 December 2011", String.valueOf(61.79), loanID);
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("60.59", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 December 2011", new JournalEntry(Float.valueOf("61.79"),
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 December 2011",
+                new JournalEntry(Float.valueOf("61.79"), JournalEntry.TransactionType.DEBIT));
+
+        Integer amountPlusInterestPercentagePenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "1", true));
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentagePenaltySpecifiedDueDate), "29 September 2011", "1"));
+        loanCharges.clear();
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+        validateCharge(amountPlusInterestPercentagePenaltySpecifiedDueDate, loanCharges, "1", "0.0", "120.0", "0.0");
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("120", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        // checking the journal entry as applied penalty has been collected
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry(
+                Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("157.57"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2791.49"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        System.out.println("----------Make repayment 3 advance------------");
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3303"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", new JournalEntry(Float.valueOf("3303"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("31.51"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3149.11"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        this.loanTransactionHelper.addChargesForLoan(
+                loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                        String.valueOf(amountPlusInterestPercentagePenaltySpecifiedDueDate), "10 January 2012", "1"));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("120", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3241.19", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Pay applied penalty ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("120"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(Float.valueOf("120"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), JournalEntry.TransactionType.CREDIT));
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("0", String.valueOf(fourthInstallment.get("penaltyChargesOutstanding")));
+        validateNumberForEqual("3121.19", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+
+        System.out.println("----------Make repayment 4 ------------");
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3121.19"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry(
+                Float.valueOf("3121.19"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("31.51"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT));
+        loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status");
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+    }
+
+    @Test
+    public void testClientLoanScheduleWithCurrencyDetails() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct("100", "0", LoanProductTestBuilder.DEFAULT_STRATEGY);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, null);
+        final ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec,
+                loanID);
+        verifyLoanRepaymentScheduleForEqualPrincipal(loanSchedule);
+
+    }
+
+    @Test
+    public void testClientLoanScheduleWithCurrencyDetails_with_grace() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct("100", "0", LoanProductTestBuilder.DEFAULT_STRATEGY);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, "5");
+        final ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec,
+                loanID);
+        verifyLoanRepaymentScheduleForEqualPrincipalWithGrace(loanSchedule);
+
+    }
+
+    /***
+     * Test case to verify RBI payment strategy
+     */
+    @Test
+    public void testRBIPaymentStrategy() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        /***
+         * Create loan product with RBI strategy
+         */
+        final Integer loanProductID = createLoanProduct("100", "0", LoanProductTestBuilder.RBI_INDIA_STRATEGY);
+        Assert.assertNotNull(loanProductID);
+
+        /***
+         * Apply for loan application and verify loan status
+         */
+        final String savingsId = null;
+        final String principal = "12,000.00";
+        final Integer loanID = applyForLoanApplicationWithPaymentStrategy(clientID, loanProductID, null, savingsId, principal,
+                LoanApplicationTestBuilder.RBI_INDIA_STRATEGY);
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("3200", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+        /***
+         * Make payment for installment #1
+         */
+        this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("3200"), loanID);
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+        /***
+         * Verify 2nd and 3rd repayments dues before making excess payment for
+         * installment no 2
+         */
+        HashMap secondInstallment = loanSchedule.get(2);
+        HashMap thirdInstallment = loanSchedule.get(3);
+
+        validateNumberForEqual("3200", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("3200", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
+
+        validateNumberForEqual("3000", String.valueOf(secondInstallment.get("principalOutstanding")));
+        validateNumberForEqual("3100", String.valueOf(thirdInstallment.get("principalOutstanding")));
+
+        /***
+         * Make payment for installment #2
+         */
+        this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3200"), loanID);
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        /***
+         * Verify 2nd and 3rd repayments after making excess payment for
+         * installment no 2
+         */
+        secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        /***
+         * According to RBI Excess payment should go to principal portion of
+         * next installment, but as interest recalculation is not implemented,
+         * it wont make any difference to schedule even though if we made excess
+         * payment, so excess payments will behave the same as regular payment
+         * with the excess amount
+         */
+        thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("3200", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("3100", String.valueOf(thirdInstallment.get("principalOutstanding")));
+        validateNumberForEqual("0", String.valueOf(thirdInstallment.get("principalPaid")));
+        validateNumberForEqual("0", String.valueOf(thirdInstallment.get("interestPaid")));
+        validateNumberForEqual("100.00", String.valueOf(thirdInstallment.get("interestOutstanding")));
+
+        /***
+         * Make payment with due amount of 3rd installment on 4th installment
+         * date
+         */
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3200"), loanID);
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+
+        /***
+         * Verify overdue interests are deducted first and then remaining amount
+         * for interest portion of due installment
+         */
+        thirdInstallment = loanSchedule.get(3);
+        HashMap fourthInstallment = loanSchedule.get(4);
+
+        validateNumberForEqual("100", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("100", String.valueOf(thirdInstallment.get("principalOutstanding")));
+
+        validateNumberForEqual("2900", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("100", String.valueOf(fourthInstallment.get("interestPaid")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+
+        this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3000"), loanID);
+
+        /***
+         * verify loan is closed as we paid full amount
+         */
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+
+    }
+
+    private void checkAccrualTransactions(final ArrayList<HashMap> loanSchedule, final Integer loanID) {
+
+        for (int i = 1; i < loanSchedule.size(); i++) {
+
+            final HashMap repayment = loanSchedule.get(i);
+
+            final ArrayList<Integer> dueDateAsArray = (ArrayList<Integer>) repayment.get("dueDate");
+            final LocalDate transactionDate = new LocalDate(dueDateAsArray.get(0), dueDateAsArray.get(1), dueDateAsArray.get(2));
+
+            final Float interestPortion = BigDecimal.valueOf(Double.valueOf(repayment.get("interestDue").toString()))
+                    .subtract(BigDecimal.valueOf(Double.valueOf(repayment.get("interestWaived").toString())))
+                    .subtract(BigDecimal.valueOf(Double.valueOf(repayment.get("interestWrittenOff").toString()))).floatValue();
+
+            final Float feePortion = BigDecimal.valueOf(Double.valueOf(repayment.get("feeChargesDue").toString()))
+                    .subtract(BigDecimal.valueOf(Double.valueOf(repayment.get("feeChargesWaived").toString())))
+                    .subtract(BigDecimal.valueOf(Double.valueOf(repayment.get("feeChargesWrittenOff").toString()))).floatValue();
+
+            final Float penaltyPortion = BigDecimal.valueOf(Double.valueOf(repayment.get("penaltyChargesDue").toString()))
+                    .subtract(BigDecimal.valueOf(Double.valueOf(repayment.get("penaltyChargesWaived").toString())))
+                    .subtract(BigDecimal.valueOf(Double.valueOf(repayment.get("penaltyChargesWrittenOff").toString()))).floatValue();
+
+            this.loanTransactionHelper.checkAccrualTransactionForRepayment(transactionDate, interestPortion, feePortion, penaltyPortion,
+                    loanID);
+        }
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_REST_SAME_AS_REPAYMENT_INTEREST_COMPOUND_NONE_STRATEGY_REDUCE_EMI() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.DEFAULT_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null,
+                LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList<HashMap>(0));
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2528.81", "11.67", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        Float earlyPayment = new Float("4000");
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -5);
+        final String LOAN_SECOND_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_SECOND_REPAYMENT_DATE, earlyPayment, loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "3965.31", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "1771.88", "16.39", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "1780.05", "8.22", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
+        String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+        validateNumberForEqualWithMsg("verify pre-close amount", "3551.93", prepayAmount);
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, new Float(prepayAmount), loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_REST_SAME_AS_REPAYMENT_INTEREST_COMPOUND_NONE_STRATEGY_REDUCE_EMI_PRE_CLOSE_INTEREST_PRE_CLOSE_DATE() {
+        String preCloseInterestStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE;
+        String preCloseAmount = "7561.84";
+        testLoanScheduleWithInterestRecalculation_WITH_REST_SAME_AS_REPAYMENT_INTEREST_COMPOUND_NONE_STRATEGY_REDUCE_EMI_PRE_CLOSE_INTEREST(
+                preCloseInterestStrategy, preCloseAmount);
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_REST_SAME_AS_REPAYMENT_INTEREST_COMPOUND_NONE_STRATEGY_REDUCE_EMI_PRE_CLOSE_INTEREST_REST_DATE() {
+        String preCloseInterestStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_REST_DATE;
+        String preCloseAmount = "7586.62";
+        testLoanScheduleWithInterestRecalculation_WITH_REST_SAME_AS_REPAYMENT_INTEREST_COMPOUND_NONE_STRATEGY_REDUCE_EMI_PRE_CLOSE_INTEREST(
+                preCloseInterestStrategy, preCloseAmount);
+    }
+
+    private void testLoanScheduleWithInterestRecalculation_WITH_REST_SAME_AS_REPAYMENT_INTEREST_COMPOUND_NONE_STRATEGY_REDUCE_EMI_PRE_CLOSE_INTEREST(
+            String preCloseInterestStrategy, String preCloseAmount) {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -16);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.DEFAULT_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, preCloseInterestStrategy, null);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null,
+                LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList<HashMap>(0));
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2551.72", "11.78", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -9);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2528.8", "11.67", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
+        String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+        validateNumberForEqualWithMsg("verify pre-close amount", preCloseAmount, prepayAmount);
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, new Float(prepayAmount), loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_REST_SAME_AS_REPAYMENT_INTEREST_COMPOUND_NONE_STRATEGY_REDUCE_EMI_WITH_INSTALLMENT_CHARGE() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.DEFAULT_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null);
+
+        List<HashMap> charges = new ArrayList<>();
+        Integer installmentCharge = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST, "10", false));
+        addCharges(charges, installmentCharge, "10", null);
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null,
+                LoanApplicationTestBuilder.DEFAULT_STRATEGY, charges);
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "4.62", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "3.47", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "2.32", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "1.16", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "4.62", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "46.15", "4.62", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "2.32", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2528.81", "11.67", "1.17", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "4.62", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "3.47", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "2.32", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "1.16", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        Float earlyPayment = new Float("4000");
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -5);
+        final String LOAN_SECOND_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_SECOND_REPAYMENT_DATE, earlyPayment, loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "4.62", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "3961.84", "34.69", "3.47", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "1773.61", "16.41", "1.64", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "1781.79", "8.22", "0.82", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
+        String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, new Float(prepayAmount), loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_REST_DAILY_INTEREST_COMPOUND_INTEREST_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.RBI_INDIA_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", LOAN_DISBURSEMENT_DATE,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
+                LOAN_DISBURSEMENT_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0));
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.54", "46.37", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2529.03", "11.67", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        loanSchedule = this.loanTransactionHelper.getLoanFutureRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, 0, false, "4965.3", "92.52", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2529.03", "11.67", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues, 0);
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        Float earlyPayment = new Float("4000");
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -5);
+        final String LOAN_SECOND_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_SECOND_REPAYMENT_DATE, earlyPayment, loanID);
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        Calendar today = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        Map<String, Object> paymentday = new HashMap<>(3);
+        paymentday.put("dueDate", getDateAsArray(today, -5, Calendar.DAY_OF_MONTH));
+        paymentday.put("principalDue", "3990.09");
+        paymentday.put("interestDue", "9.91");
+        paymentday.put("feeChargesDue", "0");
+        paymentday.put("penaltyChargesDue", "0");
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        expectedvalues.add(paymentday);
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.28", "11.63", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "1009.87", "4.66", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
+        String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, new Float(prepayAmount), loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -2);
+        final String REST_START_DATE = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        todaysDate.add(Calendar.DAY_OF_MONTH, 2);
+        final String LOAN_FLAT_CHARGE_DATE = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.DAY_OF_MONTH, 14);
+        final String LOAN_INTEREST_CHARGE_DATE = dateFormat.format(todaysDate.getTime());
+        List<HashMap> charges = new ArrayList<>(2);
+        Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", false));
+        Integer principalPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "2", false));
+
+        addCharges(charges, flat, "100", LOAN_FLAT_CHARGE_DATE);
+        addCharges(charges, principalPercentage, "2", LOAN_INTEREST_CHARGE_DATE);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.DEFAULT_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", REST_START_DATE,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", LOAN_DISBURSEMENT_DATE,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
+                REST_START_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.DEFAULT_STRATEGY, charges);
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "100.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2486.03", "42.88", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2497.5", "31.41", "200", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2533.71", "19.93", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "100.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.08", "46.83", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2488.82", "40.09", "200", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2546.34", "19.96", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        Calendar repaymentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        repaymentDate.add(Calendar.DAY_OF_MONTH, -7);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(repaymentDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "100.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2486.03", "42.88", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2497.5", "31.41", "200", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2533.71", "19.93", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        Float earlyPayment = new Float("5100");
+        repaymentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        repaymentDate.add(Calendar.DAY_OF_MONTH, -5);
+        final String LOAN_SECOND_REPAYMENT_DATE = dateFormat.format(repaymentDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_SECOND_REPAYMENT_DATE, earlyPayment, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "100.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "5100.0", "36.16", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "0", "11.16", "200", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2417.24", "11.16", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
+        String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, new Float(prepayAmount), loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST_PRE_CLOSE_DATE() {
+        String preCloseInterestStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE;
+        String preCloseAmount = "7766.82";
+        testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST(
+                preCloseInterestStrategy, preCloseAmount);
+
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST_REST_DATE() {
+        String preCloseInterestStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_REST_DATE;
+        String preCloseAmount = "7771.8";
+        testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST(
+                preCloseInterestStrategy, preCloseAmount);
+
+    }
+
+    private void testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST(
+            String preCloseInterestStrategy, String preCloseAmount) {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -16);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -4);
+        final String REST_START_DATE = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -16);
+        todaysDate.add(Calendar.DAY_OF_MONTH, 2);
+        final String LOAN_FLAT_CHARGE_DATE = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.DAY_OF_MONTH, 14);
+        final String LOAN_INTEREST_CHARGE_DATE = dateFormat.format(todaysDate.getTime());
+        List<HashMap> charges = new ArrayList<>(2);
+        Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", false));
+        Integer principalPercentage = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, "2", false));
+
+        addCharges(charges, flat, "100", LOAN_FLAT_CHARGE_DATE);
+        addCharges(charges, principalPercentage, "2", LOAN_INTEREST_CHARGE_DATE);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.DEFAULT_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", REST_START_DATE,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, null, null, preCloseInterestStrategy, null);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
+                REST_START_DATE, LoanApplicationTestBuilder.DEFAULT_STRATEGY, charges);
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "100.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2489.3", "39.61", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2500.78", "28.13", "200", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.16", "16.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "100.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.08", "46.83", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2495.47", "33.44", "200", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2539.69", "16.66", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        Calendar repaymentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        repaymentDate.add(Calendar.DAY_OF_MONTH, -9);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(repaymentDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "100.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2489.3", "39.61", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2500.7", "28.21", "200", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.24", "16.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
+        String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+        validateNumberForEqualWithMsg("verify pre-close amount", preCloseAmount, prepayAmount);
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, new Float(prepayAmount), loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_REST_DAILY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_WITH_OVERDUE_CHARGE()
+            throws InterruptedException {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7 * 3);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -2);
+        final String REST_START_DATE = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+
+        Integer overdueFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getLoanOverdueFeeJSONWithCalculattionTypePercentage());
+        Assert.assertNotNull(overdueFeeChargeId);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String recalculationCompoundingFrequencyInterval = null;
+        final String recalculationCompoundingFrequencyDate = null;
+        final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.DEFAULT_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", REST_START_DATE,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, recalculationCompoundingFrequencyInterval,
+                recalculationCompoundingFrequencyDate, LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null,
+                overdueFeeChargeId.toString(), false);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
+                REST_START_DATE, LoanApplicationTestBuilder.DEFAULT_STRATEGY, null);
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -2, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+
+        addRepaymentValues(expectedvalues, todaysDate, -2, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.54", "46.37", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.33", "46.58", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2552.37", "11.78", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        String JobName = "Apply penalty to overdue loans";
+        this.schedulerJobHelper.executeJob(JobName);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -2, false, "2482.76", "46.15", "0.0", "252.89");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2481.38", "47.53", "0.0", "252.89");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2479.99", "48.92", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2555.87", "11.8", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        Calendar repaymentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        repaymentDate.add(Calendar.DAY_OF_MONTH, -7 * 2);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(repaymentDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        totalDueForCurrentPeriod = totalDueForCurrentPeriod - new Float("252.89");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -2, false, "2482.76", "46.15", "0.0", "252.89");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2493.05", "35.86", "0.0", "252.89");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2491.72", "37.19", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2532.47", "11.69", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        repaymentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        repaymentDate.add(Calendar.DAY_OF_MONTH, -3);
+        final String LOAN_SECOND_REPAYMENT_DATE = dateFormat.format(repaymentDate.getTime());
+        totalDueForCurrentPeriod = (Float) loanSchedule.get(2).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_SECOND_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -2, false, "2482.76", "46.15", "0.0", "252.89");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2493.05", "35.86", "0.0", "252.89");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2497.22", "31.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2526.97", "11.66", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_PERIODIC_ACCOUNTING() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(this.requestSpec, this.responseSpec);
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        System.out.println("Disbursal Date Calendar " + todaysDate.getTime());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        Account[] accounts = { assetAccount, incomeAccount, expenseAccount, overpaymentAccount };
+        final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.DEFAULT_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, accounts);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null,
+                LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList<HashMap>(0));
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        System.out.println("Date during repayment schedule" + todaysDate.getTime());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2528.81", "11.67", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(10000.0f, JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(10000.0f, JournalEntry.TransactionType.DEBIT), };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, LOAN_DISBURSEMENT_DATE, assetAccountInitialEntry);
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        String runOndate = dateFormat.format(todaysDate.getTime());
+        this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOndate);
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant().minusDays(7), 46.15f, 0f, 0f, loanID);
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant(), 46.15f, 0f, 0f, loanID);
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant().minusDays(7), 46.15f, 0f, 0f, loanID);
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant(), 34.69f, 0f, 0f, loanID);
+
+        HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
+        String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, new Float(prepayAmount), loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant().minusDays(7), 46.15f, 0f, 0f, loanID);
+        this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant(), 34.69f, 0f, 0f, loanID);
+
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_CURRENT_REPAYMENT_BASED_ARREARS_AGEING() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.RBI_INDIA_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "1", LOAN_DISBURSEMENT_DATE,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
+                LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0));
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.54", "46.37", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2529.03", "11.67", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
+        List dates = (List) loanSummary.get("overdueSinceDate");
+        assertEquals(todaysDate.get(Calendar.YEAR), dates.get(0));
+        assertEquals(todaysDate.get(Calendar.MONTH) + 1, dates.get(1));
+        assertEquals(todaysDate.get(Calendar.DAY_OF_MONTH), dates.get(2));
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -8);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
+        dates = (List) loanSummary.get("overdueSinceDate");
+        assertEquals(todaysDate.get(Calendar.YEAR), dates.get(0));
+        assertEquals(todaysDate.get(Calendar.MONTH) + 1, dates.get(1));
+        assertEquals(todaysDate.get(Calendar.DAY_OF_MONTH), dates.get(2));
+
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_WITH_ORIGINAL_REPAYMENT_BASED_ARREARS_AGEING() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        System.out.println("----timeeeeeeeeeeeeee------>" + dateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String recalculationCompoundingFrequencyInterval = null;
+        final String recalculationCompoundingFrequencyDate = null;
+        final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, recalculationCompoundingFrequencyInterval,
+                recalculationCompoundingFrequencyDate, LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, null,
+                true);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
+                LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0));
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.54", "46.37", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2529.03", "11.67", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
+        List dates = (List) loanSummary.get("overdueSinceDate");
+        assertEquals(todaysDate.get(Calendar.YEAR), dates.get(0));
+        assertEquals(todaysDate.get(Calendar.MONTH) + 1, dates.get(1));
+        assertEquals(todaysDate.get(Calendar.DAY_OF_MONTH), dates.get(2));
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -8);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
+        dates = (List) loanSummary.get("overdueSinceDate");
+        Assert.assertNull(dates);
+
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM_INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE() {
+        testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM(
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, "10006.59");
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM_INTEREST_APPLICABLE_STRATEGY_REST_DATE() {
+        testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM(
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_REST_DATE, "10046.15");
+    }
+
+    private void testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM(final String preCloseStrategy,
+            final String preCloseAmount) {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
+        Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.DEFAULT_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, preCloseStrategy, null);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculationWithMoratorium(clientID, loanProductID,
+                LOAN_DISBURSEMENT_DATE, null, LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList<HashMap>(0), "1", null);
+
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "0.0", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "80.84", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        expectedvalues = new ArrayList<>();
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "0.0", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "80.84", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0");
+
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+
+        HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
+        String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+        validateNumberForEqualWithMsg("verify pre-close amount", preCloseAmount, prepayAmount);
+        todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, new Float(prepayAmount), loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+    }
+
+    private void addRepaymentValues(List<Map<String, Object>> expectedvalues, Calendar todaysDate, int addPeriod, boolean isAddDays,
+            String principalDue, String interestDue, String feeChargesDue, String penaltyChargesDue) {
+        Map<String, Object> values = new HashMap<>(3);
+        if (isAddDays) {
+            values.put("dueDate", getDateAsArray(todaysDate, addPeriod));
+        } else {
+            values.put("dueDate", getDateAsArray(todaysDate, addPeriod * 7));
+        }
+        System.out.println("Updated date " + values.get("dueDate"));
+        values.put("principalDue", principalDue);
+        values.put("interestDue", interestDue);
+        values.put("feeChargesDue", feeChargesDue);
+        values.put("penaltyChargesDue", penaltyChargesDue);
+        expectedvalues.add(values);
+    }
+
+    private List getDateAsArray(Calendar todaysDate, int addPeriod) {
+        return getDateAsArray(todaysDate, addPeriod, Calendar.DAY_OF_MONTH);
+    }
+
+    private List getDateAsArray(Calendar todaysDate, int addvalue, int type) {
+        todaysDate.add(type, addvalue);
+        return new ArrayList<>(Arrays.asList(todaysDate.get(Calendar.YEAR), todaysDate.get(Calendar.MONTH) + 1,
+                todaysDate.get(Calendar.DAY_OF_MONTH)));
+    }
+
+    private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
+            final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
+            final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
+            final String recalculationRestFrequencyDate, final String preCloseInterestCalculationStrategy, final Account[] accounts) {
+        final String recalculationCompoundingFrequencyType = null;
+        final String recalculationCompoundingFrequencyInterval = null;
+        final String recalculationCompoundingFrequencyDate = null;
+        return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod,
+                rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval,
+                recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval,
+                recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false);
+    }
+
+    private Integer createLoanProductWithInterestRecalculationAndCompoundingDetails(final String repaymentStrategy,
+            final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
+            final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
+            final String recalculationRestFrequencyDate, final String recalculationCompoundingFrequencyType,
+            final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate,
+            final String preCloseInterestCalculationStrategy, final Account[] accounts) {
+        return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod,
+                rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval,
+                recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval,
+                recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false);
+    }
+
+    private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
+            final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
+            final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
+            final String recalculationRestFrequencyDate, final String recalculationCompoundingFrequencyType,
+            final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate,
+            final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId,
+            boolean isArrearsBasedOnOriginalSchedule) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        LoanProductTestBuilder builder = new LoanProductTestBuilder()
+                .withPrincipal("10000000.00")
+                .withNumberOfRepayments("24")
+                .withRepaymentAfterEvery("1")
+                .withRepaymentTypeAsWeek()
+                .withinterestRatePerPeriod("2")
+                .withInterestRateFrequencyTypeAsMonths()
+                .withRepaymentStrategy(repaymentStrategy)
+                .withAmortizationTypeAsEqualPrincipalPayment()
+                .withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+                .withInterestTypeAsDecliningBalance()
+                .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
+                        preCloseInterestCalculationStrategy)
+                .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval,
+                        recalculationRestFrequencyDate)
+                .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType,
+                        recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate);
+        if (accounts != null) {
+            builder = builder.withAccountingRulePeriodicAccrual(accounts);
+        }
+
+        if (isArrearsBasedOnOriginalSchedule) builder = builder.withArrearsConfiguration();
+
+        final String loanProductJSON = builder.build(chargeId);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
+            final String disbursementDate, final String restStartDate, final String repaymentStrategy, final List<HashMap> charges) {
+        final String compoundingStartDate = null;
+        return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate,
+                compoundingStartDate, repaymentStrategy, charges);
+    }
+
+    private Integer applyForLoanApplicationForInterestRecalculationWithMoratorium(final Integer clientID, final Integer loanProductID,
+            final String disbursementDate, final String restStartDate, final String repaymentStrategy, final List<HashMap> charges,
+            final String graceOnInterestPayment, final String graceOnPrincipalPayment) {
+        final String compoundingStartDate = null;
+        return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate,
+                compoundingStartDate, repaymentStrategy, charges, graceOnInterestPayment, graceOnPrincipalPayment);
+    }
+
+    private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
+            final String disbursementDate, final String restStartDate, final String compoundingStartDate, final String repaymentStrategy,
+            final List<HashMap> charges) {
+        return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate,
+                compoundingStartDate, repaymentStrategy, charges, null, null);
+    }
+
+    private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
+            final String disbursementDate, final String restStartDate, final String compoundingStartDate, final String repaymentStrategy,
+            final List<HashMap> charges, final String graceOnInterestPayment, final String graceOnPrincipalPayment) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal("10000.00") //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsWeeks() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsWeeks() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate(disbursementDate) //
+                .withSubmittedOnDate(disbursementDate) //
+                .withRestFrequencyDate(restStartDate)//
+                .withCompoundingFrequencyDate(compoundingStartDate)//
+                .withwithRepaymentStrategy(repaymentStrategy) //
+                .withPrincipalGrace(graceOnPrincipalPayment) //
+                .withInterestGrace(graceOnInterestPayment)//
+                .withCharges(charges)//
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private void verifyLoanRepaymentSchedule(final ArrayList<HashMap> loanSchedule, List<Map<String, Object>> expectedvalues) {
+        int index = 1;
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues, index);
+
+    }
+
+    private void verifyLoanRepaymentSchedule(final ArrayList<HashMap> loanSchedule, List<Map<String, Object>> expectedvalues, int index) {
+        System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------");
+        for (Map<String, Object> values : expectedvalues) {
+            assertEquals("Checking for Due Date for  installment " + index, values.get("dueDate"), loanSchedule.get(index).get("dueDate"));
+            validateNumberForEqualWithMsg("Checking for Principal Due for installment " + index,
+                    String.valueOf(values.get("principalDue")), String.valueOf(loanSchedule.get(index).get("principalDue")));
+            validateNumberForEqualWithMsg("Checking for Interest Due for installment " + index, String.valueOf(values.get("interestDue")),
+                    String.valueOf(loanSchedule.get(index).get("interestDue")));
+            validateNumberForEqualWithMsg("Checking for Fee charge Due for installment " + index,
+                    String.valueOf(values.get("feeChargesDue")), String.valueOf(loanSchedule.get(index).get("feeChargesDue")));
+            validateNumberForEqualWithMsg("Checking for Penalty charge Due for installment " + index,
+                    String.valueOf(values.get("penaltyChargesDue")), String.valueOf(loanSchedule.get(index).get("penaltyChargesDue")));
+            index++;
+        }
+    }
+
+    /***
+     * Test case to verify default Style payment strategy
+     */
+    @Test
+    public void testLoanRefundByCashCashBasedAccounting() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+        Calendar fourMonthsfromNowCalendar = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, -4);
+
+        String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        /***
+         * Create loan product with Default STYLE strategy
+         */
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.DEFAULT_STRATEGY, CASH_BASED, assetAccount,
+                incomeAccount, expenseAccount, overpaymentAccount);
+        Assert.assertNotNull(loanProductID);
+
+        /***
+         * Apply for loan application and verify loan status
+         */
+        final String savingsId = null;
+        final String principal = "12,000.00";
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+
+        Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+        addCharges(charges, flatInstallmentFee, "50", null);
+
+        final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, savingsId,
+                principal, LoanApplicationTestBuilder.DEFAULT_STRATEGY, -4);
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(fourMonthsfromNow, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(fourMonthsfromNow, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, fourMonthsfromNow, assetAccountInitialEntry);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("2290", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+        // Make payment for installment #1
+
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+        final String threeMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRepayment(threeMonthsfromNow, Float.valueOf("2290"), loanID);
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+        // Make payment for installment #2
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+        final String twoMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("2290"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2000"), JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("50"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Map secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        // Make payment for installment #3
+        // Pay 2290 more than expected
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+        final String oneMonthfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("4580"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("4000"), JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("100"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("0.00", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
+
+        // Make refund of 20
+        // max 2290 to refund. Pay 20 means only principal
+        // Default style refund order(principal, interest, fees and penalties
+        // paid: principal 2000, interest 240, fees 50, penalty 0
+        // refund 20 means paid: principal 1980, interest 240, fees 50, penalty
+        // 0
+
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+        final String now = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("20"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("20"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.DEBIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+        // Make refund of 2000
+        // max 2270 to refund. Pay 2000 means only principal
+        // paid: principal 1980, interest 240, fees 50, penalty 0
+        // refund 2000 means paid: principal 0, interest 220, fees 50, penalty 0
+
+        this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("2000"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("2000"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("1980"), JournalEntry.TransactionType.DEBIT));
+
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, now, new JournalEntry(Float.valueOf("20"),
+                JournalEntry.TransactionType.DEBIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("2020.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("2000.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+        validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+    }
+
+    /***
+     * Test case to verify Default style payment strategy
+     */
+    @Test
+    public void testLoanRefundByCashAccrualBasedAccounting() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+        Calendar fourMonthsfromNowCalendar = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, -4);
+
+        String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        /***
+         * Create loan product with Default STYLE strategy
+         */
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.DEFAULT_STRATEGY, ACCRUAL_UPFRONT,
+                assetAccount, incomeAccount, expenseAccount, overpaymentAccount);// ,
+                                                                                 // LoanProductTestBuilder.EQUAL_INSTALLMENTS,
+        // LoanProductTestBuilder.FLAT_BALANCE);
+        Assert.assertNotNull(loanProductID);
+
+        /***
+         * Apply for loan application and verify loan status
+         */
+        final String savingsId = null;
+        final String principal = "12,000.00";
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+
+        Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+        addCharges(charges, flatInstallmentFee, "50", null);
+
+        final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, savingsId,
+                principal, LoanApplicationTestBuilder.DEFAULT_STRATEGY, -4);
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(fourMonthsfromNow, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(fourMonthsfromNow, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("1440"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("300.00"), JournalEntry.TransactionType.DEBIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, fourMonthsfromNow, assetAccountInitialEntry);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("2290", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+        // Make payment for installment #1
+
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+        final String threeMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRepayment(threeMonthsfromNow, Float.valueOf("2290"), loanID);
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+        // Make payment for installment #2
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+        final String twoMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("2290"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2000"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Map secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        // Make payment for installment #3
+        // Pay 2290 more than expected
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+        final String oneMonthfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("4580"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("4000"),
+                        JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("0.00", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
+
+        // Make refund of 20
+        // max 2290 to refund. Pay 20 means only principal
+        // Default style refund order(principal, interest, fees and penalties
+        // paid: principal 2000, interest 240, fees 50, penalty 0
+        // refund 20 means paid: principal 1980, interest 240, fees 50, penalty
+        // 0
+
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+        final String now = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("20"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("20"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.DEBIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+        // Make refund of 2000
+        // max 2270 to refund. Pay 2000 means only principal
+        // paid: principal 1980, interest 240, fees 50, penalty 0
+        // refund 2000 means paid: principal 0, interest 220, fees 50, penalty 0
+
+        this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("2000"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("2000"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("1980"), JournalEntry.TransactionType.DEBIT));
+
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, now, new JournalEntry(Float.valueOf("20"),
+                JournalEntry.TransactionType.DEBIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("2020.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("2000.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+        validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+    }
+
+    @Test
+    public void testLoanRefundByTransferCashBasedAccounting() {
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.accountTransferHelper = new AccountTransferHelper(this.requestSpec, this.responseSpec);
+
+        Calendar fourMonthsfromNowCalendar = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, -4);
+
+        String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
+                ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertTrue(modifications.containsKey("submittedOnDate"));
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        /***
+         * Create loan product with Default STYLE strategy
+         */
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.DEFAULT_STRATEGY, CASH_BASED, assetAccount,
+                incomeAccount, expenseAccount, overpaymentAccount);
+        Assert.assertNotNull(loanProductID);
+
+        /***
+         * Apply for loan application and verify loan status
+         */
+
+        final String principal = "12,000.00";
+
+        // Add charges with payment mode regular
+        List<HashMap> charges = new ArrayList<>();
+
+        Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+        addCharges(charges, flatInstallmentFee, "50", null);
+
+        final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, null, principal,
+                LoanApplicationTestBuilder.DEFAULT_STRATEGY, -4);
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(fourMonthsfromNow, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(fourMonthsfromNow, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+                new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, fourMonthsfromNow, assetAccountInitialEntry);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("2290", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+        // Make payment for installment #1
+
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+        final String threeMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRepayment(threeMonthsfromNow, Float.valueOf("2290"), loanID);
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        firstInstallment = loanSchedule.get(1);
+        validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+        // Make payment for installment #2
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+        final String twoMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("2290"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2000"), JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("50"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Map secondInstallment = loanSchedule.get(2);
+        validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+        // Make payment for installment #3
+        // Pay 2290 more than expected
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+        final String oneMonthfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID);
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("4580"),
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("4000"), JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("100"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap thirdInstallment = loanSchedule.get(3);
+        validateNumberForEqual("0.00", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
+
+        // Make refund of 20
+        // max 2290 to refund. Pay 20 means only principal
+        // Default style refund order(principal, interest, fees and penalties
+        // paid: principal 2000, interest 240, fees 50, penalty 0
+        // refund 20 means paid: principal 1980, interest 240, fees 50, penalty
+        // 0
+
+        Float TRANSFER_AMOUNT = 20f;
+
+        fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+        final String now = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+        final String FROM_LOAN_ACCOUNT_TYPE = "1";
+        final String TO_SAVINGS_ACCOUNT_TYPE = "2";
+
+        this.accountTransferHelper.refundLoanByTransfer(now, clientID, loanID, clientID, savingsId, FROM_LOAN_ACCOUNT_TYPE,
+                TO_SAVINGS_ACCOUNT_TYPE, TRANSFER_AMOUNT.toString());
+
+        Float toSavingsBalance = new Float(MINIMUM_OPENING_BALANCE);
+
+        HashMap toSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+        toSavingsBalance += TRANSFER_AMOUNT;
+
+        // Verifying toSavings Account Balance after Account Transfer
+        assertEquals("Verifying From Savings Account Balance after Account Transfer", toSavingsBalance,
+                toSavingsSummaryAfter.get("accountBalance"));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("20"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.DEBIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        HashMap fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+        // Make refund of 2000
+        // max 2270 to refund. Pay 2000 means only principal
+        // paid: principal 1980, interest 240, fees 50, penalty 0
+        // refund 2000 means paid: principal 0, interest 220, fees 50, penalty 0
+        // final String now = Utils.convertDate(fourMonthsfromNowCalendar);
+
+        TRANSFER_AMOUNT = 2000f;
+
+        this.accountTransferHelper.refundLoanByTransfer(now, clientID, loanID, clientID, savingsId, FROM_LOAN_ACCOUNT_TYPE,
+                TO_SAVINGS_ACCOUNT_TYPE, TRANSFER_AMOUNT.toString());
+
+        toSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+        toSavingsBalance += TRANSFER_AMOUNT;
+
+        // Verifying toSavings Account Balance after Account Transfer
+        assertEquals("Verifying From Savings Account Balance after Account Transfer", toSavingsBalance,
+                toSavingsSummaryAfter.get("accountBalance"));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("2000"),
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("1980"), JournalEntry.TransactionType.DEBIT));
+
+        this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, now, new JournalEntry(Float.valueOf("20"),
+                JournalEntry.TransactionType.DEBIT));
+
+        loanSchedule.clear();
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        fourthInstallment = loanSchedule.get(4);
+        validateNumberForEqual("2020.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+        validateNumberForEqual("2000.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+        validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+        validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+    }
+
+    private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String minOpenningBalance) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+        SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+
+        final String savingsProductJSON = savingsProductHelper
+        //
+                .withInterestCompoundingPeriodTypeAsDaily()
+                //
+                .withInterestPostingPeriodTypeAsMonthly()
+                //
+                .withInterestCalculationPeriodTypeAsDailyBalance()
+
+                .withMinimumOpenningBalance(minOpenningBalance).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    @Test
+    public void testLoanProductConfiguration() {
+        final String proposedAmount = "5000";
+        JsonObject loanProductConfigurationAsTrue = new JsonObject();
+        loanProductConfigurationAsTrue = this.createLoanProductConfigurationDetail(loanProductConfigurationAsTrue, true);
+
+        JsonObject loanProductConfigurationAsFalse = new JsonObject();
+        loanProductConfigurationAsFalse = this.createLoanProductConfigurationDetail(loanProductConfigurationAsFalse, false);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder()
+                .withAmortizationTypeAsEqualInstallments().withRepaymentTypeAsMonth().withRepaymentAfterEvery("1")
+                .withRepaymentStrategy(LoanProductTestBuilder.DEFAULT_STRATEGY).withInterestTypeAsDecliningBalance()
+                .withInterestCalculationPeriodTypeAsDays().withInArrearsTolerance("10").withMoratorium("2", "3")
+                .withLoanProductConfiguration(loanProductConfigurationAsTrue).build(null));
+        System.out.println("-----------------------LOAN PRODUCT CREATED WITH ATTRIBUTE CONFIGURATION AS TRUE--------------------------"
+                + loanProductID);
+        Integer loanID = applyForLoanApplicationWithProductConfigurationAsTrue(clientID, loanProductID, proposedAmount);
+        System.out.println("------------------------LOAN CREATED WITH ID------------------------------" + loanID);
+
+        loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments()
+                .withRepaymentTypeAsMonth().withRepaymentAfterEvery("1")
+                .withRepaymentStrategy(LoanProductTestBuilder.DEFAULT_STRATEGY).withInterestTypeAsDecliningBalance()
+                .withInterestCalculationPeriodTypeAsDays().withInArrearsTolerance("10").withMoratorium("2", "3")
+                .withLoanProductConfiguration(loanProductConfigurationAsFalse).build(null));
+        System.out.println("-------------------LOAN PRODUCT CREATED WITH ATTRIBUTE CONFIGURATION AS FALSE----------------------"
+                + loanProductID);
+        /*
+         * Try to override attribute values in loan account when attribute
+         * configurations are set to false at product level
+         */
+        loanID = applyForLoanApplicationWithProductConfigurationAsFalse(clientID, loanProductID, proposedAmount);
+        System.out.println("--------------------------LOAN CREATED WITH ID-------------------------" + loanID);
+        this.validateIfValuesAreNotOverridden(loanID, loanProductID);
+    }
+
+    private void validateIfValuesAreNotOverridden(Integer loanID, Integer loanProductID) {
+        String loanProductDetails = this.loanTransactionHelper.getLoanProductDetails(this.requestSpec, this.responseSpec, loanProductID);
+        String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
+        List<String> comparisonAttributes = Arrays.asList("amortizationType", "interestType", "transactionProcessingStrategyId",
+                "interestCalculationPeriodType", "repaymentFrequencyType", "graceOnPrincipalPayment", "graceOnInterestPayment",
+                "inArrearsTolerance", "graceOnArrearsAgeing");
+
+        for (String comparisonAttribute : comparisonAttributes) {
+            assertEquals(JsonPath.from(loanProductDetails).get(comparisonAttribute), JsonPath.from(loanDetails).get(comparisonAttribute));
+        }
+    }
+
+    private JsonObject createLoanProductConfigurationDetail(JsonObject loanProductConfiguration, Boolean bool) {
+        loanProductConfiguration.addProperty("amortizationType", bool);
+        loanProductConfiguration.addProperty("interestType", bool);
+        loanProductConfiguration.addProperty("transactionProcessingStrategyId", bool);
+        loanProductConfiguration.addProperty("interestCalculationPeriodType", bool);
+        loanProductConfiguration.addProperty("inArrearsTolerance", bool);
+        loanProductConfiguration.addProperty("repaymentEvery", bool);
+        loanProductConfiguration.addProperty("graceOnPrincipalAndInterestPayment", bool);
+        loanProductConfiguration.addProperty("graceOnArrearsAgeing", bool);
+        return loanProductConfiguration;
+    }
+
+    private Integer applyForLoanApplicationWithProductConfigurationAsTrue(final Integer clientID, final Integer loanProductID,
+            String principal) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withRepaymentEveryAfter("1") //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("1 March 2014") //
+                .withSubmittedOnDate("1 March 2014") //
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private Integer applyForLoanApplicationWithProductConfigurationAsFalse(final Integer clientID, final Integer loanProductID,
+            String principal) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder()
+                //
+                .withPrincipal(principal)
+                //
+                .withRepaymentEveryAfter("2")
+                //
+                .withAmortizationTypeAsEqualPrincipalPayments().withRepaymentFrequencyTypeAsWeeks()
+                .withwithRepaymentStrategy(LoanProductTestBuilder.RBI_INDIA_STRATEGY).withInterestTypeAsFlatBalance()
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withPrincipalGrace("1").withInterestGrace("1")
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("1 March 2014") //
+                .withSubmittedOnDate("1 March 2014") //
+                .build(clientID.toString(), loanProductID.toString(), null);
+
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+}
\ No newline at end of file
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
new file mode 100755
index 0000000..515eb5a
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java
@@ -0,0 +1,832 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.joda.time.LocalDate;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Client Savings Integration Test for checking Savings Application.
+ */
+@SuppressWarnings({ "rawtypes", "unused" })
+public class ClientSavingsIntegrationTest {
+
+    public static final String DEPOSIT_AMOUNT = "2000";
+    public static final String WITHDRAW_AMOUNT = "1000";
+    public static final String WITHDRAW_AMOUNT_ADJUSTED = "500";
+    public static final String MINIMUM_OPENING_BALANCE = "1000.0";
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void testSavingsAccount() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final boolean allowOverdraft = false;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
+                ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertTrue(modifications.containsKey("submittedOnDate"));
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        this.savingsAccountHelper.calculateInterestForSavings(savingsId);
+        HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        assertEquals(summaryBefore, summary);
+
+        this.savingsAccountHelper.postInterestForSavings(savingsId);
+        summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Assert.assertFalse(summaryBefore.equals(summary));
+
+        final Object savingsInterest = this.savingsAccountHelper.getSavingsInterest(savingsId);
+        // verifySavingsInterest(savingsInterest);
+
+    }
+
+    @Test
+    public void testSavingsAccountWithMinBalanceForInterestCalculation() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = "5000";
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final boolean allowOverdraft = false;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
+                ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertTrue(modifications.containsKey("submittedOnDate"));
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        this.savingsAccountHelper.calculateInterestForSavings(savingsId);
+        HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        assertEquals(summaryBefore, summary);
+
+        this.savingsAccountHelper.postInterestForSavings(savingsId);
+        summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        assertEquals(summaryBefore, summary);
+
+        final Object savingsInterest = this.savingsAccountHelper.getSavingsInterest(savingsId);
+        Assert.assertNull(savingsInterest);
+
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccount_CLOSE_APPLICATION() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build();
+        final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = "1000.0";
+        final String enforceMinRequiredBalance = "true";
+        final boolean allowOverdraft = false;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar todaysDate = Calendar.getInstance();
+        final String CLOSEDON_DATE = dateFormat.format(todaysDate.getTime());
+        String withdrawBalance = "false";
+        ArrayList<HashMap> savingsAccountErrorData = (ArrayList<HashMap>) validationErrorHelper.closeSavingsAccountAndGetBackRequiredField(
+                savingsId, withdrawBalance, CommonConstants.RESPONSE_ERROR, CLOSEDON_DATE);
+        assertEquals("validation.msg.savingsaccount.close.results.in.balance.not.zero",
+                savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        withdrawBalance = "true";
+        savingsStatusHashMap = this.savingsAccountHelper.closeSavingsAccount(savingsId, withdrawBalance);
+        SavingsStatusChecker.verifySavingsAccountIsClosed(savingsStatusHashMap);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccount_WITH_ENFORCE_MIN_BALANCE() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build();
+        final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = "1500.0";
+        final String openningBalance = "1600";
+        final String enforceMinRequiredBalance = "true";
+        final boolean allowOverdraft = false;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, openningBalance,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        final Integer savingsActivationChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsActivationFeeJSON());
+        Assert.assertNotNull(savingsActivationChargeId);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, savingsActivationChargeId);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Float balance = new Float(openningBalance);
+        Float chargeAmt = 100f;
+        balance -= chargeAmt;
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+        Calendar todaysDate = Calendar.getInstance();
+        final String TRANSACTION_DATE = dateFormat.format(todaysDate.getTime());
+        final String withdrawAmt = "800";
+        ArrayList<HashMap> savingsAccountErrorData = (ArrayList<HashMap>) validationErrorHelper.withdrawalFromSavingsAccount(savingsId,
+                withdrawAmt, TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance",
+                savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT,
+                TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        HashMap depositTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, depositTransactionId);
+        balance += new Float(DEPOSIT_AMOUNT);
+        assertEquals("Verifying Deposit Amount", new Float(DEPOSIT_AMOUNT), depositTransaction.get("amount"));
+        assertEquals("Verifying Balance after Deposit", balance, depositTransaction.get("runningBalance"));
+
+        Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, withdrawAmt,
+                TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        HashMap withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
+        balance -= new Float(withdrawAmt);
+        assertEquals("Verifying Withdrawal Amount", new Float(withdrawAmt), withdrawTransaction.get("amount"));
+        assertEquals("Verifying Balance after Withdrawal", balance, withdrawTransaction.get("runningBalance"));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccount_DELETE_APPLICATION() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec,
+                new ResponseSpecBuilder().build());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final boolean allowOverdraft = false;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        List<HashMap> error1 = (List<HashMap>) savingsAccountHelperValidationError.deleteSavingsApplication(savingsId,
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("validation.msg.savingsaccount.delete.not.in.submittedandpendingapproval.state",
+                error1.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        savingsStatusHashMap = this.savingsAccountHelper.undoApproval(savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        this.savingsAccountHelper.deleteSavingsApplication(savingsId, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        List<HashMap> error = savingsAccountHelperValidationError.getSavingsCollectionAttribute(savingsId, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.saving.account.id.invalid", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccount_REJECT_APPLICATION() {
+
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec,
+                new ResponseSpecBuilder().build());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final boolean allowOverdraft = false;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        List<HashMap> error1 = savingsAccountHelperValidationError.rejectApplicationWithErrorCode(savingsId,
+                SavingsAccountHelper.CREATED_DATE_PLUS_ONE);
+        assertEquals("validation.msg.savingsaccount.reject.not.in.submittedandpendingapproval.state",
+                error1.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        savingsStatusHashMap = this.savingsAccountHelper.undoApproval(savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        error1 = savingsAccountHelperValidationError.rejectApplicationWithErrorCode(savingsId, SavingsAccountHelper.getFutureDate());
+        assertEquals("validation.msg.savingsaccount.reject.cannot.be.a.future.date",
+                error1.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error1 = savingsAccountHelperValidationError.rejectApplicationWithErrorCode(savingsId, SavingsAccountHelper.CREATED_DATE_MINUS_ONE);
+        assertEquals("validation.msg.savingsaccount.reject.cannot.be.before.submittal.date",
+                error1.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        savingsStatusHashMap = this.savingsAccountHelper.rejectApplication(savingsId);
+        SavingsStatusChecker.verifySavingsIsRejected(savingsStatusHashMap);
+
+    }
+
+    @Test
+    public void testSavingsAccount_WITHDRAW_APPLICATION() {
+
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final boolean allowOverdraft = false;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.withdrawApplication(savingsId);
+        SavingsStatusChecker.verifySavingsIsWithdrawn(savingsStatusHashMap);
+
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccountTransactions() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec,
+                new ResponseSpecBuilder().build());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final boolean allowOverdraft = false;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        List<HashMap> error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "100",
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.account.is.not.active",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.depositToSavingsAccount(savingsId, "100", SavingsAccountHelper.TRANSACTION_DATE,
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.account.is.not.active",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Float balance = new Float(MINIMUM_OPENING_BALANCE);
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+        Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT,
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        HashMap depositTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, depositTransactionId);
+        balance += new Float(DEPOSIT_AMOUNT);
+        assertEquals("Verifying Deposit Amount", new Float(DEPOSIT_AMOUNT), depositTransaction.get("amount"));
+        assertEquals("Verifying Balance after Deposit", balance, depositTransaction.get("runningBalance"));
+
+        Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, WITHDRAW_AMOUNT,
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        HashMap withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
+        balance -= new Float(WITHDRAW_AMOUNT);
+        assertEquals("Verifying Withdrawal Amount", new Float(WITHDRAW_AMOUNT), withdrawTransaction.get("amount"));
+        assertEquals("Verifying Balance after Withdrawal", balance, withdrawTransaction.get("runningBalance"));
+
+        Integer newWithdrawTransactionId = this.savingsAccountHelper.updateSavingsAccountTransaction(savingsId, withdrawTransactionId,
+                WITHDRAW_AMOUNT_ADJUSTED);
+        HashMap newWithdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, newWithdrawTransactionId);
+        balance = balance + new Float(WITHDRAW_AMOUNT) - new Float(WITHDRAW_AMOUNT_ADJUSTED);
+        assertEquals("Verifying adjusted Amount", new Float(WITHDRAW_AMOUNT_ADJUSTED), newWithdrawTransaction.get("amount"));
+        assertEquals("Verifying Balance after adjust", balance, newWithdrawTransaction.get("runningBalance"));
+        summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        assertEquals("Verifying Adjusted Balance", balance, summary.get("accountBalance"));
+        withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
+        Assert.assertTrue((Boolean) withdrawTransaction.get("reversed"));
+
+        this.savingsAccountHelper.undoSavingsAccountTransaction(savingsId, newWithdrawTransactionId);
+        newWithdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
+        Assert.assertTrue((Boolean) newWithdrawTransaction.get("reversed"));
+        summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        balance += new Float(WITHDRAW_AMOUNT_ADJUSTED);
+        assertEquals("Verifying Balance After Undo Transaction", balance, summary.get("accountBalance"));
+
+        error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "5000",
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "5000",
+                SavingsAccountHelper.getFutureDate(), CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.in.the.future", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.depositToSavingsAccount(savingsId, "5000", SavingsAccountHelper.getFutureDate(),
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.in.the.future", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "5000",
+                SavingsAccountHelper.CREATED_DATE_MINUS_ONE, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.before.activation.date",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.depositToSavingsAccount(savingsId, "5000",
+                SavingsAccountHelper.CREATED_DATE_MINUS_ONE, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.before.activation.date",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccountCharges() {
+
+        final ResponseSpecification erroResponseSpec = new ResponseSpecBuilder().build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, erroResponseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final boolean allowOverdraft = false;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        Assert.assertNotNull(savingsProductID);
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        final Integer withdrawalChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsWithdrawalFeeJSON());
+        Assert.assertNotNull(withdrawalChargeId);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, withdrawalChargeId);
+        ArrayList<HashMap> chargesPendingState = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(1, chargesPendingState.size());
+
+        Integer savingsChargeId = (Integer) chargesPendingState.get(0).get("id");
+        HashMap chargeChanges = this.savingsAccountHelper.updateCharges(savingsChargeId, savingsId);
+        Assert.assertTrue(chargeChanges.containsKey("amount"));
+
+        Integer deletedChargeId = this.savingsAccountHelper.deleteCharge(savingsChargeId, savingsId);
+        assertEquals(savingsChargeId, deletedChargeId);
+
+        chargesPendingState = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertTrue(chargesPendingState == null || chargesPendingState.size() == 0);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        final Integer chargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec, ChargesHelper.getSavingsAnnualFeeJSON());
+        Assert.assertNotNull(chargeId);
+
+        ArrayList<HashMap> charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertTrue(charges == null || charges.size() == 0);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, chargeId);
+        charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(1, charges.size());
+
+        HashMap savingsChargeForPay = charges.get(0);
+        Integer annualSavingsChargeId = (Integer) savingsChargeForPay.get("id");
+
+        ArrayList<HashMap> savingsAccountErrorData = (ArrayList<HashMap>) validationErrorHelper.inactivateCharge(annualSavingsChargeId,
+                savingsId, CommonConstants.RESPONSE_ERROR);
+        assertEquals("validation.msg.savingsaccountcharge.inactivation.of.charge.not.allowed.when.charge.is.due", savingsAccountErrorData
+                .get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        SimpleDateFormat sdf = new SimpleDateFormat(CommonConstants.dateFormat, Locale.US);
+        Calendar cal = Calendar.getInstance();
+        List dates = (List) savingsChargeForPay.get("dueDate");
+        cal.set(Calendar.YEAR, (Integer) dates.get(0));
+        cal.set(Calendar.MONTH, (Integer) dates.get(1) - 1);
+        cal.set(Calendar.DAY_OF_MONTH, (Integer) dates.get(2));
+        int n = 0;
+        Calendar current = Calendar.getInstance();
+        while (cal.compareTo(current) < 0) {
+            n++;
+            cal.set(Calendar.YEAR, (Integer) dates.get(0) + n);
+        }
+        cal.set(Calendar.YEAR, (Integer) dates.get(0));
+        cal.set(Calendar.MONTH, (Integer) dates.get(1) - 1);
+        cal.set(Calendar.DAY_OF_MONTH, (Integer) dates.get(2));
+
+        for (int i = 1; i <= n; i++) {
+            this.savingsAccountHelper.payCharge((Integer) savingsChargeForPay.get("id"), savingsId,
+                    ((Float) savingsChargeForPay.get("amount")).toString(), sdf.format(cal.getTime()));
+            HashMap paidCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForPay.get("id"));
+            Float expectedValue = (Float) savingsChargeForPay.get("amount") * i;
+            assertEquals(expectedValue, paidCharge.get("amountPaid"));
+            cal.set(Calendar.YEAR, (Integer) dates.get(0) + i);
+        }
+
+        Integer inactivatedChargeId = (Integer) this.savingsAccountHelper.inactivateCharge(annualSavingsChargeId, savingsId,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+        assertEquals("Inactivated Savings Charges Id", annualSavingsChargeId, inactivatedChargeId);
+
+        final Integer monthlyFeechargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsMonthlyFeeJSON());
+        Assert.assertNotNull(monthlyFeechargeId);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, monthlyFeechargeId);
+        charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(2, charges.size());
+
+        HashMap savingsChargeForWaive = charges.get(1);
+        final Integer monthlySavingsCharge = (Integer) savingsChargeForWaive.get("id");
+
+        savingsAccountErrorData = (ArrayList<HashMap>) validationErrorHelper.inactivateCharge(monthlySavingsCharge, savingsId,
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("validation.msg.savingsaccountcharge.inactivation.of.charge.not.allowed.when.charge.is.due", savingsAccountErrorData
+                .get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        this.savingsAccountHelper.waiveCharge((Integer) savingsChargeForWaive.get("id"), savingsId);
+        HashMap waiveCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForWaive.get("id"));
+        assertEquals(savingsChargeForWaive.get("amount"), waiveCharge.get("amountWaived"));
+
+        this.savingsAccountHelper.waiveCharge((Integer) savingsChargeForWaive.get("id"), savingsId);
+        waiveCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForWaive.get("id"));
+        BigDecimal totalWaiveAmount = BigDecimal.valueOf(Double.valueOf((Float) savingsChargeForWaive.get("amount")));
+        totalWaiveAmount = totalWaiveAmount.add(totalWaiveAmount);
+        assertEquals(totalWaiveAmount.floatValue(), waiveCharge.get("amountWaived"));
+
+        final Integer weeklyFeeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsWeeklyFeeJSON());
+        Assert.assertNotNull(weeklyFeeId);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, weeklyFeeId);
+        charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(3, charges.size());
+
+        savingsChargeForPay = charges.get(2);
+        final Integer weeklySavingsFeeId = (Integer) savingsChargeForPay.get("id");
+
+        savingsAccountErrorData = (ArrayList<HashMap>) validationErrorHelper.inactivateCharge(weeklySavingsFeeId, savingsId,
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("validation.msg.savingsaccountcharge.inactivation.of.charge.not.allowed.when.charge.is.due", savingsAccountErrorData
+                .get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        cal = Calendar.getInstance();
+        dates = (List) savingsChargeForPay.get("dueDate");
+        cal.set(Calendar.YEAR, (Integer) dates.get(0));
+        cal.set(Calendar.MONTH, (Integer) dates.get(1) - 1);
+        cal.set(Calendar.DAY_OF_MONTH, (Integer) dates.get(2));
+
+        // Depositing huge amount as scheduler job deducts the fee amount
+        Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, "100000",
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(depositTransactionId);
+
+        this.savingsAccountHelper.payCharge((Integer) savingsChargeForPay.get("id"), savingsId,
+                ((Float) savingsChargeForPay.get("amount")).toString(), sdf.format(cal.getTime()));
+        HashMap paidCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForPay.get("id"));
+        assertEquals(savingsChargeForPay.get("amount"), paidCharge.get("amountPaid"));
+        List nextDueDates = (List) paidCharge.get("dueDate");
+        LocalDate nextDueDate = new LocalDate((Integer) nextDueDates.get(0), (Integer) nextDueDates.get(1), (Integer) nextDueDates.get(2));
+        LocalDate expectedNextDueDate = new LocalDate((Integer) dates.get(0), (Integer) dates.get(1), (Integer) dates.get(2))
+                .plusWeeks((Integer) paidCharge.get("feeInterval"));
+        assertEquals(expectedNextDueDate, nextDueDate);
+    }
+
+    /***
+     * Test case for overdraft account functionality. Open account with zero
+     * balance, perform transactions then post interest and verify posted
+     * interest
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccountWithOverdraft() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build();
+        final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse);
+
+        /***
+         * Create a client to apply for savings account (overdraft account).
+         */
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+        final String minBalanceForInterestCalculation = null;
+
+        /***
+         * Create savings product with zero opening balance and overdraft
+         * enabled
+         */
+        final String zeroOpeningBalance = "0.0";
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final boolean allowOverdraft = true;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, zeroOpeningBalance,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
+        Assert.assertNotNull(savingsProductID);
+
+        /***
+         * Apply for Savings account
+         */
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
+                ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertTrue(modifications.containsKey("submittedOnDate"));
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        /***
+         * Approve the savings account
+         */
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.set(Calendar.DAY_OF_MONTH, 1);
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final Integer lastDayOfMonth = todaysDate.getActualMaximum(Calendar.DAY_OF_MONTH);
+        todaysDate.set(Calendar.DAY_OF_MONTH, lastDayOfMonth);
+        final String TRANSACTION_DATE = dateFormat.format(todaysDate.getTime());
+
+        /***
+         * Activate the application and verify account status
+         * 
+         * @param activationDate
+         *            this value is every time first day of previous month
+         */
+        savingsStatusHashMap = activateSavingsAccount(savingsId, ACTIVATION_DATE);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        /***
+         * Verify the account summary
+         */
+        final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        this.savingsAccountHelper.calculateInterestForSavings(savingsId);
+        HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        assertEquals(summaryBefore, summary);
+
+        Float balance = Float.valueOf(zeroOpeningBalance);
+
+        /***
+         * Perform withdraw transaction, verify account balance(account balance
+         * will go to negative as no deposits are there prior to this
+         * transaction)
+         */
+        Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, WITHDRAW_AMOUNT,
+                ACTIVATION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        HashMap withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
+        balance -= new Float(WITHDRAW_AMOUNT);
+        assertEquals("Verifying Withdrawal Amount", new Float(WITHDRAW_AMOUNT), withdrawTransaction.get("amount"));
+        assertEquals("Verifying Balance after Withdrawal", balance, withdrawTransaction.get("runningBalance"));
+
+        /***
+         * Perform Deposit transaction on last day of month and verify account
+         * balance.
+         * 
+         * @param transactionDate
+         *            this value is every time last day of previous month
+         */
+        Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT,
+                TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        HashMap depositTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, depositTransactionId);
+        balance += new Float(DEPOSIT_AMOUNT);
+        assertEquals("Verifying Deposit Amount", new Float(DEPOSIT_AMOUNT), depositTransaction.get("amount"));
+        assertEquals("Verifying Balance after Deposit", balance, depositTransaction.get("runningBalance"));
+
+        /***
+         * Perform Post interest transaction and verify the posted amount
+         */
+        this.savingsAccountHelper.postInterestForSavings(savingsId);
+        HashMap accountDetails = this.savingsAccountHelper.getSavingsDetails(savingsId);
+        summary = (HashMap) accountDetails.get("summary");
+        Float actualInterestPosted = Float.valueOf(summary.get("totalInterestPosted").toString());
+
+        /***
+         * Calculate expected interest to be posted, interest should be posted
+         * for one day only because deposit transaction happened on last day of
+         * month before this account balance is negative.
+         */
+        final Float nominalAnnualInterest = Float.valueOf(accountDetails.get("nominalAnnualInterestRate").toString());
+        final HashMap interestCalculationDaysInYearType = (HashMap) accountDetails.get("interestCalculationDaysInYearType");
+        final Integer daysInYear = Integer.valueOf(interestCalculationDaysInYearType.get("id").toString());
+        double interestRateInFraction = (nominalAnnualInterest / 100);
+        double perDay = (double) 1 / (daysInYear);
+        double interestPerDay = interestRateInFraction * perDay;
+        Float interestPosted = (float) (interestPerDay * balance * 1);
+
+        /***
+         * Apply rounding on interestPosted, actualInterestPosted and verify
+         * both are same
+         */
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern("#.###");
+        interestPosted = new Float(decimalFormat.format(interestPosted));
+        actualInterestPosted = new Float(decimalFormat.format(actualInterestPosted));
+        assertEquals("Verifying interest posted", interestPosted, actualInterestPosted);
+
+        todaysDate = Calendar.getInstance();
+        final String CLOSEDON_DATE = dateFormat.format(todaysDate.getTime());
+        String withdrawBalance = "false";
+        ArrayList<HashMap> savingsAccountErrorData = (ArrayList<HashMap>) validationErrorHelper.closeSavingsAccountAndGetBackRequiredField(
+                savingsId, withdrawBalance, CommonConstants.RESPONSE_ERROR, CLOSEDON_DATE);
+        assertEquals("validation.msg.savingsaccount.close.results.in.balance.not.zero",
+                savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+    }
+
+    private HashMap activateSavingsAccount(final Integer savingsId, final String activationDate) {
+        final HashMap status = this.savingsAccountHelper.activateSavingsAccount(savingsId, activationDate);
+        return status;
+    }
+
+    private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String minOpenningBalance, String minBalanceForInterestCalculation, String minRequiredBalance,
+            String enforceMinRequiredBalance, final boolean allowOverdraft) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+        SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+        if (allowOverdraft) {
+            final String overDraftLimit = "2000.0";
+            savingsProductHelper = savingsProductHelper.withOverDraft(overDraftLimit);
+        }
+
+        final String savingsProductJSON = savingsProductHelper
+                //
+                .withInterestCompoundingPeriodTypeAsDaily()
+                //
+                .withInterestPostingPeriodTypeAsMonthly()
+                //
+                .withInterestCalculationPeriodTypeAsDailyBalance()
+                //
+                .withMinBalanceForInterestCalculation(minBalanceForInterestCalculation)
+                //
+                .withMinRequiredBalance(minRequiredBalance).withEnforceMinRequiredBalance(enforceMinRequiredBalance)
+                .withMinimumOpenningBalance(minOpenningBalance).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    /*
+     * private void verifySavingsInterest(final Object savingsInterest) {
+     * System.out.println(
+     * "--------------------VERIFYING THE BALANCE, INTEREST --------------------------"
+     * );
+     * 
+     * assertEquals("Verifying Interest Calculation", new Float("238.3399"),
+     * savingsInterest); }
+     */
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientStatusChecker.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientStatusChecker.java
new file mode 100644
index 0000000..12863bb
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientStatusChecker.java
@@ -0,0 +1,61 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+
+public class ClientStatusChecker {
+
+    public static void verifyClientIsActive(final HashMap<String, Object> clientStatusHashMap) {
+        assertEquals((int) clientStatusHashMap.get("id"), 300);
+    }
+
+    public static void verifyClientClosed(final HashMap<String, Object> clientStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING CLIENT IS CLOSED ------------------------------------");
+        assertEquals((int) clientStatusHashMap.get("id"), 600);
+        System.out.println("Client Status:" + clientStatusHashMap + "\n");
+    }
+
+    public static void verifyClientPending(final HashMap<String, Object> clientStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING CLIENT IS PENDING ------------------------------------");
+        assertEquals((int) clientStatusHashMap.get("id"), 100);
+        System.out.println("Client Status:" + clientStatusHashMap + "\n");
+    }
+
+    public static void verifyClientRejected(final HashMap<String, Object> clientStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING CLIENT IS REJECTED ------------------------------------");
+        assertEquals((int) clientStatusHashMap.get("id"), 700);
+        System.out.println("Client Status:" + clientStatusHashMap + "\n");
+    }
+
+    public static void verifyClientActiavted(final HashMap<String, Object> clientStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING CLIENT IS ACTIVATED ------------------------------------");
+        assertEquals((int) clientStatusHashMap.get("id"), 300);
+        System.out.println("Client Status:" + clientStatusHashMap + "\n");
+    }
+
+    public static void verifyClientWithdrawn(final HashMap<String, Object> clientStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING CLIENT IS WITHDRAWN ------------------------------------");
+        assertEquals((int) clientStatusHashMap.get("id"), 800);
+        System.out.println("Client Status:" + clientStatusHashMap + "\n");
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientTest.java
new file mode 100644
index 0000000..0d1b86e
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientTest.java
@@ -0,0 +1,148 @@
+/**
+ * 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.integrationtests;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class ClientTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private ClientHelper clientHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+    }
+
+    @Test
+    public void testClientStatus() {
+        this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        HashMap<String, Object> status = ClientHelper.getClientStatus(requestSpec, responseSpec, String.valueOf(clientId));
+        ClientStatusChecker.verifyClientIsActive(status);
+
+        HashMap<String, Object> clientStatusHashMap = this.clientHelper.closeClient(clientId);
+        ClientStatusChecker.verifyClientClosed(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.reactivateClient(clientId);
+        ClientStatusChecker.verifyClientPending(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.rejectClient(clientId);
+        ClientStatusChecker.verifyClientRejected(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.activateClient(clientId);
+        ClientStatusChecker.verifyClientActiavted(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.closeClient(clientId);
+        ClientStatusChecker.verifyClientClosed(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.reactivateClient(clientId);
+        ClientStatusChecker.verifyClientPending(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.withdrawClient(clientId);
+        ClientStatusChecker.verifyClientWithdrawn(clientStatusHashMap);
+
+    }
+    
+    @Test
+    public void testClientAsPersonStatus() {
+    	
+    	this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
+        final Integer clientId = ClientHelper.createClientAsPerson(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        HashMap<String, Object> status = ClientHelper.getClientStatus(requestSpec, responseSpec, String.valueOf(clientId));
+        ClientStatusChecker.verifyClientIsActive(status);
+
+        HashMap<String, Object> clientStatusHashMap = this.clientHelper.closeClient(clientId);
+        ClientStatusChecker.verifyClientClosed(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.reactivateClient(clientId);
+        ClientStatusChecker.verifyClientPending(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.rejectClient(clientId);
+        ClientStatusChecker.verifyClientRejected(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.activateClient(clientId);
+        ClientStatusChecker.verifyClientActiavted(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.closeClient(clientId);
+        ClientStatusChecker.verifyClientClosed(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.reactivateClient(clientId);
+        ClientStatusChecker.verifyClientPending(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.withdrawClient(clientId);
+        ClientStatusChecker.verifyClientWithdrawn(clientStatusHashMap);
+
+    }
+    
+    @Test
+    public void testClientAsEntityStatus() {
+    	
+    	this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
+        final Integer clientId = ClientHelper.createClientAsEntity(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        HashMap<String, Object> status = ClientHelper.getClientStatus(requestSpec, responseSpec, String.valueOf(clientId));
+        ClientStatusChecker.verifyClientIsActive(status);
+
+        HashMap<String, Object> clientStatusHashMap = this.clientHelper.closeClient(clientId);
+        ClientStatusChecker.verifyClientClosed(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.reactivateClient(clientId);
+        ClientStatusChecker.verifyClientPending(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.rejectClient(clientId);
+        ClientStatusChecker.verifyClientRejected(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.activateClient(clientId);
+        ClientStatusChecker.verifyClientActiavted(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.closeClient(clientId);
+        ClientStatusChecker.verifyClientClosed(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.reactivateClient(clientId);
+        ClientStatusChecker.verifyClientPending(clientStatusHashMap);
+
+        clientStatusHashMap = this.clientHelper.withdrawClient(clientId);
+        ClientStatusChecker.verifyClientWithdrawn(clientStatusHashMap);
+
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ConcurrencyIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ConcurrencyIntegrationTest.java
new file mode 100755
index 0000000..9548dc7
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ConcurrencyIntegrationTest.java
@@ -0,0 +1,165 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class ConcurrencyIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+
+    private static final String NO_ACCOUNTING = "1";
+
+    final int MYTHREADS = 30;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void verifyConcurrentLoanRepayments() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProduct(false, NO_ACCOUNTING);
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, "12,000.00");
+        this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+        this.loanTransactionHelper.disburseLoan("20 September 2011", loanID, "12,000.00");
+
+        ExecutorService executor = Executors.newFixedThreadPool(MYTHREADS);
+        Calendar date = Calendar.getInstance();
+        date.set(2011, 9, 20);
+        Float repaymentAmount = 100.0f;
+        for (int i = 0; i < 10; i++) {
+            System.out.println("Starting concurrent transaction number " + i);
+            date.add(Calendar.DAY_OF_MONTH, 1);
+            repaymentAmount = repaymentAmount + 100;
+            Runnable worker = new LoanRepaymentExecutor(loanTransactionHelper, loanID, repaymentAmount, date);
+            executor.execute(worker);
+        }
+
+        executor.shutdown();
+        // Wait until all threads are finish
+        while (!executor.isTerminated()) {
+
+        }
+        System.out.println("\nFinished all threads");
+
+    }
+
+    private Integer createLoanProduct(final boolean multiDisburseLoan, final String accountingRule, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        LoanProductTestBuilder builder = new LoanProductTestBuilder() //
+                .withPrincipal("12,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withTranches(multiDisburseLoan) //
+                .withAccounting(accountingRule, accounts);
+
+        if (multiDisburseLoan) {
+            builder = builder.withInterestCalculationPeriodTypeAsRepaymentPeriod(true);
+        }
+        final String loanProductJSON = builder.build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String principal) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2011") //
+                .withSubmittedOnDate("20 September 2011") //
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    public static class LoanRepaymentExecutor implements Runnable {
+
+        private final Integer loanId;
+        private final Float repaymentAmount;
+        private final String repaymentDate;
+        private final LoanTransactionHelper loanTransactionHelper;
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+
+        LoanRepaymentExecutor(LoanTransactionHelper loanTransactionHelper, Integer loanId, Float repaymentAmount, Calendar repaymentDate) {
+            this.loanId = loanId;
+            this.repaymentAmount = repaymentAmount;
+            this.repaymentDate = dateFormat.format(repaymentDate.getTime());
+            this.loanTransactionHelper = loanTransactionHelper;
+        }
+
+        @Override
+        public void run() {
+            try {
+                this.loanTransactionHelper.makeRepayment(repaymentDate, repaymentAmount, loanId);
+            } catch (Exception e) {
+                System.out.println("Found an exception" + e.getMessage());
+                System.out.println("Details of failed concurrent transaction (date, amount, loanId) are " + repaymentDate + ","
+                        + repaymentAmount + "," + loanId);
+                throw (e);
+            }
+            System.out.println("Details of passed concurrent transaction, details (date, amount, loanId) are " + repaymentDate + ","
+                    + repaymentAmount + "," + loanId);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/CurrenciesTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/CurrenciesTest.java
new file mode 100644
index 0000000..a418c4c
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/CurrenciesTest.java
@@ -0,0 +1,95 @@
+/**
+ * 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.integrationtests;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import org.apache.fineract.integrationtests.common.CurrenciesHelper;
+import org.apache.fineract.integrationtests.common.CurrencyDomain;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes" })
+public class CurrenciesTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void testCurrencyElements() {
+
+        CurrencyDomain currency = CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, "USD");
+        CurrencyDomain usd = CurrencyDomain.create("USD", "US Dollar", 2, "$", "currency.USD", "US Dollar ($)").build();
+
+        Assert.assertTrue(currency.getDecimalPlaces() >= 0);
+        Assert.assertNotNull(currency.getName());
+        Assert.assertNotNull(currency.getDisplaySymbol());
+        Assert.assertNotNull(currency.getDisplayLabel());
+        Assert.assertNotNull(currency.getNameCode());
+
+        Assert.assertEquals(usd, currency);
+    }
+
+    @Test
+    public void testUpdateCurrencySelection() {
+
+        // Test updation
+        ArrayList<String> currenciestoUpdate = new ArrayList<String>();
+        currenciestoUpdate.add("KES");
+        currenciestoUpdate.add("BND");
+        currenciestoUpdate.add("LBP");
+        currenciestoUpdate.add("GHC");
+        currenciestoUpdate.add("USD");
+
+        ArrayList<String> currenciesOutput = CurrenciesHelper.updateSelectedCurrencies(this.requestSpec, this.responseSpec,
+                currenciestoUpdate);
+        Assert.assertNotNull(currenciesOutput);
+
+        Assert.assertEquals("Verifying Do Outputed Currencies Match after Updation", currenciestoUpdate, currenciesOutput);
+
+        // Test that output matches updation
+        ArrayList<CurrencyDomain> currenciesBeforeUpdate = new ArrayList<CurrencyDomain>();
+        for (String e : currenciestoUpdate) {
+            currenciesBeforeUpdate.add(CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, e));
+        }
+        Collections.sort(currenciesBeforeUpdate);
+
+        ArrayList<CurrencyDomain> currenciesAfterUpdate = CurrenciesHelper.getSelectedCurrencies(requestSpec, responseSpec);
+        Assert.assertNotNull(currenciesAfterUpdate);
+
+        Assert.assertEquals("Verifying Do Selected Currencies Match after Updation", currenciesBeforeUpdate, currenciesAfterUpdate);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/CurrencyIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/CurrencyIntegrationTest.java
new file mode 100644
index 0000000..2468a40
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/CurrencyIntegrationTest.java
@@ -0,0 +1,45 @@
+/**
+ * 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.integrationtests;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.junit.Before;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class CurrencyIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsAccountHelper savingsAccountHelper;
+
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/DisbursalAndRepaymentScheduleTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/DisbursalAndRepaymentScheduleTest.java
new file mode 100644
index 0000000..611a9ee
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/DisbursalAndRepaymentScheduleTest.java
@@ -0,0 +1,368 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.*;
+import org.apache.fineract.integrationtests.common.loans.*;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Tests loan schedule change based on group meeting changes and loan
+ * rescheduling
+ **/
+@SuppressWarnings({ "rawtypes" })
+@Ignore
+public class DisbursalAndRepaymentScheduleTest {
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpecForStatusCode403;
+    private ResponseSpecification generalResponseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private LoanRescheduleRequestHelper loanRescheduleRequestHelper;
+    private Integer loanRescheduleRequestId;
+    private Integer clientId;
+    private Integer groupId;
+    private Integer groupCalendarId;
+    private Integer loanProductId;
+    private Integer loanId;
+    private final String loanPrincipalAmount = "100000.00";
+    private final String numberOfRepayments = "12";
+    private final String interestRatePerPeriod = "18";
+
+    private final SimpleDateFormat dateFormatterStandard = new SimpleDateFormat("dd MMMM yyyy");
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+    }
+
+    @Test
+    public void testRescheduleJLGLoanSynk() {
+        System.out.println("---------------------------------STARTING RESCHEDULE JLG LOAN TEST ------------------------------------------");
+
+        Calendar meetingCalendar = Calendar.getInstance();
+        meetingCalendar.setFirstDayOfWeek(Calendar.MONDAY);
+        meetingCalendar.setTime(new java.util.Date());
+
+        int today = meetingCalendar.get(Calendar.DAY_OF_WEEK);
+        // making sure that the meeting calendar is set for the coming monday.
+        if (today >= Calendar.MONDAY) {
+            meetingCalendar.add(Calendar.DAY_OF_YEAR, +(Calendar.MONDAY - today + 7));
+        } else {
+            meetingCalendar.add(Calendar.DAY_OF_YEAR, +(Calendar.MONDAY - today));
+        }
+
+        Calendar groupMeetingChangeCalendar = (Calendar) meetingCalendar.clone();
+
+        meetingCalendar.add(Calendar.WEEK_OF_YEAR, -3);
+
+        final String groupMeetingDate = this.dateFormatterStandard.format(meetingCalendar.getTime());
+
+        final String disbursalDate = groupMeetingDate; // first meeting date
+        // after group creation
+
+        final String rescheduleSubmittedDate = this.dateFormatterStandard.format(new java.util.Date());
+
+        final String loanType = "jlg";
+        final String rescheduleInterestRate = "28.0";
+        groupMeetingChangeCalendar.add(Calendar.DAY_OF_YEAR, 1);
+        final String groupMeetingNewStartDate = this.dateFormatterStandard.format(groupMeetingChangeCalendar.getTime());
+        // The date
+        // from
+        // which we
+        // start the
+        // new group
+        // meeting
+        // occasion,
+        // this is a
+        // tuesday.
+        groupMeetingChangeCalendar.add(Calendar.WEEK_OF_YEAR, 2);
+        final String rescheduleDate = this.dateFormatterStandard.format(groupMeetingChangeCalendar.getTime());
+
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.requestSpec.header("Fineract-Platform-TenantId", "default");
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.generalResponseSpec = new ResponseSpecBuilder().build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        this.loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, this.responseSpec);
+        System.out.println("---------------------------------CREATING ENTITIES AND JLG LOAN ------------------------------------------");
+        // create all required entities
+        this.createRequiredEntitiesForJLGLoanSync(groupMeetingDate);
+
+        final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount).withLoanTermFrequency("24")
+                .withLoanTermFrequencyAsWeeks().withNumberOfRepayments("12").withRepaymentEveryAfter("2")
+                .withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsDays()
+                .withInterestRatePerPeriod(interestRatePerPeriod).withRepaymentFrequencyTypeAsWeeks().withSubmittedOnDate(disbursalDate)
+                .withExpectedDisbursementDate(disbursalDate).withLoanType(loanType).withSyncDisbursementWithMeetin()
+                .withCalendarID(this.groupCalendarId.toString())
+                .build(this.clientId.toString(), this.groupId.toString(), this.loanProductId.toString(), null);
+
+        this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+
+        // Test for loan account is created
+        Assert.assertNotNull(this.loanId);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
+
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        // Test for loan account is created, can be approved
+        this.loanTransactionHelper.approveLoan(disbursalDate, this.loanId);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        // Test for loan account approved can be disbursed
+        this.loanTransactionHelper.disburseLoan(disbursalDate, this.loanId);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        System.out.println("---------------------------------CHANGING GROUP MEETING DATE ------------------------------------------");
+        CalendarHelper.updateMeetingCalendarForGroup(this.requestSpec, this.responseSpec, this.groupId, this.groupCalendarId.toString(),
+                groupMeetingNewStartDate, "2", "2", "2"); // New meeting dates
+                                                          // will be the tuesday
+                                                          // after the
+        // coming
+        // monday
+
+        ArrayList loanRepaymnetSchedule = this.loanTransactionHelper
+                .getLoanRepaymentSchedule(requestSpec, generalResponseSpec, this.loanId);
+
+        ArrayList dueDateLoanSchedule = (ArrayList) ((HashMap) loanRepaymnetSchedule.get(2)).get("dueDate");
+        Calendar dueDateCalendar = Calendar.getInstance();
+        dueDateCalendar.setFirstDayOfWeek(Calendar.MONDAY);
+        dueDateCalendar.set((Integer) dueDateLoanSchedule.get(0), (Integer) dueDateLoanSchedule.get(1) - 1,
+                (Integer) dueDateLoanSchedule.get(2));
+        assertEquals("AFTER MEETING CHANGE DATE THE NEXT REPAYMENT SHOULD BE ON TUESDAY", 3, dueDateCalendar.get(Calendar.DAY_OF_WEEK));
+
+        System.out.println("---------------------------------CREATING LOAN RESCHEDULE REQUEST------------------------------------------");
+
+        String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnInterest("2").updateGraceOnPrincipal("2")
+                .updateNewInterestRate(rescheduleInterestRate).updateRescheduleFromDate(rescheduleDate)
+                .updateSubmittedOnDate(rescheduleSubmittedDate).build(this.loanId.toString());
+
+        this.loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
+        this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId);
+
+        loanRepaymnetSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, generalResponseSpec, this.loanId);
+        dueDateLoanSchedule = (ArrayList) ((HashMap) loanRepaymnetSchedule.get(2)).get("dueDate");
+        dueDateCalendar.set((Integer) dueDateLoanSchedule.get(0), (Integer) dueDateLoanSchedule.get(1) - 1,
+                (Integer) dueDateLoanSchedule.get(2));
+        assertEquals("AFTER MEETING CHANGE DATE THE NEXT REPAYMENT SHOULD BE ON TUESDAY, EVEN AFTER LOAN RESCHEDULE REQUEST WAS SENT", 3,
+                dueDateCalendar.get(Calendar.DAY_OF_WEEK));
+
+        System.out.println("Successfully created loan reschedule request (ID: " + this.loanRescheduleRequestId + ")");
+
+        System.out.println("-----------------------------APPROVING LOAN RESCHEDULE REQUEST--------------------------");
+
+        requestJSON = new LoanRescheduleRequestTestBuilder().updateSubmittedOnDate(rescheduleSubmittedDate)
+                .getApproveLoanRescheduleRequestJSON();
+        this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId, requestJSON);
+
+        final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum");
+        assertTrue((Boolean) response.get("approved"));
+
+        loanRepaymnetSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, generalResponseSpec, this.loanId);
+
+        dueDateLoanSchedule = (ArrayList) ((HashMap) loanRepaymnetSchedule.get(2)).get("dueDate");
+        dueDateCalendar.set((Integer) dueDateLoanSchedule.get(0), (Integer) dueDateLoanSchedule.get(1) - 1,
+                (Integer) dueDateLoanSchedule.get(2));
+        assertEquals("AFTER MEETING CHANGE DATE THE NEXT REPAYMENT SHOULD BE ON TUESDAY, EVEN AFTER RESCHEDULE", 3,
+                dueDateCalendar.get(Calendar.DAY_OF_WEEK));
+        System.out.println("Successfully changed group meeting date (CAELNDAR ID: " + this.groupCalendarId
+                + ") and rescheduled loan (RESCHEDULE ID: " + this.loanRescheduleRequestId + ")");
+
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode403);
+    }
+
+    @Test
+    public void testChangeGroupMeetingMaturedOnDate() {
+        System.out
+                .println("---------------------------------STARTING GROUP LOAN MEETING CHANGE DATE EXPECTED MATURED CHANGE------------------------------------------");
+
+        Calendar meetingCalendar = Calendar.getInstance();
+        meetingCalendar.setFirstDayOfWeek(Calendar.MONDAY);
+        meetingCalendar.setTime(new java.util.Date());
+
+        int today = meetingCalendar.get(Calendar.DAY_OF_WEEK);
+        // making sure that the meeting calendar is set for the coming monday.
+        if (today >= Calendar.MONDAY) {
+            meetingCalendar.add(Calendar.DAY_OF_YEAR, +(Calendar.MONDAY - today + 7));
+        } else {
+            meetingCalendar.add(Calendar.DAY_OF_YEAR, +(Calendar.MONDAY - today));
+        }
+
+        Calendar groupMeetingChangeCalendar = (Calendar) meetingCalendar.clone();
+
+        meetingCalendar.add(Calendar.WEEK_OF_YEAR, -3);
+
+        final String groupMeetingDate = this.dateFormatterStandard.format(meetingCalendar.getTime());
+
+        final String disbursalDate = groupMeetingDate; // first meeting date
+                                                       // after group creation
+
+        final String loanType = "jlg";
+        groupMeetingChangeCalendar.add(Calendar.DAY_OF_YEAR, 1);
+        final String groupMeetingNewStartDate = this.dateFormatterStandard.format(groupMeetingChangeCalendar.getTime());
+        // The date
+        // from
+        // which we
+        // start the
+        // new group
+        // meeting
+        // occasion,
+        // this is a
+        // tuesday.
+
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.requestSpec.header("Fineract-Platform-TenantId", "default");
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.generalResponseSpec = new ResponseSpecBuilder().build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        this.loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, this.responseSpec);
+        System.out.println("---------------------------------CREATING ENTITIES AND JLG LOAN ------------------------------------------");
+        // create all required entities
+        this.createRequiredEntitiesForJLGLoanSync(groupMeetingDate);
+
+        final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount).withLoanTermFrequency("24")
+                .withLoanTermFrequencyAsWeeks().withNumberOfRepayments("12").withRepaymentEveryAfter("2")
+                .withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsDays()
+                .withInterestRatePerPeriod(interestRatePerPeriod).withRepaymentFrequencyTypeAsWeeks().withSubmittedOnDate(disbursalDate)
+                .withExpectedDisbursementDate(disbursalDate).withLoanType(loanType).withSyncDisbursementWithMeetin()
+                .withCalendarID(this.groupCalendarId.toString())
+                .build(this.clientId.toString(), this.groupId.toString(), this.loanProductId.toString(), null);
+
+        this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+
+        // Test for loan account is created
+        Assert.assertNotNull(this.loanId);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
+
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        // Test for loan account is created, can be approved
+        this.loanTransactionHelper.approveLoan(disbursalDate, this.loanId);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        // Test for loan account approved can be disbursed
+        this.loanTransactionHelper.disburseLoan(disbursalDate, this.loanId);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        System.out.println("---------------------------------CHANGING GROUP MEETING DATE ------------------------------------------");
+        CalendarHelper.updateMeetingCalendarForGroup(this.requestSpec, this.responseSpec, this.groupId, this.groupCalendarId.toString(),
+                groupMeetingNewStartDate, "2", "2", "2"); // New meeting dates
+                                                          // will be the tuesday
+                                                          // after the
+                                                          // coming
+                                                          // monday
+
+        Calendar expectedMaturityCalendar = Calendar.getInstance();
+        expectedMaturityCalendar.setFirstDayOfWeek(Calendar.MONDAY);
+        ArrayList expectedMaturityDate = ((ArrayList) ((HashMap) this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec,
+                this.loanId, "timeline")).get("expectedMaturityDate"));
+
+        expectedMaturityCalendar.set((Integer) expectedMaturityDate.get(0), (Integer) expectedMaturityDate.get(1) - 1,
+                (Integer) expectedMaturityDate.get(2));
+
+        assertEquals("AFTER MEETING CHANGE DATE THE EXPECTED MATURITY SHOULD BE ON TUESDAY", 3,
+                expectedMaturityCalendar.get(Calendar.DAY_OF_WEEK));
+
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode403);
+    }
+
+    /**
+     * entities for jlg loan
+     **/
+    private void createRequiredEntitiesForJLGLoanSync(final String groupActivationDate) {
+        this.createGroupEntityWithCalendar("2", "2", "1", groupActivationDate);// frequency=2:Weekly
+        // , interval=2:
+        // Every two weeks ,
+        // repeatsOnDay=1:Monday
+        // groupActivationDate is decided by the current date
+        this.createClientEntity();
+        this.associateClientToGroup(this.groupId, this.clientId);
+        this.createLoanProductEntity();
+
+    }
+
+    /*
+     * Associate client to the group
+     */
+
+    private void associateClientToGroup(final Integer groupId, final Integer clientId) {
+        GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupId.toString(), clientId.toString());
+        GroupHelper.verifyGroupMembers(this.requestSpec, this.responseSpec, groupId, clientId);
+    }
+
+    private void createGroupEntityWithCalendar(final String frequency, final String interval, final String repeatsOnDay,
+            final String groupActivationDate) {
+        this.groupId = GroupHelper.createGroup(this.requestSpec, this.responseSpec, groupActivationDate);
+        GroupHelper.verifyGroupCreatedOnServer(this.requestSpec, this.responseSpec, this.groupId);
+
+        final String startDate = groupActivationDate;
+
+        this.setGroupCalendarId(CalendarHelper.createMeetingCalendarForGroup(this.requestSpec, this.responseSpec, this.groupId, startDate,
+                frequency, interval, repeatsOnDay));
+    }
+
+    /**
+     * create a new client
+     **/
+    private void createClientEntity() {
+        this.clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, this.clientId);
+    }
+
+    /**
+     * create a new loan product
+     **/
+    private void createLoanProductEntity() {
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount)
+                .withNumberOfRepayments(numberOfRepayments).withinterestRatePerPeriod(interestRatePerPeriod)
+                .withInterestRateFrequencyTypeAsYear().build(null);
+        this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    public void setGroupCalendarId(Integer groupCalendarId) {
+        this.groupCalendarId = groupCalendarId;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ExternalServicesConfigurationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ExternalServicesConfigurationTest.java
new file mode 100644
index 0000000..3abbcee
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ExternalServicesConfigurationTest.java
@@ -0,0 +1,115 @@
+/**
+ * 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.integrationtests;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.ExternalServicesConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked", "static-access" })
+public class ExternalServicesConfigurationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private ExternalServicesConfigurationHelper externalServicesConfigurationHelper;
+    private ResponseSpecification httpStatusForidden;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.httpStatusForidden = new ResponseSpecBuilder().expectStatusCode(403).build();
+
+    }
+
+    @Test
+    public void testExternalServicesConfiguration() {
+        this.externalServicesConfigurationHelper = new ExternalServicesConfigurationHelper(this.requestSpec, this.responseSpec);
+
+        // Checking for S3
+        String configName = "s3_access_key";
+        ArrayList<HashMap> externalServicesConfig = this.externalServicesConfigurationHelper
+                .getExternalServicesConfigurationByServiceName(requestSpec, responseSpec, "S3");
+        Assert.assertNotNull(externalServicesConfig);
+        for (Integer configIndex = 0; configIndex < (externalServicesConfig.size()); configIndex++) {
+            String name = (String) externalServicesConfig.get(configIndex).get("name");
+            String value = null;
+            if (name.equals(configName)) {
+                value = (String) externalServicesConfig.get(configIndex).get("value");
+                if(value == null){
+                    value = "testnull";
+                }
+                String newValue = "test";
+                System.out.println(name + ":" + value);
+                HashMap arrayListValue = this.externalServicesConfigurationHelper.updateValueForExternaServicesConfiguration(requestSpec,
+                        responseSpec, "S3", name, newValue);
+                Assert.assertNotNull(arrayListValue.get("value"));
+                Assert.assertEquals(arrayListValue.get("value"), newValue);
+                HashMap arrayListValue1 = this.externalServicesConfigurationHelper.updateValueForExternaServicesConfiguration(requestSpec,
+                        responseSpec, "S3", name, value);
+                Assert.assertNotNull(arrayListValue1.get("value"));
+                Assert.assertEquals(arrayListValue1.get("value"), value);
+            }
+
+        }
+
+        // Checking for SMTP:
+
+        configName = "username";
+        externalServicesConfig = this.externalServicesConfigurationHelper.getExternalServicesConfigurationByServiceName(requestSpec,
+                responseSpec, "SMTP");
+        Assert.assertNotNull(externalServicesConfig);
+        for (Integer configIndex = 0; configIndex < (externalServicesConfig.size()); configIndex++) {
+            String name = (String) externalServicesConfig.get(configIndex).get("name");
+            String value = null;
+            if (name.equals(configName)) {
+                value = (String) externalServicesConfig.get(configIndex).get("value");
+                if(value == null){
+                    value = "testnull";
+                }
+                String newValue = "test";
+                System.out.println(name + ":" + value);
+                HashMap arrayListValue = this.externalServicesConfigurationHelper.updateValueForExternaServicesConfiguration(requestSpec,
+                        responseSpec, "SMTP", name, newValue);
+                Assert.assertNotNull(arrayListValue.get("value"));
+                Assert.assertEquals(arrayListValue.get("value"), newValue);
+                HashMap arrayListValue1 = this.externalServicesConfigurationHelper.updateValueForExternaServicesConfiguration(requestSpec,
+                        responseSpec, "SMTP", name, value);
+                Assert.assertNotNull(arrayListValue1.get("value"));
+                Assert.assertEquals(arrayListValue1.get("value"), value);
+            }
+
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FinancialActivityAccountsTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FinancialActivityAccountsTest.java
new file mode 100755
index 0000000..ef2183f
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FinancialActivityAccountsTest.java
@@ -0,0 +1,164 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.financialactivityaccount.exception.DuplicateFinancialActivityAccountFoundException;
+import org.apache.fineract.accounting.financialactivityaccount.exception.FinancialActivityAccountInvalidException;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper;
+import org.junit.Assert;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class FinancialActivityAccountsTest {
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpecForValidationError;
+    private ResponseSpecification responseSpecForDomainRuleViolation;
+    private ResponseSpecification responseSpecForResourceNotFoundError;
+    private RequestSpecification requestSpec;
+    private AccountHelper accountHelper;
+    private FinancialActivityAccountHelper financialActivityAccountHelper;
+    private final Integer assetTransferFinancialActivityId = FINANCIAL_ACTIVITY.ASSET_TRANSFER.getValue();
+    public static final Integer liabilityTransferFinancialActivityId = FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue();
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpecForValidationError = new ResponseSpecBuilder().expectStatusCode(400).build();
+        this.responseSpecForDomainRuleViolation = new ResponseSpecBuilder().expectStatusCode(403).build();
+        this.responseSpecForResourceNotFoundError = new ResponseSpecBuilder().expectStatusCode(404).build();
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.financialActivityAccountHelper = new FinancialActivityAccountHelper(this.requestSpec);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testFinancialActivityAccounts() {
+
+        /** Create a Liability and an Asset Transfer Account **/
+        Account liabilityTransferAccount = accountHelper.createLiabilityAccount();
+        Account assetTransferAccount = accountHelper.createAssetAccount();
+        Assert.assertNotNull(assetTransferAccount);
+        Assert.assertNotNull(liabilityTransferAccount);
+
+        /*** Create A Financial Activity to Account Mapping **/
+        Integer financialActivityAccountId = (Integer) financialActivityAccountHelper.createFinancialActivityAccount(
+                liabilityTransferFinancialActivityId, liabilityTransferAccount.getAccountID(), responseSpec,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(financialActivityAccountId);
+
+        /***
+         * Fetch Created Financial Activity to Account Mapping and validate
+         * created values
+         **/
+        assertFinancialActivityAccountCreation(financialActivityAccountId, liabilityTransferFinancialActivityId, liabilityTransferAccount);
+
+        /**
+         * Update Existing Financial Activity to Account Mapping and assert
+         * changes
+         **/
+        Account newLiabilityTransferAccount = accountHelper.createLiabilityAccount();
+        Assert.assertNotNull(newLiabilityTransferAccount);
+
+        HashMap changes = (HashMap) financialActivityAccountHelper.updateFinancialActivityAccount(financialActivityAccountId,
+                liabilityTransferFinancialActivityId, newLiabilityTransferAccount.getAccountID(), responseSpec,
+                CommonConstants.RESPONSE_CHANGES);
+        Assert.assertEquals(newLiabilityTransferAccount.getAccountID(), changes.get("glAccountId"));
+
+        /** Validate update works correctly **/
+        assertFinancialActivityAccountCreation(financialActivityAccountId, liabilityTransferFinancialActivityId,
+                newLiabilityTransferAccount);
+
+        /** Update with Invalid Financial Activity should fail **/
+        List<HashMap> invalidFinancialActivityUpdateError = (List<HashMap>) financialActivityAccountHelper.updateFinancialActivityAccount(
+                financialActivityAccountId, 232, newLiabilityTransferAccount.getAccountID(), responseSpecForValidationError,
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("validation.msg.financialactivityaccount.financialActivityId.is.not.one.of.expected.enumerations",
+                invalidFinancialActivityUpdateError.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        /** Creating Duplicate Financial Activity should fail **/
+        List<HashMap> duplicateFinancialActivityAccountError = (List<HashMap>) financialActivityAccountHelper
+                .createFinancialActivityAccount(liabilityTransferFinancialActivityId, liabilityTransferAccount.getAccountID(),
+                        responseSpecForDomainRuleViolation, CommonConstants.RESPONSE_ERROR);
+        assertEquals(DuplicateFinancialActivityAccountFoundException.getErrorcode(),
+                duplicateFinancialActivityAccountError.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        /**
+         * Associating incorrect GL account types with a financial activity
+         * should fail
+         **/
+        List<HashMap> invalidFinancialActivityAccountError = (List<HashMap>) financialActivityAccountHelper.updateFinancialActivityAccount(
+                financialActivityAccountId, assetTransferFinancialActivityId, newLiabilityTransferAccount.getAccountID(),
+                responseSpecForDomainRuleViolation, CommonConstants.RESPONSE_ERROR);
+        assertEquals(FinancialActivityAccountInvalidException.getErrorcode(),
+                invalidFinancialActivityAccountError.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        /** Should be able to delete a Financial Activity to Account Mapping **/
+        Integer deletedFinancialActivityAccountId = financialActivityAccountHelper.deleteFinancialActivityAccount(
+                financialActivityAccountId, responseSpec, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(deletedFinancialActivityAccountId);
+        Assert.assertEquals(financialActivityAccountId, deletedFinancialActivityAccountId);
+
+        /*** Trying to fetch a Deleted Account Mapping should give me a 404 **/
+        financialActivityAccountHelper.getFinancialActivityAccount(deletedFinancialActivityAccountId, responseSpecForResourceNotFoundError);
+    }
+
+    private void assertFinancialActivityAccountCreation(Integer financialActivityAccountId, Integer financialActivityId, Account glAccount) {
+        HashMap mappingDetails = financialActivityAccountHelper.getFinancialActivityAccount(financialActivityAccountId, responseSpec);
+        Assert.assertEquals(financialActivityId, ((HashMap) mappingDetails.get("financialActivityData")).get("id"));
+        Assert.assertEquals(glAccount.getAccountID(), ((HashMap) mappingDetails.get("glAccountData")).get("id"));
+    }
+
+    /**
+     * Delete the Financial activities
+     */
+    @After
+    public void tearDown() {
+        List<HashMap> financialActivities = this.financialActivityAccountHelper.getAllFinancialActivityAccounts(this.responseSpec);
+        for (HashMap financialActivity : financialActivities) {
+            Integer financialActivityAccountId = (Integer) financialActivity.get("id");
+            Integer deletedFinancialActivityAccountId = this.financialActivityAccountHelper.deleteFinancialActivityAccount(
+                    financialActivityAccountId, this.responseSpec, CommonConstants.RESPONSE_RESOURCE_ID);
+            Assert.assertNotNull(deletedFinancialActivityAccountId);
+            Assert.assertEquals(financialActivityAccountId, deletedFinancialActivityAccountId);
+        }
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FixedDepositTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FixedDepositTest.java
new file mode 100644
index 0000000..3d6a4ef
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FixedDepositTest.java
@@ -0,0 +1,2079 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import org.apache.fineract.integrationtests.common.accounting.Account.AccountType;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositAccountHelper;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositAccountStatusChecker;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositProductHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.joda.time.DateTime;
+import org.joda.time.Days;
+import org.joda.time.Months;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "unchecked", "rawtypes", "static-access" })
+public class FixedDepositTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private FixedDepositProductHelper fixedDepositProductHelper;
+    private FixedDepositAccountHelper fixedDepositAccountHelper;
+    private AccountHelper accountHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+    private JournalEntryHelper journalEntryHelper;
+    private FinancialActivityAccountHelper financialActivityAccountHelper;
+
+    public static final String WHOLE_TERM = "1";
+    public static final String TILL_PREMATURE_WITHDRAWAL = "2";
+    private static final String DAILY = "1";
+    private static final String MONTHLY = "4";
+    private static final String QUARTERLY = "5";
+    private static final String BI_ANNUALLY = "6";
+    private static final String ANNUALLY = "7";
+    private static final String INTEREST_CALCULATION_USING_DAILY_BALANCE = "1";
+    private static final String INTEREST_CALCULATION_USING_AVERAGE_DAILY_BALANCE = "2";
+    private static final String DAYS_360 = "360";
+    private static final String DAYS_365 = "365";
+
+    private static final String NONE = "1";
+    private static final String CASH_BASED = "2";
+
+    public static final String MINIMUM_OPENING_BALANCE = "1000.0";
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+    public static final String CLOSURE_TYPE_WITHDRAW_DEPOSIT = "100";
+    public static final String CLOSURE_TYPE_TRANSFER_TO_SAVINGS = "200";
+    public static final String CLOSURE_TYPE_REINVEST = "300";
+    public static final Integer DAILY_COMPOUNDING_INTERVAL = 0;
+    public static final Integer MONTHLY_INTERVAL = 1;
+    public static final Integer QUARTERLY_INTERVAL = 3;
+    public static final Integer BIANNULLY_INTERVAL = 6;
+    public static final Integer ANNUL_INTERVAL = 12;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.requestSpec.header("Fineract-Platform-TenantId", "default");
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.financialActivityAccountHelper = new FinancialActivityAccountHelper(this.requestSpec);
+    }
+
+    /***
+     * Test case for Fixed Deposit Account premature closure with
+     * transaction type withdrawal and Cash Based accounting enabled
+     */
+    @Test
+    public void testFixedDepositAccountWithPrematureClosureTypeWithdrawal() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+        
+        /***
+         * Create GL Accounts for product account mapping
+         */
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1;
+        todaysDate.add(Calendar.DATE, numberOfDaysLeft);
+        final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime());
+        final String CLOSED_ON_DATE = dateFormat.format(Calendar.getInstance().getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        /***
+         * Create FD product with CashBased accounting enabled
+         */
+        final String accountingRule = CASH_BASED;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount, liabilityAccount,
+                incomeAccount, expenseAccount);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        /***
+         * Apply for FD account with created product and verify status
+         */
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        /***
+         * Approve the FD account and verify whether account is approved
+         */
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        /***
+         * Activate the FD Account and verify whether account is activated
+         */
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+        
+        HashMap accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
+
+        Float depositAmount = (Float) accountSummary.get("totalDeposits");
+        
+        /***
+         * Verify journal entries posted for initial deposit transaction which
+         * happened at activation time
+         */
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, ACTIVATION_DATE, assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, ACTIVATION_DATE, liablilityAccountInitialEntry);
+
+        /***
+         * Update interest earned of FD account
+         */
+        fixedDepositAccountId = this.fixedDepositAccountHelper.calculateInterestForFixedDeposit(fixedDepositAccountId);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        /***
+         * Post interest and verify the account summary
+         */
+        Integer transactionIdForPostInterest = this.fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId);
+        Assert.assertNotNull(transactionIdForPostInterest);
+        
+        accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
+        Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted");
+
+        /***
+         * Verify journal entries transactions for interest posting transaction
+         */
+        final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry);
+
+        /***
+         * Preclose the FD account verify whether account is preClosed
+         */
+        HashMap fixedDepositPrematureData = this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId,
+                CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit(
+                fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap);
+
+        /***
+         * Verify journal entry transactions for preclosure transaction
+         */
+        HashMap accountDetails = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float maturityAmount = Float.valueOf(accountDetails.get("maturityAmount").toString());
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount,
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount,
+                JournalEntry.TransactionType.DEBIT));
+
+    }
+
+    /***
+     * Test case for FD Account premature closure with transaction transfers to
+     * savings account and Cash Based accounting enabled
+     */
+    @Test
+    public void testFixedDepositAccountWithPrematureClosureTypeTransferToSavings() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+        
+        /***
+         * Create GL Accounts for product account mapping
+         */
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1;
+        todaysDate.add(Calendar.DATE, numberOfDaysLeft);
+        final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime());
+        final String CLOSED_ON_DATE = dateFormat.format(Calendar.getInstance().getTime());
+        
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        /***
+         * Create Savings product with CashBased accounting enabled
+         */
+        final String accountingRule = CASH_BASED;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, accountingRule,
+                assetAccount, liabilityAccount, incomeAccount, expenseAccount);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientId, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        /***
+         * Create FD product with CashBased accounting enabled
+         */
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount, liabilityAccount,
+                incomeAccount, expenseAccount);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+
+        HashMap accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
+
+        Float depositAmount = (Float) accountSummary.get("totalDeposits");
+        
+        /***
+         * Verify journal entries posted for initial deposit transaction which
+         * happened at activation time
+         */
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, ACTIVATION_DATE, assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, ACTIVATION_DATE, liablilityAccountInitialEntry);
+
+        /***
+         * Update interest earned of FD account
+         */
+        fixedDepositAccountId = this.fixedDepositAccountHelper.calculateInterestForFixedDeposit(fixedDepositAccountId);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        /***
+         * Post interest and verify the account summary
+         */
+        Integer transactionIdForPostInterest = this.fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId);
+        Assert.assertNotNull(transactionIdForPostInterest);
+
+        accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
+        Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted");
+
+        /***
+         * Verify journal entries transactions for interest posting transaction
+         */
+        final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry);
+        
+        HashMap savingsSummaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Float balanceBefore = (Float) savingsSummaryBefore.get("accountBalance");
+        
+        /***
+         * Retrieve mapped financial account for liability transfer
+         */
+        Account financialAccount = getMappedLiabilityFinancialAccount();
+
+        HashMap fixedDepositPrematureData = this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId,
+                CLOSED_ON_DATE);
+
+        /***
+         * Preclose the account and verify journal entries
+         */
+        Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit(
+                fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_TRANSFER_TO_SAVINGS, savingsId, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float prematurityAmount = (Float) fixedDepositData.get("maturityAmount");
+
+        /***
+         * Verify journal entry transactions for preclosure transaction As this
+         * transaction is an account transfer you should get financial account
+         * mapping details and verify amounts
+         */
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, CLOSED_ON_DATE, new JournalEntry(prematurityAmount,
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(prematurityAmount, JournalEntry.TransactionType.DEBIT));
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(financialAccount, CLOSED_ON_DATE, new JournalEntry(prematurityAmount,
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(prematurityAmount, JournalEntry.TransactionType.CREDIT));
+        
+        HashMap savingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Float balanceAfter = (Float) savingsSummaryAfter.get("accountBalance");
+        Float expectedSavingsBalance = balanceBefore + prematurityAmount;
+
+        Assert.assertEquals("Verifying Savings Account Balance after Premature Closure", expectedSavingsBalance, balanceAfter);
+
+    }
+
+    /***
+     * Test case for Fixed Deposit Account premature closure with
+     * transaction type ReInvest and Cash Based accounting enabled
+     */
+    @Test
+    public void testFixedDepositAccountWithPrematureClosureTypeReinvest() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        FixedDepositAccountHelper fixedDepositAccountHelperValidationError = new FixedDepositAccountHelper(this.requestSpec,
+                new ResponseSpecBuilder().build());
+
+        /***
+         * Create GL Accounts for product account mapping
+         */
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1;
+        todaysDate.add(Calendar.DATE, numberOfDaysLeft);
+        final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime());
+        final String CLOSED_ON_DATE = dateFormat.format(Calendar.getInstance().getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        /***
+         * Create FD product with CashBased accounting enabled
+         */
+        final String accountingRule = CASH_BASED;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount, liabilityAccount,
+                incomeAccount, expenseAccount);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+        
+        HashMap accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
+
+        Float depositAmount = (Float) accountSummary.get("totalDeposits");
+        
+        /***
+         * Verify journal entries posted for initial deposit transaction which
+         * happened at activation time
+         */
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, ACTIVATION_DATE, assetAccountInitialEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, ACTIVATION_DATE, liablilityAccountInitialEntry);
+
+        fixedDepositAccountId = this.fixedDepositAccountHelper.calculateInterestForFixedDeposit(fixedDepositAccountId);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        Integer transactionIdForPostInterest = this.fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId);
+        Assert.assertNotNull(transactionIdForPostInterest);
+
+        accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
+        Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted");
+
+        /***
+         * Verify journal entries transactions for interest posting transaction
+         */
+        final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry);
+
+        HashMap fixedDepositPrematureData = this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId,
+                CLOSED_ON_DATE);
+
+        ArrayList<HashMap> errorResponse = (ArrayList<HashMap>) fixedDepositAccountHelperValidationError.prematureCloseForFixedDeposit(
+                fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_REINVEST, null, CommonConstants.RESPONSE_ERROR);
+
+        assertEquals("validation.msg.fixeddepositaccount.onAccountClosureId.reinvest.not.allowed",
+                errorResponse.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    @Test
+    public void testFixedDepositAccountUpdation() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        ArrayList<HashMap> allFixedDepositProductsData = this.fixedDepositProductHelper.retrieveAllFixedDepositProducts(this.requestSpec,
+                this.responseSpec);
+        HashMap fixedDepositProductData = this.fixedDepositProductHelper.retrieveFixedDepositProductById(this.requestSpec,
+                this.responseSpec, fixedDepositProductId.toString());
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        todaysDate.add(Calendar.DATE, -1);
+        SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateFixedDepositAccount(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), VALID_FROM, VALID_TO, WHOLE_TERM, SUBMITTED_ON_DATE);
+        Assert.assertTrue(modificationsHashMap.containsKey("submittedOnDate"));
+
+    }
+
+    @Test
+    public void testFixedDepositAccountUndoApproval() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.undoApproval(fixedDepositAccountId);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+    }
+
+    @Test
+    public void testFixedDepositAccountRejectedAndClosed() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String REJECTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.rejectApplication(fixedDepositAccountId, REJECTED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsRejected(fixedDepositAccountStatusHashMap);
+        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsClosed(fixedDepositAccountStatusHashMap);
+    }
+
+    @Test
+    public void testFixedDepositAccountWithdrawnByClientAndClosed() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String WITHDRAWN_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.withdrawApplication(fixedDepositAccountId, WITHDRAWN_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsWithdrawn(fixedDepositAccountStatusHashMap);
+        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsClosed(fixedDepositAccountStatusHashMap);
+    }
+
+    @Test
+    public void testFixedDepositAccountIsDeleted() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountId = (Integer) this.fixedDepositAccountHelper.deleteFixedDepositApplication(fixedDepositAccountId, "resourceId");
+        Assert.assertNotNull(fixedDepositAccountId);
+    }
+
+    @Test
+    public void testMaturityAmountForMonthlyCompoundingAndMonthlyPosting_With_365_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DATE, -(currentDate - 1));
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Float maturityAmount = (Float) fixedDepositAccountData.get("maturityAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, MONTHLY_INTERVAL, MONTHLY_INTERVAL);
+
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        principal = new Float(decimalFormat.format(principal));
+        maturityAmount = new Float(decimalFormat.format(maturityAmount));
+        System.out.println(principal);
+        Assert.assertEquals("Verifying Maturity amount for Fixed Deposit Account", principal, maturityAmount);
+    }
+
+    @Test
+    public void testMaturityAmountForMonthlyCompoundingAndMonthlyPosting_With_360_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DATE, -(currentDate - 1));
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_360,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, MONTHLY, MONTHLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Float maturityAmount = (Float) fixedDepositAccountData.get("maturityAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, MONTHLY_INTERVAL, MONTHLY_INTERVAL);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        principal = new Float(decimalFormat.format(principal));
+        maturityAmount = new Float(decimalFormat.format(maturityAmount));
+        System.out.println(principal);
+        Assert.assertEquals("Verifying Maturity amount for Fixed Deposit Account", principal, maturityAmount);
+    }
+
+    @Test
+    public void testPrematureClosureAmountWithPenalInterestForWholeTerm_With_365() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, 1);
+        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Float preClosurePenalInterestRate = (Float) fixedDepositAccountData.get("preClosurePenalInterest");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        interestRate -= preClosurePenalInterestRate;
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        daysInMonth = (daysInMonth - currentDate) + 1;
+        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
+        principal += interestPerMonth;
+        todaysDate.add(Calendar.DATE, daysInMonth);
+        System.out.println(monthDayFormat.format(todaysDate.getTime()));
+
+        interestPerMonth = (float) (interestPerDay * principal * currentDate);
+        System.out.println("IPM = " + interestPerMonth);
+        principal += interestPerMonth;
+        System.out.println("principal = " + principal);
+
+        HashMap fixedDepositPrematureData = this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId,
+                CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit(
+                fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+
+        principal = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", principal, maturityAmount);
+
+    }
+
+    @Test
+    public void testPrematureClosureAmountWithPenalInterestForWholeTerm_With_360() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, 1);
+        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_360,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, MONTHLY, MONTHLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Float preClosurePenalInterestRate = (Float) fixedDepositAccountData.get("preClosurePenalInterest");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        interestRate -= preClosurePenalInterestRate;
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        daysInMonth = (daysInMonth - currentDate) + 1;
+        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
+        principal += interestPerMonth;
+        todaysDate.add(Calendar.DATE, daysInMonth);
+        System.out.println(monthDayFormat.format(todaysDate.getTime()));
+
+        interestPerMonth = (float) (interestPerDay * principal * currentDate);
+        System.out.println("IPM = " + interestPerMonth);
+        principal += interestPerMonth;
+        System.out.println("principal = " + principal);
+
+        HashMap fixedDepositPrematureData = this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId,
+                CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit(
+                fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+
+        principal = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", principal, maturityAmount);
+
+    }
+
+    @Test
+    public void testPrematureClosureAmountWithPenalInterestTillPrematureWithdrawal_With_365_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, 1);
+        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, TILL_PREMATURE_WITHDRAWAL);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Float preClosurePenalInterestRate = (Float) fixedDepositAccountData.get("preClosurePenalInterest");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Calendar activationDate = Calendar.getInstance();
+        activationDate.add(Calendar.MONTH, -1);
+        activationDate.add(Calendar.DAY_OF_MONTH, -1);
+        DateTime startDate = new DateTime(activationDate.getTime());
+
+        Calendar prematureClosureDate = Calendar.getInstance();
+        DateTime endDate = new DateTime(prematureClosureDate.getTime());
+
+        Integer depositedPeriod = Months.monthsBetween(startDate, endDate).getMonths();
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositedPeriod);
+        interestRate -= preClosurePenalInterestRate;
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        daysInMonth = (daysInMonth - currentDate) + 1;
+        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
+        principal += interestPerMonth;
+        todaysDate.add(Calendar.DATE, daysInMonth);
+        System.out.println(monthDayFormat.format(todaysDate.getTime()));
+
+        interestPerMonth = (float) (interestPerDay * principal * currentDate);
+        System.out.println("IPM = " + interestPerMonth);
+        principal += interestPerMonth;
+        System.out.println("principal = " + principal);
+
+        HashMap fixedDepositPrematureData = this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId,
+                CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit(
+                fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    @Test
+    public void testPrematureClosureAmountWithPenalInterestTillPrematureWithdrawal_With_360_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, 1);
+        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, TILL_PREMATURE_WITHDRAWAL);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_360,
+                TILL_PREMATURE_WITHDRAWAL, INTEREST_CALCULATION_USING_DAILY_BALANCE, MONTHLY, MONTHLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Float preClosurePenalInterestRate = (Float) fixedDepositAccountData.get("preClosurePenalInterest");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Calendar activationDate = Calendar.getInstance();
+        activationDate.add(Calendar.MONTH, -1);
+        activationDate.add(Calendar.DAY_OF_MONTH, -1);
+        DateTime startDate = new DateTime(activationDate.getTime());
+
+        Calendar prematureClosureDate = Calendar.getInstance();
+        DateTime endDate = new DateTime(prematureClosureDate.getTime());
+
+        Integer depositedPeriod = Months.monthsBetween(startDate, endDate).getMonths();
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositedPeriod);
+        interestRate -= preClosurePenalInterestRate;
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        daysInMonth = (daysInMonth - currentDate) + 1;
+        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
+        principal += interestPerMonth;
+        todaysDate.add(Calendar.DATE, daysInMonth);
+        System.out.println(monthDayFormat.format(todaysDate.getTime()));
+
+        interestPerMonth = (float) (interestPerDay * principal * currentDate);
+        System.out.println("IPM = " + interestPerMonth);
+        principal += interestPerMonth;
+        System.out.println("principal = " + principal);
+
+        HashMap fixedDepositPrematureData = this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId,
+                CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit(
+                fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    @Test
+    public void testMaturityAmountForDailyCompoundingAndMonthlyPosting_With_365_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DATE, -(currentDate - 1));
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_365,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, DAILY, MONTHLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Float maturityAmount = (Float) fixedDepositAccountData.get("maturityAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, DAILY_COMPOUNDING_INTERVAL, MONTHLY_INTERVAL);
+
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        principal = new Float(decimalFormat.format(principal));
+        maturityAmount = new Float(decimalFormat.format(maturityAmount));
+        System.out.println(principal);
+        Assert.assertEquals("Verifying Maturity amount for Fixed Deposit Account", principal, maturityAmount);
+
+    }
+
+    @Test
+    public void testMaturityAmountForDailyCompoundingAndMonthlyPosting_With_360_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DATE, -(currentDate - 1));
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        System.out.println("Submitted Date:"+SUBMITTED_ON_DATE);
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_360,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, DAILY, MONTHLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Float maturityAmount = (Float) fixedDepositAccountData.get("maturityAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, DAILY_COMPOUNDING_INTERVAL, MONTHLY_INTERVAL);
+
+        principal = new BigDecimal(principal).setScale(0, BigDecimal.ROUND_FLOOR).floatValue();
+        maturityAmount = new BigDecimal(maturityAmount).setScale(0, BigDecimal.ROUND_FLOOR).floatValue();
+        System.out.println(principal);
+        Assert.assertEquals("Verifying Maturity amount for Fixed Deposit Account", principal, maturityAmount);
+
+    }
+
+    @Test
+    public void testMaturityAmountForDailyCompoundingAndAnnuallyPosting_With_365_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_365,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, DAILY, ANNUALLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, DAILY_COMPOUNDING_INTERVAL, ANNUL_INTERVAL);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    @Test
+    public void testMaturityAmountDailyCompoundingAndAnnuallyPostingWith_360_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_360,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, DAILY, ANNUALLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, DAILY_COMPOUNDING_INTERVAL, ANNUL_INTERVAL);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    @Test
+    public void testFixedDepositWithBi_AnnualCompoundingAndPosting_365_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_365,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, BI_ANNUALLY, BI_ANNUALLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, BIANNULLY_INTERVAL, BIANNULLY_INTERVAL);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    @Test
+    public void testFixedDepositWithBi_AnnualCompoundingAndPosting_360_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_360,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, BI_ANNUALLY, BI_ANNUALLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, BIANNULLY_INTERVAL, BIANNULLY_INTERVAL);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    @Test
+    public void testFixedDepositWithQuarterlyCompoundingAndQuarterlyPosting_365_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_365,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, QUARTERLY, QUARTERLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, QUARTERLY_INTERVAL, QUARTERLY_INTERVAL);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+    }
+
+    @Test
+    public void testFixedDepositWithQuarterlyCompoundingAndQuarterlyPosting_360_Days() {
+        this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap modificationsHashMap = this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(),
+                fixedDepositProductId.toString(), fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_360,
+                WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, QUARTERLY, QUARTERLY);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        HashMap fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.fixedDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, fixedDepositProductId);
+
+        Float interestRate = this.fixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.fixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod,
+                interestPerDay, QUARTERLY_INTERVAL, QUARTERLY_INTERVAL);
+
+        fixedDepositAccountData = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(fixedDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+    }
+
+    private Integer createFixedDepositProduct(final String validFrom, final String validTo, final String accountingRule,
+            Account... accounts) {
+        System.out.println("------------------------------CREATING NEW FIXED DEPOSIT PRODUCT ---------------------------------------");
+        FixedDepositProductHelper fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        if (accountingRule.equals(CASH_BASED)) {
+            fixedDepositProductHelper = fixedDepositProductHelper.withAccountingRuleAsCashBased(accounts);
+        } else if (accountingRule.equals(NONE)) {
+            fixedDepositProductHelper = fixedDepositProductHelper.withAccountingRuleAsNone();
+        }
+        final String fixedDepositProductJSON = fixedDepositProductHelper //
+                .build(validFrom, validTo);
+        return FixedDepositProductHelper.createFixedDepositProduct(fixedDepositProductJSON, requestSpec, responseSpec);
+    }
+
+    private Integer applyForFixedDepositApplication(final String clientID, final String productID, final String validFrom,
+            final String validTo, final String submittedOnDate, final String penalInterestType) {
+        System.out.println("--------------------------------APPLYING FOR FIXED DEPOSIT ACCOUNT --------------------------------");
+        final String fixedDepositApplicationJSON = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec) //
+                .withSubmittedOnDate(submittedOnDate).build(clientID, productID, validFrom, validTo, penalInterestType);
+        return this.fixedDepositAccountHelper
+                .applyFixedDepositApplication(fixedDepositApplicationJSON, this.requestSpec, this.responseSpec);
+    }
+
+    private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String minOpenningBalance, final String accountingRule, Account... accounts) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+
+        SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+        if (accountingRule.equals(CASH_BASED)) {
+            savingsProductHelper = savingsProductHelper.withAccountingRuleAsCashBased(accounts);
+        } else if (accountingRule.equals(NONE)) {
+            savingsProductHelper = savingsProductHelper.withAccountingRuleAsNone();
+        }
+
+        final String savingsProductJSON = savingsProductHelper //
+                .withInterestCompoundingPeriodTypeAsDaily() //
+                .withInterestPostingPeriodTypeAsMonthly() //
+                .withInterestCalculationPeriodTypeAsDailyBalance() //
+                .withMinimumOpenningBalance(minOpenningBalance).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    private Account getMappedLiabilityFinancialAccount() {
+        final Integer liabilityTransferFinancialActivityId = FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue();
+        List<HashMap> financialActivities = this.financialActivityAccountHelper.getAllFinancialActivityAccounts(this.responseSpec);
+        final Account financialAccount;
+        /***
+         * if no financial activities are defined for account transfers, create
+         * liability financial accounting mappings
+         */
+        if (financialActivities.isEmpty()) {
+            financialAccount = createLiabilityFinancialAccountTransferType(liabilityTransferFinancialActivityId);
+        } else {
+            /***
+             * extract mapped liability financial account
+             */
+            Account mappedLiabilityAccount = null;
+            for (HashMap financialActivity : financialActivities) {
+                HashMap financialActivityData = (HashMap) financialActivity.get("financialActivityData");
+                if (financialActivityData.get("id").equals(liabilityTransferFinancialActivityId)) {
+                    HashMap glAccountData = (HashMap) financialActivity.get("glAccountData");
+                    mappedLiabilityAccount = new Account((Integer) glAccountData.get("id"), AccountType.LIABILITY);
+                    break;
+                }
+            }
+            /***
+             * If liability transfer is not defined create liability transfer
+             */
+            if (mappedLiabilityAccount == null) {
+                mappedLiabilityAccount = createLiabilityFinancialAccountTransferType(liabilityTransferFinancialActivityId);
+            }
+            financialAccount = mappedLiabilityAccount;
+        }
+        return financialAccount;
+    }
+
+    private Account createLiabilityFinancialAccountTransferType(final Integer liabilityTransferFinancialActivityId) {
+        /***
+         * Create and verify financial account transfer type is created
+         */
+        final Account liabilityAccountForMapping = this.accountHelper.createLiabilityAccount();
+        Integer financialActivityAccountId = (Integer) financialActivityAccountHelper.createFinancialActivityAccount(
+                liabilityTransferFinancialActivityId, liabilityAccountForMapping.getAccountID(), this.responseSpec,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(financialActivityAccountId);
+        assertFinancialActivityAccountCreation(financialActivityAccountId, liabilityTransferFinancialActivityId, liabilityAccountForMapping);
+        return liabilityAccountForMapping;
+    }
+
+    private void assertFinancialActivityAccountCreation(Integer financialActivityAccountId, Integer financialActivityId, Account glAccount) {
+        HashMap mappingDetails = this.financialActivityAccountHelper.getFinancialActivityAccount(financialActivityAccountId,
+                this.responseSpec);
+        Assert.assertEquals(financialActivityId, ((HashMap) mappingDetails.get("financialActivityData")).get("id"));
+        Assert.assertEquals(glAccount.getAccountID(), ((HashMap) mappingDetails.get("glAccountData")).get("id"));
+    }
+
+    /**
+     * Delete the Liability transfer account
+     */
+    @After
+    public void tearDown() {
+        List<HashMap> financialActivities = this.financialActivityAccountHelper.getAllFinancialActivityAccounts(this.responseSpec);
+        for (HashMap financialActivity : financialActivities) {
+            Integer financialActivityAccountId = (Integer) financialActivity.get("id");
+            Integer deletedFinancialActivityAccountId = this.financialActivityAccountHelper.deleteFinancialActivityAccount(
+                    financialActivityAccountId, this.responseSpec, CommonConstants.RESPONSE_RESOURCE_ID);
+            Assert.assertNotNull(deletedFinancialActivityAccountId);
+            Assert.assertEquals(financialActivityAccountId, deletedFinancialActivityAccountId);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FlexibleSavingsInterestPostingIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FlexibleSavingsInterestPostingIntegrationTest.java
new file mode 100644
index 0000000..aa39e4e
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FlexibleSavingsInterestPostingIntegrationTest.java
@@ -0,0 +1,154 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unused", "unchecked" })
+public class FlexibleSavingsInterestPostingIntegrationTest {
+
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsProductHelper savingsProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsProductHelper = new SavingsProductHelper();
+    }
+
+    @Test
+    public void testSavingsInterestPostingAtPeriodEnd() {
+        // client activation, savings activation and 1st transaction date
+        final String startDate = "01 December 2013";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
+        Assert.assertNotNull(clientID);
+
+        // Configuring global config flags
+        configureInterestPosting(true, 4);
+
+        final Integer savingsId = createSavingsAccount(clientID, startDate);
+
+        Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+
+        /***
+         * Perform Post interest transaction and verify the posted transaction
+         * date
+         */
+        this.savingsAccountHelper.postInterestForSavings(savingsId);
+        HashMap accountDetails = this.savingsAccountHelper.getSavingsDetails(savingsId);
+        ArrayList<HashMap<String, Object>> transactions = (ArrayList<HashMap<String, Object>>) accountDetails.get("transactions");
+        HashMap<String, Object> interestPostingTransaction = transactions.get(transactions.size() - 2);
+        for (Entry<String, Object> entry : interestPostingTransaction.entrySet())
+            System.out.println(entry.getKey() + "-" + entry.getValue().toString());
+        // 1st Dec 13 to 31st March 14 - 365 days, daily compounding using daily
+        // balance
+        // 33.7016 obtained from formula in excel provided by Subramanya
+        assertEquals("Equality check for interest posted amount", "33.7016", interestPostingTransaction.get("amount").toString());
+        assertEquals("Date check for Interest Posting transaction", "[2014, 3, 31]", interestPostingTransaction.get("date").toString());
+
+    }
+
+    private Integer createSavingsAccount(final Integer clientID, final String startDate) {
+        final Integer savingsProductID = createSavingsProduct();
+        Assert.assertNotNull(savingsProductID);
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplicationOnDate(clientID, savingsProductID,
+                ACCOUNT_TYPE_INDIVIDUAL, startDate);
+        Assert.assertNotNull(savingsId);
+        HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavingsOnDate(savingsId, startDate);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavingsAccount(savingsId, startDate);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+        return savingsId;
+    }
+
+    private void configureInterestPosting(final Boolean periodEndEnable, final Integer financialYearBeginningMonth) {
+        final ArrayList<HashMap> globalConfig = GlobalConfigurationHelper.getAllGlobalConfigurations(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(globalConfig);
+
+        // Updating flag for interest posting at period end
+        Integer periodEndConfigId = (Integer) globalConfig.get(10).get("id");
+        Assert.assertNotNull(periodEndConfigId);
+
+        HashMap periodEndConfigData = GlobalConfigurationHelper.getGlobalConfigurationById(this.requestSpec, this.responseSpec,
+                periodEndConfigId.toString());
+        Assert.assertNotNull(periodEndConfigData);
+
+        Boolean enabled = (Boolean) globalConfig.get(10).get("enabled");
+
+        if (enabled != periodEndEnable)
+            periodEndConfigId = GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec,
+                    periodEndConfigId.toString(), periodEndEnable);
+
+        // Updating value for financial year beginning month
+        Integer financialYearBeginningConfigId = (Integer) globalConfig.get(11).get("id");
+        Assert.assertNotNull(financialYearBeginningConfigId);
+
+        HashMap financialYearBeginningConfigData = GlobalConfigurationHelper.getGlobalConfigurationById(this.requestSpec,
+                this.responseSpec, financialYearBeginningConfigId.toString());
+        Assert.assertNotNull(financialYearBeginningConfigData);
+
+        financialYearBeginningConfigId = GlobalConfigurationHelper.updateValueForGlobalConfiguration(this.requestSpec, this.responseSpec,
+                financialYearBeginningConfigId.toString(), financialYearBeginningMonth.toString());
+        Assert.assertNotNull(financialYearBeginningConfigId);
+    }
+
+    private Integer createSavingsProduct() {
+        final String savingsProductJSON = this.savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily()
+                .withInterestPostingPeriodTypeAsAnnual().withInterestCalculationPeriodTypeAsDailyBalance().build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    // Reset configuration fields
+    @After
+    public void tearDown() {
+        configureInterestPosting(false, 1);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FundsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FundsIntegrationTest.java
new file mode 100644
index 0000000..0c2deeb
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FundsIntegrationTest.java
@@ -0,0 +1,347 @@
+/**
+ * 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.integrationtests;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.Gson;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.funds.FundsHelper;
+import org.apache.fineract.integrationtests.common.funds.FundsResourceHandler;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+import java.util.*;
+
+/**
+ * Funds Integration Test for checking Funds Application.
+ */
+public class FundsIntegrationTest {
+
+    private ResponseSpecification statusOkResponseSpec;
+    private RequestSpecification requestSpec;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.statusOkResponseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void testCreateFund() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+    }
+
+    @Test
+    public void testCreateFundWithEmptyName() {
+        FundsHelper fh = FundsHelper
+                         .create(null)
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(400).build();
+        final Long fundID = createFund(jsonData, this.requestSpec, responseSpec);
+        Assert.assertNull(fundID);
+    }
+
+    @Test
+    public void testCreateFundWithEmptyExternalId() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(null)
+                         .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+    }
+
+    @Test
+    public void testCreateFundWithDuplicateName() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        FundsHelper fh2 = FundsHelper
+                         .create(fh.getName())
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        jsonData = fh2.toJSON();
+
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(403).build();
+        final Long fundID2 = createFund(jsonData, this.requestSpec, responseSpec);
+        Assert.assertNull(fundID2);
+    }
+
+    @Test
+    public void testCreateFundWithDuplicateExternalId() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        FundsHelper fh2 = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(fh.getExternalId())
+                         .build();
+        jsonData = fh2.toJSON();
+
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(403).build();
+        final Long fundID2 = createFund(jsonData, this.requestSpec, responseSpec);
+        Assert.assertNull(fundID2);
+    }
+
+    @Test
+    public void testCreateFundWithInvalidName() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 120))
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(400).build();
+        final Long fundID = createFund(jsonData, this.requestSpec, responseSpec);
+        Assert.assertNull(fundID);
+    }
+
+    @Test
+    public void testCreateFundWithInvalidExternalId() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(Utils.randomNameGenerator("fund-", 120))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(400).build();
+        final Long fundID = createFund(jsonData, this.requestSpec, responseSpec);
+        Assert.assertNull(fundID);
+    }
+
+    @Test
+    public void testRetrieveFund() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        jsonData = FundsResourceHandler.retrieveFund(fundID, this.requestSpec, this.statusOkResponseSpec);
+        FundsHelper fh2 = FundsHelper.fromJSON(jsonData);
+
+        assertEquals(fh.getName(), fh2.getName());
+    }
+
+    @Test
+    public void testRetrieveAllFunds() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        List<FundsHelper> fhList = FundsResourceHandler.retrieveAllFunds(this.requestSpec, this.statusOkResponseSpec);
+
+        Assert.assertNotNull(fhList);
+        Assert.assertThat(fhList.size(), greaterThanOrEqualTo(1));
+        Assert.assertThat(fhList, hasItem(fh));
+    }
+
+    @Test
+    public void testRetrieveUnknownFund() {
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(404).build();
+        String jsonData = FundsResourceHandler.retrieveFund(Long.MAX_VALUE, this.requestSpec, responseSpec);
+        HashMap<String, String> map = new Gson().fromJson(jsonData, HashMap.class);
+        assertEquals(map.get("userMessageGlobalisationCode"), "error.msg.resource.not.found");
+    }
+
+    @Test
+    public void testUpdateFund() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        String newName = Utils.randomNameGenerator("", 10);
+        String newExternalId = Utils.randomNameGenerator("fund-", 5);
+        FundsHelper fh2 = FundsResourceHandler.updateFund(fundID, newName, newExternalId, this.requestSpec, this.statusOkResponseSpec);
+
+        Assert.assertEquals(newName, fh2.getName());
+        Assert.assertEquals(newExternalId, fh2.getExternalId());
+    }
+
+    @Test
+    public void testUpdateUnknownFund() {
+        String newName = Utils.randomNameGenerator("", 10);
+        String newExternalId = Utils.randomNameGenerator("fund-", 5);
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(404).build();
+        FundsHelper fh = FundsResourceHandler.updateFund(Long.MAX_VALUE, newName, newExternalId, this.requestSpec,
+                responseSpec);
+        Assert.assertNull(fh);
+    }
+
+    @Test
+    public void testUpdateFundWithInvalidNewName() {
+        FundsHelper fh = FundsHelper
+                        .create(Utils.randomNameGenerator("", 10))
+                        .externalId(Utils.randomNameGenerator("fund-", 5))
+                        .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        String newName = Utils.randomNameGenerator("", 120);
+        String newExternalId = Utils.randomNameGenerator("fund-", 5);
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(400).build();
+        FundsHelper fh2 = FundsResourceHandler.updateFund(fundID, newName, newExternalId, this.requestSpec,
+                responseSpec);
+
+        Assert.assertNull(fh2);
+    }
+
+    @Test
+    public void testUpdateFundWithNewExternalId() {
+        FundsHelper fh = FundsHelper
+                         .create(Utils.randomNameGenerator("", 10))
+                         .externalId(Utils.randomNameGenerator("fund-", 5))
+                         .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        String newExternalId = Utils.randomNameGenerator("fund-", 5);
+        FundsHelper fh2 = FundsResourceHandler.updateFund(fundID, null, newExternalId, this.requestSpec, this.statusOkResponseSpec);
+
+        Assert.assertEquals(newExternalId, fh2.getExternalId());
+    }
+
+    @Test
+    public void testUpdateFundWithInvalidNewExternalId() {
+        FundsHelper fh = FundsHelper
+                .create(Utils.randomNameGenerator("", 10))
+                .externalId(Utils.randomNameGenerator("fund-", 5))
+                .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        String newName = Utils.randomNameGenerator("", 10);
+        String newExternalId = Utils.randomNameGenerator("fund-", 120);
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(400).build();
+        FundsHelper fh2 = FundsResourceHandler.updateFund(fundID, newName, newExternalId, this.requestSpec,
+                responseSpec);
+
+        Assert.assertNull(fh2);
+    }
+
+    @Test
+    public void testUpdateFundWithNewName() {
+        FundsHelper fh = FundsHelper
+                .create(Utils.randomNameGenerator("", 10))
+                .externalId(Utils.randomNameGenerator("fund-", 5))
+                .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        String newName = Utils.randomNameGenerator("", 10);
+        FundsHelper fh2 = FundsResourceHandler.updateFund(fundID, newName, null, this.requestSpec, this.statusOkResponseSpec);
+
+        Assert.assertEquals(newName, fh2.getName());
+    }
+
+    @Test
+    public void testUpdateFundWithEmptyParams() {
+        FundsHelper fh = FundsHelper
+                .create(Utils.randomNameGenerator("", 10))
+                .externalId(Utils.randomNameGenerator("fund-", 5))
+                .build();
+        String jsonData = fh.toJSON();
+
+        final Long fundID = createFund(jsonData, this.requestSpec, this.statusOkResponseSpec);
+        Assert.assertNotNull(fundID);
+
+        FundsHelper fh2 = FundsResourceHandler.updateFund(fundID, null, null, this.requestSpec, this.statusOkResponseSpec);
+
+        Assert.assertNull(fh2.getName());
+        Assert.assertNull(fh2.getExternalId());
+
+        // assert that there was no change in
+        // the name and external ID of the fund
+        jsonData = FundsResourceHandler.retrieveFund(fundID, this.requestSpec, this.statusOkResponseSpec);
+        FundsHelper fh3 = new Gson().fromJson(jsonData, FundsHelper.class);
+
+        Assert.assertEquals(fh.getName(), fh3.getName());
+        Assert.assertEquals(fh.getExternalId(), fh3.getExternalId());
+    }
+
+    private Long createFund(final String fundJSON,
+                            final RequestSpecification requestSpec,
+                            final ResponseSpecification responseSpec) {
+        String fundId = String.valueOf(FundsResourceHandler.createFund(fundJSON, requestSpec, responseSpec));
+        if (fundId.equals("null")) {
+            // Invalid JSON data parameters
+            return null;
+        }
+
+        return new Long(fundId);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GlobalConfigurationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GlobalConfigurationTest.java
new file mode 100644
index 0000000..b8d42d4
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GlobalConfigurationTest.java
@@ -0,0 +1,221 @@
+/**
+ * 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.integrationtests;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked", "static-access" })
+public class GlobalConfigurationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private GlobalConfigurationHelper globalConfigurationHelper;
+    private ResponseSpecification httpStatusForidden;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.httpStatusForidden = new ResponseSpecBuilder().expectStatusCode(403).build();
+    }
+
+    @Test
+    public void testGlobalConfigurations() {
+        this.globalConfigurationHelper = new GlobalConfigurationHelper(this.requestSpec, this.responseSpec);
+
+        // Retrieving All Global Configuration details
+        final ArrayList<HashMap> globalConfig = this.globalConfigurationHelper.getAllGlobalConfigurations(this.requestSpec,
+                this.responseSpec);
+        Assert.assertNotNull(globalConfig);
+
+        String configName = "penalty-wait-period";
+        for (Integer configIndex = 0; configIndex < (globalConfig.size() - 1); configIndex++) {
+            if (globalConfig.get(configIndex).get("name").equals(configName)) {
+                Integer configId = (Integer) globalConfig.get(configIndex).get("id");
+                Assert.assertNotNull(configId);
+
+                HashMap configDataBefore = this.globalConfigurationHelper.getGlobalConfigurationById(this.requestSpec, this.responseSpec,
+                        configId.toString());
+                Assert.assertNotNull(configDataBefore);
+
+                Integer value = (Integer) configDataBefore.get("value") + 1;
+
+                // Updating Value for penalty-wait-period Global Configuration
+                configId = this.globalConfigurationHelper.updateValueForGlobalConfiguration(this.requestSpec, this.responseSpec,
+                        configId.toString(), value.toString());
+                Assert.assertNotNull(configId);
+
+                HashMap configDataAfter = this.globalConfigurationHelper.getGlobalConfigurationById(this.requestSpec, this.responseSpec,
+                        configId.toString());
+
+                // Verifying Value for penalty-wait-period after Updation
+                Assert.assertEquals("Verifying Global Config Value after Updation", value, configDataAfter.get("value"));
+
+                // Updating Enabled Flag for penalty-wait-period Global
+                // Configuration
+                Boolean enabled = (Boolean) globalConfig.get(configIndex).get("enabled");
+
+                if (enabled == true) {
+                    enabled = false;
+                } else {
+                    enabled = true;
+                }
+
+                configId = this.globalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec,
+                        configId.toString(), enabled);
+
+                configDataAfter = this.globalConfigurationHelper.getGlobalConfigurationById(this.requestSpec, this.responseSpec,
+                        configId.toString());
+
+                // Verifying Enabled Flag for penalty-wait-period after Updation
+                Assert.assertEquals("Verifying Enabled Flag Global Config after Updation", enabled, configDataAfter.get("enabled"));
+                break;
+            }
+        }
+    }
+
+    @Test
+    public void testGlobalConfigurationIsCacheEnabled() {
+        this.globalConfigurationHelper = new GlobalConfigurationHelper(this.requestSpec, this.responseSpec);
+
+        // Retrieving Is Cache Enabled Global Configuration details
+        ArrayList<HashMap> isCacheGlobalConfig = this.globalConfigurationHelper.getGlobalConfigurationIsCacheEnabled(this.requestSpec,
+                this.responseSpec);
+        Assert.assertNotNull(isCacheGlobalConfig);
+
+        for (Integer cacheType = 0; cacheType <= ((isCacheGlobalConfig.size()) - 1); cacheType++) {
+
+            // Retrieving Is Cache Enabled Global Configuration details
+            isCacheGlobalConfig = this.globalConfigurationHelper.getGlobalConfigurationIsCacheEnabled(this.requestSpec, this.responseSpec);
+            Assert.assertNotNull(isCacheGlobalConfig);
+
+            HashMap cacheTypeAsHashMap = (HashMap) isCacheGlobalConfig.get(cacheType).get("cacheType");
+            Integer cacheTypeId = (Integer) cacheTypeAsHashMap.get("id");
+            String cacheTypeValue = (String) cacheTypeAsHashMap.get("value");
+            Boolean enabled = (Boolean) isCacheGlobalConfig.get(cacheType).get("enabled");
+
+            if (cacheTypeValue.compareTo("No cache") == 0 && enabled == true) {
+                cacheTypeId += 1;
+            } else if (cacheTypeValue.compareTo("Single node") == 0 && enabled == true) {
+                cacheTypeId -= 1;
+            }
+
+            HashMap changes = this.globalConfigurationHelper.updateIsCacheEnabledForGlobalConfiguration(this.requestSpec,
+                    this.responseSpec, cacheTypeId.toString());
+            Assert.assertEquals("Verifying Is Cache Enabled Global Config after Updation", cacheTypeId, changes.get("cacheType"));
+        }
+    }
+    
+    @Test
+	public void testGlobalConfigForcePasswordResetDays() {
+
+		// Retrieving All Global Configuration details
+		final ArrayList<HashMap> globalConfig = this.globalConfigurationHelper
+				.getAllGlobalConfigurations(this.requestSpec, this.responseSpec);
+		Assert.assertNotNull(globalConfig);
+
+		String configName = "force-password-reset-days";
+		String newValue = "0";
+		String newBooleanValue = "true";
+
+		for (Integer configIndex = 0; configIndex < (globalConfig.size() - 1); configIndex++) {
+			if (globalConfig.get(configIndex).get("name").equals(configName)) {
+				Integer configId = (Integer) globalConfig.get(configIndex).get(
+						"id");
+				Assert.assertNotNull(configId);
+
+				/*
+				 * Update force-password-reset-days with value as 0 and Enable
+				 * as true - failure case
+				 */
+				ArrayList error = (ArrayList) this.globalConfigurationHelper
+						.updatePasswordResetDaysForGlobalConfiguration(
+								this.requestSpec, this.httpStatusForidden,
+								configId, newValue, newBooleanValue,
+								CommonConstants.RESPONSE_ERROR);
+				HashMap hash = (HashMap) error.get(0);
+
+				Assert.assertEquals(
+						"Force Password Reset days value must be greater than zero.",
+						"error.msg.password.reset.days.value.must.be.greater.than.zero",
+						hash.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+				/*
+				 * Update force-password-reset-days with value as 50 and Enable
+				 * as true - success case
+				 */
+				final HashMap updateSuccess = (HashMap) this.globalConfigurationHelper
+						.updatePasswordResetDaysForGlobalConfiguration(
+								this.requestSpec, this.responseSpec, configId,
+								"50", newBooleanValue, "changes");
+				Assert.assertNotNull(updateSuccess);
+
+				/* Update with value as 0 and Enable as false - success case */
+				final HashMap updateSuccess1 = (HashMap) this.globalConfigurationHelper
+						.updatePasswordResetDaysForGlobalConfiguration(
+								this.requestSpec, this.responseSpec, configId,
+								newValue, "false", "changes");
+				Assert.assertNotNull(updateSuccess1);
+
+				/* Update without sending value and Enable as true - failure case*/
+				ArrayList failure = (ArrayList) this.globalConfigurationHelper
+						.updatePasswordResetDaysForGlobalConfiguration(
+								this.requestSpec, this.httpStatusForidden, configId,
+								null, newBooleanValue, CommonConstants.RESPONSE_ERROR);
+				HashMap failureHash = (HashMap) failure.get(0);
+				Assert.assertEquals(
+						"Force Password Reset days value must be greater than zero.",
+						"error.msg.password.reset.days.value.must.be.greater.than.zero",
+						failureHash.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+				break;
+			}
+		}
+		/* Update other global configuration property */
+		String otherConfigName = "maker-checker";
+		for (Integer configIndex = 0; configIndex < (globalConfig.size() - 1); configIndex++) {
+			if (globalConfig.get(configIndex).get("name")
+					.equals(otherConfigName)) {
+				String configId = (globalConfig.get(configIndex).get("id"))
+						.toString();
+				Integer updateConfigId = this.globalConfigurationHelper
+						.updateValueForGlobalConfiguration(this.requestSpec,
+								this.responseSpec, configId, newValue);
+				Assert.assertNotNull(updateConfigId);
+				break;
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GroupLoanIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GroupLoanIntegrationTest.java
new file mode 100644
index 0000000..e6f6227
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GroupLoanIntegrationTest.java
@@ -0,0 +1,134 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GroupHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Group Loan Integration Test for checking Loan Application Repayment Schedule.
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class GroupLoanIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void checkGroupLoanCreateAndDisburseFlow() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+
+        final Integer loanProductID = createLoanProduct();
+        final Integer loanID = applyForLoanApplication(groupID, loanProductID);
+        final ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec,
+                loanID);
+        verifyLoanRepaymentSchedule(loanSchedule);
+
+    }
+
+    private Integer createLoanProduct() {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("12,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer groupID, final Integer loanProductID) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal("12,000.00") //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2011") //
+                .withSubmittedOnDate("20 September 2011") //
+                .withLoanType("group").build(groupID.toString(), loanProductID.toString(), null);
+        System.out.println(loanApplicationJSON);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private void verifyLoanRepaymentSchedule(final ArrayList<HashMap> loanSchedule) {
+        System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------");
+
+        assertEquals("Checking for Due Date for 1st Month", new ArrayList<>(Arrays.asList(2011, 10, 20)),
+                loanSchedule.get(1).get("dueDate"));
+        assertEquals("Checking for Principal Due for 1st Month", new Float("2911.49"), loanSchedule.get(1).get("principalOriginalDue"));
+        assertEquals("Checking for Interest Due for 1st Month", new Float("240.00"), loanSchedule.get(1).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 2nd Month", new ArrayList<>(Arrays.asList(2011, 11, 20)),
+                loanSchedule.get(2).get("dueDate"));
+        assertEquals("Checking for Principal Due for 2nd Month", new Float("2969.72"), loanSchedule.get(2).get("principalDue"));
+        assertEquals("Checking for Interest Due for 2nd Month", new Float("181.77"), loanSchedule.get(2).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 3rd Month", new ArrayList<>(Arrays.asList(2011, 12, 20)),
+                loanSchedule.get(3).get("dueDate"));
+        assertEquals("Checking for Principal Due for 3rd Month", new Float("3029.11"), loanSchedule.get(3).get("principalDue"));
+        assertEquals("Checking for Interest Due for 3rd Month", new Float("122.38"), loanSchedule.get(3).get("interestOriginalDue"));
+
+        assertEquals("Checking for Due Date for 4th Month", new ArrayList<>(Arrays.asList(2012, 1, 20)),
+                loanSchedule.get(4).get("dueDate"));
+        assertEquals("Checking for Principal Due for 4th Month", new Float("3089.68"), loanSchedule.get(4).get("principalDue"));
+        assertEquals("Checking for Interest Due for 4th Month", new Float("61.79"), loanSchedule.get(4).get("interestOriginalDue"));
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java
new file mode 100644
index 0000000..1c9ffee
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java
@@ -0,0 +1,553 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GroupHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.joda.time.LocalDate;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Group Savings Integration Test for checking Savings Application.
+ */
+@SuppressWarnings({ "rawtypes", "unused" })
+public class GroupSavingsIntegrationTest {
+
+    public static final String DEPOSIT_AMOUNT = "2000";
+    public static final String WITHDRAW_AMOUNT = "1000";
+    public static final String WITHDRAW_AMOUNT_ADJUSTED = "500";
+    public static final String MINIMUM_OPENING_BALANCE = "1000.0";
+    public static final String ACCOUNT_TYPE_GROUP = "GROUP";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void testSavingsAccount() {
+        this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
+        Assert.assertNotNull(groupID);
+
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        Assert.assertNotNull(groupID);
+
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
+        Assert.assertNotNull(savingsId);
+
+        HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(groupID, savingsProductID, savingsId, ACCOUNT_TYPE_GROUP);
+        Assert.assertTrue(modifications.containsKey("submittedOnDate"));
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        this.savingsAccountHelper.calculateInterestForSavings(savingsId);
+        HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        assertEquals(summaryBefore, summary);
+
+        this.savingsAccountHelper.postInterestForSavings(savingsId);
+        summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Assert.assertFalse(summaryBefore.equals(summary));
+
+        final Object savingsInterest = this.savingsAccountHelper.getSavingsInterest(savingsId);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccount_CLOSE_APPLICATION() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build();
+        final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
+        Assert.assertNotNull(groupID);
+
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        Assert.assertNotNull(groupID);
+
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = "1000.0";
+        final String enforceMinRequiredBalance = "true";
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
+        Assert.assertNotNull(savingsId);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar todaysDate = Calendar.getInstance();
+        final String CLOSEDON_DATE = dateFormat.format(todaysDate.getTime());
+        String withdrawBalance = "false";
+        ArrayList<HashMap> savingsAccountErrorData = (ArrayList<HashMap>) validationErrorHelper.closeSavingsAccountAndGetBackRequiredField(
+                savingsId, withdrawBalance, CommonConstants.RESPONSE_ERROR, CLOSEDON_DATE);
+        assertEquals("validation.msg.savingsaccount.close.results.in.balance.not.zero",
+                savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        withdrawBalance = "true";
+        savingsStatusHashMap =  this.savingsAccountHelper.closeSavingsAccount(savingsId, withdrawBalance);
+        SavingsStatusChecker.verifySavingsAccountIsClosed(savingsStatusHashMap);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccount_DELETE_APPLICATION() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec,
+                new ResponseSpecBuilder().build());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
+        Assert.assertNotNull(groupID);
+
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        Assert.assertNotNull(groupID);
+
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
+        Assert.assertNotNull(savingsId);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        List<HashMap> error1 = (List<HashMap>) savingsAccountHelperValidationError.deleteSavingsApplication(savingsId,
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("validation.msg.savingsaccount.delete.not.in.submittedandpendingapproval.state",
+                error1.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        savingsStatusHashMap = this.savingsAccountHelper.undoApproval(savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        this.savingsAccountHelper.deleteSavingsApplication(savingsId, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        List<HashMap> error = savingsAccountHelperValidationError.getSavingsCollectionAttribute(savingsId, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.saving.account.id.invalid", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccount_REJECT_APPLICATION() {
+
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec,
+                new ResponseSpecBuilder().build());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
+        Assert.assertNotNull(groupID);
+
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        Assert.assertNotNull(groupID);
+
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
+        Assert.assertNotNull(savingsId);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        List<HashMap> error1 = savingsAccountHelperValidationError.rejectApplicationWithErrorCode(savingsId,
+                SavingsAccountHelper.CREATED_DATE_PLUS_ONE);
+        assertEquals("validation.msg.savingsaccount.reject.not.in.submittedandpendingapproval.state",
+                error1.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        savingsStatusHashMap = this.savingsAccountHelper.undoApproval(savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        error1 = savingsAccountHelperValidationError.rejectApplicationWithErrorCode(savingsId, SavingsAccountHelper.getFutureDate());
+        assertEquals("validation.msg.savingsaccount.reject.cannot.be.a.future.date",
+                error1.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error1 = savingsAccountHelperValidationError.rejectApplicationWithErrorCode(savingsId, SavingsAccountHelper.CREATED_DATE_MINUS_ONE);
+        assertEquals("validation.msg.savingsaccount.reject.cannot.be.before.submittal.date",
+                error1.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        savingsStatusHashMap = this.savingsAccountHelper.rejectApplication(savingsId);
+        SavingsStatusChecker.verifySavingsIsRejected(savingsStatusHashMap);
+    }
+
+    @Test
+    public void testSavingsAccount_WITHDRAW_APPLICATION() {
+
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
+        Assert.assertNotNull(groupID);
+
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        Assert.assertNotNull(groupID);
+
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
+        Assert.assertNotNull(savingsId);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.withdrawApplication(savingsId);
+        SavingsStatusChecker.verifySavingsIsWithdrawn(savingsStatusHashMap);
+
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccountTransactions() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec,
+                new ResponseSpecBuilder().build());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
+        Assert.assertNotNull(groupID);
+
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        Assert.assertNotNull(groupID);
+
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
+        Assert.assertNotNull(savingsId);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        List<HashMap> error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "100",
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.account.is.not.active",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.depositToSavingsAccount(savingsId, "100", SavingsAccountHelper.TRANSACTION_DATE,
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.account.is.not.active",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Float balance = new Float(MINIMUM_OPENING_BALANCE);
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+        Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT,
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        HashMap depositTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, depositTransactionId);
+        balance += new Float(DEPOSIT_AMOUNT);
+        assertEquals("Verifying Deposit Amount", new Float(DEPOSIT_AMOUNT), depositTransaction.get("amount"));
+        assertEquals("Verifying Balance after Deposit", balance, depositTransaction.get("runningBalance"));
+
+        Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, WITHDRAW_AMOUNT,
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        HashMap withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
+        balance -= new Float(WITHDRAW_AMOUNT);
+        assertEquals("Verifying Withdrawal Amount", new Float(WITHDRAW_AMOUNT), withdrawTransaction.get("amount"));
+        assertEquals("Verifying Balance after Withdrawal", balance, withdrawTransaction.get("runningBalance"));
+
+        Integer newWithdrawTransactionId = this.savingsAccountHelper.updateSavingsAccountTransaction(savingsId, withdrawTransactionId,
+                WITHDRAW_AMOUNT_ADJUSTED);
+        HashMap newWithdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, newWithdrawTransactionId);
+        balance = balance + new Float(WITHDRAW_AMOUNT) - new Float(WITHDRAW_AMOUNT_ADJUSTED);
+        assertEquals("Verifying adjusted Amount", new Float(WITHDRAW_AMOUNT_ADJUSTED), newWithdrawTransaction.get("amount"));
+        assertEquals("Verifying Balance after adjust", balance, newWithdrawTransaction.get("runningBalance"));
+        summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        assertEquals("Verifying Adjusted Balance", balance, summary.get("accountBalance"));
+        withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
+        Assert.assertTrue((Boolean) withdrawTransaction.get("reversed"));
+
+        this.savingsAccountHelper.undoSavingsAccountTransaction(savingsId, newWithdrawTransactionId);
+        newWithdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
+        Assert.assertTrue((Boolean) newWithdrawTransaction.get("reversed"));
+        summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        balance += new Float(WITHDRAW_AMOUNT_ADJUSTED);
+        assertEquals("Verifying Balance After Undo Transaction", balance, summary.get("accountBalance"));
+
+        error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "5000",
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "5000",
+                SavingsAccountHelper.getFutureDate(), CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.in.the.future", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.depositToSavingsAccount(savingsId, "5000", SavingsAccountHelper.getFutureDate(),
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.in.the.future", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "5000",
+                SavingsAccountHelper.CREATED_DATE_MINUS_ONE, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.before.activation.date",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        error = (List) savingsAccountHelperValidationError.depositToSavingsAccount(savingsId, "5000",
+                SavingsAccountHelper.CREATED_DATE_MINUS_ONE, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.savingsaccount.transaction.before.activation.date",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSavingsAccountCharges() {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
+        Assert.assertNotNull(groupID);
+
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        Assert.assertNotNull(groupID);
+
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
+                minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
+        Assert.assertNotNull(savingsId);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        final Integer withdrawalChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsWithdrawalFeeJSON());
+        Assert.assertNotNull(withdrawalChargeId);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, withdrawalChargeId);
+        ArrayList<HashMap> chargesPendingState = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(1, chargesPendingState.size());
+
+        Integer savingsChargeId = (Integer) chargesPendingState.get(0).get("id");
+        HashMap chargeChanges = this.savingsAccountHelper.updateCharges(savingsChargeId, savingsId);
+        Assert.assertTrue(chargeChanges.containsKey("amount"));
+
+        Integer deletedChargeId = this.savingsAccountHelper.deleteCharge(savingsChargeId, savingsId);
+        assertEquals(savingsChargeId, deletedChargeId);
+
+        chargesPendingState = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertTrue(chargesPendingState == null || chargesPendingState.size() == 0);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        final Integer chargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec, ChargesHelper.getSavingsAnnualFeeJSON());
+        Assert.assertNotNull(chargeId);
+
+        ArrayList<HashMap> charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertTrue(charges == null || charges.size() == 0);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, chargeId);
+        charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(1, charges.size());
+
+        HashMap savingsChargeForPay = charges.get(0);
+        SimpleDateFormat sdf = new SimpleDateFormat(CommonConstants.dateFormat, Locale.US);
+        Calendar cal = Calendar.getInstance();
+        List dates = (List) savingsChargeForPay.get("dueDate");
+        cal.set(Calendar.YEAR, (Integer) dates.get(0));
+        cal.set(Calendar.MONTH, (Integer) dates.get(1) - 1);
+        cal.set(Calendar.DAY_OF_MONTH, (Integer) dates.get(2));
+
+        this.savingsAccountHelper.payCharge((Integer) savingsChargeForPay.get("id"), savingsId,
+                ((Float) savingsChargeForPay.get("amount")).toString(), sdf.format(cal.getTime()));
+        HashMap paidCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForPay.get("id"));
+        assertEquals(savingsChargeForPay.get("amount"), paidCharge.get("amountPaid"));
+
+        final Integer monthlyFeechargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsMonthlyFeeJSON());
+        Assert.assertNotNull(monthlyFeechargeId);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, monthlyFeechargeId);
+        charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(2, charges.size());
+
+        HashMap savingsChargeForWaive = charges.get(1);
+        this.savingsAccountHelper.waiveCharge((Integer) savingsChargeForWaive.get("id"), savingsId);
+        HashMap waiveCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForWaive.get("id"));
+        assertEquals(savingsChargeForWaive.get("amount"), waiveCharge.get("amountWaived"));
+
+        this.savingsAccountHelper.waiveCharge((Integer) savingsChargeForWaive.get("id"), savingsId);
+        waiveCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForWaive.get("id"));
+        BigDecimal totalWaiveAmount = BigDecimal.valueOf(Double.valueOf((Float) savingsChargeForWaive.get("amount")));
+        totalWaiveAmount = totalWaiveAmount.add(totalWaiveAmount);
+        assertEquals(totalWaiveAmount.floatValue(), waiveCharge.get("amountWaived"));
+        
+        final Integer weeklyFeeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec, ChargesHelper.getSavingsWeeklyFeeJSON());
+        Assert.assertNotNull(weeklyFeeId);
+        
+        this.savingsAccountHelper.addChargesForSavings(savingsId, weeklyFeeId);
+        charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(3, charges.size());
+
+        savingsChargeForPay = charges.get(2);
+        cal = Calendar.getInstance();
+        dates = (List) savingsChargeForPay.get("dueDate");
+        cal.set(Calendar.YEAR, (Integer) dates.get(0));
+        cal.set(Calendar.MONTH, (Integer) dates.get(1) - 1);
+        cal.set(Calendar.DAY_OF_MONTH, (Integer) dates.get(2));
+
+        // Depositing huge amount as scheduler job deducts the fee amount
+        Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, "100000",
+                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(depositTransactionId);
+
+        this.savingsAccountHelper.payCharge((Integer) savingsChargeForPay.get("id"), savingsId,
+                ((Float) savingsChargeForPay.get("amount")).toString(), sdf.format(cal.getTime()));
+        paidCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForPay.get("id"));
+        assertEquals(savingsChargeForPay.get("amount"), paidCharge.get("amountPaid"));
+        List nextDueDates = (List) paidCharge.get("dueDate");
+        LocalDate nextDueDate = new LocalDate((Integer) nextDueDates.get(0), (Integer) nextDueDates.get(1), (Integer) nextDueDates.get(2));
+        LocalDate expectedNextDueDate = new LocalDate((Integer) dates.get(0), (Integer) dates.get(1), (Integer) dates.get(2))
+                .plusWeeks((Integer) paidCharge.get("feeInterval"));
+        assertEquals(expectedNextDueDate, nextDueDate);
+        cal = Calendar.getInstance();
+        
+        this.savingsAccountHelper.closeSavingsAccountAndGetBackRequiredField(savingsId, "true", null, sdf.format(cal.getTime()));
+        
+    }
+
+    public static Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String minOpenningBalance, final String minBalanceForInterestCalculation, final String minRequiredBalance,
+            final String enforceMinRequiredBalance) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+        SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+        final String savingsProductJSON = savingsProductHelper //
+                .withInterestCompoundingPeriodTypeAsDaily() //
+                .withInterestPostingPeriodTypeAsMonthly() //
+                .withInterestCalculationPeriodTypeAsDailyBalance() //
+                .withMinBalanceForInterestCalculation(minBalanceForInterestCalculation) //
+                .withMinRequiredBalance(minRequiredBalance) //
+                .withEnforceMinRequiredBalance(enforceMinRequiredBalance) //
+                .withMinimumOpenningBalance(minOpenningBalance).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GroupTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GroupTest.java
new file mode 100755
index 0000000..49b4d4c
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/GroupTest.java
@@ -0,0 +1,178 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GroupHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Group Test for checking Group: Creation, Activation, Client Association,
+ * Updating & Deletion
+ */
+public class GroupTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private final String principal = "10000.00";
+    private final String accountingRule = "1";
+    private final String numberOfRepayments = "5";
+    private final String interestRatePerPeriod = "18";
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+    }
+
+    @Test
+    public void checkGroupFunctions() {
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec);
+        GroupHelper.verifyGroupCreatedOnServer(this.requestSpec, this.responseSpec, groupID);
+
+        groupID = GroupHelper.activateGroup(this.requestSpec, this.responseSpec, groupID.toString());
+        GroupHelper.verifyGroupActivatedOnServer(this.requestSpec, this.responseSpec, groupID, true);
+
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        GroupHelper.verifyGroupMembers(this.requestSpec, this.responseSpec, groupID, clientID);
+
+        groupID = GroupHelper.disAssociateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        GroupHelper.verifyEmptyGroupMembers(this.requestSpec, this.responseSpec, groupID);
+
+        final String updatedGroupName = GroupHelper.randomNameGenerator("Group-", 5);
+        groupID = GroupHelper.updateGroup(this.requestSpec, this.responseSpec, updatedGroupName, groupID.toString());
+        GroupHelper.verifyGroupDetails(this.requestSpec, this.responseSpec, groupID, "name", updatedGroupName);
+
+        // NOTE: removed as consistently provides false positive result on
+        // cloudbees server.
+        // groupID = GroupHelper.createGroup(this.requestSpec,
+        // this.responseSpec);
+        // GroupHelper.deleteGroup(this.requestSpec, this.responseSpec,
+        // groupID.toString());
+        // GroupHelper.verifyGroupDeleted(this.requestSpec, this.responseSpec,
+        // groupID);
+    }
+
+    @Test
+    public void assignStaffToGroup() {
+        Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec);
+        GroupHelper.verifyGroupCreatedOnServer(this.requestSpec, this.responseSpec, groupID);
+
+        final String updateGroupName = Utils.randomNameGenerator("Savings Group Help_", 5);
+        groupID = GroupHelper.activateGroup(this.requestSpec, this.responseSpec, groupID.toString());
+        Integer updateGroupId = GroupHelper.updateGroup(this.requestSpec, this.responseSpec, updateGroupName, groupID.toString());
+
+        // create client and add client to group
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientID.toString());
+        GroupHelper.verifyGroupMembers(this.requestSpec, this.responseSpec, groupID, clientID);
+
+        // create staff
+        Integer createStaffId1 = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        System.out.println("--------------creating first staff with id-------------" + createStaffId1);
+        Assert.assertNotNull(createStaffId1);
+
+        Integer createStaffId2 = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        System.out.println("--------------creating second staff with id-------------" + createStaffId2);
+        Assert.assertNotNull(createStaffId2);
+
+        // assign staff "createStaffId1" to group
+        HashMap assignStaffGroupId = (HashMap) GroupHelper.assignStaff(this.requestSpec, this.responseSpec, groupID.toString(),
+                createStaffId1.longValue());
+        assertEquals("Verify assigned staff id is the same as id sent", assignStaffGroupId.get("staffId"), createStaffId1);
+
+        // assign staff "createStaffId2" to client
+        final HashMap assignStaffToClientChanges = (HashMap) ClientHelper.assignStaffToClient(this.requestSpec, this.responseSpec,
+                clientID.toString(), createStaffId2.toString());
+        assertEquals("Verify assigned staff id is the same as id sent", assignStaffToClientChanges.get("staffId"), createStaffId2);
+
+        final Integer loanProductId = this.createLoanProduct();
+
+        final Integer loanId = this.applyForLoanApplication(clientID, loanProductId, this.principal);
+
+        this.loanTransactionHelper.approveLoan("20 September 2014", loanId);
+        this.loanTransactionHelper.disburseLoan("20 September 2014", loanId);
+
+        final HashMap assignStaffAndInheritStaffForClientAccounts = (HashMap) GroupHelper.assignStaffInheritStaffForClientAccounts(
+                this.requestSpec, this.responseSpec, groupID.toString(), createStaffId1.toString());
+        final Integer getClientStaffId = ClientHelper.getClientsStaffId(this.requestSpec, this.responseSpec, clientID.toString());
+
+        // assert if client staff officer has change Note client was assigned
+        // staff with createStaffId2
+        assertNotEquals("Verify if client stuff has changed", assignStaffAndInheritStaffForClientAccounts.get("staffId"), createStaffId2);
+        assertEquals("Verify if client inherited staff assigned above", assignStaffAndInheritStaffForClientAccounts.get("staffId"),
+                getClientStaffId);
+
+        // assert if clients loan officer has changed
+        final Integer loanOfficerId = this.loanTransactionHelper.getLoanOfficerId(loanId.toString());
+        assertEquals("Verify if client loan inherited staff", assignStaffAndInheritStaffForClientAccounts.get("staffId"), loanOfficerId);
+
+    }
+
+    private Integer createLoanProduct() {
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(this.principal)
+                .withNumberOfRepayments(this.numberOfRepayments).withinterestRatePerPeriod(this.interestRatePerPeriod)
+                .withInterestRateFrequencyTypeAsYear().build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String principal) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2014") //
+                .withSubmittedOnDate("20 September 2014") //
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/HookIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/HookIntegrationTest.java
new file mode 100644
index 0000000..5013a40
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/HookIntegrationTest.java
@@ -0,0 +1,115 @@
+/**
+ * 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.integrationtests;
+
+import org.junit.Assert;
+
+import static org.junit.Assert.fail;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.fineract.integrationtests.common.HookHelper;
+import org.apache.fineract.integrationtests.common.OfficeHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.http.conn.HttpHostConnectException;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.RestAssured;
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.path.json.JsonPath;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class HookIntegrationTest {
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+
+    private HookHelper hookHelper;
+    private OfficeHelper officeHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.hookHelper = new HookHelper(this.requestSpec, this.responseSpec);
+        this.officeHelper = new OfficeHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void shouldSendOfficeCreationNotification() {
+        // Subject to https://echo-webhook.herokuapp.com being up
+        // See
+        // http://www.jamesward.com/2014/06/11/testing-webhooks-was-a-pain-so-i-fixed-the-glitch
+        final String payloadURL = "http://echo-webhook.herokuapp.com:80/Z7RXoCBdLSFMDrpn?";
+        this.hookHelper.createHook(payloadURL);
+        final Integer createdOfficeID = this.officeHelper.createOffice("01 January 2012");
+        try {
+
+            /**
+             * sleep for a three seconds after each failure to increase the
+             * likelihood of the previous request for creating office completing
+             **/
+
+            for (int i = 0; i < 6; i++) {
+                try {
+                    final String json = RestAssured.get(payloadURL.replace("?", "")).asString();
+                    final Integer notificationOfficeId = JsonPath.with(json).get("officeId");
+                    Assert.assertEquals("Equality check for created officeId and hook received payload officeId", createdOfficeID,
+                            notificationOfficeId);
+                    System.out.println("Notification Office Id - " + notificationOfficeId);
+                    i = 6;
+                } catch (Exception e) {
+                    TimeUnit.SECONDS.sleep(3);
+                    i++;
+                }
+            }
+
+        } catch (final Exception e) {
+            if (e instanceof HttpHostConnectException) {
+                fail("Failed to connect to https://echo-webhook.herokuapp.com platform");
+            }
+            throw new RuntimeException(e);
+        }
+
+    }
+    
+    @Test
+    public void createUpdateAndDeleteHook(){
+    	final String payloadURL = "http://echo-webhook.herokuapp.com:80/Z7RXoCBdLSFMDrpn?";
+    	final String updateURL = "http://localhost";
+
+        Long hookId = this.hookHelper.createHook(payloadURL).longValue();
+        Assert.assertNotNull(hookId);
+        this.hookHelper.verifyHookCreatedOnServer(hookId);
+    	System.out.println("---------------------SUCCESSFULLY CREATED AND VERIFIED HOOK-------------------------"+hookId);
+    	this.hookHelper.updateHook(updateURL, hookId);
+    	this.hookHelper.verifyUpdateHook(updateURL, hookId);
+    	System.out.println("---------------------SUCCESSFULLY UPDATED AND VERIFIED HOOK-------------------------"+hookId);
+    	this.hookHelper.deleteHook(hookId);
+    	this.hookHelper.verifyDeleteHook(hookId);
+    	System.out.println("---------------------SUCCESSFULLY DELETED AND VERIFIED HOOK-------------------------"+hookId);
+
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanApplicationApprovalTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanApplicationApprovalTest.java
new file mode 100644
index 0000000..1ef1a20
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanApplicationApprovalTest.java
@@ -0,0 +1,281 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class LoanApplicationApprovalTest {
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpecForStatusCode403;
+    private ResponseSpecification responseSpecForStatusCode400;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpecForStatusCode403 = new ResponseSpecBuilder().expectStatusCode(403).build();
+        this.responseSpecForStatusCode400 = new ResponseSpecBuilder().expectStatusCode(400).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+    }
+
+    /*
+     * Positive test case: Approved amount non zero is less than proposed amount
+     */
+    @Test
+    public void loanApplicationApprovedAmountLessThanProposedAmount() {
+
+        final String proposedAmount = "8000";
+        final String approvalAmount = "5000";
+        final String approveDate = "20 September 2012";
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+        final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder().build(null));
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, proposedAmount);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        final String expectedDisbursementDate = null;
+        List<HashMap> approveTranches = null;
+        loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, expectedDisbursementDate, approvalAmount,
+                loanID, approveTranches);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+    }
+
+    /*
+     * Negative test case: Approved amount non zero is greater than proposed
+     * amount
+     */
+    @Test
+    public void loanApplicationApprovedAmountGreaterThanProposedAmount() {
+
+        final String proposedAmount = "5000";
+        final String approvalAmount = "9000";
+        final String approveDate = "20 September 2011";
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+        final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder().build(null));
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, proposedAmount);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode403);
+
+        @SuppressWarnings("unchecked")
+        List<HashMap> error = (List<HashMap>) this.loanTransactionHelper.approveLoan(approveDate, approvalAmount, loanID,
+                CommonConstants.RESPONSE_ERROR);
+
+        assertEquals("error.msg.loan.approval.amount.can't.be.greater.than.loan.amount.demanded",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    public HashMap createTrancheDetail(final String date, final String amount) {
+        HashMap<String, Object> detail = new HashMap<>();
+        detail.put("expectedDisbursementDate", date);
+        detail.put("principal", amount);
+
+        return detail;
+    }
+
+    @Test
+    public void loanApplicationApprovalAndValidationForMultiDisburseLoans() {
+
+        List<HashMap> createTranches = new ArrayList<>();
+        createTranches.add(createTrancheDetail("1 March 2014", "1000"));
+        createTranches.add(createTrancheDetail("23 March 2014", "4000"));
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
+        System.out.println("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------"
+                + clientID);
+
+        final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder()
+                .withInterestTypeAsDecliningBalance().withTranches(true).withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+                .build(null));
+        System.out.println("----------------------------------LOAN PRODUCT CREATED WITH ID-------------------------------------------"
+                + loanProductID);
+
+        this.trancheLoansApprovedAmountLesserThanProposedAmount(clientID, loanProductID, createTranches);
+        this.trancheLoansApprovalValidation(clientID, loanProductID, createTranches);
+
+    }
+
+    private void trancheLoansApprovedAmountLesserThanProposedAmount(Integer clientID, Integer loanProductID, List<HashMap> createTranches) {
+        final String proposedAmount = "5000";
+        final String approvalAmount = "2000";
+        final String approveDate = "1 March 2014";
+        final String expectedDisbursementDate = "1 March 2014";
+
+        List<HashMap> approveTranches = new ArrayList<>();
+        approveTranches.add(createTrancheDetail("1 March 2014", "1000"));
+        approveTranches.add(createTrancheDetail("23 March 2014", "1000"));
+
+        final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, createTranches);
+        System.out.println("-----------------------------------LOAN CREATED WITH LOANID-------------------------------------------------"
+                + loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, expectedDisbursementDate, approvalAmount,
+                loanID, approveTranches);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        System.out
+                .println("-----------------------------------MULTI DISBURSAL LOAN APPROVED SUCCESSFULLY---------------------------------------");
+
+    }
+
+    private void trancheLoansApprovalValidation(Integer clientID, Integer loanProductID, List<HashMap> createTranches) {
+        final String proposedAmount = "5000";
+        final String approvalAmount1 = "10000";
+        final String approvalAmount2 = "3000";
+        final String approvalAmount3 = "400";
+        final String approvalAmount4 = "200";
+
+        final String approveDate = "1 March 2014";
+        final String expectedDisbursementDate = "1 March 2014";
+
+        List<HashMap> approveTranche1 = new ArrayList<>();
+        approveTranche1.add(createTrancheDetail("1 March 2014", "5000"));
+        approveTranche1.add(createTrancheDetail("23 March 2014", "5000"));
+
+        List<HashMap> approveTranche2 = new ArrayList<>();
+        approveTranche2.add(createTrancheDetail("1 March 2014", "1000"));
+        approveTranche2.add(createTrancheDetail("23 March 2014", "1000"));
+        approveTranche2.add(createTrancheDetail("23 March 2014", "1000"));
+
+        List<HashMap> approveTranche3 = new ArrayList<>();
+        approveTranche3.add(createTrancheDetail("1 March 2014", "100"));
+        approveTranche3.add(createTrancheDetail("23 March 2014", "100"));
+        approveTranche3.add(createTrancheDetail("24 March 2014", "100"));
+        approveTranche3.add(createTrancheDetail("25 March 2014", "100"));
+
+        List<HashMap> approveTranche4 = new ArrayList<>();
+        approveTranche4.add(createTrancheDetail("1 March 2014", "100"));
+        approveTranche4.add(createTrancheDetail("23 March 2014", "100"));
+        approveTranche4.add(createTrancheDetail("24 March 2014", "100"));
+
+        final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, createTranches);
+        System.out.println("-----------------------------------LOAN CREATED WITH LOANID-------------------------------------------------"
+                + loanID);
+
+        HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode400);
+
+        /* Tranches with same expected disbursement date */
+        List<HashMap<String, Object>> error = this.loanTransactionHelper.approveLoanForTranches(approveDate, expectedDisbursementDate,
+                approvalAmount2, loanID, approveTranche2, CommonConstants.RESPONSE_ERROR);
+        assertEquals("validation.msg.loan.expectedDisbursementDate.disbursement.date.must.be.unique.for.tranches",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        /* Sum of tranches is greater than approved amount */
+        error = this.loanTransactionHelper.approveLoanForTranches(approveDate, expectedDisbursementDate, approvalAmount4, loanID,
+                approveTranche4, CommonConstants.RESPONSE_ERROR);
+        assertEquals("validation.msg.loan.principal.sum.of.multi.disburse.amounts.must.be.equal.to.or.lesser.than.approved.principal",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode403);
+
+        /* Sum of tranches exceeds the proposed amount */
+        error = this.loanTransactionHelper.approveLoanForTranches(approveDate, expectedDisbursementDate, approvalAmount1, loanID,
+                approveTranche1, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.loan.approval.amount.can't.be.greater.than.loan.amount.demanded",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        /* No. of tranches exceeds the max tranche count at product level */
+        error = this.loanTransactionHelper.approveLoanForTranches(approveDate, expectedDisbursementDate, approvalAmount3, loanID,
+                approveTranche3, CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.disbursementData.exceeding.max.tranche.count", error.get(0)
+                .get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        /* If tranches are not specified for a multi-disburse loan */
+        /**
+         * error =
+         * this.loanTransactionHelper.approveLoanForTranches(approveDate,
+         * expectedDisbursementDate, approvalAmount5, loanID, approveTranche5,
+         * CommonConstants.RESPONSE_ERROR);
+         * assertEquals("error.msg.disbursementData.required",
+         * error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+         **/
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String proposedAmount) {
+        final String loanApplication = new LoanApplicationTestBuilder().withPrincipal(proposedAmount).withLoanTermFrequency("5")
+                .withLoanTermFrequencyAsMonths().withNumberOfRepayments("5").withRepaymentEveryAfter("1")
+                .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("2").withExpectedDisbursementDate("04 April 2012")
+                .withSubmittedOnDate("02 April 2012").build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplication);
+    }
+
+    public Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, String principal,
+            List<HashMap> tranches) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder()
+        //
+                .withPrincipal(principal)
+                //
+                .withLoanTermFrequency("5")
+                //
+                .withLoanTermFrequencyAsMonths()
+                //
+                .withNumberOfRepayments("5").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withExpectedDisbursementDate("1 March 2014") //
+                .withTranches(tranches) //
+                .withInterestTypeAsDecliningBalance() //
+                .withSubmittedOnDate("1 March 2014") //
+                .build(clientID.toString(), loanProductID.toString(), null);
+
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanApplicationUndoLastTrancheTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanApplicationUndoLastTrancheTest.java
new file mode 100644
index 0000000..672972e
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanApplicationUndoLastTrancheTest.java
@@ -0,0 +1,157 @@
+/**
+ * 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.integrationtests;
+
+import java.math.BigDecimal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.LoanApplicationApprovalTest;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class LoanApplicationUndoLastTrancheTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private LoanApplicationApprovalTest loanApplicationApprovalTest;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.loanApplicationApprovalTest = new LoanApplicationApprovalTest();
+    }
+
+    @Test
+    public void LoanApplicationUndoLastTranche() {
+
+        final String proposedAmount = "5000";
+        final String approvalAmount = "2000";
+        final String approveDate = "1 March 2014";
+        final String expectedDisbursementDate = "1 March 2014";
+        final String disbursalDate = "1 March 2014";
+
+        // CREATE CLIENT
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
+        System.out.println("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------"
+                + clientID);
+
+        // CREATE LOAN MULTIDISBURSAL PRODUCT
+        final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder()
+                .withInterestTypeAsDecliningBalance().withTranches(true).withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+                .build(null));
+        System.out.println("----------------------------------LOAN PRODUCT CREATED WITH ID-------------------------------------------"
+                + loanProductID);
+
+        // CREATE TRANCHES
+        List<HashMap> createTranches = new ArrayList<>();
+        createTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("1 March 2014", "1000"));
+        createTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("23 June 2014", "4000"));
+
+        // APPROVE TRANCHES
+        List<HashMap> approveTranches = new ArrayList<>();
+        approveTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("1 March 2014", "1000"));
+        approveTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("23 June 2014", "1000"));
+
+        // APPLY FOR LOAN WITH TRANCHES
+        final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, createTranches);
+        System.out.println("-----------------------------------LOAN CREATED WITH LOANID-------------------------------------------------"
+                + loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+
+        // VALIDATE THE LOAN STATUS
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, expectedDisbursementDate, approvalAmount,
+                loanID, approveTranches);
+
+        // VALIDATE THE LOAN IS APPROVED
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        // DISBURSE A LOAN
+        this.loanTransactionHelper.disburseLoan(disbursalDate, loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+
+        // VALIDATE THE LOAN IS ACTIVE STATUS
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        System.out.println("-------------Make repayment 1-----------");
+        this.loanTransactionHelper.makeRepayment("01 April 2014", Float.valueOf("420"), loanID);
+        System.out.println("-------------Make repayment 2-----------");
+        this.loanTransactionHelper.makeRepayment("01 May 2014", Float.valueOf("412"), loanID);
+        System.out.println("-------------Make repayment 3-----------");
+        this.loanTransactionHelper.makeRepayment("01 June 2014", Float.valueOf("204"), loanID);
+        // DISBURSE A SECOND TRANCHE
+        this.loanTransactionHelper.disburseLoan("23 June 2014", loanID);
+        // UNDO LAST TRANCHE
+        Float disbursedAmount = this.loanTransactionHelper.undoLastDisbursal(loanID);
+        validateDisbursedAmount(disbursedAmount);
+    }
+
+    private void validateDisbursedAmount(Float disbursedAmount) {
+        Assert.assertEquals(Float.valueOf("1000.0"), disbursedAmount);
+
+    }
+
+    public Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, String principal,
+            List<HashMap> tranches) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder()
+        //
+                .withPrincipal(principal)
+                //
+                .withLoanTermFrequency("5")
+                //
+                .withLoanTermFrequencyAsMonths()
+                //
+                .withNumberOfRepayments("5").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withExpectedDisbursementDate("1 March 2014") //
+                .withTranches(tranches) //
+                .withInterestTypeAsDecliningBalance() //
+                .withSubmittedOnDate("1 March 2014") //
+                .build(clientID.toString(), loanProductID.toString(), null);
+
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java
new file mode 100644
index 0000000..cecac3d
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java
@@ -0,0 +1,501 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanDisbursementTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class LoanDisbursementDetailsIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private Integer loanID;
+    private Integer disbursementId;
+    final String approveDate = "01 March 2014";
+    final String expectedDisbursementDate = "01 March 2014";
+    final String proposedAmount = "5000";
+    final String approvalAmount = "5000";
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void createAndValidateMultiDisburseLoansBasedOnEmi() {
+        List<HashMap> createTranches = new ArrayList<>();
+        String id = null;
+        String installmentAmount = "800";
+        String withoutInstallmentAmount = "";
+        String proposedAmount = "10000";
+        createTranches.add(this.loanTransactionHelper.createTrancheDetail(id, "01 June 2015", "5000"));
+        createTranches.add(this.loanTransactionHelper.createTrancheDetail(id, "01 Sep 2015", "5000"));
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
+        System.out.println("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------"
+                + clientID);
+
+        final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder()
+                .withInterestTypeAsDecliningBalance().withMoratorium("", "").withAmortizationTypeAsEqualInstallments().withTranches(true)
+                .withInterestCalculationPeriodTypeAsRepaymentPeriod(true).build(null));
+        System.out.println("----------------------------------LOAN PRODUCT CREATED WITH ID-------------------------------------------"
+                + loanProductID);
+
+        final Integer loanIDWithEmi = applyForLoanApplicationWithEmiAmount(clientID, loanProductID, proposedAmount, createTranches,
+                installmentAmount);
+
+        System.out
+                .println("-----------------------------------LOAN CREATED WITH EMI LOANID-------------------------------------------------"
+                        + loanIDWithEmi);
+
+        HashMap repaymentScheduleWithEmi = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec,
+                loanIDWithEmi, "repaymentSchedule");
+
+        ArrayList<HashMap> periods = (ArrayList<HashMap>) repaymentScheduleWithEmi.get("periods");
+        assertEquals(periods.size(), 15);
+
+        this.validateRepaymentScheduleWithEMI(periods);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanIDWithEmi);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount("01 June 2015", "01 June 2015", "10000", loanIDWithEmi,
+                createTranches);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        System.out
+                .println("-----------------------------------MULTI DISBURSAL LOAN WITH EMI APPROVED SUCCESSFULLY---------------------------------------");
+
+        final Integer loanIDWithoutEmi = applyForLoanApplicationWithEmiAmount(clientID, loanProductID, proposedAmount, createTranches,
+                withoutInstallmentAmount);
+
+        HashMap repaymentScheduleWithoutEmi = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec,
+                loanIDWithoutEmi, "repaymentSchedule");
+
+        ArrayList<HashMap> periods1 = (ArrayList<HashMap>) repaymentScheduleWithEmi.get("periods");
+        assertEquals(periods1.size(), 15);
+
+        System.out
+                .println("-----------------------------------LOAN CREATED WITHOUT EMI LOANID-------------------------------------------------"
+                        + loanIDWithoutEmi);
+
+        /* To be uncommented once issue MIFOSX-2006 is closed. */
+        // this.validateRepaymentScheduleWithoutEMI(periods1);
+
+        HashMap loanStatusMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanIDWithoutEmi);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount("01 June 2015", "01 June 2015", "10000",
+                loanIDWithoutEmi, createTranches);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        System.out
+                .println("-----------------------------------MULTI DISBURSAL LOAN WITHOUT EMI APPROVED SUCCESSFULLY---------------------------------------");
+
+    }
+
+    private void validateRepaymentScheduleWithEMI(ArrayList<HashMap> periods) {
+        LoanDisbursementTestBuilder expectedRepaymentSchedule0 = new LoanDisbursementTestBuilder("[2015, 6, 1]", 0.0f, 0.0f, null, null,
+                5000.0f, null, null, null);
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule1 = new LoanDisbursementTestBuilder("[2015, 7, 1]", 800f, 800.0f, 50.0f,
+                750.0f, 4250.0f, 750.0f, 750.0f, "[2015, 6, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule2 = new LoanDisbursementTestBuilder("[2015, 8, 1]", 800.0f, 800.0f, 42.5f,
+                757.5f, 3492.5f, 757.5f, 757.5f, "[2015, 7, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule3 = new LoanDisbursementTestBuilder("[2015, 9, 1]", 0.0f, 0.0f, null, null,
+                5000.0f, null, null, null);
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule4 = new LoanDisbursementTestBuilder("[2015, 9, 1]", 800.0f, 800.0f, 34.92f,
+                765.08f, 7727.42f, 765.08f, 765.08f, "[2015, 8, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule5 = new LoanDisbursementTestBuilder("[2015, 10, 1]", 800.0f, 800.0f, 77.27f,
+                722.73f, 7004.69f, 722.73f, 722.73f, "[2015, 9, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule6 = new LoanDisbursementTestBuilder("[2015, 11, 1]", 800.0f, 800.0f, 70.05f,
+                729.95f, 6274.74f, 729.95f, 729.95f, "[2015, 10, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule7 = new LoanDisbursementTestBuilder("[2015, 12, 1]", 800.0f, 800.0f, 62.75f,
+                737.25f, 5537.49f, 737.25f, 737.25f, "[2015, 11, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule8 = new LoanDisbursementTestBuilder("[2016, 1, 1]", 800.0f, 800.0f, 55.37f,
+                744.63f, 4792.86f, 744.63f, 744.63f, "[2015, 12, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule9 = new LoanDisbursementTestBuilder("[2016, 2, 1]", 800.0f, 800.0f, 47.93f,
+                752.07f, 4040.79f, 752.07f, 752.07f, "[2016, 1, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule10 = new LoanDisbursementTestBuilder("[2016, 3, 1]", 800.0f, 800.0f, 40.41f,
+                759.59f, 3281.2f, 759.59f, 759.59f, "[2016, 2, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule11 = new LoanDisbursementTestBuilder("[2016, 4, 1]", 800.0f, 800.0f, 32.81f,
+                767.19f, 2514.01f, 767.19f, 767.19f, "[2016, 3, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule12 = new LoanDisbursementTestBuilder("[2016, 5, 1]", 800.0f, 800.0f, 25.14f,
+                774.86f, 1739.15f, 774.86f, 774.86f, "[2016, 4, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule13 = new LoanDisbursementTestBuilder("[2016, 6, 1]", 800.0f, 800.0f, 17.39f,
+                782.61f, 956.54f, 782.61f, 782.61f, "[2016, 5, 1]");
+
+        LoanDisbursementTestBuilder expectedRepaymentSchedule14 = new LoanDisbursementTestBuilder("[2016, 7, 1]", 966.11f, 966.11f, 9.57f,
+                956.54f, 0.0f, 956.54f, 956.54f, "[2016, 6, 1]");
+
+        ArrayList<LoanDisbursementTestBuilder> list = new ArrayList<LoanDisbursementTestBuilder>();
+        list.add(expectedRepaymentSchedule0);
+        list.add(expectedRepaymentSchedule1);
+        list.add(expectedRepaymentSchedule2);
+        list.add(expectedRepaymentSchedule3);
+        list.add(expectedRepaymentSchedule4);
+        list.add(expectedRepaymentSchedule5);
+        list.add(expectedRepaymentSchedule6);
+        list.add(expectedRepaymentSchedule7);
+        list.add(expectedRepaymentSchedule8);
+        list.add(expectedRepaymentSchedule9);
+        list.add(expectedRepaymentSchedule10);
+        list.add(expectedRepaymentSchedule11);
+        list.add(expectedRepaymentSchedule12);
+        list.add(expectedRepaymentSchedule13);
+        list.add(expectedRepaymentSchedule14);
+
+        for (int i = 0; i < list.size(); i++) {
+            this.assertRepaymentScheduleValuesWithEMI(periods.get(i), list.get(i), i);
+        }
+    }
+
+    private void assertRepaymentScheduleValuesWithEMI(HashMap period, LoanDisbursementTestBuilder expectedRepaymentSchedule, int position) {
+
+        assertEquals(period.get("dueDate").toString(), expectedRepaymentSchedule.getDueDate());
+        assertEquals(period.get("principalLoanBalanceOutstanding"), expectedRepaymentSchedule.getPrincipalLoanBalanceOutstanding());
+        System.out.println(period.get("totalOriginalDueForPeriod").toString());
+        assertEquals(new Float(period.get("totalOriginalDueForPeriod").toString()), expectedRepaymentSchedule
+                .getTotalOriginalDueForPeriod().floatValue(), 0.0f);
+
+        assertEquals(new Float(period.get("totalOutstandingForPeriod").toString()).floatValue(),
+                expectedRepaymentSchedule.getTotalOutstandingForPeriod(), 0.0f);
+
+        if (position != 0 && position != 3) {
+
+            assertEquals(new Float(period.get("interestOutstanding").toString()).floatValue(),
+                    expectedRepaymentSchedule.getInterestOutstanding(), 0.0f);
+            assertEquals(new Float(period.get("principalOutstanding").toString()).floatValue(),
+                    expectedRepaymentSchedule.getPrincipalOutstanding(), 0.0f);
+            assertEquals(new Float(period.get("principalDue").toString()).floatValue(), expectedRepaymentSchedule.getPrincipalDue(), 0.0f);
+            assertEquals(new Float(period.get("principalOriginalDue").toString()).floatValue(),
+                    expectedRepaymentSchedule.getPrincipalOriginalDue(), 0.0f);
+            assertEquals(period.get("fromDate").toString(), expectedRepaymentSchedule.getFromDate());
+        }
+    }
+
+    /* Uncomment and modify test builder values once MIFOSX-2006 is closed. */
+    /*
+     * private void validateRepaymentScheduleWithoutEMI(ArrayList<HashMap>
+     * periods){ LoanDisbursementTestBuilder expectedRepaymentSchedule0 = new
+     * LoanDisbursementTestBuilder( "[2015, 6, 1]", 0.0f, 0.0f, null, null,
+     * 5000.0f, null, null, null);
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule1 = new
+     * LoanDisbursementTestBuilder( "[2015, 7, 1]", 800.0f, 800.0f, 50.0f,
+     * 750.0f, 4250.0f, 750.0f, 750.0f, "[2015, 6, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule2 = new
+     * LoanDisbursementTestBuilder( "[2015, 8, 1]", 800.0f, 800.0f, 42.5f,
+     * 757.5f, 3492.5f, 757.5f, 757.5f, "[2015, 7, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule3 = new
+     * LoanDisbursementTestBuilder( "[2015, 9, 1]", 0.0f, 0.0f, null, null,
+     * 5000.0f, null, null, null);
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule4 = new
+     * LoanDisbursementTestBuilder( "[2015, 9, 1]", 800.0f, 800.0f, 34.92f,
+     * 765.08f, 7727.42f, 765.08f, 765.08f, "[2015, 8, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule5 = new
+     * LoanDisbursementTestBuilder( "[2015, 10, 1]", 800.0f, 800.0f, 77.27f,
+     * 722.73f, 7004.69f, 722.73f, 722.73f, "[2015, 9, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule6 = new
+     * LoanDisbursementTestBuilder( "[2015, 11, 1]", 800.0f, 800.0f, 70.05f,
+     * 729.95f, 6274.74f, 729.95f, 729.95f, "[2015, 10, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule7 = new
+     * LoanDisbursementTestBuilder( "[2015, 12, 1]", 800.0f, 800.0f, 62.75f,
+     * 737.25f, 5537.49f, 737.25f, 737.25f, "[2015, 11, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule8 = new
+     * LoanDisbursementTestBuilder( "[2016, 1, 1]", 800.0f, 800.0f, 55.37f,
+     * 744.63f, 4792.86f, 744.63f, 744.63f, "[2015, 12, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule9 = new
+     * LoanDisbursementTestBuilder( "[2016, 2, 1]", 800.0f, 800.0f, 47.93f,
+     * 752.07f, 4040.79f, 752.07f, 752.07f, "[2016, 1, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule10 = new
+     * LoanDisbursementTestBuilder( "[2016, 3, 1]", 800.0f, 800.0f, 40.41f,
+     * 759.59f, 3281.2f, 759.59f, 759.59f, "[2016, 2, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule11 = new
+     * LoanDisbursementTestBuilder( "[2016, 4, 1]", 800.0f, 800.0f, 32.81f,
+     * 767.19f, 2514.01f, 767.19f, 767.19f, "[2016, 3, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule12 = new
+     * LoanDisbursementTestBuilder( "[2016, 5, 1]", 800.0f, 800.0f, 25.14f,
+     * 774.86f, 1739.15f, 774.86f, 774.86f, "[2016, 4, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule13 = new
+     * LoanDisbursementTestBuilder( "[2016, 6, 1]", 800.0f, 800.0f, 17.39f,
+     * 782.61f, 956.54f, 782.61f, 782.61f, "[2016, 5, 1]");
+     * 
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule14 = new
+     * LoanDisbursementTestBuilder( "[2016, 7, 1]", 966.11f, 966.11f, 9.57f,
+     * 956.54f, 0.0f, 956.54f, 956.54f, "[2016, 6, 1]");
+     * 
+     * ArrayList<LoanDisbursementTestBuilder> list = new
+     * ArrayList<LoanDisbursementTestBuilder>();
+     * list.add(expectedRepaymentSchedule0);
+     * list.add(expectedRepaymentSchedule1);
+     * list.add(expectedRepaymentSchedule2);
+     * list.add(expectedRepaymentSchedule3);
+     * list.add(expectedRepaymentSchedule4);
+     * list.add(expectedRepaymentSchedule5);
+     * list.add(expectedRepaymentSchedule6);
+     * list.add(expectedRepaymentSchedule7);
+     * list.add(expectedRepaymentSchedule8);
+     * list.add(expectedRepaymentSchedule9);
+     * list.add(expectedRepaymentSchedule10);
+     * list.add(expectedRepaymentSchedule11);
+     * list.add(expectedRepaymentSchedule12);
+     * list.add(expectedRepaymentSchedule13);
+     * list.add(expectedRepaymentSchedule14);
+     * 
+     * for (int i = 0; i < list.size(); i++) {
+     * this.assertRepaymentScheduleValuesWithoutEMI(periods.get(i), list.get(i),
+     * i); } }
+     * 
+     * private void assertRepaymentScheduleValuesWithoutEMI(HashMap period,
+     * LoanDisbursementTestBuilder expectedRepaymentSchedule, int position) {
+     * 
+     * assertEquals(period.get("dueDate").toString(),
+     * expectedRepaymentSchedule.getDueDate());
+     * assertEquals(period.get("principalLoanBalanceOutstanding"),
+     * expectedRepaymentSchedule.getPrincipalLoanBalanceOutstanding());
+     * assertEquals(period.get("totalOriginalDueForPeriod"),
+     * expectedRepaymentSchedule.getTotalOriginalDueForPeriod());
+     * assertEquals(period.get("totalOutstandingForPeriod"),
+     * expectedRepaymentSchedule.getTotalOutstandingForPeriod());
+     * 
+     * if (position != 0 && position != 3) {
+     * 
+     * assertEquals(period.get("interestOutstanding"),
+     * expectedRepaymentSchedule.getInterestOutstanding());
+     * assertEquals(period.get("principalOutstanding"),
+     * expectedRepaymentSchedule.getPrincipalOutstanding());
+     * assertEquals(period.get("principalDue"),
+     * expectedRepaymentSchedule.getPrincipalDue());
+     * assertEquals(period.get("principalOriginalDue"),
+     * expectedRepaymentSchedule.getPrincipalOriginalDue());
+     * assertEquals(period.get("fromDate").toString(),
+     * expectedRepaymentSchedule.getFromDate()); } }
+     */
+    private Integer applyForLoanApplicationWithEmiAmount(final Integer clientID, final Integer loanProductID, final String proposedAmount,
+            List<HashMap> tranches, final String installmentAmount) {
+
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder()
+        //
+                .withPrincipal(proposedAmount)
+                //
+                .withLoanTermFrequency("12")
+                //
+                .withLoanTermFrequencyAsMonths()
+                //
+                .withNumberOfRepayments("12").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("1") //
+                .withExpectedDisbursementDate("01 June 2015") //
+                .withTranches(tranches) //
+                .withFixedEmiAmount(installmentAmount) //
+                .withInterestTypeAsDecliningBalance() //
+                .withSubmittedOnDate("01 June 2015") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .build(clientID.toString(), loanProductID.toString(), null);
+
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+
+    }
+
+    @Test
+    public void createApproveAndValidateMultiDisburseLoan() {
+
+        List<HashMap> createTranches = new ArrayList<>();
+        String id = null;
+        createTranches.add(this.loanTransactionHelper.createTrancheDetail(id, "01 March 2014", "1000"));
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
+        System.out.println("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------"
+                + clientID);
+
+        final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder()
+                .withInterestTypeAsDecliningBalance().withTranches(true).withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+                .build(null));
+        System.out.println("----------------------------------LOAN PRODUCT CREATED WITH ID-------------------------------------------"
+                + loanProductID);
+
+        this.loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, createTranches);
+        System.out.println("-----------------------------------LOAN CREATED WITH LOANID-------------------------------------------------"
+                + loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, expectedDisbursementDate, approvalAmount,
+                loanID, createTranches);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        System.out
+                .println("-----------------------------------MULTI DISBURSAL LOAN APPROVED SUCCESSFULLY---------------------------------------");
+        ArrayList<HashMap> disbursementDetails = (ArrayList<HashMap>) this.loanTransactionHelper.getLoanDetail(this.requestSpec,
+                this.responseSpec, this.loanID, "disbursementDetails");
+        this.disbursementId = (Integer) disbursementDetails.get(0).get("id");
+        this.editLoanDisbursementDetails();
+    }
+
+    private void editLoanDisbursementDetails() {
+        this.editDateAndPrincipalOfExistingTranche();
+        this.addNewDisbursementDetails();
+        this.deleteDisbursmentDetails();
+    }
+
+    private void addNewDisbursementDetails() {
+        List<HashMap> addTranches = new ArrayList<>();
+        ArrayList<HashMap> disbursementDetails = (ArrayList<HashMap>) this.loanTransactionHelper.getLoanDetail(this.requestSpec,
+                this.responseSpec, this.loanID, "disbursementDetails");
+        ArrayList expectedDisbursementDate = (ArrayList) disbursementDetails.get(0).get("expectedDisbursementDate");
+        String date = formatExpectedDisbursementDate(expectedDisbursementDate.toString());
+
+        String id = null;
+        addTranches.add(this.loanTransactionHelper.createTrancheDetail(disbursementDetails.get(0).get("id").toString(), date,
+                disbursementDetails.get(0).get("principal").toString()));
+        addTranches.add(this.loanTransactionHelper.createTrancheDetail(id, "3 March 2014", "2000"));
+        addTranches.add(this.loanTransactionHelper.createTrancheDetail(id, "4 March 2014", "500"));
+
+        /* Add disbursement detail */
+        this.loanTransactionHelper.addAndDeleteDisbursementDetail(this.loanID, this.approvalAmount, this.expectedDisbursementDate,
+                addTranches, "");
+    }
+
+    private void deleteDisbursmentDetails() {
+        List<HashMap> deleteTranches = new ArrayList<>();
+        ArrayList<HashMap> disbursementDetails = (ArrayList<HashMap>) this.loanTransactionHelper.getLoanDetail(this.requestSpec,
+                this.responseSpec, this.loanID, "disbursementDetails");
+        /* Delete the last tranche */
+        for (int i = 0; i < disbursementDetails.size() - 1; i++) {
+            ArrayList expectedDisbursementDate = (ArrayList) disbursementDetails.get(i).get("expectedDisbursementDate");
+            String disbursementDate = formatExpectedDisbursementDate(expectedDisbursementDate.toString());
+            deleteTranches.add(this.loanTransactionHelper.createTrancheDetail(disbursementDetails.get(i).get("id").toString(),
+                    disbursementDate, disbursementDetails.get(i).get("principal").toString()));
+        }
+
+        /* Add disbursement detail */
+        this.loanTransactionHelper.addAndDeleteDisbursementDetail(this.loanID, this.approvalAmount, this.expectedDisbursementDate,
+                deleteTranches, "");
+    }
+
+    private void editDateAndPrincipalOfExistingTranche() {
+        String updatedExpectedDisbursementDate = "01 March 2014";
+        String updatedPrincipal = "900";
+        /* Update */
+        this.loanTransactionHelper.editDisbursementDetail(this.loanID, this.disbursementId, this.approvalAmount,
+                this.expectedDisbursementDate, updatedExpectedDisbursementDate, updatedPrincipal, "");
+        /* Validate Edit */
+        ArrayList<HashMap> disbursementDetails = (ArrayList<HashMap>) this.loanTransactionHelper.getLoanDetail(this.requestSpec,
+                this.responseSpec, this.loanID, "disbursementDetails");
+        assertEquals(Float.valueOf(updatedPrincipal), disbursementDetails.get(0).get("principal"));
+        ArrayList expectedDisbursementDate = (ArrayList) disbursementDetails.get(0).get("expectedDisbursementDate");
+        String date = formatExpectedDisbursementDate(expectedDisbursementDate.toString());
+        assertEquals(updatedExpectedDisbursementDate, date);
+
+    }
+
+    private String formatExpectedDisbursementDate(String expectedDisbursementDate) {
+
+        SimpleDateFormat source = new SimpleDateFormat("[yyyy, MM, dd]");
+        SimpleDateFormat target = new SimpleDateFormat("dd MMMM yyyy");
+        String date = null;
+        try {
+            date = target.format(source.parse(expectedDisbursementDate));
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return date;
+    }
+
+    private Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, String principal,
+            List<HashMap> tranches) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder()
+        //
+                .withPrincipal(principal)
+                //
+                .withLoanTermFrequency("5")
+                //
+                .withLoanTermFrequencyAsMonths()
+                //
+                .withNumberOfRepayments("5").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withExpectedDisbursementDate("01 March 2014") //
+                .withTranches(tranches) //
+                .withInterestTypeAsDecliningBalance() //
+                .withSubmittedOnDate("01 March 2014") //
+                .build(clientID.toString(), loanProductID.toString(), null);
+
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java
new file mode 100644
index 0000000..d0018cd
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java
@@ -0,0 +1,241 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class LoanRepaymentRescheduleAtDisbursementTest {
+
+	private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private LoanApplicationApprovalTest loanApplicationApprovalTest;
+    private ResponseSpecification generalResponseSpec;
+    
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.loanApplicationApprovalTest = new LoanApplicationApprovalTest();
+        this.generalResponseSpec = new ResponseSpecBuilder().build();
+    }
+    
+	@SuppressWarnings("unchecked")
+	@Test
+    public void testLoanRepaymentRescheduleAtDisbursement(){
+    	
+        final String approvalAmount = "10000";
+        final String approveDate = "01 March 2015";
+        final String expectedDisbursementDate = "01 March 2015";
+        final String disbursementDate = "01 March 2015";
+        final String adjustRepaymentDate = "16 March 2015";
+        final String recalculationRestFrequencyDate = "01 January 2012";
+         
+        // CREATE CLIENT
+    	final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
+        System.out.println("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------"
+                + clientID);
+
+        // CREATE LOAN MULTIDISBURSAL PRODUCT WITH INTEREST RECALCULATION 
+        final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "0", recalculationRestFrequencyDate,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null);
+        
+        // CREATE TRANCHES
+        List<HashMap> createTranches = new ArrayList<>();
+        createTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("01 March 2015", "5000"));
+        createTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("01 May 2015", "5000"));
+    	
+        // APPROVE TRANCHES
+        List<HashMap> approveTranches = new ArrayList<>();
+        approveTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("01 March 2015", "5000"));
+        approveTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("01 May 2015", "5000"));
+        
+        // APPLY FOR TRANCHE LOAN WITH INTEREST RECALCULATION 
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate,
+        		recalculationRestFrequencyDate, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0), createTranches);
+        
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        
+        // VALIDATE THE LOAN STATUS
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, expectedDisbursementDate, approvalAmount,
+                loanID, approveTranches);
+        
+        // VALIDATE THE LOAN IS APPROVED
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        
+        // DISBURSE A FIRST TRANCHE
+        this.loanTransactionHelper.disburseLoanWithRepaymentReschedule(disbursementDate, loanID, adjustRepaymentDate);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        
+        ArrayList<HashMap> loanRepaymnetSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, generalResponseSpec, loanID);
+        HashMap firstInstallement  = loanRepaymnetSchedule.get(1);
+        Map<String, Object> expectedvalues = new HashMap<>(3);
+        Calendar date = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        date.set(2015, Calendar.MARCH, 16);
+        expectedvalues.put("dueDate", getDateAsArray(date, 0));
+        expectedvalues.put("principalDue", "834.71");
+        expectedvalues.put("interestDue", "49.32");
+        expectedvalues.put("feeChargesDue", "0");
+        expectedvalues.put("penaltyChargesDue", "0");
+        expectedvalues.put("totalDueForPeriod", "884.03");
+        
+        // VALIDATE REPAYMENT SCHEDULE
+        verifyLoanRepaymentSchedule(firstInstallement, expectedvalues);
+        
+    }
+	
+	private void verifyLoanRepaymentSchedule(final HashMap firstInstallement, final Map<String, Object> expectedvalues) {
+       
+		assertEquals(expectedvalues.get("dueDate"), firstInstallement.get("dueDate"));
+		assertEquals(String.valueOf(expectedvalues.get("principalDue")), String.valueOf(firstInstallement.get("principalDue")));
+		assertEquals(String.valueOf(expectedvalues.get("interestDue")), String.valueOf(firstInstallement.get("interestDue")));
+		assertEquals(String.valueOf(expectedvalues.get("feeChargesDue")), String.valueOf(firstInstallement.get("feeChargesDue")));
+		assertEquals(String.valueOf(expectedvalues.get("penaltyChargesDue")), String.valueOf(firstInstallement.get("penaltyChargesDue")));
+		assertEquals(String.valueOf(expectedvalues.get("totalDueForPeriod")), String.valueOf(firstInstallement.get("totalDueForPeriod")));
+
+    }
+    
+    private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
+            final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
+            final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
+            final String recalculationRestFrequencyDate, final String preCloseInterestCalculationStrategy, final Account[] accounts) {
+        final String recalculationCompoundingFrequencyType = null;
+        final String recalculationCompoundingFrequencyInterval = null;
+        final String recalculationCompoundingFrequencyDate = null;
+        return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod,
+                rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval,
+                recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval,
+                recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false);
+    }
+    
+    private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
+            final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
+            final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
+            final String recalculationRestFrequencyDate, final String recalculationCompoundingFrequencyType,
+            final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate,
+            final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId,
+            boolean isArrearsBasedOnOriginalSchedule) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        LoanProductTestBuilder builder = new LoanProductTestBuilder()
+                .withPrincipal("10000.00")
+                .withNumberOfRepayments("12")
+                .withRepaymentAfterEvery("2")
+                .withRepaymentTypeAsWeek()
+                .withinterestRatePerPeriod("2")
+                .withInterestRateFrequencyTypeAsMonths()
+                .withTranches(true)
+                .withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+                .withRepaymentStrategy(repaymentStrategy)
+                .withInterestTypeAsDecliningBalance()
+                .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
+                        preCloseInterestCalculationStrategy)
+                .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval,
+                        recalculationRestFrequencyDate)
+                .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType,
+                        recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate);
+        if (accounts != null) {
+            builder = builder.withAccountingRulePeriodicAccrual(accounts);
+        }
+
+        if (isArrearsBasedOnOriginalSchedule) builder = builder.withArrearsConfiguration();
+
+        final String loanProductJSON = builder.build(chargeId);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+    
+    private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
+            final String disbursementDate, final String restStartDate, final String repaymentStrategy, final List<HashMap> charges, List<HashMap> tranches) {
+        final String graceOnInterestPayment = null;
+        final String compoundingStartDate = null;
+        final String graceOnPrincipalPayment = null;
+        return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate,
+                compoundingStartDate, repaymentStrategy, charges, graceOnInterestPayment, graceOnPrincipalPayment,tranches);
+    }
+    
+    private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
+            final String disbursementDate, final String restStartDate, final String compoundingStartDate, final String repaymentStrategy,
+            final List<HashMap> charges, final String graceOnInterestPayment, final String graceOnPrincipalPayment, List<HashMap> tranches) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal("10000.00") //
+                .withLoanTermFrequency("24") //
+                .withLoanTermFrequencyAsWeeks() //
+                .withNumberOfRepayments("12") //
+                .withRepaymentEveryAfter("2") //
+                .withRepaymentFrequencyTypeAsWeeks() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withTranches(tranches)
+                .withFixedEmiAmount("") //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeAsDays() //
+                .withInterestCalculationPeriodTypeAsDays() //
+                .withExpectedDisbursementDate(disbursementDate) //
+                .withSubmittedOnDate(disbursementDate) //
+                .withRestFrequencyDate(restStartDate)//
+                .withwithRepaymentStrategy(repaymentStrategy) //
+                .withCharges(charges)//
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+    
+    private List getDateAsArray(Calendar date, int addPeriod) {
+        return getDateAsArray(date, addPeriod, Calendar.DAY_OF_MONTH);
+    }
+
+    private List getDateAsArray(Calendar date, int addvalue, int type) {
+    	date.add(type, addvalue);
+        return new ArrayList<>(Arrays.asList(date.get(Calendar.YEAR), date.get(Calendar.MONTH) + 1,
+        		date.get(Calendar.DAY_OF_MONTH)));
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
new file mode 100644
index 0000000..2b00a98
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
@@ -0,0 +1,219 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanRescheduleRequestTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/** 
+ * Test the creation, approval and rejection of a loan reschedule request 
+ **/
+@SuppressWarnings({ "rawtypes" })
+public class LoanRescheduleRequestTest {
+	private ResponseSpecification responseSpec;
+	private ResponseSpecification generalResponseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private LoanRescheduleRequestHelper loanRescheduleRequestHelper;
+    private Integer clientId;
+    private Integer loanProductId;
+    private Integer loanId;
+    private Integer loanRescheduleRequestId;
+    private String loanPrincipalAmount = "100000.00";
+    private String numberOfRepayments = "12";
+    private String interestRatePerPeriod = "18";
+    private String dateString = "4 September 2014";
+    
+    @Before
+    public void initialize() {
+    	Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, this.responseSpec);
+
+        this.generalResponseSpec = new ResponseSpecBuilder().build();
+        
+        // create all required entities
+        this.createRequiredEntities();
+    }
+    
+    /** 
+     * Creates the client, loan product, and loan entities 
+     **/
+    private void createRequiredEntities() {
+    	this.createClientEntity();
+    	this.createLoanProductEntity();
+    	this.createLoanEntity();
+    }
+    
+    /** 
+     * create a new client 
+     **/ 
+    private void createClientEntity() {
+    	this.clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+    	
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, this.clientId);
+    }
+    
+    /** 
+     * create a new loan product 
+     **/
+    private void createLoanProductEntity() {
+    	System.out.println("---------------------------------CREATING LOAN PRODUCT------------------------------------------");
+    	
+    	final String loanProductJSON = new LoanProductTestBuilder()
+    			.withPrincipal(loanPrincipalAmount)
+    			.withNumberOfRepayments(numberOfRepayments)
+    			.withinterestRatePerPeriod(interestRatePerPeriod)
+    			.withInterestRateFrequencyTypeAsYear()
+    			.build(null);
+    	
+    	this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    	System.out.println("Successfully created loan product  (ID: " + this.loanProductId + ")");
+    }
+    
+    /** 
+     * submit a new loan application, approve and disburse the loan 
+     **/
+    private void createLoanEntity() {
+    	System.out.println("---------------------------------NEW LOAN APPLICATION------------------------------------------");
+    	
+    	final String loanApplicationJSON = new LoanApplicationTestBuilder()
+    			.withPrincipal(loanPrincipalAmount)
+    			.withLoanTermFrequency(numberOfRepayments)
+                .withLoanTermFrequencyAsMonths()
+                .withNumberOfRepayments(numberOfRepayments)
+                .withRepaymentEveryAfter("1")
+                .withRepaymentFrequencyTypeAsMonths()
+                .withAmortizationTypeAsEqualInstallments()
+                .withInterestCalculationPeriodTypeAsDays()
+                .withInterestRatePerPeriod(interestRatePerPeriod)
+                .withLoanTermFrequencyAsMonths()
+                .withSubmittedOnDate(dateString)
+                .withExpectedDisbursementDate(dateString)
+                .withPrincipalGrace("2")
+                .withInterestGrace("2")
+    			.build(this.clientId.toString(), this.loanProductId.toString(), null);
+    	
+    	this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    	
+    	System.out.println("Sucessfully created loan (ID: " + this.loanId + ")");
+    	
+    	this.approveLoanApplication();
+    	this.disburseLoan();
+    }
+    
+    /** 
+     * approve the loan application 
+     **/
+    private void approveLoanApplication() {
+    	
+    	if(this.loanId != null) {
+    		this.loanTransactionHelper.approveLoan(this.dateString, this.loanId);
+    		System.out.println("Successfully approved loan (ID: " + this.loanId + ")");
+    	}
+    }
+    
+    /** 
+     * disburse the newly created loan 
+     **/
+    private void disburseLoan() {
+    	
+    	if(this.loanId != null) {
+    		this.loanTransactionHelper.disburseLoan(this.dateString, this.loanId);
+    		System.out.println("Successfully disbursed loan (ID: " + this.loanId + ")");
+    	}
+    }
+    
+    /** 
+     * create new loan reschedule request 
+     **/
+    private void createLoanRescheduleRequest() {
+    	System.out.println("---------------------------------CREATING LOAN RESCHEDULE REQUEST------------------------------------------");
+    	
+    	final String requestJSON = new LoanRescheduleRequestTestBuilder().build(this.loanId.toString());
+    	
+    	this.loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
+    	this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId);
+    	
+    	System.out.println("Successfully created loan reschedule request (ID: " + this.loanRescheduleRequestId + ")");
+    }
+    
+    @Test
+    public void testCreateLoanRescheduleRequest() {
+    	this.createLoanRescheduleRequest();
+    }
+    
+    @Test
+    public void testRejectLoanRescheduleRequest() {
+    	this.createLoanRescheduleRequest();
+    	
+    	System.out.println("-----------------------------REJECTING LOAN RESCHEDULE REQUEST--------------------------");
+    	
+    	final String requestJSON = new LoanRescheduleRequestTestBuilder().getRejectLoanRescheduleRequestJSON();
+    	this.loanRescheduleRequestHelper.rejectLoanRescheduleRequest(this.loanRescheduleRequestId, requestJSON);
+    	
+    	final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum");
+    	assertTrue((Boolean)response.get("rejected"));
+    	
+    	System.out.println("Successfully rejected loan reschedule request (ID: " + this.loanRescheduleRequestId + ")");
+    }
+    
+    @Test
+    public void testApproveLoanRescheduleRequest() {
+    	this.createLoanRescheduleRequest();
+    	
+    	System.out.println("-----------------------------APPROVING LOAN RESCHEDULE REQUEST--------------------------");
+    	
+    	final String requestJSON = new LoanRescheduleRequestTestBuilder().getApproveLoanRescheduleRequestJSON();
+    	this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId, requestJSON);
+    	
+    	final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum");
+    	assertTrue((Boolean)response.get("approved"));
+    	
+    	final Integer numberOfRepayments = (Integer) this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec, loanId, "numberOfRepayments");
+    	final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
+    	final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
+    	
+    	assertEquals("NUMBER OF REPAYMENTS SHOULD BE 16, NOT 12", "16", numberOfRepayments.toString());
+    	assertEquals("TOTAL EXPECTED REPAYMENT MUST BE EQUAL TO 118000.0", "118000.0", totalExpectedRepayment.toString());
+    	
+    	System.out.println("Successfully approved loan reschedule request (ID: " + this.loanRescheduleRequestId + ")");
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanWithWaiveInterestAndWriteOffIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanWithWaiveInterestAndWriteOffIntegrationTest.java
new file mode 100644
index 0000000..5258337
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanWithWaiveInterestAndWriteOffIntegrationTest.java
@@ -0,0 +1,204 @@
+/**
+ * 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.integrationtests;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Client Loan Integration Test for checking Loan Disbursement with Waive
+ * Interest and Write-Off.
+ */
+@SuppressWarnings({ "rawtypes" })
+public class LoanWithWaiveInterestAndWriteOffIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    private final String LP_PRINCIPAL = "12,000.00", LP_REPAYMENTS = "2", LP_REPAYMENT_PERIOD = "6", LP_INTEREST_RATE = "1",
+            PRINCIPAL = "4,500.00", LOAN_TERM_FREQUENCY = "18", NUMBER_OF_REPAYMENTS = "9", REPAYMENT_PERIOD = "2",
+            DISBURSEMENT_DATE = "30 October 2010", LOAN_APPLICATION_SUBMISSION_DATE = "23 September 2010",
+            EXPECTED_DISBURSAL_DATE = "28 October 2010", RATE_OF_INTEREST_PER_PERIOD = "2", DATE_OF_JOINING = "04 March 2009",
+            INTEREST_VALUE_AMOUNT = "40.00";
+    private LoanTransactionHelper loanTransactionHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void checkClientLoanCreateAndDisburseFlow() {
+        // CREATE CLIENT
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // CREATE LOAN PRODUCT
+        final Integer loanProductID = createLoanProduct();
+        // APPLY FOR LOAN
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("28 September 2010", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        // UNDO APPROVAL
+        loanStatusHashMap = this.loanTransactionHelper.undoApproval(loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------RE-APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("1 October 2010", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        // DISBURSE
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(this.DISBURSEMENT_DATE, loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // PERFORM REPAYMENTS AND CHECK LOAN STATUS
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, 4000.0F, loanID);
+        this.loanTransactionHelper.makeRepayment("1 January 2011", 540.0f, loanID);
+
+        // UNDO DISBURSE LOAN
+        loanStatusHashMap = this.loanTransactionHelper.undoDisbursal(loanID);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        // DIBURSE AGAIN
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(this.DISBURSEMENT_DATE, loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // MAKE REPAYMENTS
+        final float repayment_with_interest = 540.0f;
+        final float repayment_without_interest = 500.0f;
+
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, 4000.0F, loanID);
+        this.loanTransactionHelper.makeRepayment("1 January 2011", repayment_with_interest, loanID);
+        this.loanTransactionHelper.makeRepayment("1 March 2011", repayment_with_interest, loanID);
+        this.loanTransactionHelper.waiveInterest("1 May 2011", this.INTEREST_VALUE_AMOUNT, loanID);
+        this.loanTransactionHelper.makeRepayment("1 May 2011", repayment_without_interest, loanID);
+        this.loanTransactionHelper.makeRepayment("1 July 2011", repayment_with_interest, loanID);
+        this.loanTransactionHelper.waiveInterest("1 September 2011", this.INTEREST_VALUE_AMOUNT, loanID);
+        this.loanTransactionHelper.makeRepayment("1 September 2011", repayment_without_interest, loanID);
+        this.loanTransactionHelper.makeRepayment("1 November 2011", repayment_with_interest, loanID);
+        this.loanTransactionHelper.waiveInterest("1 January 2012", this.INTEREST_VALUE_AMOUNT, loanID);
+        this.loanTransactionHelper.makeRepayment("1 January 2012", repayment_without_interest, loanID);
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(7, 1000.0f, loanID);
+
+        // WRITE OFF LOAN AND CHECK ACCOUNT IS CLOSED
+        LoanStatusChecker.verifyLoanAccountIsClosed(this.loanTransactionHelper.writeOffLoan("1 March 2012", loanID));
+
+    }
+
+    @Test
+    public void checkClientLoan_WRITTEN_OFF() {
+        // CREATE CLIENT
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+        // CREATE LOAN PRODUCT
+        final Integer loanProductID = createLoanProduct();
+        // APPLY FOR LOAN
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("28 September 2010", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        // DISBURSE
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(this.DISBURSEMENT_DATE, loanID);
+        System.out.println("DISBURSE " + loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // MAKE REPAYMENTS
+        final float repayment_with_interest = 680.0f;
+
+        this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, 4000.0F, loanID);
+        this.loanTransactionHelper.makeRepayment("1 January 2011", repayment_with_interest, loanID);
+
+        HashMap toLoanSummaryAfter = this.loanTransactionHelper.getLoanSummary(requestSpec, responseSpec, loanID);
+        Assert.assertTrue("Checking for Principal paid ",
+                new Float("500.0").compareTo(new Float(String.valueOf(toLoanSummaryAfter.get("principalPaid")))) == 0);
+        Assert.assertTrue("Checking for interestPaid paid ",
+                new Float("180.0").compareTo(new Float(String.valueOf(toLoanSummaryAfter.get("interestPaid")))) == 0);
+        Assert.assertTrue("Checking for total paid ",
+                new Float("680.0").compareTo(new Float(String.valueOf(toLoanSummaryAfter.get("totalRepayment")))) == 0);
+
+        // WRITE OFF LOAN AND CHECK ACCOUNT IS CLOSED
+        LoanStatusChecker.verifyLoanAccountIsClosed(this.loanTransactionHelper.writeOffLoan("1 January 2011", loanID));
+        toLoanSummaryAfter = this.loanTransactionHelper.getLoanSummary(requestSpec, responseSpec, loanID);
+        Assert.assertTrue("Checking for Principal written off ",
+                new Float("4000.0").compareTo(new Float(String.valueOf(toLoanSummaryAfter.get("principalWrittenOff")))) == 0);
+        Assert.assertTrue("Checking for interestPaid written off ",
+                new Float("1440.0").compareTo(new Float(String.valueOf(toLoanSummaryAfter.get("interestWrittenOff")))) == 0);
+        Assert.assertTrue("Checking for total written off ",
+                new Float("5440.0").compareTo(new Float(String.valueOf(toLoanSummaryAfter.get("totalWrittenOff")))) == 0);
+
+    }
+
+    private Integer createLoanProduct() {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(this.LP_PRINCIPAL).withRepaymentTypeAsMonth()
+                .withRepaymentAfterEvery(this.LP_REPAYMENT_PERIOD).withNumberOfRepayments(this.LP_REPAYMENTS).withRepaymentTypeAsMonth()
+                .withinterestRatePerPeriod(this.LP_INTEREST_RATE).withInterestRateFrequencyTypeAsMonths()
+                .withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().build(null);
+
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(this.PRINCIPAL)
+                .withLoanTermFrequency(this.LOAN_TERM_FREQUENCY).withLoanTermFrequencyAsMonths()
+                .withNumberOfRepayments(this.NUMBER_OF_REPAYMENTS).withRepaymentEveryAfter(this.REPAYMENT_PERIOD)
+                .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod(this.RATE_OF_INTEREST_PER_PERIOD)
+                .withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualInstallments()
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate(this.EXPECTED_DISBURSAL_DATE)
+                .withSubmittedOnDate(this.LOAN_APPLICATION_SUBMISSION_DATE).build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanWithdrawnByApplicantIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanWithdrawnByApplicantIntegrationTest.java
new file mode 100644
index 0000000..4313c39
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanWithdrawnByApplicantIntegrationTest.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.integrationtests;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class LoanWithdrawnByApplicantIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void loanWithdrawnByApplicant() {
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+        final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder().build(null));
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        this.loanTransactionHelper.withdrawLoanApplicationByClient("03 April 2012", loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsNotActive(loanStatusHashMap);
+
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID) {
+        final String loanApplication = new LoanApplicationTestBuilder().withPrincipal("5000").withLoanTermFrequency("5")
+                .withLoanTermFrequencyAsMonths().withNumberOfRepayments("5").withRepaymentEveryAfter("1")
+                .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("2").withExpectedDisbursementDate("04 April 2012")
+                .withSubmittedOnDate("02 April 2012").build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplication);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/MinimumDaysBetweenDisbursalAndFirstRepaymentTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/MinimumDaysBetweenDisbursalAndFirstRepaymentTest.java
new file mode 100644
index 0000000..f0d6f19
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/MinimumDaysBetweenDisbursalAndFirstRepaymentTest.java
@@ -0,0 +1,220 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.CalendarHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GroupHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Test the creation, approval and rejection of a loan reschedule request
+ **/
+@SuppressWarnings({ "rawtypes" })
+public class MinimumDaysBetweenDisbursalAndFirstRepaymentTest {
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpecForStatusCode403;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private Integer clientId;
+    private Integer groupId;
+    private Integer groupCalendarId;
+    private Integer loanProductId;
+    private Integer loanId;
+    private final String loanPrincipalAmount = "100000.00";
+    private final String numberOfRepayments = "12";
+    private final String interestRatePerPeriod = "18";
+    private final String groupActivationDate = "1 August 2014";
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+    }
+
+    /*
+     * MinimumDaysBetweenDisbursalAndFirstRepayment is set to 7 days and days
+     * between disbursal date and first repayment is set as 7. system should
+     * allow to create this loan and allow to disburse
+     */
+    @Test
+    public void createLoanEntity_WITH_DAY_BETWEEN_DISB_DATE_AND_REPAY_START_DATE_GREATER_THAN_MIN_DAY_CRITERIA() {
+
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        // create all required entities
+        this.createRequiredEntities();
+
+        final String disbursalDate = "4 September 2014";
+        final String firstRepaymentDate = "11 September 2014";
+
+        final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount)
+                .withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsWeeks().withNumberOfRepayments(numberOfRepayments)
+                .withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments()
+                .withInterestCalculationPeriodTypeAsDays().withInterestRatePerPeriod(interestRatePerPeriod)
+                .withRepaymentFrequencyTypeAsWeeks().withSubmittedOnDate(disbursalDate).withExpectedDisbursementDate(disbursalDate)
+                .withPrincipalGrace("2").withInterestGrace("2").withFirstRepaymentDate(firstRepaymentDate)
+                .build(this.clientId.toString(), this.loanProductId.toString(), null);
+
+        this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+
+        // Test for loan account is created
+        Assert.assertNotNull(this.loanId);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        // Test for loan account is created, can be approved
+        this.loanTransactionHelper.approveLoan(disbursalDate, this.loanId);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        // Test for loan account approved can be disbursed
+        this.loanTransactionHelper.disburseLoan(disbursalDate, this.loanId);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+    }
+
+    /*
+     * MinimumDaysBetweenDisbursalAndFirstRepayment is set to 7 days and days
+     * between disbursal date and first repayment is set as 7. system should
+     * allow to create this loan and allow to disburse
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void createLoanEntity_WITH_DAY_BETWEEN_DISB_DATE_AND_REPAY_START_DATE_LESS_THAN_MIN_DAY_CRITERIA() {
+
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpecForStatusCode403 = new ResponseSpecBuilder().expectStatusCode(403).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        // create all required entities
+        this.createRequiredEntities();
+
+        // loanTransactionHelper is reassigned to accept 403 status code from
+        // server
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode403);
+
+        final String disbursalDate = "4 September 2014";
+        final String firstRepaymentDate = "5 September 2014";
+
+        final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount)
+                .withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsWeeks().withNumberOfRepayments(numberOfRepayments)
+                .withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments()
+                .withInterestCalculationPeriodTypeAsDays().withInterestRatePerPeriod(interestRatePerPeriod)
+                .withRepaymentFrequencyTypeAsWeeks().withSubmittedOnDate(disbursalDate).withExpectedDisbursementDate(disbursalDate)
+                .withPrincipalGrace("2").withInterestGrace("2").withFirstRepaymentDate(firstRepaymentDate)
+                .build(this.clientId.toString(), this.loanProductId.toString(), null);
+
+        List<HashMap> error = (List<HashMap>) this.loanTransactionHelper.createLoanAccount(loanApplicationJSON,
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("error.msg.loan.days.between.first.repayment.and.disbursal.are.less.than.minimum.allowed",
+                error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    /**
+     * Creates the client, loan product, and loan entities
+     **/
+    private void createRequiredEntities() {
+        final String minimumDaysBetweenDisbursalAndFirstRepayment = "7"; // &
+                                                                         // days
+        this.createGroupEntityWithCalendar();
+        this.createClientEntity();
+        this.associateClientToGroup(this.groupId, this.clientId);
+        this.createLoanProductEntity(minimumDaysBetweenDisbursalAndFirstRepayment);
+
+    }
+
+    /*
+     * Associate client to the group
+     */
+
+    private void associateClientToGroup(final Integer groupId, final Integer clientId) {
+        GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupId.toString(), clientId.toString());
+        GroupHelper.verifyGroupMembers(this.requestSpec, this.responseSpec, groupId, clientId);
+    }
+
+    /*
+     * Create a new group
+     */
+
+    private void createGroupEntityWithCalendar() {
+        this.groupId = GroupHelper.createGroup(this.requestSpec, this.responseSpec, this.groupActivationDate);
+        GroupHelper.verifyGroupCreatedOnServer(this.requestSpec, this.responseSpec, this.groupId);
+
+        final String startDate = this.groupActivationDate;
+        final String frequency = "2"; // 2:Weekly
+        final String interval = "1"; // Every one week
+        final String repeatsOnDay = "1"; // 1:Monday
+
+        this.setGroupCalendarId(CalendarHelper.createMeetingCalendarForGroup(this.requestSpec, this.responseSpec, this.groupId, startDate,
+                frequency, interval, repeatsOnDay));
+    }
+
+    /**
+     * create a new client
+     **/
+    private void createClientEntity() {
+        this.clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, this.clientId);
+    }
+
+    /**
+     * create a new loan product
+     **/
+    private void createLoanProductEntity(final String minimumDaysBetweenDisbursalAndFirstRepayment) {
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount)
+                .withNumberOfRepayments(numberOfRepayments).withinterestRatePerPeriod(interestRatePerPeriod)
+                .withInterestRateFrequencyTypeAsYear()
+                .withMinimumDaysBetweenDisbursalAndFirstRepayment(minimumDaysBetweenDisbursalAndFirstRepayment).build(null);
+        this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    public Integer getGroupCalendarId() {
+        return groupCalendarId;
+    }
+
+    public void setGroupCalendarId(Integer groupCalendarId) {
+        this.groupCalendarId = groupCalendarId;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/OfficeIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/OfficeIntegrationTest.java
new file mode 100644
index 0000000..574ba6c
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/OfficeIntegrationTest.java
@@ -0,0 +1,66 @@
+/**
+ * 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.integrationtests;
+
+import org.apache.fineract.integrationtests.common.OfficeDomain;
+import org.apache.fineract.integrationtests.common.OfficeHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class OfficeIntegrationTest {
+
+	private ResponseSpecification responseSpec;
+	private RequestSpecification requestSpec;
+
+	@Before
+	public void setup() {
+		Utils.initializeRESTAssured();
+		this.requestSpec = new RequestSpecBuilder().setContentType(
+				ContentType.JSON).build();
+		this.requestSpec
+				.header("Authorization",
+						"Basic "
+								+ Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+		this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200)
+				.build();
+	}
+
+	@Test
+	public void testOfficeModification() {
+		OfficeHelper oh = new OfficeHelper(requestSpec, responseSpec);
+		int officeId = oh.createOffice("01 July 2007");
+		String name = Utils.randomNameGenerator("New_Office_", 4);
+		String date = "02 July 2007";
+		String[] dateArr = { "2007", "7", "2" };
+
+		oh.updateOffice(officeId, name, date);
+		OfficeDomain newOffice = oh.retrieveOfficeByID(officeId);
+
+		Assert.assertTrue(name.equals(newOffice.getName()));
+		Assert.assertArrayEquals(dateArr, newOffice.getOpeningDate());
+	}
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/PasswordPreferencesIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/PasswordPreferencesIntegrationTest.java
new file mode 100644
index 0000000..388c13e
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/PasswordPreferencesIntegrationTest.java
@@ -0,0 +1,78 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.PasswordPreferencesHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class PasswordPreferencesIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private ResponseSpecification generalResponseSpec;
+
+    @Before
+    public void setUp() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.generalResponseSpec = new ResponseSpecBuilder().build();
+
+    }
+
+    @Test
+    public void updatePasswordPreferences() {
+        String validationPolicyId = "2";
+        PasswordPreferencesHelper.updatePasswordPreferences(requestSpec, responseSpec, validationPolicyId);
+        this.validateIfThePasswordIsUpdated(validationPolicyId);
+    }
+
+    private void validateIfThePasswordIsUpdated(String validationPolicyId){
+        Integer id = PasswordPreferencesHelper.getActivePasswordPreference(requestSpec, responseSpec);
+        assertEquals(validationPolicyId, id.toString());
+        System.out.println("---------------------------------PASSWORD PREFERENCE VALIDATED SUCCESSFULLY-----------------------------------------");
+
+    }
+    
+    @Test
+    public void updateWithInvalidPolicyId() {
+        String invalidValidationPolicyId = "2000";
+        final List<HashMap> error = (List) PasswordPreferencesHelper.updateWithInvalidValidationPolicyId(requestSpec, generalResponseSpec, invalidValidationPolicyId, 
+                CommonConstants.RESPONSE_ERROR);
+        assertEquals("Password Validation Policy with identifier 2000 does not exist", "error.msg.password.validation.policy.id.invalid",
+                error.get(0).get("userMessageGlobalisationCode"));
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/PaymentTypeIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/PaymentTypeIntegrationTest.java
new file mode 100644
index 0000000..4bc07bc
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/PaymentTypeIntegrationTest.java
@@ -0,0 +1,93 @@
+/**
+ * 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.integrationtests;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.PaymentTypeDomain;
+import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class PaymentTypeIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testPaymentType() {
+        String name = PaymentTypeHelper.randomNameGenerator("P_T", 5);
+        String description = PaymentTypeHelper.randomNameGenerator("PT_Desc", 15);
+        Boolean isCashPayment = true;
+        Integer position = 1;
+
+        Integer paymentTypeId = PaymentTypeHelper.createPaymentType(requestSpec, responseSpec, name, description, isCashPayment, position);
+        Assert.assertNotNull(paymentTypeId);
+        PaymentTypeHelper.verifyPaymentTypeCreatedOnServer(requestSpec, responseSpec, paymentTypeId);
+        PaymentTypeDomain paymentTypeResponse = PaymentTypeHelper.retrieveById(requestSpec, responseSpec, paymentTypeId);
+        Assert.assertEquals(name, paymentTypeResponse.getName());
+        Assert.assertEquals(description, paymentTypeResponse.getDescription());
+        Assert.assertEquals(isCashPayment, paymentTypeResponse.getIsCashPayment());
+        Assert.assertEquals(position, paymentTypeResponse.getPosition());
+
+        // Update Payment Type
+        String newName = PaymentTypeHelper.randomNameGenerator("P_TU", 5);
+        String newDescription = PaymentTypeHelper.randomNameGenerator("PTU_Desc", 15);
+        Boolean isCashPaymentUpdatedValue = false;
+        Integer newPosition = 2;
+
+        HashMap request = new HashMap();
+        request.put("name", newName);
+        request.put("description", newDescription);
+        request.put("isCashPayment", isCashPaymentUpdatedValue);
+        request.put("position", newPosition);
+        PaymentTypeHelper.updatePaymentType(paymentTypeId, request, requestSpec, responseSpec);
+        PaymentTypeDomain paymentTypeUpdatedResponse = PaymentTypeHelper.retrieveById(requestSpec, responseSpec, paymentTypeId);
+        Assert.assertEquals(newName, paymentTypeUpdatedResponse.getName());
+        Assert.assertEquals(newDescription, paymentTypeUpdatedResponse.getDescription());
+        Assert.assertEquals(isCashPaymentUpdatedValue, paymentTypeUpdatedResponse.getIsCashPayment());
+        Assert.assertEquals(newPosition, paymentTypeUpdatedResponse.getPosition());
+
+        // Delete
+        Integer deletedPaymentTypeId = PaymentTypeHelper.deletePaymentType(paymentTypeId, requestSpec, responseSpec);
+        Assert.assertEquals(paymentTypeId, deletedPaymentTypeId);
+        ResponseSpecification responseSpecification = new ResponseSpecBuilder().expectStatusCode(404).build();
+        PaymentTypeHelper.retrieveById(requestSpec, responseSpecification, paymentTypeId);
+
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RecurringDepositTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RecurringDepositTest.java
new file mode 100644
index 0000000..b9fd152
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RecurringDepositTest.java
@@ -0,0 +1,2607 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import net.sf.ehcache.transaction.xa.EhcacheXAException;
+
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import org.apache.fineract.integrationtests.common.accounting.Account.AccountType;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositAccountHelper;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositAccountStatusChecker;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositProductHelper;
+import org.apache.fineract.integrationtests.common.recurringdeposit.RecurringDepositAccountHelper;
+import org.apache.fineract.integrationtests.common.recurringdeposit.RecurringDepositAccountStatusChecker;
+import org.apache.fineract.integrationtests.common.recurringdeposit.RecurringDepositProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.joda.time.DateTime;
+import org.joda.time.Months;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes", "unchecked", "static-access" })
+public class RecurringDepositTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private RecurringDepositProductHelper recurringDepositProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+    private AccountHelper accountHelper;
+    private RecurringDepositAccountHelper recurringDepositAccountHelper;
+    private JournalEntryHelper journalEntryHelper;
+    private FinancialActivityAccountHelper financialActivityAccountHelper;
+
+    public static final String WHOLE_TERM = "1";
+    private static final String TILL_PREMATURE_WITHDRAWAL = "2";
+    private static final String DAILY = "1";
+    private static final String MONTHLY = "4";
+    private static final String QUARTERLY = "5";
+    private static final String BI_ANNUALLY = "6";
+    private static final String ANNUALLY = "7";
+    private static final String INTEREST_CALCULATION_USING_DAILY_BALANCE = "1";
+    private static final String INTEREST_CALCULATION_USING_AVERAGE_DAILY_BALANCE = "2";
+    private static final String DAYS_360 = "360";
+    private static final String DAYS_365 = "365";
+    private static final String NONE = "1";
+    private static final String CASH_BASED = "2";
+
+    public static final String MINIMUM_OPENING_BALANCE = "1000.0";
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+    public static final String CLOSURE_TYPE_WITHDRAW_DEPOSIT = "100";
+    public static final String CLOSURE_TYPE_TRANSFER_TO_SAVINGS = "200";
+    public static final String CLOSURE_TYPE_REINVEST = "300";
+    public static final Integer DAILY_COMPOUNDING_INTERVAL = 0;
+    public static final Integer MONTHLY_INTERVAL = 1;
+    public static final Integer QUARTERLY_INTERVAL = 3;
+    public static final Integer BIANNULLY_INTERVAL = 6;
+    public static final Integer ANNUL_INTERVAL = 12;
+
+    public static final Float DEPOSIT_AMOUNT = 2000.0f;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.requestSpec.header("Fineract-Platform-TenantId", "default");
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.financialActivityAccountHelper = new FinancialActivityAccountHelper(this.requestSpec);
+    }
+
+    /***
+     * Test case for Recurring Deposit Account premature closure with
+     * transaction type withdrawal and Cash Based accounting enabled
+     */
+    @Test
+    public void testRecurringDepositAccountWithPrematureClosureTypeWithdrawal() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        /***
+         * Create GL Accounts for product account mapping
+         */
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1;
+        todaysDate.add(Calendar.DATE, numberOfDaysLeft);
+        final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime());
+        final String CLOSED_ON_DATE = dateFormat.format(Calendar.getInstance().getTime());
+
+        /***
+         * Create client for applying Deposit account
+         */
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        /***
+         * Create RD product with CashBased accounting enabled
+         */
+        final String accountingRule = CASH_BASED;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount,
+                liabilityAccount, incomeAccount, expenseAccount);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        /***
+         * Apply for RD account with created product and verify status
+         */
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        /***
+         * Approve the RD account and verify whether account is approved
+         */
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        /***
+         * Activate the RD Account and verify whether account is activated
+         */
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+
+        /***
+         * Perform Deposit transaction and verify journal entries are posted for
+         * the transaction
+         */
+        Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                depositAmount, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(depositTransactionId);
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE, new JournalEntry(
+                depositAmount, JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE, new JournalEntry(
+                depositAmount, JournalEntry.TransactionType.CREDIT));
+
+        /***
+         * Update interest earned field for RD account
+         */
+        recurringDepositAccountId = this.recurringDepositAccountHelper.calculateInterestForRecurringDeposit(recurringDepositAccountId);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        /***
+         * Post interest and verify journal entries
+         */
+        Integer transactionIdForPostInterest = this.recurringDepositAccountHelper
+                .postInterestForRecurringDeposit(recurringDepositAccountId);
+        Assert.assertNotNull(transactionIdForPostInterest);
+
+        HashMap accountSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted");
+
+        final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry);
+
+        /***
+         * Calculate expected premature closure amount
+         */
+        HashMap recurringDepositPrematureData = this.recurringDepositAccountHelper.calculatePrematureAmountForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE);
+
+        /***
+         * Preclose the RD account verify whether account is preClosed
+         */
+        Integer prematureClosureTransactionId = (Integer) this.recurringDepositAccountHelper.prematureCloseForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsPrematureClosed(recurringDepositAccountStatusHashMap);
+
+        /***
+         * Verify journal entry transactions for preclosure transaction
+         */
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float maturityAmount = Float.valueOf(recurringDepositAccountData.get("maturityAmount").toString());
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount,
+                JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount,
+                JournalEntry.TransactionType.DEBIT));
+
+    }
+
+    /***
+     * Test case for Recurring Deposit Account premature closure with
+     * transaction transfers to savings account and Cash Based accounting
+     * enabled
+     */
+    @Test
+    public void testRecurringDepositAccountWithPrematureClosureTypeTransferToSavings() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        /***
+         * Create GL Accounts for product account mapping
+         */
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1;
+        todaysDate.add(Calendar.DATE, numberOfDaysLeft);
+        final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime());
+        final String CLOSED_ON_DATE = dateFormat.format(Calendar.getInstance().getTime());
+
+        /***
+         * Create client for applying Deposit and Savings accounts
+         */
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        /***
+         * Create Savings product with CashBased accounting enabled
+         */
+        final String accountingRule = CASH_BASED;
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, accountingRule,
+                assetAccount, liabilityAccount, incomeAccount, expenseAccount);
+        Assert.assertNotNull(savingsProductID);
+
+        /***
+         * Create Savings account and verify status is pending
+         */
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientId, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        /***
+         * Approve the savings account and verify account is approved
+         */
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        /***
+         * Activate the savings account and verify account is activated
+         */
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        /***
+         * Create RD product with CashBased accounting enabled
+         */
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount,
+                liabilityAccount, incomeAccount, expenseAccount);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        /***
+         * Apply for RD account with created product and verify status
+         */
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        /***
+         * Approve the RD account and verify whether account is approved
+         */
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        /***
+         * Activate the RD Account and verify whether account is activated
+         */
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+
+        /***
+         * Perform Deposit transaction and verify journal entries are posted for
+         * the transaction
+         */
+        Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                depositAmount, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(depositTransactionId);
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE, new JournalEntry(
+                depositAmount, JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE, new JournalEntry(
+                depositAmount, JournalEntry.TransactionType.CREDIT));
+
+        /***
+         * Update interest earned field for RD account
+         */
+        recurringDepositAccountId = this.recurringDepositAccountHelper.calculateInterestForRecurringDeposit(recurringDepositAccountId);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        /***
+         * Post interest and verify journal entries
+         */
+        Integer transactionIdForPostInterest = this.recurringDepositAccountHelper
+                .postInterestForRecurringDeposit(recurringDepositAccountId);
+        Assert.assertNotNull(transactionIdForPostInterest);
+
+        HashMap accountSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted");
+
+        final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry);
+
+        /***
+         * Get saving account balance before preClosing RD account
+         */
+        HashMap savingsSummaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Float balanceBefore = (Float) savingsSummaryBefore.get("accountBalance");
+
+        HashMap recurringDepositPrematureData = this.recurringDepositAccountHelper.calculatePrematureAmountForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE);
+
+        /***
+         * Retrieve mapped financial account for liability transfer
+         */
+        Account financialAccount = getMappedLiabilityFinancialAccount();
+
+        /***
+         * Preclose the RD account verify whether account is preClosed
+         */
+        Integer prematureClosureTransactionId = (Integer) this.recurringDepositAccountHelper.prematureCloseForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_TRANSFER_TO_SAVINGS, savingsId,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsPrematureClosed(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float maturityAmount = Float.valueOf(recurringDepositAccountData.get("maturityAmount").toString());
+        /***
+         * Verify journal entry transactions for preclosure transaction As this
+         * transaction is an account transfer you should get financial account
+         * mapping details and verify amounts
+         */
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount,
+                JournalEntry.TransactionType.CREDIT), new JournalEntry(maturityAmount, JournalEntry.TransactionType.DEBIT));
+
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(financialAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount,
+                JournalEntry.TransactionType.DEBIT), new JournalEntry(maturityAmount, JournalEntry.TransactionType.CREDIT));
+        /***
+         * Verify rd account maturity amount and savings account balance
+         */
+        HashMap recurringDepositData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float prematurityAmount = (Float) recurringDepositData.get("maturityAmount");
+
+        HashMap savingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Float balanceAfter = (Float) savingsSummaryAfter.get("accountBalance");
+        Float expectedSavingsBalance = balanceBefore + prematurityAmount;
+
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+
+        Assert.assertEquals("Verifying Savings Account Balance after Premature Closure", decimalFormat.format(expectedSavingsBalance),
+                decimalFormat.format(balanceAfter));
+
+    }
+
+    /***
+     * Test case for Recurring Deposit Account premature closure with
+     * transaction type ReInvest and Cash Based accounting enabled
+     */
+    @Test
+    public void testRecurringDepositAccountWithPrematureClosureTypeReinvest() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        RecurringDepositAccountHelper recurringDepositAccountHelperValidationError = new RecurringDepositAccountHelper(this.requestSpec,
+                new ResponseSpecBuilder().build());
+
+        /***
+         * Create GL Accounts for product account mapping
+         */
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1;
+        todaysDate.add(Calendar.DATE, numberOfDaysLeft);
+        final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime());
+        final String CLOSED_ON_DATE = dateFormat.format(Calendar.getInstance().getTime());
+
+        /***
+         * Create client for applying Deposit account
+         */
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        /***
+         * Create RD product with CashBased accounting enabled
+         */
+        final String accountingRule = CASH_BASED;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount,
+                liabilityAccount, incomeAccount, expenseAccount);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        ArrayList<HashMap> allRecurringDepositProductsData = this.recurringDepositProductHelper.retrieveAllRecurringDepositProducts(
+                this.requestSpec, this.responseSpec);
+        HashMap recurringDepositProductData = this.recurringDepositProductHelper.retrieveRecurringDepositProductById(this.requestSpec,
+                this.responseSpec, recurringDepositProductId.toString());
+
+        /***
+         * Apply for RD account with created product and verify status
+         */
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        /***
+         * Approve the RD account and verify whether account is approved
+         */
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        /***
+         * Activate the RD Account and verify whether account is activated
+         */
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+
+        /***
+         * Perform Deposit transaction and verify journal entries are posted for
+         * the transaction
+         */
+        Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(depositTransactionId);
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE, new JournalEntry(
+                depositAmount, JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE, new JournalEntry(
+                depositAmount, JournalEntry.TransactionType.CREDIT));
+
+        /***
+         * Update interest earned field for RD account
+         */
+        recurringDepositAccountId = this.recurringDepositAccountHelper.calculateInterestForRecurringDeposit(recurringDepositAccountId);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        /***
+         * Post interest and verify journal entries
+         */
+        Integer transactionIdForPostInterest = this.recurringDepositAccountHelper
+                .postInterestForRecurringDeposit(recurringDepositAccountId);
+        Assert.assertNotNull(transactionIdForPostInterest);
+
+        HashMap accountSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted");
+
+        final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry);
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry);
+
+        /***
+         * Calculate expected premature closure amount
+         */
+        HashMap recurringDepositPrematureData = this.recurringDepositAccountHelper.calculatePrematureAmountForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE);
+
+        /***
+         * Expected to get an error response from api because re-invest option
+         * is not supported for account preClosure
+         */
+        ArrayList<HashMap> errorResponse = (ArrayList<HashMap>) recurringDepositAccountHelperValidationError
+                .prematureCloseForRecurringDeposit(recurringDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_REINVEST, null,
+                        CommonConstants.RESPONSE_ERROR);
+
+        assertEquals("validation.msg.recurringdepositaccount.onAccountClosureId.reinvest.not.allowed",
+                errorResponse.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+    }
+
+    /***
+     * Test case for Update Recurring Deposit Account details
+     */
+    @Test
+    public void testRecurringDepositAccountUpdation() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        todaysDate.add(Calendar.DATE, -1);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateRecurringDepositAccount(clientId.toString(),
+                recurringDepositProductId.toString(), recurringDepositAccountId.toString(), VALID_FROM, VALID_TO, WHOLE_TERM,
+                SUBMITTED_ON_DATE);
+        Assert.assertTrue(modificationsHashMap.containsKey("submittedOnDate"));
+
+    }
+
+    /***
+     * Test case for Approve and Undo Approval of Recurring Deposit Account
+     */
+    @Test
+    public void testRecurringDepositAccountUndoApproval() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.undoApproval(recurringDepositAccountId);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+    }
+
+    /***
+     * Test case for Closure of Recurring Deposit Account(Reject Application)
+     */
+    @Test
+    public void testRecurringDepositAccountRejectedAndClosed() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String REJECTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.rejectApplication(recurringDepositAccountId,
+                REJECTED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsRejected(recurringDepositAccountStatusHashMap);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsClosed(recurringDepositAccountStatusHashMap);
+    }
+
+    /***
+     * Test case for Closure of Recurring Deposit Account(Withdrawn by
+     * applicant)
+     */
+    @Test
+    public void testRecurringDepositAccountWithdrawnByClientAndClosed() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String WITHDRAWN_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.withdrawApplication(recurringDepositAccountId,
+                WITHDRAWN_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsWithdrawn(recurringDepositAccountStatusHashMap);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsClosed(recurringDepositAccountStatusHashMap);
+    }
+
+    /***
+     * Test case for Delete of Recurring Deposit Account.
+     */
+    @Test
+    public void testRecurringDepositAccountIsDeleted() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountId = (Integer) this.recurringDepositAccountHelper.deleteRecurringDepositApplication(
+                recurringDepositAccountId, "resourceId");
+        Assert.assertNotNull(recurringDepositAccountId);
+    }
+
+    /***
+     * Test case for update Recurring deposit account transactions
+     */
+    @Test
+    public void testUpdateAndUndoTransactionForRecurringDepositAccount() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        /***
+         * Create GL Accounts for product account mapping
+         */
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        final String DEPOSIT_DATE = dateFormat.format(todaysDate.getTime());
+
+        /***
+         * Create client for applying Deposit account
+         */
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        /***
+         * Create RD product with CashBased accounting enabled
+         */
+        final String accountingRule = CASH_BASED;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount,
+                liabilityAccount, incomeAccount, expenseAccount);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        /***
+         * Apply for RD account with created product and verify status
+         */
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        /***
+         * Approve the RD account and verify whether account is approved
+         */
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        /***
+         * Activate the RD Account and verify whether account is activated
+         */
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositSummaryBefore = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float balanceBefore = (Float) recurringDepositSummaryBefore.get("accountBalance");
+
+        /***
+         * Perform Deposit transaction and verify journal entries are posted for
+         * the transaction
+         */
+        Integer transactionIdForDeposit = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, DEPOSIT_DATE);
+        Assert.assertNotNull(transactionIdForDeposit);
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, DEPOSIT_DATE, new JournalEntry(DEPOSIT_AMOUNT,
+                JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, DEPOSIT_DATE, new JournalEntry(DEPOSIT_AMOUNT,
+                JournalEntry.TransactionType.CREDIT));
+
+        /***
+         * verify account balances after transactions
+         */
+        Float expectedBalanceAfter = balanceBefore + DEPOSIT_AMOUNT;
+        HashMap recurringDepositSummaryAfter = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float balanceAfter = (Float) recurringDepositSummaryAfter.get("accountBalance");
+
+        Assert.assertEquals("Verifying account balance after deposit", expectedBalanceAfter, balanceAfter);
+
+        /***
+         * Update transaction and verify account balance after transaction
+         */
+        Float updatedTransactionAmount = DEPOSIT_AMOUNT - 1000.0f;
+        Integer updateTransactionId = this.recurringDepositAccountHelper.updateTransactionForRecurringDeposit(recurringDepositAccountId,
+                transactionIdForDeposit, DEPOSIT_DATE, updatedTransactionAmount);
+        Assert.assertNotNull(updateTransactionId);
+
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, DEPOSIT_DATE, new JournalEntry(updatedTransactionAmount,
+                JournalEntry.TransactionType.DEBIT));
+        this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, DEPOSIT_DATE, new JournalEntry(
+                updatedTransactionAmount, JournalEntry.TransactionType.CREDIT));
+
+        expectedBalanceAfter = DEPOSIT_AMOUNT - updatedTransactionAmount;
+        recurringDepositSummaryAfter = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        balanceAfter = (Float) recurringDepositSummaryAfter.get("accountBalance");
+
+        Assert.assertEquals("Verifying account balance after updating Transaction", expectedBalanceAfter, balanceAfter);
+
+        Integer undoTransactionId = this.recurringDepositAccountHelper.undoTransactionForRecurringDeposit(recurringDepositAccountId,
+                updateTransactionId, DEPOSIT_DATE, 0.0f);
+        Assert.assertNotNull(undoTransactionId);
+
+        expectedBalanceAfter = expectedBalanceAfter - updatedTransactionAmount;
+        recurringDepositSummaryAfter = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        balanceAfter = (Float) recurringDepositSummaryAfter.get("accountBalance");
+
+        Assert.assertEquals("Verifying account balance after Undo Transaction", expectedBalanceAfter, balanceAfter);
+
+    }
+
+    /***
+     * Test case for verify maturity amount with monthly compounding and monthly
+     * posting with 365 days in year
+     */
+    @Test
+    public void testMaturityAmountForMonthlyCompoundingAndMonthlyPosting_With_365_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DATE, -(currentDate - 1));
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Float maturityAmount = (Float) recurringDepositAccountData.get("maturityAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositAmount,
+                depositPeriod, interestPerDay, MONTHLY_INTERVAL, MONTHLY_INTERVAL);
+
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        principal = new Float(decimalFormat.format(principal));
+        maturityAmount = new Float(decimalFormat.format(maturityAmount));
+        System.out.println(principal);
+        Assert.assertEquals("Verifying Maturity amount for Recurring Deposit Account", principal, maturityAmount);
+    }
+
+    /***
+     * Test case for verify maturity amount with monthly compounding and monthly
+     * posting with 360 days in year
+     */
+    @Test
+    public void testMaturityAmountForMonthlyCompoundingAndMonthlyPosting_With_360_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DATE, -(currentDate - 1));
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_360, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, MONTHLY, MONTHLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Float maturityAmount = (Float) recurringDepositAccountData.get("maturityAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositAmount,
+                depositPeriod, interestPerDay, MONTHLY_INTERVAL, MONTHLY_INTERVAL);
+
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        principal = new Float(decimalFormat.format(principal));
+        maturityAmount = new Float(decimalFormat.format(maturityAmount));
+        System.out.println(principal);
+        Assert.assertEquals("Verifying Maturity amount for Recurring Deposit Account", principal, maturityAmount);
+    }
+
+    /***
+     * Test case for verify interest posting of RD account
+     */
+    @Test
+    public void testPostInterestForRecurringDeposit() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(depositTransactionId);
+
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("totalDeposits");
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        daysInMonth = (daysInMonth - currentDate) + 1;
+        Float interestToBePosted = new Float(decimalFormat.format(interestPerDay * principal * daysInMonth));
+        principal += interestToBePosted;
+
+        Float expectedBalanceAfter = new Float(decimalFormat.format(principal));
+        System.out.println(expectedBalanceAfter);
+
+        Integer transactionIdForPostInterest = this.recurringDepositAccountHelper
+                .postInterestForRecurringDeposit(recurringDepositAccountId);
+        Assert.assertNotNull(transactionIdForPostInterest);
+
+        HashMap recurringDepositAccountSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float interestAmountPosted = new Float(decimalFormat.format(recurringDepositAccountSummary.get("totalInterestPosted")));
+        Float principalAfter = new Float(decimalFormat.format(recurringDepositAccountSummary.get("accountBalance")));
+
+        Assert.assertEquals("Verifying Amount of Interest Posted to Recurring Deposit Account", interestToBePosted, interestAmountPosted);
+        Assert.assertEquals("Verifying Principal Amount after Interest Posting", expectedBalanceAfter, principalAfter);
+
+    }
+
+    /***
+     * Test case for verify premature closure amount with penal interest for
+     * whole term with closure transaction type withdrawal and 365 days in year
+     */
+    @Test
+    public void testPrematureClosureAmountWithPenalInterestForWholeTerm_With_365_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Float preClosurePenalInterestRate = (Float) recurringDepositAccountData.get("preClosurePenalInterest");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(depositTransactionId);
+
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("totalDeposits");
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        interestRate -= preClosurePenalInterestRate;
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(calendar.getTime()));
+        Integer daysInMonth = calendar.getActualMaximum(Calendar.DATE);
+        daysInMonth = (daysInMonth - currentDate) + 1;
+        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
+        principal += interestPerMonth + depositAmount;
+        calendar.add(Calendar.DATE, daysInMonth);
+        System.out.println(monthDayFormat.format(calendar.getTime()));
+
+        EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(calendar.getTime());
+        Integer transactionIdForDeposit = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(transactionIdForDeposit);
+
+        currentDate = currentDate - 1;
+        interestPerMonth = (float) (interestPerDay * principal * currentDate);
+        System.out.println("IPM = " + interestPerMonth);
+        principal += interestPerMonth;
+        System.out.println("principal = " + principal);
+
+        HashMap recurringDepositPrematureData = this.recurringDepositAccountHelper.calculatePrematureAmountForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.recurringDepositAccountHelper.prematureCloseForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsPrematureClosed(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+
+        principal = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", principal, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify premature closure amount with penal interest for
+     * whole term with closure transaction type withdrawal and 360 days in year
+     */
+    @Test
+    public void testPrematureClosureAmountWithPenalInterestForWholeTerm_With_360_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_360, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, MONTHLY, MONTHLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Float preClosurePenalInterestRate = (Float) recurringDepositAccountData.get("preClosurePenalInterest");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(depositTransactionId);
+
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("totalDeposits");
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        interestRate -= preClosurePenalInterestRate;
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(calendar.getTime()));
+        Integer daysInMonth = calendar.getActualMaximum(Calendar.DATE);
+        daysInMonth = (daysInMonth - currentDate) + 1;
+        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
+        principal += interestPerMonth + depositAmount;
+        calendar.add(Calendar.DATE, daysInMonth);
+        System.out.println(monthDayFormat.format(calendar.getTime()));
+
+        EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(calendar.getTime());
+        Integer transactionIdForDeposit = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(transactionIdForDeposit);
+
+        currentDate = currentDate - 1;
+        interestPerMonth = (float) (interestPerDay * principal * currentDate);
+        System.out.println("IPM = " + interestPerMonth);
+        principal += interestPerMonth;
+        System.out.println("principal = " + principal);
+
+        HashMap recurringDepositPrematureData = this.recurringDepositAccountHelper.calculatePrematureAmountForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.recurringDepositAccountHelper.prematureCloseForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsPrematureClosed(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+
+        principal = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", principal, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify premature closure amount with penal interest till
+     * maturity date with closure transaction type withdrawal and 365 days in
+     * year
+     */
+    @Test
+    public void testPrematureClosureAmountWithPenalInterestTillPrematureWithdrawal_With_365_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, 1);
+        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, TILL_PREMATURE_WITHDRAWAL, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Float preClosurePenalInterestRate = (Float) recurringDepositAccountData.get("preClosurePenalInterest");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Calendar activationDate = Calendar.getInstance();
+        activationDate.add(Calendar.MONTH, -1);
+        activationDate.add(Calendar.DAY_OF_MONTH, -1);
+        DateTime start = new DateTime(activationDate.getTime());
+
+        Calendar prematureClosureDate = Calendar.getInstance();
+        DateTime end = new DateTime(prematureClosureDate.getTime());
+
+        Integer depositedPeriod = Months.monthsBetween(start, end).getMonths();
+
+        Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(depositTransactionId);
+
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("totalDeposits");
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositedPeriod);
+        interestRate -= preClosurePenalInterestRate;
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.MONTH, -1);
+        calendar.add(Calendar.DAY_OF_MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(calendar.getTime()));
+        Integer daysInMonth = calendar.getActualMaximum(Calendar.DATE);
+        daysInMonth = (daysInMonth - currentDate) + 1;
+        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
+        principal += interestPerMonth + depositAmount;
+        calendar.add(Calendar.DATE, daysInMonth);
+        System.out.println(monthDayFormat.format(calendar.getTime()));
+
+        EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(calendar.getTime());
+        Integer transactionIdForDeposit = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(transactionIdForDeposit);
+
+        currentDate = currentDate - 1;
+        interestPerMonth = (float) (interestPerDay * principal * currentDate);
+        System.out.println("IPM = " + interestPerMonth);
+        principal += interestPerMonth;
+        System.out.println("principal = " + principal);
+
+        HashMap recurringDepositPrematureData = this.recurringDepositAccountHelper.calculatePrematureAmountForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.recurringDepositAccountHelper.prematureCloseForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsPrematureClosed(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+
+        principal = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", principal, maturityAmount);
+
+    }
+
+    /***
+     * Test case verify premature closure amount with penal interest till
+     * maturity date with closure transaction type withdrawal and 360 days in
+     * year
+     */
+    @Test
+    public void testPrematureClosureAmountWithPenalInterestTillPrematureWithdrawal_With_360_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, 1);
+        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, TILL_PREMATURE_WITHDRAWAL, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_360, TILL_PREMATURE_WITHDRAWAL, INTEREST_CALCULATION_USING_DAILY_BALANCE, MONTHLY, MONTHLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId,
+                ACTIVATION_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Float preClosurePenalInterestRate = (Float) recurringDepositAccountData.get("preClosurePenalInterest");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Calendar activationDate = Calendar.getInstance();
+        activationDate.add(Calendar.MONTH, -1);
+        activationDate.add(Calendar.DAY_OF_MONTH, -1);
+        DateTime start = new DateTime(activationDate.getTime());
+
+        Calendar prematureClosureDate = Calendar.getInstance();
+        DateTime end = new DateTime(prematureClosureDate.getTime());
+
+        Integer depositedPeriod = Months.monthsBetween(start, end).getMonths();
+
+        Integer transactionIdForDeposit = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(transactionIdForDeposit);
+
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("totalDeposits");
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositedPeriod);
+        interestRate -= preClosurePenalInterestRate;
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.MONTH, -1);
+        calendar.add(Calendar.DAY_OF_MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(calendar.getTime()));
+        Integer daysInMonth = calendar.getActualMaximum(Calendar.DATE);
+        daysInMonth = (daysInMonth - currentDate) + 1;
+        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
+        principal += interestPerMonth + depositAmount;
+        calendar.add(Calendar.DATE, daysInMonth);
+        System.out.println(monthDayFormat.format(calendar.getTime()));
+
+        EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(calendar.getTime());
+        Integer newTransactionIdForDeposit = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId,
+                DEPOSIT_AMOUNT, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(newTransactionIdForDeposit);
+
+        currentDate = currentDate - 1;
+        interestPerMonth = (float) (interestPerDay * principal * currentDate);
+        System.out.println("IPM = " + interestPerMonth);
+        principal += interestPerMonth;
+        System.out.println("principal = " + principal);
+
+        HashMap recurringDepositPrematureData = this.recurringDepositAccountHelper.calculatePrematureAmountForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.recurringDepositAccountHelper.prematureCloseForRecurringDeposit(
+                recurringDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(prematureClosureTransactionId);
+
+        recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsPrematureClosed(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+
+        principal = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", principal, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify maturity amount with daily compounding and monthly
+     * posting with 365 days in year
+     */
+    @Test
+    public void testMaturityAmountForDailyCompoundingAndMonthlyPosting_With_365_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DATE, -(currentDate - 1));
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_365, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, DAILY, MONTHLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+        Float recurringDepositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Float maturityAmount = (Float) recurringDepositAccountData.get("maturityAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, recurringDepositAmount,
+                depositPeriod, interestPerDay, DAILY_COMPOUNDING_INTERVAL, MONTHLY_INTERVAL);
+
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        principal = new Float(decimalFormat.format(principal));
+        maturityAmount = new Float(decimalFormat.format(maturityAmount));
+        System.out.println(principal);
+        Assert.assertEquals("Verifying Maturity amount for Recurring Deposit Account", principal, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify maturity amount with daily compounding and monthly
+     * posting with 360 days in year
+     */
+    @Test
+    public void testMaturityAmountForDailyCompoundingAndMonthlyPosting_With_360_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DATE, -(currentDate - 1));
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_360, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, DAILY, MONTHLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+        Float recurringDepositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Float maturityAmount = (Float) recurringDepositAccountData.get("maturityAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, recurringDepositAmount,
+                depositPeriod, interestPerDay, DAILY_COMPOUNDING_INTERVAL, MONTHLY_INTERVAL);
+
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        principal = new Float(decimalFormat.format(principal));
+        maturityAmount = new Float(decimalFormat.format(maturityAmount));
+        System.out.println(principal);
+        Assert.assertEquals("Verifying Maturity amount for Recurring Deposit Account", principal, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify premature closure amount with Bi-annual interest
+     * compounding and Bi-annual interest posting with 365 days in year
+     */
+    @Test
+    public void testRecurringDepositWithBi_AnnualCompoundingAndPosting_365_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_365, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, BI_ANNUALLY, BI_ANNUALLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+        Float recurringDepositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, recurringDepositAmount,
+                depositPeriod, interestPerDay, BIANNULLY_INTERVAL, BIANNULLY_INTERVAL);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify premature closure amount with Bi-annual interest
+     * compounding and Bi-annual interest posting with 360 days in year
+     */
+    @Test
+    public void testRecurringDepositWithBi_AnnualCompoundingAndPosting_360_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_360, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, BI_ANNUALLY, BI_ANNUALLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+        Float recurringDepositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, recurringDepositAmount,
+                depositPeriod, interestPerDay, BIANNULLY_INTERVAL, BIANNULLY_INTERVAL);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify maturity amount with Daily interest compounding and
+     * annual interest posting with 365 days in year
+     */
+    @Test
+    public void testMaturityAmountForDailyCompoundingAndAnnuallyPosting_With_365_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_365, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, DAILY, ANNUALLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+        Float recurringDepositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, recurringDepositAmount,
+                depositPeriod, interestPerDay, DAILY_COMPOUNDING_INTERVAL, ANNUL_INTERVAL);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify maturity amount with Daily interest compounding and
+     * annual interest posting with 360 days in year
+     */
+    @Test
+    public void testMaturityAmountForDailyCompoundingAndAnnuallyPosting_With_360_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_360, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, DAILY, ANNUALLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+        Float recurringDepositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, recurringDepositAmount,
+                depositPeriod, interestPerDay, DAILY_COMPOUNDING_INTERVAL, ANNUL_INTERVAL);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify premature closure amount with Quarterly interest
+     * compounding and Quarterly interest posting with 365 days in year
+     */
+    @Test
+    public void testRecurringDepositQuarterlyCompoundingAndQuarterlyPosting_365_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_365, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, QUARTERLY, QUARTERLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+        Float recurringDepositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, recurringDepositAmount,
+                depositPeriod, interestPerDay, QUARTERLY_INTERVAL, QUARTERLY_INTERVAL);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    /***
+     * Test case for verify premature closure amount with Quarterly interest
+     * compounding and Quarterly interest posting with 360 days in year
+     */
+    @Test
+    public void testRecurringDepositQuarterlyCompoundingAndQuarterlyPosting_360_Days() {
+        this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+        DateFormat currentMonthFormat = new SimpleDateFormat("MM");
+        DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.YEAR, -1);
+        Integer currentMonth = new Integer(currentMonthFormat.format(todaysDate.getTime()));
+        Integer numberOfMonths = 12 - currentMonth;
+        todaysDate.add(Calendar.MONTH, numberOfMonths);
+        Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        Integer daysLeft = daysInMonth - currentDate;
+        todaysDate.add(Calendar.DATE, (daysLeft + 1));
+        daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        System.out.println(dateFormat.format(todaysDate.getTime()));
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String VALID_TO = null;
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+
+        final String accountingRule = NONE;
+        Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, accountingRule);
+        Assert.assertNotNull(recurringDepositProductId);
+
+        Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
+                VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
+        Assert.assertNotNull(recurringDepositAccountId);
+
+        HashMap modificationsHashMap = this.recurringDepositAccountHelper.updateInterestCalculationConfigForRecurringDeposit(
+                clientId.toString(), recurringDepositProductId.toString(), recurringDepositAccountId.toString(), SUBMITTED_ON_DATE,
+                VALID_FROM, VALID_TO, DAYS_360, WHOLE_TERM, INTEREST_CALCULATION_USING_DAILY_BALANCE, QUARTERLY, QUARTERLY,
+                EXPECTED_FIRST_DEPOSIT_ON_DATE);
+
+        HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(
+                this.requestSpec, this.responseSpec, recurringDepositAccountId.toString());
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap);
+
+        recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId,
+                APPROVED_ON_DATE);
+        RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap);
+
+        HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        HashMap recurringDepositSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId);
+        Float principal = (Float) recurringDepositSummary.get("accountBalance");
+        Float recurringDepositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount");
+        Integer depositPeriod = (Integer) recurringDepositAccountData.get("depositPeriod");
+        HashMap daysInYearMap = (HashMap) recurringDepositAccountData.get("interestCalculationDaysInYearType");
+        Integer daysInYear = (Integer) daysInYearMap.get("id");
+        ArrayList<ArrayList<HashMap>> interestRateChartData = this.recurringDepositProductHelper.getInterestRateChartSlabsByProductId(
+                this.requestSpec, this.responseSpec, recurringDepositProductId);
+
+        Float interestRate = this.recurringDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        double interestRateInFraction = (interestRate / 100);
+        double perDay = (double) 1 / (daysInYear);
+        System.out.println("per day = " + perDay);
+        double interestPerDay = interestRateInFraction * perDay;
+
+        principal = this.recurringDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, recurringDepositAmount,
+                depositPeriod, interestPerDay, QUARTERLY_INTERVAL, QUARTERLY_INTERVAL);
+
+        recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec,
+                this.responseSpec, recurringDepositAccountId);
+        DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US));
+        decimalFormat.applyPattern(".");
+        Float expectedPrematureAmount = new Float(decimalFormat.format(principal));
+        Float maturityAmount = new Float(decimalFormat.format(recurringDepositAccountData.get("maturityAmount")));
+
+        Assert.assertEquals("Verifying Pre-Closure maturity amount", expectedPrematureAmount, maturityAmount);
+
+    }
+
+    private Integer createRecurringDepositProduct(final String validFrom, final String validTo, final String accountingRule,
+            Account... accounts) {
+        System.out.println("------------------------------CREATING NEW RECURRING DEPOSIT PRODUCT ---------------------------------------");
+        RecurringDepositProductHelper recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec);
+        if (accountingRule.equals(CASH_BASED)) {
+            recurringDepositProductHelper = recurringDepositProductHelper.withAccountingRuleAsCashBased(accounts);
+        } else if (accountingRule.equals(NONE)) {
+            recurringDepositProductHelper = recurringDepositProductHelper.withAccountingRuleAsNone();
+        }
+        final String recurringDepositProductJSON = recurringDepositProductHelper.build(validFrom, validTo);
+        return RecurringDepositProductHelper.createRecurringDepositProduct(recurringDepositProductJSON, requestSpec, responseSpec);
+    }
+
+    private Integer applyForRecurringDepositApplication(final String clientID, final String productID, final String validFrom,
+            final String validTo, final String submittedOnDate, final String penalInterestType, final String expectedFirstDepositOnDate) {
+        System.out.println("--------------------------------APPLYING FOR RECURRING DEPOSIT ACCOUNT --------------------------------");
+        final String recurringDepositApplicationJSON = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec)
+                .withSubmittedOnDate(submittedOnDate).withExpectedFirstDepositOnDate(expectedFirstDepositOnDate)
+                .build(clientID, productID, validFrom, validTo, penalInterestType);
+        return this.recurringDepositAccountHelper.applyRecurringDepositApplication(recurringDepositApplicationJSON, this.requestSpec,
+                this.responseSpec);
+    }
+
+    private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String minOpenningBalance, final String accountingRule, Account... accounts) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+        SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+        if (accountingRule.equals(CASH_BASED)) {
+            savingsProductHelper = savingsProductHelper.withAccountingRuleAsCashBased(accounts);
+        } else if (accountingRule.equals(NONE)) {
+            savingsProductHelper = savingsProductHelper.withAccountingRuleAsNone();
+        }
+        final String savingsProductJSON = savingsProductHelper //
+                .withInterestCompoundingPeriodTypeAsDaily() //
+                .withInterestPostingPeriodTypeAsMonthly() //
+                .withInterestCalculationPeriodTypeAsDailyBalance() //
+                .withMinimumOpenningBalance(minOpenningBalance).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    private Account getMappedLiabilityFinancialAccount() {
+        final Integer liabilityTransferFinancialActivityId = FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue();
+        List<HashMap> financialActivities = this.financialActivityAccountHelper.getAllFinancialActivityAccounts(this.responseSpec);
+        final Account financialAccount;
+        /***
+         * if no financial activities are defined for account transfers, create
+         * liability financial accounting mappings
+         */
+        if (financialActivities.isEmpty()) {
+            financialAccount = createLiabilityFinancialAccountTransferType(liabilityTransferFinancialActivityId);
+        } else {
+            /***
+             * extract mapped liability financial account
+             */
+            Account mappedLiabilityAccount = null;
+            for (HashMap financialActivity : financialActivities) {
+                HashMap financialActivityData = (HashMap) financialActivity.get("financialActivityData");
+                if (financialActivityData.get("id").equals(liabilityTransferFinancialActivityId)) {
+                    HashMap glAccountData = (HashMap) financialActivity.get("glAccountData");
+                    mappedLiabilityAccount = new Account((Integer) glAccountData.get("id"), AccountType.LIABILITY);
+                    break;
+                }
+            }
+            /***
+             * If liability transfer is not defined create liability transfer
+             */
+            if (mappedLiabilityAccount == null) {
+                mappedLiabilityAccount = createLiabilityFinancialAccountTransferType(liabilityTransferFinancialActivityId);
+            }
+            financialAccount = mappedLiabilityAccount;
+        }
+        return financialAccount;
+    }
+
+    private Account createLiabilityFinancialAccountTransferType(final Integer liabilityTransferFinancialActivityId) {
+        /***
+         * Create and verify financial account transfer type is created
+         */
+        final Account liabilityAccountForMapping = this.accountHelper.createLiabilityAccount();
+        Integer financialActivityAccountId = (Integer) financialActivityAccountHelper.createFinancialActivityAccount(
+                liabilityTransferFinancialActivityId, liabilityAccountForMapping.getAccountID(), this.responseSpec,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+        Assert.assertNotNull(financialActivityAccountId);
+        assertFinancialActivityAccountCreation(financialActivityAccountId, liabilityTransferFinancialActivityId, liabilityAccountForMapping);
+        return liabilityAccountForMapping;
+    }
+
+    private void assertFinancialActivityAccountCreation(Integer financialActivityAccountId, Integer financialActivityId, Account glAccount) {
+        HashMap mappingDetails = this.financialActivityAccountHelper.getFinancialActivityAccount(financialActivityAccountId,
+                this.responseSpec);
+        Assert.assertEquals(financialActivityId, ((HashMap) mappingDetails.get("financialActivityData")).get("id"));
+        Assert.assertEquals(glAccount.getAccountID(), ((HashMap) mappingDetails.get("glAccountData")).get("id"));
+    }
+
+    /**
+     * Delete the Liability transfer account
+     */
+    @After
+    public void tearDown() {
+        List<HashMap> financialActivities = this.financialActivityAccountHelper.getAllFinancialActivityAccounts(this.responseSpec);
+        for (HashMap financialActivity : financialActivities) {
+            Integer financialActivityAccountId = (Integer) financialActivity.get("id");
+            Integer deletedFinancialActivityAccountId = this.financialActivityAccountHelper.deleteFinancialActivityAccount(
+                    financialActivityAccountId, this.responseSpec, CommonConstants.RESPONSE_RESOURCE_ID);
+            Assert.assertNotNull(deletedFinancialActivityAccountId);
+            Assert.assertEquals(financialActivityAccountId, deletedFinancialActivityAccountId);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RolesTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RolesTest.java
new file mode 100644
index 0000000..50a9006
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RolesTest.java
@@ -0,0 +1,167 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
+import org.apache.fineract.integrationtests.useradministration.roles.RolesHelper;
+import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class RolesTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @SuppressWarnings("cast")
+    @Test
+    public void testCreateRolesStatus() {
+
+        System.out.println("---------------------------------CREATING A ROLE---------------------------------------------");
+        final Integer roleId = RolesHelper.createRole(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(roleId);
+
+        System.out.println("--------------------------------- Getting ROLE -------------------------------");
+        HashMap<String, Object> role = RolesHelper.getRoleDetails(requestSpec, responseSpec, roleId);
+        assertEquals((Integer) role.get("id"), roleId);
+
+    }
+
+    @SuppressWarnings("cast")
+    @Test
+    public void testDisableRolesStatus() {
+
+        System.out.println("---------------------------------CREATING A ROLE---------------------------------------------");
+        final Integer roleId = RolesHelper.createRole(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(roleId);
+
+        System.out.println("--------------------------------- Getting ROLE -------------------------------");
+        HashMap<String, Object> role = RolesHelper.getRoleDetails(requestSpec, responseSpec, roleId);
+        assertEquals((Integer) role.get("id"), roleId);
+
+        System.out.println("--------------------------------- DISABLING ROLE -------------------------------");
+        final Integer disableRoleId = RolesHelper.disableRole(this.requestSpec, this.responseSpec, roleId);
+        assertEquals(disableRoleId, roleId);
+        role = RolesHelper.getRoleDetails(requestSpec, responseSpec, roleId);
+        assertEquals((Integer) role.get("id"), roleId);
+        assertEquals((Boolean) role.get("disabled"), true);
+
+    }
+
+    @SuppressWarnings("cast")
+    @Test
+    public void testEnableRolesStatus() {
+
+        System.out.println("---------------------------------CREATING A ROLE---------------------------------------------");
+        final Integer roleId = RolesHelper.createRole(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(roleId);
+
+        System.out.println("--------------------------------- Getting ROLE -------------------------------");
+        HashMap<String, Object> role = RolesHelper.getRoleDetails(requestSpec, responseSpec, roleId);
+        assertEquals((Integer) role.get("id"), roleId);
+
+        System.out.println("--------------------------------- DISABLING ROLE -------------------------------");
+        final Integer disableRoleId = RolesHelper.disableRole(this.requestSpec, this.responseSpec, roleId);
+        assertEquals(disableRoleId, roleId);
+        role = RolesHelper.getRoleDetails(requestSpec, responseSpec, roleId);
+        assertEquals((Integer) role.get("id"), roleId);
+        assertEquals((Boolean) role.get("disabled"), true);
+
+        System.out.println("--------------------------------- ENABLING ROLE -------------------------------");
+        final Integer enableRoleId = RolesHelper.enableRole(this.requestSpec, this.responseSpec, roleId);
+        assertEquals(enableRoleId, roleId);
+        role = RolesHelper.getRoleDetails(requestSpec, responseSpec, roleId);
+        assertEquals((Integer) role.get("id"), roleId);
+        assertEquals((Boolean) role.get("disabled"), false);
+
+    }
+
+    @SuppressWarnings("cast")
+    @Test
+    public void testDeleteRoleStatus() {
+
+        System.out.println("-------------------------------- CREATING A ROLE---------------------------------------------");
+        final Integer roleId = RolesHelper.createRole(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(roleId);
+
+        System.out.println("--------------------------------- Getting ROLE -------------------------------");
+        HashMap<String, Object> role = RolesHelper.getRoleDetails(requestSpec, responseSpec, roleId);
+        assertEquals((Integer) role.get("id"), roleId);
+
+        System.out.println("--------------------------------- DELETE ROLE -------------------------------");
+        final Integer deleteRoleId = RolesHelper.deleteRole(this.requestSpec, this.responseSpec, roleId);
+        assertEquals(deleteRoleId, roleId);
+    }
+
+    @Test
+    public void testRoleShouldGetDeletedIfNoActiveUserExists() {
+        final Integer roleId = RolesHelper.createRole(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(roleId);
+
+        final Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(staffId);
+
+        final Integer userId = UserHelper.createUser(this.requestSpec, this.responseSpec, roleId, staffId);
+        Assert.assertNotNull(userId);
+
+        final Integer deletedUserId = UserHelper.deleteUser(this.requestSpec, this.responseSpec, userId);
+        Assert.assertEquals(deletedUserId, userId);
+
+        final Integer deletedRoleId = RolesHelper.deleteRole(this.requestSpec, this.responseSpec, roleId);
+        assertEquals(deletedRoleId, roleId);
+    }
+
+    @Test
+    public void testRoleShouldNotGetDeletedIfActiveUserExists() {
+        final Integer roleId = RolesHelper.createRole(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(roleId);
+
+        final Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(staffId);
+
+        final Integer userId = UserHelper.createUser(this.requestSpec, this.responseSpec, roleId, staffId);
+        Assert.assertNotNull(userId);
+
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(403).build();
+        final Integer deletedRoleId = RolesHelper.deleteRole(this.requestSpec, this.responseSpec, roleId);
+        assertNotEquals(deletedRoleId, roleId);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java
new file mode 100644
index 0000000..d560784
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java
@@ -0,0 +1,123 @@
+/**
+ * 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.integrationtests;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked", "static-access" })
+public class SchedulerJobsTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpecForSchedulerJob;
+    private SchedulerJobHelper schedulerJobHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.requestSpec.header("Fineract-Platform-TenantId", "default");
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpecForSchedulerJob = new ResponseSpecBuilder().expectStatusCode(202).build();
+    }
+
+    @Test
+    public void testSchedulerJobs() throws InterruptedException {
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+
+        // Retrieving Status of Scheduler
+        HashMap schedulerStatus = this.schedulerJobHelper.getSchedulerStatus(this.requestSpec, this.responseSpec);
+        Boolean status = (Boolean) schedulerStatus.get("active");
+        if (status == true) {
+            this.schedulerJobHelper.updateSchedulerStatus(this.requestSpec, this.responseSpecForSchedulerJob, "stop");
+            schedulerStatus = this.schedulerJobHelper.getSchedulerStatus(this.requestSpec, this.responseSpec);
+            // Verifying Status of the Scheduler after updation
+            Assert.assertEquals("Verifying Scheduler Job Status", false, schedulerStatus.get("active"));
+        } else {
+            this.schedulerJobHelper.updateSchedulerStatus(this.requestSpec, this.responseSpecForSchedulerJob, "start");
+            schedulerStatus = this.schedulerJobHelper.getSchedulerStatus(this.requestSpec, this.responseSpec);
+            // Verifying Status of the Scheduler after updation
+            Assert.assertEquals("Verifying Scheduler Job Status", true, schedulerStatus.get("active"));
+        }
+
+        // Retrieving All Scheduler Jobs
+        ArrayList<HashMap> allSchedulerJobsData = this.schedulerJobHelper.getAllSchedulerJobs(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(allSchedulerJobsData);
+
+        for (Integer jobIndex = 0; jobIndex < allSchedulerJobsData.size(); jobIndex++) {
+
+            Integer jobId = (Integer) allSchedulerJobsData.get(jobIndex).get("jobId");
+
+            // Retrieving Scheduler Job by ID
+            HashMap schedulerJob = this.schedulerJobHelper.getSchedulerJobById(this.requestSpec, this.responseSpec, jobId.toString());
+            Assert.assertNotNull(schedulerJob);
+
+            Boolean active = (Boolean) schedulerJob.get("active");
+
+            if (active == true) {
+                active = false;
+            } else {
+                active = true;
+            }
+
+            // Updating Scheduler Job
+            HashMap changes = this.schedulerJobHelper.updateSchedulerJob(this.requestSpec, this.responseSpec, jobId.toString(),
+                    active.toString());
+            // Verifying Scheduler Job updation
+            Assert.assertEquals("Verifying Scheduler Job Updation", active, changes.get("active"));
+
+            // Executing Scheduler Job
+            this.schedulerJobHelper.runSchedulerJob(this.requestSpec, jobId.toString());
+
+            // Retrieving Scheduler Job by ID
+            schedulerJob = this.schedulerJobHelper.getSchedulerJobById(this.requestSpec, this.responseSpec, jobId.toString());
+            Assert.assertNotNull(schedulerJob);
+
+            // Waiting for Job to complete
+            while ((Boolean) schedulerJob.get("currentlyRunning") == true) {
+                Thread.sleep(15000);
+                schedulerJob = this.schedulerJobHelper.getSchedulerJobById(this.requestSpec, this.responseSpec, jobId.toString());
+                Assert.assertNotNull(schedulerJob);
+                System.out.println("Job is Still Running");
+            }
+            ArrayList<HashMap> jobHistoryData = this.schedulerJobHelper.getSchedulerJobHistory(this.requestSpec, this.responseSpec,
+                    jobId.toString());
+
+            // Verifying the Status of the Recently executed Scheduler Job
+            Assert.assertEquals("Verifying Last Scheduler Job Status", "success",
+                    jobHistoryData.get(jobHistoryData.size() - 1).get("status"));
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
new file mode 100644
index 0000000..985889a
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
@@ -0,0 +1,915 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.HolidayHelper;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.apache.fineract.integrationtests.common.StandingInstructionsHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositAccountHelper;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositAccountStatusChecker;
+import org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositProductHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "unchecked", "rawtypes", "static-access", "cast" })
+public class SchedulerJobsTestResults {
+
+    private static final String FROM_ACCOUNT_TYPE_LOAN = "1";
+    private static final String FROM_ACCOUNT_TYPE_SAVINGS = "2";
+    private static final String TO_ACCOUNT_TYPE_LOAN = "1";
+    private static final String TO_ACCOUNT_TYPE_SAVINGS = "2";
+    private final String DATE_OF_JOINING = "01 January 2011";
+
+    private final String TRANSACTION_DATE = "01 March 2013";
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+    public static final String MINIMUM_OPENING_BALANCE = "1000";
+
+    Float SP_BALANCE = new Float(MINIMUM_OPENING_BALANCE);
+
+    private static ResponseSpecification responseSpec;
+    private static RequestSpecification requestSpec;
+    private ResponseSpecification responseSpecForSchedulerJob;
+    private SchedulerJobHelper schedulerJobHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+    private LoanTransactionHelper loanTransactionHelper;
+    private HolidayHelper holidayHelper;
+    private GlobalConfigurationHelper globalConfigurationHelper;
+    private AccountHelper accountHelper;
+    private JournalEntryHelper journalEntryHelper;
+    private StandingInstructionsHelper standingInstructionsHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.requestSpec.header("Fineract-Platform-TenantId", "default");
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpecForSchedulerJob = new ResponseSpecBuilder().expectStatusCode(202).build();
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void testApplyAnnualFeeForSavingsJobOutcome() throws InterruptedException {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec,
+                ClientSavingsIntegrationTest.MINIMUM_OPENING_BALANCE);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID,
+                ClientSavingsIntegrationTest.ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        final Integer annualFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsAnnualFeeJSON());
+        Assert.assertNotNull(annualFeeChargeId);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, annualFeeChargeId);
+        ArrayList<HashMap> chargesPendingState = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(1, chargesPendingState.size());
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        String JobName = "Apply Annual Fee For Savings";
+
+        this.schedulerJobHelper.executeJob(JobName);
+        final HashMap chargeData = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, annualFeeChargeId);
+
+        Float chargeAmount = (Float) chargeData.get("amount");
+
+        final HashMap summaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+        Assert.assertEquals("Verifying Annual Fee after Running Scheduler Job for Apply Anual Fee", chargeAmount,
+                (Float) summaryAfter.get("totalAnnualFees"));
+
+    }
+
+    @Test
+    public void testInterestPostingForSavingsJobOutcome() throws InterruptedException {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec,
+                ClientSavingsIntegrationTest.MINIMUM_OPENING_BALANCE);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID,
+                ClientSavingsIntegrationTest.ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+        String JobName = "Post Interest For Savings";
+
+        this.schedulerJobHelper.executeJob(JobName);
+        final HashMap summaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+        Assert.assertNotSame("Verifying the Balance after running Post Interest for Savings Job", summaryBefore.get("accountBalance"),
+                summaryAfter.get("accountBalance"));
+
+    }
+
+    @Test
+    public void testTransferFeeForLoansFromSavingsJobOutcome() throws InterruptedException {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec,
+                ClientSavingsIntegrationTest.MINIMUM_OPENING_BALANCE);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID,
+                ClientSavingsIntegrationTest.ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        final Integer loanProductID = createLoanProduct(null);
+        Assert.assertNotNull(loanProductID);
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), savingsId.toString());
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(AccountTransferTest.LOAN_APPROVAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        Integer specifiedDueDateChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateWithAccountTransferJSON());
+        Assert.assertNotNull(specifiedDueDateChargeId);
+
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(specifiedDueDateChargeId.toString(), "12 March 2013", "100"));
+        ArrayList<HashMap> chargesPendingState = this.loanTransactionHelper.getLoanCharges(loanID);
+        Assert.assertEquals(1, chargesPendingState.size());
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(AccountTransferTest.LOAN_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+        final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+        String JobName = "Transfer Fee For Loans From Savings";
+        this.schedulerJobHelper.executeJob(JobName);
+        final HashMap summaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+        final HashMap chargeData = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, specifiedDueDateChargeId);
+
+        Float chargeAmount = (Float) chargeData.get("amount");
+
+        final Float balance = (Float) summaryBefore.get("accountBalance") - chargeAmount;
+
+        Assert.assertEquals("Verifying the Balance after running Transfer Fee for Loans from Savings", balance,
+                (Float) summaryAfter.get("accountBalance"));
+
+    }
+
+    @Test
+    public void testApplyHolidaysToLoansJobOutcome() throws InterruptedException {
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.holidayHelper = new HolidayHelper(this.requestSpec, this.responseSpec);
+        this.globalConfigurationHelper = new GlobalConfigurationHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        Integer holidayId = this.holidayHelper.createHolidays(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(holidayId);
+
+        final Integer loanProductID = createLoanProduct(null);
+        Assert.assertNotNull(loanProductID);
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null);
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(AccountTransferTest.LOAN_APPROVAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(AccountTransferTest.LOAN_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // Retrieving All Global Configuration details
+        final ArrayList<HashMap> globalConfig = this.globalConfigurationHelper.getAllGlobalConfigurations(this.requestSpec,
+                this.responseSpec);
+        Assert.assertNotNull(globalConfig);
+
+        // Updating Value for reschedule-repayments-on-holidays Global
+        // Configuration
+        Integer configId = (Integer) globalConfig.get(3).get("id");
+        Assert.assertNotNull(configId);
+
+        HashMap configData = this.globalConfigurationHelper.getGlobalConfigurationById(this.requestSpec, this.responseSpec,
+                configId.toString());
+        Assert.assertNotNull(configData);
+
+        Boolean enabled = (Boolean) globalConfig.get(3).get("enabled");
+
+        if (enabled == false) {
+            enabled = true;
+            configId = this.globalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec,
+                    configId.toString(), enabled);
+        }
+        final ArrayList<HashMap> repaymentScheduleDataBeforeJob = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
+                this.responseSpec, loanID);
+
+        holidayId = this.holidayHelper.activateHolidays(this.requestSpec, this.responseSpec, holidayId.toString());
+        Assert.assertNotNull(holidayId);
+
+        String JobName = "Apply Holidays To Loans";
+
+        this.schedulerJobHelper.executeJob(JobName);
+        final ArrayList<HashMap> repaymentScheduleDataAfterJob = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
+                this.responseSpec, loanID);
+
+        HashMap holidayData = this.holidayHelper.getHolidayById(this.requestSpec, this.responseSpec, holidayId.toString());
+        ArrayList<Integer> repaymentsRescheduledDate = (ArrayList<Integer>) holidayData.get("repaymentsRescheduledTo");
+
+        ArrayList<Integer> rescheduleDateAfter = (ArrayList<Integer>) repaymentScheduleDataAfterJob.get(2).get("fromDate");
+
+        Assert.assertEquals("Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job",
+                repaymentsRescheduledDate, repaymentsRescheduledDate);
+
+    }
+
+    @Test
+    public void testApplyDueFeeChargesForSavingsJobOutcome() throws InterruptedException {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec,
+                ClientSavingsIntegrationTest.MINIMUM_OPENING_BALANCE);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID,
+                ClientSavingsIntegrationTest.ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        final Integer specifiedDueDateChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getSavingsSpecifiedDueDateJSON());
+        Assert.assertNotNull(specifiedDueDateChargeId);
+
+        this.savingsAccountHelper.addChargesForSavings(savingsId, specifiedDueDateChargeId);
+        ArrayList<HashMap> chargesPendingState = this.savingsAccountHelper.getSavingsCharges(savingsId);
+        Assert.assertEquals(1, chargesPendingState.size());
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+        String JobName = "Pay Due Savings Charges";
+
+        this.schedulerJobHelper.executeJob(JobName);
+        HashMap summaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+        final HashMap chargeData = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, specifiedDueDateChargeId);
+
+        Float chargeAmount = (Float) chargeData.get("amount");
+
+        final Float balance = (Float) summaryBefore.get("accountBalance") - chargeAmount;
+
+        Assert.assertEquals("Verifying the Balance after running Pay due Savings Charges", balance,
+                (Float) summaryAfter.get("accountBalance"));
+
+    }
+
+    @Test
+    public void testUpdateAccountingRunningBalancesJobOutcome() throws InterruptedException {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+        final Integer accountID = assetAccount.getAccountID();
+
+        final Integer savingsProductID = createSavingsProduct(MINIMUM_OPENING_BALANCE, assetAccount, incomeAccount, expenseAccount,
+                liabilityAccount);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+        final Integer savingsID = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsID);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsID);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsID);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+        // Checking initial Account entries.
+        final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(this.SP_BALANCE, JournalEntry.TransactionType.DEBIT) };
+        final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(this.SP_BALANCE, JournalEntry.TransactionType.CREDIT) };
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.TRANSACTION_DATE, assetAccountInitialEntry);
+        this.journalEntryHelper
+                .checkJournalEntryForLiabilityAccount(liabilityAccount, this.TRANSACTION_DATE, liablilityAccountInitialEntry);
+
+        String JobName = "Update Accounting Running Balances";
+
+        this.schedulerJobHelper.executeJob(JobName);
+        final HashMap runningBalanceAfter = this.accountHelper.getAccountingWithRunningBalanceById(accountID.toString());
+
+        final Integer INT_BALANCE = new Integer(MINIMUM_OPENING_BALANCE);
+
+        Assert.assertEquals("Verifying Account Running Balance after running Update Accounting Running Balances Scheduler Job",
+                INT_BALANCE, runningBalanceAfter.get("organizationRunningBalance"));
+
+    }
+
+    @Test
+    public void testUpdateLoanArrearsAgingJobOutcome() throws InterruptedException {
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        final Integer loanProductID = createLoanProduct(null);
+        Assert.assertNotNull(loanProductID);
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null);
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(AccountTransferTest.LOAN_APPROVAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(AccountTransferTest.LOAN_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        String JobName = "Update Loan Arrears Ageing";
+
+        this.schedulerJobHelper.executeJob(JobName);
+        HashMap loanSummaryData = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
+
+        Float totalLoanArrearsAging = (Float) loanSummaryData.get("principalOverdue") + (Float) loanSummaryData.get("interestOverdue");
+
+        Assert.assertEquals("Verifying Arrears Aging after Running Update Loan Arrears Aging Scheduler Job", totalLoanArrearsAging,
+                loanSummaryData.get("totalOverdue"));
+
+    }
+
+    @Test
+    public void testUpdateLoanPaidInAdvanceJobOutcome() throws InterruptedException {
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+
+        Calendar todayDate = Calendar.getInstance();
+        final String currentDate = dateFormat.format(todayDate.getTime());
+
+        todayDate.add(Calendar.MONTH, -1);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todayDate.getTime());
+
+        todayDate = Calendar.getInstance();
+        todayDate.add(Calendar.DATE, -5);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todayDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        final Integer loanProductID = createLoanProduct(null);
+        Assert.assertNotNull(loanProductID);
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null);
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(AccountTransferTest.LOAN_APPROVAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> loanScheduleBefore = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec,
+                loanID);
+
+        Float totalDueForCurrentPeriod = (Float) loanScheduleBefore.get(1).get("totalDueForPeriod");
+
+        this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+
+        HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
+
+        String JobName = "Update Loan Paid In Advance";
+        this.schedulerJobHelper.executeJob(JobName);
+        // Retrieving Loan Repayment Schedule after the successful
+        // completion of
+        // Update Loan Paid in Advance Scheduler Job
+        ArrayList<HashMap> loanScheduleAfter = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec,
+                loanID);
+
+        loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
+
+        Float totalPaidInAdvance = (Float) loanScheduleAfter.get(1).get("totalPaidInAdvanceForPeriod");
+
+        Assert.assertEquals("Verifying Loan Repayment in Advance after Running Update Loan Paid in Advance Scheduler Job",
+                totalDueForCurrentPeriod, totalPaidInAdvance);
+
+    }
+
+    // Invalid test case as it won't affect summary (Loan summary is properly
+    // updated before running this job)
+    @Ignore
+    @Test
+    public void testUpdateLoanSummaryJobOutcome() throws InterruptedException {
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        final String currentDate = dateFormat.format(todaysDate.getTime());
+
+        todaysDate.add(Calendar.MONTH, -1);
+        final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DATE, -5);
+        final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        final Integer loanProductID = createLoanProduct(null);
+        Assert.assertNotNull(loanProductID);
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null);
+        Assert.assertNotNull(loanID);
+
+        Integer disburseChargeId = ChargesHelper
+                .createCharges(this.requestSpec, this.responseSpec, ChargesHelper.getLoanDisbursementJSON());
+        Assert.assertNotNull(disburseChargeId);
+
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getDisbursementChargesForLoanAsJSON(disburseChargeId.toString()));
+        ArrayList<HashMap> chargesPendingState = this.loanTransactionHelper.getLoanCharges(loanID);
+        Assert.assertEquals(1, chargesPendingState.size());
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(AccountTransferTest.LOAN_APPROVAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        HashMap loanSummaryBefore = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
+
+        String JobName = "Update loan Summary";
+        this.schedulerJobHelper.executeJob(JobName);
+        Float expectedSummaryAfterJob = (Float) loanSummaryBefore.get("totalExpectedRepayment")
+               /* - (Float) loanSummaryBefore.get("feeChargesPaid")*/;
+        HashMap loanSummaryAfter = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
+        Assert.assertEquals("Verifying Loan Summary after Running Update Loan Summary Scheduler Job", expectedSummaryAfterJob,
+                (Float) loanSummaryAfter.get("totalExpectedRepayment"));
+
+    }
+
+    @Test
+    public void testExecuteStandingInstructionsJobOutcome() throws InterruptedException {
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.standingInstructionsHelper = new StandingInstructionsHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.WEEK_OF_YEAR, -1);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+
+        final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime());
+
+        todaysDate.add(Calendar.YEAR, 1);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec,
+                ClientSavingsIntegrationTest.MINIMUM_OPENING_BALANCE);
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer fromSavingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID,
+                ClientSavingsIntegrationTest.ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap fromSavingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, fromSavingsId);
+        SavingsStatusChecker.verifySavingsIsPending(fromSavingsStatusHashMap);
+
+        fromSavingsStatusHashMap = this.savingsAccountHelper.approveSavings(fromSavingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(fromSavingsStatusHashMap);
+
+        fromSavingsStatusHashMap = this.savingsAccountHelper.activateSavings(fromSavingsId);
+        SavingsStatusChecker.verifySavingsIsActive(fromSavingsStatusHashMap);
+
+        final Integer toSavingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID,
+                ClientSavingsIntegrationTest.ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap toSavingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, toSavingsId);
+        SavingsStatusChecker.verifySavingsIsPending(toSavingsStatusHashMap);
+
+        toSavingsStatusHashMap = this.savingsAccountHelper.approveSavings(toSavingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(toSavingsStatusHashMap);
+
+        toSavingsStatusHashMap = this.savingsAccountHelper.activateSavings(toSavingsId);
+        SavingsStatusChecker.verifySavingsIsActive(toSavingsStatusHashMap);
+
+        HashMap fromSavingsSummaryBefore = this.savingsAccountHelper.getSavingsSummary(fromSavingsId);
+        Float fromSavingsBalanceBefore = (Float) fromSavingsSummaryBefore.get("accountBalance");
+
+        HashMap toSavingsSummaryBefore = this.savingsAccountHelper.getSavingsSummary(toSavingsId);
+        Float toSavingsBalanceBefore = (Float) toSavingsSummaryBefore.get("accountBalance");
+
+        Integer standingInstructionId = this.standingInstructionsHelper.createStandingInstruction(clientID.toString(),
+                fromSavingsId.toString(), toSavingsId.toString(), FROM_ACCOUNT_TYPE_SAVINGS, TO_ACCOUNT_TYPE_SAVINGS, VALID_FROM, VALID_TO,
+                MONTH_DAY);
+        Assert.assertNotNull(standingInstructionId);
+
+        String JobName = "Execute Standing Instruction";
+        this.schedulerJobHelper.executeJob(JobName);
+        HashMap fromSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(fromSavingsId);
+        Float fromSavingsBalanceAfter = (Float) fromSavingsSummaryAfter.get("accountBalance");
+
+        HashMap toSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(toSavingsId);
+        Float toSavingsBalanceAfter = (Float) toSavingsSummaryAfter.get("accountBalance");
+
+        final HashMap standingInstructionData = this.standingInstructionsHelper
+                .getStandingInstructionById(standingInstructionId.toString());
+        Float expectedFromSavingsBalance = fromSavingsBalanceBefore - (Float) standingInstructionData.get("amount");
+        Float expectedToSavingsBalance = toSavingsBalanceBefore + (Float) standingInstructionData.get("amount");
+
+        Assert.assertEquals("Verifying From Savings Balance after Successful completion of Scheduler Job", expectedFromSavingsBalance,
+                fromSavingsBalanceAfter);
+        Assert.assertEquals("Verifying To Savings Balance after Successful completion of Scheduler Job", expectedToSavingsBalance,
+                toSavingsBalanceAfter);
+        Integer fromAccountType = PortfolioAccountType.SAVINGS.getValue();
+        Integer transferType = AccountTransferType.ACCOUNT_TRANSFER.getValue();
+        List<HashMap> standinInstructionHistoryData = this.standingInstructionsHelper.getStandingInstructionHistory(fromSavingsId,
+                fromAccountType, clientID, transferType);
+        Assert.assertEquals("Verifying the no of stainding instruction transactions logged for the client", 1,
+                standinInstructionHistoryData.size());
+        HashMap loggedTransaction = standinInstructionHistoryData.get(0);
+
+        Assert.assertEquals("Verifying transferred amount and logged transaction amounts", (Float) standingInstructionData.get("amount"),
+                (Float) loggedTransaction.get("amount"));
+
+    }
+
+    @Test
+    public void testApplyPenaltyForOverdueLoansJobOutcome() throws InterruptedException {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        Integer overdueFeeChargeId = ChargesHelper
+                .createCharges(this.requestSpec, this.responseSpec, ChargesHelper.getLoanOverdueFeeJSON());
+        Assert.assertNotNull(overdueFeeChargeId);
+
+        final Integer loanProductID = createLoanProduct(overdueFeeChargeId.toString());
+        Assert.assertNotNull(loanProductID);
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null);
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(AccountTransferTest.LOAN_APPROVAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(AccountTransferTest.LOAN_APPROVAL_DATE_PLUS_ONE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> repaymentScheduleDataBefore = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
+                this.responseSpec, loanID);
+
+        String JobName = "Apply penalty to overdue loans";
+        this.schedulerJobHelper.executeJob(JobName);
+
+        final HashMap chargeData = ChargesHelper.getChargeById(this.requestSpec, this.responseSpec, overdueFeeChargeId);
+
+        Float chargeAmount = (Float) chargeData.get("amount");
+
+        ArrayList<HashMap> repaymentScheduleDataAfter = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
+                this.responseSpec, loanID);
+
+        Assert.assertEquals("Verifying From Penalty Charges due fot first Repayment after Successful completion of Scheduler Job",
+                chargeAmount, (Float) repaymentScheduleDataAfter.get(1).get("penaltyChargesDue"));
+
+        loanStatusHashMap = this.loanTransactionHelper.undoDisbursal(loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+    }
+
+    @Test
+    public void testUpdateOverdueDaysForNPA() throws InterruptedException {
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientID);
+
+        final Integer loanProductID = createLoanProduct(null);
+        Assert.assertNotNull(loanProductID);
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null);
+        Assert.assertNotNull(loanID);
+
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(AccountTransferTest.LOAN_APPROVAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(AccountTransferTest.LOAN_DISBURSAL_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        final Boolean isNPABefore = (Boolean) this.loanTransactionHelper.getLoanDetail(requestSpec, responseSpec, loanID, "isNPA");
+        Assert.assertFalse(isNPABefore);
+        // Integer jobId = (Integer) allSchedulerJobsData.get(1).get("jobId");
+        String JobName = "Update Non Performing Assets";
+        this.schedulerJobHelper.executeJob(JobName);
+        final Boolean isNPAAfter = (Boolean) this.loanTransactionHelper.getLoanDetail(requestSpec, responseSpec, loanID, "isNPA");
+        Assert.assertTrue(isNPAAfter);
+    }
+
+    @Test
+    public void testInterestTransferForSavings() throws InterruptedException {
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+        FixedDepositProductHelper fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        AccountHelper accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        FixedDepositAccountHelper fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US);
+
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -3);
+        final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.YEAR, 10);
+        final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -2);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        final String WHOLE_TERM = "1";
+
+        Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(clientId);
+        Float balance = new Float(MINIMUM_OPENING_BALANCE) + new Float(FixedDepositAccountHelper.depositAmount);
+        final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, String.valueOf(balance));
+        Assert.assertNotNull(savingsProductID);
+
+        final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientId, savingsProductID,
+                ClientSavingsIntegrationTest.ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsId);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+        HashMap summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
+
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO);
+        Assert.assertNotNull(fixedDepositProductId);
+
+        Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), VALID_FROM,
+                VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, savingsId.toString(), true, fixedDepositAccountHelper);
+        Assert.assertNotNull(fixedDepositAccountId);
+
+        HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountStatusHashMap = fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+        summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        balance = new Float(MINIMUM_OPENING_BALANCE);
+        assertEquals("Verifying Balance", balance, summary.get("accountBalance"));
+
+        fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId);
+
+        HashMap fixedDepositSummary = savingsAccountHelper.getSavingsSummary(fixedDepositAccountId);
+        Float interestPosted = (Float) fixedDepositSummary.get("accountBalance") - new Float(FixedDepositAccountHelper.depositAmount);
+
+        String JobName = "Transfer Interest To Savings";
+        this.schedulerJobHelper.executeJob(JobName);
+        fixedDepositSummary = savingsAccountHelper.getSavingsSummary(fixedDepositAccountId);
+        assertEquals("Verifying opening Balance", new Float(FixedDepositAccountHelper.depositAmount),
+                fixedDepositSummary.get("accountBalance"));
+
+        summary = savingsAccountHelper.getSavingsSummary(savingsId);
+        balance = new Float(MINIMUM_OPENING_BALANCE) + interestPosted;
+        validateNumberForEqualExcludePrecission(String.valueOf(balance), String.valueOf(summary.get("accountBalance")));
+    }
+
+    private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String minOpenningBalance) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+        SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+        final String savingsProductJSON = savingsProductHelper //
+                .withInterestCompoundingPeriodTypeAsDaily() //
+                .withInterestPostingPeriodTypeAsMonthly() //
+                .withInterestCalculationPeriodTypeAsDailyBalance() //
+                .withMinimumOpenningBalance(minOpenningBalance).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    private static Integer createSavingsProduct(final String minOpenningBalance, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+        final String savingsProductJSON = new SavingsProductHelper().withInterestCompoundingPeriodTypeAsDaily() //
+                .withInterestPostingPeriodTypeAsQuarterly() //
+                .withInterestCalculationPeriodTypeAsDailyBalance() //
+                .withMinimumOpenningBalance(minOpenningBalance).withAccountingRuleAsCashBased(accounts).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    private Integer createLoanProduct(final String chargeId) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("15,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .build(chargeId);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final String clientID, final String loanProductID, final String savingsID) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal("15,000.00") //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("10 January 2013") //
+                .withSubmittedOnDate("10 January 2013") //
+                .build(clientID, loanProductID, savingsID);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private Integer createFixedDepositProduct(final String validFrom, final String validTo, Account... accounts) {
+        System.out.println("------------------------------CREATING NEW FIXED DEPOSIT PRODUCT ---------------------------------------");
+        FixedDepositProductHelper fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        final String fixedDepositProductJSON = fixedDepositProductHelper //
+                // .withAccountingRuleAsCashBased(accounts)
+                .build(validFrom, validTo);
+        return FixedDepositProductHelper.createFixedDepositProduct(fixedDepositProductJSON, requestSpec, responseSpec);
+    }
+
+    private Integer applyForFixedDepositApplication(final String clientID, final String productID, final String validFrom,
+            final String validTo, final String submittedOnDate, final String penalInterestType, String savingsId,
+            final boolean transferInterest, final FixedDepositAccountHelper fixedDepositAccountHelper) {
+        System.out.println("--------------------------------APPLYING FOR FIXED DEPOSIT ACCOUNT --------------------------------");
+        final String fixedDepositApplicationJSON = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec)
+                //
+                .withSubmittedOnDate(submittedOnDate).withSavings(savingsId).transferInterest(true)
+                .withLockinPeriodFrequency("1", FixedDepositAccountHelper.DAYS)
+                .build(clientID, productID, validFrom, validTo, penalInterestType);
+        return fixedDepositAccountHelper.applyFixedDepositApplication(fixedDepositApplicationJSON, this.requestSpec, this.responseSpec);
+    }
+
+    public void validateNumberForEqualExcludePrecission(String val, String val2) {
+        DecimalFormat twoDForm = new DecimalFormat("#", new DecimalFormatSymbols(Locale.US));
+        Assert.assertTrue(new Float(twoDForm.format(new Float(val))).compareTo(new Float(twoDForm.format(new Float(val2)))) == 0);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/StaffImageApiTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/StaffImageApiTest.java
new file mode 100644
index 0000000..b6f4181
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/StaffImageApiTest.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.integrationtests;
+
+import com.sun.jersey.core.util.Base64;
+
+import org.apache.fineract.integrationtests.common.ImageHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+import java.io.IOException;
+
+public class StaffImageApiTest {
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private AccountHelper accountHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+
+    }
+
+    @Test
+    public void createStaffImage() {
+
+        Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        Integer imageId = ImageHelper.createImageForStaff(this.requestSpec, this.responseSpec, staffId);
+        Assert.assertNotNull("Image id should not be null", imageId);
+
+    }
+
+    @Test
+    public void getStaffImage(){
+        Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        Integer imageId = ImageHelper.createImageForStaff(this.requestSpec, this.responseSpec, staffId);
+        Assert.assertNotNull("Image id should not be null", imageId);
+        String imageAsText = ImageHelper.getStaffImageAsText(this.requestSpec, this.responseSpec, staffId);
+        Assert.assertNotNull("Image id should not be null", imageAsText);
+    }
+
+    @Test
+    public void getStaffImageAsBinary(){
+        Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        Integer imageId = ImageHelper.createImageForStaff(this.requestSpec, this.responseSpec, staffId);
+        Assert.assertNotNull("Image id should not be null", imageId);
+        byte[] imageAsBytes = ImageHelper.getStaffImageAsBinary(this.requestSpec, this.responseSpec, staffId);
+        Assert.assertNotNull("Image content should not be null", imageAsBytes);
+    }
+
+    @Test
+    public void updateImage() {
+
+        Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        Integer imageId = ImageHelper.createImageForStaff(this.requestSpec, this.responseSpec, staffId);
+        Assert.assertNotNull("Image id should not be null", imageId);
+        imageId = ImageHelper.updateImageForStaff(this.requestSpec, this.responseSpec, staffId);
+        Assert.assertNotNull("Image id should not be null", imageId);
+
+    }
+
+    @Test
+    public void deleteStaffImage() {
+
+        Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
+        Integer imageId = ImageHelper.createImageForStaff(this.requestSpec, this.responseSpec, staffId);
+        Assert.assertNotNull("Image id should not be null", imageId);
+        imageId = ImageHelper.deleteStaffImage(this.requestSpec, this.responseSpec, staffId);
+        Assert.assertNotNull("Image id should not be null", imageId);
+
+    }
+
+
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/StaffTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/StaffTest.java
new file mode 100644
index 0000000..d1d2873
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/StaffTest.java
@@ -0,0 +1,239 @@
+/**
+ * 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.integrationtests;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class StaffTest {
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpecForValidationError;
+    private ResponseSpecification responseSpecForNotFoundError;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpecForValidationError = new ResponseSpecBuilder().expectStatusCode(400).build();
+        this.responseSpecForNotFoundError = new ResponseSpecBuilder().expectStatusCode(404).build();
+    }
+
+    @Test
+    public void testStaffCreate() {
+        final HashMap response = StaffHelper.createStaffMap(requestSpec, responseSpec);
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(response.get("officeId"), 1);
+        Assert.assertNotNull(response.get("resourceId"));
+    }
+
+    @Test
+    public void testStaffCreateValidationError() {
+
+        final String noOfficeJson = StaffHelper.createStaffWithJSONFields("firstname", "lastname");
+        final String noFirstnameJson = StaffHelper.createStaffWithJSONFields("officeId", "lastname");
+        final String noLastnameJson = StaffHelper.createStaffWithJSONFields("officeId", "firstname");
+
+        StaffHelper.createStaffWithJson(requestSpec, responseSpecForValidationError, noOfficeJson);
+        StaffHelper.createStaffWithJson(requestSpec, responseSpecForValidationError, noFirstnameJson);
+        StaffHelper.createStaffWithJson(requestSpec, responseSpecForValidationError, noLastnameJson);
+
+        final HashMap<String, Object> map = new HashMap<>();
+
+        map.put("officeId", 1);
+        map.put("firstname", Utils.randomNameGenerator("michael_", 5));
+        map.put("lastname", Utils.randomNameGenerator("Doe_", 4));
+
+        /** Long firstname test */
+        map.put("firstname", Utils.randomNameGenerator("michael_", 43));
+        StaffHelper.createStaffWithJson(requestSpec, responseSpecForValidationError, new Gson().toJson(map));
+        map.put("firstname", Utils.randomNameGenerator("michael_", 5));
+
+        /** Long lastname test */
+        map.put("lastname", Utils.randomNameGenerator("Doe_", 47));
+        StaffHelper.createStaffWithJson(requestSpec, responseSpecForValidationError, new Gson().toJson(map));
+        map.put("lastname", Utils.randomNameGenerator("Doe_",4));
+
+        /** Long mobileNo test */
+        map.put("mobileNo", Utils.randomNameGenerator("num_", 47));
+        StaffHelper.createStaffWithJson(requestSpec, responseSpecForValidationError, new Gson().toJson(map));
+    }
+
+    public void testStaffCreateMaxNameLength() {
+
+        final HashMap<String, Object> map = new HashMap<>();
+
+        map.put("officeId", 1);
+        map.put("firstname", Utils.randomNameGenerator("michael_", 42));
+        map.put("lastname", Utils.randomNameGenerator("Doe_", 46));
+
+        StaffHelper.createStaffWithJson(requestSpec, responseSpec, new Gson().toJson(map));
+    }
+
+    public void testStaffCreateExternalIdValidationError() {
+        final HashMap<String, Object> map = new HashMap<>();
+
+        map.put("officeId", 1);
+        map.put("firstname", Utils.randomNameGenerator("michael_", 5));
+        map.put("lastname", Utils.randomNameGenerator("Doe_", 4));
+
+        map.put("externalId", Utils.randomStringGenerator("EXT", 98));
+        StaffHelper.createStaffWithJson(requestSpec, responseSpecForValidationError, new Gson().toJson(map));
+    }
+
+    @Test
+    public void testStaffFetch() {
+        final HashMap response = StaffHelper.getStaff(requestSpec, responseSpec, 1);
+        Assert.assertNotNull(response);
+        Assert.assertNotNull(response.get("id"));
+        Assert.assertEquals(response.get("id"), 1);
+    }
+
+    @Test
+    public void testStaffListFetch() {
+        StaffHelper.getStaffList(requestSpec, responseSpec);
+    }
+
+    @Test
+    public void testStaffListStatusAll() {
+        StaffHelper.getStaffListWithState(requestSpec, responseSpec, "all");
+    }
+
+    @Test
+    public void testStaffListStatusActive() {
+        final List<HashMap> responseActive = (List<HashMap>) StaffHelper.getStaffListWithState(requestSpec, responseSpec, "active");
+        for(final HashMap staff : responseActive) {
+            Assert.assertNotNull(staff.get("id"));
+            Assert.assertEquals(staff.get("isActive"), true);
+        }
+    }
+
+    @Test
+    public void testStaffListStatusInactive() {
+        final List<HashMap> responseInactive = (List<HashMap>) StaffHelper.getStaffListWithState(requestSpec, responseSpec, "inactive");
+
+        for(final HashMap staff : responseInactive) {
+            Assert.assertNotNull(staff.get("id"));
+            Assert.assertEquals(staff.get("isActive"), false);
+        }
+    }
+
+    @Test
+    public void testStaffListFetchWrongState() {
+        StaffHelper.getStaffListWithState(requestSpec, responseSpecForValidationError, "xyz");
+    }
+
+    @Test
+    public void testStaffFetchNotFound() {
+        StaffHelper.getStaff(requestSpec, responseSpecForNotFoundError, Integer.MAX_VALUE);
+    }
+
+    @Test
+    public void testStaffUpdate() {
+        final HashMap<String, Object> map = new HashMap<>();
+        final String firstname = Utils.randomNameGenerator("michael_", 10);
+        final String lastname = Utils.randomNameGenerator("Doe_", 10);
+        final String externalId = Utils.randomStringGenerator("EXT", 97);
+        final String mobileNo = Utils.randomStringGenerator("num_", 10);
+
+        map.put("firstname", firstname);
+        map.put("lastname", lastname);
+        map.put("externalId", externalId);
+        map.put("mobileNo", mobileNo);
+
+        final HashMap response = (HashMap) StaffHelper.updateStaff(requestSpec, responseSpec, 1, map);
+        final HashMap changes = (HashMap)  response.get("changes");
+
+        Assert.assertEquals(1, response.get("resourceId"));
+        Assert.assertEquals(firstname, changes.get("firstname"));
+        Assert.assertEquals(lastname, changes.get("lastname"));
+        Assert.assertEquals(externalId, changes.get("externalId"));
+        Assert.assertEquals(mobileNo, changes.get("mobileNo"));
+    }
+
+    public void testStaffUpdateLongExternalIdError() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("externalId", Utils.randomStringGenerator("EXT", 98));
+
+        StaffHelper.updateStaff(requestSpec, responseSpecForValidationError, 1, map);
+    }
+
+    public void testStaffUpdateWrongActiveState() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("isActive", "xyz");
+
+        StaffHelper.updateStaff(requestSpec, responseSpecForValidationError, 1, map);
+    }
+
+    @Test
+    public void testStaffUpdateNotFoundError() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("firstname", Utils.randomNameGenerator("michael_", 5));
+
+        StaffHelper.updateStaff(requestSpec, responseSpecForNotFoundError, Integer.MAX_VALUE, map);
+    }
+
+    @Test
+    public void testStaffUpdateValidationError() {
+        final HashMap<String, Object> map = new HashMap<>();
+        final String firstname = Utils.randomNameGenerator("michael_", 5);
+        final String lastname = Utils.randomNameGenerator("Doe_", 4);
+        final String firstnameLong = Utils.randomNameGenerator("michael_", 43);
+        final String lastnameLong = Utils.randomNameGenerator("Doe_", 47);
+
+        map.put("firstname", firstname);
+        map.put("lastname", lastname);
+
+        /** Test long firstname */
+        map.put("firstname", firstnameLong);
+        StaffHelper.updateStaff(requestSpec, responseSpecForValidationError, 1, map);
+        map.put("firstname", firstname);
+
+        /** Test long lastname */
+        map.put("lastname", lastnameLong);
+        StaffHelper.updateStaff(requestSpec, responseSpecForValidationError, 1, map);
+        map.put("lastname", lastname);
+
+        /** Long mobileNo test */
+        map.put("mobileNo", Utils.randomNameGenerator("num_", 47));
+        StaffHelper.updateStaff(requestSpec, responseSpecForValidationError, 1, map);
+        map.remove("mobileNo");
+
+        /** Test unsupported parameter */
+        map.put("xyz", "xyz");
+        StaffHelper.updateStaff(requestSpec, responseSpecForValidationError, 1, map);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SurveyIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SurveyIntegrationTest.java
new file mode 100644
index 0000000..c728975
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SurveyIntegrationTest.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.integrationtests;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Before;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Client Loan Integration Test for checking Loan Application Repayment
+ * Schedule.
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class SurveyIntegrationTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SystemCodeTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SystemCodeTest.java
new file mode 100644
index 0000000..99fe2e9
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SystemCodeTest.java
@@ -0,0 +1,283 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.system.CodeHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Test for creating, updating, deleting codes and code values
+ * 
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class SystemCodeTest {
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification generalResponseSpec;
+    private RequestSpecification requestSpec;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        this.generalResponseSpec = new ResponseSpecBuilder().build();
+
+    }
+
+    // @Ignore()
+    @Test
+    // scenario 57, 58, 59, 60
+    public void testCreateCode() {
+        final String codeName = "Client Marital Status";
+
+        final Integer createResponseId = (Integer) CodeHelper.createCode(this.requestSpec, this.responseSpec, codeName,
+                CodeHelper.RESPONSE_ID_ATTRIBUTE_NAME);
+
+        // verify code created
+
+        final HashMap newCodeAttributes = (HashMap) CodeHelper.getCodeById(this.requestSpec, this.responseSpec, createResponseId, "");
+
+        Assert.assertNotNull(newCodeAttributes);
+        assertEquals("Verify value of codeId", createResponseId, newCodeAttributes.get(CodeHelper.CODE_ID_ATTRIBUTE_NAME));
+
+        assertEquals("Verify code name", codeName, newCodeAttributes.get(CodeHelper.CODE_NAME_ATTRIBUTE_NAME));
+        assertEquals("Verify system defined is false", false, newCodeAttributes.get(CodeHelper.CODE_SYSTEM_DEFINED_ATTRIBUTE_NAME));
+
+        // update code
+        final HashMap updateChangeResponse = (HashMap) CodeHelper.updateCode(this.requestSpec, this.responseSpec, createResponseId,
+                codeName + "(CHANGE)", "changes");
+
+        assertEquals("Verify code name updated", codeName + "(CHANGE)", updateChangeResponse.get(CodeHelper.CODE_NAME_ATTRIBUTE_NAME));
+
+        // delete code
+        final Integer deleteResponseId = (Integer) CodeHelper.deleteCodeById(this.requestSpec, this.responseSpec, createResponseId,
+                CodeHelper.RESPONSE_ID_ATTRIBUTE_NAME);
+        assertEquals("Verify code deleted", createResponseId, deleteResponseId);
+
+        // verify code deleted
+        final HashMap deletedCodeValues = (HashMap) CodeHelper
+                .getCodeById(this.requestSpec, this.generalResponseSpec, deleteResponseId, "");
+
+        Assert.assertNotNull(deletedCodeValues);
+        assertNull("Verify value of codeId", deletedCodeValues.get(CodeHelper.CODE_ID_ATTRIBUTE_NAME));
+
+        assertNull("Verify code name", deletedCodeValues.get(CodeHelper.CODE_NAME_ATTRIBUTE_NAME));
+        assertNull("Verify system defined is false", deletedCodeValues.get(CodeHelper.CODE_SYSTEM_DEFINED_ATTRIBUTE_NAME));
+    }
+
+    // @Ignore()
+    @Test
+    // scenario 57, 60
+    public void testPreventCreateDuplicateCode() {
+        final String codeName = "Client Marital Status";
+
+        // create code
+        final Integer createResponseId = (Integer) CodeHelper.createCode(this.requestSpec, this.responseSpec, codeName,
+                CodeHelper.RESPONSE_ID_ATTRIBUTE_NAME);
+
+        // verify code created
+        final HashMap newCodeAttributes = (HashMap) CodeHelper.getCodeById(this.requestSpec, this.responseSpec, createResponseId, "");
+
+        Assert.assertNotNull(newCodeAttributes);
+        assertEquals("Verify value of codeId", createResponseId, newCodeAttributes.get(CodeHelper.CODE_ID_ATTRIBUTE_NAME));
+
+        assertEquals("Verify code name", codeName, newCodeAttributes.get(CodeHelper.CODE_NAME_ATTRIBUTE_NAME));
+        assertEquals("Verify system defined is false", false, newCodeAttributes.get(CodeHelper.CODE_SYSTEM_DEFINED_ATTRIBUTE_NAME));
+
+        // try to create duplicate-- should fail
+        final List<HashMap> error = (List) CodeHelper.createCode(this.requestSpec, this.generalResponseSpec, codeName,
+                CommonConstants.RESPONSE_ERROR);
+
+        assertEquals("Verify duplication error", "error.msg.code.duplicate.name", error.get(0).get("userMessageGlobalisationCode"));
+
+        // delete code that was just created
+
+        final Integer deleteResponseId = (Integer) CodeHelper.deleteCodeById(this.requestSpec, this.responseSpec, createResponseId,
+                CodeHelper.RESPONSE_ID_ATTRIBUTE_NAME);
+        assertEquals("Verify code deleted", createResponseId, deleteResponseId);
+
+        // verify code deleted
+        final HashMap deletedCodeAttributes = (HashMap) CodeHelper.getCodeById(this.requestSpec, this.generalResponseSpec,
+                deleteResponseId, "");
+
+        Assert.assertNotNull(deletedCodeAttributes);
+        assertNull("Verify value of codeId", deletedCodeAttributes.get(CodeHelper.CODE_ID_ATTRIBUTE_NAME));
+
+        assertNull("Verify code name", deletedCodeAttributes.get(CodeHelper.CODE_NAME_ATTRIBUTE_NAME));
+        assertNull("Verify system defined is false", deletedCodeAttributes.get(CodeHelper.CODE_SYSTEM_DEFINED_ATTRIBUTE_NAME));
+
+    }
+
+    // @Ignore
+    @Test
+    public void testUpdateDeleteSystemDefinedCode() {
+
+        // get any systemDefined code
+        final HashMap systemDefinedCode = (HashMap) CodeHelper.getSystemDefinedCodes(this.requestSpec, this.responseSpec);
+
+        // delete system-defined code should fail
+        final List<HashMap> error = (List) CodeHelper.deleteCodeById(this.requestSpec, this.generalResponseSpec,
+                (Integer) systemDefinedCode.get(CodeHelper.CODE_ID_ATTRIBUTE_NAME), CommonConstants.RESPONSE_ERROR);
+
+        assertEquals("Cannot delete system-defined code", "error.msg.code.systemdefined", error.get(0).get("userMessageGlobalisationCode"));
+
+        // update system-defined code should fail
+
+        final List<HashMap> updateError = (List) CodeHelper.updateCode(this.requestSpec, this.generalResponseSpec,
+                (Integer) systemDefinedCode.get(CodeHelper.CODE_ID_ATTRIBUTE_NAME),
+                systemDefinedCode.get(CodeHelper.CODE_NAME_ATTRIBUTE_NAME) + "CHANGE", CommonConstants.RESPONSE_ERROR);
+
+        assertEquals("Cannot update system-defined code", "error.msg.code.systemdefined",
+                updateError.get(0).get("userMessageGlobalisationCode"));
+
+    }
+
+    // @Ignore
+    @Test
+    public void testCodeValuesNotAssignedToTable() {
+
+        final String codeName = Utils.randomNameGenerator("Marital Status1", 10);
+
+        final String codeValue1 = "Married1";
+        final String codeValue2 = "Unmarried1";
+
+        final int codeValue1Position = 1;
+        final int codeValue2Position = 1;
+
+        final String codeDescription1 = "Description11";
+        final String codeDescription2 = "Description22";
+
+        // create code
+        final Integer createCodeResponseId = (Integer) CodeHelper.createCode(this.requestSpec, this.responseSpec, codeName,
+                CodeHelper.RESPONSE_ID_ATTRIBUTE_NAME);
+
+        // create first code value
+        final Integer createCodeValueResponseId1 = (Integer) CodeHelper.createCodeValue(this.requestSpec, this.responseSpec,
+                createCodeResponseId, codeValue1, codeDescription1, codeValue1Position, CodeHelper.SUBRESPONSE_ID_ATTRIBUTE_NAME);
+
+        // create second code value
+        final Integer createCodeValueResponseId2 = (Integer) CodeHelper.createCodeValue(this.requestSpec, this.responseSpec,
+                createCodeResponseId, codeValue2, codeDescription2, codeValue1Position, CodeHelper.SUBRESPONSE_ID_ATTRIBUTE_NAME);
+
+        // verify two code values created
+
+        final List<HashMap> codeValuesList = (List) CodeHelper.getCodeValuesForCode(this.requestSpec, this.responseSpec,
+                createCodeResponseId, "");
+
+        assertEquals("Number of code values returned matches number created", 2, codeValuesList.size());
+
+        // verify values of first code value
+        final HashMap codeValuesAttributes1 = (HashMap) CodeHelper.getCodeValueById(this.requestSpec, this.responseSpec,
+                createCodeResponseId, createCodeValueResponseId1, "");
+
+        Assert.assertNotNull(codeValuesAttributes1);
+        assertEquals("Verify value of codeValueId", createCodeValueResponseId1,
+                codeValuesAttributes1.get(CodeHelper.CODE_VALUE_ID_ATTRIBUTE_NAME));
+
+        assertEquals("Verify value of code name", codeValue1, codeValuesAttributes1.get(CodeHelper.CODE_VALUE_NAME_ATTRIBUTE_NAME));
+
+        assertEquals("Verify value of code description", codeDescription1,
+                codeValuesAttributes1.get(CodeHelper.CODE_VALUE_DESCRIPTION_ATTRIBUTE_NAME));
+
+        assertEquals("Verify position of code value", codeValue1Position,
+                codeValuesAttributes1.get(CodeHelper.CODE_VALUE_POSITION_ATTRIBUTE_NAME));
+
+        // verify values of second code value
+        final HashMap codeValuesAttributes2 = (HashMap) CodeHelper.getCodeValueById(this.requestSpec, this.responseSpec,
+                createCodeResponseId, createCodeValueResponseId2, "");
+
+        Assert.assertNotNull(codeValuesAttributes2);
+        assertEquals("Verify value of codeValueId", createCodeValueResponseId2,
+                codeValuesAttributes2.get(CodeHelper.CODE_VALUE_ID_ATTRIBUTE_NAME));
+
+        assertEquals("Verify value of code name", codeValue2, codeValuesAttributes2.get(CodeHelper.CODE_VALUE_NAME_ATTRIBUTE_NAME));
+
+        assertEquals("Verify value of code description", codeDescription2,
+                codeValuesAttributes2.get(CodeHelper.CODE_VALUE_DESCRIPTION_ATTRIBUTE_NAME));
+
+        assertEquals("Verify position of code value", codeValue2Position,
+                codeValuesAttributes2.get(CodeHelper.CODE_VALUE_POSITION_ATTRIBUTE_NAME));
+
+        // update code value 1
+        final HashMap codeValueChanges = (HashMap) CodeHelper.updateCodeValue(this.requestSpec, this.responseSpec, createCodeResponseId,
+                createCodeValueResponseId1, codeValue1 + "CHANGE", codeDescription1 + "CHANGE", 4, "changes");
+
+        assertEquals("Verify changed code value name", codeValueChanges.get("name"), codeValue1 + "CHANGE");
+
+        assertEquals("Verify changed code value description", codeValueChanges.get("description"), codeDescription1 + "CHANGE");
+
+        // delete code value
+        Integer deletedCodeValueResponseId1 = (Integer) CodeHelper.deleteCodeValueById(this.requestSpec, this.generalResponseSpec,
+                createCodeResponseId, createCodeValueResponseId1, CodeHelper.SUBRESPONSE_ID_ATTRIBUTE_NAME);
+
+        // Verify code value deleted
+
+        final ArrayList<HashMap> deletedCodeValueAttributes1 = (ArrayList<HashMap>) CodeHelper.getCodeValueById(this.requestSpec,
+                this.generalResponseSpec, createCodeResponseId, deletedCodeValueResponseId1, CommonConstants.RESPONSE_ERROR);
+
+        assertEquals("error.msg.codevalue.id.invalid", deletedCodeValueAttributes1.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        final List<HashMap> deletedCodeValuesList = (List) CodeHelper.getCodeValuesForCode(this.requestSpec, this.responseSpec,
+                createCodeResponseId, "");
+
+        assertEquals("Number of code values is 1", 1, deletedCodeValuesList.size());
+
+        final Integer deletedCodeValueResponseId2 = (Integer) CodeHelper.deleteCodeValueById(this.requestSpec, this.generalResponseSpec,
+                createCodeResponseId, createCodeValueResponseId2, CodeHelper.SUBRESPONSE_ID_ATTRIBUTE_NAME);
+
+        final ArrayList<HashMap> deletedCodeValueAttributes2 = (ArrayList<HashMap>) CodeHelper.getCodeValueById(this.requestSpec,
+                this.generalResponseSpec, createCodeResponseId, deletedCodeValueResponseId2, CommonConstants.RESPONSE_ERROR);
+
+        assertEquals("error.msg.codevalue.id.invalid", deletedCodeValueAttributes2.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        final List<HashMap> deletedCodeValuesList1 = (List) CodeHelper.getCodeValuesForCode(this.requestSpec, this.responseSpec,
+                createCodeResponseId, "");
+
+        assertEquals("Number of code values is 0", 0, deletedCodeValuesList1.size());
+
+    }
+
+    @Ignore
+    @Test
+    public void testCodeValuesAssignedToTable() {
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/TemplateIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/TemplateIntegrationTest.java
new file mode 100644
index 0000000..a176c97
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/TemplateIntegrationTest.java
@@ -0,0 +1,84 @@
+/**
+ * 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.integrationtests;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class TemplateIntegrationTest {
+
+    private final String GET_TEMPLATES_URL = "/fineract-provider/api/v1/templates?tenantIdentifier=default";
+    private final String GET_TEMPLATE_ID_URL = "/fineract-provider/api/v1/templates/%s?tenantIdentifier=default";
+    private final String RESPONSE_ATTRIBUTE_NAME = "name";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    @Before
+    public void setup() {
+
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Ignore
+    @Test
+    public void test() {
+
+        final HashMap<String, String> metadata = new HashMap<>();
+        metadata.put("user", "resource_url");
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("name", "foo");
+        map.put("text", "Hello {{template}}");
+        map.put("mappers", metadata);
+
+        ArrayList<?> get = Utils.performServerGet(this.requestSpec, this.responseSpec, this.GET_TEMPLATES_URL, "");
+        final int entriesBeforeTest = get.size();
+
+        final Integer id = Utils.performServerPost(this.requestSpec, this.responseSpec, this.GET_TEMPLATES_URL, new Gson().toJson(map), "resourceId");
+
+        final String templateUrlForId = String.format(this.GET_TEMPLATE_ID_URL, id);
+
+        final String getrequest2 = Utils.performServerGet(this.requestSpec, this.responseSpec, templateUrlForId, this.RESPONSE_ATTRIBUTE_NAME);
+
+        Assert.assertTrue(getrequest2.equals("foo"));
+
+        Utils.performServerDelete(this.requestSpec, this.responseSpec, templateUrlForId, "");
+
+        get = Utils.performServerGet(this.requestSpec, this.responseSpec, this.GET_TEMPLATES_URL, "");
+        final int entriesAfterTest = get.size();
+
+        Assert.assertEquals(entriesBeforeTest, entriesAfterTest);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/WorkingDaysTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/WorkingDaysTest.java
new file mode 100755
index 0000000..38278b1
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/WorkingDaysTest.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.integrationtests;

+

+import static org.junit.Assert.assertEquals;

+

+import java.util.HashMap;

+import java.util.List;

+

+import org.apache.fineract.integrationtests.common.CommonConstants;

+import org.apache.fineract.integrationtests.common.Utils;

+import org.apache.fineract.integrationtests.common.WorkingDaysHelper;

+import org.junit.Assert;

+import org.junit.Before;

+import org.junit.Test;

+

+import com.jayway.restassured.builder.RequestSpecBuilder;

+import com.jayway.restassured.builder.ResponseSpecBuilder;

+import com.jayway.restassured.http.ContentType;

+import com.jayway.restassured.specification.RequestSpecification;

+import com.jayway.restassured.specification.ResponseSpecification;

+

+@SuppressWarnings({ "rawtypes", "unchecked" })

+public class WorkingDaysTest {

+

+    private ResponseSpecification responseSpec;

+    private RequestSpecification requestSpec;

+    private ResponseSpecification generalResponseSpec;

+

+    @Before

+    public void setUp() {

+        Utils.initializeRESTAssured();

+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();

+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());

+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();

+        this.generalResponseSpec = new ResponseSpecBuilder().build();

+

+    }

+

+    @Test

+    public void updateWorkingDays() {

+        HashMap response = (HashMap) WorkingDaysHelper.updateWorkingDays(requestSpec, responseSpec);

+        Assert.assertNotNull(response.get("resourceId"));

+    }

+

+    @Test

+    public void updateWorkingDaysWithWrongRecurrencePattern() {

+        final List<HashMap> error = (List) WorkingDaysHelper.updateWorkingDaysWithWrongRecurrence(requestSpec, generalResponseSpec,

+                CommonConstants.RESPONSE_ERROR);

+        assertEquals("Verify wrong recurrence pattern error", "error.msg.recurring.rule.parsing.error",

+                error.get(0).get("userMessageGlobalisationCode"));

+    }

+

+}

diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/XBRLIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/XBRLIntegrationTest.java
new file mode 100644
index 0000000..71041b6
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/XBRLIntegrationTest.java
@@ -0,0 +1,66 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.xbrl.XBRLIntegrationTestHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class XBRLIntegrationTest {
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+
+    private XBRLIntegrationTestHelper xbrlHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void shouldRetrieveTaxonomyList() {
+        this.xbrlHelper = new XBRLIntegrationTestHelper(this.requestSpec, this.responseSpec);
+
+        final ArrayList<HashMap> taxonomyList = this.xbrlHelper.getTaxonomyList();
+        verifyTaxonomyList(taxonomyList);
+    }
+
+    private void verifyTaxonomyList(final ArrayList<HashMap> taxonomyList) {
+        System.out.println("--------------------VERIFYING TAXONOMY LIST--------------------------");
+        assertEquals("Checking for the 1st taxonomy", "AdministrativeExpense", taxonomyList.get(0).get("name"));
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/BatchHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/BatchHelper.java
new file mode 100644
index 0000000..c99ebea
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/BatchHelper.java
@@ -0,0 +1,366 @@
+/**
+ * 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.integrationtests.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.junit.Assert;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Helper class for {@link org.apache.fineract.integrationtests.BatchApiTest}. It
+ * takes care of creation of {@code BatchRequest} list and posting this list to
+ * the server.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.integrationtests.BatchApiTest
+ */
+public class BatchHelper {
+
+    private static final String BATCH_API_URL = "/fineract-provider/api/v1/batches?" + Utils.TENANT_IDENTIFIER;
+    private static final String BATCH_API_URL_EXT = BATCH_API_URL + "&enclosingTransaction=true";
+
+    private BatchHelper() {
+        super();
+    }
+
+    /**
+     * Returns a JSON String for a list of {@code BatchRequest}s
+     * 
+     * @param batchRequests
+     * @return JSON String of BatchRequest
+     */
+    public static String toJsonString(final List<BatchRequest> batchRequests) {
+        return new Gson().toJson(batchRequests);
+    }
+
+    /**
+     * Returns the converted string response into JSON.
+     * 
+     * @param json
+     * @return List<BatchResponse>
+     */
+    private static List<BatchResponse> fromJsonString(final String json) {
+        return new Gson().fromJson(json, new TypeToken<List<BatchResponse>>() {}.getType());
+    }
+
+    /**
+     * Returns a list of BatchResponse with query parameter enclosing
+     * transaction set to false by posting the jsonified BatchRequest to the
+     * server.
+     * 
+     * @param requestSpec
+     * @param responseSpec
+     * @param jsonifiedBatchRequests
+     * @return a list of BatchResponse
+     */
+    public static List<BatchResponse> postBatchRequestsWithoutEnclosingTransaction(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String jsonifiedBatchRequests) {
+        final String response = Utils.performServerPost(requestSpec, responseSpec, BATCH_API_URL, jsonifiedBatchRequests, null);
+        return BatchHelper.fromJsonString(response);
+    }
+
+    /**
+     * Returns a list of BatchResponse with query parameter enclosing
+     * transaction set to true by posting the jsonified BatchRequest to the
+     * server.
+     * 
+     * @param requestSpec
+     * @param responseSpec
+     * @param jsonifiedBatchRequests
+     * @return a list of BatchResponse
+     */
+    public static List<BatchResponse> postBatchRequestsWithEnclosingTransaction(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String jsonifiedBatchRequests) {
+        final String response = Utils.performServerPost(requestSpec, responseSpec, BATCH_API_URL_EXT, jsonifiedBatchRequests, null);
+        return BatchHelper.fromJsonString(response);
+    }
+
+    /**
+     * Returns a BatchResponse based on the given BatchRequest, by posting the
+     * request to the server.
+     * 
+     * @param BatchRequest
+     * @return List<BatchResponse>
+     */
+    public static List<BatchResponse> postWithSingleRequest(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final BatchRequest br) {
+
+        final List<BatchRequest> batchRequests = new ArrayList<>();
+        batchRequests.add(br);
+
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+        final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec, responseSpec,
+                jsonifiedRequest);
+
+        // Verifies that the response result is there
+        Assert.assertNotNull(response);
+        Assert.assertTrue(response.size() > 0);
+
+        return response;
+    }
+
+    /**
+     * Creates and returns a
+     * {@link org.apache.fineract.batch.command.internal.CreateClientCommandStrategy}
+     * Request as one of the request in Batch.
+     * 
+     * @param reqId
+     * @param externalId
+     * @return BatchRequest
+     */
+    public static BatchRequest createClientRequest(final Long requestId, final String externalId) {
+
+        final BatchRequest br = new BatchRequest();
+        br.setRequestId(requestId);
+        br.setRelativeUrl("clients");
+        br.setMethod("POST");
+
+        final String extId;
+        if (externalId.equals("")) {
+            extId = "ext" + String.valueOf((10000 * Math.random())) + String.valueOf((10000 * Math.random()));
+        } else {
+            extId = externalId;
+        }
+
+        final String body = "{ \"officeId\": 1, \"firstname\": \"Petra\", \"lastname\": \"Yton\"," + "\"externalId\": " + extId
+                + ",  \"dateFormat\": \"dd MMMM yyyy\", \"locale\": \"en\"," + "\"active\": false, \"submittedOnDate\": \"04 March 2009\"}";
+
+        br.setBody(body);
+
+        return br;
+    }
+
+    /**
+     * Creates and returns a
+     * {@link org.apache.fineract.batch.command.internal.UpdateClientCommandStrategy}
+     * Request with given requestId and reference.
+     * 
+     * @param reqId
+     * @param clientId
+     * @return BatchRequest
+     */
+    public static BatchRequest updateClientRequest(final Long requestId, final Long reference) {
+
+        final BatchRequest br = new BatchRequest();
+
+        br.setRequestId(requestId);
+        br.setRelativeUrl("clients/$.clientId");
+        br.setMethod("PUT");
+        br.setReference(reference);
+        br.setBody("{\"firstname\": \"TestFirstName\", \"lastname\": \"TestLastName\"}");
+
+        return br;
+    }
+
+    /**
+     * Creates and returns a
+     * {@link org.apache.fineract.batch.command.internal.ApplyLoanCommandStrategy}
+     * Request with given requestId and reference.
+     * 
+     * @param requestId
+     * @param reference
+     * @param productId
+     * @return BatchRequest
+     */
+    public static BatchRequest applyLoanRequest(final Long requestId, final Long reference, final Integer productId) {
+
+        final BatchRequest br = new BatchRequest();
+
+        br.setRequestId(requestId);
+        br.setRelativeUrl("loans");
+        br.setMethod("POST");
+        br.setReference(reference);
+
+        final String body = "{\"dateFormat\": \"dd MMMM yyyy\", \"locale\": \"en_GB\", \"clientId\": \"$.clientId\"," + "\"productId\": "
+                + productId + ", \"principal\": \"10,000.00\", \"loanTermFrequency\": 12,"
+                + "\"loanTermFrequencyType\": 2, \"loanType\": \"individual\", \"numberOfRepayments\": 10,"
+                + "\"repaymentEvery\": 1, \"repaymentFrequencyType\": 2, \"interestRatePerPeriod\": 10,"
+                + "\"amortizationType\": 1, \"interestType\": 0, \"interestCalculationPeriodType\": 1,"
+                + "\"transactionProcessingStrategyId\": 1, \"expectedDisbursementDate\": \"10 Jun 2013\","
+                + "\"submittedOnDate\": \"10 Jun 2013\"}";
+        br.setBody(body);
+
+        return br;
+    }
+
+    /**
+     * Creates and returns a
+     * {@link org.apache.fineract.batch.command.internal.ApplySavingsCommandStrategy}
+     * Request with given requestId and reference.
+     * 
+     * @param requestId
+     * @param reference
+     * @param productId
+     * @return BatchRequest
+     */
+    public static BatchRequest applySavingsRequest(final Long requestId, final Long reference, final Integer productId) {
+
+        final BatchRequest br = new BatchRequest();
+
+        br.setRequestId(requestId);
+        br.setRelativeUrl("savingsaccounts");
+        br.setMethod("POST");
+        br.setReference(reference);
+
+        final String body = "{\"clientId\": \"$.clientId\", \"productId\": " + productId + ","
+                + "\"locale\": \"en\", \"dateFormat\": \"dd MMMM yyyy\", \"submittedOnDate\": \"01 March 2011\"}";
+        br.setBody(body);
+
+        return br;
+    }
+
+    /**
+     * Creates and returns a
+     * {@link org.apache.fineract.batch.command.internal.CreateChargeCommandStrategy}
+     * Request with given requestId and reference
+     * 
+     * @param requestId
+     * @param reference
+     * @return BatchRequest
+     */
+    public static BatchRequest createChargeRequest(final Long requestId, final Long reference) {
+
+        final BatchRequest br = new BatchRequest();
+        br.setRequestId(requestId);
+        br.setRelativeUrl("loans/$.loanId/charges");
+        br.setMethod("POST");
+        br.setReference(reference);
+
+        final String body = "{\"chargeId\": \"2\", \"locale\": \"en\", \"amount\": \"100\", "
+                + "\"dateFormat\": \"dd MMMM yyyy\", \"dueDate\": \"29 April 2013\"}";
+        br.setBody(body);
+
+        return br;
+    }
+
+    /**
+     * Creates and returns a
+     * {@link org.apache.fineract.batch.command.internal.CollectChargesCommandStrategy}
+     * Request with given requestId and reference.
+     * 
+     * @param requestId
+     * @param reference
+     * @return BatchRequest
+     */
+    public static BatchRequest collectChargesRequest(final Long requestId, final Long reference) {
+
+        final BatchRequest br = new BatchRequest();
+
+        br.setRequestId(requestId);
+        br.setRelativeUrl("loans/$.loanId/charges");
+        br.setReference(reference);
+        br.setMethod("GET");
+        br.setBody("{ }");
+
+        return br;
+    }
+
+    /**
+     * Creates and returns a
+     * {@link org.apache.fineract.batch.command.internal.ActivateClientCommandStrategy}
+     * Request with given requestId and reference.
+     * 
+     * 
+     * @param requestId
+     * @param reference
+     * @return BatchRequest
+     */
+    public static BatchRequest activateClientRequest(final Long requestId, final Long reference) {
+
+        final BatchRequest br = new BatchRequest();
+
+        br.setRequestId(requestId);
+        br.setRelativeUrl("clients/$.clientId?command=activate");
+        br.setReference(reference);
+        br.setMethod("POST");
+        br.setBody("{\"locale\": \"en\", \"dateFormat\": \"dd MMMM yyyy\", \"activationDate\": \"01 March 2011\"}");
+
+        return br;
+    }
+    
+    /**
+     * Creates and returns a
+     * {@link org.apache.fineract.batch.command.internal.ApproveLoanCommandStrategy}
+     * Request with given requestId and reference.
+     * 
+     * 
+     * @param requestId
+     * @param reference
+     * @return BatchRequest
+     */
+    public static BatchRequest approveLoanRequest(final Long requestId, final Long reference) {
+        final BatchRequest br = new BatchRequest();
+
+        br.setRequestId(requestId);
+        br.setRelativeUrl("loans/$.loanId?command=approve");
+        br.setReference(reference);
+        br.setMethod("POST");
+        br.setBody("{\"locale\": \"en\", \"dateFormat\": \"dd MMMM yyyy\", \"approvedOnDate\": \"12 September 2013\"," 
+                + "\"note\": \"Loan approval note\"}");
+
+        return br;
+    }
+
+    /**
+     * Creates and returns a
+     * {@link org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy}
+     * Request with given requestId and reference.
+     * 
+     * 
+     * @param requestId
+     * @param reference
+     * @return BatchRequest
+     */
+    public static BatchRequest disburseLoanRequest(final Long requestId, final Long reference) {
+        final BatchRequest br = new BatchRequest();
+
+        br.setRequestId(requestId);
+        br.setRelativeUrl("loans/$.loanId?command=disburse");
+        br.setReference(reference);
+        br.setMethod("POST");
+        br.setBody("{\"locale\": \"en\", \"dateFormat\": \"dd MMMM yyyy\", \"actualDisbursementDate\": \"15 September 2013\"}");
+
+        return br;
+    }
+    
+    /**
+     * Checks that the client with given externalId is not created on the
+     * server.
+     * 
+     * @param requestSpec
+     * @param responseSpec
+     * @param externalId
+     */
+    public static void verifyClientCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String externalId) {
+        System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n");
+        final String CLIENT_URL = "/fineract-provider/api/v1/clients?externalId=" + externalId + "&" + Utils.TENANT_IDENTIFIER;
+        final Integer responseRecords = Utils.performServerGet(requestSpec, responseSpec, CLIENT_URL, "totalFilteredRecords");
+        Assert.assertEquals("No records found with given externalId", (long) responseRecords, (long) 0);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CalendarHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CalendarHelper.java
new file mode 100644
index 0000000..0e9423b
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CalendarHelper.java
@@ -0,0 +1,89 @@
+/**
+ * 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.integrationtests.common;
+
+import static com.jayway.restassured.path.json.JsonPath.from;
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class CalendarHelper {
+
+    private static final String BASE_URL = "/fineract-provider/api/v1/";
+    private static final String PARENT_ENTITY_NAME = "groups/";
+    private static final String ENITY_NAME = "/calendars";
+
+    public static Integer createMeetingCalendarForGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer groupId, final String startDate, final String frequency, final String interval, final String repeatsOnDay) {
+
+        System.out.println("---------------------------------CREATING A MEETING CALENDAR FOR THE GROUP------------------------------");
+
+        final String CALENDAR_RESOURCE_URL = BASE_URL + PARENT_ENTITY_NAME + groupId + ENITY_NAME + "?" + Utils.TENANT_IDENTIFIER;
+
+        System.out.println(CALENDAR_RESOURCE_URL);
+
+        return Utils.performServerPost(requestSpec, responseSpec, CALENDAR_RESOURCE_URL,
+                getTestCalendarAsJSON(frequency, interval, repeatsOnDay, startDate), "resourceId");
+    }
+
+    public static Integer updateMeetingCalendarForGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer groupId, String calendarID, final String startDate, final String frequency, final String interval,
+            final String repeatsOnDay) {
+
+        System.out.println("---------------------------------UPDATING A MEETING CALENDAR FOR THE GROUP------------------------------");
+
+        final String CALENDAR_RESOURCE_URL = BASE_URL + PARENT_ENTITY_NAME + groupId + ENITY_NAME + "/" + calendarID;
+
+        System.out.println(CALENDAR_RESOURCE_URL);
+        // TODO: check that resource id indeed exists in calendar update put.
+        return Utils.performServerPut(requestSpec, responseSpec, CALENDAR_RESOURCE_URL,
+                getTestCalendarAsJSON(frequency, interval, repeatsOnDay, startDate), "resourceId");
+    }
+
+    public static String getTestCalendarAsJSON(final String frequency, final String interval, final String repeatsOnDay,
+            final String startDate) {
+
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en");
+        map.put("frequency", frequency);
+        map.put("interval", interval);
+        map.put("repeating", "true");
+        map.put("repeatsOnDay", repeatsOnDay);
+        map.put("title", Utils.randomNameGenerator("groups_CollectionMeeting", 4));
+        map.put("typeId", "1");
+        map.put("startDate", startDate);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static void verifyCalendarCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedGroupId, final Integer generatedCalendarId) {
+        System.out.println("------------------------------CHECK CALENDAR DETAILS------------------------------------\n");
+        final String CLIENT_URL = "/fineract-provider/api/v1/groups/" + generatedGroupId + "?associations=all&" + Utils.TENANT_IDENTIFIER;
+        final String responseCalendarDetailsinJSON = Utils.performServerGet(requestSpec, responseSpec, CLIENT_URL,
+                "collectionMeetingCalendar");
+        final Integer responseCalendarId = from(responseCalendarDetailsinJSON).get("id");
+        assertEquals("ERROR IN CREATING THE CALENDAR", generatedCalendarId, responseCalendarId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CenterDomain.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CenterDomain.java
new file mode 100644
index 0000000..c6ff955
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CenterDomain.java
@@ -0,0 +1,248 @@
+/**
+ * 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.integrationtests.common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+
+import com.google.gson.Gson;
+
+public class CenterDomain implements Comparable<CenterDomain> {
+
+    public static class Builder {
+
+        private Integer id;
+        private String accountNo;
+        private HashMap status;
+        private boolean active;
+        private String name;
+        private String externalId;
+        private Integer staffId;
+        private Integer officeId;
+        private String officeName;
+        private String hierarchy;
+        private ArrayList<HashMap> groupMembers;
+
+        private Builder(final Integer id, final Integer statusid, final String statuscode, final String statusvalue, final boolean active,
+                final String name, final String externalId, final Integer staffId, final int officeID, final String officeName,
+                final String hierarchy, final ArrayList<HashMap> groupMembers) {
+            this.id = id;
+            this.accountNo = accountNo;
+            this.status = new HashMap();
+            this.status.put("id", statusid);
+            this.status.put("code", statuscode);
+            this.status.put("value", statusvalue);
+            this.active = active;
+            this.name = name;
+            this.externalId = externalId;
+            this.staffId = staffId;
+            this.officeId = officeID;
+            this.officeName = officeName;
+            this.hierarchy = hierarchy;
+            this.groupMembers = groupMembers;
+        }
+
+        public CenterDomain build() {
+            return new CenterDomain(this.id, this.accountNo, (int) this.status.get("id"), (String) this.status.get("code"),
+                    (String) this.status.get("value"), this.active, this.name, this.externalId, this.staffId, this.officeId,
+                    this.officeName, this.hierarchy, groupMembers);
+        }
+
+    }
+
+    private Integer id;
+    private String accountNo;
+    private HashMap status;
+    private boolean active;
+    private String name;
+    private String externalId;
+    private Integer staffId;
+    private Integer officeId;
+    private String officeName;
+    private String hierarchy;
+    private ArrayList<HashMap> groupMembers;
+
+    CenterDomain() {
+        /* super(); */
+    }
+
+    private CenterDomain(final Integer id, final String accountNo, final Integer statusid, final String statuscode, final String statusvalue, final boolean active,
+            final String name, final String externalId, final Integer staffId, final Integer officeID, final String officeName,
+            final String hierarchy, final ArrayList<HashMap> groupMembers) {
+        this.id = id;
+        this.accountNo = accountNo;
+        this.status = new HashMap();
+        this.status.put("id", statusid);
+        this.status.put("code", statuscode);
+        this.status.put("value", statusvalue);
+        this.active = active;
+        this.name = name;
+        this.externalId = externalId;
+        this.staffId = staffId;
+        this.officeId = officeID;
+        this.officeName = officeName;
+        this.hierarchy = hierarchy;
+        this.groupMembers = groupMembers;
+    }
+
+    public String toJSON() {
+        return new Gson().toJson(this);
+    }
+
+    public static CurrencyDomain fromJSON(final String jsonData) {
+        return new Gson().fromJson(jsonData, CurrencyDomain.class);
+    }
+
+    public static Builder create(final Integer id, final Integer statusid, final String statuscode, final String statusvalue,
+            final boolean active, final String name, final String externalId, final Integer staffId, final Integer officeID,
+            final String officeName, final String hierarchy, final ArrayList<HashMap> groupMembers) {
+        return new Builder(id, statusid, statuscode, statusvalue, active, name, externalId, staffId, officeID, officeName, hierarchy,
+                groupMembers);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static String jsonRequestToCreateCenter(Integer id, Integer statusId, String statusCode, String statusValue, Boolean active,
+            String activationDate, String submittedDate, String name, String externalId, Integer staffId, Integer officeID,
+            String officeName, String hierarchy, final int[] groupMembers) {
+        // String ids = String.valueOf(id);
+        final HashMap map = new HashMap<>();
+        if (id != null) map.put("id", id);
+        if (statusId != null) map.put("statusId", statusId);
+        if (statusCode != null) map.put("statusCode", statusCode);
+        if (statusValue != null) map.put("statusValue", statusValue);
+        map.put("officeId", "1");
+        map.put("name", randomNameGenerator("Center_Name_", 5));
+        map.put("externalId", randomIDGenerator("ID_", 7));
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en");
+        if (staffId != null) {
+            map.put("staffId", String.valueOf(staffId));
+        }
+        if (active) {
+            map.put("active", "true");
+            map.put("locale", "en");
+            map.put("dateFormat", "dd MMM yyyy");
+            map.put("activationDate", activationDate);
+        } else {
+            map.put("active", "false");
+            if (submittedDate == null)
+                map.put("submittedOnDate", DateUtils.getDateOfTenant());
+            else
+                map.put("submittedOnDate", submittedDate);
+        }
+        if (externalId != null) map.put("externalId", externalId);
+        if (groupMembers != null) map.put("groupMembers", groupMembers);
+        System.out.println(map);
+        return new Gson().toJson(map);
+    }
+
+    public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return Utils.randomStringGenerator(prefix, lenOfRandomSuffix);
+    }
+
+    private static String randomIDGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return Utils.randomStringGenerator(prefix, lenOfRandomSuffix, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    }
+
+    public String getExternalId() {
+        return this.externalId;
+    }
+
+    public Integer getStaffId() {
+        return this.staffId;
+    }
+
+    public Integer getId() {
+        return this.id;
+    }
+
+    public HashMap getStatus() {
+        return this.status;
+    }
+
+    public boolean isActive() {
+        return this.active;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public Integer getOfficeId() {
+        return this.officeId;
+    }
+
+    public String getOfficeName() {
+        return this.officeName;
+    }
+
+    public String getHierarchy() {
+        return this.hierarchy;
+    }
+    
+    public String getAccountNo(){
+    	return this.accountNo;
+    }
+
+    public int[] getGroupMembers() {
+        int[] groupMemberList = new int[this.groupMembers.size()];
+        for (int i = 0; i < groupMemberList.length; i++) {
+            groupMemberList[i] = ((Double) this.groupMembers.get(i).get("id")).intValue();
+        }
+        return groupMemberList;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 1;
+
+        if (this.id >= 0) hash += this.id;
+        if (this.status != null) {
+            if ((Double) this.status.get("id") >= 0) hash += (Double) this.status.get("id");
+            if ((String) this.status.get("code") != null) hash += this.status.get("code").hashCode();
+            if ((String) this.status.get("value") != null) hash += this.status.get("value").hashCode();
+        }
+        if (this.name != null) hash += this.name.hashCode();
+        if (this.officeId >= 0) hash += this.officeId;
+        if (this.officeName != null) hash += this.officeName.hashCode();
+        if (this.hierarchy != null) hash += this.hierarchy.hashCode();
+        if (this.groupMembers != null) hash += this.groupMembers.hashCode();
+
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) { return true; }
+
+        if (!(obj instanceof CenterDomain)) return false;
+
+        CenterDomain cd = (CenterDomain) obj;
+
+        if (this.hashCode() == cd.hashCode()) return true;
+        return false;
+    }
+
+    @Override
+    public int compareTo(CenterDomain cd) {
+        return ((Integer) this.id).compareTo(cd.getId());
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CenterHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CenterHelper.java
new file mode 100644
index 0000000..f81c64a
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CenterHelper.java
@@ -0,0 +1,268 @@
+/**
+ * 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.integrationtests.common;
+
+import java.util.ArrayList;
+import static org.junit.Assert.assertEquals;
+import java.util.HashMap;
+import org.apache.commons.lang3.StringUtils;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class CenterHelper {
+
+    private static final String CENTERS_URL = "/fineract-provider/api/v1/centers";
+
+    public static final String CREATED_DATE = "29 December 2014";
+    private static final String CREATE_CENTER_URL = "/fineract-provider/api/v1/centers?" + Utils.TENANT_IDENTIFIER;
+
+    public static CenterDomain retrieveByID(int id, final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        final String GET_CENTER_BY_ID_URL = CENTERS_URL + "/" + id + "?associations=groupMembers&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING CENTER AT " + id + "-------------------------");
+        final String jsonData = new Gson().toJson(Utils.performServerGet(requestSpec, responseSpec, GET_CENTER_BY_ID_URL, ""));
+        return new Gson().fromJson(jsonData, new TypeToken<CenterDomain>() {}.getType());
+    }
+
+    public static ArrayList<CenterDomain> paginatedListCenters(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        final String GET_CENTER = CENTERS_URL + "?paged=true&limit=-1&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING CENTERS-------------------------");
+        final String jsonData = new Gson().toJson(Utils.performServerGet(requestSpec, responseSpec, GET_CENTER, "pageItems"));
+        return new Gson().fromJson(jsonData, new TypeToken<ArrayList<CenterDomain>>() {}.getType());
+    }
+
+    public static ArrayList<CenterDomain> listCenters(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        final String GET_CENTER = CENTERS_URL + "?limit=-1&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING CENTERS-------------------------");
+        final String jsonData = new Gson().toJson(Utils.performServerGet(requestSpec, responseSpec, GET_CENTER, ""));
+        return new Gson().fromJson(jsonData, new TypeToken<ArrayList<CenterDomain>>() {}.getType());
+    }
+
+    public static int createCenter(final String name, final int officeId, final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        return createCenter(name, officeId, null, -1, null, null, requestSpec, responseSpec);
+    }
+
+    public static int createCenter(final String name, final int officeId, final String activationDate,
+            final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return createCenter(name, officeId, null, -1, null, activationDate, requestSpec, responseSpec);
+    }
+
+    public static int createCenter(final String name, final int officeId, final String externalId, final int staffId,
+            final int[] groupMembers, final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return createCenter(name, officeId, externalId, staffId, groupMembers, null, requestSpec, responseSpec);
+    }
+
+    public static int createCenter(final String name, final int officeId, final String externalId, final int staffId,
+            final int[] groupMembers, final String activationDate, final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        final String CREATE_CENTER_URL = CENTERS_URL + "?" + Utils.TENANT_IDENTIFIER;
+        HashMap hm = new HashMap();
+        hm.put("name", name);
+        hm.put("officeId", officeId);
+        hm.put("active", false);
+
+        if (externalId != null) hm.put("externalId", externalId);
+        if (staffId != -1) hm.put("staffId", staffId);
+        if (groupMembers != null) hm.put("groupMembers", groupMembers);
+        if (activationDate != null) {
+            hm.put("active", true);
+            hm.put("locale", "en");
+            hm.put("dateFormat", "dd MMM yyyy");
+            hm.put("activationDate", activationDate);
+        }
+        
+        System.out.println("------------------------CREATING CENTER-------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_CENTER_URL, new Gson().toJson(hm), "resourceId");
+    }
+
+    public static HashMap<String, String> updateCenter(final int id, HashMap request, final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        final String UPDATE_CENTER_URL = CENTERS_URL + "/" + id + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------UPDATE CENTER AT " + id + "---------------------------------------------");
+        HashMap<String, String> hash = Utils.performServerPut(requestSpec, responseSpec, UPDATE_CENTER_URL, new Gson().toJson(request),
+                "changes");
+        return hash;
+    }
+
+    public static int[] associateGroups(final int id, final int[] groupMembers, final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        final String ASSOCIATE_GROUP_CENTER_URL = CENTERS_URL + "/" + id + "?command=associateGroups&" + Utils.TENANT_IDENTIFIER;
+        HashMap groupMemberHashMap = new HashMap();
+        groupMemberHashMap.put("groupMembers", groupMembers);
+        System.out.println("---------------------------------ASSOCIATING GROUPS AT " + id + "--------------------------------------------");
+        HashMap hash = Utils.performServerPost(requestSpec, responseSpec, ASSOCIATE_GROUP_CENTER_URL,
+                new Gson().toJson(groupMemberHashMap), "changes");
+        System.out.println(hash);
+        ArrayList<String> arr = (ArrayList<String>) hash.get("groupMembers");
+        int[] ret = new int[arr.size()];
+        for (int i = 0; i < ret.length; i++) {
+            ret[i] = Integer.parseInt(arr.get(i));
+        }
+        return ret;
+    }
+
+    public static void deleteCenter(final int id, final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        final String DELETE_CENTER_URL = CENTERS_URL + "/" + id + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------DELETING CENTER AT " + id + "--------------------------------------------");
+        Utils.performServerDelete(requestSpec, responseSpec, DELETE_CENTER_URL, "");
+    }
+
+    public static Integer createCenter(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            @SuppressWarnings("unused") final boolean active) {
+        System.out.println("---------------------------------CREATING A CENTER---------------------------------------------");
+        return createCenter(requestSpec, responseSpec, "CREATED_DATE");
+    }
+
+    public static Integer createCenter(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String activationDate) {
+        System.out.println("---------------------------------CREATING A CENTER---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_CENTER_URL, getTestCenterAsJSON(true, activationDate), "groupId");
+    }
+
+    public static Integer createCenter(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        System.out.println("---------------------------------CREATING A CENTER---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_CENTER_URL, getTestCenterAsJSON(true, CenterHelper.CREATED_DATE),
+                "groupId");
+    }
+
+    public static int createCenterWithStaffId(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer staffId) {
+        System.out.println("---------------------------------CREATING A CENTER---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_CENTER_URL,
+                getTestCenterWithStaffAsJSON(true, CenterHelper.CREATED_DATE, staffId), "groupId");
+    }
+
+    public static void verifyCenterCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedCenterID) {
+        System.out.println("------------------------------CHECK CENTER DETAILS------------------------------------\n");
+        final String CENTER_URL = "/fineract-provider/api/v1/centers/" + generatedCenterID + "?" + Utils.TENANT_IDENTIFIER;
+        final Integer responseCenterID = Utils.performServerGet(requestSpec, responseSpec, CENTER_URL, "id");
+        assertEquals("ERROR IN CREATING THE CENTER", generatedCenterID, responseCenterID);
+    }
+
+    public static void verifyCenterActivatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedCenterID, final boolean generatedCenterStatus) {
+        System.out.println("------------------------------CHECK CENTER STATUS------------------------------------\n");
+        final String CENTER_URL = "/fineract-provider/api/v1/centers/" + generatedCenterID + "?" + Utils.TENANT_IDENTIFIER;
+        final Boolean responseCenterStatus = Utils.performServerGet(requestSpec, responseSpec, CENTER_URL, "active");
+        assertEquals("ERROR IN ACTIVATING THE CENTER", generatedCenterStatus, responseCenterStatus);
+    }
+
+    public static Integer activateCenter(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String centerId) {
+        final String CENTER_ASSOCIATE_URL = "/fineract-provider/api/v1/centers/" + centerId + "?command=activate&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------ACTIVATE A CENTER---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CENTER_ASSOCIATE_URL, activateCenterAsJSON(""), "groupId");
+    }
+
+    public static String getTestCenterWithStaffAsJSON(final boolean active, final String activationDate, final Integer staffId) {
+       
+        Integer id = null;
+        Integer statusid = null;
+        String statuscode = null;
+        String statusvalue = null;
+        String name = null;
+        String externalId = null;
+        Integer officeID = null;
+        String officeName = null;
+        String hierarchy = null;
+        int[] groupMembers = null;
+        String submittedDate = null;
+
+        return CenterDomain.jsonRequestToCreateCenter(id, statusid, statuscode, statusvalue, active, activationDate,submittedDate,name,
+                externalId, staffId, officeID, officeName, hierarchy, groupMembers);
+    }
+
+    public static String getTestCenterAsJSON(final boolean active, final String activationDate) {
+      
+        Integer id = null;
+        Integer statusid = null;
+        String statuscode = null;
+        String statusvalue = null;
+        String name = null;
+        String externalId = null;
+        Integer officeID = null;
+        String officeName = null;
+        Integer staffId = null;
+        String hierarchy = null;
+        final int[] groupMembers = null;
+        String submittedDate = null;
+
+        return CenterDomain.jsonRequestToCreateCenter(id, statusid, statuscode, statusvalue, active, activationDate,submittedDate,name,
+                externalId, staffId, officeID, officeName, hierarchy, groupMembers);
+        
+    }
+
+    public static String assignStaffAsJSON(final Long staffId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("staffId", staffId);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String unassignStaffAsJSON(final Long staffId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("staffId", staffId);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String activateCenterAsJSON(final String activationDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en");
+        if (StringUtils.isNotEmpty(activationDate)) {
+            map.put("activationDate", activationDate);
+        } else {
+            map.put("activationDate", "CREATED_DATE");
+            System.out.println("defaulting to fixed date: CREATED_DATE");
+        }
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return Utils.randomStringGenerator(prefix, lenOfRandomSuffix);
+    }
+
+    private static String randomIDGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return Utils.randomStringGenerator(prefix, lenOfRandomSuffix, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    }
+
+    public static Object assignStaff(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String groupId, final Long staffId) {
+        final String GROUP_ASSIGN_STAFF_URL = "/fineract-provider/api/v1/groups/" + groupId + "?" + Utils.TENANT_IDENTIFIER
+                + "&command=assignStaff";
+        System.out.println("---------------------------------Assign Staff---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSIGN_STAFF_URL, assignStaffAsJSON(staffId), "changes");
+    }
+
+    public static Object unassignStaff(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String groupId, final Long staffId) {
+        final String GROUP_ASSIGN_STAFF_URL = "/fineract-provider/api/v1/groups/" + groupId + "?" + Utils.TENANT_IDENTIFIER
+                + "&command=unassignStaff";
+        System.out.println("---------------------------------Unassign Staff---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSIGN_STAFF_URL, unassignStaffAsJSON(staffId), "changes");
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ClientChargesTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ClientChargesTest.java
new file mode 100644
index 0000000..f9b01c6
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ClientChargesTest.java
@@ -0,0 +1,175 @@
+/**
+ * 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.integrationtests.common;
+
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * 
+ * IntegrationTest for ClientCharges.
+ * 
+ */
+/**
+ * @author lenovo
+ * 
+ */
+public class ClientChargesTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void clientChargeTest() {
+
+        // Creates clientCharge
+        final Integer chargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getChargeSpecifiedDueDateJSON());
+        Assert.assertNotNull(chargeId);
+
+        // creates client with activation date
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 November 2012");
+        Assert.assertNotNull(clientId);
+
+        /**
+         * create a charge for loan and try to associate to client created in
+         * the above lines.it will be an invalid scenario the reason is client
+         * is not allowed to have only client charge.
+         * 
+         */
+        final Integer loanChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON());
+        Assert.assertNotNull(loanChargeId);
+        ResponseSpecification responseLoanChargeFailure = new ResponseSpecBuilder().expectStatusCode(403).build();
+        final Integer clientLoanChargeId = ClientHelper.addChargesForClient(this.requestSpec, responseLoanChargeFailure, clientId,
+                ClientHelper.getSpecifiedDueDateChargesClientAsJSON(loanChargeId.toString(), "29 October 2011"));
+        Assert.assertNull(clientLoanChargeId);
+
+        /**
+         * associates a clientCharge to a client and pay client charge for 10
+         * USD--success scenario
+         **/
+        final Integer clientChargeId = ClientHelper.addChargesForClient(this.requestSpec, this.responseSpec, clientId,
+                ClientHelper.getSpecifiedDueDateChargesClientAsJSON(chargeId.toString(), "29 October 2011"));
+        Assert.assertNotNull(clientChargeId);
+        final String clientChargePaidTransactionId = ClientHelper.payChargesForClients(this.requestSpec, this.responseSpec, clientId,
+                clientChargeId, ClientHelper.getPayChargeJSON("25 AUGUST 2015", "10"));
+        Assert.assertNotNull(clientChargePaidTransactionId);
+        isValidOutstandingAmount(ClientHelper.getClientCharge(requestSpec, responseSpec, clientId.toString(), clientChargeId.toString()),
+                (float) 190.0);
+
+        /**
+         * Revert the paid client charge transaction by passing the
+         * clientChargePaidTransactionId and ensure the same is reverted.
+         */
+                final Integer undoTrxnId = ClientHelper.revertClientChargeTransaction(this.requestSpec, this.responseSpec,
+                        clientId.toString(), clientChargePaidTransactionId);
+        Assert.assertNotNull(undoTrxnId);
+        isReversedTransaction(clientId.toString(), undoTrxnId.toString());
+        /**
+         * Now pay client charge for 20 USD and ensure the outstanding amount is
+         * updated properly
+         */
+        ResponseSpecification responseSpecFailure = new ResponseSpecBuilder().expectStatusCode(400).build();
+        final String responseId_futureDate_failure = ClientHelper.payChargesForClients(this.requestSpec, responseSpecFailure, clientId,
+                clientChargeId, ClientHelper.getPayChargeJSON("28 AUGUST 2016", "20"));
+        Assert.assertNull(responseId_futureDate_failure);
+
+        // waived off the outstanding client charge
+        final String waiveOffClientChargeTransactionId = ClientHelper.waiveChargesForClients(this.requestSpec, this.responseSpec, clientId,
+                clientChargeId, ClientHelper.getWaiveChargeJSON("100", clientChargeId.toString()));
+        Assert.assertNotNull(waiveOffClientChargeTransactionId);
+
+        /**
+         * Revert the waived off client charge transaction by passing the
+         * waiveOffClientChargeTransactionId and ensured the transaction is
+         * reversed.
+         */
+        final Integer undoWaiveTrxnId = ClientHelper.revertClientChargeTransaction(this.requestSpec, this.responseSpec, clientId.toString(),
+                waiveOffClientChargeTransactionId);
+        Assert.assertNotNull(undoWaiveTrxnId);
+        isReversedTransaction(clientId.toString(), undoWaiveTrxnId.toString());
+        /**
+         * pay client charge before client activation date and ensured its a
+         * failure test case
+         */
+
+        final String responseId_activationDate_failure = ClientHelper.payChargesForClients(this.requestSpec, responseSpecFailure, clientId,
+                clientChargeId, ClientHelper.getPayChargeJSON("30 October 2011", "20"));
+        Assert.assertNull(responseId_activationDate_failure);
+        /**
+         * pay client charge more than outstanding amount amount and ensured its
+         * a failure test case
+         */
+        final String responseId_moreAmount_failure = ClientHelper.payChargesForClients(this.requestSpec, responseSpecFailure, clientId,
+                clientChargeId, ClientHelper.getPayChargeJSON("25 AUGUST 2015", "300"));
+        Assert.assertNull(responseId_moreAmount_failure);
+        /**
+         * pay client charge for 10 USD and ensure outstanding amount is updated
+         * properly
+         */
+        final String chargePaid_responseId = ClientHelper.payChargesForClients(this.requestSpec, this.responseSpec, clientId,
+                clientChargeId, ClientHelper.getPayChargeJSON("25 AUGUST 2015", "100"));
+        Assert.assertNotNull(chargePaid_responseId);
+
+        isValidOutstandingAmount(ClientHelper.getClientCharge(requestSpec, responseSpec, clientId.toString(), clientChargeId.toString()),
+                (float) 100.0);
+
+    }
+
+    /**
+     * It checks whether the client charge transaction is reversed or not.
+     * 
+     * @param clientId
+     * @param transactionId
+     */
+    private void isReversedTransaction(String clientId, String transactionId) {
+        final Boolean isReversed = ClientHelper.getClientTransactions(this.requestSpec, this.responseSpec, clientId.toString(),
+                transactionId);
+        Assert.assertTrue(isReversed);
+    }
+
+    /**
+     * Check whether the outStandingAmount is equal to expected Amount or not
+     * after paying or after waiving off the client charge.
+     * 
+     * @param outStandingAmount
+     * @param expectedAmount
+     */
+    private void isValidOutstandingAmount(Object outStandingAmount, Object expectedAmount) {
+        Assert.assertEquals((float) outStandingAmount, expectedAmount);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ClientHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ClientHelper.java
new file mode 100755
index 0000000..fa46249
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ClientHelper.java
@@ -0,0 +1,442 @@
+/**
+ * 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.integrationtests.common;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.integrationtests.common.system.CodeHelper;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class ClientHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    private static final String CREATE_CLIENT_URL = "/fineract-provider/api/v1/clients?" + Utils.TENANT_IDENTIFIER;
+    private static final String CLIENT_URL = "/fineract-provider/api/v1/clients";
+    private static final String CLOSE_CLIENT_COMMAND = "close";
+    private static final String REACTIVATE_CLIENT_COMMAND = "reactivate";
+    private static final String REJECT_CLIENT_COMMAND = "reject";
+    private static final String ACTIVATE_CLIENT_COMMAND = "activate";
+    private static final String WITHDRAW_CLIENT_COMMAND = "withdraw";
+
+    public static final String CREATED_DATE = "27 November 2014";
+    public static final String CREATED_DATE_PLUS_ONE = "28 November 2014";
+    public static final String CREATED_DATE_MINUS_ONE = "27 November 2014";
+    public static final String TRANSACTION_DATE = "01 March 2013";
+    public static final String LAST_TRANSACTION_DATE = "01 March 2013";
+    public static final String DATE_FORMAT = "dd MMMM yyyy";
+
+    public ClientHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public static Integer createClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return createClient(requestSpec, responseSpec, "04 March 2011");
+    }
+
+    public static Integer createClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String activationDate) {
+        return createClient(requestSpec, responseSpec, activationDate, "1");
+    }
+
+    public static Integer createClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String activationDate, final String officeId) {
+        System.out.println("---------------------------------CREATING A CLIENT---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_CLIENT_URL, getTestClientAsJSON(activationDate, officeId),
+                "clientId");
+    }
+    
+    public static Integer createClientAsPerson(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return createClientAsPerson(requestSpec, responseSpec, "04 March 2011");
+    }
+
+    public static Integer createClientAsPerson(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String activationDate) {
+        return createClientAsPerson(requestSpec, responseSpec, activationDate, "1");
+    }
+
+    public static Integer createClientAsPerson(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String activationDate, final String officeId) {
+    	
+        System.out.println("---------------------------------CREATING A CLIENT NON PERSON(ORGANISATION)---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_CLIENT_URL, getTestPersonClientAsJSON(activationDate, officeId),
+                "clientId");
+    }
+    
+    public static Integer createClientAsEntity(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return createClientAsEntity(requestSpec, responseSpec, "04 March 2011");
+    }
+
+    public static Integer createClientAsEntity(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String activationDate) {
+        return createClientAsEntity(requestSpec, responseSpec, activationDate, "1");
+    }
+
+    public static Integer createClientAsEntity(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String activationDate, final String officeId) {
+    	
+    	Integer constitutionCodeId = (Integer) CodeHelper.getCodeByName(requestSpec, responseSpec, "Constitution").get("id");
+    	Integer soleProprietorCodeValueId = (Integer) CodeHelper.retrieveOrCreateCodeValue(constitutionCodeId, requestSpec, responseSpec).get("id");
+    	
+        System.out.println("---------------------------------CREATING A CLIENT NON PERSON(ORGANISATION)---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_CLIENT_URL, getTestEntityClientAsJSON(activationDate, officeId, soleProprietorCodeValueId),
+                "clientId");
+    }
+
+    public static Integer createClientForAccountPreference(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer clientType, String jsonAttributeToGetBack) {
+        final String activationDate = "04 March 2011";
+        final String officeId = "1";
+        System.out.println(
+                "---------------------------------CREATING A CLIENT BASED ON ACCOUNT PREFERENCE---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_CLIENT_URL,
+                getTestClientWithClientTypeAsJSON(activationDate, officeId, clientType.toString()), jsonAttributeToGetBack);
+    }
+
+    public static Object assignStaffToClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String clientId, final String staffId) {
+        final String CLIENT_ASSIGN_STAFF_URL = "/fineract-provider/api/v1/clients/" + clientId + "?" + Utils.TENANT_IDENTIFIER
+                + "&command=assignStaff";
+
+        System.out.println("---------------------------------CREATING A CLIENT---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CLIENT_ASSIGN_STAFF_URL, assignStaffToClientAsJson(staffId), "changes");
+    }
+
+    public static Integer getClientsStaffId(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String clientId) {
+        return (Integer) getClient(requestSpec, responseSpec, clientId, "staffId");
+    }
+
+    public static String getTestClientAsJSON(final String dateOfJoining, final String officeId) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("officeId", officeId);
+        map.put("firstname", Utils.randomNameGenerator("Client_FirstName_", 5));
+        map.put("lastname", Utils.randomNameGenerator("Client_LastName_", 4));
+        map.put("externalId", randomIDGenerator("ID_", 7));
+        map.put("dateFormat", DATE_FORMAT);
+        map.put("locale", "en");
+        map.put("active", "true");
+        map.put("activationDate", dateOfJoining);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+    
+    public static String getTestPersonClientAsJSON(final String dateOfJoining, final String officeId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("officeId", officeId);
+        map.put("fullname", Utils.randomNameGenerator("Client_FullName_", 5));
+        map.put("externalId", randomIDGenerator("ID_", 7));
+        map.put("dateFormat", DATE_FORMAT);
+        map.put("locale", "en");
+        map.put("active", "true");
+        map.put("activationDate", dateOfJoining);
+        map.put("legalFormId", 1);
+        
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+    
+    public static String getTestEntityClientAsJSON(final String dateOfJoining, final String officeId, final Integer soleProprietorCodeValueId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("officeId", officeId);
+        map.put("fullname", Utils.randomNameGenerator("Client_FullName_", 5));
+        map.put("externalId", randomIDGenerator("ID_", 7));
+        map.put("dateFormat", DATE_FORMAT);
+        map.put("locale", "en");
+        map.put("active", "true");
+        map.put("activationDate", dateOfJoining);
+        map.put("legalFormId", 2);
+        
+        final HashMap<String, Object> clientNonPersonMap = new HashMap<>();
+        clientNonPersonMap.put("constitutionId", soleProprietorCodeValueId);
+        map.put("clientNonPersonDetails", clientNonPersonMap);
+        
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String getTestClientWithClientTypeAsJSON(final String dateOfJoining, final String officeId, final String clientType) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("officeId", officeId);
+        map.put("firstname", Utils.randomNameGenerator("Client_FirstName_", 5));
+        map.put("lastname", Utils.randomNameGenerator("Client_LastName_", 4));
+        map.put("externalId", randomIDGenerator("ID_", 7));
+        map.put("dateFormat", DATE_FORMAT);
+        map.put("locale", "en");
+        map.put("active", "true");
+        map.put("activationDate", dateOfJoining);
+        map.put("clientTypeId", clientType);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String assignStaffToClientAsJson(final String staffId) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("staffId", staffId);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static void verifyClientCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedClientID) {
+        System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n");
+        final String CLIENT_URL = "/fineract-provider/api/v1/clients/" + generatedClientID + "?" + Utils.TENANT_IDENTIFIER;
+        final Integer responseClientID = Utils.performServerGet(requestSpec, responseSpec, CLIENT_URL, "id");
+        assertEquals("ERROR IN CREATING THE CLIENT", generatedClientID, responseClientID);
+    }
+
+    public static Object getClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String clientId,
+            final String jsonReturn) {
+        final String GET_CLIENT_URL = "/fineract-provider/api/v1/clients/" + clientId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------GET A CLIENT---------------------------------------------");
+        return Utils.performServerGet(requestSpec, responseSpec, GET_CLIENT_URL, jsonReturn);
+
+    }
+
+    /* Client status is a map.So adding SuppressWarnings */
+    @SuppressWarnings("unchecked")
+    public static HashMap<String, Object> getClientStatus(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String clientId) {
+        return (HashMap<String, Object>) getClient(requestSpec, responseSpec, clientId, "status");
+    }
+
+    private static String randomIDGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return Utils.randomStringGenerator(prefix, lenOfRandomSuffix, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    }
+
+    private String getCloseClientAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+
+        /* Retrieve Code id for the Code "ClientClosureReason" */
+        String codeName = "ClientClosureReason";
+        HashMap<String, Object> code = CodeHelper.getCodeByName(this.requestSpec, this.responseSpec, codeName);
+        Integer clientClosureCodeId = (Integer) code.get("id");
+
+        /* Retrieve/Create Code Values for the Code "ClientClosureReason" */
+        HashMap<String, Object> codeValue = CodeHelper.retrieveOrCreateCodeValue(clientClosureCodeId, this.requestSpec, this.responseSpec);
+        Integer closureReasonId = (Integer) codeValue.get("id");
+
+        map.put("closureReasonId", closureReasonId.toString());
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("closureDate", CREATED_DATE_PLUS_ONE);
+
+        String clientJson = new Gson().toJson(map);
+        System.out.println(clientJson);
+        return clientJson;
+
+    }
+
+    private String getReactivateClientAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("reactivationDate", CREATED_DATE_PLUS_ONE);
+        String clientJson = new Gson().toJson(map);
+        System.out.println(clientJson);
+        return clientJson;
+
+    }
+
+    private String getRejectClientAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        /* Retrieve Code id for the Code "ClientRejectReason" */
+        String codeName = "ClientRejectReason";
+        HashMap<String, Object> code = CodeHelper.getCodeByName(this.requestSpec, this.responseSpec, codeName);
+        Integer clientRejectionReasonCodeId = (Integer) code.get("id");
+
+        /* Retrieve/Create Code Values for the Code "ClientRejectReason" */
+        HashMap<String, Object> codeValue = CodeHelper.retrieveOrCreateCodeValue(clientRejectionReasonCodeId, this.requestSpec,
+                this.responseSpec);
+        Integer rejectionReasonId = (Integer) codeValue.get("id");
+
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("rejectionDate", CREATED_DATE_PLUS_ONE);
+        map.put("rejectionReasonId", rejectionReasonId.toString());
+        String clientJson = new Gson().toJson(map);
+        System.out.println(clientJson);
+        return clientJson;
+
+    }
+
+    private String getActivateClientAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("activationDate", CREATED_DATE_PLUS_ONE);
+        String clientJson = new Gson().toJson(map);
+        System.out.println(clientJson);
+        return clientJson;
+
+    }
+
+    private String getWithdrawClientAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        /* Retrieve Code id for the Code "ClientWithdrawReason" */
+        String codeName = "ClientWithdrawReason";
+        HashMap<String, Object> code = CodeHelper.getCodeByName(this.requestSpec, this.responseSpec, codeName);
+        Integer clientWithdrawReasonCodeId = (Integer) code.get("id");
+
+        /* Retrieve/Create Code Values for the Code "ClientWithdrawReason" */
+        HashMap<String, Object> codeValue = CodeHelper.retrieveOrCreateCodeValue(clientWithdrawReasonCodeId, this.requestSpec,
+                this.responseSpec);
+        Integer withdrawalReasonId = (Integer) codeValue.get("id");
+
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("withdrawalDate", CREATED_DATE_PLUS_ONE);
+        map.put("withdrawalReasonId", withdrawalReasonId.toString());
+        String clientJson = new Gson().toJson(map);
+        System.out.println(clientJson);
+        return clientJson;
+
+    }
+
+    public static String getSpecifiedDueDateChargesClientAsJSON(final String chargeId, final String dueDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("dateFormat", DATE_FORMAT);
+        map.put("dueDate", dueDate);
+        map.put("chargeId", chargeId);
+        map.put("amount", "200");
+        String json = new Gson().toJson(map);
+        return json;
+    }
+
+    public static String getPayChargeJSON(final String date, String amount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("dateFormat", DATE_FORMAT);
+        map.put("transactionDate", date);
+        map.put("amount", amount);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getWaiveChargeJSON(final String amount, String clientChargeId) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("amount", amount);
+        map.put("clientChargeId", clientChargeId);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public HashMap<String, Object> closeClient(final Integer clientId) {
+        System.out.println("--------------------------------- CLOSE CLIENT -------------------------------");
+        return performClientActions(createClientOperationURL(CLOSE_CLIENT_COMMAND, clientId), getCloseClientAsJSON(), clientId);
+    }
+
+    public HashMap<String, Object> reactivateClient(final Integer clientId) {
+        System.out.println("--------------------------------- REACTIVATE CLIENT -------------------------------");
+        return performClientActions(createClientOperationURL(REACTIVATE_CLIENT_COMMAND, clientId), getReactivateClientAsJSON(), clientId);
+    }
+
+    public HashMap<String, Object> rejectClient(final Integer clientId) {
+        System.out.println("--------------------------------- REJECT CLIENT -------------------------------");
+        return performClientActions(createClientOperationURL(REJECT_CLIENT_COMMAND, clientId), getRejectClientAsJSON(), clientId);
+    }
+
+    public HashMap<String, Object> activateClient(final Integer clientId) {
+        System.out.println("--------------------------------- ACTIVATE CLIENT -------------------------------");
+        return performClientActions(createClientOperationURL(ACTIVATE_CLIENT_COMMAND, clientId), getActivateClientAsJSON(), clientId);
+    }
+
+    public HashMap<String, Object> withdrawClient(final Integer clientId) {
+        System.out.println("--------------------------------- WITHDRAWN CLIENT -------------------------------");
+        return performClientActions(createClientOperationURL(WITHDRAW_CLIENT_COMMAND, clientId), getWithdrawClientAsJSON(), clientId);
+    }
+
+    private String createClientOperationURL(final String command, final Integer clientId) {
+        return CLIENT_URL + "/" + clientId + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private HashMap<String, Object> performClientActions(final String postURLForClient, final String jsonToBeSent, final Integer clientId) {
+        Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForClient, jsonToBeSent, CommonConstants.RESPONSE_STATUS);
+        HashMap<String, Object> response = ClientHelper.getClientStatus(requestSpec, responseSpec, String.valueOf(clientId));
+
+        return response;
+    }
+
+    public static Integer addChargesForClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer clientId, final String request) {
+        System.out.println("--------------------------------- ADD CHARGES FOR Client --------------------------------");
+        final String ADD_CHARGES_URL = "/fineract-provider/api/v1/clients/" + clientId + "/charges?" + Utils.TENANT_IDENTIFIER;
+        final HashMap<?, ?> response = Utils.performServerPost(requestSpec, responseSpec, ADD_CHARGES_URL, request, "");
+        return (Integer) response.get("resourceId");
+    }
+
+    public static String payChargesForClients(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer clientId, final Integer clientChargeId, final String json) {
+        System.out.println("--------------------------------- PAY CHARGES FOR CLIENT --------------------------------");
+        final String CHARGES_URL = "/fineract-provider/api/v1/clients/" + clientId + "/charges/" + clientChargeId + "?command=paycharge&"
+                + Utils.TENANT_IDENTIFIER;
+        final HashMap<?, ?> response = Utils.performServerPost(requestSpec, responseSpec, CHARGES_URL, json, "");
+        return response.get("transactionId") != null ? response.get("transactionId").toString() : null;
+    }
+
+    public static String waiveChargesForClients(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer clientId, final Integer clientChargeId, final String json) {
+        System.out.println("--------------------------------- WAIVE CHARGES FOR CLIENT --------------------------------");
+        final String CHARGES_URL = "/fineract-provider/api/v1/clients/" + clientId + "/charges/" + clientChargeId + "?command=waive&"
+                + Utils.TENANT_IDENTIFIER;
+
+        final HashMap<?, ?> response = Utils.performServerPost(requestSpec, responseSpec, CHARGES_URL, json, "");
+        return response.get("transactionId").toString();
+    }
+
+    public static Integer revertClientChargeTransaction(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String clientId, String clientChargeId) {
+        System.out.println("---------------------------------UNDO TRANSACTION---------------------------------------------");
+        final String CHARGES_URL = "/fineract-provider/api/v1/clients/" + clientId + "/transactions/" + clientChargeId + "?command=undo&"
+                + Utils.TENANT_IDENTIFIER;
+
+        final HashMap<?, ?> response = Utils.performServerPost(requestSpec, responseSpec, CHARGES_URL, "", "");
+        return (Integer) response.get("resourceId");
+
+    }
+
+    public static Object getClientCharge(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String clientId, final String clientChargeId) {
+        System.out.println("---------------------------------GET CLIENT CHARGE---------------------------------------------");
+        final String CHARGES_URL = "/fineract-provider/api/v1/clients/" + clientId + "/charges/" + clientChargeId + "?"
+                + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, CHARGES_URL, "amountOutstanding");
+    }
+
+    public static Boolean getClientTransactions(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String clientId, final String transactionId) {
+        System.out.println("---------------------------------GET CLIENT CHARGE TRANSACTIONS---------------------------------------------");
+        final String CHARGES_URL = "/fineract-provider/api/v1/clients/" + clientId + "/transactions/" + transactionId + "?"
+                + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, CHARGES_URL, "reversed");
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CommonConstants.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CommonConstants.java
new file mode 100755
index 0000000..8ec47af
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CommonConstants.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests.common;
+
+
+public interface CommonConstants {
+
+    public final static String locale = "en";
+    public static final String dateFormat = "dd MMMM yyyy";
+    public static final String RESPONSE_RESOURCE_ID = "resourceId";
+    public static final String RESPONSE_CHANGES = "changes";
+    public static final String RESPONSE_STATUS = "status";
+    public static final String RESPONSE_ERROR = "errors";
+    public static final String RESPONSE_ERROR_MESSAGE_CODE = "userMessageGlobalisationCode";
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java
new file mode 100644
index 0000000..900e6bb
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java
@@ -0,0 +1,78 @@
+/**
+ * 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.integrationtests.common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes", "unchecked" })
+public class CurrenciesHelper {
+
+    private static final String CURRENCIES_URL = "/fineract-provider/api/v1/currencies";
+
+    public static ArrayList<CurrencyDomain> getAllCurrencies(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        final String GET_ALL_CURRENCIES_URL = CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING ALL CURRENCIES -------------------------");
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_CURRENCIES_URL, "");
+        ArrayList<HashMap> selectedCurrencyOptions = (ArrayList<HashMap>) response.get("selectedCurrencyOptions");
+        ArrayList<HashMap> currencyOptions = (ArrayList<HashMap>) response.get("currencyOptions");
+        currencyOptions.addAll(selectedCurrencyOptions);
+        final String jsonData = new Gson().toJson(new ArrayList<HashMap>(selectedCurrencyOptions));
+        return new Gson().fromJson(jsonData, new TypeToken<ArrayList<CurrencyDomain>>() {}.getType());
+    }
+
+    public static ArrayList<CurrencyDomain> getSelectedCurrencies(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        final String GET_ALL_SELECTED_CURRENCIES_URL = CURRENCIES_URL + "?fields=selectedCurrencyOptions" + "&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING ALL SELECTED CURRENCIES -------------------------");
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_SELECTED_CURRENCIES_URL, "");
+        final String jsonData = new Gson().toJson(response.get("selectedCurrencyOptions"));
+        return new Gson().fromJson(jsonData, new TypeToken<ArrayList<CurrencyDomain>>() {}.getType());
+    }
+
+    public static CurrencyDomain getCurrencybyCode(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String code) {
+        ArrayList<CurrencyDomain> currenciesList = getAllCurrencies(requestSpec, responseSpec);
+        for (CurrencyDomain e : currenciesList) {
+            if (e.getCode().equals(code)) return e;
+        }
+        return null;
+    }
+
+    public static ArrayList<String> updateSelectedCurrencies(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final ArrayList<String> currencies) {
+        final String CURRENCIES_UPDATE_URL = CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------UPDATE SELECTED CURRENCIES LIST---------------------------------------------");
+        HashMap hash = Utils.performServerPut(requestSpec, responseSpec, CURRENCIES_UPDATE_URL, currenciesToJSON(currencies), "changes");
+        return (ArrayList<String>) hash.get("currencies");
+    }
+
+    private static String currenciesToJSON(final ArrayList<String> currencies) {
+        HashMap map = new HashMap<>();
+        map.put("currencies", currencies);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CurrencyDomain.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CurrencyDomain.java
new file mode 100644
index 0000000..00cbc64
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CurrencyDomain.java
@@ -0,0 +1,140 @@
+/**
+ * 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.integrationtests.common;
+
+import com.google.gson.Gson;
+
+public class CurrencyDomain implements Comparable<CurrencyDomain> {
+
+    public static class Builder {
+
+        private String code;
+        private String name;
+        private int decimalPlaces;
+        private String displaySymbol;
+        private String nameCode;
+        private String displayLabel;
+
+        private Builder(final String code, final String name, final int decimalPlaces, final String displaySymbol, final String nameCode,
+                final String displayLabel) {
+            this.code = code;
+            this.name = name;
+            this.decimalPlaces = decimalPlaces;
+            this.displaySymbol = displaySymbol;
+            this.nameCode = nameCode;
+            this.displayLabel = displayLabel;
+        }
+
+        public CurrencyDomain build() {
+            return new CurrencyDomain(this.code, this.name, this.decimalPlaces, this.displaySymbol, this.nameCode, this.displayLabel);
+        }
+    }
+
+    private String code;
+    private String name;
+    private int decimalPlaces;
+    private String displaySymbol;
+    private String nameCode;
+    private String displayLabel;
+
+    CurrencyDomain() {
+        super();
+    }
+
+    private CurrencyDomain(final String code, final String name, final int decimalPlaces, final String displaySymbol,
+            final String nameCode, final String displayLabel) {
+        super();
+        this.code = code;
+        this.name = name;
+        this.decimalPlaces = decimalPlaces;
+        this.displaySymbol = displaySymbol;
+        this.nameCode = nameCode;
+        this.displayLabel = displayLabel;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public int getDecimalPlaces() {
+        return this.decimalPlaces;
+    }
+
+    public String getDisplaySymbol() {
+        return this.displaySymbol;
+    }
+
+    public String getNameCode() {
+        return this.nameCode;
+    }
+
+    public String getDisplayLabel() {
+        return this.displayLabel;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String toJSON() {
+        return new Gson().toJson(this);
+    }
+
+    public static CurrencyDomain fromJSON(final String jsonData) {
+        return new Gson().fromJson(jsonData, CurrencyDomain.class);
+    }
+
+    public static Builder create(final String code, final String name, final int decimalPlaces, final String displaySymbol,
+            final String nameCode, final String displayLabel) {
+        return new Builder(code, name, decimalPlaces, displaySymbol, nameCode, displayLabel);
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 1;
+
+        if (this.name != null) hash += this.name.hashCode();
+        if (this.code != null) hash += this.code.hashCode();
+        if (this.decimalPlaces >= 0) hash += this.decimalPlaces;
+        if (this.displaySymbol != null) hash += this.displaySymbol.hashCode();
+        if (this.nameCode != null) hash += this.nameCode.hashCode();
+        if (this.displayLabel != null) hash += this.displayLabel.hashCode();
+
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) { return true; }
+
+        if (!(obj instanceof CurrencyDomain)) return false;
+
+        CurrencyDomain cd = (CurrencyDomain) obj;
+
+        if (this.name.equals(cd.name) && this.code.equals(cd.code) && this.decimalPlaces == cd.decimalPlaces
+                && this.displaySymbol.equals(cd.displaySymbol) && this.nameCode.equals(cd.nameCode)
+                && this.displayLabel.equals(cd.displayLabel)) return true;
+        return false;
+    }
+
+    @Override
+    public int compareTo(CurrencyDomain cd) {
+        return this.name.compareTo(cd.getName());
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ExternalServicesConfigurationHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ExternalServicesConfigurationHelper.java
new file mode 100644
index 0000000..35011db
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ExternalServicesConfigurationHelper.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.integrationtests.common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class ExternalServicesConfigurationHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public ExternalServicesConfigurationHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public static ArrayList<HashMap> getExternalServicesConfigurationByServiceName(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String serviceName) {
+        final String GET_EXTERNAL_SERVICES_CONFIG_BY_SERVICE_NAME_URL = "/fineract-provider/api/v1/externalservice/" + serviceName + "?"
+                + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING GLOBAL CONFIGURATION BY ID -------------------------");
+        return Utils.performServerGet(requestSpec, responseSpec, GET_EXTERNAL_SERVICES_CONFIG_BY_SERVICE_NAME_URL, "");
+    }
+
+    public static HashMap updateValueForExternaServicesConfiguration(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String serviceName, final String name, final String value) {
+        final String EXTERNAL_SERVICES_CONFIG_UPDATE_URL = "/fineract-provider/api/v1/externalservice/" + serviceName + "?"
+                + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------UPDATE VALUE FOR GLOBAL CONFIG---------------------------------------------");
+        HashMap map = Utils.performServerPut(requestSpec, responseSpec, EXTERNAL_SERVICES_CONFIG_UPDATE_URL,
+                updateExternalServicesConfigUpdateValueAsJSON(name, value), "");
+
+        return (HashMap) map.get("changes");
+    }
+
+    public static String updateExternalServicesConfigUpdateValueAsJSON(final String name, final String value) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put(name, value);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
new file mode 100644
index 0000000..d1acfb8
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.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.integrationtests.common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes" })
+public class GlobalConfigurationHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public GlobalConfigurationHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public static ArrayList getAllGlobalConfigurations(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        final String GET_ALL_GLOBAL_CONFIG_URL = "/fineract-provider/api/v1/configurations?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING ALL GLOBAL CONFIGURATIONS -------------------------");
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_GLOBAL_CONFIG_URL, "");
+        return (ArrayList) response.get("globalConfiguration");
+    }
+
+    public static HashMap getGlobalConfigurationById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String configId) {
+        final String GET_GLOBAL_CONFIG_BY_ID_URL = "/fineract-provider/api/v1/configurations/" + configId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING GLOBAL CONFIGURATION BY ID -------------------------");
+        return Utils.performServerGet(requestSpec, responseSpec, GET_GLOBAL_CONFIG_BY_ID_URL, "");
+    }
+
+    public static Integer updateValueForGlobalConfiguration(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String configId, final String value) {
+        final String GLOBAL_CONFIG_UPDATE_URL = "/fineract-provider/api/v1/configurations/" + configId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------UPDATE VALUE FOR GLOBAL CONFIG---------------------------------------------");
+        return Utils.performServerPut(requestSpec, responseSpec, GLOBAL_CONFIG_UPDATE_URL, updateGlobalConfigUpdateValueAsJSON(value),
+                "resourceId");
+    }
+
+    public static Integer updateEnabledFlagForGlobalConfiguration(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String configId, final Boolean enabled) {
+        final String GLOBAL_CONFIG_UPDATE_URL = "/fineract-provider/api/v1/configurations/" + configId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out
+                .println("---------------------------------UPDATE GLOBAL CONFIG FOR ENABLED FLAG---------------------------------------------");
+        return Utils.performServerPut(requestSpec, responseSpec, GLOBAL_CONFIG_UPDATE_URL,
+                updateGlobalConfigUpdateEnabledFlagAsJSON(enabled), "resourceId");
+    }
+
+    public static ArrayList getGlobalConfigurationIsCacheEnabled(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        final String GET_IS_CACHE_GLOBAL_CONFIG_URL = "/fineract-provider/api/v1/caches?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING IS CACHE ENABLED GLOBAL CONFIGURATION -------------------------");
+        final ArrayList<HashMap> response = Utils.performServerGet(requestSpec, responseSpec, GET_IS_CACHE_GLOBAL_CONFIG_URL, "");
+        return response;
+    }
+
+    public static HashMap updateIsCacheEnabledForGlobalConfiguration(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String cacheType) {
+        final String IS_CACHE_GLOBAL_CONFIG_UPDATE_URL = "/fineract-provider/api/v1/caches?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------UPDATE GLOBAL CONFIG FOR IS CACHE ENABLED----------------------");
+        return Utils.performServerPut(requestSpec, responseSpec, IS_CACHE_GLOBAL_CONFIG_UPDATE_URL,
+                updateIsCacheEnabledGlobalConfigUpdateAsJSON(cacheType), "changes");
+    }
+    
+    public static Object updatePasswordResetDaysForGlobalConfiguration(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final Integer configId, final String value, final String enabled, final String jsonAttributeToGetBack) {
+        final String UPDATE_URL = "/fineract-provider/api/v1/configurations/" + configId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------UPDATE GLOBAL CONFIG FOR FORCE PASSWORD RESET DAYS----------------------");
+        return Utils.performServerPut(requestSpec, responseSpec, UPDATE_URL,
+                updatePasswordResetDaysGlobalConfigAsJSON(value, enabled), jsonAttributeToGetBack);
+    }
+
+    public static String updateGlobalConfigUpdateValueAsJSON(final String value) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("value", value);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+    
+    public static String updatePasswordResetDaysGlobalConfigAsJSON(final String value, final String enabled) {
+        final HashMap<String, String> map = new HashMap<>();
+        if(value != null){
+            map.put("value", value);
+        }
+        map.put("enabled", enabled);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String updateGlobalConfigUpdateEnabledFlagAsJSON(final Boolean enabled) {
+        final HashMap<String, Boolean> map = new HashMap<String, Boolean>();
+        map.put("enabled", enabled);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String updateIsCacheEnabledGlobalConfigUpdateAsJSON(final String cacheType) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("cacheType", cacheType);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GroupHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GroupHelper.java
new file mode 100755
index 0000000..f8a3af0
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GroupHelper.java
@@ -0,0 +1,230 @@
+/**
+ * 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.integrationtests.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class GroupHelper {
+
+    private static final String CREATE_GROUP_URL = "/fineract-provider/api/v1/groups?" + Utils.TENANT_IDENTIFIER;
+
+    public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            @SuppressWarnings("unused") final boolean active) {
+        System.out.println("---------------------------------CREATING A GROUP---------------------------------------------");
+        return createGroup(requestSpec, responseSpec, "04 March 2011");
+    }
+
+    public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String activationDate) {
+        System.out.println("---------------------------------CREATING A GROUP---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_GROUP_URL, getTestGroupAsJSON(true, activationDate), "groupId");
+    }
+
+    public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        System.out.println("---------------------------------CREATING A GROUP---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_GROUP_URL, getTestGroupAsJSON(false, ""), "groupId");
+    }
+
+    public static Integer associateClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String groupId, final String clientMember) {
+        final String GROUP_ASSOCIATE_URL = "/fineract-provider/api/v1/groups/" + groupId
+                + "?command=associateClients&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------Associate Client To A GROUP---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, associateClientAsJSON(clientMember), "groupId");
+    }
+
+    public static Integer disAssociateClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String groupId, final String clientMember) {
+        final String GROUP_ASSOCIATE_URL = "/fineract-provider/api/v1/groups/" + groupId
+                + "?command=disassociateClients&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------Disassociate Client To A GROUP---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, associateClientAsJSON(clientMember), "groupId");
+    }
+
+    public static Integer activateGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String groupId) {
+        final String GROUP_ASSOCIATE_URL = "/fineract-provider/api/v1/groups/" + groupId + "?command=activate&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------Activate A GROUP---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, activateGroupAsJSON(""), "groupId");
+    }
+
+    public static Integer updateGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String name,
+            final String groupId) {
+        final String GROUP_ASSOCIATE_URL = "/fineract-provider/api/v1/groups/" + groupId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------UPDATE GROUP---------------------------------------------");
+        return Utils.performServerPut(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, updateGroupAsJSON(name), "groupId");
+    }
+
+    public static Integer deleteGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String groupId) {
+        final String GROUP_ASSOCIATE_URL = "/fineract-provider/api/v1/groups/" + groupId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------DELETE GROUP---------------------------------------------");
+        return Utils.performServerDelete(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, "groupId");
+    }
+
+
+    public static Object assignStaff(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String groupId,final Long staffId){
+        final String GROUP_ASSIGN_STAFF_URL = "/fineract-provider/api/v1/groups/" + groupId + "?" + Utils.TENANT_IDENTIFIER + "&command=assignStaff";
+        System.out.println("---------------------------------DELETE GROUP---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSIGN_STAFF_URL,assignStaffAsJSON(staffId),"changes");
+    }
+    public static Object assignStaffInheritStaffForClientAccounts(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String groupId,final String staffId){
+        final String GROUP_ASSIGN_STAFF_URL = "/fineract-provider/api/v1/groups/" + groupId + "?" + Utils.TENANT_IDENTIFIER + "&command=assignStaff";
+        System.out.println("---------------------------------DELETE GROUP---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSIGN_STAFF_URL,assignStaffAndInheritStaffForClientAccountsAsJSON(staffId),"changes");
+    }
+
+
+    public static String getTestGroupAsJSON(final boolean active, final String activationDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("officeId", "1");
+        map.put("name", randomNameGenerator("Group_Name_", 5));
+        map.put("externalId", randomIDGenerator("ID_", 7));
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en");
+        if (active) {
+            map.put("active", "true");
+            map.put("activationDate", activationDate);
+        } else {
+            map.put("active", "false");
+            map.put("submittedOnDate", "04 March 2011");
+            System.out.println("defaulting to inactive group: 04 March 2011");
+        }
+
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String associateClientAsJSON(final String clientMember) {
+        final HashMap<String, List<String>> map = new HashMap<String, List<String>>();
+        final List<String> list = new ArrayList<>();
+        list.add(clientMember);
+        map.put("clientMembers", list);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String activateGroupAsJSON(final String activationDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en");
+        if (StringUtils.isNotEmpty(activationDate)) {
+            map.put("activationDate", activationDate);
+        } else {
+            map.put("activationDate", "04 March 2011");
+            System.out.println("defaulting to fixed date: 04 March 2011");
+        }
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String updateGroupAsJSON(final String name) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("name", name);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+    public static String assignStaffAsJSON(final Long staffId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("staffId", staffId);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+    public static String assignStaffAndInheritStaffForClientAccountsAsJSON(final String staffId) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("staffId", staffId);
+        map.put("inheritStaffForClientAccounts","true");
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+
+    public static void verifyGroupCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedGroupID) {
+        System.out.println("------------------------------CHECK GROUP DETAILS------------------------------------\n");
+        final String GROUP_URL = "/fineract-provider/api/v1/groups/" + generatedGroupID + "?" + Utils.TENANT_IDENTIFIER;
+        final Integer responseGroupID = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "id");
+        assertEquals("ERROR IN CREATING THE GROUP", generatedGroupID, responseGroupID);
+    }
+
+    public static void verifyGroupDetails(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedGroupID, final String field, final String expectedValue) {
+        System.out.println("------------------------------CHECK GROUP DETAILS------------------------------------\n");
+        final String GROUP_URL = "/fineract-provider/api/v1/groups/" + generatedGroupID + "?" + Utils.TENANT_IDENTIFIER;
+        final String responseValue = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, field);
+        assertEquals("ERROR IN CREATING THE GROUP", expectedValue, responseValue);
+    }
+
+    public static void verifyGroupActivatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedGroupID, final boolean generatedGroupStatus) {
+        System.out.println("------------------------------CHECK GROUP STATUS------------------------------------\n");
+        final String GROUP_URL = "/fineract-provider/api/v1/groups/" + generatedGroupID + "?" + Utils.TENANT_IDENTIFIER;
+        final Boolean responseGroupStatus = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "active");
+        assertEquals("ERROR IN ACTIVATING THE GROUP", generatedGroupStatus, responseGroupStatus);
+    }
+
+    public static void verifyGroupMembers(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedGroupID, final Integer groupMember) {
+        List<String> list = new ArrayList<>();
+        System.out.println("------------------------------CHECK GROUP MEMBERS------------------------------------\n");
+        final String GROUP_URL = "/fineract-provider/api/v1/groups/" + generatedGroupID
+                + "?associations=clientMembers&" + Utils.TENANT_IDENTIFIER;
+        list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "clientMembers");
+        assertTrue("ERROR IN GROUP MEMBER", list.toString().contains("id=" + groupMember.toString()));
+    }
+
+    public static void verifyEmptyGroupMembers(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedGroupID) {
+        List<String> list = new ArrayList<>();
+        System.out.println("------------------------------CHECK EMPTY GROUP MEMBER LIST------------------------------------\n");
+        final String GROUP_URL = "/fineract-provider/api/v1/groups/" + generatedGroupID
+                + "?associations=clientMembers&" + Utils.TENANT_IDENTIFIER;
+        list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "clientMembers");
+        assertEquals("GROUP MEMBER LIST NOT EMPTY", list, null);
+    }
+
+    public static void verifyGroupDeleted(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedGroupID) {
+        List<String> list = new ArrayList<>();
+        System.out.println("------------------------------CHECK GROUP DELETED------------------------------------\n");
+        final String GROUP_URL = "/fineract-provider/api/v1/groups/?" + Utils.TENANT_IDENTIFIER;
+        list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "pageItems");
+
+        assertFalse("GROUP NOT DELETED", list.toString().contains("id=" + generatedGroupID.toString()));
+    }
+
+    public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return Utils.randomStringGenerator(prefix, lenOfRandomSuffix);
+    }
+
+    private static String randomIDGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return Utils.randomStringGenerator(prefix, lenOfRandomSuffix, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HolidayHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HolidayHelper.java
new file mode 100644
index 0000000..d8b90ad
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HolidayHelper.java
@@ -0,0 +1,92 @@
+/**
+ * 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.integrationtests.common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSender;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes" })
+public class HolidayHelper {
+
+    private static final String HOLIDAYS_URL = "/fineract-provider/api/v1/holidays";
+    private static final String CREATE_HOLIDAY_URL = HOLIDAYS_URL + "?" + Utils.TENANT_IDENTIFIER;
+
+    private static final String OFFICE_ID = "1";
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public HolidayHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public static String getCreateHolidayDataAsJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        List<HashMap<String, String>> offices = new ArrayList<HashMap<String, String>>();
+        HashMap<String, String> officeMap = new HashMap<>();
+        officeMap.put("officeId", OFFICE_ID);
+        offices.add(officeMap);
+
+        map.put("offices", offices);
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("name", Utils.randomNameGenerator("HOLIDAY_", 5));
+        map.put("fromDate", "01 April 2013");
+        map.put("toDate", "01 April 2013");
+        map.put("repaymentsRescheduledTo", "08 April 2013");
+
+        String HolidayCreateJson = new Gson().toJson(map);
+        System.out.println(HolidayCreateJson);
+        return HolidayCreateJson;
+    }
+    
+    public static String getActivateHolidayDataAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        String activateHoliday = new Gson().toJson(map);
+        System.out.println(activateHoliday);
+        return activateHoliday;
+    }
+
+    public static Integer createHolidays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_HOLIDAY_URL, getCreateHolidayDataAsJSON(), "resourceId");
+    }
+    
+    public static Integer activateHolidays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String holidayID) {
+        final String ACTIVATE_HOLIDAY_URL = HOLIDAYS_URL + "/" + holidayID + "?command=activate&" + Utils.TENANT_IDENTIFIER; 
+        return Utils.performServerPost(requestSpec, responseSpec, ACTIVATE_HOLIDAY_URL, getActivateHolidayDataAsJSON(), "resourceId");
+    }
+    
+    public static HashMap getHolidayById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String holidayID) {
+        final String GET_HOLIDAY_BY_ID_URL = HOLIDAYS_URL + "/" + holidayID + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING HOLIDAY BY ID -------------------------");
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_HOLIDAY_BY_ID_URL, "");
+        return response;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HookHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HookHelper.java
new file mode 100644
index 0000000..c2ca23f
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HookHelper.java
@@ -0,0 +1,103 @@
+/**
+ * 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.integrationtests.common;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class HookHelper {
+	
+	private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    private static final String CREATE_HOOK_URL = "/fineract-provider/api/v1/hooks?" + Utils.TENANT_IDENTIFIER;
+    
+    public HookHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+    
+    public Integer createHook(final String payloadURL) {
+        System.out.println("---------------------------------CREATING A HOOK---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_HOOK_URL, getTestHookAsJson(payloadURL),
+                "resourceId");
+    }
+    
+    public String getTestHookAsJson(final String payloadURL) {
+    	final HashMap<String, Object> map = new HashMap<>();
+    	map.put("name", "Web");
+    	map.put("displayName", Utils.randomNameGenerator("Hook_DisplayName_", 5));
+        map.put("isActive", "true");
+        final HashMap<String, String> config = new HashMap<>();
+        config.put("Content Type", "json");
+        config.put("Payload URL", payloadURL);
+        map.put("config", config);
+        final ArrayList<HashMap<String, String>> events = new ArrayList<>();
+        final HashMap<String, String> createOfficeEvent = new HashMap<>();
+        createOfficeEvent.put("actionName", "CREATE");
+        createOfficeEvent.put("entityName", "OFFICE");
+        events.add(createOfficeEvent);
+        map.put("events", events);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+    
+    public Integer updateHook(final String payloadURL, final Long hookId) {
+        System.out.println("---------------------------------UPDATING HOOK---------------------------------------------");
+        final String UPDATE_HOOK_URL = "/fineract-provider/api/v1/hooks/" + hookId + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, UPDATE_HOOK_URL, getTestHookAsJson(payloadURL), "resourceId");
+    }
+
+    public Integer deleteHook(final Long hookId) {
+        System.out.println("---------------------------------DELETING HOOK---------------------------------------------");
+        final String DELETE_HOOK_URL = "/fineract-provider/api/v1/hooks/" + hookId + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerDelete(this.requestSpec, this.responseSpec, DELETE_HOOK_URL, "resourceId");
+    }
+    
+    public void verifyHookCreatedOnServer(final Long hookId) {
+        System.out.println("------------------------------CHECK CREATE HOOK DETAILS------------------------------------\n");
+        final String GET_URL = "/fineract-provider/api/v1/hooks/" + hookId + "?" + Utils.TENANT_IDENTIFIER;
+        final Integer responseHookId = Utils.performServerGet(this.requestSpec, this.responseSpec, GET_URL, "id");
+        assertEquals(hookId.toString(), responseHookId.toString());
+    }
+    
+    public void verifyUpdateHook(final String updateURL, final Long hookId) {
+        System.out.println("------------------------------CHECK UPDATE HOOK DETAILS------------------------------------\n");
+        final String GET_URL = "/fineract-provider/api/v1/hooks/" + hookId + "?" + Utils.TENANT_IDENTIFIER;
+        ArrayList map = Utils.performServerGet(this.requestSpec, this.responseSpec, GET_URL, "config");
+        HashMap<String, String> hash = (HashMap<String, String>) map.get(1);
+        assertEquals(updateURL, hash.get("fieldValue"));
+    }
+    
+    public void verifyDeleteHook(final Long hookId) {
+        System.out.println("------------------------------CHECK DELETE HOOK DETAILS------------------------------------\n");
+        final String GET_URL = "/fineract-provider/api/v1/hooks/" + hookId + "?" + Utils.TENANT_IDENTIFIER;
+        ResponseSpecification responseSpec404 = new ResponseSpecBuilder().expectStatusCode(404).build();
+        ArrayList array = Utils.performServerGet(this.requestSpec, responseSpec404, GET_URL, "errors");
+		HashMap<String, String> map = (HashMap<String, String>)array.get(0);
+        assertEquals("error.msg.hook.identifier.not.found",map.get("userMessageGlobalisationCode"));
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ImageHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ImageHelper.java
new file mode 100644
index 0000000..c076740
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ImageHelper.java
@@ -0,0 +1,73 @@
+/**
+ * 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.integrationtests.common;
+
+import org.apache.http.HttpHeaders;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class ImageHelper {
+
+    private static final String STAFF_IMAGE_URL = "/fineract-provider/api/v1/staff/";
+    private static final String IMAGES_URI = "/images";
+
+    public static Integer createImageForStaff(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            Integer staffId) {
+        System.out.println("---------------------------------CREATING AN IMAGE FOR STAFF---------------------------------------------");
+        String URL = STAFF_IMAGE_URL + staffId + IMAGES_URI + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(requestSpec, responseSpec, URL, generateImageAsText(), "resourceId");
+    }
+
+    public static Integer updateImageForStaff(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            Integer staffId) {
+        System.out.println("---------------------------------UPDATING AN IMAGE FOR STAFF---------------------------------------------");
+        String URL = STAFF_IMAGE_URL + staffId + IMAGES_URI + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPut(requestSpec, responseSpec, URL, generateImageAsText(), "resourceId");
+    }
+
+    public static String getStaffImageAsText(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            Integer staffId) {
+        System.out.println("---------------------------------RETRIEVING STAFF IMAGE---------------------------------------------");
+        String URL = STAFF_IMAGE_URL + staffId + IMAGES_URI + "?" + Utils.TENANT_IDENTIFIER;
+        requestSpec.header(HttpHeaders.ACCEPT, "text/plain");
+        return Utils.performGetTextResponse(requestSpec, responseSpec, URL);
+    }
+
+    public static byte[] getStaffImageAsBinary(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            Integer staffId) {
+        System.out.println("---------------------------------RETRIEVING STAFF IMAGE---------------------------------------------");
+        String URL = STAFF_IMAGE_URL + staffId + IMAGES_URI + "?" + Utils.TENANT_IDENTIFIER;
+        requestSpec.header(HttpHeaders.ACCEPT, "application/octet-stream");
+        return Utils.performGetBinaryResponse(requestSpec, responseSpec, URL);
+    }
+
+    public static Integer deleteStaffImage(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, Integer staffId) {
+        System.out.println("---------------------------------RETRIEVING STAFF IMAGE---------------------------------------------");
+        String URL = STAFF_IMAGE_URL + staffId + IMAGES_URI + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerDelete(requestSpec, responseSpec, URL, "resourceId");
+    }
+
+    private static String generateImageAsText() {
+        return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ\n"
+                + "bWFnZVJlYWR5ccllPAAAAJ1JREFUeNpi+P//PwMIA4E9EG8E4idQDGLbw+WhiiqA+D8OXAFVAzbp\n"
+                + "DxBvB2JLIGaGYkuoGEjOhhFIHAbij0BdPgxYACMj42ogJQpifwBiXSDeC8JIbt4LxSC5DyxQjTeB\n"
+                + "+BeaYb+Q5EBOAVutCzMJHUNNPADzzDokiYdAfAmJvwLkGeTgWQfyKZICS6hYBTwc0QL8ORSjBDhA\n" + "gAEAOg13B6R/SAgAAAAASUVORK5CYII=";
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
new file mode 100644
index 0000000..7e10e9d
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
@@ -0,0 +1,66 @@
+/**
+ * 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.integrationtests.common;
+
+import static org.junit.Assert.assertEquals;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class LoanRescheduleRequestHelper {
+	private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+    
+    private static final String LOAN_RESCHEDULE_REQUEST_URL = "/fineract-provider/api/v1/rescheduleloans";
+    
+    public LoanRescheduleRequestHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+    
+    public Integer createLoanRescheduleRequest(final String requestJSON) {
+    	final String URL = LOAN_RESCHEDULE_REQUEST_URL + "?" + Utils.TENANT_IDENTIFIER; 
+    	return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, requestJSON, "resourceId");
+    }
+    
+    public Integer rejectLoanRescheduleRequest(final Integer requestId, final String requestJSON) {
+    	final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER + "&command=reject";
+    	
+    	return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, requestJSON, "resourceId");
+    }
+    
+    public Integer approveLoanRescheduleRequest(final Integer requestId, final String requestJSON) {
+    	final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER + "&command=approve";
+    	
+    	return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, requestJSON, "resourceId");
+    }
+    
+    public Object getLoanRescheduleRequest(final Integer requestId, final String param) {
+    	final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER;
+    	
+    	return Utils.performServerGet(requestSpec, responseSpec, URL, param);
+    }
+    
+    public void verifyCreationOfLoanRescheduleRequest(final Integer requestId) {
+    	final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER;
+    	
+    	final Integer id = Utils.performServerGet(requestSpec, responseSpec, URL, "id");
+    	assertEquals("ERROR IN CREATING LOAN RESCHEDULE REQUEST", requestId, id);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/OfficeDomain.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/OfficeDomain.java
new file mode 100644
index 0000000..1032b24
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/OfficeDomain.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.integrationtests.common;
+
+import com.google.gson.Gson;
+
+public class OfficeDomain {
+
+	public static class Builder {
+
+		private int id;
+		private String name;
+		private String nameDecorated;
+		private String externalId;
+		private String[] openingDate;
+		private String hierarchy;
+
+		private Builder(final int id, final String name,
+				final String nameDecorated, final String externalId,
+				final String[] openingDate, final String hierarchy) {
+			this.id = id;
+			this.name = name;
+			this.nameDecorated = nameDecorated;
+			this.externalId = externalId;
+			this.openingDate = openingDate;
+			this.hierarchy = hierarchy;
+		}
+
+		public OfficeDomain build() {
+			return new OfficeDomain(this.id, this.name, this.nameDecorated,
+					this.externalId, this.openingDate, this.hierarchy);
+		}
+	}
+
+	private int id;
+	private String name;
+	private String nameDecorated;
+	private String externalId;
+	private String[] openingDate;
+	private String hierarchy;
+
+	OfficeDomain() {
+		super();
+	}
+
+	private OfficeDomain(final int id, final String name,
+			final String nameDecorated, final String externalId,
+			final String[] openingDate, final String hierarchy) {
+		super();
+		this.id = id;
+		this.name = name;
+		this.nameDecorated = nameDecorated;
+		this.externalId = externalId;
+		this.openingDate = openingDate;
+		this.hierarchy = hierarchy;
+	}
+
+	public String toJSON() {
+		return new Gson().toJson(this);
+	}
+
+	public static OfficeDomain fromJSON(final String jsonData) {
+		return new Gson().fromJson(jsonData, OfficeDomain.class);
+	}
+
+	public static Builder create(final int id, final String name,
+			final String nameDecorated, final String externalId,
+			final String[] openingDate, final String hierarchy) {
+		return new Builder(id, name, nameDecorated, externalId, openingDate,
+				hierarchy);
+	}
+
+	public int getId() {
+		return this.id;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	public String getNameDecorated() {
+		return this.nameDecorated;
+	}
+
+	public String getExternalId() {
+		return this.externalId;
+	}
+
+	public String[] getOpeningDate() {
+		return this.openingDate;
+	}
+
+	public String getHierarchy() {
+		return this.hierarchy;
+	}
+
+	@Override
+	public int hashCode() {
+		int hash = 1;
+
+		if (this.id > 0)
+			hash += this.id;
+		if (this.name != null)
+			hash += this.name.hashCode();
+		if (this.nameDecorated != null)
+			hash += this.nameDecorated.hashCode();
+		if (this.externalId != null)
+			hash += this.externalId.hashCode();
+		if (this.openingDate != null)
+			hash += this.openingDate.hashCode();
+		if (this.hierarchy != null)
+			hash += this.hierarchy.hashCode();
+
+		return hash;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj == this) {
+			return true;
+		}
+
+		if (!(obj instanceof OfficeDomain))
+			return false;
+
+		OfficeDomain od = (OfficeDomain) obj;
+
+		if (this.id == od.getId() && this.name.equals(od.getName())
+				&& this.nameDecorated.equals(od.getName())
+				&& this.externalId.equals(od.getExternalId())
+				&& this.openingDate.equals(od.getOpeningDate())
+				&& this.hierarchy.equals(od.getHierarchy()))
+			return true;
+
+		return false;
+	}
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/OfficeHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/OfficeHelper.java
new file mode 100755
index 0000000..d73c4c6
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/OfficeHelper.java
@@ -0,0 +1,79 @@
+/**
+ * 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.integrationtests.common;
+
+import java.util.HashMap;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class OfficeHelper {
+
+	private static final String OFFICE_URL = "/fineract-provider/api/v1/offices";
+	private final RequestSpecification requestSpec;
+	private final ResponseSpecification responseSpec;
+
+	public OfficeHelper(final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec) {
+		this.requestSpec = requestSpec;
+		this.responseSpec = responseSpec;
+	}
+
+	public OfficeDomain retrieveOfficeByID(int id) {
+		final String json = new Gson().toJson(Utils.performServerGet(
+				requestSpec, responseSpec, OFFICE_URL + "/" + id + "?"
+						+ Utils.TENANT_IDENTIFIER, ""));
+		return new Gson().fromJson(json, new TypeToken<OfficeDomain>() {
+		}.getType());
+	}
+
+	public Integer createOffice(final String openingDate) {
+		String json = getAsJSON(openingDate);
+		return Utils.performServerPost(this.requestSpec, this.responseSpec,
+				OFFICE_URL + "?" + Utils.TENANT_IDENTIFIER, json,
+				CommonConstants.RESPONSE_RESOURCE_ID);
+	}
+
+	public Integer updateOffice(int id, String name, String openingDate) {
+		final HashMap map = new HashMap<>();
+		map.put("name", name);
+		map.put("dateFormat", "dd MMMM yyyy");
+		map.put("locale", "en");
+		map.put("openingDate", openingDate);
+
+		System.out.println("map : " + map);
+
+		return Utils.performServerPut(requestSpec, responseSpec, OFFICE_URL
+				+ "/" + id + "?" + Utils.TENANT_IDENTIFIER,
+				new Gson().toJson(map), "resourceId");
+	}
+
+	public static String getAsJSON(final String openingDate) {
+		final HashMap<String, String> map = new HashMap<>();
+		map.put("parentId", "1");
+		map.put("name", Utils.randomNameGenerator("Office_", 4));
+		map.put("dateFormat", "dd MMMM yyyy");
+		map.put("locale", "en");
+		map.put("openingDate", openingDate);
+		System.out.println("map : " + map);
+		return new Gson().toJson(map);
+	}
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/PasswordPreferencesHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/PasswordPreferencesHelper.java
new file mode 100644
index 0000000..52b8c5a
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/PasswordPreferencesHelper.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.integrationtests.common;
+
+import java.util.HashMap;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class PasswordPreferencesHelper {
+
+    private static final String PASSWORD_PREFERENCES_URL = "/fineract-provider/api/v1/passwordpreferences";
+
+    public static Object updatePasswordPreferences(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, 
+            String validationPolicyId) {
+        final String UPDATE_PASSWORD_PREFERENCES_URL = PASSWORD_PREFERENCES_URL + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------UPDATE PASSWORD PREFERENCE---------------------------------------------");
+        return Utils.performServerPut(requestSpec, responseSpec, UPDATE_PASSWORD_PREFERENCES_URL, updatePreferencesAsJson(validationPolicyId), "");
+    }
+
+    public static Object updateWithInvalidValidationPolicyId(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec,String invalidValidationPolicyId, String jsonAttributeToGetback) {
+        final String UPDATE_PASSWORD_PREFERENCES_URL = PASSWORD_PREFERENCES_URL + "?" + Utils.TENANT_IDENTIFIER;
+        System.out
+                .println("---------------------------------UPDATE PASSWORD PREFERENCES WITH INVALID ID-----------------------------------------");
+        return Utils.performServerPut(requestSpec, responseSpec, UPDATE_PASSWORD_PREFERENCES_URL, updatePreferencesWithInvalidId(invalidValidationPolicyId),
+                jsonAttributeToGetback);
+    }
+
+    public static String updatePreferencesAsJson(String validationPolicyId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("validationPolicyId", validationPolicyId);
+        return new Gson().toJson(map);
+    }
+
+    public static String updatePreferencesWithInvalidId(String invalidValidationPolicyId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("validationPolicyId", invalidValidationPolicyId);
+        return new Gson().toJson(map);
+    }
+
+
+    public static int getActivePasswordPreference(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return Utils.performServerGet(requestSpec, responseSpec, PASSWORD_PREFERENCES_URL + "?" + Utils.TENANT_IDENTIFIER, "id");
+    }
+
+    public static HashMap<String, Object> getAllPreferences(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+
+        return Utils.performServerGet(requestSpec, responseSpec, PASSWORD_PREFERENCES_URL + "/template" + "?" + Utils.TENANT_IDENTIFIER, "");
+
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/PaymentTypeDomain.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/PaymentTypeDomain.java
new file mode 100644
index 0000000..7437ec8
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/PaymentTypeDomain.java
@@ -0,0 +1,79 @@
+/**
+ * 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.integrationtests.common;
+
+public class PaymentTypeDomain {
+
+    private Integer id;
+    private String name;
+    private String description;
+    private Boolean isCashPayment;
+    private Integer position;
+
+    private PaymentTypeDomain(final Integer id, final String name, final String description, final Boolean isCashPayment,
+            final Integer position) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.isCashPayment = isCashPayment;
+        this.position = position;
+
+    }
+
+    public Integer getId() {
+        return this.id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Boolean getIsCashPayment() {
+        return this.isCashPayment;
+    }
+
+    public void setIsCashPayment(Boolean isCashPayment) {
+        this.isCashPayment = isCashPayment;
+    }
+
+    public Integer getPosition() {
+        return this.position;
+    }
+
+    public void setPosition(Integer position) {
+        this.position = position;
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java
new file mode 100644
index 0000000..50dcad6
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java
@@ -0,0 +1,91 @@
+/**
+ * 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.integrationtests.common;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class PaymentTypeHelper {
+
+    private static final String CREATE_PAYMENTTYPE_URL = "/fineract-provider/api/v1/paymenttypes?" + Utils.TENANT_IDENTIFIER;
+    private static final String PAYMENTTYPE_URL = "/fineract-provider/api/v1/paymenttypes";
+
+    public static Integer createPaymentType(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String name, final String description, final Boolean isCashPayment, final Integer position) {
+        System.out.println("---------------------------------CREATING A PAYMENT TYPE---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_PAYMENTTYPE_URL,
+                getJsonToCreatePaymentType(name, description, isCashPayment, position), "resourceId");
+    }
+
+    public static String getJsonToCreatePaymentType(final String name, final String description, final Boolean isCashPayment,
+            final Integer position) {
+        HashMap hm = new HashMap();
+        hm.put("name", name);
+        if (description != null) hm.put("description", description);
+        hm.put("isCashPayment", isCashPayment);
+        if (position != null) hm.put("position", position);
+
+        System.out.println("------------------------CREATING PAYMENT TYPE-------------------------" + hm);
+        return new Gson().toJson(hm);
+    }
+
+    public static void verifyPaymentTypeCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedPaymentTypeID) {
+        System.out.println("------------------------------CHECK PAYMENT DETAILS------------------------------------\n");
+        final String GET_PAYMENTTYPE_URL = PAYMENTTYPE_URL + "/" + generatedPaymentTypeID + "?" + Utils.TENANT_IDENTIFIER;
+        final Integer responsePaymentTypeID = Utils.performServerGet(requestSpec, responseSpec, GET_PAYMENTTYPE_URL, "id");
+        assertEquals("ERROR IN CREATING THE PAYMENT TYPE", generatedPaymentTypeID, responsePaymentTypeID);
+    }
+
+    public static PaymentTypeDomain retrieveById(RequestSpecification requestSpec, ResponseSpecification responseSpec,
+            final Integer paymentTypeId) {
+        final String GET_PAYMENTTYPE_URL = PAYMENTTYPE_URL + "/" + paymentTypeId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------GET PAYMENT TYPE---------------------------------------------");
+        final String jsonData = new Gson().toJson(Utils.performServerGet(requestSpec, responseSpec, GET_PAYMENTTYPE_URL, ""));
+        return new Gson().fromJson(jsonData, new TypeToken<PaymentTypeDomain>() {}.getType());
+
+    }
+
+    public static HashMap<String, String> updatePaymentType(final int id, HashMap request, final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        final String UPDATE_PAYMENTTYPE_URL = PAYMENTTYPE_URL + "/" + id + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------UPDATE PAYMENT TYPE " + id + "---------------------------------------------");
+        HashMap<String, String> hash = Utils.performServerPut(requestSpec, responseSpec, UPDATE_PAYMENTTYPE_URL,
+                new Gson().toJson(request), "changes");
+        return hash;
+    }
+
+    public static Integer deletePaymentType(final int id, final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        final String DELETE_PAYMENTTYPE_URL = PAYMENTTYPE_URL + "/" + id + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("---------------------------------DELETING PAYMENT TYPE " + id + "--------------------------------------------");
+        return Utils.performServerDelete(requestSpec, responseSpec, DELETE_PAYMENTTYPE_URL, "resourceId");
+    }
+
+    public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return Utils.randomStringGenerator(prefix, lenOfRandomSuffix);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ProvisioningIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ProvisioningIntegrationTest.java
new file mode 100644
index 0000000..107bece
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/ProvisioningIntegrationTest.java
@@ -0,0 +1,241 @@
+/**
+ * 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.integrationtests.common;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.provisioning.ProvisioningHelper;
+import org.apache.fineract.integrationtests.common.provisioning.ProvisioningTransactionHelper;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class ProvisioningIntegrationTest {
+
+    private static final String NONE = "1";
+    private final static int LOANPRODUCTS_SIZE = 10;
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+    private AccountHelper accountHelper;
+    private LoanTransactionHelper loanTransactionHelper;
+
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        Assume.assumeTrue(!isAlreadyProvisioningEntriesCreated());
+    }
+
+    @Test
+    public void testCreateProvisioningCriteria() {
+        ProvisioningTransactionHelper transactionHelper = new ProvisioningTransactionHelper(requestSpec, responseSpec);
+        ArrayList<Integer> loanProducts = new ArrayList<>(LOANPRODUCTS_SIZE);
+        List<Integer> loans = new ArrayList<>();
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        for (int i = 0; i < LOANPRODUCTS_SIZE; i++) {
+            final Integer loanProductID = createLoanProduct(false, NONE);
+            loanProducts.add(loanProductID);
+            Assert.assertNotNull(loanProductID);
+            final Integer loanID = applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+            HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+            LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+            loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
+            LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+            LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+            System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+            loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID);
+            LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+            loans.add(loanID);
+            Assert.assertNotNull(loanID);
+        }
+
+        ArrayList categories = transactionHelper.retrieveAllProvisioningCategories();
+        Assert.assertTrue(categories.size() > 0) ;
+        Account liability = accountHelper.createLiabilityAccount() ;
+        Account expense = accountHelper.createExpenseAccount() ;
+        Map requestCriteria = ProvisioningHelper.createProvisioingCriteriaJson(loanProducts, categories, liability, expense);
+        String provisioningCriteriaCreateJson = new Gson().toJson(requestCriteria);
+        Integer criteriaId = transactionHelper.createProvisioningCriteria(provisioningCriteriaCreateJson);
+        Assert.assertNotNull(criteriaId);
+
+        Map newCriteria = transactionHelper.retrieveProvisioningCriteria(criteriaId) ;
+        validateProvisioningCriteria(requestCriteria, newCriteria) ;
+        
+        ArrayList definitions = (ArrayList)newCriteria.get("definitions") ;
+        for(int i = 0 ; i < definitions.size(); i++) {
+            Map criteriadefinition = (Map) definitions.get(i) ;
+            criteriadefinition.put("provisioningPercentage", new Float(20.0)) ;
+        }
+        newCriteria.put("locale", "en");
+        String updateCriteriaString = new Gson().toJson(newCriteria) ;
+        Integer criteriaId1 = transactionHelper.updateProvisioningCriteria(criteriaId, updateCriteriaString) ;
+        Map updatedCriteria = transactionHelper.retrieveProvisioningCriteria(criteriaId1) ;
+        validateProvisioningCriteria(newCriteria, updatedCriteria) ;
+        
+        transactionHelper.deleteProvisioningCriteria(criteriaId1) ;
+ 
+        categories = transactionHelper.retrieveAllProvisioningCategories();
+        liability = accountHelper.createLiabilityAccount() ;
+        expense = accountHelper.createExpenseAccount() ;
+        requestCriteria = ProvisioningHelper.createProvisioingCriteriaJson(loanProducts, categories, liability, expense);
+        provisioningCriteriaCreateJson = new Gson().toJson(requestCriteria);
+        criteriaId = transactionHelper.createProvisioningCriteria(provisioningCriteriaCreateJson);
+        Assert.assertNotNull(criteriaId);
+
+        String provisioningEntryJson = ProvisioningHelper.createProvisioningEntryJson();
+        Integer provisioningEntryId = transactionHelper.createProvisioningEntries(provisioningEntryJson);
+        Assert.assertNotNull(provisioningEntryId);
+        
+        transactionHelper.updateProvisioningEntry("recreateprovisioningentry", provisioningEntryId, "") ;
+        transactionHelper.updateProvisioningEntry("createjournalentry", provisioningEntryId, "") ;
+        Map entry = transactionHelper.retrieveProvisioningEntry(provisioningEntryId) ;
+        Assert.assertTrue((Boolean)entry.get("journalEntry")) ;
+        Map provisioningEntry = transactionHelper.retrieveProvisioningEntries(provisioningEntryId) ;
+        Assert.assertTrue(((ArrayList)provisioningEntry.get("pageItems")).size() > 0) ;
+    }
+
+    private void validateProvisioningCriteria(Map requestCriteria, Map newCriteria) {
+        
+        //criteria name validation
+        String requestCriteriaName = (String)requestCriteria.get("criteriaName") ;
+        String criteriaName = (String)newCriteria.get("criteriaName") ;
+        Assert.assertEquals(criteriaName, requestCriteriaName) ;
+        
+        //loan products validation
+        ArrayList requestProducts = (ArrayList)requestCriteria.get("loanProducts") ;
+        ArrayList products = (ArrayList)newCriteria.get("loanProducts") ;
+        Assert.assertEquals(products.size(), requestProducts.size()) ;
+        
+        ArrayList requestedDefinitions = (ArrayList)requestCriteria.get("definitions") ;
+        ArrayList newdefintions = (ArrayList) newCriteria.get("definitions") ;
+        Assert.assertEquals(newdefintions.size(), requestedDefinitions.size()) ;
+        for(int i = 0 ; i < newdefintions.size() ; i++) {
+            Map requestedMap = (Map)requestedDefinitions.get(i) ;
+            Map newMap = (Map)newdefintions.get(i) ;
+            checkProperty("categoryId", requestedMap, newMap) ;
+            checkProperty("categoryName", requestedMap, newMap) ;
+            checkProperty("minAge", requestedMap, newMap) ;
+            checkProperty("maxAge", requestedMap, newMap) ;
+            checkProperty("provisioningPercentage", requestedMap, newMap) ;
+            checkProperty("liabilityAccount", requestedMap, newMap) ;
+            checkProperty("expenseAccount", requestedMap, newMap) ;
+        }
+    }
+    
+    private void checkProperty(String propertyName, Map requestMap, Map newMap) {
+        Object requested = requestMap.get(propertyName) ;
+        Object modified = newMap.get(propertyName) ;
+        Assert.assertEquals(requested, modified) ;
+    }
+    
+    private Integer createLoanProduct(final boolean multiDisburseLoan, final String accountingRule, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        LoanProductTestBuilder builder = new LoanProductTestBuilder() //
+                .withPrincipal("1,00,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withTranches(multiDisburseLoan) //
+                .withAccounting(accountingRule, accounts);
+        if (multiDisburseLoan) {
+            builder = builder.withInterestCalculationPeriodTypeAsRepaymentPeriod(true);
+        }
+        final String loanProductJSON = builder.build(null);
+        
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
+            final String savingsId, String principal) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2011") //
+                .withSubmittedOnDate("20 September 2011") //
+                .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+    
+    private boolean isAlreadyProvisioningEntriesCreated() {
+        ProvisioningTransactionHelper transactionHelper = new ProvisioningTransactionHelper(requestSpec, responseSpec);
+        Map entries = transactionHelper.retrieveAllProvisioningEntries() ;
+        ArrayList<Map> pageItems = (ArrayList)entries.get("pageItems") ;
+        boolean provisioningetryAlreadyCreated = false ;
+        if(pageItems != null) {
+            for(Map item: pageItems) {
+                String date = (String)item.get("createdDate") ;
+                DateFormat formatter = new SimpleDateFormat("MMM dd, yyyy");
+                try {
+                    Date date1 = formatter.parse(date) ;
+                    DateFormat simple = new SimpleDateFormat("dd MMMM yyyy");
+                    String formattedString = simple.format(Utils.getLocalDateOfTenant().toDate());
+                    Date currentDate = simple.parse(formattedString) ;
+                    if(date1.getTime() == currentDate.getTime()) {
+                        provisioningetryAlreadyCreated = true ;
+                        break ;
+                    }
+                } catch (ParseException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return provisioningetryAlreadyCreated ;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java
new file mode 100644
index 0000000..882e14d
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java
@@ -0,0 +1,148 @@
+/**
+ * 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.integrationtests.common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.junit.Assert;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class SchedulerJobHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public SchedulerJobHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public static ArrayList getAllSchedulerJobs(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        final String GET_ALL_SCHEDULER_JOBS_URL = "/fineract-provider/api/v1/jobs?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING ALL SCHEDULER JOBS -------------------------");
+        final ArrayList response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_SCHEDULER_JOBS_URL, "");
+        return response;
+    }
+
+    public static HashMap getSchedulerJobById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String jobId) {
+        final String GET_SCHEDULER_JOB_BY_ID_URL = "/fineract-provider/api/v1/jobs/" + jobId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING SCHEDULER JOB BY ID -------------------------");
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_SCHEDULER_JOB_BY_ID_URL, "");
+        return response;
+    }
+
+    public static HashMap getSchedulerStatus(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        final String GET_SCHEDULER_STATUS_URL = "/fineract-provider/api/v1/scheduler?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING SCHEDULER STATUS -------------------------");
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_SCHEDULER_STATUS_URL, "");
+        return response;
+    }
+
+    public static void updateSchedulerStatus(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String command) {
+        final String UPDATE_SCHEDULER_STATUS_URL = "/fineract-provider/api/v1/scheduler?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ UPDATING SCHEDULER STATUS -------------------------");
+        Utils.performServerPost(requestSpec, responseSpec, UPDATE_SCHEDULER_STATUS_URL, runSchedulerJobAsJSON(), null);
+    }
+
+    public static HashMap updateSchedulerJob(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String jobId, final String active) {
+        final String UPDATE_SCHEDULER_JOB_URL = "/fineract-provider/api/v1/jobs/" + jobId + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ UPDATING SCHEDULER JOB -------------------------");
+        final HashMap response = Utils.performServerPut(requestSpec, responseSpec, UPDATE_SCHEDULER_JOB_URL,
+                updateSchedulerJobAsJSON(active), "changes");
+        return response;
+    }
+
+    public static String updateSchedulerJobAsJSON(final String active) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("active", active);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static ArrayList getSchedulerJobHistory(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String jobId) {
+        final String GET_SCHEDULER_STATUS_URL = "/fineract-provider/api/v1/jobs/" + jobId + "/runhistory?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING SCHEDULER JOB HISTORY -------------------------");
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_SCHEDULER_STATUS_URL, "");
+        return (ArrayList) response.get("pageItems");
+    }
+
+    public static void runSchedulerJob(final RequestSpecification requestSpec, final String jobId) {
+        final ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(202).build();
+        final String RUN_SCHEDULER_JOB_URL = "/fineract-provider/api/v1/jobs/" + jobId + "?command=executeJob&" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RUN SCHEDULER JOB -------------------------");
+        Utils.performServerPost(requestSpec, responseSpec, RUN_SCHEDULER_JOB_URL, runSchedulerJobAsJSON(), null);
+    }
+
+    public static String runSchedulerJobAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        String runSchedulerJob = new Gson().toJson(map);
+        System.out.println(runSchedulerJob);
+        return runSchedulerJob;
+    }
+
+    public void executeJob(String JobName) throws InterruptedException {
+        ArrayList<HashMap> allSchedulerJobsData = getAllSchedulerJobs(this.requestSpec, this.responseSpec);
+        Assert.assertNotNull(allSchedulerJobsData);
+
+        for (Integer jobIndex = 0; jobIndex < allSchedulerJobsData.size(); jobIndex++) {
+            if (allSchedulerJobsData.get(jobIndex).get("displayName").equals(JobName)) {
+                Integer jobId = (Integer) allSchedulerJobsData.get(jobIndex).get("jobId");
+
+                // Executing Scheduler Job
+                runSchedulerJob(this.requestSpec, jobId.toString());
+
+                // Retrieving Scheduler Job by ID
+                HashMap schedulerJob = getSchedulerJobById(this.requestSpec, this.responseSpec, jobId.toString());
+                Assert.assertNotNull(schedulerJob);
+
+                // Waiting for Job to complete
+                while ((Boolean) schedulerJob.get("currentlyRunning") == true) {
+                    Thread.sleep(15000);
+                    schedulerJob = getSchedulerJobById(this.requestSpec, this.responseSpec, jobId.toString());
+                    Assert.assertNotNull(schedulerJob);
+                    System.out.println("Job is Still Running");
+                }
+
+                ArrayList<HashMap> jobHistoryData = getSchedulerJobHistory(this.requestSpec, this.responseSpec, jobId.toString());
+
+                // print error associated with recent job failure (if any)
+                System.out.println("Job run error message (printed only if the job fails: "
+                        + jobHistoryData.get(jobHistoryData.size() - 1).get("jobRunErrorMessage"));
+                System.out.println("Job failure error log (printed only if the job fails: "
+                        + jobHistoryData.get(jobHistoryData.size() - 1).get("jobRunErrorLog"));
+
+                // Verifying the Status of the Recently executed Scheduler Job
+                Assert.assertEquals("Verifying Last Scheduler Job Status", "success",
+                        jobHistoryData.get(jobHistoryData.size() - 1).get("status"));
+
+                break;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/StandingInstructionsHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/StandingInstructionsHelper.java
new file mode 100644
index 0000000..bf44576
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/StandingInstructionsHelper.java
@@ -0,0 +1,128 @@
+/**
+ * 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.integrationtests.common;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import ch.qos.logback.classic.pattern.Util;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes", "unchecked" })
+public class StandingInstructionsHelper {
+
+    private static final String STANDING_INSTRUCTIONS_URL = "/fineract-provider/api/v1/standinginstructions";
+    private static final String STANDING_INSTRUCTIONS_RUNHISTORY_URL = "/fineract-provider/api/v1/standinginstructionrunhistory";
+    private static final String LOCALE = "en_GB";
+    private static final String OFFICE_ID = "1";
+    private static final String INSTRUCTION_TYPE_FIXED = "1";
+    private static final String INSTRUCTION_TYPE_DUES = "2";
+    private static final String PRIORITY_URGENT = "1";
+    private static final String PRIORITY_HIGH = "2";
+    private static final String PRIORITY_MEDIUM = "3";
+    private static final String PRIORITY_LOW = "4";
+    private static final String RECURRENCE_FREQUENCY_DAYS = "0";
+    private static final String RECURRENCE_FREQUENCY_WEEKS = "1";
+    private static final String RECURRENCE_FREQUENCY_MONTHS = "2";
+    private static final String RECURRENCE_FREQUENCY_YEARS = "3";
+    private static final String RECURRENCE_TYPE_PERIODIC = "1";
+    private static final String RECURRENCE_TYPE_AS_PER_DUES = "2";
+    private static final String STATUS_ACTIVE = "1";
+    private static final String STATUS_DISABLED = "2";
+    private static final String TRANSFER_TYPE_ACCOUNT_TRANSFER = "1";
+    private static final String TRANSFER_TYPE_LOAN_REPAYMENT = "2";
+    private static final String ACCOUNT_TRANSFER_DATE = "01 March 2013";
+
+    private String transferDate = "";
+    private String officeId = OFFICE_ID;
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+
+    public StandingInstructionsHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public String build(final String clientId, final String fromAccountId, final String toAccountId, final String fromAccountType,
+            final String toAccountType, final String validFrom, final String validTo, final String monthDay) {
+
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("name", Utils.randomNameGenerator("STANDING_INSTRUCTION_", 5));
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("monthDayFormat", "dd MMMM");
+        map.put("locale", LOCALE);
+        map.put("fromClientId", clientId);
+        map.put("fromAccountId", fromAccountId);
+        map.put("fromAccountType", fromAccountType);
+        map.put("fromOfficeId", this.officeId);
+        map.put("toClientId", clientId);
+        map.put("toAccountId", toAccountId);
+        map.put("toAccountType", toAccountType);
+        map.put("toOfficeId", this.officeId);
+        map.put("amount", "500");
+        map.put("transferType", TRANSFER_TYPE_ACCOUNT_TRANSFER);
+        map.put("priority", PRIORITY_URGENT);
+        map.put("status", STATUS_ACTIVE);
+        map.put("instructionType", INSTRUCTION_TYPE_FIXED);
+        map.put("validFrom", validFrom);
+        map.put("validTill", validTo);
+        map.put("recurrenceType", RECURRENCE_TYPE_PERIODIC);
+        map.put("recurrenceInterval", "1");
+        map.put("recurrenceFrequency", RECURRENCE_FREQUENCY_WEEKS);
+        map.put("recurrenceOnMonthDay", monthDay);
+        String savingsApplicationJSON = new Gson().toJson(map);
+        System.out.println(savingsApplicationJSON);
+        return savingsApplicationJSON;
+    }
+
+    public Integer createStandingInstruction(final String clientId, final String fromAccountId, final String toAccountId,
+            final String fromAccountType, final String toAccountType, final String validFrom, final String validTo, final String monthDay) {
+        System.out.println("-------------------------------- CREATE STANDING INSTRUCTIONS --------------------------------");
+        final String standingInstructionAsJSON = new StandingInstructionsHelper(this.requestSpec, this.responseSpec) //
+                .build(clientId.toString(), fromAccountId.toString(), toAccountId.toString(), fromAccountType, toAccountType, validFrom,
+                        validTo, monthDay);
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, STANDING_INSTRUCTIONS_URL + "?" + Utils.TENANT_IDENTIFIER,
+                standingInstructionAsJSON, "resourceId");
+    }
+
+    public HashMap getStandingInstructionById(final String standingInstructionId) {
+
+        System.out.println("----------------------------- RETRIEVING STANDING INSTRUCTION BY ID---------------------------");
+        final String GET_STANDING_INSTRUCTION_BY_ID_URL = STANDING_INSTRUCTIONS_URL + "/" + standingInstructionId + "?"
+                + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(this.requestSpec, this.responseSpec, GET_STANDING_INSTRUCTION_BY_ID_URL, "");
+        return response;
+    }
+
+    public List<HashMap> getStandingInstructionHistory(Integer fromSavingsId, Integer fromAccountType, Integer fromClientId, Integer transferType) {
+        final String STANDING_INSTRUCTIONS_HISTORY_URL = STANDING_INSTRUCTIONS_RUNHISTORY_URL + "?" + Utils.TENANT_IDENTIFIER
+                + "&fromSavingsId=" + fromSavingsId + "&fromAccountType=" + fromAccountType + "&clientId=" + fromClientId
+                + "&transferType=" + transferType;
+        System.out.println("STANDING_INSTRUCTIONS_HISTORY_URL="+STANDING_INSTRUCTIONS_HISTORY_URL);
+        final List<HashMap> response = (List<HashMap>) Utils.performServerGet(this.requestSpec, this.responseSpec,
+                STANDING_INSTRUCTIONS_HISTORY_URL, "pageItems");
+        return response;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SurveyHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SurveyHelper.java
new file mode 100644
index 0000000..2fb2b09
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SurveyHelper.java
@@ -0,0 +1,74 @@
+/**
+ * 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.integrationtests.common;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+import java.util.HashMap;
+
+import static org.junit.Assert.assertEquals;
+
+public class SurveyHelper {
+
+    private static final String FULFIL_SURVEY_URL = "/fineract-provider/api/v1/survey/ppi_kenya_2009/clientId?" + Utils.TENANT_IDENTIFIER;
+
+    public static Integer fulfilSurvey(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return fulfilSurvey(requestSpec, responseSpec, "04 March 2011");
+    }
+
+    public static Integer fulfilSurvey(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String activationDate) {
+        System.out.println("---------------------------------FULFIL PPI ---------------------------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec,FULFIL_SURVEY_URL, getTestPPIAsJSON(), "clientId");
+    }
+
+    public static String getTestPPIAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+
+        map.put("date", "2014-05-19 00:00:00");
+        map.put("ppi_household_members_cd_q1_householdmembers","107");
+        map.put("ppi_highestschool_cd_q2_highestschool","112");
+        map.put("ppi_businessoccupation_cd_q3_businessoccupation","116");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en");
+        map.put("ppi_habitablerooms_cd_q4_habitablerooms", "120");
+
+        map.put("ppi_floortype_cd_q5_floortype", "124");
+        map.put("ppi_lightingsource_cd_q6_lightingsource", "126");
+        map.put("ppi_irons_cd_q7_irons", "128");
+        map.put("ppi_mosquitonets_cd_q8_mosquitonets", "132");
+        map.put("ppi_towels_cd_q9_towels", "134");
+        map.put("ppi_fryingpans_cd_q10_fryingpans", "138");
+
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static void verifySurveyCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer generatedClientID) {
+        System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n");
+        final String SURVEY_URL = "/fineract-provider/api/v1/Survey/ppi_kenya_2009/clientid/entryId" + generatedClientID + "?" + Utils.TENANT_IDENTIFIER;
+        final Integer responseClientID = Utils.performServerGet(requestSpec, responseSpec, SURVEY_URL, "id");
+        assertEquals("ERROR IN CREATING THE CLIENT", generatedClientID, responseClientID);
+    }
+
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/Utils.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/Utils.java
new file mode 100644
index 0000000..3d173c3
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/Utils.java
@@ -0,0 +1,168 @@
+/**
+ * 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.integrationtests.common;
+
+import static com.jayway.restassured.RestAssured.given;
+import static com.jayway.restassured.path.json.JsonPath.from;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Random;
+import java.util.TimeZone;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.conn.HttpHostConnectException;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+
+import com.jayway.restassured.RestAssured;
+import com.jayway.restassured.path.json.JsonPath;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Util for RestAssured tests. This class here in src/integrationTest is
+ * copy/pasted to src/test; please keep them in sync.
+ */
+@SuppressWarnings("unchecked")
+public class Utils {
+
+    public static final String TENANT_IDENTIFIER = "tenantIdentifier=default";
+
+    public static final String TENANT_TIME_ZONE = "Asia/Kolkata";
+
+    private static final String LOGIN_URL = "/fineract-provider/api/v1/authentication?username=mifos&password=password&" + TENANT_IDENTIFIER;
+
+    public static void initializeRESTAssured() {
+        RestAssured.baseURI = "https://localhost";
+        RestAssured.port = 8443;
+        RestAssured.keystore("src/main/resources/keystore.jks", "openmf");
+    }
+
+    public static String loginIntoServerAndGetBase64EncodedAuthenticationKey() {
+        try {
+            System.out.println("-----------------------------------LOGIN-----------------------------------------");
+            final String json = RestAssured.post(LOGIN_URL).asString();
+            assertThat("Failed to login into fineract platform", StringUtils.isBlank(json), is(false));
+            return JsonPath.with(json).get("base64EncodedAuthenticationKey");
+        } catch (final Exception e) {
+            if (e instanceof HttpHostConnectException) {
+                final HttpHostConnectException hh = (HttpHostConnectException) e;
+                fail("Failed to connect to fineract platform:" + hh.getMessage());
+            }
+
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String getURL, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
+        if (jsonAttributeToGetBack == null) { return (T) json; }
+        return (T) from(json).get(jsonAttributeToGetBack);
+    }
+
+    public static String performGetTextResponse(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+                                                final String getURL){
+        return given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
+    }
+
+    public static byte[] performGetBinaryResponse(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+                                                final String getURL){
+        return given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asByteArray();
+    }
+
+    public static <T> T performServerPost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String postURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).log().ifError().when().post(postURL)
+                .andReturn().asString();
+        if (jsonAttributeToGetBack == null) { return (T) json; }
+        return (T) from(json).get(jsonAttributeToGetBack);
+    }
+
+    public static <T> T performServerPut(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String putURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).log().ifError().when().put(putURL)
+                .andReturn().asString();
+        return (T) from(json).get(jsonAttributeToGetBack);
+    }
+
+    public static <T> T performServerDelete(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String deleteURL, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().delete(deleteURL).andReturn()
+                .asString();
+        return (T) from(json).get(jsonAttributeToGetBack);
+    }
+
+    public static String convertDateToURLFormat(final String dateToBeConvert) {
+        final SimpleDateFormat oldFormat = new SimpleDateFormat("dd MMMMMM yyyy", Locale.US);
+        final SimpleDateFormat newFormat = new SimpleDateFormat("yyyy-MM-dd");
+        String reformattedStr = "";
+        try {
+            reformattedStr = newFormat.format(oldFormat.parse(dateToBeConvert));
+        } catch (final ParseException e) {
+            e.printStackTrace();
+        }
+        return reformattedStr;
+    }
+
+    public static String randomStringGenerator(final String prefix, final int len, final String sourceSetString) {
+        final int lengthOfSource = sourceSetString.length();
+        final Random rnd = new Random();
+        final StringBuilder sb = new StringBuilder(len);
+        for (int i = 0; i < len; i++) {
+            sb.append((sourceSetString).charAt(rnd.nextInt(lengthOfSource)));
+        }
+        return (prefix + (sb.toString()));
+    }
+
+    public static String randomStringGenerator(final String prefix, final int len) {
+        return randomStringGenerator(prefix, len, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    }
+
+    public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return randomStringGenerator(prefix, lenOfRandomSuffix);
+    }
+
+    public static String convertDateToURLFormat(final Calendar dateToBeConvert) {
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMMMM yyyy");
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+        return dateFormat.format(dateToBeConvert.getTime());
+    }
+
+    public static LocalDate getLocalDateOfTenant() {
+        LocalDate today = new LocalDate();
+        final DateTimeZone zone = DateTimeZone.forID(TENANT_TIME_ZONE);
+        if (zone != null) {
+            today = new LocalDate(zone);
+        }
+        return today;
+    }
+
+    public static TimeZone getTimeZoneOfTenant() {
+        return TimeZone.getTimeZone(TENANT_TIME_ZONE);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/WorkingDaysHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/WorkingDaysHelper.java
new file mode 100755
index 0000000..0dbca5d
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/WorkingDaysHelper.java
@@ -0,0 +1,83 @@
+/**

+ * 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.integrationtests.common;

+

+import java.util.HashMap;

+import java.util.Random;

+

+import com.google.gson.Gson;

+import com.jayway.restassured.specification.RequestSpecification;

+import com.jayway.restassured.specification.ResponseSpecification;

+

+public class WorkingDaysHelper {

+

+    private static final String WORKINGDAYS_URL = "/fineract-provider/api/v1/workingdays";

+

+    public static Object updateWorkingDays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {

+        final String UPDATE_WORKINGDAYS_URL = WORKINGDAYS_URL + "?" + Utils.TENANT_IDENTIFIER;

+        System.out.println("---------------------------------UPDATE WORKINGDAY---------------------------------------------");

+        return Utils.performServerPut(requestSpec, responseSpec, UPDATE_WORKINGDAYS_URL, updateWorkingDaysAsJson(), "");

+    }

+

+    public static Object updateWorkingDaysWithWrongRecurrence(final RequestSpecification requestSpec,

+            final ResponseSpecification responseSpec, String jsonAttributeToGetback) {

+        final String UPDATE_WORKINGDAYS_URL = WORKINGDAYS_URL + "?" + Utils.TENANT_IDENTIFIER;

+        System.out

+                .println("---------------------------------UPDATE WORKINGDAY WITH WRONG RECURRENCE-----------------------------------------");

+        return Utils.performServerPut(requestSpec, responseSpec, UPDATE_WORKINGDAYS_URL, updateWorkingDayWithWrongRecur(),

+                jsonAttributeToGetback);

+    }

+

+    public static String updateWorkingDaysAsJson() {

+        final HashMap<String, Object> map = new HashMap<>();

+        map.put("recurrence", "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU");

+        map.put("locale", "en");

+        map.put("repaymentRescheduleType", randomInt(1, 4));

+        map.put("extendTermForDailyRepayments", false);

+        System.out.println("map : " + map);

+        return new Gson().toJson(map);

+    }

+

+    public static String updateWorkingDayWithWrongRecur() {

+        final HashMap<String, Object> map = new HashMap<>();

+        map.put("recurrence", "FREQ=WEEKLY;INTERVAL=1;BYDAY=MP,TI,TE,TH");

+        map.put("locale", "en");

+        map.put("repaymentRescheduleType", randomInt(1, 4));

+        map.put("extendTermForDailyRepayments", false);

+        System.out.println("map : " + map);

+        return new Gson().toJson(map);

+    }

+

+    public static int randomInt(int low, int high) {

+        int i = new Random().nextInt(high) + low;

+        return i;

+    }

+

+    public static int workingDaysId(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {

+        HashMap<String, Object> workingDays = getAllWorkingDays(requestSpec, responseSpec);

+        return (int) workingDays.get("id");

+    }

+

+    public static HashMap<String, Object> getAllWorkingDays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {

+

+        return Utils.performServerGet(requestSpec, responseSpec, WORKINGDAYS_URL + "?" + Utils.TENANT_IDENTIFIER, "");

+

+    }

+

+}

diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/Account.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/Account.java
new file mode 100644
index 0000000..2cb9de0
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/Account.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests.common.accounting;
+
+public class Account {
+
+    public enum AccountType {
+        ASSET("1"), INCOME("4"), EXPENSE("5"), LIABILITY("2"), EQUITY("3");
+
+        private final String accountValue;
+
+        AccountType(final String accountValue) {
+            this.accountValue = accountValue;
+        }
+
+        @Override
+        public String toString() {
+            return this.accountValue;
+        }
+    }
+
+    private final AccountType accountType;
+    private final Integer accountID;
+
+    public Account(final Integer accountID, final AccountType accountType) {
+        this.accountID = accountID;
+        this.accountType = accountType;
+    }
+
+    public AccountType getAccountType() {
+        return this.accountType;
+    }
+
+    public Integer getAccountID() {
+        return this.accountID;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/AccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/AccountHelper.java
new file mode 100644
index 0000000..3362857
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/AccountHelper.java
@@ -0,0 +1,83 @@
+/**
+ * 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.integrationtests.common.accounting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class AccountHelper {
+
+    private final String CREATE_GL_ACCOUNT_URL = "/fineract-provider/api/v1/glaccounts?" + Utils.TENANT_IDENTIFIER;
+    private final String GL_ACCOUNT_ID_RESPONSE = "resourceId";
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public AccountHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public Account createAssetAccount() {
+        final String assetAccountJSON = new GLAccountBuilder().withAccountTypeAsAsset().build();
+        final Integer accountID = Utils.performServerPost(this.requestSpec, this.responseSpec, this.CREATE_GL_ACCOUNT_URL,
+                assetAccountJSON, this.GL_ACCOUNT_ID_RESPONSE);
+        return new Account(accountID, Account.AccountType.ASSET);
+    }
+
+    public Account createIncomeAccount() {
+        final String assetAccountJSON = new GLAccountBuilder().withAccountTypeAsIncome().build();
+        final Integer accountID = Utils.performServerPost(this.requestSpec, this.responseSpec, this.CREATE_GL_ACCOUNT_URL,
+                assetAccountJSON, this.GL_ACCOUNT_ID_RESPONSE);
+        return new Account(accountID, Account.AccountType.INCOME);
+    }
+
+    public Account createExpenseAccount() {
+        final String assetAccountJSON = new GLAccountBuilder().withAccountTypeAsExpense().build();
+        final Integer accountID = Utils.performServerPost(this.requestSpec, this.responseSpec, this.CREATE_GL_ACCOUNT_URL,
+                assetAccountJSON, this.GL_ACCOUNT_ID_RESPONSE);
+        return new Account(accountID, Account.AccountType.EXPENSE);
+    }
+
+    public Account createLiabilityAccount() {
+        final String liabilityAccountJSON = new GLAccountBuilder().withAccountTypeAsLiability().build();
+        final Integer accountID = Utils.performServerPost(this.requestSpec, this.responseSpec, this.CREATE_GL_ACCOUNT_URL,
+                liabilityAccountJSON, this.GL_ACCOUNT_ID_RESPONSE);
+        return new Account(accountID, Account.AccountType.LIABILITY);
+    }
+    
+    public ArrayList getAccountingWithRunningBalances() {
+        final String GET_RUNNING_BALANCE_URL = "/fineract-provider/api/v1/glaccounts?fetchRunningBalance=true";
+        final ArrayList<HashMap> accountRunningBalance = Utils.performServerGet(this.requestSpec, this.responseSpec, GET_RUNNING_BALANCE_URL, "");
+        return accountRunningBalance;
+    }
+    
+    public HashMap getAccountingWithRunningBalanceById(final String accountId) {
+        final String GET_RUNNING_BALANCE_URL = "/fineract-provider/api/v1/glaccounts/" + accountId + "?fetchRunningBalance=true";
+        final HashMap accountRunningBalance = Utils.performServerGet(this.requestSpec, this.responseSpec, GET_RUNNING_BALANCE_URL, "");
+        return accountRunningBalance;
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountHelper.java
new file mode 100755
index 0000000..ae20cda
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountHelper.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests.common.accounting;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class FinancialActivityAccountHelper {
+
+    private static final String FINANCIAL_ACTIVITY_ACCOUNT_MAPPING_URL = "/fineract-provider/api/v1/financialactivityaccounts";
+    private final RequestSpecification requestSpec;
+
+    public FinancialActivityAccountHelper(final RequestSpecification requestSpec) {
+        this.requestSpec = requestSpec;
+    }
+
+    public Object createFinancialActivityAccount(Integer financialActivityId, Integer glAccountId,
+            final ResponseSpecification responseSpecification, String jsonBack) {
+        String json = FinancialActivityAccountsMappingBuilder.build(financialActivityId, glAccountId);
+        return Utils.performServerPost(this.requestSpec, responseSpecification, FINANCIAL_ACTIVITY_ACCOUNT_MAPPING_URL + "?"
+                + Utils.TENANT_IDENTIFIER, json, jsonBack);
+    }
+
+    public Object updateFinancialActivityAccount(Integer financialActivityAccountId, Integer financialActivityId, Integer glAccountId,
+            final ResponseSpecification responseSpecification, String jsonBack) {
+        String json = FinancialActivityAccountsMappingBuilder.build(financialActivityId, glAccountId);
+        return Utils.performServerPut(this.requestSpec, responseSpecification, FINANCIAL_ACTIVITY_ACCOUNT_MAPPING_URL + "/"
+                + financialActivityAccountId + "?" + Utils.TENANT_IDENTIFIER, json, jsonBack);
+    }
+
+    public HashMap getFinancialActivityAccount(final Integer financialActivityAccountId, final ResponseSpecification responseSpecification) {
+        final String url = FINANCIAL_ACTIVITY_ACCOUNT_MAPPING_URL + "/" + financialActivityAccountId + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpecification, url, "");
+    }
+
+    public List<HashMap> getAllFinancialActivityAccounts(final ResponseSpecification responseSpecification) {
+        final String url = FINANCIAL_ACTIVITY_ACCOUNT_MAPPING_URL + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(this.requestSpec, responseSpecification, url, "");
+    }
+
+    public Integer deleteFinancialActivityAccount(final Integer financialActivityAccountId,
+            final ResponseSpecification responseSpecification, String jsonBack) {
+        final String url = FINANCIAL_ACTIVITY_ACCOUNT_MAPPING_URL + "/" + financialActivityAccountId + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerDelete(this.requestSpec, responseSpecification, url, jsonBack);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountsMappingBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountsMappingBuilder.java
new file mode 100755
index 0000000..2436330
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountsMappingBuilder.java
@@ -0,0 +1,33 @@
+/**
+ * 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.integrationtests.common.accounting;
+
+import java.util.HashMap;
+
+import com.google.gson.Gson;
+
+public class FinancialActivityAccountsMappingBuilder {
+
+    public static String build(Integer financialActivityId, Integer glAccountId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("financialActivityId", financialActivityId);
+        map.put("glAccountId", glAccountId);
+        return new Gson().toJson(map);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/GLAccountBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/GLAccountBuilder.java
new file mode 100644
index 0000000..502e0d1
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/GLAccountBuilder.java
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests.common.accounting;
+
+import java.util.Calendar;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+
+public class GLAccountBuilder {
+
+    public static final String ASSET_ACCOUNT = "1";
+    public static final String LIABILITY_ACCOUNT = "2";
+    public static final String EQUITY_ACCOUNT = "3";
+    public static final String INCOME_ACCOUNT = "4";
+    public static final String EXPENSE_ACCOUNT = "5";
+
+    private static final String ACCOUNT_USAGE_DETAIL = "1";
+    private static final String ACCOUNT_USAGE_HEADER = "2";
+    private static final String MANUAL_ENTRIES_ALLOW = "true";
+    private static final String MANUAL_ENTRIES_NOT_ALLOW = "false";
+
+    private static String name = Utils.randomStringGenerator("ACCOUNT_NAME_", 5);
+
+    private static String GLCode = "";
+    private static String accountType = "";
+    private static String accountUsage = ACCOUNT_USAGE_DETAIL;
+    private static String manualEntriesAllowed = MANUAL_ENTRIES_ALLOW;
+    private static String description = "DEFAULT_DESCRIPTION";
+
+    public String build() {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("name", GLAccountBuilder.name);
+        map.put("glCode", GLAccountBuilder.GLCode);
+        map.put("manualEntriesAllowed", GLAccountBuilder.manualEntriesAllowed);
+        map.put("type", GLAccountBuilder.accountType);
+        map.put("usage", GLAccountBuilder.accountUsage);
+        map.put("description", GLAccountBuilder.description);
+        return new Gson().toJson(map);
+    }
+
+    public GLAccountBuilder withAccountTypeAsAsset() {
+        GLAccountBuilder.accountType = ASSET_ACCOUNT;
+        GLAccountBuilder.GLCode = Utils.randomStringGenerator("ASSET_", 2);
+        GLAccountBuilder.GLCode += Calendar.getInstance().getTimeInMillis() + ""; // Added
+        // unique
+        // timestamp
+        // for
+        // avoiding
+        // random
+        // collisions
+        return this;
+    }
+
+    public GLAccountBuilder withAccountTypeAsLiability() {
+        GLAccountBuilder.accountType = LIABILITY_ACCOUNT;
+        GLAccountBuilder.GLCode = Utils.randomStringGenerator("LIABILITY_", 2);
+        GLAccountBuilder.GLCode += Calendar.getInstance().getTimeInMillis() + "";
+        return this;
+    }
+
+    public GLAccountBuilder withAccountTypeAsAsEquity() {
+        GLAccountBuilder.accountType = EQUITY_ACCOUNT;
+        GLAccountBuilder.GLCode = Utils.randomStringGenerator("EQUITY_", 2);
+        GLAccountBuilder.GLCode += Calendar.getInstance().getTimeInMillis() + "";
+        return this;
+    }
+
+    public GLAccountBuilder withAccountTypeAsIncome() {
+        GLAccountBuilder.accountType = INCOME_ACCOUNT;
+        GLAccountBuilder.GLCode = Utils.randomStringGenerator("INCOME_", 2);
+        GLAccountBuilder.GLCode += Calendar.getInstance().getTimeInMillis() + "";
+        return this;
+    }
+
+    public GLAccountBuilder withAccountTypeAsExpense() {
+        GLAccountBuilder.accountType = EXPENSE_ACCOUNT;
+        GLAccountBuilder.GLCode = Utils.randomStringGenerator("EXPENSE_", 2);
+        GLAccountBuilder.GLCode += Calendar.getInstance().getTimeInMillis() + "";
+        return this;
+    }
+
+    public GLAccountBuilder withAccountUsageAsHeader() {
+        GLAccountBuilder.accountUsage = ACCOUNT_USAGE_HEADER;
+        return this;
+    }
+
+    public GLAccountBuilder withMaualEntriesNotAllowed() {
+        GLAccountBuilder.manualEntriesAllowed = MANUAL_ENTRIES_NOT_ALLOW;
+        return this;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntry.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntry.java
new file mode 100644
index 0000000..bbfd8fd
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntry.java
@@ -0,0 +1,56 @@
+/**
+ * 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.integrationtests.common.accounting;
+
+public class JournalEntry {
+
+    public enum TransactionType {
+        CREDIT("CREDIT"), DEBIT("DEBIT");
+
+        private TransactionType(final String type) {
+            this.type = type;
+        }
+
+        private final String type;
+
+        @Override
+        public String toString() {
+            return this.type;
+        }
+    }
+
+    private final Float transactionAmount;
+    private final TransactionType transactionType;
+    private final Integer officeId;
+
+    public JournalEntry(final float transactionAmount, final TransactionType type) {
+        this.transactionAmount = transactionAmount;
+        this.transactionType = type;
+        this.officeId = null;
+    }
+
+    public Float getTransactionAmount() {
+        return this.transactionAmount;
+    }
+
+    public String getTransactionType() {
+        return this.transactionType.toString();
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java
new file mode 100644
index 0000000..b549763
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java
@@ -0,0 +1,111 @@
+/**
+ * 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.integrationtests.common.accounting;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class JournalEntryHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public JournalEntryHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public void checkJournalEntryForExpenseAccount(final Account expenseAccount, final String date, final JournalEntry... accountEntries) {
+        checkJournalEntry(null, expenseAccount, date, accountEntries);
+    }
+
+    public void checkJournalEntryForAssetAccount(final Account assetAccount, final String date, final JournalEntry... accountEntries) {
+        checkJournalEntry(null, assetAccount, date, accountEntries);
+    }
+
+    public void checkJournalEntryForIncomeAccount(final Account incomeAccount, final String date, final JournalEntry... accountEntries) {
+        checkJournalEntry(null, incomeAccount, date, accountEntries);
+    }
+
+    public void checkJournalEntryForLiabilityAccount(final Account liabilityAccount, final String date,
+            final JournalEntry... accountEntries) {
+        checkJournalEntry(null, liabilityAccount, date, accountEntries);
+    }
+
+    public void checkJournalEntryForLiabilityAccount(final Integer officeId, final Account liabilityAccount, final String date,
+            final JournalEntry... accountEntries) {
+        checkJournalEntry(officeId, liabilityAccount, date, accountEntries);
+    }
+
+    public void ensureNoAccountingTransactionsWithTransactionId(final String transactionId) {
+        ArrayList<HashMap> transactions = getJournalEntriesByTransactionId(transactionId);
+        assertTrue("Tranasactions are is not empty", transactions.isEmpty());
+
+    }
+
+    private String getEntryValueFromJournalEntry(final ArrayList<HashMap> entryResponse, final int entryNumber) {
+        final HashMap map = (HashMap) entryResponse.get(entryNumber).get("entryType");
+        return (String) map.get("value");
+    }
+
+    private Float getTransactionAmountFromJournalEntry(final ArrayList<HashMap> entryResponse, final int entryNumber) {
+        return (Float) entryResponse.get(entryNumber).get("amount");
+    }
+
+    private void checkJournalEntry(final Integer officeId, final Account account, final String date, final JournalEntry... accountEntries) {
+        final String url = createURLForGettingAccountEntries(account, date, officeId);
+        final ArrayList<HashMap> response = Utils.performServerGet(this.requestSpec, this.responseSpec, url, "pageItems");
+        for (int i = 0; i < accountEntries.length; i++) {
+            assertThat(getEntryValueFromJournalEntry(response, i), equalTo(accountEntries[i].getTransactionType()));
+            assertThat(getTransactionAmountFromJournalEntry(response, i), equalTo(accountEntries[i].getTransactionAmount()));
+        }
+    }
+
+    private String createURLForGettingAccountEntries(final Account account, final String date, final Integer officeId) {
+        String url = new String("/fineract-provider/api/v1/journalentries?glAccountId=" + account.getAccountID() + "&type="
+                + account.getAccountType() + "&fromDate=" + date + "&toDate=" + date + "&tenantIdentifier=default"
+                + "&orderBy=id&sortOrder=desc&locale=en&dateFormat=dd MMMM yyyy");
+        if (officeId != null) {
+            url = url + "&officeId=" + officeId;
+        }
+        return url;
+    }
+
+    private ArrayList<HashMap> getJournalEntriesByTransactionId(final String transactionId) {
+        final String url = createURLForGettingAccountEntriesByTransactionId(transactionId);
+        final ArrayList<HashMap> response = Utils.performServerGet(this.requestSpec, this.responseSpec, url, "pageItems");
+        return response;
+    }
+
+    private String createURLForGettingAccountEntriesByTransactionId(final String transactionId) {
+        return new String("/fineract-provider/api/v1/journalentries?transactionId=" + transactionId + "&tenantIdentifier=default"
+                + "&orderBy=id&sortOrder=desc&locale=en&dateFormat=dd MMMM yyyy");
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/PeriodicAccrualAccountingHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/PeriodicAccrualAccountingHelper.java
new file mode 100755
index 0000000..843f8e0
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/PeriodicAccrualAccountingHelper.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests.common.accounting;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class PeriodicAccrualAccountingHelper {
+
+    private static final String PERIODIC_ACCRUAL_URL = "/fineract-provider/api/v1/runaccruals";
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public PeriodicAccrualAccountingHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public Object runPeriodicAccrualAccounting(String date) {
+        String json = getRunPeriodicAccrual(date);
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, PERIODIC_ACCRUAL_URL + "?" + Utils.TENANT_IDENTIFIER, json, "");
+    }
+
+    private String getRunPeriodicAccrual(String date) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en_GB");
+        map.put("tillDate", date);
+        return new Gson().toJson(map);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java
new file mode 100755
index 0000000..6b17468
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java
@@ -0,0 +1,412 @@
+/**
+ * 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.integrationtests.common.charges;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class ChargesHelper {
+
+    private static final String CHARGES_URL = "/fineract-provider/api/v1/charges";
+    private static final String CREATE_CHARGES_URL = CHARGES_URL + "?" + Utils.TENANT_IDENTIFIER;
+
+    private static final Integer CHARGE_APPLIES_TO_LOAN = 1;
+    private static final Integer CHARGE_APPLIES_TO_SAVINGS = 2;
+    private static final Integer CHARGE_APPLIES_TO_CLIENT = 3;
+
+    private static final Integer CHARGE_DISBURSEMENT_FEE = 1;
+    private static final Integer CHARGE_SPECIFIED_DUE_DATE = 2;
+    private static final Integer CHARGE_SAVINGS_ACTIVATION_FEE = 3;
+    private static final Integer CHARGE_WITHDRAWAL_FEE = 5;
+    private static final Integer CHARGE_ANNUAL_FEE = 6;
+    private static final Integer CHARGE_MONTHLY_FEE = 7;
+    private static final Integer CHARGE_INSTALLMENT_FEE = 8;
+    private static final Integer CHARGE_OVERDUE_INSTALLMENT_FEE = 9;
+    private static final Integer CHARGE_OVERDRAFT_FEE = 10;
+    private static final Integer WEEKLY_FEE = 11;
+    
+    private static final Integer CHARGE_CLIENT_SPECIFIED_DUE_DATE = 1;
+
+    public static final Integer CHARGE_CALCULATION_TYPE_FLAT = 1;
+    public static final Integer CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT = 2;
+    public static final Integer CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST = 3;
+    public static final Integer CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST = 4;
+
+    private static final Integer CHARGE_PAYMENT_MODE_REGULAR = 0;
+    private static final Integer CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER = 1;
+
+    private static final Integer CHARGE_FEE_FREQUENCY_DAYS = 0;
+    private static final Integer CHARGE_FEE_FREQUENCY_WEEKS = 1;
+    private static final Integer CHARGE_FEE_FREQUENCY_MONTHS = 2;
+    private static final Integer CHARGE_FEE_FREQUENCY_YEARS = 3;
+
+    private final static boolean active = true;
+    private final static boolean penalty = true;
+    private final static String amount = "100";
+    private final static String currencyCode = "USD";
+    public final static String feeOnMonthDay = "04 March";
+    private final static String monthDayFormat = "dd MMM";
+
+    public static String getSavingsSpecifiedDueDateJSON() {
+        final HashMap<String, Object> map = populateDefaultsForSavings();
+        map.put("chargeTimeType", CHARGE_SPECIFIED_DUE_DATE);
+        map.put("feeInterval", 2);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static String getSavingsActivationFeeJSON() {
+        final HashMap<String, Object> map = populateDefaultsForSavings();
+        map.put("chargeTimeType", CHARGE_SAVINGS_ACTIVATION_FEE);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static String getSavingsWithdrawalFeeJSON() {
+        final HashMap<String, Object> map = populateDefaultsForSavings();
+        map.put("chargeTimeType", CHARGE_WITHDRAWAL_FEE);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static String getSavingsAnnualFeeJSON() {
+        final HashMap<String, Object> map = populateDefaultsForSavings();
+        map.put("feeOnMonthDay", ChargesHelper.feeOnMonthDay);
+        map.put("chargeTimeType", CHARGE_ANNUAL_FEE);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static String getSavingsMonthlyFeeJSON() {
+        final HashMap<String, Object> map = populateDefaultsForSavings();
+        map.put("feeOnMonthDay", ChargesHelper.feeOnMonthDay);
+        map.put("chargeTimeType", CHARGE_MONTHLY_FEE);
+        map.put("feeInterval", 2);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+    
+    public static String getSavingsWeeklyFeeJSON() {
+        final HashMap<String, Object> map = populateDefaultsForSavings();
+        map.put("chargeTimeType", WEEKLY_FEE);
+        map.put("feeInterval", 1);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static String getSavingsOverdraftFeeJSON() {
+        final HashMap<String, Object> map = populateDefaultsForSavings();
+        map.put("chargeTimeType", CHARGE_OVERDRAFT_FEE);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static HashMap<String, Object> populateDefaultsForSavings() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("active", ChargesHelper.active);
+        map.put("amount", ChargesHelper.amount);
+        map.put("chargeAppliesTo", ChargesHelper.CHARGE_APPLIES_TO_SAVINGS);
+        map.put("chargeCalculationType", ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT);
+        map.put("currencyCode", ChargesHelper.currencyCode);
+        map.put("locale", CommonConstants.locale);
+        map.put("monthDayFormat", ChargesHelper.monthDayFormat);
+        map.put("name", Utils.randomNameGenerator("Charge_Savings_", 6));
+        return map;
+    }
+
+    public static String getLoanDisbursementJSON() {
+        return getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, ChargesHelper.amount);
+    }
+
+    public static String getLoanDisbursementJSON(final Integer chargeCalculationType, final String amount) {
+        return getLoanDisbursementJSON(chargeCalculationType, amount, ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
+    }
+
+    public static String getLoanDisbursementAccountTransferJSON(final Integer chargeCalculationType, final String amount) {
+        return getLoanDisbursementJSON(chargeCalculationType, amount, ChargesHelper.CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER);
+    }
+
+    public static String getLoanDisbursementJSON(final Integer chargeCalculationType, final String amount, final Integer paymentmode) {
+        final HashMap<String, Object> map = populateDefaultsForLoan();
+        map.put("chargeTimeType", CHARGE_DISBURSEMENT_FEE);
+        map.put("chargePaymentMode", paymentmode);
+        map.put("amount", amount);
+        map.put("chargeCalculationType", chargeCalculationType);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static String getLoanSpecifiedDueDateJSON() {
+        return getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, ChargesHelper.amount, ChargesHelper.penalty);
+    }
+
+    public static String getLoanSpecifiedDueDateJSON(final Integer chargeCalculationType, final String amount, boolean penalty) {
+        return getLoanSpecifiedDueDateJSON(chargeCalculationType, amount, penalty, ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
+    }
+
+    public static String getLoanSpecifiedDueDateJSON(final Integer chargeCalculationType, final String amount, final boolean penalty,
+            final Integer paymentMode) {
+        final HashMap<String, Object> map = populateDefaultsForLoan();
+        map.put("chargeTimeType", CHARGE_SPECIFIED_DUE_DATE);
+        map.put("chargePaymentMode", paymentMode);
+        map.put("penalty", penalty);
+        map.put("amount", amount);
+        map.put("chargeCalculationType", chargeCalculationType);
+
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static String getLoanSpecifiedDueDateWithAccountTransferJSON(final Integer chargeCalculationType, final String amount,
+            boolean penalty) {
+        return getLoanSpecifiedDueDateJSON(chargeCalculationType, amount, penalty, ChargesHelper.CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER);
+    }
+
+    public static String getLoanSpecifiedDueDateWithAccountTransferJSON() {
+        return getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, ChargesHelper.amount, ChargesHelper.penalty,
+                ChargesHelper.CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER);
+    }
+
+    public static String getLoanInstallmentJSON(final Integer chargeCalculationType, final String amount, boolean penalty) {
+        return getLoanInstallmentJSON(chargeCalculationType, amount, penalty, ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
+    }
+
+    public static String getLoanInstallmentJSON(final Integer chargeCalculationType, final String amount, final boolean penalty,
+            final Integer paymentMode) {
+        final HashMap<String, Object> map = populateDefaultsForLoan();
+        map.put("chargeTimeType", CHARGE_INSTALLMENT_FEE);
+        map.put("chargePaymentMode", paymentMode);
+        map.put("penalty", penalty);
+        map.put("amount", amount);
+        map.put("chargeCalculationType", chargeCalculationType);
+
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static String getLoanInstallmentWithAccountTransferJSON(final Integer chargeCalculationType, final String amount, boolean penalty) {
+        return getLoanInstallmentJSON(chargeCalculationType, amount, penalty, ChargesHelper.CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER);
+    }
+
+    public static String getLoanInstallmentFeeJSON() {
+        return getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, ChargesHelper.amount, ChargesHelper.penalty);
+    }
+
+    public static String getLoanOverdueFeeJSON() {
+        final HashMap<String, Object> map = populateDefaultsForLoan();
+        map.put("penalty", ChargesHelper.penalty);
+        map.put("chargePaymentMode", ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
+        map.put("chargeTimeType", CHARGE_OVERDUE_INSTALLMENT_FEE);
+        map.put("feeFrequency", ChargesHelper.CHARGE_FEE_FREQUENCY_MONTHS);
+        map.put("feeOnMonthDay", ChargesHelper.feeOnMonthDay);
+        map.put("feeInterval", 2);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+    
+    public static String getLoanOverdueFeeJSONWithCalculattionTypePercentage() {
+        final HashMap<String, Object> map = populateDefaultsForLoan();
+        map.put("penalty", ChargesHelper.penalty);
+        map.put("amount", "10");
+        map.put("chargePaymentMode", ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
+        map.put("chargeTimeType", CHARGE_OVERDUE_INSTALLMENT_FEE);
+        map.put("chargeCalculationType", ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST);
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println(chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static HashMap<String, Object> populateDefaultsForLoan() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("active", ChargesHelper.active);
+        map.put("amount", ChargesHelper.amount);
+        map.put("chargeAppliesTo", ChargesHelper.CHARGE_APPLIES_TO_LOAN);
+        map.put("chargeCalculationType", ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT);
+        map.put("currencyCode", ChargesHelper.currencyCode);
+        map.put("locale", CommonConstants.locale);
+        map.put("monthDayFormat", ChargesHelper.monthDayFormat);
+        map.put("name", Utils.randomNameGenerator("Charge_Loans_", 6));
+        return map;
+    }
+    
+    public static HashMap<String, Object> populateDefaultsClientCharge() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("active", ChargesHelper.active);
+        map.put("amount", ChargesHelper.amount);
+        map.put("chargeAppliesTo", ChargesHelper.CHARGE_APPLIES_TO_CLIENT);
+        map.put("chargeCalculationType", ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT);
+        map.put("chargeTimeType",ChargesHelper.CHARGE_SPECIFIED_DUE_DATE);
+        map.put("currencyCode", ChargesHelper.currencyCode);
+        map.put("locale", CommonConstants.locale);
+        map.put("monthDayFormat", ChargesHelper.monthDayFormat);
+        map.put("name", Utils.randomNameGenerator("Charge_client_", 8));
+        return map;
+    }
+
+    public static Integer createCharges(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String request) {
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_CHARGES_URL, request, "resourceId");
+    }
+
+    public static ArrayList<HashMap> getCharges(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return (ArrayList) Utils.performServerGet(requestSpec, responseSpec, CHARGES_URL + "?" + Utils.TENANT_IDENTIFIER, "");
+    }
+
+    public static HashMap getChargeById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer chargeId) {
+        return Utils.performServerGet(requestSpec, responseSpec, CHARGES_URL + "/" + chargeId + "?" + Utils.TENANT_IDENTIFIER, "");
+    }
+
+    public static HashMap getChargeChanges(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer chargeId) {
+        return Utils.performServerGet(requestSpec, responseSpec, CHARGES_URL + "/" + chargeId + "?" + Utils.TENANT_IDENTIFIER,
+                CommonConstants.RESPONSE_CHANGES);
+    }
+
+    public static HashMap updateCharges(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer chargeId, final String request) {
+        return Utils.performServerPut(requestSpec, responseSpec, CHARGES_URL + "/" + chargeId + "?" + Utils.TENANT_IDENTIFIER, request,
+                CommonConstants.RESPONSE_CHANGES);
+    }
+
+    public static Integer deleteCharge(final ResponseSpecification responseSpec, final RequestSpecification requestSpec,
+            final Integer chargeId) {
+        return Utils.performServerDelete(requestSpec, responseSpec, CHARGES_URL + "/" + chargeId + "?" + Utils.TENANT_IDENTIFIER,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public static String getModifyChargeJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("amount", "200.0");
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getModifyWithdrawalFeeSavingsChargeJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("chargeCalculationType", CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getModifyChargeAsPecentageAmountJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("chargeCalculationType", CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT);
+        map.put("chargePaymentMode", CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getModifyChargeAsPecentageLoanAmountWithInterestJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("chargeCalculationType", CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST);
+        map.put("chargePaymentMode", CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getModifyChargeAsPercentageInterestJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("chargeCalculationType", CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST);
+        map.put("chargePaymentMode", CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getModifyChargeFeeFrequencyAsDaysJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("feeFrequency", ChargesHelper.CHARGE_FEE_FREQUENCY_DAYS);
+        map.put("feeInterval", 2);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getModifyChargeFeeFrequencyAsWeeksJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("feeFrequency", ChargesHelper.CHARGE_FEE_FREQUENCY_WEEKS);
+        map.put("feeInterval", 2);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getModifyChargeFeeFrequencyAsMonthsJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("feeFrequency", ChargesHelper.CHARGE_FEE_FREQUENCY_MONTHS);
+        map.put("feeInterval", 2);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getModifyChargeFeeFrequencyAsYearsJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("feeFrequency", ChargesHelper.CHARGE_FEE_FREQUENCY_YEARS);
+        map.put("feeInterval", 2);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+    
+    public static String getChargeSpecifiedDueDateJSON() {
+        final HashMap<String, Object> map = populateDefaultsClientCharge();
+        String chargesCreateJson = new Gson().toJson(map);
+        System.out.println("chargesCreateJson:"+chargesCreateJson);
+        return chargesCreateJson;
+    }
+
+    public static String applyCharge(RequestSpecification requestSpec,ResponseSpecification responseSpec, String chargeId,String json) {
+        return Utils.performServerPost(requestSpec, responseSpec, CHARGES_URL + "/" + chargeId + "?" + Utils.TENANT_IDENTIFIER, json,"status");
+        
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
new file mode 100644
index 0000000..fa48717
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
@@ -0,0 +1,490 @@
+/**
+ * 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.integrationtests.common.fixeddeposit;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes" })
+public class FixedDepositAccountHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public FixedDepositAccountHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    private static final String FIXED_DEPOSIT_ACCOUNT_URL = "/fineract-provider/api/v1/fixeddepositaccounts";
+    private static final String APPLY_FIXED_DEPOSIT_ACCOUNT_URL = FIXED_DEPOSIT_ACCOUNT_URL + "?" + Utils.TENANT_IDENTIFIER;
+    private static final String APPROVE_FIXED_DEPOSIT_COMMAND = "approve";
+    private static final String UNDO_APPROVAL_FIXED_DEPOSIT_COMMAND = "undoapproval";
+    private static final String REJECT_FIXED_DEPOSIT_COMMAND = "reject";
+    private static final String WITHDRAWN_BY_CLIENT_FIXED_DEPOSIT_COMMAND = "withdrawnByApplicant";
+    private static final String ACTIVATE_FIXED_DEPOSIT_COMMAND = "activate";
+    private static final String CLOSE_FIXED_DEPOSIT_COMMAND = "close";
+    private static final String POST_INTEREST_FIXED_DEPOSIT_COMMAND = "postInterest";
+    private static final String CALCULATE_INTEREST_FIXED_DEPOSIT_COMMAND = "calculateInterest";
+    private static final String CALCULATE_PREMATURE_AMOUNT_COMMAND = "calculatePrematureAmount";
+    private static final String PREMATURE_CLOSE_COMMAND = "prematureClose";
+
+    private static final String LOCALE = "en_GB";
+    private static final String DIGITS_AFTER_DECIMAL = "4";
+    private static final String IN_MULTIPLES_OF = "100";
+    private static final String USD = "USD";
+    public static final String DAYS = "0";
+    private static final String WEEKS = "1";
+    private static final String MONTHS = "2";
+    private static final String YEARS = "3";
+    private static final String DAILY = "1";
+    private static final String MONTHLY = "4";
+    private static final String QUARTERLY = "5";
+    private static final String BI_ANNUALLY = "6";
+    private static final String ANNUALLY = "7";
+    private static final String INTEREST_CALCULATION_USING_DAILY_BALANCE = "1";
+    private static final String INTEREST_CALCULATION_USING_AVERAGE_DAILY_BALANCE = "2";
+    private static final String DAYS_360 = "360";
+    private static final String DAYS_365 = "365";
+    public final static String depositAmount = "100000";
+
+    private String interestCompoundingPeriodType = MONTHLY;
+    private String interestPostingPeriodType = MONTHLY;
+    private String interestCalculationType = INTEREST_CALCULATION_USING_DAILY_BALANCE;
+    private String lockinPeriodFrequency = "1";
+    private String lockingPeriodFrequencyType = MONTHS;
+    private final String minDepositTerm = "6";
+    private final String minDepositTermTypeId = MONTHS;
+    private final String maxDepositTerm = "10";
+    private final String maxDepositTermTypeId = YEARS;
+    private final String inMultiplesOfDepositTerm = "2";
+    private final String inMultiplesOfDepositTermTypeId = MONTHS;
+    private final String preClosurePenalInterest = "2";
+    private String interestCalculationDaysInYearType = DAYS_365;
+    private final boolean preClosurePenalApplicable = true;
+    private final boolean isActiveChart = true;
+    private final String currencyCode = USD;
+
+    private final String depositPeriod = "14";
+    private final String depositPeriodFrequencyId = MONTHS;
+    private String submittedOnDate = "";
+    private String savingsId = null;
+    private boolean transferInterest = false;
+
+    public String build(final String clientId, final String productId, final String validFrom, final String validTo,
+            final String penalInterestType) {
+        final HashMap<String, Object> map = new HashMap<>();
+
+        List<HashMap<String, String>> chartSlabs = new ArrayList<HashMap<String, String>>();
+        HashMap<String, String> chartSlabsMap1 = new HashMap<>();
+        chartSlabsMap1.put("description", "First");
+        chartSlabsMap1.put("periodType", MONTHS);
+        chartSlabsMap1.put("fromPeriod", "1");
+        chartSlabsMap1.put("toPeriod", "6");
+        chartSlabsMap1.put("annualInterestRate", "5");
+        chartSlabsMap1.put("locale", LOCALE);
+        chartSlabs.add(0, chartSlabsMap1);
+
+        HashMap<String, String> chartSlabsMap2 = new HashMap<>();
+        chartSlabsMap2.put("description", "Second");
+        chartSlabsMap2.put("periodType", MONTHS);
+        chartSlabsMap2.put("fromPeriod", "7");
+        chartSlabsMap2.put("toPeriod", "12");
+        chartSlabsMap2.put("annualInterestRate", "6");
+        chartSlabsMap2.put("locale", LOCALE);
+        chartSlabs.add(1, chartSlabsMap2);
+
+        HashMap<String, String> chartSlabsMap3 = new HashMap<>();
+        chartSlabsMap3.put("description", "Third");
+        chartSlabsMap3.put("periodType", MONTHS);
+        chartSlabsMap3.put("fromPeriod", "13");
+        chartSlabsMap3.put("toPeriod", "18");
+        chartSlabsMap3.put("annualInterestRate", "7");
+        chartSlabsMap3.put("locale", LOCALE);
+        chartSlabs.add(2, chartSlabsMap3);
+
+        HashMap<String, String> chartSlabsMap4 = new HashMap<>();
+        chartSlabsMap4.put("description", "Fourth");
+        chartSlabsMap4.put("periodType", MONTHS);
+        chartSlabsMap4.put("fromPeriod", "19");
+        chartSlabsMap4.put("toPeriod", "24");
+        chartSlabsMap4.put("annualInterestRate", "8");
+        chartSlabsMap4.put("locale", LOCALE);
+        chartSlabs.add(3, chartSlabsMap4);
+
+        List<HashMap<String, Object>> charts = new ArrayList<HashMap<String, Object>>();
+        HashMap<String, Object> chartsMap = new HashMap<>();
+        chartsMap.put("fromDate", validFrom);
+        chartsMap.put("endDate", validTo);
+        chartsMap.put("dateFormat", "dd MMMM yyyy");
+        chartsMap.put("locale", LOCALE);
+        chartsMap.put("isActiveChart", this.isActiveChart);
+        chartsMap.put("chartSlabs", chartSlabs);
+        charts.add(chartsMap);
+
+        map.put("charts", charts);
+        map.put("productId", productId);
+        map.put("clientId", clientId);
+        map.put("interestCalculationDaysInYearType", this.interestCalculationDaysInYearType);
+        map.put("locale", LOCALE);
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("monthDayFormat", "dd MMM");
+        map.put("interestCalculationType", this.interestCalculationType);
+        map.put("interestCompoundingPeriodType", this.interestCompoundingPeriodType);
+        map.put("interestPostingPeriodType", this.interestPostingPeriodType);
+        map.put("lockinPeriodFrequency", this.lockinPeriodFrequency);
+        map.put("lockinPeriodFrequencyType", this.lockingPeriodFrequencyType);
+        map.put("preClosurePenalApplicable", "true");
+        map.put("minDepositTermTypeId", this.minDepositTermTypeId);
+        map.put("minDepositTerm", this.minDepositTerm);
+        map.put("maxDepositTermTypeId", this.maxDepositTermTypeId);
+        map.put("maxDepositTerm", this.maxDepositTerm);
+        map.put("preClosurePenalApplicable", this.preClosurePenalApplicable);
+        map.put("inMultiplesOfDepositTerm", this.inMultiplesOfDepositTerm);
+        map.put("inMultiplesOfDepositTermTypeId", this.inMultiplesOfDepositTermTypeId);
+        map.put("preClosurePenalInterest", this.preClosurePenalInterest);
+        map.put("preClosurePenalInterestOnTypeId", penalInterestType);
+        map.put("depositAmount", depositAmount);
+        map.put("depositPeriod", this.depositPeriod);
+        map.put("depositPeriodFrequencyId", this.depositPeriodFrequencyId);
+        map.put("submittedOnDate", this.submittedOnDate);
+        map.put("linkAccountId", savingsId);
+        map.put("transferInterestToSavings", transferInterest);
+
+        String fixedDepositAccountJson = new Gson().toJson(map);
+        System.out.println(fixedDepositAccountJson);
+        return fixedDepositAccountJson;
+    }
+
+    public static Integer applyFixedDepositApplication(final String fixedDepositAccountAsJson, final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        System.out.println("--------------------- APPLYING FOR FIXED DEPOSIT ACCOUNT ------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, APPLY_FIXED_DEPOSIT_ACCOUNT_URL, fixedDepositAccountAsJson,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public static HashMap getFixedDepositAccountById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer accountID) {
+        final String GET_FIXED_DEPOSIT_BY_ID_URL = FIXED_DEPOSIT_ACCOUNT_URL + "/" + accountID + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING FIXED DEPOSIT ACCOUNT BY ID -------------------------");
+        return Utils.performServerGet(requestSpec, responseSpec, GET_FIXED_DEPOSIT_BY_ID_URL, "");
+    }
+
+    public HashMap getFixedDepositSummary(final Integer accountID) {
+        final String URL = FIXED_DEPOSIT_ACCOUNT_URL + "/" + accountID + "?" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "summary");
+        return response;
+    }
+
+    public static Float getInterestRate(ArrayList<ArrayList<HashMap>> interestSlabData, Integer depositPeriod) {
+
+        Float annualInterestRate = 0.0f;
+        for (Integer slabIndex = 0; slabIndex < interestSlabData.get(0).size(); slabIndex++) {
+            Integer fromPeriod = (Integer) interestSlabData.get(0).get(slabIndex).get("fromPeriod");
+            Integer toPeriod = (Integer) interestSlabData.get(0).get(slabIndex).get("toPeriod");
+            if (depositPeriod >= fromPeriod && depositPeriod <= toPeriod) {
+                annualInterestRate = (Float) interestSlabData.get(0).get(slabIndex).get("annualInterestRate");
+                break;
+            }
+        }
+
+        return annualInterestRate;
+    }
+
+    public static Float getPrincipalAfterCompoundingInterest(Calendar currentDate, Float principal, Integer depositPeriod,
+            double interestPerDay, Integer compoundingInterval, Integer postingInterval) {
+
+        Float totalInterest = 0.0f;
+        Float interestEarned = 0.0f;
+
+        for (int i = 1; i <= depositPeriod; i++) {
+            Integer daysInMonth = currentDate.getActualMaximum(Calendar.DATE);
+            for (int j = 0; j < daysInMonth; j++) {
+
+                interestEarned = (float) (principal * interestPerDay);
+                totalInterest += interestEarned;
+                if (compoundingInterval == 0) {
+                    principal += interestEarned;
+                }
+            }
+            if ((i % postingInterval) == 0 || i == depositPeriod) {
+                if (compoundingInterval != 0) {
+                    principal += totalInterest;
+                }
+                totalInterest = 0.0f;
+                System.out.println(principal);
+
+            }
+            currentDate.add(Calendar.MONTH, 1);
+            interestEarned = 0.0f;
+        }
+        return principal;
+    }
+
+    public HashMap updateFixedDepositAccount(final String clientID, final String productID, final String accountID, final String validFrom,
+            final String validTo, final String penalInterestType, final String submittedOnDate) {
+
+        final String fixedDepositApplicationJSON = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec) //
+                .withSubmittedOnDate(submittedOnDate) //
+                .build(clientID, productID, validFrom, validTo, penalInterestType);
+
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, FIXED_DEPOSIT_ACCOUNT_URL + "/" + accountID + "?"
+                + Utils.TENANT_IDENTIFIER, fixedDepositApplicationJSON, CommonConstants.RESPONSE_CHANGES);
+    }
+
+    public HashMap updateInterestCalculationConfigForFixedDeposit(final String clientID, final String productID, final String accountID,
+            final String submittedOnDate, final String validFrom, final String validTo, final String numberOfDaysPerYear,
+            final String penalInterestType, final String interestCalculationType, final String interestCompoundingPeriodType,
+            final String interestPostingPeriodType) {
+
+        final String fixedDepositApplicationJSON = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec) //
+                .withSubmittedOnDate(submittedOnDate) //
+                .withNumberOfDaysPerYear(numberOfDaysPerYear) //
+                .withInterestCalculationPeriodType(interestCalculationType) //
+                .withInterestCompoundingPeriodType(interestCompoundingPeriodType) //
+                .withInterestPostingPeriodType(interestPostingPeriodType) //
+                .build(clientID, productID, validFrom, validTo, penalInterestType);
+
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, FIXED_DEPOSIT_ACCOUNT_URL + "/" + accountID + "?"
+                + Utils.TENANT_IDENTIFIER, fixedDepositApplicationJSON, CommonConstants.RESPONSE_CHANGES);
+    }
+
+    public HashMap approveFixedDeposit(final Integer fixedDepositAccountID, final String approvedOnDate) {
+        System.out.println("--------------------------------- APPROVING FIXED DEPOSIT APPLICATION ------------------------------------");
+        return performFixedDepositApplicationActions(createFixedDepositOperationURL(APPROVE_FIXED_DEPOSIT_COMMAND, fixedDepositAccountID),
+                getApproveFixedDepositAccountAsJSON(approvedOnDate));
+    }
+
+    public HashMap undoApproval(final Integer fixedDepositAccountID) {
+        System.out.println("--------------------------------- UNDO APPROVING FIXED DEPOSIT APPLICATION -------------------------------");
+        final String undoBodyJson = "{'note':'UNDO APPROVAL'}";
+        return performFixedDepositApplicationActions(
+                createFixedDepositOperationURL(UNDO_APPROVAL_FIXED_DEPOSIT_COMMAND, fixedDepositAccountID), undoBodyJson);
+    }
+
+    public HashMap rejectApplication(final Integer fixedDepositAccountID, final String rejectedOnDate) {
+        System.out.println("--------------------------------- REJECT FIXED DEPOSIT APPLICATION -------------------------------");
+        return performFixedDepositApplicationActions(createFixedDepositOperationURL(REJECT_FIXED_DEPOSIT_COMMAND, fixedDepositAccountID),
+                getRejectedFixedDepositAsJSON(rejectedOnDate));
+    }
+
+    public HashMap withdrawApplication(final Integer fixedDepositAccountID, final String withdrawApplicationOnDate) {
+        System.out.println("--------------------------------- Withdraw FIXED DEPOSIT APPLICATION -------------------------------");
+        return performFixedDepositApplicationActions(
+                createFixedDepositOperationURL(WITHDRAWN_BY_CLIENT_FIXED_DEPOSIT_COMMAND, fixedDepositAccountID),
+                getWithdrawnFixedDepositAccountAsJSON(withdrawApplicationOnDate));
+    }
+
+    public HashMap activateFixedDeposit(final Integer fixedDepositAccountID, final String activationDate) {
+        System.out.println("---------------------------------- ACTIVATING FIXED DEPOSIT APPLICATION ----------------------------------");
+        return performFixedDepositApplicationActions(createFixedDepositOperationURL(ACTIVATE_FIXED_DEPOSIT_COMMAND, fixedDepositAccountID),
+                getActivatedFixedDepositAccountAsJSON(activationDate));
+    }
+
+    public Object deleteFixedDepositApplication(final Integer fixedDepositAccountID, final String jsonAttributeToGetBack) {
+        System.out.println("---------------------------------- DELETE FIXED DEPOSIT APPLICATION ----------------------------------");
+        return Utils.performServerDelete(this.requestSpec, this.responseSpec, FIXED_DEPOSIT_ACCOUNT_URL + "/" + fixedDepositAccountID + "?"
+                + Utils.TENANT_IDENTIFIER, jsonAttributeToGetBack);
+
+    }
+
+    public Integer calculateInterestForFixedDeposit(final Integer fixedDepositAccountId) {
+        System.out.println("--------------------------------- CALCULATING INTEREST FOR FIXED DEPOSIT --------------------------------");
+        return (Integer) performFixedDepositActions(
+                createFixedDepositCalculateInterestURL(CALCULATE_INTEREST_FIXED_DEPOSIT_COMMAND, fixedDepositAccountId),
+                getCalculatedInterestForFixedDepositApplicationAsJSON(), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public Integer postInterestForFixedDeposit(final Integer fixedDepositAccountId) {
+        System.out.println("--------------------------------- POST INTEREST FOR FIXED DEPOSIT --------------------------------");
+        return (Integer) performFixedDepositActions(
+                createFixedDepositCalculateInterestURL(POST_INTEREST_FIXED_DEPOSIT_COMMAND, fixedDepositAccountId),
+                getCalculatedInterestForFixedDepositApplicationAsJSON(), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public HashMap calculatePrematureAmountForFixedDeposit(final Integer fixedDepositAccountId, final String closedOnDate) {
+        System.out.println("--------------------- CALCULATING PREMATURE AMOUNT FOR FIXED DEPOSIT ----------------------------");
+        return (HashMap) performFixedDepositActions(
+                createFixedDepositCalculateInterestURL(CALCULATE_PREMATURE_AMOUNT_COMMAND, fixedDepositAccountId),
+                getCalculatedPrematureAmountForFixedDepositAccountAsJSON(closedOnDate), "");
+    }
+
+    public Object prematureCloseForFixedDeposit(final Integer fixedDepositAccountId, final String closedOnDate, final String closureType,
+            final Integer toSavingsId, final String jsonAttributeToGetBack) {
+        System.out.println("--------------------- PREMATURE CLOSE FOR FIXED DEPOSIT ----------------------------");
+        return performFixedDepositActions(createFixedDepositCalculateInterestURL(PREMATURE_CLOSE_COMMAND, fixedDepositAccountId),
+                getPrematureCloseForFixedDepositAccountAsJSON(closedOnDate, closureType, toSavingsId), jsonAttributeToGetBack);
+    }
+
+    private String getApproveFixedDepositAccountAsJSON(final String approvedOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("approvedOnDate", approvedOnDate);
+        map.put("note", "Approval NOTE");
+        String fixedDepositAccountApproveJson = new Gson().toJson(map);
+        System.out.println(fixedDepositAccountApproveJson);
+        return fixedDepositAccountApproveJson;
+    }
+
+    private String getRejectedFixedDepositAsJSON(final String rejectedOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("rejectedOnDate", rejectedOnDate);
+        map.put("note", "Rejected NOTE");
+        String fixedDepositAccountJson = new Gson().toJson(map);
+        System.out.println(fixedDepositAccountJson);
+        return fixedDepositAccountJson;
+    }
+
+    private String getWithdrawnFixedDepositAccountAsJSON(final String withdrawnApplicationOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("withdrawnOnDate", withdrawnApplicationOnDate);
+        map.put("note", "Withdraw NOTE");
+        String fixedDepositAccountJson = new Gson().toJson(map);
+        System.out.println(fixedDepositAccountJson);
+        return fixedDepositAccountJson;
+    }
+
+    private String getActivatedFixedDepositAccountAsJSON(final String activationDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("activatedOnDate", activationDate);
+        String fixedDepositAccountActivateJson = new Gson().toJson(map);
+        System.out.println(fixedDepositAccountActivateJson);
+        return fixedDepositAccountActivateJson;
+    }
+
+    private String getCalculatedInterestForFixedDepositApplicationAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        String fixedDepositAccountCalculatedInterestJson = new Gson().toJson(map);
+        System.out.println(fixedDepositAccountCalculatedInterestJson);
+        return fixedDepositAccountCalculatedInterestJson;
+    }
+
+    private String getCalculatedPrematureAmountForFixedDepositAccountAsJSON(final String closedOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("closedOnDate", closedOnDate);
+        String fixedDepositAccountPrematureClosureJson = new Gson().toJson(map);
+        System.out.println(fixedDepositAccountPrematureClosureJson);
+        return fixedDepositAccountPrematureClosureJson;
+    }
+
+    private String getPrematureCloseForFixedDepositAccountAsJSON(final String closedOnDate, final String closureType,
+            final Integer toSavingsId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("closedOnDate", closedOnDate);
+        map.put("onAccountClosureId", closureType);
+        if (toSavingsId != null) {
+            map.put("toSavingsAccountId", toSavingsId);
+            map.put("transferDescription", "Transferring To Savings Account");
+        }
+        String fixedDepositAccountPrematureCloseJson = new Gson().toJson(map);
+        System.out.println(fixedDepositAccountPrematureCloseJson);
+        return fixedDepositAccountPrematureCloseJson;
+    }
+
+    private String createFixedDepositOperationURL(final String command, final Integer fixedDepositAccountID) {
+        return FIXED_DEPOSIT_ACCOUNT_URL + "/" + fixedDepositAccountID + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private Object performFixedDepositActions(final String postURLForFixedDeposit, final String jsonToBeSent,
+            final String jsonAttributeToGetBack) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForFixedDeposit, jsonToBeSent, jsonAttributeToGetBack);
+    }
+
+    private HashMap performFixedDepositApplicationActions(final String postURLForFixedDepositAction, final String jsonToBeSent) {
+        HashMap status = null;
+        final HashMap response = Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForFixedDepositAction, jsonToBeSent,
+                CommonConstants.RESPONSE_CHANGES);
+        if (response != null) {
+            status = (HashMap) response.get("status");
+        }
+        return status;
+    }
+
+    private String createFixedDepositCalculateInterestURL(final String command, final Integer fixedDepositAccountID) {
+        return FIXED_DEPOSIT_ACCOUNT_URL + "/" + fixedDepositAccountID + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    public static ArrayList retrieveAllFixedDepositAccounts(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        System.out.println("-------------------- RETRIEVING ALL FIXED DEPOSIT ACCOUNTS ---------------------");
+        final ArrayList response = Utils.performServerGet(requestSpec, responseSpec, FIXED_DEPOSIT_ACCOUNT_URL + "?"
+                + Utils.TENANT_IDENTIFIER, "");
+        return response;
+    }
+
+    public FixedDepositAccountHelper withSubmittedOnDate(final String fixedDepositApplicationSubmittedDate) {
+        this.submittedOnDate = fixedDepositApplicationSubmittedDate;
+        return this;
+    }
+
+    public FixedDepositAccountHelper withNumberOfDaysPerYear(final String numberOfDaysPerYearTypeId) {
+        this.interestCalculationDaysInYearType = numberOfDaysPerYearTypeId;
+        return this;
+    }
+
+    public FixedDepositAccountHelper withInterestCalculationPeriodType(final String interestCalculationTypeId) {
+        this.interestCalculationType = interestCalculationTypeId;
+        return this;
+    }
+
+    public FixedDepositAccountHelper withInterestCompoundingPeriodType(final String interestCompoundingPeriodTypeId) {
+        this.interestCompoundingPeriodType = interestCompoundingPeriodTypeId;
+        return this;
+    }
+
+    public FixedDepositAccountHelper withInterestPostingPeriodType(final String interestPostingPeriodTypeId) {
+        this.interestPostingPeriodType = interestPostingPeriodTypeId;
+        return this;
+    }
+
+    public FixedDepositAccountHelper withSavings(final String savingsId) {
+        this.savingsId = savingsId;
+        return this;
+    }
+
+    public FixedDepositAccountHelper transferInterest(final boolean transferInterest) {
+        this.transferInterest = transferInterest;
+        return this;
+    }
+
+    public FixedDepositAccountHelper withLockinPeriodFrequency(final String lockingPeriodFrequencyType, final String lockinPeriodFrequency) {
+        this.lockingPeriodFrequencyType = lockingPeriodFrequencyType;
+        this.lockinPeriodFrequency = lockinPeriodFrequency;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountStatusChecker.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountStatusChecker.java
new file mode 100644
index 0000000..fca5cec
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountStatusChecker.java
@@ -0,0 +1,94 @@
+/**
+ * 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.integrationtests.common.fixeddeposit;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class FixedDepositAccountStatusChecker {
+
+    private static final String FIXED_DEPOSIT_ACCOUNT_URL = "/fineract-provider/api/v1/fixeddepositaccounts";
+
+    public static void verifyFixedDepositIsApproved(final HashMap fixedDepositStatusHashMap) {
+        System.out.println("-------------------- VERIFYING FIXED DEPOSIT APPLICATION IS APPROVED --------------------");
+        assertTrue("Error in Approving Fixed deposit application", getStatus(fixedDepositStatusHashMap, "approved"));
+        System.out.println(fixedDepositStatusHashMap);
+    }
+
+    public static void verifyFixedDepositIsPending(final HashMap fixedDepositStatusHashMap) {
+        System.out.println("-------------------- VERIFYING FIXED DEPOSIT APPLICATION IS PENDING --------------------");
+        assertTrue("FIXED DEPOSIT ACCOUNT IS NOT IN PENDING STATE", getStatus(fixedDepositStatusHashMap, "submittedAndPendingApproval"));
+        System.out.println(fixedDepositStatusHashMap);
+    }
+
+    public static void verifyFixedDepositIsActive(final HashMap fixedDepositStatusHashMap) {
+        System.out.println("----------------- VERIFYING FIXED DEPOSIT APPLICATION IS ACTIVE -----------------");
+        assertTrue("ERROR IN ACTIVATING THE FIXED DEPOSIT APPLICATION", getStatus(fixedDepositStatusHashMap, "active"));
+        System.out.println(fixedDepositStatusHashMap);
+    }
+
+    public static void verifyFixedDepositIsRejected(final HashMap fixedDepositStatusHashMap) {
+        System.out.println("-------------- VERIFYING FIXED DEPOSIT APPLICATION IS REJECTED ----------------");
+        assertTrue("ERROR IN REJECTING THE FIXED DEPOSIT APPLICATION", getStatus(fixedDepositStatusHashMap, "rejected"));
+        System.out.println(fixedDepositStatusHashMap);
+    }
+
+    public static void verifyFixedDepositIsWithdrawn(final HashMap fixedDepositStatusHashMap) {
+        System.out.println("---------------- VERIFYING FIXED DEPOSIT APPLICATION IS WITHDRAWN ----------------");
+        assertTrue("ERROR IN WITHDRAW  THE FIXED DEPOSIT APPLICATION", getStatus(fixedDepositStatusHashMap, "withdrawnByApplicant"));
+        System.out.println(fixedDepositStatusHashMap);
+    }
+
+    public static void verifyFixedDepositAccountIsClosed(final HashMap fixedDepositStatusHashMap) {
+        System.out.println("--------------------- VERIFYING FIXED DEPOSIT APPLICATION IS CLOSED ---------------------");
+        assertTrue("ERROR IN CLOSING THE FIXED DEPOSIT APPLICATION", getStatus(fixedDepositStatusHashMap, "closed"));
+        System.out.println(fixedDepositStatusHashMap);
+    }
+
+    public static void verifyFixedDepositAccountIsNotActive(final HashMap fixedDepositStatusHashMap) {
+        System.out.println("------------------ VERIFYING FIXED DEPOSIT APPLICATION IS INACTIVE --------------------");
+        Assert.assertFalse(getStatus(fixedDepositStatusHashMap, "active"));
+        System.out.println(fixedDepositStatusHashMap);
+    }
+
+    public static HashMap getStatusOfFixedDepositAccount(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String fixedDepositAccountID) {
+        final String GET_STATUS_OF_FIXED_DEPOSIT_ACCOUNT_URL = FIXED_DEPOSIT_ACCOUNT_URL + "/" + fixedDepositAccountID + "?"
+                + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, GET_STATUS_OF_FIXED_DEPOSIT_ACCOUNT_URL, "status");
+    }
+
+    public static void verifyFixedDepositAccountIsPrematureClosed(HashMap fixedDepositStatusHashMap) {
+        System.out.println("--------------------- VERIFYING FIXED DEPOSIT APPLICATION IS CLOSED ---------------------");
+        assertTrue("ERROR IN PREMATURELY CLOSING THE FIXED DEPOSIT ACCOUNT", getStatus(fixedDepositStatusHashMap, "prematureClosed"));
+        System.out.println(fixedDepositStatusHashMap);
+    }
+
+    private static boolean getStatus(final HashMap fixedDepositStatusMap, final String fixedDepositStatusString) {
+        return (Boolean) fixedDepositStatusMap.get(fixedDepositStatusString);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java
new file mode 100644
index 0000000..2703340
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java
@@ -0,0 +1,254 @@
+/**
+ * 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.integrationtests.common.fixeddeposit;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.Account.AccountType;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes" })
+public class FixedDepositProductHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public FixedDepositProductHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    private static final String FIXED_DEPOSIT_PRODUCT_URL = "/fineract-provider/api/v1/fixeddepositproducts";
+    private static final String INTEREST_CHART_URL = "/fineract-provider/api/v1/interestratecharts";
+    private static final String CREATE_FIXED_DEPOSIT_PRODUCT_URL = FIXED_DEPOSIT_PRODUCT_URL + "?" + Utils.TENANT_IDENTIFIER;
+
+    private static final String LOCALE = "en_GB";
+    private static final String DIGITS_AFTER_DECIMAL = "4";
+    private static final String IN_MULTIPLES_OF = "100";
+    private static final String USD = "USD";
+    private static final String DAYS = "0";
+    private static final String WEEKS = "1";
+    private static final String MONTHS = "2";
+    private static final String YEARS = "3";
+    private static final String DAILY = "1";
+    private static final String MONTHLY = "4";
+    private static final String QUARTERLY = "5";
+    private static final String BI_ANNUALLY = "6";
+    private static final String ANNUALLY = "7";
+    private static final String INTEREST_CALCULATION_USING_DAILY_BALANCE = "1";
+    private static final String INTEREST_CALCULATION_USING_AVERAGE_DAILY_BALANCE = "2";
+    private static final String DAYS_360 = "360";
+    private static final String DAYS_365 = "365";
+    private static final String NONE = "1";
+    private static final String CASH_BASED = "2";
+    private static final String ACCRUAL_PERIODIC = "3";
+    private static final String ACCRUAL_UPFRONT = "4";
+    private static final String WHOLE_TERM = "1";
+    private static final String TILL_PREMATURE_WITHDRAWAL = "2";
+
+    private String name = Utils.randomNameGenerator("FIXED_DEPOSIT_PRODUCT_", 6);
+    private String shortName = Utils.randomNameGenerator("", 4);
+    private String description = Utils.randomNameGenerator("", 20);
+    private String interestCompoundingPeriodType = MONTHLY;
+    private String interestPostingPeriodType = MONTHLY;
+    private String interestCalculationType = INTEREST_CALCULATION_USING_DAILY_BALANCE;
+    private String accountingRule = NONE;
+    private String lockinPeriodFrequency = "1";
+    private String lockingPeriodFrequencyType = MONTHS;
+    private String minDepositTerm = "6";
+    private String minDepositTermTypeId = MONTHS;
+    private String maxDepositTerm = "10";
+    private String maxDepositTermTypeId = YEARS;
+    private String inMultiplesOfDepositTerm = "2";
+    private final String depositAmount = "100000";
+    private String inMultiplesOfDepositTermTypeId = MONTHS;
+    private String preClosurePenalInterest = "2";
+    private String preClosurePenalInterestOnTypeId = WHOLE_TERM;
+    private final boolean preClosurePenalApplicable = true;
+    private final String currencyCode = USD;
+    private final String interestCalculationDaysInYearType = DAYS_365;
+    private Account[] accountList = null;
+
+    public String build(final String validFrom, final String validTo) {
+        final HashMap<String, Object> map = new HashMap<>();
+
+        List<HashMap<String, String>> chartSlabs = new ArrayList<HashMap<String, String>>();
+        HashMap<String, String> chartSlabsMap1 = new HashMap<>();
+        chartSlabsMap1.put("description", "First");
+        chartSlabsMap1.put("periodType", MONTHS);
+        chartSlabsMap1.put("fromPeriod", "1");
+        chartSlabsMap1.put("toPeriod", "6");
+        chartSlabsMap1.put("annualInterestRate", "5");
+        chartSlabsMap1.put("locale", LOCALE);
+        chartSlabs.add(0, chartSlabsMap1);
+
+        HashMap<String, String> chartSlabsMap2 = new HashMap<>();
+        chartSlabsMap2.put("description", "Second");
+        chartSlabsMap2.put("periodType", MONTHS);
+        chartSlabsMap2.put("fromPeriod", "7");
+        chartSlabsMap2.put("toPeriod", "12");
+        chartSlabsMap2.put("annualInterestRate", "6");
+        chartSlabsMap2.put("locale", LOCALE);
+        chartSlabs.add(1, chartSlabsMap2);
+
+        HashMap<String, String> chartSlabsMap3 = new HashMap<>();
+        chartSlabsMap3.put("description", "Third");
+        chartSlabsMap3.put("periodType", MONTHS);
+        chartSlabsMap3.put("fromPeriod", "13");
+        chartSlabsMap3.put("toPeriod", "18");
+        chartSlabsMap3.put("annualInterestRate", "7");
+        chartSlabsMap3.put("locale", LOCALE);
+        chartSlabs.add(2, chartSlabsMap3);
+
+        HashMap<String, String> chartSlabsMap4 = new HashMap<>();
+        chartSlabsMap4.put("description", "Fourth");
+        chartSlabsMap4.put("periodType", MONTHS);
+        chartSlabsMap4.put("fromPeriod", "19");
+        chartSlabsMap4.put("toPeriod", "24");
+        chartSlabsMap4.put("annualInterestRate", "8");
+        chartSlabsMap4.put("locale", LOCALE);
+        chartSlabs.add(3, chartSlabsMap4);
+
+        List<HashMap<String, Object>> charts = new ArrayList<HashMap<String, Object>>();
+        HashMap<String, Object> chartsMap = new HashMap<>();
+        chartsMap.put("fromDate", validFrom);
+        chartsMap.put("endDate", validTo);
+        chartsMap.put("dateFormat", "dd MMMM yyyy");
+        chartsMap.put("locale", LOCALE);
+        chartsMap.put("chartSlabs", chartSlabs);
+        charts.add(chartsMap);
+
+        map.put("charts", charts);
+        map.put("name", this.name);
+        map.put("shortName", this.shortName);
+        map.put("description", this.description);
+        map.put("currencyCode", this.currencyCode);
+        map.put("interestCalculationDaysInYearType", this.interestCalculationDaysInYearType);
+        map.put("locale", LOCALE);
+        map.put("digitsAfterDecimal", DIGITS_AFTER_DECIMAL);
+        map.put("inMultiplesOf", IN_MULTIPLES_OF);
+        map.put("interestCalculationType", this.interestCalculationType);
+        map.put("interestCompoundingPeriodType", this.interestCompoundingPeriodType);
+        map.put("interestPostingPeriodType", this.interestPostingPeriodType);
+        map.put("accountingRule", this.accountingRule);
+        map.put("lockinPeriodFrequency", this.lockinPeriodFrequency);
+        map.put("lockinPeriodFrequencyType", this.lockingPeriodFrequencyType);
+        map.put("preClosurePenalApplicable", "true");
+        map.put("minDepositTermTypeId", this.minDepositTermTypeId);
+        map.put("minDepositTerm", this.minDepositTerm);
+        map.put("maxDepositTermTypeId", this.maxDepositTermTypeId);
+        map.put("maxDepositTerm", this.maxDepositTerm);
+        map.put("depositAmount", this.depositAmount);
+        map.put("preClosurePenalApplicable", this.preClosurePenalApplicable);
+        map.put("inMultiplesOfDepositTerm", this.inMultiplesOfDepositTerm);
+        map.put("inMultiplesOfDepositTermTypeId", this.inMultiplesOfDepositTermTypeId);
+        map.put("preClosurePenalInterest", this.preClosurePenalInterest);
+        map.put("preClosurePenalInterestOnTypeId", this.preClosurePenalInterestOnTypeId);
+
+        if (this.accountingRule.equals(CASH_BASED)) {
+            map.putAll(getAccountMappingForCashBased());
+        }
+
+        String FixedDepositProductCreateJson = new Gson().toJson(map);
+        System.out.println(FixedDepositProductCreateJson);
+        return FixedDepositProductCreateJson;
+    }
+
+    public FixedDepositProductHelper withAccountingRuleAsNone() {
+        this.accountingRule = NONE;
+        return this;
+    }
+
+    public FixedDepositProductHelper withAccountingRuleAsCashBased(final Account[] account_list) {
+        this.accountingRule = CASH_BASED;
+        this.accountList = account_list;
+        return this;
+    }
+
+    private Map<String, String> getAccountMappingForCashBased() {
+        final Map<String, String> map = new HashMap<>();
+        if (accountList != null) {
+            for (int i = 0; i < this.accountList.length; i++) {
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.ASSET)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("savingsReferenceAccountId", ID);
+                }
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("savingsControlAccountId", ID);
+                    map.put("transfersInSuspenseAccountId", ID);
+                }
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("interestOnSavingsAccountId", ID);
+                }
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.INCOME)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("incomeFromFeeAccountId", ID);
+                    map.put("incomeFromPenaltyAccountId", ID);
+                }
+            }
+        }
+        return map;
+    }
+
+    public static Integer createFixedDepositProduct(final String fixedDepositProductCreateJson, final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        System.out.println("--------------------- CREATING FIXED DEPOSIT PRODUCT ------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_FIXED_DEPOSIT_PRODUCT_URL, fixedDepositProductCreateJson,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public static ArrayList retrieveAllFixedDepositProducts(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        System.out.println("-------------------- RETRIEVING ALL FIXED DEPOSIT PRODUCTS ---------------------");
+        final ArrayList response = Utils.performServerGet(requestSpec, responseSpec, FIXED_DEPOSIT_PRODUCT_URL + "?"
+                + Utils.TENANT_IDENTIFIER, "");
+        return response;
+    }
+
+    public static HashMap retrieveFixedDepositProductById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String productId) {
+        System.out.println("------------------------ RETRIEVING FIXED DEPOSIT PRODUCT BY ID ------------------------");
+        final String GET_FD_PRODUCT_BY_ID_URL = FIXED_DEPOSIT_PRODUCT_URL + "/" + productId + "?" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_FD_PRODUCT_BY_ID_URL, "");
+        return response;
+    }
+
+    public static ArrayList getInterestRateChartSlabsByProductId(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final Integer productId) {
+        System.out.println("-------------------- RETRIEVE INTEREST CHART BY PRODUCT ID ---------------------");
+        final ArrayList response = Utils.performServerGet(requestSpec, responseSpec, INTEREST_CHART_URL + "?productId=" + productId,
+                "chartSlabs");
+        return response;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/funds/FundsHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/funds/FundsHelper.java
new file mode 100644
index 0000000..eed6ae5
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/funds/FundsHelper.java
@@ -0,0 +1,111 @@
+/**
+ * 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.integrationtests.common.funds;
+
+import com.google.gson.Gson;
+
+public class FundsHelper {
+
+    public static class Builder {
+
+        private String name;
+        private String externalId;
+
+        private Builder(final String name) {
+            this.name = name;
+        }
+
+        public Builder externalId(final String externalId) {
+            this.externalId = externalId;
+            return this;
+        }
+
+        public FundsHelper build() {
+            return new FundsHelper(this.name, this.externalId);
+        }
+
+    }
+
+    private String name;
+    private String externalId;
+    private Long resourceId;
+
+    FundsHelper() {
+        super();
+    }
+
+    private FundsHelper(final String name,
+                        final String externalId) {
+        super();
+        this.name = name;
+        this.externalId = externalId;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getExternalId() {
+        return this.externalId;
+    }
+
+    public Long getResourceId() {
+        return this.resourceId;
+    }
+
+    public String toJSON() {
+        return new Gson().toJson(this);
+    }
+
+    public static FundsHelper fromJSON(final String jsonData) {
+        return new Gson().fromJson(jsonData, FundsHelper.class);
+    }
+
+    public static Builder create(final String name) {
+        return new Builder(name);
+    }
+
+    @Override
+    public int hashCode() {
+        if (this.name != null) {
+            return this.name.hashCode();
+        }
+        return super.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (!(o instanceof FundsHelper)) {
+            return false;
+        }
+
+        FundsHelper fh = (FundsHelper)o;
+
+        if (this.name.equals(fh.name)) {
+            return true;
+        }
+
+        return false;
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/funds/FundsResourceHandler.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/funds/FundsResourceHandler.java
new file mode 100644
index 0000000..5d605fc
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/funds/FundsResourceHandler.java
@@ -0,0 +1,72 @@
+/**
+ * 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.integrationtests.common.funds;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class FundsResourceHandler {
+
+    private static final String FUNDS_URL = "/fineract-provider/api/v1/funds";
+    private static final String CREATE_FUNDS_URL = FUNDS_URL + "?" + Utils.TENANT_IDENTIFIER;
+
+    public static Integer createFund(final String fundJSON,
+                                     final RequestSpecification requestSpec,
+                                     final ResponseSpecification responseSpec) {
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_FUNDS_URL, fundJSON, "resourceId");
+    }
+
+    public static List<FundsHelper> retrieveAllFunds(final RequestSpecification requestSpec,
+                                                                 final ResponseSpecification responseSpec) {
+        final String URL = FUNDS_URL + "?" + Utils.TENANT_IDENTIFIER;
+        List<HashMap<String, Object>> list = Utils.performServerGet(requestSpec, responseSpec, URL, "");
+        final String jsonData = new Gson().toJson(list);
+        return new Gson().fromJson(jsonData, new TypeToken<List<FundsHelper>>(){}.getType());
+    }
+
+    public static String retrieveFund(final Long fundID,
+                                      final RequestSpecification requestSpec,
+                                      final ResponseSpecification responseSpec) {
+        final String URL = FUNDS_URL + "/" + fundID + "?" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "");
+        return new Gson().toJson(response);
+    }
+
+    public static FundsHelper updateFund(final Long fundID,
+                                         final String newName,
+                                         final String newExternalId,
+                                         final RequestSpecification requestSpec,
+                                         final ResponseSpecification responseSpec) {
+        FundsHelper fh = FundsHelper.create(newName).externalId(newExternalId).build();
+        String updateJSON = new Gson().toJson(fh);
+
+        final String URL = FUNDS_URL + "/" + fundID + "?" + Utils.TENANT_IDENTIFIER;
+        final HashMap<String, String> response = Utils.performServerPut(requestSpec, responseSpec, URL, updateJSON, "changes");
+        final String jsonData = new Gson().toJson(response);
+        return new Gson().fromJson(jsonData, FundsHelper.class);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
new file mode 100644
index 0000000..cfa9c39
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
@@ -0,0 +1,330 @@
+/**
+ * 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.integrationtests.common.loans;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import com.google.gson.Gson;
+
+public class LoanApplicationTestBuilder {
+
+    private static final String DAYS = "0";
+    private static final String WEEKS = "1";
+    private static final String MONTHS = "2";
+    private static final String YEARS = "3";
+    private static final String DECLINING_BALANCE = "0";
+    private static final String FLAT_BALANCE = "1";
+    private static final String EQUAL_PRINCIPAL_PAYMENTS = "0";
+    private static final String EQUAL_INSTALLMENTS = "1";
+    private static final String CALCULATION_PERIOD_SAME_AS_REPAYMENT_PERIOD = "1";
+    public static final String DEFAULT_STRATEGY = "1";
+    public static final String RBI_INDIA_STRATEGY = "4";
+
+    private String principal = "10,000";
+    private String loanTermFrequency = "";
+    private String loanTermFrequencyType = "";
+    private String numberOfRepayment = "0";
+    private String repaymentPeriod = "0";
+    private String repaymentFrequencyType = "";
+
+    private String interestRate = "2";
+    private String interestType = FLAT_BALANCE;
+    private String amortizationType = EQUAL_PRINCIPAL_PAYMENTS;
+    private String interestCalculationPeriodType = CALCULATION_PERIOD_SAME_AS_REPAYMENT_PERIOD;
+    private String transactionProcessingID = DEFAULT_STRATEGY;
+    private String expectedDisbursmentDate = "";
+    private String submittedOnDate = "";
+    private String loanType = "individual";
+    private String fixedEmiAmount = "10000";
+    private String maxOutstandingLoanBalance = "36000";
+    private String graceOnPrincipalPayment = null;
+    private String graceOnInterestPayment = null;
+    @SuppressWarnings("rawtypes")
+    private List<HashMap> disbursementData = null;
+    @SuppressWarnings("rawtypes")
+    private List<HashMap> charges = new ArrayList<>();
+    private String recalculationRestFrequencyDate = null;
+    private String recalculationCompoundingFrequencyDate = null;
+    private String repaymentsStartingFromDate = null;
+
+    private String calendarId;
+    private boolean syncDisbursementWithMeeting = false;
+
+    public String build(final String clientID, final String groupID, final String loanProductId, final String savingsID) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("groupId", groupID);
+        map.put("clientId", clientID);
+        if (this.loanType == "jlg") {
+            if (this.calendarId != null) {
+                map.put("calendarId", this.calendarId);
+            }
+            map.put("syncDisbursementWithMeeting", this.syncDisbursementWithMeeting);
+        }
+        return build(map, loanProductId, savingsID);
+    }
+
+    public String build(final String ID, final String loanProductId, final String savingsID) {
+
+        final HashMap<String, Object> map = new HashMap<>();
+
+        if (this.loanType == "group") {
+            map.put("groupId", ID);
+        } else {
+            map.put("clientId", ID);
+        }
+        return build(map, loanProductId, savingsID);
+    }
+
+    private String build(final HashMap<String, Object> map, final String loanProductId, final String savingsID) {
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en_GB");
+        map.put("productId", loanProductId);
+        map.put("principal", this.principal);
+        map.put("loanTermFrequency", this.loanTermFrequency);
+        map.put("loanTermFrequencyType", this.loanTermFrequencyType);
+        map.put("numberOfRepayments", this.numberOfRepayment);
+        map.put("repaymentEvery", this.repaymentPeriod);
+        map.put("repaymentFrequencyType", this.repaymentFrequencyType);
+        map.put("interestRatePerPeriod", this.interestRate);
+        map.put("amortizationType", this.amortizationType);
+        map.put("interestType", this.interestType);
+        map.put("interestCalculationPeriodType", this.interestCalculationPeriodType);
+        map.put("transactionProcessingStrategyId", this.transactionProcessingID);
+        map.put("expectedDisbursementDate", this.expectedDisbursmentDate);
+        map.put("submittedOnDate", this.submittedOnDate);
+        map.put("loanType", this.loanType);
+        if (repaymentsStartingFromDate != null) {
+            map.put("repaymentsStartingFromDate", this.repaymentsStartingFromDate);
+        }
+        if (charges != null) {
+            map.put("charges", charges);
+        }
+        if (savingsID != null) {
+            map.put("linkAccountId", savingsID);
+        }
+
+        if (graceOnPrincipalPayment != null) {
+            map.put("graceOnPrincipalPayment", graceOnPrincipalPayment);
+        }
+
+        if (graceOnInterestPayment != null) {
+            map.put("graceOnInterestPayment", graceOnInterestPayment);
+        }
+
+        if (disbursementData != null) {
+            map.put("disbursementData", disbursementData);
+            map.put("fixedEmiAmount", fixedEmiAmount);
+            map.put("maxOutstandingLoanBalance", maxOutstandingLoanBalance);
+
+        }
+        if (recalculationRestFrequencyDate != null) {
+            map.put("recalculationRestFrequencyDate", recalculationRestFrequencyDate);
+        }
+        if (recalculationCompoundingFrequencyDate != null) {
+            map.put("recalculationCompoundingFrequencyDate", recalculationCompoundingFrequencyDate);
+        }
+
+        System.out.println("Loan Application request : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public LoanApplicationTestBuilder withPrincipal(final String principalAmount) {
+        this.principal = principalAmount;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withLoanTermFrequency(final String loanToBePayedDuration) {
+        this.loanTermFrequency = loanToBePayedDuration;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withLoanTermFrequencyAsDays() {
+        this.loanTermFrequencyType = DAYS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withLoanTermFrequencyAsMonths() {
+        this.loanTermFrequencyType = MONTHS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withLoanTermFrequencyAsWeeks() {
+        this.loanTermFrequencyType = WEEKS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withLoanTermFrequencyAsYears() {
+        this.loanTermFrequencyType = YEARS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withNumberOfRepayments(final String numberOfRepayments) {
+        this.numberOfRepayment = numberOfRepayments;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withRepaymentEveryAfter(final String repaymentPeriod) {
+        this.repaymentPeriod = repaymentPeriod;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withRepaymentFrequencyTypeAsDays() {
+        this.repaymentFrequencyType = DAYS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withRepaymentFrequencyTypeAsMonths() {
+        this.repaymentFrequencyType = MONTHS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withRepaymentFrequencyTypeAsWeeks() {
+        this.repaymentFrequencyType = WEEKS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withRepaymentFrequencyTypeAsYear() {
+        this.repaymentFrequencyType = YEARS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withInterestRatePerPeriod(final String interestRate) {
+        this.interestRate = interestRate;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withInterestTypeAsFlatBalance() {
+        this.interestType = FLAT_BALANCE;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withInterestTypeAsDecliningBalance() {
+        this.interestType = DECLINING_BALANCE;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withAmortizationTypeAsEqualInstallments() {
+        this.amortizationType = EQUAL_INSTALLMENTS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withAmortizationTypeAsEqualPrincipalPayments() {
+        this.amortizationType = EQUAL_PRINCIPAL_PAYMENTS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withInterestCalculationPeriodTypeSameAsRepaymentPeriod() {
+        this.interestCalculationPeriodType = CALCULATION_PERIOD_SAME_AS_REPAYMENT_PERIOD;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withInterestCalculationPeriodTypeAsDays() {
+        this.interestCalculationPeriodType = DAYS;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withExpectedDisbursementDate(final String expectedDisbursementDate) {
+        this.expectedDisbursmentDate = expectedDisbursementDate;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withSubmittedOnDate(final String loanApplicationSubmittedDate) {
+        this.submittedOnDate = loanApplicationSubmittedDate;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withCharges(final List<HashMap> charges) {
+        this.charges = charges;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withLoanType(final String loanType) {
+        this.loanType = loanType;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withPrincipalGrace(final String graceOnPrincipalPayment) {
+        this.graceOnPrincipalPayment = graceOnPrincipalPayment;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withInterestGrace(final String graceOnInterestPayment) {
+        this.graceOnInterestPayment = graceOnInterestPayment;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withTranches(final List<HashMap> disbursementData) {
+        this.disbursementData = disbursementData;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withwithRepaymentStrategy(final String transactionProcessingStrategy) {
+        this.transactionProcessingID = transactionProcessingStrategy;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withRestFrequencyDate(final String recalculationRestFrequencyDate) {
+        this.recalculationRestFrequencyDate = recalculationRestFrequencyDate;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withCompoundingFrequencyDate(final String recalculationCompoundingFrequencyDate) {
+        this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withFirstRepaymentDate(final String firstRepaymentDate) {
+        this.repaymentsStartingFromDate = firstRepaymentDate;
+        return this;
+    }
+
+    /**
+     * calendarID parameter is used to sync repayments with group meetings,
+     * especially when using jlg loans
+     *
+     * @param calendarId
+     *            the id of the calender record of the group meeting from
+     *            m_calendar table
+     * @return
+     */
+    public LoanApplicationTestBuilder withCalendarID(String calendarId) {
+        this.calendarId = calendarId;
+        return this;
+    }
+
+    /**
+     * This indicator is used mainly for jlg loans when we want to sync
+     * disbursement with the group meetings (it seems that if we do use this
+     * parameter we should also use calendarID to sync repayment with group
+     * meetings)
+     * 
+     * @return
+     */
+    public LoanApplicationTestBuilder withSyncDisbursementWithMeetin() {
+        this.syncDisbursementWithMeeting = true;
+        return this;
+    }
+
+    public LoanApplicationTestBuilder withFixedEmiAmount(final String installmentAmount) {
+        this.fixedEmiAmount = installmentAmount;
+        return this;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanDisbursementTestBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanDisbursementTestBuilder.java
new file mode 100644
index 0000000..339f287
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanDisbursementTestBuilder.java
@@ -0,0 +1,108 @@
+/**
+ * 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.integrationtests.common.loans;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+
+public class LoanDisbursementTestBuilder {
+	
+	String dueDate = null;
+	Float totalOriginalDueForPeriod = null;
+	Float totalOutstandingForPeriod = null;
+	Float interestOutstanding = null;
+	Float principalOutstanding = null;
+	Float principalLoanBalanceOutstanding = null;
+	Float principalDue = null;
+	Float principalOriginalDue = null;
+	String fromDate = null; 
+	
+	public LoanDisbursementTestBuilder(String dueDate,Float totalOriginalDueForPeriod,Float totalOutstandingForPeriod,
+			Float interestOutstanding, Float principalOutstanding, Float principalLoanBalanceOutstanding, Float principalDue,
+			Float principalOriginalDue, String fromDate) {
+		this.dueDate = dueDate;
+		this.totalOriginalDueForPeriod = totalOriginalDueForPeriod;
+		this.totalOutstandingForPeriod = totalOutstandingForPeriod;
+		this.interestOutstanding = interestOutstanding;
+		this.principalOutstanding = principalOutstanding;
+		this.principalLoanBalanceOutstanding = principalLoanBalanceOutstanding;
+		this.principalDue = principalDue;
+		this.principalOriginalDue = principalOriginalDue;
+		this.fromDate = fromDate; 
+	}
+
+	public String getDueDate() {
+		return this.dueDate;
+	}
+
+	public Float getTotalOriginalDueForPeriod() {
+		return this.totalOriginalDueForPeriod;
+	}
+
+	public Float getTotalOutstandingForPeriod() {
+		return this.totalOutstandingForPeriod;
+	}
+
+	public Float getInterestOutstanding() {
+		return this.interestOutstanding;
+	}
+
+	public Float getPrincipalOutstanding() {
+		return this.principalOutstanding;
+	}
+
+	public Float getPrincipalLoanBalanceOutstanding() {
+		return this.principalLoanBalanceOutstanding;
+	}
+
+	public Float getPrincipalDue() {
+		return this.principalDue;
+	}
+
+	public Float getPrincipalOriginalDue() {
+		return this.principalOriginalDue;
+	}
+
+	public String getFromDate() {
+		return this.fromDate;
+	}
+	
+	/*public HashMap<String, String> build(String dueDate,String totalOriginalDueForPeriod,String totalOutstandingForPeriod,
+			String interestOutstanding, String principalOutstanding, String principalLoanBalanceOutstanding, String principalDue,
+			String principalOriginalDue, String fromDate) {
+		HashMap<String, String> expectedRepaymentSchedule = new HashMap<String, String>();
+		expectedRepaymentSchedule.put("dueDate", dueDate);
+		expectedRepaymentSchedule.put("totalOriginalDueForPeriod",
+				totalOriginalDueForPeriod);
+		expectedRepaymentSchedule.put("totalOutstandingForPeriod",
+				totalOutstandingForPeriod);
+		expectedRepaymentSchedule.put("interestOutstanding", interestOutstanding);
+		expectedRepaymentSchedule.put("principalOutstanding",
+				principalOutstanding);
+		expectedRepaymentSchedule.put("principalLoanBalanceOutstanding",
+				principalLoanBalanceOutstanding);
+		expectedRepaymentSchedule.put("principalDue", principalDue);
+		expectedRepaymentSchedule.put("principalOriginalDue",
+				principalOriginalDue);
+		expectedRepaymentSchedule.put("fromDate", fromDate);
+		
+		return expectedRepaymentSchedule;
+	}*/
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
new file mode 100644
index 0000000..3b4c767
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
@@ -0,0 +1,493 @@
+/**
+ * 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.integrationtests.common.loans;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+public class LoanProductTestBuilder {
+
+    private static final String LOCALE = "en_GB";
+    private static final String USD = "USD";
+    private static final String DAYS = "0";
+    private static final String WEEK = "1";
+    private static final String MONTHS = "2";
+    private static final String YEARS = "3";
+    private static final String CALCULATION_PERIOD_SAME_AS_REPAYMENT_PERIOD = "1";
+    private static final String EQUAL_PRINCIPAL_PAYMENTS = "0";
+    private static final String EQUAL_INSTALLMENTS = "1";
+    private static final String DECLINING_BALANCE = "0";
+    private static final String FLAT_BALANCE = "1";
+    public static final String DEFAULT_STRATEGY = "1";
+    // private static final String HEAVENS_FAMILY_STRATEGY ="2";
+    // private static final String CREO_CORE_STRATEGY ="3";
+    public static final String RBI_INDIA_STRATEGY = "4";
+
+    public static final String RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD = "1";
+    public static final String RECALCULATION_FREQUENCY_TYPE_DAILY = "2";
+    public static final String RECALCULATION_FREQUENCY_TYPE_WEEKLY = "3";
+    public static final String RECALCULATION_FREQUENCY_TYPE_MONTHLY = "4";
+
+    public static final String RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS = "1";
+    public static final String RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS = "2";
+    public static final String RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN = "3";
+
+    public static final String RECALCULATION_COMPOUNDING_METHOD_NONE = "0";
+    public static final String RECALCULATION_COMPOUNDING_METHOD_INTEREST = "1";
+    public static final String RECALCULATION_COMPOUNDING_METHOD_FEE = "2";
+    public static final String RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE = "3";
+
+    private static final String NONE = "1";
+    private static final String CASH_BASED = "2";
+    private static final String ACCRUAL_PERIODIC = "3";
+    private static final String ACCRUAL_UPFRONT = "4";
+
+    public static final String INTEREST_APPLICABLE_STRATEGY_REST_DATE = "2";
+    public static final String INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE = "1";
+
+    private String digitsAfterDecimal = "2";
+    private String inMultiplesOf = "0";
+
+    private String nameOfLoanProduct = Utils.randomNameGenerator("LOAN_PRODUCT_", 6);
+    private final String shortName = Utils.randomNameGenerator("", 4);
+    private String principal = "10000.00";
+    private String numberOfRepayments = "5";
+    private String repaymentFrequency = MONTHS;
+    private String repaymentPeriod = "1";
+    private String interestRatePerPeriod = "2";
+    private String interestRateFrequencyType = MONTHS;
+    private String interestType = FLAT_BALANCE;
+    private String overdueDaysForNPA = "5";
+    private String interestCalculationPeriodType = CALCULATION_PERIOD_SAME_AS_REPAYMENT_PERIOD;
+    private String inArrearsTolerance = "0";
+    private String transactionProcessingStrategy = DEFAULT_STRATEGY;
+    private String accountingRule = NONE;
+    private final String currencyCode = USD;
+    private String amortizationType = EQUAL_INSTALLMENTS;
+    private String minPrincipal = "1000.00";
+    private String maxPrincipal = "10000000.00";
+    private Account[] accountList = null;
+
+    private Boolean multiDisburseLoan = false;
+    private final String outstandingLoanBalance = "35000";
+    private final String maxTrancheCount = "3";
+
+    private Boolean isInterestRecalculationEnabled = false;
+    private String daysInYearType = "1";
+    private String daysInMonthType = "1";
+    private String interestRecalculationCompoundingMethod = "0";
+    private String preCloseInterestCalculationStrategy = INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE;
+    private String rescheduleStrategyMethod = "1";
+    private String recalculationRestFrequencyType = "1";
+    private String recalculationRestFrequencyInterval = "0";
+    private String recalculationRestFrequencyDate = null;
+    private String recalculationCompoundingFrequencyType = null;
+    private String recalculationCompoundingFrequencyInterval = null;
+    private String recalculationCompoundingFrequencyDate = null;
+    private String minimumDaysBetweenDisbursalAndFirstRepayment = null;
+    private Boolean holdGuaranteeFunds = null;
+    private String mandatoryGuarantee = null;
+    private String minimumGuaranteeFromOwnFunds = null;
+    private String minimumGuaranteeFromGuarantor = null;
+    private String isArrearsBasedOnOriginalSchedule = null;
+    private String graceOnPrincipalPayment = "1";
+    private String graceOnInterestPayment = "1";
+    private JsonObject allowAttributeOverrides = null;
+    private Boolean allowPartialPeriodInterestCalcualtion = false;
+    
+    private Boolean allowVariableInstallments = Boolean.FALSE;
+    private Integer minimumGap;
+    private Integer maximumGap;
+
+    public String build(final String chargeId) {
+        final HashMap<String, Object> map = new HashMap<>();
+
+        if (chargeId != null) {
+            List<HashMap<String, String>> charges = new ArrayList<>();
+            HashMap<String, String> chargeMap = new HashMap<>();
+            chargeMap.put("id", chargeId);
+            charges.add(chargeMap);
+            map.put("charges", charges);
+        }
+        map.put("name", this.nameOfLoanProduct);
+        map.put("shortName", this.shortName);
+        map.put("currencyCode", this.currencyCode);
+        map.put("locale", LOCALE);
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("digitsAfterDecimal", digitsAfterDecimal);
+        map.put("inMultiplesOf", inMultiplesOf);
+        map.put("principal", this.principal);
+        map.put("numberOfRepayments", this.numberOfRepayments);
+        map.put("repaymentEvery", this.repaymentPeriod);
+        map.put("repaymentFrequencyType", this.repaymentFrequency);
+        map.put("interestRatePerPeriod", this.interestRatePerPeriod);
+        map.put("interestRateFrequencyType", this.interestRateFrequencyType);
+        map.put("amortizationType", this.amortizationType);
+        map.put("interestType", this.interestType);
+        map.put("interestCalculationPeriodType", this.interestCalculationPeriodType);
+        map.put("inArrearsTolerance", this.inArrearsTolerance);
+        map.put("transactionProcessingStrategyId", this.transactionProcessingStrategy);
+        map.put("accountingRule", this.accountingRule);
+        map.put("minPrincipal", this.minPrincipal);
+        map.put("maxPrincipal", this.maxPrincipal);
+        map.put("overdueDaysForNPA", this.overdueDaysForNPA);
+        if (this.minimumDaysBetweenDisbursalAndFirstRepayment != null) {
+            map.put("minimumDaysBetweenDisbursalAndFirstRepayment", this.minimumDaysBetweenDisbursalAndFirstRepayment);
+        }
+        if (multiDisburseLoan) {
+            map.put("multiDisburseLoan", this.multiDisburseLoan);
+            map.put("maxTrancheCount", this.maxTrancheCount);
+            map.put("outstandingLoanBalance", this.outstandingLoanBalance);
+        }
+
+        if (this.accountingRule.equals(ACCRUAL_UPFRONT) || this.accountingRule.equals(ACCRUAL_PERIODIC)) {
+            map.putAll(getAccountMappingForAccrualBased());
+        } else if (this.accountingRule.equals(CASH_BASED)) {
+            map.putAll(getAccountMappingForCashBased());
+        }
+        map.put("daysInMonthType", this.daysInMonthType);
+        map.put("daysInYearType", this.daysInYearType);
+        map.put("isInterestRecalculationEnabled", this.isInterestRecalculationEnabled);
+        if (this.isInterestRecalculationEnabled) {
+            map.put("interestRecalculationCompoundingMethod", this.interestRecalculationCompoundingMethod);
+            map.put("rescheduleStrategyMethod", this.rescheduleStrategyMethod);
+            map.put("recalculationRestFrequencyType", recalculationRestFrequencyType);
+            map.put("recalculationRestFrequencyInterval", recalculationRestFrequencyInterval);
+            map.put("recalculationRestFrequencyDate", recalculationRestFrequencyDate);
+            if (!RECALCULATION_COMPOUNDING_METHOD_NONE.equals(this.interestRecalculationCompoundingMethod)) {
+                map.put("recalculationCompoundingFrequencyType", recalculationCompoundingFrequencyType);
+                map.put("recalculationCompoundingFrequencyInterval", recalculationCompoundingFrequencyInterval);
+                map.put("recalculationCompoundingFrequencyDate", recalculationCompoundingFrequencyDate);
+            }
+            map.put("preClosureInterestCalculationStrategy", preCloseInterestCalculationStrategy);
+            if (isArrearsBasedOnOriginalSchedule != null) {
+                map.put("isArrearsBasedOnOriginalSchedule", isArrearsBasedOnOriginalSchedule);
+            }
+        }
+        if (holdGuaranteeFunds != null) {
+            map.put("holdGuaranteeFunds", this.holdGuaranteeFunds);
+            if (this.holdGuaranteeFunds) {
+                map.put("mandatoryGuarantee", this.mandatoryGuarantee);
+                map.put("minimumGuaranteeFromGuarantor", this.minimumGuaranteeFromGuarantor);
+                map.put("minimumGuaranteeFromOwnFunds", this.minimumGuaranteeFromOwnFunds);
+            }
+        }
+        map.put("graceOnPrincipalPayment", graceOnPrincipalPayment);
+        map.put("graceOnInterestPayment", graceOnInterestPayment);
+        if (allowAttributeOverrides != null) {
+            map.put("allowAttributeOverrides", this.allowAttributeOverrides);
+        }
+        map.put("allowPartialPeriodInterestCalcualtion", this.allowPartialPeriodInterestCalcualtion);
+        map.put("allowVariableInstallments", allowVariableInstallments) ;
+        if(allowVariableInstallments) {
+            map.put("minimumGap", minimumGap) ;
+            map.put("maximumGap", maximumGap) ;
+        }
+        return new Gson().toJson(map);
+    }
+
+    public LoanProductTestBuilder withMinPrincipal(final String minPrincipal) {
+        this.minPrincipal = minPrincipal;
+        return this;
+    }
+
+    public LoanProductTestBuilder withMaxPrincipal(final String maxPrincipal) {
+        this.maxPrincipal = maxPrincipal;
+        return this;
+    }
+
+    public LoanProductTestBuilder withLoanName(final String loanName) {
+        this.nameOfLoanProduct = loanName;
+        return this;
+    }
+
+    public LoanProductTestBuilder withPrincipal(final String principal) {
+        this.principal = principal;
+        return this;
+    }
+
+    public LoanProductTestBuilder withNumberOfRepayments(final String numberOfRepayment) {
+        this.numberOfRepayments = numberOfRepayment;
+        return this;
+    }
+
+    public LoanProductTestBuilder withRepaymentTypeAsMonth() {
+        this.repaymentFrequency = MONTHS;
+        return this;
+    }
+
+    public LoanProductTestBuilder withRepaymentTypeAsWeek() {
+        this.repaymentFrequency = WEEK;
+        return this;
+    }
+
+    public LoanProductTestBuilder withRepaymentTypeAsDays() {
+        this.repaymentFrequency = DAYS;
+        return this;
+    }
+
+    public LoanProductTestBuilder withRepaymentAfterEvery(final String repaymentAfterEvery) {
+        this.repaymentPeriod = repaymentAfterEvery;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInterestRateFrequencyTypeAsMonths() {
+        this.interestRateFrequencyType = MONTHS;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInterestRateFrequencyTypeAsYear() {
+        this.interestRateFrequencyType = YEARS;
+        return this;
+    }
+
+    public LoanProductTestBuilder withinterestRatePerPeriod(final String interestRatePerPeriod) {
+        this.interestRatePerPeriod = interestRatePerPeriod;
+        return this;
+    }
+
+    public LoanProductTestBuilder withAmortizationTypeAsEqualPrincipalPayment() {
+        this.amortizationType = EQUAL_PRINCIPAL_PAYMENTS;
+        return this;
+    }
+
+    public LoanProductTestBuilder withAmortizationTypeAsEqualInstallments() {
+        this.amortizationType = EQUAL_INSTALLMENTS;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInterestTypeAsFlat() {
+        this.interestType = FLAT_BALANCE;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInterestTypeAsDecliningBalance() {
+        this.interestType = DECLINING_BALANCE;
+        return this;
+    }
+
+    public LoanProductTestBuilder withOverdueDaysForNPA(String days) {
+        this.overdueDaysForNPA = days;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInterestCalculationPeriodTypeAsDays() {
+        this.interestCalculationPeriodType = DAYS;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInterestCalculationPeriodTypeAsRepaymentPeriod(final Boolean allowPartialPeriodInterestCalcualtion) {
+        this.interestCalculationPeriodType = CALCULATION_PERIOD_SAME_AS_REPAYMENT_PERIOD;
+        this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInArrearsTolerance(final String amountCanBeWaved) {
+        this.inArrearsTolerance = amountCanBeWaved;
+        return this;
+    }
+
+    public LoanProductTestBuilder withAccountingRuleAsNone() {
+        this.accountingRule = NONE;
+        return this;
+    }
+
+    public LoanProductTestBuilder withAccountingRuleAsCashBased(final Account[] account_list) {
+        this.accountingRule = CASH_BASED;
+        this.accountList = account_list;
+        return this;
+    }
+
+    public LoanProductTestBuilder withAccountingRuleUpfrontAccrual(final Account[] account_list) {
+        this.accountingRule = ACCRUAL_UPFRONT;
+        this.accountList = account_list;
+        return this;
+    }
+
+    public LoanProductTestBuilder withAccountingRulePeriodicAccrual(final Account[] account_list) {
+        this.accountingRule = ACCRUAL_PERIODIC;
+        this.accountList = account_list;
+        return this;
+    }
+
+    public LoanProductTestBuilder withTranches(boolean multiDisburseLoan) {
+        this.multiDisburseLoan = multiDisburseLoan;
+        return this;
+    }
+
+    private Map<String, String> getAccountMappingForCashBased() {
+        final Map<String, String> map = new HashMap<>();
+        for (int i = 0; i < this.accountList.length; i++) {
+            if (this.accountList[i].getAccountType().equals(Account.AccountType.ASSET)) {
+                final String ID = this.accountList[i].getAccountID().toString();
+                map.put("fundSourceAccountId", ID);
+                map.put("loanPortfolioAccountId", ID);
+                map.put("transfersInSuspenseAccountId", ID);
+            }
+            if (this.accountList[i].getAccountType().equals(Account.AccountType.INCOME)) {
+                final String ID = this.accountList[i].getAccountID().toString();
+                map.put("interestOnLoanAccountId", ID);
+                map.put("incomeFromFeeAccountId", ID);
+                map.put("incomeFromPenaltyAccountId", ID);
+                map.put("incomeFromRecoveryAccountId", ID);
+            }
+            if (this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) {
+                final String ID = this.accountList[i].getAccountID().toString();
+                map.put("writeOffAccountId", ID);
+            }
+            if (this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) {
+                final String ID = this.accountList[i].getAccountID().toString();
+                map.put("overpaymentLiabilityAccountId", ID);
+            }
+        }
+        return map;
+    }
+
+    private Map<String, String> getAccountMappingForAccrualBased() {
+        final Map<String, String> map = new HashMap<>();
+        for (int i = 0; i < this.accountList.length; i++) {
+            if (this.accountList[i].getAccountType().equals(Account.AccountType.ASSET)) {
+                final String ID = this.accountList[i].getAccountID().toString();
+                map.put("fundSourceAccountId", ID);
+                map.put("loanPortfolioAccountId", ID);
+                map.put("transfersInSuspenseAccountId", ID);
+                map.put("receivableInterestAccountId", ID);
+                map.put("receivableFeeAccountId", ID);
+                map.put("receivablePenaltyAccountId", ID);
+
+            }
+            if (this.accountList[i].getAccountType().equals(Account.AccountType.INCOME)) {
+                final String ID = this.accountList[i].getAccountID().toString();
+                map.put("interestOnLoanAccountId", ID);
+                map.put("incomeFromFeeAccountId", ID);
+                map.put("incomeFromPenaltyAccountId", ID);
+                map.put("incomeFromRecoveryAccountId", ID);
+            }
+            if (this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) {
+                final String ID = this.accountList[i].getAccountID().toString();
+                map.put("writeOffAccountId", ID);
+            }
+            if (this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) {
+                final String ID = this.accountList[i].getAccountID().toString();
+                map.put("overpaymentLiabilityAccountId", ID);
+            }
+        }
+
+        return map;
+    }
+
+    public LoanProductTestBuilder withAccounting(final String accountingRule, final Account[] account_list) {
+        this.accountingRule = accountingRule;
+        this.accountList = account_list;
+        return this;
+    }
+
+    public LoanProductTestBuilder currencyDetails(final String digitsAfterDecimal, final String inMultiplesOf) {
+        this.digitsAfterDecimal = digitsAfterDecimal;
+        this.inMultiplesOf = inMultiplesOf;
+        return this;
+    }
+
+    public LoanProductTestBuilder withRepaymentStrategy(final String transactionProcessingStrategy) {
+        this.transactionProcessingStrategy = transactionProcessingStrategy;
+        return this;
+    }
+
+    public LoanProductTestBuilder withDaysInMonth(final String daysInMonthType) {
+        this.daysInMonthType = daysInMonthType;
+        return this;
+    }
+
+    public LoanProductTestBuilder withDaysInYear(final String daysInYearType) {
+        this.daysInYearType = daysInYearType;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInterestRecalculationDetails(final String interestRecalculationCompoundingMethod,
+            final String rescheduleStrategyMethod, String preCloseInterestCalculationStrategy) {
+        this.isInterestRecalculationEnabled = true;
+        this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
+        this.rescheduleStrategyMethod = rescheduleStrategyMethod;
+        this.preCloseInterestCalculationStrategy = preCloseInterestCalculationStrategy;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInterestRecalculationRestFrequencyDetails(final String recalculationRestFrequencyType,
+            final String recalculationRestFrequencyInterval, final String recalculationRestFrequencyDate) {
+        this.isInterestRecalculationEnabled = true;
+        this.recalculationRestFrequencyType = recalculationRestFrequencyType;
+        this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval;
+        this.recalculationRestFrequencyDate = recalculationRestFrequencyDate;
+        return this;
+    }
+
+    public LoanProductTestBuilder withInterestRecalculationCompoundingFrequencyDetails(final String recalculationCompoundingFrequencyType,
+            final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate) {
+        this.isInterestRecalculationEnabled = true;
+        this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType;
+        this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval;
+        this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate;
+        return this;
+    }
+
+    public LoanProductTestBuilder withMinimumDaysBetweenDisbursalAndFirstRepayment(final String minimumDaysBetweenDisbursalAndFirstRepayment) {
+        this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
+        return this;
+    }
+
+    public LoanProductTestBuilder withArrearsConfiguration() {
+        this.isArrearsBasedOnOriginalSchedule = "true";
+        return this;
+    }
+
+    public LoanProductTestBuilder withOnHoldFundDetails(final String mandatoryGuarantee, final String minimumGuaranteeFromGuarantor,
+            final String minimumGuaranteeFromOwnFunds) {
+        this.holdGuaranteeFunds = true;
+        this.mandatoryGuarantee = mandatoryGuarantee;
+        this.minimumGuaranteeFromGuarantor = minimumGuaranteeFromGuarantor;
+        this.minimumGuaranteeFromOwnFunds = minimumGuaranteeFromOwnFunds;
+        return this;
+    }
+
+    public LoanProductTestBuilder withMoratorium(String principal, String interest) {
+        this.graceOnPrincipalPayment = principal;
+        this.graceOnInterestPayment = interest;
+        return this;
+    }
+
+    public LoanProductTestBuilder withLoanProductConfiguration(JsonObject loanProductConfigurableAttributes) {
+        this.allowAttributeOverrides = loanProductConfigurableAttributes;
+        return this;
+    }
+    
+    public LoanProductTestBuilder withVariableInstallmentsConfig(Boolean allowVariableInstallments, Integer minimumGap, Integer maximumGap) {
+        this.allowVariableInstallments = allowVariableInstallments ;
+        this.minimumGap = minimumGap;
+        this.maximumGap = maximumGap;
+        return this ;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanRescheduleRequestTestBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanRescheduleRequestTestBuilder.java
new file mode 100644
index 0000000..0c8124f
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanRescheduleRequestTestBuilder.java
@@ -0,0 +1,155 @@
+/**
+ * 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.integrationtests.common.loans;
+
+import java.util.HashMap;
+
+import com.google.gson.Gson;
+
+public class LoanRescheduleRequestTestBuilder {
+
+    private String rescheduleFromDate = "04 December 2014";
+    private String graceOnPrincipal = "2";
+    private String graceOnInterest = "2";
+    private String extraTerms = "2";
+    private Boolean recalculateInterest = false;
+    private String newInterestRate = null;
+    private String adjustedDueDate = null;
+    private String rescheduleReasonId = "1";
+    private String rescheduleReasonComment = null;
+    private String submittedOnDate = "04 September 2014";
+
+    public String build(final String loanId) {
+        final HashMap<String, Object> map = new HashMap<>();
+
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en_GB");
+        map.put("loanId", loanId);
+        map.put("submittedOnDate", submittedOnDate);
+        map.put("rescheduleFromDate", rescheduleFromDate);
+
+        if (graceOnPrincipal != null) {
+            map.put("graceOnPrincipal", graceOnPrincipal);
+        }
+
+        if (graceOnInterest != null) {
+            map.put("graceOnInterest", graceOnInterest);
+        }
+
+        if (extraTerms != null) {
+            map.put("extraTerms", extraTerms);
+        }
+
+        map.put("recalculateInterest", recalculateInterest);
+
+        if (newInterestRate != null) {
+            map.put("newInterestRate", newInterestRate);
+        }
+
+        if (adjustedDueDate != null) {
+            map.put("adjustedDueDate", adjustedDueDate);
+        }
+
+        map.put("rescheduleReasonId", rescheduleReasonId);
+
+        if (rescheduleReasonComment != null) {
+            map.put("rescheduleReasonComment", rescheduleReasonComment);
+        }
+
+        return new Gson().toJson(map);
+    }
+
+    public LoanRescheduleRequestTestBuilder updateRescheduleFromDate(final String rescheduleFromDate) {
+        if (rescheduleFromDate != null) {
+            this.rescheduleFromDate = rescheduleFromDate;
+        }
+
+        return this;
+    }
+
+    public LoanRescheduleRequestTestBuilder updateGraceOnPrincipal(final String graceOnPrincipal) {
+        this.graceOnPrincipal = graceOnPrincipal;
+
+        return this;
+    }
+
+    public LoanRescheduleRequestTestBuilder updateGraceOnInterest(final String graceOnInterest) {
+        this.graceOnInterest = graceOnInterest;
+
+        return this;
+    }
+
+    public LoanRescheduleRequestTestBuilder updateExtraTerms(final String extraTerms) {
+        this.extraTerms = extraTerms;
+
+        return this;
+    }
+
+    public LoanRescheduleRequestTestBuilder updateRecalculateInterest(final Boolean recalculateInterest) {
+        this.recalculateInterest = recalculateInterest;
+
+        return this;
+    }
+
+    public LoanRescheduleRequestTestBuilder updateNewInterestRate(final String newInterestRate) {
+        this.newInterestRate = newInterestRate;
+
+        return this;
+    }
+
+    public LoanRescheduleRequestTestBuilder updateAdjustedDueDate(final String adjustedDueDate) {
+        this.adjustedDueDate = adjustedDueDate;
+
+        return this;
+    }
+
+    public LoanRescheduleRequestTestBuilder updateRescheduleReasonId(final String rescheduleReasonId) {
+        this.rescheduleReasonId = rescheduleReasonId;
+
+        return this;
+    }
+
+    public LoanRescheduleRequestTestBuilder updateRescheduleReasonComment(final String rescheduleReasonComment) {
+        this.rescheduleReasonComment = rescheduleReasonComment;
+
+        return this;
+    }
+
+    public LoanRescheduleRequestTestBuilder updateSubmittedOnDate(final String submittedOnDate) {
+        this.submittedOnDate = submittedOnDate;
+
+        return this;
+    }
+
+    public String getRejectLoanRescheduleRequestJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("rejectedOnDate", submittedOnDate);
+        return new Gson().toJson(map);
+    }
+
+    public String getApproveLoanRescheduleRequestJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("approvedOnDate", submittedOnDate);
+        return new Gson().toJson(map);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanStatusChecker.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanStatusChecker.java
new file mode 100644
index 0000000..3535dfe
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanStatusChecker.java
@@ -0,0 +1,72 @@
+/**
+ * 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.integrationtests.common.loans;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class LoanStatusChecker {
+
+    public static void verifyLoanIsApproved(final HashMap loanStatusHashMap) {
+        assertFalse(getStatus(loanStatusHashMap, "pendingApproval"));
+    }
+
+    public static void verifyLoanIsWaitingForDisbursal(final HashMap loanStatusHashMap) {
+        assertTrue(getStatus(loanStatusHashMap, "waitingForDisbursal"));
+    }
+
+    public static void verifyLoanIsPending(final HashMap loanStatusHashMap) {
+        assertTrue(getStatus(loanStatusHashMap, "pendingApproval"));
+    }
+
+    public static void verifyLoanIsActive(final HashMap loanStatusHashMap) {
+        assertTrue(getStatus(loanStatusHashMap, "active"));
+    }
+
+    public static void verifyLoanAccountIsClosed(final HashMap loanStatusHashMap) {
+        assertTrue(getStatus(loanStatusHashMap, "closed"));
+    }
+
+    public static void verifyLoanAccountIsNotActive(final HashMap loanStatusHashMap) {
+        assertFalse(getStatus(loanStatusHashMap, "active"));
+    }
+
+    public static void verifyLoanAccountIsOverPaid(final HashMap loanStatusHashMap) {
+        assertTrue(getStatus(loanStatusHashMap, "overpaid"));
+    }
+
+    public static HashMap<String, Object> getStatusOfLoan(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer loanID) {
+        final String url = "/fineract-provider/api/v1/loans/" + loanID + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "status");
+    }
+
+    private static boolean getStatus(final HashMap loanStatusMap, final String nameOfLoanStatusString) {
+        return (Boolean) loanStatusMap.get(nameOfLoanStatusString);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
new file mode 100755
index 0000000..f416bfc
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -0,0 +1,648 @@
+/**
+ * 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.integrationtests.common.loans;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.joda.time.LocalDate;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class LoanTransactionHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    private static final String CREATE_LOAN_PRODUCT_URL = "/fineract-provider/api/v1/loanproducts?" + Utils.TENANT_IDENTIFIER;
+    private static final String APPLY_LOAN_URL = "/fineract-provider/api/v1/loans?" + Utils.TENANT_IDENTIFIER;
+    private static final String APPROVE_LOAN_COMMAND = "approve";
+    private static final String UNDO_APPROVAL_LOAN_COMMAND = "undoApproval";
+    private static final String DISBURSE_LOAN_COMMAND = "disburse";
+    private static final String DISBURSE_LOAN_TO_SAVINGS_COMMAND = "disburseToSavings";
+    private static final String UNDO_DISBURSE_LOAN_COMMAND = "undoDisbursal";
+    private static final String UNDO_LAST_DISBURSE_LOAN_COMMAND = "undolastdisbursal";
+    private static final String WRITE_OFF_LOAN_COMMAND = "writeoff";
+    private static final String WAIVE_INTEREST_COMMAND = "waiveinterest";
+    private static final String MAKE_REPAYMENT_COMMAND = "repayment";
+    private static final String WITHDRAW_LOAN_APPLICATION_COMMAND = "withdrawnByApplicant";
+    private static final String RECOVER_FROM_GUARANTORS_COMMAND = "recoverGuarantees";
+    private static final String MAKE_REFUND_BY_CASH_COMMAND = "refundByCash";
+
+    public LoanTransactionHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public Integer getLoanProductId(final String loanProductJSON) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, CREATE_LOAN_PRODUCT_URL, loanProductJSON, "resourceId");
+    }
+
+    public Integer getLoanId(final String loanApplicationJSON) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, APPLY_LOAN_URL, loanApplicationJSON, "loanId");
+    }
+
+    public Integer getLoanOfficerId(final String loanId) {
+        final String GET_LOAN_URL = "/fineract-provider/api/v1/loans/" + loanId + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(this.requestSpec, this.responseSpec, GET_LOAN_URL, "loanOfficerId");
+    }
+
+    public Object createLoanAccount(final String loanApplicationJSON, final String responseAttribute) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, APPLY_LOAN_URL, loanApplicationJSON, responseAttribute);
+    }
+
+    public Integer updateLoan(final Integer id, final String loanApplicationJSON) {
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, "/fineract-provider/api/v1/loans/" + id + "?"
+                + Utils.TENANT_IDENTIFIER, loanApplicationJSON, "loanId");
+    }
+
+    public ArrayList getLoanRepaymentSchedule(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer loanID) {
+        final String URL = "/fineract-provider/api/v1/loans/" + loanID + "?associations=repaymentSchedule&" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "repaymentSchedule");
+        return (ArrayList) response.get("periods");
+    }
+
+    public ArrayList getLoanFutureRepaymentSchedule(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer loanID) {
+        final String URL = "/fineract-provider/api/v1/loans/" + loanID + "?associations=repaymentSchedule,futureSchedule&"
+                + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "repaymentSchedule");
+        return (ArrayList) response.get("futurePeriods");
+    }
+
+    public HashMap getLoanSummary(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer loanID) {
+        final String URL = "/fineract-provider/api/v1/loans/" + loanID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "summary");
+        return response;
+    }
+
+    public Object getLoanDetail(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer loanID,
+            final String param) {
+        final String URL = "/fineract-provider/api/v1/loans/" + loanID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, URL, param);
+    }
+
+    public String getLoanDetails(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer loanID) {
+        final String URL = "/fineract-provider/api/v1/loans/" + loanID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, URL, null);
+    }
+
+    public Object getLoanProductDetail(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer loanProductId, final String jsonAttributeToGetBack) {
+        final String URL = "/fineract-provider/api/v1/loanproducts/" + loanProductId + "?associations=all&" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, URL, jsonAttributeToGetBack);
+    }
+
+    public String getLoanProductDetails(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer loanProductId) {
+        final String URL = "/fineract-provider/api/v1/loanproducts/" + loanProductId + "?associations=all&" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, URL, null);
+    }
+
+    public ArrayList getLoanCharges(final Integer loanId) {
+        final String GET_LOAN_CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/charges?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, "");
+    }
+
+    public HashMap approveLoan(final String approvalDate, final Integer loanID) {
+        String loanApprovalCommand = createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID);
+        String loanApprovalRequest = getApproveLoanAsJSON(approvalDate);
+        System.out.println("Loan approval command:" + loanApprovalCommand);
+        System.out.println("Loan approval request:" + loanApprovalRequest);
+        return performLoanTransaction(loanApprovalCommand, loanApprovalRequest);
+    }
+
+    public HashMap approveLoanWithApproveAmount(final String approvalDate, final String expectedDisbursementDate,
+            final String approvalAmount, final Integer loanID, List<HashMap> tranches) {
+        return performLoanTransaction(createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID),
+                getApproveLoanAsJSON(approvalDate, expectedDisbursementDate, approvalAmount, tranches));
+    }
+
+    public List<HashMap<String, Object>> approveLoanForTranches(final String approvalDate, final String expectedDisbursementDate,
+            final String approvalAmount, final Integer loanID, List<HashMap> tranches, final String responseAttribute) {
+        return (List<HashMap<String, Object>>) performLoanTransaction(createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID),
+                getApproveLoanAsJSON(approvalDate, expectedDisbursementDate, approvalAmount, tranches), responseAttribute);
+    }
+
+    public Object approveLoan(final String approvalDate, final String approvalAmount, final Integer loanID, final String responseAttribute) {
+
+        final String approvalURL = createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID);
+        final String approvalJSONData = getApproveLoanAsJSON(approvalDate, null, approvalAmount, null);
+
+        return performLoanTransaction(approvalURL, approvalJSONData, responseAttribute);
+    }
+
+    public HashMap undoApproval(final Integer loanID) {
+        final String undoBodyJson = "{'note':'UNDO APPROVAL'}";
+        return performLoanTransaction(createLoanOperationURL(UNDO_APPROVAL_LOAN_COMMAND, loanID), undoBodyJson);
+    }
+
+    public HashMap disburseLoan(final String date, final Integer loanID) {
+        return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, null));
+    }
+    
+    public HashMap disburseLoanWithRepaymentReschedule(final String date, final Integer loanID, String adjustRepaymentDate) {
+        return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanWithRepaymentRescheduleAsJSON(date,
+        		null, adjustRepaymentDate));
+    }
+
+    public HashMap disburseLoan(final String date, final Integer loanID, final String disburseAmt) {
+        return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, disburseAmt));
+    }
+
+    public HashMap disburseLoanToSavings(final String date, final Integer loanID) {
+        return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_TO_SAVINGS_COMMAND, loanID), getDisburseLoanAsJSON(date, null));
+    }
+
+    public HashMap undoDisbursal(final Integer loanID) {
+        final String undoDisburseJson = "{'note' : 'UNDO DISBURSAL'}";
+        System.out.println("IN DISBURSE LOAN");
+        final String url = createLoanOperationURL(UNDO_DISBURSE_LOAN_COMMAND, loanID);
+        System.out.println("IN DISBURSE LOAN URL " + url);
+        return performLoanTransaction(createLoanOperationURL(UNDO_DISBURSE_LOAN_COMMAND, loanID), undoDisburseJson);
+    }
+    
+    public Float undoLastDisbursal(final Integer loanID) {
+        final String undoLastDisburseJson = "{'note' : 'UNDO LAST DISBURSAL'}";
+        final String url = createLoanOperationURL(UNDO_LAST_DISBURSE_LOAN_COMMAND, loanID);
+        System.out.println("IN UNDO LAST DISBURSE LOAN URL " + url);
+        return performUndoLastLoanDisbursementTransaction(createLoanOperationURL(UNDO_LAST_DISBURSE_LOAN_COMMAND, loanID), undoLastDisburseJson);
+    }
+
+    public void recoverFromGuarantor(final Integer loanID) {
+        performLoanTransaction(createLoanOperationURL(RECOVER_FROM_GUARANTORS_COMMAND, loanID), "", "");
+    }
+
+    public HashMap writeOffLoan(final String date, final Integer loanID) {
+        return performLoanTransaction(createLoanTransactionURL(WRITE_OFF_LOAN_COMMAND, loanID), getWriteOffBodyAsJSON(date));
+    }
+
+    public HashMap waiveInterest(final String date, final String amountToBeWaived, final Integer loanID) {
+        return performLoanTransaction(createLoanTransactionURL(WAIVE_INTEREST_COMMAND, loanID), getWaiveBodyAsJSON(date, amountToBeWaived));
+    }
+
+    public Integer waiveInterestAndReturnTransactionId(final String date, final String amountToBeWaived, final Integer loanID) {
+        Integer resourceId = Utils.performServerPost(this.requestSpec, this.responseSpec,
+                createLoanTransactionURL(WAIVE_INTEREST_COMMAND, loanID), getWaiveBodyAsJSON(date, amountToBeWaived), "resourceId");
+        return resourceId;
+    }
+
+    public HashMap makeRepayment(final String date, final Float amountToBePaid, final Integer loanID) {
+        return (HashMap) performLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID),
+                getRepaymentBodyAsJSON(date, amountToBePaid), "");
+    }
+
+    public HashMap withdrawLoanApplicationByClient(final String date, final Integer loanID) {
+        return performLoanTransaction(createLoanOperationURL(WITHDRAW_LOAN_APPLICATION_COMMAND, loanID),
+                getWithdrawLoanApplicationBodyAsJSON(date));
+    }
+
+    public Integer addChargesForLoan(final Integer loanId, final String request) {
+        System.out.println("--------------------------------- ADD CHARGES FOR LOAN --------------------------------");
+        final String ADD_CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/charges?" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerPost(requestSpec, responseSpec, ADD_CHARGES_URL, request, "");
+        return (Integer) response.get("resourceId");
+    }
+
+    public Object addChargesForAllreadyDisursedLoan(final Integer loanId, final String request, final ResponseSpecification responseSpecification) {
+        final String ADD_CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/charges?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(this.requestSpec, responseSpecification, ADD_CHARGES_URL, request, "");
+    }
+    
+    public Integer updateChargesForLoan(final Integer loanId, final Integer loanchargeId, final String request) {
+        System.out.println("--------------------------------- ADD CHARGES FOR LOAN --------------------------------");
+        final String UPDATE_CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId + "?"
+                + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerPut(requestSpec, responseSpec, UPDATE_CHARGES_URL, request, "");
+        return (Integer) response.get("resourceId");
+    }
+
+    public Integer deleteChargesForLoan(final Integer loanId, final Integer loanchargeId) {
+        System.out.println("--------------------------------- DELETE CHARGES FOR LOAN --------------------------------");
+        final String DELETE_CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId + "?"
+                + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerDelete(requestSpec, responseSpec, DELETE_CHARGES_URL, "");
+        return (Integer) response.get("resourceId");
+    }
+
+    public Integer waiveChargesForLoan(final Integer loanId, final Integer loanchargeId, final String json) {
+        System.out.println("--------------------------------- WAIVE CHARGES FOR LOAN --------------------------------");
+        final String CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId + "?command=waive&"
+                + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerPost(requestSpec, responseSpec, CHARGES_URL, json, "");
+        return (Integer) response.get("resourceId");
+    }
+
+    public Integer payChargesForLoan(final Integer loanId, final Integer loanchargeId, final String json) {
+        System.out.println("--------------------------------- WAIVE CHARGES FOR LOAN --------------------------------");
+        final String CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId + "?command=pay&"
+                + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerPost(requestSpec, responseSpec, CHARGES_URL, json, "");
+        return (Integer) response.get("resourceId");
+    }
+
+    private String getDisburseLoanAsJSON(final String actualDisbursementDate, final String transactionAmount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("actualDisbursementDate", actualDisbursementDate);
+        map.put("note", "DISBURSE NOTE");
+        if (transactionAmount != null) {
+            map.put("transactionAmount", transactionAmount);
+        }
+        System.out.println("Loan Application disburse request : " + map);
+        return new Gson().toJson(map);
+    }
+    
+    private String getDisburseLoanWithRepaymentRescheduleAsJSON(final String actualDisbursementDate, final String transactionAmount, final String adjustRepaymentDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("actualDisbursementDate", actualDisbursementDate);
+        map.put("adjustRepaymentDate", adjustRepaymentDate);
+        map.put("note", "DISBURSE NOTE");
+        if (transactionAmount != null) {
+            map.put("transactionAmount", transactionAmount);
+        }
+        System.out.println("Loan Application disburse request : " + map);
+        return new Gson().toJson(map);
+    }
+
+    private String getApproveLoanAsJSON(final String approvalDate) {
+        return getApproveLoanAsJSON(approvalDate, null, null, null);
+    }
+
+    private String getApproveLoanAsJSON(final String approvalDate, final String expectedDisbursementDate, final String approvalAmount,
+            List<HashMap> tranches) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        if (approvalAmount != null) {
+            map.put("approvedLoanAmount", approvalAmount);
+        }
+        map.put("approvedOnDate", approvalDate);
+        if (expectedDisbursementDate != null) {
+            map.put("expectedDisbursementDate", expectedDisbursementDate);
+        }
+        if (tranches != null && tranches.size() > 0) {
+            map.put("disbursementData", tranches);
+        }
+        map.put("note", "Approval NOTE");
+        return new Gson().toJson(map);
+    }
+
+    private String getRepaymentBodyAsJSON(final String transactionDate, final Float transactionAmount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("transactionDate", transactionDate);
+        map.put("transactionAmount", transactionAmount.toString());
+        map.put("note", "Repayment Made!!!");
+        return new Gson().toJson(map);
+    }
+
+    private String getWriteOffBodyAsJSON(final String transactionDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en");
+        map.put("note", " LOAN WRITE OFF!!!");
+        map.put("transactionDate", transactionDate);
+        return new Gson().toJson(map);
+    }
+
+    private String getWaiveBodyAsJSON(final String transactionDate, final String amountToBeWaived) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("transactionDate", transactionDate);
+        map.put("transactionAmount", amountToBeWaived);
+        map.put("note", " Interest Waived!!!");
+        return new Gson().toJson(map);
+    }
+
+    private String getWithdrawLoanApplicationBodyAsJSON(final String withdrawDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("withdrawnOnDate", withdrawDate);
+        map.put("note", " Loan Withdrawn By Client!!!");
+        return new Gson().toJson(map);
+
+    }
+
+    public static String getSpecifiedDueDateChargesForLoanAsJSON(final String chargeId) {
+        return getSpecifiedDueDateChargesForLoanAsJSON(chargeId, "12 January 2013", "100");
+    }
+
+    public static String getSpecifiedDueDateChargesForLoanAsJSON(final String chargeId, final String dueDate, final String amount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("amount", amount);
+        map.put("dueDate", dueDate);
+        map.put("chargeId", chargeId);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getDisbursementChargesForLoanAsJSON(final String chargeId) {
+        return getDisbursementChargesForLoanAsJSON(chargeId, "100");
+    }
+
+    public static String getDisbursementChargesForLoanAsJSON(final String chargeId, String amount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("amount", amount);
+        map.put("chargeId", chargeId);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getInstallmentChargesForLoanAsJSON(final String chargeId, final String amount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("amount", amount);
+        map.put("chargeId", chargeId);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getUpdateChargesForLoanAsJSON(String amount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("amount", amount);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getPayChargeJSON(final String date, final String installmentNumber) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("transactionDate", date);
+        if (installmentNumber != null) {
+            map.put("installmentNumber", installmentNumber);
+        }
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public static String getWaiveChargeJSON(final String installmentNumber) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("installmentNumber", installmentNumber);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public String getLoanCalculationBodyAsJSON(final String productID) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", "en_GB");
+        map.put("productId", productID);
+        map.put("principal", "4,500.00");
+        map.put("loanTermFrequency", "4");
+        map.put("loanTermFrequencyType", "2");
+        map.put("numberOfRepayments", "4");
+        map.put("repaymentEvery", "1");
+        map.put("repaymentFrequencyType", "2");
+        map.put("interestRateFrequencyType", "2");
+        map.put("interestRatePerPeriod", "2");
+        map.put("amortizationType", "1");
+        map.put("interestType", "0");
+        map.put("interestCalculationPeriodType", "1");
+        map.put("expectedDisbursementDate", "20 September 2011");
+        map.put("transactionProcessingStrategyId", "1");
+        return new Gson().toJson(map);
+    }
+
+    private String createLoanOperationURL(final String command, final Integer loanID) {
+        return "/fineract-provider/api/v1/loans/" + loanID + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private String createLoanTransactionURL(final String command, final Integer loanID) {
+        return "/fineract-provider/api/v1/loans/" + loanID + "/transactions?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private HashMap performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent) {
+
+        final HashMap response = Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent,
+                "changes");
+        return (HashMap) response.get("status");
+    }
+    
+    private Float performUndoLastLoanDisbursementTransaction(final String postURLForLoanTransaction, final String jsonToBeSent) {
+
+        final HashMap response = Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent,
+                "changes");
+        return (Float) response.get("disbursedAmount");
+    }
+
+    private Object performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent, final String responseAttribute) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent, responseAttribute);
+    }
+
+    public Object adjustLoanTransaction(final Integer loanId, final Integer transactionId, final String date,
+            final String transactionAmount, final String responseAttribute) {
+        return adjustLoanTransaction(loanId, transactionId, getAdjustTransactionJSON(date, transactionAmount), responseAttribute);
+    }
+
+    private Object adjustLoanTransaction(final Integer loanId, final Integer tansactionId, final String jsonToBeSent,
+            final String responseAttribute) {
+        final String URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/" + tansactionId + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, jsonToBeSent, responseAttribute);
+    }
+
+    private String getAdjustTransactionJSON(final String date, final String transactionAmount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("transactionDate", date);
+        map.put("transactionAmount", transactionAmount);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+
+    public HashMap getPrepayAmount(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer loanID) {
+        final String URL = "/fineract-provider/api/v1/loans/" + loanID + "/transactions/template?command=prepayLoan&"
+                + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "");
+        return response;
+    }
+
+    private String createLoanRefundTransferURL() {
+        return "/fineract-provider/api/v1/accounttransfers/refundByTransfer?tenantIdentifier=default";
+    }
+
+    public void verifyRepaymentScheduleEntryFor(final int repaymentNumber, final float expectedPrincipalOutstanding, final Integer loanID) {
+        System.out.println("---------------------------GETTING LOAN REPAYMENT SCHEDULE--------------------------------");
+        final ArrayList<HashMap> repaymentPeriods = getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        assertEquals("Mismatch in Principal Loan Balance Outstanding ", expectedPrincipalOutstanding, repaymentPeriods.get(repaymentNumber)
+                .get("principalLoanBalanceOutstanding"));
+    }
+
+    public void checkAccrualTransactionForRepayment(final LocalDate transactionDate, final Float interestPortion, final Float feePortion,
+            final Float penaltyPortion, final Integer loanID) {
+
+        ArrayList<HashMap> transactions = (ArrayList<HashMap>) getLoanDetail(this.requestSpec, this.responseSpec, loanID, "transactions");
+        boolean isTransactionFound = false;
+        for (int i = 0; i < transactions.size(); i++) {
+            HashMap transactionType = (HashMap) transactions.get(i).get("type");
+            boolean isAccrualTransaction = (Boolean) transactionType.get("accrual");
+
+            if (isAccrualTransaction) {
+                ArrayList<Integer> accrualEntryDateAsArray = (ArrayList<Integer>) transactions.get(i).get("date");
+                LocalDate accrualEntryDate = new LocalDate(accrualEntryDateAsArray.get(0), accrualEntryDateAsArray.get(1),
+                        accrualEntryDateAsArray.get(2));
+
+                if (transactionDate.equals(accrualEntryDate)) {
+                    isTransactionFound = true;
+                    assertEquals("Mismatch in transaction amounts", interestPortion,
+                            Float.valueOf(String.valueOf(transactions.get(i).get("interestPortion"))));
+                    assertEquals("Mismatch in transaction amounts", feePortion,
+                            Float.valueOf(String.valueOf(transactions.get(i).get("feeChargesPortion"))));
+                    assertEquals("Mismatch in transaction amounts", penaltyPortion,
+                            Float.valueOf(String.valueOf(transactions.get(i).get("penaltyChargesPortion"))));
+                    break;
+                }
+            }
+        }
+        assertTrue("No Accrual entries are posted", isTransactionFound);
+
+    }
+
+    public HashMap makeRefundByCash(final String date, final Float amountToBeRefunded, final Integer loanID) {
+        return performLoanTransaction(createLoanTransactionURL(MAKE_REFUND_BY_CASH_COMMAND, loanID),
+                getRefundByCashBodyAsJSON(date, amountToBeRefunded));
+    }
+
+    public HashMap makeRefundByTransfer(final Integer fromAccountId, final Integer toClientId, final Integer toAccountId,
+            final Integer fromClientId, final String date, final Float amountToBeRefunded) {
+        return performLoanTransaction(createLoanRefundTransferURL(),
+                getRefundByTransferBodyAsJSON(fromAccountId, toClientId, toAccountId, fromClientId, date, amountToBeRefunded));
+    }
+
+    private String getRefundByCashBodyAsJSON(final String transactionDate, final Float transactionAmount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("transactionDate", transactionDate);
+        map.put("transactionAmount", transactionAmount.toString());
+        map.put("note", "Refund Made!!!");
+        return new Gson().toJson(map);
+    }
+
+    private String getRefundByTransferBodyAsJSON(final Integer fromAccountId, final Integer toClientId, final Integer toAccountId,
+            final Integer fromClientId, final String transactionDate, final Float transactionAmount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("fromAccountId", fromAccountId.toString());
+        map.put("fromAccountType", "1");
+        map.put("toOfficeId", "1");
+        map.put("toClientId", toClientId.toString());
+        map.put("toAccountType", "2");
+        map.put("toAccountId", toAccountId.toString());
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("transferDate", transactionDate);
+        map.put("transferAmount", transactionAmount.toString());
+        map.put("transferDescription", "Refund Made!!!");
+        map.put("fromClientId", fromClientId.toString());
+        map.put("fromOfficeId", "1");
+        map.put("locale", "en");
+        return new Gson().toJson(map);
+    }
+    
+    public HashMap createTrancheDetail(final String id, final String date, final String amount) {
+        HashMap<String, Object> detail = new HashMap<>();
+        if(id != null){
+            detail.put("id", id);
+        }
+        detail.put("expectedDisbursementDate", date);
+        detail.put("principal", amount);
+
+        return detail;
+    }
+    
+    public Object editDisbursementDetail(final Integer loanID, final Integer disbursementId, final String approvalAmount, final String expectedDisbursementDate, 
+    		final String updatedExpectedDisbursementDate, final String updatedPrincipal, final String jsonAttributeToGetBack) {
+    	
+    	return Utils.performServerPut(this.requestSpec, this.responseSpec, createEditDisbursementURL(loanID, disbursementId), getEditDisbursementsAsJSON(approvalAmount, expectedDisbursementDate, 
+    			updatedExpectedDisbursementDate, updatedPrincipal), jsonAttributeToGetBack);
+    }
+    
+    public Object addAndDeleteDisbursementDetail(final Integer loanID, final String approvalAmount, final String expectedDisbursementDate
+    		, List<HashMap> disbursementData, final String jsonAttributeToGetBack) {
+    	
+    	return Utils.performServerPut(this.requestSpec, this.responseSpec, createAddAndDeleteDisbursementURL(loanID), 
+    			getAddAndDeleteDisbursementsAsJSON(approvalAmount, expectedDisbursementDate, disbursementData), jsonAttributeToGetBack);
+    }
+    
+    private String createEditDisbursementURL(Integer loanID, Integer disbursementId) {
+        return "/fineract-provider/api/v1/loans/" + loanID + "/disbursements/" + disbursementId + "?" + Utils.TENANT_IDENTIFIER;
+    }
+    
+    private String createAddAndDeleteDisbursementURL(Integer loanID) {
+        return "/fineract-provider/api/v1/loans/" + loanID + "/disbursements/editDisbursements?" +  Utils.TENANT_IDENTIFIER;
+    }
+    
+    public static String getEditDisbursementsAsJSON(final String approvalAmount, final String expectedDisbursementDate, 
+    		final String updatedExpectedDisbursementDate, final String updatedPrincipal) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("approvedLoanAmount", approvalAmount);
+        map.put("expectedDisbursementDate", expectedDisbursementDate);
+        map.put("updatedExpectedDisbursementDate", updatedExpectedDisbursementDate);
+        map.put("updatedPrincipal", updatedPrincipal);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+    
+    public static String getAddAndDeleteDisbursementsAsJSON(final String approvalAmount, final String expectedDisbursementDate, 
+    		final List<HashMap> disbursementData) {
+        final HashMap map = new HashMap<>();
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("approvedLoanAmount", approvalAmount);
+        map.put("expectedDisbursementDate", expectedDisbursementDate);
+        map.put("disbursementData", disbursementData);
+        String json = new Gson().toJson(map);
+        System.out.println(json);
+        return json;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/organisation/Currency.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/organisation/Currency.java
new file mode 100644
index 0000000..50442bd
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/organisation/Currency.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.integrationtests.common.organisation;
+
+public class Currency {
+
+    String code;
+    String name;
+    Integer decimalPlaces;
+    String displaySymbol;
+    String nameCode;
+    String displayLabel;
+
+    public Currency(String code, String name, Integer decimalPlaces, String displaySymbol,
+                    String nameCode, String displayLabel) {
+        this.code = code;
+        this.name = name;
+        this.decimalPlaces = decimalPlaces;
+        this.displaySymbol = displaySymbol;
+        this.nameCode = nameCode;
+        this.displayLabel = displayLabel;
+    }
+
+    public boolean isValid() {
+        return (this.code != null && this.code.length() == 3) &&
+                (this.name != null && this.name.length() > 0) &&
+                (this.decimalPlaces != null && this.decimalPlaces >= 0) &&
+                (this.nameCode != null && this.nameCode.startsWith("currency.")) &&
+                (this.displayLabel != null && this.displayLabel.length() > 0);
+
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/organisation/CurrencyHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/organisation/CurrencyHelper.java
new file mode 100644
index 0000000..f6b0462
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/organisation/CurrencyHelper.java
@@ -0,0 +1,99 @@
+/**
+ * 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.integrationtests.common.organisation;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.springframework.util.Assert;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+import static com.jayway.restassured.RestAssured.given;
+
+public class CurrencyHelper {
+
+    private static final String CURRENCY_URL = "/fineract-provider/api/v1/currencies?" + Utils.TENANT_IDENTIFIER;
+    private static final String CURRENCY_URL_SELECTED = CURRENCY_URL + "&fields=selectedCurrencyOptions";
+
+    private static final List<String> permittedCurrencyArray = Arrays.asList("currencyOptions",
+            "selectedCurrencyOptions");
+
+    private static final List<String> permittedCurrencyArraySelected = Arrays.asList("selectedCurrencyOptions");
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public CurrencyHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public ArrayList<Currency> getPermittedCurrencies() {
+        return getCurrencies(CURRENCY_URL, permittedCurrencyArray);
+    }
+
+    public ArrayList<Currency> getSelectedCurrencies() {
+        return getCurrencies(CURRENCY_URL_SELECTED, permittedCurrencyArraySelected);
+    }
+
+
+    private ArrayList<Currency> getCurrencies(final String getUrl, final List<String> permittedCurrencyArrays) {
+        System.out.println("--------------------------------- GET CURRENCY OPTIONS -------------------------------");
+        final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when()
+                .get(getUrl).andReturn().asString();
+        final Gson gson = new Gson();
+        Assert.notNull(json);
+        final ArrayList<Currency> currencyList = new ArrayList<Currency>();
+        final Type typeOfHashMap = new TypeToken<Map<String, List<Currency>>>() { }.getType();
+        final Map<String, List<Currency>> responseMap = gson.fromJson(json, typeOfHashMap);
+        for(Map.Entry<String, List<Currency>> entry : responseMap.entrySet()) {
+            Assert.isTrue(permittedCurrencyArrays.contains(entry.getKey()));
+            for(Currency currency : entry.getValue()) {
+                currencyList.add(currency);
+            }
+        }
+        return currencyList;
+    }
+
+    public List<String> updateCurrencies(final List<String> currencies) {
+        System.out.println("--------------------------------- UPDATE CURRENCY OPTIONS -------------------------------");
+        final String json = given().spec(requestSpec).body(getUpdateJSON(currencies)).expect().spec(responseSpec).log().ifError().when()
+                .put(CURRENCY_URL).andReturn().asString();
+        final Gson gson = new Gson();
+        Assert.notNull(json);
+        final Type typeOfHashMap = new TypeToken<Map<String,Map<String, List<String>>>>() { }.getType();
+        final Map<String,Map<String, List<String>>> responseMap = gson.fromJson(json, typeOfHashMap);
+        return responseMap.get("changes").get("currencies");
+    }
+
+    private String getUpdateJSON(final List<String> currencies) {
+        final HashMap<String, List<String>> map = new HashMap<>();
+        map.put("currencies", currencies);
+        return new Gson().toJson(map);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/organisation/StaffHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/organisation/StaffHelper.java
new file mode 100755
index 0000000..e56f42b
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/organisation/StaffHelper.java
@@ -0,0 +1,125 @@
+/**
+ * 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.integrationtests.common.organisation;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class StaffHelper {
+
+    private static final String TRANSFER_STAFF_URL = "/fineract-provider/api/v1/groups";
+
+    private static final String CREATE_STAFF_URL = "/fineract-provider/api/v1/staff";
+
+    private static final String RESOURCE_ID = "resourceId";
+
+    public static final String GROUP_ID = "groupId";
+
+
+    public static Integer transferStaffToGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+                                               final Integer groupId,final Integer staffToTransfer ,final String note){
+        final String url = TRANSFER_STAFF_URL + "/" + groupId + "?command=transferStaff&"+ Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(requestSpec, responseSpec, url, transferStaffToGroupAsJSON(staffToTransfer, note), GROUP_ID);
+    }
+
+    public static String transferStaffToGroupAsJSON(final Integer staffToTransferId,final String note) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("staffId", staffToTransferId);
+        map.put("note", note);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static Integer createStaff(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return (Integer) createStaffWithJson(requestSpec, responseSpec, createStaffAsJSON()).get("resourceId");
+    }
+
+    public static HashMap createStaffMap(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return createStaffWithJson(requestSpec, responseSpec, createStaffAsJSON());
+    }
+
+    public static HashMap createStaffWithJson(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String json) {
+        final String url = CREATE_STAFF_URL + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(requestSpec, responseSpec, url, json, "");
+    }
+
+    public static HashMap getStaff(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final Integer staffId) {
+        final String url = CREATE_STAFF_URL + "/" + staffId + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "");
+    }
+
+    public static List<HashMap> getStaffList(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        final String url = CREATE_STAFF_URL + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "");
+    }
+
+    public static Object getStaffListWithState(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String status) {
+        final String url = CREATE_STAFF_URL + "?" + Utils.TENANT_IDENTIFIER + "&status=" + status;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "");
+    }
+
+    public static Object updateStaff(final RequestSpecification requestSpec,
+             final ResponseSpecification responseSpec, final Integer staffId, final HashMap<String, Object> changes) {
+        final String url = CREATE_STAFF_URL + "/" + staffId + "?" + Utils.TENANT_IDENTIFIER;
+        final String json = new Gson().toJson(changes);
+        return Utils.performServerPut(requestSpec, responseSpec, url, json, "");
+    }
+
+    public static String createStaffAsJSON(){
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("officeId", 1);
+        map.put("firstname", Utils.randomNameGenerator("michael_", 5));
+        map.put("lastname", Utils.randomNameGenerator("Doe_", 4));
+        map.put("isLoanOfficer", true);
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public static String createStaffWithJSONFields(String... fields) {
+        final HashMap<String, Object> map = new HashMap<>();
+        final List<String> fieldList = Arrays.asList(fields);
+        if(fieldList.contains("officeId")) {
+            map.put("officeId", 1);
+        }
+        if(fieldList.contains("firstname")) {
+            map.put("firstname", Utils.randomNameGenerator("michael_", 5));
+        }
+        if(fieldList.contains("lastname")) {
+            map.put("lastname", Utils.randomNameGenerator("Doe_", 4));
+        }
+        if(fieldList.contains("isLoanOfficer")) {
+            map.put("isLoanOfficer", true);
+        }
+        if(fieldList.contains("mobileNo")) {
+            map.put("mobileNo", "+123515198");
+        }
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/provisioning/ProvisioningHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/provisioning/ProvisioningHelper.java
new file mode 100644
index 0000000..e05a7a1
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/provisioning/ProvisioningHelper.java
@@ -0,0 +1,104 @@
+/**
+ * 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.integrationtests.common.provisioning;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+
+import com.google.gson.Gson;
+
+public class ProvisioningHelper {
+
+    public final static Map createProvisioingCriteriaJson(ArrayList<Integer> loanProducts, ArrayList categories, Account liability,
+            Account expense) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("loanProducts", addLoanProducts(loanProducts));
+        map.put("definitions", addProvisioningCategories(categories, liability, expense));
+        DateFormat simple = new SimpleDateFormat("dd MMMM yyyy");
+        String formattedString = simple.format(Utils.getLocalDateOfTenant().toDate());
+        Random rand = new Random() ;
+        String criteriaName = "General Provisioning Criteria" + formattedString+rand.nextLong();
+        map.put("criteriaName", criteriaName);
+        map.put("locale", "en");
+       return map ;
+    }
+
+    public final static String createProvisioningEntryJson() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("createjournalentries", Boolean.FALSE);
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        DateFormat simple = new SimpleDateFormat("dd MMMM yyyy");
+        map.put("date", simple.format(Utils.getLocalDateOfTenant().toDate()));
+        String provisioningEntryCreateJson = new Gson().toJson(map);
+        return provisioningEntryCreateJson;
+    }
+    
+    public final static String createProvisioningEntryJsonWithJournalsEnabled() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("createjournalentries", Boolean.TRUE);
+        map.put("locale", "en");
+        map.put("dateFormat", "dd MMMM yyyy");
+        DateFormat simple = new SimpleDateFormat("dd MMMM yyyy");
+        map.put("date", simple.format(Utils.getLocalDateOfTenant().toDate()));
+        String provisioningEntryCreateJson = new Gson().toJson(map);
+        return provisioningEntryCreateJson;
+    }
+
+    private static ArrayList addLoanProducts(ArrayList<Integer> loanProducts) {
+        ArrayList list = new ArrayList<>();
+        for (int i = 0; i < loanProducts.size(); i++) {
+            HashMap map = new HashMap();
+            map.put("id", loanProducts.get(i));
+            list.add(map);
+        }
+        return list;
+    }
+
+    public static ArrayList addProvisioningCategories(ArrayList categories, Account liability, Account expense) {
+        ArrayList list = new ArrayList();
+        int minStart = 0;
+        int maxStart = 30;
+
+        for (int i = 0; i < categories.size(); i++) {
+            HashMap map = new HashMap();
+            HashMap category = (HashMap) categories.get(i);
+            map.put("categoryId", category.get("id"));
+            map.put("categoryName", category.get("categoryName"));
+            map.put("minAge", (i * 30) + 1);
+            if (i == categories.size() - 1) {
+                map.put("maxAge", 90000);
+            } else {
+                map.put("maxAge", (i+1) * 30);
+            }
+            map.put("provisioningPercentage", new Float((i + 1) * 5.5));
+            map.put("liabilityAccount", liability.getAccountID());
+            map.put("expenseAccount", expense.getAccountID());
+            list.add(map);
+        }
+        return list;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/provisioning/ProvisioningTransactionHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/provisioning/ProvisioningTransactionHelper.java
new file mode 100644
index 0000000..5e7bddb
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/provisioning/ProvisioningTransactionHelper.java
@@ -0,0 +1,92 @@
+/**
+ * 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.integrationtests.common.provisioning;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+
+public class ProvisioningTransactionHelper {
+
+    private static final String PROVISIONING_CATEGORY_URL = "/fineract-provider/api/v1/provisioningcategory?" + Utils.TENANT_IDENTIFIER ;
+    
+    private static final String CREATE_PROVISIONING_CRITERIA_URL = "/fineract-provider/api/v1/provisioningcriteria?" + Utils.TENANT_IDENTIFIER;
+    private static final String CREATE_PROVISIONING_ENTRY_URL = "/fineract-provider/api/v1/provisioningentries?" + Utils.TENANT_IDENTIFIER;
+    
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+    
+    public ProvisioningTransactionHelper(RequestSpecification requestSpec, ResponseSpecification responeSpec) {
+        this.requestSpec = requestSpec ;
+        this.responseSpec = responeSpec ;
+    }
+    
+    public ArrayList retrieveAllProvisioningCategories() {
+        return Utils.performServerGet(requestSpec, responseSpec, PROVISIONING_CATEGORY_URL, "");
+    }
+    
+    public Integer createProvisioningCriteria(final String provsioningCriteriaJson) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, CREATE_PROVISIONING_CRITERIA_URL, provsioningCriteriaJson, "resourceId");
+    }
+    
+    public Map retrieveProvisioningCriteria(final Integer criteriaId) {
+        String url = "/fineract-provider/api/v1/provisioningcriteria/"+criteriaId+"?"+Utils.TENANT_IDENTIFIER ;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "");
+    }
+    
+    public Integer updateProvisioningCriteria(final Integer criteriaId, final String provsioningCriteriaJson) {
+        String url = "/fineract-provider/api/v1/provisioningcriteria/"+criteriaId+"?"+Utils.TENANT_IDENTIFIER ;
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, url, provsioningCriteriaJson, "resourceId");
+    }
+    
+    public Integer deleteProvisioningCriteria(final Integer criteriaId) {
+        String url = "/fineract-provider/api/v1/provisioningcriteria/"+criteriaId+"?"+Utils.TENANT_IDENTIFIER;
+        return Utils.performServerDelete(this.requestSpec, this.responseSpec, url, "resourceId");
+    }
+    
+    public Integer createProvisioningEntries(final String provsioningCriteriaJson) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, CREATE_PROVISIONING_ENTRY_URL, provsioningCriteriaJson, "resourceId");
+    }
+    
+    public Integer updateProvisioningEntry(final String command, final Integer entryId, String jsonBody) {
+        String url = "/fineract-provider/api/v1/provisioningentries/" + entryId + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(requestSpec, responseSpec, url, jsonBody, "resourceId") ;
+    }
+    
+    public Map retrieveProvisioningEntry(final Integer provisioningEntry) {
+        String url = "/fineract-provider/api/v1/provisioningentries/"+provisioningEntry+"?"+Utils.TENANT_IDENTIFIER; ;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "") ;
+    }
+    
+    public Map retrieveProvisioningEntries(final Integer provisioningEntry) {
+        String url = "/fineract-provider/api/v1/provisioningentries/entries?entryId="+provisioningEntry+"&"+Utils.TENANT_IDENTIFIER; ;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "") ;
+    }
+    
+    public Map retrieveAllProvisioningEntries() {
+        String url = "/fineract-provider/api/v1/provisioningentries?dateFormat=dd MMMM yyyy"+"&"+"locale=en"+"&"+Utils.TENANT_IDENTIFIER; ;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "") ;
+    }
+    
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountHelper.java
new file mode 100644
index 0000000..5ec7cfd
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountHelper.java
@@ -0,0 +1,556 @@
+/**
+ * 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.integrationtests.common.recurringdeposit;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes" })
+public class RecurringDepositAccountHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public RecurringDepositAccountHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    private static final String RECURRING_DEPOSIT_ACCOUNT_URL = "/fineract-provider/api/v1/recurringdepositaccounts";
+    private static final String APPLY_RECURRING_DEPOSIT_ACCOUNT_URL = RECURRING_DEPOSIT_ACCOUNT_URL + "?" + Utils.TENANT_IDENTIFIER;
+    private static final String APPROVE_RECURRING_DEPOSIT_COMMAND = "approve";
+    private static final String UNDO_APPROVAL_RECURRING_DEPOSIT_COMMAND = "undoapproval";
+    private static final String REJECT_RECURRING_DEPOSIT_COMMAND = "reject";
+    private static final String WITHDRAWN_BY_CLIENT_RECURRING_DEPOSIT_COMMAND = "withdrawnByApplicant";
+    private static final String ACTIVATE_RECURRING_DEPOSIT_COMMAND = "activate";
+    private static final String CLOSE_RECURRING_DEPOSIT_COMMAND = "close";
+    private static final String POST_INTEREST_RECURRING_DEPOSIT_COMMAND = "postInterest";
+    private static final String CALCULATE_INTEREST_RECURRING_DEPOSIT_COMMAND = "calculateInterest";
+    private static final String CALCULATE_PREMATURE_AMOUNT_COMMAND = "calculatePrematureAmount";
+    private static final String PREMATURE_CLOSE_COMMAND = "prematureClose";
+    private static final String DEPOSIT_INTO_RECURRING_DEPOSIT_COMMAND = "deposit";
+    private static final String MODIFY_TRANSACTION_COMMAND = "modify";
+    private static final String UNDO_TRANSACTION_COMMAND = "undo";
+
+    private static final String LOCALE = "en_GB";
+    private static final String DIGITS_AFTER_DECIMAL = "4";
+    private static final String IN_MULTIPLES_OF = "100";
+    private static final String USD = "USD";
+    private static final String DAYS = "0";
+    private static final String WEEKS = "1";
+    private static final String MONTHS = "2";
+    private static final String YEARS = "3";
+    private static final String DAILY = "1";
+    private static final String MONTHLY = "4";
+    private static final String QUARTERLY = "5";
+    private static final String BI_ANNUALLY = "6";
+    private static final String ANNUALLY = "7";
+    private static final String INTEREST_CALCULATION_USING_DAILY_BALANCE = "1";
+    private static final String INTEREST_CALCULATION_USING_AVERAGE_DAILY_BALANCE = "2";
+    private static final String DAYS_360 = "360";
+    private static final String DAYS_365 = "365";
+    private static final String NONE = "1";
+    private static final String CASH_BASED = "2";
+    private static final String ACCRUAL_PERIODIC = "3";
+    private static final String ACCRUAL_UPFRONT = "4";
+
+    private String interestCompoundingPeriodType = MONTHLY;
+    private String interestPostingPeriodType = MONTHLY;
+    private String interestCalculationType = INTEREST_CALCULATION_USING_DAILY_BALANCE;
+    private final String lockinPeriodFrequency = "1";
+    private final String lockingPeriodFrequencyType = MONTHS;
+    private final String minDepositTerm = "6";
+    private final String minDepositTermTypeId = MONTHS;
+    private final String maxDepositTerm = "10";
+    private final String maxDepositTermTypeId = YEARS;
+    private final String inMultiplesOfDepositTerm = "2";
+    private final String inMultiplesOfDepositTermTypeId = MONTHS;
+    private final String preClosurePenalInterest = "2";
+    private final boolean preClosurePenalApplicable = true;
+    private final boolean isActiveChart = true;
+    private final String currencyCode = USD;
+    private String interestCalculationDaysInYearType = DAYS_365;
+    private final String depositAmount = "2000";
+    private final String depositPeriod = "14";
+    private final String depositPeriodFrequencyId = MONTHS;
+    private final String recurringFrequency = "1";
+    private final String recurringFrequencyType = MONTHS;
+    private final String mandatoryRecommendedDepositAmount = "2000";
+    private String submittedOnDate = "";
+    private String expectedFirstDepositOnDate = "";
+    private boolean isCalendarInherited = false;
+
+    public String build(final String clientId, final String productId, final String validFrom, final String validTo,
+            final String penalInterestType) {
+        final HashMap<String, Object> map = new HashMap<>();
+
+        List<HashMap<String, String>> chartSlabs = new ArrayList<HashMap<String, String>>();
+        HashMap<String, String> chartSlabsMap1 = new HashMap<>();
+        chartSlabsMap1.put("description", "First");
+        chartSlabsMap1.put("periodType", MONTHS);
+        chartSlabsMap1.put("fromPeriod", "1");
+        chartSlabsMap1.put("toPeriod", "6");
+        chartSlabsMap1.put("annualInterestRate", "5");
+        chartSlabsMap1.put("locale", LOCALE);
+        chartSlabs.add(0, chartSlabsMap1);
+
+        HashMap<String, String> chartSlabsMap2 = new HashMap<>();
+        chartSlabsMap2.put("description", "Second");
+        chartSlabsMap2.put("periodType", MONTHS);
+        chartSlabsMap2.put("fromPeriod", "7");
+        chartSlabsMap2.put("toPeriod", "12");
+        chartSlabsMap2.put("annualInterestRate", "6");
+        chartSlabsMap2.put("locale", LOCALE);
+        chartSlabs.add(1, chartSlabsMap2);
+
+        HashMap<String, String> chartSlabsMap3 = new HashMap<>();
+        chartSlabsMap3.put("description", "Third");
+        chartSlabsMap3.put("periodType", MONTHS);
+        chartSlabsMap3.put("fromPeriod", "13");
+        chartSlabsMap3.put("toPeriod", "18");
+        chartSlabsMap3.put("annualInterestRate", "7");
+        chartSlabsMap3.put("locale", LOCALE);
+        chartSlabs.add(2, chartSlabsMap3);
+
+        HashMap<String, String> chartSlabsMap4 = new HashMap<>();
+        chartSlabsMap4.put("description", "Fourth");
+        chartSlabsMap4.put("periodType", MONTHS);
+        chartSlabsMap4.put("fromPeriod", "19");
+        chartSlabsMap4.put("toPeriod", "24");
+        chartSlabsMap4.put("annualInterestRate", "8");
+        chartSlabsMap4.put("locale", LOCALE);
+        chartSlabs.add(3, chartSlabsMap4);
+
+        List<HashMap<String, Object>> charts = new ArrayList<HashMap<String, Object>>();
+        HashMap<String, Object> chartsMap = new HashMap<>();
+        chartsMap.put("fromDate", validFrom);
+        chartsMap.put("endDate", validTo);
+        chartsMap.put("dateFormat", "dd MMMM yyyy");
+        chartsMap.put("locale", LOCALE);
+        chartsMap.put("isActiveChart", this.isActiveChart);
+        chartsMap.put("chartSlabs", chartSlabs);
+        charts.add(chartsMap);
+
+        map.put("charts", charts);
+        map.put("productId", productId);
+        map.put("clientId", clientId);
+        map.put("interestCalculationDaysInYearType", this.interestCalculationDaysInYearType);
+        map.put("locale", LOCALE);
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("monthDayFormat", "dd MMM");
+        map.put("interestCalculationType", this.interestCalculationType);
+        map.put("interestCompoundingPeriodType", this.interestCompoundingPeriodType);
+        map.put("interestPostingPeriodType", this.interestPostingPeriodType);
+        map.put("lockinPeriodFrequency", this.lockinPeriodFrequency);
+        map.put("lockinPeriodFrequencyType", this.lockingPeriodFrequencyType);
+        map.put("preClosurePenalApplicable", "true");
+        map.put("minDepositTermTypeId", this.minDepositTermTypeId);
+        map.put("minDepositTerm", this.minDepositTerm);
+        map.put("maxDepositTermTypeId", this.maxDepositTermTypeId);
+        map.put("maxDepositTerm", this.maxDepositTerm);
+        map.put("preClosurePenalApplicable", this.preClosurePenalApplicable);
+        map.put("inMultiplesOfDepositTerm", this.inMultiplesOfDepositTerm);
+        map.put("inMultiplesOfDepositTermTypeId", this.inMultiplesOfDepositTermTypeId);
+        map.put("preClosurePenalInterest", this.preClosurePenalInterest);
+        map.put("preClosurePenalInterestOnTypeId", penalInterestType);
+        map.put("depositAmount", this.depositAmount);
+        map.put("depositPeriod", this.depositPeriod);
+        map.put("depositPeriodFrequencyId", this.depositPeriodFrequencyId);
+        map.put("submittedOnDate", this.submittedOnDate);
+        map.put("recurringFrequency", this.recurringFrequency);
+        map.put("recurringFrequencyType", this.recurringFrequencyType);
+        map.put("mandatoryRecommendedDepositAmount", this.mandatoryRecommendedDepositAmount);
+        map.put("expectedFirstDepositOnDate", this.expectedFirstDepositOnDate);
+        map.put("isCalendarInherited", this.isCalendarInherited);
+
+        String recurringDepositAccountJson = new Gson().toJson(map);
+        System.out.println(recurringDepositAccountJson);
+        return recurringDepositAccountJson;
+    }
+
+    public static Integer applyRecurringDepositApplication(final String recurringDepositAccountAsJson,
+            final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        System.out.println("--------------------- APPLYING FOR RECURRING DEPOSIT ACCOUNT ------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, APPLY_RECURRING_DEPOSIT_ACCOUNT_URL, recurringDepositAccountAsJson,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public static HashMap getRecurringDepositAccountById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer accountID) {
+        final String GET_RECURRING_DEPOSIT_BY_ID_URL = RECURRING_DEPOSIT_ACCOUNT_URL + "/" + accountID + "?" + Utils.TENANT_IDENTIFIER;
+        System.out.println("------------------------ RETRIEVING RECURRING DEPOSIT ACCOUNT BY ID -------------------------");
+        return Utils.performServerGet(requestSpec, responseSpec, GET_RECURRING_DEPOSIT_BY_ID_URL, "");
+    }
+
+    public HashMap getRecurringDepositSummary(final Integer accountID) {
+        final String URL = RECURRING_DEPOSIT_ACCOUNT_URL + "/" + accountID + "?" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "summary");
+        return response;
+    }
+
+    public static Float getInterestRate(ArrayList<ArrayList<HashMap>> interestSlabData, Integer depositPeriod) {
+
+        Float annualInterestRate = 0.0f;
+        for (Integer slabIndex = 0; slabIndex < interestSlabData.get(0).size(); slabIndex++) {
+            Integer fromPeriod = (Integer) interestSlabData.get(0).get(slabIndex).get("fromPeriod");
+            Integer toPeriod = (Integer) interestSlabData.get(0).get(slabIndex).get("toPeriod");
+            if (depositPeriod >= fromPeriod && depositPeriod <= toPeriod) {
+                annualInterestRate = (Float) interestSlabData.get(0).get(slabIndex).get("annualInterestRate");
+                break;
+            }
+        }
+
+        return annualInterestRate;
+    }
+
+    public static Float getPrincipalAfterCompoundingInterest(Calendar currentDate, Float principal, Float depositAmount, Integer depositPeriod,
+            double interestPerDay, Integer compoundingInterval, Integer postingInterval) {
+
+        Float totalInterest = 0.0f;
+        Float interestEarned = 0.0f;
+
+        for (int i = 1; i <= depositPeriod; i++) {
+            Integer daysInMonth = currentDate.getActualMaximum(Calendar.DATE);
+            principal += depositAmount;
+            for (int j = 0; j < daysInMonth; j++) {
+
+                interestEarned = (float) (principal * interestPerDay);
+                totalInterest += interestEarned;
+                if (compoundingInterval == 0) {
+                    principal += interestEarned;
+                }
+
+            }
+            if ((i % postingInterval) == 0 || i == depositPeriod) {
+                if (compoundingInterval != 0) {
+                    principal += totalInterest;
+                }
+                totalInterest = 0.0f;
+                System.out.println(principal);
+
+            }
+            currentDate.add(Calendar.MONTH, 1);
+            interestEarned = 0.0f;
+        }
+        return principal;
+    }
+
+    public HashMap updateRecurringDepositAccount(final String clientID, final String productID, final String accountID,
+            final String validFrom, final String validTo, final String penalInterestType, final String submittedOnDate) {
+
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DATE, -1);
+        final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
+        final String EXPECTED_FIRST_DEPOSIT_ON_ON_DATE = SUBMITTED_ON_DATE;
+        final String recurringDepositApplicationJSON = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec)
+                .withSubmittedOnDate(SUBMITTED_ON_DATE).withExpectedFirstDepositOnDate(EXPECTED_FIRST_DEPOSIT_ON_ON_DATE)
+                .build(clientID, productID, validFrom, validTo, penalInterestType);
+
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, RECURRING_DEPOSIT_ACCOUNT_URL + "/" + accountID + "?"
+                + Utils.TENANT_IDENTIFIER, recurringDepositApplicationJSON, CommonConstants.RESPONSE_CHANGES);
+    }
+
+    public HashMap updateInterestCalculationConfigForRecurringDeposit(final String clientID, final String productID,
+            final String accountID, final String submittedOnDate, final String validFrom, final String validTo,
+            final String numberOfDaysPerYear, final String penalInterestType, final String interestCalculationType,
+            final String interestCompoundingPeriodType, final String interestPostingPeriodType, final String expectedFirstDepositOnDate) {
+
+        final String recurringDepositApplicationJSON = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec) //
+                .withSubmittedOnDate(submittedOnDate) //
+                .withNumberOfDaysPerYear(numberOfDaysPerYear) //
+                .withInterestCalculationPeriodType(interestCalculationType) //
+                .withInterestCompoundingPeriodType(interestCompoundingPeriodType) //
+                .withInterestPostingPeriodType(interestPostingPeriodType) //
+                .withExpectedFirstDepositOnDate(expectedFirstDepositOnDate) //
+                .build(clientID, productID, validFrom, validTo, penalInterestType);
+
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, RECURRING_DEPOSIT_ACCOUNT_URL + "/" + accountID + "?"
+                + Utils.TENANT_IDENTIFIER, recurringDepositApplicationJSON, CommonConstants.RESPONSE_CHANGES);
+    }
+
+    public Integer updateTransactionForRecurringDeposit(final Integer accountID, final Integer transactionId, final String transactionDate,
+            final Float transactionAmount) {
+        System.out.println("--------------------------------- UPDATE RECURRING DEPOSIT TRANSACTION ------------------------------------");
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, RECURRING_DEPOSIT_ACCOUNT_URL + "/" + accountID
+                + "/transactions/" + transactionId + "?command=" + MODIFY_TRANSACTION_COMMAND,
+                getUpdateTransactionAsJSON(transactionDate, transactionAmount), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public Integer undoTransactionForRecurringDeposit(final Integer accountID, final Integer transactionId, final String transactionDate,
+            final Float transactionAmount) {
+        System.out.println("--------------------------------- UNDO RECURRING DEPOSIT TRANSACTION ------------------------------------");
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, RECURRING_DEPOSIT_ACCOUNT_URL + "/" + accountID
+                + "/transactions/" + transactionId + "?command=" + UNDO_TRANSACTION_COMMAND,
+                getUpdateTransactionAsJSON(transactionDate, transactionAmount), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public HashMap approveRecurringDeposit(final Integer recurringDepositAccountID, final String approvedOnDate) {
+        System.out
+                .println("--------------------------------- APPROVING RECURRING DEPOSIT APPLICATION ------------------------------------");
+        return performRecurringDepositApplicationActions(
+                createRecurringDepositOperationURL(APPROVE_RECURRING_DEPOSIT_COMMAND, recurringDepositAccountID),
+                getApproveRecurringDepositAccountAsJSON(approvedOnDate));
+    }
+
+    public HashMap undoApproval(final Integer recurringDepositAccountID) {
+        System.out
+                .println("--------------------------------- UNDO APPROVING RECURRING DEPOSIT APPLICATION -------------------------------");
+        final String undoBodyJson = "{'note':'UNDO APPROVAL'}";
+        return performRecurringDepositApplicationActions(
+                createRecurringDepositOperationURL(UNDO_APPROVAL_RECURRING_DEPOSIT_COMMAND, recurringDepositAccountID), undoBodyJson);
+    }
+
+    public HashMap rejectApplication(final Integer recurringDepositAccountID, final String rejectedOnDate) {
+        System.out.println("--------------------------------- REJECT RECURRING DEPOSIT APPLICATION -------------------------------");
+        return performRecurringDepositApplicationActions(
+                createRecurringDepositOperationURL(REJECT_RECURRING_DEPOSIT_COMMAND, recurringDepositAccountID),
+                getRejectedRecurringDepositAsJSON(rejectedOnDate));
+    }
+
+    public HashMap withdrawApplication(final Integer recurringDepositAccountID, final String withdrawApplicationOnDate) {
+        System.out.println("--------------------------------- WITHDRAW RECURRING DEPOSIT APPLICATION -------------------------------");
+        return performRecurringDepositApplicationActions(
+                createRecurringDepositOperationURL(WITHDRAWN_BY_CLIENT_RECURRING_DEPOSIT_COMMAND, recurringDepositAccountID),
+                getWithdrawnRecurringDepositAccountAsJSON(withdrawApplicationOnDate));
+    }
+
+    public HashMap activateRecurringDeposit(final Integer recurringDepositAccountID, final String activationDate) {
+        System.out
+                .println("---------------------------------- ACTIVATING RECURRING DEPOSIT APPLICATION ----------------------------------");
+        return performRecurringDepositApplicationActions(
+                createRecurringDepositOperationURL(ACTIVATE_RECURRING_DEPOSIT_COMMAND, recurringDepositAccountID),
+                getActivatedRecurringDepositAccountAsJSON(activationDate));
+    }
+
+    public Object deleteRecurringDepositApplication(final Integer recurringDepositAccountID, final String jsonAttributeToGetBack) {
+        System.out.println("---------------------------------- DELETE RECURRING DEPOSIT APPLICATION ----------------------------------");
+        return Utils.performServerDelete(this.requestSpec, this.responseSpec, RECURRING_DEPOSIT_ACCOUNT_URL + "/"
+                + recurringDepositAccountID + "?" + Utils.TENANT_IDENTIFIER, jsonAttributeToGetBack);
+
+    }
+
+    public Integer calculateInterestForRecurringDeposit(final Integer recurringDepositAccountId) {
+        System.out.println("--------------------------------- CALCULATING INTEREST FOR RECURRING DEPOSIT --------------------------------");
+        return (Integer) performRecurringDepositActions(
+                createRecurringDepositCalculateInterestURL(CALCULATE_INTEREST_RECURRING_DEPOSIT_COMMAND, recurringDepositAccountId),
+                getCalculatedInterestForRecurringDepositApplicationAsJSON(), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public Integer postInterestForRecurringDeposit(final Integer recurringDepositAccountId) {
+        System.out.println("--------------------------------- POST INTEREST FOR RECURRING DEPOSIT --------------------------------");
+        return (Integer) performRecurringDepositActions(
+                createRecurringDepositCalculateInterestURL(POST_INTEREST_RECURRING_DEPOSIT_COMMAND, recurringDepositAccountId),
+                getCalculatedInterestForRecurringDepositApplicationAsJSON(), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public Integer depositToRecurringDepositAccount(final Integer recurringDepositAccountId, final Float depositAmount,
+            final String depositedOnDate) {
+        System.out.println("--------------------------------- DEPOSIT TO RECURRING DEPOSIT ACCOUNT --------------------------------");
+        return (Integer) performRecurringDepositActions(
+                createDepositToRecurringDepositURL(DEPOSIT_INTO_RECURRING_DEPOSIT_COMMAND, recurringDepositAccountId),
+                getDepositToRecurringDepositAccountAsJSON(depositAmount, depositedOnDate), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public HashMap calculatePrematureAmountForRecurringDeposit(final Integer recurringDepositAccountId, final String closedOnDate) {
+        System.out.println("--------------------- CALCULATING PREMATURE AMOUNT FOR RECURRING DEPOSIT ----------------------------");
+        return (HashMap) performRecurringDepositActions(
+                createRecurringDepositCalculateInterestURL(CALCULATE_PREMATURE_AMOUNT_COMMAND, recurringDepositAccountId),
+                getCalculatedPrematureAmountForRecurringDepositAccountAsJSON(closedOnDate), "");
+    }
+
+    public Object prematureCloseForRecurringDeposit(final Integer recurringDepositAccountId, final String closedOnDate,
+            final String closureType, final Integer toSavingsId, final String jsonAttributeToGetBack) {
+        System.out.println("--------------------- PREMATURE CLOSE FOR RECURRING DEPOSIT ----------------------------");
+        return performRecurringDepositActions(
+                createRecurringDepositCalculateInterestURL(PREMATURE_CLOSE_COMMAND, recurringDepositAccountId),
+                getPrematureCloseForRecurringDepositAccountAsJSON(closedOnDate, closureType, toSavingsId), jsonAttributeToGetBack);
+    }
+
+    private String getApproveRecurringDepositAccountAsJSON(final String approvedOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("approvedOnDate", approvedOnDate);
+        map.put("note", "Approval NOTE");
+        String recurringDepositAccountApproveJson = new Gson().toJson(map);
+        System.out.println(recurringDepositAccountApproveJson);
+        return recurringDepositAccountApproveJson;
+    }
+
+    private String getUpdateTransactionAsJSON(final String transactionDate, final Float transactionAmount) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("transactionDate", transactionDate);
+        map.put("transactionAmount", transactionAmount);
+        String updateTransactionJson = new Gson().toJson(map);
+        System.out.println(updateTransactionJson);
+        return updateTransactionJson;
+    }
+
+    private String getRejectedRecurringDepositAsJSON(final String rejectedOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("rejectedOnDate", rejectedOnDate);
+        map.put("note", "Rejected NOTE");
+        String recurringDepositAccountJson = new Gson().toJson(map);
+        System.out.println(recurringDepositAccountJson);
+        return recurringDepositAccountJson;
+    }
+
+    private String getWithdrawnRecurringDepositAccountAsJSON(final String withdrawnApplicationOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("withdrawnOnDate", withdrawnApplicationOnDate);
+        map.put("note", "Withdraw NOTE");
+        String recurringDepositAccountJson = new Gson().toJson(map);
+        System.out.println(recurringDepositAccountJson);
+        return recurringDepositAccountJson;
+    }
+
+    private String getActivatedRecurringDepositAccountAsJSON(final String activationDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("activatedOnDate", activationDate);
+        String recurringDepositAccountActivateJson = new Gson().toJson(map);
+        System.out.println(recurringDepositAccountActivateJson);
+        return recurringDepositAccountActivateJson;
+    }
+
+    private String getCalculatedInterestForRecurringDepositApplicationAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        String recurringDepositAccountCalculatedInterestJson = new Gson().toJson(map);
+        System.out.println(recurringDepositAccountCalculatedInterestJson);
+        return recurringDepositAccountCalculatedInterestJson;
+    }
+
+    private String getCalculatedPrematureAmountForRecurringDepositAccountAsJSON(final String closedOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("closedOnDate", closedOnDate);
+        String recurringDepositAccountPrematureClosureJson = new Gson().toJson(map);
+        System.out.println(recurringDepositAccountPrematureClosureJson);
+        return recurringDepositAccountPrematureClosureJson;
+    }
+
+    private String getDepositToRecurringDepositAccountAsJSON(final Float depositAmount, final String depositedOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("transactionAmount", depositAmount);
+        map.put("transactionDate", depositedOnDate);
+        String recurringDepositAccountPrematureClosureJson = new Gson().toJson(map);
+        System.out.println(recurringDepositAccountPrematureClosureJson);
+        return recurringDepositAccountPrematureClosureJson;
+    }
+
+    private String getPrematureCloseForRecurringDepositAccountAsJSON(final String closedOnDate, final String closureType,
+            final Integer toSavingsId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("closedOnDate", closedOnDate);
+        map.put("onAccountClosureId", closureType);
+        if (toSavingsId != null) {
+            map.put("toSavingsAccountId", toSavingsId);
+            map.put("transferDescription", "Transferring To Savings Account");
+        }
+        String recurringDepositAccountPrematureCloseJson = new Gson().toJson(map);
+        System.out.println(recurringDepositAccountPrematureCloseJson);
+        return recurringDepositAccountPrematureCloseJson;
+    }
+
+    private String createRecurringDepositOperationURL(final String command, final Integer recurringDepositAccountID) {
+        return RECURRING_DEPOSIT_ACCOUNT_URL + "/" + recurringDepositAccountID + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private Object performRecurringDepositActions(final String postURLForRecurringDeposit, final String jsonToBeSent,
+            final String jsonAttributeToGetBack) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForRecurringDeposit, jsonToBeSent,
+                jsonAttributeToGetBack);
+    }
+
+    private HashMap performRecurringDepositApplicationActions(final String postURLForRecurringDepositAction, final String jsonToBeSent) {
+        HashMap status = null;
+        final HashMap response = Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForRecurringDepositAction,
+                jsonToBeSent, CommonConstants.RESPONSE_CHANGES);
+        if (response != null) {
+            status = (HashMap) response.get("status");
+        }
+        return status;
+    }
+
+    private String createRecurringDepositCalculateInterestURL(final String command, final Integer recurringDepositAccountID) {
+        return RECURRING_DEPOSIT_ACCOUNT_URL + "/" + recurringDepositAccountID + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private String createDepositToRecurringDepositURL(final String command, final Integer recurringDepositAccountID) {
+        return RECURRING_DEPOSIT_ACCOUNT_URL + "/" + recurringDepositAccountID + "/transactions" + "?command=" + command + "&"
+                + Utils.TENANT_IDENTIFIER;
+    }
+
+    public RecurringDepositAccountHelper withSubmittedOnDate(final String recurringDepositApplicationSubmittedDate) {
+        this.submittedOnDate = recurringDepositApplicationSubmittedDate;
+        return this;
+    }
+
+    public RecurringDepositAccountHelper withExpectedFirstDepositOnDate(final String recurringDepositApplicationExpectedFirstDepositOnDate) {
+        this.expectedFirstDepositOnDate = recurringDepositApplicationExpectedFirstDepositOnDate;
+        return this;
+    }
+
+    public RecurringDepositAccountHelper withNumberOfDaysPerYear(final String numberOfDaysPerYearTypeId) {
+        this.interestCalculationDaysInYearType = numberOfDaysPerYearTypeId;
+        return this;
+    }
+
+    public RecurringDepositAccountHelper withInterestCalculationPeriodType(final String interestCalculationTypeId) {
+        this.interestCalculationType = interestCalculationTypeId;
+        return this;
+    }
+
+    public RecurringDepositAccountHelper withInterestCompoundingPeriodType(final String interestCompoundingPeriodTypeId) {
+        this.interestCompoundingPeriodType = interestCompoundingPeriodTypeId;
+        return this;
+    }
+
+    public RecurringDepositAccountHelper withInterestPostingPeriodType(final String interestPostingPeriodTypeId) {
+        this.interestPostingPeriodType = interestPostingPeriodTypeId;
+        return this;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountStatusChecker.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountStatusChecker.java
new file mode 100644
index 0000000..95cd69e
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountStatusChecker.java
@@ -0,0 +1,96 @@
+/**
+ * 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.integrationtests.common.recurringdeposit;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class RecurringDepositAccountStatusChecker {
+
+    private static final String RECURRING_DEPOSIT_ACCOUNT_URL = "/fineract-provider/api/v1/recurringdepositaccounts";
+
+    public static void verifyRecurringDepositIsApproved(final HashMap recurringDepositStatusHashMap) {
+        System.out.println("-------------------- VERIFYING RECURRING DEPOSIT APPLICATION IS APPROVED --------------------");
+        assertTrue("Error in Approving Recurring deposit application", getStatus(recurringDepositStatusHashMap, "approved"));
+        System.out.println(recurringDepositStatusHashMap);
+    }
+
+    public static void verifyRecurringDepositIsPending(final HashMap recurringDepositStatusHashMap) {
+        System.out.println("-------------------- VERIFYING RECURRING DEPOSIT APPLICATION IS PENDING --------------------");
+        assertTrue("RECURRING DEPOSIT ACCOUNT IS NOT IN PENDING STATE",
+                getStatus(recurringDepositStatusHashMap, "submittedAndPendingApproval"));
+        System.out.println(recurringDepositStatusHashMap);
+    }
+
+    public static void verifyRecurringDepositIsActive(final HashMap recurringDepositStatusHashMap) {
+        System.out.println("----------------- VERIFYING RECURRING DEPOSIT APPLICATION IS ACTIVE -----------------");
+        assertTrue("ERROR IN ACTIVATING THE RECURRING DEPOSIT APPLICATION", getStatus(recurringDepositStatusHashMap, "active"));
+        System.out.println(recurringDepositStatusHashMap);
+    }
+
+    public static void verifyRecurringDepositIsRejected(final HashMap recurringDepositStatusHashMap) {
+        System.out.println("-------------- VERIFYING RECURRING DEPOSIT APPLICATION IS REJECTED ----------------");
+        assertTrue("ERROR IN REJECTING THE RECURRING DEPOSIT APPLICATION", getStatus(recurringDepositStatusHashMap, "rejected"));
+        System.out.println(recurringDepositStatusHashMap);
+    }
+
+    public static void verifyRecurringDepositIsWithdrawn(final HashMap recurringDepositStatusHashMap) {
+        System.out.println("---------------- VERIFYING RECURRING DEPOSIT APPLICATION IS WITHDRAWN ----------------");
+        assertTrue("ERROR IN WITHDRAW  THE RECURRING DEPOSIT APPLICATION", getStatus(recurringDepositStatusHashMap, "withdrawnByApplicant"));
+        System.out.println(recurringDepositStatusHashMap);
+    }
+
+    public static void verifyRecurringDepositAccountIsClosed(final HashMap recurringDepositStatusHashMap) {
+        System.out.println("--------------------- VERIFYING RECURRING DEPOSIT APPLICATION IS CLOSED ---------------------");
+        assertTrue("ERROR IN CLOSING THE RECURRING DEPOSIT APPLICATION", getStatus(recurringDepositStatusHashMap, "closed"));
+        System.out.println(recurringDepositStatusHashMap);
+    }
+
+    public static void verifyRecurringDepositAccountIsNotActive(final HashMap recurringDepositStatusHashMap) {
+        System.out.println("------------------ VERIFYING RECURRING DEPOSIT APPLICATION IS INACTIVE --------------------");
+        Assert.assertFalse(getStatus(recurringDepositStatusHashMap, "active"));
+        System.out.println(recurringDepositStatusHashMap);
+    }
+
+    public static HashMap getStatusOfRecurringDepositAccount(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String recurringDepositAccountID) {
+        final String GET_STATUS_OF_RECURRING_DEPOSIT_ACCOUNT_URL = RECURRING_DEPOSIT_ACCOUNT_URL + "/" + recurringDepositAccountID + "?"
+                + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, GET_STATUS_OF_RECURRING_DEPOSIT_ACCOUNT_URL, "status");
+    }
+
+    public static void verifyRecurringDepositAccountIsPrematureClosed(HashMap recurringDepositStatusHashMap) {
+        System.out.println("--------------------- VERIFYING RECURRING DEPOSIT APPLICATION IS CLOSED ---------------------");
+        assertTrue("ERROR IN PREMATURELY CLOSING THE RECURRING DEPOSIT ACCOUNT",
+                getStatus(recurringDepositStatusHashMap, "prematureClosed"));
+        System.out.println(recurringDepositStatusHashMap);
+    }
+
+    private static boolean getStatus(final HashMap recurringDepositStatusMap, final String recurringDepositStatusString) {
+        return (Boolean) recurringDepositStatusMap.get(recurringDepositStatusString);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java
new file mode 100644
index 0000000..71a92f8
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java
@@ -0,0 +1,259 @@
+/**
+ * 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.integrationtests.common.recurringdeposit;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "unused", "rawtypes" })
+public class RecurringDepositProductHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public RecurringDepositProductHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    private static final String RECURRING_DEPOSIT_PRODUCT_URL = "/fineract-provider/api/v1/recurringdepositproducts";
+    private static final String INTEREST_CHART_URL = "/fineract-provider/api/v1/interestratecharts";
+    private static final String CREATE_RECURRING_DEPOSIT_PRODUCT_URL = RECURRING_DEPOSIT_PRODUCT_URL + "?" + Utils.TENANT_IDENTIFIER;
+
+    private static final String LOCALE = "en_GB";
+    private static final String DIGITS_AFTER_DECIMAL = "4";
+    private static final String IN_MULTIPLES_OF = "100";
+    private static final String USD = "USD";
+    private static final String DAYS = "0";
+    private static final String WEEKS = "1";
+    private static final String MONTHS = "2";
+    private static final String YEARS = "3";
+    private static final String DAILY = "1";
+    private static final String MONTHLY = "4";
+    private static final String QUARTERLY = "5";
+    private static final String ANNUALLY = "7";
+    private static final String INTEREST_CALCULATION_USING_DAILY_BALANCE = "1";
+    private static final String INTEREST_CALCULATION_USING_AVERAGE_DAILY_BALANCE = "2";
+    private static final String DAYS_360 = "360";
+    private static final String DAYS_365 = "365";
+    private static final String NONE = "1";
+    private static final String CASH_BASED = "2";
+    private static final String ACCRUAL_PERIODIC = "3";
+    private static final String ACCRUAL_UPFRONT = "4";
+    private static final String WHOLE_TERM = "1";
+    private static final String TILL_PREMATURE_WITHDRAWAL = "2";
+
+    private final String name = Utils.randomNameGenerator("RECURRING_DEPOSIT_PRODUCT_", 6);
+    private final String shortName = Utils.randomNameGenerator("", 4);
+    private final String description = Utils.randomNameGenerator("", 20);
+    private final String interestCompoundingPeriodType = MONTHLY;
+    private final String interestPostingPeriodType = MONTHLY;
+    private final String interestCalculationType = INTEREST_CALCULATION_USING_DAILY_BALANCE;
+    private String accountingRule = NONE;
+    private final String lockinPeriodFrequency = "1";
+    private final String lockingPeriodFrequencyType = MONTHS;
+    private final String minDepositTerm = "6";
+    private final String minDepositTermTypeId = MONTHS;
+    private final String maxDepositTerm = "10";
+    private final String maxDepositTermTypeId = YEARS;
+    private final String inMultiplesOfDepositTerm = "2";
+    private final String inMultiplesOfDepositTermTypeId = MONTHS;
+    private final String preClosurePenalInterest = "2";
+    private final String preClosurePenalInterestOnTypeId = WHOLE_TERM;
+    private final boolean preClosurePenalApplicable = true;
+    private final String currencyCode = USD;
+    private final String interestCalculationDaysInYearType = DAYS_365;
+    private final boolean isMandatoryDeposit = false;
+    private final String recurringFrequencyType = MONTHS;
+    private final String recurringFrequency = "1";
+    private final String depositAmount = "100000";
+    private final String minDepositAmount = "100";
+    private final String maxDepositAmount = "1000000";
+    private Account[] accountList = null;
+
+    public String build(final String validFrom, final String validTo) {
+        final HashMap<String, Object> map = new HashMap<>();
+
+        List<HashMap<String, String>> chartSlabs = new ArrayList<HashMap<String, String>>();
+        HashMap<String, String> chartSlabsMap1 = new HashMap<>();
+        chartSlabsMap1.put("description", "First");
+        chartSlabsMap1.put("periodType", MONTHS);
+        chartSlabsMap1.put("fromPeriod", "1");
+        chartSlabsMap1.put("toPeriod", "6");
+        chartSlabsMap1.put("annualInterestRate", "5");
+        chartSlabsMap1.put("locale", LOCALE);
+        chartSlabs.add(0, chartSlabsMap1);
+
+        HashMap<String, String> chartSlabsMap2 = new HashMap<>();
+        chartSlabsMap2.put("description", "Second");
+        chartSlabsMap2.put("periodType", MONTHS);
+        chartSlabsMap2.put("fromPeriod", "7");
+        chartSlabsMap2.put("toPeriod", "12");
+        chartSlabsMap2.put("annualInterestRate", "6");
+        chartSlabsMap2.put("locale", LOCALE);
+        chartSlabs.add(1, chartSlabsMap2);
+
+        HashMap<String, String> chartSlabsMap3 = new HashMap<>();
+        chartSlabsMap3.put("description", "Third");
+        chartSlabsMap3.put("periodType", MONTHS);
+        chartSlabsMap3.put("fromPeriod", "13");
+        chartSlabsMap3.put("toPeriod", "18");
+        chartSlabsMap3.put("annualInterestRate", "7");
+        chartSlabsMap3.put("locale", LOCALE);
+        chartSlabs.add(2, chartSlabsMap3);
+
+        HashMap<String, String> chartSlabsMap4 = new HashMap<>();
+        chartSlabsMap4.put("description", "Fourth");
+        chartSlabsMap4.put("periodType", MONTHS);
+        chartSlabsMap4.put("fromPeriod", "19");
+        chartSlabsMap4.put("toPeriod", "24");
+        chartSlabsMap4.put("annualInterestRate", "8");
+        chartSlabsMap4.put("locale", LOCALE);
+        chartSlabs.add(3, chartSlabsMap4);
+
+        List<HashMap<String, Object>> charts = new ArrayList<HashMap<String, Object>>();
+        HashMap<String, Object> chartsMap = new HashMap<>();
+        chartsMap.put("fromDate", validFrom);
+        chartsMap.put("endDate", validTo);
+        chartsMap.put("dateFormat", "dd MMMM yyyy");
+        chartsMap.put("locale", LOCALE);
+        chartsMap.put("chartSlabs", chartSlabs);
+        charts.add(chartsMap);
+
+        map.put("charts", charts);
+        map.put("name", this.name);
+        map.put("shortName", this.shortName);
+        map.put("description", this.description);
+        map.put("currencyCode", this.currencyCode);
+        map.put("interestCalculationDaysInYearType", this.interestCalculationDaysInYearType);
+        map.put("locale", LOCALE);
+        map.put("digitsAfterDecimal", DIGITS_AFTER_DECIMAL);
+        map.put("inMultiplesOf", IN_MULTIPLES_OF);
+        map.put("interestCalculationType", this.interestCalculationType);
+        map.put("interestCompoundingPeriodType", this.interestCompoundingPeriodType);
+        map.put("interestPostingPeriodType", this.interestPostingPeriodType);
+        map.put("accountingRule", this.accountingRule);
+        map.put("lockinPeriodFrequency", this.lockinPeriodFrequency);
+        map.put("lockinPeriodFrequencyType", this.lockingPeriodFrequencyType);
+        map.put("preClosurePenalApplicable", "true");
+        map.put("minDepositTermTypeId", this.minDepositTermTypeId);
+        map.put("minDepositTerm", this.minDepositTerm);
+        map.put("maxDepositTermTypeId", this.maxDepositTermTypeId);
+        map.put("maxDepositTerm", this.maxDepositTerm);
+        map.put("preClosurePenalApplicable", this.preClosurePenalApplicable);
+        map.put("inMultiplesOfDepositTerm", this.inMultiplesOfDepositTerm);
+        map.put("inMultiplesOfDepositTermTypeId", this.inMultiplesOfDepositTermTypeId);
+        map.put("preClosurePenalInterest", this.preClosurePenalInterest);
+        map.put("preClosurePenalInterestOnTypeId", this.preClosurePenalInterestOnTypeId);
+        map.put("isMandatoryDeposit", this.isMandatoryDeposit);
+        map.put("recurringFrequencyType", this.recurringFrequencyType);
+        map.put("depositAmount", this.depositAmount);
+        map.put("recurringFrequency", this.recurringFrequency);
+        map.put("depositAmount", this.depositAmount);
+        map.put("minDepositAmount", this.minDepositAmount);
+        map.put("maxDepositAmount", this.maxDepositAmount);
+
+        if (this.accountingRule.equals(CASH_BASED)) {
+            map.putAll(getAccountMappingForCashBased());
+        }
+
+        String RecurringDepositProductCreateJson = new Gson().toJson(map);
+        System.out.println(RecurringDepositProductCreateJson);
+        return RecurringDepositProductCreateJson;
+    }
+
+    public RecurringDepositProductHelper withAccountingRuleAsNone() {
+        this.accountingRule = NONE;
+        return this;
+    }
+
+    public RecurringDepositProductHelper withAccountingRuleAsCashBased(final Account[] account_list) {
+        this.accountingRule = CASH_BASED;
+        this.accountList = account_list;
+        return this;
+    }
+
+    private Map<String, String> getAccountMappingForCashBased() {
+        final Map<String, String> map = new HashMap<>();
+        if (accountList != null) {
+            for (int i = 0; i < this.accountList.length; i++) {
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.ASSET)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("savingsReferenceAccountId", ID);
+                }
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("savingsControlAccountId", ID);
+                    map.put("transfersInSuspenseAccountId", ID);
+                }
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("interestOnSavingsAccountId", ID);
+                }
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.INCOME)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("incomeFromFeeAccountId", ID);
+                    map.put("incomeFromPenaltyAccountId", ID);
+                }
+            }
+        }
+        return map;
+    }
+
+    public static Integer createRecurringDepositProduct(final String recurrungDepositProductCreateJson,
+            final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        System.out.println("------------------ CREATING RECURRING DEPOSIT PRODUCT--------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_RECURRING_DEPOSIT_PRODUCT_URL, recurrungDepositProductCreateJson,
+                "resourceId");
+    }
+
+    public static ArrayList retrieveAllRecurringDepositProducts(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        System.out.println("----------------- RETRIEVING ALL RECURRING DEPOSIT PRODUCTS---------------------------");
+        final ArrayList response = Utils.performServerGet(requestSpec, responseSpec, RECURRING_DEPOSIT_PRODUCT_URL + "?"
+                + Utils.TENANT_IDENTIFIER, "");
+        return response;
+    }
+
+    public static HashMap retrieveRecurringDepositProductById(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String productId) {
+        System.out.println("-------------------- RETRIEVING RECURRING DEPOSIT PRODUCT BY ID --------------------------");
+        final String GET_RD_PRODUCT_BY_ID_URL = RECURRING_DEPOSIT_PRODUCT_URL + "/" + productId + "?" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_RD_PRODUCT_BY_ID_URL, "");
+        return response;
+    }
+
+    public static ArrayList getInterestRateChartSlabsByProductId(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final Integer productId) {
+        System.out.println("-------------------- RETRIEVE INTEREST CHART BY PRODUCT ID ---------------------");
+        final ArrayList response = Utils.performServerGet(requestSpec, responseSpec, INTEREST_CHART_URL + "?productId=" + productId,
+                "chartSlabs");
+        return response;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/AccountTransferHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/AccountTransferHelper.java
new file mode 100644
index 0000000..f1928bf
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/AccountTransferHelper.java
@@ -0,0 +1,98 @@
+/**
+ * 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.integrationtests.common.savings;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class AccountTransferHelper {
+
+    private static final String ACCOUNT_TRANSFER_URL = "/fineract-provider/api/v1/accounttransfers";
+    private static final String LOAN_REFUND_BY_TRANSFER_URL = "/fineract-provider/api/v1/accounttransfers/refundByTransfer";
+    private static final String LOCALE = "en_GB";
+    private static final String OFFICE_ID = "1";
+    private static final String TRANSFER_DESCRIPTION = "Transfer";
+    public static final String ACCOUNT_TRANSFER_DATE = "01 March 2013";
+
+    private String transferDate = "";
+    private String officeId = OFFICE_ID;
+    private String transferDescription = TRANSFER_DESCRIPTION;
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+
+    public AccountTransferHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public String build(final String fromAccountId, final String fromClientId, final String toAccountId, final String toClientId,
+            final String fromAccountType, final String toAccountType, final String transferAmount) {
+
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("locale", LOCALE);
+        map.put("fromClientId", fromClientId);
+        map.put("fromAccountId", fromAccountId);
+        map.put("fromAccountType", fromAccountType);
+        map.put("fromOfficeId", this.officeId);
+        map.put("toClientId", toClientId);
+        map.put("toAccountId", toAccountId);
+        map.put("toAccountType", toAccountType);
+        map.put("toOfficeId", this.officeId);
+        map.put("transferDate", this.transferDate);
+        map.put("transferAmount", transferAmount);
+        map.put("transferDescription", this.transferDescription);
+        String savingsApplicationJSON = new Gson().toJson(map);
+        System.out.println(savingsApplicationJSON);
+        return savingsApplicationJSON;
+    }
+
+    public AccountTransferHelper withTransferOnDate(final String savingsAccountTransferDate) {
+        this.transferDate = savingsAccountTransferDate;
+        return this;
+    }
+
+    public Integer accountTransfer(final Integer fromClientId, final Integer fromAccountId, final Integer toClientId,
+            final Integer toAccountId, final String fromAccountType, final String toAccountType, final String transferAmount) {
+        System.out.println("--------------------------------ACCOUNT TRANSFER--------------------------------");
+        final String accountTransferJSON = new AccountTransferHelper(this.requestSpec, this.responseSpec) //
+                .withTransferOnDate(ACCOUNT_TRANSFER_DATE) //
+                .build(fromAccountId.toString(), fromClientId.toString(), toAccountId.toString(), toClientId.toString(), fromAccountType,
+                        toAccountType, transferAmount);
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, ACCOUNT_TRANSFER_URL + "?" + Utils.TENANT_IDENTIFIER,
+                accountTransferJSON, "savingsId");
+    }
+    
+    public Integer refundLoanByTransfer(final String date, final Integer fromClientId, final Integer fromAccountId, final Integer toClientId,
+            final Integer toAccountId, final String fromAccountType, final String toAccountType, final String transferAmount) {
+        System.out.println("--------------------------------ACCOUNT TRANSFER--------------------------------");
+        final String accountTransferJSON = new AccountTransferHelper(this.requestSpec, this.responseSpec) //
+                .withTransferOnDate(date) //
+                .build(fromAccountId.toString(), fromClientId.toString(), toAccountId.toString(), toClientId.toString(), fromAccountType,
+                        toAccountType, transferAmount);
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, LOAN_REFUND_BY_TRANSFER_URL + "?" + Utils.TENANT_IDENTIFIER,
+                accountTransferJSON, "savingsId");
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..e78a9cd
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
@@ -0,0 +1,485 @@
+/**
+ * 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.integrationtests.common.savings;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.Assert;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({ "rawtypes" })
+public class SavingsAccountHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    private static final String SAVINGS_ACCOUNT_URL = "/fineract-provider/api/v1/savingsaccounts";
+    private static final String APPROVE_SAVINGS_COMMAND = "approve";
+    private static final String UNDO_APPROVAL_SAVINGS_COMMAND = "undoApproval";
+    private static final String ACTIVATE_SAVINGS_COMMAND = "activate";
+    private static final String REJECT_SAVINGS_COMMAND = "reject";
+    private static final String WITHDRAWN_BY_CLIENT_SAVINGS_COMMAND = "withdrawnByApplicant";
+    private static final String CALCULATE_INTEREST_SAVINGS_COMMAND = "calculateInterest";
+    private static final String POST_INTEREST_SAVINGS_COMMAND = "postInterest";
+    private static final String CLOSE_SAVINGS_COMMAND = "close";
+
+    private static final String DEPOSIT_SAVINGS_COMMAND = "deposit";
+    private static final String WITHDRAW_SAVINGS_COMMAND = "withdrawal";
+    private static final String MODIFY_TRASACTION_COMMAND = "modify";
+    private static final String UNDO_TRASACTION_COMMAND = "undo";
+
+    public static final String CREATED_DATE = "08 January 2013";
+    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 LAST_TRANSACTION_DATE = "01 March 2013";
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    public SavingsAccountHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public static String getFutureDate() {
+        SimpleDateFormat sdf = new SimpleDateFormat(CommonConstants.dateFormat, Locale.US);
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 1);
+        return sdf.format(calendar.getTime());
+    }
+
+    public Integer applyForSavingsApplication(final Integer ID, final Integer savingsProductID, final String accountType) {
+        return applyForSavingsApplicationOnDate(ID, savingsProductID, accountType, CREATED_DATE);
+    }
+
+    public Integer applyForSavingsApplicationOnDate(final Integer ID, final Integer savingsProductID, final String accountType,
+            final String submittedOnDate) {
+        System.out.println("--------------------------------APPLYING FOR SAVINGS APPLICATION--------------------------------");
+        final String savingsApplicationJSON = new SavingsApplicationTestBuilder() //
+                .withSubmittedOnDate(submittedOnDate) //
+                .build(ID.toString(), savingsProductID.toString(), accountType);
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, SAVINGS_ACCOUNT_URL + "?" + Utils.TENANT_IDENTIFIER,
+                savingsApplicationJSON, "savingsId");
+    }
+
+    public HashMap updateSavingsAccount(final Integer ID, final Integer savingsProductID, final Integer savingsId, final String accountType) {
+        final String savingsApplicationJSON = new SavingsApplicationTestBuilder() //
+                .withSubmittedOnDate(CREATED_DATE_PLUS_ONE) //
+                .build(ID.toString(), savingsProductID.toString(), accountType);
+
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, SAVINGS_ACCOUNT_URL + "/" + savingsId + "?"
+                + Utils.TENANT_IDENTIFIER, savingsApplicationJSON, CommonConstants.RESPONSE_CHANGES);
+    }
+
+    public HashMap approveSavings(final Integer savingsID) {
+        return approveSavingsOnDate(savingsID, null);
+    }
+
+    public HashMap approveSavingsOnDate(final Integer savingsID, final String approvalDate) {
+        System.out.println("--------------------------------- APPROVING SAVINGS APPLICATION ------------------------------------");
+        final String savingsOperationURL = createSavingsOperationURL(APPROVE_SAVINGS_COMMAND, savingsID);
+        if (approvalDate == null || approvalDate == "")
+            return performSavingApplicationActions(savingsOperationURL, getApproveSavingsAsJSON());
+        return performSavingApplicationActions(savingsOperationURL, getApproveSavingsAsJsonOnDate(approvalDate));
+    }
+
+    public HashMap undoApproval(final Integer savingsID) {
+        System.out.println("--------------------------------- UNDO APPROVING SAVINGS APPLICATION -------------------------------");
+        final String undoBodyJson = "{'note':'UNDO APPROVAL'}";
+        return performSavingApplicationActions(createSavingsOperationURL(UNDO_APPROVAL_SAVINGS_COMMAND, savingsID), undoBodyJson);
+    }
+
+    public HashMap rejectApplication(final Integer savingsID) {
+        System.out.println("--------------------------------- REJECT SAVINGS APPLICATION -------------------------------");
+        return performSavingApplicationActions(createSavingsOperationURL(REJECT_SAVINGS_COMMAND, savingsID),
+                getRejectedSavingsAsJSON(CREATED_DATE_PLUS_ONE));
+    }
+
+    public List rejectApplicationWithErrorCode(final Integer savingsId, final String date) {
+        System.out.println("--------------------------------- REJECT SAVINGS APPLICATION -------------------------------");
+        return (List) performSavingActions(createSavingsOperationURL(REJECT_SAVINGS_COMMAND, savingsId), getRejectedSavingsAsJSON(date),
+                CommonConstants.RESPONSE_ERROR);
+    }
+
+    public HashMap withdrawApplication(final Integer savingsID) {
+        System.out.println("--------------------------------- Withdraw SAVINGS APPLICATION -------------------------------");
+        return performSavingApplicationActions(createSavingsOperationURL(WITHDRAWN_BY_CLIENT_SAVINGS_COMMAND, savingsID),
+                getWithdrawnSavingsAsJSON());
+    }
+
+    public HashMap activateSavings(final Integer savingsID) {
+        System.out.println("---------------------------------- ACTIVATING SAVINGS APPLICATION ----------------------------------");
+        return performSavingApplicationActions(createSavingsOperationURL(ACTIVATE_SAVINGS_COMMAND, savingsID), getActivatedSavingsAsJSON());
+    }
+
+    public HashMap closeSavingsAccount(final Integer savingsID, String withdrawBalance) {
+        System.out.println("---------------------------------- CLOSE SAVINGS APPLICATION ----------------------------------");
+        return performSavingApplicationActions(createSavingsOperationURL(CLOSE_SAVINGS_COMMAND, savingsID),
+                getCloseAccountJSON(withdrawBalance, LAST_TRANSACTION_DATE));
+    }
+
+    public Object deleteSavingsApplication(final Integer savingsId, final String jsonAttributeToGetBack) {
+        System.out.println("---------------------------------- DELETE SAVINGS APPLICATION ----------------------------------");
+        return Utils.performServerDelete(this.requestSpec, this.responseSpec, SAVINGS_ACCOUNT_URL + "/" + savingsId + "?"
+                + Utils.TENANT_IDENTIFIER, jsonAttributeToGetBack);
+
+    }
+
+    public Object depositToSavingsAccount(final Integer savingsID, final String amount, String date, String jsonAttributeToGetback) {
+        System.out.println("--------------------------------- SAVINGS TRANSACTION DEPOSIT --------------------------------");
+        return performSavingActions(createSavingsTransactionURL(DEPOSIT_SAVINGS_COMMAND, savingsID),
+                getSavingsTransactionJSON(amount, date), jsonAttributeToGetback);
+    }
+
+    public Object withdrawalFromSavingsAccount(final Integer savingsId, final String amount, String date, String jsonAttributeToGetback) {
+        System.out.println("\n--------------------------------- SAVINGS TRANSACTION WITHDRAWAL --------------------------------");
+        return performSavingActions(createSavingsTransactionURL(WITHDRAW_SAVINGS_COMMAND, savingsId),
+                getSavingsTransactionJSON(amount, date), jsonAttributeToGetback);
+    }
+
+    public Integer updateSavingsAccountTransaction(final Integer savingsId, final Integer transactionId, final String amount) {
+        System.out.println("\n--------------------------------- MODIFY SAVINGS TRANSACTION  --------------------------------");
+        return (Integer) performSavingActions(createAdjustTransactionURL(MODIFY_TRASACTION_COMMAND, savingsId, transactionId),
+                getSavingsTransactionJSON(amount, LAST_TRANSACTION_DATE), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public Integer undoSavingsAccountTransaction(final Integer savingsId, final Integer transactionId) {
+        System.out.println("\n--------------------------------- UNDO SAVINGS TRANSACTION  --------------------------------");
+        return (Integer) performSavingActions(createAdjustTransactionURL(UNDO_TRASACTION_COMMAND, savingsId, transactionId),
+                getSavingsTransactionJSON("0", LAST_TRANSACTION_DATE), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public void calculateInterestForSavings(final Integer savingsId) {
+        System.out.println("--------------------------------- CALCULATING INTEREST FOR SAVINGS --------------------------------");
+        performSavingActions(createSavingsCalculateInterestURL(CALCULATE_INTEREST_SAVINGS_COMMAND, savingsId),
+                getCalculatedInterestForSavingsApplicationAsJSON(), "");
+    }
+
+    public void postInterestForSavings(final Integer savingsId) {
+        System.out.println("--------------------------------- POST INTEREST FOR SAVINGS --------------------------------");
+        performSavingActions(createSavingsCalculateInterestURL(POST_INTEREST_SAVINGS_COMMAND, savingsId),
+                getCalculatedInterestForSavingsApplicationAsJSON(), "");
+    }
+
+    public Integer addChargesForSavings(final Integer savingsId, final Integer chargeId) {
+        System.out.println("--------------------------------- ADD CHARGES FOR SAVINGS --------------------------------");
+        return (Integer) performSavingActions(SAVINGS_ACCOUNT_URL + "/" + savingsId + "/charges?" + Utils.TENANT_IDENTIFIER,
+                getPeriodChargeRequestJSON(chargeId), CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public Integer payCharge(final Integer chargeId, final Integer savingsId, String amount, String dueDate) {
+        return (Integer) performSavingActions(createChargesURL("paycharge", savingsId, chargeId), getSavingsPayChargeJSON(amount, dueDate),
+                CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public Integer waiveCharge(final Integer chargeId, final Integer savingsId) {
+        return (Integer) performSavingActions(createChargesURL("waive", savingsId, chargeId), getSavingsWaiveChargeJSON(),
+                CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public HashMap updateCharges(final Integer chargeId, final Integer savingsId) {
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, SAVINGS_ACCOUNT_URL + "/" + savingsId + "/charges/" + chargeId
+                + "?" + Utils.TENANT_IDENTIFIER, getModifyChargeJSON(), CommonConstants.RESPONSE_CHANGES);
+    }
+
+    public Integer deleteCharge(final Integer chargeId, final Integer savingsId) {
+        return Utils.performServerDelete(this.requestSpec, this.responseSpec, SAVINGS_ACCOUNT_URL + "/" + savingsId + "/charges/"
+                + chargeId + "?" + Utils.TENANT_IDENTIFIER, CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    private String getApproveSavingsAsJSON() {
+        return getApproveSavingsAsJsonOnDate(CREATED_DATE_PLUS_ONE);
+    }
+
+    private String getApproveSavingsAsJsonOnDate(final String approvalDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("approvedOnDate", approvalDate);
+        map.put("note", "Approval NOTE");
+        String savingsAccountApproveJson = new Gson().toJson(map);
+        System.out.println(savingsAccountApproveJson);
+        return savingsAccountApproveJson;
+    }
+
+    private String getRejectedSavingsAsJSON(final String rejectedOnDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("rejectedOnDate", rejectedOnDate);
+        map.put("note", "Rejected NOTE");
+        String savingsAccountJson = new Gson().toJson(map);
+        System.out.println(savingsAccountJson);
+        return savingsAccountJson;
+    }
+
+    private String getWithdrawnSavingsAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("withdrawnOnDate", CREATED_DATE_PLUS_ONE);
+        map.put("note", "Rejected NOTE");
+        String savingsAccountJson = new Gson().toJson(map);
+        System.out.println(savingsAccountJson);
+        return savingsAccountJson;
+    }
+
+    private String getActivatedSavingsAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("activatedOnDate", TRANSACTION_DATE);
+        String savingsAccountActivateJson = new Gson().toJson(map);
+        System.out.println(savingsAccountActivateJson);
+        return savingsAccountActivateJson;
+    }
+
+    private String getSavingsTransactionJSON(final String amount, final String transactionDate) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("transactionDate", transactionDate);
+        map.put("transactionAmount", amount);
+        String savingsAccountWithdrawalJson = new Gson().toJson(map);
+        System.out.println(savingsAccountWithdrawalJson);
+        return savingsAccountWithdrawalJson;
+    }
+
+    private String getCalculatedInterestForSavingsApplicationAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        String savingsAccountCalculatedInterestJson = new Gson().toJson(map);
+        System.out.println(savingsAccountCalculatedInterestJson);
+        return savingsAccountCalculatedInterestJson;
+    }
+
+    private String getSavingsPayChargeJSON(final String amount, final String dueDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("amount", amount);
+        map.put("dueDate", dueDate);
+        String josn = new Gson().toJson(map);
+        return josn;
+    }
+
+    private String getSavingsWaiveChargeJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        String josn = new Gson().toJson(map);
+        return josn;
+    }
+
+    private String getModifyChargeJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("amount", "50");
+        String josn = new Gson().toJson(map);
+        return josn;
+    }
+
+    private String getCloseAccountJSON(String withdrawBalance, String closedOnDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("closedOnDate", closedOnDate);
+        map.put("withdrawBalance", withdrawBalance);
+        map.put("note", "Close Test");
+        String josn = new Gson().toJson(map);
+        return josn;
+    }
+
+    private String createSavingsOperationURL(final String command, final Integer savingsID) {
+        return SAVINGS_ACCOUNT_URL + "/" + savingsID + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private String createSavingsTransactionURL(final String command, final Integer savingsID) {
+        return SAVINGS_ACCOUNT_URL + "/" + savingsID + "/transactions?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private String createAdjustTransactionURL(final String command, final Integer savingsID, final Integer transactionId) {
+        return SAVINGS_ACCOUNT_URL + "/" + savingsID + "/transactions/" + transactionId + "?command=" + command + "&"
+                + Utils.TENANT_IDENTIFIER;
+    }
+
+    private String createSavingsCalculateInterestURL(final String command, final Integer savingsID) {
+        return SAVINGS_ACCOUNT_URL + "/" + savingsID + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private String createChargesURL(final String command, final Integer savingsID, final Integer chargeId) {
+        return SAVINGS_ACCOUNT_URL + "/" + savingsID + "/charges/" + chargeId + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    public ArrayList getSavingsCollectionAttribute(final Integer savingsID, final String jSONAttribute) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
+        final ArrayList<HashMap> response = Utils.performServerGet(requestSpec, responseSpec, URL, jSONAttribute);
+        return response;
+    }
+    
+    public Object getSavingsAccountDetail(final Integer savingsID, final String jsonAttribute){
+    	final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, URL, jsonAttribute);
+    }
+
+    public ArrayList getSavingsCharges(final Integer savingsID) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "/charges?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, URL, "");
+    }
+
+    public HashMap getSavingsCharge(final Integer savingsID, final Integer savingsChargeId) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "/charges/" + savingsChargeId + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, URL, "");
+    }
+
+    public HashMap getSavingsTransaction(final Integer savingsID, final Integer savingsTransactionId) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "/transactions/" + savingsTransactionId + "?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, URL, "");
+    }
+
+    public Object getSavingsInterest(final Integer savingsID) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?associations=summary&" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "summary");
+        return response.get("totalInterestEarned");
+    }
+
+    public HashMap getSavingsSummary(final Integer savingsID) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?associations=summary&" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "summary");
+        return response;
+    }
+
+    public HashMap getSavingsDetails(final Integer savingsID) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
+        final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "");
+        return response;
+    }
+
+    public Object getSavingsDetails(final Integer savingsID, final String returnAttribute) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?" + Utils.TENANT_IDENTIFIER;
+        final Object response = Utils.performServerGet(requestSpec, responseSpec, URL, returnAttribute);
+        return response;
+    }
+
+    private HashMap performSavingApplicationActions(final String postURLForSavingsTransaction, final String jsonToBeSent) {
+        HashMap status = null;
+        final HashMap response = Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForSavingsTransaction, jsonToBeSent,
+                CommonConstants.RESPONSE_CHANGES);
+        if (response != null) {
+            status = (HashMap) response.get("status");
+        }
+        return status;
+    }
+
+    private Object performSavingActions(final String postURLForSavingsTransaction, final String jsonToBeSent,
+            final String jsonAttributeToGetBack) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForSavingsTransaction, jsonToBeSent,
+                jsonAttributeToGetBack);
+    }
+
+    public Object closeSavingsAccountAndGetBackRequiredField(final Integer savingsID, String withdrawBalance,
+            final String jsonAttributeToGetBack, final String closedOnDate) {
+        System.out.println("---------------------------------- CLOSE SAVINGS APPLICATION ----------------------------------");
+        return performSavingActions(createSavingsOperationURL(CLOSE_SAVINGS_COMMAND, savingsID),
+                getCloseAccountJSON(withdrawBalance, closedOnDate), jsonAttributeToGetBack);
+    }
+
+    private String getPeriodChargeRequestJSON(Integer chargeId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("chargeId", chargeId);
+        map.put("amount", 100);
+        map.put("feeOnMonthDay", "15 January");
+        map.put("locale", CommonConstants.locale);
+        map.put("monthDayFormat", "dd MMMM");
+        map.put("dateFormat", "dd MMMM yyy");
+        map.put("dueDate", "10 January 2013");
+        String json = new Gson().toJson(map);
+        return json;
+    }
+
+    private String getAccountActivationJSON(final String activationDate) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        map.put("activatedOnDate", activationDate);
+        String savingsAccountActivateJson = new Gson().toJson(map);
+        return savingsAccountActivateJson;
+    }
+
+    public HashMap activateSavingsAccount(Integer savingsID, String activationDate) {
+        return performSavingApplicationActions(createSavingsOperationURL(ACTIVATE_SAVINGS_COMMAND, savingsID),
+                getAccountActivationJSON(activationDate));
+    }
+
+    public Object inactivateCharge(final Integer chargeId, final Integer savingsId, final String jsonAttributeToGetBack) {
+        return performSavingActions(createChargesURL("inactivate", savingsId, chargeId), getSavingsInactivateChargeJSON(),
+                jsonAttributeToGetBack);
+    }
+
+    private String getSavingsInactivateChargeJSON() {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", CommonConstants.locale);
+        map.put("dateFormat", CommonConstants.dateFormat);
+        String josn = new Gson().toJson(map);
+        return josn;
+    }
+
+    public static Integer openSavingsAccount(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer clientId, final String minimumOpeningBalance) {
+        final Integer savingsProductID = createSavingsProduct(requestSpec, responseSpec, minimumOpeningBalance);
+        Assert.assertNotNull(savingsProductID);
+
+        SavingsAccountHelper savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
+
+        final Integer savingsId = savingsAccountHelper.applyForSavingsApplication(clientId, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+        Assert.assertNotNull(savingsProductID);
+
+        HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(requestSpec, responseSpec, savingsId);
+        SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+        savingsStatusHashMap = savingsAccountHelper.approveSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+        savingsStatusHashMap = savingsAccountHelper.activateSavings(savingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+        return savingsId;
+    }
+
+    private static Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String minOpenningBalance) {
+        System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+        SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
+        final String savingsProductJSON = savingsProductHelper //
+                .withInterestCompoundingPeriodTypeAsDaily() //
+                .withInterestPostingPeriodTypeAsMonthly() //
+                .withInterestCalculationPeriodTypeAsDailyBalance() //
+                .withMinimumOpenningBalance(minOpenningBalance).build();
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsApplicationTestBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsApplicationTestBuilder.java
new file mode 100755
index 0000000..fc02831
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsApplicationTestBuilder.java
@@ -0,0 +1,52 @@
+/**
+ * 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.integrationtests.common.savings;
+
+import java.util.HashMap;
+
+import com.google.gson.Gson;
+
+public class SavingsApplicationTestBuilder {
+
+    private static final String LOCALE = "en_GB";
+
+    private String submittedOnDate = "";
+
+    public String build(final String ID, final String savingsProductId, final String accountType) {
+
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("dateFormat", "dd MMMM yyyy");
+        if (accountType == "GROUP") {
+            map.put("groupId", ID);
+        } else {
+            map.put("clientId", ID);
+        }        
+        map.put("productId", savingsProductId);
+        map.put("locale", LOCALE);
+        map.put("submittedOnDate", this.submittedOnDate);
+        String savingsApplicationJSON = new Gson().toJson(map);
+        System.out.println(savingsApplicationJSON);
+        return savingsApplicationJSON;
+    }
+
+    public SavingsApplicationTestBuilder withSubmittedOnDate(final String savingsApplicationSubmittedDate) {
+        this.submittedOnDate = savingsApplicationSubmittedDate;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java
new file mode 100644
index 0000000..d7a3912
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java
@@ -0,0 +1,253 @@
+/**
+ * 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.integrationtests.common.savings;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.Account.AccountType;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("unused")
+public class SavingsProductHelper {
+
+    private static final String SAVINGS_PRODUCT_URL = "/fineract-provider/api/v1/savingsproducts";
+    private static final String CREATE_SAVINGS_PRODUCT_URL = SAVINGS_PRODUCT_URL + "?" + Utils.TENANT_IDENTIFIER;
+
+    private static final String LOCALE = "en_GB";
+    private static final String DIGITS_AFTER_DECIMAL = "4";
+    private static final String IN_MULTIPLES_OF = "0";
+    private static final String USD = "USD";
+    private static final String DAYS = "0";
+    private static final String WEEKS = "1";
+    private static final String MONTHS = "2";
+    private static final String YEARS = "3";
+    private static final String DAILY = "1";
+    private static final String MONTHLY = "4";
+    private static final String QUARTERLY = "5";
+    private static final String ANNUAL = "7";
+    private static final String INTEREST_CALCULATION_USING_DAILY_BALANCE = "1";
+    private static final String INTEREST_CALCULATION_USING_AVERAGE_DAILY_BALANCE = "2";
+    private static final String DAYS_360 = "360";
+    private static final String DAYS_365 = "365";
+    private static final String NONE = "1";
+    private static final String CASH_BASED = "2";
+
+    private String nameOfSavingsProduct = Utils.randomNameGenerator("SAVINGS_PRODUCT_", 6);
+    private String shortName = Utils.randomNameGenerator("", 4);
+    private String description = Utils.randomNameGenerator("", 20);
+    private String interestCompoundingPeriodType = "2";
+    private String interestPostingPeriodType = "4";
+    private String interestCalculationType = INTEREST_CALCULATION_USING_DAILY_BALANCE;
+    private String nominalAnnualInterestRate = "10.0";
+    private String accountingRule = NONE;
+    private String savingsReferenceAccountId = null;
+    private String transfersInSuspenseAccountId = null;
+    private String savingsControlAccountId = null;
+    private String interestOnSavingsAccountId = null;
+    private String incomeFromFeeAccountId = null;
+    private String incomeFromPenaltyAccountId = null;
+    private String overdraftPortfolioControlId = null;
+    private String incomeFromInterestId = null;
+    private String writeOffAccountId = null;
+    private String minRequiredOpeningBalance = null;
+    private String lockinPeriodFrequency = "0";
+    private String withdrawalFeeForTransfers = "true";
+    private String lockingPeriodFrequencyType = DAYS;
+    private final String currencyCode = USD;
+    private final String interestCalculationDaysInYearType = DAYS_365;
+    private Account[] accountList = null;
+    private String minBalanceForInterestCalculation = null;
+    private String allowOverdraft = "false";
+    private String overdraftLimit = null; 
+    private String minRequiredBalance = null;
+    private String enforceMinRequiredBalance = "false";
+
+    public String build() {
+        final HashMap<String, String> map = new HashMap<>();
+
+        map.put("name", this.nameOfSavingsProduct);
+        map.put("shortName", this.shortName);
+        map.put("description", this.description);
+        map.put("currencyCode", this.currencyCode);
+        map.put("interestCalculationDaysInYearType", this.interestCalculationDaysInYearType);
+        map.put("locale", LOCALE);
+        map.put("digitsAfterDecimal", DIGITS_AFTER_DECIMAL);
+        map.put("inMultiplesOf", IN_MULTIPLES_OF);
+        map.put("interestCalculationType", this.interestCalculationType);
+        map.put("nominalAnnualInterestRate", this.nominalAnnualInterestRate);
+        map.put("interestCompoundingPeriodType", this.interestCompoundingPeriodType);
+        map.put("interestPostingPeriodType", this.interestPostingPeriodType);
+        map.put("accountingRule", this.accountingRule);
+        map.put("savingsReferenceAccountId", this.savingsReferenceAccountId);
+        map.put("transfersInSuspenseAccountId", this.transfersInSuspenseAccountId);
+        map.put("savingsControlAccountId", this.savingsControlAccountId);
+        map.put("interestOnSavingsAccountId", this.interestOnSavingsAccountId);
+        map.put("incomeFromFeeAccountId", this.incomeFromFeeAccountId);
+        map.put("incomeFromPenaltyAccountId", this.incomeFromPenaltyAccountId);
+        map.put("overdraftPortfolioControlId", this.overdraftPortfolioControlId);
+        map.put("incomeFromInterestId", this.incomeFromInterestId);
+        map.put("writeOffAccountId", this.writeOffAccountId);
+        map.put("minRequiredOpeningBalance", this.minRequiredOpeningBalance);
+        map.put("lockinPeriodFrequency", this.lockinPeriodFrequency);
+        map.put("lockinPeriodFrequencyType", this.lockingPeriodFrequencyType);
+        map.put("withdrawalFeeForTransfers", this.withdrawalFeeForTransfers);
+        map.put("minBalanceForInterestCalculation", minBalanceForInterestCalculation);
+        map.put("allowOverdraft", this.allowOverdraft);
+        map.put("overdraftLimit", this.overdraftLimit);
+        map.put("minRequiredBalance", this.minRequiredBalance);
+        map.put("enforceMinRequiredBalance", this.enforceMinRequiredBalance);
+
+        if (this.accountingRule.equals(CASH_BASED)) {
+            map.putAll(getAccountMappingForCashBased());
+        }
+        String savingsProductCreateJson = new Gson().toJson(map);
+        System.out.println(savingsProductCreateJson);
+        return savingsProductCreateJson;
+    }
+
+    public SavingsProductHelper withSavingsName(final String savingsName) {
+        this.nameOfSavingsProduct = savingsName;
+        return this;
+    }
+
+    public SavingsProductHelper withInterestCompoundingPeriodTypeAsDaily() {
+        this.interestCompoundingPeriodType = DAILY;
+        return this;
+    }
+
+    public SavingsProductHelper withMinimumOpenningBalance(String minBalance) {
+        this.minRequiredOpeningBalance = minBalance;
+        return this;
+    }
+    
+    public SavingsProductHelper withInterestCompoundingPeriodTypeAsMonthly() {
+        this.interestCompoundingPeriodType = MONTHLY;
+        return this;
+    }
+
+    public SavingsProductHelper withInterestPostingPeriodTypeAsMonthly() {
+        this.interestPostingPeriodType = MONTHLY;
+        return this;
+    }
+    
+    public SavingsProductHelper withMinBalanceForInterestCalculation(final String amount) {
+        this.minBalanceForInterestCalculation = amount;
+        return this;
+    }
+
+    public SavingsProductHelper withInterestPostingPeriodTypeAsQuarterly() {
+        this.interestPostingPeriodType = QUARTERLY;
+        return this;
+    }
+
+    public SavingsProductHelper withInterestPostingPeriodTypeAsAnnual() {
+        this.interestPostingPeriodType = ANNUAL;
+        return this;
+    }
+
+    public SavingsProductHelper withInterestCalculationPeriodTypeAsDailyBalance() {
+        this.interestCalculationType = INTEREST_CALCULATION_USING_DAILY_BALANCE;
+        return this;
+    }
+
+    public SavingsProductHelper withInterestCalculationPeriodTypeAsAverageDailyBalance() {
+        this.interestCalculationType = INTEREST_CALCULATION_USING_AVERAGE_DAILY_BALANCE;
+        return this;
+    }
+
+    public SavingsProductHelper withAccountingRuleAsNone() {
+        this.accountingRule = NONE;
+        return this;
+    }
+
+    public SavingsProductHelper withAccountingRuleAsCashBased(final Account[] account_list) {
+        this.accountingRule = CASH_BASED;
+        this.accountList = account_list;
+        return this;
+    }
+    
+    public SavingsProductHelper withMinRequiredBalance(String minBalance) {
+        this.minRequiredBalance = minBalance;
+        return this;
+    }
+    
+    public SavingsProductHelper withEnforceMinRequiredBalance(String enforceMinRequiredBalance) {
+        this.enforceMinRequiredBalance = enforceMinRequiredBalance;
+        return this;
+    }
+
+    public SavingsProductHelper withOverDraft(final String overDraftLimit) {
+        this.allowOverdraft = "true";
+        this.overdraftLimit = overDraftLimit;
+        return this;
+    }
+
+    private Map<String, String> getAccountMappingForCashBased() {
+        final Map<String, String> map = new HashMap<>();
+        if (accountList != null) {
+            for (int i = 0; i < this.accountList.length; i++) {
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.ASSET)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("savingsReferenceAccountId", ID);
+                    map.put("overdraftPortfolioControlId", ID);
+                }
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("savingsControlAccountId", ID);
+                    map.put("transfersInSuspenseAccountId", ID);
+                }
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("interestOnSavingsAccountId", ID);
+                    map.put("writeOffAccountId", ID);
+                }
+                if (this.accountList[i].getAccountType().equals(Account.AccountType.INCOME)) {
+                    final String ID = this.accountList[i].getAccountID().toString();
+                    map.put("incomeFromFeeAccountId", ID);
+                    map.put("incomeFromPenaltyAccountId", ID);
+                    map.put("incomeFromInterestId", ID);
+                }
+            }
+        }
+        return map;
+    }
+
+    public static Integer createSavingsProduct(final String savingsProductJSON, final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_SAVINGS_PRODUCT_URL, savingsProductJSON, "resourceId");
+    }
+
+    public static void verifySavingsProductCreatedOnServer(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final Integer generatedProductID) {
+        System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n");
+        final String GET_SAVINGS_PRODUCT_URL = SAVINGS_PRODUCT_URL + "/" + generatedProductID + "?" + Utils.TENANT_IDENTIFIER;
+        final Integer responseSavingsProductID = Utils.performServerGet(requestSpec, responseSpec, GET_SAVINGS_PRODUCT_URL, "id");
+        assertEquals("ERROR IN CREATING THE Savings Product", generatedProductID, responseSavingsProductID);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsStatusChecker.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsStatusChecker.java
new file mode 100755
index 0000000..fe0b62e
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsStatusChecker.java
@@ -0,0 +1,86 @@
+/**
+ * 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.integrationtests.common.savings;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class SavingsStatusChecker {
+    
+    private static final String SAVINGS_ACCOUNT_URL = "/fineract-provider/api/v1/savingsaccounts";
+
+    public static void verifySavingsIsApproved(final HashMap savingsStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING SAVINGS APPLICATION IS APPROVED ------------------------------------");
+        assertTrue("ERROR IN APPROVING SAVINGS APPLICATION", getStatus(savingsStatusHashMap, "approved"));
+        System.out.println("Savings Application Status:" + savingsStatusHashMap + "\n");
+    }
+
+    public static void verifySavingsIsPending(final HashMap savingsStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING SAVINGS APPLICATION IS PENDING ------------------------------------");
+        assertTrue("SAVINGS ACCOUNT IS NOT IN PENDING STATE", getStatus(savingsStatusHashMap, "submittedAndPendingApproval"));
+        System.out.println("Savings Application Status:" + savingsStatusHashMap + "\n");
+    }
+
+    public static void verifySavingsIsActive(final HashMap savingsStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING SAVINGS APPLICATION IS ACTIVE ------------------------------------");
+        assertTrue("ERROR IN ACTIVATING THE SAVINGS APPLICATION", getStatus(savingsStatusHashMap, "active"));
+        System.out.println("Savings Application Status:" + savingsStatusHashMap + "\n");
+    }
+    
+    public static void verifySavingsIsRejected(final HashMap savingsStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING SAVINGS APPLICATION IS REJECTED ------------------------------------");
+        assertTrue("ERROR IN REJECTING THE SAVINGS APPLICATION", getStatus(savingsStatusHashMap, "rejected"));
+        System.out.println("Savings Application Status:" + savingsStatusHashMap + "\n");
+    }
+    
+    public static void verifySavingsIsWithdrawn(final HashMap savingsStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING SAVINGS APPLICATION IS WITHDRAWN ------------------------------------");
+        assertTrue("ERROR IN WITHDRAW  THE SAVINGS APPLICATION", getStatus(savingsStatusHashMap, "withdrawnByApplicant"));
+        System.out.println("Savings Application Status:" + savingsStatusHashMap + "\n");
+    }
+    
+    public static void verifySavingsAccountIsClosed(final HashMap savingsStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING SAVINGS APPLICATION IS CLOSED ------------------------------------");
+        assertTrue("ERROR IN CLOSING THE SAVINGS APPLICATION", getStatus(savingsStatusHashMap, "closed"));
+        System.out.println("Savings Application Status:" + savingsStatusHashMap + "\n");
+    }
+
+    public static void verifySavingsAccountIsNotActive(final HashMap savingsStatusHashMap) {
+        System.out.println("\n-------------------------------------- VERIFYING SAVINGS APPLICATION IS INACTIVE ------------------------------------");
+        assertTrue(getStatus(savingsStatusHashMap, "active"));
+        System.out.println("Savings Application Status:" + savingsStatusHashMap + "\n");
+    }
+
+    public static HashMap getStatusOfSavings(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer savingsID) {
+        final String url = SAVINGS_ACCOUNT_URL+"/" + savingsID + "?"+Utils.TENANT_IDENTIFIER;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "status");
+    }
+
+    private static boolean getStatus(final HashMap savingsStatusMap, final String nameOfSavingsStatusString) {
+        return (Boolean) savingsStatusMap.get(nameOfSavingsStatusString);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/system/AccountNumberPreferencesHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/system/AccountNumberPreferencesHelper.java
new file mode 100644
index 0000000..abdedf3
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/system/AccountNumberPreferencesHelper.java
@@ -0,0 +1,226 @@
+/**
+ * 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.integrationtests.common.system;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class AccountNumberPreferencesHelper {
+
+	private final RequestSpecification requestSpec;
+
+	private final ResponseSpecification responseSpec;
+
+	private static final String ACCOUNT_NUMBER_FORMATS_REQUEST_URL = "/fineract-provider/api/v1/accountnumberformats";
+
+	public AccountNumberPreferencesHelper(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec) {
+		this.requestSpec = requestSpec;
+		this.responseSpec = responseSpec;
+	}
+
+	public Object createClientAccountNumberPreference(
+			ResponseSpecification responseSpec, String jsonAttributeToGetBack) {
+		System.out
+				.println("---------------------------------CREATING CLIENT ACCOUNT NUMBER PREFERENCE------------------------------------------");
+
+		final String requestJSON = new AccountNumberPreferencesTestBuilder()
+				.clientBuild();
+
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "?"
+				+ Utils.TENANT_IDENTIFIER;
+
+		return Utils.performServerPost(this.requestSpec, responseSpec, URL,
+				requestJSON, jsonAttributeToGetBack);
+	}
+
+	public Object createLoanAccountNumberPreference(
+			ResponseSpecification responseSpec, String jsonAttributeToGetBack) {
+		System.out
+				.println("---------------------------------CREATING LOAN ACCOUNT NUMBER PREFERENCE------------------------------------------");
+
+		final String requestJSON = new AccountNumberPreferencesTestBuilder()
+				.loanBuild();
+
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "?"
+				+ Utils.TENANT_IDENTIFIER;
+		return Utils.performServerPost(this.requestSpec, responseSpec, URL,
+				requestJSON, jsonAttributeToGetBack);
+	}
+
+	public Object createSavingsAccountNumberPreference(
+			ResponseSpecification responseSpec, String jsonAttributeToGetBack) {
+		System.out
+				.println("---------------------------------CREATING SAVINGS ACCOUNT NUMBER PREFERENCE------------------------------------------");
+
+		final String requestJSON = new AccountNumberPreferencesTestBuilder()
+				.savingsBuild();
+
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "?"
+				+ Utils.TENANT_IDENTIFIER;
+		return Utils.performServerPost(this.requestSpec, responseSpec, URL,
+				requestJSON, jsonAttributeToGetBack);
+
+	}
+	
+	public Object createGroupsAccountNumberPreference(
+			ResponseSpecification responseSpec, String jsonAttributeToGetBack) {
+		System.out
+				.println("---------------------------------CREATING GROUPS ACCOUNT NUMBER PREFERENCE------------------------------------------");
+
+		final String requestJSON = new AccountNumberPreferencesTestBuilder()
+				.groupsBuild();
+
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "?"
+				+ Utils.TENANT_IDENTIFIER;
+		return Utils.performServerPost(this.requestSpec, responseSpec, URL,
+				requestJSON, jsonAttributeToGetBack);
+
+	}
+	
+	public Object createCenterAccountNumberPreference(
+			ResponseSpecification responseSpec, String jsonAttributeToGetBack) {
+		System.out
+				.println("---------------------------------CREATING CENTER ACCOUNT NUMBER PREFERENCE------------------------------------------");
+
+		final String requestJSON = new AccountNumberPreferencesTestBuilder()
+				.centerBuild();
+
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "?"
+				+ Utils.TENANT_IDENTIFIER;
+		return Utils.performServerPost(this.requestSpec, responseSpec, URL,
+				requestJSON, jsonAttributeToGetBack);
+
+	}
+
+	public HashMap<String, Object> createAccountNumberPreferenceWithInvalidData(
+			ResponseSpecification responseSpec, String accountType,
+			String prefixType, String jsonAttributeToGetBack) {
+
+		final String requestJSON = new AccountNumberPreferencesTestBuilder()
+				.invalidDataBuild(accountType, prefixType);
+
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "?"
+				+ Utils.TENANT_IDENTIFIER;
+		return Utils.performServerPost(this.requestSpec, responseSpec, URL,
+				requestJSON, jsonAttributeToGetBack);
+
+	}
+
+	public HashMap<String, Object> updateAccountNumberPreference(
+			final Integer accountNumberFormatId, final String prefixType,
+			ResponseSpecification responseSpec, String jsonAttributeToGetBack) {
+
+		final String requestJSON = new AccountNumberPreferencesTestBuilder()
+				.updatePrefixType(prefixType);
+
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "/"
+				+ accountNumberFormatId + "?" + Utils.TENANT_IDENTIFIER;
+
+		return Utils.performServerPut(this.requestSpec, responseSpec, URL,
+				requestJSON, jsonAttributeToGetBack);
+
+	}
+
+	public HashMap<String, Object> deleteAccountNumberPreference(
+			final Integer accountNumberFormatId,
+			ResponseSpecification responseSpec, String jsonAttributeToGetBack) {
+
+		System.out
+				.println("---------------------------------DELETING ACCOUNT NUMBER PREFERENCE------------------------------------------");
+
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "/"
+				+ accountNumberFormatId + "?" + Utils.TENANT_IDENTIFIER;
+
+		return Utils.performServerDelete(this.requestSpec, responseSpec, URL,
+				jsonAttributeToGetBack);
+	}
+
+	public Object getAccountNumberPreference(
+			final Integer accountNumberFormatId,
+			final String jsonAttributeToGetBack) {
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "/"
+				+ accountNumberFormatId + "?" + Utils.TENANT_IDENTIFIER;
+
+		return Utils.performServerGet(requestSpec, responseSpec, URL,
+				jsonAttributeToGetBack);
+	}
+
+	public ArrayList<HashMap<String, Object>> getAllAccountNumberPreferences() {
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "?"
+				+ Utils.TENANT_IDENTIFIER;
+		final ArrayList<HashMap<String, Object>> response = Utils
+				.performServerGet(requestSpec, responseSpec, URL, "");
+		return response;
+	}
+
+	public void verifyCreationOfAccountNumberPreferences(
+			final Integer clientAccountNumberPreferenceId,
+			final Integer loanAccountNumberPreferenceId,
+			final Integer savingsAccountNumberPreferenceId,
+			final Integer groupsAccountNumberPreferenceId,
+			final Integer centerAccountNumberPreferenceId,
+			ResponseSpecification responseSpec, RequestSpecification requestSpec) {
+
+		final String clientURL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "/"
+				+ clientAccountNumberPreferenceId + "?"
+				+ Utils.TENANT_IDENTIFIER;
+
+		Utils.performServerGet(requestSpec, responseSpec, clientURL, "id");
+
+		final String loanURL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "/"
+				+ loanAccountNumberPreferenceId + "?" + Utils.TENANT_IDENTIFIER;
+
+		Utils.performServerGet(requestSpec, responseSpec, loanURL, "id");
+
+		final String savingsURL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "/"
+				+ savingsAccountNumberPreferenceId + "?"
+				+ Utils.TENANT_IDENTIFIER;
+
+		Utils.performServerGet(requestSpec, responseSpec, savingsURL, "id");
+		
+		final String groupsURL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "/"
+				+ groupsAccountNumberPreferenceId + "?"
+				+ Utils.TENANT_IDENTIFIER;
+
+		Utils.performServerGet(requestSpec, responseSpec, groupsURL, "id");
+		
+		final String centerURL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "/"
+				+ centerAccountNumberPreferenceId + "?"
+				+ Utils.TENANT_IDENTIFIER;
+
+		Utils.performServerGet(requestSpec, responseSpec, centerURL, "id");
+	}
+
+	public void verifyUpdationOfAccountNumberPreferences(
+			final Integer accountNumberPreferenceId,
+			ResponseSpecification responseSpec, RequestSpecification requestSpec) {
+
+		final String URL = ACCOUNT_NUMBER_FORMATS_REQUEST_URL + "/"
+				+ accountNumberPreferenceId + "?" + Utils.TENANT_IDENTIFIER;
+		Utils.performServerGet(requestSpec, responseSpec, URL, "id");
+
+	}
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/system/AccountNumberPreferencesTestBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/system/AccountNumberPreferencesTestBuilder.java
new file mode 100644
index 0000000..6c6e897
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/system/AccountNumberPreferencesTestBuilder.java
@@ -0,0 +1,91 @@
+/**
+ * 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.integrationtests.common.system;
+
+import java.util.HashMap;
+
+import com.google.gson.Gson;
+
+public class AccountNumberPreferencesTestBuilder {
+	private String clientAccountType = "1";
+	private String clientPrefixType = "101";
+	private String loanAccountType = "2";
+	private String loanPrefixType = "1";
+	private String savingsAccountType = "3";
+	private String savingsPrefixType = "1";
+	private String centerAccountType = "4";
+	private String centerPrefixType = "1";
+	private String groupsAccountType = "5";
+	private String groupsPrefixType = "1";
+
+	public String clientBuild() {
+		final HashMap<String, Object> map = new HashMap<>();
+		map.put("accountType", clientAccountType);
+		map.put("prefixType", clientPrefixType);
+
+		return new Gson().toJson(map);
+	}
+
+	public String loanBuild() {
+		final HashMap<String, Object> map = new HashMap<>();
+		map.put("accountType", loanAccountType);
+		map.put("prefixType", loanPrefixType);
+
+		return new Gson().toJson(map);
+	}
+
+	public String savingsBuild() {
+		final HashMap<String, Object> map = new HashMap<>();
+		map.put("accountType", savingsAccountType);
+		map.put("prefixType", savingsPrefixType);
+
+		return new Gson().toJson(map);
+	}
+	
+	public String groupsBuild() {
+		final HashMap<String, Object> map = new HashMap<>();
+		map.put("accountType", groupsAccountType);
+		map.put("prefixType", groupsPrefixType);
+
+		return new Gson().toJson(map);
+	}
+
+	public String centerBuild() {
+		final HashMap<String, Object> map = new HashMap<>();
+		map.put("accountType", centerAccountType);
+		map.put("prefixType", centerPrefixType);
+
+		return new Gson().toJson(map);
+	}
+	
+	public String invalidDataBuild(String accountType, String prefixType) {
+		final HashMap<String, Object> map = new HashMap<>();
+		map.put("accountType", accountType);
+		map.put("prefixType", prefixType);
+
+		return new Gson().toJson(map);
+	}
+
+	public String updatePrefixType(final String prefixType) {
+
+		final HashMap<String, Object> map = new HashMap<>();
+		map.put("prefixType", prefixType);
+		return new Gson().toJson(map);
+	}
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
new file mode 100644
index 0000000..f85d7b5
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
@@ -0,0 +1,253 @@
+/**
+ * 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.integrationtests.common.system;
+
+import static com.jayway.restassured.RestAssured.given;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.path.json.JsonPath;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class CodeHelper {
+
+	public static final String CODE_ID_ATTRIBUTE_NAME = "id";
+	public static final String RESPONSE_ID_ATTRIBUTE_NAME = "resourceId";
+	public static final String SUBRESPONSE_ID_ATTRIBUTE_NAME = "subResourceId";
+	public static final String CODE_NAME_ATTRIBUTE_NAME = "name";
+	public static final String CODE_SYSTEM_DEFINED_ATTRIBUTE_NAME = "systemDefined";
+	public static final String CODE_URL = "/fineract-provider/api/v1/codes";
+
+	public static final String CODE_VALUE_ID_ATTRIBUTE_NAME = "id";
+	public static final String CODE_VALUE_NAME_ATTRIBUTE_NAME = "name";
+	public static final String CODE_VALUE_DESCRIPTION_ATTRIBUTE_NAME = "description";
+	public static final String CODE_VALUE_POSITION_ATTRIBUTE_NAME = "position";
+	public static final String CODE_VALUE_URL = "/fineract-provider/api/v1/codes/[codeId]/codevalues";
+
+	public static Object createCode(final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final String codeName,
+			final String jsonAttributeToGetback) {
+
+		return Utils.performServerPost(requestSpec, responseSpec, CODE_URL
+				+ "?" + Utils.TENANT_IDENTIFIER, getTestCodeAsJSON(codeName),
+				jsonAttributeToGetback);
+	}
+
+	public static Object updateCode(final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final String codeName, final String jsonAttributeToGetback) {
+
+		return Utils.performServerPut(requestSpec, responseSpec, CODE_URL + "/"
+				+ codeId + "?" + Utils.TENANT_IDENTIFIER,
+				getTestCodeAsJSON(codeName), jsonAttributeToGetback);
+	}
+
+	public static Object getCodeById(final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final String jsonAttributeToGetback) {
+
+		return Utils.performServerGet(requestSpec, responseSpec, CODE_URL + "/"
+				+ codeId + "?" + Utils.TENANT_IDENTIFIER,
+				jsonAttributeToGetback);
+
+	}
+
+	public static HashMap<String, Object> getCodeByName(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final String codeName) {
+
+		final HashMap<String, Object> code = new HashMap<>();
+
+		ArrayList<HashMap<String, Object>> getAllCodes = CodeHelper
+				.getAllCodes(requestSpec, responseSpec);
+		for (HashMap<String, Object> map : getAllCodes) {
+			String name = (String) map.get("name");
+			if (name.equals(codeName)) {
+				code.put("id", map.get("id"));
+				code.put("name", map.get("name"));
+				code.put("systemDefined", map.get("systemDefined"));
+				break;
+			}
+		}
+		return code;
+	}
+
+	public static HashMap<String, Object> retrieveOrCreateCodeValue(
+			Integer codeId, final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec) {
+		Integer codeValueId = null;
+		final List<HashMap<String, Object>> codeValuesList = CodeHelper
+				.getCodeValuesForCode(requestSpec, responseSpec, codeId, "");
+		/* If Code Values doesn't exist,then create Code value */
+		if (codeValuesList.size() == 0) {
+			final Integer codeValuePosition = 0;
+			final String codeValue = Utils.randomNameGenerator("", 3);
+			codeValueId = (Integer) CodeHelper.createCodeValue(requestSpec,
+					responseSpec, codeId, codeValue, codeValuePosition,
+					"subResourceId");
+			
+		} else {
+			return codeValuesList.get(0);
+		}
+		return Utils.performServerGet(requestSpec, responseSpec,
+				CODE_VALUE_URL.replace("[codeId]", codeId.toString()) + "/"
+						+ codeValueId.toString() + "?"
+						+ Utils.TENANT_IDENTIFIER, "");
+		
+	}
+
+	public static ArrayList<HashMap<String, Object>> getAllCodes(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec) {
+
+		return Utils.performServerGet(requestSpec, responseSpec, CODE_URL + "?"
+				+ Utils.TENANT_IDENTIFIER, "");
+
+	}
+
+	public static Object getSystemDefinedCodes(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec) {
+
+		final String getResponse = given().spec(requestSpec).expect()
+				.spec(responseSpec).when()
+				.get(CodeHelper.CODE_URL + "?" + Utils.TENANT_IDENTIFIER)
+				.asString();
+
+		final JsonPath getResponseJsonPath = new JsonPath(getResponse);
+
+		// get any systemDefined code
+		return getResponseJsonPath.get("find { e -> e.systemDefined == true }");
+
+	}
+
+	public static String getTestCodeAsJSON(final String codeName) {
+		final HashMap<String, String> map = new HashMap<>();
+		map.put(CODE_NAME_ATTRIBUTE_NAME, codeName);
+		return new Gson().toJson(map);
+	}
+
+	public static String getTestCodeValueAsJSON(final String codeValueName,
+			final String description, final Integer position) {
+		final HashMap<String, Object> map = new HashMap<>();
+		map.put(CODE_VALUE_NAME_ATTRIBUTE_NAME, codeValueName);
+		if (description != null) {
+			map.put(CODE_VALUE_DESCRIPTION_ATTRIBUTE_NAME, description);
+		}
+		map.put(CODE_VALUE_POSITION_ATTRIBUTE_NAME, position);
+		return new Gson().toJson(map);
+	}
+
+	public static Object deleteCodeById(final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final String jsonAttributeToGetback) {
+
+		return Utils.performServerDelete(requestSpec, responseSpec, CODE_URL
+				+ "/" + codeId + "?" + Utils.TENANT_IDENTIFIER,
+				jsonAttributeToGetback);
+
+	}
+
+	public static Object createCodeValue(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final String codeValueName, final Integer position,
+			final String jsonAttributeToGetback) {
+		String description = null;
+		return createCodeValue(requestSpec, responseSpec, codeId,
+				codeValueName, description, position, jsonAttributeToGetback);
+	}
+
+	public static Object createCodeValue(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final String codeValueName, final String description,
+			final Integer position, final String jsonAttributeToGetback) {
+
+		return Utils.performServerPost(requestSpec, responseSpec,
+				CODE_VALUE_URL.replace("[codeId]", codeId.toString()) + "?"
+						+ Utils.TENANT_IDENTIFIER,
+				getTestCodeValueAsJSON(codeValueName, description, position),
+				jsonAttributeToGetback);
+	}
+
+	public static List<HashMap<String, Object>> getCodeValuesForCode(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final String jsonAttributeToGetback) {
+
+		return Utils.performServerGet(requestSpec, responseSpec,
+				CODE_VALUE_URL.replace("[codeId]", codeId.toString()) + "?"
+						+ Utils.TENANT_IDENTIFIER, jsonAttributeToGetback);
+		
+	}
+
+	public static Object getCodeValueById(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final Integer codeValueId, final String jsonAttributeToGetback) {
+
+		return Utils.performServerGet(requestSpec, responseSpec,
+				CODE_VALUE_URL.replace("[codeId]", codeId.toString()) + "/"
+						+ codeValueId.toString() + "?"
+						+ Utils.TENANT_IDENTIFIER, jsonAttributeToGetback);
+	}
+
+	public static Object deleteCodeValueById(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final Integer codeValueId, final String jsonAttributeToGetback) {
+
+		return Utils.performServerDelete(requestSpec, responseSpec,
+				CODE_VALUE_URL.replace("[codeId]", codeId.toString()) + "/"
+						+ codeValueId.toString() + "?"
+						+ Utils.TENANT_IDENTIFIER, jsonAttributeToGetback);
+	}
+
+	public static Object updateCodeValue(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final Integer codeValueId, final String codeValueName,
+			final Integer position, final String jsonAttributeToGetback) {
+		String description = null;
+		return updateCodeValue(requestSpec, responseSpec, codeId, codeValueId,
+				codeValueName, description, position, jsonAttributeToGetback);
+	}
+
+	public static Object updateCodeValue(
+			final RequestSpecification requestSpec,
+			final ResponseSpecification responseSpec, final Integer codeId,
+			final Integer codeValueId, final String codeValueName,
+			final String description, final Integer position,
+			final String jsonAttributeToGetback) {
+
+		return Utils.performServerPut(requestSpec, responseSpec,
+				CODE_VALUE_URL.replace("[codeId]", codeId.toString()) + "/"
+						+ codeValueId + "?" + Utils.TENANT_IDENTIFIER,
+				getTestCodeValueAsJSON(codeValueName, description, position),
+				jsonAttributeToGetback);
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/xbrl/XBRLIntegrationTestHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/xbrl/XBRLIntegrationTestHelper.java
new file mode 100644
index 0000000..6a24996
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/xbrl/XBRLIntegrationTestHelper.java
@@ -0,0 +1,54 @@
+/**
+ * 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.integrationtests.common.xbrl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings("rawtypes")
+public class XBRLIntegrationTestHelper {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    private static final String GET_TAXONOMY_LIST_URL = "/fineract-provider/api/v1/mixtaxonomy?" + Utils.TENANT_IDENTIFIER;
+    private static final String TAXONOMY_MAPPING_URL = "/fineract-provider/api/v1/mixmapping?" + Utils.TENANT_IDENTIFIER;
+
+    public XBRLIntegrationTestHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public ArrayList getTaxonomyList() {
+        final ArrayList response = Utils.performServerGet(this.requestSpec, this.responseSpec, GET_TAXONOMY_LIST_URL, "");
+
+        return response;
+    }
+
+    public HashMap getTaxonomyMapping() {
+        final HashMap response = Utils.performServerGet(this.requestSpec, this.responseSpec, TAXONOMY_MAPPING_URL, "config");
+        return response;
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/loanaccount/guarantor/GuarantorHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/loanaccount/guarantor/GuarantorHelper.java
new file mode 100755
index 0000000..75e279c
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/loanaccount/guarantor/GuarantorHelper.java
@@ -0,0 +1,72 @@
+/**
+ * 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.integrationtests.loanaccount.guarantor;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class GuarantorHelper {
+
+    private static final String LOAN_URL = "/fineract-provider/api/v1/loans/";
+    private static final String GUARANTOR_API_URL = "/guarantors/";
+    private static final String TENANT = "?" + Utils.TENANT_IDENTIFIER;
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public GuarantorHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public Integer createGuarantor(final Integer loanId, final String guarantorJSON) {
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, LOAN_URL + loanId + GUARANTOR_API_URL + TENANT, guarantorJSON,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+    }
+
+    public HashMap updateGuarantor(final Integer guarantorId, final Integer loanId, final String guarantorJSON) {
+        return Utils.performServerPut(this.requestSpec, this.responseSpec, LOAN_URL + loanId + GUARANTOR_API_URL + guarantorId + TENANT,
+                guarantorJSON, CommonConstants.RESPONSE_CHANGES);
+    }
+
+    public HashMap deleteGuarantor(final Integer guarantorId, final Integer fundId, final Integer loanId) {
+        return Utils.performServerDelete(this.requestSpec, this.responseSpec, LOAN_URL + loanId + GUARANTOR_API_URL + guarantorId + TENANT
+                + "&guarantorFundingId=" + fundId, "");
+    }
+
+    public HashMap deleteGuarantor(final Integer guarantorId, final Integer loanId) {
+        return Utils.performServerDelete(this.requestSpec, this.responseSpec, LOAN_URL + loanId + GUARANTOR_API_URL + guarantorId + TENANT,
+                "");
+    }
+
+    public Object getGuarantor(final Integer guarantorId, final Integer loanId, final String jsonToGetBack) {
+        return Utils.performServerGet(this.requestSpec, this.responseSpec, LOAN_URL + loanId + GUARANTOR_API_URL + guarantorId + TENANT,
+                jsonToGetBack);
+    }
+
+    public List getAllGuarantor(final Integer loanId) {
+        return Utils.performServerGet(this.requestSpec, this.responseSpec, LOAN_URL + loanId + GUARANTOR_API_URL + TENANT, "");
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/loanaccount/guarantor/GuarantorTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/loanaccount/guarantor/GuarantorTest.java
new file mode 100755
index 0000000..eeeaf58
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/loanaccount/guarantor/GuarantorTest.java
@@ -0,0 +1,694 @@
+/**
+ * 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.integrationtests.loanaccount.guarantor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class GuarantorTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private GuarantorHelper guarantorHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+    private final Float self1_balance = new Float(5000);
+    private final Float external1_balance = new Float(5000);
+    private final Float external2_balance = new Float(5000);
+    private final Float self1_guarantee = new Float(2000);
+    private final Float external1_guarantee = new Float(2000);
+    private final Float external2_guarantee = new Float(1000);
+
+    @Before
+    public void setUp() throws Exception {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.guarantorHelper = new GuarantorHelper(this.requestSpec, this.responseSpec);
+        savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testGuarantor() {
+
+        Float self1_hold_funds = new Float(0);
+        Float external1_hold_funds = new Float(0);
+        Float external2_hold_funds = new Float(0);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer clientID_external = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        final Integer clientID_external2 = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID_external);
+
+        final Integer selfSavigsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID,
+                String.valueOf(self1_balance));
+        final Integer externalSavigsId_1 = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID_external,
+                String.valueOf(external1_balance));
+        final Integer externalSavigsId_2 = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID_external2,
+                String.valueOf(external2_balance));
+
+        final Integer loanProductID = createLoanProductWithHoldFunds("50", "20", "20");
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7 * 4);
+        String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, LOAN_DISBURSEMENT_DATE);
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        String guarantorJSON = new GuarantorTestBuilder().externalCustomer().build();
+        Integer externalGuarantor = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        Assert.assertNotNull(externalGuarantor);
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithoutGuaranteeAmount(String.valueOf(clientID_external)).build();
+        Integer withoutGuaranteeAmount = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        Assert.assertNotNull(withoutGuaranteeAmount);
+
+        ArrayList<HashMap> errorData = (ArrayList<HashMap>) this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, null, loanID,
+                CommonConstants.RESPONSE_ERROR);
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.self.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.external.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.mandated.guarantee.required"));
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID),
+                String.valueOf(selfSavigsId), String.valueOf(self1_guarantee)).build();
+        Integer selfGuarantee = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(selfSavigsId, null);
+        Assert.assertNotNull(selfGuarantee);
+
+        errorData = (ArrayList<HashMap>) this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, null, loanID,
+                CommonConstants.RESPONSE_ERROR);
+        assertFalse(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.self.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.external.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.mandated.guarantee.required"));
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID_external),
+                String.valueOf(externalSavigsId_1), String.valueOf(external1_guarantee)).build();
+        Integer externalGuarantee_1 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(externalSavigsId_1, null);
+        Assert.assertNotNull(externalGuarantee_1);
+
+        errorData = (ArrayList<HashMap>) this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, null, loanID,
+                CommonConstants.RESPONSE_ERROR);
+        assertFalse(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.self.guarantee.required"));
+        assertFalse(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.external.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.mandated.guarantee.required"));
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID_external2),
+                String.valueOf(externalSavigsId_2), String.valueOf(external2_guarantee)).build();
+        Integer externalGuarantee_2 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(externalSavigsId_2, null);
+        Assert.assertNotNull(externalGuarantee_2);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        self1_hold_funds += self1_guarantee;
+        external1_hold_funds += external1_guarantee;
+        external2_hold_funds += external2_guarantee;
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_2, external2_hold_funds);
+
+        System.out.println("-----------------------------------UNDO APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.undoApproval(loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        verifySavingsOnHoldBalance(selfSavigsId, new Float(0));
+        verifySavingsOnHoldBalance(externalSavigsId_1, new Float(0));
+        verifySavingsOnHoldBalance(externalSavigsId_2, new Float(0));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_2, external2_hold_funds);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // First repayment
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7 * 3);
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        external1_hold_funds -= new Float(827.5867);
+        external2_hold_funds -= new Float(413.7933);
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_2, external2_hold_funds);
+
+        // Second repayment
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7 * 2);
+        LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        totalDueForCurrentPeriod = (Float) loanSchedule.get(2).get("totalDueForPeriod");
+        external1_hold_funds -= new Float(831.4067);
+        external2_hold_funds -= new Float(415.7033333);
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_2, external2_hold_funds);
+
+        // third repayment
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        totalDueForCurrentPeriod = (Float) loanSchedule.get(3).get("totalDueForPeriod");
+        self1_hold_funds -= new Float(741.355);
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, new Float(0));
+        verifySavingsOnHoldBalance(externalSavigsId_2, new Float(0));
+
+        // forth repayment
+        todaysDate = Calendar.getInstance();
+        LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        totalDueForCurrentPeriod = (Float) loanSchedule.get(3).get("totalDueForPeriod");
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, new Float(0));
+        verifySavingsOnHoldBalance(externalSavigsId_1, new Float(0));
+        verifySavingsOnHoldBalance(externalSavigsId_2, new Float(0));
+
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testGuarantor_UNDO_DISBURSAL() {
+
+        Float self1_hold_funds = new Float(0);
+        Float external1_hold_funds = new Float(0);
+        Float external2_hold_funds = new Float(0);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer clientID_external = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        final Integer clientID_external2 = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID_external);
+
+        final Integer selfSavigsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID,
+                String.valueOf(self1_balance));
+        final Integer externalSavigsId_1 = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID_external,
+                String.valueOf(external1_balance));
+        final Integer externalSavigsId_3 = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID_external,
+                String.valueOf(external1_balance));
+        final Integer externalSavigsId_2 = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID_external2,
+                String.valueOf(external2_balance));
+
+        final Integer loanProductID = createLoanProductWithHoldFunds("50", "20", "20");
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7 * 4);
+        String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, LOAN_DISBURSEMENT_DATE);
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        String guarantorJSON = new GuarantorTestBuilder().externalCustomer().build();
+        Integer externalGuarantor = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        Assert.assertNotNull(externalGuarantor);
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithoutGuaranteeAmount(String.valueOf(clientID_external)).build();
+        Integer withoutGuaranteeAmount = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        Assert.assertNotNull(withoutGuaranteeAmount);
+
+        ArrayList<HashMap> errorData = (ArrayList<HashMap>) this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, null, loanID,
+                CommonConstants.RESPONSE_ERROR);
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.self.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.external.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.mandated.guarantee.required"));
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID),
+                String.valueOf(selfSavigsId), String.valueOf(self1_guarantee)).build();
+        Integer selfGuarantee = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(selfSavigsId, null);
+        Assert.assertNotNull(selfGuarantee);
+
+        errorData = (ArrayList<HashMap>) this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, null, loanID,
+                CommonConstants.RESPONSE_ERROR);
+        assertFalse(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.self.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.external.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.mandated.guarantee.required"));
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID_external),
+                String.valueOf(externalSavigsId_1), String.valueOf(external1_guarantee)).build();
+        Integer externalGuarantee_1 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(externalSavigsId_1, null);
+        Assert.assertNotNull(externalGuarantee_1);
+
+        errorData = (ArrayList<HashMap>) this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, null, loanID,
+                CommonConstants.RESPONSE_ERROR);
+        assertFalse(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.self.guarantee.required"));
+        assertFalse(checkForErrorCode(errorData, "validation.msg.loan.guarantor.min.external.guarantee.required"));
+        assertTrue(checkForErrorCode(errorData, "validation.msg.loan.guarantor.mandated.guarantee.required"));
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID_external2),
+                String.valueOf(externalSavigsId_2), String.valueOf(external2_guarantee)).build();
+        Integer externalGuarantee_2 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        Assert.assertNotNull(externalGuarantee_2);
+        verifySavingsOnHoldBalance(externalSavigsId_2, null);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        self1_hold_funds += self1_guarantee;
+        external1_hold_funds += external1_guarantee;
+        external2_hold_funds += external2_guarantee;
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_2, external2_hold_funds);
+
+        System.out.println("-----------------------------------UNDO APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.undoApproval(loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        verifySavingsOnHoldBalance(selfSavigsId, new Float(0));
+        verifySavingsOnHoldBalance(externalSavigsId_1, new Float(0));
+        verifySavingsOnHoldBalance(externalSavigsId_2, new Float(0));
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_2, external2_hold_funds);
+        List<HashMap> guarantors = this.guarantorHelper.getAllGuarantor(loanID);
+        this.guarantorHelper.deleteGuarantor(externalGuarantor, loanID);
+        assertFalse((Boolean) this.guarantorHelper.getGuarantor(externalGuarantor, loanID, "status"));
+        HashMap errorlog = this.guarantorHelper.deleteGuarantor(withoutGuaranteeAmount, loanID);
+        ArrayList<HashMap> error = (ArrayList<HashMap>) errorlog.get(CommonConstants.RESPONSE_ERROR);
+        assertTrue(checkForErrorCode(error, "error.msg.loan.guarantor.not.found"));
+        guarantors = this.guarantorHelper.getAllGuarantor(loanID);
+        assertEquals(4, guarantors.size());
+        List<HashMap> externalGuarantee_1_details = (List<HashMap>) this.guarantorHelper.getGuarantor(externalGuarantee_1, loanID,
+                "guarantorFundingDetails");
+        Integer fundDetailId = (Integer) externalGuarantee_1_details.get(0).get("id");
+        errorlog = this.guarantorHelper.deleteGuarantor(externalGuarantee_1, fundDetailId, loanID);
+        error = (ArrayList<HashMap>) errorlog.get(CommonConstants.RESPONSE_ERROR);
+        assertTrue(checkForErrorCode(error, "validation.msg.loan.guarantor.min.external.guarantee.required"));
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID_external),
+                String.valueOf(externalSavigsId_3), String.valueOf(external1_guarantee)).build();
+        Integer externalGuarantee_3 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(externalSavigsId_3, external1_guarantee);
+        Assert.assertNotNull(externalGuarantee_3);
+
+        this.guarantorHelper.deleteGuarantor(externalGuarantee_1, fundDetailId, loanID);
+        guarantors = this.guarantorHelper.getAllGuarantor(loanID);
+        assertEquals(4, guarantors.size());
+        externalGuarantee_1_details = (List<HashMap>) this.guarantorHelper.getGuarantor(externalGuarantee_1, loanID,
+                "guarantorFundingDetails");
+        assertEquals(2, externalGuarantee_1_details.size());
+
+        for (HashMap map : externalGuarantee_1_details) {
+            if (map.get("id").equals(fundDetailId)) {
+                HashMap status = (HashMap) map.get("status");
+                assertEquals("guarantorFundStatusType.withdrawn", status.get("code"));
+            }
+        }
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // First repayment
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7 * 3);
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        external1_hold_funds -= new Float(827.5867);
+        external2_hold_funds -= new Float(413.7933);
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_3, external1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_2, external2_hold_funds);
+
+        // Second repayment
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7 * 2);
+        LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        totalDueForCurrentPeriod = (Float) loanSchedule.get(2).get("totalDueForPeriod");
+        external1_hold_funds -= new Float(831.4067);
+        external2_hold_funds -= new Float(415.7033333);
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_3, external1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_2, external2_hold_funds);
+
+        // third repayment
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -7);
+        LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        totalDueForCurrentPeriod = (Float) loanSchedule.get(3).get("totalDueForPeriod");
+        Float self1_hold_funds_temp = self1_hold_funds - new Float(741.355);
+        HashMap transactionDetail = this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds_temp);
+        verifySavingsOnHoldBalance(externalSavigsId_3, new Float(0));
+        verifySavingsOnHoldBalance(externalSavigsId_2, new Float(0));
+
+        // undo repayment
+        this.loanTransactionHelper.adjustLoanTransaction(loanID, (Integer) transactionDetail.get(CommonConstants.RESPONSE_RESOURCE_ID),
+                LOAN_REPAYMENT_DATE, "0", "");
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_3, external1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_2, external2_hold_funds);
+
+        // undo disbursal
+        loanStatusHashMap = this.loanTransactionHelper.undoDisbursal(loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        verifySavingsOnHoldBalance(selfSavigsId, new Float(self1_guarantee));
+        verifySavingsOnHoldBalance(externalSavigsId_3, new Float(external1_guarantee));
+        verifySavingsOnHoldBalance(externalSavigsId_2, new Float(external2_guarantee));
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testGuarantor_RECOVER_GUARANTEES() {
+
+        Float self1_hold_funds = new Float(0);
+        Float external1_hold_funds = new Float(0);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer clientID_external = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID_external);
+
+        final Integer selfSavigsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID,
+                String.valueOf(self1_balance));
+        final Integer externalSavigsId_1 = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID_external,
+                String.valueOf(external1_balance));
+
+        final Integer loanProductID = createLoanProductWithHoldFunds("40", "20", "20");
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -21);
+        String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, LOAN_DISBURSEMENT_DATE);
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        String guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID),
+                String.valueOf(selfSavigsId), String.valueOf(self1_guarantee)).build();
+        Integer selfGuarantee = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(selfSavigsId, null);
+        Assert.assertNotNull(selfGuarantee);
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID_external),
+                String.valueOf(externalSavigsId_1), String.valueOf(external1_guarantee)).build();
+        Integer externalGuarantee_1 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(externalSavigsId_1, null);
+        Assert.assertNotNull(externalGuarantee_1);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        self1_hold_funds += self1_guarantee;
+        external1_hold_funds += external1_guarantee;
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // First repayment
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        external1_hold_funds -= new Float(993.104);
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+
+        this.loanTransactionHelper.recoverFromGuarantor(loanID);
+        verifySavingsBalanceAndOnHoldBalance(selfSavigsId, new Float(0), self1_balance - self1_hold_funds);
+        verifySavingsBalanceAndOnHoldBalance(externalSavigsId_1, new Float(0), external1_balance - external1_hold_funds);
+
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testGuarantor_RECOVER_GUARANTEES_WITH_MORE_GUARANTEE() {
+
+        Float self1_hold_funds = new Float(0);
+        Float external1_hold_funds = new Float(0);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer clientID_external = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID_external);
+
+        Float selfBalance = new Float(10000);
+        Float externalBalance = new Float(10000);
+        Float selfguarantee = new Float(6000);
+        Float externalguarantee = new Float(7000);
+        
+        final Integer selfSavigsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID,
+                String.valueOf(selfBalance));
+        final Integer externalSavigsId_1 = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID_external,
+                String.valueOf(externalBalance));
+
+        final Integer loanProductID = createLoanProductWithHoldFunds("40", "20", "20");
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -21);
+        String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, LOAN_DISBURSEMENT_DATE);
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        String guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID),
+                String.valueOf(selfSavigsId), String.valueOf(selfguarantee)).build();
+        Integer selfGuarantee = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(selfSavigsId, null);
+        Assert.assertNotNull(selfGuarantee);
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID_external),
+                String.valueOf(externalSavigsId_1), String.valueOf(externalguarantee)).build();
+        Integer externalGuarantee_1 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(externalSavigsId_1, null);
+        Assert.assertNotNull(externalGuarantee_1);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        self1_hold_funds += selfguarantee;
+        external1_hold_funds += externalguarantee;
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // First repayment
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        external1_hold_funds -= new Float(3227.588);
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+
+        this.loanTransactionHelper.recoverFromGuarantor(loanID);
+        verifySavingsBalanceAndOnHoldBalance(selfSavigsId, new Float(0), selfBalance - new Float(4615.385));
+        verifySavingsBalanceAndOnHoldBalance(externalSavigsId_1, new Float(0), externalBalance - new Float(2901.8553));
+
+    }
+
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testGuarantor_WRITE_OFF_LOAN() {
+
+        Float self1_hold_funds = new Float(0);
+        Float external1_hold_funds = new Float(0);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer clientID_external = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID_external);
+
+        final Integer selfSavigsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID,
+                String.valueOf(self1_balance));
+        final Integer externalSavigsId_1 = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, clientID_external,
+                String.valueOf(external1_balance));
+
+        final Integer loanProductID = createLoanProductWithHoldFunds("40", "20", "20");
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -21);
+        String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
+        final Integer loanID = applyForLoanApplication(clientID, loanProductID, LOAN_DISBURSEMENT_DATE);
+        Assert.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        String guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID),
+                String.valueOf(selfSavigsId), String.valueOf(self1_guarantee)).build();
+        Integer selfGuarantee = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        verifySavingsOnHoldBalance(selfSavigsId, null);
+        Assert.assertNotNull(selfGuarantee);
+
+        guarantorJSON = new GuarantorTestBuilder().existingCustomerWithGuaranteeAmount(String.valueOf(clientID_external),
+                String.valueOf(externalSavigsId_1), String.valueOf(external1_guarantee)).build();
+        Integer externalGuarantee_1 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        Assert.assertNotNull(externalGuarantee_1);
+        verifySavingsOnHoldBalance(externalSavigsId_1, null);
+
+        System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+        self1_hold_funds += self1_guarantee;
+        external1_hold_funds += external1_guarantee;
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+
+        System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        // First repayment
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        todaysDate = Calendar.getInstance();
+        todaysDate.add(Calendar.DAY_OF_MONTH, -14);
+        String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
+        Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
+        external1_hold_funds -= new Float(993.104);
+        this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
+        verifySavingsOnHoldBalance(selfSavigsId, self1_hold_funds);
+        verifySavingsOnHoldBalance(externalSavigsId_1, external1_hold_funds);
+
+        todaysDate = Calendar.getInstance();
+        String LOAN_WRITEOFF_DATE = dateFormat.format(todaysDate.getTime());
+        this.loanTransactionHelper.writeOffLoan(LOAN_WRITEOFF_DATE, loanID);
+        verifySavingsBalanceAndOnHoldBalance(selfSavigsId, new Float(0), self1_balance);
+        verifySavingsBalanceAndOnHoldBalance(externalSavigsId_1, new Float(0), external1_balance);
+
+    }
+
+    private void verifySavingsOnHoldBalance(final Integer savingsId, final Float expectedBalance) {
+        Float onHoldAmount = (Float) this.savingsAccountHelper.getSavingsDetails(savingsId, "onHoldFunds");
+        assertEquals("Verifying On Hold Funds", expectedBalance, onHoldAmount);
+    }
+
+    @SuppressWarnings({ "rawtypes", "cast" })
+    private void verifySavingsBalanceAndOnHoldBalance(final Integer savingsId, final Float expectedBalance, final Float accountBalance) {
+        HashMap savingsDetails = (HashMap) this.savingsAccountHelper.getSavingsDetails(savingsId);
+        assertEquals("Verifying On Hold Funds", expectedBalance, savingsDetails.get("onHoldFunds"));
+        HashMap summary = (HashMap) savingsDetails.get("summary");
+        assertEquals("Verifying Account balance", accountBalance, summary.get("accountBalance"));
+    }
+
+    @SuppressWarnings("rawtypes")
+    private boolean checkForErrorCode(final ArrayList<HashMap> errorData, final String errorcode) {
+        boolean isExists = false;
+        for (HashMap errorMap : errorData) {
+            String actualErrorCode = (String) errorMap.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE);
+            if (actualErrorCode != null && actualErrorCode.equals(errorcode)) {
+                isExists = true;
+                break;
+            }
+        }
+        return isExists;
+
+    }
+
+    private Integer createLoanProductWithHoldFunds(final String mandatoryGuarantee, final String minimumGuaranteeFromGuarantor,
+            final String minimumGuaranteeFromOwnFunds) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        LoanProductTestBuilder builder = new LoanProductTestBuilder().withPrincipal("10000.00").withNumberOfRepayments("4")
+                .withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("2")
+                .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
+                .withOnHoldFundDetails(mandatoryGuarantee, minimumGuaranteeFromGuarantor, minimumGuaranteeFromOwnFunds);
+
+        final String loanProductJSON = builder.build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String disbursementDate) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal("10000.00") //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsWeeks() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsWeeks() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate(disbursementDate) //
+                .withSubmittedOnDate(disbursementDate) //
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/loanaccount/guarantor/GuarantorTestBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/loanaccount/guarantor/GuarantorTestBuilder.java
new file mode 100755
index 0000000..ea62f71
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/loanaccount/guarantor/GuarantorTestBuilder.java
@@ -0,0 +1,87 @@
+/**
+ * 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.integrationtests.loanaccount.guarantor;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+
+public class GuarantorTestBuilder {
+
+    private static final String GUARANTOR_TYPE_CUSTOMER = "1";
+    @SuppressWarnings("unused")
+    private static final String GUARANTOR_TYPE_STAFF = "2";
+    private static final String GUARANTOR_TYPE_EXTERNAL = "3";
+
+    private String guarantorTypeId = "1";
+    private String entityId = null;
+    private String addressLine1 = "addressLine1";
+    private String addressLine2 = "addressLine2";
+    private String city = "city";
+    private String state = "state";
+    private String zip = "123456";
+    private String guaranteeAmount = "500";
+    private String savingsId = null;
+
+    public String build() {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("guarantorTypeId", guarantorTypeId);
+        map.put("locale", "en");
+        if (GUARANTOR_TYPE_EXTERNAL.equals(guarantorTypeId)) {
+            map.put("firstname", Utils.randomNameGenerator("guarantor_FirstName_", 5));
+            map.put("lastname", Utils.randomNameGenerator("guarantor_LastName_", 4));
+            map.put("addressLine1", addressLine1);
+            map.put("addressLine2", addressLine2);
+            map.put("city", city);
+            map.put("state", state);
+            map.put("zip", zip);
+
+        } else if (GUARANTOR_TYPE_CUSTOMER.equals(guarantorTypeId)) {
+            map.put("entityId", entityId);
+            map.put("amount", guaranteeAmount);
+            map.put("savingsId", savingsId);
+        }
+        System.out.println("map : " + map);
+        return new Gson().toJson(map);
+    }
+
+    public GuarantorTestBuilder existingCustomerWithGuaranteeAmount(final String entityId, final String savingsId,
+            final String guaranteeAmount) {
+        this.entityId = entityId;
+        this.savingsId = savingsId;
+        this.guaranteeAmount = guaranteeAmount;
+        this.guarantorTypeId = GUARANTOR_TYPE_CUSTOMER;
+        return this;
+    }
+
+    public GuarantorTestBuilder existingCustomerWithoutGuaranteeAmount(final String entityId) {
+        this.entityId = entityId;
+        this.savingsId = null;
+        this.guaranteeAmount = null;
+        this.guarantorTypeId = GUARANTOR_TYPE_CUSTOMER;
+        return this;
+    }
+    
+    public GuarantorTestBuilder externalCustomer() {
+        this.guarantorTypeId = GUARANTOR_TYPE_EXTERNAL;
+        return this;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java
new file mode 100644
index 0000000..72cf27d
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.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.integrationtests.useradministration.roles;
+
+import java.util.HashMap;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class RolesHelper {
+
+    private static final String CREATE_ROLE_URL = "/fineract-provider/api/v1/roles?" + Utils.TENANT_IDENTIFIER;
+    private static final String ROLE_URL = "/fineract-provider/api/v1/roles";
+    private static final String DISABLE_ROLE_COMMAND = "disable";
+    private static final String ENABLE_ROLE_COMMAND = "enable";
+
+    public static Integer createRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_ROLE_URL, getTestCreaRoleAsJSON(), "resourceId");
+    }
+
+    public static String getTestCreaRoleAsJSON() {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("name", Utils.randomNameGenerator("Role_Name_", 5));
+        map.put("description", Utils.randomNameGenerator("Role_Description_", 10));
+        return new Gson().toJson(map);
+    }
+
+    public static HashMap<String, Object> getRoleDetails(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer roleId) {
+        final String GET_ROLE_URL = "/fineract-provider/api/v1/roles/" + roleId + "?" + Utils.TENANT_IDENTIFIER;
+        HashMap<String, Object> role = Utils.performServerGet(requestSpec, responseSpec, GET_ROLE_URL, "");
+        return role;
+    }
+
+    public static Integer disableRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer roleId) {
+        return Utils.performServerPost(requestSpec, responseSpec, createRoleOperationURL(DISABLE_ROLE_COMMAND, roleId), "", "resourceId");
+    }
+
+    public static Integer enableRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer roleId) {
+        return Utils.performServerPost(requestSpec, responseSpec, createRoleOperationURL(ENABLE_ROLE_COMMAND, roleId), "", "resourceId");
+    }
+
+    public static Integer deleteRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer roleId) {
+        return Utils.performServerDelete(requestSpec, responseSpec, createRoleOperationURL(ENABLE_ROLE_COMMAND, roleId), "resourceId");
+    }
+
+    private static String createRoleOperationURL(final String command, final Integer roleId) {
+        return ROLE_URL + "/" + roleId + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
new file mode 100644
index 0000000..8acabda
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests.useradministration.users;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+
+public class UserHelper {
+    private static final String CREATE_USER_URL = "/fineract-provider/api/v1/users?" + Utils.TENANT_IDENTIFIER;
+    private static final String USER_URL = "/fineract-provider/api/v1/users";
+
+    public static Integer createUser(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, int roleId, int staffId) {
+        return Utils.performServerPost(requestSpec, responseSpec, CREATE_USER_URL, getTestCreateUserAsJSON(roleId, staffId), "resourceId");
+    }
+
+    public static String getTestCreateUserAsJSON(int roleId, int staffId) {
+        String json = "{ \"username\": \"" + Utils.randomNameGenerator("User_Name_", 3)
+                + "\", \"firstname\": \"Test\", \"lastname\": \"User\", \"email\": \"whatever@mifos.org\","
+                + " \"officeId\": \"1\", \"staffId\": " + "\""
+                + Integer.toString(staffId)+"\",\"roles\": [\""
+                + Integer.toString(roleId) + "\"], \"sendPasswordToEmail\": false}";
+        System.out.println(json);
+        return json;
+
+    }
+
+    public static Integer deleteUser(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer userId) {
+        return Utils.performServerDelete(requestSpec, responseSpec, createRoleOperationURL(userId), "resourceId");
+    }
+
+    private static String createRoleOperationURL(final Integer userId) {
+        return USER_URL + "/" + userId + "?" + Utils.TENANT_IDENTIFIER;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableInstallmentsDecliningBalanceHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableInstallmentsDecliningBalanceHelper.java
new file mode 100644
index 0000000..46be990
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableInstallmentsDecliningBalanceHelper.java
@@ -0,0 +1,311 @@
+/**
+ * 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.integrationtests.variableinstallments;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+
+import com.google.gson.Gson;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class VariableInstallmentsDecliningBalanceHelper {
+
+    public static String createLoanProductWithoutVaribleConfig(final boolean multiDisburseLoan, final String accountingRule, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("1,00,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withTranches(multiDisburseLoan) //
+                .withAccounting(accountingRule, accounts).build(null);
+       return loanProductJSON ;
+    }
+    
+    public static String createLoanProductWithVaribleConfig(final boolean multiDisburseLoan, final String accountingRule, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("1,00,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withTranches(multiDisburseLoan) //
+                .withInterestCalculationPeriodTypeAsRepaymentPeriod(true)//
+                .withVariableInstallmentsConfig(Boolean.TRUE, new Integer(5), new Integer(90))//
+                .withAccounting(accountingRule, accounts).build(null);
+        return loanProductJSON ;
+    }
+    
+    public static String createLoanProductWithVaribleConfigwithEqualPrincipal(final boolean multiDisburseLoan, final String accountingRule, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("1,00,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualPrincipalPayment() //
+                .withInterestTypeAsDecliningBalance() //
+                .withTranches(multiDisburseLoan) //
+                .withInterestCalculationPeriodTypeAsRepaymentPeriod(true)//
+                .withVariableInstallmentsConfig(Boolean.TRUE, new Integer(5), new Integer(90))//
+                .withAccounting(accountingRule, accounts).build(null);
+        return loanProductJSON ;
+    }
+    
+    public static String applyForLoanApplication(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
+            final String savingsId, String principal) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2011") //
+                .withSubmittedOnDate("20 September 2011") //
+                .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+        return loanApplicationJSON ;
+    }
+    
+    public static String applyForLoanApplicationWithEqualPrincipal(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
+            final String savingsId, String principal) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualPrincipalPayments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2011") //
+                .withSubmittedOnDate("20 September 2011") //
+                .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+        return loanApplicationJSON ;
+    }
+    
+    public static String createDeleteVariations(ArrayList<Map> deletedInstallments) {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("deletedinstallments", createDeletedMap(deletedInstallments));
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+
+    private static ArrayList createDeletedMap(ArrayList<Map> deletedItems) {
+        ArrayList toReturn = new ArrayList<>();
+        for (Map map : deletedItems) {
+            ArrayList dueDate = (ArrayList) map.get("dueDate");
+            Map tosend = new HashMap();
+            tosend.put("dueDate", formatDate(dueDate));
+            toReturn.add(tosend);
+        }
+        return toReturn;
+    }
+
+    public static String createAddVariations() {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("newinstallments", createNewInstallments());
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+
+    private static ArrayList createNewInstallments() {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", "31 October 2011");
+        tosend.put("installmentAmount", "5000");
+        toReturn.add(tosend);
+        return toReturn;
+    }
+
+    public static String createModifiyVariations(Map firstSchedule) {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("modifiedinstallments", createModifyMap(firstSchedule));
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+    
+    private static ArrayList createNewInstallments(String date) {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", date);
+        tosend.put("installmentAmount", "5000");
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    
+    private static ArrayList createModifyMap(Map firstSchedule) {
+        ArrayList toReturn = new ArrayList<>();
+        ArrayList dueDate = (ArrayList) firstSchedule.get("dueDate");
+        Map tosend = new HashMap();
+        tosend.put("dueDate", formatDate(dueDate));
+        tosend.put("installmentAmount", 30000) ;
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    
+    public static String createAllVariations() {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("modifiedinstallments", createModifyMap("20 November 2011"));
+        exceptions.put("newinstallments", createNewInstallments("25 December 2011"));
+        exceptions.put("deletedinstallments", createDeletedMap("20 December 2011"));
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+    
+    public static String createAllVariationsWithEqualPrincipal() {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("modifiedinstallments", createModifyMapWithPrinciapl("20 November 2011"));
+        exceptions.put("newinstallments", createNewInstallmentsWithPrincipal("25 December 2011"));
+        exceptions.put("deletedinstallments", createDeletedMap("20 December 2011"));
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+    
+    private static ArrayList createNewInstallmentsWithPrincipal(String date) {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", date);
+        tosend.put("principal", "5000");
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    
+    private static ArrayList createModifyMapWithPrinciapl(String date) {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", date);
+        tosend.put("principal", 30000) ;
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    private static ArrayList createDeletedMap(String date) {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", date);
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    
+    private static ArrayList createModifyMap(String date) {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", date);
+        tosend.put("installmentAmount", 30000) ;
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    
+    public static String createModifiyDateVariations(String[] date, String[] newdate, String[] principal) {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("modifiedinstallments", createDateModifyMap(date, newdate, principal));
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+    
+    private static ArrayList createDateModifyMap(String[] date, String[] newdate, String[] installments) {
+        ArrayList toReturn = new ArrayList<>();
+        for(int i = 0 ; i < date.length; i++) {
+            Map tosend = new HashMap();
+            tosend.put("dueDate", date[i]);
+            tosend.put("modifiedDueDate", newdate[i]) ;
+            if(i < installments.length) {
+                tosend.put("installmentAmount", installments[i]) ;
+            }
+            toReturn.add(tosend);    
+        }
+        return toReturn;
+    }
+    public static String formatDate(ArrayList list) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, (int) list.get(0));
+        cal.set(Calendar.MONTH, (int) list.get(1) - 1);
+        cal.set(Calendar.DAY_OF_MONTH, (int) list.get(2));
+        Date date = cal.getTime();
+        DateFormat requiredFormat = new SimpleDateFormat("dd MMMM YYYY");
+        return requiredFormat.format(date);
+    }
+
+    public Map<String, Object> createModifyVarations() {
+        return null;
+    }
+
+    public static ArrayList<Map> constructVerifyData(String[] dates, String[] installments) {
+        ArrayList<Map> toReturn = new ArrayList<>();
+        for (int i = 0; i < dates.length; i++) {
+            Map<String, String> map = new HashMap<>();
+            map.put("dueDate", dates[i]);
+            map.put("installmentAmount", installments[i]);
+            toReturn.add(map);
+        }
+        return toReturn;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableInstallmentsFlatHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableInstallmentsFlatHelper.java
new file mode 100644
index 0000000..6385879
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableInstallmentsFlatHelper.java
@@ -0,0 +1,230 @@
+/**
+ * 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.integrationtests.variableinstallments;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+
+import com.google.gson.Gson;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class VariableInstallmentsFlatHelper {
+
+    public static String createLoanProductWithVaribleConfig(final boolean multiDisburseLoan, final String accountingRule, final Account... accounts) {
+        System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("1,00,000.00") //
+                .withNumberOfRepayments("4") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("1") //
+                .withAmortizationTypeAsEqualPrincipalPayment() 
+                .withInterestTypeAsFlat() //
+                .withTranches(multiDisburseLoan) //
+                .withInterestCalculationPeriodTypeAsRepaymentPeriod(true)//
+                .withVariableInstallmentsConfig(Boolean.TRUE, new Integer(5), new Integer(90))//
+                .withAccounting(accountingRule, accounts).build(null);
+        return loanProductJSON ;
+    }
+    
+   
+    
+    public static String applyForLoanApplication(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
+            final String savingsId, String principal) {
+        System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("4") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("4") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withAmortizationTypeAsEqualPrincipalPayments() //
+                .withInterestTypeAsFlatBalance() //
+                .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+                .withExpectedDisbursementDate("20 September 2011") //
+                .withSubmittedOnDate("20 September 2011") //
+                .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+        return loanApplicationJSON ;
+    }
+    
+    public static String createDeleteVariations(ArrayList<Map> deletedInstallments) {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("deletedinstallments", createDeletedMap(deletedInstallments));
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+
+    private static ArrayList createDeletedMap(ArrayList<Map> deletedItems) {
+        ArrayList toReturn = new ArrayList<>();
+        for (Map map : deletedItems) {
+            ArrayList dueDate = (ArrayList) map.get("dueDate");
+            Map tosend = new HashMap();
+            tosend.put("dueDate", formatDate(dueDate));
+            toReturn.add(tosend);
+        }
+        return toReturn;
+    }
+
+    public static String createAddVariations() {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("newinstallments", createNewInstallments());
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+
+    private static ArrayList createNewInstallments() {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", "31 October 2011");
+        tosend.put("principal", "5000");
+        toReturn.add(tosend);
+        return toReturn;
+    }
+
+    public static String createModifiyVariations(Map firstSchedule) {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("modifiedinstallments", createModifyMap(firstSchedule));
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+    
+    private static ArrayList createNewInstallments(String date) {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", date);
+        tosend.put("principal", "5000");
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    
+    public static String createModifiyDateVariations(String[] date, String[] newdate, String[] principal) {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("modifiedinstallments", createDateModifyMap(date, newdate, principal));
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+    
+    private static ArrayList createDateModifyMap(String[] date, String[] newdate, String[] principal) {
+        ArrayList toReturn = new ArrayList<>();
+        for(int i = 0 ; i < date.length; i++) {
+            Map tosend = new HashMap();
+            tosend.put("dueDate", date[i]);
+            tosend.put("modifiedDueDate", newdate[i]) ;
+            if(i < principal.length) {
+                tosend.put("principal", principal[i]) ;
+            }
+            toReturn.add(tosend);    
+        }
+        return toReturn;
+    }
+    
+    
+    private static ArrayList createModifyMap(Map firstSchedule) {
+        ArrayList toReturn = new ArrayList<>();
+        ArrayList dueDate = (ArrayList) firstSchedule.get("dueDate");
+        Map tosend = new HashMap();
+        tosend.put("dueDate", formatDate(dueDate));
+        tosend.put("principal", 30000) ;
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    
+    public static String createAllVariations() {
+        Map<String, Object> toReturn = new HashMap<>();
+        toReturn.put("locale", "en");
+        toReturn.put("dateFormat", "dd MMMM yyyy");
+        Map exceptions = new HashMap<>();
+        exceptions.put("modifiedinstallments", createModifyMap("20 November 2011"));
+        exceptions.put("newinstallments", createNewInstallments("25 December 2011"));
+        exceptions.put("deletedinstallments", createDeletedMap("20 December 2011"));
+        toReturn.put("exceptions", exceptions);
+        String json = new Gson().toJson(toReturn);
+        return json;
+    }
+    
+    private static ArrayList createDeletedMap(String date) {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", date);
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    
+    private static ArrayList createModifyMap(String date) {
+        ArrayList toReturn = new ArrayList<>();
+        Map tosend = new HashMap();
+        tosend.put("dueDate", date);
+        tosend.put("principal", 30000) ;
+        toReturn.add(tosend);
+        return toReturn;
+    }
+    
+    public static String formatDate(ArrayList list) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, (int) list.get(0));
+        cal.set(Calendar.MONTH, (int) list.get(1) - 1);
+        cal.set(Calendar.DAY_OF_MONTH, (int) list.get(2));
+        Date date = cal.getTime();
+        DateFormat requiredFormat = new SimpleDateFormat("dd MMMM YYYY");
+        return requiredFormat.format(date);
+    }
+
+    public Map<String, Object> createModifyVarations() {
+        return null;
+    }
+
+    public static ArrayList<Map> constructVerifyData(String[] dates, String[] installments) {
+        ArrayList<Map> toReturn = new ArrayList<>();
+        for (int i = 0; i < dates.length; i++) {
+            Map<String, String> map = new HashMap<>();
+            map.put("dueDate", dates[i]);
+            map.put("installmentAmount", installments[i]);
+            toReturn.add(map);
+        }
+        return toReturn;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableInstallmentsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableInstallmentsIntegrationTest.java
new file mode 100644
index 0000000..3c93c52
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableInstallmentsIntegrationTest.java
@@ -0,0 +1,440 @@
+/**
+ * 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.integrationtests.variableinstallments;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class VariableInstallmentsIntegrationTest {
+
+    
+    private static final String NONE = "1";
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    
+    @Before
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+    }
+    
+    @Test
+    public void testVariableLoanProductCreation() {
+        final String json = VariableInstallmentsDecliningBalanceHelper.createLoanProductWithVaribleConfig(false, NONE);
+        final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(json);
+        System.out.println("------------------------------RETRIEVING CREATED LOAN PRODUCT DETAILS ---------------------------------------");
+        Map loanProduct = (Map)loanTransactionHelper.getLoanProductDetail(requestSpec, responseSpec, loanProductID, "") ;
+        Assert.assertTrue((Boolean)loanProduct.get("allowVariableInstallments")) ;
+        Assert.assertEquals(new Integer(5), loanProduct.get("minimumGap")) ;
+        Assert.assertEquals(new Integer(90), loanProduct.get("maximumGap")) ;
+    }
+    
+   
+    @Test
+    public void testLoanProductCreation() {
+        final String  josn = VariableInstallmentsDecliningBalanceHelper.createLoanProductWithoutVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(josn);
+        System.out.println("------------------------------RETRIEVING CREATED LOAN PRODUCT DETAILS ---------------------------------------");
+        Map loanProduct = (Map)loanTransactionHelper.getLoanProductDetail(requestSpec, responseSpec, loanProductID, "") ;
+        Assert.assertTrue(!(Boolean)loanProduct.get("allowVariableInstallments")) ;
+    }
+    
+    @Test
+    public void testDeleteInstallmentsWithDecliningBalanceEqualInstallments() {
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsDecliningBalanceHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsDecliningBalanceHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        //Integer loanID = 49 ;
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        ArrayList toDelete = new ArrayList<>() ;
+        toDelete.add(periods.get(1)) ;
+        String toDeletedata = VariableInstallmentsDecliningBalanceHelper.createDeleteVariations(toDelete) ;
+        HashMap modifiedReschdule = transactionHelper.validateVariations(toDeletedata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsDecliningBalanceHelper.constructVerifyData(new String[] {"20 November 2011", "20 December 2011", "20 January 2012"}, 
+                new String[] {"34675.47", "34675.47", "36756.26"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;
+        transactionHelper.submitVariations(toDeletedata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+        
+    }
+    private void assertAfterSubmit(ArrayList<Map> serverData, ArrayList<Map> expectedData) {
+        Assert.assertTrue(serverData.size() == expectedData.size()) ;
+        for(int i = 0 ; i < serverData.size(); i++) {
+            Map<String, Object> serverMap = serverData.get(i) ;
+            Map<String, Object> expectedMap = expectedData.get(i) ;
+            Assert.assertTrue(VariableInstallmentsDecliningBalanceHelper.formatDate((ArrayList)serverMap.get("dueDate")).equals(expectedMap.get("dueDate"))) ;
+            Assert.assertTrue(serverMap.get("totalOutstandingForPeriod").toString().equals(expectedMap.get("installmentAmount"))) ;
+        }
+    }
+    
+    @Test
+    public void testAddInstallmentsWithDecliningBalanceEqualInstallments() {
+        //31 October 2011 - 5000
+        //Result: 20 October 2011 - 21,215.84, 31 October 2011 - 5000, 20 November 2011 26,477.31, 20 December 2011 26,477.31, 20 January 2012 25,947.7
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsDecliningBalanceHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsDecliningBalanceHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+       // Integer loanID = 57 ;
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        String addVariationsjsondata = VariableInstallmentsDecliningBalanceHelper.createAddVariations() ;
+        HashMap modifiedReschdule = transactionHelper.validateVariations(addVariationsjsondata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsDecliningBalanceHelper.constructVerifyData(new String[] {"20 October 2011", "31 October 2011", "20 November 2011", "20 December 2011", "20 January 2012"}, 
+                new String[] {"21215.84", "5000.0", "26477.31", "26477.31", "25947.7"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;
+        transactionHelper.submitVariations(addVariationsjsondata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+    }
+    
+    @Test
+    public void testModifyInstallmentWithDecliningBalanceEqualInstallments() {
+        //20 October 2011 - 30000 modify
+        //Result 20 October 2011 - 30000.0, 20 November 2011 - 24,966.34, 20 December 2011 - 24,966.34, 20 January 2012 - 24,966.33
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsDecliningBalanceHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsDecliningBalanceHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        //Integer loanID = 57 ;
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        String addVariationsjsondata = VariableInstallmentsDecliningBalanceHelper.createModifiyVariations((Map)periods.get(1)) ; //0th position will have disbursement 
+        HashMap modifiedReschdule = transactionHelper.validateVariations(addVariationsjsondata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsDecliningBalanceHelper.constructVerifyData(new String[] {"20 October 2011", "20 November 2011", "20 December 2011", "20 January 2012"}, 
+                new String[] {"30000.0", "24966.34", "24966.34", "24966.33"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;
+        transactionHelper.submitVariations(addVariationsjsondata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+        
+    }
+    
+    @Test
+    public void testAllVariationsDecliningBalancewithEqualInstallments() {
+         // Request: Delete 20 December 2011 26,262.38, Modify 20 November 2011 from 26,262.38 to 30000, Add 25 December 2011 5000
+         // Result: 20 October 2011 - 26262.38, 20 November 2011 - 30000, 25 December 2011 - 5000, 20 January 2012 - 44077  
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsDecliningBalanceHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsDecliningBalanceHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        //Integer loanID = 57 ;
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        
+        String addVariationsjsondata = VariableInstallmentsDecliningBalanceHelper.createAllVariations() ; //0th position will have disbursement 
+        HashMap modifiedReschdule = transactionHelper.validateVariations(addVariationsjsondata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsDecliningBalanceHelper.constructVerifyData(new String[] {"20 October 2011", "20 November 2011", "25 December 2011", "20 January 2012"}, 
+                new String[] {"26262.38", "30000.0", "5000.0", "44077.0"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;
+        transactionHelper.submitVariations(addVariationsjsondata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+    }
+    
+    @Test
+    public void testAllVariationsDecliningBalancewithEqualPrincipal() {
+         // Request: Delete 20 December 2011 26,262.38, Modify 20 November 2011 from 26,262.38 to 30000, Add 25 December 2011 5000
+         // Result: 20 October 2011 - 27000.0, 20 November 2011 - 31500.0, 25 December 2011 - 6045.16, 20 January 2012 - 40670.97  
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsDecliningBalanceHelper.createLoanProductWithVaribleConfigwithEqualPrincipal(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsDecliningBalanceHelper.applyForLoanApplicationWithEqualPrincipal(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+       // Integer loanID = 109 ;
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        
+        String addVariationsjsondata = VariableInstallmentsDecliningBalanceHelper.createAllVariationsWithEqualPrincipal() ; //0th position will have disbursement 
+        HashMap modifiedReschdule = transactionHelper.validateVariations(addVariationsjsondata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsDecliningBalanceHelper.constructVerifyData(new String[] {"20 October 2011", "20 November 2011", "25 December 2011", "20 January 2012"}, 
+                new String[] {"27000.0", "31500.0", "6045.16", "40670.97"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;
+        transactionHelper.submitVariations(addVariationsjsondata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+    }
+    
+    @Test
+    public void testModifyDatesWithDecliningBalanceEqualInstallments() {
+        //Modify 20 December 2011:25000 -> 04 January 2012:20000
+        //Modify 20 January 2012 -> 08 February 2012
+        //Result 20 October 2011 -26262.38, 20 November 2011 - 26262.38, 04 January 2012 -20000, 08 February 2012 - 33242.97
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsDecliningBalanceHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsDecliningBalanceHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        //Integer loanID = 57 ;
+        
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        //
+        //
+        String addVariationsjsondata = VariableInstallmentsDecliningBalanceHelper.createModifiyDateVariations(new String[]{"20 December 2011", "20 January 2012"}, new String[]{"04 January 2012", "08 February 2012"}, new String[]{"20000"}) ; //0th position will have disbursement 
+        HashMap modifiedReschdule = transactionHelper.validateVariations(addVariationsjsondata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsDecliningBalanceHelper.constructVerifyData(new String[] {"20 October 2011", "20 November 2011", "04 January 2012", "08 February 2012"}, 
+                new String[] {"26262.38", "26262.38", "20000.0", "33242.97"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;   
+        transactionHelper.submitVariations(addVariationsjsondata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+    }
+    
+    // Interest Type is FLAT
+    @Test
+    public void testDeleteInstallmentsWithInterestTypeFlat() {
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsFlatHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsFlatHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        ArrayList toDelete = new ArrayList<>() ;
+        toDelete.add(periods.get(1)) ;
+        String toDeletedata = VariableInstallmentsFlatHelper.createDeleteVariations(toDelete) ;
+        HashMap modifiedReschdule = transactionHelper.validateVariations(toDeletedata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsFlatHelper.constructVerifyData(new String[] {"20 November 2011", "20 December 2011", "20 January 2012"}, 
+                new String[] {"36000.0", "36000.0", "36000.0"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;
+        transactionHelper.submitVariations(toDeletedata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+    }
+    
+    @Test
+    public void testAddInstallmentsWithInterestTypeFlat() {
+        //31 October 2011 - 5000
+        //Result: 20 October 2011 - 21600.0, 31 October 2011 - 6600.0, 20 November 2011 26600.0, 20 December 2011 26600.0, 20 January 2012 26600.0
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsFlatHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsFlatHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        //Integer loanID = 67 ;
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        String addVariationsjsondata = VariableInstallmentsFlatHelper.createAddVariations() ;
+        HashMap modifiedReschdule = transactionHelper.validateVariations(addVariationsjsondata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsFlatHelper.constructVerifyData(new String[] {"20 October 2011", "31 October 2011", "20 November 2011", "20 December 2011", "20 January 2012"}, 
+                new String[] {"21600.0", "6600.0", "26600.0", "26600.0", "26600.0"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;
+        transactionHelper.submitVariations(addVariationsjsondata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+    }
+    
+    @Test
+    public void testModifyInstallmentsWithInterestTypeisFlat() {
+        //20 October 2011 - 30000 modify
+        //Result 20 October 2011 - 32000.0, 20 November 2011 - 25333.33, 20 December 2011 - 25333.33, 20 January 2012 - 25333.34
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsFlatHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsFlatHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        //Integer loanID = 67 ;
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        String addVariationsjsondata = VariableInstallmentsFlatHelper.createModifiyVariations((Map)periods.get(1)) ; //0th position will have disbursement 
+        HashMap modifiedReschdule = transactionHelper.validateVariations(addVariationsjsondata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsFlatHelper.constructVerifyData(new String[] {"20 October 2011", "20 November 2011", "20 December 2011", "20 January 2012"}, 
+                new String[] {"32000.0", "25333.33", "25333.33", "25333.34"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;
+        transactionHelper.submitVariations(addVariationsjsondata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+    }
+    
+    @Test
+    public void testAllVariationsWithInterestTypeFlat() {
+         // Request: Delete 20 December 2011 25000.0, Modify 20 November 2011 from 25,000 to 30000, Add 25 December 2011 5000
+         // Result: 20 October 2011 - 27000.0, 20 November 2011 - 32000.0, 25 December 2011 - 7000.0, 20 January 2012 - 42000.0  
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsFlatHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsFlatHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        //Integer loanID = 67 ;
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        
+        String addVariationsjsondata = VariableInstallmentsFlatHelper.createAllVariations() ; //0th position will have disbursement 
+        HashMap modifiedReschdule = transactionHelper.validateVariations(addVariationsjsondata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsFlatHelper.constructVerifyData(new String[] {"20 October 2011", "20 November 2011", "25 December 2011", "20 January 2012"}, 
+                new String[] {"27000.0", "32000.0", "7000.0", "42000.0"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;
+        transactionHelper.submitVariations(addVariationsjsondata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+    }
+    
+    @Test
+    public void testModifyDatesWithInterestTypeFlat() {
+        //Modify 20 December 2011:25000 -> 04 January 2012:20000
+        //Modify 20 January 2012 -> 08 February 2012
+        //Result 20 October 2011 -27306.45, 20 November 2011 - 27306.45, 04 January 2012 -22306.45, 08 February 2012 - 32306.46
+        VariableIntallmentsTransactionHelper transactionHelper = new VariableIntallmentsTransactionHelper(requestSpec, responseSpec) ;
+        final String  loanProductJson = VariableInstallmentsFlatHelper.createLoanProductWithVaribleConfig(false, NONE);
+        Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJson);
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final String json = VariableInstallmentsFlatHelper.applyForLoanApplication(clientID, loanProductID, null, null, "1,00,000.00");
+        final Integer loanID = this.loanTransactionHelper.getLoanId(json);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+        //Integer loanID = 67 ;
+        
+        Map list = transactionHelper.retrieveSchedule(loanID) ;
+        Map repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        ArrayList periods = (ArrayList)repaymentSchedule.get("periods") ;
+        //
+        //
+        String addVariationsjsondata = VariableInstallmentsFlatHelper.createModifiyDateVariations(new String[]{"20 December 2011", "20 January 2012"}, new String[]{"04 January 2012", "08 February 2012"}, new String[]{"20000"}) ; //0th position will have disbursement 
+        HashMap modifiedReschdule = transactionHelper.validateVariations(addVariationsjsondata, loanID) ;
+        ArrayList newperiods = (ArrayList) modifiedReschdule.get("periods") ;
+        ArrayList toVerifyData = VariableInstallmentsFlatHelper.constructVerifyData(new String[] {"20 October 2011", "20 November 2011", "04 January 2012", "08 February 2012"}, 
+                new String[] {"27306.45", "27306.45", "22306.45", "32306.46"}) ;
+        assertAfterSubmit(newperiods, toVerifyData) ;   
+        transactionHelper.submitVariations(addVariationsjsondata, loanID) ;
+        list = transactionHelper.retrieveSchedule(loanID) ;
+        repaymentSchedule = (Map)list.get("repaymentSchedule") ;
+        periods = (ArrayList)repaymentSchedule.get("periods") ;
+        periods.remove(0) ; //Repayments Schedule includes disbursement also. So remove this.
+        assertAfterSubmit(periods, toVerifyData) ;
+    }
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableIntallmentsTransactionHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableIntallmentsTransactionHelper.java
new file mode 100644
index 0000000..e1ee060
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/variableinstallments/VariableIntallmentsTransactionHelper.java
@@ -0,0 +1,59 @@
+/**
+ * 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.integrationtests.variableinstallments;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.integrationtests.common.Utils;
+
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+
+@SuppressWarnings("rawtypes")
+public class VariableIntallmentsTransactionHelper {
+
+    private final String URL = "https://localhost:8443/fineract-provider/api/v1/loans/" ;
+    
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+    
+    public VariableIntallmentsTransactionHelper(final RequestSpecification requestSpec, 
+            final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec ;
+        this.responseSpec = responseSpec ;
+    }
+    
+    
+    public Map retrieveSchedule(Integer loanId) {
+        String url = URL+loanId+"?associations=repaymentSchedule&exclude=guarantors&"+Utils.TENANT_IDENTIFIER ;
+        return Utils.performServerGet(requestSpec, responseSpec, url, "");
+    }
+    
+    public HashMap validateVariations(final String exceptions, Integer loanId) {
+        String url = URL+loanId+"/schedule?command=calculateLoanSchedule&"+Utils.TENANT_IDENTIFIER ;
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, url, exceptions, "");
+    }
+    
+    public HashMap submitVariations(final String exceptions, Integer loanId) {
+        String url = URL+loanId+"/schedule?command=addVariations&"+Utils.TENANT_IDENTIFIER ;
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, url, exceptions, "");
+    }
+}
diff --git a/fineract-provider/src/main/dist/How to run Fineract.txt b/fineract-provider/src/main/dist/How to run Fineract.txt
new file mode 100644
index 0000000..83c34e6
--- /dev/null
+++ b/fineract-provider/src/main/dist/How to run Fineract.txt
@@ -0,0 +1,30 @@
+Welcome to the Fineract community!
+===============================
+
+Depending on the OS you're running, you should have "runfineract.sh" (for Linux and Mac) and
+"runfineract.bat" (for Windows) in this directory. These are small scripts that allow you to
+run Apache Fineract on your computer locally. To be able to run Fineract, just double-click on the script,
+that is, "runfineract.sh" if you are on Linux or Mac, or "runfineract.bat" if you are on Windows.
+
+If you have a web front-end that you want to use along with the backend server, put your index.html
+along with all the other source files inside a (sub-folder of) the "apps" folder in the current
+directory from where runfineract.bat/.sh is launched.  The web UI front-end is then accessible
+under fineract-provider/apps - for example:
+
+    https://localhost:8443/fineract-provider/apps/community-app/index.html?baseApiUrl=https://localhost:8443&tenantIdentifier=default#/home
+
+We hope you'll have an amazing experience working with Fineract!
+
+
+PS, internal note to fineract developers preparing packages, only: For this to work, the WAR of
+the Fineract distribution ZIP package will (currently) have to be built using
+the Gradle 'dev' environment profile - e.g. using this command:
+
+    ./gradlew -Penv=dev clean war dist
+
+We now also have a script which convenently prepares both the
+UI front-end and the API back-end together into a ZIP:
+
+    git clone --recursive  https://github.com/openMF/mifosx/
+    git submodule update --init --recursive; git submodule status
+    ./build.sh
diff --git a/fineract-provider/src/main/dist/runfineract.bat b/fineract-provider/src/main/dist/runfineract.bat
new file mode 100644
index 0000000..05819f5
--- /dev/null
+++ b/fineract-provider/src/main/dist/runfineract.bat
@@ -0,0 +1,10 @@
+java -version >nul 2>&1 && (
+    echo "Found Java."
+) || (
+    echo "Java should be installed to run Fineract."
+    exit
+)
+
+echo "Starting Fineract ..... "
+
+java -Djava.awt.headless=false -jar fineract-provider.war
diff --git a/fineract-provider/src/main/dist/runfineract.sh b/fineract-provider/src/main/dist/runfineract.sh
new file mode 100755
index 0000000..5471aa3
--- /dev/null
+++ b/fineract-provider/src/main/dist/runfineract.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# First of all, check if Java is installed on this computer
+
+if hash java; then
+    echo "Found Java:"
+    echo $(java -version)
+else
+    echo "Java should be installed to run fineract."
+    exit
+fi
+
+echo "Starting fineract ..... "
+
+java -Djava.awt.headless=false -jar fineract-provider.war
diff --git a/fineract-provider/src/main/java/org/apache/fineract/ServerApplication.java b/fineract-provider/src/main/java/org/apache/fineract/ServerApplication.java
new file mode 100644
index 0000000..55f9391
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/ServerApplication.java
@@ -0,0 +1,38 @@
+package org.apache.fineract;
+
+import org.apache.fineract.infrastructure.core.boot.AbstractApplicationConfiguration;
+import org.apache.fineract.infrastructure.core.boot.ApplicationExitUtil;
+import org.apache.fineract.infrastructure.core.boot.EmbeddedTomcatWithSSLConfiguration;
+import org.apache.fineract.infrastructure.core.boot.db.DataSourceConfiguration;
+import org.apache.fineract.infrastructure.core.boot.db.DataSourceProperties;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Import;
+
+/**
+ * Fineract main() application which launches Fineract in an embedded Tomcat HTTP
+ * (using Spring Boot).
+ *
+ * The DataSource used is a to a "normal" external database (not use MariaDB4j).
+ * This DataSource can be configured with parameters, see {@link DataSourceProperties}.
+ *
+ * You can easily launch this via Debug as Java Application in your IDE -
+ * without needing command line Gradle stuff, no need to build and deploy a WAR,
+ * remote attachment etc.
+ *
+ * It's the old/classic Mifos (non-X) Workspace 2.0 reborn for Fineract! ;-)
+ *
+ * @see DataSourceProperties about how to configure the DataSource used
+ * @see ServerWithMariaDB4jApplication for an alternative with an embedded DB
+ */
+public class ServerApplication {
+
+	@Import({ DataSourceConfiguration.class, EmbeddedTomcatWithSSLConfiguration.class })
+	private static class Configuration extends AbstractApplicationConfiguration { }
+
+	public static void main(String[] args) throws Exception {
+		ConfigurableApplicationContext ctx = SpringApplication.run(Configuration.class, args);
+		ApplicationExitUtil.waitForKeyPressToCleanlyExit(ctx);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/ServerWithMariaDB4jApplication.java b/fineract-provider/src/main/java/org/apache/fineract/ServerWithMariaDB4jApplication.java
new file mode 100644
index 0000000..ecfedec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/ServerWithMariaDB4jApplication.java
@@ -0,0 +1,87 @@
+package org.apache.fineract;
+
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+
+import org.apache.fineract.infrastructure.core.boot.AbstractApplicationConfiguration;
+import org.apache.fineract.infrastructure.core.boot.ApplicationExitUtil;
+import org.apache.fineract.infrastructure.core.boot.EmbeddedTomcatWithSSLConfiguration;
+import org.apache.fineract.infrastructure.core.boot.db.MariaDB4jDataSourceConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.io.Resource;
+
+/**
+ * Fineract main() application which launches Fineract in an embedded Tomcat HTTP
+ * server (using Spring Boot), as well as an embedded database (using
+ * MariaDB4j).
+ *
+ * You can easily launch this via Debug as Java Application in your IDE -
+ * without needing command line Gradle stuff, no need to build and deploy a WAR,
+ * remote attachment etc.
+ *
+ * It's the old/classic Mifos (non-X) Workspace 2.0 reborn for Fineract! ;-)
+ *
+ * @see ServerApplication for the same without the embedded MariaDB4j database
+ */
+public class ServerWithMariaDB4jApplication {
+    private final static Logger logger = LoggerFactory.getLogger(ServerWithMariaDB4jApplication.class);
+
+	@Import({ MariaDB4jDataSourceConfiguration.class, EmbeddedTomcatWithSSLConfiguration.class })
+	public static class Configuration extends AbstractApplicationConfiguration { }
+
+	public static void main(String[] args) throws Exception {
+		ConfigurableApplicationContext ctx = SpringApplication.run(Configuration.class, args);
+		if (!Desktop.isDesktopSupported()) {
+			logger.info("Not going to open UI homepage in local web browser, because !Desktop.isDesktopSupported()");
+
+		} else {
+			// apps/community-app/dist/community-app/index.html
+			Resource distResource = ctx.getResource("file:" + System.getProperty("user.dir") +
+					System.getProperty("file.separator") + "apps" +
+					System.getProperty("file.separator") + "community-app" +
+					System.getProperty("file.separator") + "dist" +
+					System.getProperty("file.separator") + "community-app" +
+					System.getProperty("file.separator") + "index.html");
+			URI distURI = URI.create("https://localhost:8443/fineract-provider" +
+					"/apps/community-app/index.html?baseApiUrl=https://localhost:8443" +
+					"&tenantIdentifier=default#/");
+
+			// apps/community-app/app/index.html
+			Resource devResource = ctx.getResource("file:" + System.getProperty("user.dir") +
+					System.getProperty("file.separator") + "apps" +
+					System.getProperty("file.separator") + "community-app" +
+					System.getProperty("file.separator") + "app" +
+					System.getProperty("file.separator") + "index.html");
+			URI devURI = URI.create("https://localhost:8443/fineract-provider" +
+					"/apps/community-app/app/index.html?baseApiUrl=https://localhost:8443" +
+					"&tenantIdentifier=default#/");
+
+			if (distResource.exists()) {
+				openWebBrowser(distURI);
+			} else if (devResource.exists()) {
+				openWebBrowser(devURI);				
+			} else {
+				logger.error("Cannot open Fineract UI in browser; not found: " + distResource.toString());
+			}
+		}
+		
+		// TODO Tray Icon stuff; dig out my very own old @see https://github.com/mifos/head/tree/hudsonBuild-MIFOS-5157_Launch4j-EXE_NewDist-squash1/server-jetty/src/main/java/org/mifos/server/tray
+		
+		ApplicationExitUtil.waitForKeyPressToCleanlyExit(ctx);
+	}
+
+	private static void openWebBrowser(URI uri) {
+		try {
+			logger.info("Opening Fineract UI in browser: " + uri.toString());
+			Desktop.getDesktop().browse(uri);
+		} catch (IOException e) {
+			logger.error("IOException when opening Fineract UI in browser: " + uri.toString(), e);
+		}
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingApiResource.java
new file mode 100755
index 0000000..489ebc4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingApiResource.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.accounting.accrual.api;
+
+import javax.ws.rs.Consumes;
+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.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/runaccruals")
+@Component
+@Scope("singleton")
+public class AccrualAccountingApiResource {
+
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final DefaultToApiJsonSerializer<String> apiJsonSerializerService;
+
+    @Autowired
+    public AccrualAccountingApiResource(final DefaultToApiJsonSerializer<String> apiJsonSerializerService,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiJsonSerializerService = apiJsonSerializerService;
+
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String executePeriodicAccrualAccounting(final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().excuteAccrualAccounting().withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+}
\ No newline at end of file
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
new file mode 100755
index 0000000..000b3e4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingConstants.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.accounting.accrual.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class AccrualAccountingConstants {
+
+    public static final String accrueTillParamName = "tillDate";
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+    
+    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/handler/ExecutePeriodicAccrualCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/handler/ExecutePeriodicAccrualCommandHandler.java
new file mode 100755
index 0000000..b1724bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/handler/ExecutePeriodicAccrualCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.accrual.handler;
+
+import org.apache.fineract.accounting.accrual.service.AccrualAccountingWritePlatformService;
+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 = "PERIODICACCRUALACCOUNTING", action = "EXECUTE")
+public class ExecutePeriodicAccrualCommandHandler implements NewCommandSourceHandler {
+
+    private final AccrualAccountingWritePlatformService writePlatformService;
+
+    @Autowired
+    public ExecutePeriodicAccrualCommandHandler(final AccrualAccountingWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.executeLoansPeriodicAccrual(command);
+    }
+}
\ No newline at end of file
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
new file mode 100755
index 0000000..659380e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/serialization/AccrualAccountingDataValidator.java
@@ -0,0 +1,80 @@
+/**
+ * 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.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.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.loanaccount.guarantor.command.GuarantorCommand;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link GuarantorCommand}'s.
+ */
+@Component
+public final class AccrualAccountingDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public AccrualAccountingDataValidator(final FromJsonHelper fromApiJsonfromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonfromApiJsonHelper;
+    }
+
+    public void validateLoanPeriodicAccrualData(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, LOAN_PERIODIC_REQUEST_DATA_PARAMETERS);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(PERIODIC_ACCRUAL_ACCOUNTING_RESOURCE_NAME);
+
+        final LocalDate date = this.fromApiJsonHelper.extractLocalDateNamed(accrueTillParamName, element);
+        baseDataValidator.reset().parameter(accrueTillParamName).value(date).notNull().validateDateBefore(DateUtils.getLocalDateOfTenant());
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    public 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/accounting/accrual/service/AccrualAccountingWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/service/AccrualAccountingWritePlatformService.java
new file mode 100755
index 0000000..8a471a6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/service/AccrualAccountingWritePlatformService.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.accounting.accrual.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface AccrualAccountingWritePlatformService {
+
+    CommandProcessingResult executeLoansPeriodicAccrual(JsonCommand command);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/service/AccrualAccountingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/service/AccrualAccountingWritePlatformServiceImpl.java
new file mode 100755
index 0000000..f77a405
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/service/AccrualAccountingWritePlatformServiceImpl.java
@@ -0,0 +1,67 @@
+/**
+ * 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.accrual.service;
+
+import static org.apache.fineract.accounting.accrual.api.AccrualAccountingConstants.PERIODIC_ACCRUAL_ACCOUNTING_EXECUTION_ERROR_CODE;
+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.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.accounting.accrual.serialization.AccrualAccountingDataValidator;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AccrualAccountingWritePlatformServiceImpl implements AccrualAccountingWritePlatformService {
+
+    private final LoanAccrualPlatformService loanAccrualPlatformService;
+    private final AccrualAccountingDataValidator accountingDataValidator;
+
+    @Autowired
+    public AccrualAccountingWritePlatformServiceImpl(final LoanAccrualPlatformService loanAccrualPlatformService,
+            final AccrualAccountingDataValidator accountingDataValidator) {
+        this.loanAccrualPlatformService = loanAccrualPlatformService;
+        this.accountingDataValidator = accountingDataValidator;
+    }
+
+    @Override
+    public CommandProcessingResult executeLoansPeriodicAccrual(JsonCommand command) {
+        this.accountingDataValidator.validateLoanPeriodicAccrualData(command.json());
+        LocalDate tilldate = command.localDateValueOfParameterNamed(accrueTillParamName);
+        String errorlog = this.loanAccrualPlatformService.addPeriodicAccruals(tilldate);
+        if (errorlog.length() > 0) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                    .resource(PERIODIC_ACCRUAL_ACCOUNTING_RESOURCE_NAME);
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(PERIODIC_ACCRUAL_ACCOUNTING_EXECUTION_ERROR_CODE, errorlog);
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+        return CommandProcessingResult.empty();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/api/GLClosureJsonInputParams.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/api/GLClosureJsonInputParams.java
new file mode 100755
index 0000000..da87333
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/api/GLClosureJsonInputParams.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.accounting.closure.api;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/***
+ * Enum of all parameters passed in while creating/updating a loan product
+ ***/
+public enum GLClosureJsonInputParams {
+    ID("id"), OFFICE_ID("officeId"), CLOSING_DATE("closingDate"), COMMENTS("comments"), LOCALE("locale"), DATE_FORMAT("dateFormat");
+
+    private final String value;
+
+    private GLClosureJsonInputParams(final String value) {
+        this.value = value;
+    }
+
+    private static final Set<String> values = new HashSet<>();
+    static {
+        for (final GLClosureJsonInputParams type : GLClosureJsonInputParams.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/accounting/closure/api/GLClosuresApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResource.java
new file mode 100755
index 0000000..7fbf17f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResource.java
@@ -0,0 +1,151 @@
+/**
+ * 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.closure.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;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.accounting.closure.data.GLClosureData;
+import org.apache.fineract.accounting.closure.service.GLClosureReadPlatformService;
+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.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/glclosures")
+@Component
+@Scope("singleton")
+public class GLClosuresApiResource {
+
+    private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "officeId", "officeName",
+            "closingDate", "deleted", "createdDate", "lastUpdatedDate", "createdByUserId", "createdByUsername", "lastUpdatedByUserId",
+            "lastUpdatedByUsername"));
+
+    private final String resourceNameForPermission = "GLCLOSURE";
+
+    private final GLClosureReadPlatformService glClosureReadPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final DefaultToApiJsonSerializer<GLClosureData> apiJsonSerializerService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PlatformSecurityContext context;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public GLClosuresApiResource(final PlatformSecurityContext context, final GLClosureReadPlatformService glClosureReadPlatformService,
+            final DefaultToApiJsonSerializer<GLClosureData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final OfficeReadPlatformService officeReadPlatformService) {
+        this.context = context;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiJsonSerializerService = toApiJsonSerializer;
+        this.glClosureReadPlatformService = glClosureReadPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllClosures(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+        final List<GLClosureData> glClosureDatas = this.glClosureReadPlatformService.retrieveAllGLClosures(officeId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, glClosureDatas, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{glClosureId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retreiveClosure(@PathParam("glClosureId") final Long glClosureId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        final GLClosureData glClosureData = this.glClosureReadPlatformService.retrieveGLClosureById(glClosureId);
+        if (settings.isTemplate()) {
+            glClosureData.setAllowedOffices(this.officeReadPlatformService.retrieveAllOfficesForDropdown());
+        }
+
+        return this.apiJsonSerializerService.serialize(settings, glClosureData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createGLClosure(final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createGLClosure().withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @PUT
+    @Path("{glClosureId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateGLClosure(@PathParam("glClosureId") final Long glClosureId, final String jsonRequestBody) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateGLClosure(glClosureId).withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @DELETE
+    @Path("{glClosureId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteGLClosure(@PathParam("glClosureId") final Long glClosureId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteGLClosure(glClosureId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/command/GLClosureCommand.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/command/GLClosureCommand.java
new file mode 100755
index 0000000..dddd634
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/command/GLClosureCommand.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.accounting.closure.command;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.accounting.closure.api.GLClosureJsonInputParams;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable command for adding an accounting closure
+ */
+public class GLClosureCommand {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    private final Long officeId;
+    private final LocalDate closingDate;
+    private final String comments;
+
+    public GLClosureCommand(final Long id, final Long officeId, final LocalDate closingDate, final String comments) {
+        this.id = id;
+        this.officeId = officeId;
+        this.closingDate = closingDate;
+        this.comments = comments;
+    }
+
+    public void validateForCreate() {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("GLClosure");
+
+        baseDataValidator.reset().parameter(GLClosureJsonInputParams.CLOSING_DATE.getValue()).value(this.closingDate).notBlank();
+        baseDataValidator.reset().parameter(GLClosureJsonInputParams.OFFICE_ID.getValue()).value(this.officeId).notNull()
+                .integerGreaterThanZero();
+        baseDataValidator.reset().parameter(GLClosureJsonInputParams.COMMENTS.getValue()).value(this.comments).ignoreIfNull()
+                .notExceedingLengthOf(500);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForUpdate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("GLClosure");
+
+        baseDataValidator.reset().parameter(GLClosureJsonInputParams.COMMENTS.getValue()).value(this.comments).ignoreIfNull()
+                .notExceedingLengthOf(500);
+        baseDataValidator.reset().anyOfNotNull(this.comments);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/data/GLClosureData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/data/GLClosureData.java
new file mode 100755
index 0000000..73b89c3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/data/GLClosureData.java
@@ -0,0 +1,88 @@
+/**
+ * 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.closure.data;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable object representing a General Ledger Account
+ * 
+ * Note: no getter/setters required as google-gson will produce json from fields
+ * of object.
+ */
+public class GLClosureData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final Long officeId;
+    @SuppressWarnings("unused")
+    private final String officeName;
+    @SuppressWarnings("unused")
+    private final LocalDate closingDate;
+    @SuppressWarnings("unused")
+    private final boolean deleted;
+    @SuppressWarnings("unused")
+    private final LocalDate createdDate;
+    @SuppressWarnings("unused")
+    private final LocalDate lastUpdatedDate;
+    @SuppressWarnings("unused")
+    private final Long createdByUserId;
+    @SuppressWarnings("unused")
+    private final String createdByUsername;
+    @SuppressWarnings("unused")
+    private final Long lastUpdatedByUserId;
+    @SuppressWarnings("unused")
+    private final String lastUpdatedByUsername;
+    @SuppressWarnings("unused")
+    private final String comments;
+
+    private Collection<OfficeData> allowedOffices = new ArrayList<>();
+
+    public GLClosureData(final Long id, final Long officeId, final String officeName, final LocalDate closingDate, final boolean deleted,
+            final LocalDate createdDate, final LocalDate lastUpdatedDate, final Long createdByUserId, final String createdByUsername,
+            final Long lastUpdatedByUserId, final String lastUpdatedByUsername, final String comments) {
+        this.id = id;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.closingDate = closingDate;
+        this.deleted = deleted;
+        this.createdDate = createdDate;
+        this.lastUpdatedDate = lastUpdatedDate;
+        this.createdByUserId = createdByUserId;
+        this.createdByUsername = createdByUsername;
+        this.lastUpdatedByUserId = lastUpdatedByUserId;
+        this.lastUpdatedByUsername = lastUpdatedByUsername;
+        this.comments = comments;
+        this.allowedOffices = null;
+    }
+
+    public final Collection<OfficeData> getAllowedOffices() {
+        return this.allowedOffices;
+    }
+
+    public void setAllowedOffices(final Collection<OfficeData> allowedOffices) {
+        this.allowedOffices = allowedOffices;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/domain/GLClosure.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/domain/GLClosure.java
new file mode 100755
index 0000000..233b6da
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/domain/GLClosure.java
@@ -0,0 +1,105 @@
+/**
+ * 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.closure.domain;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+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 javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.closure.api.GLClosureJsonInputParams;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Entity
+@Table(name = "acc_gl_closure", uniqueConstraints = { @UniqueConstraint(columnNames = { "office_id", "closing_date" }, name = "office_id_closing_date") })
+public class GLClosure extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @Column(name = "is_deleted", nullable = false)
+    private boolean deleted = true;
+
+    @Column(name = "closing_date")
+    @Temporal(TemporalType.DATE)
+    private Date closingDate;
+
+    @Column(name = "comments", nullable = true, length = 500)
+    private String comments;
+
+    protected GLClosure() {
+        //
+    }
+
+    public GLClosure(final Office office, final Date closingDate, final String comments) {
+        this.office = office;
+        this.deleted = false;
+        this.closingDate = closingDate;
+        this.comments = StringUtils.defaultIfEmpty(comments, null);
+        if (this.comments != null) {
+            this.comments = this.comments.trim();
+        }
+    }
+
+    public static GLClosure fromJson(final Office office, final JsonCommand command) {
+        final Date closingDate = command.DateValueOfParameterNamed(GLClosureJsonInputParams.CLOSING_DATE.getValue());
+        final String comments = command.stringValueOfParameterNamed(GLClosureJsonInputParams.COMMENTS.getValue());
+        return new GLClosure(office, closingDate, comments);
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(5);
+        handlePropertyUpdate(command, actualChanges, GLClosureJsonInputParams.COMMENTS.getValue(), this.comments);
+        return actualChanges;
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            final String propertyToBeUpdated) {
+        if (command.isChangeInStringParameterNamed(paramName, propertyToBeUpdated)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            // now update actual property
+            if (paramName.equals(GLClosureJsonInputParams.COMMENTS.getValue())) {
+                this.comments = newValue;
+            }
+        }
+    }
+
+    public Date getClosingDate() {
+        return this.closingDate;
+    }
+
+    public Office getOffice() {
+        return this.office;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/domain/GLClosureRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/domain/GLClosureRepository.java
new file mode 100755
index 0000000..cc43547
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/domain/GLClosureRepository.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.accounting.closure.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 GLClosureRepository extends JpaRepository<GLClosure, Long>, JpaSpecificationExecutor<GLClosure> {
+
+    @Query("from GLClosure closure where closure.closingDate = (select max(closure1.closingDate) from GLClosure closure1 where closure1.office.id=:officeId)  and closure.office.id= :officeId")
+    GLClosure getLatestGLClosureByBranch(@Param("officeId") Long officeId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureDuplicateException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureDuplicateException.java
new file mode 100755
index 0000000..fc87121
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureDuplicateException.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.accounting.closure.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.joda.time.LocalDate;
+
+/**
+ * A {@link RuntimeException} thrown when a GL Closure for a given date and
+ * Office combination is already present
+ */
+public class GLClosureDuplicateException extends AbstractPlatformDomainRuleException {
+
+    public GLClosureDuplicateException(final Long officeId, final LocalDate closureDate) {
+        super("error.msg.glclosure.glcode.duplicate", "An accounting closure for branch with Id " + officeId
+                + " already exists for the date " + closureDate, officeId, closureDate);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureInvalidDeleteException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureInvalidDeleteException.java
new file mode 100755
index 0000000..233a99f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureInvalidDeleteException.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.accounting.closure.exception;
+
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when a GL Closure Delte command is invalid
+ */
+public class GLClosureInvalidDeleteException extends AbstractPlatformDomainRuleException {
+
+    public GLClosureInvalidDeleteException(final Long officeId, final String officeName, final Date latestclosureDate) {
+        super("error.msg.glclosure.invalid.delete", "The latest closure for office with Id " + officeId + " and name " + officeName
+                + " is on " + latestclosureDate.toString() + ", please delete this closure first", latestclosureDate);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureInvalidException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureInvalidException.java
new file mode 100755
index 0000000..130ea24
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureInvalidException.java
@@ -0,0 +1,52 @@
+/**
+ * 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.closure.exception;
+
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when a GL Closure is Invalid
+ */
+public class GLClosureInvalidException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons for invalid Accounting Closure **/
+    public static enum GL_CLOSURE_INVALID_REASON {
+        FUTURE_DATE, ACCOUNTING_CLOSED;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
+                return "Accounting closures cannot be made for a future date";
+            } else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) { return "Accounting Closure for this branch has already been defined for a greater date"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
+                return "error.msg.glclosure.invalid.future.date";
+            } else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) { return "error.msg.glclosure.invalid.accounting.closed"; }
+            return name().toString();
+        }
+    }
+
+    public GLClosureInvalidException(final GL_CLOSURE_INVALID_REASON reason, final Date date) {
+        super(reason.errorCode(), reason.errorMessage(), date);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureNotFoundException.java
new file mode 100755
index 0000000..66e4c77
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/exception/GLClosureNotFoundException.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.accounting.closure.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when GL account closure resources are not
+ * found.
+ */
+public class GLClosureNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public GLClosureNotFoundException(final Long id) {
+        super("error.msg.glclosure.id.invalid", "Accounting Closure with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/handler/CreateGLClosureCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/handler/CreateGLClosureCommandHandler.java
new file mode 100755
index 0000000..d9c049e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/handler/CreateGLClosureCommandHandler.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.accounting.closure.handler;
+
+import org.apache.fineract.accounting.closure.service.GLClosureWritePlatformService;
+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 = "GLCLOSURE", action = "CREATE")
+public class CreateGLClosureCommandHandler implements NewCommandSourceHandler {
+
+    private final GLClosureWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateGLClosureCommandHandler(final GLClosureWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createGLClosure(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/handler/DeleteGLClosureCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/handler/DeleteGLClosureCommandHandler.java
new file mode 100755
index 0000000..4070fab
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/handler/DeleteGLClosureCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.closure.handler;
+
+import org.apache.fineract.accounting.closure.service.GLClosureWritePlatformService;
+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 = "GLCLOSURE", action = "DELETE")
+public class DeleteGLClosureCommandHandler implements NewCommandSourceHandler {
+
+    private final GLClosureWritePlatformService closureWritePlatformService;
+
+    @Autowired
+    public DeleteGLClosureCommandHandler(final GLClosureWritePlatformService guarantorWritePlatformService) {
+        this.closureWritePlatformService = guarantorWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.closureWritePlatformService.deleteGLClosure(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/handler/UpdateGLClosureCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/handler/UpdateGLClosureCommandHandler.java
new file mode 100755
index 0000000..fe2ff96
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/handler/UpdateGLClosureCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.closure.handler;
+
+import org.apache.fineract.accounting.closure.service.GLClosureWritePlatformService;
+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 = "GLCLOSURE", action = "UPDATE")
+public class UpdateGLClosureCommandHandler implements NewCommandSourceHandler {
+
+    private final GLClosureWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateGLClosureCommandHandler(final GLClosureWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.updateGLClosure(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/serialization/GLClosureCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/serialization/GLClosureCommandFromApiJsonDeserializer.java
new file mode 100755
index 0000000..e9d71e3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/serialization/GLClosureCommandFromApiJsonDeserializer.java
@@ -0,0 +1,72 @@
+/**
+ * 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.closure.serialization;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.closure.api.GLClosureJsonInputParams;
+import org.apache.fineract.accounting.closure.command.GLClosureCommand;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.guarantor.command.GuarantorCommand;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link GuarantorCommand}'s.
+ */
+@Component
+public final class GLClosureCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<GLClosureCommand> {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public GLClosureCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonfromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonfromApiJsonHelper;
+    }
+
+    @Override
+    public GLClosureCommand commandFromApiJson(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        final Set<String> supportedParameters = GLClosureJsonInputParams.getAllValues();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long id = this.fromApiJsonHelper.extractLongNamed(GLClosureJsonInputParams.ID.getValue(), element);
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(GLClosureJsonInputParams.OFFICE_ID.getValue(), element);
+        final String comments = this.fromApiJsonHelper.extractStringNamed(GLClosureJsonInputParams.COMMENTS.getValue(), element);
+        final LocalDate closingDate = this.fromApiJsonHelper.extractLocalDateNamed(GLClosureJsonInputParams.CLOSING_DATE.getValue(),
+                element);
+
+        return new GLClosureCommand(id, officeId, closingDate, comments);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureReadPlatformService.java
new file mode 100755
index 0000000..2c329ef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureReadPlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.closure.service;
+
+import java.util.List;
+
+import org.apache.fineract.accounting.closure.data.GLClosureData;
+
+public interface GLClosureReadPlatformService {
+
+    List<GLClosureData> retrieveAllGLClosures(Long OfficeId);
+
+    GLClosureData retrieveGLClosureById(long glClosureId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureReadPlatformServiceImpl.java
new file mode 100755
index 0000000..91c4e6e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureReadPlatformServiceImpl.java
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.closure.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.fineract.accounting.closure.data.GLClosureData;
+import org.apache.fineract.accounting.closure.exception.GLClosureNotFoundException;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GLClosureReadPlatformServiceImpl implements GLClosureReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public GLClosureReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class GLClosureMapper implements RowMapper<GLClosureData> {
+
+        public String schema() {
+            return " glClosure.id as id, glClosure.office_id as officeId,office.name as officeName ,glClosure.closing_date as closingDate,"
+                    + " glClosure.is_deleted as isDeleted, creatingUser.id as creatingUserId,creatingUser.username as creatingUserName,"
+                    + " updatingUser.id as updatingUserId,updatingUser.username as updatingUserName, glClosure.created_date as createdDate,"
+                    + " glClosure.lastmodified_date as updatedDate, glClosure.comments as comments "
+                    + " from acc_gl_closure as glClosure, m_appuser as creatingUser, m_appuser as updatingUser,m_office as office"
+                    + " where glClosure.createdby_id=creatingUser.id and "
+                    + " glClosure.lastmodifiedby_id=updatingUser.id and glClosure.office_id=office.id";
+        }
+
+        @Override
+        public GLClosureData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long officeId = rs.getLong("officeId");
+            final String officeName = rs.getString("officeName");
+            final LocalDate closingDate = JdbcSupport.getLocalDate(rs, "closingDate");
+            final Boolean deleted = rs.getBoolean("isDeleted");
+            final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate");
+            final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate");
+            final Long creatingByUserId = rs.getLong("creatingUserId");
+            final String createdByUserName = rs.getString("creatingUserName");
+            final Long lastUpdatedByUserId = rs.getLong("updatingUserId");
+            final String lastUpdatedByUserName = rs.getString("updatingUserName");
+            final String comments = rs.getString("comments");
+
+            return new GLClosureData(id, officeId, officeName, closingDate, deleted, createdDate, lastUpdatedDate, creatingByUserId,
+                    createdByUserName, lastUpdatedByUserId, lastUpdatedByUserName, comments);
+        }
+    }
+
+    @Override
+    public List<GLClosureData> retrieveAllGLClosures(final Long officeId) {
+        final GLClosureMapper rm = new GLClosureMapper();
+
+        String sql = "select " + rm.schema() + " and glClosure.is_deleted = 0";
+        final Object[] objectArray = new Object[1];
+        int arrayPos = 0;
+        if (officeId != null && officeId != 0) {
+            sql += " and glClosure.office_id = ?";
+            objectArray[arrayPos] = officeId;
+            arrayPos = arrayPos + 1;
+        }
+
+        sql = sql + " order by glClosure.closing_date desc";
+
+        final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
+        return this.jdbcTemplate.query(sql, rm, finalObjectArray);
+    }
+
+    @Override
+    public GLClosureData retrieveGLClosureById(final long glClosureId) {
+        try {
+
+            final GLClosureMapper rm = new GLClosureMapper();
+            final String sql = "select " + rm.schema() + " and glClosure.id = ?";
+
+            final GLClosureData glAccountData = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { glClosureId });
+
+            return glAccountData;
+        } catch (final EmptyResultDataAccessException e) {
+            throw new GLClosureNotFoundException(glClosureId);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformService.java
new file mode 100755
index 0000000..f5c4823
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformService.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.accounting.closure.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface GLClosureWritePlatformService {
+
+    CommandProcessingResult createGLClosure(JsonCommand command);
+
+    CommandProcessingResult updateGLClosure(Long glClosureId, JsonCommand command);
+
+    CommandProcessingResult deleteGLClosure(Long glClosureId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..4c305df
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,156 @@
+/**
+ * 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.closure.service;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.fineract.accounting.closure.api.GLClosureJsonInputParams;
+import org.apache.fineract.accounting.closure.command.GLClosureCommand;
+import org.apache.fineract.accounting.closure.domain.GLClosure;
+import org.apache.fineract.accounting.closure.domain.GLClosureRepository;
+import org.apache.fineract.accounting.closure.exception.GLClosureDuplicateException;
+import org.apache.fineract.accounting.closure.exception.GLClosureInvalidDeleteException;
+import org.apache.fineract.accounting.closure.exception.GLClosureInvalidException;
+import org.apache.fineract.accounting.closure.exception.GLClosureNotFoundException;
+import org.apache.fineract.accounting.closure.exception.GLClosureInvalidException.GL_CLOSURE_INVALID_REASON;
+import org.apache.fineract.accounting.closure.serialization.GLClosureCommandFromApiJsonDeserializer;
+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.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.joda.time.LocalDate;
+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 GLClosureWritePlatformServiceJpaRepositoryImpl implements GLClosureWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(GLClosureWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final GLClosureRepository glClosureRepository;
+    private final OfficeRepository officeRepository;
+    private final GLClosureCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+
+    @Autowired
+    public GLClosureWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository,
+            final OfficeRepository officeRepository, final GLClosureCommandFromApiJsonDeserializer fromApiJsonDeserializer) {
+        this.glClosureRepository = glClosureRepository;
+        this.officeRepository = officeRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createGLClosure(final JsonCommand command) {
+        try {
+            final GLClosureCommand closureCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
+            closureCommand.validateForCreate();
+
+            // check office is valid
+            final Long officeId = command.longValueOfParameterNamed(GLClosureJsonInputParams.OFFICE_ID.getValue());
+            final Office office = this.officeRepository.findOne(officeId);
+            if (office == null) { throw new OfficeNotFoundException(officeId); }
+
+            // TODO: Get Tenant specific date
+            // ensure closure date is not in the future
+            final Date todaysDate = new Date();
+            final Date closureDate = command.DateValueOfParameterNamed(GLClosureJsonInputParams.CLOSING_DATE.getValue());
+            if (closureDate.after(todaysDate)) { throw new GLClosureInvalidException(GL_CLOSURE_INVALID_REASON.FUTURE_DATE, closureDate); }
+            // shouldn't be before an existing accounting closure
+            final GLClosure latestGLClosure = this.glClosureRepository.getLatestGLClosureByBranch(officeId);
+            if (latestGLClosure != null) {
+                if (latestGLClosure.getClosingDate().after(closureDate)) { throw new GLClosureInvalidException(
+                        GL_CLOSURE_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate()); }
+            }
+            final GLClosure glClosure = GLClosure.fromJson(office, command);
+
+            this.glClosureRepository.saveAndFlush(glClosure);
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
+                    .withEntityId(glClosure.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleGLClosureIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateGLClosure(final Long glClosureId, final JsonCommand command) {
+        final GLClosureCommand closureCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
+        closureCommand.validateForUpdate();
+
+        // is the glClosure valid
+        final GLClosure glClosure = this.glClosureRepository.findOne(glClosureId);
+        if (glClosure == null) { throw new GLClosureNotFoundException(glClosureId); }
+
+        final Map<String, Object> changesOnly = glClosure.update(command);
+
+        if (!changesOnly.isEmpty()) {
+            this.glClosureRepository.saveAndFlush(glClosure);
+        }
+
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(glClosure.getOffice().getId())
+                .withEntityId(glClosure.getId()).with(changesOnly).build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteGLClosure(final Long glClosureId) {
+        final GLClosure glClosure = this.glClosureRepository.findOne(glClosureId);
+
+        if (glClosure == null) { throw new GLClosureNotFoundException(glClosureId); }
+
+        /**
+         * check if any closures are present for this branch at a later date
+         * than this closure date
+         **/
+        final Date closureDate = glClosure.getClosingDate();
+        final GLClosure latestGLClosure = this.glClosureRepository.getLatestGLClosureByBranch(glClosure.getOffice().getId());
+        if (latestGLClosure.getClosingDate().after(closureDate)) { throw new GLClosureInvalidDeleteException(latestGLClosure.getOffice()
+                .getId(), latestGLClosure.getOffice().getName(), latestGLClosure.getClosingDate()); }
+
+        this.glClosureRepository.delete(glClosure);
+
+        return new CommandProcessingResultBuilder().withOfficeId(glClosure.getOffice().getId()).withEntityId(glClosure.getId()).build();
+    }
+
+    /**
+     * @param command
+     * @param dve
+     */
+    private void handleGLClosureIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("office_id_closing_date")) { throw new GLClosureDuplicateException(
+                command.longValueOfParameterNamed(GLClosureJsonInputParams.OFFICE_ID.getValue()), new LocalDate(
+                        command.DateValueOfParameterNamed(GLClosureJsonInputParams.CLOSING_DATE.getValue()))); }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.glClosure.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource GL Closure: " + realCause.getMessage());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
new file mode 100755
index 0000000..03ca3c6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
@@ -0,0 +1,314 @@
+/**
+ * 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.common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.financialactivityaccount.data.FinancialActivityData;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+
+public class AccountingConstants {
+
+    /*** Accounting placeholders for cash based accounting for loan products ***/
+    public static enum CASH_ACCOUNTS_FOR_LOAN {
+        FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6), TRANSFERS_SUSPENSE(
+                10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12);
+
+        private final Integer value;
+
+        private CASH_ACCOUNTS_FOR_LOAN(final Integer value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public Integer getValue() {
+            return this.value;
+        }
+
+        private static final Map<Integer, CASH_ACCOUNTS_FOR_LOAN> intToEnumMap = new HashMap<>();
+        static {
+            for (final CASH_ACCOUNTS_FOR_LOAN type : CASH_ACCOUNTS_FOR_LOAN.values()) {
+                intToEnumMap.put(type.value, type);
+            }
+        }
+
+        public static CASH_ACCOUNTS_FOR_LOAN fromInt(final int i) {
+            final CASH_ACCOUNTS_FOR_LOAN type = intToEnumMap.get(Integer.valueOf(i));
+            return type;
+        }
+    }
+
+    /*** Accounting placeholders for accrual based accounting for loan products ***/
+    public static enum ACCRUAL_ACCOUNTS_FOR_LOAN {
+        FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6), INTEREST_RECEIVABLE(
+                7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12);
+
+        private final Integer value;
+
+        private ACCRUAL_ACCOUNTS_FOR_LOAN(final Integer value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public Integer getValue() {
+            return this.value;
+        }
+
+        private static final Map<Integer, ACCRUAL_ACCOUNTS_FOR_LOAN> intToEnumMap = new HashMap<>();
+        static {
+            for (final ACCRUAL_ACCOUNTS_FOR_LOAN type : ACCRUAL_ACCOUNTS_FOR_LOAN.values()) {
+                intToEnumMap.put(type.value, type);
+            }
+        }
+
+        public static ACCRUAL_ACCOUNTS_FOR_LOAN fromInt(final int i) {
+            final ACCRUAL_ACCOUNTS_FOR_LOAN type = intToEnumMap.get(Integer.valueOf(i));
+            return type;
+        }
+
+    }
+
+    /***
+     * Enum of all accounting related input parameter names used while
+     * creating/updating a loan product
+     ***/
+    public static enum LOAN_PRODUCT_ACCOUNTING_PARAMS {
+        FUND_SOURCE("fundSourceAccountId"), LOAN_PORTFOLIO("loanPortfolioAccountId"), INTEREST_ON_LOANS("interestOnLoanAccountId"), INCOME_FROM_FEES(
+                "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), OVERPAYMENT(
+                "overpaymentLiabilityAccountId"), INTEREST_RECEIVABLE("receivableInterestAccountId"), FEES_RECEIVABLE(
+                "receivableFeeAccountId"), PENALTIES_RECEIVABLE("receivablePenaltyAccountId"), TRANSFERS_SUSPENSE(
+                "transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE(
+                "paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING(
+                "penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId"), INCOME_FROM_RECOVERY(
+                "incomeFromRecoveryAccountId");
+
+        private final String value;
+
+        private LOAN_PRODUCT_ACCOUNTING_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+    public static enum LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS {
+        FUND_SOURCE("fundSourceAccount"), LOAN_PORTFOLIO("loanPortfolioAccount"), INTEREST_ON_LOANS("interestOnLoanAccount"), INCOME_FROM_FEES(
+                "incomeFromFeeAccount"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccount"), LOSSES_WRITTEN_OFF("writeOffAccount"), OVERPAYMENT(
+                "overpaymentLiabilityAccount"), INTEREST_RECEIVABLE("receivableInterestAccount"), FEES_RECEIVABLE("receivableFeeAccount"), PENALTIES_RECEIVABLE(
+                "receivablePenaltyAccount"), TRANSFERS_SUSPENSE("transfersInSuspenseAccount"), INCOME_ACCOUNT_ID("incomeAccount"), INCOME_FROM_RECOVERY(
+                "incomeFromRecoveryAccount"), LIABILITY_TRANSFER_SUSPENSE("liabilityTransferInSuspenseAccount");
+
+        private final String value;
+
+        private LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+    /*** Accounting placeholders for cash based accounting for savings products ***/
+    public static enum CASH_ACCOUNTS_FOR_SAVINGS {
+        SAVINGS_REFERENCE(1), SAVINGS_CONTROL(2), INTEREST_ON_SAVINGS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), TRANSFERS_SUSPENSE(
+                10), OVERDRAFT_PORTFOLIO_CONTROL(11), INCOME_FROM_INTEREST(12), LOSSES_WRITTEN_OFF(13);
+
+        private final Integer value;
+
+        private CASH_ACCOUNTS_FOR_SAVINGS(final Integer value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public Integer getValue() {
+            return this.value;
+        }
+
+        private static final Map<Integer, CASH_ACCOUNTS_FOR_SAVINGS> intToEnumMap = new HashMap<>();
+        static {
+            for (final CASH_ACCOUNTS_FOR_SAVINGS type : CASH_ACCOUNTS_FOR_SAVINGS.values()) {
+                intToEnumMap.put(type.value, type);
+            }
+        }
+
+        public static CASH_ACCOUNTS_FOR_SAVINGS fromInt(final int i) {
+            final CASH_ACCOUNTS_FOR_SAVINGS type = intToEnumMap.get(Integer.valueOf(i));
+            return type;
+        }
+    }
+
+    /***
+     * Enum of all accounting related input parameter names used while
+     * creating/updating a savings product
+     ***/
+    public static enum SAVINGS_PRODUCT_ACCOUNTING_PARAMS {
+        SAVINGS_REFERENCE("savingsReferenceAccountId"), SAVINGS_CONTROL("savingsControlAccountId"), INCOME_FROM_FEES(
+                "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), INTEREST_ON_SAVINGS(
+                "interestOnSavingsAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE(
+                "paymentTypeId"), FUND_SOURCE("fundSourceAccountId"), TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), FEE_INCOME_ACCOUNT_MAPPING(
+                "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID(
+                "incomeAccountId"), OVERDRAFT_PORTFOLIO_CONTROL("overdraftPortfolioControlId"), INCOME_FROM_INTEREST("incomeFromInterestId"), LOSSES_WRITTEN_OFF(
+                "writeOffAccountId");
+
+        private final String value;
+
+        private SAVINGS_PRODUCT_ACCOUNTING_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+    public static enum SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS {
+        SAVINGS_REFERENCE("savingsReferenceAccount"), SAVINGS_CONTROL("savingsControlAccount"), INCOME_FROM_FEES("incomeFromFeeAccount"), INCOME_FROM_PENALTIES(
+                "incomeFromPenaltyAccount"), INTEREST_ON_SAVINGS("interestOnSavingsAccount"), PAYMENT_TYPE("paymentType"), FUND_SOURCE(
+                "fundSourceAccount"), TRANSFERS_SUSPENSE("transfersInSuspenseAccount"), PENALTY_INCOME_ACCOUNT_MAPPING(
+                "penaltyToIncomeAccountMappings"), CHARGE_ID("charge"), INCOME_ACCOUNT_ID("incomeAccount"), OVERDRAFT_PORTFOLIO_CONTROL(
+                "overdraftPortfolioControl"), INCOME_FROM_INTEREST("incomeFromInterest"), LOSSES_WRITTEN_OFF("writeOffAccount");
+
+        private final String value;
+
+        private SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+    public static enum FINANCIAL_ACTIVITY {
+        ASSET_TRANSFER(100, "assetTransfer", GLAccountType.ASSET), LIABILITY_TRANSFER(200, "liabilityTransfer", GLAccountType.LIABILITY),
+        CASH_AT_MAINVAULT (101, "cashAtMainVault", GLAccountType.ASSET),
+        CASH_AT_TELLER (102, "cashAtTeller", GLAccountType.ASSET),OPENING_BALANCES_TRANSFER_CONTRA (300,"openingBalancesTransferContra",GLAccountType.EQUITY),
+        ASSET_FUND_SOURCE(103, "fundSource", GLAccountType.ASSET);
+
+        private final Integer value;
+        private final String code;
+        private final GLAccountType mappedGLAccountType;
+        private static List<FinancialActivityData> financialActivities;
+
+        private FINANCIAL_ACTIVITY(final Integer value, final String code, final GLAccountType mappedGLAccountType) {
+            this.value = value;
+            this.code = code;
+            this.mappedGLAccountType = mappedGLAccountType;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public Integer getValue() {
+            return this.value;
+        }
+
+        public String getCode() {
+            return this.code;
+        }
+
+        public GLAccountType getMappedGLAccountType() {
+            return mappedGLAccountType;
+        }
+
+        public String getValueAsString() {
+            return this.value.toString();
+        }
+
+        private static final Map<Integer, FINANCIAL_ACTIVITY> intToEnumMap = new HashMap<>();
+        static {
+            for (final FINANCIAL_ACTIVITY type : FINANCIAL_ACTIVITY.values()) {
+                intToEnumMap.put(type.value, type);
+            }
+        }
+
+        public static FINANCIAL_ACTIVITY fromInt(final int financialActivityId) {
+            final FINANCIAL_ACTIVITY type = intToEnumMap.get(Integer.valueOf(financialActivityId));
+            return type;
+        }
+
+        public static FinancialActivityData toFinancialActivityData(final int financialActivityId) {
+            final FINANCIAL_ACTIVITY type = fromInt(financialActivityId);
+            return convertToFinancialActivityData(type);
+        }
+
+        public static List<FinancialActivityData> getAllFinancialActivities() {
+            if (financialActivities == null) {
+                financialActivities = new ArrayList<>();
+                for (final FINANCIAL_ACTIVITY type : FINANCIAL_ACTIVITY.values()) {
+                    FinancialActivityData financialActivityData = convertToFinancialActivityData(type);
+                    financialActivities.add(financialActivityData);
+                }
+            }
+            return financialActivities;
+        }
+
+        private static FinancialActivityData convertToFinancialActivityData(final FINANCIAL_ACTIVITY type) {
+            FinancialActivityData financialActivityData = new FinancialActivityData(type.value, type.code, type.getMappedGLAccountType());
+            return financialActivityData;
+        }
+    }
+
+    public static final String ASSESTS_TAG_OPTION_CODE_NAME = "AssetAccountTags";
+    public static final String LIABILITIES_TAG_OPTION_CODE_NAME = "LiabilityAccountTags";
+    public static final String EQUITY_TAG_OPTION_CODE_NAME = "EquityAccountTags";
+    public static final String INCOME_TAG_OPTION_CODE_NAME = "IncomeAccountTags";
+    public static final String EXPENSES_TAG_OPTION_CODE_NAME = "ExpenseAccountTags";
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformService.java
new file mode 100755
index 0000000..a8ce5d1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformService.java
@@ -0,0 +1,45 @@
+/**
+ * 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.common;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public interface AccountingDropdownReadPlatformService {
+
+    public List<EnumOptionData> retrieveGLAccountTypeOptions();
+
+    public List<EnumOptionData> retrieveGLAccountUsageOptions();
+
+    public List<EnumOptionData> retrieveJournalEntryTypeOptions();
+
+    public List<EnumOptionData> retrieveAccountingRuleTypeOptions();
+
+    public Map<String, List<GLAccountData>> retrieveAccountMappingOptionsForLoanProducts();
+
+    public Map<String, List<GLAccountData>> retrieveAccountMappingOptionsForSavingsProducts();
+
+    public Map<String, List<GLAccountData>> retrieveAccountMappingOptionsForCharges();
+
+    public Map<String, List<GLAccountData>> retrieveAccountMappingOptions();
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformServiceImpl.java
new file mode 100755
index 0000000..87c6b28
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformServiceImpl.java
@@ -0,0 +1,150 @@
+/**
+ * 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.common;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
+import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AccountingDropdownReadPlatformServiceImpl implements AccountingDropdownReadPlatformService {
+
+    private final GLAccountReadPlatformService accountReadPlatformService;
+
+    @Autowired
+    public AccountingDropdownReadPlatformServiceImpl(final GLAccountReadPlatformService accountReadPlatformService) {
+        this.accountReadPlatformService = accountReadPlatformService;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveGLAccountTypeOptions() {
+        return AccountingEnumerations.gLAccountType(GLAccountType.values());
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveGLAccountUsageOptions() {
+        return AccountingEnumerations.gLAccountUsage(GLAccountUsage.values());
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveJournalEntryTypeOptions() {
+        return AccountingEnumerations.journalEntryTypes(JournalEntryType.values());
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveAccountingRuleTypeOptions() {
+        return AccountingEnumerations.accountingRuleTypes(AccountingRuleType.values());
+    }
+
+    @Override
+    public Map<String, List<GLAccountData>> retrieveAccountMappingOptionsForLoanProducts() {
+        return retrieveAccountMappingOptions();
+    }
+
+    @Override
+    public Map<String, List<GLAccountData>> retrieveAccountMappingOptionsForSavingsProducts() {
+        return retrieveAccountMappingOptions();
+    }
+
+    @Override
+    public Map<String, List<GLAccountData>> retrieveAccountMappingOptionsForCharges() {
+        // only get income and liability accounts
+        boolean includeIncomeAccounts = true;
+        boolean includeLiabilityAccounts = true;
+
+        boolean includeAssetAccounts = false;
+        boolean includeExpenseAccounts = false;
+        boolean includeEquityAccounts = false;
+
+        return retrieveAccountMappingOptions(includeAssetAccounts, includeIncomeAccounts, includeExpenseAccounts, includeLiabilityAccounts,
+                includeEquityAccounts);
+    }
+
+    @Override
+    public Map<String, List<GLAccountData>> retrieveAccountMappingOptions() {
+        boolean includeAssetAccounts = true;
+        boolean includeIncomeAccounts = true;
+        boolean includeExpenseAccounts = true;
+        boolean includeLiabilityAccounts = true;
+        boolean includeEquityAccounts = true;
+        return retrieveAccountMappingOptions(includeAssetAccounts, includeIncomeAccounts, includeExpenseAccounts, includeLiabilityAccounts,
+                includeEquityAccounts);
+    }
+
+    private Map<String, List<GLAccountData>> retrieveAccountMappingOptions(boolean includeAssetAccounts, boolean includeIncomeAccounts,
+            boolean includeExpenseAccounts, boolean includeLiabilityAccounts, boolean includeEquityAccounts) {
+        final Map<String, List<GLAccountData>> accountOptions = new HashMap<>();
+
+        if (includeAssetAccounts) {
+            List<GLAccountData> assetAccountOptions = this.accountReadPlatformService
+                    .retrieveAllEnabledDetailGLAccounts(GLAccountType.ASSET);
+            if (assetAccountOptions.isEmpty()) {
+                assetAccountOptions = null;
+            }
+            accountOptions.put("assetAccountOptions", assetAccountOptions);
+        }
+
+        if (includeIncomeAccounts) {
+            List<GLAccountData> incomeAccountOptions = this.accountReadPlatformService
+                    .retrieveAllEnabledDetailGLAccounts(GLAccountType.INCOME);
+            if (incomeAccountOptions.isEmpty()) {
+                incomeAccountOptions = null;
+            }
+            accountOptions.put("incomeAccountOptions", incomeAccountOptions);
+        }
+
+        if (includeExpenseAccounts) {
+            List<GLAccountData> expenseAccountOptions = this.accountReadPlatformService
+                    .retrieveAllEnabledDetailGLAccounts(GLAccountType.EXPENSE);
+            if (expenseAccountOptions.isEmpty()) {
+                expenseAccountOptions = null;
+            }
+            accountOptions.put("expenseAccountOptions", expenseAccountOptions);
+        }
+
+        if (includeLiabilityAccounts) {
+            List<GLAccountData> liabilityAccountOptions = this.accountReadPlatformService
+                    .retrieveAllEnabledDetailGLAccounts(GLAccountType.LIABILITY);
+            if (liabilityAccountOptions.isEmpty()) {
+                liabilityAccountOptions = null;
+            }
+            accountOptions.put("liabilityAccountOptions", liabilityAccountOptions);
+        }
+
+        if (includeEquityAccounts) {
+            List<GLAccountData> equityAccountOptions = this.accountReadPlatformService
+                    .retrieveAllEnabledDetailGLAccounts(GLAccountType.EQUITY);
+            if (equityAccountOptions.isEmpty()) {
+                equityAccountOptions = null;
+            }
+            accountOptions.put("equityAccountOptions", equityAccountOptions);
+        }
+        return accountOptions;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingEnumerations.java
new file mode 100755
index 0000000..562a88b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingEnumerations.java
@@ -0,0 +1,113 @@
+/**
+ * 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.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class AccountingEnumerations {
+
+    public static EnumOptionData gLAccountType(final int id) {
+        return gLAccountType(GLAccountType.fromInt(id));
+    }
+
+    public static EnumOptionData gLAccountType(final GLAccountType accountType) {
+        final EnumOptionData optionData = new EnumOptionData(accountType.getValue().longValue(), accountType.getCode(),
+                accountType.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> gLAccountType(final GLAccountType[] accountTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final GLAccountType accountType : accountTypes) {
+            optionDatas.add(gLAccountType(accountType));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData gLAccountUsage(final int id) {
+        return gLAccountUsage(GLAccountUsage.fromInt(id));
+    }
+
+    public static EnumOptionData gLAccountUsage(final GLAccountUsage accountUsage) {
+        final EnumOptionData optionData = new EnumOptionData(accountUsage.getValue().longValue(), accountUsage.getCode(),
+                accountUsage.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> gLAccountUsage(final GLAccountUsage[] accountUsages) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final GLAccountUsage accountUsage : accountUsages) {
+            optionDatas.add(gLAccountUsage(accountUsage));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData journalEntryType(final int id) {
+        return journalEntryType(JournalEntryType.fromInt(id));
+    }
+
+    public static EnumOptionData journalEntryType(final JournalEntryType journalEntryType) {
+        final EnumOptionData optionData = new EnumOptionData(journalEntryType.getValue().longValue(), journalEntryType.getCode(),
+                journalEntryType.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> journalEntryTypes(final JournalEntryType[] journalEntryTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final JournalEntryType journalEntryType : journalEntryTypes) {
+            optionDatas.add(journalEntryType(journalEntryType));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData portfolioProductType(final int id) {
+        return portfolioProductType(PortfolioProductType.fromInt(id));
+    }
+
+    public static EnumOptionData portfolioProductType(final PortfolioProductType portfolioProductType) {
+        final EnumOptionData optionData = new EnumOptionData(portfolioProductType.getValue().longValue(), portfolioProductType.getCode(),
+                portfolioProductType.toString());
+        return optionData;
+    }
+
+    public static EnumOptionData accountingRuleType(final int id) {
+        return accountingRuleType(AccountingRuleType.fromInt(id));
+    }
+
+    public static EnumOptionData accountingRuleType(final AccountingRuleType type) {
+        final EnumOptionData optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), type.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> accountingRuleTypes(final AccountingRuleType[] accountingRuleTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final AccountingRuleType accountingRuleType : accountingRuleTypes) {
+            optionDatas.add(accountingRuleType(accountingRuleType));
+        }
+        return optionDatas;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingRuleType.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingRuleType.java
new file mode 100755
index 0000000..a77f7ff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingRuleType.java
@@ -0,0 +1,63 @@
+/**
+ * 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.common;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum AccountingRuleType {
+
+    NONE(1, "accountingRuleType.none"), //
+    CASH_BASED(2, "accountingRuleType.cash"), //
+    ACCRUAL_PERIODIC(3, "accountingRuleType.accrual.periodic"), //
+    ACCRUAL_UPFRONT(4, "accountingRuleType.accrual.upfront"); //
+
+    private final Integer value;
+    private final String code;
+
+    private static final Map<Integer, AccountingRuleType> intToEnumMap = new HashMap<>();
+    static {
+        for (final AccountingRuleType type : AccountingRuleType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static AccountingRuleType fromInt(final Integer ruleTypeValue) {
+        final AccountingRuleType type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private AccountingRuleType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString().replaceAll("_", " ");
+    }
+
+    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/accounting/financialactivityaccount/api/FinancialActivityAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsApiResource.java
new file mode 100644
index 0000000..19a94fa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsApiResource.java
@@ -0,0 +1,160 @@
+/**
+ * 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.financialactivityaccount.api;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.accounting.financialactivityaccount.data.FinancialActivityAccountData;
+import org.apache.fineract.accounting.financialactivityaccount.service.FinancialActivityAccountReadPlatformService;
+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("/financialactivityaccounts")
+@Component
+@Scope("singleton")
+public class FinancialActivityAccountsApiResource {
+
+    private final FinancialActivityAccountReadPlatformService financialActivityAccountReadPlatformService;
+    private final DefaultToApiJsonSerializer<FinancialActivityAccountData> apiJsonSerializerService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PlatformSecurityContext context;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public FinancialActivityAccountsApiResource(final PlatformSecurityContext context,
+            final FinancialActivityAccountReadPlatformService officeToGLAccountMappingReadPlatformService,
+            final DefaultToApiJsonSerializer<FinancialActivityAccountData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiJsonSerializerService = toApiJsonSerializer;
+        this.financialActivityAccountReadPlatformService = officeToGLAccountMappingReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(FinancialActivityAccountsConstants.resourceNameForPermission);
+
+        FinancialActivityAccountData financialActivityAccountData = this.financialActivityAccountReadPlatformService
+                .getFinancialActivityAccountTemplate();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, financialActivityAccountData,
+                FinancialActivityAccountsConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(FinancialActivityAccountsConstants.resourceNameForPermission);
+        final List<FinancialActivityAccountData> financialActivityAccounts = this.financialActivityAccountReadPlatformService.retrieveAll();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, financialActivityAccounts,
+                FinancialActivityAccountsConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{mappingId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retreive(@PathParam("mappingId") final Long mappingId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(FinancialActivityAccountsConstants.resourceNameForPermission);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        FinancialActivityAccountData financialActivityAccountData = this.financialActivityAccountReadPlatformService.retrieve(mappingId);
+        if (settings.isTemplate()) {
+            financialActivityAccountData = this.financialActivityAccountReadPlatformService
+                    .addTemplateDetails(financialActivityAccountData);
+        }
+
+        return this.apiJsonSerializerService.serialize(settings, financialActivityAccountData,
+                FinancialActivityAccountsConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createGLAccount(final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createOfficeToGLAccountMapping().withJson(jsonRequestBody)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @PUT
+    @Path("{mappingId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateGLAccount(@PathParam("mappingId") final Long mappingId, final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateOfficeToGLAccountMapping(mappingId)
+                .withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @DELETE
+    @Path("{mappingId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteGLAccount(@PathParam("mappingId") final Long mappingId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteOfficeToGLAccountMapping(mappingId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsConstants.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsConstants.java
new file mode 100644
index 0000000..5f59759
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsConstants.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.financialactivityaccount.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class FinancialActivityAccountsConstants {
+
+    public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "financialActivityData",
+            "glAccountData", "glAccountOptions", "financialActivityOptions"));
+
+    public static final String resourceNameForPermission = "FINANCIALACTIVITYACCOUNT";
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsJsonInputParams.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsJsonInputParams.java
new file mode 100644
index 0000000..a1a5868
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsJsonInputParams.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.accounting.financialactivityaccount.api;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/***
+ * Enum of all parameters passed in while creating/updating a GL Account
+ ***/
+public enum FinancialActivityAccountsJsonInputParams {
+    FINANCIAL_ACTIVITY_ID("financialActivityId"), GL_ACCOUNT_ID("glAccountId");
+
+    private final String value;
+
+    private FinancialActivityAccountsJsonInputParams(final String value) {
+        this.value = value;
+    }
+
+    private static final Set<String> values = new HashSet<>();
+    static {
+        for (final FinancialActivityAccountsJsonInputParams type : FinancialActivityAccountsJsonInputParams.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/accounting/financialactivityaccount/data/FinancialActivityAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/data/FinancialActivityAccountData.java
new file mode 100644
index 0000000..72994eb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/data/FinancialActivityAccountData.java
@@ -0,0 +1,86 @@
+/**
+ * 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.financialactivityaccount.data;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+
+public class FinancialActivityAccountData {
+
+    private final Long id;
+    private final FinancialActivityData financialActivityData;
+    private final GLAccountData glAccountData;
+    private Map<String, List<GLAccountData>> glAccountOptions;
+    private List<FinancialActivityData> financialActivityOptions;
+
+    public FinancialActivityAccountData() {
+        this.id = null;
+        this.glAccountData = null;
+        this.financialActivityData = null;
+        this.glAccountOptions = null;
+        this.financialActivityOptions = null;
+    }
+
+    public FinancialActivityAccountData(final Long id, final FinancialActivityData financialActivityData, final GLAccountData glAccountData) {
+        this.id = id;
+        this.glAccountData = glAccountData;
+        this.financialActivityData = financialActivityData;
+    }
+
+    public FinancialActivityAccountData(Map<String, List<GLAccountData>> glAccountOptions,
+            List<FinancialActivityData> financialActivityOptions) {
+        this.id = null;
+        this.glAccountData = null;
+        this.financialActivityData = null;
+        this.glAccountOptions = glAccountOptions;
+        this.financialActivityOptions = financialActivityOptions;
+
+    }
+
+    public List<FinancialActivityData> getFinancialActivityOptions() {
+        return financialActivityOptions;
+    }
+
+    public void setFinancialActivityOptions(List<FinancialActivityData> financialActivityOptions) {
+        this.financialActivityOptions = financialActivityOptions;
+    }
+
+    public Map<String, List<GLAccountData>> getAccountingMappingOptions() {
+        return this.glAccountOptions;
+    }
+
+    public void setAccountingMappingOptions(Map<String, List<GLAccountData>> accountingMappingOptions) {
+        this.glAccountOptions = accountingMappingOptions;
+    }
+
+    public GLAccountData getGlAccountData() {
+        return glAccountData;
+    }
+
+    public FinancialActivityData getFinancialActivityData() {
+        return financialActivityData;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/data/FinancialActivityData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/data/FinancialActivityData.java
new file mode 100644
index 0000000..e8d2857
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/data/FinancialActivityData.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.accounting.financialactivityaccount.data;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+
+public class FinancialActivityData {
+
+    private final Integer id;
+    private final String name;
+    private final GLAccountType mappedGLAccountType;
+
+    public Integer getId() {
+        return this.id;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public GLAccountType getMappedGLAccountType() {
+        return this.mappedGLAccountType;
+    }
+
+    public FinancialActivityData(Integer id, String name, GLAccountType mappedGLAccountType) {
+        super();
+        this.id = id;
+        this.name = name;
+        this.mappedGLAccountType = mappedGLAccountType;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/domain/FinancialActivityAccount.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/domain/FinancialActivityAccount.java
new file mode 100644
index 0000000..93f8e79
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/domain/FinancialActivityAccount.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.accounting.financialactivityaccount.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "acc_gl_financial_activity_account")
+public class FinancialActivityAccount extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.EAGER)
+    @JoinColumn(name = "gl_account_id")
+    private GLAccount glAccount;
+
+    @Column(name = "financial_activity_type", nullable = false)
+    private Integer financialActivityType;
+
+    public static FinancialActivityAccount createNew(final GLAccount glAccount, final Integer financialAccountType) {
+        return new FinancialActivityAccount(glAccount, financialAccountType);
+    }
+
+    protected FinancialActivityAccount() {
+        //
+    }
+
+    private FinancialActivityAccount(final GLAccount glAccount, final int financialAccountType) {
+        this.glAccount = glAccount;
+        this.financialActivityType = financialAccountType;
+    }
+
+    public GLAccount getGlAccount() {
+        return this.glAccount;
+    }
+
+    public Integer getFinancialActivityType() {
+        return this.financialActivityType;
+    }
+
+    public void updateGlAccount(final GLAccount glAccount) {
+        this.glAccount = glAccount;
+    }
+
+    public void updateFinancialActivityType(final Integer financialActivityType) {
+        this.financialActivityType = financialActivityType;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/domain/FinancialActivityAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/domain/FinancialActivityAccountRepository.java
new file mode 100644
index 0000000..7414133
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/domain/FinancialActivityAccountRepository.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.accounting.financialactivityaccount.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 FinancialActivityAccountRepository extends JpaRepository<FinancialActivityAccount, Long>,
+        JpaSpecificationExecutor<FinancialActivityAccount> {
+
+    @Query("from FinancialActivityAccount faa where faa.financialActivityType = :financialActivityType")
+    FinancialActivityAccount findByFinancialActivityType(@Param("financialActivityType") int financialAccountType);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/domain/FinancialActivityAccountRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/domain/FinancialActivityAccountRepositoryWrapper.java
new file mode 100644
index 0000000..f7c309c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/domain/FinancialActivityAccountRepositoryWrapper.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.accounting.financialactivityaccount.domain;
+
+import java.util.List;
+
+import org.apache.fineract.accounting.financialactivityaccount.exception.FinancialActivityAccountNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link FinancialActivityAccountRepository} that adds NULL
+ * checking and Error handling capabilities
+ * </p>
+ */
+@Service
+public class FinancialActivityAccountRepositoryWrapper {
+
+    private final FinancialActivityAccountRepository repository;
+
+    @Autowired
+    public FinancialActivityAccountRepositoryWrapper(final FinancialActivityAccountRepository repository) {
+        this.repository = repository;
+    }
+
+    public FinancialActivityAccount findOneWithNotFoundDetection(final Long id) {
+        final FinancialActivityAccount financialActivityAccount = this.repository.findOne(id);
+        if (financialActivityAccount == null) { throw new FinancialActivityAccountNotFoundException(id); }
+        return financialActivityAccount;
+    }
+
+    public FinancialActivityAccount findByFinancialActivityTypeWithNotFoundDetection(final int financialActivityType) {
+        FinancialActivityAccount financialActivityAccount = this.repository.findByFinancialActivityType(financialActivityType);
+        if (financialActivityAccount == null) { throw new FinancialActivityAccountNotFoundException(financialActivityType); }
+        return financialActivityAccount;
+    }
+
+    public List<FinancialActivityAccount> findAll() {
+        return this.repository.findAll();
+    }
+
+    public void save(final FinancialActivityAccount entity) {
+        this.repository.save(entity);
+    }
+
+    public void saveAndFlush(final FinancialActivityAccount entity) {
+        this.repository.saveAndFlush(entity);
+    }
+
+    public void delete(final FinancialActivityAccount entity) {
+        this.repository.delete(entity);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/exception/DuplicateFinancialActivityAccountFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/exception/DuplicateFinancialActivityAccountFoundException.java
new file mode 100644
index 0000000..919d4ff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/exception/DuplicateFinancialActivityAccountFoundException.java
@@ -0,0 +1,39 @@
+/**
+ * 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.financialactivityaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when product to GL account mapping are not
+ * found.
+ */
+public class DuplicateFinancialActivityAccountFoundException extends AbstractPlatformDomainRuleException {
+
+    private final static String errorCode = "error.msg.financialActivityAccount.exists";
+
+    public DuplicateFinancialActivityAccountFoundException(final Integer financialActivityType) {
+        super(errorCode, "Mapping for activity already exists " + financialActivityType, financialActivityType);
+    }
+
+    public static String getErrorcode() {
+        return errorCode;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/exception/FinancialActivityAccountInvalidException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/exception/FinancialActivityAccountInvalidException.java
new file mode 100644
index 0000000..faebea3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/exception/FinancialActivityAccountInvalidException.java
@@ -0,0 +1,44 @@
+/**
+ * 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.financialactivityaccount.exception;
+
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when product to GL account mapping are not
+ * found.
+ */
+public class FinancialActivityAccountInvalidException extends AbstractPlatformDomainRuleException {
+
+    private final static String errorCode = "error.msg.financialActivityAccount.invalid";
+
+    public FinancialActivityAccountInvalidException(final FINANCIAL_ACTIVITY financialActivity, final GLAccount glAccount) {
+        super(errorCode, "Financial Activity '" + financialActivity.getCode() + "' with Id :" + financialActivity.getValue()
+                + "' can only be associated with a Ledger Account of Type " + financialActivity.getMappedGLAccountType().getCode()
+                + " the provided Ledger Account '" + glAccount.getName() + "(" + glAccount.getGlCode()
+                + ")'  does not of the required type", financialActivity.getCode(), financialActivity.getValue(), financialActivity
+                .getMappedGLAccountType().getCode(), glAccount.getName(), glAccount.getGlCode());
+    }
+
+    public static String getErrorcode() {
+        return errorCode;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/exception/FinancialActivityAccountNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/exception/FinancialActivityAccountNotFoundException.java
new file mode 100644
index 0000000..a058407
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/exception/FinancialActivityAccountNotFoundException.java
@@ -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.
+ */
+package org.apache.fineract.accounting.financialactivityaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when product to GL account mapping are not
+ * found.
+ */
+public class FinancialActivityAccountNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public FinancialActivityAccountNotFoundException(final Long id) {
+        super("error.msg.financialActivityAccount.not.found", "Financial Activity account with Id " + id + " does not exist", id);
+    }
+
+    public FinancialActivityAccountNotFoundException(final Integer financialActivityType) {
+        super("error.msg.financialActivityAccount.not.found", "Financial Activity account with for the financial Activity with Id "
+                + financialActivityType + " does not exist", financialActivityType);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/handler/CreateFinancialActivityAccountHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/handler/CreateFinancialActivityAccountHandler.java
new file mode 100644
index 0000000..9218678
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/handler/CreateFinancialActivityAccountHandler.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.accounting.financialactivityaccount.handler;
+
+import org.apache.fineract.accounting.financialactivityaccount.service.FinancialActivityAccountWritePlatformService;
+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 = "FINANCIALACTIVITYACCOUNT", action = "CREATE")
+public class CreateFinancialActivityAccountHandler implements NewCommandSourceHandler {
+
+    private final FinancialActivityAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateFinancialActivityAccountHandler(final FinancialActivityAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createFinancialActivityAccountMapping(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/handler/DeleteFinancialActivityAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/handler/DeleteFinancialActivityAccountCommandHandler.java
new file mode 100644
index 0000000..6c53a7e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/handler/DeleteFinancialActivityAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.financialactivityaccount.handler;
+
+import org.apache.fineract.accounting.financialactivityaccount.service.FinancialActivityAccountWritePlatformService;
+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 = "FINANCIALACTIVITYACCOUNT", action = "DELETE")
+public class DeleteFinancialActivityAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final FinancialActivityAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteFinancialActivityAccountCommandHandler(final FinancialActivityAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteGLAccountActivityMapping(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/handler/UpdateFinancialActivityAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/handler/UpdateFinancialActivityAccountCommandHandler.java
new file mode 100644
index 0000000..d183cf2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/handler/UpdateFinancialActivityAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.financialactivityaccount.handler;
+
+import org.apache.fineract.accounting.financialactivityaccount.service.FinancialActivityAccountWritePlatformService;
+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 = "FINANCIALACTIVITYACCOUNT", action = "UPDATE")
+public class UpdateFinancialActivityAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final FinancialActivityAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateFinancialActivityAccountCommandHandler(final FinancialActivityAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.updateGLAccountActivityMapping(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/serialization/FinancialActivityAccountDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/serialization/FinancialActivityAccountDataValidator.java
new file mode 100644
index 0000000..104b3c8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/serialization/FinancialActivityAccountDataValidator.java
@@ -0,0 +1,120 @@
+/**
+ * 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.financialactivityaccount.serialization;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.financialactivityaccount.api.FinancialActivityAccountsJsonInputParams;
+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 FinancialActivityAccountDataValidator {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = FinancialActivityAccountsJsonInputParams.getAllValues();
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    private final String paramNameForFinancialActivity = FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue();
+    private final String paramNameForGLAccount = FinancialActivityAccountsJsonInputParams.GL_ACCOUNT_ID.getValue();
+
+    @Autowired
+    public FinancialActivityAccountDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForCreate(final String json) {
+        validateJSONAndCheckForUnsupportedParams(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = getDataValidator(dataValidationErrors);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Integer financialActivityId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(paramNameForFinancialActivity, element);
+        baseDataValidator.reset().parameter(paramNameForFinancialActivity).value(financialActivityId).notNull().isOneOfTheseValues(
+                FINANCIAL_ACTIVITY.ASSET_TRANSFER.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(),
+                FINANCIAL_ACTIVITY.CASH_AT_MAINVAULT.getValue(), FINANCIAL_ACTIVITY.CASH_AT_TELLER.getValue(),
+                FINANCIAL_ACTIVITY.OPENING_BALANCES_TRANSFER_CONTRA.getValue(), FINANCIAL_ACTIVITY.ASSET_FUND_SOURCE.getValue());
+
+        final Long glAccountId = this.fromApiJsonHelper.extractLongNamed(paramNameForGLAccount, element);
+        baseDataValidator.reset().parameter(paramNameForGLAccount).value(glAccountId).notNull().integerGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private DataValidatorBuilder getDataValidator(final List<ApiParameterError> dataValidationErrors) {
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("financialactivityaccount");
+        return baseDataValidator;
+    }
+
+    public void validateForUpdate(final String json) {
+        validateJSONAndCheckForUnsupportedParams(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = getDataValidator(dataValidationErrors);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(paramNameForFinancialActivity, element)) {
+            final Integer financialActivityId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(paramNameForFinancialActivity,
+                    element);
+            baseDataValidator.reset().parameter(paramNameForFinancialActivity).value(financialActivityId).ignoreIfNull().isOneOfTheseValues(
+                    FINANCIAL_ACTIVITY.ASSET_TRANSFER.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(),
+                    FINANCIAL_ACTIVITY.OPENING_BALANCES_TRANSFER_CONTRA.getValue(), FINANCIAL_ACTIVITY.ASSET_FUND_SOURCE.getValue());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(paramNameForGLAccount, element)) {
+            final Long glAccountId = this.fromApiJsonHelper.extractLongNamed(paramNameForGLAccount, element);
+            baseDataValidator.reset().parameter(paramNameForGLAccount).value(glAccountId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateJSONAndCheckForUnsupportedParams(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);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountReadPlatformService.java
new file mode 100644
index 0000000..9e0c70e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountReadPlatformService.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.accounting.financialactivityaccount.service;
+
+import java.util.List;
+
+import org.apache.fineract.accounting.financialactivityaccount.data.FinancialActivityAccountData;
+
+public interface FinancialActivityAccountReadPlatformService {
+
+    List<FinancialActivityAccountData> retrieveAll();
+
+    FinancialActivityAccountData retrieve(Long mappingId);
+
+    FinancialActivityAccountData addTemplateDetails(FinancialActivityAccountData financialActivityAccountData);
+
+    FinancialActivityAccountData getFinancialActivityAccountTemplate();
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountReadPlatformServiceImpl.java
new file mode 100644
index 0000000..d7ef819
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountReadPlatformServiceImpl.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.accounting.financialactivityaccount.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.financialactivityaccount.data.FinancialActivityAccountData;
+import org.apache.fineract.accounting.financialactivityaccount.data.FinancialActivityData;
+import org.apache.fineract.accounting.financialactivityaccount.exception.FinancialActivityAccountNotFoundException;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+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 FinancialActivityAccountReadPlatformServiceImpl implements FinancialActivityAccountReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final FinancialActivityAccountMapper financialActivityAccountMapper;
+    private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
+
+    @Autowired
+    public FinancialActivityAccountReadPlatformServiceImpl(final RoutingDataSource dataSource,
+            final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService) {
+        financialActivityAccountMapper = new FinancialActivityAccountMapper();
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
+    }
+
+    @Override
+    public List<FinancialActivityAccountData> retrieveAll() {
+        String sql = "select " + financialActivityAccountMapper.schema();
+        return this.jdbcTemplate.query(sql, financialActivityAccountMapper, new Object[] {});
+    }
+
+    @Override
+    public FinancialActivityAccountData retrieve(Long financialActivityAccountId) {
+        try {
+            StringBuilder sqlBuilder = new StringBuilder(200);
+            sqlBuilder.append("select ");
+            sqlBuilder.append(this.financialActivityAccountMapper.schema());
+            sqlBuilder.append(" where faa.id=?");
+            return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), this.financialActivityAccountMapper,
+                    new Object[] { financialActivityAccountId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new FinancialActivityAccountNotFoundException(financialActivityAccountId);
+        }
+    }
+
+    @Override
+    public FinancialActivityAccountData addTemplateDetails(FinancialActivityAccountData financialActivityAccountData) {
+        final Map<String, List<GLAccountData>> accountOptions = this.accountingDropdownReadPlatformService.retrieveAccountMappingOptions();
+        financialActivityAccountData.setAccountingMappingOptions(accountOptions);
+        financialActivityAccountData.setFinancialActivityOptions(FINANCIAL_ACTIVITY.getAllFinancialActivities());
+        return financialActivityAccountData;
+    }
+
+    @Override
+    public FinancialActivityAccountData getFinancialActivityAccountTemplate() {
+        FinancialActivityAccountData financialActivityAccountData = new FinancialActivityAccountData();
+        return addTemplateDetails(financialActivityAccountData);
+    }
+
+    private static final class FinancialActivityAccountMapper implements RowMapper<FinancialActivityAccountData> {
+
+        private final String sql;
+
+        public FinancialActivityAccountMapper() {
+            StringBuilder sb = new StringBuilder(300);
+            sb.append(" faa.id as id, faa.financial_activity_type as financialActivityId, glaccount.id as glAccountId,glaccount.name as glAccountName,glaccount.gl_code as glCode  ");
+            sb.append(" from acc_gl_financial_activity_account faa ");
+            sb.append(" join acc_gl_account glaccount on glaccount.id = faa.gl_account_id");
+            sql = sb.toString();
+        }
+
+        public String schema() {
+            return sql;
+        }
+
+        @Override
+        public FinancialActivityAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final Long glAccountId = JdbcSupport.getLong(rs, "glAccountId");
+            final Integer financialActivityId = JdbcSupport.getInteger(rs, "financialActivityId");
+            final String glAccountName = rs.getString("glAccountName");
+            final String glCode = rs.getString("glCode");
+
+            final GLAccountData glAccountData = new GLAccountData(glAccountId, glAccountName, glCode);
+            final FinancialActivityData financialActivityData = FINANCIAL_ACTIVITY.toFinancialActivityData(financialActivityId);
+
+            final FinancialActivityAccountData financialActivityAccountData = new FinancialActivityAccountData(id, financialActivityData,
+                    glAccountData);
+            return financialActivityAccountData;
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountWritePlatformService.java
new file mode 100644
index 0000000..c3296aa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountWritePlatformService.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.accounting.financialactivityaccount.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface FinancialActivityAccountWritePlatformService {
+
+    CommandProcessingResult createFinancialActivityAccountMapping(JsonCommand command);
+
+    CommandProcessingResult updateGLAccountActivityMapping(Long mappingId, JsonCommand command);
+
+    CommandProcessingResult deleteGLAccountActivityMapping(Long mappingId, JsonCommand command);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountWritePlatformServiceImpl.java
new file mode 100644
index 0000000..558cc41
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/financialactivityaccount/service/FinancialActivityAccountWritePlatformServiceImpl.java
@@ -0,0 +1,176 @@
+/**
+ * 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.financialactivityaccount.service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.financialactivityaccount.api.FinancialActivityAccountsJsonInputParams;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
+import org.apache.fineract.accounting.financialactivityaccount.exception.DuplicateFinancialActivityAccountFoundException;
+import org.apache.fineract.accounting.financialactivityaccount.exception.FinancialActivityAccountInvalidException;
+import org.apache.fineract.accounting.financialactivityaccount.serialization.FinancialActivityAccountDataValidator;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FinancialActivityAccountWritePlatformServiceImpl implements FinancialActivityAccountWritePlatformService {
+
+    private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository;
+    private final FinancialActivityAccountDataValidator fromApiJsonDeserializer;
+    private final GLAccountRepositoryWrapper glAccountRepositoryWrapper;
+    private final static Logger logger = LoggerFactory.getLogger(FinancialActivityAccountWritePlatformServiceImpl.class);
+
+    @Autowired
+    public FinancialActivityAccountWritePlatformServiceImpl(
+            final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository,
+            final FinancialActivityAccountDataValidator fromApiJsonDeserializer, final GLAccountRepositoryWrapper glAccountRepositoryWrapper) {
+        this.financialActivityAccountRepository = financialActivityAccountRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.glAccountRepositoryWrapper = glAccountRepositoryWrapper;
+    }
+
+    @Override
+    public CommandProcessingResult createFinancialActivityAccountMapping(JsonCommand command) {
+        try {
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final Integer financialActivityId = command
+                    .integerValueSansLocaleOfParameterNamed(FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue());
+            final Long accountId = command.longValueOfParameterNamed(FinancialActivityAccountsJsonInputParams.GL_ACCOUNT_ID.getValue());
+            final GLAccount glAccount = glAccountRepositoryWrapper.findOneWithNotFoundDetection(accountId);
+            FinancialActivityAccount financialActivityAccount = FinancialActivityAccount.createNew(glAccount, financialActivityId);
+
+            validateFinancialActivityAndAccountMapping(financialActivityAccount);
+            this.financialActivityAccountRepository.save(financialActivityAccount);
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(financialActivityAccount.getId()) //
+                    .build();
+        } catch (DataIntegrityViolationException dataIntegrityViolationException) {
+            handleFinancialActivityAccountDataIntegrityIssues(command, dataIntegrityViolationException);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    /**
+     * Validate that the GL Account is appropriate for the particular Financial
+     * Activity Type
+     **/
+    private void validateFinancialActivityAndAccountMapping(FinancialActivityAccount financialActivityAccount) {
+        FINANCIAL_ACTIVITY financialActivity = FINANCIAL_ACTIVITY.fromInt(financialActivityAccount.getFinancialActivityType());
+        GLAccount glAccount = financialActivityAccount.getGlAccount();
+        if (!financialActivity.getMappedGLAccountType().getValue().equals(glAccount.getType())) { throw new FinancialActivityAccountInvalidException(
+                financialActivity, glAccount); }
+    }
+
+    @Override
+    public CommandProcessingResult updateGLAccountActivityMapping(Long financialActivityAccountId, JsonCommand command) {
+        try {
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+            final FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository
+                    .findOneWithNotFoundDetection(financialActivityAccountId);
+            Map<String, Object> changes = findChanges(command, financialActivityAccount);
+
+            if (changes.containsKey(FinancialActivityAccountsJsonInputParams.GL_ACCOUNT_ID.getValue())) {
+                final Long accountId = command.longValueOfParameterNamed(FinancialActivityAccountsJsonInputParams.GL_ACCOUNT_ID.getValue());
+                final GLAccount glAccount = glAccountRepositoryWrapper.findOneWithNotFoundDetection(accountId);
+                financialActivityAccount.updateGlAccount(glAccount);
+            }
+
+            if (changes.containsKey(FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue())) {
+                final Integer financialActivityId = command
+                        .integerValueSansLocaleOfParameterNamed(FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue());
+                financialActivityAccount.updateFinancialActivityType(financialActivityId);
+            }
+
+            if (!changes.isEmpty()) {
+                validateFinancialActivityAndAccountMapping(financialActivityAccount);
+                this.financialActivityAccountRepository.save(financialActivityAccount);
+            }
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(financialActivityAccountId) //
+                    .with(changes) //
+                    .build();
+        } catch (DataIntegrityViolationException dataIntegrityViolationException) {
+            handleFinancialActivityAccountDataIntegrityIssues(command, dataIntegrityViolationException);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    public CommandProcessingResult deleteGLAccountActivityMapping(Long financialActivityAccountId, JsonCommand command) {
+        final FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository
+                .findOneWithNotFoundDetection(financialActivityAccountId);
+        this.financialActivityAccountRepository.delete(financialActivityAccount);
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(financialActivityAccountId) //
+                .build();
+    }
+
+    private void handleFinancialActivityAccountDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("financial_activity_type")) {
+            final Integer financialActivityId = command
+                    .integerValueSansLocaleOfParameterNamed(FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue());
+            throw new DuplicateFinancialActivityAccountFoundException(financialActivityId);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.glAccount.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource GL Account: " + realCause.getMessage());
+    }
+
+    public Map<String, Object> findChanges(JsonCommand command, FinancialActivityAccount financialActivityAccount) {
+
+        Map<String, Object> changes = new HashMap<>();
+
+        Long existingGLAccountId = financialActivityAccount.getGlAccount().getId();
+        Integer financialActivityType = financialActivityAccount.getFinancialActivityType();
+
+        // is the account Id changed?
+        if (command.isChangeInLongParameterNamed(FinancialActivityAccountsJsonInputParams.GL_ACCOUNT_ID.getValue(), existingGLAccountId)) {
+            final Long newValue = command.longValueOfParameterNamed(FinancialActivityAccountsJsonInputParams.GL_ACCOUNT_ID.getValue());
+            changes.put(FinancialActivityAccountsJsonInputParams.GL_ACCOUNT_ID.getValue(), newValue);
+        }
+
+        // is the financial Activity changed
+        if (command.isChangeInIntegerSansLocaleParameterNamed(FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue(),
+                financialActivityType)) {
+            final Integer newValue = command
+                    .integerValueSansLocaleOfParameterNamed(FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue());
+            changes.put(FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue(), newValue);
+        }
+        return changes;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountJsonInputParams.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountJsonInputParams.java
new file mode 100755
index 0000000..a3d85ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountJsonInputParams.java
@@ -0,0 +1,56 @@
+/**
+ * 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.glaccount.api;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/***
+ * Enum of all parameters passed in while creating/updating a GL Account
+ ***/
+public enum GLAccountJsonInputParams {
+    ID("id"), NAME("name"), PARENT_ID("parentId"), GL_CODE("glCode"), DISABLED("disabled"), MANUAL_ENTRIES_ALLOWED("manualEntriesAllowed"), TYPE(
+            "type"), USAGE("usage"), DESCRIPTION("description"), TAGID("tagId");
+
+    private final String value;
+
+    private GLAccountJsonInputParams(final String value) {
+        this.value = value;
+    }
+
+    private static final Set<String> values = new HashSet<>();
+    static {
+        for (final GLAccountJsonInputParams type : GLAccountJsonInputParams.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/accounting/glaccount/api/GLAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java
new file mode 100644
index 0000000..6139114
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java
@@ -0,0 +1,222 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.glaccount.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.accounting.common.AccountingConstants;
+import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
+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.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/glaccounts")
+@Component
+@Scope("singleton")
+public class GLAccountsApiResource {
+
+    private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "parentId", "glCode",
+            "disabled", "manualEntriesAllowed", "type", "usage", "description", "assetHeaderAccountOptions",
+            "liabilityHeaderAccountOptions", "equityHeaderAccountOptions", "incomeHeaderAccountOptions", "expenseHeaderAccountOptions",
+            "nameDecorated", "tagId", "allowedAssetsTagOptions", "allowedLiabilitiesTagOptions", "allowedEquityTagOptions",
+            "allowedIncomeTagOptions", "allowedExpensesTagOptions", "creditAccounts", "debitAccounts"));
+
+    private final String resourceNameForPermission = "GLACCOUNT";
+
+    private final GLAccountReadPlatformService glAccountReadPlatformService;
+    private final AccountingDropdownReadPlatformService dropdownReadPlatformService;
+    private final DefaultToApiJsonSerializer<GLAccountData> apiJsonSerializerService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PlatformSecurityContext context;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+
+    @Autowired
+    public GLAccountsApiResource(final PlatformSecurityContext context, final GLAccountReadPlatformService glAccountReadPlatformService,
+            final DefaultToApiJsonSerializer<GLAccountData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final AccountingDropdownReadPlatformService dropdownReadPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService) {
+        this.context = context;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiJsonSerializerService = toApiJsonSerializer;
+        this.glAccountReadPlatformService = glAccountReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveNewAccountDetails(@Context final UriInfo uriInfo, @QueryParam("type") final Integer type) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        GLAccountData glAccountData = this.glAccountReadPlatformService.retrieveNewGLAccountDetails(type);
+        glAccountData = handleTemplate(glAccountData);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, glAccountData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllAccounts(@Context final UriInfo uriInfo, @QueryParam("type") final Integer type,
+            @QueryParam("searchParam") final String searchParam, @QueryParam("usage") final Integer usage,
+            @QueryParam("manualEntriesAllowed") final Boolean manualEntriesAllowed, @QueryParam("disabled") final Boolean disabled,
+            @QueryParam("fetchRunningBalance") final boolean runningBalance) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+        JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(false, runningBalance);
+        final List<GLAccountData> glAccountDatas = this.glAccountReadPlatformService.retrieveAllGLAccounts(type, searchParam, usage,
+                manualEntriesAllowed, disabled, associationParametersData);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, glAccountDatas, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{glAccountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retreiveAccount(@PathParam("glAccountId") final Long glAccountId, @Context final UriInfo uriInfo,
+            @QueryParam("fetchRunningBalance") final boolean runningBalance) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(false, runningBalance);
+        GLAccountData glAccountData = this.glAccountReadPlatformService.retrieveGLAccountById(glAccountId, associationParametersData);
+        if (settings.isTemplate()) {
+            glAccountData = handleTemplate(glAccountData);
+        }
+
+        return this.apiJsonSerializerService.serialize(settings, glAccountData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createGLAccount(final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createGLAccount().withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @PUT
+    @Path("{glAccountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateGLAccount(@PathParam("glAccountId") final Long glAccountId, final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateGLAccount(glAccountId).withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @DELETE
+    @Path("{glAccountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteGLAccount(@PathParam("glAccountId") final Long glAccountId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteGLAccount(glAccountId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    private GLAccountData handleTemplate(final GLAccountData glAccountData) {
+        final List<EnumOptionData> accountTypeOptions = this.dropdownReadPlatformService.retrieveGLAccountTypeOptions();
+        final List<EnumOptionData> usageOptions = this.dropdownReadPlatformService.retrieveGLAccountUsageOptions();
+        final List<GLAccountData> assetHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
+                .retrieveAllEnabledHeaderGLAccounts(GLAccountType.ASSET));
+        final List<GLAccountData> liabilityHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
+                .retrieveAllEnabledHeaderGLAccounts(GLAccountType.LIABILITY));
+        final List<GLAccountData> equityHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
+                .retrieveAllEnabledHeaderGLAccounts(GLAccountType.EQUITY));
+        final List<GLAccountData> incomeHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
+                .retrieveAllEnabledHeaderGLAccounts(GLAccountType.INCOME));
+        final List<GLAccountData> expenseHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
+                .retrieveAllEnabledHeaderGLAccounts(GLAccountType.EXPENSE));
+        final Collection<CodeValueData> allowedAssetsTagOptions = this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME);
+        final Collection<CodeValueData> allowedLiabilitiesTagOptions = this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME);
+        final Collection<CodeValueData> allowedEquityTagOptions = this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME);
+        final Collection<CodeValueData> allowedIncomeTagOptions = this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.INCOME_TAG_OPTION_CODE_NAME);
+        final Collection<CodeValueData> allowedExpensesTagOptions = this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME);
+
+        return new GLAccountData(glAccountData, accountTypeOptions, usageOptions, assetHeaderAccountOptions, liabilityHeaderAccountOptions,
+                equityHeaderAccountOptions, incomeHeaderAccountOptions, expenseHeaderAccountOptions, allowedAssetsTagOptions,
+                allowedLiabilitiesTagOptions, allowedEquityTagOptions, allowedIncomeTagOptions, allowedExpensesTagOptions);
+    }
+
+    private List<GLAccountData> defaultIfEmpty(final List<GLAccountData> list) {
+        List<GLAccountData> returnList = null;
+        if (list != null && !list.isEmpty()) {
+            returnList = list;
+        }
+        return returnList;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/command/GLAccountCommand.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/command/GLAccountCommand.java
new file mode 100644
index 0000000..94ad2af
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/command/GLAccountCommand.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.accounting.glaccount.command;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.accounting.glaccount.api.GLAccountJsonInputParams;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+
+/**
+ * Immutable command for adding a general Ledger Account
+ */
+public class GLAccountCommand {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    private final String name;
+    private final Long parentId;
+    private final String glCode;
+    private final Boolean disabled;
+    private final Boolean manualEntriesAllowed;
+    private final Integer usage;
+    private final Integer type;
+    private final String description;
+    private final Long tagId;
+
+    public GLAccountCommand(final Long id, final String name, final Long parentId, final String glCode, final Boolean disabled,
+            final Boolean manualEntriesAllowed, final Integer type, final Integer usage, final String description, final Long tagId) {
+        this.id = id;
+        this.name = name;
+        this.parentId = parentId;
+        this.glCode = glCode;
+        this.disabled = disabled;
+        this.manualEntriesAllowed = manualEntriesAllowed;
+        this.type = type;
+        this.usage = usage;
+        this.description = description;
+        this.tagId = tagId;
+    }
+
+    public void validateForCreate() {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("GLAccount");
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.NAME.getValue()).value(this.name).notBlank().notExceedingLengthOf(200);
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.GL_CODE.getValue()).value(this.glCode).notBlank()
+                .notExceedingLengthOf(45);
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.PARENT_ID.getValue()).value(this.parentId).ignoreIfNull()
+                .integerGreaterThanZero();
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.TYPE.getValue()).value(this.type).notNull()
+                .inMinMaxRange(GLAccountType.getMinValue(), GLAccountType.getMaxValue());
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.USAGE.getValue()).value(this.usage)
+                .inMinMaxRange(GLAccountUsage.getMinValue(), GLAccountUsage.getMaxValue());
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.DESCRIPTION.getValue()).value(this.description).ignoreIfNull()
+                .notExceedingLengthOf(500);
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.MANUAL_ENTRIES_ALLOWED.getValue()).value(this.manualEntriesAllowed)
+                .notBlank();
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.TAGID.getValue()).value(this.tagId).ignoreIfNull()
+                .longGreaterThanZero();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForUpdate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("GLAccount");
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.NAME.getValue()).value(this.name).ignoreIfNull().notBlank()
+                .notExceedingLengthOf(200);
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.GL_CODE.getValue()).ignoreIfNull().value(this.glCode).notBlank()
+                .notExceedingLengthOf(45);
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.PARENT_ID.getValue()).value(this.parentId).ignoreIfNull()
+                .integerGreaterThanZero();
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.TYPE.getValue()).value(this.type).ignoreIfNull()
+                .inMinMaxRange(GLAccountType.getMinValue(), GLAccountType.getMaxValue());
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.USAGE.getValue()).value(this.usage).ignoreIfNull()
+                .inMinMaxRange(GLAccountUsage.getMinValue(), GLAccountUsage.getMaxValue());
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.DESCRIPTION.getValue()).value(this.description).ignoreIfNull()
+                .notBlank().notExceedingLengthOf(500);
+
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.DISABLED.getValue()).value(this.disabled).ignoreIfNull();
+
+        baseDataValidator.reset().anyOfNotNull(this.name, this.glCode, this.parentId, this.type, this.description, this.disabled);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+        baseDataValidator.reset().parameter(GLAccountJsonInputParams.TAGID.getValue()).value(this.tagId).ignoreIfNull()
+                .longGreaterThanZero();
+    }
+
+    public boolean isHeaderAccount() {
+        return GLAccountUsage.HEADER.getValue().equals(this.usage);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountData.java
new file mode 100755
index 0000000..aec2245
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountData.java
@@ -0,0 +1,198 @@
+/**
+ * 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.glaccount.data;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+/**
+ * Immutable object representing a General Ledger Account
+ * 
+ * Note: no getter/setters required as google-gson will produce json from fields
+ * of object.
+ */
+public class GLAccountData {
+
+    private final Long id;
+    private final String name;
+    private final Long parentId;
+    private final String glCode;
+    private final Boolean disabled;
+    private final Boolean manualEntriesAllowed;
+    private final EnumOptionData type;
+    private final EnumOptionData usage;
+    private final String description;
+    private final String nameDecorated;
+    private final CodeValueData tagId;
+    private final Long organizationRunningBalance;
+
+    // templates
+    final List<EnumOptionData> accountTypeOptions;
+    final List<EnumOptionData> usageOptions;
+    final List<GLAccountData> assetHeaderAccountOptions;
+    final List<GLAccountData> liabilityHeaderAccountOptions;
+    final List<GLAccountData> equityHeaderAccountOptions;
+    final List<GLAccountData> incomeHeaderAccountOptions;
+    final List<GLAccountData> expenseHeaderAccountOptions;
+    final Collection<CodeValueData> allowedAssetsTagOptions;
+    final Collection<CodeValueData> allowedLiabilitiesTagOptions;
+    final Collection<CodeValueData> allowedEquityTagOptions;
+    final Collection<CodeValueData> allowedIncomeTagOptions;
+    final Collection<CodeValueData> allowedExpensesTagOptions;
+
+    public GLAccountData(final Long id, final String name, final Long parentId, final String glCode, final boolean disabled,
+            final boolean manualEntriesAllowed, final EnumOptionData type, final EnumOptionData usage, final String description,
+            final String nameDecorated, final CodeValueData tagId, final Long organizationRunningBalance) {
+        this.id = id;
+        this.name = name;
+        this.parentId = parentId;
+        this.glCode = glCode;
+        this.disabled = disabled;
+        this.manualEntriesAllowed = manualEntriesAllowed;
+        this.type = type;
+        this.usage = usage;
+        this.description = description;
+        this.nameDecorated = nameDecorated;
+        this.tagId = tagId;
+        this.organizationRunningBalance = organizationRunningBalance;
+        this.accountTypeOptions = null;
+        this.usageOptions = null;
+        this.assetHeaderAccountOptions = null;
+        this.liabilityHeaderAccountOptions = null;
+        this.equityHeaderAccountOptions = null;
+        this.incomeHeaderAccountOptions = null;
+        this.expenseHeaderAccountOptions = null;
+        this.allowedAssetsTagOptions = null;
+        this.allowedLiabilitiesTagOptions = null;
+        this.allowedEquityTagOptions = null;
+        this.allowedIncomeTagOptions = null;
+        this.allowedExpensesTagOptions = null;
+    }
+
+    public GLAccountData(final GLAccountData accountData, final List<EnumOptionData> accountTypeOptions,
+            final List<EnumOptionData> usageOptions, final List<GLAccountData> assetHeaderAccountOptions,
+            final List<GLAccountData> liabilityHeaderAccountOptions, final List<GLAccountData> equityHeaderAccountOptions,
+            final List<GLAccountData> incomeHeaderAccountOptions, final List<GLAccountData> expenseHeaderAccountOptions,
+            final Collection<CodeValueData> allowedAssetsTagOptions, final Collection<CodeValueData> allowedLiabilitiesTagOptions,
+            final Collection<CodeValueData> allowedEquityTagOptions, final Collection<CodeValueData> allowedIncomeTagOptions,
+            final Collection<CodeValueData> allowedExpensesTagOptions) {
+        this.id = accountData.id;
+        this.name = accountData.name;
+        this.parentId = accountData.parentId;
+        this.glCode = accountData.glCode;
+        this.disabled = accountData.disabled;
+        this.manualEntriesAllowed = accountData.manualEntriesAllowed;
+        this.type = accountData.type;
+        this.usage = accountData.usage;
+        this.description = accountData.description;
+        this.nameDecorated = accountData.nameDecorated;
+        this.tagId = accountData.tagId;
+        this.organizationRunningBalance = accountData.organizationRunningBalance;
+        this.accountTypeOptions = accountTypeOptions;
+        this.usageOptions = usageOptions;
+        this.assetHeaderAccountOptions = assetHeaderAccountOptions;
+        this.liabilityHeaderAccountOptions = liabilityHeaderAccountOptions;
+        this.equityHeaderAccountOptions = equityHeaderAccountOptions;
+        this.incomeHeaderAccountOptions = incomeHeaderAccountOptions;
+        this.expenseHeaderAccountOptions = expenseHeaderAccountOptions;
+        this.allowedAssetsTagOptions = allowedAssetsTagOptions;
+        this.allowedLiabilitiesTagOptions = allowedLiabilitiesTagOptions;
+        this.allowedEquityTagOptions = allowedEquityTagOptions;
+        this.allowedIncomeTagOptions = allowedIncomeTagOptions;
+        this.allowedExpensesTagOptions = allowedExpensesTagOptions;
+    }
+
+    public static GLAccountData sensibleDefaultsForNewGLAccountCreation(final Integer glAccType) {
+        final Long id = null;
+        final String name = null;
+        final Long parentId = null;
+        final String glCode = null;
+        final boolean disabled = false;
+        final boolean manualEntriesAllowed = true;
+        final EnumOptionData type;
+        if (glAccType != null && glAccType >= GLAccountType.getMinValue() && glAccType <= GLAccountType.getMaxValue()) {
+            type = AccountingEnumerations.gLAccountType(glAccType);
+        } else {
+            type = AccountingEnumerations.gLAccountType(GLAccountType.ASSET);
+        }
+        final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(GLAccountUsage.DETAIL);
+        final String description = null;
+        final String nameDecorated = null;
+        final CodeValueData tagId = null;
+        final Long organizationRunningBalance = null;
+
+        return new GLAccountData(id, name, parentId, glCode, disabled, manualEntriesAllowed, type, usage, description, nameDecorated,
+                tagId, organizationRunningBalance);
+    }
+
+    public GLAccountData(final Long id, final String name, final String glCode) {
+        this.id = id;
+        this.name = name;
+        this.parentId = null;
+        this.glCode = glCode;
+        this.disabled = null;
+        this.manualEntriesAllowed = null;
+        this.type = null;
+        this.usage = null;
+        this.description = null;
+        this.nameDecorated = null;
+        this.tagId = null;
+        this.organizationRunningBalance = null;
+        this.accountTypeOptions = null;
+        this.usageOptions = null;
+        this.assetHeaderAccountOptions = null;
+        this.liabilityHeaderAccountOptions = null;
+        this.equityHeaderAccountOptions = null;
+        this.incomeHeaderAccountOptions = null;
+        this.expenseHeaderAccountOptions = null;
+        this.allowedAssetsTagOptions = null;
+        this.allowedLiabilitiesTagOptions = null;
+        this.allowedEquityTagOptions = null;
+        this.allowedIncomeTagOptions = null;
+        this.allowedExpensesTagOptions = null;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getGlCode() {
+        return this.glCode;
+    }
+
+    public EnumOptionData getType() {
+        return this.type;
+    }
+
+    public Integer getTypeId() {
+        if (this.type != null) { return this.type.getId().intValue(); }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountDataForLookup.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountDataForLookup.java
new file mode 100644
index 0000000..5fc0d96
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountDataForLookup.java
@@ -0,0 +1,39 @@
+/**
+ * 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.glaccount.data;
+
+public class GLAccountDataForLookup {
+
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String name;
+    @SuppressWarnings("unused")
+    private final String glCode;
+
+    public GLAccountDataForLookup(final Long id, final String name, final String glCode) {
+        this.id = id;
+        this.name = name;
+        this.glCode = glCode;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccount.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccount.java
new file mode 100644
index 0000000..5ed1ab9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccount.java
@@ -0,0 +1,250 @@
+/**
+ * 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.glaccount.domain;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.glaccount.api.GLAccountJsonInputParams;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "acc_gl_account", uniqueConstraints = { @UniqueConstraint(columnNames = { "gl_code" }, name = "acc_gl_code") })
+public class GLAccount extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "parent_id")
+    private GLAccount parent;
+
+    @Column(name = "hierarchy", nullable = true, length = 50)
+    private String hierarchy;
+
+    @OneToMany(fetch = FetchType.LAZY)
+    @JoinColumn(name = "parent_id")
+    private final List<GLAccount> children = new LinkedList<>();
+
+    @Column(name = "name", nullable = false, length = 45)
+    private String name;
+
+    @Column(name = "gl_code", nullable = false, length = 100)
+    private String glCode;
+
+    @Column(name = "disabled", nullable = false)
+    private boolean disabled = false;
+
+    @Column(name = "manual_journal_entries_allowed", nullable = false)
+    private boolean manualEntriesAllowed = true;
+
+    @Column(name = "classification_enum", nullable = false)
+    private Integer type;
+
+    @Column(name = "account_usage", nullable = false)
+    private Integer usage;
+
+    @Column(name = "description", nullable = true, length = 500)
+    private String description;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "tag_id")
+    private CodeValue tagId;
+
+    protected GLAccount() {
+        //
+    }
+
+    private GLAccount(final GLAccount parent, final String name, final String glCode, final boolean disabled,
+            final boolean manualEntriesAllowed, final Integer type, final Integer usage, final String description, final CodeValue tagId) {
+        this.name = StringUtils.defaultIfEmpty(name, null);
+        this.glCode = StringUtils.defaultIfEmpty(glCode, null);
+        this.disabled = BooleanUtils.toBooleanDefaultIfNull(disabled, false);
+        this.manualEntriesAllowed = BooleanUtils.toBooleanDefaultIfNull(manualEntriesAllowed, true);
+        this.usage = usage;
+        this.type = type;
+        this.description = StringUtils.defaultIfEmpty(description, null);
+        this.parent = parent;
+        this.tagId = tagId;
+    }
+
+    public static GLAccount fromJson(final GLAccount parent, final JsonCommand command, final CodeValue glAccountTagType) {
+        final String name = command.stringValueOfParameterNamed(GLAccountJsonInputParams.NAME.getValue());
+        final String glCode = command.stringValueOfParameterNamed(GLAccountJsonInputParams.GL_CODE.getValue());
+        final boolean disabled = command.booleanPrimitiveValueOfParameterNamed(GLAccountJsonInputParams.DISABLED.getValue());
+        final boolean manualEntriesAllowed = command.booleanPrimitiveValueOfParameterNamed(GLAccountJsonInputParams.MANUAL_ENTRIES_ALLOWED
+                .getValue());
+        final Integer usage = command.integerValueSansLocaleOfParameterNamed(GLAccountJsonInputParams.USAGE.getValue());
+        final Integer type = command.integerValueSansLocaleOfParameterNamed(GLAccountJsonInputParams.TYPE.getValue());
+        final String description = command.stringValueOfParameterNamed(GLAccountJsonInputParams.DESCRIPTION.getValue());
+        return new GLAccount(parent, name, glCode, disabled, manualEntriesAllowed, type, usage, description, glAccountTagType);
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(15);
+        handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.DESCRIPTION.getValue(), this.description);
+        handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.DISABLED.getValue(), this.disabled);
+        handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.GL_CODE.getValue(), this.glCode);
+        handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.MANUAL_ENTRIES_ALLOWED.getValue(), this.manualEntriesAllowed);
+        handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.NAME.getValue(), this.name);
+        handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.PARENT_ID.getValue(), 0L);
+        handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.TYPE.getValue(), this.type, true);
+        handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.USAGE.getValue(), this.usage, true);
+        handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.TAGID.getValue(),
+                this.tagId == null ? 0L : this.tagId.getId());
+        return actualChanges;
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            final Integer propertyToBeUpdated, final boolean sansLocale) {
+        boolean changeDetected = false;
+        if (sansLocale) {
+            changeDetected = command.isChangeInIntegerSansLocaleParameterNamed(paramName, propertyToBeUpdated);
+        } else {
+            changeDetected = command.isChangeInIntegerParameterNamed(paramName, propertyToBeUpdated);
+        }
+        if (changeDetected) {
+            Integer newValue = null;
+            if (sansLocale) {
+                newValue = command.integerValueSansLocaleOfParameterNamed(paramName);
+            } else {
+                newValue = command.integerValueOfParameterNamed(paramName);
+            }
+            actualChanges.put(paramName, newValue);
+            // now update actual property
+            if (paramName.equals(GLAccountJsonInputParams.TYPE.getValue())) {
+                this.type = newValue;
+            } else if (paramName.equals(GLAccountJsonInputParams.USAGE.getValue())) {
+                this.usage = newValue;
+            }
+        }
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            final String propertyToBeUpdated) {
+        if (command.isChangeInStringParameterNamed(paramName, propertyToBeUpdated)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            // now update actual property
+            if (paramName.equals(GLAccountJsonInputParams.DESCRIPTION.getValue())) {
+                this.description = newValue;
+            } else if (paramName.equals(GLAccountJsonInputParams.GL_CODE.getValue())) {
+                this.glCode = newValue;
+            } else if (paramName.equals(GLAccountJsonInputParams.NAME.getValue())) {
+                this.name = newValue;
+            }
+        }
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            final Long propertyToBeUpdated) {
+        if (command.isChangeInLongParameterNamed(paramName, propertyToBeUpdated)) {
+            final Long newValue = command.longValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            // now update actual property
+            if (paramName.equals(GLAccountJsonInputParams.PARENT_ID.getValue())) {
+                // do nothing as this is a nested property
+            }
+        }
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            final boolean propertyToBeUpdated) {
+        if (command.isChangeInBooleanParameterNamed(paramName, propertyToBeUpdated)) {
+            final Boolean newValue = command.booleanObjectValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            // now update actual property
+            if (paramName.equals(GLAccountJsonInputParams.MANUAL_ENTRIES_ALLOWED.getValue())) {
+                this.manualEntriesAllowed = newValue;
+            } else if (paramName.equals(GLAccountJsonInputParams.DISABLED.getValue())) {
+                this.disabled = newValue;
+            }
+        }
+    }
+
+    public boolean isHeaderAccount() {
+        return GLAccountUsage.HEADER.getValue().equals(this.usage);
+    }
+
+    public Integer getUsage() {
+        return this.usage;
+    }
+
+    public List<GLAccount> getChildren() {
+        return this.children;
+    }
+
+    public boolean isDisabled() {
+        return this.disabled;
+    }
+
+    public boolean isManualEntriesAllowed() {
+        return this.manualEntriesAllowed;
+    }
+
+    public String getGlCode() {
+        return this.glCode;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public Integer getType() {
+        return this.type;
+    }
+
+    public void generateHierarchy() {
+
+        if (this.parent != null) {
+            this.hierarchy = this.parent.hierarchyOf(getId());
+        } else {
+            this.hierarchy = ".";
+        }
+    }
+
+    private String hierarchyOf(final Long id) {
+        return this.hierarchy + id.toString() + ".";
+    }
+
+    public boolean isDetailAccount() {
+        return GLAccountUsage.DETAIL.getValue().equals(this.usage);
+    }
+
+    public void updateTagId(final CodeValue tagID) {
+        this.tagId = tagID;
+    }
+
+    public void updateParentAccount(final GLAccount parentAccount) {
+        this.parent = parentAccount;
+        generateHierarchy();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepository.java
new file mode 100644
index 0000000..a309f06
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepository.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.accounting.glaccount.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface GLAccountRepository extends JpaRepository<GLAccount, Long>, JpaSpecificationExecutor<GLAccount> {
+    // no added behaviour
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepositoryWrapper.java
new file mode 100755
index 0000000..5c7b26c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepositoryWrapper.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.glaccount.domain;
+
+import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link GLAccountRepository} .
+ * </p>
+ */
+@Service
+public class GLAccountRepositoryWrapper {
+
+    private final GLAccountRepository repository;
+
+    @Autowired
+    public GLAccountRepositoryWrapper(final GLAccountRepository repository) {
+        this.repository = repository;
+    }
+
+    public GLAccount findOneWithNotFoundDetection(final Long id) {
+        final GLAccount account = this.repository.findOne(id);
+        if (account == null) { throw new GLAccountNotFoundException(id); }
+        return account;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountType.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountType.java
new file mode 100755
index 0000000..b9cf002
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountType.java
@@ -0,0 +1,102 @@
+/**
+ * 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.glaccount.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum GLAccountType {
+    ASSET(1, "accountType.asset"), LIABILITY(2, "accountType.liability"), EQUITY(3, "accountType.equity"), INCOME(4, "accountType.income"), EXPENSE(
+            5, "accountType.expense");
+
+    private final Integer value;
+    private final String code;
+
+    private GLAccountType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, GLAccountType> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final GLAccountType type : GLAccountType.values()) {
+            if (i == 0) {
+                minValue = type.value;
+            }
+            intToEnumMap.put(type.value, type);
+            if (minValue >= type.value) {
+                minValue = type.value;
+            }
+            if (maxValue < type.value) {
+                maxValue = type.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static GLAccountType fromInt(final int i) {
+        final GLAccountType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public boolean isAssetType() {
+        return this.value.equals(GLAccountType.ASSET.getValue());
+    }
+
+    public boolean isLiabilityType() {
+        return this.value.equals(GLAccountType.LIABILITY.getValue());
+    }
+
+    public boolean isEquityType() {
+        return this.value.equals(GLAccountType.EQUITY.getValue());
+    }
+
+    public boolean isIncomeType() {
+        return this.value.equals(GLAccountType.INCOME.getValue());
+    }
+
+    public boolean isExpenseType() {
+        return this.value.equals(GLAccountType.EXPENSE.getValue());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountUsage.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountUsage.java
new file mode 100755
index 0000000..fa545ef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountUsage.java
@@ -0,0 +1,82 @@
+/**
+ * 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.glaccount.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum GLAccountUsage {
+
+    DETAIL(1, "accountUsage.detail"), HEADER(2, "accountUsage.header");
+
+    private final Integer value;
+    private final String code;
+
+    private GLAccountUsage(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, GLAccountUsage> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final GLAccountUsage type : GLAccountUsage.values()) {
+            if (i == 0) {
+                minValue = type.value;
+            }
+            intToEnumMap.put(type.value, type);
+            if (minValue >= type.value) {
+                minValue = type.value;
+            }
+            if (maxValue < type.value) {
+                maxValue = type.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static GLAccountUsage fromInt(final int i) {
+        final GLAccountUsage type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountDuplicateException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountDuplicateException.java
new file mode 100755
index 0000000..94188df
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountDuplicateException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.glaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when a GL Account with a given GL Code of
+ * the particular type is already present
+ */
+public class GLAccountDuplicateException extends AbstractPlatformDomainRuleException {
+
+    public GLAccountDuplicateException(final String glCode) {
+        super("error.msg.glaccount.glcode.duplicate", "General Ledger Account with GL code " + glCode + " is already present", glCode);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidClassificationException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidClassificationException.java
new file mode 100755
index 0000000..b4d6a2b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidClassificationException.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.accounting.glaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when trying to fetch accounts belonging to
+ * an Invalid Usage Type
+ */
+public class GLAccountInvalidClassificationException extends AbstractPlatformDomainRuleException {
+
+    public GLAccountInvalidClassificationException(final Integer usage) {
+        super("error.msg.glaccount.usage.invalid", "The following COA usage is invalid: " + usage);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidDeleteException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidDeleteException.java
new file mode 100755
index 0000000..e90fb5c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidDeleteException.java
@@ -0,0 +1,51 @@
+/**
+ * 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.glaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when a GL Account with a given GL Code of
+ * the particular type is already present
+ */
+public class GLAccountInvalidDeleteException extends AbstractPlatformDomainRuleException {
+
+    /*** Enum of reasons for invalid delete **/
+    public static enum GL_ACCOUNT_INVALID_DELETE_REASON {
+        TRANSANCTIONS_LOGGED, HAS_CHILDREN;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("TRANSANCTIONS_LOGGED")) {
+                return "This GL Account cannot be deleted as it has transactions logged against it";
+            } else if (name().toString().equalsIgnoreCase("HAS_CHILDREN")) { return "Cannot delete this Header GL Account without first deleting or reassinging its children"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("TRANSANCTIONS_LOGGED")) {
+                return "error.msg.glaccount.glcode.invalid.delete.transactions.logged";
+            } else if (name().toString().equalsIgnoreCase("HAS_CHILDREN")) { return "error.msg.glaccount.glcode.invalid.delete.has.children"; }
+            return name().toString();
+        }
+    }
+
+    public GLAccountInvalidDeleteException(final GL_ACCOUNT_INVALID_DELETE_REASON reason, final Long glAccountId) {
+        super(reason.errorCode(), reason.errorMessage(), glAccountId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidParentException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidParentException.java
new file mode 100755
index 0000000..34af779
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidParentException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.glaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when trying to map invalid parents to a GL
+ * account
+ */
+public class GLAccountInvalidParentException extends AbstractPlatformDomainRuleException {
+
+    public GLAccountInvalidParentException(final long glAccountId) {
+        super("error.msg.glaccount.parent.invalid", "The account with id " + glAccountId
+                + " is a 'Detail' account and cannot be used as a parent", glAccountId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidUpdateException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidUpdateException.java
new file mode 100755
index 0000000..70a7aed
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidUpdateException.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.accounting.glaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when an error is encountered during
+ * updating a GL Account
+ */
+public class GLAccountInvalidUpdateException extends AbstractPlatformDomainRuleException {
+
+    /*** Enum of reasons for invalid delete **/
+    public static enum GL_ACCOUNT_INVALID_UPDATE_REASON {
+        TRANSANCTIONS_LOGGED;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("TRANSANCTIONS_LOGGED")) { return "This Usage of this (detail) GL Account as it already has transactions logged against it"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("TRANSANCTIONS_LOGGED")) { return "error.msg.glaccount.glcode.invalid.update.transactions.logged"; }
+            return name().toString();
+        }
+    }
+
+    public GLAccountInvalidUpdateException(final GL_ACCOUNT_INVALID_UPDATE_REASON reason, final Long glAccountId) {
+        super(reason.errorCode(), reason.errorMessage(), glAccountId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidUsageException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidUsageException.java
new file mode 100755
index 0000000..724f8d9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountInvalidUsageException.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.accounting.glaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when trying to fetch accounts belonging to
+ * an Invalid Type
+ */
+public class GLAccountInvalidUsageException extends AbstractPlatformDomainRuleException {
+
+    public GLAccountInvalidUsageException(final Integer type) {
+        super("error.msg.glaccount.classification.invalid", "The following COA type is invalid: " + type);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountNotFoundException.java
new file mode 100755
index 0000000..0262cec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.glaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when GL account resources are not found.
+ */
+public class GLAccountNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public GLAccountNotFoundException(final Long id) {
+        super("error.msg.glaccount.id.invalid", "General Ledger account with identifier " + id + " does not exist ", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/InvalidParentGLAccountHeadException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/InvalidParentGLAccountHeadException.java
new file mode 100644
index 0000000..fe4c759
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/InvalidParentGLAccountHeadException.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.accounting.glaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class InvalidParentGLAccountHeadException extends AbstractPlatformDomainRuleException {
+
+    public InvalidParentGLAccountHeadException(final Long glAccountId, final Long parentId) {
+        super("error.msg.glaccount.id.and.parentid.must.not.same", "parentId:" + parentId + ", id" + glAccountId + " should not be same",
+                glAccountId, parentId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/handler/CreateGLAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/handler/CreateGLAccountCommandHandler.java
new file mode 100755
index 0000000..90d8a54
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/handler/CreateGLAccountCommandHandler.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.accounting.glaccount.handler;
+
+import org.apache.fineract.accounting.glaccount.service.GLAccountWritePlatformService;
+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 = "GLACCOUNT", action = "CREATE")
+public class CreateGLAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final GLAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateGLAccountCommandHandler(final GLAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createGLAccount(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/handler/DeleteGLAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/handler/DeleteGLAccountCommandHandler.java
new file mode 100755
index 0000000..859d38c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/handler/DeleteGLAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.glaccount.handler;
+
+import org.apache.fineract.accounting.glaccount.service.GLAccountWritePlatformService;
+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 = "GLACCOUNT", action = "DELETE")
+public class DeleteGLAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final GLAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteGLAccountCommandHandler(final GLAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteGLAccount(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/handler/UpdateGLAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/handler/UpdateGLAccountCommandHandler.java
new file mode 100755
index 0000000..9112b35
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/handler/UpdateGLAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.glaccount.handler;
+
+import org.apache.fineract.accounting.glaccount.service.GLAccountWritePlatformService;
+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 = "GLACCOUNT", action = "UPDATE")
+public class UpdateGLAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final GLAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateGLAccountCommandHandler(final GLAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.updateGLAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/serialization/GLAccountCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/serialization/GLAccountCommandFromApiJsonDeserializer.java
new file mode 100755
index 0000000..76f588b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/serialization/GLAccountCommandFromApiJsonDeserializer.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.accounting.glaccount.serialization;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.glaccount.api.GLAccountJsonInputParams;
+import org.apache.fineract.accounting.glaccount.command.GLAccountCommand;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.guarantor.command.GuarantorCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link GuarantorCommand}'s.
+ */
+@Component
+public final class GLAccountCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<GLAccountCommand> {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public GLAccountCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonfromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonfromApiJsonHelper;
+    }
+
+    @Override
+    public GLAccountCommand commandFromApiJson(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        final Set<String> supportedParameters = GLAccountJsonInputParams.getAllValues();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long id = this.fromApiJsonHelper.extractLongNamed(GLAccountJsonInputParams.ID.getValue(), element);
+        final String name = this.fromApiJsonHelper.extractStringNamed(GLAccountJsonInputParams.NAME.getValue(), element);
+        final Long parentId = this.fromApiJsonHelper.extractLongNamed(GLAccountJsonInputParams.PARENT_ID.getValue(), element);
+        final String glCode = this.fromApiJsonHelper.extractStringNamed(GLAccountJsonInputParams.GL_CODE.getValue(), element);
+        final Boolean disabled = this.fromApiJsonHelper.extractBooleanNamed(GLAccountJsonInputParams.DISABLED.getValue(), element);
+        final Boolean manualEntriesAllowed = this.fromApiJsonHelper.extractBooleanNamed(
+                GLAccountJsonInputParams.MANUAL_ENTRIES_ALLOWED.getValue(), element);
+        final Integer type = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(GLAccountJsonInputParams.TYPE.getValue(), element);
+        final Integer usage = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(GLAccountJsonInputParams.USAGE.getValue(), element);
+        final String description = this.fromApiJsonHelper.extractStringNamed(GLAccountJsonInputParams.DESCRIPTION.getValue(), element);
+        final Long tagId = this.fromApiJsonHelper.extractLongNamed(GLAccountJsonInputParams.TAGID.getValue(), element);
+
+        return new GLAccountCommand(id, name, parentId, glCode, disabled, manualEntriesAllowed, type, usage, description, tagId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountReadPlatformService.java
new file mode 100644
index 0000000..0e983d0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountReadPlatformService.java
@@ -0,0 +1,44 @@
+/**
+ * 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.glaccount.service;
+
+import java.util.List;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
+
+public interface GLAccountReadPlatformService {
+
+    List<GLAccountData> retrieveAllGLAccounts(Integer accountClassification, String searchParam, Integer usage,
+            Boolean manualTransactionsAllowed, Boolean disabled, JournalEntryAssociationParametersData associationParametersData);
+
+    GLAccountData retrieveGLAccountById(long glAccountId, JournalEntryAssociationParametersData associationParametersData);
+
+    List<GLAccountData> retrieveAllEnabledDetailGLAccounts();
+
+    List<GLAccountData> retrieveAllEnabledDetailGLAccounts(GLAccountType accountType);
+
+    List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(GLAccountType accountType);
+
+    GLAccountData retrieveNewGLAccountDetails(final Integer type);
+
+    List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountReadPlatformServiceImpl.java
new file mode 100755
index 0000000..d7169dd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountReadPlatformServiceImpl.java
@@ -0,0 +1,281 @@
+/**
+ * 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.glaccount.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidClassificationException;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+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.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 GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final static String nameDecoratedBaseOnHierarchy = "concat(substring('........................................', 1, ((LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '.', '')) - 1) * 4)), name)";
+
+    @Autowired
+    public GLAccountReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class GLAccountMapper implements RowMapper<GLAccountData> {
+
+        private final JournalEntryAssociationParametersData associationParametersData;
+
+        public GLAccountMapper(final JournalEntryAssociationParametersData associationParametersData) {
+            if (associationParametersData == null) {
+                this.associationParametersData = new JournalEntryAssociationParametersData();
+            } else {
+                this.associationParametersData = associationParametersData;
+            }
+        }
+
+        public String schema() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(
+                    " gl.id as id, name as name, parent_id as parentId, gl_code as glCode, disabled as disabled, manual_journal_entries_allowed as manualEntriesAllowed, ")
+                    .append("classification_enum as classification, account_usage as accountUsage, gl.description as description, ")
+                    .append(nameDecoratedBaseOnHierarchy).append(" as nameDecorated, ")
+                    .append("cv.id as codeId, cv.code_value as codeValue ");
+            if (this.associationParametersData.isRunningBalanceRequired()) {
+                sb.append(",gl_j.organization_running_balance as organizationRunningBalance ");
+            }
+            sb.append("from acc_gl_account gl left join m_code_value cv on tag_id=cv.id ");
+            if (this.associationParametersData.isRunningBalanceRequired()) {
+                sb.append("left outer Join acc_gl_journal_entry gl_j on gl_j.account_id = gl.id");
+            }
+            return sb.toString();
+        }
+
+        @Override
+        public GLAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final Long parentId = JdbcSupport.getLong(rs, "parentId");
+            final String glCode = rs.getString("glCode");
+            final boolean disabled = rs.getBoolean("disabled");
+            final boolean manualEntriesAllowed = rs.getBoolean("manualEntriesAllowed");
+            final int accountTypeId = JdbcSupport.getInteger(rs, "classification");
+            final EnumOptionData accountType = AccountingEnumerations.gLAccountType(accountTypeId);
+            final int usageId = JdbcSupport.getInteger(rs, "accountUsage");
+            final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(usageId);
+            final String description = rs.getString("description");
+            final String nameDecorated = rs.getString("nameDecorated");
+            final Long codeId = rs.wasNull() ? null : rs.getLong("codeId");
+            final String codeValue = rs.getString("codeValue");
+            final CodeValueData tagId = CodeValueData.instance(codeId, codeValue);
+            Long organizationRunningBalance = null;
+            if (associationParametersData.isRunningBalanceRequired()) {
+                organizationRunningBalance = rs.getLong("organizationRunningBalance");
+            }
+            return new GLAccountData(id, name, parentId, glCode, disabled, manualEntriesAllowed, accountType, usage, description,
+                    nameDecorated, tagId, organizationRunningBalance);
+        }
+    }
+
+    @Override
+    public List<GLAccountData> retrieveAllGLAccounts(final Integer accountClassification, final String searchParam, final Integer usage,
+            final Boolean manualTransactionsAllowed, final Boolean disabled, JournalEntryAssociationParametersData associationParametersData) {
+        if (accountClassification != null) {
+            if (!checkValidGLAccountType(accountClassification)) { throw new GLAccountInvalidClassificationException(accountClassification); }
+        }
+
+        if (usage != null) {
+            if (!checkValidGLAccountUsage(usage)) { throw new GLAccountInvalidClassificationException(accountClassification); }
+        }
+
+        final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
+        String sql = "select " + rm.schema();
+        // append SQL statement for fetching account totals
+        if (associationParametersData.isRunningBalanceRequired()) {
+            sql = sql + " and gl_j.id in (select t1.id from (select t2.account_id, max(t2.id) as id from "
+                    + "(select id, max(entry_date) as entry_date, account_id from acc_gl_journal_entry where is_running_balance_calculated = 1 "
+                    + "group by account_id desc) t3 inner join acc_gl_journal_entry t2 on t2.account_id = t3.account_id and t2.entry_date = t3.entry_date "
+                    + "group by t2.account_id desc) t1)";
+        }
+        final Object[] paramaterArray = new Object[3];
+        int arrayPos = 0;
+        boolean filtersPresent = false;
+        if ((accountClassification != null) || StringUtils.isNotBlank(searchParam) || (usage != null)
+                || (manualTransactionsAllowed != null) || (disabled != null)) {
+            filtersPresent = true;
+            sql += " where";
+        }
+
+        if (filtersPresent) {
+            boolean firstWhereConditionAdded = false;
+            if (accountClassification != null) {
+                sql += " classification_enum like ?";
+                paramaterArray[arrayPos] = accountClassification;
+                arrayPos = arrayPos + 1;
+                firstWhereConditionAdded = true;
+            }
+            if (StringUtils.isNotBlank(searchParam)) {
+                if (firstWhereConditionAdded) {
+                    sql += " and ";
+                }
+                sql += " ( name like %?% or gl_code like %?% )";
+                paramaterArray[arrayPos] = searchParam;
+                arrayPos = arrayPos + 1;
+                paramaterArray[arrayPos] = searchParam;
+                arrayPos = arrayPos + 1;
+                firstWhereConditionAdded = true;
+            }
+            if (usage != null) {
+                if (firstWhereConditionAdded) {
+                    sql += " and ";
+                }
+                if (GLAccountUsage.HEADER.getValue().equals(usage)) {
+                    sql += " account_usage = 2 ";
+                } else if (GLAccountUsage.DETAIL.getValue().equals(usage)) {
+                    sql += " account_usage = 1 ";
+                }
+                firstWhereConditionAdded = true;
+            }
+            if (manualTransactionsAllowed != null) {
+                if (firstWhereConditionAdded) {
+                    sql += " and ";
+                }
+
+                if (manualTransactionsAllowed) {
+                    sql += " manual_journal_entries_allowed = 1";
+                } else {
+                    sql += " manual_journal_entries_allowed = 0";
+                }
+                firstWhereConditionAdded = true;
+            }
+            if (disabled != null) {
+                if (firstWhereConditionAdded) {
+                    sql += " and ";
+                }
+
+                if (disabled) {
+                    sql += " disabled = 1";
+                } else {
+                    sql += " disabled = 0";
+                }
+                firstWhereConditionAdded = true;
+            }
+        }
+
+        final Object[] finalObjectArray = Arrays.copyOf(paramaterArray, arrayPos);
+        return this.jdbcTemplate.query(sql, rm, finalObjectArray);
+    }
+
+    @Override
+    public GLAccountData retrieveGLAccountById(final long glAccountId, JournalEntryAssociationParametersData associationParametersData) {
+        try {
+
+            final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
+            final StringBuilder sql = new StringBuilder();
+            sql.append("select ").append(rm.schema());
+            if (associationParametersData.isRunningBalanceRequired()) {
+                sql.append(" and gl_j.is_running_balance_calculated = 1 ");
+            }
+            sql.append("where gl.id = ?");
+            if (associationParametersData.isRunningBalanceRequired()) {
+                sql.append("  ORDER BY gl_j.entry_date DESC,gl_j.id DESC LIMIT 1");
+            }
+            final GLAccountData glAccountData = this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
+
+            return glAccountData;
+        } catch (final EmptyResultDataAccessException e) {
+            throw new GLAccountNotFoundException(glAccountId);
+        }
+    }
+
+    @Override
+    public List<GLAccountData> retrieveAllEnabledDetailGLAccounts(final GLAccountType accountType) {
+        return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.DETAIL.getValue(), null, false,
+                new JournalEntryAssociationParametersData());
+    }
+
+    @Override
+    public List<GLAccountData> retrieveAllEnabledDetailGLAccounts() {
+        return retrieveAllGLAccounts(null, null, GLAccountUsage.DETAIL.getValue(), null, false, new JournalEntryAssociationParametersData());
+    }
+
+    private static boolean checkValidGLAccountType(final int type) {
+        for (final GLAccountType accountType : GLAccountType.values()) {
+            if (accountType.getValue().equals(type)) { return true; }
+        }
+        return false;
+    }
+
+    private static boolean checkValidGLAccountUsage(final int type) {
+        for (final GLAccountUsage accountUsage : GLAccountUsage.values()) {
+            if (accountUsage.getValue().equals(type)) { return true; }
+        }
+        return false;
+    }
+
+    @Override
+    public GLAccountData retrieveNewGLAccountDetails(final Integer type) {
+        return GLAccountData.sensibleDefaultsForNewGLAccountCreation(type);
+    }
+
+    @Override
+    public List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(final GLAccountType accountType) {
+        return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.HEADER.getValue(), null, false,
+                new JournalEntryAssociationParametersData());
+    }
+
+    @Override
+    public List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType) {
+        final GLAccountDataLookUpMapper mapper = new GLAccountDataLookUpMapper();
+        final String sql = "Select " + mapper.schema() + " where rule.id=? and tags.acc_type_enum=?";
+        return this.jdbcTemplate.query(sql, mapper, new Object[] { ruleId, transactionType });
+    }
+
+    private static final class GLAccountDataLookUpMapper implements RowMapper<GLAccountDataForLookup> {
+
+        public String schema() {
+            return " gl.id as id, gl.name as name, gl.gl_code as glCode from acc_accounting_rule rule join acc_rule_tags tags on tags.acc_rule_id = rule.id join acc_gl_account gl on gl.tag_id=tags.tag_id";
+        }
+
+        @Override
+        public GLAccountDataForLookup 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 glCode = rs.getString("glCode");
+            return new GLAccountDataForLookup(id, name, glCode);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformService.java
new file mode 100644
index 0000000..c68f8b2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformService.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.accounting.glaccount.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface GLAccountWritePlatformService {
+
+    CommandProcessingResult createGLAccount(JsonCommand command);
+
+    CommandProcessingResult updateGLAccount(Long glAccountId, JsonCommand command);
+
+    CommandProcessingResult deleteGLAccount(Long glAccountId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..4597c6a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,240 @@
+/**
+ * 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.glaccount.service;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingConstants;
+import org.apache.fineract.accounting.glaccount.api.GLAccountJsonInputParams;
+import org.apache.fineract.accounting.glaccount.command.GLAccountCommand;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountDuplicateException;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidDeleteException;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidParentException;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidUpdateException;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException;
+import org.apache.fineract.accounting.glaccount.exception.InvalidParentGLAccountHeadException;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidDeleteException.GL_ACCOUNT_INVALID_DELETE_REASON;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidUpdateException.GL_ACCOUNT_INVALID_UPDATE_REASON;
+import org.apache.fineract.accounting.glaccount.serialization.GLAccountCommandFromApiJsonDeserializer;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+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.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 GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccountWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(GLAccountWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final GLAccountRepository glAccountRepository;
+    private final JournalEntryRepository glJournalEntryRepository;
+    private final GLAccountCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
+
+    @Autowired
+    public GLAccountWritePlatformServiceJpaRepositoryImpl(final GLAccountRepository glAccountRepository,
+            final JournalEntryRepository glJournalEntryRepository, final GLAccountCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final CodeValueRepositoryWrapper codeValueRepositoryWrapper) {
+        this.glAccountRepository = glAccountRepository;
+        this.glJournalEntryRepository = glJournalEntryRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createGLAccount(final JsonCommand command) {
+        try {
+            final GLAccountCommand accountCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
+            accountCommand.validateForCreate();
+
+            // check parent is valid
+            final Long parentId = command.longValueOfParameterNamed(GLAccountJsonInputParams.PARENT_ID.getValue());
+            GLAccount parentGLAccount = null;
+            if (parentId != null) {
+                parentGLAccount = validateParentGLAccount(parentId);
+            }
+
+            CodeValue glAccountTagType = null;
+            final Long tagId = command.longValueOfParameterNamed(GLAccountJsonInputParams.TAGID.getValue());
+            final Long type = command.longValueOfParameterNamed(GLAccountJsonInputParams.TYPE.getValue());
+            final GLAccountType accountType = GLAccountType.fromInt(type.intValue());
+
+            if (tagId != null) {
+                glAccountTagType = retrieveTagId(tagId, accountType);
+            }
+
+            final GLAccount glAccount = GLAccount.fromJson(parentGLAccount, command, glAccountTagType);
+
+            this.glAccountRepository.saveAndFlush(glAccount);
+
+            glAccount.generateHierarchy();
+
+            this.glAccountRepository.save(glAccount);
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(glAccount.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleGLAccountDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateGLAccount(final Long glAccountId, final JsonCommand command) {
+        try {
+            final GLAccountCommand accountCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
+            accountCommand.validateForUpdate();
+
+            final Long parentId = command.longValueOfParameterNamed(GLAccountJsonInputParams.PARENT_ID.getValue());
+            if (glAccountId.equals(parentId)) { throw new InvalidParentGLAccountHeadException(glAccountId, parentId); }
+            // is the glAccount valid
+            final GLAccount glAccount = this.glAccountRepository.findOne(glAccountId);
+            if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); }
+
+            final Map<String, Object> changesOnly = glAccount.update(command);
+
+            // is the new parent valid
+            if (changesOnly.containsKey(GLAccountJsonInputParams.PARENT_ID.getValue())) {
+                final GLAccount parentAccount = validateParentGLAccount(parentId);
+                glAccount.updateParentAccount(parentAccount);
+            }
+
+            if (changesOnly.containsKey(GLAccountJsonInputParams.TAGID.getValue())) {
+                final Long tagIdLongValue = command.longValueOfParameterNamed(GLAccountJsonInputParams.TAGID.getValue());
+                final GLAccountType accountType = GLAccountType.fromInt(glAccount.getType());
+                CodeValue tagID = null;
+                if (tagIdLongValue != null) {
+                    tagID = retrieveTagId(tagIdLongValue, accountType);
+                }
+                glAccount.updateTagId(tagID);
+            }
+
+            /**
+             * a detail account cannot be changed to a header account if
+             * transactions are already logged against it
+             **/
+            if (changesOnly.containsKey(GLAccountJsonInputParams.USAGE.getValue())) {
+                if (glAccount.isHeaderAccount()) {
+                    final List<JournalEntry> journalEntriesForAccount = this.glJournalEntryRepository
+                            .findFirstJournalEntryForAccount(glAccountId);
+                    if (journalEntriesForAccount.size() > 0) { throw new GLAccountInvalidUpdateException(
+                            GL_ACCOUNT_INVALID_UPDATE_REASON.TRANSANCTIONS_LOGGED, glAccountId); }
+                }
+            }
+
+            if (!changesOnly.isEmpty()) {
+                this.glAccountRepository.saveAndFlush(glAccount);
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(glAccount.getId())
+                    .with(changesOnly).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleGLAccountDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteGLAccount(final Long glAccountId) {
+        final GLAccount glAccount = this.glAccountRepository.findOne(glAccountId);
+
+        if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); }
+
+        // validate this isn't a header account that has children
+        if (glAccount.isHeaderAccount() && glAccount.getChildren().size() > 0) { throw new GLAccountInvalidDeleteException(
+                GL_ACCOUNT_INVALID_DELETE_REASON.HAS_CHILDREN, glAccountId); }
+
+        // does this account have transactions logged against it
+        final List<JournalEntry> journalEntriesForAccount = this.glJournalEntryRepository.findFirstJournalEntryForAccount(glAccountId);
+        if (journalEntriesForAccount.size() > 0) { throw new GLAccountInvalidDeleteException(
+                GL_ACCOUNT_INVALID_DELETE_REASON.TRANSANCTIONS_LOGGED, glAccountId); }
+        this.glAccountRepository.delete(glAccount);
+
+        return new CommandProcessingResultBuilder().withEntityId(glAccountId).build();
+    }
+
+    /**
+     * @param command
+     * @return
+     */
+    private GLAccount validateParentGLAccount(final Long parentAccountId) {
+        GLAccount parentGLAccount = null;
+        if (parentAccountId != null) {
+            parentGLAccount = this.glAccountRepository.findOne(parentAccountId);
+            if (parentGLAccount == null) { throw new GLAccountNotFoundException(parentAccountId); }
+            // ensure parent is not a detail account
+            if (parentGLAccount.isDetailAccount()) { throw new GLAccountInvalidParentException(parentAccountId); }
+        }
+        return parentGLAccount;
+    }
+
+    /**
+     * @param command
+     * @param dve
+     */
+    private void handleGLAccountDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("acc_gl_code")) {
+            final String glCode = command.stringValueOfParameterNamed(GLAccountJsonInputParams.GL_CODE.getValue());
+            throw new GLAccountDuplicateException(glCode);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.glAccount.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource GL Account: " + realCause.getMessage());
+    }
+
+    private CodeValue retrieveTagId(final Long tagId, final GLAccountType accountType) {
+        CodeValue glAccountTagType = null;
+        if (accountType.isAssetType()) {
+            glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
+                    AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME, tagId);
+        } else if (accountType.isLiabilityType()) {
+            glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
+                    AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME, tagId);
+        } else if (accountType.isEquityType()) {
+            glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
+                    AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME, tagId);
+        } else if (accountType.isIncomeType()) {
+            glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
+                    AccountingConstants.INCOME_TAG_OPTION_CODE_NAME, tagId);
+        } else if (accountType.isExpenseType()) {
+            glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
+                    AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME, tagId);
+        }
+        return glAccountTagType;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.java
new file mode 100755
index 0000000..cbc0882
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.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.accounting.journalentry.api;
+
+import java.util.Date;
+import java.util.Locale;
+
+import javax.ws.rs.WebApplicationException;
+
+import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
+import org.joda.time.LocalDate;
+
+/**
+ * Class for parsing dates sent as query parameters
+ * 
+ * TODO: Vishwas Should move this class to a more generic package
+ */
+public class DateParam {
+
+    private final String dateAsString;
+
+    public DateParam(final String dateStr) throws WebApplicationException {
+        this.dateAsString = dateStr;
+    }
+
+    public Date getDate(final String parameterName, final String dateFormat, final String localeAsString) {
+        final Locale locale = JsonParserHelper.localeFromString(localeAsString);
+        final LocalDate localDate = JsonParserHelper.convertFrom(this.dateAsString, parameterName, dateFormat, locale);
+        return localDate.toDate();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
new file mode 100755
index 0000000..9e66b0b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
@@ -0,0 +1,211 @@
+/**
+ * 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.api;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
+import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData;
+import org.apache.fineract.accounting.journalentry.service.JournalEntryReadPlatformService;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
+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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+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("/journalentries")
+@Component
+@Scope("singleton")
+public class JournalEntriesApiResource {
+
+    private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "officeId", "officeName",
+            "glAccountName", "glAccountId", "glAccountCode", "glAccountType", "transactionDate", "entryType", "amount", "transactionId",
+            "manualEntry", "entityType", "entityId", "createdByUserId", "createdDate", "createdByUserName", "comments", "reversed",
+            "referenceNumber", "currency", "transactionDetails"));
+
+    private final String resourceNameForPermission = "JOURNALENTRY";
+
+    private final JournalEntryReadPlatformService journalEntryReadPlatformService;
+    private final DefaultToApiJsonSerializer<Object> apiJsonSerializerService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PlatformSecurityContext context;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public JournalEntriesApiResource(final PlatformSecurityContext context,
+            final JournalEntryReadPlatformService journalEntryReadPlatformService,
+            final DefaultToApiJsonSerializer<Object> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiJsonSerializerService = toApiJsonSerializer;
+        this.journalEntryReadPlatformService = journalEntryReadPlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId,
+            @QueryParam("glAccountId") final Long glAccountId, @QueryParam("manualEntriesOnly") final Boolean onlyManualEntries,
+            @QueryParam("fromDate") final DateParam fromDateParam, @QueryParam("toDate") final DateParam toDateParam,
+            @QueryParam("transactionId") final String transactionId, @QueryParam("entityType") final Integer entityType,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder,
+            @QueryParam("locale") final String locale, @QueryParam("dateFormat") final String dateFormat,
+            @QueryParam("loanId") final Long loanId, @QueryParam("savingsId") final Long savingsId,
+            @QueryParam("runningBalance") final boolean runningBalance, @QueryParam("transactionDetails") final boolean transactionDetails) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        Date fromDate = null;
+        if (fromDateParam != null) {
+            fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
+        }
+        Date toDate = null;
+        if (toDateParam != null) {
+            toDate = toDateParam.getDate("toDate", dateFormat, locale);
+        }
+
+        final SearchParameters searchParameters = SearchParameters.forJournalEntries(officeId, offset, limit, orderBy, sortOrder, loanId,
+                savingsId);
+        JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(transactionDetails,
+                runningBalance);
+
+        final Page<JournalEntryData> glJournalEntries = this.journalEntryReadPlatformService.retrieveAll(searchParameters, glAccountId,
+                onlyManualEntries, fromDate, toDate, transactionId, entityType, associationParametersData);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, glJournalEntries, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{journalEntryId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retreiveJournalEntryById(@PathParam("journalEntryId") final Long journalEntryId, @Context final UriInfo uriInfo,
+            @QueryParam("runningBalance") final boolean runningBalance, @QueryParam("transactionDetails") final boolean transactionDetails) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+        JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(transactionDetails,
+                runningBalance);
+        final JournalEntryData glJournalEntryData = this.journalEntryReadPlatformService.retrieveGLJournalEntryById(journalEntryId,
+                associationParametersData);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, glJournalEntryData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createGLJournalEntry(final String jsonRequestBody, @QueryParam("command") final String commandParam) {
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "updateRunningBalance")) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRunningBalanceForJournalEntry()
+                    .withJson(jsonRequestBody).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "defineOpeningBalance")) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().defineOpeningBalanceForJournalEntry()
+                    .withJson(jsonRequestBody).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().createJournalEntry().withJson(jsonRequestBody).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @POST
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createReversalJournalEntry(final String jsonRequestBody, @PathParam("transactionId") final String transactionId,
+            @QueryParam("command") final String commandParam) {
+        CommandProcessingResult result = null;
+        if (is(commandParam, "reverse")) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().reverseJournalEntry(transactionId).withJson(jsonRequestBody)
+                    .build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam);
+        }
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @GET
+    @Path("provisioning")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveJournalEntries(@QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("entryId") final Long entryId, @Context final UriInfo uriInfo) {
+        this.context.authenticatedUser();
+        String transactionId = "P"+entryId ;
+        SearchParameters params = SearchParameters.forPagination(offset, limit) ;
+                Page<JournalEntryData> entries = this.journalEntryReadPlatformService.retrieveAll(params, null, null, null, null, transactionId, PortfolioProductType.PROVISIONING.getValue(), null) ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, entries, RESPONSE_DATA_PARAMETERS);    
+    }
+    
+    
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Path("openingbalance")
+    public String retrieveOpeningBalance(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId,
+            @QueryParam("currencyCode") final String currencyCode) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+        final OfficeOpeningBalancesData officeOpeningBalancesData = this.journalEntryReadPlatformService.retrieveOfficeOpeningBalances(
+                officeId, currencyCode);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, officeOpeningBalancesData);
+    }
+
+    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/accounting/journalentry/api/JournalEntryJsonInputParams.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntryJsonInputParams.java
new file mode 100755
index 0000000..95a7e27
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntryJsonInputParams.java
@@ -0,0 +1,58 @@
+/**
+ * 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.api;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/***
+ * Enum of all parameters passed in while creating/updating a journal Entry
+ ***/
+public enum JournalEntryJsonInputParams {
+    OFFICE_ID("officeId"), TRANSACTION_DATE("transactionDate"), COMMENTS("comments"), CREDITS("credits"), DEBITS("debits"), LOCALE("locale"), DATE_FORMAT(
+            "dateFormat"), REFERENCE_NUMBER("referenceNumber"), USE_ACCOUNTING_RULE("useAccountingRule"), ACCOUNTING_RULE("accountingRule"), AMOUNT(
+            "amount"), CURRENCY_CODE("currencyCode"), PAYMENT_TYPE_ID("paymentTypeId"), ACCOUNT_NUMBER("accountNumber"), CHECK_NUMBER(
+            "checkNumber"), ROUTING_CODE("routingCode"), RECEIPT_NUMBER("receiptNumber"), BANK_NUMBER("bankNumber");
+
+    private final String value;
+
+    private JournalEntryJsonInputParams(final String value) {
+        this.value = value;
+    }
+
+    private static final Set<String> values = new HashSet<>();
+    static {
+        for (final JournalEntryJsonInputParams type : JournalEntryJsonInputParams.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/accounting/journalentry/command/JournalEntryCommand.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/command/JournalEntryCommand.java
new file mode 100755
index 0000000..1941bcc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/command/JournalEntryCommand.java
@@ -0,0 +1,172 @@
+/**
+ * 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.command;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.accounting.journalentry.api.JournalEntryJsonInputParams;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable command for adding an accounting closure
+ */
+public class JournalEntryCommand {
+
+    private final Long officeId;
+    private final LocalDate transactionDate;
+    private final String currencyCode;
+    private final String comments;
+    private final String referenceNumber;
+    private final Long accountingRuleId;
+    private final BigDecimal amount;
+    private final Long paymentTypeId;
+    @SuppressWarnings("unused")
+    private final String accountNumber;
+    @SuppressWarnings("unused")
+    private final String checkNumber;
+    @SuppressWarnings("unused")
+    private final String receiptNumber;
+    @SuppressWarnings("unused")
+    private final String bankNumber;
+    @SuppressWarnings("unused")
+    private final String routingCode;
+
+    private final SingleDebitOrCreditEntryCommand[] credits;
+    private final SingleDebitOrCreditEntryCommand[] debits;
+
+    public JournalEntryCommand(final Long officeId, final String currencyCode, final LocalDate transactionDate, final String comments,
+            final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits, final String referenceNumber,
+            final Long accountingRuleId, final BigDecimal amount, final Long paymentTypeId, final String accountNumber,
+            final String checkNumber, final String receiptNumber, final String bankNumber, final String routingCode) {
+        this.officeId = officeId;
+        this.currencyCode = currencyCode;
+        this.transactionDate = transactionDate;
+        this.comments = comments;
+        this.credits = credits;
+        this.debits = debits;
+        this.referenceNumber = referenceNumber;
+        this.accountingRuleId = accountingRuleId;
+        this.amount = amount;
+        this.paymentTypeId = paymentTypeId;
+        this.accountNumber = accountNumber;
+        this.checkNumber = checkNumber;
+        this.receiptNumber = receiptNumber;
+        this.bankNumber = bankNumber;
+        this.routingCode = routingCode;
+
+    }
+
+    public void validateForCreate() {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("GLJournalEntry");
+
+        baseDataValidator.reset().parameter("transactionDate").value(this.transactionDate).notBlank();
+
+        baseDataValidator.reset().parameter("officeId").value(this.officeId).notNull().integerGreaterThanZero();
+
+        baseDataValidator.reset().parameter(JournalEntryJsonInputParams.CURRENCY_CODE.getValue()).value(this.currencyCode).notBlank();
+
+        baseDataValidator.reset().parameter("comments").value(this.comments).ignoreIfNull().notExceedingLengthOf(500);
+
+        baseDataValidator.reset().parameter("referenceNumber").value(this.referenceNumber).ignoreIfNull().notExceedingLengthOf(100);
+
+        baseDataValidator.reset().parameter("accountingRule").value(this.accountingRuleId).ignoreIfNull().longGreaterThanZero();
+
+        baseDataValidator.reset().parameter("paymentTypeId").value(this.paymentTypeId).ignoreIfNull().longGreaterThanZero();
+
+        // validation for credit array elements
+        if (this.credits != null) {
+            if (this.credits.length == 0) {
+                validateSingleDebitOrCredit(baseDataValidator, "credits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null));
+            } else {
+                int i = 0;
+                for (final SingleDebitOrCreditEntryCommand credit : this.credits) {
+                    validateSingleDebitOrCredit(baseDataValidator, "credits", i, credit);
+                    i++;
+                }
+            }
+        }
+
+        // validation for debit array elements
+        if (this.debits != null) {
+            if (this.debits.length == 0) {
+                validateSingleDebitOrCredit(baseDataValidator, "debits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null));
+            } else {
+                int i = 0;
+                for (final SingleDebitOrCreditEntryCommand debit : this.debits) {
+                    validateSingleDebitOrCredit(baseDataValidator, "debits", i, debit);
+                    i++;
+                }
+            }
+        }
+        baseDataValidator.reset().parameter("amount").value(this.amount).ignoreIfNull().zeroOrPositiveAmount();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    /**
+     * @param baseDataValidator
+     * @param i
+     * @param credit
+     */
+    private void validateSingleDebitOrCredit(final DataValidatorBuilder baseDataValidator, final String paramSuffix, final int arrayPos,
+            final SingleDebitOrCreditEntryCommand credit) {
+        baseDataValidator.reset().parameter(paramSuffix + "[" + arrayPos + "].glAccountId").value(credit.getGlAccountId()).notNull()
+                .integerGreaterThanZero();
+        baseDataValidator.reset().parameter(paramSuffix + "[" + arrayPos + "].amount").value(credit.getAmount()).notNull()
+                .zeroOrPositiveAmount();
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public LocalDate getTransactionDate() {
+        return this.transactionDate;
+    }
+
+    public String getComments() {
+        return this.comments;
+    }
+
+    public SingleDebitOrCreditEntryCommand[] getCredits() {
+        return this.credits;
+    }
+
+    public SingleDebitOrCreditEntryCommand[] getDebits() {
+        return this.debits;
+    }
+
+    public String getReferenceNumber() {
+        return this.referenceNumber;
+    }
+
+    public Long getAccountingRuleId() {
+        return this.accountingRuleId;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/command/SingleDebitOrCreditEntryCommand.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/command/SingleDebitOrCreditEntryCommand.java
new file mode 100755
index 0000000..fe972e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/command/SingleDebitOrCreditEntryCommand.java
@@ -0,0 +1,71 @@
+/**
+ * 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.command;
+
+import java.math.BigDecimal;
+import java.util.Set;
+
+/**
+ * Immutable command for adding a "Credit" entry to the Journal
+ */
+public class SingleDebitOrCreditEntryCommand {
+
+    private final Long glAccountId;
+    private final BigDecimal amount;
+    private final String comments;
+
+    private final Set<String> parametersPassedInRequest;
+
+    public SingleDebitOrCreditEntryCommand(final Set<String> parametersPassedInRequest, final Long glAccountId, final BigDecimal amount,
+            final String comments) {
+        this.parametersPassedInRequest = parametersPassedInRequest;
+        this.glAccountId = glAccountId;
+        this.amount = amount;
+        this.comments = comments;
+    }
+
+    public boolean isGlAccountIdChanged() {
+        return this.parametersPassedInRequest.contains("glAccountId");
+    }
+
+    public boolean isGlAmountChanged() {
+        return this.parametersPassedInRequest.contains("amount");
+    }
+
+    public boolean isCommentsChanged() {
+        return this.parametersPassedInRequest.contains("comments");
+    }
+
+    public Long getGlAccountId() {
+        return this.glAccountId;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public String getComments() {
+        return this.comments;
+    }
+
+    public Set<String> getParametersPassedInRequest() {
+        return this.parametersPassedInRequest;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ChargePaymentDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ChargePaymentDTO.java
new file mode 100755
index 0000000..27efe3b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ChargePaymentDTO.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.accounting.journalentry.data;
+
+import java.math.BigDecimal;
+
+public class ChargePaymentDTO {
+
+    private final Long chargeId;
+    private final BigDecimal amount;
+    private final Long loanChargeId;
+
+    public ChargePaymentDTO(final Long chargeId, final Long loanChargeId, final BigDecimal amount) {
+        this.chargeId = chargeId;
+        this.amount = amount;
+        this.loanChargeId = loanChargeId;
+    }
+
+    public Long getChargeId() {
+        return this.chargeId;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public Long getLoanChargeId() {
+        return this.loanChargeId;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ClientChargePaymentDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ClientChargePaymentDTO.java
new file mode 100644
index 0000000..9d9209f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ClientChargePaymentDTO.java
@@ -0,0 +1,60 @@
+/**
+ * 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.data;
+
+import java.math.BigDecimal;
+
+public class ClientChargePaymentDTO {
+
+    private final Long chargeId;
+    private final BigDecimal amount;
+    private final Long clientChargeId;
+    private final boolean isPenalty;
+    private final Long incomeAccountId;
+
+    public ClientChargePaymentDTO(Long chargeId, BigDecimal amount, Long clientChargeId, boolean isPenalty, Long incomeAccountId) {
+        super();
+        this.chargeId = chargeId;
+        this.amount = amount;
+        this.clientChargeId = clientChargeId;
+        this.isPenalty = isPenalty;
+        this.incomeAccountId = incomeAccountId;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public Long getClientChargeId() {
+        return this.clientChargeId;
+    }
+
+    public boolean isPenalty() {
+        return this.isPenalty;
+    }
+
+    public Long getChargeId() {
+        return chargeId;
+    }
+
+    public Long getIncomeAccountId() {
+        return this.incomeAccountId;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ClientTransactionDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ClientTransactionDTO.java
new file mode 100644
index 0000000..0165ecb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ClientTransactionDTO.java
@@ -0,0 +1,113 @@
+/**
+ * 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.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.client.domain.ClientTransactionType;
+
+public class ClientTransactionDTO {
+
+    private final Long officeId;
+    private final Long clientId;
+    private String currencyCode;
+
+    private final Long transactionId;
+    private final Date transactionDate;
+    private final Long paymentTypeId;
+    private final EnumOptionData transactionType;
+
+    private final BigDecimal amount;
+
+    private final Boolean accountingEnabled;
+
+    /*** Boolean values determines if the transaction is reversed ***/
+    private final boolean reversed;
+
+    /** Breakdowns of fees this Transaction pays **/
+    private final List<ClientChargePaymentDTO> chargePayments;
+
+    public ClientTransactionDTO(final Long clientId, final Long officeId, final Long paymentTypeId, final Long transactionId,
+            final Date transactionDate, final EnumOptionData transactionType, final String currencyCode, final BigDecimal amount,
+            final boolean reversed, final boolean accountingEnabled, final List<ClientChargePaymentDTO> clientChargePayments) {
+        this.clientId = clientId;
+        this.paymentTypeId = paymentTypeId;
+        this.transactionId = transactionId;
+        this.transactionDate = transactionDate;
+        this.amount = amount;
+        this.reversed = reversed;
+        this.transactionType = transactionType;
+        this.chargePayments = clientChargePayments;
+        this.officeId = officeId;
+        this.accountingEnabled = accountingEnabled;
+        this.currencyCode = currencyCode;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public Date getTransactionDate() {
+        return this.transactionDate;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public boolean isReversed() {
+        return this.reversed;
+    }
+
+    public Long getPaymentTypeId() {
+        return this.paymentTypeId;
+    }
+
+    public EnumOptionData getTransactionType() {
+        return transactionType;
+    }
+
+    public boolean isChargePayment() {
+        return ClientTransactionType.PAY_CHARGE.getValue().equals(new Integer(this.transactionType.getId().intValue()));
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+
+    public List<ClientChargePaymentDTO> getChargePayments() {
+        return this.chargePayments;
+    }
+
+    public Long getTransactionId() {
+        return this.transactionId;
+    }
+
+    public Boolean getAccountingEnabled() {
+        return this.accountingEnabled;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryAssociationParametersData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryAssociationParametersData.java
new file mode 100755
index 0000000..522be40
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryAssociationParametersData.java
@@ -0,0 +1,43 @@
+/**
+ * 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.data;
+
+public class JournalEntryAssociationParametersData {
+
+    private final boolean transactionDetailsRequired;
+    private final boolean runningBalanceRequired;
+
+    public JournalEntryAssociationParametersData() {
+        this.transactionDetailsRequired = false;
+        this.runningBalanceRequired = false;
+    }
+
+    public JournalEntryAssociationParametersData(final boolean transactionDetailsRequired, final boolean runningBalanceRequired) {
+        this.transactionDetailsRequired = transactionDetailsRequired;
+        this.runningBalanceRequired = runningBalanceRequired;
+    }
+
+    public boolean isTransactionDetailsRequired() {
+        return this.transactionDetailsRequired;
+    }
+
+    public boolean isRunningBalanceRequired() {
+        return this.runningBalanceRequired;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java
new file mode 100755
index 0000000..b6bb15d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java
@@ -0,0 +1,175 @@
+/**
+ * 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.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable object representing a General Ledger Account
+ * 
+ * Note: no getter/setters required as google will produce json from fields of
+ * object.
+ */
+public class JournalEntryData {
+
+    private final Long id;
+    private final Long officeId;
+    @SuppressWarnings("unused")
+    private final String officeName;
+    @SuppressWarnings("unused")
+    private final String glAccountName;
+    private final Long glAccountId;
+    @SuppressWarnings("unused")
+    private final String glAccountCode;
+    private final EnumOptionData glAccountType;
+    @SuppressWarnings("unused")
+    private final LocalDate transactionDate;
+    private final EnumOptionData entryType;
+    private final BigDecimal amount;
+    @SuppressWarnings("unused")
+    private final CurrencyData currency;
+    private final String transactionId;
+    @SuppressWarnings("unused")
+    private final Boolean manualEntry;
+    @SuppressWarnings("unused")
+    private final EnumOptionData entityType;
+    @SuppressWarnings("unused")
+    private final Long entityId;
+    @SuppressWarnings("unused")
+    private final Long createdByUserId;
+    @SuppressWarnings("unused")
+    private final LocalDate createdDate;
+    @SuppressWarnings("unused")
+    private final String createdByUserName;
+    @SuppressWarnings("unused")
+    private final String comments;
+    @SuppressWarnings("unused")
+    private final Boolean reversed;
+    @SuppressWarnings("unused")
+    private final String referenceNumber;
+    @SuppressWarnings("unused")
+    private final BigDecimal officeRunningBalance;
+    @SuppressWarnings("unused")
+    private final BigDecimal organizationRunningBalance;
+    @SuppressWarnings("unused")
+    private final Boolean runningBalanceComputed;
+
+    @SuppressWarnings("unused")
+    private final TransactionDetailData transactionDetails;
+
+    public JournalEntryData(final Long id, final Long officeId, final String officeName, final String glAccountName,
+            final Long glAccountId, final String glAccountCode, final EnumOptionData glAccountClassification,
+            final LocalDate transactionDate, final EnumOptionData entryType, final BigDecimal amount, final String transactionId,
+            final Boolean manualEntry, final EnumOptionData entityType, final Long entityId, final Long createdByUserId,
+            final LocalDate createdDate, final String createdByUserName, final String comments, final Boolean reversed,
+            final String referenceNumber, final BigDecimal officeRunningBalance, final BigDecimal organizationRunningBalance,
+            final Boolean runningBalanceComputed, final TransactionDetailData transactionDetailData, final CurrencyData currency) {
+        this.id = id;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.glAccountName = glAccountName;
+        this.glAccountId = glAccountId;
+        this.glAccountCode = glAccountCode;
+        this.glAccountType = glAccountClassification;
+        this.transactionDate = transactionDate;
+        this.entryType = entryType;
+        this.amount = amount;
+        this.transactionId = transactionId;
+        this.manualEntry = manualEntry;
+        this.entityType = entityType;
+        this.entityId = entityId;
+        this.createdByUserId = createdByUserId;
+        this.createdDate = createdDate;
+        this.createdByUserName = createdByUserName;
+        this.comments = comments;
+        this.reversed = reversed;
+        this.referenceNumber = referenceNumber;
+        this.officeRunningBalance = officeRunningBalance;
+        this.organizationRunningBalance = organizationRunningBalance;
+        this.runningBalanceComputed = runningBalanceComputed;
+        this.transactionDetails = transactionDetailData;
+        this.currency = currency;
+    }
+
+    public static JournalEntryData fromGLAccountData(final GLAccountData glAccountData) {
+
+        final Long id = null;
+        final Long officeId = null;
+        final String officeName = null;
+        final String glAccountName = glAccountData.getName();
+        final Long glAccountId = glAccountData.getId();
+        final String glAccountCode = glAccountData.getGlCode();
+        final EnumOptionData glAccountClassification = glAccountData.getType();
+        final LocalDate transactionDate = null;
+        final EnumOptionData entryType = null;
+        final BigDecimal amount = null;
+        final String transactionId = null;
+        final Boolean manualEntry = null;
+        final EnumOptionData entityType = null;
+        final Long entityId = null;
+        final Long createdByUserId = null;
+        final LocalDate createdDate = null;
+        final String createdByUserName = null;
+        final String comments = null;
+        final Boolean reversed = null;
+        final String referenceNumber = null;
+        final BigDecimal officeRunningBalance = null;
+        final BigDecimal organizationRunningBalance = null;
+        final Boolean runningBalanceComputed = null;
+        final TransactionDetailData transactionDetailData = null;
+        final CurrencyData currency = null;
+        return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glAccountCode, glAccountClassification,
+                transactionDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, createdDate,
+                createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance,
+                runningBalanceComputed, transactionDetailData, currency);
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public Long getGlAccountId() {
+        return this.glAccountId;
+    }
+
+    public EnumOptionData getGlAccountType() {
+        return this.glAccountType;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public EnumOptionData getEntryType() {
+        return this.entryType;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public String getTransactionId() {
+        return transactionId;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryDataValidator.java
new file mode 100755
index 0000000..240ca9b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryDataValidator.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.journalentry.data;
+
+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.fineract.accounting.journalentry.api.JournalEntryJsonInputParams;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.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.reflect.TypeToken;
+
+@Component
+public class JournalEntryDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    public static final Set<String> RUNNING_BALANCE_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(JournalEntryJsonInputParams.OFFICE_ID.getValue()));
+
+    @Autowired
+    public JournalEntryDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForUpdateRunningbalance(final JsonCommand command) {
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, command.json(), RUNNING_BALANCE_UPDATE_REQUEST_DATA_PARAMETERS);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("GLJournalEntry");
+
+        if (this.fromApiJsonHelper.parameterExists(JournalEntryJsonInputParams.OFFICE_ID.getValue(), command.parsedJson())) {
+            final String officeId = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.OFFICE_ID.getValue(),
+                    command.parsedJson());
+            baseDataValidator.reset().parameter(JournalEntryJsonInputParams.OFFICE_ID.getValue()).value(officeId).ignoreIfNull()
+                    .longGreaterThanZero();
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryIdentifier.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryIdentifier.java
new file mode 100755
index 0000000..ce1ff79
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryIdentifier.java
@@ -0,0 +1,61 @@
+/**
+ * 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.data;
+
+/**
+ * Represents the successful result of an REST API call.
+ */
+public class JournalEntryIdentifier {
+
+    private String entityId;
+
+    // TODO - Rename variable to commandId or taskId or something that shows
+    // this is the id of a command in a table/queue for processing.
+    @SuppressWarnings("unused")
+    private Long makerCheckerId;
+
+    public static JournalEntryIdentifier makerChecker(final Long makerCheckerId) {
+        return new JournalEntryIdentifier(null, makerCheckerId);
+    }
+
+    public static JournalEntryIdentifier makerChecker(final String resourceId, final Long makerCheckerId) {
+        return new JournalEntryIdentifier(resourceId, makerCheckerId);
+    }
+
+    public JournalEntryIdentifier() {
+        //
+    }
+
+    public JournalEntryIdentifier(final String entityId) {
+        this.entityId = entityId;
+    }
+
+    private JournalEntryIdentifier(final String entityId, final Long makerCheckerId) {
+        this.entityId = entityId;
+        this.makerCheckerId = makerCheckerId;
+    }
+
+    public String getEntityId() {
+        return this.entityId;
+    }
+
+    public void setEntityId(final String entityId) {
+        this.entityId = entityId;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
new file mode 100755
index 0000000..152ffc4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
@@ -0,0 +1,104 @@
+/**
+ * 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.data;
+
+import java.util.List;
+
+public class LoanDTO {
+
+    private Long loanId;
+    private Long loanProductId;
+    private Long officeId;
+
+    private String currencyCode;
+    private boolean cashBasedAccountingEnabled;
+    final boolean upfrontAccrualBasedAccountingEnabled;
+    final boolean periodicAccrualBasedAccountingEnabled;
+    private List<LoanTransactionDTO> newLoanTransactions;
+
+    public LoanDTO(final Long loanId, final Long loanProductId, final Long officeId, final String currencyCode,
+            final boolean cashBasedAccountingEnabled, final boolean upfrontAccrualBasedAccountingEnabled,
+            final boolean periodicAccrualBasedAccountingEnabled, final List<LoanTransactionDTO> newLoanTransactions) {
+        this.loanId = loanId;
+        this.loanProductId = loanProductId;
+        this.officeId = officeId;
+        this.cashBasedAccountingEnabled = cashBasedAccountingEnabled;
+        this.newLoanTransactions = newLoanTransactions;
+        this.currencyCode = currencyCode;
+        this.upfrontAccrualBasedAccountingEnabled = upfrontAccrualBasedAccountingEnabled;
+        this.periodicAccrualBasedAccountingEnabled = periodicAccrualBasedAccountingEnabled;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public void setLoanId(final Long loanId) {
+        this.loanId = loanId;
+    }
+
+    public Long getLoanProductId() {
+        return this.loanProductId;
+    }
+
+    public void setLoanProductId(final Long loanProductId) {
+        this.loanProductId = loanProductId;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public void setOfficeId(final Long officeId) {
+        this.officeId = officeId;
+    }
+
+    public boolean isCashBasedAccountingEnabled() {
+        return this.cashBasedAccountingEnabled;
+    }
+
+    public void setCashBasedAccountingEnabled(final boolean cashBasedAccountingEnabled) {
+        this.cashBasedAccountingEnabled = cashBasedAccountingEnabled;
+    }
+
+    public boolean isUpfrontAccrualBasedAccountingEnabled() {
+        return this.upfrontAccrualBasedAccountingEnabled;
+    }
+
+    public boolean isPeriodicAccrualBasedAccountingEnabled() {
+        return this.periodicAccrualBasedAccountingEnabled;
+    }
+
+    public List<LoanTransactionDTO> getNewLoanTransactions() {
+        return this.newLoanTransactions;
+    }
+
+    public void setNewLoanTransactions(final List<LoanTransactionDTO> newLoanTransactions) {
+        this.newLoanTransactions = newLoanTransactions;
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+
+    public void setCurrencyCode(final String currencyCode) {
+        this.currencyCode = currencyCode;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanTransactionDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanTransactionDTO.java
new file mode 100755
index 0000000..89306e5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanTransactionDTO.java
@@ -0,0 +1,135 @@
+/**
+ * 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.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+
+public class LoanTransactionDTO {
+
+    private final Long officeId;
+
+    private final String transactionId;
+    private final Date transactionDate;
+    private final Long paymentTypeId;
+    private final LoanTransactionEnumData transactionType;
+
+    private final BigDecimal amount;
+
+    /*** Breakup of amounts in case of repayments **/
+    private final BigDecimal principal;
+    private final BigDecimal interest;
+    private final BigDecimal fees;
+    private final BigDecimal penalties;
+    private final BigDecimal overPayment;
+
+    /*** Boolean values determines if the transaction is reversed ***/
+    private final boolean reversed;
+
+    /** Breakdowns of fees and penalties this Transaction pays **/
+    private final List<ChargePaymentDTO> penaltyPayments;
+    private final List<ChargePaymentDTO> feePayments;
+
+    private final boolean isAccountTransfer;
+
+    public LoanTransactionDTO(final Long officeId, final Long paymentTypeId, final String transactionId, final Date transactionDate,
+            final LoanTransactionEnumData transactionType, final BigDecimal amount, final BigDecimal principal, final BigDecimal interest,
+            final BigDecimal fees, final BigDecimal penalties, final BigDecimal overPayment, final boolean reversed,
+            final List<ChargePaymentDTO> feePayments, final List<ChargePaymentDTO> penaltyPayments, boolean isAccountTransfer) {
+        this.paymentTypeId = paymentTypeId;
+        this.transactionId = transactionId;
+        this.transactionDate = transactionDate;
+        this.amount = amount;
+        this.principal = principal;
+        this.interest = interest;
+        this.fees = fees;
+        this.penalties = penalties;
+        this.reversed = reversed;
+        this.transactionType = transactionType;
+        this.feePayments = feePayments;
+        this.penaltyPayments = penaltyPayments;
+        this.overPayment = overPayment;
+        this.officeId = officeId;
+        this.isAccountTransfer = isAccountTransfer;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public String getTransactionId() {
+        return this.transactionId;
+    }
+
+    public Date getTransactionDate() {
+        return this.transactionDate;
+    }
+
+    public LoanTransactionEnumData getTransactionType() {
+        return this.transactionType;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public BigDecimal getPrincipal() {
+        return this.principal;
+    }
+
+    public BigDecimal getInterest() {
+        return this.interest;
+    }
+
+    public BigDecimal getFees() {
+        return this.fees;
+    }
+
+    public BigDecimal getPenalties() {
+        return this.penalties;
+    }
+
+    public BigDecimal getOverPayment() {
+        return this.overPayment;
+    }
+
+    public boolean isReversed() {
+        return this.reversed;
+    }
+
+    public Long getPaymentTypeId() {
+        return this.paymentTypeId;
+    }
+
+    public List<ChargePaymentDTO> getPenaltyPayments() {
+        return this.penaltyPayments;
+    }
+
+    public List<ChargePaymentDTO> getFeePayments() {
+        return this.feePayments;
+    }
+
+    public boolean isAccountTransfer() {
+        return this.isAccountTransfer;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/OfficeOpeningBalancesData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/OfficeOpeningBalancesData.java
new file mode 100644
index 0000000..495aaac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/OfficeOpeningBalancesData.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.journalentry.data;
+
+import java.util.List;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.joda.time.LocalDate;
+
+public class OfficeOpeningBalancesData {
+
+    @SuppressWarnings("unused")
+    private final Long officeId;
+    @SuppressWarnings("unused")
+    private final String officeName;
+    @SuppressWarnings("unused")
+    private final LocalDate transactionDate;
+    @SuppressWarnings("unused")
+    private final GLAccountData contraAccount;
+    @SuppressWarnings("unused")
+    private final List<JournalEntryData> assetAccountOpeningBalances;
+    @SuppressWarnings("unused")
+    private final List<JournalEntryData> liabityAccountOpeningBalances;
+    @SuppressWarnings("unused")
+    private final List<JournalEntryData> incomeAccountOpeningBalances;
+    @SuppressWarnings("unused")
+    private final List<JournalEntryData> equityAccountOpeningBalances;
+    @SuppressWarnings("unused")
+    private final List<JournalEntryData> expenseAccountOpeningBalances;
+
+    private OfficeOpeningBalancesData(final Long officeId, final String officeName, final LocalDate transactionDate,
+            final GLAccountData contraAccount, final List<JournalEntryData> assetAccountOpeningBalances,
+            final List<JournalEntryData> liabityAccountOpeningBalances, final List<JournalEntryData> incomeAccountOpeningBalances,
+            final List<JournalEntryData> equityAccountOpeningBalances, final List<JournalEntryData> expenseAccountOpeningBalances) {
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.transactionDate = transactionDate;
+        this.contraAccount = contraAccount;
+        this.assetAccountOpeningBalances = assetAccountOpeningBalances;
+        this.liabityAccountOpeningBalances = liabityAccountOpeningBalances;
+        this.incomeAccountOpeningBalances = incomeAccountOpeningBalances;
+        this.equityAccountOpeningBalances = equityAccountOpeningBalances;
+        this.expenseAccountOpeningBalances = expenseAccountOpeningBalances;
+    }
+
+    public static OfficeOpeningBalancesData createNew(final Long officeId, final String officeName, final LocalDate transactionDate,
+            final GLAccountData contraAccount, final List<JournalEntryData> assetAccountOpeningBalances,
+            final List<JournalEntryData> liabityAccountOpeningBalances, final List<JournalEntryData> incomeAccountOpeningBalances,
+            final List<JournalEntryData> equityAccountOpeningBalances, final List<JournalEntryData> expenseAccountOpeningBalances) {
+        return new OfficeOpeningBalancesData(officeId, officeName, transactionDate, contraAccount, assetAccountOpeningBalances,
+                liabityAccountOpeningBalances, incomeAccountOpeningBalances, equityAccountOpeningBalances, expenseAccountOpeningBalances);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsDTO.java
new file mode 100755
index 0000000..77c0153
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsDTO.java
@@ -0,0 +1,101 @@
+/**
+ * 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.data;
+
+import java.util.List;
+
+public class SavingsDTO {
+
+    private Long savingsId;
+    private Long savingsProductId;
+    private Long officeId;
+    private String currencyCode;
+    private boolean cashBasedAccountingEnabled;
+    private boolean accrualBasedAccountingEnabled;
+    private List<SavingsTransactionDTO> newSavingsTransactions;
+
+    public SavingsDTO(final Long savingsId, final Long savingsProductId, final Long officeId, final String currencyCode,
+            final boolean cashBasedAccountingEnabled, final boolean accrualBasedAccountingEnabled,
+            final List<SavingsTransactionDTO> newSavingsTransactions) {
+        this.savingsId = savingsId;
+        this.savingsProductId = savingsProductId;
+        this.officeId = officeId;
+        this.cashBasedAccountingEnabled = cashBasedAccountingEnabled;
+        this.accrualBasedAccountingEnabled = accrualBasedAccountingEnabled;
+        this.newSavingsTransactions = newSavingsTransactions;
+        this.currencyCode = currencyCode;
+    }
+
+    public Long getSavingsId() {
+        return this.savingsId;
+    }
+
+    public void setSavingsId(final Long savingsId) {
+        this.savingsId = savingsId;
+    }
+
+    public Long getSavingsProductId() {
+        return this.savingsProductId;
+    }
+
+    public void setSavingsProductId(final Long savingsProductId) {
+        this.savingsProductId = savingsProductId;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public void setOfficeId(final Long officeId) {
+        this.officeId = officeId;
+    }
+
+    public boolean isCashBasedAccountingEnabled() {
+        return this.cashBasedAccountingEnabled;
+    }
+
+    public void setCashBasedAccountingEnabled(final boolean cashBasedAccountingEnabled) {
+        this.cashBasedAccountingEnabled = cashBasedAccountingEnabled;
+    }
+
+    public boolean isAccrualBasedAccountingEnabled() {
+        return this.accrualBasedAccountingEnabled;
+    }
+
+    public void setAccrualBasedAccountingEnabled(final boolean accrualBasedAccountingEnabled) {
+        this.accrualBasedAccountingEnabled = accrualBasedAccountingEnabled;
+    }
+
+    public List<SavingsTransactionDTO> getNewSavingsTransactions() {
+        return this.newSavingsTransactions;
+    }
+
+    public void setNewSavingsTransactions(final List<SavingsTransactionDTO> newSavingsTransactions) {
+        this.newSavingsTransactions = newSavingsTransactions;
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+
+    public void setCurrencyCode(final String currencyCode) {
+        this.currencyCode = currencyCode;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsTransactionDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsTransactionDTO.java
new file mode 100755
index 0000000..0ee7b11
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsTransactionDTO.java
@@ -0,0 +1,112 @@
+/**
+ * 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.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
+
+public class SavingsTransactionDTO {
+
+    private final Long officeId;
+    private final String transactionId;
+    private final Date transactionDate;
+    private final Long paymentTypeId;
+    private final SavingsAccountTransactionEnumData transactionType;
+
+    private final BigDecimal amount;
+    private final BigDecimal overdraftAmount;
+
+    /*** Boolean values determines if the transaction is reversed ***/
+    private final boolean reversed;
+
+    /** Breakdowns of fees and penalties this Transaction pays **/
+    private final List<ChargePaymentDTO> penaltyPayments;
+    private final List<ChargePaymentDTO> feePayments;
+
+    private final boolean isAccountTransfer;
+
+    public SavingsTransactionDTO(final Long officeId, final Long paymentTypeId, final String transactionId, final Date transactionDate,
+            final SavingsAccountTransactionEnumData transactionType, final BigDecimal amount, final boolean reversed,
+            final List<ChargePaymentDTO> feePayments, final List<ChargePaymentDTO> penaltyPayments, final BigDecimal overdraftAmount,
+            boolean isAccountTransfer) {
+        this.paymentTypeId = paymentTypeId;
+        this.transactionId = transactionId;
+        this.transactionDate = transactionDate;
+        this.amount = amount;
+        this.reversed = reversed;
+        this.transactionType = transactionType;
+        this.feePayments = feePayments;
+        this.penaltyPayments = penaltyPayments;
+        this.officeId = officeId;
+        this.overdraftAmount = overdraftAmount;
+        this.isAccountTransfer = isAccountTransfer;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public String getTransactionId() {
+        return this.transactionId;
+    }
+
+    public Date getTransactionDate() {
+        return this.transactionDate;
+    }
+
+    public Long getPaymentTypeId() {
+        return this.paymentTypeId;
+    }
+
+    public SavingsAccountTransactionEnumData getTransactionType() {
+        return this.transactionType;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public boolean isReversed() {
+        return this.reversed;
+    }
+
+    public List<ChargePaymentDTO> getPenaltyPayments() {
+        return this.penaltyPayments;
+    }
+
+    public List<ChargePaymentDTO> getFeePayments() {
+        return this.feePayments;
+    }
+
+    public BigDecimal getOverdraftAmount() {
+        return this.overdraftAmount;
+    }
+
+    public boolean isOverdraftTransaction() {
+        return this.overdraftAmount != null && this.overdraftAmount.doubleValue() > 0;
+    }
+
+    public boolean isAccountTransfer() {
+        return this.isAccountTransfer;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TransactionDetailData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TransactionDetailData.java
new file mode 100755
index 0000000..bff4753
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TransactionDetailData.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.journalentry.data;
+
+import org.apache.fineract.portfolio.note.data.NoteData;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+
+public class TransactionDetailData {
+
+    @SuppressWarnings("unused")
+    private final Long transactionId;
+
+    @SuppressWarnings("unused")
+    private final PaymentDetailData paymentDetails;
+
+    @SuppressWarnings("unused")
+    private final NoteData noteData;
+
+    @SuppressWarnings("unused")
+    private final TransactionTypeEnumData transactionType;
+
+    public TransactionDetailData(final Long transactionId, final PaymentDetailData paymentDetails, final NoteData noteData) {
+        this.transactionId = transactionId;
+        this.paymentDetails = paymentDetails;
+        this.noteData = noteData;
+        this.transactionType = null;
+
+    }
+
+    public TransactionDetailData(final Long transactionId, final PaymentDetailData paymentDetails, final NoteData noteData,
+            final TransactionTypeEnumData transactionType) {
+        this.transactionId = transactionId;
+        this.paymentDetails = paymentDetails;
+        this.noteData = noteData;
+        this.transactionType = transactionType;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TransactionTypeEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TransactionTypeEnumData.java
new file mode 100644
index 0000000..d31025b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TransactionTypeEnumData.java
@@ -0,0 +1,41 @@
+/**
+ * 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.data;
+
+/**
+ * Immutable data object represent loan and Savings status enumerations.
+ */
+@SuppressWarnings("unused")
+public class TransactionTypeEnumData {
+
+    private final Long id;
+    private final String code;
+    private final String value;
+
+    public TransactionTypeEnumData(final Long id, final String code, final String value) {
+        this.id = id;
+        this.code = code;
+        this.value = value;
+    }
+
+    public Long id() {
+        return this.id;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java
new file mode 100755
index 0000000..341baf2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java
@@ -0,0 +1,216 @@
+/**
+ * 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.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.portfolio.client.domain.ClientTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Entity
+@Table(name = "acc_gl_journal_entry")
+public class JournalEntry extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "payment_details_id", nullable = true)
+    private PaymentDetail paymentDetail;
+
+    @ManyToOne
+    @JoinColumn(name = "account_id", nullable = false)
+    private GLAccount glAccount;
+
+    @Column(name = "currency_code", length = 3, nullable = false)
+    private String currencyCode;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "reversal_id")
+    private JournalEntry reversalJournalEntry;
+
+    @Column(name = "transaction_id", nullable = false, length = 50)
+    private String transactionId;
+
+    @ManyToOne
+    @JoinColumn(name = "loan_transaction_id", nullable = false)
+    private LoanTransaction loanTransaction;
+
+    @ManyToOne
+    @JoinColumn(name = "savings_transaction_id", nullable = false)
+    private SavingsAccountTransaction savingsTransaction;
+
+    @ManyToOne
+    @JoinColumn(name = "client_transaction_id", nullable = false)
+    private ClientTransaction clientTransaction;
+
+    @Column(name = "reversed", nullable = false)
+    private boolean reversed = false;
+
+    @Column(name = "manual_entry", nullable = false)
+    private boolean manualEntry = false;
+
+    @Column(name = "entry_date")
+    @Temporal(TemporalType.DATE)
+    private Date transactionDate;
+
+    @Column(name = "type_enum", nullable = false)
+    private Integer type;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "description", length = 500)
+    private String description;
+
+    @Column(name = "entity_type_enum", length = 50)
+    private Integer entityType;
+
+    @Column(name = "entity_id")
+    private Long entityId;
+
+    @Column(name = "ref_num")
+    private String referenceNumber;
+
+    public static JournalEntry createNew(final Office office, final PaymentDetail paymentDetail, final GLAccount glAccount,
+            final String currencyCode, final String transactionId, final boolean manualEntry, final Date transactionDate,
+            final JournalEntryType journalEntryType, final BigDecimal amount, final String description, final Integer entityType,
+            final Long entityId, final String referenceNumber, final LoanTransaction loanTransaction,
+            final SavingsAccountTransaction savingsTransaction, final ClientTransaction clientTransaction) {
+        return new JournalEntry(office, paymentDetail, glAccount, currencyCode, transactionId, manualEntry, transactionDate,
+                journalEntryType.getValue(), amount, description, entityType, entityId, referenceNumber, loanTransaction,
+                savingsTransaction, clientTransaction);
+    }
+
+    protected JournalEntry() {
+        //
+    }
+
+    public JournalEntry(final Office office, final PaymentDetail paymentDetail, final GLAccount glAccount, final String currencyCode,
+            final String transactionId, final boolean manualEntry, final Date transactionDate, final Integer type, final BigDecimal amount,
+            final String description, final Integer entityType, final Long entityId, final String referenceNumber,
+            final LoanTransaction loanTransaction, final SavingsAccountTransaction savingsTransaction,
+            final ClientTransaction clientTransaction) {
+        this.office = office;
+        this.glAccount = glAccount;
+        this.reversalJournalEntry = null;
+        this.transactionId = transactionId;
+        this.reversed = false;
+        this.manualEntry = manualEntry;
+        this.transactionDate = transactionDate;
+        this.type = type;
+        this.amount = amount;
+        this.description = StringUtils.defaultIfEmpty(description, null);
+        this.entityType = entityType;
+        this.entityId = entityId;
+        this.referenceNumber = referenceNumber;
+        this.currencyCode = currencyCode;
+        this.loanTransaction = loanTransaction;
+        this.savingsTransaction = savingsTransaction;
+        this.clientTransaction = clientTransaction;
+        this.paymentDetail = paymentDetail;
+    }
+
+    public boolean isDebitEntry() {
+        return JournalEntryType.DEBIT.getValue().equals(this.type);
+    }
+
+    public Integer getType() {
+        return this.type;
+    }
+
+    public Office getOffice() {
+        return this.office;
+    }
+
+    public GLAccount getGlAccount() {
+        return this.glAccount;
+    }
+
+    public Date getTransactionDate() {
+        return this.transactionDate;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public void setReversalJournalEntry(final JournalEntry reversalJournalEntry) {
+        this.reversalJournalEntry = reversalJournalEntry;
+    }
+
+    public void setReversed(final boolean reversed) {
+        this.reversed = reversed;
+    }
+
+    public String getReferenceNumber() {
+        return this.referenceNumber;
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+
+    public LoanTransaction getLoanTransaction() {
+        return this.loanTransaction;
+    }
+
+    public SavingsAccountTransaction getSavingsTransaction() {
+        return this.savingsTransaction;
+    }
+
+    public PaymentDetail getPaymentDetails() {
+        return this.paymentDetail;
+    }
+
+    public String getTransactionId() {
+        return transactionId;
+    }
+
+    public ClientTransaction getClientTransaction() {
+        return this.clientTransaction;
+    }
+
+    public Long getEntityId() {
+        return this.entityId ;
+    }
+    
+    public Integer getEntityType() {
+        return this.entityType ;
+    }
+    
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java
new file mode 100755
index 0000000..73714e0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java
@@ -0,0 +1,42 @@
+/**
+ * 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.domain;
+
+import java.util.List;
+
+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 JournalEntryRepository extends JpaRepository<JournalEntry, Long>, JpaSpecificationExecutor<JournalEntry>,
+        JournalEntryRepositoryCustom {
+
+    @Query("from JournalEntry journalEntry where journalEntry.transactionId= :transactionId and journalEntry.reversed is false and journalEntry.manualEntry is true")
+    List<JournalEntry> findUnReversedManualJournalEntriesByTransactionId(@Param("transactionId") String transactionId);
+
+    @Query("select DISTINCT j.transactionId from JournalEntry j where j.transactionId not in (select DISTINCT je.transactionId from JournalEntry je where je.glAccount.id = :contraId)")
+    List<String> findNonContraTansactionIds(@Param("contraId") Long contraId);
+
+    @Query("select DISTINCT j.transactionId from JournalEntry j where j.office.id = :officeId and j.glAccount.id = :contraId and j.reversed is false and j.transactionId not in (select DISTINCT je.reversalJournalEntry.transactionId from JournalEntry je where je.reversed is true)")
+    List<String> findNonReversedContraTansactionIds(@Param("contraId") Long contraId, @Param("officeId") Long officeId);
+    
+    @Query("from JournalEntry journalEntry where journalEntry.entityId= :entityId and journalEntry.entityType = :entityType")    
+    List<JournalEntry> findProvisioningJournalEntriesByEntityId(@Param("entityId") Long entityId, @Param("entityType") Integer entityType) ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepositoryCustom.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepositoryCustom.java
new file mode 100755
index 0000000..dccad7b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepositoryCustom.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.accounting.journalentry.domain;
+
+import java.util.List;
+
+public interface JournalEntryRepositoryCustom {
+
+    List<JournalEntry> findFirstJournalEntryForAccount(long glAccountId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepositoryImpl.java
new file mode 100755
index 0000000..c755555
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepositoryImpl.java
@@ -0,0 +1,42 @@
+/**
+ * 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.domain;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JournalEntryRepositoryImpl implements JournalEntryRepositoryCustom {
+
+    @PersistenceContext
+    private EntityManager entityManager;
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<JournalEntry> findFirstJournalEntryForAccount(final long glAccountId) {
+        final List<JournalEntry> journalEntries = this.entityManager
+                .createQuery("SELECT journalEntry FROM JournalEntry journalEntry where journalEntry.glAccount.id= :glAccountId")
+                .setParameter("glAccountId", glAccountId).setFirstResult(0).setMaxResults(1).getResultList();
+        return journalEntries;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryType.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryType.java
new file mode 100755
index 0000000..efb5a48
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryType.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.journalentry.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum JournalEntryType {
+
+    CREDIT(1, "journalEntryType.credit"), DEBIT(2, "journalEntrytType.debit");
+
+    private final Integer value;
+    private final String code;
+
+    private JournalEntryType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, JournalEntryType> intToEnumMap = new HashMap<>();
+    static {
+        for (final JournalEntryType type : JournalEntryType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static JournalEntryType fromInt(final int i) {
+        final JournalEntryType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public boolean isDebitType() {
+        return this.value.equals(JournalEntryType.DEBIT.getValue());
+    }
+
+    public boolean isCreditType() {
+        return this.value.equals(JournalEntryType.CREDIT.getValue());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntriesNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntriesNotFoundException.java
new file mode 100755
index 0000000..5918a8d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntriesNotFoundException.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.accounting.journalentry.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when journal entry resources are not found.
+ */
+public class JournalEntriesNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public JournalEntriesNotFoundException(final String transactionId) {
+        super("error.msg.journalEntries.transactionId.invalid", "Journal Entries with transaction Identifier " + transactionId
+                + " does not exist or are not system generated/reversible ", transactionId);
+    }
+
+    public JournalEntriesNotFoundException(final Long entryId) {
+        super("error.msg.journalEntries.id.invalid", "Journal Entry with entry Id " + entryId + " does not exist ", entryId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryInvalidException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryInvalidException.java
new file mode 100755
index 0000000..85ee0f2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryInvalidException.java
@@ -0,0 +1,79 @@
+/**
+ * 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 java.util.Date;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when a GL Journal Entry is Invalid
+ */
+public class JournalEntryInvalidException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons for invalid Journal Entry **/
+    public static enum GL_JOURNAL_ENTRY_INVALID_REASON {
+        FUTURE_DATE, ACCOUNTING_CLOSED, NO_DEBITS_OR_CREDITS, DEBIT_CREDIT_SUM_MISMATCH_WITH_AMOUNT, DEBIT_CREDIT_SUM_MISMATCH, DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, GL_ACCOUNT_DISABLED, GL_ACCOUNT_MANUAL_ENTRIES_NOT_PERMITTED, INVALID_DEBIT_OR_CREDIT_ACCOUNTS;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
+                return "The journal entry cannot be made for a future date";
+            } else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) {
+                return "Journal entry cannot be made prior to last account closing date for the branch";
+            } else if (name().toString().equalsIgnoreCase("NO_DEBITS_OR_CREDITS")) {
+                return "Journal Entry must have atleast one Debit and one Credit";
+            } else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_SUM_MISMATCH_WITH_AMOUNT")) {
+                return "Sum of All Debits OR Credits must equal the Amount for a Journal Entry";
+            } else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_SUM_MISMATCH")) {
+                return "Sum of All Debits must equal the sum of all Credits for a Journal Entry";
+            } else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY")) {
+                return "Both account and amount must be specified for all Debits and Credits";
+            } else if (name().toString().equalsIgnoreCase("GL_ACCOUNT_DISABLED")) {
+                return "Target account has been disabled";
+            } else if (name().toString().equalsIgnoreCase("INVALID_DEBIT_OR_CREDIT_ACCOUNTS")) {
+                return "Invalid debit or credit accounts are passed";
+            } else if (name().toString().equalsIgnoreCase("GL_ACCOUNT_MANUAL_ENTRIES_NOT_PERMITTED")) { return "Target account does not allow maual adjustments"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
+                return "error.msg.glJournalEntry.invalid.future.date";
+            } else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) {
+                return "error.msg.glJournalEntry.invalid.accounting.closed";
+            } else if (name().toString().equalsIgnoreCase("NO_DEBITS_OR_CREDITS")) {
+                return "error.msg.glJournalEntry.invalid.no.debits.or.credits";
+            } else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_SUM_MISMATCH")) {
+                return "error.msg.glJournalEntry.invalid.mismatch.debits.credits";
+            } else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY")) {
+                return "error.msg.glJournalEntry.invalid.empty.account.or.amount";
+            } else if (name().toString().equalsIgnoreCase("GL_ACCOUNT_DISABLED")) {
+                return "error.msg.glJournalEntry.invalid.account.disabled";
+            } else if (name().toString().equalsIgnoreCase("INVALID_DEBIT_OR_CREDIT_ACCOUNTS")) {
+                return "error.msg.glJournalEntry.invalid.debit.or.credit.accounts";
+            } else if (name().toString().equalsIgnoreCase("GL_ACCOUNT_MANUAL_ENTRIES_NOT_PERMITTED")) { return "error.msg.glJournalEntry.invalid.account.manual.adjustments.not.permitted"; }
+            return name().toString();
+        }
+    }
+
+    public JournalEntryInvalidException(final GL_JOURNAL_ENTRY_INVALID_REASON reason, final Date date, final String accountName,
+            final String accountGLCode) {
+        super(reason.errorCode(), reason.errorMessage(), date, accountName, accountGLCode);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryNotFoundException.java
new file mode 100755
index 0000000..7a9ddf7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryNotFoundException.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.accounting.journalentry.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when journal entry resources are not found.
+ */
+public class JournalEntryNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public JournalEntryNotFoundException(final String transactionId) {
+        super("error.msg.journalEntries.transactionId.invalid", "Journal Entries with transaction Identifier " + transactionId
+                + " does not exist or are not system generated/reversible ", transactionId);
+    }
+
+    public JournalEntryNotFoundException(final Long entryId) {
+        super("error.msg.journalEntries.id.invalid", "Journal Entry with entry Id " + entryId + " does not exist ", entryId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/CreateJournalEntryCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/CreateJournalEntryCommandHandler.java
new file mode 100755
index 0000000..93f5e29
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/CreateJournalEntryCommandHandler.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.accounting.journalentry.handler;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+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 = "JOURNALENTRY", action = "CREATE")
+public class CreateJournalEntryCommandHandler implements NewCommandSourceHandler {
+
+    private final JournalEntryWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateJournalEntryCommandHandler(final JournalEntryWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createJournalEntry(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/DefineOpeningBalanceCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/DefineOpeningBalanceCommandHandler.java
new file mode 100644
index 0000000..dd6e298
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/DefineOpeningBalanceCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.handler;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+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;
+
+@Service
+@CommandType(entity = "JOURNALENTRY", action = "DEFINEOPENINGBALANCE")
+public class DefineOpeningBalanceCommandHandler implements NewCommandSourceHandler {
+    
+    private final JournalEntryWritePlatformService writePlatformService;
+
+    @Autowired
+    public DefineOpeningBalanceCommandHandler(final JournalEntryWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.writePlatformService.defineOpeningBalance(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/ReverseJournalEntryCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/ReverseJournalEntryCommandHandler.java
new file mode 100755
index 0000000..38a745e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/ReverseJournalEntryCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.journalentry.handler;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+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 = "JOURNALENTRY", action = "REVERSE")
+public class ReverseJournalEntryCommandHandler implements NewCommandSourceHandler {
+
+    private final JournalEntryWritePlatformService writePlatformService;
+
+    @Autowired
+    public ReverseJournalEntryCommandHandler(final JournalEntryWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.revertJournalEntry(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/UpdateRunningBalanceCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/UpdateRunningBalanceCommandHandler.java
new file mode 100755
index 0000000..a3c203e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/handler/UpdateRunningBalanceCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.handler;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryRunningBalanceUpdateService;
+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;
+
+@Service
+@CommandType(entity = "JOURNALENTRY", action = "UPDATERUNNINGBALANCE")
+public class UpdateRunningBalanceCommandHandler implements NewCommandSourceHandler {
+
+    private final JournalEntryRunningBalanceUpdateService journalEntryRunningBalanceUpdateService;
+
+    @Autowired
+    public UpdateRunningBalanceCommandHandler(final JournalEntryRunningBalanceUpdateService journalEntryRunningBalanceUpdateService) {
+        this.journalEntryRunningBalanceUpdateService = journalEntryRunningBalanceUpdateService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return journalEntryRunningBalanceUpdateService.updateOfficeRunningBalance(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java
new file mode 100755
index 0000000..625554d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.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.accounting.journalentry.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.journalentry.api.JournalEntryJsonInputParams;
+import org.apache.fineract.accounting.journalentry.command.JournalEntryCommand;
+import org.apache.fineract.accounting.journalentry.command.SingleDebitOrCreditEntryCommand;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link JournalEntryCommand}'s.
+ */
+@Component
+public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<JournalEntryCommand> {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public JournalEntryCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonfromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonfromApiJsonHelper;
+    }
+
+    @Override
+    public JournalEntryCommand commandFromApiJson(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        final Set<String> supportedParameters = JournalEntryJsonInputParams.getAllValues();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(JournalEntryJsonInputParams.OFFICE_ID.getValue(), element);
+        final String currencyCode = this.fromApiJsonHelper
+                .extractStringNamed(JournalEntryJsonInputParams.CURRENCY_CODE.getValue(), element);
+        final String comments = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.COMMENTS.getValue(), element);
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                JournalEntryJsonInputParams.TRANSACTION_DATE.getValue(), element);
+        final String referenceNumber = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.REFERENCE_NUMBER.getValue(),
+                element);
+        final Long accountingRuleId = this.fromApiJsonHelper.extractLongNamed(JournalEntryJsonInputParams.ACCOUNTING_RULE.getValue(),
+                element);
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(JournalEntryJsonInputParams.AMOUNT.getValue(), element,
+                locale);
+        final Long paymentTypeId = this.fromApiJsonHelper.extractLongNamed(JournalEntryJsonInputParams.PAYMENT_TYPE_ID.getValue(), element);
+        final String accountNumber = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.ACCOUNT_NUMBER.getValue(),
+                element);
+        final String checkNumber = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.CHECK_NUMBER.getValue(), element);
+        final String receiptNumber = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.RECEIPT_NUMBER.getValue(),
+                element);
+        final String bankNumber = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.BANK_NUMBER.getValue(), element);
+        final String routingCode = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.ROUTING_CODE.getValue(), element);
+
+        SingleDebitOrCreditEntryCommand[] credits = null;
+        SingleDebitOrCreditEntryCommand[] debits = null;
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has(JournalEntryJsonInputParams.CREDITS.getValue())
+                    && topLevelJsonElement.get(JournalEntryJsonInputParams.CREDITS.getValue()).isJsonArray()) {
+                credits = populateCreditsOrDebitsArray(topLevelJsonElement, locale, credits, JournalEntryJsonInputParams.CREDITS.getValue());
+            }
+            if (topLevelJsonElement.has(JournalEntryJsonInputParams.DEBITS.getValue())
+                    && topLevelJsonElement.get(JournalEntryJsonInputParams.DEBITS.getValue()).isJsonArray()) {
+                debits = populateCreditsOrDebitsArray(topLevelJsonElement, locale, debits, JournalEntryJsonInputParams.DEBITS.getValue());
+            }
+        }
+        return new JournalEntryCommand(officeId, currencyCode, transactionDate, comments, credits, debits, referenceNumber,
+                accountingRuleId, amount, paymentTypeId, accountNumber, checkNumber, receiptNumber, bankNumber, routingCode);
+    }
+
+    /**
+     * @param comments
+     * @param topLevelJsonElement
+     * @param locale
+     */
+    private SingleDebitOrCreditEntryCommand[] populateCreditsOrDebitsArray(final JsonObject topLevelJsonElement, final Locale locale,
+            SingleDebitOrCreditEntryCommand[] debitOrCredits, final String paramName) {
+        final JsonArray array = topLevelJsonElement.get(paramName).getAsJsonArray();
+        debitOrCredits = new SingleDebitOrCreditEntryCommand[array.size()];
+        for (int i = 0; i < array.size(); i++) {
+
+            final JsonObject creditElement = array.get(i).getAsJsonObject();
+            final Set<String> parametersPassedInForCreditsCommand = new HashSet<>();
+
+            final Long glAccountId = this.fromApiJsonHelper.extractLongNamed("glAccountId", creditElement);
+            final String comments = this.fromApiJsonHelper.extractStringNamed("comments", creditElement);
+            final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", creditElement, locale);
+
+            debitOrCredits[i] = new SingleDebitOrCreditEntryCommand(parametersPassedInForCreditsCommand, glAccountId, amount, comments);
+        }
+        return debitOrCredits;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForClientTransactions.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForClientTransactions.java
new file mode 100644
index 0000000..1683eea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForClientTransactions.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.accounting.journalentry.service;
+
+import org.apache.fineract.accounting.journalentry.data.ClientTransactionDTO;
+
+public interface AccountingProcessorForClientTransactions {
+
+    void createJournalEntriesForClientTransaction(ClientTransactionDTO clientTransactionDTO);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForLoan.java
new file mode 100755
index 0000000..dc1ecc0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForLoan.java
@@ -0,0 +1,27 @@
+/**
+ * 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.service;
+
+import org.apache.fineract.accounting.journalentry.data.LoanDTO;
+
+public interface AccountingProcessorForLoan {
+
+    void createJournalEntriesForLoan(LoanDTO loanDTO);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForLoanFactory.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForLoanFactory.java
new file mode 100755
index 0000000..6246be8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForLoanFactory.java
@@ -0,0 +1,54 @@
+/**
+ * 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.service;
+
+import org.apache.fineract.accounting.journalentry.data.LoanDTO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AccountingProcessorForLoanFactory {
+
+    private final ApplicationContext applicationContext;
+
+    @Autowired
+    public AccountingProcessorForLoanFactory(final ApplicationContext applicationContext) {
+        this.applicationContext = applicationContext;
+    }
+
+    public AccountingProcessorForLoan determineProcessor(final LoanDTO loanDTO) {
+
+        AccountingProcessorForLoan accountingProcessorForLoan = null;
+
+        if (loanDTO.isCashBasedAccountingEnabled()) {
+            accountingProcessorForLoan = this.applicationContext.getBean("cashBasedAccountingProcessorForLoan",
+                    AccountingProcessorForLoan.class);
+        } else if (loanDTO.isUpfrontAccrualBasedAccountingEnabled()) {
+            accountingProcessorForLoan = this.applicationContext.getBean("accrualBasedAccountingProcessorForLoan",
+                    AccountingProcessorForLoan.class);
+        } else if (loanDTO.isPeriodicAccrualBasedAccountingEnabled()) {
+            accountingProcessorForLoan = this.applicationContext.getBean("accrualBasedAccountingProcessorForLoan",
+                    AccountingProcessorForLoan.class);
+        }
+
+        return accountingProcessorForLoan;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavings.java
new file mode 100755
index 0000000..dbaf6e1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavings.java
@@ -0,0 +1,27 @@
+/**
+ * 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.service;
+
+import org.apache.fineract.accounting.journalentry.data.SavingsDTO;
+
+public interface AccountingProcessorForSavings {
+
+    void createJournalEntriesForSavings(SavingsDTO savingsDTO);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java
new file mode 100755
index 0000000..1394722
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.journalentry.service;
+
+import org.apache.fineract.accounting.journalentry.data.SavingsDTO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AccountingProcessorForSavingsFactory {
+
+    private final ApplicationContext applicationContext;
+
+    @Autowired
+    public AccountingProcessorForSavingsFactory(final ApplicationContext applicationContext) {
+        this.applicationContext = applicationContext;
+    }
+
+    /***
+     * Looks like overkill for now, but wanted to keep the savings side of
+     * accounting identical to that of Loans (would we need an Accrual based
+     * accounting in the future?)
+     ***/
+    public AccountingProcessorForSavings determineProcessor(final SavingsDTO savingsDTO) {
+
+        AccountingProcessorForSavings accountingProcessorForSavings = null;
+
+        if (savingsDTO.isCashBasedAccountingEnabled()) {
+            accountingProcessorForSavings = this.applicationContext.getBean("cashBasedAccountingProcessorForSavings",
+                    AccountingProcessorForSavings.class);
+        }
+
+        return accountingProcessorForSavings;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
new file mode 100755
index 0000000..575cd86
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
@@ -0,0 +1,927 @@
+/**
+ * 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.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.closure.domain.GLClosure;
+import org.apache.fineract.accounting.closure.domain.GLClosureRepository;
+import org.apache.fineract.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS;
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
+import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
+import org.apache.fineract.accounting.journalentry.data.ClientChargePaymentDTO;
+import org.apache.fineract.accounting.journalentry.data.ClientTransactionDTO;
+import org.apache.fineract.accounting.journalentry.data.LoanDTO;
+import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO;
+import org.apache.fineract.accounting.journalentry.data.SavingsDTO;
+import org.apache.fineract.accounting.journalentry.data.SavingsTransactionDTO;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+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.producttoaccountmapping.domain.PortfolioProductType;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
+import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.apache.fineract.portfolio.client.domain.ClientTransaction;
+import org.apache.fineract.portfolio.client.domain.ClientTransactionRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AccountingProcessorHelper {
+
+    public static final String LOAN_TRANSACTION_IDENTIFIER = "L";
+    public static final String SAVINGS_TRANSACTION_IDENTIFIER = "S";
+    public static final String CLIENT_TRANSACTION_IDENTIFIER = "C";
+    public static final String PROVISIONING_TRANSACTION_IDENTIFIER = "P" ;
+    private final JournalEntryRepository glJournalEntryRepository;
+    private final ProductToGLAccountMappingRepository accountMappingRepository;
+    private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository;
+    private final GLClosureRepository closureRepository;
+    private final GLAccountRepositoryWrapper accountRepositoryWrapper;
+    private final OfficeRepository officeRepository;
+    private final LoanTransactionRepository loanTransactionRepository;
+    private final ClientTransactionRepositoryWrapper clientTransactionRepository;
+    private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
+    private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
+
+    @Autowired
+    public AccountingProcessorHelper(final JournalEntryRepository glJournalEntryRepository,
+            final ProductToGLAccountMappingRepository accountMappingRepository, final GLClosureRepository closureRepository,
+            final OfficeRepository officeRepository, final LoanTransactionRepository loanTransactionRepository,
+            final SavingsAccountTransactionRepository savingsAccountTransactionRepository,
+            final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository,
+            final AccountTransfersReadPlatformService accountTransfersReadPlatformService,
+            final GLAccountRepositoryWrapper accountRepositoryWrapper,
+            final ClientTransactionRepositoryWrapper clientTransactionRepositoryWrapper) {
+        this.glJournalEntryRepository = glJournalEntryRepository;
+        this.accountMappingRepository = accountMappingRepository;
+        this.closureRepository = closureRepository;
+        this.officeRepository = officeRepository;
+        this.loanTransactionRepository = loanTransactionRepository;
+        this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
+        this.financialActivityAccountRepository = financialActivityAccountRepository;
+        this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
+        this.accountRepositoryWrapper = accountRepositoryWrapper;
+        this.clientTransactionRepository = clientTransactionRepositoryWrapper;
+    }
+
+    public LoanDTO populateLoanDtoFromMap(final Map<String, Object> accountingBridgeData, final boolean cashBasedAccountingEnabled,
+            final boolean upfrontAccrualBasedAccountingEnabled, final boolean periodicAccrualBasedAccountingEnabled) {
+        final Long loanId = (Long) accountingBridgeData.get("loanId");
+        final Long loanProductId = (Long) accountingBridgeData.get("loanProductId");
+        final Long officeId = (Long) accountingBridgeData.get("officeId");
+        final CurrencyData currencyData = (CurrencyData) accountingBridgeData.get("currency");
+        final List<LoanTransactionDTO> newLoanTransactions = new ArrayList<>();
+        boolean isAccountTransfer = (Boolean) accountingBridgeData.get("isAccountTransfer");
+
+        @SuppressWarnings("unchecked")
+        final List<Map<String, Object>> newTransactionsMap = (List<Map<String, Object>>) accountingBridgeData.get("newLoanTransactions");
+
+        for (final Map<String, Object> map : newTransactionsMap) {
+            final Long transactionOfficeId = (Long) map.get("officeId");
+            final String transactionId = ((Long) map.get("id")).toString();
+            final Date transactionDate = ((LocalDate) map.get("date")).toDate();
+            final LoanTransactionEnumData transactionType = (LoanTransactionEnumData) map.get("type");
+            final BigDecimal amount = (BigDecimal) map.get("amount");
+            final BigDecimal principal = (BigDecimal) map.get("principalPortion");
+            final BigDecimal interest = (BigDecimal) map.get("interestPortion");
+            final BigDecimal fees = (BigDecimal) map.get("feeChargesPortion");
+            final BigDecimal penalties = (BigDecimal) map.get("penaltyChargesPortion");
+            final BigDecimal overPayments = (BigDecimal) map.get("overPaymentPortion");
+            final boolean reversed = (Boolean) map.get("reversed");
+            final Long paymentTypeId = (Long) map.get("paymentTypeId");
+
+            final List<ChargePaymentDTO> feePaymentDetails = new ArrayList<>();
+            final List<ChargePaymentDTO> penaltyPaymentDetails = new ArrayList<>();
+            // extract charge payment details (if exists)
+            if (map.containsKey("loanChargesPaid")) {
+                @SuppressWarnings("unchecked")
+                final List<Map<String, Object>> loanChargesPaidData = (List<Map<String, Object>>) map.get("loanChargesPaid");
+                for (final Map<String, Object> loanChargePaid : loanChargesPaidData) {
+                    final Long chargeId = (Long) loanChargePaid.get("chargeId");
+                    final Long loanChargeId = (Long) loanChargePaid.get("loanChargeId");
+                    final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty");
+                    final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount");
+                    final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid);
+                    if (isPenalty) {
+                        penaltyPaymentDetails.add(chargePaymentDTO);
+                    } else {
+                        feePaymentDetails.add(chargePaymentDTO);
+                    }
+                }
+            }
+
+            if (!isAccountTransfer) {
+                isAccountTransfer = this.accountTransfersReadPlatformService.isAccountTransfer(Long.parseLong(transactionId),
+                        PortfolioAccountType.LOAN);
+            }
+            final LoanTransactionDTO transaction = new LoanTransactionDTO(transactionOfficeId, paymentTypeId, transactionId,
+                    transactionDate, transactionType, amount, principal, interest, fees, penalties, overPayments, reversed,
+                    feePaymentDetails, penaltyPaymentDetails, isAccountTransfer);
+
+            newLoanTransactions.add(transaction);
+
+        }
+
+        return new LoanDTO(loanId, loanProductId, officeId, currencyData.code(), cashBasedAccountingEnabled,
+                upfrontAccrualBasedAccountingEnabled, periodicAccrualBasedAccountingEnabled, newLoanTransactions);
+    }
+
+    public SavingsDTO populateSavingsDtoFromMap(final Map<String, Object> accountingBridgeData, final boolean cashBasedAccountingEnabled,
+            final boolean accrualBasedAccountingEnabled) {
+        final Long loanId = (Long) accountingBridgeData.get("savingsId");
+        final Long loanProductId = (Long) accountingBridgeData.get("savingsProductId");
+        final Long officeId = (Long) accountingBridgeData.get("officeId");
+        final CurrencyData currencyData = (CurrencyData) accountingBridgeData.get("currency");
+        final List<SavingsTransactionDTO> newSavingsTransactions = new ArrayList<>();
+        boolean isAccountTransfer = (Boolean) accountingBridgeData.get("isAccountTransfer");
+
+        @SuppressWarnings("unchecked")
+        final List<Map<String, Object>> newTransactionsMap = (List<Map<String, Object>>) accountingBridgeData.get("newSavingsTransactions");
+
+        for (final Map<String, Object> map : newTransactionsMap) {
+            final Long transactionOfficeId = (Long) map.get("officeId");
+            final String transactionId = ((Long) map.get("id")).toString();
+            final Date transactionDate = ((LocalDate) map.get("date")).toDate();
+            final SavingsAccountTransactionEnumData transactionType = (SavingsAccountTransactionEnumData) map.get("type");
+            final BigDecimal amount = (BigDecimal) map.get("amount");
+            final boolean reversed = (Boolean) map.get("reversed");
+            final Long paymentTypeId = (Long) map.get("paymentTypeId");
+            final BigDecimal overdraftAmount = (BigDecimal) map.get("overdraftAmount");
+
+            final List<ChargePaymentDTO> feePayments = new ArrayList<>();
+            final List<ChargePaymentDTO> penaltyPayments = new ArrayList<>();
+            // extract charge payment details (if exists)
+            if (map.containsKey("savingsChargesPaid")) {
+                @SuppressWarnings("unchecked")
+                final List<Map<String, Object>> savingsChargesPaidData = (List<Map<String, Object>>) map.get("savingsChargesPaid");
+                for (final Map<String, Object> loanChargePaid : savingsChargesPaidData) {
+                    final Long chargeId = (Long) loanChargePaid.get("chargeId");
+                    final Long loanChargeId = (Long) loanChargePaid.get("savingsChargeId");
+                    final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty");
+                    final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount");
+                    final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid);
+                    if (isPenalty) {
+                        penaltyPayments.add(chargePaymentDTO);
+                    } else {
+                        feePayments.add(chargePaymentDTO);
+                    }
+                }
+            }
+            if (!isAccountTransfer) {
+                isAccountTransfer = this.accountTransfersReadPlatformService.isAccountTransfer(Long.parseLong(transactionId),
+                        PortfolioAccountType.SAVINGS);
+            }
+            final SavingsTransactionDTO transaction = new SavingsTransactionDTO(transactionOfficeId, paymentTypeId, transactionId,
+                    transactionDate, transactionType, amount, reversed, feePayments, penaltyPayments, overdraftAmount, isAccountTransfer);
+
+            newSavingsTransactions.add(transaction);
+
+        }
+
+        return new SavingsDTO(loanId, loanProductId, officeId, currencyData.code(), cashBasedAccountingEnabled,
+                accrualBasedAccountingEnabled, newSavingsTransactions);
+    }
+
+    public ClientTransactionDTO populateClientTransactionDtoFromMap(final Map<String, Object> accountingBridgeData) {
+
+        final Long transactionOfficeId = (Long) accountingBridgeData.get("officeId");
+        final Long clientId = (Long) accountingBridgeData.get("clientId");
+        final Long transactionId = (Long) accountingBridgeData.get("id");
+        final Date transactionDate = ((LocalDate) accountingBridgeData.get("date")).toDate();
+        final EnumOptionData transactionType = (EnumOptionData) accountingBridgeData.get("type");
+        final BigDecimal amount = (BigDecimal) accountingBridgeData.get("amount");
+        final boolean reversed = (Boolean) accountingBridgeData.get("reversed");
+        final Long paymentTypeId = (Long) accountingBridgeData.get("paymentTypeId");
+        final String currencyCode = (String) accountingBridgeData.get("currencyCode");
+        final Boolean accountingEnabled = (Boolean) accountingBridgeData.get("accountingEnabled");
+
+        final List<ClientChargePaymentDTO> clientChargePaymentDTOs = new ArrayList<>();
+        // extract client charge payment details (if exists)
+        if (accountingBridgeData.containsKey("clientChargesPaid")) {
+            @SuppressWarnings("unchecked")
+            final List<Map<String, Object>> clientChargesPaidData = (List<Map<String, Object>>) accountingBridgeData
+                    .get("clientChargesPaid");
+            for (final Map<String, Object> clientChargePaid : clientChargesPaidData) {
+                final Long chargeId = (Long) clientChargePaid.get("chargeId");
+                final Long clientChargeId = (Long) clientChargePaid.get("clientChargeId");
+                final boolean isPenalty = (Boolean) clientChargePaid.get("isPenalty");
+                final BigDecimal chargeAmountPaid = (BigDecimal) clientChargePaid.get("amount");
+                final Long incomeAccountId = (Long) clientChargePaid.get("incomeAccountId");
+                final ClientChargePaymentDTO clientChargePaymentDTO = new ClientChargePaymentDTO(chargeId, chargeAmountPaid, clientChargeId,
+                        isPenalty, incomeAccountId);
+                clientChargePaymentDTOs.add(clientChargePaymentDTO);
+            }
+        }
+
+        final ClientTransactionDTO clientTransactionDTO = new ClientTransactionDTO(clientId, transactionOfficeId, paymentTypeId,
+                transactionId, transactionDate, transactionType, currencyCode, amount, reversed, accountingEnabled,
+                clientChargePaymentDTOs);
+
+        return clientTransactionDTO;
+
+    }
+
+    /**
+     * Convenience method that creates a pair of related Debits and Credits for
+     * Accrual Based accounting.
+     * 
+     * The target accounts for debits and credits are switched in case of a
+     * reversal
+     * 
+     * @param office
+     * @param accountTypeToBeDebited
+     *            Enum of the placeholder GLAccount to be debited
+     * @param accountTypeToBeCredited
+     *            Enum of the placeholder of the GLAccount to be credited
+     * @param loanProductId
+     * @param paymentTypeId
+     * @param loanId
+     * @param transactionId
+     * @param transactionDate
+     * @param amount
+     * @param isReversal
+     */
+    public void createAccrualBasedJournalEntriesAndReversalsForLoan(final Office office, final String currencyCode,
+            final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long paymentTypeId,
+            final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) {
+        int accountTypeToDebitId = accountTypeToBeDebited;
+        int accountTypeToCreditId = accountTypeToBeCredited;
+        // reverse debits and credits for reversals
+        if (isReversal) {
+            accountTypeToDebitId = accountTypeToBeCredited;
+            accountTypeToCreditId = accountTypeToBeDebited;
+        }
+        createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, loanId,
+                transactionId, transactionDate, amount);
+    }
+
+    /**
+     * Convenience method that creates a pair of related Debits and Credits for
+     * Accrual Based accounting.
+     * 
+     * The target accounts for debits and credits are switched in case of a
+     * reversal
+     * 
+     * @param office
+     * @param accountTypeToBeDebited
+     *            Enum of the placeholder GLAccount to be debited
+     * @param accountTypeToBeCredited
+     *            Enum of the placeholder of the GLAccount to be credited
+     * @param loanProductId
+     * @param paymentTypeId
+     * @param loanId
+     * @param transactionId
+     * @param transactionDate
+     * @param amount
+     * @param isReversal
+     */
+    public void createAccrualBasedJournalEntriesAndReversalsForLoanCharges(final Office office, final String currencyCode,
+            final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long loanId,
+            final String transactionId, final Date transactionDate, final BigDecimal totalAmount, final Boolean isReversal,
+            final List<ChargePaymentDTO> chargePaymentDTOs) {
+
+        GLAccount receivableAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountTypeToBeDebited, null);
+        final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>();
+        for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) {
+            final Long chargeId = chargePaymentDTO.getChargeId();
+            final GLAccount chargeSpecificAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountTypeToBeCredited, chargeId);
+            BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount();
+
+            // adjust net credit amount if the account is already present in the
+            // map
+            if (creditDetailsMap.containsKey(chargeSpecificAccount)) {
+                final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount);
+                chargeSpecificAmount = chargeSpecificAmount.add(existingAmount);
+            }
+            creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount);
+        }
+
+        BigDecimal totalCreditedAmount = BigDecimal.ZERO;
+        for (final Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) {
+            final GLAccount account = entry.getKey();
+            final BigDecimal amount = entry.getValue();
+            totalCreditedAmount = totalCreditedAmount.add(amount);
+            if (isReversal) {
+                createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
+                createCreditJournalEntryForLoan(office, currencyCode, receivableAccount, loanId, transactionId, transactionDate, amount);
+            } else {
+                createDebitJournalEntryForLoan(office, currencyCode, receivableAccount, loanId, transactionId, transactionDate, amount);
+                createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
+            }
+        }
+
+        if (totalAmount.compareTo(totalCreditedAmount) != 0) { throw new PlatformDataIntegrityException(
+                "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
+                "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
+                totalCreditedAmount, totalAmount); }
+    }
+
+    /**
+     * Convenience method that creates a pair of related Debits and Credits for
+     * Cash Based accounting.
+     * 
+     * The target accounts for debits and credits are switched in case of a
+     * reversal
+     * 
+     * @param office
+     * @param accountTypeToBeDebited
+     *            Enum of the placeholder GLAccount to be debited
+     * @param accountTypeToBeCredited
+     *            Enum of the placeholder of the GLAccount to be credited
+     * @param savingsProductId
+     * @param paymentTypeId
+     * @param loanId
+     * @param transactionId
+     * @param transactionDate
+     * @param amount
+     * @param isReversal
+     */
+    public void createCashBasedJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
+            final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long savingsProductId,
+            final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount,
+            final Boolean isReversal) {
+        int accountTypeToDebitId = accountTypeToBeDebited;
+        int accountTypeToCreditId = accountTypeToBeCredited;
+        // reverse debits and credits for reversals
+        if (isReversal) {
+            accountTypeToDebitId = accountTypeToBeCredited;
+            accountTypeToCreditId = accountTypeToBeDebited;
+        }
+        createJournalEntriesForSavings(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, savingsProductId, paymentTypeId,
+                loanId, transactionId, transactionDate, amount);
+    }
+
+    /**
+     * Convenience method that creates a pair of related Debits and Credits for
+     * Cash Based accounting.
+     * 
+     * The target accounts for debits and credits are switched in case of a
+     * reversal
+     * 
+     * @param office
+     * @param accountTypeToBeDebited
+     *            Enum of the placeholder GLAccount to be debited
+     * @param accountTypeToBeCredited
+     *            Enum of the placeholder of the GLAccount to be credited
+     * @param loanProductId
+     * @param paymentTypeId
+     * @param loanId
+     * @param transactionId
+     * @param transactionDate
+     * @param amount
+     * @param isReversal
+     */
+    public void createCashBasedJournalEntriesAndReversalsForLoan(final Office office, final String currencyCode,
+            final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long paymentTypeId,
+            final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) {
+        int accountTypeToDebitId = accountTypeToBeDebited;
+        int accountTypeToCreditId = accountTypeToBeCredited;
+        // reverse debits and credits for reversals
+        if (isReversal) {
+            accountTypeToDebitId = accountTypeToBeCredited;
+            accountTypeToCreditId = accountTypeToBeDebited;
+        }
+        createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, loanId,
+                transactionId, transactionDate, amount);
+    }
+
+    public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode,
+            final CASH_ACCOUNTS_FOR_LOAN accountMappingType, final Long loanProductId, final Long paymentTypeId, final Long loanId,
+            final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) {
+        final int accountMappingTypeId = accountMappingType.getValue();
+        createCreditJournalEntryOrReversalForLoan(office, currencyCode, accountMappingTypeId, loanProductId, paymentTypeId, loanId,
+                transactionId, transactionDate, amount, isReversal);
+    }
+
+    public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode,
+            final ACCRUAL_ACCOUNTS_FOR_LOAN accountMappingType, final Long loanProductId, final Long paymentTypeId, final Long loanId,
+            final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) {
+        final int accountMappingTypeId = accountMappingType.getValue();
+        createCreditJournalEntryOrReversalForLoan(office, currencyCode, accountMappingTypeId, loanProductId, paymentTypeId, loanId,
+                transactionId, transactionDate, amount, isReversal);
+    }
+
+    /**
+     * @param latestGLClosure
+     * @param transactionDate
+     */
+    public void checkForBranchClosures(final GLClosure latestGLClosure, final Date transactionDate) {
+        /**
+         * check if an accounting closure has happened for this branch after the
+         * transaction Date
+         **/
+        if (latestGLClosure != null) {
+            if (latestGLClosure.getClosingDate().after(transactionDate)
+                    || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException(
+                            GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); }
+        }
+    }
+
+    public GLClosure getLatestClosureByBranch(final long officeId) {
+        return this.closureRepository.getLatestGLClosureByBranch(officeId);
+    }
+
+    public Office getOfficeById(final long officeId) {
+        return this.officeRepository.findOne(officeId);
+    }
+
+    private void createJournalEntriesForLoan(final Office office, final String currencyCode, final int accountTypeToDebitId,
+            final int accountTypeToCreditId, final Long loanProductId, final Long paymentTypeId, final Long loanId,
+            final String transactionId, final Date transactionDate, final BigDecimal amount) {
+        final GLAccount debitAccount = getLinkedGLAccountForLoanProduct(loanProductId, accountTypeToDebitId, paymentTypeId);
+        final GLAccount creditAccount = getLinkedGLAccountForLoanProduct(loanProductId, accountTypeToCreditId, paymentTypeId);
+        createDebitJournalEntryForLoan(office, currencyCode, debitAccount, loanId, transactionId, transactionDate, amount);
+        createCreditJournalEntryForLoan(office, currencyCode, creditAccount, loanId, transactionId, transactionDate, amount);
+    }
+
+    private void createJournalEntriesForSavings(final Office office, final String currencyCode, final int accountTypeToDebitId,
+            final int accountTypeToCreditId, final Long savingsProductId, final Long paymentTypeId, final Long savingsId,
+            final String transactionId, final Date transactionDate, final BigDecimal amount) {
+        final GLAccount debitAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToDebitId, paymentTypeId);
+        final GLAccount creditAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToCreditId, paymentTypeId);
+        createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount);
+        createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount);
+    }
+
+    public void createDebitJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final int accountMappingTypeId,
+            final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate,
+            final BigDecimal amount, final Boolean isReversal) {
+        final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId, paymentTypeId);
+        if (isReversal) {
+            createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
+        } else {
+            createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
+        }
+    }
+
+    public void createCreditJournalEntryOrReversalForLoanCharges(final Office office, final String currencyCode,
+            final int accountMappingTypeId, final Long loanProductId, final Long loanId, final String transactionId,
+            final Date transactionDate, final BigDecimal totalAmount, final Boolean isReversal,
+            final List<ChargePaymentDTO> chargePaymentDTOs) {
+        /***
+         * Map to track each account and the net credit to be made for a
+         * particular account
+         ***/
+        final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>();
+        for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) {
+            final Long chargeId = chargePaymentDTO.getChargeId();
+            final GLAccount chargeSpecificAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountMappingTypeId, chargeId);
+            BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount();
+
+            // adjust net credit amount if the account is already present in the
+            // map
+            if (creditDetailsMap.containsKey(chargeSpecificAccount)) {
+                final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount);
+                chargeSpecificAmount = chargeSpecificAmount.add(existingAmount);
+            }
+            creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount);
+        }
+
+        BigDecimal totalCreditedAmount = BigDecimal.ZERO;
+        for (final Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) {
+            final GLAccount account = entry.getKey();
+            final BigDecimal amount = entry.getValue();
+            totalCreditedAmount = totalCreditedAmount.add(amount);
+            if (isReversal) {
+                createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
+            } else {
+                createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
+            }
+        }
+
+        // TODO: Vishwas Temporary validation to be removed before moving to
+        // release branch
+        if (totalAmount.compareTo(totalCreditedAmount) != 0) { throw new PlatformDataIntegrityException(
+                "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
+                "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
+                totalCreditedAmount, totalAmount); }
+    }
+
+    /**
+     * Convenience method that creates a pair of related Debits and Credits for
+     * Cash Based accounting.
+     * 
+     * The target accounts for debits and credits are switched in case of a
+     * reversal
+     * 
+     * @param office
+     * @param accountTypeToBeDebited
+     *            Enum of the placeholder GLAccount to be debited
+     * @param accountTypeToBeCredited
+     *            Enum of the placeholder of the GLAccount to be credited
+     * @param savingsProductId
+     * @param paymentTypeId
+     * @param loanId
+     * @param transactionId
+     * @param transactionDate
+     * @param amount
+     * @param isReversal
+     */
+    public void createCashBasedJournalEntriesAndReversalsForSavingsCharges(final Office office, final String currencyCode,
+            final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeDebited, final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeCredited,
+            final Long savingsProductId, final Long paymentTypeId, final Long loanId, final String transactionId,
+            final Date transactionDate, final BigDecimal totalAmount, final Boolean isReversal,
+            final List<ChargePaymentDTO> chargePaymentDTOs) {
+        // TODO Vishwas: Remove this validation, as and when appropriate Junit
+        // tests are written for accounting
+        /**
+         * Accounting module currently supports a single charge per transaction,
+         * throw an error if this is not the case here so any developers
+         * changing the expected portfolio behavior would also take care of
+         * modifying the accounting code appropriately
+         **/
+        if (chargePaymentDTOs.size() != 1) { throw new PlatformDataIntegrityException(
+                "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code",
+                "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code"); }
+        ChargePaymentDTO chargePaymentDTO = chargePaymentDTOs.get(0);
+
+        final GLAccount chargeSpecificAccount = getLinkedGLAccountForSavingsCharges(savingsProductId, accountTypeToBeCredited.getValue(),
+                chargePaymentDTO.getChargeId());
+        final GLAccount savingsControlAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToBeDebited.getValue(),
+                paymentTypeId);
+        if (isReversal) {
+            createDebitJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate,
+                    totalAmount);
+            createCreditJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate,
+                    totalAmount);
+        } else {
+            createDebitJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate,
+                    totalAmount);
+            createCreditJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate,
+                    totalAmount);
+        }
+    }
+
+    public LoanTransaction getLoanTransactionById(final long loanTransactionId) {
+        return this.loanTransactionRepository.findOne(loanTransactionId);
+    }
+
+    public SavingsAccountTransaction getSavingsTransactionById(final long savingsTransactionId) {
+        return this.savingsAccountTransactionRepository.findOne(savingsTransactionId);
+    }
+
+    private void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final int accountMappingTypeId,
+            final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate,
+            final BigDecimal amount, final Boolean isReversal) {
+        final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId, paymentTypeId);
+        if (isReversal) {
+            createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
+        } else {
+            createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
+        }
+    }
+
+    private void createCreditJournalEntryForClientPayments(final Office office, final String currencyCode, final GLAccount account,
+            final Long clientId, final Long transactionId, final Date transactionDate, final BigDecimal amount) {
+        final boolean manualEntry = false;
+        LoanTransaction loanTransaction = null;
+        SavingsAccountTransaction savingsAccountTransaction = null;
+        ClientTransaction clientTransaction = null;
+        final PaymentDetail paymentDetail = null;
+
+        clientTransaction = this.clientTransactionRepository.findOneWithNotFoundDetection(clientId, transactionId);
+
+        String modifiedTransactionId = transactionId.toString();
+        modifiedTransactionId = CLIENT_TRANSACTION_IDENTIFIER + transactionId;
+        final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
+                manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.CLIENT.getValue(), clientId, null,
+                loanTransaction, savingsAccountTransaction, clientTransaction);
+        this.glJournalEntryRepository.saveAndFlush(journalEntry);
+    }
+
+    private void createCreditJournalEntryForSavings(final Office office, final String currencyCode, final GLAccount account,
+            final Long savingsId, final String transactionId, final Date transactionDate, final BigDecimal amount) {
+        final boolean manualEntry = false;
+        LoanTransaction loanTransaction = null;
+        SavingsAccountTransaction savingsAccountTransaction = null;
+        ClientTransaction clientTransaction = null;
+        final PaymentDetail paymentDetail = null;
+        String modifiedTransactionId = transactionId;
+        if (StringUtils.isNumeric(transactionId)) {
+            long id = Long.parseLong(transactionId);
+            savingsAccountTransaction = this.savingsAccountTransactionRepository.findOne(id);
+            modifiedTransactionId = SAVINGS_TRANSACTION_IDENTIFIER + transactionId;
+        }
+        final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
+                manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId,
+                null, loanTransaction, savingsAccountTransaction, clientTransaction);
+        this.glJournalEntryRepository.saveAndFlush(journalEntry);
+    }
+
+    private void createCreditJournalEntryForLoan(final Office office, final String currencyCode, final GLAccount account, final Long loanId,
+            final String transactionId, final Date transactionDate, final BigDecimal amount) {
+        final boolean manualEntry = false;
+        LoanTransaction loanTransaction = null;
+        SavingsAccountTransaction savingsAccountTransaction = null;
+        ClientTransaction clientTransaction = null;
+        final PaymentDetail paymentDetail = null;
+        String modifiedTransactionId = transactionId;
+        if (StringUtils.isNumeric(transactionId)) {
+            long id = Long.parseLong(transactionId);
+            loanTransaction = this.loanTransactionRepository.findOne(id);
+            modifiedTransactionId = LOAN_TRANSACTION_IDENTIFIER + transactionId;
+        }
+        final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
+                manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId, null,
+                loanTransaction, savingsAccountTransaction, clientTransaction);
+        this.glJournalEntryRepository.saveAndFlush(journalEntry);
+    }
+
+    public void createProvisioningDebitJournalEntry(Date transactionDate, Long provisioningentryId, Office office, String currencyCode, GLAccount account,BigDecimal amount) {
+        LoanTransaction loanTransaction = null;
+        SavingsAccountTransaction savingsAccountTransaction = null;
+        ClientTransaction clientTransaction = null;
+        PaymentDetail paymentDetail = null ;
+        final boolean manualEntry = false;
+        String modifiedTransactionId = PROVISIONING_TRANSACTION_IDENTIFIER + provisioningentryId;
+        final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
+                manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.PROVISIONING.getValue(), provisioningentryId, null,
+                loanTransaction, savingsAccountTransaction, clientTransaction);
+        
+        this.glJournalEntryRepository.saveAndFlush(journalEntry);
+    }
+    
+    public void createProvisioningCreditJournalEntry(Date transactionDate, Long provisioningentryId, Office office, String currencyCode, GLAccount account, BigDecimal amount) {
+        LoanTransaction loanTransaction = null;
+        SavingsAccountTransaction savingsAccountTransaction = null;
+        ClientTransaction clientTransaction = null;
+        PaymentDetail paymentDetail = null ;
+        final boolean manualEntry = false;
+        String modifiedTransactionId = PROVISIONING_TRANSACTION_IDENTIFIER + provisioningentryId;
+        final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
+                manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.PROVISIONING.getValue(), provisioningentryId, null,
+                loanTransaction, savingsAccountTransaction, clientTransaction);
+        this.glJournalEntryRepository.saveAndFlush(journalEntry);
+    }
+    
+    private void createDebitJournalEntryForLoan(final Office office, final String currencyCode, final GLAccount account, final Long loanId,
+            final String transactionId, final Date transactionDate, final BigDecimal amount) {
+        final boolean manualEntry = false;
+        LoanTransaction loanTransaction = null;
+        SavingsAccountTransaction savingsAccountTransaction = null;
+        ClientTransaction clientTransaction = null;
+        final PaymentDetail paymentDetail = null;
+        String modifiedTransactionId = transactionId;
+        if (StringUtils.isNumeric(transactionId)) {
+            long id = Long.parseLong(transactionId);
+            loanTransaction = this.loanTransactionRepository.findOne(id);
+            modifiedTransactionId = LOAN_TRANSACTION_IDENTIFIER + transactionId;
+        }
+        final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
+                manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId, null,
+                loanTransaction, savingsAccountTransaction, clientTransaction);
+        this.glJournalEntryRepository.saveAndFlush(journalEntry);
+    }
+
+    private void createDebitJournalEntryForSavings(final Office office, final String currencyCode, final GLAccount account,
+            final Long savingsId, final String transactionId, final Date transactionDate, final BigDecimal amount) {
+        final boolean manualEntry = false;
+        LoanTransaction loanTransaction = null;
+        SavingsAccountTransaction savingsAccountTransaction = null;
+        ClientTransaction clientTransaction = null;
+        final PaymentDetail paymentDetail = null;
+        String modifiedTransactionId = transactionId;
+        if (StringUtils.isNumeric(transactionId)) {
+            long id = Long.parseLong(transactionId);
+            savingsAccountTransaction = this.savingsAccountTransactionRepository.findOne(id);
+            modifiedTransactionId = SAVINGS_TRANSACTION_IDENTIFIER + transactionId;
+        }
+        final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
+                manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null,
+                loanTransaction, savingsAccountTransaction, clientTransaction);
+        this.glJournalEntryRepository.saveAndFlush(journalEntry);
+    }
+
+    private void createDebitJournalEntryForClientPayments(final Office office, final String currencyCode, final GLAccount account,
+            final Long clientId, final Long transactionId, final Date transactionDate, final BigDecimal amount) {
+        final boolean manualEntry = false;
+        ClientTransaction clientTransaction = null;
+        LoanTransaction loanTransaction = null;
+        SavingsAccountTransaction savingsAccountTransaction = null;
+        final PaymentDetail paymentDetail = null;
+
+        clientTransaction = this.clientTransactionRepository.findOneWithNotFoundDetection(clientId, transactionId);
+        String modifiedTransactionId = transactionId.toString();
+        modifiedTransactionId = CLIENT_TRANSACTION_IDENTIFIER + transactionId;
+
+        final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
+                manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.CLIENT.getValue(), clientId, null,
+                loanTransaction, savingsAccountTransaction, clientTransaction);
+        this.glJournalEntryRepository.saveAndFlush(journalEntry);
+    }
+
+    private GLAccount getLinkedGLAccountForLoanProduct(final Long loanProductId, final int accountMappingTypeId, final Long paymentTypeId) {
+        GLAccount glAccount = null;
+        if (isOrganizationAccount(accountMappingTypeId)) {
+            FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository
+                    .findByFinancialActivityTypeWithNotFoundDetection(accountMappingTypeId);
+            glAccount = financialActivityAccount.getGlAccount();
+        } else {
+            ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(loanProductId,
+                    PortfolioProductType.LOAN.getValue(), accountMappingTypeId);
+
+            /****
+             * Get more specific mapping for FUND source accounts (based on
+             * payment channels). Note that fund source placeholder ID would be
+             * same for both cash and accrual accounts
+             ***/
+            if (accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()) {
+                final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository
+                        .findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(loanProductId,
+                                PortfolioProductType.LOAN.getValue(), accountMappingTypeId, paymentTypeId);
+                if (paymentChannelSpecificAccountMapping != null) {
+                    accountMapping = paymentChannelSpecificAccountMapping;
+                }
+            }
+
+            if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.LOAN, loanProductId,
+                    ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString()); }
+            glAccount = accountMapping.getGlAccount();
+        }
+        return glAccount;
+    }
+
+    private GLAccount getLinkedGLAccountForLoanCharges(final Long loanProductId, final int accountMappingTypeId, final Long chargeId) {
+        ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(loanProductId,
+                PortfolioProductType.LOAN.getValue(), accountMappingTypeId);
+                /*****
+                 * Get more specific mappings for Charges and penalties (based
+                 * on the actual charge /penalty coupled with the loan product).
+                 * Note the income from fees and income from penalties
+                 * placeholder ID would be the same for both cash and accrual
+                 * based accounts
+                 *****/
+
+        // Vishwas TODO: remove this condition as it should always be true
+        if (accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue()
+                || accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()) {
+            final ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository
+                    .findByProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(loanProductId, PortfolioProductType.LOAN.getValue(),
+                            accountMappingTypeId, chargeId);
+            if (chargeSpecificIncomeAccountMapping != null) {
+                accountMapping = chargeSpecificIncomeAccountMapping;
+            }
+        }
+        return accountMapping.getGlAccount();
+    }
+
+    private GLAccount getLinkedGLAccountForSavingsCharges(final Long savingsProductId, final int accountMappingTypeId,
+            final Long chargeId) {
+        ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId,
+                PortfolioProductType.SAVING.getValue(), accountMappingTypeId);
+                /*****
+                 * Get more specific mappings for Charges and penalties (based
+                 * on the actual charge /penalty coupled with the loan product).
+                 * Note the income from fees and income from penalties
+                 * placeholder ID would be the same for both cash and accrual
+                 * based accounts
+                 *****/
+
+        // Vishwas TODO: remove this condition as it should always be true
+        if (accountMappingTypeId == CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue()
+                || accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()) {
+            final ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository
+                    .findByProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(savingsProductId,
+                            PortfolioProductType.SAVING.getValue(), accountMappingTypeId, chargeId);
+            if (chargeSpecificIncomeAccountMapping != null) {
+                accountMapping = chargeSpecificIncomeAccountMapping;
+            }
+        }
+        return accountMapping.getGlAccount();
+    }
+
+    private GLAccount getLinkedGLAccountForSavingsProduct(final Long savingsProductId, final int accountMappingTypeId,
+            final Long paymentTypeId) {
+        GLAccount glAccount = null;
+        if (isOrganizationAccount(accountMappingTypeId)) {
+            FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository
+                    .findByFinancialActivityTypeWithNotFoundDetection(accountMappingTypeId);
+            glAccount = financialActivityAccount.getGlAccount();
+        } else {
+            ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId,
+                    PortfolioProductType.SAVING.getValue(), accountMappingTypeId);
+            /****
+             * Get more specific mapping for FUND source accounts (based on
+             * payment channels). Note that fund source placeholder ID would be
+             * same for both cash and accrual accounts
+             ***/
+            if (accountMappingTypeId == CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue()) {
+                final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository
+                        .findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(savingsProductId,
+                                PortfolioProductType.SAVING.getValue(), accountMappingTypeId, paymentTypeId);
+                if (paymentChannelSpecificAccountMapping != null) {
+                    accountMapping = paymentChannelSpecificAccountMapping;
+                }
+            }
+            glAccount = accountMapping.getGlAccount();
+        }
+        return glAccount;
+    }
+
+    private boolean isOrganizationAccount(final int accountMappingTypeId) {
+        boolean isOrganizationAccount = false;
+        if (FINANCIAL_ACTIVITY.fromInt(accountMappingTypeId) != null) {
+            isOrganizationAccount = true;
+        }
+        return isOrganizationAccount;
+    }
+
+    public BigDecimal createCreditJournalEntryOrReversalForClientPayments(final Office office, final String currencyCode,
+            final Long clientId, final Long transactionId, final Date transactionDate, final Boolean isReversal,
+            final List<ClientChargePaymentDTO> clientChargePaymentDTOs) {
+        /***
+         * Map to track each account affected and the net credit to be made for
+         * a particular account
+         ***/
+        final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>();
+        for (final ClientChargePaymentDTO clientChargePaymentDTO : clientChargePaymentDTOs) {
+            if (clientChargePaymentDTO.getIncomeAccountId() != null) {
+                final GLAccount chargeSpecificAccount = getGLAccountById(clientChargePaymentDTO.getIncomeAccountId());
+                BigDecimal chargeSpecificAmount = clientChargePaymentDTO.getAmount();
+
+                // adjust net credit amount if the account is already present in
+                // the map
+                if (creditDetailsMap.containsKey(chargeSpecificAccount)) {
+                    final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount);
+                    chargeSpecificAmount = chargeSpecificAmount.add(existingAmount);
+                }
+                creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount);
+            }
+        }
+
+        BigDecimal totalCreditedAmount = BigDecimal.ZERO;
+        for (final Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) {
+            final GLAccount account = entry.getKey();
+            final BigDecimal amount = entry.getValue();
+            totalCreditedAmount = totalCreditedAmount.add(amount);
+            if (isReversal) {
+                createDebitJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount);
+            } else {
+                createCreditJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount);
+            }
+        }
+        return totalCreditedAmount;
+    }
+
+    public void createDebitJournalEntryOrReversalForClientChargePayments(final Office office, final String currencyCode,
+            final Long clientId, final Long transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) {
+        final GLAccount account = financialActivityAccountRepository
+                .findByFinancialActivityTypeWithNotFoundDetection(FINANCIAL_ACTIVITY.ASSET_FUND_SOURCE.getValue()).getGlAccount();
+        if (isReversal) {
+            createCreditJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount);
+        } else {
+            createDebitJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount);
+        }
+    }
+
+    private GLAccount getGLAccountById(final Long accountId) {
+        return this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
new file mode 100755
index 0000000..8287b72
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
@@ -0,0 +1,441 @@
+/**
+ * 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.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.accounting.closure.domain.GLClosure;
+import org.apache.fineract.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
+import org.apache.fineract.accounting.journalentry.data.LoanDTO;
+import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AccrualBasedAccountingProcessorForLoan implements AccountingProcessorForLoan {
+
+    private final AccountingProcessorHelper helper;
+
+    @Autowired
+    public AccrualBasedAccountingProcessorForLoan(final AccountingProcessorHelper accountingProcessorHelper) {
+        this.helper = accountingProcessorHelper;
+    }
+
+    @Override
+    public void createJournalEntriesForLoan(final LoanDTO loanDTO) {
+        final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(loanDTO.getOfficeId());
+        final Office office = this.helper.getOfficeById(loanDTO.getOfficeId());
+        for (final LoanTransactionDTO loanTransactionDTO : loanDTO.getNewLoanTransactions()) {
+            final Date transactionDate = loanTransactionDTO.getTransactionDate();
+            this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
+
+            /** Handle Disbursements **/
+            if (loanTransactionDTO.getTransactionType().isDisbursement()) {
+                createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office);
+            }
+
+            /*** Handle Accruals ***/
+            if (loanTransactionDTO.getTransactionType().isAccrual()) {
+                createJournalEntriesForAccruals(loanDTO, loanTransactionDTO, office);
+            }
+
+            /***
+             * Handle repayments, repayments at disbursement and reversal of
+             * Repayments and Repayments at disbursement
+             ***/
+            else if (loanTransactionDTO.getTransactionType().isRepayment()
+                    || loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()
+                    || loanTransactionDTO.getTransactionType().isChargePayment()) {
+                createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, false, loanTransactionDTO
+                        .getTransactionType().isRepaymentAtDisbursement());
+            }
+
+            /** Logic for handling recovery payments **/
+            else if (loanTransactionDTO.getTransactionType().isRecoveryRepayment()) {
+                createJournalEntriesForRecoveryRepayments(loanDTO, loanTransactionDTO, office);
+            }
+
+            /** Logic for Refunds of Overpayments **/
+            else if (loanTransactionDTO.getTransactionType().isRefund()) {
+                createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
+            }
+
+            /** Handle Write Offs, waivers and their reversals **/
+            else if ((loanTransactionDTO.getTransactionType().isWriteOff() || loanTransactionDTO.getTransactionType().isWaiveInterest() || loanTransactionDTO
+                    .getTransactionType().isWaiveCharges())) {
+                createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true, false);
+            }
+            
+            /** Logic for Refunds of Active Loans **/
+            else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) {
+                createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office);
+            }
+        }
+    }
+
+    /**
+     * Debit loan Portfolio and credit Fund source for Disbursement.
+     * 
+     * @param loanDTO
+     * @param loanTransactionDTO
+     * @param office
+     */
+    private void createJournalEntriesForDisbursements(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office) {
+
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal disbursalAmount = loanTransactionDTO.getAmount();
+        final boolean isReversed = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        // create journal entries for the disbursement (or disbursement
+        // reversal)
+        if (loanTransactionDTO.isAccountTransfer()) {
+            this.helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, disbursalAmount, isReversed);
+        } else {
+            this.helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, disbursalAmount, isReversed);
+        }
+
+    }
+
+    /**
+     * 
+     * Handles repayments using the following posting rules <br/>
+     * <br/>
+     * <br/>
+     * 
+     * <b>Principal Repayment</b>: Debits "Fund Source" and Credits
+     * "Loan Portfolio"<br/>
+     * 
+     * <b>Interest Repayment</b>:Debits "Fund Source" and and Credits
+     * "Receivable Interest" <br/>
+     * 
+     * <b>Fee Repayment</b>:Debits "Fund Source" (or "Interest on Loans" in case
+     * of repayment at disbursement) and and Credits "Receivable Fees" <br/>
+     * 
+     * <b>Penalty Repayment</b>: Debits "Fund Source" and and Credits
+     * "Receivable Penalties" <br/>
+     * <br/>
+     * Handles write offs using the following posting rules <br/>
+     * <br/>
+     * <b>Principal Write off</b>: Debits "Losses Written Off" and Credits
+     * "Loan Portfolio"<br/>
+     * 
+     * <b>Interest Write off</b>:Debits "Losses Written off" and and Credits
+     * "Receivable Interest" <br/>
+     * 
+     * <b>Fee Write off</b>:Debits "Losses Written off" and and Credits
+     * "Receivable Fees" <br/>
+     * 
+     * <b>Penalty Write off</b>: Debits "Losses Written off" and and Credits
+     * "Receivable Penalties" <br/>
+     * <br/>
+     * <br/>
+     * In case the loan transaction has been reversed, all debits are turned
+     * into credits and vice versa
+     * 
+     * @param loanTransactionDTO
+     * @param loanDTO
+     * @param office
+     * 
+     */
+    private void createJournalEntriesForRepaymentsAndWriteOffs(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office, final boolean writeOff, final boolean isIncomeFromFee) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+
+        BigDecimal totalDebitAmount = new BigDecimal(0);
+
+        // handle principal payment or writeOff (and reversals)
+        if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(principalAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, isReversal);
+        }
+
+        // handle interest payment of writeOff (and reversals)
+        if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(interestAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE,
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, isReversal);
+        }
+
+        // handle fees payment of writeOff (and reversals)
+        if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(feesAmount);
+
+            if (isIncomeFromFee) {
+                this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate,
+                        feesAmount, isReversal, loanTransactionDTO.getFeePayments());
+            } else {
+                this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE,
+                        loanProductId, paymentTypeId, loanId, transactionId, transactionDate, feesAmount, isReversal);
+            }
+        }
+
+        // handle penalties payment of writeOff (and reversals)
+        if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+            if (isIncomeFromFee) {
+                this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
+                        penaltiesAmount, isReversal, loanTransactionDTO.getPenaltyPayments());
+            } else {
+                this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE,
+                        loanProductId, paymentTypeId, loanId, transactionId, transactionDate, penaltiesAmount, isReversal);
+            }
+        }
+        
+        if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, isReversal);
+        }
+
+        /**
+         * Single DEBIT transaction for write-offs or Repayments (and their
+         * reversals)
+         ***/
+        if (!(totalDebitAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            if (writeOff) {
+                this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
+                        transactionDate, totalDebitAmount, isReversal);
+            } else {
+                if (loanTransactionDTO.isAccountTransfer()) {
+                    this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode,
+                            FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
+                            transactionDate, totalDebitAmount, isReversal);
+                } else {
+                    this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode,
+                            ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
+                            transactionDate, totalDebitAmount, isReversal);
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a single Debit to fund source and a single credit to
+     * "Income from Recovery"
+     * 
+     * In case the loan transaction is a reversal, all debits are turned into
+     * credits and vice versa
+     */
+    private void createJournalEntriesForRecoveryRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal amount = loanTransactionDTO.getAmount();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        this.helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue(), loanProductId,
+                paymentTypeId, loanId, transactionId, transactionDate, amount, isReversal);
+
+    }
+
+    /**
+     * Recognize the receivable interest <br/>
+     * Debit "Interest Receivable" and Credit "Income from Interest"
+     * 
+     * <b>Fees:</b> Debit <i>Fees Receivable</i> and credit <i>Income from
+     * Fees</i> <br/>
+     * 
+     * <b>Penalties:</b> Debit <i>Penalties Receivable</i> and credit <i>Income
+     * from Penalties</i>
+     * 
+     * Also handles reversals for both fees and payment applications
+     * 
+     * @param loanDTO
+     * @param loanTransactionDTO
+     * @param office
+     */
+    private void createJournalEntriesForAccruals(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
+
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final boolean isReversed = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        // create journal entries for recognizing interest (or reversal)
+        if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            this.helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(),
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, isReversed);
+        }
+        // create journal entries for the fees application (or reversal)
+        if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            this.helper.createAccrualBasedJournalEntriesAndReversalsForLoanCharges(office, currencyCode,
+                    ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(),
+                    loanProductId, loanId, transactionId, transactionDate, feesAmount, isReversed, loanTransactionDTO.getFeePayments());
+        }
+        // create journal entries for the penalties application (or reversal)
+        if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+
+            this.helper.createAccrualBasedJournalEntriesAndReversalsForLoanCharges(office, currencyCode,
+                    ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
+                    loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, isReversed,
+                    loanTransactionDTO.getPenaltyPayments());
+        }
+    }
+
+    private void createJournalEntriesForRefund(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal refundAmount = loanTransactionDTO.getAmount();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        if (loanTransactionDTO.isAccountTransfer()) {
+            this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal);
+        } else {
+            this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal);
+        }
+    }
+    private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
+        // TODO Auto-generated method stub
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        BigDecimal totalDebitAmount = new BigDecimal(0);
+
+        if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(principalAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, !isReversal);
+        }
+
+        if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(interestAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS,
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, !isReversal);
+        }
+
+        if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(feesAmount);
+            
+            List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>();
+            
+            for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getFeePayments()) {
+                chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), 
+                        chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
+            }
+            this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
+                    !isReversal, chargePaymentDTOs);
+        }
+
+        if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+            List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>();
+            
+            for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getPenaltyPayments()) {
+                chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), 
+                        chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
+            }
+            
+            this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
+                    penaltiesAmount, !isReversal, chargePaymentDTOs);
+        }
+
+        if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, !isReversal);
+        }
+
+        /*** create a single debit entry (or reversal) for the entire amount **/
+        this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
+                paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal);
+     
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForClientTransactions.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForClientTransactions.java
new file mode 100644
index 0000000..5b8477d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForClientTransactions.java
@@ -0,0 +1,88 @@
+/**
+ * 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.service;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import org.apache.fineract.accounting.closure.domain.GLClosure;
+import org.apache.fineract.accounting.journalentry.data.ClientTransactionDTO;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CashBasedAccountingProcessorForClientTransactions implements AccountingProcessorForClientTransactions {
+
+    AccountingProcessorHelper helper;
+
+    @Autowired
+    public CashBasedAccountingProcessorForClientTransactions(final AccountingProcessorHelper accountingProcessorHelper) {
+        this.helper = accountingProcessorHelper;
+    }
+
+    @Override
+    public void createJournalEntriesForClientTransaction(ClientTransactionDTO clientTransactionDTO) {
+        if (clientTransactionDTO.getAccountingEnabled()) {
+            final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(clientTransactionDTO.getOfficeId());
+            final Date transactionDate = clientTransactionDTO.getTransactionDate();
+            final Office office = this.helper.getOfficeById(clientTransactionDTO.getOfficeId());
+            this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
+
+            /** Handle client payments **/
+            if (clientTransactionDTO.isChargePayment()) {
+                createJournalEntriesForChargePayments(clientTransactionDTO, office);
+            }
+        }
+    }
+
+    /**
+     * Create a single debit to fund source and multiple credits for the income
+     * account mapped with each charge this payment pays off
+     * 
+     * In case the loan transaction is a reversal, all debits are turned into
+     * credits and vice versa
+     */
+    private void createJournalEntriesForChargePayments(final ClientTransactionDTO clientTransactionDTO, final Office office) {
+        // client properties
+        final Long clientId = clientTransactionDTO.getClientId();
+
+        // transaction properties
+        final String currencyCode = clientTransactionDTO.getCurrencyCode();
+        final Long transactionId = clientTransactionDTO.getTransactionId();
+        final Date transactionDate = clientTransactionDTO.getTransactionDate();
+        final BigDecimal amount = clientTransactionDTO.getAmount();
+        final boolean isReversal = clientTransactionDTO.isReversed();
+
+        if (amount != null && !(amount.compareTo(BigDecimal.ZERO) == 0)) {
+            BigDecimal totalCreditedAmount = this.helper.createCreditJournalEntryOrReversalForClientPayments(office, currencyCode, clientId,
+                    transactionId, transactionDate, isReversal, clientTransactionDTO.getChargePayments());
+
+            /***
+             * create a single Debit entry (or reversal) for the entire amount
+             * that was credited (accounting is turned on at the level of for
+             * each charge that has been paid by this transaction)
+             **/
+            this.helper.createDebitJournalEntryOrReversalForClientChargePayments(office, currencyCode, clientId, transactionId,
+                    transactionDate, totalCreditedAmount, isReversal);
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
new file mode 100755
index 0000000..a71016f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
@@ -0,0 +1,388 @@
+/**
+ * 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.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.accounting.closure.domain.GLClosure;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
+import org.apache.fineract.accounting.journalentry.data.LoanDTO;
+import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CashBasedAccountingProcessorForLoan implements AccountingProcessorForLoan {
+
+    private final AccountingProcessorHelper helper;
+
+    @Autowired
+    public CashBasedAccountingProcessorForLoan(final AccountingProcessorHelper accountingProcessorHelper) {
+        this.helper = accountingProcessorHelper;
+    }
+
+    @Override
+    public void createJournalEntriesForLoan(final LoanDTO loanDTO) {
+        final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(loanDTO.getOfficeId());
+        // final Office office =
+        // this.helper.getOfficeById(loanDTO.getOfficeId());
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+        for (final LoanTransactionDTO loanTransactionDTO : loanDTO.getNewLoanTransactions()) {
+            final Date transactionDate = loanTransactionDTO.getTransactionDate();
+            final String transactionId = loanTransactionDTO.getTransactionId();
+            final Office office = this.helper.getOfficeById(loanTransactionDTO.getOfficeId());
+            final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+            final Long loanId = loanDTO.getLoanId();
+
+            this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
+
+            /** Handle Disbursements and reversals of disbursements **/
+            if (loanTransactionDTO.getTransactionType().isDisbursement()) {
+                createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office);
+            }
+            /***
+             * Logic for repayments, repayments at disbursement and reversal of
+             * Repayments and Repayments at disbursement
+             ***/
+            else if (loanTransactionDTO.getTransactionType().isRepayment()
+                    || loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()
+                    || loanTransactionDTO.getTransactionType().isChargePayment()) {
+                createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office);
+            }
+
+            /** Logic for handling recovery payments **/
+            else if (loanTransactionDTO.getTransactionType().isRecoveryRepayment()) {
+                createJournalEntriesForRecoveryRepayments(loanDTO, loanTransactionDTO, office);
+            }
+
+            /** Logic for Refunds of Overpayments **/
+            else if (loanTransactionDTO.getTransactionType().isRefund()) {
+                createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
+            }
+            /***
+             * Only principal write off affects cash based accounting (interest
+             * and fee write off need not be considered). Debit losses written
+             * off and credit Loan Portfolio
+             **/
+            else if (loanTransactionDTO.getTransactionType().isWriteOff()) {
+                final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+                if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(),
+                            loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount,
+                            loanTransactionDTO.isReversed());
+
+                }
+            } else if (loanTransactionDTO.getTransactionType().isInitiateTransfer()
+                    || loanTransactionDTO.getTransactionType().isApproveTransfer()
+                    || loanTransactionDTO.getTransactionType().isWithdrawTransfer()) {
+                createJournalEntriesForTransfers(loanDTO, loanTransactionDTO, office);
+            }
+            /** Logic for Refunds of Active Loans **/
+            else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) {
+                createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office);
+            }
+        }
+    }
+
+    /**
+     * Debit loan Portfolio and credit Fund source for a Disbursement <br/>
+     * 
+     * All debits are turned into credits and vice versa in case of disbursement
+     * reversals
+     * 
+     * 
+     * @param loanDTO
+     * @param loanTransactionDTO
+     * @param office
+     */
+    private void createJournalEntriesForDisbursements(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal disbursalAmount = loanTransactionDTO.getAmount();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        if (loanTransactionDTO.isAccountTransfer()) {
+            this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, disbursalAmount, isReversal);
+        } else {
+            this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, disbursalAmount, isReversal);
+        }
+
+    }
+
+    /**
+     * Debit loan Portfolio and credit Fund source for a Disbursement <br/>
+     * 
+     * All debits are turned into credits and vice versa in case of disbursement
+     * reversals
+     * 
+     * 
+     * @param loanDTO
+     * @param loanTransactionDTO
+     * @param office
+     */
+    private void createJournalEntriesForRefund(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal refundAmount = loanTransactionDTO.getAmount();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        if (loanTransactionDTO.isAccountTransfer()) {
+            this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal);
+        } else {
+            this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal);
+        }
+    }
+
+    /**
+     * Create a single Debit to fund source and multiple credits if applicable
+     * (loan portfolio for principal repayments, Interest on loans for interest
+     * repayments, Income from fees for fees payment and Income from penalties
+     * for penalty payment)
+     * 
+     * In case the loan transaction is a reversal, all debits are turned into
+     * credits and vice versa
+     */
+    private void createJournalEntriesForRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        BigDecimal totalDebitAmount = new BigDecimal(0);
+
+        if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(principalAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, isReversal);
+        }
+
+        if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(interestAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS,
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, isReversal);
+        }
+
+        if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(feesAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
+                    isReversal, loanTransactionDTO.getFeePayments());
+        }
+
+        if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
+                    penaltiesAmount, isReversal, loanTransactionDTO.getPenaltyPayments());
+        }
+
+        if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, isReversal);
+        }
+
+        /*** create a single debit entry (or reversal) for the entire amount **/
+        if (loanTransactionDTO.isAccountTransfer()) {
+            this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(),
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
+        } else {
+            this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(),
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
+        }
+    }
+
+    /**
+     * Create a single Debit to fund source and a single credit to
+     * "Income from Recovery"
+     * 
+     * In case the loan transaction is a reversal, all debits are turned into
+     * credits and vice versa
+     */
+    private void createJournalEntriesForRecoveryRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal amount = loanTransactionDTO.getAmount();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(),
+                CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
+                transactionDate, amount, isReversal);
+
+    }
+
+    /**
+     * Credit loan Portfolio and Debit Suspense Account for a Transfer
+     * Initiation. A Transfer acceptance would be treated the opposite i.e Debit
+     * Loan Portfolio and Credit Suspense Account <br/>
+     * 
+     * All debits are turned into credits and vice versa in case of Transfer
+     * Initiation disbursals
+     * 
+     * 
+     * @param loanDTO
+     * @param loanTransactionDTO
+     * @param office
+     */
+    private void createJournalEntriesForTransfers(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+        // final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        if (loanTransactionDTO.getTransactionType().isInitiateTransfer()) {
+            this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), loanProductId,
+                    null, loanId, transactionId, transactionDate, principalAmount, isReversal);
+        } else if (loanTransactionDTO.getTransactionType().isApproveTransfer()
+                || loanTransactionDTO.getTransactionType().isWithdrawTransfer()) {
+            this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue(), loanProductId,
+                    null, loanId, transactionId, transactionDate, principalAmount, isReversal);
+        }
+    }
+    
+    private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
+        // TODO Auto-generated method stub
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final Date transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        BigDecimal totalDebitAmount = new BigDecimal(0);
+
+        if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(principalAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, !isReversal);
+        }
+
+        if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(interestAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS,
+                    loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, !isReversal);
+        }
+
+        if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(feesAmount);
+            
+            List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>();
+            
+            for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getFeePayments()) {
+                chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), 
+                        chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
+            }
+            this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
+                    !isReversal, chargePaymentDTOs);
+        }
+
+        if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+            List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>();
+            
+            for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getPenaltyPayments()) {
+                chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), 
+                        chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
+            }
+            
+            this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+                    CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
+                    penaltiesAmount, !isReversal, chargePaymentDTOs);
+        }
+
+        if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
+            totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, !isReversal);
+        }
+
+        /*** create a single debit entry (or reversal) for the entire amount **/
+        this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
+                paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal);
+     
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java
new file mode 100755
index 0000000..2b8198a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java
@@ -0,0 +1,244 @@
+/**
+ * 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.service;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.accounting.closure.domain.GLClosure;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS;
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
+import org.apache.fineract.accounting.journalentry.data.SavingsDTO;
+import org.apache.fineract.accounting.journalentry.data.SavingsTransactionDTO;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CashBasedAccountingProcessorForSavings implements AccountingProcessorForSavings {
+
+    private final AccountingProcessorHelper helper;
+
+    @Autowired
+    public CashBasedAccountingProcessorForSavings(final AccountingProcessorHelper accountingProcessorHelper) {
+        this.helper = accountingProcessorHelper;
+    }
+
+    @Override
+    public void createJournalEntriesForSavings(final SavingsDTO savingsDTO) {
+        final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(savingsDTO.getOfficeId());
+        final Long savingsProductId = savingsDTO.getSavingsProductId();
+        final Long savingsId = savingsDTO.getSavingsId();
+        final String currencyCode = savingsDTO.getCurrencyCode();
+        for (final SavingsTransactionDTO savingsTransactionDTO : savingsDTO.getNewSavingsTransactions()) {
+            final Date transactionDate = savingsTransactionDTO.getTransactionDate();
+            final String transactionId = savingsTransactionDTO.getTransactionId();
+            final Office office = this.helper.getOfficeById(savingsTransactionDTO.getOfficeId());
+            final Long paymentTypeId = savingsTransactionDTO.getPaymentTypeId();
+            final boolean isReversal = savingsTransactionDTO.isReversed();
+            final BigDecimal amount = savingsTransactionDTO.getAmount();
+            final BigDecimal overdraftAmount = savingsTransactionDTO.getOverdraftAmount();
+            final List<ChargePaymentDTO> feePayments = savingsTransactionDTO.getFeePayments();
+            final List<ChargePaymentDTO> penaltyPayments = savingsTransactionDTO.getPenaltyPayments();
+
+            this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
+
+            if (savingsTransactionDTO.getTransactionType().isWithdrawal() && savingsTransactionDTO.isOverdraftTransaction()) {
+                if (savingsTransactionDTO.isAccountTransfer()) {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
+                            FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), savingsProductId, paymentTypeId, savingsId, transactionId,
+                            transactionDate, overdraftAmount, isReversal);
+                    if (amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) == 1) {
+                        this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                                CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(),
+                                savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate,
+                                amount.subtract(overdraftAmount), isReversal);
+                    }
+                } else {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
+                            CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue(), savingsProductId, paymentTypeId, savingsId,
+                            transactionId, transactionDate, overdraftAmount, isReversal);
+                    if (amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) == 1) {
+                        this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                                CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(),
+                                CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue(), savingsProductId, paymentTypeId, savingsId,
+                                transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal);
+                    }
+                }
+            } else if (savingsTransactionDTO.getTransactionType().isDeposit() && savingsTransactionDTO.isOverdraftTransaction()) {
+                if (savingsTransactionDTO.isAccountTransfer()) {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(),
+                            CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+                            transactionId, transactionDate, overdraftAmount, isReversal);
+                    if (amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) == 1) {
+                        this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                                FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(),
+                                savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate,
+                                amount.subtract(overdraftAmount), isReversal);
+                    }
+                } else {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue(),
+                            CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+                            transactionId, transactionDate, overdraftAmount, isReversal);
+                    if (amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) == 1) {
+                        this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                                CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue(),
+                                CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+                                transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal);
+                    }
+                }
+            }
+
+            /** Handle Deposits and reversals of deposits **/
+            else if (savingsTransactionDTO.getTransactionType().isDeposit()) {
+                if (savingsTransactionDTO.isAccountTransfer()) {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(),
+                            savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+                } else {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(),
+                            savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+                }
+            }
+
+            /** Handle withdrawals and reversals of withdrawals **/
+            else if (savingsTransactionDTO.getTransactionType().isWithdrawal()) {
+                if (savingsTransactionDTO.isAccountTransfer()) {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(),
+                            savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+                } else {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue(),
+                            savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+                }
+            }
+
+            /**
+             * Handle Interest Applications and reversals of Interest
+             * Applications
+             **/
+            else if (savingsTransactionDTO.getTransactionType().isInterestPosting() && savingsTransactionDTO.isOverdraftTransaction()) {
+                // Post journal entry if earned interest amount is greater than
+                // zero
+                if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) == 1) {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue(),
+                            CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+                            transactionId, transactionDate, overdraftAmount, isReversal);
+                    if (amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) == 1) {
+                        this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                                CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue(),
+                                CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+                                transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal);
+                    }
+                }
+            }
+
+            else if (savingsTransactionDTO.getTransactionType().isInterestPosting()) {
+                // Post journal entry if earned interest amount is greater than
+                // zero
+                if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) == 1) {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(),
+                            savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+                }
+            }
+
+            /** Handle Fees Deductions and reversals of Fees Deductions **/
+            else if (savingsTransactionDTO.getTransactionType().isFeeDeduction() && savingsTransactionDTO.isOverdraftTransaction()) {
+                // Is the Charge a penalty?
+                if (penaltyPayments.size() > 0) {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES,
+                            savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, overdraftAmount, isReversal,
+                            penaltyPayments);
+                    if (amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) == 1) {
+                        this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+                                CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES,
+                                savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate,
+                                amount.subtract(overdraftAmount), isReversal, penaltyPayments);
+                    }
+                } else {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES,
+                            savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, overdraftAmount, isReversal,
+                            feePayments);
+                    if (amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) == 1) {
+                        this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+                                CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES, savingsProductId,
+                                paymentTypeId, savingsId, transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal,
+                                feePayments);
+                    }
+                }
+            }
+
+            else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) {
+                // Is the Charge a penalty?
+                if (penaltyPayments.size() > 0) {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES, savingsProductId,
+                            paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, penaltyPayments);
+                } else {
+                    this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+                            CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES, savingsProductId,
+                            paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments);
+                }
+            }
+
+            /** Handle Transfers proposal **/
+            else if (savingsTransactionDTO.getTransactionType().isInitiateTransfer()) {
+                this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                        CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue(),
+                        savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+            }
+
+            /** Handle Transfer Withdrawal or Acceptance **/
+            else if (savingsTransactionDTO.getTransactionType().isWithdrawTransfer()
+                    || savingsTransactionDTO.getTransactionType().isApproveTransfer()) {
+                this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                        CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(),
+                        savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+            }
+
+            /** overdraft **/
+            else if (savingsTransactionDTO.getTransactionType().isOverdraftInterest()) {
+                this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                        CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_INTEREST.getValue(),
+                        savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+            } else if (savingsTransactionDTO.getTransactionType().isWrittenoff()) {
+                this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+                        CASH_ACCOUNTS_FOR_SAVINGS.LOSSES_WRITTEN_OFF.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+                        transactionId, transactionDate, amount, isReversal);
+            } else if (savingsTransactionDTO.getTransactionType().isOverdraftFee()) {
+                this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+                        CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES, savingsProductId,
+                        paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments);
+            }
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformService.java
new file mode 100755
index 0000000..b151e82
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformService.java
@@ -0,0 +1,39 @@
+/**
+ * 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.service;
+
+import java.util.Date;
+
+import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
+import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+
+public interface JournalEntryReadPlatformService {
+
+    JournalEntryData retrieveGLJournalEntryById(long glJournalEntryId, JournalEntryAssociationParametersData associationParametersData);
+
+    Page<JournalEntryData> retrieveAll(SearchParameters searchParameters, Long glAccountId, Boolean onlyManualEntries, Date fromDate,
+            Date toDate, String transactionId, Integer entityType, JournalEntryAssociationParametersData associationParametersData);
+
+    OfficeOpeningBalancesData retrieveOfficeOpeningBalances(Long officeId, String currencyCode);
+
+    Page<JournalEntryData> retrieveJournalEntriesByEntityId(String transactionId, Long entityId, Integer entityType) ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java
new file mode 100755
index 0000000..49efaa0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java
@@ -0,0 +1,533 @@
+/**
+ * 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.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
+import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData;
+import org.apache.fineract.accounting.journalentry.data.TransactionDetailData;
+import org.apache.fineract.accounting.journalentry.data.TransactionTypeEnumData;
+import org.apache.fineract.accounting.journalentry.exception.JournalEntriesNotFoundException;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.portfolio.note.data.NoteData;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final GLAccountReadPlatformService glAccountReadPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper;
+
+    private final PaginationHelper<JournalEntryData> paginationHelper = new PaginationHelper<>();
+
+    @Autowired
+    public JournalEntryReadPlatformServiceImpl(final RoutingDataSource dataSource,
+            final GLAccountReadPlatformService glAccountReadPlatformService, final OfficeReadPlatformService officeReadPlatformService,
+            final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.glAccountReadPlatformService = glAccountReadPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.financialActivityAccountRepositoryWrapper = financialActivityAccountRepositoryWrapper;
+    }
+
+    private static final class GLJournalEntryMapper implements RowMapper<JournalEntryData> {
+
+        private final JournalEntryAssociationParametersData associationParametersData;
+
+        public GLJournalEntryMapper(final JournalEntryAssociationParametersData associationParametersData) {
+            if (associationParametersData == null) {
+                this.associationParametersData = new JournalEntryAssociationParametersData();
+            } else {
+                this.associationParametersData = associationParametersData;
+            }
+        }
+
+        public String schema() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(" journalEntry.id as id, glAccount.classification_enum as classification ,")
+                    .append("journalEntry.transaction_id,")
+                    .append(" glAccount.name as glAccountName, glAccount.gl_code as glAccountCode,glAccount.id as glAccountId, ")
+                    .append(" journalEntry.office_id as officeId, office.name as officeName, journalEntry.ref_num as referenceNumber, ")
+                    .append(" journalEntry.manual_entry as manualEntry,journalEntry.entry_date as transactionDate, ")
+                    .append(" journalEntry.type_enum as entryType,journalEntry.amount as amount, journalEntry.transaction_id as transactionId,")
+                    .append(" journalEntry.entity_type_enum as entityType, journalEntry.entity_id as entityId, creatingUser.id as createdByUserId, ")
+                    .append(" creatingUser.username as createdByUserName, journalEntry.description as comments, ")
+                    .append(" journalEntry.created_date as createdDate, journalEntry.reversed as reversed, ")
+                    .append(" journalEntry.currency_code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ")
+                    .append(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf ");
+            if (associationParametersData.isRunningBalanceRequired()) {
+                sb.append(" ,journalEntry.is_running_balance_calculated as runningBalanceComputed, ")
+                        .append(" journalEntry.office_running_balance as officeRunningBalance, ")
+                        .append(" journalEntry.organization_running_balance as organizationRunningBalance ");
+            }
+            if (associationParametersData.isTransactionDetailsRequired()) {
+                sb.append(" ,pd.receipt_number as receiptNumber, ").append(" pd.check_number as checkNumber, ")
+                        .append(" pd.account_number as accountNumber, ").append(" pt.value as paymentTypeName, ")
+                        .append(" pd.payment_type_id as paymentTypeId,").append(" pd.bank_number as bankNumber, ")
+                        .append(" pd.routing_code as routingCode, ").append(" note.id as noteId, ")
+                        .append(" note.note as transactionNote, ").append(" lt.transaction_type_enum as loanTransactionType, ")
+                        .append(" st.transaction_type_enum as savingsTransactionType ");
+            }
+            sb.append(" from acc_gl_journal_entry as journalEntry ")
+                    .append(" left join acc_gl_account as glAccount on glAccount.id = journalEntry.account_id")
+                    .append(" left join m_office as office on office.id = journalEntry.office_id")
+                    .append(" left join m_appuser as creatingUser on creatingUser.id = journalEntry.createdby_id ")
+                    .append(" join m_currency curr on curr.code = journalEntry.currency_code ");
+            if (associationParametersData.isTransactionDetailsRequired()) {
+                sb.append(" left join m_loan_transaction as lt on journalEntry.loan_transaction_id = lt.id ")
+                        .append(" left join m_savings_account_transaction as st on journalEntry.savings_transaction_id = st.id ")
+                        .append(" left join m_payment_detail as pd on lt.payment_detail_id = pd.id or st.payment_detail_id = pd.id or journalEntry.payment_details_id = pd.id")
+                        .append(" left join m_payment_type as pt on pt.id = pd.payment_type_id ")
+                        .append(" left join m_note as note on lt.id = note.loan_transaction_id or st.id = note.savings_account_transaction_id ");
+            }
+            return sb.toString();
+
+        }
+
+        @Override
+        public JournalEntryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long officeId = rs.getLong("officeId");
+            final String officeName = rs.getString("officeName");
+            final String glCode = rs.getString("glAccountCode");
+            final String glAccountName = rs.getString("glAccountName");
+            final Long glAccountId = rs.getLong("glAccountId");
+            final int accountTypeId = JdbcSupport.getInteger(rs, "classification");
+            final EnumOptionData accountType = AccountingEnumerations.gLAccountType(accountTypeId);
+            final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, "transactionDate");
+            final Boolean manualEntry = rs.getBoolean("manualEntry");
+            final BigDecimal amount = rs.getBigDecimal("amount");
+            final int entryTypeId = JdbcSupport.getInteger(rs, "entryType");
+            final EnumOptionData entryType = AccountingEnumerations.journalEntryType(entryTypeId);
+            final String transactionId = rs.getString("transactionId");
+            final Integer entityTypeId = JdbcSupport.getInteger(rs, "entityType");
+            EnumOptionData entityType = null;
+            if (entityTypeId != null) {
+                entityType = AccountingEnumerations.portfolioProductType(entityTypeId);
+
+            }
+
+            final Long entityId = JdbcSupport.getLong(rs, "entityId");
+            final Long createdByUserId = rs.getLong("createdByUserId");
+            final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate");
+            final String createdByUserName = rs.getString("createdByUserName");
+            final String comments = rs.getString("comments");
+            final Boolean reversed = rs.getBoolean("reversed");
+            final String referenceNumber = rs.getString("referenceNumber");
+            BigDecimal officeRunningBalance = null;
+            BigDecimal organizationRunningBalance = null;
+            Boolean runningBalanceComputed = null;
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            if (associationParametersData.isRunningBalanceRequired()) {
+                officeRunningBalance = rs.getBigDecimal("officeRunningBalance");
+                organizationRunningBalance = rs.getBigDecimal("organizationRunningBalance");
+                runningBalanceComputed = rs.getBoolean("runningBalanceComputed");
+            }
+            TransactionDetailData transactionDetailData = null;
+
+            if (associationParametersData.isTransactionDetailsRequired()) {
+                PaymentDetailData paymentDetailData = null;
+                final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentTypeId");
+                if (paymentTypeId != null) {
+                    final String typeName = rs.getString("paymentTypeName");
+                    final PaymentTypeData paymentType = PaymentTypeData.instance(paymentTypeId, typeName);
+                    final String accountNumber = rs.getString("accountNumber");
+                    final String checkNumber = rs.getString("checkNumber");
+                    final String routingCode = rs.getString("routingCode");
+                    final String receiptNumber = rs.getString("receiptNumber");
+                    final String bankNumber = rs.getString("bankNumber");
+                    paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber,
+                            bankNumber);
+                }
+                NoteData noteData = null;
+                final Long noteId = JdbcSupport.getLong(rs, "noteId");
+                if (noteId != null) {
+                    final String note = rs.getString("transactionNote");
+                    noteData = new NoteData(noteId, null, null, null, null, null, null, null, note, null, null, null, null, null, null);
+                }
+                Long transaction = null;
+                if (entityType != null) {
+                    transaction = Long.parseLong(transactionId.substring(1).trim());
+                }
+
+                TransactionTypeEnumData transactionTypeEnumData = null;
+
+                if (PortfolioAccountType.fromInt(entityTypeId).isLoanAccount()) {
+                    final LoanTransactionEnumData loanTransactionType = LoanEnumerations.transactionType(JdbcSupport.getInteger(rs,
+                            "loanTransactionType"));
+                    transactionTypeEnumData = new TransactionTypeEnumData(loanTransactionType.id(), loanTransactionType.getCode(),
+                            loanTransactionType.getValue());
+                } else if (PortfolioAccountType.fromInt(entityTypeId).isSavingsAccount()) {
+                    final SavingsAccountTransactionEnumData savingsTransactionType = SavingsEnumerations.transactionType(JdbcSupport
+                            .getInteger(rs, "savingsTransactionType"));
+                    transactionTypeEnumData = new TransactionTypeEnumData(savingsTransactionType.getId(), savingsTransactionType.getCode(),
+                            savingsTransactionType.getValue());
+                }
+
+                transactionDetailData = new TransactionDetailData(transaction, paymentDetailData, noteData, transactionTypeEnumData);
+            }
+            return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glCode, accountType, transactionDate,
+                    entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, createdDate, createdByUserName,
+                    comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance, runningBalanceComputed,
+                    transactionDetailData, currency);
+        }
+    }
+
+    @Override
+    public Page<JournalEntryData> retrieveAll(final SearchParameters searchParameters, final Long glAccountId,
+            final Boolean onlyManualEntries, final Date fromDate, final Date toDate, final String transactionId, final Integer entityType,
+            final JournalEntryAssociationParametersData associationParametersData) {
+
+        GLJournalEntryMapper rm = new GLJournalEntryMapper(associationParametersData);
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(rm.schema());
+
+        final Object[] objectArray = new Object[15];
+        int arrayPos = 0;
+        String whereClose = " where ";
+
+        if (StringUtils.isNotBlank(transactionId)) {
+            sqlBuilder.append(whereClose + " journalEntry.transaction_id = ?");
+            objectArray[arrayPos] = transactionId;
+            arrayPos = arrayPos + 1;
+
+            whereClose = " and ";
+        }
+
+        if (entityType != null && entityType != 0 && (onlyManualEntries == null)) {
+
+            sqlBuilder.append(whereClose + " journalEntry.entity_type_enum = ?");
+
+            objectArray[arrayPos] = entityType;
+            arrayPos = arrayPos + 1;
+
+            whereClose = " and ";
+        }
+
+        if (searchParameters.isOfficeIdPassed()) {
+            sqlBuilder.append(whereClose + " journalEntry.office_id = ?");
+            objectArray[arrayPos] = searchParameters.getOfficeId();
+            arrayPos = arrayPos + 1;
+
+            whereClose = " and ";
+        }
+
+        if (searchParameters.isCurrencyCodePassed()) {
+            sqlBuilder.append(whereClose + " journalEntry.currency_code = ?");
+            objectArray[arrayPos] = searchParameters.getCurrencyCode();
+            arrayPos = arrayPos + 1;
+
+            whereClose = " and ";
+        }
+
+        if (glAccountId != null && glAccountId != 0) {
+            sqlBuilder.append(whereClose + " journalEntry.account_id = ?");
+            objectArray[arrayPos] = glAccountId;
+            arrayPos = arrayPos + 1;
+
+            whereClose = " and ";
+        }
+
+        if (fromDate != null || toDate != null) {
+            final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+            String fromDateString = null;
+            String toDateString = null;
+            if (fromDate != null && toDate != null) {
+                sqlBuilder.append(whereClose + " journalEntry.entry_date between ? and ? ");
+
+                whereClose = " and ";
+
+                fromDateString = df.format(fromDate);
+                toDateString = df.format(toDate);
+                objectArray[arrayPos] = fromDateString;
+                arrayPos = arrayPos + 1;
+                objectArray[arrayPos] = toDateString;
+                arrayPos = arrayPos + 1;
+            } else if (fromDate != null) {
+                sqlBuilder.append(whereClose + " journalEntry.entry_date >= ? ");
+                fromDateString = df.format(fromDate);
+                objectArray[arrayPos] = fromDateString;
+                arrayPos = arrayPos + 1;
+                whereClose = " and ";
+
+            } else if (toDate != null) {
+                sqlBuilder.append(whereClose + " journalEntry.entry_date <= ? ");
+                toDateString = df.format(toDate);
+                objectArray[arrayPos] = toDateString;
+                arrayPos = arrayPos + 1;
+
+                whereClose = " and ";
+            }
+        }
+
+        if (onlyManualEntries != null) {
+            if (onlyManualEntries) {
+                sqlBuilder.append(whereClose + " journalEntry.manual_entry = 1");
+
+                whereClose = " and ";
+            }
+        }
+
+        if (searchParameters.isLoanIdPassed()) {
+            sqlBuilder.append(whereClose + " journalEntry.loan_transaction_id  in (select id from m_loan_transaction where loan_id = ?)");
+            objectArray[arrayPos] = searchParameters.getLoanId();
+            arrayPos = arrayPos + 1;
+
+            whereClose = " and ";
+        }
+        if (searchParameters.isSavingsIdPassed()) {
+            sqlBuilder
+                    .append(whereClose
+                            + " journalEntry.savings_transaction_id in (select id from m_savings_account_transaction where savings_account_id = ?)");
+            objectArray[arrayPos] = searchParameters.getSavingsId();
+            arrayPos = arrayPos + 1;
+
+            whereClose = " and ";
+        }
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        } else {
+            sqlBuilder.append(" order by journalEntry.entry_date, journalEntry.id");
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray, rm);
+    }
+
+    @Override
+    public JournalEntryData retrieveGLJournalEntryById(final long glJournalEntryId,
+            JournalEntryAssociationParametersData associationParametersData) {
+        try {
+
+            final GLJournalEntryMapper rm = new GLJournalEntryMapper(associationParametersData);
+            final String sql = "select " + rm.schema() + " where journalEntry.id = ?";
+
+            final JournalEntryData glJournalEntryData = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { glJournalEntryId });
+
+            return glJournalEntryData;
+        } catch (final EmptyResultDataAccessException e) {
+            throw new JournalEntriesNotFoundException(glJournalEntryId);
+        }
+    }
+
+    @Override
+    public OfficeOpeningBalancesData retrieveOfficeOpeningBalances(final Long officeId, String currencyCode) {
+
+        final FinancialActivityAccount financialActivityAccountId = this.financialActivityAccountRepositoryWrapper
+                .findByFinancialActivityTypeWithNotFoundDetection(300);
+        final Long contraId = financialActivityAccountId.getGlAccount().getId();
+        if (contraId == null) { throw new GeneralPlatformDomainRuleException(
+                "error.msg.financial.activity.mapping.opening.balance.contra.account.cannot.be.null",
+                "office-opening-balances-contra-account value can not be null", "office-opening-balances-contra-account"); }
+
+        final JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData();
+        final GLAccountData contraAccount = this.glAccountReadPlatformService.retrieveGLAccountById(contraId, associationParametersData);
+        if (!GLAccountType.fromInt(contraAccount.getTypeId()).isEquityType()) { throw new GeneralPlatformDomainRuleException(
+                "error.msg.configuration.opening.balance.contra.account.value.is.invalid.account.type",
+                "Global configuration 'office-opening-balances-contra-account' value is not an equity type account", contraId); }
+
+        final OfficeData officeData = this.officeReadPlatformService.retrieveOffice(officeId);
+        final List<JournalEntryData> allOpeningTransactions = populateAllTransactionsFromGLAccounts(contraId);
+        final String contraTransactionId = retrieveContraAccountTransactionId(officeId, contraId, currencyCode);
+
+        List<JournalEntryData> existingOpeningBalanceTransactions = new ArrayList<>();
+        if (StringUtils.isNotBlank(contraTransactionId)) {
+            existingOpeningBalanceTransactions = retrieveOfficeBalanceTransactions(officeId, contraTransactionId, currencyCode);
+        }
+        final List<JournalEntryData> transactions = populateOpeningBalances(existingOpeningBalanceTransactions, allOpeningTransactions);
+        final List<JournalEntryData> assetAccountOpeningBalances = new ArrayList<>();
+        final List<JournalEntryData> liabityAccountOpeningBalances = new ArrayList<>();
+        final List<JournalEntryData> incomeAccountOpeningBalances = new ArrayList<>();
+        final List<JournalEntryData> equityAccountOpeningBalances = new ArrayList<>();
+        final List<JournalEntryData> expenseAccountOpeningBalances = new ArrayList<>();
+
+        for (final JournalEntryData journalEntryData : transactions) {
+            final GLAccountType type = GLAccountType.fromInt(journalEntryData.getGlAccountType().getId().intValue());
+            if (type.isAssetType()) {
+                assetAccountOpeningBalances.add(journalEntryData);
+            } else if (type.isLiabilityType()) {
+                liabityAccountOpeningBalances.add(journalEntryData);
+            } else if (type.isEquityType()) {
+                equityAccountOpeningBalances.add(journalEntryData);
+            } else if (type.isIncomeType()) {
+                incomeAccountOpeningBalances.add(journalEntryData);
+            } else if (type.isExpenseType()) {
+                expenseAccountOpeningBalances.add(journalEntryData);
+            }
+        }
+
+        final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
+
+        final OfficeOpeningBalancesData officeOpeningBalancesData = OfficeOpeningBalancesData.createNew(officeId, officeData.name(),
+                transactionDate, contraAccount, assetAccountOpeningBalances, liabityAccountOpeningBalances, incomeAccountOpeningBalances,
+                equityAccountOpeningBalances, expenseAccountOpeningBalances);
+        return officeOpeningBalancesData;
+    }
+
+    private List<JournalEntryData> populateOpeningBalances(final List<JournalEntryData> existingOpeningBalanceTransactions,
+            final List<JournalEntryData> allOpeningTransactions) {
+        final List<JournalEntryData> allOpeningBalnceTransactions = new ArrayList<>(allOpeningTransactions.size());
+        for (final JournalEntryData newOpeningBalanceTransaction : allOpeningTransactions) {
+            boolean isNewTransactionAddedToCollection = false;
+            for (final JournalEntryData existingOpeningBalanceTransaction : existingOpeningBalanceTransactions) {
+                if (newOpeningBalanceTransaction.getGlAccountId().equals(existingOpeningBalanceTransaction.getGlAccountId())) {
+                    allOpeningBalnceTransactions.add(existingOpeningBalanceTransaction);
+                    isNewTransactionAddedToCollection = true;
+                    break;
+                }
+            }
+            if (!isNewTransactionAddedToCollection) {
+                allOpeningBalnceTransactions.add(newOpeningBalanceTransaction);
+            }
+        }
+        return allOpeningBalnceTransactions;
+    }
+
+    private List<JournalEntryData> populateAllTransactionsFromGLAccounts(final Long contraId) {
+        final List<GLAccountData> glAccounts = this.glAccountReadPlatformService.retrieveAllEnabledDetailGLAccounts();
+        final List<JournalEntryData> openingBalanceTransactions = new ArrayList<>(glAccounts.size());
+
+        for (final GLAccountData glAccountData : glAccounts) {
+            if (!contraId.equals(glAccountData.getId())) {
+                final JournalEntryData openingBalanceTransaction = JournalEntryData.fromGLAccountData(glAccountData);
+                openingBalanceTransactions.add(openingBalanceTransaction);
+            }
+        }
+        return openingBalanceTransactions;
+    }
+
+    private List<JournalEntryData> retrieveOfficeBalanceTransactions(final Long officeId, final String transactionId,
+            final String currencyCode) {
+        final Long contraId = null;
+        return retrieveContraTransactions(officeId, contraId, transactionId, currencyCode).getPageItems();
+    }
+
+    private String retrieveContraAccountTransactionId(final Long officeId, final Long contraId, final String currencyCode) {
+        final String transactionId = "";
+        final Page<JournalEntryData> contraJournalEntries = retrieveContraTransactions(officeId, contraId, transactionId, currencyCode);
+        if (!CollectionUtils.isEmpty(contraJournalEntries.getPageItems())) {
+            final JournalEntryData contraTransaction = contraJournalEntries.getPageItems().get(
+                    contraJournalEntries.getPageItems().size() - 1);
+            return contraTransaction.getTransactionId();
+        }
+        return transactionId;
+    }
+
+    private Page<JournalEntryData> retrieveContraTransactions(final Long officeId, final Long contraId, final String transactionId,
+            final String currencyCode) {
+        final Integer offset = 0;
+        final Integer limit = null;
+        final String orderBy = "journalEntry.id";
+        final String sortOrder = "ASC";
+        final Integer entityType = null;
+        final Boolean onlyManualEntries = null;
+        final Date fromDate = null;
+        final Date toDate = null;
+        final JournalEntryAssociationParametersData associationParametersData = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+
+        final SearchParameters searchParameters = SearchParameters.forJournalEntries(officeId, offset, limit, orderBy, sortOrder, loanId,
+                savingsId, currencyCode);
+        return retrieveAll(searchParameters, contraId, onlyManualEntries, fromDate, toDate, transactionId, entityType,
+                associationParametersData);
+
+    }
+
+    @Override
+    public Page<JournalEntryData> retrieveJournalEntriesByEntityId(String transactionId, Long entityId, Integer entityType) {
+        JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(true,
+                true);
+        try {
+            final GLJournalEntryMapper rm = new GLJournalEntryMapper(associationParametersData);
+            final String sql = "select " + rm.schema() + " where journalEntry.transaction_id = ? and journalEntry.entity_id = ? and journalEntry.entity_type_enum = ?";
+            final String sqlCountRows = "SELECT FOUND_ROWS()";
+            Object[] data = {transactionId, entityId, entityType} ;
+            return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sql, data, rm);
+        } catch (final EmptyResultDataAccessException e) {
+            throw new JournalEntriesNotFoundException(entityId);
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateService.java
new file mode 100755
index 0000000..d6f4586
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateService.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.accounting.journalentry.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface JournalEntryRunningBalanceUpdateService {
+
+    void updateRunningBalance();
+
+    CommandProcessingResult updateOfficeRunningBalance(JsonCommand command);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java
new file mode 100755
index 0000000..268b2e0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java
@@ -0,0 +1,302 @@
+/**
+ * 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.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.journalentry.api.JournalEntryJsonInputParams;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
+import org.apache.fineract.accounting.journalentry.data.JournalEntryDataValidator;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+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.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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 JournalEntryRunningBalanceUpdateServiceImpl implements JournalEntryRunningBalanceUpdateService {
+
+    private final static Logger logger = LoggerFactory.getLogger(JournalEntryRunningBalanceUpdateServiceImpl.class);
+
+    private final JdbcTemplate jdbcTemplate;
+
+    private final OfficeRepository officeRepository;
+
+    private final JournalEntryDataValidator dataValidator;
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    private final GLJournalEntryMapper entryMapper = new GLJournalEntryMapper();
+    
+    // if a limit is not added to the running balance select statements below and the resultset is more than 400,000, 
+    // the script will eat up all of the server memory
+    private final String selectRunningBalanceSqlLimit = "limit 0, 10000";
+    
+    private final String officeRunningBalanceSql = "select je.office_running_balance as runningBalance,je.account_id as accountId from acc_gl_journal_entry je "
+            + "inner join (select max(id) as id from acc_gl_journal_entry where office_id=?  and entry_date < ? group by account_id,entry_date) je2 "
+            + "inner join (select max(entry_date) as date from acc_gl_journal_entry where office_id=? and entry_date < ? group by account_id) je3 "
+            + "where je2.id = je.id and je.entry_date = je3.date group by je.id order by je.entry_date DESC " + selectRunningBalanceSqlLimit;
+
+    private final String organizationRunningBalanceSql = "select je.organization_running_balance as runningBalance,je.account_id as accountId from acc_gl_journal_entry je "
+            + "inner join (select max(id) as id from acc_gl_journal_entry where entry_date < ? group by account_id,entry_date) je2 "
+            + "inner join (select max(entry_date) as date from acc_gl_journal_entry where entry_date < ? group by account_id) je3 "
+            + "where je2.id = je.id and je.entry_date = je3.date group by je.id order by je.entry_date DESC " + selectRunningBalanceSqlLimit;
+
+    private final String officesRunningBalanceSql = "select je.office_running_balance as runningBalance,je.account_id as accountId,je.office_id as officeId "
+            + "from acc_gl_journal_entry je "
+            + "inner join (select max(id) as id from acc_gl_journal_entry where entry_date < ? group by office_id,account_id,entry_date) je2 "
+            + "inner join (select max(entry_date) as date from acc_gl_journal_entry where entry_date < ? group by office_id,account_id) je3 "
+            + "where je2.id = je.id and je.entry_date = je3.date group by je.id order by je.entry_date DESC " + selectRunningBalanceSqlLimit;
+
+    @Autowired
+    public JournalEntryRunningBalanceUpdateServiceImpl(final RoutingDataSource dataSource, final OfficeRepository officeRepository,
+            final JournalEntryDataValidator dataValidator, final FromJsonHelper fromApiJsonHelper) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.officeRepository = officeRepository;
+        this.dataValidator = dataValidator;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.ACCOUNTING_RUNNING_BALANCE_UPDATE)
+    public void updateRunningBalance() {
+        String dateFinder = "select MIN(je.entry_date) as entityDate from acc_gl_journal_entry  je "
+                + "where je.is_running_balance_calculated=0 ";
+        try {
+            Date entityDate = this.jdbcTemplate.queryForObject(dateFinder, Date.class);
+            updateOrganizationRunningBalance(entityDate);
+        } catch (EmptyResultDataAccessException e) {
+            logger.debug("No results found for updation of running balance ");
+        }
+    }
+
+    @Override
+    public CommandProcessingResult updateOfficeRunningBalance(JsonCommand command) {
+        this.dataValidator.validateForUpdateRunningbalance(command);
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(JournalEntryJsonInputParams.OFFICE_ID.getValue(),
+                command.parsedJson());
+        CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder().withCommandId(command
+                .commandId());
+        if (officeId == null) {
+            updateRunningBalance();
+        } else {
+            final Office office = this.officeRepository.findOne(officeId);
+            if (office == null) { throw new OfficeNotFoundException(officeId); }
+
+            String dateFinder = "select MIN(je.entry_date) as entityDate " + "from acc_gl_journal_entry  je "
+                    + "where je.is_running_balance_calculated=0  and je.office_id=?";
+            try {
+                Date entityDate = this.jdbcTemplate.queryForObject(dateFinder, Date.class, officeId);
+                updateRunningBalance(officeId, entityDate);
+            } catch (EmptyResultDataAccessException e) {
+                logger.debug("No results found for updation of office running balance with office id:" + officeId);
+            }
+            commandProcessingResultBuilder.withOfficeId(officeId);
+        }
+        return commandProcessingResultBuilder.build();
+    }
+
+    private void updateOrganizationRunningBalance(Date entityDate) {
+        Map<Long, BigDecimal> runningBalanceMap = new HashMap<>(5);
+        Map<Long, Map<Long, BigDecimal>> officesRunningBalance = new HashMap<>();
+
+        List<Map<String, Object>> list = jdbcTemplate.queryForList(organizationRunningBalanceSql, entityDate, entityDate);
+        for (Map<String, Object> entries : list) {
+            Long accountId = (Long) entries.get("accountId");
+            if (!runningBalanceMap.containsKey(accountId)) {
+                runningBalanceMap.put(accountId, (BigDecimal) entries.get("runningBalance"));
+            }
+        }
+
+        List<Map<String, Object>> officesRunningBalanceList = jdbcTemplate.queryForList(officesRunningBalanceSql, entityDate, entityDate);
+        for (Map<String, Object> entries : officesRunningBalanceList) {
+            Long accountId = (Long) entries.get("accountId");
+            Long officeId = (Long) entries.get("officeId");
+            Map<Long, BigDecimal> runningBalance = null;
+            if (officesRunningBalance.containsKey(officeId)) {
+                runningBalance = officesRunningBalance.get(officeId);
+            } else {
+                runningBalance = new HashMap<>();
+                officesRunningBalance.put(officeId, runningBalance);
+            }
+            if (!runningBalance.containsKey(accountId)) {
+                runningBalance.put(accountId, (BigDecimal) entries.get("runningBalance"));
+            }
+        }
+
+        List<JournalEntryData> entryDatas = jdbcTemplate.query(entryMapper.organizationRunningBalanceSchema(), entryMapper,
+                new Object[] { entityDate });
+        if (entryDatas.size() > 0) {
+            // run a batch update of 1000 SQL statements at a time
+            final Integer batchUpdateSize = 1000;
+            final Integer batchUpdateSizeMinusOne = batchUpdateSize - 1;
+            String[] updateSql = new String[batchUpdateSize];
+            int i = 0;
+            for (JournalEntryData entryData : entryDatas) {
+                Map<Long, BigDecimal> officeRunningBalanceMap = null;
+                if (officesRunningBalance.containsKey(entryData.getOfficeId())) {
+                    officeRunningBalanceMap = officesRunningBalance.get(entryData.getOfficeId());
+                } else {
+                    officeRunningBalanceMap = new HashMap<>();
+                    officesRunningBalance.put(entryData.getOfficeId(), officeRunningBalanceMap);
+                }
+                BigDecimal officeRunningBalance = calculateRunningBalance(entryData, officeRunningBalanceMap);
+                BigDecimal runningBalance = calculateRunningBalance(entryData, runningBalanceMap);
+                String sql = "UPDATE acc_gl_journal_entry je SET je.is_running_balance_calculated=1, je.organization_running_balance="
+                        + runningBalance + ",je.office_running_balance=" + officeRunningBalance + " WHERE  je.id=" + entryData.getId();
+                updateSql[i++] = sql;
+                
+                if (i == batchUpdateSizeMinusOne) {
+                    // run a batch update of the 1000 update SQL statements
+                    this.jdbcTemplate.batchUpdate(updateSql);
+                    
+                    // reset counter and string array
+                    i = 0;
+                    updateSql = new String[batchUpdateSize];
+                }
+            }
+            this.jdbcTemplate.batchUpdate(updateSql);
+        }
+
+    }
+
+    private void updateRunningBalance(Long officeId, Date entityDate) {
+        Map<Long, BigDecimal> runningBalanceMap = new HashMap<>(5);
+
+        List<Map<String, Object>> list = jdbcTemplate.queryForList(officeRunningBalanceSql, officeId, entityDate, officeId, entityDate);
+        for (Map<String, Object> entries : list) {
+            Long accountId = (Long) entries.get("accountId");
+            if (!runningBalanceMap.containsKey(accountId)) {
+                runningBalanceMap.put(accountId, (BigDecimal) entries.get("runningBalance"));
+            }
+        }
+        List<JournalEntryData> entryDatas = jdbcTemplate.query(entryMapper.officeRunningBalanceSchema(), entryMapper, new Object[] {
+                officeId, entityDate });
+        String[] updateSql = new String[entryDatas.size()];
+        int i = 0;
+        for (JournalEntryData entryData : entryDatas) {
+            BigDecimal runningBalance = calculateRunningBalance(entryData, runningBalanceMap);
+            String sql = "UPDATE acc_gl_journal_entry je SET je.office_running_balance=" + runningBalance + " WHERE  je.id="
+                    + entryData.getId();
+            updateSql[i++] = sql;
+        }
+        this.jdbcTemplate.batchUpdate(updateSql);
+    }
+
+    private BigDecimal calculateRunningBalance(JournalEntryData entry, Map<Long, BigDecimal> runningBalanceMap) {
+        BigDecimal runningBalance = BigDecimal.ZERO;
+        if (runningBalanceMap.containsKey(entry.getGlAccountId())) {
+            runningBalance = runningBalanceMap.get(entry.getGlAccountId());
+        }
+        GLAccountType accounttype = GLAccountType.fromInt(entry.getGlAccountType().getId().intValue());
+        JournalEntryType entryType = JournalEntryType.fromInt(entry.getEntryType().getId().intValue());
+        boolean isIncrease = false;
+        switch (accounttype) {
+            case ASSET:
+                if (entryType.isDebitType()) {
+                    isIncrease = true;
+                }
+            break;
+            case EQUITY:
+                if (entryType.isCreditType()) {
+                    isIncrease = true;
+                }
+            break;
+            case EXPENSE:
+                if (entryType.isDebitType()) {
+                    isIncrease = true;
+                }
+            break;
+            case INCOME:
+                if (entryType.isCreditType()) {
+                    isIncrease = true;
+                }
+            break;
+            case LIABILITY:
+                if (entryType.isCreditType()) {
+                    isIncrease = true;
+                }
+            break;
+        }
+        if (isIncrease) {
+            runningBalance = runningBalance.add(entry.getAmount());
+        } else {
+            runningBalance = runningBalance.subtract(entry.getAmount());
+        }
+        runningBalanceMap.put(entry.getGlAccountId(), runningBalance);
+        return runningBalance;
+    }
+
+    private static final class GLJournalEntryMapper implements RowMapper<JournalEntryData> {
+
+        public String officeRunningBalanceSchema() {
+            return "select je.id as id,je.account_id as glAccountId,je.type_enum as entryType,je.amount as amount, "
+                    + "glAccount.classification_enum as classification,je.office_id as officeId "
+                    + "from acc_gl_journal_entry je , acc_gl_account glAccount " + "where je.account_id = glAccount.id "
+                    + "and je.office_id=? and je.entry_date >= ? order by je.entry_date,je.id";
+        }
+
+        public String organizationRunningBalanceSchema() {
+            return "select je.id as id,je.account_id as glAccountId," + "je.type_enum as entryType,je.amount as amount, "
+                    + "glAccount.classification_enum as classification,je.office_id as officeId  "
+                    + "from acc_gl_journal_entry je , acc_gl_account glAccount " + "where je.account_id = glAccount.id "
+                    + "and je.entry_date >= ? order by je.entry_date,je.id";
+        }
+
+        @Override
+        public JournalEntryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long glAccountId = rs.getLong("glAccountId");
+            final Long officeId = rs.getLong("officeId");
+            final int accountTypeId = JdbcSupport.getInteger(rs, "classification");
+            final EnumOptionData accountType = AccountingEnumerations.gLAccountType(accountTypeId);
+            final BigDecimal amount = rs.getBigDecimal("amount");
+            final int entryTypeId = JdbcSupport.getInteger(rs, "entryType");
+            final EnumOptionData entryType = AccountingEnumerations.journalEntryType(entryTypeId);
+
+            return new JournalEntryData(id, officeId, null, null, glAccountId, null, accountType, null, entryType, amount, null, null,
+                    null, null, null, null, null, null, null, null, null, null, null, null, null);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java
new file mode 100755
index 0000000..480e722
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java
@@ -0,0 +1,45 @@
+/**
+ * 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.service;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntry;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface JournalEntryWritePlatformService {
+
+    CommandProcessingResult createJournalEntry(JsonCommand command);
+
+    CommandProcessingResult revertJournalEntry(JsonCommand command);
+
+    void createJournalEntriesForLoan(Map<String, Object> accountingBridgeData);
+
+    void createJournalEntriesForSavings(Map<String, Object> accountingBridgeData);
+
+    void createJournalEntriesForClientTransactions(Map<String, Object> accountingBridgeData);
+
+    CommandProcessingResult defineOpeningBalance(JsonCommand command);
+    
+    public String revertProvisioningJournalEntries(final Date reversalTransactionDate, final Long entityId, final Integer entityType) ;
+
+    public String createProvisioningJournalEntries(ProvisioningEntry entry) ;
+}
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
new file mode 100755
index 0000000..4ac3dc2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,709 @@
+/**
+ * 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.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.closure.domain.GLClosure;
+import org.apache.fineract.accounting.closure.domain.GLClosureRepository;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
+import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException;
+import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService;
+import org.apache.fineract.accounting.journalentry.api.JournalEntryJsonInputParams;
+import org.apache.fineract.accounting.journalentry.command.JournalEntryCommand;
+import org.apache.fineract.accounting.journalentry.command.SingleDebitOrCreditEntryCommand;
+import org.apache.fineract.accounting.journalentry.data.ClientTransactionDTO;
+import org.apache.fineract.accounting.journalentry.data.LoanDTO;
+import org.apache.fineract.accounting.journalentry.data.SavingsDTO;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+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.serialization.JournalEntryCommandFromApiJsonDeserializer;
+import org.apache.fineract.accounting.provisioning.domain.LoanProductProvisioningEntry;
+import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntry;
+import org.apache.fineract.accounting.rule.domain.AccountingRule;
+import org.apache.fineract.accounting.rule.domain.AccountingRuleRepository;
+import org.apache.fineract.accounting.rule.exception.AccountingRuleNotFoundException;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.domain.OrganisationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.apache.fineract.portfolio.client.domain.ClientTransaction;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements JournalEntryWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(JournalEntryWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final GLClosureRepository glClosureRepository;
+    private final GLAccountRepository glAccountRepository;
+    private final JournalEntryRepository glJournalEntryRepository;
+    private final OfficeRepository officeRepository;
+    private final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory;
+    private final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory;
+    private final AccountingProcessorHelper helper;
+    private final JournalEntryCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final AccountingRuleRepository accountingRuleRepository;
+    private final GLAccountReadPlatformService glAccountReadPlatformService;
+    private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository;
+    private final PlatformSecurityContext context;
+    private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
+    private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper;
+    private final CashBasedAccountingProcessorForClientTransactions accountingProcessorForClientTransactions;
+
+    @Autowired
+    public JournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository,
+            final JournalEntryRepository glJournalEntryRepository, final OfficeRepository officeRepository,
+            final GLAccountRepository glAccountRepository, final JournalEntryCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final AccountingProcessorHelper accountingProcessorHelper, final AccountingRuleRepository accountingRuleRepository,
+            final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory,
+            final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory,
+            final GLAccountReadPlatformService glAccountReadPlatformService,
+            final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository, final PlatformSecurityContext context,
+            final PaymentDetailWritePlatformService paymentDetailWritePlatformService,
+            final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper,
+            final CashBasedAccountingProcessorForClientTransactions accountingProcessorForClientTransactions) {
+        this.glClosureRepository = glClosureRepository;
+        this.officeRepository = officeRepository;
+        this.glJournalEntryRepository = glJournalEntryRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.glAccountRepository = glAccountRepository;
+        this.accountingProcessorForLoanFactory = accountingProcessorForLoanFactory;
+        this.accountingProcessorForSavingsFactory = accountingProcessorForSavingsFactory;
+        this.helper = accountingProcessorHelper;
+        this.accountingRuleRepository = accountingRuleRepository;
+        this.glAccountReadPlatformService = glAccountReadPlatformService;
+        this.organisationCurrencyRepository = organisationCurrencyRepository;
+        this.context = context;
+        this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
+        this.financialActivityAccountRepositoryWrapper = financialActivityAccountRepositoryWrapper;
+        this.accountingProcessorForClientTransactions = accountingProcessorForClientTransactions;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createJournalEntry(final JsonCommand command) {
+        try {
+            final JournalEntryCommand journalEntryCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
+            journalEntryCommand.validateForCreate();
+
+            // check office is valid
+            final Long officeId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.OFFICE_ID.getValue());
+            final Office office = this.officeRepository.findOne(officeId);
+            if (office == null) { throw new OfficeNotFoundException(officeId); }
+
+            final Long accountRuleId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.ACCOUNTING_RULE.getValue());
+            final String currencyCode = command.stringValueOfParameterNamed(JournalEntryJsonInputParams.CURRENCY_CODE.getValue());
+
+            validateBusinessRulesForJournalEntries(journalEntryCommand);
+
+            /** Capture payment details **/
+            final Map<String, Object> changes = new LinkedHashMap<>();
+            final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+            /** Set a transaction Id and save these Journal entries **/
+            final Date transactionDate = command.DateValueOfParameterNamed(JournalEntryJsonInputParams.TRANSACTION_DATE.getValue());
+            final String transactionId = generateTransactionId(officeId);
+            final String referenceNumber = command.stringValueOfParameterNamed(JournalEntryJsonInputParams.REFERENCE_NUMBER.getValue());
+
+            if (accountRuleId != null) {
+
+                final AccountingRule accountingRule = this.accountingRuleRepository.findOne(accountRuleId);
+                if (accountingRule == null) { throw new AccountingRuleNotFoundException(accountRuleId); }
+
+                if (accountingRule.getAccountToCredit() == null) {
+                    if (journalEntryCommand.getCredits() == null) { throw new JournalEntryInvalidException(
+                            GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, null, null); }
+                    if (journalEntryCommand.getDebits() != null) {
+                        checkDebitOrCreditAccountsAreValid(accountingRule, journalEntryCommand.getCredits(),
+                                journalEntryCommand.getDebits());
+                        checkDebitAndCreditAmounts(journalEntryCommand.getCredits(), journalEntryCommand.getDebits());
+                    }
+
+                    saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate,
+                            journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber);
+                } else {
+                    final GLAccount creditAccountHead = accountingRule.getAccountToCredit();
+                    validateGLAccountForTransaction(creditAccountHead);
+                    validateDebitOrCreditArrayForExistingGLAccount(creditAccountHead, journalEntryCommand.getCredits());
+                    saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate,
+                            journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber);
+                }
+
+                if (accountingRule.getAccountToDebit() == null) {
+                    if (journalEntryCommand.getDebits() == null) { throw new JournalEntryInvalidException(
+                            GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, null, null); }
+                    if (journalEntryCommand.getCredits() != null) {
+                        checkDebitOrCreditAccountsAreValid(accountingRule, journalEntryCommand.getCredits(),
+                                journalEntryCommand.getDebits());
+                        checkDebitAndCreditAmounts(journalEntryCommand.getCredits(), journalEntryCommand.getDebits());
+                    }
+
+                    saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate,
+                            journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber);
+                } else {
+                    final GLAccount debitAccountHead = accountingRule.getAccountToDebit();
+                    validateGLAccountForTransaction(debitAccountHead);
+                    validateDebitOrCreditArrayForExistingGLAccount(debitAccountHead, journalEntryCommand.getDebits());
+                    saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate,
+                            journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber);
+                }
+            } else {
+
+                saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate,
+                        journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber);
+
+                saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate,
+                        journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber);
+
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
+                    .withTransactionId(transactionId).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleJournalEntryDataIntegrityIssues(dve);
+            return null;
+        }
+    }
+
+    private void validateDebitOrCreditArrayForExistingGLAccount(final GLAccount glaccount,
+            final SingleDebitOrCreditEntryCommand[] creditOrDebits) {
+        /**
+         * If a glaccount is assigned for a rule the credits or debits array
+         * should have only one entry and it must be same as existing account
+         */
+        if (creditOrDebits.length != 1) { throw new JournalEntryInvalidException(
+                GL_JOURNAL_ENTRY_INVALID_REASON.INVALID_DEBIT_OR_CREDIT_ACCOUNTS, null, null, null); }
+        for (final SingleDebitOrCreditEntryCommand creditOrDebit : creditOrDebits) {
+            if (!glaccount.getId().equals(creditOrDebit.getGlAccountId())) { throw new JournalEntryInvalidException(
+                    GL_JOURNAL_ENTRY_INVALID_REASON.INVALID_DEBIT_OR_CREDIT_ACCOUNTS, null, null, null); }
+        }
+    }
+
+    @SuppressWarnings("null")
+    private void checkDebitOrCreditAccountsAreValid(final AccountingRule accountingRule, final SingleDebitOrCreditEntryCommand[] credits,
+            final SingleDebitOrCreditEntryCommand[] debits) {
+        // Validate the debit and credit arrays are appropriate accounts
+        List<GLAccountDataForLookup> allowedCreditGLAccounts = new ArrayList<>();
+        List<GLAccountDataForLookup> allowedDebitGLAccounts = new ArrayList<>();
+        final SingleDebitOrCreditEntryCommand[] validCredits = new SingleDebitOrCreditEntryCommand[credits.length];
+        final SingleDebitOrCreditEntryCommand[] validDebits = new SingleDebitOrCreditEntryCommand[debits.length];
+
+        if (credits != null && credits.length > 0) {
+            allowedCreditGLAccounts = this.glAccountReadPlatformService.retrieveAccountsByTagId(accountingRule.getId(),
+                    JournalEntryType.CREDIT.getValue());
+            for (final GLAccountDataForLookup accountDataForLookup : allowedCreditGLAccounts) {
+                for (int i = 0; i < credits.length; i++) {
+                    final SingleDebitOrCreditEntryCommand credit = credits[i];
+                    if (credit.getGlAccountId().equals(accountDataForLookup.getId())) {
+                        validCredits[i] = credit;
+                    }
+                }
+            }
+            if (credits.length != validCredits.length) { throw new RuntimeException("Invalid credits"); }
+        }
+
+        if (debits != null && debits.length > 0) {
+            allowedDebitGLAccounts = this.glAccountReadPlatformService.retrieveAccountsByTagId(accountingRule.getId(),
+                    JournalEntryType.DEBIT.getValue());
+            for (final GLAccountDataForLookup accountDataForLookup : allowedDebitGLAccounts) {
+                for (int i = 0; i < debits.length; i++) {
+                    final SingleDebitOrCreditEntryCommand debit = debits[i];
+                    if (debit.getGlAccountId().equals(accountDataForLookup.getId())) {
+                        validDebits[i] = debit;
+                    }
+                }
+            }
+            if (debits.length != validDebits.length) { throw new RuntimeException("Invalid debits"); }
+        }
+    }
+
+    private void checkDebitAndCreditAmounts(final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits) {
+        // sum of all debits must be = sum of all credits
+        BigDecimal creditsSum = BigDecimal.ZERO;
+        BigDecimal debitsSum = BigDecimal.ZERO;
+        for (final SingleDebitOrCreditEntryCommand creditEntryCommand : credits) {
+            if (creditEntryCommand.getAmount() == null || creditEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException(
+                    GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); }
+            creditsSum = creditsSum.add(creditEntryCommand.getAmount());
+        }
+        for (final SingleDebitOrCreditEntryCommand debitEntryCommand : debits) {
+            if (debitEntryCommand.getAmount() == null || debitEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException(
+                    GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); }
+            debitsSum = debitsSum.add(debitEntryCommand.getAmount());
+        }
+        if (creditsSum.compareTo(debitsSum) != 0) { throw new JournalEntryInvalidException(
+                GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_SUM_MISMATCH, null, null, null); }
+    }
+
+    private void validateGLAccountForTransaction(final GLAccount creditOrDebitAccountHead) {
+        /***
+         * validate that the account allows manual adjustments and is not
+         * disabled
+         **/
+        if (creditOrDebitAccountHead.isDisabled()) {
+            throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.GL_ACCOUNT_DISABLED, null,
+                    creditOrDebitAccountHead.getName(), creditOrDebitAccountHead.getGlCode());
+        } else if (!creditOrDebitAccountHead.isManualEntriesAllowed()) { throw new JournalEntryInvalidException(
+                GL_JOURNAL_ENTRY_INVALID_REASON.GL_ACCOUNT_MANUAL_ENTRIES_NOT_PERMITTED, null, creditOrDebitAccountHead.getName(),
+                creditOrDebitAccountHead.getGlCode()); }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult revertJournalEntry(final JsonCommand command) {
+        // is the transaction Id valid
+        final List<JournalEntry> journalEntries = this.glJournalEntryRepository.findUnReversedManualJournalEntriesByTransactionId(command
+                .getTransactionId());
+        String reversalComment = command.stringValueOfParameterNamed("comments");
+
+        if (journalEntries.size() <= 1) { throw new JournalEntriesNotFoundException(command.getTransactionId()); }
+        final String reversalTransactionId = revertJournalEntry(journalEntries, reversalComment);
+        return new CommandProcessingResultBuilder().withTransactionId(reversalTransactionId).build();
+    }
+
+    public String revertJournalEntry(final List<JournalEntry> journalEntries, String reversalComment) {
+        final Long officeId = journalEntries.get(0).getOffice().getId();
+        final String reversalTransactionId = generateTransactionId(officeId);
+        final boolean manualEntry = true;
+
+        final boolean useDefaultComment = StringUtils.isBlank(reversalComment);
+
+        validateCommentForReversal(reversalComment);
+
+        for (final JournalEntry journalEntry : journalEntries) {
+            JournalEntry reversalJournalEntry;
+            if (useDefaultComment) {
+                reversalComment = "Reversal entry for Journal Entry with Entry Id  :" + journalEntry.getId() + " and transaction Id "
+                        + journalEntry.getTransactionId();
+            }
+            if (journalEntry.isDebitEntry()) {
+                reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(),
+                        journalEntry.getGlAccount(), journalEntry.getCurrencyCode(), reversalTransactionId, manualEntry,
+                        journalEntry.getTransactionDate(), JournalEntryType.CREDIT, journalEntry.getAmount(), reversalComment, null, null,
+                        journalEntry.getReferenceNumber(), journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction(),
+                        journalEntry.getClientTransaction());
+            } else {
+                reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(),
+                        journalEntry.getGlAccount(), journalEntry.getCurrencyCode(), reversalTransactionId, manualEntry,
+                        journalEntry.getTransactionDate(), JournalEntryType.DEBIT, journalEntry.getAmount(), reversalComment, null, null,
+                        journalEntry.getReferenceNumber(), journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction(),
+                        journalEntry.getClientTransaction());
+            }
+            // save the reversal entry
+            this.glJournalEntryRepository.saveAndFlush(reversalJournalEntry);
+            journalEntry.setReversed(true);
+            journalEntry.setReversalJournalEntry(reversalJournalEntry);
+            // save the updated journal entry
+            this.glJournalEntryRepository.saveAndFlush(journalEntry);
+        }
+        return reversalTransactionId;
+    }
+
+    @Override
+    public String revertProvisioningJournalEntries(final Date reversalTransactionDate, final Long entityId, final Integer entityType) {
+        List<JournalEntry> journalEntries = this.glJournalEntryRepository.findProvisioningJournalEntriesByEntityId(entityId, entityType);
+        final String reversalTransactionId = journalEntries.get(0).getTransactionId();
+        for (final JournalEntry journalEntry : journalEntries) {
+            JournalEntry reversalJournalEntry;
+            String reversalComment = "Reversal entry for Journal Entry with Entry Id  :" + journalEntry.getId() + " and transaction Id "
+                    + journalEntry.getTransactionId();
+            if (journalEntry.isDebitEntry()) {
+                reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(),
+                        journalEntry.getGlAccount(), journalEntry.getCurrencyCode(), journalEntry.getTransactionId(), Boolean.FALSE,
+                        reversalTransactionDate, JournalEntryType.CREDIT, journalEntry.getAmount(), reversalComment,
+                        journalEntry.getEntityType(), journalEntry.getEntityId(), journalEntry.getReferenceNumber(),
+                        journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction(), journalEntry.getClientTransaction());
+            } else {
+                reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(),
+                        journalEntry.getGlAccount(), journalEntry.getCurrencyCode(), journalEntry.getTransactionId(), Boolean.FALSE,
+                        reversalTransactionDate, JournalEntryType.DEBIT, journalEntry.getAmount(), reversalComment,
+                        journalEntry.getEntityType(), journalEntry.getEntityId(), journalEntry.getReferenceNumber(),
+                        journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction(), journalEntry.getClientTransaction());
+            }
+            // save the reversal entry
+            this.glJournalEntryRepository.save(reversalJournalEntry);
+            journalEntry.setReversalJournalEntry(reversalJournalEntry);
+            // save the updated journal entry
+            this.glJournalEntryRepository.save(journalEntry);
+        }
+        return reversalTransactionId;
+
+    }
+
+    @Override
+    public String createProvisioningJournalEntries(ProvisioningEntry provisioningEntry) {
+        Collection<LoanProductProvisioningEntry> provisioningEntries = provisioningEntry.getLoanProductProvisioningEntries();
+        Map<OfficeCurrencyKey, List<LoanProductProvisioningEntry>> officeMap = new HashMap<>();
+
+        for (LoanProductProvisioningEntry entry : provisioningEntries) {
+            OfficeCurrencyKey key = new OfficeCurrencyKey(entry.getOffice(), entry.getCurrencyCode());
+            if (officeMap.containsKey(key)) {
+                List<LoanProductProvisioningEntry> list = officeMap.get(key);
+                list.add(entry);
+            } else {
+                List<LoanProductProvisioningEntry> list = new ArrayList<>();
+                list.add(entry);
+                officeMap.put(key, list);
+            }
+        }
+
+        Set<OfficeCurrencyKey> officeSet = officeMap.keySet();
+        Map<GLAccount, BigDecimal> liabilityMap = new HashMap<>();
+        Map<GLAccount, BigDecimal> expenseMap = new HashMap<>();
+
+        for (OfficeCurrencyKey key : officeSet) {
+            liabilityMap.clear();
+            expenseMap.clear();
+            List<LoanProductProvisioningEntry> entries = officeMap.get(key);
+            for (LoanProductProvisioningEntry entry : entries) {
+                if (liabilityMap.containsKey(entry.getLiabilityAccount())) {
+                    BigDecimal amount = liabilityMap.get(entry.getLiabilityAccount());
+                    amount = amount.add(entry.getReservedAmount());
+                    liabilityMap.put(entry.getLiabilityAccount(), amount);
+                } else {
+                    BigDecimal amount = BigDecimal.ZERO.add(entry.getReservedAmount());
+                    liabilityMap.put(entry.getLiabilityAccount(), amount);
+                }
+
+                if (expenseMap.containsKey(entry.getExpenseAccount())) {
+                    BigDecimal amount = expenseMap.get(entry.getExpenseAccount());
+                    amount = amount.add(entry.getReservedAmount());
+                    expenseMap.put(entry.getExpenseAccount(), amount);
+                } else {
+                    BigDecimal amount = BigDecimal.ZERO.add(entry.getReservedAmount());
+                    expenseMap.put(entry.getExpenseAccount(), amount);
+                }
+            }
+            createJournalEnry(provisioningEntry.getCreatedDate(), provisioningEntry.getId(), key.office, key.currency, liabilityMap, expenseMap);
+        }
+        return "P"+provisioningEntry.getId() ;
+    }
+    
+    private void createJournalEnry(Date transactionDate, Long entryId, Office office, String currencyCode, Map<GLAccount, BigDecimal> liabilityMap,
+            Map<GLAccount, BigDecimal> expenseMap) {
+        Set<GLAccount> liabilityAccounts = liabilityMap.keySet();
+        for (GLAccount account : liabilityAccounts) {
+            this.helper.createProvisioningCreditJournalEntry(transactionDate,entryId, office, currencyCode, account,
+                    liabilityMap.get(account));
+        }
+        Set<GLAccount> expenseAccounts = expenseMap.keySet();
+        for (GLAccount account : expenseAccounts) {
+            this.helper.createProvisioningDebitJournalEntry(transactionDate,entryId, office, currencyCode, account,
+                    expenseMap.get(account));
+        }
+    }
+    
+    
+    private void validateCommentForReversal(final String reversalComment) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("GLJournalEntry");
+
+        baseDataValidator.reset().parameter("comments").value(reversalComment).notExceedingLengthOf(500);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    @Transactional
+    @Override
+    public void createJournalEntriesForLoan(final Map<String, Object> accountingBridgeData) {
+
+        final boolean cashBasedAccountingEnabled = (Boolean) accountingBridgeData.get("cashBasedAccountingEnabled");
+        final boolean upfrontAccrualBasedAccountingEnabled = (Boolean) accountingBridgeData.get("upfrontAccrualBasedAccountingEnabled");
+        final boolean periodicAccrualBasedAccountingEnabled = (Boolean) accountingBridgeData.get("periodicAccrualBasedAccountingEnabled");
+
+        if (cashBasedAccountingEnabled || upfrontAccrualBasedAccountingEnabled || periodicAccrualBasedAccountingEnabled) {
+            final LoanDTO loanDTO = this.helper.populateLoanDtoFromMap(accountingBridgeData, cashBasedAccountingEnabled,
+                    upfrontAccrualBasedAccountingEnabled, periodicAccrualBasedAccountingEnabled);
+            final AccountingProcessorForLoan accountingProcessorForLoan = this.accountingProcessorForLoanFactory
+                    .determineProcessor(loanDTO);
+            accountingProcessorForLoan.createJournalEntriesForLoan(loanDTO);
+        }
+    }
+
+    @Transactional
+    @Override
+    public void createJournalEntriesForSavings(final Map<String, Object> accountingBridgeData) {
+
+        final boolean cashBasedAccountingEnabled = (Boolean) accountingBridgeData.get("cashBasedAccountingEnabled");
+        final boolean accrualBasedAccountingEnabled = (Boolean) accountingBridgeData.get("accrualBasedAccountingEnabled");
+
+        if (cashBasedAccountingEnabled || accrualBasedAccountingEnabled) {
+            final SavingsDTO savingsDTO = this.helper.populateSavingsDtoFromMap(accountingBridgeData, cashBasedAccountingEnabled,
+                    accrualBasedAccountingEnabled);
+            final AccountingProcessorForSavings accountingProcessorForSavings = this.accountingProcessorForSavingsFactory
+                    .determineProcessor(savingsDTO);
+            accountingProcessorForSavings.createJournalEntriesForSavings(savingsDTO);
+        }
+
+    }
+
+    private void validateBusinessRulesForJournalEntries(final JournalEntryCommand command) {
+        /** check if date of Journal entry is valid ***/
+        final LocalDate entryLocalDate = command.getTransactionDate();
+        final Date transactionDate = entryLocalDate.toDateTimeAtStartOfDay().toDate();
+        // shouldn't be in the future
+        final Date todaysDate = new Date();
+        if (transactionDate.after(todaysDate)) { throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.FUTURE_DATE,
+                transactionDate, null, null); }
+        // shouldn't be before an accounting closure
+        final GLClosure latestGLClosure = this.glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId());
+        if (latestGLClosure != null) {
+            if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException(
+                    GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); }
+        }
+
+        /*** check if credits and debits are valid **/
+        final SingleDebitOrCreditEntryCommand[] credits = command.getCredits();
+        final SingleDebitOrCreditEntryCommand[] debits = command.getDebits();
+
+        // atleast one debit or credit must be present
+        if (credits == null || credits.length <= 0 || debits == null || debits.length <= 0) { throw new JournalEntryInvalidException(
+                GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, null, null); }
+
+        checkDebitAndCreditAmounts(credits, debits);
+    }
+
+    private void saveAllDebitOrCreditEntries(final JournalEntryCommand command, final Office office, final PaymentDetail paymentDetail,
+            final String currencyCode, final Date transactionDate,
+            final SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, final String transactionId,
+            final JournalEntryType type, final String referenceNumber) {
+        final boolean manualEntry = true;
+        for (final SingleDebitOrCreditEntryCommand singleDebitOrCreditEntryCommand : singleDebitOrCreditEntryCommands) {
+            final GLAccount glAccount = this.glAccountRepository.findOne(singleDebitOrCreditEntryCommand.getGlAccountId());
+            if (glAccount == null) { throw new GLAccountNotFoundException(singleDebitOrCreditEntryCommand.getGlAccountId()); }
+
+            validateGLAccountForTransaction(glAccount);
+
+            String comments = command.getComments();
+            if (!StringUtils.isBlank(singleDebitOrCreditEntryCommand.getComments())) {
+                comments = singleDebitOrCreditEntryCommand.getComments();
+            }
+
+            /** Validate current code is appropriate **/
+            this.organisationCurrencyRepository.findOneWithNotFoundDetection(currencyCode);
+
+            final ClientTransaction clientTransaction = null;
+            final JournalEntry glJournalEntry = JournalEntry.createNew(office, paymentDetail, glAccount, currencyCode, transactionId,
+                    manualEntry, transactionDate, type, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, referenceNumber,
+                    null, null, clientTransaction);
+            this.glJournalEntryRepository.saveAndFlush(glJournalEntry);
+        }
+    }
+
+    /**
+     * TODO: Need a better implementation with guaranteed uniqueness (but not a
+     * long UUID)...maybe something tied to system clock..
+     */
+    private String generateTransactionId(final Long officeId) {
+        final AppUser user = this.context.authenticatedUser();
+        final Long time = System.currentTimeMillis();
+        final String uniqueVal = String.valueOf(time) + user.getId() + officeId;
+        final String transactionId = Long.toHexString(Long.parseLong(uniqueVal));
+        return transactionId;
+    }
+
+    private void handleJournalEntryDataIntegrityIssues(final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.glJournalEntry.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource Journal Entry: " + realCause.getMessage());
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult defineOpeningBalance(final JsonCommand command) {
+        try {
+            final JournalEntryCommand journalEntryCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
+            journalEntryCommand.validateForCreate();
+
+            final FinancialActivityAccount financialActivityAccountId = this.financialActivityAccountRepositoryWrapper
+                    .findByFinancialActivityTypeWithNotFoundDetection(300);
+            final Long contraId = financialActivityAccountId.getGlAccount().getId();
+            if (contraId == null) { throw new GeneralPlatformDomainRuleException(
+                    "error.msg.financial.activity.mapping.opening.balance.contra.account.cannot.be.null",
+                    "office-opening-balances-contra-account value can not be null", "office-opening-balances-contra-account"); }
+
+            validateJournalEntriesArePostedBefore(contraId);
+
+            // check office is valid
+            final Long officeId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.OFFICE_ID.getValue());
+            final Office office = this.officeRepository.findOne(officeId);
+            if (office == null) { throw new OfficeNotFoundException(officeId); }
+
+            final String currencyCode = command.stringValueOfParameterNamed(JournalEntryJsonInputParams.CURRENCY_CODE.getValue());
+
+            validateBusinessRulesForJournalEntries(journalEntryCommand);
+
+            /**
+             * revert old journal entries
+             */
+            final List<String> transactionIdsToBeReversed = this.glJournalEntryRepository.findNonReversedContraTansactionIds(contraId,
+                    officeId);
+            for (String transactionId : transactionIdsToBeReversed) {
+                final List<JournalEntry> journalEntries = this.glJournalEntryRepository
+                        .findUnReversedManualJournalEntriesByTransactionId(transactionId);
+                revertJournalEntry(journalEntries, "defining opening balance");
+            }
+
+            /** Set a transaction Id and save these Journal entries **/
+            final Date transactionDate = command.DateValueOfParameterNamed(JournalEntryJsonInputParams.TRANSACTION_DATE.getValue());
+            final String transactionId = generateTransactionId(officeId);
+
+            saveAllDebitOrCreditOpeningBalanceEntries(journalEntryCommand, office, currencyCode, transactionDate,
+                    journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, contraId);
+
+            saveAllDebitOrCreditOpeningBalanceEntries(journalEntryCommand, office, currencyCode, transactionDate,
+                    journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, contraId);
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
+                    .withTransactionId(transactionId).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleJournalEntryDataIntegrityIssues(dve);
+            return null;
+        }
+    }
+
+    private void saveAllDebitOrCreditOpeningBalanceEntries(final JournalEntryCommand command, final Office office,
+            final String currencyCode, final Date transactionDate,
+            final SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, final String transactionId,
+            final JournalEntryType type, final Long contraAccountId) {
+
+        final boolean manualEntry = true;
+        final GLAccount contraAccount = this.glAccountRepository.findOne(contraAccountId);
+        if (contraAccount == null) { throw new GLAccountNotFoundException(contraAccountId); }
+        if (!GLAccountType.fromInt(contraAccount.getType()).isEquityType()) { throw new GeneralPlatformDomainRuleException(
+                "error.msg.configuration.opening.balance.contra.account.value.is.invalid.account.type",
+                "Global configuration 'office-opening-balances-contra-account' value is not an equity type account", contraAccountId); }
+        validateGLAccountForTransaction(contraAccount);
+        final JournalEntryType contraType = getContraType(type);
+        String comments = command.getComments();
+
+        /** Validate current code is appropriate **/
+        this.organisationCurrencyRepository.findOneWithNotFoundDetection(currencyCode);
+
+        for (final SingleDebitOrCreditEntryCommand singleDebitOrCreditEntryCommand : singleDebitOrCreditEntryCommands) {
+            final GLAccount glAccount = this.glAccountRepository.findOne(singleDebitOrCreditEntryCommand.getGlAccountId());
+            if (glAccount == null) { throw new GLAccountNotFoundException(singleDebitOrCreditEntryCommand.getGlAccountId()); }
+
+            validateGLAccountForTransaction(glAccount);
+
+            if (!StringUtils.isBlank(singleDebitOrCreditEntryCommand.getComments())) {
+                comments = singleDebitOrCreditEntryCommand.getComments();
+            }
+
+            final ClientTransaction clientTransaction = null;
+            final JournalEntry glJournalEntry = JournalEntry.createNew(office, null, glAccount, currencyCode, transactionId, manualEntry,
+                    transactionDate, type, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, null, null, null,
+                    clientTransaction);
+            this.glJournalEntryRepository.saveAndFlush(glJournalEntry);
+
+            final JournalEntry contraEntry = JournalEntry.createNew(office, null, contraAccount, currencyCode, transactionId, manualEntry,
+                    transactionDate, contraType, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, null, null, null,
+                    clientTransaction);
+            this.glJournalEntryRepository.saveAndFlush(contraEntry);
+        }
+    }
+
+    private JournalEntryType getContraType(final JournalEntryType type) {
+        final JournalEntryType contraType;
+        if (type.isCreditType()) {
+            contraType = JournalEntryType.DEBIT;
+        } else {
+            contraType = JournalEntryType.CREDIT;
+        }
+        return contraType;
+    }
+
+    private void validateJournalEntriesArePostedBefore(final Long contraId) {
+        final List<String> transactionIds = this.glJournalEntryRepository.findNonContraTansactionIds(contraId);
+        if (!CollectionUtils.isEmpty(transactionIds)) { throw new GeneralPlatformDomainRuleException(
+                "error.msg.journalentry.defining.openingbalance.not.allowed",
+                "Defining Opening balances not allowed after journal entries posted", transactionIds); }
+    }
+
+    @Override
+    public void createJournalEntriesForClientTransactions(Map<String, Object> accountingBridgeData) {
+        final ClientTransactionDTO clientTransactionDTO = this.helper.populateClientTransactionDtoFromMap(accountingBridgeData);
+        accountingProcessorForClientTransactions.createJournalEntriesForClientTransaction(clientTransactionDTO);
+    }
+    
+    private class OfficeCurrencyKey {
+
+        Office office;
+        String currency;
+
+        OfficeCurrencyKey(Office office, String currency) {
+            this.office = office;
+            this.currency = currency;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!obj.getClass().equals(this.getClass())) return false;
+            OfficeCurrencyKey copy = (OfficeCurrencyKey) obj;
+            return this.office.getId() == copy.office.getId() && this.currency.equals(copy.currency);
+        }
+
+        @Override
+        public int hashCode() {
+            return this.office.hashCode() + this.currency.hashCode();
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/data/ChargeToGLAccountMapper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/data/ChargeToGLAccountMapper.java
new file mode 100755
index 0000000..1fa60ab
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/data/ChargeToGLAccountMapper.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.accounting.producttoaccountmapping.data;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+
+public class ChargeToGLAccountMapper {
+
+    @SuppressWarnings("unused")
+    private final ChargeData charge;
+    @SuppressWarnings("unused")
+    private final GLAccountData incomeAccount;
+
+    public ChargeToGLAccountMapper(final ChargeData charge, final GLAccountData incomeAccount) {
+        this.charge = charge;
+        this.incomeAccount = incomeAccount;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/data/PaymentTypeToGLAccountMapper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/data/PaymentTypeToGLAccountMapper.java
new file mode 100755
index 0000000..56443e7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/data/PaymentTypeToGLAccountMapper.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.accounting.producttoaccountmapping.data;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+
+public class PaymentTypeToGLAccountMapper {
+
+    @SuppressWarnings("unused")
+    private final PaymentTypeData paymentType;
+    @SuppressWarnings("unused")
+    private final GLAccountData fundSourceAccount;
+
+    public PaymentTypeToGLAccountMapper(final PaymentTypeData paymentType, final GLAccountData fundSourceAccount) {
+        this.paymentType = paymentType;
+        this.fundSourceAccount = fundSourceAccount;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/PortfolioProductType.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/PortfolioProductType.java
new file mode 100755
index 0000000..786699c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/PortfolioProductType.java
@@ -0,0 +1,73 @@
+/**
+ * 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.producttoaccountmapping.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum PortfolioProductType {
+    LOAN(1, "productType.loan"), SAVING(2, "productType.saving"), CLIENT(2, "productType.client"), PROVISIONING(3, "productType.provisioning");
+
+    private final Integer value;
+    private final String code;
+
+    private PortfolioProductType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString().replaceAll("_", " ");
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, PortfolioProductType> intToEnumMap = new HashMap<>();
+
+    static {
+        for (final PortfolioProductType type : PortfolioProductType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static PortfolioProductType fromInt(final int i) {
+        final PortfolioProductType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public boolean isSavingProduct() {
+        return this.value.equals(PortfolioProductType.SAVING.getValue());
+    }
+
+    public boolean isLoanProduct() {
+        return this.value.equals(PortfolioProductType.LOAN.getValue());
+    }
+
+    public boolean isClient() {
+        return this.value.equals(PortfolioProductType.CLIENT.getValue());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
new file mode 100755
index 0000000..4c9bbaf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
@@ -0,0 +1,141 @@
+/**
+ * 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.producttoaccountmapping.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "acc_product_mapping", uniqueConstraints = { @UniqueConstraint(columnNames = { "product_id", "product_type",
+        "financial_account_type", "payment_type" }, name = "financial_action") })
+public class ProductToGLAccountMapping extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "gl_account_id")
+    private GLAccount glAccount;
+
+    @Column(name = "product_id", nullable = false)
+    private Long productId;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "payment_type", nullable = true)
+    private PaymentType paymentType;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "charge_id", nullable = true)
+    private Charge charge;
+
+    @Column(name = "product_type", nullable = false)
+    private int productType;
+
+    @Column(name = "financial_account_type", nullable = false)
+    private int financialAccountType;
+
+    public static ProductToGLAccountMapping createNew(final GLAccount glAccount, final Long productId, final int productType,
+            final int financialAccountType) {
+        return new ProductToGLAccountMapping(glAccount, productId, productType, financialAccountType);
+    }
+
+    protected ProductToGLAccountMapping() {
+        //
+    }
+
+    public ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType, final int financialAccountType) {
+        this(glAccount, productId, productType, financialAccountType, null, null);
+    }
+
+    public ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType,
+            final int financialAccountType, final Charge charge) {
+        this(glAccount, productId, productType, financialAccountType, null, charge);
+    }
+
+    public ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType,
+            final int financialAccountType, final PaymentType paymentType) {
+        this(glAccount, productId, productType, financialAccountType, paymentType, null);
+    }
+
+    private ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType,
+            final int financialAccountType, final PaymentType paymentType, final Charge charge) {
+        this.glAccount = glAccount;
+        this.productId = productId;
+        this.productType = productType;
+        this.financialAccountType = financialAccountType;
+        this.paymentType = paymentType;
+        this.charge = charge;
+    }
+
+    public GLAccount getGlAccount() {
+        return this.glAccount;
+    }
+
+    public void setGlAccount(final GLAccount glAccount) {
+        this.glAccount = glAccount;
+    }
+
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    public void setProductId(final Long productId) {
+        this.productId = productId;
+    }
+
+    public int getProductType() {
+        return this.productType;
+    }
+
+    public void setProductType(final int productType) {
+        this.productType = productType;
+    }
+
+    public int getFinancialAccountType() {
+        return this.financialAccountType;
+    }
+
+    public void setFinancialAccountType(final int financialAccountType) {
+        this.financialAccountType = financialAccountType;
+    }
+
+    public PaymentType getPaymentType() {
+        return this.paymentType;
+    }
+
+    public void setPaymentType(final PaymentType paymentType) {
+        this.paymentType = paymentType;
+    }
+
+    public Charge getCharge() {
+        return this.charge;
+    }
+
+    public void setCharge(final Charge charge) {
+        this.charge = charge;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
new file mode 100755
index 0000000..230de00
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
@@ -0,0 +1,57 @@
+/**
+ * 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.producttoaccountmapping.domain;
+
+import java.util.List;
+
+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 ProductToGLAccountMappingRepository extends JpaRepository<ProductToGLAccountMapping, Long>,
+        JpaSpecificationExecutor<ProductToGLAccountMapping> {
+
+    ProductToGLAccountMapping findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(Long productId, int productType,
+            int financialAccountType, Long paymentType);
+
+    ProductToGLAccountMapping findByProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(Long productId, int productType,
+            int financialAccountType, Long chargeId);
+
+    @Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL and mapping.charge is NULL")
+    ProductToGLAccountMapping findCoreProductToFinAccountMapping(@Param("productId") Long productId, @Param("productType") int productType,
+            @Param("financialAccountType") int financialAccountType);
+
+    /*** The financial Account Type for a fund source will always be an asset (1) ***/
+    @Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=1 and mapping.paymentType is not NULL")
+    List<ProductToGLAccountMapping> findAllPaymentTypeToFundSourceMappings(@Param("productId") Long productId,
+            @Param("productType") int productType);
+
+    /*** The financial Account Type for income from interest will always be 4 ***/
+    @Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=4 and mapping.charge is not NULL")
+    List<ProductToGLAccountMapping> findAllFeeToIncomeAccountMappings(@Param("productId") Long productId,
+            @Param("productType") int productType);
+
+    /*** The financial Account Type for income from interest will always be 5 ***/
+    @Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=5 and mapping.charge is not NULL")
+    List<ProductToGLAccountMapping> findAllPenaltyToIncomeAccountMappings(@Param("productId") Long productId,
+            @Param("productType") int productType);
+
+    List<ProductToGLAccountMapping> findByProductIdAndProductType(Long productId, int productType);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingInvalidException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingInvalidException.java
new file mode 100755
index 0000000..2b3c1f7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingInvalidException.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.accounting.producttoaccountmapping.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when product to GL account mapping are not
+ * found.
+ */
+public class ProductToGLAccountMappingInvalidException extends AbstractPlatformDomainRuleException {
+
+    public ProductToGLAccountMappingInvalidException(final String paramName, final String accountName, final Long accountId,
+            final String actualAccountCategory, final String expectedAccountCategory) {
+        super("error.msg." + paramName + ".invalid.account.type", "Passed in GLAccount " + paramName + " with Id " + accountId
+                + "maps to the account " + accountName + " of type " + actualAccountCategory + ", the expected account type was one among"
+                + expectedAccountCategory, paramName, accountId, accountName, actualAccountCategory, expectedAccountCategory);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingNotFoundException.java
new file mode 100755
index 0000000..04c3a16
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingNotFoundException.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.accounting.producttoaccountmapping.exception;
+
+import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when product to GL account mapping are not
+ * found.
+ */
+public class ProductToGLAccountMappingNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ProductToGLAccountMappingNotFoundException(final PortfolioProductType type, final Long productId, final String accountType) {
+        super("error.msg.productToAccountMapping.not.found", "Mapping for product of type " + type.toString() + " with Id " + productId
+                + " does not exist for an account of type " + accountType, type.toString(), productId, accountType);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java
new file mode 100755
index 0000000..1755828
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java
@@ -0,0 +1,243 @@
+/**
+ * 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.producttoaccountmapping.serialization;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+
+/**
+ * TODO Vishwas find a better approach for validation
+ * 
+ * Currently, validation of the passed in JSON is done before calling save or
+ * update method on the target resource (in our case the loan Product)
+ * 
+ * However, in the case of a loan product it would be difficult to validate the
+ * passed in JSON for valid {@link LoanProduct} to {@link GLAccount} mappings
+ * during update because of the following scenario
+ * 
+ * The accounting rule type may be changed in the update command, so we would
+ * have to validate if all required account heads for a particular account type
+ * have been passed in (would be different for CASH and Accrual based). However,
+ * till we have access to the domain object it would not be possible to detect
+ * if an accounting rule has actually been changed
+ * 
+ * Hence, method {@link #validateForLoanProductCreate(String)} from this class
+ * is called separately for validation only if an accounting rule change is
+ * detected by {@link ProductToGLAccountMappingWritePlatformService}
+ * 
+ * Also, the class is probably named wrong (*FromApiJsonDeserializer) should
+ * probably be named as (*Validator) instead
+ * 
+ */
+@Component
+public final class ProductToGLAccountMappingFromApiJsonDeserializer {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ProductToGLAccountMappingFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForLoanProductCreate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanproduct");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        // accounting related data validation
+        final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4);
+
+        if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) {
+
+            final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(fundAccountId).notNull()
+                    .integerGreaterThanZero();
+
+            final Long loanPortfolioAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromInterestId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue()).value(incomeFromRecoveryAccountId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue()).value(overpaymentAccountId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue())
+                    .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
+
+        }
+
+        if (isAccrualBasedAccounting(accountingRuleType)) {
+
+            final Long receivableInterestAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue())
+                    .value(receivableInterestAccountId).notNull().integerGreaterThanZero();
+
+            final Long receivableFeeAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue()).value(receivableFeeAccountId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long receivablePenaltyAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue())
+                    .value(receivablePenaltyAccountId).notNull().integerGreaterThanZero();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForSavingsProductCreate(final String json, DepositAccountType accountType) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_PRODUCT_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        // accounting related data validation
+        final Integer accountingRuleType = this.fromApiJsonHelper
+                .extractIntegerNamed(accountingRuleParamName, element, Locale.getDefault());
+        baseDataValidator.reset().parameter(accountingRuleParamName).value(accountingRuleType).notNull().inMinMaxRange(1, 3);
+
+        if (isCashBasedAccounting(accountingRuleType)) {
+
+            final Long savingsControlAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue())
+                    .value(savingsControlAccountId).notNull().integerGreaterThanZero();
+
+            final Long savingsReferenceAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue())
+                    .value(savingsReferenceAccountId).notNull().integerGreaterThanZero();
+
+            final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue())
+                    .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
+
+            final Long interestOnSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue())
+                    .value(interestOnSavingsAccountId).notNull().integerGreaterThanZero();
+
+            final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue())
+                    .value(incomeFromPenaltyId).notNull().integerGreaterThanZero();
+
+            if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
+                final Long overdraftAccount = this.fromApiJsonHelper.extractLongNamed(
+                        SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
+                baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue())
+                        .value(overdraftAccount).notNull().integerGreaterThanZero();
+
+                final Long incomeFromInterest = this.fromApiJsonHelper.extractLongNamed(
+                        SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), element);
+                baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue())
+                        .value(incomeFromInterest).notNull().integerGreaterThanZero();
+
+                final Long writtenOff = this.fromApiJsonHelper.extractLongNamed(
+                        SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
+                baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writtenOff)
+                        .notNull().integerGreaterThanZero();
+            }
+
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private boolean isCashBasedAccounting(final Integer accountingRuleType) {
+        return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType);
+    }
+
+    private boolean isAccrualBasedAccounting(final Integer accountingRuleType) {
+        return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType)
+                || AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java
new file mode 100755
index 0000000..2021f81
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java
@@ -0,0 +1,306 @@
+/**
+ * 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.producttoaccountmapping.service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+
+@Component
+public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappingHelper {
+
+    @Autowired
+    public LoanProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository,
+            final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
+            final ChargeRepositoryWrapper chargeRepositoryWrapper, final GLAccountRepositoryWrapper accountRepositoryWrapper,
+            final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
+        super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, chargeRepositoryWrapper, accountRepositoryWrapper,
+                paymentTypeRepositoryWrapper);
+    }
+
+    /*** Set of abstractions for saving Loan Products to GL Account Mappings ***/
+
+    public void saveLoanToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId,
+            final int placeHolderTypeId) {
+        saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET, PortfolioProductType.LOAN);
+    }
+
+    public void saveLoanToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId,
+            final int placeHolderTypeId) {
+        saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME, PortfolioProductType.LOAN);
+    }
+
+    public void saveLoanToExpenseAccountMapping(final JsonElement element, final String paramName, final Long productId,
+            final int placeHolderTypeId) {
+        saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE, PortfolioProductType.LOAN);
+    }
+
+    public void saveLoanToLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId,
+            final int placeHolderTypeId) {
+        saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.LIABILITY, PortfolioProductType.LOAN);
+    }
+
+    /*** Set of abstractions for merging Savings Products to GL Account Mappings ***/
+    public void mergeLoanToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
+            final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
+        mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET,
+                PortfolioProductType.LOAN);
+    }
+
+    public void mergeLoanToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
+            final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
+        mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME,
+                PortfolioProductType.LOAN);
+    }
+
+    public void mergeLoanToExpenseAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
+            final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
+        mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EXPENSE,
+                PortfolioProductType.LOAN);
+    }
+
+    public void mergeLoanToLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
+            final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
+        mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes,
+                GLAccountType.LIABILITY, PortfolioProductType.LOAN);
+    }
+
+    /*** Abstractions for payments channel related to loan products ***/
+
+    public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        savePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.LOAN);
+    }
+
+    public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.LOAN);
+    }
+
+    public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        // save both fee and penalty charges
+        saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, true);
+        saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false);
+    }
+
+    public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        // update both fee and penalty charges
+        updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, true);
+        updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false);
+    }
+
+    public Map<String, Object> populateChangesForNewLoanProductToGLAccountMappingCreation(final JsonElement element,
+            final AccountingRuleType accountingRuleType) {
+        final Map<String, Object> changes = new HashMap<>();
+
+        final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element);
+        final Long loanPortfolioAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element);
+        final Long incomeFromInterestId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element);
+        final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
+                element);
+        final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+
+        final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), element);
+
+        final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
+        final Long overPaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(),
+                element);
+        final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+
+        final Long receivableInterestAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element);
+        final Long receivableFeeAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element);
+        final Long receivablePenaltyAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element);
+
+        switch (accountingRuleType) {
+            case NONE:
+            break;
+            case CASH_BASED:
+                populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
+                        incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
+                        incomeFromRecoveryAccountId);
+            break;
+            case ACCRUAL_PERIODIC:
+                populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
+                        incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
+                        incomeFromRecoveryAccountId, receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
+            break;
+            case ACCRUAL_UPFRONT:
+                populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
+                        incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
+                        incomeFromRecoveryAccountId, receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
+            break;
+        }
+
+        return changes;
+    }
+
+    private void populateChangesForAccrualBasedAccounting(final Map<String, Object> changes, final Long fundAccountId,
+            final Long loanPortfolioAccountId, final Long incomeFromInterestId, final Long incomeFromFeeId, final Long incomeFromPenaltyId,
+            final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId,
+            final Long incomeFromRecoveryAccountId, final Long receivableInterestAccountId, final Long receivableFeeAccountId,
+            final Long receivablePenaltyAccountId) {
+
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), receivableInterestAccountId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), receivableFeeAccountId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId);
+
+        populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId, incomeFromFeeId,
+                incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId, incomeFromRecoveryAccountId);
+
+    }
+
+    private void populateChangesForCashBasedAccounting(final Map<String, Object> changes, final Long fundAccountId,
+            final Long loanPortfolioAccountId, final Long incomeFromInterestId, final Long incomeFromFeeId, final Long incomeFromPenaltyId,
+            final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId,
+            final Long incomeFromRecoveryAccountId) {
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), overPaymentAccountId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
+        changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), incomeFromRecoveryAccountId);
+
+    }
+
+    /**
+     * Examines and updates each account mapping for given loan product with
+     * changes passed in from the Json element
+     * 
+     * @param loanProductId
+     * @param changes
+     * @param element
+     * @param accountingRuleType
+     */
+    public void handleChangesToLoanProductToGLAccountMappings(final Long loanProductId, final Map<String, Object> changes,
+            final JsonElement element, final AccountingRuleType accountingRuleType) {
+        switch (accountingRuleType) {
+            case NONE:
+            break;
+            case CASH_BASED:
+                // asset
+                mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes);
+                mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes);
+                mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue(), CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.toString(), changes);
+
+                // income
+                mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(), changes);
+                mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), changes);
+                mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
+                        loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
+                        CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes);
+                mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(),
+                        loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue(),
+                        CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.toString(), changes);
+
+                // expenses
+                mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
+                        loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(),
+                        CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes);
+
+                // liabilities
+                mergeLoanToLiabilityAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString(), changes);
+            break;
+            case ACCRUAL_UPFRONT:
+                // fall through to periodic accrual
+            case ACCRUAL_PERIODIC:
+                // assets (including receivables)
+                mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes);
+                mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes);
+                mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.toString(),
+                        changes);
+                mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(),
+                        loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(),
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.toString(), changes);
+                mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.toString(), changes);
+                mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(),
+                        loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue(),
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.toString(), changes);
+
+                // income
+                mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(),
+                        changes);
+                mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(),
+                        changes);
+                mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
+                        loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes);
+                mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(),
+                        loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue(),
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.toString(), changes);
+
+                // expenses
+                mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
+                        loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(),
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes);
+
+                // liabilities
+                mergeLoanToLiabilityAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString(), changes);
+            break;
+        }
+    }
+
+    public void deleteLoanProductToGLAccountMapping(final Long loanProductId) {
+        deleteProductToGLAccountMapping(loanProductId, PortfolioProductType.LOAN);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
new file mode 100755
index 0000000..7e2ee41
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
@@ -0,0 +1,432 @@
+/**
+ * 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.producttoaccountmapping.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
+import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingInvalidException;
+import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Component
+public class ProductToGLAccountMappingHelper {
+
+    protected final GLAccountRepository accountRepository;
+    protected final GLAccountRepositoryWrapper accountRepositoryWrapper;
+    protected final ProductToGLAccountMappingRepository accountMappingRepository;
+    protected final FromJsonHelper fromApiJsonHelper;
+    private final ChargeRepositoryWrapper chargeRepositoryWrapper;
+    private final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper;
+
+    @Autowired
+    public ProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository,
+            final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
+            final ChargeRepositoryWrapper chargeRepositoryWrapper, final GLAccountRepositoryWrapper accountRepositoryWrapper,
+            PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
+        this.accountRepository = glAccountRepository;
+        this.accountMappingRepository = glAccountMappingRepository;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.chargeRepositoryWrapper = chargeRepositoryWrapper;
+        this.accountRepositoryWrapper = accountRepositoryWrapper;
+        this.paymentTypeRepositoryWrapper = paymentTypeRepositoryWrapper;
+
+    }
+
+    public void saveProductToAccountMapping(final JsonElement element, final String paramName, final Long productId,
+            final int placeHolderTypeId, final GLAccountType expectedAccountType, final PortfolioProductType portfolioProductType) {
+        final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element);
+        final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId);
+
+        final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId,
+                portfolioProductType.getValue(), placeHolderTypeId);
+        this.accountMappingRepository.save(accountMapping);
+    }
+
+    public void mergeProductToAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
+            final int accountTypeId, final String accountTypeName, final Map<String, Object> changes,
+            final GLAccountType expectedAccountType, final PortfolioProductType portfolioProductType) {
+        final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element);
+
+        // get the existing product
+        if (accountId != null) {
+            final ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(productId,
+                    portfolioProductType.getValue(), accountTypeId);
+            if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(portfolioProductType, productId,
+                    accountTypeName); }
+            if (accountMapping.getGlAccount().getId() != accountId) {
+                final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId);
+                changes.put(paramName, accountId);
+                accountMapping.setGlAccount(glAccount);
+                this.accountMappingRepository.save(accountMapping);
+            }
+        }
+    }
+
+    /**
+     * Saves the payment type to Fund source mappings for a particular
+     * product/product type (also populates the changes array if passed in)
+     * 
+     * @param command
+     * @param element
+     * @param productId
+     * @param changes
+     */
+    public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes, final PortfolioProductType portfolioProductType) {
+        final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
+        if (paymentChannelMappingArray != null) {
+            if (changes != null) {
+                changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
+                        command.jsonFragment(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue()));
+            }
+            for (int i = 0; i < paymentChannelMappingArray.size(); i++) {
+                final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject();
+                final Long paymentTypeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).getAsLong();
+                final Long paymentSpecificFundAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).getAsLong();
+                savePaymentChannelToFundSourceMapping(productId, paymentTypeId, paymentSpecificFundAccountId, portfolioProductType);
+            }
+        }
+    }
+
+    /**
+     * Saves the Charge to Income / Liability account mappings for a particular
+     * product/product type (also populates the changes array if passed in)
+     * 
+     * @param command
+     * @param element
+     * @param productId
+     * @param changes
+     */
+    public void saveChargesToIncomeOrLiabilityAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes, final PortfolioProductType portfolioProductType, final boolean isPenalty) {
+        String arrayName;
+        if (isPenalty) {
+            arrayName = LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
+        } else {
+            arrayName = LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue();
+        }
+
+        final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayName, element);
+        if (chargeToIncomeAccountMappingArray != null) {
+            if (changes != null) {
+                changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
+                        command.jsonFragment(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue()));
+            }
+            for (int i = 0; i < chargeToIncomeAccountMappingArray.size(); i++) {
+                final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
+                final Long chargeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue()).getAsLong();
+                final Long incomeAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue()).getAsLong();
+                saveChargeToFundSourceMapping(productId, chargeId, incomeAccountId, portfolioProductType, isPenalty);
+            }
+        }
+    }
+
+    /**
+     * @param command
+     * @param element
+     * @param productId
+     * @param changes
+     */
+    public void updateChargeToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes, final PortfolioProductType portfolioProductType, final boolean isPenalty) {
+        // find all existing payment Channel to Fund source Mappings
+        List<ProductToGLAccountMapping> existingChargeToIncomeAccountMappings;
+        String arrayFragmentName;
+
+        if (isPenalty) {
+            existingChargeToIncomeAccountMappings = this.accountMappingRepository.findAllPenaltyToIncomeAccountMappings(productId,
+                    portfolioProductType.getValue());
+            arrayFragmentName = LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
+        } else {
+            existingChargeToIncomeAccountMappings = this.accountMappingRepository.findAllFeeToIncomeAccountMappings(productId,
+                    portfolioProductType.getValue());
+            arrayFragmentName = LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue();
+        }
+
+        final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayFragmentName, element);
+        /**
+         * Variable stores a map representation of charges (key) and their
+         * associated income Id's (value) extracted from the passed in
+         * Jsoncommand
+         **/
+        final Map<Long, Long> inputChargeToIncomeAccountMap = new HashMap<>();
+        /***
+         * Variable stores all charges which have already been mapped to Income
+         * Accounts in the system
+         **/
+        final Set<Long> existingCharges = new HashSet<>();
+        if (chargeToIncomeAccountMappingArray != null) {
+            if (changes != null) {
+                changes.put(arrayFragmentName, command.jsonFragment(arrayFragmentName));
+            }
+
+            for (int i = 0; i < chargeToIncomeAccountMappingArray.size(); i++) {
+                final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
+                final Long chargeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue()).getAsLong();
+                final Long incomeAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue()).getAsLong();
+                inputChargeToIncomeAccountMap.put(chargeId, incomeAccountId);
+            }
+
+            // If input map is empty, delete all existing mappings
+            if (inputChargeToIncomeAccountMap.size() == 0) {
+                this.accountMappingRepository.deleteInBatch(existingChargeToIncomeAccountMappings);
+            }/**
+             * Else, <br/>
+             * update existing mappings OR <br/>
+             * delete old mappings (which are already present, but not passed in
+             * as a part of Jsoncommand)<br/>
+             * Create new mappings for charges that are passed in as a part of
+             * the Jsoncommand but not already present
+             * 
+             **/
+            else {
+                for (final ProductToGLAccountMapping chargeToIncomeAccountMapping : existingChargeToIncomeAccountMappings) {
+                    final Long currentCharge = chargeToIncomeAccountMapping.getCharge().getId();
+                    existingCharges.add(currentCharge);
+                    // update existing mappings (if required)
+                    if (inputChargeToIncomeAccountMap.containsKey(currentCharge)) {
+                        final Long newGLAccountId = inputChargeToIncomeAccountMap.get(currentCharge);
+                        if (newGLAccountId != chargeToIncomeAccountMapping.getGlAccount().getId()) {
+                            final GLAccount glAccount;
+                            if (isPenalty) {
+                                glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(),
+                                        GLAccountType.INCOME, newGLAccountId);
+                            } else {
+                                List<GLAccountType> allowedAccountTypes = getAllowedAccountTypesForFeeMapping();
+                                glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(),
+                                        allowedAccountTypes, newGLAccountId);
+                            }
+                            chargeToIncomeAccountMapping.setGlAccount(glAccount);
+                            this.accountMappingRepository.save(chargeToIncomeAccountMapping);
+                        }
+                    }// deleted payment type
+                    else {
+                        this.accountMappingRepository.delete(chargeToIncomeAccountMapping);
+                    }
+                }
+                // create new mappings
+                final Set<Long> incomingCharges = inputChargeToIncomeAccountMap.keySet();
+                incomingCharges.removeAll(existingCharges);
+                // incomingPaymentTypes now only contains the newly added
+                // payment Type mappings
+                for (final Long newCharge : incomingCharges) {
+                    final Long newGLAccountId = inputChargeToIncomeAccountMap.get(newCharge);
+                    saveChargeToFundSourceMapping(productId, newCharge, newGLAccountId, portfolioProductType, isPenalty);
+                }
+            }
+        }
+    }
+
+    /**
+     * @param command
+     * @param element
+     * @param productId
+     * @param changes
+     */
+    public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes, final PortfolioProductType portfolioProductType) {
+        // find all existing payment Channel to Fund source Mappings
+        final List<ProductToGLAccountMapping> existingPaymentChannelToFundSourceMappings = this.accountMappingRepository
+                .findAllPaymentTypeToFundSourceMappings(productId, portfolioProductType.getValue());
+        final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
+        /**
+         * Variable stores a map representation of Payment channels (key) and
+         * their fund sources (value) extracted from the passed in Jsoncommand
+         **/
+        final Map<Long, Long> inputPaymentChannelFundSourceMap = new HashMap<>();
+        /***
+         * Variable stores all payment types which have already been mapped to
+         * Fund Sources in the system
+         **/
+        final Set<Long> existingPaymentTypes = new HashSet<>();
+        if (paymentChannelMappingArray != null && paymentChannelMappingArray.size() > 0) {
+            if (changes != null) {
+                changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
+                        command.jsonFragment(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue()));
+            }
+
+            for (int i = 0; i < paymentChannelMappingArray.size(); i++) {
+                final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject();
+                final Long paymentTypeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).getAsLong();
+                final Long paymentSpecificFundAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).getAsLong();
+                inputPaymentChannelFundSourceMap.put(paymentTypeId, paymentSpecificFundAccountId);
+            }
+
+            // If input map is empty, delete all existing mappings
+            if (inputPaymentChannelFundSourceMap.size() == 0) {
+                this.accountMappingRepository.deleteInBatch(existingPaymentChannelToFundSourceMappings);
+            }/**
+             * Else, <br/>
+             * update existing mappings OR <br/>
+             * delete old mappings (which re already present, but not passed in
+             * as a part of Jsoncommand)<br/>
+             * Create new mappings for payment types that are passed in as a
+             * part of the Jsoncommand but not already present
+             * 
+             **/
+            else {
+                for (final ProductToGLAccountMapping existingPaymentChannelToFundSourceMapping : existingPaymentChannelToFundSourceMappings) {
+                    final Long currentPaymentChannelId = existingPaymentChannelToFundSourceMapping.getPaymentType().getId();
+                    existingPaymentTypes.add(currentPaymentChannelId);
+                    // update existing mappings (if required)
+                    if (inputPaymentChannelFundSourceMap.containsKey(currentPaymentChannelId)) {
+                        final Long newGLAccountId = inputPaymentChannelFundSourceMap.get(currentPaymentChannelId);
+                        if (newGLAccountId != existingPaymentChannelToFundSourceMapping.getGlAccount().getId()) {
+                            final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(),
+                                    GLAccountType.ASSET, newGLAccountId);
+                            existingPaymentChannelToFundSourceMapping.setGlAccount(glAccount);
+                            this.accountMappingRepository.save(existingPaymentChannelToFundSourceMapping);
+                        }
+                    }// deleted payment type
+                    else {
+                        this.accountMappingRepository.delete(existingPaymentChannelToFundSourceMapping);
+                    }
+                }
+                // create new mappings
+                final Set<Long> incomingPaymentTypes = inputPaymentChannelFundSourceMap.keySet();
+                incomingPaymentTypes.removeAll(existingPaymentTypes);
+                // incomingPaymentTypes now only contains the newly added
+                // payment Type mappings
+                for (final Long newPaymentType : incomingPaymentTypes) {
+                    final Long newGLAccountId = inputPaymentChannelFundSourceMap.get(newPaymentType);
+                    savePaymentChannelToFundSourceMapping(productId, newPaymentType, newGLAccountId, portfolioProductType);
+                }
+            }
+        }
+    }
+
+    /**
+     * @param productId
+     * @param jsonObject
+     */
+    private void savePaymentChannelToFundSourceMapping(final Long productId, final Long paymentTypeId,
+            final Long paymentTypeSpecificFundAccountId, final PortfolioProductType portfolioProductType) {
+        final PaymentType paymentType = this.paymentTypeRepositoryWrapper.findOneWithNotFoundDetection(paymentTypeId);
+        final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), GLAccountType.ASSET,
+                paymentTypeSpecificFundAccountId);
+        final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId,
+                portfolioProductType.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), paymentType);
+        this.accountMappingRepository.save(accountMapping);
+    }
+
+    /**
+     * @param productId
+     * @param jsonObject
+     */
+    private void saveChargeToFundSourceMapping(final Long productId, final Long chargeId, final Long incomeAccountId,
+            final PortfolioProductType portfolioProductType, final boolean isPenalty) {
+        final Charge charge = this.chargeRepositoryWrapper.findOneWithNotFoundDetection(chargeId);
+
+        // TODO Vishwas: Need to validate if given charge is fee or Penalty
+        // based on input condition
+
+        GLAccount glAccount;
+        /**
+         * Both CASH and Accrual placeholders have the same value for income
+         * from Interest and penalties
+         **/
+        CASH_ACCOUNTS_FOR_LOAN placeHolderAccountType;
+        if (isPenalty) {
+            glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(), GLAccountType.INCOME,
+                    incomeAccountId);
+            placeHolderAccountType = CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES;
+        } else {
+            List<GLAccountType> allowedAccountTypes = getAllowedAccountTypesForFeeMapping();
+            glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(), allowedAccountTypes,
+                    incomeAccountId);
+            placeHolderAccountType = CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES;
+        }
+        final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId,
+                portfolioProductType.getValue(), placeHolderAccountType.getValue(), charge);
+        this.accountMappingRepository.save(accountMapping);
+    }
+
+    private List<GLAccountType> getAllowedAccountTypesForFeeMapping() {
+        List<GLAccountType> allowedAccountTypes = new ArrayList<>();
+        allowedAccountTypes.add(GLAccountType.INCOME);
+        allowedAccountTypes.add(GLAccountType.LIABILITY);
+        return allowedAccountTypes;
+    }
+
+    /**
+     * Fetches account with a particular Id and throws and Exception it is not
+     * of the expected Account Category ('ASSET','liability' etc)
+     * 
+     * @param paramName
+     * @param expectedAccountType
+     * @param accountId
+     * @return
+     */
+    public GLAccount getAccountByIdAndType(final String paramName, final GLAccountType expectedAccountType, final Long accountId) {
+        final GLAccount glAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountId);
+
+        // validate account is of the expected Type
+        if (glAccount.getType().intValue() != expectedAccountType.getValue()) { throw new ProductToGLAccountMappingInvalidException(
+                paramName, glAccount.getName(), accountId, GLAccountType.fromInt(glAccount.getType()).toString(),
+                expectedAccountType.toString()); }
+        return glAccount;
+    }
+
+    public GLAccount getAccountByIdAndType(final String paramName, final List<GLAccountType> expectedAccountTypes, final Long accountId) {
+        final GLAccount glAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountId);
+        // validate account is of the expected Type
+        List<Integer> glAccountTypeValues = new ArrayList<>();
+        for (GLAccountType glAccountType : expectedAccountTypes) {
+            glAccountTypeValues.add(glAccountType.getValue());
+        }
+        if (!glAccountTypeValues.contains(glAccount.getType())) { throw new ProductToGLAccountMappingInvalidException(paramName,
+                glAccount.getName(), accountId, GLAccountType.fromInt(glAccount.getType()).toString(), glAccountTypeValues.toString()); }
+        return glAccount;
+    }
+
+    public void deleteProductToGLAccountMapping(final Long loanProductId, final PortfolioProductType portfolioProductType) {
+        final List<ProductToGLAccountMapping> productToGLAccountMappings = this.accountMappingRepository.findByProductIdAndProductType(
+                loanProductId, portfolioProductType.getValue());
+        if (productToGLAccountMappings != null && productToGLAccountMappings.size() > 0) {
+            this.accountMappingRepository.deleteInBatch(productToGLAccountMappings);
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
new file mode 100755
index 0000000..2a903d5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
@@ -0,0 +1,45 @@
+/**
+ * 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.producttoaccountmapping.service;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+
+public interface ProductToGLAccountMappingReadPlatformService {
+
+    public Map<String, Object> fetchAccountMappingDetailsForLoanProduct(final Long loanProductId, final Integer accountingType);
+
+    public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForLoanProduct(final Long loanProductId);
+
+    public List<ChargeToGLAccountMapper> fetchFeeToIncomeOrLiabilityAccountMappingsForLoanProduct(final Long loanProductId);
+
+    public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForLoanProduct(final Long loanProductId);
+
+    public Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType);
+
+    public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId);
+
+    public List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId);
+
+    public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
new file mode 100755
index 0000000..ef1a2af
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
@@ -0,0 +1,329 @@
+/**
+ * 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.producttoaccountmapping.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS;
+import org.apache.fineract.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS;
+import org.apache.fineract.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ProductToGLAccountMappingReadPlatformServiceImpl implements ProductToGLAccountMappingReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public ProductToGLAccountMappingReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class ProductToGLAccountMappingMapper implements RowMapper<Map<String, Object>> {
+
+        public String schema() {
+            return " mapping.id as id, mapping.gl_account_id as glAccountId,glaccount.name as name,glaccount.gl_code as code,"
+                    + " mapping.product_id as productId, mapping.product_type as productType,mapping.financial_account_type as financialAccountType, "
+                    + " mapping.payment_type as paymentTypeId,pt.value as paymentTypeValue, mapping.charge_id as chargeId, charge.is_penalty as penalty, "
+                    + " charge.name as chargeName "
+                    + " from acc_product_mapping mapping left join m_charge charge on mapping.charge_id=charge.id "
+                    + " left join acc_gl_account as  glaccount on mapping.gl_account_id = glaccount.id"
+                    + " left join m_payment_type pt on mapping.payment_type=pt.id" + " where mapping.product_type= ? ";
+        }
+
+        @Override
+        public Map<String, Object> mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long glAccountId = rs.getLong("glAccountId");
+            final Long productId = rs.getLong("productId");
+            final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentTypeId");
+            final Long chargeId = rs.getLong("chargeId");
+            final Integer productType = rs.getInt("productType");
+            final String paymentTypeValue = rs.getString("paymentTypeValue");
+            final Integer financialAccountType = rs.getInt("financialAccountType");
+            final String glAccountName = rs.getString("name");
+            final String glCode = rs.getString("code");
+            final String chargeName = rs.getString("chargeName");
+            final Boolean penalty = rs.getBoolean("penalty");
+
+            final Map<String, Object> loanProductToGLAccountMap = new LinkedHashMap<>(5);
+            loanProductToGLAccountMap.put("id", id);
+            loanProductToGLAccountMap.put("glAccountId", glAccountId);
+            loanProductToGLAccountMap.put("productId", productId);
+            loanProductToGLAccountMap.put("productType", productType);
+            loanProductToGLAccountMap.put("financialAccountType", financialAccountType);
+            loanProductToGLAccountMap.put("paymentTypeId", paymentTypeId);
+            loanProductToGLAccountMap.put("paymentTypeValue", paymentTypeValue);
+            loanProductToGLAccountMap.put("chargeId", chargeId);
+            loanProductToGLAccountMap.put("chargeName", chargeName);
+            loanProductToGLAccountMap.put("penalty", penalty);
+            loanProductToGLAccountMap.put("glAccountName", glAccountName);
+            loanProductToGLAccountMap.put("glCode", glCode);
+            return loanProductToGLAccountMap;
+        }
+    }
+
+    @Override
+    public Map<String, Object> fetchAccountMappingDetailsForLoanProduct(final Long loanProductId, final Integer accountingType) {
+
+        final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8);
+
+        final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
+        final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null";
+
+        final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, new Object[] {
+                PortfolioProductType.LOAN.getValue(), loanProductId });
+
+        if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) {
+
+            for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
+
+                final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
+                final CASH_ACCOUNTS_FOR_LOAN glAccountForLoan = CASH_ACCOUNTS_FOR_LOAN.fromInt(financialAccountType);
+
+                final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
+                final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
+                final String glCode = (String) productToGLAccountMap.get("glCode");
+                final GLAccountData gLAccountData = new GLAccountData(glAccountId, glAccountName, glCode);
+
+                if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.FUND_SOURCE.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_FEES.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_PENALTIES.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INTEREST_ON_LOANS.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.LOAN_PORTFOLIO.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.TRANSFERS_SUSPENSE.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.OVERPAYMENT.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_RECOVERY.getValue(), gLAccountData);
+                }
+            }
+        } else if (AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingType)
+                || AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingType)) {
+
+            for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
+                final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
+                final ACCRUAL_ACCOUNTS_FOR_LOAN glAccountForLoan = ACCRUAL_ACCOUNTS_FOR_LOAN.fromInt(financialAccountType);
+
+                final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
+                final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
+                final String glCode = (String) productToGLAccountMap.get("glCode");
+                final GLAccountData gLAccountData = new GLAccountData(glAccountId, glAccountName, glCode);
+
+                if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.FUND_SOURCE.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_FEES.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_PENALTIES.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INTEREST_ON_LOANS.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.LOAN_PORTFOLIO.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.OVERPAYMENT.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.TRANSFERS_SUSPENSE.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INTEREST_RECEIVABLE.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.FEES_RECEIVABLE.getValue(), gLAccountData);
+                } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE)) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.PENALTIES_RECEIVABLE.getValue(), gLAccountData);
+                } else if ((glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY))) {
+                    accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_RECOVERY.getValue(), gLAccountData);
+                }
+            }
+
+        }
+
+        return accountMappingDetails;
+    }
+
+    @Override
+    public Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType) {
+        final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8);
+
+        final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
+        final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null ";
+
+        final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, new Object[] {
+                PortfolioProductType.SAVING.getValue(), savingsProductId });
+
+        if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) {
+
+            for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
+
+                final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
+                final CASH_ACCOUNTS_FOR_SAVINGS glAccountForSavings = CASH_ACCOUNTS_FOR_SAVINGS.fromInt(financialAccountType);
+
+                final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
+                final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
+                final String glCode = (String) productToGLAccountMap.get("glCode");
+                final GLAccountData gLAccountData = new GLAccountData(glAccountId, glAccountName, glCode);
+
+                if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE)) {
+                    accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.SAVINGS_REFERENCE.getValue(), gLAccountData);
+                } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL)) {
+                    accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.SAVINGS_CONTROL.getValue(), gLAccountData);
+                } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES)) {
+                    accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_FEES.getValue(), gLAccountData);
+                } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES)) {
+                    accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_PENALTIES.getValue(), gLAccountData);
+                } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE)) {
+                    accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.TRANSFERS_SUSPENSE.getValue(), gLAccountData);
+                } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS)) {
+                    accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.INTEREST_ON_SAVINGS.getValue(), gLAccountData);
+                } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL)) {
+                    accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), gLAccountData);
+                } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.LOSSES_WRITTEN_OFF)) {
+                    accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
+                } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_INTEREST)) {
+                    accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_INTEREST.getValue(), gLAccountData);
+                }
+            }
+        }
+        return accountMappingDetails;
+    }
+
+    @Override
+    public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForLoanProduct(final Long loanProductId) {
+        return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.LOAN, loanProductId);
+    }
+
+    @Override
+    public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId) {
+        return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.SAVING, savingsProductId);
+    }
+
+    /**
+     * @param loanProductId
+     * @param paymentTypeToGLAccountMappers
+     * @return
+     */
+    private List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappings(final PortfolioProductType portfolioProductType,
+            final Long loanProductId) {
+        final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
+        final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is not null";
+
+        final List<Map<String, Object>> paymentTypeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, new Object[] {
+                portfolioProductType.getValue(), loanProductId });
+
+        List<PaymentTypeToGLAccountMapper> paymentTypeToGLAccountMappers = null;
+        for (final Map<String, Object> productToGLAccountMap : paymentTypeToFundSourceMappingsList) {
+            if (paymentTypeToGLAccountMappers == null) {
+                paymentTypeToGLAccountMappers = new ArrayList<>();
+            }
+            final Long paymentTypeId = (Long) productToGLAccountMap.get("paymentTypeId");
+            final String paymentTypeValue = (String) productToGLAccountMap.get("paymentTypeValue");
+            final PaymentTypeData paymentTypeData = PaymentTypeData.instance(paymentTypeId, paymentTypeValue);
+            final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
+            final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
+            final String glCode = (String) productToGLAccountMap.get("glCode");
+            final GLAccountData gLAccountData = new GLAccountData(glAccountId, glAccountName, glCode);
+
+            final PaymentTypeToGLAccountMapper paymentTypeToGLAccountMapper = new PaymentTypeToGLAccountMapper(paymentTypeData,
+                    gLAccountData);
+            paymentTypeToGLAccountMappers.add(paymentTypeToGLAccountMapper);
+        }
+        return paymentTypeToGLAccountMappers;
+    }
+
+    @Override
+    public List<ChargeToGLAccountMapper> fetchFeeToIncomeOrLiabilityAccountMappingsForLoanProduct(final Long loanProductId) {
+        return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, false);
+    }
+
+    @Override
+    public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForLoanProduct(final Long loanProductId) {
+        return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, true);
+    }
+
+    @Override
+    public List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) {
+        return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, false);
+    }
+
+    @Override
+    public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) {
+        return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, true);
+    }
+
+    private List<ChargeToGLAccountMapper> fetchChargeToIncomeAccountMappings(final PortfolioProductType portfolioProductType,
+            final Long loanProductId, final boolean penalty) {
+        final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
+        String sql = "select " + rm.schema() + " and product_id = ? and mapping.charge_id is not null and charge.is_penalty=";
+        if (penalty) {
+            sql = sql + " 1";
+        } else {
+            sql = sql + " 0";
+        }
+
+        final List<Map<String, Object>> chargeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, new Object[] {
+                portfolioProductType.getValue(), loanProductId });
+        List<ChargeToGLAccountMapper> chargeToGLAccountMappers = null;
+        for (final Map<String, Object> chargeToIncomeAccountMap : chargeToFundSourceMappingsList) {
+            if (chargeToGLAccountMappers == null) {
+                chargeToGLAccountMappers = new ArrayList<>();
+            }
+            final Long glAccountId = (Long) chargeToIncomeAccountMap.get("glAccountId");
+            final String glAccountName = (String) chargeToIncomeAccountMap.get("glAccountName");
+            final String glCode = (String) chargeToIncomeAccountMap.get("glCode");
+            final GLAccountData gLAccountData = new GLAccountData(glAccountId, glAccountName, glCode);
+            final Long chargeId = (Long) chargeToIncomeAccountMap.get("chargeId");
+            final String chargeName = (String) chargeToIncomeAccountMap.get("chargeName");
+            final Boolean penalty1 = (Boolean) chargeToIncomeAccountMap.get("penalty");
+            final ChargeData chargeData = ChargeData.lookup(chargeId, chargeName, penalty1);
+            final ChargeToGLAccountMapper chargeToGLAccountMapper = new ChargeToGLAccountMapper(chargeData, gLAccountData);
+            chargeToGLAccountMappers.add(chargeToGLAccountMapper);
+        }
+        return chargeToGLAccountMappers;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformService.java
new file mode 100755
index 0000000..359a3d1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformService.java
@@ -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.
+ */
+package org.apache.fineract.accounting.producttoaccountmapping.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+
+public interface ProductToGLAccountMappingWritePlatformService {
+
+    void createLoanProductToGLAccountMapping(Long loanProductId, JsonCommand command);
+
+    void createSavingProductToGLAccountMapping(Long savingProductId, JsonCommand command, DepositAccountType accountType);
+
+    Map<String, Object> updateLoanProductToGLAccountMapping(Long loanProductId, JsonCommand command, boolean accountingRuleChanged,
+            int accountingRuleTypeId);
+
+    Map<String, Object> updateSavingsProductToGLAccountMapping(Long savingsProductId, JsonCommand command, boolean accountingRuleChanged,
+            int accountingRuleTypeId, DepositAccountType accountType);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
new file mode 100755
index 0000000..8e0c87b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
@@ -0,0 +1,296 @@
+/**
+ * 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.producttoaccountmapping.service;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS;
+import org.apache.fineract.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.accounting.producttoaccountmapping.serialization.ProductToGLAccountMappingFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonElement;
+
+@Service
+public class ProductToGLAccountMappingWritePlatformServiceImpl implements ProductToGLAccountMappingWritePlatformService {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final ProductToGLAccountMappingFromApiJsonDeserializer deserializer;
+    private final LoanProductToGLAccountMappingHelper loanProductToGLAccountMappingHelper;
+    private final SavingsProductToGLAccountMappingHelper savingsProductToGLAccountMappingHelper;
+
+    @Autowired
+    public ProductToGLAccountMappingWritePlatformServiceImpl(final FromJsonHelper fromApiJsonHelper,
+            final ProductToGLAccountMappingFromApiJsonDeserializer deserializer,
+            final LoanProductToGLAccountMappingHelper loanProductToGLAccountMappingHelper,
+            final SavingsProductToGLAccountMappingHelper savingsProductToGLAccountMappingHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.deserializer = deserializer;
+        this.loanProductToGLAccountMappingHelper = loanProductToGLAccountMappingHelper;
+        this.savingsProductToGLAccountMappingHelper = savingsProductToGLAccountMappingHelper;
+    }
+
+    @Override
+    @Transactional
+    public void createLoanProductToGLAccountMapping(final Long loanProductId, final JsonCommand command) {
+        final JsonElement element = this.fromApiJsonHelper.parse(command.json());
+        final Integer accountingRuleTypeId = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
+        final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
+
+        switch (accountingRuleType) {
+            case NONE:
+            break;
+            case CASH_BASED:
+                // asset
+                this.loanProductToGLAccountMappingHelper
+                        .saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId,
+                                CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue());
+
+                // income
+                this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue());
+
+                // expenses
+                this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
+                        CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue());
+
+                // liabilities
+                this.loanProductToGLAccountMappingHelper
+                        .saveLoanToLiabilityAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId,
+                                CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue());
+
+                // advanced accounting mappings
+                this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
+                this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
+            break;
+            case ACCRUAL_UPFRONT:
+                // Fall Through
+            case ACCRUAL_PERIODIC:
+                // assets (including receivables)
+                this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue());
+
+                // income
+                this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue());
+                this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue());
+
+                // expenses
+                this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue());
+
+                // liabilities
+                this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element,
+                        LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId,
+                        ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue());
+
+                // advanced accounting mappings
+                this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
+                this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
+            break;
+        }
+    }
+
+    @Override
+    @Transactional
+    public void createSavingProductToGLAccountMapping(final Long savingProductId, final JsonCommand command, DepositAccountType accountType) {
+        final JsonElement element = this.fromApiJsonHelper.parse(command.json());
+        final Integer accountingRuleTypeId = this.fromApiJsonHelper.extractIntegerNamed(accountingRuleParamName, element,
+                Locale.getDefault());
+        final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
+
+        switch (accountingRuleType) {
+            case NONE:
+            break;
+            case CASH_BASED:
+                // asset
+                this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element,
+                        SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), savingProductId,
+                        CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue());
+
+                if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
+                    this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element,
+                            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingProductId,
+                            CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL.getValue());
+                }
+
+                // income
+                this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
+                        SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), savingProductId,
+                        CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue());
+
+                this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
+                        SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), savingProductId,
+                        CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES.getValue());
+
+                if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
+                    this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
+                            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), savingProductId,
+                            CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_INTEREST.getValue());
+                }
+
+                // expenses
+                this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
+                        SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingProductId,
+                        CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue());
+
+                if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
+                    this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
+                            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), savingProductId,
+                            CASH_ACCOUNTS_FOR_SAVINGS.LOSSES_WRITTEN_OFF.getValue());
+                }
+
+                // liability
+                this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
+                        SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), savingProductId,
+                        CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue());
+                this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
+                        SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), savingProductId,
+                        CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue());
+
+                // advanced accounting mappings
+                this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null);
+                this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null);
+            break;
+            default:
+            break;
+        }
+
+    }
+
+    @Override
+    @Transactional
+    public Map<String, Object> updateLoanProductToGLAccountMapping(final Long loanProductId, final JsonCommand command,
+            final boolean accountingRuleChanged, final int accountingRuleTypeId) {
+        /***
+         * Variable tracks all accounting mapping properties that have been
+         * updated
+         ***/
+        Map<String, Object> changes = new HashMap<>();
+        final JsonElement element = this.fromApiJsonHelper.parse(command.json());
+        final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
+
+        /***
+         * If the accounting rule has been changed, delete all existing mapping
+         * for the product and recreate a new set of mappings
+         ***/
+        if (accountingRuleChanged) {
+            this.deserializer.validateForLoanProductCreate(command.json());
+            this.loanProductToGLAccountMappingHelper.deleteLoanProductToGLAccountMapping(loanProductId);
+            createLoanProductToGLAccountMapping(loanProductId, command);
+            changes = this.loanProductToGLAccountMappingHelper.populateChangesForNewLoanProductToGLAccountMappingCreation(element,
+                    accountingRuleType);
+        }/*** else examine and update individual changes ***/
+        else {
+            this.loanProductToGLAccountMappingHelper.handleChangesToLoanProductToGLAccountMappings(loanProductId, changes, element,
+                    accountingRuleType);
+            this.loanProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, loanProductId, changes);
+            this.loanProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, loanProductId, changes);
+        }
+        return changes;
+    }
+
+    @Override
+    public Map<String, Object> updateSavingsProductToGLAccountMapping(final Long savingsProductId, final JsonCommand command,
+            final boolean accountingRuleChanged, final int accountingRuleTypeId, final DepositAccountType accountType) {
+        /***
+         * Variable tracks all accounting mapping properties that have been
+         * updated
+         ***/
+        Map<String, Object> changes = new HashMap<>();
+        final JsonElement element = this.fromApiJsonHelper.parse(command.json());
+        final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
+
+        /***
+         * If the accounting rule has been changed, delete all existing mapping
+         * for the product and recreate a new set of mappings
+         ***/
+        if (accountingRuleChanged) {
+            this.deserializer.validateForSavingsProductCreate(command.json(), accountType);
+            this.savingsProductToGLAccountMappingHelper.deleteSavingsProductToGLAccountMapping(savingsProductId);
+            createSavingProductToGLAccountMapping(savingsProductId, command, accountType);
+            changes = this.savingsProductToGLAccountMappingHelper.populateChangesForNewSavingsProductToGLAccountMappingCreation(element,
+                    accountingRuleType);
+        }/*** else examine and update individual changes ***/
+        else {
+            this.savingsProductToGLAccountMappingHelper.handleChangesToSavingsProductToGLAccountMappings(savingsProductId, changes,
+                    element, accountingRuleType);
+            this.savingsProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, savingsProductId,
+                    changes);
+            this.savingsProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, savingsProductId, changes);
+        }
+        return changes;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java
new file mode 100755
index 0000000..ed519ef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java
@@ -0,0 +1,238 @@
+/**
+ * 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.producttoaccountmapping.service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS;
+import org.apache.fineract.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+
+@Component
+public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMappingHelper {
+
+    @Autowired
+    public SavingsProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository,
+            final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
+            final ChargeRepositoryWrapper chargeRepositoryWrapper, final GLAccountRepositoryWrapper accountRepositoryWrapper,
+            final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
+        super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, chargeRepositoryWrapper, accountRepositoryWrapper,
+                paymentTypeRepositoryWrapper);
+    }
+
+    /*** Set of abstractions for saving Saving Products to GL Account Mappings ***/
+
+    public void saveSavingsToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId,
+            final int placeHolderTypeId) {
+        saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET, PortfolioProductType.SAVING);
+    }
+
+    public void saveSavingsToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId,
+            final int placeHolderTypeId) {
+        saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME, PortfolioProductType.SAVING);
+    }
+
+    public void saveSavingsToExpenseAccountMapping(final JsonElement element, final String paramName, final Long productId,
+            final int placeHolderTypeId) {
+        saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE, PortfolioProductType.SAVING);
+    }
+
+    public void saveSavingsToLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId,
+            final int placeHolderTypeId) {
+        saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.LIABILITY, PortfolioProductType.SAVING);
+    }
+
+    /*** Set of abstractions for merging Savings Products to GL Account Mappings ***/
+
+    public void mergeSavingsToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
+            final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
+        mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET,
+                PortfolioProductType.SAVING);
+    }
+
+    public void mergeSavingsToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
+            final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
+        mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME,
+                PortfolioProductType.SAVING);
+    }
+
+    public void mergeSavingsToExpenseAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
+            final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
+        mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EXPENSE,
+                PortfolioProductType.SAVING);
+    }
+
+    public void mergeSavingsToLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
+            final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
+        mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes,
+                GLAccountType.LIABILITY, PortfolioProductType.SAVING);
+    }
+
+    /*** Abstractions for payments channel related to savings products ***/
+
+    public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        savePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING);
+    }
+
+    public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING);
+    }
+
+    public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        // save both fee and penalty charges
+        saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true);
+        saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false);
+    }
+
+    public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        // update both fee and penalty charges
+        updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true);
+        updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false);
+    }
+
+    public Map<String, Object> populateChangesForNewSavingsProductToGLAccountMappingCreation(final JsonElement element,
+            final AccountingRuleType accountingRuleType) {
+        final Map<String, Object> changes = new HashMap<>();
+
+        final Long savingsReferenceId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element);
+        final Long incomeFromFeesId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element);
+        final Long incomeFromPenaltiesId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+        final Long interestOnSavingsId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element);
+        final Long savingsControlId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(),
+                element);
+        final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+        final Long overdraftControlId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
+        final Long incomeFromInterest = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), element);
+        final Long writeOffId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
+                element);
+        switch (accountingRuleType) {
+            case NONE:
+            break;
+            case CASH_BASED:
+                changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), savingsControlId);
+                changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), savingsReferenceId);
+                changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), interestOnSavingsId);
+                changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeesId);
+                changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltiesId);
+                changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
+                changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), overdraftControlId);
+                changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), incomeFromInterest);
+                changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffId);
+            break;
+            case ACCRUAL_PERIODIC:
+            break;
+            case ACCRUAL_UPFRONT:
+            break;
+            default:
+            break;
+        }
+        return changes;
+    }
+
+    /**
+     * Examines and updates each account mapping for given loan product with
+     * changes passed in from the Json element
+     * 
+     * @param savingsProductId
+     * @param changes
+     * @param element
+     * @param accountingRuleType
+     */
+    public void handleChangesToSavingsProductToGLAccountMappings(final Long savingsProductId, final Map<String, Object> changes,
+            final JsonElement element, final AccountingRuleType accountingRuleType) {
+        switch (accountingRuleType) {
+            case NONE:
+            break;
+            case CASH_BASED:
+                // asset
+                mergeSavingsToAssetAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(),
+                        savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.toString(), changes);
+
+                mergeSavingsToAssetAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
+                        savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.OVERDRAFT_PORTFOLIO_CONTROL.toString(), changes);
+
+                // income
+                mergeSavingsToIncomeAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
+                        savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.toString(), changes);
+
+                mergeSavingsToIncomeAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
+                        savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES.toString(), changes);
+
+                mergeSavingsToIncomeAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(),
+                        savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_INTEREST.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_INTEREST.toString(), changes);
+
+                // expenses
+                mergeSavingsToExpenseAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(),
+                        savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.toString(), changes);
+                mergeSavingsToExpenseAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
+                        savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.LOSSES_WRITTEN_OFF.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.LOSSES_WRITTEN_OFF.toString(), changes);
+
+                // liability
+                mergeSavingsToLiabilityAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(),
+                        savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.toString(), changes);
+                mergeSavingsToLiabilityAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(),
+                        savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue(),
+                        CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.toString(), changes);
+            break;
+            case ACCRUAL_PERIODIC:
+            break;
+            case ACCRUAL_UPFRONT:
+            break;
+            default:
+            break;
+        }
+    }
+
+    public void deleteSavingsProductToGLAccountMapping(final Long savingsProductId) {
+        deleteProductToGLAccountMapping(savingsProductId, PortfolioProductType.SAVING);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/api/ProvisioningEntriesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/api/ProvisioningEntriesApiResource.java
new file mode 100644
index 0000000..1a7b4a5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/api/ProvisioningEntriesApiResource.java
@@ -0,0 +1,145 @@
+/**
+ * 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.provisioning.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.accounting.provisioning.constant.ProvisioningEntriesApiConstants;
+import org.apache.fineract.accounting.provisioning.data.LoanProductProvisioningEntryData;
+import org.apache.fineract.accounting.provisioning.data.ProvisioningEntryData;
+import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesReadPlatformService;
+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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+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("/provisioningentries")
+@Component
+@Scope("singleton")
+public class ProvisioningEntriesApiResource {
+
+    private final PlatformSecurityContext platformSecurityContext;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final DefaultToApiJsonSerializer<ProvisioningEntryData> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<Object> entriesApiJsonSerializer;
+    private final ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public ProvisioningEntriesApiResource(final PlatformSecurityContext platformSecurityContext,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final DefaultToApiJsonSerializer<ProvisioningEntryData> toApiJsonSerializer,
+            final ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper, final DefaultToApiJsonSerializer<Object> entriesApiJsonSerializer) {
+        this.platformSecurityContext = platformSecurityContext;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.provisioningEntriesReadPlatformService = provisioningEntriesReadPlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.entriesApiJsonSerializer = entriesApiJsonSerializer;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createProvisioningEntries(final String apiRequestBodyAsJson) {
+        CommandWrapper commandWrapper = null;
+        this.platformSecurityContext.authenticatedUser();
+        commandWrapper = new CommandWrapperBuilder().createProvisioningEntries().withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+        return this.toApiJsonSerializer.serialize(commandProcessingResult);
+    }
+
+    @POST
+    @Path("{entryId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String modifyProvisioningEntry(@PathParam("entryId") final Long entryId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+        CommandWrapper commandWrapper = null;
+        this.platformSecurityContext.authenticatedUser();
+        if ("createjournalentry".equals(commandParam)) {
+            commandWrapper = new CommandWrapperBuilder().createProvisioningJournalEntries(entryId).withJson(apiRequestBodyAsJson).build();
+            final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService
+                    .logCommandSource(commandWrapper);
+            return this.toApiJsonSerializer.serialize(commandProcessingResult);
+        } else if ("recreateprovisioningentry".equals(commandParam)) {
+            commandWrapper = new CommandWrapperBuilder().reCreateProvisioningEntries(entryId).withJson(apiRequestBodyAsJson).build();
+            final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService
+                    .logCommandSource(commandWrapper);
+            return this.toApiJsonSerializer.serialize(commandProcessingResult);
+        }
+        throw new UnrecognizedQueryParamException("command", commandParam);
+    }
+
+    @GET
+    @Path("{entryId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveProvisioningEntry(@PathParam("entryId") final Long entryId, @Context final UriInfo uriInfo) {
+        platformSecurityContext.authenticatedUser();
+        ProvisioningEntryData data = this.provisioningEntriesReadPlatformService.retrieveProvisioningEntryData(entryId);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, data, ProvisioningEntriesApiConstants.PROVISIONING_ENTRY_PARAMETERS);
+    }
+
+    @GET
+    @Path("entries")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveProviioningEntries(@QueryParam("entryId") final Long entryId, @QueryParam("offset") final Integer offset,
+            @QueryParam("limit") final Integer limit, @QueryParam("officeId") final Long officeId,
+            @QueryParam("productId") final Long productId, @QueryParam("categoryId") final Long categoryId, @Context final UriInfo uriInfo) {
+        this.platformSecurityContext.authenticatedUser();
+        SearchParameters params = SearchParameters.forProvisioningEntries(entryId, officeId, productId, categoryId, offset, limit);
+        Page<LoanProductProvisioningEntryData> entries = this.provisioningEntriesReadPlatformService.retrieveProvisioningEntries(params);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.entriesApiJsonSerializer.serialize(settings, entries, ProvisioningEntriesApiConstants.PROVISIONING_ENTRY_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllProvisioningEntries(@QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @Context final UriInfo uriInfo) {
+        platformSecurityContext.authenticatedUser();
+        Page<ProvisioningEntryData> data = this.provisioningEntriesReadPlatformService.retrieveAllProvisioningEntries(offset, limit);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.entriesApiJsonSerializer.serialize(settings, data, ProvisioningEntriesApiConstants.ALL_PROVISIONING_ENTRIES);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/constant/ProvisioningEntriesApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/constant/ProvisioningEntriesApiConstants.java
new file mode 100644
index 0000000..f5a2b06
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/constant/ProvisioningEntriesApiConstants.java
@@ -0,0 +1,42 @@
+/**
+ * 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.provisioning.constant;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public interface ProvisioningEntriesApiConstants {
+
+    public final static String JSON_DATE_PARAM = "date" ;
+    
+    public final static String JSON_DATEFORMAT_PARAM = "dateFormat" ;
+    
+    public final static String JSON_LOCALE_PARAM = "locale" ;
+    
+    public final static String JSON_CREATEJOURNALENTRIES_PARAM = "createjournalentries" ;
+    
+    Set<String> supportedParameters = new HashSet<>(Arrays.asList(JSON_DATE_PARAM, JSON_DATEFORMAT_PARAM,JSON_LOCALE_PARAM,
+            JSON_CREATEJOURNALENTRIES_PARAM));
+    
+    Set<String> PROVISIONING_ENTRY_PARAMETERS = new HashSet<>(Arrays.asList("provisioningentry", "entries"));
+
+    Set<String> ALL_PROVISIONING_ENTRIES = new HashSet<>(Arrays.asList("provisioningentry"));
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/data/LoanProductProvisioningEntryData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/data/LoanProductProvisioningEntryData.java
new file mode 100644
index 0000000..eb8743e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/data/LoanProductProvisioningEntryData.java
@@ -0,0 +1,154 @@
+/**
+ * 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.provisioning.data;
+
+import java.math.BigDecimal;
+
+public class LoanProductProvisioningEntryData {
+
+    private final Long historyId;
+
+    private final Long officeId;
+
+    private final String officeName ;
+    
+    private final String currencyCode;
+
+    private final Long productId;
+
+    private final String productName ;
+    
+    private final Long categoryId;
+
+    private final String categoryName ;
+    
+    private final Long overdueInDays;
+
+    private final BigDecimal percentage;
+
+    private final BigDecimal balance;
+
+    private final BigDecimal amountreserved ;
+    
+    private final Long liablityAccount;
+
+    private final String liabilityAccountCode ;
+    
+    private final String liabilityAccountName ;
+    
+    private final Long expenseAccount;
+
+    private final String expenseAccountCode ;
+    
+    private final String expenseAccountName ;
+    
+    private final Long criteriaId ;
+    
+    public LoanProductProvisioningEntryData(final Long historyId, final Long officeId, final String currencyCode, final Long productId,
+            final Long categoryId, final Long overdueInDays, final BigDecimal percentage, final BigDecimal balance, Long liablityAccount,
+            Long expenseAccount, final Long criteriaId) {
+        this.historyId = historyId;
+        this.officeId = officeId;
+        this.currencyCode = currencyCode;
+        this.productId = productId;
+        this.categoryId = categoryId;
+        this.overdueInDays = overdueInDays;
+        this.percentage = percentage;
+        this.balance = balance;
+        this.liablityAccount = liablityAccount;
+        this.expenseAccount = expenseAccount;
+        this.amountreserved = null ;
+        this.officeName = null ;
+        this.productName = null ;
+        this.categoryName = null ;
+        this.liabilityAccountCode = null ;
+        this.liabilityAccountName = null ;
+        this.expenseAccountCode = null ;
+        this.expenseAccountName = null ;
+        this.criteriaId = criteriaId ;
+    }
+
+    public LoanProductProvisioningEntryData(final Long historyId, final Long officeId, final String officeName, final String currencyCode, final Long productId,
+            final String productName, final Long categoryId, final String categoryName, final Long overdueInDays, final BigDecimal amountReserved, 
+            Long liablityAccount, String liabilityAccountglCode, String liabilityAccountName, Long expenseAccount, String expenseAccountglCode, String expenseAccountName, final Long criteriaId) {
+        this.historyId = historyId;
+        this.officeId = officeId;
+        this.currencyCode = currencyCode;
+        this.productId = productId;
+        this.categoryId = categoryId;
+        this.categoryName = categoryName ;
+        this.overdueInDays = overdueInDays;
+        this.percentage = null;
+        this.balance = null;
+        this.liablityAccount = liablityAccount;
+        this.expenseAccount = expenseAccount;
+        this.officeName = officeName ;
+        this.productName = productName ;
+        this.amountreserved = amountReserved ;
+        this.liabilityAccountCode = liabilityAccountglCode ;
+        this.liabilityAccountName = liabilityAccountName ;
+        this.expenseAccountCode = expenseAccountglCode ;
+        this.expenseAccountName = expenseAccountName ;
+        this.criteriaId = criteriaId ;
+    }
+    public Long getHistoryId() {
+        return this.historyId;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    public Long getCategoryId() {
+        return this.categoryId;
+    }
+
+    public Long getOverdueInDays() {
+        return this.overdueInDays;
+    }
+
+    public BigDecimal getOutstandingBalance() {
+        return balance;
+    }
+
+    public BigDecimal getPercentage() {
+        return percentage;
+    }
+
+    public Long getLiablityAccount() {
+        return this.liablityAccount;
+    }
+
+    public Long getExpenseAccount() {
+        return this.expenseAccount;
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+    
+    public Long getCriteriaId() {
+        return this.criteriaId ;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/data/ProvisioningEntryData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/data/ProvisioningEntryData.java
new file mode 100644
index 0000000..4fe18b7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/data/ProvisioningEntryData.java
@@ -0,0 +1,74 @@
+/**
+ * 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.provisioning.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Date;
+
+@SuppressWarnings("unused")
+public class ProvisioningEntryData {
+
+    private Long id ;
+    
+    private Boolean journalEntry ;
+    
+    private Long createdById ;
+    
+    private String createdUser ;
+
+    Date createdDate ;
+    
+    Long modifiedById ;
+    
+    private String modifiedUser ;
+
+    private BigDecimal reservedAmount ;
+    
+    private Collection<LoanProductProvisioningEntryData> provisioningEntries ;
+    
+    public ProvisioningEntryData(final Long id, final Collection<LoanProductProvisioningEntryData> provisioningEntries) {
+        this.provisioningEntries = provisioningEntries ;
+        this.id = id ;
+    }
+
+    public ProvisioningEntryData(Long id, Boolean journalEntry, Long createdById,
+            String createdUser, Date createdDate, Long modifiedById, String modifiedUser, BigDecimal totalReservedAmount) {
+        this.id = id ;
+        this.journalEntry = journalEntry ;
+        this.createdById = createdById ;
+        this.createdUser = createdUser ;
+        this.modifiedById = modifiedById ;
+        this.modifiedUser = modifiedUser ;
+        this.createdDate = createdDate ;
+        this.reservedAmount = totalReservedAmount ;
+    }
+    
+    public void setEntries(Collection<LoanProductProvisioningEntryData> provisioningEntries) {
+        this.provisioningEntries = provisioningEntries ;
+    }
+
+    public Long getId() {
+        return this.id ;
+    }
+    
+    public Date getCreatedDate() {
+        return this.createdDate ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/LoanProductProvisioningEntry.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/LoanProductProvisioningEntry.java
new file mode 100644
index 0000000..49ea228
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/LoanProductProvisioningEntry.java
@@ -0,0 +1,128 @@
+/**
+ * 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.provisioning.domain;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCategory;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loanproduct_provisioning_entry")
+public class LoanProductProvisioningEntry extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "history_id", referencedColumnName = "id", nullable = false)
+    private ProvisioningEntry entry;
+
+    @Column(name = "criteria_id", nullable = false)
+    private Long criteriaId;
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @Column(name = "currency_code", length = 3)
+    private String currencyCode;
+
+    @ManyToOne
+    @JoinColumn(name = "product_id", nullable = false)
+    private LoanProduct loanProduct;
+
+    @ManyToOne
+    @JoinColumn(name = "category_id", nullable = false)
+    private ProvisioningCategory provisioningCategory;
+
+    @Column(name = "overdue_in_days", nullable = false)
+    private Long overdueInDays;
+
+    @Column(name = "reseve_amount", nullable = false)
+    private BigDecimal reservedAmount;
+
+    @ManyToOne
+    @JoinColumn(name = "liability_account", nullable = false)
+    private GLAccount liabilityAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "expense_account", nullable = false)
+    private GLAccount expenseAccount;
+
+    protected LoanProductProvisioningEntry() {
+        
+    }
+    public LoanProductProvisioningEntry(final LoanProduct loanProduct, final Office office, final String currencyCode,
+            final ProvisioningCategory provisioningCategory, final Long overdueInDays, final BigDecimal reservedAmount,
+            final GLAccount liabilityAccount, final GLAccount expenseAccount, Long criteriaId) {
+        this.loanProduct = loanProduct;
+        this.office = office;
+        this.currencyCode = currencyCode;
+        this.provisioningCategory = provisioningCategory;
+        this.overdueInDays = overdueInDays;
+        this.reservedAmount = reservedAmount;
+        this.liabilityAccount = liabilityAccount;
+        this.expenseAccount = expenseAccount;
+        this.criteriaId = criteriaId ;
+    }
+
+    public void setProvisioningEntry(ProvisioningEntry provisioningEntry) {
+        this.entry = provisioningEntry;
+    }
+
+    public BigDecimal getReservedAmount() {
+        return this.reservedAmount ;
+    }
+    public void addReservedAmount(BigDecimal value) {
+        this.reservedAmount = this.reservedAmount.add(value) ;
+    }
+    
+    public Office getOffice() {
+        return this.office ;
+    }
+
+    public GLAccount getLiabilityAccount() {
+        return this.liabilityAccount ;
+    }
+    
+    public String getCurrencyCode() {
+        return this.currencyCode ;
+    }
+    
+    public GLAccount getExpenseAccount() {
+        return this.expenseAccount ;
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (!obj.getClass().equals(getClass())) return false;
+        LoanProductProvisioningEntry entry = (LoanProductProvisioningEntry) obj;
+        return entry.loanProduct.getId().equals(this.loanProduct.getId())
+                && entry.provisioningCategory.getId().equals(this.provisioningCategory.getId())
+                && entry.office.getId().equals(this.office.getId())
+                && entry.getCurrencyCode().equals(this.getCurrencyCode());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/ProvisioningEntry.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/ProvisioningEntry.java
new file mode 100644
index 0000000..7d75c5c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/ProvisioningEntry.java
@@ -0,0 +1,96 @@
+/**
+ * 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.provisioning.domain;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_provisioning_history")
+public class ProvisioningEntry extends AbstractPersistable<Long> {
+
+    @Column(name = "journal_entry_created")
+    private Boolean isJournalEntryCreated;
+    
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "entry", orphanRemoval = true)
+    Collection<LoanProductProvisioningEntry> provisioningEntries = new HashSet<>();
+    
+    @OneToOne
+    @JoinColumn(name = "createdby_id")
+    private AppUser createdBy;
+
+    @Column(name = "created_date")
+    @Temporal(TemporalType.DATE)
+    private Date createdDate;
+
+    @OneToOne
+    @JoinColumn(name = "lastmodifiedby_id")
+    private AppUser lastModifiedBy;
+
+    @Column(name = "lastmodified_date")
+    @Temporal(TemporalType.DATE)
+    private Date lastModifiedDate;
+
+    protected ProvisioningEntry() {
+        
+    }
+    
+    public ProvisioningEntry(AppUser createdBy, Date createdDate, AppUser lastModifiedBy, Date lastModifiedDate, Collection<LoanProductProvisioningEntry> provisioningEntries ) {
+        this.provisioningEntries = provisioningEntries ;
+        this.createdBy = createdBy ;
+        this.createdDate = createdDate ;
+        this.lastModifiedBy = lastModifiedBy ;
+        this.lastModifiedDate = lastModifiedDate ;
+    }
+    
+    public void setProvisioningEntries(Collection<LoanProductProvisioningEntry> provisioningEntries) {
+        if(this.provisioningEntries == null) this.provisioningEntries = new HashSet<>(); 
+        this.provisioningEntries.addAll(provisioningEntries) ;
+    }
+    
+    public Collection<LoanProductProvisioningEntry> getLoanProductProvisioningEntries() {
+        return this.provisioningEntries ;
+    }
+    
+    public void setJournalEntryCreated(Boolean bool) {
+        this.isJournalEntryCreated = bool ;
+    }
+    
+    public Date getCreatedDate() {
+        return this.createdDate ;
+    }
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/ProvisioningEntryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/ProvisioningEntryRepository.java
new file mode 100644
index 0000000..5abed20
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/ProvisioningEntryRepository.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.accounting.provisioning.domain;
+
+import java.util.Date;
+
+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 ProvisioningEntryRepository extends JpaRepository<ProvisioningEntry, Long>, JpaSpecificationExecutor<ProvisioningEntry> {
+
+    @Query("select entry from ProvisioningEntry entry where entry.createdDate = :createdDate")
+    ProvisioningEntry findByProvisioningEntryDate(@Param("createdDate") Date createdDate);
+    
+    @Query("select entry from ProvisioningEntry entry where entry.createdDate = (select max(entry1.createdDate) from ProvisioningEntry entry1 where entry1.isJournalEntryCreated='1')")
+    ProvisioningEntry findExistingProvisioningEntryWithJournalEntries() ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/NoProvisioningCriteriaDefinitionFound.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/NoProvisioningCriteriaDefinitionFound.java
new file mode 100644
index 0000000..796f02e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/NoProvisioningCriteriaDefinitionFound.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.provisioning.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+
+public class NoProvisioningCriteriaDefinitionFound extends AbstractPlatformResourceNotFoundException {
+
+    public NoProvisioningCriteriaDefinitionFound() {
+        super("error.msg.no.provisioning.criteria.definitions.found", "No Provisioning Criteria Definitions are found");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/ProvisioningEntryAlreadyCreatedException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/ProvisioningEntryAlreadyCreatedException.java
new file mode 100644
index 0000000..219953b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/ProvisioningEntryAlreadyCreatedException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.provisioning.exception;
+
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+
+public class ProvisioningEntryAlreadyCreatedException extends AbstractPlatformResourceNotFoundException {
+
+    public ProvisioningEntryAlreadyCreatedException(Long id, Date date) {
+        super("error.msg.provisioningentry.already.exists.for.this.date", "ProvisioningEntry with identifier " + id + " exists for given date" + date, id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/ProvisioningEntryNotfoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/ProvisioningEntryNotfoundException.java
new file mode 100644
index 0000000..0f9ac45
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/ProvisioningEntryNotfoundException.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.accounting.provisioning.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+
+public class ProvisioningEntryNotfoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ProvisioningEntryNotfoundException(Long id) {
+        super("error.msg.provisioningentry.id.invalid", "ProvisioningEntry with identifier " + id + " does not exist", id);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/ProvisioningJournalEntriesCannotbeCreatedException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/ProvisioningJournalEntriesCannotbeCreatedException.java
new file mode 100644
index 0000000..51fa6d8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/exception/ProvisioningJournalEntriesCannotbeCreatedException.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.accounting.provisioning.exception;
+
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ProvisioningJournalEntriesCannotbeCreatedException extends AbstractPlatformResourceNotFoundException {
+
+    public ProvisioningJournalEntriesCannotbeCreatedException(Date existingEntriesDate, Date requestedDate) {
+        super("error.msg.provisioning.journalentries.cannot.be.created", "Provisioning Journal Entries already created on later date "
+                + existingEntriesDate + " than requested date " + requestedDate);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/handler/CreateProvisioningEntriesRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/handler/CreateProvisioningEntriesRequestCommandHandler.java
new file mode 100644
index 0000000..354a3d1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/handler/CreateProvisioningEntriesRequestCommandHandler.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.accounting.provisioning.handler;
+
+import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesWritePlatformService;
+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 = "PROVISIONENTRIES", action = "CREATE")
+public class CreateProvisioningEntriesRequestCommandHandler implements NewCommandSourceHandler {
+
+    private final ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService ;
+    @Autowired
+    public CreateProvisioningEntriesRequestCommandHandler(
+            final ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService) {
+        this.provisioningEntriesWritePlatformService = provisioningEntriesWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.provisioningEntriesWritePlatformService.createProvisioningEntries(jsonCommand) ;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/handler/CreateProvisioningJournalEntriesRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/handler/CreateProvisioningJournalEntriesRequestCommandHandler.java
new file mode 100644
index 0000000..b940534
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/handler/CreateProvisioningJournalEntriesRequestCommandHandler.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.accounting.provisioning.handler;
+
+import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesWritePlatformService;
+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 = "PROVISIONJOURNALENTRIES", action = "CREATE")
+public class CreateProvisioningJournalEntriesRequestCommandHandler implements NewCommandSourceHandler {
+
+    private final ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService ;
+    @Autowired
+    public CreateProvisioningJournalEntriesRequestCommandHandler(
+            final ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService) {
+        this.provisioningEntriesWritePlatformService = provisioningEntriesWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.provisioningEntriesWritePlatformService.createProvisioningJournalEntries(jsonCommand.entityId(), jsonCommand) ;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/handler/ReCreateProvisioningEntryRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/handler/ReCreateProvisioningEntryRequestCommandHandler.java
new file mode 100644
index 0000000..1156333
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/handler/ReCreateProvisioningEntryRequestCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.accounting.provisioning.handler;
+
+import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesWritePlatformService;
+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 = "PROVISIONENTRIES", action = "RECREATE")
+public class ReCreateProvisioningEntryRequestCommandHandler  implements NewCommandSourceHandler {
+
+    private final ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService ;
+    
+    @Autowired
+    public ReCreateProvisioningEntryRequestCommandHandler(
+            final ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService) {
+        this.provisioningEntriesWritePlatformService = provisioningEntriesWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.provisioningEntriesWritePlatformService.reCreateProvisioningEntries(jsonCommand.entityId(), jsonCommand) ;
+    }
+
+}
+
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/serialization/ProvisioningEntriesDefinitionJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/serialization/ProvisioningEntriesDefinitionJsonDeserializer.java
new file mode 100644
index 0000000..4e61c78
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/serialization/ProvisioningEntriesDefinitionJsonDeserializer.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.accounting.provisioning.serialization;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.provisioning.constant.ProvisioningEntriesApiConstants;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.provisioning.exception.ProvisioningCriteriaCannotBeCreatedException;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class ProvisioningEntriesDefinitionJsonDeserializer implements ProvisioningEntriesApiConstants {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ProvisioningEntriesDefinitionJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForCreate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new ProvisioningCriteriaCannotBeCreatedException(
+                "error.msg.provisioningentry.cannot.be.created",
+                "locale, dateformat, date, createjournalentries params are missing in the request");
+
+        }
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("provisioningcriteria");
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        baseDataValidator.reset().parameter(JSON_DATEFORMAT_PARAM).value(locale).notNull();
+        final String dateformat = this.fromApiJsonHelper.extractDateFormatParameter(element.getAsJsonObject());
+        baseDataValidator.reset().parameter(JSON_DATEFORMAT_PARAM).value(dateformat).notBlank();
+        LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed(JSON_DATE_PARAM, element) ;
+        baseDataValidator.reset().parameter(JSON_DATE_PARAM).value(localDate).notBlank();
+        baseDataValidator.reset().parameter(JSON_DATE_PARAM).value(localDate).validateDateBeforeOrEqual(DateUtils.getLocalDateOfTenant()) ;
+        if(this.fromApiJsonHelper.parameterExists(JSON_CREATEJOURNALENTRIES_PARAM, element)) {
+            Boolean bool = this.fromApiJsonHelper.extractBooleanNamed(JSON_CREATEJOURNALENTRIES_PARAM, element) ;
+            baseDataValidator.reset().parameter(JSON_CREATEJOURNALENTRIES_PARAM).value(bool).validateForBooleanValue() ;
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformService.java
new file mode 100644
index 0000000..310a031
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformService.java
@@ -0,0 +1,45 @@
+/**
+ * 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.provisioning.service;
+
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.fineract.accounting.provisioning.data.LoanProductProvisioningEntryData;
+import org.apache.fineract.accounting.provisioning.data.ProvisioningEntryData;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+
+
+public interface ProvisioningEntriesReadPlatformService {
+
+    public Collection<LoanProductProvisioningEntryData> retrieveLoanProductsProvisioningData(Date date) ;
+    
+    public ProvisioningEntryData retrieveProvisioningEntryData(Long entryId) ;
+    
+    public Page<ProvisioningEntryData> retrieveAllProvisioningEntries(Integer offset, Integer limit) ;
+    
+    public ProvisioningEntryData retrieveProvisioningEntryData(String date) ;
+    
+    public ProvisioningEntryData retrieveProvisioningEntryDataByCriteriaId(Long criteriaId) ;
+    
+    public ProvisioningEntryData retrieveExistingProvisioningIdDateWithJournals() ;
+    
+    public Page<LoanProductProvisioningEntryData> retrieveProvisioningEntries(SearchParameters searchParams) ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java
new file mode 100644
index 0000000..3608740
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java
@@ -0,0 +1,355 @@
+/**
+ * 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.provisioning.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.provisioning.data.LoanProductProvisioningEntryData;
+import org.apache.fineract.accounting.provisioning.data.ProvisioningEntryData;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.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 ProvisioningEntriesReadPlatformServiceImpl implements ProvisioningEntriesReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    private final PaginationHelper<LoanProductProvisioningEntryData> loanProductProvisioningEntryDataPaginationHelper = new PaginationHelper<>();
+    private final PaginationHelper<ProvisioningEntryData> provisioningEntryDataPaginationHelper = new PaginationHelper<>();
+
+    @Autowired
+    public ProvisioningEntriesReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public Collection<LoanProductProvisioningEntryData> retrieveLoanProductsProvisioningData(Date date) {
+        String formattedDate = new SimpleDateFormat("yyyy-MM-dd").format(date);
+        formattedDate = "'" + formattedDate + "'";
+        LoanProductProvisioningEntryMapper mapper = new LoanProductProvisioningEntryMapper(formattedDate);
+        final String sql = mapper.schema();
+        return this.jdbcTemplate.query(sql, mapper, new Object[] {});
+    }
+
+    private static final class LoanProductProvisioningEntryMapper implements RowMapper<LoanProductProvisioningEntryData> {
+
+        private final StringBuilder sqlQuery;
+
+        protected LoanProductProvisioningEntryMapper(String formattedDate) {
+            sqlQuery = new StringBuilder()
+                    .append("select if(loan.loan_type_enum=1, mclient.office_id, mgroup.office_id) as office_id, loan.loan_type_enum, pcd.criteria_id as criteriaid, loan.product_id,loan.currency_code,")
+                    .append("GREATEST(datediff(")
+                    .append(formattedDate)
+                    .append(",sch.duedate),0) as numberofdaysoverdue,sch.duedate, pcd.category_id, pcd.provision_percentage,")
+                    .append("loan.total_outstanding_derived as outstandingbalance, pcd.liability_account, pcd.expense_account from m_loan_repayment_schedule sch")
+                    .append(" LEFT JOIN m_loan loan on sch.loan_id = loan.id")
+                    .append(" JOIN m_loanproduct_provisioning_mapping lpm on lpm.product_id = loan.product_id")
+                    .append(" JOIN m_provisioning_criteria_definition pcd on pcd.criteria_id = lpm.criteria_id and ")
+                    .append("(pcd.min_age <= GREATEST(datediff(").append(formattedDate).append(",sch.duedate),0) and ")
+                    .append("GREATEST(datediff(").append(formattedDate).append(",sch.duedate),0) <= pcd.max_age) and ")
+                    .append("pcd.criteria_id is not null ").append("LEFT JOIN m_client mclient ON mclient.id = loan.client_id ")
+                    .append("LEFT JOIN m_group mgroup ON mgroup.id = loan.group_id ")
+                    .append("where loan.loan_status_id=300 and sch.duedate = ")
+                    .append("(select MIN(sch1.duedate) from m_loan_repayment_schedule sch1 where sch1.loan_id=loan.id and sch1.completed_derived=false)");
+        }
+
+        @Override
+        @SuppressWarnings("unused")
+        public LoanProductProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws SQLException {
+            Long officeId = rs.getLong("office_id");
+            Long productId = rs.getLong("product_id");
+            String currentcyCode = rs.getString("currency_code");
+            Long overdueDays = rs.getLong("numberofdaysoverdue");
+            Long categoryId = rs.getLong("category_id");
+            BigDecimal percentage = rs.getBigDecimal("provision_percentage");
+            BigDecimal outstandingBalance = rs.getBigDecimal("outstandingbalance");
+            Long liabilityAccountCode = rs.getLong("liability_account");
+            Long expenseAccountCode = rs.getLong("expense_account");
+            Long criteriaId = rs.getLong("criteriaid");
+            Long historyId = null;
+
+            return new LoanProductProvisioningEntryData(historyId, officeId, currentcyCode, productId, categoryId, overdueDays, percentage,
+                    outstandingBalance, liabilityAccountCode, expenseAccountCode, criteriaId);
+        }
+
+        public String schema() {
+            return sqlQuery.toString();
+        }
+    }
+
+    @Override
+    public ProvisioningEntryData retrieveProvisioningEntryData(Long entryId) {
+        ProvisioningEntryDataMapperWithSumReserved mapper1 = new ProvisioningEntryDataMapperWithSumReserved();
+        final String sql1 = "select" + mapper1.getSchema() + " where entry.id = ?";
+        ProvisioningEntryData data = this.jdbcTemplate.queryForObject(sql1, mapper1, new Object[] { entryId });
+        return data;
+    }
+
+    private static final class ProvisioningEntryDataMapper implements RowMapper<ProvisioningEntryData> {
+
+        private final StringBuilder sqlQuery = new StringBuilder()
+                .append(" entry.id, entry.journal_entry_created, entry.createdby_id, entry.created_date, created.username as createduser,")
+                .append("entry.lastmodifiedby_id, modified.username as modifieduser, entry.lastmodified_date ")
+                .append("from m_provisioning_history entry ").append("left JOIN m_appuser created ON created.id = entry.createdby_id ")
+                .append("left JOIN m_appuser modified ON modified.id = entry.lastmodifiedby_id ");
+
+        @Override
+        @SuppressWarnings("unused")
+        public ProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws SQLException {
+            Long id = rs.getLong("id");
+            Boolean journalEntry = rs.getBoolean("journal_entry_created");
+            Long createdById = rs.getLong("createdby_id");
+            String createdUser = rs.getString("createduser");
+            Date createdDate = rs.getDate("created_date");
+            Long modifiedById = rs.getLong("lastmodifiedby_id");
+            String modifieUser = rs.getString("modifieduser");
+            BigDecimal totalReservedAmount = null;
+            return new ProvisioningEntryData(id, journalEntry, createdById, createdUser, createdDate, modifiedById, modifieUser,
+                    totalReservedAmount);
+        }
+
+        public String getSchema() {
+            return sqlQuery.toString();
+        }
+
+    }
+
+    private static final class LoanProductProvisioningEntryRowMapper implements RowMapper<LoanProductProvisioningEntryData> {
+
+        private final StringBuilder sqlQuery = new StringBuilder()
+                .append(" entry.id, entry.history_id as historyId, office_id, entry.criteria_id as criteriaid, office.name as officename, product.name as productname, entry.product_id, ")
+                .append("category_id, category.category_name, liability.id as liabilityid, liability.gl_code as liabilitycode, liability.name as liabilityname, ")
+                .append("expense.id as expenseid, expense.gl_code as expensecode, expense.name as expensename, entry.currency_code, entry.overdue_in_days, entry.reseve_amount from m_loanproduct_provisioning_entry entry ")
+                .append("left join m_office office ON office.id = entry.office_id ")
+                .append("left join m_product_loan product ON product.id = entry.product_id ")
+                .append("left join m_provision_category category ON category.id = entry.category_id ")
+                .append("left join acc_gl_account liability ON liability.id = entry.liability_account ")
+                .append("left join acc_gl_account expense ON expense.id = entry.expense_account ");
+
+        @Override
+        @SuppressWarnings("unused")
+        public LoanProductProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws SQLException {
+            Long historyId = rs.getLong("historyId");
+            Long officeId = rs.getLong("office_id");
+            String officeName = rs.getString("officename");
+            Long productId = rs.getLong("product_id");
+            String productName = rs.getString("productname");
+            String currentcyCode = rs.getString("currency_code");
+            Long overdueDays = rs.getLong("overdue_in_days");
+            Long categoryId = rs.getLong("category_id");
+            String categoryName = rs.getString("category_name");
+            BigDecimal amountreserved = rs.getBigDecimal("reseve_amount");
+            Long liabilityAccountCode = rs.getLong("liabilityid");
+            String liabilityAccountglCode = rs.getString("liabilitycode");
+            String expenseAccountglCode = rs.getString("expensecode");
+            Long expenseAccountCode = rs.getLong("expenseid");
+            Long criteriaId = rs.getLong("criteriaid");
+            String liabilityAccountName = rs.getString("liabilityname");
+            String expenseAccountName = rs.getString("expensename");
+            return new LoanProductProvisioningEntryData(historyId, officeId, officeName, currentcyCode, productId, productName, categoryId,
+                    categoryName, overdueDays, amountreserved, liabilityAccountCode, liabilityAccountglCode, liabilityAccountName,
+                    expenseAccountCode, expenseAccountglCode, expenseAccountName, criteriaId);
+        }
+
+        public String getSchema() {
+            return sqlQuery.toString();
+        }
+    }
+
+    private static final class ProvisioningEntryDataMapperWithSumReserved implements RowMapper<ProvisioningEntryData> {
+
+        private final StringBuilder sqlQuery = new StringBuilder()
+                .append(" entry.id, journal_entry_created, createdby_id, created_date, created.username as createduser,")
+                .append("lastmodifiedby_id, modified.username as modifieduser, lastmodified_date, SUM(reserved.reseve_amount) as totalreserved ")
+                .append("from m_provisioning_history entry ")
+                .append("JOIN m_loanproduct_provisioning_entry reserved on entry.id = reserved.history_id ")
+                .append("left JOIN m_appuser created ON created.id = entry.createdby_id ")
+                .append("left JOIN m_appuser modified ON modified.id = entry.lastmodifiedby_id ");
+
+        @Override
+        @SuppressWarnings("unused")
+        public ProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws SQLException {
+            Long id = rs.getLong("id");
+            Boolean journalEntry = rs.getBoolean("journal_entry_created");
+            Long createdById = rs.getLong("createdby_id");
+            String createdUser = rs.getString("createduser");
+            Date createdDate = rs.getDate("created_date");
+            Long modifiedById = rs.getLong("lastmodifiedby_id");
+            String modifieUser = rs.getString("modifieduser");
+            BigDecimal totalReservedAmount = rs.getBigDecimal("totalreserved");
+            return new ProvisioningEntryData(id, journalEntry, createdById, createdUser, createdDate, modifiedById, modifieUser,
+                    totalReservedAmount);
+        }
+
+        public String getSchema() {
+            return sqlQuery.toString();
+        }
+
+    }
+
+    @Override
+    public Page<ProvisioningEntryData> retrieveAllProvisioningEntries(Integer offset, Integer limit) {
+        ProvisioningEntryDataMapper mapper = new ProvisioningEntryDataMapper();
+        StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(mapper.getSchema());
+        sqlBuilder.append(" order by entry.created_date");
+        if(limit != null ) {
+            sqlBuilder.append(" limit ").append(limit);    
+        }
+        if(offset != null) {
+            sqlBuilder.append(" offset ").append(offset);    
+        }
+        
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        Object[] whereClauseItemsitems = new Object[] {};
+        return this.provisioningEntryDataPaginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(),
+                whereClauseItemsitems, mapper);
+    }
+
+    @Override
+    public ProvisioningEntryData retrieveProvisioningEntryData(String date) {
+        ProvisioningEntryDataMapper mapper1 = new ProvisioningEntryDataMapper();
+        final String sql1 = "select " + mapper1.getSchema() + " where entry.created_date like " + "'" + date + "%'";
+        ProvisioningEntryData data = null;
+        try {
+            data = this.jdbcTemplate.queryForObject(sql1, mapper1, new Object[] {});
+        } catch (EmptyResultDataAccessException e) {
+
+        }
+
+        return data;
+    }
+
+    @Override
+    public ProvisioningEntryData retrieveProvisioningEntryDataByCriteriaId(Long criteriaId) {
+        ProvisioningEntryData data = null;
+        LoanProductProvisioningEntryRowMapper mapper = new LoanProductProvisioningEntryRowMapper();
+        final String sql = "select " + mapper.getSchema() + " where entry.criteria_id = ?";
+        Collection<LoanProductProvisioningEntryData> entries = this.jdbcTemplate.query(sql, mapper, new Object[] { criteriaId });
+        if (entries != null && entries.size() > 0) {
+            Long entryId = ((LoanProductProvisioningEntryData) entries.toArray()[0]).getHistoryId();
+            ProvisioningEntryDataMapper mapper1 = new ProvisioningEntryDataMapper();
+            final String sql1 = "select " + mapper1.getSchema() + " where entry.id = ?";
+            data = this.jdbcTemplate.queryForObject(sql1, mapper1, new Object[] { entryId });
+            data.setEntries(entries);
+        }
+        return data;
+    }
+
+    @Override
+    public ProvisioningEntryData retrieveExistingProvisioningIdDateWithJournals() {
+        ProvisioningEntryData data = null;
+        ProvisioningEntryIdDateRowMapper mapper = new ProvisioningEntryIdDateRowMapper();
+        try {
+            data = this.jdbcTemplate.queryForObject(mapper.schema(), mapper, new Object[] {});
+        } catch (EmptyResultDataAccessException e) {
+            data = null;
+        }
+        return data;
+    }
+
+    private static final class ProvisioningEntryIdDateRowMapper implements RowMapper<ProvisioningEntryData> {
+
+        StringBuffer buff = new StringBuffer().append("select history1.id, history1.created_date from m_provisioning_history history1 ")
+                .append("where history1.created_date = (select max(history2.created_date) from m_provisioning_history history2 ")
+                .append("where history2.journal_entry_created='1')");
+
+        @Override
+        @SuppressWarnings("unused")
+        public ProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws SQLException {
+            Map<String, Object> map = new HashMap<>();
+            Long id = rs.getLong("id");
+            Date createdDate = rs.getDate("created_date");
+            Long createdBy = null;
+            String createdName = null;
+            Long modifiedBy = null;
+            String modifiedName = null;
+            BigDecimal totalReservedAmount = null;
+            return new ProvisioningEntryData(id, Boolean.TRUE, createdBy, createdName, createdDate, modifiedBy, modifiedName,
+                    totalReservedAmount);
+        }
+
+        public String schema() {
+            return buff.toString();
+        }
+    }
+
+    @Override
+    public Page<LoanProductProvisioningEntryData> retrieveProvisioningEntries(SearchParameters searchParams) {
+        LoanProductProvisioningEntryRowMapper mapper = new LoanProductProvisioningEntryRowMapper();
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(mapper.getSchema());
+        String whereClose = " where ";
+        List<Object> items = new ArrayList<>();
+
+        if (searchParams.isProvisioningEntryIdPassed()) {
+            sqlBuilder.append(whereClose + " entry.history_id = ?");
+            items.add(searchParams.getProvisioningEntryId());
+            whereClose = " and ";
+        }
+
+        if (searchParams.isOfficeIdPassed()) {
+            sqlBuilder.append(whereClose + " entry.office_id = ?");
+            items.add(searchParams.getOfficeId());
+            whereClose = " and ";
+        }
+
+        if (searchParams.isProductIdPassed()) {
+            sqlBuilder.append(whereClose + " entry.product_id = ?");
+            items.add(searchParams.getProductId());
+            whereClose = " and ";
+        }
+
+        if (searchParams.isCategoryIdPassed()) {
+            sqlBuilder.append(whereClose + " entry.category_id = ?");
+            items.add(searchParams.getCategoryId());
+        }
+        sqlBuilder.append(" order by entry.id");
+
+        if (searchParams.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParams.getLimit());
+            if (searchParams.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParams.getOffset());
+            }
+        }
+        Object[] whereClauseItemsitems = items.toArray();
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.loanProductProvisioningEntryDataPaginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(),
+                whereClauseItemsitems, mapper);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformService.java
new file mode 100644
index 0000000..3bdebac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformService.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.accounting.provisioning.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+
+public interface ProvisioningEntriesWritePlatformService {
+
+    CommandProcessingResult createProvisioningEntries(JsonCommand command);
+    
+    CommandProcessingResult reCreateProvisioningEntries(Long provisioningEntryId, JsonCommand command) ;
+    
+    CommandProcessingResult createProvisioningJournalEntries(Long provisioningEntryId, JsonCommand command);
+    
+    void generateLoanLossProvisioningAmount() ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..2eb191e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,247 @@
+/**
+ * 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.provisioning.service;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
+import org.apache.fineract.accounting.provisioning.data.LoanProductProvisioningEntryData;
+import org.apache.fineract.accounting.provisioning.data.ProvisioningEntryData;
+import org.apache.fineract.accounting.provisioning.domain.LoanProductProvisioningEntry;
+import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntry;
+import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntryRepository;
+import org.apache.fineract.accounting.provisioning.exception.NoProvisioningCriteriaDefinitionFound;
+import org.apache.fineract.accounting.provisioning.exception.ProvisioningEntryAlreadyCreatedException;
+import org.apache.fineract.accounting.provisioning.exception.ProvisioningEntryNotfoundException;
+import org.apache.fineract.accounting.provisioning.exception.ProvisioningJournalEntriesCannotbeCreatedException;
+import org.apache.fineract.accounting.provisioning.serialization.ProvisioningEntriesDefinitionJsonDeserializer;
+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.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaData;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCategory;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCategoryRepository;
+import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+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 com.google.gson.JsonObject;
+
+@Service
+public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements ProvisioningEntriesWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService;
+    private final ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService ;
+    private final LoanProductRepository loanProductRepository;
+    private final GLAccountRepository glAccountRepository;
+    private final OfficeRepository officeRepository;
+    private final ProvisioningCategoryRepository provisioningCategoryRepository;
+    private final PlatformSecurityContext platformSecurityContext;
+    private final ProvisioningEntryRepository provisioningEntryRepository;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+    private final ProvisioningEntriesDefinitionJsonDeserializer fromApiJsonDeserializer;
+    private final FromJsonHelper fromApiJsonHelper;
+    
+    @Autowired
+    public ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl(
+            final ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService,
+            final ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService,
+            final LoanProductRepository loanProductRepository, final GLAccountRepository glAccountRepository,
+            final OfficeRepository officeRepository, final ProvisioningCategoryRepository provisioningCategoryRepository,
+            final PlatformSecurityContext platformSecurityContext, final ProvisioningEntryRepository provisioningEntryRepository,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService,
+            final ProvisioningEntriesDefinitionJsonDeserializer fromApiJsonDeserializer, final FromJsonHelper fromApiJsonHelper) {
+        this.provisioningEntriesReadPlatformService = provisioningEntriesReadPlatformService;
+        this.provisioningCriteriaReadPlatformService = provisioningCriteriaReadPlatformService ;
+        this.loanProductRepository = loanProductRepository;
+        this.glAccountRepository = glAccountRepository;
+        this.officeRepository = officeRepository;
+        this.provisioningCategoryRepository = provisioningCategoryRepository;
+        this.platformSecurityContext = platformSecurityContext;
+        this.provisioningEntryRepository = provisioningEntryRepository;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public CommandProcessingResult createProvisioningJournalEntries(Long provisioningEntryId, JsonCommand command) {
+        ProvisioningEntry requestedEntry = this.provisioningEntryRepository.findOne(provisioningEntryId);
+        if (requestedEntry == null) { throw new ProvisioningEntryNotfoundException(provisioningEntryId); }
+
+        ProvisioningEntryData exisProvisioningEntryData = this.provisioningEntriesReadPlatformService
+                .retrieveExistingProvisioningIdDateWithJournals();
+        revertAndAddJournalEntries(exisProvisioningEntryData, requestedEntry);
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(requestedEntry.getId()).build();
+    }
+
+    private void revertAndAddJournalEntries(ProvisioningEntryData existingEntryData, ProvisioningEntry requestedEntry) {
+        if (existingEntryData != null) {
+            validateForCreateJournalEntry(existingEntryData, requestedEntry);
+            this.journalEntryWritePlatformService.revertProvisioningJournalEntries(requestedEntry.getCreatedDate(),
+                    existingEntryData.getId(), PortfolioProductType.PROVISIONING.getValue());
+        }
+        if(requestedEntry.getLoanProductProvisioningEntries() == null || requestedEntry.getLoanProductProvisioningEntries().size() == 0) {
+            requestedEntry.setJournalEntryCreated(Boolean.FALSE);    
+        }else {
+            requestedEntry.setJournalEntryCreated(Boolean.TRUE);
+        }
+        
+        this.provisioningEntryRepository.save(requestedEntry);
+        this.journalEntryWritePlatformService.createProvisioningJournalEntries(requestedEntry);
+    }
+
+    private void validateForCreateJournalEntry(ProvisioningEntryData existingEntry, ProvisioningEntry requested) {
+        Date existingDate = existingEntry.getCreatedDate();
+        Date requestedDate = requested.getCreatedDate();
+        if (existingDate.after(requestedDate) || existingDate.equals(requestedDate)) { throw new ProvisioningJournalEntriesCannotbeCreatedException(
+                existingEntry.getCreatedDate(), requestedDate); }
+    }
+
+    private boolean isJournalEntriesRequired(JsonCommand command) {
+        boolean bool = false;
+        if (this.fromApiJsonHelper.parameterExists("createjournalentries", command.parsedJson())) {
+            JsonObject jsonObject = command.parsedJson().getAsJsonObject();
+            bool = jsonObject.get("createjournalentries").getAsBoolean();
+        }
+        return bool;
+    }
+
+    private Date parseDate(JsonCommand command) {
+        LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed("date", command.parsedJson());
+        return localDate.toDate();
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.GENERATE_LOANLOSS_PROVISIONING)
+    public void generateLoanLossProvisioningAmount() {
+        Date currentDate  = DateUtils.getLocalDateOfTenant().toDate() ;
+        boolean addJournalEntries = true;
+        try {
+            Collection<ProvisioningCriteriaData> criteriaCollection = this.provisioningCriteriaReadPlatformService.retrieveAllProvisioningCriterias() ; 
+            if(criteriaCollection == null || criteriaCollection.size() == 0){
+                return ;
+                //FIXME: Do we need to throw NoProvisioningCriteriaDefinitionFound()?
+            }
+            createProvsioningEntry(currentDate, addJournalEntries);
+        } catch (ProvisioningEntryAlreadyCreatedException peace) {} catch (DataIntegrityViolationException dive) {}
+    }
+
+    @Override
+    public CommandProcessingResult createProvisioningEntries(JsonCommand command) {
+        this.fromApiJsonDeserializer.validateForCreate(command.json());
+        Date createdDate = parseDate(command);
+        boolean addJournalEntries = isJournalEntriesRequired(command);
+        try {
+            Collection<ProvisioningCriteriaData> criteriaCollection = this.provisioningCriteriaReadPlatformService.retrieveAllProvisioningCriterias() ; 
+            if(criteriaCollection == null || criteriaCollection.size() == 0){
+                throw new NoProvisioningCriteriaDefinitionFound() ;
+            }
+            ProvisioningEntry requestedEntry = createProvsioningEntry(createdDate, addJournalEntries);
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(requestedEntry.getId()).build();
+        } catch (DataIntegrityViolationException dve) {
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private ProvisioningEntry createProvsioningEntry(Date date, boolean addJournalEntries) {
+        ProvisioningEntry existingEntry = this.provisioningEntryRepository.findByProvisioningEntryDate(date);
+        if (existingEntry != null) { throw new ProvisioningEntryAlreadyCreatedException(existingEntry.getId(),
+                existingEntry.getCreatedDate()); }
+        AppUser currentUser = this.platformSecurityContext.authenticatedUser();
+        AppUser lastModifiedBy = null;
+        Date lastModifiedDate = null;
+        Collection<LoanProductProvisioningEntry> nullEntries = null;
+        ProvisioningEntry requestedEntry = new ProvisioningEntry(currentUser, date, lastModifiedBy, lastModifiedDate, nullEntries);
+        Collection<LoanProductProvisioningEntry> entries = generateLoanProvisioningEntry(requestedEntry, date);
+        requestedEntry.setProvisioningEntries(entries);
+        if (addJournalEntries) {
+            ProvisioningEntryData exisProvisioningEntryData = this.provisioningEntriesReadPlatformService
+                    .retrieveExistingProvisioningIdDateWithJournals();
+            revertAndAddJournalEntries(exisProvisioningEntryData, requestedEntry);
+        } else {
+            this.provisioningEntryRepository.save(requestedEntry);
+        }
+        return requestedEntry;
+    }
+
+    @Override
+    public CommandProcessingResult reCreateProvisioningEntries(Long provisioningEntryId, JsonCommand command) {
+        ProvisioningEntry requestedEntry = this.provisioningEntryRepository.findOne(provisioningEntryId);
+        if (requestedEntry == null) { throw new ProvisioningEntryNotfoundException(provisioningEntryId); }
+        requestedEntry.getLoanProductProvisioningEntries().clear();
+        this.provisioningEntryRepository.save(requestedEntry);
+        Collection<LoanProductProvisioningEntry> entries = generateLoanProvisioningEntry(requestedEntry, requestedEntry.getCreatedDate());
+        requestedEntry.setProvisioningEntries(entries);
+        this.provisioningEntryRepository.save(requestedEntry);
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(requestedEntry.getId()).build();
+    }
+
+    private Collection<LoanProductProvisioningEntry> generateLoanProvisioningEntry(ProvisioningEntry parent, Date date) {
+        Collection<LoanProductProvisioningEntryData> entries = this.provisioningEntriesReadPlatformService
+                .retrieveLoanProductsProvisioningData(date);
+        Map<LoanProductProvisioningEntry, LoanProductProvisioningEntry> provisioningEntries = new HashMap<>();
+        for (LoanProductProvisioningEntryData data : entries) {
+            LoanProduct loanProduct = this.loanProductRepository.findOne(data.getProductId());
+            Office office = this.officeRepository.findOne(data.getOfficeId());
+            ProvisioningCategory provisioningCategory = provisioningCategoryRepository.findOne(data.getCategoryId());
+            GLAccount liabilityAccount = glAccountRepository.findOne(data.getLiablityAccount());
+            GLAccount expenseAccount = glAccountRepository.findOne(data.getExpenseAccount());
+            MonetaryCurrency currency = loanProduct.getPrincipalAmount().getCurrency();
+            Money money = Money.of(currency, data.getOutstandingBalance());
+            Money amountToReserve = money.percentageOf(data.getPercentage(), MoneyHelper.getRoundingMode());
+            Long criteraId = data.getCriteriaId();
+            LoanProductProvisioningEntry entry = new LoanProductProvisioningEntry(loanProduct, office, data.getCurrencyCode(),
+                    provisioningCategory, data.getOverdueInDays(), amountToReserve.getAmount(), liabilityAccount, expenseAccount, criteraId);
+            entry.setProvisioningEntry(parent);
+            if (!provisioningEntries.containsKey(entry)) {
+                provisioningEntries.put(entry, entry);
+            } else {
+                LoanProductProvisioningEntry entry1 = provisioningEntries.get(entry);
+                entry1.addReservedAmount(entry.getReservedAmount());
+            }
+        }
+        return provisioningEntries.values();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleApiResource.java
new file mode 100755
index 0000000..7955f14
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleApiResource.java
@@ -0,0 +1,260 @@
+/**
+ * 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.rule.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+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.fineract.accounting.common.AccountingConstants;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService;
+import org.apache.fineract.accounting.rule.data.AccountingRuleData;
+import org.apache.fineract.accounting.rule.data.AccountingTagRuleData;
+import org.apache.fineract.accounting.rule.service.AccountingRuleReadPlatformService;
+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.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+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.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+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("/accountingrules")
+@Component
+@Scope("singleton")
+public class AccountingRuleApiResource {
+
+    private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "officeId", "officeName",
+            "accountToDebitId", "accountToCreditId", "name", "description", "systemDefined", "allowedCreditTagOptions",
+            "allowedDebitTagOptions", "debitTags", "creditTags", "creditAccounts", "debitAccounts", "allowMultipleCreditEntries",
+            "allowMultipleDebitEntries", "tag"));
+
+    private final String resourceNameForPermission = "ACCOUNTINGRULE";
+
+    private final AccountingRuleReadPlatformService accountingRuleReadPlatformService;
+    private final GLAccountReadPlatformService accountReadPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final DefaultToApiJsonSerializer<AccountingRuleData> apiJsonSerializerService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PlatformSecurityContext context;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+
+    @Autowired
+    public AccountingRuleApiResource(final PlatformSecurityContext context,
+            final AccountingRuleReadPlatformService accountingRuleReadPlatformService,
+            final DefaultToApiJsonSerializer<AccountingRuleData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper, final GLAccountReadPlatformService accountReadPlatformService,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final OfficeReadPlatformService officeReadPlatformService, final CodeValueReadPlatformService codeValueReadPlatformService) {
+        this.context = context;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiJsonSerializerService = toApiJsonSerializer;
+        this.accountingRuleReadPlatformService = accountingRuleReadPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.accountReadPlatformService = accountReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        AccountingRuleData accountingRuleData = null;
+        accountingRuleData = handleTemplate(accountingRuleData);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, accountingRuleData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllAccountingRules(@Context final UriInfo uriInfo) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        currentUser.validateHasReadPermission(this.resourceNameForPermission);
+
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        boolean isAssociationParametersExists = false;
+        if (!associationParameters.isEmpty()) {
+            if (associationParameters.contains("all")) {
+                isAssociationParametersExists = true; // If true, retrieve
+                                                      // additional fields for
+                                                      // journal entry form.
+            }
+        }
+        final List<AccountingRuleData> accountingRuleDatas = this.accountingRuleReadPlatformService.retrieveAllAccountingRules(
+                hierarchySearchString, isAssociationParametersExists);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, accountingRuleDatas, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{accountingRuleId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retreiveAccountingRule(@PathParam("accountingRuleId") final Long accountingRuleId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        AccountingRuleData accountingRuleData = this.accountingRuleReadPlatformService.retrieveAccountingRuleById(accountingRuleId);
+        if (settings.isTemplate()) {
+            accountingRuleData = handleTemplate(accountingRuleData);
+        }
+
+        return this.apiJsonSerializerService.serialize(settings, accountingRuleData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createAccountingRule(final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createAccountingRule().withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @PUT
+    @Path("{accountingRuleId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateAccountingRule(@PathParam("accountingRuleId") final Long accountingRuleId, final String jsonRequestBody) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateAccountingRule(accountingRuleId).withJson(jsonRequestBody)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @DELETE
+    @Path("{accountingRuleId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteAccountingRule(@PathParam("accountingRuleId") final Long accountingRuleId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteAccountingRule(accountingRuleId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    private AccountingRuleData handleTemplate(AccountingRuleData accountingRuleData) {
+        final List<GLAccountData> allowedAccounts = this.accountReadPlatformService.retrieveAllEnabledDetailGLAccounts();
+        final List<OfficeData> allowedOffices = (List<OfficeData>) this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+        final Collection<CodeValueData> allowedTagOptions = this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME);
+
+        allowedTagOptions.addAll(this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME));
+        allowedTagOptions.addAll(this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME));
+        allowedTagOptions.addAll(this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.INCOME_TAG_OPTION_CODE_NAME));
+        allowedTagOptions.addAll(this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME));
+
+        if (accountingRuleData == null) {
+
+            final Collection<CodeValueData> allowedCreditTagOptions = allowedTagOptions;
+            final Collection<CodeValueData> allowedDebitTagOptions = allowedTagOptions;
+
+            accountingRuleData = new AccountingRuleData(allowedAccounts, allowedOffices, allowedCreditTagOptions, allowedDebitTagOptions);
+
+        } else {
+
+            final Collection<CodeValueData> allowedCreditTagOptions;
+            final Collection<CodeValueData> allowedDebitTagOptions;
+
+            if (accountingRuleData.getCreditTags() != null) {
+                allowedCreditTagOptions = retrieveSelectedTags(allowedTagOptions, accountingRuleData.getCreditTags());
+            } else {
+                allowedCreditTagOptions = allowedTagOptions;
+            }
+
+            if (accountingRuleData.getDebitTags() != null) {
+                allowedDebitTagOptions = retrieveSelectedTags(allowedTagOptions, accountingRuleData.getDebitTags());
+            } else {
+                allowedDebitTagOptions = allowedTagOptions;
+            }
+
+            accountingRuleData = new AccountingRuleData(accountingRuleData, allowedAccounts, allowedOffices, allowedCreditTagOptions,
+                    allowedDebitTagOptions);
+        }
+        return accountingRuleData;
+    }
+
+    private Collection<CodeValueData> retrieveSelectedTags(final Collection<CodeValueData> allowedTagOptions,
+            final List<AccountingTagRuleData> existedTags) {
+        final Collection<CodeValueData> tempOptions = new ArrayList<>(allowedTagOptions);
+        final Map<Long, CodeValueData> selectedTags = new HashMap<>();
+        for (final AccountingTagRuleData accountingTagRuleData : existedTags) {
+            for (final CodeValueData codeValueData : tempOptions) {
+                if (codeValueData.getId().equals(accountingTagRuleData.getTag().getId())) {
+                    selectedTags.put(codeValueData.getId(), codeValueData);
+                }
+            }
+        }
+        tempOptions.removeAll(selectedTags.values());
+        return tempOptions;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleJsonInputParams.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleJsonInputParams.java
new file mode 100755
index 0000000..91d286e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleJsonInputParams.java
@@ -0,0 +1,57 @@
+/**
+ * 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.rule.api;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/***
+ * Enum of all parameters passed in while creating/updating a loan product
+ ***/
+public enum AccountingRuleJsonInputParams {
+    ID("id"), OFFICE_ID("officeId"), ACCOUNT_TO_DEBIT("accountToDebit"), ACCOUNT_TO_CREDIT("accountToCredit"), NAME("name"), DESCRIPTION(
+            "description"), SYSTEM_DEFINED("systemDefined"), DEBIT_ACCOUNT_TAGS("debitTags"), CREDIT_ACCOUNT_TAGS("creditTags"), ALLOW_MULTIPLE_CREDIT_ENTRIES(
+            "allowMultipleCreditEntries"), ALLOW_MULTIPLE_DEBIT_ENTRIES("allowMultipleDebitEntries");
+
+    private final String value;
+
+    private AccountingRuleJsonInputParams(final String value) {
+        this.value = value;
+    }
+
+    private static final Set<String> values = new HashSet<>();
+    static {
+        for (final AccountingRuleJsonInputParams type : AccountingRuleJsonInputParams.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/accounting/rule/data/AccountingRuleData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/data/AccountingRuleData.java
new file mode 100755
index 0000000..1acd5a1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/data/AccountingRuleData.java
@@ -0,0 +1,132 @@
+/**
+ * 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.rule.data;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+
+/**
+ * Immutable object representing a General Ledger Account
+ * 
+ * Note: no getter/setters required as google-gson will produce json from fields
+ * of object.
+ */
+public class AccountingRuleData {
+
+    private final Long id;
+    private final Long officeId;
+    private final String officeName;
+    private final String name;
+    private final String description;
+    private final boolean systemDefined;
+    private final boolean allowMultipleDebitEntries;
+    private final boolean allowMultipleCreditEntries;
+    private final List<AccountingTagRuleData> creditTags;
+    private final List<AccountingTagRuleData> debitTags;
+
+    // template
+    @SuppressWarnings("unused")
+    private List<OfficeData> allowedOffices = new ArrayList<OfficeData>();
+    @SuppressWarnings("unused")
+    private List<GLAccountData> allowedAccounts = new ArrayList<GLAccountData>();
+    @SuppressWarnings("unused")
+    private final Collection<CodeValueData> allowedCreditTagOptions;
+    @SuppressWarnings("unused")
+    private final Collection<CodeValueData> allowedDebitTagOptions;
+    private final List<GLAccountDataForLookup> creditAccounts;
+    private final List<GLAccountDataForLookup> debitAccounts;
+
+    public AccountingRuleData(final AccountingRuleData accountingRuleData, final List<GLAccountData> allowedAccounts,
+            final List<OfficeData> allowedOffices, final Collection<CodeValueData> allowedCreditTagOptions,
+            final Collection<CodeValueData> allowedDebitTagOptions) {
+        this.id = accountingRuleData.id;
+        this.officeId = accountingRuleData.officeId;
+        this.officeName = accountingRuleData.officeName;
+        this.name = accountingRuleData.name;
+        this.description = accountingRuleData.description;
+        this.systemDefined = accountingRuleData.systemDefined;
+        this.allowMultipleDebitEntries = accountingRuleData.allowMultipleDebitEntries;
+        this.allowMultipleCreditEntries = accountingRuleData.allowMultipleCreditEntries;
+        this.allowedOffices = allowedOffices;
+        this.allowedAccounts = allowedAccounts;
+        this.allowedCreditTagOptions = allowedCreditTagOptions;
+        this.allowedDebitTagOptions = allowedDebitTagOptions;
+        this.creditTags = accountingRuleData.creditTags;
+        this.debitTags = accountingRuleData.debitTags;
+        this.creditAccounts = accountingRuleData.creditAccounts;
+        this.debitAccounts = accountingRuleData.debitAccounts;
+    }
+
+    public AccountingRuleData(final List<GLAccountData> allowedAccounts, final List<OfficeData> allowedOffices,
+            final Collection<CodeValueData> allowedCreditTagOptions, final Collection<CodeValueData> allowedDebitTagOptions) {
+        this.id = null;
+        this.officeId = null;
+        this.officeName = null;
+        this.name = null;
+        this.description = null;
+        this.systemDefined = false;
+        this.allowMultipleDebitEntries = false;
+        this.allowMultipleCreditEntries = false;
+        this.allowedOffices = allowedOffices;
+        this.allowedAccounts = allowedAccounts;
+        this.allowedCreditTagOptions = allowedCreditTagOptions;
+        this.allowedDebitTagOptions = allowedDebitTagOptions;
+        this.creditTags = null;
+        this.debitTags = null;
+        this.creditAccounts = null;
+        this.debitAccounts = null;
+    }
+
+    public AccountingRuleData(final Long id, final Long officeId, final String officeName, final String name, final String description,
+            final boolean systemDefined, final boolean allowMultipleDebitEntries, final boolean allowMultipleCreditEntries,
+            final List<AccountingTagRuleData> creditTags, final List<AccountingTagRuleData> debitTags,
+            final List<GLAccountDataForLookup> creditAccounts, final List<GLAccountDataForLookup> debitAccounts) {
+        this.id = id;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.name = name;
+        this.description = description;
+        this.systemDefined = systemDefined;
+        this.allowMultipleDebitEntries = allowMultipleDebitEntries;
+        this.allowMultipleCreditEntries = allowMultipleCreditEntries;
+        this.allowedOffices = null;
+        this.allowedAccounts = null;
+        this.allowedCreditTagOptions = null;
+        this.allowedDebitTagOptions = null;
+        this.creditTags = creditTags;
+        this.debitTags = debitTags;
+        this.creditAccounts = creditAccounts;
+        this.debitAccounts = debitAccounts;
+    }
+
+    public List<AccountingTagRuleData> getCreditTags() {
+        return this.creditTags;
+    }
+
+    public List<AccountingTagRuleData> getDebitTags() {
+        return this.debitTags;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/data/AccountingTagRuleData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/data/AccountingTagRuleData.java
new file mode 100644
index 0000000..930beda
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/data/AccountingTagRuleData.java
@@ -0,0 +1,42 @@
+/**
+ * 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.rule.data;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class AccountingTagRuleData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    private final CodeValueData tag;
+    @SuppressWarnings("unused")
+    private final EnumOptionData transactionType;
+
+    public AccountingTagRuleData(final Long id, final CodeValueData tag, final EnumOptionData transactionType) {
+        this.id = id;
+        this.tag = tag;
+        this.transactionType = transactionType;
+    }
+
+    public CodeValueData getTag() {
+        return this.tag;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingRule.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingRule.java
new file mode 100755
index 0000000..c0027b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingRule.java
@@ -0,0 +1,267 @@
+/**
+ * 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.rule.domain;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+import org.apache.fineract.accounting.rule.api.AccountingRuleJsonInputParams;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "acc_accounting_rule", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "accounting_rule_name_unique") })
+public class AccountingRule extends AbstractPersistable<Long> {
+
+    @Column(name = "name", nullable = false, length = 500)
+    private String name;
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = true)
+    private Office office;
+
+    @ManyToOne
+    @JoinColumn(name = "debit_account_id", nullable = true)
+    private GLAccount accountToDebit;
+
+    @ManyToOne
+    @JoinColumn(name = "credit_account_id", nullable = true)
+    private GLAccount accountToCredit;
+
+    @Column(name = "description", nullable = true, length = 500)
+    private String description;
+
+    @Column(name = "system_defined", nullable = false)
+    private Boolean systemDefined;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "accountingRule", orphanRemoval = true)
+    private final List<AccountingTagRule> accountingTagRules = new ArrayList<>();
+
+    @Column(name = "allow_multiple_credits", nullable = false)
+    private boolean allowMultipleCreditEntries;
+
+    @Column(name = "allow_multiple_debits", nullable = false)
+    private boolean allowMultipleDebitEntries;
+
+    protected AccountingRule() {}
+
+    private AccountingRule(final Office office, final GLAccount accountToDebit, final GLAccount accountToCredit, final String name,
+            final String description, final boolean systemDefined, final boolean allowMultipleCreditEntries,
+            final boolean allowMultipleDebitEntries) {
+        this.accountToDebit = accountToDebit;
+        this.accountToCredit = accountToCredit;
+        this.name = name;
+        this.office = office;
+        this.description = StringUtils.defaultIfEmpty(description, null);
+        if (this.description != null) {
+            this.description = this.description.trim();
+        }
+        this.systemDefined = systemDefined;
+        this.allowMultipleCreditEntries = allowMultipleCreditEntries;
+        this.allowMultipleDebitEntries = allowMultipleDebitEntries;
+    }
+
+    public static AccountingRule fromJson(final Office office, final GLAccount accountToDebit, final GLAccount accountToCredit,
+            final JsonCommand command, final boolean allowMultipleCreditEntries, final boolean allowMultipleDebitEntries) {
+        final String name = command.stringValueOfParameterNamed(AccountingRuleJsonInputParams.NAME.getValue());
+        final String description = command.stringValueOfParameterNamed(AccountingRuleJsonInputParams.DESCRIPTION.getValue());
+        final boolean systemDefined = false;
+        return new AccountingRule(office, accountToDebit, accountToCredit, name, description, systemDefined, allowMultipleCreditEntries,
+                allowMultipleDebitEntries);
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+        handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.OFFICE_ID.getValue(), this.office == null ? 0L
+                : this.office.getId());
+        handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(),
+                this.accountToDebit == null ? 0L : this.accountToDebit.getId());
+        handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
+                this.accountToCredit == null ? 0L : this.accountToCredit.getId());
+        handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.NAME.getValue(), this.name);
+        handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.DESCRIPTION.getValue(), this.description);
+        handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.SYSTEM_DEFINED.getValue(), this.systemDefined);
+        handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(),
+                this.allowMultipleCreditEntries);
+        handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(),
+                this.allowMultipleDebitEntries);
+        return actualChanges;
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            final String propertyToBeUpdated) {
+        if (command.isChangeInStringParameterNamed(paramName, propertyToBeUpdated)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            // now update actual property
+            if (paramName.equals(AccountingRuleJsonInputParams.DESCRIPTION.getValue())) {
+                this.description = newValue;
+            } else if (paramName.equals(AccountingRuleJsonInputParams.NAME.getValue())) {
+                this.name = newValue;
+            }
+        }
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            final boolean propertyToBeUpdated) {
+        if (command.isChangeInBooleanParameterNamed(paramName, propertyToBeUpdated)) {
+            final Boolean newValue = command.booleanObjectValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            // now update actual property
+            if (paramName.equals(AccountingRuleJsonInputParams.SYSTEM_DEFINED.getValue())) {
+                this.systemDefined = newValue;
+            } else if (paramName.equals(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())) {
+                if (this.accountToCredit == null) {
+                    this.allowMultipleCreditEntries = newValue;
+                }
+            } else if (paramName.equals(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())) {
+                if (this.accountToDebit == null) {
+                    this.allowMultipleDebitEntries = newValue;
+                }
+            }
+        }
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            final Long propertyToBeUpdated) {
+        if (command.isChangeInLongParameterNamed(paramName, propertyToBeUpdated)) {
+            final Long newValue = command.longValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            // now update actual property
+            if (paramName.equals(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue())) {
+                // do nothing as this is a nested property
+            } else if (paramName.equals(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue())) {
+                // do nothing as this is a nested property
+            } else if (paramName.equals(AccountingRuleJsonInputParams.OFFICE_ID.getValue())) {
+                // do nothing as this is a nested property
+            }
+        }
+    }
+
+    public void setOffice(final Office office) {
+        this.office = office;
+    }
+
+    public Office getOffice() {
+        return this.office;
+    }
+
+    public GLAccount getAccountToDebit() {
+        return this.accountToDebit;
+    }
+
+    public GLAccount getAccountToCredit() {
+        return this.accountToCredit;
+    }
+
+    public void setAccountToDebit(final GLAccount accountToDebit) {
+        this.accountToDebit = accountToDebit;
+    }
+
+    public void setAccountToCredit(final GLAccount accountToCredit) {
+        this.accountToCredit = accountToCredit;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public List<AccountingTagRule> getAccountingTagRules() {
+        return this.accountingTagRules;
+    }
+
+    public void updateAccountingRuleForTags(final List<AccountingTagRule> debitAccountingTagRules) {
+        for (final AccountingTagRule accountingTagRule : debitAccountingTagRules) {
+            accountingTagRule.updateAccountingTagRule(this);
+            this.accountingTagRules.add(accountingTagRule);
+        }
+    }
+
+    public void updateDebitAccount(final GLAccount accountToDebit) {
+        this.accountToDebit = accountToDebit;
+        this.allowMultipleDebitEntries = false;
+    }
+
+    public void updateCreditAccount(final GLAccount accountToCredit) {
+        this.accountToCredit = accountToCredit;
+        this.allowMultipleCreditEntries = false;
+    }
+
+    public void updateAllowMultipleCreditEntries(final boolean allowMultipleCreditEntries) {
+        this.allowMultipleCreditEntries = allowMultipleCreditEntries;
+    }
+
+    public void updateAllowMultipleDebitEntries(final boolean allowMultipleDebitEntries) {
+        this.allowMultipleDebitEntries = allowMultipleDebitEntries;
+    }
+
+    public void updateTags(final JournalEntryType type) {
+        final Set<AccountingTagRule> existedCreditTags = new HashSet<>();
+        final Set<AccountingTagRule> existedDebitTags = new HashSet<>();
+        for (final AccountingTagRule accountingTagRule : this.accountingTagRules) {
+            if (accountingTagRule.isCreditAccount()) {
+                existedCreditTags.add(accountingTagRule);
+            } else if (accountingTagRule.isDebitAccount()) {
+                existedDebitTags.add(accountingTagRule);
+            }
+        }
+
+        if (type.isCreditType()) {
+            this.accountingTagRules.retainAll(existedCreditTags);
+        } else if (type.isDebitType()) {
+            this.accountingTagRules.retainAll(existedDebitTags);
+        }
+
+    }
+
+    public Set<AccountingTagRule> getAccountingTagRulesByType(final JournalEntryType type) {
+        final Set<AccountingTagRule> existedTags = new HashSet<>();
+        for (final AccountingTagRule accountingTagRule : this.accountingTagRules) {
+            if (accountingTagRule.getAccountType().equals(type.getValue())) {
+                existedTags.add(accountingTagRule);
+            }
+        }
+        return existedTags;
+    }
+
+    public void removeOldTags(final List<AccountingTagRule> accountsToRemove) {
+        this.accountingTagRules.removeAll(accountsToRemove);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingRuleRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingRuleRepository.java
new file mode 100755
index 0000000..89e67c2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingRuleRepository.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.accounting.rule.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 AccountingRuleRepository extends JpaRepository<AccountingRule, Long>, JpaSpecificationExecutor<AccountingRule> {
+
+    @Query("from AccountingRule accountingRule where accountingRule.office is null or accountingRule.office.id =:officeId")
+    AccountingRule getAccountingRuleByOfficeId(@Param("officeId") Long officeId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingRuleRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingRuleRepositoryWrapper.java
new file mode 100755
index 0000000..ee4a047
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingRuleRepositoryWrapper.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.rule.domain;
+
+import org.apache.fineract.accounting.rule.exception.AccountingRuleNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link AccountingRuleRepository} .
+ * </p>
+ */
+@Service
+public class AccountingRuleRepositoryWrapper {
+
+    private final AccountingRuleRepository repository;
+
+    @Autowired
+    public AccountingRuleRepositoryWrapper(final AccountingRuleRepository repository) {
+        this.repository = repository;
+    }
+
+    public AccountingRule findOneWithNotFoundDetection(final Long id) {
+        final AccountingRule accountingRule = this.repository.findOne(id);
+        if (accountingRule == null) { throw new AccountingRuleNotFoundException(id); }
+        return accountingRule;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingTagRule.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingTagRule.java
new file mode 100644
index 0000000..f47e977
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/domain/AccountingTagRule.java
@@ -0,0 +1,80 @@
+/**
+ * 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.rule.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "acc_rule_tags", uniqueConstraints = { @UniqueConstraint(columnNames = { "acc_rule_id", "tag_id", "acc_type_enum" }, name = "UNIQUE_ACCOUNT_RULE_TAGS") })
+public class AccountingTagRule extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "acc_rule_id", nullable = false)
+    private AccountingRule accountingRule;
+
+    @ManyToOne
+    @JoinColumn(name = "tag_id", nullable = false)
+    private CodeValue tagId;
+
+    @Column(name = "acc_type_enum", nullable = false)
+    private Integer accountType;
+
+    public static AccountingTagRule create(final CodeValue tagId, final Integer accountType) {
+        return new AccountingTagRule(tagId, accountType);
+    }
+
+    public AccountingTagRule(final CodeValue tagId, final Integer accountType) {
+        this.tagId = tagId;
+        this.accountType = accountType;
+    }
+
+    public void updateAccountingTagRule(final AccountingRule accountingRule) {
+        this.accountingRule = accountingRule;
+    }
+
+    public AccountingTagRule() {
+        // TODO Auto-generated constructor stub
+    }
+
+    public Integer getAccountType() {
+        return this.accountType;
+    }
+
+    public boolean isDebitAccount() {
+        return JournalEntryType.fromInt(this.accountType).isDebitType();
+    }
+
+    public boolean isCreditAccount() {
+        return JournalEntryType.fromInt(this.accountType).isCreditType();
+    }
+
+    public Long getTagId() {
+        return this.tagId.getId();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleDataException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleDataException.java
new file mode 100644
index 0000000..1082fd5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleDataException.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.accounting.rule.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class AccountingRuleDataException extends AbstractPlatformDomainRuleException {
+
+    public AccountingRuleDataException(final String debitOrCreditAccount, final String debitOrCreditTags) {
+        super("error.msg.accounting.rule." + debitOrCreditAccount + ".or." + debitOrCreditTags + ".required", debitOrCreditAccount + " or "
+                + debitOrCreditTags + " required", debitOrCreditAccount, debitOrCreditTags);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleDuplicateException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleDuplicateException.java
new file mode 100755
index 0000000..3f34d87
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleDuplicateException.java
@@ -0,0 +1,37 @@
+/**
+ * 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.rule.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when an Accounting rule with a given name
+ * already exists
+ */
+public class AccountingRuleDuplicateException extends AbstractPlatformDomainRuleException {
+
+    public AccountingRuleDuplicateException(final String name) {
+        super("error.msg.accounting.rule.duplicate", "An accounting rule with the name " + name + " already exists" + name);
+    }
+
+    public AccountingRuleDuplicateException() {
+        super("error.msg.accounting.rule.tag.duplicate", "The accounting rule already have the tags which you defined");
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleInvalidDeleteException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleInvalidDeleteException.java
new file mode 100755
index 0000000..910f5cc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleInvalidDeleteException.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.accounting.rule.exception;
+
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when a GL Closure Delte command is invalid
+ */
+public class AccountingRuleInvalidDeleteException extends AbstractPlatformDomainRuleException {
+
+    public AccountingRuleInvalidDeleteException(final Long officeId, final String officeName, final Date latestclosureDate) {
+        super("error.msg.glclosure.invalid.delete", "The latest closure for office with Id " + officeId + " and name " + officeName
+                + " is on " + latestclosureDate.toString() + ", please delete this closure first", latestclosureDate);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleInvalidException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleInvalidException.java
new file mode 100755
index 0000000..fdfa56b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleInvalidException.java
@@ -0,0 +1,52 @@
+/**
+ * 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.rule.exception;
+
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when a GL Closure is Invalid
+ */
+public class AccountingRuleInvalidException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons for invalid Accounting Closure **/
+    public static enum GL_CLOSURE_INVALID_REASON {
+        FUTURE_DATE, ACCOUNTING_CLOSED;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
+                return "Accounting closures cannot be made for a future date";
+            } else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) { return "Accounting Closure for this branch has already been defined for a greater date"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
+                return "error.msg.glclosure.invalid.future.date";
+            } else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) { return "error.msg.glclosure.invalid.accounting.closed"; }
+            return name().toString();
+        }
+    }
+
+    public AccountingRuleInvalidException(final GL_CLOSURE_INVALID_REASON reason, final Date date) {
+        super(reason.errorCode(), reason.errorMessage(), date);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleNotFoundException.java
new file mode 100755
index 0000000..121d6a3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/exception/AccountingRuleNotFoundException.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.accounting.rule.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when Accounting rule resources are not
+ * found.
+ */
+public class AccountingRuleNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public AccountingRuleNotFoundException(final Long id) {
+        super("error.msg.accounting.rule.id.invalid", "Accounting Rule with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/handler/CreateAccountingRuleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/handler/CreateAccountingRuleCommandHandler.java
new file mode 100755
index 0000000..8b69cdc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/handler/CreateAccountingRuleCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.rule.handler;
+
+import org.apache.fineract.accounting.rule.service.AccountingRuleWritePlatformService;
+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 = "ACCOUNTINGRULE", action = "CREATE")
+public class CreateAccountingRuleCommandHandler implements NewCommandSourceHandler {
+
+    private final AccountingRuleWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateAccountingRuleCommandHandler(final AccountingRuleWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.createAccountingRule(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/handler/DeleteAccountingRuleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/handler/DeleteAccountingRuleCommandHandler.java
new file mode 100755
index 0000000..190be85
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/handler/DeleteAccountingRuleCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.rule.handler;
+
+import org.apache.fineract.accounting.rule.service.AccountingRuleWritePlatformService;
+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 = "ACCOUNTINGRULE", action = "DELETE")
+public class DeleteAccountingRuleCommandHandler implements NewCommandSourceHandler {
+
+    private final AccountingRuleWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteAccountingRuleCommandHandler(final AccountingRuleWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteAccountingRule(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/handler/UpdateAccountingRuleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/handler/UpdateAccountingRuleCommandHandler.java
new file mode 100755
index 0000000..ad9b5e3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/handler/UpdateAccountingRuleCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.rule.handler;
+
+import org.apache.fineract.accounting.rule.service.AccountingRuleWritePlatformService;
+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 = "ACCOUNTINGRULE", action = "UPDATE")
+public class UpdateAccountingRuleCommandHandler implements NewCommandSourceHandler {
+
+    private final AccountingRuleWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateAccountingRuleCommandHandler(final AccountingRuleWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.updateAccountingRule(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/serialization/AccountingRuleCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/serialization/AccountingRuleCommandFromApiJsonDeserializer.java
new file mode 100755
index 0000000..8788323
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/serialization/AccountingRuleCommandFromApiJsonDeserializer.java
@@ -0,0 +1,224 @@
+/**
+ * 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.rule.serialization;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.rule.api.AccountingRuleJsonInputParams;
+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 org.springframework.util.ObjectUtils;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class AccountingRuleCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = AccountingRuleJsonInputParams.getAllValues();
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public AccountingRuleCommandFromApiJsonDeserializer(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("AccountingRule");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long accountToDebitId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(),
+                element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue()).value(accountToDebitId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long accountToCreditId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
+                element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue()).value(accountToCreditId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.OFFICE_ID.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.OFFICE_ID.getValue()).value(officeId).notNull()
+                .integerGreaterThanZero();
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(AccountingRuleJsonInputParams.NAME.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.NAME.getValue()).value(name).notBlank().notExceedingLengthOf(100);
+
+        final String description = this.fromApiJsonHelper.extractStringNamed(AccountingRuleJsonInputParams.DESCRIPTION.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DESCRIPTION.getValue()).value(description).ignoreIfNull()
+                .notExceedingLengthOf(500);
+
+        final String[] creditTags = this.fromApiJsonHelper.extractArrayNamed(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue(),
+                element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue()).value(creditTags).ignoreIfNull()
+                .arrayNotEmpty();
+        validateCreditOrDebitTagArray(creditTags, baseDataValidator, AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
+
+        final String[] debitTags = this.fromApiJsonHelper.extractArrayNamed(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue(),
+                element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue()).value(debitTags).ignoreIfNull()
+                .arrayNotEmpty();
+        validateCreditOrDebitTagArray(debitTags, baseDataValidator, AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
+
+        if (creditTags == null && accountToCreditId == null) {
+            final String creditTag = AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue();
+            final String creditAccount = AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue();
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(creditAccount).append(".or.")
+                    .append(creditTag).append(".required");
+            final StringBuilder defaultUserMessage = new StringBuilder("The parameter ").append(creditAccount).append(" or")
+                    .append(creditTag).append(" required");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultUserMessage.toString(),
+                    creditAccount + "," + creditTag, new Object[] { creditAccount, creditTag });
+            dataValidationErrors.add(error);
+        }
+
+        if (debitTags == null && accountToDebitId == null) {
+            final String debitTag = AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue();
+            final String debitAccount = AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue();
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(debitAccount).append(".or.")
+                    .append(debitTag).append(".required");
+            final StringBuilder defaultUserMessage = new StringBuilder("The parameter ").append(debitAccount).append(" or")
+                    .append(debitTag).append(" required");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultUserMessage.toString(),
+                    debitAccount + "," + debitTag, new Object[] { debitAccount, debitTag });
+            dataValidationErrors.add(error);
+        }
+
+        final String allowMultipleCredits = this.fromApiJsonHelper.extractStringNamed(
+                AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())
+                .value(allowMultipleCredits).ignoreIfNull().notBlank();
+        final Boolean allowMultipleCreditEntries = this.fromApiJsonHelper.extractBooleanNamed(
+                AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())
+                .value(allowMultipleCreditEntries).ignoreIfNull();
+        final String allowMultipleDebits = this.fromApiJsonHelper.extractStringNamed(
+                AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())
+                .value(allowMultipleDebits).ignoreIfNull().notBlank();
+        final Boolean allowMultipleDebitEntries = this.fromApiJsonHelper.extractBooleanNamed(
+                AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())
+                .value(allowMultipleDebitEntries).ignoreIfNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateCreditOrDebitTagArray(final String[] creditOrDebitTagArray, final DataValidatorBuilder baseDataValidator,
+            final String parameter) {
+        if (creditOrDebitTagArray != null && !ObjectUtils.isEmpty(creditOrDebitTagArray)) {
+            for (final String tag : creditOrDebitTagArray) {
+                baseDataValidator.reset().parameter(parameter).value(tag).ignoreIfNull().notBlank().longGreaterThanZero();
+            }
+        }
+    }
+
+    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("AccountingRule");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long accountToDebitId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(),
+                element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue()).value(accountToDebitId)
+                .ignoreIfNull().notBlank().integerGreaterThanZero();
+
+        final Long accountToCreditId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
+                element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue()).value(accountToCreditId)
+                .ignoreIfNull().notBlank().integerGreaterThanZero();
+
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.OFFICE_ID.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.OFFICE_ID.getValue()).value(officeId).ignoreIfNull()
+                .integerGreaterThanZero();
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(AccountingRuleJsonInputParams.NAME.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.NAME.getValue()).value(name).ignoreIfNull().notBlank()
+                .notExceedingLengthOf(100);
+
+        final String description = this.fromApiJsonHelper.extractStringNamed(AccountingRuleJsonInputParams.DESCRIPTION.getValue(), element);
+
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DESCRIPTION.getValue()).value(description).ignoreIfNull()
+                .notExceedingLengthOf(500);
+
+        final String[] creditTags = this.fromApiJsonHelper.extractArrayNamed(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue(),
+                element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue()).value(creditTags).ignoreIfNull()
+                .arrayNotEmpty();
+        validateCreditOrDebitTagArray(creditTags, baseDataValidator, AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
+
+        final String[] debitTags = this.fromApiJsonHelper.extractArrayNamed(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue(),
+                element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue()).value(debitTags).ignoreIfNull()
+                .arrayNotEmpty();
+        validateCreditOrDebitTagArray(debitTags, baseDataValidator, AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
+
+        final String allowMultipleCredits = this.fromApiJsonHelper.extractStringNamed(
+                AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())
+                .value(allowMultipleCredits).ignoreIfNull().notBlank();
+        final Boolean allowMultipleCreditEntries = this.fromApiJsonHelper.extractBooleanNamed(
+                AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())
+                .value(allowMultipleCreditEntries).ignoreIfNull();
+        final String allowMultipleDebits = this.fromApiJsonHelper.extractStringNamed(
+                AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())
+                .value(allowMultipleDebits).ignoreIfNull().notBlank();
+        final Boolean allowMultipleDebitEntries = this.fromApiJsonHelper.extractBooleanNamed(
+                AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(), element);
+        baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())
+                .value(allowMultipleDebitEntries).ignoreIfNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformService.java
new file mode 100755
index 0000000..f078f64
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.rule.service;
+
+import java.util.List;
+
+import org.apache.fineract.accounting.rule.data.AccountingRuleData;
+
+public interface AccountingRuleReadPlatformService {
+
+    List<AccountingRuleData> retrieveAllAccountingRules(String hierarchySearchString, boolean isAssociationParametersExists);
+
+    AccountingRuleData retrieveAccountingRuleById(Long accountingRuleId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformServiceImpl.java
new file mode 100755
index 0000000..7db4f18
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformServiceImpl.java
@@ -0,0 +1,208 @@
+/**
+ * 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.rule.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
+import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+import org.apache.fineract.accounting.rule.data.AccountingRuleData;
+import org.apache.fineract.accounting.rule.data.AccountingTagRuleData;
+import org.apache.fineract.accounting.rule.exception.AccountingRuleNotFoundException;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AccountingRuleReadPlatformServiceImpl implements AccountingRuleReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final GLAccountReadPlatformService glAccountReadPlatformService;
+
+    @Autowired
+    public AccountingRuleReadPlatformServiceImpl(final RoutingDataSource dataSource,
+            final GLAccountReadPlatformService glAccountReadPlatformService) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.glAccountReadPlatformService = glAccountReadPlatformService;
+    }
+
+    private static final class AccountingRuleDataExtractor implements ResultSetExtractor<Map<Long, AccountingRuleData>> {
+
+        private final String schemaSql;
+        private final JdbcTemplate jdbcTemplate;
+        private final GLAccountReadPlatformService glAccountReadPlatformService;
+        private final boolean isAssociationParametersExists;
+
+        public AccountingRuleDataExtractor(final JdbcTemplate jdbcTemplate,
+                final GLAccountReadPlatformService glAccountReadPlatformService, final boolean isAssociationParametersExists) {
+            this.jdbcTemplate = jdbcTemplate;
+            this.glAccountReadPlatformService = glAccountReadPlatformService;
+            this.isAssociationParametersExists = isAssociationParametersExists;
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder
+                    .append(" rule.id as id,rule.name as name, rule.office_id as officeId,office.name as officeName,")
+                    .append(" rule.description as description, rule.system_defined as systemDefined, rule.allow_multiple_debits as allowMultipleDebitEntries, rule.allow_multiple_credits as allowMultipleCreditEntries, ")
+                    .append("debitAccount.id AS debitAccountId, debitAccount.name as debitAccountName, debitAccount.gl_code as debitAccountGLCode, ")
+                    .append("creditAccount.id AS creditAccountId, creditAccount.name as creditAccountName, creditAccount.gl_code as creditAccountGLCode")
+                    .append(" from m_office AS office, acc_accounting_rule AS rule ")
+                    .append(" LEFT JOIN acc_gl_account AS creditAccount ON rule.credit_account_id = creditAccount.id ")
+                    .append(" LEFT JOIN acc_gl_account AS debitAccount ON rule.debit_account_id = debitAccount.id ")
+                    .append("WHERE office.id=rule.office_id ");
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public Map<Long, AccountingRuleData> extractData(final ResultSet rs) throws SQLException, DataAccessException {
+            final Map<Long, AccountingRuleData> extractedData = new HashMap<>();
+
+            while (rs.next()) {
+                final Long id = rs.getLong("id");
+                AccountingRuleData accountingRuleData = extractedData.get(id);
+                if (accountingRuleData == null) {
+                    final Long officeId = JdbcSupport.getLong(rs, "officeId");
+                    final String officeName = rs.getString("officeName");
+                    final String name = rs.getString("name");
+                    final String description = rs.getString("description");
+                    final Long accountToDebitId = JdbcSupport.getLong(rs, "debitAccountId");
+                    final Long accountToCreditId = JdbcSupport.getLong(rs, "creditAccountId");
+                    final boolean systemDefined = rs.getBoolean("systemDefined");
+                    final boolean allowMultipleDebitEntries = rs.getBoolean("allowMultipleDebitEntries");
+                    final boolean allowMultipleCreditEntries = rs.getBoolean("allowMultipleCreditEntries");
+                    final String debitAccountName = rs.getString("debitAccountName");
+                    final String creditAccountName = rs.getString("creditAccountName");
+                    final String debitAccountGLCode = rs.getString("debitAccountGLCode");
+                    final String creditAccountGLCode = rs.getString("creditAccountGLCode");
+
+                    final List<AccountingTagRuleData> creditTags;
+                    final List<AccountingTagRuleData> debitTags;
+                    final List<GLAccountDataForLookup> creditAccounts;
+                    final List<GLAccountDataForLookup> debitAccounts;
+
+                    if (accountToCreditId == null) {
+                        creditTags = !this.isAssociationParametersExists ? getCreditOrDebitTags(id, JournalEntryType.CREDIT.getValue())
+                                : null;
+                        creditAccounts = this.isAssociationParametersExists ? this.glAccountReadPlatformService.retrieveAccountsByTagId(id,
+                                JournalEntryType.CREDIT.getValue()) : null;
+                    } else {
+                        creditTags = null;
+                        final GLAccountDataForLookup creditAccount = new GLAccountDataForLookup(accountToCreditId, creditAccountName,
+                                creditAccountGLCode);
+                        creditAccounts = new ArrayList<>(Arrays.asList(creditAccount));
+                    }
+                    if (accountToDebitId == null) {
+                        debitTags = !this.isAssociationParametersExists ? getCreditOrDebitTags(id, JournalEntryType.DEBIT.getValue())
+                                : null;
+                        debitAccounts = this.isAssociationParametersExists ? this.glAccountReadPlatformService.retrieveAccountsByTagId(id,
+                                JournalEntryType.DEBIT.getValue()) : null;
+                    } else {
+                        debitTags = null;
+                        final GLAccountDataForLookup debitAccount = new GLAccountDataForLookup(accountToDebitId, debitAccountName,
+                                debitAccountGLCode);
+                        debitAccounts = new ArrayList<>(Arrays.asList(debitAccount));
+                    }
+                    accountingRuleData = new AccountingRuleData(id, officeId, officeName, name, description, systemDefined,
+                            allowMultipleDebitEntries, allowMultipleCreditEntries, creditTags, debitTags, creditAccounts, debitAccounts);
+                }
+
+                extractedData.put(id, accountingRuleData);
+            }
+            return extractedData;
+        }
+
+        private List<AccountingTagRuleData> getCreditOrDebitTags(final Long creditOrDebitAccount, final Integer transactionType) {
+            final AccountingTagRuleDataMapper mapper = new AccountingTagRuleDataMapper();
+            final String taggedAccountsSchema = "Select " + mapper.taggedAccountSchema() + " where rule.id = ? and tag.acc_type_enum=?";
+            return this.jdbcTemplate.query(taggedAccountsSchema, mapper, new Object[] { creditOrDebitAccount, transactionType });
+        }
+
+    }
+
+    @Override
+    public List<AccountingRuleData> retrieveAllAccountingRules(final String hierarchySearchString,
+            final boolean isAssociationParametersExists) {
+        final AccountingRuleDataExtractor resultSetExtractor = new AccountingRuleDataExtractor(this.jdbcTemplate,
+                this.glAccountReadPlatformService, isAssociationParametersExists);
+        Object[] arguments = new Object[] {};
+        String sql = "select " + resultSetExtractor.schema() + " and system_defined=0 ";
+        if (hierarchySearchString != null) {
+            sql = sql + " and office.hierarchy like ?";
+            arguments = new Object[] { hierarchySearchString };
+        }
+        sql = sql + " order by rule.id asc";
+        final Map<Long, AccountingRuleData> extractedData = this.jdbcTemplate.query(sql, resultSetExtractor, arguments);
+        return new ArrayList<>(extractedData.values());
+    }
+
+    @Override
+    public AccountingRuleData retrieveAccountingRuleById(final Long accountingRuleId) {
+        try {
+            final AccountingRuleDataExtractor resultSetExtractor = new AccountingRuleDataExtractor(this.jdbcTemplate,
+                    this.glAccountReadPlatformService, false);
+            final String sql = "select " + resultSetExtractor.schema() + " and rule.id = ?";
+
+            final Map<Long, AccountingRuleData> extractedData = this.jdbcTemplate.query(sql, resultSetExtractor,
+                    new Object[] { accountingRuleId });
+            final AccountingRuleData accountingRuleData = extractedData.get(accountingRuleId);
+            if (accountingRuleData == null) { throw new AccountingRuleNotFoundException(accountingRuleId); }
+            return accountingRuleData;
+        } catch (final EmptyResultDataAccessException e) {
+            throw new AccountingRuleNotFoundException(accountingRuleId);
+        }
+    }
+
+    private static final class AccountingTagRuleDataMapper implements RowMapper<AccountingTagRuleData> {
+
+        public String taggedAccountSchema() {
+            return " tag.id as id,tag.tag_id as tagId, tag.acc_type_enum as transactionType, cv.code_value as tagName from m_code_value cv join acc_rule_tags tag on tag.tag_id=cv.id "
+                    + "join acc_accounting_rule rule on tag.acc_rule_id=rule.id ";
+        }
+
+        @Override
+        public AccountingTagRuleData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final Long tagId = rs.getLong("tagId");
+            final Integer transactionType = JdbcSupport.getInteger(rs, "transactionType");
+            final String tagName = rs.getString("tagName");
+            final CodeValueData tag = CodeValueData.instance(tagId, tagName);
+            final EnumOptionData transactionTypeEnum = AccountingEnumerations.journalEntryType(transactionType);
+            return new AccountingTagRuleData(id, tag, transactionTypeEnum);
+        }
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformService.java
new file mode 100755
index 0000000..c18ecc4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformService.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.accounting.rule.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface AccountingRuleWritePlatformService {
+
+    CommandProcessingResult createAccountingRule(JsonCommand command);
+
+    CommandProcessingResult updateAccountingRule(Long accountingRuleId, JsonCommand command);
+
+    CommandProcessingResult deleteAccountingRule(Long accountingRuleId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..e81b464
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,349 @@
+/**
+ * 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.rule.service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+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.closure.api.GLClosureJsonInputParams;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+import org.apache.fineract.accounting.rule.api.AccountingRuleJsonInputParams;
+import org.apache.fineract.accounting.rule.domain.AccountingRule;
+import org.apache.fineract.accounting.rule.domain.AccountingRuleRepository;
+import org.apache.fineract.accounting.rule.domain.AccountingRuleRepositoryWrapper;
+import org.apache.fineract.accounting.rule.domain.AccountingTagRule;
+import org.apache.fineract.accounting.rule.exception.AccountingRuleDataException;
+import org.apache.fineract.accounting.rule.exception.AccountingRuleDuplicateException;
+import org.apache.fineract.accounting.rule.serialization.AccountingRuleCommandFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
+import org.apache.fineract.infrastructure.codes.exception.CodeValueNotFoundException;
+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.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+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 AccountingRuleWritePlatformServiceJpaRepositoryImpl implements AccountingRuleWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(AccountingRuleWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final AccountingRuleRepositoryWrapper accountingRuleRepositoryWrapper;
+    private final AccountingRuleRepository accountingRuleRepository;
+    private final GLAccountRepositoryWrapper accountRepositoryWrapper;
+    private final OfficeRepository officeRepository;
+    private final AccountingRuleCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final CodeValueRepository codeValueRepository;
+
+    @Autowired
+    public AccountingRuleWritePlatformServiceJpaRepositoryImpl(final AccountingRuleRepositoryWrapper accountingRuleRepositoryWrapper,
+            final GLAccountRepositoryWrapper accountRepositoryWrapper, final OfficeRepository officeRepository,
+            final AccountingRuleRepository ruleRepository, final AccountingRuleCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final CodeValueRepository codeValueRepository) {
+        this.accountRepositoryWrapper = accountRepositoryWrapper;
+        this.officeRepository = officeRepository;
+        this.accountingRuleRepository = ruleRepository;
+        this.accountingRuleRepositoryWrapper = accountingRuleRepositoryWrapper;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.codeValueRepository = codeValueRepository;
+    }
+
+    /**
+     * @param command
+     * @param dve
+     */
+    private void handleAccountingRuleIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("accounting_rule_name_unique")) {
+            throw new AccountingRuleDuplicateException(command.stringValueOfParameterNamed(AccountingRuleJsonInputParams.NAME.getValue()));
+        } else if (realCause.getMessage().contains("UNIQUE_ACCOUNT_RULE_TAGS")) { throw new AccountingRuleDuplicateException(); }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.accounting.rule.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource Accounting Rule: " + realCause.getMessage());
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createAccountingRule(final JsonCommand command) {
+        try {
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            // check office is valid
+            final Long officeId = command.longValueOfParameterNamed(GLClosureJsonInputParams.OFFICE_ID.getValue());
+            Office office = null;
+            if (officeId != null) {
+                office = this.officeRepository.findOne(officeId);
+                if (office == null) { throw new OfficeNotFoundException(officeId); }
+            }
+
+            final AccountingRule accountingRule = assembleAccountingRuleAndTags(office, command);
+            this.accountingRuleRepository.saveAndFlush(accountingRule);
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
+                    .withEntityId(accountingRule.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleAccountingRuleIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private AccountingRule assembleAccountingRuleAndTags(final Office office, final JsonCommand command) {
+        // get the GL Accounts or tags to Debit and Credit
+        final String[] debitTags = command.arrayValueOfParameterNamed(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
+        final String[] creditTags = command.arrayValueOfParameterNamed(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
+        final Set<String> incomingDebitTags = debitTags == null ? new HashSet<String>() : new HashSet<>(Arrays.asList(debitTags));
+        final Set<String> incomingCreditTags = creditTags == null ? new HashSet<String>() : new HashSet<>(Arrays.asList(creditTags));
+        final Long accountToDebitId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue());
+        final Long accountToCreditId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue());
+
+        boolean allowMultipleCreditEntries = false;
+        boolean allowMultipleDebitEntries = false;
+        GLAccount debitAccount = null;
+        GLAccount creditAccount = null;
+        List<AccountingTagRule> accountingTagRules = new ArrayList<>();
+
+        if ((accountToDebitId != null && debitTags != null) || (accountToDebitId == null && debitTags == null)) {
+            throw new AccountingRuleDataException(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(),
+                    AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
+        } else if (accountToDebitId != null) {
+            debitAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountToDebitId);
+        } else if (debitTags != null) {
+            accountingTagRules = saveDebitOrCreditTags(incomingDebitTags, JournalEntryType.DEBIT, accountingTagRules);
+            allowMultipleDebitEntries = command
+                    .booleanPrimitiveValueOfParameterNamed(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue());
+        }
+
+        if ((accountToCreditId != null && creditTags != null) || (accountToCreditId == null && creditTags == null)) {
+            throw new AccountingRuleDataException(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
+                    AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
+        } else if (accountToCreditId != null) {
+            creditAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountToCreditId);
+        } else if (creditTags != null) {
+            accountingTagRules = saveDebitOrCreditTags(incomingCreditTags, JournalEntryType.CREDIT, accountingTagRules);
+            allowMultipleCreditEntries = command
+                    .booleanPrimitiveValueOfParameterNamed(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue());
+        }
+
+        final AccountingRule accountingRule = AccountingRule.fromJson(office, debitAccount, creditAccount, command,
+                allowMultipleCreditEntries, allowMultipleDebitEntries);
+        accountingRule.updateAccountingRuleForTags(accountingTagRules);
+
+        return accountingRule;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateAccountingRule(final Long accountingRuleId, final JsonCommand command) {
+
+        try {
+
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            Long officeId = null;
+            if (command.parameterExists(AccountingRuleJsonInputParams.OFFICE_ID.getValue())) {
+                officeId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.OFFICE_ID.getValue());
+            }
+
+            Long accountToDebitId = null;
+            if (command.parameterExists(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue())) {
+                accountToDebitId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue());
+            }
+
+            Long accountToCreditId = null;
+            if (command.parameterExists(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue())) {
+                accountToCreditId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue());
+            }
+
+            String[] debitTags = null;
+            if (command.parameterExists(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue())) {
+                debitTags = command.arrayValueOfParameterNamed(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
+            }
+
+            String[] creditTags = null;
+            if (command.parameterExists(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue())) {
+                creditTags = command.arrayValueOfParameterNamed(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
+            }
+
+            if (accountToDebitId != null && debitTags != null) { throw new AccountingRuleDataException(
+                    AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(), AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue()); }
+
+            if (accountToCreditId != null && creditTags != null) { throw new AccountingRuleDataException(
+                    AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
+                    AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue()); }
+
+            boolean allowMultipleCreditEntries = false;
+            if (command.parameterExists(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())) {
+                allowMultipleCreditEntries = command
+                        .booleanPrimitiveValueOfParameterNamed(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue());
+            }
+
+            boolean allowMultipleDebitEntries = false;
+            if (command.parameterExists(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())) {
+                allowMultipleDebitEntries = command
+                        .booleanPrimitiveValueOfParameterNamed(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue());
+            }
+
+            final AccountingRule accountingRule = this.accountingRuleRepositoryWrapper.findOneWithNotFoundDetection(accountingRuleId);
+            final Map<String, Object> changesOnly = accountingRule.update(command);
+
+            if (accountToDebitId != null && changesOnly.containsKey(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue())) {
+                final GLAccount accountToDebit = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountToDebitId);
+                accountingRule.updateDebitAccount(accountToDebit);
+                accountingRule.updateTags(JournalEntryType.CREDIT);
+            }
+
+            if (accountToCreditId != null && changesOnly.containsKey(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue())) {
+                final GLAccount accountToCredit = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountToCreditId);
+                accountingRule.updateCreditAccount(accountToCredit);
+                accountingRule.updateTags(JournalEntryType.DEBIT);
+            }
+
+            if (creditTags != null && creditTags.length > 0
+                    && command.parameterExists(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue())) {
+
+                final Set<String> creditTagsToAdd = determineCreditTagToAddAndRemoveOldTags(creditTags, JournalEntryType.CREDIT,
+                        accountingRule);
+
+                if (!creditTagsToAdd.isEmpty()) {
+                    List<AccountingTagRule> accountingTagRules = new ArrayList<>();
+                    accountingTagRules = saveDebitOrCreditTags(creditTagsToAdd, JournalEntryType.CREDIT, accountingTagRules);
+                    accountingRule.updateAccountingRuleForTags(accountingTagRules);
+                    accountingRule.updateCreditAccount(null);
+                    if (allowMultipleCreditEntries) {
+                        accountingRule.updateAllowMultipleCreditEntries(allowMultipleCreditEntries);
+                    }
+                    changesOnly.put(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue(), creditTagsToAdd);
+                }
+
+            }
+
+            if (debitTags != null && debitTags.length > 0
+                    && command.parameterExists(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue())) {
+                final Set<String> debitTagsToAdd = determineCreditTagToAddAndRemoveOldTags(debitTags, JournalEntryType.DEBIT,
+                        accountingRule);
+                if (!debitTagsToAdd.isEmpty()) {
+                    List<AccountingTagRule> accountingTagRules = new ArrayList<>();
+                    accountingTagRules = saveDebitOrCreditTags(debitTagsToAdd, JournalEntryType.DEBIT, accountingTagRules);
+                    accountingRule.updateAccountingRuleForTags(accountingTagRules);
+                    accountingRule.updateDebitAccount(null);
+                    if (allowMultipleDebitEntries) {
+                        accountingRule.updateAllowMultipleDebitEntries(allowMultipleDebitEntries);
+                    }
+                    changesOnly.put(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue(), debitTagsToAdd);
+                }
+            }
+
+            if (officeId != null && changesOnly.containsKey(AccountingRuleJsonInputParams.OFFICE_ID.getValue())) {
+                final Office userOffice = this.officeRepository.findOne(officeId);
+                if (userOffice == null) { throw new OfficeNotFoundException(officeId); }
+                accountingRule.setOffice(userOffice);
+            }
+
+            if (!changesOnly.isEmpty()) {
+                this.accountingRuleRepository.saveAndFlush(accountingRule);
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(accountingRule.getId())
+                    .with(changesOnly).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleAccountingRuleIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+
+    private Set<String> determineCreditTagToAddAndRemoveOldTags(final String[] creditOrDebitTags, final JournalEntryType type,
+            final AccountingRule accountingRule) {
+
+        final Set<String> incomingTags = new HashSet<>(Arrays.asList(creditOrDebitTags));
+        final Set<AccountingTagRule> existingTags = accountingRule.getAccountingTagRulesByType(type);
+        final Set<String> existingTagIds = retrieveExistingTagIds(existingTags);
+        final Set<String> tagsToAdd = new HashSet<>();
+        final Set<String> tagsToRemove = existingTagIds;
+        final Map<Long, AccountingTagRule> accountsToRemove = new HashMap<>();
+
+        for (final String tagId : incomingTags) {
+            if (existingTagIds.contains(tagId)) {
+                tagsToRemove.remove(tagId);
+            } else {
+                tagsToAdd.add(tagId);
+            }
+        }
+
+        if (!tagsToRemove.isEmpty()) {
+            for (final String tagId : tagsToRemove) {
+                for (final AccountingTagRule accountingTagRule : existingTags) {
+                    if (tagId.equals(accountingTagRule.getTagId().toString())) {
+                        accountsToRemove.put(accountingTagRule.getId(), accountingTagRule);
+                    }
+                }
+            }
+            accountingRule.removeOldTags(new ArrayList<>(accountsToRemove.values()));
+        }
+        return tagsToAdd;
+    }
+
+    private Set<String> retrieveExistingTagIds(final Set<AccountingTagRule> existingCreditTags) {
+        final Set<String> existingCreditTagIds = new HashSet<>();
+        for (final AccountingTagRule accountingTagRule : existingCreditTags) {
+            existingCreditTagIds.add(accountingTagRule.getTagId().toString());
+        }
+        return existingCreditTagIds;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteAccountingRule(final Long accountingRuleId) {
+        final AccountingRule accountingRule = this.accountingRuleRepositoryWrapper.findOneWithNotFoundDetection(accountingRuleId);
+        this.accountingRuleRepository.delete(accountingRule);
+        return new CommandProcessingResultBuilder().withEntityId(accountingRule.getId()).build();
+    }
+
+    private List<AccountingTagRule> saveDebitOrCreditTags(final Set<String> creditOrDebitTagArray, final JournalEntryType transactionType,
+            final List<AccountingTagRule> accountingTagRules) {
+        for (final String creditOrDebitTag : creditOrDebitTagArray) {
+            if (creditOrDebitTag != null && StringUtils.isNotBlank(creditOrDebitTag)) {
+                final Long creditOrDebitTagIdLongValue = Long.valueOf(creditOrDebitTag);
+                final CodeValue creditOrDebitAccount = this.codeValueRepository.findOne(creditOrDebitTagIdLongValue);
+                if (creditOrDebitAccount == null) { throw new CodeValueNotFoundException(creditOrDebitTagIdLongValue); }
+                final AccountingTagRule accountingTagRule = AccountingTagRule.create(creditOrDebitAccount, transactionType.getValue());
+                accountingTagRules.add(accountingTagRule);
+            }
+        }
+        return accountingTagRules;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java
new file mode 100644
index 0000000..ca30f71
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java
@@ -0,0 +1,123 @@
+/**
+ * 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.batch.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.serialization.BatchRequestJsonHelper;
+import org.apache.fineract.batch.service.BatchApiService;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+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;
+
+/**
+ * Provides a REST resource for Batch Requests. This class acts as a proxy to
+ * {@link org.apache.fineract.batch.service.BatchApiService} and de-serializes the
+ * incoming JSON string to a list of
+ * {@link org.apache.fineract.batch.domain .BatchRequest} type. This list is
+ * forwarded to BatchApiService which finally returns a list of
+ * {@link org.apache.fineract.batch.domain.BatchResponse} type which is then
+ * serialized into JSON response by this Resource class.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.service.BatchApiService
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Path("/batches")
+@Component
+@Scope("singleton")
+public class BatchApiResource {
+
+    private final PlatformSecurityContext context;
+    private final ToApiJsonSerializer<BatchResponse> toApiJsonSerializer;
+    private final BatchApiService service;
+    private final BatchRequestJsonHelper batchRequestJsonHelper;
+
+    /**
+     * Constructs a 'BatchApiService' with context, toApiJsonSerializer, service
+     * and batchRequestJsonHelper.
+     * 
+     * @param context
+     * @param toApiJsonSerializer
+     * @param service
+     * @param batchRequestJsonHelper
+     */
+    @Autowired
+    public BatchApiResource(final PlatformSecurityContext context, final ToApiJsonSerializer<BatchResponse> toApiJsonSerializer,
+            final BatchApiService service, final BatchRequestJsonHelper batchRequestJsonHelper) {
+
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.service = service;
+        this.batchRequestJsonHelper = batchRequestJsonHelper;
+    }
+
+    /**
+     * Rest assured POST method to get {@link BatchRequest} and returns back the
+     * consolidated {@link BatchResponse}
+     * 
+     * @param jsonRequestString
+     * @param enclosingTransaction
+     * @param uriInfo
+     * @return serialized JSON
+     */
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String handleBatchRequests(@DefaultValue("false") @QueryParam("enclosingTransaction") final boolean enclosingTransaction,
+            final String jsonRequestString, @Context UriInfo uriInfo) {
+
+        // Handles user authentication
+        this.context.authenticatedUser();
+
+        // Converts request array into BatchRequest List
+        final List<BatchRequest> requestList = this.batchRequestJsonHelper.extractList(jsonRequestString);
+
+        // Gets back the consolidated BatchResponse from BatchApiservice
+        List<BatchResponse> result = new ArrayList<>();
+
+        // If the request is to be handled as a Transaction. All requests will
+        // be rolled back on error
+        if (enclosingTransaction) {
+            result = service.handleBatchRequestsWithEnclosingTransaction(requestList, uriInfo);
+        } else {
+            result = service.handleBatchRequestsWithoutEnclosingTransaction(requestList, uriInfo);
+        }
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandContext.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandContext.java
new file mode 100644
index 0000000..298f2a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandContext.java
@@ -0,0 +1,108 @@
+/**
+ * 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.batch.command;
+
+/**
+ * Provides an object to {@link org.apache.fineract.batch.service.BatchApiService}
+ * to get the proper commandStrategy for each request in BatchRequest. It uses
+ * Builder pattern to create object of this type.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.service.BatchApiService
+ */
+public class CommandContext {
+
+    /**
+     * Static Builder class to provide a Build method for CommandContext.
+     * 
+     * @author Rishabh Shukla
+     */
+    public static class Builder {
+
+        private String resource;
+        private String method;
+
+        private Builder(final String resource) {
+            this.resource = resource;
+        }
+
+        public Builder method(final String method) {
+            this.method = method;
+            return this;
+        }
+
+        public CommandContext build() {
+            return new CommandContext(this.resource, this.method);
+        }
+
+    }
+
+    private final String resource;
+    private final String method;
+
+    private CommandContext(final String resource, final String method) {
+
+        this.resource = resource;
+        this.method = method;
+    }
+
+    public static Builder resource(final String resource) {
+        return new Builder(resource);
+    }
+
+    /**
+     * Returns a boolean value if the relativeUrl 'matches' one of the regex
+     * keys in the available commandStrategies. It take CommandContext object as
+     * parameter which contains a 'resource' member as a regex key for available
+     * commandStrategies.
+     * 
+     * @param other
+     * @return boolean
+     */
+    public boolean matcher(CommandContext other) {
+        if (this.resource.matches(other.resource) && this.method.equals(other.method)) { return true; }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((this.method == null) ? 0 : this.method.hashCode());
+        result = prime * result + ((this.resource == null) ? 0 : this.resource.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        CommandContext other = (CommandContext) obj;
+        if (this.method == null) {
+            if (other.method != null) return false;
+        } else if (!this.method.equals(other.method)) return false;
+        if (this.resource == null) {
+            if (other.resource != null) return false;
+        } else if (!this.resource.equals(other.resource)) return false;
+        return true;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategy.java
new file mode 100644
index 0000000..2b77408
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategy.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.batch.command;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+
+/**
+ * An interface for various Command Strategies. It contains a single function
+ * which returns appropriate response from a particular command strategy.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.internal.UnknownCommandStrategy
+ */
+public interface CommandStrategy {
+
+    /**
+     * Returns an object of type
+     * {@link org.apache.fineract.batch.domain.BatchResponse}. This takes
+     * {@link org.apache.fineract.batch.domain.BatchRequest} as it's single
+     * argument and provides appropriate response.
+     * 
+     * @param batchRequest
+     * @param uriInfo
+     * @return BatchResponse
+     */
+    public BatchResponse execute(BatchRequest batchRequest, UriInfo uriInfo);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java
new file mode 100644
index 0000000..454c32d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java
@@ -0,0 +1,103 @@
+/**
+ * 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.batch.command;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.fineract.batch.command.internal.UnknownCommandStrategy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+/**
+ * Provides an appropriate CommandStrategy using the 'method' and 'resourceUrl'.
+ * CommandStrategy bean is created using Spring Application Context.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.internal.UnknownCommandStrategy
+ */
+@Component
+public class CommandStrategyProvider {
+
+    private final ApplicationContext applicationContext;
+    private final ConcurrentHashMap<CommandContext, String> commandStrategies = new ConcurrentHashMap<>();
+
+    /**
+     * Constructs a CommandStrategyProvider with argument of ApplicationContext
+     * type. It also initialize commandStrategies using init() function by
+     * filling it with available CommandStrategies in
+     * {@link org.apache.fineract.batch.command.internal}.
+     * 
+     * @param applicationContext
+     */
+    @Autowired
+    public CommandStrategyProvider(final ApplicationContext applicationContext) {
+
+        // calls init() function of this class.
+        init();
+
+        this.applicationContext = applicationContext;
+    }
+
+    /**
+     * Returns an appropriate commandStrategy after determining it using the
+     * CommandContext of the request. If no such Strategy is found then a
+     * default strategy is returned back.
+     * 
+     * @param commandContext
+     * @return CommandStrategy
+     * @see org.apache.fineract.batch.command.internal.UnknownCommandStrategy
+     */
+    public CommandStrategy getCommandStrategy(final CommandContext commandContext) {
+
+        if (this.commandStrategies.containsKey(commandContext)) { return (CommandStrategy) this.applicationContext
+                .getBean(this.commandStrategies.get(commandContext)); }
+
+        for (ConcurrentHashMap.Entry<CommandContext, String> entry : this.commandStrategies.entrySet()) {
+            if (commandContext.matcher(entry.getKey())) { return (CommandStrategy) this.applicationContext.getBean(this.commandStrategies
+                    .get(entry.getKey())); }
+        }
+
+        return new UnknownCommandStrategy();
+    }
+
+    /**
+     * Contains various available command strategies in
+     * {@link org.apache.fineract.batch.command.internal}. Any new command
+     * Strategy will have to be added within this function in order to initiate
+     * it within the constructor.
+     */
+    private void init() {
+        this.commandStrategies.put(CommandContext.resource("clients").method("POST").build(), "createClientCommandStrategy");
+        this.commandStrategies.put(CommandContext.resource("clients\\/\\d+").method("PUT").build(), "updateClientCommandStrategy");
+        this.commandStrategies.put(CommandContext.resource("loans").method("POST").build(), "applyLoanCommandStrategy");
+        this.commandStrategies.put(CommandContext.resource("savingsaccounts").method("POST").build(), "applySavingsCommandStrategy");
+        this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges").method("POST").build(), "createChargeCommandStrategy");
+        this.commandStrategies
+                .put(CommandContext.resource("loans\\/\\d+\\/charges").method("GET").build(), "collectChargesCommandStrategy");
+        this.commandStrategies.put(CommandContext.resource("clients\\/\\d+\\?command=activate").method("POST").build(),
+                "activateClientCommandStrategy");
+        this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\?command=approve").method("POST").build(),
+                "approveLoanCommandStrategy");
+        this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\?command=disburse").method("POST").build(),
+                "disburseLoanCommandStrategy");
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ActivateClientCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ActivateClientCommandStrategy.java
new file mode 100644
index 0000000..c64b73f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ActivateClientCommandStrategy.java
@@ -0,0 +1,93 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.portfolio.client.api.ClientsApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.apache.fineract.batch.command.CommandStrategy} to handle
+ * activation of a pending client. It passes the contents of the body from the
+ * BatchRequest to
+ * {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and gets
+ * back the response. This class will also catch any errors raised by
+ * {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and map
+ * those errors to appropriate status codes in BatchResponse.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Component
+public class ActivateClientCommandStrategy implements CommandStrategy {
+
+    private final ClientsApiResource clientsApiResource;
+
+    @Autowired
+    public ActivateClientCommandStrategy(final ClientsApiResource clientsApiResource) {
+        this.clientsApiResource = clientsApiResource;
+    }
+
+    @Override
+    public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+        
+        final String[] pathParameters = request.getRelativeUrl().split("/");
+        Long clientId = Long.parseLong(pathParameters[1].substring(0, pathParameters[1].indexOf("?")));
+
+        // Try-catch blocks to map exceptions to appropriate status codes
+        try {
+
+            // Calls 'activate' function from 'ClientsApiResource' to activate a client
+            responseBody = clientsApiResource.activate(clientId, "activate", request.getBody());
+
+            response.setStatusCode(200);
+            // Sets the body of the response after the successful activation of
+            // the client
+            response.setBody(responseBody);
+
+        } catch (RuntimeException e) {
+
+            // Gets an object of type ErrorInfo, containing information about
+            // raised exception
+            ErrorInfo ex = ErrorHandler.handler(e);
+
+            response.setStatusCode(ex.getStatusCode());
+            response.setBody(ex.getMessage());
+        }
+
+        return response;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplyLoanCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplyLoanCommandStrategy.java
new file mode 100644
index 0000000..8183012
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplyLoanCommandStrategy.java
@@ -0,0 +1,89 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.portfolio.loanaccount.api.LoansApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.apache.fineract.batch.command.CommandStrategy} and
+ * applies a new loan on an existing client. It passes the contents of the body
+ * from the BatchRequest to
+ * {@link org.apache.fineract.portfolio.client.api.LoansApiResource} and gets back
+ * the response. This class will also catch any errors raised by
+ * {@link org.apache.fineract.portfolio.client.api.LoansApiResource} and map those
+ * errors to appropriate status codes in BatchResponse.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Component
+public class ApplyLoanCommandStrategy implements CommandStrategy {
+
+    private final LoansApiResource loansApiResource;
+
+    @Autowired
+    public ApplyLoanCommandStrategy(final LoansApiResource loansApiResource) {
+        this.loansApiResource = loansApiResource;
+    }
+
+    @Override
+    public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+
+        // Try-catch blocks to map exceptions to appropriate status codes
+        try {
+
+            // Calls 'SubmitLoanFunction' function from 'LoansApiResource' to
+            // Apply Loan to an existing client
+            responseBody = loansApiResource.calculateLoanScheduleOrSubmitLoanApplication(null, null, request.getBody());
+
+            response.setStatusCode(200);
+            // Sets the body of the response after loan is successfully applied
+            response.setBody(responseBody);
+
+        } catch (RuntimeException e) {
+
+            // Gets an object of type ErrorInfo, containing information about
+            // raised exception
+            ErrorInfo ex = ErrorHandler.handler(e);
+
+            response.setStatusCode(ex.getStatusCode());
+            response.setBody(ex.getMessage());
+        }
+
+        return response;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplySavingsCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplySavingsCommandStrategy.java
new file mode 100644
index 0000000..8c97a56
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplySavingsCommandStrategy.java
@@ -0,0 +1,91 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.apache.fineract.batch.command.CommandStrategy} and
+ * applies a new savings on an existing client. It passes the contents of the
+ * body from the BatchRequest to
+ * {@link org.apache.fineract.portfolio.client.api.SavingsAccountsApiResource} and
+ * gets back the response. This class will also catch any errors raised by
+ * {@link org.apache.fineract.portfolio.client.api.SavingsAccountsApiResource} and
+ * map those errors to appropriate status codes in BatchResponse.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Component
+public class ApplySavingsCommandStrategy implements CommandStrategy {
+
+    private final SavingsAccountsApiResource savingsAccountsApiResource;
+
+    @Autowired
+    public ApplySavingsCommandStrategy(final SavingsAccountsApiResource savingsAccountsApiResource) {
+        this.savingsAccountsApiResource = savingsAccountsApiResource;
+    }
+
+    @Override
+    public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+
+        // Try-catch blocks to map exceptions to appropriate status codes
+        try {
+
+            // Calls 'submitApplication' function from
+            // 'SavingsAccountsApiResource' to Apply Savings to an existing
+            // client
+            responseBody = savingsAccountsApiResource.submitApplication(request.getBody());
+
+            response.setStatusCode(200);
+            // Sets the body of the response after savings is successfully
+            // applied
+            response.setBody(responseBody);
+
+        } catch (RuntimeException e) {
+
+            // Gets an object of type ErrorInfo, containing information about
+            // raised exception
+            ErrorInfo ex = ErrorHandler.handler(e);
+
+            response.setStatusCode(ex.getStatusCode());
+            response.setBody(ex.getMessage());
+        }
+
+        return response;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApproveLoanCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApproveLoanCommandStrategy.java
new file mode 100644
index 0000000..dbe3629
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApproveLoanCommandStrategy.java
@@ -0,0 +1,92 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.portfolio.loanaccount.api.LoansApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.apache.fineract.batch.command.CommandStrategy} to handle
+ * approval of a pending loan. It passes the contents of the body from the
+ * BatchRequest to
+ * {@link org.apache.fineract.portfolio.client.api.LoansApiResource} and gets
+ * back the response. This class will also catch any errors raised by
+ * {@link org.apache.fineract.portfolio.client.api.LoansApiResource} and map
+ * those errors to appropriate status codes in BatchResponse.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Component
+public class ApproveLoanCommandStrategy implements CommandStrategy {
+
+    private final LoansApiResource loansApiResource;
+
+    @Autowired
+    public ApproveLoanCommandStrategy(final LoansApiResource loansApiResource) {
+        this.loansApiResource = loansApiResource;
+    }
+
+    @Override
+    public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+        
+        final String[] pathParameters = request.getRelativeUrl().split("/");
+        Long loanId = Long.parseLong(pathParameters[1].substring(0, pathParameters[1].indexOf("?")));
+
+        // Try-catch blocks to map exceptions to appropriate status codes
+        try {
+
+            // Calls 'approve' function from 'LoansApiResource' to approve a loan          
+            responseBody = loansApiResource.stateTransitions(loanId, "approve", request.getBody());
+
+            response.setStatusCode(200);
+            // Sets the body of the response after the successful approval of a loan
+            response.setBody(responseBody);
+
+        } catch (RuntimeException e) {
+
+            // Gets an object of type ErrorInfo, containing information about
+            // raised exception
+            ErrorInfo ex = ErrorHandler.handler(e);
+
+            response.setStatusCode(ex.getStatusCode());
+            response.setBody(ex.getMessage());
+        }
+
+        return response;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CollectChargesCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CollectChargesCommandStrategy.java
new file mode 100644
index 0000000..5ec887e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CollectChargesCommandStrategy.java
@@ -0,0 +1,96 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.apache.fineract.batch.command.CommandStrategy} and
+ * Collect Charges for a Loan. It passes the contents of the body from the
+ * BatchRequest to
+ * {@link org.apache.fineract.portfolio.client.api.LoanChargesApiResource} and
+ * gets back the response. This class will also catch any errors raised by
+ * {@link org.apache.fineract.portfolio.client.api.LoanChargesApiResource} and map
+ * those errors to appropriate status codes in BatchResponse.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Component
+public class CollectChargesCommandStrategy implements CommandStrategy {
+
+    private final LoanChargesApiResource loanChargesApiResource;
+
+    @Autowired
+    public CollectChargesCommandStrategy(final LoanChargesApiResource loanChargesApiResource) {
+        this.loanChargesApiResource = loanChargesApiResource;
+    }
+
+    @Override
+    public BatchResponse execute(BatchRequest request, UriInfo uriInfo) {
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+
+        final String[] pathParameters = request.getRelativeUrl().split("/");
+
+        // Pluck out the loanId out of the relative path
+        Long loanId = Long.parseLong(pathParameters[1]);
+
+        // Try-catch blocks to map exceptions to appropriate status codes
+        try {
+
+            // Calls 'retrieveAllLoanCharges' function from
+            // 'LoanChargesApiResource' to Collect
+            // Charges for a loan
+            responseBody = loanChargesApiResource.retrieveAllLoanCharges(loanId, uriInfo);
+
+            response.setStatusCode(200);
+            // Sets the body of the response after Charges have been
+            // successfully collected
+            response.setBody(responseBody);
+
+        } catch (RuntimeException e) {
+
+            // Gets an object of type ErrorInfo, containing information about
+            // raised exception
+            ErrorInfo ex = ErrorHandler.handler(e);
+
+            response.setStatusCode(ex.getStatusCode());
+            response.setBody(ex.getMessage());
+        }
+
+        return response;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateChargeCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateChargeCommandStrategy.java
new file mode 100644
index 0000000..90678a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateChargeCommandStrategy.java
@@ -0,0 +1,93 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.apache.fineract.batch.command.CommandStrategy} and Create
+ * Charge for a Loan. It passes the contents of the body from the BatchRequest
+ * to {@link org.apache.fineract.portfolio.client.api.LoanChargesApiResource} and
+ * gets back the response. This class will also catch any errors raised by
+ * {@link org.apache.fineract.portfolio.client.api.LoanChargesApiResource} and map
+ * those errors to appropriate status codes in BatchResponse.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Component
+public class CreateChargeCommandStrategy implements CommandStrategy {
+
+    private final LoanChargesApiResource loanChargesApiResource;
+
+    @Autowired
+    public CreateChargeCommandStrategy(final LoanChargesApiResource loanChargesApiResource) {
+        this.loanChargesApiResource = loanChargesApiResource;
+    }
+
+    @Override
+    public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+
+        final String[] pathParameters = request.getRelativeUrl().split("/");
+        Long loanId = Long.parseLong(pathParameters[1]);
+
+        // Try-catch blocks to map exceptions to appropriate status codes
+        try {
+
+            // Calls 'executeLoanCharge' function from 'LoanChargesApiResource'
+            // to create
+            // a new charge for a loan
+            responseBody = loanChargesApiResource.executeLoanCharge(loanId, null, request.getBody());
+
+            response.setStatusCode(200);
+            // Sets the body of the response after Charge has been successfully
+            // created
+            response.setBody(responseBody);
+
+        } catch (RuntimeException e) {
+
+            // Gets an object of type ErrorInfo, containing information about
+            // raised exception
+            ErrorInfo ex = ErrorHandler.handler(e);
+
+            response.setStatusCode(ex.getStatusCode());
+            response.setBody(ex.getMessage());
+        }
+
+        return response;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateClientCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateClientCommandStrategy.java
new file mode 100644
index 0000000..cb4993e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateClientCommandStrategy.java
@@ -0,0 +1,91 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.portfolio.client.api.ClientsApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.apache.fineract.batch.command.CommandStrategy} to handle
+ * creation of a new client. It passes the contents of the body from the
+ * BatchRequest to
+ * {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and gets
+ * back the response. This class will also catch any errors raised by
+ * {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and map
+ * those errors to appropriate status codes in BatchResponse.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Component
+public class CreateClientCommandStrategy implements CommandStrategy {
+
+    private final ClientsApiResource clientsApiResource;
+
+    @Autowired
+    public CreateClientCommandStrategy(final ClientsApiResource clientsApiResource) {
+        this.clientsApiResource = clientsApiResource;
+    }
+
+    @Override
+    public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+
+        // Try-catch blocks to map exceptions to appropriate status codes
+        try {
+
+            // Calls 'create' function from 'ClientsApiResource' to create a new
+            // client
+            responseBody = clientsApiResource.create(request.getBody());
+
+            response.setStatusCode(200);
+            // Sets the body of the response after the successful creation of
+            // the client
+            response.setBody(responseBody);
+
+        } catch (RuntimeException e) {
+
+            // Gets an object of type ErrorInfo, containing information about
+            // raised exception
+            ErrorInfo ex = ErrorHandler.handler(e);
+
+            response.setStatusCode(ex.getStatusCode());
+            response.setBody(ex.getMessage());
+        }
+
+        return response;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/DisburseLoanCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/DisburseLoanCommandStrategy.java
new file mode 100644
index 0000000..89ad9ef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/DisburseLoanCommandStrategy.java
@@ -0,0 +1,93 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.portfolio.loanaccount.api.LoansApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.apache.fineract.batch.command.CommandStrategy} to handle
+ * disburse of a loan. It passes the contents of the body from the
+ * BatchRequest to
+ * {@link org.apache.fineract.portfolio.client.api.LoansApiResource} and gets
+ * back the response. This class will also catch any errors raised by
+ * {@link org.apache.fineract.portfolio.client.api.LoansApiResource} and map
+ * those errors to appropriate status codes in BatchResponse.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Component
+public class DisburseLoanCommandStrategy implements CommandStrategy {
+
+    private final LoansApiResource loansApiResource;
+
+    @Autowired
+    public DisburseLoanCommandStrategy(final LoansApiResource loansApiResource) {
+        this.loansApiResource = loansApiResource;
+    }
+
+    @Override
+    public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+        
+        final String[] pathParameters = request.getRelativeUrl().split("/");
+        Long loanId = Long.parseLong(pathParameters[1].substring(0, pathParameters[1].indexOf("?")));
+
+        // Try-catch blocks to map exceptions to appropriate status codes
+        try {
+
+            // Calls 'disburse' function from 'LoansApiResource' to disburse a loan          
+            responseBody = loansApiResource.stateTransitions(loanId, "disburse", request.getBody());
+
+            response.setStatusCode(200);
+            
+            // Sets the body of the response after the successful disbursal of the loan
+            response.setBody(responseBody);
+
+        } catch (RuntimeException e) {
+
+            // Gets an object of type ErrorInfo, containing information about
+            // raised exception
+            ErrorInfo ex = ErrorHandler.handler(e);
+
+            response.setStatusCode(ex.getStatusCode());
+            response.setBody(ex.getMessage());
+        }
+
+        return response;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UnknownCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UnknownCommandStrategy.java
new file mode 100644
index 0000000..5edd010
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UnknownCommandStrategy.java
@@ -0,0 +1,51 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.springframework.stereotype.Component;
+
+/**
+ * Provides a default CommandStrategy by implementing
+ * {@link org.apache.fineract.batch.command.CommandStrategy} in case there is no
+ * appropriate command strategy with requested 'method' and 'resoureUrl'.
+ * 
+ * @author Rishabh Shukla
+ */
+@Component
+public class UnknownCommandStrategy implements CommandStrategy {
+
+    @Override
+    public BatchResponse execute(BatchRequest batchRequest, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+        final BatchResponse batchResponse = new BatchResponse();
+
+        batchResponse.setRequestId(batchRequest.getRequestId());
+        batchResponse.setStatusCode(501);
+        batchResponse.setBody("Resource with method " + batchRequest.getMethod() + " and relativeUrl " + batchRequest.getRelativeUrl()
+                + " doesn't exist");
+
+        return batchResponse;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UpdateClientCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UpdateClientCommandStrategy.java
new file mode 100644
index 0000000..82cdc53
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UpdateClientCommandStrategy.java
@@ -0,0 +1,95 @@
+/**
+ * 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.batch.command.internal;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.portfolio.client.api.ClientsApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link org.apache.fineract.batch.command.CommandStrategy} and
+ * updates the information of an existing client. It passes the contents of the
+ * body from the BatchRequest to
+ * {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and gets
+ * back the response. This class will also catch any errors raised by
+ * {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and map
+ * those errors to appropriate status codes in BatchResponse.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ */
+@Component
+public class UpdateClientCommandStrategy implements CommandStrategy {
+
+    private final ClientsApiResource clientsApiResource;
+
+    @Autowired
+    public UpdateClientCommandStrategy(final ClientsApiResource clientsApiResource) {
+        this.clientsApiResource = clientsApiResource;
+    }
+
+    @Override
+    public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) {
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+
+        // Get the clientID
+        final String relativeUrl = request.getRelativeUrl();
+        final Long clientId = Long.parseLong(relativeUrl.substring(relativeUrl.indexOf('/') + 1));
+
+        // Try-catch blocks to map exceptions to appropriate status codes
+        try {
+
+            // Calls 'update' function from 'ClientsApiResource' to update a
+            // client
+            responseBody = clientsApiResource.update(clientId, request.getBody());
+
+            response.setStatusCode(200);
+            // Sets the body of the response after the successful update of
+            // client information
+            response.setBody(responseBody);
+
+        } catch (RuntimeException e) {
+
+            // Gets an object of type ErrorInfo, containing information about
+            // raised exception
+            ErrorInfo ex = ErrorHandler.handler(e);
+
+            response.setStatusCode(ex.getStatusCode());
+            response.setBody(ex.getMessage());
+        }
+
+        return response;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/domain/BatchRequest.java b/fineract-provider/src/main/java/org/apache/fineract/batch/domain/BatchRequest.java
new file mode 100644
index 0000000..9a7fc5d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/domain/BatchRequest.java
@@ -0,0 +1,190 @@
+/**
+ * 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.batch.domain;
+
+import java.util.Set;
+
+/**
+ * Provides an object for separate HTTP requests in the Batch Request for Batch
+ * API. A requestId is also included as data field which takes care of
+ * dependency issues among various requests. This class also provides getter and
+ * setter functions to access Batch Request data fields.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.api.BatchApiResource
+ * @see Header
+ */
+public class BatchRequest {
+
+    private Long requestId;
+    private String relativeUrl;
+    private String method;
+    private Set<Header> headers;
+    private Long reference;
+    private String body;
+
+    /**
+     * Constructs a 'BatchRequest' with requestId, relativeUrl, method, headers,
+     * reference and body of the incoming request.
+     * 
+     * @param requestId
+     *            of HTTP request.
+     * @param relativeUrl
+     *            of HTTP request.
+     * @param method
+     *            of HTTP request.
+     * @param headers
+     *            of HTTP request.
+     * @param reference
+     *            of HTTP request.
+     * @param body
+     *            of HTTP request.
+     * 
+     * @see Header
+     */
+    public BatchRequest(Long requestId, String relativeUrl, String method, Set<Header> headers, Long reference, String body) {
+
+        this.requestId = requestId;
+        this.relativeUrl = relativeUrl;
+        this.method = method;
+        this.headers = headers;
+        this.reference = reference;
+        this.body = body;
+    }
+
+    /**
+     * Constructs a default constructor of 'BatchRequest'
+     */
+    public BatchRequest() {
+
+    }
+
+    /**
+     * Returns the value of 'requestId' of an object of this class.
+     * 
+     * @return requestId of the HTTP request.
+     */
+    public Long getRequestId() {
+        return this.requestId;
+    }
+
+    /**
+     * Sets the value of 'requestId' of an object of this class.
+     * 
+     * @param requestId
+     */
+    public void setRequestId(Long requestId) {
+        this.requestId = requestId;
+    }
+
+    /**
+     * Returns the value of 'relativeUrl' of an object of this class.
+     * 
+     * @return relativeUrl of the HTTP request.
+     */
+    public String getRelativeUrl() {
+        return this.relativeUrl;
+    }
+
+    /**
+     * Sets the value of 'relativeUrl' of an object of this class.
+     * 
+     * @param relativeUrl
+     */
+    public void setRelativeUrl(String relativeUrl) {
+        this.relativeUrl = relativeUrl;
+    }
+
+    /**
+     * Returns the value of 'method' of an object of this class.
+     * 
+     * @return method of the HTTP request.
+     */
+    public String getMethod() {
+        return this.method;
+    }
+
+    /**
+     * Sets the value of 'method' of the object of this class.
+     * 
+     * @param method
+     */
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    /**
+     * Returns the values of 'headers' of {@link Header} type of an object of
+     * this class.
+     * 
+     * @return headers of the HTTP request.
+     * @see Header
+     */
+    public Set<Header> getHeaders() {
+        return this.headers;
+    }
+
+    /**
+     * Sets the values of 'headers' of {@link Header} type of an object of this
+     * class.
+     * 
+     * @param headers
+     * @see Header
+     */
+    public void setHeaders(Set<Header> headers) {
+        this.headers = headers;
+    }
+
+    /**
+     * Returns the value of 'reference' of an object of this class
+     * 
+     * @return reference of the HTTP request
+     */
+    public Long getReference() {
+        return this.reference;
+    }
+
+    /**
+     * Sets the value of 'reference' of an object of this class.
+     * 
+     * @param reference
+     */
+    public void setReference(Long reference) {
+        this.reference = reference;
+    }
+
+    /**
+     * Returns the value of 'body' of an object of this class.
+     * 
+     * @return body of the HTTP request.
+     */
+    public String getBody() {
+        return this.body;
+    }
+
+    /**
+     * Sets the value of 'body' of an object of this class.
+     * 
+     * @param body
+     */
+    public void setBody(String body) {
+        this.body = body;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/domain/BatchResponse.java b/fineract-provider/src/main/java/org/apache/fineract/batch/domain/BatchResponse.java
new file mode 100644
index 0000000..439e99b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/domain/BatchResponse.java
@@ -0,0 +1,141 @@
+/**
+ * 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.batch.domain;
+
+import java.util.Set;
+
+/**
+ * Provides an object for separate HTTP responses in the Batch Response for
+ * Batch API. It contains all the information about a particular HTTP response
+ * in the Batch Response. Getter and Setter functions are also included to
+ * access response data fields.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.api.BatchApiResource
+ * @see org.apache.fineract.batch.service.BatchApiService
+ * @see Header
+ */
+public class BatchResponse {
+
+    private Long requestId;
+    private Integer statusCode;
+    private Set<Header> headers;
+    private String body;
+
+    /**
+     * Constructs a 'BatchResponse' with requestId, statusCode, headers and body
+     * of the HTTP requests.
+     * 
+     * @param requestId
+     * @param statusCode
+     * @param headers
+     * @param body
+     * @see Header
+     */
+    public BatchResponse(Long requestId, Integer statusCode, Set<Header> headers, String body) {
+        this.requestId = requestId;
+        this.statusCode = statusCode;
+        this.headers = headers;
+        this.body = body;
+    }
+
+    /**
+     * Constructs a default constructor of 'BatchResponse'
+     */
+    public BatchResponse() {
+
+    }
+
+    /**
+     * Returns the 'requestId' of an object of this class.
+     * 
+     * @return requestId of the HTTP request.
+     */
+    public Long getRequestId() {
+        return this.requestId;
+    }
+
+    /**
+     * Sets the value of 'requestId' of an object of this class.
+     * 
+     * @param requestId
+     */
+    public void setRequestId(Long requestId) {
+        this.requestId = requestId;
+    }
+
+    /**
+     * Returns the 'statusCode' of an object of this class.
+     * 
+     * @return statusCode of the HTTP request.
+     */
+    public Integer getStatusCode() {
+        return this.statusCode;
+    }
+
+    /**
+     * Sets the value of 'statusCode' of an object of this class.
+     * 
+     * @param statusCode
+     */
+    public void setStatusCode(Integer statusCode) {
+        this.statusCode = statusCode;
+    }
+
+    /**
+     * Returns the 'headers' of {@link Header} type of an object of this class.
+     * 
+     * @return headers of the HTTP request.
+     * @see Header
+     */
+    public Set<Header> getHeaders() {
+        return this.headers;
+    }
+
+    /**
+     * Sets the value of 'headers' of {@link Header} type of an object of this
+     * class.
+     * 
+     * @param headers
+     *            of {@link Header} Type
+     * @see Header
+     */
+    public void setHeaders(Set<Header> headers) {
+        this.headers = headers;
+    }
+
+    /**
+     * Returns the 'body' of an object of this class.
+     * 
+     * @return body of the HTTP request.
+     */
+    public String getBody() {
+        return this.body;
+    }
+
+    /**
+     * Sets the value of 'body' of an object of this class.
+     * 
+     * @param body
+     */
+    public void setBody(String body) {
+        this.body = body;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/domain/Header.java b/fineract-provider/src/main/java/org/apache/fineract/batch/domain/Header.java
new file mode 100644
index 0000000..992f5ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/domain/Header.java
@@ -0,0 +1,92 @@
+/**
+ * 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.batch.domain;
+
+/**
+ * Provides an object to handle HTTP headers as name and value pairs for Batch
+ * API. It is used in {@link BatchRequest} and {@link BatchResponse} to store
+ * the information regarding the headers in incoming and outgoing JSON Strings.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see BatchRequest
+ * @see BatchResponse
+ */
+public class Header {
+
+    private String name;
+    private String value;
+
+    /**
+     * Constructs a 'Header' with the name and value of HTTP headers.
+     * 
+     * @param name
+     *            of the HTTP header.
+     * @param value
+     *            of the HTTP header.
+     */
+    public Header(String name, String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    /**
+     * Constructs a default constructor of 'Header'
+     */
+    public Header() {
+
+    }
+
+    /**
+     * Returns the 'name' data field of the object of this class.
+     * 
+     * @return name data field of this class
+     */
+    public String getName() {
+        return this.name;
+    }
+
+    /**
+     * Sets 'name' data field of this class.
+     * 
+     * @param name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the 'value' data field of the object of this class.
+     * 
+     * @return value data field of this class
+     */
+    public String getValue() {
+        return this.value;
+    }
+
+    /**
+     * Sets 'value' data field of this class.
+     * 
+     * @param value
+     */
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java b/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java
new file mode 100644
index 0000000..65d166d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.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.batch.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
+import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
+import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformApiDataValidationExceptionMapper;
+import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDataIntegrityExceptionMapper;
+import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDomainRuleExceptionMapper;
+import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformInternalServerExceptionMapper;
+import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformResourceNotFoundExceptionMapper;
+import org.apache.fineract.infrastructure.core.exceptionmapper.UnsupportedParameterExceptionMapper;
+import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
+import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
+import org.springframework.transaction.TransactionException;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Provides an Error Handler method that returns an object of type
+ * {@link ErrorInfo} to the CommandStrategy which raised the exception. This
+ * class uses various subclasses of RuntimeException to check the kind of
+ * exception raised and provide appropriate status and error codes for each one
+ * of the raised exception.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.command.CommandStrategy
+ * @see org.apache.fineract.batch.command.internal.CreateClientCommandStrategy
+ */
+public class ErrorHandler extends RuntimeException {
+
+    private static Gson jsonHelper = new GsonBuilder().setPrettyPrinting().create();
+
+    /**
+     * Sole Constructor
+     */
+    ErrorHandler() {
+        super();
+    }
+
+    /**
+     * Returns an object of ErrorInfo type containing the information regarding
+     * the raised error.
+     * 
+     * @param exception
+     * @return ErrorInfo
+     */
+    public static ErrorInfo handler(final RuntimeException exception) {
+
+        if (exception instanceof AbstractPlatformResourceNotFoundException) {
+
+            final PlatformResourceNotFoundExceptionMapper mapper = new PlatformResourceNotFoundExceptionMapper();
+            final String errorBody = jsonHelper
+                    .toJson(mapper.toResponse((AbstractPlatformResourceNotFoundException) exception).getEntity());
+
+            return new ErrorInfo(404, 1001, errorBody);
+
+        } else if (exception instanceof UnsupportedParameterException) {
+
+            final UnsupportedParameterExceptionMapper mapper = new UnsupportedParameterExceptionMapper();
+            final String errorBody = jsonHelper.toJson(mapper.toResponse((UnsupportedParameterException) exception).getEntity());
+
+            return new ErrorInfo(400, 2001, errorBody);
+
+        } else if (exception instanceof PlatformApiDataValidationException) {
+
+            final PlatformApiDataValidationExceptionMapper mapper = new PlatformApiDataValidationExceptionMapper();
+            final String errorBody = jsonHelper.toJson(mapper.toResponse((PlatformApiDataValidationException) exception).getEntity());
+
+            return new ErrorInfo(400, 2002, errorBody);
+
+        } else if (exception instanceof PlatformDataIntegrityException) {
+
+            final PlatformDataIntegrityExceptionMapper mapper = new PlatformDataIntegrityExceptionMapper();
+            final String errorBody = jsonHelper.toJson(mapper.toResponse((PlatformDataIntegrityException) exception).getEntity());
+
+            return new ErrorInfo(403, 3001, errorBody);
+
+        } else if (exception instanceof LinkedAccountRequiredException) {
+
+            final PlatformDomainRuleExceptionMapper mapper = new PlatformDomainRuleExceptionMapper();
+            final String errorBody = jsonHelper.toJson(mapper.toResponse((LinkedAccountRequiredException) exception).getEntity());
+
+            return new ErrorInfo(403, 3002, errorBody);
+            
+        } else if (exception instanceof MultiDisbursementDataRequiredException) {
+
+            final PlatformDomainRuleExceptionMapper mapper = new PlatformDomainRuleExceptionMapper();
+            final String errorBody = jsonHelper.toJson(mapper.toResponse((MultiDisbursementDataRequiredException) exception).getEntity());
+
+            return new ErrorInfo(403, 3003, errorBody);
+            
+        } else if (exception instanceof TransactionException) {
+            return new ErrorInfo(400, 4001, "{\"Exception\": " + exception.getMessage()+"}");
+
+        } else if (exception instanceof PlatformInternalServerException) {
+
+            final PlatformInternalServerExceptionMapper mapper = new PlatformInternalServerExceptionMapper();
+            final String errorBody = jsonHelper.toJson(mapper.toResponse((PlatformInternalServerException) exception).getEntity());
+
+            return new ErrorInfo(500, 5001, errorBody);
+        }
+
+        return new ErrorInfo(500, 9999, "{\"Exception\": " + exception.toString() + "}");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorInfo.java b/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorInfo.java
new file mode 100644
index 0000000..1f320f6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorInfo.java
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.batch.exception;
+
+/**
+ * Provides members to hold the basic information about the exceptions raised in
+ * commandStrategy classes.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see ErrorHandler
+ */
+public final class ErrorInfo {
+
+    private Integer statusCode;
+    private Integer errorCode;
+    private String message;
+
+    /**
+     * Constructor to initialize the members of this class.
+     * 
+     * @param statusCode
+     * @param errorCode
+     * @param message
+     */
+    public ErrorInfo(final Integer statusCode, final Integer errorCode, final String message) {
+        super();
+        this.statusCode = statusCode;
+        this.errorCode = errorCode;
+        this.message = message;
+    }
+
+    /**
+     * Constructor so JSON serialization will work with out special Serialiazer
+     */
+    ErrorInfo() {
+        super();
+    }
+
+    /**
+     * Getter method to provide the statusCode for an object of this type.
+     * 
+     * @return Integer
+     */
+    public Integer getStatusCode() {
+        return this.statusCode;
+    }
+
+    /**
+     * Setter method to set the statusCode for an object of this type.
+     * 
+     * @param statusCode
+     */
+    public void setStatusCode(final Integer statusCode) {
+        this.statusCode = statusCode;
+    }
+
+    /**
+     * Getter method to provide the errorCode for an object of this type.
+     * 
+     * @return Integer
+     */
+    public Integer getErrorCode() {
+        return this.errorCode;
+    }
+
+    /**
+     * Setter method to set the errorCode for an object of this type.
+     * 
+     * @param errorCode
+     */
+    public void setErrorCode(final Integer errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    /**
+     * Getter method to provide the message of the error for an object of this
+     * type.
+     * 
+     * @return String
+     */
+    public String getMessage() {
+        return this.message;
+    }
+
+    /**
+     * Setter method to set the message of the error for an object of this type.
+     * 
+     * @param message
+     */
+    public void setMessage(final String message) {
+        this.message = message;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/serialization/BatchRequestJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/batch/serialization/BatchRequestJsonHelper.java
new file mode 100644
index 0000000..723b085
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/serialization/BatchRequestJsonHelper.java
@@ -0,0 +1,56 @@
+/**
+ * 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.batch.serialization;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Extends
+ * {@link org.apache.fineract.infrastructure.core.serialization.FromJsonHelper} to
+ * de-serialize the incoming String into a JSON List of type
+ * {@link org.apache.fineract.batch.domain.BatchRequest}
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.infrastructure.core.serialization.FromJsonHelper
+ */
+@Component
+public class BatchRequestJsonHelper extends FromJsonHelper {
+
+    /**
+     * Returns a list of batchRequests after de-serializing it from the input
+     * JSON string.
+     * 
+     * @param json
+     * @return List<BatchRequest>
+     */
+    public List<BatchRequest> extractList(final String json) {
+        final Type listType = new TypeToken<List<BatchRequest>>() {}.getType();
+        final List<BatchRequest> requests = super.getGsonConverter().fromJson(json, listType);
+        return requests;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiService.java b/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiService.java
new file mode 100644
index 0000000..4053127
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiService.java
@@ -0,0 +1,65 @@
+/**
+ * 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.batch.service;
+
+import java.util.List;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+
+/**
+ * Provides an interface for service class, that implements the method to handle
+ * separate Batch Requests.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ * @see BatchApiServiceImpl
+ */
+public interface BatchApiService {
+
+    /**
+     * Returns a list of {@link org.apache.fineract.batch.domain.BatchResponse}s
+     * by getting the appropriate CommandStrategy for every
+     * {@link org.apache.fineract.batch.domain.BatchRequest}. It will be used when
+     * the Query Parameter "enclosingTransaction "is set to 'false'.
+     * 
+     * @param requestList
+     * @param uriInfo
+     * @return List<BatchResponse>
+     */
+    List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(List<BatchRequest> requestList, UriInfo uriInfo);
+
+    /**
+     * returns a list of {@link org.apache.fineract.batch.domain.BatchResponse}s
+     * by getting the appropriate CommandStrategy for every
+     * {@link org.apache.fineract.batch.domain.BatchRequest}. It will be used when
+     * the Query Parameter "enclosingTransaction "is set to 'true'. If one or
+     * more of the requests are not completed properly then whole of the
+     * transaction will be rolled back properly.
+     * 
+     * @param requestList
+     * @param uriInfo
+     * @return List<BatchResponse>
+     */
+    List<BatchResponse> handleBatchRequestsWithEnclosingTransaction(List<BatchRequest> requestList, UriInfo uriInfo);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
new file mode 100644
index 0000000..d7aadd8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
@@ -0,0 +1,222 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.batch.service;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.command.CommandContext;
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.command.CommandStrategyProvider;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.batch.service.ResolutionHelper.BatchRequestNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.google.gson.Gson;
+
+import edu.emory.mathcs.backport.java.util.Collections;
+
+/**
+ * Implementation for {@link BatchApiService} to iterate through all the
+ * incoming requests and obtain the appropriate CommandStrategy from
+ * CommandStrategyProvider.
+ * 
+ * @author Rishabh Shukla
+ * 
+ * @see org.apache.fineract.batch.domain.BatchRequest
+ * @see org.apache.fineract.batch.domain.BatchResponse
+ * @see org.apache.fineract.batch.command.CommandStrategyProvider
+ */
+@Service
+public class BatchApiServiceImpl implements BatchApiService {
+
+    private final CommandStrategyProvider strategyProvider;
+    private final ResolutionHelper resolutionHelper;
+    private final TransactionTemplate transactionTemplate;
+    private List<BatchResponse> checkList = new ArrayList<>();
+
+    /**
+     * Constructs a 'BatchApiServiceImpl' with an argument of
+     * {@link org.apache.fineract.batch.command.CommandStrategyProvider} type.
+     * 
+     * @param strategyProvider
+     * @param resolutionHelper
+     * @param transactionTemplate
+     */
+    @Autowired
+    public BatchApiServiceImpl(final CommandStrategyProvider strategyProvider, final ResolutionHelper resolutionHelper,
+            final TransactionTemplate transactionTemplate) {
+        this.strategyProvider = strategyProvider;
+        this.resolutionHelper = resolutionHelper;
+        this.transactionTemplate = transactionTemplate;
+    }
+
+    /**
+     * Returns the response list by getting a proper
+     * {@link org.apache.fineract.batch.command.CommandStrategy}. execute() method
+     * of acquired commandStrategy is then provided with the separate Request.
+     * 
+     * @param requestList
+     * @param uriInfo
+     * @return List<BatchResponse>
+     */
+    private List<BatchResponse> handleBatchRequests(final List<BatchRequest> requestList, final UriInfo uriInfo) {
+
+        final List<BatchResponse> responseList = new ArrayList<>(requestList.size());
+
+        final List<BatchRequestNode> batchRequestNodes = this.resolutionHelper.getDependingRequests(requestList);
+        checkList.clear();
+
+        for (BatchRequestNode rootNode : batchRequestNodes) {
+            final BatchRequest rootRequest = rootNode.getRequest();
+            final CommandStrategy commandStrategy = this.strategyProvider.getCommandStrategy(CommandContext
+                    .resource(rootRequest.getRelativeUrl()).method(rootRequest.getMethod()).build());
+            final BatchResponse rootResponse = commandStrategy.execute(rootRequest, uriInfo);
+
+            responseList.add(rootResponse);
+            responseList.addAll(this.processChildRequests(rootNode, rootResponse, uriInfo));
+        }
+
+        Collections.sort(responseList, new Comparator<BatchResponse>() {
+
+            @Override
+            public int compare(BatchResponse source, BatchResponse testee) {
+                return source.getRequestId().compareTo(testee.getRequestId());
+            }
+        });
+
+        checkList = responseList;
+        return responseList;
+
+    }
+
+    private List<BatchResponse> processChildRequests(final BatchRequestNode rootRequest, BatchResponse rootResponse, UriInfo uriInfo) {
+
+        final List<BatchResponse> childResponses = new ArrayList<>();
+        if (rootRequest.getChildRequests().size() > 0) {
+
+            for (BatchRequestNode childNode : rootRequest.getChildRequests()) {
+
+                BatchRequest childRequest = childNode.getRequest();
+                BatchResponse childResponse;
+
+                try {
+
+                    if (rootResponse.getStatusCode().equals(200)) {
+                        childRequest = this.resolutionHelper.resoluteRequest(childRequest, rootResponse);
+                        final CommandStrategy commandStrategy = this.strategyProvider.getCommandStrategy(CommandContext
+                                .resource(childRequest.getRelativeUrl()).method(childRequest.getMethod()).build());
+
+                        childResponse = commandStrategy.execute(childRequest, uriInfo);
+
+                    } else {
+                        // Something went wrong with the parent request, create
+                        // a response with status code 409
+                        childResponse = new BatchResponse();
+                        childResponse.setRequestId(childRequest.getRequestId());
+                        childResponse.setStatusCode(Status.CONFLICT.getStatusCode());
+
+                        // Some detail information about the error
+                        final ErrorInfo conflictError = new ErrorInfo(Status.CONFLICT.getStatusCode(), 8001, "Parent request with id "
+                                + rootResponse.getRequestId() + " was erroneous!");
+                        childResponse.setBody(conflictError.getMessage());
+                    }
+                    childResponses.addAll(this.processChildRequests(childNode, childResponse, uriInfo));
+
+                } catch (Throwable ex) {
+
+                    childResponse = new BatchResponse();
+                    childResponse.setRequestId(childRequest.getRequestId());
+                    childResponse.setStatusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+                    childResponse.setBody(ex.getMessage());
+                }
+
+                childResponses.add(childResponse);
+            }
+        }
+
+        return childResponses;
+    }
+
+    @Override
+    public List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> requestList, UriInfo uriInfo) {
+
+        return handleBatchRequests(requestList, uriInfo);
+    }
+
+    @Override
+    public List<BatchResponse> handleBatchRequestsWithEnclosingTransaction(final List<BatchRequest> requestList, final UriInfo uriInfo) {
+
+        try {
+            return this.transactionTemplate.execute(new TransactionCallback<List<BatchResponse>>() {
+
+                @Override
+                public List<BatchResponse> doInTransaction(TransactionStatus status) {
+                    try {
+                        return handleBatchRequests(requestList, uriInfo);
+                    } catch (RuntimeException ex) {
+
+                        ErrorInfo e = ErrorHandler.handler(ex);
+                        BatchResponse errResponse = new BatchResponse();
+                        errResponse.setStatusCode(e.getStatusCode());
+                        errResponse.setBody(e.getMessage());
+
+                        List<BatchResponse> errResponseList = new ArrayList<>();
+                        errResponseList.add(errResponse);
+
+                        status.setRollbackOnly();
+                        return errResponseList;
+                    }
+                }
+
+            });
+        } catch (TransactionException ex) {
+            ErrorInfo e = ErrorHandler.handler(ex);
+            BatchResponse errResponse = new BatchResponse();
+            errResponse.setStatusCode(e.getStatusCode());
+
+            for (BatchResponse res : checkList) {
+                if (!res.getStatusCode().equals(200)) {
+                    errResponse.setBody("Transaction is being rolled back. First erroneous request: \n" + new Gson().toJson(res));
+                    break;
+                }
+            }
+
+            checkList.clear();
+            List<BatchResponse> errResponseList = new ArrayList<>();
+            errResponseList.add(errResponse);
+
+            return errResponseList;
+        }
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/service/ResolutionHelper.java b/fineract-provider/src/main/java/org/apache/fineract/batch/service/ResolutionHelper.java
new file mode 100644
index 0000000..c0c3ed9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/service/ResolutionHelper.java
@@ -0,0 +1,234 @@
+/**
+ * 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.batch.service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.jayway.jsonpath.JsonModel;
+
+/**
+ * Provides methods to create dependency map among the various batchRequests. It
+ * also provides method that takes care of dependency resolution among related
+ * requests.
+ * 
+ * @author Rishabh Shukla
+ * @see BatchApiServiceImpl
+ */
+@Component
+public class ResolutionHelper {
+
+    /**
+     * Provides a Node like object for the request tree.
+     * 
+     * @author Rishabh shukla
+     * 
+     */
+    public class BatchRequestNode {
+
+        private BatchRequest request;
+        private final List<BatchRequestNode> childRequests = new ArrayList<>();
+
+        public BatchRequestNode() {
+            super();
+        }
+
+        public BatchRequest getRequest() {
+            return this.request;
+        }
+
+        public void setRequest(BatchRequest request) {
+            this.request = request;
+        }
+
+        public List<BatchRequestNode> getChildRequests() {
+            return this.childRequests;
+        }
+
+        public void addChildRequest(final BatchRequestNode batchRequest) {
+            this.childRequests.add(batchRequest);
+        }
+
+    }
+
+    private FromJsonHelper fromJsonHelper;
+
+    @Autowired
+    public ResolutionHelper(final FromJsonHelper fromJsonHelper) {
+        this.fromJsonHelper = fromJsonHelper;
+    }
+
+    /**
+     * Returns a map containing requests that are divided in accordance of
+     * dependency relations among them. Each different list is identified with a
+     * "Key" which is the "requestId" of the request at topmost level in
+     * dependency hierarchy of that particular list.
+     * 
+     * @param batchRequests
+     * @return List<ArrayList<BatchRequestNode>>
+     */
+    public List<BatchRequestNode> getDependingRequests(final List<BatchRequest> batchRequests) {
+        final List<BatchRequestNode> rootRequests = new ArrayList<>();
+
+        for (BatchRequest batchRequest : batchRequests) {
+            if (batchRequest.getReference() == null) {
+                final BatchRequestNode node = new BatchRequestNode();
+                node.setRequest(batchRequest);
+                rootRequests.add(node);
+            } else {
+                this.addDependingRequest(batchRequest, rootRequests);
+            }
+        }
+
+        return rootRequests;
+    }
+
+    private void addDependingRequest(final BatchRequest batchRequest, final List<BatchRequestNode> parentRequests) {
+        for (BatchRequestNode batchRequestNode : parentRequests) {
+            if (batchRequestNode.getRequest().getRequestId().equals(batchRequest.getReference())) {
+                final BatchRequestNode dependingRequest = new BatchRequestNode();
+                dependingRequest.setRequest(batchRequest);
+                batchRequestNode.addChildRequest(dependingRequest);
+            } else {
+                addDependingRequest(batchRequest, batchRequestNode.getChildRequests());
+            }
+        }
+    }
+
+    /**
+     * Returns a BatchRequest after dependency resolution. It takes a request
+     * and the response of the request it is dependent upon as its arguments and
+     * change the body or relativeUrl of the request according to parent
+     * Request.
+     * 
+     * @param request
+     * @param lastResponse
+     * @return BatchRequest
+     */
+    public BatchRequest resoluteRequest(final BatchRequest request, final BatchResponse parentResponse) {
+
+        // Create a duplicate request
+        final BatchRequest br = request;
+
+        final JsonModel responseJsonModel = JsonModel.model(parentResponse.getBody());
+
+        // Gets the body from current Request as a JsonObject
+        final JsonObject jsonRequestBody = this.fromJsonHelper.parse(request.getBody()).getAsJsonObject();
+
+        JsonObject jsonResultBody = new JsonObject();
+
+        // Iterate through each element in the requestBody to find dependent
+        // parameter
+        for (Entry<String, JsonElement> element : jsonRequestBody.entrySet()) {
+            final String key = element.getKey();
+            final JsonElement value = resolveDependentVariables(element, responseJsonModel);
+            jsonResultBody.add(key, value);
+        }
+
+        // Set the body after dependency resolution
+        br.setBody(jsonResultBody.toString());
+
+        // Also check the relativeUrl for any dependency resolution
+        String relativeUrl = request.getRelativeUrl();
+
+        if (relativeUrl.contains("$.")) {
+
+            String queryParams = "";
+            if(relativeUrl.contains("?")) {
+                queryParams = relativeUrl.substring(relativeUrl.indexOf("?"));
+                relativeUrl = relativeUrl.substring(0, relativeUrl.indexOf("?"));
+            }
+            
+            final String[] parameters = relativeUrl.split("/");
+            
+            for (String parameter : parameters) {
+                if (parameter.contains("$.")) {
+                    final String resParamValue = responseJsonModel.get(parameter).toString();
+                    relativeUrl = relativeUrl.replace(parameter, resParamValue);
+                    br.setRelativeUrl(relativeUrl+queryParams);
+                }
+            }
+        }
+
+        return br;
+    }
+
+    private JsonElement resolveDependentVariables(final Entry<String, JsonElement> entryElement, final JsonModel responseJsonModel) {
+        JsonElement value = null;
+
+        final JsonElement element = entryElement.getValue();
+
+        if (element.isJsonObject()) {
+            final JsonObject jsObject = element.getAsJsonObject();
+            value = processJsonObject(jsObject, responseJsonModel);
+        } else if (element.isJsonArray()) {
+            final JsonArray jsElementArray = element.getAsJsonArray();
+            value = processJsonArray(jsElementArray, responseJsonModel);
+        } else {
+            value = resolveDependentVariable(element, responseJsonModel);
+        }
+        return value;
+    }
+
+    private JsonElement processJsonObject(final JsonObject jsObject, final JsonModel responseJsonModel) {
+        JsonObject valueObj = new JsonObject();
+        for (Entry<String, JsonElement> element : jsObject.entrySet()) {
+            final String key = element.getKey();
+            final JsonElement value = resolveDependentVariable(element.getValue(), responseJsonModel);
+            valueObj.add(key, value);
+        }
+        return valueObj;
+    }
+
+    private JsonArray processJsonArray(final JsonArray elementArray, final JsonModel responseJsonModel) {
+
+        JsonArray valueArr = new JsonArray();
+
+        for (JsonElement element : elementArray) {
+            if (element.isJsonObject()) {
+                final JsonObject jsObject = element.getAsJsonObject();
+                valueArr.add(processJsonObject(jsObject, responseJsonModel));
+            }
+        }
+
+        return valueArr;
+    }
+
+    private JsonElement resolveDependentVariable(final JsonElement element, final JsonModel responseJsonModel) {
+        JsonElement value = element;
+        String paramVal = element.getAsString();
+        if (paramVal.contains("$.")) {
+            // Get the value of the parameter from parent response
+            final String resParamValue = responseJsonModel.get(paramVal).toString();
+            value = this.fromJsonHelper.parse(resParamValue);
+        }
+        return value;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/annotation/CommandType.java b/fineract-provider/src/main/java/org/apache/fineract/commands/annotation/CommandType.java
new file mode 100644
index 0000000..2df98ef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/annotation/CommandType.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.commands.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Specifies the command type for the annotated class.<br/>
+ * <br/>
+ * The entity name (e.g. CLIENT, SAVINGSACCOUNT, LOANPRODUCT) and the action (e.g. CREATE, DELETE) must be given.
+ *
+ * @author Markus Geiss
+ * @version 1.0
+ * @since 15.06
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface CommandType {
+
+    /**
+     * Returns the name of the entity for this {@link CommandType}.
+     */
+    String entity();
+
+    /**
+     * Return the name of the action for this {@link CommandType}.
+     */
+    String action();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java
new file mode 100644
index 0000000..f1b278a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java
@@ -0,0 +1,211 @@
+/**
+ * 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.commands.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;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.commands.data.AuditData;
+import org.apache.fineract.commands.data.AuditSearchData;
+import org.apache.fineract.commands.service.AuditReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+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("/audits")
+@Component
+@Scope("singleton")
+public class AuditsApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "actionName", "entityName", "resourceId",
+            "subresourceId", "maker", "madeOnDate", "checker", "checkedOnDate", "processingResult", "commandAsJson", "officeName",
+            "groupLevelName", "groupName", "clientName", "loanAccountNo", "savingsAccountNo", "clientId", "loanId", "url"));
+
+    private final String resourceNameForPermissions = "AUDIT";
+
+    private final PlatformSecurityContext context;
+    private final AuditReadPlatformService auditReadPlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<AuditSearchData> toApiJsonSerializerSearchTemplate;
+
+    @Autowired
+    public AuditsApiResource(final PlatformSecurityContext context, final AuditReadPlatformService auditReadPlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper, final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializer,
+            final DefaultToApiJsonSerializer<AuditSearchData> toApiJsonSerializerSearchTemplate) {
+        this.context = context;
+        this.auditReadPlatformService = auditReadPlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.toApiJsonSerializerSearchTemplate = toApiJsonSerializerSearchTemplate;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAuditEntries(@Context final UriInfo uriInfo, @QueryParam("actionName") final String actionName,
+            @QueryParam("entityName") final String entityName, @QueryParam("resourceId") final Long resourceId,
+            @QueryParam("makerId") final Long makerId, @QueryParam("makerDateTimeFrom") final String makerDateTimeFrom,
+            @QueryParam("makerDateTimeTo") final String makerDateTimeTo, @QueryParam("checkerId") final Long checkerId,
+            @QueryParam("checkerDateTimeFrom") final String checkerDateTimeFrom,
+            @QueryParam("checkerDateTimeTo") final String checkerDateTimeTo,
+            @QueryParam("processingResult") final Integer processingResult, @QueryParam("officeId") final Integer officeId,
+            @QueryParam("groupId") final Integer groupId, @QueryParam("clientId") final Integer clientId,
+            @QueryParam("loanid") final Integer loanId, @QueryParam("savingsAccountId") final Integer savingsAccountId,
+            @QueryParam("paged") final Boolean paged, @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder);
+        final String extraCriteria = getExtraCriteria(actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo,
+                checkerId, checkerDateTimeFrom, checkerDateTimeTo, processingResult, officeId, groupId, clientId, loanId, savingsAccountId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (parameters.isPaged()) {
+            final Page<AuditData> auditEntries = this.auditReadPlatformService.retrievePaginatedAuditEntries(extraCriteria,
+                    settings.isIncludeJson(), parameters);
+            return this.toApiJsonSerializer.serialize(settings, auditEntries, this.RESPONSE_DATA_PARAMETERS);
+        }
+
+        final Collection<AuditData> auditEntries = this.auditReadPlatformService.retrieveAuditEntries(extraCriteria,
+                settings.isIncludeJson());
+
+        return this.toApiJsonSerializer.serialize(settings, auditEntries, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{auditId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAuditEntry(@PathParam("auditId") final Long auditId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final AuditData auditEntry = this.auditReadPlatformService.retrieveAuditEntry(auditId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, auditEntry, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("/searchtemplate")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAuditSearchTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        final AuditSearchData auditSearchData = this.auditReadPlatformService.retrieveSearchTemplate("audit");
+
+        final Set<String> RESPONSE_DATA_PARAMETERS_SEARCH_TEMPLATE = new HashSet<>(Arrays.asList("appUsers", "actionNames",
+                "entityNames", "processingResults"));
+
+        return this.toApiJsonSerializerSearchTemplate.serialize(settings, auditSearchData, RESPONSE_DATA_PARAMETERS_SEARCH_TEMPLATE);
+    }
+
+    private String getExtraCriteria(final String actionName, final String entityName, final Long resourceId, final Long makerId,
+            final String makerDateTimeFrom, final String makerDateTimeTo, final Long checkerId, final String checkerDateTimeFrom,
+            final String checkerDateTimeTo, final Integer processingResult, final Integer officeId, final Integer groupId,
+            final Integer clientId, final Integer loanId, final Integer savingsAccountId) {
+
+        String extraCriteria = "";
+
+        if (actionName != null) {
+            extraCriteria += " and aud.action_name = " + ApiParameterHelper.sqlEncodeString(actionName);
+        }
+        if (entityName != null) {
+            extraCriteria += " and aud.entity_name like " + ApiParameterHelper.sqlEncodeString(entityName + "%");
+        }
+
+        if (resourceId != null) {
+            extraCriteria += " and aud.resource_id = " + resourceId;
+        }
+        if (makerId != null) {
+            extraCriteria += " and aud.maker_id = " + makerId;
+        }
+        if (checkerId != null) {
+            extraCriteria += " and aud.checker_id = " + checkerId;
+        }
+        if (makerDateTimeFrom != null) {
+            extraCriteria += " and aud.made_on_date >= " + ApiParameterHelper.sqlEncodeString(makerDateTimeFrom);
+        }
+        if (makerDateTimeTo != null) {
+            extraCriteria += " and aud.made_on_date <= " + ApiParameterHelper.sqlEncodeString(makerDateTimeTo);
+        }
+        if (checkerDateTimeFrom != null) {
+            extraCriteria += " and aud.checked_on_date >= " + ApiParameterHelper.sqlEncodeString(checkerDateTimeFrom);
+        }
+        if (checkerDateTimeTo != null) {
+            extraCriteria += " and aud.checked_on_date <= " + ApiParameterHelper.sqlEncodeString(checkerDateTimeTo);
+        }
+
+        if (processingResult != null) {
+            extraCriteria += " and aud.processing_result_enum = " + processingResult;
+        }
+
+        if (officeId != null) {
+            extraCriteria += " and aud.office_id = " + officeId;
+        }
+
+        if (groupId != null) {
+            extraCriteria += " and aud.group_id = " + groupId;
+        }
+
+        if (clientId != null) {
+            extraCriteria += " and aud.client_id = " + clientId;
+        }
+
+        if (loanId != null) {
+            extraCriteria += " and aud.loan_id = " + loanId;
+        }
+
+        if (savingsAccountId != null) {
+            extraCriteria += " and aud.savings_account_id = " + savingsAccountId;
+        }
+
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            extraCriteria = extraCriteria.substring(4);
+        }
+
+        return extraCriteria;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/api/MakercheckersApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/commands/api/MakercheckersApiResource.java
new file mode 100755
index 0000000..327fee1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/api/MakercheckersApiResource.java
@@ -0,0 +1,202 @@
+/**
+ * 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.commands.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.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.commands.data.AuditData;
+import org.apache.fineract.commands.data.AuditSearchData;
+import org.apache.fineract.commands.service.AuditReadPlatformService;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/makercheckers")
+@Component
+@Scope("singleton")
+public class MakercheckersApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "actionName", "entityName", "resourceId",
+            "subresourceId", "maker", "madeOnDate", "checker", "checkedOnDate", "processingResult", "commandAsJson", "officeName",
+            "groupLevelName", "groupName", "clientName", "loanAccountNo", "savingsAccountNo", "clientId", "loanId"));
+
+    private final AuditReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializerAudit;
+    private final DefaultToApiJsonSerializer<AuditSearchData> toApiJsonSerializerSearchTemplate;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService writePlatformService;
+
+    @Autowired
+    public MakercheckersApiResource(final AuditReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializerAudit,
+            final DefaultToApiJsonSerializer<AuditSearchData> toApiJsonSerializerSearchTemplate,
+            final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioCommandSourceWritePlatformService writePlatformService) {
+        this.readPlatformService = readPlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.toApiJsonSerializerAudit = toApiJsonSerializerAudit;
+        this.toApiJsonSerializerSearchTemplate = toApiJsonSerializerSearchTemplate;
+        this.writePlatformService = writePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCommands(@Context final UriInfo uriInfo, @QueryParam("actionName") final String actionName,
+            @QueryParam("entityName") final String entityName, @QueryParam("resourceId") final Long resourceId,
+            @QueryParam("makerId") final Long makerId, @QueryParam("makerDateTimeFrom") final String makerDateTimeFrom,
+            @QueryParam("makerDateTimeTo") final String makerDateTimeTo, @QueryParam("officeId") final Integer officeId,
+            @QueryParam("groupId") final Integer groupId, @QueryParam("clientId") final Integer clientId,
+            @QueryParam("loanid") final Integer loanId, @QueryParam("savingsAccountId") final Integer savingsAccountId) {
+
+        final String extraCriteria = getExtraCriteria(actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo,
+                officeId, groupId, clientId, loanId, savingsAccountId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        final Collection<AuditData> entries = this.readPlatformService.retrieveAllEntriesToBeChecked(extraCriteria,
+                settings.isIncludeJson());
+
+        return this.toApiJsonSerializerAudit.serialize(settings, entries, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("/searchtemplate")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAuditSearchTemplate(@Context final UriInfo uriInfo) {
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        final AuditSearchData auditSearchData = this.readPlatformService.retrieveSearchTemplate("makerchecker");
+
+        final Set<String> RESPONSE_DATA_PARAMETERS_SEARCH_TEMPLATE = new HashSet<>(Arrays.asList("appUsers", "actionNames",
+                "entityNames"));
+
+        return this.toApiJsonSerializerSearchTemplate.serialize(settings, auditSearchData, RESPONSE_DATA_PARAMETERS_SEARCH_TEMPLATE);
+    }
+
+    @POST
+    @Path("{auditId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String approveMakerCheckerEntry(@PathParam("auditId") final Long auditId, @QueryParam("command") final String commandParam) {
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "approve")) {
+            result = this.writePlatformService.approveEntry(auditId);
+        } else if (is(commandParam, "reject")) {
+            final Long id = this.writePlatformService.rejectEntry(auditId);
+            result = CommandProcessingResult.commandOnlyResult(id);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam);
+        }
+        return this.toApiJsonSerializerAudit.serialize(result);
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @DELETE
+    @Path("{auditId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteMakerCheckerEntry(@PathParam("auditId") final Long auditId) {
+
+        final Long id = this.writePlatformService.deleteEntry(auditId);
+
+        return this.toApiJsonSerializerAudit.serialize(CommandProcessingResult.commandOnlyResult(id));
+    }
+
+    private String getExtraCriteria(final String actionName, final String entityName, final Long resourceId, final Long makerId,
+            final String makerDateTimeFrom, final String makerDateTimeTo, final Integer officeId, final Integer groupId,
+            final Integer clientId, final Integer loanId, final Integer savingsAccountId) {
+
+        String extraCriteria = "";
+
+        if (actionName != null) {
+            extraCriteria += " and aud.action_name = " + ApiParameterHelper.sqlEncodeString(actionName);
+        }
+        if (entityName != null) {
+            extraCriteria += " and aud.entity_name like " + ApiParameterHelper.sqlEncodeString(entityName + "%");
+        }
+
+        if (resourceId != null) {
+            extraCriteria += " and aud.resource_id = " + resourceId;
+        }
+        if (makerId != null) {
+            extraCriteria += " and aud.maker_id = " + makerId;
+        }
+        if (makerDateTimeFrom != null) {
+            extraCriteria += " and aud.made_on_date >= " + ApiParameterHelper.sqlEncodeString(makerDateTimeFrom);
+        }
+        if (makerDateTimeTo != null) {
+            extraCriteria += " and aud.made_on_date <= " + ApiParameterHelper.sqlEncodeString(makerDateTimeTo);
+        }
+
+        if (officeId != null) {
+            extraCriteria += " and aud.office_id = " + officeId;
+        }
+
+        if (groupId != null) {
+            extraCriteria += " and aud.group_id = " + groupId;
+        }
+
+        if (clientId != null) {
+            extraCriteria += " and aud.client_id = " + clientId;
+        }
+
+        if (loanId != null) {
+            extraCriteria += " and aud.loan_id = " + loanId;
+        }
+
+        if (savingsAccountId != null) {
+            extraCriteria += " and aud.savings_account_id = " + savingsAccountId;
+        }
+
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            extraCriteria = extraCriteria.substring(4);
+        }
+
+        return extraCriteria;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditData.java b/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditData.java
new file mode 100755
index 0000000..af53b55
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditData.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.commands.data;
+
+import org.joda.time.DateTime;
+
+/**
+ * Immutable data object representing client data.
+ */
+public final class AuditData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String actionName;
+    private final String entityName;
+    @SuppressWarnings("unused")
+    private final Long resourceId;
+    @SuppressWarnings("unused")
+    private final Long subresourceId;
+    @SuppressWarnings("unused")
+    private final String maker;
+    @SuppressWarnings("unused")
+    private final DateTime madeOnDate;
+    @SuppressWarnings("unused")
+    private final String checker;
+    @SuppressWarnings("unused")
+    private final DateTime checkedOnDate;
+    @SuppressWarnings("unused")
+    private final String processingResult;
+    private String commandAsJson;
+    @SuppressWarnings("unused")
+    private final String officeName;
+    @SuppressWarnings("unused")
+    private final String groupLevelName;
+    @SuppressWarnings("unused")
+    private final String groupName;
+    @SuppressWarnings("unused")
+    private final String clientName;
+    @SuppressWarnings("unused")
+    private final String loanAccountNo;
+    @SuppressWarnings("unused")
+    private final String savingsAccountNo;
+    @SuppressWarnings("unused")
+    private final Long clientId;
+    @SuppressWarnings("unused")
+    private final Long loanId;
+    @SuppressWarnings("unused")
+    private final String url;
+
+    public AuditData(final Long id, final String actionName, final String entityName, final Long resourceId, final Long subresourceId,
+            final String maker, final DateTime madeOnDate, final String checker, final DateTime checkedOnDate,
+            final String processingResult, final String commandAsJson, final String officeName, final String groupLevelName,
+            final String groupName, final String clientName, final String loanAccountNo, final String savingsAccountNo,
+            final Long clientId, final Long loanId, final String url) {
+
+        this.id = id;
+        this.actionName = actionName;
+        this.entityName = entityName;
+        this.resourceId = resourceId;
+        this.subresourceId = subresourceId;
+        this.maker = maker;
+        this.madeOnDate = madeOnDate;
+        this.checker = checker;
+        this.checkedOnDate = checkedOnDate;
+        this.commandAsJson = commandAsJson;
+        this.processingResult = processingResult;
+        this.officeName = officeName;
+        this.groupLevelName = groupLevelName;
+        this.groupName = groupName;
+        this.clientName = clientName;
+        this.loanAccountNo = loanAccountNo;
+        this.savingsAccountNo = savingsAccountNo;
+        this.clientId = clientId;
+        this.loanId = loanId;
+        this.url = url;
+    }
+
+    public void setCommandAsJson(final String commandAsJson) {
+        this.commandAsJson = commandAsJson;
+    }
+
+    public String getCommandAsJson() {
+        return this.commandAsJson;
+    }
+
+    public String getEntityName() {
+        return this.entityName;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditSearchData.java b/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditSearchData.java
new file mode 100644
index 0000000..a180cf2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditSearchData.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.commands.data;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.useradministration.data.AppUserData;
+
+/**
+ * Immutable data object representing audit search results.
+ */
+public final class AuditSearchData {
+
+    @SuppressWarnings("unused")
+    private final Collection<AppUserData> appUsers;
+    @SuppressWarnings("unused")
+    private final List<String> actionNames;
+    @SuppressWarnings("unused")
+    private final List<String> entityNames;
+    @SuppressWarnings("unused")
+    private final Collection<ProcessingResultLookup> processingResults;
+
+    public AuditSearchData(final Collection<AppUserData> appUsers, final List<String> apiOperations, final List<String> resources,
+            final Collection<ProcessingResultLookup> processingResults) {
+        this.appUsers = appUsers;
+        this.actionNames = apiOperations;
+        this.entityNames = resources;
+        this.processingResults = processingResults;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/data/ProcessingResultLookup.java b/fineract-provider/src/main/java/org/apache/fineract/commands/data/ProcessingResultLookup.java
new file mode 100644
index 0000000..18dac42
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/data/ProcessingResultLookup.java
@@ -0,0 +1,42 @@
+/**
+ * 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.commands.data;
+
+/**
+ * Immutable data object for application user data.
+ */
+public class ProcessingResultLookup {
+
+    private final Long id;
+    private final String processingResult;
+
+    public ProcessingResultLookup(final Long id, final String processingResult) {
+        this.id = id;
+        this.processingResult = processingResult;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getProcessingResult() {
+        return this.processingResult;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandProcessingResultType.java b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandProcessingResultType.java
new file mode 100644
index 0000000..0e75ec8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandProcessingResultType.java
@@ -0,0 +1,59 @@
+/**
+ * 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.commands.domain;
+
+public enum CommandProcessingResultType {
+
+    INVALID(0, "commandProcessingResultType.invalid"), //
+    PROCESSED(1, "commandProcessingResultType.processed"), //
+    AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
+    REJECTED(3, "commandProcessingResultType.rejected");
+
+    private final Integer value;
+    private final String code;
+
+    private CommandProcessingResultType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static CommandProcessingResultType fromInt(final Integer typeValue) {
+        CommandProcessingResultType type = CommandProcessingResultType.INVALID;
+        switch (typeValue) {
+            case 1:
+                type = PROCESSED;
+            break;
+            case 2:
+                type = AWAITING_APPROVAL;
+            break;
+            case 3:
+                type = REJECTED;
+            break;
+        }
+        return type;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
new file mode 100755
index 0000000..a859e8e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
@@ -0,0 +1,256 @@
+/**
+ * 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.commands.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.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.DateTime;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_portfolio_command_source")
+public class CommandSource extends AbstractPersistable<Long> {
+
+    @Column(name = "action_name", nullable = true, length = 100)
+    private String actionName;
+
+    @Column(name = "entity_name", nullable = true, length = 100)
+    private String entityName;
+
+    @Column(name = "office_id")
+    private Long officeId;
+
+    @Column(name = "group_id")
+    private Long groupId;
+
+    @Column(name = "client_id")
+    private Long clientId;
+
+    @Column(name = "loan_id")
+    private Long loanId;
+
+    @Column(name = "savings_account_id")
+    private Long savingsId;
+
+    @Column(name = "api_get_url", length = 100)
+    private String resourceGetUrl;
+
+    @Column(name = "resource_id")
+    private Long resourceId;
+
+    @Column(name = "subresource_id")
+    private Long subresourceId;
+
+    @Column(name = "command_as_json", length = 1000)
+    private String commandAsJson;
+
+    @ManyToOne
+    @JoinColumn(name = "maker_id", nullable = false)
+    private AppUser maker;
+
+    @Column(name = "made_on_date", nullable = false)
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date madeOnDate;
+
+    @ManyToOne
+    @JoinColumn(name = "checker_id", nullable = true)
+    private AppUser checker;
+
+    @Column(name = "checked_on_date", nullable = true)
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date checkedOnDate;
+
+    @Column(name = "processing_result_enum", nullable = false)
+    private Integer processingResult;
+
+    @Column(name = "product_id")
+    private Long productId;
+    
+    @Column(name = "transaction_id", length = 100)
+    private String transactionId;
+
+    public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker) {
+        return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(),
+                command.json(), maker, DateTime.now());
+    }
+
+    protected CommandSource() {
+        //
+    }
+
+    private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId,
+            final Long subresourceId, final String commandSerializedAsJson, final AppUser maker, final DateTime madeOnDateTime) {
+        this.actionName = actionName;
+        this.entityName = entityName;
+        this.resourceGetUrl = href;
+        this.resourceId = resourceId;
+        this.subresourceId = subresourceId;
+        this.commandAsJson = commandSerializedAsJson;
+        this.maker = maker;
+        this.madeOnDate = madeOnDateTime.toDate();
+        this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
+    }
+
+    public void markAsChecked(final AppUser checker, final DateTime checkedOnDate) {
+        this.checker = checker;
+        this.checkedOnDate = checkedOnDate.toDate();
+        this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
+    }
+
+    public void markAsRejected(final AppUser checker, final DateTime checkedOnDate){
+        this.checker = checker;
+        this.checkedOnDate = checkedOnDate.toDate();
+        this.processingResult = CommandProcessingResultType.REJECTED.getValue();
+    }
+
+    public void updateResourceId(final Long resourceId) {
+        this.resourceId = resourceId;
+    }
+
+    public void updateSubresourceId(final Long subresourceId) {
+        this.subresourceId = subresourceId;
+    }
+
+    public void updateJsonTo(final String json) {
+        this.commandAsJson = json;
+    }
+
+    public Long resourceId() {
+        return this.resourceId;
+    }
+
+    public Long subresourceId() {
+        return this.subresourceId;
+    }
+
+    public boolean hasJson() {
+        return StringUtils.isNotBlank(this.commandAsJson);
+    }
+
+    public String json() {
+        return this.commandAsJson;
+    }
+
+    public String getActionName() {
+        return this.actionName;
+    }
+
+    public String getEntityName() {
+        return this.entityName;
+    }
+
+    public String getPermissionCode() {
+        return this.actionName + "_" + this.entityName;
+    }
+
+    public Long getResourceId() {
+        return this.resourceId;
+    }
+
+    public Long getSubresourceId() {
+        return this.subresourceId;
+    }
+
+    public void markAsAwaitingApproval() {
+        this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL.getValue();
+    }
+
+    public boolean isMarkedAsAwaitingApproval() {
+        if (this.processingResult.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue())) { return true; }
+
+        return false;
+    }
+
+    public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId,
+            final Long productId, final String transactionId) {
+        this.officeId = officeId;
+        this.groupId = groupId;
+        this.clientId = clientId;
+        this.loanId = loanId;
+        this.savingsId = savingsId;
+        this.productId = productId;
+        this.transactionId = transactionId;
+    }
+
+    public String getResourceGetUrl() {
+        return this.resourceGetUrl;
+    }
+
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    /**
+     * @return the clientId
+     */
+    public Long getClientId() {
+        return clientId;
+    }
+
+    /**
+     * @return the groupId
+     */
+    public Long getGroupId() {
+        return groupId;
+    }
+
+    /**
+     * @return the loanId
+     */
+    public Long getLoanId() {
+        return loanId;
+    }
+
+    /**
+     * @return the officeId
+     */
+    public Long getOfficeId() {
+        return officeId;
+    }
+
+    /**
+     * @return the savingsId
+     */
+    public Long getSavingsId() {
+        return savingsId;
+    }
+
+    /**
+     * @return the transactionId
+     */
+    public String getTransactionId() {
+        return this.transactionId;
+    }
+
+    public void updateTransaction(final String transactionId) {
+        this.transactionId = transactionId;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSourceRepository.java b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSourceRepository.java
new file mode 100644
index 0000000..4ecd013
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSourceRepository.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.commands.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface CommandSourceRepository extends JpaRepository<CommandSource, Long>, JpaSpecificationExecutor<CommandSource> {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java
new file mode 100755
index 0000000..3caa3a6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java
@@ -0,0 +1,322 @@
+/**
+ * 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.commands.domain;
+
+import org.apache.fineract.useradministration.api.PasswordPreferencesApiConstants;
+
+public class CommandWrapper {
+
+    private final Long commandId;
+    @SuppressWarnings("unused")
+    private final Long officeId;
+    private final Long groupId;
+    private final Long clientId;
+    private final Long loanId;
+    private final Long savingsId;
+    private final String actionName;
+    private final String entityName;
+    private final String taskPermissionName;
+    private final Long entityId;
+    private final Long subentityId;
+    private final String href;
+    private final String json;
+    private final String transactionId;
+    private final Long productId;
+
+    @SuppressWarnings("unused")
+    private Long templateId;
+
+    public static CommandWrapper wrap(final String actionName, final String entityName, final Long resourceId, final Long subresourceId) {
+        return new CommandWrapper(null, actionName, entityName, resourceId, subresourceId, null, null);
+    }
+
+    public static CommandWrapper fromExistingCommand(final Long commandId, final String actionName, final String entityName,
+            final Long resourceId, final Long subresourceId, final String resourceGetUrl, final Long productId) {
+        return new CommandWrapper(commandId, actionName, entityName, resourceId, subresourceId, resourceGetUrl, productId);
+    }
+
+    public static CommandWrapper fromExistingCommand(final Long commandId, final String actionName, final String entityName,
+            final Long resourceId, final Long subresourceId, final String resourceGetUrl, final Long productId, final Long officeId,
+            final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId) {
+        return new CommandWrapper(commandId, actionName, entityName, resourceId, subresourceId, resourceGetUrl, productId, officeId,
+                groupId, clientId, loanId, savingsId, transactionId);
+    }
+
+    private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId,
+            final Long subresourceId, final String resourceGetUrl, final Long productId) {
+        this.commandId = commandId;
+        this.officeId = null;
+        this.groupId = null;
+        this.clientId = null;
+        this.loanId = null;
+        this.savingsId = null;
+        this.actionName = actionName;
+        this.entityName = entityName;
+        this.taskPermissionName = actionName + "_" + entityName;
+        this.entityId = resourceId;
+        this.subentityId = subresourceId;
+        this.href = resourceGetUrl;
+        this.json = null;
+        this.transactionId = null;
+        this.productId = productId;
+    }
+
+    public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId,
+            final String actionName, final String entityName, final Long entityId, final Long subentityId, final String href,
+            final String json, final String transactionId, final Long productId, final Long templateId) {
+
+        this.commandId = null;
+        this.officeId = officeId;
+        this.groupId = groupId;
+        this.clientId = clientId;
+        this.loanId = loanId;
+        this.savingsId = savingsId;
+        this.actionName = actionName;
+        this.entityName = entityName;
+        this.taskPermissionName = actionName + "_" + entityName;
+        this.entityId = entityId;
+        this.subentityId = subentityId;
+        this.href = href;
+        this.json = json;
+        this.transactionId = transactionId;
+        this.productId = productId;
+        this.templateId = templateId;
+    }
+
+    private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId,
+            final Long subresourceId, final String resourceGetUrl, final Long productId, final Long officeId, final Long groupId,
+            final Long clientId, final Long loanId, final Long savingsId, final String transactionId) {
+
+        this.commandId = commandId;
+        this.officeId = officeId;
+        this.groupId = groupId;
+        this.clientId = clientId;
+        this.loanId = loanId;
+        this.savingsId = savingsId;
+        this.actionName = actionName;
+        this.entityName = entityName;
+        this.taskPermissionName = actionName + "_" + entityName;
+        this.entityId = resourceId;
+        this.subentityId = subresourceId;
+        this.href = resourceGetUrl;
+        this.json = null;
+        this.transactionId = transactionId;
+        this.productId = productId;
+    }
+
+    public String getHref() {
+        return this.href;
+    }
+
+    public String getJson() {
+        return this.json;
+    }
+
+    public boolean isCreate() {
+        return this.actionName.equalsIgnoreCase("CREATE");
+    }
+
+    public boolean isCreateDatatable() {
+        return this.actionName.equalsIgnoreCase("CREATE") && this.href.startsWith("/datatables/") && this.entityId == null;
+    }
+
+    public boolean isDeleteDatatable() {
+        return this.actionName.equalsIgnoreCase("DELETE") && this.href.startsWith("/datatables/") && this.entityId == null;
+    }
+
+    public boolean isUpdateDatatable() {
+        return this.actionName.equalsIgnoreCase("UPDATE") && this.href.startsWith("/datatables/") && this.entityId == null;
+    }
+
+    public boolean isDatatableResource() {
+        return this.href.startsWith("/datatables/");
+    }
+
+    public boolean isDeleteOneToOne() {
+        /* also covers case of deleting all of a one to many */
+        return isDatatableResource() && isDeleteOperation() && this.subentityId == null;
+    }
+
+    public boolean isDeleteMultiple() {
+        return isDatatableResource() && isDeleteOperation() && this.subentityId != null;
+    }
+
+    public boolean isUpdateOneToOne() {
+        return isDatatableResource() && isUpdateOperation() && this.subentityId == null;
+    }
+
+    public boolean isUpdateMultiple() {
+        return isDatatableResource() && isUpdateOperation() && this.subentityId != null;
+    }
+
+    public boolean isRegisterDatatable() {
+        return this.actionName.equalsIgnoreCase("REGISTER") && this.href.startsWith("/datatables/") && this.entityId == null;
+    }
+
+    public boolean isNoteResource() {
+        boolean isnoteResource = false;
+        if (this.entityName.equalsIgnoreCase("CLIENTNOTE") || this.entityName.equalsIgnoreCase("LOANNOTE")
+                || this.entityName.equalsIgnoreCase("LOANTRANSACTIONNOTE") || this.entityName.equalsIgnoreCase("SAVINGNOTE")
+                || this.entityName.equalsIgnoreCase("GROUPNOTE")) {
+            isnoteResource = true;
+        }
+        return isnoteResource;
+    }
+
+    public boolean isUpdateOfOwnUserDetails(final Long loggedInUserId) {
+        return isUserResource() && isUpdate() && loggedInUserId.equals(this.entityId);
+    }
+
+    public boolean isUpdate() {
+        // permissions resource has special update which involves no resource.
+        return isPermissionResource() && isUpdateOperation() || isCurrencyResource() && isUpdateOperation() || isCacheResource()
+                && isUpdateOperation() || isWorkingDaysResource() && isUpdateOperation() || isPasswordPreferencesResource()
+                && isUpdateOperation() || isUpdateOperation() && this.entityId != null;
+    }
+
+    public boolean isCacheResource() {
+        return this.entityName.equalsIgnoreCase("CACHE");
+    }
+
+    public Long getSubentityId() {
+        return this.subentityId;
+    }
+
+    public String getTransactionId() {
+        return this.transactionId;
+    }
+
+    public String getEntityName() {
+        return this.entityName;
+    }
+
+    public Long getEntityId() {
+        return this.entityId;
+    }
+
+    public boolean isUpdateOperation() {
+        return this.actionName.equalsIgnoreCase("UPDATE");
+    }
+
+    public boolean isDelete() {
+        return isDeleteOperation() && this.entityId != null;
+    }
+
+    public boolean isDeleteOperation() {
+        return this.actionName.equalsIgnoreCase("DELETE");
+    }
+
+    public boolean isSurveyResource() {
+        return this.href.startsWith("/survey/");
+    }
+
+    public boolean isRegisterSurvey() {
+        return this.actionName.equalsIgnoreCase("REGISTER");
+    }
+
+    public boolean isFullFilSurvey() {
+        return this.actionName.equalsIgnoreCase("CREATE");
+    }
+
+    public boolean isWorkingDaysResource() {
+        return this.entityName.equalsIgnoreCase("WORKINGDAYS");
+    }
+
+    public boolean isPasswordPreferencesResource() {
+        return this.entityName.equalsIgnoreCase(PasswordPreferencesApiConstants.ENTITY_NAME);
+    }
+
+    public Long commandId() {
+        return this.commandId;
+    }
+
+    public String actionName() {
+        return this.actionName;
+    }
+
+    public String entityName() {
+        return this.entityName;
+    }
+
+    public Long resourceId() {
+        return this.entityId;
+    }
+
+    public Long subresourceId() {
+        return this.subentityId;
+    }
+
+    public String taskPermissionName() {
+        return this.actionName + "_" + this.entityName;
+    }
+
+    public String getTaskPermissionName() {
+        return this.taskPermissionName;
+    }
+
+    public Long getGroupId() {
+        return this.groupId;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public Long getSavingsId() {
+        return this.savingsId;
+    }
+
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    public boolean isPermissionResource() {
+        return this.entityName.equalsIgnoreCase("PERMISSION");
+    }
+
+    public boolean isUserResource() {
+        return this.entityName.equalsIgnoreCase("USER");
+    }
+
+    public boolean isCurrencyResource() {
+        return this.entityName.equalsIgnoreCase("CURRENCY");
+    }
+
+    public String commandName() {
+        return this.actionName + "_" + this.entityName;
+    }
+
+    public boolean isLoanDisburseDetailResource() {
+        return this.entityName.equalsIgnoreCase("DISBURSEMENTDETAIL");
+    }
+
+    public boolean isUpdateDisbursementDate() {
+        return this.actionName.equalsIgnoreCase("UPDATE") && this.entityName.equalsIgnoreCase("DISBURSEMENTDETAIL")
+                && this.entityId != null;
+    }
+
+    public boolean addAndDeleteDisbursementDetails() {
+        return this.actionName.equalsIgnoreCase("UPDATE") && this.entityName.equalsIgnoreCase("DISBURSEMENTDETAIL")
+                && this.entityId == null;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/exception/CommandNotAwaitingApprovalException.java b/fineract-provider/src/main/java/org/apache/fineract/commands/exception/CommandNotAwaitingApprovalException.java
new file mode 100644
index 0000000..3fcff4d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/exception/CommandNotAwaitingApprovalException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.commands.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when client resources are not found.
+ */
+public class CommandNotAwaitingApprovalException extends AbstractPlatformResourceNotFoundException {
+
+    public CommandNotAwaitingApprovalException(final Long id) {
+        super("error.msg.command.id.not.awaiting.approval", "Audit with identifier " + id + " is Not Awaiting Approval", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/exception/CommandNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/commands/exception/CommandNotFoundException.java
new file mode 100644
index 0000000..edf80b2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/exception/CommandNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.commands.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when client resources are not found.
+ */
+public class CommandNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public CommandNotFoundException(final Long id) {
+        super("error.msg.command.id.invalid", "Audit with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/exception/RollbackTransactionAsCommandIsNotApprovedByCheckerException.java b/fineract-provider/src/main/java/org/apache/fineract/commands/exception/RollbackTransactionAsCommandIsNotApprovedByCheckerException.java
new file mode 100644
index 0000000..c0b44bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/exception/RollbackTransactionAsCommandIsNotApprovedByCheckerException.java
@@ -0,0 +1,45 @@
+/**
+ * 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.commands.exception;
+
+import org.apache.fineract.commands.domain.CommandSource;
+
+public class RollbackTransactionAsCommandIsNotApprovedByCheckerException extends RuntimeException {
+
+    /**
+     * When maker-checker is configured globally and also for the current
+     * transaction.
+     * 
+     * An initial save determines if there are any integrity rule or data
+     * problems.
+     * 
+     * If there isn't... and the transaction is from a maker... then this roll
+     * back is issued and the commandSourceResult is used to write the audit
+     * entry.
+     */
+    private final CommandSource commandSourceResult;
+
+    public RollbackTransactionAsCommandIsNotApprovedByCheckerException(final CommandSource commandSourceResult) {
+        this.commandSourceResult = commandSourceResult;
+    }
+
+    public CommandSource getCommandSourceResult() {
+        return this.commandSourceResult;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/exception/UnsupportedCommandException.java b/fineract-provider/src/main/java/org/apache/fineract/commands/exception/UnsupportedCommandException.java
new file mode 100644
index 0000000..010844f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/exception/UnsupportedCommandException.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.commands.exception;
+
+/**
+ * A {@link RuntimeException} that is thrown in the case where an invalid or
+ * unknown command is attempted to be processed by platform.
+ */
+public class UnsupportedCommandException extends RuntimeException {
+
+    private final String unsupportedCommandName;
+
+    public UnsupportedCommandException(final String unsupportedCommandName) {
+        this.unsupportedCommandName = unsupportedCommandName;
+    }
+
+    public String getUnsupportedCommandName() {
+        return this.unsupportedCommandName;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java b/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
new file mode 100644
index 0000000..a8e914d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
@@ -0,0 +1,27 @@
+/**
+ * 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.commands.handler;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface NewCommandSourceHandler {
+
+    CommandProcessingResult processCommand(JsonCommand command);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/provider/CommandHandlerProvider.java b/fineract-provider/src/main/java/org/apache/fineract/commands/provider/CommandHandlerProvider.java
new file mode 100644
index 0000000..a55d89b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/provider/CommandHandlerProvider.java
@@ -0,0 +1,107 @@
+/**
+ * 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.commands.provider;
+
+import com.google.common.base.Preconditions;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.exception.UnsupportedCommandException;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nonnull;
+import java.util.HashMap;
+
+/**
+ * {@link CommandHandlerProvider} provides {@link NewCommandSourceHandler}s for a given entity and action.<br/>
+ * <br/>
+ * A {@link NewCommandSourceHandler} can be registered and the annotation {@link CommandType} is used to determine
+ * the entity and the action the handler is capable to process.
+ *
+ * @author Markus Geiss
+ * @version 1.0
+ * @since 15.06
+ * @see NewCommandSourceHandler
+ * @see CommandType
+ */
+@Component
+@Scope("singleton")
+public class CommandHandlerProvider implements ApplicationContextAware {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CommandHandlerProvider.class);
+
+    private ApplicationContext applicationContext;
+    private HashMap<String, String> registeredHandlers;
+
+    CommandHandlerProvider() {
+        super();
+    }
+
+    /**
+     * Returns a handler for the given entity and action.<br/>
+     * <br/>
+     * Throws an {@link UnsupportedCommandException} if no handler
+     * for the given entity, action combination can be found.
+     * @param entity the entity to lookup the handler, must be given.
+     * @param action the action to lookup the handler, must be given.
+     */
+    @Nonnull
+    public NewCommandSourceHandler getHandler (@Nonnull final String entity, @Nonnull final String action) {
+        Preconditions.checkArgument(StringUtils.isNoneEmpty(entity), "An entity must be given!");
+        Preconditions.checkArgument(StringUtils.isNoneEmpty(action), "An action must be given!");
+
+        final String key =  entity + "|" + action;
+        if (!this.registeredHandlers.containsKey(key)) {
+            throw new UnsupportedCommandException(key);
+        }
+        return (NewCommandSourceHandler)this.applicationContext.getBean(this.registeredHandlers.get(key));
+    }
+
+    private void initializeHandlerRegistry() {
+        if (this.registeredHandlers == null) {
+            this.registeredHandlers = new HashMap<>();
+
+            final String[] commandHandlerBeans = this.applicationContext.getBeanNamesForAnnotation(CommandType.class);
+            if (ArrayUtils.isNotEmpty(commandHandlerBeans)) {
+                for (final String commandHandlerName : commandHandlerBeans) {
+                    LOGGER.info("Register command handler '" + commandHandlerName + "' ...");
+                    final CommandType commandType = this.applicationContext.findAnnotationOnBean(commandHandlerName, CommandType.class);
+                    try {
+                        this.registeredHandlers.put(commandType.entity() + "|" + commandType.action(), commandHandlerName);
+                    } catch (final Throwable th) {
+                        LOGGER.error("Unable to register command handler '" + commandHandlerName + "'!", th);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.applicationContext = applicationContext;
+        this.initializeHandlerRegistry();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformService.java
new file mode 100644
index 0000000..a6acad7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformService.java
@@ -0,0 +1,39 @@
+/**
+ * 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.commands.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.commands.data.AuditData;
+import org.apache.fineract.commands.data.AuditSearchData;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.service.Page;
+
+public interface AuditReadPlatformService {
+
+    Collection<AuditData> retrieveAuditEntries(String extraCriteria, boolean includeJson);
+
+    Page<AuditData> retrievePaginatedAuditEntries(String extraCriteria, boolean includeJson, PaginationParameters parameters);
+
+    Collection<AuditData> retrieveAllEntriesToBeChecked(String extraCriteria, boolean includeJson);
+
+    AuditData retrieveAuditEntry(Long auditId);
+
+    AuditSearchData retrieveSearchTemplate(String useType);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java
new file mode 100755
index 0000000..6b2e58c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java
@@ -0,0 +1,531 @@
+/**
+ * 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.commands.service;
+
+import java.lang.reflect.Type;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+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 org.apache.commons.lang.StringUtils;
+import org.apache.fineract.commands.data.AuditData;
+import org.apache.fineract.commands.data.AuditSearchData;
+import org.apache.fineract.commands.data.ProcessingResultLookup;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.DepositProductData;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
+import org.apache.fineract.useradministration.data.AppUserData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.service.AppUserReadPlatformService;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class AuditReadPlatformServiceImpl implements AuditReadPlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(AuditReadPlatformServiceImpl.class);
+    private final static Set<String> supportedOrderByValues = new HashSet<>(
+            Arrays.asList("id", "actionName", "entityName", "resourceId", "subresourceId", "madeOnDate", "checkedOnDate", "officeName",
+                    "groupName", "clientName", "loanAccountNo", "savingsAccountNo", "clientId", "loanId"));
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final AppUserReadPlatformService appUserReadPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final LoanProductReadPlatformService loanProductReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final PaginationHelper<AuditData> paginationHelper = new PaginationHelper<>();
+    private final PaginationParametersDataValidator paginationParametersDataValidator;
+    private final SavingsProductReadPlatformService savingsProductReadPlatformService;
+    private final DepositProductReadPlatformService depositProductReadPlatformService;
+
+    @Autowired
+    public AuditReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final FromJsonHelper fromApiJsonHelper, final AppUserReadPlatformService appUserReadPlatformService,
+            final OfficeReadPlatformService officeReadPlatformService, final ClientReadPlatformService clientReadPlatformService,
+            final LoanProductReadPlatformService loanProductReadPlatformService, final StaffReadPlatformService staffReadPlatformService,
+            final PaginationParametersDataValidator paginationParametersDataValidator,
+            final SavingsProductReadPlatformService savingsProductReadPlatformService,
+            final DepositProductReadPlatformService depositProductReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.appUserReadPlatformService = appUserReadPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.loanProductReadPlatformService = loanProductReadPlatformService;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.paginationParametersDataValidator = paginationParametersDataValidator;
+        this.savingsProductReadPlatformService = savingsProductReadPlatformService;
+        this.depositProductReadPlatformService = depositProductReadPlatformService;
+    }
+
+    private static final class AuditMapper implements RowMapper<AuditData> {
+
+        public String schema(final boolean includeJson, final String hierarchy) {
+
+            String commandAsJsonString = "";
+            if (includeJson) {
+                commandAsJsonString = ", aud.command_as_json as commandAsJson ";
+            }
+
+            String partSql = " aud.id as id, aud.action_name as actionName, aud.entity_name as entityName,"
+                    + " aud.resource_id as resourceId, aud.subresource_id as subresourceId,aud.client_id as clientId, aud.loan_id as loanId,"
+                    + " mk.username as maker, aud.made_on_date as madeOnDate, " + " aud.api_get_url as resourceGetUrl, "
+                    + "ck.username as checker, aud.checked_on_date as checkedOnDate, ev.enum_message_property as processingResult "
+                    + commandAsJsonString + ", "
+                    + " o.name as officeName, gl.level_name as groupLevelName, g.display_name as groupName, c.display_name as clientName, "
+                    + " l.account_no as loanAccountNo, s.account_no as savingsAccountNo " + " from m_portfolio_command_source aud "
+                    + " left join m_appuser mk on mk.id = aud.maker_id" + " left join m_appuser ck on ck.id = aud.checker_id"
+                    + " left join m_office o on o.id = aud.office_id" + " left join m_group g on g.id = aud.group_id"
+                    + " left join m_group_level gl on gl.id = g.level_id" + " left join m_client c on c.id = aud.client_id"
+                    + " left join m_loan l on l.id = aud.loan_id" + " left join m_savings_account s on s.id = aud.savings_account_id"
+                    + " left join r_enum_value ev on ev.enum_name = 'processing_result_enum' and ev.enum_id = aud.processing_result_enum";
+
+            // data scoping: head office (hierarchy = ".") can see all audit
+            // entries
+            if (!(hierarchy.equals("."))) {
+                partSql += " join m_office o2 on o2.id = aud.office_id and o2.hierarchy like '" + hierarchy + "%' ";
+            }
+
+            return partSql;
+        }
+
+        @Override
+        public AuditData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String actionName = rs.getString("actionName");
+            final String entityName = rs.getString("entityName");
+            final Long resourceId = JdbcSupport.getLong(rs, "resourceId");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final Long loanId = JdbcSupport.getLong(rs, "loanId");
+            final Long subresourceId = JdbcSupport.getLong(rs, "subresourceId");
+            final String maker = rs.getString("maker");
+            final DateTime madeOnDate = JdbcSupport.getDateTime(rs, "madeOnDate");
+            final String checker = rs.getString("checker");
+            final DateTime checkedOnDate = JdbcSupport.getDateTime(rs, "checkedOnDate");
+            final String processingResult = rs.getString("processingResult");
+            final String resourceGetUrl = rs.getString("resourceGetUrl");
+            String commandAsJson;
+            // commandAsJson might not be on the select list of columns
+            try {
+                commandAsJson = rs.getString("commandAsJson");
+            } catch (final SQLException e) {
+                commandAsJson = null;
+            }
+
+            final String officeName = rs.getString("officeName");
+            final String groupLevelName = rs.getString("groupLevelName");
+            final String groupName = rs.getString("groupName");
+            final String clientName = rs.getString("clientName");
+            final String loanAccountNo = rs.getString("loanAccountNo");
+            final String savingsAccountNo = rs.getString("savingsAccountNo");
+
+            return new AuditData(id, actionName, entityName, resourceId, subresourceId, maker, madeOnDate, checker, checkedOnDate,
+                    processingResult, commandAsJson, officeName, groupLevelName, groupName, clientName, loanAccountNo, savingsAccountNo,
+                    clientId, loanId, resourceGetUrl);
+        }
+    }
+
+    @Override
+    public Collection<AuditData> retrieveAuditEntries(final String extraCriteria, final boolean includeJson) {
+
+        String updatedExtraCriteria = "";
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            updatedExtraCriteria = " where (" + extraCriteria + ")";
+        }
+
+        updatedExtraCriteria += " order by aud.id DESC limit " + PaginationParameters.getCheckedLimit(null);
+        return retrieveEntries("audit", updatedExtraCriteria, includeJson);
+    }
+
+    @Override
+    public Page<AuditData> retrievePaginatedAuditEntries(final String extraCriteria, final boolean includeJson,
+            final PaginationParameters parameters) {
+
+        this.paginationParametersDataValidator.validateParameterValues(parameters, supportedOrderByValues, "audits");
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+
+        String updatedExtraCriteria = "";
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            updatedExtraCriteria = " where (" + extraCriteria + ")";
+        }
+
+        final AuditMapper rm = new AuditMapper();
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(rm.schema(includeJson, hierarchy));
+        sqlBuilder.append(' ').append(updatedExtraCriteria);
+
+        if (parameters.isOrderByRequested()) {
+            sqlBuilder.append(' ').append(parameters.orderBySql());
+        } else {
+            sqlBuilder.append(' ').append(' ').append(" order by aud.id DESC");
+        }
+
+        if (parameters.isLimited()) {
+            sqlBuilder.append(' ').append(parameters.limitSql());
+        }
+
+        logger.info("sql: " + sqlBuilder.toString());
+
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), new Object[] {}, rm);
+    }
+
+    @Override
+    public Collection<AuditData> retrieveAllEntriesToBeChecked(final String extraCriteria, final boolean includeJson) {
+
+        String updatedExtraCriteria = "";
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            updatedExtraCriteria = " where (" + extraCriteria + ")" + " and aud.processing_result_enum = 2";
+        } else {
+            updatedExtraCriteria = " where aud.processing_result_enum = 2";
+        }
+
+        updatedExtraCriteria += " order by aud.id";
+
+        return retrieveEntries("makerchecker", updatedExtraCriteria, includeJson);
+    }
+
+    public Collection<AuditData> retrieveEntries(final String useType, final String extraCriteria, final boolean includeJson) {
+
+        if (!(useType.equals("audit") || useType.equals("makerchecker"))) { throw new PlatformDataIntegrityException(
+                "error.msg.invalid.auditSearchTemplate.useType", "Invalid Audit Search Template UseType: " + useType); }
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+
+        final AuditMapper rm = new AuditMapper();
+        String sql = "select " + rm.schema(includeJson, hierarchy);
+
+        Boolean isLimitedChecker = false;
+        if (useType.equals("makerchecker")) {
+            if (currentUser.hasNotPermissionForAnyOf("ALL_FUNCTIONS", "CHECKER_SUPER_USER")) {
+                isLimitedChecker = true;
+            }
+        }
+
+        if (isLimitedChecker) {
+            sql += " join m_permission p on REPLACE(p.action_name, '_CHECKER', '')  = aud.action_name and p.entity_name = aud.entity_name and p.code like '%\\_CHECKER'"
+                    + " join m_role_permission rp on rp.permission_id = p.id" + " join m_role r on r.id = rp.role_id "
+                    + " join m_appuser_role ur on ur.role_id = r.id and ur.appuser_id = " + currentUser.getId();
+        }
+        sql += extraCriteria;
+
+        logger.info("sql: " + sql);
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public AuditData retrieveAuditEntry(final Long auditId) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+
+        final AuditMapper rm = new AuditMapper();
+
+        final String sql = "select " + rm.schema(true, hierarchy) + " where aud.id = " + auditId;
+
+        final AuditData auditResult = this.jdbcTemplate.queryForObject(sql, rm, new Object[] {});
+
+        return replaceIdsOnAuditData(auditResult);
+    }
+
+    private AuditData replaceIdsOnAuditData(final AuditData auditResult) {
+
+        final String auditAsJson = auditResult.getCommandAsJson();
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Map<String, Object> commandAsJsonMap = this.fromApiJsonHelper.extractObjectMap(typeOfMap, auditAsJson);
+        final JsonElement auditJsonFragment = this.fromApiJsonHelper.parse(auditAsJson);
+        final JsonObject auditObject = auditJsonFragment.getAsJsonObject();
+
+        if (commandAsJsonMap.containsKey("officeId")) {
+            commandAsJsonMap.remove("officeId");
+
+            Long officeId = null;
+            final String officeIdStr = auditObject.get("officeId").getAsString();
+            if (StringUtils.isNotBlank(officeIdStr)) {
+                officeId = Long.valueOf(officeIdStr);
+                final OfficeData office = this.officeReadPlatformService.retrieveOffice(officeId);
+                commandAsJsonMap.put("officeName", office.name());
+            } else {
+                commandAsJsonMap.put("officeName", "");
+            }
+        }
+
+        if (commandAsJsonMap.containsKey("clientId")) {
+            commandAsJsonMap.remove("clientId");
+
+            Long clientId = null;
+            final String clientIdStr = auditObject.get("clientId").getAsString();
+            if (StringUtils.isNotBlank(clientIdStr)) {
+                clientId = Long.valueOf(clientIdStr);
+                final ClientData client = this.clientReadPlatformService.retrieveOne(clientId);
+                commandAsJsonMap.put("clientName", client.displayName());
+            } else {
+                commandAsJsonMap.put("clientName", "");
+            }
+        }
+
+        if (commandAsJsonMap.containsKey("productId")) {
+            commandAsJsonMap.remove("productId");
+
+            Long productId = null;
+            final String productIdStr = auditObject.get("productId").getAsString();
+            if (StringUtils.isNotBlank(productIdStr)) {
+                productId = Long.valueOf(productIdStr);
+                if (auditResult.getEntityName().equalsIgnoreCase("LOAN")) {
+                    final LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
+                    commandAsJsonMap.put("productName", loanProduct.getName());
+                } else if (auditResult.getEntityName().equalsIgnoreCase("SAVINGSACCOUNT")) {
+                    final SavingsProductData savingProduct = this.savingsProductReadPlatformService.retrieveOne(productId);
+                    commandAsJsonMap.put("productName", savingProduct.getName());
+                } else if (auditResult.getEntityName().equalsIgnoreCase("RECURRINGDEPOSITACCOUNT")) {
+                    final DepositProductData depositProduct = this.depositProductReadPlatformService
+                            .retrieveOne(DepositAccountType.RECURRING_DEPOSIT, productId);
+                    commandAsJsonMap.put("productName", depositProduct.getName());
+                } else if (auditResult.getEntityName().equalsIgnoreCase("FIXEDDEPOSITACCOUNT")) {
+                    final DepositProductData depositProduct = this.depositProductReadPlatformService
+                            .retrieveOne(DepositAccountType.FIXED_DEPOSIT, productId);
+                    commandAsJsonMap.put("productName", depositProduct.getName());
+                } else {
+                    commandAsJsonMap.put("productName", "");
+                }
+            } else {
+                commandAsJsonMap.put("productName", "");
+            }
+        }
+
+        if (commandAsJsonMap.containsKey("loanOfficerId") || commandAsJsonMap.containsKey("fieldOfficerId")
+                || commandAsJsonMap.containsKey("staffId")) {
+            String staffIdStr = "";
+            String staffNameParamName = "";
+
+            if (commandAsJsonMap.containsKey("loanOfficerId")) {
+                commandAsJsonMap.remove("loanOfficerId");
+                staffIdStr = auditObject.get("loanOfficerId").getAsString();
+                staffNameParamName = "loanOfficerName";
+            } else if (commandAsJsonMap.containsKey("fieldOfficerId")) {
+                commandAsJsonMap.remove("fieldOfficerId");
+                staffIdStr = auditObject.get("fieldOfficerId").getAsString();
+                staffNameParamName = "fieldOfficerName";
+            } else if (commandAsJsonMap.containsKey("staffId")) {
+                commandAsJsonMap.remove("staffId");
+                staffIdStr = auditObject.get("staffId").getAsString();
+                staffNameParamName = "staffName";
+            }
+
+            replaceStaffIdWithStaffName(staffIdStr, staffNameParamName, commandAsJsonMap);
+
+        }
+
+        updateEnumerations(commandAsJsonMap, auditObject, auditResult.getEntityName());
+
+        final String newAuditAsJson = this.fromApiJsonHelper.toJson(commandAsJsonMap);
+        auditResult.setCommandAsJson(newAuditAsJson);
+
+        return auditResult;
+    }
+
+    private void updateEnumerations(Map<String, Object> commandAsJsonMap, JsonObject auditObject, String entityName) {
+
+        if (entityName.equalsIgnoreCase("LOAN") || entityName.equalsIgnoreCase("LOANPRODUCT")) {
+
+            final String[] enumTypes = { "loanTermFrequencyType", "termFrequencyType", "repaymentFrequencyType", "amortizationType",
+                    "interestType", "interestCalculationPeriodType", "interestRateFrequencyType", "accountingRule" };
+
+            for (final String typeName : enumTypes) {
+                if (commandAsJsonMap.containsKey(typeName)) {
+                    commandAsJsonMap.remove(typeName);
+
+                    final Integer enumTypeId = auditObject.get(typeName).getAsInt();
+                    final String code = LoanEnumerations.loanEnumueration(typeName, enumTypeId).getValue();
+                    if (code != null) {
+                        commandAsJsonMap.put(typeName, code);
+                    }
+                }
+            }
+
+        } else if (entityName.equalsIgnoreCase("SAVINGSPRODUCT") || entityName.equalsIgnoreCase("SAVINGSACCOUNT")
+                || entityName.equalsIgnoreCase("RECURRINGDEPOSITPRODUCT") || entityName.equalsIgnoreCase("RECURRINGDEPOSITACCOUNT")
+                || entityName.equalsIgnoreCase("FIXEDDEPOSITPRODUCT") || entityName.equalsIgnoreCase("FIXEDDEPOSITACCOUNT")) {
+
+            final String[] enumTypes = { "interestCompoundingPeriodType", "interestPostingPeriodType", "interestCalculationType",
+                    "lockinPeriodFrequencyType", "minDepositTermTypeId", "maxDepositTermTypeId", "inMultiplesOfDepositTermTypeId",
+                    "depositPeriodFrequencyId", "accountingRule", "interestCalculationDaysInYearType", "preClosurePenalInterestOnTypeId",
+                    "recurringFrequencyType" };
+
+            for (final String typeName : enumTypes) {
+                if (commandAsJsonMap.containsKey(typeName)) {
+                    commandAsJsonMap.remove(typeName);
+
+                    final Integer enumTypeId = auditObject.get(typeName).getAsInt();
+                    final String code = SavingsEnumerations.savingEnumueration(typeName, enumTypeId).getValue();
+                    if (code != null) {
+                        commandAsJsonMap.put(typeName, code);
+                    }
+                }
+            }
+        }
+    }
+
+    private void replaceStaffIdWithStaffName(final String staffIdStr, final String staffNameParamName,
+            Map<String, Object> commandAsJsonMap) {
+
+        Long staffId = null;
+        if (StringUtils.isNotBlank(staffIdStr)) {
+            staffId = Long.valueOf(staffIdStr);
+            final StaffData officer = this.staffReadPlatformService.retrieveStaff(staffId);
+            commandAsJsonMap.put(staffNameParamName, officer.getDisplayName());
+        } else {
+            commandAsJsonMap.put(staffNameParamName, "");
+        }
+    }
+
+    @Override
+    public AuditSearchData retrieveSearchTemplate(final String useType) {
+
+        if (!(useType.equals("audit") || useType.equals("makerchecker"))) { throw new PlatformDataIntegrityException(
+                "error.msg.invalid.auditSearchTemplate.useType", "Invalid Audit Search Template UseType: " + useType); }
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        final Collection<AppUserData> appUsers = this.appUserReadPlatformService.retrieveSearchTemplate();
+
+        String sql = " SELECT distinct(action_name) as actionName FROM m_permission p ";
+        sql += makercheckerCapabilityOnly(useType, currentUser);
+        sql += " order by if(action_name in ('CREATE', 'DELETE', 'UPDATE'), action_name, 'ZZZ'), action_name";
+        final ActionNamesMapper mapper = new ActionNamesMapper();
+        final List<String> actionNames = this.jdbcTemplate.query(sql, mapper, new Object[] {});
+
+        sql = " select distinct(entity_name) as entityName from m_permission p ";
+        sql += makercheckerCapabilityOnly(useType, currentUser);
+        sql += " order by if(grouping = 'datatable', 'ZZZ', entity_name), entity_name";
+        final EntityNamesMapper mapper2 = new EntityNamesMapper();
+        final List<String> entityNames = this.jdbcTemplate.query(sql, mapper2, new Object[] {});
+
+        Collection<ProcessingResultLookup> processingResults = null;
+        if (useType.equals("audit")) {
+            final ProcessingResultsMapper mapper3 = new ProcessingResultsMapper();
+            processingResults = this.jdbcTemplate.query(mapper3.schema(), mapper3, new Object[] {});
+        }
+
+        return new AuditSearchData(appUsers, actionNames, entityNames, processingResults);
+    }
+
+    private String makercheckerCapabilityOnly(final String useType, final AppUser currentUser) {
+        String sql = "";
+        Boolean isLimitedChecker = false;
+        if (useType.equals("makerchecker")) {
+            if (currentUser.hasNotPermissionForAnyOf("ALL_FUNCTIONS", "CHECKER_SUPER_USER")) {
+                isLimitedChecker = true;
+            }
+        }
+
+        if (isLimitedChecker) {
+            sql += " join m_role_permission rp on rp.permission_id = p.id" + " join m_role r on r.id = rp.role_id "
+                    + " join m_appuser_role ur on ur.role_id = r.id and ur.appuser_id = " + currentUser.getId();
+
+        }
+        sql += " where p.action_name is not null and p.action_name <> 'READ' ";
+        if (isLimitedChecker) {
+            sql += "and p.code like '%\\_CHECKER'";
+        }
+        return sql;
+    }
+
+    private static final class ActionNamesMapper implements RowMapper<String> {
+
+        @Override
+        public String mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            return rs.getString("actionName");
+        }
+
+    }
+
+    private static final class EntityNamesMapper implements RowMapper<String> {
+
+        @Override
+        public String mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            return rs.getString("entityName");
+        }
+
+    }
+
+    private static final class ProcessingResultsMapper implements RowMapper<ProcessingResultLookup> {
+
+        @Override
+        public ProcessingResultLookup mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String processingResult = rs.getString("processingResult");
+
+            return new ProcessingResultLookup(id, processingResult);
+        }
+
+        public String schema() {
+            return " select enum_id as id, enum_message_property as processingResult from r_enum_value where enum_name = 'processing_result_enum' "
+                    + " order by enum_id";
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandProcessingService.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandProcessingService.java
new file mode 100644
index 0000000..01a7a72
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandProcessingService.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.commands.service;
+
+import org.apache.fineract.commands.domain.CommandSource;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+public interface CommandProcessingService {
+
+    CommandProcessingResult processAndLogCommand(CommandWrapper wrapper, JsonCommand command, boolean isApprovedByChecker);
+
+    CommandProcessingResult logCommand(CommandSource commandSourceResult);
+    
+    boolean validateCommand(final CommandWrapper commandWrapper,final AppUser user);
+
+}
\ 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
new file mode 100755
index 0000000..d532049
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -0,0 +1,2619 @@
+/**
+ * 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.commands.service;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatConstants;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceConstants;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.useradministration.api.PasswordPreferencesApiConstants;
+
+public class CommandWrapperBuilder {
+
+    private Long officeId;
+    private Long groupId;
+    private Long clientId;
+    private Long loanId;
+    private Long savingsId;
+    private String actionName;
+    private String entityName;
+    private Long entityId;
+    private Long subentityId;
+    private String href;
+    private String json = "{}";
+    private String transactionId;
+    private Long productId;
+    private Long templateId;
+
+    public CommandWrapper build() {
+        return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName,
+                this.entityName, this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId);
+    }
+
+    public CommandWrapperBuilder withLoanId(final Long withLoanId) {
+        this.loanId = withLoanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder withSavingsId(final Long withSavingsId) {
+        this.savingsId = withSavingsId;
+        return this;
+    }
+
+    public CommandWrapperBuilder withClientId(final Long withClientId) {
+        this.clientId = withClientId;
+        return this;
+    }
+
+    public CommandWrapperBuilder withGroupId(final Long withGroupId) {
+        this.groupId = withGroupId;
+        return this;
+    }
+
+    public CommandWrapperBuilder withEntityName(final String withEntityName) {
+        this.entityName = withEntityName;
+        return this;
+    }
+
+    public CommandWrapperBuilder withSubEntityId(final Long withSubEntityId) {
+        this.subentityId = withSubEntityId;
+        return this;
+    }
+
+    public CommandWrapperBuilder withJson(final String withJson) {
+        this.json = withJson;
+        return this;
+    }
+
+    public CommandWrapperBuilder withNoJsonBody() {
+        this.json = null;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateGlobalConfiguration(final Long configId) {
+        this.actionName = "UPDATE";
+        this.entityName = "CONFIGURATION";
+        this.entityId = configId;
+
+        this.href = "/configurations/" + configId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updatePermissions() {
+        this.actionName = "UPDATE";
+        this.entityName = "PERMISSION";
+        this.entityId = null;
+        this.href = "/permissions";
+        return this;
+    }
+
+    public CommandWrapperBuilder createRole() {
+        this.actionName = "CREATE";
+        this.entityName = "ROLE";
+        this.href = "/roles/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateRole(final Long roleId) {
+        this.actionName = "UPDATE";
+        this.entityName = "ROLE";
+        this.entityId = roleId;
+        this.href = "/roles/" + roleId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateRolePermissions(final Long roleId) {
+        this.actionName = "PERMISSIONS";
+        this.entityName = "ROLE";
+        this.entityId = roleId;
+        this.href = "/roles/" + roleId + "/permissions";
+        return this;
+    }
+
+    public CommandWrapperBuilder createUser() {
+        this.actionName = "CREATE";
+        this.entityName = "USER";
+        this.entityId = null;
+        this.href = "/users/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateUser(final Long userId) {
+        this.actionName = "UPDATE";
+        this.entityName = "USER";
+        this.entityId = userId;
+        this.href = "/users/" + userId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteUser(final Long userId) {
+        this.actionName = "DELETE";
+        this.entityName = "USER";
+        this.entityId = userId;
+        this.href = "/users/" + userId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createOffice() {
+        this.actionName = "CREATE";
+        this.entityName = "OFFICE";
+        this.entityId = null;
+        this.href = "/offices/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateOffice(final Long officeId) {
+        this.actionName = "UPDATE";
+        this.entityName = "OFFICE";
+        this.entityId = officeId;
+        this.href = "/offices/" + officeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createOfficeTransaction() {
+        this.actionName = "CREATE";
+        this.entityName = "OFFICETRANSACTION";
+        this.href = "/officetransactions/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteOfficeTransaction(final Long transactionId) {
+        this.actionName = "DELETE";
+        this.entityName = "OFFICETRANSACTION";
+        this.entityId = transactionId;
+        this.href = "/officetransactions/" + transactionId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createStaff() {
+        this.actionName = "CREATE";
+        this.entityName = "STAFF";
+        this.entityId = null;
+        this.href = "/staff/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateStaff(final Long staffId) {
+        this.actionName = "UPDATE";
+        this.entityName = "STAFF";
+        this.entityId = staffId;
+        this.href = "/staff/" + staffId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createGuarantor(final Long loanId) {
+        this.actionName = "CREATE";
+        this.entityName = "GUARANTOR";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/guarantors";
+        return this;
+    }
+
+    public CommandWrapperBuilder recoverFromGuarantor(final Long loanId) {
+        this.actionName = "RECOVERGUARANTEES";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "?command=recoverGuarantees";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateGuarantor(final Long loanId, final Long guarantorId) {
+        this.actionName = "UPDATE";
+        this.entityName = "GUARANTOR";
+        this.entityId = guarantorId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/guarantors/" + guarantorId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteGuarantor(final Long loanId, final Long guarantorId, final Long guarantorFundingId) {
+        this.actionName = "DELETE";
+        this.entityName = "GUARANTOR";
+        this.entityId = guarantorId;
+        this.subentityId = guarantorFundingId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/guarantors/" + guarantorId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createFund() {
+        this.actionName = "CREATE";
+        this.entityName = "FUND";
+        this.entityId = null;
+        this.href = "/funds/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateFund(final Long fundId) {
+        this.actionName = "UPDATE";
+        this.entityName = "FUND";
+        this.entityId = fundId;
+        this.href = "/funds/" + fundId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createReport() {
+        this.actionName = "CREATE";
+        this.entityName = "REPORT";
+        this.entityId = null;
+        this.href = "/reports/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateReport(final Long id) {
+        this.actionName = "UPDATE";
+        this.entityName = "REPORT";
+        this.entityId = id;
+        this.href = "/reports/" + id;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteReport(final Long id) {
+        this.actionName = "DELETE";
+        this.entityName = "REPORT";
+        this.entityId = id;
+        this.href = "/reports/" + id;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateCurrencies() {
+        this.actionName = "UPDATE";
+        this.entityName = "CURRENCY";
+        this.href = "/currencies";
+        return this;
+    }
+
+    public CommandWrapperBuilder createSms() {
+        this.actionName = "CREATE";
+        this.entityName = "SMS";
+        this.entityId = null;
+        this.href = "/sms/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateSms(final Long resourceId) {
+        this.actionName = "UPDATE";
+        this.entityName = "SMS";
+        this.entityId = resourceId;
+        this.href = "/sms/" + resourceId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteSms(final Long resourceId) {
+        this.actionName = "DELETE";
+        this.entityName = "SMS";
+        this.entityId = resourceId;
+        this.href = "/sms/" + resourceId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createCode() {
+        this.actionName = "CREATE";
+        this.entityName = "CODE";
+        this.entityId = null;
+        this.href = "/codes/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateCode(final Long codeId) {
+        this.actionName = "UPDATE";
+        this.entityName = "CODE";
+        this.entityId = codeId;
+        this.href = "/codes/" + codeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteCode(final Long codeId) {
+        this.actionName = "DELETE";
+        this.entityName = "CODE";
+        this.entityId = codeId;
+        this.href = "/codes/" + codeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createHook() {
+        this.actionName = "CREATE";
+        this.entityName = "HOOK";
+        this.entityId = null;
+        this.href = "/hooks/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateHook(final Long hookId) {
+        this.actionName = "UPDATE";
+        this.entityName = "HOOK";
+        this.entityId = hookId;
+        this.href = "/hooks/" + hookId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteHook(final Long hookId) {
+        this.actionName = "DELETE";
+        this.entityName = "HOOK";
+        this.entityId = hookId;
+        this.href = "/hooks/" + hookId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createCharge() {
+        this.actionName = "CREATE";
+        this.entityName = "CHARGE";
+        this.entityId = null;
+        this.href = "/charges/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateCharge(final Long chargeId) {
+        this.actionName = "UPDATE";
+        this.entityName = "CHARGE";
+        this.entityId = chargeId;
+        this.href = "/charges/" + chargeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteCharge(final Long chargeId) {
+        this.actionName = "DELETE";
+        this.entityName = "CHARGE";
+        this.entityId = chargeId;
+        this.href = "/charges/" + chargeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createLoanProduct() {
+        this.actionName = "CREATE";
+        this.entityName = "LOANPRODUCT";
+        this.entityId = null;
+        this.href = "/loanproducts/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateLoanProduct(final Long productId) {
+        this.actionName = "UPDATE";
+        this.entityName = "LOANPRODUCT";
+        this.entityId = productId;
+        this.href = "/loanproducts/" + productId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createClientIdentifier(final Long clientId) {
+        this.actionName = "CREATE";
+        this.entityName = "CLIENTIDENTIFIER";
+        this.entityId = null;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "/identifiers/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateClientIdentifier(final Long clientId, final Long clientIdentifierId) {
+        this.actionName = "UPDATE";
+        this.entityName = "CLIENTIDENTIFIER";
+        this.entityId = clientIdentifierId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "/identifiers/" + clientIdentifierId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteClientIdentifier(final Long clientId, final Long clientIdentifierId) {
+        this.actionName = "DELETE";
+        this.entityName = "CLIENTIDENTIFIER";
+        this.entityId = clientIdentifierId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "/identifiers/" + clientIdentifierId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createClient() {
+        this.actionName = "CREATE";
+        this.entityName = "CLIENT";
+        this.href = "/clients/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder activateClient(final Long clientId) {
+        this.actionName = "ACTIVATE";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "?command=activate&template=true";
+        return this;
+    }
+
+    public CommandWrapperBuilder closeClient(final Long clientId) {
+        this.actionName = "CLOSE";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "?command=close&template=true";
+        return this;
+    }
+
+    public CommandWrapperBuilder rejectClient(final Long clientId) {
+        this.actionName = "REJECT";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "?command=reject&template=true";
+        return this;
+    }
+
+    public CommandWrapperBuilder withdrawClient(final Long clientId) {
+        this.actionName = "WITHDRAW";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "?command=withdraw&template=true";
+        return this;
+    }
+
+    public CommandWrapperBuilder reActivateClient(final Long clientId) {
+        this.actionName = "REACTIVATE";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "?command=reactivate&template=true";
+        return this;
+    }
+
+    public CommandWrapperBuilder proposeClientTransfer(final Long clientId) {
+        this.actionName = "PROPOSETRANSFER";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clientId/" + clientId + "?command=proposeTransfer";
+        return this;
+    }
+
+    public CommandWrapperBuilder proposeAndAcceptClientTransfer(final Long clientId) {
+        this.actionName = "PROPOSEANDACCEPTTRANSFER";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clientId/" + clientId + "?command=proposeAndAcceptTransfer";
+        return this;
+    }
+
+    public CommandWrapperBuilder withdrawClientTransferRequest(final Long clientId) {
+        this.actionName = "WITHDRAWTRANSFER";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clientId/" + clientId + "?command=withdrawTransfer";
+        return this;
+    }
+
+    public CommandWrapperBuilder acceptClientTransfer(final Long clientId) {
+        this.actionName = "ACCEPTTRANSFER";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clientId/" + clientId + "?command=acceptTransfer";
+        return this;
+    }
+
+    public CommandWrapperBuilder rejectClientTransfer(final Long clientId) {
+        this.actionName = "REJECTTRANSFER";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clientId/" + clientId + "?command=rejectTransfer";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateClient(final Long clientId) {
+        this.actionName = "UPDATE";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteClient(final Long clientId) {
+        this.actionName = "DELETE";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId;
+        this.json = "{}";
+        return this;
+    }
+
+    public CommandWrapperBuilder createDBDatatable(final String json) {
+        this.actionName = "CREATE";
+        this.entityName = "DATATABLE";
+        this.entityId = null;
+        this.href = "/datatables/";
+        this.json = json;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateDBDatatable(final String datatable, final String json) {
+        this.actionName = "UPDATE";
+        this.entityName = "DATATABLE";
+        this.entityId = null;
+        this.href = "/datatables/" + datatable;
+        this.json = json;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteDBDatatable(final String datatable, final String json) {
+        this.actionName = "DELETE";
+        this.entityName = "DATATABLE";
+        this.entityId = null;
+        this.href = "/datatables/" + datatable;
+        this.json = json;
+        return this;
+    }
+
+    public CommandWrapperBuilder createDatatable(final String datatable, final Long apptableId, final Long datatableId) {
+        this.actionName = "CREATE";
+        commonDatatableSettings(datatable, apptableId, datatableId);
+        return this;
+    }
+
+    public CommandWrapperBuilder updateDatatable(final String datatable, final Long apptableId, final Long datatableId) {
+        this.actionName = "UPDATE";
+        commonDatatableSettings(datatable, apptableId, datatableId);
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteDatatable(final String datatable, final Long apptableId, final Long datatableId) {
+        this.actionName = "DELETE";
+        commonDatatableSettings(datatable, apptableId, datatableId);
+        return this;
+    }
+
+    private void commonDatatableSettings(final String datatable, final Long apptableId, final Long datatableId) {
+
+        this.entityName = datatable;
+        this.entityId = apptableId;
+        this.subentityId = datatableId;
+        if (datatableId == null) {
+            this.href = "/datatables/" + datatable + "/" + apptableId;
+        } else {
+            this.href = "/datatables/" + datatable + "/" + apptableId + "/" + datatableId;
+        }
+    }
+
+    public CommandWrapperBuilder createLoanCharge(final Long loanId) {
+        this.actionName = "CREATE";
+        this.entityName = "LOANCHARGE";
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/charges";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateLoanCharge(final Long loanId, final Long loanChargeId) {
+        this.actionName = "UPDATE";
+        this.entityName = "LOANCHARGE";
+        this.entityId = loanChargeId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/charges/" + loanChargeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder waiveLoanCharge(final Long loanId, final Long loanChargeId) {
+        this.actionName = "WAIVE";
+        this.entityName = "LOANCHARGE";
+        this.entityId = loanChargeId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/charges/" + loanChargeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder payLoanCharge(final Long loanId, final Long loanChargeId) {
+        this.actionName = "PAY";
+        this.entityName = "LOANCHARGE";
+        this.entityId = loanChargeId;
+        this.loanId = loanId;
+        if (loanChargeId == null) {
+            this.href = "/loans/" + loanId;
+        } else {
+            this.href = "/loans/" + loanId + "/charges/" + loanChargeId;
+        }
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteLoanCharge(final Long loanId, final Long loanChargeId) {
+        this.actionName = "DELETE";
+        this.entityName = "LOANCHARGE";
+        this.entityId = loanChargeId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/charges/" + loanChargeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder loanRepaymentTransaction(final Long loanId) {
+        this.actionName = "REPAYMENT";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/template?command=repayment";
+        return this;
+    }
+
+    public CommandWrapperBuilder loanRecoveryPaymentTransaction(final Long loanId) {
+        this.actionName = "RECOVERYPAYMENT";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/template?command=recoverypayment";
+        return this;
+    }
+
+    public CommandWrapperBuilder waiveInterestPortionTransaction(final Long loanId) {
+        this.actionName = "WAIVEINTERESTPORTION";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/template?command=waiveinterest";
+        return this;
+    }
+
+    public CommandWrapperBuilder writeOffLoanTransaction(final Long loanId) {
+        this.actionName = "WRITEOFF";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/template?command=writeoff";
+        return this;
+    }
+
+    public CommandWrapperBuilder undoWriteOffLoanTransaction(final Long loanId) {
+        this.actionName = "UNDOWRITEOFF";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/template?command=undowriteoff";
+        return this;
+    }
+
+    public CommandWrapperBuilder closeLoanAsRescheduledTransaction(final Long loanId) {
+        this.actionName = "CLOSEASRESCHEDULED";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/template?command=close-rescheduled";
+        return this;
+    }
+
+    public CommandWrapperBuilder closeLoanTransaction(final Long loanId) {
+        this.actionName = "CLOSE";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/template?command=close";
+        return this;
+    }
+
+    public CommandWrapperBuilder adjustTransaction(final Long loanId, final Long transactionId) {
+        this.actionName = "ADJUST";
+        this.entityName = "LOAN";
+        this.entityId = transactionId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/" + transactionId;
+        return this;
+    }
+
+    public CommandWrapperBuilder refundLoanTransactionByCash(final Long loanId) {
+        this.actionName = "REFUNDBYCASH";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/template?command=refundbycash";
+        return this;
+    }
+
+    public CommandWrapperBuilder createLoanApplication() {
+        this.actionName = "CREATE";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = null;
+        this.href = "/loans";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateLoanApplication(final Long loanId) {
+        this.actionName = "UPDATE";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateDisbusementDate(final Long loanId, final Long disbursementId) {
+        this.actionName = "UPDATE";
+        this.entityName = "DISBURSEMENTDETAIL";
+        this.entityId = disbursementId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/disbursementdetail/" + disbursementId;
+        return this;
+    }
+
+    public CommandWrapperBuilder addAndDeleteDisbursementDetails(final Long loanId) {
+        this.actionName = "UPDATE";
+        this.entityName = "DISBURSEMENTDETAIL";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/editdisbursementdetails/";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteLoanApplication(final Long loanId) {
+        this.actionName = "DELETE";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder rejectLoanApplication(final Long loanId) {
+        this.actionName = "REJECT";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder withdrawLoanApplication(final Long loanId) {
+        this.actionName = "WITHDRAW";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder approveLoanApplication(final Long loanId) {
+        this.actionName = "APPROVE";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder disburseLoanApplication(final Long loanId) {
+        this.actionName = "DISBURSE";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder disburseLoanToSavingsApplication(final Long loanId) {
+        this.actionName = "DISBURSETOSAVINGS";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder undoLoanApplicationApproval(final Long loanId) {
+        this.actionName = "APPROVALUNDO";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder undoLoanApplicationDisbursal(final Long loanId) {
+        this.actionName = "DISBURSALUNDO";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+    
+    public CommandWrapperBuilder undoLastDisbursalLoanApplication(final Long loanId) {
+        this.actionName = "DISBURSALLASTUNDO";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder assignLoanOfficer(final Long loanId) {
+        this.actionName = "UPDATELOANOFFICER";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder unassignLoanOfficer(final Long loanId) {
+        this.actionName = "REMOVELOANOFFICER";
+        this.entityName = "LOAN";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder assignLoanOfficersInBulk() {
+        this.actionName = "BULKREASSIGN";
+        this.entityName = "LOAN";
+        this.href = "/loans/loanreassignment";
+        return this;
+    }
+
+    public CommandWrapperBuilder createCodeValue(final Long codeId) {
+        this.actionName = "CREATE";
+        this.entityName = "CODEVALUE";
+        this.entityId = codeId;
+        this.href = "/codes/" + codeId + "/codevalues/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateCodeValue(final Long codeId, final Long codeValueId) {
+        this.actionName = "UPDATE";
+        this.entityName = "CODEVALUE";
+        this.subentityId = codeValueId;
+        this.entityId = codeId;
+        this.href = "/codes/" + codeId + "/codevalues/" + codeValueId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteCodeValue(final Long codeId, final Long codeValueId) {
+        this.actionName = "DELETE";
+        this.entityName = "CODEVALUE";
+        this.subentityId = codeValueId;
+        this.entityId = codeId;
+        this.href = "/codes/" + codeId + "/codevalues/" + codeValueId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createGLClosure() {
+        this.actionName = "CREATE";
+        this.entityName = "GLCLOSURE";
+        this.entityId = null;
+        this.href = "/glclosures/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateGLClosure(final Long glClosureId) {
+        this.actionName = "UPDATE";
+        this.entityName = "GLCLOSURE";
+        this.entityId = glClosureId;
+        this.href = "/glclosures/" + glClosureId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteGLClosure(final Long glClosureId) {
+        this.actionName = "DELETE";
+        this.entityName = "GLCLOSURE";
+        this.entityId = glClosureId;
+        this.href = "/glclosures/" + glClosureId;
+        return this;
+    }
+
+    public CommandWrapperBuilder excuteAccrualAccounting() {
+        this.actionName = "EXECUTE";
+        this.entityName = "PERIODICACCRUALACCOUNTING";
+        this.entityId = null;
+        this.href = "/accrualaccounting";
+        return this;
+    }
+
+    public CommandWrapperBuilder createGLAccount() {
+        this.actionName = "CREATE";
+        this.entityName = "GLACCOUNT";
+        this.entityId = null;
+        this.href = "/glaccounts/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateGLAccount(final Long glAccountId) {
+        this.actionName = "UPDATE";
+        this.entityName = "GLACCOUNT";
+        this.entityId = glAccountId;
+        this.href = "/glaccounts/" + glAccountId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteGLAccount(final Long glAccountId) {
+        this.actionName = "DELETE";
+        this.entityName = "GLACCOUNT";
+        this.entityId = glAccountId;
+        this.href = "/glaccounts/" + glAccountId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createJournalEntry() {
+        this.actionName = "CREATE";
+        this.entityName = "JOURNALENTRY";
+        this.entityId = null;
+        this.href = "/journalentries/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder reverseJournalEntry(final String transactionId) {
+        this.actionName = "REVERSE";
+        this.entityName = "JOURNALENTRY";
+        this.entityId = null;
+        this.transactionId = transactionId;
+        this.href = "/journalentries/" + transactionId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateRunningBalanceForJournalEntry() {
+        this.actionName = "UPDATERUNNINGBALANCE";
+        this.entityName = "JOURNALENTRY";
+        this.entityId = null;
+        this.href = "/journalentries/update";
+        return this;
+    }
+
+    public CommandWrapperBuilder defineOpeningBalanceForJournalEntry() {
+        this.actionName = "DEFINEOPENINGBALANCE";
+        this.entityName = "JOURNALENTRY";
+        this.entityId = null;
+        this.href = "/journalentries/update";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateOpeningBalanceForJournalEntry() {
+        this.actionName = "UPDATEOPENINGBALANCE";
+        this.entityName = "JOURNALENTRY";
+        this.entityId = null;
+        this.href = "/journalentries/update";
+        return this;
+    }
+
+    public CommandWrapperBuilder createSavingProduct() {
+        this.actionName = "CREATE";
+        this.entityName = "SAVINGSPRODUCT";
+        this.entityId = null;
+        this.href = "/savingsproducts/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateSavingProduct(final Long productId) {
+        this.actionName = "UPDATE";
+        this.entityName = "SAVINGSPRODUCT";
+        this.entityId = productId;
+        this.href = "/savingsproducts/" + productId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteSavingProduct(final Long productId) {
+        this.actionName = "DELETE";
+        this.entityName = "SAVINGSPRODUCT";
+        this.entityId = productId;
+        this.href = "/savingsproducts/" + productId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createSavingsAccount() {
+        this.actionName = "CREATE";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = null;
+        this.href = "/savingsaccounts/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateSavingsAccount(final Long accountId) {
+        this.actionName = "UPDATE";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = accountId;
+        this.href = "/savingsaccounts/" + accountId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteSavingsAccount(final Long accountId) {
+        this.actionName = "DELETE";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = accountId;
+        this.href = "/savingsaccounts/" + accountId;
+        return this;
+    }
+
+    public CommandWrapperBuilder rejectSavingsAccountApplication(final Long accountId) {
+        this.actionName = "REJECT";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?command=reject";
+        return this;
+    }
+
+    public CommandWrapperBuilder withdrawSavingsAccountApplication(final Long accountId) {
+        this.actionName = "WITHDRAW";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?command=withdrawnByApplicant";
+        return this;
+    }
+
+    public CommandWrapperBuilder approveSavingsAccountApplication(final Long accountId) {
+        this.actionName = "APPROVE";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?command=approve";
+        return this;
+    }
+
+    public CommandWrapperBuilder undoSavingsAccountApplication(final Long accountId) {
+        this.actionName = "APPROVALUNDO";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?command=undoapproval";
+        return this;
+    }
+
+    public CommandWrapperBuilder savingsAccountActivation(final Long accountId) {
+        this.actionName = "ACTIVATE";
+        this.entityName = "SAVINGSACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = null;
+        this.href = "/savingsaccounts/" + accountId + "?command=activate";
+        return this;
+    }
+
+    public CommandWrapperBuilder closeSavingsAccountApplication(final Long accountId) {
+        this.actionName = "CLOSE";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?command=close";
+        return this;
+    }
+
+    public CommandWrapperBuilder createAccountTransfer() {
+        this.actionName = "CREATE";
+        this.entityName = "ACCOUNTTRANSFER";
+        this.entityId = null;
+        this.href = "/accounttransfers";
+        return this;
+    }
+
+    public CommandWrapperBuilder createStandingInstruction() {
+        this.actionName = "CREATE";
+        this.entityName = "STANDINGINSTRUCTION";
+        this.entityId = null;
+        this.href = "/standinginstructions";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateStandingInstruction(final Long standingInstructionId) {
+        this.actionName = "UPDATE";
+        this.entityName = "STANDINGINSTRUCTION";
+        this.entityId = standingInstructionId;
+        this.href = "/standinginstructions";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteStandingInstruction(final Long standingInstructionId) {
+        this.actionName = "DELETE";
+        this.entityName = "STANDINGINSTRUCTION";
+        this.entityId = standingInstructionId;
+        this.href = "/standinginstructions";
+        return this;
+    }
+
+    public CommandWrapperBuilder savingsAccountDeposit(final Long accountId) {
+        this.actionName = "DEPOSIT";
+        this.entityName = "SAVINGSACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = null;
+        this.href = "/savingsaccounts/" + accountId + "/transactions";
+        return this;
+    }
+
+    public CommandWrapperBuilder savingsAccountWithdrawal(final Long accountId) {
+        this.actionName = "WITHDRAWAL";
+        this.entityName = "SAVINGSACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = null;
+        this.href = "/savingsaccounts/" + accountId + "/transactions";
+        return this;
+    }
+
+    public CommandWrapperBuilder undoSavingsAccountTransaction(final Long accountId, final Long transactionId) {
+        this.actionName = "UNDOTRANSACTION";
+        this.entityName = "SAVINGSACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.subentityId = transactionId;
+        this.transactionId = transactionId.toString();
+        this.href = "/savingsaccounts/" + accountId + "/transactions/" + transactionId + "?command=undo";
+        return this;
+    }
+
+    public CommandWrapperBuilder adjustSavingsAccountTransaction(final Long accountId, final Long transactionId) {
+        this.actionName = "ADJUSTTRANSACTION";
+        this.entityName = "SAVINGSACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.subentityId = transactionId;
+        this.transactionId = transactionId.toString();
+        this.href = "/savingsaccounts/" + accountId + "/transactions/" + transactionId + "?command=modify";
+        return this;
+    }
+
+    public CommandWrapperBuilder savingsAccountInterestCalculation(final Long accountId) {
+        this.actionName = "CALCULATEINTEREST";
+        this.entityName = "SAVINGSACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?command=calculateInterest";
+        return this;
+    }
+
+    public CommandWrapperBuilder savingsAccountInterestPosting(final Long accountId) {
+        this.actionName = "POSTINTEREST";
+        this.entityName = "SAVINGSACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?command=postInterest";
+        return this;
+    }
+
+    public CommandWrapperBuilder savingsAccountApplyAnnualFees(final Long accountId) {
+        this.actionName = "APPLYANNUALFEE";
+        this.entityName = "SAVINGSACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?command=applyAnnualFees";
+        return this;
+    }
+
+    public CommandWrapperBuilder createSavingsAccountCharge(final Long savingsAccountId) {
+        this.actionName = "CREATE";
+        this.entityName = "SAVINGSACCOUNTCHARGE";
+        this.savingsId = savingsAccountId;
+        this.href = "/savingsaccounts/" + savingsAccountId + "/charges";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+        this.actionName = "UPDATE";
+        this.entityName = "SAVINGSACCOUNTCHARGE";
+        this.entityId = savingsAccountChargeId;
+        this.savingsId = savingsAccountId;
+        this.href = "/savingsaccounts/" + savingsAccountId + "/charges/" + savingsAccountChargeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder waiveSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+        this.actionName = "WAIVE";
+        this.entityName = "SAVINGSACCOUNTCHARGE";
+        this.entityId = savingsAccountChargeId;
+        this.savingsId = savingsAccountId;
+        this.href = "/savingsaccounts/" + savingsAccountId + "/charges/" + savingsAccountChargeId;
+        return this;
+
+    }
+
+    public CommandWrapperBuilder paySavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+        this.actionName = "PAY";
+        this.entityName = "SAVINGSACCOUNTCHARGE";
+        this.entityId = savingsAccountChargeId;
+        this.savingsId = savingsAccountId;
+        this.href = "/savingsaccounts/" + savingsAccountId + "/charges/" + savingsAccountChargeId;
+        return this;
+
+    }
+
+    public CommandWrapperBuilder inactivateSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+        this.actionName = "INACTIVATE";
+        this.entityName = "SAVINGSACCOUNTCHARGE";
+        this.entityId = savingsAccountChargeId;
+        this.savingsId = savingsAccountId;
+        this.href = "/savingsaccounts/" + savingsAccountId + "/charges/" + savingsAccountChargeId;
+        return this;
+
+    }
+
+    public CommandWrapperBuilder deleteSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+        this.actionName = "DELETE";
+        this.entityName = "SAVINGSACCOUNTCHARGE";
+        this.entityId = savingsAccountChargeId;
+        this.savingsId = savingsAccountId;
+        this.href = "/savingsaccounts/" + savingsAccountId + "/charges/" + savingsAccountChargeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createFixedDepositProduct() {
+        this.actionName = "CREATE";
+        this.entityName = "FIXEDDEPOSITPRODUCT";
+        this.entityId = null;
+        this.href = "/fixeddepositproducts/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateFixedDepositProduct(final Long productId) {
+        this.actionName = "UPDATE";
+        this.entityName = "FIXEDDEPOSITPRODUCT";
+        this.entityId = productId;
+        this.href = "/fixeddepositproducts/" + productId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteFixedDepositProduct(final Long productId) {
+        this.actionName = "DELETE";
+        this.entityName = "FIXEDDEPOSITPRODUCT";
+        this.entityId = productId;
+        this.href = "/fixeddepositproducts/" + productId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createRecurringDepositProduct() {
+        this.actionName = "CREATE";
+        this.entityName = "RECURRINGDEPOSITPRODUCT";
+        this.entityId = null;
+        this.href = "/recurringdepositproducts/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateRecurringDepositProduct(final Long productId) {
+        this.actionName = "UPDATE";
+        this.entityName = "RECURRINGDEPOSITPRODUCT";
+        this.entityId = productId;
+        this.href = "/recurringdepositproducts/" + productId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteRecurringDepositProduct(final Long productId) {
+        this.actionName = "DELETE";
+        this.entityName = "RECURRINGDEPOSITPRODUCT";
+        this.entityId = productId;
+        this.href = "/recurringdepositproducts/" + productId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createInterestRateChart() {
+        this.actionName = "CREATE";
+        this.entityName = "INTERESTRATECHART";
+        this.entityId = null;
+        this.href = "/interestratechart/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateInterestRateChart(final Long interestRateChartId) {
+        this.actionName = "UPDATE";
+        this.entityName = "INTERESTRATECHART";
+        this.entityId = interestRateChartId;
+        this.href = "/interestratechart/" + interestRateChartId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteInterestRateChart(final Long interestRateChartId) {
+        this.actionName = "DELETE";
+        this.entityName = "INTERESTRATECHART";
+        this.entityId = interestRateChartId;
+        this.href = "/interestratechart/" + interestRateChartId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createInterestRateChartSlab(final Long chartId) {
+        this.actionName = "CREATE";
+        this.entityName = "CHARTSLAB";
+        this.entityId = null;
+        this.subentityId = chartId; // refer to chart id
+        this.href = "/interestratechart/" + chartId + "/chartdetails/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateInterestRateChartSlab(final Long chartId, final Long chartSlabId) {
+        this.actionName = "UPDATE";
+        this.entityName = "CHARTSLAB";
+        this.entityId = chartSlabId;
+        this.subentityId = chartId;// refers parent chart
+        this.href = "/interestratechart/" + chartId + "/chartdetails/" + chartSlabId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteInterestRateChartSlab(final Long chartId, final Long chartSlabId) {
+        this.actionName = "DELETE";
+        this.entityName = "CHARTSLAB";
+        this.entityId = chartSlabId;
+        this.subentityId = chartId;// refers parent chart
+        this.href = "/interestratechart/" + chartId + "/chartdetails/" + chartSlabId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createCalendar(final CommandWrapper resourceDetails, final String supportedEntityType,
+            final Long supportedEntityId) {
+        this.actionName = "CREATE";
+        this.entityName = "CALENDAR";
+        this.clientId = resourceDetails.getClientId();
+        this.loanId = resourceDetails.getLoanId();
+        this.groupId = resourceDetails.getGroupId();
+        this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/calendars/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateCalendar(final String supportedEntityType, final Long supportedEntityId, final Long calendarId) {
+        this.actionName = "UPDATE";
+        this.entityName = "CALENDAR";
+        this.entityId = calendarId;
+        this.groupId = supportedEntityId;
+        this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/calendars/" + calendarId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteCalendar(final String supportedEntityType, final Long supportedEntityId, final Long calendarId) {
+        this.actionName = "DELETE";
+        this.entityName = "CALENDAR";
+        this.entityId = calendarId;
+        this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/calendars/" + calendarId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createNote(final CommandWrapper resourceDetails, final String resourceType, final Long resourceId) {
+        this.actionName = "CREATE";
+        this.entityName = resourceDetails.entityName();// Note supports multiple
+                                                       // resources. Note
+                                                       // Permissions are set
+                                                       // for each resource.
+        this.clientId = resourceDetails.getClientId();
+        this.loanId = resourceDetails.getLoanId();
+        this.savingsId = resourceDetails.getSavingsId();
+        this.groupId = resourceDetails.getGroupId();
+        this.subentityId = resourceDetails.subresourceId();
+        this.href = "/" + resourceType + "/" + resourceId + "/notes/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateNote(final CommandWrapper resourceDetails, final String resourceType, final Long resourceId,
+            final Long noteId) {
+        this.actionName = "UPDATE";
+        this.entityName = resourceDetails.entityName();// Note supports multiple
+                                                       // resources. Note
+                                                       // Permissions are set
+                                                       // for each resource.
+        this.entityId = noteId;
+        this.clientId = resourceDetails.getClientId();
+        this.loanId = resourceDetails.getLoanId();
+        this.savingsId = resourceDetails.getSavingsId();
+        this.groupId = resourceDetails.getGroupId();
+        this.subentityId = resourceDetails.subresourceId();
+        this.href = "/" + resourceType + "/" + resourceId + "/notes";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteNote(final CommandWrapper resourceDetails, final String resourceType, final Long resourceId,
+            final Long noteId) {
+        this.actionName = "DELETE";
+        this.entityName = resourceDetails.entityName();// Note supports multiple
+                                                       // resources. Note
+                                                       // Permissions are set
+                                                       // for each resource.
+        this.entityId = noteId;
+        this.clientId = resourceDetails.getClientId();
+        this.loanId = resourceDetails.getLoanId();
+        this.savingsId = resourceDetails.getSavingsId();
+        this.groupId = resourceDetails.getGroupId();
+        this.subentityId = resourceDetails.subresourceId();
+        this.href = "/" + resourceType + "/" + resourceId + "/calendars/" + noteId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createGroup() {
+        this.actionName = "CREATE";
+        this.entityName = "GROUP";
+        this.href = "/groups/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateGroup(final Long groupId) {
+        this.actionName = "UPDATE";
+        this.entityName = "GROUP";
+        this.entityId = groupId;
+        this.groupId = groupId;
+        this.href = "/groups/" + groupId;
+        return this;
+    }
+
+    public CommandWrapperBuilder activateGroup(final Long groupId) {
+        this.actionName = "ACTIVATE";
+        this.entityName = "GROUP";
+        this.entityId = groupId;
+        this.groupId = groupId;
+        this.href = "/groups/" + groupId + "?command=activate";
+        return this;
+    }
+
+    public CommandWrapperBuilder saveGroupCollectionSheet(final Long groupId) {
+        this.actionName = "SAVECOLLECTIONSHEET";
+        this.entityName = "GROUP";
+        this.entityId = groupId;
+        this.groupId = groupId;
+        this.href = "/groups/" + groupId + "?command=saveCollectionSheet";
+        return this;
+    }
+
+    public CommandWrapperBuilder saveIndividualCollectionSheet() {
+        this.actionName = "SAVE";
+        this.entityName = "COLLECTIONSHEET";
+        this.href = "/collectionsheet?command=saveCollectionSheet";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteGroup(final Long groupId) {
+        this.actionName = "DELETE";
+        this.entityName = "GROUP";
+        this.entityId = groupId;
+        this.groupId = groupId;
+        this.href = "/groups/" + groupId;
+        return this;
+    }
+
+    public CommandWrapperBuilder associateClientsToGroup(final Long groupId) {
+        this.actionName = "ASSOCIATECLIENTS";
+        this.entityName = "GROUP";
+        this.entityId = groupId;
+        this.groupId = groupId;
+        this.href = "/groups/" + groupId + "?command=associateClients";
+        return this;
+    }
+
+    public CommandWrapperBuilder disassociateClientsFromGroup(final Long groupId) {
+        this.actionName = "DISASSOCIATECLIENTS";
+        this.entityName = "GROUP";
+        this.entityId = groupId;
+        this.groupId = groupId;
+        this.href = "/groups/" + groupId + "?command=disassociateClients";
+        return this;
+    }
+
+    public CommandWrapperBuilder transferClientsBetweenGroups(final Long sourceGroupId) {
+        this.actionName = "TRANSFERCLIENTS";
+        this.entityName = "GROUP";
+        this.entityId = sourceGroupId;
+        this.groupId = sourceGroupId;
+        this.href = "/groups/" + sourceGroupId + "?command=transferClients";
+        return this;
+    }
+
+    public CommandWrapperBuilder unassignGroupStaff(final Long groupId) {
+        this.actionName = "UNASSIGNSTAFF";
+        this.entityName = "GROUP";
+        this.entityId = groupId;
+        this.groupId = groupId;
+        this.href = "/groups/" + groupId;
+        return this;
+    }
+
+    public CommandWrapperBuilder assignGroupStaff(final Long groupId) {
+        this.actionName = "ASSIGNSTAFF";
+        this.entityName = "GROUP";
+        this.entityId = groupId;
+        this.groupId = groupId;
+        this.href = "/groups/" + groupId + "?command=assignStaff";
+        return this;
+    }
+
+    public CommandWrapperBuilder closeGroup(final Long groupId) {
+        this.actionName = "CLOSE";
+        this.entityName = "GROUP";
+        this.entityId = groupId;
+        this.groupId = groupId;
+        this.href = "/groups/" + groupId + "?command=close";
+        return this;
+    }
+
+    public CommandWrapperBuilder createCollateral(final Long loanId) {
+        this.actionName = "CREATE";
+        this.entityName = "COLLATERAL";
+        this.entityId = null;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/collaterals/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateCollateral(final Long loanId, final Long collateralId) {
+        this.actionName = "UPDATE";
+        this.entityName = "COLLATERAL";
+        this.entityId = collateralId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/collaterals/" + collateralId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteCollateral(final Long loanId, final Long collateralId) {
+        this.actionName = "DELETE";
+        this.entityName = "COLLATERAL";
+        this.entityId = collateralId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/collaterals/" + collateralId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateCollectionSheet(final Long groupId) {
+        this.actionName = "UPDATE";
+        this.entityName = "COLLECTIONSHEET";
+        this.entityId = groupId;
+        this.href = "/groups/" + groupId + "/collectionsheet";
+        return this;
+    }
+
+    public CommandWrapperBuilder createCenter() {
+        this.actionName = "CREATE";
+        this.entityName = "CENTER";
+        this.href = "/centers/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateCenter(final Long centerId) {
+        this.actionName = "UPDATE";
+        this.entityName = "CENTER";
+        this.entityId = centerId;
+        this.href = "/centers/" + centerId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteCenter(final Long centerId) {
+        this.actionName = "DELETE";
+        this.entityName = "CENTER";
+        this.entityId = centerId;
+        this.href = "/centers/" + centerId;
+        return this;
+    }
+
+    public CommandWrapperBuilder activateCenter(final Long centerId) {
+        this.actionName = "ACTIVATE";
+        this.entityName = "CENTER";
+        this.entityId = centerId;
+        this.groupId = centerId;
+        this.href = "/centers/" + centerId + "?command=activate";
+        return this;
+    }
+
+    public CommandWrapperBuilder saveCenterCollectionSheet(final Long centerId) {
+        this.actionName = "SAVECOLLECTIONSHEET";
+        this.entityName = "CENTER";
+        this.entityId = centerId;
+        this.groupId = centerId;
+        this.href = "/centers/" + centerId + "?command=saveCollectionSheet";
+        return this;
+    }
+
+    public CommandWrapperBuilder closeCenter(final Long centerId) {
+        this.actionName = "CLOSE";
+        this.entityName = "CENTER";
+        this.entityId = centerId;
+        this.groupId = centerId;
+        this.href = "/centers/" + centerId + "?command=close";
+        return this;
+    }
+
+    public CommandWrapperBuilder associateGroupsToCenter(final Long centerId) {
+        this.actionName = "ASSOCIATEGROUPS";
+        this.entityName = "CENTER";
+        this.entityId = centerId;
+        this.groupId = centerId;
+        this.href = "/groups/" + centerId + "?command=associateGroups";
+        return this;
+    }
+
+    public CommandWrapperBuilder disassociateGroupsFromCenter(final Long centerId) {
+        this.actionName = "DISASSOCIATEGROUPS";
+        this.entityName = "CENTER";
+        this.entityId = centerId;
+        this.groupId = centerId;
+        this.href = "/groups/" + centerId + "?command=disassociateGroups";
+        return this;
+    }
+
+    public CommandWrapperBuilder createAccountingRule() {
+        this.actionName = "CREATE";
+        this.entityName = "ACCOUNTINGRULE";
+        this.entityId = null;
+        this.href = "/accountingrules/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateAccountingRule(final Long accountingRuleId) {
+        this.actionName = "UPDATE";
+        this.entityName = "ACCOUNTINGRULE";
+        this.entityId = accountingRuleId;
+        this.href = "/accountingrules/" + accountingRuleId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteAccountingRule(final Long accountingRuleId) {
+        this.actionName = "DELETE";
+        this.entityName = "ACCOUNTINGRULE";
+        this.entityId = accountingRuleId;
+        this.href = "/accountingrules/" + accountingRuleId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateTaxonomyMapping(final Long mappingId) {
+        this.actionName = "UPDATE";
+        this.entityName = "XBRLMAPPING";
+        this.entityId = mappingId;
+        this.href = "/xbrlmapping";
+        return this;
+    }
+
+    public CommandWrapperBuilder createHoliday() {
+        this.actionName = "CREATE";
+        this.entityName = "HOLIDAY";
+        this.entityId = null;
+        this.href = "/holidays/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder activateHoliday(final Long holidayId) {
+        this.actionName = "ACTIVATE";
+        this.entityName = "HOLIDAY";
+        this.entityId = holidayId;
+        this.href = "/holidays/" + holidayId + "command=activate";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateHoliday(final Long holidayId) {
+        this.actionName = "UPDATE";
+        this.entityName = "HOLIDAY";
+        this.entityId = holidayId;
+        this.href = "/holidays/" + holidayId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteHoliday(final Long holidayId) {
+        this.actionName = "DELETE";
+        this.entityName = "HOLIDAY";
+        this.entityId = holidayId;
+        this.href = "/holidays/" + holidayId + "command=delete";
+        return this;
+    }
+
+    public CommandWrapperBuilder assignRole(final Long groupId) {
+        this.actionName = "ASSIGNROLE";
+        this.entityName = "GROUP";
+        this.groupId = groupId;
+        this.entityId = null;
+        this.href = "/groups/" + groupId + "?command=assignRole";
+        return this;
+    }
+
+    public CommandWrapperBuilder unassignRole(final Long groupId, final Long roleId) {
+        this.actionName = "UNASSIGNROLE";
+        this.entityName = "GROUP";
+        this.groupId = groupId;
+        this.entityId = roleId;
+        this.href = "/groups/" + groupId + "?command=unassignRole";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateRole(final Long groupId, final Long roleId) {
+        this.actionName = "UPDATEROLE";
+        this.entityName = "GROUP";
+        this.groupId = groupId;
+        this.entityId = roleId;
+        this.href = "/groups/" + groupId + "?command=updateRole";
+        return this;
+    }
+
+    public CommandWrapperBuilder unassignClientStaff(final Long clientId) {
+        this.actionName = "UNASSIGNSTAFF";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "?command=unassignStaff";
+        return this;
+    }
+
+    public CommandWrapperBuilder createTemplate() {
+        this.actionName = "CREATE";
+        this.entityName = "TEMPLATE";
+        this.entityId = null;
+        this.href = "/templates";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateTemplate(final Long templateId) {
+        this.actionName = "UPDATE";
+        this.entityName = "TEMPLATE";
+        this.entityId = templateId;
+        this.href = "/templates/" + templateId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteTemplate(final Long templateId) {
+        this.actionName = "DELETE";
+        this.entityName = "TEMPLATE";
+        this.entityId = templateId;
+        this.href = "/templates/" + templateId;
+        return this;
+    }
+
+    public CommandWrapperBuilder assignClientStaff(final Long clientId) {
+        this.actionName = "ASSIGNSTAFF";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "?command=assignStaff";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateClientSavingsAccount(final Long clientId) {
+        this.actionName = "UPDATESAVINGSACCOUNT";
+        this.entityName = "CLIENT";
+        this.entityId = clientId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "?command=updateSavingsAccount";
+        return this;
+    }
+
+    public CommandWrapperBuilder createProductMix(final Long productId) {
+        this.actionName = "CREATE";
+        this.entityName = "PRODUCTMIX";
+        this.entityId = null;
+        this.productId = productId;
+        this.href = "/loanproducts/" + productId + "/productmix";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateProductMix(final Long productId) {
+        this.actionName = "UPDATE";
+        this.entityName = "PRODUCTMIX";
+        this.entityId = null;
+        this.productId = productId;
+        this.href = "/loanproducts/" + productId + "/productmix";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteProductMix(final Long productId) {
+        this.actionName = "DELETE";
+        this.entityName = "PRODUCTMIX";
+        this.entityId = null;
+        this.productId = productId;
+        this.href = "/loanproducts/" + productId + "/productmix";
+        return this;
+    }
+
+    public CommandWrapperBuilder withProduct(final Long productId) {
+        this.productId = productId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateJobDetail(final Long jobId) {
+        this.actionName = "UPDATE";
+        this.entityName = "SCHEDULER";
+        this.entityId = jobId;
+        this.href = "/updateJobDetail/" + jobId + "/updateJobDetail";
+        return this;
+    }
+
+    public CommandWrapperBuilder createMeeting(final CommandWrapper resourceDetails, final String supportedEntityType,
+            final Long supportedEntityId) {
+        this.actionName = "CREATE";
+        this.entityName = "MEETING";
+        this.clientId = resourceDetails.getClientId();
+        this.loanId = resourceDetails.getLoanId();
+        this.groupId = resourceDetails.getGroupId();
+        this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateMeeting(final String supportedEntityType, final Long supportedEntityId, final Long meetingId) {
+        this.actionName = "UPDATE";
+        this.entityName = "MEETING";
+        this.entityId = meetingId;
+        this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + meetingId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteMeeting(final String supportedEntityType, final Long supportedEntityId, final Long meetingId) {
+        this.actionName = "DELETE";
+        this.entityName = "MEETING";
+        this.entityId = meetingId;
+        this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + meetingId;
+        return this;
+    }
+
+    public CommandWrapperBuilder saveOrUpdateAttendance(final Long entityId, final String supportedEntityType, final Long supportedEntityId) {
+        this.actionName = "SAVEORUPDATEATTENDANCE";
+        this.entityName = "MEETING";
+        this.entityId = entityId;
+        this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + entityId + "?command=saveOrUpdateAttendance";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateCache() {
+        this.actionName = "UPDATE";
+        this.entityName = "CACHE";
+        this.href = "/cache";
+        return this;
+    }
+
+    /**
+     * Deposit account mappings
+     */
+
+    public CommandWrapperBuilder createFixedDepositAccount() {
+        this.actionName = "CREATE";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.entityId = null;
+        this.href = "/fixeddepositaccounts/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateFixedDepositAccount(final Long accountId) {
+        this.actionName = "UPDATE";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteFixedDepositAccount(final Long accountId) {
+        this.actionName = "DELETE";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId;
+        return this;
+    }
+
+    public CommandWrapperBuilder rejectFixedDepositAccountApplication(final Long accountId) {
+        this.actionName = "REJECT";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "?command=reject";
+        return this;
+    }
+
+    public CommandWrapperBuilder withdrawFixedDepositAccountApplication(final Long accountId) {
+        this.actionName = "WITHDRAW";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "?command=withdrawnByApplicant";
+        return this;
+    }
+
+    public CommandWrapperBuilder approveFixedDepositAccountApplication(final Long accountId) {
+        this.actionName = "APPROVE";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "?command=approve";
+        return this;
+    }
+
+    public CommandWrapperBuilder undoFixedDepositAccountApplication(final Long accountId) {
+        this.actionName = "APPROVALUNDO";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "?command=undoapproval";
+        return this;
+    }
+
+    public CommandWrapperBuilder fixedDepositAccountActivation(final Long accountId) {
+        this.actionName = "ACTIVATE";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "?command=activate";
+        return this;
+    }
+
+    public CommandWrapperBuilder closeFixedDepositAccount(final Long accountId) {
+        this.actionName = "CLOSE";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "?command=close";
+        return this;
+    }
+
+    public CommandWrapperBuilder prematureCloseFixedDepositAccount(final Long accountId) {
+        this.actionName = "PREMATURECLOSE";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "?command=prematureClose";
+        return this;
+    }
+
+    public CommandWrapperBuilder fixedDepositAccountInterestCalculation(final Long accountId) {
+        this.actionName = "CALCULATEINTEREST";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "?command=calculateInterest";
+        return this;
+    }
+
+    public CommandWrapperBuilder fixedDepositAccountInterestPosting(final Long accountId) {
+        this.actionName = "POSTINTEREST";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "?command=postInterest";
+        return this;
+    }
+
+    public CommandWrapperBuilder fixedDepositAccountDeposit(final Long accountId) {
+        this.actionName = "DEPOSIT";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "/transactions?command=deposit";
+        return this;
+    }
+
+    public CommandWrapperBuilder fixedDepositAccountWithdrawal(final Long accountId) {
+        this.actionName = "WITHDRAWAL";
+        this.entityName = "FIXEDDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/fixeddepositaccounts/" + accountId + "/transactions?command=withdrawal";
+        return this;
+    }
+
+    public CommandWrapperBuilder createRecurringDepositAccount() {
+        this.actionName = "CREATE";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = null;
+        this.href = "/recurringdepositaccounts/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateRecurringDepositAccount(final Long accountId) {
+        this.actionName = "UPDATE";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId;
+        return this;
+    }
+
+    public CommandWrapperBuilder recurringAccountDeposit(final Long accountId) {
+        this.actionName = "DEPOSIT";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "/transactions?command=deposit";
+        return this;
+    }
+
+    public CommandWrapperBuilder recurringAccountWithdrawal(final Long accountId) {
+        this.actionName = "WITHDRAWAL";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "/transactions?command=withdrawal";
+        return this;
+    }
+
+    public CommandWrapperBuilder adjustRecurringAccountTransaction(final Long accountId, final Long transactionId) {
+        this.actionName = "ADJUSTTRANSACTION";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.subentityId = transactionId;
+        this.transactionId = transactionId.toString();
+        this.href = "/recurringdepositaccounts/" + accountId + "/transactions/" + transactionId + "?command=modify";
+        return this;
+    }
+
+    public CommandWrapperBuilder undoRecurringAccountTransaction(final Long accountId, final Long transactionId) {
+        this.actionName = "UNDOTRANSACTION";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.subentityId = transactionId;
+        this.transactionId = transactionId.toString();
+        this.href = "/recurringdepositaccounts/" + accountId + "/transactions/" + transactionId + "?command=undo";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteRecurringDepositAccount(final Long accountId) {
+        this.actionName = "DELETE";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId;
+        return this;
+    }
+
+    public CommandWrapperBuilder rejectRecurringDepositAccountApplication(final Long accountId) {
+        this.actionName = "REJECT";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=reject";
+        return this;
+    }
+
+    public CommandWrapperBuilder withdrawRecurringDepositAccountApplication(final Long accountId) {
+        this.actionName = "WITHDRAW";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=withdrawnByApplicant";
+        return this;
+    }
+
+    public CommandWrapperBuilder approveRecurringDepositAccountApplication(final Long accountId) {
+        this.actionName = "APPROVE";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=approve";
+        return this;
+    }
+
+    public CommandWrapperBuilder undoRecurringDepositAccountApplication(final Long accountId) {
+        this.actionName = "APPROVALUNDO";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=undoapproval";
+        return this;
+    }
+
+    public CommandWrapperBuilder recurringDepositAccountActivation(final Long accountId) {
+        this.actionName = "ACTIVATE";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=activate";
+        return this;
+    }
+
+    public CommandWrapperBuilder closeRecurringDepositAccount(final Long accountId) {
+        this.actionName = "CLOSE";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=close";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateDepositAmountForRecurringDepositAccount(final Long accountId) {
+        this.actionName = DepositsApiConstants.UPDATE_DEPOSIT_AMOUNT.toUpperCase();
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=" + DepositsApiConstants.UPDATE_DEPOSIT_AMOUNT;
+        return this;
+    }
+
+    public CommandWrapperBuilder prematureCloseRecurringDepositAccount(final Long accountId) {
+        this.actionName = "PREMATURECLOSE";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.entityId = accountId;
+        this.savingsId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=prematureClose";
+        return this;
+    }
+
+    public CommandWrapperBuilder recurringDepositAccountInterestCalculation(final Long accountId) {
+        this.actionName = "CALCULATEINTEREST";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=calculateInterest";
+        return this;
+    }
+
+    public CommandWrapperBuilder recurringDepositAccountInterestPosting(final Long accountId) {
+        this.actionName = "POSTINTEREST";
+        this.entityName = "RECURRINGDEPOSITACCOUNT";
+        this.savingsId = accountId;
+        this.entityId = accountId;
+        this.href = "/recurringdepositaccounts/" + accountId + "?command=postInterest";
+        return this;
+    }
+
+    public CommandWrapperBuilder createOfficeToGLAccountMapping() {
+        this.actionName = "CREATE";
+        this.entityName = "FINANCIALACTIVITYACCOUNT";
+        this.entityId = null;
+        this.href = "/organizationglaccounts/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateOfficeToGLAccountMapping(final Long mappingId) {
+        this.actionName = "UPDATE";
+        this.entityName = "FINANCIALACTIVITYACCOUNT";
+        this.entityId = mappingId;
+        this.href = "/organizationglaccounts/" + mappingId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteOfficeToGLAccountMapping(final Long mappingId) {
+        this.actionName = "DELETE";
+        this.entityName = "FINANCIALACTIVITYACCOUNT";
+        this.entityId = mappingId;
+        this.href = "/organizationglaccounts/" + mappingId;
+        return this;
+    }
+
+    public CommandWrapperBuilder registerDBDatatable(final String datatable, final String apptable) {
+        this.actionName = "REGISTER";
+        this.entityName = "DATATABLE";
+        this.entityId = null;
+        this.href = "/datatables/register/" + datatable + "/" + apptable;
+        return this;
+    }
+
+    public CommandWrapperBuilder registerSurvey(final String datatable, final String apptable) {
+        this.actionName = "REGISTER";
+        this.entityName = "SURVEY";
+        this.entityId = null;
+        this.href = "/survey/register/" + datatable + "/" + apptable;
+        return this;
+    }
+
+    public CommandWrapperBuilder fullFilSurvey(final String datatable, final Long apptableId) {
+        this.entityName = datatable;
+        this.entityId = apptableId;
+        this.actionName = "CREATE";
+        this.href = "/survey/" + datatable + "/" + apptableId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateLikelihood(final Long entityId) {
+        this.actionName = "UPDATE";
+        this.entityName = "LIKELIHOOD";
+        this.href = "/likelihood/" + entityId;
+        this.entityId = entityId;
+        return this;
+    }
+
+    public CommandWrapperBuilder assignSavingsOfficer(final Long accountId) {
+        this.actionName = "UPDATESAVINGSOFFICER";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?command=assignSavingsOfficer";
+        return this;
+    }
+
+    public CommandWrapperBuilder unassignSavingsOfficer(final Long accountId) {
+        this.actionName = "REMOVESAVINGSOFFICER";
+        this.entityName = "SAVINGSACCOUNT";
+        this.entityId = accountId;
+        this.href = "/savingsaccounts/" + accountId + "?commad=unassignSavingsOfficer";
+        return this;
+    }
+
+    public CommandWrapperBuilder createLoanRescheduleRequest(final String entityName) {
+        this.actionName = "CREATE";
+        this.entityName = entityName;
+        this.entityId = null;
+        this.href = "/rescheduleloans";
+        return this;
+    }
+
+    public CommandWrapperBuilder approveLoanRescheduleRequest(final String entityName, final Long requestId) {
+        this.actionName = "APPROVE";
+        this.entityName = entityName;
+        this.entityId = requestId;
+        this.href = "/rescheduleloans/" + requestId + "?command=approve";
+        return this;
+    }
+
+    public CommandWrapperBuilder rejectLoanRescheduleRequest(final String entityName, final Long requestId) {
+        this.actionName = "REJECT";
+        this.entityName = entityName;
+        this.entityId = requestId;
+        this.href = "/rescheduleloans/" + requestId + "?command=reject";
+        return this;
+    }
+
+    public CommandWrapperBuilder createAccountNumberFormat() {
+        this.actionName = "CREATE";
+        this.entityName = AccountNumberFormatConstants.ENTITY_NAME.toUpperCase();
+        this.href = AccountNumberFormatConstants.resourceRelativeURL;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateAccountNumberFormat(final Long accountNumberFormatId) {
+        this.actionName = "UPDATE";
+        this.entityName = AccountNumberFormatConstants.ENTITY_NAME.toUpperCase();
+        this.entityId = accountNumberFormatId;
+        this.href = AccountNumberFormatConstants.resourceRelativeURL + "/" + accountNumberFormatId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteAccountNumberFormat(final Long accountNumberFormatId) {
+        this.actionName = "DELETE";
+        this.entityName = AccountNumberFormatConstants.ENTITY_NAME.toUpperCase();
+        this.entityId = accountNumberFormatId;
+        this.href = "AccountNumberFormatConstants.resourceRelativeURL" + "/" + accountNumberFormatId;
+        this.json = "{}";
+        return this;
+    }
+
+    public CommandWrapperBuilder refundByTransfer() {
+        this.actionName = "REFUNDBYTRANSFER";
+        this.entityName = "ACCOUNTTRANSFER";
+        this.entityId = null;
+        this.href = "/refundByTransfer";
+        return this;
+    }
+
+    public CommandWrapperBuilder createTeller() {
+        this.actionName = "CREATE";
+        this.entityName = "TELLER";
+        this.entityId = null;
+        this.href = "/tellers/templates";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateTeller(final Long tellerId) {
+        this.actionName = "UPDATE";
+        this.entityName = "TELLER";
+        this.entityId = tellerId;
+        this.href = "/tellers/" + tellerId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteTeller(final Long tellerId) {
+        this.actionName = "DELETE";
+        this.entityName = "TELLER";
+        this.entityId = tellerId;
+        this.href = "/tellers/" + tellerId;
+        return this;
+    }
+
+    public CommandWrapperBuilder allocateTeller(final long tellerId) {
+        this.actionName = "ALLOCATECASHIER";
+        this.entityName = "TELLER";
+        this.entityId = tellerId;
+        this.href = "/tellers/" + tellerId + "/cashiers/templates";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateAllocationTeller(final Long tellerId, final Long cashierId) {
+        this.actionName = "UPDATECASHIERALLOCATION";
+        this.entityName = "TELLER";
+        this.entityId = tellerId;
+        this.subentityId = cashierId;
+        this.href = "/tellers/" + tellerId + "/cashiers/" + cashierId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteAllocationTeller(final Long tellerId, final Long cashierId) {
+        this.actionName = "DELETECASHIERALLOCATION";
+        this.entityName = "TELLER";
+        this.entityId = tellerId;
+        this.subentityId = cashierId;
+        this.href = "/tellers/" + tellerId + "/cashiers/" + cashierId;
+        return this;
+    }
+
+    public CommandWrapperBuilder allocateCashToCashier(final Long tellerId, final Long cashierId) {
+        this.actionName = "ALLOCATECASHTOCASHIER";
+        this.entityName = "TELLER";
+        this.entityId = tellerId;
+        this.subentityId = cashierId;
+        this.href = "/tellers/" + tellerId + "/cashiers/" + cashierId + "/allocate";
+        return this;
+    }
+
+    public CommandWrapperBuilder settleCashFromCashier(final Long tellerId, final Long cashierId) {
+        this.actionName = "SETTLECASHFROMCASHIER";
+        this.entityName = "TELLER";
+        this.entityId = tellerId;
+        this.subentityId = cashierId;
+        this.href = "/tellers/" + tellerId + "/cashiers/" + cashierId + "/settle";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteRole(Long roleId) {
+        this.actionName = "DELETE";
+        this.entityName = "ROLE";
+        this.entityId = roleId;
+        this.href = "/roles/" + roleId;
+        this.json = "{}";
+        return this;
+    }
+
+    public CommandWrapperBuilder disableRole(Long roleId) {
+        this.actionName = "DISABLE";
+        this.entityName = "ROLE";
+        this.entityId = roleId;
+        this.href = "/roles/" + roleId + "/disbales";
+        this.json = "{}";
+        return this;
+    }
+
+    public CommandWrapperBuilder enableRole(Long roleId) {
+        this.actionName = "ENABLE";
+        this.entityName = "ROLE";
+        this.entityId = roleId;
+        this.href = "/roles/" + roleId + "/enable";
+        this.json = "{}";
+        return this;
+    }
+
+    public CommandWrapperBuilder createMap(Long relId) {
+        this.actionName = "CREATE";
+        this.entityName = "ENTITYMAPPING";
+        this.entityId = relId;
+        this.href = "/entitytoentitymapping/" + relId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateMap(Long mapId) {
+        this.actionName = "UPDATE";
+        this.entityName = "ENTITYMAPPING";
+        this.entityId = mapId;
+        this.href = "/entitytoentitymapping" + mapId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteMap(final Long mapId) {
+        this.actionName = "DELETE";
+        this.entityName = "ENTITYMAPPING";
+        this.entityId = mapId;
+        this.href = "/entitytoentitymapping/" + mapId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateWorkingDays() {
+        this.actionName = "UPDATE";
+        this.entityName = "WORKINGDAYS";
+        this.href = "/workingdays/";
+        return this;
+    }
+
+    public CommandWrapperBuilder updatePasswordPreferences() {
+        this.actionName = "UPDATE";
+        this.entityName = PasswordPreferencesApiConstants.ENTITY_NAME;
+        this.href = "/" + PasswordPreferencesApiConstants.RESOURCE_NAME;
+        return this;
+    }
+
+    public CommandWrapperBuilder createPaymentType() {
+        this.actionName = "CREATE";
+        this.entityName = PaymentTypeApiResourceConstants.ENTITY_NAME;
+        this.entityId = null;
+        this.href = "/" + PaymentTypeApiResourceConstants.RESOURCE_NAME;
+        return this;
+    }
+
+    public CommandWrapperBuilder updatePaymentType(final Long paymentTypeId) {
+        this.actionName = "UPDATE";
+        this.entityName = PaymentTypeApiResourceConstants.ENTITY_NAME;
+        this.entityId = paymentTypeId;
+        this.href = "/" + PaymentTypeApiResourceConstants.RESOURCE_NAME + paymentTypeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deletePaymentType(final Long paymentTypeId) {
+        this.actionName = "DELETE";
+        this.entityName = "PAYMENTTYPE";
+        this.entityId = paymentTypeId;
+        this.href = "/" + PaymentTypeApiResourceConstants.RESOURCE_NAME + paymentTypeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder updateExternalServiceProperties(final String externalServiceName) {
+        this.actionName = "UPDATE";
+        this.entityName = "EXTERNALSERVICES";
+        this.transactionId = externalServiceName;
+        this.href = "/externalservices/" + externalServiceName;
+        return this;
+    }
+
+    public CommandWrapperBuilder createClientCharge(final Long clientId) {
+        this.actionName = ClientApiConstants.CLIENT_CHARGE_ACTION_CREATE;
+        this.entityName = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "/charges";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteClientCharge(final Long clientId, final Long chargeId) {
+        this.actionName = ClientApiConstants.CLIENT_CHARGE_ACTION_DELETE;
+        this.entityName = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME;
+        this.clientId = clientId;
+        this.entityId = chargeId;
+        this.href = "/clients/" + clientId + "/charges/" + chargeId;
+        return this;
+    }
+
+    public CommandWrapperBuilder waiveClientCharge(final Long clientId, final Long chargeId) {
+        this.actionName = ClientApiConstants.CLIENT_CHARGE_ACTION_WAIVE;
+        this.entityName = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME;
+        this.entityId = chargeId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "/charges/" + chargeId + "?command=waive";
+        return this;
+    }
+
+    public CommandWrapperBuilder payClientCharge(final Long clientId, final Long chargeId) {
+        this.actionName = ClientApiConstants.CLIENT_CHARGE_ACTION_PAY;
+        this.entityName = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME;
+        this.entityId = chargeId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "/charges/" + chargeId + "?command=paycharge";
+        return this;
+    }
+
+    public CommandWrapperBuilder inactivateClientCharge(final Long clientId, final Long chargeId) {
+        this.actionName = "INACTIVATE";
+        this.entityName = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME;
+        this.entityId = chargeId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "/charges/" + chargeId + "?command=inactivate";
+        return this;
+    }
+
+    public CommandWrapperBuilder undoClientTransaction(final Long clientId, final Long transactionId) {
+        this.actionName = ClientApiConstants.CLIENT_TRANSACTION_ACTION_UNDO;
+        this.entityName = ClientApiConstants.CLIENT_RESOURCE_NAME;
+        this.entityId = transactionId;
+        this.clientId = clientId;
+        this.href = "/clients/" + clientId + "/transactions/" + transactionId + "?command=undo";
+        return this;
+    }
+
+    public CommandWrapperBuilder createProvisioningCategory() {
+        this.actionName = "CREATE";
+        this.entityName = "PROVISIONCATEGORY";
+        this.entityId = null;
+        this.href = "/provisioningcategory";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateProvisioningCategory(final Long cateoryId) {
+        this.actionName = "UPDATE";
+        this.entityName = "PROVISIONCATEGORY";
+        this.entityId = cateoryId;
+        this.href = "/provisioningcategory/" + cateoryId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteProvisioningCategory(final Long categoryId) {
+        this.actionName = "DELETE";
+        this.entityName = "PROVISIONCATEGORY";
+        this.entityId = categoryId;
+        this.href = "/provisioningcategory/" + categoryId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createProvisioningCriteria() {
+        this.actionName = "CREATE";
+        this.entityName = "PROVISIONCRITERIA";
+        this.entityId = null;
+        this.href = "/provisioningcriteria";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateProvisioningCriteria(final Long criteriaId) {
+        this.actionName = "UPDATE";
+        this.entityName = "PROVISIONCRITERIA";
+        this.entityId = criteriaId;
+        this.href = "/provisioningcriteria/" + criteriaId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteProvisioningCriteria(final Long criteriaId) {
+        this.actionName = "DELETE";
+        this.entityName = "PROVISIONCRITERIA";
+        this.entityId = criteriaId;
+        this.href = "/provisioningcriteria/" + criteriaId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createProvisioningEntries() {
+        this.actionName = "CREATE";
+        this.entityName = "PROVISIONENTRIES";
+        this.entityId = null;
+        this.href = "/provisioningentries";
+        return this;
+    }
+
+    public CommandWrapperBuilder createProvisioningJournalEntries(final Long entryId) {
+        this.actionName = "CREATE";
+        this.entityName = "PROVISIONJOURNALENTRIES";
+        this.entityId = entryId;
+        this.href = "/provisioningentries/" + entryId;
+        return this;
+    }
+
+    public CommandWrapperBuilder reCreateProvisioningEntries(final Long entryId) {
+        this.actionName = "RECREATE";
+        this.entityName = "PROVISIONENTRIES";
+        this.entityId = entryId;
+        this.href = "/provisioningentries/" + entryId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createFloatingRate() {
+        this.actionName = "CREATE";
+        this.entityName = "FLOATINGRATE";
+        this.entityId = null;
+        this.href = "/floatingrates";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateFloatingRate(final Long floatingRateId) {
+        this.actionName = "UPDATE";
+        this.entityName = "FLOATINGRATE";
+        this.entityId = floatingRateId;
+        this.href = "/floatingrates/" + floatingRateId;
+        return this;
+    }
+
+    public CommandWrapperBuilder createScheduleExceptions(final Long loanId) {
+        this.actionName = "CREATESCHEDULEEXCEPTIONS";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/schedule";
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteScheduleExceptions(final Long loanId) {
+        this.actionName = "DELETESCHEDULEEXCEPTIONS";
+        this.entityName = "LOAN";
+        this.entityId = loanId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/schedule";
+        return this;
+    }
+    
+    public CommandWrapperBuilder createProduct(String productType) {
+        this.entityName = productType.toUpperCase()+"PRODUCT" ; //To Support different type of products
+        this.actionName = "CREATE";
+        this.entityId = null;
+        this.href = "/products/"+productType;
+        return this;
+    }
+    
+    public CommandWrapperBuilder updateProduct(String productType, final Long productId) {
+        this.entityName = productType.toUpperCase()+"PRODUCT" ;
+        this.actionName = "UPDATE";
+        this.entityId = productId;
+        this.href = "/products/" + productType+"/"+productId;
+        return this;
+    }
+    
+    public CommandWrapperBuilder createAccount(String accountType) {
+        this.entityName = accountType.toUpperCase()+"ACCOUNT" ; //To Support different type of Accounts
+        this.actionName = "CREATE";
+        this.entityId = null;
+        this.href = "/accounts/"+accountType;
+        return this;
+    }
+    
+    public CommandWrapperBuilder updateAccount(String accountType, final Long accountId) {
+        this.entityName = accountType.toUpperCase()+"ACCOUNT" ;
+        this.actionName = "UPDATE";
+        this.entityId = accountId;
+        this.href = "/accounts/" + accountType+"/"+accountId;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
new file mode 100755
index 0000000..8fa8587
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
@@ -0,0 +1,33 @@
+/**
+ * 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.commands.service;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface PortfolioCommandSourceWritePlatformService {
+
+    CommandProcessingResult logCommandSource(CommandWrapper commandRequest);
+
+    CommandProcessingResult approveEntry(Long id);
+
+    Long rejectEntry(Long id);
+
+    Long deleteEntry(Long makerCheckerId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
new file mode 100755
index 0000000..481290e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
@@ -0,0 +1,191 @@
+/**
+ * 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.commands.service;
+
+import java.util.Random;
+
+import org.apache.fineract.commands.domain.CommandSource;
+import org.apache.fineract.commands.domain.CommandSourceRepository;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.exception.CommandNotAwaitingApprovalException;
+import org.apache.fineract.commands.exception.CommandNotFoundException;
+import org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.service.SchedulerJobRunnerReadService;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.CannotAcquireLockException;
+import org.springframework.orm.ObjectOptimisticLockingFailureException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonElement;
+
+@Service
+public class PortfolioCommandSourceWritePlatformServiceImpl implements PortfolioCommandSourceWritePlatformService {
+
+    private final PlatformSecurityContext context;
+    private final CommandSourceRepository commandSourceRepository;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final CommandProcessingService processAndLogCommandService;
+    private final SchedulerJobRunnerReadService schedulerJobRunnerReadService;
+    private final static Logger logger = LoggerFactory.getLogger(PortfolioCommandSourceWritePlatformServiceImpl.class);
+
+    @Autowired
+    public PortfolioCommandSourceWritePlatformServiceImpl(final PlatformSecurityContext context,
+            final CommandSourceRepository commandSourceRepository, final FromJsonHelper fromApiJsonHelper,
+            final CommandProcessingService processAndLogCommandService, final SchedulerJobRunnerReadService schedulerJobRunnerReadService) {
+        this.context = context;
+        this.commandSourceRepository = commandSourceRepository;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.processAndLogCommandService = processAndLogCommandService;
+        this.schedulerJobRunnerReadService = schedulerJobRunnerReadService;
+    }
+
+    @Override
+    public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) {
+
+        boolean isApprovedByChecker = false;
+        // check if is update of own account details
+        if (wrapper.isUpdateOfOwnUserDetails(this.context.authenticatedUser(wrapper).getId())) {
+            // then allow this operation to proceed.
+            // maker checker doesnt mean anything here.
+            isApprovedByChecker = true; // set to true in case permissions have
+                                        // been maker-checker enabled by
+                                        // accident.
+        } else {
+            // if not user changing their own details - check user has
+            // permission to perform specific task.
+            this.context.authenticatedUser(wrapper).validateHasPermissionTo(wrapper.getTaskPermissionName());
+        }
+        validateIsUpdateAllowed();
+
+        final String json = wrapper.getJson();
+        CommandProcessingResult result = null;
+        JsonCommand command = null;
+        Integer numberOfRetries = 0;
+        Integer maxNumberOfRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxRetriesOnDeadlock();
+        Integer maxIntervalBetweenRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxIntervalBetweenRetries();
+        final JsonElement parsedCommand = this.fromApiJsonHelper.parse(json);
+        command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), wrapper.getEntityId(),
+                wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), wrapper.getSavingsId(),
+                wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId());
+        while (numberOfRetries <= maxNumberOfRetries) {
+            try {
+                result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
+                numberOfRetries = maxNumberOfRetries + 1;
+            } catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) {
+                logger.info("The following command " + command.json() + " has been retried  " + numberOfRetries + " time(s)");
+                /***
+                 * Fail if the transaction has been retired for
+                 * maxNumberOfRetries
+                 **/
+                if (numberOfRetries >= maxNumberOfRetries) {
+                    logger.warn("The following command " + command.json() + " has been retried for the max allowed attempts of "
+                            + numberOfRetries + " and will be rolled back");
+                    throw (exception);
+                }
+                /***
+                 * Else sleep for a random time (between 1 to 10 seconds) and
+                 * continue
+                 **/
+                try {
+                    Random random = new Random();
+                    int randomNum = random.nextInt(maxIntervalBetweenRetries + 1);
+                    Thread.sleep(1000 + (randomNum * 1000));
+                    numberOfRetries = numberOfRetries + 1;
+                } catch (InterruptedException e) {
+                    throw (exception);
+                }
+            } catch (final RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
+                numberOfRetries = maxNumberOfRetries + 1;
+                result = this.processAndLogCommandService.logCommand(e.getCommandSourceResult());
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public CommandProcessingResult approveEntry(final Long makerCheckerId) {
+
+        final CommandSource commandSourceInput = validateMakerCheckerTransaction(makerCheckerId);
+        validateIsUpdateAllowed();
+
+        final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(makerCheckerId, commandSourceInput.getActionName(),
+                commandSourceInput.getEntityName(), commandSourceInput.resourceId(), commandSourceInput.subresourceId(),
+                commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getOfficeId(),
+                commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(),
+                commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId());
+        final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json());
+        final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.json(), parsedCommand,
+                this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
+                commandSourceInput.subresourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(),
+                commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(),
+                commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId());
+
+        final boolean makerCheckerApproval = true;
+        return this.processAndLogCommandService.processAndLogCommand(wrapper, command, makerCheckerApproval);
+    }
+
+    @Transactional
+    @Override
+    public Long deleteEntry(final Long makerCheckerId) {
+
+        validateMakerCheckerTransaction(makerCheckerId);
+        validateIsUpdateAllowed();
+
+        this.commandSourceRepository.delete(makerCheckerId);
+
+        return makerCheckerId;
+    }
+
+    private CommandSource validateMakerCheckerTransaction(final Long makerCheckerId) {
+
+        final CommandSource commandSourceInput = this.commandSourceRepository.findOne(makerCheckerId);
+        if (commandSourceInput == null) { throw new CommandNotFoundException(makerCheckerId); }
+        if (!(commandSourceInput.isMarkedAsAwaitingApproval())) { throw new CommandNotAwaitingApprovalException(makerCheckerId); }
+
+        this.context.authenticatedUser().validateHasCheckerPermissionTo(commandSourceInput.getPermissionCode());
+
+        return commandSourceInput;
+    }
+
+    private boolean validateIsUpdateAllowed() {
+        return this.schedulerJobRunnerReadService.isUpdatesAllowed();
+
+    }
+
+    @Override
+    public Long rejectEntry(final Long makerCheckerId) {
+        final CommandSource commandSourceInput = validateMakerCheckerTransaction(makerCheckerId);
+        validateIsUpdateAllowed();
+        final AppUser maker = this.context.authenticatedUser();
+        commandSourceInput.markAsRejected(maker, DateTime.now());
+        this.commandSourceRepository.save(commandSourceInput);
+        return makerCheckerId;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
new file mode 100755
index 0000000..ed908dd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
@@ -0,0 +1,224 @@
+/**
+ * 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.commands.service;
+
+import java.util.Map;
+
+import org.apache.fineract.commands.domain.CommandSource;
+import org.apache.fineract.commands.domain.CommandSourceRepository;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
+import org.apache.fineract.commands.exception.UnsupportedCommandException;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.commands.provider.CommandHandlerProvider;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+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.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.hooks.event.HookEvent;
+import org.apache.fineract.infrastructure.hooks.event.HookEventSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.DateTime;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class SynchronousCommandProcessingService implements CommandProcessingService {
+
+    private PlatformSecurityContext context;
+    private final ApplicationContext applicationContext;
+    private final ToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
+    private final ToApiJsonSerializer<CommandProcessingResult> toApiResultJsonSerializer;
+    private CommandSourceRepository commandSourceRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final CommandHandlerProvider commandHandlerProvider;
+
+    @Autowired
+    public SynchronousCommandProcessingService(final PlatformSecurityContext context, final ApplicationContext applicationContext,
+            final ToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer,
+            final ToApiJsonSerializer<CommandProcessingResult> toApiResultJsonSerializer,
+            final CommandSourceRepository commandSourceRepository, final ConfigurationDomainService configurationDomainService,
+            final CommandHandlerProvider commandHandlerProvider) {
+        this.context = context;
+        this.context = context;
+        this.applicationContext = applicationContext;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.toApiResultJsonSerializer = toApiResultJsonSerializer;
+        this.commandSourceRepository = commandSourceRepository;
+        this.commandSourceRepository = commandSourceRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.commandHandlerProvider = commandHandlerProvider;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processAndLogCommand(final CommandWrapper wrapper, final JsonCommand command,
+            final boolean isApprovedByChecker) {
+
+        final boolean rollbackTransaction = this.configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName());
+
+        final NewCommandSourceHandler handler = findCommandHandler(wrapper);
+
+        final CommandProcessingResult result = handler.processCommand(command);
+
+        final AppUser maker = this.context.authenticatedUser(wrapper);
+
+        CommandSource commandSourceResult = null;
+        if (command.commandId() != null) {
+            commandSourceResult = this.commandSourceRepository.findOne(command.commandId());
+            commandSourceResult.markAsChecked(maker, DateTime.now());
+        } else {
+            commandSourceResult = CommandSource.fullEntryFrom(wrapper, command, maker);
+        }
+        commandSourceResult.updateResourceId(result.resourceId());
+        commandSourceResult.updateForAudit(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(),
+                result.getSavingsId(), result.getProductId(), result.getTransactionId());
+
+        String changesOnlyJson = null;
+        if (result.hasChanges()) {
+            changesOnlyJson = this.toApiJsonSerializer.serializeResult(result.getChanges());
+            commandSourceResult.updateJsonTo(changesOnlyJson);
+        }
+
+        if (!result.hasChanges() && wrapper.isUpdateOperation() && !wrapper.isUpdateDatatable()) {
+            commandSourceResult.updateJsonTo(null);
+        }
+
+        if (commandSourceResult.hasJson()) {
+            this.commandSourceRepository.save(commandSourceResult);
+        }
+
+        if ((rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker) {
+            /*
+             * JournalEntry will generate a new transactionId every time.
+             * Updating the transactionId with old transactionId, because as
+             * there are no entries are created with new transactionId, will
+             * throw an error when checker approves the transaction
+             */
+            commandSourceResult.updateTransaction(command.getTransactionId());
+            /*
+             * Update CommandSource json data with JsonCommand json data, line
+             * 77 and 81 may update the json data
+             */
+            commandSourceResult.updateJsonTo(command.json());
+            throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(commandSourceResult);
+        }
+        result.setRollbackTransaction(null);
+
+        publishEvent(wrapper.entityName(), wrapper.actionName(), result);
+
+        return result;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult logCommand(CommandSource commandSourceResult) {
+
+        commandSourceResult.markAsAwaitingApproval();
+        commandSourceResult = this.commandSourceRepository.save(commandSourceResult);
+
+        return new CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId())
+                .withEntityId(commandSourceResult.getResourceId()).build();
+    }
+
+    private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) {
+        NewCommandSourceHandler handler = null;
+
+        if (wrapper.isDatatableResource()) {
+            if (wrapper.isCreateDatatable()) {
+                handler = this.applicationContext.getBean("createDatatableCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isDeleteDatatable()) {
+                handler = this.applicationContext.getBean("deleteDatatableCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isUpdateDatatable()) {
+                handler = this.applicationContext.getBean("updateDatatableCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isCreate()) {
+                handler = this.applicationContext.getBean("createDatatableEntryCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isUpdateMultiple()) {
+                handler = this.applicationContext.getBean("updateOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isUpdateOneToOne()) {
+                handler = this.applicationContext.getBean("updateOneToOneDatatableEntryCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isDeleteMultiple()) {
+                handler = this.applicationContext.getBean("deleteOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isDeleteOneToOne()) {
+                handler = this.applicationContext.getBean("deleteOneToOneDatatableEntryCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isRegisterDatatable()) {
+                handler = this.applicationContext.getBean("registerDatatableCommandHandler", NewCommandSourceHandler.class);
+            } else {
+                throw new UnsupportedCommandException(wrapper.commandName());
+            }
+        } else if (wrapper.isNoteResource()) {
+            if (wrapper.isCreate()) {
+                handler = this.applicationContext.getBean("createNoteCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isUpdate()) {
+                handler = this.applicationContext.getBean("updateNoteCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isDelete()) {
+                handler = this.applicationContext.getBean("deleteNoteCommandHandler", NewCommandSourceHandler.class);
+            } else {
+                throw new UnsupportedCommandException(wrapper.commandName());
+            }
+        } else if (wrapper.isSurveyResource()) {
+            if (wrapper.isRegisterSurvey()) {
+                handler = this.applicationContext.getBean("registerSurveyCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.isFullFilSurvey()) {
+                handler = this.applicationContext.getBean("fullFilSurveyCommandHandler", NewCommandSourceHandler.class);
+            } else {
+                throw new UnsupportedCommandException(wrapper.commandName());
+            }
+        } else if (wrapper.isLoanDisburseDetailResource()) {
+            if (wrapper.isUpdateDisbursementDate()) {
+                handler = this.applicationContext.getBean("updateLoanDisbuseDateCommandHandler", NewCommandSourceHandler.class);
+            } else if (wrapper.addAndDeleteDisbursementDetails()) {
+                handler = this.applicationContext.getBean("addAndDeleteLoanDisburseDetailsCommandHandler", NewCommandSourceHandler.class);
+            } else {
+                throw new UnsupportedCommandException(wrapper.commandName());
+            }
+        } else {
+            handler = this.commandHandlerProvider.getHandler(wrapper.entityName(), wrapper.actionName());
+        }
+
+        return handler;
+    }
+
+    @Override
+    public boolean validateCommand(final CommandWrapper commandWrapper, final AppUser user) {
+        boolean rollbackTransaction = this.configurationDomainService.isMakerCheckerEnabledForTask(commandWrapper.taskPermissionName());
+        user.validateHasPermissionTo(commandWrapper.getTaskPermissionName());
+        return rollbackTransaction;
+    }
+
+    private void publishEvent(final String entityName, final String actionName, final CommandProcessingResult result) {
+
+        final String authToken = ThreadLocalContextUtil.getAuthToken();
+        final String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
+        final AppUser appUser = this.context.authenticatedUser();
+
+        final HookEventSource hookEventSource = new HookEventSource(entityName, actionName);
+
+        final String serializedResult = this.toApiResultJsonSerializer.serialize(result);
+
+        final HookEvent applicationEvent = new HookEvent(hookEventSource, serializedResult, tenantIdentifier, appUser, authToken);
+
+        applicationContext.publishEvent(applicationEvent);
+    }
+}
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
new file mode 100644
index 0000000..a420e2e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/api/AccountNumberFormatsApiResource.java
@@ -0,0 +1,174 @@
+/**
+ * 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.accountnumberformat.api;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.accountnumberformat.data.AccountNumberFormatData;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatConstants;
+import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatReadPlatformService;
+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.ToApiJsonSerializer;
+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(AccountNumberFormatConstants.resourceRelativeURL)
+@Component
+@Scope("singleton")
+public class AccountNumberFormatsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final AccountNumberFormatReadPlatformService accountNumberFormatReadPlatformService;
+    private final ToApiJsonSerializer<AccountNumberFormatData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public AccountNumberFormatsApiResource(final PlatformSecurityContext context,
+            final ToApiJsonSerializer<AccountNumberFormatData> toApiJsonSerializer,
+            final AccountNumberFormatReadPlatformService accountNumberFormatReadPlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.accountNumberFormatReadPlatformService = accountNumberFormatReadPlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(AccountNumberFormatConstants.ENTITY_NAME);
+
+        EntityAccountType accountType = null;
+        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);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(AccountNumberFormatConstants.ENTITY_NAME);
+
+        final List<AccountNumberFormatData> accountNumberFormatData = this.accountNumberFormatReadPlatformService
+                .getAllAccountNumberFormats();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
+                AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{accountNumberFormatId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@Context final UriInfo uriInfo, @PathParam("accountNumberFormatId") final Long accountNumberFormatId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(AccountNumberFormatConstants.ENTITY_NAME);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        AccountNumberFormatData accountNumberFormatData = this.accountNumberFormatReadPlatformService
+                .getAccountNumberFormat(accountNumberFormatId);
+        if (settings.isTemplate()) {
+            final AccountNumberFormatData templateData = this.accountNumberFormatReadPlatformService.retrieveTemplate(EntityAccountType
+                    .fromInt(accountNumberFormatData.getAccountType().getId().intValue()));
+            accountNumberFormatData.templateOnTop(templateData.getAccountTypeOptions(), templateData.getPrefixTypeOptions());
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
+                AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createAccountNumberFormat() //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{accountNumberFormatId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("accountNumberFormatId") final Long accountNumberFormatId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateAccountNumberFormat(accountNumberFormatId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{accountNumberFormatId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("accountNumberFormatId") final Long accountNumberFormatId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteAccountNumberFormat(accountNumberFormatId) //
+                .build(); //
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatData.java
new file mode 100644
index 0000000..f71b389
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatData.java
@@ -0,0 +1,80 @@
+/**
+ * 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.accountnumberformat.data;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class AccountNumberFormatData implements Serializable {
+
+    private final Long id;
+
+    private final EnumOptionData accountType;
+    private final EnumOptionData prefixType;
+
+    // template options
+    private List<EnumOptionData> accountTypeOptions;
+    private Map<String, List<EnumOptionData>> prefixTypeOptions;
+
+    public AccountNumberFormatData(final Long id, final EnumOptionData accountType, final EnumOptionData prefixType) {
+        this(id, accountType, prefixType, null, null);
+    }
+
+    public AccountNumberFormatData(final List<EnumOptionData> accountTypeOptions, Map<String, List<EnumOptionData>> prefixTypeOptions) {
+        this(null, null, null, accountTypeOptions, prefixTypeOptions);
+    }
+
+    public void templateOnTop(List<EnumOptionData> accountTypeOptions, Map<String, List<EnumOptionData>> prefixTypeOptions) {
+        this.accountTypeOptions = accountTypeOptions;
+        this.prefixTypeOptions = prefixTypeOptions;
+    }
+
+    private AccountNumberFormatData(final Long id, final EnumOptionData accountType, final EnumOptionData prefixType,
+            final List<EnumOptionData> accountTypeOptions, Map<String, List<EnumOptionData>> prefixTypeOptions) {
+        this.id = id;
+        this.accountType = accountType;
+        this.prefixType = prefixType;
+        this.accountTypeOptions = accountTypeOptions;
+        this.prefixTypeOptions = prefixTypeOptions;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public EnumOptionData getAccountType() {
+        return this.accountType;
+    }
+
+    public EnumOptionData getPrefixType() {
+        return this.prefixType;
+    }
+
+    public List<EnumOptionData> getAccountTypeOptions() {
+        return this.accountTypeOptions;
+    }
+
+    public Map<String, List<EnumOptionData>> getPrefixTypeOptions() {
+        return this.prefixTypeOptions;
+    }
+
+}
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
new file mode 100644
index 0000000..90cac29
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java
@@ -0,0 +1,174 @@
+/**
+ * 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.accountnumberformat.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+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.accountnumberformat.domain.AccountNumberFormatEnumerations;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatEnumerations.AccountNumberPrefixType;
+import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatConstants;
+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 class AccountNumberFormatDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public AccountNumberFormatDataValidator(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,
+                AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(AccountNumberFormatConstants.ENTITY_NAME);
+
+        final Integer accountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(AccountNumberFormatConstants.accountTypeParamName,
+                element);
+        baseDataValidator.reset().parameter(AccountNumberFormatConstants.accountTypeParamName).value(accountType).notNull()
+                .integerGreaterThanZero().inMinMaxRange(EntityAccountType.getMinValue(), EntityAccountType.getMaxValue());
+
+        if (this.fromApiJsonHelper.parameterExists(AccountNumberFormatConstants.prefixTypeParamName, element)) {
+            final Integer prefixType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    AccountNumberFormatConstants.prefixTypeParamName, element);
+            DataValidatorBuilder dataValidatorForValidatingPrefixType = baseDataValidator.reset()
+                    .parameter(AccountNumberFormatConstants.prefixTypeParamName).value(prefixType).notNull().integerGreaterThanZero();
+
+            /**
+             * Permitted values for prefix type vary based on the actual
+             * selected accountType, carry out this validation only if data
+             * validation errors do not exist for both entity type and prefix
+             * type
+             **/
+            boolean areAccountTypeAndPrefixTypeValid = true;
+            for (ApiParameterError apiParameterError : dataValidationErrors) {
+                if (apiParameterError.getParameterName().equalsIgnoreCase(AccountNumberFormatConstants.accountTypeParamName)
+                        || apiParameterError.getParameterName().equalsIgnoreCase(AccountNumberFormatConstants.prefixTypeParamName)) {
+                    areAccountTypeAndPrefixTypeValid = false;
+                }
+            }
+
+            if (areAccountTypeAndPrefixTypeValid) {
+                EntityAccountType entityAccountType = EntityAccountType.fromInt(accountType);
+                Set<Integer> validAccountNumberPrefixes = determineValidAccountNumberPrefixes(entityAccountType);
+                dataValidatorForValidatingPrefixType.isOneOfTheseValues(validAccountNumberPrefixes.toArray());
+            }
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+	public Set<Integer> determineValidAccountNumberPrefixes(
+			EntityAccountType entityAccountType) {
+		Set<AccountNumberPrefixType> validAccountNumberPrefixes = new HashSet<>();
+
+		switch (entityAccountType) {
+		case CLIENT:
+			validAccountNumberPrefixes = AccountNumberFormatEnumerations.accountNumberPrefixesForClientAccounts;
+			break;
+
+		case LOAN:
+			validAccountNumberPrefixes = AccountNumberFormatEnumerations.accountNumberPrefixesForLoanAccounts;
+			break;
+
+		case SAVINGS:
+			validAccountNumberPrefixes = AccountNumberFormatEnumerations.accountNumberPrefixesForSavingsAccounts;
+			break;
+
+		case CENTER:
+			validAccountNumberPrefixes = AccountNumberFormatEnumerations.accountNumberPrefixesForCenters;
+			break;
+			
+		case GROUP:
+			validAccountNumberPrefixes = AccountNumberFormatEnumerations.accountNumberPrefixesForGroups;
+			break;
+		}
+
+        Set<Integer> validAccountNumberPrefixValues = new HashSet<>();
+        for (AccountNumberPrefixType validAccountNumberPrefix : validAccountNumberPrefixes) {
+            validAccountNumberPrefixValues.add(validAccountNumberPrefix.getValue());
+        }
+        return validAccountNumberPrefixValues;
+    }
+
+    public void validateForUpdate(final String json, EntityAccountType entityAccountType) {
+
+        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);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(AccountNumberFormatConstants.ENTITY_NAME);
+
+        boolean atLeastOneParameterPassedForUpdate = false;
+        if (this.fromApiJsonHelper.parameterExists(AccountNumberFormatConstants.prefixTypeParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            Set<Integer> validAccountNumberPrefixes = determineValidAccountNumberPrefixes(entityAccountType);
+            final Integer prefixType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    AccountNumberFormatConstants.prefixTypeParamName, element);
+            baseDataValidator.reset().parameter(AccountNumberFormatConstants.prefixTypeParamName).value(prefixType).notNull()
+                    .integerGreaterThanZero().isOneOfTheseValues(validAccountNumberPrefixes.toArray());
+        }
+
+        if (!atLeastOneParameterPassedForUpdate) {
+            final Object forceError = null;
+            baseDataValidator.reset().anyOfNotNull(forceError);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            //
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormat.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormat.java
new file mode 100644
index 0000000..064c1a6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormat.java
@@ -0,0 +1,78 @@
+/**
+ * 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.accountnumberformat.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatEnumerations.AccountNumberPrefixType;
+import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatConstants;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_TABLE_NAME, uniqueConstraints = { @UniqueConstraint(columnNames = { AccountNumberFormatConstants.ACCOUNT_TYPE_ENUM_COLUMN_NAME }, name = AccountNumberFormatConstants.ACCOUNT_TYPE_UNIQUE_CONSTRAINT_NAME) })
+public class AccountNumberFormat extends AbstractPersistable<Long> {
+
+    @Column(name = AccountNumberFormatConstants.ACCOUNT_TYPE_ENUM_COLUMN_NAME, nullable = false)
+    private Integer accountTypeEnum;
+
+    @Column(name = AccountNumberFormatConstants.PREFIX_TYPE_ENUM_COLUMN_NAME, nullable = false)
+    private Integer prefixEnum;
+
+    protected AccountNumberFormat() {
+        //
+    }
+
+    public AccountNumberFormat(EntityAccountType entityAccountType, AccountNumberPrefixType prefixType) {
+        this.accountTypeEnum = entityAccountType.getValue();
+        if (prefixType != null) {
+            this.prefixEnum = prefixType.getValue();
+        }
+    }
+
+    public Integer getAccountTypeEnum() {
+        return this.accountTypeEnum;
+    }
+
+    public EntityAccountType getAccountType() {
+        return EntityAccountType.fromInt(this.accountTypeEnum);
+    }
+
+    private void setAccountTypeEnum(Integer accountTypeEnum) {
+        this.accountTypeEnum = accountTypeEnum;
+    }
+
+    public void setAccountType(EntityAccountType entityAccountType) {
+        setAccountTypeEnum(entityAccountType.getValue());
+    }
+
+    public Integer getPrefixEnum() {
+        return this.prefixEnum;
+    }
+
+    private void setPrefixEnum(Integer prefixEnum) {
+        this.prefixEnum = prefixEnum;
+    }
+
+    public void setPrefix(AccountNumberPrefixType accountNumberPrefixType) {
+        setPrefixEnum(accountNumberPrefixType.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatEnumerations.java
new file mode 100644
index 0000000..b5fa6dd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatEnumerations.java
@@ -0,0 +1,141 @@
+/**
+ * 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.accountnumberformat.domain;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class AccountNumberFormatEnumerations {
+
+    public final static Set<AccountNumberPrefixType> accountNumberPrefixesForClientAccounts = new HashSet<>(Arrays.asList(
+            AccountNumberPrefixType.OFFICE_NAME, AccountNumberPrefixType.CLIENT_TYPE));
+    public final static Set<AccountNumberPrefixType> accountNumberPrefixesForLoanAccounts = new HashSet<>(Arrays.asList(
+            AccountNumberPrefixType.OFFICE_NAME, AccountNumberPrefixType.LOAN_PRODUCT_SHORT_NAME));
+    public final static Set<AccountNumberPrefixType> accountNumberPrefixesForSavingsAccounts = new HashSet<>(Arrays.asList(
+            AccountNumberPrefixType.OFFICE_NAME, AccountNumberPrefixType.SAVINGS_PRODUCT_SHORT_NAME));
+    public final static Set<AccountNumberPrefixType> accountNumberPrefixesForCenters = new HashSet<>(Arrays.asList(
+    		AccountNumberPrefixType.OFFICE_NAME));
+    public final static Set<AccountNumberPrefixType> accountNumberPrefixesForGroups = new HashSet<>(Arrays.asList(
+    		AccountNumberPrefixType.OFFICE_NAME));
+
+    public enum AccountNumberPrefixType {
+        OFFICE_NAME(1, "accountNumberPrefixType.officeName"), CLIENT_TYPE(101, "accountNumberPrefixType.clientType"), LOAN_PRODUCT_SHORT_NAME(
+                201, "accountNumberPrefixType.loanProductShortName"), SAVINGS_PRODUCT_SHORT_NAME(301,
+                "accountNumberPrefixType.savingsProductShortName");
+
+        private final Integer value;
+        private final String code;
+
+        private AccountNumberPrefixType(final Integer value, final String code) {
+            this.value = value;
+            this.code = code;
+        }
+
+        public Integer getValue() {
+            return this.value;
+        }
+
+        public String getCode() {
+            return this.code;
+        }
+
+        private static final Map<Integer, AccountNumberPrefixType> intToEnumMap = new HashMap<>();
+        private static int minValue;
+        private static int maxValue;
+        static {
+            int i = 0;
+            for (final AccountNumberPrefixType type : AccountNumberPrefixType.values()) {
+                if (i == 0) {
+                    minValue = type.value;
+                }
+                intToEnumMap.put(type.value, type);
+                if (minValue >= type.value) {
+                    minValue = type.value;
+                }
+                if (maxValue < type.value) {
+                    maxValue = type.value;
+                }
+                i = i + 1;
+            }
+        }
+
+        public static AccountNumberPrefixType fromInt(final int i) {
+            final AccountNumberPrefixType type = intToEnumMap.get(Integer.valueOf(i));
+            return type;
+        }
+
+        public static int getMinValue() {
+            return minValue;
+        }
+
+        public static int getMaxValue() {
+            return maxValue;
+        }
+
+    }
+
+    public static EnumOptionData entityAccountType(final Integer accountTypeId) {
+        return AccountNumberFormatEnumerations.entityAccountType(EntityAccountType.fromInt(accountTypeId));
+    }
+
+    public static List<EnumOptionData> entityAccountType(final EntityAccountType[] entityAccountTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final EntityAccountType accountType : entityAccountTypes) {
+            optionDatas.add(entityAccountType(accountType));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData entityAccountType(final EntityAccountType accountType) {
+        final EnumOptionData optionData = new EnumOptionData(accountType.getValue().longValue(), accountType.getCode(),
+                accountType.toString());
+        return optionData;
+    }
+
+    public static EnumOptionData accountNumberPrefixType(final Integer accountNumberPrefixTypeId) {
+        return AccountNumberFormatEnumerations.entityAccountType(AccountNumberPrefixType.fromInt(accountNumberPrefixTypeId));
+    }
+
+    public static List<EnumOptionData> accountNumberPrefixType(final AccountNumberPrefixType[] accountNumberPrefixTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final AccountNumberPrefixType accountNumberPrefixType : accountNumberPrefixTypes) {
+            optionDatas.add(entityAccountType(accountNumberPrefixType));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData entityAccountType(final AccountNumberPrefixType accountNumberPrefixType) {
+        final EnumOptionData optionData = new EnumOptionData(accountNumberPrefixType.getValue().longValue(),
+                accountNumberPrefixType.getCode(), accountNumberPrefixType.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> accountNumberPrefixType(Object[] array) {
+        AccountNumberPrefixType[] accountNumberPrefixTypes = Arrays.copyOf(array, array.length, AccountNumberPrefixType[].class);
+        return accountNumberPrefixType(accountNumberPrefixTypes);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatRepository.java
new file mode 100644
index 0000000..3557bad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatRepository.java
@@ -0,0 +1,33 @@
+/**
+ * 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.accountnumberformat.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 AccountNumberFormatRepository extends JpaRepository<AccountNumberFormat, Long>,
+        JpaSpecificationExecutor<AccountNumberFormat> {
+
+    public static final String FIND_ACCOUNT_NUMBER_FORMAT_FOR_ENTITY = "from  AccountNumberFormat anf where anf.accountTypeEnum = :accountTypeEnum";
+
+    @Query(FIND_ACCOUNT_NUMBER_FORMAT_FOR_ENTITY)
+    AccountNumberFormat findOneByAccountTypeEnum(@Param("accountTypeEnum") Integer accountTypeEnum);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatRepositoryWrapper.java
new file mode 100644
index 0000000..741b8e7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatRepositoryWrapper.java
@@ -0,0 +1,56 @@
+/**
+ * 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.accountnumberformat.domain;
+
+import org.apache.fineract.infrastructure.accountnumberformat.exception.AccountNumberFormatNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class AccountNumberFormatRepositoryWrapper {
+
+    private final AccountNumberFormatRepository repository;
+
+    @Autowired
+    public AccountNumberFormatRepositoryWrapper(final AccountNumberFormatRepository repository) {
+        this.repository = repository;
+    }
+
+    public AccountNumberFormat findOneWithNotFoundDetection(final Long id) {
+        final AccountNumberFormat accountNumberFormat = this.repository.findOne(id);
+        if (accountNumberFormat == null) { throw new AccountNumberFormatNotFoundException(id); }
+        return accountNumberFormat;
+    }
+
+    public void save(final AccountNumberFormat accountNumberFormat) {
+        this.repository.save(accountNumberFormat);
+    }
+
+    public void saveAndFlush(final AccountNumberFormat accountNumberFormat) {
+        this.repository.saveAndFlush(accountNumberFormat);
+    }
+
+    public void delete(final AccountNumberFormat accountNumberFormat) {
+        this.repository.delete(accountNumberFormat);
+    }
+
+    public AccountNumberFormat findByAccountType(final EntityAccountType entityAccountType) {
+        return this.repository.findOneByAccountTypeEnum(entityAccountType.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/EntityAccountType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/EntityAccountType.java
new file mode 100644
index 0000000..2c2a65a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/EntityAccountType.java
@@ -0,0 +1,102 @@
+/**
+ * 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.accountnumberformat.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum EntityAccountType {
+    CLIENT(1, "accountType.client"), LOAN(2, "accountType.loan"), SAVINGS(3, "accountType.savings"), CENTER(4, "accountType.center"), 
+    GROUP(5, "accountType.group");
+
+    private final Integer value;
+    private final String code;
+
+    private EntityAccountType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, EntityAccountType> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final EntityAccountType type : EntityAccountType.values()) {
+            if (i == 0) {
+                minValue = type.value;
+            }
+            intToEnumMap.put(type.value, type);
+            if (minValue >= type.value) {
+                minValue = type.value;
+            }
+            if (maxValue < type.value) {
+                maxValue = type.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static EntityAccountType fromInt(final int i) {
+        final EntityAccountType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public boolean isClientAccount() {
+        return this.value.equals(EntityAccountType.CLIENT.getValue());
+    }
+
+    public boolean isLoanAccount() {
+        return this.value.equals(EntityAccountType.LOAN.getValue());
+    }
+
+    public boolean isSavingsAccount() {
+        return this.value.equals(EntityAccountType.SAVINGS.getValue());
+    }
+    
+    public Boolean isCenterAccount(){
+    	return this.value.equals(EntityAccountType.CENTER.getValue());
+    }
+    
+    public Boolean isGroupAccount(){
+    	return this.value.equals(EntityAccountType.GROUP.getValue());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/exception/AccountNumberFormatNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/exception/AccountNumberFormatNotFoundException.java
new file mode 100644
index 0000000..4ff275a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/exception/AccountNumberFormatNotFoundException.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.accountnumberformat.exception;
+
+import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatConstants;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when client resources are not found.
+ */
+public class AccountNumberFormatNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public AccountNumberFormatNotFoundException(final Long id) {
+        super(AccountNumberFormatConstants.EXCEPTION_ACCOUNT_NUMBER_FORMAT_NOT_FOUND, "AccountNumber format with identifier " + id
+                + " does not exist", id);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/handler/CreateAccountNumberFormatCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/handler/CreateAccountNumberFormatCommandHandler.java
new file mode 100644
index 0000000..6d0596e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/handler/CreateAccountNumberFormatCommandHandler.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.infrastructure.accountnumberformat.handler;
+
+import javax.transaction.Transactional;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatWritePlatformService;
+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;
+
+@Service
+@CommandType(entity = "ACCOUNTNUMBERFORMAT", action = "CREATE")
+public class CreateAccountNumberFormatCommandHandler implements NewCommandSourceHandler {
+
+    private final AccountNumberFormatWritePlatformService accountNumberFormatWritePlatformService;
+
+    @Autowired
+    public CreateAccountNumberFormatCommandHandler(final AccountNumberFormatWritePlatformService accountNumberFormatWritePlatformService) {
+        this.accountNumberFormatWritePlatformService = accountNumberFormatWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.accountNumberFormatWritePlatformService.createAccountNumberFormat(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/handler/DeleteAccountNumberFormatCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/handler/DeleteAccountNumberFormatCommandHandler.java
new file mode 100644
index 0000000..dc47e28
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/handler/DeleteAccountNumberFormatCommandHandler.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.infrastructure.accountnumberformat.handler;
+
+import javax.transaction.Transactional;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatWritePlatformService;
+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;
+
+@Service
+@CommandType(entity = "ACCOUNTNUMBERFORMAT", action = "DELETE")
+public class DeleteAccountNumberFormatCommandHandler implements NewCommandSourceHandler {
+
+    private final AccountNumberFormatWritePlatformService accountNumberFormatWritePlatformService;
+
+    @Autowired
+    public DeleteAccountNumberFormatCommandHandler(final AccountNumberFormatWritePlatformService accountNumberFormatWritePlatformService) {
+        this.accountNumberFormatWritePlatformService = accountNumberFormatWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.accountNumberFormatWritePlatformService.deleteAccountNumberFormat(command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/handler/UpdateAccountNumberFormatCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/handler/UpdateAccountNumberFormatCommandHandler.java
new file mode 100644
index 0000000..975de3b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/handler/UpdateAccountNumberFormatCommandHandler.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.infrastructure.accountnumberformat.handler;
+
+import javax.transaction.Transactional;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatWritePlatformService;
+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;
+
+@Service
+@CommandType(entity = "ACCOUNTNUMBERFORMAT", action = "UPDATE")
+public class UpdateAccountNumberFormatCommandHandler implements NewCommandSourceHandler {
+
+    private final AccountNumberFormatWritePlatformService accountNumberFormatWritePlatformService;
+
+    @Autowired
+    public UpdateAccountNumberFormatCommandHandler(final AccountNumberFormatWritePlatformService accountNumberFormatWritePlatformService) {
+        this.accountNumberFormatWritePlatformService = accountNumberFormatWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.accountNumberFormatWritePlatformService.updateAccountNumberFormat(command.entityId(), command);
+    }
+
+}
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
new file mode 100644
index 0000000..4836d8b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java
@@ -0,0 +1,74 @@
+/**
+ * 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.accountnumberformat.service;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.accountnumberformat.data.AccountNumberFormatData;
+
+public class AccountNumberFormatConstants {
+
+    // resource name for validation
+    public static final String ENTITY_NAME = "accountNumberFormat";
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // REST end point
+    public static final String resourceRelativeURL = "/accountnumberformats";
+
+    // request parameters
+    public static final String idParamName = "id";
+    public static final String accountTypeParamName = "accountType";
+    public static final String prefixTypeParamName = "prefixType";
+
+    // response parameters
+
+    // associations related part of response
+
+    // template related part of response
+    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";
+    public static final String EXCEPTION_ACCOUNT_NUMBER_FORMAT_NOT_FOUND = "error.msg.account.number.format.id.invalid";
+    // JPA related constants
+    public static final String ACCOUNT_NUMBER_FORMAT_TABLE_NAME = "c_account_number_format";
+    public static final String ACCOUNT_TYPE_ENUM_COLUMN_NAME = "account_type_enum";
+    public static final String PREFIX_TYPE_ENUM_COLUMN_NAME = "prefix_type_enum";
+    public static final String ACCOUNT_TYPE_UNIQUE_CONSTRAINT_NAME = "account_type_enum";
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformService.java
new file mode 100644
index 0000000..713749c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformService.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.accountnumberformat.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.accountnumberformat.data.AccountNumberFormatData;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+
+public interface AccountNumberFormatReadPlatformService {
+
+    List<AccountNumberFormatData> getAllAccountNumberFormats();
+
+    AccountNumberFormatData getAccountNumberFormat(Long id);
+
+    AccountNumberFormatData retrieveTemplate(EntityAccountType entityAccountTypeForTemplate);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java
new file mode 100644
index 0000000..17a1e39
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java
@@ -0,0 +1,157 @@
+/**
+ * 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.accountnumberformat.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.accountnumberformat.data.AccountNumberFormatData;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatEnumerations;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatEnumerations.AccountNumberPrefixType;
+import org.apache.fineract.infrastructure.accountnumberformat.exception.AccountNumberFormatNotFoundException;
+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.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 AccountNumberFormatReadPlatformServiceImpl implements AccountNumberFormatReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    // data mapper
+    private final AccountNumberFormatMapper accountNumberFormatMapper = new AccountNumberFormatMapper();
+
+    @Autowired
+    public AccountNumberFormatReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class AccountNumberFormatMapper implements RowMapper<AccountNumberFormatData> {
+
+        private final String schema;
+
+        public AccountNumberFormatMapper() {
+            final StringBuilder builder = new StringBuilder(400);
+
+            builder.append(" anf.id as id, anf.account_type_enum as accountTypeEnum, anf.prefix_type_enum as prefixTypeEnum");
+            builder.append(" from c_account_number_format anf ");
+
+            this.schema = builder.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+
+        @Override
+        public AccountNumberFormatData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Integer accountTypeEnum = rs.getInt("accountTypeEnum");
+            final Integer prefixTypeEnum = JdbcSupport.getInteger(rs, "prefixTypeEnum");
+
+            final EnumOptionData accountNumberType = AccountNumberFormatEnumerations.entityAccountType(accountTypeEnum);
+            EnumOptionData prefixType = null;
+            if (prefixTypeEnum != null) {
+                prefixType = AccountNumberFormatEnumerations.accountNumberPrefixType(prefixTypeEnum);
+            }
+            return new AccountNumberFormatData(id, accountNumberType, prefixType);
+        }
+    }
+
+    @Override
+    public List<AccountNumberFormatData> getAllAccountNumberFormats() {
+        String sql = "select " + this.accountNumberFormatMapper.schema();
+        return this.jdbcTemplate.query(sql, this.accountNumberFormatMapper, new Object[] {});
+    }
+
+    @Override
+    public AccountNumberFormatData getAccountNumberFormat(Long id) {
+        try {
+            final String sql = "select " + this.accountNumberFormatMapper.schema() + " where anf.id = ?";
+
+            final AccountNumberFormatData accountNumberFormatData = this.jdbcTemplate.queryForObject(sql, this.accountNumberFormatMapper,
+                    new Object[] { id });
+            return accountNumberFormatData;
+        } catch (final EmptyResultDataAccessException e) {
+            throw new AccountNumberFormatNotFoundException(id);
+        }
+    }
+
+    @Override
+    public AccountNumberFormatData retrieveTemplate(EntityAccountType entityAccountTypeForTemplate) {
+        final List<EnumOptionData> entityAccountTypeOptions = AccountNumberFormatEnumerations.entityAccountType(EntityAccountType.values());
+
+        Map<String, List<EnumOptionData>> accountNumberPrefixTypeOptions = new HashMap<>();
+        /***
+         * If an Account type is passed in, return prefixes only for the passed
+         * in account type, else return all allowed prefixes keyed by all
+         * possible entity type
+         **/
+        if (entityAccountTypeForTemplate != null) {
+            determinePrefixTypesForAccounts(accountNumberPrefixTypeOptions, entityAccountTypeForTemplate);
+        } else {
+            for (EntityAccountType entityAccountType : EntityAccountType.values()) {
+                determinePrefixTypesForAccounts(accountNumberPrefixTypeOptions, entityAccountType);
+
+            }
+        }
+        return new AccountNumberFormatData(entityAccountTypeOptions, accountNumberPrefixTypeOptions);
+    }
+
+    public void determinePrefixTypesForAccounts(Map<String, List<EnumOptionData>> accountNumberPrefixTypeOptions,
+            EntityAccountType entityAccountType) {
+        Set<AccountNumberPrefixType> accountNumberPrefixTypesSet = new HashSet<>();
+        switch (entityAccountType) {
+            case CLIENT:
+                accountNumberPrefixTypesSet = AccountNumberFormatEnumerations.accountNumberPrefixesForClientAccounts;
+            break;
+            case LOAN:
+                accountNumberPrefixTypesSet = AccountNumberFormatEnumerations.accountNumberPrefixesForLoanAccounts;
+            break;
+            case SAVINGS:
+                accountNumberPrefixTypesSet = AccountNumberFormatEnumerations.accountNumberPrefixesForSavingsAccounts;
+            break;
+            case CENTER :
+                accountNumberPrefixTypesSet = AccountNumberFormatEnumerations.accountNumberPrefixesForCenters;
+            break;
+            case GROUP :
+                accountNumberPrefixTypesSet = AccountNumberFormatEnumerations.accountNumberPrefixesForGroups;
+            break;
+        }
+
+        Object[] array = accountNumberPrefixTypesSet.toArray();
+        AccountNumberPrefixType[] accountNumberPrefixTypes = Arrays.copyOf(array, array.length, AccountNumberPrefixType[].class);
+
+        accountNumberPrefixTypeOptions.put(entityAccountType.getCode(),
+                AccountNumberFormatEnumerations.accountNumberPrefixType(accountNumberPrefixTypes));
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatWritePlatformService.java
new file mode 100644
index 0000000..444bba6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatWritePlatformService.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.infrastructure.accountnumberformat.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface AccountNumberFormatWritePlatformService {
+
+    CommandProcessingResult createAccountNumberFormat(JsonCommand command);
+
+    CommandProcessingResult updateAccountNumberFormat(Long accountNumberFormatId, JsonCommand command);
+
+    CommandProcessingResult deleteAccountNumberFormat(Long accountNumberFormatId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..b1f4313
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,149 @@
+/**
+ * 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.accountnumberformat.service;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.transaction.Transactional;
+
+import org.apache.fineract.infrastructure.accountnumberformat.data.AccountNumberFormatDataValidator;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatEnumerations.AccountNumberPrefixType;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AccountNumberFormatWritePlatformServiceJpaRepositoryImpl implements AccountNumberFormatWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(AccountNumberFormatWritePlatformServiceJpaRepositoryImpl.class);
+    private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
+    private final AccountNumberFormatDataValidator accountNumberFormatDataValidator;
+
+    @Autowired
+    AccountNumberFormatWritePlatformServiceJpaRepositoryImpl(final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
+            final AccountNumberFormatDataValidator accountNumberFormatDataValidator) {
+        this.accountNumberFormatRepository = accountNumberFormatRepository;
+        this.accountNumberFormatDataValidator = accountNumberFormatDataValidator;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult createAccountNumberFormat(JsonCommand command) {
+        try {
+            this.accountNumberFormatDataValidator.validateForCreate(command.json());
+
+            final Integer accountTypeId = command.integerValueSansLocaleOfParameterNamed(AccountNumberFormatConstants.accountTypeParamName);
+            final EntityAccountType entityAccountType = EntityAccountType.fromInt(accountTypeId);
+
+            final Integer prefixTypeId = command.integerValueSansLocaleOfParameterNamed(AccountNumberFormatConstants.prefixTypeParamName);
+            AccountNumberPrefixType accountNumberPrefixType = null;
+            if (prefixTypeId != null) {
+                accountNumberPrefixType = AccountNumberPrefixType.fromInt(prefixTypeId);
+            }
+
+            AccountNumberFormat accountNumberFormat = new AccountNumberFormat(entityAccountType, accountNumberPrefixType);
+
+            this.accountNumberFormatRepository.save(accountNumberFormat);
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(accountNumberFormat.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult updateAccountNumberFormat(Long accountNumberFormatId, JsonCommand command) {
+        try {
+
+            final AccountNumberFormat accountNumberFormatForUpdate = this.accountNumberFormatRepository
+                    .findOneWithNotFoundDetection(accountNumberFormatId);
+            EntityAccountType accountType = accountNumberFormatForUpdate.getAccountType();
+
+            this.accountNumberFormatDataValidator.validateForUpdate(command.json(), accountType);
+
+            final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+            if (command.isChangeInIntegerSansLocaleParameterNamed(AccountNumberFormatConstants.prefixTypeParamName,
+                    accountNumberFormatForUpdate.getPrefixEnum())) {
+                final Integer newValue = command.integerValueSansLocaleOfParameterNamed(AccountNumberFormatConstants.prefixTypeParamName);
+                final AccountNumberPrefixType accountNumberPrefixType = AccountNumberPrefixType.fromInt(newValue);
+                actualChanges.put(AccountNumberFormatConstants.prefixTypeParamName, accountNumberPrefixType);
+                accountNumberFormatForUpdate.setPrefix(accountNumberPrefixType);
+            }
+
+            if (!actualChanges.isEmpty()) {
+                this.accountNumberFormatRepository.saveAndFlush(accountNumberFormatForUpdate);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(accountNumberFormatId) //
+                    .with(actualChanges) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult deleteAccountNumberFormat(Long accountNumberFormatId) {
+        AccountNumberFormat accountNumberFormat = this.accountNumberFormatRepository.findOneWithNotFoundDetection(accountNumberFormatId);
+        this.accountNumberFormatRepository.delete(accountNumberFormat);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(accountNumberFormatId) //
+                .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(AccountNumberFormatConstants.ACCOUNT_TYPE_UNIQUE_CONSTRAINT_NAME)) {
+
+            final Integer accountTypeId = command.integerValueSansLocaleOfParameterNamed(AccountNumberFormatConstants.accountTypeParamName);
+            final EntityAccountType entityAccountType = EntityAccountType.fromInt(accountTypeId);
+            throw new PlatformDataIntegrityException(AccountNumberFormatConstants.EXCEPTION_DUPLICATE_ACCOUNT_TYPE,
+                    "Account Format preferences for Account type `" + entityAccountType.getCode() + "` already exists", "externalId",
+                    entityAccountType.getValue(), entityAccountType.getCode());
+        }
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.account.number.format.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+}
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
new file mode 100644
index 0000000..ccbc7fd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.cache;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class CacheApiConstants {
+
+    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/CacheEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheEnumerations.java
new file mode 100644
index 0000000..fbf98cc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheEnumerations.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.infrastructure.cache;
+
+import org.apache.fineract.infrastructure.cache.domain.CacheType;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class CacheEnumerations {
+
+    public static EnumOptionData cacheType(final int id) {
+        return cacheType(CacheType.fromInt(id));
+    }
+
+    public static EnumOptionData cacheType(final CacheType cacheType) {
+        EnumOptionData optionData = new EnumOptionData(CacheType.INVALID.getValue().longValue(), CacheType.INVALID.getCode(), "Invalid");
+        switch (cacheType) {
+            case INVALID:
+                optionData = new EnumOptionData(CacheType.INVALID.getValue().longValue(), CacheType.INVALID.getCode(), "Invalid");
+            break;
+            case NO_CACHE:
+                optionData = new EnumOptionData(CacheType.NO_CACHE.getValue().longValue(), CacheType.NO_CACHE.getCode(), "No cache");
+            break;
+            case SINGLE_NODE:
+                optionData = new EnumOptionData(CacheType.SINGLE_NODE.getValue().longValue(), CacheType.SINGLE_NODE.getCode(),
+                        "Single node");
+            break;
+            case MULTI_NODE:
+                optionData = new EnumOptionData(CacheType.MULTI_NODE.getValue().longValue(), CacheType.MULTI_NODE.getCode(), "Multi node");
+            break;
+        }
+
+        return optionData;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java
new file mode 100644
index 0000000..38d1ac9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.cache;
+
+import org.apache.fineract.infrastructure.cache.service.RuntimeDelegatingCacheManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.CachingConfigurer;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.DefaultKeyGenerator;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@SuppressWarnings("deprecation")
+@Configuration
+@EnableCaching
+public class PlatformCacheConfiguration implements CachingConfigurer {
+
+    @Autowired
+    private RuntimeDelegatingCacheManager delegatingCacheManager;
+
+    @Bean
+    @Override
+    public CacheManager cacheManager() {
+        return this.delegatingCacheManager;
+    }
+
+    @Override
+    public KeyGenerator keyGenerator() {
+        return new DefaultKeyGenerator();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/api/CacheApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/api/CacheApiResource.java
new file mode 100644
index 0000000..fd3d1d3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/api/CacheApiResource.java
@@ -0,0 +1,98 @@
+/**
+ * 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.cache.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;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.cache.data.CacheData;
+import org.apache.fineract.infrastructure.cache.service.RuntimeDelegatingCacheManager;
+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.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/caches")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+public class CacheApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id"));
+    private final String resourceNameForPermissions = "CACHE";
+
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<CacheData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final RuntimeDelegatingCacheManager cacheService;
+
+    @Autowired
+    public CacheApiResource(final PlatformSecurityContext context,
+            @Qualifier("runtimeDelegatingCacheManager") final RuntimeDelegatingCacheManager cacheService,
+            final DefaultToApiJsonSerializer<CacheData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.cacheService = cacheService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<CacheData> codes = this.cacheService.retrieveAll();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, codes, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    public String switchCache(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCache().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..7d7e9fc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
@@ -0,0 +1,83 @@
+/**
+ * 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.cache.command;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.cache.CacheApiConstants;
+import org.apache.fineract.infrastructure.cache.domain.CacheType;
+import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.reflect.TypeToken;
+
+@Service
+@CommandType(entity = "CACHE", action = "UPDATE")
+public class UpdateCacheCommandHandler implements NewCommandSourceHandler {
+
+    private final CacheWritePlatformService cacheService;
+
+    @Autowired
+    public UpdateCacheCommandHandler(final CacheWritePlatformService cacheService) {
+        this.cacheService = cacheService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        command.checkForUnsupportedParameters(typeOfMap, json, CacheApiConstants.REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(CacheApiConstants.RESOURCE_NAME.toLowerCase());
+
+        final int cacheTypeEnum = command.integerValueSansLocaleOfParameterNamed(CacheApiConstants.cacheTypeParameter);
+        baseDataValidator.reset().parameter(CacheApiConstants.cacheTypeParameter).value(Integer.valueOf(cacheTypeEnum)).notNull()
+                .isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3));
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+        final CacheType cacheType = CacheType.fromInt(cacheTypeEnum);
+
+        final Map<String, Object> changes = this.cacheService.switchToCache(cacheType);
+
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).with(changes).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/data/CacheData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/data/CacheData.java
new file mode 100644
index 0000000..3162b73
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/data/CacheData.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.cache.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class CacheData {
+
+    @SuppressWarnings("unused")
+    private final EnumOptionData cacheType;
+    @SuppressWarnings("unused")
+    private final boolean enabled;
+
+    public static CacheData instance(final EnumOptionData cacheType, final boolean enabled) {
+        return new CacheData(cacheType, enabled);
+    }
+
+    private CacheData(final EnumOptionData cacheType, final boolean enabled) {
+        this.cacheType = cacheType;
+        this.enabled = enabled;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/CacheType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/CacheType.java
new file mode 100644
index 0000000..1af333b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/CacheType.java
@@ -0,0 +1,79 @@
+/**
+ * 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.cache.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum CacheType {
+
+    INVALID(0, "cacheType.invalid"), //
+    NO_CACHE(1, "cacheType.noCache"), //
+    SINGLE_NODE(2, "cacheType.singleNode"), //
+    MULTI_NODE(3, "cacheType.multiNode");
+
+    private final Integer value;
+    private final String code;
+
+    private static final Map<Integer, CacheType> intToEnumMap = new HashMap<>();
+
+    static {
+        for (final CacheType type : CacheType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static CacheType fromInt(final Integer value) {
+        CacheType type = intToEnumMap.get(value);
+        if (type == null) {
+            type = INVALID;
+        }
+        return type;
+    }
+
+    private CacheType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString().replaceAll("_", " ");
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isNoCache() {
+        return NO_CACHE.getValue().equals(this.value);
+    }
+
+    public boolean isEhcache() {
+        return SINGLE_NODE.getValue().equals(this.value);
+    }
+
+    public boolean isDistributedCache() {
+        return MULTI_NODE.getValue().equals(this.value);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/PlatformCache.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/PlatformCache.java
new file mode 100644
index 0000000..9af367b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/PlatformCache.java
@@ -0,0 +1,57 @@
+/**
+ * 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.cache.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "c_cache")
+public class PlatformCache extends AbstractPersistable<Long> {
+
+    @Column(name = "cache_type_enum")
+    private Integer cacheType;
+
+    protected PlatformCache() {
+        this.cacheType = null;
+    }
+
+    public PlatformCache(final CacheType cacheType) {
+        this.cacheType = cacheType.getValue();
+    }
+
+    public boolean isNoCachedEnabled() {
+        return CacheType.fromInt(this.cacheType).isNoCache();
+    }
+
+    public boolean isEhcacheEnabled() {
+        return CacheType.fromInt(this.cacheType).isEhcache();
+    }
+
+    public boolean isDistributedCacheEnabled() {
+        return CacheType.fromInt(this.cacheType).isDistributedCache();
+    }
+
+    public void update(final CacheType cacheType) {
+        this.cacheType = cacheType.getValue();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/PlatformCacheRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/PlatformCacheRepository.java
new file mode 100644
index 0000000..d4eb92a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/PlatformCacheRepository.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.infrastructure.cache.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface PlatformCacheRepository extends JpaRepository<PlatformCache, Long>, JpaSpecificationExecutor<PlatformCache> {
+    //
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/CacheWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/CacheWritePlatformService.java
new file mode 100644
index 0000000..d917727
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/CacheWritePlatformService.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.cache.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.cache.domain.CacheType;
+
+public interface CacheWritePlatformService {
+
+    Map<String, Object> switchToCache(CacheType cacheType);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/CacheWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/CacheWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..1b006f1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/CacheWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,57 @@
+/**
+ * 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.cache.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.cache.domain.CacheType;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class CacheWritePlatformServiceJpaRepositoryImpl implements CacheWritePlatformService {
+
+    private final ConfigurationDomainService configurationDomainService;
+    private final RuntimeDelegatingCacheManager cacheService;
+
+    @Autowired
+    public CacheWritePlatformServiceJpaRepositoryImpl(final ConfigurationDomainService configurationDomainService,
+            @Qualifier("runtimeDelegatingCacheManager") final RuntimeDelegatingCacheManager cacheService) {
+        this.configurationDomainService = configurationDomainService;
+        this.cacheService = cacheService;
+    }
+
+    @Transactional
+    @Override
+    public Map<String, Object> switchToCache(final CacheType toCacheType) {
+
+        final boolean ehCacheEnabled = this.configurationDomainService.isEhcacheEnabled();
+
+        final Map<String, Object> changes = this.cacheService.switchToCache(ehCacheEnabled, toCacheType);
+
+        if (!changes.isEmpty()) {
+            this.configurationDomainService.updateCache(toCacheType);
+        }
+
+        return changes;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java
new file mode 100644
index 0000000..9f21a5c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java
@@ -0,0 +1,126 @@
+/**
+ * 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.cache.service;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.cache.CacheApiConstants;
+import org.apache.fineract.infrastructure.cache.CacheEnumerations;
+import org.apache.fineract.infrastructure.cache.data.CacheData;
+import org.apache.fineract.infrastructure.cache.domain.CacheType;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.ehcache.EhCacheCacheManager;
+import org.springframework.cache.support.NoOpCacheManager;
+import org.springframework.stereotype.Component;
+
+/**
+ * At present this implementation of {@link CacheManager} just delegates to the
+ * real {@link CacheManager} to use.
+ * 
+ * By default it is {@link NoOpCacheManager} but we can change that by checking
+ * some persisted configuration in the database on startup and allow user to
+ * switch implementation through UI/API
+ */
+@Component(value = "runtimeDelegatingCacheManager")
+public class RuntimeDelegatingCacheManager implements CacheManager {
+
+    private final EhCacheCacheManager ehcacheCacheManager;
+    private final CacheManager noOpCacheManager = new NoOpCacheManager();
+    private CacheManager currentCacheManager;
+
+    @Autowired
+    public RuntimeDelegatingCacheManager(final EhCacheCacheManager ehCacheCacheManager) {
+        this.ehcacheCacheManager = ehCacheCacheManager;
+        this.currentCacheManager = this.noOpCacheManager;
+    }
+
+    @Override
+    public Cache getCache(final String name) {
+        return this.currentCacheManager.getCache(name);
+    }
+
+    @Override
+    public Collection<String> getCacheNames() {
+        return this.currentCacheManager.getCacheNames();
+    }
+
+    public Collection<CacheData> retrieveAll() {
+
+        final boolean noCacheEnabled = this.currentCacheManager instanceof NoOpCacheManager;
+        final boolean ehcacheEnabled = this.currentCacheManager instanceof EhCacheCacheManager;
+
+        // final boolean distributedCacheEnabled = false;
+
+        final EnumOptionData noCacheType = CacheEnumerations.cacheType(CacheType.NO_CACHE);
+        final EnumOptionData singleNodeCacheType = CacheEnumerations.cacheType(CacheType.SINGLE_NODE);
+        // final EnumOptionData multiNodeCacheType =
+        // CacheEnumerations.cacheType(CacheType.MULTI_NODE);
+
+        final CacheData noCache = CacheData.instance(noCacheType, noCacheEnabled);
+        final CacheData singleNodeCache = CacheData.instance(singleNodeCacheType, ehcacheEnabled);
+        // final CacheData distributedCache =
+        // CacheData.instance(multiNodeCacheType, distributedCacheEnabled);
+
+        final Collection<CacheData> caches = Arrays.asList(noCache, singleNodeCache);
+        return caches;
+    }
+
+    public Map<String, Object> switchToCache(final boolean ehcacheEnabled, final CacheType toCacheType) {
+
+        final Map<String, Object> changes = new HashMap<>();
+
+        final boolean noCacheEnabled = !ehcacheEnabled;
+        final boolean distributedCacheEnabled = !ehcacheEnabled;
+
+        switch (toCacheType) {
+            case INVALID:
+            break;
+            case NO_CACHE:
+                if (!noCacheEnabled) {
+                    changes.put(CacheApiConstants.cacheTypeParameter, toCacheType.getValue());
+                }
+                this.currentCacheManager = this.noOpCacheManager;
+            break;
+            case SINGLE_NODE:
+                if (!ehcacheEnabled) {
+                    changes.put(CacheApiConstants.cacheTypeParameter, toCacheType.getValue());
+                    clearEhCache();
+                }
+                this.currentCacheManager = this.ehcacheCacheManager;
+            break;
+            case MULTI_NODE:
+                if (!distributedCacheEnabled) {
+                    changes.put(CacheApiConstants.cacheTypeParameter, toCacheType.getValue());
+                }
+            break;
+        }
+
+        return changes;
+    }
+
+    private void clearEhCache() {
+        this.ehcacheCacheManager.getCacheManager().clearAll();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/CodeConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/CodeConstants.java
new file mode 100644
index 0000000..90f71a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/CodeConstants.java
@@ -0,0 +1,59 @@
+/**
+ * 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.codes;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class CodeConstants {
+
+    /***
+     * Enum of all parameters passed in while creating/updating a code and code
+     * value
+     ***/
+    public static enum CODEVALUE_JSON_INPUT_PARAMS {
+        CODEVALUE_ID("id"), NAME("name"), POSITION("position"), DESCRIPTION("description"), IS_ACTIVE("isActive");
+
+        private final String value;
+
+        private CODEVALUE_JSON_INPUT_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+        static {
+            for (final CODEVALUE_JSON_INPUT_PARAMS type : CODEVALUE_JSON_INPUT_PARAMS.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;
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResource.java
new file mode 100644
index 0000000..77f74f7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResource.java
@@ -0,0 +1,148 @@
+/**
+ * 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.codes.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.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.codes.data.CodeData;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+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("/codes/{codeId}/codevalues")
+@Component
+@Scope("singleton")
+public class CodeValuesApiResource {
+
+    /**
+     * The set of parameters that are supported in response for {@link CodeData}
+     */
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "position", "description"));
+    private final String resourceNameForPermissions = "CODEVALUE";
+
+    private final PlatformSecurityContext context;
+    private final CodeValueReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<CodeValueData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public CodeValuesApiResource(final PlatformSecurityContext context, final CodeValueReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<CodeValueData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllCodeValues(@Context final UriInfo uriInfo, @PathParam("codeId") final Long codeId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<CodeValueData> codeValues = this.readPlatformService.retrieveAllCodeValues(codeId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, codeValues, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{codeValueId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCodeValue(@Context final UriInfo uriInfo, @PathParam("codeValueId") final Long codeValueId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final CodeValueData codeValue = this.readPlatformService.retrieveCodeValue(codeValueId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, codeValue, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createCodeValue(@PathParam("codeId") final Long codeId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createCodeValue(codeId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @PUT
+    @Path("{codeValueId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateCodeValue(@PathParam("codeId") final Long codeId, @PathParam("codeValueId") final Long codeValueId,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCodeValue(codeId, codeValueId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{codeValueId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteCodeValue(@PathParam("codeId") final Long codeId, @PathParam("codeValueId") final Long codeValueId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCodeValue(codeId, codeValueId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
new file mode 100644
index 0000000..3326574
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
@@ -0,0 +1,142 @@
+/**
+ * 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.codes.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.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.codes.data.CodeData;
+import org.apache.fineract.infrastructure.codes.service.CodeReadPlatformService;
+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("/codes")
+@Component
+@Scope("singleton")
+public class CodesApiResource {
+
+    /**
+     * The set of parameters that are supported in response for {@link CodeData}
+     */
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "systemDefined"));
+    private final String resourceNameForPermissions = "CODE";
+
+    private final PlatformSecurityContext context;
+    private final CodeReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<CodeData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public CodesApiResource(final PlatformSecurityContext context, final CodeReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<CodeData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCodes(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<CodeData> codes = this.readPlatformService.retrieveAllCodes();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, codes, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createCode(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createCode().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{codeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCode(@PathParam("codeId") final Long codeId, @Context final UriInfo uriInfo) {
+
+        final CodeData code = this.readPlatformService.retrieveCode(codeId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, code, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{codeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateCode(@PathParam("codeId") final Long codeId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCode(codeId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{codeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteCode(@PathParam("codeId") final Long codeId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCode(codeId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/data/CodeData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/data/CodeData.java
new file mode 100644
index 0000000..4709392
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/data/CodeData.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.data;
+
+import java.io.Serializable;
+
+/**
+ * Immutable data object representing a code.
+ */
+public class CodeData implements Serializable {
+
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String name;
+    @SuppressWarnings("unused")
+    private final boolean systemDefined;
+
+    public static CodeData instance(final Long id, final String name, final boolean systemDefined) {
+        return new CodeData(id, name, systemDefined);
+    }
+
+    private CodeData(final Long id, final String name, final boolean systemDefined) {
+        this.id = id;
+        this.name = name;
+        this.systemDefined = systemDefined;
+    }
+
+    public Long getCodeId() {
+        return this.id;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/data/CodeValueData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/data/CodeValueData.java
new file mode 100644
index 0000000..2eeb1b0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/data/CodeValueData.java
@@ -0,0 +1,75 @@
+/**
+ * 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.codes.data;
+
+import java.io.Serializable;
+
+/**
+ * Immutable data object represent code-value data in system.
+ */
+public class CodeValueData implements Serializable {
+
+    private final Long id;
+
+    private final String name;
+
+    @SuppressWarnings("unused")
+    private final Integer position;
+
+    @SuppressWarnings("unused")
+    private final String description;
+    private final boolean isActive;
+
+    public static CodeValueData instance(final Long id, final String name, final Integer position, final boolean isActive) {
+        String description = null;
+        return new CodeValueData(id, name, position, description,isActive);
+    }
+
+    public static CodeValueData instance(final Long id, final String name, final String description, final boolean isActive) {
+        Integer position = null;
+        return new CodeValueData(id, name, position, description,isActive);
+    }
+
+    public static CodeValueData instance(final Long id, final String name) {
+        String description = null;
+        Integer position = null;
+        boolean isActive = false;
+        return new CodeValueData(id, name, position, description, isActive);
+    }
+
+    public static CodeValueData instance(final Long id, final String name, final Integer position, final String description, final boolean isActive) {
+        return new CodeValueData(id, name, position, description,isActive);
+    }
+
+    private CodeValueData(final Long id, final String name, final Integer position, final String description, final boolean isActive) {
+        this.id = id;
+        this.name = name;
+        this.position = position;
+        this.description = description;
+        this.isActive = isActive;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/Code.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/Code.java
new file mode 100644
index 0000000..22042a3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/Code.java
@@ -0,0 +1,96 @@
+/**
+ * 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.codes.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.exception.SystemDefinedCodeCannotBeChangedException;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_code", uniqueConstraints = { @UniqueConstraint(columnNames = { "code_name" }, name = "code_name") })
+public class Code extends AbstractPersistable<Long> {
+
+    @Column(name = "code_name", length = 100)
+    private String name;
+
+    @Column(name = "is_system_defined")
+    private final boolean systemDefined;
+
+    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "code", orphanRemoval = true)
+    private Set<CodeValue> values;
+
+    public static Code fromJson(final JsonCommand command) {
+        final String name = command.stringValueOfParameterNamed("name");
+        return new Code(name);
+    }
+    
+    public static Code createNew(final String name) {
+        return new Code(name);
+    }
+
+    protected Code() {
+        this.systemDefined = false;
+    }
+
+    private Code(final String name) {
+        this.name = name;
+        this.systemDefined = false;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public boolean isSystemDefined() {
+        return this.systemDefined;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        if (this.systemDefined) { throw new SystemDefinedCodeCannotBeChangedException(); }
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(1);
+
+        final String firstnameParamName = "name";
+        if (command.isChangeInStringParameterNamed(firstnameParamName, this.name)) {
+            final String newValue = command.stringValueOfParameterNamed(firstnameParamName);
+            actualChanges.put(firstnameParamName, newValue);
+            this.name = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        return actualChanges;
+    }
+
+    public boolean remove(final CodeValue codeValueToDelete) {
+        return this.values.remove(codeValueToDelete);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeRepository.java
new file mode 100644
index 0000000..b02590b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.codes.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface CodeRepository extends JpaRepository<Code, Long>, JpaSpecificationExecutor<Code> {
+
+    Code findOneByName(String name);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeValue.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeValue.java
new file mode 100644
index 0000000..a9029f6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeValue.java
@@ -0,0 +1,136 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.CodeConstants.CODEVALUE_JSON_INPUT_PARAMS;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_code_value", uniqueConstraints = { @UniqueConstraint(columnNames = { "code_id", "code_value" }, name = "code_value_duplicate") })
+public class CodeValue extends AbstractPersistable<Long> {
+
+    @Column(name = "code_value", length = 100)
+    private String label;
+
+    @Column(name = "order_position")
+    private int position;
+
+    @Column(name = "code_description")
+    private String description;
+
+    @ManyToOne
+    @JoinColumn(name = "code_id", nullable = false)
+    private Code code;
+
+    @Column(name = "is_active")
+    private boolean isActive;
+
+    public static CodeValue createNew(final Code code, final String label, final int position, final String description,
+            final boolean isActive) {
+        return new CodeValue(code, label, position, description, isActive);
+    }
+
+    protected CodeValue() {
+        //
+    }
+
+    private CodeValue(final Code code, final String label, final int position, final String description, final boolean isActive) {
+        this.code = code;
+        this.label = StringUtils.defaultIfEmpty(label, null);
+        this.position = position;
+        this.description = description;
+        this.isActive = isActive;
+    }
+
+    public String label() {
+        return this.label;
+    }
+
+    public int position() {
+        return this.position;
+    }
+
+    public static CodeValue fromJson(final Code code, final JsonCommand command) {
+
+        final String label = command.stringValueOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue());
+        Integer position = command.integerValueSansLocaleOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue());
+        String description = command.stringValueOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.DESCRIPTION.getValue());
+        Boolean isActiveObj = command.booleanObjectValueOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.IS_ACTIVE.getValue());
+        boolean isActive = true;
+        if (isActiveObj != null) {
+            isActive = isActiveObj;
+        }
+        if (position == null) {
+            position = new Integer(0);
+        }
+        return new CodeValue(code, label, position.intValue(), description, isActive);
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(2);
+
+        final String labelParamName = CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue();
+        if (command.isChangeInStringParameterNamed(labelParamName, this.label)) {
+            final String newValue = command.stringValueOfParameterNamed(labelParamName);
+            actualChanges.put(labelParamName, newValue);
+            this.label = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String decriptionParamName = CODEVALUE_JSON_INPUT_PARAMS.DESCRIPTION.getValue();
+        if (command.isChangeInStringParameterNamed(decriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(decriptionParamName);
+            actualChanges.put(decriptionParamName, newValue);
+            this.description = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String positionParamName = CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue();
+        if (command.isChangeInIntegerSansLocaleParameterNamed(positionParamName, this.position)) {
+            final Integer newValue = command.integerValueSansLocaleOfParameterNamed(positionParamName);
+            actualChanges.put(positionParamName, newValue);
+            this.position = newValue.intValue();
+        }
+
+        final String isActiveParamName = CODEVALUE_JSON_INPUT_PARAMS.IS_ACTIVE.getValue();
+        if (command.isChangeInBooleanParameterNamed(isActiveParamName, this.isActive)) {
+            final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isActiveParamName);
+            actualChanges.put(isActiveParamName, newValue);
+            this.isActive = newValue.booleanValue();
+        }
+
+        return actualChanges;
+    }
+
+    public CodeValueData toData() {
+        return CodeValueData.instance(getId(), this.label, this.position, this.isActive);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeValueRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeValueRepository.java
new file mode 100644
index 0000000..a9c9609
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeValueRepository.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.infrastructure.codes.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface CodeValueRepository extends JpaRepository<CodeValue, Long>, JpaSpecificationExecutor<CodeValue> {
+
+    CodeValue findByCodeNameAndId(String codeName, Long id);
+    
+    CodeValue findByCodeNameAndLabel (String codeName, String label);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeValueRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeValueRepositoryWrapper.java
new file mode 100644
index 0000000..f268558
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/domain/CodeValueRepositoryWrapper.java
@@ -0,0 +1,66 @@
+/**
+ * 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.codes.domain;
+
+import org.apache.fineract.infrastructure.codes.exception.CodeValueNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link CodeValueRepository} that is responsible for checking if
+ * {@link CodeValue} is returned when using <code>findOne</code> and
+ * <code>findByCodeNameAndId</code> repository methods and throwing an
+ * appropriate not found exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link CodeValueRepository} is required.
+ * </p>
+ */
+@Service
+public class CodeValueRepositoryWrapper {
+
+    private final CodeValueRepository repository;
+
+    @Autowired
+    public CodeValueRepositoryWrapper(final CodeValueRepository repository) {
+        this.repository = repository;
+    }
+
+    public CodeValue findOneWithNotFoundDetection(final Long id) {
+        final CodeValue codeValue = this.repository.findOne(id);
+        if (codeValue == null) { throw new CodeValueNotFoundException(id); }
+        return codeValue;
+    }
+
+    public CodeValue findOneByCodeNameAndIdWithNotFoundDetection(final String codeName, final Long id) {
+        final CodeValue codeValue = this.repository.findByCodeNameAndId(codeName, id);
+        if (codeValue == null) { throw new CodeValueNotFoundException(codeName, id); }
+        return codeValue;
+    }
+    
+    public CodeValue findOneByCodeNameAndLabelWithNotFoundDetection(final String codeName, final String label) {
+        final CodeValue codeValue = this.repository.findByCodeNameAndLabel(codeName, label);
+        if (codeValue == null) { throw new CodeValueNotFoundException(codeName, label); }
+        return codeValue;
+    }
+    
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/exception/CodeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/exception/CodeNotFoundException.java
new file mode 100644
index 0000000..4dfdbe7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/exception/CodeNotFoundException.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.infrastructure.codes.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when a code is not found.
+ */
+public class CodeNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public CodeNotFoundException(final String name) {
+        super("error.msg.code.not.found", "Code with name `" + name + "` does not exist", name);
+    }
+
+    public CodeNotFoundException(final Long codeId) {
+        super("error.msg.code.identifier.not.found", "Code with identifier `" + codeId + "` does not exist", codeId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/exception/CodeValueNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/exception/CodeValueNotFoundException.java
new file mode 100644
index 0000000..f56a661
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/exception/CodeValueNotFoundException.java
@@ -0,0 +1,41 @@
+/**
+ * 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.codes.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when client resources are not found.
+ */
+public class CodeValueNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public CodeValueNotFoundException(final Long id) {
+        super("error.msg.codevalue.id.invalid", "Code value with identifier " + id + " does not exist", id);
+    }
+
+    public CodeValueNotFoundException(final String codeName, final Long id) {
+        super("error.msg.codevalue.codename.id.combination.invalid", "Code value with identifier " + id
+                + " does not exist for a code with name " + codeName, id, codeName);
+    }
+    
+    public CodeValueNotFoundException(final String codeName, final String label) {
+        super("error.msg.codevalue.codename.id.combination.invalid", "Code value with label " + label
+                + " does not exist for a code with name " + codeName, label, codeName);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/exception/SystemDefinedCodeCannotBeChangedException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/exception/SystemDefinedCodeCannotBeChangedException.java
new file mode 100644
index 0000000..da17e5e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/exception/SystemDefinedCodeCannotBeChangedException.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.infrastructure.codes.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link AbstractPlatformDomainRuleException} thrown when someone attempts to
+ * update or delete a system defined code.
+ */
+public class SystemDefinedCodeCannotBeChangedException extends AbstractPlatformDomainRuleException {
+
+    public SystemDefinedCodeCannotBeChangedException() {
+        super("error.msg.code.systemdefined", "This code is system defined and cannot be modified or deleted.");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/CreateCodeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/CreateCodeCommandHandler.java
new file mode 100644
index 0000000..7a18de1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/CreateCodeCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.codes.service.CodeWritePlatformService;
+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 = "CODE", action = "CREATE")
+public class CreateCodeCommandHandler implements NewCommandSourceHandler {
+
+    private final CodeWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateCodeCommandHandler(final CodeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createCode(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/CreateCodeValueCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/CreateCodeValueCommandHandler.java
new file mode 100644
index 0000000..4654ff6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/CreateCodeValueCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.codes.service.CodeValueWritePlatformService;
+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 = "CODEVALUE", action = "CREATE")
+public class CreateCodeValueCommandHandler implements NewCommandSourceHandler {
+
+    private final CodeValueWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateCodeValueCommandHandler(final CodeValueWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createCodeValue(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/DeleteCodeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/DeleteCodeCommandHandler.java
new file mode 100644
index 0000000..930f0e2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/DeleteCodeCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.codes.service.CodeWritePlatformService;
+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 = "CODE", action = "DELETE")
+public class DeleteCodeCommandHandler implements NewCommandSourceHandler {
+
+    private final CodeWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteCodeCommandHandler(final CodeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.deleteCode(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/DeleteCodeValueCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/DeleteCodeValueCommandHandler.java
new file mode 100644
index 0000000..1227df4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/DeleteCodeValueCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.codes.service.CodeValueWritePlatformService;
+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 = "CODEVALUE", action = "DELETE")
+public class DeleteCodeValueCommandHandler implements NewCommandSourceHandler {
+
+    private final CodeValueWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteCodeValueCommandHandler(final CodeValueWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.deleteCodeValue(command.entityId(), command.subentityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/UpdateCodeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/UpdateCodeCommandHandler.java
new file mode 100644
index 0000000..ad77c90
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/UpdateCodeCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.codes.service.CodeWritePlatformService;
+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 = "CODE", action = "UPDATE")
+public class UpdateCodeCommandHandler implements NewCommandSourceHandler {
+
+    private final CodeWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateCodeCommandHandler(final CodeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateCode(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/UpdateCodeValueCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/UpdateCodeValueCommandHandler.java
new file mode 100644
index 0000000..9f2e061
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/handler/UpdateCodeValueCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.codes.service.CodeValueWritePlatformService;
+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 = "CODEVALUE", action = "UPDATE")
+public class UpdateCodeValueCommandHandler implements NewCommandSourceHandler {
+
+    private final CodeValueWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateCodeValueCommandHandler(final CodeValueWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateCodeValue(command.subentityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/serialization/CodeCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/serialization/CodeCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..be93bc5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/serialization/CodeCommandFromApiJsonDeserializer.java
@@ -0,0 +1,97 @@
+/**
+ * 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.codes.serialization;
+
+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;
+
+/**
+ * Deserializer for code JSON to validate API request.
+ */
+@Component
+public final class CodeCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("name"));
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public CodeCommandFromApiJsonDeserializer(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("code");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
+        baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
+
+        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("code");
+
+        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);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/serialization/CodeValueCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/serialization/CodeValueCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..e92b271
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/serialization/CodeValueCommandFromApiJsonDeserializer.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.infrastructure.codes.serialization;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.CodeConstants.CODEVALUE_JSON_INPUT_PARAMS;
+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;
+
+/**
+ * Deserializer for code JSON to validate API request.
+ */
+@Component
+public final class CodeValueCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = CODEVALUE_JSON_INPUT_PARAMS.getAllValues();
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public CodeValueCommandFromApiJsonDeserializer(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("code.value");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue(), element);
+        baseDataValidator.reset().parameter(CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue()).value(name).notBlank().notExceedingLengthOf(100);
+
+        if (this.fromApiJsonHelper.parameterExists(CODEVALUE_JSON_INPUT_PARAMS.DESCRIPTION.getValue(), element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(CODEVALUE_JSON_INPUT_PARAMS.DESCRIPTION.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CODEVALUE_JSON_INPUT_PARAMS.DESCRIPTION.getValue()).value(description)
+                    .notExceedingLengthOf(500);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue(), element)) {
+            // Validate input value is a valid Integer
+            this.fromApiJsonHelper.extractIntegerSansLocaleNamed(CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue(), element);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CODEVALUE_JSON_INPUT_PARAMS.IS_ACTIVE.getValue(), element)) {
+            final Boolean isActive = this.fromApiJsonHelper.extractBooleanNamed(CODEVALUE_JSON_INPUT_PARAMS.IS_ACTIVE.getValue(), element);
+            baseDataValidator.reset().parameter(CODEVALUE_JSON_INPUT_PARAMS.IS_ACTIVE.getValue()).value(isActive).validateForBooleanValue();
+        }
+
+        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("code.value");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue(), element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed(CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue(), element);
+            baseDataValidator.reset().parameter(CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue()).value(name).notBlank()
+                    .notExceedingLengthOf(100);
+        }
+        if (this.fromApiJsonHelper.parameterExists(CODEVALUE_JSON_INPUT_PARAMS.DESCRIPTION.getValue(), element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(CODEVALUE_JSON_INPUT_PARAMS.DESCRIPTION.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CODEVALUE_JSON_INPUT_PARAMS.DESCRIPTION.getValue()).value(description)
+                    .notExceedingLengthOf(500);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue(), element)) {
+            // Validate input value is a valid Integer
+            this.fromApiJsonHelper.extractIntegerSansLocaleNamed(CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue(), element);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CODEVALUE_JSON_INPUT_PARAMS.IS_ACTIVE.getValue(), element)) {
+            final Boolean isActive = this.fromApiJsonHelper.extractBooleanNamed(CODEVALUE_JSON_INPUT_PARAMS.IS_ACTIVE.getValue(), element);
+            baseDataValidator.reset().parameter(CODEVALUE_JSON_INPUT_PARAMS.IS_ACTIVE.getValue()).value(isActive).validateForBooleanValue();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeReadPlatformService.java
new file mode 100644
index 0000000..6988ed0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeReadPlatformService.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.infrastructure.codes.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeData;
+
+public interface CodeReadPlatformService {
+
+    Collection<CodeData> retrieveAllCodes();
+
+    CodeData retrieveCode(Long codeId);
+
+    CodeData retriveCode(String codeName);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeReadPlatformServiceImpl.java
new file mode 100644
index 0000000..7254baf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeReadPlatformServiceImpl.java
@@ -0,0 +1,103 @@
+/**
+ * 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.codes.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeData;
+import org.apache.fineract.infrastructure.codes.exception.CodeNotFoundException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+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 CodeReadPlatformServiceImpl implements CodeReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public CodeReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class CodeMapper implements RowMapper<CodeData> {
+
+        public String schema() {
+            return " c.id as id, c.code_name as code_name, c.is_system_defined as systemDefined from m_code c ";
+        }
+
+        @Override
+        public CodeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String code_name = rs.getString("code_name");
+            final boolean systemDefined = rs.getBoolean("systemDefined");
+
+            return CodeData.instance(id, code_name, systemDefined);
+        }
+    }
+
+    @Override
+    @Cacheable(value = "codes", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('CD')")
+    public Collection<CodeData> retrieveAllCodes() {
+        this.context.authenticatedUser();
+
+        final CodeMapper rm = new CodeMapper();
+        final String sql = "select " + rm.schema() + " order by c.code_name";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public CodeData retrieveCode(final Long codeId) {
+        try {
+            this.context.authenticatedUser();
+
+            final CodeMapper rm = new CodeMapper();
+            final String sql = "select " + rm.schema() + " where c.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { codeId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new CodeNotFoundException(codeId);
+        }
+    }
+
+    @Override
+    public CodeData retriveCode(final String codeName) {
+        try {
+            this.context.authenticatedUser();
+
+            final CodeMapper rm = new CodeMapper();
+            final String sql = "select " + rm.schema() + " where c.code_name = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { codeName });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new CodeNotFoundException(codeName);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformService.java
new file mode 100644
index 0000000..fc93e10
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformService.java
@@ -0,0 +1,51 @@
+/**
+ * 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.codes.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+
+/**
+ * A service for retrieving code value information based on the code itself.
+ * 
+ * There are two types of code information in the platform:
+ * <ol>
+ * <li>System defined codes</li>
+ * <li>User defined codes</li>
+ * </ol>
+ * 
+ * <p>
+ * System defined codes cannot be altered or removed but their code values may
+ * be allowed to be added to or removed.
+ * </p>
+ * 
+ * <p>
+ * User defined codes can be changed in any way by application users with system
+ * permissions.
+ * </p>
+ */
+public interface CodeValueReadPlatformService {
+
+    Collection<CodeValueData> retrieveCodeValuesByCode(final String code);
+
+    Collection<CodeValueData> retrieveAllCodeValues(final Long codeId);
+
+    CodeValueData retrieveCodeValue(final Long codeValueId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformServiceImpl.java
new file mode 100644
index 0000000..de13981
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueReadPlatformServiceImpl.java
@@ -0,0 +1,105 @@
+/**
+ * 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.codes.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.exception.CodeValueNotFoundException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+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 CodeValueReadPlatformServiceImpl implements CodeValueReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public CodeValueReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class CodeValueDataMapper implements RowMapper<CodeValueData> {
+
+        public String schema() {
+            return " cv.id as id, cv.code_value as value, cv.code_id as codeId, cv.code_description as description, cv.order_position as position,"
+                    + " cv.is_active isActive from m_code_value as cv join m_code c on cv.code_id = c.id ";
+        }
+
+        @Override
+        public CodeValueData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String value = rs.getString("value");
+            final Integer position = rs.getInt("position");
+            final String description = rs.getString("description");
+            final boolean isActive = rs.getBoolean("isActive");
+            return CodeValueData.instance(id, value, position, description, isActive);
+        }
+    }
+
+    @Override
+    public Collection<CodeValueData> retrieveCodeValuesByCode(final String code) {
+
+        this.context.authenticatedUser();
+
+        final CodeValueDataMapper rm = new CodeValueDataMapper();
+        final String sql = "select " + rm.schema() + "where c.code_name like ? and cv.is_active = 1 order by position";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { code });
+    }
+
+    @Override
+    @Cacheable(value = "code_values", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#codeId+'cv')")
+    public Collection<CodeValueData> retrieveAllCodeValues(final Long codeId) {
+
+        this.context.authenticatedUser();
+
+        final CodeValueDataMapper rm = new CodeValueDataMapper();
+        final String sql = "select " + rm.schema() + "where cv.code_id = ? order by position";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { codeId });
+    }
+
+    @Override
+    public CodeValueData retrieveCodeValue(final Long codeValueId) {
+
+        try {
+            this.context.authenticatedUser();
+
+            final CodeValueDataMapper rm = new CodeValueDataMapper();
+            final String sql = "select " + rm.schema() + "where cv.id = ? order by position";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { codeValueId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new CodeValueNotFoundException(codeValueId);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueWritePlatformService.java
new file mode 100644
index 0000000..841ead0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface CodeValueWritePlatformService {
+
+    CommandProcessingResult createCodeValue(JsonCommand command);
+
+    CommandProcessingResult updateCodeValue(Long codeValueId, JsonCommand command);
+
+    CommandProcessingResult deleteCodeValue(Long codeId, Long codeValueId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..5e4de3e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeValueWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,175 @@
+/**
+ * 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.codes.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.codes.domain.Code;
+import org.apache.fineract.infrastructure.codes.domain.CodeRepository;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.codes.exception.CodeNotFoundException;
+import org.apache.fineract.infrastructure.codes.serialization.CodeValueCommandFromApiJsonDeserializer;
+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.cache.annotation.CacheEvict;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class CodeValueWritePlatformServiceJpaRepositoryImpl implements CodeValueWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(CodeValueWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
+    private final CodeValueRepository codeValueRepository;
+    private final CodeRepository codeRepository;
+    private final CodeValueCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+
+    @Autowired
+    public CodeValueWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final CodeRepository codeRepository,
+            final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final CodeValueRepository codeValueRepository,
+            final CodeValueCommandFromApiJsonDeserializer fromApiJsonDeserializer) {
+        this.context = context;
+        this.codeRepository = codeRepository;
+        this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
+        this.codeValueRepository = codeValueRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "code_values", allEntries = true)
+    public CommandProcessingResult createCodeValue(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final Long codeId = command.entityId();
+            final Code code = this.codeRepository.findOne(codeId);
+            if (code == null) {
+                throw new CodeNotFoundException(codeId);
+            }
+            final CodeValue codeValue = CodeValue.fromJson(code, command);
+            this.codeValueRepository.save(codeValue);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(code.getId()) //
+                    .withSubEntityId(codeValue.getId())//
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleCodeValueDataIntegrityIssues(command, dve);
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .build();
+        }
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleCodeValueDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("code_value")) {
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.code.value.duplicate.label", "A code value with lable '" + name
+                    + "' already exists", "name", name);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.code.value.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "code_values", allEntries = true)
+    public CommandProcessingResult updateCodeValue(final Long codeValueId, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final CodeValue codeValue = this.codeValueRepositoryWrapper.findOneWithNotFoundDetection(codeValueId);
+            final Map<String, Object> changes = codeValue.update(command);
+
+            if (!changes.isEmpty()) {
+                this.codeValueRepository.saveAndFlush(codeValue);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(codeValueId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleCodeValueDataIntegrityIssues(command, dve);
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .build();
+        }
+
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "code_values", allEntries = true)
+    public CommandProcessingResult deleteCodeValue(final Long codeId, final Long codeValueId) {
+
+        try {
+            this.context.authenticatedUser();
+
+            final Code code = this.codeRepository.findOne(codeId);
+            if (code == null) { throw new CodeNotFoundException(codeId); }
+
+            final CodeValue codeValueToDelete = this.codeValueRepositoryWrapper.findOneWithNotFoundDetection(codeValueId);
+
+            final boolean removed = code.remove(codeValueToDelete);
+            if (removed) {
+                this.codeRepository.saveAndFlush(code);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(codeId) //
+                    .withSubEntityId(codeValueId)//
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            logger.error(dve.getMessage(), dve);
+            final Throwable realCause = dve.getMostSpecificCause();
+            if (realCause.getMessage().contains("code_value")) { throw new PlatformDataIntegrityException("error.msg.codeValue.in.use",
+                    "This code value is in use", codeValueId); }
+            throw new PlatformDataIntegrityException("error.msg.code.value.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource: " + dve.getMostSpecificCause().getMessage());
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeWritePlatformService.java
new file mode 100644
index 0000000..40ec683
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.codes.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface CodeWritePlatformService {
+
+    CommandProcessingResult createCode(JsonCommand command);
+
+    CommandProcessingResult updateCode(Long codeId, JsonCommand command);
+
+    CommandProcessingResult deleteCode(Long codeId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..3926d18
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/service/CodeWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,148 @@
+/**
+ * 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.codes.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.codes.domain.Code;
+import org.apache.fineract.infrastructure.codes.domain.CodeRepository;
+import org.apache.fineract.infrastructure.codes.exception.CodeNotFoundException;
+import org.apache.fineract.infrastructure.codes.exception.SystemDefinedCodeCannotBeChangedException;
+import org.apache.fineract.infrastructure.codes.serialization.CodeCommandFromApiJsonDeserializer;
+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.cache.annotation.CacheEvict;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class CodeWritePlatformServiceJpaRepositoryImpl implements CodeWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(CodeWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final CodeRepository codeRepository;
+    private final CodeCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+
+    @Autowired
+    public CodeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final CodeRepository codeRepository,
+            final CodeCommandFromApiJsonDeserializer fromApiJsonDeserializer) {
+        this.context = context;
+        this.codeRepository = codeRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "codes", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('cv')")
+    public CommandProcessingResult createCode(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final Code code = Code.fromJson(command);
+            this.codeRepository.save(code);
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(code.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleCodeDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "codes", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('cv')")
+    public CommandProcessingResult updateCode(final Long codeId, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final Code code = retrieveCodeBy(codeId);
+            final Map<String, Object> changes = code.update(command);
+
+            if (!changes.isEmpty()) {
+                this.codeRepository.save(code);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(codeId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleCodeDataIntegrityIssues(command, dve);
+            return null;
+        }
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "codes", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('cv')")
+    public CommandProcessingResult deleteCode(final Long codeId) {
+
+        this.context.authenticatedUser();
+
+        final Code code = retrieveCodeBy(codeId);
+        if (code.isSystemDefined()) { throw new SystemDefinedCodeCannotBeChangedException(); }
+
+        try {
+            this.codeRepository.delete(code);
+            this.codeRepository.flush();
+        } catch (final DataIntegrityViolationException e) {
+            throw new PlatformDataIntegrityException("error.msg.cund.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource: " + e.getMostSpecificCause());
+        }
+        return new CommandProcessingResultBuilder().withEntityId(codeId).build();
+    }
+
+    private Code retrieveCodeBy(final Long codeId) {
+        final Code code = this.codeRepository.findOne(codeId);
+        if (code == null) { throw new CodeNotFoundException(codeId.toString()); }
+        return code;
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleCodeDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("code_name")) {
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.code.duplicate.name", "A code with name '" + name + "' already exists",
+                    "name", name);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.cund.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/ExternalServiceConfigurationApiConstant.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/ExternalServiceConfigurationApiConstant.java
new file mode 100644
index 0000000..f4a3af6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/ExternalServiceConfigurationApiConstant.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.infrastructure.configuration.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ExternalServiceConfigurationApiConstant {
+
+    public static final String NAME = "name";
+    public static final String VALUE = "value";
+    public static final String EXTERNAL_SERVICE_RESOURCE_NAME = "externalServiceConfiguration";
+
+    public static final Set<String> EXTERNAL_SERVICE_CONFIGURATION_DATA_PARAMETERS = new HashSet<>(Arrays.asList(NAME, VALUE));
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/ExternalServicesConfigurationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/ExternalServicesConfigurationApiResource.java
new file mode 100644
index 0000000..18405d1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/ExternalServicesConfigurationApiResource.java
@@ -0,0 +1,100 @@
+/**
+ * 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.configuration.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.data.ExternalServicesPropertiesData;
+import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService;
+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.ToApiJsonSerializer;
+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("/externalservice")
+@Component
+@Scope("singleton")
+public class ExternalServicesConfigurationApiResource {
+
+    private final PlatformSecurityContext context;
+    private final ExternalServicesPropertiesReadPlatformService externalServicePropertiesReadPlatformService;
+    private final ToApiJsonSerializer<ExternalServicesPropertiesData> toApiJsonSerializer;
+    // private final ToApiJsonSerializer<S3CredentialsData>
+    // s3ToApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public ExternalServicesConfigurationApiResource(final PlatformSecurityContext context,
+            final ExternalServicesPropertiesReadPlatformService readPlatformService,
+            final ToApiJsonSerializer<ExternalServicesPropertiesData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.externalServicePropertiesReadPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        // this.s3ToApiJsonSerializer = s3ToApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Path("{servicename}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("servicename") final String serviceName, @Context final UriInfo uriInfo) {
+        this.context.authenticatedUser().validateHasReadPermission(ExternalServiceConfigurationApiConstant.EXTERNAL_SERVICE_RESOURCE_NAME);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        final Collection<ExternalServicesPropertiesData> externalServiceNVPs = this.externalServicePropertiesReadPlatformService
+                .retrieveOne(serviceName);
+        return this.toApiJsonSerializer.serialize(settings, externalServiceNVPs,
+                ExternalServiceConfigurationApiConstant.EXTERNAL_SERVICE_CONFIGURATION_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{servicename}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateExternalServiceProperties(@PathParam("servicename") final String serviceName, final String apiRequestBodyAsJson) {
+        // ExternalServicesData external =
+        // this.externalServiceReadPlatformService.getExternalServiceDetailsByServiceName(serviceName);
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateExternalServiceProperties(serviceName)
+                .withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiConstant.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiConstant.java
new file mode 100644
index 0000000..93c3345
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiConstant.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.configuration.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class GlobalConfigurationApiConstant {
+
+    public static final String ENABLED = "enabled";
+    public static final String VALUE = "value";
+    public static final String ID = "id";
+    public static final String CONFIGURATION_RESOURCE_NAME = "globalConfiguration";
+
+    public static final Set<String> UPDATE_CONFIGURATION_DATA_PARAMETERS = new HashSet<>(Arrays.asList(ENABLED, VALUE));
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiResource.java
new file mode 100644
index 0000000..57ab554
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiResource.java
@@ -0,0 +1,119 @@
+/**
+ * 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.configuration.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationData;
+import org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationPropertyData;
+import org.apache.fineract.infrastructure.configuration.service.ConfigurationReadPlatformService;
+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("/configurations")
+@Component
+@Scope("singleton")
+public class GlobalConfigurationApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("globalConfiguration"));
+
+    private final String resourceNameForPermissions = "CONFIGURATION";
+
+    private final PlatformSecurityContext context;
+    private final ConfigurationReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<GlobalConfigurationData> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<GlobalConfigurationPropertyData> propertyDataJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public GlobalConfigurationApiResource(final PlatformSecurityContext context,
+            final ConfigurationReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<GlobalConfigurationData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final DefaultToApiJsonSerializer<GlobalConfigurationPropertyData> propertyDataJsonSerializer) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.propertyDataJsonSerializer = propertyDataJsonSerializer;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveConfiguration(@Context final UriInfo uriInfo,@DefaultValue("false") @QueryParam("survey") final boolean survey) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final GlobalConfigurationData configurationData = this.readPlatformService.retrieveGlobalConfiguration(survey);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, configurationData, this.RESPONSE_DATA_PARAMETERS);
+    }
+    
+    @GET
+    @Path("{configId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("configId") final Long configId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final GlobalConfigurationPropertyData configurationData = this.readPlatformService.retrieveGlobalConfiguration(configId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.propertyDataJsonSerializer.serialize(settings, configurationData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+
+    @PUT
+    @Path("{configId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateConfiguration(@PathParam("configId") final Long configId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateGlobalConfiguration(configId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/command/UpdateGlobalConfigurationCommand.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/command/UpdateGlobalConfigurationCommand.java
new file mode 100644
index 0000000..d855196
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/command/UpdateGlobalConfigurationCommand.java
@@ -0,0 +1,37 @@
+/**
+ * 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.configuration.command;
+
+import java.util.Map;
+
+/**
+ * Immutable command for updating global configuration settings.
+ */
+public class UpdateGlobalConfigurationCommand {
+
+    private final Map<String, Boolean> globalConfiguration;
+
+    public UpdateGlobalConfigurationCommand(final Map<String, Boolean> globalConfigurationMap) {
+        this.globalConfiguration = globalConfigurationMap;
+    }
+
+    public Map<String, Boolean> getGlobalConfiguration() {
+        return this.globalConfiguration;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/ExternalServicesData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/ExternalServicesData.java
new file mode 100644
index 0000000..1d05cc8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/ExternalServicesData.java
@@ -0,0 +1,39 @@
+/**
+ * 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.configuration.data;
+
+public class ExternalServicesData {
+
+    private final Long id;
+    private final String name;
+
+    public ExternalServicesData(final Long id, final String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/ExternalServicesPropertiesData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/ExternalServicesPropertiesData.java
new file mode 100644
index 0000000..d99f78f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/ExternalServicesPropertiesData.java
@@ -0,0 +1,41 @@
+/**
+ * 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.configuration.data;
+
+import java.io.Serializable;
+
+public class ExternalServicesPropertiesData implements Serializable {
+
+    private final String name;
+    private final String value;
+
+    public ExternalServicesPropertiesData(final String name, final String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationData.java
new file mode 100644
index 0000000..9ab45cc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationData.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.configuration.data;
+
+import java.util.List;
+
+/**
+ * Immutable data object for global configuration.
+ */
+public class GlobalConfigurationData {
+
+    @SuppressWarnings("unused")
+    private final List<GlobalConfigurationPropertyData> globalConfiguration;
+
+    public GlobalConfigurationData(final List<GlobalConfigurationPropertyData> globalConfiguration) {
+        this.globalConfiguration = globalConfiguration;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationDataValidator.java
new file mode 100644
index 0000000..9472832
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationDataValidator.java
@@ -0,0 +1,78 @@
+/**
+ * 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.configuration.data;
+
+import static org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationApiConstant.CONFIGURATION_RESOURCE_NAME;
+import static org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationApiConstant.ENABLED;
+import static org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationApiConstant.UPDATE_CONFIGURATION_DATA_PARAMETERS;
+import static org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationApiConstant.VALUE;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class GlobalConfigurationDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public GlobalConfigurationDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForUpdate(final JsonCommand command) {
+        final String json = command.json();
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, UPDATE_CONFIGURATION_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CONFIGURATION_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(ENABLED, element)) {
+            final boolean enabledBool = this.fromApiJsonHelper.extractBooleanNamed(ENABLED, element);
+            baseDataValidator.reset().parameter(ENABLED).value(enabledBool).validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(VALUE, element)) {
+            final Long valueStr = this.fromApiJsonHelper.extractLongNamed(VALUE, element);
+            baseDataValidator.reset().parameter(ENABLED).value(valueStr).zeroOrPositiveAmount();
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationPropertyData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationPropertyData.java
new file mode 100755
index 0000000..855c1c5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationPropertyData.java
@@ -0,0 +1,57 @@
+/**
+ * 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.configuration.data;
+
+/**
+ * Immutable data object for global configuration property.
+ */
+public class GlobalConfigurationPropertyData {
+
+    @SuppressWarnings("unused")
+    private final String name;
+    @SuppressWarnings("unused")
+    private final boolean enabled;
+    @SuppressWarnings("unused")
+    private final Long value;
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String description;
+    @SuppressWarnings("unused")
+    private final boolean trapDoor;
+
+    public GlobalConfigurationPropertyData(final String name, final boolean enabled, final Long value, final String description, final boolean trapDoor) {
+        this.name = name;
+        this.enabled = enabled;
+        this.value = value;
+        this.id = null;
+        this.description = description;
+        this.trapDoor = trapDoor;
+    }
+
+    public GlobalConfigurationPropertyData(final String name, final boolean enabled, final Long value, final Long id,
+            final String description, final boolean isTrapDoor) {
+        this.name = name;
+        this.enabled = enabled;
+        this.value = value;
+        this.id = id;
+        this.description = description;
+        this.trapDoor = isTrapDoor;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/S3CredentialsData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/S3CredentialsData.java
new file mode 100755
index 0000000..f864434
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/S3CredentialsData.java
@@ -0,0 +1,45 @@
+/**
+ * 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.configuration.data;
+
+public class S3CredentialsData {
+
+    private final String bucketName;
+    private final String accessKey;
+    private final String secretKey;
+
+    public S3CredentialsData(final String bucketName, final String accessKey, final String secretKey) {
+        this.bucketName = bucketName;
+        this.accessKey = accessKey;
+        this.secretKey = secretKey;
+    }
+
+    public String getBucketName() {
+        return this.bucketName;
+    }
+
+    public String getAccessKey() {
+        return this.accessKey;
+    }
+
+    public String getSecretKey() {
+        return this.secretKey;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/SMTPCredentialsData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/SMTPCredentialsData.java
new file mode 100644
index 0000000..111b374
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/SMTPCredentialsData.java
@@ -0,0 +1,57 @@
+/**
+ * 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.configuration.data;
+
+public class SMTPCredentialsData {
+
+    private final String username;
+    private final String password;
+    private final String host;
+    private final String port;
+    private final boolean useTLS;
+
+    public SMTPCredentialsData(final String username, final String password, final String host, final String port, final boolean useTLS) {
+        this.username = username;
+        this.password = password;
+        this.host = host;
+        this.port = port;
+        this.useTLS = useTLS;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public String getPort() {
+        return port;
+    }
+
+    public boolean isUseTLS() {
+        return useTLS;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
new file mode 100644
index 0000000..087ce3d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
@@ -0,0 +1,66 @@
+/**
+ * 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.configuration.domain;
+
+import org.apache.fineract.infrastructure.cache.domain.CacheType;
+
+public interface ConfigurationDomainService {
+
+    boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
+
+    boolean isAmazonS3Enabled();
+
+    boolean isRescheduleFutureRepaymentsEnabled();
+
+    boolean isRescheduleRepaymentsOnHolidaysEnabled();
+
+    boolean allowTransactionsOnHolidayEnabled();
+
+    boolean allowTransactionsOnNonWorkingDayEnabled();
+
+    boolean isConstraintApproachEnabledForDatatables();
+
+    boolean isEhcacheEnabled();
+
+    void updateCache(CacheType cacheType);
+
+    Long retrievePenaltyWaitPeriod();
+
+    boolean isPasswordForcedResetEnable();
+
+    Long retrievePasswordLiveTime();
+
+    Long retrieveGraceOnPenaltyPostingPeriod();
+
+    Long retrieveOpeningBalancesContraAccount();
+
+    boolean isSavingsInterestPostingAtCurrentPeriodEnd();
+
+    Integer retrieveFinancialYearBeginningMonth();
+
+    public Integer retrieveMinAllowedClientsInGroup();
+
+    public Integer retrieveMaxAllowedClientsInGroup();
+
+    boolean isMeetingMandatoryForJLGLoans();
+
+    int getRoundingMode();
+
+    boolean isBackdatePenaltiesEnabled();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
new file mode 100644
index 0000000..70e5494
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -0,0 +1,217 @@
+/**
+ * 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.configuration.domain;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.cache.domain.CacheType;
+import org.apache.fineract.infrastructure.cache.domain.PlatformCache;
+import org.apache.fineract.infrastructure.cache.domain.PlatformCacheRepository;
+import org.apache.fineract.useradministration.domain.Permission;
+import org.apache.fineract.useradministration.domain.PermissionRepository;
+import org.apache.fineract.useradministration.exception.PermissionNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class ConfigurationDomainServiceJpa implements ConfigurationDomainService {
+
+    private final PermissionRepository permissionRepository;
+    private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository;
+    private final PlatformCacheRepository cacheTypeRepository;
+
+    @Autowired
+    public ConfigurationDomainServiceJpa(final PermissionRepository permissionRepository,
+            final GlobalConfigurationRepositoryWrapper globalConfigurationRepository, final PlatformCacheRepository cacheTypeRepository) {
+        this.permissionRepository = permissionRepository;
+        this.globalConfigurationRepository = globalConfigurationRepository;
+        this.cacheTypeRepository = cacheTypeRepository;
+    }
+
+    @Override
+    public boolean isMakerCheckerEnabledForTask(final String taskPermissionCode) {
+        if (StringUtils.isBlank(taskPermissionCode)) { throw new PermissionNotFoundException(taskPermissionCode); }
+
+        final Permission thisTask = this.permissionRepository.findOneByCode(taskPermissionCode);
+        if (thisTask == null) { throw new PermissionNotFoundException(taskPermissionCode); }
+
+        final String makerCheckerConfigurationProperty = "maker-checker";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository
+                .findOneByNameWithNotFoundDetection(makerCheckerConfigurationProperty);
+
+        return thisTask.hasMakerCheckerEnabled() && property.isEnabled();
+    }
+
+    @Override
+    public boolean isAmazonS3Enabled() {
+        return this.globalConfigurationRepository.findOneByNameWithNotFoundDetection("amazon-S3").isEnabled();
+    }
+
+    @Override
+    public boolean isRescheduleFutureRepaymentsEnabled() {
+        final String rescheduleRepaymentsConfigurationProperty = "reschedule-future-repayments";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository
+                .findOneByNameWithNotFoundDetection(rescheduleRepaymentsConfigurationProperty);
+        return property.isEnabled();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.fineract.infrastructure.configuration.domain.
+     * ConfigurationDomainService#isHolidaysEnabled()
+     */
+    @Override
+    public boolean isRescheduleRepaymentsOnHolidaysEnabled() {
+        final String holidaysConfigurationProperty = "reschedule-repayments-on-holidays";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository
+                .findOneByNameWithNotFoundDetection(holidaysConfigurationProperty);
+        return property.isEnabled();
+    }
+
+    @Override
+    public boolean allowTransactionsOnHolidayEnabled() {
+        final String allowTransactionsOnHolidayProperty = "allow-transactions-on-holiday";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository
+                .findOneByNameWithNotFoundDetection(allowTransactionsOnHolidayProperty);
+        return property.isEnabled();
+    }
+
+    @Override
+    public boolean allowTransactionsOnNonWorkingDayEnabled() {
+        final String propertyName = "allow-transactions-on-non_workingday";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.isEnabled();
+    }
+
+    @Override
+    public boolean isConstraintApproachEnabledForDatatables() {
+        final String propertyName = "constraint_approach_for_datatables";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.isEnabled();
+    }
+
+    @Override
+    public boolean isEhcacheEnabled() {
+        return this.cacheTypeRepository.findOne(Long.valueOf(1)).isEhcacheEnabled();
+    }
+
+    @Transactional
+    @Override
+    public void updateCache(final CacheType cacheType) {
+        final PlatformCache cache = this.cacheTypeRepository.findOne(Long.valueOf(1));
+        cache.update(cacheType);
+        this.cacheTypeRepository.save(cache);
+    }
+
+    @Override
+    public Long retrievePenaltyWaitPeriod() {
+        final String propertyName = "penalty-wait-period";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.getValue();
+    }
+
+    @Override
+    public Long retrieveGraceOnPenaltyPostingPeriod() {
+        final String propertyName = "grace-on-penalty-posting";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.getValue();
+    }
+
+    @Override
+    public boolean isPasswordForcedResetEnable() {
+        final String propertyName = "force-password-reset-days";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.isEnabled();
+    }
+
+    @Override
+    public Long retrievePasswordLiveTime() {
+        final String propertyName = "force-password-reset-days";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.getValue();
+    }
+
+    @Override
+    public Long retrieveOpeningBalancesContraAccount() {
+        final String propertyName = "office-opening-balances-contra-account";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.getValue();
+    }
+
+    @Override
+    public boolean isSavingsInterestPostingAtCurrentPeriodEnd() {
+        final String propertyName = "savings-interest-posting-current-period-end";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.isEnabled();
+    }
+
+    @Override
+    public Integer retrieveFinancialYearBeginningMonth() {
+        final String propertyName = "financial-year-beginning-month";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        if (property.isEnabled()) return property.getValue().intValue();
+        return 1;
+    }
+
+    @Override
+    public Integer retrieveMinAllowedClientsInGroup() {
+        final String propertyName = "min-clients-in-group";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        if (property.isEnabled()) { return property.getValue().intValue(); }
+        return null;
+    }
+
+    @Override
+    public Integer retrieveMaxAllowedClientsInGroup() {
+        final String propertyName = "max-clients-in-group";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        if (property.isEnabled()) { return property.getValue().intValue(); }
+        return null;
+    }
+
+    @Override
+    public boolean isMeetingMandatoryForJLGLoans() {
+        final String propertyName = "meetings-mandatory-for-jlg-loans";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.isEnabled();
+    }
+
+    @Override
+    public int getRoundingMode() {
+        final String propertyName = "rounding-mode";
+        int defaultValue = 6; // 6 Stands for HALF-EVEN
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        if (property.isEnabled()) {
+            int value = property.getValue().intValue();
+            if (value < 0 || value > 6) {
+                return defaultValue;
+            }
+            return value;
+        }
+        return defaultValue;
+    }
+
+    public boolean isBackdatePenaltiesEnabled() {
+        final String propertyName = "backdate-penalties-enabled";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.isEnabled();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalService.java
new file mode 100644
index 0000000..5bbd0ed
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalService.java
@@ -0,0 +1,54 @@
+/**
+ * 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.configuration.domain;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "c_external_service", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "name_UNIQUE") })
+public class ExternalService extends AbstractPersistable<Long> {
+
+    @Column(name = "name", length = 50)
+    private String name;
+
+    // @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy =
+    // "externalServicePropertiesPK.externalService", orphanRemoval = true)
+    // private Set<ExternalServicesProperties> values;
+
+    public static ExternalService fromJson(final JsonCommand command) {
+        final String name = command.stringValueOfParameterNamed("name");
+        return new ExternalService(name);
+    }
+
+    private ExternalService(final String name) {
+        this.name = name;
+    }
+
+    protected ExternalService() {}
+
+    public String name() {
+        return this.name;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicePropertiesPK.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicePropertiesPK.java
new file mode 100644
index 0000000..c9fc332
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicePropertiesPK.java
@@ -0,0 +1,52 @@
+/**
+ * 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.configuration.domain;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+@Embeddable
+public class ExternalServicePropertiesPK implements Serializable {
+
+    @Column(name = "name", length = 150)
+    private String name;
+
+    @Column(name = "external_service_id")
+    private Long externalServiceId;
+
+    public ExternalServicePropertiesPK() {
+
+    }
+
+    public ExternalServicePropertiesPK(Long externalServiceId, String name) {
+        this.externalServiceId = externalServiceId;
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Long getExternalService() {
+        return externalServiceId;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicesProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicesProperties.java
new file mode 100644
index 0000000..3504056
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicesProperties.java
@@ -0,0 +1,84 @@
+/**
+ * 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.configuration.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.configuration.data.ExternalServicesPropertiesData;
+import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS;
+import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.SMTP_JSON_INPUT_PARAMS;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+
+@Entity
+@Table(name = "c_external_service_properties")
+public class ExternalServicesProperties {
+
+    @EmbeddedId
+    ExternalServicePropertiesPK externalServicePropertiesPK;
+
+    @Column(name = "value", length = 250)
+    private String value;
+
+    protected ExternalServicesProperties() {
+
+    }
+
+    private ExternalServicesProperties(final ExternalServicePropertiesPK externalServicePropertiesPK, final String value) {
+        this.externalServicePropertiesPK = externalServicePropertiesPK;
+
+        this.value = value;
+
+    }
+
+    public static ExternalServicesProperties fromJson(final ExternalService externalService, final JsonCommand command) {
+        final String name = command.stringValueOfParameterNamed(EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS.NAME.getValue());
+        final String value = command.stringValueOfParameterNamed(EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS.VALUE.getValue());
+        return new ExternalServicesProperties(new ExternalServicePropertiesPK(externalService.getId(), name), value);
+    }
+
+    public Map<String, Object> update(final JsonCommand command, String paramName) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(2);
+
+        final String valueParamName = EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS.VALUE.getValue();
+        if (command.isChangeInStringParameterNamed(paramName, this.value)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            if (paramName.equals(SMTP_JSON_INPUT_PARAMS.PASSWORD.getValue()) && newValue.equals("XXXX")) {
+                // If Param Name is Password and ParamValue is XXXX that means
+                // the password has not been changed.
+            } else {
+                actualChanges.put(valueParamName, newValue);
+            }
+            this.value = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        return actualChanges;
+    }
+
+    public ExternalServicesPropertiesData toData() {
+        return new ExternalServicesPropertiesData(this.externalServicePropertiesPK.getName(), this.value);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicesPropertiesRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicesPropertiesRepository.java
new file mode 100644
index 0000000..98a0e5c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicesPropertiesRepository.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.configuration.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ExternalServicesPropertiesRepository extends JpaRepository<ExternalServicesProperties, ExternalServicePropertiesPK>,
+        JpaSpecificationExecutor<ExternalServicesProperties> {
+
+    ExternalServicesProperties findOneByExternalServicePropertiesPK(ExternalServicePropertiesPK externalServicesProptiesPK);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicesPropertiesRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicesPropertiesRepositoryWrapper.java
new file mode 100644
index 0000000..92201f4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ExternalServicesPropertiesRepositoryWrapper.java
@@ -0,0 +1,41 @@
+/**
+ * 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.configuration.domain;
+
+import org.apache.fineract.infrastructure.configuration.exception.ExternalServiceConfigurationNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ExternalServicesPropertiesRepositoryWrapper {
+
+    private final ExternalServicesPropertiesRepository repository;
+
+    @Autowired
+    public ExternalServicesPropertiesRepositoryWrapper(final ExternalServicesPropertiesRepository repository) {
+        this.repository = repository;
+    }
+
+    public ExternalServicesProperties findOneByIdAndName(Long id, String name, String externalServiceName) {
+        final ExternalServicesProperties externalServicesProperties = this.repository
+                .findOneByExternalServicePropertiesPK(new ExternalServicePropertiesPK(id, name));
+        if (externalServicesProperties == null) throw new ExternalServiceConfigurationNotFoundException(externalServiceName, name);
+        return externalServicesProperties;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java
new file mode 100755
index 0000000..b29f1f8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java
@@ -0,0 +1,118 @@
+/**
+ * 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.configuration.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.configuration.exception.GlobalConfigurationPropertyCannotBeModfied;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.security.exception.ForcePasswordResetException;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "c_configuration")
+public class GlobalConfigurationProperty extends AbstractPersistable<Long> {
+
+    @Column(name = "name", nullable = false)
+    private final String name;
+
+    @Column(name = "enabled", nullable = false)
+    private boolean enabled;
+
+    @Column(name = "value", nullable = true)
+    private Long value;
+
+    @Column(name = "description", nullable = true)
+    private final String description;
+
+    @Column(name = "is_trap_door", nullable = false)
+    private boolean isTrapDoor;
+
+    protected GlobalConfigurationProperty() {
+        this.name = null;
+        this.enabled = false;
+        this.value = null;
+        this.description = null;
+        this.isTrapDoor = false;
+    }
+
+    public GlobalConfigurationProperty(final String name, final boolean enabled, final Long value, final String description,
+            final boolean isTrapDoor) {
+        this.name = name;
+        this.enabled = enabled;
+        this.value = value;
+        this.description = description;
+        this.isTrapDoor = isTrapDoor;
+    }
+
+    public boolean isEnabled() {
+        return this.enabled;
+    }
+
+    public Long getValue() {
+        return this.value;
+    }
+
+    public boolean updateTo(final boolean value) {
+        final boolean updated = this.enabled != value;
+        this.enabled = value;
+        return updated;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        if (this.isTrapDoor == true) { throw new GlobalConfigurationPropertyCannotBeModfied(this.getId()); }
+
+        final String enabledParamName = "enabled";
+        if (command.isChangeInBooleanParameterNamed(enabledParamName, this.enabled)) {
+            final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enabledParamName);
+            actualChanges.put(enabledParamName, newValue);
+            this.enabled = newValue;
+        }
+
+        final String valueParamName = "value";
+        final Long previousValue = this.value;
+        if (command.isChangeInLongParameterNamed(valueParamName, this.value)) {
+            final Long newValue = command.longValueOfParameterNamed(valueParamName);
+            actualChanges.put(valueParamName, newValue);
+            this.value = newValue;
+        }
+
+        final String passwordPropertyName = "force-password-reset-days";
+        if (this.name.equalsIgnoreCase(passwordPropertyName)) {
+            if (this.enabled == true && command.hasParameter(valueParamName) && this.value == 0 || this.enabled == true
+                    && !command.hasParameter(valueParamName) && previousValue == 0) { throw new ForcePasswordResetException(); }
+        }
+
+        return actualChanges;
+
+    }
+
+    public static GlobalConfigurationProperty newSurveyConfiguration(final String name) {
+        return new GlobalConfigurationProperty(name, false, null, null, false);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepository.java
new file mode 100644
index 0000000..faeaffa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepository.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.configuration.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface GlobalConfigurationRepository extends JpaRepository<GlobalConfigurationProperty, Long>,
+        JpaSpecificationExecutor<GlobalConfigurationProperty> {
+
+    GlobalConfigurationProperty findOneByName(String name);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepositoryWrapper.java
new file mode 100644
index 0000000..d7ea53d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepositoryWrapper.java
@@ -0,0 +1,65 @@
+/**
+ * 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.configuration.domain;
+
+import org.apache.fineract.infrastructure.configuration.exception.GlobalConfigurationPropertyNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link GlobalConfigurationRepository} that adds NULL checking and
+ * Error handling capabilities
+ * </p>
+ */
+@Service
+public class GlobalConfigurationRepositoryWrapper {
+
+    private final GlobalConfigurationRepository repository;
+
+    @Autowired
+    public GlobalConfigurationRepositoryWrapper(final GlobalConfigurationRepository repository) {
+        this.repository = repository;
+    }
+
+    public GlobalConfigurationProperty findOneByNameWithNotFoundDetection(final String propertyName) {
+        final GlobalConfigurationProperty property = this.repository.findOneByName(propertyName);
+        if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(propertyName); }
+        return property;
+    }
+
+    public GlobalConfigurationProperty findOneWithNotFoundDetection(final Long configId) {
+        final GlobalConfigurationProperty property = this.repository.findOne(configId);
+        if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(configId); }
+        return property;
+    }
+
+    public void save(final GlobalConfigurationProperty globalConfigurationProperty) {
+        this.repository.save(globalConfigurationProperty);
+    }
+
+    public void saveAndFlush(final GlobalConfigurationProperty globalConfigurationProperty) {
+        this.repository.saveAndFlush(globalConfigurationProperty);
+    }
+
+    public void delete(final GlobalConfigurationProperty globalConfigurationProperty) {
+        this.repository.delete(globalConfigurationProperty);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/exception/ExternalServiceConfigurationNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/exception/ExternalServiceConfigurationNotFoundException.java
new file mode 100644
index 0000000..bd91eae
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/exception/ExternalServiceConfigurationNotFoundException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.configuration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ExternalServiceConfigurationNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ExternalServiceConfigurationNotFoundException(final String serviceName) {
+        super("error.msg.externalservice.servicename.invalid", "Service Name`" + serviceName + "` does not exist", serviceName);
+    }
+
+    public ExternalServiceConfigurationNotFoundException(final String externalServiceName, final String name) {
+        super("error.msg.externalservice.property.invalid",
+                "Parameter`" + name + "` does not exist for the ServiceName `" + externalServiceName + "`", name, externalServiceName);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/exception/GlobalConfigurationPropertyCannotBeModfied.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/exception/GlobalConfigurationPropertyCannotBeModfied.java
new file mode 100644
index 0000000..3e264ad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/exception/GlobalConfigurationPropertyCannotBeModfied.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.infrastructure.configuration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class GlobalConfigurationPropertyCannotBeModfied extends AbstractPlatformDomainRuleException{
+    
+    public GlobalConfigurationPropertyCannotBeModfied(final Long configId) {
+        super("error.msg.configuration.id.not.modifiable", "Configuration identifier `" + configId + "` cannot be modified", new Object[] {configId});
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/exception/GlobalConfigurationPropertyNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/exception/GlobalConfigurationPropertyNotFoundException.java
new file mode 100644
index 0000000..3ac9051
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/exception/GlobalConfigurationPropertyNotFoundException.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.infrastructure.configuration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when global configuration properties are
+ * not found.
+ */
+public class GlobalConfigurationPropertyNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public GlobalConfigurationPropertyNotFoundException(final String propertyName) {
+        super("error.msg.configuration.property.invalid", "Configuration property `" + propertyName + "` does not exist", propertyName);
+    }
+
+    public GlobalConfigurationPropertyNotFoundException(final Long configId) {
+        super("error.msg.configuration.id.invalid", "Configuration identifier `" + configId + "` does not exist", configId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/handler/UpdateExternalServiceConfigurationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/handler/UpdateExternalServiceConfigurationCommandHandler.java
new file mode 100644
index 0000000..a61d512
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/handler/UpdateExternalServiceConfigurationCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.configuration.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.configuration.service.ExternalServiceWritePlatformService;
+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 = "EXTERNALSERVICES", action = "UPDATE")
+public class UpdateExternalServiceConfigurationCommandHandler implements NewCommandSourceHandler {
+
+    private final ExternalServiceWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateExternalServiceConfigurationCommandHandler(final ExternalServiceWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.updateExternalServicesProperties(command.getTransactionId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/handler/UpdateGlobalConfigurationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/handler/UpdateGlobalConfigurationCommandHandler.java
new file mode 100644
index 0000000..990e58a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/handler/UpdateGlobalConfigurationCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.configuration.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.configuration.service.GlobalConfigurationWritePlatformService;
+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 = "CONFIGURATION", action = "UPDATE")
+public class UpdateGlobalConfigurationCommandHandler implements NewCommandSourceHandler {
+
+    private final GlobalConfigurationWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateGlobalConfigurationCommandHandler(final GlobalConfigurationWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.update(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..698ee43
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java
@@ -0,0 +1,73 @@
+/**
+ * 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.configuration.serialization;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.configuration.exception.ExternalServiceConfigurationNotFoundException;
+import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.S3_JSON_INPUT_PARAMS;
+import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.SMTP_JSON_INPUT_PARAMS;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class ExternalServicesPropertiesCommandFromApiJsonDeserializer {
+
+    private final Set<String> S3SupportedParameters = S3_JSON_INPUT_PARAMS.getAllValues();
+    private final Set<String> SMTPSupportedParameters = SMTP_JSON_INPUT_PARAMS.getAllValues();
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ExternalServicesPropertiesCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForUpdate(final String json, final String externalServiceName) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        switch (externalServiceName) {
+            case "S3":
+                this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.S3SupportedParameters);
+            break;
+
+            case "SMTP":
+                this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.SMTPSupportedParameters);
+            break;
+
+            default:
+                throw new ExternalServiceConfigurationNotFoundException(externalServiceName);
+        }
+
+    }
+
+    public Set<String> getNameKeys(final String json) {
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        Map<String, String> jsonMap = this.fromApiJsonHelper.extractDataMap(typeOfMap, json);
+        Set<String> keyNames = jsonMap.keySet();
+        return keyNames;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/GlobalConfigurationCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/GlobalConfigurationCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..933abf4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/GlobalConfigurationCommandFromApiJsonDeserializer.java
@@ -0,0 +1,67 @@
+/**
+ * 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.configuration.serialization;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.configuration.command.UpdateGlobalConfigurationCommand;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link UpdateGlobalConfigurationCommand}'s.
+ */
+@Component
+public final class GlobalConfigurationCommandFromApiJsonDeserializer extends
+        AbstractFromApiJsonDeserializer<UpdateGlobalConfigurationCommand> {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("globalConfiguration"));
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public GlobalConfigurationCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public UpdateGlobalConfigurationCommand commandFromApiJson(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);
+
+        return this.fromApiJsonHelper.fromJson(json, UpdateGlobalConfigurationCommand.class);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformService.java
new file mode 100644
index 0000000..27a7349
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformService.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.configuration.service;
+
+import org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationData;
+import org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationPropertyData;
+
+public interface ConfigurationReadPlatformService {
+
+    GlobalConfigurationPropertyData retrieveGlobalConfiguration(Long configId);
+
+    GlobalConfigurationData retrieveGlobalConfiguration(boolean survey);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java
new file mode 100755
index 0000000..d5c51f9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java
@@ -0,0 +1,98 @@
+/**
+ * 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.configuration.service;
+
+import org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationData;
+import org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationPropertyData;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+@Service
+public class ConfigurationReadPlatformServiceImpl implements ConfigurationReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final RowMapper<GlobalConfigurationPropertyData> rm;
+
+    @Autowired
+    public ConfigurationReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+
+        this.rm = new GlobalConfigurationRowMapper();
+    }
+
+    @Override
+    public GlobalConfigurationData retrieveGlobalConfiguration(final boolean survey) {
+
+        this.context.authenticatedUser();
+
+        String sql = "SELECT c.id, c.name, c.enabled, c.value, c.description, c.is_trap_door FROM c_configuration c ";
+
+        if (survey) {
+            sql += " JOIN x_registered_table on x_registered_table.registered_table_name = c.name ";
+            sql += " WHERE x_registered_table.category =" + DataTableApiConstant.CATEGORY_PPI;
+
+        } 
+
+        sql += "  order by c.id";
+        final List<GlobalConfigurationPropertyData> globalConfiguration = this.jdbcTemplate.query(sql, this.rm, new Object[] {});
+
+        return new GlobalConfigurationData(globalConfiguration);
+    }
+
+    @Override
+    public GlobalConfigurationPropertyData retrieveGlobalConfiguration(Long configId) {
+
+        this.context.authenticatedUser();
+
+        final String sql = "SELECT c.id, c.name, c.enabled, c.value, c.description, c.is_trap_door FROM "
+                + "c_configuration c where c.id=? order by c.id";
+        final GlobalConfigurationPropertyData globalConfiguration = this.jdbcTemplate.queryForObject(sql, this.rm,
+                new Object[] { configId });
+
+        return globalConfiguration;
+    }
+
+    private static final class GlobalConfigurationRowMapper implements RowMapper<GlobalConfigurationPropertyData> {
+
+        @Override
+        public GlobalConfigurationPropertyData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+
+            final String name = rs.getString("name");
+            final boolean enabled = rs.getBoolean("enabled");
+            final Long value = rs.getLong("value");
+            final String description = rs.getString("description");
+            final Long id = rs.getLong("id");
+            final boolean isTrapDoor = rs.getBoolean("is_trap_door");
+            return new GlobalConfigurationPropertyData(name, enabled, value, id, description, isTrapDoor);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServiceWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServiceWritePlatformService.java
new file mode 100644
index 0000000..aa491fb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServiceWritePlatformService.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.configuration.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface ExternalServiceWritePlatformService {
+
+    CommandProcessingResult updateExternalServicesProperties(String externalServiceName, JsonCommand command);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServiceWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServiceWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..b07f71e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServiceWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,93 @@
+/**
+ * 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.configuration.service;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.configuration.data.ExternalServicesData;
+import org.apache.fineract.infrastructure.configuration.domain.ExternalServicesProperties;
+import org.apache.fineract.infrastructure.configuration.domain.ExternalServicesPropertiesRepository;
+import org.apache.fineract.infrastructure.configuration.domain.ExternalServicesPropertiesRepositoryWrapper;
+import org.apache.fineract.infrastructure.configuration.serialization.ExternalServicesPropertiesCommandFromApiJsonDeserializer;
+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.security.service.PlatformSecurityContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class ExternalServiceWritePlatformServiceJpaRepositoryImpl implements ExternalServiceWritePlatformService {
+
+    @SuppressWarnings("unused")
+    private final static Logger logger = LoggerFactory.getLogger(ExternalServiceWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final ExternalServicesPropertiesRepositoryWrapper repositoryWrapper;
+    private final ExternalServicesPropertiesRepository repository;
+    private final ExternalServicesPropertiesCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final ExternalServicesReadPlatformService readPlatformService;
+
+    @Autowired
+    public ExternalServiceWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final ExternalServicesPropertiesRepositoryWrapper repositoryWrapper, final ExternalServicesPropertiesRepository repository,
+            final ExternalServicesPropertiesCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final ExternalServicesReadPlatformService readPlatformService) {
+
+        this.context = context;
+        this.repositoryWrapper = repositoryWrapper;
+        this.repository = repository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.readPlatformService = readPlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateExternalServicesProperties(String externalServiceName, JsonCommand command) {
+        // TODO Auto-generated method stub
+        this.context.authenticatedUser();
+        this.fromApiJsonDeserializer.validateForUpdate(command.json(), externalServiceName);
+        Set<String> keyName = this.fromApiJsonDeserializer.getNameKeys(command.json());
+        ExternalServicesData external = this.readPlatformService.getExternalServiceDetailsByServiceName(externalServiceName);
+        Long externalServiceId = external.getId();
+        Iterator<String> it = keyName.iterator();
+        Map<String, Object> changesList = new LinkedHashMap<>();
+        while (it.hasNext()) {
+            String name = it.next();
+            final ExternalServicesProperties externalServicesProperties = this.repositoryWrapper.findOneByIdAndName(externalServiceId, name,
+                    externalServiceName);
+            final Map<String, Object> changes = externalServicesProperties.update(command, name);
+            changesList.putAll(changes);
+            if (!changes.isEmpty()) {
+                this.repository.saveAndFlush(externalServicesProperties);
+            }
+        }
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()).withEntityId(externalServiceId).with(changesList).build();
+        //
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java
new file mode 100755
index 0000000..c36b444
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.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.infrastructure.configuration.service;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class ExternalServicesConstants {
+
+    public static final String S3_SERVICE_NAME = "S3";
+    public static final String S3_BUCKET_NAME = "s3_bucket_name";
+    public static final String S3_ACCESS_KEY = "s3_access_key";
+    public static final String S3_SECRET_KEY = "s3_secret_key";
+
+    public static final String SMTP_SERVICE_NAME = "SMTP_Email_Account";
+    public static final String SMTP_USERNAME = "username";
+    public static final String SMTP_PASSWORD = "password";
+    public static final String SMTP_HOST = "host";
+    public static final String SMTP_PORT = "port";
+    public static final String SMTP_USE_TLS = "useTLS";
+
+    public static enum EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS {
+        EXTERNAL_SERVICE_ID("external_service_id"), NAME("name"), VALUE("value");
+
+        private final String value;
+
+        private EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+
+        static {
+            for (final EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS type : EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS.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;
+        }
+    }
+
+    public static enum SMTP_JSON_INPUT_PARAMS {
+        USERNAME("username"), PASSWORD("password"), HOST("host"), PORT("port"), USETLS("useTLS");
+
+        private final String value;
+
+        private SMTP_JSON_INPUT_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+
+        static {
+            for (final SMTP_JSON_INPUT_PARAMS type : SMTP_JSON_INPUT_PARAMS.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;
+        }
+    }
+
+    public static enum S3_JSON_INPUT_PARAMS {
+        S3_ACCESS_KEY("s3_access_key"), S3_BUCKET_NAME("s3_bucket_name"), S3_SECRET_KEY("s3_secret_key");
+
+        private final String value;
+
+        private S3_JSON_INPUT_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+
+        static {
+            for (final S3_JSON_INPUT_PARAMS type : S3_JSON_INPUT_PARAMS.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;
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java
new file mode 100644
index 0000000..023d06c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.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.infrastructure.configuration.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.configuration.data.ExternalServicesPropertiesData;
+import org.apache.fineract.infrastructure.configuration.data.S3CredentialsData;
+import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData;
+
+public interface ExternalServicesPropertiesReadPlatformService {
+
+    S3CredentialsData getS3Credentials();
+
+    SMTPCredentialsData getSMTPCredentials();
+
+    Collection<ExternalServicesPropertiesData> retrieveOne(String serviceName);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java
new file mode 100644
index 0000000..a01d2a5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java
@@ -0,0 +1,150 @@
+/**
+ * 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.configuration.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.configuration.data.ExternalServicesPropertiesData;
+import org.apache.fineract.infrastructure.configuration.data.S3CredentialsData;
+import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData;
+import org.apache.fineract.infrastructure.configuration.exception.ExternalServiceConfigurationNotFoundException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ExternalServicesPropertiesReadPlatformServiceImpl implements ExternalServicesPropertiesReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public ExternalServicesPropertiesReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class S3CredentialsDataExtractor implements ResultSetExtractor<S3CredentialsData> {
+
+        @Override
+        public S3CredentialsData extractData(final ResultSet rs) throws SQLException, DataAccessException {
+            String accessKey = null;
+            String bucketName = null;
+            String secretKey = null;
+            while (rs.next()) {
+                if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.S3_ACCESS_KEY)) {
+                    accessKey = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.S3_BUCKET_NAME)) {
+                    bucketName = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.S3_SECRET_KEY)) {
+                    secretKey = rs.getString("value");
+                }
+            }
+            return new S3CredentialsData(bucketName, accessKey, secretKey);
+        }
+    }
+
+    private static final class SMTPCredentialsDataExtractor implements ResultSetExtractor<SMTPCredentialsData> {
+
+        @Override
+        public SMTPCredentialsData extractData(final ResultSet rs) throws SQLException, DataAccessException {
+            String username = null;
+            String password = null;
+            String host = null;
+            String port = "25";
+            boolean useTLS = false;
+
+            while (rs.next()) {
+                if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_USERNAME)) {
+                    username = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_PASSWORD)) {
+                    password = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_HOST)) {
+                    host = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_PORT)) {
+                    port = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_USE_TLS)) {
+                    useTLS = Boolean.parseBoolean(rs.getString("value"));
+                }
+            }
+            return new SMTPCredentialsData(username, password, host, port, useTLS);
+        }
+    }
+
+    private static final class ExternalServiceMapper implements RowMapper<ExternalServicesPropertiesData> {
+
+        @Override
+        public ExternalServicesPropertiesData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            // TODO Auto-generated method stub
+            final String name = rs.getString("name");
+            String value = rs.getString("value");
+            // Masking the password as we should not send the password back
+            if (name != null && "password".equalsIgnoreCase(name)) {
+                value = "XXXX";
+            }
+            return new ExternalServicesPropertiesData(name, value);
+        }
+
+    }
+
+    @Override
+    public S3CredentialsData getS3Credentials() {
+        final ResultSetExtractor<S3CredentialsData> resultSetExtractor = new S3CredentialsDataExtractor();
+        final String sql = "SELECT esp.name, esp.value FROM c_external_service_properties esp inner join c_external_service es on esp.external_service_id = es.id where es.name = '"
+                + ExternalServicesConstants.S3_SERVICE_NAME + "'";
+        final S3CredentialsData s3CredentialsData = this.jdbcTemplate.query(sql, resultSetExtractor, new Object[] {});
+        return s3CredentialsData;
+    }
+
+    @Override
+    public SMTPCredentialsData getSMTPCredentials() {
+        // TODO Auto-generated method stub
+        final ResultSetExtractor<SMTPCredentialsData> resultSetExtractor = new SMTPCredentialsDataExtractor();
+        final String sql = "SELECT esp.name, esp.value FROM c_external_service_properties esp inner join c_external_service es on esp.external_service_id = es.id where es.name = '"
+                + ExternalServicesConstants.SMTP_SERVICE_NAME + "'";
+        final SMTPCredentialsData smtpCredentialsData = this.jdbcTemplate.query(sql, resultSetExtractor, new Object[] {});
+        return smtpCredentialsData;
+    }
+
+    @Override
+    public Collection<ExternalServicesPropertiesData> retrieveOne(String serviceName) {
+        String serviceNameToUse = null;
+        switch (serviceName) {
+            case "S3":
+                serviceNameToUse = ExternalServicesConstants.S3_SERVICE_NAME;
+            break;
+
+            case "SMTP":
+                serviceNameToUse = ExternalServicesConstants.SMTP_SERVICE_NAME;
+            break;
+
+            default:
+                throw new ExternalServiceConfigurationNotFoundException(serviceName);
+        }
+        final ExternalServiceMapper mapper = new ExternalServiceMapper();
+        final String sql = "SELECT esp.name, esp.value FROM c_external_service_properties esp inner join c_external_service es on esp.external_service_id = es.id where es.name = '"
+                + serviceNameToUse + "'";
+        return this.jdbcTemplate.query(sql, mapper, new Object[] {});
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformService.java
new file mode 100755
index 0000000..4ff816c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformService.java
@@ -0,0 +1,27 @@
+/**
+ * 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.configuration.service;
+
+import org.apache.fineract.infrastructure.configuration.data.ExternalServicesData;
+
+public interface ExternalServicesReadPlatformService {
+
+    ExternalServicesData getExternalServiceDetailsByServiceName(String serviceName);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java
new file mode 100644
index 0000000..db25cea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java
@@ -0,0 +1,82 @@
+/**
+ * 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.configuration.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.fineract.infrastructure.configuration.data.ExternalServicesData;
+import org.apache.fineract.infrastructure.configuration.exception.ExternalServiceConfigurationNotFoundException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ExternalServicesReadPlatformServiceImpl implements ExternalServicesReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public ExternalServicesReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public ExternalServicesData getExternalServiceDetailsByServiceName(String serviceName) {
+        // TODO Auto-generated method stub
+        final ResultSetExtractor<ExternalServicesData> resultSetExtractor = new ExternalServicesDetailsDataExtractor();
+        String serviceNameToUse = null;
+        switch (serviceName) {
+            case "S3":
+                serviceNameToUse = ExternalServicesConstants.S3_SERVICE_NAME;
+            break;
+
+            case "SMTP":
+                serviceNameToUse = ExternalServicesConstants.SMTP_SERVICE_NAME;
+            break;
+
+            default:
+                throw new ExternalServiceConfigurationNotFoundException(serviceName);
+        }
+        final String sql = "SELECT es.name as name, es.id as id FROM c_external_service es where es.name='" + serviceNameToUse + "'";
+        final ExternalServicesData externalServicesData = this.jdbcTemplate.query(sql, resultSetExtractor, new Object[] {});
+        return externalServicesData;
+    }
+
+    private static final class ExternalServicesDetailsDataExtractor implements ResultSetExtractor<ExternalServicesData> {
+
+        @Override
+        public ExternalServicesData extractData(ResultSet rs) throws SQLException, DataAccessException {
+            // TODO Auto-generated method stub
+            Long id = (long) 0;
+            String name = null;
+            while (rs.next()) {
+                name = rs.getString("name");
+                id = rs.getLong("id");
+            }
+
+            return new ExternalServicesData(id, name);
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformService.java
new file mode 100644
index 0000000..1eb9682
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformService.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.configuration.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface GlobalConfigurationWritePlatformService {
+
+    CommandProcessingResult update(Long configId, JsonCommand command);
+    void addSurveyConfig(String name);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..d3fffa4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.configuration.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationDataValidator;
+import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationProperty;
+import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationRepositoryWrapper;
+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 GlobalConfigurationWritePlatformServiceJpaRepositoryImpl implements GlobalConfigurationWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final GlobalConfigurationRepositoryWrapper repository;
+    private final GlobalConfigurationDataValidator globalConfigurationDataValidator;
+
+    @Autowired
+    public GlobalConfigurationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final GlobalConfigurationRepositoryWrapper codeRepository, final GlobalConfigurationDataValidator dataValidator) {
+        this.context = context;
+        this.repository = codeRepository;
+        this.globalConfigurationDataValidator = dataValidator;
+
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult update(final Long configId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        try {
+            this.globalConfigurationDataValidator.validateForUpdate(command);
+
+            final GlobalConfigurationProperty configItemForUpdate = this.repository.findOneWithNotFoundDetection(configId);
+            
+            final Map<String, Object> changes = configItemForUpdate.update(command);
+
+            if (!changes.isEmpty()) {
+                this.repository.save(configItemForUpdate);
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(configId).with(changes).build();
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+
+    @Transactional
+    @Override
+    public void addSurveyConfig(final String name)
+    {
+        try{
+            final GlobalConfigurationProperty ppi = GlobalConfigurationProperty.newSurveyConfiguration(name);
+            this.repository.save(ppi);
+        }
+        catch (final DataIntegrityViolationException dve)
+        {
+            handleDataIntegrityIssues(dve);
+        }
+
+    }
+
+
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.globalConfiguration.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ApiParameterHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ApiParameterHelper.java
new file mode 100644
index 0000000..2828f5b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ApiParameterHelper.java
@@ -0,0 +1,183 @@
+/**
+ * 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.core.api;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
+
+public class ApiParameterHelper {
+
+    public static Long commandId(final MultivaluedMap<String, String> queryParams) {
+        Long id = null;
+        if (queryParams.getFirst("commandId") != null) {
+            final String value = queryParams.getFirst("commandId");
+            if (StringUtils.isNotBlank(value)) {
+                id = Long.valueOf(value);
+            }
+        }
+        return id;
+    }
+
+    public static Set<String> extractFieldsForResponseIfProvided(final MultivaluedMap<String, String> queryParams) {
+        Set<String> fields = new HashSet<>();
+        String commaSerperatedParameters = "";
+        if (queryParams.getFirst("fields") != null) {
+            commaSerperatedParameters = queryParams.getFirst("fields");
+            if (StringUtils.isNotBlank(commaSerperatedParameters)) {
+                fields = new HashSet<>(Arrays.asList(commaSerperatedParameters.split("\\s*,\\s*")));
+            }
+        }
+        return fields;
+    }
+
+    public static Set<String> extractAssociationsForResponseIfProvided(final MultivaluedMap<String, String> queryParams) {
+        Set<String> fields = new HashSet<>();
+        String commaSerperatedParameters = "";
+        if (queryParams.getFirst("associations") != null) {
+            commaSerperatedParameters = queryParams.getFirst("associations");
+            if (StringUtils.isNotBlank(commaSerperatedParameters)) {
+                fields = new HashSet<>(Arrays.asList(commaSerperatedParameters.split("\\s*,\\s*")));
+            }
+        }
+        return fields;
+    }
+
+    public static void excludeAssociationsForResponseIfProvided(final MultivaluedMap<String, String> queryParams, Set<String> fields) {
+        String commaSerperatedParameters = "";
+        if (queryParams.getFirst("exclude") != null) {
+            commaSerperatedParameters = queryParams.getFirst("exclude");
+            if (StringUtils.isNotBlank(commaSerperatedParameters)) {
+                fields.removeAll(new HashSet<>(Arrays.asList(commaSerperatedParameters.split("\\s*,\\s*"))));
+            }
+        }
+    }
+
+    public static boolean prettyPrint(final MultivaluedMap<String, String> queryParams) {
+        boolean prettyPrint = false;
+        if (queryParams.getFirst("pretty") != null) {
+            final String prettyPrintValue = queryParams.getFirst("pretty");
+            prettyPrint = "true".equalsIgnoreCase(prettyPrintValue);
+        }
+        return prettyPrint;
+    }
+
+    public static Locale extractLocale(final MultivaluedMap<String, String> queryParams) {
+        Locale locale = null;
+        if (queryParams.getFirst("locale") != null) {
+            final String localeAsString = queryParams.getFirst("locale");
+            locale = JsonParserHelper.localeFromString(localeAsString);
+        }
+        return locale;
+    }
+
+    public static boolean exportCsv(final MultivaluedMap<String, String> queryParams) {
+        boolean exportCsv = false;
+        if (queryParams.getFirst("exportCSV") != null) {
+            final String exportCsvValue = queryParams.getFirst("exportCSV");
+            exportCsv = "true".equalsIgnoreCase(exportCsvValue);
+        }
+        return exportCsv;
+    }
+
+    public static boolean exportPdf(final MultivaluedMap<String, String> queryParams) {
+        boolean exportPDF = false;
+        if (queryParams.getFirst("exportPDF") != null) {
+            final String exportPdfValue = queryParams.getFirst("exportPDF");
+            exportPDF = "true".equalsIgnoreCase(exportPdfValue);
+        }
+        return exportPDF;
+    }
+
+    public static boolean parameterType(final MultivaluedMap<String, String> queryParams) {
+        boolean parameterType = false;
+        if (queryParams.getFirst("parameterType") != null) {
+            final String parameterTypeValue = queryParams.getFirst("parameterType");
+            parameterType = "true".equalsIgnoreCase(parameterTypeValue);
+        }
+        return parameterType;
+    }
+
+    public static boolean template(final MultivaluedMap<String, String> queryParams) {
+        boolean template = false;
+        if (queryParams.getFirst("template") != null) {
+            final String prettyPrintValue = queryParams.getFirst("template");
+            template = "true".equalsIgnoreCase(prettyPrintValue);
+        }
+        return template;
+    }
+
+    public static boolean makerCheckerable(final MultivaluedMap<String, String> queryParams) {
+        boolean makerCheckerable = false;
+        if (queryParams.getFirst("makerCheckerable") != null) {
+            final String prettyPrintValue = queryParams.getFirst("makerCheckerable");
+            makerCheckerable = "true".equalsIgnoreCase(prettyPrintValue);
+        }
+        return makerCheckerable;
+    }
+
+    public static boolean includeJson(final MultivaluedMap<String, String> queryParams) {
+        boolean includeJson = false;
+        if (queryParams.getFirst("includeJson") != null) {
+            final String includeJsonValue = queryParams.getFirst("includeJson");
+            includeJson = "true".equalsIgnoreCase(includeJsonValue);
+        }
+        return includeJson;
+    }
+
+    public static boolean genericResultSet(final MultivaluedMap<String, String> queryParams) {
+        boolean genericResultSet = false;
+        if (queryParams.getFirst("genericResultSet") != null) {
+            final String genericResultSetValue = queryParams.getFirst("genericResultSet");
+            genericResultSet = "true".equalsIgnoreCase(genericResultSetValue);
+        }
+        return genericResultSet;
+    }
+
+    public static boolean genericResultSetPassed(final MultivaluedMap<String, String> queryParams) {
+        return queryParams.getFirst("genericResultSet") != null;
+    }
+
+    public static String sqlEncodeString(final String str) {
+        final String singleQuote = "'";
+        final String twoSingleQuotes = "''";
+        return singleQuote + StringUtils.replace(str, singleQuote, twoSingleQuotes, -1) + singleQuote;
+    }
+
+    public static Map<String, String> asMap(final MultivaluedMap<String, String> queryParameters) {
+
+        final Map<String, String> map = new HashMap<>(queryParameters.size());
+
+        for (final String parameterName : queryParameters.keySet()) {
+            final List<String> values = queryParameters.get(parameterName);
+            map.put(parameterName, values.get(0));
+        }
+
+        return map;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ApiRequestParameterHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ApiRequestParameterHelper.java
new file mode 100644
index 0000000..3961890
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ApiRequestParameterHelper.java
@@ -0,0 +1,67 @@
+/**
+ * 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.core.api;
+
+import java.util.Set;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.springframework.stereotype.Component;
+
+/**
+ * <p>
+ * Used to process the query parameters provided in a API request to see
+ * features of the RESTful API are being asked for such as:
+ * </p>
+ * <ul>
+ * <li>Pretty printing through pretty=true, defaults to false</li>
+ * <li>Partial response through fields=id, name etc, when empty, the full data
+ * is returned by default.</li>
+ * </ul
+ */
+@Component
+public class ApiRequestParameterHelper {
+
+    public ApiRequestJsonSerializationSettings process(final MultivaluedMap<String, String> queryParameters,
+            final Set<String> mandatoryResponseParameters) {
+
+        final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(queryParameters);
+        if (!responseParameters.isEmpty()) {
+            responseParameters.addAll(mandatoryResponseParameters);
+        }
+        final boolean prettyPrint = ApiParameterHelper.prettyPrint(queryParameters);
+        final boolean template = ApiParameterHelper.template(queryParameters);
+        final boolean makerCheckerable = ApiParameterHelper.makerCheckerable(queryParameters);
+        final boolean includeJson = ApiParameterHelper.includeJson(queryParameters);
+
+        return ApiRequestJsonSerializationSettings.from(prettyPrint, responseParameters, template, makerCheckerable, includeJson);
+    }
+
+    public ApiRequestJsonSerializationSettings process(final MultivaluedMap<String, String> queryParameters) {
+
+        final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(queryParameters);
+        final boolean prettyPrint = ApiParameterHelper.prettyPrint(queryParameters);
+        final boolean template = ApiParameterHelper.template(queryParameters);
+        final boolean makerCheckerable = ApiParameterHelper.makerCheckerable(queryParameters);
+        final boolean includeJson = ApiParameterHelper.includeJson(queryParameters);
+
+        return ApiRequestJsonSerializationSettings.from(prettyPrint, responseParameters, template, makerCheckerable, includeJson);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaDateTimeAdapter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaDateTimeAdapter.java
new file mode 100644
index 0000000..b47919c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaDateTimeAdapter.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.api;
+
+import java.lang.reflect.Type;
+
+import org.joda.time.DateTime;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * Serializer for joda time {@link DateTime} that returns date as long to match
+ * previous functionality.
+ */
+public class JodaDateTimeAdapter implements JsonSerializer<DateTime> {
+
+    @SuppressWarnings("unused")
+    @Override
+    public JsonElement serialize(final DateTime src, final Type typeOfSrc, final JsonSerializationContext context) {
+
+        JsonElement element = null;
+        if (src != null) {
+            element = new JsonPrimitive(src.getMillis());
+        }
+
+        return element;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaLocalDateAdapter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaLocalDateAdapter.java
new file mode 100644
index 0000000..96f7acc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaLocalDateAdapter.java
@@ -0,0 +1,51 @@
+/**
+ * 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.core.api;
+
+import java.lang.reflect.Type;
+
+import org.joda.time.LocalDate;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * Serializer for joda time {@link LocalDate} that returns date in array format
+ * to match previous jackson functionality.
+ */
+public class JodaLocalDateAdapter implements JsonSerializer<LocalDate> {
+
+    @SuppressWarnings("unused")
+    @Override
+    public JsonElement serialize(final LocalDate src, final Type typeOfSrc, final JsonSerializationContext context) {
+
+        JsonArray array = null;
+        if (src != null) {
+            array = new JsonArray();
+            array.add(new JsonPrimitive(src.getYearOfEra()));
+            array.add(new JsonPrimitive(src.getMonthOfYear()));
+            array.add(new JsonPrimitive(src.getDayOfMonth()));
+        }
+
+        return array;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaMonthDayAdapter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaMonthDayAdapter.java
new file mode 100644
index 0000000..5cce8cb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaMonthDayAdapter.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.infrastructure.core.api;
+
+import java.lang.reflect.Type;
+
+import org.joda.time.MonthDay;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * Serializer for joda time {@link MonthDay} that returns date in array format
+ * to match previous jackson functionality.
+ */
+public class JodaMonthDayAdapter implements JsonSerializer<MonthDay> {
+
+    @SuppressWarnings("unused")
+    @Override
+    public JsonElement serialize(final MonthDay src, final Type typeOfSrc, final JsonSerializationContext context) {
+
+        JsonArray array = null;
+        if (src != null) {
+            array = new JsonArray();
+            array.add(new JsonPrimitive(src.getMonthOfYear()));
+            array.add(new JsonPrimitive(src.getDayOfMonth()));
+        }
+
+        return array;
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..cc04657
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
@@ -0,0 +1,536 @@
+/**
+ * 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.core.api;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.security.domain.BasicPasswordEncodablePlatformUser;
+import org.apache.fineract.infrastructure.security.domain.PlatformUser;
+import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Immutable representation of a command.
+ * 
+ * Wraps the provided JSON with convenience functions for extracting parameter
+ * values and checking for changes against an existing value.
+ */
+public final class JsonCommand {
+
+    private final String jsonCommand;
+    private final JsonElement parsedCommand;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final Long commandId;
+    private final Long resourceId;
+    private final Long subresourceId;
+    private final Long groupId;
+    private final Long clientId;
+    private final Long loanId;
+    private final Long savingsId;
+    private final String entityName;
+    private final String transactionId;
+    private final String url;
+    private final Long productId;
+
+    public static JsonCommand from(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper,
+            final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId,
+            final Long loanId, final Long savingsId, final String transactionId, final String url, final Long productId) {
+        return new JsonCommand(null, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId,
+                clientId, loanId, savingsId, transactionId, url, productId);
+
+    }
+
+    public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
+            final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
+            final String url, final Long productId) {
+        return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, null, null,
+                null, null, null, url, productId);
+    }
+
+    public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
+            final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
+            final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final String url,
+            final Long productId) {
+        return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId,
+                clientId, loanId, savingsId, transactionId, url, productId);
+
+    }
+
+    public static JsonCommand fromExistingCommand(JsonCommand command, final JsonElement parsedCommand) {
+        final String jsonCommand = command.fromApiJsonHelper.toJson(parsedCommand);
+        return new JsonCommand(command.commandId, jsonCommand, parsedCommand, command.fromApiJsonHelper, command.entityName,
+                command.resourceId, command.subresourceId, command.groupId, command.clientId, command.loanId, command.savingsId,
+                command.transactionId, command.url, command.productId);
+    }
+
+    public JsonCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
+            final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
+            final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final String url,
+            final Long productId) {
+
+        this.commandId = commandId;
+        this.jsonCommand = jsonCommand;
+        this.parsedCommand = parsedCommand;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.entityName = entityName;
+        this.resourceId = resourceId;
+        this.subresourceId = subresourceId;
+        this.groupId = groupId;
+        this.clientId = clientId;
+        this.loanId = loanId;
+        this.savingsId = savingsId;
+        this.transactionId = transactionId;
+        this.url = url;
+        this.productId = productId;
+    }
+
+    public String json() {
+        return this.jsonCommand;
+    }
+
+    public JsonElement parsedJson() {
+        return this.parsedCommand;
+    }
+
+    public String jsonFragment(final String paramName) {
+        String jsonFragment = null;
+        if (this.parsedCommand.getAsJsonObject().has(paramName)) {
+            final JsonElement fragment = this.parsedCommand.getAsJsonObject().get(paramName);
+            jsonFragment = this.fromApiJsonHelper.toJson(fragment);
+        }
+        return jsonFragment;
+    }
+
+    public Long commandId() {
+        return this.commandId;
+    }
+
+    public String entityName() {
+        return this.entityName;
+    }
+
+    public Long entityId() {
+        return this.resourceId;
+    }
+
+    public Long subentityId() {
+        return this.subresourceId;
+    }
+
+    public Long getGroupId() {
+        return this.groupId;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public Long getSavingsId() {
+        return this.savingsId;
+    }
+
+    public String getTransactionId() {
+        return this.transactionId;
+    }
+
+    public String getUrl() {
+        return this.url;
+    }
+
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    private boolean differenceExists(final LocalDate baseValue, final LocalDate workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (baseValue != null) {
+            differenceExists = !baseValue.equals(workingCopyValue);
+        } else {
+            differenceExists = workingCopyValue != null;
+        }
+
+        return differenceExists;
+    }
+
+    private boolean differenceExists(final String baseValue, final String workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (StringUtils.isNotBlank(baseValue)) {
+            differenceExists = !baseValue.equals(workingCopyValue);
+        } else {
+            differenceExists = StringUtils.isNotBlank(workingCopyValue);
+        }
+
+        return differenceExists;
+    }
+
+    private boolean differenceExists(final String[] baseValue, final String[] workingCopyValue) {
+        Arrays.sort(baseValue);
+        Arrays.sort(workingCopyValue);
+        return !Arrays.equals(baseValue, workingCopyValue);
+    }
+
+    private boolean differenceExists(final Number baseValue, final Number workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (baseValue != null) {
+            if (workingCopyValue != null) {
+                differenceExists = !baseValue.equals(workingCopyValue);
+            } else {
+                differenceExists = true;
+            }
+        } else {
+            differenceExists = workingCopyValue != null;
+        }
+
+        return differenceExists;
+    }
+
+    private boolean differenceExists(final BigDecimal baseValue, final BigDecimal workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (baseValue != null) {
+            if (workingCopyValue != null) {
+                differenceExists = baseValue.compareTo(workingCopyValue) != 0;
+            } else {
+                differenceExists = true;
+            }
+        } else {
+            differenceExists = workingCopyValue != null;
+        }
+
+        return differenceExists;
+    }
+
+    private boolean differenceExists(final Boolean baseValue, final Boolean workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (baseValue != null) {
+            differenceExists = !baseValue.equals(workingCopyValue);
+        } else {
+            differenceExists = workingCopyValue != null;
+        }
+
+        return differenceExists;
+    }
+
+    public boolean parameterExists(final String parameterName) {
+        return this.fromApiJsonHelper.parameterExists(parameterName, this.parsedCommand);
+    }
+
+    public boolean hasParameter(final String parameterName) {
+        return parameterExists(parameterName);
+    }
+
+    public String dateFormat() {
+        return stringValueOfParameterNamed("dateFormat");
+    }
+
+    public String locale() {
+        return stringValueOfParameterNamed("locale");
+    }
+
+    public boolean isChangeInLongParameterNamed(final String parameterName, final Long existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Long workingValue = longValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public Long longValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractLongNamed(parameterName, this.parsedCommand);
+    }
+
+    public boolean isChangeInDateParameterNamed(final String parameterName, final Date existingValue) {
+        LocalDate localDate = null;
+        if (existingValue != null) {
+            localDate = LocalDate.fromDateFields(existingValue);
+        }
+        return isChangeInLocalDateParameterNamed(parameterName, localDate);
+    }
+
+    public boolean isChangeInLocalDateParameterNamed(final String parameterName, final LocalDate existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final LocalDate workingValue = localDateValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public LocalDate localDateValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractLocalDateNamed(parameterName, this.parsedCommand);
+    }
+
+    public MonthDay extractMonthDayNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractMonthDayNamed(parameterName, this.parsedCommand);
+    }
+
+    public Date DateValueOfParameterNamed(final String parameterName) {
+        final LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed(parameterName, this.parsedCommand);
+        if (localDate == null) { return null; }
+        return localDate.toDateTimeAtStartOfDay().toDate();
+    }
+
+    public boolean isChangeInStringParameterNamed(final String parameterName, final String existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final String workingValue = stringValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public String stringValueOfParameterNamed(final String parameterName) {
+        final String value = this.fromApiJsonHelper.extractStringNamed(parameterName, this.parsedCommand);
+        return StringUtils.defaultIfEmpty(value, "");
+    }
+
+    public String stringValueOfParameterNamedAllowingNull(final String parameterName) {
+        return this.fromApiJsonHelper.extractStringNamed(parameterName, this.parsedCommand);
+    }
+
+    public Map<String, String> mapValueOfParameterNamed(final String json) {
+        final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType();
+        final Map<String, String> value = this.fromApiJsonHelper.extractDataMap(typeOfMap, json);
+        return value;
+    }
+
+    public boolean isChangeInBigDecimalParameterNamedDefaultingZeroToNull(final String parameterName, final BigDecimal existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final BigDecimal workingValue = bigDecimalValueOfParameterNamedDefaultToNullIfZero(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public boolean isChangeInBigDecimalParameterNamed(final String parameterName, final BigDecimal existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final BigDecimal workingValue = bigDecimalValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public boolean isChangeInBigDecimalParameterNamed(final String parameterName, final BigDecimal existingValue, final Locale locale) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final BigDecimal workingValue = bigDecimalValueOfParameterNamed(parameterName, locale);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public boolean isChangeInBigDecimalParameterNamedWithNullCheck(final String parameterName, final BigDecimal existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final BigDecimal workingValue = bigDecimalValueOfParameterNamed(parameterName);
+            if (workingValue == null && existingValue != null) {
+                isChanged = true;
+            } else {
+                isChanged = differenceExists(existingValue, workingValue);
+            }
+        }
+        return isChanged;
+    }
+
+    private static BigDecimal defaultToNullIfZero(final BigDecimal value) {
+        BigDecimal result = value;
+        if (value != null && BigDecimal.ZERO.compareTo(value) == 0) {
+            result = null;
+        }
+        return result;
+    }
+
+    private static Integer defaultToNullIfZero(final Integer value) {
+        Integer result = value;
+        if (value != null && value == 0) {
+            result = null;
+        }
+        return result;
+    }
+
+    public BigDecimal bigDecimalValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(parameterName, this.parsedCommand);
+    }
+
+    public BigDecimal bigDecimalValueOfParameterNamed(final String parameterName, final Locale locale) {
+        return this.fromApiJsonHelper.extractBigDecimalNamed(parameterName, this.parsedCommand, locale);
+    }
+
+    public BigDecimal bigDecimalValueOfParameterNamedDefaultToNullIfZero(final String parameterName) {
+        return defaultToNullIfZero(bigDecimalValueOfParameterNamed(parameterName));
+    }
+
+    public boolean isChangeInIntegerParameterNamedDefaultingZeroToNull(final String parameterName, final Integer existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Integer workingValue = integerValueOfParameterNamedDefaultToNullIfZero(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public boolean isChangeInIntegerParameterNamed(final String parameterName, final Integer existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Integer workingValue = integerValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public boolean isChangeInIntegerParameterNamed(final String parameterName, final Integer existingValue, final Locale locale) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Integer workingValue = integerValueOfParameterNamed(parameterName, locale);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public boolean isChangeInIntegerParameterNamedWithNullCheck(final String parameterName, final Integer existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Integer workingValue = integerValueOfParameterNamed(parameterName);
+            if (workingValue == null && existingValue != null) {
+                isChanged = true;
+            } else {
+                isChanged = differenceExists(existingValue, workingValue);
+            }
+        }
+        return isChanged;
+    }
+
+    public Integer integerValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractIntegerWithLocaleNamed(parameterName, this.parsedCommand);
+    }
+
+    public Integer integerValueOfParameterNamed(final String parameterName, final Locale locale) {
+        return this.fromApiJsonHelper.extractIntegerNamed(parameterName, this.parsedCommand, locale);
+    }
+
+    public Integer integerValueOfParameterNamedDefaultToNullIfZero(final String parameterName) {
+        return defaultToNullIfZero(integerValueOfParameterNamed(parameterName));
+    }
+
+    public boolean isChangeInIntegerSansLocaleParameterNamed(final String parameterName, final Integer existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Integer workingValue = integerValueSansLocaleOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public Integer integerValueSansLocaleOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractIntegerSansLocaleNamed(parameterName, this.parsedCommand);
+    }
+
+    public boolean isChangeInBooleanParameterNamed(final String parameterName, final Boolean existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Boolean workingValue = booleanObjectValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    /**
+     * Returns {@link Boolean} that could possibly be null.
+     */
+    public Boolean booleanObjectValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractBooleanNamed(parameterName, this.parsedCommand);
+    }
+
+    /**
+     * always returns true or false
+     */
+    public boolean booleanPrimitiveValueOfParameterNamed(final String parameterName) {
+        final Boolean value = this.fromApiJsonHelper.extractBooleanNamed(parameterName, this.parsedCommand);
+        return (Boolean) ObjectUtils.defaultIfNull(value, Boolean.FALSE);
+    }
+
+    public boolean isChangeInArrayParameterNamed(final String parameterName, final String[] existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final String[] workingValue = arrayValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public String[] arrayValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractArrayNamed(parameterName, this.parsedCommand);
+    }
+
+    public JsonArray arrayOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, this.parsedCommand);
+    }
+
+    public boolean isChangeInPasswordParameterNamed(final String parameterName, final String existingValue,
+            final PlatformPasswordEncoder platformPasswordEncoder, final Long saltValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final String workingValue = passwordValueOfParameterNamed(parameterName, platformPasswordEncoder, saltValue);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public String passwordValueOfParameterNamed(final String parameterName, final PlatformPasswordEncoder platformPasswordEncoder,
+            final Long saltValue) {
+        final String passwordPlainText = stringValueOfParameterNamed(parameterName);
+
+        final PlatformUser dummyPlatformUser = new BasicPasswordEncodablePlatformUser(saltValue, "", passwordPlainText);
+        return platformPasswordEncoder.encode(dummyPlatformUser);
+    }
+
+    public Locale extractLocale() {
+        return this.fromApiJsonHelper.extractLocaleParameter(this.parsedCommand.getAsJsonObject());
+    }
+
+    public void checkForUnsupportedParameters(final Type typeOfMap, final String json, final Set<String> requestDataParameters) {
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, requestDataParameters);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonQuery.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonQuery.java
new file mode 100644
index 0000000..e8580d7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonQuery.java
@@ -0,0 +1,279 @@
+/**
+ * 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.core.api;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.security.domain.BasicPasswordEncodablePlatformUser;
+import org.apache.fineract.infrastructure.security.domain.PlatformUser;
+import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder;
+import org.joda.time.LocalDate;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Immutable representation of a query.
+ * 
+ * Wraps the provided JSON with convenience functions for extracting parameter
+ * values.
+ */
+public final class JsonQuery {
+
+    private final String jsonQuery;
+    private final JsonElement parsedQuery;
+    private final FromJsonHelper fromApiJsonHelper;
+
+    public static JsonQuery from(final String jsonCommand, final JsonElement parsedQuery, final FromJsonHelper fromApiJsonHelper) {
+        return new JsonQuery(jsonCommand, parsedQuery, fromApiJsonHelper);
+    }
+
+    public JsonQuery(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper) {
+        this.jsonQuery = jsonCommand;
+        this.parsedQuery = parsedCommand;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public String json() {
+        return this.jsonQuery;
+    }
+
+    public JsonElement parsedJson() {
+        return this.parsedQuery;
+    }
+
+    private boolean differenceExists(final LocalDate baseValue, final LocalDate workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (baseValue != null) {
+            differenceExists = !baseValue.equals(workingCopyValue);
+        } else {
+            differenceExists = workingCopyValue != null;
+        }
+
+        return differenceExists;
+    }
+
+    private boolean differenceExists(final String baseValue, final String workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (StringUtils.isNotBlank(baseValue)) {
+            differenceExists = !baseValue.equals(workingCopyValue);
+        } else {
+            differenceExists = StringUtils.isNotBlank(workingCopyValue);
+        }
+
+        return differenceExists;
+    }
+
+    private boolean differenceExists(final String[] baseValue, final String[] workingCopyValue) {
+        Arrays.sort(baseValue);
+        Arrays.sort(workingCopyValue);
+        return !Arrays.equals(baseValue, workingCopyValue);
+    }
+
+    private boolean differenceExists(final Number baseValue, final Number workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (baseValue != null) {
+            differenceExists = !baseValue.equals(workingCopyValue);
+        } else {
+            differenceExists = workingCopyValue != null;
+        }
+
+        return differenceExists;
+    }
+
+    private boolean differenceExists(final BigDecimal baseValue, final BigDecimal workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (baseValue != null) {
+            differenceExists = baseValue.compareTo(workingCopyValue) != 0;
+        } else {
+            differenceExists = workingCopyValue != null;
+        }
+
+        return differenceExists;
+    }
+
+    private boolean differenceExists(final Boolean baseValue, final Boolean workingCopyValue) {
+        boolean differenceExists = false;
+
+        if (baseValue != null) {
+            differenceExists = !baseValue.equals(workingCopyValue);
+        } else {
+            differenceExists = workingCopyValue != null;
+        }
+
+        return differenceExists;
+    }
+
+    private boolean parameterExists(final String parameterName) {
+        return this.fromApiJsonHelper.parameterExists(parameterName, this.parsedQuery);
+    }
+
+    public boolean hasParameter(final String parameterName) {
+        return parameterExists(parameterName);
+    }
+
+    public String dateFormat() {
+        return stringValueOfParameterNamed("dateFormat");
+    }
+
+    public String locale() {
+        return stringValueOfParameterNamed("locale");
+    }
+
+    public Map<String, Boolean> mapValueOfParameterNamed(final String parameterName) {
+        final Type typeOfMap = new TypeToken<Map<String, Boolean>>() {}.getType();
+
+        if (this.parsedQuery.getAsJsonObject().has(parameterName)) {
+            this.parsedQuery.getAsJsonObject().get(parameterName);
+        }
+
+        return this.fromApiJsonHelper.extractMap(typeOfMap, this.jsonQuery);
+    }
+
+    public boolean isChangeInLongParameterNamed(final String parameterName, final Long existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Long workingValue = longValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public Long longValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractLongNamed(parameterName, this.parsedQuery);
+    }
+
+    public boolean isChangeInLocalDateParameterNamed(final String parameterName, final LocalDate existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final LocalDate workingValue = localDateValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public LocalDate localDateValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractLocalDateNamed(parameterName, this.parsedQuery);
+    }
+
+    public boolean isChangeInStringParameterNamed(final String parameterName, final String existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final String workingValue = stringValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public String stringValueOfParameterNamed(final String parameterName) {
+        final String value = this.fromApiJsonHelper.extractStringNamed(parameterName, this.parsedQuery);
+        return StringUtils.defaultIfEmpty(value, "");
+    }
+
+    public boolean isChangeInBigDecimalParameterNamed(final String parameterName, final BigDecimal existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final BigDecimal workingValue = bigDecimalValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public BigDecimal bigDecimalValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(parameterName, this.parsedQuery);
+    }
+
+    public boolean isChangeInIntegerParameterNamed(final String parameterName, final Integer existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Integer workingValue = integerValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public Integer integerValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractIntegerWithLocaleNamed(parameterName, this.parsedQuery);
+    }
+
+    public boolean isChangeInBooleanParameterNamed(final String parameterName, final Boolean existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final Boolean workingValue = booleanObjectValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    /**
+     * Returns {@link Boolean} that could possibly be null.
+     */
+    public Boolean booleanObjectValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractBooleanNamed(parameterName, this.parsedQuery);
+    }
+
+    /**
+     * always returns true or false
+     */
+    public boolean booleanPrimitiveValueOfParameterNamed(final String parameterName) {
+        final Boolean value = this.fromApiJsonHelper.extractBooleanNamed(parameterName, this.parsedQuery);
+        return (Boolean) ObjectUtils.defaultIfNull(value, Boolean.FALSE);
+    }
+
+    public boolean isChangeInArrayParameterNamed(final String parameterName, final String[] existingValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final String[] workingValue = arrayValueOfParameterNamed(parameterName);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public String[] arrayValueOfParameterNamed(final String parameterName) {
+        return this.fromApiJsonHelper.extractArrayNamed(parameterName, this.parsedQuery);
+    }
+
+    public boolean isChangeInPasswordParameterNamed(final String parameterName, final String existingValue,
+            final PlatformPasswordEncoder platformPasswordEncoder, final Long saltValue) {
+        boolean isChanged = false;
+        if (parameterExists(parameterName)) {
+            final String workingValue = passwordValueOfParameterNamed(parameterName, platformPasswordEncoder, saltValue);
+            isChanged = differenceExists(existingValue, workingValue);
+        }
+        return isChanged;
+    }
+
+    public String passwordValueOfParameterNamed(final String parameterName, final PlatformPasswordEncoder platformPasswordEncoder,
+            final Long saltValue) {
+        final String passwordPlainText = stringValueOfParameterNamed(parameterName);
+
+        final PlatformUser dummyPlatformUser = new BasicPasswordEncodablePlatformUser(saltValue, "", passwordPlainText);
+        return platformPasswordEncoder.encode(dummyPlatformUser);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ParameterListExclusionStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ParameterListExclusionStrategy.java
new file mode 100644
index 0000000..3db5edb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ParameterListExclusionStrategy.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.infrastructure.core.api;
+
+import java.util.Set;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+
+// FIXME - KW - General thing to fix about REST API is if use partial response approach of fields=id,name,selectedPermissions
+//        It will return just id,name parameters of RoleData and ignore description, however as PermissionData used in selectedPermissions collection
+//        also has a field called description it gets ignored also. This is because of the implementation of ParameterListExclusionStrategy which doesnt take
+//        into account the Object its looking at.
+public class ParameterListExclusionStrategy implements ExclusionStrategy {
+
+    private final Set<String> parameterNamesToSkip;
+
+    public ParameterListExclusionStrategy(final Set<String> parameterNamesToSkip) {
+        this.parameterNamesToSkip = parameterNamesToSkip;
+    }
+
+    @Override
+    public boolean shouldSkipField(final FieldAttributes f) {
+        return this.parameterNamesToSkip.contains(f.getName());
+    }
+
+    @SuppressWarnings("unused")
+    @Override
+    public boolean shouldSkipClass(final Class<?> clazz) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ParameterListInclusionStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ParameterListInclusionStrategy.java
new file mode 100644
index 0000000..7a8bcd6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/ParameterListInclusionStrategy.java
@@ -0,0 +1,44 @@
+/**
+ * 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.core.api;
+
+import java.util.Set;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+
+public class ParameterListInclusionStrategy implements ExclusionStrategy {
+
+    private final Set<String> parameterNamesToInclude;
+
+    public ParameterListInclusionStrategy(final Set<String> parameterNamesToSkip) {
+        this.parameterNamesToInclude = parameterNamesToSkip;
+    }
+
+    @Override
+    public boolean shouldSkipField(final FieldAttributes f) {
+        return !this.parameterNamesToInclude.contains(f.getName());
+    }
+
+    @SuppressWarnings("unused")
+    @Override
+    public boolean shouldSkipClass(final Class<?> clazz) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java
new file mode 100644
index 0000000..282eb33
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.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.infrastructure.core.boot;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.ImportResource;
+
+/**
+ * Base Spring Configuration with what's common to all Configuration subclasses.
+ *
+ * Notably the EnableAutoConfiguration excludes relevant for (and often adjusted
+ * when upgrading versions of) Spring Boot, the "old" (pre. Spring Boot &
+ * MariaDB4j) fineract appContext.xml which all configurations need, and the
+ * web.xml successor WebXmlConfiguration.
+ *
+ * Should NOT include Configuration related to embedded Tomcat, data sources,
+ * and MariaDB4j (because those differ in the subclasses).
+ */
+@Configuration
+@Import({ WebXmlConfiguration.class, WebXmlOauthConfiguration.class, WebFrontEndConfiguration.class })
+@ImportResource({ "classpath*:META-INF/spring/appContext.xml" })
+@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
+		HibernateJpaAutoConfiguration.class,
+		DataSourceTransactionManagerAutoConfiguration.class,
+		FlywayAutoConfiguration.class })
+public abstract class AbstractApplicationConfiguration {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/ApplicationExitUtil.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/ApplicationExitUtil.java
new file mode 100644
index 0000000..2dac008
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/ApplicationExitUtil.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.boot;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import org.springframework.context.ConfigurableApplicationContext;
+
+public abstract class ApplicationExitUtil {
+
+    private ApplicationExitUtil() {}
+
+    public static void waitForKeyPressToCleanlyExit(ConfigurableApplicationContext ctx) throws IOException {
+
+        // NOTE: In Eclipse, the Shutdown Hooks are not invoked on exit (red
+        // button).. In the case of MariaDB4j that's a problem because then the
+        // mysqld won't be stopped, so:
+        // (@see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38016)
+        System.out.println("\nHit Enter to quit...");
+        // NOTE: In Eclipse, System.console() is not available.. so:
+        // (@see https://bugs.eclipse.org/bugs/show_bug.cgi?id=122429)
+        BufferedReader d = new BufferedReader(new InputStreamReader(System.in));
+        d.readLine();
+
+        ctx.stop();
+        ctx.close();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/EmbeddedTomcatWithSSLConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/EmbeddedTomcatWithSSLConfiguration.java
new file mode 100644
index 0000000..0eeec99
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/EmbeddedTomcatWithSSLConfiguration.java
@@ -0,0 +1,102 @@
+package org.apache.fineract.infrastructure.core.boot;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.catalina.connector.Connector;
+import org.apache.commons.io.FileUtils;
+import org.apache.coyote.http11.Http11NioProtocol;
+import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
+import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+@Configuration
+public class EmbeddedTomcatWithSSLConfiguration {
+
+    // http://docs.spring.io/spring-boot/docs/1.1.5.RELEASE/reference/htmlsingle/#howto-enable-multiple-connectors-in-tomcat
+
+    @Bean
+    public EmbeddedServletContainerFactory servletContainer() {
+        TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
+        tomcat.setContextPath(getContextPath());
+        tomcat.addAdditionalTomcatConnectors(createSslConnector());
+        return tomcat;
+    }
+
+    private String getContextPath() {
+        return "/fineract-provider";
+    }
+
+    protected Connector createSslConnector() {
+        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
+        Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
+        try {
+            File keystore = getFile(getKeystore());
+            File truststore = keystore;
+            connector.setScheme("https");
+            connector.setSecure(true);
+            connector.setPort(getHTTPSPort());
+            protocol.setSSLEnabled(true);
+            protocol.setKeystoreFile(keystore.getAbsolutePath());
+            protocol.setKeystorePass(getKeystorePass());
+            protocol.setTruststoreFile(truststore.getAbsolutePath());
+            protocol.setTruststorePass(getKeystorePass());
+            // ? protocol.setKeyAlias("apitester");
+            return connector;
+        } catch (IOException ex) {
+            throw new IllegalStateException("can't access keystore: [" + "keystore" + "] or truststore: [" + "keystore" + "]", ex);
+        }
+    }
+
+    protected int getHTTPSPort() {
+        // TODO This shouldn't be hard-coded here, but configurable
+        return 8443;
+    }
+
+    protected String getKeystorePass() {
+        return "openmf";
+    }
+
+    protected Resource getKeystore() {
+        return new ClassPathResource("/keystore.jks");
+    }
+
+    public File getFile(Resource resource) throws IOException {
+        try {
+            return resource.getFile();
+        } catch (IOException e) {
+            // Uops.. OK, try again (below)
+        }
+
+        try {
+            URL url = resource.getURL();
+            /**
+             * // If this creates filenames that are too long on Win, // then
+             * could just use resource.getFilename(), // even though not unique,
+             * real risk prob. min.bon String tempDir =
+             * System.getProperty("java.io.tmpdir"); tempDir = tempDir + "/" +
+             * getClass().getSimpleName() + "/"; String path = url.getPath();
+             * String uniqName = path.replace("file:/", "").replace('!', '_');
+             * String tempFullPath = tempDir + uniqName;
+             **/
+            // instead of File.createTempFile(prefix?, suffix?);
+            File targetFile = new File(resource.getFilename());
+            long len = resource.contentLength();
+            if (!targetFile.exists() || targetFile.length() != len) { // Only
+                                                                      // copy
+                                                                      // new
+                                                                      // files
+                FileUtils.copyURLToFile(url, targetFile);
+            }
+            return targetFile;
+        } catch (IOException e) {
+            // Uops.. erm, give up:
+            throw new IOException("Cannot obtain a File for Resource: " + resource.toString(), e);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WarWebApplicationInitializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WarWebApplicationInitializer.java
new file mode 100644
index 0000000..907f827
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WarWebApplicationInitializer.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.boot;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.context.web.SpringBootServletInitializer;
+import org.springframework.context.annotation.ImportResource;
+
+/**
+ * Required to still have a working legacy "classic" WAR.
+ *
+ * Configuration just adds the JNDI-based DataSource lookup to its
+ * AbstractApplicationConfiguration.
+ *
+ * This (intentionally) only configures the original (pre-Spring Boot &
+ * MariaDB4j) fineract Spring Beans, and does NOT include the embedded Tomcat
+ * (incl. TomcatSSLConfiguration) nor the MariaDB4jSetupService or
+ * MariaDB4jDataSourceConfiguration, and not even the DataSourceConfiguration
+ * (as it uses "classic" JNDI) - we want the WAR to "work like before".
+ *
+ * @see <a
+ *      href="http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-convert-an-existing-application-to-spring-boot">#howto-convert-an-existing-application-to-spring-boot</a>
+ */
+public class WarWebApplicationInitializer extends SpringBootServletInitializer {
+
+	@ImportResource({ "classpath*:META-INF/spring/jndi.xml" })
+	private static class Configuration extends AbstractApplicationConfiguration {
+	}
+
+	@Override
+	protected SpringApplicationBuilder configure(
+			SpringApplicationBuilder application) {
+		// let's share Spring Boot Love, so no showBanner(false)
+		return application.sources(Configuration.class);
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java
new file mode 100644
index 0000000..1664b4b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java
@@ -0,0 +1,37 @@
+/**
+ * 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.core.boot;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+@EnableWebMvc
+@Configuration
+public class WebFrontEndConfiguration extends WebMvcConfigurerAdapter {
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/apps/**").addResourceLocations("file:" +
+                System.getProperty("user.dir") + System.getProperty("file.separator") +
+                "apps" + System.getProperty("file.separator"));
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlConfiguration.java
new file mode 100644
index 0000000..6a8f702
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlConfiguration.java
@@ -0,0 +1,80 @@
+/**
+ * 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.core.boot;
+
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+import org.apache.fineract.infrastructure.core.filters.ResponseCorsFilter;
+import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.embedded.FilterRegistrationBean;
+import org.springframework.boot.context.embedded.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.web.filter.DelegatingFilterProxy;
+
+import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
+
+/**
+ * This Configuration replaces what formerly was in web.xml.
+ *
+ * @see <a
+ *      href="http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-convert-an-existing-application-to-spring-boot">#howto-convert-an-existing-application-to-spring-boot</a>
+ */
+@Configuration
+@Profile("basicauth")
+public class WebXmlConfiguration {
+
+    @Autowired
+    private TenantAwareBasicAuthenticationFilter basicAuthenticationProcessingFilter;
+
+    @Bean
+    public Filter springSecurityFilterChain() {
+        return new DelegatingFilterProxy();
+    }
+
+    @Bean
+    public ServletRegistrationBean jersey() {
+        Servlet jerseyServlet = new SpringServlet();
+        ServletRegistrationBean jerseyServletRegistration = new ServletRegistrationBean();
+        jerseyServletRegistration.setServlet(jerseyServlet);
+        jerseyServletRegistration.addUrlMappings("/api/v1/*");
+        jerseyServletRegistration.setName("jersey-servlet");
+        jerseyServletRegistration.setLoadOnStartup(1);
+        jerseyServletRegistration.addInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
+        jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters",
+                ResponseCorsFilter.class.getName());
+        jerseyServletRegistration.addInitParameter("com.sun.jersey.config.feature.DisableWADL", "true");
+        // debugging for development:
+        // jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters",
+        // LoggingFilter.class.getName());
+        return jerseyServletRegistration;
+    }
+
+    @Bean
+    public FilterRegistrationBean filterRegistrationBean() {
+        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
+        filterRegistrationBean.setFilter(basicAuthenticationProcessingFilter);
+        filterRegistrationBean.setEnabled(false);
+        return filterRegistrationBean;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlOauthConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlOauthConfiguration.java
new file mode 100644
index 0000000..c5918b2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlOauthConfiguration.java
@@ -0,0 +1,74 @@
+/**
+ * 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.core.boot;
+
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+import org.springframework.boot.context.embedded.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.web.filter.DelegatingFilterProxy;
+import org.springframework.web.servlet.DispatcherServlet;
+
+import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
+
+/**
+ * This Configuration replaces what formerly was in web.xml. Beans are loaded only when "oauth" Profile is enabled. 
+ * 
+ * @see <a
+ *      href="http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-convert-an-existing-application-to-spring-boot">#howto-convert-an-existing-application-to-spring-boot</a>
+ */
+@Configuration
+@Profile("oauth")
+public class WebXmlOauthConfiguration {
+
+
+    @Bean
+    public Filter springSecurityFilterChain() {
+        return new DelegatingFilterProxy();
+    }
+
+    @Bean
+    public ServletRegistrationBean jersey() {
+        Servlet jerseyServlet = new SpringServlet();
+        ServletRegistrationBean jerseyServletRegistration = new ServletRegistrationBean();
+        jerseyServletRegistration.setServlet(jerseyServlet);
+        jerseyServletRegistration.addUrlMappings("/api/v1/*");
+        jerseyServletRegistration.setName("jersey-servlet");
+        jerseyServletRegistration.setLoadOnStartup(1);
+        jerseyServletRegistration.addInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
+//        jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters",
+//                ResponseCorsFilter.class.getName());
+        jerseyServletRegistration.addInitParameter("com.sun.jersey.config.feature.DisableWADL", "true");
+        // debugging for development:
+        // jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters",
+        // LoggingFilter.class.getName());
+        return jerseyServletRegistration;
+    }
+
+    @Bean
+    public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
+        ServletRegistrationBean registrationBean = new ServletRegistrationBean(dispatcherServlet);
+        registrationBean.addUrlMappings("/api/oauth/token");
+        return registrationBean;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/DataSourceConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/DataSourceConfiguration.java
new file mode 100644
index 0000000..df8dac4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/DataSourceConfiguration.java
@@ -0,0 +1,54 @@
+/**
+ * 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.core.boot.db;
+
+import javax.sql.DataSource;
+
+import org.apache.tomcat.jdbc.pool.PoolConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Configuration for a DataSource.
+ * @see DataSourceProperties about how to configure this DS
+ */
+@Configuration
+public class DataSourceConfiguration {
+	private static final Logger logger = LoggerFactory.getLogger(DataSourceConfiguration.class);
+
+    @Bean
+    public DataSourceProperties dataSourceProperties() {
+	return new DataSourceProperties();
+    }
+
+    @Bean
+    public DataSource tenantDataSourceJndi() {
+	PoolConfiguration p = getProperties();
+        org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource(p);
+        logger.info("Created new DataSource; url=" + p.getUrl());
+        return ds;
+    }
+
+    protected DataSourceProperties getProperties() {
+        return dataSourceProperties();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/DataSourceProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/DataSourceProperties.java
new file mode 100644
index 0000000..38fe6e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/DataSourceProperties.java
@@ -0,0 +1,159 @@
+/**
+ * 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.core.boot.db;
+
+import javax.validation.constraints.NotNull;
+
+import org.apache.tomcat.jdbc.pool.PoolProperties;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.util.StringUtils;
+
+/**
+ * Configurable DataSource. Properties have sensible defaults, but end-users can
+ * override those via the Spring Values listed below; i.e. via -D Java System
+ * properties, or main() command line arguments, OS environment variables, from
+ * JNDI, or application.properties (thanks Spring Boot). For example:
+ * -Dfineract.datasource.port=3307.
+ */
+// NOT a @Component - we do not want this to picked up by component scan, only explicitly declared in DataSourceConfiguration (if that's active)
+public class DataSourceProperties extends PoolProperties {
+
+    public final static String PORT = "fineract.datasource.port";
+    public final static String HOST = "fineract.datasource.host";
+    public final static String DB = "fineract.datasource.db";
+    public final static String UID = "fineract.datasource.username";
+    public final static String PWD = "fineract.datasource.password";
+    public final static String PROTOCOL = "fineract.datasource.protocol";
+    public final static String SUBPROTOCOL = "fineract.datasource.subprotocol";
+
+    @Value("${" + PORT + ":3306}")
+    private volatile @NotNull int port;
+
+    @Value("${" + HOST + ":localhost}")
+    private volatile @NotNull String hostname;
+
+    @Value("${" + DB + ":mifosplatform-tenants}")
+    private volatile @NotNull String dbName;
+
+    @Value("${" + UID + ":root}")
+    private volatile @NotNull String username;
+
+    @Value("${" + PWD + ":mysql}")
+    private volatile @NotNull String password;
+
+    @Value("${" + PROTOCOL + ":jdbc}")
+    private volatile @NotNull String jdbcProtocol;
+
+    @Value("${" + SUBPROTOCOL + ":mysql}")
+    private volatile @NotNull String jdbcSubprotocol;
+
+
+    public DataSourceProperties() {
+        super();
+
+        // default to save us from re-specifying this; note that it can still be
+        // overridden
+        setDriverClassName(com.mysql.jdbc.Driver.class.getName());
+
+        setDefaults();
+    }
+
+    /**
+     * as per (some of..) INSTALL.md and
+     * org.apache.fineract.infrastructure.core.service
+     * .TomcatJdbcDataSourcePerTenantService
+     * .createNewDataSourceFor(FineractPlatformTenant)
+     */
+    protected void setDefaults() {
+        setInitialSize(3);
+        // setMaxIdle(6); -- strange, why?
+        // setMinIdle(3); -- JavaDoc says default is initialSize.. so shouldn't
+        // be needed
+        if (getValidationQuery() == null) setValidationQuery("SELECT 1");
+        setTestOnBorrow(true);
+        setTestOnReturn(true);
+        setTestWhileIdle(true);
+        setTimeBetweenEvictionRunsMillis(30000);
+        setTimeBetweenEvictionRunsMillis(60000);
+        setLogAbandoned(true);
+        setSuspectTimeout(60);
+
+        setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"
+                + "org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer;org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport");
+    }
+
+    @Override
+    public void setUrl(@SuppressWarnings("unused") String url) {
+	throw new UnsupportedOperationException("Use setHost/Port/DB() instead of setURL()");
+    }
+
+	@Override
+	public String getUrl() {
+		String url = super.getUrl();
+		if (StringUtils.hasText(url)) {
+			throw new IllegalStateException();
+		}
+		return jdbcProtocol + ":" + jdbcSubprotocol + "://" + getHost() + ":" + getPort() + "/" + getDBName();
+	}
+
+	public String getHost() {
+		return hostname;
+	}
+
+	public int getPort() {
+		return port;
+	}
+
+	public String getDBName() {
+		return dbName;
+	}
+
+	public void setPort(int port) {
+		this.port = port;
+	}
+
+	public void setHost(String hostname) {
+		this.hostname = hostname;
+	}
+
+	public void setDBName(String dbName) {
+		this.dbName = dbName;
+	}
+
+	@Override
+	public String getUsername() {
+		return this.username;
+	}
+
+	@Override
+	public void setUsername(String username) {
+		this.username = username;
+	}
+
+	@Override
+	public String getPassword() {
+		return this.password;
+	}
+
+	@Override
+	public void setPassword(String password) {
+		this.password = password;
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jDataSourceConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jDataSourceConfiguration.java
new file mode 100644
index 0000000..08971d2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jDataSourceConfiguration.java
@@ -0,0 +1,44 @@
+package org.apache.fineract.infrastructure.core.boot.db;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import ch.vorburger.mariadb4j.springframework.MariaDB4jSpringService;
+
+@Configuration
+public class MariaDB4jDataSourceConfiguration extends DataSourceConfiguration {
+
+	@Bean
+	public MariaDB4jSetupService mariaDB4jSetUp() {
+		return new MariaDB4jSetupService(mariaDB4j().getDB());
+	}
+
+	@Bean
+	public MariaDB4jSpringService mariaDB4j() {
+		MariaDB4jSpringService mariaDB4jSpringService = new MariaDB4jSpringService();
+		mariaDB4jSpringService.setDefaultBaseDir("build/db/bin");
+		mariaDB4jSpringService.setDefaultDataDir("build/db/data");
+		return mariaDB4jSpringService;
+	}
+
+	@Override
+    // NOT @Bean @Override dataSourceProperties() - doesn't work :(
+	protected DataSourceProperties getProperties() {
+		DataSourceProperties p = super.getProperties();
+		String dbName = mariaDB4jSetUp().getTenantDBName();
+		// Do not use p.setUrl(mariaDB4j().getConfiguration().getURL(dbName));
+		// Because TenantDataSourcePortFixService needs separate
+		// host/port/db/uid/pwd:
+		// (DataSourceProperties getUrl() creates the correct JDBC URL from it)
+		// This intentionally overrides any fineract.datasource.* settings, because
+		// in this configuration, logically the mariaDB4j settings take
+		// precedence:
+		p.setHost("localhost");
+		p.setPort(mariaDB4j().getConfiguration().getPort());
+		p.setDBName(dbName);
+		// TODO p.setUsername(mariaDB4j().getConfiguration().getUsername());
+		// TODO p.setPassword(mariaDB4j().getConfiguration().getPassword());
+		return p;
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jSetupService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jSetupService.java
new file mode 100644
index 0000000..b29579f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jSetupService.java
@@ -0,0 +1,36 @@
+package org.apache.fineract.infrastructure.core.boot.db;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import ch.vorburger.exec.ManagedProcessException;
+import ch.vorburger.mariadb4j.DB;
+
+public class MariaDB4jSetupService {
+
+    private DB db;
+
+    @Autowired
+    public MariaDB4jSetupService(DB db) {
+        this.db = db;
+    }
+
+    @PostConstruct
+    protected void setUpDBs() throws ManagedProcessException {
+        db.createDB(getTenantDBName());
+        db.createDB("mifostenant-default");
+        // Note that we don't need to initialize the DBs, because
+        // the TenantDatabaseUpgradeService will do this in just a moment.
+    }
+
+    public String getTenantDBName() {
+        return "mifosplatform-tenants";
+    }
+
+    @PreDestroy
+    protected void stop() throws ManagedProcessException {
+        db = null;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/TenantDataSourcePortFixService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/TenantDataSourcePortFixService.java
new file mode 100644
index 0000000..5ca3dfe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/TenantDataSourcePortFixService.java
@@ -0,0 +1,96 @@
+/**
+ * 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.core.boot.db;
+
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+/**
+ * Service which "fixes up" the schemaServerPort in the tenants table of the
+ * mifosplatform-tenants schema. It sets it to the actual current port of
+ * MariaDB4j, instead of the default 3306 which is hard-coded in the initial
+ * set-up SQL script (V1__mifos-platform-shared-tenants.sql).
+ *
+ * This service is called by the TenantDatabaseUpgradeService
+ * "just at the right time" during start-up, that is AFTER the initialization of
+ * the mifosplatform-tenants but BEFORE Flyway runs on all tenant schemas.
+ *
+ * It's difficult to achieve the same goal elsewhere, because we cannot do this
+ * e.g. in MariaDB4jSetupService, as that's too early
+ * (TenantDatabaseUpgradeService has not yet run), nor e.g. in
+ * TomcatJdbcDataSourcePerTenantService because in there we do not have access
+ * to and should not depend on MariaDB4j configuration.
+ */
+@Service
+public class TenantDataSourcePortFixService {
+	private static final Logger logger = LoggerFactory.getLogger(TenantDataSourcePortFixService.class);
+
+	/**
+	 * While what this class does is convenient in 96% you may would like to
+	 * completely disable it if you've hand tuned your tenants table for an
+	 * advanced configuration and use e.g. different databases maybe on
+	 * different hosts for different tenants.
+	 */
+    public final static String ENABLED = "fineract.tenantdb.fixup";
+    @Value("${" + ENABLED + ":true}")
+    private boolean enabled;
+
+	// required=false is important here, because in
+	// WebApplicationInitializerConfiguration for classic WAR there
+	// is (intentionally) no MariaDB4j nor a DataSourceProperties
+	// bean (because in the WAR we're using a DS from JNDI)
+	private @Autowired(required = false) DataSourceProperties dsp;
+
+	private JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public TenantDataSourcePortFixService(@Qualifier("tenantDataSourceJndi") final DataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    public void fixUpTenantsSchemaServerPort() {
+	if (!enabled)  {
+		logger.info("No schema_server_port UPDATE made to tenant_server_connections table of the mifosplatform-tenants schema, because " + ENABLED + " = false");
+		return;
+	}
+	if (dsp == null) {
+		// we don't have any generic mechanism to know the DB port, given just a tenant DataSource
+		logger.debug("No schema_server_port UPDATE made to tenant_server_connections table of the mifosplatform-tenants schema (because neither MariaDB4j nor our own Spring Boot DataSourceConfiguration is used in a traditional WAR)");
+		return;
+	}
+		int r = jdbcTemplate
+				.update("UPDATE tenant_server_connections SET schema_server = ?, schema_server_port = ?, schema_username = ?, schema_password = ?",
+						dsp.getHost(), dsp.getPort(), dsp.getUsername(), dsp.getPassword());
+	if ( r == 0 )
+		logger.warn("UPDATE tenant_server_connections SET ... did not update ANY rows - something is probably wrong");
+	else
+			logger.info("Upated "
+					+ r
+					+ " rows in the tenant_server_connections table of the mifosplatform-tenants schema to the real current host: "
+					+ dsp.getHost() + ", port: " + dsp.getPort());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiErrorMessageArg.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiErrorMessageArg.java
new file mode 100644
index 0000000..ff37416
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiErrorMessageArg.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.data;
+
+public class ApiErrorMessageArg {
+
+    /**
+     * The actual value of the parameter (if any) as passed to API.
+     */
+    private Object value;
+
+    public static ApiErrorMessageArg from(final Object object) {
+        return new ApiErrorMessageArg(object);
+    }
+
+    protected ApiErrorMessageArg() {
+        //
+    }
+
+    public ApiErrorMessageArg(final Object object) {
+        this.value = object;
+    }
+
+    public Object getValue() {
+        return this.value;
+    }
+
+    public void setValue(final Object value) {
+        this.value = value;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java
new file mode 100644
index 0000000..6455259
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java
@@ -0,0 +1,242 @@
+/**
+ * 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.core.data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ *
+ */
+public class ApiGlobalErrorResponse {
+
+    /**
+     * A developer friendly plain English description of why the HTTP error
+     * response was returned from the API.
+     */
+    private String developerMessage;
+
+    /**
+     * The HTTP status code of the response.
+     */
+    private String httpStatusCode;
+
+    /**
+     * A user friendly plain English description of why the HTTP error response
+     * was returned from the API that can be presented to end users.
+     */
+    private String defaultUserMessage;
+
+    /**
+     * A code that can be used for globalisation support by client applications
+     * of the API.
+     */
+    private String userMessageGlobalisationCode;
+
+    /**
+     * A list of zero or more of the actual reasons for the HTTP error should
+     * they be needed. Typically used to express data validation errors with
+     * parameters passed to API.
+     */
+    private List<ApiParameterError> errors = new ArrayList<>();
+
+    public static ApiGlobalErrorResponse unAuthenticated() {
+
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode("401");
+        globalErrorResponse.setDeveloperMessage("Invalid authentication details were passed in api request.");
+        globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authenticated");
+        globalErrorResponse.setDefaultUserMessage("Unauthenticated. Please login.");
+
+        return globalErrorResponse;
+    }
+
+    public static ApiGlobalErrorResponse invalidTenantIdentifier() {
+
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode("401");
+        globalErrorResponse.setDeveloperMessage("Invalid tenant details were passed in api request.");
+        globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.tenant.identifier");
+        globalErrorResponse.setDefaultUserMessage("Invalide tenant identifier provided with request.");
+
+        return globalErrorResponse;
+    }
+
+    public static ApiGlobalErrorResponse unAuthorized(final String defaultUserMessage) {
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode("403");
+        globalErrorResponse
+                .setDeveloperMessage("The user associated with credentials passed on this request does not have sufficient privileges to perform this action.");
+        globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authorized");
+        globalErrorResponse.setDefaultUserMessage("Insufficient privileges to perform this action.");
+
+        final List<ApiParameterError> errors = new ArrayList<>();
+        errors.add(ApiParameterError.generalError("error.msg.not.authorized", defaultUserMessage));
+        globalErrorResponse.setErrors(errors);
+
+        return globalErrorResponse;
+    }
+
+    public static ApiGlobalErrorResponse domainRuleViolation(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode("403");
+        globalErrorResponse.setDeveloperMessage("Request was understood but caused a domain rule violation.");
+        globalErrorResponse.setUserMessageGlobalisationCode("validation.msg.domain.rule.violation");
+        globalErrorResponse.setDefaultUserMessage("Errors contain reason for domain rule violation.");
+
+        final List<ApiParameterError> errors = new ArrayList<>();
+        errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
+        globalErrorResponse.setErrors(errors);
+
+        return globalErrorResponse;
+    }
+
+    public static ApiGlobalErrorResponse notFound(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode("404");
+        globalErrorResponse.setDeveloperMessage("The requested resource is not available.");
+        globalErrorResponse.setUserMessageGlobalisationCode("error.msg.resource.not.found");
+        globalErrorResponse.setDefaultUserMessage("The requested resource is not available.");
+
+        final List<ApiParameterError> errors = new ArrayList<>();
+        errors.add(ApiParameterError.resourceIdentifierNotFound(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
+        globalErrorResponse.setErrors(errors);
+
+        return globalErrorResponse;
+    }
+
+    public static ApiGlobalErrorResponse dataIntegrityError(final String globalisationMessageCode, final String defaultUserMessage,
+            final String parameterName, final Object... defaultUserMessageArgs) {
+
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode("403");
+        globalErrorResponse.setDeveloperMessage("The request caused a data integrity issue to be fired by the database.");
+        globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
+        globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
+
+        final List<ApiParameterError> errors = new ArrayList<>();
+        errors.add(ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, parameterName, defaultUserMessageArgs));
+        globalErrorResponse.setErrors(errors);
+
+        return globalErrorResponse;
+    }
+
+    public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage,
+            final List<ApiParameterError> errors) {
+
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode("400");
+        globalErrorResponse
+                .setDeveloperMessage("The request was invalid. This typically will happen due to validation errors which are provided.");
+        globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
+        globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
+
+        globalErrorResponse.setErrors(errors);
+
+        return globalErrorResponse;
+    }
+
+    public static ApiGlobalErrorResponse serverSideError(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode("500");
+        globalErrorResponse.setDeveloperMessage("An unexpected error occured on the platform server.");
+        globalErrorResponse.setUserMessageGlobalisationCode("error.msg.platform.server.side.error");
+        globalErrorResponse.setDefaultUserMessage("An unexpected error occured on the platform server.");
+
+        final List<ApiParameterError> errors = new ArrayList<>();
+        errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
+        globalErrorResponse.setErrors(errors);
+
+        return globalErrorResponse;
+    }
+
+    public static ApiGlobalErrorResponse serviceUnavailable(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode("503");
+        globalErrorResponse.setDeveloperMessage("The server is currently unable to handle the request , please try after some time.");
+        globalErrorResponse.setUserMessageGlobalisationCode("error.msg.platform.service.unavailable");
+        globalErrorResponse.setDefaultUserMessage("The server is currently unable to handle the request , please try after some time.");
+
+        final List<ApiParameterError> errors = new ArrayList<>();
+        errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
+        globalErrorResponse.setErrors(errors);
+
+        return globalErrorResponse;
+    }
+
+    protected ApiGlobalErrorResponse() {
+        //
+    }
+
+    public ApiGlobalErrorResponse(final List<ApiParameterError> errors) {
+        this.errors = errors;
+    }
+
+    @XmlElementWrapper(name = "errors")
+    @XmlElement(name = "errorResponse")
+    public List<ApiParameterError> getErrors() {
+        return this.errors;
+    }
+
+    public void setErrors(final List<ApiParameterError> errors) {
+        this.errors = errors;
+    }
+
+    public String getDeveloperMessage() {
+        return this.developerMessage;
+    }
+
+    public void setDeveloperMessage(final String developerMessage) {
+        this.developerMessage = developerMessage;
+    }
+
+    public String getHttpStatusCode() {
+        return this.httpStatusCode;
+    }
+
+    public void setHttpStatusCode(final String httpStatusCode) {
+        this.httpStatusCode = httpStatusCode;
+    }
+
+    public String getDefaultUserMessage() {
+        return this.defaultUserMessage;
+    }
+
+    public void setDefaultUserMessage(final String defaultUserMessage) {
+        this.defaultUserMessage = defaultUserMessage;
+    }
+
+    public String getUserMessageGlobalisationCode() {
+        return this.userMessageGlobalisationCode;
+    }
+
+    public void setUserMessageGlobalisationCode(final String userMessageGlobalisationCode) {
+        this.userMessageGlobalisationCode = userMessageGlobalisationCode;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiParameterError.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiParameterError.java
new file mode 100644
index 0000000..9a669a0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiParameterError.java
@@ -0,0 +1,144 @@
+/**
+ * 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.core.data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ApiParameterError {
+
+    /**
+     * A developer friendly plain English description of why the HTTP error
+     * response was returned from the API.
+     */
+    private String developerMessage;
+
+    /**
+     * A user friendly plain English description of why the HTTP error response
+     * was returned from the API that can be presented to end users.
+     */
+    private String defaultUserMessage;
+
+    /**
+     * A code that can be used for globalisation support by client applications
+     * of the API.
+     */
+    private String userMessageGlobalisationCode;
+
+    /**
+     * The name of the field or parameter passed to the API that this error
+     * relates to.
+     */
+    private String parameterName;
+
+    /**
+     * The actual value of the parameter (if any) as passed to API.
+     */
+    private Object value;
+
+    /**
+     * Arguments related to the user error message.
+     */
+    private List<ApiErrorMessageArg> args = new ArrayList<>();
+
+    public static ApiParameterError generalError(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        return new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+    public static ApiParameterError resourceIdentifierNotFound(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        return new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+    public static ApiParameterError parameterError(final String globalisationMessageCode, final String defaultUserMessage,
+            final String parameterName, final Object... defaultUserMessageArgs) {
+        final ApiParameterError error = new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+        error.setParameterName(parameterName);
+        return error;
+    }
+
+    protected ApiParameterError() {
+        //
+    }
+
+    private ApiParameterError(final String globalisationMessageCode, final String defaultUserMessage, final Object[] defaultUserMessageArgs) {
+        this.userMessageGlobalisationCode = globalisationMessageCode;
+        this.developerMessage = defaultUserMessage;
+        this.defaultUserMessage = defaultUserMessage;
+
+        final List<ApiErrorMessageArg> messageArgs = new ArrayList<>();
+        if (defaultUserMessageArgs != null) {
+            for (final Object object : defaultUserMessageArgs) {
+                messageArgs.add(ApiErrorMessageArg.from(object));
+            }
+        }
+        this.args = messageArgs;
+
+        this.parameterName = "id";
+    }
+
+    public String getDeveloperMessage() {
+        return this.developerMessage;
+    }
+
+    public void setDeveloperMessage(final String developerMessage) {
+        this.developerMessage = developerMessage;
+    }
+
+    public String getDefaultUserMessage() {
+        return this.defaultUserMessage;
+    }
+
+    public void setDefaultUserMessage(final String defaultUserMessage) {
+        this.defaultUserMessage = defaultUserMessage;
+    }
+
+    public String getUserMessageGlobalisationCode() {
+        return this.userMessageGlobalisationCode;
+    }
+
+    public void setUserMessageGlobalisationCode(final String userMessageGlobalisationCode) {
+        this.userMessageGlobalisationCode = userMessageGlobalisationCode;
+    }
+
+    public String getParameterName() {
+        return this.parameterName;
+    }
+
+    public void setParameterName(final String parameterName) {
+        this.parameterName = parameterName;
+    }
+
+    public Object getValue() {
+        return this.value;
+    }
+
+    public void setValue(final Object value) {
+        this.value = value;
+    }
+
+    public List<ApiErrorMessageArg> getArgs() {
+        return this.args;
+    }
+
+    public void setArgs(final List<ApiErrorMessageArg> args) {
+        this.args = args;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java
new file mode 100644
index 0000000..d2754c2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java
@@ -0,0 +1,204 @@
+/**
+ * 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.core.data;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents the successful result of an REST API call that results in
+ * processing a command.
+ */
+public class CommandProcessingResult implements Serializable {
+
+    private Long commandId;
+    private Long officeId;
+    private final Long groupId;
+    private final Long clientId;
+    private final Long loanId;
+    private final Long savingsId;
+    private final Long resourceId;
+    private final Long subResourceId;
+    private final String transactionId;
+    private final Map<String, Object> changes;
+    @SuppressWarnings("unused")
+    private final String resourceIdentifier;
+    private final Long productId;
+    private Boolean rollbackTransaction;
+
+    public static CommandProcessingResult fromDetails(final Long commandId, final Long officeId, final Long groupId, final Long clientId,
+            final Long loanId, final Long savingsId, final String resourceIdentifier, final Long entityId, final String transactionId,
+            final Map<String, Object> changes, final Long productId, final Boolean rollbackTransaction, final Long subResourceId) {
+        return new CommandProcessingResult(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId,
+                transactionId, changes, productId, rollbackTransaction, subResourceId);
+    }
+
+    public static CommandProcessingResult commandOnlyResult(final Long commandId) {
+        return new CommandProcessingResult(null, null, commandId, null);
+    }
+
+    public static CommandProcessingResult resourceResult(final Long resourceId, final Long commandId) {
+        return new CommandProcessingResult(resourceId, null, commandId, null);
+    }
+
+    public static CommandProcessingResult resourceResult(final Long resourceId, final Long commandId, final Map<String, Object> changes) {
+        return new CommandProcessingResult(resourceId, null, commandId, changes);
+    }
+
+    public static CommandProcessingResult subResourceResult(final Long resourceId, final Long subResourceId, final Long commandId) {
+        return new CommandProcessingResult(resourceId, subResourceId, commandId, null);
+    }
+
+    public static CommandProcessingResult subResourceResult(final Long resourceId, final Long subResourceId, final Long commandId,
+            final Map<String, Object> changes) {
+        return new CommandProcessingResult(resourceId, subResourceId, commandId, changes);
+    }
+
+    public static CommandProcessingResult withChanges(final Long resourceId, final Map<String, Object> changes) {
+        return new CommandProcessingResult(resourceId, null, null, changes);
+    }
+
+    public static CommandProcessingResult empty() {
+        return new CommandProcessingResult(null, null, null, null);
+    }
+
+    /*
+     * Deprecated
+     */
+    public CommandProcessingResult(final Long entityId) {
+        if (entityId != null) {
+            this.resourceIdentifier = entityId.toString();
+        } else {
+            this.resourceIdentifier = null;
+        }
+        this.resourceId = entityId;
+        this.officeId = null;
+        this.groupId = null;
+        this.clientId = null;
+        this.loanId = null;
+        this.savingsId = null;
+        this.transactionId = null;
+        this.changes = new HashMap<>();
+        this.productId = null;
+        this.subResourceId = null;
+    }
+
+    private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId,
+            final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId,
+            final Map<String, Object> changesOnly, final Long productId, Boolean rollbackTransaction, final Long subResourceId) {
+        this.commandId = commandId;
+        this.officeId = officeId;
+        this.groupId = groupId;
+        this.clientId = clientId;
+        this.loanId = loanId;
+        this.savingsId = savingsId;
+        this.resourceIdentifier = resourceIdentifier;
+        this.resourceId = resourceId;
+        this.changes = changesOnly;
+        this.transactionId = transactionId;
+        this.productId = productId;
+        this.rollbackTransaction = rollbackTransaction;
+        this.subResourceId = subResourceId;
+    }
+
+    private CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map<String, Object> changesOnly) {
+        if (resourceId != null) {
+            this.resourceIdentifier = resourceId.toString();
+        } else {
+            this.resourceIdentifier = null;
+        }
+        this.resourceId = resourceId;
+        this.officeId = officeId;
+        this.groupId = null;
+        this.clientId = null;
+        this.loanId = null;
+        this.savingsId = null;
+        this.transactionId = null;
+        this.commandId = commandId;
+        this.changes = changesOnly;
+        this.productId = null;
+        this.subResourceId = null;
+    }
+
+    public Long commandId() {
+        return this.commandId;
+    }
+
+    public Long resourceId() {
+        return this.resourceId;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public void setOfficeId(final Long officeId) {
+        this.officeId = officeId;
+    }
+
+    public Long getGroupId() {
+        return this.groupId;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public Long getSavingsId() {
+        return this.savingsId;
+    }
+
+    public String getTransactionId() {
+        return this.transactionId;
+    }
+
+    public Map<String, Object> getChanges() {
+        Map<String, Object> checkIfEmpty = null;
+        if (this.changes != null && !this.changes.isEmpty()) {
+            checkIfEmpty = this.changes;
+        }
+        return checkIfEmpty;
+    }
+
+    public boolean hasChanges() {
+        final boolean noChanges = this.changes == null || this.changes.isEmpty();
+        return !noChanges;
+    }
+
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    public boolean isRollbackTransaction() {
+        return this.rollbackTransaction != null && this.rollbackTransaction;
+    }
+
+    public void setRollbackTransaction(Boolean rollbackTransaction) {
+        this.rollbackTransaction = rollbackTransaction;
+    }
+
+    public Long getSubResourceId() {
+        return subResourceId;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java
new file mode 100644
index 0000000..147df29
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.data;
+
+import java.util.Map;
+
+/**
+ * Represents the successful result of an REST API call that results in
+ * processing a command.
+ */
+public class CommandProcessingResultBuilder {
+
+    private Long commandId;
+    private Long officeId;
+    private Long groupId;
+    private Long clientId;
+    private Long loanId;
+    private Long savingsId;
+    private String resourceIdentifier;
+    private Long entityId;
+    private Long subEntityId;
+    private String transactionId;
+    private Map<String, Object> changes;
+    private Long productId;
+    private boolean rollbackTransaction = false;
+
+    public CommandProcessingResult build() {
+        return CommandProcessingResult.fromDetails(this.commandId, this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId,
+                this.resourceIdentifier, this.entityId, this.transactionId, this.changes, this.productId, this.rollbackTransaction,
+                this.subEntityId);
+    }
+
+    public CommandProcessingResultBuilder withCommandId(final Long withCommandId) {
+        this.commandId = withCommandId;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder with(final Map<String, Object> withChanges) {
+        this.changes = withChanges;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder withResourceIdAsString(final String withResourceIdentifier) {
+        this.resourceIdentifier = withResourceIdentifier;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder withEntityId(final Long withEntityId) {
+        this.entityId = withEntityId;
+        return this;
+    }
+    
+    public CommandProcessingResultBuilder withSubEntityId(final Long withSubEntityId) {
+        this.subEntityId = withSubEntityId;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder withOfficeId(final Long withOfficeId) {
+        this.officeId = withOfficeId;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder withClientId(final Long withClientId) {
+        this.clientId = withClientId;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder withGroupId(final Long withGroupId) {
+        this.groupId = withGroupId;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder withLoanId(final Long withLoanId) {
+        this.loanId = withLoanId;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder withSavingsId(final Long withSavingsId) {
+        this.savingsId = withSavingsId;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder withTransactionId(final String withTransactionId) {
+        this.transactionId = withTransactionId;
+        return this;
+    }
+
+    public CommandProcessingResultBuilder withProductId(final Long productId) {
+        this.productId = productId;
+        return this;
+    }
+    
+    public CommandProcessingResultBuilder setRollbackTransaction(final boolean rollbackTransaction) {
+        this.rollbackTransaction = this.rollbackTransaction || rollbackTransaction;
+        return this;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
new file mode 100644
index 0000000..0d819fe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
@@ -0,0 +1,969 @@
+/**
+ * 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.core.data;
+
+import com.google.gson.JsonArray;
+import net.fortuna.ical4j.model.ValidationException;
+import net.fortuna.ical4j.model.property.RRule;
+import org.apache.commons.lang.StringUtils;
+import org.joda.time.LocalDate;
+import org.quartz.CronExpression;
+import org.springframework.util.ObjectUtils;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DataValidatorBuilder {
+
+    public static final String VALID_INPUT_SEPERATOR = "_";
+    private final List<ApiParameterError> dataValidationErrors;
+    private String resource;
+    private String parameter;
+    private String arrayPart;
+    private Integer arrayIndex;
+    private Object value;
+    private boolean ignoreNullValue = false;
+
+    public DataValidatorBuilder(final List<ApiParameterError> dataValidationErrors) {
+        this.dataValidationErrors = dataValidationErrors;
+    }
+
+    public DataValidatorBuilder reset() {
+        return new DataValidatorBuilder(this.dataValidationErrors).resource(this.resource);
+    }
+
+    public DataValidatorBuilder resource(final String resource) {
+        this.resource = resource;
+        return this;
+    }
+
+    public DataValidatorBuilder parameter(final String parameter) {
+        this.parameter = parameter;
+        return this;
+    }
+
+    public DataValidatorBuilder parameterAtIndexArray(final String arrayPart, final Integer arrayIndex) {
+        this.arrayPart = arrayPart;
+        this.arrayIndex = arrayIndex;
+        return this;
+    }
+
+    public DataValidatorBuilder value(final Object value) {
+        this.value = value;
+        return this;
+    }
+
+    public DataValidatorBuilder ignoreIfNull() {
+        this.ignoreNullValue = true;
+        return this;
+    }
+
+    public DataValidatorBuilder andNotBlank(final String linkedParameterName, final String linkedValue) {
+        if (this.value == null && linkedValue == null && this.ignoreNullValue) { return this; }
+
+        if (StringUtils.isBlank(linkedValue)) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(linkedParameterName).append(".cannot.be.empty.when.").append(this.parameter).append(".is.populated");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(linkedParameterName)
+                    .append(" cannot be empty when ").append(this.parameter).append(" is populated.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), linkedParameterName, linkedValue, this.value);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public void failWithCode(final String errorCode, final Object... defaultUserMessageArgs) {
+        final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                .append(this.parameter).append(".").append(errorCode);
+        final StringBuilder defaultEnglishMessage = new StringBuilder("Failed data validation due to: ").append(errorCode).append(".");
+        final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
+                this.parameter, this.value, defaultUserMessageArgs);
+        this.dataValidationErrors.add(error);
+    }
+
+    public void failWithCodeNoParameterAddedToErrorCode(final String errorCode, final Object... defaultUserMessageArgs) {
+        final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".").append(errorCode);
+        final StringBuilder defaultEnglishMessage = new StringBuilder("Failed data validation due to: ").append(errorCode).append(".");
+        final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
+                this.parameter, this.value, defaultUserMessageArgs);
+        this.dataValidationErrors.add(error);
+    }
+
+    public DataValidatorBuilder equalToParameter(final String linkedParameterName, final Object linkedValue) {
+        if (this.value == null && linkedValue == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && !this.value.equals(linkedValue)) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(linkedParameterName).append(".not.equal.to.").append(this.parameter);
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(linkedParameterName)
+                    .append(" is not equal to ").append(this.parameter).append(".");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), linkedParameterName, linkedValue, this.value);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder notSameAsParameter(final String linkedParameterName, final Object linkedValue) {
+        if (this.value == null && linkedValue == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && this.value.equals(linkedValue)) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(linkedParameterName).append(".same.as.").append(this.parameter);
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(linkedParameterName)
+                    .append(" is same as ").append(this.parameter).append(".");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), linkedParameterName, linkedValue, this.value);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    /*** FIXME: Vishwas, why does this method have a parameter? Seems wrong ***/
+    /*
+     * This method is not meant for validation, if you have mandatory boolean
+     * param and if it has invalid value or value not passed then call this
+     * method, this method is always used with input as false
+     */
+    public DataValidatorBuilder trueOrFalseRequired(final boolean trueOfFalseFieldProvided) {
+        if (!trueOfFalseFieldProvided && !this.ignoreNullValue) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".must.be.true.or.false");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                    " must be set as true or false.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder notNull() {
+        if (this.value == null && !this.ignoreNullValue) {
+
+            String realParameterName = this.parameter;
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter);
+            if (this.arrayIndex != null && StringUtils.isNotBlank(this.arrayPart)) {
+                validationErrorCode.append(".").append(this.arrayPart);
+                realParameterName = new StringBuilder(this.parameter).append('[').append(this.arrayIndex).append("][")
+                        .append(this.arrayPart).append(']').toString();
+            }
+
+            validationErrorCode.append(".cannot.be.blank");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(realParameterName).append(
+                    " is mandatory.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), realParameterName, this.arrayIndex);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder notBlank() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value == null || StringUtils.isBlank(this.value.toString())) {
+            String realParameterName = this.parameter;
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter);
+            if (this.arrayIndex != null && StringUtils.isNotBlank(this.arrayPart)) {
+                validationErrorCode.append(".").append(this.arrayPart);
+                realParameterName = new StringBuilder(this.parameter).append('[').append(this.arrayIndex).append("][")
+                        .append(this.arrayPart).append(']').toString();
+            }
+
+            validationErrorCode.append(".cannot.be.blank");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(realParameterName).append(
+                    " is mandatory.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), realParameterName, this.arrayIndex);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder notExceedingLengthOf(final Integer maxLength) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && this.value.toString().trim().length() > maxLength) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".exceeds.max.length");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                    .append(" exceeds max length of ").append(maxLength).append(".");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter, maxLength, this.value.toString());
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder inMinMaxRange(final Integer min, final Integer max) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Integer number = Integer.valueOf(this.value.toString());
+            if (number < min || number > max) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.not.within.expected.range");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                        .append(" must be between ").append(min).append(" and ").append(max).append(".");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, number, min, max);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder isOneOfTheseValues(final Object... values) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        final List<Object> valuesList = Arrays.asList(values);
+        final String valuesListStr = StringUtils.join(valuesList, ", ");
+
+        if (this.value == null || !valuesList.contains(this.value)) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".is.not.one.of.expected.enumerations");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                    .append(" must be one of [ ").append(valuesListStr).append(" ] ").append(".");
+
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter, this.value, values);
+
+            this.dataValidationErrors.add(error);
+        }
+
+        return this;
+    }
+
+    public DataValidatorBuilder isOneOfTheseStringValues(final Object... values) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        final List<Object> valuesList = Arrays.asList(values);
+        final String valuesListStr = StringUtils.join(valuesList, ", ");
+
+        if (this.value == null || !valuesList.contains(this.value.toString().toLowerCase())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".is.not.one.of.expected.enumerations");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                    .append(" must be one of [ ").append(valuesListStr).append(" ] ").append(".");
+
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter, this.value, values);
+
+            this.dataValidationErrors.add(error);
+        }
+
+        return this;
+    }
+
+    public DataValidatorBuilder isNotOneOfTheseValues(final Object... values) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final List<Object> valuesList = Arrays.asList(values);
+            final String valuesListStr = StringUtils.join(valuesList, ", ");
+
+            if (valuesList.contains(this.value)) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.one.of.unwanted.enumerations");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                        .append(" must not be any of [ ").append(valuesListStr).append(" ] ").append(".");
+
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, this.value, values);
+
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder positiveAmount() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final BigDecimal number = BigDecimal.valueOf(Double.valueOf(this.value.toString()));
+            if (number.compareTo(BigDecimal.ZERO) <= 0) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.greater.than.zero");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                        " must be greater than 0.");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, number, 0);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    /*
+     * should be used with .notNull() before it
+     */
+    public DataValidatorBuilder zeroOrPositiveAmount() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final BigDecimal number = BigDecimal.valueOf(Double.valueOf(this.value.toString()));
+            if (number.compareTo(BigDecimal.ZERO) < 0) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.zero.or.greater");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                        " must be greater than or equal to 0.");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, number, 0);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    /*
+     * should be used with .notNull() before it
+     */
+    public DataValidatorBuilder integerZeroOrGreater() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Integer number = Integer.valueOf(this.value.toString());
+            if (number < 0) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.zero.or.greater");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                        " must be zero or greater.");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, number, 0);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    /*
+     * should be used with .notNull() before it
+     */
+    public DataValidatorBuilder integerGreaterThanZero() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Integer number = Integer.valueOf(this.value.toString());
+            if (number < 1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.greater.than.zero");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                        " must be greater than 0.");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, number, 0);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder integerGreaterThanNumber(Integer number) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Integer intValue = Integer.valueOf(this.value.toString());
+            if (intValue < number + 1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.greater.than.specified.number");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                        .append(" must be greater than ").append(number);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, intValue, number);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder integerEqualToOrGreaterThanNumber(Integer number) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Integer intValue = Integer.valueOf(this.value.toString());
+            if (intValue < number) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.equal.to.or.greater.than.specified.number");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                        .append(" must be equal to or greater than").append(number);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, intValue, number);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder integerSameAsNumber(Integer number) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Integer intValue = Integer.valueOf(this.value.toString());
+            if (intValue != number) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.equal.to.specified.number");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                        .append(" must be same as").append(number);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, intValue, number);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder integerInMultiplesOfNumber(Integer number) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Integer intValue = Integer.valueOf(this.value.toString());
+            if (intValue < number || intValue % number != 0) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.in.multiples.of.specified.number");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                        .append(" must be multiples of ").append(number);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, intValue, number);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    /*
+     * should be used with .notNull() before it
+     */
+    public DataValidatorBuilder longGreaterThanZero() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Long number = Long.valueOf(this.value.toString());
+            if (number < 1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.greater.than.zero");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                        " must be greater than 0.");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, number, 0);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    /*
+     * should be used with .notNull() before it
+     */
+    public DataValidatorBuilder longZeroOrGreater() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Long number = Long.valueOf(this.value.toString());
+            if (number < 0) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.equal.or.greater.than.zero");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                        " must be equal or greater than 0.");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, number, 0);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder longGreaterThanNumber(Long number) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Long longValue = Long.valueOf(this.value.toString());
+            if (longValue < number + 1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.greater.than.specified.number");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                        .append(" must be greater than ").append(number);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, longValue, number);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+    
+    public DataValidatorBuilder longGreaterThanNumber(String paramName, Long number, int index) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null) {
+            final Long longValue = Long.valueOf(this.value.toString());
+            if (longValue < number + 1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".not.greater.than.specified.").append(paramName).append(".at Index.").append(index);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                        .append(" must be greater than ").append(number);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, longValue, number);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+    
+    public DataValidatorBuilder arrayNotEmpty() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        final Object[] array = (Object[]) this.value;
+        if (ObjectUtils.isEmpty(array)) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".cannot.be.empty");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                    " cannot be empty. You must select at least one.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder jsonArrayNotEmpty() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        final JsonArray array = (JsonArray) this.value;
+        if (this.value != null && !array.iterator().hasNext()) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".cannot.be.empty");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                    " cannot be empty. You must select at least one.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public void expectedArrayButIsNot() {
+        final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                .append(this.parameter).append(".is.not.an.array");
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(" is not an array.");
+        final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
+                this.parameter);
+        this.dataValidationErrors.add(error);
+    }
+
+    public DataValidatorBuilder anyOfNotNull(final Object... object) {
+        boolean hasData = false;
+        for (final Object obj : object) {
+            if (obj != null) {
+                hasData = true;
+                break;
+            }
+        }
+
+        if (!hasData) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(
+                    ".no.parameters.for.update");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("No parameters passed for update.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), "id");
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder inValidValue(final String parameterValueCode, final Object invalidValue) {
+        final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                .append(this.parameter).append(".invalid.").append(parameterValueCode);
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                " has an invalid value.");
+        final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
+                this.parameter, invalidValue);
+        this.dataValidationErrors.add(error);
+        return this;
+    }
+
+    public DataValidatorBuilder mustBeBlankWhenParameterProvided(final String parameterName, final Object parameterValue) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value == null && parameterValue != null) { return this; }
+
+        if (this.value != null && StringUtils.isBlank(this.value.toString()) && parameterValue != null
+                && StringUtils.isNotBlank(parameterValue.toString())) { return this; }
+
+        final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                .append(this.parameter).append(".cannot.also.be.provided.when.").append(parameterName).append(".is.populated");
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                .append(" cannot also be provided when ").append(parameterName).append(" is populated.");
+        final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
+                this.parameter, this.value, parameterName, parameterValue);
+        this.dataValidationErrors.add(error);
+        return this;
+    }
+
+    public DataValidatorBuilder mustBeBlankWhenParameterProvidedIs(final String parameterName, final Object parameterValue) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value == null && parameterValue != null) { return this; }
+
+        if (this.value != null && StringUtils.isBlank(this.value.toString()) && parameterValue != null
+                && StringUtils.isNotBlank(parameterValue.toString())) { return this; }
+
+        final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                .append(this.parameter).append(".cannot.also.be.provided.when.").append(parameterName).append(".is.")
+                .append(parameterValue);
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                .append(" cannot also be provided when ").append(parameterName).append(" is ").append(parameterValue);
+        final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
+                this.parameter, this.value, parameterName, parameterValue);
+        this.dataValidationErrors.add(error);
+        return this;
+    }
+
+    public DataValidatorBuilder cantBeBlankWhenParameterProvidedIs(final String parameterName, final Object parameterValue) {
+        if (this.value != null && StringUtils.isNotBlank(this.value.toString())) { return this; }
+
+        final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                .append(this.parameter).append(".must.be.provided.when.").append(parameterName).append(".is.").append(parameterValue);
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                .append(" must be provided when ").append(parameterName).append(" is ").append(parameterValue);
+        final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
+                this.parameter, this.value, parameterName, parameterValue);
+        this.dataValidationErrors.add(error);
+        return this;
+    }
+
+    public DataValidatorBuilder comapareMinimumAndMaximumAmounts(final BigDecimal minimumBalance, final BigDecimal maximumBalance) {
+        if (minimumBalance != null && maximumBalance != null) {
+            if (maximumBalance.compareTo(minimumBalance) == -1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.not.within.expected.range");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(" minimum amount ")
+                        .append(minimumBalance).append(" should less than maximum amount ").append(maximumBalance).append(".");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, minimumBalance, maximumBalance);
+                this.dataValidationErrors.add(error);
+                return this;
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder inMinAndMaxAmountRange(final BigDecimal minimumAmount, final BigDecimal maximumAmount) {
+        if (minimumAmount != null && maximumAmount != null && this.value != null) {
+            final BigDecimal amount = BigDecimal.valueOf(Double.valueOf(this.value.toString()));
+            if (amount.compareTo(minimumAmount) == -1 || amount.compareTo(maximumAmount) == 1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".amount.is.not.within.min.max.range");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter).append(" amount ")
+                        .append(amount).append(" must be between ").append(minimumAmount).append(" and ").append(maximumAmount)
+                        .append(" .");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, amount, minimumAmount, maximumAmount);
+                this.dataValidationErrors.add(error);
+                return this;
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder notLessThanMin(final BigDecimal min) {
+        if (min != null && this.value != null) {
+            final BigDecimal amount = BigDecimal.valueOf(Double.valueOf(this.value.toString()));
+            if (amount.compareTo(min) == -1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.less.than.min");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter).append(" value ")
+                        .append(amount).append(" must not be less than minimum value ").append(min);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, amount, min);
+                this.dataValidationErrors.add(error);
+                return this;
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder notGreaterThanMax(final BigDecimal max) {
+        if (max != null && this.value != null) {
+            final BigDecimal amount = BigDecimal.valueOf(Double.valueOf(this.value.toString()));
+            if (amount.compareTo(max) == 1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.greater.than.max");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter).append(" value ")
+                        .append(amount).append(" must not be more than maximum value ").append(max);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, amount, max);
+                this.dataValidationErrors.add(error);
+                return this;
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder comapareMinAndMaxOfTwoBigDecmimalNos(final BigDecimal min, final BigDecimal max) {
+        if (min != null && max != null) {
+            if (max.compareTo(min) == -1) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.not.within.expected.range");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(" min number ").append(min)
+                        .append(" should less than max number ").append(max).append(".");
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, min, max);
+                this.dataValidationErrors.add(error);
+                return this;
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder isValidRecurringRule(final String recurringRule) {
+        if (StringUtils.isNotBlank(recurringRule)) {
+            try {
+                final RRule rRule = new RRule(recurringRule);
+                rRule.validate();
+            } catch (final ValidationException e) {
+                final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.recurring.rule",
+                        "The Recurring Rule value: " + recurringRule + " is not valid.", this.parameter, recurringRule);
+                this.dataValidationErrors.add(error);
+                return this;
+            } catch (final ParseException e) {
+                final ApiParameterError error = ApiParameterError.parameterError("validation.msg.recurring.rule.parsing.error",
+                        "Error in pasring the Recurring Rule value: " + recurringRule + ".", this.parameter, recurringRule);
+                this.dataValidationErrors.add(error);
+                return this;
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder notLessThanMin(final Integer min) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && min != null) {
+            final Integer number = Integer.valueOf(this.value.toString());
+            if (number < min) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.less.than.min");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
+                        .append(" must be greater than minimum value ").append(min);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, number, min);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder notGreaterThanMax(final Integer max) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && max != null) {
+            final Integer number = Integer.valueOf(this.value.toString());
+            if (number > max) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.greater.than.max");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
+                        .append(" must be less than maximum value ").append(max);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, number, max);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder matchesRegularExpression(final String expression) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && !this.value.toString().matches(expression)) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".does.not.match.regexp");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
+                    .append(" must match the provided regular expression [ ").append(expression).append(" ] ").append(".");
+
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter, this.value, expression);
+
+            this.dataValidationErrors.add(error);
+        }
+
+        return this;
+    }
+
+    public DataValidatorBuilder matchesRegularExpression(final String expression, final String Message) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && !this.value.toString().matches(expression)) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".does.not.match.regexp");
+            final StringBuilder defaultEnglishMessage = new StringBuilder(Message);
+
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter, this.value, expression);
+
+            this.dataValidationErrors.add(error);
+        }
+
+        return this;
+    }
+
+    private DataValidatorBuilder validateStringFor(final String validInputs) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+        final String[] inputs = validInputs.split(VALID_INPUT_SEPERATOR);
+        boolean validationErr = true;
+        for (final String input : inputs) {
+            if (input.equalsIgnoreCase(this.value.toString().trim())) {
+                validationErr = false;
+                break;
+            }
+        }
+        if (validationErr) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".value.should.true.or.false");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                    " value should true or false ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter, this.value);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder validateForBooleanValue() {
+        return validateStringFor("TRUE" + VALID_INPUT_SEPERATOR + "FALSE");
+    }
+
+    public DataValidatorBuilder validatePhoneNumber() {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+        boolean validationErr = true;
+        /*
+         * supports numbers, parentheses(), hyphens and may contain + sign in
+         * the beginning and can contain whitespaces in between and length
+         * allowed is 0-25 chars.
+         */
+        final String regex = "^\\+?[0-9. ()-]{0,25}$";
+        final Pattern pattern = Pattern.compile(regex);
+        final Matcher matcher = pattern.matcher(this.value.toString());
+        if (matcher.matches()) {
+            validationErr = false;
+        }
+        if (validationErr) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".format.is.invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.resource).append(this.parameter)
+                    .append(" is in invalid format, should contain '-','+','()' and numbers only.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter, this.value);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder validateCronExpression() {
+        if (this.value != null && !CronExpression.isValidExpression(this.value.toString().trim())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                    .append(this.parameter).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
+                    " value is not a valid cron expression");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), this.parameter, this.value);
+            this.dataValidationErrors.add(error);
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder validateDateAfter(final LocalDate date) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && date != null) {
+            final LocalDate dateVal = (LocalDate) this.value;
+            if (date.isAfter(dateVal)) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.less.than.date");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
+                        .append(" must be greter than provided date").append(date);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, dateVal, date);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder validateDateBefore(final LocalDate date) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && date != null) {
+            final LocalDate dateVal = (LocalDate) this.value;
+            if (date.isBefore(dateVal)) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.greater.than.date");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
+                        .append(" must be less than provided date").append(date);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, dateVal, date);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder validateDateBeforeOrEqual(final LocalDate date) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && date != null) {
+            final LocalDate dateVal = (LocalDate) this.value;
+            if (dateVal.isAfter(date)) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.greater.than.date");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
+                        .append(" must be less than or equal to provided date").append(date);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, dateVal, date);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+    public DataValidatorBuilder validateDateForEqual(final LocalDate date) {
+        if (this.value == null && this.ignoreNullValue) { return this; }
+
+        if (this.value != null && date != null) {
+            final LocalDate dateVal = (LocalDate) this.value;
+            if (!dateVal.isEqual(date)) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
+                        .append(this.parameter).append(".is.not.equal.to.date");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
+                        .append(" must be equal to provided date").append(date);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), this.parameter, dateVal, date);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/EnumOptionData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/EnumOptionData.java
new file mode 100644
index 0000000..ee3da0d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/EnumOptionData.java
@@ -0,0 +1,51 @@
+/**
+ * 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.core.data;
+
+/**
+ * <p>
+ * Immutable data object representing generic enumeration value.
+ * </p>
+ */
+public class EnumOptionData {
+
+    private final Long id;
+    private final String code;
+    private final String value;
+
+    public EnumOptionData(final Long id, final String code, final String value) {
+        this.id = id;
+        this.code = code;
+        this.value = value;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+    
+    public String getValue() {
+        return this.value;
+    }
+    
+    
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParameters.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParameters.java
new file mode 100644
index 0000000..3e0c7b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParameters.java
@@ -0,0 +1,140 @@
+/**
+ * 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.core.data;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>
+ * Immutable data object representing pagination parameter values.
+ * </p>
+ */
+public class PaginationParameters {
+
+    private final boolean paged;
+    private final Integer offset;
+    private final Integer limit;
+    private final String orderBy;
+    private final String sortOrder;
+
+    public static PaginationParameters instance(Boolean paged, Integer offset, Integer limit, String orderBy, String sortOrder) {
+        if (null == paged) {
+            paged = false;
+        }
+
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+
+        return new PaginationParameters(paged, offset, maxLimitAllowed, orderBy, sortOrder);
+    }
+
+    private PaginationParameters(boolean paged, Integer offset, Integer limit, String orderBy, String sortOrder) {
+        this.paged = paged;
+        this.offset = offset;
+        this.limit = limit;
+        this.orderBy = orderBy;
+        this.sortOrder = sortOrder;
+    }
+
+    public static Integer getCheckedLimit(final Integer limit) {
+
+        final Integer maxLimitAllowed = 200;
+        // default to max limit first off
+        Integer checkedLimit = maxLimitAllowed;
+
+        if (limit != null && limit > 0) {
+            checkedLimit = limit;
+        } else if (limit != null) {
+            // unlimited case: limit provided and 0 or less
+            checkedLimit = null;
+        }
+
+        return checkedLimit;
+    }
+
+    public boolean isPaged() {
+        return this.paged;
+    }
+
+    public Integer getOffset() {
+        return this.offset;
+    }
+
+    public Integer getLimit() {
+        return this.limit;
+    }
+
+    public String getOrderBy() {
+        return this.orderBy;
+    }
+
+    public String getSortOrder() {
+        return this.sortOrder;
+    }
+
+    public boolean isOrderByRequested() {
+        return StringUtils.isNotBlank(this.orderBy);
+    }
+
+    public boolean isSortOrderProvided() {
+        return StringUtils.isNotBlank(this.sortOrder);
+    }
+
+    public boolean isLimited() {
+        return this.limit != null && this.limit.intValue() > 0;
+    }
+
+    public boolean isOffset() {
+        return this.offset != null;
+    }
+
+    public String orderBySql() {
+        final StringBuffer sql = new StringBuffer();
+
+        if (this.isOrderByRequested()) {
+            sql.append(" order by ").append(this.getOrderBy());
+            if (this.isSortOrderProvided()) {
+                sql.append(' ').append(this.getSortOrder());
+            }
+        }
+        return sql.toString();
+    }
+
+    public String limitSql() {
+        final StringBuffer sql = new StringBuffer();
+        if (this.isLimited()) {
+            sql.append(" limit ").append(this.getLimit());
+            if (this.isOffset()) {
+                sql.append(" offset ").append(this.getOffset());
+            }
+        }
+        return sql.toString();
+    }
+    
+    public String paginationSql(){
+        final StringBuilder sqlBuilder = new StringBuilder(50); 
+        if (this.isOrderByRequested()) {
+            sqlBuilder.append(' ').append(this.orderBySql());
+        }        
+        if (this.isLimited()) {
+            sqlBuilder.append(' ').append(this.limitSql());
+        }
+        
+        return sqlBuilder.toString();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParametersDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParametersDataValidator.java
new file mode 100644
index 0000000..5c23f55
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParametersDataValidator.java
@@ -0,0 +1,63 @@
+/**
+ * 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.core.data;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PaginationParametersDataValidator {
+
+    public static Set<String> sortOrderValues = new HashSet<>(Arrays.asList("ASC", "DESC"));
+
+    public void validateParameterValues(PaginationParameters parameters, final Set<String> supportedOrdeByValues, final String resourceName) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        if (parameters.isOrderByRequested() && !supportedOrdeByValues.contains(parameters.getOrderBy())) {
+            final String defaultUserMessage = "The orderBy value '" + parameters.getOrderBy()
+                    + "' is not supported. The supported orderBy values are " + supportedOrdeByValues.toString();
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg." + resourceName
+                    + ".orderBy.value.is.not.supported", defaultUserMessage, "orderBy", parameters.getOrderBy(),
+                    supportedOrdeByValues.toString());
+            dataValidationErrors.add(error);
+        }
+
+        if (parameters.isSortOrderProvided() && !sortOrderValues.contains(parameters.getSortOrder().toUpperCase())) {
+            final String defaultUserMessage = "The sortOrder value '" + parameters.getSortOrder()
+                    + "' is not supported. The supported sortOrder values are " + sortOrderValues.toString();
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg." + resourceName
+                    + ".sortOrder.value.is.not.supported", defaultUserMessage, "sortOrder", parameters.getSortOrder(),
+                    sortOrderValues.toString());
+            dataValidationErrors.add(error);
+        }
+
+        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/infrastructure/core/domain/AbstractAuditableCustom.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableCustom.java
new file mode 100644
index 0000000..4707af1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableCustom.java
@@ -0,0 +1,163 @@
+/**
+ * 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.core.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.JoinColumn;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.OneToOne;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.joda.time.DateTime;
+import org.springframework.data.domain.Auditable;
+import org.springframework.data.jpa.domain.AbstractAuditable;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+/**
+ * A custom copy of {@link AbstractAuditable} to override the column names used
+ * on database.
+ * 
+ * Abstract base class for auditable entities. Stores the audition values in
+ * persistent fields.
+ * 
+ * @param <U>
+ *            the auditing type. Typically some kind of user.
+ * @param <PK>
+ *            the type of the auditing type's identifier
+ */
+@MappedSuperclass
+public abstract class AbstractAuditableCustom<U, PK extends Serializable> extends AbstractPersistable<PK> implements Auditable<U, PK> {
+
+    private static final long serialVersionUID = 141481953116476081L;
+
+    @OneToOne
+    @JoinColumn(name = "createdby_id")
+    private U createdBy;
+
+    @Column(name = "created_date")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date createdDate;
+
+    @OneToOne
+    @JoinColumn(name = "lastmodifiedby_id")
+    private U lastModifiedBy;
+
+    @Column(name = "lastmodified_date")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date lastModifiedDate;
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.springframework.data.domain.Auditable#getCreatedBy()
+     */
+    @Override
+    public U getCreatedBy() {
+
+        return this.createdBy;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.springframework.data.domain.Auditable#setCreatedBy(java.lang.Object)
+     */
+    @Override
+    public void setCreatedBy(final U createdBy) {
+
+        this.createdBy = createdBy;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.springframework.data.domain.Auditable#getCreatedDate()
+     */
+    @Override
+    public DateTime getCreatedDate() {
+
+        return null == this.createdDate ? null : new DateTime(this.createdDate);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.springframework.data.domain.Auditable#setCreatedDate(org.joda.time
+     * .DateTime)
+     */
+    @Override
+    public void setCreatedDate(final DateTime createdDate) {
+
+        this.createdDate = null == createdDate ? null : createdDate.toDate();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.springframework.data.domain.Auditable#getLastModifiedBy()
+     */
+    @Override
+    public U getLastModifiedBy() {
+
+        return this.lastModifiedBy;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.springframework.data.domain.Auditable#setLastModifiedBy(java.lang
+     * .Object)
+     */
+    @Override
+    public void setLastModifiedBy(final U lastModifiedBy) {
+
+        this.lastModifiedBy = lastModifiedBy;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.springframework.data.domain.Auditable#getLastModifiedDate()
+     */
+    @Override
+    public DateTime getLastModifiedDate() {
+
+        return null == this.lastModifiedDate ? null : new DateTime(this.lastModifiedDate);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.springframework.data.domain.Auditable#setLastModifiedDate(org.joda
+     * .time.DateTime)
+     */
+    @Override
+    public void setLastModifiedDate(final DateTime lastModifiedDate) {
+
+        this.lastModifiedDate = null == lastModifiedDate ? null : lastModifiedDate.toDate();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditorAwareImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditorAwareImpl.java
new file mode 100644
index 0000000..f859579
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditorAwareImpl.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.infrastructure.core.domain;
+
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.AppUserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public class AuditorAwareImpl implements AuditorAware<AppUser> {
+
+    @Autowired
+    private AppUserRepository userRepository;
+
+    @Override
+    public AppUser getCurrentAuditor() {
+
+        AppUser currentUser = null;
+        final SecurityContext securityContext = SecurityContextHolder.getContext();
+        if (securityContext != null) {
+            final Authentication authentication = securityContext.getAuthentication();
+            if (authentication != null) {
+                currentUser = (AppUser) authentication.getPrincipal();
+            } else {
+                currentUser = retrieveSuperUser();
+            }
+        } else {
+            currentUser = retrieveSuperUser();
+        }
+        return currentUser;
+    }
+
+    private AppUser retrieveSuperUser() {
+        return this.userRepository.findOne(Long.valueOf("1"));
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/Base64EncodedImage.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/Base64EncodedImage.java
new file mode 100644
index 0000000..2f6e17f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/Base64EncodedImage.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.core.domain;
+
+public class Base64EncodedImage {
+
+    private final String base64EncodedString;
+    private final String fileExtension;
+
+    public Base64EncodedImage(final String base64EncodedString, final String fileExtension) {
+        this.base64EncodedString = base64EncodedString;
+        this.fileExtension = fileExtension;
+    }
+
+    public String getBase64EncodedString() {
+        return this.base64EncodedString;
+    }
+
+    public String getFileExtension() {
+        return this.fileExtension;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/DefaultPlatformPasswordEncoder.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/DefaultPlatformPasswordEncoder.java
new file mode 100644
index 0000000..b09c5d8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/DefaultPlatformPasswordEncoder.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.domain;
+
+import org.apache.fineract.infrastructure.security.domain.PlatformUser;
+import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.security.authentication.dao.SaltSource;
+import org.springframework.security.authentication.encoding.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+@SuppressWarnings("deprecation")
+@Service(value = "applicationPasswordEncoder")
+@Scope("singleton")
+public class DefaultPlatformPasswordEncoder implements PlatformPasswordEncoder {
+
+    private final PasswordEncoder passwordEncoder;
+    private final SaltSource saltSource;
+
+    @Autowired
+    public DefaultPlatformPasswordEncoder(final PasswordEncoder passwordEncoder, final SaltSource saltSource) {
+        this.passwordEncoder = passwordEncoder;
+        this.saltSource = saltSource;
+    }
+
+    @Override
+    public String encode(final PlatformUser appUser) {
+        return this.passwordEncoder.encodePassword(appUser.getPassword(), this.saltSource.getSalt(appUser));
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/EmailDetail.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/EmailDetail.java
new file mode 100644
index 0000000..435016c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/EmailDetail.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.infrastructure.core.domain;
+
+public class EmailDetail {
+
+    private final String organisationName;
+    private final String username;
+    private final String contactName;
+    private final String address;
+
+    public EmailDetail(final String organisationName, final String contactName, final String address, final String username) {
+        this.organisationName = organisationName;
+        this.contactName = contactName;
+        this.address = address;
+        this.username = username;
+    }
+
+    public String getOrganisationName() {
+        return this.organisationName;
+    }
+
+    public String getUsername() {
+        return this.username;
+    }
+
+    public String getContactName() {
+        return this.contactName;
+    }
+
+    public String getAddress() {
+        return this.address;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenant.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenant.java
new file mode 100644
index 0000000..a462c92
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenant.java
@@ -0,0 +1,58 @@
+/**
+ * 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.core.domain;
+
+public class FineractPlatformTenant {
+
+    private final Long id;
+    private final String tenantIdentifier;
+    private final String name;
+    private final String timezoneId;
+    private final FineractPlatformTenantConnection connection;
+
+    public FineractPlatformTenant(final Long id, final String tenantIdentifier, final String name,
+            final String timezoneId, final FineractPlatformTenantConnection connection) {
+        this.id = id;
+        this.tenantIdentifier = tenantIdentifier;
+        this.name = name;
+        this.timezoneId = timezoneId;
+        this.connection = connection;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getTenantIdentifier() {
+        return this.tenantIdentifier;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getTimezoneId() {
+        return this.timezoneId;
+    }
+
+    public FineractPlatformTenantConnection getConnection() {
+        return connection;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java
new file mode 100644
index 0000000..11a8ebe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java
@@ -0,0 +1,252 @@
+/**
+ * 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.core.domain;
+
+
+/**
+ * Holds DB server connection details.
+ *
+ */
+public class FineractPlatformTenantConnection {
+    
+    private final Long connectionId;
+    private final String schemaServer;
+    private final String schemaServerPort;
+    private final String schemaUsername;
+    private final String schemaPassword;
+    private final String schemaName;
+    private final boolean autoUpdateEnabled;
+    private final int initialSize;
+    private final long validationInterval;
+    private final boolean removeAbandoned;
+    private final int removeAbandonedTimeout;
+    private final boolean logAbandoned;
+    private final int abandonWhenPercentageFull;
+    private final int maxActive;
+    private final int minIdle;
+    private final int maxIdle;
+    private final int suspectTimeout;
+    private final int timeBetweenEvictionRunsMillis;
+    private final int minEvictableIdleTimeMillis;
+    private final int maxRetriesOnDeadlock;
+    private final int maxIntervalBetweenRetries;
+    private final boolean testOnBorrow;
+    
+    public FineractPlatformTenantConnection(final Long connectionId,final String schemaName, String schemaServer,final String schemaServerPort,final String schemaUsername,final String schemaPassword,
+            final boolean autoUpdateEnabled,final int initialSize,final long validationInterval,final boolean removeAbandoned,final int removeAbandonedTimeout,
+            final boolean logAbandoned,final int abandonWhenPercentageFull,final int maxActive,final int minIdle,final int maxIdle,final int suspectTimeout,
+            final int timeBetweenEvictionRunsMillis,final int minEvictableIdleTimeMillis,final int maxRetriesOnDeadlock,final int maxIntervalBetweenRetries,final boolean tesOnBorrow) {
+       
+        this.connectionId = connectionId;
+        this.schemaName =schemaName;
+        this.schemaServer = schemaServer;
+        this.schemaServerPort = schemaServerPort;
+        this.schemaUsername = schemaUsername;
+        this.schemaPassword = schemaPassword;
+        this.autoUpdateEnabled = autoUpdateEnabled;
+        this.initialSize = initialSize;
+        this.validationInterval = validationInterval;
+        this.removeAbandoned = removeAbandoned;
+        this.removeAbandonedTimeout = removeAbandonedTimeout;
+        this.logAbandoned = logAbandoned;
+        this.abandonWhenPercentageFull = abandonWhenPercentageFull;
+        this.maxActive = maxActive;
+        this.minIdle = minIdle;
+        this.maxIdle = maxIdle;
+        this.suspectTimeout = suspectTimeout;
+        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
+        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
+        this.maxRetriesOnDeadlock = maxRetriesOnDeadlock;
+        this.maxIntervalBetweenRetries = maxIntervalBetweenRetries;
+        this.testOnBorrow=tesOnBorrow;
+    }
+
+    public String databaseURL() {
+        final String url = new StringBuilder("jdbc:mysql://").append(this.schemaServer).append(':').append(this.schemaServerPort)
+                .append('/').append(this.schemaName).toString();
+        return url;
+    }
+    
+    /**
+     * @return the schemaServer
+     */
+    public String getSchemaServer() {
+        return this.schemaServer;
+    }
+
+    
+    /**
+     * @return the schemaServerPort
+     */
+    public String getSchemaServerPort() {
+        return this.schemaServerPort;
+    }
+
+    
+    /**
+     * @return the schemaUsername
+     */
+    public String getSchemaUsername() {
+        return this.schemaUsername;
+    }
+
+    
+    /**
+     * @return the schemaPassword
+     */
+    public String getSchemaPassword() {
+        return this.schemaPassword;
+    }
+
+    
+    /**
+     * @return the autoUpdateEnabled
+     */
+    public boolean isAutoUpdateEnabled() {
+        return this.autoUpdateEnabled;
+    }
+
+    
+    /**
+     * @return the initialSize
+     */
+    public int getInitialSize() {
+        return this.initialSize;
+    }
+
+    
+    /**
+     * @return the validationInterval
+     */
+    public long getValidationInterval() {
+        return this.validationInterval;
+    }
+
+    
+    /**
+     * @return the removeAbandoned
+     */
+    public boolean isRemoveAbandoned() {
+        return this.removeAbandoned;
+    }
+
+    
+    /**
+     * @return the removeAbandonedTimeout
+     */
+    public int getRemoveAbandonedTimeout() {
+        return this.removeAbandonedTimeout;
+    }
+
+    
+    /**
+     * @return the logAbandoned
+     */
+    public boolean isLogAbandoned() {
+        return this.logAbandoned;
+    }
+
+    
+    /**
+     * @return the abandonWhenPercentageFull
+     */
+    public int getAbandonWhenPercentageFull() {
+        return this.abandonWhenPercentageFull;
+    }
+
+    
+    /**
+     * @return the maxActive
+     */
+    public int getMaxActive() {
+        return this.maxActive;
+    }
+
+    
+    /**
+     * @return the minIdle
+     */
+    public int getMinIdle() {
+        return this.minIdle;
+    }
+
+    
+    /**
+     * @return the maxIdle
+     */
+    public int getMaxIdle() {
+        return this.maxIdle;
+    }
+
+    
+    /**
+     * @return the suspectTimeout
+     */
+    public int getSuspectTimeout() {
+        return this.suspectTimeout;
+    }
+
+    
+    /**
+     * @return the timeBetweenEvictionRunsMillis
+     */
+    public int getTimeBetweenEvictionRunsMillis() {
+        return this.timeBetweenEvictionRunsMillis;
+    }
+
+    
+    /**
+     * @return the minEvictableIdleTimeMillis
+     */
+    public int getMinEvictableIdleTimeMillis() {
+        return this.minEvictableIdleTimeMillis;
+    }
+
+    
+    /**
+     * @return the maxRetriesOnDeadlock
+     */
+    public int getMaxRetriesOnDeadlock() {
+        return this.maxRetriesOnDeadlock;
+    }
+
+    
+    /**
+     * @return the maxIntervalBetweenRetries
+     */
+    public int getMaxIntervalBetweenRetries() {
+        return this.maxIntervalBetweenRetries;
+    }
+
+    public boolean isTestOnBorrow() {
+        return testOnBorrow;
+    }
+
+    public Long getConnectionId() {
+        return connectionId;
+    }
+
+    public String getSchemaName() {
+        return schemaName;
+    }
+    @Override
+    public String toString() {
+        return this.schemaName+":"+this.schemaServer+":"+this.schemaServerPort;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java
new file mode 100644
index 0000000..759c22f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.domain;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * Support for retrieving possibly null values from jdbc recordset delegating to
+ * springs {@link JdbcUtils} where possible.
+ */
+public class JdbcSupport {
+
+    public static DateTime getDateTime(final ResultSet rs, final String columnName) throws SQLException {
+        DateTime dateTime = null;
+        final Timestamp dateValue = rs.getTimestamp(columnName);
+        if (dateValue != null) {
+            dateTime = new DateTime(dateValue.getTime());
+        }
+        return dateTime;
+    }
+
+    public static LocalDate getLocalDate(final ResultSet rs, final String columnName) throws SQLException {
+        LocalDate localDate = null;
+        final Date dateValue = rs.getDate(columnName);
+        if (dateValue != null) {
+            localDate = new LocalDate(dateValue);
+        }
+        return localDate;
+    }
+
+    public static Long getLong(final ResultSet rs, final String columnName) throws SQLException {
+        return (Long) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Long.class);
+    }
+
+    public static Integer getInteger(final ResultSet rs, final String columnName) throws SQLException {
+        return (Integer) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Integer.class);
+    }
+
+    public static Integer getIntegerDefaultToNullIfZero(final ResultSet rs, final String columnName) throws SQLException {
+        final Integer value = (Integer) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Integer.class);
+        return defaultToNullIfZero(value);
+    }
+    
+    public static Long getLongDefaultToNullIfZero(final ResultSet rs, final String columnName) throws SQLException {
+        final Long value = (Long) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Long.class);
+        return defaultToNullIfZero(value);
+    }
+
+    private static Integer defaultToNullIfZero(final Integer value) {
+        Integer result = value;
+        if (result != null && Integer.valueOf(0).equals(value)) {
+            result = null;
+        }
+        return result;
+    }
+    
+    private static Long defaultToNullIfZero(final Long value) {
+        Long result = value;
+        if (result != null && Long.valueOf(0).equals(value)) {
+            result = null;
+        }
+        return result;
+    }
+
+    public static BigDecimal getBigDecimalDefaultToZeroIfNull(final ResultSet rs, final String columnName) throws SQLException {
+        final BigDecimal value = rs.getBigDecimal(columnName);
+        return defaultToZeroIfNull(value);
+    }
+
+    private static BigDecimal defaultToZeroIfNull(final BigDecimal value) {
+        BigDecimal result = BigDecimal.ZERO;
+        if (value != null) {
+            result = value;
+        }
+        return result;
+    }
+
+    public static BigDecimal getBigDecimalDefaultToNullIfZero(final ResultSet rs, final String columnName) throws SQLException {
+        final BigDecimal value = rs.getBigDecimal(columnName);
+        return defaultToNullIfZero(value);
+    }
+
+    private static BigDecimal defaultToNullIfZero(final BigDecimal value) {
+        BigDecimal result = value;
+        if (value != null && BigDecimal.ZERO.compareTo(value) == 0) {
+            result = null;
+        }
+        return result;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java
new file mode 100644
index 0000000..d09b290
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java
@@ -0,0 +1,80 @@
+/**
+ * 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.core.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+
+public class LocalDateInterval {
+
+    private final LocalDate startDate;
+    private final LocalDate endDate;
+
+    public static LocalDateInterval create(final LocalDate startDate, final LocalDate endDate) {
+        return new LocalDateInterval(startDate, endDate);
+    }
+
+    public LocalDateInterval(final LocalDate startDate, final LocalDate endDate) {
+        this.startDate = startDate;
+        this.endDate = endDate;
+    }
+
+    public LocalDate startDate() {
+        return this.startDate;
+    }
+
+    public LocalDate endDate() {
+        return this.endDate;
+    }
+
+    public Integer daysInPeriodInclusiveOfEndDate() {
+        return daysBetween() + 1;
+    }
+
+    private Integer daysBetween() {
+        return Days.daysBetween(this.startDate, this.endDate).getDays();
+    }
+
+    public boolean containsPortionOf(final LocalDateInterval interval) {
+        return contains(interval.startDate) || contains(interval.endDate);
+    }
+
+    public boolean contains(final LocalDateInterval interval) {
+        return contains(interval.startDate) && contains(interval.endDate);
+    }
+
+    public boolean contains(final LocalDate target) {
+        return isBetweenInclusive(this.startDate, this.endDate, target);
+    }
+
+    private boolean isBetweenInclusive(final LocalDate start, final LocalDate end, final LocalDate target) {
+        return !target.isBefore(start) && !target.isAfter(end);
+    }
+
+    public boolean fallsBefore(final LocalDate dateToCheck) {
+        return this.endDate.isBefore(dateToCheck);
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/Tenant.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/Tenant.java
new file mode 100755
index 0000000..7fde855
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/Tenant.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.infrastructure.core.domain;
+
+public class Tenant {
+
+    private final Long id;
+    private final String name;
+    private final String schemaName;
+    private final String schemaServer;
+    private final String schemaServerPort;
+    private final String schemaUsername;
+    private final String schemaPassword;
+    private final String timezoneId;
+
+    public Tenant(final Long id, final String name, final String schemaName, final String schemaServer, final String schemaServerPort,
+            final String schemaUsername, final String schemaPassword, final String timezoneId) {
+        this.id = id;
+        this.name = name;
+        this.schemaName = schemaName;
+        this.schemaServer = schemaServer;
+        this.schemaServerPort = schemaServerPort;
+        this.schemaUsername = schemaUsername;
+        this.schemaPassword = schemaPassword;
+        this.timezoneId = timezoneId;
+
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getSchemaName() {
+        return this.schemaName;
+    }
+
+    public String getSchemaServer() {
+        return this.schemaServer;
+    }
+
+    public String getSchemaServerPort() {
+        return this.schemaServerPort;
+    }
+
+    public String getSchemaUsername() {
+        return this.schemaUsername;
+    }
+
+    public String getSchemaPassword() {
+        return this.schemaPassword;
+    }
+
+    public String getTimezoneId() {
+        return this.timezoneId;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/AbstractPlatformDomainRuleException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/AbstractPlatformDomainRuleException.java
new file mode 100644
index 0000000..314723f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/AbstractPlatformDomainRuleException.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.core.exception;
+
+/**
+ * A {@link RuntimeException} thrown when valid api request end up violating
+ * some domain rule.
+ */
+public abstract class AbstractPlatformDomainRuleException extends RuntimeException {
+
+    private final String globalisationMessageCode;
+    private final String defaultUserMessage;
+    private final Object[] defaultUserMessageArgs;
+
+    public AbstractPlatformDomainRuleException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        this.globalisationMessageCode = globalisationMessageCode;
+        this.defaultUserMessage = defaultUserMessage;
+        this.defaultUserMessageArgs = defaultUserMessageArgs;
+    }
+
+    public String getGlobalisationMessageCode() {
+        return this.globalisationMessageCode;
+    }
+
+    public String getDefaultUserMessage() {
+        return this.defaultUserMessage;
+    }
+
+    public Object[] getDefaultUserMessageArgs() {
+        return this.defaultUserMessageArgs;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/AbstractPlatformResourceNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/AbstractPlatformResourceNotFoundException.java
new file mode 100644
index 0000000..64b9823
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/AbstractPlatformResourceNotFoundException.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.core.exception;
+
+/**
+ * A {@link RuntimeException} thrown when resources that are queried for are not
+ * found.
+ */
+public abstract class AbstractPlatformResourceNotFoundException extends RuntimeException {
+
+    private final String globalisationMessageCode;
+    private final String defaultUserMessage;
+    private final Object[] defaultUserMessageArgs;
+
+    public AbstractPlatformResourceNotFoundException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        this.globalisationMessageCode = globalisationMessageCode;
+        this.defaultUserMessage = defaultUserMessage;
+        this.defaultUserMessageArgs = defaultUserMessageArgs;
+    }
+
+    public String getGlobalisationMessageCode() {
+        return this.globalisationMessageCode;
+    }
+
+    public String getDefaultUserMessage() {
+        return this.defaultUserMessage;
+    }
+
+    public Object[] getDefaultUserMessageArgs() {
+        return this.defaultUserMessageArgs;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/AbstractPlatformServiceUnavailableException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/AbstractPlatformServiceUnavailableException.java
new file mode 100755
index 0000000..e43b4d0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/AbstractPlatformServiceUnavailableException.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.core.exception;
+
+/**
+ * A {@link RuntimeException} thrown when resources that are queried for are not
+ * found.
+ */
+public abstract class AbstractPlatformServiceUnavailableException extends RuntimeException {
+
+    private final String globalisationMessageCode;
+    private final String defaultUserMessage;
+    private final Object[] defaultUserMessageArgs;
+
+    public AbstractPlatformServiceUnavailableException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        this.globalisationMessageCode = globalisationMessageCode;
+        this.defaultUserMessage = defaultUserMessage;
+        this.defaultUserMessageArgs = defaultUserMessageArgs;
+    }
+
+    public String getGlobalisationMessageCode() {
+        return this.globalisationMessageCode;
+    }
+
+    public String getDefaultUserMessage() {
+        return this.defaultUserMessage;
+    }
+
+    public Object[] getDefaultUserMessageArgs() {
+        return this.defaultUserMessageArgs;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/GeneralPlatformDomainRuleException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/GeneralPlatformDomainRuleException.java
new file mode 100644
index 0000000..e7e0bbe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/GeneralPlatformDomainRuleException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.exception;
+
+/**
+ * A {@link RuntimeException} thrown when valid api request end up violating
+ * some domain rule.
+ */
+public final class GeneralPlatformDomainRuleException extends AbstractPlatformDomainRuleException {
+
+    public GeneralPlatformDomainRuleException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/ImageDataURLNotValidException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/ImageDataURLNotValidException.java
new file mode 100644
index 0000000..c2270bf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/ImageDataURLNotValidException.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.infrastructure.core.exception;
+
+public class ImageDataURLNotValidException extends AbstractPlatformDomainRuleException {
+
+    public ImageDataURLNotValidException() {
+        super("error.msg.dataURL.save", "Only GIF, PNG and JPEG Data URL's are allowed");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/ImageUploadException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/ImageUploadException.java
new file mode 100644
index 0000000..f28847d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/ImageUploadException.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.infrastructure.core.exception;
+
+public class ImageUploadException extends AbstractPlatformDomainRuleException {
+
+    public ImageUploadException() {
+        super("error.msg.image.type.upload", "Only image files of type GIF,PNG and JPG are allowed ");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/InvalidJsonException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/InvalidJsonException.java
new file mode 100644
index 0000000..9a265ba
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/InvalidJsonException.java
@@ -0,0 +1,27 @@
+/**
+ * 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.core.exception;
+
+/**
+ * A {@link RuntimeException} that is thrown in the case where invalid JSON is
+ * sent in the body of the request to the platform api.
+ */
+public class InvalidJsonException extends RuntimeException {
+    //
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java
new file mode 100644
index 0000000..5e284d1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java
@@ -0,0 +1,58 @@
+/**
+ * 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.core.exception;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+
+/**
+ * Exception thrown when problem with an API request to the platform.
+ */
+public class PlatformApiDataValidationException extends RuntimeException {
+
+    private final String globalisationMessageCode;
+    private final String defaultUserMessage;
+    private final List<ApiParameterError> errors;
+
+    public PlatformApiDataValidationException(final List<ApiParameterError> errors) {
+        this.globalisationMessageCode = "validation.msg.validation.errors.exist";
+        this.defaultUserMessage = "Validation errors exist.";
+        this.errors = errors;
+    }
+
+    public PlatformApiDataValidationException(final String globalisationMessageCode, final String defaultUserMessage,
+            final List<ApiParameterError> errors) {
+        this.globalisationMessageCode = globalisationMessageCode;
+        this.defaultUserMessage = defaultUserMessage;
+        this.errors = errors;
+    }
+
+    public String getGlobalisationMessageCode() {
+        return this.globalisationMessageCode;
+    }
+
+    public String getDefaultUserMessage() {
+        return this.defaultUserMessage;
+    }
+
+    public List<ApiParameterError> getErrors() {
+        return this.errors;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformDataIntegrityException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformDataIntegrityException.java
new file mode 100644
index 0000000..ec00f5d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformDataIntegrityException.java
@@ -0,0 +1,63 @@
+/**
+ * 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.core.exception;
+
+/**
+ * A {@link RuntimeException} thrown when data integrity problems happen due to
+ * state modifying actions.
+ */
+public class PlatformDataIntegrityException extends RuntimeException {
+
+    private final String globalisationMessageCode;
+    private final String defaultUserMessage;
+    private final String parameterName;
+    private final Object[] defaultUserMessageArgs;
+
+    public PlatformDataIntegrityException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        this.globalisationMessageCode = globalisationMessageCode;
+        this.defaultUserMessage = defaultUserMessage;
+        this.parameterName = null;
+        this.defaultUserMessageArgs = defaultUserMessageArgs;
+    }
+
+    public PlatformDataIntegrityException(final String globalisationMessageCode, final String defaultUserMessage,
+            final String parameterName, final Object... defaultUserMessageArgs) {
+        this.globalisationMessageCode = globalisationMessageCode;
+        this.defaultUserMessage = defaultUserMessage;
+        this.parameterName = parameterName;
+        this.defaultUserMessageArgs = defaultUserMessageArgs;
+    }
+
+    public String getGlobalisationMessageCode() {
+        return this.globalisationMessageCode;
+    }
+
+    public String getDefaultUserMessage() {
+        return this.defaultUserMessage;
+    }
+
+    public Object[] getDefaultUserMessageArgs() {
+        return this.defaultUserMessageArgs;
+    }
+
+    public String getParameterName() {
+        return this.parameterName;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformInternalServerException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformInternalServerException.java
new file mode 100644
index 0000000..e9fcdec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformInternalServerException.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.infrastructure.core.exception;
+
+/**
+ * A {@link RuntimeException} thrown when unexpected server side errors happen.
+ */
+public class PlatformInternalServerException extends RuntimeException {
+
+    private final String globalisationMessageCode;
+    private final String defaultUserMessage;
+    private final Object[] defaultUserMessageArgs;
+
+    public PlatformInternalServerException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        this.globalisationMessageCode = globalisationMessageCode;
+        this.defaultUserMessage = defaultUserMessage;
+        this.defaultUserMessageArgs = defaultUserMessageArgs;
+    }
+
+    public String getGlobalisationMessageCode() {
+        return this.globalisationMessageCode;
+    }
+
+    public String getDefaultUserMessage() {
+        return this.defaultUserMessage;
+    }
+
+    public Object[] getDefaultUserMessageArgs() {
+        return this.defaultUserMessageArgs;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformServiceUnavailableException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformServiceUnavailableException.java
new file mode 100755
index 0000000..bc8c6e5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformServiceUnavailableException.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.core.exception;
+
+public class PlatformServiceUnavailableException extends AbstractPlatformServiceUnavailableException {
+
+    public PlatformServiceUnavailableException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/UnrecognizedQueryParamException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/UnrecognizedQueryParamException.java
new file mode 100644
index 0000000..76fba29
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/UnrecognizedQueryParamException.java
@@ -0,0 +1,44 @@
+/**
+ * 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.core.exception;
+
+public class UnrecognizedQueryParamException extends RuntimeException {
+
+    private final String queryParamKey;
+    private final String queryParamValue;
+    private final Object[] supportedParams;
+
+    public UnrecognizedQueryParamException(final String queryParamKey, final String queryParamValue, final Object... supportedParams) {
+        this.queryParamKey = queryParamKey;
+        this.queryParamValue = queryParamValue;
+        this.supportedParams = supportedParams;
+    }
+
+    public String getQueryParamKey() {
+        return this.queryParamKey;
+    }
+
+    public String getQueryParamValue() {
+        return this.queryParamValue;
+    }
+
+    public Object[] getSupportedParams() {
+        return this.supportedParams;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/UnsupportedParameterException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/UnsupportedParameterException.java
new file mode 100644
index 0000000..da0a627
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/UnsupportedParameterException.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.core.exception;
+
+import java.util.List;
+
+/**
+ * A {@link RuntimeException} that is thrown in the case where invalid
+ * parameters are sent in the body of the request to the platform api.
+ */
+public class UnsupportedParameterException extends RuntimeException {
+
+    private final List<String> unsupportedParameters;
+
+    public UnsupportedParameterException(final List<String> unsupportedParameters) {
+        this.unsupportedParameters = unsupportedParameters;
+    }
+
+    public List<String> getUnsupportedParameters() {
+        return this.unsupportedParameters;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/AccessDeniedExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/AccessDeniedExceptionMapper.java
new file mode 100644
index 0000000..10e821b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/AccessDeniedExceptionMapper.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.springframework.context.annotation.Scope;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link AccessDeniedException} thrown by
+ * platform into a HTTP API friendly format.
+ * 
+ * The {@link AccessDeniedException} is thrown by spring security on platform
+ * when an attempt is made to use functionality for which the user does have
+ * sufficient privileges.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class AccessDeniedExceptionMapper implements ExceptionMapper<AccessDeniedException> {
+
+    @Override
+    public Response toResponse(final AccessDeniedException exception) {
+        // Status code 403 really reads as:
+        // "Authenticated - but not authorized":
+        final String defaultUserMessage = exception.getMessage();
+        return Response.status(Status.FORBIDDEN).entity(ApiGlobalErrorResponse.unAuthorized(defaultUserMessage))
+                .type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/BadCredentialsExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/BadCredentialsExceptionMapper.java
new file mode 100644
index 0000000..4f63d5b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/BadCredentialsExceptionMapper.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.infrastructure.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.springframework.context.annotation.Scope;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link BadCredentialsException} thrown by
+ * platform during authentication into a HTTP API friendly format.
+ * 
+ * The {@link BadCredentialsException} is thrown by spring security on platform
+ * when an attempt is made to authenticate using invalid username/password
+ * credentials.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class BadCredentialsExceptionMapper implements ExceptionMapper<BadCredentialsException> {
+
+    @Override
+    public Response toResponse(@SuppressWarnings("unused") final BadCredentialsException exception) {
+        return Response.status(Status.UNAUTHORIZED).entity(ApiGlobalErrorResponse.unAuthenticated()).type(MediaType.APPLICATION_JSON)
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidJsonExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidJsonExceptionMapper.java
new file mode 100644
index 0000000..62e2ac2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidJsonExceptionMapper.java
@@ -0,0 +1,51 @@
+/**
+ * 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.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link InvalidJsonException} thrown by
+ * platform into a HTTP API friendly format.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class InvalidJsonExceptionMapper implements ExceptionMapper<InvalidJsonException> {
+
+    @Override
+    public Response toResponse(@SuppressWarnings("unused") final InvalidJsonException exception) {
+
+        final String globalisationMessageCode = "error.msg.invalid.request.body";
+        final String defaultUserMessage = "The JSON provided in the body of the request is invalid or missing.";
+
+        final ApiParameterError error = ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage);
+
+        return Response.status(Status.BAD_REQUEST).entity(error).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidTenantIdentifierExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidTenantIdentifierExceptionMapper.java
new file mode 100644
index 0000000..23e1450
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/InvalidTenantIdentifierExceptionMapper.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentiferException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link InvalidTenantIdentiferException}
+ * thrown by platform during authentication into a HTTP API friendly format.
+ * 
+ * The {@link InvalidTenantIdentiferException} is thrown by spring security on
+ * platform when a request contains an invalid tenant identifier.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class InvalidTenantIdentifierExceptionMapper implements ExceptionMapper<InvalidTenantIdentiferException> {
+
+    @Override
+    public Response toResponse(@SuppressWarnings("unused") final InvalidTenantIdentiferException e) {
+        return Response.status(Status.UNAUTHORIZED).entity(ApiGlobalErrorResponse.invalidTenantIdentifier())
+                .type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/JsonSyntaxExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/JsonSyntaxExceptionMapper.java
new file mode 100644
index 0000000..3f578a9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/JsonSyntaxExceptionMapper.java
@@ -0,0 +1,52 @@
+/**
+ * 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.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * An {@link ExceptionMapper} to map {@link JsonSyntaxException} thrown by
+ * platform into a HTTP API friendly format.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class JsonSyntaxExceptionMapper implements ExceptionMapper<JsonSyntaxException> {
+
+    @Override
+    public Response toResponse(final JsonSyntaxException exception) {
+
+        final String globalisationMessageCode = "error.msg.invalid.request.body";
+        final String defaultUserMessage = "The JSON syntax provided in the body of the request is invalid: " + exception.getMessage();
+
+        final ApiParameterError error = ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage);
+
+        return Response.status(Status.BAD_REQUEST).entity(error).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/MalformedJsonExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/MalformedJsonExceptionMapper.java
new file mode 100644
index 0000000..6b7b2cc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/MalformedJsonExceptionMapper.java
@@ -0,0 +1,52 @@
+/**
+ * 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.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.stream.MalformedJsonException;
+
+/**
+ * An {@link ExceptionMapper} to map {@link MalformedJsonException} thrown by
+ * platform into a HTTP API friendly format.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class MalformedJsonExceptionMapper implements ExceptionMapper<MalformedJsonException> {
+
+    @Override
+    public Response toResponse(@SuppressWarnings("unused") final MalformedJsonException exception) {
+
+        final String globalisationMessageCode = "error.msg.invalid.request.body";
+        final String defaultUserMessage = "The JSON provided in the body of the request is invalid or missing.";
+
+        final ApiParameterError error = ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage);
+
+        return Response.status(Status.BAD_REQUEST).entity(error).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/NoAuthorizationExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/NoAuthorizationExceptionMapper.java
new file mode 100644
index 0000000..53f5cfc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/NoAuthorizationExceptionMapper.java
@@ -0,0 +1,52 @@
+/**
+ * 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.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link NoAuthorizationException} thrown by
+ * platform into a HTTP API friendly format.
+ * 
+ * The {@link NoAuthorizationException} is thrown on platform when an attempt is
+ * made to use functionality for which the user does have sufficient privileges.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class NoAuthorizationExceptionMapper implements ExceptionMapper<NoAuthorizationException> {
+
+    @Override
+    public Response toResponse(final NoAuthorizationException exception) {
+        // Status code 403 really reads as:
+        // "Authenticated - but not authorized":
+        final String defaultUserMessage = exception.getMessage();
+        return Response.status(Status.FORBIDDEN).entity(ApiGlobalErrorResponse.unAuthorized(defaultUserMessage))
+                .type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java
new file mode 100644
index 0000000..33bf74e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java
@@ -0,0 +1,52 @@
+/**
+ * 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.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link PlatformApiDataValidationException}
+ * thrown by platform into a HTTP API friendly format.
+ * 
+ * The {@link PlatformApiDataValidationException} is typically thrown in data
+ * validation of the parameters passed in with an api request.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class PlatformApiDataValidationExceptionMapper implements ExceptionMapper<PlatformApiDataValidationException> {
+
+    @Override
+    public Response toResponse(final PlatformApiDataValidationException exception) {
+
+        final ApiGlobalErrorResponse dataValidationErrorResponse = ApiGlobalErrorResponse.badClientRequest(
+                exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(), exception.getErrors());
+
+        return Response.status(Status.BAD_REQUEST).entity(dataValidationErrorResponse).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDataIntegrityExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDataIntegrityExceptionMapper.java
new file mode 100644
index 0000000..edc1bf5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDataIntegrityExceptionMapper.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link PlatformDataIntegrityException}
+ * thrown by platform into a HTTP API friendly format.
+ * 
+ * The {@link PlatformDataIntegrityException} is thrown when modifying api call
+ * result in data integrity checks to be fired.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class PlatformDataIntegrityExceptionMapper implements ExceptionMapper<PlatformDataIntegrityException> {
+
+    @Override
+    public Response toResponse(final PlatformDataIntegrityException exception) {
+
+        final ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.dataIntegrityError(
+                exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(), exception.getParameterName(),
+                exception.getDefaultUserMessageArgs());
+
+        return Response.status(Status.FORBIDDEN).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
new file mode 100644
index 0000000..e1ad390
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link AbstractPlatformDomainRuleException}
+ * thrown by platform into a HTTP API friendly format.
+ * 
+ * The {@link AbstractPlatformDomainRuleException} is thrown when an api call
+ * results is some internal business/domain logic been violated.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class PlatformDomainRuleExceptionMapper implements ExceptionMapper<AbstractPlatformDomainRuleException> {
+
+    @Override
+    public Response toResponse(final AbstractPlatformDomainRuleException exception) {
+
+        final ApiGlobalErrorResponse notFoundErrorResponse = ApiGlobalErrorResponse.domainRuleViolation(
+                exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
+        // request understood but not carried out due to it violating some
+        // domain/business logic
+        return Response.status(Status.FORBIDDEN).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java
new file mode 100644
index 0000000..262b302
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java
@@ -0,0 +1,51 @@
+/**
+ * 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.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link PlatformInternalServerException}
+ * thrown by platform into a HTTP API friendly format.
+ * 
+ * The {@link PlatformInternalServerException} is thrown when an api call
+ * results in unexpected server side exceptions.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class PlatformInternalServerExceptionMapper implements ExceptionMapper<PlatformInternalServerException> {
+
+    @Override
+    public Response toResponse(final PlatformInternalServerException exception) {
+
+        final ApiGlobalErrorResponse notFoundErrorResponse = ApiGlobalErrorResponse.serverSideError(exception.getGlobalisationMessageCode(),
+                exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
+        return Response.status(Status.INTERNAL_SERVER_ERROR).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformResourceNotFoundExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformResourceNotFoundExceptionMapper.java
new file mode 100644
index 0000000..5485380
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformResourceNotFoundExceptionMapper.java
@@ -0,0 +1,52 @@
+/**
+ * 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.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map
+ * {@link AbstractPlatformResourceNotFoundException} thrown by platform into a
+ * HTTP API friendly format.
+ * 
+ * The {@link AbstractPlatformResourceNotFoundException} is thrown when an api
+ * call for a resource that is expected to exist does not.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class PlatformResourceNotFoundExceptionMapper implements ExceptionMapper<AbstractPlatformResourceNotFoundException> {
+
+    @Override
+    public Response toResponse(final AbstractPlatformResourceNotFoundException exception) {
+
+        final ApiGlobalErrorResponse notFoundErrorResponse = ApiGlobalErrorResponse.notFound(exception.getGlobalisationMessageCode(),
+                exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
+        return Response.status(Status.NOT_FOUND).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformServiceUnavailableExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformServiceUnavailableExceptionMapper.java
new file mode 100755
index 0000000..a95ed16
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformServiceUnavailableExceptionMapper.java
@@ -0,0 +1,54 @@
+/**
+ * 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.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map
+ * {@link AbstractPlatformServiceUnavailableException} thrown by platform into a
+ * HTTP API friendly format.
+ * 
+ * The {@link AbstractPlatformServiceUnavailableException} is thrown when an api
+ * call for a resource that is expected to exist does not.
+ */
+
+@Provider
+@Component
+@Scope("singleton")
+public class PlatformServiceUnavailableExceptionMapper implements ExceptionMapper<AbstractPlatformServiceUnavailableException> {
+
+    @Override
+    public Response toResponse(final AbstractPlatformServiceUnavailableException exception) {
+        final ApiGlobalErrorResponse serviceUnavailableExceptionResponse = ApiGlobalErrorResponse.serviceUnavailable(
+                exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
+        return Response.status(Status.SERVICE_UNAVAILABLE).entity(serviceUnavailableExceptionResponse).type(MediaType.APPLICATION_JSON)
+                .build();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnAuthenticatedUserExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnAuthenticatedUserExceptionMapper.java
new file mode 100644
index 0000000..ac990bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnAuthenticatedUserExceptionMapper.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link UnAuthenticatedUserException} thrown
+ * by platform into a HTTP API friendly format.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class UnAuthenticatedUserExceptionMapper implements ExceptionMapper<UnAuthenticatedUserException> {
+
+    @Override
+    public Response toResponse(@SuppressWarnings("unused") final UnAuthenticatedUserException exception) {
+        // Status code 401 really reads as: "Unauthenticated":
+        return Response.status(Status.UNAUTHORIZED).entity(ApiGlobalErrorResponse.unAuthenticated()).type(MediaType.APPLICATION_JSON)
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnrecognizedQueryParamExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnrecognizedQueryParamExceptionMapper.java
new file mode 100644
index 0000000..429b085
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnrecognizedQueryParamExceptionMapper.java
@@ -0,0 +1,71 @@
+/**
+ * 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.core.exceptionmapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link UnrecognizedQueryParamException}
+ * thrown by platform into a HTTP API friendly format.
+ * 
+ * The {@link UnrecognizedQueryParamException} is typically thrown when a
+ * parameter is passed during and post or put that is not expected.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class UnrecognizedQueryParamExceptionMapper implements ExceptionMapper<UnrecognizedQueryParamException> {
+
+    @Override
+    public Response toResponse(final UnrecognizedQueryParamException exception) {
+
+        final String parameterName = exception.getQueryParamKey();
+        final String parameterValue = exception.getQueryParamValue();
+
+        final StringBuilder validationErrorCode = new StringBuilder("error.msg.query.parameter.value.unsupported");
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The query parameter ") //
+                .append(parameterName) //
+                .append(" has an unsupported value of: ") //
+                .append(parameterValue);
+
+        final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
+                parameterName, parameterName, parameterValue, exception.getSupportedParams());
+
+        final List<ApiParameterError> errors = new ArrayList<>();
+        errors.add(error);
+
+        final ApiGlobalErrorResponse invalidParameterError = ApiGlobalErrorResponse.badClientRequest(
+                "validation.msg.validation.errors.exist", "Validation errors exist.", errors);
+
+        return Response.status(Status.BAD_REQUEST).entity(invalidParameterError).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedCommandExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedCommandExceptionMapper.java
new file mode 100644
index 0000000..d1a2728
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedCommandExceptionMapper.java
@@ -0,0 +1,63 @@
+/**
+ * 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.core.exceptionmapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.commands.exception.UnsupportedCommandException;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link UnsupportedCommandException} thrown
+ * by platform into a HTTP API friendly format.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class UnsupportedCommandExceptionMapper implements ExceptionMapper<UnsupportedCommandException> {
+
+    @Override
+    public Response toResponse(final UnsupportedCommandException exception) {
+
+        final List<ApiParameterError> errors = new ArrayList<>();
+
+        final StringBuilder validationErrorCode = new StringBuilder("error.msg.command.unsupported");
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The command ").append(exception.getUnsupportedCommandName()).append(
+                " is not supported.");
+        final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
+                exception.getUnsupportedCommandName(), exception.getUnsupportedCommandName());
+
+        errors.add(error);
+
+        final ApiGlobalErrorResponse invalidParameterError = ApiGlobalErrorResponse.badClientRequest(
+                "validation.msg.validation.errors.exist", "Validation errors exist.", errors);
+
+        return Response.status(Status.BAD_REQUEST).entity(invalidParameterError).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedParameterExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedParameterExceptionMapper.java
new file mode 100644
index 0000000..79c9b0b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedParameterExceptionMapper.java
@@ -0,0 +1,65 @@
+/**
+ * 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.core.exceptionmapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * An {@link ExceptionMapper} to map {@link UnsupportedParameterException}
+ * thrown by platform into a HTTP API friendly format.
+ */
+@Provider
+@Component
+@Scope("singleton")
+public class UnsupportedParameterExceptionMapper implements ExceptionMapper<UnsupportedParameterException> {
+
+    @Override
+    public Response toResponse(final UnsupportedParameterException exception) {
+
+        final List<ApiParameterError> errors = new ArrayList<>();
+
+        for (final String parameterName : exception.getUnsupportedParameters()) {
+            final StringBuilder validationErrorCode = new StringBuilder("error.msg.parameter.unsupported");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameterName).append(
+                    " is not supported.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), parameterName, parameterName);
+
+            errors.add(error);
+        }
+
+        final ApiGlobalErrorResponse invalidParameterError = ApiGlobalErrorResponse.badClientRequest(
+                "validation.msg.validation.errors.exist", "Validation errors exist.", errors);
+
+        return Response.status(Status.BAD_REQUEST).entity(invalidParameterError).type(MediaType.APPLICATION_JSON).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java
new file mode 100644
index 0000000..639a82a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.filters;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerResponse;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+
+/**
+ * Filter that returns a response with headers that allows for Cross-Origin
+ * Requests (CORs) to be performed against the platform API.
+ */
+public class ResponseCorsFilter implements ContainerResponseFilter {
+
+    @Override
+    public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response) {
+
+        final ResponseBuilder resp = Response.fromResponse(response.getResponse());
+
+        resp.header("Access-Control-Allow-Origin", "*")
+        // .header("Access-Control-Expose-Headers", "Fineract-Platform-TenantId")
+                .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
+
+        final String reqHead = request.getHeaderValue("Access-Control-Request-Headers");
+
+        if (null != reqHead && !reqHead.equals(null)) {
+            resp.header("Access-Control-Allow-Headers", reqHead);
+        }
+
+        response.setResponse(resp.build());
+
+        return response;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/AbstractFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/AbstractFromApiJsonDeserializer.java
new file mode 100644
index 0000000..4f5dd5a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/AbstractFromApiJsonDeserializer.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.infrastructure.core.serialization;
+
+/**
+ * Abstract implementation of {@link FromApiJsonDeserializer} that can be
+ * extended for specific commands.
+ */
+public abstract class AbstractFromApiJsonDeserializer<T> implements FromApiJsonDeserializer<T> {
+
+    @Override
+    public abstract T commandFromApiJson(final String json);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/AbstractFromCommandJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/AbstractFromCommandJsonDeserializer.java
new file mode 100644
index 0000000..6723c11
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/AbstractFromCommandJsonDeserializer.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.infrastructure.core.serialization;
+
+/**
+ * Abstract implementation of {@link FromCommandJsonDeserializer} that can be
+ * extended for specific commands.
+ */
+public abstract class AbstractFromCommandJsonDeserializer<T> implements FromCommandJsonDeserializer<T> {
+
+    @Override
+    public T commandFromCommandJson(final String commandAsJson) {
+        return commandFromCommandJson(null, commandAsJson);
+    }
+
+    @Override
+    public T commandFromCommandJson(final Long codeId, final String commandAsJson) {
+        return commandFromCommandJson(codeId, commandAsJson, false);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ApiRequestJsonSerializationSettings.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ApiRequestJsonSerializationSettings.java
new file mode 100644
index 0000000..a66ebe2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ApiRequestJsonSerializationSettings.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.infrastructure.core.serialization;
+
+import java.util.Set;
+
+/**
+ * A class to encapsulate settings we allow on API that affect how JSON is to be
+ * serialized for the response to api call.
+ */
+public class ApiRequestJsonSerializationSettings {
+
+    private final boolean prettyPrint;
+    private final Set<String> parametersForPartialResponse;
+    private final boolean template;
+    private final boolean makerCheckerable;
+    private final boolean includeJson;
+
+    public ApiRequestJsonSerializationSettings(final boolean prettyPrint, final Set<String> parametersForPartialResponse,
+            final boolean template, final boolean makerCheckerable, final boolean includeJson) {
+        this.prettyPrint = prettyPrint;
+        this.parametersForPartialResponse = parametersForPartialResponse;
+        this.template = template;
+        this.makerCheckerable = makerCheckerable;
+        this.includeJson = includeJson;
+    }
+
+    public static ApiRequestJsonSerializationSettings from(final boolean prettyPrint, final Set<String> parametersForPartialResponse,
+            final boolean template, final boolean makerCheckerable, final boolean includeJson) {
+
+        // FIXME - KW - rather than always creating new objects for this could
+        // just send by common ones like, prettyprint=false, empty response
+        // parameters
+        return new ApiRequestJsonSerializationSettings(prettyPrint, parametersForPartialResponse, template, makerCheckerable, includeJson);
+    }
+
+    public boolean isPrettyPrint() {
+        return this.prettyPrint;
+    }
+
+    public boolean isTemplate() {
+        return this.template;
+    }
+
+    public boolean isMakerCheckerable() {
+        return this.makerCheckerable;
+    }
+
+    public boolean isIncludeJson() {
+        return this.includeJson;
+    }
+
+    public Set<String> getParametersForPartialResponse() {
+        return this.parametersForPartialResponse;
+    }
+
+    public boolean isPartialResponseRequired() {
+        return !this.parametersForPartialResponse.isEmpty();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/CommandProcessingResultJsonSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/CommandProcessingResultJsonSerializer.java
new file mode 100644
index 0000000..29bc811
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/CommandProcessingResultJsonSerializer.java
@@ -0,0 +1,65 @@
+/**
+ * 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.core.serialization;
+
+import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
+import org.apache.fineract.infrastructure.core.api.JodaLocalDateAdapter;
+import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * <p>
+ * A google gson implementation of {@link ExcludeNothingJsonSerializer}
+ * contract.
+ * </p>
+ * 
+ * <p>
+ * It serializes all fields of any Java {@link Object} passed to it.
+ * </p>
+ */
+@Component
+public final class CommandProcessingResultJsonSerializer {
+
+    private final Gson gson;
+
+    public CommandProcessingResultJsonSerializer() {
+        final GsonBuilder builder = new GsonBuilder();
+        builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter());
+        builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter());
+        builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
+        builder.serializeNulls();
+
+        this.gson = builder.create();
+    }
+
+    public String serialize(final Object result) {
+        String returnedResult = null;
+        final String serializedResult = this.gson.toJson(result);
+        if (!"null".equalsIgnoreCase(serializedResult)) {
+            returnedResult = serializedResult;
+        }
+        return returnedResult;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/CommandSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/CommandSerializer.java
new file mode 100644
index 0000000..b28ded3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/CommandSerializer.java
@@ -0,0 +1,33 @@
+/**
+ * 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.core.serialization;
+
+/**
+ * Service for serializing commands into another format.
+ * 
+ * <p>
+ * Known implementations:
+ * </p>
+ * 
+ * @see CommandSerializerDefaultToJson
+ */
+public interface CommandSerializer {
+
+    String serializeCommandToJson(Object command);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/CommandSerializerDefaultToJson.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/CommandSerializerDefaultToJson.java
new file mode 100644
index 0000000..f01b504
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/CommandSerializerDefaultToJson.java
@@ -0,0 +1,43 @@
+/**
+ * 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.core.serialization;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implementation of {@link CommandSerializer} that serializes the commands into
+ * JSON using google-gson.
+ */
+@Component
+public class CommandSerializerDefaultToJson implements CommandSerializer {
+
+    private final ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson excludeNothingWithPrettyPrintingOff;
+
+    @Autowired
+    public CommandSerializerDefaultToJson(
+            final ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson excludeNothingWithPrettyPrintingOff) {
+        this.excludeNothingWithPrettyPrintingOff = excludeNothingWithPrettyPrintingOff;
+    }
+
+    @Override
+    public String serializeCommandToJson(final Object command) {
+        return this.excludeNothingWithPrettyPrintingOff.serialize(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..2d52b4f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java
@@ -0,0 +1,258 @@
+/**
+ * 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.core.serialization;
+
+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.exception.PlatformDataIntegrityException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class DatatableCommandFromApiJsonDeserializer {
+
+    private final static String DATATABLE_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,48}[a-zA-Z0-9]$";
+    private final static String DATATABLE_COLUMN_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,}[a-zA-Z0-9]$";
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParametersForCreate = new HashSet<>(Arrays.asList("datatableName", "apptableName", "multiRow",
+            "columns"));
+    private final Set<String> supportedParametersForCreateColumns = new HashSet<>(Arrays.asList("name", "type", "length",
+            "mandatory", "code"));
+    private final Set<String> supportedParametersForUpdate = new HashSet<>(Arrays.asList("apptableName", "changeColumns",
+            "addColumns", "dropColumns"));
+    private final Set<String> supportedParametersForAddColumns = new HashSet<>(Arrays.asList("name", "type", "length", "mandatory",
+            "after", "code"));
+    private final Set<String> supportedParametersForChangeColumns = new HashSet<>(Arrays.asList("name", "newName", "length",
+            "mandatory", "after", "code", "newCode"));
+    private final Set<String> supportedParametersForDropColumns = new HashSet<>(Arrays.asList("name"));
+    private final Object[] supportedColumnTypes = { "string", "number", "boolean", "decimal", "date", "datetime", "text", "dropdown" };
+    private final Object[] supportedApptableNames = { "m_loan", "m_savings_account", "m_client", "m_group", "m_center", "m_office",
+            "m_savings_product", "m_product_loan" };
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public DatatableCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    private void validateType(final DataValidatorBuilder baseDataValidator, final JsonElement column) {
+        final String type = this.fromApiJsonHelper.extractStringNamed("type", column);
+        baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseStringValues(this.supportedColumnTypes);
+
+        if (type != null && type.equalsIgnoreCase("String")) {
+            if (this.fromApiJsonHelper.parameterExists("length", column)) {
+                final String lengthStr = this.fromApiJsonHelper.extractStringNamed("length", column);
+                if (lengthStr != null && !StringUtils.isWhitespace(lengthStr) && StringUtils.isNumeric(lengthStr)
+                        && StringUtils.isNotBlank(lengthStr)) {
+                    final Integer length = Integer.parseInt(lengthStr);
+                    baseDataValidator.reset().parameter("length").value(length).positiveAmount();
+                } else if (StringUtils.isBlank(lengthStr) || StringUtils.isWhitespace(lengthStr)) {
+                    baseDataValidator.reset().parameter("length").failWithCode("must.be.provided.when.type.is.String");
+                } else if (!StringUtils.isNumeric(lengthStr)) {
+                    baseDataValidator.reset().parameter("length").failWithCode("not.greater.than.zero");
+                }
+            } else {
+                baseDataValidator.reset().parameter("length").failWithCode("must.be.provided.when.type.is.String");
+            }
+        } else {
+            baseDataValidator.reset().parameter("length").mustBeBlankWhenParameterProvidedIs("type", type);
+        }
+
+        final String code = this.fromApiJsonHelper.extractStringNamed("code", column);
+        if (type != null && type.equalsIgnoreCase("Dropdown")) {
+            if (code != null) {
+                baseDataValidator.reset().parameter("code").value(code).notBlank().matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
+            } else {
+                baseDataValidator.reset().parameter("code").value(code).cantBeBlankWhenParameterProvidedIs("type", type);
+            }
+        } else {
+            baseDataValidator.reset().parameter("code").value(code).mustBeBlankWhenParameterProvided("type", type);
+        }
+    }
+
+    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.supportedParametersForCreate);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String datatableName = this.fromApiJsonHelper.extractStringNamed("datatableName", element);
+        baseDataValidator.reset().parameter("datatableName").value(datatableName).notBlank().notExceedingLengthOf(50)
+                .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
+
+        final String apptableName = this.fromApiJsonHelper.extractStringNamed("apptableName", element);
+        baseDataValidator.reset().parameter("apptableName").value(apptableName).notBlank().notExceedingLengthOf(50)
+                .isOneOfTheseValues(this.supportedApptableNames);
+        final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : "";
+
+        final Boolean multiRow = this.fromApiJsonHelper.extractBooleanNamed("multiRow", element);
+        baseDataValidator.reset().parameter("multiRow").value(multiRow).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
+
+        final JsonArray columns = this.fromApiJsonHelper.extractJsonArrayNamed("columns", element);
+        baseDataValidator.reset().parameter("columns").value(columns).notNull().jsonArrayNotEmpty();
+
+        if (columns != null) {
+            for (final JsonElement column : columns) {
+                this.fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), this.supportedParametersForCreateColumns);
+
+                final String name = this.fromApiJsonHelper.extractStringNamed("name", column);
+                baseDataValidator.reset().parameter("name").value(name).notBlank().isNotOneOfTheseValues("id", fkColumnName)
+                        .matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
+
+                validateType(baseDataValidator, column);
+
+                final Boolean mandatory = this.fromApiJsonHelper.extractBooleanNamed("mandatory", column);
+                baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
+            }
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+        // Because all parameters are optional, a check to see if at least one
+        // parameter
+        // has been specified is necessary in order to avoid JSON requests with
+        // no parameters
+        if (!json.matches("(?s)\\A\\{.*?(\\\".*?\\\"\\s*?:\\s*?)+.*?\\}\\z")) { throw new PlatformDataIntegrityException(
+                "error.msg.invalid.request.body.no.parameters", "Provided JSON request body does not have any parameters."); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParametersForUpdate);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final String apptableName = this.fromApiJsonHelper.extractStringNamed("apptableName", element);
+        baseDataValidator.reset().parameter("apptableName").value(apptableName).ignoreIfNull().notBlank()
+                .isOneOfTheseValues(this.supportedApptableNames);
+        final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : "";
+
+        final JsonArray changeColumns = this.fromApiJsonHelper.extractJsonArrayNamed("changeColumns", element);
+        baseDataValidator.reset().parameter("changeColumns").value(changeColumns).ignoreIfNull().jsonArrayNotEmpty();
+
+        if (changeColumns != null) {
+            for (final JsonElement column : changeColumns) {
+                this.fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), this.supportedParametersForChangeColumns);
+
+                final String name = this.fromApiJsonHelper.extractStringNamed("name", column);
+                baseDataValidator.reset().parameter("name").value(name).notBlank().isNotOneOfTheseValues("id", fkColumnName)
+                        .matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
+
+                final String newName = this.fromApiJsonHelper.extractStringNamed("newName", column);
+                baseDataValidator.reset().parameter("newName").value(newName).ignoreIfNull().notBlank().notExceedingLengthOf(50)
+                        .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
+
+                if (this.fromApiJsonHelper.parameterExists("length", column)) {
+                    final String lengthStr = this.fromApiJsonHelper.extractStringNamed("length", column);
+                    if (StringUtils.isWhitespace(lengthStr) || !StringUtils.isNumeric(lengthStr) || StringUtils.isBlank(lengthStr)) {
+                        baseDataValidator.reset().parameter("length").failWithCode("not.greater.than.zero");
+                    } else {
+                        final Integer length = Integer.parseInt(lengthStr);
+                        baseDataValidator.reset().parameter("length").value(length).ignoreIfNull().notBlank().positiveAmount();
+                    }
+                }
+
+                final String code = this.fromApiJsonHelper.extractStringNamed("code", column);
+                baseDataValidator.reset().parameter("code").value(code).ignoreIfNull().notBlank().notExceedingLengthOf(100)
+                        .matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
+
+                final String newCode = this.fromApiJsonHelper.extractStringNamed("newCode", column);
+                baseDataValidator.reset().parameter("newCode").value(newCode).ignoreIfNull().notBlank().notExceedingLengthOf(100)
+                        .matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
+
+                if (StringUtils.isBlank(code) && StringUtils.isNotBlank(newCode)) {
+                    baseDataValidator.reset().parameter("code").value(code).cantBeBlankWhenParameterProvidedIs("newCode", newCode);
+                }
+
+                final Boolean mandatory = this.fromApiJsonHelper.extractBooleanNamed("mandatory", column);
+                baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
+
+                final Boolean after = this.fromApiJsonHelper.extractBooleanNamed("after", column);
+                baseDataValidator.reset().parameter("after").value(after).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
+            }
+        }
+
+        final JsonArray addColumns = this.fromApiJsonHelper.extractJsonArrayNamed("addColumns", element);
+        baseDataValidator.reset().parameter("addColumns").value(addColumns).ignoreIfNull().jsonArrayNotEmpty();
+
+        if (addColumns != null) {
+            for (final JsonElement column : addColumns) {
+                this.fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), this.supportedParametersForAddColumns);
+
+                final String name = this.fromApiJsonHelper.extractStringNamed("name", column);
+                baseDataValidator.reset().parameter("name").value(name).notBlank().isNotOneOfTheseValues("id", fkColumnName)
+                        .matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
+
+                validateType(baseDataValidator, column);
+
+                final Boolean mandatory = this.fromApiJsonHelper.extractBooleanNamed("mandatory", column);
+                baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
+
+                final Boolean after = this.fromApiJsonHelper.extractBooleanNamed("after", column);
+                baseDataValidator.reset().parameter("after").value(after).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
+            }
+        }
+
+        final JsonArray dropColumns = this.fromApiJsonHelper.extractJsonArrayNamed("dropColumns", element);
+        baseDataValidator.reset().parameter("dropColumns").value(dropColumns).ignoreIfNull().jsonArrayNotEmpty();
+
+        if (dropColumns != null) {
+            for (final JsonElement column : dropColumns) {
+                this.fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), this.supportedParametersForDropColumns);
+
+                final String name = this.fromApiJsonHelper.extractStringNamed("name", column);
+                baseDataValidator.reset().parameter("name").value(name).notBlank().isNotOneOfTheseValues("id", fkColumnName)
+                        .matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
+            }
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DefaultToApiJsonSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DefaultToApiJsonSerializer.java
new file mode 100644
index 0000000..3432fb8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DefaultToApiJsonSerializer.java
@@ -0,0 +1,159 @@
+/**
+ * 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.core.serialization;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.Gson;
+
+/**
+ * An abstract helper implementation of {@link ToApiJsonSerializer} for
+ * resources to serialize their Java data objects into JSON.
+ */
+@Component
+public final class DefaultToApiJsonSerializer<T> implements ToApiJsonSerializer<T> {
+
+    private final ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson excludeNothingWithPrettyPrintingOff;
+    private final ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson excludeNothingWithPrettyPrintingOn;
+    private final CommandProcessingResultJsonSerializer commandProcessingResultSerializer;
+    private final GoogleGsonSerializerHelper helper;
+
+    @Autowired
+    public DefaultToApiJsonSerializer(
+            final ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson excludeNothingWithPrettyPrintingOff,
+            final ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson excludeNothingWithPrettyPrintingOn,
+            final CommandProcessingResultJsonSerializer commandProcessingResultSerializer, final GoogleGsonSerializerHelper helper) {
+        this.excludeNothingWithPrettyPrintingOff = excludeNothingWithPrettyPrintingOff;
+        this.excludeNothingWithPrettyPrintingOn = excludeNothingWithPrettyPrintingOn;
+        this.commandProcessingResultSerializer = commandProcessingResultSerializer;
+        this.helper = helper;
+    }
+
+    @Override
+    public String serializeResult(final Object object) {
+        return this.commandProcessingResultSerializer.serialize(object);
+    }
+
+    @Override
+    public String serialize(final Object object) {
+        return this.excludeNothingWithPrettyPrintingOff.serialize(object);
+    }
+
+    @Override
+    public String serializePretty(final boolean prettyOn, final Object object) {
+        String json = "";
+
+        if (prettyOn) {
+            json = this.excludeNothingWithPrettyPrintingOn.serialize(object);
+        } else {
+            json = serialize(object);
+        }
+        return json;
+    }
+
+    @Override
+    public String serialize(final ApiRequestJsonSerializationSettings settings, final Collection<T> collection,
+            final Set<String> supportedResponseParameters) {
+        final Gson delegatedSerializer = findAppropriateSerializer(settings, supportedResponseParameters);
+        return serializeWithSettings(delegatedSerializer, settings, collection.toArray());
+    }
+
+    @Override
+    public String serialize(final ApiRequestJsonSerializationSettings settings, final T singleObject,
+            final Set<String> supportedResponseParameters) {
+        final Gson delegatedSerializer = findAppropriateSerializer(settings, supportedResponseParameters);
+        return serializeWithSettings(delegatedSerializer, settings, singleObject);
+    }
+
+    @Override
+    public String serialize(final ApiRequestJsonSerializationSettings settings, final Page<T> singleObject,
+            final Set<String> supportedResponseParameters) {
+        final Gson delegatedSerializer = findAppropriateSerializer(settings, supportedResponseParameters);
+        return serializeWithSettings(delegatedSerializer, settings, singleObject);
+    }
+
+    @Override
+    public String serialize(final ApiRequestJsonSerializationSettings settings, final Collection<T> collection) {
+        final Gson delegatedSerializer = findAppropriateSerializer(settings);
+        return serializeWithSettings(delegatedSerializer, settings, collection.toArray());
+    }
+
+    @Override
+    public String serialize(final ApiRequestJsonSerializationSettings settings, final T singleObject) {
+        final Gson delegatedSerializer = findAppropriateSerializer(settings);
+        return serializeWithSettings(delegatedSerializer, settings, singleObject);
+    }
+
+    @Override
+    public String serialize(final ApiRequestJsonSerializationSettings settings, final Page<T> singleObject) {
+        final Gson delegatedSerializer = findAppropriateSerializer(settings);
+        return serializeWithSettings(delegatedSerializer, settings, singleObject);
+    }
+
+    private String serializeWithSettings(final Gson gson, final ApiRequestJsonSerializationSettings settings, final Object[] dataObject) {
+        String json = null;
+        if (gson != null) {
+            json = this.helper.serializedJsonFrom(gson, dataObject);
+        } else {
+            if (settings.isPrettyPrint()) {
+                json = this.excludeNothingWithPrettyPrintingOn.serialize(dataObject);
+            } else {
+                json = serialize(dataObject);
+            }
+        }
+        return json;
+    }
+
+    private String serializeWithSettings(final Gson gson, final ApiRequestJsonSerializationSettings settings, final Object dataObject) {
+        String json = null;
+        if (gson != null) {
+            json = this.helper.serializedJsonFrom(gson, dataObject);
+        } else {
+            if (settings.isPrettyPrint()) {
+                json = this.excludeNothingWithPrettyPrintingOn.serialize(dataObject);
+            } else {
+                json = serialize(dataObject);
+            }
+        }
+        return json;
+    }
+
+    private Gson findAppropriateSerializer(final ApiRequestJsonSerializationSettings settings, final Set<String> supportedResponseParameters) {
+        Gson gson = null;
+        if (settings.isPartialResponseRequired()) {
+            gson = this.helper.createGsonBuilderWithParameterExclusionSerializationStrategy(supportedResponseParameters,
+                    settings.isPrettyPrint(), settings.getParametersForPartialResponse());
+        }
+        return gson;
+    }
+
+    private Gson findAppropriateSerializer(final ApiRequestJsonSerializationSettings settings) {
+        Gson gson = null;
+        if (settings.isPartialResponseRequired()) {
+            gson = this.helper.createGsonBuilderForPartialResponseFiltering(settings.isPrettyPrint(),
+                    settings.getParametersForPartialResponse());
+        }
+        return gson;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson.java
new file mode 100644
index 0000000..5c819c7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson.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.infrastructure.core.serialization;
+
+import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
+import org.apache.fineract.infrastructure.core.api.JodaLocalDateAdapter;
+import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * <p>
+ * A google gson implementation of {@link ExcludeNothingJsonSerializer}
+ * contract.
+ * </p>
+ * 
+ * <p>
+ * It serializes all fields of any Java {@link Object} passed to it.
+ * </p>
+ */
+@Component
+public final class ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson {
+
+    private final Gson gson;
+
+    public ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson() {
+        final GsonBuilder builder = new GsonBuilder();
+        builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter());
+        builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter());
+        builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
+
+        this.gson = builder.create();
+    }
+
+    public String serialize(final Object result) {
+        String returnedResult = null;
+        final String serializedResult = this.gson.toJson(result);
+        if (!"null".equalsIgnoreCase(serializedResult)) {
+            returnedResult = serializedResult;
+        }
+        return returnedResult;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson.java
new file mode 100644
index 0000000..b649905
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson.java
@@ -0,0 +1,60 @@
+/**
+ * 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.core.serialization;
+
+import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
+import org.apache.fineract.infrastructure.core.api.JodaLocalDateAdapter;
+import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * <p>
+ * A google gson implementation of {@link ExcludeNothingJsonSerializer}
+ * contract.
+ * </p>
+ * 
+ * <p>
+ * It serializes all fields of any Java {@link Object} passed to it.
+ * </p>
+ */
+@Component
+public final class ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson {
+
+    private final Gson gson;
+
+    public ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson() {
+        final GsonBuilder builder = new GsonBuilder();
+        builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter());
+        builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter());
+        builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
+        builder.setPrettyPrinting();
+
+        this.gson = builder.create();
+    }
+
+    public String serialize(final Object result) {
+        return this.gson.toJson(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromApiJsonDeserializer.java
new file mode 100644
index 0000000..fe27cc8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromApiJsonDeserializer.java
@@ -0,0 +1,27 @@
+/**
+ * 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.core.serialization;
+
+/**
+ * 
+ */
+public interface FromApiJsonDeserializer<T> {
+
+    T commandFromApiJson(final String json);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromCommandJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromCommandJsonDeserializer.java
new file mode 100644
index 0000000..7b9535c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromCommandJsonDeserializer.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.serialization;
+
+/**
+ * 
+ */
+public interface FromCommandJsonDeserializer<T> {
+
+    T commandFromCommandJson(final String json);
+
+    T commandFromCommandJson(final Long resourceId, final String json);
+
+    T commandFromCommandJson(final Long resourceId, final String json, final boolean makerCheckerApproval);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
new file mode 100644
index 0000000..af2507f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
@@ -0,0 +1,272 @@
+/**
+ * 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.core.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+@Primary
+@Component
+public class FromJsonHelper {
+
+    private final Gson gsonConverter;
+    private final JsonParserHelper helperDelegator;
+    private final JsonParser parser;
+
+    public FromJsonHelper() {
+        this.gsonConverter = new Gson();
+        this.helperDelegator = new JsonParserHelper();
+        this.parser = new JsonParser();
+    }
+
+    public Map<String, Boolean> extractMap(final Type typeOfMap, final String json) {
+        return this.gsonConverter.fromJson(json, typeOfMap);
+    }
+
+    public Map<String, String> extractDataMap(final Type typeOfMap, final String json) {
+        return this.gsonConverter.fromJson(json, typeOfMap);
+    }
+
+    public Map<String, Object> extractObjectMap(final Type typeOfMap, final String json) {
+        return this.gsonConverter.fromJson(json, typeOfMap);
+    }
+
+    public <T> T fromJson(final String json, final Class<T> classOfT) {
+        return this.gsonConverter.fromJson(json, classOfT);
+    }
+
+    public String toJson(final JsonElement jsonElement) {
+        return this.gsonConverter.toJson(jsonElement);
+    }
+
+    public String toJson(final Object object) {
+        return this.gsonConverter.toJson(object);
+    }
+
+    public void checkForUnsupportedParameters(final Type typeOfMap, final String json, final Set<String> supportedParams) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Map<String, Object> requestMap = this.gsonConverter.fromJson(json, typeOfMap);
+
+        final List<String> unsupportedParameterList = new ArrayList<>();
+        for (final String providedParameter : requestMap.keySet()) {
+            if (!supportedParams.contains(providedParameter)) {
+                unsupportedParameterList.add(providedParameter);
+            }
+        }
+
+        if (!unsupportedParameterList.isEmpty()) { throw new UnsupportedParameterException(unsupportedParameterList); }
+    }
+
+    public void checkForUnsupportedParameters(final JsonObject object, final Set<String> supportedParams) {
+        if (object == null) { throw new InvalidParameterException(); }
+
+        final Set<Entry<String, JsonElement>> entries = object.entrySet();
+        final List<String> unsupportedParameterList = new ArrayList<>();
+
+        for (final Entry<String, JsonElement> providedParameter : entries) {
+            if (!supportedParams.contains(providedParameter.getKey())) {
+                unsupportedParameterList.add(providedParameter.getKey());
+            }
+        }
+
+        if (!unsupportedParameterList.isEmpty()) { throw new UnsupportedParameterException(unsupportedParameterList); }
+    }
+
+    /**
+     * @param parentPropertyName
+     *            The full json path to this property,the value is appended to
+     *            the parameter name while generating an error message <br/>
+     *            Ex: property "name" in Object "person" would be named as
+     *            "person.name"
+     * @param object
+     * @param supportedParams
+     */
+    public void checkForUnsupportedNestedParameters(final String parentPropertyName, final JsonObject object,
+            final Set<String> supportedParams) {
+        try {
+            checkForUnsupportedParameters(object, supportedParams);
+        } catch (UnsupportedParameterException exception) {
+            List<String> unsupportedParameters = exception.getUnsupportedParameters();
+            List<String> updatedUnsupportedParameters = new ArrayList<>();
+            for (String unsupportedParameter : unsupportedParameters) {
+                String updatedUnsupportedParameter = parentPropertyName + "." + unsupportedParameter;
+                updatedUnsupportedParameters.add(updatedUnsupportedParameter);
+            }
+            throw new UnsupportedParameterException(updatedUnsupportedParameters);
+        }
+
+    }
+
+    public JsonElement parse(final String json) {
+
+        JsonElement parsedElement = null;
+        if (StringUtils.isNotBlank(json)) {
+            parsedElement = this.parser.parse(json);
+        }
+        return parsedElement;
+    }
+
+    public boolean parameterExists(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.parameterExists(parameterName, element);
+    }
+
+    public String extractStringNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractStringNamed(parameterName, element, new HashSet<String>());
+    }
+
+    public String extractStringNamed(final String parameterName, final JsonElement element, final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractStringNamed(parameterName, element, parametersPassedInRequest);
+    }
+
+    public Long extractLongNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractLongNamed(parameterName, element, new HashSet<String>());
+    }
+
+    public Long extractLongNamed(final String parameterName, final JsonElement element, final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractLongNamed(parameterName, element, parametersPassedInRequest);
+    }
+
+    public JsonArray extractJsonArrayNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractJsonArrayNamed(parameterName, element);
+    }
+
+    public String[] extractArrayNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractArrayNamed(parameterName, element, new HashSet<String>());
+    }
+
+    public String[] extractArrayNamed(final String parameterName, final JsonElement element, final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractArrayNamed(parameterName, element, parametersPassedInRequest);
+    }
+
+    public Boolean extractBooleanNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractBooleanNamed(parameterName, element, new HashSet<String>());
+    }
+
+    public Boolean extractBooleanNamed(final String parameterName, final JsonElement element, final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractBooleanNamed(parameterName, element, parametersPassedInRequest);
+    }
+
+    public MonthDay extractMonthDayNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractMonthDayNamed(parameterName, element);
+    }
+
+    public MonthDay extractMonthDayNamed(final String parameterName, final JsonObject object, final String dateFormat,
+            final Locale clientApplicationLocale) {
+        return this.helperDelegator.extractMonthDayNamed(parameterName, object, dateFormat, clientApplicationLocale);
+    }
+
+    public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractLocalDateNamed(parameterName, element, new HashSet<String>());
+    }
+
+    public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element, final String dateFormat,
+            final Locale locale) {
+        return this.helperDelegator.extractLocalDateNamed(parameterName, element.getAsJsonObject(), dateFormat, locale,
+                new HashSet<String>());
+    }
+
+    public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element,
+            final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractLocalDateNamed(parameterName, element, parametersPassedInRequest);
+    }
+
+    public LocalDate extractLocalDateAsArrayNamed(final String parameterName, final JsonElement element,
+            final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractLocalDateAsArrayNamed(parameterName, element, parametersPassedInRequest);
+    }
+
+    public BigDecimal extractBigDecimalWithLocaleNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractBigDecimalWithLocaleNamed(parameterName, element, new HashSet<String>());
+    }
+
+    public BigDecimal extractBigDecimalWithLocaleNamed(final String parameterName, final JsonElement element,
+            final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractBigDecimalWithLocaleNamed(parameterName, element, parametersPassedInRequest);
+    }
+
+    public BigDecimal extractBigDecimalNamed(final String parameterName, final JsonElement element, final Locale locale) {
+        return this.helperDelegator.extractBigDecimalNamed(parameterName, element.getAsJsonObject(), locale, new HashSet<String>());
+    }
+
+    public BigDecimal extractBigDecimalNamed(final String parameterName, final JsonElement element,
+            final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractBigDecimalNamed(parameterName, element.getAsJsonObject(), Locale.US, parametersPassedInRequest);
+    }
+
+    public Integer extractIntegerWithLocaleNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractIntegerWithLocaleNamed(parameterName, element.getAsJsonObject(), new HashSet<String>());
+    }
+
+    public Integer extractIntegerSansLocaleNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractIntegerSansLocaleNamed(parameterName, element.getAsJsonObject(), new HashSet<String>());
+    }
+
+    public Integer extractIntegerWithLocaleNamed(final String parameterName, final JsonElement element,
+            final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractIntegerWithLocaleNamed(parameterName, element.getAsJsonObject(), parametersPassedInRequest);
+    }
+
+    public Integer extractIntegerNamed(final String parameterName, final JsonElement element, final Locale locale) {
+        return this.helperDelegator.extractIntegerNamed(parameterName, element.getAsJsonObject(), locale, new HashSet<String>());
+    }
+
+    public Integer extractIntegerNamed(final String parameterName, final JsonElement element, final Set<String> parametersPassedInRequest) {
+        return this.helperDelegator.extractIntegerNamed(parameterName, element.getAsJsonObject(), Locale.US, parametersPassedInRequest);
+    }
+
+    public Locale extractLocaleParameter(final JsonObject element) {
+        return this.helperDelegator.extractLocaleParameter(element);
+    }
+
+    public String extractDateFormatParameter(final JsonObject element) {
+        return this.helperDelegator.extractDateFormatParameter(element);
+    }
+
+    public String extractMonthDayFormatParameter(final JsonObject element) {
+        return this.helperDelegator.extractMonthDayFormatParameter(element);
+    }
+
+    public Gson getGsonConverter() {
+        return this.gsonConverter;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java
new file mode 100644
index 0000000..173936a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.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.infrastructure.core.serialization;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
+import org.apache.fineract.infrastructure.core.api.JodaLocalDateAdapter;
+import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
+import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy;
+import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy;
+import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Helper class for serialization of java objects into JSON using google-gson.
+ */
+@Service
+public final class GoogleGsonSerializerHelper {
+
+    public Gson createGsonBuilder(final boolean prettyPrint) {
+        final GsonBuilder builder = new GsonBuilder();
+        builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter());
+        builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter());
+        builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
+        if (prettyPrint) {
+            builder.setPrettyPrinting();
+        }
+        return builder.create();
+    }
+
+    public Gson createGsonBuilderForPartialResponseFiltering(final boolean prettyPrint, final Set<String> responseParameters) {
+
+        final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters);
+
+        final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
+        builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter());
+        builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter());
+        builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
+        if (prettyPrint) {
+            builder.setPrettyPrinting();
+        }
+        return builder.create();
+    }
+
+    public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set<String> supportedParameters,
+            final boolean prettyPrint, final Set<String> responseParameters) {
+
+        final Set<String> parameterNamesToSkip = new HashSet<>();
+
+        if (!responseParameters.isEmpty()) {
+
+            // strip out all known support parameters from expected response to
+            // see if unsupported parameters requested for response.
+            final Set<String> differentParametersDetectedSet = new HashSet<>(responseParameters);
+            differentParametersDetectedSet.removeAll(supportedParameters);
+
+            if (!differentParametersDetectedSet.isEmpty()) { throw new UnsupportedParameterException(new ArrayList<>(
+                    differentParametersDetectedSet)); }
+
+            parameterNamesToSkip.addAll(supportedParameters);
+            parameterNamesToSkip.removeAll(responseParameters);
+        }
+
+        final ExclusionStrategy strategy = new ParameterListExclusionStrategy(parameterNamesToSkip);
+
+        final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
+        builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter());
+        builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter());
+        builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
+        if (prettyPrint) {
+            builder.setPrettyPrinting();
+        }
+        return builder.create();
+    }
+
+    public String serializedJsonFrom(final Gson serializer, final Object[] dataObjects) {
+        return serializer.toJson(dataObjects);
+    }
+
+    public String serializedJsonFrom(final Gson serializer, final Object singleDataObject) {
+        return serializer.toJson(singleDataObject);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
new file mode 100644
index 0000000..c70d805
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
@@ -0,0 +1,637 @@
+/**
+ * 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.core.serialization;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.joda.time.MonthDay;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.format.number.NumberFormatter;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+/**
+ * Helper class to extract values of json named attributes.
+ */
+public class JsonParserHelper {
+
+    public boolean parameterExists(final String parameterName, final JsonElement element) {
+        if (element == null) { return false; }
+        return element.getAsJsonObject().has(parameterName);
+    }
+
+    public Boolean extractBooleanNamed(final String parameterName, final JsonElement element, final Set<String> requestParamatersDetected) {
+        Boolean value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
+                requestParamatersDetected.add(parameterName);
+
+                final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
+                value = primitive.getAsBoolean();
+            }
+        }
+        return value;
+    }
+
+    public Long extractLongNamed(final String parameterName, final JsonElement element, final Set<String> parametersPassedInRequest) {
+        Long longValue = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
+                parametersPassedInRequest.add(parameterName);
+                final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
+                final String stringValue = primitive.getAsString();
+                if (StringUtils.isNotBlank(stringValue)) {
+                    longValue = Long.valueOf(stringValue);
+                }
+            }
+        }
+        return longValue;
+    }
+
+    public String extractStringNamed(final String parameterName, final JsonElement element, final Set<String> parametersPassedInRequest) {
+        String stringValue = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
+                parametersPassedInRequest.add(parameterName);
+                final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
+                final String valueAsString = primitive.getAsString();
+                if (StringUtils.isNotBlank(valueAsString)) {
+                    stringValue = valueAsString;
+                }
+            }
+        }
+        return stringValue;
+    }
+
+    public BigDecimal extractBigDecimalWithLocaleNamed(final String parameterName, final JsonElement element,
+            final Set<String> modifiedParameters) {
+        BigDecimal value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            final Locale locale = extractLocaleValue(object);
+            value = extractBigDecimalNamed(parameterName, object, locale, modifiedParameters);
+        }
+        return value;
+    }
+
+    public BigDecimal extractBigDecimalNamed(final String parameterName, final JsonObject element, final Locale locale,
+            final Set<String> modifiedParameters) {
+        BigDecimal value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
+                modifiedParameters.add(parameterName);
+                final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
+                final String valueAsString = primitive.getAsString();
+                if (StringUtils.isNotBlank(valueAsString)) {
+                    value = convertFrom(valueAsString, parameterName, locale);
+                }
+            }
+        }
+        return value;
+    }
+
+    public Integer extractIntegerWithLocaleNamed(final String parameterName, final JsonElement element, final Set<String> modifiedParameters) {
+        Integer value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            final Locale locale = extractLocaleValue(object);
+            value = extractIntegerNamed(parameterName, object, locale, modifiedParameters);
+        }
+        return value;
+    }
+
+    public Integer extractIntegerNamed(final String parameterName, final JsonElement element, final Locale locale,
+            final Set<String> modifiedParameters) {
+        Integer value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
+                modifiedParameters.add(parameterName);
+                final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
+                final String valueAsString = primitive.getAsString();
+                if (StringUtils.isNotBlank(valueAsString)) {
+                    value = convertToInteger(valueAsString, parameterName, locale);
+                }
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Method used to extract integers from unformatted strings. Ex: "1" ,
+     * "100002" etc
+     *
+     * Please note that this method does not support extracting Integers from
+     * locale specific formatted strings Ex "1,000" etc
+     *
+     * @param parameterName
+     * @param element
+     * @param parametersPassedInRequest
+     * @return
+     */
+    public Integer extractIntegerSansLocaleNamed(final String parameterName, final JsonElement element,
+            final Set<String> parametersPassedInRequest) {
+        Integer intValue = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
+                parametersPassedInRequest.add(parameterName);
+                final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
+                final String stringValue = primitive.getAsString();
+                if (StringUtils.isNotBlank(stringValue)) {
+                    intValue = convertToIntegerSanLocale(stringValue, parameterName);
+                }
+            }
+        }
+        return intValue;
+    }
+
+    public String extractDateFormatParameter(final JsonObject element) {
+        String value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+
+            final String dateFormatParameter = "dateFormat";
+            if (object.has(dateFormatParameter) && object.get(dateFormatParameter).isJsonPrimitive()) {
+                final JsonPrimitive primitive = object.get(dateFormatParameter).getAsJsonPrimitive();
+                value = primitive.getAsString();
+            }
+        }
+        return value;
+    }
+
+    public String extractMonthDayFormatParameter(final JsonObject element) {
+        String value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+
+            final String monthDayFormatParameter = "monthDayFormat";
+            if (object.has(monthDayFormatParameter) && object.get(monthDayFormatParameter).isJsonPrimitive()) {
+                final JsonPrimitive primitive = object.get(monthDayFormatParameter).getAsJsonPrimitive();
+                value = primitive.getAsString();
+            }
+        }
+        return value;
+    }
+
+    public Locale extractLocaleParameter(final JsonObject element) {
+        Locale clientApplicationLocale = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+
+            String locale = null;
+            final String localeParameter = "locale";
+            if (object.has(localeParameter) && object.get(localeParameter).isJsonPrimitive()) {
+                final JsonPrimitive primitive = object.get(localeParameter).getAsJsonPrimitive();
+                locale = primitive.getAsString();
+                clientApplicationLocale = localeFromString(locale);
+            }
+        }
+        return clientApplicationLocale;
+    }
+
+    public String[] extractArrayNamed(final String parameterName, final JsonElement element, final Set<String> parametersPassedInRequest) {
+        String[] arrayValue = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            if (object.has(parameterName)) {
+                parametersPassedInRequest.add(parameterName);
+                final JsonArray array = object.get(parameterName).getAsJsonArray();
+                arrayValue = new String[array.size()];
+                for (int i = 0; i < array.size(); i++) {
+                    arrayValue[i] = array.get(i).getAsString();
+                }
+            }
+        }
+        return arrayValue;
+    }
+
+    public JsonArray extractJsonArrayNamed(final String parameterName, final JsonElement element) {
+        JsonArray jsonArray = null;
+
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            if (object.has(parameterName)) {
+                jsonArray = object.get(parameterName).getAsJsonArray();
+            }
+        }
+
+        return jsonArray;
+    }
+
+    /**
+     * Used with the local date is in array format
+     */
+    public LocalDate extractLocalDateAsArrayNamed(final String parameterName, final JsonElement element,
+            final Set<String> parametersPassedInCommand) {
+        LocalDate value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+
+            if (object.has(parameterName) && object.get(parameterName).isJsonArray()) {
+
+                parametersPassedInCommand.add(parameterName);
+
+                final JsonArray dateArray = object.get(parameterName).getAsJsonArray();
+
+                final Integer year = dateArray.get(0).getAsInt();
+                final Integer month = dateArray.get(1).getAsInt();
+                final Integer day = dateArray.get(2).getAsInt();
+
+                value = new LocalDate().withYearOfEra(year).withMonthOfYear(month).withDayOfMonth(day);
+            }
+
+        }
+        return value;
+    }
+
+    public MonthDay extractMonthDayNamed(final String parameterName, final JsonElement element) {
+
+        MonthDay value = null;
+
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+
+            final String monthDayFormat = extractMonthDayFormatParameter(object);
+            final Locale clientApplicationLocale = extractLocaleParameter(object);
+            value = extractMonthDayNamed(parameterName, object, monthDayFormat, clientApplicationLocale);
+        }
+        return value;
+    }
+
+    public MonthDay extractMonthDayNamed(final String parameterName, final JsonObject element, final String dateFormat,
+            final Locale clientApplicationLocale) {
+        MonthDay value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+
+            if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
+
+                final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
+                final String valueAsString = primitive.getAsString();
+                if (StringUtils.isNotBlank(valueAsString)) {
+                    try {
+                        final DateTimeFormatter formatter = DateTimeFormat.forPattern(dateFormat).withLocale(clientApplicationLocale);
+                        value = MonthDay.parse(valueAsString.toLowerCase(clientApplicationLocale), formatter);
+                    } catch (final IllegalArgumentException e) {
+                        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                        final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.month.day",
+                                "The parameter " + parameterName + " is invalid based on the monthDayFormat: '" + dateFormat
+                                        + "' and locale: '" + clientApplicationLocale + "' provided:", parameterName, valueAsString,
+                                dateFormat);
+                        dataValidationErrors.add(error);
+
+                        throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                                dataValidationErrors);
+                    }
+                }
+            }
+
+        }
+        return value;
+    }
+
+    public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element,
+            final Set<String> parametersPassedInCommand) {
+
+        LocalDate value = null;
+
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+
+            final String dateFormat = extractDateFormatParameter(object);
+            final Locale clientApplicationLocale = extractLocaleParameter(object);
+            value = extractLocalDateNamed(parameterName, object, dateFormat, clientApplicationLocale, parametersPassedInCommand);
+        }
+        return value;
+    }
+
+    public LocalDate extractLocalDateNamed(final String parameterName, final JsonObject element, final String dateFormat,
+            final Locale clientApplicationLocale, final Set<String> parametersPassedInCommand) {
+        LocalDate value = null;
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+
+            if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
+
+                parametersPassedInCommand.add(parameterName);
+
+                final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
+                final String valueAsString = primitive.getAsString();
+                if (StringUtils.isNotBlank(valueAsString)) {
+                    value = convertFrom(valueAsString, parameterName, dateFormat, clientApplicationLocale);
+                }
+            }
+
+        }
+        return value;
+    }
+
+    public static LocalDate convertFrom(final String dateAsString, final String parameterName, final String dateFormat,
+            final Locale clientApplicationLocale) {
+
+        return convertDateTimeFrom(dateAsString, parameterName,
+                dateFormat, clientApplicationLocale).toLocalDate();
+    }
+
+    public static LocalDateTime convertDateTimeFrom(final String dateTimeAsString, final String parameterName,
+                                        final String dateTimeFormat, final Locale clientApplicationLocale) {
+
+        validateDateFormatAndLocale(parameterName, dateTimeFormat, clientApplicationLocale);
+        LocalDateTime eventLocalDateTime = null;
+        if (StringUtils.isNotBlank(dateTimeAsString)) {
+            try {
+                eventLocalDateTime = DateTimeFormat.forPattern(dateTimeFormat).withLocale(clientApplicationLocale)
+                        .parseLocalDateTime(dateTimeAsString.toLowerCase(clientApplicationLocale));
+            } catch (final IllegalArgumentException e) {
+                final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.dateFormat.format", "The parameter "
+                        + parameterName + " is invalid based on the dateFormat: '" + dateTimeFormat + "' and locale: '"
+                        + clientApplicationLocale + "' provided:", parameterName, eventLocalDateTime, dateTimeFormat);
+                dataValidationErrors.add(error);
+
+                throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                        dataValidationErrors);
+            }
+        }
+
+        return eventLocalDateTime;
+    }
+
+    private static void validateDateFormatAndLocale(final String parameterName, final String dateFormat,
+                                        final Locale clientApplicationLocale) {
+        if (StringUtils.isBlank(dateFormat) || clientApplicationLocale == null) {
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            if (StringUtils.isBlank(dateFormat)) {
+                final String defaultMessage = new StringBuilder("The parameter '" + parameterName
+                        + "' requires a 'dateFormat' parameter to be passed with it.").toString();
+                final ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.dateFormat.parameter",
+                        defaultMessage, parameterName);
+                dataValidationErrors.add(error);
+            }
+            if (clientApplicationLocale == null) {
+                final String defaultMessage = new StringBuilder("The parameter '" + parameterName
+                        + "' requires a 'locale' parameter to be passed with it.").toString();
+                final ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.locale.parameter", defaultMessage,
+                        parameterName);
+                dataValidationErrors.add(error);
+            }
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+
+    }
+
+    public Integer convertToInteger(final String numericalValueFormatted, final String parameterName, final Locale clientApplicationLocale) {
+
+        if (clientApplicationLocale == null) {
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final String defaultMessage = new StringBuilder("The parameter '" + parameterName
+                    + "' requires a 'locale' parameter to be passed with it.").toString();
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.locale.parameter", defaultMessage,
+                    parameterName);
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+
+        try {
+            Integer number = null;
+
+            if (StringUtils.isNotBlank(numericalValueFormatted)) {
+
+                String source = numericalValueFormatted.trim();
+
+                final NumberFormat format = NumberFormat.getInstance(clientApplicationLocale);
+                final DecimalFormat df = (DecimalFormat) format;
+                final DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
+                df.setParseBigDecimal(true);
+
+                // http://bugs.sun.com/view_bug.do?bug_id=4510618
+                final char groupingSeparator = symbols.getGroupingSeparator();
+                if (groupingSeparator == '\u00a0') {
+                    source = source.replaceAll(" ", Character.toString('\u00a0'));
+                }
+
+                final Number parsedNumber = df.parse(source);
+
+                final double parsedNumberDouble = parsedNumber.doubleValue();
+                final int parsedNumberInteger = parsedNumber.intValue();
+
+                if (source.contains(Character.toString(symbols.getDecimalSeparator()))) { throw new ParseException(source, 0); }
+
+                if (!Double.valueOf(parsedNumberDouble).equals(Double.valueOf(Integer.valueOf(parsedNumberInteger)))) { throw new ParseException(
+                        source, 0); }
+
+                number = parsedNumber.intValue();
+            }
+
+            return number;
+        } catch (final ParseException e) {
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.integer.format", "The parameter "
+                    + parameterName + " has value: " + numericalValueFormatted + " which is invalid integer value for provided locale of ["
+                    + clientApplicationLocale.toString() + "].", parameterName, numericalValueFormatted, clientApplicationLocale);
+            error.setValue(numericalValueFormatted);
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+
+    public Integer convertToIntegerSanLocale(final String numericalValueFormatted, final String parameterName) {
+
+        try {
+            Integer number = null;
+
+            if (StringUtils.isNotBlank(numericalValueFormatted)) {
+                number = Integer.valueOf(numericalValueFormatted);
+            }
+
+            return number;
+        } catch (final NumberFormatException e) {
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.integer", "The parameter "
+                    + parameterName + " has value: " + numericalValueFormatted + " which is invalid integer.", parameterName,
+                    numericalValueFormatted);
+            error.setValue(numericalValueFormatted);
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+
+    public BigDecimal convertFrom(final String numericalValueFormatted, final String parameterName, final Locale clientApplicationLocale) {
+
+        if (clientApplicationLocale == null) {
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final String defaultMessage = new StringBuilder("The parameter '" + parameterName
+                    + "' requires a 'locale' parameter to be passed with it.").toString();
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.locale.parameter", defaultMessage,
+                    parameterName);
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+
+        try {
+            BigDecimal number = null;
+
+            if (StringUtils.isNotBlank(numericalValueFormatted)) {
+
+                String source = numericalValueFormatted.trim();
+
+                final NumberFormat format = NumberFormat.getNumberInstance(clientApplicationLocale);
+                final DecimalFormat df = (DecimalFormat) format;
+                final DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
+                // http://bugs.sun.com/view_bug.do?bug_id=4510618
+                final char groupingSeparator = symbols.getGroupingSeparator();
+                if (groupingSeparator == '\u00a0') {
+                    source = source.replaceAll(" ", Character.toString('\u00a0'));
+                }
+
+                final NumberFormatter numberFormatter = new NumberFormatter();
+                final Number parsedNumber = numberFormatter.parse(source, clientApplicationLocale);
+                if (parsedNumber instanceof BigDecimal) {
+                    number = (BigDecimal) parsedNumber;
+                } else {
+                    number = BigDecimal.valueOf(Double.valueOf(parsedNumber.doubleValue()));
+                }
+            }
+
+            return number;
+        } catch (final ParseException e) {
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.decimal.format", "The parameter "
+                    + parameterName + " has value: " + numericalValueFormatted + " which is invalid decimal value for provided locale of ["
+                    + clientApplicationLocale.toString() + "].", parameterName, numericalValueFormatted, clientApplicationLocale);
+            error.setValue(numericalValueFormatted);
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+
+    /*** TODO: Vishwas move all Locale related code to a separate Utils class ***/
+    public static Locale localeFromString(final String localeAsString) {
+
+        if (StringUtils.isBlank(localeAsString)) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.locale.format",
+                    "The parameter locale is invalid. It cannot be blank.", "locale");
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+
+        String languageCode = "";
+        String countryCode = "";
+        String variantCode = "";
+
+        final String[] localeParts = localeAsString.split("_");
+
+        if (localeParts != null && localeParts.length == 1) {
+            languageCode = localeParts[0];
+        }
+
+        if (localeParts != null && localeParts.length == 2) {
+            languageCode = localeParts[0];
+            countryCode = localeParts[1];
+        }
+
+        if (localeParts != null && localeParts.length == 3) {
+            languageCode = localeParts[0];
+            countryCode = localeParts[1];
+            variantCode = localeParts[2];
+        }
+
+        return localeFrom(languageCode, countryCode, variantCode);
+    }
+
+    private static Locale localeFrom(final String languageCode, final String courntryCode, final String variantCode) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final List<String> allowedLanguages = Arrays.asList(Locale.getISOLanguages());
+        if (!allowedLanguages.contains(languageCode.toLowerCase())) {
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.locale.format",
+                    "The parameter locale has an invalid language value " + languageCode + " .", "locale", languageCode);
+            dataValidationErrors.add(error);
+        }
+
+        if (StringUtils.isNotBlank(courntryCode.toUpperCase())) {
+            final List<String> allowedCountries = Arrays.asList(Locale.getISOCountries());
+            if (!allowedCountries.contains(courntryCode)) {
+                final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.locale.format",
+                        "The parameter locale has an invalid country value " + courntryCode + " .", "locale", courntryCode);
+                dataValidationErrors.add(error);
+            }
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+
+        return new Locale(languageCode.toLowerCase(), courntryCode.toUpperCase(), variantCode);
+    }
+
+    private Locale extractLocaleValue(final JsonObject object) {
+        Locale clientApplicationLocale = null;
+        String locale = null;
+        if (object.has("locale") && object.get("locale").isJsonPrimitive()) {
+            final JsonPrimitive primitive = object.get("locale").getAsJsonPrimitive();
+            locale = primitive.getAsString();
+            clientApplicationLocale = localeFromString(locale);
+        }
+        return clientApplicationLocale;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ToApiJsonSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ToApiJsonSerializer.java
new file mode 100644
index 0000000..fb44292
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ToApiJsonSerializer.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.infrastructure.core.serialization;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+
+public interface ToApiJsonSerializer<T> {
+
+    String serialize(Object object);
+
+    String serializePretty(boolean prettyOn, Object object);
+
+    String serializeResult(Object object);
+
+    String serialize(ApiRequestJsonSerializationSettings settings, Collection<T> collection);
+
+    String serialize(ApiRequestJsonSerializationSettings settings, T single);
+
+    String serialize(ApiRequestJsonSerializationSettings settings, Page<T> singleObject);
+
+    // TODO: TECHDEBT - bottom three will be deprecated going forward to remove
+    // need for people to pass full list of supported parameters. It was only
+    // used in cases where the partial response features was used (fields=x,y,x)
+    // to report error if incorrect field was passed. From now on it will just
+    // ignore unknown fields and fail silently
+    String serialize(ApiRequestJsonSerializationSettings settings, Collection<T> collection, Set<String> supportedResponseParameters);
+
+    String serialize(ApiRequestJsonSerializationSettings settings, T single, Set<String> supportedResponseParameters);
+
+    String serialize(ApiRequestJsonSerializationSettings settings, Page<T> singleObject, Set<String> supportedResponseParameters);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/AbandonedConnectionCleanupShutdownListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/AbandonedConnectionCleanupShutdownListener.java
new file mode 100644
index 0000000..6365f1a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/AbandonedConnectionCleanupShutdownListener.java
@@ -0,0 +1,71 @@
+/**
+ * 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.core.service;
+
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.util.Enumeration;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.stereotype.Service;
+
+import com.mysql.jdbc.AbandonedConnectionCleanupThread;
+
+@Service
+public class AbandonedConnectionCleanupShutdownListener implements ApplicationListener<ContextClosedEvent> {
+
+    private final static Logger logger = LoggerFactory.getLogger(AbandonedConnectionCleanupShutdownListener.class);
+
+    /**
+     * @see JobRegisterServiceImpl#onApplicationEvent(ContextClosedEvent) doc
+     *      re. why we use ContextClosedEvent instead of ContextStoppedEvent
+     */
+    @Override
+    public void onApplicationEvent(@SuppressWarnings("unused") ContextClosedEvent event) {
+        shutDowncleanUpThreadAndDeregisterJDBCDriver();
+    }
+
+    private void shutDowncleanUpThreadAndDeregisterJDBCDriver() {
+        try {
+            AbandonedConnectionCleanupThread.shutdown();
+            logger.info("Shut-down of AbandonedConnectionCleanupThread successful");
+        } catch (Throwable t) {
+            logger.error("Exception occurred while shut-down of AbandonedConnectionCleanupThread", t);
+        }
+
+        // This manually deregisters JDBC driver, which prevents Tomcat 7 from
+        // complaining about memory leaks
+        Enumeration<Driver> drivers = DriverManager.getDrivers();
+        while (drivers.hasMoreElements()) {
+            Driver driver = drivers.nextElement();
+            try {
+                java.sql.DriverManager.deregisterDriver(driver);
+                logger.info("JDBC driver de-registered successfully");
+            } catch (Throwable t) {
+                logger.error("Exception occured while deristering jdbc driver", t);
+            }
+        }
+        try {
+            Thread.sleep(2000L);
+        } catch (Exception e) {}
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DataSourceForTenants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DataSourceForTenants.java
new file mode 100755
index 0000000..d2ba586
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DataSourceForTenants.java
@@ -0,0 +1,45 @@
+/**
+ * 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.core.service;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+/**
+ * Implementation that returns connection pool datasource for tenants database
+ */
+@Service
+public class DataSourceForTenants implements RoutingDataSourceService {
+
+    private final DataSource tenantDataSource;
+
+    @Autowired
+    public DataSourceForTenants(final @Qualifier("tenantDataSourceJndi") DataSource tenantDataSource) {
+        this.tenantDataSource = tenantDataSource;
+    }
+
+    @Override
+    public DataSource retrieveDataSource() {
+        return this.tenantDataSource;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
new file mode 100644
index 0000000..3d7edad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.service;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+public class DateUtils {
+
+    public static DateTimeZone getDateTimeZoneOfTenant() {
+        final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
+        DateTimeZone zone = null;
+        if (tenant != null) {
+            zone = DateTimeZone.forID(tenant.getTimezoneId());
+            TimeZone.getTimeZone(tenant.getTimezoneId());
+        }
+        return zone;
+    }
+
+    public static TimeZone getTimeZoneOfTenant() {
+        final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
+        TimeZone zone = null;
+        if (tenant != null) {
+            zone = TimeZone.getTimeZone(tenant.getTimezoneId());
+        }
+        return zone;
+    }
+
+    public static Date getDateOfTenant() {
+        return getLocalDateOfTenant().toDateTimeAtStartOfDay().toDate();
+    }
+
+    public static LocalDate getLocalDateOfTenant() {
+
+        LocalDate today = new LocalDate();
+
+        final DateTimeZone zone = getDateTimeZoneOfTenant();
+        if (zone != null) {
+            today = new LocalDate(zone);
+        }
+
+        return today;
+    }
+
+    public static LocalDateTime getLocalDateTimeOfTenant() {
+
+        LocalDateTime today = new LocalDateTime();
+
+        final DateTimeZone zone = getDateTimeZoneOfTenant();
+        if (zone != null) {
+            today = new LocalDateTime(zone);
+        }
+
+        return today;
+    }
+
+    public static LocalDate parseLocalDate(final String stringDate, final String pattern) {
+
+        try {
+            final DateTimeFormatter dateStringFormat = DateTimeFormat.forPattern(pattern);
+            dateStringFormat.withZone(getDateTimeZoneOfTenant());
+            final DateTime dateTime = dateStringFormat.parseDateTime(stringDate);
+            return dateTime.toLocalDate();
+        } catch (final IllegalArgumentException e) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.date.pattern", "The parameter date ("
+                    + stringDate + ") is invalid w.r.t. pattern " + pattern, "date", stringDate, pattern);
+            dataValidationErrors.add(error);
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+
+    public static String formatToSqlDate(final Date date) {
+        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+        df.setTimeZone(getTimeZoneOfTenant());
+        final String formattedSqlDate = df.format(date);
+        return formattedSqlDate;
+    }
+
+    public static boolean isDateInTheFuture(final LocalDate localDate) {
+        return localDate.isAfter(getLocalDateOfTenant());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java
new file mode 100644
index 0000000..9e86664
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java
@@ -0,0 +1,81 @@
+/**
+ * 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.core.service;
+
+import org.apache.commons.mail.DefaultAuthenticator;
+import org.apache.commons.mail.Email;
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.SimpleEmail;
+import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData;
+import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService;
+import org.apache.fineract.infrastructure.core.domain.EmailDetail;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GmailBackedPlatformEmailService implements PlatformEmailService {
+	
+	private final ExternalServicesPropertiesReadPlatformService externalServicesReadPlatformService;
+	
+	@Autowired
+	public GmailBackedPlatformEmailService(final ExternalServicesPropertiesReadPlatformService externalServicesReadPlatformService){
+		this.externalServicesReadPlatformService = externalServicesReadPlatformService;
+	}
+
+    @Override
+    public void sendToUserAccount(final EmailDetail emailDetail, final String unencodedPassword) {
+        final Email email = new SimpleEmail();
+        final SMTPCredentialsData smtpCredentialsData = this.externalServicesReadPlatformService.getSMTPCredentials();
+        final String authuserName = smtpCredentialsData.getUsername();
+
+        final String authuser = smtpCredentialsData.getUsername();
+        final String authpwd = smtpCredentialsData.getPassword();
+
+        // Very Important, Don't use email.setAuthentication()
+        email.setAuthenticator(new DefaultAuthenticator(authuser, authpwd));
+        email.setDebug(false); // true if you want to debug
+        email.setHostName(smtpCredentialsData.getHost());
+        try {
+        	if(smtpCredentialsData.isUseTLS()){
+        		email.getMailSession().getProperties().put("mail.smtp.starttls.enable", "true");
+        	}
+        	email.setFrom(authuser, authuserName);
+
+            final StringBuilder subjectBuilder = new StringBuilder().append("Fineract Prototype Demo: ").append(emailDetail.getContactName())
+                    .append(" user account creation.");
+
+            email.setSubject(subjectBuilder.toString());
+
+            final String sendToEmail = emailDetail.getAddress();
+
+            final StringBuilder messageBuilder = new StringBuilder().append("You are receiving this email as your email account: ")
+                    .append(sendToEmail).append(" has being used to create a user account for an organisation named [")
+                    .append(emailDetail.getOrganisationName()).append("] on Fineract Prototype Demo.")
+                    .append("You can login using the following credentials: username: ").append(emailDetail.getUsername())
+                    .append(" password: ").append(unencodedPassword);
+
+            email.setMsg(messageBuilder.toString());
+
+            email.addTo(sendToEmail, emailDetail.getContactName());
+            email.send();
+        } catch (final EmailException e) {
+            throw new PlatformEmailSendException(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/Page.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/Page.java
new file mode 100644
index 0000000..d571aef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/Page.java
@@ -0,0 +1,40 @@
+/**
+ * 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.core.service;
+
+import java.util.List;
+
+public class Page<E> {
+
+    private final int totalFilteredRecords;
+    private final List<E> pageItems;
+
+    public Page(final List<E> pageItems, final int totalFilteredRecords) {
+        this.pageItems = pageItems;
+        this.totalFilteredRecords = totalFilteredRecords;
+    }
+
+    public int getTotalFilteredRecords() {
+        return this.totalFilteredRecords;
+    }
+
+    public List<E> getPageItems() {
+        return this.pageItems;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java
new file mode 100644
index 0000000..827bacd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java
@@ -0,0 +1,39 @@
+/**
+ * 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.core.service;
+
+import java.util.List;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+
+public class PaginationHelper<E> {
+
+    public Page<E> fetchPage(final JdbcTemplate jt, final String sqlCountRows, final String sqlFetchRows, final Object args[],
+            final RowMapper<E> rowMapper) {
+
+        final List<E> items = jt.query(sqlFetchRows, args, rowMapper);
+
+        // determine how many rows are available
+        @SuppressWarnings("deprecation")
+        final int totalFilteredRecords = jt.queryForInt(sqlCountRows);
+
+        return new Page<>(items, totalFilteredRecords);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PlatformEmailSendException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PlatformEmailSendException.java
new file mode 100644
index 0000000..09b8ec7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PlatformEmailSendException.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.infrastructure.core.service;
+
+public class PlatformEmailSendException extends RuntimeException {
+
+    public PlatformEmailSendException(final Throwable e) {
+        super(e);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PlatformEmailService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PlatformEmailService.java
new file mode 100644
index 0000000..843b55d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PlatformEmailService.java
@@ -0,0 +1,27 @@
+/**
+ * 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.core.service;
+
+import org.apache.fineract.infrastructure.core.domain.EmailDetail;
+
+public interface PlatformEmailService {
+
+    void sendToUserAccount(EmailDetail emailDetail, String unencodedPassword);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSource.java
new file mode 100644
index 0000000..68af2fb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSource.java
@@ -0,0 +1,60 @@
+/**
+ * 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.core.service;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.datasource.AbstractDataSource;
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+import org.springframework.stereotype.Service;
+
+/**
+ * Based on springs {@link AbstractRoutingDataSource} idea, this is a
+ * {@link DataSource} that routes or delegates to another data source depending
+ * on the tenant details passed in the request.
+ * 
+ * The tenant details are process earlier and stored in a {@link ThreadLocal}.
+ * 
+ * The {@link RoutingDataSourceService} is responsible for returning the
+ * appropriate {@link DataSource} for the tenant of this request.
+ */
+@Service(value = "routingDataSource")
+public class RoutingDataSource extends AbstractDataSource {
+
+    @Autowired
+    private RoutingDataSourceServiceFactory dataSourceServiceFactory;
+
+    @Override
+    public Connection getConnection() throws SQLException {
+        return determineTargetDataSource().getConnection();
+    }
+
+    private DataSource determineTargetDataSource() {
+        return this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource();
+    }
+
+    @Override
+    public Connection getConnection(final String username, final String password) throws SQLException {
+        return determineTargetDataSource().getConnection(username, password);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSourceService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSourceService.java
new file mode 100644
index 0000000..a279aec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSourceService.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.service;
+
+import javax.sql.DataSource;
+
+/**
+ * A service for getting hold of the appropriate {@link DataSource} connection
+ * pool.
+ */
+public interface RoutingDataSourceService {
+
+    DataSource retrieveDataSource();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSourceServiceFactory.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSourceServiceFactory.java
new file mode 100755
index 0000000..a99a49f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSourceServiceFactory.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+/**
+ * Factory class to get data source service based on the details stored in
+ * {@link ThreadLocal} variable for this request
+ * 
+ * {@link ThreadLocalContextUtil} is used to retrieve the Context
+ * 
+ */
+@Component
+public class RoutingDataSourceServiceFactory {
+
+    @Autowired
+    private ApplicationContext applicationContext;
+
+    public RoutingDataSourceService determineDataSourceService() {
+        String serviceName = "tomcatJdbcDataSourcePerTenantService";
+        if (ThreadLocalContextUtil.CONTEXT_TENANTS.equalsIgnoreCase(ThreadLocalContextUtil.getDataSourceContext())) {
+            serviceName = "dataSourceForTenants";
+        }
+        return this.applicationContext.getBean(serviceName, RoutingDataSourceService.class);
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
new file mode 100755
index 0000000..67ed845
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
@@ -0,0 +1,431 @@
+/**
+ * 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.core.service;
+
+import org.apache.commons.lang.StringUtils;
+
+public final class SearchParameters {
+
+    private final String sqlSearch;
+    private final Long officeId;
+    private final String externalId;
+    private final String name;
+    private final String hierarchy;
+    private final String firstname;
+    private final String lastname;
+    private final Integer offset;
+    private final Integer limit;
+    private final String orderBy;
+    private final String sortOrder;
+    private final String accountNo;
+    private final String currencyCode;
+
+    private final Long staffId;
+
+    private final Long loanId;
+
+    private final Long savingsId;
+    private final Boolean orphansOnly;
+
+  //Provisning Entries Search Params
+    private final Long provisioningEntryId ;
+    private final Long productId ;
+    private final Long categoryId ;
+	private final boolean isSelfUser;
+    
+	public static SearchParameters from(final String sqlSearch, final Long officeId, final String externalId, final String name,
+            final String hierarchy) {
+        final Long staffId = null;
+        final String accountNo = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+        final Boolean orphansOnly = false;
+        final boolean isSelfUser = false;
+        return new SearchParameters(sqlSearch, officeId, externalId, name, hierarchy, null, null, null, null, null, null, staffId,
+                accountNo, loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
+    public static SearchParameters forClients(final String sqlSearch, final Long officeId, final String externalId,
+            final String displayName, final String firstname, final String lastname, final String hierarchy, final Integer offset,
+            final Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly, final boolean isSelfUser) {
+
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final String accountNo = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+
+        return new SearchParameters(sqlSearch, officeId, externalId, displayName, hierarchy, firstname, lastname, offset, maxLimitAllowed,
+                orderBy, sortOrder, staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
+    public static SearchParameters forGroups(final String sqlSearch, final Long officeId, final Long staffId, final String externalId,
+            final String name, final String hierarchy, final Integer offset, final Integer limit, final String orderBy,
+            final String sortOrder, final Boolean orphansOnly) {
+
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final String accountNo = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+        final boolean isSelfUser = false;
+
+        return new SearchParameters(sqlSearch, officeId, externalId, name, hierarchy, null, null, offset, maxLimitAllowed, orderBy,
+                sortOrder, staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
+    public static SearchParameters forOffices(final String orderBy, final String sortOrder) {
+        final Boolean orphansOnly = false;
+        final boolean isSelfUser = false;
+        return new SearchParameters(null, null, null, null, null, null, null, null, null, orderBy, sortOrder, null, null, null, null,
+                orphansOnly, isSelfUser);
+    }
+
+    public static SearchParameters forLoans(final String sqlSearch, final String externalId, final Integer offset, final Integer limit,
+            final String orderBy, final String sortOrder, final String accountNo) {
+
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+        final Boolean orphansOnly = false;
+        final boolean isSelfUser = false;
+
+        return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
+                staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
+    public static SearchParameters forJournalEntries(final Long officeId, final Integer offset, final Integer limit, final String orderBy,
+            final String sortOrder, final Long loanId, final Long savingsId) {
+
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final Boolean orphansOnly = false;
+        final boolean isSelfUser = false;
+
+        return new SearchParameters(null, officeId, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
+                null, loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
+    public static SearchParameters forJournalEntries(final Long officeId, final Integer offset, final Integer limit, final String orderBy,
+            final String sortOrder, final Long loanId, final Long savingsId, final String currencyCode) {
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final Boolean orphansOnly = false;
+
+        return new SearchParameters(null, officeId, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
+                null, loanId, savingsId, orphansOnly, currencyCode);
+    }
+
+    public static SearchParameters forPagination(final Integer offset, final Integer limit, final String orderBy, final String sortOrder) {
+
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+        final Boolean orphansOnly = false;
+        final boolean isSelfUser = false;
+
+        return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null,
+                loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
+    public static SearchParameters forPagination(final Integer offset, final Integer limit) {
+
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+        final Boolean orphansOnly = false;
+        final String orderBy = null;
+        final String sortOrder = null;
+        final boolean isSelfUser = false;
+
+        return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null,
+                loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
+    public final static SearchParameters forProvisioningEntries(final Long provisioningEntryId, final Long officeId, final Long productId, 
+            final Long categoryId, final Integer offset, final Integer limit) {
+        return new SearchParameters(provisioningEntryId, officeId, productId, categoryId, offset, limit) ;
+    }
+    
+    public static SearchParameters forSavings(final String sqlSearch, final String externalId, final Integer offset, final Integer limit,
+            final String orderBy, final String sortOrder) {
+
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final String accountNo = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+        final Boolean orphansOnly = false;
+        final boolean isSelfUser = false;
+
+        return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
+                staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
+    public static SearchParameters forAccountTransfer(final String sqlSearch, final String externalId, final Integer offset,
+            final Integer limit, final String orderBy, final String sortOrder) {
+
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final String accountNo = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+        final Boolean orphansOnly = false;
+        final boolean isSelfUser = false;
+
+        return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
+                staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
+    private SearchParameters(final String sqlSearch, final Long officeId, final String externalId, final String name,
+            final String hierarchy, final String firstname, final String lastname, final Integer offset, final Integer limit,
+            final String orderBy, final String sortOrder, final Long staffId, final String accountNo, final Long loanId,
+            final Long savingsId, final Boolean orphansOnly, boolean isSelfUser) {
+        this.sqlSearch = sqlSearch;
+        this.officeId = officeId;
+        this.externalId = externalId;
+        this.name = name;
+        this.hierarchy = hierarchy;
+        this.firstname = firstname;
+        this.lastname = lastname;
+        this.offset = offset;
+        this.limit = limit;
+        this.orderBy = orderBy;
+        this.sortOrder = sortOrder;
+        this.staffId = staffId;
+        this.accountNo = accountNo;
+        this.loanId = loanId;
+        this.savingsId = savingsId;
+        this.orphansOnly = orphansOnly;
+        this.currencyCode = null;
+        this.provisioningEntryId = null ;
+        this.productId = null ;
+        this.categoryId = null ;
+        this.isSelfUser = isSelfUser;
+      
+    }
+
+    private SearchParameters(final Long provisioningEntryId, final Long officeId, final Long productId, final Long categoryId,
+            final Integer offset, final Integer limit) {
+        this.sqlSearch = null;
+        this.externalId = null;
+        this.name = null;
+        this.hierarchy = null;
+        this.firstname = null;
+        this.lastname = null;
+        this.orderBy = null;
+        this.sortOrder = null;
+        this.staffId = null;
+        this.accountNo = null;
+        this.loanId = null;
+        this.savingsId = null;
+        this.orphansOnly = null;
+        this.currencyCode = null;
+        this.officeId = officeId;
+        this.offset = offset;
+        this.limit = limit;
+        this.provisioningEntryId = provisioningEntryId ;
+        this.productId = productId ;
+        this.categoryId = categoryId ;
+        this.isSelfUser = false;
+        
+    }
+    
+    public SearchParameters(final String sqlSearch, final Long officeId, final String externalId, final String name, final String hierarchy,
+            final String firstname, final String lastname, final Integer offset, final Integer limit, final String orderBy,
+            final String sortOrder, final Long staffId, final String accountNo, final Long loanId, final Long savingsId,
+            final Boolean orphansOnly, final String currencyCode) {
+        this.sqlSearch = sqlSearch;
+        this.officeId = officeId;
+        this.externalId = externalId;
+        this.name = name;
+        this.hierarchy = hierarchy;
+        this.firstname = firstname;
+        this.lastname = lastname;
+        this.offset = offset;
+        this.limit = limit;
+        this.orderBy = orderBy;
+        this.sortOrder = sortOrder;
+        this.staffId = staffId;
+        this.accountNo = accountNo;
+        this.loanId = loanId;
+        this.savingsId = savingsId;
+        this.orphansOnly = orphansOnly;
+        this.currencyCode = currencyCode;
+        this.provisioningEntryId = null ;
+        this.productId = null ;
+        this.categoryId = null ;
+        this.isSelfUser = false;
+    }
+
+    public boolean isOrderByRequested() {
+        return StringUtils.isNotBlank(this.orderBy);
+    }
+
+    public boolean isSortOrderProvided() {
+        return StringUtils.isNotBlank(this.sortOrder);
+    }
+
+    public static Integer getCheckedLimit(final Integer limit) {
+
+        final Integer maxLimitAllowed = 200;
+        // default to max limit first off
+        Integer checkedLimit = maxLimitAllowed;
+
+        if (limit != null && limit > 0) {
+            checkedLimit = limit;
+        } else if (limit != null) {
+            // unlimited case: limit provided and 0 or less
+            checkedLimit = null;
+        }
+
+        return checkedLimit;
+    }
+
+    public boolean isOfficeIdPassed() {
+        return this.officeId != null && this.officeId != 0;
+    }
+
+    public boolean isCurrencyCodePassed() {
+        return this.currencyCode != null;
+    }
+
+    public boolean isLimited() {
+        return this.limit != null && this.limit.intValue() > 0;
+    }
+
+    public boolean isOffset() {
+        return this.offset != null;
+    }
+
+    public boolean isScopedByOfficeHierarchy() {
+        return StringUtils.isNotBlank(this.hierarchy);
+    }
+
+    public String getSqlSearch() {
+        return this.sqlSearch;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+
+    public String getExternalId() {
+        return this.externalId;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getHierarchy() {
+        return this.hierarchy;
+    }
+
+    public String getFirstname() {
+        return this.firstname;
+    }
+
+    public String getLastname() {
+        return this.lastname;
+    }
+
+    public Integer getOffset() {
+        return this.offset;
+    }
+
+    public Integer getLimit() {
+        return this.limit;
+    }
+
+    public String getOrderBy() {
+        return this.orderBy;
+    }
+
+    public String getSortOrder() {
+        return this.sortOrder;
+    }
+
+    public boolean isStaffIdPassed() {
+        return this.staffId != null && this.staffId != 0;
+    }
+
+    public Long getStaffId() {
+        return this.staffId;
+    }
+
+    public String getAccountNo() {
+        return this.accountNo;
+    }
+
+    public boolean isLoanIdPassed() {
+        return this.loanId != null && this.loanId != 0;
+    }
+
+    public boolean isSavingsIdPassed() {
+        return this.savingsId != null && this.savingsId != 0;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public Long getSavingsId() {
+        return this.savingsId;
+    }
+
+    public Boolean isOrphansOnly() {
+        if (this.orphansOnly != null) { return this.orphansOnly; }
+        return false;
+    }
+    
+    public Long getProvisioningEntryId() {
+        return this.provisioningEntryId;
+    }
+    
+    public boolean isProvisioningEntryIdPassed() {
+        return this.provisioningEntryId != null && this.provisioningEntryId != 0 ;
+    }
+    
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    public boolean isProductIdPassed() {
+        return this.productId != null && this.productId != 0 ;
+    }
+    public Long getCategoryId() {
+        return this.categoryId;
+    }
+    
+    public boolean isCategoryIdPassed() {
+        return this.categoryId != null && this.categoryId != 0 ;
+    }
+
+    public boolean isSelfUser() {
+		return this.isSelfUser;
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java
new file mode 100644
index 0000000..a6a4445
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java
@@ -0,0 +1,91 @@
+/**
+ * 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.core.service;
+
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+
+import org.apache.fineract.infrastructure.core.boot.db.TenantDataSourcePortFixService;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
+import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import com.googlecode.flyway.core.Flyway;
+import com.googlecode.flyway.core.api.FlywayException;
+
+/**
+ * A service that picks up on tenants that are configured to auto-update their
+ * specific schema on application startup.
+ */
+@Service
+public class TenantDatabaseUpgradeService {
+
+    private final TenantDetailsService tenantDetailsService;
+    protected final DataSource tenantDataSource;
+    protected final TenantDataSourcePortFixService tenantDataSourcePortFixService;
+
+    @Autowired
+    public TenantDatabaseUpgradeService(final TenantDetailsService detailsService,
+            @Qualifier("tenantDataSourceJndi") final DataSource dataSource, TenantDataSourcePortFixService tenantDataSourcePortFixService) {
+        this.tenantDetailsService = detailsService;
+        this.tenantDataSource = dataSource;
+        this.tenantDataSourcePortFixService = tenantDataSourcePortFixService;
+    }
+
+    @PostConstruct
+    public void upgradeAllTenants() {
+        upgradeTenantDB();
+        final List<FineractPlatformTenant> tenants = this.tenantDetailsService.findAllTenants();
+        for (final FineractPlatformTenant tenant : tenants) {
+            final FineractPlatformTenantConnection connection = tenant.getConnection();
+            if (connection.isAutoUpdateEnabled()) {
+                final Flyway flyway = new Flyway();
+                flyway.setDataSource(connection.databaseURL(), connection.getSchemaUsername(), connection.getSchemaPassword());
+                flyway.setLocations("sql/migrations/core_db");
+                flyway.setOutOfOrder(true);
+                try {
+                    flyway.migrate();
+                } catch (FlywayException e) {
+                    String betterMessage = e.getMessage() + "; for Tenant DB URL: " + connection.databaseURL() + ", username: "
+                            + connection.getSchemaPassword();
+                    throw new FlywayException(betterMessage, e.getCause());
+                }
+            }
+        }
+    }
+
+    /**
+     * Initializes, and if required upgrades (using Flyway) the Tenant DB
+     * itself.
+     */
+    private void upgradeTenantDB() {
+        final Flyway flyway = new Flyway();
+        flyway.setDataSource(tenantDataSource);
+        flyway.setLocations("sql/migrations/list_db");
+        flyway.setOutOfOrder(true);
+        flyway.migrate();
+
+        tenantDataSourcePortFixService.fixUpTenantsSchemaServerPort();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/ThreadLocalContextUtil.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/ThreadLocalContextUtil.java
new file mode 100644
index 0000000..80b3ba8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/ThreadLocalContextUtil.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.infrastructure.core.service;
+
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.springframework.util.Assert;
+
+/**
+ *
+ */
+public class ThreadLocalContextUtil {
+
+    public static final String CONTEXT_TENANTS = "tenants";
+
+    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
+
+    private static final ThreadLocal<FineractPlatformTenant> tenantcontext = new ThreadLocal<>();
+    
+    private static final ThreadLocal<String> authTokenContext = new ThreadLocal<>();
+    
+    public static void setTenant(final FineractPlatformTenant tenant) {
+        Assert.notNull(tenant, "tenant cannot be null");
+        tenantcontext.set(tenant);
+    }
+
+    public static FineractPlatformTenant getTenant() {
+        return tenantcontext.get();
+    }
+
+    public static void clearTenant() {
+        tenantcontext.remove();
+    }
+
+    public static String getDataSourceContext() {
+        return contextHolder.get();
+    }
+
+    public static void setDataSourceContext(final String dataSourceContext) {
+        contextHolder.set(dataSourceContext);
+    }
+
+    public static void clearDataSourceContext() {
+        contextHolder.remove();
+    }
+    
+    public static void setAuthToken(final String authToken) {
+    	authTokenContext.set(authToken);
+    }
+
+    public static String getAuthToken() {
+        return authTokenContext.get();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TomcatJdbcDataSourcePerTenantService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TomcatJdbcDataSourcePerTenantService.java
new file mode 100644
index 0000000..dc4fa7f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TomcatJdbcDataSourcePerTenantService.java
@@ -0,0 +1,123 @@
+/**
+ * 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.core.service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
+import org.apache.tomcat.jdbc.pool.PoolConfiguration;
+import org.apache.tomcat.jdbc.pool.PoolProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+/**
+ * Implementation that returns a new or existing tomcat 7 jdbc connection pool
+ * datasource based on the tenant details stored in a {@link ThreadLocal}
+ * variable for this request.
+ * 
+ * {@link ThreadLocalContextUtil} is used to retrieve the
+ * {@link FineractPlatformTenant} for the request.
+ */
+@Service
+public class TomcatJdbcDataSourcePerTenantService implements RoutingDataSourceService {
+
+    private final Map<Long, DataSource> tenantToDataSourceMap = new HashMap<>(1);
+    private final DataSource tenantDataSource;
+
+    @Autowired
+    public TomcatJdbcDataSourcePerTenantService(final @Qualifier("tenantDataSourceJndi") DataSource tenantDataSource) {
+        this.tenantDataSource = tenantDataSource;
+    }
+
+    @Override
+    public DataSource retrieveDataSource() {
+
+        // default to tenant database datasource
+        DataSource tenantDataSource = this.tenantDataSource;
+
+        final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant(); 
+        if (tenant != null) {
+            final FineractPlatformTenantConnection tenantConnection = tenant.getConnection();
+
+            synchronized (this.tenantToDataSourceMap) {
+                // if tenantConnection information available switch to
+                // appropriate
+                // datasource
+                // for that tenant.
+                if (this.tenantToDataSourceMap.containsKey(tenantConnection.getConnectionId())) {
+                    tenantDataSource = this.tenantToDataSourceMap.get(tenantConnection.getConnectionId());
+                } else {
+                    tenantDataSource = createNewDataSourceFor(tenantConnection);
+                    this.tenantToDataSourceMap.put(tenantConnection.getConnectionId(), tenantDataSource);
+                }
+            }
+        }
+
+        return tenantDataSource;
+    }
+
+    // creates the data source oltp and report databases
+    private DataSource createNewDataSourceFor(final FineractPlatformTenantConnection tenantConnectionObj) {
+        // see
+        // http://www.tomcatexpert.com/blog/2010/04/01/configuring-jdbc-pool-high-concurrency
+
+        // see also org.apache.fineract.DataSourceProperties.setDefaults()
+
+        final String jdbcUrl = tenantConnectionObj.databaseURL();
+        final PoolConfiguration poolConfiguration = new PoolProperties();
+        poolConfiguration.setDriverClassName("com.mysql.jdbc.Driver");
+        poolConfiguration.setName(tenantConnectionObj.getSchemaName() + "_pool");
+        poolConfiguration.setUrl(jdbcUrl);
+        poolConfiguration.setUsername(tenantConnectionObj.getSchemaUsername());
+        poolConfiguration.setPassword(tenantConnectionObj.getSchemaPassword());
+
+        poolConfiguration.setInitialSize(tenantConnectionObj.getInitialSize());
+
+        poolConfiguration.setTestOnBorrow(tenantConnectionObj.isTestOnBorrow());
+        poolConfiguration.setValidationQuery("SELECT 1");
+        poolConfiguration.setValidationInterval(tenantConnectionObj.getValidationInterval());
+
+        poolConfiguration.setRemoveAbandoned(tenantConnectionObj.isRemoveAbandoned());
+        poolConfiguration.setRemoveAbandonedTimeout(tenantConnectionObj.getRemoveAbandonedTimeout());
+        poolConfiguration.setLogAbandoned(tenantConnectionObj.isLogAbandoned());
+        poolConfiguration.setAbandonWhenPercentageFull(tenantConnectionObj.getAbandonWhenPercentageFull());
+
+        /**
+         * Vishwas- Do we need to enable the below properties and add
+         * ResetAbandonedTimer for long running batch Jobs?
+         **/
+        // poolConfiguration.setMaxActive(tenant.getMaxActive());
+        // poolConfiguration.setMinIdle(tenant.getMinIdle());
+        // poolConfiguration.setMaxIdle(tenant.getMaxIdle());
+
+        // poolConfiguration.setSuspectTimeout(tenant.getSuspectTimeout());
+        // poolConfiguration.setTimeBetweenEvictionRunsMillis(tenant.getTimeBetweenEvictionRunsMillis());
+        // poolConfiguration.setMinEvictableIdleTimeMillis(tenant.getMinEvictableIdleTimeMillis());
+
+        poolConfiguration.setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"
+                + "org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer;org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport");
+
+        return new org.apache.tomcat.jdbc.pool.DataSource(poolConfiguration);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java
new file mode 100644
index 0000000..4b7958c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java
@@ -0,0 +1,39 @@
+/**
+ * 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.dataqueries.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Created by Cieyou on 2/26/14.
+ */
+public class DataTableApiConstant {
+
+    public static final Integer CATEGORY_PPI = 200;
+    public static final Integer CATEGORY_DEFAULT = 100;
+
+    public static final String categoryParamName ="category";
+    public static final String localParamName = "locale";
+    public static final Set<String> REGISTER_PARAMS = new HashSet<>(Arrays.asList(categoryParamName,localParamName));
+
+    public static final String DATATABLE_RESOURCE_NAME ="dataTables";
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
new file mode 100644
index 0000000..1ab734b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
@@ -0,0 +1,297 @@
+/**
+ * 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.dataqueries.api;
+
+import java.util.List;
+
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+//import org.slf4j.Logger;
+
+@Path("/datatables")
+@Component
+@Scope("singleton")
+public class DatatablesApiResource {
+
+    private final PlatformSecurityContext context;
+    private final GenericDataService genericDataService;
+    private final ReadWriteNonCoreDataService readWriteNonCoreDataService;
+    private final ToApiJsonSerializer<GenericResultsetData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final static org.slf4j.Logger logger = LoggerFactory.getLogger(DatatablesApiResource.class);
+
+    @Autowired
+    public DatatablesApiResource(final PlatformSecurityContext context, final GenericDataService genericDataService,
+            final ReadWriteNonCoreDataService readWriteNonCoreDataService,
+            final ToApiJsonSerializer<GenericResultsetData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.genericDataService = genericDataService;
+        this.readWriteNonCoreDataService = readWriteNonCoreDataService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getDatatables(@QueryParam("apptable") final String apptable, @Context final UriInfo uriInfo) {
+
+        final List<DatatableData> result = this.readWriteNonCoreDataService.retrieveDatatableNames(apptable);
+
+        final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serializePretty(prettyPrint, result);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createDatatable(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createDBDatatable(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{datatableName}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateDatatable(@PathParam("datatableName") final String datatableName, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateDBDatatable(datatableName, apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{datatableName}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteDatatable(@PathParam("datatableName") final String datatableName, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteDBDatatable(datatableName, apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("register/{datatable}/{apptable}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String registerDatatable(@PathParam("datatable") final String datatable, @PathParam("apptable") final String apptable,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().registerDBDatatable(datatable, apptable)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("deregister/{datatable}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deregisterDatatable(@PathParam("datatable") final String datatable) {
+
+        this.readWriteNonCoreDataService.deregisterDatatable(datatable);
+
+        final CommandProcessingResult result = new CommandProcessingResultBuilder().withResourceIdAsString(datatable).build();
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{datatable}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getDatatable(@PathParam("datatable") final String datatable, @Context final UriInfo uriInfo) {
+
+        final DatatableData result = this.readWriteNonCoreDataService.retrieveDatatable(datatable);
+
+        final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serializePretty(prettyPrint, result);
+    }
+
+    @GET
+    @Path("{datatable}/{apptableId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getDatatable(@PathParam("datatable") final String datatable, @PathParam("apptableId") final Long apptableId,
+            @QueryParam("order") final String order, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasDatatableReadPermission(datatable);
+
+        final GenericResultsetData results = this.readWriteNonCoreDataService.retrieveDataTableGenericResultSet(datatable, apptableId,
+                order, null);
+
+        String json = "";
+        final boolean genericResultSet = ApiParameterHelper.genericResultSet(uriInfo.getQueryParameters());
+        if (genericResultSet) {
+            final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
+            json = this.toApiJsonSerializer.serializePretty(prettyPrint, results);
+        } else {
+            json = this.genericDataService.generateJsonFromGenericResultsetData(results);
+        }
+
+        return json;
+    }
+
+    @GET
+    @Path("{datatable}/{apptableId}/{datatableId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getDatatableManyEntry(@PathParam("datatable") final String datatable, @PathParam("apptableId") final Long apptableId,
+            @PathParam("datatableId") final Long datatableId, @QueryParam("order") final String order, @Context final UriInfo uriInfo) {
+
+        logger.debug("::1 we came in the getDatatbleManyEntry apiRessource method");
+
+        this.context.authenticatedUser().validateHasDatatableReadPermission(datatable);
+
+        final GenericResultsetData results = this.readWriteNonCoreDataService.retrieveDataTableGenericResultSet(datatable, apptableId,
+                order, datatableId);
+
+        String json = "";
+        final boolean genericResultSet = ApiParameterHelper.genericResultSet(uriInfo.getQueryParameters());
+        if (genericResultSet) {
+            final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
+            json = this.toApiJsonSerializer.serializePretty(prettyPrint, results);
+        } else {
+            json = this.genericDataService.generateJsonFromGenericResultsetData(results);
+        }
+
+        return json;
+    }
+
+    @POST
+    @Path("{datatable}/{apptableId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createDatatableEntry(@PathParam("datatable") final String datatable, @PathParam("apptableId") final Long apptableId,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createDatatable(datatable, apptableId, null) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{datatable}/{apptableId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateDatatableEntryOnetoOne(@PathParam("datatable") final String datatable,
+            @PathParam("apptableId") final Long apptableId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateDatatable(datatable, apptableId, null) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{datatable}/{apptableId}/{datatableId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateDatatableEntryOneToMany(@PathParam("datatable") final String datatable,
+            @PathParam("apptableId") final Long apptableId, @PathParam("datatableId") final Long datatableId,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateDatatable(datatable, apptableId, datatableId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{datatable}/{apptableId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteDatatableEntries(@PathParam("datatable") final String datatable, @PathParam("apptableId") final Long apptableId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteDatatable(datatable, apptableId, null) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{datatable}/{apptableId}/{datatableId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteDatatableEntries(@PathParam("datatable") final String datatable, @PathParam("apptableId") final Long apptableId,
+            @PathParam("datatableId") final Long datatableId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteDatatable(datatable, apptableId, datatableId) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/ReportsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/ReportsApiResource.java
new file mode 100644
index 0000000..77b3c07
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/ReportsApiResource.java
@@ -0,0 +1,163 @@
+/**
+ * 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.dataqueries.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.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.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
+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("/reports")
+@Component
+@Scope("singleton")
+public class ReportsApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "reportName", "reportType",
+            "reportSubType", "reportCategory", "description", "reportSql", "coreReport", "useReport", "reportParameters"));
+
+    private final String resourceNameForPermissions = "REPORT";
+    private final PlatformSecurityContext context;
+    private final ToApiJsonSerializer<ReportData> toApiJsonSerializer;
+    private final ReadReportingService readReportingService;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public ReportsApiResource(final PlatformSecurityContext context, final ReadReportingService readReportingService,
+            final ToApiJsonSerializer<ReportData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper) {
+        this.context = context;
+        this.readReportingService = readReportingService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveReportList(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<ReportData> result = this.readReportingService.retrieveReportList();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, result, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{id}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveReport(@PathParam("id") final Long id, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ReportData result = this.readReportingService.retrieveReport(id);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (settings.isTemplate()) {
+            result.appendedTemplate(this.readReportingService.getAllowedParameters());
+        }
+        return this.toApiJsonSerializer.serialize(settings, result, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOfficeTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ReportData result = new ReportData();
+        result.appendedTemplate(this.readReportingService.getAllowedParameters());
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, result, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createReport(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createReport().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{id}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateReport(@PathParam("id") final Long id, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateReport(id).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{id}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteReport(@PathParam("id") final Long id) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteReport(id).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java
new file mode 100644
index 0000000..b664d41
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java
@@ -0,0 +1,176 @@
+/**
+ * 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.dataqueries.api;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+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.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.StreamingOutput;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
+import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
+import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider;
+import org.apache.fineract.infrastructure.report.service.ReportingProcessService;
+import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+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("/runreports")
+@Component
+@Scope("singleton")
+public class RunreportsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final ToApiJsonSerializer<ReportData> toApiJsonSerializer;
+    private final ReadReportingService readExtraDataAndReportingService;
+    private final GenericDataService genericDataService;
+    private final ReportingProcessServiceProvider reportingProcessServiceProvider;
+
+    @Autowired
+    public RunreportsApiResource(final PlatformSecurityContext context, final ReadReportingService readExtraDataAndReportingService,
+            final GenericDataService genericDataService, final ToApiJsonSerializer<ReportData> toApiJsonSerializer,
+            final ReportingProcessServiceProvider reportingProcessServiceProvider) {
+        this.context = context;
+        this.readExtraDataAndReportingService = readExtraDataAndReportingService;
+        this.genericDataService = genericDataService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.reportingProcessServiceProvider = reportingProcessServiceProvider;
+    }
+
+    @GET
+    @Path("{reportName}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON, "text/csv", "application/vnd.ms-excel", "application/pdf", "text/html" })
+    public Response runReport(@PathParam("reportName") final String reportName, @Context final UriInfo uriInfo) {
+
+        final MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
+
+        final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
+        final boolean exportCsv = ApiParameterHelper.exportCsv(uriInfo.getQueryParameters());
+        final boolean parameterType = ApiParameterHelper.parameterType(uriInfo.getQueryParameters());
+        final boolean exportPdf = ApiParameterHelper.exportPdf(uriInfo.getQueryParameters());
+
+        checkUserPermissionForReport(reportName, parameterType);
+
+        String parameterTypeValue = null;
+        if (!parameterType) {
+            parameterTypeValue = "report";
+            String reportType = this.readExtraDataAndReportingService.getReportType(reportName);
+            ReportingProcessService reportingProcessService = this.reportingProcessServiceProvider.findReportingProcessService(reportType);
+            if (reportingProcessService != null) { return reportingProcessService.processRequest(reportName, queryParams); }
+        } else {
+            parameterTypeValue = "parameter";
+        }
+
+        // PDF format
+
+        if (exportPdf) {
+            final Map<String, String> reportParams = getReportParams(queryParams);
+            final String pdfFileName = this.readExtraDataAndReportingService
+                    .retrieveReportPDF(reportName, parameterTypeValue, reportParams);
+
+            final File file = new File(pdfFileName);
+
+            final ResponseBuilder response = Response.ok(file);
+            response.header("Content-Disposition", "attachment; filename=\"" + pdfFileName + "\"");
+            response.header("content-Type", "application/pdf");
+
+            return response.build();
+
+        }
+
+        if (!exportCsv) {
+            final Map<String, String> reportParams = getReportParams(queryParams);
+
+            final GenericResultsetData result = this.readExtraDataAndReportingService.retrieveGenericResultset(reportName,
+                    parameterTypeValue, reportParams);
+
+            String json = "";
+            final boolean genericResultSetIsPassed = ApiParameterHelper.genericResultSetPassed(uriInfo.getQueryParameters());
+            final boolean genericResultSet = ApiParameterHelper.genericResultSet(uriInfo.getQueryParameters());
+            if (genericResultSetIsPassed) {
+                if (genericResultSet) {
+                    json = this.toApiJsonSerializer.serializePretty(prettyPrint, result);
+                } else {
+                    json = this.genericDataService.generateJsonFromGenericResultsetData(result);
+                }
+            } else {
+                json = this.toApiJsonSerializer.serializePretty(prettyPrint, result);
+            }
+
+            return Response.ok().entity(json).type(MediaType.APPLICATION_JSON).build();
+        }
+
+        // CSV Export
+        final Map<String, String> reportParams = getReportParams(queryParams);
+        final StreamingOutput result = this.readExtraDataAndReportingService
+                .retrieveReportCSV(reportName, parameterTypeValue, reportParams);
+
+        return Response.ok().entity(result).type("text/csv")
+                .header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".csv").build();
+    }
+
+    private void checkUserPermissionForReport(final String reportName, final boolean parameterType) {
+
+        // Anyone can run a 'report' that is simply getting possible parameter
+        // (dropdown listbox) values.
+        if (!parameterType) {
+            final AppUser currentUser = this.context.authenticatedUser();
+            if (currentUser.hasNotPermissionForReport(reportName)) { throw new NoAuthorizationException("Not authorised to run report: "
+                    + reportName); }
+        }
+    }
+
+    private Map<String, String> getReportParams(final MultivaluedMap<String, String> queryParams) {
+
+        final Map<String, String> reportParams = new HashMap<>();
+        final Set<String> keys = queryParams.keySet();
+        String pKey;
+        String pValue;
+        for (final String k : keys) {
+
+            if (k.startsWith("R_")) {
+                pKey = "${" + k.substring(2) + "}";
+                pValue = queryParams.get(k).get(0);
+                reportParams.put(pKey, pValue);
+            }
+        }
+        return reportParams;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DataTableValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DataTableValidator.java
new file mode 100644
index 0000000..f8172db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DataTableValidator.java
@@ -0,0 +1,71 @@
+/**
+ * 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.dataqueries.data;
+
+import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.CATEGORY_DEFAULT;
+import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.CATEGORY_PPI;
+import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.DATATABLE_RESOURCE_NAME;
+import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.REGISTER_PARAMS;
+import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.categoryParamName;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+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 class DataTableValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public DataTableValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateDataTableRegistration(final String json) {
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, REGISTER_PARAMS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(DATATABLE_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(categoryParamName, element)) {
+
+            final Integer category = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(categoryParamName, element);
+            Object[] objectArray = new Integer[] { CATEGORY_PPI, CATEGORY_DEFAULT };
+            baseDataValidator.reset().parameter(categoryParamName).value(category).isOneOfTheseValues(objectArray);
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DatatableData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DatatableData.java
new file mode 100644
index 0000000..f23b8ff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DatatableData.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.infrastructure.dataqueries.data;
+
+import java.util.List;
+
+/**
+ * Immutable data object representing datatable data.
+ */
+public class DatatableData {
+
+    @SuppressWarnings("unused")
+    private final String applicationTableName;
+    @SuppressWarnings("unused")
+    private final String registeredTableName;
+    @SuppressWarnings("unused")
+    private final List<ResultsetColumnHeaderData> columnHeaderData;
+
+
+    public static DatatableData create(final String applicationTableName, final String registeredTableName,
+            final List<ResultsetColumnHeaderData> columnHeaderData) {
+        return new DatatableData(applicationTableName, registeredTableName, columnHeaderData);
+    }
+
+    private DatatableData(final String applicationTableName, final String registeredTableName,
+            final List<ResultsetColumnHeaderData> columnHeaderData) {
+        this.applicationTableName = applicationTableName;
+        this.registeredTableName = registeredTableName;
+        this.columnHeaderData = columnHeaderData;
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/GenericResultsetData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/GenericResultsetData.java
new file mode 100644
index 0000000..d589aa9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/GenericResultsetData.java
@@ -0,0 +1,67 @@
+/**
+ * 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.dataqueries.data;
+
+import java.util.List;
+
+/**
+ * Immutable data object for generic resultset data.
+ */
+public final class GenericResultsetData {
+
+    private final List<ResultsetColumnHeaderData> columnHeaders;
+    private final List<ResultsetRowData> data;
+
+    public GenericResultsetData(final List<ResultsetColumnHeaderData> columnHeaders, final List<ResultsetRowData> resultsetDataRows) {
+        this.columnHeaders = columnHeaders;
+        this.data = resultsetDataRows;
+    }
+
+    public List<ResultsetColumnHeaderData> getColumnHeaders() {
+        return this.columnHeaders;
+    }
+
+    public List<ResultsetRowData> getData() {
+        return this.data;
+    }
+
+    public String getColTypeOfColumnNamed(final String columnName) {
+
+        String colType = null;
+        for (final ResultsetColumnHeaderData columnHeader : this.columnHeaders) {
+            if (columnHeader.isNamed(columnName)) {
+                colType = columnHeader.getColumnType();
+            }
+        }
+
+        return colType;
+    }
+
+    public boolean hasNoEntries() {
+        return this.data.isEmpty();
+    }
+
+    public boolean hasEntries() {
+        return !hasNoEntries();
+    }
+
+    public boolean hasMoreThanOneEntry() {
+        return this.data.size() > 1;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ReportData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ReportData.java
new file mode 100644
index 0000000..c55602b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ReportData.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.dataqueries.data;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+final public class ReportData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String reportName;
+    @SuppressWarnings("unused")
+    private final String reportType;
+    @SuppressWarnings("unused")
+    private final String reportSubType;
+    @SuppressWarnings("unused")
+    private final String reportCategory;
+    @SuppressWarnings("unused")
+    private final String description;
+    @SuppressWarnings("unused")
+    private final String reportSql;
+    @SuppressWarnings("unused")
+    private final Boolean coreReport;
+    @SuppressWarnings("unused")
+    private final Boolean useReport;
+    @SuppressWarnings("unused")
+    private final Collection<ReportParameterData> reportParameters;
+
+    @SuppressWarnings("unused")
+    private List<String> allowedReportTypes;
+    @SuppressWarnings("unused")
+    private List<String> allowedReportSubTypes;
+    @SuppressWarnings("unused")
+    private Collection<ReportParameterData> allowedParameters;
+
+    public ReportData(final Long id, final String reportName, final String reportType, final String reportSubType,
+            final String reportCategory, final String description, final String reportSql, final Boolean coreReport,
+            final Boolean useReport, final Collection<ReportParameterData> reportParameters) {
+        this.id = id;
+        this.reportName = reportName;
+        this.reportType = reportType;
+        this.reportSubType = reportSubType;
+        this.reportCategory = reportCategory;
+        this.description = description;
+        this.reportParameters = reportParameters;
+        this.reportSql = reportSql;
+        this.coreReport = coreReport;
+        this.useReport = useReport;
+        this.allowedReportTypes = null;
+        this.allowedReportSubTypes = null;
+        this.allowedParameters = null;
+    }
+
+    public ReportData() {
+        this.id = null;
+        this.reportName = null;
+        this.reportType = null;
+        this.reportSubType = null;
+        this.reportCategory = null;
+        this.description = null;
+        this.reportParameters = null;
+        this.reportSql = null;
+        this.coreReport = null;
+        this.useReport = null;
+        this.allowedReportTypes = null;
+        this.allowedReportSubTypes = null;
+        this.allowedParameters = null;
+    }
+
+    public void appendedTemplate(final Collection<ReportParameterData> allowedParameters) {
+
+        final List<String> reportTypes = new ArrayList<>();
+        reportTypes.add("Table");
+        reportTypes.add("Pentaho");
+        reportTypes.add("Chart");
+        this.allowedReportTypes = reportTypes;
+
+        final List<String> reportSubTypes = new ArrayList<>();
+        reportSubTypes.add("Bar");
+        reportSubTypes.add("Pie");
+        this.allowedReportSubTypes = reportSubTypes;
+
+        this.allowedParameters = allowedParameters;
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ReportParameterData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ReportParameterData.java
new file mode 100644
index 0000000..baf81f0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ReportParameterData.java
@@ -0,0 +1,39 @@
+/**
+ * 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.dataqueries.data;
+
+/* used to show list of parameters used by a report and also for getting a list of parameters available (the reportParameterName is left null */
+final public class ReportParameterData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final Long parameterId;
+    @SuppressWarnings("unused")
+    private final String parameterName;
+    @SuppressWarnings("unused")
+    private final String reportParameterName;
+
+    public ReportParameterData(final Long id, final Long parameterId, final String reportParameterName, final String parameterName) {
+        this.id = id;
+        this.parameterId = parameterId;
+        this.parameterName = parameterName;
+        this.reportParameterName = reportParameterName;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ReportParameterJoinData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ReportParameterJoinData.java
new file mode 100644
index 0000000..bafe0b0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ReportParameterJoinData.java
@@ -0,0 +1,108 @@
+/**
+ * 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.dataqueries.data;
+
+final public class ReportParameterJoinData {
+
+    private final Long reportId;
+    private final String reportName;
+    private final String reportType;
+    private final String reportSubType;
+    private final String reportCategory;
+    private final String description;
+    private final String reportSql;
+    private final Boolean coreReport;
+    private final Boolean useReport;
+
+    private final Long reportParameterId;
+    private final Long parameterId;
+    private final String reportParameterName;
+    private final String parameterName;
+
+    public ReportParameterJoinData(final Long reportId, final String reportName, final String reportType, final String reportSubType,
+            final String reportCategory, final String description, final String reportSql, final Boolean coreReport,
+            final Boolean useReport, final Long reportParameterId, final Long parameterId, final String reportParameterName,
+            final String parameterName) {
+        this.reportId = reportId;
+        this.reportName = reportName;
+        this.reportType = reportType;
+        this.reportSubType = reportSubType;
+        this.reportCategory = reportCategory;
+        this.description = description;
+        this.reportSql = reportSql;
+        this.coreReport = coreReport;
+        this.useReport = useReport;
+        this.reportParameterId = reportParameterId;
+        this.parameterId = parameterId;
+        this.reportParameterName = reportParameterName;
+        this.parameterName = parameterName;
+    }
+
+    public Long getReportId() {
+        return this.reportId;
+    }
+
+    public String getReportName() {
+        return this.reportName;
+    }
+
+    public String getReportType() {
+        return this.reportType;
+    }
+
+    public String getReportSubType() {
+        return this.reportSubType;
+    }
+
+    public String getReportCategory() {
+        return this.reportCategory;
+    }
+
+    public String getReportSql() {
+        return this.reportSql;
+    }
+
+    public Boolean getCoreReport() {
+        return this.coreReport;
+    }
+
+    public Boolean getUseReport() {
+        return this.useReport;
+    }
+
+    public Long getReportParameterId() {
+        return this.reportParameterId;
+    }
+
+    public Long getParameterId() {
+        return this.parameterId;
+    }
+
+    public String getReportParameterName() {
+        return this.reportParameterName;
+    }
+
+    public String getParameterName() {
+        return this.parameterName;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java
new file mode 100644
index 0000000..1418689
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java
@@ -0,0 +1,269 @@
+/**
+ * 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.dataqueries.data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+
+/**
+ * Immutable data object representing a resultset column.
+ */
+public final class ResultsetColumnHeaderData {
+
+    private final String columnName;
+    private final String columnType;
+    private final Long columnLength;
+    private final String columnDisplayType;
+    private final boolean isColumnNullable;
+    @SuppressWarnings("unused")
+    private final boolean isColumnPrimaryKey;
+
+    private final List<ResultsetColumnValueData> columnValues;
+    private final String columnCode;
+
+    public static ResultsetColumnHeaderData basic(final String columnName, final String columnType) {
+
+        final Long columnLength = null;
+        final boolean columnNullable = false;
+        final boolean columnIsPrimaryKey = false;
+        final List<ResultsetColumnValueData> columnValues = new ArrayList<>();
+        final String columnCode = null;
+        return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues,
+                columnCode);
+    }
+
+    public static ResultsetColumnHeaderData detailed(final String columnName, final String columnType, final Long columnLength,
+            final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues,
+            final String columnCode) {
+        return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues,
+                columnCode);
+    }
+
+    private ResultsetColumnHeaderData(final String columnName, final String columnType, final Long columnLength,
+            final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues,
+            final String columnCode) {
+        this.columnName = columnName;
+        this.columnType = columnType;
+        this.columnLength = columnLength;
+        this.isColumnNullable = columnNullable;
+        this.isColumnPrimaryKey = columnIsPrimaryKey;
+        this.columnValues = columnValues;
+        this.columnCode = columnCode;
+
+        String displayType = null;
+        if (this.columnCode == null) {
+            if (isString()) {
+                displayType = "STRING";
+            } else if (isAnyInteger()) {
+                displayType = "INTEGER";
+            } else if (isDate()) {
+                displayType = "DATE";
+            } else if (isDateTime()) {
+                displayType = "DATETIME";
+            } else if (isDecimal()) {
+                displayType = "DECIMAL";
+            } else if (isAnyText()) {
+                displayType = "TEXT";
+            } else if(isBit()) {
+                displayType = "BOOLEAN";
+            } else {
+                throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type", "Invalid Lookup Type:" + this.columnType
+                        + " - Column Name: " + this.columnName);
+            }
+
+        } else {
+            if (isInt()) {
+                displayType = "CODELOOKUP";
+            } else if (isVarchar()) {
+                displayType = "CODEVALUE";
+            } else {
+                throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type", "Invalid Lookup Type:" + this.columnType
+                        + " - Column Name: " + this.columnName);
+            }
+        }
+
+        this.columnDisplayType = displayType;
+    }
+
+    public boolean isNamed(final String columnName) {
+        return this.columnName.equalsIgnoreCase(columnName);
+    }
+
+    private boolean isAnyText() {
+        return isText() || isTinyText() || isMediumText() || isLongText();
+    }
+
+    private boolean isText() {
+        return "text".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isTinyText() {
+        return "tinytext".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isMediumText() {
+        return "mediumtext".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isLongText() {
+        return "longtext".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isDecimal() {
+        return "decimal".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isDate() {
+        return "date".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isDateTime() {
+        return "datetime".equalsIgnoreCase(this.columnType);
+    }
+
+    public boolean isString() {
+        return isVarchar() || isChar();
+    }
+
+    private boolean isChar() {
+        return "char".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isVarchar() {
+        return "varchar".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isAnyInteger() {
+        return isInt() || isSmallInt() || isTinyInt() || isMediumInt() || isBigInt();
+    }
+
+    private boolean isInt() {
+        return "int".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isSmallInt() {
+        return "smallint".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isTinyInt() {
+        return "tinyint".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isMediumInt() {
+        return "mediumint".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isBigInt() {
+        return "bigint".equalsIgnoreCase(this.columnType);
+    }
+
+    private boolean isBit() {
+        return "bit".equalsIgnoreCase(this.columnType);
+    }
+
+    public String getColumnName() {
+        return this.columnName;
+    }
+
+    public String getColumnType() {
+        return this.columnType;
+    }
+
+    public Long getColumnLength() {
+        return this.columnLength;
+    }
+
+    public String getColumnDisplayType() {
+        return this.columnDisplayType;
+    }
+
+    public boolean isDateDisplayType() {
+        return "DATE".equalsIgnoreCase(this.columnDisplayType);
+    }
+
+    public boolean isDateTimeDisplayType() {
+        return "DATETIME".equalsIgnoreCase(this.columnDisplayType);
+    }
+
+    public boolean isIntegerDisplayType() {
+        return "INTEGER".equalsIgnoreCase(this.columnDisplayType);
+    }
+
+    public boolean isDecimalDisplayType() {
+        return "DECIMAL".equalsIgnoreCase(this.columnDisplayType);
+    }
+
+    public boolean isBooleanDisplayType() {
+        return "BOOLEAN".equalsIgnoreCase(this.columnDisplayType);
+    }
+
+    public boolean isCodeValueDisplayType() {
+        return "CODEVALUE".equalsIgnoreCase(this.columnDisplayType);
+    }
+
+    public boolean isCodeLookupDisplayType() {
+        return "CODELOOKUP".equalsIgnoreCase(this.columnDisplayType);
+    }
+
+    public boolean isMandatory() {
+        return !isOptional();
+    }
+
+    public boolean isOptional() {
+        return this.isColumnNullable;
+    }
+
+    public boolean hasColumnValues() {
+        return !this.columnValues.isEmpty();
+    }
+
+    public boolean isColumnValueAllowed(final String match) {
+        boolean allowed = false;
+        for (final ResultsetColumnValueData allowedValue : this.columnValues) {
+            if (allowedValue.matches(match)) {
+                allowed = true;
+            }
+        }
+        return allowed;
+    }
+
+    public boolean isColumnValueNotAllowed(final String match) {
+        return !isColumnValueAllowed(match);
+    }
+
+    public boolean isColumnCodeNotAllowed(final Integer match) {
+        return !isColumnCodeAllowed(match);
+    }
+
+    public boolean isColumnCodeAllowed(final Integer match) {
+        boolean allowed = false;
+        for (final ResultsetColumnValueData allowedValue : this.columnValues) {
+            if (allowedValue.codeMatches(match)) {
+                allowed = true;
+            }
+        }
+        return allowed;
+    }
+
+    public String getColumnCode() {
+        return this.columnCode;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnValueData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnValueData.java
new file mode 100644
index 0000000..c42513b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnValueData.java
@@ -0,0 +1,51 @@
+/**
+ * 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.dataqueries.data;
+
+/**
+ * Immutable data object representing a possible value for a given resultset
+ * column.
+ */
+public class ResultsetColumnValueData {
+
+    private final int id;
+    private final String value;
+    @SuppressWarnings("unused")
+    private final Integer score;
+
+    public ResultsetColumnValueData(final int id, final String value) {
+        this.id = id;
+        this.value = value;
+        this.score = null;
+    }
+
+    public ResultsetColumnValueData(final int id, final String value, final int score) {
+        this.id = id;
+        this.value = value;
+        this.score = score;
+    }
+
+    public boolean matches(final String match) {
+        return match.equalsIgnoreCase(this.value);
+    }
+
+    public boolean codeMatches(final Integer match) {
+        return match.intValue() == this.id;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetRowData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetRowData.java
new file mode 100644
index 0000000..452934f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetRowData.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.dataqueries.data;
+
+import java.util.List;
+
+public class ResultsetRowData {
+
+    private final List<String> row;
+
+    public static ResultsetRowData create(final List<String> rowValues) {
+        return new ResultsetRowData(rowValues);
+    }
+
+    private ResultsetRowData(final List<String> rowValues) {
+        this.row = rowValues;
+    }
+
+    public List<String> getRow() {
+        return this.row;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/Report.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/Report.java
new file mode 100644
index 0000000..9668f4b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/Report.java
@@ -0,0 +1,281 @@
+/**
+ * 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.dataqueries.domain;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import com.google.gson.JsonArray;
+
+@Entity
+@Table(name = "stretchy_report", uniqueConstraints = { @UniqueConstraint(columnNames = { "report_name" }, name = "unq_report_name") })
+public final class Report extends AbstractPersistable<Long> {
+
+    @Column(name = "report_name", nullable = false, unique = true)
+    private String reportName;
+
+    @Column(name = "report_type", nullable = false)
+    private String reportType;
+
+    @Column(name = "report_subtype")
+    private String reportSubType;
+
+    @Column(name = "report_category")
+    private String reportCategory;
+
+    @Column(name = "description")
+    private String description;
+
+    @Column(name = "core_report", nullable = false)
+    private boolean coreReport;
+
+    // only defines if report should appear in reference app UI List
+    @Column(name = "use_report", nullable = false)
+    private boolean useReport;
+
+    @Column(name = "report_sql")
+    private String reportSql;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "report", orphanRemoval = true)
+    private final Set<ReportParameterUsage> reportParameterUsages = new HashSet<>();
+
+    public static Report fromJson(final JsonCommand command) {
+
+        String reportName = null;
+        String reportType = null;
+        String reportSubType = null;
+        String reportCategory = null;
+        String description = null;
+        boolean useReport = false;
+        String reportSql = null;
+
+        if (command.parameterExists("reportName")) {
+            reportName = command.stringValueOfParameterNamed("reportName");
+        }
+        if (command.parameterExists("reportType")) {
+            reportType = command.stringValueOfParameterNamed("reportType");
+        }
+        if (command.parameterExists("reportSubType")) {
+            reportSubType = command.stringValueOfParameterNamed("reportSubType");
+        }
+        if (command.parameterExists("reportCategory")) {
+            reportCategory = command.stringValueOfParameterNamed("reportCategory");
+        }
+        if (command.parameterExists("description")) {
+            description = command.stringValueOfParameterNamed("description");
+        }
+        if (command.parameterExists("useReport")) {
+            useReport = command.booleanPrimitiveValueOfParameterNamed("useReport");
+        }
+        if (command.parameterExists("reportSql")) {
+            reportSql = command.stringValueOfParameterNamed("reportSql");
+        }
+
+        return new Report(reportName, reportType, reportSubType, reportCategory, description, useReport, reportSql);
+    }
+
+    protected Report() {
+        //
+    }
+
+    public Report(final String reportName, final String reportType, final String reportSubType, final String reportCategory,
+            final String description, final boolean useReport, final String reportSql) {
+        this.reportName = reportName;
+        this.reportType = reportType;
+        this.reportSubType = reportSubType;
+        this.reportCategory = reportCategory;
+        this.description = description;
+        this.coreReport = false;
+        this.useReport = useReport;
+        this.reportSql = reportSql;
+        validate();
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(8);
+
+        String paramName = "reportName";
+        if (command.isChangeInStringParameterNamed(paramName, this.reportName)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            this.reportName = StringUtils.defaultIfEmpty(newValue, null);
+        }
+        paramName = "reportType";
+        if (command.isChangeInStringParameterNamed(paramName, this.reportType)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            this.reportType = StringUtils.defaultIfEmpty(newValue, null);
+        }
+        paramName = "reportSubType";
+        if (command.isChangeInStringParameterNamed(paramName, this.reportSubType)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            this.reportSubType = StringUtils.defaultIfEmpty(newValue, null);
+        }
+        paramName = "reportCategory";
+        if (command.isChangeInStringParameterNamed(paramName, this.reportCategory)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            this.reportCategory = StringUtils.defaultIfEmpty(newValue, null);
+        }
+        paramName = "description";
+        if (command.isChangeInStringParameterNamed(paramName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            this.description = StringUtils.defaultIfEmpty(newValue, null);
+        }
+        paramName = "useReport";
+        if (command.isChangeInBooleanParameterNamed(paramName, this.useReport)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            this.useReport = newValue;
+        }
+        paramName = "reportSql";
+        if (command.isChangeInStringParameterNamed(paramName, this.reportSql)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            this.reportSql = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String reportParametersParamName = "reportParameters";
+        if (command.hasParameter(reportParametersParamName)) {
+            final JsonArray jsonArray = command.arrayOfParameterNamed(reportParametersParamName);
+            if (jsonArray != null) {
+                actualChanges.put(reportParametersParamName, command.jsonFragment(reportParametersParamName));
+            }
+        }
+
+        validate();
+
+        if (!actualChanges.isEmpty()) {
+            if (isCoreReport()) {
+                for (final String key : actualChanges.keySet()) {
+                    if (!(key.equals("useReport"))) { throw new PlatformDataIntegrityException(
+                            "error.msg.only.use.report.can.be.updated.for.core.report",
+                            "Only the Use Report field can be updated for Core Reports", key); }
+                }
+            }
+        }
+
+        return actualChanges;
+    }
+
+    public boolean isCoreReport() {
+        return this.coreReport;
+    }
+
+    public ReportParameterUsage findReportParameterById(final Long reportParameterId) {
+        ReportParameterUsage reportParameterUsage = null;
+        for (final ReportParameterUsage rpu : this.reportParameterUsages) {
+            if (rpu.hasIdOf(reportParameterId)) {
+                reportParameterUsage = rpu;
+                break;
+            }
+        }
+        return reportParameterUsage;
+    }
+
+    private void validate() {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("report");
+
+        baseDataValidator.reset().parameter("reportName").value(this.reportName).notBlank().notExceedingLengthOf(100);
+
+        baseDataValidator.reset().parameter("reportType").value(this.reportType).notBlank()
+                .isOneOfTheseValues(new Object[] { "Table", "Pentaho", "Chart" });
+
+        baseDataValidator.reset().parameter("reportSubType").value(this.reportSubType).notExceedingLengthOf(20);
+
+        if (StringUtils.isNotBlank(this.reportType)) {
+            if (this.reportType.equals("Chart")) {
+                baseDataValidator.reset().parameter("reportSubType").value(this.reportSubType)
+                        .cantBeBlankWhenParameterProvidedIs("reportType", this.reportType)
+                        .isOneOfTheseValues(new Object[] { "Bar", "Pie" });
+            } else {
+                baseDataValidator.reset().parameter("reportSubType").value(this.reportSubType)
+                        .mustBeBlankWhenParameterProvidedIs("reportType", this.reportType);
+            }
+        }
+
+        baseDataValidator.reset().parameter("reportCategory").value(this.reportCategory).notExceedingLengthOf(45);
+
+        if (StringUtils.isNotBlank(this.reportType)) {
+            if ((this.reportType.equals("Table")) || (this.reportType.equals("Chart"))) {
+                baseDataValidator.reset().parameter("reportSql").value(this.reportSql)
+                        .cantBeBlankWhenParameterProvidedIs("reportType", this.reportType);
+            } else {
+                baseDataValidator.reset().parameter("reportSql").value(this.reportSql)
+                        .mustBeBlankWhenParameterProvidedIs("reportType", this.reportType);
+            }
+        }
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public String getReportName() {
+        return this.reportName;
+    }
+
+    public boolean update(final Set<ReportParameterUsage> newReportParameterUsages) {
+        if (newReportParameterUsages == null) { return false; }
+
+        boolean updated = false;
+
+        if (changeInReportParameters(newReportParameterUsages)) {
+            updated = true;
+            this.reportParameterUsages.clear();
+            this.reportParameterUsages.addAll(newReportParameterUsages);
+        }
+        return updated;
+    }
+
+    private boolean changeInReportParameters(final Set<ReportParameterUsage> newReportParameterUsages) {
+
+        if (!(this.reportParameterUsages.equals(newReportParameterUsages))) { return true; }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameter.java
new file mode 100644
index 0000000..6d5ba22
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameter.java
@@ -0,0 +1,37 @@
+/**
+ * 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.dataqueries.domain;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "stretchy_parameter")
+public class ReportParameter extends AbstractPersistable<Long> {
+
+    protected ReportParameter() {
+        //
+    }
+
+    public boolean hasIdOf(final Long id) {
+        return getId().equals(id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterRepository.java
new file mode 100644
index 0000000..ecfc27b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterRepository.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.infrastructure.dataqueries.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ReportParameterRepository extends JpaRepository<ReportParameter, Long>, JpaSpecificationExecutor<ReportParameter> {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsage.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsage.java
new file mode 100644
index 0000000..43f6744
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsage.java
@@ -0,0 +1,91 @@
+/**
+ * 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.dataqueries.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "stretchy_report_parameter")
+public final class ReportParameterUsage extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "report_id", nullable = false)
+    private Report report;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "parameter_id", nullable = false)
+    private ReportParameter parameter;
+
+    @Column(name = "report_parameter_name")
+    private String reportParameterName;
+
+    protected ReportParameterUsage() {
+        //
+    }
+
+    public ReportParameterUsage(final Report report, final ReportParameter parameter, final String reportParameterName) {
+        this.report = report;
+        this.parameter = parameter;
+        this.reportParameterName = reportParameterName;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final ReportParameterUsage rhs = (ReportParameterUsage) obj;
+        return new EqualsBuilder().appendSuper(super.equals(obj)) //
+                .append(getId(), rhs.getId()) //
+                .append(this.report.getId(), rhs.report.getId()) //
+                .append(this.parameter.getId(), rhs.parameter.getId()) //
+                .append(this.reportParameterName, rhs.reportParameterName) //
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(3, 5) //
+                .append(getId()) //
+                .append(this.report.getId()) //
+                .append(this.parameter.getId()) //
+                .append(this.reportParameterName) //
+                .toHashCode();
+    }
+
+    public boolean hasIdOf(final Long id) {
+        return getId().equals(id);
+    }
+
+    public boolean hasParameterIdOf(final Long parameterId) {
+        return this.parameter != null && this.parameter.hasIdOf(parameterId);
+    }
+
+    public void updateParameterName(final String parameterName) {
+        this.reportParameterName = parameterName;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsageRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsageRepository.java
new file mode 100644
index 0000000..8a49331
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsageRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.dataqueries.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ReportParameterUsageRepository extends JpaRepository<ReportParameterUsage, Long>,
+        JpaSpecificationExecutor<ReportParameterUsage> {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportRepository.java
new file mode 100644
index 0000000..2a1f843
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportRepository.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.infrastructure.dataqueries.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ReportRepository extends JpaRepository<Report, Long>, JpaSpecificationExecutor<Report> {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/DatatableNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/DatatableNotFoundException.java
new file mode 100644
index 0000000..d70b26d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/DatatableNotFoundException.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.infrastructure.dataqueries.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when datatable resources are not found.
+ */
+public class DatatableNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public DatatableNotFoundException(final String datatable, final Long id) {
+        super("error.msg.datatable.data.not.found", "Data not found for datatable: ", datatable + "  Id:" + id);
+    }
+
+    public DatatableNotFoundException(final String datatable) {
+        super("error.msg.datatable.not.found", "Datatable not found.", datatable);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/DatatableSystemErrorException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/DatatableSystemErrorException.java
new file mode 100644
index 0000000..c842aa0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/DatatableSystemErrorException.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.dataqueries.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class DatatableSystemErrorException extends AbstractPlatformDomainRuleException {
+
+    public DatatableSystemErrorException(final String msg) {
+        super("error.msg.datatable.system.error", msg, msg);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/ReportNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/ReportNotFoundException.java
new file mode 100644
index 0000000..c558110
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/ReportNotFoundException.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.infrastructure.dataqueries.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when report resources are not found.
+ */
+public class ReportNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ReportNotFoundException(final String reportSql) {
+        super("error.msg.report.name.not.found", "Reporting meta-data entry not found.", "Input sql: " + reportSql);
+    }
+
+    public ReportNotFoundException(final Long id) {
+        super("error.msg.report.parameter.id.invalid", "Report Parameter with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/ReportParameterNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/ReportParameterNotFoundException.java
new file mode 100644
index 0000000..402b028
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/exception/ReportParameterNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.dataqueries.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when report resources are not found.
+ */
+public class ReportParameterNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ReportParameterNotFoundException(final Long id) {
+        super("error.msg.report.parameter.id.invalid", "Report Parameter with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/CreateDatatableCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/CreateDatatableCommandHandler.java
new file mode 100644
index 0000000..78f2643
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/CreateDatatableCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.dataqueries.handler;
+
+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.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class CreateDatatableCommandHandler implements NewCommandSourceHandler {
+
+    private final ReadWriteNonCoreDataService writePlatformService;
+
+    @Autowired
+    public CreateDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createDatatable(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/CreateDatatableEntryCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/CreateDatatableEntryCommandHandler.java
new file mode 100644
index 0000000..056f88d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/CreateDatatableEntryCommandHandler.java
@@ -0,0 +1,57 @@
+/**
+ * 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.dataqueries.handler;
+
+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.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class CreateDatatableEntryCommandHandler implements NewCommandSourceHandler {
+
+    private final ReadWriteNonCoreDataService writePlatformService;
+
+    @Autowired
+    public CreateDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final CommandProcessingResult commandProcessingResult = this.writePlatformService.createNewDatatableEntry(command.entityName(),
+                command.entityId(), command);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(command.entityId()) //
+                .withOfficeId(commandProcessingResult.getOfficeId()) //
+                .withGroupId(commandProcessingResult.getGroupId()) //
+                .withClientId(commandProcessingResult.getClientId()) //
+                .withSavingsId(commandProcessingResult.getSavingsId()) //
+                .withLoanId(commandProcessingResult.getLoanId()) //
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/CreateReportCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/CreateReportCommandHandler.java
new file mode 100644
index 0000000..4d6dd52
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/CreateReportCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.dataqueries.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.infrastructure.dataqueries.service.ReportWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "REPORT", action = "CREATE")
+public class CreateReportCommandHandler implements NewCommandSourceHandler {
+
+    private final ReportWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateReportCommandHandler(final ReportWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createReport(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteDatatableCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteDatatableCommandHandler.java
new file mode 100644
index 0000000..59214cf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteDatatableCommandHandler.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.infrastructure.dataqueries.handler;
+
+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.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class DeleteDatatableCommandHandler implements NewCommandSourceHandler {
+
+    private final ReadWriteNonCoreDataService writePlatformService;
+
+    @Autowired
+    public DeleteDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final String datatableName = command.getUrl().replaceAll("/datatables/", "");
+
+        this.writePlatformService.deleteDatatable(datatableName);
+
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToManyDatatableEntryCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToManyDatatableEntryCommandHandler.java
new file mode 100644
index 0000000..b3d9f5f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToManyDatatableEntryCommandHandler.java
@@ -0,0 +1,57 @@
+/**
+ * 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.dataqueries.handler;
+
+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.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class DeleteOneToManyDatatableEntryCommandHandler implements NewCommandSourceHandler {
+
+    private final ReadWriteNonCoreDataService writePlatformService;
+
+    @Autowired
+    public DeleteOneToManyDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final CommandProcessingResult commandProcessingResult = this.writePlatformService.deleteDatatableEntry(command.entityName(),
+                command.entityId(), command.subentityId());
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(command.entityId()) //
+                .withOfficeId(commandProcessingResult.getOfficeId()) //
+                .withGroupId(commandProcessingResult.getGroupId()) //
+                .withClientId(commandProcessingResult.getClientId()) //
+                .withSavingsId(commandProcessingResult.getSavingsId()) //
+                .withLoanId(commandProcessingResult.getLoanId()) //
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToOneDatatableEntryCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToOneDatatableEntryCommandHandler.java
new file mode 100644
index 0000000..db23e58
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToOneDatatableEntryCommandHandler.java
@@ -0,0 +1,57 @@
+/**
+ * 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.dataqueries.handler;
+
+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.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class DeleteOneToOneDatatableEntryCommandHandler implements NewCommandSourceHandler {
+
+    private final ReadWriteNonCoreDataService writePlatformService;
+
+    @Autowired
+    public DeleteOneToOneDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final CommandProcessingResult commandProcessingResult = this.writePlatformService.deleteDatatableEntries(command.entityName(),
+                command.entityId());
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(command.entityId()) //
+                .withOfficeId(commandProcessingResult.getOfficeId()) //
+                .withGroupId(commandProcessingResult.getGroupId()) //
+                .withClientId(commandProcessingResult.getClientId()) //
+                .withSavingsId(commandProcessingResult.getSavingsId()) //
+                .withLoanId(commandProcessingResult.getLoanId()) //
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteReportCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteReportCommandHandler.java
new file mode 100644
index 0000000..eb08e44
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteReportCommandHandler.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.dataqueries.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.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.dataqueries.service.ReportWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "REPORT", action = "DELETE")
+public class DeleteReportCommandHandler implements NewCommandSourceHandler {
+
+    private final ReportWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteReportCommandHandler(final ReportWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        this.writePlatformService.deleteReport(command.entityId());
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(command.entityId()) //
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/RegisterDatatableCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/RegisterDatatableCommandHandler.java
new file mode 100644
index 0000000..1088b56
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/RegisterDatatableCommandHandler.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.infrastructure.dataqueries.handler;
+
+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.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class RegisterDatatableCommandHandler implements NewCommandSourceHandler {
+
+    private final ReadWriteNonCoreDataService writePlatformService;
+
+    @Autowired
+    public RegisterDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        this.writePlatformService.registerDatatable(command);
+
+        return new CommandProcessingResultBuilder().withResourceIdAsString(this.writePlatformService.getDataTableName(command.getUrl())).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateDatatableCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateDatatableCommandHandler.java
new file mode 100644
index 0000000..a103911
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateDatatableCommandHandler.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.infrastructure.dataqueries.handler;
+
+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.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class UpdateDatatableCommandHandler implements NewCommandSourceHandler {
+
+    private final ReadWriteNonCoreDataService writePlatformService;
+
+    @Autowired
+    public UpdateDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final String datatableName = command.getUrl().replaceAll("/datatables/", "");
+
+        this.writePlatformService.updateDatatable(datatableName, command);
+
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateOneToManyDatatableEntryCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateOneToManyDatatableEntryCommandHandler.java
new file mode 100644
index 0000000..96b58b1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateOneToManyDatatableEntryCommandHandler.java
@@ -0,0 +1,58 @@
+/**
+ * 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.dataqueries.handler;
+
+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.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class UpdateOneToManyDatatableEntryCommandHandler implements NewCommandSourceHandler {
+
+    private final ReadWriteNonCoreDataService writePlatformService;
+
+    @Autowired
+    public UpdateOneToManyDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final CommandProcessingResult commandProcessingResult = this.writePlatformService.updateDatatableEntryOneToMany(
+                command.entityName(), command.entityId(), command.subentityId(), command);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(command.entityId()) //
+                .withOfficeId(commandProcessingResult.getOfficeId()) //
+                .withGroupId(commandProcessingResult.getGroupId()) //
+                .withClientId(commandProcessingResult.getClientId()) //
+                .withSavingsId(commandProcessingResult.getSavingsId()) //
+                .withLoanId(commandProcessingResult.getLoanId()) //
+                .with(commandProcessingResult.getChanges()) //
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateOneToOneDatatableEntryCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateOneToOneDatatableEntryCommandHandler.java
new file mode 100644
index 0000000..42b3454
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateOneToOneDatatableEntryCommandHandler.java
@@ -0,0 +1,58 @@
+/**
+ * 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.dataqueries.handler;
+
+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.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class UpdateOneToOneDatatableEntryCommandHandler implements NewCommandSourceHandler {
+
+    private final ReadWriteNonCoreDataService writePlatformService;
+
+    @Autowired
+    public UpdateOneToOneDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final CommandProcessingResult commandProcessingResult = this.writePlatformService.updateDatatableEntryOneToOne(
+                command.entityName(), command.entityId(), command);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(command.entityId()) //
+                .withOfficeId(commandProcessingResult.getOfficeId()) //
+                .withGroupId(commandProcessingResult.getGroupId()) //
+                .withClientId(commandProcessingResult.getClientId()) //
+                .withSavingsId(commandProcessingResult.getSavingsId()) //
+                .withLoanId(commandProcessingResult.getLoanId()) //
+                .with(commandProcessingResult.getChanges()) //
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateReportCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateReportCommandHandler.java
new file mode 100644
index 0000000..5e3d241
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/UpdateReportCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.dataqueries.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.infrastructure.dataqueries.service.ReportWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "REPORT", action = "UPDATE")
+public class UpdateReportCommandHandler implements NewCommandSourceHandler {
+
+    private final ReportWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateReportCommandHandler(final ReportWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateReport(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/serialization/ReportCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/serialization/ReportCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..8d3808f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/serialization/ReportCommandFromApiJsonDeserializer.java
@@ -0,0 +1,58 @@
+/**
+ * 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.dataqueries.serialization;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public final class ReportCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("reportName", "reportType", "reportSubType",
+            "reportCategory", "description", "reportSql", "useReport", "reportParameters"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ReportCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validate(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);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataService.java
new file mode 100644
index 0000000..327fa47
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataService.java
@@ -0,0 +1,37 @@
+/**
+ * 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.dataqueries.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
+
+public interface GenericDataService {
+
+    GenericResultsetData fillGenericResultSet(final String sql);
+
+    String generateJsonFromGenericResultsetData(GenericResultsetData grs);
+
+    String replace(String str, String pattern, String replace);
+
+    String wrapSQL(String sql);
+
+    List<ResultsetColumnHeaderData> fillResultsetColumnHeaders(String datatable);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java
new file mode 100644
index 0000000..db49b93
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java
@@ -0,0 +1,316 @@
+/**
+ * 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.dataqueries.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnValueData;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
+import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GenericDataServiceImpl implements GenericDataService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final DataSource dataSource;
+    private final static Logger logger = LoggerFactory.getLogger(GenericDataServiceImpl.class);
+
+    @Autowired
+    public GenericDataServiceImpl(final RoutingDataSource dataSource) {
+        this.dataSource = dataSource;
+        this.jdbcTemplate = new JdbcTemplate(this.dataSource);
+
+    }
+
+    @Override
+    public GenericResultsetData fillGenericResultSet(final String sql) {
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+        final List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<>();
+        final List<ResultsetRowData> resultsetDataRows = new ArrayList<>();
+
+        final SqlRowSetMetaData rsmd = rs.getMetaData();
+
+        for (int i = 0; i < rsmd.getColumnCount(); i++) {
+
+            final String columnName = rsmd.getColumnName(i + 1);
+            final String columnType = rsmd.getColumnTypeName(i + 1);
+
+            final ResultsetColumnHeaderData columnHeader = ResultsetColumnHeaderData.basic(columnName, columnType);
+            columnHeaders.add(columnHeader);
+        }
+
+        while (rs.next()) {
+            final List<String> columnValues = new ArrayList<>();
+            for (int i = 0; i < rsmd.getColumnCount(); i++) {
+                final String columnName = rsmd.getColumnName(i + 1);
+                final String columnValue = rs.getString(columnName);
+                columnValues.add(columnValue);
+            }
+
+            final ResultsetRowData resultsetDataRow = ResultsetRowData.create(columnValues);
+            resultsetDataRows.add(resultsetDataRow);
+        }
+
+        return new GenericResultsetData(columnHeaders, resultsetDataRows);
+    }
+
+    @Override
+    public String replace(final String str, final String pattern, final String replace) {
+        // JPW - this replace may / may not be any better or quicker than the
+        // apache stringutils equivalent. It works, but if someone shows the
+        // apache one to be about the same then this can be removed.
+        int s = 0;
+        int e = 0;
+        final StringBuffer result = new StringBuffer();
+
+        while ((e = str.indexOf(pattern, s)) >= 0) {
+            result.append(str.substring(s, e));
+            result.append(replace);
+            s = e + pattern.length();
+        }
+        result.append(str.substring(s));
+        return result.toString();
+    }
+
+    @Override
+    public String wrapSQL(final String sql) {
+        // wrap sql to prevent JDBC sql errors, prevent malicious sql and a
+        // CachedRowSetImpl bug
+
+        // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7046875 - prevent
+        // Invalid Column Name bug in sun's CachedRowSetImpl where it doesn't
+        // pick up on label names, only column names
+        return "select x.* from (" + sql + ") x";
+    }
+
+    @Override
+    public String generateJsonFromGenericResultsetData(final GenericResultsetData grs) {
+
+        final StringBuffer writer = new StringBuffer();
+
+        writer.append("[");
+
+        final List<ResultsetColumnHeaderData> columnHeaders = grs.getColumnHeaders();
+
+        final List<ResultsetRowData> data = grs.getData();
+        List<String> row;
+        Integer rSize;
+        final String doubleQuote = "\"";
+        final String slashDoubleQuote = "\\\"";
+        String currColType;
+        String currVal;
+
+        for (int i = 0; i < data.size(); i++) {
+            writer.append("\n{");
+
+            row = data.get(i).getRow();
+            rSize = row.size();
+            for (int j = 0; j < rSize; j++) {
+
+                writer.append(doubleQuote + columnHeaders.get(j).getColumnName() + doubleQuote + ": ");
+                currColType = columnHeaders.get(j).getColumnDisplayType();
+                final String colType = columnHeaders.get(j).getColumnType();
+                if (currColType == null && colType.equalsIgnoreCase("INT")) {
+                    currColType = "INTEGER";
+                }
+                if (currColType == null && colType.equalsIgnoreCase("VARCHAR")) {
+                    currColType = "VARCHAR";
+                }
+                if (currColType == null && colType.equalsIgnoreCase("DATE")) {
+                    currColType = "DATE";
+                }
+                currVal = row.get(j);
+                if (currVal != null && currColType != null) {
+                    if (currColType.equals("DECIMAL") || currColType.equals("INTEGER")) {
+                        writer.append(currVal);
+                    } else {
+                        if (currColType.equals("DATE")) {
+                            final LocalDate localDate = new LocalDate(currVal);
+                            writer.append("[" + localDate.getYear() + ", " + localDate.getMonthOfYear() + ", " + localDate.getDayOfMonth()
+                                    + "]");
+                        } else if (currColType.equals("DATETIME")) {
+                            final LocalDateTime localDateTime = new LocalDateTime(currVal);
+                            writer.append("[" + localDateTime.getYear() + ", " + localDateTime.getMonthOfYear() + ", "
+                                    + localDateTime.getDayOfMonth() + " " + localDateTime.getHourOfDay() + ", "
+                                    + localDateTime.getMinuteOfHour() + ", " + localDateTime.getSecondOfMinute() + ", "
+                                    + localDateTime.getMillisOfSecond() + "]");
+                        } else {
+                            writer.append(doubleQuote + replace(currVal, doubleQuote, slashDoubleQuote) + doubleQuote);
+                        }
+                    }
+                } else {
+                    writer.append("null");
+                }
+                if (j < (rSize - 1)) {
+                    writer.append(",\n");
+                }
+            }
+
+            if (i < (data.size() - 1)) {
+                writer.append("},");
+            } else {
+                writer.append("}");
+            }
+        }
+
+        writer.append("\n]");
+        return writer.toString();
+
+    }
+
+    @Override
+    public List<ResultsetColumnHeaderData> fillResultsetColumnHeaders(final String datatable) {
+
+        logger.debug("::3 Was inside the fill ResultSetColumnHeader");
+
+        final SqlRowSet columnDefinitions = getDatatableMetaData(datatable);
+
+        final List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<>();
+
+        columnDefinitions.beforeFirst();
+        while (columnDefinitions.next()) {
+            final String columnName = columnDefinitions.getString("COLUMN_NAME");
+            final String isNullable = columnDefinitions.getString("IS_NULLABLE");
+            final String isPrimaryKey = columnDefinitions.getString("COLUMN_KEY");
+            final String columnType = columnDefinitions.getString("DATA_TYPE");
+            final Long columnLength = columnDefinitions.getLong("CHARACTER_MAXIMUM_LENGTH");
+
+            final boolean columnNullable = "YES".equalsIgnoreCase(isNullable);
+            final boolean columnIsPrimaryKey = "PRI".equalsIgnoreCase(isPrimaryKey);
+
+            List<ResultsetColumnValueData> columnValues = new ArrayList<>();
+            String codeName = null;
+            if ("varchar".equalsIgnoreCase(columnType)) {
+
+                final int codePosition = columnName.indexOf("_cv");
+                if (codePosition > 0) {
+                    codeName = columnName.substring(0, codePosition);
+
+                    columnValues = retreiveColumnValues(codeName);
+                }
+
+            } else if ("int".equalsIgnoreCase(columnType)) {
+
+                final int codePosition = columnName.indexOf("_cd");
+                if (codePosition > 0) {
+                    codeName = columnName.substring(0, codePosition);
+                    columnValues = retreiveColumnValues(codeName);
+                }
+            }
+            if (codeName == null) {
+                final SqlRowSet rsValues = getDatatableCodeData(datatable, columnName);
+                Integer codeId = null;
+                while (rsValues.next()) {
+                    codeId = rsValues.getInt("id");
+                    codeName = rsValues.getString("code_name");
+                }
+                columnValues = retreiveColumnValues(codeId);
+
+            }
+
+            final ResultsetColumnHeaderData rsch = ResultsetColumnHeaderData.detailed(columnName, columnType, columnLength, columnNullable,
+                    columnIsPrimaryKey, columnValues, codeName);
+
+            columnHeaders.add(rsch);
+        }
+
+        return columnHeaders;
+    }
+
+    /*
+     * Candidate for using caching there to get allowed 'column values' from
+     * code/codevalue tables
+     */
+    private List<ResultsetColumnValueData> retreiveColumnValues(final String codeName) {
+
+        final List<ResultsetColumnValueData> columnValues = new ArrayList<>();
+
+        final String sql = "select v.id, v.code_score, v.code_value from m_code m " + " join m_code_value v on v.code_id = m.id "
+                + " where m.code_name = '" + codeName + "' order by v.order_position, v.id";
+
+        final SqlRowSet rsValues = this.jdbcTemplate.queryForRowSet(sql);
+
+        rsValues.beforeFirst();
+        while (rsValues.next()) {
+            final Integer id = rsValues.getInt("id");
+            final String codeValue = rsValues.getString("code_value");
+            final Integer score = rsValues.getInt("code_score");
+
+            columnValues.add(new ResultsetColumnValueData(id, codeValue, score));
+        }
+
+        return columnValues;
+    }
+
+    private List<ResultsetColumnValueData> retreiveColumnValues(final Integer codeId) {
+
+        final List<ResultsetColumnValueData> columnValues = new ArrayList<>();
+        if (codeId != null) {
+            final String sql = "select v.id, v.code_value from m_code_value v where v.code_id =" + codeId
+                    + " order by v.order_position, v.id";
+            final SqlRowSet rsValues = this.jdbcTemplate.queryForRowSet(sql);
+            rsValues.beforeFirst();
+            while (rsValues.next()) {
+                final Integer id = rsValues.getInt("id");
+                final String codeValue = rsValues.getString("code_value");
+                columnValues.add(new ResultsetColumnValueData(id, codeValue));
+            }
+        }
+
+        return columnValues;
+    }
+
+    private SqlRowSet getDatatableMetaData(final String datatable) {
+
+        final String sql = "select COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMN_KEY"
+                + " from INFORMATION_SCHEMA.COLUMNS " + " where TABLE_SCHEMA = schema() and TABLE_NAME = '" + datatable
+                + "'order by ORDINAL_POSITION";
+
+        final SqlRowSet columnDefinitions = this.jdbcTemplate.queryForRowSet(sql);
+        if (columnDefinitions.next()) { return columnDefinitions; }
+
+        throw new DatatableNotFoundException(datatable);
+    }
+
+    private SqlRowSet getDatatableCodeData(final String datatable, final String columnName) {
+
+        final String sql = "select mc.id,mc.code_name from m_code mc join x_table_column_code_mappings xcc on xcc.code_id = mc.id where xcc.column_alias_name='"
+                + datatable.toLowerCase().replaceAll("\\s", "_") + "_" + columnName + "'";
+        final SqlRowSet rsValues = this.jdbcTemplate.queryForRowSet(sql);
+
+        return rsValues;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
new file mode 100644
index 0000000..4891d55
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
@@ -0,0 +1,45 @@
+/**
+ * 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.dataqueries.service;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.ws.rs.core.StreamingOutput;
+
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
+import org.apache.fineract.infrastructure.dataqueries.data.ReportParameterData;
+
+public interface ReadReportingService {
+
+    StreamingOutput retrieveReportCSV(String name, String type, Map<String, String> extractedQueryParams);
+
+    GenericResultsetData retrieveGenericResultset(String name, String type, Map<String, String> extractedQueryParams);
+
+    String retrieveReportPDF(String name, String type, Map<String, String> extractedQueryParams);
+
+    String getReportType(String reportName);
+
+    Collection<ReportData> retrieveReportList();
+
+    Collection<ReportParameterData> getAllowedParameters();
+
+    ReportData retrieveReport(final Long id);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
new file mode 100644
index 0000000..672e70f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
@@ -0,0 +1,476 @@
+/**
+ * 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.dataqueries.service;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.sql.DataSource;
+import javax.ws.rs.core.StreamingOutput;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
+import org.apache.fineract.infrastructure.dataqueries.data.ReportParameterData;
+import org.apache.fineract.infrastructure.dataqueries.data.ReportParameterJoinData;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
+import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+import org.springframework.stereotype.Service;
+
+import com.lowagie.text.Document;
+import com.lowagie.text.PageSize;
+import com.lowagie.text.pdf.PdfPTable;
+import com.lowagie.text.pdf.PdfWriter;
+
+@Service
+public class ReadReportingServiceImpl implements ReadReportingService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ReadReportingServiceImpl.class);
+
+    private final JdbcTemplate jdbcTemplate;
+    private final DataSource dataSource;
+    private final PlatformSecurityContext context;
+    private final GenericDataService genericDataService;
+
+    @Autowired
+    public ReadReportingServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final GenericDataService genericDataService) {
+
+        this.context = context;
+        this.dataSource = dataSource;
+        this.jdbcTemplate = new JdbcTemplate(this.dataSource);
+        this.genericDataService = genericDataService;
+    }
+
+    @Override
+    public StreamingOutput retrieveReportCSV(final String name, final String type, final Map<String, String> queryParams) {
+
+        return new StreamingOutput() {
+
+            @Override
+            public void write(final OutputStream out) {
+                try {
+
+                    final GenericResultsetData result = retrieveGenericResultset(name, type, queryParams);
+                    final StringBuffer sb = generateCsvFileBuffer(result);
+
+                    final InputStream in = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
+
+                    final byte[] outputByte = new byte[4096];
+                    Integer readLen = in.read(outputByte, 0, 4096);
+
+                    while (readLen != -1) {
+                        out.write(outputByte, 0, readLen);
+                        readLen = in.read(outputByte, 0, 4096);
+                    }
+                    // in.close();
+                    // out.flush();
+                    // out.close();
+                } catch (final Exception e) {
+                    throw new PlatformDataIntegrityException("error.msg.exception.error", e.getMessage());
+                }
+            }
+        };
+
+    }
+
+    private StringBuffer generateCsvFileBuffer(final GenericResultsetData result) {
+        final StringBuffer writer = new StringBuffer();
+
+        final List<ResultsetColumnHeaderData> columnHeaders = result.getColumnHeaders();
+        logger.info("NO. of Columns: " + columnHeaders.size());
+        final Integer chSize = columnHeaders.size();
+        for (int i = 0; i < chSize; i++) {
+            writer.append('"' + columnHeaders.get(i).getColumnName() + '"');
+            if (i < (chSize - 1)) {
+                writer.append(",");
+            }
+        }
+        writer.append('\n');
+
+        final List<ResultsetRowData> data = result.getData();
+        List<String> row;
+        Integer rSize;
+        // String currCol;
+        String currColType;
+        String currVal;
+        final String doubleQuote = "\"";
+        final String twoDoubleQuotes = doubleQuote + doubleQuote;
+        logger.info("NO. of Rows: " + data.size());
+        for (int i = 0; i < data.size(); i++) {
+            row = data.get(i).getRow();
+            rSize = row.size();
+            for (int j = 0; j < rSize; j++) {
+                // currCol = columnHeaders.get(j).getColumnName();
+                currColType = columnHeaders.get(j).getColumnType();
+                currVal = row.get(j);
+                if (currVal != null) {
+                    if (currColType.equals("DECIMAL") || currColType.equals("DOUBLE") || currColType.equals("BIGINT")
+                            || currColType.equals("SMALLINT") || currColType.equals("INT")) {
+                        writer.append(currVal);
+                    } else {
+                        writer.append('"' + this.genericDataService.replace(currVal, doubleQuote, twoDoubleQuotes) + '"');
+                    }
+
+                }
+                if (j < (rSize - 1)) {
+                    writer.append(",");
+                }
+            }
+            writer.append('\n');
+        }
+
+        return writer;
+    }
+
+    @Override
+    public GenericResultsetData retrieveGenericResultset(final String name, final String type, final Map<String, String> queryParams) {
+
+        final long startTime = System.currentTimeMillis();
+        logger.info("STARTING REPORT: " + name + "   Type: " + type);
+
+        final String sql = getSQLtoRun(name, type, queryParams);
+
+        final GenericResultsetData result = this.genericDataService.fillGenericResultSet(sql);
+
+        final long elapsed = System.currentTimeMillis() - startTime;
+        logger.info("FINISHING Report/Request Name: " + name + " - " + type + "     Elapsed Time: " + elapsed);
+        return result;
+    }
+
+    private String getSQLtoRun(final String name, final String type, final Map<String, String> queryParams) {
+
+        String sql = getSql(name, type);
+
+        final Set<String> keys = queryParams.keySet();
+        for (final String key : keys) {
+            final String pValue = queryParams.get(key);
+            // logger.info("(" + key + " : " + pValue + ")");
+            sql = this.genericDataService.replace(sql, key, pValue);
+        }
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        // Allows sql query to restrict data by office hierarchy if required
+        sql = this.genericDataService.replace(sql, "${currentUserHierarchy}", currentUser.getOffice().getHierarchy());
+        // Allows sql query to restrict data by current user Id if required
+        // (typically used to return report lists containing only reports
+        // permitted to be run by the user
+        sql = this.genericDataService.replace(sql, "${currentUserId}", currentUser.getId().toString());
+
+        sql = this.genericDataService.wrapSQL(sql);
+
+        return sql;
+
+    }
+
+    private String getSql(final String name, final String type) {
+
+        final String inputSql = "select " + type + "_sql as the_sql from stretchy_" + type + " where " + type + "_name = '" + name + "'";
+        final String inputSqlWrapped = this.genericDataService.wrapSQL(inputSql);
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(inputSqlWrapped);
+
+        if (rs.next() && rs.getString("the_sql") != null) { return rs.getString("the_sql"); }
+        throw new ReportNotFoundException(inputSql);
+    }
+
+    @Override
+    public String getReportType(final String reportName) {
+
+        final String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '" + reportName + "'";
+
+        final String sqlWrapped = this.genericDataService.wrapSQL(sql);
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sqlWrapped);
+
+        if (rs.next()) { return rs.getString("report_type"); }
+        throw new ReportNotFoundException(sql);
+    }
+
+    @Override
+    public String retrieveReportPDF(final String reportName, final String type, final Map<String, String> queryParams) {
+
+        final String fileLocation = FileSystemContentRepository.FINERACT_BASE_DIR + File.separator + "";
+        if (!new File(fileLocation).isDirectory()) {
+            new File(fileLocation).mkdirs();
+        }
+
+        final String genaratePdf = fileLocation + File.separator + reportName + ".pdf";
+
+        try {
+            final GenericResultsetData result = retrieveGenericResultset(reportName, type, queryParams);
+
+            final List<ResultsetColumnHeaderData> columnHeaders = result.getColumnHeaders();
+            final List<ResultsetRowData> data = result.getData();
+            List<String> row;
+
+            logger.info("NO. of Columns: " + columnHeaders.size());
+            final Integer chSize = columnHeaders.size();
+
+            final Document document = new Document(PageSize.B0.rotate());
+
+            PdfWriter.getInstance(document, new FileOutputStream(new File(fileLocation + reportName + ".pdf")));
+            document.open();
+
+            final PdfPTable table = new PdfPTable(chSize);
+            table.setWidthPercentage(100);
+
+            for (int i = 0; i < chSize; i++) {
+
+                table.addCell(columnHeaders.get(i).getColumnName());
+
+            }
+            table.completeRow();
+
+            Integer rSize;
+            String currColType;
+            String currVal;
+            logger.info("NO. of Rows: " + data.size());
+            for (int i = 0; i < data.size(); i++) {
+                row = data.get(i).getRow();
+                rSize = row.size();
+                for (int j = 0; j < rSize; j++) {
+                    currColType = columnHeaders.get(j).getColumnType();
+                    currVal = row.get(j);
+                    if (currVal != null) {
+                        if (currColType.equals("DECIMAL") || currColType.equals("DOUBLE") || currColType.equals("BIGINT")
+                                || currColType.equals("SMALLINT") || currColType.equals("INT")) {
+
+                            table.addCell(currVal.toString());
+                        } else {
+                            table.addCell(currVal.toString());
+                        }
+                    }
+                }
+            }
+            table.completeRow();
+            document.add(table);
+            document.close();
+            return genaratePdf;
+        } catch (final Exception e) {
+            logger.error("error.msg.reporting.error:" + e.getMessage());
+            throw new PlatformDataIntegrityException("error.msg.exception.error", e.getMessage());
+        }
+    }
+
+    @Override
+    public ReportData retrieveReport(final Long id) {
+        final Collection<ReportData> reports = retrieveReports(id);
+
+        for (final ReportData report : reports) {
+            return report;
+        }
+        return null;
+    }
+
+    @Override
+    public Collection<ReportData> retrieveReportList() {
+        return retrieveReports(null);
+    }
+
+    private Collection<ReportData> retrieveReports(final Long id) {
+
+        final ReportParameterJoinMapper rm = new ReportParameterJoinMapper();
+
+        final String sql = rm.schema(id);
+
+        final Collection<ReportParameterJoinData> rpJoins = this.jdbcTemplate.query(sql, rm, new Object[] {});
+
+        final Collection<ReportData> reportList = new ArrayList<>();
+        if (rpJoins == null || rpJoins.size() == 0) { return reportList; }
+
+        Collection<ReportParameterData> reportParameters = null;
+
+        Long reportId = null;
+        String reportName = null;
+        String reportType = null;
+        String reportSubType = null;
+        String reportCategory = null;
+        String description = null;
+        Boolean coreReport = null;
+        Boolean useReport = null;
+        String reportSql = null;
+
+        Long prevReportId = (long) -1234;
+        Boolean firstReport = true;
+        for (final ReportParameterJoinData rpJoin : rpJoins) {
+
+            if (rpJoin.getReportId().equals(prevReportId)) {
+                // more than one parameter for report
+                if (reportParameters == null) {
+                    reportParameters = new ArrayList<>();
+                }
+                reportParameters.add(new ReportParameterData(rpJoin.getReportParameterId(), rpJoin.getParameterId(), rpJoin
+                        .getReportParameterName(), rpJoin.getParameterName()));
+
+            } else {
+                if (firstReport) {
+                    firstReport = false;
+                } else {
+                    // write report entry
+                    reportList.add(new ReportData(reportId, reportName, reportType, reportSubType, reportCategory, description, reportSql,
+                            coreReport, useReport, reportParameters));
+                }
+
+                prevReportId = rpJoin.getReportId();
+
+                reportId = rpJoin.getReportId();
+                reportName = rpJoin.getReportName();
+                reportType = rpJoin.getReportType();
+                reportSubType = rpJoin.getReportSubType();
+                reportCategory = rpJoin.getReportCategory();
+                description = rpJoin.getDescription();
+                reportSql = rpJoin.getReportSql();
+                coreReport = rpJoin.getCoreReport();
+                useReport = rpJoin.getUseReport();
+
+                if (rpJoin.getReportParameterId() != null) {
+                    // report has at least one parameter
+                    reportParameters = new ArrayList<>();
+                    reportParameters.add(new ReportParameterData(rpJoin.getReportParameterId(), rpJoin.getParameterId(), rpJoin
+                            .getReportParameterName(), rpJoin.getParameterName()));
+                } else {
+                    reportParameters = null;
+                }
+            }
+
+        }
+        // write last report
+        reportList.add(new ReportData(reportId, reportName, reportType, reportSubType, reportCategory, description, reportSql, coreReport,
+                useReport, reportParameters));
+
+        return reportList;
+    }
+
+    @Override
+    public Collection<ReportParameterData> getAllowedParameters() {
+
+        final ReportParameterMapper rm = new ReportParameterMapper();
+        final String sql = rm.schema();
+        final Collection<ReportParameterData> parameters = this.jdbcTemplate.query(sql, rm, new Object[] {});
+        return parameters;
+    }
+
+    private static final class ReportParameterJoinMapper implements RowMapper<ReportParameterJoinData> {
+
+        public String schema(final Long reportId) {
+
+            String sql = "select r.id as reportId, r.report_name as reportName, r.report_type as reportType, "
+                    + " r.report_subtype as reportSubType, r.report_category as reportCategory, r.description, r.core_report as coreReport, r.use_report as useReport, "
+                    + " rp.id as reportParameterId, rp.parameter_id as parameterId, rp.report_parameter_name as reportParameterName, p.parameter_name as parameterName";
+
+            if (reportId != null) {
+                sql += ", r.report_sql as reportSql ";
+            }
+
+            sql += " from stretchy_report r" + " left join stretchy_report_parameter rp on rp.report_id = r.id"
+                    + " left join stretchy_parameter p on p.id = rp.parameter_id";
+            if (reportId != null) {
+                sql += " where r.id = " + reportId;
+            } else {
+                sql += " order by r.id, rp.parameter_id";
+            }
+
+            return sql;
+
+            /*
+             * used to only return reports that the use can run as done in
+             * report UI but not necessary as there is a read_report permission
+             * which should give user access to look all reports +
+             * " where exists" + " (select 'f'" + " from m_appuser_role ur " +
+             * " join m_role r on r.id = ur.role_id" +
+             * " left join m_role_permission rp on rp.role_id = r.id" +
+             * " left join m_permission p on p.id = rp.permission_id" +
+             * " where ur.appuser_id = " + userId +
+             * " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', r.report_name))) "
+             * ;
+             */
+        }
+
+        @Override
+        public ReportParameterJoinData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long reportId = rs.getLong("reportId");
+            final String reportName = rs.getString("reportName");
+            final String reportType = rs.getString("reportType");
+            final String reportSubType = rs.getString("reportSubType");
+            final String reportCategory = rs.getString("reportCategory");
+            final String description = rs.getString("description");
+            final Boolean coreReport = rs.getBoolean("coreReport");
+            final Boolean useReport = rs.getBoolean("useReport");
+
+            String reportSql;
+            // reportSql might not be on the select list of columns
+            try {
+                reportSql = rs.getString("reportSql");
+            } catch (final SQLException e) {
+                reportSql = null;
+            }
+
+            final Long reportParameterId = JdbcSupport.getLong(rs, "reportParameterId");
+            final Long parameterId = JdbcSupport.getLong(rs, "parameterId");
+            final String reportParameterName = rs.getString("reportParameterName");
+            final String parameterName = rs.getString("parameterName");
+
+            return new ReportParameterJoinData(reportId, reportName, reportType, reportSubType, reportCategory, description, reportSql,
+                    coreReport, useReport, reportParameterId, parameterId, reportParameterName, parameterName);
+        }
+    }
+
+    private static final class ReportParameterMapper implements RowMapper<ReportParameterData> {
+
+        public String schema() {
+
+            return "select p.id as id, p.parameter_name as parameterName from stretchy_parameter p where ifnull(p.special,'') != 'Y' order by p.id";
+
+        }
+
+        @Override
+        public ReportParameterData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String parameterName = rs.getString("parameterName");
+
+            return new ReportParameterData(id, null, null, parameterName);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
new file mode 100644
index 0000000..22529a3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
@@ -0,0 +1,71 @@
+/**
+ * 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.dataqueries.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.springframework.security.access.prepost.PreAuthorize;
+
+public interface ReadWriteNonCoreDataService {
+
+    List<DatatableData> retrieveDatatableNames(String appTable);
+
+    DatatableData retrieveDatatable(String datatable);
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
+    void registerDatatable(JsonCommand command);
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
+    void registerDatatable(String dataTableName, String applicationTableName);
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
+    void registerDatatable(JsonCommand command, String permissionTable);
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'DEREGISTER_DATATABLE')")
+    void deregisterDatatable(String datatable);
+
+    GenericResultsetData retrieveDataTableGenericResultSet(String datatable, Long appTableId, String order, Long id);
+
+    CommandProcessingResult createDatatable(JsonCommand command);
+
+    void updateDatatable(String datatableName, JsonCommand command);
+
+    void deleteDatatable(String datatableName);
+
+    CommandProcessingResult createNewDatatableEntry(String datatable, Long appTableId, JsonCommand command);
+
+    CommandProcessingResult createPPIEntry(String datatable, Long appTableId, JsonCommand command);
+
+    CommandProcessingResult updateDatatableEntryOneToOne(String datatable, Long appTableId, JsonCommand command);
+
+    CommandProcessingResult updateDatatableEntryOneToMany(String datatable, Long appTableId, Long datatableId, JsonCommand command);
+
+    CommandProcessingResult deleteDatatableEntries(String datatable, Long appTableId);
+
+    CommandProcessingResult deleteDatatableEntry(String datatable, Long appTableId, Long datatableId);
+
+    String getTableName(String Url);
+
+    String getDataTableName(String Url);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
new file mode 100644
index 0000000..13edd7b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
@@ -0,0 +1,1694 @@
+/**
+ * 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.dataqueries.service;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.service.CodeReadPlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
+import org.apache.fineract.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant;
+import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
+import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
+import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException;
+import org.apache.fineract.infrastructure.dataqueries.exception.DatatableSystemErrorException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.exception.ConstraintViolationException;
+import org.hibernate.exception.GenericJDBCException;
+import org.hibernate.exception.SQLGrammarException;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Service
+public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataService {
+
+    private final static String DATATABLE_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,48}[a-zA-Z0-9]$";
+
+    private final static String CODE_VALUES_TABLE = "m_code_value";
+
+    private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class);
+    private final static HashMap<String, String> apiTypeToMySQL = new HashMap<String, String>() {
+
+        {
+            put("string", "VARCHAR");
+            put("number", "INT");
+            put("boolean", "BIT");
+            put("decimal", "DECIMAL");
+            put("date", "DATE");
+            put("datetime", "DATETIME");
+            put("text", "TEXT");
+            put("dropdown", "INT");
+        }
+    };
+
+    private final static List<String> stringDataTypes = Arrays.asList("char", "varchar", "blob", "text", "tinyblob", "tinytext",
+            "mediumblob", "mediumtext", "longblob", "longtext");
+
+    private final JdbcTemplate jdbcTemplate;
+    private final DataSource dataSource;
+    private final PlatformSecurityContext context;
+    private final FromJsonHelper fromJsonHelper;
+    private final JsonParserHelper helper;
+    private final GenericDataService genericDataService;
+    private final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final ConfigurationDomainService configurationDomainService;
+    private final CodeReadPlatformService codeReadPlatformService;
+    private final DataTableValidator dataTableValidator;
+
+    // private final GlobalConfigurationWritePlatformServiceJpaRepositoryImpl
+    // configurationWriteService;
+
+    @Autowired(required = true)
+    public ReadWriteNonCoreDataServiceImpl(final RoutingDataSource dataSource, final PlatformSecurityContext context,
+            final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService,
+            final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer, final CodeReadPlatformService codeReadPlatformService,
+            final ConfigurationDomainService configurationDomainService, final DataTableValidator dataTableValidator) {
+        this.dataSource = dataSource;
+        this.jdbcTemplate = new JdbcTemplate(this.dataSource);
+        this.context = context;
+        this.fromJsonHelper = fromJsonHelper;
+        this.helper = new JsonParserHelper();
+        this.genericDataService = genericDataService;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.codeReadPlatformService = codeReadPlatformService;
+        this.configurationDomainService = configurationDomainService;
+        this.dataTableValidator = dataTableValidator;
+        // this.configurationWriteService = configurationWriteService;
+    }
+
+    @Override
+    public List<DatatableData> retrieveDatatableNames(final String appTable) {
+
+        String andClause;
+        if (appTable == null) {
+            andClause = "";
+        } else {
+            andClause = " and application_table_name = '" + appTable + "'";
+        }
+
+        // PERMITTED datatables
+        final String sql = "select application_table_name, registered_table_name" + " from x_registered_table " + " where exists"
+                + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
+                + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
+                + " where ur.appuser_id = " + this.context.authenticatedUser().getId()
+                + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
+                + andClause + " order by application_table_name, registered_table_name";
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+        final List<DatatableData> datatables = new ArrayList<>();
+        while (rs.next()) {
+            final String appTableName = rs.getString("application_table_name");
+            final String registeredDatatableName = rs.getString("registered_table_name");
+            final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService
+                    .fillResultsetColumnHeaders(registeredDatatableName);
+
+            datatables.add(DatatableData.create(appTableName, registeredDatatableName, columnHeaderData));
+        }
+
+        return datatables;
+    }
+
+    @Override
+    public DatatableData retrieveDatatable(final String datatable) {
+
+        // PERMITTED datatables
+        final String sql = "select application_table_name, registered_table_name" + " from x_registered_table " + " where exists"
+                + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
+                + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
+                + " where ur.appuser_id = " + this.context.authenticatedUser().getId() + " and registered_table_name='" + datatable + "'"
+                + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
+                + " order by application_table_name, registered_table_name";
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+        DatatableData datatableData = null;
+        while (rs.next()) {
+            final String appTableName = rs.getString("application_table_name");
+            final String registeredDatatableName = rs.getString("registered_table_name");
+            final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService
+                    .fillResultsetColumnHeaders(registeredDatatableName);
+
+            datatableData = DatatableData.create(appTableName, registeredDatatableName, columnHeaderData);
+        }
+
+        return datatableData;
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) {
+        logger.error(dve.getMessage(), dve);
+    }
+
+    @Transactional
+    @Override
+    public void registerDatatable(final String dataTableName, final String applicationTableName) {
+
+        Integer category = DataTableApiConstant.CATEGORY_DEFAULT;
+
+        final String permissionSql = this._getPermissionSql(dataTableName);
+        this._registerDataTable(applicationTableName, dataTableName, category, permissionSql);
+
+    }
+
+    @Transactional
+    @Override
+    public void registerDatatable(final JsonCommand command) {
+
+        final String applicationTableName = this.getTableName(command.getUrl());
+        final String dataTableName = this.getDataTableName(command.getUrl());
+
+        Integer category = this.getCategory(command);
+
+        this.dataTableValidator.validateDataTableRegistration(command.json());
+        final String permissionSql = this._getPermissionSql(dataTableName);
+        this._registerDataTable(applicationTableName, dataTableName, category, permissionSql);
+
+    }
+
+    @Transactional
+    @Override
+    public void registerDatatable(final JsonCommand command, final String permissionSql) {
+        final String applicationTableName = this.getTableName(command.getUrl());
+        final String dataTableName = this.getDataTableName(command.getUrl());
+
+        Integer category = this.getCategory(command);
+
+        this.dataTableValidator.validateDataTableRegistration(command.json());
+
+        this._registerDataTable(applicationTableName, dataTableName, category, permissionSql);
+
+    }
+
+    @Transactional
+    private void _registerDataTable(final String applicationTableName, final String dataTableName, final Integer category,
+            final String permissionsSql) {
+
+        validateAppTable(applicationTableName);
+        assertDataTableExists(dataTableName);
+
+        final String registerDatatableSql = "insert into x_registered_table (registered_table_name, application_table_name,category) values ('"
+                + dataTableName + "', '" + applicationTableName + "', '" + category + "')";
+
+        try {
+
+            final String[] sqlArray = { registerDatatableSql, permissionsSql };
+            this.jdbcTemplate.batchUpdate(sqlArray);
+
+            // add the registered table to the config if it is a ppi
+            if (this.isSurveyCategory(category)) {
+                this.jdbcTemplate.execute("insert into c_configuration (name, value, enabled ) values('" + dataTableName + "', '0','0')");
+            }
+
+        }
+        /***
+         * Strangely, a Hibernate contraint violation exception is thrown
+         ****/
+        catch (final ConstraintViolationException cve) {
+            final Throwable realCause = cve.getCause();
+            // even if duplicate is only due to permission duplicate, okay to
+            // show duplicate datatable error msg
+            if (realCause.getMessage()
+                    .contains("Duplicate entry")) { throw new PlatformDataIntegrityException("error.msg.datatable.registered",
+                            "Datatable `" + dataTableName + "` is already registered against an application table.", "dataTableName",
+                            dataTableName); }
+        } catch (final DataIntegrityViolationException dve) {
+            final Throwable realCause = dve.getMostSpecificCause();
+            // even if duplicate is only due to permission duplicate, okay to
+            // show duplicate datatable error msg
+            if (realCause.getMessage()
+                    .contains("Duplicate entry")) { throw new PlatformDataIntegrityException("error.msg.datatable.registered",
+                            "Datatable `" + dataTableName + "` is already registered against an application table.", "dataTableName",
+                            dataTableName); }
+            logAsErrorUnexpectedDataIntegrityException(dve);
+            throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.");
+        }
+
+    }
+
+    private String _getPermissionSql(final String dataTableName) {
+        final String createPermission = "'CREATE_" + dataTableName + "'";
+        final String createPermissionChecker = "'CREATE_" + dataTableName + "_CHECKER'";
+        final String readPermission = "'READ_" + dataTableName + "'";
+        final String updatePermission = "'UPDATE_" + dataTableName + "'";
+        final String updatePermissionChecker = "'UPDATE_" + dataTableName + "_CHECKER'";
+        final String deletePermission = "'DELETE_" + dataTableName + "'";
+        final String deletePermissionChecker = "'DELETE_" + dataTableName + "_CHECKER'";
+
+        return "insert into m_permission (grouping, code, action_name, entity_name, can_maker_checker) values " + "('datatable', "
+                + createPermission + ", 'CREATE', '" + dataTableName + "', true)," + "('datatable', " + createPermissionChecker
+                + ", 'CREATE', '" + dataTableName + "', false)," + "('datatable', " + readPermission + ", 'READ', '" + dataTableName
+                + "', false)," + "('datatable', " + updatePermission + ", 'UPDATE', '" + dataTableName + "', true)," + "('datatable', "
+                + updatePermissionChecker + ", 'UPDATE', '" + dataTableName + "', false)," + "('datatable', " + deletePermission
+                + ", 'DELETE', '" + dataTableName + "', true)," + "('datatable', " + deletePermissionChecker + ", 'DELETE', '"
+                + dataTableName + "', false)";
+
+    }
+
+    private Integer getCategory(final JsonCommand command) {
+        Integer category = command.integerValueOfParameterNamedDefaultToNullIfZero(DataTableApiConstant.categoryParamName);
+        if (category == null) category = DataTableApiConstant.CATEGORY_DEFAULT;
+        return category;
+    }
+
+    private boolean isSurveyCategory(final Integer category) {
+        return category.equals(DataTableApiConstant.CATEGORY_PPI);
+    }
+
+    @Override
+    public String getDataTableName(String url) {
+
+        String[] urlParts = url.split("/");
+
+        return urlParts[3];
+
+    }
+
+    @Override
+    public String getTableName(String url) {
+        String[] urlParts = url.split("/");
+        return urlParts[4];
+    }
+
+    @Transactional
+    @Override
+    public void deregisterDatatable(final String datatable) {
+        final String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_"
+                + datatable + "', 'UPDATE_" + datatable + "_CHECKER', 'DELETE_" + datatable + "', 'DELETE_" + datatable + "_CHECKER')";
+
+        final String deleteRolePermissionsSql = "delete from m_role_permission where m_role_permission.permission_id in (select id from m_permission where code in "
+                + permissionList + ")";
+
+        final String deletePermissionsSql = "delete from m_permission where code in " + permissionList;
+
+        final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + datatable + "'";
+
+        final String deleteFromConfigurationSql = "delete from c_configuration where name ='" + datatable + "'";
+
+        String[] sqlArray = new String[4];
+        sqlArray[0] = deleteRolePermissionsSql;
+        sqlArray[1] = deletePermissionsSql;
+        sqlArray[2] = deleteRegisteredDatatableSql;
+        sqlArray[3] = deleteFromConfigurationSql;
+
+        this.jdbcTemplate.batchUpdate(sqlArray);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createNewDatatableEntry(final String dataTableName, final Long appTableId, final JsonCommand command) {
+
+        try {
+            final String appTable = queryForApplicationTableName(dataTableName);
+            final CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(appTable, appTableId);
+
+            final List<ResultsetColumnHeaderData> columnHeaders = this.genericDataService.fillResultsetColumnHeaders(dataTableName);
+
+            final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType();
+            final Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json());
+
+            final String sql = getAddSql(columnHeaders, dataTableName, getFKField(appTable), appTableId, dataParams);
+
+            this.jdbcTemplate.update(sql);
+
+            return commandProcessingResult; //
+
+        } catch (final ConstraintViolationException dve) {
+            // NOTE: jdbctemplate throws a
+            // org.hibernate.exception.ConstraintViolationException even though
+            // it should be a DataAccessException?
+            final Throwable realCause = dve.getCause();
+            if (realCause.getMessage()
+                    .contains("Duplicate entry")) { throw new PlatformDataIntegrityException(
+                            "error.msg.datatable.entry.duplicate", "An entry already exists for datatable `" + dataTableName
+                                    + "` and application table with identifier `" + appTableId + "`.",
+                            "dataTableName", dataTableName, appTableId); }
+
+            logAsErrorUnexpectedDataIntegrityException(dve);
+            throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.");
+        } catch (final DataAccessException dve) {
+            final Throwable realCause = dve.getMostSpecificCause();
+            if (realCause.getMessage()
+                    .contains("Duplicate entry")) { throw new PlatformDataIntegrityException(
+                            "error.msg.datatable.entry.duplicate", "An entry already exists for datatable `" + dataTableName
+                                    + "` and application table with identifier `" + appTableId + "`.",
+                            "dataTableName", dataTableName, appTableId); }
+
+            logAsErrorUnexpectedDataIntegrityException(dve);
+            throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.");
+        }
+    }
+
+    @Override
+    public CommandProcessingResult createPPIEntry(final String dataTableName, final Long appTableId, final JsonCommand command) {
+
+        try {
+            final String appTable = queryForApplicationTableName(dataTableName);
+            final CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(appTable, appTableId);
+
+            final List<ResultsetColumnHeaderData> columnHeaders = this.genericDataService.fillResultsetColumnHeaders(dataTableName);
+
+            final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType();
+            final Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json());
+
+            final String sql = getAddSqlWithScore(columnHeaders, dataTableName, getFKField(appTable), appTableId, dataParams);
+
+            this.jdbcTemplate.update(sql);
+
+            return commandProcessingResult; //
+
+        } catch (final ConstraintViolationException dve) {
+            // NOTE: jdbctemplate throws a
+            // org.hibernate.exception.ConstraintViolationException even though
+            // it should be a DataAccessException?
+            final Throwable realCause = dve.getCause();
+            if (realCause.getMessage()
+                    .contains("Duplicate entry")) { throw new PlatformDataIntegrityException(
+                            "error.msg.datatable.entry.duplicate", "An entry already exists for datatable `" + dataTableName
+                                    + "` and application table with identifier `" + appTableId + "`.",
+                            "dataTableName", dataTableName, appTableId); }
+
+            logAsErrorUnexpectedDataIntegrityException(dve);
+            throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.");
+        } catch (final DataAccessException dve) {
+            final Throwable realCause = dve.getMostSpecificCause();
+            if (realCause.getMessage()
+                    .contains("Duplicate entry")) { throw new PlatformDataIntegrityException(
+                            "error.msg.datatable.entry.duplicate", "An entry already exists for datatable `" + dataTableName
+                                    + "` and application table with identifier `" + appTableId + "`.",
+                            "dataTableName", dataTableName, appTableId); }
+
+            logAsErrorUnexpectedDataIntegrityException(dve);
+            throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.");
+        }
+    }
+
+    private boolean isRegisteredDataTable(final String name) {
+        // PERMITTED datatables
+        final String sql = "select if((exists (select 1 from x_registered_table where registered_table_name = ?)) = 1, 'true', 'false')";
+        final String isRegisteredDataTable = this.jdbcTemplate.queryForObject(sql, String.class, new Object[] { name });
+        return new Boolean(isRegisteredDataTable);
+    }
+
+    private void assertDataTableExists(final String datatableName) {
+        final String sql = "select if((exists (select 1 from information_schema.tables where table_schema = schema() and table_name = ?)) = 1, 'true', 'false')";
+        final String dataTableExistsString = this.jdbcTemplate.queryForObject(sql, String.class, new Object[] { datatableName });
+        final boolean dataTableExists = new Boolean(dataTableExistsString);
+        if (!dataTableExists) { throw new PlatformDataIntegrityException("error.msg.invalid.datatable",
+                "Invalid Data Table: " + datatableName, "name", datatableName); }
+    }
+
+    private void validateDatatableName(final String name) {
+
+        if (name == null || name.isEmpty()) {
+            throw new PlatformDataIntegrityException("error.msg.datatables.datatable.null.name", "Data table name must not be blank.");
+        } else if (!name.matches(DATATABLE_NAME_REGEX_PATTERN)) { throw new PlatformDataIntegrityException(
+                "error.msg.datatables.datatable.invalid.name.regex", "Invalid data table name.", name); }
+    }
+
+    private String datatableColumnNameToCodeValueName(final String columnName, final String code) {
+
+        return (code + "_cd_" + columnName);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    private void parseDatatableColumnObjectForCreate(final JsonObject column, StringBuilder sqlBuilder,
+            final StringBuilder constrainBuilder, final String dataTableNameAlias, final Map<String, Long> codeMappings,
+            final boolean isConstraintApproach) {
+
+        String name = (column.has("name")) ? column.get("name").getAsString() : null;
+        final String type = (column.has("type")) ? column.get("type").getAsString().toLowerCase() : null;
+        final Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
+        final Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
+        final String code = (column.has("code")) ? column.get("code").getAsString() : null;
+
+        if (StringUtils.isNotBlank(code)) {
+            if (isConstraintApproach) {
+                codeMappings.put(dataTableNameAlias + "_" + name, this.codeReadPlatformService.retriveCode(code).getCodeId());
+                constrainBuilder.append(", CONSTRAINT `fk_").append(dataTableNameAlias).append("_").append(name).append("` ")
+                        .append("FOREIGN KEY (`" + name + "`) ").append("REFERENCES `").append(CODE_VALUES_TABLE).append("` (`id`)");
+            } else {
+                name = datatableColumnNameToCodeValueName(name, code);
+            }
+        }
+
+        final String mysqlType = apiTypeToMySQL.get(type);
+        sqlBuilder = sqlBuilder.append("`" + name + "` " + mysqlType);
+
+        if (type != null) {
+            if (type.equalsIgnoreCase("String")) {
+                sqlBuilder = sqlBuilder.append("(" + length + ")");
+            } else if (type.equalsIgnoreCase("Decimal")) {
+                sqlBuilder = sqlBuilder.append("(19,6)");
+            } else if (type.equalsIgnoreCase("Dropdown")) {
+                sqlBuilder = sqlBuilder.append("(11)");
+            }
+        }
+
+        if (mandatory) {
+            sqlBuilder = sqlBuilder.append(" NOT NULL");
+        } else {
+            sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
+        }
+
+        sqlBuilder = sqlBuilder.append(", ");
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createDatatable(final JsonCommand command) {
+
+        String datatableName = null;
+
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final JsonElement element = this.fromJsonHelper.parse(command.json());
+            final JsonArray columns = this.fromJsonHelper.extractJsonArrayNamed("columns", element);
+            datatableName = this.fromJsonHelper.extractStringNamed("datatableName", element);
+            final String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element);
+            Boolean multiRow = this.fromJsonHelper.extractBooleanNamed("multiRow", element);
+
+            /***
+             * In cases of tables storing hierarchical entities (like m_group),
+             * different entities would end up being stored in the same table.
+             * 
+             * Ex: Centers are a specific type of group, add abstractions for
+             * the same
+             ***/
+            final String actualAppTableName = mapToActualAppTable(apptableName);
+
+            if (multiRow == null) {
+                multiRow = false;
+            }
+
+            validateDatatableName(datatableName);
+            validateAppTable(apptableName);
+            final boolean isConstraintApproach = this.configurationDomainService.isConstraintApproachEnabledForDatatables();
+            final String fkColumnName = apptableName.substring(2) + "_id";
+            final String dataTableNameAlias = datatableName.toLowerCase().replaceAll("\\s", "_");
+            final String fkName = dataTableNameAlias + "_" + fkColumnName;
+            StringBuilder sqlBuilder = new StringBuilder();
+            final StringBuilder constrainBuilder = new StringBuilder();
+            final Map<String, Long> codeMappings = new HashMap<>();
+            sqlBuilder = sqlBuilder.append("CREATE TABLE `" + datatableName + "` (");
+
+            if (multiRow) {
+                sqlBuilder = sqlBuilder.append("`id` BIGINT(20) NOT NULL AUTO_INCREMENT, ")
+                        .append("`" + fkColumnName + "` BIGINT(20) NOT NULL, ");
+            } else {
+                sqlBuilder = sqlBuilder.append("`" + fkColumnName + "` BIGINT(20) NOT NULL, ");
+            }
+
+            for (final JsonElement column : columns) {
+                parseDatatableColumnObjectForCreate(column.getAsJsonObject(), sqlBuilder, constrainBuilder, dataTableNameAlias,
+                        codeMappings, isConstraintApproach);
+            }
+
+            // Remove trailing comma and space
+            sqlBuilder = sqlBuilder.delete(sqlBuilder.length() - 2, sqlBuilder.length());
+
+            if (multiRow) {
+                sqlBuilder = sqlBuilder.append(", PRIMARY KEY (`id`)")
+                        .append(", KEY `fk_" + apptableName.substring(2) + "_id` (`" + fkColumnName + "`)")
+                        .append(", CONSTRAINT `fk_" + fkName + "` ").append("FOREIGN KEY (`" + fkColumnName + "`) ")
+                        .append("REFERENCES `" + actualAppTableName + "` (`id`)");
+            } else {
+                sqlBuilder = sqlBuilder.append(", PRIMARY KEY (`" + fkColumnName + "`)").append(", CONSTRAINT `fk_" + fkName + "` ")
+                        .append("FOREIGN KEY (`" + fkColumnName + "`) ").append("REFERENCES `" + actualAppTableName + "` (`id`)");
+            }
+
+            sqlBuilder.append(constrainBuilder);
+
+            sqlBuilder = sqlBuilder.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+            this.jdbcTemplate.execute(sqlBuilder.toString());
+
+            registerDatatable(datatableName, apptableName);
+            registerColumnCodeMapping(codeMappings);
+        } catch (final SQLGrammarException e) {
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
+
+            if (realCause.getMessage().toLowerCase().contains("duplicate column name")) {
+                baseDataValidator.reset().parameter("name").failWithCode("duplicate.column.name");
+            } else if (realCause.getMessage().contains("Table") && realCause.getMessage().contains("already exists")) {
+                baseDataValidator.reset().parameter("datatableName").value(datatableName).failWithCode("datatable.already.exists");
+            } else if (realCause.getMessage().contains("Column") && realCause.getMessage().contains("big")) {
+                baseDataValidator.reset().parameter("column").failWithCode("length.too.big");
+            } else if (realCause.getMessage().contains("Row") && realCause.getMessage().contains("large")) {
+                baseDataValidator.reset().parameter("row").failWithCode("size.too.large");
+            }
+
+            throwExceptionIfValidationWarningsExist(dataValidationErrors);
+        }
+
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build();
+    }
+
+    private void parseDatatableColumnForUpdate(final JsonObject column,
+            final Map<String, ResultsetColumnHeaderData> mapColumnNameDefinition, StringBuilder sqlBuilder, final String datatableName,
+            final StringBuilder constrainBuilder, final Map<String, Long> codeMappings, final List<String> removeMappings,
+            final boolean isConstraintApproach) {
+
+        String name = (column.has("name")) ? column.get("name").getAsString() : null;
+        final String lengthStr = (column.has("length")) ? column.get("length").getAsString() : null;
+        Integer length = (StringUtils.isNotBlank(lengthStr)) ? Integer.parseInt(lengthStr) : null;
+        String newName = (column.has("newName")) ? column.get("newName").getAsString() : name;
+        final Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
+        final String after = (column.has("after")) ? column.get("after").getAsString() : null;
+        final String code = (column.has("code")) ? column.get("code").getAsString() : null;
+        final String newCode = (column.has("newCode")) ? column.get("newCode").getAsString() : null;
+        final String dataTableNameAlias = datatableName.toLowerCase().replaceAll("\\s", "_");
+        if (isConstraintApproach) {
+            if (StringUtils.isBlank(newName)) {
+                newName = name;
+            }
+            if (!StringUtils.equalsIgnoreCase(code, newCode) || !StringUtils.equalsIgnoreCase(name, newName)) {
+                if (StringUtils.equalsIgnoreCase(code, newCode)) {
+                    final int codeId = getCodeIdForColumn(dataTableNameAlias, name);
+                    if (codeId > 0) {
+                        removeMappings.add(dataTableNameAlias + "_" + name);
+                        constrainBuilder.append(", DROP FOREIGN KEY `fk_").append(dataTableNameAlias).append("_").append(name).append("` ");
+                        codeMappings.put(dataTableNameAlias + "_" + newName, (long) codeId);
+                        constrainBuilder.append(",ADD CONSTRAINT  `fk_").append(dataTableNameAlias).append("_").append(newName).append("` ")
+                                .append("FOREIGN KEY (`" + newName + "`) ").append("REFERENCES `").append(CODE_VALUES_TABLE)
+                                .append("` (`id`)");
+                    }
+
+                } else {
+                    if (code != null) {
+                        removeMappings.add(dataTableNameAlias + "_" + name);
+                        if (newCode == null || !StringUtils.equalsIgnoreCase(name, newName)) {
+                            constrainBuilder.append(", DROP FOREIGN KEY `fk_").append(dataTableNameAlias).append("_").append(name)
+                                    .append("` ");
+                        }
+                    }
+                    if (newCode != null) {
+                        codeMappings.put(dataTableNameAlias + "_" + newName, this.codeReadPlatformService.retriveCode(newCode).getCodeId());
+                        if (code == null || !StringUtils.equalsIgnoreCase(name, newName)) {
+                            constrainBuilder.append(",ADD CONSTRAINT  `fk_").append(dataTableNameAlias).append("_").append(newName)
+                                    .append("` ").append("FOREIGN KEY (`" + newName + "`) ").append("REFERENCES `")
+                                    .append(CODE_VALUES_TABLE).append("` (`id`)");
+                        }
+                    }
+                }
+            }
+        } else {
+            if (StringUtils.isNotBlank(code)) {
+                name = datatableColumnNameToCodeValueName(name, code);
+                if (StringUtils.isNotBlank(newCode)) {
+                    newName = datatableColumnNameToCodeValueName(newName, newCode);
+                } else {
+                    newName = datatableColumnNameToCodeValueName(newName, code);
+                }
+            }
+        }
+        if (!mapColumnNameDefinition.containsKey(name)) { throw new PlatformDataIntegrityException(
+                "error.msg.datatable.column.missing.update.parse", "Column " + name + " does not exist.", name); }
+        final String type = mapColumnNameDefinition.get(name).getColumnType();
+        if (length == null && type.toLowerCase().equals("varchar")) {
+            length = mapColumnNameDefinition.get(name).getColumnLength().intValue();
+        }
+
+        sqlBuilder = sqlBuilder.append(", CHANGE `" + name + "` `" + newName + "` " + type);
+        if (length != null && length > 0) {
+            if (type.toLowerCase().equals("decimal")) {
+                sqlBuilder.append("(19,6)");
+            } else if (type.toLowerCase().equals("varchar")) {
+                sqlBuilder.append("(" + length + ")");
+            }
+        }
+
+        if (mandatory) {
+            sqlBuilder = sqlBuilder.append(" NOT NULL");
+        } else {
+            sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
+        }
+
+        if (after != null) {
+            sqlBuilder = sqlBuilder.append(" AFTER `" + after + "`");
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private int getCodeIdForColumn(final String dataTableNameAlias, final String name) {
+        final StringBuilder checkColumnCodeMapping = new StringBuilder();
+        checkColumnCodeMapping.append("select ccm.code_id from x_table_column_code_mappings ccm where ccm.column_alias_name='")
+                .append(dataTableNameAlias).append("_").append(name).append("'");
+        int codeId = 0;
+        try {
+            codeId = this.jdbcTemplate.queryForInt(checkColumnCodeMapping.toString());
+        } catch (final EmptyResultDataAccessException e) {
+            logger.info(e.getMessage());
+        }
+        return codeId;
+    }
+
+    private void parseDatatableColumnForAdd(final JsonObject column, StringBuilder sqlBuilder, final String dataTableNameAlias,
+            final StringBuilder constrainBuilder, final Map<String, Long> codeMappings, final boolean isConstraintApproach) {
+
+        String name = (column.has("name")) ? column.get("name").getAsString() : null;
+        final String type = (column.has("type")) ? column.get("type").getAsString().toLowerCase() : null;
+        final Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
+        final Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
+        final String after = (column.has("after")) ? column.get("after").getAsString() : null;
+        final String code = (column.has("code")) ? column.get("code").getAsString() : null;
+
+        if (StringUtils.isNotBlank(code)) {
+            if (isConstraintApproach) {
+                codeMappings.put(dataTableNameAlias + "_" + name, this.codeReadPlatformService.retriveCode(code).getCodeId());
+                constrainBuilder.append(",ADD CONSTRAINT  `fk_").append(dataTableNameAlias).append("_").append(name).append("` ")
+                        .append("FOREIGN KEY (`" + name + "`) ").append("REFERENCES `").append(CODE_VALUES_TABLE).append("` (`id`)");
+            } else {
+                name = datatableColumnNameToCodeValueName(name, code);
+            }
+        }
+
+        final String mysqlType = apiTypeToMySQL.get(type);
+        sqlBuilder = sqlBuilder.append(", ADD `" + name + "` " + mysqlType);
+
+        if (type != null) {
+            if (type.equalsIgnoreCase("String") && length != null) {
+                sqlBuilder = sqlBuilder.append("(" + length + ")");
+            } else if (type.equalsIgnoreCase("Decimal")) {
+                sqlBuilder = sqlBuilder.append("(19,6)");
+            } else if (type.equalsIgnoreCase("Dropdown")) {
+                sqlBuilder = sqlBuilder.append("(11)");
+            }
+        }
+
+        if (mandatory) {
+            sqlBuilder = sqlBuilder.append(" NOT NULL");
+        } else {
+            sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
+        }
+
+        if (after != null) {
+            sqlBuilder = sqlBuilder.append(" AFTER `" + after + "`");
+        }
+    }
+
+    private void parseDatatableColumnForDrop(final JsonObject column, StringBuilder sqlBuilder, final String datatableName,
+            final StringBuilder constrainBuilder, final List<String> codeMappings) {
+        final String datatableAlias = datatableName.toLowerCase().replaceAll("\\s", "_");
+        final String name = (column.has("name")) ? column.get("name").getAsString() : null;
+        sqlBuilder = sqlBuilder.append(", DROP COLUMN `" + name + "`");
+        final StringBuilder findFKSql = new StringBuilder();
+        findFKSql.append("SELECT count(*)").append("FROM information_schema.TABLE_CONSTRAINTS i")
+                .append(" WHERE i.CONSTRAINT_TYPE = 'FOREIGN KEY'").append(" AND i.TABLE_SCHEMA = DATABASE()")
+                .append(" AND i.TABLE_NAME = '").append(datatableName).append("' AND i.CONSTRAINT_NAME = 'fk_").append(datatableAlias)
+                .append("_").append(name).append("' ");
+        @SuppressWarnings("deprecation")
+        final int count = this.jdbcTemplate.queryForInt(findFKSql.toString());
+        if (count > 0) {
+            codeMappings.add(datatableAlias + "_" + name);
+            constrainBuilder.append(", DROP FOREIGN KEY `fk_").append(datatableAlias).append("_").append(name).append("` ");
+        }
+    }
+
+    private void registerColumnCodeMapping(final Map<String, Long> codeMappings) {
+        if (codeMappings != null && !codeMappings.isEmpty()) {
+            final String[] addSqlList = new String[codeMappings.size()];
+            int i = 0;
+            for (final Map.Entry<String, Long> mapEntry : codeMappings.entrySet()) {
+                addSqlList[i++] = "insert into x_table_column_code_mappings (column_alias_name, code_id) values ('" + mapEntry.getKey()
+                        + "'," + mapEntry.getValue() + ");";
+            }
+
+            this.jdbcTemplate.batchUpdate(addSqlList);
+        }
+    }
+
+    private void deleteColumnCodeMapping(final List<String> columnNames) {
+        if (columnNames != null && !columnNames.isEmpty()) {
+            final String[] deleteSqlList = new String[columnNames.size()];
+            int i = 0;
+            for (final String columnName : columnNames) {
+                deleteSqlList[i++] = "DELETE FROM x_table_column_code_mappings WHERE  column_alias_name='" + columnName + "';";
+            }
+
+            this.jdbcTemplate.batchUpdate(deleteSqlList);
+        }
+
+    }
+
+    /**
+     * Update data table, set column value to empty string where current value
+     * is NULL. Run update SQL only if the "mandatory" property is set to true
+     * 
+     * @param datatableName
+     *            Name of data table
+     * @param column
+     *            JSON encoded array of column properties
+     * @see https://mifosforge.jira.com/browse/MIFOSX-1145
+     **/
+    private void removeNullValuesFromStringColumn(final String datatableName, final JsonObject column,
+            final Map<String, ResultsetColumnHeaderData> mapColumnNameDefinition) {
+        final Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
+        final String name = (column.has("name")) ? column.get("name").getAsString() : "";
+        final String type = (mapColumnNameDefinition.containsKey(name)) ? mapColumnNameDefinition.get(name).getColumnType() : "";
+
+        if (StringUtils.isNotEmpty(type)) {
+            if (mandatory && stringDataTypes.contains(type.toLowerCase())) {
+                StringBuilder sqlBuilder = new StringBuilder();
+                sqlBuilder.append("UPDATE `" + datatableName + "` SET `" + name + "` = '' WHERE `" + name + "` IS NULL");
+
+                this.jdbcTemplate.update(sqlBuilder.toString());
+            }
+        }
+    }
+
+    @Transactional
+    @Override
+    public void updateDatatable(final String datatableName, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final JsonElement element = this.fromJsonHelper.parse(command.json());
+            final JsonArray changeColumns = this.fromJsonHelper.extractJsonArrayNamed("changeColumns", element);
+            final JsonArray addColumns = this.fromJsonHelper.extractJsonArrayNamed("addColumns", element);
+            final JsonArray dropColumns = this.fromJsonHelper.extractJsonArrayNamed("dropColumns", element);
+            final String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element);
+
+            validateDatatableName(datatableName);
+
+            final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService.fillResultsetColumnHeaders(datatableName);
+            final Map<String, ResultsetColumnHeaderData> mapColumnNameDefinition = new HashMap<>();
+            for (final ResultsetColumnHeaderData columnHeader : columnHeaderData) {
+                mapColumnNameDefinition.put(columnHeader.getColumnName(), columnHeader);
+            }
+
+            final boolean isConstraintApproach = this.configurationDomainService.isConstraintApproachEnabledForDatatables();
+
+            if (!StringUtils.isBlank(apptableName)) {
+                validateAppTable(apptableName);
+
+                final String oldApptableName = queryForApplicationTableName(datatableName);
+                if (!StringUtils.equals(oldApptableName, apptableName)) {
+                    final String oldFKName = oldApptableName.substring(2) + "_id";
+                    final String newFKName = apptableName.substring(2) + "_id";
+                    final String actualAppTableName = mapToActualAppTable(apptableName);
+                    final String oldConstraintName = datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + oldFKName;
+                    final String newConstraintName = datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + newFKName;
+                    StringBuilder sqlBuilder = new StringBuilder();
+
+                    if (mapColumnNameDefinition.containsKey("id")) {
+                        sqlBuilder = sqlBuilder.append("ALTER TABLE `" + datatableName + "` ").append("DROP KEY `fk_" + oldFKName + "`,")
+                                .append("DROP FOREIGN KEY `fk_" + oldConstraintName + "`,")
+                                .append("CHANGE COLUMN `" + oldFKName + "` `" + newFKName + "` BIGINT(20) NOT NULL,")
+                                .append("ADD KEY `fk_" + newFKName + "` (`" + newFKName + "`),")
+                                .append("ADD CONSTRAINT `fk_" + newConstraintName + "` ").append("FOREIGN KEY (`" + newFKName + "`) ")
+                                .append("REFERENCES `" + actualAppTableName + "` (`id`)");
+                    } else {
+                        sqlBuilder = sqlBuilder.append("ALTER TABLE `" + datatableName + "` ")
+                                .append("DROP FOREIGN KEY `fk_" + oldConstraintName + "`,")
+                                .append("CHANGE COLUMN `" + oldFKName + "` `" + newFKName + "` BIGINT(20) NOT NULL,")
+                                .append("ADD CONSTRAINT `fk_" + newConstraintName + "` ").append("FOREIGN KEY (`" + newFKName + "`) ")
+                                .append("REFERENCES `" + actualAppTableName + "` (`id`)");
+                    }
+
+                    this.jdbcTemplate.execute(sqlBuilder.toString());
+
+                    deregisterDatatable(datatableName);
+                    registerDatatable(datatableName, apptableName);
+                }
+            }
+
+            if (changeColumns == null && addColumns == null && dropColumns == null) { return; }
+
+            if (dropColumns != null) {
+
+                StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`");
+                final StringBuilder constrainBuilder = new StringBuilder();
+                final List<String> codeMappings = new ArrayList<>();
+                for (final JsonElement column : dropColumns) {
+                    parseDatatableColumnForDrop(column.getAsJsonObject(), sqlBuilder, datatableName, constrainBuilder, codeMappings);
+                }
+
+                // Remove the first comma, right after ALTER TABLE `datatable`
+                final int indexOfFirstComma = sqlBuilder.indexOf(",");
+                if (indexOfFirstComma != -1) {
+                    sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma);
+                }
+                sqlBuilder.append(constrainBuilder);
+                this.jdbcTemplate.execute(sqlBuilder.toString());
+                deleteColumnCodeMapping(codeMappings);
+            }
+            if (addColumns != null) {
+
+                StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`");
+                final StringBuilder constrainBuilder = new StringBuilder();
+                final Map<String, Long> codeMappings = new HashMap<>();
+                for (final JsonElement column : addColumns) {
+                    parseDatatableColumnForAdd(column.getAsJsonObject(), sqlBuilder, datatableName.toLowerCase().replaceAll("\\s", "_"),
+                            constrainBuilder, codeMappings, isConstraintApproach);
+                }
+
+                // Remove the first comma, right after ALTER TABLE `datatable`
+                final int indexOfFirstComma = sqlBuilder.indexOf(",");
+                if (indexOfFirstComma != -1) {
+                    sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma);
+                }
+                sqlBuilder.append(constrainBuilder);
+                this.jdbcTemplate.execute(sqlBuilder.toString());
+                registerColumnCodeMapping(codeMappings);
+            }
+            if (changeColumns != null) {
+
+                StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`");
+                final StringBuilder constrainBuilder = new StringBuilder();
+                final Map<String, Long> codeMappings = new HashMap<>();
+                final List<String> removeMappings = new ArrayList<>();
+                for (final JsonElement column : changeColumns) {
+                    // remove NULL values from column where mandatory is true
+                    removeNullValuesFromStringColumn(datatableName, column.getAsJsonObject(), mapColumnNameDefinition);
+
+                    parseDatatableColumnForUpdate(column.getAsJsonObject(), mapColumnNameDefinition, sqlBuilder, datatableName,
+                            constrainBuilder, codeMappings, removeMappings, isConstraintApproach);
+                }
+
+                // Remove the first comma, right after ALTER TABLE `datatable`
+                final int indexOfFirstComma = sqlBuilder.indexOf(",");
+                if (indexOfFirstComma != -1) {
+                    sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma);
+                }
+                sqlBuilder.append(constrainBuilder);
+                try {
+                    this.jdbcTemplate.execute(sqlBuilder.toString());
+                    deleteColumnCodeMapping(removeMappings);
+                    registerColumnCodeMapping(codeMappings);
+                } catch (final GenericJDBCException e) {
+                    if (e.getMessage().contains("Error on rename")) { throw new PlatformServiceUnavailableException(
+                            "error.msg.datatable.column.update.not.allowed", "One of the column name modification not allowed"); }
+                } catch (final Exception e) {
+                    // handle all other exceptions in here
+
+                    // check if exception message contains the
+                    // "invalid use of null value" SQL exception message
+                    // throw a 503 HTTP error -
+                    // PlatformServiceUnavailableException
+                    if (e.getMessage().toLowerCase()
+                            .contains("invalid use of null value")) { throw new PlatformServiceUnavailableException(
+                                    "error.msg.datatable.column.update.not.allowed",
+                                    "One of the data table columns contains null values"); }
+                }
+            }
+        } catch (final SQLGrammarException e) {
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
+
+            if (realCause.getMessage().toLowerCase().contains("unknown column")) {
+                baseDataValidator.reset().parameter("name").failWithCode("does.not.exist");
+            } else if (realCause.getMessage().toLowerCase().contains("can't drop")) {
+                baseDataValidator.reset().parameter("name").failWithCode("does.not.exist");
+            } else if (realCause.getMessage().toLowerCase().contains("duplicate column")) {
+                baseDataValidator.reset().parameter("name").failWithCode("column.already.exists");
+            }
+
+            throwExceptionIfValidationWarningsExist(dataValidationErrors);
+        }
+    }
+
+    @Transactional
+    @Override
+    public void deleteDatatable(final String datatableName) {
+
+        try {
+            this.context.authenticatedUser();
+            if (!isRegisteredDataTable(datatableName)) { throw new DatatableNotFoundException(datatableName); }
+            validateDatatableName(datatableName);
+            assertDataTableEmpty(datatableName);
+            deregisterDatatable(datatableName);
+            String[] sqlArray = null;
+            if (this.configurationDomainService.isConstraintApproachEnabledForDatatables()) {
+                final String deleteColumnCodeSql = "delete from x_table_column_code_mappings where column_alias_name like'"
+                        + datatableName.toLowerCase().replaceAll("\\s", "_") + "_%'";
+                sqlArray = new String[2];
+                sqlArray[1] = deleteColumnCodeSql;
+            } else {
+                sqlArray = new String[1];
+            }
+            final String sql = "DROP TABLE `" + datatableName + "`";
+            sqlArray[0] = sql;
+            this.jdbcTemplate.batchUpdate(sqlArray);
+        } catch (final SQLGrammarException e) {
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
+            if (realCause.getMessage().contains("Unknown table")) {
+                baseDataValidator.reset().parameter("datatableName").failWithCode("does.not.exist");
+            }
+
+            throwExceptionIfValidationWarningsExist(dataValidationErrors);
+        }
+    }
+
+    private void assertDataTableEmpty(final String datatableName) {
+        final String sql = "select count(*) from `" + datatableName + "`";
+        final int rowCount = this.jdbcTemplate.queryForObject(sql, Integer.class);
+        if (rowCount != 0) { throw new GeneralPlatformDomainRuleException("error.msg.non.empty.datatable.cannot.be.deleted",
+                "Non-empty datatable cannot be deleted."); }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateDatatableEntryOneToOne(final String dataTableName, final Long appTableId,
+            final JsonCommand command) {
+
+        return updateDatatableEntry(dataTableName, appTableId, null, command);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateDatatableEntryOneToMany(final String dataTableName, final Long appTableId, final Long datatableId,
+            final JsonCommand command) {
+
+        return updateDatatableEntry(dataTableName, appTableId, datatableId, command);
+    }
+
+    private CommandProcessingResult updateDatatableEntry(final String dataTableName, final Long appTableId, final Long datatableId,
+            final JsonCommand command) {
+
+        final String appTable = queryForApplicationTableName(dataTableName);
+        final CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(appTable, appTableId);
+
+        final GenericResultsetData grs = retrieveDataTableGenericResultSetForUpdate(appTable, dataTableName, appTableId, datatableId);
+
+        if (grs.hasNoEntries()) { throw new DatatableNotFoundException(dataTableName, appTableId); }
+
+        if (grs.hasMoreThanOneEntry()) { throw new PlatformDataIntegrityException("error.msg.attempting.multiple.update",
+                "Application table: " + dataTableName + " Foreign key id: " + appTableId); }
+
+        final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType();
+        final Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json());
+
+        String pkName = "id"; // 1:M datatable
+        if (datatableId == null) {
+            pkName = getFKField(appTable);
+        } // 1:1 datatable
+
+        final Map<String, Object> changes = getAffectedAndChangedColumns(grs, dataParams, pkName);
+
+        if (!changes.isEmpty()) {
+            Long pkValue = appTableId;
+            if (datatableId != null) {
+                pkValue = datatableId;
+            }
+            final String sql = getUpdateSql(grs.getColumnHeaders(), dataTableName, pkName, pkValue, changes);
+            logger.info("Update sql: " + sql);
+            if (StringUtils.isNotBlank(sql)) {
+                this.jdbcTemplate.update(sql);
+                changes.put("locale", dataParams.get("locale"));
+                changes.put("dateFormat", "yyyy-MM-dd");
+            } else {
+                logger.info("No Changes");
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withOfficeId(commandProcessingResult.getOfficeId()) //
+                .withGroupId(commandProcessingResult.getGroupId()) //
+                .withClientId(commandProcessingResult.getClientId()) //
+                .withSavingsId(commandProcessingResult.getSavingsId()) //
+                .withLoanId(commandProcessingResult.getLoanId()) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteDatatableEntries(final String dataTableName, final Long appTableId) {
+
+        final String appTable = queryForApplicationTableName(dataTableName);
+        final CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(appTable, appTableId);
+
+        final String deleteOneToOneEntrySql = getDeleteEntriesSql(dataTableName, getFKField(appTable), appTableId);
+
+        final int rowsDeleted = this.jdbcTemplate.update(deleteOneToOneEntrySql);
+        if (rowsDeleted < 1) { throw new DatatableNotFoundException(dataTableName, appTableId); }
+
+        return commandProcessingResult;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteDatatableEntry(final String dataTableName, final Long appTableId, final Long datatableId) {
+
+        final String appTable = queryForApplicationTableName(dataTableName);
+        final CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(appTable, appTableId);
+
+        final String sql = getDeleteEntrySql(dataTableName, datatableId);
+
+        this.jdbcTemplate.update(sql);
+        return commandProcessingResult;
+    }
+
+    @Override
+    public GenericResultsetData retrieveDataTableGenericResultSet(final String dataTableName, final Long appTableId, final String order,
+            final Long id) {
+
+        final String appTable = queryForApplicationTableName(dataTableName);
+
+        checkMainResourceExistsWithinScope(appTable, appTableId);
+
+        final List<ResultsetColumnHeaderData> columnHeaders = this.genericDataService.fillResultsetColumnHeaders(dataTableName);
+
+        String sql = "";
+
+        // id only used for reading a specific entry in a one to many datatable
+        // (when updating)
+        if (id == null) {
+            sql = sql + "select * from `" + dataTableName + "` where " + getFKField(appTable) + " = " + appTableId;
+        } else {
+            sql = sql + "select * from `" + dataTableName + "` where id = " + id;
+        }
+
+        if (order != null) {
+            sql = sql + " order by " + order;
+        }
+
+        final List<ResultsetRowData> result = fillDatatableResultSetDataRows(sql);
+
+        return new GenericResultsetData(columnHeaders, result);
+    }
+
+    private GenericResultsetData retrieveDataTableGenericResultSetForUpdate(final String appTable, final String dataTableName,
+            final Long appTableId, final Long id) {
+
+        final List<ResultsetColumnHeaderData> columnHeaders = this.genericDataService.fillResultsetColumnHeaders(dataTableName);
+
+        String sql = "";
+
+        // id only used for reading a specific entry in a one to many datatable
+        // (when updating)
+        if (id == null) {
+            sql = sql + "select * from `" + dataTableName + "` where " + getFKField(appTable) + " = " + appTableId;
+        } else {
+            sql = sql + "select * from `" + dataTableName + "` where id = " + id;
+        }
+
+        final List<ResultsetRowData> result = fillDatatableResultSetDataRows(sql);
+
+        return new GenericResultsetData(columnHeaders, result);
+    }
+
+    private CommandProcessingResult checkMainResourceExistsWithinScope(final String appTable, final Long appTableId) {
+
+        final String sql = dataScopedSQL(appTable, appTableId);
+        logger.info("data scoped sql: " + sql);
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+        if (!rs.next()) { throw new DatatableNotFoundException(appTable, appTableId); }
+
+        final Long officeId = getLongSqlRowSet(rs, "officeId");
+        final Long groupId = getLongSqlRowSet(rs, "groupId");
+        final Long clientId = getLongSqlRowSet(rs, "clientId");
+        final Long savingsId = getLongSqlRowSet(rs, "savingsId");
+        final Long LoanId = getLongSqlRowSet(rs, "loanId");
+        final Long entityId = getLongSqlRowSet(rs, "entityId");
+
+        if (rs.next()) { throw new DatatableSystemErrorException("System Error: More than one row returned from data scoping query"); }
+
+        return new CommandProcessingResultBuilder() //
+                .withOfficeId(officeId) //
+                .withGroupId(groupId) //
+                .withClientId(clientId) //
+                .withSavingsId(savingsId) //
+                .withLoanId(LoanId).withEntityId(entityId)//
+                .build();
+    }
+
+    private Long getLongSqlRowSet(final SqlRowSet rs, final String column) {
+        Long val = rs.getLong(column);
+        if (val == 0) {
+            val = null;
+        }
+        return val;
+    }
+
+    private String dataScopedSQL(final String appTable, final Long appTableId) {
+        /*
+         * unfortunately have to, one way or another, be able to restrict data
+         * to the users office hierarchy. Here, a few key tables are done. But
+         * if additional fields are needed on other tables the same pattern
+         * applies
+         */
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        String scopedSQL = null;
+        /*
+         * m_loan and m_savings_account are connected to an m_office thru either
+         * an m_client or an m_group If both it means it relates to an m_client
+         * that is in a group (still an m_client account)
+         */
+        if (appTable.equalsIgnoreCase("m_loan")) {
+            scopedSQL = "select  distinctrow x.* from ("
+                    + " (select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId, null as entityId from m_loan l "
+                    + " join m_client c on c.id = l.client_id " + " join m_office o on o.id = c.office_id and o.hierarchy like '"
+                    + currentUser.getOffice().getHierarchy() + "%'" + " where l.id = " + appTableId + ")" + " union all "
+                    + " (select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId, null as entityId from m_loan l "
+                    + " join m_group g on g.id = l.group_id " + " join m_office o on o.id = g.office_id and o.hierarchy like '"
+                    + currentUser.getOffice().getHierarchy() + "%'" + " where l.id = " + appTableId + ")" + " ) x";
+        }
+        if (appTable.equalsIgnoreCase("m_savings_account")) {
+            scopedSQL = "select  distinctrow x.* from ("
+                    + " (select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, null as entityId from m_savings_account s "
+                    + " join m_client c on c.id = s.client_id " + " join m_office o on o.id = c.office_id and o.hierarchy like '"
+                    + currentUser.getOffice().getHierarchy() + "%'" + " where s.id = " + appTableId + ")" + " union all "
+                    + " (select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, null as entityId from m_savings_account s "
+                    + " join m_group g on g.id = s.group_id " + " join m_office o on o.id = g.office_id and o.hierarchy like '"
+                    + currentUser.getOffice().getHierarchy() + "%'" + " where s.id = " + appTableId + ")" + " ) x";
+        }
+        if (appTable.equalsIgnoreCase("m_client")) {
+            scopedSQL = "select o.id as officeId, null as groupId, c.id as clientId, null as savingsId, null as loanId, null as entityId from m_client c "
+                    + " join m_office o on o.id = c.office_id and o.hierarchy like '" + currentUser.getOffice().getHierarchy() + "%'"
+                    + " where c.id = " + appTableId;
+        }
+        if (appTable.equalsIgnoreCase("m_group") || appTable.equalsIgnoreCase("m_center")) {
+            scopedSQL = "select o.id as officeId, g.id as groupId, null as clientId, null as savingsId, null as loanId, null as entityId from m_group g "
+                    + " join m_office o on o.id = g.office_id and o.hierarchy like '" + currentUser.getOffice().getHierarchy() + "%'"
+                    + " where g.id = " + appTableId;
+        }
+        if (appTable.equalsIgnoreCase("m_office")) {
+            scopedSQL = "select o.id as officeId, null as groupId, null as clientId, null as savingsId, null as loanId, null as entityId from m_office o "
+                    + " where o.hierarchy like '" + currentUser.getOffice().getHierarchy() + "%'" + " and o.id = " + appTableId;
+        }
+
+        if (appTable.equalsIgnoreCase("m_product_loan") || appTable.equalsIgnoreCase("m_savings_product")) {
+            scopedSQL = "select null as officeId, null as groupId, null as clientId, null as savingsId, null as loanId, p.id as entityId from "
+                    + appTable + " as p WHERE p.id = " + appTableId;
+        }
+
+        if (scopedSQL == null) { throw new PlatformDataIntegrityException("error.msg.invalid.dataScopeCriteria",
+                "Application Table: " + appTable + " not catered for in data Scoping"); }
+
+        return scopedSQL;
+
+    }
+
+    private void validateAppTable(final String appTable) {
+
+        if (appTable.equalsIgnoreCase("m_loan")) { return; }
+        if (appTable.equalsIgnoreCase("m_savings_account")) { return; }
+        if (appTable.equalsIgnoreCase("m_client")) { return; }
+        if (appTable.equalsIgnoreCase("m_group")) { return; }
+        if (appTable.equalsIgnoreCase("m_center")) { return; }
+        if (appTable.equalsIgnoreCase("m_office")) { return; }
+        if (appTable.equalsIgnoreCase("m_product_loan")) { return; }
+        if (appTable.equalsIgnoreCase("m_savings_product")) { return; }
+
+        throw new PlatformDataIntegrityException("error.msg.invalid.application.table", "Invalid Application Table: " + appTable, "name",
+                appTable);
+    }
+
+    private String mapToActualAppTable(final String appTable) {
+        if (appTable.equalsIgnoreCase("m_center")) { return "m_group"; }
+        return appTable;
+    }
+
+    private List<ResultsetRowData> fillDatatableResultSetDataRows(final String sql) {
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+        final List<ResultsetRowData> resultsetDataRows = new ArrayList<>();
+
+        final SqlRowSetMetaData rsmd = rs.getMetaData();
+
+        while (rs.next()) {
+            final List<String> columnValues = new ArrayList<>();
+            for (int i = 0; i < rsmd.getColumnCount(); i++) {
+                final String columnName = rsmd.getColumnName(i + 1);
+                final String columnValue = rs.getString(columnName);
+                columnValues.add(columnValue);
+            }
+
+            final ResultsetRowData resultsetDataRow = ResultsetRowData.create(columnValues);
+            resultsetDataRows.add(resultsetDataRow);
+        }
+
+        return resultsetDataRows;
+    }
+
+    private String queryForApplicationTableName(final String datatable) {
+        final String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = '" + datatable + "'";
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+        String applicationTableName = null;
+        if (rs.next()) {
+            applicationTableName = rs.getString("application_table_name");
+        } else {
+            throw new DatatableNotFoundException(datatable);
+        }
+
+        return applicationTableName;
+    }
+
+    private String getFKField(final String applicationTableName) {
+
+        return applicationTableName.substring(2) + "_id";
+    }
+
+    private String getAddSql(final List<ResultsetColumnHeaderData> columnHeaders, final String datatable, final String fkName,
+            final Long appTableId, final Map<String, String> queryParams) {
+
+        final Map<String, String> affectedColumns = getAffectedColumns(columnHeaders, queryParams, fkName);
+
+        String pValueWrite = "";
+        String addSql = "";
+        final String singleQuote = "'";
+
+        String insertColumns = "";
+        String selectColumns = "";
+        String columnName = "";
+        String pValue = null;
+        for (final ResultsetColumnHeaderData pColumnHeader : columnHeaders) {
+            final String key = pColumnHeader.getColumnName();
+            if (affectedColumns.containsKey(key)) {
+                pValue = affectedColumns.get(key);
+                if (StringUtils.isEmpty(pValue)) {
+                    pValueWrite = "null";
+                } else {
+                    if ("bit".equalsIgnoreCase(pColumnHeader.getColumnType())) {
+                        pValueWrite = BooleanUtils.toString(BooleanUtils.toBooleanObject(pValue), "1", "0", "null");
+                    } else {
+                        pValueWrite = singleQuote + this.genericDataService.replace(pValue, singleQuote, singleQuote + singleQuote)
+                                + singleQuote;
+                    }
+
+                }
+                columnName = "`" + key + "`";
+                insertColumns += ", " + columnName;
+                selectColumns += "," + pValueWrite + " as " + columnName;
+            }
+        }
+
+        addSql = "insert into `" + datatable + "` (`" + fkName + "` " + insertColumns + ")" + " select " + appTableId + " as id"
+                + selectColumns;
+
+        logger.info(addSql);
+
+        return addSql;
+    }
+
+    /**
+     * This method is used special for ppi cases Where the score need to be
+     * computed
+     * 
+     * @param columnHeaders
+     * @param datatable
+     * @param fkName
+     * @param appTableId
+     * @param queryParams
+     * @return
+     */
+    public String getAddSqlWithScore(final List<ResultsetColumnHeaderData> columnHeaders, final String datatable, final String fkName,
+            final Long appTableId, final Map<String, String> queryParams) {
+
+        final Map<String, String> affectedColumns = getAffectedColumns(columnHeaders, queryParams, fkName);
+
+        String pValueWrite = "";
+        String scoresId = " ";
+        final String singleQuote = "'";
+
+        String insertColumns = "";
+        String selectColumns = "";
+        String columnName = "";
+        String pValue = null;
+        for (final String key : affectedColumns.keySet()) {
+            pValue = affectedColumns.get(key);
+
+            if (StringUtils.isEmpty(pValue)) {
+                pValueWrite = "null";
+            } else {
+                pValueWrite = singleQuote + this.genericDataService.replace(pValue, singleQuote, singleQuote + singleQuote) + singleQuote;
+
+                scoresId += pValueWrite + " ,";
+
+            }
+            columnName = "`" + key + "`";
+            insertColumns += ", " + columnName;
+            selectColumns += "," + pValueWrite + " as " + columnName;
+        }
+
+        scoresId = scoresId.replaceAll(" ,$", "");
+
+        String vaddSql = "insert into `" + datatable + "` (`" + fkName + "` " + insertColumns + ", `score` )" + " select " + appTableId
+                + " as id" + selectColumns + " , ( SELECT SUM( code_score ) FROM m_code_value WHERE m_code_value.id IN (" + scoresId
+                + " ) ) as score";
+
+        logger.info(vaddSql);
+
+        return vaddSql;
+    }
+
+    private String getUpdateSql(List<ResultsetColumnHeaderData> columnHeaders, final String datatable, final String keyFieldName,
+            final Long keyFieldValue, final Map<String, Object> changedColumns) {
+
+        // just updating fields that have changed since pre-update read - though
+        // its possible these values are different from the page the user was
+        // looking at and even different from the current db values (if some
+        // other update got in quick) - would need a version field for
+        // completeness but its okay to take this risk with additional fields
+        // data
+
+        if (changedColumns.size() == 0) { return null; }
+
+        String pValue = null;
+        String pValueWrite = "";
+        final String singleQuote = "'";
+        boolean firstColumn = true;
+        String sql = "update `" + datatable + "` ";
+        for (final ResultsetColumnHeaderData pColumnHeader : columnHeaders) {
+            final String key = pColumnHeader.getColumnName();
+            if (changedColumns.containsKey(key)) {
+                if (firstColumn) {
+                    sql += " set ";
+                    firstColumn = false;
+                } else {
+                    sql += ", ";
+                }
+
+                pValue = (String) changedColumns.get(key);
+                if (StringUtils.isEmpty(pValue)) {
+                    pValueWrite = "null";
+                } else {
+                    if ("bit".equalsIgnoreCase(pColumnHeader.getColumnType())) {
+                        pValueWrite = BooleanUtils.toString(BooleanUtils.toBooleanObject(pValue), "1", "0", "null");
+                    } else {
+                        pValueWrite = singleQuote + this.genericDataService.replace(pValue, singleQuote, singleQuote + singleQuote)
+                                + singleQuote;
+                    }
+                }
+                sql += "`" + key + "` = " + pValueWrite;
+            }
+        }
+
+        sql += " where " + keyFieldName + " = " + keyFieldValue;
+
+        return sql;
+    }
+
+    private Map<String, Object> getAffectedAndChangedColumns(final GenericResultsetData grs, final Map<String, String> queryParams,
+            final String fkName) {
+
+        final Map<String, String> affectedColumns = getAffectedColumns(grs.getColumnHeaders(), queryParams, fkName);
+        final Map<String, Object> affectedAndChangedColumns = new HashMap<>();
+
+        for (final String key : affectedColumns.keySet()) {
+            final String columnValue = affectedColumns.get(key);
+            final String colType = grs.getColTypeOfColumnNamed(key);
+            if (columnChanged(key, columnValue, colType, grs)) {
+                affectedAndChangedColumns.put(key, columnValue);
+            }
+        }
+
+        return affectedAndChangedColumns;
+    }
+
+    private boolean columnChanged(final String key, final String keyValue, final String colType, final GenericResultsetData grs) {
+
+        final List<String> columnValues = grs.getData().get(0).getRow();
+
+        String columnValue = null;
+        for (int i = 0; i < grs.getColumnHeaders().size(); i++) {
+
+            if (key.equals(grs.getColumnHeaders().get(i).getColumnName())) {
+                columnValue = columnValues.get(i);
+
+                if (notTheSame(columnValue, keyValue, colType)) { return true; }
+                return false;
+            }
+        }
+
+        throw new PlatformDataIntegrityException("error.msg.invalid.columnName", "Parameter Column Name: " + key + " not found");
+    }
+
+    public Map<String, String> getAffectedColumns(final List<ResultsetColumnHeaderData> columnHeaders,
+            final Map<String, String> queryParams, final String keyFieldName) {
+
+        final String dateFormat = queryParams.get("dateFormat");
+        Locale clientApplicationLocale = null;
+        final String localeQueryParam = queryParams.get("locale");
+        if (!(StringUtils.isBlank(localeQueryParam))) {
+            clientApplicationLocale = new Locale(queryParams.get("locale"));
+        }
+
+        final String underscore = "_";
+        final String space = " ";
+        String pValue = null;
+        String queryParamColumnUnderscored;
+        String columnHeaderUnderscored;
+        boolean notFound;
+
+        final Map<String, String> affectedColumns = new HashMap<>();
+        final Set<String> keys = queryParams.keySet();
+        for (final String key : keys) {
+            // ignores id and foreign key fields
+            // also ignores locale and dateformat fields that are used for
+            // validating numeric and date data
+            if (!((key.equalsIgnoreCase("id")) || (key.equalsIgnoreCase(keyFieldName)) || (key.equals("locale"))
+                    || (key.equals("dateFormat")))) {
+                notFound = true;
+                // matches incoming fields with and without underscores (spaces
+                // and underscores considered the same)
+                queryParamColumnUnderscored = this.genericDataService.replace(key, space, underscore);
+                for (final ResultsetColumnHeaderData columnHeader : columnHeaders) {
+                    if (notFound) {
+                        columnHeaderUnderscored = this.genericDataService.replace(columnHeader.getColumnName(), space, underscore);
+                        if (queryParamColumnUnderscored.equalsIgnoreCase(columnHeaderUnderscored)) {
+                            pValue = queryParams.get(key);
+                            pValue = validateColumn(columnHeader, pValue, dateFormat, clientApplicationLocale);
+                            affectedColumns.put(columnHeader.getColumnName(), pValue);
+                            notFound = false;
+                        }
+                    }
+
+                }
+                if (notFound) { throw new PlatformDataIntegrityException("error.msg.column.not.found", "Column: " + key + " Not Found"); }
+            }
+        }
+        return affectedColumns;
+    }
+
+    private String validateColumn(final ResultsetColumnHeaderData columnHeader, final String pValue, final String dateFormat,
+            final Locale clientApplicationLocale) {
+
+        String paramValue = pValue;
+        if (columnHeader.isDateDisplayType() || columnHeader.isDateTimeDisplayType() || columnHeader.isIntegerDisplayType()
+                || columnHeader.isDecimalDisplayType() || columnHeader.isBooleanDisplayType()) {
+            // only trim if string is not empty and is not null.
+            // throws a NULL pointer exception if the check below is not applied
+            paramValue = StringUtils.isNotEmpty(paramValue) ? paramValue.trim() : paramValue;
+        }
+
+        if (StringUtils.isEmpty(paramValue) && columnHeader.isMandatory()) {
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.column.mandatory", "Mandatory",
+                    columnHeader.getColumnName());
+            dataValidationErrors.add(error);
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+
+        if (StringUtils.isNotEmpty(paramValue)) {
+
+            if (columnHeader.hasColumnValues()) {
+                if (columnHeader.isCodeValueDisplayType()) {
+
+                    if (columnHeader.isColumnValueNotAllowed(paramValue)) {
+                        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                        final ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
+                                "Value not found in Allowed Value list", columnHeader.getColumnName(), paramValue);
+                        dataValidationErrors.add(error);
+                        throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                                dataValidationErrors);
+                    }
+
+                    return paramValue;
+                } else if (columnHeader.isCodeLookupDisplayType()) {
+
+                    final Integer codeLookup = Integer.valueOf(paramValue);
+                    if (columnHeader.isColumnCodeNotAllowed(codeLookup)) {
+                        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                        final ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
+                                "Value not found in Allowed Value list", columnHeader.getColumnName(), paramValue);
+                        dataValidationErrors.add(error);
+                        throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                                dataValidationErrors);
+                    }
+
+                    return paramValue;
+                } else {
+                    throw new PlatformDataIntegrityException("error.msg.invalid.columnType.", "Code: " + columnHeader.getColumnName()
+                            + " - Invalid Type " + columnHeader.getColumnType() + " (neither varchar nor int)");
+                }
+            }
+
+            if (columnHeader.isDateDisplayType()) {
+                final LocalDate tmpDate = JsonParserHelper.convertFrom(paramValue, columnHeader.getColumnName(), dateFormat,
+                        clientApplicationLocale);
+                if (tmpDate == null) {
+                    paramValue = null;
+                } else {
+                    paramValue = tmpDate.toString();
+                }
+            } else if (columnHeader.isDateTimeDisplayType()) {
+                final LocalDateTime tmpDateTime = JsonParserHelper.convertDateTimeFrom(paramValue, columnHeader.getColumnName(), dateFormat,
+                        clientApplicationLocale);
+                if (tmpDateTime == null) {
+                    paramValue = null;
+                } else {
+                    paramValue = tmpDateTime.toString();
+                }
+            } else if (columnHeader.isIntegerDisplayType()) {
+                final Integer tmpInt = this.helper.convertToInteger(paramValue, columnHeader.getColumnName(), clientApplicationLocale);
+                if (tmpInt == null) {
+                    paramValue = null;
+                } else {
+                    paramValue = tmpInt.toString();
+                }
+            } else if (columnHeader.isDecimalDisplayType()) {
+                final BigDecimal tmpDecimal = this.helper.convertFrom(paramValue, columnHeader.getColumnName(), clientApplicationLocale);
+                if (tmpDecimal == null) {
+                    paramValue = null;
+                } else {
+                    paramValue = tmpDecimal.toString();
+                }
+            } else if (columnHeader.isBooleanDisplayType()) {
+
+                final Boolean tmpBoolean = BooleanUtils.toBooleanObject(paramValue);
+                if (tmpBoolean == null) {
+                    final ApiParameterError error = ApiParameterError
+                            .parameterError(
+                                    "validation.msg.invalid.boolean.format", "The parameter " + columnHeader.getColumnName()
+                                            + " has value: " + paramValue + " which is invalid boolean value.",
+                                    columnHeader.getColumnName(), paramValue);
+                    final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                    dataValidationErrors.add(error);
+                    throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                            dataValidationErrors);
+                }
+                paramValue = tmpBoolean.toString();
+            } else if (columnHeader.isString()) {
+                if (paramValue.length() > columnHeader.getColumnLength()) {
+                    final ApiParameterError error = ApiParameterError.parameterError(
+                            "validation.msg.datatable.entry.column.exceeds.maxlength",
+                            "The column `" + columnHeader.getColumnName() + "` exceeds its defined max-length ",
+                            columnHeader.getColumnName(), paramValue);
+                    final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                    dataValidationErrors.add(error);
+                    throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                            dataValidationErrors);
+                }
+            }
+        }
+
+        return paramValue;
+    }
+
+    private String getDeleteEntriesSql(final String datatable, final String FKField, final Long appTableId) {
+
+        return "delete from `" + datatable + "` where `" + FKField + "` = " + appTableId;
+
+    }
+
+    private String getDeleteEntrySql(final String datatable, final Long datatableId) {
+
+        return "delete from `" + datatable + "` where `id` = " + datatableId;
+
+    }
+
+    private boolean notTheSame(final String currValue, final String pValue, final String colType) {
+        if (StringUtils.isEmpty(currValue) && StringUtils.isEmpty(pValue)) { return false; }
+
+        if (StringUtils.isEmpty(currValue)) { return true; }
+
+        if (StringUtils.isEmpty(pValue)) { return true; }
+
+        if ("DECIMAL".equalsIgnoreCase(colType)) {
+            final BigDecimal currentDecimal = BigDecimal.valueOf(Double.valueOf(currValue));
+            final BigDecimal newDecimal = BigDecimal.valueOf(Double.valueOf(pValue));
+
+            return currentDecimal.compareTo(newDecimal) != 0;
+        }
+
+        if (currValue.equals(pValue)) { return false; }
+
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformService.java
new file mode 100644
index 0000000..f6c0d3f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.dataqueries.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface ReportWritePlatformService {
+
+    CommandProcessingResult createReport(JsonCommand command);
+
+    CommandProcessingResult updateReport(Long reportId, JsonCommand command);
+
+    CommandProcessingResult deleteReport(Long reportId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformServiceImpl.java
new file mode 100644
index 0000000..548b156
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformServiceImpl.java
@@ -0,0 +1,254 @@
+/**
+ * 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.dataqueries.service;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.dataqueries.domain.Report;
+import org.apache.fineract.infrastructure.dataqueries.domain.ReportParameter;
+import org.apache.fineract.infrastructure.dataqueries.domain.ReportParameterRepository;
+import org.apache.fineract.infrastructure.dataqueries.domain.ReportParameterUsage;
+import org.apache.fineract.infrastructure.dataqueries.domain.ReportParameterUsageRepository;
+import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepository;
+import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException;
+import org.apache.fineract.infrastructure.dataqueries.exception.ReportParameterNotFoundException;
+import org.apache.fineract.infrastructure.dataqueries.serialization.ReportCommandFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.domain.Permission;
+import org.apache.fineract.useradministration.domain.PermissionRepository;
+import org.apache.fineract.useradministration.exception.PermissionNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+@Service
+public class ReportWritePlatformServiceImpl implements ReportWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ReportWritePlatformServiceImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final ReportCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final ReportRepository reportRepository;
+    private final ReportParameterUsageRepository reportParameterUsageRepository;
+    private final ReportParameterRepository reportParameterRepository;
+    private final PermissionRepository permissionRepository;
+
+    @Autowired
+    public ReportWritePlatformServiceImpl(final PlatformSecurityContext context,
+            final ReportCommandFromApiJsonDeserializer fromApiJsonDeserializer, final ReportRepository reportRepository,
+            final ReportParameterRepository reportParameterRepository, final ReportParameterUsageRepository reportParameterUsageRepository,
+            final PermissionRepository permissionRepository) {
+        this.context = context;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.reportRepository = reportRepository;
+        this.reportParameterRepository = reportParameterRepository;
+        this.reportParameterUsageRepository = reportParameterUsageRepository;
+        this.permissionRepository = permissionRepository;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createReport(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validate(command.json());
+
+            final Report report = Report.fromJson(command);
+            final Set<ReportParameterUsage> reportParameterUsages = assembleSetOfReportParameterUsages(report, command);
+            report.update(reportParameterUsages);
+
+            this.reportRepository.save(report);
+
+            final Permission permission = new Permission("report", report.getReportName(), "READ");
+            this.permissionRepository.save(permission);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(report.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleReportDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateReport(final Long reportId, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validate(command.json());
+
+            final Report report = this.reportRepository.findOne(reportId);
+            if (report == null) { throw new ReportNotFoundException(reportId); }
+
+            final Map<String, Object> changes = report.update(command);
+
+            if (changes.containsKey("reportParameters")) {
+                final Set<ReportParameterUsage> reportParameterUsages = assembleSetOfReportParameterUsages(report, command);
+                final boolean updated = report.update(reportParameterUsages);
+                if (!updated) {
+                    changes.remove("reportParameters");
+                }
+            }
+
+            if (!changes.isEmpty()) {
+                this.reportRepository.saveAndFlush(report);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(report.getId()) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleReportDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteReport(final Long reportId) {
+
+        final Report report = this.reportRepository.findOne(reportId);
+        if (report == null) { throw new ReportNotFoundException(reportId); }
+
+        if (report.isCoreReport()) {
+            //
+            throw new PlatformDataIntegrityException("error.msg.cant.delete.core.report", "Core Reports Can't be Deleted", "");
+        }
+
+        final Permission permission = this.permissionRepository.findOneByCode("READ" + "_" + report.getReportName());
+        if (permission == null) { throw new PermissionNotFoundException("READ" + "_" + report.getReportName()); }
+
+        this.reportRepository.delete(report);
+        this.permissionRepository.delete(permission);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(reportId) //
+                .build();
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleReportDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("unq_report_name")) {
+            final String name = command.stringValueOfParameterNamed("reportName");
+            throw new PlatformDataIntegrityException("error.msg.report.duplicate.name", "A report with name '" + name + "' already exists",
+                    "name", name);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.report.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+
+    private Set<ReportParameterUsage> assembleSetOfReportParameterUsages(final Report report, final JsonCommand command) {
+
+        Set<ReportParameterUsage> reportParameterUsages = null;
+
+        if (command.parameterExists("reportParameters")) {
+            final JsonArray reportParametersArray = command.arrayOfParameterNamed("reportParameters");
+            if (reportParametersArray != null) {
+
+                reportParameterUsages = new HashSet<>();
+
+                for (int i = 0; i < reportParametersArray.size(); i++) {
+
+                    final JsonObject jsonObject = reportParametersArray.get(i).getAsJsonObject();
+
+                    Long id = null;
+                    ReportParameterUsage reportParameterUsageItem = null;
+                    ReportParameter reportParameter = null;
+                    String reportParameterName = null;
+
+                    if (jsonObject.has("id")) {
+                        final String idStr = jsonObject.get("id").getAsString();
+                        if (StringUtils.isNotBlank(idStr)) {
+                            id = Long.parseLong(idStr);
+                        }
+                    }
+
+                    if (id != null) {
+                        // existing report parameter usage
+                        reportParameterUsageItem = this.reportParameterUsageRepository.findOne(id);
+                        if (reportParameterUsageItem == null) { throw new ReportParameterNotFoundException(id); }
+
+                        // check parameter
+                        if (jsonObject.has("parameterId")) {
+                            final Long parameterId = jsonObject.get("parameterId").getAsLong();
+                            reportParameter = this.reportParameterRepository.findOne(parameterId);
+                            if (reportParameter == null || !reportParameterUsageItem.hasParameterIdOf(parameterId)) {
+                                //
+                                throw new ReportParameterNotFoundException(parameterId);
+                            }
+                        }
+
+                        if (jsonObject.has("reportParameterName")) {
+                            reportParameterName = jsonObject.get("reportParameterName").getAsString();
+                            reportParameterUsageItem.updateParameterName(reportParameterName);
+                        }
+                    } else {
+                        // new report parameter usage
+                        if (jsonObject.has("parameterId")) {
+                            final Long parameterId = jsonObject.get("parameterId").getAsLong();
+                            reportParameter = this.reportParameterRepository.findOne(parameterId);
+                            if (reportParameter == null) { throw new ReportParameterNotFoundException(parameterId); }
+                        } else {
+                            throw new PlatformDataIntegrityException("error.msg.parameter.id.mandatory.in.report.parameter",
+                                    "parameterId column is mandatory in Report Parameter Entry");
+                        }
+
+                        if (jsonObject.has("reportParameterName")) {
+                            reportParameterName = jsonObject.get("reportParameterName").getAsString();
+                        }
+
+                        reportParameterUsageItem = new ReportParameterUsage(report, reportParameter, reportParameterName);
+                    }
+
+                    reportParameterUsages.add(reportParameterUsageItem);
+                }
+            }
+        }
+
+        return reportParameterUsages;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java
new file mode 100644
index 0000000..83537f8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java
@@ -0,0 +1,211 @@
+/**
+ * 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.documentmanagement.api;
+
+import java.io.InputStream;
+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.HeaderParam;
+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.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.UriInfo;
+
+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.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommand;
+import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
+import org.apache.fineract.infrastructure.documentmanagement.data.FileData;
+import org.apache.fineract.infrastructure.documentmanagement.service.DocumentReadPlatformService;
+import org.apache.fineract.infrastructure.documentmanagement.service.DocumentWritePlatformService;
+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;
+
+import com.sun.jersey.core.header.FormDataContentDisposition;
+import com.sun.jersey.multipart.FormDataBodyPart;
+import com.sun.jersey.multipart.FormDataParam;
+
+@Path("{entityType}/{entityId}/documents")
+@Component
+@Scope("singleton")
+public class DocumentManagementApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "parentEntityType", "parentEntityId",
+            "name", "fileName", "size", "type", "description"));
+
+    private final String SystemEntityType = "DOCUMENT";
+
+    private final PlatformSecurityContext context;
+    private final DocumentReadPlatformService documentReadPlatformService;
+    private final DocumentWritePlatformService documentWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final ToApiJsonSerializer<DocumentData> toApiJsonSerializer;
+
+    @Autowired
+    public DocumentManagementApiResource(final PlatformSecurityContext context,
+            final DocumentReadPlatformService documentReadPlatformService, final DocumentWritePlatformService documentWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper, final ToApiJsonSerializer<DocumentData> toApiJsonSerializer) {
+        this.context = context;
+        this.documentReadPlatformService = documentReadPlatformService;
+        this.documentWritePlatformService = documentWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retreiveAllDocuments(@Context final UriInfo uriInfo, @PathParam("entityType") final String entityType,
+            @PathParam("entityId") final Long entityId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.SystemEntityType);
+
+        final Collection<DocumentData> documentDatas = this.documentReadPlatformService.retrieveAllDocuments(entityType, entityId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, documentDatas, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.MULTIPART_FORM_DATA })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createDocument(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @HeaderParam("Content-Length") final Long fileSize, @FormDataParam("file") final InputStream inputStream,
+            @FormDataParam("file") final FormDataContentDisposition fileDetails, @FormDataParam("file") final FormDataBodyPart bodyPart,
+            @FormDataParam("name") final String name, @FormDataParam("description") final String description) {
+
+        /**
+         * TODO: also need to have a backup and stop reading from stream after
+         * max size is reached to protect against malicious clients
+         **/
+
+        /**
+         * TODO: need to extract the actual file type and determine if they are
+         * permissable
+         **/
+        final DocumentCommand documentCommand = new DocumentCommand(null, null, entityType, entityId, name, fileDetails.getFileName(),
+                fileSize, bodyPart.getMediaType().toString(), description, null);
+
+        final Long documentId = this.documentWritePlatformService.createDocument(documentCommand, inputStream);
+
+        return this.toApiJsonSerializer.serialize(CommandProcessingResult.resourceResult(documentId, null));
+    }
+
+    @PUT
+    @Path("{documentId}")
+    @Consumes({ MediaType.MULTIPART_FORM_DATA })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateDocument(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @PathParam("documentId") final Long documentId, @HeaderParam("Content-Length") final Long fileSize,
+            @FormDataParam("file") final InputStream inputStream, @FormDataParam("file") final FormDataContentDisposition fileDetails,
+            @FormDataParam("file") final FormDataBodyPart bodyPart, @FormDataParam("name") final String name,
+            @FormDataParam("description") final String description) {
+
+        final Set<String> modifiedParams = new HashSet<>();
+        modifiedParams.add("name");
+        modifiedParams.add("description");
+
+        /***
+         * Populate Document command based on whether a file has also been
+         * passed in as a part of the update
+         ***/
+        DocumentCommand documentCommand = null;
+        if (inputStream != null && fileDetails.getFileName() != null) {
+            modifiedParams.add("fileName");
+            modifiedParams.add("size");
+            modifiedParams.add("type");
+            modifiedParams.add("location");
+            documentCommand = new DocumentCommand(modifiedParams, documentId, entityType, entityId, name, fileDetails.getFileName(),
+                    fileSize, bodyPart.getMediaType().toString(), description, null);
+        } else {
+            documentCommand = new DocumentCommand(modifiedParams, documentId, entityType, entityId, name, null, null, null, description,
+                    null);
+        }
+        /***
+         * TODO: does not return list of changes, should be done for consistency
+         * with rest of API
+         **/
+        final CommandProcessingResult identifier = this.documentWritePlatformService.updateDocument(documentCommand, inputStream);
+
+        return this.toApiJsonSerializer.serialize(identifier);
+    }
+
+    @GET
+    @Path("{documentId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getDocument(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @PathParam("documentId") final Long documentId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.SystemEntityType);
+
+        final DocumentData documentData = this.documentReadPlatformService.retrieveDocument(entityType, entityId, documentId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, documentData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{documentId}/attachment")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_OCTET_STREAM })
+    public Response downloadFile(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @PathParam("documentId") final Long documentId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.SystemEntityType);
+
+        final FileData fileData = this.documentReadPlatformService.retrieveFileData(entityType, entityId, documentId);
+        final ResponseBuilder response = Response.ok(fileData.file());
+        response.header("Content-Disposition", "attachment; filename=\"" + fileData.name() + "\"");
+        response.header("Content-Type", fileData.contentType());
+
+        return response.build();
+    }
+
+    @DELETE
+    @Path("{documentId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteDocument(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @PathParam("documentId") final Long documentId) {
+
+        final DocumentCommand documentCommand = new DocumentCommand(null, documentId, entityType, entityId, null, null, null, null, null,
+                null);
+
+        final CommandProcessingResult documentIdentifier = this.documentWritePlatformService.deleteDocument(documentCommand);
+
+        return this.toApiJsonSerializer.serialize(documentIdentifier);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java
new file mode 100644
index 0000000..6a653d4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java
@@ -0,0 +1,231 @@
+/**
+ * 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.documentmanagement.api;
+
+import java.io.InputStream;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+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.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.domain.Base64EncodedImage;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryUtils;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryUtils.IMAGE_FILE_EXTENSION;
+import org.apache.fineract.infrastructure.documentmanagement.data.ImageData;
+import org.apache.fineract.infrastructure.documentmanagement.exception.InvalidEntityTypeForImageManagementException;
+import org.apache.fineract.infrastructure.documentmanagement.service.ImageReadPlatformService;
+import org.apache.fineract.infrastructure.documentmanagement.service.ImageWritePlatformService;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import com.lowagie.text.pdf.codec.Base64;
+import com.sun.jersey.core.header.FormDataContentDisposition;
+import com.sun.jersey.multipart.FormDataBodyPart;
+import com.sun.jersey.multipart.FormDataParam;
+
+@Path("{entity}/{entityId}/images")
+@Component
+@Scope("singleton")
+public class ImagesApiResource {
+
+    private final PlatformSecurityContext context;
+    private final ImageReadPlatformService imageReadPlatformService;
+    private final ImageWritePlatformService imageWritePlatformService;
+    private final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer;
+
+    @Autowired
+    public ImagesApiResource(final PlatformSecurityContext context, final ImageReadPlatformService readPlatformService,
+            final ImageWritePlatformService imageWritePlatformService, final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer) {
+        this.context = context;
+        this.imageReadPlatformService = readPlatformService;
+        this.imageWritePlatformService = imageWritePlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+    }
+
+    /**
+     * Upload images through multi-part form upload
+     */
+    @POST
+    @Consumes({ MediaType.MULTIPART_FORM_DATA })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String addNewClientImage(@PathParam("entity") final String entityName, @PathParam("entityId") final Long entityId,
+            @HeaderParam("Content-Length") final Long fileSize, @FormDataParam("file") final InputStream inputStream,
+            @FormDataParam("file") final FormDataContentDisposition fileDetails, @FormDataParam("file") final FormDataBodyPart bodyPart) {
+        validateEntityTypeforImage(entityName);
+        // TODO: vishwas might need more advances validation (like reading magic
+        // number) for handling malicious clients
+        // and clients not setting mime type
+        ContentRepositoryUtils.validateClientImageNotEmpty(fileDetails.getFileName());
+        ContentRepositoryUtils.validateImageMimeType(bodyPart.getMediaType().toString());
+
+        final CommandProcessingResult result = this.imageWritePlatformService.saveOrUpdateImage(entityName, entityId,
+                fileDetails.getFileName(), inputStream, fileSize);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    /**
+     * Upload image as a Data URL (essentially a base64 encoded stream)
+     */
+    @POST
+    @Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String addNewClientImage(@PathParam("entity") final String entityName, @PathParam("entityId") final Long entityId,
+            final String jsonRequestBody) {
+        validateEntityTypeforImage(entityName);
+        final Base64EncodedImage base64EncodedImage = ContentRepositoryUtils.extractImageFromDataURL(jsonRequestBody);
+
+        final CommandProcessingResult result = this.imageWritePlatformService.saveOrUpdateImage(entityName, entityId, base64EncodedImage);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    /**
+     * Returns a base 64 encoded client image Data URI
+     */
+    @GET
+    @Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.TEXT_PLAIN })
+    public Response retrieveImage(@PathParam("entity") final String entityName, @PathParam("entityId") final Long entityId,
+            @QueryParam("maxWidth") final Integer maxWidth, @QueryParam("maxHeight") final Integer maxHeight,
+            @QueryParam("output") final String output) {
+        validateEntityTypeforImage(entityName);
+        if (ENTITY_TYPE_FOR_IMAGES.CLIENTS.toString().equalsIgnoreCase(entityName)) {
+            this.context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
+        } else if (ENTITY_TYPE_FOR_IMAGES.STAFF.toString().equalsIgnoreCase(entityName)) {
+            this.context.authenticatedUser().validateHasReadPermission("STAFFIMAGE");
+        }
+
+        if (output != null && (output.equals("octet") || output.equals("inline_octet"))) { return downloadClientImage(entityName, entityId,
+                maxWidth, maxHeight, output); }
+
+        final ImageData imageData = this.imageReadPlatformService.retrieveImage(entityName, entityId);
+
+        // TODO: Need a better way of determining image type
+        String imageDataURISuffix = ContentRepositoryUtils.IMAGE_DATA_URI_SUFFIX.JPEG.getValue();
+        if (StringUtils.endsWith(imageData.location(), ContentRepositoryUtils.IMAGE_FILE_EXTENSION.GIF.getValue())) {
+            imageDataURISuffix = ContentRepositoryUtils.IMAGE_DATA_URI_SUFFIX.GIF.getValue();
+        } else if (StringUtils.endsWith(imageData.location(), ContentRepositoryUtils.IMAGE_FILE_EXTENSION.PNG.getValue())) {
+            imageDataURISuffix = ContentRepositoryUtils.IMAGE_DATA_URI_SUFFIX.PNG.getValue();
+        }
+
+        final String clientImageAsBase64Text = imageDataURISuffix + Base64.encodeBytes(imageData.getContentOfSize(maxWidth, maxHeight));
+        return Response.ok(clientImageAsBase64Text).build();
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_OCTET_STREAM })
+    public Response downloadClientImage(@PathParam("entity") final String entityName, @PathParam("entityId") final Long entityId,
+            @QueryParam("maxWidth") final Integer maxWidth, @QueryParam("maxHeight") final Integer maxHeight,
+            @QueryParam("output") String output) {
+        validateEntityTypeforImage(entityName);
+        if (ENTITY_TYPE_FOR_IMAGES.CLIENTS.toString().equalsIgnoreCase(entityName)) {
+            this.context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
+        } else if (ENTITY_TYPE_FOR_IMAGES.STAFF.toString().equalsIgnoreCase(entityName)) {
+            this.context.authenticatedUser().validateHasReadPermission("STAFFIMAGE");
+        }
+
+        final ImageData imageData = this.imageReadPlatformService.retrieveImage(entityName, entityId);
+
+        final ResponseBuilder response = Response.ok(imageData.getContentOfSize(maxWidth, maxHeight));
+        String dispositionType = "inline_octet".equals(output) ? "inline" : "attachment";
+        response.header("Content-Disposition", dispositionType + "; filename=\"" + imageData.getEntityDisplayName()
+                + IMAGE_FILE_EXTENSION.JPEG + "\"");
+
+        // TODO: Need a better way of determining image type
+
+        response.header("Content-Type", imageData.contentType());
+        return response.build();
+    }
+
+    /**
+     * This method is added only for consistency with other URL patterns and for
+     * maintaining consistency of usage of the HTTP "verb" at the client side
+     */
+    @PUT
+    @Consumes({ MediaType.MULTIPART_FORM_DATA })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateClientImage(@PathParam("entity") final String entityName, @PathParam("entityId") final Long entityId,
+            @HeaderParam("Content-Length") final Long fileSize, @FormDataParam("file") final InputStream inputStream,
+            @FormDataParam("file") final FormDataContentDisposition fileDetails, @FormDataParam("file") final FormDataBodyPart bodyPart) {
+        return addNewClientImage(entityName, entityId, fileSize, inputStream, fileDetails, bodyPart);
+    }
+
+    /**
+     * This method is added only for consistency with other URL patterns and for
+     * maintaining consistency of usage of the HTTP "verb" at the client side
+     * 
+     * Upload image as a Data URL (essentially a base64 encoded stream)
+     */
+    @PUT
+    @Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateClientImage(@PathParam("entity") final String entityName, @PathParam("entityId") final Long entityId,
+            final String jsonRequestBody) {
+        return addNewClientImage(entityName, entityId, jsonRequestBody);
+    }
+
+    @DELETE
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteClientImage(@PathParam("entity") final String entityName, @PathParam("entityId") final Long entityId) {
+        validateEntityTypeforImage(entityName);
+        this.imageWritePlatformService.deleteImage(entityName, entityId);
+        return this.toApiJsonSerializer.serialize(new CommandProcessingResult(entityId));
+    }
+
+    /*** Entities for document Management **/
+    public static enum ENTITY_TYPE_FOR_IMAGES {
+        STAFF, CLIENTS;
+
+        @Override
+        public String toString() {
+            return name().toString().toLowerCase();
+        }
+    }
+
+    private void validateEntityTypeforImage(final String entityName) {
+        if (!checkValidEntityType(entityName)) { throw new InvalidEntityTypeForImageManagementException(entityName); }
+    }
+
+    private static boolean checkValidEntityType(final String entityType) {
+        for (final ENTITY_TYPE_FOR_IMAGES entities : ENTITY_TYPE_FOR_IMAGES.values()) {
+            if (entities.name().equalsIgnoreCase(entityType)) { return true; }
+        }
+        return false;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/command/DocumentCommand.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/command/DocumentCommand.java
new file mode 100644
index 0000000..0e92728
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/command/DocumentCommand.java
@@ -0,0 +1,144 @@
+/**
+ * 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.documentmanagement.command;
+
+import java.util.Set;
+
+/**
+ * Immutable command for creating or updating details of a client identifier.
+ */
+public class DocumentCommand {
+
+    private final Long id;
+    private final String parentEntityType;
+    private final Long parentEntityId;
+    private final String name;
+    private final String description;
+
+    private String fileName;
+    private Long size;
+    private String type;
+    private String location;
+    private Integer storageType;
+
+    private final Set<String> modifiedParameters;
+
+    public DocumentCommand(final Set<String> modifiedParameters, final Long id, final String parentEntityType, final Long parentEntityId,
+            final String name, final String fileName, final Long size, final String type, final String description, final String location) {
+        this.modifiedParameters = modifiedParameters;
+        this.id = id;
+        this.parentEntityType = parentEntityType;
+        this.parentEntityId = parentEntityId;
+        this.name = name;
+        this.fileName = fileName;
+        this.size = size;
+        this.type = type;
+        this.description = description;
+        this.location = location;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getParentEntityType() {
+        return this.parentEntityType;
+    }
+
+    public Long getParentEntityId() {
+        return this.parentEntityId;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getFileName() {
+        return this.fileName;
+    }
+
+    public Long getSize() {
+        return this.size;
+    }
+
+    public String getType() {
+        return this.type;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public String getLocation() {
+        return this.location;
+    }
+
+    public Set<String> getModifiedParameters() {
+        return this.modifiedParameters;
+    }
+
+    public void setFileName(final String fileName) {
+        this.fileName = fileName;
+    }
+
+    public void setSize(final Long size) {
+        this.size = size;
+    }
+
+    public void setType(final String type) {
+        this.type = type;
+    }
+
+    public void setLocation(final String location) {
+        this.location = location;
+    }
+
+    public Integer getStorageType() {
+        return this.storageType;
+    }
+
+    public void setStorageType(final Integer storageType) {
+        this.storageType = storageType;
+    }
+
+    public boolean isNameChanged() {
+        return this.modifiedParameters.contains("name");
+    }
+
+    public boolean isFileNameChanged() {
+        return this.modifiedParameters.contains("fileName");
+    }
+
+    public boolean isSizeChanged() {
+        return this.modifiedParameters.contains("size");
+    }
+
+    public boolean isFileTypeChanged() {
+        return this.modifiedParameters.contains("type");
+    }
+
+    public boolean isDescriptionChanged() {
+        return this.modifiedParameters.contains("description");
+    }
+
+    public boolean isLocationChanged() {
+        return this.modifiedParameters.contains("location");
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/command/DocumentCommandValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/command/DocumentCommandValidator.java
new file mode 100644
index 0000000..062a9f8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/command/DocumentCommandValidator.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.infrastructure.documentmanagement.command;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+
+public class DocumentCommandValidator {
+
+    private final DocumentCommand command;
+
+    public DocumentCommandValidator(final DocumentCommand command) {
+        this.command = command;
+    }
+
+    public void validateForUpdate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("document");
+
+        baseDataValidator.reset().parameter("name").value(this.command.getName()).ignoreIfNull().notBlank();
+        baseDataValidator.reset().parameter("size").value(this.command.getSize()).ignoreIfNull().integerGreaterThanZero();
+        baseDataValidator.reset().parameter("fileName").value(this.command.getFileName()).ignoreIfNull().notBlank()
+                .notExceedingLengthOf(250);
+        baseDataValidator.reset().parameter("location").value(this.command.getLocation()).ignoreIfNull().notBlank();
+        baseDataValidator.reset().parameter("description").value(this.command.getName()).ignoreIfNull().notExceedingLengthOf(250);
+
+        baseDataValidator.reset().anyOfNotNull(this.command.getName(), this.command.getFileName(), this.command.getDescription(),
+                this.command.getLocation(), this.command.getSize());
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForCreate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("document");
+        baseDataValidator.reset().parameter("parentEntityType").value(this.command.getParentEntityType()).notBlank()
+                .notExceedingLengthOf(50);
+        baseDataValidator.reset().parameter("parentEntityId").value(this.command.getParentEntityId()).integerGreaterThanZero();
+        baseDataValidator.reset().parameter("name").value(this.command.getName()).notBlank().notExceedingLengthOf(250);
+        baseDataValidator.reset().parameter("size").value(this.command.getSize()).integerGreaterThanZero();
+        baseDataValidator.reset().parameter("fileName").value(this.command.getFileName()).notBlank().notExceedingLengthOf(250);
+        baseDataValidator.reset().parameter("description").value(this.command.getName()).notExceedingLengthOf(250);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepository.java
new file mode 100644
index 0000000..13af6a4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepository.java
@@ -0,0 +1,56 @@
+/**
+ * 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.documentmanagement.contentrepository;
+
+import java.io.InputStream;
+
+import org.apache.fineract.infrastructure.core.domain.Base64EncodedImage;
+import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommand;
+import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
+import org.apache.fineract.infrastructure.documentmanagement.data.FileData;
+import org.apache.fineract.infrastructure.documentmanagement.data.ImageData;
+import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
+
+public interface ContentRepository {
+
+    public StorageType type = null;
+
+    // TODO:Vishwas Need to move these settings to the Database
+    public static final Integer MAX_FILE_UPLOAD_SIZE_IN_MB = 5;
+
+    // TODO:Vishwas Need to move these settings to the Database
+    public static final Integer MAX_IMAGE_UPLOAD_SIZE_IN_MB = 1;
+
+    public abstract String saveFile(InputStream uploadedInputStream, DocumentCommand documentCommand);
+
+    public abstract void deleteFile(String fileName, String documentPath);
+
+    public abstract FileData fetchFile(DocumentData documentData);
+
+    public abstract String saveImage(InputStream uploadedInputStream, Long resourceId, String imageName, Long fileSize);
+
+    public abstract String saveImage(Base64EncodedImage base64EncodedImage, Long resourceId, String imageName);
+
+    public abstract void deleteImage(final Long resourceId, final String location);
+
+    public abstract ImageData fetchImage(ImageData imageData);
+
+    public abstract StorageType getStorageType();
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryFactory.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryFactory.java
new file mode 100644
index 0000000..c891eb4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryFactory.java
@@ -0,0 +1,59 @@
+/**
+ * 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.documentmanagement.contentrepository;
+
+import org.apache.fineract.infrastructure.configuration.data.S3CredentialsData;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService;
+import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ContentRepositoryFactory {
+
+    private final ApplicationContext applicationContext;
+    private final ExternalServicesPropertiesReadPlatformService externalServicesReadPlatformService;
+
+    @Autowired
+    public ContentRepositoryFactory(final ApplicationContext applicationContext,
+            final ExternalServicesPropertiesReadPlatformService externalServicesReadPlatformService) {
+        this.applicationContext = applicationContext;
+        this.externalServicesReadPlatformService = externalServicesReadPlatformService;
+    }
+
+    public ContentRepository getRepository() {
+        final ConfigurationDomainService configurationDomainServiceJpa = this.applicationContext.getBean("configurationDomainServiceJpa",
+                ConfigurationDomainService.class);
+        if (configurationDomainServiceJpa.isAmazonS3Enabled()) { return createS3DocumentStore(); }
+        return new FileSystemContentRepository();
+    }
+
+    public ContentRepository getRepository(final StorageType documentStoreType) {
+        if (documentStoreType == StorageType.FILE_SYSTEM) { return new FileSystemContentRepository(); }
+        return createS3DocumentStore();
+    }
+
+    private ContentRepository createS3DocumentStore() {
+        final S3CredentialsData s3CredentialsData = this.externalServicesReadPlatformService.getS3Credentials();
+        return new S3ContentRepository(s3CredentialsData.getBucketName(), s3CredentialsData.getSecretKey(),
+                s3CredentialsData.getAccessKey());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryUtils.java
new file mode 100644
index 0000000..a455f55
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryUtils.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.infrastructure.documentmanagement.contentrepository;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.domain.Base64EncodedImage;
+import org.apache.fineract.infrastructure.core.exception.ImageDataURLNotValidException;
+import org.apache.fineract.infrastructure.core.exception.ImageUploadException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.documentmanagement.exception.ContentManagementException;
+
+public class ContentRepositoryUtils {
+
+    public static Random random = new Random();
+
+    public static enum IMAGE_MIME_TYPE {
+        GIF("image/gif"), JPEG("image/jpeg"), PNG("image/png");
+
+        private final String value;
+
+        private IMAGE_MIME_TYPE(final String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+
+        public static IMAGE_MIME_TYPE fromFileExtension(IMAGE_FILE_EXTENSION fileExtension) {
+            switch (fileExtension) {
+                case GIF:
+                    return IMAGE_MIME_TYPE.GIF;
+                case JPG:
+                case JPEG:
+                    return IMAGE_MIME_TYPE.JPEG;
+                case PNG:
+                    return IMAGE_MIME_TYPE.PNG;
+                default:
+                    throw new IllegalArgumentException();
+            }
+        }
+    }
+
+    public static enum IMAGE_FILE_EXTENSION {
+        GIF(".gif"), JPEG(".jpeg"), JPG(".jpg"), PNG(".png");
+
+        private final String value;
+
+        private IMAGE_FILE_EXTENSION(final String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+
+        public String getValueWithoutDot() {
+            return this.value.substring(1);
+        }
+
+        public IMAGE_FILE_EXTENSION getFileExtension() {
+            switch (this) {
+                case GIF:
+                    return IMAGE_FILE_EXTENSION.GIF;
+                case JPEG:
+                    return IMAGE_FILE_EXTENSION.JPEG;
+                case PNG:
+                    return IMAGE_FILE_EXTENSION.PNG;
+                default:
+                    throw new IllegalArgumentException();
+            }
+        }
+    }
+
+    public static enum IMAGE_DATA_URI_SUFFIX {
+        GIF("data:" + IMAGE_MIME_TYPE.GIF.getValue() + ";base64,"), JPEG("data:" + IMAGE_MIME_TYPE.JPEG.getValue() + ";base64,"), PNG(
+                "data:" + IMAGE_MIME_TYPE.PNG.getValue() + ";base64,");
+
+        private final String value;
+
+        private IMAGE_DATA_URI_SUFFIX(final String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+    /**
+     * Validates that passed in Mime type maps to known image mime types
+     * 
+     * @param mimeType
+     */
+    public static void validateImageMimeType(final String mimeType) {
+        if (!(mimeType.equalsIgnoreCase(IMAGE_MIME_TYPE.GIF.getValue()) || mimeType.equalsIgnoreCase(IMAGE_MIME_TYPE.JPEG.getValue()) || mimeType
+                .equalsIgnoreCase(IMAGE_MIME_TYPE.PNG.getValue()))) { throw new ImageUploadException(); }
+    }
+
+    /**
+     * Extracts Image from a Data URL
+     * 
+     * @param mimeType
+     */
+    public static Base64EncodedImage extractImageFromDataURL(final String dataURL) {
+        String fileExtension = "";
+        String base64EncodedString = null;
+        if (StringUtils.startsWith(dataURL, IMAGE_DATA_URI_SUFFIX.GIF.getValue())) {
+            base64EncodedString = dataURL.replaceAll(IMAGE_DATA_URI_SUFFIX.GIF.getValue(), "");
+            fileExtension = IMAGE_FILE_EXTENSION.GIF.getValue();
+        } else if (StringUtils.startsWith(dataURL, IMAGE_DATA_URI_SUFFIX.PNG.getValue())) {
+            base64EncodedString = dataURL.replaceAll(IMAGE_DATA_URI_SUFFIX.PNG.getValue(), "");
+            fileExtension = IMAGE_FILE_EXTENSION.PNG.getValue();
+        } else if (StringUtils.startsWith(dataURL, IMAGE_DATA_URI_SUFFIX.JPEG.getValue())) {
+            base64EncodedString = dataURL.replaceAll(IMAGE_DATA_URI_SUFFIX.JPEG.getValue(), "");
+            fileExtension = IMAGE_FILE_EXTENSION.JPEG.getValue();
+        } else {
+            throw new ImageDataURLNotValidException();
+        }
+
+        return new Base64EncodedImage(base64EncodedString, fileExtension);
+    }
+
+    public static void validateFileSizeWithinPermissibleRange(final Long fileSize, final String name) {
+        /**
+         * Using Content-Length gives me size of the entire request, which is
+         * good enough for now for a fast fail as the length of the rest of the
+         * content i.e name and description while compared to the uploaded file
+         * size is negligible
+         **/
+        if (fileSize != null && ((fileSize / (1024 * 1024)) > ContentRepository.MAX_FILE_UPLOAD_SIZE_IN_MB)) { throw new ContentManagementException(
+                name, fileSize, ContentRepository.MAX_FILE_UPLOAD_SIZE_IN_MB); }
+    }
+
+    public static void validateClientImageNotEmpty(final String imageFileName) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        if (imageFileName == null) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg.clientImage.cannot.be.blank");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter image cannot be blank.");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), "image");
+            dataValidationErrors.add(error);
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+
+    /**
+     * Generate a random String
+     * 
+     * @return
+     */
+    public static String generateRandomString() {
+        final String characters = "abcdefghijklmnopqrstuvwxyz123456789";
+        final int length = generateRandomNumber();
+        final char[] text = new char[length];
+        for (int i = 0; i < length; i++) {
+            text[i] = characters.charAt(random.nextInt(characters.length()));
+        }
+        return new String(text);
+    }
+
+    /**
+     * Generate a random number between 5 to 16
+     * 
+     * @return
+     */
+    public static int generateRandomNumber() {
+        final Random randomGenerator = new Random();
+        return randomGenerator.nextInt(11) + 5;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/FileSystemContentRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/FileSystemContentRepository.java
new file mode 100644
index 0000000..7baec40
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/FileSystemContentRepository.java
@@ -0,0 +1,177 @@
+/**
+ * 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.documentmanagement.contentrepository;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.fineract.infrastructure.core.domain.Base64EncodedImage;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommand;
+import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
+import org.apache.fineract.infrastructure.documentmanagement.data.FileData;
+import org.apache.fineract.infrastructure.documentmanagement.data.ImageData;
+import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
+import org.apache.fineract.infrastructure.documentmanagement.exception.ContentManagementException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.lowagie.text.pdf.codec.Base64;
+
+public class FileSystemContentRepository implements ContentRepository {
+
+    private final static Logger logger = LoggerFactory.getLogger(FileSystemContentRepository.class);
+
+    public static final String FINERACT_BASE_DIR = System.getProperty("user.home") + File.separator + ".fineract";
+
+    @Override
+    public String saveFile(final InputStream uploadedInputStream, final DocumentCommand documentCommand) {
+        final String fileName = documentCommand.getFileName();
+        final String uploadDocumentLocation = generateFileParentDirectory(documentCommand.getParentEntityType(),
+                documentCommand.getParentEntityId());
+
+        ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(documentCommand.getSize(), fileName);
+        makeDirectories(uploadDocumentLocation);
+
+        final String fileLocation = uploadDocumentLocation + File.separator + fileName;
+
+        writeFileToFileSystem(fileName, uploadedInputStream, fileLocation);
+        return fileLocation;
+    }
+
+    @Override
+    public String saveImage(final InputStream uploadedInputStream, final Long resourceId, final String imageName, final Long fileSize) {
+        final String uploadImageLocation = generateClientImageParentDirectory(resourceId);
+
+        ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(fileSize, imageName);
+        makeDirectories(uploadImageLocation);
+
+        final String fileLocation = uploadImageLocation + File.separator + imageName;
+
+        writeFileToFileSystem(imageName, uploadedInputStream, fileLocation);
+        return fileLocation;
+    }
+
+    @Override
+    public String saveImage(final Base64EncodedImage base64EncodedImage, final Long resourceId, final String imageName) {
+        final String uploadImageLocation = generateClientImageParentDirectory(resourceId);
+
+        makeDirectories(uploadImageLocation);
+
+        final String fileLocation = uploadImageLocation + File.separator + imageName + base64EncodedImage.getFileExtension();
+        try {
+            final OutputStream out = new FileOutputStream(new File(fileLocation));
+            final byte[] imgBytes = Base64.decode(base64EncodedImage.getBase64EncodedString());
+            out.write(imgBytes);
+            out.flush();
+            out.close();
+        } catch (final IOException ioe) {
+            throw new ContentManagementException(imageName, ioe.getMessage());
+        }
+        return fileLocation;
+    }
+
+    @Override
+    public void deleteImage(final Long resourceId, final String location) {
+        final boolean fileDeleted = deleteFile(location);
+        if (!fileDeleted) {
+            // no need to throw an Error, simply log a warning
+            logger.warn("Unable to delete image associated with clients with Id " + resourceId);
+        }
+    }
+
+    @Override
+    public void deleteFile(final String fileName, final String documentPath) {
+        final boolean fileDeleted = deleteFile(documentPath);
+        if (!fileDeleted) { throw new ContentManagementException(fileName, null); }
+    }
+
+    private boolean deleteFile(final String documentPath) {
+        final File fileToBeDeleted = new File(documentPath);
+        return fileToBeDeleted.delete();
+    }
+
+    @Override
+    public StorageType getStorageType() {
+        return StorageType.FILE_SYSTEM;
+    }
+
+    @Override
+    public FileData fetchFile(final DocumentData documentData) {
+        final File file = new File(documentData.fileLocation());
+        return new FileData(file, documentData.fileName(), documentData.contentType());
+    }
+
+    @Override
+    public ImageData fetchImage(final ImageData imageData) {
+        final File file = new File(imageData.location());
+        imageData.updateContent(file);
+        return imageData;
+    }
+
+    /**
+     * Generate the directory path for storing the new document
+     * 
+     * @param entityType
+     * @param entityId
+     * @return
+     */
+    private String generateFileParentDirectory(final String entityType, final Long entityId) {
+        return FileSystemContentRepository.FINERACT_BASE_DIR + File.separator
+                + ThreadLocalContextUtil.getTenant().getName().replaceAll(" ", "").trim() + File.separator + "documents" + File.separator
+                + entityType + File.separator + entityId + File.separator + ContentRepositoryUtils.generateRandomString();
+    }
+
+    /**
+     * Generate directory path for storing new Image
+     */
+    private String generateClientImageParentDirectory(final Long resourceId) {
+        return FileSystemContentRepository.FINERACT_BASE_DIR + File.separator
+                + ThreadLocalContextUtil.getTenant().getName().replaceAll(" ", "").trim() + File.separator + "images" + File.separator
+                + "clients" + File.separator + resourceId;
+    }
+
+    /**
+     * Recursively create the directory if it does not exist *
+     */
+    private void makeDirectories(final String uploadDocumentLocation) {
+        if (!new File(uploadDocumentLocation).isDirectory()) {
+            new File(uploadDocumentLocation).mkdirs();
+        }
+    }
+
+    private void writeFileToFileSystem(final String fileName, final InputStream uploadedInputStream, final String fileLocation) {
+        try {
+            final OutputStream out = new FileOutputStream(new File(fileLocation));
+            int read = 0;
+            final byte[] bytes = new byte[1024];
+
+            while ((read = uploadedInputStream.read(bytes)) != -1) {
+                out.write(bytes, 0, read);
+            }
+            out.flush();
+            out.close();
+        } catch (final IOException ioException) {
+            throw new ContentManagementException(fileName, ioException.getMessage());
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/S3ContentRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/S3ContentRepository.java
new file mode 100644
index 0000000..ce7cd5e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/S3ContentRepository.java
@@ -0,0 +1,177 @@
+/**
+ * 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.documentmanagement.contentrepository;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+
+import org.apache.fineract.infrastructure.core.domain.Base64EncodedImage;
+import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommand;
+import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
+import org.apache.fineract.infrastructure.documentmanagement.data.FileData;
+import org.apache.fineract.infrastructure.documentmanagement.data.ImageData;
+import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
+import org.apache.fineract.infrastructure.documentmanagement.exception.ContentManagementException;
+import org.apache.fineract.infrastructure.documentmanagement.exception.DocumentNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.amazonaws.AmazonClientException;
+import com.amazonaws.AmazonServiceException;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.model.DeleteObjectRequest;
+import com.amazonaws.services.s3.model.GetObjectRequest;
+import com.amazonaws.services.s3.model.ObjectMetadata;
+import com.amazonaws.services.s3.model.PutObjectRequest;
+import com.amazonaws.services.s3.model.S3Object;
+import com.lowagie.text.pdf.codec.Base64;
+
+public class S3ContentRepository implements ContentRepository {
+
+    private final static Logger logger = LoggerFactory.getLogger(S3ContentRepository.class);
+
+    private final String s3BucketName;
+    private final AmazonS3 s3Client;
+
+    public S3ContentRepository(final String bucketName, final String secretKey, final String accessKey) {
+        this.s3BucketName = bucketName;
+        this.s3Client = new AmazonS3Client(new BasicAWSCredentials(accessKey, secretKey));
+    }
+
+    @Override
+    public String saveFile(final InputStream toUpload, final DocumentCommand documentCommand) {
+        final String fileName = documentCommand.getFileName();
+        ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(documentCommand.getSize(), fileName);
+
+        final String uploadDocFolder = generateFileParentDirectory(documentCommand.getParentEntityType(),
+                documentCommand.getParentEntityId());
+        final String uploadDocFullPath = uploadDocFolder + File.separator + fileName;
+
+        uploadDocument(fileName, toUpload, uploadDocFullPath);
+        return uploadDocFullPath;
+    }
+
+    @Override
+    public void deleteFile(final String documentName, final String documentPath) {
+        try {
+            deleteObjectFromS3(documentPath);
+        } catch (final AmazonClientException ace) {
+            throw new ContentManagementException(documentName, ace.getMessage());
+        }
+    }
+
+    @Override
+    public String saveImage(final InputStream toUploadInputStream, final Long resourceId, final String imageName, final Long fileSize) {
+        ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(fileSize, imageName);
+        final String uploadImageLocation = generateClientImageParentDirectory(resourceId);
+        final String fileLocation = uploadImageLocation + File.separator + imageName;
+
+        uploadDocument(imageName, toUploadInputStream, fileLocation);
+        return fileLocation;
+    }
+
+    @Override
+    public String saveImage(final Base64EncodedImage base64EncodedImage, final Long resourceId, final String imageName) {
+        final String uploadImageLocation = generateClientImageParentDirectory(resourceId);
+        final String fileLocation = uploadImageLocation + File.separator + imageName + base64EncodedImage.getFileExtension();
+        final InputStream toUploadInputStream = new ByteArrayInputStream(Base64.decode(base64EncodedImage.getBase64EncodedString()));
+
+        uploadDocument(imageName, toUploadInputStream, fileLocation);
+        return fileLocation;
+    }
+
+    @Override
+    public void deleteImage(final Long resourceId, final String location) {
+        try {
+            deleteObjectFromS3(location);
+        } catch (final AmazonServiceException ase) {
+            deleteObjectAmazonServiceExceptionMessage(ase);
+            logger.warn("Unable to delete image associated with clients with Id " + resourceId);
+        } catch (final AmazonClientException ace) {
+            deleteObjectAmazonClientExceptionMessage(ace);
+            logger.warn("Unable to delete image associated with clients with Id " + resourceId);
+        }
+    }
+
+    @Override
+    public StorageType getStorageType() {
+        return StorageType.S3;
+    }
+
+    @Override
+    public FileData fetchFile(final DocumentData documentData) throws DocumentNotFoundException {
+        FileData fileData = null;
+        final String fileName = documentData.fileName();
+        try {
+            logger.info("Downloading an object");
+            final S3Object s3object = this.s3Client.getObject(new GetObjectRequest(this.s3BucketName, documentData.fileLocation()));
+            fileData = new FileData(s3object.getObjectContent(), fileName, documentData.contentType());
+        } catch (final AmazonClientException ace) {
+            logger.error(ace.getMessage());
+            throw new DocumentNotFoundException(documentData.getParentEntityType(), documentData.getParentEntityId(), documentData.getId());
+        }
+        return fileData;
+    }
+
+    @Override
+    public ImageData fetchImage(final ImageData imageData) {
+        final S3Object s3object = this.s3Client.getObject(new GetObjectRequest(this.s3BucketName, imageData.location()));
+        imageData.updateContent(s3object.getObjectContent());
+        return imageData;
+    }
+
+    private void deleteObjectAmazonClientExceptionMessage(final AmazonClientException ace) {
+        final String message = "Caught an AmazonClientException." + "Error Message: " + ace.getMessage();
+        logger.error(message);
+    }
+
+    private void deleteObjectAmazonServiceExceptionMessage(final AmazonServiceException ase) {
+        final String message = "Caught an AmazonServiceException." + "Error Message:    " + ase.getMessage() + "HTTP Status Code: "
+                + ase.getStatusCode() + "AWS Error Code:   " + ase.getErrorCode() + "Error Type:       " + ase.getErrorType()
+                + "Request ID:       " + ase.getRequestId();
+        logger.error(message);
+    }
+
+    private String generateFileParentDirectory(final String entityType, final Long entityId) {
+        return "documents" + File.separator + entityType + File.separator + entityId + File.separator
+                + ContentRepositoryUtils.generateRandomString();
+    }
+
+    private String generateClientImageParentDirectory(final Long resourceId) {
+        return "images" + File.separator + "clients" + File.separator + resourceId;
+    }
+
+    private void deleteObjectFromS3(final String location) {
+        this.s3Client.deleteObject(new DeleteObjectRequest(this.s3BucketName, location));
+    }
+
+    private void uploadDocument(final String filename, final InputStream inputStream, final String s3UploadLocation)
+            throws ContentManagementException {
+        try {
+            logger.info("Uploading a new object to S3 from a file to " + s3UploadLocation);
+            this.s3Client.putObject(new PutObjectRequest(this.s3BucketName, s3UploadLocation, inputStream, new ObjectMetadata()));
+        } catch (final AmazonClientException ace) {
+            final String message = ace.getMessage();
+            throw new ContentManagementException(filename, message);
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/data/DocumentData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/data/DocumentData.java
new file mode 100644
index 0000000..7e36bfc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/data/DocumentData.java
@@ -0,0 +1,84 @@
+/**
+ * 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.documentmanagement.data;
+
+import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
+
+/**
+ * Immutable data object represent document being managed on platform.
+ */
+public class DocumentData {
+
+    private final Long id;
+    private final String parentEntityType;
+    private final Long parentEntityId;
+    @SuppressWarnings("unused")
+    private final String name;
+    private final String fileName;
+    @SuppressWarnings("unused")
+    private final Long size;
+    private final String type;
+    @SuppressWarnings("unused")
+    private final String description;
+    private final String location;
+    private final Integer storageType;
+
+    public DocumentData(final Long id, final String parentEntityType, final Long parentEntityId, final String name, final String fileName,
+            final Long size, final String type, final String description, final String location, final Integer storageType) {
+        this.id = id;
+        this.parentEntityType = parentEntityType;
+        this.parentEntityId = parentEntityId;
+        this.name = name;
+        this.fileName = fileName;
+        this.size = size;
+        this.type = type;
+        this.description = description;
+        this.location = location;
+        this.storageType = storageType;
+    }
+
+    public String contentType() {
+        return this.type;
+    }
+
+    public String fileName() {
+        return this.fileName;
+    }
+
+    public String fileLocation() {
+        return this.location;
+    }
+
+    public StorageType storageType() {
+        return StorageType.fromInt(this.storageType);
+    }
+
+    public String getParentEntityType() {
+        return this.parentEntityType;
+    }
+
+    public Long getParentEntityId() {
+        return this.parentEntityId;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/data/FileData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/data/FileData.java
new file mode 100644
index 0000000..e057030
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/data/FileData.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.documentmanagement.data;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FileData {
+
+    private static final Logger logger = LoggerFactory.getLogger(FileData.class);
+
+    private final File file;
+    private final String fileName;
+    private final String contentType;
+    private final InputStream inputStream;
+
+    public FileData(final File file, final String fileName, final String contentType) {
+        this.file = file;
+        this.fileName = fileName;
+        this.contentType = contentType;
+        this.inputStream = null;
+    }
+
+    public FileData(final InputStream inputStream, final String fileName, final String contentType) {
+        this.file = null;
+        this.inputStream = inputStream;
+        this.fileName = fileName;
+        this.contentType = contentType;
+    }
+
+    public String contentType() {
+        return this.contentType;
+    }
+
+    public String name() {
+        return this.fileName;
+    }
+
+    public InputStream file() {
+        try {
+            if (this.inputStream == null) { return new FileInputStream(this.file); }
+            return this.inputStream;
+        } catch (final FileNotFoundException e) {
+            logger.error(e.toString());
+            return null;
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/data/ImageData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/data/ImageData.java
new file mode 100644
index 0000000..a0373d1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/data/ImageData.java
@@ -0,0 +1,164 @@
+/**
+ * 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.documentmanagement.data;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryUtils;
+import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
+import org.apache.poi.util.IOUtils;
+
+public class ImageData {
+
+    @SuppressWarnings("unused")
+    private final Long imageId;
+    private final String location;
+    private final Integer storageType;
+    private final String entityDisplayName;
+
+    private File file;
+    private ContentRepositoryUtils.IMAGE_FILE_EXTENSION fileExtension;
+    private InputStream inputStream;
+
+    public ImageData(final Long imageId, final String location, final Integer storageType, final String entityDisplayName) {
+        this.imageId = imageId;
+        this.location = location;
+        this.storageType = storageType;
+        this.entityDisplayName = entityDisplayName;
+    }
+
+    public byte[] getContent() {
+        // TODO Vishwas Fix error handling
+        try {
+            if (this.inputStream == null) {
+                final FileInputStream fileInputStream = new FileInputStream(this.file);
+                return IOUtils.toByteArray(fileInputStream);
+            }
+
+            return IOUtils.toByteArray(this.inputStream);
+        } catch (final IOException e) {
+            return null;
+        }
+    }
+
+    public byte[] resizeImage(InputStream in, int maxWidth, int maxHeight) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        resizeImage(in, out, maxWidth, maxHeight);
+        return out.toByteArray();
+    }
+
+    public void resizeImage(InputStream in, OutputStream out, int maxWidth, int maxHeight) throws IOException {
+
+        BufferedImage src = ImageIO.read(in);
+        if (src.getWidth() <= maxWidth && src.getHeight() <= maxHeight) {
+            out.write(getContent());
+            return;
+        }
+        float widthRatio = (float) src.getWidth() / maxWidth;
+        float heightRatio = (float) src.getHeight() / maxHeight;
+        float scaleRatio = widthRatio > heightRatio ? widthRatio : heightRatio;
+
+        // TODO(lindahl): Improve compressed image quality (perhaps quality
+        // ratio)
+
+        int newWidth = (int) (src.getWidth() / scaleRatio);
+        int newHeight = (int) (src.getHeight() / scaleRatio);
+        int colorModel = fileExtension == ContentRepositoryUtils.IMAGE_FILE_EXTENSION.JPEG ? BufferedImage.TYPE_INT_RGB
+                : BufferedImage.TYPE_INT_ARGB;
+        BufferedImage target = new BufferedImage(newWidth, newHeight, colorModel);
+        Graphics2D g = target.createGraphics();
+        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+        g.drawImage(src, 0, 0, newWidth, newHeight, Color.BLACK, null);
+        g.dispose();
+        ImageIO.write(target, fileExtension != null ? fileExtension.getValueWithoutDot() : "jpeg", out);
+    }
+
+    public byte[] getContentOfSize(Integer maxWidth, Integer maxHeight) {
+        if (maxWidth == null && maxHeight == null) { return getContent(); }
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(this.file);
+            byte[] out = resizeImage(fis, maxWidth != null ? maxWidth : Integer.MAX_VALUE, maxHeight != null ? maxHeight
+                    : Integer.MAX_VALUE);
+            return out;
+        } catch (IOException ex) {
+            return null;
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException ex) {}
+            }
+        }
+    }
+
+    private void setImageContentType(String filename) {
+        fileExtension = ContentRepositoryUtils.IMAGE_FILE_EXTENSION.JPEG;
+
+        if (StringUtils.endsWith(filename.toLowerCase(), ContentRepositoryUtils.IMAGE_FILE_EXTENSION.GIF.getValue())) {
+            fileExtension = ContentRepositoryUtils.IMAGE_FILE_EXTENSION.GIF;
+        } else if (StringUtils.endsWith(filename, ContentRepositoryUtils.IMAGE_FILE_EXTENSION.PNG.getValue())) {
+            fileExtension = ContentRepositoryUtils.IMAGE_FILE_EXTENSION.PNG;
+        }
+    }
+
+    public void updateContent(final File file) {
+        this.file = file;
+        if (this.file != null) {
+            setImageContentType(this.file.getName());
+        }
+    }
+
+    public String contentType() {
+        return ContentRepositoryUtils.IMAGE_MIME_TYPE.fromFileExtension(this.fileExtension).getValue();
+    }
+
+    public StorageType storageType() {
+        return StorageType.fromInt(this.storageType);
+    }
+
+    public String name() {
+        return this.file.getName();
+    }
+
+    public String location() {
+        return this.location;
+    }
+
+    public void updateContent(final InputStream objectContent) {
+        this.inputStream = objectContent;
+    }
+
+    public String getEntityDisplayName() {
+        return this.entityDisplayName;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/Document.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/Document.java
new file mode 100644
index 0000000..eb311b6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/Document.java
@@ -0,0 +1,168 @@
+/**
+ * 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.documentmanagement.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommand;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_document")
+public class Document extends AbstractPersistable<Long> {
+
+    @Column(name = "parent_entity_type", length = 50)
+    private String parentEntityType;
+
+    @Column(name = "parent_entity_id", length = 1000)
+    private Long parentEntityId;
+
+    @Column(name = "name", length = 250)
+    private String name;
+
+    @Column(name = "file_name", length = 250)
+    private String fileName;
+
+    @Column(name = "size")
+    private Long size;
+
+    @Column(name = "type", length = 50)
+    private String type;
+
+    @Column(name = "description", length = 1000)
+    private String description;
+
+    @Column(name = "location", length = 500)
+    private String location;
+
+    @Column(name = "storage_type_enum")
+    private Integer storageType;
+
+    public Document() {}
+
+    public static Document createNew(final String parentEntityType, final Long parentEntityId, final String name, final String fileName,
+            final Long size, final String type, final String description, final String location, final StorageType storageType) {
+        return new Document(parentEntityType, parentEntityId, name, fileName, size, type, description, location, storageType);
+    }
+
+    private Document(final String parentEntityType, final Long parentEntityId, final String name, final String fileName, final Long size,
+            final String type, final String description, final String location, final StorageType storageType) {
+        this.parentEntityType = StringUtils.defaultIfEmpty(parentEntityType, null);
+        this.parentEntityId = parentEntityId;
+        this.name = StringUtils.defaultIfEmpty(name, null);
+        this.fileName = StringUtils.defaultIfEmpty(fileName, null);
+        this.size = size;
+        this.type = StringUtils.defaultIfEmpty(type, null);
+        this.description = StringUtils.defaultIfEmpty(description, null);
+        this.location = StringUtils.defaultIfEmpty(location, null);
+        this.storageType = storageType.getValue();
+    }
+
+    public void update(final DocumentCommand command) {
+        if (command.isDescriptionChanged()) {
+            this.description = command.getDescription();
+        }
+        if (command.isFileNameChanged()) {
+            this.fileName = command.getFileName();
+        }
+        if (command.isFileTypeChanged()) {
+            this.type = command.getType();
+        }
+        if (command.isLocationChanged()) {
+            this.location = command.getLocation();
+        }
+        if (command.isNameChanged()) {
+            this.name = command.getName();
+        }
+        if (command.isSizeChanged()) {
+            this.size = command.getSize();
+        }
+    }
+
+    public String getParentEntityType() {
+        return this.parentEntityType;
+    }
+
+    public void setParentEntityType(final String parentEntityType) {
+        this.parentEntityType = parentEntityType;
+    }
+
+    public Long getParentEntityId() {
+        return this.parentEntityId;
+    }
+
+    public void setParentEntityId(final Long parentEntityId) {
+        this.parentEntityId = parentEntityId;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public String getFileName() {
+        return this.fileName;
+    }
+
+    public void setFileName(final String fileName) {
+        this.fileName = fileName;
+    }
+
+    public Long getSize() {
+        return this.size;
+    }
+
+    public void setSize(final Long size) {
+        this.size = size;
+    }
+
+    public String getType() {
+        return this.type;
+    }
+
+    public void setType(final String type) {
+        this.type = type;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    public String getLocation() {
+        return this.location;
+    }
+
+    public void setLocation(final String location) {
+        this.location = location;
+    }
+
+    public StorageType storageType() {
+        return StorageType.fromInt(this.storageType);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/DocumentRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/DocumentRepository.java
new file mode 100644
index 0000000..0597fbc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/DocumentRepository.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.infrastructure.documentmanagement.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface DocumentRepository extends JpaRepository<Document, Long>, JpaSpecificationExecutor<Document> {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/Image.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/Image.java
new file mode 100644
index 0000000..c27a182
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/Image.java
@@ -0,0 +1,62 @@
+/**
+ * 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.documentmanagement.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_image")
+public final class Image extends AbstractPersistable<Long> {
+
+    @Column(name = "location", length = 500)
+    private String location;
+
+    @Column(name = "storage_type_enum")
+    private Integer storageType;
+
+    public Image(final String location, final StorageType storageType) {
+        this.location = location;
+        this.storageType = storageType.getValue();
+    }
+
+    protected Image() {
+
+    }
+
+    public String getLocation() {
+        return this.location;
+    }
+
+    public Integer getStorageType() {
+        return this.storageType;
+    }
+
+    public void setLocation(final String location) {
+        this.location = location;
+    }
+
+    public void setStorageType(final Integer storageType) {
+        this.storageType = storageType;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/ImageRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/ImageRepository.java
new file mode 100644
index 0000000..f336a78
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/ImageRepository.java
@@ -0,0 +1,25 @@
+/**
+ * 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.documentmanagement.domain;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ImageRepository extends JpaRepository<Image, Long>, JpaSpecificationExecutor<Client> {}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/StorageType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/StorageType.java
new file mode 100644
index 0000000..0a2757a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/domain/StorageType.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.infrastructure.documentmanagement.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum StorageType {
+    FILE_SYSTEM(1), S3(2);
+
+    private Integer value;
+
+    StorageType(final Integer value) {
+        this.value = value;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    private static final Map<Integer, StorageType> intToEnumMap = new HashMap<>();
+    static {
+        for (final StorageType type : StorageType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static StorageType fromInt(final int i) {
+        final StorageType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/ContentManagementException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/ContentManagementException.java
new file mode 100644
index 0000000..788917e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/ContentManagementException.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.infrastructure.documentmanagement.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ContentManagementException extends AbstractPlatformDomainRuleException {
+
+    public ContentManagementException(final String filename, final String message) {
+        super("error.msg.document.save",
+                "Error while manipulating file " + filename + " due to a File system / Amazon S3 issue " + message, filename, message);
+    }
+
+    public ContentManagementException(final String name, final Long fileSize, final int maxFileSize) {
+        super("error.msg.document.file.too.big", "Unable to save the document with name" + name + " since its file Size of " + fileSize
+                / (1024 * 1024) + " MB exceeds the max permissable file size  of " + maxFileSize + " MB", name, fileSize);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/DocumentNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/DocumentNotFoundException.java
new file mode 100644
index 0000000..5773795
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/DocumentNotFoundException.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.infrastructure.documentmanagement.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class DocumentNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public DocumentNotFoundException(final String entityType, final Long entityId, final Long id) {
+        super("error.msg.document.id.invalid", "Document with identifier " + id + " does not exist for the " + entityType
+                + " with Identifier " + entityId, id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/InvalidEntityTypeForDocumentManagementException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/InvalidEntityTypeForDocumentManagementException.java
new file mode 100644
index 0000000..5cfafa6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/InvalidEntityTypeForDocumentManagementException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.documentmanagement.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when document management functionality is
+ * invoked for invalid Entity Types
+ */
+public class InvalidEntityTypeForDocumentManagementException extends AbstractPlatformResourceNotFoundException {
+
+    public InvalidEntityTypeForDocumentManagementException(final String entityType) {
+        super("error.documentmanagement.entitytype.invalid", "Document Management is not support for the Entity Type: " + entityType,
+                entityType);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/InvalidEntityTypeForImageManagementException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/InvalidEntityTypeForImageManagementException.java
new file mode 100644
index 0000000..679a4a1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/InvalidEntityTypeForImageManagementException.java
@@ -0,0 +1,31 @@
+/**

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements. See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ * http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied. See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.fineract.infrastructure.documentmanagement.exception;

+

+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;

+

+/**

+ * Runtime exception for invalid image types

+ */

+public class InvalidEntityTypeForImageManagementException extends AbstractPlatformResourceNotFoundException {

+

+    public InvalidEntityTypeForImageManagementException(String imageType) {

+        super("error.imagemanagement.entitytype.invalid", "Image Management is not support for the Entity Type: " + imageType);

+    }

+}

diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentReadPlatformService.java
new file mode 100644
index 0000000..0d41858
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentReadPlatformService.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.documentmanagement.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
+import org.apache.fineract.infrastructure.documentmanagement.data.FileData;
+
+public interface DocumentReadPlatformService {
+
+    Collection<DocumentData> retrieveAllDocuments(String entityType, Long entityId);
+
+    FileData retrieveFileData(String entityType, Long entityId, Long documentId);
+
+    DocumentData retrieveDocument(String entityType, Long entityId, Long documentId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentReadPlatformServiceImpl.java
new file mode 100644
index 0000000..5e20bb3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentReadPlatformServiceImpl.java
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.documentmanagement.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepository;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryFactory;
+import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
+import org.apache.fineract.infrastructure.documentmanagement.data.FileData;
+import org.apache.fineract.infrastructure.documentmanagement.exception.DocumentNotFoundException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+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 DocumentReadPlatformServiceImpl implements DocumentReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final ContentRepositoryFactory contentRepositoryFactory;
+
+    @Autowired
+    public DocumentReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final ContentRepositoryFactory documentStoreFactory) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.contentRepositoryFactory = documentStoreFactory;
+    }
+
+    @Override
+    public Collection<DocumentData> retrieveAllDocuments(final String entityType, final Long entityId) {
+
+        this.context.authenticatedUser();
+
+        // TODO verify if the entities are valid and a user
+        // has data
+        // scope for the particular entities
+        final DocumentMapper mapper = new DocumentMapper(true, true);
+        final String sql = "select " + mapper.schema() + " order by d.id";
+        return this.jdbcTemplate.query(sql, mapper, new Object[] { entityType, entityId });
+    }
+
+    @Override
+    public FileData retrieveFileData(final String entityType, final Long entityId, final Long documentId) {
+        try {
+            final DocumentMapper mapper = new DocumentMapper(false, false);
+            final DocumentData documentData = fetchDocumentDetails(entityType, entityId, documentId, mapper);
+            final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository(documentData.storageType());
+            return contentRepository.fetchFile(documentData);
+        } catch (final EmptyResultDataAccessException e) {
+            throw new DocumentNotFoundException(entityType, entityId, documentId);
+        }
+    }
+
+    @Override
+    public DocumentData retrieveDocument(final String entityType, final Long entityId, final Long documentId) {
+        try {
+            final DocumentMapper mapper = new DocumentMapper(true, true);
+            return fetchDocumentDetails(entityType, entityId, documentId, mapper);
+        } catch (final EmptyResultDataAccessException e) {
+            throw new DocumentNotFoundException(entityType, entityId, documentId);
+        }
+    }
+
+    /**
+     * @param entityType
+     * @param entityId
+     * @param documentId
+     * @param mapper
+     * @return
+     */
+    private DocumentData fetchDocumentDetails(final String entityType, final Long entityId, final Long documentId,
+            final DocumentMapper mapper) {
+        final String sql = "select " + mapper.schema() + " and d.id=? ";
+        return this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { entityType, entityId, documentId });
+    }
+
+    private static final class DocumentMapper implements RowMapper<DocumentData> {
+
+        private final boolean hideLocation;
+        private final boolean hideStorageType;
+
+        public DocumentMapper(final boolean hideLocation, final boolean hideStorageType) {
+            this.hideLocation = hideLocation;
+            this.hideStorageType = hideStorageType;
+        }
+
+        public String schema() {
+            return "d.id as id, d.parent_entity_type as parentEntityType, d.parent_entity_id as parentEntityId, d.name as name, "
+                    + " d.file_name as fileName, d.size as fileSize, d.type as fileType, "
+                    + " d.description as description, d.location as location," + " d.storage_type_enum as storageType"
+                    + " from m_document d where d.parent_entity_type=? and d.parent_entity_id=?";
+        }
+
+        @Override
+        public DocumentData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final Long parentEntityId = JdbcSupport.getLong(rs, "parentEntityId");
+            final Long fileSize = JdbcSupport.getLong(rs, "fileSize");
+            final String parentEntityType = rs.getString("parentEntityType");
+            final String name = rs.getString("name");
+            final String fileName = rs.getString("fileName");
+            final String fileType = rs.getString("fileType");
+            final String description = rs.getString("description");
+            String location = null;
+            Integer storageType = null;
+            if (!this.hideLocation) {
+                location = rs.getString("location");
+            }
+            if (!this.hideStorageType) {
+                storageType = rs.getInt("storageType");
+            }
+            return new DocumentData(id, parentEntityType, parentEntityId, name, fileName, fileSize, fileType, description, location,
+                    storageType);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformService.java
new file mode 100644
index 0000000..0e08deb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformService.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.documentmanagement.service;
+
+import java.io.InputStream;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommand;
+import org.springframework.security.access.prepost.PreAuthorize;
+
+public interface DocumentWritePlatformService {
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'CREATE_DOCUMENT')")
+    Long createDocument(DocumentCommand documentCommand, InputStream inputStream);
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'UPDATE_DOCUMENT')")
+    CommandProcessingResult updateDocument(DocumentCommand documentCommand, InputStream inputStream);
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'DELETE_DOCUMENT')")
+    CommandProcessingResult deleteDocument(DocumentCommand documentCommand);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..b229db5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,171 @@
+/**
+ * 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.documentmanagement.service;
+
+import java.io.InputStream;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommand;
+import org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommandValidator;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepository;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryFactory;
+import org.apache.fineract.infrastructure.documentmanagement.domain.Document;
+import org.apache.fineract.infrastructure.documentmanagement.domain.DocumentRepository;
+import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
+import org.apache.fineract.infrastructure.documentmanagement.exception.ContentManagementException;
+import org.apache.fineract.infrastructure.documentmanagement.exception.DocumentNotFoundException;
+import org.apache.fineract.infrastructure.documentmanagement.exception.InvalidEntityTypeForDocumentManagementException;
+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 DocumentWritePlatformServiceJpaRepositoryImpl implements DocumentWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(DocumentWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final DocumentRepository documentRepository;
+    private final ContentRepositoryFactory contentRepositoryFactory;
+
+    @Autowired
+    public DocumentWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final DocumentRepository documentRepository, final ContentRepositoryFactory documentStoreFactory) {
+        this.context = context;
+        this.documentRepository = documentRepository;
+        this.contentRepositoryFactory = documentStoreFactory;
+    }
+
+    @Transactional
+    @Override
+    public Long createDocument(final DocumentCommand documentCommand, final InputStream inputStream) {
+        try {
+            this.context.authenticatedUser();
+
+            final DocumentCommandValidator validator = new DocumentCommandValidator(documentCommand);
+
+            validateParentEntityType(documentCommand);
+
+            validator.validateForCreate();
+
+            final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository();
+
+            final String fileLocation = contentRepository.saveFile(inputStream, documentCommand);
+
+            final Document document = Document.createNew(documentCommand.getParentEntityType(), documentCommand.getParentEntityId(),
+                    documentCommand.getName(), documentCommand.getFileName(), documentCommand.getSize(), documentCommand.getType(),
+                    documentCommand.getDescription(), fileLocation, contentRepository.getStorageType());
+
+            this.documentRepository.save(document);
+
+            return document.getId();
+        } catch (final DataIntegrityViolationException dve) {
+            logger.error(dve.getMessage(), dve);
+            throw new PlatformDataIntegrityException("error.msg.document.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.");
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateDocument(final DocumentCommand documentCommand, final InputStream inputStream) {
+        try {
+            this.context.authenticatedUser();
+
+            String oldLocation = null;
+            final DocumentCommandValidator validator = new DocumentCommandValidator(documentCommand);
+            validator.validateForUpdate();
+            // TODO check if entity id is valid and within data scope for the
+            // user
+            final Document documentForUpdate = this.documentRepository.findOne(documentCommand.getId());
+            if (documentForUpdate == null) { throw new DocumentNotFoundException(documentCommand.getParentEntityType(),
+                    documentCommand.getParentEntityId(), documentCommand.getId()); }
+
+            final StorageType documentStoreType = documentForUpdate.storageType();
+            oldLocation = documentForUpdate.getLocation();
+            if (inputStream != null && documentCommand.isFileNameChanged()) {
+                final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository();
+                documentCommand.setLocation(contentRepository.saveFile(inputStream, documentCommand));
+                documentCommand.setStorageType(contentRepository.getStorageType().getValue());
+            }
+
+            documentForUpdate.update(documentCommand);
+
+            if (inputStream != null && documentCommand.isFileNameChanged()) {
+                final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository(documentStoreType);
+                contentRepository.deleteFile(documentCommand.getName(), oldLocation);
+            }
+
+            this.documentRepository.saveAndFlush(documentForUpdate);
+
+            return new CommandProcessingResult(documentForUpdate.getId());
+        } catch (final DataIntegrityViolationException dve) {
+            logger.error(dve.getMessage(), dve);
+            throw new PlatformDataIntegrityException("error.msg.document.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.");
+        } catch (final ContentManagementException cme) {
+            logger.error(cme.getMessage(), cme);
+            throw new ContentManagementException(documentCommand.getName(), cme.getMessage());
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteDocument(final DocumentCommand documentCommand) {
+        this.context.authenticatedUser();
+
+        validateParentEntityType(documentCommand);
+        // TODO: Check document is present under this entity Id
+        final Document document = this.documentRepository.findOne(documentCommand.getId());
+        if (document == null) { throw new DocumentNotFoundException(documentCommand.getParentEntityType(),
+                documentCommand.getParentEntityId(), documentCommand.getId()); }
+        this.documentRepository.delete(document);
+
+        final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository(document.storageType());
+        contentRepository.deleteFile(document.getName(), document.getLocation());
+        return new CommandProcessingResult(document.getId());
+    }
+
+    private void validateParentEntityType(final DocumentCommand documentCommand) {
+        if (!checkValidEntityType(documentCommand.getParentEntityType())) { throw new InvalidEntityTypeForDocumentManagementException(
+                documentCommand.getParentEntityType()); }
+    }
+
+    private static boolean checkValidEntityType(final String entityType) {
+        for (final DOCUMENT_MANAGEMENT_ENTITY entities : DOCUMENT_MANAGEMENT_ENTITY.values()) {
+            if (entities.name().equalsIgnoreCase(entityType)) { return true; }
+        }
+        return false;
+    }
+
+    /*** Entities for document Management **/
+    public static enum DOCUMENT_MANAGEMENT_ENTITY {
+        CLIENTS, CLIENT_IDENTIFIERS, STAFF, LOANS, SAVINGS, GROUPS;
+
+        @Override
+        public String toString() {
+            return name().toString().toLowerCase();
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformService.java
new file mode 100755
index 0000000..c283664
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformService.java
@@ -0,0 +1,27 @@
+/**
+ * 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.documentmanagement.service;
+
+import org.apache.fineract.infrastructure.documentmanagement.data.ImageData;
+
+public interface ImageReadPlatformService {
+
+    ImageData retrieveImage(String entityType, Long entityId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImpl.java
new file mode 100755
index 0000000..9348268
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImpl.java
@@ -0,0 +1,113 @@
+/**
+ * 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.documentmanagement.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.documentmanagement.api.ImagesApiResource.ENTITY_TYPE_FOR_IMAGES;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepository;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryFactory;
+import org.apache.fineract.infrastructure.documentmanagement.data.ImageData;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ImageNotFoundException;
+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 ImageReadPlatformServiceImpl implements ImageReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final ContentRepositoryFactory contentRepositoryFactory;
+    private final ClientRepositoryWrapper clientRepositoryWrapper;
+    private final StaffRepositoryWrapper staffRepositoryWrapper;
+
+    @Autowired
+    public ImageReadPlatformServiceImpl(final RoutingDataSource dataSource, final ContentRepositoryFactory documentStoreFactory,
+            final ClientRepositoryWrapper clientRepositoryWrapper, StaffRepositoryWrapper staffRepositoryWrapper) {
+        this.staffRepositoryWrapper = staffRepositoryWrapper;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.contentRepositoryFactory = documentStoreFactory;
+        this.clientRepositoryWrapper = clientRepositoryWrapper;
+    }
+
+    private static final class ImageMapper implements RowMapper<ImageData> {
+
+        private final String entityDisplayName;
+
+        public ImageMapper(final String entityDisplayName) {
+            this.entityDisplayName = entityDisplayName;
+        }
+
+        public String schema(String entityType) {
+            StringBuilder builder = new StringBuilder("image.id as id, image.location as location, image.storage_type_enum as storageType ");
+            if (ENTITY_TYPE_FOR_IMAGES.CLIENTS.toString().equalsIgnoreCase(entityType)) {
+                builder.append(" from m_image image , m_client client " + " where client.image_id = image.id and client.id=?");
+            } else if (ENTITY_TYPE_FOR_IMAGES.STAFF.toString().equalsIgnoreCase(entityType)) {
+                builder.append("from m_image image , m_staff staff " + " where staff.image_id = image.id and staff.id=?");
+            }
+            return builder.toString();
+        }
+
+        @Override
+        public ImageData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String location = rs.getString("location");
+            final Integer storageType = JdbcSupport.getInteger(rs, "storageType");
+            return new ImageData(id, location, storageType, this.entityDisplayName);
+        }
+    }
+
+    @Override
+    public ImageData retrieveImage(String entityType, final Long entityId) {
+        try {
+            Object owner;
+            String displayName = null;
+            if (ENTITY_TYPE_FOR_IMAGES.CLIENTS.toString().equalsIgnoreCase(entityType)) {
+                owner = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId);
+                displayName = ((Client) owner).getDisplayName();
+            } else if (ENTITY_TYPE_FOR_IMAGES.STAFF.toString().equalsIgnoreCase(entityType)) {
+                owner = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId);
+                displayName = ((Staff) owner).displayName();
+            }
+            final ImageMapper imageMapper = new ImageMapper(displayName);
+
+            final String sql = "select " + imageMapper.schema(entityType);
+
+            final ImageData imageData = this.jdbcTemplate.queryForObject(sql, imageMapper, new Object[] { entityId });
+            final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository(imageData.storageType());
+            final ImageData result = contentRepository.fetchImage(imageData);
+
+            if (result.getContent() == null) { throw new ImageNotFoundException(entityType, entityId); }
+
+            return result;
+        } catch (final EmptyResultDataAccessException e) {
+            throw new ImageNotFoundException("clients", entityId);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformService.java
new file mode 100755
index 0000000..652f7bc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformService.java
@@ -0,0 +1,37 @@
+/**
+ * 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.documentmanagement.service;
+
+import java.io.InputStream;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.domain.Base64EncodedImage;
+import org.springframework.security.access.prepost.PreAuthorize;
+
+public interface ImageWritePlatformService {
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'CREATE_CLIENTIMAGE','CREATE_STAFFIMAGE')")
+    CommandProcessingResult saveOrUpdateImage(String entityName, Long entityId, String imageName, InputStream inputStream, Long fileSize);
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'CREATE_CLIENTIMAGE','CREATE_STAFFIMAGE')")
+    CommandProcessingResult saveOrUpdateImage(String entityName, Long entityId, Base64EncodedImage encodedImage);
+
+    @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'DELETE_CLIENTIMAGE','DELETE_STAFFIMAGE')")
+    CommandProcessingResult deleteImage(String entityName, Long entityId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..46b45fd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,168 @@
+/**
+ * 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.documentmanagement.service;
+
+import java.io.InputStream;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.domain.Base64EncodedImage;
+import org.apache.fineract.infrastructure.documentmanagement.api.ImagesApiResource.ENTITY_TYPE_FOR_IMAGES;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepository;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryFactory;
+import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
+import org.apache.fineract.infrastructure.documentmanagement.domain.ImageRepository;
+import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class ImageWritePlatformServiceJpaRepositoryImpl implements ImageWritePlatformService {
+
+    private final ContentRepositoryFactory contentRepositoryFactory;
+    private final ClientRepositoryWrapper clientRepositoryWrapper;
+    private final ImageRepository imageRepository;
+    private final StaffRepositoryWrapper staffRepositoryWrapper;
+
+    @Autowired
+    public ImageWritePlatformServiceJpaRepositoryImpl(final ContentRepositoryFactory documentStoreFactory,
+            final ClientRepositoryWrapper clientRepositoryWrapper, final ImageRepository imageRepository,
+            StaffRepositoryWrapper staffRepositoryWrapper) {
+        this.contentRepositoryFactory = documentStoreFactory;
+        this.clientRepositoryWrapper = clientRepositoryWrapper;
+        this.imageRepository = imageRepository;
+        this.staffRepositoryWrapper = staffRepositoryWrapper;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult saveOrUpdateImage(String entityName, final Long clientId, final String imageName,
+            final InputStream inputStream, final Long fileSize) {
+        Object owner = deletePreviousImage(entityName, clientId);
+
+        final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository();
+        final String imageLocation = contentRepository.saveImage(inputStream, clientId, imageName, fileSize);
+        return updateImage(owner, imageLocation, contentRepository.getStorageType());
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult saveOrUpdateImage(String entityName, final Long clientId, final Base64EncodedImage encodedImage) {
+        Object owner = deletePreviousImage(entityName, clientId);
+
+        final ContentRepository contenRepository = this.contentRepositoryFactory.getRepository();
+        final String imageLocation = contenRepository.saveImage(encodedImage, clientId, "image");
+
+        return updateImage(owner, imageLocation, contenRepository.getStorageType());
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteImage(String entityName, final Long clientId) {
+        Object owner = null;
+        Image image = null;
+        if (ENTITY_TYPE_FOR_IMAGES.CLIENTS.toString().equals(entityName)) {
+            owner = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
+            Client client = (Client) owner;
+            image = client.getImage();
+            client.setImage(null);
+            this.clientRepositoryWrapper.save(client);
+
+        } else if (ENTITY_TYPE_FOR_IMAGES.STAFF.toString().equals(entityName)) {
+            owner = this.staffRepositoryWrapper.findOneWithNotFoundDetection(clientId);
+            Staff staff = (Staff) owner;
+            image = staff.getImage();
+            staff.setImage(null);
+            this.staffRepositoryWrapper.save(staff);
+
+        }
+        // delete image from the file system
+        if (image != null) {
+            final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository(StorageType.fromInt(image
+                    .getStorageType()));
+            contentRepository.deleteImage(clientId, image.getLocation());
+            this.imageRepository.delete(image);
+        }
+
+        return new CommandProcessingResult(clientId);
+    }
+
+    /**
+     * @param entityName
+     * @param entityId
+     * @return
+     */
+    private Object deletePreviousImage(String entityName, final Long entityId) {
+        Object owner = null;
+        Image image = null;
+        if (ENTITY_TYPE_FOR_IMAGES.CLIENTS.toString().equals(entityName)) {
+            Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId);
+            image = client.getImage();
+            owner = client;
+        } else if (ENTITY_TYPE_FOR_IMAGES.STAFF.toString().equals(entityName)) {
+            Staff staff = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId);
+            image = staff.getImage();
+            owner = staff;
+        }
+        if (image != null) {
+            final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository(StorageType.fromInt(image
+                    .getStorageType()));
+            contentRepository.deleteImage(entityId, image.getLocation());
+        }
+        return owner;
+    }
+
+    private CommandProcessingResult updateImage(final Object owner, final String imageLocation, final StorageType storageType) {
+        Image image = null;
+        Long clientId = null;
+        if (owner instanceof Client) {
+            Client client = (Client) owner;
+            image = client.getImage();
+            clientId = client.getId();
+            image = createImage(image, imageLocation, storageType);
+            client.setImage(image);
+            this.clientRepositoryWrapper.save(client);
+        } else if (owner instanceof Staff) {
+            Staff staff = (Staff) owner;
+            image = staff.getImage();
+            clientId = staff.getId();
+            image = createImage(image, imageLocation, storageType);
+            staff.setImage(image);
+            this.staffRepositoryWrapper.save(staff);
+        }
+
+        this.imageRepository.save(image);
+        return new CommandProcessingResult(clientId);
+    }
+
+    private Image createImage(Image image, final String imageLocation, final StorageType storageType) {
+        if (image == null) {
+            image = new Image(imageLocation, storageType);
+        } else {
+            image.setLocation(imageLocation);
+            image.setStorageType(storageType.getValue());
+        }
+        return image;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/FineractEntityAccessConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/FineractEntityAccessConstants.java
new file mode 100644
index 0000000..330c059
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/FineractEntityAccessConstants.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.infrastructure.entityaccess;
+
+public class FineractEntityAccessConstants {
+
+	public static final String GLOBAL_CONFIG_FOR_OFFICE_SPECIFIC_PRODUCTS = "office-specific-products-enabled";
+	public static final String GLOBAL_CONFIG_FOR_RESTRICT_PRODUCTS_TO_USER_OFFICE = "restrict-products-to-user-office";
+    public static final String ENTITY_ACCESS_CODENAME = "Entity to Entity Access Types";
+
+    /***
+     * Enum of all parameters passed in while creating/updating an entity access
+     ***/
+    public static enum ENTITY_ACCESS_JSON_INPUT_PARAMS {
+        ENTITY_TYPE("entityType"),
+    	ENTITY_ID("entityId"),
+        ENTITY_ACCESS_TYPE_ID("entityAccessTypeId"),
+        SECOND_ENTITY_TYPE("secondEntityType"),
+    	SECOND_ENTITY_ID("secondEntityId")
+        ;
+
+        private final String value;
+
+        private ENTITY_ACCESS_JSON_INPUT_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/api/FineractEntityApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/api/FineractEntityApiResource.java
new file mode 100644
index 0000000..173c827
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/api/FineractEntityApiResource.java
@@ -0,0 +1,169 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.entityaccess.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityRelationData;
+import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityToEntityMappingData;
+import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessReadService;
+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("/entitytoentitymapping")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+public class FineractEntityApiResource {
+
+    private final PlatformSecurityContext context;
+    private final FineractEntityAccessReadService readPlatformService;
+    private final DefaultToApiJsonSerializer<FineractEntityRelationData> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<FineractEntityToEntityMappingData> toApiJsonSerializerOfficeToLoanProducts;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public FineractEntityApiResource(final PlatformSecurityContext context, final FineractEntityAccessReadService readPlatformService,
+            final DefaultToApiJsonSerializer<FineractEntityRelationData> toApiJsonSerializer,
+            final DefaultToApiJsonSerializer<FineractEntityToEntityMappingData> toApiJsonSerializerOfficeToLoanProducts,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.toApiJsonSerializerOfficeToLoanProducts = toApiJsonSerializerOfficeToLoanProducts;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(FineractEntityApiResourceConstants.FINERACT_ENTITY_RESOURCE_NAME);
+
+        final Collection<FineractEntityRelationData> entityMappings = this.readPlatformService.retrieveAllSupportedMappingTypes();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, entityMappings, FineractEntityApiResourceConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("/{mapId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("mapId") final Long mapId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(FineractEntityApiResourceConstants.FINERACT_ENTITY_RESOURCE_NAME);
+
+        final Collection<FineractEntityToEntityMappingData> entityToEntityMappings = this.readPlatformService.retrieveOneMapping(mapId);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializerOfficeToLoanProducts.serialize(settings, entityToEntityMappings,
+                FineractEntityApiResourceConstants.FETCH_ENTITY_TO_ENTITY_MAPPINGS);
+    }
+
+    @GET
+    @Path("/{mapId}/{fromId}/{toId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getEntityToEntityMappings(@PathParam("mapId") final Long mapId, @PathParam("fromId") final Long fromId,
+            @PathParam("toId") final Long toId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(FineractEntityApiResourceConstants.FINERACT_ENTITY_RESOURCE_NAME);
+
+        final Collection<FineractEntityToEntityMappingData> entityToEntityMappings = this.readPlatformService.retrieveEntityToEntityMappings(
+                mapId, fromId, toId);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializerOfficeToLoanProducts.serialize(settings, entityToEntityMappings,
+                FineractEntityApiResourceConstants.FETCH_ENTITY_TO_ENTITY_MAPPINGS);
+    }
+
+    @POST
+    @Path("/{relId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createMap(@PathParam("relId") final Long relId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createMap(relId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @PUT
+    @Path("/{mapId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateMap(@PathParam("mapId") final Long mapId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateMap(mapId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @DELETE
+    @Path("{mapId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("mapId") final Long mapId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteMap(mapId) //
+                .build(); //
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/api/FineractEntityApiResourceConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/api/FineractEntityApiResourceConstants.java
new file mode 100644
index 0000000..4788c52
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/api/FineractEntityApiResourceConstants.java
@@ -0,0 +1,54 @@
+/**
+ * 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.entityaccess.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class FineractEntityApiResourceConstants {
+
+    public static final String FINERACT_ENTITY_RESOURCE_NAME = "FineractEntity";
+    public static final String mappingTypes = "mappingTypes";
+    public static final String mapId = "mapId";
+    public static final String relId = "relId";
+    public static final String fromEnityType = "fromId";
+    public static final String toEntityType = "toId";
+    public static final String startDate = "startDate";
+    public static final String endDate = "endDate";
+    public static final String LOCALE = "locale";
+    public static final String DATE_FORMAT = "dateFormat";
+
+    public static final String OFFICE_ACCESS_TO_LOAN_PRODUCTS = " office_access_to_loan_products ";
+    public static final String OFFICE_ACCESS_TO_SAVINGS_PRODUCTS = " office_access_to_savings_products ";
+    public static final String OFFICE_ACCESS_TO_CHARGES_FEES = " office_access_to_fees/charges ";
+    public static final String ROLE_ACCESS_TO_LOAN_PRODUCTS = " role_access_to_loan_products ";
+    public static final String ROLE_ACCESS_TO_SAVINGS_PRODUCTS = " role_access_to_savings_products ";
+
+    public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(mappingTypes));
+
+    public static final Set<String> FETCH_ENTITY_TO_ENTITY_MAPPINGS = new HashSet<>(Arrays.asList(mapId,relId,fromEnityType, toEntityType));
+
+    public static final Set<String> CREATE_ENTITY_MAPPING_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(fromEnityType,
+            toEntityType, startDate, LOCALE, DATE_FORMAT, endDate));
+
+    public static final Set<String> UPDATE_ENTITY_MAPPING_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(relId,fromEnityType,
+            toEntityType, startDate,LOCALE, DATE_FORMAT, endDate));
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityAccessData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityAccessData.java
new file mode 100644
index 0000000..d7808f7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityAccessData.java
@@ -0,0 +1,51 @@
+/**
+ * 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.entityaccess.data;
+
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntity;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType;
+
+public class FineractEntityAccessData {
+	private FineractEntity firstEntity;
+	private FineractEntityAccessType accessType;
+	private FineractEntity secondEntity;
+	
+	public FineractEntityAccessData (
+			FineractEntity firstEntity,
+			FineractEntityAccessType accessType,
+			FineractEntity secondEntity
+			) {
+		this.firstEntity = firstEntity;
+		this.accessType = accessType;
+		this.secondEntity = secondEntity;
+	}
+	
+	public FineractEntity getFirstEntity() {
+		return this.firstEntity;
+	}
+	
+	public FineractEntityAccessType getAccessType() {
+		return this.accessType;
+	}
+	
+	public FineractEntity getSecondEntity() {
+		return this.secondEntity;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityDataValidator.java
new file mode 100644
index 0000000..cc97dd8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityDataValidator.java
@@ -0,0 +1,217 @@
+/**
+ * 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.entityaccess.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.entityaccess.api.FineractEntityApiResourceConstants;
+import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
+import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
+import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
+import org.apache.fineract.useradministration.domain.Role;
+import org.apache.fineract.useradministration.domain.RoleRepository;
+import org.apache.fineract.useradministration.exception.RoleNotFoundException;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class FineractEntityDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final OfficeRepositoryWrapper officeRepositoryWrapper;
+    private final LoanProductRepository loanProductRepository;
+    private final SavingsProductRepository savingsProductRepository;
+    private final ChargeRepositoryWrapper chargeRepositoryWrapper;
+    private final RoleRepository roleRepository;
+
+    @Autowired
+    public FineractEntityDataValidator(final FromJsonHelper fromApiJsonHelper, final OfficeRepositoryWrapper officeRepositoryWrapper,
+            final LoanProductRepository loanProductRepository, final SavingsProductRepository savingsProductRepository,
+            final ChargeRepositoryWrapper chargeRepositoryWrapper, final RoleRepository roleRepository) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.officeRepositoryWrapper = officeRepositoryWrapper;
+        this.loanProductRepository = loanProductRepository;
+        this.savingsProductRepository = savingsProductRepository;
+        this.chargeRepositoryWrapper = chargeRepositoryWrapper;
+        this.roleRepository = roleRepository;
+    }
+
+    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,
+                FineractEntityApiResourceConstants.CREATE_ENTITY_MAPPING_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FineractEntityApiResourceConstants.FINERACT_ENTITY_RESOURCE_NAME);
+
+        if (this.fromApiJsonHelper.parameterExists(FineractEntityApiResourceConstants.fromEnityType, element)) {
+            final Long fromId = this.fromApiJsonHelper.extractLongNamed(FineractEntityApiResourceConstants.fromEnityType, element);
+            baseDataValidator.reset().parameter(FineractEntityApiResourceConstants.fromEnityType).value(fromId).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(FineractEntityApiResourceConstants.toEntityType, element)) {
+            final Long toId = this.fromApiJsonHelper.extractLongNamed(FineractEntityApiResourceConstants.toEntityType, element);
+            baseDataValidator.reset().parameter(FineractEntityApiResourceConstants.toEntityType).value(toId).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(FineractEntityApiResourceConstants.startDate, element)) {
+            final LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed(FineractEntityApiResourceConstants.startDate, element);
+            baseDataValidator.reset().parameter(FineractEntityApiResourceConstants.startDate).value(startDate);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(FineractEntityApiResourceConstants.endDate, element)) {
+            final LocalDate endDate = this.fromApiJsonHelper.extractLocalDateNamed(FineractEntityApiResourceConstants.endDate, element);
+            baseDataValidator.reset().parameter(FineractEntityApiResourceConstants.endDate).value(endDate);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            //
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    public void checkForEntity(String relId, Long fromId, Long toId) {
+
+        switch (relId) {
+            case "1":
+                checkForOffice(fromId);
+                checkForLoanProducts(toId);
+            break;
+            case "2":
+                checkForOffice(fromId);
+                checkForSavingsProducts(toId);
+            break;
+            case "3":
+                checkForOffice(fromId);
+                checkForCharges(toId);
+            break;
+            case "4":
+                checkForRoles(fromId);
+                checkForLoanProducts(toId);
+            break;
+            case "5":
+                checkForRoles(fromId);
+                checkForSavingsProducts(toId);
+            break;
+
+        }
+
+    }
+
+    public void checkForOffice(Long id) {
+        this.officeRepositoryWrapper.findOneWithNotFoundDetection(id);
+    }
+
+    public void checkForLoanProducts(final Long id) {
+        final LoanProduct loanProduct = this.loanProductRepository.findOne(id);
+        if (loanProduct == null) { throw new LoanProductNotFoundException(id); }
+    }
+
+    public void checkForSavingsProducts(final Long id) {
+        final SavingsProduct savingsProduct = this.savingsProductRepository.findOne(id);
+        if (savingsProduct == null) { throw new SavingsProductNotFoundException(id); }
+    }
+
+    public void checkForCharges(final Long id) {
+        this.chargeRepositoryWrapper.findOneWithNotFoundDetection(id);
+    }
+
+    public void checkForRoles(final Long id) {
+        final Role role = this.roleRepository.findOne(id);
+        if (role == null) { throw new RoleNotFoundException(id); }
+    }
+
+    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,
+                FineractEntityApiResourceConstants.UPDATE_ENTITY_MAPPING_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FineractEntityApiResourceConstants.FINERACT_ENTITY_RESOURCE_NAME);
+
+        boolean atLeastOneParameterPassedForUpdate = false;
+        if (this.fromApiJsonHelper.parameterExists(FineractEntityApiResourceConstants.fromEnityType, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String fromEnityType = this.fromApiJsonHelper.extractStringNamed(FineractEntityApiResourceConstants.fromEnityType, element);
+            baseDataValidator.reset().parameter(FineractEntityApiResourceConstants.fromEnityType).value(fromEnityType);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(FineractEntityApiResourceConstants.fromEnityType, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String toEnityType = this.fromApiJsonHelper.extractStringNamed(FineractEntityApiResourceConstants.toEntityType, element);
+            baseDataValidator.reset().parameter(FineractEntityApiResourceConstants.fromEnityType).value(toEnityType);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(FineractEntityApiResourceConstants.toEntityType, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(FineractEntityApiResourceConstants.startDate, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(FineractEntityApiResourceConstants.endDate, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+        }
+
+        if (!atLeastOneParameterPassedForUpdate) {
+            final Object forceError = null;
+            baseDataValidator.reset().anyOfNotNull(forceError);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityRelationData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityRelationData.java
new file mode 100644
index 0000000..860f130
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityRelationData.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.entityaccess.data;
+
+import java.io.Serializable;
+
+public class FineractEntityRelationData implements Serializable {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final Integer fromEntityType;
+    @SuppressWarnings("unused")
+    private final Integer toEntityType;
+    @SuppressWarnings("unused")
+    private final String mappingTypes;
+
+    public FineractEntityRelationData(final Long id, final Integer fromEntityType, final Integer toEntityType, final String mappingTypes) {
+        this.id = id;
+        this.fromEntityType = fromEntityType;
+        this.toEntityType = toEntityType;
+        this.mappingTypes = mappingTypes;
+    }
+
+    public static FineractEntityRelationData getMappingTypes(final Long id,final String mappingTypes) {
+        Integer fromEntityType = null;
+        final Integer toEntityType = null;
+        return new FineractEntityRelationData(id, fromEntityType, toEntityType, mappingTypes);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityToEntityMappingData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityToEntityMappingData.java
new file mode 100644
index 0000000..24d59b7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/data/FineractEntityToEntityMappingData.java
@@ -0,0 +1,80 @@
+/**
+ * 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.entityaccess.data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class FineractEntityToEntityMappingData implements Serializable {
+
+    @SuppressWarnings("unused")
+    private Long mapId;
+
+    @SuppressWarnings("unused")
+    private Long relationId;
+
+    @SuppressWarnings("unused")
+    private Long fromId;
+
+    @SuppressWarnings("unused")
+    private Long toId;
+
+    @SuppressWarnings("unused")
+    private Date startDate;
+
+    @SuppressWarnings("unused")
+    private Date endDate;
+
+    @SuppressWarnings("unused")
+    private final String fromEntity;
+
+    @SuppressWarnings("unused")
+    private final String toEntity;
+
+    private FineractEntityToEntityMappingData(final Long mapId, final Long relationId, final Long fromId, final Long toId,
+            final Date startDate, final Date endDate, final String fromEntity, final String toEntity) {
+        this.mapId = mapId;
+        this.relationId = relationId;
+        this.fromId = fromId;
+        this.toId = toId;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.fromEntity = fromEntity;
+        this.toEntity = toEntity;
+    }
+
+    public static FineractEntityToEntityMappingData getRelatedEntities(final Long mapId, final Long relationId, final Long fromId,
+            final Long toId, final Date startDate, final Date endDate ,final String fromEntity, final String toEntity) {
+
+
+        return new FineractEntityToEntityMappingData(mapId, relationId, fromId, toId, startDate, endDate, fromEntity, toEntity);
+
+    }
+
+    public static FineractEntityToEntityMappingData getRelatedEntities(final Long relationId, final Long fromId, final Long toId,
+            final Date startDate, final Date endDate) {
+        final Long mapId = null;
+        final String fromEntity = null;
+        final String toEntity = null;
+
+        return new FineractEntityToEntityMappingData(mapId, relationId, fromId, toId, startDate, endDate, fromEntity, toEntity);
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntity.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntity.java
new file mode 100644
index 0000000..89a2840
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntity.java
@@ -0,0 +1,41 @@
+/**
+ * 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.entityaccess.domain;
+
+public class FineractEntity {
+	private Long entityId;
+	private FineractEntityType type;
+	
+	@SuppressWarnings("unused")
+	private FineractEntity() {
+	}
+	
+	public FineractEntity(Long entityId, FineractEntityType type) {
+		this.entityId = entityId;
+		this.type = type;
+	}
+	
+	public Long getId () {
+		return this.entityId;
+	}
+	
+	public FineractEntityType getType () {
+		return this.type;
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccess.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccess.java
new file mode 100644
index 0000000..75c852a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccess.java
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.entityaccess.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.entityaccess.FineractEntityAccessConstants;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_entity_to_entity_access")
+public class FineractEntityAccess extends AbstractPersistable<Long> {
+	
+	@Column(name = "entity_type", length = 50)
+    private String entityType;
+	
+	@Column(name = "entity_id")
+    private Long entityId;
+	
+    @ManyToOne
+    @JoinColumn(name = "access_type_code_value_id", nullable = false)
+    private CodeValue accessType;
+
+	@Column(name = "second_entity_type", length = 50)
+    private String secondEntityType;
+	
+	@Column(name = "second_entity_id")
+    private Long secondEntityId;
+
+    protected FineractEntityAccess () {
+
+    }
+
+    public static FineractEntityAccess createNew(final String entityType, final Long entityId,
+    		final CodeValue accessType,
+    		final String secondEntityType, final Long secondEntityId) {
+        return new FineractEntityAccess(entityType, entityId, accessType,
+        		secondEntityType, secondEntityId);
+    }
+
+    public FineractEntityAccess(final String entityType, final Long entityId,
+    		final CodeValue accessType,
+    		final String secondEntityType, final Long secondEntityId) {
+    	this.entityType = entityType;
+    	this.entityId = entityId;
+    	this.accessType = accessType;
+    	this.secondEntityType = secondEntityType;
+    	this.secondEntityId = secondEntityId;
+    }
+
+    public static FineractEntityAccess fromJson(final CodeValue accessType, final JsonCommand command) {
+        final String entityType = command.stringValueOfParameterNamed(
+        		FineractEntityAccessConstants.ENTITY_ACCESS_JSON_INPUT_PARAMS.ENTITY_TYPE.getValue());
+        final Long entityId = command.longValueOfParameterNamed(
+        		FineractEntityAccessConstants.ENTITY_ACCESS_JSON_INPUT_PARAMS.ENTITY_ID.getValue());
+        final String secondEntityType = command.stringValueOfParameterNamed(
+        		FineractEntityAccessConstants.ENTITY_ACCESS_JSON_INPUT_PARAMS.SECOND_ENTITY_ID.getValue());
+        final Long secondEntityId = command.longValueOfParameterNamed(
+        		FineractEntityAccessConstants.ENTITY_ACCESS_JSON_INPUT_PARAMS.SECOND_ENTITY_ID.getValue());
+        
+        return new FineractEntityAccess (entityType, entityId, accessType,
+        		secondEntityType, secondEntityId);
+
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        String paramName = null;
+
+        paramName = FineractEntityAccessConstants.ENTITY_ACCESS_JSON_INPUT_PARAMS.ENTITY_TYPE.getValue();
+        if (command.isChangeInStringParameterNamed(paramName, this.entityType)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            this.entityType = newValue;
+        }
+
+        paramName = FineractEntityAccessConstants.ENTITY_ACCESS_JSON_INPUT_PARAMS.ENTITY_ID.getValue(); 
+        if (command.isChangeInLongParameterNamed(paramName, getEntityId())) {
+        	this.entityId = command.longValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, this.entityId);
+        }
+        
+        Long existingAccessTypeId = null;
+        if (this.accessType != null) {
+            existingAccessTypeId = this.accessType.getId();
+        }
+        
+        paramName = FineractEntityAccessConstants.ENTITY_ACCESS_JSON_INPUT_PARAMS.ENTITY_ACCESS_TYPE_ID.getValue(); 
+        if (command.isChangeInLongParameterNamed(paramName, existingAccessTypeId)) {
+        	final Long newValue = command.longValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+        }
+
+        paramName = FineractEntityAccessConstants.ENTITY_ACCESS_JSON_INPUT_PARAMS.SECOND_ENTITY_TYPE.getValue();
+        if (command.isChangeInStringParameterNamed(paramName, this.secondEntityType)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            this.secondEntityType = newValue;
+        }
+
+        paramName = FineractEntityAccessConstants.ENTITY_ACCESS_JSON_INPUT_PARAMS.SECOND_ENTITY_ID.getValue(); 
+        if (command.isChangeInLongParameterNamed(paramName, getSecondEntityId())) {
+        	this.secondEntityId = command.longValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, this.secondEntityId);
+        }
+        
+        return actualChanges;
+    }
+
+    public String getEntityType() {
+        return this.entityType;
+    }
+
+    public Long getEntityId() {
+        return this.entityId;
+    }
+
+    public CodeValue getAccessType() {
+        return this.accessType;
+    }
+
+    public void updateAccessType(final CodeValue accessType) {
+        this.accessType = accessType;
+    }
+    
+    public String getSecondEntityType() {
+        return this.secondEntityType;
+    }
+
+    public Long getSecondEntityId() {
+        return this.secondEntityId;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccessRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccessRepository.java
new file mode 100644
index 0000000..6c3c5f5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccessRepository.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.infrastructure.entityaccess.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface FineractEntityAccessRepository extends JpaRepository<FineractEntityAccess, Long>, JpaSpecificationExecutor<FineractEntityAccess> {
+    // no added behaviour
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccessRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccessRepositoryWrapper.java
new file mode 100644
index 0000000..a12364d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccessRepositoryWrapper.java
@@ -0,0 +1,58 @@
+/**
+ * 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.entityaccess.domain;
+
+import org.apache.fineract.infrastructure.entityaccess.exception.FineractEntityAccessNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link FineractEntityAccessRepository} that adds NULL checking and Error
+ * handling capabilities
+ * </p>
+ */
+@Service
+public class FineractEntityAccessRepositoryWrapper {
+
+    private final FineractEntityAccessRepository repository;
+
+    @Autowired
+    public FineractEntityAccessRepositoryWrapper(final FineractEntityAccessRepository repository) {
+        this.repository = repository;
+    }
+
+    public FineractEntityAccess findOneWithNotFoundDetection(final Long id) {
+        final FineractEntityAccess fineractEntityAccess = this.repository.findOne(id);
+        if (fineractEntityAccess == null) { throw new FineractEntityAccessNotFoundException(id); }
+        return fineractEntityAccess;
+    }
+
+    public void save(final FineractEntityAccess fineractEntityAccess) {
+        this.repository.save(fineractEntityAccess);
+    }
+
+    public void saveAndFlush(final FineractEntityAccess fineractEntityAccess) {
+        this.repository.saveAndFlush(fineractEntityAccess);
+    }
+
+    public void delete(final FineractEntityAccess fineractEntityAccess) {
+        this.repository.delete(fineractEntityAccess);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccessType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccessType.java
new file mode 100644
index 0000000..a54ac0f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityAccessType.java
@@ -0,0 +1,51 @@
+/**
+ * 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.entityaccess.domain;
+
+public class FineractEntityAccessType {
+	
+	private String str;
+	
+	public static final FineractEntityAccessType OFFICE_ACCESS_TO_LOAN_PRODUCTS = new FineractEntityAccessType("Office Access to Loan Products");
+	public static final FineractEntityAccessType OFFICE_ACCESS_TO_SAVINGS_PRODUCTS = new FineractEntityAccessType("Office Access to Savings Products");
+	public static final FineractEntityAccessType OFFICE_ACCESS_TO_CHARGES = new FineractEntityAccessType("Office Access to Fees/Charges");
+    
+    private FineractEntityAccessType (String str) {
+    	this.str = str;
+    }
+    
+    public String toStr () {
+    	return this.str;
+    }
+    
+    public static FineractEntityAccessType get (String type) {
+    	
+    	FineractEntityAccessType retType = null;
+    	
+    	if (type.equals(OFFICE_ACCESS_TO_LOAN_PRODUCTS.str)) {
+    		retType =  OFFICE_ACCESS_TO_LOAN_PRODUCTS;
+    	} else if (type.equals(OFFICE_ACCESS_TO_SAVINGS_PRODUCTS.str)) {
+    		retType = OFFICE_ACCESS_TO_SAVINGS_PRODUCTS;
+    	} else if (type.equals(OFFICE_ACCESS_TO_CHARGES.str)) { 
+    			retType = OFFICE_ACCESS_TO_CHARGES;
+    	}
+    	
+    	return retType;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityRelation.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityRelation.java
new file mode 100644
index 0000000..b3d46c0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityRelation.java
@@ -0,0 +1,112 @@
+/**
+ * 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.entityaccess.domain;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_entity_relation")
+public class FineractEntityRelation extends AbstractPersistable<Long> {
+
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "relationId", orphanRemoval = true)
+    private Set<FineractEntityToEntityMapping> fineractEntityToEntityMapping = new HashSet<>();
+    
+    @Column(name = "from_entity_type", nullable = false, length = 10)
+    private String fromEntityType;
+
+    @Column(name = "to_entity_type", nullable = false, length = 10)
+    private String toEntityType;
+
+    @Column(name = "code_name", nullable = false, length = 50)
+    private String codeName;
+
+   /* private FineractEntityRelation(final String fromEntityType, final String toEntityType, final String codeName) {
+        this.fromEntityType = fromEntityType;
+        this.toEntityType = toEntityType;
+        this.codeName = codeName;
+    }*/
+    
+    
+    public FineractEntityRelation() {
+        // TODO Auto-generated constructor stub
+    }
+
+
+    
+    public Set<FineractEntityToEntityMapping> getFineractEntityToEntityMapping() {
+        return this.fineractEntityToEntityMapping;
+    }
+
+
+    
+    public void setFineractEntityToEntityMapping(Set<FineractEntityToEntityMapping> fineractEntityToEntityMapping) {
+        this.fineractEntityToEntityMapping = fineractEntityToEntityMapping;
+    }
+
+
+    
+    public String getFromEntityType() {
+        return this.fromEntityType;
+    }
+
+
+    
+    public void setFromEntityType(String fromEntityType) {
+        this.fromEntityType = fromEntityType;
+    }
+
+
+    
+    public String getToEntityType() {
+        return this.toEntityType;
+    }
+
+
+    
+    public void setToEntityType(String toEntityType) {
+        this.toEntityType = toEntityType;
+    }
+
+
+    
+    public String getCodeName() {
+        return this.codeName;
+    }
+
+
+    
+    public void setCodeName(String codeName) {
+        this.codeName = codeName;
+    }
+    
+    
+
+    
+    
+  
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityRelationRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityRelationRepository.java
new file mode 100644
index 0000000..303bfdb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityRelationRepository.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.infrastructure.entityaccess.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+
+public interface FineractEntityRelationRepository extends JpaRepository<FineractEntityRelation, Long>{
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityRelationRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityRelationRepositoryWrapper.java
new file mode 100644
index 0000000..a851f5a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityRelationRepositoryWrapper.java
@@ -0,0 +1,42 @@
+/**
+ * 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.entityaccess.domain;
+
+import org.apache.fineract.infrastructure.entityaccess.exception.FineractEntityAccessNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FineractEntityRelationRepositoryWrapper {
+
+    private final FineractEntityRelationRepository fineractEntityRelationRepository;
+
+    @Autowired
+    private FineractEntityRelationRepositoryWrapper(final FineractEntityRelationRepository fineractEntityRelationRepository) {
+        this.fineractEntityRelationRepository = fineractEntityRelationRepository;
+
+    }
+    
+    public FineractEntityRelation findOneWithNotFoundDetection(final Long id) {
+        final FineractEntityRelation fineractEntityRelation = this.fineractEntityRelationRepository.findOne(id);
+        if (fineractEntityRelation == null) { throw new FineractEntityAccessNotFoundException(id); }
+        return fineractEntityRelation;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMapping.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMapping.java
new file mode 100644
index 0000000..2ae9ef8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMapping.java
@@ -0,0 +1,137 @@
+/**
+ * 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.entityaccess.domain;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+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 javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.entityaccess.api.FineractEntityApiResourceConstants;
+import org.apache.fineract.infrastructure.entityaccess.exception.FineractEntityToEntityMappingDateException;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_entity_to_entity_mapping", uniqueConstraints = { @UniqueConstraint(columnNames = { "rel_id", "from_id", "to_id" }) })
+public class FineractEntityToEntityMapping extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "rel_id")
+    private FineractEntityRelation relationId;
+
+    @Column(name = "from_id")
+    private Long fromId;
+
+    @Column(name = "to_id")
+    private Long toId;
+
+    @Column(name = "start_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date startDate;
+
+    @Column(name = "end_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date endDate;
+
+    private FineractEntityToEntityMapping(final FineractEntityRelation relationId, final Long fromId, final Long toId, final Date startDate,
+            final Date endDate) {
+        this.relationId = relationId;
+        this.fromId = fromId;
+        this.toId = toId;
+        this.startDate = startDate;
+        this.endDate = endDate;
+
+    }
+
+    public FineractEntityToEntityMapping() {
+        //
+    }
+
+    public static FineractEntityToEntityMapping newMap(FineractEntityRelation relationId, Long fromId, Long toId, Date startDate, Date endDate) {
+
+        return new FineractEntityToEntityMapping(relationId, fromId, toId, startDate, endDate);
+
+    }
+
+    public Map<String, Object> updateMap(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+        if (command.isChangeInLongParameterNamed(FineractEntityApiResourceConstants.fromEnityType, this.fromId)) {
+            final Long newValue = command.longValueOfParameterNamed(FineractEntityApiResourceConstants.fromEnityType);
+            actualChanges.put(FineractEntityApiResourceConstants.fromEnityType, newValue);
+            this.fromId = newValue;
+        }
+
+        if (command.isChangeInLongParameterNamed(FineractEntityApiResourceConstants.toEntityType, this.toId)) {
+            final Long newValue = command.longValueOfParameterNamed(FineractEntityApiResourceConstants.toEntityType);
+            actualChanges.put(FineractEntityApiResourceConstants.toEntityType, newValue);
+            this.toId = newValue;
+        }
+
+        if (command.isChangeInDateParameterNamed(FineractEntityApiResourceConstants.startDate, this.startDate)) {
+            final String valueAsInput = command.stringValueOfParameterNamed(FineractEntityApiResourceConstants.startDate);
+            actualChanges.put(FineractEntityApiResourceConstants.startDate, valueAsInput);
+            final Date startDate = command.DateValueOfParameterNamed(FineractEntityApiResourceConstants.startDate);
+            this.startDate = startDate;
+        }
+
+        if (command.isChangeInDateParameterNamed(FineractEntityApiResourceConstants.endDate, this.endDate)) {
+            final String valueAsInput = command.stringValueOfParameterNamed(FineractEntityApiResourceConstants.endDate);
+            actualChanges.put(FineractEntityApiResourceConstants.endDate, valueAsInput);
+            final Date endDate = command.DateValueOfParameterNamed(FineractEntityApiResourceConstants.endDate);
+            this.endDate = endDate;
+        }
+        if (startDate != null && endDate != null) {
+            if (endDate.before(startDate)) { throw new FineractEntityToEntityMappingDateException(startDate.toString(), endDate.toString()); }
+        }
+
+        return actualChanges;
+
+    }
+
+    public FineractEntityRelation getRelationId() {
+        return this.relationId;
+    }
+
+    public void setRelationId(FineractEntityRelation relationId) {
+        this.relationId = relationId;
+    }
+
+    /*
+     * public Date getStartDate() { Date startDate = null; if (this.startDate !=
+     * null) { startDate = Date.fromDateFields(this.startDate); } return
+     * startDate; }
+     */
+
+    /*
+     * public Date getStartDate() { return (Date) ObjectUtils.defaultIfNull(new
+     * Date(this.startDate), null); }
+     */
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMappingRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMappingRepository.java
new file mode 100644
index 0000000..7b4bd83
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMappingRepository.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.entityaccess.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+
+public interface FineractEntityToEntityMappingRepository extends JpaRepository<FineractEntityToEntityMapping, Long>{
+    
+   
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMappingRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMappingRepositoryWrapper.java
new file mode 100644
index 0000000..3c738ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMappingRepositoryWrapper.java
@@ -0,0 +1,45 @@
+/**
+ * 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.entityaccess.domain;
+
+import org.apache.fineract.infrastructure.entityaccess.exception.FineractEntityAccessNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FineractEntityToEntityMappingRepositoryWrapper {
+
+    private final FineractEntityToEntityMappingRepository fineractEntityToEntityMappingRepository;
+
+    @Autowired
+    public FineractEntityToEntityMappingRepositoryWrapper(final FineractEntityToEntityMappingRepository fineractEntityToEntityMappingRepository) {
+        this.fineractEntityToEntityMappingRepository = fineractEntityToEntityMappingRepository;
+    }
+
+    public FineractEntityToEntityMapping findOneWithNotFoundDetection(final Long id) {
+        final FineractEntityToEntityMapping fineractEntityToEntityMapping = this.fineractEntityToEntityMappingRepository.findOne(id);
+        if (fineractEntityToEntityMapping == null) { throw new FineractEntityAccessNotFoundException(id); }
+        return fineractEntityToEntityMapping;
+    }
+
+    public void delete(final FineractEntityToEntityMapping mapId) {
+        this.fineractEntityToEntityMappingRepository.delete(mapId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityType.java
new file mode 100644
index 0000000..8280095
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityType.java
@@ -0,0 +1,65 @@
+/**
+ * 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.entityaccess.domain;
+
+public class FineractEntityType {
+	private String type;
+	private String description;
+	private String table_name;
+	
+	public static FineractEntityType OFFICE = new FineractEntityType ("office", "Offices", "m_office"); 
+	public static FineractEntityType LOAN_PRODUCT = new FineractEntityType ("loan_product", "Loan Products", "m_product_loan");
+	public static FineractEntityType SAVINGS_PRODUCT = new FineractEntityType ("savings_product", "Savings Products", "m_savings_product");
+	public static FineractEntityType CHARGE = new FineractEntityType ("charge", "Fees/Charges", "m_charge");
+		
+	private FineractEntityType (String type, String description, String table_name) {
+		this.type = type;
+		this.description = description;
+		this.table_name = table_name;
+	}
+	
+	public String getType () {
+		return this.type;
+	}
+	
+	public String getDescription () {
+		return this.description;
+	}
+	
+	public String getTable () {
+		return this.table_name;
+	}
+	
+	public static FineractEntityType get (String type) {
+
+    	FineractEntityType retType = null;
+    	
+    	if (type.equals(OFFICE.type)) {
+    		retType =  OFFICE;
+    	} else if (type.equals(LOAN_PRODUCT.type)) { 
+    			retType = LOAN_PRODUCT;
+    	} else if (type.equals(SAVINGS_PRODUCT)) { 
+    			retType = SAVINGS_PRODUCT;
+    	} else if (type.equals(CHARGE)) 
+    			retType = CHARGE;
+    	
+    	return retType;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityAccessConfigurationException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityAccessConfigurationException.java
new file mode 100644
index 0000000..f57fb7c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityAccessConfigurationException.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.infrastructure.entityaccess.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType;
+
+public class FineractEntityAccessConfigurationException extends AbstractPlatformDomainRuleException {
+
+    public FineractEntityAccessConfigurationException(final Long firstEntityId,
+    		final FineractEntityType entityType1,
+    		final FineractEntityAccessType accessType,
+    		final FineractEntityType entityType2) {
+        super("error.msg.entityaccess.config",
+                "Error while getting entity access configuration for " + entityType1.getType() + ":" + firstEntityId + 
+                " with type " + accessType.toStr() + " against " + entityType2.getType());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityAccessNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityAccessNotFoundException.java
new file mode 100644
index 0000000..b03d2fd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityAccessNotFoundException.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.infrastructure.entityaccess.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when office resources are not found.
+ */
+public class FineractEntityAccessNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public FineractEntityAccessNotFoundException(final Long id) {
+        super("error.msg.entityaccess.id.invalid", "FineractEntityAccess with identifier " + id + " does not exist", id);
+    }
+     
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityToEntityMappingDateException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityToEntityMappingDateException.java
new file mode 100644
index 0000000..661fdda
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityToEntityMappingDateException.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.infrastructure.entityaccess.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+
+public class FineractEntityToEntityMappingDateException extends AbstractPlatformResourceNotFoundException {
+
+    public FineractEntityToEntityMappingDateException(final String startDate,final String endDate) {
+        super("error.msg.invalid.endDate", "EndDate " + endDate + " cannot be before StartDate "+startDate);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityToEntityMappingNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityToEntityMappingNotFoundException.java
new file mode 100644
index 0000000..f50d930
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/exception/FineractEntityToEntityMappingNotFoundException.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.entityaccess.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class FineractEntityToEntityMappingNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public FineractEntityToEntityMappingNotFoundException(final String id) {
+        super("error.msg.entityaccess.id.invalid", "FineractEntityToEntityMapping with identifier " + id + " does not exist", id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/handler/CreateEntityToEntityMappingCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/handler/CreateEntityToEntityMappingCommandHandler.java
new file mode 100644
index 0000000..28b3121
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/handler/CreateEntityToEntityMappingCommandHandler.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.infrastructure.entityaccess.handler;
+
+import javax.transaction.Transactional;
+
+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.infrastructure.entityaccess.service.FineractEntityAccessWriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "ENTITYMAPPING", action = "CREATE")
+public class CreateEntityToEntityMappingCommandHandler implements NewCommandSourceHandler {
+    
+    private final FineractEntityAccessWriteService fineractEntityAccessWriteService;
+
+    @Autowired
+    public CreateEntityToEntityMappingCommandHandler(final FineractEntityAccessWriteService fineractEntityAccessWriteService) {
+        this.fineractEntityAccessWriteService = fineractEntityAccessWriteService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.fineractEntityAccessWriteService.createEntityToEntityMapping(command.entityId(),command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/handler/DeleteEntityToEntityMappingCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/handler/DeleteEntityToEntityMappingCommandHandler.java
new file mode 100644
index 0000000..958c1e4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/handler/DeleteEntityToEntityMappingCommandHandler.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.infrastructure.entityaccess.handler;
+
+import javax.transaction.Transactional;
+
+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.infrastructure.entityaccess.service.FineractEntityAccessWriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "ENTITYMAPPING", action = "DELETE")
+public class DeleteEntityToEntityMappingCommandHandler implements NewCommandSourceHandler {
+
+    private final FineractEntityAccessWriteService fineractEntityAccessWriteService;
+
+    @Autowired
+    public DeleteEntityToEntityMappingCommandHandler(final FineractEntityAccessWriteService fineractEntityAccessWriteService) {
+        this.fineractEntityAccessWriteService = fineractEntityAccessWriteService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.fineractEntityAccessWriteService.deleteEntityToEntityMapping(command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/handler/UpdateEntityToEntityMappingCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/handler/UpdateEntityToEntityMappingCommandHandler.java
new file mode 100644
index 0000000..bc35640
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/handler/UpdateEntityToEntityMappingCommandHandler.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.infrastructure.entityaccess.handler;
+
+import javax.transaction.Transactional;
+
+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.infrastructure.entityaccess.service.FineractEntityAccessWriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "ENTITYMAPPING", action = "UPDATE")
+public class UpdateEntityToEntityMappingCommandHandler implements NewCommandSourceHandler {
+    
+    private final FineractEntityAccessWriteService fineractEntityAccessWriteService;
+
+    @Autowired
+    public UpdateEntityToEntityMappingCommandHandler(final FineractEntityAccessWriteService fineractEntityAccessWriteService) {
+        this.fineractEntityAccessWriteService = fineractEntityAccessWriteService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.fineractEntityAccessWriteService.updateEntityToEntityMapping(command.entityId(),command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadService.java
new file mode 100644
index 0000000..7b33bd9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadService.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.entityaccess.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityAccessData;
+import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityRelationData;
+import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityToEntityMappingData;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType;
+
+public interface FineractEntityAccessReadService {
+
+    Collection<FineractEntityAccessData> retrieveEntityAccessFor(Long entityId, FineractEntityType type, FineractEntityAccessType accessType,
+            FineractEntityType secondType, boolean includeAllOffices);
+
+    String getSQLQueryInClause_WithListOfIDsForEntityAccess(Long entityId, FineractEntityType firstEntityType,
+            FineractEntityAccessType accessType, FineractEntityType secondEntityType, boolean includeAllOffices);
+
+    String getSQLQueryInClauseIDList_ForLoanProductsForOffice(Long loanProductId, boolean includeAllOffices);
+
+    String getSQLQueryInClauseIDList_ForSavingsProductsForOffice(Long savingsProductId, boolean includeAllOffices);
+
+    String getSQLQueryInClauseIDList_ForChargesForOffice(Long officeId, boolean includeAllOffices);
+
+    Collection<FineractEntityRelationData> retrieveAllSupportedMappingTypes();
+
+    Collection<FineractEntityToEntityMappingData> retrieveOneMapping(Long mapId);
+
+    Collection<FineractEntityToEntityMappingData> retrieveEntityToEntityMappings(Long mapId, Long fromoId, Long toId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java
new file mode 100644
index 0000000..1505c3b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java
@@ -0,0 +1,392 @@
+/**
+ * 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.entityaccess.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.dataqueries.service.GenericDataServiceImpl;
+import org.apache.fineract.infrastructure.entityaccess.FineractEntityAccessConstants;
+import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityAccessData;
+import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityRelationData;
+import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityToEntityMappingData;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntity;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType;
+import org.apache.fineract.infrastructure.entityaccess.exception.FineractEntityAccessConfigurationException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FineractEntityAccessReadServiceImpl implements FineractEntityAccessReadService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final static Logger logger = LoggerFactory.getLogger(GenericDataServiceImpl.class);
+
+    @Autowired
+    public FineractEntityAccessReadServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.fineract.infrastructure.entityaccess.service.
+     * FineractEntityAccessReadService#getSQLQueryWithListOfIDsForEntityAccess
+     * (Long,
+     * org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType,
+     * org.apache.fineract.infrastructure.entityaccess.domain.
+     * FineractEntityAccessType,
+     * org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType,
+     * boolean)
+     * 
+     * This method returns the list of entity IDs as a comma separated list Or
+     * null if there is no entity restrictions or if there
+     */
+    @Override
+    public String getSQLQueryInClause_WithListOfIDsForEntityAccess(Long firstEntityId, FineractEntityType firstEntityType,
+            FineractEntityAccessType accessType, FineractEntityType secondEntityType, boolean includeAllOffices) {
+        Collection<FineractEntityAccessData> accesslist = retrieveEntityAccessFor(firstEntityId, firstEntityType, accessType, secondEntityType,
+                includeAllOffices);
+        String returnIdListStr = null;
+        StringBuffer accessListCSVStrBuf = null;
+        if ((accesslist != null) && (accesslist.size() > 0)) {
+            logger.debug("Found " + accesslist.size() + " access type restrictions while getting entity access configuration for "
+                    + firstEntityType.getType() + ":" + firstEntityId + " with type " + accessType.toStr() + " against "
+                    + secondEntityType.getType());
+            accessListCSVStrBuf = new StringBuffer(" ");
+            for (int i = 0; i < accesslist.size(); i++) {
+                FineractEntityAccessData accessData = (FineractEntityAccessData) accesslist.toArray()[i];
+                if (accessData == null) { throw new FineractEntityAccessConfigurationException(firstEntityId, firstEntityType, accessType,
+                        secondEntityType); }
+                if (accessData.getSecondEntity().getId() == 0) { // If there is
+                                                                 // any ID that
+                                                                 // zero, then
+                                                                 // allow access
+                                                                 // to all
+                    accessListCSVStrBuf = null;
+                    break;
+                }
+                if (i > 0) {
+                    accessListCSVStrBuf.append(',');
+                }
+                accessListCSVStrBuf.append(accessData.getSecondEntity().getId());
+            }
+
+        } else {
+            logger.debug("Found zero access type restrictions while getting entity access configuration for " + firstEntityType.getType()
+                    + ":" + firstEntityId + " with type " + accessType.toStr() + " against " + secondEntityType.getType());
+            accessListCSVStrBuf = new StringBuffer();
+            accessListCSVStrBuf.append("false"); // Append false so that no rows
+                                                 // will be returned
+        }
+        if (accessListCSVStrBuf != null) {
+            returnIdListStr = accessListCSVStrBuf.toString();
+        }
+        logger.debug("List of IDs applicable:" + returnIdListStr);
+        return returnIdListStr;
+    }
+
+    @Override
+    public Collection<FineractEntityAccessData> retrieveEntityAccessFor(Long firstEntityId, FineractEntityType firstEntityType,
+            FineractEntityAccessType accessType, FineractEntityType secondEntityType, boolean includeAllSubOffices) {
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        String hierarchySearchString = null;
+        if (includeAllSubOffices) {
+            hierarchySearchString = "." + "%";
+        } else {
+            hierarchySearchString = hierarchy + "%";
+        }
+        String sql = getSQLForRetriveEntityAccessFor(firstEntityType, accessType, secondEntityType);
+
+        Collection<FineractEntityAccessData> entityAccessData = null;
+        FineractEntityAccessDataMapper mapper = new FineractEntityAccessDataMapper();
+
+        if (includeAllSubOffices && (firstEntityType.getTable().equals("m_office"))) {
+            sql += " where firstentity.hierarchy like ? order by firstEntity.hierarchy";
+            entityAccessData = this.jdbcTemplate.query(sql, mapper, new Object[] { firstEntityId, hierarchySearchString });
+        } else {
+            entityAccessData = this.jdbcTemplate.query(sql, mapper, new Object[] { firstEntityId });
+        }
+
+        return entityAccessData;
+    }
+
+    private String getSQLForRetriveEntityAccessFor(FineractEntityType firstEntityType, FineractEntityAccessType accessType,
+            FineractEntityType secondEntityType) {
+        StringBuffer str = new StringBuffer("select eea.entity_id as entity_id, entity_type as entity_type, ");
+        str.append("access_type_code_value_id as access_id, cv.code_value as access_type_desc, c.code_name as code, ");
+        str.append("firstentity.id as first_entity_id, firstentity.name as entity_name, ");
+        str.append("otherentity.id as second_entity_id, otherentity.name as second_entity_name, ");
+        str.append("eea.second_entity_type as second_entity_type ");
+        str.append("from m_entity_to_entity_access eea ");
+        str.append("left join m_code_value cv on (cv.code_value = ");
+        str.append("'");
+        str.append(accessType.toStr());
+        str.append("' ");
+        str.append("and eea.access_type_code_value_id = cv.id) ");
+        str.append("left join m_code c on (c.code_name = '");
+        str.append(FineractEntityAccessConstants.ENTITY_ACCESS_CODENAME);
+        str.append("' and cv.code_id = c.id) ");
+        str.append("left join ");
+        str.append(firstEntityType.getTable());
+        str.append(" firstentity on (eea.entity_type = ");
+        str.append("'");
+        str.append(firstEntityType.getType());
+        str.append("'");
+        str.append(" and eea.entity_id = firstentity.id)        left join ");
+        str.append(secondEntityType.getTable());
+        str.append(" otherentity on (eea.second_entity_type = ");
+        str.append("'");
+        str.append(secondEntityType.getType());
+        str.append("' ");
+        str.append("and eea.second_entity_id = otherentity.id) ");
+        str.append("where eea.access_type_code_value_id = cv.id ");
+        str.append("and eea.entity_id = ? ");
+        logger.debug(str.toString());
+        return str.toString();
+    }
+
+    private static final class FineractEntityAccessDataMapper implements RowMapper<FineractEntityAccessData> {
+
+        @Override
+        public FineractEntityAccessData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final String entityType = rs.getString("entity_type");
+            final Long entityId = rs.getLong("entity_id");
+            // final String entityName = rs.getString("entity_name");
+            // final Long accessId = rs.getLong("access_id");
+            final String accessTypeDesc = rs.getString("access_type_desc");
+            // final String code = rs.getString("code");
+            final Long secondEntityId = rs.getLong("second_entity_id");
+            // final String secondEntityName =
+            // rs.getString("second_entity_name");
+            final String secondEntityType = rs.getString("second_entity_type");
+
+            FineractEntity firstEntity = null;
+            FineractEntityType etype = FineractEntityType.get(entityType);
+            if (etype != null) {
+                firstEntity = new FineractEntity(entityId, etype);
+            }
+
+            FineractEntity secondEntity = null;
+            FineractEntityType secondetype = FineractEntityType.get(secondEntityType);
+            if (etype != null) {
+                secondEntity = new FineractEntity(secondEntityId, secondetype);
+            }
+
+            FineractEntityAccessType accessType = null;
+            if (accessTypeDesc != null) {
+                accessType = FineractEntityAccessType.get(accessTypeDesc);
+            }
+
+            FineractEntityAccessData returnFineractEntityAccessData = null;
+            if (firstEntity != null && secondEntity != null && accessType != null) {
+                returnFineractEntityAccessData = new FineractEntityAccessData(firstEntity, accessType, secondEntity);
+            }
+            return returnFineractEntityAccessData;
+        }
+    }
+
+    @Override
+    public String getSQLQueryInClauseIDList_ForLoanProductsForOffice(Long officeId, boolean includeAllOffices) {
+
+        FineractEntityType firstEntityType = FineractEntityType.OFFICE;
+        FineractEntityAccessType accessType = FineractEntityAccessType.OFFICE_ACCESS_TO_LOAN_PRODUCTS;
+        FineractEntityType secondEntityType = FineractEntityType.LOAN_PRODUCT;
+
+        return getSQLQueryInClause_WithListOfIDsForEntityAccess(officeId, firstEntityType, accessType, secondEntityType, includeAllOffices);
+    }
+
+    @Override
+    public String getSQLQueryInClauseIDList_ForSavingsProductsForOffice(Long officeId, boolean includeAllOffices) {
+
+        FineractEntityType firstEntityType = FineractEntityType.OFFICE;
+        FineractEntityAccessType accessType = FineractEntityAccessType.OFFICE_ACCESS_TO_SAVINGS_PRODUCTS;
+        FineractEntityType secondEntityType = FineractEntityType.SAVINGS_PRODUCT;
+
+        return getSQLQueryInClause_WithListOfIDsForEntityAccess(officeId, firstEntityType, accessType, secondEntityType, includeAllOffices);
+    }
+
+    @Override
+    public String getSQLQueryInClauseIDList_ForChargesForOffice(Long officeId, boolean includeAllOffices) {
+
+        FineractEntityType firstEntityType = FineractEntityType.OFFICE;
+        FineractEntityAccessType accessType = FineractEntityAccessType.OFFICE_ACCESS_TO_CHARGES;
+        FineractEntityType secondEntityType = FineractEntityType.CHARGE;
+
+        return getSQLQueryInClause_WithListOfIDsForEntityAccess(officeId, firstEntityType, accessType, secondEntityType, includeAllOffices);
+    }
+
+    @Override
+    public Collection<FineractEntityRelationData> retrieveAllSupportedMappingTypes() {
+        EntityRelationMapper entityMapper = new EntityRelationMapper();
+        final String sql = entityMapper.schema();
+        final Collection<FineractEntityRelationData> mapTypes = this.jdbcTemplate.query(sql, entityMapper, new Object[] {});
+        return mapTypes;
+    }
+
+    private static final class EntityRelationMapper implements RowMapper<FineractEntityRelationData> {
+
+        private final StringBuilder sqlBuilder = new StringBuilder("select id as id,code_name as mapping_Types from m_entity_relation ");
+
+        public String schema() {
+            return this.sqlBuilder.toString();
+        }
+
+        @Override
+        public FineractEntityRelationData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long mappingTypesId = rs.getLong("id");
+            final String mappingTypes = rs.getString("mapping_Types");
+            return FineractEntityRelationData.getMappingTypes(mappingTypesId, mappingTypes);
+        }
+    }
+
+    @Override
+    public Collection<FineractEntityToEntityMappingData> retrieveEntityToEntityMappings(Long mapId, Long fromId, Long toId) {
+
+        EntityToEntityMapper entityToEntityMapper = new EntityToEntityMapper();
+        String sql = entityToEntityMapper.schema();
+        final Collection<FineractEntityToEntityMappingData> mapTypes = this.jdbcTemplate.query(sql, entityToEntityMapper,
+                new Object[] { mapId, fromId, fromId, toId, toId });
+        return mapTypes;
+
+    }
+
+    @Override
+    public Collection<FineractEntityToEntityMappingData> retrieveOneMapping(Long mapId) {
+        GetOneEntityMapper entityMapper = new GetOneEntityMapper();
+        String sql = entityMapper.schema();
+        final Collection<FineractEntityToEntityMappingData> mapTypes = this.jdbcTemplate.query(sql, entityMapper, new Object[] { mapId });
+        return mapTypes;
+    }
+
+    private static final class GetOneEntityMapper implements RowMapper<FineractEntityToEntityMappingData> {
+
+        private final String schema;
+
+        public GetOneEntityMapper() {
+
+            StringBuffer str = new StringBuffer("select eem.rel_id as relId, ");
+            str.append("eem.from_id as fromId,eem.to_Id as toId,eem.start_date as startDate,eem.end_date as endDate ");
+            str.append("from m_entity_to_entity_mapping eem ");
+            str.append("where eem.id= ? ");
+            this.schema = str.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+
+        @Override
+        public FineractEntityToEntityMappingData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long relId = rs.getLong("relId");
+            final Long fromId = rs.getLong("fromId");
+            final Long toId = rs.getLong("toId");
+            final Date startDate = rs.getDate("startDate");
+            final Date endDate = rs.getDate("endDate");
+            return FineractEntityToEntityMappingData.getRelatedEntities(relId, fromId, toId, startDate, endDate);
+        }
+
+    }
+
+    private static final class EntityToEntityMapper implements RowMapper<FineractEntityToEntityMappingData> {
+
+        private final String schema;
+
+        public EntityToEntityMapper() {
+
+            StringBuffer str = new StringBuffer("select eem.id as mapId, ");
+            str.append("eem.rel_id as relId, ");
+            str.append("eem.from_id as from_id, ");
+            str.append("eem.to_id as to_id, ");
+            str.append("eem.start_date as startDate, ");
+            str.append("eem.end_date as endDate, ");
+            str.append("case er.code_name ");
+            str.append("when 'office_access_to_loan_products' then ");
+            str.append("o.name ");
+            str.append("when 'office_access_to_savings_products' then ");
+            str.append("o.name ");
+            str.append("when 'office_access_to_fees/charges' then ");
+            str.append("o.name ");
+            str.append("when 'role_access_to_loan_products' then ");
+            str.append("r.name ");
+            str.append("when 'role_access_to_savings_products' then ");
+            str.append("r.name ");
+            str.append("end as from_name, ");
+            str.append("case er.code_name ");
+            str.append("when 'office_access_to_loan_products' then ");
+            str.append("lp.name ");
+            str.append("when 'office_access_to_savings_products' then ");
+            str.append("sp.name ");
+            str.append("when 'office_access_to_fees/charges' then ");
+            str.append("charge.name ");
+            str.append("when 'role_access_to_loan_products' then ");
+            str.append("lp.name ");
+            str.append("when 'role_access_to_savings_products' then ");
+            str.append("sp.name ");
+            str.append("end as to_name, ");
+            str.append("er.code_name ");
+            str.append("from m_entity_to_entity_mapping eem ");
+            str.append("join m_entity_relation er on eem.rel_id = er.id ");
+            str.append("left join m_office o on er.from_entity_type = 1 and eem.from_id = o.id ");
+            str.append("left join m_role r on er.from_entity_type = 5 and eem.from_id = r.id ");
+            str.append("left join m_product_loan lp on er.to_entity_type = 2 and eem.to_id = lp.id ");
+            str.append("left join m_savings_product sp on er.to_entity_type = 3 and eem.to_id = sp.id ");
+            str.append("left join m_charge charge on er.to_entity_type = 4 and eem.to_id = charge.id ");
+            str.append("where ");
+            str.append("er.id = ? and ");
+            str.append("( ? = 0 or from_id = ? ) and ");
+            str.append("( ? = 0 or to_id = ? ) ");
+
+            this.schema = str.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+
+        @Override
+        public FineractEntityToEntityMappingData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long mapId = rs.getLong("mapId");
+            final Long relId = rs.getLong("relId");
+            final Long fromId = rs.getLong("from_id");
+            final Long toId = rs.getLong("to_id");
+            final String fromEntity = rs.getString("from_name");
+            final String toEntity = rs.getString("to_name");
+            final Date startDate = rs.getDate("startDate");
+            final Date endDate = rs.getDate("endDate");
+            return FineractEntityToEntityMappingData.getRelatedEntities(mapId, relId, fromId, toId, startDate, endDate, fromEntity, toEntity);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessUtil.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessUtil.java
new file mode 100644
index 0000000..bb3fbc9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessUtil.java
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.entityaccess.service;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationProperty;
+import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationRepositoryWrapper;
+import org.apache.fineract.infrastructure.entityaccess.FineractEntityAccessConstants;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class FineractEntityAccessUtil {
+    
+    private final PlatformSecurityContext context;
+    private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+    private final CodeValueRepositoryWrapper codeValueRepository;
+    private final FineractEntityAccessWriteService fineractEntityAccessWriteService;
+    private final FineractEntityAccessReadService fineractEntityAccessReadService;
+
+    @Autowired
+    public FineractEntityAccessUtil (
+    		final PlatformSecurityContext context,
+    		final GlobalConfigurationRepositoryWrapper globalConfigurationRepository,
+            final FineractEntityAccessWriteService fineractEntityAccessWriteService,
+            final CodeValueReadPlatformService codeValueReadPlatformService,
+            final CodeValueRepositoryWrapper codeValueRepository,
+            final FineractEntityAccessReadService fineractEntityAccessReadService) {
+    	this.context = context;
+        this.globalConfigurationRepository = globalConfigurationRepository;
+        this.fineractEntityAccessWriteService = fineractEntityAccessWriteService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+        this.codeValueRepository = codeValueRepository;
+        this.fineractEntityAccessReadService = fineractEntityAccessReadService;
+    }
+
+	
+	@Transactional
+	public void checkConfigurationAndAddProductResrictionsForUserOffice (
+			final FineractEntityAccessType fineractEntityAccessType,
+			final FineractEntityType fineractEntityType,
+			final Long productOrChargeId) {
+		
+		AppUser thisUser = this.context.authenticatedUser();
+		
+		// check if the office specific products are enabled. If yes, then save this product or charge against a specific office
+        // i.e. this product or charge is specific for this office.
+		
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository
+        		.findOneByNameWithNotFoundDetection(
+        				FineractEntityAccessConstants.GLOBAL_CONFIG_FOR_OFFICE_SPECIFIC_PRODUCTS);
+        if (property.isEnabled() ) {
+        	// If this property is enabled, then Fineract need to restrict access to this loan product to only the office of the current user            	
+            final GlobalConfigurationProperty restrictToUserOfficeProperty = this.globalConfigurationRepository
+            		.findOneByNameWithNotFoundDetection(
+            				FineractEntityAccessConstants.GLOBAL_CONFIG_FOR_RESTRICT_PRODUCTS_TO_USER_OFFICE);
+            
+            if (restrictToUserOfficeProperty.isEnabled() ) {
+            	final Long officeId = thisUser.getOffice().getId();
+            	Collection<CodeValueData> codevalues = codeValueReadPlatformService.retrieveCodeValuesByCode(
+            			FineractEntityAccessConstants.ENTITY_ACCESS_CODENAME);
+            	if (codevalues != null) {
+            		Iterator<CodeValueData> iterator = codevalues.iterator();
+            		while(iterator.hasNext()) {
+            			CodeValueData oneCodeValue = iterator.next();
+            			if ( (oneCodeValue != null) &&
+            					(oneCodeValue.getName().equals(fineractEntityAccessType.toStr())) ) {
+            				CodeValue cv = codeValueRepository.findOneByCodeNameAndLabelWithNotFoundDetection(
+            						FineractEntityAccessConstants.ENTITY_ACCESS_CODENAME,
+            						fineractEntityAccessType.toStr()
+            						);
+            				if (cv != null) {
+            					fineractEntityAccessWriteService.addNewEntityAccess(
+            							FineractEntityType.OFFICE.getType(), officeId,
+            							cv,
+            							fineractEntityType.getType(), productOrChargeId);
+            				}
+            			}
+            		}
+            	}
+            }
+        }
+		
+	}
+	
+	public String getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled (
+			FineractEntityType fineractEntityType) {
+		String inClause = "";
+		
+		final GlobalConfigurationProperty property = this.globalConfigurationRepository
+        		.findOneByNameWithNotFoundDetection(
+        				FineractEntityAccessConstants.GLOBAL_CONFIG_FOR_OFFICE_SPECIFIC_PRODUCTS);
+		
+        if (property.isEnabled() ) {
+        	// Get 'SQL In Clause' for fetching only products/charges that are relevant for current user's office
+        	if (fineractEntityType.equals(FineractEntityType.SAVINGS_PRODUCT)) {
+        		inClause = fineractEntityAccessReadService.
+        				getSQLQueryInClauseIDList_ForSavingsProductsForOffice (
+        				this.context.authenticatedUser().getOffice().getId(), false);
+        	} else if (fineractEntityType.equals(FineractEntityType.LOAN_PRODUCT)) {
+        		inClause = fineractEntityAccessReadService.
+        				getSQLQueryInClauseIDList_ForLoanProductsForOffice (
+        				this.context.authenticatedUser().getOffice().getId(), false);
+        	} else if (fineractEntityType.equals(FineractEntityType.CHARGE)) {
+        		inClause = fineractEntityAccessReadService.
+        				getSQLQueryInClauseIDList_ForChargesForOffice(
+        				this.context.authenticatedUser().getOffice().getId(), false);
+        	}
+        }
+		return inClause;
+	}
+	
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteService.java
new file mode 100644
index 0000000..721148e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteService.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.entityaccess.service;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface FineractEntityAccessWriteService {
+
+    CommandProcessingResult createEntityAccess(final JsonCommand command);
+
+    CommandProcessingResult createEntityToEntityMapping(final Long relId,final JsonCommand command);
+
+    CommandProcessingResult updateEntityToEntityMapping(final Long mapId, final JsonCommand command);
+
+    CommandProcessingResult deleteEntityToEntityMapping(final Long mapId);
+
+    void addNewEntityAccess(final String entityType, final Long entityId, final CodeValue accessType, final String secondEntityType,
+            final Long secondEntityId);
+
+    /*
+     * CommandProcessingResult updateEntityAccess ( final Long entityAccessId,
+     * final JsonCommand command);
+     * 
+     * CommandProcessingResult removeEntityAccess ( final String entityType,
+     * final Long entityId, final Long accessType, final String
+     * secondEntityType, final Long secondEntityId);
+     */
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteServiceImpl.java
new file mode 100644
index 0000000..914a238
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteServiceImpl.java
@@ -0,0 +1,186 @@
+/**
+ * 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.entityaccess.service;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+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.entityaccess.api.FineractEntityApiResourceConstants;
+import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityDataValidator;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccess;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessRepository;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityRelation;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityRelationRepositoryWrapper;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityToEntityMapping;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityToEntityMappingRepository;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityToEntityMappingRepositoryWrapper;
+import org.apache.fineract.infrastructure.entityaccess.exception.FineractEntityToEntityMappingDateException;
+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 FineractEntityAccessWriteServiceImpl implements FineractEntityAccessWriteService {
+
+    private final static Logger logger = LoggerFactory.getLogger(FineractEntityAccessWriteServiceImpl.class);
+    private final FineractEntityAccessRepository entityAccessRepository;
+    private final FineractEntityRelationRepositoryWrapper fineractEntityRelationRepositoryWrapper;
+    private final FineractEntityToEntityMappingRepository fineractEntityToEntityMappingRepository;
+    private final FineractEntityToEntityMappingRepositoryWrapper fineractEntityToEntityMappingRepositoryWrapper;
+    private final FineractEntityDataValidator fromApiJsonDeserializer;
+
+    @Autowired
+    public FineractEntityAccessWriteServiceImpl(final FineractEntityAccessRepository entityAccessRepository,
+            final FineractEntityRelationRepositoryWrapper fineractEntityRelationRepositoryWrapper,
+            final FineractEntityToEntityMappingRepository fineractEntityToEntityMappingRepository,
+            final FineractEntityToEntityMappingRepositoryWrapper fineractEntityToEntityMappingRepositoryWrapper,
+            FineractEntityDataValidator fromApiJsonDeserializer) {
+        this.entityAccessRepository = entityAccessRepository;
+        this.fineractEntityToEntityMappingRepository = fineractEntityToEntityMappingRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.fineractEntityRelationRepositoryWrapper = fineractEntityRelationRepositoryWrapper;
+        this.fineractEntityToEntityMappingRepositoryWrapper = fineractEntityToEntityMappingRepositoryWrapper;
+    }
+
+    @Override
+    public CommandProcessingResult createEntityAccess(@SuppressWarnings("unused") JsonCommand command) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    @Transactional
+    public void addNewEntityAccess(final String entityType, final Long entityId, final CodeValue accessType, final String secondEntityType,
+            final Long secondEntityId) {
+        FineractEntityAccess entityAccess = FineractEntityAccess.createNew(entityType, entityId, accessType, secondEntityType, secondEntityId);
+        entityAccessRepository.save(entityAccess);
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult createEntityToEntityMapping(Long relId, JsonCommand command) {
+
+        try {
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final FineractEntityRelation mapId = this.fineractEntityRelationRepositoryWrapper.findOneWithNotFoundDetection(relId);
+
+            final Long fromId = command.longValueOfParameterNamed(FineractEntityApiResourceConstants.fromEnityType);
+            final Long toId = command.longValueOfParameterNamed(FineractEntityApiResourceConstants.toEntityType);
+            final Date startDate = command.DateValueOfParameterNamed(FineractEntityApiResourceConstants.startDate);
+            final Date endDate = command.DateValueOfParameterNamed(FineractEntityApiResourceConstants.endDate);
+
+            fromApiJsonDeserializer.checkForEntity(relId.toString(), fromId, toId);
+            if (startDate != null && endDate != null) {
+                if (endDate
+                        .before(startDate)) { throw new FineractEntityToEntityMappingDateException(startDate.toString(), endDate.toString()); }
+            }
+
+            final FineractEntityToEntityMapping newMap = FineractEntityToEntityMapping.newMap(mapId, fromId, toId, startDate, endDate);
+
+            this.fineractEntityToEntityMappingRepository.save(newMap);
+
+            return new CommandProcessingResultBuilder().withEntityId(newMap.getId()).withCommandId(command.commandId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult updateEntityToEntityMapping(Long mapId, JsonCommand command) {
+
+        try {
+
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final FineractEntityToEntityMapping mapForUpdate = this.fineractEntityToEntityMappingRepositoryWrapper
+                    .findOneWithNotFoundDetection(mapId);
+
+            String relId = mapForUpdate.getRelationId().getId().toString();
+            final Long fromId = command.longValueOfParameterNamed(FineractEntityApiResourceConstants.fromEnityType);
+            final Long toId = command.longValueOfParameterNamed(FineractEntityApiResourceConstants.toEntityType);
+            fromApiJsonDeserializer.checkForEntity(relId, fromId, toId);
+
+            final Map<String, Object> changes = mapForUpdate.updateMap(command);
+
+            if (!changes.isEmpty()) {
+                this.fineractEntityToEntityMappingRepository.saveAndFlush(mapForUpdate);
+            }
+            return new CommandProcessingResultBuilder(). //
+                    withEntityId(mapForUpdate.getId()).withCommandId(command.commandId()).build();
+        } catch (DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteEntityToEntityMapping(Long mapId) {
+        // TODO Auto-generated method stub
+
+        final FineractEntityToEntityMapping deleteMap = this.fineractEntityToEntityMappingRepositoryWrapper.findOneWithNotFoundDetection(mapId);
+        this.fineractEntityToEntityMappingRepository.delete(deleteMap);
+
+        return new CommandProcessingResultBuilder(). //
+                withEntityId(deleteMap.getId()).build();
+
+    }
+
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        realCause.printStackTrace();
+        if (realCause.getMessage().contains("rel_id_from_id_to_id")) {
+            final String fromId = command.stringValueOfParameterNamed(FineractEntityApiResourceConstants.fromEnityType);
+            final String toId = command.stringValueOfParameterNamed(FineractEntityApiResourceConstants.toEntityType);
+            throw new PlatformDataIntegrityException("error.msg.duplicate.entity.mapping",
+                    "EntityMapping from " + fromId + " to " + toId + " already exist");
+        }
+
+        logAsErrorUnexpectedDataIntegrityException(dve);
+        throw new PlatformDataIntegrityException("error.msg.entity.mapping", "Unknown data integrity issue with resource.");
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
+        logger.error(dve.getMessage(), dve);
+    }
+
+    /*
+     * @Override public CommandProcessingResult updateEntityAccess(Long
+     * entityAccessId, JsonCommand command) { // TODO Auto-generated method stub
+     * return null; }
+     * 
+     * @Override public CommandProcessingResult removeEntityAccess(String
+     * entityType, Long entityId, Long accessType, String secondEntityType, Long
+     * secondEntityId) { // TODO Auto-generated method stub return null; }
+     */
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java
new file mode 100644
index 0000000..96d53d8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java
@@ -0,0 +1,75 @@
+/**
+ * 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.hooks.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class HookApiConstants {
+
+    public static final String HOOK_RESOURCE_NAME = "HOOK";
+
+    public static final String nameParamName = "name";
+
+    public static final String displayNameParamName = "displayName";
+
+    public static final String isActiveParamName = "isActive";
+
+    public static final String webTemplateName = "Web";
+
+    public static final String smsTemplateName = "SMS Bridge";
+
+    public static final String payloadURLName = "Payload URL";
+
+    public static final String contentTypeName = "Content Type";
+
+    public static final String smsProviderName = "SMS Provider";
+
+    public static final String smsProviderAccountIdName = "SMS Provider Account Id";
+
+    public static final String smsProviderTokenIdName = "SMS Provider Token";
+
+    public static final String phoneNumberName = "Phone Number";
+
+    public static final String apiKeyName = "Api Key";
+
+    public static final String configParamName = "config";
+
+    public static final String eventsParamName = "events";
+
+    public static final String entityNameParamName = "entityName";
+
+    public static final String actionNameParamName = "actionName";
+
+    public static final String templateIdParamName = "templateId";
+
+    public static final String templateNameParamName = "templateName";
+
+    public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(nameParamName, displayNameParamName,
+                    templateIdParamName, isActiveParamName, configParamName,
+                    eventsParamName, templateNameParamName));
+
+    public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(nameParamName, displayNameParamName,
+                    templateIdParamName, isActiveParamName, configParamName,
+                    eventsParamName, templateNameParamName));
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiResource.java
new file mode 100644
index 0000000..5aff5da
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiResource.java
@@ -0,0 +1,172 @@
+/**
+ * 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.hooks.api;
+
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.HOOK_RESOURCE_NAME;
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.RESPONSE_DATA_PARAMETERS;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.hooks.data.HookData;
+import org.apache.fineract.infrastructure.hooks.service.HookReadPlatformService;
+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("/hooks")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+public class HookApiResource {
+
+	private final PlatformSecurityContext context;
+	private final HookReadPlatformService readPlatformService;
+	private final DefaultToApiJsonSerializer<HookData> toApiJsonSerializer;
+	private final ApiRequestParameterHelper apiRequestParameterHelper;
+	private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+	@Autowired
+	public HookApiResource(
+			final PlatformSecurityContext context,
+			final HookReadPlatformService readPlatformService,
+			final DefaultToApiJsonSerializer<HookData> toApiJsonSerializer,
+			final ApiRequestParameterHelper apiRequestParameterHelper,
+			final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+		this.context = context;
+		this.readPlatformService = readPlatformService;
+		this.toApiJsonSerializer = toApiJsonSerializer;
+		this.apiRequestParameterHelper = apiRequestParameterHelper;
+		this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+	}
+
+	@GET
+	public String retrieveHooks(@Context final UriInfo uriInfo) {
+
+		this.context.authenticatedUser().validateHasReadPermission(
+				HOOK_RESOURCE_NAME);
+
+		final Collection<HookData> hooks = this.readPlatformService
+				.retrieveAllHooks();
+
+		final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+				.process(uriInfo.getQueryParameters());
+		return this.toApiJsonSerializer.serialize(settings, hooks,
+				RESPONSE_DATA_PARAMETERS);
+	}
+
+	@GET
+	@Path("{hookId}")
+	public String retrieveHook(@PathParam("hookId") final Long hookId,
+			@Context final UriInfo uriInfo) {
+
+		this.context.authenticatedUser().validateHasReadPermission(
+				HOOK_RESOURCE_NAME);
+
+		final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+				.process(uriInfo.getQueryParameters());
+
+		HookData hook = this.readPlatformService.retrieveHook(hookId);
+
+		if (settings.isTemplate()) {
+			final HookData hookData = this.readPlatformService
+					.retrieveNewHookDetails(hook.getTemplateName());
+			hook = HookData.templateExisting(hook, hookData.getTemplates(),
+					hookData.getGroupings());
+		}
+		return this.toApiJsonSerializer.serialize(settings, hook,
+				RESPONSE_DATA_PARAMETERS);
+	}
+
+	@GET
+	@Path("template")
+	public String template(@Context final UriInfo uriInfo) {
+
+		this.context.authenticatedUser().validateHasReadPermission(
+				HOOK_RESOURCE_NAME);
+
+		final HookData hook = this.readPlatformService
+				.retrieveNewHookDetails(null);
+
+		final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+				.process(uriInfo.getQueryParameters());
+		return this.toApiJsonSerializer.serialize(settings, hook,
+				RESPONSE_DATA_PARAMETERS);
+	}
+
+	@POST
+	public String createHook(final String apiRequestBodyAsJson) {
+
+		final CommandWrapper commandRequest = new CommandWrapperBuilder()
+				.createHook().withJson(apiRequestBodyAsJson).build();
+
+		final CommandProcessingResult result = this.commandsSourceWritePlatformService
+				.logCommandSource(commandRequest);
+
+		return this.toApiJsonSerializer.serialize(result);
+	}
+
+	@PUT
+	@Path("{hookId}")
+	public String updateHook(@PathParam("hookId") final Long hookId,
+			final String apiRequestBodyAsJson) {
+
+		final CommandWrapper commandRequest = new CommandWrapperBuilder()
+				.updateHook(hookId).withJson(apiRequestBodyAsJson).build();
+
+		final CommandProcessingResult result = this.commandsSourceWritePlatformService
+				.logCommandSource(commandRequest);
+
+		return this.toApiJsonSerializer.serialize(result);
+	}
+
+	@DELETE
+	@Path("{hookId}")
+	public String deleteHook(@PathParam("hookId") final Long hookId) {
+
+		final CommandWrapper commandRequest = new CommandWrapperBuilder()
+				.deleteHook(hookId).build();
+
+		final CommandProcessingResult result = this.commandsSourceWritePlatformService
+				.logCommandSource(commandRequest);
+
+		return this.toApiJsonSerializer.serialize(result);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Entity.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Entity.java
new file mode 100644
index 0000000..fe7bdf8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Entity.java
@@ -0,0 +1,43 @@
+/**
+ * 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.hooks.data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class Entity implements Serializable {
+
+	private String name;
+
+	private List<String> actions;
+
+	public void setName(final String name) {
+		this.name = name;
+	}
+
+	public void setActions(final List<String> actions) {
+		this.actions = actions;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Event.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Event.java
new file mode 100644
index 0000000..8ecc9c3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Event.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.hooks.data;
+
+import java.io.Serializable;
+
+public class Event implements Serializable {
+
+	private final String actionName;
+	private final String entityName;
+
+	public static Event instance(final String actionName,
+			final String entityName) {
+		return new Event(actionName, entityName);
+	}
+
+	private Event(final String actionName, final String entityName) {
+		this.actionName = actionName;
+		this.entityName = entityName;
+	}
+
+	public String getActionName() {
+		return this.actionName;
+	}
+
+	public String getEntityName() {
+		return this.entityName;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/EventResultSetExtractor.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/EventResultSetExtractor.java
new file mode 100644
index 0000000..01d07c8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/EventResultSetExtractor.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.infrastructure.hooks.data;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.ResultSetExtractor;
+
+public class EventResultSetExtractor implements
+		ResultSetExtractor<List<Grouping>> {
+
+	@Override
+	public List<Grouping> extractData(final ResultSet rs) throws SQLException,
+			DataAccessException {
+		final List<Grouping> groupings = new ArrayList<>();
+
+		final Map<String, Map<String, List<String>>> groupToEntityMapping = new HashMap<>();
+		Map<String, List<String>> entityToActionMapping = new HashMap<>();
+
+		while (rs.next()) {
+			final String groupingName = rs.getString("grouping");
+			final String entityName = rs.getString("entity_name");
+			final String actionName = rs.getString("action_name");
+			Map<String, List<String>> entities = groupToEntityMapping
+					.get(groupingName);
+			List<String> actions = entityToActionMapping.get(entityName);
+
+			if (entities == null) {
+				entityToActionMapping = new HashMap<>();
+			}
+
+			if (actions == null) {
+				actions = new ArrayList<>();
+			}
+			actions.add(actionName);
+			entityToActionMapping.put(entityName, actions);
+
+			if (entities == null) {
+				entities = new HashMap<>();
+			}
+			entities.putAll(entityToActionMapping);
+			groupToEntityMapping.put(groupingName, entities);
+		}
+
+		for (final Entry<String, Map<String, List<String>>> groupingEntry : groupToEntityMapping
+				.entrySet()) {
+			final List<Entity> entities = new ArrayList<>();
+			final Grouping group = new Grouping();
+			group.setName(groupingEntry.getKey());
+			for (final Entry<String, List<String>> entityEntry : groupingEntry
+					.getValue().entrySet()) {
+				final List<String> actions = new ArrayList<>();
+				final Entity entity = new Entity();
+				entity.setName(entityEntry.getKey());
+				for (final String action : entityEntry.getValue()) {
+					actions.add(action);
+				}
+				Collections.sort(actions);
+				entity.setActions(actions);
+				entities.add(entity);
+			}
+
+			Collections.sort(entities, new Comparator<Entity>() {
+				@Override
+				public int compare(final Entity entity1, final Entity entity2) {
+					return entity1.getName().compareTo(entity2.getName());
+				}
+			});
+			group.setEntities(entities);
+			groupings.add(group);
+		}
+
+		Collections.sort(groupings, new Comparator<Grouping>() {
+			@Override
+			public int compare(final Grouping grouping1,
+					final Grouping grouping2) {
+				return grouping1.getName().compareTo(grouping2.getName());
+			}
+		});
+
+		return groupings;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Field.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Field.java
new file mode 100644
index 0000000..84d25b5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Field.java
@@ -0,0 +1,63 @@
+/**
+ * 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.hooks.data;
+
+@SuppressWarnings("unused")
+public class Field {
+
+	private final String fieldName;
+	private final String fieldValue;
+	private final String fieldType;
+	private final Boolean optional;
+	private final String placeholder;
+
+	public static Field fromConfig(final String fieldName,
+			final String fieldValue) {
+		return new Field(null, fieldName, fieldValue, null, null);
+	}
+
+	public static Field fromSchema(final String fieldType,
+			final String fieldName, final Boolean optional,
+			final String placeholder) {
+		return new Field(fieldType, fieldName, null, optional, placeholder);
+	}
+
+	private Field(final String fieldType, final String fieldName,
+			final String fieldValue, final Boolean optional,
+			final String placeholder) {
+		this.fieldType = fieldType;
+		this.fieldName = fieldName;
+		this.fieldValue = fieldValue;
+		this.optional = optional;
+		this.placeholder = placeholder;
+	}
+
+	public String getFieldName() {
+		return this.fieldName;
+	}
+
+	public String getFieldValue() {
+		return this.fieldValue;
+	}
+
+	public String getFieldType() {
+		return this.fieldType;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Grouping.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Grouping.java
new file mode 100644
index 0000000..416fe7b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/Grouping.java
@@ -0,0 +1,43 @@
+/**
+ * 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.hooks.data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class Grouping implements Serializable {
+
+	private String name;
+
+	private List<Entity> entities;
+
+	public void setName(final String name) {
+		this.name = name;
+	}
+
+	public void setEntities(final List<Entity> entities) {
+		this.entities = entities;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/HookData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/HookData.java
new file mode 100644
index 0000000..041695c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/HookData.java
@@ -0,0 +1,112 @@
+/**
+ * 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.hooks.data;
+
+import org.joda.time.LocalDate;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class HookData implements Serializable {
+
+    private final Long id;
+    private final String name;
+    private final String displayName;
+    private final Boolean isActive;
+    private final LocalDate createdAt;
+    private final LocalDate updatedAt;
+    private final Long templateId;
+    private final String templateName;
+
+    // associations
+    private final List<Event> events;
+    private final List<Field> config;
+
+    // template data
+    private final List<HookTemplateData> templates;
+    private final List<Grouping> groupings;
+
+    public static HookData instance(final Long id, final String name,
+            final String displayName, final boolean isActive,
+            final LocalDate createdAt, final LocalDate updatedAt,
+            final Long templateId, final List<Event> registeredEvents,
+            final List<Field> config, final String templateName) {
+        return new HookData(id, name, displayName, isActive, createdAt,
+                updatedAt, templateId, registeredEvents, config, templateName,
+                null, null);
+    }
+
+    public static HookData template(final List<HookTemplateData> templates,
+            final List<Grouping> groupings) {
+        return new HookData(null, null, null, null, null, null, null, null,
+                null, null, templates, groupings);
+    }
+
+    public static HookData templateExisting(final HookData hookData,
+            final List<HookTemplateData> templates,
+            final List<Grouping> groupings) {
+        return new HookData(hookData.id, hookData.name, hookData.displayName,
+                hookData.isActive, hookData.createdAt, hookData.updatedAt,
+                hookData.templateId, hookData.events, hookData.config,
+                hookData.templateName, templates, groupings);
+    }
+
+    private HookData(final Long id, final String name,
+            final String displayName, final Boolean isActive,
+            final LocalDate createdAt, final LocalDate updatedAt,
+            final Long templateId, final List<Event> events,
+            final List<Field> config, final String templateName,
+            final List<HookTemplateData> templates,
+            final List<Grouping> groupings) {
+        this.id = id;
+        this.name = name;
+        this.displayName = displayName;
+        this.isActive = isActive;
+        this.createdAt = createdAt;
+        this.updatedAt = updatedAt;
+        this.templateId = templateId;
+        this.templateName = templateName;
+
+        // associations
+        this.events = events;
+        this.config = config;
+
+        // template
+        this.templates = templates;
+        this.groupings = groupings;
+
+    }
+
+    public Long getHookId() {
+        return this.id;
+    }
+
+    public List<HookTemplateData> getTemplates() {
+        return this.templates;
+    }
+
+    public List<Grouping> getGroupings() {
+        return this.groupings;
+    }
+
+    public String getTemplateName() {
+        return this.name;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/HookTemplateData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/HookTemplateData.java
new file mode 100644
index 0000000..01aeadc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/data/HookTemplateData.java
@@ -0,0 +1,51 @@
+/**
+ * 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.hooks.data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class HookTemplateData implements Serializable {
+
+	private final Long id;
+	private final String name;
+
+	// associations
+	private final List<Field> schema;
+
+	public static HookTemplateData instance(final Long id, final String name,
+			final List<Field> schema) {
+		return new HookTemplateData(id, name, schema);
+	}
+
+	private HookTemplateData(final Long id, final String name,
+			final List<Field> schema) {
+		this.id = id;
+		this.name = name;
+
+		// associations
+		this.schema = schema;
+	}
+
+	public Long getServiceId() {
+		return this.id;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/Hook.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/Hook.java
new file mode 100644
index 0000000..986e690
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/Hook.java
@@ -0,0 +1,199 @@
+/**
+ * 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.hooks.domain;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.template.domain.Template;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.util.CollectionUtils;
+
+import javax.persistence.*;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.*;
+
+@Entity
+@Table(name = "m_hook")
+public class Hook extends AbstractAuditableCustom<AppUser, Long> {
+
+    @Column(name = "name", nullable = false, length = 100)
+    private String name;
+
+    @Column(name = "is_active", nullable = false)
+    private Boolean isActive;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "hook", orphanRemoval = true)
+    private Set<HookResource> events = new HashSet<>();
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "hook", orphanRemoval = true)
+    private Set<HookConfiguration> config = new HashSet<>();
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "template_id", referencedColumnName = "id", nullable = false)
+    private HookTemplate template;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "ugd_template_id", referencedColumnName = "id", nullable = true)
+    private Template ugdTemplate;
+
+    protected Hook() {
+        //
+    }
+
+    public static Hook fromJson(final JsonCommand command, final HookTemplate template, final Set<HookConfiguration> config,
+            final Set<HookResource> events, final Template ugdTemplate) {
+        final String displayName = command.stringValueOfParameterNamed(displayNameParamName);
+        Boolean isActive = command.booleanObjectValueOfParameterNamed(isActiveParamName);
+        if (isActive == null) isActive = false;
+        return new Hook(template, displayName, isActive, config, events, ugdTemplate);
+    }
+
+    private Hook(final HookTemplate template, final String displayName, final Boolean isActive, final Set<HookConfiguration> config,
+            final Set<HookResource> events, final Template ugdTemplate) {
+
+        this.template = template;
+
+        if (StringUtils.isNotBlank(displayName)) {
+            this.name = displayName.trim();
+        } else {
+            this.name = template.getName();
+        }
+        this.isActive = isActive;
+        if (!CollectionUtils.isEmpty(config)) {
+            this.config = associateConfigWithThisHook(config);
+        }
+        if (!CollectionUtils.isEmpty(events)) {
+            this.events = associateEventsWithThisHook(events);
+        }
+
+        this.ugdTemplate = ugdTemplate;
+    }
+
+    private Set<HookConfiguration> associateConfigWithThisHook(final Set<HookConfiguration> config) {
+        for (final HookConfiguration hookConfiguration : config) {
+            hookConfiguration.update(this);
+        }
+        return config;
+    }
+
+    private Set<HookResource> associateEventsWithThisHook(final Set<HookResource> events) {
+        for (final HookResource hookResource : events) {
+            hookResource.update(this);
+        }
+        return events;
+    }
+
+    public HookTemplate getHookTemplate() {
+        return this.template;
+    }
+
+    public Template getUgdTemplate() {
+        return this.ugdTemplate;
+    }
+
+    public Long getUgdTemplateId() {
+        Long templateId = null;
+        if (this.ugdTemplate != null) {
+            templateId = this.ugdTemplate.getId();
+        }
+        return templateId;
+    }
+
+    public Set<HookConfiguration> getHookConfig() {
+        return this.config;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(5);
+
+        if (command.isChangeInStringParameterNamed(displayNameParamName, this.name)) {
+            final String newValue = command.stringValueOfParameterNamed(displayNameParamName);
+            actualChanges.put(displayNameParamName, newValue);
+            this.name = newValue;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(isActiveParamName, this.isActive)) {
+            final Boolean newValue = command.booleanObjectValueOfParameterNamed(isActiveParamName);
+            actualChanges.put(isActiveParamName, newValue);
+            this.isActive = newValue;
+        }
+
+        if (command.isChangeInLongParameterNamed(templateIdParamName, getUgdTemplateId())) {
+            final Long newValue = command.longValueOfParameterNamed(templateIdParamName);
+            actualChanges.put(templateIdParamName, newValue);
+        }
+
+        // events
+        if (command.hasParameter(eventsParamName)) {
+            final JsonArray jsonArray = command.arrayOfParameterNamed(eventsParamName);
+            if (jsonArray != null) {
+                actualChanges.put(eventsParamName, jsonArray);
+            }
+        }
+
+        // config
+        if (command.hasParameter(configParamName)) {
+            final JsonElement element = command.parsedJson().getAsJsonObject().get(configParamName);
+            if (element != null) {
+                actualChanges.put(configParamName, element);
+            }
+        }
+
+        return actualChanges;
+    }
+
+    public boolean updateEvents(final Set<HookResource> newHookEvents) {
+        if (newHookEvents == null) { return false; }
+
+        if (this.events == null) {
+            this.events = new HashSet<>();
+        }
+        this.events.clear();
+        this.events.addAll(associateEventsWithThisHook(newHookEvents));
+        return true;
+    }
+
+    public boolean updateConfig(final Set<HookConfiguration> newHookConfig) {
+        if (newHookConfig == null) { return false; }
+
+        if (this.config == null) {
+            this.config = new HashSet<>();
+        }
+        this.config.clear();
+        this.config.addAll(associateConfigWithThisHook(newHookConfig));
+        return true;
+    }
+
+    public void updateUgdTemplate(final Template ugdTemplate) {
+        this.ugdTemplate = ugdTemplate;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookConfiguration.java
new file mode 100644
index 0000000..5ed0e66
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookConfiguration.java
@@ -0,0 +1,85 @@
+/**
+ * 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.hooks.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_hook_configuration")
+public class HookConfiguration extends AbstractPersistable<Long> {
+
+	@ManyToOne(optional = false)
+	@JoinColumn(name = "hook_id", referencedColumnName = "id", nullable = false)
+	private Hook hook;
+
+	@Column(name = "field_type", nullable = false, length = 20)
+	private String fieldType;
+
+	@Column(name = "field_name", nullable = false, length = 100)
+	private String fieldName;
+
+	@Column(name = "field_value", nullable = false, length = 100)
+	private String fieldValue;
+
+	public static HookConfiguration createNewWithoutHook(
+			final String fieldType, final String fieldName,
+			final String fieldValue) {
+		return new HookConfiguration(null, fieldType, fieldName, fieldValue);
+	}
+	
+	public static HookConfiguration createNew(final Hook hook, final String fieldType,
+			final String fieldName, final String fieldValue) {
+		return new HookConfiguration(hook, fieldType, fieldName, fieldValue);
+	}
+
+	protected HookConfiguration() {
+		//
+	}
+
+	private HookConfiguration(final Hook hook, final String fieldType,
+			final String fieldName, final String fieldValue) {
+		this.hook = hook;
+		this.fieldType = fieldType;
+		this.fieldName = fieldName;
+		this.fieldValue = fieldValue;
+	}
+
+	public String getFieldName() {
+		return this.fieldName;
+	}
+
+	public String getFieldType() {
+		return this.fieldType;
+	}
+
+	public String getFieldValue() {
+		return this.fieldValue;
+	}
+
+	public void update(final Hook hook) {
+		this.hook = hook;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookConfigurationRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookConfigurationRepository.java
new file mode 100644
index 0000000..f59d80e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookConfigurationRepository.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.hooks.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 HookConfigurationRepository extends
+		JpaRepository<HookConfiguration, Long>,
+		JpaSpecificationExecutor<HookConfiguration> {
+
+	@Query("select config.fieldValue from HookConfiguration config where config.hook.id = :hookId and config.fieldName = :fieldName")
+	String findOneByHookIdAndFieldName(@Param("hookId") Long hookId,
+			@Param("fieldName") String fieldName);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookRepository.java
new file mode 100644
index 0000000..9501142
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookRepository.java
@@ -0,0 +1,39 @@
+/**
+ * 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.hooks.domain;
+
+import java.util.List;
+
+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 HookRepository extends JpaRepository<Hook, Long>,
+		JpaSpecificationExecutor<Hook> {
+
+	@Query("select hook from Hook hook inner join hook.events event where event.entityName = :entityName and event.actionName = :actionName and hook.isActive = 1")
+	List<Hook> findAllHooksListeningToEvent(
+			@Param("entityName") String entityName,
+			@Param("actionName") String actionName);
+
+	@Query("from Hook hook where hook.template.id = :templateId ")
+	Hook findOneByTemplateId(@Param("templateId") Long templateId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookResource.java
new file mode 100644
index 0000000..9b6b2f6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookResource.java
@@ -0,0 +1,62 @@
+/**
+ * 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.hooks.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_hook_registered_events")
+public class HookResource extends AbstractPersistable<Long> {
+
+	@ManyToOne(optional = false)
+	@JoinColumn(name = "hook_id", referencedColumnName = "id", nullable = false)
+	private Hook hook;
+
+	@Column(name = "entity_name", nullable = false, length = 45)
+	private String entityName;
+
+	@Column(name = "action_name", nullable = false, length = 45)
+	private String actionName;
+
+	protected HookResource() {
+		//
+	}
+
+	public static HookResource createNewWithoutHook(final String entityName,
+			final String actionName) {
+		return new HookResource(null, entityName, actionName);
+	}
+
+	private HookResource(final Hook hook, final String entityName,
+			final String actionName) {
+		this.hook = hook;
+		this.entityName = entityName;
+		this.actionName = actionName;
+	}
+
+	public void update(final Hook hook) {
+		this.hook = hook;
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookTemplate.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookTemplate.java
new file mode 100644
index 0000000..0da4af8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookTemplate.java
@@ -0,0 +1,74 @@
+/**
+ * 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.hooks.domain;
+
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.nameParamName;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_hook_templates")
+public class HookTemplate extends AbstractPersistable<Long> {
+
+	@Column(name = "name", nullable = false, length = 100)
+	private String name;
+
+	@LazyCollection(LazyCollectionOption.FALSE)
+	@OneToMany(cascade = CascadeType.ALL, mappedBy = "template", orphanRemoval = true)
+	private final Set<Schema> fields = new HashSet<>();
+
+	private HookTemplate(final String name) {
+
+		if (StringUtils.isNotBlank(name)) {
+			this.name = name.trim();
+		} else {
+			this.name = null;
+		}
+	}
+
+	protected HookTemplate() {
+
+	}
+
+	public static HookTemplate fromJson(final JsonCommand command) {
+		final String name = command.stringValueOfParameterNamed(nameParamName);
+		return new HookTemplate(name);
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	public Set<Schema> getSchema() {
+		return this.fields;
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookTemplateRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookTemplateRepository.java
new file mode 100644
index 0000000..2902b40
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/HookTemplateRepository.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.infrastructure.hooks.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 HookTemplateRepository extends
+		JpaRepository<HookTemplate, Long>,
+		JpaSpecificationExecutor<HookTemplate> {
+
+	@Query("from HookTemplate template where template.name = :name")
+	HookTemplate findOne(@Param("name") String name);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/Schema.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/Schema.java
new file mode 100644
index 0000000..728e292
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/domain/Schema.java
@@ -0,0 +1,65 @@
+/**
+ * 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.hooks.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_hook_schema")
+public class Schema extends AbstractPersistable<Long> {
+
+	@ManyToOne(optional = false)
+	@JoinColumn(name = "hook_template_id", referencedColumnName = "id", nullable = false)
+	private HookTemplate template;
+
+	@Column(name = "field_type", nullable = false, length = 20)
+	private String fieldType;
+
+	@Column(name = "field_name", nullable = false, length = 100)
+	private String fieldName;
+
+	@Column(name = "placeholder", length = 100)
+	private String placeholder;
+
+	@Column(name = "optional", nullable = false)
+	private final boolean optional = false;
+
+	public Schema() {
+		//
+	}
+
+	public String getFieldName() {
+		return this.fieldName;
+	}
+
+	public String getFieldType() {
+		return this.fieldType;
+	}
+
+	public boolean isOptional() {
+		return this.optional;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/event/HookEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/event/HookEvent.java
new file mode 100644
index 0000000..63ffd68
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/event/HookEvent.java
@@ -0,0 +1,65 @@
+/**
+ * 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.hooks.event;
+
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.context.ApplicationEvent;
+
+public class HookEvent extends ApplicationEvent {
+
+	private final String payload;
+
+	private final String tenantIdentifier;
+
+	private final AppUser appUser;
+
+	private final String authToken;
+
+	public HookEvent(final HookEventSource source, final String payload,
+			final String tenantIdentifier, final AppUser appUser,
+			final String authToken) {
+		super(source);
+		this.payload = payload;
+		this.tenantIdentifier = tenantIdentifier;
+		this.appUser = appUser;
+		this.authToken = authToken;
+	}
+
+	public String getPayload() {
+		return this.payload;
+	}
+
+	@Override
+	public HookEventSource getSource() {
+		return (HookEventSource) super.source;
+	}
+
+	public String getTenantIdentifier() {
+		return this.tenantIdentifier;
+	}
+
+	public AppUser getAppUser() {
+		return this.appUser;
+	}
+
+	public String getAuthToken() {
+		return this.authToken;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/event/HookEventSource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/event/HookEventSource.java
new file mode 100644
index 0000000..283465d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/event/HookEventSource.java
@@ -0,0 +1,43 @@
+/**
+ * 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.hooks.event;
+
+import java.io.Serializable;
+
+public class HookEventSource implements Serializable {
+
+	private final String entityName;
+
+	private final String actionName;
+
+	public HookEventSource(final String entityName, final String actionName) {
+		super();
+		this.entityName = entityName;
+		this.actionName = actionName;
+	}
+
+	public String getEntityName() {
+		return this.entityName;
+	}
+
+	public String getActionName() {
+		return this.actionName;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/exception/HookNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/exception/HookNotFoundException.java
new file mode 100644
index 0000000..b92fa1d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/exception/HookNotFoundException.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.infrastructure.hooks.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class HookNotFoundException extends
+		AbstractPlatformResourceNotFoundException {
+
+	public HookNotFoundException(final String name) {
+		super("error.msg.hook.not.found", "Hook with name `" + name
+				+ "` does not exist", name);
+	}
+
+	public HookNotFoundException(final Long hookId) {
+		super("error.msg.hook.identifier.not.found", "Hook with identifier `"
+				+ hookId + "` does not exist", hookId);
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/exception/HookTemplateNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/exception/HookTemplateNotFoundException.java
new file mode 100644
index 0000000..3bd91f6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/exception/HookTemplateNotFoundException.java
@@ -0,0 +1,37 @@
+/**
+ * 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.hooks.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class HookTemplateNotFoundException extends
+		AbstractPlatformResourceNotFoundException {
+
+	public HookTemplateNotFoundException(final String name) {
+		super("error.msg.template.not.found", "Template with name `" + name
+				+ "` does not exist", name);
+	}
+
+	public HookTemplateNotFoundException(final Long templateId) {
+		super("error.msg.template.identifier.not.found",
+				"Template with identifier `" + templateId + "` does not exist",
+				templateId);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/handler/CreateHookCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/handler/CreateHookCommandHandler.java
new file mode 100644
index 0000000..55cd759
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/handler/CreateHookCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.hooks.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.infrastructure.hooks.service.HookWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "HOOK", action = "CREATE")
+public class CreateHookCommandHandler implements NewCommandSourceHandler {
+
+	private final HookWritePlatformService writePlatformService;
+
+	@Autowired
+	public CreateHookCommandHandler(
+			final HookWritePlatformService writePlatformService) {
+		this.writePlatformService = writePlatformService;
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(final JsonCommand command) {
+
+		return this.writePlatformService.createHook(command);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/handler/DeleteHookCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/handler/DeleteHookCommandHandler.java
new file mode 100644
index 0000000..01da33b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/handler/DeleteHookCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.hooks.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.infrastructure.hooks.service.HookWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "HOOK", action = "DELETE")
+public class DeleteHookCommandHandler implements NewCommandSourceHandler {
+
+	private final HookWritePlatformService writePlatformService;
+
+	@Autowired
+	public DeleteHookCommandHandler(
+			final HookWritePlatformService writePlatformService) {
+		this.writePlatformService = writePlatformService;
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(final JsonCommand command) {
+
+		return this.writePlatformService.deleteHook(command.entityId());
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/handler/UpdateHookCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/handler/UpdateHookCommandHandler.java
new file mode 100644
index 0000000..500eeae
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/handler/UpdateHookCommandHandler.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.infrastructure.hooks.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.infrastructure.hooks.service.HookWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "HOOK", action = "UPDATE")
+public class UpdateHookCommandHandler implements NewCommandSourceHandler {
+
+	private final HookWritePlatformService writePlatformService;
+
+	@Autowired
+	public UpdateHookCommandHandler(
+			final HookWritePlatformService writePlatformService) {
+		this.writePlatformService = writePlatformService;
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(final JsonCommand command) {
+
+		return this.writePlatformService
+				.updateHook(command.entityId(), command);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/FineractHookListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/FineractHookListener.java
new file mode 100644
index 0000000..749aac5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/FineractHookListener.java
@@ -0,0 +1,80 @@
+/**
+ * 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.hooks.listener;
+
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.hooks.domain.Hook;
+import org.apache.fineract.infrastructure.hooks.event.HookEvent;
+import org.apache.fineract.infrastructure.hooks.event.HookEventSource;
+import org.apache.fineract.infrastructure.hooks.processor.HookProcessor;
+import org.apache.fineract.infrastructure.hooks.processor.HookProcessorProvider;
+import org.apache.fineract.infrastructure.hooks.service.HookReadPlatformService;
+import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class FineractHookListener implements HookListener {
+
+    private final HookProcessorProvider hookProcessorProvider;
+    private final HookReadPlatformService hookReadPlatformService;
+    private final TenantDetailsService tenantDetailsService;
+
+    @Autowired
+    public FineractHookListener(final HookProcessorProvider hookProcessorProvider,
+            final HookReadPlatformService hookReadPlatformService,
+            final TenantDetailsService tenantDetailsService) {
+        this.hookReadPlatformService = hookReadPlatformService;
+        this.hookProcessorProvider = hookProcessorProvider;
+        this.tenantDetailsService = tenantDetailsService;
+    }
+
+    @Override
+    public void onApplicationEvent(final HookEvent event) {
+
+        final String tenantIdentifier = event.getTenantIdentifier();
+        final FineractPlatformTenant tenant = this.tenantDetailsService
+                .loadTenantById(tenantIdentifier);
+        ThreadLocalContextUtil.setTenant(tenant);
+
+        final AppUser appUser = event.getAppUser();
+        final String authToken = event.getAuthToken();
+
+        final HookEventSource hookEventSource = event.getSource();
+        final String entityName = hookEventSource.getEntityName();
+        final String actionName = hookEventSource.getActionName();
+        final String payload = event.getPayload();
+
+        final List<Hook> hooks = this.hookReadPlatformService
+                .retrieveHooksByEvent(hookEventSource.getEntityName(),
+                        hookEventSource.getActionName());
+
+        for (final Hook hook : hooks) {
+            final HookProcessor processor = this.hookProcessorProvider
+                    .getProcessor(hook);
+            processor.process(hook, appUser, payload, entityName, actionName,
+                    tenantIdentifier, authToken);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/HookListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/HookListener.java
new file mode 100644
index 0000000..ef4bac2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/HookListener.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.infrastructure.hooks.listener;
+
+import org.apache.fineract.infrastructure.hooks.event.HookEvent;
+import org.springframework.context.ApplicationListener;
+
+public interface HookListener extends ApplicationListener<HookEvent> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessor.java
new file mode 100644
index 0000000..1a1bacd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessor.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.infrastructure.hooks.processor;
+
+import org.apache.fineract.infrastructure.hooks.domain.Hook;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+public interface HookProcessor {
+
+	void process(Hook hook, AppUser appUser, String payload, String entityName,
+			String actionName, String tenantIdentifier, String authToken);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java
new file mode 100644
index 0000000..09e1027
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java
@@ -0,0 +1,56 @@
+/**
+ * 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.hooks.processor;
+
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.smsTemplateName;
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.webTemplateName;
+
+import org.apache.fineract.infrastructure.hooks.domain.Hook;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Service;
+
+@Service
+public class HookProcessorProvider implements ApplicationContextAware {
+
+	private ApplicationContext applicationContext;
+
+	@Override
+	public void setApplicationContext(
+			final ApplicationContext applicationContext) throws BeansException {
+		this.applicationContext = applicationContext;
+	}
+
+	public HookProcessor getProcessor(final Hook hook) {
+		HookProcessor processor;
+		final String templateName = hook.getHookTemplate().getName();
+		if (templateName.equalsIgnoreCase(smsTemplateName)) {
+			processor = this.applicationContext.getBean("twilioHookProcessor",
+					TwilioHookProcessor.class);
+		} else if (templateName.equals(webTemplateName)) {
+			processor = this.applicationContext.getBean("webHookProcessor",
+					WebHookProcessor.class);
+		} else {
+			processor = null;
+		}
+		return processor;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java
new file mode 100644
index 0000000..3770039
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java
@@ -0,0 +1,123 @@
+/**
+ * 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.hooks.processor;
+
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import retrofit.Callback;
+import retrofit.RestAdapter;
+import retrofit.RetrofitError;
+import retrofit.client.OkClient;
+import retrofit.client.Response;
+
+import com.squareup.okhttp.OkHttpClient;
+
+@SuppressWarnings("unused")
+public class ProcessorHelper {
+
+	private final static Logger logger = LoggerFactory
+			.getLogger(ProcessorHelper.class);
+
+	@SuppressWarnings("null")
+	public static OkHttpClient configureClient(final OkHttpClient client) {
+		final TrustManager[] certs = new TrustManager[] { new X509TrustManager() {
+
+			@Override
+			public X509Certificate[] getAcceptedIssuers() {
+				return null;
+			}
+
+			@Override
+			public void checkServerTrusted(final X509Certificate[] chain,
+					final String authType) throws CertificateException {
+			}
+
+			@Override
+			public void checkClientTrusted(final X509Certificate[] chain,
+					final String authType) throws CertificateException {
+			}
+		} };
+
+		SSLContext ctx = null;
+		try {
+			ctx = SSLContext.getInstance("TLS");
+			ctx.init(null, certs, new SecureRandom());
+		} catch (final java.security.GeneralSecurityException ex) {
+		}
+
+		try {
+			final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
+				@Override
+				public boolean verify(final String hostname,
+						final SSLSession session) {
+					return true;
+				}
+			};
+			client.setHostnameVerifier(hostnameVerifier);
+			client.setSslSocketFactory(ctx.getSocketFactory());
+		} catch (final Exception e) {
+		}
+
+		return client;
+	}
+
+	public static OkHttpClient createClient() {
+		final OkHttpClient client = new OkHttpClient();
+		return configureClient(client);
+	}
+
+	@SuppressWarnings("rawtypes")
+	public static Callback createCallback(final String url) {
+
+		return new Callback() {
+			@Override
+			public void success(final Object o, final Response response) {
+				logger.info("URL : " + url + "\tStatus : "
+						+ response.getStatus());
+			}
+
+			@Override
+			public void failure(final RetrofitError retrofitError) {
+				logger.info(retrofitError.getMessage());
+			}
+		};
+	}
+
+	public static WebHookService createWebHookService(final String url) {
+
+		final OkHttpClient client = ProcessorHelper.createClient();
+
+		final RestAdapter restAdapter = new RestAdapter.Builder()
+				.setEndpoint(url).setClient(new OkClient(client)).build();
+
+		return restAdapter.create(WebHookService.class);
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/TwilioHookProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/TwilioHookProcessor.java
new file mode 100644
index 0000000..aa87e7c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/TwilioHookProcessor.java
@@ -0,0 +1,149 @@
+/**
+ * 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.hooks.processor;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import org.apache.fineract.infrastructure.hooks.domain.Hook;
+import org.apache.fineract.infrastructure.hooks.domain.HookConfiguration;
+import org.apache.fineract.infrastructure.hooks.domain.HookConfigurationRepository;
+import org.apache.fineract.infrastructure.hooks.processor.data.SmsProviderData;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepository;
+import org.apache.fineract.template.service.TemplateMergeService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import retrofit.Callback;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.apiKeyName;
+
+@Service
+public class TwilioHookProcessor implements HookProcessor {
+
+    private final HookConfigurationRepository hookConfigurationRepository;
+    private final TemplateMergeService templateMergeService;
+    private final ClientRepository clientRepository;
+
+    @Autowired
+    public TwilioHookProcessor(
+            final HookConfigurationRepository hookConfigurationRepository,
+            final TemplateMergeService templateMergeService,
+            final ClientRepository clientRepository) {
+        this.hookConfigurationRepository = hookConfigurationRepository;
+        this.templateMergeService = templateMergeService;
+        this.clientRepository = clientRepository;
+    }
+
+    @Override
+    public void process(final Hook hook,
+            @SuppressWarnings("unused") final AppUser appUser,
+            final String payload, final String entityName,
+            final String actionName, final String tenantIdentifier,
+            final String authToken) {
+
+        final SmsProviderData smsProviderData = new SmsProviderData(
+                hook.getHookConfig());
+
+        sendRequest(smsProviderData, payload, entityName, actionName,
+                tenantIdentifier, authToken, hook);
+    }
+
+    @SuppressWarnings("unchecked")
+    private void sendRequest(final SmsProviderData smsProviderData,
+            final String payload, String entityName, String actionName,
+            final String tenantIdentifier, final String authToken,
+            final Hook hook) {
+
+        final WebHookService service = ProcessorHelper
+                .createWebHookService(smsProviderData.getUrl());
+
+        @SuppressWarnings("rawtypes")
+        final Callback callback = ProcessorHelper
+                .createCallback(smsProviderData.getUrl());
+
+        String apiKey = this.hookConfigurationRepository
+                .findOneByHookIdAndFieldName(hook.getId(), apiKeyName);
+        if (apiKey == null) {
+            smsProviderData.setUrl(null);
+            smsProviderData.setEndpoint(System.getProperty("baseUrl"));
+            smsProviderData.setTenantId(tenantIdentifier);
+            smsProviderData.setMifosToken(authToken);
+            apiKey = service.sendSmsBridgeConfigRequest(smsProviderData);
+            final HookConfiguration apiKeyEntry = HookConfiguration.createNew(
+                    hook, "string", apiKeyName, apiKey);
+            this.hookConfigurationRepository.save(apiKeyEntry);
+        }
+
+        if (apiKey != null && !apiKey.equals("")) {
+            JsonObject json = null;
+            if (hook.getUgdTemplate() != null) {
+                entityName = "sms";
+                actionName = "send";
+                json = processUgdTemplate(payload, hook, authToken);
+                if (json == null) {
+                    return;
+                }
+            } else {
+                json = new JsonParser().parse(payload).getAsJsonObject();
+            }
+            service.sendSmsBridgeRequest(entityName, actionName,
+                    tenantIdentifier, apiKey, json, callback);
+        }
+
+    }
+
+    private JsonObject processUgdTemplate(final String payload,
+            final Hook hook, final String authToken) {
+        JsonObject json = null;
+        try {
+            @SuppressWarnings("unchecked")
+            final HashMap<String, Object> map = new ObjectMapper().readValue(
+                    payload, HashMap.class);
+            map.put("BASE_URI", System.getProperty("baseUrl"));
+            if (map.containsKey("clientId")) {
+                final Long clientId = new Long(Integer.toString((int) map
+                        .get("clientId")));
+                final Client client = this.clientRepository.findOne(clientId);
+                final String mobileNo = client.mobileNo();
+                if (mobileNo != null && !mobileNo.isEmpty()) {
+                    this.templateMergeService.setAuthToken(authToken);
+                    final String compiledMessage = this.templateMergeService
+                            .compile(hook.getUgdTemplate(), map)
+                            .replace("<p>", "").replace("</p>", "");
+                    final Map<String, String> jsonMap = new HashMap<>();
+                    jsonMap.put("mobileNo", mobileNo);
+                    jsonMap.put("message", compiledMessage);
+                    final String jsonString = new Gson().toJson(jsonMap);
+                    json = new JsonParser().parse(jsonString).getAsJsonObject();
+                }
+            }
+        } catch (IOException e) {
+        }
+        return json;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/WebHookProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/WebHookProcessor.java
new file mode 100644
index 0000000..d4aa4d9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/WebHookProcessor.java
@@ -0,0 +1,97 @@
+/**
+ * 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.hooks.processor;
+
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.contentTypeName;
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.payloadURLName;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.hooks.domain.Hook;
+import org.apache.fineract.infrastructure.hooks.domain.HookConfiguration;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.stereotype.Service;
+
+import retrofit.Callback;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+@Service
+public class WebHookProcessor implements HookProcessor {
+
+	@Override
+	public void process(final Hook hook,
+			@SuppressWarnings("unused") final AppUser appUser,
+			final String payload, final String entityName,
+			final String actionName, final String tenantIdentifier,
+			final String authToken) {
+
+		final Set<HookConfiguration> config = hook.getHookConfig();
+
+		String url = "";
+		String contentType = "";
+
+		for (final HookConfiguration conf : config) {
+			final String fieldName = conf.getFieldName();
+			if (fieldName.equals(payloadURLName)) {
+				url = conf.getFieldValue();
+			}
+			if (fieldName.equals(contentTypeName)) {
+				contentType = conf.getFieldValue();
+			}
+		}
+
+		sendRequest(url, contentType, payload, entityName, actionName,
+				tenantIdentifier, authToken);
+
+	}
+
+	@SuppressWarnings("unchecked")
+	private void sendRequest(final String url, final String contentType,
+			final String payload, final String entityName,
+			final String actionName, final String tenantIdentifier,
+			@SuppressWarnings("unused") final String authToken) {
+
+		final String fineractEndpointUrl = System.getProperty("baseUrl");
+		final WebHookService service = ProcessorHelper
+				.createWebHookService(url);
+
+		@SuppressWarnings("rawtypes")
+		final Callback callback = ProcessorHelper.createCallback(url);
+
+		if (contentType.equalsIgnoreCase("json")
+				|| contentType.contains("json")) {
+			final JsonObject json = new JsonParser().parse(payload)
+					.getAsJsonObject();
+			service.sendJsonRequest(entityName, actionName, tenantIdentifier,
+					fineractEndpointUrl, json, callback);
+		} else {
+			Map<String, String> map = new HashMap<>();
+			map = new Gson().fromJson(payload, map.getClass());
+			service.sendFormRequest(entityName, actionName, tenantIdentifier,
+					fineractEndpointUrl, map, callback);
+		}
+
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/WebHookService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/WebHookService.java
new file mode 100644
index 0000000..62de67b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/WebHookService.java
@@ -0,0 +1,75 @@
+/**
+ * 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.hooks.processor;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.hooks.processor.data.SmsProviderData;
+
+import retrofit.Callback;
+import retrofit.client.Response;
+import retrofit.http.Body;
+import retrofit.http.FieldMap;
+import retrofit.http.FormUrlEncoded;
+import retrofit.http.GET;
+import retrofit.http.Header;
+import retrofit.http.POST;
+
+import com.google.gson.JsonObject;
+
+public interface WebHookService {
+
+	final static String ENTITY_HEADER = "X-Fineract-Entity";
+	final static String ACTION_HEADER = "X-Fineract-Action";
+	final static String TENANT_HEADER = "Fineract-Platform-TenantId";
+	final static String ENDPOINT_HEADER = "X-Fineract-Endpoint";
+	final static String API_KEY_HEADER = "X-Fineract-API-Key";
+
+	// Ping
+	@GET("/")
+	Response sendEmptyRequest();
+
+	// Template - Web
+	@POST("/")
+	void sendJsonRequest(@Header(ENTITY_HEADER) String entityHeader,
+			@Header(ACTION_HEADER) String actionHeader,
+			@Header(TENANT_HEADER) String tenantHeader,
+			@Header(ENDPOINT_HEADER) String endpointHeader,
+			@Body JsonObject result, Callback<Response> callBack);
+
+	@FormUrlEncoded
+	@POST("/")
+	void sendFormRequest(@Header(ENTITY_HEADER) String entityHeader,
+			@Header(ACTION_HEADER) String actionHeader,
+			@Header(TENANT_HEADER) String tenantHeader,
+			@Header(ENDPOINT_HEADER) String endpointHeader,
+			@FieldMap Map<String, String> params, Callback<Response> callBack);
+
+	// Template - SMS Bridge
+	@POST("/")
+	void sendSmsBridgeRequest(@Header(ENTITY_HEADER) String entityHeader,
+			@Header(ACTION_HEADER) String actionHeader,
+			@Header(TENANT_HEADER) String tenantHeader,
+			@Header(API_KEY_HEADER) String apiKeyHeader,
+			@Body JsonObject result, Callback<Response> callBack);
+
+	@POST("/configuration")
+	String sendSmsBridgeConfigRequest(@Body SmsProviderData config);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/data/SmsProviderData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/data/SmsProviderData.java
new file mode 100644
index 0000000..6dca283
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/data/SmsProviderData.java
@@ -0,0 +1,112 @@
+/**
+ * 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.hooks.processor.data;
+
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.payloadURLName;
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.phoneNumberName;
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.smsProviderAccountIdName;
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.smsProviderName;
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.smsProviderTokenIdName;
+
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.hooks.domain.HookConfiguration;
+
+public class SmsProviderData {
+
+	private String url;
+	private String phoneNo;
+	private String smsProvider;
+	private String smsProviderAccountId;
+	private String smsProviderToken;
+	
+	private String tenantId;
+	private String mifosToken;
+	private String endpoint;
+	
+	public SmsProviderData(final Set<HookConfiguration> config) {
+		
+		for (final HookConfiguration conf : config) {
+			final String fieldName = conf.getFieldName();
+			if (fieldName.equals(payloadURLName)) {
+				this.url = conf.getFieldValue();
+			}
+			if (fieldName.equals(smsProviderName)) {
+				this.smsProvider = conf.getFieldValue();
+			}
+			if (fieldName.equals(smsProviderAccountIdName)) {
+				this.smsProviderAccountId = conf.getFieldValue();
+			}
+			if (fieldName.equals(smsProviderTokenIdName)) {
+				this.smsProviderToken = conf.getFieldValue();
+			}
+			if (fieldName.equals(phoneNumberName)) {
+				this.phoneNo = conf.getFieldValue();
+			}
+		}
+	}
+
+	public String getUrl() {
+		return url;
+	}
+	
+	public void setUrl(String url) {
+		this.url = url;
+	}
+
+	public String getPhoneNumber() {
+		return phoneNo;
+	}
+
+	public String getSmsProvider() {
+		return smsProvider;
+	}
+
+	public String getSmsProviderAccountId() {
+		return smsProviderAccountId;
+	}
+
+	public String getSmsProviderTokenId() {
+		return smsProviderToken;
+	}
+
+	public String getTenantId() {
+		return tenantId;
+	}
+
+	public void setTenantId(String tenantId) {
+		this.tenantId = tenantId;
+	}
+
+	public String getMifosToken() {
+		return mifosToken;
+	}
+
+	public void setMifosToken(String mifosToken) {
+		this.mifosToken = mifosToken;
+	}
+
+	public String getEndpoint() {
+		return endpoint;
+	}
+
+	public void setEndpoint(String endpoint) {
+		this.endpoint = endpoint;
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/serialization/HookCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/serialization/HookCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..db01568
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/serialization/HookCommandFromApiJsonDeserializer.java
@@ -0,0 +1,129 @@
+/**
+ * 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.hooks.serialization;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.hooks.api.HookApiConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Type;
+import java.util.*;
+
+@Component
+public class HookCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(
+            Arrays.asList("name", "displayName", "isActive", "events",
+                    "config", "templateId"));
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public HookCommandFromApiJsonDeserializer(
+            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("hook");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String name = this.fromApiJsonHelper.extractStringNamed("name",
+                element);
+        baseDataValidator.reset().parameter("name").value(name).notBlank()
+                .notExceedingLengthOf(100);
+
+        if (this.fromApiJsonHelper.parameterExists(
+                HookApiConstants.templateIdParamName, element)) {
+            final Long templateId = this.fromApiJsonHelper.extractLongNamed(
+                    HookApiConstants.templateIdParamName, element);
+            baseDataValidator.reset()
+                    .parameter(HookApiConstants.templateIdParamName)
+                    .value(templateId).notNull().integerGreaterThanZero();
+        }
+
+        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("hook");
+
+        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(
+                HookApiConstants.templateIdParamName, element)) {
+            final Long templateId = this.fromApiJsonHelper.extractLongNamed(
+                    HookApiConstants.templateIdParamName, element);
+            baseDataValidator.reset()
+                    .parameter(HookApiConstants.templateIdParamName)
+                    .value(templateId).notNull().integerGreaterThanZero();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(
+            final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(
+                    "validation.msg.validation.errors.exist",
+                    "Validation errors exist.", dataValidationErrors);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookReadPlatformService.java
new file mode 100644
index 0000000..9b1595d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookReadPlatformService.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.infrastructure.hooks.service;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.hooks.data.HookData;
+import org.apache.fineract.infrastructure.hooks.domain.Hook;
+
+public interface HookReadPlatformService {
+
+	Collection<HookData> retrieveAllHooks();
+
+	HookData retrieveHook(Long hookId);
+
+    List<Hook> retrieveHooksByEvent(final String actionName, final String entityName);
+
+    HookData retrieveNewHookDetails(String templateName);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookReadPlatformServiceImpl.java
new file mode 100644
index 0000000..e0157e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookReadPlatformServiceImpl.java
@@ -0,0 +1,270 @@
+/**
+ * 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.hooks.service;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.hooks.data.*;
+import org.apache.fineract.infrastructure.hooks.domain.Hook;
+import org.apache.fineract.infrastructure.hooks.domain.HookRepository;
+import org.apache.fineract.infrastructure.hooks.exception.HookNotFoundException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+
+@Service
+public class HookReadPlatformServiceImpl implements HookReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final HookRepository hookRepository;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public HookReadPlatformServiceImpl(final PlatformSecurityContext context,
+            final HookRepository hookRepository,
+            final RoutingDataSource dataSource) {
+        this.context = context;
+        this.hookRepository = hookRepository;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public Collection<HookData> retrieveAllHooks() {
+        this.context.authenticatedUser();
+        final HookMapper rm = new HookMapper(this.jdbcTemplate);
+        final String sql = "select " + rm.schema() + " order by h.name";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[]{});
+    }
+
+    @Override
+    public HookData retrieveHook(final Long hookId) {
+        try {
+            this.context.authenticatedUser();
+            final HookMapper rm = new HookMapper(this.jdbcTemplate);
+            final String sql = "select " + rm.schema() + " where h.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, rm,
+                    new Object[]{hookId});
+        } catch (final EmptyResultDataAccessException e) {
+            throw new HookNotFoundException(hookId);
+        }
+
+    }
+
+    @Override
+    @Cacheable(value = "hooks", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('HK')")
+    public List<Hook> retrieveHooksByEvent(final String actionName,
+            final String entityName) {
+
+        return this.hookRepository.findAllHooksListeningToEvent(actionName,
+                entityName);
+    }
+
+    @Override
+    public HookData retrieveNewHookDetails(final String templateName) {
+
+        this.context.authenticatedUser();
+        final TemplateMapper rm = new TemplateMapper(this.jdbcTemplate);
+        final String sql;
+        List<HookTemplateData> templateData;
+
+        if (templateName == null) {
+            sql = "select " + rm.schema() + " order by s.name";
+            templateData = this.jdbcTemplate.query(sql, rm, new Object[]{});
+        } else {
+            sql = "select " + rm.schema() + " where s.name = ? order by s.name";
+            templateData = this.jdbcTemplate.query(sql, rm,
+                    new Object[]{templateName});
+        }
+
+        final List<Grouping> events = getTemplateForEvents();
+
+        return HookData.template(templateData, events);
+    }
+
+    private List<Grouping> getTemplateForEvents() {
+        final String sql = "select p.grouping, p.entity_name, p.action_name from m_permission p "
+                + " where p.action_name NOT LIKE '%CHECKER%' AND p.action_name NOT LIKE '%READ%' "
+                + " order by p.grouping, p.entity_name ";
+        final EventResultSetExtractor extractor = new EventResultSetExtractor();
+        return this.jdbcTemplate.query(sql, extractor);
+    }
+
+    private static final class HookMapper implements RowMapper<HookData> {
+
+        private final JdbcTemplate jdbcTemplate;
+
+        public HookMapper(final JdbcTemplate jdbcTemplate) {
+            this.jdbcTemplate = jdbcTemplate;
+        }
+
+        public String schema() {
+            return " h.id, s.name as name, h.name as display_name, h.is_active, h.created_date,"
+                    + " h.lastmodified_date, h.ugd_template_id, tp.name as ugd_template_name, "
+                    + "h.ugd_template_id from m_hook h left join m_hook_templates s on h.template_id = s.id"
+                    + " left join m_template tp on h.ugd_template_id = tp.id";
+        }
+
+        @Override
+        public HookData mapRow(final ResultSet rs,
+                @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final String displayname = rs.getString("display_name");
+            final boolean isActive = rs.getBoolean("is_active");
+            final LocalDate createdAt = JdbcSupport.getLocalDate(rs,
+                    "created_date");
+            final LocalDate updatedAt = JdbcSupport.getLocalDate(rs,
+                    "lastmodified_date");
+            final Long templateId = rs.getLong("ugd_template_id");
+            final String templateName = rs.getString("ugd_template_name");
+            final List<Event> registeredEvents = retrieveEvents(id);
+            final List<Field> config = retrieveConfig(id);
+
+            return HookData.instance(id, name, displayname, isActive,
+                    createdAt, updatedAt, templateId, registeredEvents, config,
+                    templateName);
+        }
+
+        private List<Event> retrieveEvents(final Long hookId) {
+
+            final HookEventMapper rm = new HookEventMapper();
+            final String sql = "select " + rm.schema() + " where h.id= ?";
+
+            return this.jdbcTemplate.query(sql, rm, new Object[]{hookId});
+        }
+
+        private List<Field> retrieveConfig(final Long hookId) {
+
+            final HookConfigMapper rm = new HookConfigMapper();
+            final String sql = "select " + rm.schema()
+                    + " where h.id= ? order by hc.field_name";
+
+            final List<Field> fields = this.jdbcTemplate.query(sql, rm,
+                    new Object[]{hookId});
+
+            return fields;
+        }
+    }
+
+    private static final class HookEventMapper implements RowMapper<Event> {
+
+        public String schema() {
+            return " re.action_name, re.entity_name from m_hook h inner join m_hook_registered_events re on h.id = re.hook_id ";
+        }
+
+        @Override
+        public Event mapRow(final ResultSet rs,
+                @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+            final String actionName = rs.getString("action_name");
+            final String entityName = rs.getString("entity_name");
+            return Event.instance(actionName, entityName);
+        }
+    }
+
+    private static final class HookConfigMapper implements RowMapper<Field> {
+
+        public String schema() {
+            return " hc.field_name, hc.field_value from m_hook h inner join m_hook_configuration hc on h.id = hc.hook_id ";
+        }
+
+        @Override
+        public Field mapRow(final ResultSet rs,
+                @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+            final String fieldName = rs.getString("field_name");
+            final String fieldValue = rs.getString("field_value");
+            return Field.fromConfig(fieldName, fieldValue);
+        }
+    }
+
+    private static final class TemplateMapper
+            implements
+                RowMapper<HookTemplateData> {
+
+        private final JdbcTemplate jdbcTemplate;
+
+        public TemplateMapper(final JdbcTemplate jdbcTemplate) {
+            this.jdbcTemplate = jdbcTemplate;
+        }
+
+        public String schema() {
+            return " s.id, s.name from m_hook_templates s ";
+        }
+
+        @Override
+        public HookTemplateData mapRow(final ResultSet rs,
+                @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final List<Field> schema = retrieveSchema(id);
+
+            return HookTemplateData.instance(id, name, schema);
+        }
+
+        private List<Field> retrieveSchema(final Long templateId) {
+
+            final TemplateSchemaMapper rm = new TemplateSchemaMapper();
+            final String sql = "select " + rm.schema()
+                    + " where s.id= ? order by hs.field_name ";
+
+            final List<Field> fields = this.jdbcTemplate.query(sql, rm,
+                    new Object[]{templateId});
+
+            return fields;
+        }
+    }
+
+    private static final class TemplateSchemaMapper implements RowMapper<Field> {
+
+        public String schema() {
+            return " hs.field_type, hs.field_name, hs.placeholder, hs.optional from m_hook_templates s "
+                    + " inner join m_hook_schema hs on s.id = hs.hook_template_id ";
+        }
+
+        @Override
+        public Field mapRow(final ResultSet rs,
+                @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+            final String fieldName = rs.getString("field_name");
+            final String fieldType = rs.getString("field_type");
+            final Boolean optional = rs.getBoolean("optional");
+            final String placeholder = rs.getString("placeholder");
+            return Field
+                    .fromSchema(fieldType, fieldName, optional, placeholder);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookWritePlatformService.java
new file mode 100644
index 0000000..9cff741
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookWritePlatformService.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.infrastructure.hooks.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface HookWritePlatformService {
+
+	CommandProcessingResult createHook(JsonCommand command);
+
+	CommandProcessingResult updateHook(Long hookId, JsonCommand command);
+
+	CommandProcessingResult deleteHook(Long hookId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..1696512
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/service/HookWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,366 @@
+/**
+ * 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.hooks.service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+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.hooks.domain.*;
+import org.apache.fineract.infrastructure.hooks.exception.HookNotFoundException;
+import org.apache.fineract.infrastructure.hooks.exception.HookTemplateNotFoundException;
+import org.apache.fineract.infrastructure.hooks.processor.ProcessorHelper;
+import org.apache.fineract.infrastructure.hooks.processor.WebHookService;
+import org.apache.fineract.infrastructure.hooks.serialization.HookCommandFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.template.domain.Template;
+import org.apache.fineract.template.domain.TemplateRepository;
+import org.apache.fineract.template.exception.TemplateNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import retrofit.RetrofitError;
+
+import java.util.*;
+import java.util.Map.Entry;
+
+import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.*;
+
+@Service
+public class HookWritePlatformServiceJpaRepositoryImpl
+        implements
+            HookWritePlatformService {
+
+    private final PlatformSecurityContext context;
+    private final HookRepository hookRepository;
+    private final HookTemplateRepository hookTemplateRepository;
+    private final TemplateRepository ugdTemplateRepository;
+    private final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public HookWritePlatformServiceJpaRepositoryImpl(
+            final PlatformSecurityContext context,
+            final HookRepository hookRepository,
+            final HookTemplateRepository hookTemplateRepository,
+            final TemplateRepository ugdTemplateRepository,
+            final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final FromJsonHelper fromApiJsonHelper) {
+        this.context = context;
+        this.hookRepository = hookRepository;
+        this.hookTemplateRepository = hookTemplateRepository;
+        this.ugdTemplateRepository = ugdTemplateRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "hooks", allEntries = true)
+    public CommandProcessingResult createHook(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final HookTemplate template = retrieveHookTemplateBy(command
+                    .stringValueOfParameterNamed(nameParamName));
+            final String configJson = command.jsonFragment(configParamName);
+            final Set<HookConfiguration> config = assembleConfig(
+                    command.mapValueOfParameterNamed(configJson), template);
+            final JsonArray events = command
+                    .arrayOfParameterNamed(eventsParamName);
+            final Set<HookResource> allEvents = assembleSetOfEvents(events);
+            Template ugdTemplate = null;
+            if (command.hasParameter(templateIdParamName)) {
+                final Long ugdTemplateId = command
+                        .longValueOfParameterNamed(templateIdParamName);
+                ugdTemplate = this.ugdTemplateRepository.findOne(ugdTemplateId);
+                if (ugdTemplate == null) {
+                    throw new TemplateNotFoundException(ugdTemplateId);
+                }
+            }
+            final Hook hook = Hook.fromJson(command, template, config,
+                    allEvents, ugdTemplate);
+
+            validateHookRules(template, config, allEvents);
+
+            this.hookRepository.save(hook);
+
+            return new CommandProcessingResultBuilder()
+                    .withCommandId(command.commandId())
+                    .withEntityId(hook.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleHookDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "hooks", allEntries = true)
+    public CommandProcessingResult updateHook(final Long hookId,
+            final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final Hook hook = retrieveHookBy(hookId);
+            final HookTemplate template = hook.getHookTemplate();
+            final Map<String, Object> changes = hook.update(command);
+
+            if (!changes.isEmpty()) {
+
+                if (changes.containsKey(templateIdParamName)) {
+                    final Long ugdTemplateId = command
+                            .longValueOfParameterNamed(templateIdParamName);
+                    final Template ugdTemplate = this.ugdTemplateRepository
+                            .findOne(ugdTemplateId);
+                    if (ugdTemplate == null) {
+                        changes.remove(templateIdParamName);
+                        throw new TemplateNotFoundException(ugdTemplateId);
+                    }
+                    hook.updateUgdTemplate(ugdTemplate);
+                }
+
+                if (changes.containsKey(eventsParamName)) {
+                    final Set<HookResource> events = assembleSetOfEvents(command
+                            .arrayOfParameterNamed(eventsParamName));
+                    final boolean updated = hook.updateEvents(events);
+                    if (!updated) {
+                        changes.remove(eventsParamName);
+                    }
+                }
+
+                if (changes.containsKey(configParamName)) {
+                    final String configJson = command
+                            .jsonFragment(configParamName);
+                    final Set<HookConfiguration> config = assembleConfig(
+                            command.mapValueOfParameterNamed(configJson),
+                            template);
+                    final boolean updated = hook.updateConfig(config);
+                    if (!updated) {
+                        changes.remove(configParamName);
+                    }
+                }
+
+                this.hookRepository.saveAndFlush(hook);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(hookId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleHookDataIntegrityIssues(command, dve);
+            return null;
+        }
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "hooks", allEntries = true)
+    public CommandProcessingResult deleteHook(final Long hookId) {
+
+        this.context.authenticatedUser();
+
+        final Hook hook = retrieveHookBy(hookId);
+
+        try {
+            this.hookRepository.delete(hook);
+            this.hookRepository.flush();
+        } catch (final DataIntegrityViolationException e) {
+            throw new PlatformDataIntegrityException(
+                    "error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource: "
+                            + e.getMostSpecificCause());
+        }
+        return new CommandProcessingResultBuilder().withEntityId(hookId)
+                .build();
+    }
+
+    private Hook retrieveHookBy(final Long hookId) {
+        final Hook hook = this.hookRepository.findOne(hookId);
+        if (hook == null) {
+            throw new HookNotFoundException(hookId);
+        }
+        return hook;
+    }
+
+    private HookTemplate retrieveHookTemplateBy(final String templateName) {
+        final HookTemplate template = this.hookTemplateRepository
+                .findOne(templateName);
+        if (template == null) {
+            throw new HookTemplateNotFoundException(templateName);
+        }
+        return template;
+    }
+
+    private Set<HookConfiguration> assembleConfig(
+            final Map<String, String> hookConfig, final HookTemplate template) {
+
+        final Set<HookConfiguration> configuration = new HashSet<>();
+        final Set<Schema> fields = template.getSchema();
+
+        for (final Entry<String, String> configEntry : hookConfig.entrySet()) {
+            for (final Schema field : fields) {
+                final String fieldName = field.getFieldName();
+                if (fieldName.equalsIgnoreCase(configEntry.getKey())) {
+
+                    final HookConfiguration config = HookConfiguration
+                            .createNewWithoutHook(field.getFieldType(),
+                                    configEntry.getKey(),
+                                    configEntry.getValue());
+                    configuration.add(config);
+                    break;
+                }
+            }
+
+        }
+
+        return configuration;
+    }
+
+    private Set<HookResource> assembleSetOfEvents(final JsonArray eventsArray) {
+
+        final Set<HookResource> allEvents = new HashSet<>();
+
+        for (int i = 0; i < eventsArray.size(); i++) {
+
+            final JsonObject eventElement = eventsArray.get(i)
+                    .getAsJsonObject();
+
+            final String entityName = this.fromApiJsonHelper
+                    .extractStringNamed(entityNameParamName, eventElement);
+            final String actionName = this.fromApiJsonHelper
+                    .extractStringNamed(actionNameParamName, eventElement);
+            final HookResource event = HookResource.createNewWithoutHook(
+                    entityName, actionName);
+            allEvents.add(event);
+        }
+
+        return allEvents;
+    }
+
+    private void validateHookRules(final HookTemplate template,
+            final Set<HookConfiguration> config, Set<HookResource> events) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
+                dataValidationErrors).resource("hook");
+
+        if (!template.getName().equalsIgnoreCase(webTemplateName)
+                && this.hookRepository.findOneByTemplateId(template.getId()) != null) {
+            final String errorMessage = "multiple.non.web.template.hooks.not.supported";
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                    errorMessage);
+        }
+
+        for (final HookConfiguration conf : config) {
+            final String fieldValue = conf.getFieldValue();
+            if (conf.getFieldName().equals(contentTypeName)) {
+                if (!(fieldValue.equalsIgnoreCase("json") || fieldValue
+                        .equalsIgnoreCase("form"))) {
+                    final String errorMessage = "content.type.must.be.json.or.form";
+                    baseDataValidator.reset()
+                            .failWithCodeNoParameterAddedToErrorCode(
+                                    errorMessage);
+                }
+            }
+
+            if (conf.getFieldName().equals(payloadURLName)) {
+                try {
+                    final WebHookService service = ProcessorHelper
+                            .createWebHookService(fieldValue);
+                    service.sendEmptyRequest();
+                } catch (RetrofitError re) {
+                    // Swallow error if it's because of method not supported or
+                    // if url throws 404 - required for integration test,
+                    // url generated on 1st POST request
+                    if (re.getResponse() == null) {
+                        String errorMessage = "url.invalid";
+                        baseDataValidator.reset()
+                                .failWithCodeNoParameterAddedToErrorCode(
+                                        errorMessage);
+                    }
+                }
+            }
+        }
+
+        if (events == null || events.isEmpty()) {
+            final String errorMessage = "registered.events.cannot.be.empty";
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                    errorMessage);
+        }
+
+        final Set<Schema> fields = template.getSchema();
+        for (final Schema field : fields) {
+            if (!field.isOptional()) {
+                boolean found = false;
+                for (final HookConfiguration conf : config) {
+                    if (field.getFieldName().equals(conf.getFieldName())) {
+                        found = true;
+                    }
+                }
+                if (!found) {
+                    final String errorMessage = "required.config.field."
+                            + "not.provided";
+                    baseDataValidator
+                            .reset()
+                            .value(field.getFieldName())
+                            .failWithCodeNoParameterAddedToErrorCode(
+                                    errorMessage);
+                }
+            }
+        }
+
+        if (!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    private void handleHookDataIntegrityIssues(final JsonCommand command,
+            final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("hook_name")) {
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException(
+                    "error.msg.hook.duplicate.name", "A hook with name '"
+                            + name + "' already exists", "name", name);
+        }
+
+        throw new PlatformDataIntegrityException(
+                "error.msg.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: "
+                        + realCause.getMessage());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/annotation/CronMethodParser.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/annotation/CronMethodParser.java
new file mode 100644
index 0000000..f788fc8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/annotation/CronMethodParser.java
@@ -0,0 +1,98 @@
+/**
+ * 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.jobs.annotation;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.springframework.core.env.StandardEnvironment;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.MethodMetadata;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Parser to find method which is marked with CronTargetMethod annotation
+ */
+public class CronMethodParser {
+
+    public static class ClassMethodNamesPair {
+
+        public String className;
+        public String methodName;
+    }
+
+    private static final String SEARCH_PACKAGE = "org.apache.fineract.";
+
+    private static final String CRON_ANNOTATION_ATTRIBUTE_NAME = "jobName";
+
+    private static final String RESOURCE_PATTERN = "**/*.class";
+
+    private static final Map<String, ClassMethodNamesPair> targetMethosMap = new HashMap<>();
+
+    private static final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+
+    private static final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
+
+    public static ClassMethodNamesPair findTargetMethodDetails(final String attributeValue) throws IOException {
+        if (!targetMethosMap.containsKey(attributeValue)) {
+            findAnnotationMethods(CronTarget.class, CRON_ANNOTATION_ATTRIBUTE_NAME);
+        }
+        return targetMethosMap.get(attributeValue);
+    }
+
+    /**
+     * method adds all the method names to map with annotation attribute value
+     * as key
+     */
+    private static void findAnnotationMethods(final Class<? extends Annotation> annotationClass, final String attributeName)
+            throws IOException {
+        final String basePackagePath = ClassUtils.convertClassNameToResourcePath(new StandardEnvironment()
+                .resolveRequiredPlaceholders(SEARCH_PACKAGE));
+        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + basePackagePath + "/" + RESOURCE_PATTERN;
+        packageSearchPath = packageSearchPath.replace("//", "/"); // else it doesn't work if *.class are in WAR!!
+        final Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
+        for (final Resource resource : resources) {
+            if (resource.isReadable()) {
+                final MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
+                final Set<MethodMetadata> metadataSet = metadataReader.getAnnotationMetadata().getAnnotatedMethods(
+                        annotationClass.getName());
+                if (metadataSet != null && metadataSet.size() > 0) {
+                    for (final MethodMetadata metadata : metadataSet) {
+                        final Map<String, Object> attributes = metadata.getAnnotationAttributes(annotationClass.getName());
+                        final JobName attributeValue = (JobName) attributes.get(attributeName);
+                        final String className = metadata.getDeclaringClassName();
+                        final ClassMethodNamesPair pair = new ClassMethodNamesPair();
+                        pair.className = className;
+                        pair.methodName = metadata.getMethodName();
+                        targetMethosMap.put(attributeValue.toString(), pair);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/annotation/CronTarget.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/annotation/CronTarget.java
new file mode 100644
index 0000000..efc4468
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/annotation/CronTarget.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.jobs.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+
+/**
+ * Annotation that marks a method to be picked while scheduling a cron jobs.
+ * 
+ */
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CronTarget {
+
+    JobName jobName();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerApiResource.java
new file mode 100755
index 0000000..fe74307
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerApiResource.java
@@ -0,0 +1,100 @@
+/**
+ * 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.jobs.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.jobs.data.SchedulerDetailData;
+import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
+import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Path("/scheduler")
+@Component
+public class SchedulerApiResource {
+
+    private final PlatformSecurityContext context;
+    private final JobRegisterService jobRegisterService;
+    private final ToApiJsonSerializer<SchedulerDetailData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public SchedulerApiResource(final PlatformSecurityContext context, final JobRegisterService jobRegisterService,
+            final ToApiJsonSerializer<SchedulerDetailData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper) {
+        this.context = context;
+        this.jobRegisterService = jobRegisterService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveStatus(@Context final UriInfo uriInfo) {
+        this.context.authenticatedUser().validateHasReadPermission(SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME);
+        final boolean isSchedulerRunning = this.jobRegisterService.isSchedulerRunning();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        final SchedulerDetailData schedulerDetailData = new SchedulerDetailData(isSchedulerRunning);
+        return this.toApiJsonSerializer.serialize(settings, schedulerDetailData,
+                SchedulerJobApiConstants.SCHEDULER_DETAIL_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public Response changeSchedulerStatus(@QueryParam(SchedulerJobApiConstants.COMMAND) final String commandParam) {
+        // check the logged in user have permissions to update scheduler status
+        final boolean hasNotPermission = this.context.authenticatedUser().hasNotPermissionForAnyOf("ALL_FUNCTIONS", "UPDATE_SCHEDULER");
+        if (hasNotPermission) {
+            final String authorizationMessage = "User has no authority to update scheduler status";
+            throw new NoAuthorizationException(authorizationMessage);
+        }
+        Response response = Response.status(400).build();
+        if (is(commandParam, SchedulerJobApiConstants.COMMAND_START_SCHEDULER)) {
+            this.jobRegisterService.startScheduler();
+            response = Response.status(202).build();
+        } else if (is(commandParam, SchedulerJobApiConstants.COMMAND_STOP_SCHEDULER)) {
+            this.jobRegisterService.pauseScheduler();
+            response = Response.status(202).build();
+        } else {
+            throw new UnrecognizedQueryParamException(SchedulerJobApiConstants.COMMAND, commandParam);
+        }
+        return response;
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiConstants.java
new file mode 100755
index 0000000..e6683b5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiConstants.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.infrastructure.jobs.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SchedulerJobApiConstants {
+
+    public static final String JOB_RESOURCE_NAME = "schedulerjob";
+    public static final String SCHEDULER_RESOURCE_NAME = "SCHEDULER";
+    // response parameters
+    public static final String jobIdentifierParamName = "jobId";
+    public static final String displayNameParamName = "displayName";
+    public static final String nextRunTimeParamName = "nextRunTime";
+    public static final String initializingErrorParamName = "initializingError";
+    public static final String jobActiveStatusParamName = "active";
+    public static final String currentlyRunningParamName = "currentlyRunning";
+    public static final String lastRunHistoryObjParamName = "lastRunHistory";
+
+    public static final String versionParamName = "version";
+    public static final String jobRunStartTimeParamName = "jobRunStartTime";
+    public static final String jobRunEndTimeParamName = "jobRunEndTime";
+    public static final String statusParamName = "status";
+    public static final String jobRunErrorMessageParamName = "jobRunErrorMessage";
+    public static final String triggerTypeParamName = "triggerType";
+    public static final String jobRunErrorLogParamName = "jobRunErrorLog";
+    public static final String cronExpressionParamName = "cronExpression";
+    public static final String schedulerStatusParamName = "active";
+
+    public static final Set<String> JOB_DETAIL_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(jobIdentifierParamName,
+            displayNameParamName, nextRunTimeParamName, initializingErrorParamName, cronExpressionParamName, jobActiveStatusParamName,
+            currentlyRunningParamName, lastRunHistoryObjParamName));
+
+    public static final Set<String> JOB_HISTORY_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(versionParamName,
+            jobRunStartTimeParamName, jobRunEndTimeParamName, statusParamName, jobRunErrorMessageParamName, triggerTypeParamName,
+            jobRunErrorLogParamName));
+
+    public static final Set<String> JOB_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(displayNameParamName,
+            jobActiveStatusParamName, cronExpressionParamName));
+
+    public static final Set<String> SCHEDULER_DETAIL_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(schedulerStatusParamName));
+
+    public static final String COMMAND_EXECUTE_JOB = "executeJob";
+    public static final String COMMAND_STOP_SCHEDULER = "stop";
+    public static final String COMMAND_START_SCHEDULER = "start";
+    public static final String COMMAND = "command";
+    public static final String JOB_ID = "jobId";
+    public static final String JOB_RUN_HISTORY = "runhistory";
+    public static final String SCHEDULER_STATUS_PATH = "scheduler";
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java
new file mode 100755
index 0000000..61faf10
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java
@@ -0,0 +1,157 @@
+/**
+ * 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.jobs.api;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.jobs.data.JobDetailData;
+import org.apache.fineract.infrastructure.jobs.data.JobDetailHistoryData;
+import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
+import org.apache.fineract.infrastructure.jobs.service.SchedulerJobRunnerReadService;
+import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Path("/jobs")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+public class SchedulerJobApiResource {
+
+    private final SchedulerJobRunnerReadService schedulerJobRunnerReadService;
+    private final JobRegisterService jobRegisterService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final ToApiJsonSerializer<JobDetailData> toApiJsonSerializer;
+    private final ToApiJsonSerializer<JobDetailHistoryData> jobHistoryToApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public SchedulerJobApiResource(final SchedulerJobRunnerReadService schedulerJobRunnerReadService,
+            final JobRegisterService jobRegisterService, final ToApiJsonSerializer<JobDetailData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final ToApiJsonSerializer<JobDetailHistoryData> jobHistoryToApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final PlatformSecurityContext context) {
+        this.schedulerJobRunnerReadService = schedulerJobRunnerReadService;
+        this.jobRegisterService = jobRegisterService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.jobHistoryToApiJsonSerializer = jobHistoryToApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.context = context;
+    }
+
+    @GET
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+        this.context.authenticatedUser().validateHasReadPermission(SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME);
+        final List<JobDetailData> jobDetailDatas = this.schedulerJobRunnerReadService.findAllJobDeatils();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, jobDetailDatas, SchedulerJobApiConstants.JOB_DETAIL_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
+    public String retrieveOne(@PathParam(SchedulerJobApiConstants.JOB_ID) final Long jobId, @Context final UriInfo uriInfo) {
+        this.context.authenticatedUser().validateHasReadPermission(SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME);
+        final JobDetailData jobDetailData = this.schedulerJobRunnerReadService.retrieveOne(jobId);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, jobDetailData, SchedulerJobApiConstants.JOB_DETAIL_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{" + SchedulerJobApiConstants.JOB_ID + "}/" + SchedulerJobApiConstants.JOB_RUN_HISTORY)
+    public String retrieveHistory(@Context final UriInfo uriInfo, @PathParam(SchedulerJobApiConstants.JOB_ID) final Long jobId,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
+        this.context.authenticatedUser().validateHasReadPermission(SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME);
+        final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder);
+        final Page<JobDetailHistoryData> jobhistoryDetailData = this.schedulerJobRunnerReadService.retrieveJobHistory(jobId,
+                searchParameters);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.jobHistoryToApiJsonSerializer.serialize(settings, jobhistoryDetailData,
+                SchedulerJobApiConstants.JOB_HISTORY_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
+    public Response executeJob(@PathParam(SchedulerJobApiConstants.JOB_ID) final Long jobId,
+            @QueryParam(SchedulerJobApiConstants.COMMAND) final String commandParam) {
+        // check the logged in user have permissions to execute scheduler jobs
+        final boolean hasNotPermission = this.context.authenticatedUser().hasNotPermissionForAnyOf("ALL_FUNCTIONS", "EXECUTEJOB_SCHEDULER");
+        if (hasNotPermission) {
+            final String authorizationMessage = "User has no authority to execute scheduler jobs";
+            throw new NoAuthorizationException(authorizationMessage);
+        }
+        Response response = Response.status(400).build();
+        if (is(commandParam, SchedulerJobApiConstants.COMMAND_EXECUTE_JOB)) {
+            this.jobRegisterService.executeJob(jobId);
+            response = Response.status(202).build();
+        } else {
+            throw new UnrecognizedQueryParamException(SchedulerJobApiConstants.COMMAND, commandParam);
+        }
+        return response;
+    }
+
+    @PUT
+    @Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
+    public String updateJobDetail(@PathParam(SchedulerJobApiConstants.JOB_ID) final Long jobId, final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateJobDetail(jobId) //
+                .withJson(jsonRequestBody) //
+                .build(); //
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        if (result.getChanges() != null
+                && (result.getChanges().containsKey(SchedulerJobApiConstants.jobActiveStatusParamName) || result.getChanges().containsKey(
+                        SchedulerJobApiConstants.cronExpressionParamName))) {
+            this.jobRegisterService.rescheduleJob(jobId);
+        }
+        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/infrastructure/jobs/data/JobDetailData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailData.java
new file mode 100755
index 0000000..90eae1a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailData.java
@@ -0,0 +1,60 @@
+/**
+ * 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.jobs.data;
+
+import java.util.Date;
+
+public class JobDetailData {
+
+    @SuppressWarnings("unused")
+    private final Long jobId;
+
+    @SuppressWarnings("unused")
+    private final String displayName;
+
+    @SuppressWarnings("unused")
+    private final Date nextRunTime;
+
+    @SuppressWarnings("unused")
+    private final String initializingError;
+
+    @SuppressWarnings("unused")
+    private final String cronExpression;
+
+    @SuppressWarnings("unused")
+    private final boolean active;
+
+    @SuppressWarnings("unused")
+    private final boolean currentlyRunning;
+
+    @SuppressWarnings("unused")
+    private final JobDetailHistoryData lastRunHistory;
+
+    public JobDetailData(final Long jobId, final String displayName, final Date nextRunTime, final String initializingError,
+            final String cronExpression, final boolean active, final boolean currentlyRunning, final JobDetailHistoryData lastRunHistory) {
+        this.jobId = jobId;
+        this.displayName = displayName;
+        this.nextRunTime = nextRunTime;
+        this.initializingError = initializingError;
+        this.cronExpression = cronExpression;
+        this.active = active;
+        this.lastRunHistory = lastRunHistory;
+        this.currentlyRunning = currentlyRunning;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailDataValidator.java
new file mode 100755
index 0000000..bc58c7c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailDataValidator.java
@@ -0,0 +1,94 @@
+/**
+ * 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.jobs.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.jobs.api.SchedulerJobApiConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class JobDetailDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public JobDetailDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        boolean atLeastOneParameterPassedForUpdate = false;
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SchedulerJobApiConstants.JOB_UPDATE_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SchedulerJobApiConstants.JOB_RESOURCE_NAME);
+        if (this.fromApiJsonHelper.parameterExists(SchedulerJobApiConstants.displayNameParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String displayName = this.fromApiJsonHelper.extractStringNamed(SchedulerJobApiConstants.displayNameParamName, element);
+            baseDataValidator.reset().parameter(SchedulerJobApiConstants.displayNameParamName).value(displayName).notBlank();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(SchedulerJobApiConstants.cronExpressionParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String cronExpression = this.fromApiJsonHelper.extractStringNamed(SchedulerJobApiConstants.cronExpressionParamName,
+                    element);
+            baseDataValidator.reset().parameter(SchedulerJobApiConstants.cronExpressionParamName).value(cronExpression).notBlank()
+                    .validateCronExpression();
+        }
+        if (this.fromApiJsonHelper.parameterExists(SchedulerJobApiConstants.jobActiveStatusParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String status = this.fromApiJsonHelper.extractStringNamed(SchedulerJobApiConstants.jobActiveStatusParamName, element);
+            baseDataValidator.reset().parameter(SchedulerJobApiConstants.jobActiveStatusParamName).value(status).notBlank()
+                    .validateForBooleanValue();
+        }
+
+        if (!atLeastOneParameterPassedForUpdate) {
+            final Object forceError = null;
+            baseDataValidator.reset().anyOfNotNull(forceError);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailHistoryData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailHistoryData.java
new file mode 100755
index 0000000..99a1fe2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailHistoryData.java
@@ -0,0 +1,56 @@
+/**
+ * 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.jobs.data;
+
+import java.util.Date;
+
+public class JobDetailHistoryData {
+
+    @SuppressWarnings("unused")
+    private final Long version;
+
+    @SuppressWarnings("unused")
+    private final Date jobRunStartTime;
+
+    @SuppressWarnings("unused")
+    private final Date jobRunEndTime;
+
+    @SuppressWarnings("unused")
+    private final String status;
+
+    @SuppressWarnings("unused")
+    private final String jobRunErrorMessage;
+
+    @SuppressWarnings("unused")
+    private final String triggerType;
+
+    @SuppressWarnings("unused")
+    private final String jobRunErrorLog;
+
+    public JobDetailHistoryData(final Long version, final Date jobRunStartTime, final Date jobRunEndTime, final String status,
+            final String jobRunErrorMessage, final String triggerType, final String jobRunErrorLog) {
+        this.version = version;
+        this.jobRunStartTime = jobRunStartTime;
+        this.jobRunEndTime = jobRunEndTime;
+        this.status = status;
+        this.jobRunErrorMessage = jobRunErrorMessage;
+        this.triggerType = triggerType;
+        this.jobRunErrorLog = jobRunErrorLog;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/SchedulerDetailData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/SchedulerDetailData.java
new file mode 100755
index 0000000..e624b6f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/SchedulerDetailData.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.infrastructure.jobs.data;
+
+public class SchedulerDetailData {
+
+    @SuppressWarnings("unused")
+    private final boolean active;
+
+    public SchedulerDetailData(final boolean active) {
+        this.active = active;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetail.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetail.java
new file mode 100644
index 0000000..659c3db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetail.java
@@ -0,0 +1,183 @@
+/**
+ * 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.jobs.domain;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.jobs.api.SchedulerJobApiConstants;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "job")
+public class ScheduledJobDetail extends AbstractPersistable<Long> {
+
+    @Column(name = "name")
+    private String jobName;
+
+    @Column(name = "display_name")
+    private String jobDisplayName;
+
+    @Column(name = "cron_expression")
+    private String cronExpression;
+
+    @Column(name = "create_time")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date createTime;
+
+    @Column(name = "task_priority")
+    private Short taskPriority;
+
+    @Column(name = "group_name")
+    private String groupName;
+
+    @Column(name = "previous_run_start_time")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date previousRunStartTime;
+
+    @Column(name = "next_run_time")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date nextRunTime;
+
+    @Column(name = "job_key")
+    private String jobKey;
+
+    @Column(name = "initializing_errorlog")
+    private String errorLog;
+
+    @Column(name = "is_active")
+    private boolean activeSchedular;
+
+    @Column(name = "currently_running")
+    private boolean currentlyRunning;
+
+    @Column(name = "updates_allowed")
+    private boolean updatesAllowed;
+
+    @Column(name = "scheduler_group")
+    private Short schedulerGroup;
+
+    @Column(name = "is_misfired")
+    private boolean triggerMisfired;
+
+    protected ScheduledJobDetail() {
+
+    }
+
+    public String getJobName() {
+        return this.jobName;
+    }
+
+    public String getCronExpression() {
+        return this.cronExpression;
+    }
+
+    public Short getTaskPriority() {
+        return this.taskPriority;
+    }
+
+    public String getGroupName() {
+        return this.groupName;
+    }
+
+    public String getJobKey() {
+        return this.jobKey;
+    }
+
+    public Short getSchedulerGroup() {
+        return this.schedulerGroup;
+    }
+
+    public boolean isActiveSchedular() {
+        return this.activeSchedular;
+    }
+
+    public void updateCronExpression(final String cronExpression) {
+        this.cronExpression = cronExpression;
+    }
+
+    public void updatePreviousRunStartTime(final Date previousRunStartTime) {
+        this.previousRunStartTime = previousRunStartTime;
+    }
+
+    public Date getNextRunTime() {
+        return this.nextRunTime;
+    }
+
+    public void updateNextRunTime(final Date nextRunTime) {
+        this.nextRunTime = nextRunTime;
+    }
+
+    public void updateJobKey(final String jobKey) {
+        this.jobKey = jobKey;
+    }
+
+    public void updateErrorLog(final String errorLog) {
+        this.errorLog = errorLog;
+    }
+
+    public boolean isCurrentlyRunning() {
+        return this.currentlyRunning;
+    }
+
+    public void updateCurrentlyRunningStatus(final boolean currentlyRunning) {
+        this.currentlyRunning = currentlyRunning;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+        if (command.isChangeInStringParameterNamed(SchedulerJobApiConstants.displayNameParamName, this.jobDisplayName)) {
+            final String newValue = command.stringValueOfParameterNamed(SchedulerJobApiConstants.displayNameParamName).trim();
+            actualChanges.put(SchedulerJobApiConstants.displayNameParamName, newValue);
+            this.jobDisplayName = StringUtils.defaultIfEmpty(newValue, null);
+        }
+        if (command.isChangeInStringParameterNamed(SchedulerJobApiConstants.cronExpressionParamName, this.cronExpression)) {
+            final String newValue = command.stringValueOfParameterNamed(SchedulerJobApiConstants.cronExpressionParamName).trim();
+            actualChanges.put(SchedulerJobApiConstants.cronExpressionParamName, newValue);
+            this.cronExpression = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInBooleanParameterNamed(SchedulerJobApiConstants.jobActiveStatusParamName, this.activeSchedular)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(SchedulerJobApiConstants.jobActiveStatusParamName);
+            actualChanges.put(SchedulerJobApiConstants.jobActiveStatusParamName, newValue);
+            this.activeSchedular = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    public boolean isTriggerMisfired() {
+        return this.triggerMisfired;
+    }
+
+    public void updateTriggerMisfired(final boolean triggerMisfired) {
+        this.triggerMisfired = triggerMisfired;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
new file mode 100644
index 0000000..86140a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
@@ -0,0 +1,41 @@
+/**
+ * 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.jobs.domain;
+
+import javax.persistence.LockModeType;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Lock;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+public interface ScheduledJobDetailRepository extends JpaRepository<ScheduledJobDetail, Long>, JpaSpecificationExecutor<ScheduledJobDetail> {
+
+    @Query("from ScheduledJobDetail jobDetail where jobDetail.jobKey = :jobKey")
+    ScheduledJobDetail findByJobKey(@Param("jobKey") String jobKey);
+
+    @Query("from ScheduledJobDetail jobDetail where jobDetail.id=:jobId")
+    ScheduledJobDetail findByJobId(@Param("jobId") Long jobId);
+
+    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
+    @Query("from ScheduledJobDetail jobDetail where jobDetail.jobKey = :jobKey")
+    ScheduledJobDetail findByJobKeyWithLock(@Param("jobKey") String jobKey);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobRunHistory.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobRunHistory.java
new file mode 100644
index 0000000..8c66002
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobRunHistory.java
@@ -0,0 +1,80 @@
+/**
+ * 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.jobs.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.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "job_run_history")
+public class ScheduledJobRunHistory extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "job_id")
+    private ScheduledJobDetail scheduledJobDetail;
+
+    @Column(name = "version")
+    private Long version;
+
+    @Column(name = "start_time")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date startTime;
+
+    @Column(name = "end_time")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date endTime;
+
+    @Column(name = "status")
+    private String status;
+
+    @Column(name = "error_message")
+    private String errorMessage;
+
+    @Column(name = "trigger_type")
+    private String triggerType;
+
+    @Column(name = "error_log")
+    private String errorLog;
+
+    public ScheduledJobRunHistory() {
+
+    }
+
+    public ScheduledJobRunHistory(final ScheduledJobDetail scheduledJobDetail, final Long version, final Date startTime,
+            final Date endTime, final String status, final String errorMessage, final String triggerType, final String errorLog) {
+        this.scheduledJobDetail = scheduledJobDetail;
+        this.version = version;
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.status = status;
+        this.errorMessage = errorMessage;
+        this.triggerType = triggerType;
+        this.errorLog = errorLog;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobRunHistoryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobRunHistoryRepository.java
new file mode 100644
index 0000000..12c23c3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobRunHistoryRepository.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.infrastructure.jobs.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 ScheduledJobRunHistoryRepository extends JpaRepository<ScheduledJobRunHistory, Long>,
+        JpaSpecificationExecutor<ScheduledJobRunHistory> {
+
+    @Query("select max(sjrh.version) from ScheduledJobRunHistory sjrh where sjrh.scheduledJobDetail.jobKey = :jobKey")
+    Long findMaxVersionByJobKey(@Param("jobKey") String jobKey);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/SchedulerDetail.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/SchedulerDetail.java
new file mode 100755
index 0000000..0afb31b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/SchedulerDetail.java
@@ -0,0 +1,67 @@
+/**
+ * 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.jobs.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "scheduler_detail")
+public class SchedulerDetail extends AbstractPersistable<Long> {
+
+    @Column(name = "execute_misfired_jobs")
+    private boolean executeInstructionForMisfiredJobs;
+
+    @Column(name = "is_suspended")
+    private boolean suspended;
+
+    @Column(name = "reset_scheduler_on_bootup")
+    private boolean resetSchedulerOnBootup;
+
+    protected SchedulerDetail() {
+
+    }
+
+    public boolean isExecuteInstructionForMisfiredJobs() {
+        return this.executeInstructionForMisfiredJobs;
+    }
+
+    public void updateExecuteInstructionForMisfiredJobs(final boolean executeInstructionForMisfiredJobs) {
+        this.executeInstructionForMisfiredJobs = executeInstructionForMisfiredJobs;
+    }
+
+    public boolean isSuspended() {
+        return this.suspended;
+    }
+
+    public void updateSuspendedState(final boolean suspended) {
+        this.suspended = suspended;
+    }
+
+    public boolean isResetSchedulerOnBootup() {
+        return this.resetSchedulerOnBootup;
+    }
+
+    public void updateResetSchedulerOnBootup(final boolean resetSchedulerOnBootup) {
+        this.resetSchedulerOnBootup = resetSchedulerOnBootup;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/SchedulerDetailRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/SchedulerDetailRepository.java
new file mode 100755
index 0000000..a9ae63f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/SchedulerDetailRepository.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.infrastructure.jobs.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface SchedulerDetailRepository extends JpaRepository<SchedulerDetail, Long>, JpaSpecificationExecutor<SchedulerDetail> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java
new file mode 100755
index 0000000..3ebc87b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.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.infrastructure.jobs.exception;
+
+public class JobExecutionException extends Exception {
+
+    public JobExecutionException(final String msg) {
+        super(msg);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobInProcessExecution.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobInProcessExecution.java
new file mode 100755
index 0000000..367cca2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobInProcessExecution.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.infrastructure.jobs.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class JobInProcessExecution extends AbstractPlatformResourceNotFoundException {
+
+    public JobInProcessExecution(final String identifier) {
+        super("error.msg.sheduler.job.inprogress", "job execution is in process for " + identifier, identifier);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java
new file mode 100755
index 0000000..a50a7a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.jobs.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when Job resources are not found.
+ */
+public class JobNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public JobNotFoundException(final String identifier) {
+        super("error.msg.sheduler.job.id.invalid", "Job with identifier " + identifier + " does not exist", identifier);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/OperationNotAllowedException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/OperationNotAllowedException.java
new file mode 100755
index 0000000..e6504fe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/OperationNotAllowedException.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.infrastructure.jobs.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
+
+/**
+ * A {@link RuntimeException} thrown when Job execution is in progress.
+ */
+public class OperationNotAllowedException extends AbstractPlatformServiceUnavailableException {
+
+    public OperationNotAllowedException(final String jobNames) {
+        super("error.msg.sheduler.job.currently.running", "Execution is in-process for jobs " + jobNames
+                + "...., so update operations are not allowed at this moment", jobNames);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/UpdateJobDetailCommandhandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/UpdateJobDetailCommandhandler.java
new file mode 100755
index 0000000..9cc9af8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/UpdateJobDetailCommandhandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.jobs.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.infrastructure.jobs.service.SchedularWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "SCHEDULER", action = "UPDATE")
+public class UpdateJobDetailCommandhandler implements NewCommandSourceHandler {
+
+    private final SchedularWritePlatformService schedularWritePlatformService;
+
+    @Autowired
+    public UpdateJobDetailCommandhandler(final SchedularWritePlatformService schedularWritePlatformService) {
+        this.schedularWritePlatformService = schedularWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.schedularWritePlatformService.updateJobDetail(command.entityId(), command);
+    }
+
+}
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
new file mode 100755
index 0000000..b244804
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.jobs.service;
+
+public enum JobName {
+
+    UPDATE_LOAN_SUMMARY("Update loan Summary"), //
+    UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), //
+    UPDATE_LOAN_PAID_IN_ADVANCE("Update Loan Paid In Advance"), //
+    APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), //
+    APPLY_HOLIDAYS_TO_LOANS("Apply Holidays To Loans"), //
+    POST_INTEREST_FOR_SAVINGS("Post Interest For Savings"), //
+    TRANSFER_FEE_CHARGE_FOR_LOANS("Transfer Fee For Loans From Savings"), //
+    ACCOUNTING_RUNNING_BALANCE_UPDATE("Update Accounting Running Balances"), //
+    PAY_DUE_SAVINGS_CHARGES("Pay Due Savings Charges"), //
+    APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT("Apply penalty to overdue loans"),
+    EXECUTE_STANDING_INSTRUCTIONS("Execute Standing Instruction"),
+    ADD_ACCRUAL_ENTRIES("Add Accrual Transactions"),
+    UPDATE_NPA("Update Non Performing Assets"),
+    UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS("Update Deposit Accounts Maturity details"),
+    TRANSFER_INTEREST_TO_SAVINGS("Transfer Interest To Savings"),
+    ADD_PERIODIC_ACCRUAL_ENTRIES("Add Periodic Accrual Transactions"),
+    RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans"),
+    GENERATE_RD_SCEHDULE("Generate Mandatory Savings Schedule"),
+    GENERATE_LOANLOSS_PROVISIONING("Generate Loan Loss Provisioning");
+    
+    private final String name;
+
+    private JobName(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        return this.name;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java
new file mode 100755
index 0000000..54ac539
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java
@@ -0,0 +1,37 @@
+/**
+ * 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.jobs.service;
+
+public interface JobRegisterService {
+
+    public void executeJob(Long jobId);
+
+    public void rescheduleJob(Long jobId);
+
+    public void pauseScheduler();
+
+    public void startScheduler();
+
+    public boolean isSchedulerRunning();
+
+    public void stopScheduler(String name);
+
+    public void stopAllSchedulers();
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
new file mode 100644
index 0000000..89fe38e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
@@ -0,0 +1,421 @@
+/**
+ * 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.jobs.service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+import java.util.TimeZone;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.annotation.CronMethodParser;
+import org.apache.fineract.infrastructure.jobs.annotation.CronMethodParser.ClassMethodNamesPair;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
+import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail;
+import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
+import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.JobListener;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
+import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.stereotype.Service;
+
+/**
+ * Service class to create and load batch jobs to Scheduler using
+ * {@link SchedulerFactoryBean} ,{@link MethodInvokingJobDetailFactoryBean} and
+ * {@link CronTriggerFactoryBean}
+ */
+@Service
+public class JobRegisterServiceImpl implements JobRegisterService, ApplicationListener<ContextClosedEvent> {
+
+    private final static Logger logger = LoggerFactory.getLogger(JobRegisterServiceImpl.class);
+
+    // MIFOSX-1184: This class cannot use constructor injection, because one of
+    // its dependencies (SchedulerStopListener) has a circular dependency to
+    // itself. So, slightly differently from how it's done elsewhere in this
+    // code base, the following fields are not final, and there is no
+    // constructor, but setters.
+
+    private ApplicationContext applicationContext;
+    private SchedularWritePlatformService schedularWritePlatformService;
+    private TenantDetailsService tenantDetailsService;
+    private SchedulerJobListener schedulerJobListener;
+    private SchedulerStopListener schedulerStopListener;
+    private SchedulerTriggerListener globalSchedulerTriggerListener;
+
+    private final HashMap<String, Scheduler> schedulers = new HashMap<>(4);
+
+    @Autowired
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        this.applicationContext = applicationContext;
+    }
+
+    @Autowired
+    public void setSchedularWritePlatformService(SchedularWritePlatformService schedularWritePlatformService) {
+        this.schedularWritePlatformService = schedularWritePlatformService;
+    }
+
+    @Autowired
+    public void setTenantDetailsService(TenantDetailsService tenantDetailsService) {
+        this.tenantDetailsService = tenantDetailsService;
+    }
+
+    @Autowired
+    public void setSchedulerJobListener(SchedulerJobListener schedulerJobListener) {
+        this.schedulerJobListener = schedulerJobListener;
+    }
+
+    @Autowired
+    public void setSchedulerStopListener(SchedulerStopListener schedulerStopListener) {
+        this.schedulerStopListener = schedulerStopListener;
+    }
+
+    @Autowired
+    public void setGlobalTriggerListener(SchedulerTriggerListener globalTriggerListener) {
+        this.globalSchedulerTriggerListener = globalTriggerListener;
+    }
+
+    @PostConstruct
+    public void loadAllJobs() {
+        final List<FineractPlatformTenant> allTenants = this.tenantDetailsService.findAllTenants();
+        for (final FineractPlatformTenant tenant : allTenants) {
+            ThreadLocalContextUtil.setTenant(tenant);
+            final List<ScheduledJobDetail> scheduledJobDetails = this.schedularWritePlatformService.retrieveAllJobs();
+            for (final ScheduledJobDetail jobDetails : scheduledJobDetails) {
+                scheduleJob(jobDetails);
+                jobDetails.updateTriggerMisfired(false);
+                this.schedularWritePlatformService.saveOrUpdate(jobDetails);
+            }
+            final SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
+            if (schedulerDetail.isResetSchedulerOnBootup()) {
+                schedulerDetail.updateSuspendedState(false);
+                this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
+            }
+        }
+    }
+
+    public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType) {
+        try {
+            final JobDataMap jobDataMap = new JobDataMap();
+            if (triggerType == null) {
+                triggerType = SchedulerServiceConstants.TRIGGER_TYPE_APPLICATION;
+            }
+            jobDataMap.put(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE, triggerType);
+            jobDataMap.put(SchedulerServiceConstants.TENANT_IDENTIFIER, ThreadLocalContextUtil.getTenant().getTenantIdentifier());
+            final String key = scheduledJobDetail.getJobKey();
+            final JobKey jobKey = constructJobKey(key);
+            final String schedulerName = getSchedulerName(scheduledJobDetail);
+            final Scheduler scheduler = this.schedulers.get(schedulerName);
+            if (scheduler == null || !scheduler.checkExists(jobKey)) {
+                final JobDetail jobDetail = createJobDetail(scheduledJobDetail);
+                final String tempSchedulerName = "temp" + scheduledJobDetail.getId();
+                final Scheduler tempScheduler = createScheduler(tempSchedulerName, 1, schedulerJobListener, schedulerStopListener);
+                tempScheduler.addJob(jobDetail, true);
+                jobDataMap.put(SchedulerServiceConstants.SCHEDULER_NAME, tempSchedulerName);
+                this.schedulers.put(tempSchedulerName, tempScheduler);
+                tempScheduler.triggerJob(jobDetail.getKey(), jobDataMap);
+            } else {
+                scheduler.triggerJob(jobKey, jobDataMap);
+            }
+
+        } catch (final Exception e) {
+            final String msg = "Job execution failed for job with id:" + scheduledJobDetail.getId();
+            logger.error(msg, e);
+            throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", msg, scheduledJobDetail.getId());
+        }
+
+    }
+
+    public void rescheduleJob(final ScheduledJobDetail scheduledJobDetail) {
+        try {
+            final String jobIdentity = scheduledJobDetail.getJobKey();
+            final JobKey jobKey = constructJobKey(jobIdentity);
+            final String schedulername = getSchedulerName(scheduledJobDetail);
+            final Scheduler scheduler = this.schedulers.get(schedulername);
+            if (scheduler != null) {
+                scheduler.deleteJob(jobKey);
+            }
+            scheduleJob(scheduledJobDetail);
+            this.schedularWritePlatformService.saveOrUpdate(scheduledJobDetail);
+        } catch (final Throwable throwable) {
+            final String stackTrace = getStackTraceAsString(throwable);
+            scheduledJobDetail.updateErrorLog(stackTrace);
+            this.schedularWritePlatformService.saveOrUpdate(scheduledJobDetail);
+        }
+    }
+
+    @Override
+    public void pauseScheduler() {
+        final SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
+        if (!schedulerDetail.isSuspended()) {
+            schedulerDetail.updateSuspendedState(true);
+            this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
+        }
+    }
+
+    @Override
+    public void startScheduler() {
+        final SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
+        if (schedulerDetail.isSuspended()) {
+            schedulerDetail.updateSuspendedState(false);
+            this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
+            if (schedulerDetail.isExecuteInstructionForMisfiredJobs()) {
+                final List<ScheduledJobDetail> scheduledJobDetails = this.schedularWritePlatformService.retrieveAllJobs();
+                for (final ScheduledJobDetail jobDetail : scheduledJobDetails) {
+                    if (jobDetail.isTriggerMisfired()) {
+                        if (jobDetail.isActiveSchedular()) {
+                            executeJob(jobDetail, SchedulerServiceConstants.TRIGGER_TYPE_CRON);
+                        }
+                        final String schedulerName = getSchedulerName(jobDetail);
+                        final Scheduler scheduler = this.schedulers.get(schedulerName);
+                        if (scheduler != null) {
+                            final String key = jobDetail.getJobKey();
+                            final JobKey jobKey = constructJobKey(key);
+                            try {
+                                final List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
+                                for (final Trigger trigger : triggers) {
+                                    if (trigger.getNextFireTime() != null && trigger.getNextFireTime().after(jobDetail.getNextRunTime())) {
+                                        jobDetail.updateNextRunTime(trigger.getNextFireTime());
+                                    }
+                                }
+                            } catch (final SchedulerException e) {
+                                logger.error(e.getMessage(), e);
+                            }
+                        }
+                        jobDetail.updateTriggerMisfired(false);
+                        this.schedularWritePlatformService.saveOrUpdate(jobDetail);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void rescheduleJob(final Long jobId) {
+        final ScheduledJobDetail scheduledJobDetail = this.schedularWritePlatformService.findByJobId(jobId);
+        rescheduleJob(scheduledJobDetail);
+    }
+
+    @Override
+    public void executeJob(final Long jobId) {
+        final ScheduledJobDetail scheduledJobDetail = this.schedularWritePlatformService.findByJobId(jobId);
+        if (scheduledJobDetail == null) { throw new JobNotFoundException(String.valueOf(jobId)); }
+        executeJob(scheduledJobDetail, null);
+    }
+
+    @Override
+    public boolean isSchedulerRunning() {
+        return !this.schedularWritePlatformService.retriveSchedulerDetail().isSuspended();
+    }
+
+    /**
+     * Need to use ContextClosedEvent instead of ContextStoppedEvent because in
+     * case Spring Boot fails to start-up (e.g. because Tomcat port is already
+     * in use) then org.springframework.boot.SpringApplication.run(String...)
+     * does a context.close(); and not a context.stop();
+     */
+    @Override
+    public void onApplicationEvent(@SuppressWarnings("unused") ContextClosedEvent event) {
+        this.stopAllSchedulers();
+    }
+
+    private void scheduleJob(final ScheduledJobDetail scheduledJobDetails) {
+        if (!scheduledJobDetails.isActiveSchedular()) {
+            scheduledJobDetails.updateNextRunTime(null);
+            scheduledJobDetails.updateCurrentlyRunningStatus(false);
+            return;
+        }
+        try {
+            final JobDetail jobDetail = createJobDetail(scheduledJobDetails);
+            final Trigger trigger = createTrigger(scheduledJobDetails, jobDetail);
+            final Scheduler scheduler = getScheduler(scheduledJobDetails);
+            scheduler.scheduleJob(jobDetail, trigger);
+            scheduledJobDetails.updateJobKey(getJobKeyAsString(jobDetail.getKey()));
+            scheduledJobDetails.updateNextRunTime(trigger.getNextFireTime());
+            scheduledJobDetails.updateErrorLog(null);
+        } catch (final Throwable throwable) {
+            scheduledJobDetails.updateNextRunTime(null);
+            final String stackTrace = getStackTraceAsString(throwable);
+            scheduledJobDetails.updateErrorLog(stackTrace);
+            logger.error("Could not schedule job: " + scheduledJobDetails.getJobName(), throwable);
+        }
+        scheduledJobDetails.updateCurrentlyRunningStatus(false);
+    }
+
+    @Override
+    public void stopAllSchedulers() {
+        for (Scheduler scheduler : this.schedulers.values()) {
+            try {
+                scheduler.shutdown();
+            } catch (final SchedulerException e) {
+                logger.error(e.getMessage(), e);
+            }
+        }
+    }
+
+    private Scheduler getScheduler(final ScheduledJobDetail scheduledJobDetail) throws Exception {
+        final String schedulername = getSchedulerName(scheduledJobDetail);
+        Scheduler scheduler = this.schedulers.get(schedulername);
+        if (scheduler == null) {
+            int noOfThreads = SchedulerServiceConstants.DEFAULT_THREAD_COUNT;
+            if (scheduledJobDetail.getSchedulerGroup() > 0) {
+                noOfThreads = SchedulerServiceConstants.GROUP_THREAD_COUNT;
+            }
+            scheduler = createScheduler(schedulername, noOfThreads, schedulerJobListener);
+            this.schedulers.put(schedulername, scheduler);
+        }
+        return scheduler;
+    }
+
+    @Override
+    public void stopScheduler(final String name) {
+        final Scheduler scheduler = this.schedulers.remove(name);
+        try {
+            scheduler.shutdown();
+        } catch (final SchedulerException e) {
+            logger.error(e.getMessage(), e);
+        }
+    }
+
+    private String getSchedulerName(final ScheduledJobDetail scheduledJobDetail) {
+        final StringBuilder sb = new StringBuilder(20);
+        final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
+        sb.append(SchedulerServiceConstants.SCHEDULER).append(tenant.getId());
+        if (scheduledJobDetail.getSchedulerGroup() > 0) {
+            sb.append(SchedulerServiceConstants.SCHEDULER_GROUP).append(scheduledJobDetail.getSchedulerGroup());
+        }
+        return sb.toString();
+    }
+
+    private Scheduler createScheduler(final String name, final int noOfThreads, JobListener... jobListeners) throws Exception {
+        final SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
+        schedulerFactoryBean.setSchedulerName(name);
+        schedulerFactoryBean.setGlobalJobListeners(jobListeners);
+        final TriggerListener[] globalTriggerListeners = { globalSchedulerTriggerListener };
+        schedulerFactoryBean.setGlobalTriggerListeners(globalTriggerListeners);
+        final Properties quartzProperties = new Properties();
+        quartzProperties.put(SchedulerFactoryBean.PROP_THREAD_COUNT, Integer.toString(noOfThreads));
+        schedulerFactoryBean.setQuartzProperties(quartzProperties);
+        schedulerFactoryBean.afterPropertiesSet();
+        schedulerFactoryBean.start();
+        return schedulerFactoryBean.getScheduler();
+    }
+
+    private JobDetail createJobDetail(final ScheduledJobDetail scheduledJobDetail) throws Exception {
+        final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
+        final ClassMethodNamesPair jobDetails = CronMethodParser.findTargetMethodDetails(scheduledJobDetail.getJobName());
+        if (jobDetails == null) { throw new IllegalArgumentException(
+                "Code has no @CronTarget with this job name (@see JobName); seems like DB/code are not in line: "
+                        + scheduledJobDetail.getJobName()); }
+        final Object targetObject = getBeanObject(Class.forName(jobDetails.className));
+        final MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();
+        jobDetailFactoryBean.setName(scheduledJobDetail.getJobName() + "JobDetail" + tenant.getId());
+        jobDetailFactoryBean.setTargetObject(targetObject);
+        jobDetailFactoryBean.setTargetMethod(jobDetails.methodName);
+        jobDetailFactoryBean.setGroup(scheduledJobDetail.getGroupName());
+        jobDetailFactoryBean.setConcurrent(false);
+        jobDetailFactoryBean.afterPropertiesSet();
+        return jobDetailFactoryBean.getObject();
+    }
+
+    private Object getBeanObject(final Class<?> classType) throws ClassNotFoundException {
+        final List<Class<?>> typesList = new ArrayList<>();
+        final Class<?>[] interfaceType = classType.getInterfaces();
+        if (interfaceType.length > 0) {
+            typesList.addAll(Arrays.asList(interfaceType));
+        } else {
+            Class<?> superclassType = classType;
+            while (!Object.class.getName().equals(superclassType.getSuperclass().getName())) {
+                superclassType = superclassType.getSuperclass();
+            }
+            typesList.add(superclassType);
+        }
+        final List<String> beanNames = new ArrayList<>();
+        for (final Class<?> clazz : typesList) {
+            beanNames.addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(clazz)));
+        }
+        Object targetObject = null;
+        for (final String beanName : beanNames) {
+            final Object nextObject = this.applicationContext.getBean(beanName);
+            String targetObjName = nextObject.toString();
+            targetObjName = targetObjName.substring(0, targetObjName.lastIndexOf("@"));
+            if (classType.getName().equals(targetObjName)) {
+                targetObject = nextObject;
+                break;
+            }
+        }
+        return targetObject;
+    }
+
+    private Trigger createTrigger(final ScheduledJobDetail scheduledJobDetails, final JobDetail jobDetail) {
+        final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
+        final CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
+        cronTriggerFactoryBean.setName(scheduledJobDetails.getJobName() + "Trigger" + tenant.getId());
+        cronTriggerFactoryBean.setJobDetail(jobDetail);
+        final JobDataMap jobDataMap = new JobDataMap();
+        jobDataMap.put(SchedulerServiceConstants.TENANT_IDENTIFIER, tenant.getTenantIdentifier());
+        cronTriggerFactoryBean.setJobDataMap(jobDataMap);
+        final TimeZone timeZone = TimeZone.getTimeZone(tenant.getTimezoneId());
+        cronTriggerFactoryBean.setTimeZone(timeZone);
+        cronTriggerFactoryBean.setGroup(scheduledJobDetails.getGroupName());
+        cronTriggerFactoryBean.setCronExpression(scheduledJobDetails.getCronExpression());
+        cronTriggerFactoryBean.setPriority(scheduledJobDetails.getTaskPriority());
+        cronTriggerFactoryBean.afterPropertiesSet();
+        return cronTriggerFactoryBean.getObject();
+    }
+
+    private String getStackTraceAsString(final Throwable throwable) {
+        final StackTraceElement[] stackTraceElements = throwable.getStackTrace();
+        final StringBuffer sb = new StringBuffer(throwable.toString());
+        for (final StackTraceElement element : stackTraceElements) {
+            sb.append("\n \t at ").append(element.getClassName()).append(".").append(element.getMethodName()).append("(")
+                    .append(element.getLineNumber()).append(")");
+        }
+        return sb.toString();
+    }
+
+    private String getJobKeyAsString(final JobKey jobKey) {
+        return jobKey.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + jobKey.getGroup();
+    }
+
+    private JobKey constructJobKey(final String Key) {
+        final String[] keyParams = Key.split(SchedulerServiceConstants.JOB_KEY_SEPERATOR);
+        final JobKey JobKey = new JobKey(keyParams[0], keyParams[1]);
+        return JobKey;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformService.java
new file mode 100644
index 0000000..e08d9f1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformService.java
@@ -0,0 +1,51 @@
+/**
+ * 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.jobs.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobRunHistory;
+import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail;
+
+public interface SchedularWritePlatformService {
+
+    public List<ScheduledJobDetail> retrieveAllJobs();
+
+    public ScheduledJobDetail findByJobKey(String triggerKey);
+
+    public void saveOrUpdate(ScheduledJobDetail scheduledJobDetails);
+
+    public void saveOrUpdate(ScheduledJobDetail scheduledJobDetails, ScheduledJobRunHistory scheduledJobRunHistory);
+
+    public Long fetchMaxVersionBy(String triggerKey);
+
+    public ScheduledJobDetail findByJobId(Long jobId);
+
+    public CommandProcessingResult updateJobDetail(Long jobId, JsonCommand command);
+
+    public SchedulerDetail retriveSchedulerDetail();
+
+    public void updateSchedulerDetail(final SchedulerDetail schedulerDetail);
+
+    public boolean processJobDetailForExecution(String jobKey, String triggerType);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..f4ac6ac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.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.infrastructure.jobs.service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.jobs.data.JobDetailDataValidator;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobRunHistory;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobRunHistoryRepository;
+import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail;
+import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetailRepository;
+import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class SchedularWritePlatformServiceJpaRepositoryImpl implements SchedularWritePlatformService {
+
+    private final ScheduledJobDetailRepository scheduledJobDetailsRepository;
+
+    private final ScheduledJobRunHistoryRepository scheduledJobRunHistoryRepository;
+
+    private final SchedulerDetailRepository schedulerDetailRepository;
+
+    private final JobDetailDataValidator dataValidator;
+
+    @Autowired
+    public SchedularWritePlatformServiceJpaRepositoryImpl(final ScheduledJobDetailRepository scheduledJobDetailsRepository,
+            final ScheduledJobRunHistoryRepository scheduledJobRunHistoryRepository, final JobDetailDataValidator dataValidator,
+            final SchedulerDetailRepository schedulerDetailRepository) {
+        this.scheduledJobDetailsRepository = scheduledJobDetailsRepository;
+        this.scheduledJobRunHistoryRepository = scheduledJobRunHistoryRepository;
+        this.schedulerDetailRepository = schedulerDetailRepository;
+        this.dataValidator = dataValidator;
+    }
+
+    @Override
+    public List<ScheduledJobDetail> retrieveAllJobs() {
+        return this.scheduledJobDetailsRepository.findAll();
+    }
+
+    @Override
+    public ScheduledJobDetail findByJobKey(final String jobKey) {
+        return this.scheduledJobDetailsRepository.findByJobKey(jobKey);
+    }
+
+    @Transactional
+    @Override
+    public void saveOrUpdate(final ScheduledJobDetail scheduledJobDetails) {
+        this.scheduledJobDetailsRepository.save(scheduledJobDetails);
+    }
+
+    @Transactional
+    @Override
+    public void saveOrUpdate(final ScheduledJobDetail scheduledJobDetails, final ScheduledJobRunHistory scheduledJobRunHistory) {
+        this.scheduledJobDetailsRepository.save(scheduledJobDetails);
+        this.scheduledJobRunHistoryRepository.save(scheduledJobRunHistory);
+    }
+
+    @Override
+    public Long fetchMaxVersionBy(final String jobKey) {
+        Long version = 0L;
+        final Long versionFromDB = this.scheduledJobRunHistoryRepository.findMaxVersionByJobKey(jobKey);
+        if (versionFromDB != null) {
+            version = versionFromDB;
+        }
+        return version;
+    }
+
+    @Override
+    public ScheduledJobDetail findByJobId(final Long jobId) {
+        return this.scheduledJobDetailsRepository.findByJobId(jobId);
+    }
+
+    @Override
+    @Transactional
+    public void updateSchedulerDetail(final SchedulerDetail schedulerDetail) {
+        this.schedulerDetailRepository.save(schedulerDetail);
+    }
+
+    @Override
+    public SchedulerDetail retriveSchedulerDetail() {
+        SchedulerDetail schedulerDetail = null;
+        final List<SchedulerDetail> schedulerDetailList = this.schedulerDetailRepository.findAll();
+        if (schedulerDetailList != null) {
+            schedulerDetail = schedulerDetailList.get(0);
+        }
+        return schedulerDetail;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateJobDetail(final Long jobId, final JsonCommand command) {
+        this.dataValidator.validateForUpdate(command.json());
+        final ScheduledJobDetail scheduledJobDetail = findByJobId(jobId);
+        if (scheduledJobDetail == null) { throw new JobNotFoundException(String.valueOf(jobId)); }
+        final Map<String, Object> changes = scheduledJobDetail.update(command);
+        if (!changes.isEmpty()) {
+            this.scheduledJobDetailsRepository.saveAndFlush(scheduledJobDetail);
+        }
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(jobId) //
+                .with(changes) //
+                .build();
+
+    }
+
+    @Transactional
+    @Override
+    public boolean processJobDetailForExecution(final String jobKey, final String triggerType) {
+        boolean isStopExecution = false;
+        final ScheduledJobDetail scheduledJobDetail = this.scheduledJobDetailsRepository.findByJobKeyWithLock(jobKey);
+        if (scheduledJobDetail.isCurrentlyRunning()
+                || (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && (scheduledJobDetail.getNextRunTime().after(new Date())))) {
+            isStopExecution = true;
+        }
+        final SchedulerDetail schedulerDetail = retriveSchedulerDetail();
+        if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && schedulerDetail.isSuspended()) {
+            scheduledJobDetail.updateTriggerMisfired(true);
+            isStopExecution = true;
+        } else if (!isStopExecution) {
+            scheduledJobDetail.updateCurrentlyRunningStatus(true);
+        }
+        this.scheduledJobDetailsRepository.save(scheduledJobDetail);
+        return isStopExecution;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java
new file mode 100644
index 0000000..6397c6d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java
@@ -0,0 +1,142 @@
+/**
+ * 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.jobs.service;
+
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobRunHistory;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.JobKey;
+import org.quartz.JobListener;
+import org.quartz.Trigger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+
+/**
+ * Global job Listener class to set Tenant details to
+ * {@link ThreadLocalContextUtil} for batch Job and stores the batch job status
+ * to database after the execution
+ * 
+ */
+@Component
+public class SchedulerJobListener implements JobListener {
+
+    private int stackTraceLevel = 0;
+
+    private final String name = SchedulerServiceConstants.DEFAULT_LISTENER_NAME;
+
+    private final SchedularWritePlatformService schedularService;
+
+    private final AppUserRepositoryWrapper userRepository ;
+    
+    private final GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
+    
+    @Autowired
+    public SchedulerJobListener(final SchedularWritePlatformService schedularService,
+            final AppUserRepositoryWrapper userRepository) {
+        this.schedularService = schedularService;
+        this.userRepository = userRepository ;
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public void jobToBeExecuted(@SuppressWarnings("unused") final JobExecutionContext context) {
+        AppUser user = this.userRepository.fetchSystemUser();
+        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, user.getPassword(),
+                authoritiesMapper.mapAuthorities(user.getAuthorities()));
+        SecurityContextHolder.getContext().setAuthentication(auth);
+    }
+
+    @Override
+    public void jobExecutionVetoed(@SuppressWarnings("unused") final JobExecutionContext context) {
+
+    }
+
+    @Override
+    public void jobWasExecuted(final JobExecutionContext context, final JobExecutionException jobException) {
+        final Trigger trigger = context.getTrigger();
+        final JobKey key = context.getJobDetail().getKey();
+        final String jobKey = key.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + key.getGroup();
+        final ScheduledJobDetail scheduledJobDetails = this.schedularService.findByJobKey(jobKey);
+        final Long version = this.schedularService.fetchMaxVersionBy(jobKey) + 1;
+        String status = SchedulerServiceConstants.STATUS_SUCCESS;
+        String errorMessage = null;
+        String errorLog = null;
+        if (jobException != null) {
+            status = SchedulerServiceConstants.STATUS_FAILED;
+            this.stackTraceLevel = 0;
+            final Throwable throwable = getCauseFromException(jobException);
+            this.stackTraceLevel = 0;
+            StackTraceElement[] stackTraceElements = null;
+            errorMessage = throwable.getMessage();
+            stackTraceElements = throwable.getStackTrace();
+            final StringBuffer sb = new StringBuffer(throwable.toString());
+            for (final StackTraceElement element : stackTraceElements) {
+                sb.append("\n \t at ").append(element.getClassName()).append(".").append(element.getMethodName()).append("(")
+                        .append(element.getLineNumber()).append(")");
+            }
+            errorLog = sb.toString();
+
+        }
+        String triggerType = SchedulerServiceConstants.TRIGGER_TYPE_CRON;
+        if (context.getMergedJobDataMap().containsKey(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE)) {
+            triggerType = context.getMergedJobDataMap().getString(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE);
+        }
+        if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && trigger.getNextFireTime() != null
+                && trigger.getNextFireTime().after(scheduledJobDetails.getNextRunTime())) {
+            scheduledJobDetails.updateNextRunTime(trigger.getNextFireTime());
+        }
+
+        scheduledJobDetails.updatePreviousRunStartTime(context.getFireTime());
+        scheduledJobDetails.updateCurrentlyRunningStatus(false);
+
+        final ScheduledJobRunHistory runHistory = new ScheduledJobRunHistory(scheduledJobDetails, version, context.getFireTime(),
+                new Date(), status, errorMessage, triggerType, errorLog);
+        // scheduledJobDetails.addRunHistory(runHistory);
+
+        this.schedularService.saveOrUpdate(scheduledJobDetails, runHistory);
+
+    }
+
+    private Throwable getCauseFromException(final Throwable exception) {
+        if (this.stackTraceLevel <= SchedulerServiceConstants.STACK_TRACE_LEVEL
+                && exception.getCause() != null
+                && (exception.getCause().toString().contains(SchedulerServiceConstants.SCHEDULER_EXCEPTION)
+                        || exception.getCause().toString().contains(SchedulerServiceConstants.JOB_EXECUTION_EXCEPTION) || exception
+                        .getCause().toString().contains(SchedulerServiceConstants.JOB_METHOD_INVOCATION_FAILED_EXCEPTION))) {
+            this.stackTraceLevel++;
+            return getCauseFromException(exception.getCause());
+        } else if (exception.getCause() != null) { return exception.getCause(); }
+        return exception;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadService.java
new file mode 100755
index 0000000..e7dd82a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadService.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.jobs.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.jobs.data.JobDetailData;
+import org.apache.fineract.infrastructure.jobs.data.JobDetailHistoryData;
+
+public interface SchedulerJobRunnerReadService {
+
+    public List<JobDetailData> findAllJobDeatils();
+
+    public JobDetailData retrieveOne(Long jobId);
+
+    public Page<JobDetailHistoryData> retrieveJobHistory(Long jobId, SearchParameters searchParameters);
+
+    public boolean isUpdatesAllowed();
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java
new file mode 100755
index 0000000..b61b8da
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java
@@ -0,0 +1,190 @@
+/**
+ * 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.jobs.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.jobs.data.JobDetailData;
+import org.apache.fineract.infrastructure.jobs.data.JobDetailHistoryData;
+import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
+import org.apache.fineract.infrastructure.jobs.exception.OperationNotAllowedException;
+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 SchedulerJobRunnerReadServiceImpl implements SchedulerJobRunnerReadService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    private final PaginationHelper<JobDetailHistoryData> paginationHelper = new PaginationHelper<>();
+
+    @Autowired
+    public SchedulerJobRunnerReadServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public List<JobDetailData> findAllJobDeatils() {
+        final JobDetailMapper detailMapper = new JobDetailMapper();
+        final String sql = detailMapper.schema();
+        final List<JobDetailData> JobDeatils = this.jdbcTemplate.query(sql, detailMapper, new Object[] {});
+        return JobDeatils;
+
+    }
+
+    @Override
+    public JobDetailData retrieveOne(final Long jobId) {
+        try {
+            final JobDetailMapper detailMapper = new JobDetailMapper();
+            final String sql = detailMapper.schema() + " where job.id=?";
+            return this.jdbcTemplate.queryForObject(sql, detailMapper, new Object[] { jobId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new JobNotFoundException(String.valueOf(jobId));
+        }
+    }
+
+    @Override
+    public Page<JobDetailHistoryData> retrieveJobHistory(final Long jobId, final SearchParameters searchParameters) {
+        if (!isJobExist(jobId)) { throw new JobNotFoundException(String.valueOf(jobId)); }
+        final JobHistoryMapper jobHistoryMapper = new JobHistoryMapper();
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(jobHistoryMapper.schema());
+        sqlBuilder.append(" where job.id=?");
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), new Object[] { jobId },
+                jobHistoryMapper);
+    }
+
+    @Override
+    public boolean isUpdatesAllowed() {
+        final String sql = "select job.display_name from job job where job.currently_running=true and job.updates_allowed=false";
+        final List<String> names = this.jdbcTemplate.queryForList(sql, String.class);
+        if (names != null && names.size() > 0) {
+            final String listVals = names.toString();
+            final String jobNames = listVals.substring(listVals.indexOf("[") + 1, listVals.indexOf("]"));
+            throw new OperationNotAllowedException(jobNames);
+        }
+        return true;
+    }
+
+    private boolean isJobExist(final Long jobId) {
+        boolean isJobPresent = false;
+        final String sql = "select count(*) from job job where job.id=" + jobId;
+        @SuppressWarnings("deprecation")
+        final int count = this.jdbcTemplate.queryForInt(sql);
+        if (count == 1) {
+            isJobPresent = true;
+        }
+        return isJobPresent;
+    }
+
+    private static final class JobDetailMapper implements RowMapper<JobDetailData> {
+
+        private final StringBuilder sqlBuilder = new StringBuilder("select")
+                .append(" job.id,job.display_name as displayName,job.next_run_time as nextRunTime,job.initializing_errorlog as initializingError,job.cron_expression as cronExpression,job.is_active as active,job.currently_running as currentlyRunning,")
+                .append(" runHistory.version,runHistory.start_time as lastRunStartTime,runHistory.end_time as lastRunEndTime,runHistory.`status`,runHistory.error_message as jobRunErrorMessage,runHistory.trigger_type as triggerType,runHistory.error_log as jobRunErrorLog ")
+                .append(" from job job  left join job_run_history runHistory ON job.id=runHistory.job_id and job.previous_run_start_time=runHistory.start_time ");
+
+        public String schema() {
+            return this.sqlBuilder.toString();
+        }
+
+        @Override
+        public JobDetailData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final String displayName = rs.getString("displayName");
+            final Date nextRunTime = rs.getTimestamp("nextRunTime");
+            final String initializingError = rs.getString("initializingError");
+            final String cronExpression = rs.getString("cronExpression");
+            final boolean active = rs.getBoolean("active");
+            final boolean currentlyRunning = rs.getBoolean("currentlyRunning");
+
+            final Long version = rs.getLong("version");
+            final Date jobRunStartTime = rs.getTimestamp("lastRunStartTime");
+            final Date jobRunEndTime = rs.getTimestamp("lastRunEndTime");
+            final String status = rs.getString("status");
+            final String jobRunErrorMessage = rs.getString("jobRunErrorMessage");
+            final String triggerType = rs.getString("triggerType");
+            final String jobRunErrorLog = rs.getString("jobRunErrorLog");
+
+            JobDetailHistoryData lastRunHistory = null;
+            if (version > 0) {
+                lastRunHistory = new JobDetailHistoryData(version, jobRunStartTime, jobRunEndTime, status, jobRunErrorMessage, triggerType,
+                        jobRunErrorLog);
+            }
+            final JobDetailData jobDetail = new JobDetailData(id, displayName, nextRunTime, initializingError, cronExpression, active,
+                    currentlyRunning, lastRunHistory);
+            return jobDetail;
+        }
+
+    }
+
+    private static final class JobHistoryMapper implements RowMapper<JobDetailHistoryData> {
+
+        private final StringBuilder sqlBuilder = new StringBuilder(200)
+                .append(" runHistory.version,runHistory.start_time as runStartTime,runHistory.end_time as runEndTime,runHistory.`status`,runHistory.error_message as jobRunErrorMessage,runHistory.trigger_type as triggerType,runHistory.error_log as jobRunErrorLog ")
+                .append(" from job job join job_run_history runHistory ON job.id=runHistory.job_id");
+
+        public String schema() {
+            return this.sqlBuilder.toString();
+        }
+
+        @Override
+        public JobDetailHistoryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long version = rs.getLong("version");
+            final Date jobRunStartTime = rs.getTimestamp("runStartTime");
+            final Date jobRunEndTime = rs.getTimestamp("runEndTime");
+            final String status = rs.getString("status");
+            final String jobRunErrorMessage = rs.getString("jobRunErrorMessage");
+            final String triggerType = rs.getString("triggerType");
+            final String jobRunErrorLog = rs.getString("jobRunErrorLog");
+            final JobDetailHistoryData jobDetailHistory = new JobDetailHistoryData(version, jobRunStartTime, jobRunEndTime, status,
+                    jobRunErrorMessage, triggerType, jobRunErrorLog);
+            return jobDetailHistory;
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerServiceConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerServiceConstants.java
new file mode 100644
index 0000000..ddc0b0f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerServiceConstants.java
@@ -0,0 +1,41 @@
+/**
+ * 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.jobs.service;
+
+public interface SchedulerServiceConstants {
+
+    public static final String JOB_KEY_SEPERATOR = " _ ";
+    public static final String TRIGGER_TYPE_CRON = "cron";
+    public static final String TRIGGER_TYPE_APPLICATION = "application";
+    public static final String TRIGGER_TYPE_REFERENCE = "TRIGGER_TYPE_REFERENCE";
+    public static final String SCHEDULER_EXCEPTION = "SchedulerException";
+    public static final String JOB_EXECUTION_EXCEPTION = "JobExecutionException";
+    public static final String JOB_METHOD_INVOCATION_FAILED_EXCEPTION = "JobMethodInvocationFailedException";
+    public static final String STATUS_SUCCESS = "success";
+    public static final String STATUS_FAILED = "failed";
+    public static final String DEFAULT_LISTENER_NAME = "Global Listner";
+    public static final int STACK_TRACE_LEVEL = 7;
+    public static final String TENANT_IDENTIFIER = "tenantIdentifier";
+    public static final String SCHEDULER = "Scheduler";
+    public static final String SCHEDULER_GROUP = "group";
+    public static final int DEFAULT_THREAD_COUNT = 7;
+    public static final int GROUP_THREAD_COUNT = 1;
+    public static final String SCHEDULER_NAME = "schedulerName";
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerStopListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerStopListener.java
new file mode 100755
index 0000000..8103eaf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerStopListener.java
@@ -0,0 +1,79 @@
+/**
+ * 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.jobs.service;
+
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.JobListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Global job Listener class to Stop the temporary scheduler once job execution
+ * completes
+ */
+@Component
+public class SchedulerStopListener implements JobListener {
+
+    private static final String name = "Singlr Trigger Global Listner";
+
+    // MIFOSX-1184: This class cannot use constructor injection, because one of
+    // its dependencies (SchedulerStopListener) has a circular dependency to
+    // itself. So, slightly differently from how it's done elsewhere in this
+    // code base, the following fields are not final, and there is no
+    // constructor, but setters.
+
+    private JobRegisterService jobRegisterService;
+
+    @Autowired
+    public void setJobRegisterService(JobRegisterService jobRegisterService) {
+        this.jobRegisterService = jobRegisterService;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void jobToBeExecuted(@SuppressWarnings("unused") final JobExecutionContext context) {
+
+    }
+
+    @Override
+    public void jobExecutionVetoed(@SuppressWarnings("unused") final JobExecutionContext context) {
+
+    }
+
+    @Override
+    public void jobWasExecuted(final JobExecutionContext context, @SuppressWarnings("unused") final JobExecutionException jobException) {
+        final String schedulerName = context.getTrigger().getJobDataMap().getString(SchedulerServiceConstants.SCHEDULER_NAME);
+        if (schedulerName != null) {
+            final Thread newThread = new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    SchedulerStopListener.this.jobRegisterService.stopScheduler(schedulerName);
+                }
+            });
+            newThread.run();
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java
new file mode 100755
index 0000000..90a1e1e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java
@@ -0,0 +1,86 @@
+/**
+ * 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.jobs.service;
+
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobKey;
+import org.quartz.Trigger;
+import org.quartz.Trigger.CompletedExecutionInstruction;
+import org.quartz.TriggerListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SchedulerTriggerListener implements TriggerListener {
+
+    private final String name = "Global trigger Listner";
+
+    private final SchedularWritePlatformService schedularService;
+
+    private final TenantDetailsService tenantDetailsService;
+
+    @Autowired
+    public SchedulerTriggerListener(final SchedularWritePlatformService schedularService, final TenantDetailsService tenantDetailsService) {
+        this.schedularService = schedularService;
+        this.tenantDetailsService = tenantDetailsService;
+
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public void triggerFired(@SuppressWarnings("unused") final Trigger trigger,
+            @SuppressWarnings("unused") final JobExecutionContext context) {
+
+    }
+
+    @Override
+    public boolean vetoJobExecution(final Trigger trigger, final JobExecutionContext context) {
+
+        final String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
+        final FineractPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier);
+        ThreadLocalContextUtil.setTenant(tenant);
+        final JobKey key = trigger.getJobKey();
+        final String jobKey = key.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + key.getGroup();
+        String triggerType = SchedulerServiceConstants.TRIGGER_TYPE_CRON;
+        if (context.getMergedJobDataMap().containsKey(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE)) {
+            triggerType = context.getMergedJobDataMap().getString(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE);
+        }
+        return this.schedularService.processJobDetailForExecution(jobKey, triggerType);
+    }
+
+    @Override
+    public void triggerMisfired(@SuppressWarnings("unused") final Trigger trigger) {
+
+    }
+
+    @Override
+    public void triggerComplete(@SuppressWarnings("unused") final Trigger trigger,
+            @SuppressWarnings("unused") final JobExecutionContext context,
+            @SuppressWarnings("unused") final CompletedExecutionInstruction triggerInstructionCode) {
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/annotation/ReportService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/annotation/ReportService.java
new file mode 100644
index 0000000..e6c2d54
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/annotation/ReportService.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.infrastructure.report.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface ReportService {
+
+    /**
+     * @return the type of the report
+     */
+    String type();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java
new file mode 100644
index 0000000..8d1c0fe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java
@@ -0,0 +1,79 @@
+/**
+ * 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.report.provider;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.fineract.infrastructure.report.annotation.ReportService;
+import org.apache.fineract.infrastructure.report.service.ReportingProcessService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Component
+@Scope("singleton")
+public class ReportingProcessServiceProvider implements ApplicationContextAware {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ReportingProcessServiceProvider.class);
+
+    private ApplicationContext applicationContext;
+
+    Map<String, String> reportingProcessServices = null;
+
+    ReportingProcessServiceProvider() {
+        super();
+    }
+
+    public ReportingProcessService findReportingProcessService(final String reportType) {
+        if (this.reportingProcessServices.containsKey(reportType)) { return (ReportingProcessService) this.applicationContext
+                .getBean(this.reportingProcessServices.get(reportType)); }
+        return null;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.applicationContext = applicationContext;
+        this.initializeRegistry();
+    }
+
+    private void initializeRegistry() {
+        if (this.reportingProcessServices == null) {
+            this.reportingProcessServices = new HashMap<>();
+
+            final String[] reportServiceBeans = this.applicationContext.getBeanNamesForAnnotation(ReportService.class);
+            if (ArrayUtils.isNotEmpty(reportServiceBeans)) {
+                for (final String reportName : reportServiceBeans) {
+                    LOGGER.info("Register report service '" + reportName + "' ...");
+                    final ReportService service = this.applicationContext.findAnnotationOnBean(reportName, ReportService.class);
+                    try {
+                        this.reportingProcessServices.put(service.type(), reportName);
+                    } catch (final Throwable th) {
+                        LOGGER.error("Unable to register reporting service '" + reportName + "'!", th);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java
new file mode 100644
index 0000000..87c11b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.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.report.service;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+public interface ReportingProcessService {
+
+    Response processRequest(String reportName, MultivaluedMap<String, String> queryParams);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java
new file mode 100644
index 0000000..1638359
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java
@@ -0,0 +1,115 @@
+/**
+ * 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.security.api;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.data.AuthenticatedUserData;
+import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext;
+import org.apache.fineract.useradministration.data.RoleData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.Role;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.Scope;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.stereotype.Component;
+
+import com.sun.jersey.core.util.Base64;
+
+@Path("/authentication")
+@Component
+@Profile("basicauth")
+@Scope("singleton")
+public class AuthenticationApiResource {
+
+    private final DaoAuthenticationProvider customAuthenticationProvider;
+    private final ToApiJsonSerializer<AuthenticatedUserData> apiJsonSerializerService;
+    private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext;
+
+    @Autowired
+    public AuthenticationApiResource(
+            @Qualifier("customAuthenticationProvider") final DaoAuthenticationProvider customAuthenticationProvider,
+            final ToApiJsonSerializer<AuthenticatedUserData> apiJsonSerializerService,
+            final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext) {
+        this.customAuthenticationProvider = customAuthenticationProvider;
+        this.apiJsonSerializerService = apiJsonSerializerService;
+        this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext;
+    }
+
+    @POST
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String authenticate(@QueryParam("username") final String username, @QueryParam("password") final String password) {
+
+        final Authentication authentication = new UsernamePasswordAuthenticationToken(username, password);
+        final Authentication authenticationCheck = this.customAuthenticationProvider.authenticate(authentication);
+
+        final Collection<String> permissions = new ArrayList<>();
+        AuthenticatedUserData authenticatedUserData = new AuthenticatedUserData(username, permissions);
+
+        if (authenticationCheck.isAuthenticated()) {
+            final Collection<GrantedAuthority> authorities = new ArrayList<>(authenticationCheck.getAuthorities());
+            for (final GrantedAuthority grantedAuthority : authorities) {
+                permissions.add(grantedAuthority.getAuthority());
+            }
+
+            final byte[] base64EncodedAuthenticationKey = Base64.encode(username + ":" + password);
+
+            final AppUser principal = (AppUser) authenticationCheck.getPrincipal();
+            final Collection<RoleData> roles = new ArrayList<>();
+            final Set<Role> userRoles = principal.getRoles();
+            for (final Role role : userRoles) {
+                roles.add(role.toData());
+            }
+
+            final Long officeId = principal.getOffice().getId();
+            final String officeName = principal.getOffice().getName();
+
+            final Long staffId = principal.getStaffId();
+            final String staffDisplayName = principal.getStaffDisplayName();
+
+            final EnumOptionData organisationalRole = principal.organisationalRoleData();
+
+            if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
+                authenticatedUserData = new AuthenticatedUserData(username, principal.getId(), new String(base64EncodedAuthenticationKey));
+            } else {
+
+                authenticatedUserData = new AuthenticatedUserData(username, officeId, officeName, staffId, staffDisplayName,
+                        organisationalRole, roles, permissions, principal.getId(), new String(base64EncodedAuthenticationKey));
+            }
+
+        }
+
+        return this.apiJsonSerializerService.serialize(authenticatedUserData);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java
new file mode 100644
index 0000000..67f0616
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java
@@ -0,0 +1,111 @@
+/**
+ * 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.security.api;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.data.AuthenticatedOauthUserData;
+import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext;
+import org.apache.fineract.useradministration.data.RoleData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.Role;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.Scope;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
+import org.springframework.stereotype.Component;
+
+/*
+ * Implementation of Oauth2 authentication APIs, loaded only when "oauth" profile is enabled. 
+ */
+@Path("/userdetails")
+@Component
+@Profile("oauth")
+@Scope("singleton")
+public class UserDetailsApiResource {
+
+    private final ResourceServerTokenServices tokenServices;
+    private final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService;
+    private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext;
+
+    @Autowired
+    public UserDetailsApiResource(@Qualifier("tokenServices") final ResourceServerTokenServices tokenServices,
+            final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService,
+            final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext) {
+        this.tokenServices = tokenServices;
+        this.apiJsonSerializerService = apiJsonSerializerService;
+        this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext;
+    }
+
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String fetchAuthenticatedUserData(@QueryParam("access_token") final String accessToken) {
+
+        final Authentication authentication = this.tokenServices.loadAuthentication(accessToken);
+        if (authentication.isAuthenticated()) {
+            final AppUser principal = (AppUser) authentication.getPrincipal();
+
+            final Collection<String> permissions = new ArrayList<>();
+            AuthenticatedOauthUserData authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), permissions);
+
+            final Collection<GrantedAuthority> authorities = new ArrayList<>(authentication.getAuthorities());
+            for (final GrantedAuthority grantedAuthority : authorities) {
+                permissions.add(grantedAuthority.getAuthority());
+            }
+
+            final Collection<RoleData> roles = new ArrayList<>();
+            final Set<Role> userRoles = principal.getRoles();
+            for (final Role role : userRoles) {
+                roles.add(role.toData());
+            }
+
+            final Long officeId = principal.getOffice().getId();
+            final String officeName = principal.getOffice().getName();
+
+            final Long staffId = principal.getStaffId();
+            final String staffDisplayName = principal.getStaffDisplayName();
+
+            final EnumOptionData organisationalRole = principal.organisationalRoleData();
+
+            if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
+                authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), principal.getId(), accessToken);
+            } else {
+
+                authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), officeId, officeName, staffId, staffDisplayName,
+                        organisationalRole, roles, permissions, principal.getId(), accessToken);
+            }
+            return this.apiJsonSerializerService.serialize(authenticatedUserData);
+        }
+        return null;
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java
new file mode 100644
index 0000000..830fc67
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java
@@ -0,0 +1,103 @@
+/**
+ * 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.security.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.useradministration.data.RoleData;
+
+/**
+ * Immutable data object for authentication. Used in case of Oauth2.
+ */
+public class AuthenticatedOauthUserData {
+
+    @SuppressWarnings("unused")
+    private final String username;
+    @SuppressWarnings("unused")
+    private final Long userId;
+    @SuppressWarnings("unused")
+    private final String accessToken;
+    @SuppressWarnings("unused")
+    private final boolean authenticated;
+    @SuppressWarnings("unused")
+    private final Long officeId;
+    @SuppressWarnings("unused")
+    private final String officeName;
+    @SuppressWarnings("unused")
+    private final Long staffId;
+    @SuppressWarnings("unused")
+    private final String staffDisplayName;
+    @SuppressWarnings("unused")
+    private final EnumOptionData organisationalRole;
+    @SuppressWarnings("unused")
+    private final Collection<RoleData> roles;
+    @SuppressWarnings("unused")
+    private final Collection<String> permissions;
+
+    @SuppressWarnings("unused")
+    private final boolean shouldRenewPassword;
+
+    public AuthenticatedOauthUserData(final String username, final Collection<String> permissions) {
+        this.username = username;
+        this.userId = null;
+        this.accessToken = null;
+        this.authenticated = false;
+        this.officeId = null;
+        this.officeName = null;
+        this.staffId = null;
+        this.staffDisplayName = null;
+        this.organisationalRole = null;
+        this.roles = null;
+        this.permissions = permissions;
+        this.shouldRenewPassword = false;
+    }
+
+    public AuthenticatedOauthUserData(final String username, final Long officeId, final String officeName, final Long staffId,
+            final String staffDisplayName, final EnumOptionData organisationalRole, final Collection<RoleData> roles,
+            final Collection<String> permissions, final Long userId, final String accessToken) {
+        this.username = username;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.staffId = staffId;
+        this.staffDisplayName = staffDisplayName;
+        this.organisationalRole = organisationalRole;
+        this.userId = userId;
+        this.accessToken = accessToken;
+        this.authenticated = true;
+        this.roles = roles;
+        this.permissions = permissions;
+        this.shouldRenewPassword = false;
+    }
+
+    public AuthenticatedOauthUserData(final String username, final Long userId, final String accessToken) {
+        this.username = username;
+        this.officeId = null;
+        this.officeName = null;
+        this.staffId = null;
+        this.staffDisplayName = null;
+        this.organisationalRole = null;
+        this.userId = userId;
+        this.accessToken = accessToken;
+        this.authenticated = true;
+        this.roles = null;
+        this.permissions = null;
+        this.shouldRenewPassword = true;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java
new file mode 100644
index 0000000..513f5df
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java
@@ -0,0 +1,103 @@
+/**
+ * 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.security.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.useradministration.data.RoleData;
+
+/**
+ * Immutable data object for authentication.
+ */
+public class AuthenticatedUserData {
+
+    @SuppressWarnings("unused")
+    private final String username;
+    @SuppressWarnings("unused")
+    private final Long userId;
+    @SuppressWarnings("unused")
+    private final String base64EncodedAuthenticationKey;
+    @SuppressWarnings("unused")
+    private final boolean authenticated;
+    @SuppressWarnings("unused")
+    private final Long officeId;
+    @SuppressWarnings("unused")
+    private final String officeName;
+    @SuppressWarnings("unused")
+    private final Long staffId;
+    @SuppressWarnings("unused")
+    private final String staffDisplayName;
+    @SuppressWarnings("unused")
+    private final EnumOptionData organisationalRole;
+    @SuppressWarnings("unused")
+    private final Collection<RoleData> roles;
+    @SuppressWarnings("unused")
+    private final Collection<String> permissions;
+
+    @SuppressWarnings("unused")
+    private final boolean shouldRenewPassword;
+
+    public AuthenticatedUserData(final String username, final Collection<String> permissions) {
+        this.username = username;
+        this.userId = null;
+        this.base64EncodedAuthenticationKey = null;
+        this.authenticated = false;
+        this.officeId = null;
+        this.officeName = null;
+        this.staffId = null;
+        this.staffDisplayName = null;
+        this.organisationalRole = null;
+        this.roles = null;
+        this.permissions = permissions;
+        this.shouldRenewPassword = false;
+    }
+
+    public AuthenticatedUserData(final String username, final Long officeId, final String officeName, final Long staffId,
+            final String staffDisplayName, final EnumOptionData organisationalRole, final Collection<RoleData> roles,
+            final Collection<String> permissions, final Long userId, final String base64EncodedAuthenticationKey) {
+        this.username = username;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.staffId = staffId;
+        this.staffDisplayName = staffDisplayName;
+        this.organisationalRole = organisationalRole;
+        this.userId = userId;
+        this.base64EncodedAuthenticationKey = base64EncodedAuthenticationKey;
+        this.authenticated = true;
+        this.roles = roles;
+        this.permissions = permissions;
+        this.shouldRenewPassword = false;
+    }
+
+    public AuthenticatedUserData(final String username, final Long userId, final String base64EncodedAuthenticationKey) {
+        this.username = username;
+        this.officeId = null;
+        this.officeName = null;
+        this.staffId = null;
+        this.staffDisplayName = null;
+        this.organisationalRole = null;
+        this.userId = userId;
+        this.base64EncodedAuthenticationKey = base64EncodedAuthenticationKey;
+        this.authenticated = true;
+        this.roles = null;
+        this.permissions = null;
+        this.shouldRenewPassword = true;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/PlatformRequestLog.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/PlatformRequestLog.java
new file mode 100644
index 0000000..bf5b921
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/PlatformRequestLog.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.infrastructure.security.data;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang.time.StopWatch;
+
+/**
+ * Immutable data object representing platform API request used for
+ * logging/debugging.
+ */
+public class PlatformRequestLog {
+
+    @SuppressWarnings("unused")
+    private final long startTime;
+    @SuppressWarnings("unused")
+    private final long totalTime;
+    @SuppressWarnings("unused")
+    private final String method;
+    @SuppressWarnings("unused")
+    private final String url;
+    @SuppressWarnings("unused")
+    private final Map<String, String[]> parameters;
+
+    public static PlatformRequestLog from(final StopWatch task, final HttpServletRequest request) throws IOException {
+        final String requestUrl = request.getRequestURL().toString();
+
+        final Map<String, String[]> parameters = new HashMap<>(request.getParameterMap());
+        parameters.remove("password");
+        parameters.remove("_");
+
+        return new PlatformRequestLog(task.getStartTime(), task.getTime(), request.getMethod(), requestUrl, parameters);
+    }
+
+    private PlatformRequestLog(final long startTime, final long time, final String method, final String requestUrl,
+            final Map<String, String[]> parameters) {
+        this.startTime = startTime;
+        this.totalTime = time;
+        this.method = method;
+        this.url = requestUrl;
+        this.parameters = parameters;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/BasicPasswordEncodablePlatformUser.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/BasicPasswordEncodablePlatformUser.java
new file mode 100644
index 0000000..c9a646f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/BasicPasswordEncodablePlatformUser.java
@@ -0,0 +1,75 @@
+/**
+ * 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.security.domain;
+
+import java.util.Collection;
+
+import org.springframework.security.core.GrantedAuthority;
+
+public class BasicPasswordEncodablePlatformUser implements PlatformUser {
+
+    private final Long id;
+    private final String username;
+    private final String password;
+
+    public BasicPasswordEncodablePlatformUser(final Long id, final String username, final String password) {
+        this.id = id;
+        this.username = username;
+        this.password = password;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    @Override
+    public String getPassword() {
+        return this.password;
+    }
+
+    @Override
+    public String getUsername() {
+        return this.username;
+    }
+
+    @Override
+    public Collection<GrantedAuthority> getAuthorities() {
+        return null;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return false;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return false;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return false;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/PlatformUser.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/PlatformUser.java
new file mode 100644
index 0000000..f6928f9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/PlatformUser.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.security.domain;
+
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * Interface to protect platform from implementation detail of spring security.
+ */
+public interface PlatformUser extends UserDetails {
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/PlatformUserRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/PlatformUserRepository.java
new file mode 100644
index 0000000..0afa9f3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/PlatformUserRepository.java
@@ -0,0 +1,25 @@
+/**
+ * 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.security.domain;
+
+public interface PlatformUserRepository {
+
+    PlatformUser findByUsernameAndDeletedAndEnabled(String username, boolean deleted, boolean enabled);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/ForcePasswordResetException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/ForcePasswordResetException.java
new file mode 100644
index 0000000..14be60d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/ForcePasswordResetException.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.infrastructure.security.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ForcePasswordResetException extends AbstractPlatformDomainRuleException {
+	
+	public ForcePasswordResetException(){
+		super("error.msg.password.reset.days.value.must.be.greater.than.zero" , "For enabling 'Force Password Reset Days' configuration , the value (number of days after which a user is forced to reset his password) must be set to a number greater than 0.");
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidTenantIdentiferException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidTenantIdentiferException.java
new file mode 100644
index 0000000..68b1613
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidTenantIdentiferException.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.infrastructure.security.exception;
+
+/**
+ * {@link RuntimeException} thrown when an invalid tenant identifier is used in
+ * request to platform.
+ * 
+ * @see CustomRequestHeaderAuthenticationFilter
+ */
+public class InvalidTenantIdentiferException extends RuntimeException {
+
+    public InvalidTenantIdentiferException(final String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/NoAuthorizationException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/NoAuthorizationException.java
new file mode 100644
index 0000000..ff6ab94
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/NoAuthorizationException.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.exception;
+
+/**
+ * A {@link RuntimeException} that is thrown in the case where a user does not
+ * have sufficient authorization to execute operation on platform.
+ */
+public class NoAuthorizationException extends RuntimeException {
+
+    public NoAuthorizationException(final String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/ResetPasswordException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/ResetPasswordException.java
new file mode 100644
index 0000000..9e1ee63
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/ResetPasswordException.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.infrastructure.security.exception;
+
+import java.util.ArrayList;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+
+/**
+ * A {@link RuntimeException} that is thrown in the case where a user does not
+ * have sufficient authorization to execute operation on platform.
+ */
+public class ResetPasswordException extends PlatformApiDataValidationException {
+
+    public ResetPasswordException(final Long userId) {
+
+        super("error.msg.password.outdated", "The password of the user with id " + userId + " has expired, please reset it",
+                new ArrayList<ApiParameterError>() {
+
+                    {
+                        add(ApiParameterError.parameterError("error.msg.password.outdated", "The password of the user with id " + userId
+                                + " has expired, please reset it", "userId", userId));
+
+                    }
+                }
+
+        );
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java
new file mode 100644
index 0000000..abe243e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java
@@ -0,0 +1,181 @@
+/**
+ * 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.security.filter;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.time.StopWatch;
+import org.apache.fineract.infrastructure.cache.domain.CacheType;
+import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
+import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentiferException;
+import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.stereotype.Service;
+
+/**
+ * A customised version of spring security's {@link BasicAuthenticationFilter}.
+ * 
+ * This filter is responsible for extracting multi-tenant and basic auth
+ * credentials from the request and checking that the details provided are
+ * valid.
+ * 
+ * If multi-tenant and basic auth credentials are valid, the details of the
+ * tenant are stored in {@link FineractPlatformTenant} and stored in a
+ * {@link ThreadLocal} variable for this request using
+ * {@link ThreadLocalContextUtil}.
+ * 
+ * If multi-tenant and basic auth credentials are invalid, a http error response
+ * is returned.
+ */
+@Service(value = "basicAuthenticationProcessingFilter")
+@Profile("basicauth")
+public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter {
+
+    private static boolean firstRequestProcessed = false;
+    private final static Logger logger = LoggerFactory.getLogger(TenantAwareBasicAuthenticationFilter.class);
+
+    private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
+    private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
+    private final ConfigurationDomainService configurationDomainService;
+    private final CacheWritePlatformService cacheWritePlatformService;
+
+    private final String tenantRequestHeader = "Fineract-Platform-TenantId";
+    private final boolean exceptionIfHeaderMissing = true;
+
+    @Autowired
+    public TenantAwareBasicAuthenticationFilter(final AuthenticationManager authenticationManager,
+            final AuthenticationEntryPoint authenticationEntryPoint, final BasicAuthTenantDetailsService basicAuthTenantDetailsService,
+            final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer, final ConfigurationDomainService configurationDomainService,
+            final CacheWritePlatformService cacheWritePlatformService) {
+        super(authenticationManager, authenticationEntryPoint);
+        this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.configurationDomainService = configurationDomainService;
+        this.cacheWritePlatformService = cacheWritePlatformService;
+    }
+
+    @Override
+    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
+
+        final HttpServletRequest request = (HttpServletRequest) req;
+        final HttpServletResponse response = (HttpServletResponse) res;
+
+        final StopWatch task = new StopWatch();
+        task.start();
+
+        try {
+
+            if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
+                // ignore to allow 'preflight' requests from AJAX applications
+                // in different origin (domain name)
+            } else {
+
+                String tenantIdentifier = request.getHeader(this.tenantRequestHeader);
+
+                if (org.apache.commons.lang.StringUtils.isBlank(tenantIdentifier)) {
+                    tenantIdentifier = request.getParameter("tenantIdentifier");
+                }
+
+                if (tenantIdentifier == null && this.exceptionIfHeaderMissing) { throw new InvalidTenantIdentiferException(
+                        "No tenant identifier found: Add request header of '" + this.tenantRequestHeader
+                                + "' or add the parameter 'tenantIdentifier' to query string of request URL."); }
+
+                String pathInfo = request.getRequestURI();
+                boolean isReportRequest = false;
+                if (pathInfo != null && pathInfo.contains("report")) {
+                    isReportRequest = true;
+                }
+                final FineractPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(tenantIdentifier, isReportRequest);
+
+                ThreadLocalContextUtil.setTenant(tenant);
+                String authToken = request.getHeader("Authorization");
+
+                if (authToken != null && authToken.startsWith("Basic ")) {
+                    ThreadLocalContextUtil.setAuthToken(authToken.replaceFirst("Basic ", ""));
+                }
+
+                if (!firstRequestProcessed) {
+                    final String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "/");
+                    System.setProperty("baseUrl", baseUrl);
+
+                    final boolean ehcacheEnabled = this.configurationDomainService.isEhcacheEnabled();
+                    if (ehcacheEnabled) {
+                        this.cacheWritePlatformService.switchToCache(CacheType.SINGLE_NODE);
+                    } else {
+                        this.cacheWritePlatformService.switchToCache(CacheType.NO_CACHE);
+                    }
+                    TenantAwareBasicAuthenticationFilter.firstRequestProcessed = true;
+                }
+            }
+
+            super.doFilter(req, res, chain);
+        } catch (final InvalidTenantIdentiferException e) {
+            // deal with exception at low level
+            SecurityContextHolder.getContext().setAuthentication(null);
+
+            response.addHeader("WWW-Authenticate", "Basic realm=\"" + "Fineract Platform API" + "\"");
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
+        } finally {
+            task.stop();
+            final PlatformRequestLog log = PlatformRequestLog.from(task, request);
+            logger.info(this.toApiJsonSerializer.serialize(log));
+        }
+    }
+    
+    @Override
+    protected void onSuccessfulAuthentication(HttpServletRequest request,
+    		HttpServletResponse response, Authentication authResult)
+    		throws IOException {
+    	super.onSuccessfulAuthentication(request, response, authResult);
+		AppUser user = (AppUser) authResult.getPrincipal();
+		
+		String pathURL = request.getRequestURI();
+		boolean isSelfServiceRequest = (pathURL != null && pathURL.contains("/self/"));
+
+		boolean notAllowed = ((isSelfServiceRequest && !user.isSelfServiceUser())
+				||(!isSelfServiceRequest && user.isSelfServiceUser()));
+		
+		if(notAllowed){
+			throw new BadCredentialsException("User not authorised to use the requested resource.");
+		}
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java
new file mode 100644
index 0000000..896f697
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.filter;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.time.StopWatch;
+import org.apache.fineract.infrastructure.cache.domain.CacheType;
+import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
+import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentiferException;
+import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.filter.GenericFilterBean;
+
+/**
+ * 
+ * This filter is responsible for extracting multi-tenant from the request and
+ * setting Cross-Origin details to response. 
+ * 
+ * If multi-tenant are valid, the details of the tenant are stored in
+ * {@link FineractPlatformTenant} and stored in a {@link ThreadLocal} variable for
+ * this request using {@link ThreadLocalContextUtil}.
+ * 
+ * If multi-tenant are invalid, a http error response is returned.
+ * 
+ * Used to support Oauth2 authentication and the service is loaded only when "oauth" profile is active.
+ */
+@Service(value = "tenantIdentifierProcessingFilter")
+@Profile("oauth")
+public class TenantAwareTenantIdentifierFilter extends GenericFilterBean {
+
+    private static boolean firstRequestProcessed = false;
+    private final static Logger logger = LoggerFactory.getLogger(TenantAwareTenantIdentifierFilter.class);
+
+    private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
+    private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
+    private final ConfigurationDomainService configurationDomainService;
+    private final CacheWritePlatformService cacheWritePlatformService;
+
+    private final String tenantRequestHeader = "Fineract-Platform-TenantId";
+    private final boolean exceptionIfHeaderMissing = true;
+    private final String apiUri = "/api/v1/";
+
+    @Autowired
+    public TenantAwareTenantIdentifierFilter(final BasicAuthTenantDetailsService basicAuthTenantDetailsService,
+            final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer, final ConfigurationDomainService configurationDomainService,
+            final CacheWritePlatformService cacheWritePlatformService) {
+        this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.configurationDomainService = configurationDomainService;
+        this.cacheWritePlatformService = cacheWritePlatformService;
+    }
+
+    @Override
+    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
+
+        final HttpServletRequest request = (HttpServletRequest) req;
+        final HttpServletResponse response = (HttpServletResponse) res;
+
+        final StopWatch task = new StopWatch();
+        task.start();
+
+        try {
+
+            // allows for Cross-Origin
+            // Requests (CORs) to be performed against the platform API.
+            response.setHeader("Access-Control-Allow-Origin", "*");
+            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
+            final String reqHead = request.getHeader("Access-Control-Request-Headers");
+
+            if (null != reqHead && !reqHead.isEmpty()) {
+                response.setHeader("Access-Control-Allow-Headers", reqHead);
+            }
+
+            if (!"OPTIONS".equalsIgnoreCase(request.getMethod())) {
+
+                String tenantIdentifier = request.getHeader(this.tenantRequestHeader);
+                if (org.apache.commons.lang.StringUtils.isBlank(tenantIdentifier)) {
+                    tenantIdentifier = request.getParameter("tenantIdentifier");
+                }
+
+                if (tenantIdentifier == null && this.exceptionIfHeaderMissing) { throw new InvalidTenantIdentiferException(
+                        "No tenant identifier found: Add request header of '" + this.tenantRequestHeader
+                                + "' or add the parameter 'tenantIdentifier' to query string of request URL."); }
+
+                String pathInfo = request.getRequestURI();
+                boolean isReportRequest = false;
+                if (pathInfo != null && pathInfo.contains("report")) {
+                    isReportRequest = true;
+                }
+                final FineractPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(tenantIdentifier, isReportRequest);
+
+                ThreadLocalContextUtil.setTenant(tenant);
+                String authToken = request.getHeader("Authorization");
+
+                if (authToken != null && authToken.startsWith("bearer ")) {
+                    ThreadLocalContextUtil.setAuthToken(authToken.replaceFirst("bearer ", ""));
+                }
+
+                if (!firstRequestProcessed) {
+                    final String baseUrl = request.getRequestURL().toString()
+                            .replace(request.getRequestURI(), request.getContextPath() + apiUri);
+                    System.setProperty("baseUrl", baseUrl);
+
+                    final boolean ehcacheEnabled = this.configurationDomainService.isEhcacheEnabled();
+                    if (ehcacheEnabled) {
+                        this.cacheWritePlatformService.switchToCache(CacheType.SINGLE_NODE);
+                    } else {
+                        this.cacheWritePlatformService.switchToCache(CacheType.NO_CACHE);
+                    }
+                    TenantAwareTenantIdentifierFilter.firstRequestProcessed = true;
+                }
+                chain.doFilter(request, response);
+            }
+        } catch (final InvalidTenantIdentiferException e) {
+            // deal with exception at low level
+            SecurityContextHolder.getContext().setAuthentication(null);
+
+            response.addHeader("WWW-Authenticate", "Basic realm=\"" + "Fineract Platform API" + "\"");
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
+        } finally {
+            task.stop();
+            final PlatformRequestLog log = PlatformRequestLog.from(task, request);
+            logger.info(this.toApiJsonSerializer.serialize(log));
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/BasicAuthTenantDetailsService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/BasicAuthTenantDetailsService.java
new file mode 100644
index 0000000..11eef22
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/BasicAuthTenantDetailsService.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.infrastructure.security.service;
+
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+
+public interface BasicAuthTenantDetailsService {
+
+    FineractPlatformTenant loadTenantById(String tenantId, boolean isReport);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/BasicAuthTenantDetailsServiceJdbc.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/BasicAuthTenantDetailsServiceJdbc.java
new file mode 100644
index 0000000..922d0fb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/BasicAuthTenantDetailsServiceJdbc.java
@@ -0,0 +1,146 @@
+/**
+ * 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.security.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
+import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentiferException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+/**
+ * A JDBC implementation of {@link BasicAuthTenantDetailsService} for loading a
+ * tenants details by a <code>tenantIdentifier</code>.
+ */
+@Service
+public class BasicAuthTenantDetailsServiceJdbc implements BasicAuthTenantDetailsService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public BasicAuthTenantDetailsServiceJdbc(@Qualifier("tenantDataSourceJndi") final DataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class TenantMapper implements RowMapper<FineractPlatformTenant> {
+
+        private final boolean isReport;
+        private final StringBuilder sqlBuilder = new StringBuilder(" t.id, ts.id as connectionId , ")//
+                .append(" t.timezone_id as timezoneId , t.name,t.identifier, ts.schema_name as schemaName, ts.schema_server as schemaServer,")//
+                .append(" ts.schema_server_port as schemaServerPort, ts.auto_update as autoUpdate,")//
+                .append(" ts.schema_username as schemaUsername, ts.schema_password as schemaPassword , ts.pool_initial_size as initialSize,")//
+                .append(" ts.pool_validation_interval as validationInterval, ts.pool_remove_abandoned as removeAbandoned, ts.pool_remove_abandoned_timeout as removeAbandonedTimeout,")//
+                .append(" ts.pool_log_abandoned as logAbandoned, ts.pool_abandon_when_percentage_full as abandonedWhenPercentageFull, ts.pool_test_on_borrow as testOnBorrow,")//
+                .append(" ts.pool_max_active as poolMaxActive, ts.pool_min_idle as poolMinIdle, ts.pool_max_idle as poolMaxIdle,")//
+                .append(" ts.pool_suspect_timeout as poolSuspectTimeout, ts.pool_time_between_eviction_runs_millis as poolTimeBetweenEvictionRunsMillis,")//
+                .append(" ts.pool_min_evictable_idle_time_millis as poolMinEvictableIdleTimeMillis,")//
+                .append(" ts.deadlock_max_retries as maxRetriesOnDeadlock,")//
+                .append(" ts.deadlock_max_retry_interval as maxIntervalBetweenRetries ")//
+                .append(" from tenants t left join tenant_server_connections ts ");
+
+        public TenantMapper(boolean isReport) {
+            this.isReport = isReport;
+        }
+
+        public String schema() {
+            if(this.isReport){
+                this.sqlBuilder.append(" on t.report_Id = ts.id");
+            }else{
+                this.sqlBuilder.append(" on t.oltp_Id = ts.id");
+            }
+            return this.sqlBuilder.toString();
+        }
+
+        @Override
+        public FineractPlatformTenant mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final String tenantIdentifier = rs.getString("identifier");
+            final String name = rs.getString("name");
+            final String timezoneId = rs.getString("timezoneId");
+            final FineractPlatformTenantConnection connection = getDBConnection(rs);
+            return new FineractPlatformTenant(id, tenantIdentifier, name, timezoneId, connection);
+        }
+
+        // gets the DB connection
+        private FineractPlatformTenantConnection getDBConnection(ResultSet rs) throws SQLException {
+
+            final Long connectionId = rs.getLong("connectionId");
+            final String schemaName = rs.getString("schemaName");
+            final String schemaServer = rs.getString("schemaServer");
+            final String schemaServerPort = rs.getString("schemaServerPort");
+            final String schemaUsername = rs.getString("schemaUsername");
+            final String schemaPassword = rs.getString("schemaPassword");
+            final boolean autoUpdateEnabled = rs.getBoolean("autoUpdate");
+            final int initialSize = rs.getInt("initialSize");
+            final boolean testOnBorrow = rs.getBoolean("testOnBorrow");
+            final long validationInterval = rs.getLong("validationInterval");
+            final boolean removeAbandoned = rs.getBoolean("removeAbandoned");
+            final int removeAbandonedTimeout = rs.getInt("removeAbandonedTimeout");
+            final boolean logAbandoned = rs.getBoolean("logAbandoned");
+            final int abandonWhenPercentageFull = rs.getInt("abandonedWhenPercentageFull");
+            final int maxActive = rs.getInt("poolMaxActive");
+            final int minIdle = rs.getInt("poolMinIdle");
+            final int maxIdle = rs.getInt("poolMaxIdle");
+            final int suspectTimeout = rs.getInt("poolSuspectTimeout");
+            final int timeBetweenEvictionRunsMillis = rs.getInt("poolTimeBetweenEvictionRunsMillis");
+            final int minEvictableIdleTimeMillis = rs.getInt("poolMinEvictableIdleTimeMillis");
+            int maxRetriesOnDeadlock = rs.getInt("maxRetriesOnDeadlock");
+            int maxIntervalBetweenRetries = rs.getInt("maxIntervalBetweenRetries");
+
+            maxRetriesOnDeadlock = bindValueInMinMaxRange(maxRetriesOnDeadlock, 0, 15);
+            maxIntervalBetweenRetries = bindValueInMinMaxRange(maxIntervalBetweenRetries, 1, 15);
+
+            return new FineractPlatformTenantConnection(connectionId, schemaName, schemaServer, schemaServerPort, schemaUsername,
+                    schemaPassword, autoUpdateEnabled, initialSize, validationInterval, removeAbandoned, removeAbandonedTimeout,
+                    logAbandoned, abandonWhenPercentageFull, maxActive, minIdle, maxIdle, suspectTimeout, timeBetweenEvictionRunsMillis,
+                    minEvictableIdleTimeMillis, maxRetriesOnDeadlock, maxIntervalBetweenRetries, testOnBorrow);
+        }
+
+        private int bindValueInMinMaxRange(final int value, int min, int max) {
+            if (value < min) {
+                return min;
+            } else if (value > max) { return max; }
+            return value;
+        }
+    }
+
+    @Override
+    @Cacheable(value = "tenantsById")
+    public FineractPlatformTenant loadTenantById(final String tenantIdentifier, final boolean isReport) {
+
+        try {
+            final TenantMapper rm = new TenantMapper(isReport);
+            final String sql = "select  " + rm.schema() + " where t.identifier like ?";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { tenantIdentifier });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new InvalidTenantIdentiferException("The tenant identifier: " + tenantIdentifier + " is not valid.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/CustomAuthenticationFailureHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/CustomAuthenticationFailureHandler.java
new file mode 100644
index 0000000..3f7a821
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/CustomAuthenticationFailureHandler.java
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.service;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.DefaultRedirectStrategy;
+import org.springframework.security.web.RedirectStrategy;
+import org.springframework.security.web.WebAttributes;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.util.UrlUtils;
+import org.springframework.util.Assert;
+
+public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
+
+    protected final Log logger = LogFactory.getLog(getClass());
+
+    private String defaultFailureUrl;
+    private boolean forwardToDestination = false;
+    private boolean allowSessionCreation = true;
+    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+
+    public CustomAuthenticationFailureHandler() {}
+
+    /**
+     * Performs the redirect or forward to the {@code defaultFailureUrl} if set,
+     * otherwise returns a 401 error code.
+     * <p>
+     * If redirecting or forwarding, {@code saveException} will be called to
+     * cache the exception for use in the target view.
+     */
+    @Override
+    public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response,
+            final AuthenticationException exception) throws IOException, ServletException {
+
+        if (this.defaultFailureUrl == null) {
+            this.logger.debug("No failure URL set, sending 401 Unauthorized error");
+
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed: " + exception.getMessage());
+        } else {
+            saveException(request, exception);
+
+            if (this.forwardToDestination) {
+                this.logger.debug("Forwarding to " + this.defaultFailureUrl);
+
+                request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
+            } else {
+                this.logger.debug("Redirecting to " + this.defaultFailureUrl);
+
+                final String oauthToken = request.getParameter("oauth_token");
+                request.setAttribute("oauth_token", oauthToken);
+                final String url = this.defaultFailureUrl + "?oauth_token=" + oauthToken;
+                this.redirectStrategy.sendRedirect(request, response, url);
+            }
+        }
+    }
+
+    /**
+     * Caches the {@code AuthenticationException} for use in view rendering.
+     * <p>
+     * If {@code forwardToDestination} is set to true, request scope will be
+     * used, otherwise it will attempt to store the exception in the session. If
+     * there is no session and {@code allowSessionCreation} is {@code true} a
+     * session will be created. Otherwise the exception will not be stored.
+     */
+    protected final void saveException(final HttpServletRequest request, final AuthenticationException exception) {
+        if (this.forwardToDestination) {
+            request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
+        } else {
+            final HttpSession session = request.getSession(false);
+
+            if (session != null || this.allowSessionCreation) {
+                request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
+            }
+        }
+    }
+
+    /**
+     * The URL which will be used as the failure destination.
+     * 
+     * @param defaultFailureUrl
+     *            the failure URL, for example "/loginFailed.jsp".
+     */
+    public void setDefaultFailureUrl(final String defaultFailureUrl) {
+        Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultFailureUrl), "'" + defaultFailureUrl + "' is not a valid redirect URL");
+        this.defaultFailureUrl = defaultFailureUrl;
+    }
+
+    protected boolean isUseForward() {
+        return this.forwardToDestination;
+    }
+
+    /**
+     * If set to <tt>true</tt>, performs a forward to the failure destination
+     * URL instead of a redirect. Defaults to <tt>false</tt>.
+     */
+    public void setUseForward(final boolean forwardToDestination) {
+        this.forwardToDestination = forwardToDestination;
+    }
+
+    /**
+     * Allows overriding of the behaviour when redirecting to a target URL.
+     */
+    public void setRedirectStrategy(final RedirectStrategy redirectStrategy) {
+        this.redirectStrategy = redirectStrategy;
+    }
+
+    protected RedirectStrategy getRedirectStrategy() {
+        return this.redirectStrategy;
+    }
+
+    protected boolean isAllowSessionCreation() {
+        return this.allowSessionCreation;
+    }
+
+    public void setAllowSessionCreation(final boolean allowSessionCreation) {
+        this.allowSessionCreation = allowSessionCreation;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/JdbcTenantDetailsService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/JdbcTenantDetailsService.java
new file mode 100644
index 0000000..d7433da
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/JdbcTenantDetailsService.java
@@ -0,0 +1,148 @@
+/**
+ * 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.security.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
+import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentiferException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+/**
+ * A JDBC implementation of {@link TenantDetailsService} for loading a tenants
+ * details by a <code>tenantIdentifier</code>.
+ */
+@Service
+public class JdbcTenantDetailsService implements TenantDetailsService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public JdbcTenantDetailsService(@Qualifier("tenantDataSourceJndi") final DataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class TenantMapper implements RowMapper<FineractPlatformTenant> {
+
+        private final StringBuilder sqlBuilder = new StringBuilder("t.id, ts.id as connectionId , ")//
+                .append(" t.timezone_id as timezoneId , t.name,t.identifier, ts.schema_name as schemaName, ts.schema_server as schemaServer,")//
+                .append(" ts.schema_server_port as schemaServerPort, ts.auto_update as autoUpdate,")//
+                .append(" ts.schema_username as schemaUsername, ts.schema_password as schemaPassword , ts.pool_initial_size as initialSize,")//
+                .append(" ts.pool_validation_interval as validationInterval, ts.pool_remove_abandoned as removeAbandoned, ts.pool_remove_abandoned_timeout as removeAbandonedTimeout,")//
+                .append(" ts.pool_log_abandoned as logAbandoned, ts.pool_abandon_when_percentage_full as abandonedWhenPercentageFull, ts.pool_test_on_borrow as testOnBorrow,")//
+                .append(" ts.pool_max_active as poolMaxActive, ts.pool_min_idle as poolMinIdle, ts.pool_max_idle as poolMaxIdle,")//
+                .append(" ts.pool_suspect_timeout as poolSuspectTimeout, ts.pool_time_between_eviction_runs_millis as poolTimeBetweenEvictionRunsMillis,")//
+                .append(" ts.pool_min_evictable_idle_time_millis as poolMinEvictableIdleTimeMillis,")//
+                .append(" ts.deadlock_max_retries as maxRetriesOnDeadlock,")//
+                .append(" ts.deadlock_max_retry_interval as maxIntervalBetweenRetries ")//
+                .append(" from tenants t left join tenant_server_connections ts on t.oltp_Id=ts.id ");
+
+        public String schema() {
+            return this.sqlBuilder.toString();
+        }
+
+        @Override
+        public FineractPlatformTenant mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final String tenantIdentifier = rs.getString("identifier");
+            final String name = rs.getString("name");
+            final String timezoneId = rs.getString("timezoneId");
+            final FineractPlatformTenantConnection connection = getDBConnection(rs);
+
+            return new FineractPlatformTenant(id, tenantIdentifier, name, timezoneId, connection);
+        }
+
+        // gets the DB connection
+        private FineractPlatformTenantConnection getDBConnection(ResultSet rs) throws SQLException {
+
+            final Long connectionId = rs.getLong("connectionId");
+            final String schemaName = rs.getString("schemaName");
+            final String schemaServer = rs.getString("schemaServer");
+            final String schemaServerPort = rs.getString("schemaServerPort");
+            final String schemaUsername = rs.getString("schemaUsername");
+            final String schemaPassword = rs.getString("schemaPassword");
+            final boolean autoUpdateEnabled = rs.getBoolean("autoUpdate");
+            final int initialSize = rs.getInt("initialSize");
+            final boolean testOnBorrow = rs.getBoolean("testOnBorrow");
+            final long validationInterval = rs.getLong("validationInterval");
+            final boolean removeAbandoned = rs.getBoolean("removeAbandoned");
+            final int removeAbandonedTimeout = rs.getInt("removeAbandonedTimeout");
+            final boolean logAbandoned = rs.getBoolean("logAbandoned");
+            final int abandonWhenPercentageFull = rs.getInt("abandonedWhenPercentageFull");
+            final int maxActive = rs.getInt("poolMaxActive");
+            final int minIdle = rs.getInt("poolMinIdle");
+            final int maxIdle = rs.getInt("poolMaxIdle");
+            final int suspectTimeout = rs.getInt("poolSuspectTimeout");
+            final int timeBetweenEvictionRunsMillis = rs.getInt("poolTimeBetweenEvictionRunsMillis");
+            final int minEvictableIdleTimeMillis = rs.getInt("poolMinEvictableIdleTimeMillis");
+            int maxRetriesOnDeadlock = rs.getInt("maxRetriesOnDeadlock");
+            int maxIntervalBetweenRetries = rs.getInt("maxIntervalBetweenRetries");
+
+            maxRetriesOnDeadlock = bindValueInMinMaxRange(maxRetriesOnDeadlock, 0, 15);
+            maxIntervalBetweenRetries = bindValueInMinMaxRange(maxIntervalBetweenRetries, 1, 15);
+
+            return new FineractPlatformTenantConnection(connectionId, schemaName, schemaServer, schemaServerPort, schemaUsername,
+                    schemaPassword, autoUpdateEnabled, initialSize, validationInterval, removeAbandoned, removeAbandonedTimeout,
+                    logAbandoned, abandonWhenPercentageFull, maxActive, minIdle, maxIdle, suspectTimeout, timeBetweenEvictionRunsMillis,
+                    minEvictableIdleTimeMillis, maxRetriesOnDeadlock, maxIntervalBetweenRetries, testOnBorrow);
+            
+        }
+
+        private int bindValueInMinMaxRange(final int value, int min, int max) {
+            if (value < min) {
+                return min;
+            } else if (value > max) { return max; }
+            return value;
+        }
+    }
+
+    @Override
+    @Cacheable(value = "tenantsById")
+    public FineractPlatformTenant loadTenantById(final String tenantIdentifier) {
+
+        try {
+            final TenantMapper rm = new TenantMapper();
+            final String sql = "select  " + rm.schema() + " where t.identifier like ?";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { tenantIdentifier });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new InvalidTenantIdentiferException("The tenant identifier: " + tenantIdentifier + " is not valid.");
+        }
+    }
+
+    @Override
+    public List<FineractPlatformTenant> findAllTenants() {
+        final TenantMapper rm = new TenantMapper();
+        final String sql = "select  " + rm.schema();
+
+        final List<FineractPlatformTenant> fineractPlatformTenants = this.jdbcTemplate.query(sql, rm, new Object[] {});
+        return fineractPlatformTenants;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformPasswordEncoder.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformPasswordEncoder.java
new file mode 100644
index 0000000..eedeae6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformPasswordEncoder.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.infrastructure.security.service;
+
+import org.apache.fineract.infrastructure.security.domain.PlatformUser;
+
+public interface PlatformPasswordEncoder {
+
+    String encode(PlatformUser appUser);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformSecurityContext.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformSecurityContext.java
new file mode 100644
index 0000000..379e080
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformSecurityContext.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.service;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+public interface PlatformSecurityContext {
+
+    AppUser authenticatedUser();
+
+    /**
+     * Convenience method returns null (does not throw an exception) if an
+     * authenticated user is not present
+     * 
+     * To be used only in service layer methods that can be triggered via both
+     * the API and batch Jobs (which do not have an authenticated user)
+     * 
+     * @return
+     */
+    AppUser getAuthenticatedUserIfPresent();
+
+    void validateAccessRights(String resourceOfficeHierarchy);
+
+    String officeHierarchy();
+
+    boolean doesPasswordHasToBeRenewed(AppUser currentUser);
+
+    AppUser authenticatedUser(CommandWrapper commandWrapper);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformUserDetailsService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformUserDetailsService.java
new file mode 100644
index 0000000..d855739
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformUserDetailsService.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.security.service;
+
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+/**
+ * Interface to hide implementation detail of spring security.
+ */
+public interface PlatformUserDetailsService extends UserDetailsService {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/RandomPasswordGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/RandomPasswordGenerator.java
new file mode 100644
index 0000000..938c913
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/RandomPasswordGenerator.java
@@ -0,0 +1,37 @@
+/**
+ * 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.security.service;
+
+public class RandomPasswordGenerator {
+
+    private final int numberOfCharactersInPassword;
+
+    public RandomPasswordGenerator(final int numberOfCharactersInPassword) {
+        this.numberOfCharactersInPassword = numberOfCharactersInPassword;
+    }
+
+    public String generate() {
+
+        final StringBuilder passwordBuilder = new StringBuilder(this.numberOfCharactersInPassword);
+        for (int i = 0; i < this.numberOfCharactersInPassword; i++) {
+            passwordBuilder.append((char) ((int) (Math.random() * 26) + 97));
+        }
+        return passwordBuilder.toString();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java
new file mode 100644
index 0000000..d878f3d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java
@@ -0,0 +1,168 @@
+/**
+ * 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.security.service;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
+import org.apache.fineract.infrastructure.security.exception.ResetPasswordException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+/**
+ * Wrapper around spring security's {@link SecurityContext} for extracted the
+ * current authenticated {@link AppUser}.
+ */
+
+@Service
+public class SpringSecurityPlatformSecurityContext implements PlatformSecurityContext {
+
+    // private final static Logger logger =
+    // LoggerFactory.getLogger(SpringSecurityPlatformSecurityContext.class);
+
+    private final ConfigurationDomainService configurationDomainService;
+
+    public static final List<CommandWrapper> EXEMPT_FROM_PASSWORD_RESET_CHECK = new ArrayList<CommandWrapper>() {
+
+        {
+            add(new CommandWrapperBuilder().updateUser(null).build());
+        }
+    };
+
+    @Autowired
+    SpringSecurityPlatformSecurityContext(final ConfigurationDomainService configurationDomainService) {
+        this.configurationDomainService = configurationDomainService;
+    }
+
+    @Override
+    public AppUser authenticatedUser() {
+
+        AppUser currentUser = null;
+        final SecurityContext context = SecurityContextHolder.getContext();
+        if (context != null) {
+            final Authentication auth = context.getAuthentication();
+            if (auth != null) {
+                currentUser = (AppUser) auth.getPrincipal();
+            }
+        }
+
+        if (currentUser == null) { throw new UnAuthenticatedUserException(); }
+
+        if (this.doesPasswordHasToBeRenewed(currentUser)) { throw new ResetPasswordException(currentUser.getId()); }
+
+        return currentUser;
+    }
+
+    @Override
+    public AppUser getAuthenticatedUserIfPresent() {
+
+        AppUser currentUser = null;
+        final SecurityContext context = SecurityContextHolder.getContext();
+        if (context != null) {
+            final Authentication auth = context.getAuthentication();
+            if (auth != null) {
+                currentUser = (AppUser) auth.getPrincipal();
+            }
+        }
+
+        if (currentUser == null) { return null; }
+
+        if (this.doesPasswordHasToBeRenewed(currentUser)) { throw new ResetPasswordException(currentUser.getId()); }
+
+        return currentUser;
+    }
+
+    @Override
+    public AppUser authenticatedUser(CommandWrapper commandWrapper) {
+
+        AppUser currentUser = null;
+        final SecurityContext context = SecurityContextHolder.getContext();
+        if (context != null) {
+            final Authentication auth = context.getAuthentication();
+            if (auth != null) {
+                currentUser = (AppUser) auth.getPrincipal();
+            }
+        }
+
+        if (currentUser == null) { throw new UnAuthenticatedUserException(); }
+
+        if (this.shouldCheckForPasswordForceReset(commandWrapper) && this.doesPasswordHasToBeRenewed(currentUser)) { throw new ResetPasswordException(
+                currentUser.getId()); }
+
+        return currentUser;
+
+    }
+
+    @Override
+    public void validateAccessRights(final String resourceOfficeHierarchy) {
+
+        final AppUser user = authenticatedUser();
+        final String userOfficeHierarchy = user.getOffice().getHierarchy();
+
+        if (!resourceOfficeHierarchy.startsWith(userOfficeHierarchy)) { throw new NoAuthorizationException(
+                "The user doesn't have enough permissions to access the resource."); }
+
+    }
+
+    @Override
+    public String officeHierarchy() {
+        return authenticatedUser().getOffice().getHierarchy();
+    }
+
+    @Override
+    public boolean doesPasswordHasToBeRenewed(AppUser currentUser) {
+
+        if (this.configurationDomainService.isPasswordForcedResetEnable() && !currentUser.getPasswordNeverExpires()) {
+
+            Long passwordDurationDays = this.configurationDomainService.retrievePasswordLiveTime();
+            final Date passWordLastUpdateDate = currentUser.getLastTimePasswordUpdated();
+
+            Calendar c = Calendar.getInstance();
+            c.setTime(passWordLastUpdateDate);
+            c.add(Calendar.DATE, passwordDurationDays.intValue());
+
+            final Date passwordExpirationDate = c.getTime();
+
+            if (DateUtils.getDateOfTenant().after(passwordExpirationDate)) { return true; }
+        }
+        return false;
+
+    }
+
+    private boolean shouldCheckForPasswordForceReset(CommandWrapper commandWrapper) {
+        for (CommandWrapper commandItem : EXEMPT_FROM_PASSWORD_RESET_CHECK) {
+            if (commandItem.actionName().equals(commandWrapper.actionName())
+                    && commandItem.getEntityName().equals(commandWrapper.getEntityName())) { return false; }
+        }
+        return true;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TenantAwareJpaPlatformUserDetailsService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TenantAwareJpaPlatformUserDetailsService.java
new file mode 100644
index 0000000..5062906
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TenantAwareJpaPlatformUserDetailsService.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.infrastructure.security.service;
+
+import org.apache.fineract.infrastructure.security.domain.PlatformUser;
+import org.apache.fineract.infrastructure.security.domain.PlatformUserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+/**
+ * Used in securityContext.xml as implementation of spring security's
+ * {@link UserDetailsService}.
+ */
+@Service(value = "userDetailsService")
+public class TenantAwareJpaPlatformUserDetailsService implements PlatformUserDetailsService {
+
+    @Autowired
+    private PlatformUserRepository platformUserRepository;
+
+    @Override
+    @Cacheable(value = "usersByUsername", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#username+'ubu')")
+    public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException, DataAccessException {
+
+        // Retrieve active users only
+        final boolean deleted = false;
+        final boolean enabled = true;
+
+        final PlatformUser appUser = this.platformUserRepository.findByUsernameAndDeletedAndEnabled(username, deleted, enabled);
+
+        if (appUser == null) { throw new UsernameNotFoundException(username + ": not found"); }
+
+        return appUser;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TenantDetailsService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TenantDetailsService.java
new file mode 100644
index 0000000..6a781d2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TenantDetailsService.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+
+public interface TenantDetailsService {
+
+    FineractPlatformTenant loadTenantById(String tenantId);
+
+    List<FineractPlatformTenant> findAllTenants();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/vote/SelfServiceUserAccessVote.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/vote/SelfServiceUserAccessVote.java
new file mode 100644
index 0000000..83c89c7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/vote/SelfServiceUserAccessVote.java
@@ -0,0 +1,61 @@
+/**
+ * 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.security.vote;
+
+import java.util.Collection;
+
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.FilterInvocation;
+
+public class SelfServiceUserAccessVote implements AccessDecisionVoter<FilterInvocation> {
+
+	@Override
+	public boolean supports(@SuppressWarnings("unused") ConfigAttribute attribute) {
+		// This implementation supports any attribute, because it does not rely on it.
+		return true;
+	}
+
+	@Override
+	public boolean supports(Class<?> clazz) {
+		return FilterInvocation.class.isAssignableFrom(clazz);
+	}
+
+	@Override
+	public int vote(final Authentication authentication, final FilterInvocation fi,
+			@SuppressWarnings("unused") final Collection<ConfigAttribute> attributes) {
+		if(!"OPTIONS".equalsIgnoreCase(fi.getHttpRequest().getMethod())){
+			AppUser user = (AppUser) authentication.getPrincipal();
+			
+			String pathURL = fi.getRequestUrl();
+			boolean isSelfServiceRequest = (pathURL != null && pathURL.contains("/self/"));
+
+			boolean notAllowed = ((isSelfServiceRequest && !user.isSelfServiceUser())
+					||(!isSelfServiceRequest && user.isSelfServiceUser()));
+			
+			if(notAllowed){
+				return ACCESS_DENIED;
+			}
+		}
+		return ACCESS_GRANTED;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java
new file mode 100644
index 0000000..2980764
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.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.infrastructure.sms;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SmsApiConstants {
+
+    public static final String RESOURCE_NAME = "sms";
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // request parameters
+    public static final String idParamName = "id";
+    public static final String groupIdParamName = "groupId";
+    public static final String clientIdParamName = "clientId";
+    public static final String staffIdParamName = "staffId";
+    public static final String messageParamName = "message";
+
+    // response parameters
+    public static final String statusParamName = "status";
+
+    public static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName));
+
+    public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(messageParamName));
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java
new file mode 100644
index 0000000..506dde8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.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.infrastructure.sms.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.sms.data.SmsData;
+import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/sms")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+public class SmsApiResource {
+
+    private final String resourceNameForPermissions = "SMS";
+
+    private final PlatformSecurityContext context;
+    private final SmsReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<SmsData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public SmsApiResource(final PlatformSecurityContext context, final SmsReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<SmsData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<SmsData> smsMessages = this.readPlatformService.retrieveAll();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, smsMessages);
+    }
+
+    @POST
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createSms().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{resourceId}")
+    public String retrieveOne(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
+
+        final SmsData smsMessage = this.readPlatformService.retrieveOne(resourceId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, smsMessage);
+    }
+
+    @PUT
+    @Path("{resourceId}")
+    public String update(@PathParam("resourceId") final Long resourceId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSms(resourceId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{resourceId}")
+    public String delete(@PathParam("resourceId") final Long resourceId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSms(resourceId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java
new file mode 100644
index 0000000..d5449e2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+/**
+ * Immutable data object representing a SMS message.
+ */
+public class SmsData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final Long groupId;
+    @SuppressWarnings("unused")
+    private final Long clientId;
+    @SuppressWarnings("unused")
+    private final Long staffId;
+    @SuppressWarnings("unused")
+    private final EnumOptionData status;
+    @SuppressWarnings("unused")
+    private final String mobileNo;
+    @SuppressWarnings("unused")
+    private final String message;
+
+    public static SmsData instance(final Long id, final Long groupId, final Long clientId, final Long staffId, final EnumOptionData status,
+            final String mobileNo, final String message) {
+        return new SmsData(id, groupId, clientId, staffId, status, mobileNo, message);
+    }
+
+    private SmsData(final Long id, final Long groupId, final Long clientId, final Long staffId, final EnumOptionData status,
+            final String mobileNo, final String message) {
+        this.id = id;
+        this.groupId = groupId;
+        this.clientId = clientId;
+        this.staffId = staffId;
+        this.status = status;
+        this.mobileNo = mobileNo;
+        this.message = message;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsDataValidator.java
new file mode 100644
index 0000000..99dae7e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsDataValidator.java
@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.sms.SmsApiConstants;
+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 SmsDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public SmsDataValidator(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, SmsApiConstants.CREATE_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SmsApiConstants.RESOURCE_NAME);
+
+        if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) {
+            final Long groupId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.groupIdParamName, element);
+            baseDataValidator.reset().parameter(SmsApiConstants.groupIdParamName).value(groupId).notNull().integerGreaterThanZero();
+
+            // ensure clientId and staffId are not passed
+            if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) {
+                baseDataValidator.reset().parameter(SmsApiConstants.clientIdParamName).failWithCode("cannot.be.passed.with.groupId");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
+                baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName).failWithCode("cannot.be.passed.with.groupId");
+            }
+        } else if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) {
+            final Long clientId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.clientIdParamName, element);
+            baseDataValidator.reset().parameter(SmsApiConstants.clientIdParamName).value(clientId).notNull().integerGreaterThanZero();
+
+            // ensure groupId and staffId are not passed
+            if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) {
+                baseDataValidator.reset().parameter(SmsApiConstants.groupIdParamName).failWithCode("cannot.be.passed.with.clientId");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
+                baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName).failWithCode("cannot.be.passed.with.clientId");
+            }
+        } else if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.staffIdParamName, element);
+            baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName).value(staffId).ignoreIfNull().longGreaterThanZero();
+
+            // ensure groupId and clientId are not passed
+            if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) {
+                baseDataValidator.reset().parameter(SmsApiConstants.groupIdParamName).failWithCode("cannot.be.passed.with.staffId");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) {
+                baseDataValidator.reset().parameter(SmsApiConstants.clientIdParamName).failWithCode("cannot.be.passed.with.staffId");
+            }
+        }
+
+        if (!this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)
+                && !this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)
+                && !this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
+            baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName)
+                    .failWithCodeNoParameterAddedToErrorCode("no.entity.provided");
+        }
+
+        final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element);
+        baseDataValidator.reset().parameter(SmsApiConstants.messageParamName).value(message).notBlank().notExceedingLengthOf(1000);
+
+        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, SmsApiConstants.UPDATE_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SmsApiConstants.RESOURCE_NAME);
+
+        if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.messageParamName, element)) {
+            final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element);
+            baseDataValidator.reset().parameter(SmsApiConstants.messageParamName).value(message).notBlank().notExceedingLengthOf(1000);
+        }
+
+        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/infrastructure/sms/domain/SmsMessage.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java
new file mode 100644
index 0000000..003f086
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java
@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.sms.SmsApiConstants;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "sms_messages_outbound")
+public class SmsMessage extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "group_id", nullable = true)
+    private Group group;
+
+    @ManyToOne
+    @JoinColumn(name = "client_id", nullable = true)
+    private Client client;
+
+    @ManyToOne
+    @JoinColumn(name = "staff_id", nullable = true)
+    private Staff staff;
+
+    @Column(name = "status_enum", nullable = false)
+    private Integer statusType;
+
+    @Column(name = "mobile_no", nullable = false, length = 50)
+    private String mobileNo;
+
+    @Column(name = "message", nullable = false)
+    private String message;
+
+    public static SmsMessage pendingSms(final Group group, final Client client, final Staff staff, final String message,
+            final String mobileNo) {
+        return new SmsMessage(group, client, staff, SmsMessageStatusType.PENDING, message, mobileNo);
+    }
+
+    protected SmsMessage() {
+        //
+    }
+
+    private SmsMessage(final Group group, final Client client, final Staff staff, final SmsMessageStatusType statusType,
+            final String message, final String mobileNo) {
+        this.group = group;
+        this.client = client;
+        this.staff = staff;
+        this.statusType = statusType.getValue();
+        this.mobileNo = mobileNo;
+        this.message = message;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(1);
+
+        if (command.isChangeInStringParameterNamed(SmsApiConstants.messageParamName, this.message)) {
+            final String newValue = command.stringValueOfParameterNamed(SmsApiConstants.messageParamName);
+            actualChanges.put(SmsApiConstants.messageParamName, newValue);
+            this.message = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        return actualChanges;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
new file mode 100644
index 0000000..32a56e1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
@@ -0,0 +1,92 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.domain;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.sms.SmsApiConstants;
+import org.apache.fineract.infrastructure.sms.exception.SmsNotFoundException;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+
+@Component
+public class SmsMessageAssembler {
+
+    private final SmsMessageRepository smsMessageRepository;
+    private final GroupRepositoryWrapper groupRepository;
+    private final ClientRepositoryWrapper clientRepository;
+    private final StaffRepositoryWrapper staffRepository;
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public SmsMessageAssembler(final SmsMessageRepository smsMessageRepository, final GroupRepositoryWrapper groupRepositoryWrapper,
+            final ClientRepositoryWrapper clientRepository, final StaffRepositoryWrapper staffRepository,
+            final FromJsonHelper fromApiJsonHelper) {
+        this.smsMessageRepository = smsMessageRepository;
+        this.groupRepository = groupRepositoryWrapper;
+        this.clientRepository = clientRepository;
+        this.staffRepository = staffRepository;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public SmsMessage assembleFromJson(final JsonCommand command) {
+
+        final JsonElement element = command.parsedJson();
+
+        String mobileNo = null;
+
+        Group group = null;
+        if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) {
+            final Long groupId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.groupIdParamName, element);
+            group = this.groupRepository.findOneWithNotFoundDetection(groupId);
+        }
+
+        Client client = null;
+        if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) {
+            final Long clientId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.clientIdParamName, element);
+            client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            mobileNo = client.mobileNo();
+        }
+
+        Staff staff = null;
+        if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.staffIdParamName, element);
+            staff = this.staffRepository.findOneWithNotFoundDetection(staffId);
+            mobileNo = staff.mobileNo();
+        }
+
+        final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element);
+
+        return SmsMessage.pendingSms(group, client, staff, message, mobileNo);
+    }
+
+    public SmsMessage assembleFromResourceId(final Long resourceId) {
+        final SmsMessage sms = this.smsMessageRepository.findOne(resourceId);
+        if (sms == null) { throw new SmsNotFoundException(resourceId); }
+        return sms;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageEnumerations.java
new file mode 100644
index 0000000..c16f965
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageEnumerations.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.domain;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class SmsMessageEnumerations {
+
+    public static EnumOptionData status(final Integer statusId) {
+        return status(SmsMessageStatusType.fromInt(statusId));
+    }
+
+    public static EnumOptionData status(final SmsMessageStatusType status) {
+        EnumOptionData optionData = new EnumOptionData(SmsMessageStatusType.INVALID.getValue().longValue(),
+                SmsMessageStatusType.INVALID.getCode(), "Invalid");
+        switch (status) {
+            case INVALID:
+                optionData = new EnumOptionData(SmsMessageStatusType.INVALID.getValue().longValue(),
+                        SmsMessageStatusType.INVALID.getCode(), "Invalid");
+            break;
+            case PENDING:
+                optionData = new EnumOptionData(SmsMessageStatusType.PENDING.getValue().longValue(),
+                        SmsMessageStatusType.PENDING.getCode(), "Pending");
+            break;
+            case SENT:
+                optionData = new EnumOptionData(SmsMessageStatusType.SENT.getValue().longValue(), SmsMessageStatusType.SENT.getCode(),
+                        "Sent");
+            break;
+            case DELIVERED:
+                optionData = new EnumOptionData(SmsMessageStatusType.DELIVERED.getValue().longValue(),
+                        SmsMessageStatusType.DELIVERED.getCode(), "Delivered");
+            break;
+            case FAILED:
+                optionData = new EnumOptionData(SmsMessageStatusType.FAILED.getValue().longValue(), SmsMessageStatusType.FAILED.getCode(),
+                        "Failed");
+            break;
+
+        }
+
+        return optionData;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java
new file mode 100644
index 0000000..4d9768a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.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.infrastructure.sms.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface SmsMessageRepository extends JpaRepository<SmsMessage, Long>, JpaSpecificationExecutor<SmsMessage> {
+    // no extra behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java
new file mode 100644
index 0000000..72dce38
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.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.infrastructure.sms.domain;
+
+public enum SmsMessageStatusType {
+
+    INVALID(0, "smsMessageStatusType.invalid"), //
+    PENDING(100, "smsMessageStatusType.pending"), //
+    SENT(200, "smsMessageStatusType.sent"), //
+    DELIVERED(300, "smsMessageStatusType.delivered"), //
+    FAILED(400, "smsMessageStatusType.failed");
+
+    private final Integer value;
+    private final String code;
+
+    public static SmsMessageStatusType fromInt(final Integer statusValue) {
+
+        SmsMessageStatusType enumeration = SmsMessageStatusType.INVALID;
+        switch (statusValue) {
+            case 100:
+                enumeration = SmsMessageStatusType.PENDING;
+            break;
+            case 200:
+                enumeration = SmsMessageStatusType.SENT;
+            break;
+            case 300:
+                enumeration = SmsMessageStatusType.DELIVERED;
+            break;
+            case 400:
+                enumeration = SmsMessageStatusType.FAILED;
+            break;
+        }
+        return enumeration;
+    }
+
+    private SmsMessageStatusType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    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/infrastructure/sms/exception/SmsNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsNotFoundException.java
new file mode 100644
index 0000000..752c3ed
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when a code is not found.
+ */
+public class SmsNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public SmsNotFoundException(final Long resourceId) {
+        super("error.msg.sms.identifier.not.found", "SMS with identifier `" + resourceId + "` does not exist", resourceId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/handler/CreateSmsCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/handler/CreateSmsCommandHandler.java
new file mode 100644
index 0000000..2ad4cdc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/handler/CreateSmsCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.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.infrastructure.sms.service.SmsWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SMS", action = "CREATE")
+public class CreateSmsCommandHandler implements NewCommandSourceHandler {
+
+    private final SmsWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateSmsCommandHandler(final SmsWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.create(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/handler/DeleteSmsCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/handler/DeleteSmsCommandHandler.java
new file mode 100644
index 0000000..767db17
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/handler/DeleteSmsCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.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.infrastructure.sms.service.SmsWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SMS", action = "DELETE")
+public class DeleteSmsCommandHandler implements NewCommandSourceHandler {
+
+    private final SmsWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteSmsCommandHandler(final SmsWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.delete(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/handler/UpdateSmsCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/handler/UpdateSmsCommandHandler.java
new file mode 100644
index 0000000..4fe92a7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/handler/UpdateSmsCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.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.infrastructure.sms.service.SmsWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SMS", action = "UPDATE")
+public class UpdateSmsCommandHandler implements NewCommandSourceHandler {
+
+    private final SmsWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateSmsCommandHandler(final SmsWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.update(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java
new file mode 100644
index 0000000..db330da
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.sms.data.SmsData;
+
+public interface SmsReadPlatformService {
+
+    Collection<SmsData> retrieveAll();
+
+    SmsData retrieveOne(Long resourceId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java
new file mode 100644
index 0000000..c4fd088
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java
@@ -0,0 +1,107 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.sms.data.SmsData;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageEnumerations;
+import org.apache.fineract.infrastructure.sms.exception.SmsNotFoundException;
+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 SmsReadPlatformServiceImpl implements SmsReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final SmsMapper smsRowMapper;
+
+    @Autowired
+    public SmsReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.smsRowMapper = new SmsMapper();
+    }
+
+    private static final class SmsMapper implements RowMapper<SmsData> {
+
+        final String schema;
+
+        public SmsMapper() {
+            final StringBuilder sql = new StringBuilder(300);
+            sql.append("smo.id as id, ");
+            sql.append("smo.group_id as groupId, ");
+            sql.append("smo.client_id as clientId, ");
+            sql.append("smo.staff_id as staffId, ");
+            sql.append("smo.status_enum as statusId, ");
+            sql.append("smo.mobile_no as mobileNo, ");
+            sql.append("smo.message as message ");
+            sql.append("from sms_messages_outbound smo");
+
+            this.schema = sql.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+
+        @Override
+        public SmsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+
+            final String mobileNo = rs.getString("mobileNo");
+            final String message = rs.getString("message");
+
+            final Integer statusId = JdbcSupport.getInteger(rs, "statusId");
+            final EnumOptionData status = SmsMessageEnumerations.status(statusId);
+
+            return SmsData.instance(id, groupId, clientId, staffId, status, mobileNo, message);
+        }
+    }
+
+    @Override
+    public Collection<SmsData> retrieveAll() {
+
+        final String sql = "select " + this.smsRowMapper.schema();
+
+        return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {});
+    }
+
+    @Override
+    public SmsData retrieveOne(final Long resourceId) {
+        try {
+            final String sql = "select " + this.smsRowMapper.schema() + " where smo.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, this.smsRowMapper, new Object[] { resourceId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new SmsNotFoundException(resourceId);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsWritePlatformService.java
new file mode 100644
index 0000000..08ef233
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface SmsWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    CommandProcessingResult update(Long resourceId, JsonCommand command);
+
+    CommandProcessingResult delete(Long resourceId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..e3b308a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,134 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.sms.data.SmsDataValidator;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageAssembler;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
+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 SmsWritePlatformServiceJpaRepositoryImpl implements SmsWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(SmsWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final SmsMessageAssembler assembler;
+    private final SmsMessageRepository repository;
+    private final SmsDataValidator validator;
+
+    @Autowired
+    public SmsWritePlatformServiceJpaRepositoryImpl(final SmsMessageAssembler assembler, final SmsMessageRepository repository,
+            final SmsDataValidator validator) {
+        this.assembler = assembler;
+        this.repository = repository;
+        this.validator = validator;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult create(final JsonCommand command) {
+
+        try {
+            this.validator.validateForCreate(command.json());
+
+            final SmsMessage message = this.assembler.assembleFromJson(command);
+
+            // TODO: at this point we also want to fire off request using third
+            // party service to send SMS.
+            // TODO: decision to be made on wheter we 'wait' for response or use
+            // 'future/promise' to capture response and update the SmsMessage
+            // table
+            this.repository.save(message);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(message.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult update(final Long resourceId, final JsonCommand command) {
+
+        try {
+            this.validator.validateForUpdate(command.json());
+
+            final SmsMessage message = this.assembler.assembleFromResourceId(resourceId);
+            final Map<String, Object> changes = message.update(command);
+            if (!changes.isEmpty()) {
+                this.repository.save(message);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(resourceId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult delete(final Long resourceId) {
+
+        try {
+            final SmsMessage message = this.assembler.assembleFromResourceId(resourceId);
+            this.repository.delete(message);
+            this.repository.flush();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(null, dve);
+            return CommandProcessingResult.empty();
+        }
+        return new CommandProcessingResultBuilder().withEntityId(resourceId).build();
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(@SuppressWarnings("unused") final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+
+        if (realCause.getMessage().contains("mobile_no")) { throw new PlatformDataIntegrityException("error.msg.sms.no.mobile.no.exists",
+                "The group, client or staff provided has no mobile no.", "id"); }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.sms.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/LikelihoodApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/LikelihoodApiConstants.java
new file mode 100644
index 0000000..e7f296f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/LikelihoodApiConstants.java
@@ -0,0 +1,37 @@
+/**
+ * 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.survey.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.survey.data.LikelihoodStatus;
+
+public class LikelihoodApiConstants {
+
+    public static final String ACTIVE = "active";
+
+    public static final String LIKELIHOOD_RESOURCE_NAME = "likelihood";
+
+    public static final Set<String> UPDATE_LIKELIHOOD_DATA_PARAMETERS = new HashSet<>(Arrays.asList(ACTIVE));
+
+    public static final Set<Long> VALID_LIKELIHOOD_ENABLED_VALUES = new HashSet<>(Arrays.asList(LikelihoodStatus.DISABLED,
+            LikelihoodStatus.ENABLED));
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/LikelihoodApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/LikelihoodApiResource.java
new file mode 100644
index 0000000..4256fe5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/LikelihoodApiResource.java
@@ -0,0 +1,112 @@
+/**
+ * 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.survey.api;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+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.MediaType;
+
+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.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.survey.data.LikelihoodData;
+import org.apache.fineract.infrastructure.survey.service.ReadLikelihoodService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+@Path("/likelihood")
+@Component
+@Scope("singleton")
+public class LikelihoodApiResource {
+
+    private final DefaultToApiJsonSerializer<LikelihoodData> toApiJsonSerializer;
+    private final PlatformSecurityContext context;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ReadLikelihoodService readService;
+
+    @Autowired
+    LikelihoodApiResource(final PlatformSecurityContext context,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final DefaultToApiJsonSerializer<LikelihoodData> toApiJsonSerializer, final ReadLikelihoodService readService) {
+
+        this.context = context;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.readService = readService;
+
+    }
+
+    @GET
+    @Path("{ppiName}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@PathParam("ppiName") final String ppiName) {
+
+        this.context.authenticatedUser().validateHasReadPermission(PovertyLineApiConstants.POVERTY_LINE_RESOURCE_NAME);
+
+        List<LikelihoodData> likelihoodData = this.readService.retrieveAll(ppiName);
+        return this.toApiJsonSerializer.serialize(likelihoodData);
+
+    }
+
+    @GET
+    @Path("{ppiName}/{likelihoodId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieve(@PathParam("likelihoodId") final Long likelihoodId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(PovertyLineApiConstants.POVERTY_LINE_RESOURCE_NAME);
+
+        LikelihoodData likelihoodData = this.readService.retrieve(likelihoodId);
+        return this.toApiJsonSerializer.serialize(likelihoodData);
+
+    }
+
+    @PUT
+    @Path("{ppiName}/{likelihoodId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("likelihoodId") final Long likelihoodId, final String apiRequestBodyAsJson) {
+
+        this.context.authenticatedUser().validateHasReadPermission(PovertyLineApiConstants.POVERTY_LINE_RESOURCE_NAME);
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateLikelihood(likelihoodId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/PovertyLineApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/PovertyLineApiConstants.java
new file mode 100644
index 0000000..053c24e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/PovertyLineApiConstants.java
@@ -0,0 +1,24 @@
+/**
+ * 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.survey.api;
+
+public class PovertyLineApiConstants {
+
+    final static String POVERTY_LINE_RESOURCE_NAME = "PovertyLine";
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/PovertyLineApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/PovertyLineApiResource.java
new file mode 100644
index 0000000..ce3cfdc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/PovertyLineApiResource.java
@@ -0,0 +1,87 @@
+/**
+ * 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.survey.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.survey.data.LikeliHoodPovertyLineData;
+import org.apache.fineract.infrastructure.survey.data.PpiPovertyLineData;
+import org.apache.fineract.infrastructure.survey.service.PovertyLineService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/povertyLine")
+@Component
+@Scope("singleton")
+public class PovertyLineApiResource {
+
+    private final DefaultToApiJsonSerializer<PpiPovertyLineData> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<LikeliHoodPovertyLineData> likelihoodToApiJsonSerializer;
+    // private final DefaultToApiJsonSerializer<PpiPovertyLineData>
+    // toApiJsonSerializer;
+    private final PlatformSecurityContext context;
+    private final PovertyLineService readService;
+
+    @Autowired
+    PovertyLineApiResource(final PlatformSecurityContext context, final DefaultToApiJsonSerializer<PpiPovertyLineData> toApiJsonSerializer,
+            final PovertyLineService readService, final DefaultToApiJsonSerializer<LikeliHoodPovertyLineData> likelihoodToApiJsonSerializer) {
+
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.readService = readService;
+        this.likelihoodToApiJsonSerializer = likelihoodToApiJsonSerializer;
+
+    }
+
+    @GET
+    @Path("{ppiName}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@PathParam("ppiName") final String ppiName) {
+
+        this.context.authenticatedUser().validateHasReadPermission(PovertyLineApiConstants.POVERTY_LINE_RESOURCE_NAME);
+
+        PpiPovertyLineData povertyLine = this.readService.retrieveAll(ppiName);
+        return this.toApiJsonSerializer.serialize(povertyLine);
+
+    }
+
+    @GET
+    @Path("{ppiName}/{likelihoodId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@PathParam("ppiName") final String ppiName, @PathParam("likelihoodId") final Long likelihoodId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(PovertyLineApiConstants.POVERTY_LINE_RESOURCE_NAME);
+
+        LikeliHoodPovertyLineData likeliHoodPovertyLineData = this.readService.retrieveForLikelihood(ppiName, likelihoodId);
+
+        return this.likelihoodToApiJsonSerializer.serialize(likeliHoodPovertyLineData);
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiConstants.java
new file mode 100644
index 0000000..56caeb5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiConstants.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.survey.api;
+
+/**
+ * Created by Cieyou on 2/27/14.
+ */
+public class SurveyApiConstants {
+
+
+    final static String SURVEY_RESOURCE_NAME ="Survey";
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResource.java
new file mode 100644
index 0000000..ec0e46e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResource.java
@@ -0,0 +1,180 @@
+/**
+ * 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.survey.api;
+
+import java.util.List;
+
+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.MediaType;
+
+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.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview;
+import org.apache.fineract.infrastructure.survey.data.SurveyData;
+import org.apache.fineract.infrastructure.survey.data.SurveyDataTableData;
+import org.apache.fineract.infrastructure.survey.service.ReadSurveyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by Cieyou on 2/27/14.
+ */
+@Path("/survey")
+@Component
+@Scope("singleton")
+public class SurveyApiResource {
+
+    private final DefaultToApiJsonSerializer<SurveyData> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<ClientScoresOverview> toApiJsonClientScoreOverviewSerializer;
+    private final PlatformSecurityContext context;
+    private final ReadSurveyService readSurveyService;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final GenericDataService genericDataService;
+
+    @Autowired
+    public SurveyApiResource(final DefaultToApiJsonSerializer<SurveyData> toApiJsonSerializer, final PlatformSecurityContext context,
+            final ReadSurveyService readSurveyService, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final DefaultToApiJsonSerializer<ClientScoresOverview> toApiJsonClientScoreOverviewSerializer,
+            final GenericDataService genericDataService) {
+
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.context = context;
+        this.readSurveyService = readSurveyService;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.toApiJsonClientScoreOverviewSerializer = toApiJsonClientScoreOverviewSerializer;
+        this.genericDataService = genericDataService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveSurveys() {
+
+        this.context.authenticatedUser().validateHasReadPermission(SurveyApiConstants.SURVEY_RESOURCE_NAME);
+
+        List<SurveyDataTableData> surveys = this.readSurveyService.retrieveAllSurveys();
+        return this.toApiJsonSerializer.serialize(surveys);
+    }
+
+    @GET
+    @Path("{surveyName}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveSurvey(@PathParam("surveyName") final String surveyName) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SurveyApiConstants.SURVEY_RESOURCE_NAME);
+
+        SurveyDataTableData surveys = this.readSurveyService.retrieveSurvey(surveyName);
+
+        return this.toApiJsonSerializer.serialize(surveys);
+
+    }
+
+    @POST
+    @Path("{surveyName}/{apptableId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createDatatableEntry(@PathParam("surveyName") final String datatable, @PathParam("apptableId") final Long apptableId,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .fullFilSurvey(datatable, apptableId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    /** FIXME Vishwas what does this API really do? ***/
+    @GET
+    @Path("{surveyName}/{clientId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getClientSurveyOverview(@PathParam("surveyName") final String surveyName, @PathParam("clientId") final Long clientId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SurveyApiConstants.SURVEY_RESOURCE_NAME);
+
+        List<ClientScoresOverview> scores = this.readSurveyService.retrieveClientSurveyScoreOverview(clientId);
+
+        return this.toApiJsonClientScoreOverviewSerializer.serialize(scores);
+    }
+
+    @GET
+    @Path("{surveyName}/{clientId}/{entryId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getSurveyEntry(@PathParam("surveyName") final String surveyName, @PathParam("clientId") final Long clientId,
+            @PathParam("entryId") final Long entryId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SurveyApiConstants.SURVEY_RESOURCE_NAME);
+
+        final GenericResultsetData results = this.readSurveyService.retrieveSurveyEntry(surveyName, clientId, entryId);
+
+        return this.genericDataService.generateJsonFromGenericResultsetData(results);
+
+    }
+
+    @PUT
+    @Path("register/{surveyName}/{apptable}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String register(@PathParam("surveyName") final String datatable, @PathParam("apptable") final String apptable,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().registerSurvey(datatable, apptable)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @DELETE
+    @Path("{surveyName}/{clientId}/{fulfilledId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteDatatableEntries(@PathParam("surveyName") final String surveyName, @PathParam("clientId") final Long clientId,
+            @PathParam("fulfilledId") final Long fulfilledId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteDatatable(surveyName, clientId, fulfilledId) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/ClientScoresOverview.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/ClientScoresOverview.java
new file mode 100644
index 0000000..e4b8a62
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/ClientScoresOverview.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.infrastructure.survey.data;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Created by Cieyou on 3/18/14.
+ */
+public class ClientScoresOverview {
+
+    @SuppressWarnings("unused")
+    final private String surveyName;
+    @SuppressWarnings("unused")
+    final private long id;
+    @SuppressWarnings("unused")
+    final private String likelihoodCode;
+    @SuppressWarnings("unused")
+    final private String likelihoodName;
+    @SuppressWarnings("unused")
+    final private long score;
+    @SuppressWarnings("unused")
+    final private Double povertyLine;
+    @SuppressWarnings("unused")
+    final private LocalDate date;
+
+    public ClientScoresOverview(final String likelihoodCode, final String likelihoodName, final long score, final Double povertyLine,
+            final LocalDate date, final long resourceId, final String surveyName) {
+
+        this.likelihoodCode = likelihoodCode;
+        this.likelihoodName = likelihoodName;
+        this.score = score;
+        this.povertyLine = povertyLine;
+        this.date = date;
+        this.id = resourceId;
+        this.surveyName = surveyName;
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikeliHoodPovertyLineData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikeliHoodPovertyLineData.java
new file mode 100644
index 0000000..44e6574
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikeliHoodPovertyLineData.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.infrastructure.survey.data;
+
+import java.util.List;
+
+/**
+ * Created by Cieyou on 3/11/14.
+ */
+public class LikeliHoodPovertyLineData {
+
+    final long resourceId;
+    final String likeliHoodName;
+    final String likeliHoodCode;
+    final long enabled;
+    List<PovertyLineData> povertyLineData;
+
+    public LikeliHoodPovertyLineData(final long resourceId, final List<PovertyLineData> povertyLineData, final String likeliHoodName,
+            final String likeliHoodCode, final long enabled) {
+        this.resourceId = resourceId;
+        this.povertyLineData = povertyLineData;
+        this.likeliHoodName = likeliHoodName;
+        this.likeliHoodCode = likeliHoodCode;
+        this.enabled = enabled;
+
+    }
+
+    public void addPovertyLine(PovertyLineData povertyLineData) {
+        this.povertyLineData.add(povertyLineData);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikelihoodData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikelihoodData.java
new file mode 100644
index 0000000..55ea4ed
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikelihoodData.java
@@ -0,0 +1,39 @@
+/**
+ * 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.survey.data;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+public class LikelihoodData {
+
+    final long resourceId;
+    final String likeliHoodName;
+    final String likeliHoodCode;
+    final long enabled;
+
+    public LikelihoodData(final long resourceId, final String likeliHoodName, final String likeliHoodCode, final long enabled) {
+        this.resourceId = resourceId;
+        this.likeliHoodName = likeliHoodName;
+        this.likeliHoodCode = likeliHoodCode;
+        this.enabled = enabled;
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikelihoodDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikelihoodDataValidator.java
new file mode 100644
index 0000000..98120cc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikelihoodDataValidator.java
@@ -0,0 +1,73 @@
+/**
+ * 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.survey.data;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.survey.api.LikelihoodApiConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+
+@Component
+public class LikelihoodDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public LikelihoodDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForUpdate(final JsonCommand command) {
+        final String json = command.json();
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, LikelihoodApiConstants.UPDATE_LIKELIHOOD_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(LikelihoodApiConstants.LIKELIHOOD_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(LikelihoodApiConstants.ACTIVE, element)) {
+            final boolean enabledBool = this.fromApiJsonHelper.extractBooleanNamed(LikelihoodApiConstants.ACTIVE, element);
+
+            baseDataValidator.reset().parameter(LikelihoodApiConstants.ACTIVE).value(enabledBool).validateForBooleanValue();
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikelihoodStatus.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikelihoodStatus.java
new file mode 100644
index 0000000..ced9fe1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/LikelihoodStatus.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.survey.data;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+public class LikelihoodStatus {
+
+    public final static long  ENABLED = 200;
+    public final static long DISABLED =100;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/PovertyLineData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/PovertyLineData.java
new file mode 100644
index 0000000..b5b50fa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/PovertyLineData.java
@@ -0,0 +1,41 @@
+/**
+ * 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.survey.data;
+
+/**
+ * Created by Cieyou on 3/11/14.
+ */
+public class PovertyLineData {
+
+    final Long resourceId;
+    final Long scoreFrom;
+    final Long scoreTo;
+    final Double povertyLine;
+
+    public PovertyLineData(final Long resourceId,
+            final Long scoreFrom,
+            final Long scoreTo,
+            final Double povertyLine){
+
+        this.resourceId = resourceId;
+        this.scoreTo = scoreTo;
+        this.scoreFrom = scoreFrom;
+        this.povertyLine = povertyLine;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/PpiPovertyLineData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/PpiPovertyLineData.java
new file mode 100644
index 0000000..955dcb6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/PpiPovertyLineData.java
@@ -0,0 +1,41 @@
+/**
+ * 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.survey.data;
+
+import java.util.List;
+
+/**
+ * Created by Cieyou on 3/11/14.
+ */
+public class PpiPovertyLineData {
+
+    final String ppi;
+    final List<LikeliHoodPovertyLineData> likeliHoodPovertyLineData;
+
+
+    public PpiPovertyLineData(final List<LikeliHoodPovertyLineData> likeliHoodPovertyLineData,
+                       final String ppi){
+
+        this.likeliHoodPovertyLineData = likeliHoodPovertyLineData;
+        this.ppi = ppi;
+
+    }
+
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/SurveyData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/SurveyData.java
new file mode 100644
index 0000000..1885e14
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/SurveyData.java
@@ -0,0 +1,25 @@
+/**
+ * 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.survey.data;
+
+/**
+ * Created by Cieyou on 2/27/14.
+ */
+public class SurveyData {
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/SurveyDataTableData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/SurveyDataTableData.java
new file mode 100644
index 0000000..ce83b48
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/data/SurveyDataTableData.java
@@ -0,0 +1,40 @@
+/**
+ * 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.survey.data;
+
+import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
+
+public class SurveyDataTableData {
+
+    @SuppressWarnings("unused")
+    private final DatatableData datatableData;
+
+    @SuppressWarnings("unused")
+    private final boolean enabled;
+
+    public static SurveyDataTableData create(final DatatableData datatableData, final boolean enabled) {
+
+        return new SurveyDataTableData(datatableData, enabled);
+    }
+
+    private SurveyDataTableData(final DatatableData datatableData, final boolean enabled) {
+        this.datatableData = datatableData;
+        this.enabled = enabled;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/domain/Likelihood.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/domain/Likelihood.java
new file mode 100644
index 0000000..60f2481
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/domain/Likelihood.java
@@ -0,0 +1,83 @@
+/**
+ * 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.survey.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.survey.api.LikelihoodApiConstants;
+import org.apache.fineract.infrastructure.survey.data.LikelihoodStatus;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "ppi_likelihoods_ppi")
+public final class Likelihood extends AbstractPersistable<Long> {
+
+    @Column(name = "ppi_name", nullable = false)
+    private String ppiName;
+
+    @Column(name = "likelihood_id", nullable = false)
+    private Long likelihoodId;
+
+    @Column(name = "enabled", nullable = false)
+    private Long enabled;
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final boolean enabled = command.booleanPrimitiveValueOfParameterNamed(LikelihoodApiConstants.ACTIVE);
+
+        Long changeToValue = null;
+
+        if (enabled) {
+            changeToValue = LikelihoodStatus.ENABLED;
+        } else {
+            changeToValue = LikelihoodStatus.DISABLED;
+        }
+
+        if (!changeToValue.equals(this.enabled)) {
+            actualChanges.put(LikelihoodApiConstants.ACTIVE, enabled);
+            this.enabled = changeToValue;
+        }
+
+        return actualChanges;
+    }
+
+    public boolean isActivateCommand(final JsonCommand command) {
+        return command.booleanPrimitiveValueOfParameterNamed(LikelihoodApiConstants.ACTIVE);
+    }
+
+    public String getPpiName() {
+        return ppiName;
+    }
+
+    @Override
+    public Long getId() {
+        return super.getId();
+    }
+
+    public void disable() {
+        this.enabled = LikelihoodStatus.DISABLED;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/domain/LikelihoodRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/domain/LikelihoodRepository.java
new file mode 100644
index 0000000..dc49753
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/domain/LikelihoodRepository.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.infrastructure.survey.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;
+
+import java.util.List;
+
+
+public interface LikelihoodRepository extends JpaRepository<Likelihood, Long>, JpaSpecificationExecutor<Likelihood> {
+
+   @Query("FROM Likelihood WHERE ppi_name =:ppiName AND id !=:id")
+   List<Likelihood> findByPpiNameAndLikeliHoodId(@Param("ppiName") String ppiName,@Param("id") Long likeliHoodId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/handler/FullFilSurveyCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/handler/FullFilSurveyCommandHandler.java
new file mode 100644
index 0000000..59e8cfd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/handler/FullFilSurveyCommandHandler.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.infrastructure.survey.handler;
+
+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.infrastructure.survey.service.WriteSurveyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+
+@Service
+public class FullFilSurveyCommandHandler implements NewCommandSourceHandler {
+
+
+    private final WriteSurveyService writePlatformService;
+
+    @Autowired
+    public FullFilSurveyCommandHandler(final WriteSurveyService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.fullFillSurvey (command.entityName(),command.entityId(),command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/handler/RegisterSurveyCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/handler/RegisterSurveyCommandHandler.java
new file mode 100644
index 0000000..e26465a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/handler/RegisterSurveyCommandHandler.java
@@ -0,0 +1,51 @@
+/**
+ * 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.survey.handler;
+
+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.infrastructure.survey.service.WriteLikelihoodService;
+import org.apache.fineract.infrastructure.survey.service.WriteSurveyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+
+@Service
+public class RegisterSurveyCommandHandler implements NewCommandSourceHandler {
+
+
+    private final WriteSurveyService writePlatformService;
+
+    @Autowired
+    public RegisterSurveyCommandHandler(final WriteSurveyService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.registerSurvey(command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/handler/UpdateLikelihoodCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/handler/UpdateLikelihoodCommandHandler.java
new file mode 100644
index 0000000..3eb6f79
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/handler/UpdateLikelihoodCommandHandler.java
@@ -0,0 +1,52 @@
+/**
+ * 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.survey.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.infrastructure.survey.service.WriteLikelihoodService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+
+@Service
+@CommandType(entity = "LIKELIHOOD", action = "UPDATE")
+public class UpdateLikelihoodCommandHandler implements NewCommandSourceHandler {
+
+
+    private final WriteLikelihoodService writePlatformService;
+
+    @Autowired
+    public UpdateLikelihoodCommandHandler(final WriteLikelihoodService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.update(command.entityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/PovertyLineService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/PovertyLineService.java
new file mode 100644
index 0000000..e23f3e5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/PovertyLineService.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.survey.service;
+
+import org.apache.fineract.infrastructure.survey.data.LikeliHoodPovertyLineData;
+import org.apache.fineract.infrastructure.survey.data.PpiPovertyLineData;
+
+public interface PovertyLineService {
+
+    PpiPovertyLineData retrieveAll(final String ppiName);
+
+    LikeliHoodPovertyLineData retrieveForLikelihood(final String ppiName, final Long likelihood);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/PovertyLineServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/PovertyLineServiceImpl.java
new file mode 100644
index 0000000..269a31d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/PovertyLineServiceImpl.java
@@ -0,0 +1,141 @@
+/**
+ * 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.survey.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.survey.data.LikeliHoodPovertyLineData;
+import org.apache.fineract.infrastructure.survey.data.PovertyLineData;
+import org.apache.fineract.infrastructure.survey.data.PpiPovertyLineData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PovertyLineServiceImpl implements PovertyLineService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final DataSource dataSource;
+
+    @Autowired
+    PovertyLineServiceImpl(final RoutingDataSource dataSource) {
+        this.dataSource = dataSource;
+        this.jdbcTemplate = new JdbcTemplate(this.dataSource);
+
+    }
+
+    @Override
+    public PpiPovertyLineData retrieveAll(final String ppiName) {
+
+        final SqlRowSet povertyLines = this._getPovertyLines(ppiName);
+
+        final SqlRowSet likelihoods = this._getLikelihoods();
+
+        List<LikeliHoodPovertyLineData> listOfLikeliHoodPovertyLineData = new ArrayList<>();
+
+        while (likelihoods.next()) {
+            final String codeName = likelihoods.getString("code");
+
+            List<PovertyLineData> povertyLineDatas = new ArrayList<>();
+
+            // create a new povertyLine object when ever it belong to the
+            // current likelihood
+            while (povertyLines.next()) {
+                String likelihoodCode = povertyLines.getString("code");
+
+                if (likelihoodCode.equals(codeName)) {
+                    povertyLineDatas.add(new PovertyLineData(povertyLines.getLong("id"), povertyLines.getLong("score_from"), povertyLines
+                            .getLong("score_to"), povertyLines.getDouble("poverty_line")));
+                }
+            }
+
+            povertyLines.beforeFirst();
+
+            // create the likelihood object with the list of povertyLine object
+            // belonging to it
+
+            LikeliHoodPovertyLineData likeliHoodPovertyLineData = new LikeliHoodPovertyLineData(likelihoods.getLong("id"),
+                    povertyLineDatas, likelihoods.getString("name"), likelihoods.getString("code"), likelihoods.getLong("enabled"));
+
+            listOfLikeliHoodPovertyLineData.add(likeliHoodPovertyLineData);
+
+        }
+
+        PpiPovertyLineData ppiPovertyLineData = new PpiPovertyLineData(listOfLikeliHoodPovertyLineData, ppiName);
+
+        return ppiPovertyLineData;
+    }
+
+    @Override
+    public LikeliHoodPovertyLineData retrieveForLikelihood(final String ppiName, final Long likelihoodId) {
+
+        final SqlRowSet povertyLines = this._getPovertyLines(likelihoodId);
+
+        List<PovertyLineData> povertyLineDatas = new ArrayList<>();
+
+        while (povertyLines.next()) {
+
+            povertyLineDatas.add(new PovertyLineData(povertyLines.getLong("id"), povertyLines.getLong("score_from"), povertyLines
+                    .getLong("score_to"), povertyLines.getDouble("poverty_line")));
+        }
+
+        povertyLines.first();
+
+        // create the likelihood object with the list of povertyLine object
+        // belonging to it
+
+        return new LikeliHoodPovertyLineData(povertyLines.getLong("likelihood_id"), povertyLineDatas, povertyLines.getString("name"),
+                povertyLines.getString("code"), povertyLines.getLong("enabled"));
+
+    }
+
+    private SqlRowSet _getLikelihoods() {
+        String sql = "SELECT lkp.id, lkh.code , lkh.name, lkp.enabled " + " FROM ppi_likelihoods lkh "
+                + " JOIN ppi_likelihoods_ppi lkp on lkp.likelihood_id = lkh.id ";
+        return this.jdbcTemplate.queryForRowSet(sql);
+    }
+
+    private SqlRowSet _getPovertyLines(final String ppiName) {
+        String sql = "SELECT pl.id, sc.score_from, sc.score_to , pl.poverty_line,lkh.code ,  lkh.name , lkp.ppi_name "
+                + " FROM ppi_poverty_line pl " + " JOIN ppi_likelihoods lkh on lkh.id = pl.likelihood_ppi_id "
+                + " JOIN ppi_likelihoods_ppi lkp on lkp.id = pl.likelihood_ppi_id " + " JOIN ppi_scores sc on sc.id = pl.score_id "
+                + " WHERE lkp.ppi_name = ? ";
+
+        return this.jdbcTemplate.queryForRowSet(sql, new Object[] { ppiName });
+
+    }
+
+    private SqlRowSet _getPovertyLines(final Long likelihoodId) {
+        String sql = "SELECT pl.id, sc.score_from, sc.score_to , pl.poverty_line,lkh.code , lkp.enabled, lkp.id as likelihood_id , lkh.name , lkp.ppi_name "
+                + " FROM ppi_poverty_line pl "
+                + " JOIN ppi_likelihoods_ppi lkp on lkp.id = pl.likelihood_ppi_id "
+                + " JOIN ppi_likelihoods lkh on lkh.id = lkp.likelihood_id "
+                + " JOIN ppi_scores sc on sc.id = pl.score_id "
+                + " WHERE pl.likelihood_ppi_id = ? ";
+
+        return this.jdbcTemplate.queryForRowSet(sql, new Object[] { likelihoodId });
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadLikelihoodService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadLikelihoodService.java
new file mode 100644
index 0000000..320ddb3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadLikelihoodService.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.infrastructure.survey.service;
+
+import org.apache.fineract.infrastructure.survey.data.LikelihoodData;
+
+import java.util.List;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+public interface ReadLikelihoodService {
+
+    public List<LikelihoodData> retrieveAll(final String ppiName);
+    public LikelihoodData retrieve(final Long likelihoodId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadLikelihoodServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadLikelihoodServiceImpl.java
new file mode 100644
index 0000000..97c51f1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadLikelihoodServiceImpl.java
@@ -0,0 +1,94 @@
+/**
+ * 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.survey.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.survey.data.LikelihoodData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReadLikelihoodServiceImpl implements ReadLikelihoodService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final DataSource dataSource;
+
+    @Autowired
+    ReadLikelihoodServiceImpl(final RoutingDataSource dataSource) {
+        this.dataSource = dataSource;
+        this.jdbcTemplate = new JdbcTemplate(this.dataSource);
+
+    }
+
+    @Override
+    public List<LikelihoodData> retrieveAll(final String ppiName) {
+        final SqlRowSet likelihood = this._getLikelihood(ppiName);
+
+        List<LikelihoodData> likelihoodDatas = new ArrayList<>();
+
+        while (likelihood.next()) {
+            likelihoodDatas.add(new LikelihoodData(likelihood.getLong("id"), likelihood.getString("name"), likelihood.getString("code"),
+                    likelihood.getLong("enabled")
+
+            ));
+
+        }
+
+        return likelihoodDatas;
+    }
+
+    private SqlRowSet _getLikelihood(final String ppiName) {
+        String sql = "SELECT lkp.id, lkh.code , lkh.name, lkp.enabled " + " FROM ppi_poverty_line pl "
+                + " JOIN ppi_likelihoods_ppi lkp on lkp.id = pl.likelihood_ppi_id "
+                + " JOIN ppi_likelihoods lkh on lkp.likelihood_id = lkh.id " + " WHERE lkp.ppi_name = ? "
+                + " GROUP BY pl.likelihood_ppi_id ";
+
+        return this.jdbcTemplate.queryForRowSet(sql, new Object[] { ppiName });
+
+    }
+
+    @Override
+    public LikelihoodData retrieve(final Long likelihoodId) {
+        final SqlRowSet likelihood = this._getLikelihood(likelihoodId);
+
+        likelihood.first();
+
+        return new LikelihoodData(likelihood.getLong("id"), likelihood.getString("name"), likelihood.getString("code"),
+                likelihood.getLong("enabled")
+
+        );
+
+    }
+
+    private SqlRowSet _getLikelihood(final Long likelihoodId) {
+        String sql = "SELECT lkp.id, lkh.code , lkh.name, lkp.enabled " + " FROM ppi_likelihoods lkh "
+                + " JOIN ppi_likelihoods_ppi lkp on lkp.likelihood_id = lkh.id " + " WHERE lkp.id = ? ";
+
+        return this.jdbcTemplate.queryForRowSet(sql, new Object[] { likelihoodId });
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyService.java
new file mode 100644
index 0000000..7bd192b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyService.java
@@ -0,0 +1,42 @@
+/**
+ * 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.survey.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview;
+import org.apache.fineract.infrastructure.survey.data.SurveyDataTableData;
+
+/**
+ * Created by Cieyou on 2/27/14.
+ */
+public interface ReadSurveyService {
+
+    List<SurveyDataTableData> retrieveAllSurveys();
+
+    SurveyDataTableData retrieveSurvey(String surveyName);
+
+    List<ClientScoresOverview> retrieveClientSurveyScoreOverview(String surveyName, Long clientId);
+
+    List<ClientScoresOverview> retrieveClientSurveyScoreOverview(Long clientId);
+
+    GenericResultsetData retrieveSurveyEntry(String surveyName, Long clientId, Long entryId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java
new file mode 100644
index 0000000..9844006
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java
@@ -0,0 +1,196 @@
+/**
+ * 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.survey.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant;
+import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
+import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview;
+import org.apache.fineract.infrastructure.survey.data.LikelihoodStatus;
+import org.apache.fineract.infrastructure.survey.data.SurveyDataTableData;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReadSurveyServiceImpl implements ReadSurveyService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final DataSource dataSource;
+    private final GenericDataService genericDataService;
+    private final ReadWriteNonCoreDataService readWriteNonCoreDataService;
+
+    @Autowired
+    public ReadSurveyServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final GenericDataService genericDataService, final ReadWriteNonCoreDataService readWriteNonCoreDataService) {
+
+        this.context = context;
+        this.dataSource = dataSource;
+        this.jdbcTemplate = new JdbcTemplate(this.dataSource);
+        this.genericDataService = genericDataService;
+        this.readWriteNonCoreDataService = readWriteNonCoreDataService;
+    }
+
+    @Override
+    public List<SurveyDataTableData> retrieveAllSurveys() {
+
+        String sql = this.retrieveAllSurveySQL("");
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+        final List<SurveyDataTableData> surveyDataTables = new ArrayList<>();
+        while (rs.next()) {
+            final String appTableName = rs.getString("application_table_name");
+            final String registeredDatatableName = rs.getString("registered_table_name");
+            final boolean enabled = rs.getBoolean("enabled");
+            final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService
+                    .fillResultsetColumnHeaders(registeredDatatableName);
+
+            surveyDataTables.add(SurveyDataTableData.create(DatatableData.create(appTableName, registeredDatatableName, columnHeaderData),
+                    enabled));
+        }
+
+        return surveyDataTables;
+    }
+
+    private String retrieveAllSurveySQL(String andClause) {
+        // PERMITTED datatables
+        return "select application_table_name, cf.enabled, registered_table_name" + " from x_registered_table "
+                + " left join c_configuration cf on x_registered_table.registered_table_name = cf.name " + " where exists" + " (select 'f'"
+                + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
+                + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
+                + " where ur.appuser_id = " + this.context.authenticatedUser().getId()
+                + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
+                + " and x_registered_table.category = " + DataTableApiConstant.CATEGORY_PPI + andClause
+                + " order by application_table_name, registered_table_name";
+    }
+
+    @Override
+    public SurveyDataTableData retrieveSurvey(String surveyName) {
+        final String sql = "select cf.enabled, application_table_name, registered_table_name" + " from x_registered_table "
+                + " left join c_configuration cf on x_registered_table.registered_table_name = cf.name " + " where exists" + " (select 'f'"
+                + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
+                + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
+                + " where ur.appuser_id = " + this.context.authenticatedUser().getId() + " and registered_table_name='" + surveyName + "'"
+                + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
+                + " order by application_table_name, registered_table_name";
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+        SurveyDataTableData datatableData = null;
+        while (rs.next()) {
+            final String appTableName = rs.getString("application_table_name");
+            final String registeredDatatableName = rs.getString("registered_table_name");
+            final boolean enabled = rs.getBoolean("enabled");
+            final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService
+                    .fillResultsetColumnHeaders(registeredDatatableName);
+
+            datatableData = SurveyDataTableData.create(DatatableData.create(appTableName, registeredDatatableName, columnHeaderData),
+                    enabled);
+
+        }
+
+        return datatableData;
+    }
+
+    @Override
+    public List<ClientScoresOverview> retrieveClientSurveyScoreOverview(String surveyName, Long clientId) {
+
+        final String sql = "SELECT  tz.id, lkh.name, lkh.code, poverty_line, tz.date, tz.score FROM " + surveyName + " tz"
+                + " JOIN ppi_likelihoods_ppi lkp on lkp.ppi_name = '" + surveyName + "' AND enabled = '" + LikelihoodStatus.ENABLED
+                + "' JOIN ppi_scores sc on score_from  <= tz.score AND score_to >=tz.score"
+                + " JOIN ppi_poverty_line pvl on pvl.likelihood_ppi_id = lkp.id AND pvl.score_id = sc.id"
+                + " JOIN ppi_likelihoods lkh on lkh.id = lkp.likelihood_id " + " WHERE  client_id = " + clientId;
+
+        final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+        List<ClientScoresOverview> scoresOverviews = new ArrayList<>();
+
+        while (rs.next()) {
+            scoresOverviews.add(new ClientScoresOverview(rs.getString("code"), rs.getString("name"), rs.getLong("score"), rs
+                    .getDouble("poverty_line"), new LocalDate(rs.getTimestamp("date").getTime()), rs.getLong("id"), surveyName));
+        }
+
+        return scoresOverviews;
+    }
+
+    @Override
+    public List<ClientScoresOverview> retrieveClientSurveyScoreOverview(Long clientId) {
+        final String surveyNameSql = retrieveAllSurveyNameSQL();
+        final SqlRowSet surveyNames = this.jdbcTemplate.queryForRowSet(surveyNameSql);
+
+        ArrayList<String> sqls = new ArrayList<>();
+
+        while (surveyNames.next()) {
+            sqls.add("SELECT '" + surveyNames.getString("name")
+                    + "' as surveyName, tz.id, lkh.name, lkh.code, poverty_line, tz.date, tz.score FROM " + surveyNames.getString("name")
+                    + " tz" + " JOIN ppi_likelihoods_ppi lkp on lkp.ppi_name = '" + surveyNames.getString("name") + "' AND enabled = '"
+                    + LikelihoodStatus.ENABLED + "' JOIN ppi_scores sc on score_from  <= tz.score AND score_to >=tz.score"
+                    + " JOIN ppi_poverty_line pvl on pvl.likelihood_ppi_id = lkp.id AND pvl.score_id = sc.id"
+                    + " JOIN ppi_likelihoods lkh on lkh.id = lkp.likelihood_id " + " WHERE  client_id = " + clientId);
+        }
+
+        List<ClientScoresOverview> scoresOverviews = new ArrayList<>();
+
+        for (String sql : sqls) {
+            final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+
+            while (rs.next()) {
+                scoresOverviews.add(new ClientScoresOverview(rs.getString("code"), rs.getString("name"), rs.getLong("score"), rs
+                        .getDouble("poverty_line"), new LocalDate(rs.getTimestamp("date").getTime()), rs.getLong("id"), rs
+                        .getString("surveyName")));
+            }
+
+        }
+
+        return scoresOverviews;
+    }
+
+    private String retrieveAllSurveyNameSQL() {
+        // PERMITTED datatables
+        return "select cf.name from x_registered_table "
+                + " join c_configuration cf on x_registered_table.registered_table_name = cf.name " + " where exists" + " (select 'f'"
+                + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
+                + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
+                + " where ur.appuser_id = " + this.context.authenticatedUser().getId()
+                + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
+                + " and x_registered_table.category = " + DataTableApiConstant.CATEGORY_PPI
+                + " order by application_table_name, registered_table_name";
+    }
+
+    @Override
+    public GenericResultsetData retrieveSurveyEntry(String surveyName, Long clientId, Long entryId) {
+
+        return readWriteNonCoreDataService.retrieveDataTableGenericResultSet(surveyName, clientId, null, entryId);
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteLikelihoodService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteLikelihoodService.java
new file mode 100644
index 0000000..e8f75df
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteLikelihoodService.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.survey.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+public interface WriteLikelihoodService {
+
+    CommandProcessingResult update(Long likelihoodId, JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteLikelihoodServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteLikelihoodServiceImpl.java
new file mode 100644
index 0000000..5621e14
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteLikelihoodServiceImpl.java
@@ -0,0 +1,103 @@
+/**
+ * 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.survey.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.survey.data.LikelihoodDataValidator;
+import org.apache.fineract.infrastructure.survey.domain.Likelihood;
+import org.apache.fineract.infrastructure.survey.domain.LikelihoodRepository;
+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;
+
+/**
+ * Created by Cieyou on 3/12/14.
+ */
+@Service
+public class WriteLikelihoodServiceImpl implements WriteLikelihoodService {
+
+    private final static Logger logger = LoggerFactory.getLogger(PovertyLineService.class);
+    private final PlatformSecurityContext context;
+    private final LikelihoodDataValidator likelihoodDataValidator;
+    private final LikelihoodRepository repository;
+
+    @Autowired
+    WriteLikelihoodServiceImpl(final PlatformSecurityContext context, final LikelihoodDataValidator likelihoodDataValidator,
+            final LikelihoodRepository repository) {
+        this.context = context;
+        this.likelihoodDataValidator = likelihoodDataValidator;
+        this.repository = repository;
+
+    }
+
+    @Override
+    public CommandProcessingResult update(Long likelihoodId, JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        try {
+
+            this.likelihoodDataValidator.validateForUpdate(command);
+
+            final Likelihood likelihood = this.repository.findOne(likelihoodId);
+
+            if (!likelihood.update(command).isEmpty()) {
+                this.repository.save(likelihood);
+
+                if (likelihood.isActivateCommand(command)) {
+                    List<Likelihood> likelihoods = this.repository
+                            .findByPpiNameAndLikeliHoodId(likelihood.getPpiName(), likelihood.getId());
+
+                    for (Likelihood aLikelihood : likelihoods) {
+                        aLikelihood.disable();
+                    }
+                    this.repository.save(likelihoods);
+                }
+
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(likelihood.getId()).build();
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.likelihood.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteSurveyService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteSurveyService.java
new file mode 100644
index 0000000..2e98ccb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteSurveyService.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.infrastructure.survey.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+/**
+ * Created by Cieyou on 3/13/14.
+ */
+public interface WriteSurveyService {
+
+    CommandProcessingResult registerSurvey(JsonCommand command);
+
+    CommandProcessingResult fullFillSurvey(String datatable, Long appTableId, JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteSurveyServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteSurveyServiceImpl.java
new file mode 100644
index 0000000..c11103b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/WriteSurveyServiceImpl.java
@@ -0,0 +1,79 @@
+/**
+ * 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.survey.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Created by Cieyou on 3/13/14.
+ */
+@Service
+public class WriteSurveyServiceImpl implements WriteSurveyService {
+
+    private final ReadWriteNonCoreDataService readWriteNonCoreDataService;
+
+    @Autowired(required = true)
+    WriteSurveyServiceImpl(final ReadWriteNonCoreDataService readWriteNonCoreDataService) {
+        this.readWriteNonCoreDataService = readWriteNonCoreDataService;
+
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult registerSurvey(JsonCommand command) {
+
+        final String dataTableName = this.readWriteNonCoreDataService.getDataTableName(command.getUrl());
+        final String permissionSql = this._getPermissionSql(dataTableName);
+        this.readWriteNonCoreDataService.registerDatatable(command, permissionSql);
+
+        return CommandProcessingResult.commandOnlyResult(command.commandId());
+
+    }
+
+    private String _getPermissionSql(final String dataTableName) {
+        final String createPermission = "'CREATE_" + dataTableName + "'";
+        final String createPermissionChecker = "'CREATE_" + dataTableName + "_CHECKER'";
+        final String readPermission = "'READ_" + dataTableName + "'";
+        final String updatePermission = "'UPDATE_" + dataTableName + "'";
+        final String updatePermissionChecker = "'UPDATE_" + dataTableName + "_CHECKER'";
+        final String deletePermission = "'DELETE_" + dataTableName + "'";
+        final String deletePermissionChecker = "'DELETE_" + dataTableName + "_CHECKER'";
+
+        return "insert into m_permission (grouping, code, action_name, entity_name, can_maker_checker) values " + "('datatable', "
+                + createPermission + ", 'CREATE', '" + dataTableName + "', false)," + "('datatable', " + createPermissionChecker
+                + ", 'CREATE', '" + dataTableName + "', false)," + "('datatable', " + readPermission + ", 'READ', '" + dataTableName
+                + "', false)," + "('datatable', " + updatePermission + ", 'UPDATE', '" + dataTableName + "', false)," + "('datatable', "
+                + updatePermissionChecker + ", 'UPDATE', '" + dataTableName + "', false)," + "('datatable', " + deletePermission
+                + ", 'DELETE', '" + dataTableName + "', false)," + "('datatable', " + deletePermissionChecker + ", 'DELETE', '"
+                + dataTableName + "', false)";
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult fullFillSurvey(final String dataTableName, final Long appTableId, final JsonCommand command) {
+
+        return readWriteNonCoreDataService.createPPIEntry(dataTableName, appTableId, command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java
new file mode 100644
index 0000000..7abd936
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java
@@ -0,0 +1,59 @@
+/**
+ * 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.mix.api;
+
+import java.sql.Date;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.mix.data.XBRLData;
+import org.apache.fineract.mix.service.XBRLBuilder;
+import org.apache.fineract.mix.service.XBRLResultService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/mixreport")
+@Component
+@Scope("singleton")
+public class MixReportApiResource {
+
+    private final XBRLResultService xbrlResultService;
+    private final XBRLBuilder xbrlBuilder;
+
+    @Autowired
+    public MixReportApiResource(final XBRLResultService xbrlResultService, final XBRLBuilder xbrlBuilder) {
+        this.xbrlResultService = xbrlResultService;
+        this.xbrlBuilder = xbrlBuilder;
+    }
+
+    @GET
+    @Produces({ MediaType.APPLICATION_XML })
+    public String retrieveXBRLReport(@QueryParam("startDate") final Date startDate, @QueryParam("endDate") final Date endDate,
+            @QueryParam("currency") final String currency) {
+
+        final XBRLData data = this.xbrlResultService.getXBRLResult(startDate, endDate, currency);
+
+        return this.xbrlBuilder.build(data);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java
new file mode 100644
index 0000000..38dde6a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java
@@ -0,0 +1,79 @@
+/**
+ * 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.mix.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.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.mix.data.MixTaxonomyData;
+import org.apache.fineract.mix.service.MixTaxonomyReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/mixtaxonomy")
+@Component
+@Scope("singleton")
+public class MixTaxonomyApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("taxonomyId", "name", "namespace", "dimension",
+            "description"));
+
+    private final PlatformSecurityContext context;
+    private final ToApiJsonSerializer<MixTaxonomyData> toApiJsonSerializer;
+    private final MixTaxonomyReadPlatformService readTaxonomyService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public MixTaxonomyApiResource(final PlatformSecurityContext context, final ToApiJsonSerializer<MixTaxonomyData> toApiJsonSerializer,
+            final MixTaxonomyReadPlatformService readTaxonomyService, final ApiRequestParameterHelper apiRequestParameterHelper) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.readTaxonomyService = readTaxonomyService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        // FIXME - KW - no check for permission to read mix taxonomy data.
+        this.context.authenticatedUser();
+
+        final List<MixTaxonomyData> taxonomyDatas = this.readTaxonomyService.retrieveAll();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return this.toApiJsonSerializer.serialize(settings, taxonomyDatas, this.RESPONSE_DATA_PARAMETERS);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java
new file mode 100644
index 0000000..7d50c51
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java
@@ -0,0 +1,99 @@
+/**
+ * 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.mix.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.mix.data.MixTaxonomyMappingData;
+import org.apache.fineract.mix.service.MixTaxonomyMappingReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/mixmapping")
+@Component
+@Scope("singleton")
+public class MixTaxonomyMappingApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("identifier", "config"));
+
+    private final PlatformSecurityContext context;
+    private final ToApiJsonSerializer<MixTaxonomyMappingData> toApiJsonSerializer;
+    private final MixTaxonomyMappingReadPlatformService readTaxonomyMappingService;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public MixTaxonomyMappingApiResource(final PlatformSecurityContext context,
+            final ToApiJsonSerializer<MixTaxonomyMappingData> toApiJsonSerializer,
+            final MixTaxonomyMappingReadPlatformService readTaxonomyMappingService,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper) {
+
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.readTaxonomyMappingService = readTaxonomyMappingService;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTaxonomyMapping(@Context final UriInfo uriInfo) {
+        this.context.authenticatedUser();
+        final MixTaxonomyMappingData mappingData = this.readTaxonomyMappingService.retrieveTaxonomyMapping();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, mappingData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateTaxonomyMapping(final String jsonRequestBody) {
+        // TODO support multiple configuration file loading
+        final Long mappingId = (long) 1;
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateTaxonomyMapping(mappingId).withJson(jsonRequestBody)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/ContextData.java b/fineract-provider/src/main/java/org/apache/fineract/mix/data/ContextData.java
new file mode 100644
index 0000000..c6ee5c3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/data/ContextData.java
@@ -0,0 +1,72 @@
+/**
+ * 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.mix.data;
+
+public class ContextData {
+
+    private final String dimensionType;
+    private final String dimension;
+    private final Integer periodType;
+
+    public ContextData(final String dimensionType, final String dimension, final Integer taxonomyType) {
+        this.dimensionType = dimensionType;
+        this.dimension = dimension;
+        this.periodType = taxonomyType == MixTaxonomyData.BALANCESHEET || taxonomyType == MixTaxonomyData.PORTFOLIO ? 0 : 1;
+    }
+
+    public String getDimensionType() {
+        return this.dimensionType;
+    }
+
+    public String getDimension() {
+        return this.dimension;
+    }
+
+    public Integer getPeriodType() {
+        return this.periodType;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (this.dimension == null ? 0 : this.dimension.hashCode());
+        result = prime * result + (this.dimensionType == null ? 0 : this.dimensionType.hashCode());
+        result = prime * result + (this.periodType == null ? 0 : this.periodType.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) { return true; }
+        if (obj == null) { return false; }
+        if (getClass() != obj.getClass()) { return false; }
+        final ContextData other = (ContextData) obj;
+        if (this.dimension == null) {
+            if (other.dimension != null) { return false; }
+        } else if (!this.dimension.equals(other.dimension)) { return false; }
+        if (this.dimensionType == null) {
+            if (other.dimensionType != null) { return false; }
+        } else if (!this.dimensionType.equals(other.dimensionType)) { return false; }
+        if (this.periodType == null) {
+            if (other.periodType != null) { return false; }
+        } else if (!this.periodType.equals(other.periodType)) { return false; }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java b/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java
new file mode 100644
index 0000000..58be7a9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java
@@ -0,0 +1,67 @@
+/**
+ * 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.mix.data;
+
+public class MixTaxonomyData {
+
+    public static final Integer PORTFOLIO = 0;
+    public static final Integer BALANCESHEET = 1;
+    public static final Integer INCOME = 2;
+    public static final Integer EXPENSE = 3;
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    private final String name;
+    private final String namespace;
+    private final String dimension;
+    private final Integer type;
+    @SuppressWarnings("unused")
+    private final String description;
+
+    public MixTaxonomyData(final Long id, final String name, final String namespace, final String dimension, final Integer type,
+            final String description) {
+
+        this.id = id;
+        this.name = name;
+        this.namespace = namespace;
+        this.dimension = dimension;
+        this.type = type;
+        this.description = description;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getNamespace() {
+        return this.namespace;
+    }
+
+    public String getDimension() {
+        return this.dimension;
+    }
+
+    public Integer getType() {
+        return this.type;
+    }
+
+    public boolean isPortfolio() {
+        return this.type == 5;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java b/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java
new file mode 100644
index 0000000..11b6b7e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java
@@ -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.
+ */
+package org.apache.fineract.mix.data;
+
+public class MixTaxonomyMappingData {
+
+    private final String identifier;
+    private final String config;
+
+    public MixTaxonomyMappingData(final String identifier, final String config) {
+        this.identifier = identifier;
+        this.config = config;
+    }
+
+    public String getIdentifier() {
+        return this.identifier;
+    }
+
+    public String getConfig() {
+        return this.config;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/NamespaceData.java b/fineract-provider/src/main/java/org/apache/fineract/mix/data/NamespaceData.java
new file mode 100644
index 0000000..f42bbbf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/data/NamespaceData.java
@@ -0,0 +1,39 @@
+/**
+ * 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.mix.data;
+
+public class NamespaceData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String prefix;
+    private final String url;
+
+    public NamespaceData(final Long id, final String prefix, final String url) {
+
+        this.id = id;
+        this.prefix = prefix;
+        this.url = url;
+    }
+
+    public String url() {
+        return this.url;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/XBRLData.java b/fineract-provider/src/main/java/org/apache/fineract/mix/data/XBRLData.java
new file mode 100644
index 0000000..77e3675
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/data/XBRLData.java
@@ -0,0 +1,54 @@
+/**
+ * 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.mix.data;
+
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.util.HashMap;
+
+public class XBRLData {
+
+    private final HashMap<MixTaxonomyData, BigDecimal> resultMap;
+    private final Date startDate;
+    private final Date endDate;
+    private final String currency;
+
+    public XBRLData(final HashMap<MixTaxonomyData, BigDecimal> resultMap, final Date startDate, final Date endDate, final String currency) {
+        this.resultMap = resultMap;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.currency = currency;
+    }
+
+    public HashMap<MixTaxonomyData, BigDecimal> getResultMap() {
+        return this.resultMap;
+    }
+
+    public Date getStartDate() {
+        return this.startDate;
+    }
+
+    public Date getEndDate() {
+        return this.endDate;
+    }
+
+    public String getCurrency() {
+        return this.currency;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java b/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java
new file mode 100644
index 0000000..6573f93
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java
@@ -0,0 +1,67 @@
+/**
+ * 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.mix.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "mix_taxonomy_mapping")
+public class MixTaxonomyMapping extends AbstractPersistable<Long> {
+
+    @Column(name = "identifier")
+    private String identifier;
+
+    @Column(name = "config")
+    private String config;
+
+    @Column(name = "currency")
+    private String currency;
+
+    protected MixTaxonomyMapping() {
+        // default
+    }
+
+    private MixTaxonomyMapping(final String identifier, final String config, final String currency) {
+        this.identifier = StringUtils.defaultIfEmpty(identifier, null);
+        this.config = StringUtils.defaultIfEmpty(config, null);
+        this.currency = StringUtils.defaultIfEmpty(currency, null);
+    }
+
+    public static MixTaxonomyMapping fromJson(final JsonCommand command) {
+        final String identifier = command.stringValueOfParameterNamed("identifier");
+        final String config = command.stringValueOfParameterNamed("config");
+        final String currency = command.stringValueOfParameterNamed("currency");
+        return new MixTaxonomyMapping(identifier, config, currency);
+    }
+
+    public void update(final JsonCommand command) {
+
+        this.identifier = command.stringValueOfParameterNamed("identifier");
+        this.config = command.stringValueOfParameterNamed("config");
+        this.currency = command.stringValueOfParameterNamed("currency");
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.java b/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.java
new file mode 100644
index 0000000..f8c5298
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.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.mix.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface MixTaxonomyMappingRepository extends JpaRepository<MixTaxonomyMapping, Long>, JpaSpecificationExecutor<MixTaxonomyMapping> {
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/exception/XBRLMappingInvalidException.java b/fineract-provider/src/main/java/org/apache/fineract/mix/exception/XBRLMappingInvalidException.java
new file mode 100644
index 0000000..4539061
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/exception/XBRLMappingInvalidException.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.mix.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class XBRLMappingInvalidException extends AbstractPlatformDomainRuleException {
+
+    public XBRLMappingInvalidException(final String msg) {
+        super("error.msg.xbrl.report.mapping.invalid.id", "Mapping does not exist", msg);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/handler/UpdateTaxonomyMappingCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/mix/handler/UpdateTaxonomyMappingCommandHandler.java
new file mode 100644
index 0000000..aab50d4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/handler/UpdateTaxonomyMappingCommandHandler.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.mix.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.mix.service.MixTaxonomyMappingWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "XBRLMAPPING", action = "UPDATE")
+public class UpdateTaxonomyMappingCommandHandler implements NewCommandSourceHandler {
+
+    private final MixTaxonomyMappingWritePlatformService writeTaxonomyService;
+
+    @Autowired
+    public UpdateTaxonomyMappingCommandHandler(final MixTaxonomyMappingWritePlatformService writeTaxonomyService) {
+        this.writeTaxonomyService = writeTaxonomyService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writeTaxonomyService.updateMapping(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformService.java
new file mode 100644
index 0000000..38bbf8f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformService.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.mix.service;
+
+import org.apache.fineract.mix.data.MixTaxonomyMappingData;
+
+public interface MixTaxonomyMappingReadPlatformService {
+
+    MixTaxonomyMappingData retrieveTaxonomyMapping();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java
new file mode 100644
index 0000000..dc3f032
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.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.mix.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.mix.data.MixTaxonomyMappingData;
+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 MixTaxonomyMappingReadPlatformServiceImpl implements MixTaxonomyMappingReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public MixTaxonomyMappingReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class TaxonomyMappingMapper implements RowMapper<MixTaxonomyMappingData> {
+
+        public String schema() {
+            return "identifier, config " + "from mix_taxonomy_mapping";
+        }
+
+        @Override
+        public MixTaxonomyMappingData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final String identifier = rs.getString("identifier");
+            final String config = rs.getString("config");
+            return new MixTaxonomyMappingData(identifier, config);
+        }
+
+    }
+
+    @Override
+    public MixTaxonomyMappingData retrieveTaxonomyMapping() {
+        try {
+            final TaxonomyMappingMapper rm = new TaxonomyMappingMapper();
+            final String sqlString = "select " + rm.schema();
+            return this.jdbcTemplate.queryForObject(sqlString, rm);
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformService.java
new file mode 100644
index 0000000..77fa292
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformService.java
@@ -0,0 +1,27 @@
+/**
+ * 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.mix.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface MixTaxonomyMappingWritePlatformService {
+
+    CommandProcessingResult updateMapping(Long mappingId, JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java
new file mode 100644
index 0000000..dfefd5d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java
@@ -0,0 +1,60 @@
+/**
+ * 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.mix.service;
+
+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.mix.domain.MixTaxonomyMapping;
+import org.apache.fineract.mix.domain.MixTaxonomyMappingRepository;
+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 MixTaxonomyMappingWritePlatformServiceImpl implements MixTaxonomyMappingWritePlatformService {
+
+    private final MixTaxonomyMappingRepository mappingRepository;
+
+    @Autowired
+    public MixTaxonomyMappingWritePlatformServiceImpl(final MixTaxonomyMappingRepository mappingRepository) {
+        this.mappingRepository = mappingRepository;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateMapping(final Long mappingId, final JsonCommand command) {
+        try {
+            MixTaxonomyMapping mapping = this.mappingRepository.findOne(mappingId);
+            if (mapping == null) {
+                mapping = MixTaxonomyMapping.fromJson(command);
+            } else {
+                mapping.update(command);
+            }
+
+            this.mappingRepository.saveAndFlush(mapping);
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(mapping.getId()).build();
+
+        } catch (final DataIntegrityViolationException dve) {
+            return CommandProcessingResult.empty();
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformService.java
new file mode 100644
index 0000000..6f72890
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformService.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.mix.service;
+
+import java.util.List;
+
+import org.apache.fineract.mix.data.MixTaxonomyData;
+
+public interface MixTaxonomyReadPlatformService {
+
+    List<MixTaxonomyData> retrieveAll();
+
+    MixTaxonomyData retrieveOne(Long id);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java
new file mode 100644
index 0000000..25d2124
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.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.mix.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.mix.data.MixTaxonomyData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MixTaxonomyReadPlatformServiceImpl implements MixTaxonomyReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final MixTaxonomyMapper mixTaxonomyMapper;
+
+    @Autowired
+    public MixTaxonomyReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.mixTaxonomyMapper = new MixTaxonomyMapper();
+    }
+
+    private static final class MixTaxonomyMapper implements RowMapper<MixTaxonomyData> {
+
+        public String schema() {
+            return "tx.id as id, name, dimension, type, description, prefix "
+                    + "from mix_taxonomy tx left join mix_xbrl_namespace xn on tx.namespace_id=xn.id";
+        }
+
+        @Override
+        public MixTaxonomyData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final String namespace = rs.getString("prefix");
+
+            final String dimension = rs.getString("dimension");
+            final Integer type = rs.getInt("type");
+            final String desc = rs.getString("description");
+            return new MixTaxonomyData(id, name, namespace, dimension, type, desc);
+        }
+
+    }
+
+    @Override
+    public List<MixTaxonomyData> retrieveAll() {
+        final String sql = "select " + this.mixTaxonomyMapper.schema();
+        return this.jdbcTemplate.query(sql, this.mixTaxonomyMapper);
+    }
+
+    @Override
+    public MixTaxonomyData retrieveOne(final Long id) {
+        final String sql = "select " + this.mixTaxonomyMapper.schema() + " where tx.id =" + id;
+        return this.jdbcTemplate.queryForObject(sql, this.mixTaxonomyMapper);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformService.java
new file mode 100644
index 0000000..aa51754
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformService.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.mix.service;
+
+import org.apache.fineract.mix.data.NamespaceData;
+
+public interface NamespaceReadPlatformService {
+
+    NamespaceData retrieveNamespaceById(Long id);
+
+    NamespaceData retrieveNamespaceByPrefix(String prefix);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java
new file mode 100644
index 0000000..3cfd26b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java
@@ -0,0 +1,72 @@
+/**
+ * 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.mix.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.mix.data.NamespaceData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class NamespaceReadPlatformServiceImpl implements NamespaceReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final NamespaceMapper namespaceMapper;
+
+    @Autowired
+    public NamespaceReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.namespaceMapper = new NamespaceMapper();
+    }
+
+    private static final class NamespaceMapper implements RowMapper<NamespaceData> {
+
+        public String schema() {
+            return "select id, prefix, url " + "from mix_xbrl_namespace";
+        }
+
+        @Override
+        public NamespaceData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final long id = rs.getLong("id");
+            final String prefix = rs.getString("prefix");
+            final String url = rs.getString("url");
+            return new NamespaceData(id, prefix, url);
+        }
+
+    }
+
+    @Override
+    public NamespaceData retrieveNamespaceById(final Long id) {
+        final String sql = this.namespaceMapper.schema() + " where id=" + id;
+
+        return this.jdbcTemplate.queryForObject(sql, this.namespaceMapper);
+    }
+
+    @Override
+    public NamespaceData retrieveNamespaceByPrefix(final String prefix) {
+        final String sql = this.namespaceMapper.schema() + " where prefix='" + prefix + "'";
+
+        return this.jdbcTemplate.queryForObject(sql, this.namespaceMapper);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLBuilder.java
new file mode 100644
index 0000000..eb25a5b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLBuilder.java
@@ -0,0 +1,210 @@
+/**
+ * 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.mix.service;
+
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.fineract.mix.data.ContextData;
+import org.apache.fineract.mix.data.MixTaxonomyData;
+import org.apache.fineract.mix.data.NamespaceData;
+import org.apache.fineract.mix.data.XBRLData;
+import org.apache.fineract.mix.exception.XBRLMappingInvalidException;
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class XBRLBuilder {
+
+    private static final String SCHEME_URL = "http://www.themix.org";
+    private static final String IDENTIFIER = "000000";
+    private static final String UNITID_PURE = "Unit1";
+    private static final String UNITID_CUR = "Unit2";
+
+    private Element root;
+    private HashMap<ContextData, String> contextMap;
+    Date startDate;
+    Date endDate;
+    private Integer instantScenarioCounter = 1;
+    private Integer durationScenarioCounter = 1;
+
+    @Autowired
+    private NamespaceReadPlatformService readNamespaceService;
+
+    public String build(final XBRLData xbrlData) {
+        return this.build(xbrlData.getResultMap(), xbrlData.getStartDate(), xbrlData.getEndDate(), xbrlData.getCurrency());
+    }
+
+    public String build(final Map<MixTaxonomyData, BigDecimal> map, final Date startDate, final Date endDate, final String currency) {
+        this.instantScenarioCounter = 1;
+        this.durationScenarioCounter = 1;
+        this.contextMap = new HashMap<>();
+        final Document doc = DocumentHelper.createDocument();
+        this.root = doc.addElement("xbrl");
+
+        this.root.addElement("schemaRef").addNamespace("link",
+                "http://www.themix.org/sites/default/files/Taxonomy2010/dct/dc-all_2010-08-31.xsd");
+
+        this.startDate = startDate;
+        this.endDate = endDate;
+
+        for (final Entry<MixTaxonomyData, BigDecimal> entry : map.entrySet()) {
+            final MixTaxonomyData taxonomy = entry.getKey();
+            final BigDecimal value = entry.getValue();
+            addTaxonomy(this.root, taxonomy, value);
+
+        }
+
+        addContexts();
+        addCurrencyUnit(currency);
+        addNumberUnit();
+
+        doc.setXMLEncoding("UTF-8");
+
+        return doc.asXML();
+    }
+
+    Element addTaxonomy(final Element rootElement, final MixTaxonomyData taxonomy, final BigDecimal value) {
+
+        // throw an error is start / endate is null
+        if (this.startDate == null || this.endDate == null) { throw new XBRLMappingInvalidException(
+                "start date and end date should not be null"); }
+
+        final String prefix = taxonomy.getNamespace();
+        String qname = taxonomy.getName();
+        if (prefix != null && (!prefix.isEmpty())) {
+            final NamespaceData ns = this.readNamespaceService.retrieveNamespaceByPrefix(prefix);
+            if (ns != null) {
+
+                this.root.addNamespace(prefix, ns.url());
+            }
+            qname = prefix + ":" + taxonomy.getName();
+
+        }
+        final Element xmlElement = rootElement.addElement(qname);
+
+        final String dimension = taxonomy.getDimension();
+        final SimpleDateFormat timeFormat = new SimpleDateFormat("MM_dd_yyyy");
+
+        ContextData context = null;
+        if (dimension != null) {
+            final String[] dims = dimension.split(":");
+
+            if (dims.length == 2) {
+                context = new ContextData(dims[0], dims[1], taxonomy.getType());
+                if (this.contextMap.containsKey(context)) {
+
+                } else {
+
+                }
+            }
+        }
+
+        if (context == null) {
+            context = new ContextData(null, null, taxonomy.getType());
+        }
+
+        if (!this.contextMap.containsKey(context)) {
+
+            final String startDateStr = timeFormat.format(this.startDate);
+            final String endDateStr = timeFormat.format(this.endDate);
+
+            final String contextRefID = (context.getPeriodType() == 0) ? ("As_Of_" + endDateStr + (this.instantScenarioCounter++))
+                    : ("Duration_" + startDateStr + "_To_" + endDateStr + (this.durationScenarioCounter++));
+
+            this.contextMap.put(context, contextRefID);
+        }
+
+        xmlElement.addAttribute("contextRef", this.contextMap.get(context));
+        xmlElement.addAttribute("unitRef", getUnitRef(taxonomy));
+        xmlElement.addAttribute("decimals", getNumberOfDecimalPlaces(value).toString());
+
+        // add the child
+        xmlElement.addText(value.toPlainString());
+
+        return xmlElement;
+    }
+
+    private String getUnitRef(final MixTaxonomyData tx) {
+        return tx.isPortfolio() ? UNITID_PURE : UNITID_CUR;
+    }
+
+    /**
+     * Adds the generic number unit
+     */
+    void addNumberUnit() {
+        final Element numerUnit = this.root.addElement("unit");
+        numerUnit.addAttribute("id", UNITID_PURE);
+        final Element measure = numerUnit.addElement("measure");
+        measure.addText("xbrli:pure");
+
+    }
+
+    /**
+     * Adds the currency unit to the document
+     * 
+     * @param currencyCode
+     */
+    public void addCurrencyUnit(final String currencyCode) {
+        final Element currencyUnitElement = this.root.addElement("unit");
+        currencyUnitElement.addAttribute("id", UNITID_CUR);
+        final Element measure = currencyUnitElement.addElement("measure");
+        measure.addText("iso4217:" + currencyCode);
+
+    }
+
+    public void addContexts() {
+        final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+        for (final Entry<ContextData, String> entry : this.contextMap.entrySet()) {
+            final ContextData context = entry.getKey();
+            final Element contextElement = this.root.addElement("context");
+            contextElement.addAttribute("id", entry.getValue());
+            contextElement.addElement("entity").addElement("identifier").addAttribute("scheme", SCHEME_URL).addText(IDENTIFIER);
+
+            final Element periodElement = contextElement.addElement("period");
+
+            if (context.getPeriodType() == 0) {
+                periodElement.addElement("instant").addText(format.format(this.endDate));
+            } else {
+                periodElement.addElement("startDate").addText(format.format(this.startDate));
+                periodElement.addElement("endDate").addText(format.format(this.endDate));
+            }
+
+            final String dimension = context.getDimension();
+            final String dimType = context.getDimensionType();
+            if (dimType != null && dimension != null) {
+                contextElement.addElement("scenario").addElement("explicitMember").addAttribute("dimension", dimType).addText(dimension);
+            }
+        }
+
+    }
+
+    private Integer getNumberOfDecimalPlaces(final BigDecimal bigDecimal) {
+        final String string = bigDecimal.stripTrailingZeros().toPlainString();
+        final int index = string.indexOf(".");
+        return index < 0 ? 0 : string.length() - index - 1;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultService.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultService.java
new file mode 100644
index 0000000..25d0281
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultService.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.mix.service;
+
+import java.sql.Date;
+
+import org.apache.fineract.mix.data.XBRLData;
+
+public interface XBRLResultService {
+
+    XBRLData getXBRLResult(Date startDate, Date endDate, String currency);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java
new file mode 100644
index 0000000..508bfee
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java
@@ -0,0 +1,207 @@
+/**
+ * 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.mix.service;
+
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.mix.data.MixTaxonomyData;
+import org.apache.fineract.mix.data.MixTaxonomyMappingData;
+import org.apache.fineract.mix.data.XBRLData;
+import org.apache.fineract.mix.exception.XBRLMappingInvalidException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.Gson;
+
+@Component
+public class XBRLResultServiceImpl implements XBRLResultService {
+
+    private static final ScriptEngine SCRIPT_ENGINE = new ScriptEngineManager().getEngineByName("JavaScript");
+
+    private final MixTaxonomyMappingReadPlatformService readTaxonomyMappingService;
+    private final MixTaxonomyReadPlatformService readTaxonomyService;
+    private final JdbcTemplate jdbcTemplate;
+    private HashMap<String, BigDecimal> accountBalanceMap;
+
+    @Autowired
+    public XBRLResultServiceImpl(final RoutingDataSource dataSource,
+            final MixTaxonomyMappingReadPlatformService readTaxonomyMappingService, final MixTaxonomyReadPlatformService readTaxonomyService) {
+        this.readTaxonomyMappingService = readTaxonomyMappingService;
+        this.readTaxonomyService = readTaxonomyService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public XBRLData getXBRLResult(final Date startDate, final Date endDate, final String currency) {
+
+        final HashMap<MixTaxonomyData, BigDecimal> config = retrieveTaxonomyConfig(startDate, endDate);
+        if (config == null || config.size() == 0) { throw new XBRLMappingInvalidException("Mapping is empty"); }
+        return new XBRLData(config, startDate, endDate, currency);
+    }
+
+    @SuppressWarnings("unchecked")
+    private HashMap<MixTaxonomyData, BigDecimal> retrieveTaxonomyConfig(final Date startDate, final Date endDate) {
+        final MixTaxonomyMappingData taxonomyMapping = this.readTaxonomyMappingService.retrieveTaxonomyMapping();
+        if (taxonomyMapping == null) { return null; }
+        final String config = taxonomyMapping.getConfig();
+        if (config != null) {
+            // <taxonomyId, mapping>
+            HashMap<String, String> configMap = new HashMap<>();
+            configMap = new Gson().fromJson(config, configMap.getClass());
+            if (configMap == null) { return null; }
+            // <taxonomyId, value>
+            final HashMap<MixTaxonomyData, BigDecimal> resultMap = new HashMap<>();
+            setupBalanceMap(getAccountSql(startDate, endDate));
+            for (final Entry<String, String> entry : configMap.entrySet()) {
+                final BigDecimal value = processMappingString(entry.getValue());
+                if (value != null) {
+                    final MixTaxonomyData taxonomy = this.readTaxonomyService.retrieveOne(Long.parseLong(entry.getKey()));
+                    resultMap.put(taxonomy, value);
+                }
+
+            }
+            return resultMap;
+        }
+        return null;
+    }
+
+    private String getAccountSql(final Date startDate, final Date endDate) {
+        final String sql = "select debits.glcode as 'glcode', debits.name as 'name', (ifnull(debits.debitamount,0)-ifnull(credits.creditamount,0)) as 'balance' "
+                + "from (select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'debitamount' "
+                + "from acc_gl_journal_entry,acc_gl_account "
+                + "where acc_gl_account.id = acc_gl_journal_entry.account_id "
+                + "and acc_gl_journal_entry.type_enum=2 " + "and acc_gl_journal_entry.entry_date <= "
+                + endDate
+                + " and acc_gl_journal_entry.entry_date > "
+                + startDate
+                +
+                // "and (acc_gl_journal_entry.office_id=${branch} or ${branch}=1) "
+                // +
+                " group by glcode "
+                + "order by glcode) debits "
+                + "LEFT OUTER JOIN "
+                + "(select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'creditamount' "
+                + "from acc_gl_journal_entry,acc_gl_account "
+                + "where acc_gl_account.id = acc_gl_journal_entry.account_id "
+                + "and acc_gl_journal_entry.type_enum=1 "
+                + "and acc_gl_journal_entry.entry_date <= "
+                + endDate
+                + " and acc_gl_journal_entry.entry_date > "
+                + startDate
+                +
+                // "and (acc_gl_journal_entry.office_id=${branch} or ${branch}=1) "
+                // +
+                " group by glcode "
+                + "order by glcode) credits "
+                + "on debits.glcode=credits.glcode "
+                + "union "
+                + "select credits.glcode as 'glcode', credits.name as 'name', (ifnull(debits.debitamount,0)-ifnull(credits.creditamount,0)) as 'balance' "
+                + "from (select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'debitamount' "
+                + "from acc_gl_journal_entry,acc_gl_account "
+                + "where acc_gl_account.id = acc_gl_journal_entry.account_id "
+                + "and acc_gl_journal_entry.type_enum=2 "
+                + "and acc_gl_journal_entry.entry_date <= "
+                + endDate
+                + " and acc_gl_journal_entry.entry_date > "
+                + startDate
+                +
+                // "and (acc_gl_journal_entry.office_id=${branch} or ${branch}=1) "
+                // +
+                " group by glcode "
+                + "order by glcode) debits "
+                + "RIGHT OUTER JOIN "
+                + "(select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'creditamount' "
+                + "from acc_gl_journal_entry,acc_gl_account "
+                + "where acc_gl_account.id = acc_gl_journal_entry.account_id "
+                + "and acc_gl_journal_entry.type_enum=1 "
+                + "and acc_gl_journal_entry.entry_date <= "
+                + endDate
+                + " and acc_gl_journal_entry.entry_date > " + startDate +
+                // "and (acc_gl_journal_entry.office_id=${branch} or ${branch}=1) "
+                // +
+                " group by name " + "order by glcode) credits " + "on debits.glcode=credits.glcode;";
+        return sql;
+    }
+
+    private void setupBalanceMap(final String sql) {
+        if (this.accountBalanceMap == null) {
+            this.accountBalanceMap = new HashMap<>();
+            final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
+            while (rs.next()) {
+                this.accountBalanceMap.put(rs.getString("glcode"), rs.getBigDecimal("balance"));
+            }
+        }
+    }
+
+    // Calculate Taxonomy value from expression
+    private BigDecimal processMappingString(String mappingString) {
+        final ArrayList<String> glCodes = getGLCodes(mappingString);
+        for (final String glcode : glCodes) {
+
+            final BigDecimal balance = this.accountBalanceMap.get(glcode);
+            mappingString = mappingString.replaceAll("\\{" + glcode + "\\}", balance != null ? balance.toString() : "0");
+        }
+
+        // evaluate the expression
+        Float eval = 0f;
+        try {
+            final Number value = (Number) SCRIPT_ENGINE.eval(mappingString);
+            if (value != null) {
+                eval = value.floatValue();
+            }
+        } catch (final ScriptException e) {
+            e.printStackTrace();
+            throw new IllegalArgumentException(e.getMessage());
+        }
+
+        return new BigDecimal(eval);
+    }
+
+    public ArrayList<String> getGLCodes(final String template) {
+
+        final ArrayList<String> placeholders = new ArrayList<>();
+
+        if (template != null) {
+
+            final Pattern p = Pattern.compile("\\{(.*?)\\}");
+            final Matcher m = p.matcher(template);
+
+            while (m.find()) { // find next match
+                final String match = m.group();
+                final String code = match.substring(1, match.length() - 1);
+                placeholders.add(code);
+            }
+
+        }
+        return placeholders;
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..5bf2ba7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidayApiConstants.java
@@ -0,0 +1,52 @@
+/**
+ * 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.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class HolidayApiConstants {
+
+    public static final String HOLIDAY_RESOURCE_NAME = "holiday";
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // request parameters
+    public static final String idParamName = "id";
+    public static final String nameParamName = "name";
+    public static final String fromDateParamName = "fromDate";
+    public static final String toDateParamName = "toDate";
+    public static final String descriptionParamName = "description";
+    public static final String officesParamName = "offices";
+    public static final String officeIdParamName = "officeId";
+    public static final String repaymentsRescheduledToParamName = "repaymentsRescheduledTo";
+    public static final String processed = "processed";
+    public static final String status = "status";
+
+    public static final Set<String> HOLIDAY_CREATE_OR_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, nameParamName, fromDateParamName, toDateParamName, descriptionParamName, officesParamName,
+            repaymentsRescheduledToParamName));
+
+    public static final Set<String> HOLIDAY_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, nameParamName,
+            fromDateParamName, descriptionParamName, toDateParamName, repaymentsRescheduledToParamName, localeParamName,
+            dateFormatParamName, status));
+}
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
new file mode 100644
index 0000000..061f225
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
@@ -0,0 +1,183 @@
+/**
+ * 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.api;
+
+import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.HOLIDAY_RESOURCE_NAME;
+import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.HOLIDAY_RESPONSE_DATA_PARAMETERS;
+
+import java.util.Collection;
+import java.util.Date;
+
+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.QueryParam;
+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.accounting.journalentry.api.DateParam;
+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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.holiday.data.HolidayData;
+import org.apache.fineract.organisation.holiday.service.HolidayReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/holidays")
+@Component
+@Scope("singleton")
+public class HolidaysApiResource {
+
+    private final DefaultToApiJsonSerializer<HolidayData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final PlatformSecurityContext context;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    private final HolidayReadPlatformService holidayReadPlatformService;
+
+    @Autowired
+    public HolidaysApiResource(final DefaultToApiJsonSerializer<HolidayData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, final PlatformSecurityContext context,
+            final ApiRequestParameterHelper apiRequestParameterHelper, final HolidayReadPlatformService holidayReadPlatformService) {
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.context = context;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.holidayReadPlatformService = holidayReadPlatformService;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createNewHoliday(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createHoliday().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{holidayId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String handleCommands(@PathParam("holidayId") final Long holidayId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        String jsonApiRequest = apiRequestBodyAsJson;
+        if (StringUtils.isBlank(jsonApiRequest)) {
+            jsonApiRequest = "{}";
+        }
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "activate")) {
+            final CommandWrapper commandRequest = builder.activateHoliday(holidayId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate" }); }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{holidayId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("holidayId") final Long holidayId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(HOLIDAY_RESOURCE_NAME);
+
+        final HolidayData holidayData = this.holidayReadPlatformService.retrieveHoliday(holidayId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return this.toApiJsonSerializer.serialize(settings, holidayData, HOLIDAY_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{holidayId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("holidayId") final Long holidayId, final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateHoliday(holidayId).withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{holidayId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("holidayId") final Long holidayId) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteHoliday(holidayId).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);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllHolidays(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId,
+            @QueryParam("fromDate") final DateParam fromDateParam, @QueryParam("toDate") final DateParam toDateParam,
+            @QueryParam("locale") final String locale, @QueryParam("dateFormat") final String dateFormat) {
+
+        this.context.authenticatedUser().validateHasReadPermission(HOLIDAY_RESOURCE_NAME);
+
+        Date fromDate = null;
+        if (fromDateParam != null) {
+            fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
+        }
+        Date toDate = null;
+        if (toDateParam != null) {
+            toDate = toDateParam.getDate("toDate", dateFormat, locale);
+        }
+
+        final Collection<HolidayData> holidays = this.holidayReadPlatformService.retrieveAllHolidaysBySearchParamerters(officeId, fromDate,
+                toDate);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, holidays, HOLIDAY_RESPONSE_DATA_PARAMETERS);
+    }
+}
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
new file mode 100644
index 0000000..70f9839
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayData.java
@@ -0,0 +1,54 @@
+/**
+ * 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.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.joda.time.LocalDate;
+
+public class HolidayData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String name;
+    @SuppressWarnings("unused")
+    private final String description;
+    @SuppressWarnings("unused")
+    private final LocalDate fromDate;
+    @SuppressWarnings("unused")
+    private final LocalDate toDate;
+    @SuppressWarnings("unused")
+    private final LocalDate repaymentsRescheduledTo;
+    @SuppressWarnings("unused")
+    private final Long officeId;
+    @SuppressWarnings("unused")
+    private final EnumOptionData status;
+
+    public HolidayData(final Long id, final String name, final String description, final LocalDate fromDate, final LocalDate toDate,
+            final LocalDate repaymentsRescheduledTo, final EnumOptionData status) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.fromDate = fromDate;
+        this.toDate = toDate;
+        this.repaymentsRescheduledTo = repaymentsRescheduledTo;
+        this.officeId = null;
+        this.status = status;
+    }
+}
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
new file mode 100644
index 0000000..264af0e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayDataValidator.java
@@ -0,0 +1,166 @@
+/**
+ * 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.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+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.apache.fineract.organisation.holiday.api.HolidayApiConstants;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class HolidayDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public HolidayDataValidator(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,
+                HolidayApiConstants.HOLIDAY_CREATE_OR_UPDATE_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(HolidayApiConstants.HOLIDAY_RESOURCE_NAME);
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(HolidayApiConstants.nameParamName, element);
+        baseDataValidator.reset().parameter(HolidayApiConstants.nameParamName).value(name).notNull().notExceedingLengthOf(100);
+
+        final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.fromDateParamName, element);
+        baseDataValidator.reset().parameter(HolidayApiConstants.fromDateParamName).value(fromDate).notNull();
+
+        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();
+
+        Set<Long> offices = null;
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+
+        if (topLevelJsonElement.has(HolidayApiConstants.officesParamName)
+                && topLevelJsonElement.get(HolidayApiConstants.officesParamName).isJsonArray()) {
+
+            final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.officesParamName).getAsJsonArray();
+            if (array.size() > 0) {
+                offices = new HashSet<>(array.size());
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject officeElement = array.get(i).getAsJsonObject();
+                    final Long officeId = this.fromApiJsonHelper.extractLongNamed(HolidayApiConstants.officeIdParamName, officeElement);
+                    baseDataValidator.reset().parameter(HolidayApiConstants.officesParamName).value(officeId).notNull();
+                    offices.add(officeId);
+                }
+            }
+        }
+        baseDataValidator.reset().parameter(HolidayApiConstants.officesParamName).value(offices).notNull();
+        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,
+                HolidayApiConstants.HOLIDAY_CREATE_OR_UPDATE_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(HolidayApiConstants.HOLIDAY_RESOURCE_NAME);
+
+        if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.nameParamName, element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed(HolidayApiConstants.nameParamName, element);
+            baseDataValidator.reset().parameter(HolidayApiConstants.nameParamName).value(name).notNull().notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.fromDateParamName, element)) {
+            final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.fromDateParamName, element);
+            baseDataValidator.reset().parameter(HolidayApiConstants.fromDateParamName).value(fromDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.toDateParamName, element)) {
+            final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDateParamName, element);
+            baseDataValidator.reset().parameter(HolidayApiConstants.toDateParamName).value(toDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.repaymentsRescheduledToParamName, element)) {
+            final LocalDate repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(
+                    HolidayApiConstants.repaymentsRescheduledToParamName, element);
+            baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo)
+                    .notNull();
+        }
+
+        Set<Long> offices = null;
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.officesParamName, element)) {
+            if (topLevelJsonElement.has(HolidayApiConstants.officesParamName)
+                    && topLevelJsonElement.get(HolidayApiConstants.officesParamName).isJsonArray()) {
+
+                final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.officesParamName).getAsJsonArray();
+                if (array.size() > 0) {
+                    offices = new HashSet<>(array.size());
+                    for (int i = 0; i < array.size(); i++) {
+                        final JsonObject officeElement = array.get(i).getAsJsonObject();
+                        final Long officeId = this.fromApiJsonHelper.extractLongNamed(HolidayApiConstants.officeIdParamName, officeElement);
+                        baseDataValidator.reset().parameter(HolidayApiConstants.officesParamName).value(officeId).notNull();
+                        offices.add(officeId);
+                    }
+                }
+            }
+            baseDataValidator.reset().parameter(HolidayApiConstants.officesParamName).value(offices).notNull();
+            throwExceptionIfValidationWarningsExist(dataValidationErrors);
+        }
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            //
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+}
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
new file mode 100644
index 0000000..e506a58
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/Holiday.java
@@ -0,0 +1,301 @@
+/**
+ * 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;
+
+import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.descriptionParamName;
+import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.fromDateParamName;
+import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.nameParamName;
+import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.officesParamName;
+import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.repaymentsRescheduledToParamName;
+import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.toDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dateFormatParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.organisation.holiday.api.HolidayApiConstants;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import com.google.gson.JsonArray;
+
+@Entity
+@Table(name = "m_holiday", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "holiday_name") })
+public class Holiday extends AbstractPersistable<Long> {
+
+    @Column(name = "name", unique = true, nullable = false, length = 100)
+    private String name;
+
+    @Column(name = "from_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date fromDate;
+
+    @Column(name = "to_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date toDate;
+
+    @Column(name = "repayments_rescheduled_to", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date repaymentsRescheduledTo;
+
+    @Column(name = "status_enum", nullable = false)
+    private Integer status;
+
+    @Column(name = "processed", nullable = false)
+    private boolean processed;
+
+    @Column(name = "description", length = 100)
+    private String description;
+
+    @ManyToMany(fetch = FetchType.EAGER)
+    @JoinTable(name = "m_holiday_office", joinColumns = @JoinColumn(name = "holiday_id"), inverseJoinColumns = @JoinColumn(name = "office_id"))
+    private Set<Office> offices;
+
+    public static Holiday createNew(final Set<Office> offices, final JsonCommand command) {
+        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);
+        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);
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("holiday" + ".update");
+
+        final HolidayStatusType currentStatus = HolidayStatusType.fromInt(this.status);
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+
+        if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
+            final String newValue = command.stringValueOfParameterNamed(nameParamName);
+            actualChanges.put(nameParamName, newValue);
+            this.name = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (currentStatus.isPendingActivation()) {
+            if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateLocalDate())) {
+                final String valueAsInput = command.stringValueOfParameterNamed(fromDateParamName);
+                actualChanges.put(fromDateParamName, valueAsInput);
+                actualChanges.put(dateFormatParamName, dateFormatAsInput);
+                actualChanges.put(localeParamName, localeAsInput);
+                final LocalDate newValue = command.localDateValueOfParameterNamed(fromDateParamName);
+                this.fromDate = newValue.toDate();
+            }
+
+            if (command.isChangeInLocalDateParameterNamed(toDateParamName, getToDateLocalDate())) {
+                final String valueAsInput = command.stringValueOfParameterNamed(toDateParamName);
+                actualChanges.put(toDateParamName, valueAsInput);
+                actualChanges.put(dateFormatParamName, dateFormatAsInput);
+                actualChanges.put(localeParamName, localeAsInput);
+
+                final LocalDate newValue = command.localDateValueOfParameterNamed(toDateParamName);
+                this.toDate = newValue.toDate();
+            }
+
+            if (command.isChangeInLocalDateParameterNamed(repaymentsRescheduledToParamName, getRepaymentsRescheduledToLocalDate())) {
+                final String valueAsInput = command.stringValueOfParameterNamed(repaymentsRescheduledToParamName);
+                actualChanges.put(repaymentsRescheduledToParamName, valueAsInput);
+                actualChanges.put(dateFormatParamName, dateFormatAsInput);
+                actualChanges.put(localeParamName, localeAsInput);
+
+                final LocalDate newValue = command.localDateValueOfParameterNamed(repaymentsRescheduledToParamName);
+                this.repaymentsRescheduledTo = newValue.toDate();
+            }
+
+            if (command.hasParameter(officesParamName)) {
+                final JsonArray jsonArray = command.arrayOfParameterNamed(officesParamName);
+                if (jsonArray != null) {
+                    actualChanges.put(officesParamName, command.jsonFragment(officesParamName));
+                }
+            }
+        } else {
+            if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateLocalDate())) {
+                baseDataValidator.reset().parameter(fromDateParamName).failWithCode("cannot.edit.holiday.in.active.state");
+            }
+
+            if (command.isChangeInLocalDateParameterNamed(toDateParamName, getToDateLocalDate())) {
+                baseDataValidator.reset().parameter(toDateParamName).failWithCode("cannot.edit.holiday.in.active.state");
+            }
+
+            if (command.isChangeInLocalDateParameterNamed(repaymentsRescheduledToParamName, getRepaymentsRescheduledToLocalDate())) {
+                baseDataValidator.reset().parameter(repaymentsRescheduledToParamName).failWithCode("cannot.edit.holiday.in.active.state");
+            }
+
+            if (command.hasParameter(officesParamName)) {
+                baseDataValidator.reset().parameter(repaymentsRescheduledToParamName).failWithCode("cannot.edit.holiday.in.active.state");
+            }
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        return actualChanges;
+    }
+
+    public boolean update(final Set<Office> newOffices) {
+        if (newOffices == null) { return false; }
+
+        boolean updated = false;
+        if (this.offices != null) {
+            final Set<Office> currentSetOfOffices = new HashSet<>(this.offices);
+            final Set<Office> newSetOfOffices = new HashSet<>(newOffices);
+
+            if (!(currentSetOfOffices.equals(newSetOfOffices))) {
+                updated = true;
+                this.offices = newOffices;
+            }
+        } else {
+            updated = true;
+            this.offices = newOffices;
+        }
+        return updated;
+    }
+
+    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) {
+        if (StringUtils.isNotBlank(name)) {
+            this.name = name.trim();
+        }
+
+        if (fromDate != null) {
+            this.fromDate = fromDate.toDate();
+        }
+
+        if (toDate != null) {
+            this.toDate = toDate.toDate();
+        }
+
+        if (repaymentsRescheduledTo != null) {
+            this.repaymentsRescheduledTo = repaymentsRescheduledTo.toDate();
+        }
+
+        this.status = status;
+        this.processed = processed;
+
+        if (StringUtils.isNotBlank(name)) {
+            this.description = description.trim();
+        } else {
+            this.description = null;
+        }
+
+        if (offices != null) {
+            this.offices = offices;
+        }
+    }
+
+    protected Holiday() {}
+
+    public LocalDate getRepaymentsRescheduledToLocalDate() {
+        LocalDate repaymentsRescheduledTo = null;
+        if (this.repaymentsRescheduledTo != null) {
+            repaymentsRescheduledTo = new LocalDate(this.repaymentsRescheduledTo);
+        }
+        return repaymentsRescheduledTo;
+    }
+
+    public boolean isProcessed() {
+        return this.processed;
+    }
+
+    public Set<Office> getOffices() {
+        return this.offices;
+    }
+
+    public LocalDate getFromDateLocalDate() {
+        LocalDate fromDate = null;
+        if (this.fromDate != null) {
+            fromDate = new LocalDate(this.fromDate);
+        }
+        return fromDate;
+    }
+
+    public LocalDate getToDateLocalDate() {
+        LocalDate toDate = null;
+        if (this.toDate != null) {
+            toDate = new LocalDate(this.toDate);
+        }
+        return toDate;
+    }
+
+    public void processed() {
+        this.processed = true;
+    }
+
+    public void activate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("holiday" + ".activate");
+
+        final HolidayStatusType currentStatus = HolidayStatusType.fromInt(this.status);
+        if (!currentStatus.isPendingActivation()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.pending.for.activation.state");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.status = HolidayStatusType.ACTIVE.getValue();
+    }
+
+    public void delete() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("holiday" + ".delete");
+
+        final HolidayStatusType currentStatus = HolidayStatusType.fromInt(this.status);
+        if (currentStatus.isDeleted()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("already.in.deleted.state");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        this.status = HolidayStatusType.DELETED.getValue();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayRepository.java
new file mode 100644
index 0000000..9be986e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayRepository.java
@@ -0,0 +1,37 @@
+/**
+ * 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;
+
+import java.util.Date;
+import java.util.List;
+
+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 HolidayRepository extends JpaRepository<Holiday, Long>, JpaSpecificationExecutor<Holiday> {
+
+    @Query("select holiday from Holiday holiday, IN(holiday.offices) office where (holiday.fromDate >= :date OR :date <= holiday.toDate) and holiday.status = :status and office.id = :officeId")
+    List<Holiday> findByOfficeIdAndGreaterThanDate(@Param("officeId") Long officeId, @Param("date") Date date,
+            @Param("status") Integer status);
+
+    @Query("from Holiday holiday where holiday.processed = false and holiday.status = :status")
+    List<Holiday> findUnprocessed(@Param("status") Integer status);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayRepositoryWrapper.java
new file mode 100644
index 0000000..1c7f2d7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayRepositoryWrapper.java
@@ -0,0 +1,80 @@
+/**
+ * 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;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.organisation.holiday.exception.HolidayNotFoundException;
+import org.apache.fineract.organisation.holiday.service.HolidayUtil;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link HolidayRepository} that adds NULL checking and Error
+ * handling capabilities
+ * </p>
+ */
+@Service
+public class HolidayRepositoryWrapper {
+
+    private final HolidayRepository repository;
+
+    @Autowired
+    public HolidayRepositoryWrapper(final HolidayRepository repository) {
+        this.repository = repository;
+    }
+
+    public Holiday findOneWithNotFoundDetection(final Long id) {
+        final Holiday holiday = this.repository.findOne(id);
+        if (holiday == null) { throw new HolidayNotFoundException(id); }
+        return holiday;
+    }
+
+    public void save(final Holiday holiday) {
+        this.repository.save(holiday);
+    }
+
+    public void save(final Iterable<Holiday> holidays) {
+        this.repository.save(holidays);
+    }
+
+    public void saveAndFlush(final Holiday holiday) {
+        this.repository.saveAndFlush(holiday);
+    }
+
+    public void delete(final Holiday holiday) {
+        this.repository.delete(holiday);
+    }
+
+    public List<Holiday> findByOfficeIdAndGreaterThanDate(final Long officeId, final Date date) {
+        return this.repository.findByOfficeIdAndGreaterThanDate(officeId, date, HolidayStatusType.ACTIVE.getValue());
+    }
+
+    public List<Holiday> findUnprocessed() {
+        return this.repository.findUnprocessed(HolidayStatusType.ACTIVE.getValue());
+    }
+
+    public boolean isHoliday(Long officeId, LocalDate transactionDate) {
+        final List<Holiday> holidays = findByOfficeIdAndGreaterThanDate(officeId, transactionDate.toDate());
+        return HolidayUtil.isHoliday(transactionDate, holidays);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayStatusType.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayStatusType.java
new file mode 100644
index 0000000..4dd4760
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayStatusType.java
@@ -0,0 +1,78 @@
+/**
+ * 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;
+
+/**
+ * Enum representation of {@link Holiday} status states.
+ */
+public enum HolidayStatusType {
+
+    INVALID(0, "holidayStatusType.invalid"), //
+    PENDING_FOR_ACTIVATION(100, "holidayStatusType.pending.for.activation"), //
+    ACTIVE(300, "holidayStatusType.active"), //
+    DELETED(600, "savingsAccountStatusType.transfer.in.progress");
+
+    private final Integer value;
+    private final String code;
+
+    public static HolidayStatusType fromInt(final Integer type) {
+        HolidayStatusType enumeration = HolidayStatusType.INVALID;
+        switch (type) {
+            case 100:
+                enumeration = HolidayStatusType.PENDING_FOR_ACTIVATION;
+            break;
+            case 300:
+                enumeration = HolidayStatusType.ACTIVE;
+            break;
+            case 600:
+                enumeration = HolidayStatusType.DELETED;
+            break;
+        }
+        return enumeration;
+    }
+
+    private HolidayStatusType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final HolidayStatusType state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isPendingActivation() {
+        return this.value.equals(HolidayStatusType.PENDING_FOR_ACTIVATION.getValue());
+    }
+
+    public boolean isActive() {
+        return this.value.equals(HolidayStatusType.ACTIVE.getValue());
+    }
+
+    public boolean isDeleted() {
+        return this.value.equals(HolidayStatusType.DELETED.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/exception/HolidayDateException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/exception/HolidayDateException.java
new file mode 100644
index 0000000..16349ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/exception/HolidayDateException.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.organisation.holiday.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class HolidayDateException extends AbstractPlatformDomainRuleException {
+
+    public HolidayDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.holiday." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/exception/HolidayNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/exception/HolidayNotFoundException.java
new file mode 100644
index 0000000..18adf6a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/exception/HolidayNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.holiday.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when holiday resources are not found.
+ */
+public class HolidayNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public HolidayNotFoundException(final Long id) {
+        super("error.msg.holiday.id.invalid", "Holiday with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/ActivateHolidayCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/ActivateHolidayCommandHandler.java
new file mode 100644
index 0000000..62df041
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/ActivateHolidayCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.holiday.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.organisation.holiday.service.HolidayWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "HOLIDAY", action = "ACTIVATE")
+public class ActivateHolidayCommandHandler implements NewCommandSourceHandler {
+
+    private final HolidayWritePlatformService holidayWritePlatformService;
+
+    @Autowired
+    public ActivateHolidayCommandHandler(final HolidayWritePlatformService holidayWritePlatformService) {
+        this.holidayWritePlatformService = holidayWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.holidayWritePlatformService.activateHoliday(command.entityId());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/CreateHolidayCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/CreateHolidayCommandHandler.java
new file mode 100644
index 0000000..02cc70b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/CreateHolidayCommandHandler.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.organisation.holiday.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.organisation.holiday.service.HolidayWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "HOLIDAY", action = "CREATE")
+public class CreateHolidayCommandHandler implements NewCommandSourceHandler {
+
+    private final HolidayWritePlatformService holidayWritePlatformService;
+
+    @Autowired
+    public CreateHolidayCommandHandler(final HolidayWritePlatformService holidayWritePlatformService) {
+        this.holidayWritePlatformService = holidayWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.holidayWritePlatformService.createHoliday(command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/DeleteHolidayCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/DeleteHolidayCommandHandler.java
new file mode 100644
index 0000000..10a36a6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/DeleteHolidayCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.holiday.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.organisation.holiday.service.HolidayWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "HOLIDAY", action = "DELETE")
+public class DeleteHolidayCommandHandler implements NewCommandSourceHandler {
+
+    private final HolidayWritePlatformService holidayWritePlatformService;
+
+    @Autowired
+    public DeleteHolidayCommandHandler(final HolidayWritePlatformService holidayWritePlatformService) {
+        this.holidayWritePlatformService = holidayWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.holidayWritePlatformService.deleteHoliday(command.entityId());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/UpdateHolidayCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/UpdateHolidayCommandHandler.java
new file mode 100644
index 0000000..1149730
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/handler/UpdateHolidayCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.holiday.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.organisation.holiday.service.HolidayWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "HOLIDAY", action = "UPDATE")
+public class UpdateHolidayCommandHandler implements NewCommandSourceHandler {
+
+    private final HolidayWritePlatformService holidayWritePlatformService;
+
+    @Autowired
+    public UpdateHolidayCommandHandler(final HolidayWritePlatformService holidayWritePlatformService) {
+        this.holidayWritePlatformService = holidayWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.holidayWritePlatformService.updateHoliday(command);
+    }
+}
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
new file mode 100644
index 0000000..43e22d0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayEnumerations.java
@@ -0,0 +1,52 @@
+/**
+ * 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.service;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
+
+public class HolidayEnumerations {
+
+    public static EnumOptionData holidayStatusType(final int id) {
+        return holidayStatusType(HolidayStatusType.fromInt(id));
+    }
+
+    public static EnumOptionData holidayStatusType(final HolidayStatusType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case INVALID:
+                optionData = new EnumOptionData(HolidayStatusType.INVALID.getValue().longValue(), HolidayStatusType.INVALID.getCode(),
+                        "Invalid");
+            break;
+            case PENDING_FOR_ACTIVATION:
+                optionData = new EnumOptionData(HolidayStatusType.PENDING_FOR_ACTIVATION.getValue().longValue(),
+                        HolidayStatusType.PENDING_FOR_ACTIVATION.getCode(), "Pending for activation");
+            break;
+            case ACTIVE:
+                optionData = new EnumOptionData(HolidayStatusType.ACTIVE.getValue().longValue(), HolidayStatusType.ACTIVE.getCode(),
+                        "Active");
+            break;
+            case DELETED:
+                optionData = new EnumOptionData(HolidayStatusType.DELETED.getValue().longValue(), HolidayStatusType.DELETED.getCode(),
+                        "Deleted");
+            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
new file mode 100644
index 0000000..309af96
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.holiday.service;
+
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.fineract.organisation.holiday.data.HolidayData;
+
+public interface HolidayReadPlatformService {
+
+    Collection<HolidayData> retrieveAllHolidaysBySearchParamerters(final Long officeId, Date fromDate, Date toDate);
+
+    HolidayData retrieveHoliday(final Long holidayId);
+}
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
new file mode 100644
index 0000000..2601d21
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java
@@ -0,0 +1,137 @@
+/**
+ * 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.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+
+import 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.exception.HolidayNotFoundException;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public HolidayReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class HolidayMapper implements RowMapper<HolidayData> {
+
+        private final String schema;
+
+        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("from m_holiday h ");
+            this.schema = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+
+        @Override
+        public HolidayData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final String description = rs.getString("description");
+            final LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromDate");
+            final LocalDate toDate = JdbcSupport.getLocalDate(rs, "toDate");
+            final LocalDate repaymentsScheduleTO = JdbcSupport.getLocalDate(rs, "repaymentsScheduleTO");
+            final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final EnumOptionData status = HolidayEnumerations.holidayStatusType(statusEnum);
+
+            return new HolidayData(id, name, description, fromDate, toDate, repaymentsScheduleTO, status);
+        }
+
+    }
+
+    @Override
+    public Collection<HolidayData> retrieveAllHolidaysBySearchParamerters(final Long officeId, final Date fromDate, final Date toDate) {
+        this.context.authenticatedUser();
+
+        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+
+        final Object[] objectArray = new Object[3];
+        int arrayPos = 0;
+
+        final HolidayMapper rm = new HolidayMapper();
+        String sql = "select " + rm.schema() + " join m_holiday_office hf on h.id = hf.holiday_id and hf.office_id = ? ";
+
+        objectArray[arrayPos] = officeId;
+        arrayPos = arrayPos + 1;
+
+        if (fromDate != null || toDate != null) {
+            sql += "and ";
+
+            if (fromDate != null) {
+                sql += "h.from_Date >= ? ";
+
+                objectArray[arrayPos] = df.format(fromDate);
+                arrayPos = arrayPos + 1;
+            }
+
+            if (toDate != null) {
+                sql += fromDate != null ? "and " : "";
+                sql += "h.to_date <= ? ";
+                objectArray[arrayPos] = df.format(toDate);
+                arrayPos = arrayPos + 1;
+            }
+        }
+
+        final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
+
+        return this.jdbcTemplate.query(sql, rm, finalObjectArray);
+    }
+
+    @Override
+    public HolidayData retrieveHoliday(Long holidayId) {
+        try {
+            final HolidayMapper rm = new HolidayMapper();
+
+            final String sql = " select " + rm.schema() + " where h.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { holidayId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new HolidayNotFoundException(holidayId);
+        }
+    }
+
+}
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
new file mode 100644
index 0000000..c026ac1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java
@@ -0,0 +1,59 @@
+/**
+ * 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.service;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.joda.time.LocalDate;
+
+public class HolidayUtil {
+
+    public static LocalDate getRepaymentRescheduleDateToIfHoliday(LocalDate repaymentDate, final List<Holiday> holidays) {
+
+        for (final Holiday holiday : holidays) {
+            if (repaymentDate.equals(holiday.getFromDateLocalDate()) || repaymentDate.equals(holiday.getToDateLocalDate())
+                    || (repaymentDate.isAfter(holiday.getFromDateLocalDate()) && repaymentDate.isBefore(holiday.getToDateLocalDate()))) {
+                repaymentDate = getRepaymentRescheduleDateIfHoliday(repaymentDate, holidays);
+            }
+        }
+        return repaymentDate;
+    }
+
+    private static LocalDate getRepaymentRescheduleDateIfHoliday(final LocalDate repaymentDate, final List<Holiday> holidays) {
+
+        for (final Holiday holiday : holidays) {
+            if (repaymentDate.equals(holiday.getFromDateLocalDate()) || repaymentDate.equals(holiday.getToDateLocalDate())
+                    || (repaymentDate.isAfter(holiday.getFromDateLocalDate()) && repaymentDate.isBefore(holiday.getToDateLocalDate()))) {
+                // should be take from holiday
+                return holiday.getRepaymentsRescheduledToLocalDate();
+            }
+        }
+        return repaymentDate;
+    }
+
+    public static boolean isHoliday(final LocalDate date, final List<Holiday> holidays) {
+        for (final Holiday holiday : holidays) {
+            if (date.isEqual(holiday.getFromDateLocalDate()) || date.isEqual(holiday.getToDateLocalDate())
+                    || (date.isAfter(holiday.getFromDateLocalDate()) && date.isBefore(holiday.getToDateLocalDate()))) { return true; }
+        }
+
+        return false;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformService.java
new file mode 100644
index 0000000..482d61d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformService.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.organisation.holiday.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface HolidayWritePlatformService {
+
+    CommandProcessingResult createHoliday(JsonCommand command);
+
+    CommandProcessingResult updateHoliday(JsonCommand command);
+
+    CommandProcessingResult activateHoliday(final Long holidayId);
+
+    CommandProcessingResult deleteHoliday(final Long holidayId);
+
+}
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
new file mode 100644
index 0000000..d159f3a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,236 @@
+/**
+ * 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.service;
+
+import static org.apache.fineract.organisation.holiday.api.HolidayApiConstants.officesParamName;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.holiday.api.HolidayApiConstants;
+import org.apache.fineract.organisation.holiday.data.HolidayDataValidator;
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
+import org.apache.fineract.organisation.holiday.exception.HolidayDateException;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
+import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+@Service
+public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(HolidayWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final HolidayDataValidator fromApiJsonDeserializer;
+    private final HolidayRepositoryWrapper holidayRepository;
+    private final WorkingDaysRepositoryWrapper daysRepositoryWrapper;
+    private final PlatformSecurityContext context;
+    private final OfficeRepository officeRepository;
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public HolidayWritePlatformServiceJpaRepositoryImpl(final HolidayDataValidator fromApiJsonDeserializer,
+            final HolidayRepositoryWrapper holidayRepository, final PlatformSecurityContext context,
+            final OfficeRepository officeRepository, final FromJsonHelper fromApiJsonHelper,
+            final WorkingDaysRepositoryWrapper daysRepositoryWrapper) {
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.holidayRepository = holidayRepository;
+        this.context = context;
+        this.officeRepository = officeRepository;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.daysRepositoryWrapper = daysRepositoryWrapper;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createHoliday(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            validateInputDates(command);
+
+            final Set<Office> offices = getSelectedOffices(command);
+
+            final Holiday holiday = Holiday.createNew(offices, command);
+
+            this.holidayRepository.save(holiday);
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(holiday.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateHoliday(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final Holiday holiday = this.holidayRepository.findOneWithNotFoundDetection(command.entityId());
+            Map<String, Object> changes = holiday.update(command);
+
+            validateInputDates(holiday.getFromDateLocalDate(), holiday.getToDateLocalDate(), holiday.getRepaymentsRescheduledToLocalDate());
+
+            if (changes.containsKey(officesParamName)) {
+                final Set<Office> offices = getSelectedOffices(command);
+                final boolean updated = holiday.update(offices);
+                if (!updated) {
+                    changes.remove(officesParamName);
+                }
+            }
+
+            this.holidayRepository.saveAndFlush(holiday);
+
+            return new CommandProcessingResultBuilder().withEntityId(holiday.getId()).with(changes).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult activateHoliday(final Long holidayId) {
+        this.context.authenticatedUser();
+        final Holiday holiday = this.holidayRepository.findOneWithNotFoundDetection(holidayId);
+
+        holiday.activate();
+        this.holidayRepository.saveAndFlush(holiday);
+        return new CommandProcessingResultBuilder().withEntityId(holiday.getId()).build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteHoliday(final Long holidayId) {
+        this.context.authenticatedUser();
+        final Holiday holiday = this.holidayRepository.findOneWithNotFoundDetection(holidayId);
+        holiday.delete();
+        this.holidayRepository.saveAndFlush(holiday);
+        return new CommandProcessingResultBuilder().withEntityId(holidayId).build();
+    }
+
+    private Set<Office> getSelectedOffices(final JsonCommand command) {
+        Set<Office> offices = null;
+        final JsonObject topLevelJsonElement = this.fromApiJsonHelper.parse(command.json()).getAsJsonObject();
+        if (topLevelJsonElement.has(HolidayApiConstants.officesParamName)
+                && topLevelJsonElement.get(HolidayApiConstants.officesParamName).isJsonArray()) {
+
+            final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.officesParamName).getAsJsonArray();
+            offices = new HashSet<>(array.size());
+            for (int i = 0; i < array.size(); i++) {
+                final JsonObject officeElement = array.get(i).getAsJsonObject();
+                final Long officeId = this.fromApiJsonHelper.extractLongNamed(HolidayApiConstants.officeIdParamName, officeElement);
+                final Office office = this.officeRepository.findOne(officeId);
+                if (office == null) { throw new OfficeNotFoundException(officeId); }
+                offices.add(office);
+            }
+        }
+        return offices;
+    }
+
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("holiday_name")) {
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.holiday.duplicate.name", "Holiday with name `" + name + "` already exists",
+                    "name", name);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.office.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    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);
+    }
+
+    private void validateInputDates(final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo) {
+
+        String defaultUserMessage = "";
+
+        if (toDate.isBefore(fromDate)) {
+            defaultUserMessage = "To Date date cannot be before the From Date.";
+            throw new HolidayDateException("to.date.cannot.be.before.from.date", defaultUserMessage, fromDate.toString(),
+                    toDate.toString());
+        }
+
+        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());
+        }
+
+        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/monetary/api/CurrenciesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java
new file mode 100644
index 0000000..e55cd6d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java
@@ -0,0 +1,102 @@
+/**
+ * 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.monetary.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.ApplicationCurrencyConfigurationData;
+import org.apache.fineract.organisation.monetary.service.OrganisationCurrencyReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/currencies")
+@Component
+@Scope("singleton")
+public class CurrenciesApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("selectedCurrencyOptions", "currencyOptions"));
+
+    private final String resourceNameForPermissions = "CURRENCY";
+
+    private final PlatformSecurityContext context;
+    private final OrganisationCurrencyReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<ApplicationCurrencyConfigurationData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public CurrenciesApiResource(final PlatformSecurityContext context, final OrganisationCurrencyReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<ApplicationCurrencyConfigurationData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCurrencies(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApplicationCurrencyConfigurationData configurationData = this.readPlatformService.retrieveCurrencyConfiguration();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, configurationData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateCurrencies(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateCurrencies() //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/data/ApplicationCurrencyConfigurationData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/data/ApplicationCurrencyConfigurationData.java
new file mode 100644
index 0000000..de768c9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/data/ApplicationCurrencyConfigurationData.java
@@ -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.
+ */
+package org.apache.fineract.organisation.monetary.data;
+
+import java.util.Collection;
+
+/**
+ * Immutable data object for application currency.
+ */
+public class ApplicationCurrencyConfigurationData {
+
+    @SuppressWarnings("unused")
+    private final Collection<CurrencyData> selectedCurrencyOptions;
+    @SuppressWarnings("unused")
+    private final Collection<CurrencyData> currencyOptions;
+
+    public ApplicationCurrencyConfigurationData(final Collection<CurrencyData> currencyOptions,
+            final Collection<CurrencyData> selectedCurrencyOptions) {
+        this.currencyOptions = currencyOptions;
+        this.selectedCurrencyOptions = selectedCurrencyOptions;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyData.java
new file mode 100644
index 0000000..217e096
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyData.java
@@ -0,0 +1,86 @@
+/**
+ * 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.monetary.data;
+
+/**
+ * Immutable data object representing currency.
+ */
+public class CurrencyData {
+
+    private final String code;
+    private final String name;
+    private final int decimalPlaces;
+    private final Integer inMultiplesOf;
+    private final String displaySymbol;
+    @SuppressWarnings("unused")
+    private final String nameCode;
+    @SuppressWarnings("unused")
+    private final String displayLabel;
+
+    public static CurrencyData blank() {
+        return new CurrencyData("", "", 0, 0, "", "");
+    }
+
+    public CurrencyData(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
+            final String displaySymbol, final String nameCode) {
+        this.code = code;
+        this.name = name;
+        this.decimalPlaces = decimalPlaces;
+        this.inMultiplesOf = inMultiplesOf;
+        this.displaySymbol = displaySymbol;
+        this.nameCode = nameCode;
+        this.displayLabel = generateDisplayLabel();
+    }
+
+    public String code() {
+        return this.code;
+    }
+
+    public int decimalPlaces() {
+        return this.decimalPlaces;
+    }
+
+    public Integer currencyInMultiplesOf() {
+        return this.inMultiplesOf;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        final CurrencyData currencyData = (CurrencyData) obj;
+        return currencyData.code.equals(this.code);
+    }
+
+    @Override
+    public int hashCode() {
+        return this.code.hashCode();
+    }
+
+    private String generateDisplayLabel() {
+
+        final StringBuilder builder = new StringBuilder(this.name).append(' ');
+
+        if (this.displaySymbol != null && !"".equalsIgnoreCase(this.displaySymbol.trim())) {
+            builder.append('(').append(this.displaySymbol).append(')');
+        } else {
+            builder.append('[').append(this.code).append(']');
+        }
+
+        return builder.toString();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/data/MoneyData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/data/MoneyData.java
new file mode 100644
index 0000000..d376920
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/data/MoneyData.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.organisation.monetary.data;
+
+import java.math.BigDecimal;
+
+/**
+ * Immutable data object representing currency.
+ */
+public class MoneyData {
+
+    private final String code;
+    private final BigDecimal amount;
+    private final int decimalPlaces;
+
+    public MoneyData(final String code, final BigDecimal amount, final int decimalPlaces) {
+        this.code = code;
+        this.amount = amount;
+        this.decimalPlaces = decimalPlaces;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public int getDecimalPlaces() {
+        return this.decimalPlaces;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrency.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrency.java
new file mode 100644
index 0000000..911a4f9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrency.java
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.monetary.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.office.domain.OrganisationCurrency;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_currency")
+public class ApplicationCurrency extends AbstractPersistable<Long> {
+
+    @Column(name = "code", nullable = false, length = 3)
+    private final String code;
+
+    @Column(name = "decimal_places", nullable = false)
+    private final Integer decimalPlaces;
+
+    @Column(name = "currency_multiplesof")
+    private final Integer inMultiplesOf;
+
+    @Column(name = "name", nullable = false, length = 50)
+    private final String name;
+
+    @Column(name = "internationalized_name_code", nullable = false, length = 50)
+    private final String nameCode;
+
+    @Column(name = "display_symbol", nullable = true, length = 10)
+    private final String displaySymbol;
+
+    protected ApplicationCurrency() {
+        this.code = null;
+        this.name = null;
+        this.decimalPlaces = null;
+        this.inMultiplesOf = null;
+        this.nameCode = null;
+        this.displaySymbol = null;
+    }
+
+    public static ApplicationCurrency from(final ApplicationCurrency currency, final int decimalPlaces, final Integer inMultiplesOf) {
+        return new ApplicationCurrency(currency.code, currency.name, decimalPlaces, inMultiplesOf, currency.nameCode,
+                currency.displaySymbol);
+    }
+
+    private ApplicationCurrency(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
+            final String nameCode, final String displaySymbol) {
+        this.code = code;
+        this.name = name;
+        this.decimalPlaces = decimalPlaces;
+        this.inMultiplesOf = inMultiplesOf;
+        this.nameCode = nameCode;
+        this.displaySymbol = displaySymbol;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public Integer getDecimalPlaces() {
+        return this.decimalPlaces;
+    }
+
+    public Integer getCurrencyInMultiplesOf() {
+        return this.inMultiplesOf;
+    }
+
+    public String getNameCode() {
+        return this.nameCode;
+    }
+
+    public String getDisplaySymbol() {
+        return this.displaySymbol;
+    }
+
+    public CurrencyData toData() {
+        return new CurrencyData(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.displaySymbol, this.nameCode);
+    }
+
+    public CurrencyData toData(final int digitsAfterDecimalSupported, final Integer inMultiplesOf) {
+        return new CurrencyData(this.code, this.name, digitsAfterDecimalSupported, inMultiplesOf, this.displaySymbol, this.nameCode);
+    }
+
+    public OrganisationCurrency toOrganisationCurrency() {
+        return new OrganisationCurrency(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.nameCode, this.displaySymbol);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepository.java
new file mode 100644
index 0000000..7c9f382
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepository.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.organisation.monetary.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ApplicationCurrencyRepository extends JpaRepository<ApplicationCurrency, Long>,
+        JpaSpecificationExecutor<ApplicationCurrency> {
+
+    ApplicationCurrency findOneByCode(String currencyCode);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java
new file mode 100644
index 0000000..564bd44
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.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.organisation.monetary.domain;
+
+import org.apache.fineract.organisation.monetary.exception.CurrencyNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link ApplicationCurrencyRepository} that is responsible for
+ * checking if {@link ApplicationCurrency} is returned when using
+ * <code>findOne</code> repository method and throwing an appropriate not found
+ * exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link ApplicationCurrencyRepository} is required.
+ * </p>
+ */
+@Service
+public class ApplicationCurrencyRepositoryWrapper {
+
+    private final ApplicationCurrencyRepository repository;
+
+    @Autowired
+    public ApplicationCurrencyRepositoryWrapper(final ApplicationCurrencyRepository repository) {
+        this.repository = repository;
+    }
+
+    public ApplicationCurrency findOneWithNotFoundDetection(final MonetaryCurrency currency) {
+
+        final ApplicationCurrency defaultApplicationCurrency = this.repository.findOneByCode(currency.getCode());
+        if (defaultApplicationCurrency == null) { throw new CurrencyNotFoundException(currency.getCode()); }
+
+        final ApplicationCurrency applicationCurrency = ApplicationCurrency.from(defaultApplicationCurrency,
+                currency.getDigitsAfterDecimal(), currency.getCurrencyInMultiplesOf());
+
+        return applicationCurrency;
+    }
+
+    /**
+     * Used when its not needed for {@link ApplicationCurrency} to inherit
+     * decimal place settings of existing currency.
+     */
+    public ApplicationCurrency findOneWithNotFoundDetection(final String currencyCode) {
+        final ApplicationCurrency applicationCurrency = this.repository.findOneByCode(currencyCode);
+        if (applicationCurrency == null) { throw new CurrencyNotFoundException(currencyCode); }
+        return applicationCurrency;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java
new file mode 100644
index 0000000..641567b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java
@@ -0,0 +1,63 @@
+/**
+ * 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.monetary.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+@Embeddable
+public class MonetaryCurrency {
+
+    @Column(name = "currency_code", length = 3, nullable = false)
+    private final String code;
+
+    @Column(name = "currency_digits", nullable = false)
+    private final int digitsAfterDecimal;
+
+    @Column(name = "currency_multiplesof")
+    private final Integer inMultiplesOf;
+
+    protected MonetaryCurrency() {
+        this.code = null;
+        this.digitsAfterDecimal = 0;
+        this.inMultiplesOf = 0;
+    }
+
+    public MonetaryCurrency(final String code, final int digitsAfterDecimal, final Integer inMultiplesOf) {
+        this.code = code;
+        this.digitsAfterDecimal = digitsAfterDecimal;
+        this.inMultiplesOf = inMultiplesOf;
+    }
+
+    public MonetaryCurrency copy() {
+        return new MonetaryCurrency(this.code, this.digitsAfterDecimal, this.inMultiplesOf);
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public int getDigitsAfterDecimal() {
+        return this.digitsAfterDecimal;
+    }
+
+    public Integer getCurrencyInMultiplesOf() {
+        return this.inMultiplesOf;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java
new file mode 100644
index 0000000..d8960f4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java
@@ -0,0 +1,337 @@
+/**
+ * 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.monetary.domain;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Iterator;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+@Embeddable
+public class Money implements Comparable<Money> {
+
+    @Column(name = "currency_code", length = 3)
+    private final String currencyCode;
+
+    @Column(name = "currency_digits")
+    private final int currencyDigitsAfterDecimal;
+
+    @Column(name = "currency_multiplesof")
+    private final Integer inMultiplesOf;
+
+    @Column(name = "amount", scale = 6, precision = 19)
+    private final BigDecimal amount;
+
+    public static Money total(final Money... monies) {
+        if (monies.length == 0) { throw new IllegalArgumentException("Money array must not be empty"); }
+        Money total = monies[0];
+        for (int i = 1; i < monies.length; i++) {
+            total = total.plus(monies[i]);
+        }
+        return total;
+    }
+
+    public static Money total(final Iterable<? extends Money> monies) {
+        final Iterator<? extends Money> it = monies.iterator();
+        if (it.hasNext() == false) { throw new IllegalArgumentException("Money iterator must not be empty"); }
+        Money total = it.next();
+        while (it.hasNext()) {
+            total = total.plus(it.next());
+        }
+        return total;
+    }
+
+    public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) {
+        return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount),
+                currency.getCurrencyInMultiplesOf());
+    }
+
+    public static Money zero(final MonetaryCurrency currency) {
+        return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf());
+    }
+
+    protected Money() {
+        this.currencyCode = null;
+        this.currencyDigitsAfterDecimal = 0;
+        this.inMultiplesOf = 0;
+        this.amount = null;
+    }
+
+    private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf) {
+        this.currencyCode = currencyCode;
+        this.currencyDigitsAfterDecimal = digitsAfterDecimal;
+        this.inMultiplesOf = inMultiplesOf;
+
+        final BigDecimal amountZeroed = defaultToZeroIfNull(amount);
+        final BigDecimal amountStripped = amountZeroed.stripTrailingZeros();
+        BigDecimal amountScaled = amountStripped;
+
+        // round monetary amounts into multiplesof say 20/50.
+        if (inMultiplesOf != null && this.currencyDigitsAfterDecimal == 0 && inMultiplesOf > 0 && amountScaled.doubleValue() > 0) {
+            final double existingVal = amountScaled.doubleValue();
+            amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, inMultiplesOf));
+        }
+        this.amount = amountScaled.setScale(this.currencyDigitsAfterDecimal, MoneyHelper.getRoundingMode());
+    }
+
+    public static double roundToMultiplesOf(final double existingVal, final Integer inMultiplesOf) {
+        double amountScaled = existingVal;
+        final double ceilingOfValue = ceiling(existingVal, inMultiplesOf);
+        final double floorOfValue = floor(existingVal, inMultiplesOf);
+
+        final double floorDiff = existingVal - floorOfValue;
+        final double ceilDiff = ceilingOfValue - existingVal;
+
+        if (ceilDiff > floorDiff) {
+            amountScaled = floorOfValue;
+        } else {
+            amountScaled = ceilingOfValue;
+        }
+        return amountScaled;
+    }
+
+    public static double ceiling(final double n, final double s) {
+        double c;
+
+        if ((n < 0 && s > 0) || (n > 0 && s < 0)) {
+            c = Double.NaN;
+        } else {
+            c = (n == 0 || s == 0) ? 0 : Math.ceil(n / s) * s;
+        }
+
+        return c;
+    }
+
+    public static double floor(final double n, final double s) {
+        double f;
+
+        if ((n < 0 && s > 0) || (n > 0 && s < 0) || (s == 0 && n != 0)) {
+            f = Double.NaN;
+        } else {
+            f = (n == 0 || s == 0) ? 0 : Math.floor(n / s) * s;
+        }
+
+        return f;
+    }
+
+    private static BigDecimal defaultToZeroIfNull(final BigDecimal value) {
+        BigDecimal result = BigDecimal.ZERO;
+        if (value != null) {
+            result = value;
+        }
+        return result;
+    }
+
+    public Money copy() {
+        return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros(), this.inMultiplesOf);
+    }
+
+    public Money plus(final Iterable<? extends Money> moniesToAdd) {
+        BigDecimal total = this.amount;
+        for (final Money moneyProvider : moniesToAdd) {
+            final Money money = checkCurrencyEqual(moneyProvider);
+            total = total.add(money.amount);
+        }
+        return Money.of(monetaryCurrency(), total);
+    }
+
+    public Money plus(final Money moneyToAdd) {
+        final Money toAdd = checkCurrencyEqual(moneyToAdd);
+        return this.plus(toAdd.getAmount());
+    }
+
+    public Money plus(final BigDecimal amountToAdd) {
+        if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) { return this; }
+        final BigDecimal newAmount = this.amount.add(amountToAdd);
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    public Money plus(final double amountToAdd) {
+        if (amountToAdd == 0) { return this; }
+        final BigDecimal newAmount = this.amount.add(BigDecimal.valueOf(amountToAdd));
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    public Money minus(final Money moneyToSubtract) {
+        final Money toSubtract = checkCurrencyEqual(moneyToSubtract);
+        return this.minus(toSubtract.getAmount());
+    }
+
+    public Money minus(final BigDecimal amountToSubtract) {
+        if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) == 0) { return this; }
+        final BigDecimal newAmount = this.amount.subtract(amountToSubtract);
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    private Money checkCurrencyEqual(final Money money) {
+        if (isSameCurrency(money) == false) { throw new UnsupportedOperationException("currencies are different."); }
+        return money;
+    }
+
+    public boolean isSameCurrency(final Money money) {
+        return this.currencyCode.equals(money.getCurrencyCode());
+    }
+
+    public Money dividedBy(final BigDecimal valueToDivideBy, final RoundingMode roundingMode) {
+        if (valueToDivideBy.compareTo(BigDecimal.ONE) == 0) { return this; }
+        final BigDecimal newAmount = this.amount.divide(valueToDivideBy, roundingMode);
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    public Money dividedBy(final double valueToDivideBy, final RoundingMode roundingMode) {
+        if (valueToDivideBy == 1) { return this; }
+        final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), roundingMode);
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    public Money dividedBy(final long valueToDivideBy, final RoundingMode roundingMode) {
+        if (valueToDivideBy == 1) { return this; }
+        final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), roundingMode);
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    public Money multipliedBy(final BigDecimal valueToMultiplyBy) {
+        if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) { return this; }
+        final BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy);
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    public Money multipliedBy(final double valueToMultiplyBy) {
+        if (valueToMultiplyBy == 1) { return this; }
+        final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy));
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    public Money multipliedBy(final long valueToMultiplyBy) {
+        if (valueToMultiplyBy == 1) { return this; }
+        final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy));
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    public Money multiplyRetainScale(final BigDecimal valueToMultiplyBy, final RoundingMode roundingMode) {
+        if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) { return this; }
+        BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy);
+        newAmount = newAmount.setScale(this.currencyDigitsAfterDecimal, roundingMode);
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+
+    public Money multiplyRetainScale(final double valueToMultiplyBy, final RoundingMode roundingMode) {
+        return this.multiplyRetainScale(BigDecimal.valueOf(valueToMultiplyBy), roundingMode);
+    }
+
+    public Money percentageOf(BigDecimal percentage, final RoundingMode roundingMode) {
+        final BigDecimal newAmount = (this.amount.multiply(percentage)).divide(BigDecimal.valueOf(100), roundingMode);
+        return Money.of(monetaryCurrency(), newAmount);
+    }
+    @Override
+    public int compareTo(final Money other) {
+        final Money otherMoney = other;
+        if (this.currencyCode
+                .equals(otherMoney.currencyCode) == false) { throw new UnsupportedOperationException("currencies arent different"); }
+        return this.amount.compareTo(otherMoney.amount);
+    }
+
+    public boolean isZero() {
+        return isEqualTo(Money.zero(getCurrency()));
+    }
+
+    public boolean isEqualTo(final Money other) {
+        return compareTo(other) == 0;
+    }
+
+    public boolean isNotEqualTo(final Money other) {
+        return !isEqualTo(other);
+    }
+
+    public boolean isGreaterThanOrEqualTo(final Money other) {
+        return isGreaterThan(other) || isEqualTo(other);
+    }
+
+    public boolean isGreaterThan(final Money other) {
+        return compareTo(other) > 0;
+    }
+
+    public boolean isGreaterThanZero() {
+        return isGreaterThan(Money.zero(getCurrency()));
+    }
+
+    public boolean isLessThan(final Money other) {
+        return compareTo(other) < 0;
+    }
+
+    public boolean isLessThanZero() {
+        return isLessThan(Money.zero(getCurrency()));
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+
+    public int getCurrencyDigitsAfterDecimal() {
+        return this.currencyDigitsAfterDecimal;
+    }
+
+    public Integer getCurrencyInMultiplesOf() {
+        return this.inMultiplesOf;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public BigDecimal getAmountDefaultedToNullIfZero() {
+        return defaultToNullIfZero(this.amount);
+    }
+
+    private static BigDecimal defaultToNullIfZero(final BigDecimal value) {
+        BigDecimal result = value;
+        if (value != null && BigDecimal.ZERO.compareTo(value) == 0) {
+            result = null;
+        }
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder().append(this.currencyCode).append(' ').append(this.amount.toPlainString()).toString();
+    }
+
+    public Money negated() {
+        if (isZero()) { return this; }
+        return Money.of(monetaryCurrency(), this.amount.negate());
+    }
+
+    public Money abs() {
+        return isLessThanZero() ? negated() : this;
+    }
+
+    public MonetaryCurrency getCurrency() {
+        return monetaryCurrency();
+    }
+
+    private MonetaryCurrency monetaryCurrency() {
+        return new MonetaryCurrency(this.currencyCode, this.currencyDigitsAfterDecimal, this.inMultiplesOf);
+    }
+
+    public Money zero() {
+        return Money.zero(getCurrency());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java
new file mode 100644
index 0000000..1d370d3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java
@@ -0,0 +1,51 @@
+/**
+ * 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.monetary.domain;
+
+import java.math.RoundingMode;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MoneyHelper {
+    
+    private static RoundingMode roundingMode = null;
+    private static ConfigurationDomainService staticConfigurationDomainService;
+    
+    @Autowired
+    private ConfigurationDomainService configurationDomainService;
+
+    @PostConstruct
+    public void someFunction () {
+        staticConfigurationDomainService = configurationDomainService;
+    }
+
+    
+    public static RoundingMode getRoundingMode() {
+        if (roundingMode == null) {
+            roundingMode = RoundingMode.valueOf(staticConfigurationDomainService.getRoundingMode());
+        }
+        return roundingMode;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/exception/CurrencyInUseException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/exception/CurrencyInUseException.java
new file mode 100644
index 0000000..cfc2a0e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/exception/CurrencyInUseException.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.organisation.monetary.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when trying to delete a currency that is
+ * still in use.
+ */
+public class CurrencyInUseException extends AbstractPlatformDomainRuleException {
+
+	public CurrencyInUseException(final String currencyCode) {
+		super("error.msg.currency.currencyCode.inUse",
+				"Cannot remove currency with identifier " + currencyCode
+						+ " while it is still in use", currencyCode);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/exception/CurrencyNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/exception/CurrencyNotFoundException.java
new file mode 100644
index 0000000..1dac243
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/exception/CurrencyNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.monetary.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when loan resources are not found.
+ */
+public class CurrencyNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public CurrencyNotFoundException(final String currencyCode) {
+        super("error.msg.currency.currencyCode.invalid", "Currency with identifier " + currencyCode + " does not exist", currencyCode);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/exception/OrganizationalCurrencyNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/exception/OrganizationalCurrencyNotFoundException.java
new file mode 100644
index 0000000..151fb59
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/exception/OrganizationalCurrencyNotFoundException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.monetary.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when urrency is not supported by an
+ * Organization.
+ */
+public class OrganizationalCurrencyNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public OrganizationalCurrencyNotFoundException(final String currencyCode) {
+        super("error.msg.currency.currencyCode.invalid.or.not.supported", "Currency with identifier " + currencyCode
+                + " does not exist or is not supported by the Organization", currencyCode);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/handler/UpdateCurrencyCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/handler/UpdateCurrencyCommandHandler.java
new file mode 100644
index 0000000..3cfd7b1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/handler/UpdateCurrencyCommandHandler.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.organisation.monetary.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.organisation.monetary.service.CurrencyWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CURRENCY", action = "UPDATE")
+public class UpdateCurrencyCommandHandler implements NewCommandSourceHandler {
+
+    private final CurrencyWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateCurrencyCommandHandler(final CurrencyWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateAllowedCurrencies(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/serialization/CurrencyCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/serialization/CurrencyCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..04c272f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/serialization/CurrencyCommandFromApiJsonDeserializer.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.organisation.monetary.serialization;
+
+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 CurrencyCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("currencies"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public CurrencyCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    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("currencies");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final String[] currencies = this.fromApiJsonHelper.extractArrayNamed("currencies", element);
+        baseDataValidator.reset().parameter("currencies").value(currencies).arrayNotEmpty();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformService.java
new file mode 100644
index 0000000..b6293ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformService.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.organisation.monetary.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+public interface CurrencyReadPlatformService {
+
+    Collection<CurrencyData> retrieveAllowedCurrencies();
+
+    Collection<CurrencyData> retrieveAllPlatformCurrencies();
+
+    CurrencyData retrieveCurrency(String code);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java
new file mode 100644
index 0000000..13ebaf4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java
@@ -0,0 +1,93 @@
+/**
+ * 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.monetary.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+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.monetary.data.CurrencyData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final CurrencyMapper currencyRowMapper;
+
+    @Autowired
+    public CurrencyReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.currencyRowMapper = new CurrencyMapper();
+    }
+
+    @Override
+    public Collection<CurrencyData> retrieveAllowedCurrencies() {
+
+        this.context.authenticatedUser();
+
+        final String sql = "select " + this.currencyRowMapper.schema() + " from m_organisation_currency c order by c.name";
+
+        return this.jdbcTemplate.query(sql, this.currencyRowMapper, new Object[] {});
+    }
+
+    @Override
+    public Collection<CurrencyData> retrieveAllPlatformCurrencies() {
+
+        final String sql = "select " + this.currencyRowMapper.schema() + " from m_currency c order by c.name";
+
+        return this.jdbcTemplate.query(sql, this.currencyRowMapper, new Object[] {});
+    }
+
+    @Override
+    public CurrencyData retrieveCurrency(final String code) {
+
+        final String sql = "select " + this.currencyRowMapper.schema() + " from m_currency c  where c.code = ? order by c.name";
+
+        return this.jdbcTemplate.queryForObject(sql, this.currencyRowMapper, new Object[] { code });
+    }
+
+    private static final class CurrencyMapper implements RowMapper<CurrencyData> {
+
+        @Override
+        public CurrencyData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final String code = rs.getString("code");
+            final String name = rs.getString("name");
+            final int decimalPlaces = JdbcSupport.getInteger(rs, "decimalPlaces");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final String displaySymbol = rs.getString("displaySymbol");
+            final String nameCode = rs.getString("nameCode");
+
+            return new CurrencyData(code, name, decimalPlaces, inMultiplesOf, displaySymbol, nameCode);
+        }
+
+        public String schema() {
+            return " c.code as code, c.name as name, c.decimal_places as decimalPlaces,c.currency_multiplesof as inMultiplesOf, c.display_symbol as displaySymbol, c.internationalized_name_code as nameCode ";
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformService.java
new file mode 100644
index 0000000..725fc7c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformService.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.organisation.monetary.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface CurrencyWritePlatformService {
+
+    CommandProcessingResult updateAllowedCurrencies(JsonCommand command);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..92264ce
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.monetary.service;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.exception.CurrencyInUseException;
+import org.apache.fineract.organisation.monetary.serialization.CurrencyCommandFromApiJsonDeserializer;
+import org.apache.fineract.organisation.office.domain.OrganisationCurrency;
+import org.apache.fineract.organisation.office.domain.OrganisationCurrencyRepository;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class CurrencyWritePlatformServiceJpaRepositoryImpl implements CurrencyWritePlatformService {
+
+    private final PlatformSecurityContext context;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
+    private final OrganisationCurrencyRepository organisationCurrencyRepository;
+    private final CurrencyCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final LoanProductReadPlatformService loanProductService;
+    private final SavingsProductReadPlatformService savingsProductService;
+    private final ChargeReadPlatformService chargeService;
+
+    @Autowired
+    public CurrencyWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final CurrencyCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
+            final OrganisationCurrencyRepository organisationCurrencyRepository, final LoanProductReadPlatformService loanProductService,
+            final SavingsProductReadPlatformService savingsProductService, final ChargeReadPlatformService chargeService) {
+        this.context = context;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.applicationCurrencyRepository = applicationCurrencyRepository;
+        this.organisationCurrencyRepository = organisationCurrencyRepository;
+        this.loanProductService = loanProductService;
+        this.savingsProductService = savingsProductService;
+        this.chargeService = chargeService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateAllowedCurrencies(final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+        final String[] currencies = command.arrayValueOfParameterNamed("currencies");
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final List<String> allowedCurrencyCodes = new ArrayList<>();
+        final Set<OrganisationCurrency> allowedCurrencies = new HashSet<>();
+        for (final String currencyCode : currencies) {
+
+            final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currencyCode);
+
+            final OrganisationCurrency allowedCurrency = currency.toOrganisationCurrency();
+
+            allowedCurrencyCodes.add(currencyCode);
+            allowedCurrencies.add(allowedCurrency);
+        }
+
+        for (OrganisationCurrency priorCurrency : this.organisationCurrencyRepository.findAll()) {
+            if (!allowedCurrencyCodes.contains(priorCurrency.getCode())) {
+                // Check if it's safe to remove this currency.
+                if (!loanProductService.retrieveAllLoanProductsForCurrency(priorCurrency.getCode()).isEmpty()
+                        || !savingsProductService.retrieveAllForCurrency(priorCurrency.getCode()).isEmpty()
+                        || !chargeService.retrieveAllChargesForCurrency(priorCurrency.getCode()).isEmpty()) { throw new CurrencyInUseException(
+                        priorCurrency.getCode()); }
+            }
+        }
+
+        changes.put("currencies", allowedCurrencyCodes.toArray(new String[allowedCurrencyCodes.size()]));
+
+        this.organisationCurrencyRepository.deleteAll();
+        this.organisationCurrencyRepository.save(allowedCurrencies);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .with(changes) //
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformService.java
new file mode 100644
index 0000000..b13ab21
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformService.java
@@ -0,0 +1,27 @@
+/**
+ * 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.monetary.service;
+
+import org.apache.fineract.organisation.monetary.data.ApplicationCurrencyConfigurationData;
+
+public interface OrganisationCurrencyReadPlatformService {
+
+    ApplicationCurrencyConfigurationData retrieveCurrencyConfiguration();
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java
new file mode 100644
index 0000000..f7fa583
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java
@@ -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.
+ */
+package org.apache.fineract.organisation.monetary.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.monetary.data.ApplicationCurrencyConfigurationData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class OrganisationCurrencyReadPlatformServiceImpl implements OrganisationCurrencyReadPlatformService {
+
+    private final CurrencyReadPlatformService currencyReadPlatformService;
+
+    @Autowired
+    public OrganisationCurrencyReadPlatformServiceImpl(final CurrencyReadPlatformService currencyReadPlatformService) {
+        this.currencyReadPlatformService = currencyReadPlatformService;
+    }
+
+    @Override
+    public ApplicationCurrencyConfigurationData retrieveCurrencyConfiguration() {
+
+        final Collection<CurrencyData> selectedCurrencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
+        final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllPlatformCurrencies();
+
+        // remove selected currency options
+        currencyOptions.removeAll(selectedCurrencyOptions);
+
+        return new ApplicationCurrencyConfigurationData(currencyOptions, selectedCurrencyOptions);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficeTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficeTransactionsApiResource.java
new file mode 100644
index 0000000..564edfc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficeTransactionsApiResource.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.organisation.office.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.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.data.OfficeTransactionData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/officetransactions")
+@Component
+@Scope("singleton")
+public class OfficeTransactionsApiResource {
+
+    private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "transactionDate", "fromOfficeId",
+            "fromOfficeName", "toOfficeId", "toOfficeIdName", "currencyCode", "digitsAfterDecimal", "inMultiplesOf", "transactionAmount",
+            "description", "allowedOffices", "currencyOptions"));
+
+    private final String resourceNameForReadPermissions = "OFFICE";
+
+    private final PlatformSecurityContext context;
+    private final OfficeReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<OfficeTransactionData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public OfficeTransactionsApiResource(final PlatformSecurityContext context, final OfficeReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<OfficeTransactionData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOfficeTransactions(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForReadPermissions);
+
+        final Collection<OfficeTransactionData> officeTransactions = this.readPlatformService.retrieveAllOfficeTransactions();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, officeTransactions, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String newOfficeTransactionDetails(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForReadPermissions);
+
+        final OfficeTransactionData officeTransactionData = this.readPlatformService.retrieveNewOfficeTransactionDetails();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, officeTransactionData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String transferMoneyFrom(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createOfficeTransaction().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("transactionId") final Long transactionId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteOfficeTransaction(transactionId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java
new file mode 100644
index 0000000..9f0b109
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java
@@ -0,0 +1,168 @@
+/**
+ * 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.office.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.DefaultValue;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/offices")
+@Component
+@Scope("singleton")
+public class OfficesApiResource {
+
+    /**
+     * The set of parameters that are supported in response for
+     * {@link OfficeData}.
+     */
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "nameDecorated", "externalId",
+            "openingDate", "hierarchy", "parentId", "parentName", "allowedParents"));
+
+    private final String resourceNameForPermissions = "OFFICE";
+
+    private final PlatformSecurityContext context;
+    private final OfficeReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<OfficeData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public OfficesApiResource(final PlatformSecurityContext context, final OfficeReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<OfficeData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOffices(@Context final UriInfo uriInfo,
+            @DefaultValue("false") @QueryParam("includeAllOffices") final boolean onlyManualEntries,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final SearchParameters searchParameters = SearchParameters.forOffices(orderBy, sortOrder);
+
+        final Collection<OfficeData> offices = this.readPlatformService.retrieveAllOffices(onlyManualEntries, searchParameters);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, offices, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOfficeTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        OfficeData office = this.readPlatformService.retrieveNewOfficeTemplate();
+
+        final Collection<OfficeData> allowedParents = this.readPlatformService.retrieveAllOfficesForDropdown();
+        office = OfficeData.appendedTemplate(office, allowedParents);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, office, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createOffice(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createOffice() //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{officeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retreiveOffice(@PathParam("officeId") final Long officeId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        OfficeData office = this.readPlatformService.retrieveOffice(officeId);
+        if (settings.isTemplate()) {
+            final Collection<OfficeData> allowedParents = this.readPlatformService.retrieveAllowedParents(officeId);
+            office = OfficeData.appendedTemplate(office, allowedParents);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, office, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{officeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateOffice(@PathParam("officeId") final Long officeId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateOffice(officeId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/data/OfficeData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/data/OfficeData.java
new file mode 100644
index 0000000..3ea04ee
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/data/OfficeData.java
@@ -0,0 +1,80 @@
+/**
+ * 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.office.data;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object for office data.
+ */
+public class OfficeData implements Serializable {
+
+    private final Long id;
+    private final String name;
+    private final String nameDecorated;
+    private final String externalId;
+    private final LocalDate openingDate;
+    private final String hierarchy;
+    private final Long parentId;
+    private final String parentName;
+    @SuppressWarnings("unused")
+    private final Collection<OfficeData> allowedParents;
+
+    public static OfficeData dropdown(final Long id, final String name, final String nameDecorated) {
+        return new OfficeData(id, name, nameDecorated, null, null, null, null, null, null);
+    }
+
+    public static OfficeData template(final List<OfficeData> parentLookups, final LocalDate defaultOpeningDate) {
+        return new OfficeData(null, null, null, null, defaultOpeningDate, null, null, null, parentLookups);
+    }
+
+    public static OfficeData appendedTemplate(final OfficeData office, final Collection<OfficeData> allowedParents) {
+        return new OfficeData(office.id, office.name, office.nameDecorated, office.externalId, office.openingDate, office.hierarchy,
+                office.parentId, office.parentName, allowedParents);
+    }
+
+    public OfficeData(final Long id, final String name, final String nameDecorated, final String externalId, final LocalDate openingDate,
+            final String hierarchy, final Long parentId, final String parentName, final Collection<OfficeData> allowedParents) {
+        this.id = id;
+        this.name = name;
+        this.nameDecorated = nameDecorated;
+        this.externalId = externalId;
+        this.openingDate = openingDate;
+        this.hierarchy = hierarchy;
+        this.parentName = parentName;
+        this.parentId = parentId;
+        this.allowedParents = allowedParents;
+    }
+
+    public boolean hasIdentifyOf(final Long officeId) {
+        return this.id.equals(officeId);
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public String getHierarchy() {
+        return this.hierarchy;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/data/OfficeTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/data/OfficeTransactionData.java
new file mode 100644
index 0000000..3d9d0e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/data/OfficeTransactionData.java
@@ -0,0 +1,82 @@
+/**
+ * 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.office.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object for office transactions.
+ */
+public class OfficeTransactionData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final LocalDate transactionDate;
+    @SuppressWarnings("unused")
+    private final Long fromOfficeId;
+    @SuppressWarnings("unused")
+    private final String fromOfficeName;
+    @SuppressWarnings("unused")
+    private final Long toOfficeId;
+    @SuppressWarnings("unused")
+    private final String toOfficeName;
+    @SuppressWarnings("unused")
+    private final CurrencyData currency;
+    @SuppressWarnings("unused")
+    private final BigDecimal transactionAmount;
+    @SuppressWarnings("unused")
+    private final String description;
+    @SuppressWarnings("unused")
+    private final Collection<CurrencyData> currencyOptions;
+    @SuppressWarnings("unused")
+    private final Collection<OfficeData> allowedOffices;
+
+    public static OfficeTransactionData instance(final Long id, final LocalDate transactionDate, final Long fromOfficeId,
+            final String fromOfficeName, final Long toOfficeId, final String toOfficeName, final CurrencyData currency,
+            final BigDecimal transactionAmount, final String description) {
+        return new OfficeTransactionData(id, transactionDate, fromOfficeId, fromOfficeName, toOfficeId, toOfficeName, currency,
+                transactionAmount, description, null, null);
+    }
+
+    public static OfficeTransactionData template(final LocalDate transactionDate, final Collection<OfficeData> parentLookups,
+            final Collection<CurrencyData> currencyOptions) {
+        return new OfficeTransactionData(null, transactionDate, null, null, null, null, null, null, null, parentLookups, currencyOptions);
+    }
+
+    private OfficeTransactionData(final Long id, final LocalDate transactionDate, final Long fromOfficeId, final String fromOfficeName,
+            final Long toOfficeId, final String toOfficeName, final CurrencyData currency, final BigDecimal transactionAmount,
+            final String description, final Collection<OfficeData> allowedOffices, final Collection<CurrencyData> currencyOptions) {
+        this.id = id;
+        this.fromOfficeId = fromOfficeId;
+        this.fromOfficeName = fromOfficeName;
+        this.toOfficeId = toOfficeId;
+        this.toOfficeName = toOfficeName;
+        this.currency = currency;
+        this.transactionAmount = transactionAmount;
+        this.description = description;
+        this.transactionDate = transactionDate;
+        this.allowedOffices = allowedOffices;
+        this.currencyOptions = currencyOptions;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/Office.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/Office.java
new file mode 100644
index 0000000..55a4c65
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/Office.java
@@ -0,0 +1,241 @@
+/**
+ * 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.office.domain;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.office.exception.CannotUpdateOfficeWithParentOfficeSameAsSelf;
+import org.apache.fineract.organisation.office.exception.RootOfficeParentCannotBeUpdated;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_office", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "name_org"),
+        @UniqueConstraint(columnNames = { "external_id" }, name = "externalid_org") })
+public class Office extends AbstractPersistable<Long> {
+
+    @OneToMany(fetch = FetchType.EAGER)
+    @JoinColumn(name = "parent_id")
+    private final List<Office> children = new LinkedList<>();
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "parent_id")
+    private Office parent;
+
+    @Column(name = "name", nullable = false, length = 100)
+    private String name;
+
+    @Column(name = "hierarchy", nullable = true, length = 50)
+    private String hierarchy;
+
+    @Column(name = "opening_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date openingDate;
+
+    @Column(name = "external_id", length = 100)
+    private String externalId;
+
+    public static Office headOffice(final String name, final LocalDate openingDate, final String externalId) {
+        return new Office(null, name, openingDate, externalId);
+    }
+
+    public static Office fromJson(final Office parentOffice, final JsonCommand command) {
+
+        final String name = command.stringValueOfParameterNamed("name");
+        final LocalDate openingDate = command.localDateValueOfParameterNamed("openingDate");
+        final String externalId = command.stringValueOfParameterNamed("externalId");
+        return new Office(parentOffice, name, openingDate, externalId);
+    }
+
+    protected Office() {
+        this.openingDate = null;
+        this.parent = null;
+        this.name = null;
+        this.externalId = null;
+    }
+
+    private Office(final Office parent, final String name, final LocalDate openingDate, final String externalId) {
+        this.parent = parent;
+        this.openingDate = openingDate.toDateTimeAtStartOfDay().toDate();
+        if (parent != null) {
+            this.parent.addChild(this);
+        }
+
+        if (StringUtils.isNotBlank(name)) {
+            this.name = name.trim();
+        } else {
+            this.name = null;
+        }
+        if (StringUtils.isNotBlank(externalId)) {
+            this.externalId = externalId.trim();
+        } else {
+            this.externalId = null;
+        }
+    }
+
+    private void addChild(final Office office) {
+        this.children.add(office);
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+
+        final String parentIdParamName = "parentId";
+
+        if (command.parameterExists(parentIdParamName) && this.parent == null) { throw new RootOfficeParentCannotBeUpdated(); }
+
+        if (this.parent != null && command.isChangeInLongParameterNamed(parentIdParamName, this.parent.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(parentIdParamName);
+            actualChanges.put(parentIdParamName, newValue);
+        }
+
+        final String openingDateParamName = "openingDate";
+        if (command.isChangeInLocalDateParameterNamed(openingDateParamName, getOpeningLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(openingDateParamName);
+            actualChanges.put(openingDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(openingDateParamName);
+            this.openingDate = newValue.toDate();
+        }
+
+        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 externalIdParamName = "externalId";
+        if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) {
+            final String newValue = command.stringValueOfParameterNamed(externalIdParamName);
+            actualChanges.put(externalIdParamName, newValue);
+            this.externalId = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        return actualChanges;
+    }
+
+    public boolean isOpeningDateBefore(final LocalDate baseDate) {
+        return getOpeningLocalDate().isBefore(baseDate);
+    }
+
+    public boolean isOpeningDateAfter(final LocalDate activationLocalDate) {
+        return getOpeningLocalDate().isAfter(activationLocalDate);
+    }
+
+    public LocalDate getOpeningLocalDate() {
+        LocalDate openingLocalDate = null;
+        if (this.openingDate != null) {
+            openingLocalDate = LocalDate.fromDateFields(this.openingDate);
+        }
+        return openingLocalDate;
+    }
+
+    public void update(final Office newParent) {
+
+        if (this.parent == null) { throw new RootOfficeParentCannotBeUpdated(); }
+
+        if (identifiedBy(newParent.getId())) { throw new CannotUpdateOfficeWithParentOfficeSameAsSelf(getId(), newParent.getId()); }
+
+        this.parent = newParent;
+        generateHierarchy();
+    }
+
+    public boolean identifiedBy(final Long id) {
+        return getId().equals(id);
+    }
+
+    public void generateHierarchy() {
+
+        if (this.parent != null) {
+            this.hierarchy = this.parent.hierarchyOf(getId());
+        } else {
+            this.hierarchy = ".";
+        }
+    }
+
+    private String hierarchyOf(final Long id) {
+        return this.hierarchy + id.toString() + ".";
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getHierarchy() {
+        return this.hierarchy;
+    }
+
+    public boolean hasParentOf(final Office office) {
+        boolean isParent = false;
+        if (this.parent != null) {
+            isParent = this.parent.equals(office);
+        }
+        return isParent;
+    }
+
+    public boolean doesNotHaveAnOfficeInHierarchyWithId(final Long officeId) {
+        return !hasAnOfficeInHierarchyWithId(officeId);
+    }
+
+    private boolean hasAnOfficeInHierarchyWithId(final Long officeId) {
+
+        boolean match = false;
+
+        if (identifiedBy(officeId)) {
+            match = true;
+        }
+
+        if (!match) {
+            for (final Office child : this.children) {
+                final boolean result = child.hasAnOfficeInHierarchyWithId(officeId);
+
+                if (result) {
+                    match = result;
+                    break;
+                }
+            }
+        }
+
+        return match;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepository.java
new file mode 100644
index 0000000..0864019
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepository.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.organisation.office.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface OfficeRepository extends JpaRepository<Office, Long>, JpaSpecificationExecutor<Office> {
+    // no added behaviour
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepositoryWrapper.java
new file mode 100644
index 0000000..36fef09
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepositoryWrapper.java
@@ -0,0 +1,58 @@
+/**
+ * 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.office.domain;
+
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link OfficeRepository} that adds NULL checking and Error
+ * handling capabilities
+ * </p>
+ */
+@Service
+public class OfficeRepositoryWrapper {
+
+    private final OfficeRepository repository;
+
+    @Autowired
+    public OfficeRepositoryWrapper(final OfficeRepository repository) {
+        this.repository = repository;
+    }
+
+    public Office findOneWithNotFoundDetection(final Long id) {
+        final Office office = this.repository.findOne(id);
+        if (office == null) { throw new OfficeNotFoundException(id); }
+        return office;
+    }
+
+    public void save(final Office entity) {
+        this.repository.save(entity);
+    }
+
+    public void saveAndFlush(final Office entity) {
+        this.repository.saveAndFlush(entity);
+    }
+
+    public void delete(final Office entity) {
+        this.repository.delete(entity);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeTransaction.java
new file mode 100644
index 0000000..d08ba1f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeTransaction.java
@@ -0,0 +1,88 @@
+/**
+ * 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.office.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+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.api.JsonCommand;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_office_transaction")
+public class OfficeTransaction extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "from_office_id")
+    private Office from;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "to_office_id")
+    private Office to;
+
+    @Column(name = "transaction_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date transactionDate;
+
+    @Embedded
+    private MonetaryCurrency currency;
+
+    @Column(name = "transaction_amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal transactionAmount;
+
+    @Column(name = "description", nullable = true, length = 100)
+    private String description;
+
+    public static OfficeTransaction fromJson(final Office fromOffice, final Office toOffice, final Money amount, final JsonCommand command) {
+
+        final LocalDate transactionLocalDate = command.localDateValueOfParameterNamed("transactionDate");
+        final String description = command.stringValueOfParameterNamed("description");
+
+        return new OfficeTransaction(fromOffice, toOffice, transactionLocalDate, amount, description);
+    }
+
+    protected OfficeTransaction() {
+        this.transactionDate = null;
+    }
+
+    private OfficeTransaction(final Office fromOffice, final Office toOffice, final LocalDate transactionLocalDate, final Money amount,
+            final String description) {
+        this.from = fromOffice;
+        this.to = toOffice;
+        if (transactionLocalDate != null) {
+            this.transactionDate = transactionLocalDate.toDate();
+        }
+        this.currency = amount.getCurrency();
+        this.transactionAmount = amount.getAmount();
+        this.description = description;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeTransactionRepository.java
new file mode 100644
index 0000000..3497d97
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OfficeTransactionRepository.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.organisation.office.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface OfficeTransactionRepository extends JpaRepository<OfficeTransaction, Long>, JpaSpecificationExecutor<OfficeTransaction> {
+    // no added behaviour
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OrganisationCurrency.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OrganisationCurrency.java
new file mode 100644
index 0000000..894628a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OrganisationCurrency.java
@@ -0,0 +1,79 @@
+/**
+ * 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.office.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+/**
+ * Represents currencies allowed for this MFI/organisation.
+ */
+@Entity
+@Table(name = "m_organisation_currency")
+public class OrganisationCurrency extends AbstractPersistable<Long> {
+
+    @Column(name = "code", nullable = false, length = 3)
+    private final String code;
+
+    @Column(name = "decimal_places", nullable = false)
+    private final Integer decimalPlaces;
+
+    @Column(name = "currency_multiplesof")
+    private final Integer inMultiplesOf;
+
+    @Column(name = "name", nullable = false, length = 50)
+    private final String name;
+
+    @Column(name = "internationalized_name_code", nullable = false, length = 50)
+    private final String nameCode;
+
+    @Column(name = "display_symbol", nullable = true, length = 10)
+    private final String displaySymbol;
+
+    protected OrganisationCurrency() {
+        this.code = null;
+        this.name = null;
+        this.decimalPlaces = null;
+        this.inMultiplesOf = null;
+        this.nameCode = null;
+        this.displaySymbol = null;
+    }
+
+    public OrganisationCurrency(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
+            final String nameCode, final String displaySymbol) {
+        this.code = code;
+        this.name = name;
+        this.decimalPlaces = decimalPlaces;
+        this.inMultiplesOf = inMultiplesOf;
+        this.nameCode = nameCode;
+        this.displaySymbol = displaySymbol;
+    }
+
+    public final String getCode() {
+        return code;
+    }
+
+    public final MonetaryCurrency toMonetaryCurrency() {
+        return new MonetaryCurrency(this.code, this.decimalPlaces, this.inMultiplesOf);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OrganisationCurrencyRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OrganisationCurrencyRepository.java
new file mode 100644
index 0000000..496e1d4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OrganisationCurrencyRepository.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.organisation.office.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface OrganisationCurrencyRepository extends JpaRepository<OrganisationCurrency, Long>,
+        JpaSpecificationExecutor<OrganisationCurrency> {
+
+    OrganisationCurrency findOneByCode(String currencyCode);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OrganisationCurrencyRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OrganisationCurrencyRepositoryWrapper.java
new file mode 100644
index 0000000..861018d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/domain/OrganisationCurrencyRepositoryWrapper.java
@@ -0,0 +1,52 @@
+/**
+ * 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.office.domain;
+
+import org.apache.fineract.organisation.monetary.exception.OrganizationalCurrencyNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link OrganisationCurrency} that is responsible for checking if
+ * {@link OrganisationCurrency} is returned when using <code>findOne</code>
+ * repository method and throwing an appropriate not found exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link OrganisationCurrency} is required.
+ * </p>
+ */
+@Service
+public class OrganisationCurrencyRepositoryWrapper {
+
+    private final OrganisationCurrencyRepository repository;
+
+    @Autowired
+    public OrganisationCurrencyRepositoryWrapper(final OrganisationCurrencyRepository repository) {
+        this.repository = repository;
+    }
+
+    public OrganisationCurrency findOneWithNotFoundDetection(final String currencyCode) {
+        final OrganisationCurrency organisationCurrency = this.repository.findOneByCode(currencyCode);
+        if (organisationCurrency == null) { throw new OrganizationalCurrencyNotFoundException(currencyCode); }
+        return organisationCurrency;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/CannotUpdateOfficeWithParentOfficeSameAsSelf.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/CannotUpdateOfficeWithParentOfficeSameAsSelf.java
new file mode 100644
index 0000000..0dd7066
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/CannotUpdateOfficeWithParentOfficeSameAsSelf.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.office.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * Exception thrown when an attempt is made update the parent of a root office.
+ */
+public class CannotUpdateOfficeWithParentOfficeSameAsSelf extends AbstractPlatformDomainRuleException {
+
+    public CannotUpdateOfficeWithParentOfficeSameAsSelf(final Long officeId, final Long parentId) {
+        super("error.msg.office.parentId.same.as.id", "Cannot update office with parent same as self.", officeId, parentId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/InvalidOfficeException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/InvalidOfficeException.java
new file mode 100644
index 0000000..21298bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/InvalidOfficeException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.office.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when office mismatch
+ * occurs
+ */
+public class InvalidOfficeException extends AbstractPlatformDomainRuleException {
+
+    public InvalidOfficeException(final String entity, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + "." + postFix + ".invalid.office", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/OfficeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/OfficeNotFoundException.java
new file mode 100644
index 0000000..198e937
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/OfficeNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.office.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when office resources are not found.
+ */
+public class OfficeNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public OfficeNotFoundException(final Long id) {
+        super("error.msg.office.id.invalid", "Office with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/OfficeTransactionNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/OfficeTransactionNotFoundException.java
new file mode 100644
index 0000000..53796e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/OfficeTransactionNotFoundException.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.organisation.office.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when office transaction resources are not
+ * found.
+ */
+public class OfficeTransactionNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public OfficeTransactionNotFoundException(final Long id) {
+        super("error.msg.officetransaction.id.invalid", "Office transaction with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/RootOfficeParentCannotBeUpdated.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/RootOfficeParentCannotBeUpdated.java
new file mode 100644
index 0000000..8e32c04
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/exception/RootOfficeParentCannotBeUpdated.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.office.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * Exception thrown when an attempt is made update the parent of a root office.
+ */
+public class RootOfficeParentCannotBeUpdated extends AbstractPlatformDomainRuleException {
+
+    public RootOfficeParentCannotBeUpdated() {
+        super("error.msg.office.cannot.update.parent.office.of.root.office", "The root office must not be set with a parent office.");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/CreateOfficeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/CreateOfficeCommandHandler.java
new file mode 100644
index 0000000..c99396a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/CreateOfficeCommandHandler.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.organisation.office.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.organisation.office.service.OfficeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "OFFICE", action = "CREATE")
+public class CreateOfficeCommandHandler implements NewCommandSourceHandler {
+
+    private final OfficeWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateOfficeCommandHandler(final OfficeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createOffice(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/CreateOfficeTransactionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/CreateOfficeTransactionCommandHandler.java
new file mode 100644
index 0000000..8ea0290
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/CreateOfficeTransactionCommandHandler.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.organisation.office.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.organisation.office.service.OfficeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "OFFICETRANSACTION", action = "CREATE")
+public class CreateOfficeTransactionCommandHandler implements NewCommandSourceHandler {
+
+    private final OfficeWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateOfficeTransactionCommandHandler(final OfficeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.officeTransaction(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/DeleteOfficeTransactionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/DeleteOfficeTransactionCommandHandler.java
new file mode 100644
index 0000000..90ab4e5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/DeleteOfficeTransactionCommandHandler.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.organisation.office.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.organisation.office.service.OfficeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "OFFICETRANSACTION", action = "DELETE")
+public class DeleteOfficeTransactionCommandHandler implements NewCommandSourceHandler {
+
+    private final OfficeWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteOfficeTransactionCommandHandler(final OfficeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.deleteOfficeTransaction(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/UpdateOfficeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/UpdateOfficeCommandHandler.java
new file mode 100644
index 0000000..77539d2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/UpdateOfficeCommandHandler.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.organisation.office.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.organisation.office.service.OfficeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "OFFICE", action = "UPDATE")
+public class UpdateOfficeCommandHandler implements NewCommandSourceHandler {
+
+    private final OfficeWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateOfficeCommandHandler(final OfficeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateOffice(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/serialization/OfficeCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/serialization/OfficeCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..f95defe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/serialization/OfficeCommandFromApiJsonDeserializer.java
@@ -0,0 +1,129 @@
+/**
+ * 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.office.serialization;
+
+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.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Deserializer of JSON for office API.
+ */
+@Component
+public final class OfficeCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("name", "parentId", "openingDate", "externalId",
+            "locale", "dateFormat"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public OfficeCommandFromApiJsonDeserializer(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("office");
+
+        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 LocalDate openingDate = this.fromApiJsonHelper.extractLocalDateNamed("openingDate", element);
+        baseDataValidator.reset().parameter("openingDate").value(openingDate).notNull();
+
+        if (this.fromApiJsonHelper.parameterExists("externalId", element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
+            baseDataValidator.reset().parameter("externalId").value(externalId).notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("parentId", element)) {
+            final Long parentId = this.fromApiJsonHelper.extractLongNamed("parentId", element);
+            baseDataValidator.reset().parameter("parentId").value(parentId).notNull().integerGreaterThanZero();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", 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("office");
+
+        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("openingDate", element)) {
+            final LocalDate openingDate = this.fromApiJsonHelper.extractLocalDateNamed("openingDate", element);
+            baseDataValidator.reset().parameter("openingDate").value(openingDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("externalId", element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
+            baseDataValidator.reset().parameter("externalId").value(externalId).notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("parentId", element)) {
+            final Long parentId = this.fromApiJsonHelper.extractLongNamed("parentId", element);
+            baseDataValidator.reset().parameter("parentId").value(parentId).notNull().integerGreaterThanZero();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/serialization/OfficeTransactionCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/serialization/OfficeTransactionCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..bd268bf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/serialization/OfficeTransactionCommandFromApiJsonDeserializer.java
@@ -0,0 +1,102 @@
+/**
+ * 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.office.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+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.joda.time.LocalDate;
+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 OfficeTransactionCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("fromOfficeId", "toOfficeId", "transactionDate",
+            "currencyCode", "transactionAmount", "description", "locale", "dateFormat"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public OfficeTransactionCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateOfficeTransfer(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("officeTransaction");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed("fromOfficeId", element);
+        baseDataValidator.reset().parameter("fromOfficeId").value(fromOfficeId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed("toOfficeId", element);
+        baseDataValidator.reset().parameter("toOfficeId").value(toOfficeId).ignoreIfNull().integerGreaterThanZero();
+
+        if (fromOfficeId == null && toOfficeId == null) {
+            baseDataValidator.reset().parameter("toOfficeId").value(toOfficeId).notNull();
+        }
+
+        if (fromOfficeId != null && toOfficeId != null) {
+            baseDataValidator.reset().parameter("fromOfficeId").value(fromOfficeId).notSameAsParameter("toOfficeId", toOfficeId);
+        }
+
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
+        baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
+
+        final String currencyCode = this.fromApiJsonHelper.extractStringNamed("currencyCode", element);
+        baseDataValidator.reset().parameter("currencyCode").value(currencyCode).notBlank().notExceedingLengthOf(3);
+
+        final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
+        baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
+
+        final String description = this.fromApiJsonHelper.extractStringNamed("description", element);
+        baseDataValidator.reset().parameter("description").value(description).notExceedingLengthOf(100);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformService.java
new file mode 100644
index 0000000..b33e3f2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformService.java
@@ -0,0 +1,42 @@
+/**
+ * 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.office.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.data.OfficeTransactionData;
+
+public interface OfficeReadPlatformService {
+
+    Collection<OfficeData> retrieveAllOffices(boolean includeAllOffices, SearchParameters searchParameters);
+
+    Collection<OfficeData> retrieveAllOfficesForDropdown();
+
+    OfficeData retrieveOffice(Long officeId);
+
+    OfficeData retrieveNewOfficeTemplate();
+
+    Collection<OfficeData> retrieveAllowedParents(Long officeId);
+
+    Collection<OfficeTransactionData> retrieveAllOfficeTransactions();
+
+    OfficeTransactionData retrieveNewOfficeTransactionDetails();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java
new file mode 100644
index 0000000..79bd1a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java
@@ -0,0 +1,264 @@
+/**
+ * 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.office.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.data.OfficeTransactionData;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+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 OfficeReadPlatformServiceImpl implements OfficeReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final CurrencyReadPlatformService currencyReadPlatformService;
+    private final static String nameDecoratedBaseOnHierarchy = "concat(substring('........................................', 1, ((LENGTH(o.hierarchy) - LENGTH(REPLACE(o.hierarchy, '.', '')) - 1) * 4)), o.name)";
+
+    @Autowired
+    public OfficeReadPlatformServiceImpl(final PlatformSecurityContext context,
+            final CurrencyReadPlatformService currencyReadPlatformService, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.currencyReadPlatformService = currencyReadPlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class OfficeMapper implements RowMapper<OfficeData> {
+
+        public String officeSchema() {
+            return " o.id as id, o.name as name, "
+                    + nameDecoratedBaseOnHierarchy
+                    + " as nameDecorated, o.external_id as externalId, o.opening_date as openingDate, o.hierarchy as hierarchy, parent.id as parentId, parent.name as parentName "
+                    + "from m_office o LEFT JOIN m_office AS parent ON parent.id = o.parent_id ";
+        }
+
+        @Override
+        public OfficeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final String nameDecorated = rs.getString("nameDecorated");
+            final String externalId = rs.getString("externalId");
+            final LocalDate openingDate = JdbcSupport.getLocalDate(rs, "openingDate");
+            final String hierarchy = rs.getString("hierarchy");
+            final Long parentId = JdbcSupport.getLong(rs, "parentId");
+            final String parentName = rs.getString("parentName");
+
+            return new OfficeData(id, name, nameDecorated, externalId, openingDate, hierarchy, parentId, parentName, null);
+        }
+    }
+
+    private static final class OfficeDropdownMapper implements RowMapper<OfficeData> {
+
+        public String schema() {
+            return " o.id as id, " + nameDecoratedBaseOnHierarchy + " as nameDecorated, o.name as name from m_office o ";
+        }
+
+        @Override
+        public OfficeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final String nameDecorated = rs.getString("nameDecorated");
+
+            return OfficeData.dropdown(id, name, nameDecorated);
+        }
+    }
+
+    private static final class OfficeTransactionMapper implements RowMapper<OfficeTransactionData> {
+
+        public String schema() {
+            return " ot.id as id, ot.transaction_date as transactionDate, ot.from_office_id as fromOfficeId, fromoff.name as fromOfficeName, "
+                    + " ot.to_office_id as toOfficeId, tooff.name as toOfficeName, ot.transaction_amount as transactionAmount, ot.description as description, "
+                    + " ot.currency_code as currencyCode, rc.decimal_places as currencyDigits, rc.currency_multiplesof as inMultiplesOf, "
+                    + " rc.name as currencyName, rc.internationalized_name_code as currencyNameCode, rc.display_symbol as currencyDisplaySymbol "
+                    + " from m_office_transaction ot "
+                    + " left join m_office fromoff on fromoff.id = ot.from_office_id "
+                    + " left join m_office tooff on tooff.id = ot.to_office_id " + " join m_currency rc on rc.`code` = ot.currency_code";
+        }
+
+        @Override
+        public OfficeTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, "transactionDate");
+            final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId");
+            final String fromOfficeName = rs.getString("fromOfficeName");
+            final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId");
+            final String toOfficeName = rs.getString("toOfficeName");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+
+            final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final BigDecimal transactionAmount = rs.getBigDecimal("transactionAmount");
+            final String description = rs.getString("description");
+
+            return OfficeTransactionData.instance(id, transactionDate, fromOfficeId, fromOfficeName, toOfficeId, toOfficeName,
+                    currencyData, transactionAmount, description);
+        }
+    }
+
+    @Override
+    @Cacheable(value = "offices", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'of')")
+    public Collection<OfficeData> retrieveAllOffices(final boolean includeAllOffices, final SearchParameters searchParameters) {
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        String hierarchySearchString = null;
+        if (includeAllOffices) {
+            hierarchySearchString = "." + "%";
+        } else {
+            hierarchySearchString = hierarchy + "%";
+        }
+        final OfficeMapper rm = new OfficeMapper();
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select ");
+        sqlBuilder.append(rm.officeSchema());
+        sqlBuilder.append(" where o.hierarchy like ? ");
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append("order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        } else {
+            sqlBuilder.append("order by o.hierarchy");
+        }
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { hierarchySearchString });
+    }
+
+    @Override
+    @Cacheable(value = "officesForDropdown", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'ofd')")
+    public Collection<OfficeData> retrieveAllOfficesForDropdown() {
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final OfficeDropdownMapper rm = new OfficeDropdownMapper();
+        final String sql = "select " + rm.schema() + "where o.hierarchy like ? order by o.hierarchy";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { hierarchySearchString });
+    }
+
+    @Override
+    @Cacheable(value = "officesById", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#officeId)")
+    public OfficeData retrieveOffice(final Long officeId) {
+
+        try {
+            this.context.authenticatedUser();
+
+            final OfficeMapper rm = new OfficeMapper();
+            final String sql = "select " + rm.officeSchema() + " where o.id = ?";
+
+            final OfficeData selectedOffice = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { officeId });
+
+            return selectedOffice;
+        } catch (final EmptyResultDataAccessException e) {
+            throw new OfficeNotFoundException(officeId);
+        }
+    }
+
+    @Override
+    public OfficeData retrieveNewOfficeTemplate() {
+
+        this.context.authenticatedUser();
+
+        return OfficeData.template(null, new LocalDate());
+    }
+
+    @Override
+    public Collection<OfficeData> retrieveAllowedParents(final Long officeId) {
+
+        this.context.authenticatedUser();
+        final Collection<OfficeData> filterParentLookups = new ArrayList<>();
+
+        if (isNotHeadOffice(officeId)) {
+            final Collection<OfficeData> parentLookups = retrieveAllOfficesForDropdown();
+
+            for (final OfficeData office : parentLookups) {
+                if (!office.hasIdentifyOf(officeId)) {
+                    filterParentLookups.add(office);
+                }
+            }
+        }
+
+        return filterParentLookups;
+    }
+
+    private boolean isNotHeadOffice(final Long officeId) {
+        return !Long.valueOf(1).equals(officeId);
+    }
+
+    @Override
+    public Collection<OfficeTransactionData> retrieveAllOfficeTransactions() {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final OfficeTransactionMapper rm = new OfficeTransactionMapper();
+        final String sql = "select " + rm.schema()
+                + " where (fromoff.hierarchy like ? or tooff.hierarchy like ?) order by ot.transaction_date, ot.id";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { hierarchySearchString, hierarchySearchString });
+    }
+
+    @Override
+    public OfficeTransactionData retrieveNewOfficeTransactionDetails() {
+        this.context.authenticatedUser();
+
+        final Collection<OfficeData> parentLookups = retrieveAllOfficesForDropdown();
+        final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
+
+        return OfficeTransactionData.template(new LocalDate(), parentLookups, currencyOptions);
+    }
+
+    public PlatformSecurityContext getContext() {
+        return this.context;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformService.java
new file mode 100644
index 0000000..56253c3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformService.java
@@ -0,0 +1,33 @@
+/**
+ * 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.office.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface OfficeWritePlatformService {
+
+    CommandProcessingResult createOffice(JsonCommand command);
+
+    CommandProcessingResult updateOffice(Long officeId, JsonCommand command);
+
+    CommandProcessingResult officeTransaction(JsonCommand command);
+
+    CommandProcessingResult deleteOfficeTransaction(Long id, JsonCommand command);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..13a8744
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,260 @@
+/**
+ * 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.office.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.domain.OfficeTransaction;
+import org.apache.fineract.organisation.office.domain.OfficeTransactionRepository;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.apache.fineract.organisation.office.serialization.OfficeCommandFromApiJsonDeserializer;
+import org.apache.fineract.organisation.office.serialization.OfficeTransactionCommandFromApiJsonDeserializer;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(OfficeWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final OfficeCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final OfficeTransactionCommandFromApiJsonDeserializer moneyTransferCommandFromApiJsonDeserializer;
+    private final OfficeRepository officeRepository;
+    private final OfficeTransactionRepository officeTransactionRepository;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
+
+    @Autowired
+    public OfficeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final OfficeCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final OfficeTransactionCommandFromApiJsonDeserializer moneyTransferCommandFromApiJsonDeserializer,
+            final OfficeRepository officeRepository, final OfficeTransactionRepository officeMonetaryTransferRepository,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository) {
+        this.context = context;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.moneyTransferCommandFromApiJsonDeserializer = moneyTransferCommandFromApiJsonDeserializer;
+        this.officeRepository = officeRepository;
+        this.officeTransactionRepository = officeMonetaryTransferRepository;
+        this.applicationCurrencyRepository = applicationCurrencyRepository;
+    }
+
+    @Transactional
+    @Override
+    @Caching(evict = {
+            @CacheEvict(value = "offices", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'of')"),
+            @CacheEvict(value = "officesForDropdown", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'ofd')") })
+    public CommandProcessingResult createOffice(final JsonCommand command) {
+
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            Long parentId = null;
+            if (command.parameterExists("parentId")) {
+                parentId = command.longValueOfParameterNamed("parentId");
+            }
+
+            final Office parent = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, parentId);
+            final Office office = Office.fromJson(parent, command);
+
+            // pre save to generate id for use in office hierarchy
+            this.officeRepository.save(office);
+
+            office.generateHierarchy();
+
+            this.officeRepository.save(office);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(office.getId()) //
+                    .withOfficeId(office.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleOfficeDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    @Caching(evict = {
+            @CacheEvict(value = "offices", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'of')"),
+            @CacheEvict(value = "officesForDropdown", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'ofd')"),
+            @CacheEvict(value = "officesById", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#officeId)") })
+    public CommandProcessingResult updateOffice(final Long officeId, final JsonCommand command) {
+
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            Long parentId = null;
+            if (command.parameterExists("parentId")) {
+                parentId = command.longValueOfParameterNamed("parentId");
+            }
+
+            final Office office = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, officeId);
+
+            final Map<String, Object> changes = office.update(command);
+
+            if (changes.containsKey("parentId")) {
+                final Office parent = validateUserPriviledgeOnOfficeAndRetrieve(currentUser, parentId);
+                office.update(parent);
+            }
+
+            if (!changes.isEmpty()) {
+                this.officeRepository.saveAndFlush(office);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(office.getId()) //
+                    .withOfficeId(office.getId()) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleOfficeDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult officeTransaction(final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        this.moneyTransferCommandFromApiJsonDeserializer.validateOfficeTransfer(command.json());
+
+        Long officeId = null;
+        Office fromOffice = null;
+        final Long fromOfficeId = command.longValueOfParameterNamed("fromOfficeId");
+        if (fromOfficeId != null) {
+            fromOffice = this.officeRepository.findOne(fromOfficeId);
+            officeId = fromOffice.getId();
+        }
+        Office toOffice = null;
+        final Long toOfficeId = command.longValueOfParameterNamed("toOfficeId");
+        if (toOfficeId != null) {
+            toOffice = this.officeRepository.findOne(toOfficeId);
+            officeId = toOffice.getId();
+        }
+
+        if (fromOffice == null && toOffice == null) { throw new OfficeNotFoundException(toOfficeId); }
+
+        final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
+        final ApplicationCurrency appCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currencyCode);
+
+        final MonetaryCurrency currency = new MonetaryCurrency(appCurrency.getCode(), appCurrency.getDecimalPlaces(),
+                appCurrency.getCurrencyInMultiplesOf());
+        final Money amount = Money.of(currency, command.bigDecimalValueOfParameterNamed("transactionAmount"));
+
+        final OfficeTransaction entity = OfficeTransaction.fromJson(fromOffice, toOffice, amount, command);
+
+        this.officeTransactionRepository.save(entity);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(entity.getId()) //
+                .withOfficeId(officeId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteOfficeTransaction(final Long transactionId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        this.officeTransactionRepository.delete(transactionId);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(transactionId) //
+                .build();
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleOfficeDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("externalid_org")) {
+            final String externalId = command.stringValueOfParameterNamed("externalId");
+            throw new PlatformDataIntegrityException("error.msg.office.duplicate.externalId", "Office with externalId `" + externalId
+                    + "` already exists", "externalId", externalId);
+        } else if (realCause.getMessage().contains("name_org")) {
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.office.duplicate.name", "Office with name `" + name + "` already exists",
+                    "name", name);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.office.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    /*
+     * used to restrict modifying operations to office that are either the users
+     * office or lower (child) in the office hierarchy
+     */
+    private Office validateUserPriviledgeOnOfficeAndRetrieve(final AppUser currentUser, final Long officeId) {
+
+        final Long userOfficeId = currentUser.getOffice().getId();
+        final Office userOffice = this.officeRepository.findOne(userOfficeId);
+        if (userOffice == null) { throw new OfficeNotFoundException(userOfficeId); }
+
+        if (userOffice.doesNotHaveAnOfficeInHierarchyWithId(officeId)) { throw new NoAuthorizationException(
+                "User does not have sufficient priviledges to act on the provided office."); }
+
+        Office officeToReturn = userOffice;
+        if (!userOffice.identifiedBy(officeId)) {
+            officeToReturn = this.officeRepository.findOne(officeId);
+            if (officeToReturn == null) { throw new OfficeNotFoundException(officeId); }
+        }
+
+        return officeToReturn;
+    }
+
+    public PlatformSecurityContext getContext() {
+        return this.context;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/api/ProvisioningCategoryApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/api/ProvisioningCategoryApiResource.java
new file mode 100644
index 0000000..6a0fdea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/api/ProvisioningCategoryApiResource.java
@@ -0,0 +1,117 @@
+/**
+ * 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.provisioning.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCategoryData;
+import org.apache.fineract.organisation.provisioning.service.ProvisioningCategoryReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/provisioningcategory")
+@Component
+@Scope("singleton")
+public class ProvisioningCategoryApiResource {
+
+    private final PlatformSecurityContext platformSecurityContext;
+    private final ProvisioningCategoryReadPlatformService provisioningCategoryReadPlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final DefaultToApiJsonSerializer<ProvisioningCategoryData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public ProvisioningCategoryApiResource(final PlatformSecurityContext platformSecurityContext,
+            final ProvisioningCategoryReadPlatformService provisioningCategoryReadPlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final DefaultToApiJsonSerializer<ProvisioningCategoryData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.platformSecurityContext = platformSecurityContext;
+        this.provisioningCategoryReadPlatformService = provisioningCategoryReadPlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+        this.platformSecurityContext.authenticatedUser();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        Collection<ProvisioningCategoryData> provisionCategoriesSet = null;
+        provisionCategoriesSet = this.provisioningCategoryReadPlatformService.retrieveAllProvisionCategories();
+        return this.toApiJsonSerializer.serialize(settings, provisionCategoriesSet);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createProvisioningCategory(final String apiRequestBodyAsJson) {
+        CommandWrapper commandWrapper = null;
+        this.platformSecurityContext.authenticatedUser();
+        commandWrapper = new CommandWrapperBuilder().createProvisioningCategory().withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+        return this.toApiJsonSerializer.serialize(commandProcessingResult);
+    }
+
+    @PUT
+    @Path("{categoryId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateProvisioningCategory(@PathParam("categoryId") final Long categoryId, final String apiRequestBodyAsJson) {
+        this.platformSecurityContext.authenticatedUser();
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateProvisioningCategory(categoryId)
+                .withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{categoryId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteProvisioningCategory(@PathParam("categoryId") final Long categoryId) {
+        this.platformSecurityContext.authenticatedUser();
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteProvisioningCategory(categoryId).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/api/ProvisioningCriteriaApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/api/ProvisioningCriteriaApiResource.java
new file mode 100644
index 0000000..d627d99
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/api/ProvisioningCriteriaApiResource.java
@@ -0,0 +1,141 @@
+/**
+ * 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.provisioning.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.provisioning.constants.ProvisioningCriteriaConstants;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaData;
+import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/provisioningcriteria")
+@Component
+@Scope("singleton")
+public class ProvisioningCriteriaApiResource {
+
+    private final PlatformSecurityContext platformSecurityContext;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final DefaultToApiJsonSerializer<ProvisioningCriteriaData> toApiJsonSerializer;
+    @Autowired
+    public ProvisioningCriteriaApiResource(final PlatformSecurityContext platformSecurityContext,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final DefaultToApiJsonSerializer<ProvisioningCriteriaData> toApiJsonSerializer) {
+        this.platformSecurityContext = platformSecurityContext;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.provisioningCriteriaReadPlatformService = provisioningCriteriaReadPlatformService;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService ;
+        this.toApiJsonSerializer = toApiJsonSerializer ;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo) {
+        this.platformSecurityContext.authenticatedUser();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        ProvisioningCriteriaData data = this.provisioningCriteriaReadPlatformService.retrievePrivisiongCriteriaTemplate();
+        return this.toApiJsonSerializer.serialize(settings, data, ProvisioningCriteriaConstants.PROVISIONING_CRITERIA_TEMPLATE_PARAMETER);
+    }
+    
+    @GET
+    @Path("{criteriaId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveProvisioningCriteria(@PathParam("criteriaId") final Long criteriaId, @Context final UriInfo uriInfo) {
+        platformSecurityContext.authenticatedUser() ;
+        ProvisioningCriteriaData criteria = this.provisioningCriteriaReadPlatformService.retrieveProvisioningCriteria(criteriaId) ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if(settings.isTemplate()) {
+            criteria = this.provisioningCriteriaReadPlatformService.retrievePrivisiongCriteriaTemplate(criteria);   
+        }
+        return this.toApiJsonSerializer.serialize(settings, criteria, ProvisioningCriteriaConstants.PROVISIONING_CRITERIA_PARAMETERS); 
+    }
+        
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllProvisioningCriterias(@Context final UriInfo uriInfo) {
+        platformSecurityContext.authenticatedUser() ;
+        Collection<ProvisioningCriteriaData> data = this.provisioningCriteriaReadPlatformService.retrieveAllProvisioningCriterias() ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, data, ProvisioningCriteriaConstants.ALL_PROVISIONING_CRITERIA_PARAMETERS); 
+    }
+    
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createProvisioningCriteria(final String apiRequestBodyAsJson) {
+        CommandWrapper commandWrapper = null;
+        this.platformSecurityContext.authenticatedUser();
+        commandWrapper = new CommandWrapperBuilder().createProvisioningCriteria().withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+        return this.toApiJsonSerializer.serialize(commandProcessingResult);
+    }
+    
+    @PUT
+    @Path("{criteriaId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateProvisioningCriteria(@PathParam("criteriaId") final Long criteriaId, final String apiRequestBodyAsJson) {
+        this.platformSecurityContext.authenticatedUser();
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateProvisioningCriteria(criteriaId)
+                .withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+    
+    @DELETE
+    @Path("{criteriaId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteProvisioningCriteria(@PathParam("criteriaId") final Long criteriaId) {
+        this.platformSecurityContext.authenticatedUser();
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteProvisioningCriteria(criteriaId).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/constants/ProvisioningCriteriaConstants.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/constants/ProvisioningCriteriaConstants.java
new file mode 100644
index 0000000..87b6d7d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/constants/ProvisioningCriteriaConstants.java
@@ -0,0 +1,79 @@
+/**
+ * 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.provisioning.constants;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public interface ProvisioningCriteriaConstants {
+
+    public final static String JSON_LOCALE_PARAM = "locale" ;
+    
+    public final static String JSON_CRITERIAID_PARAM = "criteriaId" ;
+    
+    public final static String JSON_CRITERIANAME_PARAM = "criteriaName";
+    
+    public final static String JSON_LOANPRODUCTS_PARAM = "loanProducts";
+
+    public final static String JSON_LOAN_PRODUCT_ID_PARAM = "id" ;
+    
+    public final static String JSON_LOAN_PRODUCTNAME_PARAM = "name" ;
+    
+    public final static String JSON_LOAN_PRODUCT_BORROWERCYCLE_PARAM = "includeInBorrowerCycle" ;
+    
+    public final static String JSON_PROVISIONING_DEFINITIONS_PARAM = "definitions";
+    
+    public final static String JSON_CATEOGRYID_PARAM = "categoryId";
+
+    public final static String JSON_CATEOGRYNAME_PARAM = "categoryName";
+    
+    public final static String JSON_MINIMUM_AGE_PARAM = "minAge";
+
+    public final static String JSON_MAXIMUM_AGE_PARAM = "maxAge";
+
+    public final static String JSON_PROVISIONING_PERCENTAGE_PARAM = "provisioningPercentage";
+    
+    public final static String JSON_LIABILITY_ACCOUNT_PARAM = "liabilityAccount";
+
+    public final static String JSON_EXPENSE_ACCOUNT_PARAM = "expenseAccount";
+    
+    Set<String> supportedParametersForCreate = new HashSet<>(Arrays.asList(JSON_LOCALE_PARAM, JSON_CRITERIANAME_PARAM,
+            JSON_LOANPRODUCTS_PARAM, JSON_PROVISIONING_DEFINITIONS_PARAM));
+
+    Set<String> supportedParametersForUpdate = new HashSet<>(Arrays.asList(JSON_CRITERIAID_PARAM, JSON_LOCALE_PARAM, JSON_CRITERIANAME_PARAM,
+            JSON_LOANPRODUCTS_PARAM, JSON_PROVISIONING_DEFINITIONS_PARAM));
+
+    Set<String> loanProductSupportedParams = new HashSet<>(Arrays.asList(JSON_LOAN_PRODUCT_ID_PARAM,
+            JSON_LOAN_PRODUCTNAME_PARAM, JSON_LOAN_PRODUCT_BORROWERCYCLE_PARAM)) ;
+    
+    Set<String> provisioningcriteriaSupportedParams = new HashSet<>(Arrays.asList(JSON_CATEOGRYID_PARAM,
+            JSON_CATEOGRYNAME_PARAM, JSON_MINIMUM_AGE_PARAM, JSON_MAXIMUM_AGE_PARAM, JSON_MINIMUM_AGE_PARAM, JSON_PROVISIONING_PERCENTAGE_PARAM, JSON_EXPENSE_ACCOUNT_PARAM, JSON_LIABILITY_ACCOUNT_PARAM)) ;
+    
+    
+    Set<String> PROVISIONING_CRITERIA_TEMPLATE_PARAMETER = new HashSet<>(Arrays.asList("definitions", "loanProducts",
+            "glAccounts"));
+
+    Set<String> PROVISIONING_CRITERIA_PARAMETERS = new HashSet<>(Arrays.asList("criteriaName", "loanProducts",
+            "definitions"));
+    
+    Set<String> ALL_PROVISIONING_CRITERIA_PARAMETERS = new HashSet<>(Arrays.asList("criteriaId", "criterianame",
+            "createdby"));
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/data/ProvisioningCategoryData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/data/ProvisioningCategoryData.java
new file mode 100644
index 0000000..7d1f4b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/data/ProvisioningCategoryData.java
@@ -0,0 +1,67 @@
+/**
+ * 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.provisioning.data;
+
+import java.io.Serializable;
+
+/**
+ * Immutable object representing organization's provision category data
+ */
+public class ProvisioningCategoryData implements Comparable<ProvisioningCategoryData>, Serializable {
+
+    private final Long id;
+    private final String categoryName;
+    private final String categoryDescription;
+
+    public ProvisioningCategoryData(final Long id, final String categoryName, final String categoryDescription) {
+        this.id = id;
+        this.categoryName = categoryName;
+        this.categoryDescription = categoryDescription;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getCategoryName() {
+        return this.categoryName;
+    }
+
+    public String getCategoryDescription() {
+        return this.categoryDescription;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (!(obj instanceof ProvisioningCategoryData)) return false;
+        final ProvisioningCategoryData provisionCategoryData = (ProvisioningCategoryData) obj;
+        return provisionCategoryData.id.equals(this.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return this.id.hashCode();
+    }
+
+    @Override
+    public int compareTo(ProvisioningCategoryData obj) {
+        if (obj == null) { return -1; }
+        return obj.id.compareTo(this.id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/data/ProvisioningCriteriaData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/data/ProvisioningCriteriaData.java
new file mode 100644
index 0000000..af6c7c7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/data/ProvisioningCriteriaData.java
@@ -0,0 +1,92 @@
+/**
+ * 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.provisioning.data;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+
+@SuppressWarnings("unused")
+public class ProvisioningCriteriaData implements Comparable<ProvisioningCriteriaData>, Serializable {
+
+    private final Long criteriaId;
+    private final String criteriaName;
+    private final String createdBy;
+    private final Collection<LoanProductData> loanProducts;
+    private Collection<LoanProductData> selectedLoanProducts ;
+    private final Collection<ProvisioningCriteriaDefinitionData> definitions;
+    private final Collection<GLAccountData> glAccounts;
+
+    private ProvisioningCriteriaData(final Long criteriaId, final String criteriaName, final Collection<LoanProductData> loanProducts,
+            Collection<ProvisioningCriteriaDefinitionData> definitions, Collection<GLAccountData> glAccounts, final String createdBy) {
+        this.criteriaId = criteriaId;
+        this.criteriaName = criteriaName;
+        this.loanProducts = loanProducts;
+        this.definitions = definitions;
+        this.glAccounts = glAccounts;
+        this.createdBy = createdBy;
+    }
+
+    private ProvisioningCriteriaData(ProvisioningCriteriaData data, final Collection<LoanProductData> loanProducts,
+            Collection<GLAccountData> glAccounts) {
+        this.criteriaId = data.criteriaId;
+        this.criteriaName = data.criteriaName;
+        this.selectedLoanProducts = data.loanProducts ;
+        this.loanProducts = loanProducts;
+        this.loanProducts.removeAll(selectedLoanProducts) ;
+        this.definitions = data.definitions;
+        this.glAccounts = glAccounts;
+        this.createdBy = data.createdBy;
+    }
+    public static ProvisioningCriteriaData toLookup(final Long criteriaId, final String criteriaName, final Collection<LoanProductData> loanProducts,
+            final List<ProvisioningCriteriaDefinitionData> definitions) {
+        Collection<GLAccountData> glAccounts = null;
+        String createdBy = null;
+        return new ProvisioningCriteriaData(criteriaId, criteriaName, loanProducts, definitions, glAccounts, createdBy);
+    }
+
+    public static ProvisioningCriteriaData toLookup(final Long criteriaId, final String criteriaName, String createdBy) {
+        Collection<GLAccountData> glAccounts = null;
+        Collection<LoanProductData> loanProducts = null;
+        List<ProvisioningCriteriaDefinitionData> definitions = null;
+        return new ProvisioningCriteriaData(criteriaId, criteriaName, loanProducts, definitions, glAccounts, createdBy);
+    }
+
+    public static ProvisioningCriteriaData toTemplate(final Collection<ProvisioningCriteriaDefinitionData> definitions,
+            final Collection<LoanProductData> loanProducts, final Collection<GLAccountData> glAccounts) {
+        Long criteriaId = null;
+        String criteriaName = null;
+        String createdBy = null;
+        return new ProvisioningCriteriaData(criteriaId, criteriaName, loanProducts, definitions, glAccounts, createdBy);
+    }
+    
+    public static ProvisioningCriteriaData toTemplate(final ProvisioningCriteriaData data, final Collection<ProvisioningCriteriaDefinitionData> definitions,
+            final Collection<LoanProductData> loanProducts, final Collection<GLAccountData> glAccounts) {
+        return new ProvisioningCriteriaData(data, loanProducts, glAccounts);
+    }
+
+    @Override
+    public int compareTo(ProvisioningCriteriaData obj) {
+        if (obj == null ) { return -1; }
+        return obj.criteriaId.compareTo(this.criteriaId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/data/ProvisioningCriteriaDefinitionData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/data/ProvisioningCriteriaDefinitionData.java
new file mode 100644
index 0000000..f3ed09c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/data/ProvisioningCriteriaDefinitionData.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.provisioning.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public final class ProvisioningCriteriaDefinitionData implements Comparable<ProvisioningCriteriaDefinitionData>, Serializable {
+
+    private final Long id;
+    private final Long categoryId;
+    private final String categoryName;
+    private final Long minAge;
+    private final Long maxAge;
+    private final BigDecimal provisioningPercentage;
+    private final Long liabilityAccount;
+    private final String liabilityCode;
+    private final String liabilityName ;
+    private final Long expenseAccount;
+    private final String expenseCode;
+    private final String expenseName ;
+    
+    public ProvisioningCriteriaDefinitionData(Long id, Long categoryId, String categoryName, Long minAge, Long maxAge,
+            BigDecimal provisioningPercentage, Long liabilityAccount, final String liabilityCode, String liabilityName, Long expenseAccount, 
+            final String expenseCode, final String expenseName) {
+        this.id = id;
+        this.categoryId = categoryId;
+        this.minAge = minAge;
+        this.maxAge = maxAge;
+        this.provisioningPercentage = provisioningPercentage;
+        this.liabilityAccount = liabilityAccount;
+        this.expenseAccount = expenseAccount;
+        this.categoryName = categoryName;
+        this.liabilityCode = liabilityCode;
+        this.expenseCode = expenseCode;
+        this.liabilityName = liabilityName ;
+        this.expenseName =  expenseName ;
+    }
+
+    public static ProvisioningCriteriaDefinitionData template(Long categoryId, String categoryName) {
+        return new ProvisioningCriteriaDefinitionData(null, categoryId, categoryName, null, null, null, null, null, null, null, null, null);
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public Long getCategoryId() {
+        return this.categoryId;
+    }
+
+    public String getCategoryName() {
+        return this.categoryName;
+    }
+
+    public Long getMinAge() {
+        return this.minAge;
+    }
+
+    public Long getMaxAge() {
+        return this.maxAge;
+    }
+
+    public BigDecimal getProvisioningPercentage() {
+        return this.provisioningPercentage;
+    }
+
+    public Long getLiabilityAccount() {
+        return this.liabilityAccount;
+    }
+
+    public Long getExpenseAccount() {
+        return this.expenseAccount;
+    }
+
+    public String getLiabilityCode() {
+        return this.liabilityCode;
+    }
+
+    public String getExpenseCode() {
+        return this.expenseCode;
+    }
+
+    @Override
+    public int compareTo(ProvisioningCriteriaDefinitionData obj) {
+        if (obj == null) { return -1; }
+        return obj.id.compareTo(this.id);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/LoanProductProvisionCriteria.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/LoanProductProvisionCriteria.java
new file mode 100644
index 0000000..0ddf762
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/LoanProductProvisionCriteria.java
@@ -0,0 +1,54 @@
+/**
+ * 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.provisioning.domain;
+
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loanproduct_provisioning_mapping", uniqueConstraints = { @UniqueConstraint(columnNames = { "product_id" }, name = "product_id") })
+public class LoanProductProvisionCriteria extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "criteria_id", referencedColumnName = "id", nullable = false)
+    private ProvisioningCriteria criteria;
+    
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "product_id", referencedColumnName = "id", nullable = false)
+    private LoanProduct loanProduct;
+    
+    protected LoanProductProvisionCriteria() {
+        
+    }
+    
+    public LoanProductProvisionCriteria(ProvisioningCriteria criteria, LoanProduct loanProduct) {
+        this.criteria = criteria ;
+        this.loanProduct = loanProduct ;
+    }
+
+    public LoanProduct getLoanProduct() {
+        return this.loanProduct ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCategory.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCategory.java
new file mode 100644
index 0000000..7492d12
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCategory.java
@@ -0,0 +1,96 @@
+/**
+ * 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.provisioning.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_provision_category", uniqueConstraints = { @UniqueConstraint(columnNames = { "category_name" }, name = "category_name") })
+public class ProvisioningCategory extends AbstractPersistable<Long> {
+
+    @Column(name = "category_name", nullable = false, unique = true)
+    private String categoryName;
+
+    @Column(name = "description", nullable = true)
+    private String categoryDescription;
+
+    protected ProvisioningCategory() {
+
+    }
+
+    private ProvisioningCategory(String categoryName, String categoryDescription) {
+        this.categoryName = categoryName;
+        this.categoryDescription = categoryDescription;
+    }
+
+    public static ProvisioningCategory fromJson(JsonCommand jsonCommand) {
+        final String categoryName = jsonCommand.stringValueOfParameterNamed("categoryname");
+        final String description = jsonCommand.stringValueOfParameterNamed("description");
+        return new ProvisioningCategory(categoryName, description);
+    }
+
+    public Map<String, Object> update(JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(2);
+        final String nameParamName = "categoryname";
+        if (command.isChangeInStringParameterNamed(nameParamName, this.categoryName)) {
+            final String newValue = command.stringValueOfParameterNamed(nameParamName);
+            actualChanges.put(nameParamName, newValue);
+            this.categoryName = newValue;
+        }
+
+        final String descriptionParamName = "categorydescription";
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.categoryDescription)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.categoryDescription = newValue;
+        }
+        return actualChanges;
+    }
+
+    public String getCategoryName() {
+        return this.categoryName;
+    }
+
+    public String getCategoryDescription() {
+        return this.categoryDescription;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        ProvisioningCategory pc = (ProvisioningCategory) obj;
+        return pc.getCategoryName().equals(this.categoryName);
+    }
+
+    @Override
+    public int hashCode() {
+        return categoryName.hashCode() ^ getId().hashCode();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCategoryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCategoryRepository.java
new file mode 100644
index 0000000..f53abaf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCategoryRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.provisioning.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+
+public interface ProvisioningCategoryRepository extends JpaRepository<ProvisioningCategory, Long>, JpaSpecificationExecutor<ProvisioningCategory>{
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteria.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteria.java
new file mode 100644
index 0000000..90aeb47
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteria.java
@@ -0,0 +1,128 @@
+/**
+ * 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.provisioning.domain;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.organisation.provisioning.constants.ProvisioningCriteriaConstants;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaDefinitionData;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.DateTime;
+
+@Entity
+@Table(name = "m_provisioning_criteria", uniqueConstraints = { @UniqueConstraint(columnNames = { "criteria_name" }, name = "criteria_name") })
+public class ProvisioningCriteria extends AbstractAuditableCustom<AppUser, Long> {
+
+    @Column(name = "criteria_name", nullable = false)
+    private String criteriaName;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "criteria", orphanRemoval = true)
+    Set<ProvisioningCriteriaDefinition> provisioningCriteriaDefinition = new HashSet<>();
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "criteria", orphanRemoval = true)
+    Set<LoanProductProvisionCriteria> loanProductMapping = new HashSet<>();
+
+    public String getCriteriaName() {
+        return this.criteriaName;
+    }
+
+    public void setCriteriaName(String criteriaName) {
+        this.criteriaName = criteriaName;
+    }
+
+    protected ProvisioningCriteria() {
+        
+    }
+    
+    public ProvisioningCriteria(String criteriaName, AppUser createdBy, DateTime createdDate, AppUser lastModifiedBy, DateTime lastModifiedDate) {
+        this.criteriaName = criteriaName;
+        setCreatedBy(createdBy) ;
+        setCreatedDate(createdDate) ;
+        setLastModifiedBy(lastModifiedBy) ;
+        setLastModifiedDate(lastModifiedDate) ;
+    }
+
+    public void setProvisioningCriteriaDefinitions(Set<ProvisioningCriteriaDefinition> provisioningCriteriaDefinition) {
+        this.provisioningCriteriaDefinition.clear();
+        this.provisioningCriteriaDefinition.addAll(provisioningCriteriaDefinition);
+    }
+
+    public void setLoanProductProvisioningCriteria(Set<LoanProductProvisionCriteria> loanProductMapping) {
+        this.loanProductMapping.clear();
+        this.loanProductMapping.addAll(loanProductMapping);
+    }
+    
+    public Map<String, Object> update(JsonCommand command, List<LoanProduct> loanProducts) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+        if(command.isChangeInStringParameterNamed(ProvisioningCriteriaConstants.JSON_CRITERIANAME_PARAM, criteriaName)) {
+            final String valueAsInput = command.stringValueOfParameterNamed(ProvisioningCriteriaConstants.JSON_CRITERIANAME_PARAM);
+            actualChanges.put(ProvisioningCriteriaConstants.JSON_CRITERIANAME_PARAM, valueAsInput);
+            this.criteriaName = valueAsInput ;
+        }
+
+        Set<LoanProductProvisionCriteria> temp = new HashSet<>() ;
+        Set<LoanProduct> productsTemp = new HashSet<>() ;
+        
+        for(LoanProductProvisionCriteria mapping: loanProductMapping) {
+            if(!loanProducts.contains(mapping.getLoanProduct())) {
+                temp.add(mapping) ;
+            }else {
+                productsTemp.add(mapping.getLoanProduct()) ;
+            }
+        }
+        loanProductMapping.removeAll(temp) ;
+        
+        for(LoanProduct loanProduct: loanProducts) {
+            if(!productsTemp.contains(loanProduct)) {
+                this.loanProductMapping.add( new LoanProductProvisionCriteria(this, loanProduct)) ;     
+            }
+        }
+        
+        actualChanges.put(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM, loanProductMapping);
+        return actualChanges ;
+    }
+    
+    public void update(ProvisioningCriteriaDefinitionData data, GLAccount liability, GLAccount expense) {
+        for(ProvisioningCriteriaDefinition def: provisioningCriteriaDefinition) {
+            if(data.getId() == def.getId()) {
+                def.update(data.getMinAge(), data.getMaxAge(), data.getProvisioningPercentage(), liability, expense) ;
+                break ;
+            }
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteriaDefinition.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteriaDefinition.java
new file mode 100644
index 0000000..01eb54b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteriaDefinition.java
@@ -0,0 +1,96 @@
+/**
+ * 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.provisioning.domain;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_provisioning_criteria_definition")
+public class ProvisioningCriteriaDefinition extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "criteria_id", referencedColumnName = "id", nullable = false)
+    private ProvisioningCriteria criteria;
+    
+    @ManyToOne
+    @JoinColumn(name = "category_id", nullable = false)
+    private ProvisioningCategory provisioningCategory;
+
+    @Column(name = "min_age", nullable = false)
+    private Long minimumAge;
+
+    @Column(name = "max_age", nullable = false)
+    private Long maximumAge;
+
+    @Column(name = "provision_percentage", nullable = false)
+    private BigDecimal provisioningPercentage;
+
+    @ManyToOne
+    @JoinColumn(name = "liability_account", nullable = false)
+    private GLAccount liabilityAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "expense_account", nullable = false)
+    private GLAccount expenseAccount;
+
+    protected ProvisioningCriteriaDefinition() {
+        
+    }
+    
+    private ProvisioningCriteriaDefinition(ProvisioningCriteria criteria, ProvisioningCategory provisioningCategory, Long minimumAge,
+            Long maximumAge, BigDecimal provisioningPercentage, GLAccount liabilityAccount, GLAccount expenseAccount) {
+        this.criteria = criteria;
+        this.provisioningCategory = provisioningCategory;
+        this.minimumAge = minimumAge;
+        this.maximumAge = maximumAge;
+        this.provisioningPercentage = provisioningPercentage;
+        this.liabilityAccount = liabilityAccount;
+        this.expenseAccount = expenseAccount;
+    }
+
+    public static ProvisioningCriteriaDefinition newPrivisioningCriteria(ProvisioningCriteria criteria,
+            ProvisioningCategory provisioningCategory, Long minimumAge, Long maximumAge, BigDecimal provisioningPercentage,
+            GLAccount liabilityAccount, GLAccount expenseAccount) {
+
+        return new ProvisioningCriteriaDefinition(criteria, provisioningCategory, minimumAge, maximumAge, provisioningPercentage,
+                liabilityAccount, expenseAccount);
+    }
+    
+    public void update(Long minAge, Long maxAge, BigDecimal percentage, GLAccount lia, GLAccount exp) {
+        this.minimumAge = minAge ;
+        this.maximumAge = maxAge ;
+        this.provisioningPercentage = percentage ;
+        this.liabilityAccount = lia ;
+        this.expenseAccount = exp ;
+    }
+    
+    
+    public boolean isOverlapping(ProvisioningCriteriaDefinition def) {
+        return this.minimumAge <= def.maximumAge && def.minimumAge <= this.maximumAge;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteriaDefinitionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteriaDefinitionRepository.java
new file mode 100644
index 0000000..03acc0b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteriaDefinitionRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.provisioning.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+
+public interface ProvisioningCriteriaDefinitionRepository extends JpaRepository<ProvisioningCriteriaDefinition, Long>, JpaSpecificationExecutor<ProvisioningCriteriaDefinition>   {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteriaRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteriaRepository.java
new file mode 100644
index 0000000..00aff41
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/domain/ProvisioningCriteriaRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.provisioning.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+
+public interface ProvisioningCriteriaRepository extends JpaRepository<ProvisioningCriteria, Long>, JpaSpecificationExecutor<ProvisioningCriteria>{
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCategoryCannotBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCategoryCannotBeDeletedException.java
new file mode 100644
index 0000000..cbf21d2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCategoryCannotBeDeletedException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.provisioning.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ProvisioningCategoryCannotBeDeletedException extends AbstractPlatformDomainRuleException {
+
+    public ProvisioningCategoryCannotBeDeletedException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+
+        super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCategoryNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCategoryNotFoundException.java
new file mode 100644
index 0000000..d83758f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCategoryNotFoundException.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.organisation.provisioning.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ProvisioningCategoryNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ProvisioningCategoryNotFoundException(final Long id) {
+        super("error.msg.provisioning.id.invalid", "Provisioning Category with identifier " + id + " does not exist", id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaCannotBeCreatedException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaCannotBeCreatedException.java
new file mode 100644
index 0000000..c3b6f10
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaCannotBeCreatedException.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.organisation.provisioning.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ProvisioningCriteriaCannotBeCreatedException extends AbstractPlatformDomainRuleException {
+
+    public ProvisioningCriteriaCannotBeCreatedException(String globalisationMessageCode, String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaCannotBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaCannotBeDeletedException.java
new file mode 100644
index 0000000..f5caf40
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaCannotBeDeletedException.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.organisation.provisioning.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ProvisioningCriteriaCannotBeDeletedException extends AbstractPlatformDomainRuleException {
+
+    public ProvisioningCriteriaCannotBeDeletedException(Long criteriaId) {
+        super("error.msg.provisioningcriteria.id.usedin.provisioning.entries", "Provisioningcriteria with identifier " + criteriaId + "associated in journal entries");
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaNotFoundException.java
new file mode 100644
index 0000000..415c827
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaNotFoundException.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.organisation.provisioning.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ProvisioningCriteriaNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ProvisioningCriteriaNotFoundException(final Long id) {
+        super("error.msg.provisioning.criteria.id.invalid", "Provisioning Criteria with identifier " + id + " does not exist", id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaOverlappingDefinitionException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaOverlappingDefinitionException.java
new file mode 100644
index 0000000..a9a0b39
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/exception/ProvisioningCriteriaOverlappingDefinitionException.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.organisation.provisioning.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ProvisioningCriteriaOverlappingDefinitionException extends AbstractPlatformResourceNotFoundException {
+
+    public ProvisioningCriteriaOverlappingDefinitionException() {
+        super("error.msg.provisioning.criteria.definitions.are.overlapping", "Provisioning Criteria definitions are overlapping ");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/CreateProvisioningCategoryRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/CreateProvisioningCategoryRequestCommandHandler.java
new file mode 100644
index 0000000..5da9288
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/CreateProvisioningCategoryRequestCommandHandler.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.organisation.provisioning.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.organisation.provisioning.service.ProvisioningCategoryWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PROVISIONCATEGORY", action = "CREATE")
+public class CreateProvisioningCategoryRequestCommandHandler implements NewCommandSourceHandler {
+	
+	private final ProvisioningCategoryWritePlatformService provisioningCategoryWritePlatformService;
+	
+	@Autowired
+	public CreateProvisioningCategoryRequestCommandHandler(
+	        ProvisioningCategoryWritePlatformService provisioningCategoryWritePlatformService) {
+		this.provisioningCategoryWritePlatformService = provisioningCategoryWritePlatformService;
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+		return this.provisioningCategoryWritePlatformService.createProvisioningCateogry(jsonCommand);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/CreateProvisioningCriteriaRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/CreateProvisioningCriteriaRequestCommandHandler.java
new file mode 100644
index 0000000..8ae8af2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/CreateProvisioningCriteriaRequestCommandHandler.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.organisation.provisioning.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.organisation.provisioning.service.ProvisioningCriteriaWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PROVISIONCRITERIA", action = "CREATE")
+public class CreateProvisioningCriteriaRequestCommandHandler implements NewCommandSourceHandler {
+
+    private final ProvisioningCriteriaWritePlatformService provisioningCriteriaWritePlatformService;
+
+    @Autowired
+    public CreateProvisioningCriteriaRequestCommandHandler(
+            final ProvisioningCriteriaWritePlatformService provisioningCriteriaWritePlatformService) {
+        this.provisioningCriteriaWritePlatformService = provisioningCriteriaWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.provisioningCriteriaWritePlatformService.createProvisioningCriteria(jsonCommand);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/DeleteProvisioningCategoryRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/DeleteProvisioningCategoryRequestCommandHandler.java
new file mode 100644
index 0000000..c32cc92
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/DeleteProvisioningCategoryRequestCommandHandler.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.organisation.provisioning.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.organisation.provisioning.service.ProvisioningCategoryWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PROVISIONCATEGORY", action = "DELETE")
+public class DeleteProvisioningCategoryRequestCommandHandler implements NewCommandSourceHandler {
+	
+	private final ProvisioningCategoryWritePlatformService provisioningCategoryWritePlatformService;
+	
+	@Autowired
+	public DeleteProvisioningCategoryRequestCommandHandler(
+	        ProvisioningCategoryWritePlatformService provisioningCategoryWritePlatformService) {
+		this.provisioningCategoryWritePlatformService = provisioningCategoryWritePlatformService;
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+		return this.provisioningCategoryWritePlatformService.deleteProvisioningCateogry(jsonCommand);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/DeleteProvisioningCriteriaRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/DeleteProvisioningCriteriaRequestCommandHandler.java
new file mode 100644
index 0000000..732ba22
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/DeleteProvisioningCriteriaRequestCommandHandler.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.organisation.provisioning.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.organisation.provisioning.service.ProvisioningCriteriaWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PROVISIONCRITERIA", action = "DELETE")
+public class DeleteProvisioningCriteriaRequestCommandHandler implements NewCommandSourceHandler {
+
+    private final ProvisioningCriteriaWritePlatformService provisioningCriteriaWritePlatformService;
+
+    @Autowired
+    public DeleteProvisioningCriteriaRequestCommandHandler(final ProvisioningCriteriaWritePlatformService provisioningCriteriaWritePlatformService) {
+        this.provisioningCriteriaWritePlatformService = provisioningCriteriaWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.provisioningCriteriaWritePlatformService.deleteProvisioningCriteria(jsonCommand.entityId()) ;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/UpdateProvisioningCategoryRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/UpdateProvisioningCategoryRequestCommandHandler.java
new file mode 100644
index 0000000..f19fd7c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/UpdateProvisioningCategoryRequestCommandHandler.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.organisation.provisioning.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.organisation.provisioning.service.ProvisioningCategoryWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PROVISIONCATEGORY", action = "UPDATE")
+public class UpdateProvisioningCategoryRequestCommandHandler implements NewCommandSourceHandler {
+
+    private final ProvisioningCategoryWritePlatformService provisioningCategoryWritePlatformService;
+
+    @Autowired
+    public UpdateProvisioningCategoryRequestCommandHandler(ProvisioningCategoryWritePlatformService provisioningCategoryWritePlatformService) {
+        this.provisioningCategoryWritePlatformService = provisioningCategoryWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.provisioningCategoryWritePlatformService.updateProvisioningCategory(jsonCommand.entityId(), jsonCommand);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/UpdateProvisioningCriteriaRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/UpdateProvisioningCriteriaRequestCommandHandler.java
new file mode 100644
index 0000000..0fe26a9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/handler/UpdateProvisioningCriteriaRequestCommandHandler.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.organisation.provisioning.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.organisation.provisioning.service.ProvisioningCriteriaWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PROVISIONCRITERIA", action = "UPDATE")
+public class UpdateProvisioningCriteriaRequestCommandHandler implements NewCommandSourceHandler {
+
+    private final ProvisioningCriteriaWritePlatformService provisioningCriteriaWritePlatformService;
+
+    @Autowired
+    public UpdateProvisioningCriteriaRequestCommandHandler(final ProvisioningCriteriaWritePlatformService provisioningCriteriaWritePlatformService) {
+        this.provisioningCriteriaWritePlatformService = provisioningCriteriaWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.provisioningCriteriaWritePlatformService.updateProvisioningCriteria(jsonCommand.entityId(), jsonCommand);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/serialization/ProvisioningCategoryDefinitionJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/serialization/ProvisioningCategoryDefinitionJsonDeserializer.java
new file mode 100644
index 0000000..e7ee2e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/serialization/ProvisioningCategoryDefinitionJsonDeserializer.java
@@ -0,0 +1,87 @@
+/**
+ * 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.provisioning.serialization;
+
+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 class ProvisioningCategoryDefinitionJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("categoryname", "categorydescription"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ProvisioningCategoryDefinitionJsonDeserializer(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("provisioningcategories");
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        
+        final String name = this.fromApiJsonHelper.extractStringNamed("categoryname", element);
+        baseDataValidator.reset().parameter("categoryname").value(name).notBlank().notExceedingLengthOf(100);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(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("provisioningcategories");
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        
+        if (this.fromApiJsonHelper.parameterExists("categoryname", element)) {
+            final String categoryName = this.fromApiJsonHelper.extractStringNamed("categoryname", element);
+            baseDataValidator.reset().parameter("categoryname").value(categoryName).notBlank().notExceedingLengthOf(100);
+        }
+        
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/serialization/ProvisioningCriteriaDefinitionJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/serialization/ProvisioningCriteriaDefinitionJsonDeserializer.java
new file mode 100644
index 0000000..4e7b051
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/serialization/ProvisioningCriteriaDefinitionJsonDeserializer.java
@@ -0,0 +1,224 @@
+/**
+ * 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.provisioning.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.provisioning.constants.ProvisioningCriteriaConstants;
+import org.apache.fineract.organisation.provisioning.exception.ProvisioningCriteriaCannotBeCreatedException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class ProvisioningCriteriaDefinitionJsonDeserializer implements ProvisioningCriteriaConstants{
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ProvisioningCriteriaDefinitionJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForCreate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new ProvisioningCriteriaCannotBeCreatedException(
+                "error.msg.provisioningcriteria.cannot.be.created",
+                "criterianame, loanproducts[], provisioningcriteria[] params are missing in the request");
+
+        }
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersForCreate);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("provisioningcriteria");
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        final String name = this.fromApiJsonHelper.extractStringNamed(ProvisioningCriteriaConstants.JSON_CRITERIANAME_PARAM, element);
+        baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_CRITERIANAME_PARAM).value(name).notBlank()
+                .notExceedingLengthOf(200);
+
+        // if the param present, then we should have the loan product ids. If
+        // not we will load all loan products
+        if (this.fromApiJsonHelper.parameterExists(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM, element)) {
+            JsonArray jsonloanProducts = this.fromApiJsonHelper.extractJsonArrayNamed(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM,
+                    element);
+            baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM).value(jsonloanProducts)
+                    .jsonArrayNotEmpty();
+            // check for unsupported params
+            int i = 0 ;
+            for (JsonElement obj : jsonloanProducts) {
+                this.fromApiJsonHelper.checkForUnsupportedParameters(obj.getAsJsonObject() , loanProductSupportedParams);
+                Long productId = this.fromApiJsonHelper.extractLongNamed("id", obj.getAsJsonObject());
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM)
+                .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_LOAN_PRODUCT_ID_PARAM, i + 1).value(productId).notNull()
+                .longGreaterThanZero();
+                i++ ;
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM, element)) {
+            JsonArray jsonProvisioningCriteria = this.fromApiJsonHelper.extractJsonArrayNamed(
+                    ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM, element);
+            baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM)
+                    .value(jsonProvisioningCriteria).jsonArrayNotEmpty();
+            for (int i = 0; i < jsonProvisioningCriteria.size(); i++) {
+                JsonObject jsonObject = jsonProvisioningCriteria.get(i).getAsJsonObject();
+                this.fromApiJsonHelper.checkForUnsupportedParameters(jsonObject , provisioningcriteriaSupportedParams);
+                final Long categoryId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_CATEOGRYID_PARAM, jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_CATEOGRYID_PARAM, i + 1).value(categoryId).notNull()
+                        .longGreaterThanZero();
+
+                Long minimumAge = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_MINIMUM_AGE_PARAM, jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_MINIMUM_AGE_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_MINIMUM_AGE_PARAM, i + 1).value(minimumAge).notNull()
+                        .longZeroOrGreater() ;
+
+                Long maximumAge = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_MAXIMUM_AGE_PARAM, jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_MAXIMUM_AGE_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_MAXIMUM_AGE_PARAM, i + 1).value(maximumAge).notNull()
+                        .longGreaterThanNumber(ProvisioningCriteriaConstants.JSON_MINIMUM_AGE_PARAM, minimumAge, (i+1));
+
+                
+                BigDecimal provisioningpercentage = this.fromApiJsonHelper.extractBigDecimalNamed(
+                        ProvisioningCriteriaConstants.JSON_PROVISIONING_PERCENTAGE_PARAM, jsonObject, locale);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_PERCENTAGE_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_PROVISIONING_PERCENTAGE_PARAM, i + 1).value(provisioningpercentage)
+                        .notNull().zeroOrPositiveAmount();
+
+                Long liabilityAccountId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_LIABILITY_ACCOUNT_PARAM,
+                        jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_LIABILITY_ACCOUNT_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_LIABILITY_ACCOUNT_PARAM, i + 1).value(liabilityAccountId)
+                        .notNull().longGreaterThanZero() ;
+
+                Long expenseAccountId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_EXPENSE_ACCOUNT_PARAM,
+                        jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_EXPENSE_ACCOUNT_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_EXPENSE_ACCOUNT_PARAM, i + 1).value(expenseAccountId).notNull()
+                        .longGreaterThanZero() ;
+            }
+        }else {
+            baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM).jsonArrayNotEmpty() ;
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateForUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new ProvisioningCriteriaCannotBeCreatedException(
+                "error.msg.provisioningcriteria.cannot.be.updated",
+                "update params are missing in the request");
+
+        }
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersForUpdate);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("provisioningcriteria");
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        
+        if(this.fromApiJsonHelper.parameterExists(ProvisioningCriteriaConstants.JSON_CRITERIANAME_PARAM, element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed(ProvisioningCriteriaConstants.JSON_CRITERIANAME_PARAM, element);
+            baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_CRITERIANAME_PARAM).value(name).notBlank()
+                    .notExceedingLengthOf(200);    
+        }
+        
+
+        // if the param present, then we should have the loan product ids. If
+        // not we will load all loan products
+        if (this.fromApiJsonHelper.parameterExists(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM, element)) {
+            JsonArray jsonloanProducts = this.fromApiJsonHelper.extractJsonArrayNamed(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM,
+                    element);
+            baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM).value(jsonloanProducts)
+                    .jsonArrayNotEmpty();
+            // check for unsupported params
+            int i = 0 ;
+            for (JsonElement obj : jsonloanProducts) {
+                Long productId = this.fromApiJsonHelper.extractLongNamed("id", obj.getAsJsonObject());
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM)
+                .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_LOAN_PRODUCT_ID_PARAM, i + 1).value(productId).notNull()
+                .longGreaterThanZero();
+                i++ ;
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM, element)) {
+            JsonArray jsonProvisioningCriteria = this.fromApiJsonHelper.extractJsonArrayNamed(
+                    ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM, element);
+            baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM)
+                    .value(jsonProvisioningCriteria).jsonArrayNotEmpty();
+            for (int i = 0; i < jsonProvisioningCriteria.size(); i++) {
+                // check for unsupported params
+                JsonObject jsonObject = jsonProvisioningCriteria.get(i).getAsJsonObject();
+                final Long categoryId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_CATEOGRYID_PARAM, jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_CATEOGRYID_PARAM, i + 1).value(categoryId).notNull()
+                        .longGreaterThanZero();
+
+                Long minimumAge = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_MINIMUM_AGE_PARAM, jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_MINIMUM_AGE_PARAM, i + 1).value(minimumAge).notNull()
+                        .longZeroOrGreater() ;
+
+                Long maximumAge = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_MAXIMUM_AGE_PARAM, jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_MAXIMUM_AGE_PARAM)
+                .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_MAXIMUM_AGE_PARAM, i + 1).value(maximumAge).notNull()
+                .longGreaterThanNumber(ProvisioningCriteriaConstants.JSON_MINIMUM_AGE_PARAM, minimumAge, (i+1));
+
+                BigDecimal provisioningpercentage = this.fromApiJsonHelper.extractBigDecimalNamed(
+                        ProvisioningCriteriaConstants.JSON_PROVISIONING_PERCENTAGE_PARAM, jsonObject, locale);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_PROVISIONING_PERCENTAGE_PARAM, i + 1).value(provisioningpercentage)
+                        .notNull().zeroOrPositiveAmount();
+
+                Long liabilityAccountId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_LIABILITY_ACCOUNT_PARAM,
+                        jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_LIABILITY_ACCOUNT_PARAM, i + 1).value(liabilityAccountId)
+                        .notNull().longGreaterThanZero() ;
+
+                Long expenseAccountId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_EXPENSE_ACCOUNT_PARAM,
+                        jsonObject);
+                baseDataValidator.reset().parameter(ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM)
+                        .parameterAtIndexArray(ProvisioningCriteriaConstants.JSON_EXPENSE_ACCOUNT_PARAM, i + 1).value(expenseAccountId).notNull()
+                        .longGreaterThanZero() ;
+            }
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformService.java
new file mode 100644
index 0000000..33b8755
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformService.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.organisation.provisioning.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCategoryData;
+
+
+public interface ProvisioningCategoryReadPlatformService {
+
+    public Collection<ProvisioningCategoryData> retrieveAllProvisionCategories() ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformServiceImpl.java
new file mode 100644
index 0000000..659efaf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformServiceImpl.java
@@ -0,0 +1,65 @@
+/**
+ * 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.provisioning.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCategoryData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ProvisioningCategoryReadPlatformServiceImpl implements ProvisioningCategoryReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final ProvisioningCategoryRowMapper provisionCategoryRowMapper;
+
+    @Autowired
+    public ProvisioningCategoryReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.provisionCategoryRowMapper = new ProvisioningCategoryRowMapper();
+    }
+
+    @Override
+    public Collection<ProvisioningCategoryData> retrieveAllProvisionCategories() {
+        //User is already authenticated by API. So we no need to check again here
+        final String sql = "select " + this.provisionCategoryRowMapper.schema() + " from m_provision_category pc order by pc.id";
+        return this.jdbcTemplate.query(sql, this.provisionCategoryRowMapper, new Object[] {});
+    }
+
+    private static final class ProvisioningCategoryRowMapper implements RowMapper<ProvisioningCategoryData> {
+        @Override
+        public ProvisioningCategoryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String categoryName = rs.getString("category_name");
+            final String description = rs.getString("description");
+            return new ProvisioningCategoryData(id, categoryName, description);
+        }
+
+        public String schema() {
+            return " pc.id as id, pc.category_name as category_name, pc.description as description";
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformService.java
new file mode 100644
index 0000000..1de05e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformService.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.organisation.provisioning.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+
+public interface ProvisioningCategoryWritePlatformService {
+
+    CommandProcessingResult createProvisioningCateogry(JsonCommand command);
+    
+    CommandProcessingResult updateProvisioningCategory(final Long categoryId, JsonCommand command) ;
+    
+    CommandProcessingResult deleteProvisioningCateogry(JsonCommand command) ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..91dc24f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,126 @@
+/**
+ * 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.provisioning.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCategory;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCategoryRepository;
+import org.apache.fineract.organisation.provisioning.exception.ProvisioningCategoryCannotBeDeletedException;
+import org.apache.fineract.organisation.provisioning.exception.ProvisioningCategoryNotFoundException;
+import org.apache.fineract.organisation.provisioning.serialization.ProvisioningCategoryDefinitionJsonDeserializer;
+import org.apache.fineract.portfolio.charge.service.ChargeWritePlatformServiceJpaRepositoryImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl implements ProvisioningCategoryWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ChargeWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final ProvisioningCategoryRepository provisioningCategoryRepository;
+
+    private final ProvisioningCategoryDefinitionJsonDeserializer fromApiJsonDeserializer;
+    private final JdbcTemplate jdbcTemplate;
+    
+    @Autowired
+    public ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl(final ProvisioningCategoryRepository provisioningCategoryRepository,
+            final ProvisioningCategoryDefinitionJsonDeserializer fromApiJsonDeserializer, final RoutingDataSource dataSource) {
+        this.provisioningCategoryRepository = provisioningCategoryRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public CommandProcessingResult createProvisioningCateogry(JsonCommand command) {
+        try {
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+            final ProvisioningCategory provisioningCategory = ProvisioningCategory.fromJson(command);
+            this.provisioningCategoryRepository.save(provisioningCategory);
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(provisioningCategory.getId())
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    public CommandProcessingResult deleteProvisioningCateogry(JsonCommand command) {
+        this.fromApiJsonDeserializer.validateForCreate(command.json());
+        final ProvisioningCategory provisioningCategory = ProvisioningCategory.fromJson(command);
+        boolean isProvisioningCategoryInUse = isAnyLoanProductsAssociateWithThisProvisioningCategory(provisioningCategory.getId()) ;
+        if(isProvisioningCategoryInUse) {
+            throw new ProvisioningCategoryCannotBeDeletedException(
+                    "error.msg.provisioningcategory.cannot.be.deleted.it.is.already.used.in.loanproduct",
+                    "This provisioning category cannot be deleted, it is already used in loan product");
+        }
+        this.provisioningCategoryRepository.delete(provisioningCategory);
+        return new CommandProcessingResultBuilder().withEntityId(provisioningCategory.getId()).build();
+    }
+
+    @Override
+    public CommandProcessingResult updateProvisioningCategory(final Long categoryId, JsonCommand command) {
+        try {
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+            final ProvisioningCategory provisioningCategoryForUpdate = this.provisioningCategoryRepository.findOne(categoryId);
+            if (provisioningCategoryForUpdate == null) { throw new ProvisioningCategoryNotFoundException(categoryId); }
+            final Map<String, Object> changes = provisioningCategoryForUpdate.update(command);
+            if (!changes.isEmpty()) {
+                this.provisioningCategoryRepository.save(provisioningCategoryForUpdate);
+            }
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(categoryId).with(changes).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private boolean isAnyLoanProductsAssociateWithThisProvisioningCategory(final Long categoryID) {
+        final String sql = "select if((exists (select 1 from m_loanproduct_provisioning_details lpd where lpd.category_id = ?)) = 1, 'true', 'false')";
+        final String isLoansUsingCharge = this.jdbcTemplate.queryForObject(sql, String.class, new Object[] { categoryID });
+        return new Boolean(isLoansUsingCharge);
+    }
+    /*
+     * 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("category_name")) {
+            final String name = command.stringValueOfParameterNamed("category_name");
+            throw new PlatformDataIntegrityException("error.msg.provisioning.duplicate.categoryname", "Provisioning Cateory with name `"
+                    + name + "` already exists", "category name", name);
+        }
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.charge.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaAssembler.java
new file mode 100644
index 0000000..8186fa2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaAssembler.java
@@ -0,0 +1,147 @@
+/**
+ * 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.provisioning.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.provisioning.constants.ProvisioningCriteriaConstants;
+import org.apache.fineract.organisation.provisioning.domain.LoanProductProvisionCriteria;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCategory;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCategoryRepository;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteriaDefinition;
+import org.apache.fineract.organisation.provisioning.exception.ProvisioningCriteriaOverlappingDefinitionException;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.DateTime;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class ProvisioningCriteriaAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final ProvisioningCategoryRepository provisioningCategoryRepository;
+    private final LoanProductRepository loanProductRepository;
+    private final GLAccountRepository glAccountRepository;
+    private final PlatformSecurityContext platformSecurityContext;
+
+    @Autowired
+    public ProvisioningCriteriaAssembler(final FromJsonHelper fromApiJsonHelper,
+            final ProvisioningCategoryRepository provisioningCategoryRepository, final LoanProductRepository loanProductRepository,
+            final GLAccountRepository glAccountRepository, final PlatformSecurityContext platformSecurityContext) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.provisioningCategoryRepository = provisioningCategoryRepository;
+        this.loanProductRepository = loanProductRepository;
+        this.glAccountRepository = glAccountRepository;
+        this.platformSecurityContext = platformSecurityContext;
+    }
+
+    public List<LoanProduct> parseLoanProducts(final JsonElement jsonElement) {
+        List<LoanProduct> loanProducts = new ArrayList<>();
+        if (fromApiJsonHelper.parameterExists(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM, jsonElement)) {
+            JsonArray jsonloanProducts = this.fromApiJsonHelper.extractJsonArrayNamed(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM,
+                    jsonElement);
+            for (JsonElement element : jsonloanProducts) {
+                Long productId = this.fromApiJsonHelper.extractLongNamed("id", element.getAsJsonObject());
+                loanProducts.add(loanProductRepository.findOne(productId));
+            }
+        } else {
+            loanProducts = loanProductRepository.findAll();
+        }
+        return loanProducts ;
+    }
+    
+    private void validateRange(Set<ProvisioningCriteriaDefinition> criteriaDefinitions) {
+        List<ProvisioningCriteriaDefinition> def = new ArrayList<>() ;
+        def.addAll(criteriaDefinitions) ;
+        
+        for (int i = 0; i < def.size(); i++) {
+            for (int j = i + 1; j < def.size(); j++) {
+                if (def.get(i).isOverlapping(def.get(j))) {
+                    throw new ProvisioningCriteriaOverlappingDefinitionException() ;
+                }
+            }
+        }
+    }
+    
+    public ProvisioningCriteria fromParsedJson(final JsonElement jsonElement) {
+        ProvisioningCriteria provisioningCriteria = createCriteria(jsonElement);
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(jsonElement.getAsJsonObject());
+        List<LoanProduct> loanProducts = parseLoanProducts(jsonElement) ;
+        
+        Set<ProvisioningCriteriaDefinition> criteriaDefinitions = new HashSet<>();
+        JsonArray jsonProvisioningCriteria = this.fromApiJsonHelper.extractJsonArrayNamed(
+                ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM, jsonElement);
+        for (JsonElement element : jsonProvisioningCriteria) {
+            JsonObject jsonObject = element.getAsJsonObject();
+            ProvisioningCriteriaDefinition provisioningCriteriaData = createProvisioningCriteriaDefinitions(jsonObject, locale,
+                    provisioningCriteria);
+            criteriaDefinitions.add(provisioningCriteriaData);
+        }
+        validateRange(criteriaDefinitions) ;
+        Set<LoanProductProvisionCriteria> mapping = new HashSet<>();
+        for (LoanProduct loanProduct : loanProducts) {
+            mapping.add(new LoanProductProvisionCriteria(provisioningCriteria, loanProduct));
+        }
+        provisioningCriteria.setProvisioningCriteriaDefinitions(criteriaDefinitions);
+        provisioningCriteria.setLoanProductProvisioningCriteria(mapping);
+        return provisioningCriteria;
+    }
+
+    private ProvisioningCriteria createCriteria(final JsonElement jsonElement) {
+        final String criteriaName = this.fromApiJsonHelper.extractStringNamed(ProvisioningCriteriaConstants.JSON_CRITERIANAME_PARAM, jsonElement);
+        AppUser modifiedBy = null;
+        DateTime modifiedOn = null;
+        ProvisioningCriteria criteria = new ProvisioningCriteria(criteriaName, platformSecurityContext.authenticatedUser(), new DateTime(),
+                modifiedBy, modifiedOn);
+        return criteria;
+    }
+
+    private ProvisioningCriteriaDefinition createProvisioningCriteriaDefinitions(JsonObject jsonObject, Locale locale,
+            ProvisioningCriteria criteria) {
+        Long categoryId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_CATEOGRYID_PARAM, jsonObject);
+        Long minimumAge = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_MINIMUM_AGE_PARAM, jsonObject);
+        Long maximumAge = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_MAXIMUM_AGE_PARAM, jsonObject);
+        BigDecimal provisioningpercentage = this.fromApiJsonHelper.extractBigDecimalNamed(ProvisioningCriteriaConstants.JSON_PROVISIONING_PERCENTAGE_PARAM,
+                jsonObject, locale);
+        Long liabilityAccountId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_LIABILITY_ACCOUNT_PARAM, jsonObject);
+        Long expenseAccountId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_EXPENSE_ACCOUNT_PARAM, jsonObject);
+
+        ProvisioningCategory provisioningCategory = provisioningCategoryRepository.findOne(categoryId);
+        GLAccount liabilityAccount = glAccountRepository.findOne(liabilityAccountId);
+        GLAccount expenseAccount = glAccountRepository.findOne(expenseAccountId);
+        return ProvisioningCriteriaDefinition.newPrivisioningCriteria(criteria, provisioningCategory, minimumAge, maximumAge,
+                provisioningpercentage, liabilityAccount, expenseAccount);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformService.java
new file mode 100644
index 0000000..3b94312
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformService.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.organisation.provisioning.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaData;
+
+
+public interface ProvisioningCriteriaReadPlatformService {
+
+    public ProvisioningCriteriaData retrievePrivisiongCriteriaTemplate() ;
+    
+    public ProvisioningCriteriaData retrieveProvisioningCriteria(Long criteriaId) ;
+    
+    public Collection<ProvisioningCriteriaData> retrieveAllProvisioningCriterias() ;
+    
+    public ProvisioningCriteriaData retrievePrivisiongCriteriaTemplate(ProvisioningCriteriaData data) ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java
new file mode 100644
index 0000000..cf7600c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java
@@ -0,0 +1,190 @@
+/**
+ * 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.provisioning.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCategoryData;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaData;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaDefinitionData;
+import org.apache.fineract.organisation.provisioning.exception.ProvisioningCriteriaNotFoundException;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+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 ProvisioningCriteriaReadPlatformServiceImpl implements ProvisioningCriteriaReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final ProvisioningCategoryReadPlatformService provisioningCategoryReadPlatformService;
+    private final LoanProductReadPlatformService loanProductReadPlatformService;
+    private final GLAccountReadPlatformService glAccountReadPlatformService;
+    private final LoanProductReadPlatformService loanProductReaPlatformService;
+
+    @Autowired
+    public ProvisioningCriteriaReadPlatformServiceImpl(final RoutingDataSource dataSource,
+            final ProvisioningCategoryReadPlatformService provisioningCategoryReadPlatformService,
+            final LoanProductReadPlatformService loanProductReadPlatformService,
+            final GLAccountReadPlatformService glAccountReadPlatformService,
+            final LoanProductReadPlatformService loanProductReaPlatformService) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.provisioningCategoryReadPlatformService = provisioningCategoryReadPlatformService;
+        this.loanProductReadPlatformService = loanProductReadPlatformService;
+        this.glAccountReadPlatformService = glAccountReadPlatformService;
+        this.loanProductReaPlatformService = loanProductReaPlatformService;
+    }
+
+    @Override
+    public ProvisioningCriteriaData retrievePrivisiongCriteriaTemplate() {
+        boolean onlyActive = true;
+        final Collection<ProvisioningCategoryData> categories = this.provisioningCategoryReadPlatformService
+                .retrieveAllProvisionCategories();
+        final Collection<LoanProductData> allLoanProducts = this.loanProductReadPlatformService
+                .retrieveAllLoanProductsForLookup(onlyActive);
+        final Collection<GLAccountData> glAccounts = this.glAccountReadPlatformService.retrieveAllEnabledDetailGLAccounts();
+        return ProvisioningCriteriaData.toTemplate(constructCriteriaTemplate(categories), allLoanProducts, glAccounts);
+    }
+
+    @Override
+    public ProvisioningCriteriaData retrievePrivisiongCriteriaTemplate(ProvisioningCriteriaData data) {
+        boolean onlyActive = true;
+        final Collection<ProvisioningCategoryData> categories = this.provisioningCategoryReadPlatformService
+                .retrieveAllProvisionCategories();
+        final Collection<LoanProductData> allLoanProducts = this.loanProductReadPlatformService
+                .retrieveAllLoanProductsForLookup(onlyActive);
+        final Collection<GLAccountData> glAccounts = this.glAccountReadPlatformService.retrieveAllEnabledDetailGLAccounts();
+        return ProvisioningCriteriaData.toTemplate(data, constructCriteriaTemplate(categories), allLoanProducts, glAccounts);
+    }
+    
+    private Collection<ProvisioningCriteriaDefinitionData> constructCriteriaTemplate(Collection<ProvisioningCategoryData> categories) {
+        List<ProvisioningCriteriaDefinitionData> definitions = new ArrayList<>();
+        for (ProvisioningCategoryData data : categories) {
+            definitions.add(ProvisioningCriteriaDefinitionData.template(data.getId(), data.getCategoryName()));
+        }
+        return definitions;
+    }
+    @Override
+    public Collection<ProvisioningCriteriaData> retrieveAllProvisioningCriterias() {
+        ProvisioningCriteriaRowMapper mapper = new ProvisioningCriteriaRowMapper() ;
+        final String sql = "select " + mapper.schema() ;
+        return this.jdbcTemplate.query(sql, mapper, new Object[] {});
+    }
+    
+    private static final class ProvisioningCriteriaRowMapper implements RowMapper<ProvisioningCriteriaData> {
+
+        @Override
+        public ProvisioningCriteriaData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            Long criteriaId = rs.getLong("id") ;
+            String criteriaName = rs.getString("criteriaName");
+            String createdBy = rs.getString("username") ;
+            return ProvisioningCriteriaData.toLookup(criteriaId, criteriaName, createdBy) ;
+        }
+        public String schema() {
+            return "mpc.id as id, mpc.criteria_name as criteriaName, appu.username as username from m_provisioning_criteria as mpc LEFT JOIN m_appuser appu on mpc.createdby_id=appu.id";
+        }
+    }
+
+    @Override
+    public ProvisioningCriteriaData retrieveProvisioningCriteria(Long criteriaId) {
+        try {
+            String criteriaName = retrieveCriteriaName(criteriaId);
+            Collection<LoanProductData> loanProducts = loanProductReaPlatformService
+                    .retrieveAllLoanProductsForLookup("select product_id from m_loanproduct_provisioning_mapping where m_loanproduct_provisioning_mapping.criteria_id="
+                            + criteriaId);
+            List<ProvisioningCriteriaDefinitionData> definitions = retrieveProvisioningDefinitions(criteriaId);
+            return ProvisioningCriteriaData.toLookup(criteriaId, criteriaName, loanProducts, definitions);
+        }catch(EmptyResultDataAccessException e) {
+            throw new ProvisioningCriteriaNotFoundException(criteriaId) ;
+        }
+       
+    }
+
+    private List<ProvisioningCriteriaDefinitionData> retrieveProvisioningDefinitions(Long criteriaId) {
+        ProvisioningCriteriaDefinitionRowMapper rowMapper = new ProvisioningCriteriaDefinitionRowMapper();
+        final String sql = "select " + rowMapper.schema() + " where pc.criteria_id = ?";
+        return this.jdbcTemplate.query(sql, rowMapper, new Object[] { criteriaId });
+    }
+
+    private static final class ProvisioningCriteriaDefinitionRowMapper implements RowMapper<ProvisioningCriteriaDefinitionData> {
+
+        private final StringBuilder sqlQuery = new StringBuilder()
+                .append("pc.id, pc.criteria_id, pc.category_id, mpc.category_name, pc.min_age, pc.max_age, ")
+                .append("pc.provision_percentage, pc.liability_account, pc.expense_account, lia.gl_code as liabilitycode, expe.gl_code as expensecode, ")
+                .append("lia.name as liabilityname, expe.name as expensename ")
+                .append("from m_provisioning_criteria_definition as pc ")
+                .append("LEFT JOIN acc_gl_account lia ON lia.id = pc.liability_account ")
+                .append("LEFT JOIN acc_gl_account expe ON expe.id = pc.expense_account ")
+                .append("LEFT JOIN m_provision_category mpc ON mpc.id = pc.category_id");
+
+        @Override
+        public ProvisioningCriteriaDefinitionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+            Long id = rs.getLong("id");
+            // Long criteriaId = rs.getLong("criteria_id");
+            Long categoryId = rs.getLong("category_id");
+            String categoryName = rs.getString("category_name");
+            Long minAge = rs.getLong("min_age");
+            Long maxAge = rs.getLong("max_age");
+            BigDecimal provisioningPercentage = rs.getBigDecimal("provision_percentage");
+            Long liabilityAccount = rs.getLong("liability_account");
+            String liabilityAccountCode = rs.getString("liabilitycode");
+            String liabilityAccountName = rs.getString("liabilityname") ;
+            Long expenseAccount = rs.getLong("expense_account");
+            String expenseAccountCode = rs.getString("expensecode");
+            String expenseAccountName = rs.getString("expensename") ;
+            
+            return new ProvisioningCriteriaDefinitionData(id, categoryId, categoryName, minAge, maxAge, provisioningPercentage,
+                    liabilityAccount, liabilityAccountCode, liabilityAccountName, expenseAccount, expenseAccountCode, expenseAccountName);
+        }
+
+        public String schema() {
+            return sqlQuery.toString();
+        }
+    }
+
+    private String retrieveCriteriaName(Long criteriaId) {
+        ProvisioningCriteriaNameRowMapper rowMapper = new ProvisioningCriteriaNameRowMapper();
+        final String sql = "select " + rowMapper.schema() + " from m_provisioning_criteria pc where pc.id = ?";
+        return this.jdbcTemplate.queryForObject(sql, rowMapper, new Object[] { criteriaId });
+    }
+
+    private static final class ProvisioningCriteriaNameRowMapper implements RowMapper<String> {
+
+        @Override
+        public String mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            return rs.getString("criteriaName");
+        }
+
+        public String schema() {
+            return " pc.criteria_name as criteriaName";
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformService.java
new file mode 100644
index 0000000..d92206a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformService.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.organisation.provisioning.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+
+public interface ProvisioningCriteriaWritePlatformService {
+
+    CommandProcessingResult createProvisioningCriteria(JsonCommand command);
+    
+    CommandProcessingResult updateProvisioningCriteria(final Long categoryId, JsonCommand command) ;
+    
+    CommandProcessingResult deleteProvisioningCriteria(Long entryId) ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..48f5554
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,164 @@
+/**
+ * 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.provisioning.service;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
+import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.provisioning.constants.ProvisioningCriteriaConstants;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaDefinitionData;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria;
+import org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteriaRepository;
+import org.apache.fineract.organisation.provisioning.exception.ProvisioningCategoryNotFoundException;
+import org.apache.fineract.organisation.provisioning.exception.ProvisioningCriteriaCannotBeDeletedException;
+import org.apache.fineract.organisation.provisioning.exception.ProvisioningCriteriaNotFoundException;
+import org.apache.fineract.organisation.provisioning.serialization.ProvisioningCriteriaDefinitionJsonDeserializer;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+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 com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl implements ProvisioningCriteriaWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final ProvisioningCriteriaDefinitionJsonDeserializer fromApiJsonDeserializer;
+    private final ProvisioningCriteriaAssembler provisioningCriteriaAssembler;
+    private final ProvisioningCriteriaRepository provisioningCriteriaRepository;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final GLAccountRepository glAccountRepository;
+    private final ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService ;
+    
+    @Autowired
+    public ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl(final ProvisioningCriteriaDefinitionJsonDeserializer fromApiJsonDeserializer,
+            final ProvisioningCriteriaAssembler provisioningCriteriaAssembler, final ProvisioningCriteriaRepository provisioningCriteriaRepository,
+            final FromJsonHelper fromApiJsonHelper,
+            final GLAccountRepository glAccountRepository,
+            final ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService) {
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.provisioningCriteriaAssembler = provisioningCriteriaAssembler;
+        this.provisioningCriteriaRepository = provisioningCriteriaRepository;
+        this.fromApiJsonHelper = fromApiJsonHelper ;
+        this.glAccountRepository = glAccountRepository ; 
+        this.provisioningEntriesReadPlatformService = provisioningEntriesReadPlatformService ;
+    }
+
+    @Override
+    public CommandProcessingResult createProvisioningCriteria(JsonCommand command) {
+        try {
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+            ProvisioningCriteria provisioningCriteria = provisioningCriteriaAssembler.fromParsedJson(command.parsedJson());
+            this.provisioningCriteriaRepository.save(provisioningCriteria);
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(provisioningCriteria.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    public CommandProcessingResult deleteProvisioningCriteria(Long criteriaId) {
+        ProvisioningCriteria criteria = this.provisioningCriteriaRepository.findOne(criteriaId) ;
+        if(criteria == null) {
+            throw new ProvisioningCriteriaNotFoundException(criteriaId) ;
+        }
+        if(this.provisioningEntriesReadPlatformService.retrieveProvisioningEntryDataByCriteriaId(criteriaId) != null) {
+            throw new ProvisioningCriteriaCannotBeDeletedException(criteriaId) ;
+        }
+        this.provisioningCriteriaRepository.delete(criteriaId) ;
+        return new CommandProcessingResultBuilder().withEntityId(criteriaId).build();
+    }
+
+    @Override
+    public CommandProcessingResult updateProvisioningCriteria(final Long criteriaId, JsonCommand command) {
+        this.fromApiJsonDeserializer.validateForUpdate(command.json());
+        ProvisioningCriteria provisioningCriteria = provisioningCriteriaRepository.findOne(criteriaId) ;
+        if(provisioningCriteria == null) {
+            throw new ProvisioningCategoryNotFoundException(criteriaId) ;
+        }
+        List<LoanProduct> products = this.provisioningCriteriaAssembler.parseLoanProducts(command.parsedJson()) ;
+        
+        final Map<String, Object> changes = provisioningCriteria.update(command, products) ;
+        if(!changes.isEmpty()) {
+            updateProvisioningCriteriaDefinitions(provisioningCriteria, command) ;
+            provisioningCriteriaRepository.save(provisioningCriteria) ;    
+        }
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(provisioningCriteria.getId()).build();
+    }
+
+    private void updateProvisioningCriteriaDefinitions(ProvisioningCriteria provisioningCriteria, JsonCommand command) {
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command.parsedJson().getAsJsonObject());
+        JsonArray jsonProvisioningCriteria = this.fromApiJsonHelper.extractJsonArrayNamed(
+                ProvisioningCriteriaConstants.JSON_PROVISIONING_DEFINITIONS_PARAM, command.parsedJson());
+        for (JsonElement element : jsonProvisioningCriteria) {
+            JsonObject jsonObject = element.getAsJsonObject();
+            Long id = this.fromApiJsonHelper.extractLongNamed("id", jsonObject) ;
+            Long categoryId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_CATEOGRYID_PARAM, jsonObject);
+            Long minimumAge = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_MINIMUM_AGE_PARAM, jsonObject);
+            Long maximumAge = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_MAXIMUM_AGE_PARAM, jsonObject);
+            BigDecimal provisioningpercentage = this.fromApiJsonHelper.extractBigDecimalNamed(ProvisioningCriteriaConstants.JSON_PROVISIONING_PERCENTAGE_PARAM,
+                    jsonObject, locale);
+            Long liabilityAccountId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_LIABILITY_ACCOUNT_PARAM, jsonObject);
+            Long expenseAccountId = this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_EXPENSE_ACCOUNT_PARAM, jsonObject);
+            GLAccount liabilityAccount = glAccountRepository.findOne(liabilityAccountId);
+            GLAccount expenseAccount = glAccountRepository.findOne(expenseAccountId);
+            String categoryName = null ;
+            String liabilityAccountName = null ;
+            String expenseAccountName = null ;
+            ProvisioningCriteriaDefinitionData data = new ProvisioningCriteriaDefinitionData(id, categoryId, 
+                    categoryName, minimumAge, maximumAge, provisioningpercentage, 
+                    liabilityAccount.getId(), liabilityAccount.getGlCode(), liabilityAccountName, expenseAccount.getId(), expenseAccount.getGlCode(), expenseAccountName) ;
+            provisioningCriteria.update(data, liabilityAccount, expenseAccount) ;
+        }
+    }
+    
+    /*
+     * 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("criteria_name")) {
+            final String name = command.stringValueOfParameterNamed("criteria_name");
+            throw new PlatformDataIntegrityException("error.msg.provisioning.duplicate.criterianame", "Provisioning Criteria with name `"
+                    + name + "` already exists", "category name", name);
+        }
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.provisioning.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/api/StaffApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/api/StaffApiResource.java
new file mode 100644
index 0000000..c857874
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/api/StaffApiResource.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.organisation.staff.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.DefaultValue;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/staff")
+@Component
+@Scope("singleton")
+public class StaffApiResource {
+
+    /**
+     * The set of parameters that are supported in response for
+     * {@link StaffData}.
+     */
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "firstname", "lastname", "displayName",
+            "officeId", "officeName", "isLoanOfficer", "externalId", "mobileNo", "allowedOffices", "isActive", "joiningDate"));
+
+    private final String resourceNameForPermissions = "STAFF";
+
+    private final PlatformSecurityContext context;
+    private final StaffReadPlatformService readPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final DefaultToApiJsonSerializer<StaffData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public StaffApiResource(final PlatformSecurityContext context, final StaffReadPlatformService readPlatformService,
+            final OfficeReadPlatformService officeReadPlatformService, final DefaultToApiJsonSerializer<StaffData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveStaff(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("officeId") final Long officeId,
+            @DefaultValue("false") @QueryParam("staffInOfficeHierarchy") final boolean staffInOfficeHierarchy,
+            @DefaultValue("false") @QueryParam("loanOfficersOnly") final boolean loanOfficersOnly,
+            @DefaultValue("active") @QueryParam("status") final String status) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<StaffData> staff;
+        if (staffInOfficeHierarchy) {
+            staff = this.readPlatformService.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeId, loanOfficersOnly);
+        } else {
+            staff = this.readPlatformService.retrieveAllStaff(sqlSearch, officeId, loanOfficersOnly, status);
+        }
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, staff, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createStaff(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createStaff().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{staffId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retreiveStaff(@PathParam("staffId") final Long staffId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        StaffData staff = this.readPlatformService.retrieveStaff(staffId);
+        if (settings.isTemplate()) {
+            final Collection<OfficeData> allowedOffices = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+            staff = StaffData.templateData(staff, allowedOffices);
+        }
+        return this.toApiJsonSerializer.serialize(settings, staff, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{staffId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateStaff(@PathParam("staffId") final Long staffId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateStaff(staffId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/BulkTransferLoanOfficerData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/BulkTransferLoanOfficerData.java
new file mode 100644
index 0000000..44964ae
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/BulkTransferLoanOfficerData.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.organisation.staff.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object returned for loan-officer bulk transfer screens.
+ */
+public class BulkTransferLoanOfficerData {
+
+    @SuppressWarnings("unused")
+    private final Long officeId;
+    @SuppressWarnings("unused")
+    private final Long fromLoanOfficerId;
+    @SuppressWarnings("unused")
+    private final LocalDate assignmentDate;
+
+    // template
+    @SuppressWarnings("unused")
+    private final Collection<OfficeData> officeOptions;
+    @SuppressWarnings("unused")
+    private final Collection<StaffData> loanOfficerOptions;
+    @SuppressWarnings("unused")
+    private final StaffAccountSummaryCollectionData accountSummaryCollection;
+
+    public static BulkTransferLoanOfficerData templateForBulk(final Long officeId, final Long fromLoanOfficerId,
+            final LocalDate assignmentDate, final Collection<OfficeData> officeOptions, final Collection<StaffData> loanOfficerOptions,
+            final StaffAccountSummaryCollectionData accountSummaryCollection) {
+        return new BulkTransferLoanOfficerData(officeId, fromLoanOfficerId, assignmentDate, officeOptions, loanOfficerOptions,
+                accountSummaryCollection);
+    }
+
+    public static BulkTransferLoanOfficerData template(final Long fromLoanOfficerId, final Collection<StaffData> loanOfficerOptions,
+            final LocalDate assignmentDate) {
+        return new BulkTransferLoanOfficerData(null, fromLoanOfficerId, assignmentDate, null, loanOfficerOptions, null);
+    }
+
+    private BulkTransferLoanOfficerData(final Long officeId, final Long fromLoanOfficerId, final LocalDate assignmentDate,
+            final Collection<OfficeData> officeOptions, final Collection<StaffData> loanOfficerOptions,
+            final StaffAccountSummaryCollectionData accountSummaryCollection) {
+        this.officeId = officeId;
+        this.fromLoanOfficerId = fromLoanOfficerId;
+        this.assignmentDate = assignmentDate;
+        this.officeOptions = officeOptions;
+        this.loanOfficerOptions = loanOfficerOptions;
+        this.accountSummaryCollection = accountSummaryCollection;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffAccountSummaryCollectionData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffAccountSummaryCollectionData.java
new file mode 100644
index 0000000..fb754ed
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffAccountSummaryCollectionData.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.organisation.staff.data;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData;
+
+/**
+ *
+ */
+public class StaffAccountSummaryCollectionData {
+
+    @SuppressWarnings("unused")
+    private final List<LoanAccountSummary> clients;
+    @SuppressWarnings("unused")
+    private final List<LoanAccountSummary> groups;
+
+    public StaffAccountSummaryCollectionData(final List<LoanAccountSummary> clients, final List<LoanAccountSummary> groups) {
+        this.clients = clients;
+        this.groups = groups;
+    }
+
+    public static final class LoanAccountSummary {
+
+        private final Long id;
+        private final String displayName;
+
+        private Collection<LoanAccountSummaryData> loans;
+
+        public LoanAccountSummary(final Long id, final String displayName) {
+            this.id = id;
+            this.displayName = displayName;
+        }
+
+        public Long getId() {
+            return this.id;
+        }
+
+        public String getDisplayName() {
+            return this.displayName;
+        }
+
+        public Collection<LoanAccountSummaryData> getLoans() {
+            return this.loans;
+        }
+
+        public void setLoans(final Collection<LoanAccountSummaryData> loans) {
+            this.loans = loans;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffData.java
new file mode 100644
index 0000000..45f0dbd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffData.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.staff.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing staff data.
+ */
+public class StaffData {
+
+    private final Long id;
+    private final String externalId;
+    private final String firstname;
+    private final String lastname;
+    private final String displayName;
+    private final String mobileNo;
+    private final Long officeId;
+    private final String officeName;
+    private final Boolean isLoanOfficer;
+    private final Boolean isActive;
+    private final LocalDate joiningDate;
+
+    @SuppressWarnings("unused")
+    private final Collection<OfficeData> allowedOffices;
+
+    public static StaffData templateData(final StaffData staff, final Collection<OfficeData> allowedOffices) {
+        return new StaffData(staff.id, staff.firstname, staff.lastname, staff.displayName, staff.officeId, staff.officeName,
+                staff.isLoanOfficer, staff.externalId, staff.mobileNo, allowedOffices, staff.isActive, staff.joiningDate);
+    }
+
+    public static StaffData lookup(final Long id, final String displayName) {
+        return new StaffData(id, null, null, displayName, null, null, null, null, null, null, null, null);
+    }
+
+    public static StaffData instance(final Long id, final String firstname, final String lastname, final String displayName,
+            final Long officeId, final String officeName, final Boolean isLoanOfficer, final String externalId, final String mobileNo,
+            final boolean isActive, final LocalDate joiningDate) {
+        return new StaffData(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, mobileNo, null,
+                isActive, joiningDate);
+    }
+
+    private StaffData(final Long id, final String firstname, final String lastname, final String displayName, final Long officeId,
+            final String officeName, final Boolean isLoanOfficer, final String externalId, final String mobileNo,
+            final Collection<OfficeData> allowedOffices, final Boolean isActive, final LocalDate joiningDate) {
+        this.id = id;
+        this.firstname = firstname;
+        this.lastname = lastname;
+        this.displayName = displayName;
+        this.officeName = officeName;
+        this.isLoanOfficer = isLoanOfficer;
+        this.externalId = externalId;
+        this.officeId = officeId;
+        this.mobileNo = mobileNo;
+        this.allowedOffices = allowedOffices;
+        this.isActive = isActive;
+        this.joiningDate = joiningDate;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getDisplayName() {
+        return this.displayName;
+    }
+
+    public String getFirstname() {
+        return this.firstname;
+    }
+
+    public String getLastname() {
+        return this.lastname;
+    }
+
+    public String getOfficeName() {
+        return this.officeName;
+    }
+
+    public LocalDate getJoiningDate() {
+        return this.joiningDate;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/Staff.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/Staff.java
new file mode 100644
index 0000000..d616ab4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/Staff.java
@@ -0,0 +1,264 @@
+/**
+ * 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.staff.domain;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.*;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_staff", uniqueConstraints = { @UniqueConstraint(columnNames = { "display_name" }, name = "display_name"),
+        @UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE"),
+        @UniqueConstraint(columnNames = { "mobile_no" }, name = "mobile_no_UNIQUE") })
+public class Staff extends AbstractPersistable<Long> {
+
+    @Column(name = "firstname", length = 50)
+    private String firstname;
+
+    @Column(name = "lastname", length = 50)
+    private String lastname;
+
+    @Column(name = "display_name", length = 100)
+    private String displayName;
+
+    @Column(name = "mobile_no", length = 50, nullable = false, unique = true)
+    private String mobileNo;
+
+    @Column(name = "external_id", length = 100, nullable = true, unique = true)
+    private String externalId;
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @Column(name = "is_loan_officer ", nullable = false)
+    private boolean loanOfficer;
+
+    @Column(name = "organisational_role_enum", nullable = true)
+    private Integer organisationalRoleType;
+
+    @Column(name = "is_active", nullable = false)
+    private boolean active;
+
+    @Column(name = "joining_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date joiningDate;
+
+    @ManyToOne
+    @JoinColumn(name = "organisational_role_parent_staff_id", nullable = true)
+    private Staff organisationalRoleParentStaff;
+
+    @OneToOne(optional = true)
+    @JoinColumn(name = "image_id", nullable = true)
+    private Image image;
+
+    public static Staff fromJson(final Office staffOffice, final JsonCommand command) {
+
+        final String firstnameParamName = "firstname";
+        final String firstname = command.stringValueOfParameterNamed(firstnameParamName);
+
+        final String lastnameParamName = "lastname";
+        final String lastname = command.stringValueOfParameterNamed(lastnameParamName);
+
+        final String externalIdParamName = "externalId";
+        final String externalId = command.stringValueOfParameterNamedAllowingNull(externalIdParamName);
+
+        final String mobileNoParamName = "mobileNo";
+        final String mobileNo = command.stringValueOfParameterNamedAllowingNull(mobileNoParamName);
+
+        final String isLoanOfficerParamName = "isLoanOfficer";
+        final boolean isLoanOfficer = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
+
+        final String isActiveParamName = "isActive";
+        final Boolean isActive = command.booleanObjectValueOfParameterNamed(isActiveParamName);
+
+        LocalDate joiningDate = null;
+
+        final String joiningDateParamName = "joiningDate";
+        if (command.hasParameter(joiningDateParamName)) {
+            joiningDate = command.localDateValueOfParameterNamed(joiningDateParamName);
+        }
+
+        return new Staff(staffOffice, firstname, lastname, externalId, mobileNo, isLoanOfficer, isActive, joiningDate);
+    }
+
+    protected Staff() {
+        //
+    }
+
+    private Staff(final Office staffOffice, final String firstname, final String lastname, final String externalId, final String mobileNo,
+            final boolean isLoanOfficer, final Boolean isActive, final LocalDate joiningDate) {
+        this.office = staffOffice;
+        this.firstname = StringUtils.defaultIfEmpty(firstname, null);
+        this.lastname = StringUtils.defaultIfEmpty(lastname, null);
+        this.externalId = StringUtils.defaultIfEmpty(externalId, null);
+        this.mobileNo = StringUtils.defaultIfEmpty(mobileNo, null);
+        this.loanOfficer = isLoanOfficer;
+        this.active = (isActive == null) ? true : isActive;
+        deriveDisplayName(firstname);
+        if (joiningDate != null) {
+            this.joiningDate = joiningDate.toDateTimeAtStartOfDay().toDate();
+        }
+    }
+
+    public EnumOptionData organisationalRoleData() {
+        EnumOptionData organisationalRole = null;
+        if (this.organisationalRoleType != null) {
+            organisationalRole = StaffEnumerations.organisationalRole(this.organisationalRoleType);
+        }
+        return organisationalRole;
+    }
+
+    public void changeOffice(final Office newOffice) {
+        this.office = newOffice;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String officeIdParamName = "officeId";
+        if (command.isChangeInLongParameterNamed(officeIdParamName, this.office.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(officeIdParamName);
+            actualChanges.put(officeIdParamName, newValue);
+        }
+
+        boolean firstnameChanged = false;
+        final String firstnameParamName = "firstname";
+        if (command.isChangeInStringParameterNamed(firstnameParamName, this.firstname)) {
+            final String newValue = command.stringValueOfParameterNamed(firstnameParamName);
+            actualChanges.put(firstnameParamName, newValue);
+            this.firstname = newValue;
+            firstnameChanged = true;
+        }
+
+        boolean lastnameChanged = false;
+        final String lastnameParamName = "lastname";
+        if (command.isChangeInStringParameterNamed(lastnameParamName, this.lastname)) {
+            final String newValue = command.stringValueOfParameterNamed(lastnameParamName);
+            actualChanges.put(lastnameParamName, newValue);
+            this.lastname = newValue;
+            lastnameChanged = true;
+        }
+
+        if (firstnameChanged || lastnameChanged) {
+            deriveDisplayName(this.firstname);
+        }
+
+        final String externalIdParamName = "externalId";
+        if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) {
+            final String newValue = command.stringValueOfParameterNamed(externalIdParamName);
+            actualChanges.put(externalIdParamName, newValue);
+            this.externalId = newValue;
+        }
+
+        final String mobileNoParamName = "mobileNo";
+        if (command.isChangeInStringParameterNamed(mobileNoParamName, this.mobileNo)) {
+            final String newValue = command.stringValueOfParameterNamed(mobileNoParamName);
+            actualChanges.put(mobileNoParamName, newValue);
+            this.mobileNo = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String isLoanOfficerParamName = "isLoanOfficer";
+        if (command.isChangeInBooleanParameterNamed(isLoanOfficerParamName, this.loanOfficer)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
+            actualChanges.put(isLoanOfficerParamName, newValue);
+            this.loanOfficer = newValue;
+        }
+
+        final String isActiveParamName = "isActive";
+        if (command.isChangeInBooleanParameterNamed(isActiveParamName, this.active)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isActiveParamName);
+            actualChanges.put(isActiveParamName, newValue);
+            this.active = newValue;
+        }
+
+        final String joiningDateParamName = "joiningDate";
+        if (command.isChangeInDateParameterNamed(joiningDateParamName, this.joiningDate)) {
+            final String valueAsInput = command.stringValueOfParameterNamed(joiningDateParamName);
+            actualChanges.put(joiningDateParamName, valueAsInput);
+            final LocalDate newValue = command.localDateValueOfParameterNamed(joiningDateParamName);
+            this.joiningDate = newValue.toDate();
+        }
+
+        return actualChanges;
+    }
+
+    public boolean isNotLoanOfficer() {
+        return !isLoanOfficer();
+    }
+
+    public boolean isLoanOfficer() {
+        return this.loanOfficer;
+    }
+
+    public boolean isNotActive() {
+        return !isActive();
+    }
+
+    public boolean isActive() {
+        return this.active;
+    }
+
+    private void deriveDisplayName(final String firstname) {
+        if (!StringUtils.isBlank(firstname)) {
+            this.displayName = this.lastname + ", " + this.firstname;
+        } else {
+            this.displayName = this.lastname;
+        }
+    }
+
+    public boolean identifiedBy(final Staff staff) {
+        return getId().equals(staff.getId());
+    }
+
+    public Long officeId() {
+        return this.office.getId();
+    }
+
+    public String displayName() {
+        return this.displayName;
+    }
+
+    public String mobileNo() {
+        return this.mobileNo;
+    }
+
+    public Office office() {
+        return this.office;
+    }
+
+    public void setImage(Image image) {
+        this.image = image;
+    }
+
+    public Image getImage() {
+        return this.image;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffEnumerations.java
new file mode 100644
index 0000000..9a5312f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffEnumerations.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.organisation.staff.domain;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class StaffEnumerations {
+
+    public static EnumOptionData organisationalRole(final Integer id) {
+        return organisationalRole(StaffOrganisationalRoleType.fromInt(id));
+    }
+
+    public static EnumOptionData organisationalRole(final StaffOrganisationalRoleType type) {
+        EnumOptionData optionData = new EnumOptionData(StaffOrganisationalRoleType.INVALID.getValue().longValue(),
+                StaffOrganisationalRoleType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case PROGRAM_DIRECTOR:
+                optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Program Director");
+            break;
+            case BRANCH_MANAGER:
+                optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Branch Manager");
+            break;
+            case FIELD_OFFICER_COORDINATOR:
+                optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Field Officer Coordinator");
+            break;
+            case FIELD_OFFICER:
+                optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Field Officer");
+            break;
+            case INVALID:
+            break;
+        }
+        return optionData;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffOrganisationalRoleType.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffOrganisationalRoleType.java
new file mode 100644
index 0000000..b2bd263
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffOrganisationalRoleType.java
@@ -0,0 +1,63 @@
+/**
+ * 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.staff.domain;
+
+public enum StaffOrganisationalRoleType {
+
+    INVALID(0, "staffOrganisationalRoleType.invalid"), //
+    PROGRAM_DIRECTOR(100, "staffOrganisationalRoleType.programDirector"), //
+    BRANCH_MANAGER(200, "staffOrganisationalRoleType.branchManager"), //
+    FIELD_OFFICER_COORDINATOR(300, "staffOrganisationalRoleType.coordinator"), //
+    FIELD_OFFICER(400, "staffOrganisationalRoleType.fieldAgent");
+
+    private final Integer value;
+    private final String code;
+
+    private StaffOrganisationalRoleType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static StaffOrganisationalRoleType fromInt(final Integer chargeCalculation) {
+        StaffOrganisationalRoleType chargeCalculationType = StaffOrganisationalRoleType.INVALID;
+        switch (chargeCalculation) {
+            case 100:
+                chargeCalculationType = PROGRAM_DIRECTOR;
+            break;
+            case 200:
+                chargeCalculationType = BRANCH_MANAGER;
+            break;
+            case 300:
+                chargeCalculationType = FIELD_OFFICER_COORDINATOR;
+            break;
+            case 400:
+                chargeCalculationType = FIELD_OFFICER;
+            break;
+        }
+        return chargeCalculationType;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffRepository.java
new file mode 100644
index 0000000..e716a00
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffRepository.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.organisation.staff.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 StaffRepository extends JpaRepository<Staff, Long>, JpaSpecificationExecutor<Staff> {
+
+    public final static String FIND_BY_OFFICE_QUERY = "select s from Staff s where s.id = :id AND s.office.id = :officeId";
+
+    /**
+     * Find staff by officeid.
+     */
+    @Query(FIND_BY_OFFICE_QUERY)
+    public Staff findByOffice(@Param("id") Long id, @Param("officeId") Long officeId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffRepositoryWrapper.java
new file mode 100755
index 0000000..72c17bc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/StaffRepositoryWrapper.java
@@ -0,0 +1,63 @@
+/**
+ * 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.staff.domain;
+
+import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link StaffRepository} that adds NULL checking and Error
+ * handling capabilities
+ * </p>
+ */
+@Service
+public class StaffRepositoryWrapper {
+
+    private final StaffRepository repository;
+
+    @Autowired
+    public StaffRepositoryWrapper(final StaffRepository repository) {
+        this.repository = repository;
+    }
+
+    public Staff findOneWithNotFoundDetection(final Long id) {
+        final Staff staff = this.repository.findOne(id);
+        if (staff == null) { throw new StaffNotFoundException(id); }
+        return staff;
+    }
+
+    public Staff findByOfficeWithNotFoundDetection(final Long staffId, final Long officeId) {
+        final Staff staff = this.repository.findByOffice(staffId, officeId);
+        if (staff == null) { throw new StaffNotFoundException(staffId); }
+        return staff;
+    }
+
+    public Staff findByOfficeHierarchyWithNotFoundDetection(final Long staffId, final String hierarchy) {
+        final Staff staff = this.repository.findOne(staffId);
+        if (staff == null) { throw new StaffNotFoundException(staffId); }
+        final String staffhierarchy = staff.office().getHierarchy();
+        if (!hierarchy.startsWith(staffhierarchy)) { throw new StaffNotFoundException(staffId); }
+        return staff;
+    }
+    public void save(final Staff staff){
+        this.repository.save(staff);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/exception/StaffNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/exception/StaffNotFoundException.java
new file mode 100644
index 0000000..cfa5c32
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/exception/StaffNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.staff.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when staff resources are not found.
+ */
+public class StaffNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public StaffNotFoundException(final Long id) {
+        super("error.msg.staff.id.invalid", "Staff with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/exception/StaffRoleException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/exception/StaffRoleException.java
new file mode 100644
index 0000000..a5f980f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/exception/StaffRoleException.java
@@ -0,0 +1,40 @@
+/**
+ * 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.staff.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when staff resources are not found.
+ */
+public class StaffRoleException extends AbstractPlatformResourceNotFoundException {
+
+    public static enum STAFF_ROLE {
+        LOAN_OFFICER, BRANCH_MANAGER,SAVINGS_OFFICER;
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("-", " ").toLowerCase();
+        }
+    }
+
+    public StaffRoleException(final Long id, final STAFF_ROLE role) {
+        super("error.msg.staff.id.invalid.role", "Staff with identifier " + id + " is not a " + role.toString(), id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/handler/CreateStaffCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/handler/CreateStaffCommandHandler.java
new file mode 100644
index 0000000..75a17b2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/handler/CreateStaffCommandHandler.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.organisation.staff.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.organisation.staff.service.StaffWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "STAFF", action = "CREATE")
+public class CreateStaffCommandHandler implements NewCommandSourceHandler {
+
+    private final StaffWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateStaffCommandHandler(final StaffWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createStaff(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/handler/UpdateStaffCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/handler/UpdateStaffCommandHandler.java
new file mode 100644
index 0000000..37ba167
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/handler/UpdateStaffCommandHandler.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.organisation.staff.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.organisation.staff.service.StaffWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "STAFF", action = "UPDATE")
+public class UpdateStaffCommandHandler implements NewCommandSourceHandler {
+
+    private final StaffWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateStaffCommandHandler(final StaffWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateStaff(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/serialization/StaffCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/serialization/StaffCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..1619741
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/serialization/StaffCommandFromApiJsonDeserializer.java
@@ -0,0 +1,196 @@
+/**
+ * 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.staff.serialization;
+
+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.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.joda.time.LocalDate;
+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 StaffCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("firstname", "lastname", "officeId", "externalId",
+            "mobileNo", "isLoanOfficer", "isActive", "joiningDate", "dateFormat", "locale", "forceStatus"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+    
+    private final StaffReadPlatformService staffReadPlatformService;
+
+
+    @Autowired
+    public StaffCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper,
+            final StaffReadPlatformService staffReadPlatformService) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.staffReadPlatformService = staffReadPlatformService;        
+    }
+
+    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("staff");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed("officeId", element);
+        baseDataValidator.reset().parameter("officeId").value(officeId).notNull().integerGreaterThanZero();
+
+        final String firstname = this.fromApiJsonHelper.extractStringNamed("firstname", element);
+        baseDataValidator.reset().parameter("firstname").value(firstname).notBlank().notExceedingLengthOf(50);
+
+        final String lastname = this.fromApiJsonHelper.extractStringNamed("lastname", element);
+        baseDataValidator.reset().parameter("lastname").value(lastname).notBlank().notExceedingLengthOf(50);
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mobileNoParamName, element)) {
+            final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("isLoanOfficer", element)) {
+            final Boolean loanOfficerFlag = this.fromApiJsonHelper.extractBooleanNamed("isLoanOfficer", element);
+            baseDataValidator.reset().parameter("isLoanOfficer").value(loanOfficerFlag).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("isActive", element)) {
+            final Boolean activeFlag = this.fromApiJsonHelper.extractBooleanNamed("isActive", element);
+            baseDataValidator.reset().parameter("isActive").value(activeFlag).notNull();
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists("joiningDate", element)) {
+            final LocalDate joiningDate = this.fromApiJsonHelper.extractLocalDateNamed("joiningDate", element);
+            baseDataValidator.reset().parameter("joiningDate").value(joiningDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("dateFormat", element)) {
+        	final String dateFormat = this.fromApiJsonHelper.extractStringNamed("dateFormat", element);
+        	baseDataValidator.reset().parameter("dateFormat").value(dateFormat).notBlank();
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists("locale", element)) {
+        	final String locale = this.fromApiJsonHelper.extractStringNamed("locale", element);
+        	baseDataValidator.reset().parameter("locale").value(locale).notBlank();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdate(final String json) {
+        validateForUpdate(json, null); 
+    }
+    
+    public void validateForUpdate(final String json,Long staffId) {
+        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("staff");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        if (this.fromApiJsonHelper.parameterExists("officeId", element)) {
+            final Long officeId = this.fromApiJsonHelper.extractLongNamed("officeId", element);
+            baseDataValidator.reset().parameter("officeId").value(officeId).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("firstname", element)) {
+            final String firstname = this.fromApiJsonHelper.extractStringNamed("firstname", element);
+            baseDataValidator.reset().parameter("firstname").value(firstname).notBlank().notExceedingLengthOf(50);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("lastname", element)) {
+            final String lastname = this.fromApiJsonHelper.extractStringNamed("lastname", element);
+            baseDataValidator.reset().parameter("lastname").value(lastname).notBlank().notExceedingLengthOf(50);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mobileNoParamName, element)) {
+            final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).notExceedingLengthOf(50);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("isLoanOfficer", element)) {
+            final Boolean loanOfficerFlag = this.fromApiJsonHelper.extractBooleanNamed("isLoanOfficer", element);
+            baseDataValidator.reset().parameter("isLoanOfficer").value(loanOfficerFlag).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("isActive", element)) {
+            final Boolean activeFlag = this.fromApiJsonHelper.extractBooleanNamed("isActive", element);
+            //Need to add here check to see if any clients, group, account and loans are assigned to this staff if staff is being set to inactive --LJB
+            final Boolean forceStatus = this.fromApiJsonHelper.extractBooleanNamed("forceStatus", element);
+            if ((!activeFlag && forceStatus == null) || 
+                (!activeFlag && forceStatus)) {           
+            	 Object[] result = staffReadPlatformService.hasAssociatedItems(staffId);
+            	
+            	if (result != null && result.length > 0) {
+            		baseDataValidator.reset().parameter("isactive").failWithCode("staff.is.assigned",result);
+            	}
+            	
+            }
+            baseDataValidator.reset().parameter("isActive").value(activeFlag).notNull();
+        }
+                
+        if (this.fromApiJsonHelper.parameterExists("joiningDate", element)) {
+            final LocalDate joiningDate = this.fromApiJsonHelper.extractLocalDateNamed("joiningDate", element);
+            baseDataValidator.reset().parameter("joiningDate").value(joiningDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("dateFormat", element)) {
+        	final String dateFormat = this.fromApiJsonHelper.extractStringNamed("dateFormat", element);
+        	baseDataValidator.reset().parameter("dateFormat").value(dateFormat).notBlank();
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists("locale", element)) {
+        	final String locale = this.fromApiJsonHelper.extractStringNamed("locale", element);
+        	baseDataValidator.reset().parameter("locale").value(locale).notBlank();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformService.java
new file mode 100644
index 0000000..f56c6cc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformService.java
@@ -0,0 +1,42 @@
+/**
+ * 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.staff.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.staff.data.StaffData;
+
+public interface StaffReadPlatformService {
+
+    StaffData retrieveStaff(Long staffId);
+
+    Collection<StaffData> retrieveAllStaffForDropdown(Long officeId);
+
+    Collection<StaffData> retrieveAllLoanOfficersInOfficeById(final Long officeId);
+
+    /**
+     * returns all staff in offices that are above the provided
+     * <code>officeId</code>.
+     */
+    Collection<StaffData> retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(Long officeId, boolean loanOfficersOnly);
+
+    Collection<StaffData> retrieveAllStaff(String sqlSearch, Long officeId, boolean loanOfficersOnly, String status);
+    
+    Object[] hasAssociatedItems(final Long staffId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformServiceImpl.java
new file mode 100644
index 0000000..73f648e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformServiceImpl.java
@@ -0,0 +1,296 @@
+/**
+ * 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.staff.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
+import org.apache.fineract.portfolio.client.domain.ClientStatus;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final StaffLookupMapper lookupMapper = new StaffLookupMapper();
+    private final StaffInOfficeHierarchyMapper staffInOfficeHierarchyMapper = new StaffInOfficeHierarchyMapper();
+
+    @Autowired
+    public StaffReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class StaffMapper implements RowMapper<StaffData> {
+
+        public String schema() {
+            return " s.id as id,s.office_id as officeId, o.name as officeName, s.firstname as firstname, s.lastname as lastname,"
+                    + " s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId, s.mobile_no as mobileNo,"
+            		+ " s.is_active as isActive, s.joining_date as joiningDate from m_staff s "
+                    + " join m_office o on o.id = s.office_id";
+        }
+
+        @Override
+        public StaffData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String firstname = rs.getString("firstname");
+            final String lastname = rs.getString("lastname");
+            final String displayName = rs.getString("displayName");
+            final Long officeId = rs.getLong("officeId");
+            final boolean isLoanOfficer = rs.getBoolean("isLoanOfficer");
+            final String officeName = rs.getString("officeName");
+            final String externalId = rs.getString("externalId");
+            final String mobileNo = rs.getString("mobileNo");
+            final boolean isActive = rs.getBoolean("isActive");
+            final LocalDate joiningDate = JdbcSupport.getLocalDate(rs, "joiningDate");
+
+            return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, mobileNo,
+                    isActive, joiningDate);
+        }
+    }
+
+    private static final class StaffInOfficeHierarchyMapper implements RowMapper<StaffData> {
+
+        public String schema(final boolean loanOfficersOnly) {
+
+            final StringBuilder sqlBuilder = new StringBuilder(200);
+
+            sqlBuilder.append("s.id as id, s.office_id as officeId, ohierarchy.name as officeName,");
+            sqlBuilder.append("s.firstname as firstname, s.lastname as lastname,");
+            sqlBuilder.append("s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId, ");
+            sqlBuilder.append("s.mobile_no as mobileNo, s.is_active as isActive, s.joining_date as joiningDate ");
+            sqlBuilder.append("from m_office o ");
+            sqlBuilder.append("join m_office ohierarchy on o.hierarchy like concat(ohierarchy.hierarchy, '%') ");
+            sqlBuilder.append("join m_staff s on s.office_id = ohierarchy.id and s.is_active=1 ");
+
+            if (loanOfficersOnly) {
+                sqlBuilder.append("and s.is_loan_officer is true ");
+            }
+
+            sqlBuilder.append("where o.id = ? ");
+
+            return sqlBuilder.toString();
+        }
+
+        @Override
+        public StaffData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String firstname = rs.getString("firstname");
+            final String lastname = rs.getString("lastname");
+            final String displayName = rs.getString("displayName");
+            final Long officeId = rs.getLong("officeId");
+            final String officeName = rs.getString("officeName");
+            final boolean isLoanOfficer = rs.getBoolean("isLoanOfficer");
+            final String externalId = rs.getString("externalId");
+            final String mobileNo = rs.getString("mobileNo");
+            final boolean isActive = rs.getBoolean("isActive");
+            final LocalDate joiningDate = JdbcSupport.getLocalDate(rs, "joiningDate");
+
+            return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, mobileNo,
+                    isActive, joiningDate);
+        }
+    }
+
+    private static final class StaffLookupMapper implements RowMapper<StaffData> {
+
+        private final String schemaSql;
+
+        public StaffLookupMapper() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(100);
+            sqlBuilder.append("s.id as id, s.display_name as displayName ");
+            sqlBuilder.append("from m_staff s ");
+            sqlBuilder.append("join m_office o on o.id = s.office_id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public StaffData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String displayName = rs.getString("displayName");
+            return StaffData.lookup(id, displayName);
+        }
+    }
+
+    @Override
+    public Collection<StaffData> retrieveAllLoanOfficersInOfficeById(final Long officeId) {
+        return retrieveAllStaff(" office_id=" + officeId + " and is_loan_officer=1");
+    }
+
+    @Override
+    public Collection<StaffData> retrieveAllStaffForDropdown(final Long officeId) {
+    	
+    	//adding the Authorization criteria so that a user cannot see an employee who does not belong to his office or 	a sub office for his office.
+        final String hierarchy = this.context.authenticatedUser().getOffice().getHierarchy();
+
+        final Long defaultOfficeId = defaultToUsersOfficeIfNull(officeId);
+
+        final String sql = "select " + this.lookupMapper.schema() + " where s.office_id = ? and s.is_active=1 and o.hierarchy like '"+ hierarchy+ "%'";
+
+        return this.jdbcTemplate.query(sql, this.lookupMapper, new Object[] { defaultOfficeId });
+    }
+
+    private Long defaultToUsersOfficeIfNull(final Long officeId) {
+        Long defaultOfficeId = officeId;
+        if (defaultOfficeId == null) {
+            defaultOfficeId = this.context.authenticatedUser().getOffice().getId();
+        }
+        return defaultOfficeId;
+    }
+
+    @Override
+    public StaffData retrieveStaff(final Long staffId) {
+    	
+        //adding the Authorization criteria so that a user cannot see an employee who does not belong to his office or 	a sub office for his office.
+        final String hierarchy = this.context.authenticatedUser().getOffice().getHierarchy();
+         
+
+        try {
+            final StaffMapper rm = new StaffMapper();
+            final String sql = "select " + rm.schema() + " where s.id = ? and o.hierarchy like '"+ hierarchy+ "%'" ;
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { staffId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new StaffNotFoundException(staffId);
+        }
+    }
+
+    @Override
+    public Collection<StaffData> retrieveAllStaff(final String sqlSearch, final Long officeId, final boolean loanOfficersOnly,
+            final String status) {
+        final String extraCriteria = getStaffCriteria(sqlSearch, officeId, loanOfficersOnly, status);
+        return retrieveAllStaff(extraCriteria);
+    }
+
+    private Collection<StaffData> retrieveAllStaff(final String extraCriteria) {
+
+        final StaffMapper rm = new StaffMapper();
+        String sql = "select " + rm.schema();
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            sql += " where " + extraCriteria;
+        }
+        sql = sql + " order by s.lastname";
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    private String getStaffCriteria(final String sqlSearch, final Long officeId, final boolean loanOfficersOnly, final String status) {
+
+        final StringBuffer extraCriteria = new StringBuffer(200);
+
+        if (sqlSearch != null) {
+            extraCriteria.append(" and (").append(sqlSearch).append(")");
+        }
+        if (officeId != null) {
+            extraCriteria.append(" and office_id = ").append(officeId).append(" ");
+        }
+        if (loanOfficersOnly) {
+            extraCriteria.append(" and s.is_loan_officer is true ");
+        }
+        // Passing status parameter to get ACTIVE (By Default), INACTIVE or ALL
+        // (Both active and Inactive) employees
+        if (status.equalsIgnoreCase("active")) {
+            extraCriteria.append(" and is_active = 1 ");
+        } else if (status.equalsIgnoreCase("inActive")) {
+            extraCriteria.append(" and is_active = 0 ");
+        } else if (status.equalsIgnoreCase("all")) {} else {
+            throw new UnrecognizedQueryParamException("status", status, new Object[] { "all", "active", "inactive" });
+        }
+        
+        //adding the Authorization criteria so that a user cannot see an employee who does not belong to his office or 	a sub office for his office.
+        final String hierarchy = this.context.authenticatedUser().getOffice().getHierarchy();
+        extraCriteria.append(" and o.hierarchy like '"+ hierarchy+ "%' ");
+
+        if (StringUtils.isNotBlank(extraCriteria.toString())) {
+            extraCriteria.delete(0, 4);
+        }
+
+        // remove begin four letter including a space from the string.
+        return extraCriteria.toString();
+    }
+
+    @Override
+    public Collection<StaffData> retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(final Long officeId, final boolean loanOfficersOnly) {
+
+        String sql = "select " + this.staffInOfficeHierarchyMapper.schema(loanOfficersOnly);
+        sql = sql + " order by s.lastname";
+        return this.jdbcTemplate.query(sql, this.staffInOfficeHierarchyMapper, new Object[] { officeId });
+    }
+    
+    @Override
+	public Object[] hasAssociatedItems(final Long staffId){
+        ArrayList<String> params = new ArrayList<String>();        
+                
+        String sql =  "select c.display_name as client, g.display_name as grp,l.loan_officer_id as loan, s.field_officer_id as sav"+
+        			  " from m_staff staff "+
+        			  " left outer join m_client c on staff.id = c.staff_id  AND c.status_enum < "+ ClientStatus.CLOSED.getValue() +
+        			  " left outer join m_group g on staff.id = g.staff_id " +
+                      " left outer join m_loan l on staff.id = l.loan_officer_id and l.loan_status_id < " +  LoanStatus.WITHDRAWN_BY_CLIENT.getValue() +
+                      " left outer join m_savings_account s on c.staff_id = s.field_officer_id and s.status_enum < "+ SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue() +
+                      " where  staff.id  =  " + staffId +
+                      " group by staff.id";
+        
+       
+        List<Map<String, Object>> result =  this.jdbcTemplate.queryForList(sql);
+        if (result != null) {
+       		for (Map<String, Object> map : result) {
+       			if (map.get("client") != null) {
+       				params.add("client");
+       			}
+       			if (map.get("grp") != null) {
+       				params.add("group");
+       			}
+       			if (map.get("loan")  != null) {
+       				params.add("loan");
+       			}
+       			if (map.get("sav")  != null) {
+       				params.add("savings account");
+       			}
+       		}		
+        }
+        return params.toArray();
+        
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformService.java
new file mode 100644
index 0000000..c3682b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformService.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.organisation.staff.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface StaffWritePlatformService {
+
+    CommandProcessingResult createStaff(final JsonCommand command);
+
+    CommandProcessingResult updateStaff(final Long staffId, final JsonCommand command);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..a8a6e4f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,144 @@
+/**
+ * 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.staff.service;
+
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepository;
+import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
+import org.apache.fineract.organisation.staff.serialization.StaffCommandFromApiJsonDeserializer;
+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 StaffWritePlatformServiceJpaRepositoryImpl implements StaffWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(StaffWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final StaffCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final StaffRepository staffRepository;
+    private final OfficeRepository officeRepository;
+
+    @Autowired
+    public StaffWritePlatformServiceJpaRepositoryImpl(final StaffCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final StaffRepository staffRepository, final OfficeRepository officeRepository) {
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.staffRepository = staffRepository;
+        this.officeRepository = officeRepository;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createStaff(final JsonCommand command) {
+
+        try {
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final Long officeId = command.longValueOfParameterNamed("officeId");
+
+            final Office staffOffice = this.officeRepository.findOne(officeId);
+            if (staffOffice == null) { throw new OfficeNotFoundException(officeId); }
+
+            final Staff staff = Staff.fromJson(staffOffice, command);
+
+            this.staffRepository.save(staff);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(staff.getId()).withOfficeId(officeId) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleStaffDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateStaff(final Long staffId, final JsonCommand command) {
+
+        try {
+            this.fromApiJsonDeserializer.validateForUpdate(command.json(), staffId);
+
+            final Staff staffForUpdate = this.staffRepository.findOne(staffId);
+            if (staffForUpdate == null) { throw new StaffNotFoundException(staffId); }
+
+            final Map<String, Object> changesOnly = staffForUpdate.update(command);
+
+            if (changesOnly.containsKey("officeId")) {
+                final Long officeId = (Long) changesOnly.get("officeId");
+                final Office newOffice = this.officeRepository.findOne(officeId);
+                if (newOffice == null) { throw new OfficeNotFoundException(officeId); }
+
+                staffForUpdate.changeOffice(newOffice);
+            }
+
+            if (!changesOnly.isEmpty()) {
+                this.staffRepository.saveAndFlush(staffForUpdate);
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(staffId)
+                    .withOfficeId(staffForUpdate.officeId()).with(changesOnly).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleStaffDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleStaffDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+
+        if (realCause.getMessage().contains("external_id")) {
+
+            final String externalId = command.stringValueOfParameterNamed("externalId");
+            throw new PlatformDataIntegrityException("error.msg.staff.duplicate.externalId", "Staff with externalId `" + externalId
+                    + "` already exists", "externalId", externalId);
+        } else if (realCause.getMessage().contains("display_name")) {
+            final String lastname = command.stringValueOfParameterNamed("lastname");
+            String displayName = lastname;
+            if (!StringUtils.isBlank(displayName)) {
+                final String firstname = command.stringValueOfParameterNamed("firstname");
+                displayName = lastname + ", " + firstname;
+            }
+            throw new PlatformDataIntegrityException("error.msg.staff.duplicate.displayName", "A staff with the given display name '"
+                    + displayName + "' already exists", "displayName", displayName);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.staff.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/CashierApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/CashierApiResource.java
new file mode 100644
index 0000000..788adfc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/CashierApiResource.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.organisation.teller.api;
+
+import java.util.Collection;
+import java.util.Date;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.organisation.teller.data.CashierData;
+import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformService;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("cashiers")
+@Component
+@Scope("singleton")
+public class CashierApiResource {
+
+    private final DefaultToApiJsonSerializer<CashierData> jsonSerializer;
+    private final TellerManagementReadPlatformService readPlatformService;
+
+    @Autowired
+    public CashierApiResource(DefaultToApiJsonSerializer<CashierData> jsonSerializer,
+            TellerManagementReadPlatformService readPlatformService) {
+        this.jsonSerializer = jsonSerializer;
+        this.readPlatformService = readPlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getCashierData(@QueryParam("officeId") final Long officeId, @QueryParam("tellerId") final Long tellerId,
+            @QueryParam("staffId") final Long staffId, @QueryParam("date") final String date) {
+        final DateTimeFormatter dateFormatter = ISODateTimeFormat.basicDate();
+
+        final Date dateRestriction = (date != null ? dateFormatter.parseDateTime(date).toDate() : new Date());
+
+        final Collection<CashierData> allCashiers = this.readPlatformService.getCashierData(officeId, tellerId, staffId, dateRestriction);
+
+        return this.jsonSerializer.serialize(allCashiers);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java
new file mode 100644
index 0000000..0ea3d8b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java
@@ -0,0 +1,339 @@
+/**
+ * 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.teller.api;
+
+import java.util.Collection;
+import java.util.Date;
+
+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.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+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.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.teller.data.CashierData;
+import org.apache.fineract.organisation.teller.data.CashierTransactionData;
+import org.apache.fineract.organisation.teller.data.CashierTransactionsWithSummaryData;
+import org.apache.fineract.organisation.teller.data.TellerData;
+import org.apache.fineract.organisation.teller.data.TellerJournalData;
+import org.apache.fineract.organisation.teller.data.TellerTransactionData;
+import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformService;
+import org.apache.fineract.organisation.teller.util.DateRange;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("tellers")
+@Component
+@Scope("singleton")
+public class TellerApiResource {
+
+    private final PlatformSecurityContext securityContext;
+    private final DefaultToApiJsonSerializer<TellerData> jsonSerializer;
+    private final TellerManagementReadPlatformService readPlatformService;
+    private final PortfolioCommandSourceWritePlatformService commandWritePlatformService;
+
+    @Autowired
+    public TellerApiResource(PlatformSecurityContext securityContext, DefaultToApiJsonSerializer<TellerData> jsonSerializer,
+            TellerManagementReadPlatformService readPlatformService,
+            PortfolioCommandSourceWritePlatformService commandWritePlatformService) {
+        super();
+        this.securityContext = securityContext;
+        this.jsonSerializer = jsonSerializer;
+        this.readPlatformService = readPlatformService;
+        this.commandWritePlatformService = commandWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getTellerData(@QueryParam("officeId") final Long officeId) {
+        final Collection<TellerData> foundTellers = this.readPlatformService.getTellers(officeId);
+
+        return this.jsonSerializer.serialize(foundTellers);
+    }
+
+    @Path("{tellerId}")
+    @GET
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String findTeller(@PathParam("tellerId") final Long tellerId) {
+        final TellerData teller = this.readPlatformService.findTeller(tellerId);
+
+        return this.jsonSerializer.serialize(teller);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String createTeller(final String tellerData) {
+        final CommandWrapper request = new CommandWrapperBuilder().createTeller().withJson(tellerData).build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(request);
+
+        return this.jsonSerializer.serialize(result);
+    }
+
+    @Path("{tellerId}")
+    @PUT
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String updateTeller(@PathParam("tellerId") final Long tellerId, final String tellerData) {
+        final CommandWrapper request = new CommandWrapperBuilder().updateTeller(tellerId).withJson(tellerData).build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(request);
+
+        return this.jsonSerializer.serialize(result);
+    }
+
+    @Path("{tellerId}")
+    @DELETE
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String deleteTeller(@PathParam("tellerId") final Long tellerId) {
+        final CommandWrapper request = new CommandWrapperBuilder().deleteTeller(tellerId).build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(request);
+
+        return this.jsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{tellerId}/cashiers")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getCashierData(@PathParam("tellerId") final Long tellerId, @QueryParam("fromdate") final String fromDateStr,
+            @QueryParam("todate") final String toDateStr) {
+        final DateTimeFormatter dateFormatter = ISODateTimeFormat.basicDate();
+
+        final Date fromDate = (fromDateStr != null ? dateFormatter.parseDateTime(fromDateStr).toDate() : new Date());
+        final Date toDate = (toDateStr != null ? dateFormatter.parseDateTime(toDateStr).toDate() : new Date());
+
+        final TellerData teller = this.readPlatformService.findTeller(tellerId);
+        final Collection<CashierData> cashiers = this.readPlatformService.getCashiersForTeller(tellerId, fromDate, toDate);
+
+        CashiersForTeller cashiersForTeller = new CashiersForTeller();
+        cashiersForTeller.cashiers = cashiers;
+        cashiersForTeller.tellerId = tellerId;
+        cashiersForTeller.tellerName = teller.getName();
+        cashiersForTeller.officeId = teller.getOfficeId();
+        cashiersForTeller.officeName = teller.getOfficeName();
+
+        return this.jsonSerializer.serialize(cashiersForTeller);
+    }
+
+    @GET
+    @Path("{tellerId}/cashiers/{cashierId}")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String findCashierData(@PathParam("tellerId") final Long tellerId, @PathParam("cashierId") final Long cashierId) {
+        final CashierData cashier = this.readPlatformService.findCashier(cashierId);
+
+        return this.jsonSerializer.serialize(cashier);
+    }
+
+    @GET
+    @Path("{tellerId}/cashiers/template")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getCashierTemplate(@PathParam("tellerId") final Long tellerId) {
+
+        final TellerData teller = this.readPlatformService.findTeller(tellerId);
+        Long officeId = teller.getOfficeId();
+
+        final CashierData cashier = this.readPlatformService.retrieveCashierTemplate(officeId, tellerId, true);
+
+        return this.jsonSerializer.serialize(cashier);
+    }
+
+    @POST
+    @Path("{tellerId}/cashiers")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String createCashier(@PathParam("tellerId") final Long tellerId, final String cashierData) {
+        final CommandWrapper request = new CommandWrapperBuilder().allocateTeller(tellerId).withJson(cashierData).build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(request);
+
+        return this.jsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{tellerId}/cashiers/{cashierId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String updateCashier(@PathParam("tellerId") final Long tellerId, @PathParam("cashierId") final Long cashierId,
+            final String cashierDate) {
+        final CommandWrapper request = new CommandWrapperBuilder().updateAllocationTeller(tellerId, cashierId).withJson(cashierDate)
+                .build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(request);
+
+        return this.jsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{tellerId}/cashiers/{cashierId}")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String deleteCashier(@PathParam("tellerId") final Long tellerId, @PathParam("cashierId") final Long cashierId) {
+        final CommandWrapper request = new CommandWrapperBuilder().deleteAllocationTeller(tellerId, cashierId).build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(request);
+
+        return this.jsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{tellerId}/cashiers/{cashierId}/allocate")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String allocateCashToCashier(@PathParam("tellerId") final Long tellerId, @PathParam("cashierId") final Long cashierId,
+            final String cashierTxnData) {
+        final CommandWrapper request = new CommandWrapperBuilder().allocateCashToCashier(tellerId, cashierId).withJson(cashierTxnData)
+                .build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(request);
+
+        return this.jsonSerializer.serialize(result);
+
+    }
+
+    @POST
+    @Path("{tellerId}/cashiers/{cashierId}/settle")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String settleCashFromCashier(@PathParam("tellerId") final Long tellerId, @PathParam("cashierId") final Long cashierId,
+            final String cashierTxnData) {
+        final CommandWrapper request = new CommandWrapperBuilder().settleCashFromCashier(tellerId, cashierId).withJson(cashierTxnData)
+                .build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(request);
+
+        return this.jsonSerializer.serialize(result);
+
+    }
+
+    @GET
+    @Path("{tellerId}/cashiers/{cashierId}/transactions")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getTransactionsForCashier(@PathParam("tellerId") final Long tellerId, @PathParam("cashierId") final Long cashierId,
+            @QueryParam("currencyCode") final String currencyCode) {
+        final TellerData teller = this.readPlatformService.findTeller(tellerId);
+        final CashierData cashier = this.readPlatformService.findCashier(cashierId);
+
+        final Date fromDate = null;
+        final Date toDate = null;
+
+        final Collection<CashierTransactionData> cashierTxns = this.readPlatformService.retrieveCashierTransactions(cashierId, false,
+                fromDate, toDate, currencyCode);
+
+        return this.jsonSerializer.serialize(cashierTxns);
+    }
+
+    @GET
+    @Path("{tellerId}/cashiers/{cashierId}/summaryandtransactions")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getTransactionsWtihSummaryForCashier(@PathParam("tellerId") final Long tellerId,
+            @PathParam("cashierId") final Long cashierId, @QueryParam("currencyCode") final String currencyCode) {
+        final TellerData teller = this.readPlatformService.findTeller(tellerId);
+        final CashierData cashier = this.readPlatformService.findCashier(cashierId);
+
+        final Date fromDate = null;
+        final Date toDate = null;
+
+        final CashierTransactionsWithSummaryData cashierTxnWithSummary = this.readPlatformService
+                .retrieveCashierTransactionsWithSummary(cashierId, false, fromDate, toDate, currencyCode);
+
+        return this.jsonSerializer.serialize(cashierTxnWithSummary);
+    }
+
+    @GET
+    @Path("{tellerId}/cashiers/{cashierId}/transactions/template")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getCashierTxnTemplate(@PathParam("tellerId") final Long tellerId, @PathParam("cashierId") final Long cashierId) {
+
+        final CashierTransactionData cashierTxnTemplate = this.readPlatformService.retrieveCashierTxnTemplate(cashierId);
+
+        return this.jsonSerializer.serialize(cashierTxnTemplate);
+    }
+
+    @GET
+    @Path("{tellerId}/transactions")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getTransactionData(@PathParam("tellerId") final Long tellerId, @QueryParam("dateRange") final String dateRange) {
+        final DateRange dateRangeHolder = DateRange.fromString(dateRange);
+
+        final Collection<TellerTransactionData> transactions = this.readPlatformService.fetchTellerTransactionsByTellerId(tellerId,
+                dateRangeHolder.getStartDate(), dateRangeHolder.getEndDate());
+
+        return this.jsonSerializer.serialize(transactions);
+    }
+
+    @GET
+    @Path("{tellerId}/transactions/{transactionId}")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String findTransactionData(@PathParam("tellerId") final Long tellerid, @PathParam("transactionId") final Long transactionId) {
+        final TellerTransactionData transaction = this.readPlatformService.findTellerTransaction(transactionId);
+
+        return this.jsonSerializer.serialize(transaction);
+    }
+
+    @GET
+    @Path("{tellerId}/journals")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getJournalData(@PathParam("tellerId") final Long tellerId, @QueryParam("cashierId") final Long cashierDate,
+            @QueryParam("dateRange") final String dateRange) {
+        final DateRange dateRangeHolder = DateRange.fromString(dateRange);
+
+        final Collection<TellerJournalData> journals = this.readPlatformService.fetchTellerJournals(tellerId, cashierDate,
+                dateRangeHolder.getStartDate(), dateRangeHolder.getEndDate());
+
+        return this.jsonSerializer.serialize(journals);
+    }
+
+    private class CashiersForTeller {
+
+        public Long tellerId;
+        public String tellerName;
+        public Long officeId;
+        public String officeName;
+        public Collection<CashierData> cashiers;
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerJournalApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerJournalApiResource.java
new file mode 100644
index 0000000..744fbf1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerJournalApiResource.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.organisation.teller.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.teller.data.TellerData;
+import org.apache.fineract.organisation.teller.data.TellerJournalData;
+import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformService;
+import org.apache.fineract.organisation.teller.util.DateRange;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("cashiersjournal")
+@Component
+@Scope("singleton")
+public class TellerJournalApiResource {
+
+    private final PlatformSecurityContext securityContext;
+    private final DefaultToApiJsonSerializer<TellerData> jsonSerializer;
+    private final TellerManagementReadPlatformService readPlatformService;
+
+    @Autowired
+    public TellerJournalApiResource(final PlatformSecurityContext securityContext,
+            final DefaultToApiJsonSerializer<TellerData> jsonSerializer, final TellerManagementReadPlatformService readPlatformService) {
+        super();
+        this.securityContext = securityContext;
+        this.jsonSerializer = jsonSerializer;
+        this.readPlatformService = readPlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getJournalData(@QueryParam("officeId") final Long officeId, @QueryParam("tellerId") final Long tellerId,
+            @QueryParam("cashierId") final Long cashierId, @QueryParam("dateRange") final String dateRange) {
+        final DateRange dateRangeHolder = DateRange.fromString(dateRange);
+
+        final Collection<TellerJournalData> journals = this.readPlatformService.getJournals(officeId, tellerId, cashierId,
+                dateRangeHolder.getStartDate(), dateRangeHolder.getEndDate());
+
+        return this.jsonSerializer.serialize(journals);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierData.java
new file mode 100644
index 0000000..f6e61bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierData.java
@@ -0,0 +1,238 @@
+/**
+ * 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.teller.data;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.joda.time.LocalDate;
+
+/**
+ * Represents a cashier, providing access to the cashier's office, staff
+ * information, teller, and more.
+ *
+ * @author Markus Geiss
+<<<<<<< HEAD
+ * @since 2.0.0
+ * @see org.apache.fineract.organisation.teller.domain.Cashier
+ * @since 2.0.0
+ */
+public final class CashierData implements Serializable {
+
+    private final Long id;
+    private final Long tellerId;
+    private final Long officeId;
+    private final Long staffId;
+    private final String description;
+    private final Date startDate;
+    private final Date endDate;
+    private final Boolean isFullDay;
+    private final String startTime;
+    private final String endTime;
+    
+    // Template fields
+    private final String officeName;
+    private final String tellerName;
+    private final String staffName;
+    private final Collection<StaffData> staffOptions;
+
+    /*
+     * Creates a new cashier.
+     */
+    private CashierData(final Long id, final Long officeId, String officeName, 
+    		final Long staffId, final String staffName, final Long tellerId, final String tellerName,
+    		final String description,
+    		final Date startDate, final Date endDate, final Boolean isFullDay,
+            final String startTime, final String endTime, Collection<StaffData> staffOptions) {
+        this.id = id;
+        this.officeId = officeId;
+        this.staffId = staffId;
+        this.tellerId = tellerId;
+        this.description = description;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.isFullDay = isFullDay;
+        this.startTime = startTime;
+        this.endTime = endTime;
+        
+        this.officeName = officeName;
+        this.tellerName = tellerName;
+        this.staffName = staffName;
+        this.staffOptions = staffOptions;
+        
+    }
+
+    /**
+     * Creates a new cashier.
+     * <p/>
+     * <p>The valid from/to dates may be used to define a time period in which
+     * the cashier is assignable to a teller.</p>
+     * <p/>
+     * <p>The start/end times may be used to define a time period in which
+     * the cashier works part time.</p>
+     *
+     * @param id          the primary key of this cashier
+     * @param officeId    the primary key of the related office
+     * @param staffId     the primary key of the related staff
+     * @param tellerId    the primary key of the related teller
+     * @param description the description of this cashier
+     * @param validFrom   the valid from date of this cashier
+     * @param validTo     the valid to date of this cashier
+     * @param partTime    the part time flag of this cashier
+     * @param startTime   the start time of this cashier
+     * @param endTime     the end time of this cashier
+     * @return the cashier
+     */
+    public static CashierData instance(final Long id, final Long officeId, String officeName, 
+    		final Long staffId, final String staffName, final Long tellerId, final String tellerName,
+    		final String description, final Date startDate, final Date endDate,
+    		final Boolean isFullDay, final String startTime, final String endTime) {
+        return new CashierData(id, officeId, officeName, staffId, staffName, tellerId, tellerName, 
+        		description, startDate, endDate, isFullDay, startTime, endTime, null);
+    }
+    
+    /*
+     * Creates a new cashier.
+     */
+    public static CashierData template (final Long officeId, final String officeName, 
+    		final Long tellerId, final String tellerName, final Collection<StaffData> staffOptions) {
+        return new CashierData(null, officeId, officeName, null, null, tellerId, tellerName, 
+        		null, null, null, null, null, null, staffOptions);
+    }
+
+    /**
+     * Returns the primary key of this cashier.
+     *
+     * @return the primary key of this cashier
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * Returns the primary key of this cashiers related office.
+     *
+     * @return the primary key of this cashiers related office
+     */
+    public Long getOfficeId() {
+        return officeId;
+    }
+
+    /**
+     * Returns the primary key of this cashiers related staff.
+     *
+     * @return the primary key of this cashiers related staff
+     */
+    public Long getStaffId() {
+        return staffId;
+    }
+
+    /**
+     * Returns the primary key of this cashiers related teller.
+     *
+     * @return the primary key of this cashiers related teller
+     */
+    public Long getTellerId() {
+        return tellerId;
+    }
+
+    /**
+     * Returns the description of this cashier.
+     *
+     * @return the description of this cashier
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Returns the valid from date of this cashier.
+     * <p/>
+     * <p>The valid from/to dates may be used to define a time period in which
+     * the cashier is assignable to a teller.</p>
+     *
+     * @return the valid from date of this cashier
+     */
+    public Date getStartDate() {
+        return startDate;
+    }
+
+    /**
+     * Returns the valid to date of this cashier.
+     * <p/>
+     * <p>The valid from/to dates may be used to define a time period in which
+     * the cashier is assignable to a teller.</p>
+     *
+     * @return the valid to date of this cashier
+     */
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    /**
+     * Returns whether this cashier works part time or not.
+     *
+     * @return {@code true} if this cashier works part time; {@code false} otherwise
+     */
+    public Boolean isFullDay() {
+        return isFullDay;
+    }
+
+    /**
+     * Returns the start time of this cashier.
+     * <p/>
+     * <p>The start/end times may be used to define a time period in which
+     * the cashier works part time.</p>
+     *
+     * @return the start time of this cashier
+     */
+    public String getStartTime() {
+        return startTime;
+    }
+
+    /**
+     * Returns the end time of this cashier.
+     * <p/>
+     * <p>The start/end times may be used to define a time period in which
+     * the cashier works part time.</p>
+     *
+     * @return the start time of this cashier
+     */
+    public String getEndTime() {
+        return endTime;
+    }
+
+	public String getOfficeName() {
+		return officeName;
+	}
+
+	public String getTellerName() {
+		return tellerName;
+	}
+	
+	public String getStaffName() {
+		return staffName;
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionData.java
new file mode 100644
index 0000000..9342af1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionData.java
@@ -0,0 +1,173 @@
+/**
+ * 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.teller.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.teller.domain.CashierTxnType;
+
+public final class CashierTransactionData implements Serializable {
+
+    private final Long id;
+    private final Long cashierId;
+    private final CashierTxnType txnType;
+    private final BigDecimal txnAmount;
+    private final Date txnDate;
+    private final Long entityId;
+    private final String entityType;
+    private final String txnNote;
+    private final Date createdDate;
+    
+    // Template fields
+    private final Long officeId;
+    private final String officeName;
+    private final Long tellerId;
+    private final String tellerName;
+    private final String cashierName;
+        
+    private final CashierData cashierData;
+    private final Date startDate;
+    private final Date endDate;
+    
+    private final Collection<CurrencyData> currencyOptions;
+
+    /*
+     * Creates a new cashier.
+     */
+    private CashierTransactionData(final Long id, final Long cashierId, CashierTxnType txnType, 
+    		final BigDecimal txnAmount, final Date txnDate, String txnNote, 
+    		String entityType, Long entityId, Date createdDate, 
+    		Long officeId, String officeName, Long tellerId, String tellerName, String cashierName,
+    		CashierData cashierData, Date startDate, Date endDate, final Collection<CurrencyData> currencyOptions) {
+        this.id = id;
+        this.cashierId = cashierId;
+        this.txnType = txnType;
+        this.txnAmount = txnAmount;
+        this.txnDate = txnDate;
+        this.txnNote = txnNote;
+        this.entityType = entityType;
+        this.entityId = entityId;
+        this.createdDate = createdDate;
+        
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.tellerId = tellerId;
+        this.tellerName = tellerName;
+        this.cashierName = cashierName;
+        this.cashierData = cashierData;
+        
+        this.startDate = startDate;
+        this.endDate = endDate;
+        
+        this.currencyOptions = currencyOptions;
+    }
+
+    public static CashierTransactionData instance(final Long id, final Long cashierId, CashierTxnType txnType,
+    		final BigDecimal txnAmount, final Date txnDate, final String txnNote,
+    		final String entityType, final Long entityId, final Date createdDate,
+    		final Long officeId, final String officeName, final Long tellerId,
+    		final String tellerName, final String cashierName, final CashierData cashierData,
+    		Date startDate, Date endDate) {
+        return new CashierTransactionData(id, cashierId, txnType, txnAmount, txnDate, txnNote, entityType, 
+        		entityId, createdDate, officeId, officeName, tellerId,
+        		tellerName, cashierName, cashierData, startDate, endDate, null);
+    }
+    
+    public static CashierTransactionData template (final Long cashierId,  
+    		final Long tellerId, final String tellerName,
+    		final Long officeId, final String officeName, final String cashierName,
+    		final CashierData cashierData, Date startDate, Date endDate, final Collection<CurrencyData> currencyOptions) {
+        return new CashierTransactionData(null, cashierId, null, null, null, null, null, 
+        		null, null, officeId, officeName, tellerId, tellerName, cashierName, cashierData,
+        		startDate, endDate, currencyOptions);
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public Long getCashierId() {
+        return cashierId;
+    }
+    
+    public CashierTxnType getTxnType() {
+    	return txnType;
+    }
+    
+    public BigDecimal getTxnAmount() {
+    	return txnAmount;
+    }
+    
+    public Date getTxnDate() {
+    	return txnDate;
+    }
+    
+    public String getTxnNote() {
+    	return txnNote;
+    }
+    
+    public String getEntityType() {
+    	return entityType;
+    }
+    
+    public Long getEntityId() {
+    	return entityId;
+    }
+
+    public Date getCreatedDate() {
+    	return createdDate;
+    }
+
+    public Long getOfficeId() {
+        return officeId;
+    }
+    
+    public String getOfficeName() {
+    	return officeName;
+    }
+
+    public Long getTellerId() {
+        return tellerId;
+    }
+    
+    public String getTellerName() {
+    	return tellerName;
+    }
+
+    public String getCashierName() {
+    	return cashierName;
+    }
+    
+    public Date getStartDate() {
+    	return startDate;
+    }
+    
+    public Date getEndDate() {
+    	return endDate;
+    }
+
+    public CashierData getCashierData() {
+    	return cashierData;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionTypeTotalsData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionTypeTotalsData.java
new file mode 100644
index 0000000..a781bd5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionTypeTotalsData.java
@@ -0,0 +1,52 @@
+/**
+ * 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.teller.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public final class CashierTransactionTypeTotalsData implements Serializable {
+	private final Integer cashierTxnType;
+	private final BigDecimal cashTotal;
+
+    private CashierTransactionTypeTotalsData (
+    		final Integer cashierTxnType, 
+    		final BigDecimal cashTotal
+    		) {
+    	this.cashierTxnType = cashierTxnType;
+    	this.cashTotal = cashTotal;
+    }
+
+    public static CashierTransactionTypeTotalsData instance(
+    		final Integer cashierTxnType, 
+    		final BigDecimal cashTotal
+    		) {
+           return new CashierTransactionTypeTotalsData(
+        	cashierTxnType, cashTotal);
+    }
+
+    public Integer getCashierTxnType() {
+		return cashierTxnType;
+	}
+    
+    public BigDecimal getCashTotal() {
+		return cashTotal;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionsWithSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionsWithSummaryData.java
new file mode 100644
index 0000000..fcdc23d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionsWithSummaryData.java
@@ -0,0 +1,135 @@
+/**
+ * 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.teller.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Collection;
+
+public final class CashierTransactionsWithSummaryData implements Serializable {
+	
+	private final BigDecimal sumCashAllocation;
+	private final BigDecimal sumInwardCash;
+	private final BigDecimal sumOutwardCash;
+	private final BigDecimal sumCashSettlement;
+	private final BigDecimal netCash;
+	private final String officeName;
+	private final long tellerId;
+	private final String tellerName;
+	private final long cashierId;
+	private final String cashierName;
+
+    private final Collection<CashierTransactionData> cashierTransactions;
+
+    private CashierTransactionsWithSummaryData(
+    		final Collection<CashierTransactionData> cashierTransactions, 
+    		final BigDecimal sumCashAllocation,
+    		final BigDecimal sumInwardCash,
+    		final BigDecimal sumOutwardCash,
+    		final BigDecimal sumCashSettlement,
+    		final BigDecimal netCash,
+    		final String officeName,
+    		final Long tellerId,
+    		final String tellerName,
+    		final Long cashierId,
+    		final String cashierName
+    		) {
+    	this.cashierTransactions = cashierTransactions;
+    	this.sumCashAllocation = sumCashAllocation;
+    	this.sumInwardCash = sumInwardCash;
+    	this.sumOutwardCash = sumOutwardCash;
+    	this.sumCashSettlement = sumCashSettlement;
+    	this.netCash = netCash;
+    	this.officeName = officeName;
+    	this.tellerId = tellerId;
+    	this.tellerName = tellerName;
+    	this.cashierId = cashierId;
+    	this.cashierName = cashierName;
+    }
+
+    public static CashierTransactionsWithSummaryData instance(
+    		final Collection<CashierTransactionData> cashierTransactions, 
+    		final BigDecimal sumCashAllocation,
+    		final BigDecimal sumInwardCash,
+    		final BigDecimal sumOutwardCash,
+    		final BigDecimal sumCashSettlement,
+    		final String officeName,
+    		final Long tellerId,
+    		final String tellerName,
+    		final Long cashierId,
+    		final String cashierName
+    		) {
+    	
+    	final BigDecimal netCash = 
+    			sumCashAllocation.add(sumInwardCash).
+    				subtract(sumOutwardCash).
+    				subtract(sumCashSettlement); 
+        return new CashierTransactionsWithSummaryData(
+        		cashierTransactions, 
+        		sumCashAllocation,
+        		sumInwardCash,
+        		sumOutwardCash,
+        		sumCashSettlement, netCash,
+        		officeName, tellerId, tellerName, cashierId, cashierName);
+    }
+
+	public BigDecimal getSumCashAllocation() {
+		return sumCashAllocation;
+	}
+
+	public BigDecimal getSumInwardCash() {
+		return sumInwardCash;
+	}
+
+	public BigDecimal getSumOutwardCash() {
+		return sumOutwardCash;
+	}
+
+	public BigDecimal getSumCashSettlement() {
+		return sumCashSettlement;
+	}
+
+	public BigDecimal getNetCash() {
+		return netCash;
+	}
+	
+	public String getOfficeName() {
+		return officeName;
+	}
+	
+	public Long getTellerId() {
+		return tellerId;
+	}
+	
+	public String getTellerName() {
+		return tellerName;
+	}
+
+	public Long getCashierId() {
+		return cashierId;
+	}
+	
+	public String getCashierName() {
+		return cashierName;
+	}
+	public Collection<CashierTransactionData> getCashierTransactions() {
+		return cashierTransactions;
+	}
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/TellerData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/TellerData.java
new file mode 100644
index 0000000..e86f14f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/TellerData.java
@@ -0,0 +1,160 @@
+/**
+ * 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.teller.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.teller.domain.TellerStatus;
+import org.joda.time.LocalDate;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * {@code TellerData} represents an immutable data object for teller data.
+ *
+ * @version 1.0
+<<<<<<< HEAD
+ * @since 2.0.0
+ * @see java.io.Serializable
+ * @since 2.0.0
+ */
+public final class TellerData implements Serializable {
+
+    private final Long id;
+    private final Long officeId;
+    private final Long debitAccountId;
+    private final Long creditAccountId;
+    private final String name;
+    
+    private final String description;
+    private final LocalDate startDate;
+    private final LocalDate endDate;
+    private final TellerStatus status;
+    private final Boolean hasTransactions;
+    private final Boolean hasMappedCashiers;
+    
+    private String officeName;
+    
+    private final Collection<OfficeData> officeOptions;
+    private final Collection<StaffData> staffOptions;
+
+    /*
+     * Sole private CTOR to create a new instance.
+     */
+    private TellerData(final Long id, final Long officeId, final Long debitAccountId, final Long creditAccountId,
+                       final String name, final String description, final LocalDate startDate, final LocalDate endDate,
+                       final TellerStatus status, final Boolean hasTransactions, final Boolean hasMappedCashiers) {
+        super();
+        this.id = id;
+        this.officeId = officeId;
+        this.debitAccountId = debitAccountId;
+        this.creditAccountId = creditAccountId;
+        this.name = name;
+        this.description = description;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.status = status;
+        this.hasTransactions = hasTransactions;
+        this.hasMappedCashiers = hasMappedCashiers;
+        this.officeOptions = null;
+        this.staffOptions = null;
+    }
+
+    /**
+     * Creates a new teller data object.
+     *
+     * @param id                - id of the teller
+     * @param officeId          - id of the related office
+     * @param debitAccountId    - id of the debit account to use
+     * @param creditAccountId   - id of the credit account to use
+     * @param name              - name of the teller
+     * @param description       - description of the teller
+     * @param startDate         - date when the teller becomes available
+     * @param endDate           - date when the teller becomes unavailable
+     * @param status            - current state of the teller, eg. active, inactive, pending
+     * @param hasTransactions   - indicates that this teller already is used to perform postings
+     * @param hasMappedCashiers - indicates that the teller already has @code Cashier}s assigned to it
+     * @return the new created {@code TellerData}
+     */
+    public static TellerData instance(final Long id, final Long officeId, final Long debitAccountId,
+                                      final Long creditAccountId, final String name, final String description,
+                                      final LocalDate startDate, final LocalDate endDate, final TellerStatus status,
+                                      final String officeName,
+                                      final Boolean hasTransactions, final Boolean hasMappedCashiers) {
+        TellerData tellerData = new TellerData(id, officeId, debitAccountId, creditAccountId, name, description, startDate, endDate,
+                status, hasTransactions, hasMappedCashiers);
+        tellerData.officeName = officeName;
+        return tellerData;
+    }
+    
+    public static TellerData lookup(final Long id, final String name) {
+        return new TellerData(id, null, null, null, name, null, null, null, null, null, null);
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public Long getDebitAccountId() {
+        return this.debitAccountId;
+    }
+
+    public Long getCreditAccountId() {
+        return this.creditAccountId;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+    
+    public String getOfficeName() {
+        return this.officeName;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public LocalDate getStartDate() {
+        return this.startDate;
+    }
+
+    public LocalDate getEndDate() {
+        return this.endDate;
+    }
+
+    public TellerStatus getStatus() {
+        return this.status;
+    }
+
+    public Boolean hasTransactions() {
+        return this.hasTransactions;
+    }
+
+    public Boolean hasMappedCashiers() {
+        return this.hasMappedCashiers;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/TellerJournalData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/TellerJournalData.java
new file mode 100644
index 0000000..6ae4d32
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/TellerJournalData.java
@@ -0,0 +1,112 @@
+/**
+ * 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.teller.data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * {@code TellerJournalData} represents an immutable journal data object.
+ *
+ * @version 1.0.0
+<<<<<<< HEAD
+ * @since 2.0.0
+ * @see java.io.Serializable
+ * @since 2.0.0
+ */
+public final class TellerJournalData implements Serializable {
+
+    private final Long officeId;
+    private final Long tellerId;
+    private final Date day;
+    private final Double openingBalance;
+    private final Double settledBalance;
+    private final Double closingBalance;
+    private final Double sumReceipts;
+    private final Double sumPayments;
+
+    /*
+     * Sole private CTOR to create a new instance.
+     */
+    private TellerJournalData(final Long officeId, final Long tellerId, final Date day, final Double openingBalance,
+                              final Double settledBalance, final Double closingBalance, final Double sumReceipts,
+                              final Double sumPayments) {
+        this.officeId = officeId;
+        this.tellerId = tellerId;
+        this.day = day;
+        this.openingBalance = openingBalance;
+        this.settledBalance = settledBalance;
+        this.closingBalance = closingBalance;
+        this.sumReceipts = sumReceipts;
+        this.sumPayments = sumPayments;
+    }
+
+    /**
+     * Create a new teller journal data object.
+     *
+     * @param officeId       - id of related office
+     * @param tellerId       - id of related teller
+     * @param day            - day of this journals data
+     * @param openingBalance - balance at the time of opening the teller
+     * @param settledBalance - balance at the time od settling the teller
+     * @param closingBalance - balance at the time of closing the teller
+     * @param sumReceipts    - sum of all posted receipts
+     * @param sumPayments    - sum of all posted payments
+     * @return the new created {@code TellerJournalData}
+     */
+    public static TellerJournalData instance(final Long officeId, final Long tellerId, final Date day,
+                                             final Double openingBalance, final Double settledBalance,
+                                             final Double closingBalance, final Double sumReceipts,
+                                             final Double sumPayments) {
+        return new TellerJournalData(officeId, tellerId, day, openingBalance, settledBalance, closingBalance, sumReceipts,
+                sumPayments);
+    }
+
+    public Long getOfficeId() {
+        return officeId;
+    }
+
+    public Long getTellerId() {
+        return tellerId;
+    }
+
+    public Date getDay() {
+        return day;
+    }
+
+    public Double getOpeningBalance() {
+        return openingBalance;
+    }
+
+    public Double getSettledBalance() {
+        return settledBalance;
+    }
+
+    public Double getClosingBalance() {
+        return closingBalance;
+    }
+
+    public Double getSumReceipts() {
+        return sumReceipts;
+    }
+
+    public Double getSumPayments() {
+        return sumPayments;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/TellerTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/TellerTransactionData.java
new file mode 100644
index 0000000..e260a6a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/TellerTransactionData.java
@@ -0,0 +1,112 @@
+/**
+ * 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.teller.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * {@code TellerTransactionData} represents an immutable data object for a transction.
+ *
+ * @version 1.0.0
+<<<<<<< HEAD
+ * @since 2.0.0
+ * @see java.io.Serializable
+ * @since 2.0.0
+ */
+public final class TellerTransactionData implements Serializable {
+
+    private final Long id;
+    private final Long officeId;
+    private final Long tellerId;
+    private final Long cashierId;
+    private final Long clientId;
+    private final EnumOptionData type;
+    private final Double amount;
+    private final Date postingDate;
+
+    /*
+     * Sole private CTOR to create a new instance
+     */
+    private TellerTransactionData(final Long id, final Long officeId, final Long tellerId, final Long cashierId,
+                                  final Long clientId, final EnumOptionData type, final Double amount,
+                                  final Date postingDate) {
+        this.id = id;
+        this.officeId = officeId;
+        this.tellerId = tellerId;
+        this.cashierId = cashierId;
+        this.clientId = clientId;
+        this.type = type;
+        this.amount = amount;
+        this.postingDate = postingDate;
+    }
+
+    /**
+     * Creates a new teller transaction data object.
+     *
+     * @param id          - id of the transaction
+     * @param officeId    - id of the related office
+     * @param tellerId    - id of the related teller
+     * @param cashierId   - id of the cashier
+     * @param clientId    - id of the client
+     * @param type        - type of transaction (eg receipt, payment, open, close, settle)
+     * @param amount      - amount of the transaction
+     * @param postingDate - posting date of the transaction
+     * @return the new created {@code TellerTransactionData}
+     */
+    public static TellerTransactionData instance(final Long id, final Long officeId, final Long tellerId, final Long cashierId,
+                                                 final Long clientId, final EnumOptionData type, final Double amount,
+                                                 final Date postingDate) {
+        return new TellerTransactionData(id, officeId, tellerId, cashierId, clientId, type, amount, postingDate);
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public Long getOfficeId() {
+        return officeId;
+    }
+
+    public Long getTellerId() {
+        return tellerId;
+    }
+
+    public Long getCashierId() {
+        return cashierId;
+    }
+
+    public Long getClientId() {
+        return clientId;
+    }
+
+    public EnumOptionData getType() {
+        return type;
+    }
+
+    public Double getAmount() {
+        return amount;
+    }
+
+    public Date getPostingDate() {
+        return postingDate;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/Cashier.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/Cashier.java
new file mode 100644
index 0000000..cb2c16d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/Cashier.java
@@ -0,0 +1,476 @@
+/**
+ * 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.teller.domain;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.*;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Provides the base model for a cashier. Represents a row in the
+ * &quot;m_cashiers&quot; database table, with each column mapped to a property
+ * of this class.
+ * 
+ * @author Markus Geiss
+ * @since 2.0.0
+ */
+@Entity
+@Table(name = "m_cashiers", uniqueConstraints = { @UniqueConstraint(name = "ux_cashiers_staff_teller", columnNames = { "staff_id",
+        "teller_id" }) })
+public class Cashier extends AbstractPersistable<Long> {
+
+    // ManyToOne(fetch = FetchType.LAZY)
+    // JoinColumn(name = "office_id", nullable = false)
+    @Transient
+    private Office office;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "staff_id", nullable = false)
+    private Staff staff;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "teller_id", nullable = false)
+    private Teller teller;
+
+    @Column(name = "description", nullable = true, length = 500)
+    private String description;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "start_date", nullable = false)
+    private Date startDate;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "end_date", nullable = false)
+    private Date endDate;
+
+    @Column(name = "full_day", nullable = true)
+    private Boolean isFullDay;
+
+    @Column(name = "start_time", nullable = true, length = 10)
+    private String startTime;
+
+    @Column(name = "end_time", nullable = true, length = 10)
+    private String endTime;
+
+    /**
+     * Creates a new cashier.
+     */
+    public Cashier() {
+        super();
+    }
+
+    public static Cashier fromJson(final Office cashierOffice, final Teller teller, final Staff staff, final String startTime,
+            final String endTime, final JsonCommand command) {
+        // final Long tellerId = teller.getId();
+        // final Long staffId = command.longValueOfParameterNamed("staffId");
+        final String description = command.stringValueOfParameterNamed("description");
+        final LocalDate startDate = command.localDateValueOfParameterNamed("startDate");
+        final LocalDate endDate = command.localDateValueOfParameterNamed("endDate");
+        final Boolean isFullDay = command.booleanObjectValueOfParameterNamed("isFullDay");
+        /*
+         * final String startTime =
+         * command.stringValueOfParameterNamed("startTime"); final String
+         * endTime = command.stringValueOfParameterNamed("endTime");
+         */
+
+        return new Cashier(cashierOffice, teller, staff, description, startDate, endDate, isFullDay, startTime, endTime);
+    }
+
+    public Cashier(Office office, Teller teller, Staff staff, String description, LocalDate startDate, LocalDate endDate,
+            Boolean isFullDay, String startTime, String endTime) {
+        this.office = office;
+        this.teller = teller;
+        this.staff = staff;
+        this.description = description;
+        this.startDate = startDate.toDate();
+        this.endDate = endDate.toDate();
+        this.isFullDay = isFullDay;
+        this.startTime = startTime;
+        this.endTime = endTime;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+
+        final String descriptionParamName = "description";
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = newValue;
+        }
+
+        final String startDateParamName = "startDate";
+        if (command.isChangeInLocalDateParameterNamed(startDateParamName, getStartLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName);
+            actualChanges.put(startDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName);
+            this.startDate = newValue.toDate();
+        }
+
+        final String endDateParamName = "endDate";
+        if (command.isChangeInLocalDateParameterNamed(endDateParamName, getEndLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(endDateParamName);
+            actualChanges.put(endDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(endDateParamName);
+            this.endDate = newValue.toDate();
+        }
+
+        final Boolean isFullDay = command.booleanObjectValueOfParameterNamed("isFullDay");
+
+        final String isFullDayParamName = "isFullDay";
+        if (command.isChangeInBooleanParameterNamed(isFullDayParamName, this.isFullDay)) {
+            final Boolean newValue = command.booleanObjectValueOfParameterNamed(isFullDayParamName);
+            actualChanges.put(isFullDayParamName, newValue);
+            /*
+             * this.startTime="00"; this.endTime="00";
+             */
+            this.isFullDay = newValue;
+        }
+
+        if (!isFullDay) {
+            String newStartHour = "";
+            String newStartMin = "";
+            String newEndHour = "";
+            String newEndMin = "";
+            final String hourStartTimeParamName = "hourStartTime";
+            final String minStartTimeParamName = "minStartTime";
+            final String hourEndTimeParamName = "hourEndTime";
+            final String minEndTimeParamName = "minEndTime";
+            if (command.isChangeInLongParameterNamed(hourStartTimeParamName, this.getHourFromStartTime())
+                    || command.isChangeInLongParameterNamed(minStartTimeParamName, this.getMinFromStartTime())) {
+                newStartHour = command.stringValueOfParameterNamed(hourStartTimeParamName);
+                if(newEndHour.equalsIgnoreCase("0")){
+                    newEndHour= newEndHour + "0";
+                }
+                actualChanges.put(hourStartTimeParamName, newStartHour);
+                newStartMin = command.stringValueOfParameterNamed(minStartTimeParamName);
+                if(newStartMin.equalsIgnoreCase("0")){
+                    newStartMin= newStartMin + "0";
+                }
+                actualChanges.put(minStartTimeParamName, newStartMin);
+                this.startTime = newStartHour + ":" + newStartMin;
+            }
+
+            if (command.isChangeInLongParameterNamed(hourEndTimeParamName, this.getHourFromEndTime())
+                    || command.isChangeInLongParameterNamed(minEndTimeParamName, this.getMinFromEndTime())) {
+                newEndHour = command.stringValueOfParameterNamed(hourEndTimeParamName);
+                if(newEndHour.equalsIgnoreCase("0")){
+                    newEndHour= newEndHour + "0";
+                }
+                actualChanges.put(hourEndTimeParamName, newEndHour);
+                newEndMin = command.stringValueOfParameterNamed(minEndTimeParamName);
+                if(newEndMin.equalsIgnoreCase("0")){
+                    newEndMin= newEndMin + "0";
+                }
+                actualChanges.put(minEndTimeParamName, newEndMin);
+                this.endTime = newEndHour + ":" + newEndMin;
+            }
+
+        }
+
+        return actualChanges;
+    }
+
+    /**
+     * Returns the office of this cashier.
+     * 
+     * @return the office of this cashier
+     * @see org.apache.fineract.organisation.office.domain.Office
+     */
+    public Office getOffice() {
+        return office;
+    }
+
+    public Long getHourFromStartTime() {
+        if (this.startTime != null && !this.startTime.equalsIgnoreCase("")) {
+            String[] extractHourFromStartTime = this.startTime.split(":");
+            Long hour = Long.parseLong(extractHourFromStartTime[1]);
+            return hour;
+        }
+        return null;
+    }
+
+    public Long getMinFromStartTime() {
+        if (this.startTime != null && !this.startTime.equalsIgnoreCase("")) {
+            String[] extractMinFromStartTime = this.startTime.split(":");
+            Long min = Long.parseLong(extractMinFromStartTime[1]);
+            return min;
+        }
+        return null;
+    }
+
+    public Long getHourFromEndTime() {
+        if (this.endTime != null && !this.endTime.equalsIgnoreCase("")) {
+            String[] extractHourFromEndTime = this.endTime.split(":");
+            Long hour = Long.parseLong(extractHourFromEndTime[0]);
+            return hour;
+        }
+        return null;
+    }
+
+    public Long getMinFromEndTime() {
+        if (this.endTime != null && !this.endTime.equalsIgnoreCase("")) {
+            String[] extractMinFromEndTime = this.endTime.split(":");
+            Long min = Long.parseLong(extractMinFromEndTime[1]);
+            return min;
+        }
+        return null;
+    }
+
+    /**
+     * Sets the office of this cashier.
+     * 
+     * @param office
+     *            the office of this cashier
+     * @see org.apache.fineract.organisation.office.domain.Office
+     */
+    public void setOffice(Office office) {
+        this.office = office;
+    }
+
+    /**
+     * Returns the staff of this cashier.
+     * 
+     * @return the staff of this cashier
+     * @see org.apache.fineract.organisation.staff.domain.Staff
+     */
+    public Staff getStaff() {
+        return staff;
+    }
+
+    /**
+     * Sets the staff of this cashier.
+     * 
+     * @param staff
+     *            the staff of this cashier
+     * @see org.apache.fineract.organisation.staff.domain.Staff
+     */
+    public void setStaff(Staff staff) {
+        this.staff = staff;
+    }
+
+    /**
+     * Returns the teller of this cashier.
+     * 
+     * @return the teller of this cashier
+     * @see org.apache.fineract.organisation.teller.domain.Teller
+     */
+    public Teller getTeller() {
+        return teller;
+    }
+
+    /**
+     * Sets the teller of this cashier.
+     * 
+     * @param teller
+     *            the teller of this cashier
+     * @see org.apache.fineract.organisation.teller.domain.Teller
+     */
+    public void setTeller(Teller teller) {
+        this.teller = teller;
+    }
+
+    /**
+     * Returns the description of this cashier. .
+     * 
+     * @return the description of this cashier or {@code null} if not present.
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Sets the description of this cashier.
+     * 
+     * @param description
+     *            the description of this cashier
+     */
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    /**
+     * Returns the valid from date of this cashier.
+     * <p/>
+     * <p>
+     * The valid from/to dates may be used to define a time period in which the
+     * cashier is assignable to a teller.
+     * </p>
+     * 
+     * @return the valid from date of this cashier
+     */
+    public Date getStartDate() {
+        return startDate;
+    }
+
+    public LocalDate getStartLocalDate() {
+        LocalDate startLocalDate = null;
+        if (this.startDate != null) {
+            startLocalDate = LocalDate.fromDateFields(this.startDate);
+        }
+        return startLocalDate;
+    }
+
+    /**
+     * Sets the valid from date of this cashier.
+     * <p/>
+     * <p>
+     * The valid from/to dates may be used to define a time period in which the
+     * cashier is assignable to a teller.
+     * </p>
+     * 
+     * @param validFrom
+     *            the valid from date of this cashier
+     */
+    public void setStartDate(Date startDate) {
+        this.startDate = startDate;
+    }
+
+    /**
+     * Returns the valid to date of this cashier.
+     * <p/>
+     * <p>
+     * The valid from/to dates may be used to define a time period in which the
+     * cashier is assignable to a teller.
+     * </p>
+     * 
+     * @return the valid to date of this cashier
+     */
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    public LocalDate getEndLocalDate() {
+        LocalDate endLocalDate = null;
+        if (this.endDate != null) {
+            endLocalDate = LocalDate.fromDateFields(this.endDate);
+        }
+        return endLocalDate;
+    }
+
+    /**
+     * Sets the valid to date of this cashier.
+     * <p/>
+     * <p>
+     * The valid from/to dates may be used to define a time period in which the
+     * cashier is assignable to a teller.
+     * </p>
+     * 
+     * @param validTo
+     *            the valid to date of this cashier
+     */
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
+    }
+
+    /**
+     * Returns whether this cashier works part time or not.
+     * 
+     * @return {@code true} if this cashier works part time; {@code false}
+     *         otherwise
+     */
+    public Boolean isFullDay() {
+        return isFullDay;
+    }
+
+    /**
+     * Sets the part time flag of this cashier.
+     * 
+     * @param partTime
+     *            the part time flag of this cashier
+     */
+    public void setFullDay(Boolean isFullDay) {
+        this.isFullDay = isFullDay;
+    }
+
+    /**
+     * Returns the start time of this cashier.
+     * <p/>
+     * <p>
+     * The start/end times may be used to define a time period in which the
+     * cashier works part time.
+     * </p>
+     * 
+     * @return the start time of this cashier
+     */
+    public String getStartTime() {
+        return startTime;
+    }
+
+    /**
+     * Set the start time of this cashier.
+     * <p/>
+     * <p>
+     * The start/end times may be used to define a time period in which the
+     * cashier works part time.
+     * </p>
+     * 
+     * @param startTime
+     *            the start time of this cashier
+     */
+    public void setStartTime(String startTime) {
+        this.startTime = startTime;
+    }
+
+    /**
+     * Returns the end time of this cashier.
+     * <p/>
+     * <p>
+     * The start/end times may be used to define a time period in which the
+     * cashier works part time.
+     * </p>
+     * 
+     * @return the end time of this cashier
+     */
+    public String getEndTime() {
+        return endTime;
+    }
+
+    /**
+     * Sets the end time of this cashier.
+     * <p/>
+     * <p>
+     * The start/end times may be used to define a time period in which the
+     * cashier works part time.
+     * </p>
+     * 
+     * @param endTime
+     *            the end time of this cashier
+     */
+    public void setEndTime(String endTime) {
+        this.endTime = endTime;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierRepository.java
new file mode 100644
index 0000000..dce7f1f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierRepository.java
@@ -0,0 +1,33 @@
+/**
+ * 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.teller.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+/**
+ * Provides the domain repository for accessing, adding, modifying or deleting cashiers.
+ *
+ * @author Markus Geiss
+ * @see org.apache.fineract.organisation.teller.domain.Cashier
+ * @since 2.0.0
+ */
+public interface CashierRepository extends JpaRepository<Cashier, Long>, JpaSpecificationExecutor<Cashier> {
+    // no added behavior
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierRepositoryWrapper.java
new file mode 100644
index 0000000..c0fa1ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierRepositoryWrapper.java
@@ -0,0 +1,39 @@
+/**
+ * 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.teller.domain;
+
+import org.apache.fineract.organisation.teller.exception.TellerNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CashierRepositoryWrapper {
+    private final CashierRepository repository;
+
+    @Autowired
+    public CashierRepositoryWrapper(final CashierRepository repository) {
+        this.repository = repository;
+    }
+
+    public Cashier findOneWithNotFoundDetection(final Long id) {
+        final Cashier cashier = this.repository.findOne(id);
+        if (cashier == null) { throw new TellerNotFoundException(id); }
+        return cashier;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierTransaction.java
new file mode 100644
index 0000000..28ff20c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierTransaction.java
@@ -0,0 +1,284 @@
+/**
+ * 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.teller.domain;
+
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.*;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Entity
+@Table(name = "m_cashier_transactions")
+public class CashierTransaction extends AbstractPersistable<Long> {
+
+	@Transient
+    private Office office;
+	
+	@Transient
+    private Teller teller;
+	
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "cashier_id", nullable = false)
+    private Cashier cashier;
+    
+    @Column(name = "txn_type", nullable = false)
+    private Integer txnType;
+    
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "txn_date", nullable = false)
+    private Date txnDate;
+
+    @Column(name = "txn_amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal txnAmount;
+    
+    @Column(name = "txn_note", nullable = true)
+    private String txnNote;
+    
+    @Column(name = "entity_type", nullable = false)
+    private String entityType;
+    
+    @Column(name = "entity_id", nullable = false)
+    private Long entityId;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "created_date", nullable = false)
+    private Date createdDate;
+    
+    @Column(name = "currency_code", nullable = true)
+    private String currencyCode;
+
+    /**
+     * Creates a new cashier.
+     */
+    public CashierTransaction() {
+        super();
+    }
+    
+    public static CashierTransaction fromJson(
+    		final Cashier cashier,
+    		final JsonCommand command) {
+        final Integer txnType = command.integerValueOfParameterNamed("txnType");
+        final BigDecimal txnAmount = command.bigDecimalValueOfParameterNamed("txnAmount");
+        final LocalDate txnDate = command.localDateValueOfParameterNamed("txnDate");
+        final String entityType = command.stringValueOfParameterNamed("entityType");
+        final String txnNote = command.stringValueOfParameterNamed("txnNote");
+        final Long entityId = command.longValueOfParameterNamed("entityId");
+        final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
+
+        // TODO: get client/loan/savings details
+        return new CashierTransaction (cashier, txnType, txnAmount, txnDate, 
+        		entityType, entityId, txnNote, currencyCode);
+        
+    }
+    
+    public CashierTransaction (Cashier cashier, Integer txnType, BigDecimal txnAmount, 
+    		LocalDate txnDate, String entityType, Long entityId, String txnNote, String currencyCode) {
+    	this.cashier = cashier;
+    	this.txnType = txnType;
+    	if (txnDate != null) {
+    		this.txnDate = txnDate.toDate();
+    	}
+    	this.txnAmount = txnAmount;
+    	this.entityType = entityType;
+    	this.entityId = entityId;
+    	this.txnNote = txnNote;
+    	this.createdDate = new Date(); 
+    	this.currencyCode = currencyCode;
+    }
+    
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();        
+      
+        final String txnTypeParamName = "txnType";
+        if (command.isChangeInIntegerParameterNamed(txnTypeParamName, this.txnType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(txnTypeParamName);
+            actualChanges.put(txnTypeParamName, newValue);
+            this.txnType = newValue;
+        }
+
+        final String txnDateParamName = "txnDate";
+        if (command.isChangeInLocalDateParameterNamed(txnDateParamName, getTxnLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(txnDateParamName);
+            actualChanges.put(txnDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(txnDateParamName);
+            this.txnDate = newValue.toDate();
+        }
+        
+        final String txnAmountParamName = "txnAmount";
+        if (command.isChangeInBigDecimalParameterNamed(txnAmountParamName, this.txnAmount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(txnAmountParamName);
+            actualChanges.put(txnAmountParamName, newValue);
+            this.txnAmount = newValue;
+        }
+        
+        final String txnNoteParamName = "txnNote";
+        if (command.isChangeInStringParameterNamed(txnNoteParamName, this.txnNote)) {
+            final String newValue = command.stringValueOfParameterNamed(txnNoteParamName);
+            actualChanges.put(txnNoteParamName, newValue);
+            this.txnNote = newValue;
+        }
+        
+        final String currencyCodeParamName = "currencyCode";
+        if (command.isChangeInStringParameterNamed(currencyCodeParamName, this.currencyCode)) {
+            final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName);
+            actualChanges.put(currencyCodeParamName, newValue);
+            this.currencyCode = newValue;
+        }
+                
+        return actualChanges;
+    }
+
+
+    /**
+     * Returns the office of this cashier transaction.
+     *
+     * @return the office of this cashier transaction
+     * @see org.apache.fineract.organisation.office.domain.Office
+     */
+    public Office getOffice() {
+        return office;
+    }
+
+    /**
+     * Sets the office of this cashier transaction.
+     *
+     * @param office the office of this cashier transaction
+     * @see org.apache.fineract.organisation.office.domain.Office
+     */
+    public void setOffice(Office office) {
+        this.office = office;
+    }
+
+    /**
+     * Returns the teller of this cashier transaction.
+     *
+     * @return the teller of this cashier transaction
+     * @see org.apache.fineract.organisation.teller.domain.Teller
+     */
+    public Teller getTeller() {
+        return teller;
+    }
+
+    /**
+     * Sets the teller of this cashier transaction.
+     *
+     * @param teller the teller of this cashier transaction
+     * @see org.apache.fineract.organisation.teller.domain.Teller
+     */
+    public void setTeller(Teller teller) {
+        this.teller = teller;
+    }
+
+    /**
+     * Returns the transaction type of this cashier transaction.
+     * .
+     *
+     * @return the transaction type of this cashier transaction or {@code null} if not present.
+     */
+    public Integer getTxnType() {
+        return txnType;
+    }
+
+    /**
+     * Sets the transaction type of this cashier transaction.
+     *
+     * @param description the transaction type of this cashier transaction
+     */
+    public void setTxnType(Integer txnType) {
+        this.txnType = txnType;
+    }
+
+    /**
+     * Returns the transaction date of this cashier transaction.
+     *
+     * @return the transaction date of this cashier transaction
+     */
+    public Date getTxnDate() {
+        return txnDate;
+    }
+    
+    public LocalDate getTxnLocalDate() {
+        LocalDate txnLocalDate = null;
+        if (this.txnDate != null) {
+            txnLocalDate = LocalDate.fromDateFields(this.txnDate);
+        }
+        return txnLocalDate;
+    }
+
+    /**
+     * Sets the transaction date of this cashier transaction.
+     *
+     * @param transaction date of this cashier transaction
+     */
+    public void setTxnDate(Date txnDate) {
+        this.txnDate = txnDate;
+    }
+
+    public String getEntityType() {
+        return entityType;
+    }
+
+    public void setEntityType(String entityType) {
+        this.entityType = entityType;
+    }
+
+    public Long getEntityId() {
+        return entityId;
+    }
+
+    public void setEntityId(Long entityId) {
+        this.entityId = entityId;
+    }
+
+    public String getTxnNote() {
+        return txnNote;
+    }
+    
+    public BigDecimal getTxnAmount() {
+        return txnAmount;
+    }
+
+    public void setTxnNote (String txnNote) {
+        this.txnNote = txnNote;
+    }
+    
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+    
+    public void setCurrencyCode(String currencyCode) {
+        this.currencyCode = currencyCode;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierTransactionRepository.java
new file mode 100644
index 0000000..23099c2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierTransactionRepository.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.organisation.teller.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface CashierTransactionRepository extends JpaRepository<CashierTransaction, Long>, JpaSpecificationExecutor<CashierTransaction> {
+    // no added behavior
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierTxnType.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierTxnType.java
new file mode 100644
index 0000000..8b1ddb7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/CashierTxnType.java
@@ -0,0 +1,66 @@
+/**
+ * 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.teller.domain;
+
+import java.util.HashMap;
+
+public class CashierTxnType {
+	
+	private Integer id;
+	private String value;
+	
+	public static CashierTxnType ALLOCATE 			= new CashierTxnType (101, "Allocate Cash");
+	public static CashierTxnType SETTLE 			= new CashierTxnType (102, "Settle Cash");
+	public static CashierTxnType INWARD_CASH_TXN 	= new CashierTxnType (103, "Cash In");
+	public static CashierTxnType OUTWARD_CASH_TXN 	= new CashierTxnType (104, "Cash Out");
+	
+	private CashierTxnType () {
+	}
+	
+	private CashierTxnType (Integer id, String value) {
+		this.id = id;
+		this.value = value;
+	}
+		
+	public Integer getId () {
+		return id;
+	}
+	
+	public String getValue () {
+		return value;
+	}
+	
+	public static CashierTxnType getCashierTxnType (Integer id) {
+		CashierTxnType retVal = null;
+		
+		switch(id) {
+		case 101: 
+			retVal = ALLOCATE; break;
+		case 102: 
+			retVal = SETTLE; break;
+		case 103: 
+			retVal = INWARD_CASH_TXN; break;
+		case 104: 
+			retVal = OUTWARD_CASH_TXN; break;
+		default:
+			break;
+		}
+		return retVal;
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/Teller.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/Teller.java
new file mode 100644
index 0000000..ec3d0f3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/Teller.java
@@ -0,0 +1,297 @@
+/**
+ * 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.teller.domain;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_tellers", uniqueConstraints = {
+        @UniqueConstraint(name = "ux_tellers_name", columnNames = {"name"})
+})
+public class Teller extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "debit_account_id", nullable = false)
+    private GLAccount debitAccount;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "credit_account_id", nullable = false)
+    private GLAccount creditAccount;
+
+    @Column(name = "name", nullable = false, length = 100)
+    private String name;
+
+    @Column(name = "description", nullable = true, length = 500)
+    private String description;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "valid_from", nullable = false)
+    private Date startDate;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "valid_to", nullable = false)
+    private Date endDate;
+
+    @Column(name = "state", nullable = false)
+    private Integer status;
+
+    @OneToMany(mappedBy = "teller", fetch = FetchType.LAZY)
+    private Set<Cashier> cashiers;
+
+    @OneToMany(mappedBy = "teller", fetch = FetchType.LAZY)
+    private Set<TellerTransaction> tellerTransactions;
+
+    public Teller() {
+        super();
+    }
+    
+    private Teller (final Office staffOffice, final String name, final String description, final LocalDate startDate, 
+    		final LocalDate endDate, final TellerStatus status) {
+    	
+    	this.name = StringUtils.defaultIfEmpty(name, null);
+    	this.description = StringUtils.defaultIfEmpty(description, null);
+    	if (startDate != null) {
+    		this.startDate = startDate.toDateTimeAtStartOfDay().toDate();
+    	}
+    	if (endDate != null) {
+    		this.endDate = endDate.toDateTimeAtStartOfDay().toDate();
+    	}
+    	if (status != null) {
+    		this.status = status.getValue();
+    	}
+    	this.office = staffOffice;
+    	
+    	/*
+        if (StringUtils.isNotBlank(name)) {
+            this.name = name.trim();
+        } else {
+            this.name = null;
+        }
+        
+        if (StringUtils.isNotBlank(description)) {
+            this.description = description.trim();
+        } else {
+            this.description = null;
+        } */
+
+    }
+
+    
+    public static Teller fromJson(final Office tellerOffice, final JsonCommand command) {
+        final String name = command.stringValueOfParameterNamed("name");
+        final String description = command.stringValueOfParameterNamed("description");
+        final LocalDate startDate = command.localDateValueOfParameterNamed("startDate");
+        final LocalDate endDate = command.localDateValueOfParameterNamed("endDate");
+        final Integer tellerStatusInt = command.integerValueOfParameterNamed("status");
+        final TellerStatus status = TellerStatus.fromInt(tellerStatusInt);
+
+        return new Teller (tellerOffice, name, description, startDate, endDate, status);
+    }
+    
+    public Map<String, Object> update(Office tellerOffice, final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+        
+        final String officeIdParamName = "officeId";
+        if (command.isChangeInLongParameterNamed(officeIdParamName, this.officeId())) {
+            final long newValue = command.longValueOfParameterNamed(officeIdParamName);
+            actualChanges.put(officeIdParamName, newValue);
+            this.office = tellerOffice;
+        }
+
+        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 = "description";
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = newValue;
+        }
+
+        final String startDateParamName = "startDate";
+        if (command.isChangeInLocalDateParameterNamed(startDateParamName, getStartLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName);
+            actualChanges.put(startDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName);
+            this.startDate = newValue.toDate();
+        }
+
+        final String endDateParamName = "endDate";
+        if (command.isChangeInLocalDateParameterNamed(endDateParamName, getEndLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(endDateParamName);
+            actualChanges.put(endDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(endDateParamName);
+            this.endDate = newValue.toDate();
+        }
+        
+        final String statusParamName = "status";
+        if (command.isChangeInIntegerParameterNamed(statusParamName, getStatus())) {
+            final Integer valueAsInput = command.integerValueOfParameterNamed(statusParamName);
+            actualChanges.put(statusParamName, valueAsInput);
+            final Integer newValue = command.integerValueOfParameterNamed(statusParamName);
+            final TellerStatus status = TellerStatus.fromInt(newValue);
+            if (status != TellerStatus.INVALID) {
+            	this.status = status.getValue(); 
+            }
+        }
+
+        return actualChanges;
+    }
+
+
+
+    public Office getOffice() {
+        return office;
+    }
+
+    public void setOffice(Office office) {
+        this.office = office;
+    }
+
+    public GLAccount getDebitAccount() {
+        return debitAccount;
+    }
+
+    public void setDebitAccount(GLAccount debitAccount) {
+        this.debitAccount = debitAccount;
+    }
+
+    public GLAccount getCreditAccount() {
+        return creditAccount;
+    }
+
+    public void setCreditAccount(GLAccount creditAccount) {
+        this.creditAccount = creditAccount;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Date getStartDate() {
+        return startDate;
+    }
+    
+    public LocalDate getStartLocalDate() {
+        LocalDate startLocalDate = null;
+        if (this.startDate != null) {
+            startLocalDate = LocalDate.fromDateFields(this.startDate);
+        }
+        return startLocalDate;
+    }
+
+    public void setStartDate(Date startDate) {
+        this.startDate = startDate;
+    }
+
+    public Date getEndDate() {
+        return endDate;
+    }
+    
+    public LocalDate getEndLocalDate() {
+        LocalDate endLocalDate = null;
+        if (this.endDate != null) {
+            endLocalDate = LocalDate.fromDateFields(this.endDate);
+        }
+        return endLocalDate;
+    }
+
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+    
+    public Long officeId() {
+        return this.office.getId();
+    }
+
+    public Set<Cashier> getCashiers() {
+        return cashiers;
+    }
+
+    public void setCashiers(Set<Cashier> cashiers) {
+        this.cashiers = cashiers;
+    }
+
+    public Set<TellerTransaction> getTransactions() {
+        return tellerTransactions;
+    }
+
+    public void setTransactions(Set<TellerTransaction> tellerTransactions) {
+        this.tellerTransactions = tellerTransactions;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerJournal.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerJournal.java
new file mode 100644
index 0000000..2b815b9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerJournal.java
@@ -0,0 +1,22 @@
+/**
+ * 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.teller.domain;
+
+public class TellerJournal {
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerRepository.java
new file mode 100644
index 0000000..2d02432
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerRepository.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.organisation.teller.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import java.util.Collection;
+
+/**
+ * Provides the domain repository for accessing, adding, modifying or deleting tellers.
+ *
+ * @see org.apache.fineract.organisation.teller.domain.Teller
+ * @since 2.0.0
+ */
+public interface TellerRepository extends JpaRepository<Teller, Long>, JpaSpecificationExecutor<Teller> {
+    Collection<Teller> findTellerByOfficeId(Long officeId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerRepositoryWrapper.java
new file mode 100644
index 0000000..bb447bc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerRepositoryWrapper.java
@@ -0,0 +1,40 @@
+/**
+ * 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.teller.domain;
+
+import org.apache.fineract.organisation.teller.exception.TellerNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TellerRepositoryWrapper {
+
+    private final TellerRepository repository;
+
+    @Autowired
+    public TellerRepositoryWrapper(final TellerRepository repository) {
+        this.repository = repository;
+    }
+
+    public Teller findOneWithNotFoundDetection(final Long id) {
+        final Teller teller = this.repository.findOne(id);
+        if (teller == null) { throw new TellerNotFoundException(id); }
+        return teller;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerStatus.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerStatus.java
new file mode 100644
index 0000000..3381535
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerStatus.java
@@ -0,0 +1,89 @@
+/**
+ * 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.teller.domain;
+
+/**
+ * Enum representation of teller status states.
+ */
+
+public enum TellerStatus {
+	
+    INVALID(0, "tellerStatusType.invalid"),
+    PENDING(100, "tellerStatusType.pending"),
+    ACTIVE(300, "tellerStatusType.active"),
+    INACTIVE(400, "tellerStatusType.inactive"),
+    CLOSED(600, "tellerStatusType.closed");
+
+    private final Integer value;
+    private final String code;
+
+    public static TellerStatus fromInt(final Integer statusValue) {
+
+        TellerStatus status = TellerStatus.INVALID;
+        switch (statusValue) {
+            case 100:
+            	status = TellerStatus.PENDING;
+            break;
+            case 300:
+            	status = TellerStatus.ACTIVE;
+            break;
+            case 400:
+            	status = TellerStatus.INACTIVE;
+            break;
+            case 600:
+            	status = TellerStatus.CLOSED;
+            break;
+        }
+        return status;
+    }
+
+    private TellerStatus(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final TellerStatus state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isPending() {
+        return this.value.equals(TellerStatus.PENDING.getValue());
+    }
+
+    public boolean isActive() {
+        return this.value.equals(TellerStatus.ACTIVE.getValue());
+    }
+
+    public boolean isClosed() {
+        return this.value.equals(TellerStatus.CLOSED.getValue());
+    }
+
+    public boolean isInactive() {
+        return this.value.equals(TellerStatus.INACTIVE.getValue());
+    }
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerTransaction.java
new file mode 100644
index 0000000..1b4aea9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerTransaction.java
@@ -0,0 +1,117 @@
+/**
+ * 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.teller.domain;
+
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.*;
+import java.util.Date;
+
+@Entity
+@Table(name = "m_teller_transactions")
+public class TellerTransaction extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "teller_id", nullable = false)
+    private Teller teller;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "cashier_id", nullable = false)
+    private Cashier cashier;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "client_id", nullable = false)
+    private Client client;
+
+    @Column(name = "type", nullable = false)
+    private Integer type;
+
+    @Column(name = "amount", nullable = false)
+    private Double amount;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "posting_date", nullable = false)
+    private Date postingDate;
+
+    public TellerTransaction() {
+        super();
+    }
+
+    public Office getOffice() {
+        return office;
+    }
+
+    public void setOffice(Office office) {
+        this.office = office;
+    }
+
+    public Teller getTeller() {
+        return teller;
+    }
+
+    public void setTeller(Teller teller) {
+        this.teller = teller;
+    }
+
+    public Cashier getCashier() {
+        return cashier;
+    }
+
+    public void setCashier(Cashier cashier) {
+        this.cashier = cashier;
+    }
+
+    public Client getClient() {
+        return client;
+    }
+
+    public void setClient(Client client) {
+        this.client = client;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public Double getAmount() {
+        return amount;
+    }
+
+    public void setAmount(Double amount) {
+        this.amount = amount;
+    }
+
+    public Date getPostingDate() {
+        return postingDate;
+    }
+
+    public void setPostingDate(Date postingDate) {
+        this.postingDate = postingDate;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerTransactionRepository.java
new file mode 100644
index 0000000..3b4dce8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/domain/TellerTransactionRepository.java
@@ -0,0 +1,33 @@
+/**
+ * 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.teller.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+/**
+ * Provides the domain repository for accessing, adding, modifying or deleting teller transactions.
+ *
+ * @see org.apache.fineract.organisation.teller.domain.TellerTransaction
+ * @since 2.0.0
+ */
+public interface TellerTransactionRepository extends JpaRepository<TellerTransaction, Long>,
+        JpaSpecificationExecutor<TellerTransaction> {
+    // no added behavior
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/CashierExistForTellerException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/CashierExistForTellerException.java
new file mode 100644
index 0000000..aa11e32
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/CashierExistForTellerException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.teller.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+
+public class CashierExistForTellerException extends AbstractPlatformDomainRuleException{
+    
+    private static final String ERROR_MESSAGE_CODE = "error.msg.cashier.is.associated.with.this.teller";
+    private static final String DEFAULT_ERROR_MESSAGE = "Cannot delete teller, Cashier is associated for this teller ";
+
+    public CashierExistForTellerException(Long tellerId) {
+        super(ERROR_MESSAGE_CODE, DEFAULT_ERROR_MESSAGE, tellerId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/CashierNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/CashierNotFoundException.java
new file mode 100644
index 0000000..62068fa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/CashierNotFoundException.java
@@ -0,0 +1,42 @@
+/**
+ * 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.teller.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * Indicates that a cashier could not be found.
+ *
+ * @author Markus Geiss
+ * @since 2.0.0
+ */
+public class CashierNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    private static final String ERROR_MESSAGE_CODE = "error.msg.cashier.not.found";
+    private static final String DEFAULT_ERROR_MESSAGE = "Cashier with identifier {0,number,long} not found!";
+
+    /**
+     * Creates a new instance.
+     *
+     * @param cashierId the primary key of the cashier
+     */
+    public CashierNotFoundException(Long cashierId) {
+        super(ERROR_MESSAGE_CODE, DEFAULT_ERROR_MESSAGE, cashierId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/InvalidDateInputException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/InvalidDateInputException.java
new file mode 100644
index 0000000..36b9aca
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/InvalidDateInputException.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.organisation.teller.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+
+public class InvalidDateInputException extends AbstractPlatformResourceNotFoundException {
+
+    public InvalidDateInputException(final String startDate,final String endDate) {
+        super("error.msg.invalid.endDate", "EndDate " + endDate + " cannot be before StartDate "+startDate);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/TellerNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/TellerNotFoundException.java
new file mode 100644
index 0000000..47407ef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/exception/TellerNotFoundException.java
@@ -0,0 +1,42 @@
+/**
+ * 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.teller.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * Indicates that a teller could not be found.
+ *
+ * @author Markus Geiss
+ * @since 2.0.0
+ */
+public class TellerNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    private static final String ERROR_MESSAGE_CODE = "error.msg.teller.not.found";
+    private static final String DEFAULT_ERROR_MESSAGE = "Teller with identifier {0,number,long} not found!";
+
+    /**
+     * Creates a new instance.
+     *
+     * @param tellerId the primary key of the teller
+     */
+    public TellerNotFoundException(Long tellerId) {
+        super(ERROR_MESSAGE_CODE, DEFAULT_ERROR_MESSAGE, tellerId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/AllocateCashToCashierCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/AllocateCashToCashierCommandHandler.java
new file mode 100644
index 0000000..5b2e544
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/AllocateCashToCashierCommandHandler.java
@@ -0,0 +1,51 @@
+/**
+ * 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.teller.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.organisation.teller.service.TellerWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "TELLER", action = "ALLOCATECASHTOCASHIER")
+public class AllocateCashToCashierCommandHandler implements NewCommandSourceHandler {
+
+    private final TellerWritePlatformService writePlatformService;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param writePlatformService the {@code CashierWritePlatformService} used to access the backend
+     */
+    @Autowired
+    public AllocateCashToCashierCommandHandler(final TellerWritePlatformService writePlatformService) {
+        super();
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+    	
+        return this.writePlatformService.allocateCashToCashier(command.subentityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/AllocateCashierToTellerCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/AllocateCashierToTellerCommandHandler.java
new file mode 100644
index 0000000..131b641
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/AllocateCashierToTellerCommandHandler.java
@@ -0,0 +1,52 @@
+/**
+ * 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.teller.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.organisation.teller.service.TellerWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "TELLER", action = "ALLOCATECASHIER")
+public class AllocateCashierToTellerCommandHandler implements NewCommandSourceHandler {
+
+    private final TellerWritePlatformService writePlatformService;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param writePlatformService the {@code CashierWritePlatformService} used to access the backend
+     */
+    @Autowired
+    public AllocateCashierToTellerCommandHandler(final TellerWritePlatformService writePlatformService) {
+        super();
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+    	
+        return this.writePlatformService.allocateCashierToTeller(
+        				command.entityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/CreateTellerCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/CreateTellerCommandHandler.java
new file mode 100644
index 0000000..3f605b6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/CreateTellerCommandHandler.java
@@ -0,0 +1,57 @@
+/**
+ * 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.teller.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.organisation.teller.service.TellerWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * Handles a create teller command.
+ *
+ * @author Markus Geiss
+ * @see org.apache.fineract.organisation.teller.service.TellerWritePlatformService
+ * @since 2.0.0
+ */
+@Service
+@CommandType(entity = "TELLER", action = "CREATE")
+public class CreateTellerCommandHandler implements NewCommandSourceHandler {
+
+    private final TellerWritePlatformService writePlatformService;
+
+    /**
+     * Creates a new instance
+     *
+     * @param writePlatformService the {@code TellerWritePlatformService} used to access the backend
+     */
+    @Autowired
+    public CreateTellerCommandHandler(final TellerWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.createTeller(command);
+    }
+}
+
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/CreateTellerTransactionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/CreateTellerTransactionCommandHandler.java
new file mode 100644
index 0000000..1b02832
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/CreateTellerTransactionCommandHandler.java
@@ -0,0 +1,41 @@
+/**
+ * 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.teller.handler;
+
+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.organisation.teller.service.TellerTransactionWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class CreateTellerTransactionCommandHandler implements NewCommandSourceHandler {
+
+    private final TellerTransactionWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateTellerTransactionCommandHandler(final TellerTransactionWritePlatformService writePlatformService) {
+        super();
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.createTellerTransaction(command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/DeleteCashierAllocationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/DeleteCashierAllocationCommandHandler.java
new file mode 100644
index 0000000..1ada3cb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/DeleteCashierAllocationCommandHandler.java
@@ -0,0 +1,58 @@
+/**
+ * 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.teller.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.organisation.teller.service.TellerWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * Handles a delete cashier command.
+ *
+ * @author Markus Geiss
+ * @see org.apache.fineract.organisation.teller.service.CashierWritePlatformService
+ * @since 2.0.0
+ */
+@Service
+@CommandType(entity = "TELLER", action = "DELETECASHIERALLOCATION")
+public class DeleteCashierAllocationCommandHandler implements NewCommandSourceHandler {
+
+    private final TellerWritePlatformService writePlatformService;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param writePlatformService the {@code CashierWritePlatformService} used to access the backend
+     */
+    @Autowired
+    public DeleteCashierAllocationCommandHandler(final TellerWritePlatformService writePlatformService) {
+        super();
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteCashierAllocation(command.entityId(), 
+        		command.subentityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/DeleteTellerCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/DeleteTellerCommandHandler.java
new file mode 100644
index 0000000..92110e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/DeleteTellerCommandHandler.java
@@ -0,0 +1,57 @@
+/**
+ * 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.teller.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.organisation.teller.service.TellerWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * Handles a delete teller command.
+ *
+ * @author Markus Geiss
+ * @see org.apache.fineract.organisation.teller.service.TellerWritePlatformService
+ * @since 2.0.0
+ */
+@Service
+@CommandType(entity = "TELLER", action = "DELETE")
+public class DeleteTellerCommandHandler implements NewCommandSourceHandler {
+
+    private final TellerWritePlatformService writePlatformService;
+
+    /**
+     * Creates a new instance
+     *
+     * @param writePlatformService the {@code TellerWritePlatformService} used to access the backend
+     */
+    @Autowired
+    public DeleteTellerCommandHandler(final TellerWritePlatformService writePlatformService) {
+        super();
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteTeller(command.entityId());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/ModifyCashierCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/ModifyCashierCommandHandler.java
new file mode 100644
index 0000000..1c948b7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/ModifyCashierCommandHandler.java
@@ -0,0 +1,54 @@
+/**
+ * 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.teller.handler;
+
+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.organisation.teller.service.CashierWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Handles a modify cashier command.
+ *
+ * @author Markus Geiss
+ * @see org.apache.fineract.organisation.teller.service.CashierWritePlatformService
+ * @since 2.0.0
+ */
+public class ModifyCashierCommandHandler implements NewCommandSourceHandler {
+
+    private final CashierWritePlatformService writePlatformService;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param writePlatformService the {@code CashierWritePlatformService} used to access the backend
+     */
+    @Autowired
+    public ModifyCashierCommandHandler(final CashierWritePlatformService writePlatformService) {
+        super();
+        this.writePlatformService = writePlatformService;
+    }
+
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.modifyCashier(command.entityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/SettleCashFromCashierCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/SettleCashFromCashierCommandHandler.java
new file mode 100644
index 0000000..afaaddc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/SettleCashFromCashierCommandHandler.java
@@ -0,0 +1,51 @@
+/**
+ * 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.teller.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.organisation.teller.service.TellerWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "TELLER", action = "SETTLECASHFROMCASHIER")
+public class SettleCashFromCashierCommandHandler implements NewCommandSourceHandler {
+
+    private final TellerWritePlatformService writePlatformService;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param writePlatformService the {@code CashierWritePlatformService} used to access the backend
+     */
+    @Autowired
+    public SettleCashFromCashierCommandHandler(final TellerWritePlatformService writePlatformService) {
+        super();
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+    	
+        return this.writePlatformService.settleCashFromCashier(command.subentityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/UpdateCashierAllocationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/UpdateCashierAllocationCommandHandler.java
new file mode 100644
index 0000000..7840a96
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/UpdateCashierAllocationCommandHandler.java
@@ -0,0 +1,58 @@
+/**
+ * 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.teller.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.organisation.teller.service.TellerWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * Handles a delete teller command.
+ *
+ * @author Markus Geiss
+ * @see org.apache.fineract.organisation.teller.service.TellerWritePlatformService
+ * @since 2.0.0
+ */
+@Service
+@CommandType(entity = "TELLER", action = "UPDATECASHIERALLOCATION")
+public class UpdateCashierAllocationCommandHandler implements NewCommandSourceHandler {
+
+    private final TellerWritePlatformService writePlatformService;
+
+    /**
+     * Creates a new instance
+     *
+     * @param writePlatformService the {@code TellerWritePlatformService} used to access the backend
+     */
+    @Autowired
+    public UpdateCashierAllocationCommandHandler(final TellerWritePlatformService writePlatformService) {
+        super();
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.updateCashierAllocation(command.entityId(), 
+        		command.subentityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/UpdateTellerCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/UpdateTellerCommandHandler.java
new file mode 100644
index 0000000..3868139
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/handler/UpdateTellerCommandHandler.java
@@ -0,0 +1,57 @@
+/**
+ * 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.teller.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.organisation.teller.service.TellerWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * Handles a delete teller command.
+ *
+ * @author Markus Geiss
+ * @see org.apache.fineract.organisation.teller.service.TellerWritePlatformService
+ * @since 2.0.0
+ */
+@Service
+@CommandType(entity = "TELLER", action = "UPDATE")
+public class UpdateTellerCommandHandler implements NewCommandSourceHandler {
+
+    private final TellerWritePlatformService writePlatformService;
+
+    /**
+     * Creates a new instance
+     *
+     * @param writePlatformService the {@code TellerWritePlatformService} used to access the backend
+     */
+    @Autowired
+    public UpdateTellerCommandHandler(final TellerWritePlatformService writePlatformService) {
+        super();
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.modifyTeller(command.entityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/serialization/TellerCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/serialization/TellerCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..949620f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/serialization/TellerCommandFromApiJsonDeserializer.java
@@ -0,0 +1,169 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.teller.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+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.joda.time.LocalDate;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.teller.exception.InvalidDateInputException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Deserializer of JSON for Teller API.
+ */
+
+@Component
+public final class TellerCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("officeId", "name", "description", "startDate", "endDate",
+            "status", "dateFormat", "locale", "isFullDay", "staffId", "hourStartTime", "minStartTime", "hourEndTime", "minEndTime",
+            "txnAmount", "txnDate", "txnNote", "entityType", "entityId", "currencyCode"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public TellerCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForCreateAndUpdateTeller(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("teller");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed("officeId", element);
+        baseDataValidator.reset().parameter("officeId").value(officeId).notNull().integerGreaterThanZero();
+
+        final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
+        baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(50);
+
+        final String description = this.fromApiJsonHelper.extractStringNamed("description", element);
+        baseDataValidator.reset().parameter("description").value(description).notExceedingLengthOf(100);
+
+        final LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed("startDate", element);
+        baseDataValidator.reset().parameter("startDate").value(startDate).notNull();
+
+        final LocalDate endDate = this.fromApiJsonHelper.extractLocalDateNamed("endDate", element);
+        baseDataValidator.reset().parameter("endDate").value(endDate).ignoreIfNull();
+
+        final String status = this.fromApiJsonHelper.extractStringNamed("status", element);
+        baseDataValidator.reset().parameter("status").value(status).notBlank().notExceedingLengthOf(50);
+
+        if (endDate != null) {
+            if (endDate.isBefore(startDate)) { throw new InvalidDateInputException(startDate.toString(), endDate.toString()); }
+        }
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForAllocateCashier(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("teller");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long staffId = this.fromApiJsonHelper.extractLongNamed("staffId", element);
+        baseDataValidator.reset().parameter("staffId").value(staffId).notNull().integerGreaterThanZero();
+
+        final String description = this.fromApiJsonHelper.extractStringNamed("description", element);
+        baseDataValidator.reset().parameter("description").value(description).notExceedingLengthOf(100);
+
+        final LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed("startDate", element);
+        baseDataValidator.reset().parameter("startDate").value(startDate).notNull();
+
+        final LocalDate endDate = this.fromApiJsonHelper.extractLocalDateNamed("endDate", element);
+        baseDataValidator.reset().parameter("endDate").value(endDate).notNull();
+
+        final Boolean isFullDay = this.fromApiJsonHelper.extractBooleanNamed("isFullDay", element);
+        baseDataValidator.reset().parameter("isFullDay").value(isFullDay).notNull();
+
+        if (!isFullDay) {
+            final String hourStartTime = this.fromApiJsonHelper.extractStringNamed("hourStartTime", element);
+            baseDataValidator.reset().parameter("startTime").value(hourStartTime).notBlank();
+            final String minStartTime = this.fromApiJsonHelper.extractStringNamed("minStartTime", element);
+            baseDataValidator.reset().parameter("startTime").value(minStartTime).notBlank();
+            final String hourEndTime = this.fromApiJsonHelper.extractStringNamed("hourEndTime", element);
+            baseDataValidator.reset().parameter("hourEndTime").value(hourEndTime).notBlank();
+            final String minEndTime = this.fromApiJsonHelper.extractStringNamed("minEndTime", element);
+            baseDataValidator.reset().parameter("minEndTime").value(minEndTime).notBlank();
+
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForCashTxnForCashier(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("teller");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final BigDecimal txnAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("txnAmount", element);
+        baseDataValidator.reset().parameter("txnAmount").value(txnAmount).notNull();
+
+        final LocalDate txnDate = this.fromApiJsonHelper.extractLocalDateNamed("txnDate", element);
+        baseDataValidator.reset().parameter("txnDate").value(txnDate).notNull();
+
+        final String txnNote = this.fromApiJsonHelper.extractStringNamed("txnNote", element);
+        baseDataValidator.reset().parameter("txnNote").value(txnNote).notExceedingLengthOf(200);
+
+        final String currencyCode = this.fromApiJsonHelper.extractStringNamed("currencyCode", element);
+        baseDataValidator.reset().parameter("currencyCode").value(currencyCode).notExceedingLengthOf(3);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/CashierWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/CashierWritePlatformService.java
new file mode 100644
index 0000000..aea242e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/CashierWritePlatformService.java
@@ -0,0 +1,42 @@
+/**
+ * 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.teller.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public class CashierWritePlatformService {
+
+	public CommandProcessingResult allocateCashierToTeller(JsonCommand command) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	public CommandProcessingResult deleteCashier(Long entityId) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	public CommandProcessingResult modifyCashier(Long entityId,
+			JsonCommand command) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformService.java
new file mode 100644
index 0000000..57ce7fa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformService.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.teller.service;
+
+import org.apache.fineract.organisation.teller.data.CashierData;
+import org.apache.fineract.organisation.teller.data.CashierTransactionData;
+import org.apache.fineract.organisation.teller.data.CashierTransactionsWithSummaryData;
+import org.apache.fineract.organisation.teller.data.TellerData;
+import org.apache.fineract.organisation.teller.data.TellerJournalData;
+import org.apache.fineract.organisation.teller.data.TellerTransactionData;
+
+import java.util.Collection;
+import java.util.Date;
+
+public interface TellerManagementReadPlatformService {
+
+    public Collection<TellerData> getTellers(Long officeId);
+
+    public TellerData findTeller(Long tellerId);
+
+    public CashierData findCashier(Long cashierId);
+
+    public Collection<CashierData> getCashierData(Long officeId, Long tellerId, Long staffId, Date date);
+
+    public Collection<CashierData> getTellerCashiers(Long tellerId, Date date);
+
+    public CashierData retrieveCashierTemplate(Long officeId, Long tellerId, boolean staffInSelectedOfficeOnly);
+
+    public CashierTransactionData retrieveCashierTxnTemplate(Long cashierId);
+
+    public TellerTransactionData findTellerTransaction(Long transactionId);
+
+    public Collection<TellerTransactionData> fetchTellerTransactionsByTellerId(Long tellerId, Date fromDate, Date toDate);
+
+    public Collection<TellerJournalData> getJournals(Long officeId, Long tellerId, Long cashierId, Date dateFrom, Date dateTo);
+
+    public Collection<TellerJournalData> fetchTellerJournals(Long tellerId, Long cashierId, Date fromDate, Date toDate);
+
+    public Collection<TellerData> retrieveAllTellersForDropdown(Long officeId);
+
+    public Collection<TellerData> retrieveAllTellers(String sqlSearch, Long officeId, String status);
+
+    public Collection<CashierData> getCashiersForTeller(Long tellerId, Date fromDate, Date toDate);
+
+    public Collection<CashierData> retrieveCashiersForTellers(String sqlSearch, Long tellerId);
+
+    public Collection<CashierTransactionData> retrieveCashierTransactions(Long cashierId, boolean includeAllTellers, Date fromDate,
+            Date toDate, String currencyCode);
+
+    public CashierTransactionsWithSummaryData retrieveCashierTransactionsWithSummary(Long cashierId, boolean includeAllTellers,
+            Date fromDate, Date toDate, String currencyCode);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java
new file mode 100644
index 0000000..c52cb04a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java
@@ -0,0 +1,787 @@
+/**
+ * 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.teller.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.organisation.teller.data.CashierData;
+import org.apache.fineract.organisation.teller.data.CashierTransactionData;
+import org.apache.fineract.organisation.teller.data.CashierTransactionTypeTotalsData;
+import org.apache.fineract.organisation.teller.data.CashierTransactionsWithSummaryData;
+import org.apache.fineract.organisation.teller.data.TellerData;
+import org.apache.fineract.organisation.teller.data.TellerJournalData;
+import org.apache.fineract.organisation.teller.data.TellerTransactionData;
+import org.apache.fineract.organisation.teller.domain.CashierTxnType;
+import org.apache.fineract.organisation.teller.domain.TellerStatus;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class TellerManagementReadPlatformServiceImpl implements TellerManagementReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final TellerLookupMapper lookupMapper = new TellerLookupMapper();
+    private final TellerInOfficeHierarchyMapper tellerInOfficeHierarchyMapper = new TellerInOfficeHierarchyMapper();
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final CurrencyReadPlatformService currencyReadPlatformService;
+
+    @Autowired
+    public TellerManagementReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final OfficeReadPlatformService officeReadPlatformService, StaffReadPlatformService staffReadPlatformService,
+            final CurrencyReadPlatformService currencyReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.currencyReadPlatformService = currencyReadPlatformService;
+    }
+
+    private static final class TellerMapper implements RowMapper<TellerData> {
+
+        public String schema() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder.append("t.id as id,t.office_id as office_id, t.name as teller_name, t.description as description, ");
+            sqlBuilder.append("t.valid_from as start_date, t.valid_to as end_date, t.state as status, o.name as office_name, ");
+            sqlBuilder.append("t.debit_account_id as debit_account_id, t.credit_account_id as credit_account_id ");
+            sqlBuilder.append("from m_tellers t ");
+            sqlBuilder.append("join m_office o on o.id = t.office_id ");
+
+            return sqlBuilder.toString();
+        }
+
+        @Override
+        public TellerData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long officeId = rs.getLong("office_id");
+            final String tellerName = rs.getString("teller_name");
+            final String description = rs.getString("description");
+            final String officeName = rs.getString("office_name");
+            TellerStatus tellerStatus = null;
+            final Integer status = rs.getInt("status");
+            if (status != null) {
+                tellerStatus = TellerStatus.fromInt(status);
+            }
+            final Long debitAccountId = rs.getLong("debit_account_id");
+            final Long creditAccountId = rs.getLong("credit_account_id");
+
+            final LocalDate startDate = JdbcSupport.getLocalDate(rs, "start_date");
+            final LocalDate endDate = JdbcSupport.getLocalDate(rs, "end_date");
+
+            return TellerData.instance(id, officeId, debitAccountId, creditAccountId, tellerName, description, startDate, endDate,
+                    tellerStatus, officeName, null, null);
+        }
+    }
+
+    private static final class TellerInOfficeHierarchyMapper implements RowMapper<TellerData> {
+
+        public String schema() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(200);
+
+            sqlBuilder.append("t.id as id,t.office_id as office_id, t.name as teller_name, t.description as description, ");
+            sqlBuilder.append("t.valid_from as start_date, t.valid_to as end_date, t.state as status, o.name as office_name ");
+            sqlBuilder.append("t.debit_account_id as debit_account_id, t.credit_account_id as credit_account_id ");
+            sqlBuilder.append("from m_office o ");
+            sqlBuilder.append("join m_office ohierarchy on o.hierarchy like concat(ohierarchy.hierarchy, '%') ");
+            sqlBuilder.append("join m_tellers t on t.office_id = ohierarchy.id and s.is_active=1 ");
+
+            sqlBuilder.append("where o.id = ? ");
+
+            return sqlBuilder.toString();
+        }
+
+        @Override
+        public TellerData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String tellerName = rs.getString("teller_name");
+            final String description = rs.getString("description");
+            final String officeName = rs.getString("office_name");
+            final Long officeId = rs.getLong("office_id");
+            TellerStatus tellerStatus = null;
+            final Integer status = rs.getInt("status");
+            if (status != null) {
+                tellerStatus = TellerStatus.fromInt(status);
+            }
+            final Long debitAccountId = rs.getLong("debit_account_id");
+            final Long creditAccountId = rs.getLong("credit_account_id");
+
+            final LocalDate startDate = JdbcSupport.getLocalDate(rs, "start_date");
+            final LocalDate endDate = JdbcSupport.getLocalDate(rs, "end_date");
+
+            return TellerData.instance(id, officeId, debitAccountId, creditAccountId, tellerName, description, startDate, endDate,
+                    tellerStatus, officeName, null, null);
+
+        }
+    }
+
+    private static final class TellerLookupMapper implements RowMapper<TellerData> {
+
+        private final String schemaSql;
+
+        public TellerLookupMapper() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(100);
+            sqlBuilder.append("t.id as id, t.name as teller_name ");
+            sqlBuilder.append("from m_tellers t ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public TellerData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String tellerName = rs.getString("teller_name");
+            return TellerData.lookup(id, tellerName);
+        }
+    }
+
+    @Override
+    public Collection<TellerData> retrieveAllTellersForDropdown(final Long officeId) {
+
+        final Long defaultOfficeId = defaultToUsersOfficeIfNull(officeId);
+
+        final String sql = "select " + this.lookupMapper.schema() + " where s.office_id = ? and s.is_active=1 ";
+
+        return this.jdbcTemplate.query(sql, this.lookupMapper, new Object[] { defaultOfficeId });
+    }
+
+    private Long defaultToUsersOfficeIfNull(final Long officeId) {
+        Long defaultOfficeId = officeId;
+        if (defaultOfficeId == null) {
+            defaultOfficeId = this.context.authenticatedUser().getOffice().getId();
+        }
+        return defaultOfficeId;
+    }
+
+    @Override
+    public TellerData findTeller(final Long tellerId) {
+
+        try {
+            final TellerMapper tm = new TellerMapper();
+            final String sql = "select " + tm.schema() + " where t.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, tm, new Object[] { tellerId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new StaffNotFoundException(tellerId);
+        }
+    }
+
+    @Override
+    public Collection<TellerData> retrieveAllTellers(final String sqlSearch, final Long officeId, final String status) {
+        final String extraCriteria = getTellerCriteria(sqlSearch, officeId, status);
+        return retrieveAllTeller(extraCriteria);
+    }
+
+    private Collection<TellerData> retrieveAllTeller(final String extraCriteria) {
+
+        final TellerMapper tm = new TellerMapper();
+        String sql = "select " + tm.schema();
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            sql += " where " + extraCriteria;
+        }
+        sql = sql + " order by t.teller_name";
+        return this.jdbcTemplate.query(sql, tm, new Object[] {});
+    }
+
+    private String getTellerCriteria(final String sqlSearch, final Long officeId, final String status) {
+
+        final StringBuffer extraCriteria = new StringBuffer(200);
+
+        if (sqlSearch != null) {
+            extraCriteria.append(" and (").append(sqlSearch).append(")");
+        }
+        if (officeId != null) {
+            extraCriteria.append(" and office_id = ").append(officeId).append(" ");
+        }
+        // Passing status parameter to get ACTIVE (By Default), INACTIVE or ALL
+        // (Both active and Inactive) employees
+        if (status.equalsIgnoreCase("active")) {
+            extraCriteria.append(" and status = 300 ");
+        } else if (status.equalsIgnoreCase("inActive")) {
+            extraCriteria.append(" and status = 0 ");
+        } else if (status.equalsIgnoreCase("all")) {} else {
+            throw new UnrecognizedQueryParamException("status", status, new Object[] { "all", "active", "inactive" });
+        }
+
+        if (StringUtils.isNotBlank(extraCriteria.toString())) {
+            extraCriteria.delete(0, 4);
+        }
+
+        // remove begin four letter including a space from the string.
+        return extraCriteria.toString();
+    }
+
+    @Override
+    public Collection<TellerData> getTellers(Long officeId) {
+        return retrieveAllTellers(false);
+    }
+
+    @Override
+    public Collection<CashierData> getCashiersForTeller(Long tellerId, Date fromDate, Date toDate) {
+        return retrieveCashiersForTellers(null, tellerId);
+    }
+
+    @Override
+    public Collection<CashierData> retrieveCashiersForTellers(final String sqlSearch, final Long tellerId) {
+        final String extraCriteria = getTellerCriteria(sqlSearch, tellerId);
+        return fetchCashiers(extraCriteria);
+    }
+
+    private String getTellerCriteria(final String sqlSearch, final Long tellerId) {
+
+        final StringBuffer extraCriteria = new StringBuffer(200);
+
+        if (sqlSearch != null) {
+            extraCriteria.append(" and (").append(sqlSearch).append(")");
+        }
+        if (tellerId != null) {
+            extraCriteria.append(" and teller_id = ").append(tellerId).append(" ");
+        }
+
+        // remove begin four letter including a space from the string.
+        if (StringUtils.isNotBlank(extraCriteria.toString())) {
+            extraCriteria.delete(0, 4);
+        }
+
+        return extraCriteria.toString();
+    }
+
+    private Collection<CashierData> fetchCashiers(final String extraCriteria) {
+
+        final CashierMapper cm = new CashierMapper();
+        String sql = "select " + cm.schema();
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            sql += " where " + extraCriteria;
+        }
+        sql = sql + " order by teller_name";
+        return this.jdbcTemplate.query(sql, cm, new Object[] {});
+    }
+
+    @Override
+    public CashierData findCashier(Long cashierId) {
+        try {
+            final CashierMapper cm = new CashierMapper();
+            final String sql = "select " + cm.schema() + " where c.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, cm, new Object[] { cashierId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new StaffNotFoundException(cashierId);
+        }
+    }
+
+    @Override
+    public Collection<CashierData> getCashierData(Long officeId, Long tellerId, Long staffId, Date date) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Collection<CashierData> getTellerCashiers(Long tellerId, Date date) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public TellerTransactionData findTellerTransaction(Long transactionId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Collection<TellerTransactionData> fetchTellerTransactionsByTellerId(Long tellerId, Date fromDate, Date toDate) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Collection<TellerJournalData> getJournals(Long officeId, Long tellerId, Long cashierId, Date dateFrom, Date dateTo) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Collection<TellerJournalData> fetchTellerJournals(Long tellerId, Long cashierId, Date fromDate, Date toDate) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Cacheable(value = "tellers", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'of')")
+    public Collection<TellerData> retrieveAllTellers(final boolean includeAllTellers) {
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        String hierarchySearchString = null;
+        if (includeAllTellers) {
+            hierarchySearchString = "." + "%";
+        } else {
+            hierarchySearchString = hierarchy + "%";
+        }
+        final TellerMapper tm = new TellerMapper();
+        final String sql = "select " + tm.schema() + "where o.hierarchy like ? order by o.hierarchy";
+
+        return this.jdbcTemplate.query(sql, tm, new Object[] { hierarchySearchString });
+    }
+
+    @Override
+    public CashierData retrieveCashierTemplate(Long officeId, Long tellerId, boolean staffInSelectedOfficeOnly) {
+        final Long defaultOfficeId = defaultToUsersOfficeIfNull(officeId);
+
+        final OfficeData officeData = this.officeReadPlatformService.retrieveOffice(defaultOfficeId);
+        String officeName = "";
+        if (officeData != null) {
+            officeName = officeData.name();
+        }
+
+        TellerData tellerData = findTeller(tellerId);
+        String tellerName = "";
+        if (tellerData != null) {
+            tellerName = tellerData.getName();
+        }
+
+        final Collection<OfficeData> offices = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+        Collection<StaffData> staffOptions = null;
+
+        final boolean loanOfficersOnly = false;
+        if (staffInSelectedOfficeOnly) {
+            staffOptions = this.staffReadPlatformService.retrieveAllStaffForDropdown(defaultOfficeId);
+        } else {
+            staffOptions = this.staffReadPlatformService.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(defaultOfficeId,
+                    loanOfficersOnly);
+        }
+        if (CollectionUtils.isEmpty(staffOptions)) {
+            staffOptions = null;
+        }
+
+        return CashierData.template(officeId, officeName, tellerId, tellerName, staffOptions);
+    }
+
+    @Override
+    public CashierTransactionData retrieveCashierTxnTemplate(Long cashierId) {
+        String officeName = "";
+        String tellerName = "";
+        String cashierName = "";
+        Long officeId = null;
+        Long tellerId = null;
+        Date startDate = null;
+        Date endDate = null;
+
+        CashierData cashierData = findCashier(cashierId);
+        if (cashierData != null) {
+            cashierName = cashierData.getStaffName();
+            tellerId = cashierData.getTellerId();
+            if (tellerId != null) {
+                TellerData tellerData = findTeller(tellerId);
+                if (tellerData != null) {
+                    tellerName = tellerData.getName();
+                    officeName = tellerData.getOfficeName();
+                }
+            }
+            startDate = cashierData.getStartDate();
+            endDate = cashierData.getEndDate();
+        }
+        // Fetching all currency type from m_organisation_currency table
+        final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
+
+        return CashierTransactionData.template(cashierId, tellerId, tellerName, officeId, officeName, cashierName, cashierData, startDate,
+                endDate, currencyOptions);
+    }
+
+    @Override
+    public CashierTransactionsWithSummaryData retrieveCashierTransactionsWithSummary(final Long cashierId, final boolean includeAllTellers,
+            final Date fromDate, final Date toDate, final String currencyCode) {
+        CashierData cashierData = findCashier(cashierId);
+        Long staffId = cashierData.getStaffId();
+        StaffData staffData = staffReadPlatformService.retrieveStaff(staffId);
+        OfficeData officeData = officeReadPlatformService.retrieveOffice(staffData.getOfficeId());
+        final String hierarchy = officeData.getHierarchy();
+        String hierarchySearchString = null;
+        if (includeAllTellers) {
+            hierarchySearchString = "." + "%";
+        } else {
+            hierarchySearchString = hierarchy;
+        }
+        final CashierTransactionSummaryMapper ctsm = new CashierTransactionSummaryMapper();
+        final String sql = "select " + ctsm.cashierTxnSummarySchema() + " limit 1000";
+
+        Collection<CashierTransactionTypeTotalsData> cashierTxnTypeTotals = this.jdbcTemplate.query(sql, ctsm, new Object[] { cashierId,
+                currencyCode, hierarchySearchString, cashierId, currencyCode, hierarchySearchString, cashierId, currencyCode,
+                hierarchySearchString });
+
+        Iterator<CashierTransactionTypeTotalsData> itr = cashierTxnTypeTotals.iterator();
+        BigDecimal allocAmount = new BigDecimal(0);
+        BigDecimal cashInAmount = new BigDecimal(0);
+        BigDecimal cashOutAmount = new BigDecimal(0);
+        BigDecimal settleAmount = new BigDecimal(0);
+
+        while (itr.hasNext()) {
+            CashierTransactionTypeTotalsData total = itr.next();
+            if (total != null) {
+                if (total.getCashierTxnType() == CashierTxnType.ALLOCATE.getId()) {
+                    allocAmount = total.getCashTotal();
+                } else if (total.getCashierTxnType() == CashierTxnType.SETTLE.getId()) {
+                    settleAmount = total.getCashTotal();
+                } else if (total.getCashierTxnType() == CashierTxnType.INWARD_CASH_TXN.getId()) {
+                    cashInAmount = total.getCashTotal();
+                } else if (total.getCashierTxnType() == CashierTxnType.OUTWARD_CASH_TXN.getId()) {
+                    cashOutAmount = total.getCashTotal();
+                }
+            }
+        }
+
+        final Collection<CashierTransactionData> cashierTransactions = retrieveCashierTransactions(cashierId, includeAllTellers, fromDate,
+                toDate, currencyCode);
+
+        CashierTransactionData cashierTxnTemplate = retrieveCashierTxnTemplate(cashierId);
+
+        CashierTransactionsWithSummaryData txnsWithSummary = CashierTransactionsWithSummaryData.instance(cashierTransactions, allocAmount,
+                cashInAmount, cashOutAmount, settleAmount, cashierTxnTemplate.getOfficeName(), cashierTxnTemplate.getTellerId(),
+                cashierTxnTemplate.getTellerName(), cashierTxnTemplate.getCashierId(), cashierTxnTemplate.getCashierName());
+        return txnsWithSummary;
+    }
+
+    @Override
+    public Collection<CashierTransactionData> retrieveCashierTransactions(final Long cashierId, final boolean includeAllTellers,
+            final Date fromDate, final Date toDate, final String currencyCode) {
+        CashierData cashierData = findCashier(cashierId);
+        Long staffId = cashierData.getStaffId();
+        StaffData staffData = staffReadPlatformService.retrieveStaff(staffId);
+        OfficeData officeData = officeReadPlatformService.retrieveOffice(staffData.getOfficeId());
+        final String hierarchy = officeData.getHierarchy();
+        String hierarchySearchString = null;
+        if (includeAllTellers) {
+            hierarchySearchString = "." + "%";
+        } else {
+            hierarchySearchString = hierarchy;
+        }
+
+        final CashierTransactionMapper ctm = new CashierTransactionMapper();
+
+        final String sql = "select * from (select " + ctm.cashierTxnSchema()
+                + " where txn.cashier_id = ? and txn.currency_code = ? and o.hierarchy like ? ) cashier_txns " + " union (select "
+                + ctm.savingsTxnSchema()
+                + " where sav_txn.is_reversed = 0 and c.id = ? and sav.currency_code = ? and o.hierarchy like ? and "
+                + " sav_txn.transaction_date between c.start_date and date_add(c.end_date, interval 1 day) "
+                + " and renum.enum_value in ('deposit','withdrawal fee', 'Pay Charge', 'withdrawal') ) " + " union (select "
+                + ctm.loansTxnSchema()
+                + " where loan_txn.is_reversed = 0 and c.id = ? and loan.currency_code = ? and o.hierarchy like ? and "
+                + " loan_txn.transaction_date between c.start_date and date_add(c.end_date, interval 1 day) "
+                + " and renum.enum_value in ('Repayment At Disbursement','Repayment', 'Recovery Payment','Disbursement') ) "
+                + " order by created_date ";
+
+        return this.jdbcTemplate.query(sql, ctm, new Object[] { cashierId, currencyCode, hierarchySearchString, cashierId, currencyCode,
+                hierarchySearchString, cashierId, currencyCode, hierarchySearchString });
+    }
+
+    private static final class CashierMapper implements RowMapper<CashierData> {
+
+        public String schema() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder.append("c.id as id,c.teller_id as teller_id, t.name as teller_name, c.description as description, ");
+            sqlBuilder.append("c.staff_id as staff_id, s.display_name as staff_name,  ");
+            sqlBuilder.append("c.start_date as start_date, c.end_date as end_date,  ");
+            sqlBuilder.append("c.full_day as full_day, c.start_time as start_time, c.end_time as end_time ");
+            sqlBuilder.append("from m_cashiers c ");
+            sqlBuilder.append("join m_tellers t on t.id = c.teller_id ");
+            sqlBuilder.append("join m_staff s on s.id = c.staff_id ");
+
+            return sqlBuilder.toString();
+        }
+
+        @Override
+        public CashierData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long tellerId = rs.getLong("teller_id");
+            final String tellerName = rs.getString("teller_name");
+            final Long staffId = rs.getLong("staff_id");
+            final String staffName = rs.getString("staff_name");
+            final String description = rs.getString("description");
+
+            final LocalDate startDate = JdbcSupport.getLocalDate(rs, "start_date");
+            final LocalDate endDate = JdbcSupport.getLocalDate(rs, "end_date");
+            final Integer fullDayFromDB = rs.getInt("full_day");
+            Boolean fullDay = false;
+            if (fullDayFromDB == 1) {
+                fullDay = true;
+            }
+            final String startTime = rs.getString("start_time");
+            final String endTime = rs.getString("end_time");
+
+            return CashierData.instance(id, null, null, staffId, staffName, tellerId, tellerName, description, startDate.toDate(),
+                    endDate.toDate(), fullDay, startTime, endTime);
+        }
+    }
+
+    private static final class CashierTransactionMapper implements RowMapper<CashierTransactionData> {
+
+        public String cashierTxnSchema() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder.append(" txn.id as txn_id, txn.cashier_id as cashier_id, ");
+            sqlBuilder.append(" txn.txn_type as txn_type, ");
+            sqlBuilder.append(" txn.txn_amount as txn_amount, txn.txn_date as txn_date, txn.txn_note as txn_note, ");
+            sqlBuilder.append(" txn.entity_type as entity_type, txn.entity_id as entity_id, txn.created_date as created_date, ");
+            sqlBuilder
+                    .append(" o.id as office_id, o.name as office_name, t.id as teller_id, t.name as teller_name, s.display_name as cashier_name ");
+            sqlBuilder.append(" from m_cashier_transactions txn ");
+            sqlBuilder.append(" left join m_cashiers c on c.id = txn.cashier_id ");
+            sqlBuilder.append(" left join m_tellers t on t.id = c.teller_id ");
+            sqlBuilder.append(" left join m_office o on o.id = t.office_id ");
+            sqlBuilder.append(" left join m_staff s on s.id = c.staff_id ");
+
+            return sqlBuilder.toString();
+        }
+
+        public String savingsTxnSchema() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder.append(" sav_txn.id as txn_id, null as cashier_id, ");
+            sqlBuilder.append(" case ");
+            sqlBuilder.append(" 	when renum.enum_value in ('deposit','withdrawal fee', 'Pay Charge') ");
+            sqlBuilder.append(" 		then 103 ");
+            sqlBuilder.append(" 	when renum.enum_value in ('withdrawal', '') ");
+            sqlBuilder.append(" 		then 104 ");
+            sqlBuilder.append(" 	else ");
+            sqlBuilder.append(" 		105 ");
+            sqlBuilder.append(" end as txn_type, ");
+            sqlBuilder.append(" sav_txn.amount as txn_amount, sav_txn.transaction_date as txn_date, ");
+            sqlBuilder
+                    .append(" concat (renum.enum_value, ', Sav:', sav.id, '-', sav.account_no, ',Client:', cl.id, '-',cl.display_name) as txn_note, ");
+            sqlBuilder.append(" 'savings' as entity_type, sav.id as entity_id, sav_txn.created_date as created_date, ");
+            sqlBuilder
+                    .append(" o.id as office_id, o.name as office_name, null as teller_id, null as teller_name, staff.display_name as cashier_name ");
+            sqlBuilder.append(" from m_savings_account_transaction sav_txn ");
+            sqlBuilder
+                    .append(" left join r_enum_value renum on sav_txn.transaction_type_enum = renum.enum_id and renum.enum_name = 'savings_transaction_type_enum' ");
+            sqlBuilder.append(" left join m_savings_account sav on sav_txn.savings_account_id = sav.id ");
+            sqlBuilder.append(" left join m_client cl on sav.client_id = cl.id ");
+            sqlBuilder.append(" left join m_office o on cl.office_id = o.id ");
+            sqlBuilder.append(" left join m_appuser user on sav_txn.appuser_id = user.id ");
+            sqlBuilder.append(" left join m_staff staff on user.staff_id = staff.id ");
+            sqlBuilder.append(" left join m_cashiers c on c.staff_id = staff.id ");
+
+            return sqlBuilder.toString();
+        }
+
+        public String loansTxnSchema() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder.append(" loan_txn.id as txn_id, c.id as cashier_id, ");
+            sqlBuilder.append(" case ");
+            sqlBuilder.append(" 	when renum.enum_value in ('Repayment At Disbursement','Repayment', 'Recovery Payment') ");
+            sqlBuilder.append(" 		then 103 ");
+            sqlBuilder.append(" 	when renum.enum_value in ('Disbursement') ");
+            sqlBuilder.append(" 		then 104 ");
+            sqlBuilder.append(" 	else ");
+            sqlBuilder.append(" 		105 ");
+            sqlBuilder.append(" end as cash_txn_type, ");
+            sqlBuilder.append(" loan_txn.amount as txn_amount, loan_txn.transaction_date as txn_date, ");
+            sqlBuilder
+                    .append(" concat (renum.enum_value, ', Loan:', loan.id, '-', loan.account_no, ',Client:', cl.id, '-',cl.display_name) as txn_note, ");
+            sqlBuilder.append(" 'loans' as entity_type, loan.id as entity_id, loan_txn.created_date as created_date, ");
+            sqlBuilder
+                    .append(" o.id as office_id, o.name as office_name, null as teller_id, null as teller_name, staff.display_name as cashier_name ");
+            sqlBuilder.append(" from m_loan_transaction loan_txn ");
+            sqlBuilder
+                    .append(" left join r_enum_value renum on loan_txn.transaction_type_enum = renum.enum_id and renum.enum_name = 'transaction_type_enum' ");
+            sqlBuilder.append(" left join m_loan loan on loan_txn.loan_id = loan.id ");
+            sqlBuilder.append(" left join m_client cl on loan.client_id = cl.id ");
+            sqlBuilder.append(" left join m_office o on cl.office_id = o.id ");
+            sqlBuilder.append(" left join m_appuser user on loan_txn.appuser_id = user.id ");
+            sqlBuilder.append(" left join m_staff staff on user.staff_id = staff.id ");
+            sqlBuilder.append(" left join m_cashiers c on c.staff_id = staff.id ");
+
+            return sqlBuilder.toString();
+        }
+
+        @Override
+        public CashierTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("txn_id");
+            final Long cashierId = rs.getLong("cashier_id");
+            final Integer tType = rs.getInt("txn_type");
+            final CashierTxnType txnType = CashierTxnType.getCashierTxnType(tType);
+            final BigDecimal txnAmount = rs.getBigDecimal("txn_amount");
+            final LocalDate txnLocalDate = JdbcSupport.getLocalDate(rs, "txn_date");
+            final String txnNote = rs.getString("txn_note");
+            final String entityType = rs.getString("entity_type");
+            final Long entityId = rs.getLong("entity_id");
+            final LocalDate createdLocalDate = JdbcSupport.getLocalDate(rs, "created_date");
+
+            Date txnDate = null;
+            if (txnLocalDate != null) {
+                txnDate = txnLocalDate.toDate();
+            }
+            Date createdDate = null;
+            if (createdLocalDate != null) {
+                createdDate = createdLocalDate.toDate();
+            }
+
+            final Long officeId = rs.getLong("office_id");
+            final String officeName = rs.getString("office_name");
+            final Long tellerId = rs.getLong("teller_id");
+            final String tellerName = rs.getString("teller_name");
+            final String cashierName = rs.getString("cashier_name");
+
+            return CashierTransactionData.instance(id, cashierId, txnType, txnAmount, txnDate, txnNote, entityType, entityId, createdDate,
+                    officeId, officeName, tellerId, tellerName, cashierName, null, null, null);
+        }
+    }
+
+    private static final class CashierTransactionSummaryMapper implements RowMapper<CashierTransactionTypeTotalsData> {
+
+        public String cashierTxnSummarySchema() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder.append(" cash_txn_type, sum(txn_amount) as txn_total from ");
+            sqlBuilder.append(" (select * from ");
+            sqlBuilder.append(" (select txn.id as txn_id, txn.cashier_id as cashier_id, ");
+            sqlBuilder.append("	txn.txn_type as cash_txn_type, ");
+            sqlBuilder.append("	txn.txn_amount as txn_amount, txn.txn_date as txn_date, txn.txn_note as txn_note, ");
+            sqlBuilder.append("	txn.entity_type as entity_type, txn.entity_id as entity_id, txn.created_date as created_date, ");
+            sqlBuilder
+                    .append("	o.id as office_id, o.name as office_name, t.id as teller_id, t.name as teller_name, s.display_name as cashier_name ");
+            sqlBuilder.append("	from m_cashier_transactions txn ");
+            sqlBuilder.append("	left join m_cashiers c on c.id = txn.cashier_id ");
+            sqlBuilder.append("	left join m_tellers t on t.id = c.teller_id ");
+            sqlBuilder.append("	left join m_office o on o.id = t.office_id ");
+            sqlBuilder.append("	left join m_staff s on s.id = c.staff_id ");
+            sqlBuilder.append("	where txn.cashier_id = ? ");
+            sqlBuilder.append(" and   txn.currency_code = ? ");
+            sqlBuilder.append("	and o.hierarchy like ?  ) cashier_txns ");
+            sqlBuilder.append("	UNION ");
+            sqlBuilder.append("	(select sav_txn.id as txn_id, c.id as cashier_id, ");
+            sqlBuilder.append("	case ");
+            sqlBuilder.append("		when renum.enum_value in ('deposit','withdrawal fee', 'Pay Charge') ");
+            sqlBuilder.append("			then 103 ");
+            sqlBuilder.append("		when renum.enum_value in ('withdrawal') ");
+            sqlBuilder.append("			then 104 ");
+            sqlBuilder.append("		else ");
+            sqlBuilder.append("			105 ");
+            sqlBuilder.append("	end as cash_txn_type, ");
+            sqlBuilder.append("	sav_txn.amount as txn_amount, sav_txn.transaction_date as txn_date, ");
+            sqlBuilder
+                    .append("	concat (renum.enum_value, ', Sav:', sav.id, '-', sav.account_no, ',Client:', cl.id, '-',cl.display_name) as txn_note, ");
+            sqlBuilder.append("	'savings' as entity_type, sav.id as entity_id, sav_txn.created_date as created_date, ");
+            sqlBuilder
+                    .append("	o.id as office_id, o.name as office_name, null as teller_id, null as teller_name, staff.display_name as cashier_name ");
+            sqlBuilder.append("	from m_savings_account_transaction sav_txn ");
+            sqlBuilder
+                    .append("	left join r_enum_value renum on sav_txn.transaction_type_enum = renum.enum_id and renum.enum_name = 'savings_transaction_type_enum' ");
+            sqlBuilder.append("	left join m_savings_account sav on sav_txn.savings_account_id = sav.id ");
+            sqlBuilder.append("	left join m_client cl on sav.client_id = cl.id ");
+            sqlBuilder.append("	left join m_office o on cl.office_id = o.id ");
+            sqlBuilder.append("	left join m_appuser user on sav_txn.appuser_id = user.id ");
+            sqlBuilder.append("	left join m_staff staff on user.staff_id = staff.id ");
+            sqlBuilder.append("	left join m_cashiers c on c.staff_id = staff.id ");
+            sqlBuilder.append("	where sav_txn.is_reversed = 0 and c.id = ? ");
+            sqlBuilder.append(" and sav.currency_code = ? ");
+            sqlBuilder.append("	and o.hierarchy like ? ");
+            sqlBuilder.append("	and sav_txn.transaction_date between c.start_date and date_add(c.end_date, interval 1 day) ");
+            sqlBuilder.append("	) ");
+            sqlBuilder.append("	UNION ");
+            sqlBuilder.append("	( ");
+            sqlBuilder.append("	select loan_txn.id as txn_id, c.id as cashier_id, ");
+            sqlBuilder.append("	case ");
+            sqlBuilder.append("		when renum.enum_value in ('Repayment At Disbursement','Repayment', 'Recovery Payment') ");
+            sqlBuilder.append("			then 103 ");
+            sqlBuilder.append("		when renum.enum_value in ('Disbursement') ");
+            sqlBuilder.append("			then 104 ");
+            sqlBuilder.append("		else ");
+            sqlBuilder.append("			105 ");
+            sqlBuilder.append("	end as cash_txn_type, ");
+            sqlBuilder.append("	loan_txn.amount as txn_amount, loan_txn.transaction_date as txn_date, ");
+            sqlBuilder
+                    .append("	concat (renum.enum_value, ', Loan:', loan.id, '-', loan.account_no, ',Client:', cl.id, '-',cl.display_name) as txn_note, ");
+            sqlBuilder.append("	'loans' as entity_type, loan.id as entity_id, loan_txn.created_date as created_date, ");
+            sqlBuilder
+                    .append("	o.id as office_id, o.name as office_name, null as teller_id, null as teller_name, staff.display_name as cashier_name ");
+            sqlBuilder.append("	from m_loan_transaction loan_txn ");
+            sqlBuilder
+                    .append("	left join r_enum_value renum on loan_txn.transaction_type_enum = renum.enum_id and renum.enum_name = 'transaction_type_enum' ");
+            sqlBuilder.append("	left join m_loan loan on loan_txn.loan_id = loan.id ");
+            sqlBuilder.append("	left join m_client cl on loan.client_id = cl.id ");
+            sqlBuilder.append("	left join m_office o on cl.office_id = o.id ");
+            sqlBuilder.append("	left join m_appuser user on loan_txn.appuser_id = user.id ");
+            sqlBuilder.append("	left join m_staff staff on user.staff_id = staff.id ");
+            sqlBuilder.append("	left join m_cashiers c on c.staff_id = staff.id ");
+            sqlBuilder.append("	where loan_txn.is_reversed = 0 and c.id = ? ");
+            sqlBuilder.append(" and loan.currency_code = ? ");
+            sqlBuilder.append("	and o.hierarchy like ? ");
+            sqlBuilder.append("	and loan_txn.transaction_date between c.start_date and date_add(c.end_date, interval 1 day) ");
+            sqlBuilder.append("	) ");
+            sqlBuilder.append("	) txns ");
+            sqlBuilder.append("	group by cash_txn_type ");
+
+            return sqlBuilder.toString();
+        }
+
+        @Override
+        public CashierTransactionTypeTotalsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+
+            final Integer cashierTxnType = rs.getInt("cash_txn_type");
+            final BigDecimal txnTotal = rs.getBigDecimal("txn_total");
+
+            return CashierTransactionTypeTotalsData.instance(cashierTxnType, txnTotal);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerTransactionWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerTransactionWritePlatformService.java
new file mode 100644
index 0000000..eba0ad7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerTransactionWritePlatformService.java
@@ -0,0 +1,39 @@
+/**
+ * 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.teller.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+/**
+ * Provides the local service for adding teller transactions.
+ *
+ * @see org.apache.fineract.organisation.teller.domain.TellerTransactionRepository
+ * @since 2.0.0
+ */
+public interface TellerTransactionWritePlatformService {
+
+    /**
+     * Stores teller transactions into the data base.
+     *
+     * @param command the command containing the teller transaction details
+     * @return the result i
+     */
+    public CommandProcessingResult createTellerTransaction(JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformService.java
new file mode 100644
index 0000000..bc04ce2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformService.java
@@ -0,0 +1,89 @@
+/**
+ * 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.teller.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+/**
+ * Provides the local service for adding, modifying and deleting tellers.
+ *
+ * @author Markus Geiss
+ * @see org.apache.fineract.organisation.teller.domain.TellerRepository
+ * @since 2.0.0
+ */
+public interface TellerWritePlatformService {
+
+    /**
+     * Creates a new teller.
+     *
+     * @param command the command to create a new teller
+     * @return {@code CommandProcessingResult} if successful
+     * @throws org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException
+     * @throws org.apache.fineract.infrastructure.core.exception.InvalidJsonException
+     */
+    public CommandProcessingResult createTeller(JsonCommand command);
+
+    /**
+     * Modifies a new teller.
+     *
+     * @param tellerId the primary key of the teller
+     * @param command  the command to modifya new teller
+     * @return {@code CommandProcessingResult} if successful
+     * @throws org.apache.fineract.organisation.teller.exception.TellerNotFoundException
+     * @throws org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException
+     * @throws org.apache.fineract.infrastructure.core.exception.InvalidJsonException
+     */
+    public CommandProcessingResult modifyTeller(Long tellerId, JsonCommand command);
+
+    /**
+     * deletes a new teller.
+     *
+     * @param tellerId the primary key of the teller
+     * @return {@code CommandProcessingResult} if successful
+     * @throws org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException
+     * @throws org.apache.fineract.infrastructure.core.exception.InvalidJsonException
+     */
+    public CommandProcessingResult deleteTeller(Long tellerId);
+    
+    /**
+     * Allocates a cashier to an existing teller. The allocation can be for a duration
+     * from a date to a date
+     * from a certain start time to an end time.
+     *
+	 * @param command the command to allocate a cashier for a specific teller
+     * @return {@code CommandProcessingResult} if successful
+     * @throws org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException
+     * @throws org.apache.fineract.infrastructure.core.exception.InvalidJsonException
+     */
+    public CommandProcessingResult allocateCashierToTeller(Long tellerId, JsonCommand command);
+
+	CommandProcessingResult updateCashierAllocation(Long tellerId, Long cashierId,
+			JsonCommand command);
+
+	CommandProcessingResult deleteCashierAllocation(final Long tellerId, Long cashierId, 
+			JsonCommand command);
+
+	CommandProcessingResult allocateCashToCashier(Long cashierId,
+			JsonCommand command);
+
+	CommandProcessingResult settleCashFromCashier(Long cashierId,
+			JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java
new file mode 100644
index 0000000..8a6d15f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java
@@ -0,0 +1,458 @@
+/**
+ * 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.teller.service;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount;
+import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
+import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
+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.exception.NoAuthorizationException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepository;
+import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
+import org.apache.fineract.organisation.teller.domain.Cashier;
+import org.apache.fineract.organisation.teller.domain.CashierRepository;
+import org.apache.fineract.organisation.teller.domain.CashierTransaction;
+import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository;
+import org.apache.fineract.organisation.teller.domain.CashierTxnType;
+import org.apache.fineract.organisation.teller.domain.Teller;
+import org.apache.fineract.organisation.teller.domain.TellerRepository;
+import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
+import org.apache.fineract.organisation.teller.exception.CashierExistForTellerException;
+import org.apache.fineract.organisation.teller.exception.CashierNotFoundException;
+import org.apache.fineract.organisation.teller.exception.TellerNotFoundException;
+import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
+import org.apache.fineract.portfolio.client.domain.ClientTransaction;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(TellerWritePlatformServiceJpaImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final TellerRepository tellerRepository;
+    private final TellerRepositoryWrapper tellerRepositoryWrapper;
+    private final OfficeRepository officeRepository;
+    private final StaffRepository staffRepository;
+    private final CashierRepository cashierRepository;
+    private final CashierTransactionRepository cashierTxnRepository;
+    private final JournalEntryRepository glJournalEntryRepository;
+    private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper;
+
+    @Autowired
+    public TellerWritePlatformServiceJpaImpl(final PlatformSecurityContext context,
+            final TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer, final TellerRepository tellerRepository,
+            final TellerRepositoryWrapper tellerRepositoryWrapper, final OfficeRepository officeRepository,
+            final StaffRepository staffRepository, CashierRepository cashierRepository, CashierTransactionRepository cashierTxnRepository,
+            JournalEntryRepository glJournalEntryRepository,
+            FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper) {
+        this.context = context;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.tellerRepository = tellerRepository;
+        this.tellerRepositoryWrapper = tellerRepositoryWrapper;
+        this.officeRepository = officeRepository;
+        this.staffRepository = staffRepository;
+        this.cashierRepository = cashierRepository;
+        this.cashierTxnRepository = cashierTxnRepository;
+        this.glJournalEntryRepository = glJournalEntryRepository;
+        this.financialActivityAccountRepositoryWrapper = financialActivityAccountRepositoryWrapper;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult createTeller(JsonCommand command) {
+        try {
+            this.context.authenticatedUser();
+
+            final Long officeId = command.longValueOfParameterNamed("officeId");
+
+            this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
+
+            // final Office parent =
+            // validateUserPriviledgeOnOfficeAndRetrieve(currentUser, officeId);
+            final Office tellerOffice = this.officeRepository.findOne(officeId);
+            if (tellerOffice == null) { throw new OfficeNotFoundException(officeId); }
+
+            final Teller teller = Teller.fromJson(tellerOffice, command);
+
+            // pre save to generate id for use in office hierarchy
+            this.tellerRepository.save(teller);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(teller.getId()) //
+                    .withOfficeId(teller.getOffice().getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleTellerDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult modifyTeller(Long tellerId, JsonCommand command) {
+        try {
+
+            final Long officeId = command.longValueOfParameterNamed("officeId");
+            final Office tellerOffice = this.officeRepository.findOne(officeId);
+            if (tellerOffice == null) { throw new OfficeNotFoundException(officeId); }
+
+            final AppUser currentUser = this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
+
+            final Teller teller = validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);
+
+            final Map<String, Object> changes = teller.update(tellerOffice, command);
+
+            if (!changes.isEmpty()) {
+                this.tellerRepository.saveAndFlush(teller);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(teller.getId()) //
+                    .withOfficeId(teller.officeId()) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleTellerDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    /*
+     * used to restrict modifying operations to office that are either the users
+     * office or lower (child) in the office hierarchy
+     */
+    private Teller validateUserPriviledgeOnTellerAndRetrieve(final AppUser currentUser, final Long tellerId) {
+
+        final Long userOfficeId = currentUser.getOffice().getId();
+        final Office userOffice = this.officeRepository.findOne(userOfficeId);
+        if (userOffice == null) { throw new OfficeNotFoundException(userOfficeId); }
+
+        final Teller tellerToReturn = this.tellerRepository.findOne(tellerId);
+        if (tellerToReturn != null) {
+            final Long tellerOfficeId = tellerToReturn.officeId();
+            if (userOffice.doesNotHaveAnOfficeInHierarchyWithId(tellerOfficeId)) { throw new NoAuthorizationException(
+                    "User does not have sufficient priviledges to act on the provided office."); }
+        } else {
+            throw new TellerNotFoundException(tellerId);
+        }
+
+        return tellerToReturn;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult deleteTeller(Long tellerId) {
+        // TODO Auto-generated method stub
+
+        Teller teller = tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
+        Set<Cashier> isTellerIdPresentInCashier = teller.getCashiers();
+
+        for (final Cashier tellerIdInCashier : isTellerIdPresentInCashier) {
+            if (tellerIdInCashier.getTeller().getId().toString()
+                    .equalsIgnoreCase(tellerId.toString())) { throw new CashierExistForTellerException(tellerId); }
+
+        }
+        tellerRepository.delete(teller);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(teller.getId()) //
+                .build();
+
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleTellerDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("m_tellers_name_unq")) {
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.teller.duplicate.name", "Teller with name `" + name + "` already exists",
+                    "name", name);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.teller.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    @Override
+    public CommandProcessingResult allocateCashierToTeller(final Long tellerId, JsonCommand command) {
+        try {
+            this.context.authenticatedUser();
+            Long hourStartTime;
+            Long minStartTime;
+            Long hourEndTime;
+            Long minEndTime;
+            String startTime = " ";
+            String endTime = " ";
+            final Teller teller = this.tellerRepository.findOne(tellerId);
+            if (teller == null) { throw new TellerNotFoundException(tellerId); }
+            final Office tellerOffice = teller.getOffice();
+
+            final Long staffId = command.longValueOfParameterNamed("staffId");
+
+            this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());
+
+            final Staff staff = this.staffRepository.findOne(staffId);
+            if (staff == null) { throw new StaffNotFoundException(staffId); }
+            final Boolean isFullDay = command.booleanObjectValueOfParameterNamed("isFullDay");
+            if (!isFullDay) {
+                hourStartTime = command.longValueOfParameterNamed("hourStartTime");
+                minStartTime = command.longValueOfParameterNamed("minStartTime");
+
+                if (minStartTime == 0)
+                    startTime = hourStartTime.toString() + ":" + minStartTime.toString() + "0";
+                else
+                    startTime = hourStartTime.toString() + ":" + minStartTime.toString();
+
+                hourEndTime = command.longValueOfParameterNamed("hourEndTime");
+                minEndTime = command.longValueOfParameterNamed("minEndTime");
+                if (minEndTime == 0)
+                    endTime = hourEndTime.toString() + ":" + minEndTime.toString() + "0";
+                else
+                    endTime = hourEndTime.toString() + ":" + minEndTime.toString();
+
+            }
+
+            final Cashier cashier = Cashier.fromJson(tellerOffice, teller, staff, startTime, endTime, command);
+
+            this.cashierRepository.save(cashier);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(teller.getId()) //
+                    .withSubEntityId(cashier.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleTellerDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult updateCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());
+
+            final Long staffId = command.longValueOfParameterNamed("staffId");
+            final Staff staff = this.staffRepository.findOne(staffId);
+            if (staff == null) { throw new StaffNotFoundException(staffId); }
+
+            final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);
+
+            cashier.setStaff(staff);
+
+            // TODO - check if staff office and teller office match
+
+            final Map<String, Object> changes = cashier.update(command);
+
+            if (!changes.isEmpty()) {
+                this.cashierRepository.saveAndFlush(cashier);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(cashier.getTeller().getId()) //
+                    .withSubEntityId(cashier.getId()) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleTellerDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private Cashier validateUserPriviledgeOnCashierAndRetrieve(final AppUser currentUser, final Long tellerId, final Long cashierId) {
+
+        validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);
+
+        final Cashier cashierToReturn = this.cashierRepository.findOne(cashierId);
+
+        return cashierToReturn;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult deleteCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+            final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);
+            this.cashierRepository.delete(cashier);
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleTellerDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(cashierId) //
+                .build();
+    }
+
+    /*
+     * @Override public CommandProcessingResult inwardCashToCashier (final Long
+     * cashierId, final CashierTransaction cashierTxn) { CashierTxnType txnType
+     * = CashierTxnType.INWARD_CASH_TXN; // pre save to generate id for use in
+     * office hierarchy this.cashierTxnRepository.save(cashierTxn); }
+     */
+
+    @Override
+    public CommandProcessingResult allocateCashToCashier(final Long cashierId, JsonCommand command) {
+        return doTransactionForCashier(cashierId, CashierTxnType.ALLOCATE, command); // For
+                                                                                     // fund
+                                                                                     // allocation
+                                                                                     // to
+                                                                                     // cashier
+    }
+
+    @Override
+    public CommandProcessingResult settleCashFromCashier(final Long cashierId, JsonCommand command) {
+        return doTransactionForCashier(cashierId, CashierTxnType.SETTLE, command); // For
+                                                                                   // fund
+                                                                                   // settlement
+                                                                                   // from
+                                                                                   // cashier
+    }
+
+    private CommandProcessingResult doTransactionForCashier(final Long cashierId, final CashierTxnType txnType, JsonCommand command) {
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+
+            final Cashier cashier = this.cashierRepository.findOne(cashierId);
+            if (cashier == null) { throw new CashierNotFoundException(cashierId); }
+
+            this.fromApiJsonDeserializer.validateForCashTxnForCashier(command.json());
+
+            final String entityType = command.stringValueOfParameterNamed("entityType");
+            final Long entityId = command.longValueOfParameterNamed("entityId");
+            if (entityType != null) {
+                if (entityType.equals("loan account")) {
+                    // TODO : Check if loan account exists
+                    // LoanAccount loan = null;
+                    // if (loan == null) { throw new
+                    // LoanAccountFoundException(entityId); }
+                } else if (entityType.equals("savings account")) {
+                    // TODO : Check if loan account exists
+                    // SavingsAccount savingsaccount = null;
+                    // if (savingsaccount == null) { throw new
+                    // SavingsAccountNotFoundException(entityId); }
+
+                }
+                if (entityType.equals("client")) {
+                    // TODO: Check if client exists
+                    // Client client = null;
+                    // if (client == null) { throw new
+                    // ClientNotFoundException(entityId); }
+                } else {
+                    // TODO : Invalid type handling
+                }
+            }
+
+            final CashierTransaction cashierTxn = CashierTransaction.fromJson(cashier, command);
+            cashierTxn.setTxnType(txnType.getId());
+
+            this.cashierTxnRepository.save(cashierTxn);
+
+            // Pass the journal entries
+            FinancialActivityAccount mainVaultFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper
+                    .findByFinancialActivityTypeWithNotFoundDetection(FINANCIAL_ACTIVITY.CASH_AT_MAINVAULT.getValue());
+            FinancialActivityAccount tellerCashFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper
+                    .findByFinancialActivityTypeWithNotFoundDetection(FINANCIAL_ACTIVITY.CASH_AT_TELLER.getValue());
+            GLAccount creditAccount = null;
+            GLAccount debitAccount = null;
+            if (txnType.equals(CashierTxnType.ALLOCATE)) {
+                debitAccount = tellerCashFinancialActivityAccount.getGlAccount();
+                creditAccount = mainVaultFinancialActivityAccount.getGlAccount();
+            } else if (txnType.equals(CashierTxnType.SETTLE)) {
+                debitAccount = mainVaultFinancialActivityAccount.getGlAccount();
+                creditAccount = tellerCashFinancialActivityAccount.getGlAccount();
+            }
+
+            final Office cashierOffice = cashier.getTeller().getOffice();
+
+            final Long time = System.currentTimeMillis();
+            final String uniqueVal = String.valueOf(time) + currentUser.getId() + cashierOffice.getId();
+            final String transactionId = Long.toHexString(Long.parseLong(uniqueVal));
+            ClientTransaction clientTransaction = null;
+
+            final JournalEntry debitJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment
+                                                                                               // detail
+                    debitAccount, "USD", // FIXME: Take currency code from the
+                                         // transaction
+                    transactionId, false, // manual entry
+                    cashierTxn.getTxnDate(), JournalEntryType.DEBIT, cashierTxn.getTxnAmount(), cashierTxn.getTxnNote(), // Description
+                    null, null, null, // entity Type, entityId, reference number
+                    null, null, clientTransaction); // Loan and Savings Txn
+
+            final JournalEntry creditJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment
+                                                                                                // detail
+                    creditAccount, "USD", // FIXME: Take currency code from the
+                                          // transaction
+                    transactionId, false, // manual entry
+                    cashierTxn.getTxnDate(), JournalEntryType.CREDIT, cashierTxn.getTxnAmount(), cashierTxn.getTxnNote(), // Description
+                    null, null, null, // entity Type, entityId, reference number
+                    null, null, clientTransaction); // Loan and Savings Txn
+
+            this.glJournalEntryRepository.saveAndFlush(debitJournalEntry);
+            this.glJournalEntryRepository.saveAndFlush(creditJournalEntry);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(cashier.getId()) //
+                    .withSubEntityId(cashierTxn.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleTellerDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/util/DateRange.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/util/DateRange.java
new file mode 100644
index 0000000..081c4f4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/util/DateRange.java
@@ -0,0 +1,98 @@
+/**
+ * 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.teller.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.StringTokenizer;
+
+public class DateRange {
+
+    private static final String ISO_8601_DATE_PATTERN = "yyy-MM-dd";
+    private static final String RANGE_DELIMITER = "..";
+
+    private Date startDate;
+    private Date endDate;
+
+    public DateRange() {
+        super();
+    }
+
+    public Date getStartDate() {
+        return startDate;
+    }
+
+    public void setStartDate(Date startDate) {
+        this.startDate = startDate;
+    }
+
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
+    }
+
+    public static DateRange fromString(final String dateToParse) {
+
+        final DateRange dateRange = new DateRange();
+        final SimpleDateFormat sdf = new SimpleDateFormat(DateRange.ISO_8601_DATE_PATTERN);
+        final Calendar cal = Calendar.getInstance();
+
+        final String testee;
+        if (dateToParse == null) {
+            testee = sdf.format(cal.getTime());
+        } else {
+            testee = dateToParse;
+        }
+
+        final StringTokenizer tokenizer = new StringTokenizer(testee, DateRange.RANGE_DELIMITER);
+
+        try {
+            cal.setTime(sdf.parse(tokenizer.nextToken()));
+        } catch (ParseException ex) {
+
+        }
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+
+        dateRange.setStartDate(cal.getTime());
+
+        if (tokenizer.hasMoreTokens()) {
+            try {
+                cal.setTime(sdf.parse(tokenizer.nextToken()));
+            } catch (ParseException ex) {
+
+            }
+        }
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        cal.set(Calendar.MILLISECOND, 999);
+
+        dateRange.setEndDate(cal.getTime());
+
+        return dateRange;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiConstants.java
new file mode 100755
index 0000000..5eebbe9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiConstants.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.organisation.workingdays.api;

+

+import java.util.Arrays;

+import java.util.HashSet;

+import java.util.Set;

+

+public class WorkingDaysApiConstants {

+

+    public static final String WORKING_DAYS_RESOURCE_NAME = "workingdays";

+

+    // request parameters

+    public static final String recurrence = "recurrence";

+

+    public static final String repayment_rescheduling_enum = "repaymentRescheduleType";

+

+    public static final String idParamName = "id";

+

+    public static final String rescheduleRepaymentTemplate = "rescheduleRepaymentTemplate";

+    public static final String localeParamName = "locale";

+    public static final String extendTermForDailyRepayments = "extendTermForDailyRepayments";

+

+

+

+    public static final Set<String> WORKING_DAYS_CREATE_OR_UPDATE_REQUEST_DATA_PARAMETERS =new HashSet<>(Arrays.asList(

+            recurrence,repayment_rescheduling_enum,localeParamName,extendTermForDailyRepayments

+    ));

+    public static final Set<String> WORKING_DAYS_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,

+            recurrence,repayment_rescheduling_enum,extendTermForDailyRepayments

+    ));

+

+    public static final Set<String> WORKING_DAYS_TEMPLATE_PARAMETERS = new HashSet<>(Arrays.asList(rescheduleRepaymentTemplate));

+}

diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java
new file mode 100755
index 0000000..65c8dcc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java
@@ -0,0 +1,104 @@
+/**

+ * 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.api;

+

+import javax.ws.rs.Consumes;

+import javax.ws.rs.GET;

+import javax.ws.rs.PUT;

+import javax.ws.rs.Path;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.Context;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.UriInfo;

+

+import org.apache.fineract.commands.domain.CommandWrapper;

+import org.apache.fineract.commands.service.CommandWrapperBuilder;

+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;

+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;

+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;

+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;

+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;

+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;

+import org.apache.fineract.organisation.workingdays.data.WorkingDaysData;

+import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformService;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.context.annotation.Scope;

+import org.springframework.stereotype.Component;

+

+@Path("/workingdays")

+@Component

+@Scope("singleton")

+public class WorkingDaysApiResource {

+

+    private final DefaultToApiJsonSerializer<WorkingDaysData> toApiJsonSerializer;

+    private final WorkingDaysReadPlatformService workingDaysReadPlatformService;

+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;

+    private final PlatformSecurityContext context;

+    private final ApiRequestParameterHelper apiRequestParameterHelper;

+

+    @Autowired

+    public WorkingDaysApiResource(DefaultToApiJsonSerializer<WorkingDaysData> toApiJsonSerializer,

+            WorkingDaysReadPlatformService workingDaysReadPlatformService,

+            PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, PlatformSecurityContext context,

+            ApiRequestParameterHelper apiRequestParameterHelper) {

+        this.toApiJsonSerializer = toApiJsonSerializer;

+        this.workingDaysReadPlatformService = workingDaysReadPlatformService;

+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;

+        this.context = context;

+        this.apiRequestParameterHelper = apiRequestParameterHelper;

+    }

+

+    @GET

+    @Consumes({ MediaType.APPLICATION_JSON })

+    @Produces({ MediaType.APPLICATION_JSON })

+    public String retrieveAll(@Context final UriInfo uriInfo) {

+        this.context.authenticatedUser().validateHasReadPermission(WorkingDaysApiConstants.WORKING_DAYS_RESOURCE_NAME);

+        final WorkingDaysData workingDaysData = this.workingDaysReadPlatformService.retrieve();

+

+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());

+        return this.toApiJsonSerializer.serialize(settings, workingDaysData);

+    }

+

+    @PUT

+    @Consumes({ MediaType.APPLICATION_JSON })

+    @Produces({ MediaType.APPLICATION_JSON })

+    public String update(final String jsonRequestBody) {

+

+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateWorkingDays().withJson(jsonRequestBody).build();

+

+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);

+

+        return this.toApiJsonSerializer.serialize(result);

+    }

+

+    @GET

+    @Path("/template")

+    @Consumes({ MediaType.APPLICATION_JSON })

+    @Produces({ MediaType.APPLICATION_JSON })

+    public String template(@Context final UriInfo uriInfo) {

+        this.context.authenticatedUser().validateHasReadPermission(WorkingDaysApiConstants.WORKING_DAYS_RESOURCE_NAME);

+

+        final WorkingDaysData repaymentRescheduleOptions = this.workingDaysReadPlatformService.repaymentRescheduleType();

+

+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());

+        return this.toApiJsonSerializer.serialize(settings, repaymentRescheduleOptions,

+                WorkingDaysApiConstants.WORKING_DAYS_TEMPLATE_PARAMETERS);

+    }

+

+}

diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java
new file mode 100755
index 0000000..271a5a6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java
@@ -0,0 +1,81 @@
+/**

+ * 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 java.lang.reflect.Type;

+import java.util.ArrayList;

+import java.util.List;

+import java.util.Map;

+

+import org.apache.commons.lang.StringUtils;

+import org.apache.fineract.infrastructure.core.data.ApiParameterError;

+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;

+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;

+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;

+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;

+import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Component;

+

+import com.google.gson.JsonElement;

+import com.google.gson.reflect.TypeToken;

+

+@Component

+public class WorkingDayValidator {

+

+    private final FromJsonHelper fromApiJsonHelper;

+

+    @Autowired

+    public WorkingDayValidator(FromJsonHelper fromApiJsonHelper) {

+        this.fromApiJsonHelper = fromApiJsonHelper;

+    }

+

+    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,

+                WorkingDaysApiConstants.WORKING_DAYS_CREATE_OR_UPDATE_REQUEST_DATA_PARAMETERS);

+        final JsonElement element = this.fromApiJsonHelper.parse(json);

+

+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();

+

+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)

+                .resource(WorkingDaysApiConstants.WORKING_DAYS_RESOURCE_NAME);

+

+        final String recurrence = this.fromApiJsonHelper.extractStringNamed(WorkingDaysApiConstants.recurrence, element);

+        baseDataValidator.reset().parameter(WorkingDaysApiConstants.recurrence).value(recurrence).notNull();

+

+        final Integer repaymentRescheduleType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("repaymentRescheduleType", element);

+        baseDataValidator.reset().parameter("repaymentRescheduleType").value(repaymentRescheduleType).ignoreIfNull().inMinMaxRange(1, 4);

+

+        final Boolean extendTermForDailyRepayments = this.fromApiJsonHelper.extractBooleanNamed("extendTermForDailyRepayments", element);

+        baseDataValidator.reset().parameter(WorkingDaysApiConstants.extendTermForDailyRepayments).value(extendTermForDailyRepayments).ignoreIfNull().validateForBooleanValue();

+        

+        throwExceptionIfValidationWarningsExist(dataValidationErrors);

+

+    }

+

+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {

+        if (!dataValidationErrors.isEmpty()) {

+            //

+            throw new PlatformApiDataValidationException(dataValidationErrors);

+        }

+    }

+}

diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java
new file mode 100755
index 0000000..affdc03
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java
@@ -0,0 +1,58 @@
+/**

+ * 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.apache.fineract.infrastructure.core.data.EnumOptionData;

+

+import java.util.Collection;

+

+public class WorkingDaysData {

+

+    @SuppressWarnings("unused")

+    private final Long id;

+    @SuppressWarnings("unused")

+    private final String recurrence;

+

+    @SuppressWarnings("unused")

+    private final EnumOptionData repaymentRescheduleType;

+    

+    @SuppressWarnings("unused")

+    private final Boolean extendTermForDailyRepayments;

+

+    // template date

+    @SuppressWarnings("unused")

+    private final Collection<EnumOptionData> repaymentRescheduleOptions;

+

+    public WorkingDaysData(Long id, String recurrence, EnumOptionData repaymentRescheduleType, Boolean extendTermForDailyRepayments) {

+        this.id = id;

+        this.recurrence = recurrence;

+        this.repaymentRescheduleType = repaymentRescheduleType;

+        this.repaymentRescheduleOptions = null;

+        this.extendTermForDailyRepayments = extendTermForDailyRepayments;

+    }

+

+    public WorkingDaysData(Long id, String recurrence, EnumOptionData repaymentRescheduleType,

+            Collection<EnumOptionData> repaymentRescheduleOptions, Boolean extendTermForDailyRepayments) {

+        this.id = id;

+        this.recurrence = recurrence;

+        this.repaymentRescheduleType = repaymentRescheduleType;

+        this.repaymentRescheduleOptions = repaymentRescheduleOptions;

+        this.extendTermForDailyRepayments = extendTermForDailyRepayments;

+    }

+}

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
new file mode 100644
index 0000000..0194cd7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/RepaymentRescheduleType.java
@@ -0,0 +1,84 @@
+/**
+ * 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.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum RepaymentRescheduleType {
+
+    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");
+
+    private final Integer value;
+    private final String code;
+
+    private RepaymentRescheduleType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, RepaymentRescheduleType> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final RepaymentRescheduleType entityType : RepaymentRescheduleType.values()) {
+            if (i == 0) {
+                minValue = entityType.value;
+            }
+            intToEnumMap.put(entityType.value, entityType);
+            if (minValue >= entityType.value) {
+                minValue = entityType.value;
+            }
+            if (maxValue < entityType.value) {
+                maxValue = entityType.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static RepaymentRescheduleType fromInt(final int i) {
+        final RepaymentRescheduleType RepaymentRescheduleType = intToEnumMap.get(Integer.valueOf(i));
+        return RepaymentRescheduleType;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java
new file mode 100755
index 0000000..adeba4a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java
@@ -0,0 +1,102 @@
+/**
+ * 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.domain;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Entity
+@Table(name = "m_working_days")
+public class WorkingDays extends AbstractPersistable<Long> {
+
+    @Column(name = "recurrence", length = 100, nullable = true)
+    private String recurrence;
+
+    @Column(name = "repayment_rescheduling_enum", nullable = false)
+    private Integer repaymentReschedulingType;
+
+    @Column(name = "extend_term_daily_repayments", nullable = false)
+    private Boolean extendTermForDailyRepayments;
+    
+    protected WorkingDays() {
+
+    }
+
+    protected WorkingDays(final String recurrence, final Integer repaymentReschedulingType, final Boolean extendTermForDailyRepayments ) {
+        this.recurrence = recurrence;
+        this.repaymentReschedulingType = repaymentReschedulingType;
+        this.extendTermForDailyRepayments = extendTermForDailyRepayments;
+    }
+
+    /**
+     * @return the recurrence
+     */
+    public String getRecurrence() {
+        return this.recurrence;
+    }
+
+    /**
+     * @return the repaymentReschedulingType
+     */
+    public Integer getRepaymentReschedulingType() {
+        return this.repaymentReschedulingType;
+    }
+
+    public void setRepaymentReschedulingType(Integer repaymentReschedulingType) {
+        this.repaymentReschedulingType = repaymentReschedulingType;
+    }
+    
+    public Boolean getExtendTermForDailyRepayments(){
+        return this.extendTermForDailyRepayments;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String recurrenceParamName = "recurrence";
+        if (command.isChangeInStringParameterNamed(recurrenceParamName, this.recurrence)) {
+            final String newValue = command.stringValueOfParameterNamed(recurrenceParamName);
+            actualChanges.put(recurrenceParamName, newValue);
+            this.recurrence = newValue;
+        }
+
+        final String repaymentRescheduleTypeParamName = "repaymentRescheduleType";
+        if (command.isChangeInIntegerParameterNamed(repaymentRescheduleTypeParamName, this.repaymentReschedulingType)) {
+            final Integer newValue =command.integerValueOfParameterNamed(repaymentRescheduleTypeParamName);
+            actualChanges.put(repaymentRescheduleTypeParamName,  WorkingDaysEnumerations.workingDaysStatusType(newValue));
+            this.repaymentReschedulingType = RepaymentRescheduleType.fromInt(newValue).getValue();
+        }
+        
+        if(command.isChangeInBooleanParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments, this.extendTermForDailyRepayments)){
+            final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments);
+            actualChanges.put(WorkingDaysApiConstants.extendTermForDailyRepayments, newValue);
+            this.extendTermForDailyRepayments = newValue;
+        }
+        return actualChanges;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java
new file mode 100755
index 0000000..8b2b752
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java
@@ -0,0 +1,60 @@
+/**

+ * 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.domain;

+

+

+import org.apache.fineract.infrastructure.core.data.EnumOptionData;

+

+public class WorkingDaysEnumerations {

+

+    public static EnumOptionData workingDaysStatusType(final int id) {

+        return repaymentRescheduleType(RepaymentRescheduleType.fromInt(id));

+    }

+

+    public static EnumOptionData repaymentRescheduleType(final RepaymentRescheduleType type){

+        EnumOptionData optionData = null;

+        switch(type){

+            case INVALID:

+                optionData = new EnumOptionData(RepaymentRescheduleType.INVALID.getValue().longValue(),RepaymentRescheduleType.INVALID.getCode(),

+                        "invalid");

+            break;

+

+            case SAME_DAY:

+                optionData = new EnumOptionData(RepaymentRescheduleType.SAME_DAY.getValue().longValue(),RepaymentRescheduleType.SAME_DAY.getCode(),

+                        "same day");

+

+                break;

+            case MOVE_TO_NEXT_WORKING_DAY:

+                optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue().longValue(),RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getCode(),

+                        "move to next working day");

+                break;

+

+            case MOVE_TO_NEXT_REPAYMENT_MEETING_DAY:

+                optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getValue().longValue(),RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getCode(),

+                        "move to next repayment meeting day");

+                break;

+            case MOVE_TO_PREVIOUS_WORKING_DAY:

+                optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_PREVIOUS_WORKING_DAY.getValue().longValue(),RepaymentRescheduleType.MOVE_TO_PREVIOUS_WORKING_DAY.getCode(),

+                        "move to previous working day");

+                break;

+        }

+

+        return optionData;

+    }

+}

diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysRepository.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysRepository.java
new file mode 100644
index 0000000..ce9511d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysRepository.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.organisation.workingdays.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface WorkingDaysRepository extends JpaRepository<WorkingDays, Long>, JpaSpecificationExecutor<WorkingDays> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java
new file mode 100644
index 0000000..7267772
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.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.organisation.workingdays.domain;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.workingdays.exception.WorkingDaysNotFoundException;
+import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link WorkingDaysRepository} that adds NULL checking and Error
+ * handling capabilities
+ * </p>
+ */
+@Service
+public class WorkingDaysRepositoryWrapper {
+
+    private final WorkingDaysRepository repository;
+
+    @Autowired
+    public WorkingDaysRepositoryWrapper(final WorkingDaysRepository repository) {
+        this.repository = repository;
+    }
+
+    public WorkingDays findOne() {
+        final List<WorkingDays> workingDaysList = this.repository.findAll();
+
+        if (workingDaysList == null || workingDaysList.isEmpty()) { throw new WorkingDaysNotFoundException(); }
+        return workingDaysList.get(0);
+    }
+
+    public void save(final WorkingDays workingDays) {
+        this.repository.save(workingDays);
+    }
+
+    public void saveAndFlush(final WorkingDays workingDays) {
+        this.repository.saveAndFlush(workingDays);
+    }
+
+    public void delete(final WorkingDays workingDays) {
+        this.repository.delete(workingDays);
+    }
+
+    public boolean isWorkingDay(LocalDate transactionDate) {
+        final WorkingDays workingDays = findOne();
+        return WorkingDaysUtil.isWorkingDay(workingDays, transactionDate);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/exception/WorkingDaysNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/exception/WorkingDaysNotFoundException.java
new file mode 100755
index 0000000..d090dab
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/exception/WorkingDaysNotFoundException.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.organisation.workingdays.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when Calendar resources are not found.
+ */
+public class WorkingDaysNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public WorkingDaysNotFoundException() {
+        super("error.msg.working.days.not.configured", "Must configure the Working days for the organisation.");
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java
new file mode 100755
index 0000000..160758a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java
@@ -0,0 +1,46 @@
+/**

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements. See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ * http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied. See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.fineract.organisation.workingdays.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.organisation.workingdays.service.WorkingDaysWritePlatformService;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Service;

+import org.springframework.transaction.annotation.Transactional;

+

+@Service

+@CommandType(entity = "WORKINGDAYS", action = "UPDATE")

+public class UpdateWorkingDaysCommandHandler  implements NewCommandSourceHandler {

+

+    private final WorkingDaysWritePlatformService workingDaysWritePlatformService;

+

+    @Autowired

+    public UpdateWorkingDaysCommandHandler(final WorkingDaysWritePlatformService workingDaysWritePlatformService) {

+        this.workingDaysWritePlatformService = workingDaysWritePlatformService;

+    }

+    @Transactional

+    @Override

+    public CommandProcessingResult processCommand(JsonCommand command) {

+        return this.workingDaysWritePlatformService.updateWorkingDays(command);

+    }

+}

diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformService.java
new file mode 100755
index 0000000..90ba1db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformService.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.organisation.workingdays.service;

+

+import org.apache.fineract.organisation.workingdays.data.WorkingDaysData;

+

+public interface WorkingDaysReadPlatformService {

+

+    WorkingDaysData retrieve();

+

+    WorkingDaysData repaymentRescheduleType();

+}

diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java
new file mode 100755
index 0000000..085e42e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java
@@ -0,0 +1,98 @@
+/**

+ * 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.service;

+

+import java.sql.ResultSet;

+import java.sql.SQLException;

+import java.util.Arrays;

+import java.util.Collection;

+

+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.organisation.workingdays.data.WorkingDaysData;

+import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;

+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysEnumerations;

+import org.apache.fineract.organisation.workingdays.exception.WorkingDaysNotFoundException;

+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 WorkingDaysReadPlatformServiceImpl implements WorkingDaysReadPlatformService {

+

+    private final JdbcTemplate jdbcTemplate;

+

+    @Autowired

+    public WorkingDaysReadPlatformServiceImpl(final RoutingDataSource dataSource) {

+        this.jdbcTemplate = new JdbcTemplate(dataSource);

+    }

+

+    private static final class WorkingDaysMapper implements RowMapper<WorkingDaysData> {

+

+        private final String schema;

+

+        public WorkingDaysMapper() {

+            final StringBuilder sqlBuilder = new StringBuilder(100);

+            sqlBuilder.append("w.id as id,w.recurrence as recurrence,w.repayment_rescheduling_enum as status_enum,");

+            sqlBuilder.append("w.extend_term_daily_repayments as extendTermForDailyRepayments ");

+            sqlBuilder.append("from m_working_days w");

+

+            this.schema = sqlBuilder.toString();

+        }

+

+        public String schema() {

+            return this.schema;

+        }

+

+        @Override

+        public WorkingDaysData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {

+            final Long id = rs.getLong("id");

+            final String recurrence = rs.getString("recurrence");

+            final Integer statusEnum = JdbcSupport.getInteger(rs, "status_enum");

+            final EnumOptionData status = WorkingDaysEnumerations.workingDaysStatusType(statusEnum);

+            final Boolean extendTermForDailyRepayments = rs.getBoolean("extendTermForDailyRepayments");

+

+            return new WorkingDaysData(id, recurrence, status,extendTermForDailyRepayments);

+        }

+    }

+

+    @Override

+    public WorkingDaysData retrieve() {

+        try {

+            final WorkingDaysMapper rm = new WorkingDaysMapper();

+            final String sql = " select " + rm.schema();

+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] {});

+        } catch (final EmptyResultDataAccessException e) {

+            throw new WorkingDaysNotFoundException();

+        }

+    }

+

+    @Override

+    public WorkingDaysData repaymentRescheduleType() {

+        Collection<EnumOptionData> repaymentRescheduleOptions = Arrays.asList(

+                WorkingDaysEnumerations.repaymentRescheduleType(RepaymentRescheduleType.SAME_DAY),

+                WorkingDaysEnumerations.repaymentRescheduleType(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY),

+                WorkingDaysEnumerations.repaymentRescheduleType(RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY),

+                WorkingDaysEnumerations.repaymentRescheduleType(RepaymentRescheduleType.MOVE_TO_PREVIOUS_WORKING_DAY));

+        return new WorkingDaysData(null, null, null, repaymentRescheduleOptions, null);

+    }

+}

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
new file mode 100644
index 0000000..ac0fc39
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysUtil.java
@@ -0,0 +1,59 @@
+/**
+ * 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.service;
+
+import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.joda.time.LocalDate;
+
+public class WorkingDaysUtil {
+
+    public static LocalDate getOffSetDateIfNonWorkingDay(final LocalDate date, final LocalDate nextMeetingDate,
+            final WorkingDays workingDays) {
+
+        // If date is not a non working day then return date.
+        if (isWorkingDay(workingDays, date)) { return date; }
+
+        final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(workingDays.getRepaymentReschedulingType());
+
+        switch (rescheduleType) {
+            case INVALID:
+                return date;
+            case SAME_DAY:
+                return date;
+            case MOVE_TO_NEXT_WORKING_DAY:
+                return getOffSetDateIfNonWorkingDay(date.plusDays(1), nextMeetingDate, workingDays);
+            case MOVE_TO_NEXT_REPAYMENT_MEETING_DAY:
+                return nextMeetingDate;
+            case MOVE_TO_PREVIOUS_WORKING_DAY:
+                return getOffSetDateIfNonWorkingDay(date.minusDays(1), nextMeetingDate, workingDays);
+            default:
+                return date;
+        }
+    }
+
+    public static boolean isWorkingDay(final WorkingDays workingDays, final LocalDate date) {
+        return CalendarUtils.isValidRedurringDate(workingDays.getRecurrence(), date, date);
+    }
+    
+    public static boolean isNonWorkingDay(final WorkingDays workingDays, final LocalDate date) {
+        return !isWorkingDay(workingDays, date);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java
new file mode 100755
index 0000000..dcdb3d1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java
@@ -0,0 +1,27 @@
+/**
+ * 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.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface WorkingDaysWritePlatformService {
+
+    CommandProcessingResult updateWorkingDays(JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..b3acd50
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,78 @@
+/**
+ * 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.service;
+
+import java.text.ParseException;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants;
+import org.apache.fineract.organisation.workingdays.data.WorkingDayValidator;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import net.fortuna.ical4j.model.ValidationException;
+import net.fortuna.ical4j.model.property.RRule;
+
+@Service
+public class WorkingDaysWritePlatformServiceJpaRepositoryImpl implements WorkingDaysWritePlatformService {
+
+    private final WorkingDaysRepositoryWrapper daysRepositoryWrapper;
+    private final WorkingDayValidator fromApiJsonDeserializer;
+
+    @Autowired
+    public WorkingDaysWritePlatformServiceJpaRepositoryImpl(final WorkingDaysRepositoryWrapper daysRepositoryWrapper,
+            final WorkingDayValidator fromApiJsonDeserializer) {
+        this.daysRepositoryWrapper = daysRepositoryWrapper;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateWorkingDays(JsonCommand command) {
+        String recurrence = "";
+        RRule rrule = null;
+        try {
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+            final WorkingDays workingDays = this.daysRepositoryWrapper.findOne();
+
+            recurrence = command.stringValueOfParameterNamed(WorkingDaysApiConstants.recurrence);
+            rrule = new RRule(recurrence);
+            rrule.validate();
+
+            Map<String, Object> changes = workingDays.update(command);
+            this.daysRepositoryWrapper.saveAndFlush(workingDays);
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(workingDays.getId()).with(changes)
+                    .build();
+        } catch (final ValidationException e) {
+            throw new PlatformDataIntegrityException("error.msg.invalid.recurring.rule",
+                    "The Recurring Rule value: " + recurrence + " is not valid.", "recurrence", recurrence);
+        } catch (final IllegalArgumentException | ParseException e) {
+            throw new PlatformDataIntegrityException("error.msg.recurring.rule.parsing.error",
+                    "Error in passing the Recurring Rule value: " + recurrence, "recurrence", e.getMessage());
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/AccountDetailConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/AccountDetailConstants.java
new file mode 100755
index 0000000..3a2d07c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/AccountDetailConstants.java
@@ -0,0 +1,40 @@
+/**
+ * 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.account;
+
+
+public class AccountDetailConstants {
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // savings product and account parameters
+    public static final String idParamName = "id";
+    public static final String fromOfficeIdParamName = "fromOfficeId";
+    public static final String fromClientIdParamName = "fromClientId";
+    public static final String fromAccountIdParamName = "fromAccountId";
+    public static final String fromAccountTypeParamName = "fromAccountType";
+    public static final String toOfficeIdParamName = "toOfficeId";
+    public static final String toClientIdParamName = "toClientId";
+    public static final String toAccountIdParamName = "toAccountId";
+    public static final String toAccountTypeParamName = "toAccountType";
+    public static final String transferTypeParamName = "transferType";
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java
new file mode 100644
index 0000000..0c6b013
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java
@@ -0,0 +1,80 @@
+/**
+ * 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.account;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public enum PortfolioAccountType {
+
+    INVALID(0, "accountType.invalid"), //
+    LOAN(1, "accountType.loan"), //
+    SAVINGS(2, "accountType.savings");
+
+    private final Integer value;
+    private final String code;
+
+    private PortfolioAccountType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final PortfolioAccountType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public static PortfolioAccountType fromInt(final Integer type) {
+
+        PortfolioAccountType enumType = PortfolioAccountType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 1:
+                    enumType = LOAN;
+                break;
+                case 2:
+                    enumType = SAVINGS;
+                break;
+            }
+        }
+        return enumType;
+    }
+
+    public boolean isSavingsAccount() {
+        return this.value == Integer.valueOf(2);
+    }
+
+    public boolean isLoanAccount() {
+        return this.value == Integer.valueOf(1);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiConstants.java
new file mode 100644
index 0000000..08cc368
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiConstants.java
@@ -0,0 +1,51 @@
+/**
+ * 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.account.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.account.AccountDetailConstants;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+
+public class AccountTransfersApiConstants {
+
+    public static final String ACCOUNT_TRANSFER_RESOURCE_NAME = "accounttransfer";
+
+    // transaction parameters
+    public static final String transferDateParamName = "transferDate";
+    public static final String transferAmountParamName = "transferAmount";
+    public static final String transferDescriptionParamName = "transferDescription";
+
+    public static final Set<String> REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.localeParamName,
+            AccountDetailConstants.dateFormatParamName, AccountDetailConstants.fromOfficeIdParamName,
+            AccountDetailConstants.fromClientIdParamName, AccountDetailConstants.fromAccountTypeParamName,
+            AccountDetailConstants.fromAccountIdParamName, AccountDetailConstants.toOfficeIdParamName,
+            AccountDetailConstants.toClientIdParamName, AccountDetailConstants.toAccountTypeParamName,
+            AccountDetailConstants.toAccountIdParamName, transferDateParamName, transferAmountParamName, transferDescriptionParamName));
+
+    /**
+     * These parameters will match the class level parameters of
+     * {@link AccountTransferData}. Where possible, we try to get response
+     * parameters to match those of request parameters.
+     */
+    public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.idParamName,
+            transferDescriptionParamName, "currency"));
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java
new file mode 100644
index 0000000..84fa436
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java
@@ -0,0 +1,167 @@
+/**
+ * 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.account.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.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.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/accounttransfers")
+@Component
+@Scope("singleton")
+public class AccountTransfersApiResource {
+
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<AccountTransferData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
+
+    @Autowired
+    public AccountTransfersApiResource(final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<AccountTransferData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@QueryParam("fromOfficeId") final Long fromOfficeId, @QueryParam("fromClientId") final Long fromClientId,
+            @QueryParam("fromAccountId") final Long fromAccountId, @QueryParam("fromAccountType") final Integer fromAccountType,
+            @QueryParam("toOfficeId") final Long toOfficeId, @QueryParam("toClientId") final Long toClientId,
+            @QueryParam("toAccountId") final Long toAccountId, @QueryParam("toAccountType") final Integer toAccountType,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+        final AccountTransferData transferData = this.accountTransfersReadPlatformService.retrieveTemplate(fromOfficeId, fromClientId,
+                fromAccountId, fromAccountType, toOfficeId, toClientId, toAccountId, toAccountType);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, transferData, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createAccountTransfer().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("externalId") final String externalId, @QueryParam("offset") final Integer offset,
+            @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
+            @QueryParam("sortOrder") final String sortOrder,@QueryParam("accountDetailId") final Long accountDetailId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+        final SearchParameters searchParameters = SearchParameters.forAccountTransfer(sqlSearch, externalId, offset, limit, orderBy,
+                sortOrder);
+
+        final Page<AccountTransferData> transfers = this.accountTransfersReadPlatformService.retrieveAll(searchParameters, accountDetailId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, transfers, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{transferId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("transferId") final Long transferId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+        final AccountTransferData transfer = this.accountTransfersReadPlatformService.retrieveOne(transferId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, transfer, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+    
+    @GET
+    @Path("templateRefundByTransfer")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String templateRefundByTransfer(@QueryParam("fromOfficeId") final Long fromOfficeId, @QueryParam("fromClientId") final Long fromClientId,
+            @QueryParam("fromAccountId") final Long fromAccountId, @QueryParam("fromAccountType") final Integer fromAccountType,
+            @QueryParam("toOfficeId") final Long toOfficeId, @QueryParam("toClientId") final Long toClientId,
+            @QueryParam("toAccountId") final Long toAccountId, @QueryParam("toAccountType") final Integer toAccountType,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+        final AccountTransferData transferData = this.accountTransfersReadPlatformService.retrieveRefundByTransferTemplate(fromOfficeId, fromClientId,
+                fromAccountId, fromAccountType, toOfficeId, toClientId, toAccountId, toAccountType);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, transferData, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+    
+    @POST
+    @Path("refundByTransfer")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String templateRefundByTransferPost(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().refundByTransfer().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiConstants.java
new file mode 100755
index 0000000..fae6cbd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiConstants.java
@@ -0,0 +1,63 @@
+/**
+ * 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.account.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.account.AccountDetailConstants;
+
+public class StandingInstructionApiConstants {
+
+    public static final String STANDING_INSTRUCTION_RESOURCE_NAME = "standinginstruction";
+
+    public static final String nameParamName = "name";
+    public static final String priorityParamName = "priority";
+    public static final String instructionTypeParamName = "instructionType";
+    public static final String statusParamName = "status";
+    public static final String amountParamName = "amount";
+    public static final String validFromParamName = "validFrom";
+    public static final String validTillParamName = "validTill";
+    public static final String recurrenceTypeParamName = "recurrenceType";
+    public static final String recurrenceFrequencyParamName = "recurrenceFrequency";
+    public static final String recurrenceIntervalParamName = "recurrenceInterval";
+    public static final String recurrenceOnMonthDayParamName = "recurrenceOnMonthDay";
+    public static final String monthDayFormatParamName = "monthDayFormat";
+
+    public static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+            AccountDetailConstants.localeParamName, AccountDetailConstants.dateFormatParamName,
+            AccountDetailConstants.fromOfficeIdParamName, AccountDetailConstants.fromClientIdParamName,
+            AccountDetailConstants.fromAccountTypeParamName, AccountDetailConstants.fromAccountIdParamName,
+            AccountDetailConstants.toOfficeIdParamName, AccountDetailConstants.toClientIdParamName,
+            AccountDetailConstants.toAccountTypeParamName, AccountDetailConstants.toAccountIdParamName,
+            AccountDetailConstants.transferTypeParamName, priorityParamName, instructionTypeParamName, statusParamName, amountParamName,
+            validFromParamName, validTillParamName, recurrenceTypeParamName, recurrenceFrequencyParamName, recurrenceIntervalParamName,
+            recurrenceOnMonthDayParamName, nameParamName, monthDayFormatParamName));
+
+    public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+            AccountDetailConstants.localeParamName, AccountDetailConstants.dateFormatParamName, priorityParamName,
+            instructionTypeParamName, statusParamName, amountParamName, validFromParamName, validTillParamName, recurrenceTypeParamName,
+            recurrenceFrequencyParamName, recurrenceIntervalParamName, recurrenceOnMonthDayParamName, monthDayFormatParamName));
+
+    public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.idParamName,
+            nameParamName, priorityParamName, instructionTypeParamName, statusParamName, AccountDetailConstants.transferTypeParamName,
+            validFromParamName, validTillParamName));
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java
new file mode 100755
index 0000000..2d780e0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java
@@ -0,0 +1,211 @@
+/**
+ * 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.account.api;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+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.QueryParam;
+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.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
+import org.apache.fineract.portfolio.account.data.StandingInstructionData;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/standinginstructions")
+@Component
+@Scope("singleton")
+public class StandingInstructionApiResource {
+
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<StandingInstructionData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
+    private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
+
+    @Autowired
+    public StandingInstructionApiResource(final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<StandingInstructionData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final StandingInstructionReadPlatformService standingInstructionReadPlatformService,
+            final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.standingInstructionReadPlatformService = standingInstructionReadPlatformService;
+        this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@QueryParam("fromOfficeId") final Long fromOfficeId, @QueryParam("fromClientId") final Long fromClientId,
+            @QueryParam("fromAccountId") final Long fromAccountId, @QueryParam("fromAccountType") final Integer fromAccountType,
+            @QueryParam("toOfficeId") final Long toOfficeId, @QueryParam("toClientId") final Long toClientId,
+            @QueryParam("toAccountId") final Long toAccountId, @QueryParam("toAccountType") final Integer toAccountType,
+            @QueryParam("transferType") final Integer transferType, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
+
+        final StandingInstructionData standingInstructionData = this.standingInstructionReadPlatformService.retrieveTemplate(fromOfficeId,
+                fromClientId, fromAccountId, fromAccountType, toOfficeId, toClientId, toAccountId, toAccountType, transferType);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, standingInstructionData,
+                StandingInstructionApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createStandingInstruction().withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{standingInstructionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("standingInstructionId") final Long standingInstructionId, final String apiRequestBodyAsJson,
+            @QueryParam("command") final String commandParam) {
+
+        CommandWrapper commandRequest = null;
+        if (is(commandParam, "update")) {
+            commandRequest = new CommandWrapperBuilder().updateStandingInstruction(standingInstructionId).withJson(apiRequestBodyAsJson)
+                    .build();
+        } else if (is(commandParam, "delete")) {
+            commandRequest = new CommandWrapperBuilder().deleteStandingInstruction(standingInstructionId).withJson(apiRequestBodyAsJson)
+                    .build();
+        }
+
+        if (commandRequest == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
+        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);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("externalId") final String externalId, @QueryParam("offset") final Integer offset,
+            @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
+            @QueryParam("sortOrder") final String sortOrder, @QueryParam("transferType") final Integer transferType,
+            @QueryParam("clientName") final String clientName, @QueryParam("clientId") final Long clientId,
+            @QueryParam("fromAccountId") final Long fromAccount, @QueryParam("fromAccountType") final Integer fromAccountType) {
+
+        this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
+
+        final SearchParameters searchParameters = SearchParameters.forAccountTransfer(sqlSearch, externalId, offset, limit, orderBy,
+                sortOrder);
+
+        final Date startDateRange = null;
+        final Date endDateRange = null;
+        StandingInstructionDTO standingInstructionDTO = new StandingInstructionDTO(searchParameters, transferType, clientName, clientId,
+                fromAccount, fromAccountType, startDateRange, endDateRange);
+
+        final Page<StandingInstructionData> transfers = this.standingInstructionReadPlatformService.retrieveAll(standingInstructionDTO);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, transfers, StandingInstructionApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{standingInstructionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("standingInstructionId") final Long standingInstructionId, @Context final UriInfo uriInfo,
+            @QueryParam("sqlSearch") final String sqlSearch, @QueryParam("externalId") final String externalId,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
+
+        this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
+
+        StandingInstructionData standingInstructionData = this.standingInstructionReadPlatformService.retrieveOne(standingInstructionId);
+        final SearchParameters searchParameters = SearchParameters.forAccountTransfer(sqlSearch, externalId, offset, limit, orderBy,
+                sortOrder);
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        Page<AccountTransferData> transfers = null;
+        if (!associationParameters.isEmpty()) {
+            if (associationParameters.contains("all")) {
+                associationParameters.addAll(Arrays.asList("transactions", "template"));
+            }
+            if (associationParameters.contains("transactions")) {
+                transfers = this.accountTransfersReadPlatformService.retrieveByStandingInstruction(standingInstructionId, searchParameters);
+                standingInstructionData = StandingInstructionData.withTransferData(standingInstructionData, transfers);
+            }
+            if (associationParameters.contains("template")) {
+                final StandingInstructionData templateData = this.standingInstructionReadPlatformService.retrieveTemplate(
+                        standingInstructionData.fromClient().officeId(), standingInstructionData.fromClient().id(), standingInstructionData
+                                .fromAccount().accountId(), standingInstructionData.fromAccountType().getValue(), standingInstructionData
+                                .toClient().officeId(), standingInstructionData.toClient().id(), standingInstructionData.toAccount()
+                                .accountId(), standingInstructionData.toAccountType().getValue(), standingInstructionData.transferType()
+                                .getValue());
+                standingInstructionData = StandingInstructionData.withTemplateData(standingInstructionData, templateData);
+            }
+        }
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, standingInstructionData,
+                StandingInstructionApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java
new file mode 100755
index 0000000..616cf05
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java
@@ -0,0 +1,101 @@
+/**
+ * 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.account.api;
+
+import java.util.Date;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.accounting.journalentry.api.DateParam;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
+import org.apache.fineract.portfolio.account.data.StandingInstructionHistoryData;
+import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/standinginstructionrunhistory")
+@Component
+@Scope("singleton")
+public class StandingInstructionHistoryApiResource {
+
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<StandingInstructionHistoryData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService;
+
+    @Autowired
+    public StandingInstructionHistoryApiResource(final PlatformSecurityContext context,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService,
+            final DefaultToApiJsonSerializer<StandingInstructionHistoryData> toApiJsonSerializer) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.standingInstructionHistoryReadPlatformService = standingInstructionHistoryReadPlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("externalId") final String externalId, @QueryParam("offset") final Integer offset,
+            @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
+            @QueryParam("sortOrder") final String sortOrder, @QueryParam("transferType") final Integer transferType,
+            @QueryParam("clientName") final String clientName, @QueryParam("clientId") final Long clientId,
+            @QueryParam("fromAccountId") final Long fromAccount, @QueryParam("fromAccountType") final Integer fromAccountType,
+            @QueryParam("locale") final String locale, @QueryParam("dateFormat") final String dateFormat,
+            @QueryParam("fromDate") final DateParam fromDateParam, @QueryParam("toDate") final DateParam toDateParam) {
+
+        this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
+
+        final SearchParameters searchParameters = SearchParameters.forAccountTransfer(sqlSearch, externalId, offset, limit, orderBy,
+                sortOrder);
+        Date startDateRange = null;
+        Date endDateRange = null;
+        if (fromDateParam != null) {
+            startDateRange = fromDateParam.getDate("fromDate", dateFormat, locale);
+        }
+        if (toDateParam != null) {
+            endDateRange = toDateParam.getDate("toDate", dateFormat, locale);
+        }
+
+        StandingInstructionDTO standingInstructionDTO = new StandingInstructionDTO(searchParameters, transferType, clientName, clientId,
+                fromAccount, fromAccountType, startDateRange, endDateRange);
+
+        final Page<StandingInstructionHistoryData> history = this.standingInstructionHistoryReadPlatformService
+                .retrieveAll(standingInstructionDTO);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, history);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountAssociationsData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountAssociationsData.java
new file mode 100755
index 0000000..bd0d8ce
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountAssociationsData.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.account.data;
+
+public class AccountAssociationsData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final PortfolioAccountData account;
+    private final PortfolioAccountData linkedAccount;
+
+    public AccountAssociationsData(final Long id, final PortfolioAccountData account, final PortfolioAccountData linkedAccount) {
+        this.id = id;
+        this.account = account;
+        this.linkedAccount = linkedAccount;
+    }
+
+    public PortfolioAccountData linkedAccount() {
+        return this.linkedAccount;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransferDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransferDTO.java
new file mode 100755
index 0000000..9c62046
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransferDTO.java
@@ -0,0 +1,182 @@
+/**
+ * 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.account.data;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+
+public class AccountTransferDTO {
+
+    private final LocalDate transactionDate;
+    private final BigDecimal transactionAmount;
+    private final PortfolioAccountType fromAccountType;
+    private final PortfolioAccountType toAccountType;
+    private final Long fromAccountId;
+    private final Long toAccountId;
+    private final String description;
+    private final Locale locale;
+    private final DateTimeFormatter fmt;
+    private final PaymentDetail paymentDetail;
+    private final Integer fromTransferType;
+    private final Integer toTransferType;
+    private final Long chargeId;
+    private final Integer loanInstallmentNumber;
+    private final Integer transferType;
+    private final AccountTransferDetails accountTransferDetails;
+    private final String noteText;
+    private final String txnExternalId;
+    private final Loan loan;
+    private final SavingsAccount toSavingsAccount;
+    private final SavingsAccount fromSavingsAccount;
+    private final Boolean isRegularTransaction;
+    private final Boolean isExceptionForBalanceCheck;
+
+    public AccountTransferDTO(final LocalDate transactionDate, final BigDecimal transactionAmount,
+            final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType, final Long fromAccountId,
+            final Long toAccountId, final String description, final Locale locale, final DateTimeFormatter fmt,
+            final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId,
+            Integer loanInstallmentNumber, Integer transferType, final AccountTransferDetails accountTransferDetails,
+            final String noteText, final String txnExternalId, final Loan loan, SavingsAccount toSavingsAccount,
+            final SavingsAccount fromSavingsAccount, final Boolean isRegularTransaction, Boolean isExceptionForBalanceCheck) {
+        this.transactionDate = transactionDate;
+        this.transactionAmount = transactionAmount;
+        this.fromAccountType = fromAccountType;
+        this.toAccountType = toAccountType;
+        this.fromAccountId = fromAccountId;
+        this.toAccountId = toAccountId;
+        this.description = description;
+        this.locale = locale;
+        this.fmt = fmt;
+        this.paymentDetail = paymentDetail;
+        this.fromTransferType = fromTransferType;
+        this.toTransferType = toTransferType;
+        this.chargeId = chargeId;
+        this.loanInstallmentNumber = loanInstallmentNumber;
+        this.transferType = transferType;
+        this.accountTransferDetails = accountTransferDetails;
+        this.noteText = noteText;
+        this.txnExternalId = txnExternalId;
+        this.loan = loan;
+        this.toSavingsAccount = toSavingsAccount;
+        this.fromSavingsAccount = fromSavingsAccount;
+        this.isRegularTransaction = isRegularTransaction;
+        this.isExceptionForBalanceCheck = isExceptionForBalanceCheck;
+    }
+
+    public LocalDate getTransactionDate() {
+        return this.transactionDate;
+    }
+
+    public BigDecimal getTransactionAmount() {
+        return this.transactionAmount;
+    }
+
+    public PortfolioAccountType getFromAccountType() {
+        return this.fromAccountType;
+    }
+
+    public PortfolioAccountType getToAccountType() {
+        return this.toAccountType;
+    }
+
+    public Long getFromAccountId() {
+        return this.fromAccountId;
+    }
+
+    public Long getToAccountId() {
+        return this.toAccountId;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public Locale getLocale() {
+        return this.locale;
+    }
+
+    public DateTimeFormatter getFmt() {
+        return this.fmt;
+    }
+
+    public PaymentDetail getPaymentDetail() {
+        return this.paymentDetail;
+    }
+
+    public Integer getFromTransferType() {
+        return this.fromTransferType;
+    }
+
+    public Integer getToTransferType() {
+        return this.toTransferType;
+    }
+
+    public Long getChargeId() {
+        return this.chargeId;
+    }
+
+    public Integer getLoanInstallmentNumber() {
+        return this.loanInstallmentNumber;
+    }
+
+    public Integer getTransferType() {
+        return this.transferType;
+    }
+
+    public AccountTransferDetails getAccountTransferDetails() {
+        return this.accountTransferDetails;
+    }
+
+    public String getNoteText() {
+        return this.noteText;
+    }
+
+    public String getTxnExternalId() {
+        return this.txnExternalId;
+    }
+
+    public Loan getLoan() {
+        return this.loan;
+    }
+
+    public SavingsAccount getToSavingsAccount() {
+        return this.toSavingsAccount;
+    }
+
+    public SavingsAccount getFromSavingsAccount() {
+        return this.fromSavingsAccount;
+    }
+
+    public Boolean isRegularTransaction() {
+        return this.isRegularTransaction;
+    }
+
+    public Boolean isExceptionForBalanceCheck() {
+        return this.isExceptionForBalanceCheck;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransferData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransferData.java
new file mode 100644
index 0000000..26fa23a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransferData.java
@@ -0,0 +1,137 @@
+/**
+ * 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.account.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a savings account.
+ */
+@SuppressWarnings("unused")
+public class AccountTransferData {
+
+    private final Long id;
+    private final Boolean reversed;
+    private final CurrencyData currency;
+    private final BigDecimal transferAmount;
+    private final LocalDate transferDate;
+    private final String transferDescription;
+    private final OfficeData fromOffice;
+    private final ClientData fromClient;
+    private final EnumOptionData fromAccountType;
+    private final PortfolioAccountData fromAccount;
+    private final OfficeData toOffice;
+    private final ClientData toClient;
+    private final EnumOptionData toAccountType;
+    private final PortfolioAccountData toAccount;
+
+    // template
+    private final Collection<OfficeData> fromOfficeOptions;
+    private final Collection<ClientData> fromClientOptions;
+    private final Collection<EnumOptionData> fromAccountTypeOptions;
+    private final Collection<PortfolioAccountData> fromAccountOptions;
+    private final Collection<OfficeData> toOfficeOptions;
+    private final Collection<ClientData> toClientOptions;
+    private final Collection<EnumOptionData> toAccountTypeOptions;
+    private final Collection<PortfolioAccountData> toAccountOptions;
+
+    public static AccountTransferData template(final OfficeData fromOffice, final ClientData fromClient,
+            final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final LocalDate transferDate,
+            final OfficeData toOffice, final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
+            final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
+            final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
+            final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
+            final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions) {
+        final Long id = null;
+        CurrencyData currency = null;
+        BigDecimal transferAmount = BigDecimal.ZERO;
+        if (fromAccount != null) {
+            currency = fromAccount.currency();
+            if (fromAccount.getAmtForTransfer() != null) {
+                transferAmount = fromAccount.getAmtForTransfer();
+            }
+        }
+        final String transferDescription = null;
+        final Boolean reversed = null;
+        return new AccountTransferData(id, reversed, fromOffice, fromClient, fromAccountType, fromAccount, currency, transferAmount,
+                transferDate, transferDescription, toOffice, toClient, toAccountType, toAccount, fromOfficeOptions, fromClientOptions,
+                fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
+    }
+
+    public static AccountTransferData instance(final Long id, final Boolean reversed, final LocalDate transferDate,
+            final CurrencyData currency, final BigDecimal transferAmount, final String transferDescription, final OfficeData fromOffice,
+            final OfficeData toOffice, final ClientData fromClient, final ClientData toClient, final EnumOptionData fromAccountType,
+            final PortfolioAccountData fromAccount, final EnumOptionData toAccountType, final PortfolioAccountData toAccount) {
+
+        return new AccountTransferData(id, reversed, fromOffice, fromClient, fromAccountType, fromAccount, currency, transferAmount,
+                transferDate, transferDescription, toOffice, toClient, toAccountType, toAccount, null, null, null, null, null, null, null,
+                null);
+    }
+
+    public static AccountTransferData transferBasicDetails(final Long id, final CurrencyData currency, final BigDecimal transferAmount,
+            final LocalDate transferDate, final String description, final Boolean reversed) {
+
+        final EnumOptionData fromAccountType = null;
+        final EnumOptionData toAccountType = null;
+
+        return new AccountTransferData(id, reversed, null, null, fromAccountType, null, currency, transferAmount, transferDate,
+                description, null, null, toAccountType, null, null, null, null, null, null, null, null, null);
+    }
+
+    private AccountTransferData(final Long id, final Boolean reversed, final OfficeData fromOffice, final ClientData fromClient,
+            final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final CurrencyData currency,
+            final BigDecimal transferAmount, final LocalDate transferDate, final String transferDescription, final OfficeData toOffice,
+            final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
+            final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
+            final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
+            final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
+            final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions) {
+        this.id = id;
+        this.reversed = reversed;
+        this.fromOffice = fromOffice;
+        this.fromClient = fromClient;
+        this.fromAccountType = fromAccountType;
+        this.fromAccount = fromAccount;
+        this.toOffice = toOffice;
+        this.toClient = toClient;
+        this.toAccountType = toAccountType;
+        this.toAccount = toAccount;
+
+        this.currency = currency;
+        this.transferAmount = transferAmount;
+        this.transferDate = transferDate;
+        this.transferDescription = transferDescription;
+
+        this.fromOfficeOptions = fromOfficeOptions;
+        this.fromClientOptions = fromClientOptions;
+        this.fromAccountTypeOptions = fromAccountTypeOptions;
+        this.fromAccountOptions = fromAccountOptions;
+        this.toOfficeOptions = toOfficeOptions;
+        this.toClientOptions = toClientOptions;
+        this.toAccountTypeOptions = toAccountTypeOptions;
+        this.toAccountOptions = toAccountOptions;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransfersDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransfersDataValidator.java
new file mode 100644
index 0000000..0b79597
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransfersDataValidator.java
@@ -0,0 +1,93 @@
+/**
+ * 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.account.data;
+
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferAmountParamName;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDescriptionParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class AccountTransfersDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator;
+
+    @Autowired
+    public AccountTransfersDataValidator(final FromJsonHelper fromApiJsonHelper,
+            final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.accountTransfersDetailDataValidator = accountTransfersDetailDataValidator;
+    }
+
+    public void validate(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        this.accountTransfersDetailDataValidator.validate(command, baseDataValidator);
+
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transferDateParamName, element);
+        baseDataValidator.reset().parameter(transferDateParamName).value(transactionDate).notNull();
+
+        final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transferAmountParamName, element);
+        baseDataValidator.reset().parameter(transferAmountParamName).value(transactionAmount).notNull().positiveAmount();
+
+        final String transactionDescription = this.fromApiJsonHelper.extractStringNamed(transferDescriptionParamName, element);
+        baseDataValidator.reset().parameter(transferDescriptionParamName).value(transactionDescription).notBlank()
+                .notExceedingLengthOf(200);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    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/portfolio/account/data/AccountTransfersDetailDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransfersDetailDataValidator.java
new file mode 100755
index 0000000..d155951
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/AccountTransfersDetailDataValidator.java
@@ -0,0 +1,80 @@
+/**
+ * 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.account.data;
+
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+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;
+
+@Component
+public class AccountTransfersDetailDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public AccountTransfersDetailDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validate(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
+
+        final JsonElement element = command.parsedJson();
+
+        final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
+        baseDataValidator.reset().parameter(fromOfficeIdParamName).value(fromOfficeId).notNull().integerGreaterThanZero();
+
+        final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
+        baseDataValidator.reset().parameter(fromClientIdParamName).value(fromClientId).notNull().integerGreaterThanZero();
+
+        final Long fromAccountId = this.fromApiJsonHelper.extractLongNamed(fromAccountIdParamName, element);
+        baseDataValidator.reset().parameter(fromAccountIdParamName).value(fromAccountId).notNull().integerGreaterThanZero();
+
+        final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
+        baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType).notNull()
+                .isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
+
+        final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
+        baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero();
+
+        final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
+        baseDataValidator.reset().parameter(toClientIdParamName).value(toClientId).notNull().integerGreaterThanZero();
+
+        final Long toAccountId = this.fromApiJsonHelper.extractLongNamed(toAccountIdParamName, element);
+        baseDataValidator.reset().parameter(toAccountIdParamName).value(toAccountId).notNull().integerGreaterThanZero();
+
+        final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
+        baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType).notNull()
+                .isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/PortfolioAccountDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/PortfolioAccountDTO.java
new file mode 100755
index 0000000..b259ff1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/PortfolioAccountDTO.java
@@ -0,0 +1,83 @@
+/**
+ * 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.account.data;
+
+public class PortfolioAccountDTO {
+
+    private final Integer accountTypeId;
+    private final Long clientId;
+    private final String currencyCode;
+    private final long[] accountStatus;
+    private final Integer depositType;
+    private final boolean excludeOverDraftAccounts;
+
+    public PortfolioAccountDTO(final Integer accountTypeId, final Long clientId, final String currencyCode, final long[] accountStatus,
+            final Integer depositType, final boolean excludeOverDraftAccounts) {
+        this.accountTypeId = accountTypeId;
+        this.clientId = clientId;
+        this.currencyCode = currencyCode;
+        this.accountStatus = accountStatus;
+        this.depositType = depositType;
+        this.excludeOverDraftAccounts = excludeOverDraftAccounts;
+    }
+
+    public PortfolioAccountDTO(final Integer accountTypeId, final Long clientId, final long[] accountStatus) {
+        this.accountTypeId = accountTypeId;
+        this.clientId = clientId;
+        this.currencyCode = null;
+        this.accountStatus = accountStatus;
+        this.depositType = null;
+        this.excludeOverDraftAccounts = false;
+    }
+    
+    public PortfolioAccountDTO(final Integer accountTypeId, final Long clientId, final String currencyCode, final long[] accountStatus,
+            final Integer depositType) {
+        this.accountTypeId = accountTypeId;
+        this.clientId = clientId;
+        this.currencyCode = currencyCode;
+        this.accountStatus = accountStatus;
+        this.depositType = depositType;
+        this.excludeOverDraftAccounts = false;
+    }
+
+    public Integer getAccountTypeId() {
+        return this.accountTypeId;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+
+    public long[] getAccountStatus() {
+        return this.accountStatus;
+    }
+
+    public Integer getDepositType() {
+        return this.depositType;
+    }
+
+    public boolean isExcludeOverDraftAccounts() {
+        return this.excludeOverDraftAccounts;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/PortfolioAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/PortfolioAccountData.java
new file mode 100644
index 0000000..ab9f521
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/PortfolioAccountData.java
@@ -0,0 +1,123 @@
+/**
+ * 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.account.data;
+
+import java.math.BigDecimal;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+/**
+ * Immutable data object representing a savings account.
+ */
+@SuppressWarnings("unused")
+public class PortfolioAccountData {
+
+    private final Long id;
+    private final String accountNo;
+    private final String externalId;
+    private final Long groupId;
+    private final String groupName;
+    private final Long clientId;
+    private final String clientName;
+    private final Long productId;
+    private final String productName;
+    private final Long fieldOfficerId;
+    private final String fieldOfficerName;
+    private final CurrencyData currency;
+    private final BigDecimal amtForTransfer;
+
+    public static PortfolioAccountData lookup(final Long accountId, final String accountNo) {
+        return new PortfolioAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null, null);
+    }
+
+    public PortfolioAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName,
+            final Long clientId, final String clientName, final Long productId, final String productName, final Long fieldofficerId,
+            final String fieldofficerName, final CurrencyData currency) {
+        this.id = id;
+        this.accountNo = accountNo;
+        this.externalId = externalId;
+        this.groupId = groupId;
+        this.groupName = groupName;
+        this.clientId = clientId;
+        this.clientName = clientName;
+        this.productId = productId;
+        this.productName = productName;
+        this.fieldOfficerId = fieldofficerId;
+        this.fieldOfficerName = fieldofficerName;
+        this.currency = currency;
+        this.amtForTransfer = null;
+    }
+
+    public PortfolioAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName,
+            final Long clientId, final String clientName, final Long productId, final String productName, final Long fieldofficerId,
+            final String fieldofficerName, final CurrencyData currency, final BigDecimal amtForTransfer) {
+        this.id = id;
+        this.accountNo = accountNo;
+        this.externalId = externalId;
+        this.groupId = groupId;
+        this.groupName = groupName;
+        this.clientId = clientId;
+        this.clientName = clientName;
+        this.productId = productId;
+        this.productName = productName;
+        this.fieldOfficerId = fieldofficerId;
+        this.fieldOfficerName = fieldofficerName;
+        this.currency = currency;
+        this.amtForTransfer = amtForTransfer;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final PortfolioAccountData rhs = (PortfolioAccountData) obj;
+        return new EqualsBuilder().append(this.id, rhs.id).append(this.accountNo, rhs.accountNo).append(this.productId, rhs.productId)
+                .append(this.productName, rhs.productName).isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).append(this.productId).append(this.productName)
+                .toHashCode();
+    }
+
+    public Long clientId() {
+        return this.clientId;
+    }
+
+    public CurrencyData currency() {
+        return this.currency;
+    }
+
+    public String currencyCode() {
+        return this.currency.code();
+    }
+
+    public BigDecimal getAmtForTransfer() {
+        return this.amtForTransfer;
+    }
+
+    public Long accountId() {
+        return this.id;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDTO.java
new file mode 100755
index 0000000..f67588e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDTO.java
@@ -0,0 +1,85 @@
+/**
+ * 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.account.data;
+
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+
+public class StandingInstructionDTO {
+
+    final SearchParameters searchParameters;
+    final Long clientId;
+    final String clientName;
+    final Integer transferType;
+    final Integer fromAccountType;
+    final Long fromAccount;
+    final Date startDateRange;
+    final Date endDateRange;
+
+    public StandingInstructionDTO(final SearchParameters searchParameters, final Integer transferType, final String clientName,
+            final Long clientId, final Long fromAccount, final Integer fromAccountType, final Date startDateRange, final Date endDateRange) {
+        this.searchParameters = searchParameters;
+        this.transferType = transferType;
+        this.clientName = clientName;
+        this.clientId = clientId;
+        this.fromAccount = fromAccount;
+        this.fromAccountType = fromAccountType;
+        this.startDateRange = startDateRange;
+        this.endDateRange = endDateRange;
+    }
+
+    public SearchParameters searchParameters() {
+        return this.searchParameters;
+    }
+
+    public Long clientId() {
+        return this.clientId;
+    }
+
+    public String clientName() {
+        return this.clientName;
+    }
+
+    public Integer transferType() {
+        return this.transferType;
+    }
+
+    public Long fromAccount() {
+        return this.fromAccount;
+    }
+
+    public Integer fromAccountType() {
+        return this.fromAccountType;
+    }
+
+    public Integer getTransferType() {
+        return this.transferType;
+    }
+
+    public Date startDateRange() {
+        return this.startDateRange;
+    }
+
+    
+    public Date endDateRange() {
+        return this.endDateRange;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionData.java
new file mode 100755
index 0000000..751b0fc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionData.java
@@ -0,0 +1,389 @@
+/**
+ * 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.account.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.joda.time.DateTimeFieldType;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+
+/**
+ * Immutable data object representing a savings account.
+ */
+@SuppressWarnings("unused")
+public class StandingInstructionData {
+
+    private final Long id;
+    private final Long accountDetailId;
+    private final String name;
+    private final OfficeData fromOffice;
+    private final ClientData fromClient;
+    private final EnumOptionData fromAccountType;
+    private final PortfolioAccountData fromAccount;
+    private final OfficeData toOffice;
+    private final ClientData toClient;
+    private final EnumOptionData toAccountType;
+    private final PortfolioAccountData toAccount;
+    private final EnumOptionData transferType;
+    private final EnumOptionData priority;
+    private final EnumOptionData instructionType;
+    private final EnumOptionData status;
+    private final BigDecimal amount;
+    private final LocalDate validFrom;
+    private final LocalDate validTill;
+    private final EnumOptionData recurrenceType;
+    private final EnumOptionData recurrenceFrequency;
+    private final Integer recurrenceInterval;
+    private final MonthDay recurrenceOnMonthDay;
+    private final Page<AccountTransferData> transactions;
+
+    private final Collection<OfficeData> fromOfficeOptions;
+    private final Collection<ClientData> fromClientOptions;
+    private final Collection<EnumOptionData> fromAccountTypeOptions;
+    private final Collection<PortfolioAccountData> fromAccountOptions;
+    private final Collection<OfficeData> toOfficeOptions;
+    private final Collection<ClientData> toClientOptions;
+    private final Collection<EnumOptionData> toAccountTypeOptions;
+    private final Collection<PortfolioAccountData> toAccountOptions;
+    private final Collection<EnumOptionData> transferTypeOptions;
+    private final Collection<EnumOptionData> statusOptions;
+    private final Collection<EnumOptionData> instructionTypeOptions;
+    private final Collection<EnumOptionData> priorityOptions;
+    private final Collection<EnumOptionData> recurrenceTypeOptions;
+    private final Collection<EnumOptionData> recurrenceFrequencyOptions;
+
+    public static StandingInstructionData template(final Collection<OfficeData> fromOfficeOptions,
+            final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
+            final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
+            final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
+            final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
+            final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
+            final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
+            final Collection<EnumOptionData> recurrenceFrequencyOptions) {
+
+        final Long id = null;
+        final Long accountDetailId = null;
+        final String name = null;
+        final OfficeData fromOffice = null;
+        final OfficeData toOffice = null;
+        final ClientData fromClient = null;
+        final ClientData toClient = null;
+        final EnumOptionData fromAccountType = null;
+        final PortfolioAccountData fromAccount = null;
+        final EnumOptionData toAccountType = null;
+        final PortfolioAccountData toAccount = null;
+        final EnumOptionData transferType = null;
+        final EnumOptionData priority = null;
+        final EnumOptionData instructionType = null;
+        final EnumOptionData status = null;
+        final BigDecimal amount = null;
+        final LocalDate validFrom = null;
+        final LocalDate validTill = null;
+        final EnumOptionData recurrenceType = null;
+        final EnumOptionData recurrenceFrequency = null;
+        final Integer recurrenceInterval = null;
+        final MonthDay recurrenceOnMonthDay = null;
+        final Page<AccountTransferData> transactions = null;
+
+        return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
+                toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
+                recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
+                fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
+                toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
+                recurrenceFrequencyOptions);
+    }
+
+    public static StandingInstructionData instance(final Long id, final Long accountDetailId, final String name,
+            final OfficeData fromOffice, final OfficeData toOffice, final ClientData fromClient, final ClientData toClient,
+            final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final EnumOptionData toAccountType,
+            final PortfolioAccountData toAccount, final EnumOptionData transferType, final EnumOptionData priority,
+            final EnumOptionData instructionType, final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom,
+            final LocalDate validTill, final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency,
+            final Integer recurrenceInterval, final MonthDay recurrenceOnMonthDay) {
+        final Page<AccountTransferData> transactions = null;
+        final Collection<OfficeData> fromOfficeOptions = null;
+        final Collection<ClientData> fromClientOptions = null;
+        final Collection<EnumOptionData> fromAccountTypeOptions = null;
+        final Collection<PortfolioAccountData> fromAccountOptions = null;
+        final Collection<OfficeData> toOfficeOptions = null;
+        final Collection<ClientData> toClientOptions = null;
+        final Collection<EnumOptionData> toAccountTypeOptions = null;
+        final Collection<PortfolioAccountData> toAccountOptions = null;
+        final Collection<EnumOptionData> transferTypeOptions = null;
+        final Collection<EnumOptionData> statusOptions = null;
+        final Collection<EnumOptionData> instructionTypeOptions = null;
+        final Collection<EnumOptionData> priorityOptions = null;
+        final Collection<EnumOptionData> recurrenceTypeOptions = null;
+        final Collection<EnumOptionData> recurrenceFrequencyOptions = null;
+
+        return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
+                toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
+                recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
+                fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
+                toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
+                recurrenceFrequencyOptions);
+    }
+
+    public static StandingInstructionData withTemplateData(StandingInstructionData instructionData, StandingInstructionData templateData) {
+        return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
+                instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
+                instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
+                instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
+                instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
+                instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay,
+                instructionData.transactions, templateData.fromOfficeOptions, templateData.fromClientOptions,
+                templateData.fromAccountTypeOptions, templateData.fromAccountOptions, templateData.toOfficeOptions,
+                templateData.toClientOptions, templateData.toAccountTypeOptions, templateData.toAccountOptions,
+                templateData.transferTypeOptions, templateData.statusOptions, templateData.instructionTypeOptions,
+                templateData.priorityOptions, templateData.recurrenceTypeOptions, templateData.recurrenceFrequencyOptions);
+    }
+
+    private StandingInstructionData(final Long id, final Long accountDetailId, final String name, final OfficeData fromOffice,
+            final ClientData fromClient, final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount,
+            final OfficeData toOffice, final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
+            final EnumOptionData transferType, final EnumOptionData priority, final EnumOptionData instructionType,
+            final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom, LocalDate validTill,
+            final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency, final Integer recurrenceInterval,
+            final MonthDay recurrenceOnMonthDay, final Page<AccountTransferData> transactions,
+            final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
+            final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
+            final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
+            final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions,
+            final Collection<EnumOptionData> transferTypeOptions, final Collection<EnumOptionData> statusOptions,
+            final Collection<EnumOptionData> instructionTypeOptions, final Collection<EnumOptionData> priorityOptions,
+            final Collection<EnumOptionData> recurrenceTypeOptions, final Collection<EnumOptionData> recurrenceFrequencyOptions) {
+        this.id = id;
+        this.accountDetailId = accountDetailId;
+        this.name = name;
+        this.fromOffice = fromOffice;
+        this.fromClient = fromClient;
+        this.fromAccountType = fromAccountType;
+        this.fromAccount = fromAccount;
+        this.toOffice = toOffice;
+        this.toClient = toClient;
+        this.toAccountType = toAccountType;
+        this.toAccount = toAccount;
+        this.transferType = transferType;
+
+        this.priority = priority;
+        this.instructionType = instructionType;
+        this.status = status;
+        this.amount = amount;
+        this.validFrom = validFrom;
+        this.validTill = validTill;
+        this.recurrenceType = recurrenceType;
+        this.recurrenceFrequency = recurrenceFrequency;
+        this.recurrenceInterval = recurrenceInterval;
+        this.recurrenceOnMonthDay = recurrenceOnMonthDay;
+
+        this.fromOfficeOptions = fromOfficeOptions;
+        this.fromClientOptions = fromClientOptions;
+        this.fromAccountTypeOptions = fromAccountTypeOptions;
+        this.fromAccountOptions = fromAccountOptions;
+        this.toOfficeOptions = toOfficeOptions;
+        this.toClientOptions = toClientOptions;
+        this.toAccountTypeOptions = toAccountTypeOptions;
+        this.toAccountOptions = toAccountOptions;
+        this.transferTypeOptions = transferTypeOptions;
+        this.statusOptions = statusOptions;
+        this.instructionTypeOptions = instructionTypeOptions;
+        this.priorityOptions = priorityOptions;
+        this.recurrenceTypeOptions = recurrenceTypeOptions;
+        this.recurrenceFrequencyOptions = recurrenceFrequencyOptions;
+        this.transactions = transactions;
+    }
+
+    public static StandingInstructionData template(OfficeData fromOffice, ClientData fromClient, EnumOptionData fromAccountType,
+            PortfolioAccountData fromAccount, LocalDate transferDate, OfficeData toOffice, ClientData toClient,
+            EnumOptionData toAccountType, PortfolioAccountData toAccount, final Collection<OfficeData> fromOfficeOptions,
+            final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
+            final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
+            final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
+            final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
+            final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
+            final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
+            final Collection<EnumOptionData> recurrenceFrequencyOptions) {
+        final Long id = null;
+        final Long accountDetailId = null;
+        final String name = null;
+        final EnumOptionData transferType = null;
+        final EnumOptionData priority = null;
+        final EnumOptionData instructionType = null;
+        final EnumOptionData status = null;
+        final BigDecimal amount = null;
+        final LocalDate validFrom = null;
+        final LocalDate validTill = null;
+        final EnumOptionData recurrenceType = null;
+        final EnumOptionData recurrenceFrequency = null;
+        final Integer recurrenceInterval = null;
+        final MonthDay recurrenceOnMonthDay = null;
+        final Page<AccountTransferData> transactions = null;
+
+        return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
+                toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
+                recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
+                fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
+                toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
+                recurrenceFrequencyOptions);
+    }
+
+    public static StandingInstructionData withTransferData(StandingInstructionData instructionData,
+            final Page<AccountTransferData> transactions) {
+        return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
+                instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
+                instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
+                instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
+                instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
+                instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay,
+                transactions, instructionData.fromOfficeOptions, instructionData.fromClientOptions, instructionData.fromAccountTypeOptions,
+                instructionData.fromAccountOptions, instructionData.toOfficeOptions, instructionData.toClientOptions,
+                instructionData.toAccountTypeOptions, instructionData.toAccountOptions, instructionData.transferTypeOptions,
+                instructionData.statusOptions, instructionData.instructionTypeOptions, instructionData.priorityOptions,
+                instructionData.recurrenceTypeOptions, instructionData.recurrenceFrequencyOptions);
+    }
+
+    public StandingInstructionType instructionType() {
+        StandingInstructionType standingInstructionType = null;
+        if (this.instructionType != null) {
+            standingInstructionType = StandingInstructionType.fromInt(this.instructionType.getId().intValue());
+        }
+        return standingInstructionType;
+    }
+
+    public AccountTransferRecurrenceType recurrenceType() {
+        AccountTransferRecurrenceType recurrenceType = null;
+        if (this.recurrenceType != null) {
+            recurrenceType = AccountTransferRecurrenceType.fromInt(this.recurrenceType.getId().intValue());
+        }
+        return recurrenceType;
+    }
+
+    public PeriodFrequencyType recurrenceFrequency() {
+        PeriodFrequencyType frequencyType = null;
+        if (this.recurrenceFrequency != null) {
+            frequencyType = PeriodFrequencyType.fromInt(this.recurrenceFrequency.getId().intValue());
+        }
+        return frequencyType;
+    }
+
+    public PortfolioAccountType fromAccountType() {
+        PortfolioAccountType accountType = null;
+        if (this.fromAccountType != null) {
+            accountType = PortfolioAccountType.fromInt(this.fromAccountType.getId().intValue());
+        }
+        return accountType;
+    }
+
+    public PortfolioAccountType toAccountType() {
+        PortfolioAccountType accountType = null;
+        if (this.toAccountType != null) {
+            accountType = PortfolioAccountType.fromInt(this.toAccountType.getId().intValue());
+        }
+        return accountType;
+    }
+
+    public AccountTransferType transferType() {
+        AccountTransferType accountTransferType = null;
+        if (this.transferType != null) {
+            accountTransferType = AccountTransferType.fromInt(this.transferType.getId().intValue());
+        }
+        return accountTransferType;
+    }
+
+    public Integer recurrenceInterval() {
+        return this.recurrenceInterval;
+    }
+
+    public Integer recurrenceOnDay() {
+        Integer recurrenceOnDay = 0;
+        if (this.recurrenceOnMonthDay != null) {
+            recurrenceOnDay = this.recurrenceOnMonthDay.get(DateTimeFieldType.dayOfMonth());
+        }
+        return recurrenceOnDay;
+    }
+
+    public Integer recurrenceOnMonth() {
+        Integer recurrenceOnMonth = 0;
+        if (this.recurrenceOnMonthDay != null) {
+            recurrenceOnMonth = this.recurrenceOnMonthDay.get(DateTimeFieldType.monthOfYear());
+        }
+        return recurrenceOnMonth;
+    }
+
+    public LocalDate validFrom() {
+        return this.validFrom;
+    }
+
+    public BigDecimal amount() {
+        return this.amount;
+    }
+
+    public PortfolioAccountData fromAccount() {
+        return this.fromAccount;
+    }
+
+    public PortfolioAccountData toAccount() {
+        return this.toAccount;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public Integer toTransferType() {
+        Integer transferType = null;
+        AccountTransferType accountTransferType = transferType();
+        if (accountTransferType.isChargePayment()) {
+            transferType = LoanTransactionType.CHARGE_PAYMENT.getValue();
+        } else if (accountTransferType.isLoanRepayment()) {
+            transferType = LoanTransactionType.REPAYMENT.getValue();
+        }
+        return transferType;
+    }
+
+    public Long accountDetailId() {
+        return this.accountDetailId;
+    }
+
+    public ClientData fromClient() {
+        return this.fromClient;
+    }
+
+    public ClientData toClient() {
+        return this.toClient;
+    }
+
+    
+    public Long getId() {
+        return this.id;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDataValidator.java
new file mode 100755
index 0000000..92e6042
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDataValidator.java
@@ -0,0 +1,250 @@
+/**
+ * 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.account.data;
+
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.CREATE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.UPDATE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.instructionTypeParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.nameParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.priorityParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceFrequencyParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceIntervalParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceOnMonthDayParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceTypeParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validFromParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validTillParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class StandingInstructionDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator;
+
+    @Autowired
+    public StandingInstructionDataValidator(final FromJsonHelper fromApiJsonHelper,
+            final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.accountTransfersDetailDataValidator = accountTransfersDetailDataValidator;
+    }
+
+    public void validateForCreate(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CREATE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(STANDING_INSTRUCTION_RESOURCE_NAME);
+        this.accountTransfersDetailDataValidator.validate(command, baseDataValidator);
+
+        final JsonElement element = command.parsedJson();
+
+        final Integer status = this.fromApiJsonHelper.extractIntegerNamed(statusParamName, element, Locale.getDefault());
+        baseDataValidator.reset().parameter(statusParamName).value(status).notNull().inMinMaxRange(1, 2);
+
+        final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(validFromParamName, element);
+        baseDataValidator.reset().parameter(validFromParamName).value(validFrom).notNull();
+
+        final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(validTillParamName, element);
+        baseDataValidator.reset().parameter(validTillParamName).value(validTill).validateDateAfter(validFrom);
+
+        final BigDecimal transferAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element);
+        baseDataValidator.reset().parameter(amountParamName).value(transferAmount).positiveAmount();
+
+        final Integer transferType = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
+        baseDataValidator.reset().parameter(transferTypeParamName).value(transferType).notNull().inMinMaxRange(1, 3);
+
+        final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(priorityParamName, element, Locale.getDefault());
+        baseDataValidator.reset().parameter(priorityParamName).value(priority).notNull().inMinMaxRange(1, 4);
+
+        final Integer standingInstructionType = this.fromApiJsonHelper.extractIntegerNamed(instructionTypeParamName, element,
+                Locale.getDefault());
+        baseDataValidator.reset().parameter(instructionTypeParamName).value(standingInstructionType).notNull().inMinMaxRange(1, 2);
+
+        final Integer recurrenceType = this.fromApiJsonHelper.extractIntegerNamed(recurrenceTypeParamName, element, Locale.getDefault());
+        baseDataValidator.reset().parameter(recurrenceTypeParamName).value(recurrenceType).notNull().inMinMaxRange(1, 2);
+        boolean isPeriodic = false;
+        if (recurrenceType != null) {
+            isPeriodic = AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence();
+        }
+
+        final Integer recurrenceFrequency = this.fromApiJsonHelper.extractIntegerNamed(recurrenceFrequencyParamName, element,
+                Locale.getDefault());
+        baseDataValidator.reset().parameter(recurrenceFrequencyParamName).value(recurrenceFrequency).inMinMaxRange(0, 3);
+
+        if (recurrenceFrequency != null) {
+            PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(recurrenceFrequency);
+            if (frequencyType.isMonthly() || frequencyType.isYearly()) {
+                final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed(recurrenceOnMonthDayParamName, element);
+                baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(monthDay).notNull();
+            }
+        }
+
+        final Integer recurrenceInterval = this.fromApiJsonHelper.extractIntegerNamed(recurrenceIntervalParamName, element,
+                Locale.getDefault());
+        if (isPeriodic) {
+            baseDataValidator.reset().parameter(recurrenceIntervalParamName).value(recurrenceInterval).notNull();
+            baseDataValidator.reset().parameter(recurrenceFrequencyParamName).value(recurrenceFrequency).notNull();
+        }
+        baseDataValidator.reset().parameter(recurrenceIntervalParamName).value(recurrenceInterval).integerGreaterThanZero();
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
+        baseDataValidator.reset().parameter(nameParamName).value(name).notNull();
+
+        final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
+        if (toAccountType != null && PortfolioAccountType.fromInt(toAccountType).isSavingsAccount()) {
+            baseDataValidator.reset().parameter(instructionTypeParamName).value(standingInstructionType).notNull().inMinMaxRange(1, 1);
+            baseDataValidator.reset().parameter(recurrenceTypeParamName).value(recurrenceType).notNull().inMinMaxRange(1, 1);
+
+        }
+        if (standingInstructionType != null && StandingInstructionType.fromInt(standingInstructionType).isFixedAmoutTransfer()) {
+            baseDataValidator.reset().parameter(amountParamName).value(transferAmount).notNull();
+        }
+
+        String errorCode = null;
+        AccountTransferType accountTransferType = AccountTransferType.fromInt(transferType);
+        final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
+        if (fromAccountType != null && toAccountType != null) {
+            PortfolioAccountType fromPortfolioAccountType = PortfolioAccountType.fromInt(fromAccountType);
+            PortfolioAccountType toPortfolioAccountType = PortfolioAccountType.fromInt(toAccountType);
+            if (accountTransferType.isAccountTransfer()
+                    && (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isLoanAccount())) {
+                errorCode = "not.account.transfer";
+            } else if (accountTransferType.isLoanRepayment()
+                    && (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isSavingsAccount())) {
+                errorCode = "not.loan.repayment";
+            }
+            if (errorCode != null) {
+                baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
+            }
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdate(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, UPDATE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(STANDING_INSTRUCTION_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+        if (this.fromApiJsonHelper.parameterExists(validFromParamName, element)) {
+            final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(validFromParamName, element);
+            baseDataValidator.reset().parameter(validFromParamName).value(validFrom).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(validTillParamName, element)) {
+            final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(validTillParamName, element);
+            baseDataValidator.reset().parameter(validTillParamName).value(validTill).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(amountParamName, element)) {
+            final BigDecimal transferAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element);
+            baseDataValidator.reset().parameter(amountParamName).value(transferAmount).positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(statusParamName, element)) {
+            final Integer status = this.fromApiJsonHelper.extractIntegerNamed(statusParamName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(statusParamName).value(status).notNull().inMinMaxRange(1, 2);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(priorityParamName, element)) {
+            final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(priorityParamName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(priorityParamName).value(priority).notNull().inMinMaxRange(1, 4);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(instructionTypeParamName, element)) {
+            final Integer standingInstructionType = this.fromApiJsonHelper.extractIntegerNamed(instructionTypeParamName, element,
+                    Locale.getDefault());
+            baseDataValidator.reset().parameter(instructionTypeParamName).value(standingInstructionType).notNull().inMinMaxRange(1, 2);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(recurrenceTypeParamName, element)) {
+            final Integer recurrenceType = this.fromApiJsonHelper
+                    .extractIntegerNamed(recurrenceTypeParamName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(recurrenceTypeParamName).value(recurrenceType).notNull().inMinMaxRange(1, 2);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(recurrenceFrequencyParamName, element)) {
+            final Integer recurrenceFrequency = this.fromApiJsonHelper.extractIntegerNamed(recurrenceFrequencyParamName, element,
+                    Locale.getDefault());
+            baseDataValidator.reset().parameter(recurrenceFrequencyParamName).value(recurrenceFrequency).inMinMaxRange(0, 3);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(recurrenceIntervalParamName, element)) {
+            final Integer recurrenceInterval = this.fromApiJsonHelper.extractIntegerNamed(recurrenceIntervalParamName, element,
+                    Locale.getDefault());
+            baseDataValidator.reset().parameter(recurrenceIntervalParamName).value(recurrenceInterval).integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(nameParamName, element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
+            baseDataValidator.reset().parameter(nameParamName).value(name).notNull();
+        }
+
+        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/portfolio/account/data/StandingInstructionDuesData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDuesData.java
new file mode 100755
index 0000000..8fc9435
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDuesData.java
@@ -0,0 +1,43 @@
+/**
+ * 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.account.data;
+
+import java.math.BigDecimal;
+
+import org.joda.time.LocalDate;
+
+public class StandingInstructionDuesData {
+
+    private final LocalDate dueDate;
+    private final BigDecimal totalDueAmount;
+
+    public StandingInstructionDuesData(final LocalDate dueDate, final BigDecimal totalDueAmount) {
+        this.dueDate = dueDate;
+        this.totalDueAmount = totalDueAmount;
+    }
+
+    public LocalDate dueDate() {
+        return this.dueDate;
+    }
+
+    public BigDecimal totalDueAmount() {
+        return this.totalDueAmount;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionHistoryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionHistoryData.java
new file mode 100755
index 0000000..9d98125
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionHistoryData.java
@@ -0,0 +1,67 @@
+/**
+ * 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.account.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.joda.time.LocalDate;
+
+@SuppressWarnings("unused")
+public class StandingInstructionHistoryData {
+
+    private final Long standingInstructionId;
+    private final String name;
+    private final OfficeData fromOffice;
+    private final ClientData fromClient;
+    private final EnumOptionData fromAccountType;
+    private final PortfolioAccountData fromAccount;
+    private final EnumOptionData toAccountType;
+    private final PortfolioAccountData toAccount;
+    private final OfficeData toOffice;
+    private final ClientData toClient;
+    private final BigDecimal amount;
+    private final String status;
+    private final LocalDate executionTime;
+    private final String errorLog;
+
+    public StandingInstructionHistoryData(final Long standingInstructionId, final String name, final OfficeData fromOffice,
+            final ClientData fromClient, final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount,
+            final EnumOptionData toAccountType, final PortfolioAccountData toAccount, final OfficeData toOffice, final ClientData toClient,
+            final BigDecimal amount, final String status, final LocalDate executionTime, final String errorLog) {
+        this.standingInstructionId = standingInstructionId;
+        this.name = name;
+        this.fromOffice = fromOffice;
+        this.fromClient = fromClient;
+        this.fromAccountType = fromAccountType;
+        this.toAccountType = toAccountType;
+        this.fromAccount = fromAccount;
+        this.toAccount = toAccount;
+        this.toOffice = toOffice;
+        this.toClient = toClient;
+        this.amount = amount;
+        this.errorLog = errorLog;
+        this.status = status;
+        this.executionTime = executionTime;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationType.java
new file mode 100755
index 0000000..a386db4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationType.java
@@ -0,0 +1,73 @@
+/**
+ * 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.account.domain;
+
+/**
+ * Enum representation of loan status states.
+ */
+public enum AccountAssociationType {
+
+    INVALID(0, "accountAssociationType.invalid"), //
+    LINKED_ACCOUNT_ASSOCIATION(1, "accountAssociationType.loan.account.association"), //
+    GUARANTOR_ACCOUNT_ASSOCIATION(2, "accountAssociationType.guarantor.account.association"); //
+
+    private final Integer value;
+    private final String code;
+
+    public static AccountAssociationType fromInt(final Integer statusValue) {
+
+        AccountAssociationType enumeration = AccountAssociationType.INVALID;
+        switch (statusValue) {
+            case 1:
+                enumeration = AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION;
+            break;
+            case 2:
+                enumeration = AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION;
+            break;
+
+        }
+        return enumeration;
+    }
+
+    private AccountAssociationType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final AccountAssociationType state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isLinkedAccountAssociation() {
+        return this.value.equals(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
+    }
+
+    public boolean isGuarantorAccountAssociation() {
+        return this.value.equals(AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue());
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociations.java
new file mode 100755
index 0000000..82574bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociations.java
@@ -0,0 +1,86 @@
+/**
+ * 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.account.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_portfolio_account_associations")
+public class AccountAssociations extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "loan_account_id", nullable = true)
+    private Loan loanAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "savings_account_id", nullable = true)
+    private SavingsAccount savingsAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "linked_loan_account_id", nullable = true)
+    private Loan linkedLoanAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "linked_savings_account_id", nullable = true)
+    private SavingsAccount linkedSavingsAccount;
+
+    @Column(name = "association_type_enum", nullable = false)
+    private Integer associationType;
+
+    @Column(name = "is_active", nullable = false)
+    private boolean active = true;
+
+    protected AccountAssociations() {}
+
+    private AccountAssociations(final Loan loanAccount, final SavingsAccount savingsAccount, final Loan linkedLoanAccount,
+            final SavingsAccount linkedSavingsAccount, final Integer associationType, boolean active) {
+        this.loanAccount = loanAccount;
+        this.savingsAccount = savingsAccount;
+        this.linkedLoanAccount = linkedLoanAccount;
+        this.linkedSavingsAccount = linkedSavingsAccount;
+        this.associationType = associationType;
+        this.active = active;
+    }
+
+    public static AccountAssociations associateSavingsAccount(final Loan loan, final SavingsAccount savingsAccount,
+            final Integer associationType, boolean isActive) {
+        return new AccountAssociations(loan, null, null, savingsAccount, associationType, isActive);
+    }
+
+    public static AccountAssociations associateSavingsAccount(final SavingsAccount savingsAccount,
+            final SavingsAccount linkedSavingsAccount, final Integer associationType, boolean isActive) {
+        return new AccountAssociations(null, savingsAccount, null, linkedSavingsAccount, associationType, isActive);
+    }
+
+    public SavingsAccount linkedSavingsAccount() {
+        return this.linkedSavingsAccount;
+    }
+
+    public void updateLinkedSavingsAccount(final SavingsAccount savingsAccount) {
+        this.linkedSavingsAccount = savingsAccount;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsRepository.java
new file mode 100755
index 0000000..141a8bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsRepository.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.portfolio.account.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 AccountAssociationsRepository extends JpaRepository<AccountAssociations, Long>,
+        JpaSpecificationExecutor<AccountAssociations> {
+
+    @Query("from AccountAssociations aa where aa.loanAccount.id= :loanId and aa.associationType = :associationType")
+    AccountAssociations findByLoanIdAndType(@Param("loanId") Long loanId, @Param("associationType") Integer accountAssociationType);
+
+    @Query("from AccountAssociations aa where aa.savingsAccount.id= :savingsId and aa.associationType = :associationType")
+    AccountAssociations findBySavingsIdAndType(@Param("savingsId") Long savingsId, @Param("associationType") Integer accountAssociationType);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferAssembler.java
new file mode 100644
index 0000000..e1550e3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferAssembler.java
@@ -0,0 +1,148 @@
+/**
+ * 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.account.domain;
+
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferAmountParamName;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDescriptionParamName;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AccountTransferAssembler {
+
+    private final AccountTransferDetailAssembler accountTransferDetailAssembler;
+
+    @Autowired
+    public AccountTransferAssembler(final AccountTransferDetailAssembler accountTransferDetailAssembler) {
+        this.accountTransferDetailAssembler = accountTransferDetailAssembler;
+    }
+
+    public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
+            final SavingsAccount toSavingsAccount, final SavingsAccountTransaction withdrawal, final SavingsAccountTransaction deposit) {
+
+        final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToSavingsTransfer(command,
+                fromSavingsAccount, toSavingsAccount);
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
+        final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), transactionAmount);
+
+        final String description = command.stringValueOfParameterNamed(transferDescriptionParamName);
+        AccountTransferTransaction accountTransferTransaction = AccountTransferTransaction.savingsToSavingsTransfer(accountTransferDetails,
+                withdrawal, deposit, transactionDate, transactionMonetaryAmount, description);
+        accountTransferDetails.addAccountTransferTransaction(accountTransferTransaction);
+        return accountTransferDetails;
+    }
+
+    public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
+            final Loan toLoanAccount, final SavingsAccountTransaction withdrawal, final LoanTransaction loanRepaymentTransaction) {
+
+        final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(command,
+                fromSavingsAccount, toLoanAccount);
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
+        final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), transactionAmount);
+
+        final String description = command.stringValueOfParameterNamed(transferDescriptionParamName);
+
+        AccountTransferTransaction accountTransferTransaction = AccountTransferTransaction.savingsToLoanTransfer(accountTransferDetails,
+                withdrawal, loanRepaymentTransaction, transactionDate, transactionMonetaryAmount, description);
+        accountTransferDetails.addAccountTransferTransaction(accountTransferTransaction);
+        return accountTransferDetails;
+    }
+
+    public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command, final Loan fromLoanAccount,
+            final SavingsAccount toSavingsAccount, final SavingsAccountTransaction deposit, final LoanTransaction loanRefundTransaction) {
+
+        final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleLoanToSavingsTransfer(command,
+                fromLoanAccount, toSavingsAccount);
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
+        final Money transactionMonetaryAmount = Money.of(toSavingsAccount.getCurrency(), transactionAmount);
+
+        final String description = command.stringValueOfParameterNamed(transferDescriptionParamName);
+
+        AccountTransferTransaction accountTransferTransaction = AccountTransferTransaction.LoanTosavingsTransfer(accountTransferDetails,
+                deposit, loanRefundTransaction, transactionDate, transactionMonetaryAmount, description);
+        accountTransferDetails.addAccountTransferTransaction(accountTransferTransaction);
+        return accountTransferDetails;
+    }
+
+    public AccountTransferDetails assembleSavingsToLoanTransfer(final AccountTransferDTO accountTransferDTO,
+            final SavingsAccount fromSavingsAccount, final Loan toLoanAccount, final SavingsAccountTransaction savingsAccountTransaction,
+            final LoanTransaction loanTransaction) {
+        final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), accountTransferDTO.getTransactionAmount());
+        AccountTransferDetails accountTransferDetails = accountTransferDTO.getAccountTransferDetails();
+        if (accountTransferDetails == null) {
+            accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(fromSavingsAccount, toLoanAccount,
+                    accountTransferDTO.getTransferType());
+        }
+        AccountTransferTransaction accountTransferTransaction = AccountTransferTransaction.savingsToLoanTransfer(accountTransferDetails,
+                savingsAccountTransaction, loanTransaction, accountTransferDTO.getTransactionDate(), transactionMonetaryAmount,
+                accountTransferDTO.getDescription());
+        accountTransferDetails.addAccountTransferTransaction(accountTransferTransaction);
+        return accountTransferDetails;
+    }
+
+    public AccountTransferDetails assembleSavingsToSavingsTransfer(final AccountTransferDTO accountTransferDTO,
+            final SavingsAccount fromSavingsAccount, final SavingsAccount toSavingsAccount, final SavingsAccountTransaction withdrawal,
+            final SavingsAccountTransaction deposit) {
+        final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), accountTransferDTO.getTransactionAmount());
+        AccountTransferDetails accountTransferDetails = accountTransferDTO.getAccountTransferDetails();
+        if (accountTransferDetails == null) {
+            accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToSavingsTransfer(fromSavingsAccount,
+                    toSavingsAccount, accountTransferDTO.getTransferType());
+        }
+
+        AccountTransferTransaction accountTransferTransaction = AccountTransferTransaction.savingsToSavingsTransfer(accountTransferDetails,
+                withdrawal, deposit, accountTransferDTO.getTransactionDate(), transactionMonetaryAmount,
+                accountTransferDTO.getDescription());
+        accountTransferDetails.addAccountTransferTransaction(accountTransferTransaction);
+        return accountTransferDetails;
+    }
+
+    public AccountTransferDetails assembleLoanToSavingsTransfer(final AccountTransferDTO accountTransferDTO, final Loan fromLoanAccount,
+            final SavingsAccount toSavingsAccount, final SavingsAccountTransaction deposit, final LoanTransaction loanRefundTransaction) {
+        final Money transactionMonetaryAmount = Money.of(fromLoanAccount.getCurrency(), accountTransferDTO.getTransactionAmount());
+        AccountTransferDetails accountTransferDetails = accountTransferDTO.getAccountTransferDetails();
+        if (accountTransferDetails == null) {
+            accountTransferDetails = this.accountTransferDetailAssembler.assembleLoanToSavingsTransfer(fromLoanAccount, toSavingsAccount,
+                    accountTransferDTO.getTransferType());
+        }
+        AccountTransferTransaction accountTransferTransaction = AccountTransferTransaction.LoanTosavingsTransfer(accountTransferDetails,
+                deposit, loanRefundTransaction, accountTransferDTO.getTransactionDate(), transactionMonetaryAmount,
+                accountTransferDTO.getDescription());
+        accountTransferDetails.addAccountTransferTransaction(accountTransferTransaction);
+        return accountTransferDetails;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferDetailAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferDetailAssembler.java
new file mode 100755
index 0000000..3ffa445
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferDetailAssembler.java
@@ -0,0 +1,204 @@
+/**
+ * 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.account.domain;
+
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
+
+import java.util.Locale;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonElement;
+
+@Service
+public class AccountTransferDetailAssembler {
+
+    private final ClientRepositoryWrapper clientRepository;
+    private final OfficeRepository officeRepository;
+    private final SavingsAccountAssembler savingsAccountAssembler;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final LoanAssembler loanAccountAssembler;
+
+    @Autowired
+    public AccountTransferDetailAssembler(final ClientRepositoryWrapper clientRepository, final OfficeRepository officeRepository,
+            final SavingsAccountAssembler savingsAccountAssembler, final FromJsonHelper fromApiJsonHelper,
+            final LoanAssembler loanAccountAssembler) {
+        this.clientRepository = clientRepository;
+        this.officeRepository = officeRepository;
+        this.savingsAccountAssembler = savingsAccountAssembler;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.loanAccountAssembler = loanAccountAssembler;
+    }
+
+    public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
+
+        final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
+        final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId);
+
+        final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
+        final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId);
+
+        return assembleSavingsToSavingsTransfer(command, fromSavingsAccount, toSavingsAccount);
+
+    }
+
+    public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
+
+        final Long fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
+        final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId);
+
+        final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
+        final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
+
+        return assembleSavingsToLoanTransfer(command, fromSavingsAccount, toLoanAccount);
+
+    }
+
+    public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
+
+        final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
+        final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
+
+        final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
+        final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId);
+
+        return assembleLoanToSavingsTransfer(command, fromLoanAccount, toSavingsAccount);
+    }
+
+    public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
+            final SavingsAccount toSavingsAccount) {
+
+        final JsonElement element = command.parsedJson();
+
+        final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
+        final Office fromOffice = this.officeRepository.findOne(fromOfficeId);
+
+        final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
+        final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
+
+        final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
+        final Office toOffice = this.officeRepository.findOne(toOfficeId);
+
+        final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
+        final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
+
+        final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
+
+        return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
+                toSavingsAccount, transfertype);
+
+    }
+
+    public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
+            final Loan toLoanAccount) {
+
+        final JsonElement element = command.parsedJson();
+
+        final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
+        final Office fromOffice = this.officeRepository.findOne(fromOfficeId);
+
+        final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
+        final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
+
+        final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
+        final Office toOffice = this.officeRepository.findOne(toOfficeId);
+
+        final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
+        final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
+
+        final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
+
+        return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
+                transfertype);
+
+    }
+
+    public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command, final Loan fromLoanAccount,
+            final SavingsAccount toSavingsAccount) {
+
+        final JsonElement element = command.parsedJson();
+
+        final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
+        final Office fromOffice = this.officeRepository.findOne(fromOfficeId);
+
+        final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
+        final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
+
+        final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
+        final Office toOffice = this.officeRepository.findOne(toOfficeId);
+
+        final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
+        final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
+        final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
+
+        return AccountTransferDetails.LoanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
+                transfertype);
+    }
+
+    public AccountTransferDetails assembleSavingsToLoanTransfer(final SavingsAccount fromSavingsAccount, final Loan toLoanAccount,
+            Integer transferType) {
+        final Office fromOffice = fromSavingsAccount.office();
+        final Client fromClient = fromSavingsAccount.getClient();
+        final Office toOffice = toLoanAccount.getOffice();
+        final Client toClient = toLoanAccount.client();
+
+        return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
+                transferType);
+
+    }
+
+    public AccountTransferDetails assembleSavingsToSavingsTransfer(final SavingsAccount fromSavingsAccount,
+            final SavingsAccount toSavingsAccount, Integer transferType) {
+        final Office fromOffice = fromSavingsAccount.office();
+        final Client fromClient = fromSavingsAccount.getClient();
+        final Office toOffice = toSavingsAccount.office();
+        final Client toClient = toSavingsAccount.getClient();
+
+        return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
+                toSavingsAccount, transferType);
+    }
+
+    public AccountTransferDetails assembleLoanToSavingsTransfer(final Loan fromLoanAccount, final SavingsAccount toSavingsAccount,
+            Integer transferType) {
+        final Office fromOffice = fromLoanAccount.getOffice();
+        final Client fromClient = fromLoanAccount.client();
+        final Office toOffice = toSavingsAccount.office();
+        final Client toClient = toSavingsAccount.getClient();
+
+        return AccountTransferDetails.LoanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
+                transferType);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferDetailRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferDetailRepository.java
new file mode 100755
index 0000000..f4d881f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferDetailRepository.java
@@ -0,0 +1,25 @@
+/**
+ * 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.account.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface AccountTransferDetailRepository extends JpaRepository<AccountTransferDetails, Long>,
+        JpaSpecificationExecutor<AccountTransferDetails> {}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferDetails.java
new file mode 100755
index 0000000..7106602
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferDetails.java
@@ -0,0 +1,162 @@
+/**
+ * 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.account.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_account_transfer_details")
+public class AccountTransferDetails extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "from_office_id", nullable = false)
+    private Office fromOffice;
+
+    @ManyToOne
+    @JoinColumn(name = "from_client_id", nullable = false)
+    private Client fromClient;
+
+    @ManyToOne
+    @JoinColumn(name = "from_savings_account_id", nullable = true)
+    private SavingsAccount fromSavingsAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "to_office_id", nullable = false)
+    private Office toOffice;
+
+    @ManyToOne
+    @JoinColumn(name = "to_client_id", nullable = false)
+    private Client toClient;
+
+    @ManyToOne
+    @JoinColumn(name = "to_savings_account_id", nullable = true)
+    private SavingsAccount toSavingsAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "to_loan_account_id", nullable = true)
+    private Loan toLoanAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "from_loan_account_id", nullable = true)
+    private Loan fromLoanAccount;
+
+    @Column(name = "transfer_type")
+    private Integer transferType;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "accountTransferDetails", orphanRemoval = true)
+    private final List<AccountTransferTransaction> accountTransferTransactions = new ArrayList<>();
+
+    @OneToOne(mappedBy = "accountTransferDetails", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY)
+    private AccountTransferStandingInstruction accountTransferStandingInstruction;
+
+    public static AccountTransferDetails savingsToSavingsTransfer(final Office fromOffice, final Client fromClient,
+            final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
+            Integer transferType) {
+
+        return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, toOffice, toClient, toSavingsAccount, null,
+                transferType, null);
+    }
+
+    public static AccountTransferDetails savingsToLoanTransfer(final Office fromOffice, final Client fromClient,
+            final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final Loan toLoanAccount,
+            Integer transferType) {
+        return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, toOffice, toClient, null, toLoanAccount,
+                transferType, null);
+    }
+
+    public static AccountTransferDetails LoanTosavingsTransfer(final Office fromOffice, final Client fromClient,
+            final Loan fromLoanAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
+            Integer transferType) {
+        return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, toOffice, toClient, toSavingsAccount, null,
+                transferType, null);
+    }
+
+    protected AccountTransferDetails() {
+        //
+    }
+
+    private AccountTransferDetails(final Office fromOffice, final Client fromClient, final SavingsAccount fromSavingsAccount,
+            final Loan fromLoanAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
+            final Loan toLoanAccount, final Integer transferType,
+            final AccountTransferStandingInstruction accountTransferStandingInstruction) {
+        this.fromOffice = fromOffice;
+        this.fromClient = fromClient;
+        this.fromSavingsAccount = fromSavingsAccount;
+        this.fromLoanAccount = fromLoanAccount;
+        this.toOffice = toOffice;
+        this.toClient = toClient;
+        this.toSavingsAccount = toSavingsAccount;
+        this.toLoanAccount = toLoanAccount;
+        this.transferType = transferType;
+        this.accountTransferStandingInstruction = accountTransferStandingInstruction;
+    }
+
+    public SavingsAccount toSavingsAccount() {
+        return this.toSavingsAccount;
+    }
+
+    public SavingsAccount fromSavingsAccount() {
+        return this.fromSavingsAccount;
+    }
+
+    public void addAccountTransferTransaction(AccountTransferTransaction accountTransferTransaction) {
+        this.accountTransferTransactions.add(accountTransferTransaction);
+    }
+
+    public void updateAccountTransferStandingInstruction(final AccountTransferStandingInstruction accountTransferStandingInstruction) {
+        this.accountTransferStandingInstruction = accountTransferStandingInstruction;
+    }
+
+    public Loan toLoanAccount() {
+        return this.toLoanAccount;
+    }
+
+    public Loan fromLoanAccount() {
+        return this.fromLoanAccount;
+    }
+
+    public AccountTransferStandingInstruction accountTransferStandingInstruction() {
+        return this.accountTransferStandingInstruction;
+    }
+
+    public AccountTransferType transferType() {
+        return AccountTransferType.fromInt(this.transferType);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRecurrenceType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRecurrenceType.java
new file mode 100755
index 0000000..5ac0c18
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRecurrenceType.java
@@ -0,0 +1,72 @@
+/**
+ * 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.account.domain;
+
+/**
+ * Enum representation of loan status states.
+ */
+public enum AccountTransferRecurrenceType {
+
+    INVALID(0, "accountTransferRecurrenceType.invalid"), //
+    PERIODIC(1, "accountTransferRecurrenceType.periodic"), //
+    AS_PER_DUES(2, "accountTransferRecurrenceType.as.per.dues"); //
+
+    private final Integer value;
+    private final String code;
+
+    public static AccountTransferRecurrenceType fromInt(final Integer statusValue) {
+
+        AccountTransferRecurrenceType enumeration = AccountTransferRecurrenceType.INVALID;
+        switch (statusValue) {
+            case 1:
+                enumeration = AccountTransferRecurrenceType.PERIODIC;
+            break;
+            case 2:
+                enumeration = AccountTransferRecurrenceType.AS_PER_DUES;
+            break;
+        }
+        return enumeration;
+    }
+
+    private AccountTransferRecurrenceType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final AccountTransferRecurrenceType state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isPeriodicRecurrence() {
+        return this.value.equals(AccountTransferRecurrenceType.PERIODIC.getValue());
+    }
+
+    public boolean isDuesRecurrence() {
+        return this.value.equals(AccountTransferRecurrenceType.AS_PER_DUES.getValue());
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRepository.java
new file mode 100644
index 0000000..8158cb8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRepository.java
@@ -0,0 +1,43 @@
+/**
+ * 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.account.domain;
+
+import java.util.Collection;
+import java.util.List;
+
+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 AccountTransferRepository extends JpaRepository<AccountTransferTransaction, Long>,
+        JpaSpecificationExecutor<AccountTransferTransaction> {
+
+    @Query("from AccountTransferTransaction att where att.accountTransferDetails.fromLoanAccount.id= :accountNumber and att.reversed=false")
+    List<AccountTransferTransaction> findByFromLoanId(@Param("accountNumber") Long accountNumber);
+
+    @Query("from AccountTransferTransaction att where (att.accountTransferDetails.fromLoanAccount.id= :accountNumber or att.accountTransferDetails.toLoanAccount.id=:accountNumber) and att.reversed=false order by att.id desc")
+    List<AccountTransferTransaction> findAllByLoanId(@Param("accountNumber") Long accountNumber);
+
+    @Query("from AccountTransferTransaction att where att.toLoanTransaction.id= :loanTransactionId and att.reversed=false")
+    AccountTransferTransaction findByToLoanTransactionId(@Param("loanTransactionId") Long loanTransactionId);
+
+    @Query("from AccountTransferTransaction att where att.fromLoanTransaction.id IN :loanTransactions and att.reversed=false")
+    List<AccountTransferTransaction> findByFromLoanTransactions(@Param("loanTransactions") Collection<Long> loanTransactions);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstruction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstruction.java
new file mode 100755
index 0000000..a9b2858
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstruction.java
@@ -0,0 +1,294 @@
+/**
+ * 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.account.domain;
+
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.instructionTypeParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.priorityParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceFrequencyParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceIntervalParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceOnMonthDayParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceTypeParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validFromParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validTillParamName;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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 javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_account_transfer_standing_instructions", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "name") })
+public class AccountTransferStandingInstruction extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "account_transfer_details_id", nullable = true)
+    private AccountTransferDetails accountTransferDetails;
+
+    @Column(name = "name")
+    private String name;
+
+    @Column(name = "priority")
+    private Integer priority;
+
+    @Column(name = "instruction_type")
+    private Integer instructionType;
+
+    @Column(name = "status")
+    private Integer status;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amount;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "valid_from")
+    private Date validFrom;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "valid_till")
+    private Date validTill;
+
+    @Column(name = "recurrence_type")
+    private Integer recurrenceType;
+
+    @Column(name = "recurrence_frequency")
+    private Integer recurrenceFrequency;
+
+    @Column(name = "recurrence_interval")
+    private Integer recurrenceInterval;
+
+    @Column(name = "recurrence_on_day")
+    private Integer recurrenceOnDay;
+
+    @Column(name = "recurrence_on_month")
+    private Integer recurrenceOnMonth;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "last_run_date")
+    private Date latsRunDate;
+
+    protected AccountTransferStandingInstruction() {
+
+    }
+
+    public static AccountTransferStandingInstruction create(final AccountTransferDetails accountTransferDetails, final String name,
+            final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount,
+            final LocalDate validFrom, final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency,
+            final Integer recurrenceInterval, final MonthDay recurrenceOnMonthDay) {
+        Integer recurrenceOnDay = null;
+        Integer recurrenceOnMonth = null;
+        if (recurrenceOnMonthDay != null) {
+            recurrenceOnDay = recurrenceOnMonthDay.getDayOfMonth();
+            recurrenceOnMonth = recurrenceOnMonthDay.getMonthOfYear();
+        }
+        return new AccountTransferStandingInstruction(accountTransferDetails, name, priority, instructionType, status, amount, validFrom,
+                validTill, recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnDay, recurrenceOnMonth);
+    }
+
+    private AccountTransferStandingInstruction(final AccountTransferDetails accountTransferDetails, final String name,
+            final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount,
+            final LocalDate validFrom, final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency,
+            final Integer recurrenceInterval, final Integer recurrenceOnDay, final Integer recurrenceOnMonth) {
+        this.accountTransferDetails = accountTransferDetails;
+        this.name = name;
+        this.priority = priority;
+        this.instructionType = instructionType;
+        this.status = status;
+        this.amount = amount;
+        if (validFrom != null) {
+            this.validFrom = validFrom.toDate();
+        }
+        if (validTill == null) {
+            this.validTill = null;
+        } else {
+            this.validTill = validTill.toDate();
+        }
+        this.recurrenceType = recurrenceType;
+        this.recurrenceFrequency = recurrenceFrequency;
+        this.recurrenceInterval = recurrenceInterval;
+        this.recurrenceOnDay = recurrenceOnDay;
+        this.recurrenceOnMonth = recurrenceOnMonth;
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(STANDING_INSTRUCTION_RESOURCE_NAME);
+
+        validateDependencies(baseDataValidator);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public Map<String, Object> update(JsonCommand command) {
+        final Map<String, Object> actualChanges = new HashMap<>();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(STANDING_INSTRUCTION_RESOURCE_NAME);
+
+        if (StandingInstructionStatus.fromInt(this.status).isDeleted()) {
+            baseDataValidator.reset().parameter(statusParamName).failWithCode("can.not.modify.once.deleted");
+        }
+
+        if (command.isChangeInDateParameterNamed(validFromParamName, this.validFrom)) {
+            final LocalDate newValue = command.localDateValueOfParameterNamed(validFromParamName);
+            actualChanges.put(validFromParamName, newValue);
+            this.validFrom = newValue.toDate();
+        }
+
+        if (command.isChangeInDateParameterNamed(validTillParamName, this.validTill)) {
+            final LocalDate newValue = command.localDateValueOfParameterNamed(validTillParamName);
+            actualChanges.put(validTillParamName, newValue);
+            this.validTill = newValue.toDate();
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
+            actualChanges.put(amountParamName, newValue);
+            this.amount = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(statusParamName, this.status)) {
+            final Integer newValue = command.integerValueOfParameterNamed(statusParamName);
+            actualChanges.put(statusParamName, newValue);
+            this.status = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(priorityParamName, this.priority)) {
+            final Integer newValue = command.integerValueOfParameterNamed(priorityParamName);
+            actualChanges.put(priorityParamName, newValue);
+            this.priority = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(instructionTypeParamName, this.instructionType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(instructionTypeParamName);
+            actualChanges.put(instructionTypeParamName, newValue);
+            this.instructionType = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(recurrenceTypeParamName, this.recurrenceType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(recurrenceTypeParamName);
+            actualChanges.put(recurrenceTypeParamName, newValue);
+            this.recurrenceType = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(recurrenceFrequencyParamName, this.recurrenceFrequency)) {
+            final Integer newValue = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
+            actualChanges.put(recurrenceFrequencyParamName, newValue);
+            this.recurrenceFrequency = newValue;
+        }
+
+        if (command.hasParameter(recurrenceOnMonthDayParamName)) {
+            final MonthDay monthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
+            final String actualValueEntered = command.stringValueOfParameterNamed(recurrenceOnMonthDayParamName);
+            final Integer dayOfMonthValue = monthDay.getDayOfMonth();
+            if (this.recurrenceOnDay != dayOfMonthValue) {
+                actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
+                this.recurrenceOnDay = dayOfMonthValue;
+            }
+
+            final Integer monthOfYear = monthDay.getMonthOfYear();
+            if (this.recurrenceOnMonth != monthOfYear) {
+                actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
+                this.recurrenceOnMonth = monthOfYear;
+            }
+        }
+
+        if (command.isChangeInIntegerParameterNamed(recurrenceIntervalParamName, this.recurrenceInterval)) {
+            final Integer newValue = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
+            actualChanges.put(recurrenceIntervalParamName, newValue);
+            this.recurrenceInterval = newValue;
+        }
+        validateDependencies(baseDataValidator);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        return actualChanges;
+    }
+
+    private void validateDependencies(final DataValidatorBuilder baseDataValidator) {
+
+        if (this.validTill != null && this.validFrom != null) {
+            baseDataValidator.reset().parameter(validTillParamName).value(LocalDate.fromDateFields(this.validTill))
+                    .validateDateAfter(LocalDate.fromDateFields(this.validFrom));
+        }
+
+        if (AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence()) {
+            baseDataValidator.reset().parameter(recurrenceFrequencyParamName).value(this.recurrenceFrequency).notNull();
+            baseDataValidator.reset().parameter(recurrenceIntervalParamName).value(this.recurrenceInterval).notNull();
+            if (this.recurrenceFrequency != null) {
+                PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(this.recurrenceFrequency);
+                if (frequencyType.isMonthly()) {
+                    baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
+                } else if (frequencyType.isYearly()) {
+                    baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
+                    baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnMonth).notNull();
+                }
+            }
+        }
+
+        if (this.accountTransferDetails.toSavingsAccount() != null) {
+            baseDataValidator.reset().parameter(instructionTypeParamName).value(this.instructionType).notNull().inMinMaxRange(1, 1);
+            baseDataValidator.reset().parameter(recurrenceTypeParamName).value(this.recurrenceType).notNull().inMinMaxRange(1, 1);
+        }
+
+        if (StandingInstructionType.fromInt(this.instructionType).isFixedAmoutTransfer()) {
+            baseDataValidator.reset().parameter(amountParamName).value(this.amount).notNull();
+        }
+
+        String errorCode = null;
+        if (this.accountTransferDetails.transferType().isAccountTransfer()
+                && (this.accountTransferDetails.fromSavingsAccount() == null || this.accountTransferDetails.toSavingsAccount() == null)) {
+            errorCode = "not.account.transfer";
+        } else if (this.accountTransferDetails.transferType().isLoanRepayment()
+                && (this.accountTransferDetails.fromSavingsAccount() == null || this.accountTransferDetails.toLoanAccount() == null)) {
+            errorCode = "not.loan.repayment";
+        }
+        if (errorCode != null) {
+            baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
+        }
+
+    }
+
+    public void updateLatsRunDate(Date latsRunDate) {
+        this.latsRunDate = latsRunDate;
+    }
+    
+    public void updateStatus(Integer status){
+        this.status = status;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferTransaction.java
new file mode 100644
index 0000000..d47ea1e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferTransaction.java
@@ -0,0 +1,148 @@
+/**
+ * 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.account.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+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.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_account_transfer_transaction")
+public class AccountTransferTransaction extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "account_transfer_details_id", nullable = true)
+    private AccountTransferDetails accountTransferDetails;
+
+    @ManyToOne
+    @JoinColumn(name = "from_savings_transaction_id", nullable = true)
+    private SavingsAccountTransaction fromSavingsTransaction;
+
+    @ManyToOne
+    @JoinColumn(name = "to_savings_transaction_id", nullable = true)
+    private SavingsAccountTransaction toSavingsTransaction;
+
+    @ManyToOne
+    @JoinColumn(name = "to_loan_transaction_id", nullable = true)
+    private LoanTransaction toLoanTransaction;
+
+    @ManyToOne
+    @JoinColumn(name = "from_loan_transaction_id", nullable = true)
+    private LoanTransaction fromLoanTransaction;
+
+    @Column(name = "is_reversed", nullable = false)
+    private boolean reversed = false;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "transaction_date")
+    private Date date;
+
+    @Embedded
+    private MonetaryCurrency currency;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "description", length = 100)
+    private String description;
+
+    public static AccountTransferTransaction savingsToSavingsTransfer(final AccountTransferDetails accountTransferDetails,
+            final SavingsAccountTransaction withdrawal, final SavingsAccountTransaction deposit, final LocalDate transactionDate,
+            final Money transactionAmount, final String description) {
+
+        return new AccountTransferTransaction(accountTransferDetails, withdrawal, deposit, null, null, transactionDate, transactionAmount,
+                description);
+    }
+
+    public static AccountTransferTransaction savingsToLoanTransfer(final AccountTransferDetails accountTransferDetails,
+            final SavingsAccountTransaction withdrawal, final LoanTransaction loanRepaymentTransaction, final LocalDate transactionDate,
+            final Money transactionAmount, final String description) {
+        return new AccountTransferTransaction(accountTransferDetails, withdrawal, null, loanRepaymentTransaction, null, transactionDate,
+                transactionAmount, description);
+    }
+
+    public static AccountTransferTransaction LoanTosavingsTransfer(final AccountTransferDetails accountTransferDetails,
+            final SavingsAccountTransaction deposit, final LoanTransaction loanRefundTransaction, final LocalDate transactionDate,
+            final Money transactionAmount, final String description) {
+        return new AccountTransferTransaction(accountTransferDetails, null, deposit, null, loanRefundTransaction, transactionDate,
+                transactionAmount, description);
+    }
+
+    protected AccountTransferTransaction() {
+        //
+    }
+
+    private AccountTransferTransaction(final AccountTransferDetails accountTransferDetails, final SavingsAccountTransaction withdrawal,
+            final SavingsAccountTransaction deposit, final LoanTransaction loanRepaymentTransaction,
+            final LoanTransaction loanRefundTransaction, final LocalDate transactionDate, final Money transactionAmount,
+            final String description) {
+        this.accountTransferDetails = accountTransferDetails;
+        this.fromLoanTransaction = loanRefundTransaction;
+        this.fromSavingsTransaction = withdrawal;
+        this.toSavingsTransaction = deposit;
+        this.toLoanTransaction = loanRepaymentTransaction;
+        this.date = transactionDate.toDate();
+        this.currency = transactionAmount.getCurrency();
+        this.amount = transactionAmount.getAmountDefaultedToNullIfZero();
+        this.description = description;
+    }
+
+    public LoanTransaction getFromLoanTransaction() {
+        return this.fromLoanTransaction;
+    }
+
+    public SavingsAccountTransaction getFromTransaction() {
+        return this.fromSavingsTransaction;
+    }
+
+    public LoanTransaction getToLoanTransaction() {
+        return this.toLoanTransaction;
+    }
+
+    public SavingsAccountTransaction getToSavingsTransaction() {
+        return this.toSavingsTransaction;
+    }
+
+    public void reverse() {
+        this.reversed = true;
+    }
+
+    public void updateToLoanTransaction(LoanTransaction toLoanTransaction) {
+        this.toLoanTransaction = toLoanTransaction;
+    }
+
+    public AccountTransferDetails accountTransferDetails() {
+        return this.accountTransferDetails;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferType.java
new file mode 100755
index 0000000..4a273a0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferType.java
@@ -0,0 +1,87 @@
+/**
+ * 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.account.domain;
+
+/**
+ * Enum representation of loan status states.
+ */
+public enum AccountTransferType {
+
+    INVALID(0, "accountTransferType.invalid"), //
+    ACCOUNT_TRANSFER(1, "accountTransferType.account.transfer"), //
+    LOAN_REPAYMENT(2, "accountTransferType.loan.repayment"), //
+    CHARGE_PAYMENT(3, "accountTransferType.charge.payment"), //
+    INTEREST_TRANSFER(4, "accountTransferType.interest.transfer"); //
+
+    private final Integer value;
+    private final String code;
+
+    public static AccountTransferType fromInt(final Integer statusValue) {
+
+        AccountTransferType enumeration = AccountTransferType.INVALID;
+        switch (statusValue) {
+            case 1:
+                enumeration = AccountTransferType.ACCOUNT_TRANSFER;
+            break;
+            case 2:
+                enumeration = AccountTransferType.LOAN_REPAYMENT;
+            break;
+            case 3:
+                enumeration = AccountTransferType.CHARGE_PAYMENT;
+            break;
+            case 4:
+                enumeration = AccountTransferType.INTEREST_TRANSFER;
+            break;
+        }
+        return enumeration;
+    }
+
+    private AccountTransferType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final AccountTransferType state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isAccountTransfer() {
+        return this.value.equals(AccountTransferType.ACCOUNT_TRANSFER.getValue());
+    }
+
+    public boolean isLoanRepayment() {
+        return this.value.equals(AccountTransferType.LOAN_REPAYMENT.getValue());
+    }
+
+    public boolean isChargePayment() {
+        return this.value.equals(AccountTransferType.CHARGE_PAYMENT.getValue());
+    }
+
+    public boolean isInterestTransfer() {
+        return this.value.equals(AccountTransferType.INTEREST_TRANSFER.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionAssembler.java
new file mode 100755
index 0000000..90982bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionAssembler.java
@@ -0,0 +1,94 @@
+/**
+ * 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.account.domain;
+
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.instructionTypeParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.nameParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.priorityParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceFrequencyParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceIntervalParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceOnMonthDayParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceTypeParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validFromParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validTillParamName;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class StandingInstructionAssembler {
+
+    private final AccountTransferDetailAssembler accountTransferDetailAssembler;
+
+    @Autowired
+    public StandingInstructionAssembler(final AccountTransferDetailAssembler accountTransferDetailAssembler) {
+
+        this.accountTransferDetailAssembler = accountTransferDetailAssembler;
+    }
+
+    public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
+        final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToSavingsTransfer(command);
+        assembleStandingInstruction(command, accountTransferDetails);
+        return accountTransferDetails;
+    }
+
+    public void assembleStandingInstruction(final JsonCommand command, final AccountTransferDetails accountTransferDetails) {
+        final LocalDate validFrom = command.localDateValueOfParameterNamed(validFromParamName);
+        final LocalDate validTill = command.localDateValueOfParameterNamed(validTillParamName);
+        BigDecimal amount = null;
+        final BigDecimal transferAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
+        if (transferAmount != null) {
+            final Money monetaryAmount = Money.of(accountTransferDetails.fromSavingsAccount().getCurrency(), transferAmount);
+            amount = monetaryAmount.getAmount();
+        }
+        final Integer status = command.integerValueOfParameterNamed(statusParamName);
+        final Integer priority = command.integerValueOfParameterNamed(priorityParamName);
+        final Integer standingInstructionType = command.integerValueOfParameterNamed(instructionTypeParamName);
+        final Integer recurrenceType = command.integerValueOfParameterNamed(recurrenceTypeParamName);
+        final Integer recurrenceFrequency = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
+        final MonthDay recurrenceOnMonthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
+        final Integer recurrenceInterval = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
+        final String name = command.stringValueOfParameterNamed(nameParamName);
+        AccountTransferStandingInstruction accountTransferStandingInstruction = AccountTransferStandingInstruction.create(
+                accountTransferDetails, name, priority, standingInstructionType, status, amount, validFrom, validTill, recurrenceType,
+                recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay);
+        accountTransferDetails.updateAccountTransferStandingInstruction(accountTransferStandingInstruction);
+    }
+
+    public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
+        final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(command);
+        assembleStandingInstruction(command, accountTransferDetails);
+        return accountTransferDetails;
+    }
+
+    public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
+        final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleLoanToSavingsTransfer(command);
+        assembleStandingInstruction(command, accountTransferDetails);
+        return accountTransferDetails;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionPriority.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionPriority.java
new file mode 100755
index 0000000..c55e942
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionPriority.java
@@ -0,0 +1,87 @@
+/**
+ * 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.account.domain;
+
+/**
+ * Enum representation of loan status states.
+ */
+public enum StandingInstructionPriority {
+
+    INVALID(0, "standingInstructionPriority.invalid"), //
+    URGENT(1, "standingInstructionPriority.urgent"), //
+    HIGH(2, "standingInstructionPriority.high"), //
+    MEDIUM(3, "standingInstructionPriority.medium"), //
+    LOW(4, "standingInstructionPriority.low");
+
+    private final Integer value;
+    private final String code;
+
+    public static StandingInstructionPriority fromInt(final Integer statusValue) {
+
+        StandingInstructionPriority enumeration = StandingInstructionPriority.INVALID;
+        switch (statusValue) {
+            case 1:
+                enumeration = StandingInstructionPriority.URGENT;
+            break;
+            case 2:
+                enumeration = StandingInstructionPriority.HIGH;
+            break;
+            case 3:
+                enumeration = StandingInstructionPriority.MEDIUM;
+            break;
+            case 4:
+                enumeration = StandingInstructionPriority.LOW;
+            break;
+        }
+        return enumeration;
+    }
+
+    private StandingInstructionPriority(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final StandingInstructionPriority state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isHighPriority() {
+        return this.value.equals(StandingInstructionPriority.HIGH.getValue());
+    }
+
+    public boolean isLowPriority() {
+        return this.value.equals(StandingInstructionPriority.LOW.getValue());
+    }
+
+    public boolean isUrgentPriority() {
+        return this.value.equals(StandingInstructionPriority.URGENT.getValue());
+    }
+
+    public boolean isMediumPriority() {
+        return this.value.equals(StandingInstructionPriority.MEDIUM.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionRepository.java
new file mode 100755
index 0000000..ac8e8a0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionRepository.java
@@ -0,0 +1,25 @@
+/**
+ * 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.account.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface StandingInstructionRepository extends JpaRepository<AccountTransferStandingInstruction, Long>,
+        JpaSpecificationExecutor<AccountTransferStandingInstruction> {}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionStatus.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionStatus.java
new file mode 100755
index 0000000..1ff99a0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionStatus.java
@@ -0,0 +1,80 @@
+/**
+ * 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.account.domain;
+
+/**
+ * Enum representation of loan status states.
+ */
+public enum StandingInstructionStatus {
+
+    INVALID(0, "standingInstructionStatus.invalid"), //
+    ACTIVE(1, "standingInstructionStatus.active"), //
+    DISABLED(2, "standingInstructionStatus.disabled"), //
+    DELETED(3, "standingInstructionStatus.deleted");
+
+    private final Integer value;
+    private final String code;
+
+    public static StandingInstructionStatus fromInt(final Integer statusValue) {
+
+        StandingInstructionStatus enumeration = StandingInstructionStatus.INVALID;
+        switch (statusValue) {
+            case 1:
+                enumeration = StandingInstructionStatus.ACTIVE;
+            break;
+            case 2:
+                enumeration = StandingInstructionStatus.DISABLED;
+            break;
+            case 3:
+                enumeration = StandingInstructionStatus.DELETED;
+            break;
+        }
+        return enumeration;
+    }
+
+    private StandingInstructionStatus(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final StandingInstructionStatus state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isDisabled() {
+        return this.value.equals(StandingInstructionStatus.DISABLED.getValue());
+    }
+
+    public boolean isDeleted() {
+        return this.value.equals(StandingInstructionStatus.DELETED.getValue());
+    }
+
+    public boolean isActive() {
+        return this.value.equals(StandingInstructionStatus.ACTIVE.getValue());
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionType.java
new file mode 100755
index 0000000..b212d3a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionType.java
@@ -0,0 +1,72 @@
+/**
+ * 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.account.domain;
+
+/**
+ * Enum representation of loan status states.
+ */
+public enum StandingInstructionType {
+
+    INVALID(0, "standingInstructionType.invalid"), //
+    FIXED(1, "standingInstructionType.fixed"), //
+    DUES(2, "standingInstructionType.dues"); //
+
+    private final Integer value;
+    private final String code;
+
+    public static StandingInstructionType fromInt(final Integer statusValue) {
+
+        StandingInstructionType enumeration = StandingInstructionType.INVALID;
+        switch (statusValue) {
+            case 1:
+                enumeration = StandingInstructionType.FIXED;
+            break;
+            case 2:
+                enumeration = StandingInstructionType.DUES;
+            break;
+        }
+        return enumeration;
+    }
+
+    private StandingInstructionType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final StandingInstructionType state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isFixedAmoutTransfer() {
+        return this.value.equals(StandingInstructionType.FIXED.getValue());
+    }
+
+    public boolean isDuesAmoutTransfer() {
+        return this.value.equals(StandingInstructionType.DUES.getValue());
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/exception/AccountTransferNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/exception/AccountTransferNotFoundException.java
new file mode 100644
index 0000000..14c729d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/exception/AccountTransferNotFoundException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.account.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class AccountTransferNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public AccountTransferNotFoundException(final Long id) {
+        super("error.msg.accounttransfer.id.invalid", "Account transfer with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/exception/StandingInstructionNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/exception/StandingInstructionNotFoundException.java
new file mode 100755
index 0000000..e18f3e3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/exception/StandingInstructionNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.account.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when loan resources are not found.
+ */
+public class StandingInstructionNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public StandingInstructionNotFoundException(final Long id) {
+        super("error.msg.standinginstruction.id.invalid", "AccountTransferStandingInstruction with identifier " + id + " does not exist",
+                id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/CreateAccountTransferCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/CreateAccountTransferCommandHandler.java
new file mode 100644
index 0000000..85bca07
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/CreateAccountTransferCommandHandler.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.portfolio.account.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ACCOUNTTRANSFER", action = "CREATE")
+public class CreateAccountTransferCommandHandler implements NewCommandSourceHandler {
+
+    private final AccountTransfersWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateAccountTransferCommandHandler(final AccountTransfersWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.create(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/CreateStandingInstructionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/CreateStandingInstructionCommandHandler.java
new file mode 100755
index 0000000..2d57044
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/CreateStandingInstructionCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.account.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "STANDINGINSTRUCTION", action = "CREATE")
+public class CreateStandingInstructionCommandHandler implements NewCommandSourceHandler {
+
+    private StandingInstructionWritePlatformService standingInstructionWritePlatformService;
+
+    @Autowired
+    public CreateStandingInstructionCommandHandler(StandingInstructionWritePlatformService standingInstructionWritePlatformService) {
+        this.standingInstructionWritePlatformService = standingInstructionWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.standingInstructionWritePlatformService.create(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/DeleteStandingInstructionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/DeleteStandingInstructionCommandHandler.java
new file mode 100755
index 0000000..10c9210
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/DeleteStandingInstructionCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.account.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "STANDINGINSTRUCTION", action = "DELETE")
+public class DeleteStandingInstructionCommandHandler implements NewCommandSourceHandler {
+
+    private StandingInstructionWritePlatformService standingInstructionWritePlatformService;
+
+    @Autowired
+    public DeleteStandingInstructionCommandHandler(StandingInstructionWritePlatformService standingInstructionWritePlatformService) {
+        this.standingInstructionWritePlatformService = standingInstructionWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.standingInstructionWritePlatformService.delete(command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/RefundByTransferCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/RefundByTransferCommandHandler.java
new file mode 100644
index 0000000..5165e31
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/RefundByTransferCommandHandler.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.portfolio.account.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ACCOUNTTRANSFER", action = "REFUNDBYTRANSFER")
+public class RefundByTransferCommandHandler implements NewCommandSourceHandler {
+
+    private final AccountTransfersWritePlatformService writePlatformService;
+
+    @Autowired
+    public RefundByTransferCommandHandler(final AccountTransfersWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.refundByTransfer(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/UpdateStandingInstructionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/UpdateStandingInstructionCommandHandler.java
new file mode 100755
index 0000000..5637fbc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/handler/UpdateStandingInstructionCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.account.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "STANDINGINSTRUCTION", action = "UPDATE")
+public class UpdateStandingInstructionCommandHandler implements NewCommandSourceHandler {
+
+    private StandingInstructionWritePlatformService standingInstructionWritePlatformService;
+
+    @Autowired
+    public UpdateStandingInstructionCommandHandler(StandingInstructionWritePlatformService standingInstructionWritePlatformService) {
+        this.standingInstructionWritePlatformService = standingInstructionWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.standingInstructionWritePlatformService.update(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformService.java
new file mode 100755
index 0000000..498ea84
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformService.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.portfolio.account.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.account.data.AccountAssociationsData;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+
+public interface AccountAssociationsReadPlatformService {
+
+    public PortfolioAccountData retriveLoanLinkedAssociation(final Long loanId);
+
+    public boolean isLinkedWithAnyActiveAccount(final Long savingsId);
+
+    public PortfolioAccountData retriveSavingsLinkedAssociation(final Long savingsId);
+
+    public Collection<AccountAssociationsData> retriveLoanAssociations(Long loanId, Integer associationType);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java
new file mode 100755
index 0000000..5914692
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java
@@ -0,0 +1,195 @@
+/**
+ * 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.account.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.portfolio.account.data.AccountAssociationsData;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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 AccountAssociationsReadPlatformServiceImpl implements AccountAssociationsReadPlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(AccountAssociationsReadPlatformServiceImpl.class);
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public AccountAssociationsReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public PortfolioAccountData retriveLoanLinkedAssociation(final Long loanId) {
+        PortfolioAccountData linkedAccount = null;
+        final AccountAssociationsMapper mapper = new AccountAssociationsMapper();
+        final String sql = "select " + mapper.schema() + " where aa.loan_account_id = ? and aa.association_type_enum = ?";
+        try {
+            final AccountAssociationsData accountAssociationsData = this.jdbcTemplate.queryForObject(sql, mapper, loanId,
+                    AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
+            if (accountAssociationsData != null) {
+                linkedAccount = accountAssociationsData.linkedAccount();
+            }
+        } catch (final EmptyResultDataAccessException e) {
+            logger.debug("Linking account is not configured");
+        }
+        return linkedAccount;
+    }
+
+    @Override
+    public Collection<AccountAssociationsData> retriveLoanAssociations(final Long loanId, final Integer associationType) {
+        final AccountAssociationsMapper mapper = new AccountAssociationsMapper();
+        final String sql = "select " + mapper.schema() + " where aa.loan_account_id = ? and aa.association_type_enum = ?";
+        try {
+            return this.jdbcTemplate.query(sql, mapper, loanId, associationType);
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+
+    }
+
+    @Override
+    public PortfolioAccountData retriveSavingsLinkedAssociation(final Long savingsId) {
+        PortfolioAccountData linkedAccount = null;
+        final AccountAssociationsMapper mapper = new AccountAssociationsMapper();
+        final String sql = "select " + mapper.schema() + " where aa.savings_account_id = ? and aa.association_type_enum = ?";
+        try {
+            final AccountAssociationsData accountAssociationsData = this.jdbcTemplate.queryForObject(sql, mapper, savingsId,
+                    AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
+            if (accountAssociationsData != null) {
+                linkedAccount = accountAssociationsData.linkedAccount();
+            }
+        } catch (final EmptyResultDataAccessException e) {
+            logger.debug("Linking account is not configured");
+        }
+        return linkedAccount;
+    }
+
+    @Override
+    public boolean isLinkedWithAnyActiveAccount(final Long savingsId) {
+        boolean hasActiveAccount = false;
+
+        final String sql1 = "select aa.is_active as active,aa.association_type_enum as type, loanAccount.loan_status_id as loanStatus,"
+                + "savingAccount.status_enum as savingsStatus from m_portfolio_account_associations aa "
+                + "left join m_loan loanAccount on loanAccount.id = aa.loan_account_id "
+                + "left join m_savings_account savingAccount on savingAccount.id = aa.savings_account_id "
+                + "where aa.linked_savings_account_id = ?";
+
+        final List<Map<String, Object>> statusList = this.jdbcTemplate.queryForList(sql1, savingsId);
+        for (final Map<String, Object> statusMap : statusList) {
+            AccountAssociationType associationType = AccountAssociationType.fromInt((Integer) statusMap.get("type"));
+            if (!associationType.isLinkedAccountAssociation() && (Boolean) statusMap.get("active")) {
+                hasActiveAccount = true;
+                break;
+            }
+
+            if (statusMap.get("loanStatus") != null) {
+                final LoanStatus loanStatus = LoanStatus.fromInt((Integer) statusMap.get("loanStatus"));
+                if (loanStatus.isActiveOrAwaitingApprovalOrDisbursal() || loanStatus.isUnderTransfer()) {
+                    hasActiveAccount = true;
+                    break;
+                }
+            }
+
+            if (statusMap.get("savingsStatus") != null) {
+                final SavingsAccountStatusType saveStatus = SavingsAccountStatusType.fromInt((Integer) statusMap.get("savingsStatus"));
+                if (saveStatus.isActiveOrAwaitingApprovalOrDisbursal() || saveStatus.isUnderTransfer()) {
+                    hasActiveAccount = true;
+                    break;
+                }
+            }
+        }
+
+        return hasActiveAccount;
+    }
+
+    private static final class AccountAssociationsMapper implements RowMapper<AccountAssociationsData> {
+
+        private final String schemaSql;
+
+        public AccountAssociationsMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder();
+            sqlBuilder.append("aa.id as id,");
+            // sqlBuilder.append("savingsAccount.id as savingsAccountId, savingsAccount.account_no as savingsAccountNo,");
+            sqlBuilder.append("loanAccount.id as loanAccountId, loanAccount.account_no as loanAccountNo,");
+            // sqlBuilder.append("linkLoanAccount.id as linkLoanAccountId, linkLoanAccount.account_no as linkLoanAccountNo, ");
+            sqlBuilder.append("linkSavingsAccount.id as linkSavingsAccountId, linkSavingsAccount.account_no as linkSavingsAccountNo ");
+            sqlBuilder.append("from m_portfolio_account_associations aa ");
+            // sqlBuilder.append("left join m_savings_account savingsAccount on savingsAccount.id = aa.savings_account_id ");
+            sqlBuilder.append("left join m_loan loanAccount on loanAccount.id = aa.loan_account_id ");
+            sqlBuilder.append("left join m_savings_account linkSavingsAccount on linkSavingsAccount.id = aa.linked_savings_account_id ");
+            // sqlBuilder.append("left join m_loan linkLoanAccount on linkLoanAccount.id = aa.linked_loan_account_id ");
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public AccountAssociationsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            // final Long savingsAccountId = JdbcSupport.getLong(rs,
+            // "savingsAccountId");
+            // final String savingsAccountNo = rs.getString("savingsAccountNo");
+            final Long loanAccountId = JdbcSupport.getLong(rs, "loanAccountId");
+            final String loanAccountNo = rs.getString("loanAccountNo");
+            final PortfolioAccountData account = PortfolioAccountData.lookup(loanAccountId, loanAccountNo);
+            /*
+             * if (savingsAccountId != null) { account =
+             * PortfolioAccountData.lookup(savingsAccountId, savingsAccountNo);
+             * } else if (loanAccountId != null) { account =
+             * PortfolioAccountData.lookup(loanAccountId, loanAccountNo); }
+             */
+            final Long linkSavingsAccountId = JdbcSupport.getLong(rs, "linkSavingsAccountId");
+            final String linkSavingsAccountNo = rs.getString("linkSavingsAccountNo");
+            // final Long linkLoanAccountId = JdbcSupport.getLong(rs,
+            // "linkLoanAccountId");
+            // final String linkLoanAccountNo =
+            // rs.getString("linkLoanAccountNo");
+            final PortfolioAccountData linkedAccount = PortfolioAccountData.lookup(linkSavingsAccountId, linkSavingsAccountNo);
+            /*
+             * if (linkSavingsAccountId != null) { linkedAccount =
+             * PortfolioAccountData.lookup(linkSavingsAccountId,
+             * linkSavingsAccountNo); } else if (linkLoanAccountId != null) {
+             * linkedAccount = PortfolioAccountData.lookup(linkLoanAccountId,
+             * linkLoanAccountNo); }
+             */
+
+            return new AccountAssociationsData(id, account, linkedAccount);
+        }
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransferEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransferEnumerations.java
new file mode 100644
index 0000000..6894fbb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransferEnumerations.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.portfolio.account.service;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
+
+public class AccountTransferEnumerations {
+
+    public static EnumOptionData accountType(final Integer type) {
+        return accountType(PortfolioAccountType.fromInt(type));
+    }
+
+    public static EnumOptionData accountType(final PortfolioAccountType type) {
+
+        EnumOptionData optionData = null;
+
+        if (type != null) {
+            switch (type) {
+                case INVALID:
+                break;
+                case LOAN:
+                    optionData = new EnumOptionData(PortfolioAccountType.LOAN.getValue().longValue(), PortfolioAccountType.LOAN.getCode(),
+                            "Loan Account");
+                break;
+                case SAVINGS:
+                    optionData = new EnumOptionData(PortfolioAccountType.SAVINGS.getValue().longValue(),
+                            PortfolioAccountType.SAVINGS.getCode(), "Savings Account");
+                break;
+            }
+        }
+
+        return optionData;
+    }
+
+    public static EnumOptionData recurrenceType(final Integer type) {
+        return recurrenceType(AccountTransferRecurrenceType.fromInt(type));
+    }
+
+    public static EnumOptionData recurrenceType(final AccountTransferRecurrenceType type) {
+        EnumOptionData optionData = null;
+        if (type != null) {
+            switch (type) {
+                case INVALID:
+                break;
+                case PERIODIC:
+                    optionData = new EnumOptionData(AccountTransferRecurrenceType.PERIODIC.getValue().longValue(),
+                            AccountTransferRecurrenceType.PERIODIC.getCode(), "Periodic Recurrence");
+                break;
+                case AS_PER_DUES:
+                    optionData = new EnumOptionData(AccountTransferRecurrenceType.AS_PER_DUES.getValue().longValue(),
+                            AccountTransferRecurrenceType.AS_PER_DUES.getCode(), "As Per Dues Recurrence");
+                break;
+            }
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData transferType(final Integer type) {
+        return transferType(AccountTransferType.fromInt(type));
+    }
+
+    public static EnumOptionData transferType(final AccountTransferType type) {
+        EnumOptionData optionData = null;
+        if (type != null) {
+            switch (type) {
+                case INVALID:
+                break;
+                case ACCOUNT_TRANSFER:
+                    optionData = new EnumOptionData(AccountTransferType.ACCOUNT_TRANSFER.getValue().longValue(),
+                            AccountTransferType.ACCOUNT_TRANSFER.getCode(), "Account Transfer");
+                break;
+                case LOAN_REPAYMENT:
+                    optionData = new EnumOptionData(AccountTransferType.LOAN_REPAYMENT.getValue().longValue(),
+                            AccountTransferType.LOAN_REPAYMENT.getCode(), "Loan Repayment");
+                break;
+                case CHARGE_PAYMENT:
+                    optionData = new EnumOptionData(AccountTransferType.CHARGE_PAYMENT.getValue().longValue(),
+                            AccountTransferType.CHARGE_PAYMENT.getCode(), "Charge Payment");
+                break;
+                case INTEREST_TRANSFER:
+                    optionData = new EnumOptionData(AccountTransferType.INTEREST_TRANSFER.getValue().longValue(),
+                            AccountTransferType.INTEREST_TRANSFER.getCode(), "Interest Transfer");
+                break;
+            }
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData standingInstructionPriority(final Integer type) {
+        return standingInstructionPriority(StandingInstructionPriority.fromInt(type));
+    }
+
+    public static EnumOptionData standingInstructionPriority(final StandingInstructionPriority type) {
+        EnumOptionData optionData = null;
+        if (type != null) {
+            switch (type) {
+                case INVALID:
+                break;
+                case HIGH:
+                    optionData = new EnumOptionData(StandingInstructionPriority.HIGH.getValue().longValue(),
+                            StandingInstructionPriority.HIGH.getCode(), "High Priority");
+                break;
+                case LOW:
+                    optionData = new EnumOptionData(StandingInstructionPriority.LOW.getValue().longValue(),
+                            StandingInstructionPriority.LOW.getCode(), "Low Priority");
+                break;
+                case MEDIUM:
+                    optionData = new EnumOptionData(StandingInstructionPriority.MEDIUM.getValue().longValue(),
+                            StandingInstructionPriority.MEDIUM.getCode(), "Medium Priority");
+                break;
+                case URGENT:
+                    optionData = new EnumOptionData(StandingInstructionPriority.URGENT.getValue().longValue(),
+                            StandingInstructionPriority.URGENT.getCode(), "Urgent Priority");
+                break;
+            }
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData standingInstructionStatus(final Integer type) {
+        return standingInstructionStatus(StandingInstructionStatus.fromInt(type));
+    }
+
+    public static EnumOptionData standingInstructionStatus(final StandingInstructionStatus type) {
+        EnumOptionData optionData = null;
+        if (type != null) {
+            switch (type) {
+                case INVALID:
+                break;
+                case ACTIVE:
+                    optionData = new EnumOptionData(StandingInstructionStatus.ACTIVE.getValue().longValue(),
+                            StandingInstructionStatus.ACTIVE.getCode(), "Active");
+                break;
+                case DELETED:
+                    optionData = new EnumOptionData(StandingInstructionStatus.DELETED.getValue().longValue(),
+                            StandingInstructionStatus.DELETED.getCode(), "Deleted");
+                break;
+                case DISABLED:
+                    optionData = new EnumOptionData(StandingInstructionStatus.DISABLED.getValue().longValue(),
+                            StandingInstructionStatus.DISABLED.getCode(), "Disabled");
+                break;
+            }
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData standingInstructionType(final Integer type) {
+        return standingInstructionType(StandingInstructionType.fromInt(type));
+    }
+
+    public static EnumOptionData standingInstructionType(final StandingInstructionType type) {
+        EnumOptionData optionData = null;
+        if (type != null) {
+            switch (type) {
+                case INVALID:
+                break;
+                case DUES:
+                    optionData = new EnumOptionData(StandingInstructionType.DUES.getValue().longValue(),
+                            StandingInstructionType.DUES.getCode(), "Dues");
+                break;
+                case FIXED:
+                    optionData = new EnumOptionData(StandingInstructionType.FIXED.getValue().longValue(),
+                            StandingInstructionType.FIXED.getCode(), "Fixed");
+                break;
+
+            }
+        }
+        return optionData;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java
new file mode 100644
index 0000000..c053b01
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java
@@ -0,0 +1,45 @@
+/**
+ * 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.account.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+
+public interface AccountTransfersReadPlatformService {
+
+    AccountTransferData retrieveTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
+            Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
+
+    Page<AccountTransferData> retrieveAll(SearchParameters searchParameters, Long accountDetailId);
+
+    AccountTransferData retrieveOne(Long transferId);
+
+    boolean isAccountTransfer(Long transactionId, PortfolioAccountType accountType);
+
+    Page<AccountTransferData> retrieveByStandingInstruction(Long id, SearchParameters searchParameters);
+
+    Collection<Long> fetchPostInterestTransactionIds(Long accountId);
+    
+    AccountTransferData retrieveRefundByTransferTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
+            Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
new file mode 100644
index 0000000..9dbe4c9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
@@ -0,0 +1,537 @@
+/**
+ * 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.account.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class AccountTransfersReadPlatformServiceImpl implements AccountTransfersReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService;
+
+    // mapper
+    private final AccountTransfersMapper accountTransfersMapper;
+
+    // pagination
+    private final PaginationHelper<AccountTransferData> paginationHelper = new PaginationHelper<>();
+
+    @Autowired
+    public AccountTransfersReadPlatformServiceImpl(final RoutingDataSource dataSource,
+            final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService,
+            final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService;
+
+        this.accountTransfersMapper = new AccountTransfersMapper();
+    }
+
+    @Override
+    public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId,
+            final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) {
+
+        final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
+        final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
+
+        final Integer mostRelevantFromAccountType = fromAccountType;
+        final Collection<EnumOptionData> fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType);
+        final Collection<EnumOptionData> toAccountTypeOptions;
+        if (mostRelevantFromAccountType != null && mostRelevantFromAccountType == 1) {
+            // overpaid loan amt transfer to savings account
+            toAccountTypeOptions = Arrays.asList(savingsAccountType);
+        } else {
+            toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType);
+        }
+        final Integer mostRelevantToAccountType = toAccountType;
+
+        final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType);
+        final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType);
+
+        // from settings
+        OfficeData fromOffice = null;
+        ClientData fromClient = null;
+        PortfolioAccountData fromAccount = null;
+
+        OfficeData toOffice = null;
+        ClientData toClient = null;
+        PortfolioAccountData toAccount = null;
+
+        // template
+        Collection<PortfolioAccountData> fromAccountOptions = null;
+        Collection<PortfolioAccountData> toAccountOptions = null;
+
+        Long mostRelevantFromOfficeId = fromOfficeId;
+        Long mostRelevantFromClientId = fromClientId;
+
+        Long mostRelevantToOfficeId = toOfficeId;
+        Long mostRelevantToClientId = toClientId;
+
+        if (fromAccountId != null) {
+            Integer accountType;
+            if (mostRelevantFromAccountType == 1) {
+                accountType = PortfolioAccountType.LOAN.getValue();
+            } else {
+                accountType = PortfolioAccountType.SAVINGS.getValue();
+            }
+            fromAccount = this.portfolioAccountReadPlatformService.retrieveOne(fromAccountId, accountType);
+
+            // override provided fromClient with client of account
+            mostRelevantFromClientId = fromAccount.clientId();
+        }
+
+        if (mostRelevantFromClientId != null) {
+            fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId);
+            mostRelevantFromOfficeId = fromClient.officeId();
+            long[] loanStatus = null;
+            if (mostRelevantFromAccountType == 1) {
+                loanStatus = new long[] { 300, 700 };
+            }
+            PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId,
+                    loanStatus);
+            fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO);
+        }
+
+        Collection<OfficeData> fromOfficeOptions = null;
+        Collection<ClientData> fromClientOptions = null;
+        if (mostRelevantFromOfficeId != null) {
+            fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId);
+            fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+            fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
+        }
+
+        // defaults
+        final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
+        Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
+        Collection<ClientData> toClientOptions = null;
+
+        if (toAccountId != null && fromAccount != null) {
+            toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType,
+                    fromAccount.currencyCode());
+            mostRelevantToClientId = toAccount.clientId();
+        }
+
+        if (mostRelevantToClientId != null) {
+            toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId);
+            mostRelevantToOfficeId = toClient.officeId();
+
+            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+
+            toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
+        }
+
+        if (mostRelevantToOfficeId != null) {
+            toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId);
+            toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+            if (toClientOptions != null && toClientOptions.size() == 1) {
+                toClient = new ArrayList<>(toClientOptions).get(0);
+
+                toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
+            }
+        }
+
+        return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient,
+                toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
+                toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
+    }
+
+    private Collection<PortfolioAccountData> retrieveToAccounts(final PortfolioAccountData excludeThisAccountFromOptions,
+            final Integer toAccountType, final Long toClientId) {
+
+        final String currencyCode = excludeThisAccountFromOptions != null ? excludeThisAccountFromOptions.currencyCode() : null;
+
+        PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(toAccountType, toClientId, currencyCode, null, null);
+        Collection<PortfolioAccountData> accountOptions = this.portfolioAccountReadPlatformService
+                .retrieveAllForLookup(portfolioAccountDTO);
+        if (!CollectionUtils.isEmpty(accountOptions)) {
+            accountOptions.remove(excludeThisAccountFromOptions);
+        } else {
+            accountOptions = null;
+        }
+
+        return accountOptions;
+    }
+
+    @Override
+    public Page<AccountTransferData> retrieveAll(final SearchParameters searchParameters, final Long accountDetailId) {
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.accountTransfersMapper.schema());
+        Object[] finalObjectArray = {};
+        if (accountDetailId != null) {
+            sqlBuilder.append(" where att.account_transfer_details_id=?");
+            finalObjectArray = new Object[] { accountDetailId };
+        }
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
+                this.accountTransfersMapper);
+    }
+
+    @Override
+    public AccountTransferData retrieveOne(final Long transferId) {
+
+        try {
+            final String sql = "select " + this.accountTransfersMapper.schema() + " where att.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, this.accountTransfersMapper, new Object[] { transferId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new AccountTransferNotFoundException(transferId);
+        }
+    }
+
+    @Override
+    public Collection<Long> fetchPostInterestTransactionIds(final Long accountId) {
+        final String sql = "select att.from_savings_transaction_id from m_account_transfer_transaction att inner join m_account_transfer_details atd on atd.id = att.account_transfer_details_id where atd.from_savings_account_id=? and att.is_reversed =0 and atd.transfer_type = ?";
+
+        final List<Long> transactionId = this.jdbcTemplate.queryForList(sql, Long.class, accountId,
+                AccountTransferType.INTEREST_TRANSFER.getValue());
+
+        return transactionId;
+    }
+
+    private static final class AccountTransfersMapper implements RowMapper<AccountTransferData> {
+
+        private final String schemaSql;
+
+        public AccountTransfersMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("att.id as id, att.is_reversed as isReversed,");
+            sqlBuilder.append("att.transaction_date as transferDate, att.amount as transferAmount,");
+            sqlBuilder.append("att.description as transferDescription,");
+            sqlBuilder.append("att.currency_code as currencyCode, att.currency_digits as currencyDigits,");
+            sqlBuilder.append("att.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,");
+            sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,");
+            sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,");
+            sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,");
+            sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,");
+            sqlBuilder.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,");
+            sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,");
+            sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo,");
+            sqlBuilder.append("fromsavtran.id as fromSavingsAccountTransactionId,");
+            sqlBuilder.append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,");
+            sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,");
+            sqlBuilder.append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType");
+            sqlBuilder.append(" FROM m_account_transfer_transaction att ");
+            sqlBuilder.append("left join m_account_transfer_details atd on atd.id = att.account_transfer_details_id ");
+            sqlBuilder.append("join m_currency curr on curr.code = att.currency_code ");
+            sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id ");
+            sqlBuilder.append("join m_office tooff on tooff.id = atd.to_office_id ");
+            sqlBuilder.append("join m_client fromclient on fromclient.id = atd.from_client_id ");
+            sqlBuilder.append("join m_client toclient on toclient.id = atd.to_client_id ");
+            sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id ");
+            sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id ");
+            sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id ");
+            sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id ");
+            sqlBuilder.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = att.from_savings_transaction_id ");
+            sqlBuilder.append("left join m_savings_account_transaction tosavtran on tosavtran.id = att.to_savings_transaction_id ");
+            sqlBuilder.append("left join m_loan_transaction fromloantran on fromloantran.id = att.from_savings_transaction_id ");
+            sqlBuilder.append("left join m_loan_transaction toloantran on toloantran.id = att.to_savings_transaction_id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public AccountTransferData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final boolean reversed = rs.getBoolean("isReversed");
+
+            final LocalDate transferDate = JdbcSupport.getLocalDate(rs, "transferDate");
+            final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transferAmount");
+            final String transferDescription = rs.getString("transferDescription");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId");
+            final String fromOfficeName = rs.getString("fromOfficeName");
+            final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null);
+
+            final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId");
+            final String toOfficeName = rs.getString("toOfficeName");
+            final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null);
+
+            final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId");
+            final String fromClientName = rs.getString("fromClientName");
+            final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName);
+
+            final Long toClientId = JdbcSupport.getLong(rs, "toClientId");
+            final String toClientName = rs.getString("toClientName");
+            final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName);
+
+            final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId");
+            final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo");
+            final Long fromLoanAccountId = JdbcSupport.getLong(rs, "fromLoanAccountId");
+            final String fromLoanAccountNo = rs.getString("fromLoanAccountNo");
+            PortfolioAccountData fromAccount = null;
+            EnumOptionData fromAccountType = null;
+            if (fromSavingsAccountId != null) {
+                fromAccount = PortfolioAccountData.lookup(fromSavingsAccountId, fromSavingsAccountNo);
+                fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
+            } else if (fromLoanAccountId != null) {
+                fromAccount = PortfolioAccountData.lookup(fromLoanAccountId, fromLoanAccountNo);
+                fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
+            }
+
+            PortfolioAccountData toAccount = null;
+            EnumOptionData toAccountType = null;
+            final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId");
+            final String toSavingsAccountNo = rs.getString("toSavingsAccountNo");
+            final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId");
+            final String toLoanAccountNo = rs.getString("toLoanAccountNo");
+
+            if (toSavingsAccountId != null) {
+                toAccount = PortfolioAccountData.lookup(toSavingsAccountId, toSavingsAccountNo);
+                toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
+            } else if (toLoanAccountId != null) {
+                toAccount = PortfolioAccountData.lookup(toLoanAccountId, toLoanAccountNo);
+                toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
+            }
+
+            return AccountTransferData.instance(id, reversed, transferDate, currency, transferAmount, transferDescription, fromOffice,
+                    toOffice, fromClient, toClient, fromAccountType, fromAccount, toAccountType, toAccount);
+        }
+    }
+
+    @Override
+    public boolean isAccountTransfer(final Long transactionId, final PortfolioAccountType accountType) {
+        final StringBuilder sql = new StringBuilder("select count(*) from m_account_transfer_transaction at where ");
+        if (accountType.isLoanAccount()) {
+            sql.append("at.from_loan_transaction_id=").append(transactionId).append(" or at.to_loan_transaction_id=").append(transactionId);
+        } else {
+            sql.append("at.from_savings_transaction_id=").append(transactionId).append(" or at.to_savings_transaction_id=")
+                    .append(transactionId);
+        }
+
+        @SuppressWarnings("deprecation")
+        final int count = this.jdbcTemplate.queryForInt(sql.toString());
+        return count > 0;
+    }
+
+    @Override
+    public Page<AccountTransferData> retrieveByStandingInstruction(final Long id, final SearchParameters searchParameters) {
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder
+                .append(this.accountTransfersMapper.schema())
+                .append(" join m_account_transfer_standing_instructions atsi on atsi.account_transfer_details_id = att.account_transfer_details_id ");
+        sqlBuilder.append(" where atsi.id = ?");
+
+        if (searchParameters != null) {
+            if (searchParameters.isOrderByRequested()) {
+                sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+                if (searchParameters.isSortOrderProvided()) {
+                    sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+                }
+            }
+
+            if (searchParameters.isLimited()) {
+                sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+                if (searchParameters.isOffset()) {
+                    sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+                }
+            }
+        }
+
+        final Object[] finalObjectArray = { id };
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
+                this.accountTransfersMapper);
+    }
+
+    @Override
+    public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId,
+            final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) {
+        // TODO Auto-generated method stub
+        final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
+        final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
+
+        final Integer mostRelevantFromAccountType = fromAccountType;
+        final Collection<EnumOptionData> fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType);
+        final Collection<EnumOptionData> toAccountTypeOptions;
+        if (mostRelevantFromAccountType == 1) {
+            // overpaid loan amt transfer to savings account
+            toAccountTypeOptions = Arrays.asList(savingsAccountType);
+        } else {
+            toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType);
+        }
+        final Integer mostRelevantToAccountType = toAccountType;
+
+        final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType);
+        final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType);
+
+        // from settings
+        OfficeData fromOffice = null;
+        ClientData fromClient = null;
+        PortfolioAccountData fromAccount = null;
+
+        OfficeData toOffice = null;
+        ClientData toClient = null;
+        PortfolioAccountData toAccount = null;
+
+        // template
+        Collection<PortfolioAccountData> fromAccountOptions = null;
+        Collection<PortfolioAccountData> toAccountOptions = null;
+
+        Long mostRelevantFromOfficeId = fromOfficeId;
+        Long mostRelevantFromClientId = fromClientId;
+
+        Long mostRelevantToOfficeId = toOfficeId;
+        Long mostRelevantToClientId = toClientId;
+
+        if (fromAccountId != null) {
+            Integer accountType;
+            if (mostRelevantFromAccountType == 1) {
+                accountType = PortfolioAccountType.LOAN.getValue();
+            } else {
+                accountType = PortfolioAccountType.SAVINGS.getValue();
+            }
+            fromAccount = this.portfolioAccountReadPlatformService.retrieveOneByPaidInAdvance(fromAccountId, accountType);
+
+            // override provided fromClient with client of account
+            mostRelevantFromClientId = fromAccount.clientId();
+        }
+
+        if (mostRelevantFromClientId != null) {
+            fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId);
+            mostRelevantFromOfficeId = fromClient.officeId();
+            long[] loanStatus = null;
+            if (mostRelevantFromAccountType == 1) {
+                loanStatus = new long[] { 300, 700 };
+            }
+            PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId,
+                    loanStatus);
+            fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO);
+        }
+
+        Collection<OfficeData> fromOfficeOptions = null;
+        Collection<ClientData> fromClientOptions = null;
+        if (mostRelevantFromOfficeId != null) {
+            fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId);
+            fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+            fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
+        }
+
+        // defaults
+        final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
+        Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
+        Collection<ClientData> toClientOptions = null;
+
+        if (toAccountId != null && fromAccount != null) {
+            toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType,
+                    fromAccount.currencyCode());
+            mostRelevantToClientId = toAccount.clientId();
+        }
+
+        if (mostRelevantToClientId != null) {
+            toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId);
+            mostRelevantToOfficeId = toClient.officeId();
+
+            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+
+            toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
+        }
+
+        if (mostRelevantToOfficeId != null) {
+            toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId);
+            toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+            if (toClientOptions != null && toClientOptions.size() == 1) {
+                toClient = new ArrayList<>(toClientOptions).get(0);
+
+                toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
+            }
+        }
+
+        return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient,
+                toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
+                toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformService.java
new file mode 100644
index 0000000..eeac44d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformService.java
@@ -0,0 +1,44 @@
+/**
+ * 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.account.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+
+public interface AccountTransfersWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    void reverseTransfersWithFromAccountType(Long accountNumber, PortfolioAccountType accountTypeId);
+
+    Long transferFunds(AccountTransferDTO accountTransferDTO);
+
+    void reverseAllTransactions(Long accountId, PortfolioAccountType accountTypeId);
+
+    void updateLoanTransaction(Long loanTransactionId, LoanTransaction newLoanTransaction);
+    
+    CommandProcessingResult refundByTransfer(JsonCommand command);
+
+    void reverseTransfersWithFromAccountTransactions(Collection<Long> fromTransactionIds, PortfolioAccountType accountTypeId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java
new file mode 100644
index 0000000..fb0c7ea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java
@@ -0,0 +1,488 @@
+/**
+ * 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.account.service;
+
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferAmountParamName;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+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.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator;
+import org.apache.fineract.portfolio.account.domain.AccountTransferAssembler;
+import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
+import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
+import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
+import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class AccountTransfersWritePlatformServiceImpl implements AccountTransfersWritePlatformService {
+
+    private final AccountTransfersDataValidator accountTransfersDataValidator;
+    private final AccountTransferAssembler accountTransferAssembler;
+    private final AccountTransferRepository accountTransferRepository;
+    private final SavingsAccountAssembler savingsAccountAssembler;
+    private final SavingsAccountDomainService savingsAccountDomainService;
+    private final LoanAssembler loanAccountAssembler;
+    private final LoanAccountDomainService loanAccountDomainService;
+    private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
+    private final AccountTransferDetailRepository accountTransferDetailRepository;
+    private final LoanReadPlatformService loanReadPlatformService;
+
+    @Autowired
+    public AccountTransfersWritePlatformServiceImpl(final AccountTransfersDataValidator accountTransfersDataValidator,
+            final AccountTransferAssembler accountTransferAssembler, final AccountTransferRepository accountTransferRepository,
+            final SavingsAccountAssembler savingsAccountAssembler, final SavingsAccountDomainService savingsAccountDomainService,
+            final LoanAssembler loanAssembler, final LoanAccountDomainService loanAccountDomainService,
+            final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
+            final AccountTransferDetailRepository accountTransferDetailRepository,
+            final LoanReadPlatformService loanReadPlatformService) {
+        this.accountTransfersDataValidator = accountTransfersDataValidator;
+        this.accountTransferAssembler = accountTransferAssembler;
+        this.accountTransferRepository = accountTransferRepository;
+        this.savingsAccountAssembler = savingsAccountAssembler;
+        this.savingsAccountDomainService = savingsAccountDomainService;
+        this.loanAccountAssembler = loanAssembler;
+        this.loanAccountDomainService = loanAccountDomainService;
+        this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
+        this.accountTransferDetailRepository = accountTransferDetailRepository;
+        this.loanReadPlatformService = loanReadPlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult create(final JsonCommand command) {
+        boolean isRegularTransaction = true;
+
+        this.accountTransfersDataValidator.validate(command);
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+        final Integer fromAccountTypeId = command.integerValueSansLocaleOfParameterNamed(fromAccountTypeParamName);
+        final PortfolioAccountType fromAccountType = PortfolioAccountType.fromInt(fromAccountTypeId);
+
+        final Integer toAccountTypeId = command.integerValueSansLocaleOfParameterNamed(toAccountTypeParamName);
+        final PortfolioAccountType toAccountType = PortfolioAccountType.fromInt(toAccountTypeId);
+
+        final PaymentDetail paymentDetail = null;
+        Long fromSavingsAccountId = null;
+        Long transferDetailId = null;
+        boolean isInterestTransfer = false;
+        boolean isAccountTransfer = true;
+        Long fromLoanAccountId = null;
+        boolean isWithdrawBalance = false;
+
+        if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) {
+
+            fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
+            final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId);
+
+            final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
+                    isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance);
+            final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt,
+                    transactionDate, transactionAmount, paymentDetail, transactionBooleanValues);
+
+            final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
+            final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId);
+
+            final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt,
+                    transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction);
+
+            final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleSavingsToSavingsTransfer(command,
+                    fromSavingsAccount, toSavingsAccount, withdrawal, deposit);
+            this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
+            transferDetailId = accountTransferDetails.getId();
+
+        } else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) {
+            //
+            fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
+            final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId);
+
+            final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
+                    isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance);
+            final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt,
+                    transactionDate, transactionAmount, paymentDetail, transactionBooleanValues);
+
+            final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
+            final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
+
+            final Boolean isHolidayValidationDone = false;
+            final HolidayDetailDTO holidayDetailDto = null;
+            final boolean isRecoveryRepayment = false;
+            final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount,
+                    new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null,
+                    isRecoveryRepayment, isAccountTransfer, holidayDetailDto, isHolidayValidationDone);
+
+            final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command,
+                    fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
+            this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
+            transferDetailId = accountTransferDetails.getId();
+
+        } else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) {
+            // FIXME - kw - ADD overpaid loan to savings account transfer
+            // support.
+
+            fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
+            final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
+
+            final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefund(fromLoanAccountId,
+                    new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
+
+            final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
+            final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId);
+
+            final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt,
+                    transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction);
+
+            final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command,
+                    fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction);
+            this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
+            transferDetailId = accountTransferDetails.getId();
+
+        } else {
+
+        }
+
+        final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(transferDetailId);
+
+        if (fromAccountType.isSavingsAccount()) {
+            builder.withSavingsId(fromSavingsAccountId);
+        }
+        if (fromAccountType.isLoanAccount()) {
+            builder.withLoanId(fromLoanAccountId);
+        }
+
+        return builder.build();
+    }
+
+    @Override
+    @Transactional
+    public void reverseTransfersWithFromAccountType(final Long accountNumber, final PortfolioAccountType accountTypeId) {
+        List<AccountTransferTransaction> acccountTransfers = null;
+        if (accountTypeId.isLoanAccount()) {
+            acccountTransfers = this.accountTransferRepository.findByFromLoanId(accountNumber);
+        }
+        if (acccountTransfers != null && acccountTransfers.size() > 0) {
+            undoTransactions(acccountTransfers);
+        }
+
+    }
+    
+    @Override
+    @Transactional
+    public void reverseTransfersWithFromAccountTransactions(final Collection<Long> fromTransactionIds, final PortfolioAccountType accountTypeId) {
+        List<AccountTransferTransaction> acccountTransfers = null;
+        if (accountTypeId.isLoanAccount()) {
+            acccountTransfers = this.accountTransferRepository.findByFromLoanTransactions(fromTransactionIds);
+        }
+        if (acccountTransfers != null && acccountTransfers.size() > 0) {
+            undoTransactions(acccountTransfers);
+        }
+
+    }
+
+    @Override
+    @Transactional
+    public void reverseAllTransactions(final Long accountId, final PortfolioAccountType accountTypeId) {
+        List<AccountTransferTransaction> acccountTransfers = null;
+        if (accountTypeId.isLoanAccount()) {
+            acccountTransfers = this.accountTransferRepository.findAllByLoanId(accountId);
+        }
+        if (acccountTransfers != null && acccountTransfers.size() > 0) {
+            undoTransactions(acccountTransfers);
+        }
+    }
+
+    /**
+     * @param acccountTransfers
+     */
+    private void undoTransactions(final List<AccountTransferTransaction> acccountTransfers) {
+        for (final AccountTransferTransaction accountTransfer : acccountTransfers) {
+            if (accountTransfer.getFromLoanTransaction() != null) {
+                this.loanAccountDomainService.reverseTransfer(accountTransfer.getFromLoanTransaction());
+            } else if (accountTransfer.getToLoanTransaction() != null) {
+                this.loanAccountDomainService.reverseTransfer(accountTransfer.getToLoanTransaction());
+            }
+            if (accountTransfer.getFromTransaction() != null) {
+                this.savingsAccountWritePlatformService.undoTransaction(accountTransfer.accountTransferDetails().fromSavingsAccount()
+                        .getId(), accountTransfer.getFromTransaction().getId(), true);
+            }
+            if (accountTransfer.getToSavingsTransaction() != null) {
+                this.savingsAccountWritePlatformService.undoTransaction(
+                        accountTransfer.accountTransferDetails().toSavingsAccount().getId(), accountTransfer.getToSavingsTransaction()
+                                .getId(), true);
+            }
+            accountTransfer.reverse();
+            this.accountTransferRepository.save(accountTransfer);
+        }
+    }
+
+    @Override
+    @Transactional
+    public Long transferFunds(final AccountTransferDTO accountTransferDTO) {
+        Long transferTransactionId = null;
+        final boolean isAccountTransfer = true;
+        final boolean isRegularTransaction = accountTransferDTO.isRegularTransaction();
+        AccountTransferDetails accountTransferDetails = accountTransferDTO.getAccountTransferDetails();
+        if (isSavingsToLoanAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
+            //
+            SavingsAccount fromSavingsAccount = null;
+            Loan toLoanAccount = null;
+            if (accountTransferDetails == null) {
+                if (accountTransferDTO.getFromSavingsAccount() == null) {
+                    fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
+                } else {
+                    fromSavingsAccount = accountTransferDTO.getFromSavingsAccount();
+                    this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
+                }
+                if (accountTransferDTO.getLoan() == null) {
+                    toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
+                } else {
+                    toLoanAccount = accountTransferDTO.getLoan();
+                    this.loanAccountAssembler.setHelpers(toLoanAccount);
+                }
+
+            } else {
+                fromSavingsAccount = accountTransferDetails.fromSavingsAccount();
+                this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
+                toLoanAccount = accountTransferDetails.toLoanAccount();
+                this.loanAccountAssembler.setHelpers(toLoanAccount);
+            }
+
+            final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
+                    isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), AccountTransferType.fromInt(
+                            accountTransferDTO.getTransferType()).isInterestTransfer(), accountTransferDTO.isExceptionForBalanceCheck());
+
+            final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
+                    accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
+                    accountTransferDTO.getPaymentDetail(), transactionBooleanValues);
+
+            LoanTransaction loanTransaction = null;
+
+            if (AccountTransferType.fromInt(accountTransferDTO.getTransferType()).isChargePayment()) {
+                loanTransaction = this.loanAccountDomainService.makeChargePayment(toLoanAccount, accountTransferDTO.getChargeId(),
+                        accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
+                        accountTransferDTO.getPaymentDetail(), null, null, accountTransferDTO.getToTransferType(),
+                        accountTransferDTO.getLoanInstallmentNumber());
+
+            } else {
+                final boolean isRecoveryRepayment = false;
+                final Boolean isHolidayValidationDone=false;
+                final HolidayDetailDTO holidayDetailDto=null;
+                loanTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount, new CommandProcessingResultBuilder(),
+                        accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
+                        accountTransferDTO.getPaymentDetail(), null, null, isRecoveryRepayment, isAccountTransfer,holidayDetailDto,isHolidayValidationDone);
+            }
+
+            accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, fromSavingsAccount,
+                    toLoanAccount, withdrawal, loanTransaction);
+            this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
+            transferTransactionId = accountTransferDetails.getId();
+        } else if (isSavingsToSavingsAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
+
+            SavingsAccount fromSavingsAccount = null;
+            SavingsAccount toSavingsAccount = null;
+            if (accountTransferDetails == null) {
+                if (accountTransferDTO.getFromSavingsAccount() == null) {
+                    fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
+                } else {
+                    fromSavingsAccount = accountTransferDTO.getFromSavingsAccount();
+                    this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
+                }
+                if (accountTransferDTO.getToSavingsAccount() == null) {
+                    toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
+                } else {
+                    toSavingsAccount = accountTransferDTO.getToSavingsAccount();
+                    this.savingsAccountAssembler.setHelpers(toSavingsAccount);
+                }
+            } else {
+                fromSavingsAccount = accountTransferDetails.fromSavingsAccount();
+                this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
+                toSavingsAccount = accountTransferDetails.toSavingsAccount();
+                this.savingsAccountAssembler.setHelpers(toSavingsAccount);
+            }
+
+            final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
+                    isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), AccountTransferType.fromInt(
+                            accountTransferDTO.getTransferType()).isInterestTransfer(), accountTransferDTO.isExceptionForBalanceCheck());
+
+            final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
+                    accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
+                    accountTransferDTO.getPaymentDetail(), transactionBooleanValues);
+
+            final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount,
+                    accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
+                    accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction);
+
+            accountTransferDetails = this.accountTransferAssembler.assembleSavingsToSavingsTransfer(accountTransferDTO, fromSavingsAccount,
+                    toSavingsAccount, withdrawal, deposit);
+            this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
+            transferTransactionId = accountTransferDetails.getId();
+
+        } else if (isLoanToSavingsAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
+
+            Loan fromLoanAccount = null;
+            SavingsAccount toSavingsAccount = null;
+            if (accountTransferDetails == null) {
+                if (accountTransferDTO.getLoan() == null) {
+                    fromLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
+                } else {
+                    fromLoanAccount = accountTransferDTO.getLoan();
+                    this.loanAccountAssembler.setHelpers(fromLoanAccount);
+                }
+                toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
+            } else {
+                fromLoanAccount = accountTransferDetails.fromLoanAccount();
+                this.loanAccountAssembler.setHelpers(fromLoanAccount);
+                toSavingsAccount = accountTransferDetails.toSavingsAccount();
+                this.savingsAccountAssembler.setHelpers(toSavingsAccount);
+            }
+            LoanTransaction loanTransaction = null;
+            if (LoanTransactionType.DISBURSEMENT.getValue().equals(accountTransferDTO.getFromTransferType())) {
+                loanTransaction = this.loanAccountDomainService.makeDisburseTransaction(accountTransferDTO.getFromAccountId(),
+                        accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
+                        accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(), accountTransferDTO.getTxnExternalId());
+            } else {
+                loanTransaction = this.loanAccountDomainService.makeRefund(accountTransferDTO.getFromAccountId(),
+                        new CommandProcessingResultBuilder(), accountTransferDTO.getTransactionDate(),
+                        accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(),
+                        accountTransferDTO.getTxnExternalId());
+            }
+
+            final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount,
+                    accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
+                    accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction);
+            accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(accountTransferDTO, fromLoanAccount,
+                    toSavingsAccount, deposit, loanTransaction);
+            this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
+            transferTransactionId = accountTransferDetails.getId();
+        }
+
+        return transferTransactionId;
+    }
+
+    @Override
+    @Transactional
+    public void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
+        final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
+        if (transferTransaction != null) {
+            transferTransaction.updateToLoanTransaction(newLoanTransaction);
+            this.accountTransferRepository.save(transferTransaction);
+        }
+    }
+
+    private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
+        return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount();
+    }
+
+    private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
+        return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount();
+    }
+
+    private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
+        return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
+    }
+    
+    @Override
+    @Transactional
+    public CommandProcessingResult refundByTransfer(JsonCommand command) {
+        // TODO Auto-generated method stub
+        this.accountTransfersDataValidator.validate(command);
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+        final PaymentDetail paymentDetail = null; 
+        Long transferTransactionId = null;
+
+        final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
+        final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
+
+        BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(fromLoanAccountId).getPaidInAdvance();
+
+        if (overpaid == null || overpaid.equals(BigDecimal.ZERO) || transactionAmount.floatValue() > overpaid.floatValue()) {
+            if(overpaid == null)
+                overpaid = BigDecimal.ZERO;
+            throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString());
+        }
+
+        final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefundForActiveLoan(fromLoanAccountId,
+                new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
+
+        final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
+        final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId);
+
+        final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate,
+                transactionAmount, paymentDetail, true, true);
+
+        final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command,
+                fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction);
+        this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
+        transferTransactionId = accountTransferDetails.getId();
+
+        final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(transferTransactionId);
+
+        // if (fromAccountType.isSavingsAccount()) {
+
+        builder.withSavingsId(toSavingsAccountId);
+        // }
+
+        return builder.build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformService.java
new file mode 100644
index 0000000..7a36c68
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformService.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.portfolio.account.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+
+public interface PortfolioAccountReadPlatformService {
+
+    PortfolioAccountData retrieveOne(Long accountId, Integer accountTypeId);
+
+    PortfolioAccountData retrieveOne(Long accountId, Integer accountTypeId, String currencyCode);
+
+    Collection<PortfolioAccountData> retrieveAllForLookup(final PortfolioAccountDTO portfolioAccountDTO);
+
+    PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java
new file mode 100644
index 0000000..0c323dd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java
@@ -0,0 +1,386 @@
+/**
+ * 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.account.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException;
+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 PortfolioAccountReadPlatformServiceImpl implements PortfolioAccountReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    // mapper
+    private final PortfolioSavingsAccountMapper savingsAccountMapper;
+    private final PortfolioLoanAccountMapper loanAccountMapper;
+    private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper;
+
+    @Autowired
+    public PortfolioAccountReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
+        this.loanAccountMapper = new PortfolioLoanAccountMapper();
+        this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper();
+    }
+
+    @Override
+    public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId) {
+        return retrieveOne(accountId, accountTypeId, null);
+    }
+
+    @Override
+    public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId, final String currencyCode) {
+
+        Object[] sqlParams = new Object[] { accountId };
+        PortfolioAccountData accountData = null;
+        try {
+            String sql = null;
+            final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
+            switch (accountType) {
+                case INVALID:
+                break;
+                case LOAN:
+
+                    sql = "select " + this.loanAccountMapper.schema() + " where la.id = ?";
+                    if (currencyCode != null) {
+                        sql += " and la.currency_code = ?";
+                        sqlParams = new Object[] { accountId, currencyCode };
+                    }
+
+                    accountData = this.jdbcTemplate.queryForObject(sql, this.loanAccountMapper, sqlParams);
+                break;
+                case SAVINGS:
+                    sql = "select " + this.savingsAccountMapper.schema() + " where sa.id = ?";
+                    if (currencyCode != null) {
+                        sql += " and sa.currency_code = ?";
+                        sqlParams = new Object[] { accountId, currencyCode };
+                    }
+
+                    accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams);
+                break;
+                default:
+                break;
+            }
+        } catch (final EmptyResultDataAccessException e) {
+            throw new AccountTransferNotFoundException(accountId);
+        }
+
+        return accountData;
+    }
+
+    @Override
+    public Collection<PortfolioAccountData> retrieveAllForLookup(final PortfolioAccountDTO portfolioAccountDTO) {
+
+        List<Object> sqlParams = new ArrayList<>();
+        sqlParams.add(portfolioAccountDTO.getClientId());
+        Collection<PortfolioAccountData> accounts = null;
+        String sql = null;
+        String defaultAccountStatus = "300";
+        if (portfolioAccountDTO.getAccountStatus() != null) {
+            for (final long status : portfolioAccountDTO.getAccountStatus()) {
+                defaultAccountStatus += ", " + status;
+            }
+            defaultAccountStatus = defaultAccountStatus.substring(defaultAccountStatus.indexOf(",") + 1);
+        }
+        final PortfolioAccountType accountType = PortfolioAccountType.fromInt(portfolioAccountDTO.getAccountTypeId());
+        switch (accountType) {
+            case INVALID:
+            break;
+            case LOAN:
+                sql = "select " + this.loanAccountMapper.schema() + " where la.client_id = ? and la.loan_status_id in ("
+                        + defaultAccountStatus.toString() + ")";
+                if (portfolioAccountDTO.getCurrencyCode() != null) {
+                    sql += " and la.currency_code = ?";
+                    sqlParams.add(portfolioAccountDTO.getCurrencyCode());
+                }
+
+                accounts = this.jdbcTemplate.query(sql, this.loanAccountMapper, sqlParams.toArray());
+            break;
+            case SAVINGS:
+                sql = "select " + this.savingsAccountMapper.schema() + " where sa.client_id = ? and sa.status_enum in ("
+                        + defaultAccountStatus.toString() + ")";
+                if (portfolioAccountDTO.getCurrencyCode() != null) {
+                    sql += " and sa.currency_code = ?";
+                    sqlParams.add(portfolioAccountDTO.getCurrencyCode());
+                }
+
+                if (portfolioAccountDTO.getDepositType() != null) {
+                    sql += " and sa.deposit_type_enum = ?";
+                    sqlParams.add(portfolioAccountDTO.getDepositType());
+                }
+                
+                if(portfolioAccountDTO.isExcludeOverDraftAccounts()){
+                    sql += " and sa.allow_overdraft = 0";
+                }
+
+                accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams.toArray());
+            break;
+            default:
+            break;
+        }
+
+        return accounts;
+    }
+
+    private static final class PortfolioSavingsAccountMapper implements RowMapper<PortfolioAccountData> {
+
+        private final String schemaSql;
+
+        public PortfolioSavingsAccountMapper() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, ");
+            sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
+            sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
+            sqlBuilder.append("sp.id as productId, sp.name as productName, ");
+            sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
+            sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits,");
+            sqlBuilder.append("sa.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
+            sqlBuilder.append("from m_savings_account sa ");
+            sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
+            sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
+            sqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
+            sqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
+            sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final String externalId = rs.getString("externalId");
+
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final String groupName = rs.getString("groupName");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final String clientName = rs.getString("clientName");
+
+            final Long productId = rs.getLong("productId");
+            final String productName = rs.getString("productName");
+
+            final Long fieldOfficerId = rs.getLong("fieldOfficerId");
+            final String fieldOfficerName = rs.getString("fieldOfficerName");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
+                    fieldOfficerId, fieldOfficerName, currency);
+        }
+    }
+
+    private static final class PortfolioLoanAccountMapper implements RowMapper<PortfolioAccountData> {
+
+        private final String schemaSql;
+
+        public PortfolioLoanAccountMapper() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
+            sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
+            sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
+            sqlBuilder.append("lp.id as productId, lp.name as productName, ");
+            sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
+            sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
+            sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("la.total_overpaid_derived as totalOverpaid, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
+            sqlBuilder.append("from m_loan la ");
+            sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
+            sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
+            sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
+            sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
+            sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final String externalId = rs.getString("externalId");
+
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final String groupName = rs.getString("groupName");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final String clientName = rs.getString("clientName");
+
+            final Long productId = rs.getLong("productId");
+            final String productName = rs.getString("productName");
+
+            final Long fieldOfficerId = rs.getLong("fieldOfficerId");
+            final String fieldOfficerName = rs.getString("fieldOfficerName");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
+                    fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
+        }
+    }
+    
+    private static final class PortfolioLoanAccountRefundByTransferMapper implements RowMapper<PortfolioAccountData> {
+
+        private final String schemaSql;
+
+        public PortfolioLoanAccountRefundByTransferMapper() {
+            
+            final StringBuilder amountQueryString = new StringBuilder(400);
+            amountQueryString.append("(select (SUM(ifnull(mr.principal_completed_derived, 0)) +"); 
+            amountQueryString.append("SUM(ifnull(mr.interest_completed_derived, 0)) + "); 
+             amountQueryString.append("SUM(ifnull(mr.fee_charges_completed_derived, 0)) + "); 
+             amountQueryString.append(" SUM(ifnull(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived"); 
+             amountQueryString.append(" from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id"); 
+             amountQueryString.append(" where ml.id=? and ml.loan_status_id = 300"); 
+             amountQueryString.append("  and  mr.duedate >= CURDATE() group by ml.id having"); 
+             amountQueryString.append(" (SUM(ifnull(mr.principal_completed_derived, 0)) + "); 
+             amountQueryString.append(" SUM(ifnull(mr.interest_completed_derived, 0)) + "); 
+             amountQueryString.append("SUM(ifnull(mr.fee_charges_completed_derived, 0)) + "); 
+             amountQueryString.append("SUM(ifnull(mr.penalty_charges_completed_derived, 0))) > 0) as totalOverpaid ");
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
+            sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
+            sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
+            sqlBuilder.append("lp.id as productId, lp.name as productName, ");
+            sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
+            sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
+            sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append(amountQueryString.toString());
+            sqlBuilder.append(", ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
+            sqlBuilder.append("from m_loan la ");
+            sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
+            sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
+            sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
+            sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
+            sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final String externalId = rs.getString("externalId");
+
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final String groupName = rs.getString("groupName");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final String clientName = rs.getString("clientName");
+
+            final Long productId = rs.getLong("productId");
+            final String productName = rs.getString("productName");
+
+            final Long fieldOfficerId = rs.getLong("fieldOfficerId");
+            final String fieldOfficerName = rs.getString("fieldOfficerName");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
+                    fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
+        }
+    }
+    
+    @Override
+    public PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId) {
+        // TODO Auto-generated method stub
+        Object[] sqlParams = new Object[] { accountId , accountId};
+        PortfolioAccountData accountData = null;
+        //String currencyCode = null;
+        try {
+            String sql = null;
+            //final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
+           
+                    sql = "select " + this.accountRefundByTransferMapper.schema() + " where la.id = ?";
+                  /*  if (currencyCode != null) {
+                        sql += " and la.currency_code = ?";
+                        sqlParams = new Object[] {accountId , accountId,currencyCode };
+                    }*/
+
+                    accountData = this.jdbcTemplate.queryForObject(sql, this.accountRefundByTransferMapper, sqlParams);
+         
+        } catch (final EmptyResultDataAccessException e) {
+            throw new AccountTransferNotFoundException(accountId);
+        }
+
+        return accountData;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformService.java
new file mode 100755
index 0000000..3fdf796
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformService.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.account.service;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
+import org.apache.fineract.portfolio.account.data.StandingInstructionHistoryData;
+
+public interface StandingInstructionHistoryReadPlatformService {
+
+    Page<StandingInstructionHistoryData> retrieveAll(StandingInstructionDTO standingInstructionDTO);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java
new file mode 100755
index 0000000..d0df176
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java
@@ -0,0 +1,278 @@
+/**
+ * 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.account.service;
+
+import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.accountType;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
+import org.apache.fineract.portfolio.account.data.StandingInstructionHistoryData;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class StandingInstructionHistoryReadPlatformServiceImpl implements StandingInstructionHistoryReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    // mapper
+    private final StandingInstructionHistoryMapper standingInstructionHistoryMapper;
+
+    // pagination
+    private final PaginationHelper<StandingInstructionHistoryData> paginationHelper = new PaginationHelper<>();
+
+    @Autowired
+    public StandingInstructionHistoryReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.standingInstructionHistoryMapper = new StandingInstructionHistoryMapper();
+    }
+
+    @Override
+    public Page<StandingInstructionHistoryData> retrieveAll(StandingInstructionDTO standingInstructionDTO) {
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.standingInstructionHistoryMapper.schema());
+        if (standingInstructionDTO.transferType() != null || standingInstructionDTO.clientId() != null
+                || standingInstructionDTO.clientName() != null
+                || (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null)
+                || standingInstructionDTO.startDateRange() != null || standingInstructionDTO.endDateRange() != null) {
+            sqlBuilder.append(" where ");
+        }
+        boolean addAndCaluse = false;
+        List<Object> paramObj = new ArrayList<>();
+        if (standingInstructionDTO.transferType() != null) {
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            sqlBuilder.append(" atd.transfer_type=? ");
+            paramObj.add(standingInstructionDTO.transferType());
+            addAndCaluse = true;
+        }
+        if (standingInstructionDTO.clientId() != null) {
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            sqlBuilder.append(" fromclient.id=? ");
+            paramObj.add(standingInstructionDTO.clientId());
+            addAndCaluse = true;
+        } else if (standingInstructionDTO.clientName() != null) {
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            sqlBuilder.append(" fromclient.display_name=? ");
+            paramObj.add(standingInstructionDTO.clientName());
+            addAndCaluse = true;
+        }
+
+        if (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null) {
+            PortfolioAccountType accountType = PortfolioAccountType.fromInt(standingInstructionDTO.fromAccountType());
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            if (accountType.isSavingsAccount()) {
+                sqlBuilder.append(" fromsavacc.id=? ");
+                paramObj.add(standingInstructionDTO.fromAccount());
+            } else if (accountType.isLoanAccount()) {
+                sqlBuilder.append(" fromloanacc.id=? ");
+                paramObj.add(standingInstructionDTO.fromAccount());
+            }
+            addAndCaluse = true;
+        }
+
+        if (standingInstructionDTO.startDateRange() != null) {
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+            sqlBuilder.append(" atsih.execution_time >= ? ");
+            paramObj.add(df.format(standingInstructionDTO.startDateRange()));
+            addAndCaluse = true;
+        }
+        
+        if (standingInstructionDTO.endDateRange() != null) {
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+            sqlBuilder.append(" atsih.execution_time < ? ");
+            paramObj.add(df.format(standingInstructionDTO.endDateRange()));
+            addAndCaluse = true;
+        }
+
+        final SearchParameters searchParameters = standingInstructionDTO.searchParameters();
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final Object[] finalObjectArray = paramObj.toArray();
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
+                this.standingInstructionHistoryMapper);
+    }
+
+    private static final class StandingInstructionHistoryMapper implements RowMapper<StandingInstructionHistoryData> {
+
+        private final String schemaSql;
+
+        public StandingInstructionHistoryMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("atsi.id as id,atsi.name as name, ");
+            sqlBuilder.append("atsih.status as status, atsih.execution_time as executionTime, ");
+            sqlBuilder.append("atsih.amount as amount, atsih.error_log as errorLog, ");
+            sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,");
+            sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,");
+            sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,");
+            sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,");
+            sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,");
+            sqlBuilder.append("fromsp.id as fromProductId, fromsp.name as fromProductName, ");
+            sqlBuilder.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,");
+            sqlBuilder.append("fromlp.id as fromLoanProductId, fromlp.name as fromLoanProductName,");
+            sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,");
+            sqlBuilder.append("tosp.id as toProductId, tosp.name as toProductName, ");
+            sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo, ");
+            sqlBuilder.append("tolp.id as toLoanProductId, tolp.name as toLoanProductName ");
+            sqlBuilder.append(" FROM m_account_transfer_standing_instructions_history atsih ");
+            sqlBuilder.append(" join m_account_transfer_standing_instructions atsi on atsi.id = atsih.standing_instruction_id ");
+            sqlBuilder.append("join m_account_transfer_details atd on atd.id = atsi.account_transfer_details_id ");
+            sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id ");
+            sqlBuilder.append("join m_office tooff on tooff.id = atd.to_office_id ");
+            sqlBuilder.append("join m_client fromclient on fromclient.id = atd.from_client_id ");
+            sqlBuilder.append("join m_client toclient on toclient.id = atd.to_client_id ");
+            sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id ");
+            sqlBuilder.append("left join m_savings_product fromsp ON fromsavacc.product_id = fromsp.id ");
+            sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id ");
+            sqlBuilder.append("left join m_product_loan fromlp ON fromloanacc.product_id = fromlp.id ");
+            sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id ");
+            sqlBuilder.append("left join m_savings_product tosp ON tosavacc.product_id = tosp.id ");
+            sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id ");
+            sqlBuilder.append("left join m_product_loan tolp ON toloanacc.product_id = tolp.id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public StandingInstructionHistoryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+
+            final String status = rs.getString("status");
+            final LocalDate executionTime = JdbcSupport.getLocalDate(rs, "executionTime");
+            final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amount");
+            final String errorLog = rs.getString("errorLog");
+
+            final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId");
+            final String fromOfficeName = rs.getString("fromOfficeName");
+            final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null);
+
+            final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId");
+            final String toOfficeName = rs.getString("toOfficeName");
+            final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null);
+
+            final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId");
+            final String fromClientName = rs.getString("fromClientName");
+            final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName);
+
+            final Long toClientId = JdbcSupport.getLong(rs, "toClientId");
+            final String toClientName = rs.getString("toClientName");
+            final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName);
+
+            final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId");
+            final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo");
+            final Long fromProductId = JdbcSupport.getLong(rs, "fromProductId");
+            final String fromProductName = rs.getString("fromProductName");
+            final Long fromLoanAccountId = JdbcSupport.getLong(rs, "fromLoanAccountId");
+            final String fromLoanAccountNo = rs.getString("fromLoanAccountNo");
+            final Long fromLoanProductId = JdbcSupport.getLong(rs, "fromLoanProductId");
+            final String fromLoanProductName = rs.getString("fromLoanProductName");
+            PortfolioAccountData fromAccount = null;
+            EnumOptionData fromAccountType = null;
+            if (fromSavingsAccountId != null) {
+                fromAccount = new PortfolioAccountData(fromSavingsAccountId, fromSavingsAccountNo, null, null, null, null, null,
+                        fromProductId, fromProductName, null, null, null);
+                fromAccountType = accountType(PortfolioAccountType.SAVINGS);
+            } else if (fromLoanAccountId != null) {
+                fromAccount = new PortfolioAccountData(fromLoanAccountId, fromLoanAccountNo, null, null, null, null, null,
+                        fromLoanProductId, fromLoanProductName, null, null, null);
+                fromAccountType = accountType(PortfolioAccountType.LOAN);
+            }
+
+            PortfolioAccountData toAccount = null;
+            EnumOptionData toAccountType = null;
+            final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId");
+            final String toSavingsAccountNo = rs.getString("toSavingsAccountNo");
+            final Long toProductId = JdbcSupport.getLong(rs, "toProductId");
+            final String toProductName = rs.getString("toProductName");
+            final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId");
+            final String toLoanAccountNo = rs.getString("toLoanAccountNo");
+            final Long toLoanProductId = JdbcSupport.getLong(rs, "toLoanProductId");
+            final String toLoanProductName = rs.getString("toLoanProductName");
+
+            if (toSavingsAccountId != null) {
+                toAccount = new PortfolioAccountData(toSavingsAccountId, toSavingsAccountNo, null, null, null, null, null, toProductId,
+                        toProductName, null, null, null);
+                toAccountType = accountType(PortfolioAccountType.SAVINGS);
+            } else if (toLoanAccountId != null) {
+                toAccount = new PortfolioAccountData(toLoanAccountId, toLoanAccountNo, null, null, null, null, null, toLoanProductId,
+                        toLoanProductName, null, null, null);
+                toAccountType = accountType(PortfolioAccountType.LOAN);
+            }
+
+            return new StandingInstructionHistoryData(id, name, fromOffice, fromClient, fromAccountType, fromAccount, toAccountType,
+                    toAccount, toOffice, toClient, transferAmount, status, executionTime, errorLog);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformService.java
new file mode 100755
index 0000000..52574e2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformService.java
@@ -0,0 +1,41 @@
+/**
+ * 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.account.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
+import org.apache.fineract.portfolio.account.data.StandingInstructionData;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
+
+public interface StandingInstructionReadPlatformService {
+
+    StandingInstructionData retrieveTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
+            Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType, Integer transferType);
+
+    Page<StandingInstructionData> retrieveAll(StandingInstructionDTO standingInstructionDTO);
+
+    StandingInstructionData retrieveOne(Long instructionId);
+
+    Collection<StandingInstructionData> retrieveAll(Integer status);
+
+    StandingInstructionDuesData retriveLoanDuesData(Long loanId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java
new file mode 100755
index 0000000..9c35c4f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java
@@ -0,0 +1,591 @@
+/**
+ * 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.account.service;
+
+import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.accountType;
+import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.recurrenceType;
+import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.standingInstructionPriority;
+import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.standingInstructionStatus;
+import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.standingInstructionType;
+import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.transferType;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
+import org.apache.fineract.portfolio.account.data.StandingInstructionData;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
+import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+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;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class StandingInstructionReadPlatformServiceImpl implements StandingInstructionReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService;
+    private final DropdownReadPlatformService dropdownReadPlatformService;
+
+    // mapper
+    private final StandingInstructionMapper standingInstructionMapper;
+
+    // pagination
+    private final PaginationHelper<StandingInstructionData> paginationHelper = new PaginationHelper<>();
+
+    @Autowired
+    public StandingInstructionReadPlatformServiceImpl(final RoutingDataSource dataSource,
+            final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService,
+            final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService,
+            final DropdownReadPlatformService dropdownReadPlatformService) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.standingInstructionMapper = new StandingInstructionMapper();
+    }
+
+    @Override
+    public StandingInstructionData retrieveTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId,
+            final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId,
+            final Integer toAccountType, Integer transferType) {
+
+        AccountTransferType accountTransferType = AccountTransferType.INVALID;
+        if (transferType != null) {
+            accountTransferType = AccountTransferType.fromInt(transferType);
+        }
+
+        final EnumOptionData loanAccountType = accountType(PortfolioAccountType.LOAN);
+        final EnumOptionData savingsAccountType = accountType(PortfolioAccountType.SAVINGS);
+
+        final Integer mostRelevantFromAccountType = fromAccountType;
+        Collection<EnumOptionData> fromAccountTypeOptions = null;
+        Collection<EnumOptionData> toAccountTypeOptions = null;
+
+        if (accountTransferType.isAccountTransfer()) {
+            fromAccountTypeOptions = Arrays.asList(savingsAccountType);
+            toAccountTypeOptions = Arrays.asList(savingsAccountType);
+        } else if (accountTransferType.isLoanRepayment()) {
+            fromAccountTypeOptions = Arrays.asList(savingsAccountType);
+            toAccountTypeOptions = Arrays.asList(loanAccountType);
+        } else {
+            fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType);
+            toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType);
+        }
+        final Integer mostRelevantToAccountType = toAccountType;
+
+        final EnumOptionData fromAccountTypeData = accountType(mostRelevantFromAccountType);
+        final EnumOptionData toAccountTypeData = accountType(mostRelevantToAccountType);
+
+        // from settings
+        OfficeData fromOffice = null;
+        ClientData fromClient = null;
+        PortfolioAccountData fromAccount = null;
+
+        OfficeData toOffice = null;
+        ClientData toClient = null;
+        PortfolioAccountData toAccount = null;
+
+        // template
+        Collection<PortfolioAccountData> fromAccountOptions = null;
+        Collection<PortfolioAccountData> toAccountOptions = null;
+
+        Long mostRelevantFromOfficeId = fromOfficeId;
+        Long mostRelevantFromClientId = fromClientId;
+
+        Long mostRelevantToOfficeId = toOfficeId;
+        Long mostRelevantToClientId = toClientId;
+
+        if (fromAccountId != null) {
+            Integer accountType;
+            if (mostRelevantFromAccountType == 1) {
+                accountType = PortfolioAccountType.LOAN.getValue();
+            } else {
+                accountType = PortfolioAccountType.SAVINGS.getValue();
+            }
+            fromAccount = this.portfolioAccountReadPlatformService.retrieveOne(fromAccountId, accountType);
+
+            // override provided fromClient with client of account
+            mostRelevantFromClientId = fromAccount.clientId();
+        }
+
+        if (mostRelevantFromClientId != null) {
+            fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId);
+            mostRelevantFromOfficeId = fromClient.officeId();
+            long[] loanStatus = null;
+            if (mostRelevantFromAccountType == 1) {
+                loanStatus = new long[] { 300, 700 };
+            }
+            PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId,
+                    loanStatus);
+            fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO);
+        }
+
+        Collection<OfficeData> fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+        Collection<ClientData> fromClientOptions = null;
+
+        if (mostRelevantFromOfficeId != null) {
+            fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId);
+            fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
+        }
+
+        // defaults
+        final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
+        Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
+        Collection<ClientData> toClientOptions = null;
+
+        if (toAccountId != null && fromAccount != null) {
+            toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType,
+                    fromAccount.currencyCode());
+            mostRelevantToClientId = toAccount.clientId();
+        }
+
+        if (mostRelevantToClientId != null) {
+            toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId);
+            mostRelevantToOfficeId = toClient.officeId();
+
+            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+
+            toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
+        }
+
+        if (mostRelevantToOfficeId != null) {
+            toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId);
+            toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+            if (toClientOptions != null && toClientOptions.size() == 1) {
+                toClient = new ArrayList<>(toClientOptions).get(0);
+
+                toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
+            }
+        }
+
+        final Collection<EnumOptionData> transferTypeOptions = Arrays.asList(transferType(AccountTransferType.ACCOUNT_TRANSFER),
+                transferType(AccountTransferType.LOAN_REPAYMENT)/*
+                                                                 * ,
+                                                                 * transferType(
+                                                                 * AccountTransferType
+                                                                 * .
+                                                                 * CHARGE_PAYMENT
+                                                                 * )
+                                                                 */);
+        final Collection<EnumOptionData> statusOptions = Arrays.asList(standingInstructionStatus(StandingInstructionStatus.ACTIVE),
+                standingInstructionStatus(StandingInstructionStatus.DISABLED));
+        final Collection<EnumOptionData> instructionTypeOptions = Arrays.asList(standingInstructionType(StandingInstructionType.FIXED),
+                standingInstructionType(StandingInstructionType.DUES));
+        final Collection<EnumOptionData> priorityOptions = Arrays.asList(standingInstructionPriority(StandingInstructionPriority.URGENT),
+                standingInstructionPriority(StandingInstructionPriority.HIGH),
+                standingInstructionPriority(StandingInstructionPriority.MEDIUM),
+                standingInstructionPriority(StandingInstructionPriority.LOW));
+        final Collection<EnumOptionData> recurrenceTypeOptions = Arrays.asList(recurrenceType(AccountTransferRecurrenceType.PERIODIC),
+                recurrenceType(AccountTransferRecurrenceType.AS_PER_DUES));
+        final Collection<EnumOptionData> recurrenceFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
+
+        return StandingInstructionData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient,
+                toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
+                toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions, transferTypeOptions, statusOptions,
+                instructionTypeOptions, priorityOptions, recurrenceTypeOptions, recurrenceFrequencyOptions);
+    }
+
+    private Collection<PortfolioAccountData> retrieveToAccounts(final PortfolioAccountData excludeThisAccountFromOptions,
+            final Integer toAccountType, final Long toClientId) {
+
+        final String currencyCode = excludeThisAccountFromOptions != null ? excludeThisAccountFromOptions.currencyCode() : null;
+
+        PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(toAccountType, toClientId, currencyCode, null, null);
+        Collection<PortfolioAccountData> accountOptions = this.portfolioAccountReadPlatformService
+                .retrieveAllForLookup(portfolioAccountDTO);
+        if (!CollectionUtils.isEmpty(accountOptions)) {
+            accountOptions.remove(excludeThisAccountFromOptions);
+        } else {
+            accountOptions = null;
+        }
+
+        return accountOptions;
+    }
+
+    @Override
+    public Page<StandingInstructionData> retrieveAll(final StandingInstructionDTO standingInstructionDTO) {
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.standingInstructionMapper.schema());
+        if (standingInstructionDTO.transferType() != null || standingInstructionDTO.clientId() != null
+                || standingInstructionDTO.clientName() != null) {
+            sqlBuilder.append(" where ");
+        }
+        boolean addAndCaluse = false;
+        List<Object> paramObj = new ArrayList<>();
+        if (standingInstructionDTO.transferType() != null) {
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            sqlBuilder.append(" atd.transfer_type=? ");
+            paramObj.add(standingInstructionDTO.transferType());
+            addAndCaluse = true;
+        }
+        if (standingInstructionDTO.clientId() != null) {
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            sqlBuilder.append(" fromclient.id=? ");
+            paramObj.add(standingInstructionDTO.clientId());
+            addAndCaluse = true;
+        } else if (standingInstructionDTO.clientName() != null) {
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            sqlBuilder.append(" fromclient.display_name=? ");
+            paramObj.add(standingInstructionDTO.clientName());
+            addAndCaluse = true;
+        }
+
+        if (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null) {
+            PortfolioAccountType accountType = PortfolioAccountType.fromInt(standingInstructionDTO.fromAccountType());
+            if (addAndCaluse) {
+                sqlBuilder.append(" and ");
+            }
+            if (accountType.isSavingsAccount()) {
+                sqlBuilder.append(" fromsavacc.id=? ");
+                paramObj.add(standingInstructionDTO.fromAccount());
+            } else if (accountType.isLoanAccount()) {
+                sqlBuilder.append(" fromloanacc.id=? ");
+                paramObj.add(standingInstructionDTO.fromAccount());
+            }
+            addAndCaluse = true;
+        }
+
+        final SearchParameters searchParameters = standingInstructionDTO.searchParameters();
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final Object[] finalObjectArray = paramObj.toArray();
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
+                this.standingInstructionMapper);
+    }
+
+    @Override
+    public Collection<StandingInstructionData> retrieveAll(final Integer status) {
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select ");
+        sqlBuilder.append(this.standingInstructionMapper.schema());
+        sqlBuilder
+                .append(" where atsi.status=? and CURRENT_DATE() >= atsi.valid_from and (atsi.valid_till IS NULL or CURRENT_DATE() < atsi.valid_till) ")
+                .append(" and  (atsi.last_run_date <> CURRENT_DATE() or atsi.last_run_date IS NULL)")
+                .append(" ORDER BY atsi.priority DESC");
+        return this.jdbcTemplate.query(sqlBuilder.toString(), this.standingInstructionMapper, status);
+    }
+
+    @Override
+    public StandingInstructionData retrieveOne(final Long instructionId) {
+
+        try {
+            final String sql = "select " + this.standingInstructionMapper.schema() + " where atsi.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, this.standingInstructionMapper, new Object[] { instructionId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new AccountTransferNotFoundException(instructionId);
+        }
+    }
+
+    @Override
+    public StandingInstructionDuesData retriveLoanDuesData(final Long loanId) {
+        final StandingInstructionLoanDuesMapper rm = new StandingInstructionLoanDuesMapper();
+        final String sql = "select " + rm.schema() + " where ml.id= ? and ls.duedate <= CURRENT_DATE() and ls.completed_derived <> 1";
+        return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanId });
+    }
+
+    private static final class StandingInstructionMapper implements RowMapper<StandingInstructionData> {
+
+        private final String schemaSql;
+
+        public StandingInstructionMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("atsi.id as id,atsi.name as name, atsi.priority as priority,");
+            sqlBuilder.append("atsi.status as status, atsi.instruction_type as instructionType,");
+            sqlBuilder.append("atsi.amount as amount,");
+            sqlBuilder.append("atsi.valid_from as validFrom, atsi.valid_till as validTill,");
+            sqlBuilder.append("atsi.recurrence_type as recurrenceType, atsi.recurrence_frequency as recurrenceFrequency,");
+            sqlBuilder.append("atsi.recurrence_interval as recurrenceInterval, atsi.recurrence_on_day as recurrenceOnDay,");
+            sqlBuilder.append("atsi.recurrence_on_month as recurrenceOnMonth,");
+            sqlBuilder.append("atd.id as accountDetailId,atd.transfer_type as transferType,");
+            sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,");
+            sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,");
+            sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,");
+            sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,");
+            sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,");
+            sqlBuilder.append("fromsp.id as fromProductId, fromsp.name as fromProductName, ");
+            sqlBuilder.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,");
+            sqlBuilder.append("fromlp.id as fromLoanProductId, fromlp.name as fromLoanProductName,");
+            sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,");
+            sqlBuilder.append("tosp.id as toProductId, tosp.name as toProductName, ");
+            sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo, ");
+            sqlBuilder.append("tolp.id as toLoanProductId, tolp.name as toLoanProductName ");
+            sqlBuilder.append(" FROM m_account_transfer_standing_instructions atsi ");
+            sqlBuilder.append("join m_account_transfer_details atd on atd.id = atsi.account_transfer_details_id ");
+            sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id ");
+            sqlBuilder.append("join m_office tooff on tooff.id = atd.to_office_id ");
+            sqlBuilder.append("join m_client fromclient on fromclient.id = atd.from_client_id ");
+            sqlBuilder.append("join m_client toclient on toclient.id = atd.to_client_id ");
+            sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id ");
+            sqlBuilder.append("left join m_savings_product fromsp ON fromsavacc.product_id = fromsp.id ");
+            sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id ");
+            sqlBuilder.append("left join m_product_loan fromlp ON fromloanacc.product_id = fromlp.id ");
+            sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id ");
+            sqlBuilder.append("left join m_savings_product tosp ON tosavacc.product_id = tosp.id ");
+            sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id ");
+            sqlBuilder.append("left join m_product_loan tolp ON toloanacc.product_id = tolp.id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public StandingInstructionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long accountDetailId = rs.getLong("accountDetailId");
+            final String name = rs.getString("name");
+            final Integer priority = JdbcSupport.getInteger(rs, "priority");
+            EnumOptionData priorityEnum = AccountTransferEnumerations.standingInstructionPriority(priority);
+
+            final Integer status = JdbcSupport.getInteger(rs, "status");
+            EnumOptionData statusEnum = AccountTransferEnumerations.standingInstructionStatus(status);
+            final Integer instructionType = JdbcSupport.getInteger(rs, "instructionType");
+            EnumOptionData instructionTypeEnum = AccountTransferEnumerations.standingInstructionType(instructionType);
+            final LocalDate validFrom = JdbcSupport.getLocalDate(rs, "validFrom");
+            final LocalDate validTill = JdbcSupport.getLocalDate(rs, "validTill");
+            final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amount");
+            final Integer recurrenceType = JdbcSupport.getInteger(rs, "recurrenceType");
+            EnumOptionData recurrenceTypeEnum = AccountTransferEnumerations.recurrenceType(recurrenceType);
+            final Integer recurrenceFrequency = JdbcSupport.getInteger(rs, "recurrenceFrequency");
+
+            EnumOptionData recurrenceFrequencyEnum = null;
+            if (recurrenceFrequency != null) {
+                recurrenceFrequencyEnum = CommonEnumerations.termFrequencyType(recurrenceFrequency, "recurrence");
+            }
+            final Integer recurrenceInterval = JdbcSupport.getInteger(rs, "recurrenceInterval");
+
+            MonthDay recurrenceOnMonthDay = null;
+            final Integer recurrenceOnDay = JdbcSupport.getInteger(rs, "recurrenceOnDay");
+            final Integer recurrenceOnMonth = JdbcSupport.getInteger(rs, "recurrenceOnMonth");
+            if (recurrenceOnDay != null) {
+                recurrenceOnMonthDay = new MonthDay(recurrenceOnMonth, recurrenceOnDay);
+            }
+
+            final Integer transferType = rs.getInt("transferType");
+            EnumOptionData transferTypeEnum = AccountTransferEnumerations.transferType(transferType);
+
+            /*
+             * final String currencyCode = rs.getString("currencyCode"); final
+             * String currencyName = rs.getString("currencyName"); final String
+             * currencyNameCode = rs.getString("currencyNameCode"); final String
+             * currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+             * final Integer currencyDigits = JdbcSupport.getInteger(rs,
+             * "currencyDigits"); final Integer inMultiplesOf =
+             * JdbcSupport.getInteger(rs, "inMultiplesOf"); final CurrencyData
+             * currency = new CurrencyData(currencyCode, currencyName,
+             * currencyDigits, inMultiplesOf, currencyDisplaySymbol,
+             * currencyNameCode);
+             */
+            final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId");
+            final String fromOfficeName = rs.getString("fromOfficeName");
+            final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null);
+
+            final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId");
+            final String toOfficeName = rs.getString("toOfficeName");
+            final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null);
+
+            final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId");
+            final String fromClientName = rs.getString("fromClientName");
+            final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName);
+
+            final Long toClientId = JdbcSupport.getLong(rs, "toClientId");
+            final String toClientName = rs.getString("toClientName");
+            final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName);
+
+            final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId");
+            final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo");
+            final Long fromProductId = JdbcSupport.getLong(rs, "fromProductId");
+            final String fromProductName = rs.getString("fromProductName");
+            final Long fromLoanAccountId = JdbcSupport.getLong(rs, "fromLoanAccountId");
+            final String fromLoanAccountNo = rs.getString("fromLoanAccountNo");
+            final Long fromLoanProductId = JdbcSupport.getLong(rs, "fromLoanProductId");
+            final String fromLoanProductName = rs.getString("fromLoanProductName");
+            PortfolioAccountData fromAccount = null;
+            EnumOptionData fromAccountType = null;
+            if (fromSavingsAccountId != null) {
+                fromAccount = new PortfolioAccountData(fromSavingsAccountId, fromSavingsAccountNo, null, null, null, null, null,
+                        fromProductId, fromProductName, null, null, null);
+                fromAccountType = accountType(PortfolioAccountType.SAVINGS);
+            } else if (fromLoanAccountId != null) {
+                fromAccount = new PortfolioAccountData(fromLoanAccountId, fromLoanAccountNo, null, null, null, null, null,
+                        fromLoanProductId, fromLoanProductName, null, null, null);
+                fromAccountType = accountType(PortfolioAccountType.LOAN);
+            }
+
+            PortfolioAccountData toAccount = null;
+            EnumOptionData toAccountType = null;
+            final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId");
+            final String toSavingsAccountNo = rs.getString("toSavingsAccountNo");
+            final Long toProductId = JdbcSupport.getLong(rs, "toProductId");
+            final String toProductName = rs.getString("toProductName");
+            final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId");
+            final String toLoanAccountNo = rs.getString("toLoanAccountNo");
+            final Long toLoanProductId = JdbcSupport.getLong(rs, "toLoanProductId");
+            final String toLoanProductName = rs.getString("toLoanProductName");
+
+            if (toSavingsAccountId != null) {
+                toAccount = new PortfolioAccountData(toSavingsAccountId, toSavingsAccountNo, null, null, null, null, null, toProductId,
+                        toProductName, null, null, null);
+                toAccountType = accountType(PortfolioAccountType.SAVINGS);
+            } else if (toLoanAccountId != null) {
+                toAccount = new PortfolioAccountData(toLoanAccountId, toLoanAccountNo, null, null, null, null, null, toLoanProductId,
+                        toLoanProductName, null, null, null);
+                toAccountType = accountType(PortfolioAccountType.LOAN);
+            }
+
+            return StandingInstructionData.instance(id, accountDetailId, name, fromOffice, toOffice, fromClient, toClient, fromAccountType,
+                    fromAccount, toAccountType, toAccount, transferTypeEnum, priorityEnum, instructionTypeEnum, statusEnum, transferAmount,
+                    validFrom, validTill, recurrenceTypeEnum, recurrenceFrequencyEnum, recurrenceInterval, recurrenceOnMonthDay);
+        }
+    }
+
+    private static final class StandingInstructionLoanDuesMapper implements RowMapper<StandingInstructionDuesData> {
+
+        private final String schemaSql;
+
+        public StandingInstructionLoanDuesMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder.append("max(ls.duedate) as dueDate,sum(ls.principal_amount) as principalAmount,");
+            sqlBuilder.append("sum(ls.principal_completed_derived) as principalCompleted,");
+            sqlBuilder.append("sum(ls.principal_writtenoff_derived) as principalWrittenOff,");
+            sqlBuilder.append("sum(ls.interest_amount) as interestAmount,");
+            sqlBuilder.append("sum(ls.interest_completed_derived) as interestCompleted,");
+            sqlBuilder.append("sum(ls.interest_writtenoff_derived) as interestWrittenOff,");
+            sqlBuilder.append("sum(ls.interest_waived_derived) as interestWaived,");
+            sqlBuilder.append("sum(ls.penalty_charges_amount) as penalityAmount,");
+            sqlBuilder.append("sum(ls.penalty_charges_completed_derived) as penalityCompleted,");
+            sqlBuilder.append("sum(ls.penalty_charges_writtenoff_derived)as penaltyWrittenOff,");
+            sqlBuilder.append("sum(ls.penalty_charges_waived_derived) as penaltyWaived,");
+            sqlBuilder.append("sum(ls.fee_charges_amount) as feeAmount,");
+            sqlBuilder.append("sum(ls.fee_charges_completed_derived) as feecompleted,");
+            sqlBuilder.append("sum(ls.fee_charges_writtenoff_derived) as feeWrittenOff,");
+            sqlBuilder.append("sum(ls.fee_charges_waived_derived) as feeWaived ");
+            sqlBuilder.append("from m_loan_repayment_schedule ls ");
+            sqlBuilder.append(" join m_loan ml on ml.id = ls.loan_id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public StandingInstructionDuesData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
+            final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalAmount");
+            final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalCompleted");
+            final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
+            final BigDecimal principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff);
+
+            final BigDecimal interestExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestAmount");
+            final BigDecimal interestPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestCompleted");
+            final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff");
+            final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
+            final BigDecimal interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff);
+            final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid);
+
+            final BigDecimal penaltyChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalityAmount");
+            final BigDecimal penaltyChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalityCompleted");
+            final BigDecimal penaltyChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyWrittenOff");
+            final BigDecimal penaltyChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyWaived");
+            final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived).subtract(
+                    penaltyChargesWrittenOff);
+            final BigDecimal penaltyChargesOutstanding = penaltyChargesActualDue.subtract(penaltyChargesPaid);
+
+            final BigDecimal feeChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeAmount");
+            final BigDecimal feeChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feecompleted");
+            final BigDecimal feeChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeWrittenOff");
+            final BigDecimal feeChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeWaived");
+            final BigDecimal feeChargesActualDue = feeChargesExpectedDue.subtract(feeChargesWaived).subtract(feeChargesWrittenOff);
+            final BigDecimal feeChargesOutstanding = feeChargesActualDue.subtract(feeChargesPaid);
+
+            final BigDecimal totalOutstanding = principalOutstanding.add(interestOutstanding).add(feeChargesOutstanding)
+                    .add(penaltyChargesOutstanding);
+
+            return new StandingInstructionDuesData(dueDate, totalOutstanding);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformService.java
new file mode 100755
index 0000000..922f8b0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformService.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.portfolio.account.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+
+public interface StandingInstructionWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    CommandProcessingResult update(Long id, JsonCommand command);
+
+    void executeStandingInstructions() throws JobExecutionException;
+
+    CommandProcessingResult delete(Long id);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java
new file mode 100755
index 0000000..9d6a48b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java
@@ -0,0 +1,295 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.account.service;
+
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.account.data.StandingInstructionData;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
+import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
+import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
+import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
+import org.apache.fineract.portfolio.account.exception.StandingInstructionNotFoundException;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
+import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class StandingInstructionWritePlatformServiceImpl implements StandingInstructionWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(StandingInstructionWritePlatformServiceImpl.class);
+
+    private final StandingInstructionDataValidator standingInstructionDataValidator;
+    private final StandingInstructionAssembler standingInstructionAssembler;
+    private final AccountTransferDetailRepository accountTransferDetailRepository;
+    private final StandingInstructionRepository standingInstructionRepository;
+    private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
+    private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public StandingInstructionWritePlatformServiceImpl(final StandingInstructionDataValidator standingInstructionDataValidator,
+            final StandingInstructionAssembler standingInstructionAssembler,
+            final AccountTransferDetailRepository accountTransferDetailRepository,
+            final StandingInstructionRepository standingInstructionRepository,
+            final StandingInstructionReadPlatformService standingInstructionReadPlatformService,
+            final AccountTransfersWritePlatformService accountTransfersWritePlatformService, final RoutingDataSource dataSource) {
+        this.standingInstructionDataValidator = standingInstructionDataValidator;
+        this.standingInstructionAssembler = standingInstructionAssembler;
+        this.accountTransferDetailRepository = accountTransferDetailRepository;
+        this.standingInstructionRepository = standingInstructionRepository;
+        this.standingInstructionReadPlatformService = standingInstructionReadPlatformService;
+        this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult create(final JsonCommand command) {
+
+        this.standingInstructionDataValidator.validateForCreate(command);
+
+        final Integer fromAccountTypeId = command.integerValueSansLocaleOfParameterNamed(fromAccountTypeParamName);
+        final PortfolioAccountType fromAccountType = PortfolioAccountType.fromInt(fromAccountTypeId);
+
+        final Integer toAccountTypeId = command.integerValueSansLocaleOfParameterNamed(toAccountTypeParamName);
+        final PortfolioAccountType toAccountType = PortfolioAccountType.fromInt(toAccountTypeId);
+
+        final Long fromClientId = command.longValueOfParameterNamed(fromClientIdParamName);
+
+        Long standingInstructionId = null;
+        try {
+            if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) {
+                final AccountTransferDetails standingInstruction = this.standingInstructionAssembler
+                        .assembleSavingsToSavingsTransfer(command);
+                this.accountTransferDetailRepository.save(standingInstruction);
+                standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
+            } else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) {
+                final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleSavingsToLoanTransfer(command);
+                this.accountTransferDetailRepository.save(standingInstruction);
+                standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
+            } else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) {
+
+                final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleLoanToSavingsTransfer(command);
+                this.accountTransferDetailRepository.save(standingInstruction);
+                standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
+
+            }
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+        final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(standingInstructionId)
+                .withClientId(fromClientId);
+        return builder.build();
+    }
+
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("name")) {
+            final String name = command.stringValueOfParameterNamed(StandingInstructionApiConstants.nameParamName);
+            throw new PlatformDataIntegrityException("error.msg.standinginstruction.duplicate.name", "Standinginstruction with name `"
+                    + name + "` already exists", "name", name);
+        }
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.client.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
+        return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount();
+    }
+
+    private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
+        return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount();
+    }
+
+    private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
+        return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
+    }
+
+    @Override
+    public CommandProcessingResult update(final Long id, final JsonCommand command) {
+        this.standingInstructionDataValidator.validateForUpdate(command);
+        AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findOne(id);
+        if (standingInstructionsForUpdate == null) { throw new StandingInstructionNotFoundException(id); }
+        final Map<String, Object> actualChanges = standingInstructionsForUpdate.update(command);
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(id) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult delete(final Long id) {
+        AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findOne(id);
+        standingInstructionsForUpdate.updateStatus(StandingInstructionStatus.DELETED.getValue());
+        final Map<String, Object> actualChanges = new HashMap<>();
+        actualChanges.put(statusParamName, StandingInstructionStatus.DELETED.getValue());
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(id) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.EXECUTE_STANDING_INSTRUCTIONS)
+    public void executeStandingInstructions() throws JobExecutionException {
+        Collection<StandingInstructionData> instructionDatas = this.standingInstructionReadPlatformService
+                .retrieveAll(StandingInstructionStatus.ACTIVE.getValue());
+        final StringBuilder sb = new StringBuilder();
+        for (StandingInstructionData data : instructionDatas) {
+            boolean isDueForTransfer = false;
+            AccountTransferRecurrenceType recurrenceType = data.recurrenceType();
+            StandingInstructionType instructionType = data.instructionType();
+            LocalDate transactionDate = new LocalDate();
+            if (recurrenceType.isPeriodicRecurrence()) {
+                final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
+                PeriodFrequencyType frequencyType = data.recurrenceFrequency();
+                LocalDate startDate = data.validFrom();
+                if (frequencyType.isMonthly()) {
+                    startDate = startDate.withDayOfMonth(data.recurrenceOnDay());
+                    if (startDate.isBefore(data.validFrom())) {
+                        startDate = startDate.plusMonths(1);
+                    }
+                } else if (frequencyType.isYearly()) {
+                    startDate = startDate.withDayOfMonth(data.recurrenceOnDay()).withMonthOfYear(data.recurrenceOnMonth());
+                    if (startDate.isBefore(data.validFrom())) {
+                        startDate = startDate.plusYears(1);
+                    }
+                }
+                isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType, data.recurrenceInterval(), startDate,
+                        transactionDate);
+
+            }
+            BigDecimal transactionAmount = data.amount();
+            if (data.toAccountType().isLoanAccount()
+                    && (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
+                StandingInstructionDuesData standingInstructionDuesData = this.standingInstructionReadPlatformService
+                        .retriveLoanDuesData(data.toAccount().accountId());
+                if (data.instructionType().isDuesAmoutTransfer()) {
+                    transactionAmount = standingInstructionDuesData.totalDueAmount();
+                }
+                if (recurrenceType.isDuesRecurrence()) {
+                    isDueForTransfer = new LocalDate().equals(standingInstructionDuesData.dueDate());
+                }
+            }
+
+            if (isDueForTransfer && transactionAmount != null && transactionAmount.compareTo(BigDecimal.ZERO) > 0) {
+                final AccountTransferDetails accountTransferDetails = this.accountTransferDetailRepository.findOne(data.accountDetailId());
+                final SavingsAccount fromSavingsAccount = null;
+                final boolean isRegularTransaction = true;
+                final boolean isExceptionForBalanceCheck = false;
+                accountTransferDetails.accountTransferStandingInstruction().updateLatsRunDate(transactionDate.toDate());
+                AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount, data.fromAccountType(),
+                        data.toAccountType(), data.fromAccount().accountId(), data.toAccount().accountId(), data.name()
+                                + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null, data
+                                .transferType().getValue(), accountTransferDetails, null, null, null, null, fromSavingsAccount,
+                        isRegularTransaction, isExceptionForBalanceCheck);
+                transferAmount(sb, accountTransferDTO, data.getId());
+            }
+        }
+        if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
+
+    }
+
+    /**
+     * @param sb
+     * @param accountTransferDTO
+     */
+    private void transferAmount(final StringBuilder sb, final AccountTransferDTO accountTransferDTO, final Long instructionId) {
+        StringBuffer errorLog = new StringBuffer();
+        StringBuffer updateQuery = new StringBuffer(
+                "INSERT INTO `m_account_transfer_standing_instructions_history` (`standing_instruction_id`, `status`, `amount`,`execution_time`, `error_log`) VALUES (");
+        try {
+            this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+        } catch (final PlatformApiDataValidationException e) {
+            sb.append("Validation exception while trasfering funds for standing Instruction id").append(instructionId).append(" from ")
+                    .append(accountTransferDTO.getFromAccountId()).append(" to ").append(accountTransferDTO.getToAccountId())
+                    .append("--------");
+            errorLog.append("Validation exception while trasfering funds " + e.getDefaultUserMessage());
+        } catch (final InsufficientAccountBalanceException e) {
+            sb.append("InsufficientAccountBalance Exception while trasfering funds for standing Instruction id").append(instructionId)
+                    .append(" from ").append(accountTransferDTO.getFromAccountId()).append(" to ")
+                    .append(accountTransferDTO.getToAccountId()).append("--------");
+            errorLog.append("InsufficientAccountBalance Exception ");
+        } catch (final AbstractPlatformServiceUnavailableException e) {
+            sb.append("Platform exception while trasfering funds for standing Instruction id").append(instructionId).append(" from ")
+                    .append(accountTransferDTO.getFromAccountId()).append(" to ").append(accountTransferDTO.getToAccountId())
+                    .append("--------");
+            errorLog.append("Platform exception while trasfering funds " + e.getDefaultUserMessage());
+        } catch (Exception e) {
+            sb.append("Exception while trasfering funds for standing Instruction id").append(instructionId).append(" from ")
+                    .append(accountTransferDTO.getFromAccountId()).append(" to ").append(accountTransferDTO.getToAccountId())
+                    .append("--------");
+            errorLog.append("Exception while trasfering funds " + e.getMessage());
+
+        }
+        updateQuery.append(instructionId).append(",");
+        if (errorLog.length() > 0) {
+            updateQuery.append("'failed'").append(",");
+        } else {
+            updateQuery.append("'success'").append(",");
+        }
+        updateQuery.append(accountTransferDTO.getTransactionAmount().doubleValue());
+        updateQuery.append(", now(),");
+        updateQuery.append("'").append(errorLog.toString()).append("')");
+        this.jdbcTemplate.update(updateQuery.toString());
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/AccountSummaryCollectionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/AccountSummaryCollectionData.java
new file mode 100644
index 0000000..2e7f1c6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/AccountSummaryCollectionData.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.accountdetails.data;
+
+import java.util.Collection;
+
+/**
+ * Immutable data object representing a summary of various accounts.
+ */
+public class AccountSummaryCollectionData {
+
+    @SuppressWarnings("unused")
+    private final Collection<LoanAccountSummaryData> loanAccounts;
+    @SuppressWarnings("unused")
+    private final Collection<SavingsAccountSummaryData> savingsAccounts;
+    @SuppressWarnings("unused")
+    private final Collection<LoanAccountSummaryData> memberLoanAccounts;
+    @SuppressWarnings("unused")
+    private final Collection<SavingsAccountSummaryData> memberSavingsAccounts;
+
+    public AccountSummaryCollectionData(final Collection<LoanAccountSummaryData> loanAccounts,
+            final Collection<SavingsAccountSummaryData> savingsAccounts) {
+        this.loanAccounts = defaultLoanAccountsIfEmpty(loanAccounts);
+        this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
+        this.memberLoanAccounts = null;
+        this.memberSavingsAccounts = null;
+    }
+
+    public AccountSummaryCollectionData(final Collection<LoanAccountSummaryData> loanAccounts,
+            final Collection<SavingsAccountSummaryData> savingsAccounts, final Collection<LoanAccountSummaryData> memberLoanAccounts,
+            final Collection<SavingsAccountSummaryData> memberSavingsAccounts) {
+        this.loanAccounts = defaultLoanAccountsIfEmpty(loanAccounts);
+        this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
+        this.memberLoanAccounts = defaultLoanAccountsIfEmpty(memberLoanAccounts);
+        this.memberSavingsAccounts = defaultSavingsAccountsIfEmpty(memberSavingsAccounts);
+    }
+
+    private Collection<LoanAccountSummaryData> defaultLoanAccountsIfEmpty(final Collection<LoanAccountSummaryData> collection) {
+        Collection<LoanAccountSummaryData> returnCollection = null;
+        if (collection != null && !collection.isEmpty()) {
+            returnCollection = collection;
+        }
+        return returnCollection;
+    }
+
+    private Collection<SavingsAccountSummaryData> defaultSavingsAccountsIfEmpty(final Collection<SavingsAccountSummaryData> collection) {
+        Collection<SavingsAccountSummaryData> returnCollection = null;
+        if (collection != null && !collection.isEmpty()) {
+            returnCollection = collection;
+        }
+        return returnCollection;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/LoanAccountSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/LoanAccountSummaryData.java
new file mode 100644
index 0000000..a4c7517
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/LoanAccountSummaryData.java
@@ -0,0 +1,66 @@
+/**
+ * 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.accountdetails.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
+
+/**
+ * Immutable data object for loan accounts.
+ */
+@SuppressWarnings("unused")
+public class LoanAccountSummaryData {
+
+    private final Long id;
+    private final String accountNo;
+    private final String externalId;
+    private final Long productId;
+    private final String productName;
+    private final String shortProductName;
+    private final LoanStatusEnumData status;
+    private final EnumOptionData loanType;
+    private final Integer loanCycle;
+    private final LoanApplicationTimelineData timeline;
+    private final Boolean inArrears;
+    private final BigDecimal originalLoan;
+    private final BigDecimal loanBalance;
+    private final BigDecimal amountPaid;
+    
+    public LoanAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
+            final String loanProductName, final String shortLoanProductName, final LoanStatusEnumData loanStatus, final EnumOptionData loanType, final Integer loanCycle,
+            final LoanApplicationTimelineData timeline, final Boolean inArrears,final BigDecimal originalLoan,final BigDecimal loanBalance,final BigDecimal amountPaid) {
+        this.id = id;
+        this.accountNo = accountNo;
+        this.externalId = externalId;
+        this.productId = productId;
+        this.productName = loanProductName;
+        this.shortProductName = shortLoanProductName;
+        this.status = loanStatus;
+        this.loanType = loanType;
+        this.loanCycle = loanCycle;
+        this.timeline = timeline;
+        this.inArrears = inArrears;
+        this.loanBalance = loanBalance;
+        this.originalLoan = originalLoan;
+        this.amountPaid = amountPaid;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/SavingsAccountSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/SavingsAccountSummaryData.java
new file mode 100644
index 0000000..f14962a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/SavingsAccountSummaryData.java
@@ -0,0 +1,66 @@
+/**
+ * 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.accountdetails.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
+
+/**
+ * Immutable data object for savings accounts.
+ */
+@SuppressWarnings("unused")
+public class SavingsAccountSummaryData {
+
+    private final Long id;
+    private final String accountNo;
+    private final String externalId;
+    private final Long productId;
+    private final String productName;
+    private final String shortProductName;
+    private final SavingsAccountStatusEnumData status;
+    private final CurrencyData currency;
+    private final BigDecimal accountBalance;
+    //differentiate Individual, JLG or Group account
+    private final EnumOptionData accountType;
+    private final SavingsAccountApplicationTimelineData timeline;
+
+    //differentiate deposit accounts Savings, FD and RD accounts
+    private final EnumOptionData depositType;
+
+    public SavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
+            final String productName, final String shortProductName, final SavingsAccountStatusEnumData status, final CurrencyData currency,
+            final BigDecimal accountBalance, final EnumOptionData accountType, final SavingsAccountApplicationTimelineData timeline, final EnumOptionData depositType) {
+        this.id = id;
+        this.accountNo = accountNo;
+        this.externalId = externalId;
+        this.productId = productId;
+        this.productName = productName;
+        this.shortProductName = shortProductName;
+        this.status = status;
+        this.currency = currency;
+        this.accountBalance = accountBalance;
+        this.accountType = accountType;
+        this.timeline = timeline;
+        this.depositType = depositType;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/domain/AccountType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/domain/AccountType.java
new file mode 100644
index 0000000..386d67c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/domain/AccountType.java
@@ -0,0 +1,94 @@
+/**
+ * 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.accountdetails.domain;
+
+/**
+ * Enum representation of account types .
+ */
+public enum AccountType {
+
+    INVALID(0, "accountType.invalid"), //
+    INDIVIDUAL(1, "accountType.individual"), //
+    GROUP(2, "accountType.group"), //
+    JLG(3, "accountType.jlg");// JLG account given in group context
+
+    private final Integer value;
+    private final String code;
+
+    private AccountType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public static AccountType fromInt(final Integer accountTypeValue) {
+
+        AccountType enumeration = AccountType.INVALID;
+        switch (accountTypeValue) {
+            case 1:
+                enumeration = AccountType.INDIVIDUAL;
+            break;
+            case 2:
+                enumeration = AccountType.GROUP;
+            break;
+            case 3:
+                enumeration = AccountType.JLG;
+            break;
+        }
+        return enumeration;
+    }
+
+    public static AccountType fromName(final String name) {
+        AccountType accountType = AccountType.INVALID;
+        for (final AccountType type : AccountType.values()) {
+            if (type.getName().equals(name)) {
+                accountType = type;
+                break;
+            }
+        }
+        return accountType;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public String getName() {
+        return name().toLowerCase();
+    }
+
+    public boolean isInvalid() {
+        return this.value.equals(AccountType.INVALID.getValue());
+    }
+
+    public boolean isIndividualAccount() {
+        return this.value.equals(AccountType.INDIVIDUAL.getValue());
+    }
+
+    public boolean isGroupAccount() {
+        return this.value.equals(AccountType.GROUP.getValue());
+    }
+
+    public boolean isJLGAccount() {
+        return this.value.equals(AccountType.JLG.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformService.java
new file mode 100755
index 0000000..58bb057
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformService.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.portfolio.accountdetails.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData;
+import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData;
+
+public interface AccountDetailsReadPlatformService {
+
+    public AccountSummaryCollectionData retrieveClientAccountDetails(final Long clientId);
+
+    public AccountSummaryCollectionData retrieveGroupAccountDetails(final Long groupId);
+
+    public Collection<LoanAccountSummaryData> retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId);
+
+    public Collection<LoanAccountSummaryData> retrieveGroupLoanAccountsByLoanOfficerId(final Long groupId, final Long loanOfficerId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..5622c8d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,368 @@
+/**
+ * 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.accountdetails.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData;
+import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData;
+import org.apache.fineract.portfolio.accountdetails.data.SavingsAccountSummaryData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements AccountDetailsReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final GroupReadPlatformService groupReadPlatformService;
+
+    @Autowired
+    public AccountDetailsReadPlatformServiceJpaRepositoryImpl(final ClientReadPlatformService clientReadPlatformService,
+            final RoutingDataSource dataSource, final GroupReadPlatformService groupReadPlatformService) {
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.groupReadPlatformService = groupReadPlatformService;
+    }
+
+    @Override
+    public AccountSummaryCollectionData retrieveClientAccountDetails(final Long clientId) {
+        // Check if client exists
+        this.clientReadPlatformService.retrieveOne(clientId);
+        final String loanwhereClause = " where l.client_id = ?";
+        final String savingswhereClause = " where sa.client_id = ? order by sa.status_enum ASC, sa.account_no ASC";
+        final List<LoanAccountSummaryData> loanAccounts = retrieveLoanAccountDetails(loanwhereClause, new Object[] { clientId });
+        final List<SavingsAccountSummaryData> savingsAccounts = retrieveAccountDetails(savingswhereClause, new Object[] { clientId });
+        return new AccountSummaryCollectionData(loanAccounts, savingsAccounts);
+    }
+
+    @Override
+    public AccountSummaryCollectionData retrieveGroupAccountDetails(final Long groupId) {
+        // Check if group exists
+        this.groupReadPlatformService.retrieveOne(groupId);
+        final String loanWhereClauseForGroup = " where l.group_id = ? and l.client_id is null";
+        final String loanWhereClauseForMembers = " where l.group_id = ? and l.client_id is not null";
+        final String savingswhereClauseForGroup = " where sa.group_id = ? and sa.client_id is null order by sa.status_enum ASC, sa.account_no ASC";
+        final String savingswhereClauseForMembers = " where sa.group_id = ? and sa.client_id is not null order by sa.status_enum ASC, sa.account_no ASC";
+        final List<LoanAccountSummaryData> groupLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroup, new Object[] { groupId });
+        final List<SavingsAccountSummaryData> groupSavingsAccounts = retrieveAccountDetails(savingswhereClauseForGroup,
+                new Object[] { groupId });
+        final List<LoanAccountSummaryData> memberLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForMembers,
+                new Object[] { groupId });
+        final List<SavingsAccountSummaryData> memberSavingsAccounts = retrieveAccountDetails(savingswhereClauseForMembers,
+                new Object[] { groupId });
+        return new AccountSummaryCollectionData(groupLoanAccounts, groupSavingsAccounts, memberLoanAccounts, memberSavingsAccounts);
+    }
+
+    @Override
+    public Collection<LoanAccountSummaryData> retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId) {
+        // Check if client exists
+        this.clientReadPlatformService.retrieveOne(clientId);
+        final String loanWhereClause = " where l.client_id = ? and l.loan_officer_id = ?";
+        return retrieveLoanAccountDetails(loanWhereClause, new Object[] { clientId, loanOfficerId });
+    }
+
+    @Override
+    public Collection<LoanAccountSummaryData> retrieveGroupLoanAccountsByLoanOfficerId(final Long groupId, final Long loanOfficerId) {
+        // Check if group exists
+        this.groupReadPlatformService.retrieveOne(groupId);
+        final String loanWhereClause = " where l.group_id = ? and l.client_id is null and l.loan_officer_id = ?";
+        return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId, loanOfficerId });
+    }
+
+    private List<LoanAccountSummaryData> retrieveLoanAccountDetails(final String loanwhereClause, final Object[] inputs) {
+        final LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper();
+        final String sql = "select " + rm.loanAccountSummarySchema() + loanwhereClause;
+        return this.jdbcTemplate.query(sql, rm, inputs);
+    }
+
+    /**
+     * @param entityId
+     * @return
+     */
+    private List<SavingsAccountSummaryData> retrieveAccountDetails(final String savingswhereClause, final Object[] inputs) {
+        final SavingsAccountSummaryDataMapper savingsAccountSummaryDataMapper = new SavingsAccountSummaryDataMapper();
+        final String savingsSql = "select " + savingsAccountSummaryDataMapper.schema() + savingswhereClause;
+        return this.jdbcTemplate.query(savingsSql, savingsAccountSummaryDataMapper, inputs);
+    }
+
+    private static final class SavingsAccountSummaryDataMapper implements RowMapper<SavingsAccountSummaryData> {
+
+        final String schemaSql;
+
+        public SavingsAccountSummaryDataMapper() {
+            final StringBuilder accountsSummary = new StringBuilder();
+            accountsSummary.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, sa.status_enum as statusEnum, ");
+            accountsSummary.append("sa.account_type_enum as accountType, ");
+            accountsSummary.append("sa.account_balance_derived as accountBalance, ");
+
+            accountsSummary.append("sa.submittedon_date as submittedOnDate,");
+            accountsSummary.append("sbu.username as submittedByUsername,");
+            accountsSummary.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,");
+
+            accountsSummary.append("sa.rejectedon_date as rejectedOnDate,");
+            accountsSummary.append("rbu.username as rejectedByUsername,");
+            accountsSummary.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,");
+
+            accountsSummary.append("sa.withdrawnon_date as withdrawnOnDate,");
+            accountsSummary.append("wbu.username as withdrawnByUsername,");
+            accountsSummary.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,");
+
+            accountsSummary.append("sa.approvedon_date as approvedOnDate,");
+            accountsSummary.append("abu.username as approvedByUsername,");
+            accountsSummary.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,");
+
+            accountsSummary.append("sa.activatedon_date as activatedOnDate,");
+            accountsSummary.append("avbu.username as activatedByUsername,");
+            accountsSummary.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,");
+
+            accountsSummary.append("sa.closedon_date as closedOnDate,");
+            accountsSummary.append("cbu.username as closedByUsername,");
+            accountsSummary.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,");
+
+            accountsSummary
+                    .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+            accountsSummary.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            accountsSummary.append("curr.display_symbol as currencyDisplaySymbol, ");
+            accountsSummary.append("sa.product_id as productId, p.name as productName, p.short_name as shortProductName, ");
+            accountsSummary.append("sa.deposit_type_enum as depositType ");
+            accountsSummary.append("from m_savings_account sa ");
+            accountsSummary.append("join m_savings_product as p on p.id = sa.product_id ");
+            accountsSummary.append("join m_currency curr on curr.code = sa.currency_code ");
+            accountsSummary.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid ");
+            accountsSummary.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid ");
+            accountsSummary.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid ");
+            accountsSummary.append("left join m_appuser abu on abu.id = sa.approvedon_userid ");
+            accountsSummary.append("left join m_appuser avbu on rbu.id = sa.activatedon_userid ");
+            accountsSummary.append("left join m_appuser cbu on cbu.id = sa.closedon_userid ");
+
+            this.schemaSql = accountsSummary.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String accountNo = rs.getString("accountNo");
+            final String externalId = rs.getString("externalId");
+            final Long productId = JdbcSupport.getLong(rs, "productId");
+            final String productName = rs.getString("productName");
+            final String shortProductName = rs.getString("shortProductName");
+            final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum");
+            final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance");
+            final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId);
+            final Integer accountType = JdbcSupport.getInteger(rs, "accountType");
+            final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType);
+            final Integer depositTypeId = JdbcSupport.getInteger(rs, "depositType");
+            final EnumOptionData depositTypeData = SavingsEnumerations.depositType(depositTypeId);
+            
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate");
+            final String rejectedByUsername = rs.getString("rejectedByUsername");
+            final String rejectedByFirstname = rs.getString("rejectedByFirstname");
+            final String rejectedByLastname = rs.getString("rejectedByLastname");
+
+            final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate");
+            final String withdrawnByUsername = rs.getString("withdrawnByUsername");
+            final String withdrawnByFirstname = rs.getString("withdrawnByFirstname");
+            final String withdrawnByLastname = rs.getString("withdrawnByLastname");
+
+            final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
+            final String approvedByUsername = rs.getString("approvedByUsername");
+            final String approvedByFirstname = rs.getString("approvedByFirstname");
+            final String approvedByLastname = rs.getString("approvedByLastname");
+
+            final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate");
+            final String activatedByUsername = rs.getString("activatedByUsername");
+            final String activatedByFirstname = rs.getString("activatedByFirstname");
+            final String activatedByLastname = rs.getString("activatedByLastname");
+
+            final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+            final String closedByUsername = rs.getString("closedByUsername");
+            final String closedByFirstname = rs.getString("closedByFirstname");
+            final String closedByLastname = rs.getString("closedByLastname");
+
+            final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate,
+                    submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername,
+                    rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname,
+                    withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate,
+                    activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname,
+                    closedByLastname);
+
+            return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency, accountBalance,
+                    accountTypeData, timeline, depositTypeData);
+        }
+    }
+
+    private static final class LoanAccountSummaryDataMapper implements RowMapper<LoanAccountSummaryData> {
+
+        public String loanAccountSummarySchema() {
+
+            final StringBuilder accountsSummary = new StringBuilder("l.id as id, l.account_no as accountNo, l.external_id as externalId,");
+            accountsSummary
+                    .append(" l.product_id as productId, lp.name as productName, lp.short_name as shortProductName,")
+                    .append(" l.loan_status_id as statusId, l.loan_type_enum as loanType,")
+                    
+                    .append("l.principal_disbursed_derived as originalLoan,")
+                    .append("l.total_outstanding_derived as loanBalance,")
+                    .append("l.total_repayment_derived as amountPaid,")
+                    
+                    .append(" l.loan_product_counter as loanCycle,")
+
+                    .append(" l.submittedon_date as submittedOnDate,")
+                    .append(" sbu.username as submittedByUsername, sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,")
+
+                    .append(" l.rejectedon_date as rejectedOnDate,")
+                    .append(" rbu.username as rejectedByUsername, rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,")
+
+                    .append(" l.withdrawnon_date as withdrawnOnDate,")
+                    .append(" wbu.username as withdrawnByUsername, wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,")
+
+                    .append(" l.approvedon_date as approvedOnDate,")
+                    .append(" abu.username as approvedByUsername, abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,")
+
+                    .append(" l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate,")
+                    .append(" dbu.username as disbursedByUsername, dbu.firstname as disbursedByFirstname, dbu.lastname as disbursedByLastname,")
+
+                    .append(" l.closedon_date as closedOnDate,")
+                    .append(" cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,")
+                    .append(" la.overdue_since_date_derived as overdueSinceDate,")
+                    .append(" l.writtenoffon_date as writtenOffOnDate, l.expected_maturedon_date as expectedMaturityDate")
+
+                    .append(" from m_loan l ").append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id")
+                    .append(" left join m_appuser sbu on sbu.id = l.submittedon_userid")
+                    .append(" left join m_appuser rbu on rbu.id = l.rejectedon_userid")
+                    .append(" left join m_appuser wbu on wbu.id = l.withdrawnon_userid")
+                    .append(" left join m_appuser abu on abu.id = l.approvedon_userid")
+                    .append(" left join m_appuser dbu on dbu.id = l.disbursedon_userid")
+                    .append(" left join m_appuser cbu on cbu.id = l.closedon_userid")
+                    .append(" left join m_loan_arrears_aging la on la.loan_id = l.id");
+
+            return accountsSummary.toString();
+        }
+
+        @Override
+        public LoanAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String accountNo = rs.getString("accountNo");
+            final String externalId = rs.getString("externalId");
+            final Long productId = JdbcSupport.getLong(rs, "productId");
+            final String loanProductName = rs.getString("productName");
+            final String shortLoanProductName = rs.getString("shortProductName");
+            final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId");
+            final LoanStatusEnumData loanStatus = LoanEnumerations.status(loanStatusId);
+            final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType");
+            final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeId);
+            final Integer loanCycle = JdbcSupport.getInteger(rs, "loanCycle");
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate");
+            final String rejectedByUsername = rs.getString("rejectedByUsername");
+            final String rejectedByFirstname = rs.getString("rejectedByFirstname");
+            final String rejectedByLastname = rs.getString("rejectedByLastname");
+
+            final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate");
+            final String withdrawnByUsername = rs.getString("withdrawnByUsername");
+            final String withdrawnByFirstname = rs.getString("withdrawnByFirstname");
+            final String withdrawnByLastname = rs.getString("withdrawnByLastname");
+
+            final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
+            final String approvedByUsername = rs.getString("approvedByUsername");
+            final String approvedByFirstname = rs.getString("approvedByFirstname");
+            final String approvedByLastname = rs.getString("approvedByLastname");
+
+            final LocalDate expectedDisbursementDate = JdbcSupport.getLocalDate(rs, "expectedDisbursementDate");
+            final LocalDate actualDisbursementDate = JdbcSupport.getLocalDate(rs, "actualDisbursementDate");
+            final String disbursedByUsername = rs.getString("disbursedByUsername");
+            final String disbursedByFirstname = rs.getString("disbursedByFirstname");
+            final String disbursedByLastname = rs.getString("disbursedByLastname");
+
+            final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+            final String closedByUsername = rs.getString("closedByUsername");
+            final String closedByFirstname = rs.getString("closedByFirstname");
+            final String closedByLastname = rs.getString("closedByLastname");
+            
+            final BigDecimal originalLoan = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,"originalLoan");
+            final BigDecimal loanBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,"loanBalance");
+            final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,"amountPaid");
+
+            final LocalDate writtenOffOnDate = JdbcSupport.getLocalDate(rs, "writtenOffOnDate");
+
+            final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate");
+
+            final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate");
+            Boolean inArrears = true;
+            if (overdueSinceDate == null) {
+                inArrears = false;
+            }
+
+            final LoanApplicationTimelineData timeline = new LoanApplicationTimelineData(submittedOnDate, submittedByUsername,
+                    submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname,
+                    withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername,
+                    approvedByFirstname, approvedByLastname, expectedDisbursementDate, actualDisbursementDate, disbursedByUsername,
+                    disbursedByFirstname, disbursedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname,
+                    expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname);
+
+            return new LoanAccountSummaryData(id, accountNo, externalId, productId, loanProductName, shortLoanProductName, loanStatus, loanType, loanCycle,
+                    timeline, inArrears,originalLoan,loanBalance,amountPaid);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountEnumerations.java
new file mode 100755
index 0000000..0f3c1e1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountEnumerations.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.portfolio.accountdetails.service;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+
+public class AccountEnumerations {
+
+    public static EnumOptionData loanType(final Integer loanTypeId) {
+        return loanType(AccountType.fromInt(loanTypeId));
+    }
+
+    public static EnumOptionData loanType(final String name) {
+        return loanType(AccountType.fromName(name));
+    }
+
+    public static EnumOptionData loanType(final AccountType type) {
+        EnumOptionData optionData = new EnumOptionData(AccountType.INVALID.getValue().longValue(), AccountType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case INVALID:
+                optionData = new EnumOptionData(AccountType.INVALID.getValue().longValue(), AccountType.INVALID.getCode(), "Invalid");
+            break;
+            case INDIVIDUAL:
+                optionData = new EnumOptionData(AccountType.INDIVIDUAL.getValue().longValue(), AccountType.INDIVIDUAL.getCode(),
+                        "Individual");
+            break;
+            case GROUP:
+                optionData = new EnumOptionData(AccountType.GROUP.getValue().longValue(), AccountType.GROUP.getCode(), "Group");
+            break;
+            case JLG:
+                optionData = new EnumOptionData(AccountType.JLG.getValue().longValue(), AccountType.JLG.getCode(), "JLG");
+            break;
+        }
+
+        return optionData;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/api/AccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/api/AccountsApiResource.java
new file mode 100644
index 0000000..b20a19b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/api/AccountsApiResource.java
@@ -0,0 +1,140 @@
+/**
+ * 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.accounts.api;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import javax.ws.rs.Consumes;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.accounts.constants.AccountsApiConstants;
+import org.apache.fineract.portfolio.accounts.data.AccountData;
+import org.apache.fineract.portfolio.accounts.service.AccountReadPlatformService;
+import org.apache.fineract.portfolio.accounts.service.AccountsCommandsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+
+@Path("/accounts/{type}")
+@Component
+@Scope("singleton")
+public class AccountsApiResource {
+
+    private final ApplicationContext applicationContext ;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final DefaultToApiJsonSerializer<AccountData> toApiJsonSerializer;
+    private final PlatformSecurityContext platformSecurityContext;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final DefaultToApiJsonSerializer<Object> toApiObjectJsonSerializer ;
+    
+    @Autowired
+    public AccountsApiResource(final ApplicationContext applicationContext,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final DefaultToApiJsonSerializer<AccountData> toApiJsonSerializer,
+            final PlatformSecurityContext platformSecurityContext,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final DefaultToApiJsonSerializer<Object> toApiObjectJsonSerializer) {
+        this.applicationContext = applicationContext ;
+        this.apiRequestParameterHelper = apiRequestParameterHelper ;
+        this.toApiJsonSerializer = toApiJsonSerializer ;
+        this.platformSecurityContext = platformSecurityContext ; 
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService ;
+        this.toApiObjectJsonSerializer = toApiObjectJsonSerializer ;
+    }
+    
+    @GET
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAccount(@PathParam("accountId") final Long accountId, @PathParam("type") final String accountType,
+            @Context final UriInfo uriInfo) {
+        String serviceName = accountType+AccountsApiConstants.READPLATFORM_NAME ;
+        AccountReadPlatformService service = (AccountReadPlatformService) this.applicationContext.getBean(serviceName) ;
+        AccountData data = service.retrieveOne(accountId) ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, data, service.getResponseDataParams());
+    }
+    
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllAccounts(@PathParam("type") final String accountType, @Context final UriInfo uriInfo) {
+        String serviceName = accountType+AccountsApiConstants.READPLATFORM_NAME ;
+        AccountReadPlatformService service = (AccountReadPlatformService) this.applicationContext.getBean(serviceName) ;
+        Collection<AccountData> data = service.retrieveAll() ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, data, service.getResponseDataParams()); 
+    }
+    
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createAccount(@PathParam("type") final String accountType, final String apiRequestBodyAsJson) {
+        CommandWrapper commandWrapper = null;
+        this.platformSecurityContext.authenticatedUser();
+        commandWrapper = new CommandWrapperBuilder().createAccount(accountType).withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+        return this.toApiJsonSerializer.serialize(commandProcessingResult);
+    }
+    
+    @POST
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String handleCommands(@PathParam("type") final String accountType, @PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
+        String serviceName = accountType.toUpperCase()+AccountsApiConstants.ACCOUNT_COMMANDSERVICE ;
+        AccountsCommandsService service = (AccountsCommandsService) this.applicationContext.getBean(serviceName) ;
+        final Object obj = service.handleCommand(accountId, commandParam, apiRequestBodyAsJson) ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiObjectJsonSerializer.serialize(settings, obj, new HashSet<String>());
+    }
+    
+    @PUT
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateAccount(@PathParam("type") final String accountType, @PathParam("accountId") final Long accountId, final String apiRequestBodyAsJson) {
+        this.platformSecurityContext.authenticatedUser();
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateAccount(accountType, accountId)
+                .withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/AccountsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/AccountsApiConstants.java
new file mode 100644
index 0000000..a52a87e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/AccountsApiConstants.java
@@ -0,0 +1,27 @@
+/**
+ * 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.accounts.constants;
+
+
+public interface AccountsApiConstants {
+
+    public final String READPLATFORM_NAME = "AccountReadPlatformService" ;
+    
+    public final String ACCOUNT_COMMANDSERVICE = "ACCOUNT_COMMANDSERVICE" ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/ShareAccountApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/ShareAccountApiConstants.java
new file mode 100644
index 0000000..3de0f73
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/ShareAccountApiConstants.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.portfolio.accounts.constants;
+
+
+
+public interface ShareAccountApiConstants {
+
+    //Command Strings
+    String APPROVE_COMMAND = "approve" ;
+    String REJECT_COMMAND = "reject" ;
+    String APPLY_ADDITIONALSHARES_COMMAND = "applyadditionalshares" ;
+    String APPROVE_ADDITIONSHARES_COMMAND = "approveadditionalshares" ;
+    String REJECT_ADDITIONSHARES_COMMAND = "rejectadditionalshares" ;
+    
+    //
+    String id_paramname = "id" ;
+    
+    String clientid_paramname = "clientId" ;
+    
+    String productid_paramname = "productId" ;
+    
+    String submitteddate_paramname = "submittedDate" ;
+    
+    String approveddate_paramname = "approvedDate" ;
+    
+    String fieldofferid_paramname = "fieldOfficerId" ;
+    
+    String externalid_paramname = "externalId" ;
+    
+    String currency_paramname = "currencyCode" ;
+    
+    String digitsafterdecimal_paramname = "digitsAfterDecimal" ;
+    
+    String inmultiplesof_paramname = "inMultiplesOf" ;
+    
+    String purchasedshares_paramname = "purchasedShares" ;
+    
+    String additionalshares_paramname = "additionalShares" ;
+    
+    String suspenseaccount_paramname = "suspenseAccount" ;
+    
+    String equityaccount_paramname = "equityAccount" ;
+    
+    String savingsaccountid_paramname = "savingsAccountId" ;
+    
+    String lockperiod_paramname = "lockPeriod" ;
+    
+    String minimumactiveperiodfordividends_paramname = "minimumActivePeriodForDividends" ;
+    
+    String allowdividendcalculationforinactiveclients_paramname = "allowDividendCalculationForInactiveClients" ;
+    
+    String charges_paramname = "charges" ; 
+    
+    String purchaseddate_paramname = "purchasedDate" ;
+    
+    String numberofshares_paramname = "numberOfShares" ;
+    
+    String purchasedprice_paramname = "purchasedPrice" ;
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/AccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/AccountData.java
new file mode 100644
index 0000000..1513d73
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/AccountData.java
@@ -0,0 +1,24 @@
+/**
+ * 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.accounts.data;
+
+
+public interface AccountData {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/PurchasedSharesData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/PurchasedSharesData.java
new file mode 100644
index 0000000..28414aa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/PurchasedSharesData.java
@@ -0,0 +1,60 @@
+/**
+ * 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.accounts.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+
+public class PurchasedSharesData {
+
+    private final Date purchasedDate ;
+    
+    private final Long numberOfShares ;
+    
+    private final BigDecimal purchasedPrice ;
+    
+    private final String status ;
+    
+    public PurchasedSharesData(final Date purchasedDate, final Long numberOfShares, final BigDecimal purchasedPrice, final String status) {
+        this.purchasedDate = purchasedDate ;
+        this.numberOfShares = numberOfShares ;
+        this.purchasedPrice = purchasedPrice ;
+        this.status = status ;
+    }
+
+    
+    public Date getPurchasedDate() {
+        return this.purchasedDate;
+    }
+
+    
+    public Long getNumberOfShares() {
+        return this.numberOfShares;
+    }
+
+    
+    public BigDecimal getPurchasedPrice() {
+        return this.purchasedPrice;
+    }
+    
+    public String getStatus() {
+        return this.status ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/ShareAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/ShareAccountData.java
new file mode 100644
index 0000000..a7cc0e0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/ShareAccountData.java
@@ -0,0 +1,99 @@
+/**
+ * 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.accounts.data;
+
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+public class ShareAccountData implements AccountData{
+
+    private Long id;
+    
+    private String accountNo;
+    
+    private String externalId;
+
+    private Long productId;
+    
+    private String productName;
+    
+    private String loanProductDescription;
+    
+    private Long fieldOfficerId;
+    
+    private String fieldOfficerName;
+
+    private Long clientId;
+    
+    private String clientName;
+
+    private Long clientOfficeId;
+
+    private CurrencyData currency;
+
+    private Date submittedDate;
+
+    private Date approvedDate;
+
+    private Collection<PurchasedSharesData> purchasedShares;
+
+    private GLAccountData suspenseAccount;
+
+    private GLAccountData equityAccount;
+
+    private Long savingsAccountId;
+
+    private EnumOptionData lockPeriod;
+
+    private EnumOptionData minimumActivePeriodForDividends;
+    
+    private Boolean allowDividendCalculationForInactiveClients;
+    
+    private Collection<ShareChargeData> charges ;
+    
+    private String status ;
+    
+    public ShareAccountData(final Long id, final String accountNo, final Long clientId, final String clientName, final Long productId, final String productName,
+            final Long fieldOfficerId, final String externalId, final Date submittedDate, final Collection<PurchasedSharesData> purchasedShares, final GLAccountData suspenseAccount,
+            final GLAccountData equityAccount, final EnumOptionData lockPeriod, final EnumOptionData minimumActivePeriodForDividends,
+            final Boolean allowDividendCalculationForInactiveClients, final Collection<ShareChargeData> charges, final String status) {
+        this.id = id ;
+        this.accountNo = accountNo ;
+        this.clientId = clientId ;
+        this.clientName = clientName ;
+        this.productId = productId ;
+        this.productName = productName ;
+        this.fieldOfficerId = fieldOfficerId ;
+        this.externalId = externalId ;
+        this.submittedDate = submittedDate ;
+        this.purchasedShares = purchasedShares ;
+        this.suspenseAccount = suspenseAccount ;
+        this.equityAccount = equityAccount ;
+        this.lockPeriod = lockPeriod ;
+        this.minimumActivePeriodForDividends = minimumActivePeriodForDividends ;
+        this.allowDividendCalculationForInactiveClients = allowDividendCalculationForInactiveClients ;
+        this.charges = charges ;
+        this.status = status ;
+        
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/ShareChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/ShareChargeData.java
new file mode 100644
index 0000000..ef302bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/data/ShareChargeData.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.accounts.data;
+
+
+public class ShareChargeData {
+
+    private Long accountId ;
+    
+    private Long chargeId ;
+    
+    public ShareChargeData(final Long accountId, final Long chargeId) {
+        this.accountId = accountId ;
+        this.chargeId = chargeId ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/PurchasedShares.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/PurchasedShares.java
new file mode 100644
index 0000000..afa4f4f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/PurchasedShares.java
@@ -0,0 +1,93 @@
+/**
+ * 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.accounts.domain;
+
+import java.math.BigDecimal;
+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.portfolio.accounts.data.PurchasedSharesData;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_purchasedshares")
+public class PurchasedShares extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "account_id", referencedColumnName = "id", nullable = false)
+    private ShareAccount shareAccount ;
+    
+    @Column(name = "purchased_date")
+    @Temporal(TemporalType.DATE)
+    private Date purchasedDate ;
+    
+    @Column(name = "share_count")
+    private Long totalShares ;
+    
+    @Column(name = "share_price")
+    private BigDecimal shareValue ; 
+    
+    @Column(name ="status")
+    private String status = "Submitted";
+    
+    protected PurchasedShares() {
+        
+    }
+    
+    public void setShareAccount(final ShareAccount shareAccount) {
+        this.shareAccount = shareAccount ;
+    }
+    
+    public PurchasedShares(final Date purchasedDate, final Long totalShares, final BigDecimal shareValue) {
+        this.purchasedDate = purchasedDate ;
+        this.totalShares = totalShares ;
+        this.shareValue = shareValue ;
+    }
+    
+    public PurchasedSharesData toData() {
+        return new PurchasedSharesData(this.purchasedDate, this.totalShares, this.shareValue, this.status) ;
+    }
+    
+    public Date getPurchasedDate() {
+        return this.purchasedDate ;
+    }
+    
+    public Long getTotalShares() {
+        return this.totalShares ;
+    }
+    
+    public BigDecimal getPurchasePrice() {
+        return this.shareValue ;
+    }
+    
+    public String getStatus() {
+        return this.status ;
+    }
+    
+    public void setStatus(String status) {
+        this.status = status ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccount.java
new file mode 100644
index 0000000..55d4435
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccount.java
@@ -0,0 +1,333 @@
+/**
+ * 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.accounts.domain;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.accounts.data.PurchasedSharesData;
+import org.apache.fineract.portfolio.accounts.data.ShareAccountData;
+import org.apache.fineract.portfolio.accounts.data.ShareChargeData;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.shares.domain.ShareProduct;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.DateTime;
+
+@Entity
+@Table(name = "m_shareaccounts")
+public class ShareAccount extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "client_id", nullable = true)
+    private Client client ;
+    
+    @ManyToOne
+    @JoinColumn(name = "product_id", nullable = true)
+    private ShareProduct shareProduct ;
+    
+    @Column(name = "submitted_date")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date submittedDate ;
+    
+    @Column(name = "approved_date")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date approvedDate ;
+    
+    @Column(name = "field_officer")
+    private Long fieldOfficerId ;
+    
+    @Column(name = "external_id")
+    private String externalId ;
+    
+    @Embedded
+    private MonetaryCurrency currency;
+    
+    @ManyToOne
+    @JoinColumn(name = "suspense_account", nullable = true)
+    private GLAccount suspenseAccount ;
+    
+    @ManyToOne
+    @JoinColumn(name = "equity_account", nullable = true)
+    private GLAccount equityAccount ;
+    
+    @ManyToOne
+    @JoinColumn(name = "savings_id", nullable = true)
+    private SavingsAccount savingsAccount ;
+    
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "shareAccount", orphanRemoval = true)
+    private Set<PurchasedShares> purchasedShares ;
+    
+    @Column(name = "allow_dividends_inactive_clients")
+    private Boolean allowDividendCalculationForInactiveClients;
+
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "lock_period", nullable = true)
+    private PeriodFrequencyType lockPeriod;
+
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "dividend_active_period", nullable = true)
+    private PeriodFrequencyType minimumActivePeriodForDividends;
+    
+    @Column(name = "status")
+    private String status = "Submitted"; //change it to enum
+    
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "shareAccount", orphanRemoval = true)
+    private Set<ShareAccountCharge> charges ;
+    
+    protected ShareAccount() {
+        
+    }
+    
+    public ShareAccount(final Client client, final ShareProduct shareProduct, final Date submittedDate, final Date approvedDate,
+            final Long fieldOfficerId , final String externalId, final MonetaryCurrency currency, GLAccount suspenseAccount, 
+            final GLAccount equityAccount, final SavingsAccount savingsAccount, final Set<PurchasedShares> purchasedShares,
+            final Boolean allowDividendCalculationForInactiveClients, final PeriodFrequencyType lockPeriod, 
+            final PeriodFrequencyType minimumActivePeriodForDividends, Set<ShareAccountCharge> charges,
+            AppUser createdBy, DateTime createdDate, AppUser lastModifiedBy, DateTime lastModifiedDate) {
+        this.client = client ;
+        this.shareProduct = shareProduct ;
+        this.submittedDate = submittedDate ;
+        this.approvedDate =  approvedDate ;
+        this.fieldOfficerId = fieldOfficerId ;
+        this.externalId = externalId ;
+        this.currency = currency ;
+        this.suspenseAccount = suspenseAccount ;
+        this.equityAccount = equityAccount ;
+        this.savingsAccount = savingsAccount ;
+        this.purchasedShares = purchasedShares ;
+        this.allowDividendCalculationForInactiveClients = allowDividendCalculationForInactiveClients ;
+        this.lockPeriod = lockPeriod ;
+        this.minimumActivePeriodForDividends = minimumActivePeriodForDividends ; 
+        this.charges = charges ;
+        setCreatedBy(createdBy) ;
+        setCreatedDate(createdDate) ;
+        setLastModifiedBy(lastModifiedBy) ;
+        setLastModifiedDate(lastModifiedDate) ;
+    }
+    
+    public boolean setShareProduct(final ShareProduct shareProduct) {
+        boolean toReturn = false ;
+        if(!this.shareProduct.getId().equals(shareProduct.getId())) {
+            this.shareProduct = shareProduct ;
+            toReturn = true ;
+        }
+        return toReturn ;
+    }
+    
+    public ShareProduct getShareProduct() {
+        return this.shareProduct ;
+    }
+    
+    public boolean setSubmittedDate(final Date submittedDate) {
+        boolean toReturn = false ;
+        if(!this.submittedDate.equals(submittedDate)) {
+            this.submittedDate = submittedDate ;
+            toReturn = true ;
+        }
+        return toReturn ;
+    }
+    
+    public boolean setApprovedDate(final Date approvedDate) {
+        boolean toReturn = false ;
+        if(!this.approvedDate.equals(approvedDate)) {
+            this.approvedDate = approvedDate ;
+            toReturn = true ;
+        }
+        return toReturn ;
+    }
+    
+    public boolean setFieldOfficer(final Long fieldOfficerId) {
+        boolean toReturn = false ;
+        if(!this.fieldOfficerId.equals(fieldOfficerId)) {
+            this.fieldOfficerId = fieldOfficerId ;
+            toReturn = true ;
+        }
+        return toReturn ;
+    }
+    
+    public boolean setExternalId(final String externalId) {
+        boolean toReturn = false ;
+        if(!this.externalId.equals(externalId)) {
+            this.externalId = externalId ;
+            toReturn = true ;
+        }
+        return toReturn ;
+    }
+    
+    public boolean setSuspenseAccount(GLAccount suspenseAccount) {
+        boolean returnValue = false;
+        if (!this.suspenseAccount.getId().equals(suspenseAccount.getId())) {
+            this.suspenseAccount = suspenseAccount;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setEquityAccount(GLAccount equityAccount) {
+        boolean returnValue = false;
+        if (!this.equityAccount.getId().equals(equityAccount.getId())) {
+            this.equityAccount = equityAccount;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setSavingsAccount(final SavingsAccount savingsAccount) {
+        boolean returnValue = false;
+        if (!this.savingsAccount.getId().equals(savingsAccount.getId())) {
+            this.savingsAccount = savingsAccount;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+    
+    public boolean setPurchasedShares(Set<PurchasedShares> purchasedShares) {
+        this.purchasedShares = purchasedShares ;
+        return true ;
+    }
+    
+    public boolean setAllowDividendCalculationForInactiveClients(Boolean allowDividendCalculationForInactiveClients) {
+        boolean returnValue = false;
+        if (!this.allowDividendCalculationForInactiveClients.equals(allowDividendCalculationForInactiveClients)) {
+            this.allowDividendCalculationForInactiveClients = allowDividendCalculationForInactiveClients;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setLockPeriod(final PeriodFrequencyType lockPeriod) {
+        boolean returnValue = false;
+        if (!this.lockPeriod.equals(lockPeriod)) {
+            this.lockPeriod = lockPeriod;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setminimumActivePeriodForDividends(final PeriodFrequencyType minimumActivePeriodForDividends) {
+        boolean returnValue = false;
+        if (!this.minimumActivePeriodForDividends.equals(minimumActivePeriodForDividends)) {
+            this.minimumActivePeriodForDividends = minimumActivePeriodForDividends;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+    
+    public boolean setCharges(final Set<ShareAccountCharge> charges) {
+        this.charges = charges ;
+        return true ;
+    }
+    //FIXME: Remove this
+    public void setTempId(Long id) {
+        setId(id) ;
+    }
+    
+    public ShareAccountData toData() {
+        String accountNo = "000025900"+getId() ;
+        Collection<PurchasedSharesData> purchasedSharesdata = new ArrayList<>();
+        for(PurchasedShares val: purchasedShares) {
+            purchasedSharesdata.add(new PurchasedSharesData(val.getPurchasedDate(), val.getTotalShares(), val.getPurchasePrice(), val.getStatus())) ;
+        }
+        Collection<ShareChargeData> chargesdata =  new ArrayList<>();
+        for(ShareAccountCharge charge: charges) {
+            chargesdata.add(new ShareChargeData(charge.getAccountId(), charge.getChargeId())) ;
+        }
+        
+        GLAccountData suspenseAccount1 = new GLAccountData(suspenseAccount.getId(), suspenseAccount.getName(), suspenseAccount.getGlCode());
+        GLAccountData equityAccount1 = new GLAccountData(equityAccount.getId(), equityAccount.getName(), equityAccount.getGlCode());
+        EnumOptionData lock = new EnumOptionData(this.lockPeriod.getValue().longValue(), this.lockPeriod.getCode(),
+                this.lockPeriod.toString());
+        EnumOptionData mini = new EnumOptionData(this.minimumActivePeriodForDividends.getValue().longValue(), this.minimumActivePeriodForDividends.getCode(),
+                this.minimumActivePeriodForDividends.toString());;
+                
+        ShareAccountData data = new ShareAccountData(getId(), accountNo, this.client.getId(), this.client.getDisplayName(), this.shareProduct.getId(), 
+                this.shareProduct.getProductName(), fieldOfficerId, externalId,
+                submittedDate, purchasedSharesdata, suspenseAccount1, equityAccount1, lock, mini, 
+                allowDividendCalculationForInactiveClients, chargesdata, status) ;
+        return data ;
+    }
+    
+    public Long getClientId() {
+        return this.client.getId() ;
+    }
+    
+    public String getClientName() {
+        return this.client.getDisplayName() ;
+    }
+    
+    public Long getTotalShares() {
+        long value = 0 ;
+        for(PurchasedShares val: purchasedShares) {
+            if(val.getStatus().equals("Approved")) {
+                value += val.getTotalShares().longValue() ;    
+            }
+        }
+        return new Long(value) ;
+    }
+    
+    public String getShareAccountNo() {
+        return "000025900"+getId() ; 
+    }
+    
+    public String getSavingsAccountNo() {
+        return this.savingsAccount.getAccountNumber() ;
+    }
+    
+    public void setStatus(String status) {
+        this.status = status ;
+    }
+    
+    public String getStatus() {
+        return this.status ;
+    }
+    
+    public void addAddtionalShares(Set<PurchasedShares> additionalShares) {
+        this.purchasedShares.addAll(additionalShares) ;
+    }
+    
+    public Set<PurchasedShares> getPurchasedShares() {
+        return this.purchasedShares ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccountCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccountCharge.java
new file mode 100644
index 0000000..73fbfa5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccountCharge.java
@@ -0,0 +1,65 @@
+/**
+ * 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.accounts.domain;
+
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_shareaccounts_charges")
+public class ShareAccountCharge extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "account_id", referencedColumnName = "id", nullable = false)
+    private ShareAccount shareAccount;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "charge_id", referencedColumnName = "id", nullable = false)
+    private Charge charge;
+
+    protected ShareAccountCharge() {
+
+    }
+
+    public ShareAccountCharge(final ShareAccount shareAccount, final Charge charge) {
+        this.shareAccount = shareAccount;
+        this.charge = charge;
+    }
+
+    public ShareAccountCharge(final Charge charge) {
+        this.charge = charge;
+    }
+
+    public void setShareAccount(ShareAccount shareAccount) {
+        this.shareAccount = shareAccount;
+    }
+    
+    public Long getAccountId() {
+        return this.shareAccount.getId() ;
+    }
+    
+    public Long getChargeId() {
+        return this.charge.getId() ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccountRepository.java
new file mode 100644
index 0000000..4867bf4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccountRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.accounts.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+
+public interface ShareAccountRepository extends JpaRepository<ShareAccount, Long>, JpaSpecificationExecutor<ShareAccount> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccountTempRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccountTempRepository.java
new file mode 100644
index 0000000..91602ff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/domain/ShareAccountTempRepository.java
@@ -0,0 +1,56 @@
+/**
+ * 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.accounts.domain;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.portfolio.shares.domain.ShareProductTempRepository;
+
+
+public class ShareAccountTempRepository {
+    
+    private final static ShareAccountTempRepository instance = new ShareAccountTempRepository() ;
+    
+    private Map<Long, ShareAccount> cache = new HashMap<>() ;
+    
+    private ShareAccountTempRepository() {
+        
+    }
+    
+    public static ShareAccountTempRepository getInstance() {
+        return instance ;
+    }
+    
+    public void save(ShareAccount account) {
+        Long id = new Long(cache.size()+1) ;
+        account.setTempId(id) ;
+        cache.put(id, account) ;
+        ShareProductTempRepository.getInstance().addAccount(account.getShareProduct().getId(), account) ;
+    }
+    
+    public ShareAccount findOne(Long id) {
+        return cache.get(id) ;
+    }
+    
+    public Collection<ShareAccount> findAll() {
+        return cache.values() ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/exceptions/ShareAccountNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/exceptions/ShareAccountNotFoundException.java
new file mode 100644
index 0000000..61c35e1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/exceptions/ShareAccountNotFoundException.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.accounts.exceptions;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+
+public class ShareAccountNotFoundException extends AbstractPlatformResourceNotFoundException{
+
+    public ShareAccountNotFoundException(final Long id) {
+        super("error.msg.shareaccount.id.invalid", " Account with identifier " + id + " does not exist" , id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/handler/CreateShareAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/handler/CreateShareAccountCommandHandler.java
new file mode 100644
index 0000000..ce4ca75
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/handler/CreateShareAccountCommandHandler.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.portfolio.accounts.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.accounts.service.ShareAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SHAREACCOUNT", action = "CREATE")
+public class CreateShareAccountCommandHandler implements NewCommandSourceHandler{
+
+
+    private final ShareAccountWritePlatformService shareAccountWritePlatformService ;
+    
+    @Autowired
+    public CreateShareAccountCommandHandler(final ShareAccountWritePlatformService shareAccountWritePlatformService) {
+        this.shareAccountWritePlatformService = shareAccountWritePlatformService ;
+    }
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.shareAccountWritePlatformService.createShareAccount(jsonCommand);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/handler/UpdateShareAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/handler/UpdateShareAccountCommandHandler.java
new file mode 100644
index 0000000..5945d0b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/handler/UpdateShareAccountCommandHandler.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.portfolio.accounts.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.accounts.service.ShareAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SHAREACCOUNT", action = "UPDATE")
+public class UpdateShareAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final ShareAccountWritePlatformService shareAccountWritePlatformService ;
+    
+    @Autowired
+    public UpdateShareAccountCommandHandler(final ShareAccountWritePlatformService shareAccountWritePlatformService) {
+        this.shareAccountWritePlatformService = shareAccountWritePlatformService ;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.shareAccountWritePlatformService.updateShareAccount(jsonCommand.entityId(), jsonCommand);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/serialization/ShareAccountDataSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/serialization/ShareAccountDataSerializer.java
new file mode 100644
index 0000000..815f369
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/serialization/ShareAccountDataSerializer.java
@@ -0,0 +1,333 @@
+/**
+ * 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.accounts.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants;
+import org.apache.fineract.portfolio.accounts.domain.PurchasedShares;
+import org.apache.fineract.portfolio.accounts.domain.ShareAccount;
+import org.apache.fineract.portfolio.accounts.domain.ShareAccountCharge;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
+import org.apache.fineract.portfolio.products.exception.ProductNotFoundException;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
+import org.apache.fineract.portfolio.shares.domain.ShareProduct;
+import org.apache.fineract.portfolio.shares.domain.ShareProductTempRepository;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Service
+public class ShareAccountDataSerializer {
+
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList(""));
+
+    private final PlatformSecurityContext platformSecurityContext;
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    private final ChargeRepositoryWrapper chargeRepository;
+
+    private final GLAccountRepositoryWrapper glAccountRepository;
+
+    private final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper;
+
+    private final ClientRepositoryWrapper clientRepositoryWrapper;
+
+    @Autowired
+    public ShareAccountDataSerializer(final PlatformSecurityContext platformSecurityContext, final FromJsonHelper fromApiJsonHelper,
+            final ChargeRepositoryWrapper chargeRepository, GLAccountRepositoryWrapper glAccountRepository,
+            final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, final ClientRepositoryWrapper clientRepositoryWrapper) {
+        this.platformSecurityContext = platformSecurityContext;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.chargeRepository = chargeRepository;
+        this.glAccountRepository = glAccountRepository;
+        this.savingsAccountRepositoryWrapper = savingsAccountRepositoryWrapper;
+        this.clientRepositoryWrapper = clientRepositoryWrapper;
+    }
+
+    public ShareAccount validateAndCreate(JsonCommand jsonCommand) {
+
+        if (StringUtils.isBlank(jsonCommand.json())) { throw new InvalidJsonException(); }
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        //this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, jsonCommand.json(), supportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("sharesaccount");
+        JsonElement element = jsonCommand.parsedJson();
+
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        final Long clientId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.clientid_paramname, element);
+        final Long productId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.productid_paramname, element);
+        final Date submittedDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareAccountApiConstants.submitteddate_paramname, element)
+                .toDate();
+        // On creation submitted date will not be there.
+        final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.fieldofferid_paramname, element);
+        final String externalId = this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.externalid_paramname, element);
+
+        Long suspenseAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.suspenseaccount_paramname, element);
+        Long equityAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.equityaccount_paramname, element);
+        Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.savingsaccountid_paramname, element);
+        Set<PurchasedShares> sharesPurchased = asemblePurchasedShares(element);
+        
+        Boolean allowdividendsForInactiveClients = this.fromApiJsonHelper.extractBooleanNamed(
+                ShareAccountApiConstants.allowdividendcalculationforinactiveclients_paramname, element);
+        PeriodFrequencyType lockPeriod = extractPeriodType(ShareAccountApiConstants.lockperiod_paramname, element);
+        PeriodFrequencyType minimumActivePeriod = extractPeriodType(ShareAccountApiConstants.minimumactiveperiodfordividends_paramname,
+                element);
+
+        Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
+        GLAccount suspenseAccount = glAccountRepository.findOneWithNotFoundDetection(suspenseAccountId);
+        GLAccount equityAccount = glAccountRepository.findOneWithNotFoundDetection(equityAccountId);
+        SavingsAccount savingsAccount = this.savingsAccountRepositoryWrapper.findOneWithNotFoundDetection(savingsAccountId);
+        ShareProduct shareProduct = ShareProductTempRepository.getInstance().fineOne(productId);
+        if(shareProduct == null) {
+            throw new ProductNotFoundException(productId, "Share") ;
+        }
+        final MonetaryCurrency currency = shareProduct.getCurrency() ;
+        Set<ShareAccountCharge> charges = assembleListOfAccountCharges(element, currency.getCode());
+        AppUser modifiedBy = null;
+        DateTime modifiedOn = null;
+        Date approvedDate = null;
+        AppUser createdBy = platformSecurityContext.authenticatedUser();
+        DateTime createdDate = DateUtils.getLocalDateTimeOfTenant().toDateTime();
+        ShareAccount account = new ShareAccount(client, shareProduct, submittedDate, approvedDate, fieldOfficerId, externalId, currency,
+                suspenseAccount, equityAccount, savingsAccount, sharesPurchased, allowdividendsForInactiveClients, lockPeriod,
+                minimumActivePeriod, charges, createdBy, createdDate, modifiedBy, modifiedOn);
+        
+        for(PurchasedShares pur: sharesPurchased) {
+            pur.setShareAccount(account) ;
+        }
+        
+        for(ShareAccountCharge charge: charges) {
+            charge.setShareAccount(account) ;
+        }
+        return account;
+    }
+
+    public Map<String, Object> validateAndUpdate(JsonCommand jsonCommand, ShareAccount account) {
+        Map<String, Object> actualChanges = new HashMap<>();
+        if (StringUtils.isBlank(jsonCommand.json())) { throw new InvalidJsonException(); }
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        //this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, jsonCommand.json(), supportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("sharesaccount");
+        JsonElement element = jsonCommand.parsedJson();
+
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        
+        if(this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.productid_paramname, element)) {
+            final Long productId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.productid_paramname, element);
+            ShareProduct shareProduct = ShareProductTempRepository.getInstance().fineOne(productId);
+            if(account.setShareProduct(shareProduct)) {
+                actualChanges.put(ShareAccountApiConstants.productid_paramname, shareProduct) ;    
+            }
+            
+        }
+        
+        if(this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.submitteddate_paramname, element)) {
+            final Date submittedDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareAccountApiConstants.submitteddate_paramname, element)
+                    .toDate();
+            if(account.setSubmittedDate(submittedDate)) {
+                actualChanges.put(ShareAccountApiConstants.submitteddate_paramname, submittedDate) ;    
+            }
+        }
+        
+        if(this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.fieldofferid_paramname, element)) {
+            final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.fieldofferid_paramname, element);
+            if(account.setFieldOfficer(fieldOfficerId)){
+                actualChanges.put(ShareAccountApiConstants.fieldofferid_paramname, fieldOfficerId) ;    
+            }
+        }
+        
+        if(this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.externalid_paramname, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.externalid_paramname, element);
+            if(account.setExternalId(externalId)) {
+                actualChanges.put(ShareAccountApiConstants.externalid_paramname, externalId) ;
+            }
+        }
+
+        if(this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.suspenseaccount_paramname, element)) {
+            Long suspenseAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.suspenseaccount_paramname, element);
+            GLAccount suspenseAccount = glAccountRepository.findOneWithNotFoundDetection(suspenseAccountId);
+            if(account.setSuspenseAccount(suspenseAccount)) {
+                actualChanges.put(ShareAccountApiConstants.suspenseaccount_paramname, suspenseAccount) ;
+            }
+        }
+        
+        if(this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.equityaccount_paramname, element)) {
+            Long equityAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.equityaccount_paramname, element);
+            GLAccount equityAccount = glAccountRepository.findOneWithNotFoundDetection(equityAccountId);
+            if(account.setEquityAccount(equityAccount)) {
+                actualChanges.put(ShareAccountApiConstants.equityaccount_paramname, equityAccount) ;
+            }
+        }
+        
+        if(this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.savingsaccountid_paramname, element)) {
+            Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.savingsaccountid_paramname, element);
+            SavingsAccount savingsAccount = this.savingsAccountRepositoryWrapper.findOneWithNotFoundDetection(savingsAccountId);
+            if(account.setSavingsAccount(savingsAccount)) {
+                actualChanges.put(ShareAccountApiConstants.savingsaccountid_paramname, savingsAccount) ;
+            }
+        }
+        
+        if(this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.purchasedshares_paramname, element)) {
+            Set<PurchasedShares> sharesPurchased = asemblePurchasedShares(element);
+            account.setPurchasedShares(sharesPurchased) ;
+            actualChanges.put(ShareAccountApiConstants.purchasedshares_paramname, sharesPurchased) ;
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.allowdividendcalculationforinactiveclients_paramname, element)) {
+            Boolean allowdividendsForInactiveClients = this.fromApiJsonHelper.extractBooleanNamed(
+                    ShareAccountApiConstants.allowdividendcalculationforinactiveclients_paramname, element);
+            if (account.setAllowDividendCalculationForInactiveClients(allowdividendsForInactiveClients)) {
+                actualChanges.put(ShareAccountApiConstants.allowdividendcalculationforinactiveclients_paramname,
+                        allowdividendsForInactiveClients);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.lockperiod_paramname, element)) {
+            PeriodFrequencyType lockPeriod = extractPeriodType(ShareAccountApiConstants.lockperiod_paramname, element);
+            if (account.setLockPeriod(lockPeriod)) {
+                actualChanges.put(ShareAccountApiConstants.lockperiod_paramname, lockPeriod);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.minimumactiveperiodfordividends_paramname, element)) {
+            PeriodFrequencyType minimumActivePeriod = extractPeriodType(ShareAccountApiConstants.minimumactiveperiodfordividends_paramname,
+                    element);
+            if (account.setminimumActivePeriodForDividends(minimumActivePeriod)) {
+                actualChanges.put(ShareAccountApiConstants.minimumactiveperiodfordividends_paramname, minimumActivePeriod);
+            }
+        }
+        if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.charges_paramname, element)) {
+            ShareProduct shareProduct = account.getShareProduct() ;
+            Set<ShareAccountCharge> charges = assembleListOfAccountCharges(element, shareProduct.getCurrency().getCode());
+            if(account.setCharges(charges)) {
+                actualChanges.put(ShareAccountApiConstants.charges_paramname, charges);
+            }
+        }
+        AppUser modifiedBy = platformSecurityContext.authenticatedUser();
+        DateTime modifiedOn = DateUtils.getLocalDateTimeOfTenant().toDateTime();
+       
+        return actualChanges;
+    }
+
+    private Set<ShareAccountCharge> assembleListOfAccountCharges(final JsonElement element, final String currencyCode) {
+        final Set<ShareAccountCharge> charges = new HashSet<>();
+        if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.charges_paramname, element)) {
+            JsonArray chargesArray = this.fromApiJsonHelper.extractJsonArrayNamed(ShareAccountApiConstants.charges_paramname, element);
+            if (chargesArray != null) {
+                for (int i = 0; i < chargesArray.size(); i++) {
+                    final JsonObject jsonObject = chargesArray.get(i).getAsJsonObject();
+                    if (jsonObject.has("id")) {
+                        final Long id = jsonObject.get("id").getAsLong();
+                        final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(id);
+                        if (!currencyCode.equals(charge.getCurrencyCode())) {
+                            final String errorMessage = "Charge and Share Account must have the same currency.";
+                            throw new InvalidCurrencyException("charge", "attach.to.share.account", errorMessage);
+                        }
+                        ShareAccountCharge accountCharge = new ShareAccountCharge(charge);
+                        charges.add(accountCharge);
+                    }
+                }
+            }
+        }
+        return charges;
+    }
+
+    private PeriodFrequencyType extractPeriodType(String paramName, final JsonElement element) {
+        PeriodFrequencyType frequencyType = PeriodFrequencyType.INVALID;
+        frequencyType = PeriodFrequencyType.fromInt(this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paramName, element));
+        return frequencyType;
+    }
+
+    private Set<PurchasedShares> asemblePurchasedShares(final JsonElement element) {
+        Set<PurchasedShares> set = null;
+        if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.purchasedshares_paramname, element)) {
+            set = new HashSet<>();
+            JsonArray array = this.fromApiJsonHelper.extractJsonArrayNamed(ShareAccountApiConstants.purchasedshares_paramname, element);
+            for (JsonElement arrayElement : array) {
+                LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareAccountApiConstants.purchaseddate_paramname,
+                        arrayElement);
+                final Long shares = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.numberofshares_paramname, arrayElement);
+                final BigDecimal shareValue = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        ShareAccountApiConstants.purchasedprice_paramname, arrayElement);
+
+                PurchasedShares obj = new PurchasedShares(localDate.toDate(), shares, shareValue);
+                set.add(obj);
+            }
+        }
+        return set;
+    }
+    
+    public Set<PurchasedShares> asembleAdditionalShares(final JsonElement element) {
+        Set<PurchasedShares> set = null;
+        if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.additionalshares_paramname, element)) {
+            set = new HashSet<>();
+            JsonArray array = this.fromApiJsonHelper.extractJsonArrayNamed(ShareAccountApiConstants.additionalshares_paramname, element);
+            for (JsonElement arrayElement : array) {
+                LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareAccountApiConstants.purchaseddate_paramname,
+                        arrayElement);
+                final Long shares = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.numberofshares_paramname, arrayElement);
+                final BigDecimal shareValue = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        ShareAccountApiConstants.purchasedprice_paramname, arrayElement);
+
+                PurchasedShares obj = new PurchasedShares(localDate.toDate(), shares, shareValue);
+                set.add(obj);
+            }
+        }
+        return set;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/AccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/AccountReadPlatformService.java
new file mode 100644
index 0000000..9274284
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/AccountReadPlatformService.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.portfolio.accounts.service;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.accounts.data.AccountData;
+
+
+public interface AccountReadPlatformService {
+
+    public AccountData retrieveOne(Long id) ;
+    
+    public Collection<AccountData> retrieveAll() ;
+    
+    public Set<String> getResponseDataParams();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/AccountsCommandsService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/AccountsCommandsService.java
new file mode 100644
index 0000000..a88c02d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/AccountsCommandsService.java
@@ -0,0 +1,25 @@
+/**
+ * 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.accounts.service;
+
+
+public interface AccountsCommandsService {
+
+    public Object handleCommand(final Long accountId, final String command, final String jsonBody) ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountCommandsServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountCommandsServiceImpl.java
new file mode 100644
index 0000000..14a1b37
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountCommandsServiceImpl.java
@@ -0,0 +1,136 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.accounts.service;
+
+import java.util.Set;
+
+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.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants;
+import org.apache.fineract.portfolio.accounts.domain.PurchasedShares;
+import org.apache.fineract.portfolio.accounts.domain.ShareAccount;
+import org.apache.fineract.portfolio.accounts.domain.ShareAccountTempRepository;
+import org.apache.fineract.portfolio.accounts.serialization.ShareAccountDataSerializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonElement;
+
+@Service(value = "SHAREACCOUNT_COMMANDSERVICE")
+public class ShareAccountCommandsServiceImpl implements AccountsCommandsService {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    
+    private final ShareAccountDataSerializer shareAccountDataSerializer ;
+    
+    @Autowired
+    public ShareAccountCommandsServiceImpl(final FromJsonHelper fromApiJsonHelper,
+            final ShareAccountDataSerializer shareAccountDataSerializer) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.shareAccountDataSerializer = shareAccountDataSerializer ;
+    }
+
+    @Override
+    public Object handleCommand(Long accountId, String command, String jsonBody) {
+        final JsonElement parsedCommand = this.fromApiJsonHelper.parse(jsonBody);
+        final JsonCommand jsonCommand = JsonCommand.from(jsonBody, parsedCommand, this.fromApiJsonHelper, null, null, null, null, null,
+                null, null, null, null, null);
+        if(ShareAccountApiConstants.APPROVE_COMMAND.equals(command)){
+            return approveShareAccount(accountId, jsonCommand) ;
+        }if(ShareAccountApiConstants.REJECT_COMMAND.equals(command)){
+            return rejectShareAccount(accountId, jsonCommand) ;
+        }else if(ShareAccountApiConstants.APPLY_ADDITIONALSHARES_COMMAND.equals(command)) {
+            return applyAdditionalShares(accountId, jsonCommand) ;
+        }else if(ShareAccountApiConstants.APPROVE_ADDITIONSHARES_COMMAND.equals(command)) {
+            return approveAdditionalShares(accountId, jsonCommand) ;
+        }else if(ShareAccountApiConstants.REJECT_ADDITIONSHARES_COMMAND.equals(command)) {
+            return rejectAdditionalShares(accountId, jsonCommand) ;
+        }
+        
+        return CommandProcessingResult.empty();
+    }
+
+    public Object approveShareAccount(Long accountId, JsonCommand jsonCommand) {
+        //We need to add approval date also
+        ShareAccount account = ShareAccountTempRepository.getInstance().findOne(accountId);
+        account.setStatus("Approved");
+        Set<PurchasedShares> purchasedShares = account.getPurchasedShares() ;
+        for(PurchasedShares pur: purchasedShares) {
+            pur.setStatus("Approved") ;    
+        }
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(jsonCommand.commandId()) //
+                .withEntityId(account.getId()) //
+                .build();
+    }
+
+    public Object rejectShareAccount(Long accountId, JsonCommand jsonCommand) {
+        ShareAccount account = ShareAccountTempRepository.getInstance().findOne(accountId);
+        account.setStatus("Rejected");
+        //rejection date we need to capture
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(jsonCommand.commandId()) //
+                .withEntityId(account.getId()) //
+                .build();
+    }
+
+    public Object applyAdditionalShares(Long accountId, JsonCommand jsonCommand) {
+        ShareAccount account = ShareAccountTempRepository.getInstance().findOne(accountId);
+        Set<PurchasedShares> additionalShares = this.shareAccountDataSerializer.asembleAdditionalShares(jsonCommand.parsedJson()) ;
+        account.addAddtionalShares(additionalShares) ;
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(jsonCommand.commandId()) //
+                .withEntityId(account.getId()) //
+                .build();
+    }
+
+    public Object approveAdditionalShares(Long accountId, JsonCommand jsonCommand) {
+        //user might have requested for different dates.
+        //we need to capture either purchase date or ids []
+        ShareAccount account = ShareAccountTempRepository.getInstance().findOne(accountId);
+        Set<PurchasedShares> purchasedShares = account.getPurchasedShares() ;
+        for(PurchasedShares pur: purchasedShares) {
+            if(pur.getStatus().equals("Submitted") && !pur.getStatus().equals("Rejected")) {
+                pur.setStatus("Approved") ;    
+            }
+        }
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(jsonCommand.commandId()) //
+                .withEntityId(account.getId()) //
+                .build();
+    }
+
+    public Object rejectAdditionalShares(Long accountId, JsonCommand jsonCommand) {
+        //user might have requested for different dates.
+        //we need to capture either purchase date or ids []
+        ShareAccount account = ShareAccountTempRepository.getInstance().findOne(accountId);
+        Set<PurchasedShares> purchasedShares = account.getPurchasedShares() ;
+        for(PurchasedShares pur: purchasedShares) {
+            if(pur.getStatus().equals("Submitted")) {
+                pur.setStatus("Rejected") ;    
+            }
+        }
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(jsonCommand.commandId()) //
+                .withEntityId(account.getId()) //
+                .build();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountReadPlatformServiceImpl.java
new file mode 100644
index 0000000..971d3fb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountReadPlatformServiceImpl.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.accounts.service;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.accounts.constants.AccountsApiConstants;
+import org.apache.fineract.portfolio.accounts.data.AccountData;
+import org.apache.fineract.portfolio.accounts.domain.ShareAccount;
+import org.apache.fineract.portfolio.accounts.domain.ShareAccountTempRepository;
+import org.springframework.stereotype.Service;
+
+@Service(value = "share"+AccountsApiConstants.READPLATFORM_NAME)
+public class ShareAccountReadPlatformServiceImpl implements AccountReadPlatformService{
+
+    @Override
+    public AccountData retrieveOne(Long id) {
+        return ShareAccountTempRepository.getInstance().findOne(id).toData();
+    }
+
+    @Override
+    public Collection<AccountData> retrieveAll() {
+        Collection<ShareAccount> collection = ShareAccountTempRepository.getInstance().findAll() ;
+        Set<AccountData> set = new HashSet<>() ;
+        for(ShareAccount data: collection) {
+            set.add(data.toData()) ;
+        }
+        return set;
+    }
+
+    @Override
+    public Set<String> getResponseDataParams() {
+        return null;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountWritePlatformService.java
new file mode 100644
index 0000000..feb754b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountWritePlatformService.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.accounts.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+
+public interface ShareAccountWritePlatformService {
+
+    public CommandProcessingResult createShareAccount(JsonCommand jsonCommand) ;
+    
+    public CommandProcessingResult updateShareAccount(Long accountId, JsonCommand jsonCommand) ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..d3afa5d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,85 @@
+/**
+ * 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.accounts.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.portfolio.accounts.domain.ShareAccount;
+import org.apache.fineract.portfolio.accounts.domain.ShareAccountTempRepository;
+import org.apache.fineract.portfolio.accounts.exceptions.ShareAccountNotFoundException;
+import org.apache.fineract.portfolio.accounts.serialization.ShareAccountDataSerializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareAccountWritePlatformService {
+
+    private final ShareAccountDataSerializer accountDataSerializer;
+
+    @Autowired
+    public ShareAccountWritePlatformServiceJpaRepositoryImpl(final ShareAccountDataSerializer accountDataSerializer) {
+        this.accountDataSerializer = accountDataSerializer;
+    }
+
+    @Override
+    public CommandProcessingResult createShareAccount(JsonCommand jsonCommand) {
+        try {
+            ShareAccount account = this.accountDataSerializer.validateAndCreate(jsonCommand);
+            ShareAccountTempRepository.getInstance().save(account);
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(jsonCommand.commandId()) //
+                    .withEntityId(account.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(jsonCommand, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    public CommandProcessingResult updateShareAccount(Long accountId, JsonCommand jsonCommand) {
+        try {
+            ShareAccount account = ShareAccountTempRepository.getInstance().findOne(accountId);
+            if (account == null) {
+                throw new ShareAccountNotFoundException(accountId) ;
+            }
+            Map<String, Object> changes = this.accountDataSerializer.validateAndUpdate(jsonCommand, account);
+            if (!changes.isEmpty()) {
+                // Save the data here
+            }
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(jsonCommand.commandId()) //
+                    .withEntityId(accountId) //
+                    .with(changes) //
+                    .build();
+        } catch (DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(jsonCommand, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/CalendarConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/CalendarConstants.java
new file mode 100644
index 0000000..02e9162
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/CalendarConstants.java
@@ -0,0 +1,63 @@
+/**
+ * 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.calendar;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class CalendarConstants {
+
+    public static final String CALENDAR_RESOURCE_NAME = "calendar";
+
+    public static enum CALENDAR_SUPPORTED_PARAMETERS {
+        CALENDAR_ID("id"), ENTITY_TYPE("entityType"), ENTITY_ID("entityId"), TITLE("title"), DESCRIPTION("description"), LOCATION(
+                "location"), START_DATE("startDate"), END_DATE("endDate"), CREATED_DATE("createdDate"), DURATION("duration"), TYPE_ID(
+                "typeId"), REPEATING("repeating"), REMIND_BY_ID("remindById"), FIRST_REMINDER("firstReminder"), SECOND_REMINDER(
+                "secondReminder"), LOCALE("locale"), DATE_FORMAT("dateFormat"), FREQUENCY("frequency"), INTERVAL("interval"), REPEATS_ON_DAY(
+                "repeatsOnDay"), RESCHEDULE_BASED_ON_MEETING_DATES("reschedulebasedOnMeetingDates"), PRESENT_MEETING_DATE(
+                "presentMeetingDate"), NEW_MEETING_DATE("newMeetingDate"), ;
+
+        private final String value;
+
+        private CALENDAR_SUPPORTED_PARAMETERS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+        static {
+            for (final CALENDAR_SUPPORTED_PARAMETERS param : CALENDAR_SUPPORTED_PARAMETERS.values()) {
+                values.add(param.value);
+            }
+        }
+
+        public static Set<String> getAllValues() {
+            return values;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/api/CalendarsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/api/CalendarsApiResource.java
new file mode 100644
index 0000000..f2ac608
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/api/CalendarsApiResource.java
@@ -0,0 +1,265 @@
+/**
+ * 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.calendar.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.exception.CalendarEntityTypeNotSupportedException;
+import org.apache.fineract.portfolio.calendar.service.CalendarDropdownReadPlatformService;
+import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/{entityType}/{entityId}/calendars")
+@Component
+@Scope("singleton")
+public class CalendarsApiResource {
+
+    /**
+     * The set of parameters that are supported in response for {@link Calendar}
+     */
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "entityId", "entityType", "title",
+            "description", "location", "startDate", "endDate", "duration", "type", "repeating", "recurrence", "frequency", "interval",
+            "repeatsOnDay", "remindBy", "firstReminder", "secondReminder", "humanReadable", "createdDate", "lastUpdatedDate",
+            "createdByUserId", "createdByUsername", "lastUpdatedByUserId", "lastUpdatedByUsername", "recurringDates",
+            "nextTenRecurringDates", "entityTypeOptions", "calendarTypeOptions", "remindByOptions", "frequencyOptions",
+            "repeatsOnDayOptions"));
+    private final String resourceNameForPermissions = "CALENDAR";
+
+    private final PlatformSecurityContext context;
+    private final CalendarReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<CalendarData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final CalendarDropdownReadPlatformService dropdownReadPlatformService;
+
+    @Autowired
+    public CalendarsApiResource(final PlatformSecurityContext context, final CalendarReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<CalendarData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final CalendarDropdownReadPlatformService dropdownReadPlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+    }
+
+    @GET
+    @Path("{calendarId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCalendar(@PathParam("calendarId") final Long calendarId, @PathParam("entityType") final String entityType,
+            @PathParam("entityId") final Long entityId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue();
+        CalendarData calendarData = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId);
+
+        // Include recurring date details
+        final boolean withHistory = true;
+        final LocalDate tillDate = null;
+        final Collection<LocalDate> recurringDates = this.readPlatformService.generateRecurringDates(calendarData, withHistory, tillDate);
+        final Collection<LocalDate> nextTenRecurringDates = this.readPlatformService.generateNextTenRecurringDates(calendarData);
+        final LocalDate recentEligibleMeetingDate = null;
+        calendarData = CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            calendarData = handleTemplate(calendarData);
+        }
+        return this.toApiJsonSerializer.serialize(settings, calendarData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    /**
+     * @param entityType
+     * @param entityId
+     * @param uriInfo
+     * @param calendarType
+     * @return
+     */
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCalendarsByEntity(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @Context final UriInfo uriInfo, @DefaultValue("all") @QueryParam("calendarType") final String calendarType) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+
+        Collection<CalendarData> calendarsData = new ArrayList<>();
+
+        final List<Integer> calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter(calendarType);
+
+        if (!associationParameters.isEmpty()) {
+            if (associationParameters.contains("parentCalendars")) {
+                calendarsData.addAll(this.readPlatformService.retrieveParentCalendarsByEntity(entityId,
+                        CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(), calendarTypeOptions));
+            }
+        }
+
+        calendarsData.addAll(this.readPlatformService.retrieveCalendarsByEntity(entityId,
+                CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(), calendarTypeOptions));
+
+        // Add recurring dates
+        calendarsData = this.readPlatformService.updateWithRecurringDates(calendarsData);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, calendarsData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveNewCalendarDetails(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        CalendarData calendarData = this.readPlatformService.retrieveNewCalendarDetails();
+        calendarData = handleTemplate(calendarData);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, calendarData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createCalendar(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            final String apiRequestBodyAsJson) {
+
+        final CalendarEntityType calendarEntityType = CalendarEntityType.getEntityType(entityType);
+        if (calendarEntityType == null) { throw new CalendarEntityTypeNotSupportedException(entityType); }
+
+        final CommandWrapper resourceDetails = getResourceDetails(calendarEntityType, entityId);
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createCalendar(resourceDetails, entityType, entityId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @PUT
+    @Path("{calendarId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateCalendar(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @PathParam("calendarId") final Long calendarId, final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCalendar(entityType, entityId, calendarId)
+                .withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{calendarId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteCalendar(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @PathParam("calendarId") final Long calendarId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCalendar(entityType, entityId, calendarId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    private CalendarData handleTemplate(final CalendarData calendarData) {
+        final List<EnumOptionData> entityTypeOptions = this.dropdownReadPlatformService.retrieveCalendarEntityTypeOptions();
+        final List<EnumOptionData> calendarTypeOptions = this.dropdownReadPlatformService.retrieveCalendarTypeOptions();
+        final List<EnumOptionData> remindByOptions = this.dropdownReadPlatformService.retrieveCalendarRemindByOptions();
+        final List<EnumOptionData> frequencyOptions = this.dropdownReadPlatformService.retrieveCalendarFrequencyTypeOptions();
+        final List<EnumOptionData> repeatsOnDayOptions = this.dropdownReadPlatformService.retrieveCalendarWeekDaysTypeOptions();
+        return CalendarData.withTemplateOptions(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions, frequencyOptions,
+                repeatsOnDayOptions);
+    }
+
+    private CommandWrapper getResourceDetails(final CalendarEntityType type, final Long entityId) {
+        CommandWrapperBuilder resourceDetails = new CommandWrapperBuilder();
+        switch (type) {
+            case CENTERS:
+                resourceDetails.withGroupId(entityId);
+            break;
+            case CLIENTS:
+                resourceDetails.withClientId(entityId);
+            break;
+            case GROUPS:
+                resourceDetails.withGroupId(entityId);
+            break;
+            case LOANS:
+                resourceDetails.withLoanId(entityId);
+            break;
+            case SAVINGS:
+                resourceDetails.withSavingsId(entityId);
+            break;
+            case INVALID:
+            break;
+            case LOAN_RECALCULATION_REST_DETAIL:
+            break;
+            default:
+            break;
+        }
+        return resourceDetails.build();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/command/CalendarCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/command/CalendarCommand.java
new file mode 100644
index 0000000..1d77b61
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/command/CalendarCommand.java
@@ -0,0 +1,67 @@
+/**
+ * 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.calendar.command;
+
+import org.joda.time.LocalDate;
+
+public class CalendarCommand {
+
+    @SuppressWarnings("unused")
+    private final String title;
+    @SuppressWarnings("unused")
+    private final String description;
+    @SuppressWarnings("unused")
+    private final String location;
+    @SuppressWarnings("unused")
+    private final LocalDate startDate;
+    @SuppressWarnings("unused")
+    private final LocalDate endDate;
+    @SuppressWarnings("unused")
+    private final LocalDate createdDate;
+    @SuppressWarnings("unused")
+    private final Integer duration;
+    @SuppressWarnings("unused")
+    private final Integer typeId;
+    @SuppressWarnings("unused")
+    private final boolean repeating;
+    @SuppressWarnings("unused")
+    private final Integer remindById;
+    @SuppressWarnings("unused")
+    private final Integer firstReminder;
+    @SuppressWarnings("unused")
+    private final Integer secondReminder;
+
+    public CalendarCommand(final String title, final String description, final String location, final LocalDate startDate,
+            final LocalDate endDate, final LocalDate createdDate, final Integer duration, final Integer typeId, final boolean repeating,
+            final Integer remindById, final Integer firstReminder, final Integer secondReminder) {
+        this.title = title;
+        this.description = description;
+        this.location = location;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.createdDate = createdDate;
+        this.duration = duration;
+        this.typeId = typeId;
+        this.repeating = repeating;
+        this.remindById = remindById;
+        this.firstReminder = firstReminder;
+        this.secondReminder = secondReminder;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java
new file mode 100644
index 0000000..9368462
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java
@@ -0,0 +1,350 @@
+/**
+ * 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.calendar.data;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRemindBy;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
+import org.apache.fineract.portfolio.calendar.service.CalendarEnumerations;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a Calendar.
+ */
+public class CalendarData {
+
+    private final Long id;
+    private final Long calendarInstanceId;
+    private final Long entityId;
+    private final EnumOptionData entityType;
+    private final String title;
+    private final String description;
+    private final String location;
+    private final LocalDate startDate;
+    private final LocalDate endDate;
+    private final Integer duration;
+    private final EnumOptionData type;
+    private final boolean repeating;
+    private final String recurrence;
+    private final EnumOptionData frequency;
+    private final Integer interval;
+    private final EnumOptionData repeatsOnDay;
+    private final EnumOptionData remindBy;
+    private final Integer firstReminder;
+    private final Integer secondReminder;
+    private final Collection<LocalDate> recurringDates;
+    private final Collection<LocalDate> nextTenRecurringDates;
+    private final String humanReadable;
+    private final LocalDate recentEligibleMeetingDate;
+
+    private final LocalDate createdDate;
+    private final LocalDate lastUpdatedDate;
+    private final Long createdByUserId;
+    private final String createdByUsername;
+    private final Long lastUpdatedByUserId;
+    private final String lastUpdatedByUsername;
+
+    // template related
+    final List<EnumOptionData> entityTypeOptions;
+    final List<EnumOptionData> calendarTypeOptions;
+    final List<EnumOptionData> remindByOptions;
+    final List<EnumOptionData> frequencyOptions;
+    final List<EnumOptionData> repeatsOnDayOptions;
+
+    public static CalendarData instance(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType,
+            final String title, final String description, final String location, final LocalDate startDate, final LocalDate endDate,
+            final Integer duration, final EnumOptionData type, final boolean repeating, final String recurrence,
+            final EnumOptionData frequency, final Integer interval, final EnumOptionData repeatsOnDay, final EnumOptionData remindBy,
+            final Integer firstReminder, final Integer secondReminder, final String humanReadable, final LocalDate createdDate,
+            final LocalDate lastUpdatedDate, final Long createdByUserId, final String createdByUsername, final Long lastUpdatedByUserId,
+            final String lastUpdatedByUsername) {
+
+        final Collection<LocalDate> recurringDates = null;
+        final Collection<LocalDate> nextTenRecurringDates = null;
+        final LocalDate recentEligibleMeetingDate = null;
+
+        final List<EnumOptionData> entityTypeOptions = null;
+        final List<EnumOptionData> calendarTypeOptions = null;
+        final List<EnumOptionData> remindByOptions = null;
+        final List<EnumOptionData> frequencyOptions = null;
+        final List<EnumOptionData> repeatsOnDayOptions = null;
+
+        return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration,
+                type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates,
+                nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId,
+                createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions,
+                frequencyOptions, repeatsOnDayOptions);
+    }
+
+    public static CalendarData withRecurringDates(final CalendarData calendarData, final Collection<LocalDate> recurringDates,
+            final Collection<LocalDate> nextTenRecurringDates, final LocalDate recentEligibleMeetingDate) {
+        return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType,
+                calendarData.title, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate,
+                calendarData.duration, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency,
+                calendarData.interval, calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder,
+                calendarData.secondReminder, recurringDates, nextTenRecurringDates, calendarData.humanReadable, recentEligibleMeetingDate,
+                calendarData.createdDate, calendarData.lastUpdatedDate, calendarData.createdByUserId, calendarData.createdByUsername,
+                calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername, calendarData.entityTypeOptions,
+                calendarData.calendarTypeOptions, calendarData.remindByOptions, calendarData.frequencyOptions,
+                calendarData.repeatsOnDayOptions);
+    }
+
+    public static CalendarData withRecentEligibleMeetingDate(final CalendarData calendarData, final LocalDate recentEligibleMeetingDate) {
+        return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType,
+                calendarData.title, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate,
+                calendarData.duration, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency,
+                calendarData.interval, calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder,
+                calendarData.secondReminder, calendarData.recurringDates, calendarData.nextTenRecurringDates, calendarData.humanReadable,
+                recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate, calendarData.createdByUserId,
+                calendarData.createdByUsername, calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername,
+                calendarData.entityTypeOptions, calendarData.calendarTypeOptions, calendarData.remindByOptions,
+                calendarData.frequencyOptions, calendarData.repeatsOnDayOptions);
+    }
+
+    public static CalendarData sensibleDefaultsForNewCalendarCreation() {
+        final Long id = null;
+        final Long calendarInstanceId = null;
+        final Long entityId = null;
+        final EnumOptionData entityType = null;
+        final String title = null;
+        final String description = null;
+        final String location = null;
+        final LocalDate startDate = null;
+        final LocalDate endDate = null;
+        final Integer duration = new Integer(0);
+        final EnumOptionData type = CalendarEnumerations.calendarType(CalendarType.COLLECTION);
+        final boolean repeating = false;
+        final String recurrence = null;
+        final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarFrequencyType.DAILY);
+        final Integer interval = new Integer(1);
+        final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.MO);
+        final EnumOptionData remindBy = CalendarEnumerations.calendarRemindBy(CalendarRemindBy.EMAIL);
+        final Integer firstReminder = new Integer(0);
+        final Integer secondReminder = new Integer(0);
+        final String humanReadable = null;
+        final Collection<LocalDate> recurringDates = null;
+        final Collection<LocalDate> nextTenRecurringDates = null;
+        final LocalDate recentEligibleMeetingDate = null;
+
+        final List<EnumOptionData> entityTypeOptions = null;
+        final List<EnumOptionData> calendarTypeOptions = null;
+        final List<EnumOptionData> remindByOptions = null;
+        final List<EnumOptionData> frequencyOptions = null;
+        final List<EnumOptionData> repeatsOnDayOptions = null;
+
+        final LocalDate createdDate = null;
+        final LocalDate lastUpdatedDate = null;
+        final Long createdByUserId = null;
+        final String createdByUsername = null;
+        final Long lastUpdatedByUserId = null;
+        final String lastUpdatedByUsername = null;
+
+        return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration,
+                type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates,
+                nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId,
+                createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions,
+                frequencyOptions, repeatsOnDayOptions);
+    }
+
+    public static CalendarData withTemplateOptions(final CalendarData calendarData, final List<EnumOptionData> entityTypeOptions,
+            final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions,
+            final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions) {
+
+        return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType,
+                calendarData.title, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate,
+                calendarData.duration, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency,
+                calendarData.interval, calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder,
+                calendarData.secondReminder, calendarData.recurringDates, calendarData.nextTenRecurringDates, calendarData.humanReadable,
+                calendarData.recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate,
+                calendarData.createdByUserId, calendarData.createdByUsername, calendarData.lastUpdatedByUserId,
+                calendarData.lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions, repeatsOptions,
+                repeatsOnDayOptions);
+    }
+
+    private CalendarData(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType,
+            final String title, final String description, final String location, final LocalDate startDate, final LocalDate endDate,
+            final Integer duration, final EnumOptionData type, final boolean repeating, final String recurrence,
+            final EnumOptionData frequency, final Integer interval, final EnumOptionData repeatsOnDay, final EnumOptionData remindBy,
+            final Integer firstReminder, final Integer secondReminder, final Collection<LocalDate> recurringDates,
+            final Collection<LocalDate> nextTenRecurringDates, final String humanReadable, final LocalDate recentEligibleMeetingDate,
+            final LocalDate createdDate, final LocalDate lastUpdatedDate, final Long createdByUserId, final String createdByUsername,
+            final Long lastUpdatedByUserId, final String lastUpdatedByUsername, final List<EnumOptionData> entityTypeOptions,
+            final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions,
+            final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions) {
+        this.id = id;
+        this.calendarInstanceId = calendarInstanceId;
+        this.entityId = entityId;
+        this.entityType = entityType;
+        this.title = title;
+        this.description = description;
+        this.location = location;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.duration = duration;
+        this.type = type;
+        this.repeating = repeating;
+        this.recurrence = recurrence;
+        this.frequency = frequency;
+        this.interval = interval;
+        this.repeatsOnDay = repeatsOnDay;
+        this.remindBy = remindBy;
+        this.firstReminder = firstReminder;
+        this.secondReminder = secondReminder;
+        this.recurringDates = recurringDates;
+        this.nextTenRecurringDates = nextTenRecurringDates;
+        this.humanReadable = humanReadable;
+        this.recentEligibleMeetingDate = recentEligibleMeetingDate;
+        this.createdDate = createdDate;
+        this.lastUpdatedDate = lastUpdatedDate;
+        this.createdByUserId = createdByUserId;
+        this.createdByUsername = createdByUsername;
+        this.lastUpdatedByUserId = lastUpdatedByUserId;
+        this.lastUpdatedByUsername = lastUpdatedByUsername;
+        this.entityTypeOptions = entityTypeOptions;
+        this.calendarTypeOptions = calendarTypeOptions;
+        this.remindByOptions = remindByOptions;
+        this.frequencyOptions = repeatsOptions;
+        this.repeatsOnDayOptions = repeatsOnDayOptions;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public Long getEntityId() {
+        return this.entityId;
+    }
+
+    public EnumOptionData getEntityType() {
+        return this.entityType;
+    }
+
+    public String getTitle() {
+        return this.title;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public String getLocation() {
+        return this.location;
+    }
+
+    public LocalDate getStartDate() {
+        return this.startDate;
+    }
+
+    public LocalDate getEndDate() {
+        return this.endDate;
+    }
+
+    public LocalDate getCreatedDate() {
+        return this.createdDate;
+    }
+
+    public Integer getDuration() {
+        return this.duration;
+    }
+
+    public EnumOptionData getType() {
+        return this.type;
+    }
+
+    public boolean isRepeating() {
+        return this.repeating;
+    }
+
+    public String getRecurrence() {
+        return this.recurrence;
+    }
+
+    public EnumOptionData getRemindBy() {
+        return this.remindBy;
+    }
+
+    public Integer getFirstReminder() {
+        return this.firstReminder;
+    }
+
+    public Integer getSecondReminder() {
+        return this.secondReminder;
+    }
+
+    public List<EnumOptionData> getEntityTypeOptions() {
+        return this.entityTypeOptions;
+    }
+
+    public List<EnumOptionData> getCalendarTypeOptions() {
+        return this.calendarTypeOptions;
+    }
+
+    public List<EnumOptionData> getRemindByOptions() {
+        return this.remindByOptions;
+    }
+
+    public String getHumanReadable() {
+        return this.humanReadable;
+    }
+
+    public Long getCalendarInstanceId() {
+        return this.calendarInstanceId;
+    }
+
+    public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
+        if (this.startDate != null && compareDate != null) {
+            if (this.startDate.isBefore(compareDate) || this.startDate.equals(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
+        if (this.endDate != null && compareDate != null) {
+            if (this.endDate.isAfter(compareDate) || this.endDate.isEqual(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    public boolean isBetweenStartAndEndDate(final LocalDate compareDate) {
+        if (isStartDateBeforeOrEqual(compareDate)) {
+            if (this.endDate == null || isEndDateAfterOrEqual(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    public boolean isValidRecurringDate(final LocalDate compareDate) {
+        if (isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(this.getRecurrence(), this.getStartDate(),
+                compareDate); }
+        return false;
+    }
+    
+    public Integer interval(){
+        return this.interval;
+    }
+    
+    public EnumOptionData frequencyType(){
+        return this.frequency;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java
new file mode 100644
index 0000000..103b96f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java
@@ -0,0 +1,584 @@
+/**
+ * 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.calendar.domain;
+
+import static org.apache.fineract.portfolio.calendar.CalendarConstants.CALENDAR_RESOURCE_NAME;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
+import org.apache.fineract.portfolio.calendar.exception.CalendarDateException;
+import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+
+@Entity
+@Table(name = "m_calendar")
+public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
+
+    @Column(name = "title", length = 50, nullable = false)
+    private String title;
+
+    @Column(name = "description", length = 100, nullable = true)
+    private String description;
+
+    @Column(name = "location", length = 100, nullable = true)
+    private String location;
+
+    @Column(name = "start_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date startDate;
+
+    @Column(name = "end_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date endDate;
+
+    @Column(name = "duration", nullable = true)
+    private Integer duration;
+
+    @Column(name = "calendar_type_enum", nullable = false)
+    private Integer typeId;
+
+    @Column(name = "repeating", nullable = false)
+    private boolean repeating = false;
+
+    @Column(name = "recurrence", length = 100, nullable = true)
+    private String recurrence;
+
+    @Column(name = "remind_by_enum", nullable = true)
+    private Integer remindById;
+
+    @Column(name = "first_reminder", nullable = true)
+    private Integer firstReminder;
+
+    @Column(name = "second_reminder", nullable = true)
+    private Integer secondReminder;
+
+    @OneToMany(fetch = FetchType.EAGER)
+    @JoinColumn(name = "calendar_id")
+    private final Set<CalendarHistory> calendarHistory = new HashSet<>();
+
+    protected Calendar() {
+
+    }
+
+    public Calendar(final String title, final String description, final String location, final LocalDate startDate,
+            final LocalDate endDate, final Integer duration, final Integer typeId, final boolean repeating, final String recurrence,
+            final Integer remindById, final Integer firstReminder, final Integer secondReminder) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CALENDAR_RESOURCE_NAME);
+
+        final CalendarType calendarType = CalendarType.fromInt(typeId);
+        if (calendarType.isCollection() && !repeating) {
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue())
+                    .failWithCodeNoParameterAddedToErrorCode("must.repeat.for.collection.calendar");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.title = StringUtils.defaultIfEmpty(title, null);
+        this.description = StringUtils.defaultIfEmpty(description, null);
+        this.location = StringUtils.defaultIfEmpty(location, null);
+
+        if (null != startDate) {
+            this.startDate = startDate.toDateTimeAtStartOfDay().toDate();
+        } else {
+            this.startDate = null;
+        }
+
+        if (null != endDate) {
+            this.endDate = endDate.toDateTimeAtStartOfDay().toDate();
+        } else {
+            this.endDate = null;
+        }
+
+        this.duration = duration;
+        this.typeId = typeId;
+        this.repeating = repeating;
+        this.recurrence = StringUtils.defaultIfEmpty(recurrence, null);
+        this.remindById = remindById;
+        this.firstReminder = firstReminder;
+        this.secondReminder = secondReminder;
+    }
+
+    public static Calendar createRepeatingCalendar(final String title, final LocalDate startDate, final Integer typeId,
+            final CalendarFrequencyType frequencyType, final Integer interval, final Integer repeatsOnDay) {
+
+        final String description = null;
+        final String location = null;
+        final LocalDate endDate = null;
+        final Integer duration = null;
+        final boolean repeating = true;
+        final Integer remindById = null;
+        final Integer firstReminder = null;
+        final Integer secondReminder = null;
+        final String recurrence = constructRecurrence(frequencyType, interval, repeatsOnDay);
+
+        return new Calendar(title, description, location, startDate, endDate, duration, typeId, repeating, recurrence, remindById,
+                firstReminder, secondReminder);
+    }
+
+    public static Calendar fromJson(final JsonCommand command) {
+
+        // final Long entityId = command.getSupportedEntityId();
+        // final Integer entityTypeId =
+        // CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()).getValue();
+        final String title = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue());
+        final String description = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue());
+        final String location = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue());
+        final LocalDate startDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue());
+        final LocalDate endDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue());
+        final Integer duration = command.integerValueSansLocaleOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue());
+        final Integer typeId = command.integerValueSansLocaleOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue());
+        final boolean repeating = command.booleanPrimitiveValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue());
+        final Integer remindById = command.integerValueSansLocaleOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REMIND_BY_ID.getValue());
+        final Integer firstReminder = command.integerValueSansLocaleOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.FIRST_REMINDER
+                .getValue());
+        final Integer secondReminder = command.integerValueSansLocaleOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER
+                .getValue());
+        final String recurrence = Calendar.constructRecurrence(command, null);
+
+        return new Calendar(title, description, location, startDate, endDate, duration, typeId, repeating, recurrence, remindById,
+                firstReminder, secondReminder);
+    }
+
+    public Map<String, Object> updateStartDateAndDerivedFeilds(final LocalDate newMeetingStartDate) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+        final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+
+        if (newMeetingStartDate.isBefore(currentDate)) {
+            final String defaultUserMessage = "New meeting effective from date cannot be in past";
+            throw new CalendarDateException("new.start.date.cannot.be.in.past", defaultUserMessage, newMeetingStartDate,
+                    getStartDateLocalDate());
+        } else if (isStartDateAfter(newMeetingStartDate) && isStartDateBeforeOrEqual(currentDate)) {
+            // new meeting date should be on or after start date or current
+            // date
+            final String defaultUserMessage = "New meeting effective from date cannot be a date before existing meeting start date";
+            throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newMeetingStartDate,
+                    getStartDateLocalDate());
+        } else {
+
+            actualChanges.put(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue(), newMeetingStartDate.toString());
+            this.startDate = newMeetingStartDate.toDate();
+
+            /*
+             * If meeting start date is changed then there is possibilities of
+             * recurring day may change, so derive the recurring day and update
+             * it if it is changed. For weekly type is weekday and for monthly
+             * type it is day of the month
+             */
+
+            CalendarFrequencyType calendarFrequencyType = CalendarUtils.getFrequency(this.recurrence);
+            Integer interval = CalendarUtils.getInterval(this.recurrence);
+            Integer repeatsOnDay = null;
+
+            /*
+             * Repeats on day, need to derive based on the start date
+             */
+
+            if (calendarFrequencyType.isWeekly()) {
+                repeatsOnDay = newMeetingStartDate.getDayOfWeek();
+            } else if (calendarFrequencyType.isMonthly()) {
+                repeatsOnDay = newMeetingStartDate.getDayOfMonth();
+            }
+
+            // TODO cover other recurrence also
+
+            this.recurrence = constructRecurrence(calendarFrequencyType, interval, repeatsOnDay);
+
+        }
+
+        return actualChanges;
+
+    }
+
+    public Map<String, Object> update(final JsonCommand command, final Boolean areActiveEntitiesSynced ) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+        if (command.isChangeInStringParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue(), this.title)) {
+            final String newValue = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue());
+            actualChanges.put(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue(), newValue);
+            this.title = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue(), this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue());
+            actualChanges.put(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue(), newValue);
+            this.description = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue(), this.location)) {
+            final String newValue = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue());
+            actualChanges.put(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue(), newValue);
+            this.location = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+        final String startDateParamName = CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue();
+        if (command.isChangeInLocalDateParameterNamed(startDateParamName, getStartDateLocalDate())) {
+
+            final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName);
+            final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName);
+            final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+
+            if (newValue.isBefore(currentDate)) {
+                final String defaultUserMessage = "New meeting effective from date cannot be in past";
+                throw new CalendarDateException("new.start.date.cannot.be.in.past", defaultUserMessage, newValue, getStartDateLocalDate());
+            } else if (isStartDateAfter(newValue) && isStartDateBeforeOrEqual(currentDate)) {
+                // new meeting date should be on or after start date or current
+                // date
+                final String defaultUserMessage = "New meeting effective from date cannot be a date before existing meeting start date";
+                throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newValue,
+                        getStartDateLocalDate());
+            } else {
+                actualChanges.put(startDateParamName, valueAsInput);
+                actualChanges.put("dateFormat", dateFormatAsInput);
+                actualChanges.put("locale", localeAsInput);
+                this.startDate = newValue.toDate();
+            }
+        }
+
+        final String endDateParamName = CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue();
+        if (command.isChangeInLocalDateParameterNamed(endDateParamName, getEndDateLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(endDateParamName);
+            actualChanges.put(endDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(endDateParamName);
+            this.endDate = newValue.toDate();
+        }
+
+        final String durationParamName = CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue();
+        if (command.isChangeInIntegerSansLocaleParameterNamed(durationParamName, this.duration)) {
+            final Integer newValue = command.integerValueSansLocaleOfParameterNamed(durationParamName);
+            actualChanges.put(durationParamName, newValue);
+            this.duration = newValue;
+        }
+
+        // Do not allow to change calendar type
+        // TODO: AA Instead of throwing an exception, do not allow meeting
+        // calendar type to update.
+        final String typeParamName = CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue();
+        if (command.isChangeInIntegerSansLocaleParameterNamed(typeParamName, this.typeId)) {
+            final Integer newValue = command.integerValueSansLocaleOfParameterNamed(typeParamName);
+            final String defaultUserMessage = "Meeting calendar type update is not supported";
+            final String oldMeeingType = CalendarType.fromInt(this.typeId).name();
+            final String newMeetingType = CalendarType.fromInt(newValue).name();
+
+            throw new CalendarParameterUpdateNotSupportedException("meeting.type", defaultUserMessage, newMeetingType, oldMeeingType);
+            /*
+             * final Integer newValue =
+             * command.integerValueSansLocaleOfParameterNamed(typeParamName);
+             * actualChanges.put(typeParamName, newValue); this.typeId =
+             * newValue;
+             */
+        }
+
+        final String repeatingParamName = CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue();
+        if (command.isChangeInBooleanParameterNamed(repeatingParamName, this.repeating)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(repeatingParamName);
+            actualChanges.put(repeatingParamName, newValue);
+            this.repeating = newValue;
+        }
+
+        // if repeating is false then update recurrence to NULL
+        if (!this.repeating) this.recurrence = null;
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CALENDAR_RESOURCE_NAME);
+
+        final CalendarType calendarType = CalendarType.fromInt(this.typeId);
+        if (calendarType.isCollection() && !this.repeating) {
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue())
+                    .failWithCodeNoParameterAddedToErrorCode("must.repeat.for.collection.calendar");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final String newRecurrence = Calendar.constructRecurrence(command, this);
+        if (!StringUtils.isBlank(this.recurrence) && !newRecurrence.equalsIgnoreCase(this.recurrence)) {
+            /*
+             * If active entities like JLG loan or RD accounts are synced to the
+             * calendar then do not allow to change meeting frequency
+             */
+
+            if ( areActiveEntitiesSynced && !CalendarUtils.isFrequencySame(this.recurrence, newRecurrence)) {
+                final String defaultUserMessage = "Update of meeting frequency is not supported";
+                throw new CalendarParameterUpdateNotSupportedException("meeting.frequency", defaultUserMessage);
+            }
+
+            /*
+             * If active entities like JLG loan or RD accounts are synced to the
+             * calendar then do not allow to change meeting interval
+             */
+
+            if (areActiveEntitiesSynced && !CalendarUtils.isIntervalSame(this.recurrence, newRecurrence)) {
+                final String defaultUserMessage = "Update of meeting interval is not supported";
+                throw new CalendarParameterUpdateNotSupportedException("meeting.interval", defaultUserMessage);
+            }
+
+            actualChanges.put("recurrence", newRecurrence);
+            this.recurrence = StringUtils.defaultIfEmpty(newRecurrence, null);
+        }
+
+        final String remindByParamName = CALENDAR_SUPPORTED_PARAMETERS.REMIND_BY_ID.getValue();
+        if (command.isChangeInIntegerSansLocaleParameterNamed(remindByParamName, this.remindById)) {
+            final Integer newValue = command.integerValueSansLocaleOfParameterNamed(remindByParamName);
+            actualChanges.put(remindByParamName, newValue);
+            this.remindById = newValue;
+        }
+
+        final String firstRemindarParamName = CALENDAR_SUPPORTED_PARAMETERS.FIRST_REMINDER.getValue();
+        if (command.isChangeInIntegerSansLocaleParameterNamed(firstRemindarParamName, this.firstReminder)) {
+            final Integer newValue = command.integerValueSansLocaleOfParameterNamed(firstRemindarParamName);
+            actualChanges.put(firstRemindarParamName, newValue);
+            this.firstReminder = newValue;
+        }
+
+        final String secondRemindarParamName = CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER.getValue();
+        if (command.isChangeInIntegerSansLocaleParameterNamed(secondRemindarParamName, this.secondReminder)) {
+            final Integer newValue = command.integerValueSansLocaleOfParameterNamed(secondRemindarParamName);
+            actualChanges.put(secondRemindarParamName, newValue);
+            this.secondReminder = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    @SuppressWarnings("null")
+    public Map<String, Object> updateRepeatingCalendar(final LocalDate calendarStartDate, final CalendarFrequencyType frequencyType,
+            final Integer interval, final Integer repeatsOnDay) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+        if (calendarStartDate != null & this.startDate != null) {
+            if (!calendarStartDate.equals(this.getStartDateLocalDate())) {
+                actualChanges.put("startDate", calendarStartDate);
+                this.startDate = calendarStartDate.toDate();
+            }
+        }
+
+        final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, repeatsOnDay);
+        if (!StringUtils.isBlank(this.recurrence) && !newRecurrence.equalsIgnoreCase(this.recurrence)) {
+            actualChanges.put("recurrence", newRecurrence);
+            this.recurrence = newRecurrence;
+        }
+        return actualChanges;
+    }
+
+    public String getTitle() {
+        return this.title;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public String getLocation() {
+        return this.location;
+    }
+
+    public Date getStartDate() {
+        return this.startDate;
+    }
+
+    public Date getEndDate() {
+        return this.endDate;
+    }
+
+    public Integer getDuration() {
+        return this.duration;
+    }
+
+    public Integer getTypeId() {
+        return this.typeId;
+    }
+
+    public boolean isRepeating() {
+        return this.repeating;
+    }
+
+    public String getRecurrence() {
+        return this.recurrence;
+    }
+
+    public Integer getRemindById() {
+        return this.remindById;
+    }
+
+    public Integer getFirstReminder() {
+        return this.firstReminder;
+    }
+
+    public Integer getSecondReminder() {
+        return this.secondReminder;
+    }
+
+    public LocalDate getStartDateLocalDate() {
+        LocalDate startDateLocalDate = null;
+        if (this.startDate != null) {
+            startDateLocalDate = LocalDate.fromDateFields(this.startDate);
+        }
+        return startDateLocalDate;
+    }
+
+    public LocalDate getEndDateLocalDate() {
+        LocalDate endDateLocalDate = null;
+        if (this.endDate != null) {
+            endDateLocalDate = LocalDate.fromDateFields(this.endDate);
+        }
+        return endDateLocalDate;
+    }
+
+    public Set<CalendarHistory> history() {
+        return this.calendarHistory;
+    }
+
+    public boolean isStartDateBefore(final LocalDate compareDate) {
+        if (this.startDate != null && compareDate != null && getStartDateLocalDate().isBefore(compareDate)) { return true; }
+        return false;
+    }
+
+    public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
+        if (this.startDate != null && compareDate != null) {
+            if (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    public boolean isStartDateAfter(final LocalDate compareDate) {
+        if (this.startDate != null && compareDate != null && getStartDateLocalDate().isAfter(compareDate)) { return true; }
+        return false;
+    }
+
+    public boolean isStartDateAfterOrEqual(final LocalDate compareDate) {
+        if (this.startDate != null && compareDate != null) {
+            if (getStartDateLocalDate().isAfter(compareDate) || getStartDateLocalDate().isEqual(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
+        if (this.endDate != null && compareDate != null) {
+            if (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    public boolean isBetweenStartAndEndDate(final LocalDate compareDate) {
+        if (isStartDateBeforeOrEqual(compareDate)) {
+            if (getEndDateLocalDate() == null || isEndDateAfterOrEqual(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    private static String constructRecurrence(final JsonCommand command, final Calendar calendar) {
+        final boolean repeating;
+        if (command.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue())) {
+            repeating = command.booleanPrimitiveValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue());
+        } else if (calendar != null) {
+            repeating = calendar.isRepeating();
+        } else {
+            repeating = false;
+        }
+
+        if (repeating) {
+            final Integer frequency = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue());
+            final CalendarFrequencyType frequencyType = CalendarFrequencyType.fromInt(frequency);
+            final Integer interval = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue());
+            Integer repeatsOnDay = null;
+            if (frequencyType.isWeekly()) {
+                repeatsOnDay = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue());
+            }
+
+            return constructRecurrence(frequencyType, interval, repeatsOnDay);
+        }
+        return "";
+    }
+
+    private static String constructRecurrence(final CalendarFrequencyType frequencyType, final Integer interval, final Integer repeatsOnDay) {
+        final StringBuilder recurrenceBuilder = new StringBuilder(200);
+
+        recurrenceBuilder.append("FREQ=");
+        recurrenceBuilder.append(frequencyType.toString().toUpperCase());
+        if (interval > 1) {
+            recurrenceBuilder.append(";INTERVAL=");
+            recurrenceBuilder.append(interval);
+        }
+        if (frequencyType.isWeekly()) {
+            final CalendarWeekDaysType weekDays = CalendarWeekDaysType.fromInt(repeatsOnDay);
+            if (!weekDays.isInvalid()) {
+                recurrenceBuilder.append(";BYDAY=");
+                recurrenceBuilder.append(weekDays.toString().toUpperCase());
+            }
+        }
+        return recurrenceBuilder.toString();
+    }
+
+    public boolean isValidRecurringDate(final LocalDate compareDate) {
+
+        if (isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(getRecurrence(), getStartDateLocalDate(),
+                compareDate); }
+
+        // validate with history details.
+        for (CalendarHistory history : history()) {
+            if (history.isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(history.getRecurrence(),
+                    history.getStartDateLocalDate(), compareDate); }
+        }
+
+        return false;
+    }
+
+    public void updateStartAndEndDate(final LocalDate startDate, final LocalDate endDate) {
+
+        final CalendarFrequencyType frequencyType = CalendarUtils.getFrequency(this.recurrence);
+        final Integer interval = new Integer(CalendarUtils.getInterval(this.recurrence));
+        final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, startDate.getDayOfWeek());
+
+        this.recurrence = newRecurrence;
+        this.startDate = startDate.toDate();
+        this.endDate = endDate.toDate();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarEntityType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarEntityType.java
new file mode 100644
index 0000000..4617e79
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarEntityType.java
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.calendar.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum CalendarEntityType {
+
+    INVALID(0, "calendarEntityType.invalid"), //
+    CLIENTS(1, "calendarEntityType.clients"), //
+    GROUPS(2, "calendarEntityType.groups"), //
+    LOANS(3, "calendarEntityType.loans"), //
+    CENTERS(4, "calendarEntityType.centers"), //
+    SAVINGS(5, "calendarEntityType.savings"), //
+    LOAN_RECALCULATION_REST_DETAIL(6, "calendarEntityType.loan.recalculation.rest.detail"), //
+    LOAN_RECALCULATION_COMPOUNDING_DETAIL(7, "calendarEntityType.loan.recalculation.compounding.detail");
+
+    private final Integer value;
+    private final String code;
+
+    private CalendarEntityType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, CalendarEntityType> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final CalendarEntityType entityType : CalendarEntityType.values()) {
+            if (i == 0) {
+                minValue = entityType.value;
+            }
+            intToEnumMap.put(entityType.value, entityType);
+            if (minValue >= entityType.value) {
+                minValue = entityType.value;
+            }
+            if (maxValue < entityType.value) {
+                maxValue = entityType.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static CalendarEntityType fromInt(final int i) {
+        final CalendarEntityType entityType = intToEnumMap.get(Integer.valueOf(i));
+        return entityType;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public static boolean isGroup(final Integer value) {
+        return CalendarEntityType.GROUPS.value.equals(value) ? true : false;
+    }
+
+    public static boolean isGroup(final String name) {
+        return CalendarEntityType.GROUPS.name().equalsIgnoreCase(name) ? true : false;
+    }
+
+    public static boolean isCenter(final Integer value) {
+        return CalendarEntityType.CENTERS.value.equals(value) ? true : false;
+    }
+
+    public static boolean isCenter(final String name) {
+        return CalendarEntityType.CENTERS.name().equalsIgnoreCase(name) ? true : false;
+    }
+
+    public static boolean isLoan(final Integer value) {
+        return CalendarEntityType.LOANS.value.equals(value) ? true : false;
+    }
+
+    public static boolean isLoan(final String name) {
+        return CalendarEntityType.LOANS.name().equalsIgnoreCase(name) ? true : false;
+    }
+
+    public static boolean isClient(final Integer value) {
+        return CalendarEntityType.CLIENTS.value.equals(value) ? true : false;
+    }
+
+    public static boolean isClient(final String name) {
+        return CalendarEntityType.CLIENTS.name().equalsIgnoreCase(name) ? true : false;
+    }
+
+    public boolean isCenter() {
+        return this.value.equals(CalendarEntityType.CENTERS.getValue());
+    }
+
+    public boolean isGroup() {
+        return this.value.equals(CalendarEntityType.GROUPS.getValue());
+    }
+
+    public boolean isLoan() {
+        return this.value.equals(CalendarEntityType.LOANS.getValue());
+    }
+
+    public boolean isClient() {
+        return this.value.equals(CalendarEntityType.CLIENTS.getValue());
+    }
+
+    private static final Map<String, CalendarEntityType> entityNameToEnumMap = new HashMap<>();
+
+    static {
+        for (final CalendarEntityType entityType : CalendarEntityType.values()) {
+            entityNameToEnumMap.put(entityType.name().toLowerCase(), entityType);
+        }
+    }
+
+    public static CalendarEntityType getEntityType(String entityType) {
+        return entityNameToEnumMap.get(entityType.toLowerCase());
+    }
+
+    public static boolean isSavings(final Integer value) {
+        return CalendarEntityType.SAVINGS.value.equals(value) ? true : false;
+    }
+
+    public static boolean isSavings(final String name) {
+        return CalendarEntityType.SAVINGS.name().equalsIgnoreCase(name) ? true : false;
+    }
+
+    public boolean isSavings() {
+        return this.value.equals(CalendarEntityType.SAVINGS.getValue());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarFrequencyType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarFrequencyType.java
new file mode 100644
index 0000000..e7aa8b7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarFrequencyType.java
@@ -0,0 +1,149 @@
+/**
+ * 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.calendar.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.springframework.util.StringUtils;
+
+public enum CalendarFrequencyType {
+
+    INVALID(0, "calendarFrequencyType.invalid"), DAILY(1, "calendarFrequencyType.daily"), WEEKLY(2, "calendarFrequencyType.weekly"), MONTHLY(
+            3, "calendarFrequencyType.monthly"), YEARLY(4, "calendarFrequencyType.yearly");
+
+    private final Integer value;
+    private final String code;
+
+    private CalendarFrequencyType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, CalendarFrequencyType> intToEnumMap = new HashMap<>();
+    private static int minValue = CalendarFrequencyType.DAILY.value;
+    private static int maxValue = CalendarFrequencyType.YEARLY.value;
+
+    static {
+        for (final CalendarFrequencyType type : CalendarFrequencyType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static CalendarFrequencyType fromInt(final int i) {
+        final CalendarFrequencyType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static CalendarFrequencyType fromString(final String frequencyString) {
+        CalendarFrequencyType frequency = CalendarFrequencyType.INVALID;
+
+        if (StringUtils.isEmpty(frequencyString)) return frequency;
+
+        if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.DAILY.toString())) {
+            frequency = CalendarFrequencyType.DAILY;
+        } else if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.WEEKLY.toString())) {
+            frequency = CalendarFrequencyType.WEEKLY;
+        } else if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.MONTHLY.toString())) {
+            frequency = CalendarFrequencyType.MONTHLY;
+        } else if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.YEARLY.toString())) {
+            frequency = CalendarFrequencyType.YEARLY;
+        }
+
+        return frequency;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public boolean isWeekly() {
+        return this.value.equals(CalendarFrequencyType.WEEKLY.value);
+    }
+
+    public boolean isMonthly() {
+        return this.value.equals(CalendarFrequencyType.MONTHLY.value);
+    }
+
+    public boolean isInvalid() {
+        return this.value.equals(CalendarFrequencyType.INVALID.value);
+    }
+
+    /**
+     * To convert from period frequency type tp calendar frequency type. This
+     * method requires code refactoring.
+     * 
+     * @param periodFrequencyType
+     * @return
+     */
+    public static CalendarFrequencyType from(final PeriodFrequencyType periodFrequencyType) {
+        switch (periodFrequencyType) {
+            case DAYS:
+                return CalendarFrequencyType.DAILY;
+            case WEEKS:
+                return CalendarFrequencyType.WEEKLY;
+            case MONTHS:
+                return CalendarFrequencyType.MONTHLY;
+            case YEARS:
+                return CalendarFrequencyType.YEARLY;
+            default:
+                return CalendarFrequencyType.INVALID;
+        }
+    }
+
+    /**
+     * To convert from period frequency type tp calendar frequency type. This
+     * method requires code refactoring.
+     * 
+     * @param frequencyType
+     * @return
+     */
+    public static PeriodFrequencyType from(final CalendarFrequencyType frequencyType) {
+        switch (frequencyType) {
+            case DAILY:
+                return PeriodFrequencyType.DAYS;
+            case WEEKLY:
+                return PeriodFrequencyType.WEEKS;
+            case MONTHLY:
+                return PeriodFrequencyType.MONTHS;
+            case YEARLY:
+                return PeriodFrequencyType.YEARS;
+            default:
+                return PeriodFrequencyType.INVALID;
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistory.java
new file mode 100644
index 0000000..4b193ab
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistory.java
@@ -0,0 +1,145 @@
+/**
+ * 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.calendar.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.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_calendar_history")
+public class CalendarHistory extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "calendar_id", referencedColumnName = "id", nullable = false)
+    private Calendar calendar;
+
+    @Column(name = "title", length = 50, nullable = false)
+    private String title;
+
+    @Column(name = "description", length = 100, nullable = true)
+    private String description;
+
+    @Column(name = "location", length = 100, nullable = true)
+    private String location;
+
+    @Column(name = "start_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date startDate;
+
+    @Column(name = "end_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date endDate;
+
+    @Column(name = "duration", nullable = true)
+    private Integer duration;
+
+    @Column(name = "calendar_type_enum", nullable = false)
+    private Integer typeId;
+
+    @Column(name = "repeating", nullable = false)
+    private boolean repeating = false;
+
+    @Column(name = "recurrence", length = 100, nullable = true)
+    private String recurrence;
+
+    @Column(name = "remind_by_enum", nullable = true)
+    private Integer remindById;
+
+    @Column(name = "first_reminder", nullable = true)
+    private Integer firstReminder;
+
+    @Column(name = "second_reminder", nullable = true)
+    private Integer secondReminder;
+
+    protected CalendarHistory() {
+
+    }
+
+    public CalendarHistory(Calendar calendar, Date startDate) {
+        this.calendar = calendar;
+        this.title = calendar.getTitle();
+        this.description = calendar.getDescription();
+        this.location = calendar.getLocation();
+        this.startDate = startDate;
+        this.endDate = calendar.getStartDate();// Calendar start date become end
+                                               // date for history data.
+        this.duration = calendar.getDuration();
+        this.typeId = calendar.getTypeId();
+        this.repeating = calendar.isRepeating();
+        this.recurrence = calendar.getRecurrence();
+        this.remindById = calendar.getRemindById();
+        this.firstReminder = calendar.getFirstReminder();
+        this.secondReminder = calendar.getSecondReminder();
+    }
+
+    public String getRecurrence() {
+        return this.recurrence;
+    }
+
+    public LocalDate getStartDateLocalDate() {
+        LocalDate startDateLocalDate = null;
+        if (this.startDate != null) {
+            startDateLocalDate = LocalDate.fromDateFields(this.startDate);
+        }
+        return startDateLocalDate;
+    }
+
+    public LocalDate getEndDateLocalDate() {
+        LocalDate endDateLocalDate = null;
+        if (this.endDate != null) {
+            endDateLocalDate = LocalDate.fromDateFields(this.endDate);
+        }
+        return endDateLocalDate;
+    }
+
+    public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
+        if (this.endDate != null && compareDate != null) {
+            if (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
+        if (this.startDate != null && compareDate != null) {
+            if (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    public boolean isBetweenStartAndEndDate(final LocalDate compareDate) {
+        if (isStartDateBeforeOrEqual(compareDate)) {
+            if (getEndDateLocalDate() == null || isEndDateAfterOrEqual(compareDate)) { return true; }
+        }
+        return false;
+    }
+
+    public void updateEndDate(Date historyCalEndDate) {
+        this.endDate = historyCalEndDate;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistoryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistoryRepository.java
new file mode 100644
index 0000000..e6ef431
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistoryRepository.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.calendar.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface CalendarHistoryRepository extends JpaRepository<CalendarHistory, Long>, JpaSpecificationExecutor<CalendarHistory> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstance.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstance.java
new file mode 100644
index 0000000..6c40b04
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstance.java
@@ -0,0 +1,74 @@
+/**
+ * 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.calendar.domain;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_calendar_instance")
+public class CalendarInstance extends AbstractPersistable<Long> {
+
+    @ManyToOne(cascade = CascadeType.ALL)
+    @JoinColumn(name = "calendar_id", nullable = false)
+    private Calendar calendar;
+
+    @Column(name = "entity_id", nullable = false)
+    private Long entityId;
+
+    @Column(name = "entity_type_enum", nullable = false)
+    private Integer entityTypeId;
+
+    public CalendarInstance(final Calendar calendar, final Long entityId, final Integer entityTypeId) {
+        this.calendar = calendar;
+        this.entityId = entityId;
+        this.entityTypeId = entityTypeId;
+    }
+
+    protected CalendarInstance() {
+
+    }
+
+    public static CalendarInstance from(final Calendar calendar, final Long entityId, final Integer entityTypeId) {
+        return new CalendarInstance(calendar, entityId, entityTypeId);
+    }
+
+    public void updateCalendar(final Calendar calendar) {
+        this.calendar = calendar;
+    }
+
+    public Calendar getCalendar() {
+        return this.calendar;
+    }
+
+    public Long getEntityId() {
+        return this.entityId;
+    }
+
+    public Integer getEntityTypeId() {
+        return this.entityTypeId;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstanceRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstanceRepository.java
new file mode 100644
index 0000000..fd36862
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstanceRepository.java
@@ -0,0 +1,65 @@
+/**
+ * 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.calendar.domain;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+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;
+
+import ch.qos.logback.core.net.server.Client;
+
+public interface CalendarInstanceRepository extends JpaRepository<CalendarInstance, Long>, JpaSpecificationExecutor<CalendarInstance> {
+
+    CalendarInstance findByCalendarIdAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId);
+
+    Collection<CalendarInstance> findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId);
+
+    /**
+     * @param entityId : Id of {@link Client}, {@link Group}, {@link Loan} or {@link SavingsAccount}.
+     * @param entityTypeId: {@link CalendarEntityType}
+     * @param calendarTypeId: {@link CalendarType}
+     * @return
+     */
+    CalendarInstance findByEntityIdAndEntityTypeIdAndCalendarTypeId(Long entityId, Integer entityTypeId, Integer calendarTypeId);
+
+    @Query("from CalendarInstance ci where ci.entityId = :entityId and ci.entityTypeId = :entityTypeId")
+    CalendarInstance findCalendarInstaneByEntityId(@Param("entityId") Long entityId, @Param("entityTypeId") Integer entityTypeId);
+
+    Collection<CalendarInstance> findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId);
+
+    /** Should use in clause, can I do it without creating a new class? **/
+    @Query("from CalendarInstance ci where ci.entityId in (select id from Loan loan where loan.client.id = :clientId and loan.group.id = :groupId and (loan.loanStatus = 100 or loan.loanStatus = 200 or loan.loanStatus = 300)) and ci.entityTypeId = 3")
+    List<CalendarInstance> findCalendarInstancesForActiveLoansByGroupIdAndClientId(@Param("groupId") Long groupId,
+            @Param("clientId") Long clientId);
+    
+    /** 
+     *  EntityType = 3 is for loan
+     */
+    
+    @Query("SELECT COUNT(ci.id) FROM CalendarInstance ci, Loan ln WHERE ln.id = ci.entityId AND ci.entityTypeId = 3 AND ci.calendar.id = :calendarId AND ln.loanStatus IN :loanStatuses ") 
+    Integer countOfLoansSyncedWithCalendar(@Param("calendarId") Long calendarId, @Param("loanStatuses") Collection<Integer> loanStatuses );
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java
new file mode 100644
index 0000000..81508c0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java
@@ -0,0 +1,65 @@
+/**
+ * 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.calendar.domain;
+
+import org.apache.fineract.portfolio.calendar.exception.CalendarInstanceNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link CalendarInstanceRepository} that is responsible for
+ * checking if {@link CalendarInstance} is returned when using
+ * <code>findOne</code> repository method and throwing an appropriate not found
+ * exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link CalendarInstanceRepository} is required.
+ * </p>
+ */
+@Service
+public class CalendarInstanceRepositoryWrapper {
+
+    private final CalendarInstanceRepository repository;
+
+    @Autowired
+    public CalendarInstanceRepositoryWrapper(final CalendarInstanceRepository repository) {
+        this.repository = repository;
+    }
+
+    public CalendarInstance findOneWithNotFoundDetection(final Long CalendarInstanceId) {
+        final CalendarInstance calendatInstance = this.repository.findOne(CalendarInstanceId);
+        if (calendatInstance == null) { throw new CalendarInstanceNotFoundException(CalendarInstanceId); }
+        return calendatInstance;
+    }
+
+    public void save(final CalendarInstance calendatInstance) {
+        this.repository.save(calendatInstance);
+    }
+
+    public void delete(final CalendarInstance calendatInstance) {
+        this.repository.delete(calendatInstance);
+    }
+
+    public void saveAndFlush(final CalendarInstance calendatInstance) {
+        this.repository.saveAndFlush(calendatInstance);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarRemindBy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarRemindBy.java
new file mode 100644
index 0000000..39e1fd4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarRemindBy.java
@@ -0,0 +1,81 @@
+/**
+ * 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.calendar.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum CalendarRemindBy {
+
+    SMS(1, "calendarRemindBy.sms"), EMAIL(2, "calendarRemindBy.email"), SYSTEMALERT(3, "calendarRemindBy.systemalert");
+
+    private final Integer value;
+    private final String code;
+
+    private CalendarRemindBy(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, CalendarRemindBy> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final CalendarRemindBy remindBy : CalendarRemindBy.values()) {
+            if (i == 0) {
+                minValue = remindBy.value;
+            }
+            intToEnumMap.put(remindBy.value, remindBy);
+            if (minValue >= remindBy.value) {
+                minValue = remindBy.value;
+            }
+            if (maxValue < remindBy.value) {
+                maxValue = remindBy.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static CalendarRemindBy fromInt(final int i) {
+        final CalendarRemindBy remindBy = intToEnumMap.get(Integer.valueOf(i));
+        return remindBy;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarRepository.java
new file mode 100644
index 0000000..c773787
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarRepository.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.calendar.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface CalendarRepository extends JpaRepository<Calendar, Long>, JpaSpecificationExecutor<Calendar> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarRepositoryWrapper.java
new file mode 100644
index 0000000..9884ffa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarRepositoryWrapper.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.portfolio.calendar.domain;
+
+import org.apache.fineract.portfolio.calendar.exception.CalendarNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link CalendarRepository} that is responsible for checking if
+ * {@link Calendar} is returned when using <code>findOne</code> repository
+ * method and throwing an appropriate not found exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link CalendarRepository} is required.
+ * </p>
+ */
+@Service
+public class CalendarRepositoryWrapper {
+
+    private final CalendarRepository repository;
+
+    @Autowired
+    public CalendarRepositoryWrapper(final CalendarRepository repository) {
+        this.repository = repository;
+    }
+
+    public Calendar findOneWithNotFoundDetection(final Long calendarId) {
+        final Calendar calendar = this.repository.findOne(calendarId);
+        if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
+        return calendar;
+    }
+
+    public void save(final Calendar calendar) {
+        this.repository.save(calendar);
+    }
+
+    public void delete(final Calendar calendar) {
+        this.repository.delete(calendar);
+    }
+
+    public void saveAndFlush(final Calendar calendar) {
+        this.repository.saveAndFlush(calendar);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarType.java
new file mode 100644
index 0000000..c1fa7df
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarType.java
@@ -0,0 +1,86 @@
+/**
+ * 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.calendar.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum CalendarType {
+
+    COLLECTION(1, "calendarType.collection"), TRAINING(2, "calendarType.training"), AUDIT(3, "calendarType.audit"), GENERAL(4,
+            "calendarType.general");
+
+    private final Integer value;
+    private final String code;
+
+    private CalendarType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, CalendarType> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final CalendarType type : CalendarType.values()) {
+            if (i == 0) {
+                minValue = type.value;
+            }
+            intToEnumMap.put(type.value, type);
+            if (minValue >= type.value) {
+                minValue = type.value;
+            }
+            if (maxValue < type.value) {
+                maxValue = type.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static CalendarType fromInt(final int i) {
+        final CalendarType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public boolean isCollection() {
+        return this.value.equals(CalendarType.COLLECTION.value);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarWeekDaysType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarWeekDaysType.java
new file mode 100644
index 0000000..5eb1ab7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarWeekDaysType.java
@@ -0,0 +1,103 @@
+/**
+ * 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.calendar.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.util.StringUtils;
+
+public enum CalendarWeekDaysType {
+
+    INVALID(0, "calendarWeekDaysType.invalid"), MO(1, "calendarWeekDaysType.monday"), TU(2, "calendarWeekDaysType.tuesday"), WE(3,
+            "calendarWeekDaysType.wednesday"), TH(4, "calendarWeekDaysType.thursday"), FR(5, "calendarWeekDaysType.friday"), SA(6,
+            "calendarWeekDaysType.saturday"), SU(7, "calendarWeekDaysType.sunday");
+
+    private final Integer value;
+    private final String code;
+
+    private CalendarWeekDaysType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, CalendarWeekDaysType> intToEnumMap = new HashMap<>();
+    private static int minValue = CalendarWeekDaysType.MO.value;
+    private static int maxValue = CalendarWeekDaysType.SU.value;
+
+    static {
+        for (final CalendarWeekDaysType type : CalendarWeekDaysType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static CalendarWeekDaysType fromInt(final int i) {
+        final CalendarWeekDaysType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public boolean isInvalid() {
+        return this.value.equals(CalendarWeekDaysType.INVALID.value);
+    }
+
+    public static CalendarWeekDaysType fromString(final String weekDayString) {
+        CalendarWeekDaysType weekDay = CalendarWeekDaysType.INVALID;
+
+        if (StringUtils.isEmpty(weekDayString)) return weekDay;
+
+        if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.MO.toString())) {
+            weekDay = CalendarWeekDaysType.MO;
+        } else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.TU.toString())) {
+            weekDay = CalendarWeekDaysType.TU;
+        } else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.WE.toString())) {
+            weekDay = CalendarWeekDaysType.WE;
+        } else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.TH.toString())) {
+            weekDay = CalendarWeekDaysType.TH;
+        } else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.FR.toString())) {
+            weekDay = CalendarWeekDaysType.FR;
+        } else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.SA.toString())) {
+            weekDay = CalendarWeekDaysType.SA;
+        } else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.SU.toString())) {
+            weekDay = CalendarWeekDaysType.SU;
+        }
+
+        return weekDay;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarDateException.java
new file mode 100644
index 0000000..b2e2d1f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarDateException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.calendar.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when Calendar resources are not found.
+ */
+public class CalendarDateException extends AbstractPlatformDomainRuleException {
+
+    public CalendarDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.calendar." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarEntityTypeNotSupportedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarEntityTypeNotSupportedException.java
new file mode 100644
index 0000000..1ce29c7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarEntityTypeNotSupportedException.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.calendar.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class CalendarEntityTypeNotSupportedException extends AbstractPlatformResourceNotFoundException {
+
+    public CalendarEntityTypeNotSupportedException(final String resource) {
+        super("calendar.entitytype.not.supported", "Calendar does not support resource "+ resource);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarInstanceNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarInstanceNotFoundException.java
new file mode 100644
index 0000000..4c97aca
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarInstanceNotFoundException.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.calendar.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when CalendarInstance resources are not
+ * found.
+ */
+public class CalendarInstanceNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public CalendarInstanceNotFoundException(final Long id) {
+        super("error.msg.calendar.instance.id.invalid", "Calendar Instance with identifier " + id + " does not exist", id);
+    }
+
+    public CalendarInstanceNotFoundException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.calendar.instance." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarNotFoundException.java
new file mode 100644
index 0000000..7bf0371
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.calendar.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when Calendar resources are not found.
+ */
+public class CalendarNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public CalendarNotFoundException(final Long id) {
+        super("error.msg.calendar.id.invalid", "Calendar with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarParameterUpdateNotSupportedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarParameterUpdateNotSupportedException.java
new file mode 100644
index 0000000..34396cf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/CalendarParameterUpdateNotSupportedException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.calendar.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when Calendar resources are not found.
+ */
+public class CalendarParameterUpdateNotSupportedException extends AbstractPlatformDomainRuleException {
+
+    public CalendarParameterUpdateNotSupportedException(final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.calendar.update.of." + postFix + ".is.not.supported", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/MeetingFrequencyMismatchException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/MeetingFrequencyMismatchException.java
new file mode 100644
index 0000000..7c5d463
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/MeetingFrequencyMismatchException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.calendar.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when Calendar resources are not found.
+ */
+public class MeetingFrequencyMismatchException extends AbstractPlatformDomainRuleException {
+
+    public MeetingFrequencyMismatchException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.calendar." + postFix + ".not.the.same.as.meeting.frequency", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/NotValidRecurringDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/NotValidRecurringDateException.java
new file mode 100644
index 0000000..2adee96
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/exception/NotValidRecurringDateException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.calendar.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when Calendar resources are not found.
+ */
+public class NotValidRecurringDateException extends AbstractPlatformDomainRuleException {
+
+    public NotValidRecurringDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.calendar." + postFix + ".not.valid.recurring.date", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/handler/CreateCalendarCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/handler/CreateCalendarCommandHandler.java
new file mode 100644
index 0000000..10a074c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/handler/CreateCalendarCommandHandler.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.portfolio.calendar.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.calendar.service.CalendarWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CALENDAR", action = "CREATE")
+public class CreateCalendarCommandHandler implements NewCommandSourceHandler {
+
+    private final CalendarWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateCalendarCommandHandler(final CalendarWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.createCalendar(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/handler/DeleteCalendarCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/handler/DeleteCalendarCommandHandler.java
new file mode 100644
index 0000000..a415104
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/handler/DeleteCalendarCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.calendar.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.calendar.service.CalendarWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CALENDAR", action = "DELETE")
+public class DeleteCalendarCommandHandler implements NewCommandSourceHandler {
+
+    private final CalendarWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteCalendarCommandHandler(final CalendarWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteCalendar(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/handler/UpdateCalendarCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/handler/UpdateCalendarCommandHandler.java
new file mode 100644
index 0000000..12c680b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/handler/UpdateCalendarCommandHandler.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.calendar.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.calendar.service.CalendarWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CALENDAR", action = "UPDATE")
+public class UpdateCalendarCommandHandler implements NewCommandSourceHandler {
+
+    private final CalendarWritePlatformService calendarWritePlatformService;
+
+    @Autowired
+    public UpdateCalendarCommandHandler(final CalendarWritePlatformService calendarWritePlatformService) {
+        this.calendarWritePlatformService = calendarWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.calendarWritePlatformService.updateCalendar(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..8a7c2bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java
@@ -0,0 +1,335 @@
+/**
+ * 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.calendar.serialization;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+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.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
+import org.apache.fineract.portfolio.calendar.command.CalendarCommand;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRemindBy;
+import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<CalendarCommand> {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = CALENDAR_SUPPORTED_PARAMETERS.getAllValues();
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public CalendarCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public CalendarCommand commandFromApiJson(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 JsonElement element = this.fromApiJsonHelper.parse(json);
+        final String title = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue(), element);
+        final String description = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue(), element);
+        final String location = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue(), element);
+        final LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue(),
+                element);
+        final LocalDate endDate = this.fromApiJsonHelper.extractLocalDateNamed(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue(), element);
+        final LocalDate createdDate = this.fromApiJsonHelper.extractLocalDateNamed(CALENDAR_SUPPORTED_PARAMETERS.CREATED_DATE.getValue(),
+                element);
+        final Integer duration = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue(),
+                element);
+        final Integer typeId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue(),
+                element);
+        final boolean repeating = this.fromApiJsonHelper.extractBooleanNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue(), element);
+        final Integer remindById = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                CALENDAR_SUPPORTED_PARAMETERS.REMIND_BY_ID.getValue(), element);
+        final Integer firstReminder = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                CALENDAR_SUPPORTED_PARAMETERS.FIRST_REMINDER.getValue(), element);
+        final Integer secondReminder = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER.getValue(), element);
+
+        return new CalendarCommand(title, description, location, startDate, endDate, createdDate, duration, typeId, repeating, remindById,
+                firstReminder, secondReminder);
+    }
+
+    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 JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("calendar");
+
+        final String title = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue(), element);
+        baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue()).value(title).notBlank()
+                .notExceedingLengthOf(50);
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue(), element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue()).value(description).ignoreIfNull()
+                    .notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue(), element)) {
+            final String location = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue()).value(location).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+        }
+
+        final String startDateStr = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue(), element);
+        baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()).value(startDateStr).notBlank();
+
+        if (!StringUtils.isBlank(startDateStr)) {
+            final LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()).value(startDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue(), element)) {
+            final String endDateStr = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue()).value(endDateStr).notBlank();
+
+            final LocalDate endDate = this.fromApiJsonHelper.extractLocalDateNamed(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue()).value(endDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue(), element)) {
+            final Integer duration = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue()).value(duration).ignoreIfNull();
+        }
+
+        final Integer typeId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue(),
+                element);
+        baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue()).value(typeId).notNull()
+                .inMinMaxRange(CalendarEntityType.getMinValue(), CalendarEntityType.getMaxValue());
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue(), element)) {
+            // FIXME - Throws NullPointerException when boolean value is null
+            final boolean repeating = this.fromApiJsonHelper.extractBooleanNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue()).value(repeating).notNull();
+
+            if (repeating) {
+                final Integer frequency = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                        CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue(), element);
+                baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue()).value(frequency).notBlank()
+                        .inMinMaxRange(CalendarFrequencyType.getMinValue(), CalendarFrequencyType.getMaxValue());
+
+                if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue(), element)) {
+                    final Integer interval = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                            CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue(), element);
+                    baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue()).value(interval).notNull()
+                            .integerGreaterThanZero();
+                }
+                if (CalendarFrequencyType.fromInt(frequency).isWeekly()) {
+                    final Integer repeatsOnDay = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                            CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element);
+                    baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue()).value(repeatsOnDay)
+                            .notBlank().inMinMaxRange(CalendarWeekDaysType.getMinValue(), CalendarWeekDaysType.getMaxValue());
+                }
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REMIND_BY_ID.getValue(), element)) {
+            final Integer remindById = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.REMIND_BY_ID.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REMIND_BY_ID.getValue()).value(remindById).ignoreIfNull()
+                    .inMinMaxRange(CalendarRemindBy.getMinValue(), CalendarRemindBy.getMaxValue());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.FIRST_REMINDER.getValue(), element)) {
+            final Integer firstReminder = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.FIRST_REMINDER.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.FIRST_REMINDER.getValue()).value(firstReminder)
+                    .ignoreIfNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER.getValue(), element)) {
+            final Integer secondReminder = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER.getValue()).value(secondReminder)
+                    .ignoreIfNull().integerGreaterThanZero();
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", 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 JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("calendar");
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.RESCHEDULE_BASED_ON_MEETING_DATES.getValue(), element)) {
+            final Boolean rescheduleBasedOnMeetingDates = this.fromApiJsonHelper.extractBooleanNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.RESCHEDULE_BASED_ON_MEETING_DATES.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.RESCHEDULE_BASED_ON_MEETING_DATES.getValue())
+                    .value(rescheduleBasedOnMeetingDates).validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.PRESENT_MEETING_DATE.getValue(), element)) {
+            final String presentMeetingDate = this.fromApiJsonHelper.extractStringNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.PRESENT_MEETING_DATE.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.PRESENT_MEETING_DATE.getValue()).value(presentMeetingDate)
+                    .notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.NEW_MEETING_DATE.getValue(), element)) {
+            final String newMeetingDate = this.fromApiJsonHelper.extractStringNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.NEW_MEETING_DATE.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.NEW_MEETING_DATE.getValue()).value(newMeetingDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue(), element)) {
+            final String title = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue()).value(title).notBlank()
+                    .notExceedingLengthOf(50);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue(), element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue()).value(description).ignoreIfNull()
+                    .notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue(), element)) {
+            final String location = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue()).value(location).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue(), element)) {
+            final String startDateStr = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()).value(startDateStr).notNull();
+
+            final LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()).value(startDate).notNull();
+        }
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue(), element)) {
+            final LocalDate endDate = this.fromApiJsonHelper.extractLocalDateNamed(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue()).value(endDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue(), element)) {
+            final Integer duration = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue()).value(duration).ignoreIfNull();
+        }
+        // TODO: AA do not allow to change calendar type.
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue(), element)) {
+            final Integer typeId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue()).value(typeId).notNull()
+                    .inMinMaxRange(CalendarEntityType.getMinValue(), CalendarEntityType.getMaxValue());
+        }
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue(), element)) {
+            // FIXME - Throws NullPointerException when boolean value is null
+            final boolean repeating = this.fromApiJsonHelper.extractBooleanNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue()).value(repeating).notNull();
+
+            if (repeating) {
+                final Integer frequency = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                        CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue(), element);
+                baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue()).value(frequency).notBlank()
+                        .inMinMaxRange(CalendarFrequencyType.getMinValue(), CalendarFrequencyType.getMaxValue());
+
+                if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue(), element)) {
+                    final Integer interval = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                            CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue(), element);
+                    baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue()).value(interval).notNull()
+                            .integerGreaterThanZero();
+                }
+
+                if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element)) {
+                    final Integer repeatsOnDay = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                            CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element);
+                    baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue()).value(repeatsOnDay)
+                            .notBlank().inMinMaxRange(CalendarWeekDaysType.getMinValue(), CalendarWeekDaysType.getMaxValue());
+                }
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REMIND_BY_ID.getValue(), element)) {
+            final Integer remindById = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.REMIND_BY_ID.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REMIND_BY_ID.getValue()).value(remindById).ignoreIfNull()
+                    .inMinMaxRange(CalendarRemindBy.getMinValue(), CalendarRemindBy.getMaxValue());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.FIRST_REMINDER.getValue(), element)) {
+            final Integer firstReminder = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.FIRST_REMINDER.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.FIRST_REMINDER.getValue()).value(firstReminder)
+                    .ignoreIfNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER.getValue(), element)) {
+            final Integer secondReminder = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER.getValue(), element);
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER.getValue()).value(secondReminder)
+                    .ignoreIfNull();
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+        
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformService.java
new file mode 100644
index 0000000..1c95c77
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformService.java
@@ -0,0 +1,37 @@
+/**
+ * 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.calendar.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public interface CalendarDropdownReadPlatformService {
+
+    List<EnumOptionData> retrieveCalendarEntityTypeOptions();
+
+    List<EnumOptionData> retrieveCalendarTypeOptions();
+
+    List<EnumOptionData> retrieveCalendarRemindByOptions();
+
+    List<EnumOptionData> retrieveCalendarFrequencyTypeOptions();
+
+    List<EnumOptionData> retrieveCalendarWeekDaysTypeOptions();
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformServiceImpl.java
new file mode 100644
index 0000000..713a38c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformServiceImpl.java
@@ -0,0 +1,58 @@
+/**
+ * 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.calendar.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRemindBy;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CalendarDropdownReadPlatformServiceImpl implements CalendarDropdownReadPlatformService {
+
+    @Override
+    public List<EnumOptionData> retrieveCalendarEntityTypeOptions() {
+        return CalendarEnumerations.calendarEntityType(CalendarEntityType.values());
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveCalendarTypeOptions() {
+        return CalendarEnumerations.calendarType(CalendarType.values());
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveCalendarRemindByOptions() {
+        return CalendarEnumerations.calendarRemindBy(CalendarRemindBy.values());
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveCalendarFrequencyTypeOptions() {
+        return CalendarEnumerations.calendarFrequencyType(CalendarFrequencyType.values());
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveCalendarWeekDaysTypeOptions() {
+        return CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.values());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarEnumerations.java
new file mode 100644
index 0000000..e96dba6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarEnumerations.java
@@ -0,0 +1,123 @@
+/**
+ * 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.calendar.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRemindBy;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
+
+public class CalendarEnumerations {
+
+    public static EnumOptionData calendarEntityType(final int id) {
+        return calendarEntityType(CalendarEntityType.fromInt(id));
+    }
+
+    public static EnumOptionData calendarEntityType(final CalendarEntityType entityType) {
+        final EnumOptionData optionData = new EnumOptionData(entityType.getValue().longValue(), entityType.getCode(), entityType.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> calendarEntityType(final CalendarEntityType[] entityTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final CalendarEntityType entityType : entityTypes) {
+            optionDatas.add(calendarEntityType(entityType));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData calendarType(final int id) {
+        return calendarType(CalendarType.fromInt(id));
+    }
+
+    public static EnumOptionData calendarType(final CalendarType type) {
+        final EnumOptionData optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), type.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> calendarType(final CalendarType[] types) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final CalendarType type : types) {
+            optionDatas.add(calendarType(type));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData calendarRemindBy(final int id) {
+        return calendarRemindBy(CalendarRemindBy.fromInt(id));
+    }
+
+    public static EnumOptionData calendarRemindBy(final CalendarRemindBy remindBy) {
+        final EnumOptionData optionData = new EnumOptionData(remindBy.getValue().longValue(), remindBy.getCode(), remindBy.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> calendarRemindBy(final CalendarRemindBy[] remindBys) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final CalendarRemindBy remindBy : remindBys) {
+            optionDatas.add(calendarRemindBy(remindBy));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData calendarFrequencyType(final int id) {
+        return calendarFrequencyType(CalendarFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData calendarFrequencyType(final CalendarFrequencyType calendarFrequencyType) {
+        final EnumOptionData optionData = new EnumOptionData(calendarFrequencyType.getValue().longValue(), calendarFrequencyType.getCode(),
+                calendarFrequencyType.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> calendarFrequencyType(final CalendarFrequencyType[] calendarFrequencyTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final CalendarFrequencyType calendarFrequencyType : calendarFrequencyTypes) {
+            if (!calendarFrequencyType.isInvalid()) {
+                optionDatas.add(calendarFrequencyType(calendarFrequencyType));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData calendarWeekDaysType(final int id) {
+        return calendarWeekDaysType(CalendarWeekDaysType.fromInt(id));
+    }
+
+    public static EnumOptionData calendarWeekDaysType(final CalendarWeekDaysType calendarWeekDaysType) {
+        final EnumOptionData optionData = new EnumOptionData(calendarWeekDaysType.getValue().longValue(), calendarWeekDaysType.getCode(),
+                calendarWeekDaysType.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> calendarWeekDaysType(final CalendarWeekDaysType[] calendarWeekDaysTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final CalendarWeekDaysType calendarWeekDaysType : calendarWeekDaysTypes) {
+            if (!calendarWeekDaysType.isInvalid()) {
+                optionDatas.add(calendarWeekDaysType(calendarWeekDaysType));
+            }
+        }
+        return optionDatas;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformService.java
new file mode 100644
index 0000000..4584cc4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformService.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.calendar.service;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.meeting.data.MeetingData;
+import org.joda.time.LocalDate;
+
+public interface CalendarReadPlatformService {
+
+    CalendarData retrieveCalendar(final Long calendarId, Long entityId, Integer entityTypeId);
+
+    Collection<CalendarData> retrieveCalendarsByEntity(final Long entityId, final Integer entityTypeId, List<Integer> calendarTypeOptions);
+
+    Collection<CalendarData> retrieveParentCalendarsByEntity(final Long entityId, final Integer entityTypeId,
+            List<Integer> calendarTypeOptions);
+
+    Collection<CalendarData> retrieveAllCalendars();
+
+    CalendarData retrieveNewCalendarDetails();
+
+    Collection<LocalDate> generateRecurringDates(final CalendarData calendarData, final boolean withHistory, final LocalDate tillDate);
+
+    Collection<LocalDate> generateNextTenRecurringDates(final CalendarData calendarData);
+
+    Collection<CalendarData> updateWithRecurringDates(final Collection<CalendarData> calendarsData);
+
+    CalendarData retrieveLoanCalendar(final Long loanId);
+
+    CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId);
+
+    LocalDate generateNextEligibleMeetingDateForCollection(CalendarData calendarData, MeetingData lastMeetingData);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java
new file mode 100644
index 0000000..9c867c1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java
@@ -0,0 +1,468 @@
+/**
+ * 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.calendar.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.exception.CalendarNotFoundException;
+import org.apache.fineract.portfolio.meeting.data.MeetingData;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public CalendarReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class CalendarDataMapper implements RowMapper<CalendarData> {
+
+        public String schema() {
+            return " select c.id as id, ci.id as calendarInstanceId, ci.entity_id as entityId, ci.entity_type_enum as entityTypeId, c.title as title, "
+                    + " c.description as description, c.location as location, c.start_date as startDate, c.end_date as endDate, "
+                    + " c.duration as duration, c.calendar_type_enum as typeId, c.repeating as repeating, "
+                    + " c.recurrence as recurrence, c.remind_by_enum as remindById, c.first_reminder as firstReminder, c.second_reminder as secondReminder, "
+                    + " c.created_date as createdDate, c.lastmodified_date as updatedDate, creatingUser.id as creatingUserId, creatingUser.username as creatingUserName, "
+                    + " updatingUser.id as updatingUserId, updatingUser.username as updatingUserName "
+                    + " from m_calendar c join m_calendar_instance ci on ci.calendar_id=c.id, m_appuser as creatingUser, m_appuser as updatingUser"
+                    + " where c.createdby_id=creatingUser.id and c.lastmodifiedby_id=updatingUser.id ";
+        }
+
+        @Override
+        public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long calendarInstanceId = rs.getLong("calendarInstanceId");
+            final Long entityId = rs.getLong("entityId");
+            final Integer entityTypeId = rs.getInt("entityTypeId");
+            final EnumOptionData entityType = CalendarEnumerations.calendarEntityType(entityTypeId);
+            final String title = rs.getString("title");
+            final String description = rs.getString("description");
+            final String location = rs.getString("location");
+            final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate");
+            final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate");
+            final Integer duration = rs.getInt("duration");
+            final Integer typeId = rs.getInt("typeId");
+            final EnumOptionData type = CalendarEnumerations.calendarType(typeId);
+            final boolean repeating = rs.getBoolean("repeating");
+            final String recurrence = rs.getString("recurrence");
+            final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarUtils.getFrequency(recurrence));
+            final Integer interval = new Integer(CalendarUtils.getInterval(recurrence));
+            final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarUtils.getRepeatsOnDay(recurrence));
+            final Integer remindById = rs.getInt("remindById");
+            EnumOptionData remindBy = null;
+            if (remindById != null && remindById != 0) {
+                remindBy = CalendarEnumerations.calendarRemindBy(remindById);
+            }
+            final Integer firstReminder = rs.getInt("firstReminder");
+            final Integer secondReminder = rs.getInt("secondReminder");
+            String humanReadable = null;
+            if (startDate != null && recurrence != null) {
+                humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence);
+            }
+
+            final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate");
+            final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate");
+            final Long createdByUserId = rs.getLong("creatingUserId");
+            final String createdByUserName = rs.getString("creatingUserName");
+            final Long lastUpdatedByUserId = rs.getLong("updatingUserId");
+            final String lastUpdatedByUserName = rs.getString("updatingUserName");
+
+            return CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate,
+                    duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder,
+                    humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId,
+                    lastUpdatedByUserName);
+        }
+    }
+
+    @Override
+    public CalendarData retrieveCalendar(final Long calendarId, final Long entityId, final Integer entityTypeId) {
+
+        try {
+            final CalendarDataMapper rm = new CalendarDataMapper();
+
+            final String sql = rm.schema() + " and c.id = ? and ci.entity_id = ? and ci.entity_type_enum = ? ";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { calendarId, entityId, entityTypeId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new CalendarNotFoundException(calendarId);
+        }
+    }
+
+    @Override
+    public Collection<CalendarData> retrieveCalendarsByEntity(final Long entityId, final Integer entityTypeId,
+            final List<Integer> calendarTypeOptions) {
+        final CalendarDataMapper rm = new CalendarDataMapper();
+
+        Collection<CalendarData> result = null;
+
+        String sql = "";
+
+        if (calendarTypeOptions == null || calendarTypeOptions.isEmpty()) {
+            sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? order by c.start_date ";
+            result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId });
+        } else if (!calendarTypeOptions.isEmpty()) {
+            final String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions);
+            sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? and c.calendar_type_enum in ( " + sqlCalendarTypeOptions
+                    + " ) order by c.start_date ";
+            result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId });
+        }
+        return result;
+    }
+
+    @Override
+    public CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId) {
+        final CalendarDataMapper rm = new CalendarDataMapper();
+
+        final String sql = rm.schema()
+                + " and ci.entity_id = ? and ci.entity_type_enum = ? and calendar_type_enum = ? order by c.start_date ";
+        final List<CalendarData> result = this.jdbcTemplate.query(sql, rm,
+                new Object[] { entityId, entityTypeId, CalendarType.COLLECTION.getValue() });
+
+        if (!result.isEmpty() && result.size() > 0) { return result.get(0); }
+
+        return null;
+    }
+
+    @Override
+    public Collection<CalendarData> retrieveParentCalendarsByEntity(final Long entityId, final Integer entityTypeId,
+            final List<Integer> calendarTypeOptions) {
+
+        final CalendarDataMapper rm = new CalendarDataMapper();
+        Collection<CalendarData> result = null;
+        String sql = "";
+        final CalendarEntityType ceType = CalendarEntityType.fromInt(entityTypeId);
+        final String parentHeirarchyCondition = getParentHierarchyCondition(ceType);
+
+        // FIXME :AA center is the parent entity of group, change this code to
+        // support more parent entity types.
+        if (calendarTypeOptions == null || calendarTypeOptions.isEmpty()) {
+            sql = rm.schema() + " " + parentHeirarchyCondition + " and ci.entity_type_enum = ? order by c.start_date ";
+            result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, CalendarEntityType.CENTERS.getValue() });
+        } else {
+            final String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions);
+            sql = rm.schema() + " " + parentHeirarchyCondition + " and ci.entity_type_enum = ? and c.calendar_type_enum in ("
+                    + sqlCalendarTypeOptions + ") order by c.start_date ";
+            result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, CalendarEntityType.CENTERS.getValue() });
+        }
+        return result;
+    }
+
+    @Override
+    public Collection<CalendarData> retrieveAllCalendars() {
+
+        final CalendarDataMapper rm = new CalendarDataMapper();
+
+        final String sql = rm.schema();
+
+        return this.jdbcTemplate.query(sql, rm);
+    }
+
+    @Override
+    public CalendarData retrieveNewCalendarDetails() {
+        return CalendarData.sensibleDefaultsForNewCalendarCreation();
+    }
+
+    @Override
+    public Collection<LocalDate> generateRecurringDates(final CalendarData calendarData, final boolean withHistory, final LocalDate tillDate) {
+        final LocalDate fromDate = null;
+        Collection<LocalDate> recurringDates = generateRecurringDate(calendarData, fromDate, tillDate, -1);
+
+        if (withHistory) {
+            final Collection<CalendarData> calendarHistorys = this.retrieveCalendarsFromHistory(calendarData.getId());
+            for (CalendarData calendarHistory : calendarHistorys) {
+                recurringDates.addAll(generateRecurringDate(calendarHistory, fromDate, tillDate, -1));
+            }
+        }
+
+        return recurringDates;
+    }
+
+    @Override
+    public Collection<LocalDate> generateNextTenRecurringDates(CalendarData calendarData) {
+        final LocalDate tillDate = null;
+        return generateRecurringDate(calendarData, DateUtils.getLocalDateOfTenant(), tillDate, 10);
+    }
+
+    private Collection<LocalDate> generateRecurringDate(final CalendarData calendarData, final LocalDate fromDate,
+            final LocalDate tillDate, final int maxCount) {
+
+        if (!calendarData.isRepeating()) { return null; }
+        final String rrule = calendarData.getRecurrence();
+        /**
+         * Start date or effective from date of calendar recurrence.
+         */
+        final LocalDate seedDate = this.getSeedDate(calendarData.getStartDate());
+        /**
+         * periodStartDate date onwards recurring dates will be generated.
+         */
+        final LocalDate periodStartDate = this.getPeriodStartDate(seedDate, calendarData.getStartDate(), fromDate);
+        /**
+         * till periodEndDate recurring dates will be generated.
+         */
+        final LocalDate periodEndDate = this.getPeriodEndDate(calendarData.getEndDate(), tillDate);
+
+        final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, periodStartDate, periodEndDate,
+                maxCount);
+        return recurringDates;
+    }
+
+    private LocalDate getSeedDate(LocalDate date) {
+        return date;
+    }
+
+    private LocalDate getPeriodStartDate(final LocalDate seedDate, final LocalDate recurrenceStartDate, final LocalDate fromDate) {
+        LocalDate periodStartDate = null;
+        if (fromDate != null) {
+            periodStartDate = fromDate;
+        } else {
+            final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+            if (seedDate.isBefore(currentDate.minusYears(1))) {
+                periodStartDate = currentDate.minusYears(1);
+            } else {
+                periodStartDate = recurrenceStartDate;
+            }
+        }
+        return periodStartDate;
+    }
+
+    private LocalDate getPeriodEndDate(LocalDate endDate, LocalDate tillDate) {
+        LocalDate periodEndDate = endDate;
+        final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+
+        if (tillDate != null) {
+            if (endDate != null) {
+                if (endDate.isAfter(tillDate)) {
+                    // to retrieve meeting dates tillspecified date (tillDate)
+                    periodEndDate = tillDate;
+                }
+            } else {
+                // end date is null then fetch meeting dates tillDate
+                periodEndDate = tillDate;
+            }
+        } else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) {
+            periodEndDate = currentDate.plusYears(1);
+        }
+        return periodEndDate;
+    }
+
+    @Override
+    public LocalDate generateNextEligibleMeetingDateForCollection(final CalendarData calendarData, final MeetingData lastMeetingData) {
+
+        final LocalDate lastMeetingDate = (lastMeetingData == null) ? null : lastMeetingData.getMeetingDate();
+        // get applicable calendar based on meeting date
+        CalendarData applicableCalendarData = calendarData;
+        LocalDate nextEligibleMeetingDate = null;
+        /**
+         * The next available meeting date for collection should be taken from
+         * application calendar for that time period. e.g. If the previous
+         * calendar details has weekly meeting starting from 1st of Oct 2013 on
+         * every Tuesday, then meeting dates for collection are 1,8,15,22,29..
+         * 
+         * If meeting schedule has changed from Tuesday to Friday with effective
+         * from 15th of Oct (calendar update has made on 2nd of Oct) , then
+         * application should allow to generate collection sheet on 8th of Oct
+         * which is still on Tuesday and next collection sheet date should be on
+         * 18th of Oct as per current calendar
+         */
+        if (lastMeetingDate != null && !calendarData.isBetweenStartAndEndDate(lastMeetingDate)
+                && !calendarData.isBetweenStartAndEndDate(DateUtils.getLocalDateOfTenant())) {
+            applicableCalendarData = this.retrieveApplicableCalendarFromHistory(calendarData.getId(), lastMeetingDate);
+            nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), lastMeetingDate);
+        }
+
+        /**
+         * If nextEligibleMeetingDate is on or after current calendar startdate
+         * then regenerate the nextEligible meeting date based on
+         */
+        if (nextEligibleMeetingDate == null) {
+            final LocalDate seedDate = (lastMeetingDate != null) ? lastMeetingDate : calendarData.getStartDate();
+            nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), seedDate);
+        } else if (calendarData.isBetweenStartAndEndDate(nextEligibleMeetingDate)) {
+            nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(),
+                    calendarData.getStartDate());
+        }
+
+        return nextEligibleMeetingDate;
+    }
+
+    @Override
+    public Collection<CalendarData> updateWithRecurringDates(final Collection<CalendarData> calendarsData) {
+        final Collection<CalendarData> recuCalendarsData = new ArrayList<>();
+        final boolean withHistory = true;
+        final LocalDate tillDate = null;
+        for (final CalendarData calendarData : calendarsData) {
+            final Collection<LocalDate> recurringDates = this.generateRecurringDates(calendarData, withHistory, tillDate);
+            final Collection<LocalDate> nextTenRecurringDates = this.generateNextTenRecurringDates(calendarData);
+            final LocalDate recentEligibleMeetingDate = null;
+            final CalendarData updatedCalendarData = CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates,
+                    recentEligibleMeetingDate);
+            recuCalendarsData.add(updatedCalendarData);
+        }
+
+        return recuCalendarsData;
+    }
+
+    @Override
+    public CalendarData retrieveLoanCalendar(final Long loanId) {
+        final CalendarDataMapper rm = new CalendarDataMapper();
+
+        final String sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? order by c.start_date ";
+        CalendarData calendarData = null;
+        final Collection<CalendarData> calendars = this.jdbcTemplate.query(sql, rm,
+                new Object[] { loanId, CalendarEntityType.LOANS.getValue() });
+
+        if (!CollectionUtils.isEmpty(calendars)) {
+            for (final CalendarData calendar : calendars) {
+                calendarData = calendar;
+                break;// Loans are associated with only one calendar
+            }
+        }
+
+        return calendarData;
+    }
+
+    public static String getParentHierarchyCondition(final CalendarEntityType calendarEntityType) {
+        String conditionSql = "";
+
+        switch (calendarEntityType) {
+            case CLIENTS:
+                // TODO : AA : do we need to propagate to top level parent in
+                // hierarchy?
+                conditionSql = " and ci.entity_id in (select gc.group_id from m_client c join m_group_client gc "
+                        + " on c.id=gc.client_id where c.id = ? ) ";
+            break;
+
+            case GROUPS:
+                // TODO : AA: add parent hierarchy for groups
+                conditionSql = " and ci.entity_id in (select g.parent_id from m_group g where g.id = ? ) ";
+            break;
+
+            case LOANS:
+                // TODO : AA: do we need parent hierarchy calendars for loans?
+                conditionSql = " and ci.entity_id = ?  ";
+            break;
+
+            default:
+            break;
+        }
+
+        return conditionSql;
+    }
+
+    private CalendarData retrieveApplicableCalendarFromHistory(Long calendarId, LocalDate compareDate) {
+        try {
+            final CalendarDataFromHistoryMapper rm = new CalendarDataFromHistoryMapper();
+
+            final String sql = rm.schema() + " where c.calendar_id = ? and date(?) between c.start_date and c.end_date limit 1";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { calendarId, compareDate.toDate() });
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    private Collection<CalendarData> retrieveCalendarsFromHistory(Long calendarId) {
+        try {
+            final CalendarDataFromHistoryMapper rm = new CalendarDataFromHistoryMapper();
+
+            final String sql = rm.schema() + " where c.calendar_id = ? ";
+
+            final Collection<CalendarData> calendars = this.jdbcTemplate.query(sql, rm, new Object[] { calendarId });
+            return calendars;
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    private static final class CalendarDataFromHistoryMapper implements RowMapper<CalendarData> {
+
+        public String schema() {
+            return " select c.calendar_id as id, c.title as title, c.description as description, c.location as location, c.start_date as startDate, "
+                    + " c.end_date as endDate, c.duration as duration, c.calendar_type_enum as typeId, c.repeating as repeating, "
+                    + " c.recurrence as recurrence, c.remind_by_enum as remindById, c.first_reminder as firstReminder, c.second_reminder as secondReminder "
+                    + " from m_calendar_history c ";
+        }
+
+        @Override
+        public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long calendarInstanceId = null;
+            final Long entityId = null;
+            final EnumOptionData entityType = null;
+            final String title = rs.getString("title");
+            final String description = rs.getString("description");
+            final String location = rs.getString("location");
+            final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate");
+            final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate");
+            final Integer duration = rs.getInt("duration");
+            final Integer typeId = rs.getInt("typeId");
+            final EnumOptionData type = CalendarEnumerations.calendarType(typeId);
+            final boolean repeating = rs.getBoolean("repeating");
+            final String recurrence = rs.getString("recurrence");
+            final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarUtils.getFrequency(recurrence));
+            final Integer interval = new Integer(CalendarUtils.getInterval(recurrence));
+            final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarUtils.getRepeatsOnDay(recurrence));
+            final Integer remindById = rs.getInt("remindById");
+            EnumOptionData remindBy = null;
+            if (remindById != null && remindById != 0) {
+                remindBy = CalendarEnumerations.calendarRemindBy(remindById);
+            }
+            final Integer firstReminder = rs.getInt("firstReminder");
+            final Integer secondReminder = rs.getInt("secondReminder");
+            String humanReadable = null;
+            if (startDate != null && recurrence != null) {
+                humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence);
+            }
+
+            final LocalDate createdDate = null;
+            final LocalDate lastUpdatedDate = null;
+            final Long createdByUserId = null;
+            final String createdByUserName = null;
+            final Long lastUpdatedByUserId = null;
+            final String lastUpdatedByUserName = null;
+
+            return CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate,
+                    duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder,
+                    humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId,
+                    lastUpdatedByUserName);
+        }
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..ce42c0a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java
@@ -0,0 +1,537 @@
+/**
+ * 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.calendar.service;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import net.fortuna.ical4j.model.Date;
+import net.fortuna.ical4j.model.DateList;
+import net.fortuna.ical4j.model.DateTime;
+import net.fortuna.ical4j.model.Recur;
+import net.fortuna.ical4j.model.ValidationException;
+import net.fortuna.ical4j.model.WeekDay;
+import net.fortuna.ical4j.model.WeekDayList;
+import net.fortuna.ical4j.model.parameter.Value;
+import net.fortuna.ical4j.model.property.RRule;
+
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+public class CalendarUtils {
+
+    static {
+        System.setProperty("net.fortuna.ical4j.timezone.date.floating", "true");
+    }
+
+    public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) {
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        if (recur == null) { return null; }
+        LocalDate nextDate = getNextRecurringDate(recur, seedDate, startDate);
+        nextDate = adjustDate(nextDate, seedDate, getMeetingPeriodFrequencyType(recurringRule));
+        return nextDate;
+    }
+
+    public static LocalDate adjustDate(final LocalDate date, final LocalDate seedDate, final PeriodFrequencyType frequencyType) {
+        LocalDate adjustedVal = date;
+        if (frequencyType.isMonthly() && seedDate.getDayOfMonth() > 28) {
+            switch (date.getMonthOfYear()) {
+                case 2:
+                    if (date.year().isLeap()) {
+                        adjustedVal = date.dayOfMonth().setCopy(29);
+                    }
+                break;
+                case 4:
+                case 6:
+                case 9:
+                case 11:
+                    if (seedDate.getDayOfMonth() > 30) {
+                        adjustedVal = date.dayOfMonth().setCopy(30);
+                    } else {
+                        adjustedVal = date.dayOfMonth().setCopy(seedDate.getDayOfMonth());
+                    }
+                break;
+                case 1:
+                case 3:
+                case 5:
+                case 7:
+                case 8:
+                case 10:
+                case 12:
+                    adjustedVal = date.dayOfMonth().setCopy(seedDate.getDayOfMonth());
+                break;
+            }
+        }
+        return adjustedVal;
+    }
+
+    private static LocalDate getNextRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate startDate) {
+        final DateTime periodStart = new DateTime(startDate.toDate());
+        final Date seed = convertToiCal4JCompatibleDate(seedDate);
+        final Date nextRecDate = recur.getNextDate(seed, periodStart);
+        return nextRecDate == null ? null : new LocalDate(nextRecDate);
+    }
+
+    private static Date convertToiCal4JCompatibleDate(final LocalDate inputDate) {
+        // Date format in iCal4J is hard coded
+        Date formattedDate = null;
+        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+        final String seedDateStr = df.format(inputDate.toDateTimeAtStartOfDay().toDate());
+        try {
+            formattedDate = new Date(seedDateStr, "yyyy-MM-dd");
+        } catch (final ParseException e) {
+            e.printStackTrace();
+        }
+        return formattedDate;
+    }
+
+    public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate, final LocalDate endDate) {
+
+        final LocalDate periodStartDate = DateUtils.getLocalDateOfTenant();
+        final LocalDate periodEndDate = (endDate == null) ? DateUtils.getLocalDateOfTenant().plusYears(5) : endDate;
+        return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate);
+    }
+
+    public static Collection<LocalDate> getRecurringDatesFrom(final String recurringRule, final LocalDate seedDate,
+            final LocalDate startDate) {
+        final LocalDate periodStartDate = (startDate == null) ? DateUtils.getLocalDateOfTenant() : startDate;
+        final LocalDate periodEndDate = DateUtils.getLocalDateOfTenant().plusYears(5);
+        return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate);
+    }
+
+    public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
+            final LocalDate periodStartDate, final LocalDate periodEndDate) {
+        final int maxCount = 10;// Default number of recurring dates
+        return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate, maxCount);
+    }
+
+    public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
+            final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount) {
+
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+
+        return getRecurringDates(recur, seedDate, periodStartDate, periodEndDate, maxCount);
+    }
+
+    private static Collection<LocalDate> getRecurringDates(final Recur recur, final LocalDate seedDate, final LocalDate periodStartDate,
+            final LocalDate periodEndDate, final int maxCount) {
+        if (recur == null) { return null; }
+        final Date seed = convertToiCal4JCompatibleDate(seedDate);
+        final DateTime periodStart = new DateTime(periodStartDate.toDate());
+        final DateTime periodEnd = new DateTime(periodEndDate.toDate());
+
+        final Value value = new Value(Value.DATE.getValue());
+        final DateList recurringDates = recur.getDates(seed, periodStart, periodEnd, value, maxCount);
+        return convertToLocalDateList(recurringDates, seedDate, getMeetingPeriodFrequencyType(recur));
+    }
+
+    private static Collection<LocalDate> convertToLocalDateList(final DateList dates, final LocalDate seedDate,
+            final PeriodFrequencyType frequencyType) {
+
+        final Collection<LocalDate> recurringDates = new ArrayList<>();
+
+        for (@SuppressWarnings("rawtypes")
+        final Iterator iterator = dates.iterator(); iterator.hasNext();) {
+            final Date date = (Date) iterator.next();
+            recurringDates.add(adjustDate(new LocalDate(date), seedDate, frequencyType));
+        }
+
+        return recurringDates;
+    }
+
+    public static Recur getICalRecur(final String recurringRule) {
+
+        // Construct RRule
+        try {
+            final RRule rrule = new RRule(recurringRule);
+            rrule.validate();
+
+            final Recur recur = rrule.getRecur();
+
+            return recur;
+        } catch (final ParseException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (final ValidationException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    public static String getRRuleReadable(final LocalDate startDate, final String recurringRule) {
+
+        String humanReadable = "";
+
+        RRule rrule;
+        Recur recur = null;
+        try {
+            rrule = new RRule(recurringRule);
+            rrule.validate();
+            recur = rrule.getRecur();
+        } catch (final ValidationException e) {
+            throw new PlatformDataIntegrityException("error.msg.invalid.recurring.rule", "The Recurring Rule value: " + recurringRule
+                    + " is not valid.", "recurrence", recurringRule);
+        } catch (final ParseException e) {
+            throw new PlatformDataIntegrityException("error.msg.recurring.rule.parsing.error",
+                    "Error in pasring the Recurring Rule value: " + recurringRule, "recurrence", recurringRule);
+        }
+
+        if (recur == null) { return humanReadable; }
+
+        if (recur.getFrequency().equals(Recur.DAILY)) {
+            if (recur.getInterval() == 1) {
+                humanReadable = "Daily";
+            } else {
+                humanReadable = "Every " + recur.getInterval() + " days";
+            }
+        } else if (recur.getFrequency().equals(Recur.WEEKLY)) {
+            if (recur.getInterval() == 1 || recur.getInterval() == -1) {
+                humanReadable = "Weekly";
+            } else {
+                humanReadable = "Every " + recur.getInterval() + " weeks";
+            }
+
+            humanReadable += " on ";
+            final WeekDayList weekDayList = recur.getDayList();
+
+            for (@SuppressWarnings("rawtypes")
+            final Iterator iterator = weekDayList.iterator(); iterator.hasNext();) {
+                final WeekDay weekDay = (WeekDay) iterator.next();
+                humanReadable += DayNameEnum.from(weekDay.getDay()).getCode();
+            }
+
+        } else if (recur.getFrequency().equals(Recur.MONTHLY)) {
+            if (recur.getInterval() == 1) {
+                humanReadable = "Monthly on day " + startDate.getDayOfMonth();
+            } else {
+                humanReadable = "Every " + recur.getInterval() + " months on day " + startDate.getDayOfMonth();
+            }
+        } else if (recur.getFrequency().equals(Recur.YEARLY)) {
+            if (recur.getInterval() == 1) {
+                humanReadable = "Annually on " + startDate.toString("MMM") + " " + startDate.getDayOfMonth();
+            } else {
+                humanReadable = "Every " + recur.getInterval() + " years on " + startDate.toString("MMM") + " " + startDate.getDayOfMonth();
+            }
+        }
+
+        if (recur.getCount() > 0) {
+            if (recur.getCount() == 1) {
+                humanReadable = "Once";
+            }
+            humanReadable += ", " + recur.getCount() + " times";
+        }
+
+        final Date endDate = recur.getUntil();
+        final LocalDate date = new LocalDate(endDate);
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MMMM YY");
+        final String formattedDate = date.toString(fmt);
+        if (endDate != null) {
+            humanReadable += ", until " + formattedDate;
+        }
+
+        return humanReadable;
+    }
+
+    public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date) {
+
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        if (recur == null) { return false; }
+
+        return isValidRecurringDate(recur, seedDate, date);
+    }
+
+    public static boolean isValidRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate date) {
+
+        final Collection<LocalDate> recurDate = getRecurringDates(recur, seedDate, date, date.plusDays(1), 1);
+        return (recurDate == null || recurDate.isEmpty()) ? false : true;
+    }
+
+    public static enum DayNameEnum {
+        MO(1, "Monday"), TU(2, "Tuesday"), WE(3, "Wednesday"), TH(4, "Thursday"), FR(5, "Friday"), SA(6, "Saturday"), SU(7, "Sunday");
+
+        private final String code;
+        private final Integer value;
+
+        private DayNameEnum(final Integer value, final String code) {
+            this.value = value;
+            this.code = code;
+        }
+
+        public String getCode() {
+            return this.code;
+        }
+
+        public int getValue() {
+            return this.value;
+        }
+
+        public static DayNameEnum from(final String name) {
+            for (final DayNameEnum dayName : DayNameEnum.values()) {
+                if (dayName.toString().equals(name)) { return dayName; }
+            }
+            return DayNameEnum.MO;// Default it to Monday
+        }
+    }
+
+    public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule) {
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        return getMeetingPeriodFrequencyType(recur);
+    }
+
+    private static PeriodFrequencyType getMeetingPeriodFrequencyType(final Recur recur) {
+        PeriodFrequencyType meetingFrequencyType = PeriodFrequencyType.INVALID;
+        if (recur.getFrequency().equals(Recur.DAILY)) {
+            meetingFrequencyType = PeriodFrequencyType.DAYS;
+        } else if (recur.getFrequency().equals(Recur.WEEKLY)) {
+            meetingFrequencyType = PeriodFrequencyType.WEEKS;
+        } else if (recur.getFrequency().equals(Recur.MONTHLY)) {
+            meetingFrequencyType = PeriodFrequencyType.MONTHS;
+        } else if (recur.getFrequency().equals(Recur.YEARLY)) {
+            meetingFrequencyType = PeriodFrequencyType.YEARS;
+        }
+        return meetingFrequencyType;
+    }
+
+    public static String getMeetingFrequencyFromPeriodFrequencyType(final PeriodFrequencyType periodFrequency) {
+        String frequency = null;
+        if (periodFrequency.equals(PeriodFrequencyType.DAYS)) {
+            frequency = Recur.DAILY;
+        } else if (periodFrequency.equals(PeriodFrequencyType.WEEKS)) {
+            frequency = Recur.WEEKLY;
+        } else if (periodFrequency.equals(PeriodFrequencyType.MONTHS)) {
+            frequency = Recur.MONTHLY;
+        } else if (periodFrequency.equals(PeriodFrequencyType.YEARS)) {
+            frequency = Recur.YEARLY;
+        }
+        return frequency;
+    }
+
+    public static int getInterval(final String recurringRule) {
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        return recur.getInterval();
+    }
+
+    public static CalendarFrequencyType getFrequency(final String recurringRule) {
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        return CalendarFrequencyType.fromString(recur.getFrequency());
+    }
+
+    public static CalendarWeekDaysType getRepeatsOnDay(final String recurringRule) {
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        final WeekDayList weekDays = recur.getDayList();
+        if (weekDays.isEmpty()) return CalendarWeekDaysType.INVALID;
+        // supports only one day
+        WeekDay weekDay = (WeekDay) weekDays.get(0);
+        return CalendarWeekDaysType.fromString(weekDay.getDay());
+    }
+
+    public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate,
+            final Integer loanRepaymentInterval, final String frequency) {
+        final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence());
+        if (recur == null) { return null; }
+        LocalDate startDate = disbursementDate;
+        final LocalDate seedDate = calendar.getStartDateLocalDate();
+        if (isValidRedurringDate(calendar.getRecurrence(), seedDate, startDate)) {
+            startDate = startDate.plusDays(1);
+        }
+        // Recurring dates should follow loanRepaymentInterval.
+        // e.g.
+        // for weekly meeting interval is 1
+        // where as for loan product with fortnightly frequency interval is 2
+        // to generate currect set of meeting dates reset interval same as loan
+        // repayment interval.
+        recur.setInterval(loanRepaymentInterval);
+
+        // Recurring dates should follow loanRepayment frequency.
+        // e.g.
+        // daily meeting frequency should support all loan products with any
+        // frequency type.
+        // to generate currect set of meeting dates reset frequency same as loan
+        // repayment frequency.
+        if (recur.getFrequency().equals(Recur.DAILY)) {
+            recur.setFrequency(frequency);
+        }
+
+        final LocalDate firstRepaymentDate = getNextRecurringDate(recur, seedDate, startDate);
+
+        return firstRepaymentDate;
+    }
+
+    public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
+            final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays) {
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        if (recur == null) { return null; }
+        if (isValidRecurringDate(recur, seedDate, oldRepaymentDate)) { return oldRepaymentDate; }
+        return getNextRepaymentMeetingDate(recurringRule, seedDate, oldRepaymentDate, loanRepaymentInterval, frequency, workingDays);
+    }
+
+    public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
+            final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays) {
+
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        if (recur == null) { return null; }
+        LocalDate tmpDate = repaymentDate;
+        if (isValidRecurringDate(recur, seedDate, repaymentDate)) {
+            tmpDate = repaymentDate.plusDays(1);
+        }
+        /*
+         * 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.
+         */
+        recur.setInterval(loanRepaymentInterval);
+
+        /*
+         * 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);
+        }
+
+        LocalDate newRepaymentDate = getNextRecurringDate(recur, seedDate, tmpDate);
+        final LocalDate nextRepaymentDate = getNextRecurringDate(recur, seedDate, newRepaymentDate);
+
+        newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDate, nextRepaymentDate, workingDays);
+
+        return newRepaymentDate;
+    }
+
+    public static boolean isFrequencySame(final String oldRRule, final String newRRule) {
+        final Recur oldRecur = getICalRecur(oldRRule);
+        final Recur newRecur = getICalRecur(newRRule);
+
+        if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null || newRecur.getFrequency() == null) { return false; }
+        return oldRecur.getFrequency().equals(newRecur.getFrequency());
+    }
+
+    public static boolean isIntervalSame(final String oldRRule, final String newRRule) {
+        final Recur oldRecur = getICalRecur(oldRRule);
+        final Recur newRecur = getICalRecur(newRRule);
+
+        if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null || newRecur.getFrequency() == null) { return false; }
+        return (oldRecur.getInterval() == newRecur.getInterval());
+    }
+
+    public static List<Integer> createIntegerListFromQueryParameter(final String calendarTypeQuery) {
+        final List<Integer> calendarTypeOptions = new ArrayList<>();
+        // adding all calendar Types if query parameter is "all"
+        if (calendarTypeQuery.equalsIgnoreCase("all")) {
+            calendarTypeOptions.add(1);
+            calendarTypeOptions.add(2);
+            calendarTypeOptions.add(3);
+            calendarTypeOptions.add(4);
+            return calendarTypeOptions;
+        }
+        // creating a list of calendar type options from the comma separated
+        // query parameter.
+        final List<String> calendarTypeOptionsInQuery = new ArrayList<>();
+        final StringTokenizer st = new StringTokenizer(calendarTypeQuery, ",");
+        while (st.hasMoreElements()) {
+            calendarTypeOptionsInQuery.add(st.nextElement().toString());
+        }
+
+        for (final String calType : calendarTypeOptionsInQuery) {
+            if (calType.equalsIgnoreCase("collection")) {
+                calendarTypeOptions.add(1);
+            } else if (calType.equalsIgnoreCase("training")) {
+                calendarTypeOptions.add(2);
+            } else if (calType.equalsIgnoreCase("audit")) {
+                calendarTypeOptions.add(3);
+            } else if (calType.equalsIgnoreCase("general")) {
+                calendarTypeOptions.add(4);
+            }
+        }
+
+        return calendarTypeOptions;
+    }
+
+    /**
+     * function returns a comma separated list of calendar_type_enum values ex.
+     * 1,2,3,4
+     * 
+     * @param calendarTypeOptions
+     * @return
+     */
+    public static String getSqlCalendarTypeOptionsInString(final List<Integer> calendarTypeOptions) {
+        String sqlCalendarTypeOptions = "";
+        final int size = calendarTypeOptions.size();
+        for (int i = 0; i < size - 1; i++) {
+            sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ",";
+        }
+        sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString();
+        return sqlCalendarTypeOptions;
+    }
+
+    public static LocalDate getRecentEligibleMeetingDate(final String recurringRule, final LocalDate seedDate) {
+        LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        if (recur == null) { return null; }
+
+        if (isValidRecurringDate(recur, seedDate, currentDate)) { return currentDate; }
+
+        if (recur.getFrequency().equals(Recur.DAILY)) {
+            currentDate = currentDate.plusDays(recur.getInterval());
+        } else if (recur.getFrequency().equals(Recur.WEEKLY)) {
+            currentDate = currentDate.plusWeeks(recur.getInterval());
+        } else if (recur.getFrequency().equals(Recur.MONTHLY)) {
+            currentDate = currentDate.plusMonths(recur.getInterval());
+        } else if (recur.getFrequency().equals(Recur.YEARLY)) {
+            currentDate = currentDate.plusYears(recur.getInterval());
+        }
+
+        return getNextRecurringDate(recur, seedDate, currentDate);
+    }
+
+    public static LocalDate getNextScheduleDate(final Calendar calendar, final LocalDate startDate) {
+        final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence());
+        if (recur == null) { return null; }
+        LocalDate date = startDate;
+        final LocalDate seedDate = calendar.getStartDateLocalDate();
+        /**
+         * if (isValidRedurringDate(calendar.getRecurrence(), seedDate, date)) {
+         * date = date.plusDays(1); }
+         **/
+
+        final LocalDate scheduleDate = getNextRecurringDate(recur, seedDate, date);
+
+        return scheduleDate;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformService.java
new file mode 100644
index 0000000..b286c25
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformService.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.portfolio.calendar.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface CalendarWritePlatformService {
+
+    CommandProcessingResult createCalendar(JsonCommand command);
+
+    CommandProcessingResult updateCalendar(JsonCommand command);
+
+    CommandProcessingResult deleteCalendar(Long calendarId);
+
+    CommandProcessingResult createCalendarInstance(Long calendarId, Long entityId, Integer entityTypeId);
+
+    CommandProcessingResult updateCalendarInstance(Long calendarId, Long entityId, Integer entityTypeId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..7e59642
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,361 @@
+/**
+ * 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.calendar.service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarHistory;
+import org.apache.fineract.portfolio.calendar.domain.CalendarHistoryRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.exception.CalendarNotFoundException;
+import org.apache.fineract.portfolio.calendar.serialization.CalendarCommandFromApiJsonDeserializer;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWritePlatformService {
+
+    private final CalendarRepository calendarRepository;
+    private final CalendarHistoryRepository calendarHistoryRepository;
+    private final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final LoanWritePlatformService loanWritePlatformService;
+    private final ConfigurationDomainService configurationDomainService;
+    private final GroupRepositoryWrapper groupRepository;
+    private final LoanRepository loanRepository;
+    private final ClientRepositoryWrapper clientRepository;
+
+    @Autowired
+    public CalendarWritePlatformServiceJpaRepositoryImpl(final CalendarRepository calendarRepository,
+            final CalendarHistoryRepository calendarHistoryRepository,
+            final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final CalendarInstanceRepository calendarInstanceRepository, final LoanWritePlatformService loanWritePlatformService,
+            final ConfigurationDomainService configurationDomainService, final GroupRepositoryWrapper groupRepository,
+            final LoanRepository loanRepository, final ClientRepositoryWrapper clientRepository) {
+        this.calendarRepository = calendarRepository;
+        this.calendarHistoryRepository = calendarHistoryRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.loanWritePlatformService = loanWritePlatformService;
+        this.configurationDomainService = configurationDomainService;
+        this.groupRepository = groupRepository;
+        this.loanRepository = loanRepository;
+        this.clientRepository = clientRepository;
+    }
+
+    @Override
+    public CommandProcessingResult createCalendar(final JsonCommand command) {
+
+        this.fromApiJsonDeserializer.validateForCreate(command.json());
+        Long entityId = null;
+        CalendarEntityType entityType = CalendarEntityType.INVALID;
+        LocalDate entityActivationDate = null;
+        Group centerOrGroup = null;
+        if (command.getGroupId() != null) {
+            centerOrGroup = this.groupRepository.findOneWithNotFoundDetection(command.getGroupId());
+            entityActivationDate = centerOrGroup.getActivationLocalDate();
+            entityType = centerOrGroup.isCenter() ? CalendarEntityType.CENTERS : CalendarEntityType.GROUPS;
+            entityId = command.getGroupId();
+        } else if (command.getLoanId() != null) {
+            final Loan loan = this.loanRepository.findOne(command.getLoanId());
+            entityActivationDate = (loan.getApprovedOnDate() == null) ? loan.getSubmittedOnDate() : loan.getApprovedOnDate();
+            entityType = CalendarEntityType.LOANS;
+            entityId = command.getLoanId();
+        } else if (command.getClientId() != null) {
+            final Client client = this.clientRepository.findOneWithNotFoundDetection(command.getClientId());
+            entityActivationDate = client.getActivationLocalDate();
+            entityType = CalendarEntityType.CLIENTS;
+            entityId = command.getClientId();
+        }
+
+        final Integer entityTypeId = entityType.getValue();
+        final Calendar newCalendar = Calendar.fromJson(command);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("calendar");
+        if (entityActivationDate == null || newCalendar.getStartDateLocalDate().isBefore(entityActivationDate)) {
+            final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
+            String dateAsString = "";
+            if (entityActivationDate != null) dateAsString = formatter.print(entityActivationDate);
+
+            final String errorMessage = "cannot.be.before." + entityType.name().toLowerCase() + ".activation.date";
+            baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()).value(dateAsString)
+                    .failWithCodeNoParameterAddedToErrorCode(errorMessage);
+        }
+
+        if (centerOrGroup != null) {
+            Long centerOrGroupId = centerOrGroup.getId();
+            Integer centerOrGroupEntityTypeId = entityType.getValue();
+
+            final Group parent = centerOrGroup.getParent();
+            if (parent != null) {
+                centerOrGroupId = parent.getId();
+                centerOrGroupEntityTypeId = CalendarEntityType.CENTERS.getValue();
+            }
+
+            final CalendarInstance collectionCalendarInstance = this.calendarInstanceRepository
+                    .findByEntityIdAndEntityTypeIdAndCalendarTypeId(centerOrGroupId, centerOrGroupEntityTypeId,
+                            CalendarType.COLLECTION.getValue());
+            if (collectionCalendarInstance != null) {
+                final String errorMessage = "multiple.collection.calendar.not.supported";
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(errorMessage);
+            }
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+        this.calendarRepository.save(newCalendar);
+
+        final CalendarInstance newCalendarInstance = CalendarInstance.from(newCalendar, entityId, entityTypeId);
+        this.calendarInstanceRepository.save(newCalendarInstance);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(newCalendar.getId()) //
+                .withClientId(command.getClientId()) //
+                .withGroupId(command.getGroupId()) //
+                .withLoanId(command.getLoanId()) //
+                .build();
+
+    }
+
+
+	public void validateIsEditMeetingAllowed(Long groupId) {
+
+		final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+		final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
+				dataValidationErrors).resource("calendar");
+		Group centerOrGroup = null;
+
+		if (groupId != null) {
+			centerOrGroup = this.groupRepository
+					.findOneWithNotFoundDetection(groupId);
+			final Group parent = centerOrGroup.getParent();
+			/* Check if it is a Group and belongs to a center */
+			if (centerOrGroup.isGroup() && parent != null) {
+				
+				Integer centerEntityTypeId = CalendarEntityType.CENTERS
+						.getValue();
+				/* Check if calendar is created at center */
+				final CalendarInstance collectionCalendarInstance = this.calendarInstanceRepository
+						.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
+								parent.getId(), centerEntityTypeId,
+								CalendarType.COLLECTION.getValue());
+				/* If calendar is created by parent group, then it cannot be edited by the child group */
+				if (collectionCalendarInstance != null) {
+					final String errorMessage = "meeting.created.at.center.cannot.be.edited.at.group.level";
+					baseDataValidator.reset()
+							.failWithCodeNoParameterAddedToErrorCode(
+									errorMessage);
+				}
+			}
+
+		}
+		if (!dataValidationErrors.isEmpty()) {
+			throw new PlatformApiDataValidationException(
+					"validation.msg.validation.errors.exist",
+					"Validation errors exist.", dataValidationErrors);
+		}
+
+	}
+    
+    @Override
+    public CommandProcessingResult updateCalendar(final JsonCommand command) {
+
+    	/** Validate to check if Edit is Allowed **/
+    	this.validateIsEditMeetingAllowed(command.getGroupId());
+        /*
+         * Validate all the data for updating the calendar
+         */
+        this.fromApiJsonDeserializer.validateForUpdate(command.json());
+        
+        Boolean areActiveEntitiesSynced = false;
+        final Long calendarId = command.entityId();
+
+        final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
+                LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue()));
+
+        final Integer numberOfActiveLoansSyncedWithThisCalendar = this.calendarInstanceRepository.countOfLoansSyncedWithCalendar(
+                calendarId, loanStatuses);
+
+        /*
+         * areActiveEntitiesSynced is set to true, if there are any active loans
+         * synced to this calendar.
+         */
+        
+        if(numberOfActiveLoansSyncedWithThisCalendar > 0){
+            areActiveEntitiesSynced = true;
+        }
+
+        
+        final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
+        if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); }
+        
+        final Date oldStartDate = calendarForUpdate.getStartDate();
+        final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+        // create calendar history before updating calendar
+        final CalendarHistory calendarHistory = new CalendarHistory(calendarForUpdate, oldStartDate);
+
+        Map<String, Object> changes = null;
+        
+        final Boolean reschedulebasedOnMeetingDates = command
+                .booleanObjectValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.RESCHEDULE_BASED_ON_MEETING_DATES.getValue());
+        
+        /*
+         * System allows to change the meeting date by two means,
+         * 
+         * Option 1: reschedulebasedOnMeetingDates = false or reschedulebasedOnMeetingDates is not passed 
+         * By directly editing the recurring day with effective from
+         * date and system decides the next meeting date based on some sensible
+         * logic (i.e., number of minimum days between two repayments)
+         * 
+         * 
+         * Option 2: reschedulebasedOnMeetingDates = true 
+         * By providing alternative meeting date for one of future
+         * meeting date and derive the day of recurrence from the new meeting
+         * date. Ex: User proposes new meeting date say "14/Nov/2014" for
+         * present meeting date "12/Nov/2014", based on this input other values
+         * re derived and loans are rescheduled
+         * 
+         */
+        
+        LocalDate newMeetingDate = null;
+        LocalDate presentMeetingDate = null;
+        
+        if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) {
+
+            newMeetingDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.NEW_MEETING_DATE.getValue());
+            presentMeetingDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.PRESENT_MEETING_DATE.getValue());
+
+            /*
+             * New meeting date proposed will become the new start date for the
+             * updated calendar
+             */
+
+            changes = calendarForUpdate.updateStartDateAndDerivedFeilds(newMeetingDate);
+
+        } else {
+            changes = calendarForUpdate.update(command, areActiveEntitiesSynced);
+        }
+        
+        if (!changes.isEmpty()) {
+            // update calendar history table only if there is a change in
+            // calendar start date.
+            if (currentDate.isAfter(new LocalDate(oldStartDate))) {
+                final Date endDate = calendarForUpdate.getStartDateLocalDate().minusDays(1).toDate();
+                calendarHistory.updateEndDate(endDate);
+                this.calendarHistoryRepository.save(calendarHistory);
+            }
+
+            this.calendarRepository.saveAndFlush(calendarForUpdate);
+
+            if (this.configurationDomainService.isRescheduleFutureRepaymentsEnabled() && calendarForUpdate.isRepeating()) {
+                // fetch all loan calendar instances associated with modifying
+                // calendar.
+                final Collection<CalendarInstance> loanCalendarInstances = this.calendarInstanceRepository.findByCalendarIdAndEntityTypeId(
+                        calendarId, CalendarEntityType.LOANS.getValue());
+
+                if (!CollectionUtils.isEmpty(loanCalendarInstances)) {
+                    // update all loans associated with modifying calendar
+                    this.loanWritePlatformService.applyMeetingDateChanges(calendarForUpdate, loanCalendarInstances,
+                            reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
+
+                }
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(calendarForUpdate.getId()) //
+                .with(changes) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult deleteCalendar(final Long calendarId) {
+        final Calendar calendarForDelete = this.calendarRepository.findOne(calendarId);
+        if (calendarForDelete == null) { throw new CalendarNotFoundException(calendarId); }
+
+        this.calendarRepository.delete(calendarForDelete);
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(null) //
+                .withEntityId(calendarId) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult createCalendarInstance(final Long calendarId, final Long entityId, final Integer entityTypeId) {
+
+        final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
+        if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); }
+
+        final CalendarInstance newCalendarInstance = new CalendarInstance(calendarForUpdate, entityId, entityTypeId);
+        this.calendarInstanceRepository.save(newCalendarInstance);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(null) //
+                .withEntityId(calendarForUpdate.getId()) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult updateCalendarInstance(final Long calendarId, final Long entityId, final Integer entityTypeId) {
+        final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
+        if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); }
+
+        final CalendarInstance calendarInstanceForUpdate = this.calendarInstanceRepository.findByCalendarIdAndEntityIdAndEntityTypeId(
+                calendarId, entityId, entityTypeId);
+        this.calendarInstanceRepository.saveAndFlush(calendarInstanceForUpdate);
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(null) //
+                .withEntityId(calendarForUpdate.getId()) //
+                .build();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiConstants.java
new file mode 100644
index 0000000..01488dd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiConstants.java
@@ -0,0 +1,25 @@
+/**
+ * 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.charge.api;
+
+public class ChargesApiConstants {
+
+    public static final String glAccountIdParamName = "incomeAccountId";
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResource.java
new file mode 100644
index 0000000..eef68b3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResource.java
@@ -0,0 +1,165 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.charge.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.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.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/charges")
+@Component
+@Scope("singleton")
+public class ChargesApiResource {
+
+    private final Set<String> CHARGES_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "amount", "currency", "penalty", "active",
+            "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "chargeAppliesToOptions",
+            "chargeTimeTypeOptions", "currencyOptions", "loanChargeCalculationTypeOptions", "loanChargeTimeTypeOptions",
+            "savingsChargeCalculationTypeOptions", "savingsChargeTimeTypeOptions", "incomeAccount", "clientChargeCalculationTypeOptions",
+            "clientChargeTimeTypeOptions"));
+
+    private final String resourceNameForPermissions = "CHARGE";
+
+    private final PlatformSecurityContext context;
+    private final ChargeReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<ChargeData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public ChargesApiResource(final PlatformSecurityContext context, final ChargeReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<ChargeData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllCharges(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<ChargeData> charges = this.readPlatformService.retrieveAllCharges();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, charges, this.CHARGES_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCharge(@PathParam("chargeId") final Long chargeId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        ChargeData charge = this.readPlatformService.retrieveCharge(chargeId);
+        if (settings.isTemplate()) {
+            final ChargeData templateData = this.readPlatformService.retrieveNewChargeDetails();
+            charge = ChargeData.withTemplate(charge, templateData);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, charge, this.CHARGES_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveNewChargeDetails(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ChargeData charge = this.readPlatformService.retrieveNewChargeDetails();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, charge, this.CHARGES_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createCharge(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createCharge().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateCharge(@PathParam("chargeId") final Long chargeId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCharge(chargeId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteCharge(@PathParam("chargeId") final Long chargeId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCharge(chargeId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java
new file mode 100755
index 0000000..a818bd3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java
@@ -0,0 +1,268 @@
+/**
+ * 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.charge.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+
+/**
+ * Immutable data object for charge data.
+ */
+public class ChargeData implements Comparable<ChargeData>, Serializable {
+
+    private final Long id;
+    private final String name;
+    private final boolean active;
+    private final boolean penalty;
+    private final CurrencyData currency;
+    private final BigDecimal amount;
+    private final EnumOptionData chargeTimeType;
+    private final EnumOptionData chargeAppliesTo;
+    private final EnumOptionData chargeCalculationType;
+    private final EnumOptionData chargePaymentMode;
+    private final MonthDay feeOnMonthDay;
+    private final Integer feeInterval;
+    private final BigDecimal minCap;
+    private final BigDecimal maxCap;
+    private final EnumOptionData feeFrequency;
+    private final GLAccountData incomeOrLiabilityAccount;
+
+    private final Collection<CurrencyData> currencyOptions;
+    private final List<EnumOptionData> chargeCalculationTypeOptions;//
+    private final List<EnumOptionData> chargeAppliesToOptions;//
+    private final List<EnumOptionData> chargeTimeTypeOptions;//
+    private final List<EnumOptionData> chargePaymetModeOptions;//
+
+    private final List<EnumOptionData> loanChargeCalculationTypeOptions;
+    private final List<EnumOptionData> loanChargeTimeTypeOptions;
+    private final List<EnumOptionData> savingsChargeCalculationTypeOptions;
+    private final List<EnumOptionData> savingsChargeTimeTypeOptions;
+    private final List<EnumOptionData> clientChargeCalculationTypeOptions;
+    private final List<EnumOptionData> clientChargeTimeTypeOptions;
+    private final List<EnumOptionData> feeFrequencyOptions;
+
+    private final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions;
+
+    public static ChargeData template(final Collection<CurrencyData> currencyOptions,
+            final List<EnumOptionData> chargeCalculationTypeOptions, final List<EnumOptionData> chargeAppliesToOptions,
+            final List<EnumOptionData> chargeTimeTypeOptions, final List<EnumOptionData> chargePaymentModeOptions,
+            final List<EnumOptionData> loansChargeCalculationTypeOptions, final List<EnumOptionData> loansChargeTimeTypeOptions,
+            final List<EnumOptionData> savingsChargeCalculationTypeOptions, final List<EnumOptionData> savingsChargeTimeTypeOptions,
+            final List<EnumOptionData> clientChargeCalculationTypeOptions, final List<EnumOptionData> clientChargeTimeTypeOptions,
+            final List<EnumOptionData> feeFrequencyOptions, final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions) {
+        final GLAccountData account = null;
+        return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions,
+                chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions,
+                loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions,
+                clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, null, null, null, null, null, feeFrequencyOptions, account,
+                incomeOrLiabilityAccountOptions);
+    }
+
+    public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) {
+        return new ChargeData(charge.id, charge.name, charge.amount, charge.currency, charge.chargeTimeType, charge.chargeAppliesTo,
+                charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, template.currencyOptions,
+                template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions,
+                template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions,
+                template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions,
+                template.clientChargeCalculationTypeOptions, template.clientChargeTimeTypeOptions, charge.feeOnMonthDay, charge.feeInterval,
+                charge.minCap, charge.maxCap, charge.feeFrequency, template.feeFrequencyOptions, charge.incomeOrLiabilityAccount,
+                template.incomeOrLiabilityAccountOptions);
+    }
+
+    public static ChargeData instance(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
+            final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
+            final EnumOptionData chargePaymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final boolean penalty,
+            final boolean active, final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency,
+            final GLAccountData accountData) {
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final List<EnumOptionData> chargeCalculationTypeOptions = null;
+        final List<EnumOptionData> chargeAppliesToOptions = null;
+        final List<EnumOptionData> chargeTimeTypeOptions = null;
+        final List<EnumOptionData> chargePaymentModeOptions = null;
+        final List<EnumOptionData> loansChargeCalculationTypeOptions = null;
+        final List<EnumOptionData> loansChargeTimeTypeOptions = null;
+        final List<EnumOptionData> savingsChargeCalculationTypeOptions = null;
+        final List<EnumOptionData> savingsChargeTimeTypeOptions = null;
+        final List<EnumOptionData> feeFrequencyOptions = null;
+        final List<EnumOptionData> clientChargeCalculationTypeOptions = null;
+        final List<EnumOptionData> clientChargeTimeTypeOptions = null;
+        final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
+
+        return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
+                penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
+                chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
+                savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions,
+                clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, accountData,
+                incomeOrLiabilityAccountOptions);
+    }
+
+    public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) {
+        final BigDecimal amount = null;
+        final CurrencyData currency = null;
+        final EnumOptionData chargeTimeType = null;
+        final EnumOptionData chargeAppliesTo = null;
+        final EnumOptionData chargeCalculationType = null;
+        final EnumOptionData chargePaymentMode = null;
+        final MonthDay feeOnMonthDay = null;
+        final Integer feeInterval = null;
+        final Boolean penalty = isPenalty;
+        final Boolean active = false;
+        final BigDecimal minCap = null;
+        final BigDecimal maxCap = null;
+        final Collection<CurrencyData> currencyOptions = null;
+        final List<EnumOptionData> chargeCalculationTypeOptions = null;
+        final List<EnumOptionData> chargeAppliesToOptions = null;
+        final List<EnumOptionData> chargeTimeTypeOptions = null;
+        final List<EnumOptionData> chargePaymentModeOptions = null;
+        final List<EnumOptionData> loansChargeCalculationTypeOptions = null;
+        final List<EnumOptionData> loansChargeTimeTypeOptions = null;
+        final List<EnumOptionData> savingsChargeCalculationTypeOptions = null;
+        final List<EnumOptionData> savingsChargeTimeTypeOptions = null;
+        final List<EnumOptionData> clientChargeCalculationTypeOptions = null;
+        final List<EnumOptionData> clientChargeTimeTypeOptions = null;
+        final EnumOptionData feeFrequency = null;
+        final List<EnumOptionData> feeFrequencyOptions = null;
+        final GLAccountData account = null;
+        final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
+        return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
+                penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
+                chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
+                savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions,
+                clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, account,
+                incomeOrLiabilityAccountOptions);
+    }
+
+    private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
+            final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
+            final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active,
+            final Collection<CurrencyData> currencyOptions, final List<EnumOptionData> chargeCalculationTypeOptions,
+            final List<EnumOptionData> chargeAppliesToOptions, final List<EnumOptionData> chargeTimeTypeOptions,
+            final List<EnumOptionData> chargePaymentModeOptions, final List<EnumOptionData> loansChargeCalculationTypeOptions,
+            final List<EnumOptionData> loansChargeTimeTypeOptions, final List<EnumOptionData> savingsChargeCalculationTypeOptions,
+            final List<EnumOptionData> savingsChargeTimeTypeOptions, final List<EnumOptionData> clientChargeCalculationTypeOptions,
+            final List<EnumOptionData> clientChargeTimeTypeOptions, final MonthDay feeOnMonthDay, final Integer feeInterval,
+            final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency,
+            final List<EnumOptionData> feeFrequencyOptions, final GLAccountData account,
+            final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions) {
+        this.id = id;
+        this.name = name;
+        this.amount = amount;
+        this.currency = currency;
+        this.chargeTimeType = chargeTimeType;
+        this.chargeAppliesTo = chargeAppliesTo;
+        this.chargeCalculationType = chargeCalculationType;
+        this.chargePaymentMode = chargePaymentMode;
+        this.feeInterval = feeInterval;
+        this.feeOnMonthDay = feeOnMonthDay;
+        this.penalty = penalty;
+        this.active = active;
+        this.minCap = minCap;
+        this.maxCap = maxCap;
+        this.currencyOptions = currencyOptions;
+        this.chargeCalculationTypeOptions = chargeCalculationTypeOptions;
+        this.chargeAppliesToOptions = chargeAppliesToOptions;
+        this.chargeTimeTypeOptions = chargeTimeTypeOptions;
+        this.chargePaymetModeOptions = chargePaymentModeOptions;
+        this.savingsChargeCalculationTypeOptions = savingsChargeCalculationTypeOptions;
+        this.savingsChargeTimeTypeOptions = savingsChargeTimeTypeOptions;
+        this.clientChargeCalculationTypeOptions = clientChargeCalculationTypeOptions;
+        this.clientChargeTimeTypeOptions = clientChargeTimeTypeOptions;
+        this.loanChargeCalculationTypeOptions = loansChargeCalculationTypeOptions;
+        this.loanChargeTimeTypeOptions = loansChargeTimeTypeOptions;
+        this.feeFrequency = feeFrequency;
+        this.feeFrequencyOptions = feeFrequencyOptions;
+        this.incomeOrLiabilityAccount = account;
+        this.incomeOrLiabilityAccountOptions = incomeOrLiabilityAccountOptions;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        final ChargeData chargeData = (ChargeData) obj;
+        return this.id.equals(chargeData.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return this.id.hashCode();
+    }
+
+    @Override
+    public int compareTo(final ChargeData obj) {
+        if (obj == null) { return -1; }
+
+        return obj.id.compareTo(this.id);
+    }
+
+    public LoanChargeData toLoanChargeData() {
+
+        BigDecimal percentage = null;
+        if (this.chargeCalculationType.getId() == 2) {
+            percentage = this.amount;
+        }
+
+        return LoanChargeData.newLoanChargeDetails(this.id, this.name, this.currency, this.amount, percentage, this.chargeTimeType,
+                this.chargeCalculationType, this.penalty, this.chargePaymentMode, this.minCap, this.maxCap);
+    }
+
+    public SavingsAccountChargeData toSavingsAccountChargeData() {
+
+        final Long savingsChargeId = null;
+        final Long savingsAccountId = null;
+        final BigDecimal amountPaid = BigDecimal.ZERO;
+        final BigDecimal amountWaived = BigDecimal.ZERO;
+        final BigDecimal amountWrittenOff = BigDecimal.ZERO;
+        final BigDecimal amountOutstanding = BigDecimal.ZERO;
+        final BigDecimal percentage = BigDecimal.ZERO;
+        final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
+        final Collection<ChargeData> chargeOptions = null;
+        final LocalDate dueAsOfDate = null;
+        final Boolean isActive = null;
+        final LocalDate inactivationDate = null;
+
+        return SavingsAccountChargeData.instance(savingsChargeId, this.id, savingsAccountId, this.name, this.currency, this.amount,
+                amountPaid, amountWaived, amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType,
+                percentage, amountPercentageAppliedTo, chargeOptions, this.penalty, this.feeOnMonthDay, this.feeInterval, isActive,
+                inactivationDate);
+    }
+
+    public boolean isPenalty() {
+        return this.penalty;
+    }
+
+    public boolean isOverdueInstallmentCharge() {
+        boolean isOverdueInstallmentCharge = false;
+        if (this.chargeTimeType != null) {
+            isOverdueInstallmentCharge = ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isOverdueInstallment();
+        }
+        return isOverdueInstallmentCharge;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
new file mode 100755
index 0000000..7f8a161
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
@@ -0,0 +1,589 @@
+/**
+ * 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.charge.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+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.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.api.ChargesApiConstants;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.exception.ChargeDueAtDisbursementCannotBePenaltyException;
+import org.apache.fineract.portfolio.charge.exception.ChargeMustBePenaltyException;
+import org.apache.fineract.portfolio.charge.exception.ChargeParameterUpdateNotSupportedException;
+import org.apache.fineract.portfolio.charge.service.ChargeEnumerations;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.joda.time.MonthDay;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "name") })
+public class Charge extends AbstractPersistable<Long> {
+
+    @Column(name = "name", length = 100)
+    private String name;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "currency_code", length = 3)
+    private String currencyCode;
+
+    @Column(name = "charge_applies_to_enum", nullable = false)
+    private Integer chargeAppliesTo;
+
+    @Column(name = "charge_time_enum", nullable = false)
+    private Integer chargeTimeType;
+
+    @Column(name = "charge_calculation_enum")
+    private Integer chargeCalculation;
+
+    @Column(name = "charge_payment_mode_enum", nullable = true)
+    private Integer chargePaymentMode;
+
+    @Column(name = "fee_on_day", nullable = true)
+    private Integer feeOnDay;
+
+    @Column(name = "fee_interval", nullable = true)
+    private Integer feeInterval;
+
+    @Column(name = "fee_on_month", nullable = true)
+    private Integer feeOnMonth;
+
+    @Column(name = "is_penalty", nullable = false)
+    private boolean penalty;
+
+    @Column(name = "is_active", nullable = false)
+    private boolean active;
+
+    @Column(name = "is_deleted", nullable = false)
+    private boolean deleted = false;
+
+    @Column(name = "min_cap", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minCap;
+
+    @Column(name = "max_cap", scale = 6, precision = 19, nullable = true)
+    private BigDecimal maxCap;
+
+    @Column(name = "fee_frequency", nullable = true)
+    private Integer feeFrequency;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "income_or_liability_account_id")
+    private GLAccount account;
+
+    public static Charge fromJson(final JsonCommand command, final GLAccount account) {
+
+        final String name = command.stringValueOfParameterNamed("name");
+        final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
+        final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
+
+        final ChargeAppliesTo chargeAppliesTo = ChargeAppliesTo.fromInt(command.integerValueOfParameterNamed("chargeAppliesTo"));
+        final ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(command.integerValueOfParameterNamed("chargeTimeType"));
+        final ChargeCalculationType chargeCalculationType = ChargeCalculationType
+                .fromInt(command.integerValueOfParameterNamed("chargeCalculationType"));
+        final Integer chargePaymentMode = command.integerValueOfParameterNamed("chargePaymentMode");
+
+        final ChargePaymentMode paymentMode = chargePaymentMode == null ? null : ChargePaymentMode.fromInt(chargePaymentMode);
+
+        final boolean penalty = command.booleanPrimitiveValueOfParameterNamed("penalty");
+        final boolean active = command.booleanPrimitiveValueOfParameterNamed("active");
+        final MonthDay feeOnMonthDay = command.extractMonthDayNamed("feeOnMonthDay");
+        final Integer feeInterval = command.integerValueOfParameterNamed("feeInterval");
+        final BigDecimal minCap = command.bigDecimalValueOfParameterNamed("minCap");
+        final BigDecimal maxCap = command.bigDecimalValueOfParameterNamed("maxCap");
+        final Integer feeFrequency = command.integerValueOfParameterNamed("feeFrequency");
+
+        return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active, paymentMode,
+                feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, account);
+    }
+
+    protected Charge() {
+        //
+    }
+
+    private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo,
+            final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, final boolean active,
+            final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final BigDecimal minCap,
+            final BigDecimal maxCap, final Integer feeFrequency, final GLAccount account) {
+        this.name = name;
+        this.amount = amount;
+        this.currencyCode = currencyCode;
+        this.chargeAppliesTo = chargeAppliesTo.getValue();
+        this.chargeTimeType = chargeTime.getValue();
+        this.chargeCalculation = chargeCalculationType.getValue();
+        this.penalty = penalty;
+        this.active = active;
+        this.account = account;
+        this.chargePaymentMode = paymentMode == null ? null : paymentMode.getValue();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
+
+        if (isMonthlyFee() || isAnnualFee()) {
+            this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
+            this.feeOnDay = feeOnMonthDay.getDayOfMonth();
+        }
+        this.feeInterval = feeInterval;
+        this.feeFrequency = feeFrequency;
+
+        if (isSavingsCharge()) {
+            // TODO vishwas, this validation seems unnecessary as identical
+            // validation is performed in the write service
+            if (!isAllowedSavingsChargeTime()) {
+                baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
+                        .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings");
+            }
+            // TODO vishwas, this validation seems unnecessary as identical
+            // validation is performed in the writeservice
+            if (!isAllowedSavingsChargeCalculationType()) {
+                baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
+                        .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
+            }
+
+            if (!ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee()
+                    && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) {
+                baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
+                        .failWithCodeNoParameterAddedToErrorCode("savings.charge.calculation.type.percentage.allowed.only.for.withdrawal");
+            }
+
+        } else if (isLoanCharge()) {
+
+            if (penalty && (chargeTime.isTimeOfDisbursement()
+                    || chargeTime.isTrancheDisbursement())) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); }
+            if (!penalty && chargeTime.isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); }
+            // TODO vishwas, this validation seems unnecessary as identical
+            // validation is performed in the write service
+            if (!isAllowedLoanChargeTime()) {
+                baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
+                        .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
+            }
+        }
+        if (isPercentageOfApprovedAmount()) {
+            this.minCap = minCap;
+            this.maxCap = maxCap;
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final LoanCharge rhs = (LoanCharge) obj;
+        return new EqualsBuilder().appendSuper(super.equals(obj)) //
+                .append(getId(), rhs.getId()) //
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(3, 5) //
+                .append(getId()) //
+                .toHashCode();
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+
+    public Integer getChargeTimeType() {
+        return this.chargeTimeType;
+    }
+
+    public Integer getChargeCalculation() {
+        return this.chargeCalculation;
+    }
+
+    public boolean isActive() {
+        return this.active;
+    }
+
+    public boolean isPenalty() {
+        return this.penalty;
+    }
+
+    public boolean isDeleted() {
+        return this.deleted;
+    }
+
+    public boolean isLoanCharge() {
+        return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isLoanCharge();
+    }
+
+    public boolean isAllowedLoanChargeTime() {
+        return ChargeTimeType.fromInt(this.chargeTimeType).isAllowedLoanChargeTime();
+    }
+
+    public boolean isAllowedClientChargeTime() {
+        return ChargeTimeType.fromInt(this.chargeTimeType).isAllowedClientChargeTime();
+    }
+
+    public boolean isSavingsCharge() {
+        return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isSavingsCharge();
+    }
+
+    public boolean isClientCharge() {
+        return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isClientCharge();
+    }
+
+    public boolean isAllowedSavingsChargeTime() {
+        return ChargeTimeType.fromInt(this.chargeTimeType).isAllowedSavingsChargeTime();
+    }
+
+    public boolean isAllowedSavingsChargeCalculationType() {
+        return ChargeCalculationType.fromInt(this.chargeCalculation).isAllowedSavingsChargeCalculationType();
+    }
+
+    public boolean isAllowedClientChargeCalculationType() {
+        return ChargeCalculationType.fromInt(this.chargeCalculation).isAllowedClientChargeCalculationType();
+    }
+
+    public boolean isPercentageOfApprovedAmount() {
+        return ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfAmount();
+    }
+
+    public boolean isPercentageOfDisbursementAmount() {
+        return ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfDisbursementAmount();
+    }
+
+    public BigDecimal getMinCap() {
+        return this.minCap;
+    }
+
+    public BigDecimal getMaxCap() {
+        return this.maxCap;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String localeAsInput = command.locale();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
+
+        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 currencyCodeParamName = "currencyCode";
+        if (command.isChangeInStringParameterNamed(currencyCodeParamName, this.currencyCode)) {
+            final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName);
+            actualChanges.put(currencyCodeParamName, newValue);
+            this.currencyCode = newValue;
+        }
+
+        final String amountParamName = "amount";
+        if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
+            actualChanges.put(amountParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.amount = newValue;
+        }
+
+        final String chargeTimeParamName = "chargeTimeType";
+        if (command.isChangeInIntegerParameterNamed(chargeTimeParamName, this.chargeTimeType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(chargeTimeParamName);
+            actualChanges.put(chargeTimeParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.chargeTimeType = ChargeTimeType.fromInt(newValue).getValue();
+
+            if (isSavingsCharge()) {
+                if (!isAllowedSavingsChargeTime()) {
+                    baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
+                            .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings");
+                }
+                // if charge time is changed to monthly then validate for
+                // feeOnMonthDay and feeInterval
+                if (isMonthlyFee()) {
+                    final MonthDay monthDay = command.extractMonthDayNamed("feeOnMonthDay");
+                    baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).notNull();
+
+                    final Integer feeInterval = command.integerValueOfParameterNamed("feeInterval");
+                    baseDataValidator.reset().parameter("feeInterval").value(feeInterval).notNull().inMinMaxRange(1, 12);
+                }
+            } else if (isLoanCharge()) {
+                if (!isAllowedLoanChargeTime()) {
+                    baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
+                            .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
+                }
+            } else if (isClientCharge()) {
+                if (!isAllowedLoanChargeTime()) {
+                    baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType)
+                            .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.client");
+                }
+            }
+        }
+
+        final String chargeAppliesToParamName = "chargeAppliesTo";
+        if (command.isChangeInIntegerParameterNamed(chargeAppliesToParamName, this.chargeAppliesTo)) {
+            /*
+             * final Integer newValue =
+             * command.integerValueOfParameterNamed(chargeAppliesToParamName);
+             * actualChanges.put(chargeAppliesToParamName, newValue);
+             * actualChanges.put("locale", localeAsInput); this.chargeAppliesTo
+             * = ChargeAppliesTo.fromInt(newValue).getValue();
+             */
+
+            // AA: Do not allow to change chargeAppliesTo.
+            final String errorMessage = "Update of Charge applies to is not supported";
+            throw new ChargeParameterUpdateNotSupportedException("charge.applies.to", errorMessage);
+        }
+
+        final String chargeCalculationParamName = "chargeCalculationType";
+        if (command.isChangeInIntegerParameterNamed(chargeCalculationParamName, this.chargeCalculation)) {
+            final Integer newValue = command.integerValueOfParameterNamed(chargeCalculationParamName);
+            actualChanges.put(chargeCalculationParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.chargeCalculation = ChargeCalculationType.fromInt(newValue).getValue();
+
+            if (isSavingsCharge()) {
+                if (!isAllowedSavingsChargeCalculationType()) {
+                    baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
+                            .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
+                }
+
+                if (!ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee()
+                        && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) {
+                    baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
+                            .failWithCodeNoParameterAddedToErrorCode("charge.calculation.type.percentage.allowed.only.for.withdrawal");
+                }
+            } else if (isClientCharge()) {
+                if (!isAllowedClientChargeCalculationType()) {
+                    baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
+                            .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.client");
+                }
+            }
+        }
+
+        if (isLoanCharge()) {// validate only for loan charge
+            final String paymentModeParamName = "chargePaymentMode";
+            if (command.isChangeInIntegerParameterNamed(paymentModeParamName, this.chargePaymentMode)) {
+                final Integer newValue = command.integerValueOfParameterNamed(paymentModeParamName);
+                actualChanges.put(paymentModeParamName, newValue);
+                actualChanges.put("locale", localeAsInput);
+                this.chargePaymentMode = ChargePaymentMode.fromInt(newValue).getValue();
+            }
+        }
+
+        if (command.hasParameter("feeOnMonthDay")) {
+            final MonthDay monthDay = command.extractMonthDayNamed("feeOnMonthDay");
+            final String actualValueEntered = command.stringValueOfParameterNamed("feeOnMonthDay");
+            final Integer dayOfMonthValue = monthDay.getDayOfMonth();
+            if (this.feeOnDay != dayOfMonthValue) {
+                actualChanges.put("feeOnMonthDay", actualValueEntered);
+                actualChanges.put("locale", localeAsInput);
+                this.feeOnDay = dayOfMonthValue;
+            }
+
+            final Integer monthOfYear = monthDay.getMonthOfYear();
+            if (this.feeOnMonth != monthOfYear) {
+                actualChanges.put("feeOnMonthDay", actualValueEntered);
+                actualChanges.put("locale", localeAsInput);
+                this.feeOnMonth = monthOfYear;
+            }
+        }
+
+        final String feeInterval = "feeInterval";
+        if (command.isChangeInIntegerParameterNamed(feeInterval, this.feeInterval)) {
+            final Integer newValue = command.integerValueOfParameterNamed(feeInterval);
+            actualChanges.put(feeInterval, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.feeInterval = newValue;
+        }
+
+        final String feeFrequency = "feeFrequency";
+        if (command.isChangeInIntegerParameterNamed(feeFrequency, this.feeFrequency)) {
+            final Integer newValue = command.integerValueOfParameterNamed(feeFrequency);
+            actualChanges.put(feeFrequency, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.feeFrequency = newValue;
+        }
+
+        if (this.feeFrequency != null) {
+            baseDataValidator.reset().parameter("feeInterval").value(this.feeInterval).notNull();
+        }
+
+        final String penaltyParamName = "penalty";
+        if (command.isChangeInBooleanParameterNamed(penaltyParamName, this.penalty)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(penaltyParamName);
+            actualChanges.put(penaltyParamName, newValue);
+            this.penalty = newValue;
+        }
+
+        final String activeParamName = "active";
+        if (command.isChangeInBooleanParameterNamed(activeParamName, this.active)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(activeParamName);
+            actualChanges.put(activeParamName, newValue);
+            this.active = newValue;
+        }
+        // allow min and max cap to be only added to PERCENT_OF_AMOUNT for now
+        if (isPercentageOfApprovedAmount()) {
+            final String minCapParamName = "minCap";
+            if (command.isChangeInBigDecimalParameterNamed(minCapParamName, this.minCap)) {
+                final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minCapParamName);
+                actualChanges.put(minCapParamName, newValue);
+                actualChanges.put("locale", localeAsInput);
+                this.minCap = newValue;
+            }
+            final String maxCapParamName = "maxCap";
+            if (command.isChangeInBigDecimalParameterNamed(maxCapParamName, this.maxCap)) {
+                final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxCapParamName);
+                actualChanges.put(maxCapParamName, newValue);
+                actualChanges.put("locale", localeAsInput);
+                this.maxCap = newValue;
+            }
+
+        }
+
+        if (this.penalty && ChargeTimeType.fromInt(this.chargeTimeType)
+                .isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(this.name); }
+        if (!penalty
+                && ChargeTimeType.fromInt(this.chargeTimeType).isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); }
+
+        if (command.isChangeInLongParameterNamed(ChargesApiConstants.glAccountIdParamName, getIncomeAccountId())) {
+            final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.glAccountIdParamName);
+            actualChanges.put(ChargesApiConstants.glAccountIdParamName, newValue);
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+        return actualChanges;
+    }
+
+    /**
+     * Delete is a <i>soft delete</i>. Updates flag on charge so it wont appear
+     * in query/report results.
+     * 
+     * Any fields with unique constraints and prepended with id of record.
+     */
+    public void delete() {
+        this.deleted = true;
+        this.name = getId() + "_" + this.name;
+    }
+
+    public ChargeData toData() {
+
+        final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(this.chargeTimeType);
+        final EnumOptionData chargeAppliesTo = ChargeEnumerations.chargeAppliesTo(this.chargeAppliesTo);
+        final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(this.chargeCalculation);
+        final EnumOptionData chargePaymentmode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode);
+        final EnumOptionData feeFrequencyType = ChargeEnumerations.chargePaymentMode(this.feeFrequency);
+        GLAccountData accountData = null;
+        if (account != null) {
+            accountData = new GLAccountData(account.getId(), account.getName(), account.getGlCode());
+        }
+        final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null);
+        return ChargeData.instance(getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType,
+                chargePaymentmode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.minCap, this.maxCap,
+                feeFrequencyType, accountData);
+    }
+
+    public Integer getChargePaymentMode() {
+        return this.chargePaymentMode;
+    }
+
+    public Integer getFeeInterval() {
+        return this.feeInterval;
+    }
+
+    public boolean isMonthlyFee() {
+        return ChargeTimeType.fromInt(this.chargeTimeType).isMonthlyFee();
+    }
+
+    public boolean isAnnualFee() {
+        return ChargeTimeType.fromInt(this.chargeTimeType).isAnnualFee();
+    }
+
+    public boolean isOverdueInstallment() {
+        return ChargeTimeType.fromInt(this.chargeTimeType).isOverdueInstallment();
+    }
+
+    public MonthDay getFeeOnMonthDay() {
+        MonthDay feeOnMonthDay = null;
+        if (this.feeOnDay != null && this.feeOnMonth != null) {
+            feeOnMonthDay = new MonthDay(this.feeOnMonth, this.feeOnDay);
+        }
+        return feeOnMonthDay;
+    }
+
+    public Integer feeInterval() {
+        return this.feeInterval;
+    }
+
+    public Integer feeFrequency() {
+        return this.feeFrequency;
+    }
+
+    public GLAccount getAccount() {
+        return this.account;
+    }
+
+    public void setAccount(GLAccount account) {
+        this.account = account;
+    }
+
+    private Long getIncomeAccountId() {
+        Long incomeAccountId = null;
+        if (this.account != null) {
+            incomeAccountId = this.account.getId();
+        }
+        return incomeAccountId;
+    }
+
+    public boolean isDisbursementCharge() {
+        return ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.DISBURSEMENT)
+                || ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeAppliesTo.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeAppliesTo.java
new file mode 100644
index 0000000..76f5742
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeAppliesTo.java
@@ -0,0 +1,82 @@
+/**
+ * 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.charge.domain;
+
+public enum ChargeAppliesTo {
+
+    INVALID(0, "chargeAppliesTo.invalid"), //
+    LOAN(1, "chargeAppliesTo.loan"), //
+    SAVINGS(2, "chargeAppliesTo.savings"), //
+    CLIENT(3, "chargeAppliesTo.client");
+
+    private final Integer value;
+    private final String code;
+
+    private ChargeAppliesTo(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static ChargeAppliesTo fromInt(final Integer chargeAppliesTo) {
+        ChargeAppliesTo chargeAppliesToType = ChargeAppliesTo.INVALID;
+
+        if (chargeAppliesTo != null) {
+            switch (chargeAppliesTo) {
+                case 1:
+                    chargeAppliesToType = LOAN;
+                break;
+                case 2:
+                    chargeAppliesToType = SAVINGS;
+                break;
+                case 3:
+                    chargeAppliesToType = CLIENT;
+                break;
+                default:
+                    chargeAppliesToType = INVALID;
+                break;
+            }
+        }
+
+        return chargeAppliesToType;
+    }
+
+    public boolean isLoanCharge() {
+        return this.value.equals(ChargeAppliesTo.LOAN.getValue());
+    }
+
+    public boolean isSavingsCharge() {
+        return this.value.equals(ChargeAppliesTo.SAVINGS.getValue());
+    }
+
+    public boolean isClientCharge() {
+        return this.value.equals(ChargeAppliesTo.CLIENT.getValue());
+    }
+
+    public static Object[] validValues() {
+        return new Object[] { ChargeAppliesTo.LOAN.getValue(), ChargeAppliesTo.SAVINGS.getValue(), ChargeAppliesTo.CLIENT.getValue() };
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationType.java
new file mode 100644
index 0000000..cd7fe36
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationType.java
@@ -0,0 +1,117 @@
+/**
+ * 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.charge.domain;
+
+public enum ChargeCalculationType {
+
+    INVALID(0, "chargeCalculationType.invalid"), //
+    FLAT(1, "chargeCalculationType.flat"), //
+    PERCENT_OF_AMOUNT(2, "chargeCalculationType.percent.of.amount"), //
+    PERCENT_OF_AMOUNT_AND_INTEREST(3, "chargeCalculationType.percent.of.amount.and.interest"), //
+    PERCENT_OF_INTEREST(4, "chargeCalculationType.percent.of.interest"),
+    PERCENT_OF_DISBURSEMENT_AMOUNT(5,"chargeCalculationType.percent.of.disbursement.amount");
+
+    private final Integer value;
+    private final String code;
+
+    private ChargeCalculationType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] validValuesForLoan() {
+        return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_AMOUNT.getValue(),
+                ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue(), ChargeCalculationType.PERCENT_OF_INTEREST.getValue(),
+                ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue()};
+    }
+
+    public static Object[] validValuesForSavings() {
+        return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_AMOUNT.getValue() };
+    }
+
+    public static Object[] validValuesForClients() {
+        return new Integer[] { ChargeCalculationType.FLAT.getValue() };
+    }
+    
+    public static Object[] validValuesForTrancheDisbursement(){
+    	return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue()};
+    }
+
+    public static ChargeCalculationType fromInt(final Integer chargeCalculation) {
+        ChargeCalculationType chargeCalculationType = ChargeCalculationType.INVALID;
+        switch (chargeCalculation) {
+            case 1:
+                chargeCalculationType = FLAT;
+            break;
+            case 2:
+                chargeCalculationType = PERCENT_OF_AMOUNT;
+            break;
+            case 3:
+                chargeCalculationType = PERCENT_OF_AMOUNT_AND_INTEREST;
+            break;
+            case 4:
+                chargeCalculationType = PERCENT_OF_INTEREST;
+            break;
+            case 5:
+            	chargeCalculationType = PERCENT_OF_DISBURSEMENT_AMOUNT;
+            break;
+        }
+        return chargeCalculationType;
+    }
+
+    public boolean isPercentageOfAmount() {
+        return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue());
+    }
+
+    public boolean isPercentageOfAmountAndInterest() {
+        return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue());
+    }
+
+    public boolean isPercentageOfInterest() {
+        return this.value.equals(ChargeCalculationType.PERCENT_OF_INTEREST.getValue());
+    }
+
+    public boolean isFlat() {
+        return this.value.equals(ChargeCalculationType.FLAT.getValue());
+    }
+
+    public boolean isAllowedSavingsChargeCalculationType() {
+        return isFlat() || isPercentageOfAmount();
+    }
+
+    public boolean isAllowedClientChargeCalculationType() {
+        return isFlat();
+    }
+
+    public boolean isPercentageBased() {
+        return isPercentageOfAmount() || isPercentageOfAmountAndInterest() || isPercentageOfInterest() || isPercentageOfDisbursementAmount();
+    }
+    
+    public boolean isPercentageOfDisbursementAmount(){
+    	return this.value.equals(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargePaymentMode.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargePaymentMode.java
new file mode 100755
index 0000000..ddafebe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargePaymentMode.java
@@ -0,0 +1,62 @@
+/**
+ * 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.charge.domain;
+
+public enum ChargePaymentMode {
+
+    REGULAR(0, "chargepaymentmode.regular"), //
+    ACCOUNT_TRANSFER(1, "chargepaymentmode.accounttransfer");
+
+    private final Integer value;
+    private final String code;
+
+    private ChargePaymentMode(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] validValues() {
+        return new Integer[] { ChargePaymentMode.REGULAR.getValue(), ChargePaymentMode.ACCOUNT_TRANSFER.getValue() };
+    }
+
+    public static ChargePaymentMode fromInt(final Integer paymentMode) {
+        ChargePaymentMode chargeAppliesToType = ChargePaymentMode.REGULAR;
+        switch (paymentMode) {
+            case 1:
+                chargeAppliesToType = ACCOUNT_TRANSFER;
+            break;
+            default:
+                chargeAppliesToType = REGULAR;
+            break;
+        }
+        return chargeAppliesToType;
+    }
+
+    public boolean isPaymentModeAccountTransfer() {
+        return this.value.equals(ChargePaymentMode.ACCOUNT_TRANSFER.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeRepository.java
new file mode 100644
index 0000000..da0b63d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeRepository.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.charge.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ChargeRepository extends JpaRepository<Charge, Long>, JpaSpecificationExecutor<Charge> {
+    // no added behaviour
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeRepositoryWrapper.java
new file mode 100644
index 0000000..beb49c2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeRepositoryWrapper.java
@@ -0,0 +1,56 @@
+/**
+ * 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.charge.domain;
+
+import org.apache.fineract.portfolio.charge.exception.ChargeIsNotActiveException;
+import org.apache.fineract.portfolio.charge.exception.ChargeNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link ChargeRepository} that is responsible for checking if
+ * {@link Charge} is returned when using <code>findOne</code> repository method
+ * and throwing an appropriate not found exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link ChargeRepository} is required.
+ * </p>
+ */
+@Service
+public class ChargeRepositoryWrapper {
+
+    private final ChargeRepository repository;
+
+    @Autowired
+    public ChargeRepositoryWrapper(final ChargeRepository repository) {
+        this.repository = repository;
+    }
+
+    public Charge findOneWithNotFoundDetection(final Long id) {
+
+        final Charge chargeDefinition = this.repository.findOne(id);
+        if (chargeDefinition == null || chargeDefinition.isDeleted()) { throw new ChargeNotFoundException(id); }
+        if (!chargeDefinition.isActive()) { throw new ChargeIsNotActiveException(id, chargeDefinition.getName()); }
+
+        return chargeDefinition;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeType.java
new file mode 100755
index 0000000..5ec2ad8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeType.java
@@ -0,0 +1,182 @@
+/**
+ * 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.charge.domain;
+
+public enum ChargeTimeType {
+
+    INVALID(0, "chargeTimeType.invalid"), //
+    DISBURSEMENT(1, "chargeTimeType.disbursement"), // only for loan charges
+    SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"), // for loan and
+    SAVINGS_ACTIVATION(3, "chargeTimeType.savingsActivation"), // only for
+    SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings
+    WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings
+    ANNUAL_FEE(6, "chargeTimeType.annualFee"), // only for savings
+    MONTHLY_FEE(7, "chargeTimeType.monthlyFee"), // only for savings
+    INSTALMENT_FEE(8, "chargeTimeType.instalmentFee"), // only for loan charges
+    OVERDUE_INSTALLMENT(9, "chargeTimeType.overdueInstallment"), // only for
+    OVERDRAFT_FEE(10, "chargeTimeType.overdraftFee"),// only for savings
+    WEEKLY_FEE(11, "chargeTimeType.weeklyFee"), // only for savings
+    TRANCHE_DISBURSEMENT(12,"chargeTimeType.tranchedisbursement"); // only for loan
+
+    private final Integer value;
+    private final String code;
+
+    private ChargeTimeType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] validLoanValues() {
+        return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(),
+                ChargeTimeType.INSTALMENT_FEE.getValue(), ChargeTimeType.OVERDUE_INSTALLMENT.getValue(), ChargeTimeType.TRANCHE_DISBURSEMENT.getValue() };
+    }
+
+    public static Object[] validLoanChargeValues() {
+        return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(),
+                ChargeTimeType.INSTALMENT_FEE.getValue() };
+    }
+
+    public static Object[] validSavingsValues() {
+        return new Integer[] { ChargeTimeType.SPECIFIED_DUE_DATE.getValue(), ChargeTimeType.SAVINGS_ACTIVATION.getValue(),
+                ChargeTimeType.SAVINGS_CLOSURE.getValue(), ChargeTimeType.WITHDRAWAL_FEE.getValue(), ChargeTimeType.ANNUAL_FEE.getValue(),
+                ChargeTimeType.MONTHLY_FEE.getValue(), ChargeTimeType.OVERDRAFT_FEE.getValue(), ChargeTimeType.WEEKLY_FEE.getValue() };
+    }
+
+    public static Object[] validClientValues() {
+        return new Integer[] { ChargeTimeType.SPECIFIED_DUE_DATE.getValue() };
+    }
+
+    public static ChargeTimeType fromInt(final Integer chargeTime) {
+        ChargeTimeType chargeTimeType = ChargeTimeType.INVALID;
+        if (chargeTime != null) {
+            switch (chargeTime) {
+                case 1:
+                    chargeTimeType = DISBURSEMENT;
+                break;
+                case 2:
+                    chargeTimeType = SPECIFIED_DUE_DATE;
+                break;
+                case 3:
+                    chargeTimeType = SAVINGS_ACTIVATION;
+                break;
+                case 4:
+                    chargeTimeType = SAVINGS_CLOSURE;
+                break;
+                case 5:
+                    chargeTimeType = WITHDRAWAL_FEE;
+                break;
+                case 6:
+                    chargeTimeType = ANNUAL_FEE;
+                break;
+                case 7:
+                    chargeTimeType = MONTHLY_FEE;
+                break;
+                case 8:
+                    chargeTimeType = INSTALMENT_FEE;
+                break;
+                case 9:
+                    chargeTimeType = OVERDUE_INSTALLMENT;
+                break;
+                case 10:
+                    chargeTimeType = OVERDRAFT_FEE;
+                break;
+                case 11:
+                    chargeTimeType = WEEKLY_FEE;
+                break;
+                case 12:
+                    chargeTimeType = TRANCHE_DISBURSEMENT;
+                break;
+                default:
+                    chargeTimeType = INVALID;
+                break;
+            }
+        }
+        return chargeTimeType;
+    }
+
+    public boolean isTimeOfDisbursement() {
+        return ChargeTimeType.DISBURSEMENT.getValue().equals(this.value);
+    }
+
+    public boolean isOnSpecifiedDueDate() {
+        return this.value.equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue());
+    }
+
+    public boolean isSavingsActivation() {
+        return this.value.equals(ChargeTimeType.SAVINGS_ACTIVATION.getValue());
+    }
+
+    public boolean isSavingsClosure() {
+        return this.value.equals(ChargeTimeType.SAVINGS_CLOSURE.getValue());
+    }
+
+    public boolean isWithdrawalFee() {
+        return this.value.equals(ChargeTimeType.WITHDRAWAL_FEE.getValue());
+    }
+
+    public boolean isAnnualFee() {
+        return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue());
+    }
+
+    public boolean isMonthlyFee() {
+        return this.value.equals(ChargeTimeType.MONTHLY_FEE.getValue());
+    }
+
+    public boolean isWeeklyFee() {
+        return this.value.equals(ChargeTimeType.WEEKLY_FEE.getValue());
+    }
+
+    public boolean isInstalmentFee() {
+        return this.value.equals(ChargeTimeType.INSTALMENT_FEE.getValue());
+    }
+
+    public boolean isOverdueInstallment() {
+        return this.value.equals(ChargeTimeType.OVERDUE_INSTALLMENT.getValue());
+    }
+
+    public boolean isAllowedLoanChargeTime() {
+        return isTimeOfDisbursement() || isOnSpecifiedDueDate() || isInstalmentFee() || isOverdueInstallment() || isTrancheDisbursement();
+    }
+
+    public boolean isAllowedClientChargeTime() {
+        return isOnSpecifiedDueDate();
+    }
+
+    public boolean isAllowedSavingsChargeTime() {
+        return isOnSpecifiedDueDate() || isSavingsActivation() || isSavingsClosure() || isWithdrawalFee() || isAnnualFee() || isMonthlyFee()
+                || isWeeklyFee() || isOverdraftFee();
+    }
+
+    public boolean isOverdraftFee() {
+        return this.value.equals(ChargeTimeType.OVERDRAFT_FEE.getValue());
+    }
+    
+    public boolean isTrancheDisbursement(){
+    	return this.value.equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue());
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeCannotBeAppliedToException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeCannotBeAppliedToException.java
new file mode 100644
index 0000000..4927c25
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeCannotBeAppliedToException.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.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ChargeCannotBeAppliedToException extends AbstractPlatformDomainRuleException {
+
+    public ChargeCannotBeAppliedToException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+
+        super("error.msg.charge.cannot.be.applied.to" + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeCannotBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeCannotBeDeletedException.java
new file mode 100644
index 0000000..a1af6c5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeCannotBeDeletedException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ChargeCannotBeDeletedException extends AbstractPlatformDomainRuleException {
+
+    public ChargeCannotBeDeletedException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+
+        super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeCannotBeUpdatedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeCannotBeUpdatedException.java
new file mode 100644
index 0000000..e528b8a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeCannotBeUpdatedException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ChargeCannotBeUpdatedException extends AbstractPlatformDomainRuleException {
+
+    public ChargeCannotBeUpdatedException(final String globalisationMessageCode, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+
+        super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeDueAtDisbursementCannotBePenaltyException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeDueAtDisbursementCannotBePenaltyException.java
new file mode 100644
index 0000000..ecea08c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeDueAtDisbursementCannotBePenaltyException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when attempting to crate
+ * charge of type penalty due at disbursement .
+ */
+public class ChargeDueAtDisbursementCannotBePenaltyException extends AbstractPlatformDomainRuleException {
+
+    public ChargeDueAtDisbursementCannotBePenaltyException(final String name) {
+        super("error.msg.charge.due.at.disbursement.cannot.be.penalty", "Charge '" + name + "' is invalid.", name, name);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeIsNotActiveException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeIsNotActiveException.java
new file mode 100644
index 0000000..70d33e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeIsNotActiveException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when charge is not active.
+ */
+public class ChargeIsNotActiveException extends AbstractPlatformDomainRuleException {
+
+    public ChargeIsNotActiveException(final Long id, final String name) {
+        super("error.msg.charge.is.not.active", "Charge '" + name + "' with identifier " + id + " is not active", name, id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeMustBePenaltyException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeMustBePenaltyException.java
new file mode 100755
index 0000000..135cac7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeMustBePenaltyException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when attempting to crate
+ * charge of type penalty due at disbursement .
+ */
+public class ChargeMustBePenaltyException extends AbstractPlatformDomainRuleException {
+
+    public ChargeMustBePenaltyException(final String name) {
+        super("error.msg.charge.must.be.penalty", "Charge '" + name + "' is invalid.", name, name);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeNotFoundException.java
new file mode 100644
index 0000000..0af425f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeNotFoundException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ChargeNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ChargeNotFoundException(final Long id) {
+        super("error.msg.charge.id.invalid", "Charge with identifier " + id + " does not exist", id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeParameterUpdateNotSupportedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeParameterUpdateNotSupportedException.java
new file mode 100644
index 0000000..c762ef4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/ChargeParameterUpdateNotSupportedException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when Calendar resources are not found.
+ */
+public class ChargeParameterUpdateNotSupportedException extends AbstractPlatformDomainRuleException {
+
+    public ChargeParameterUpdateNotSupportedException(final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.charge.update.of." + postFix + ".is.not.supported", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeAddedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeAddedException.java
new file mode 100644
index 0000000..302942d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeAddedException.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.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when loan charge is not
+ * allowed to be added to the loan.
+ */
+public class LoanChargeCannotBeAddedException extends AbstractPlatformDomainRuleException {
+
+    public LoanChargeCannotBeAddedException(final String entity, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + ".cannot.be.added.as." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeDeletedException.java
new file mode 100755
index 0000000..a2540ce
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeDeletedException.java
@@ -0,0 +1,51 @@
+/**
+ * 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.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanChargeCannotBeDeletedException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons of why Loan Charge cannot be waived **/
+    public static enum LOAN_CHARGE_CANNOT_BE_DELETED_REASON {
+        ALREADY_PAID, ALREADY_WAIVED, LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "This loan charge has been partially/completely paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "This loan charge has already been waived";
+            } else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This charge cannot be deleted as the loan it is associated with is not in submitted and pending approval stage"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "error.msg.loan.charge.already.paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "error.msg.loan.charge.already.waived";
+            } else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage"; }
+            return name().toString();
+        }
+    }
+
+    public LoanChargeCannotBeDeletedException(final LOAN_CHARGE_CANNOT_BE_DELETED_REASON reason, final Long loanChargeId) {
+        super(reason.errorCode(), reason.errorMessage(), loanChargeId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBePayedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBePayedException.java
new file mode 100755
index 0000000..8a2ed1c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBePayedException.java
@@ -0,0 +1,60 @@
+/**
+ * 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.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanChargeCannotBePayedException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons of why Loan Charge cannot be waived **/
+    public static enum LOAN_CHARGE_CANNOT_BE_PAYED_REASON {
+        ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, CHARGE_NOT_ACCOUNT_TRANSFER, CHARGE_NOT_PAYABLE;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "This loan charge has been completely paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "This loan charge has already been waived";
+            } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
+                return "This loan charge can be payed as the loan associated with it is currently inactive";
+            } else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) {
+                return "This loan charge can be payed as the charge payment mode is not account transfer";
+            } else if (name().toString().equalsIgnoreCase("CHARGE_NOT_PAYABLE")) { return "This loan charge is not Payable through account transfer"; }
+
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "error.msg.loan.charge.already.paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "error.msg.loan.charge.already.waived";
+            } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
+                return "error.msg.loan.charge.associated.loan.inactive";
+            } else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) {
+                return "error.msg.loan.charge.payment.mode.not.account.transfer";
+            } else if (name().toString().equalsIgnoreCase("CHARGE_NOT_PAYABLE")) { return "error.msg.loan.charge.payment.not.allowed.account.transfer"; }
+            return name().toString();
+        }
+    }
+
+    public LoanChargeCannotBePayedException(final LOAN_CHARGE_CANNOT_BE_PAYED_REASON reason, final Long loanChargeId) {
+        super(reason.errorCode(), reason.errorMessage(), loanChargeId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeUpdatedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeUpdatedException.java
new file mode 100755
index 0000000..fd56d1a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeUpdatedException.java
@@ -0,0 +1,51 @@
+/**
+ * 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.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanChargeCannotBeUpdatedException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons of why Loan Charge cannot be waived **/
+    public static enum LOAN_CHARGE_CANNOT_BE_UPDATED_REASON {
+        ALREADY_PAID, ALREADY_WAIVED, LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "This loan charge has been partially/completely paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "This loan charge has already been waived";
+            } else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This charge cannot be updated as the loan it is associated with is not in submitted and pending approval stage"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "error.msg.loan.charge.already.paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "error.msg.loan.charge.already.waived";
+            } else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage"; }
+            return name().toString();
+        }
+    }
+
+    public LoanChargeCannotBeUpdatedException(final LOAN_CHARGE_CANNOT_BE_UPDATED_REASON reason, final Long loanChargeId) {
+        super(reason.errorCode(), reason.errorMessage(), loanChargeId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeWaivedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeWaivedException.java
new file mode 100755
index 0000000..4507a78
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeCannotBeWaivedException.java
@@ -0,0 +1,56 @@
+/**
+ * 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.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanChargeCannotBeWaivedException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons of why Loan Charge cannot be waived **/
+    public static enum LOAN_CHARGE_CANNOT_BE_WAIVED_REASON {
+        ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, WAIVE_NOT_ALLOWED_FOR_CHARGE;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "This loan charge has been completely paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "This loan charge has already been waived";
+            } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
+                return "This loan charge can be waived as the loan associated with it is currently inactive";
+            } else if (name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) { return "This loan charge can be waived"; }
+
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "error.msg.loan.charge.already.paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "error.msg.loan.charge.already.waived";
+            } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
+                return "error.msg.loan.charge.associated.loan.inactive";
+            } else if (name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) { return "error.msg.loan.charge.waive.not.allowed"; }
+            return name().toString();
+        }
+    }
+
+    public LoanChargeCannotBeWaivedException(final LOAN_CHARGE_CANNOT_BE_WAIVED_REASON reason, final Long loanChargeId) {
+        super(reason.errorCode(), reason.errorMessage(), loanChargeId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java
new file mode 100644
index 0000000..3f62f17
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when loan charge does not
+ * exist.
+ */
+public class LoanChargeNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public LoanChargeNotFoundException(final Long id) {
+        super("error.msg.loanCharge.id.invalid", "Loan charge with identifier " + id + " does not exist", id);
+    }
+
+    public LoanChargeNotFoundException(final Long id, final Long loanId) {
+        super("error.msg.loanCharge.id.invalid.for.given.loan", "Loan charge with identifier " + id + " does not exist for loan " + loanId,
+                id, loanId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWithoutMandatoryFieldException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWithoutMandatoryFieldException.java
new file mode 100644
index 0000000..b896986
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWithoutMandatoryFieldException.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.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when currency mismatch
+ * occurs
+ */
+public class LoanChargeWithoutMandatoryFieldException extends AbstractPlatformDomainRuleException {
+
+    public LoanChargeWithoutMandatoryFieldException(final String entity, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + "." + postFix + ".cannot.be.blank", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/SavingsAccountChargeCannotBeWaivedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/SavingsAccountChargeCannotBeWaivedException.java
new file mode 100644
index 0000000..891da1f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/SavingsAccountChargeCannotBeWaivedException.java
@@ -0,0 +1,56 @@
+/**
+ * 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.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SavingsAccountChargeCannotBeWaivedException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons of why Savings Account Charge cannot be waived **/
+    public static enum SAVINGS_ACCOUNT_CHARGE_CANNOT_BE_WAIVED_REASON {
+        ALREADY_PAID, ALREADY_WAIVED, SAVINGS_ACCOUNT_NOT_ACTIVE, SAVINGS_ACCOUNT_CLOSED;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "This savings account charge has been completely paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "This savings account charge has already been waived";
+            } else if (name().toString().equalsIgnoreCase("SAVINGS_ACCOUNT_NOT_ACTIVE")) {
+                return "This savings account charge cannot be waived as the Savings account associated with it is not active";
+            } else if (name().toString().equalsIgnoreCase("SAVINGS_ACCOUNT_CLOSED")) { return "This savings account charge cannot be waived as the Savings account associated with it is closed"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+                return "error.msg.savings.account.charge.already.paid";
+            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+                return "error.msg.savings.account.charge.already.waived";
+            } else if (name().toString().equalsIgnoreCase("SAVINGS_ACCOUNT_NOT_ACTIVE")) {
+                return "error.msg.savings.account.charge.associated.savings.account.not.active";
+            } else if (name().toString().equalsIgnoreCase("SAVINGS_ACCOUNT_CLOSED")) { return "error.msg.savings.account.charge.associated.savings.account.closed"; }
+            return name().toString();
+        }
+    }
+
+    public SavingsAccountChargeCannotBeWaivedException(final SAVINGS_ACCOUNT_CHARGE_CANNOT_BE_WAIVED_REASON reason,
+            final Long savingsAccountChargeId) {
+        super(reason.errorCode(), reason.errorMessage(), savingsAccountChargeId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/SavingsAccountChargeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/SavingsAccountChargeNotFoundException.java
new file mode 100644
index 0000000..b487ee3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/SavingsAccountChargeNotFoundException.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when savings account
+ * charge does not exist.
+ */
+public class SavingsAccountChargeNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public SavingsAccountChargeNotFoundException(final Long id) {
+        super("error.msg.savings.account.charge.id.invalid", "Savings Account charge with identifier " + id + " does not exist", id);
+    }
+
+    public SavingsAccountChargeNotFoundException(final Long id, final Long savingsAccountId) {
+        super("error.msg.savings.account.charge.id.invalid.for.given.savings.account", "Savings Account charge with identifier " + id
+                + " does not exist for Savings Account " + savingsAccountId, id, savingsAccountId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/SavingsAccountChargeWithoutMandatoryFieldException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/SavingsAccountChargeWithoutMandatoryFieldException.java
new file mode 100644
index 0000000..8ce64b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/SavingsAccountChargeWithoutMandatoryFieldException.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.portfolio.charge.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when currency mismatch
+ * occurs
+ */
+public class SavingsAccountChargeWithoutMandatoryFieldException extends AbstractPlatformDomainRuleException {
+
+    public SavingsAccountChargeWithoutMandatoryFieldException(final String entity, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + "." + postFix + ".cannot.be.blank", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/handler/CreateChargeDefinitionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/handler/CreateChargeDefinitionCommandHandler.java
new file mode 100644
index 0000000..d77f37b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/handler/CreateChargeDefinitionCommandHandler.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.portfolio.charge.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.charge.service.ChargeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CHARGE", action = "CREATE")
+public class CreateChargeDefinitionCommandHandler implements NewCommandSourceHandler {
+
+    private final ChargeWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public CreateChargeDefinitionCommandHandler(final ChargeWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.createCharge(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/handler/DeleteChargeDefinitionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/handler/DeleteChargeDefinitionCommandHandler.java
new file mode 100644
index 0000000..d780776
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/handler/DeleteChargeDefinitionCommandHandler.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.portfolio.charge.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.charge.service.ChargeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CHARGE", action = "DELETE")
+public class DeleteChargeDefinitionCommandHandler implements NewCommandSourceHandler {
+
+    private final ChargeWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public DeleteChargeDefinitionCommandHandler(final ChargeWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.deleteCharge(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/handler/UpdateChargeDefinitionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/handler/UpdateChargeDefinitionCommandHandler.java
new file mode 100644
index 0000000..c5ba569
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/handler/UpdateChargeDefinitionCommandHandler.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.portfolio.charge.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.charge.service.ChargeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CHARGE", action = "UPDATE")
+public class UpdateChargeDefinitionCommandHandler implements NewCommandSourceHandler {
+
+    private final ChargeWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public UpdateChargeDefinitionCommandHandler(final ChargeWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.updateCharge(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java
new file mode 100755
index 0000000..c8a90e4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java
@@ -0,0 +1,343 @@
+/**
+ * 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.charge.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+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.apache.fineract.portfolio.charge.api.ChargesApiConstants;
+import org.apache.fineract.portfolio.charge.domain.ChargeAppliesTo;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.joda.time.MonthDay;
+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 ChargeDefinitionCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("name", "amount", "locale", "currencyCode",
+            "currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty",
+            "active", "chargePaymentMode", "feeOnMonthDay", "feeInterval", "monthDayFormat", "minCap", "maxCap", "feeFrequency",
+            ChargesApiConstants.glAccountIdParamName));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ChargeDefinitionCommandFromApiJsonDeserializer(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("charge");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Integer chargeAppliesTo = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeAppliesTo", element);
+        baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull();
+        if (chargeAppliesTo != null) {
+            baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).isOneOfTheseValues(ChargeAppliesTo.validValues());
+        }
+
+        final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeCalculationType", element);
+        baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull();
+
+        final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed("feeInterval", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("feeInterval").value(feeInterval).integerGreaterThanZero();
+
+        final Integer feeFrequency = this.fromApiJsonHelper.extractIntegerNamed("feeFrequency", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("feeFrequency").value(feeFrequency).inMinMaxRange(0, 3);
+
+        if (feeFrequency != null) {
+            baseDataValidator.reset().parameter("feeInterval").value(feeInterval).notNull();
+        }
+
+        final ChargeAppliesTo appliesTo = ChargeAppliesTo.fromInt(chargeAppliesTo);
+        if (appliesTo.isLoanCharge()) {
+            // loan applicable validation
+            final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeTimeType", element);
+            baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull();
+            if (chargeTimeType != null) {
+                baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType)
+                        .isOneOfTheseValues(ChargeTimeType.validLoanValues());
+            }
+
+            final Integer chargePaymentMode = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargePaymentMode", element);
+            baseDataValidator.reset().parameter("chargePaymentMode").value(chargePaymentMode).notNull()
+                    .isOneOfTheseValues(ChargePaymentMode.validValues());
+            if (chargePaymentMode != null) {
+                baseDataValidator.reset().parameter("chargePaymentMode").value(chargePaymentMode)
+                        .isOneOfTheseValues(ChargePaymentMode.validValues());
+            }
+
+            if (chargeCalculationType != null) {
+                baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
+                        .isOneOfTheseValues(ChargeCalculationType.validValuesForLoan());
+            }
+
+            if (chargeTimeType != null && chargeCalculationType != null) {
+                performChargeTimeNCalculationTypeValidation(baseDataValidator, chargeTimeType, chargeCalculationType);
+            }
+
+        } else if (appliesTo.isSavingsCharge()) {
+            // savings applicable validation
+            final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeTimeType", element);
+            baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull();
+            if (chargeTimeType != null) {
+                baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType)
+                        .isOneOfTheseValues(ChargeTimeType.validSavingsValues());
+            }
+
+            final ChargeTimeType ctt = ChargeTimeType.fromInt(chargeTimeType);
+
+            if (ctt.isWeeklyFee()) {
+                final String monthDay = this.fromApiJsonHelper.extractStringNamed("feeOnMonthDay", element);
+                baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay)
+                        .mustBeBlankWhenParameterProvidedIs("chargeTimeType", chargeTimeType);
+            }
+
+            if (ctt.isMonthlyFee()) {
+                final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed("feeOnMonthDay", element);
+                baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).notNull();
+
+                baseDataValidator.reset().parameter("feeInterval").value(feeInterval).notNull().inMinMaxRange(1, 12);
+            }
+
+            if (ctt.isAnnualFee()) {
+                final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed("feeOnMonthDay", element);
+                baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).notNull();
+            }
+
+            if (chargeCalculationType != null) {
+                baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
+                        .isOneOfTheseValues(ChargeCalculationType.validValuesForSavings());
+            }
+        } else if (appliesTo.isClientCharge()) {
+            // client applicable validation
+            final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeTimeType", element);
+            baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull();
+            if (chargeTimeType != null) {
+                baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType)
+                        .isOneOfTheseValues(ChargeTimeType.validClientValues());
+            }
+
+            if (chargeCalculationType != null) {
+                baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
+                        .isOneOfTheseValues(ChargeCalculationType.validValuesForClients());
+            }
+
+            // GL Account can be linked to clients
+            if (this.fromApiJsonHelper.parameterExists(ChargesApiConstants.glAccountIdParamName, element)) {
+                final Long glAccountId = this.fromApiJsonHelper.extractLongNamed(ChargesApiConstants.glAccountIdParamName, element);
+                baseDataValidator.reset().parameter(ChargesApiConstants.glAccountIdParamName).value(glAccountId).notNull()
+                        .longGreaterThanZero();
+            }
+
+        }
+
+        final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
+        baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
+
+        final String currencyCode = this.fromApiJsonHelper.extractStringNamed("currencyCode", element);
+        baseDataValidator.reset().parameter("currencyCode").value(currencyCode).notBlank().notExceedingLengthOf(3);
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element.getAsJsonObject());
+        baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
+
+        if (this.fromApiJsonHelper.parameterExists("penalty", element)) {
+            final Boolean penalty = this.fromApiJsonHelper.extractBooleanNamed("penalty", element);
+            baseDataValidator.reset().parameter("penalty").value(penalty).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("active", element)) {
+            final Boolean active = this.fromApiJsonHelper.extractBooleanNamed("active", element);
+            baseDataValidator.reset().parameter("active").value(active).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("minCap", element)) {
+            final BigDecimal minCap = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("minCap", element.getAsJsonObject());
+            baseDataValidator.reset().parameter("minCap").value(minCap).notNull().positiveAmount();
+        }
+        if (this.fromApiJsonHelper.parameterExists("maxCap", element)) {
+            final BigDecimal maxCap = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("maxCap", element.getAsJsonObject());
+            baseDataValidator.reset().parameter("maxCap").value(maxCap).notNull().positiveAmount();
+        }
+
+        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("charge");
+
+        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("currencyCode", element)) {
+            final String currencyCode = this.fromApiJsonHelper.extractStringNamed("currencyCode", element);
+            baseDataValidator.reset().parameter("currencyCode").value(currencyCode).notBlank().notExceedingLengthOf(3);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("amount", element)) {
+            final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element.getAsJsonObject());
+            baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("minCap", element)) {
+            final BigDecimal minCap = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("minCap", element.getAsJsonObject());
+            baseDataValidator.reset().parameter("minCap").value(minCap).notNull().positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("maxCap", element)) {
+            final BigDecimal maxCap = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("maxCap", element.getAsJsonObject());
+            baseDataValidator.reset().parameter("maxCap").value(maxCap).notNull().positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("chargeAppliesTo", element)) {
+            final Integer chargeAppliesTo = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeAppliesTo", element);
+            baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull()
+                    .isOneOfTheseValues(ChargeAppliesTo.validValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("chargeTimeType", element)) {
+
+            final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeTimeType", element);
+
+            final Collection<Object> validLoanValues = Arrays.asList(ChargeTimeType.validLoanValues());
+            final Collection<Object> validSavingsValues = Arrays.asList(ChargeTimeType.validSavingsValues());
+            final Collection<Object> validClientValues = Arrays.asList(ChargeTimeType.validClientValues());
+
+            final Collection<Object> allValidValues = new ArrayList<>(validLoanValues);
+            allValidValues.addAll(validSavingsValues);
+            allValidValues.addAll(validClientValues);
+
+            baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull()
+                    .isOneOfTheseValues(allValidValues.toArray(new Object[allValidValues.size()]));
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("feeOnMonthDay", element)) {
+            final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed("feeOnMonthDay", element);
+            baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("feeInterval", element)) {
+            final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed("feeInterval", element, Locale.getDefault());
+            baseDataValidator.reset().parameter("feeInterval").value(feeInterval).integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("chargeCalculationType", element)) {
+            final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", element,
+                    Locale.getDefault());
+            baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull().inMinMaxRange(1, 5);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("chargePaymentMode", element)) {
+            final Integer chargePaymentMode = this.fromApiJsonHelper.extractIntegerNamed("chargePaymentMode", element, Locale.getDefault());
+            baseDataValidator.reset().parameter("chargePaymentMode").value(chargePaymentMode).notNull().inMinMaxRange(0, 1);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("penalty", element)) {
+            final Boolean penalty = this.fromApiJsonHelper.extractBooleanNamed("penalty", element);
+            baseDataValidator.reset().parameter("penalty").value(penalty).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("active", element)) {
+            final Boolean active = this.fromApiJsonHelper.extractBooleanNamed("active", element);
+            baseDataValidator.reset().parameter("active").value(active).notNull();
+        }
+        if (this.fromApiJsonHelper.parameterExists("minCap", element)) {
+            final BigDecimal minCap = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("minCap", element.getAsJsonObject());
+            baseDataValidator.reset().parameter("minCap").value(minCap).notNull().positiveAmount();
+        }
+        if (this.fromApiJsonHelper.parameterExists("maxCap", element)) {
+            final BigDecimal maxCap = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("maxCap", element.getAsJsonObject());
+            baseDataValidator.reset().parameter("maxCap").value(maxCap).notNull().positiveAmount();
+        }
+        if (this.fromApiJsonHelper.parameterExists("feeFrequency", element)) {
+            final Integer feeFrequency = this.fromApiJsonHelper.extractIntegerNamed("feeFrequency", element, Locale.getDefault());
+            baseDataValidator.reset().parameter("feeFrequency").value(feeFrequency).inMinMaxRange(0, 3);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ChargesApiConstants.glAccountIdParamName, element)) {
+            final Long glAccountId = this.fromApiJsonHelper.extractLongNamed(ChargesApiConstants.glAccountIdParamName, element);
+            baseDataValidator.reset().parameter(ChargesApiConstants.glAccountIdParamName).value(glAccountId).notNull()
+                    .longGreaterThanZero();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateChargeTimeNCalculationType(Integer chargeTimeType, Integer ChargeCalculationType) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
+        performChargeTimeNCalculationTypeValidation(baseDataValidator, chargeTimeType, ChargeCalculationType);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void performChargeTimeNCalculationTypeValidation(DataValidatorBuilder baseDataValidator, final Integer chargeTimeType,
+            final Integer chargeCalculationType) {
+        if (chargeTimeType.equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue())) {
+            baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
+                    .isOneOfTheseValues(ChargeCalculationType.validValuesForTrancheDisbursement());
+        } else {
+            baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
+                    .isNotOneOfTheseValues(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue());
+        }
+    }
+
+    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/portfolio/charge/service/ChargeDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformService.java
new file mode 100644
index 0000000..18ff74b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformService.java
@@ -0,0 +1,45 @@
+/**
+ * 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.charge.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public interface ChargeDropdownReadPlatformService {
+
+    List<EnumOptionData> retrieveCalculationTypes();
+
+    List<EnumOptionData> retrieveApplicableToTypes();
+
+    List<EnumOptionData> retrieveCollectionTimeTypes();
+
+    List<EnumOptionData> retrivePaymentModes();
+
+    List<EnumOptionData> retrieveLoanCalculationTypes();
+
+    List<EnumOptionData> retrieveLoanCollectionTimeTypes();
+
+    List<EnumOptionData> retrieveSavingsCalculationTypes();
+
+    List<EnumOptionData> retrieveSavingsCollectionTimeTypes();
+    List<EnumOptionData> retrieveClientCalculationTypes();
+
+    List<EnumOptionData> retrieveClientCollectionTimeTypes();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java
new file mode 100755
index 0000000..8b5c68a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java
@@ -0,0 +1,117 @@
+/**
+ * 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.charge.service;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.charge.domain.ChargeAppliesTo;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+import static org.apache.fineract.portfolio.charge.service.ChargeEnumerations.*;
+
+@Service
+public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownReadPlatformService {
+
+    @Override
+    public List<EnumOptionData> retrieveCalculationTypes() {
+
+        return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT),
+                chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT),
+                chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST),
+                chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST),
+                chargeCalculationType(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT));
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveApplicableToTypes() {
+        final List<EnumOptionData> chargeAppliesToTypes = new ArrayList<>();
+        for (final ChargeAppliesTo chargeAppliesTo : ChargeAppliesTo.values()) {
+            if (ChargeAppliesTo.INVALID.equals(chargeAppliesTo)) {
+                continue;
+            }
+            chargeAppliesToTypes.add(ChargeEnumerations.chargeAppliesTo(chargeAppliesTo));
+        }
+        return chargeAppliesToTypes;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveCollectionTimeTypes() {
+        final List<EnumOptionData> chargeTimeTypes = new ArrayList<>();
+        for (final ChargeTimeType chargeTimeType : ChargeTimeType.values()) {
+            if (ChargeTimeType.INVALID.equals(chargeTimeType) || ChargeTimeType.SAVINGS_CLOSURE.equals(chargeTimeType)) {
+                continue;
+            }
+            chargeTimeTypes.add(ChargeEnumerations.chargeTimeType(chargeTimeType));
+        }
+        return chargeTimeTypes;
+    }
+
+    @Override
+    public List<EnumOptionData> retrivePaymentModes() {
+        return Arrays.asList(chargePaymentMode(ChargePaymentMode.REGULAR), chargePaymentMode(ChargePaymentMode.ACCOUNT_TRANSFER));
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveLoanCalculationTypes() {
+        return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT),
+                chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT),
+                chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST),
+                chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST),
+                chargeCalculationType(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT));
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveLoanCollectionTimeTypes() {
+        return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE),
+                chargeTimeType(ChargeTimeType.INSTALMENT_FEE), chargeTimeType(ChargeTimeType.OVERDUE_INSTALLMENT),
+                chargeTimeType(ChargeTimeType.TRANCHE_DISBURSEMENT));
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveSavingsCalculationTypes() {
+        return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT),
+                chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT));
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveSavingsCollectionTimeTypes() {
+        return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE), chargeTimeType(ChargeTimeType.SAVINGS_ACTIVATION),
+                // chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE),
+                chargeTimeType(ChargeTimeType.WITHDRAWAL_FEE), chargeTimeType(ChargeTimeType.ANNUAL_FEE),
+                chargeTimeType(ChargeTimeType.MONTHLY_FEE), chargeTimeType(ChargeTimeType.WEEKLY_FEE),
+                chargeTimeType(ChargeTimeType.OVERDRAFT_FEE));
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveClientCalculationTypes() {
+        return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT));
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveClientCollectionTimeTypes() {
+        return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE));
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeEnumerations.java
new file mode 100755
index 0000000..68cabfd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeEnumerations.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.portfolio.charge.service;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.charge.domain.ChargeAppliesTo;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+
+public class ChargeEnumerations {
+
+    public static EnumOptionData chargeTimeType(final int id) {
+        return chargeTimeType(ChargeTimeType.fromInt(id));
+    }
+
+    public static EnumOptionData chargeTimeType(final ChargeTimeType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case DISBURSEMENT:
+                optionData = new EnumOptionData(ChargeTimeType.DISBURSEMENT.getValue().longValue(), ChargeTimeType.DISBURSEMENT.getCode(),
+                        "Disbursement");
+            break;
+            case SPECIFIED_DUE_DATE:
+                optionData = new EnumOptionData(ChargeTimeType.SPECIFIED_DUE_DATE.getValue().longValue(),
+                        ChargeTimeType.SPECIFIED_DUE_DATE.getCode(), "Specified due date");
+            break;
+            case SAVINGS_ACTIVATION:
+                optionData = new EnumOptionData(ChargeTimeType.SAVINGS_ACTIVATION.getValue().longValue(),
+                        ChargeTimeType.SAVINGS_ACTIVATION.getCode(), "Savings Activation");
+            break;
+            case SAVINGS_CLOSURE:
+                optionData = new EnumOptionData(ChargeTimeType.SAVINGS_CLOSURE.getValue().longValue(),
+                        ChargeTimeType.SAVINGS_CLOSURE.getCode(), "Savings Closure");
+            break;
+            case WITHDRAWAL_FEE:
+                optionData = new EnumOptionData(ChargeTimeType.WITHDRAWAL_FEE.getValue().longValue(),
+                        ChargeTimeType.WITHDRAWAL_FEE.getCode(), "Withdrawal Fee");
+            break;
+            case ANNUAL_FEE:
+                optionData = new EnumOptionData(ChargeTimeType.ANNUAL_FEE.getValue().longValue(), ChargeTimeType.ANNUAL_FEE.getCode(),
+                        "Annual Fee");
+            break;
+            case MONTHLY_FEE:
+                optionData = new EnumOptionData(ChargeTimeType.MONTHLY_FEE.getValue().longValue(), ChargeTimeType.MONTHLY_FEE.getCode(),
+                        "Monthly Fee");
+            break;
+            case WEEKLY_FEE:
+                optionData = new EnumOptionData(ChargeTimeType.WEEKLY_FEE.getValue().longValue(), ChargeTimeType.WEEKLY_FEE.getCode(),
+                        "Weekly Fee");
+            break;
+            case INSTALMENT_FEE:
+                optionData = new EnumOptionData(ChargeTimeType.INSTALMENT_FEE.getValue().longValue(),
+                        ChargeTimeType.INSTALMENT_FEE.getCode(), "Installment Fee");
+            break;
+            case OVERDUE_INSTALLMENT:
+                optionData = new EnumOptionData(ChargeTimeType.OVERDUE_INSTALLMENT.getValue().longValue(),
+                        ChargeTimeType.OVERDUE_INSTALLMENT.getCode(), "Overdue Fees");
+            break;
+            case OVERDRAFT_FEE:
+                optionData = new EnumOptionData(ChargeTimeType.OVERDRAFT_FEE.getValue().longValue(), ChargeTimeType.OVERDRAFT_FEE.getCode(),
+                        "Overdraft Fee");
+            break;
+            case TRANCHE_DISBURSEMENT:
+                optionData = new EnumOptionData(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().longValue(),
+                        ChargeTimeType.TRANCHE_DISBURSEMENT.getCode(), "Tranche Disbursement");
+            break;
+            default:
+                optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData chargeAppliesTo(final int id) {
+        return chargeAppliesTo(ChargeAppliesTo.fromInt(id));
+    }
+
+    public static EnumOptionData chargeAppliesTo(final ChargeAppliesTo type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case LOAN:
+                optionData = new EnumOptionData(ChargeAppliesTo.LOAN.getValue().longValue(), ChargeAppliesTo.LOAN.getCode(), "Loan");
+            break;
+            case SAVINGS:
+                optionData = new EnumOptionData(ChargeAppliesTo.SAVINGS.getValue().longValue(), ChargeAppliesTo.SAVINGS.getCode(),
+                        "Savings");
+            break;
+            case CLIENT:
+                optionData = new EnumOptionData(ChargeAppliesTo.CLIENT.getValue().longValue(), ChargeAppliesTo.CLIENT.getCode(), "Client");
+            break;
+            default:
+                optionData = new EnumOptionData(ChargeAppliesTo.INVALID.getValue().longValue(), ChargeAppliesTo.INVALID.getCode(),
+                        "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData chargeCalculationType(final int id) {
+        return chargeCalculationType(ChargeCalculationType.fromInt(id));
+    }
+
+    public static EnumOptionData chargeCalculationType(final ChargeCalculationType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case FLAT:
+                optionData = new EnumOptionData(ChargeCalculationType.FLAT.getValue().longValue(), ChargeCalculationType.FLAT.getCode(),
+                        "Flat");
+            break;
+            case PERCENT_OF_AMOUNT:
+                optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue().longValue(),
+                        ChargeCalculationType.PERCENT_OF_AMOUNT.getCode(), "% Amount");
+            break;
+            case PERCENT_OF_AMOUNT_AND_INTEREST:
+                optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue().longValue(),
+                        ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getCode(), "% Loan Amount + Interest");
+            break;
+            case PERCENT_OF_INTEREST:
+                optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_INTEREST.getValue().longValue(),
+                        ChargeCalculationType.PERCENT_OF_INTEREST.getCode(), "% Interest");
+            break;
+            case PERCENT_OF_DISBURSEMENT_AMOUNT:
+            	optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue().longValue(),
+            	        ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getCode(), "% Disbursement Amount");
+            break;
+            default:
+                optionData = new EnumOptionData(ChargeCalculationType.INVALID.getValue().longValue(),
+                        ChargeCalculationType.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData chargePaymentMode(final int id) {
+        return chargePaymentMode(ChargePaymentMode.fromInt(id));
+    }
+
+    public static EnumOptionData chargePaymentMode(final ChargePaymentMode type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case ACCOUNT_TRANSFER:
+                optionData = new EnumOptionData(ChargePaymentMode.ACCOUNT_TRANSFER.getValue().longValue(),
+                        ChargePaymentMode.ACCOUNT_TRANSFER.getCode(), "Account transfer");
+            break;
+            default:
+                optionData = new EnumOptionData(ChargePaymentMode.REGULAR.getValue().longValue(), ChargePaymentMode.REGULAR.getCode(),
+                        "Regular");
+            break;
+        }
+        return optionData;
+    }
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformService.java
new file mode 100644
index 0000000..677e5d4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformService.java
@@ -0,0 +1,120 @@
+/**
+ * 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.charge.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+
+public interface ChargeReadPlatformService {
+
+    Collection<ChargeData> retrieveAllCharges();
+
+    Collection<ChargeData> retrieveAllChargesForCurrency(String currencyCode);
+
+    ChargeData retrieveCharge(Long chargeId);
+
+    ChargeData retrieveNewChargeDetails();
+
+    /**
+     * Returns all charges that can be applied to Cients
+     * 
+     * @return
+     */
+    Collection<ChargeData> retrieveAllChargesApplicableToClients();
+
+    /**
+     * Returns all Fees (excluding penalties) applicable for loans
+     * 
+     * @return
+     */
+    Collection<ChargeData> retrieveLoanApplicableFees();
+
+    /**
+     * Returns all charges applicable for a given loan account
+     * 
+     * @param excludeChargeTimes
+     *            Excludes Given List of Charge Types from the response
+     * @return
+     */
+    Collection<ChargeData> retrieveLoanAccountApplicableCharges(final Long loanId, ChargeTimeType[] excludeChargeTimes);
+
+    /**
+     * Returns all charges applicable for a given loan product (filter based on
+     * Currency of Selected Loan Product)
+     * 
+     * @param excludeChargeTimes
+     *            Excludes Given List of Charge Types from the response
+     * @return
+     */
+    Collection<ChargeData> retrieveLoanProductApplicableCharges(final Long loanProductId, ChargeTimeType[] excludeChargeTimes);
+
+    /**
+     * Returns all Penalties applicable for loans
+     * 
+     * @return
+     */
+    Collection<ChargeData> retrieveLoanApplicablePenalties();
+
+    /**
+     * Returns all Charges associated with a given Loan Product
+     * 
+     * @param loanProductId
+     * @return
+     */
+    Collection<ChargeData> retrieveLoanProductCharges(Long loanProductId);
+
+    /**
+     * Returns all charges applicable for a given loan product
+     * 
+     * @param loanProductId
+     * @param chargeTime
+     *            Filters based on the type of the charge to be returned
+     * @return
+     */
+    Collection<ChargeData> retrieveLoanProductCharges(Long loanProductId, ChargeTimeType chargeTime);
+
+    /**
+     * Returns all charges applicable for savings
+     * 
+     * @param feeChargesOnly
+     * @return
+     */
+    Collection<ChargeData> retrieveSavingsProductApplicableCharges(boolean feeChargesOnly);
+
+    /**
+     * Returns all penalties applicable for savings
+     * 
+     * @return
+     */
+    Collection<ChargeData> retrieveSavingsApplicablePenalties();
+
+    /**
+     * Returns all charges applicable for a given savings product
+     * 
+     * @param savingsProductId
+     * @return
+     */
+    Collection<ChargeData> retrieveSavingsProductCharges(Long savingsProductId);
+
+    /** Retrieve savings account charges **/
+    Collection<ChargeData> retrieveSavingsAccountApplicableCharges(Long savingsId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java
new file mode 100755
index 0000000..a1ae0bc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java
@@ -0,0 +1,406 @@
+/**
+ * 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.charge.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+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.entityaccess.domain.FineractEntityType;
+import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.domain.ChargeAppliesTo;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.exception.ChargeNotFoundException;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author vishwas
+ * 
+ */
+@Service
+public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final CurrencyReadPlatformService currencyReadPlatformService;
+    private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService;
+    private final DropdownReadPlatformService dropdownReadPlatformService;
+    private final FineractEntityAccessUtil fineractEntityAccessUtil;
+    private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
+    private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+    @Autowired
+    public ChargeReadPlatformServiceImpl(final CurrencyReadPlatformService currencyReadPlatformService,
+            final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource,
+            final DropdownReadPlatformService dropdownReadPlatformService, final FineractEntityAccessUtil fineractEntityAccessUtil,
+            final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService) {
+        this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.currencyReadPlatformService = currencyReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.fineractEntityAccessUtil = fineractEntityAccessUtil;
+        this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
+        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
+    }
+
+    @Override
+    @Cacheable(value = "charges", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('ch')")
+    public Collection<ChargeData> retrieveAllCharges() {
+        final ChargeMapper rm = new ChargeMapper();
+
+        String sql = "select " + rm.chargeSchema() + " where c.is_deleted=0 ";
+
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+
+        sql += " order by c.name ";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveAllChargesForCurrency(String currencyCode) {
+        final ChargeMapper rm = new ChargeMapper();
+
+        String sql = "select " + rm.chargeSchema() + " where c.is_deleted=0 and c.currency_code='" + currencyCode + "' ";
+
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+
+        sql += " order by c.name ";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public ChargeData retrieveCharge(final Long chargeId) {
+        try {
+            final ChargeMapper rm = new ChargeMapper();
+
+            String sql = "select " + rm.chargeSchema() + " where c.id = ? and c.is_deleted=0 ";
+
+            sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { chargeId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new ChargeNotFoundException(chargeId);
+        }
+    }
+
+    @Override
+    public ChargeData retrieveNewChargeDetails() {
+
+        final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
+        final List<EnumOptionData> allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes();
+        final List<EnumOptionData> allowedChargeAppliesToOptions = this.chargeDropdownReadPlatformService.retrieveApplicableToTypes();
+        final List<EnumOptionData> allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes();
+        final List<EnumOptionData> chargePaymentOptions = this.chargeDropdownReadPlatformService.retrivePaymentModes();
+        final List<EnumOptionData> loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveLoanCalculationTypes();
+        final List<EnumOptionData> loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes();
+        final List<EnumOptionData> savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveSavingsCalculationTypes();
+        final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveSavingsCollectionTimeTypes();
+        final List<EnumOptionData> clientChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveClientCalculationTypes();
+        final List<EnumOptionData> clientChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveClientCollectionTimeTypes();
+        final List<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
+        final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = this.accountingDropdownReadPlatformService
+                .retrieveAccountMappingOptionsForCharges();
+
+        return ChargeData.template(currencyOptions, allowedChargeCalculationTypeOptions, allowedChargeAppliesToOptions,
+                allowedChargeTimeOptions, chargePaymentOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
+                savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions,
+                clientChargeTimeTypeOptions, feeFrequencyOptions, incomeOrLiabilityAccountOptions);
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveLoanProductCharges(final Long loanProductId) {
+        final ChargeMapper rm = new ChargeMapper();
+
+        String sql = "select " + rm.loanProductChargeSchema() + " where c.is_deleted=0 and c.is_active=1 and plc.product_loan_id=? ";
+
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { loanProductId });
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveLoanProductCharges(final Long loanProductId, final ChargeTimeType chargeTime) {
+
+        final ChargeMapper rm = new ChargeMapper();
+
+        String sql = "select " + rm.loanProductChargeSchema()
+                + " where c.is_deleted=0 and c.is_active=1 and plc.product_loan_id=? and c.charge_time_enum=? ";
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { loanProductId, chargeTime.getValue() });
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveLoanApplicableFees() {
+        final ChargeMapper rm = new ChargeMapper();
+        Object[] params = new Object[] { ChargeAppliesTo.LOAN.getValue() };
+        String sql = "select " + rm.chargeSchema()
+                + " where c.is_deleted=0 and c.is_active=1 and c.is_penalty=0 and c.charge_applies_to_enum=? ";
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+        sql += " order by c.name ";
+
+        return this.jdbcTemplate.query(sql, rm, params);
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveLoanAccountApplicableCharges(final Long loanId, ChargeTimeType[] excludeChargeTimes) {
+        final ChargeMapper rm = new ChargeMapper();
+        StringBuilder excludeClause = new StringBuilder("");
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("loanId", loanId);
+        paramMap.put("chargeAppliesTo", ChargeAppliesTo.LOAN.getValue());
+        processChargeExclusionsForLoans(excludeChargeTimes, excludeClause);
+        String sql = "select " + rm.chargeSchema() + " join m_loan la on la.currency_code = c.currency_code" + " where la.id=:loanId"
+                + " and c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=:chargeAppliesTo" + excludeClause + " ";
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+        sql += " order by c.name ";
+        return this.namedParameterJdbcTemplate.query(sql, paramMap, rm);
+    }
+
+    /**
+     * @param excludeChargeTimes
+     * @param excludeClause
+     * @param params
+     * @return
+     */
+    private void processChargeExclusionsForLoans(ChargeTimeType[] excludeChargeTimes, StringBuilder excludeClause) {
+        if (excludeChargeTimes != null && excludeChargeTimes.length > 0) {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < excludeChargeTimes.length; i++) {
+                if (i != 0) {
+                    sb.append(",");
+                }
+                sb.append(excludeChargeTimes[i].getValue());
+            }
+            excludeClause = excludeClause.append(" and c.charge_time_enum not in(" + sb.toString() + ") ");
+        }
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveLoanProductApplicableCharges(final Long loanProductId, ChargeTimeType[] excludeChargeTimes) {
+        final ChargeMapper rm = new ChargeMapper();
+        StringBuilder excludeClause = new StringBuilder("");
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("productId", loanProductId);
+        paramMap.put("chargeAppliesTo", ChargeAppliesTo.LOAN.getValue());
+        processChargeExclusionsForLoans(excludeChargeTimes, excludeClause);
+        String sql = "select " + rm.chargeSchema() + " join m_product_loan lp on lp.currency_code = c.currency_code"
+                + " where lp.id=:productId" + " and c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=:chargeAppliesTo"
+                + excludeClause + " ";
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+        sql += " order by c.name ";
+
+        return this.namedParameterJdbcTemplate.query(sql, paramMap, rm);
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveLoanApplicablePenalties() {
+        final ChargeMapper rm = new ChargeMapper();
+
+        String sql = "select " + rm.chargeSchema()
+                + " where c.is_deleted=0 and c.is_active=1 and c.is_penalty=1 and c.charge_applies_to_enum=? ";
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+        sql += " order by c.name ";
+        return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.LOAN.getValue() });
+    }
+
+    private String addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled() {
+
+        String sql = "";
+
+        // Check if branch specific products are enabled. If yes, fetch only
+        // charges mapped to current user's office
+        String inClause = fineractEntityAccessUtil.getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.CHARGE);
+        if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+            sql += " and c.id in ( " + inClause + " ) ";
+        }
+
+        return sql;
+    }
+
+    private static final class ChargeMapper implements RowMapper<ChargeData> {
+
+        public String chargeSchema() {
+            return "c.id as id, c.name as name, c.amount as amount, c.currency_code as currencyCode, "
+                    + "c.charge_applies_to_enum as chargeAppliesTo, c.charge_time_enum as chargeTime, "
+                    + "c.charge_payment_mode_enum as chargePaymentMode, "
+                    + "c.charge_calculation_enum as chargeCalculation, c.is_penalty as penalty, "
+                    + "c.is_active as active, oc.name as currencyName, oc.decimal_places as currencyDecimalPlaces, "
+                    + "oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+                    + "oc.internationalized_name_code as currencyNameCode, c.fee_on_day as feeOnDay, c.fee_on_month as feeOnMonth, "
+                    + "c.fee_interval as feeInterval, c.fee_frequency as feeFrequency,c.min_cap as minCap,c.max_cap as maxCap, "
+                    + "c.income_or_liability_account_id as glAccountId , acc.name as glAccountName, acc.gl_code as glCode "
+                    + "from m_charge c " + "join m_organisation_currency oc on c.currency_code = oc.code "
+                    + " LEFT JOIN acc_gl_account acc on acc.id = c.income_or_liability_account_id ";
+        }
+
+        public String loanProductChargeSchema() {
+            return chargeSchema() + " join m_product_loan_charge plc on plc.charge_id = c.id";
+        }
+
+        public String savingsProductChargeSchema() {
+            return chargeSchema() + " join m_savings_product_charge spc on spc.charge_id = c.id";
+        }
+
+        @Override
+        public ChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final BigDecimal amount = rs.getBigDecimal("amount");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final int chargeAppliesTo = rs.getInt("chargeAppliesTo");
+            final EnumOptionData chargeAppliesToType = ChargeEnumerations.chargeAppliesTo(chargeAppliesTo);
+
+            final int chargeTime = rs.getInt("chargeTime");
+            final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
+
+            final int chargeCalculation = rs.getInt("chargeCalculation");
+            final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
+
+            final int paymentMode = rs.getInt("chargePaymentMode");
+            final EnumOptionData chargePaymentMode = ChargeEnumerations.chargePaymentMode(paymentMode);
+
+            final boolean penalty = rs.getBoolean("penalty");
+            final boolean active = rs.getBoolean("active");
+
+            final Integer feeInterval = JdbcSupport.getInteger(rs, "feeInterval");
+            EnumOptionData feeFrequencyType = null;
+            final Integer feeFrequency = JdbcSupport.getInteger(rs, "feeFrequency");
+            if (feeFrequency != null) {
+                feeFrequencyType = CommonEnumerations.termFrequencyType(feeFrequency, "feeFrequency");
+            }
+            MonthDay feeOnMonthDay = null;
+            final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth");
+            final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay");
+            if (feeOnDay != null && feeOnMonth != null) {
+                feeOnMonthDay = new MonthDay(feeOnMonth, feeOnDay);
+            }
+            final BigDecimal minCap = rs.getBigDecimal("minCap");
+            final BigDecimal maxCap = rs.getBigDecimal("maxCap");
+
+            // extract GL Account
+            final Long glAccountId = JdbcSupport.getLong(rs, "glAccountId");
+            final String glAccountName = rs.getString("glAccountName");
+            final String glCode = rs.getString("glCode");
+            GLAccountData glAccountData = null;
+            if (glAccountId != null) {
+                glAccountData = new GLAccountData(glAccountId, glAccountName, glCode);
+            }
+
+            return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType,
+                    chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, minCap, maxCap, feeFrequencyType, glAccountData);
+        }
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveSavingsProductApplicableCharges(final boolean feeChargesOnly) {
+        final ChargeMapper rm = new ChargeMapper();
+
+        String sql = "select " + rm.chargeSchema() + " where c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=? ";
+        if (feeChargesOnly) {
+            sql = "select " + rm.chargeSchema()
+                    + " where c.is_deleted=0 and c.is_active=1 and c.is_penalty=0 and c.charge_applies_to_enum=? ";
+        }
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+        sql += " order by c.name ";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.SAVINGS.getValue() });
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveSavingsApplicablePenalties() {
+        final ChargeMapper rm = new ChargeMapper();
+
+        String sql = "select " + rm.chargeSchema()
+                + " where c.is_deleted=0 and c.is_active=1 and c.is_penalty=1 and c.charge_applies_to_enum=? ";
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+        sql += " order by c.name ";
+        return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.SAVINGS.getValue() });
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveSavingsProductCharges(final Long savingsProductId) {
+        final ChargeMapper rm = new ChargeMapper();
+
+        String sql = "select " + rm.savingsProductChargeSchema() + " where c.is_deleted=0 and c.is_active=1 and spc.savings_product_id=? ";
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { savingsProductId });
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveSavingsAccountApplicableCharges(Long savingsAccountId) {
+
+        final ChargeMapper rm = new ChargeMapper();
+
+        String sql = "select " + rm.chargeSchema() + " join m_savings_account sa on sa.currency_code = c.currency_code"
+                + " where c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=? " + " and sa.account_no = ?";
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.SAVINGS.getValue(), savingsAccountId });
+
+    }
+
+    @Override
+    public Collection<ChargeData> retrieveAllChargesApplicableToClients() {
+        final ChargeMapper rm = new ChargeMapper();
+        String sql = "select " + rm.chargeSchema() + " where c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=? ";
+        sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+        sql += " order by c.name ";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.CLIENT.getValue() });
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformService.java
new file mode 100644
index 0000000..c157173
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.charge.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface ChargeWritePlatformService {
+
+    CommandProcessingResult createCharge(JsonCommand command);
+
+    CommandProcessingResult updateCharge(Long chargeId, JsonCommand command);
+
+    CommandProcessingResult deleteCharge(Long chargeId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..3327701
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,242 @@
+/**
+ * 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.charge.service;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType;
+import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.charge.api.ChargesApiConstants;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepository;
+import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeDeletedException;
+import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeUpdatedException;
+import org.apache.fineract.portfolio.charge.exception.ChargeNotFoundException;
+import org.apache.fineract.portfolio.charge.serialization.ChargeDefinitionCommandFromApiJsonDeserializer;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ChargeWritePlatformServiceJpaRepositoryImpl.class);
+    private final PlatformSecurityContext context;
+    private final ChargeDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final JdbcTemplate jdbcTemplate;
+    private final DataSource dataSource;
+    private final ChargeRepository chargeRepository;
+    private final LoanProductRepository loanProductRepository;
+    private final FineractEntityAccessUtil fineractEntityAccessUtil;
+    private final GLAccountRepositoryWrapper gLAccountRepository;
+
+    @Autowired
+    public ChargeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final ChargeDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer, final ChargeRepository chargeRepository,
+            final LoanProductRepository loanProductRepository, final RoutingDataSource dataSource,
+            final FineractEntityAccessUtil fineractEntityAccessUtil, final GLAccountRepositoryWrapper glAccountRepository) {
+        this.context = context;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.dataSource = dataSource;
+        this.jdbcTemplate = new JdbcTemplate(this.dataSource);
+        this.chargeRepository = chargeRepository;
+        this.loanProductRepository = loanProductRepository;
+        this.fineractEntityAccessUtil = fineractEntityAccessUtil;
+        this.gLAccountRepository = glAccountRepository;
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "charges", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('ch')")
+    public CommandProcessingResult createCharge(final JsonCommand command) {
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            // Retrieve linked GLAccount for Client charges (if present)
+            final Long glAccountId = command.longValueOfParameterNamed(ChargesApiConstants.glAccountIdParamName);
+
+            GLAccount glAccount = null;
+            if (glAccountId != null) {
+                glAccount = this.gLAccountRepository.findOneWithNotFoundDetection(glAccountId);
+            }
+
+            final Charge charge = Charge.fromJson(command, glAccount);
+            this.chargeRepository.save(charge);
+
+            // check if the office specific products are enabled. If yes, then
+            // save this savings product against a specific office
+            // i.e. this savings product is specific for this office.
+            fineractEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice(FineractEntityAccessType.OFFICE_ACCESS_TO_CHARGES,
+                    FineractEntityType.CHARGE, charge.getId());
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(charge.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "charges", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('ch')")
+    public CommandProcessingResult updateCharge(final Long chargeId, final JsonCommand command) {
+
+        try {
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final Charge chargeForUpdate = this.chargeRepository.findOne(chargeId);
+            if (chargeForUpdate == null) { throw new ChargeNotFoundException(chargeId); }
+
+            final Map<String, Object> changes = chargeForUpdate.update(command);
+
+            this.fromApiJsonDeserializer.validateChargeTimeNCalculationType(chargeForUpdate.getChargeTimeType(),
+            																	chargeForUpdate.getChargeCalculation());
+
+            // MIFOSX-900: Check if the Charge has been active before and now is
+            // deactivated:
+            if (changes.containsKey("active")) {
+                // IF the key exists then it has changed (otherwise it would
+                // have been filtered), so check current state:
+                if (!chargeForUpdate.isActive()) {
+                    // TODO: Change this function to only check the mappings!!!
+                    final Boolean isChargeExistWithLoans = isAnyLoanProductsAssociateWithThisCharge(chargeId);
+                    final Boolean isChargeExistWithSavings = isAnySavingsProductsAssociateWithThisCharge(chargeId);
+
+                    if (isChargeExistWithLoans || isChargeExistWithSavings) { throw new ChargeCannotBeUpdatedException(
+                            "error.msg.charge.cannot.be.updated.it.is.used.in.loan", "This charge cannot be updated, it is used in loan"); }
+                }
+            } else if ((changes.containsKey("feeFrequency") || changes.containsKey("feeInterval")) && chargeForUpdate.isLoanCharge()) {
+                final Boolean isChargeExistWithLoans = isAnyLoanProductsAssociateWithThisCharge(chargeId);
+                if (isChargeExistWithLoans) { throw new ChargeCannotBeUpdatedException(
+                        "error.msg.charge.frequency.cannot.be.updated.it.is.used.in.loan",
+                        "This charge frequency cannot be updated, it is used in loan"); }
+            } 
+
+            // Has account Id been changed ?
+            if (changes.containsKey(ChargesApiConstants.glAccountIdParamName)) {
+                final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.glAccountIdParamName);
+                GLAccount newIncomeAccount = null;
+                if (newValue != null) {
+                    newIncomeAccount = this.gLAccountRepository.findOneWithNotFoundDetection(newValue);
+                }
+                chargeForUpdate.setAccount(newIncomeAccount);
+            }
+
+            if (!changes.isEmpty()) {
+                this.chargeRepository.save(chargeForUpdate);
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(chargeId).with(changes).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "charges", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('ch')")
+    public CommandProcessingResult deleteCharge(final Long chargeId) {
+
+        final Charge chargeForDelete = this.chargeRepository.findOne(chargeId);
+        if (chargeForDelete == null || chargeForDelete.isDeleted()) { throw new ChargeNotFoundException(chargeId); }
+
+        final Collection<LoanProduct> loanProducts = this.loanProductRepository.retrieveLoanProductsByChargeId(chargeId);
+        final Boolean isChargeExistWithLoans = isAnyLoansAssociateWithThisCharge(chargeId);
+        final Boolean isChargeExistWithSavings = isAnySavingsAssociateWithThisCharge(chargeId);
+
+        // TODO: Change error messages around:
+        if (!loanProducts.isEmpty() || isChargeExistWithLoans
+                || isChargeExistWithSavings) { throw new ChargeCannotBeDeletedException(
+                        "error.msg.charge.cannot.be.deleted.it.is.already.used.in.loan",
+                        "This charge cannot be deleted, it is already used in loan"); }
+
+        chargeForDelete.delete();
+
+        this.chargeRepository.save(chargeForDelete);
+
+        return new CommandProcessingResultBuilder().withEntityId(chargeForDelete.getId()).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("name")) {
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.charge.duplicate.name", "Charge with name `" + name + "` already exists",
+                    "name", name);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.charge.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+
+    private boolean isAnyLoansAssociateWithThisCharge(final Long chargeId) {
+
+        final String sql = "select if((exists (select 1 from m_loan_charge lc where lc.charge_id = ?)) = 1, 'true', 'false')";
+        final String isLoansUsingCharge = this.jdbcTemplate.queryForObject(sql, String.class, new Object[] { chargeId });
+        return new Boolean(isLoansUsingCharge);
+    }
+
+    private boolean isAnySavingsAssociateWithThisCharge(final Long chargeId) {
+
+        final String sql = "select if((exists (select 1 from m_savings_account_charge sc where sc.charge_id = ?)) = 1, 'true', 'false')";
+        final String isSavingsUsingCharge = this.jdbcTemplate.queryForObject(sql, String.class, new Object[] { chargeId });
+        return new Boolean(isSavingsUsingCharge);
+    }
+
+    private boolean isAnyLoanProductsAssociateWithThisCharge(final Long chargeId) {
+
+        final String sql = "select if((exists (select 1 from m_product_loan_charge lc where lc.charge_id = ?)) = 1, 'true', 'false')";
+        final String isLoansUsingCharge = this.jdbcTemplate.queryForObject(sql, String.class, new Object[] { chargeId });
+        return new Boolean(isLoansUsingCharge);
+    }
+
+    private boolean isAnySavingsProductsAssociateWithThisCharge(final Long chargeId) {
+
+        final String sql = "select if((exists (select 1 from m_savings_product_charge sc where sc.charge_id = ?)) = 1, 'true', 'false')";
+        final String isSavingsUsingCharge = this.jdbcTemplate.queryForObject(sql, String.class, new Object[] { chargeId });
+        return new Boolean(isSavingsUsingCharge);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientApiConstants.java
new file mode 100644
index 0000000..861358a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientApiConstants.java
@@ -0,0 +1,242 @@
+/**
+ * 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.client.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.client.data.ClientData;
+
+public class ClientApiConstants {
+
+    public static final String CLIENT_RESOURCE_NAME = "client";
+    public static final String CLIENT_CHARGES_RESOURCE_NAME = "CLIENTCHARGE";
+
+    // Client Charge Action Names
+    public static final String CLIENT_CHARGE_ACTION_CREATE = "CREATE";
+    public static final String CLIENT_CHARGE_ACTION_DELETE = "DELETE";
+    public static final String CLIENT_CHARGE_ACTION_WAIVE = "WAIVE";
+    public static final String CLIENT_CHARGE_ACTION_PAY = "PAY";
+    public static final String CLIENT_CHARGE_ACTION_INACTIVATE = "INACTIVATE";
+
+    // Client charge associations and query parameters
+    public static final String CLIENT_CHARGE_QUERY_PARAM_STATUS = "chargeStatus";
+    public static final String CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL = "all";
+    public static final String CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE = "active";
+    public static final String CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE = "inactive";
+    public static final String CLIENT_CHARGE_ASSOCIATIONS_TRANSACTIONS = "transactions";
+
+    // Client transaction action names
+    public static final String CLIENT_TRANSACTION_ACTION_READ = "READTRANSACTION";
+    public static final String CLIENT_TRANSACTION_ACTION_UNDO = "UNDOTRANSACTION";
+
+    // Commands
+    public static final String CLIENT_CHARGE_COMMAND_WAIVE_CHARGE = "waive";
+    public static final String CLIENT_CHARGE_COMMAND_PAY_CHARGE = "paycharge";
+    public static final String CLIENT_CHARGE_COMMAND_INACTIVATE_CHARGE = "inactivate";
+    public static final String CLIENT_TRANSACTION_COMMAND_UNDO = "undo";
+
+    public static final String CLIENT_CLOSURE_REASON = "ClientClosureReason";
+    public static final String CLIENT_ACTION_REASON = "ClientActionReason";
+    public static final String CLIENT_REJECT_REASON = "ClientRejectReason";
+    public static final String CLIENT_WITHDRAW_REASON = "ClientWithdrawReason";
+
+    public static final String GENDER = "Gender";
+    public static final String CLIENT_TYPE = "ClientType";
+    public static final String CLIENT_CLASSIFICATION = "ClientClassification";
+    
+    public static final String CLIENT_NON_PERSON_CONSTITUTION = "Constitution";
+    public static final String CLIENT_NON_PERSON_MAIN_BUSINESS_LINE = "Main Business Line";
+    
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+    
+    public static final String CLIENT_TYPE_INDIVIDUAL = "Individual";
+
+    // request parameters
+    public static final String idParamName = "id";
+    public static final String groupIdParamName = "groupId";
+    public static final String accountNoParamName = "accountNo";
+    public static final String externalIdParamName = "externalId";
+    public static final String mobileNoParamName = "mobileNo";
+    public static final String firstnameParamName = "firstname";
+    public static final String middlenameParamName = "middlename";
+    public static final String lastnameParamName = "lastname";
+    public static final String fullnameParamName = "fullname";
+    public static final String displaynameParamName = "displayname";
+    public static final String officeIdParamName = "officeId";
+    public static final String transferOfficeIdParamName = "transferOfficeIdParamName";
+    public static final String activeParamName = "active";
+    public static final String activationDateParamName = "activationDate";
+    public static final String reactivationDateParamName = "reactivationDate";
+    public static final String staffIdParamName = "staffId";
+    public static final String closureDateParamName = "closureDate";
+    public static final String closureReasonIdParamName = "closureReasonId";
+
+    public static final String rejectionDateParamName = "rejectionDate";
+    public static final String rejectionReasonIdParamName = "rejectionReasonId";
+    public static final String withdrawalDateParamName = "withdrawalDate";
+    public static final String withdrawalReasonIdParamName = "withdrawalReasonId";
+
+    public static final String submittedOnDateParamName = "submittedOnDate";
+    public static final String savingsProductIdParamName = "savingsProductId";
+    public static final String savingsAccountIdParamName = "savingsAccountId";
+    public static final String dateOfBirthParamName = "dateOfBirth";
+    public static final String genderIdParamName = "genderId";
+    public static final String genderParamName = "gender";
+    public static final String clientTypeIdParamName = "clientTypeId";
+    public static final String clientTypeParamName = "clientType";
+    public static final String clientClassificationIdParamName = "clientClassificationId";
+    public static final String clientClassificationParamName = "clientClassification";
+    public static final String legalFormIdParamName = "legalFormId";
+    public static final String legalFormParamName = "legalForm";
+    // request parameters for payment details
+    public static final String paymentTypeIdParamName = "paymentTypeId";
+    public static final String transactionAccountNumberParamName = "accountNumber";
+    public static final String checkNumberParamName = "checkNumber";
+    public static final String routingCodeParamName = "routingCode";
+    public static final String receiptNumberParamName = "receiptNumber";
+    public static final String bankNumberParamName = "bankNumber";
+    
+    //request parameters for client non person
+    public static final String clientNonPersonDetailsParamName = "clientNonPersonDetails";
+    public static final String incorpNumberParamName = "incorpNumber";
+    public static final String remarksParamName = "remarks";
+    public static final String incorpValidityTillParamName = "incorpValidityTillDate";
+    public static final String constitutionIdParamName = "constitutionId";
+    public static final String mainBusinessLineIdParamName = "mainBusinessLineId";
+    
+
+    // response parameters
+    public static final String statusParamName = "status";
+    public static final String hierarchyParamName = "hierarchy";
+    public static final String displayNameParamName = "displayName";
+    public static final String officeNameParamName = "officeName";
+    public static final String staffNameParamName = "staffName";
+    public static final String trasnferOfficeNameParamName = "transferOfficeName";
+    public static final String transferToOfficeNameParamName = "transferToOfficeName";
+    public static final String transferToOfficeIdParamName = "transferToOfficeId";
+    public static final String imageKeyParamName = "imageKey";
+    public static final String imageIdParamName = "imageId";
+    public static final String imagePresentParamName = "imagePresent";
+    public static final String timelineParamName = "timeline";
+
+    // client charges response parameters
+    public static final String chargeIdParamName = "chargeId";
+    public static final String clientIdParamName = "clientId";
+    public static final String chargesParamName = "charges";
+    public static final String chargeNameParamName = "name";
+    public static final String penaltyParamName = "penalty";
+    public static final String chargeTimeTypeParamName = "chargeTimeType";
+    public static final String dueAsOfDateParamName = "dueDate";
+    public static final String transactionDateParamName = "transactionDate";
+    public static final String chargeCalculationTypeParamName = "chargeCalculationType";
+    public static final String currencyParamName = "currency";
+    public static final String amountWaivedParamName = "amountWaived";
+    public static final String amountWrittenOffParamName = "amountWrittenOff";
+    public static final String amountOutstandingParamName = "amountOutstanding";
+    public static final String amountOrPercentageParamName = "amountOrPercentage";
+    public static final String amountParamName = "amount";
+    public static final String amountPaidParamName = "amountPaid";
+    public static final String chargeOptionsParamName = "chargeOptions";
+    public static final String transactionsParamName = "transactions";
+
+    // client transactions response parameters
+    public static final String transactionAmountParamName = "transactionAmount";
+    public static final String paymentDetailDataParamName = "paymentDetailData";
+    public static final String reversedParamName = "reversed";
+    public static final String dateParamName = "date";
+    private static final String transactionTypeParamName = "type";
+    private static final String transactionCurrencyParamName = "currency";
+
+    // associations related part of response
+    public static final String groupsParamName = "groups";
+
+    // template related part of response
+    public static final String officeOptionsParamName = "officeOptions";
+    public static final String staffOptionsParamName = "staffOptions";
+
+    public static final Set<String> CLIENT_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(localeParamName, dateFormatParamName, groupIdParamName, accountNoParamName, externalIdParamName,
+                    mobileNoParamName, firstnameParamName, middlenameParamName, lastnameParamName, fullnameParamName, officeIdParamName,
+                    activeParamName, activationDateParamName, staffIdParamName, submittedOnDateParamName, savingsProductIdParamName,
+                    dateOfBirthParamName, genderIdParamName, clientTypeIdParamName, clientClassificationIdParamName, 
+                    clientNonPersonDetailsParamName, displaynameParamName, legalFormIdParamName));
+    
+    public static final Set<String> CLIENT_NON_PERSON_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(localeParamName, dateFormatParamName, incorpNumberParamName, remarksParamName, incorpValidityTillParamName, 
+            		constitutionIdParamName, mainBusinessLineIdParamName));
+
+    public static final Set<String> CLIENT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, accountNoParamName, externalIdParamName, mobileNoParamName, firstnameParamName, middlenameParamName,
+
+    lastnameParamName, fullnameParamName, activeParamName, activationDateParamName, staffIdParamName, savingsProductIdParamName,
+            dateOfBirthParamName, genderIdParamName, clientTypeIdParamName, clientClassificationIdParamName, submittedOnDateParamName, 
+            clientNonPersonDetailsParamName, displaynameParamName, legalFormIdParamName));
+    
+    public static final Set<String> CLIENT_NON_PERSON_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, incorpNumberParamName, remarksParamName, incorpValidityTillParamName, 
+    		constitutionIdParamName, mainBusinessLineIdParamName));
+
+    /**
+     * These parameters will match the class level parameters of
+     * {@link ClientData}. Where possible, we try to get response parameters to
+     * match those of request parameters.
+     */
+    public static final Set<String> CLIENT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, accountNoParamName,
+            externalIdParamName, statusParamName, activeParamName, activationDateParamName, firstnameParamName, middlenameParamName,
+            lastnameParamName, fullnameParamName, displayNameParamName, mobileNoParamName, officeIdParamName, officeNameParamName,
+            transferToOfficeIdParamName, transferToOfficeNameParamName, hierarchyParamName, imageIdParamName, imagePresentParamName,
+            staffIdParamName, staffNameParamName, timelineParamName, groupsParamName, officeOptionsParamName, staffOptionsParamName,
+            dateOfBirthParamName, genderParamName, clientTypeParamName, clientClassificationParamName, legalFormParamName, 
+            clientNonPersonDetailsParamName));
+
+    public static final Set<String> ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(localeParamName, dateFormatParamName, activationDateParamName));
+    public static final Set<String> REACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(localeParamName, dateFormatParamName, reactivationDateParamName));
+
+    public static final Set<String> CLIENT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(localeParamName, dateFormatParamName, closureDateParamName, closureReasonIdParamName));
+
+    public static final Set<String> CLIENT_REJECT_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(localeParamName, dateFormatParamName, rejectionDateParamName, rejectionReasonIdParamName));
+
+    public static final Set<String> CLIENT_WITHDRAW_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(localeParamName, dateFormatParamName, withdrawalDateParamName, withdrawalReasonIdParamName));
+
+    public static final Set<String> CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(chargeIdParamName,
+            clientIdParamName, chargeNameParamName, penaltyParamName, chargeTimeTypeParamName, dueAsOfDateParamName,
+            chargeCalculationTypeParamName, currencyParamName, amountWaivedParamName, amountWrittenOffParamName, amountOutstandingParamName,
+            amountOrPercentageParamName, amountParamName, amountPaidParamName, chargeOptionsParamName, transactionsParamName));
+
+    public static final Set<String> CLIENT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
+
+    public static final Set<String> CLIENT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(amountParamName,
+            transactionDateParamName, dateFormatParamName, localeParamName, paymentTypeIdParamName, transactionAccountNumberParamName,
+            checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName));
+
+    public static final Set<String> CLIENT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
+            transactionAmountParamName, paymentDetailDataParamName, reversedParamName, dateParamName, officeIdParamName,
+            officeNameParamName, transactionTypeParamName, transactionCurrencyParamName, externalIdParamName, submittedOnDateParamName));
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResource.java
new file mode 100644
index 0000000..4719c42
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResource.java
@@ -0,0 +1,223 @@
+/**
+ * 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.client.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+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.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientChargeData;
+import org.apache.fineract.portfolio.client.data.ClientTransactionData;
+import org.apache.fineract.portfolio.client.service.ClientChargeReadPlatformService;
+import org.apache.fineract.portfolio.client.service.ClientTransactionReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+@Path("/clients/{clientId}/charges")
+@Component
+public class ClientChargesApiResource {
+
+    private final PlatformSecurityContext context;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final ClientChargeReadPlatformService clientChargeReadPlatformService;
+    private final ClientTransactionReadPlatformService clientTransactionReadPlatformService;
+    private final DefaultToApiJsonSerializer<ClientChargeData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public ClientChargesApiResource(final PlatformSecurityContext context, final ChargeReadPlatformService chargeReadPlatformService,
+            final ClientChargeReadPlatformService clientChargeReadPlatformService,
+            final DefaultToApiJsonSerializer<ClientChargeData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ClientTransactionReadPlatformService clientTransactionReadPlatformService) {
+        this.context = context;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.clientChargeReadPlatformService = clientChargeReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.clientTransactionReadPlatformService = clientTransactionReadPlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllClientCharges(@PathParam("clientId") final Long clientId,
+            @DefaultValue(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL) @QueryParam(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS) final String chargeStatus,
+            @QueryParam("pendingPayment") final Boolean pendingPayment, @Context final UriInfo uriInfo,
+            @QueryParam("limit") final Integer limit, @QueryParam("offset") final Integer offset) {
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (!(is(chargeStatus, ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL)
+                || is(chargeStatus, ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE)
+                || is(chargeStatus,
+                        ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE))) { throw new UnrecognizedQueryParamException(
+                                ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS, chargeStatus,
+                                new Object[] { ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL,
+                                        ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE,
+                                        ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE }); }
+        final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit);
+
+        final Page<ClientChargeData> clientCharges = this.clientChargeReadPlatformService.retrieveClientCharges(clientId, chargeStatus,
+                pendingPayment, searchParameters);
+        return this.toApiJsonSerializer.serialize(settings, clientCharges, ClientApiConstants.CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS);
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+
+        final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveAllChargesApplicableToClients();
+        final ClientChargeData clientChargeData = ClientChargeData.template(chargeOptions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, clientChargeData, ClientApiConstants.CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS);
+
+    }
+
+    @GET
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveClientCharge(@PathParam("clientId") final Long clientId, @PathParam("chargeId") final Long chargeId,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+        ClientChargeData clientCharge = this.clientChargeReadPlatformService.retrieveClientCharge(clientId, chargeId);
+        // extract associations
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        if (!associationParameters.isEmpty()) {
+            if (associationParameters.contains("all")) {
+                associationParameters.addAll(Arrays.asList(ClientApiConstants.CLIENT_CHARGE_ASSOCIATIONS_TRANSACTIONS));
+            }
+            ApiParameterHelper.excludeAssociationsForResponseIfProvided(uriInfo.getQueryParameters(), associationParameters);
+            if (associationParameters.contains(ClientApiConstants.CLIENT_CHARGE_ASSOCIATIONS_TRANSACTIONS)) {
+                Collection<ClientTransactionData> clientTransactionDatas = this.clientTransactionReadPlatformService
+                        .retrieveAllTransactions(clientId, chargeId);
+                if (!CollectionUtils.isEmpty(clientTransactionDatas)) {
+                    clientCharge = ClientChargeData.addAssociations(clientCharge, clientTransactionDatas);
+                }
+            }
+        }
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, clientCharge, ClientApiConstants.CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String applyClientCharge(@PathParam("clientId") final Long clientId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createClientCharge(clientId).withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String payOrWaiveClientCharge(@PathParam("clientId") final Long clientId, @PathParam("chargeId") final Long chargeId,
+            @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) {
+
+        String json = "";
+        if (is(commandParam, ClientApiConstants.CLIENT_CHARGE_COMMAND_WAIVE_CHARGE)) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().waiveClientCharge(clientId, chargeId)
+                    .withJson(apiRequestBodyAsJson).build();
+
+            final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+            json = this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, ClientApiConstants.CLIENT_CHARGE_COMMAND_PAY_CHARGE)) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().payClientCharge(clientId, chargeId)
+                    .withJson(apiRequestBodyAsJson).build();
+
+            final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+            json = this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, ClientApiConstants.CLIENT_CHARGE_COMMAND_INACTIVATE_CHARGE)) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().inactivateClientCharge(clientId, chargeId).build();
+
+            final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+            json = this.toApiJsonSerializer.serialize(result);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam, ClientApiConstants.CLIENT_CHARGE_COMMAND_WAIVE_CHARGE,
+                    ClientApiConstants.CLIENT_CHARGE_COMMAND_PAY_CHARGE, ClientApiConstants.CLIENT_CHARGE_COMMAND_INACTIVATE_CHARGE);
+        }
+
+        return json;
+    }
+
+    @DELETE
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteClientCharge(@PathParam("clientId") final Long clientId, @PathParam("chargeId") final Long chargeId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteClientCharge(clientId, chargeId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientIdentifiersApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientIdentifiersApiResource.java
new file mode 100644
index 0000000..eaa8850
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientIdentifiersApiResource.java
@@ -0,0 +1,205 @@
+/**
+ * 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.client.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.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+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.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.data.ClientIdentifierData;
+import org.apache.fineract.portfolio.client.exception.DuplicateClientIdentifierException;
+import org.apache.fineract.portfolio.client.service.ClientIdentifierReadPlatformService;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/clients/{clientId}/identifiers")
+@Component
+@Scope("singleton")
+public class ClientIdentifiersApiResource {
+
+    private static final Set<String> CLIENT_IDENTIFIER_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "clientId",
+            "documentType", "documentKey", "description", "allowedDocumentTypes"));
+
+    private final String resourceNameForPermissions = "CLIENTIDENTIFIER";
+
+    private final PlatformSecurityContext context;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final ClientIdentifierReadPlatformService clientIdentifierReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+    private final DefaultToApiJsonSerializer<ClientIdentifierData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public ClientIdentifiersApiResource(final PlatformSecurityContext context, final ClientReadPlatformService readPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService,
+            final DefaultToApiJsonSerializer<ClientIdentifierData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ClientIdentifierReadPlatformService clientIdentifierReadPlatformService) {
+        this.context = context;
+        this.clientReadPlatformService = readPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.clientIdentifierReadPlatformService = clientIdentifierReadPlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllClientIdentifiers(@Context final UriInfo uriInfo, @PathParam("clientId") final Long clientId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<ClientIdentifierData> clientIdentifiers = this.clientIdentifierReadPlatformService
+                .retrieveClientIdentifiers(clientId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, clientIdentifiers, CLIENT_IDENTIFIER_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String newClientIdentifierDetails(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<CodeValueData> codeValues = this.codeValueReadPlatformService.retrieveCodeValuesByCode("Customer Identifier");
+        final ClientIdentifierData clientIdentifierData = ClientIdentifierData.template(codeValues);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, clientIdentifierData, CLIENT_IDENTIFIER_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createClientIdentifier(@PathParam("clientId") final Long clientId, final String apiRequestBodyAsJson) {
+
+        try {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().createClientIdentifier(clientId)
+                    .withJson(apiRequestBodyAsJson).build();
+
+            final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+            return this.toApiJsonSerializer.serialize(result);
+        } catch (final DuplicateClientIdentifierException e) {
+            DuplicateClientIdentifierException rethrowas = e;
+            if (e.getDocumentTypeId() != null) {
+                // need to fetch client info
+                final ClientData clientInfo = this.clientReadPlatformService.retrieveClientByIdentifier(e.getDocumentTypeId(),
+                        e.getIdentifierKey());
+                rethrowas = new DuplicateClientIdentifierException(clientInfo.displayName(), clientInfo.officeName(),
+                        e.getIdentifierType(), e.getIdentifierKey());
+            }
+            throw rethrowas;
+        }
+    }
+
+    @GET
+    @Path("{identifierId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveClientIdentifiers(@PathParam("clientId") final Long clientId,
+            @PathParam("identifierId") final Long clientIdentifierId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        ClientIdentifierData clientIdentifierData = this.clientIdentifierReadPlatformService.retrieveClientIdentifier(clientId,
+                clientIdentifierId);
+        if (settings.isTemplate()) {
+            final Collection<CodeValueData> codeValues = this.codeValueReadPlatformService.retrieveCodeValuesByCode("Customer Identifier");
+            clientIdentifierData = ClientIdentifierData.template(clientIdentifierData, codeValues);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, clientIdentifierData, CLIENT_IDENTIFIER_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{identifierId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateClientIdentifer(@PathParam("clientId") final Long clientId,
+            @PathParam("identifierId") final Long clientIdentifierId, final String apiRequestBodyAsJson) {
+
+        try {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().updateClientIdentifier(clientId, clientIdentifierId)
+                    .withJson(apiRequestBodyAsJson).build();
+
+            final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+            return this.toApiJsonSerializer.serialize(result);
+        } catch (final DuplicateClientIdentifierException e) {
+            DuplicateClientIdentifierException reThrowAs = e;
+            if (e.getDocumentTypeId() != null) {
+                final ClientData clientInfo = this.clientReadPlatformService.retrieveClientByIdentifier(e.getDocumentTypeId(),
+                        e.getIdentifierKey());
+                reThrowAs = new DuplicateClientIdentifierException(clientInfo.displayName(), clientInfo.officeName(),
+                        e.getIdentifierType(), e.getIdentifierKey());
+            }
+            throw reThrowAs;
+        }
+    }
+
+    @DELETE
+    @Path("{identifierId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteClientIdentifier(@PathParam("clientId") final Long clientId,
+            @PathParam("identifierId") final Long clientIdentifierId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteClientIdentifier(clientId, clientIdentifierId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java
new file mode 100644
index 0000000..a485957
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.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.client.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.data.ClientTransactionData;
+import org.apache.fineract.portfolio.client.service.ClientTransactionReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Path("/clients/{clientId}/transactions")
+@Component
+public class ClientTransactionsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final ClientTransactionReadPlatformService clientTransactionReadPlatformService;
+    private final DefaultToApiJsonSerializer<ClientTransactionData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public ClientTransactionsApiResource(final PlatformSecurityContext context,
+            final ClientTransactionReadPlatformService clientTransactionReadPlatformService,
+            final DefaultToApiJsonSerializer<ClientTransactionData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.clientTransactionReadPlatformService = clientTransactionReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllClientTransactions(@PathParam("clientId") final Long clientId, @Context final UriInfo uriInfo,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit) {
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+
+        SearchParameters searchParameters = SearchParameters.forPagination(offset, limit);
+        final Page<ClientTransactionData> clientTransactions = this.clientTransactionReadPlatformService.retrieveAllTransactions(clientId,
+                searchParameters);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, clientTransactions,
+                ClientApiConstants.CLIENT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveClientTransaction(@PathParam("clientId") final Long clientId,
+            @PathParam("transactionId") final Long transactionId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+
+        final ClientTransactionData clientTransaction = this.clientTransactionReadPlatformService.retrieveTransaction(clientId,
+                transactionId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, clientTransaction,
+                ClientApiConstants.CLIENT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String undoClientTransaction(@PathParam("clientId") final Long clientId, @PathParam("transactionId") final Long transactionId,
+            @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) {
+
+        String json = "";
+        if (is(commandParam, ClientApiConstants.CLIENT_TRANSACTION_COMMAND_UNDO)) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().undoClientTransaction(clientId, transactionId)
+                    .withJson(apiRequestBodyAsJson).build();
+
+            final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+            json = this.toApiJsonSerializer.serialize(result);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam, ClientApiConstants.CLIENT_TRANSACTION_COMMAND_UNDO);
+        }
+
+        return json;
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java
new file mode 100644
index 0000000..21aba2e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java
@@ -0,0 +1,307 @@
+/**
+ * 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.client.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.DefaultValue;
+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.QueryParam;
+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.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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData;
+import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/clients")
+@Component
+@Scope("singleton")
+public class ClientsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final ToApiJsonSerializer<ClientData> toApiJsonSerializer;
+    private final ToApiJsonSerializer<AccountSummaryCollectionData> clientAccountSummaryToApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
+    private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
+
+    @Autowired
+    public ClientsApiResource(final PlatformSecurityContext context, final ClientReadPlatformService readPlatformService,
+            final ToApiJsonSerializer<ClientData> toApiJsonSerializer,
+            final ToApiJsonSerializer<AccountSummaryCollectionData> clientAccountSummaryToApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
+            final SavingsAccountReadPlatformService savingsAccountReadPlatformService) {
+        this.context = context;
+        this.clientReadPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.clientAccountSummaryToApiJsonSerializer = clientAccountSummaryToApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
+        this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId,
+            @QueryParam("commandParam") final String commandParam,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly) {
+
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        ClientData clientData = null;
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
+        if (is(commandParam, "close")) {
+            clientData = this.clientReadPlatformService.retrieveAllNarrations(ClientApiConstants.CLIENT_CLOSURE_REASON);
+        } else if (is(commandParam, "acceptTransfer")) {
+            clientData = this.clientReadPlatformService.retrieveAllNarrations(ClientApiConstants.CLIENT_CLOSURE_REASON);
+        } else if (is(commandParam, "reject")) {
+            clientData = this.clientReadPlatformService.retrieveAllNarrations(ClientApiConstants.CLIENT_REJECT_REASON);
+        } else if (is(commandParam, "withdraw")) {
+            clientData = this.clientReadPlatformService.retrieveAllNarrations(ClientApiConstants.CLIENT_WITHDRAW_REASON);
+        } else {
+            clientData = this.clientReadPlatformService.retrieveTemplate(officeId, staffInSelectedOfficeOnly);
+        }
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("officeId") final Long officeId, @QueryParam("externalId") final String externalId,
+            @QueryParam("displayName") final String displayName, @QueryParam("firstName") final String firstname,
+            @QueryParam("lastName") final String lastname, @QueryParam("underHierarchy") final String hierarchy,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder,
+            @QueryParam("orphansOnly") final Boolean orphansOnly) {
+
+        return this.retrieveAll(uriInfo, sqlSearch, officeId, externalId, displayName, firstname, 
+        		lastname, hierarchy, offset, limit, orderBy, sortOrder, orphansOnly, false);
+    }
+    
+    public String retrieveAll(final UriInfo uriInfo, final String sqlSearch,
+            final Long officeId, final String externalId,
+            final String displayName, final String firstname,
+            final String lastname, final String hierarchy,
+            final Integer offset, final Integer limit,
+            final String orderBy, final String sortOrder,
+            final Boolean orphansOnly, final boolean isSelfUser) {
+
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final SearchParameters searchParameters = SearchParameters.forClients(sqlSearch, officeId, externalId, displayName, firstname,
+                lastname, hierarchy, offset, limit, orderBy, sortOrder, orphansOnly, isSelfUser);
+
+        final Page<ClientData> clientData = this.clientReadPlatformService.retrieveAll(searchParameters);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{clientId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("clientId") final Long clientId, @Context final UriInfo uriInfo,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly) {
+
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        ClientData clientData = this.clientReadPlatformService.retrieveOne(clientId);
+        if (settings.isTemplate()) {
+            final ClientData templateData = this.clientReadPlatformService.retrieveTemplate(clientData.officeId(),
+                    staffInSelectedOfficeOnly);
+            clientData = ClientData.templateOnTop(clientData, templateData);
+            Collection<SavingsAccountData> savingAccountOptions = this.savingsAccountReadPlatformService.retrieveForLookup(clientId, null);
+            if (savingAccountOptions != null && savingAccountOptions.size() > 0) {
+                clientData = ClientData.templateWithSavingAccountOptions(clientData, savingAccountOptions);
+            }
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createClient() //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{clientId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("clientId") final Long clientId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateClient(clientId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{clientId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("clientId") final Long clientId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteClient(clientId) //
+                .build(); //
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{clientId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String activate(@PathParam("clientId") final Long clientId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+        CommandProcessingResult result = null;
+        CommandWrapper commandRequest = null;
+        if (is(commandParam, "activate")) {
+            commandRequest = builder.activateClient(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "assignStaff")) {
+            commandRequest = builder.assignClientStaff(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "unassignStaff")) {
+            commandRequest = builder.unassignClientStaff(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "close")) {
+            commandRequest = builder.closeClient(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "proposeTransfer")) {
+            commandRequest = builder.proposeClientTransfer(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "proposeAndAcceptTransfer")) {
+            commandRequest = builder.proposeAndAcceptClientTransfer(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "withdrawTransfer")) {
+            commandRequest = builder.withdrawClientTransferRequest(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "acceptTransfer")) {
+            commandRequest = builder.acceptClientTransfer(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "rejectTransfer")) {
+            commandRequest = builder.rejectClientTransfer(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "updateSavingsAccount")) {
+            commandRequest = builder.updateClientSavingsAccount(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "reject")) {
+            commandRequest = builder.rejectClient(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "withdraw")) {
+            commandRequest = builder.withdrawClient(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "reactivate")) {
+            commandRequest = builder.reActivateClient(clientId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate",
+                "unassignStaff", "assignStaff", "close", "proposeTransfer", "withdrawTransfer", "acceptTransfer", "rejectTransfer",
+                "updateSavingsAccount", "reject", "withdraw", "reactivate" }); }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Path("{clientId}/accounts")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAssociatedAccounts(@PathParam("clientId") final Long clientId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final AccountSummaryCollectionData clientAccount = this.accountDetailsReadPlatformService.retrieveClientAccountDetails(clientId);
+
+        final Set<String> CLIENT_ACCOUNTS_DATA_PARAMETERS = new HashSet<>(Arrays.asList("loanAccounts", "savingsAccounts"));
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.clientAccountSummaryToApiJsonSerializer.serialize(settings, clientAccount, CLIENT_ACCOUNTS_DATA_PARAMETERS);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientIdentifierCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientIdentifierCommand.java
new file mode 100644
index 0000000..a11e914
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientIdentifierCommand.java
@@ -0,0 +1,84 @@
+/**
+ * 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.client.command;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+
+/**
+ * Immutable command for creating or updating details of a client identifier.
+ */
+public class ClientIdentifierCommand {
+
+    private final Long documentTypeId;
+    private final String documentKey;
+    private final String description;
+
+    public ClientIdentifierCommand(final Long documentTypeId, final String documentKey, final String description) {
+        this.documentTypeId = documentTypeId;
+        this.documentKey = documentKey;
+        this.description = description;
+    }
+
+    public Long getDocumentTypeId() {
+        return this.documentTypeId;
+    }
+
+    public String getDocumentKey() {
+        return this.documentKey;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public void validateForCreate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("clientIdentifier");
+
+        baseDataValidator.reset().parameter("documentTypeId").value(this.documentTypeId).notNull().integerGreaterThanZero();
+        baseDataValidator.reset().parameter("documentKey").value(this.documentKey).notBlank();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForUpdate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("clientIdentifier");
+
+        baseDataValidator.reset().parameter("documentKey").value(this.documentKey).ignoreIfNull().notBlank();
+
+        // FIXME - KW - add in validation
+        // if (command.isDocumentTypeChanged()) {
+        // baseDataValidator.reset().parameter("documentTypeId").value(command.getDocumentTypeId()).notNull().integerGreaterThanZero();
+        // }
+
+        baseDataValidator.reset().anyOfNotNull(this.documentTypeId, this.documentKey);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientNoteCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientNoteCommand.java
new file mode 100644
index 0000000..0f8b5ca
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientNoteCommand.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.client.command;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+
+/**
+ * Immutable command used for create or update of notes.
+ */
+public class ClientNoteCommand {
+
+    private final String note;
+
+    public ClientNoteCommand(final String note) {
+        this.note = note;
+    }
+
+    public void validate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("clientnote");
+
+        baseDataValidator.reset().parameter("note").value(this.note).notBlank().notExceedingLengthOf(1000);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientChargeData.java
new file mode 100644
index 0000000..61de539
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientChargeData.java
@@ -0,0 +1,149 @@
+/**
+ * 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.client.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.joda.time.LocalDate;
+
+@SuppressWarnings("unused")
+public class ClientChargeData {
+
+    private final Long id;
+
+    private final Long clientId;
+
+    private final Long chargeId;
+
+    private final String name;
+
+    private final EnumOptionData chargeTimeType;
+
+    private final LocalDate dueDate;
+
+    private final EnumOptionData chargeCalculationType;
+
+    private final CurrencyData currency;
+
+    private final BigDecimal amount;
+
+    private final BigDecimal amountPaid;
+
+    private final BigDecimal amountWaived;
+
+    private final BigDecimal amountWrittenOff;
+
+    private final BigDecimal amountOutstanding;
+
+    private final boolean penalty;
+
+    private final Boolean isActive;
+
+    private final Boolean isPaid;
+
+    private final Boolean isWaived;
+
+    private final LocalDate inactivationDate;
+
+    private final Collection<ChargeData> chargeOptions;
+
+    private final Collection<ClientTransactionData> clientTransactionDatas;
+
+    public static ClientChargeData instance(Long id, Long clientId, Long chargeId, String name, EnumOptionData chargeTimeType,
+            LocalDate dueDate, EnumOptionData chargeCalculationType, CurrencyData currency, BigDecimal amount, BigDecimal amountPaid,
+            BigDecimal amountWaived, BigDecimal amountWrittenOff, BigDecimal amountOutstanding, boolean penalty, Boolean isPaid,
+            Boolean isWaived, Boolean isActive, LocalDate inactivationDate, Collection<ChargeData> chargeOptions) {
+        Collection<ClientTransactionData> clientTransactionDatas = null;
+        return new ClientChargeData(id, clientId, chargeId, name, chargeTimeType, dueDate, chargeCalculationType, currency, amount,
+                amountPaid, amountWaived, amountWrittenOff, amountOutstanding, penalty, isPaid, isWaived, isActive, inactivationDate,
+                chargeOptions, clientTransactionDatas);
+    }
+
+    public static ClientChargeData addAssociations(ClientChargeData clientChargeData,
+            Collection<ClientTransactionData> clientTransactionDatas) {
+        return new ClientChargeData(clientChargeData.id, clientChargeData.clientId, clientChargeData.chargeId, clientChargeData.name,
+                clientChargeData.chargeTimeType, clientChargeData.dueDate, clientChargeData.chargeCalculationType,
+                clientChargeData.currency, clientChargeData.amount, clientChargeData.amountPaid, clientChargeData.amountWaived,
+                clientChargeData.amountWrittenOff, clientChargeData.amountOutstanding, clientChargeData.penalty, clientChargeData.isPaid,
+                clientChargeData.isWaived, clientChargeData.isActive, clientChargeData.inactivationDate, clientChargeData.chargeOptions,
+                clientTransactionDatas);
+    }
+
+    public static ClientChargeData template(final Collection<ChargeData> chargeOptions) {
+        final Long id = null;
+        final Long clientId = null;
+        final Long chargeId = null;
+        final String name = null;
+        final EnumOptionData chargeTimeType = null;
+        final LocalDate dueDate = null;
+        final EnumOptionData chargeCalculationType = null;
+        final CurrencyData currency = null;
+        final BigDecimal amount = null;
+        final BigDecimal amountPaid = null;
+        final BigDecimal amountWaived = null;
+        final BigDecimal amountWrittenOff = null;
+        final BigDecimal amountOutstanding = null;
+        final Boolean penalty = false;
+        final Boolean isPaid = null;
+        final Boolean isActive = null;
+        final Boolean isWaived = null;
+        final LocalDate inactivationDate = null;
+        final Collection<ClientTransactionData> clientTransactionDatas = null;
+
+        return new ClientChargeData(id, clientId, chargeId, name, chargeTimeType, dueDate, chargeCalculationType, currency, amount,
+                amountPaid, amountWaived, amountWrittenOff, amountOutstanding, penalty, isPaid, isWaived, isActive, inactivationDate,
+                chargeOptions, clientTransactionDatas);
+    }
+
+    private ClientChargeData(Long id, Long clientId, Long chargeId, String name, EnumOptionData chargeTimeType, LocalDate dueDate,
+            EnumOptionData chargeCalculationType, CurrencyData currency, BigDecimal amount, BigDecimal amountPaid, BigDecimal amountWaived,
+            BigDecimal amountWrittenOff, BigDecimal amountOutstanding, boolean penalty, Boolean isPaid, Boolean isWaived, Boolean isActive,
+            LocalDate inactivationDate, Collection<ChargeData> chargeOptions, Collection<ClientTransactionData> clientTransactionDatas) {
+        super();
+        this.id = id;
+        this.clientId = clientId;
+        this.chargeId = chargeId;
+        this.name = name;
+        this.chargeTimeType = chargeTimeType;
+        this.dueDate = dueDate;
+        this.chargeCalculationType = chargeCalculationType;
+        this.currency = currency;
+        this.amount = amount;
+        this.amountPaid = amountPaid;
+        this.amountWaived = amountWaived;
+        this.amountWrittenOff = amountWrittenOff;
+        this.amountOutstanding = amountOutstanding;
+        this.penalty = penalty;
+        this.isPaid = isPaid;
+        this.isWaived = isWaived;
+        this.isActive = isActive;
+        this.inactivationDate = inactivationDate;
+
+        // template related fields
+        this.chargeOptions = chargeOptions;
+
+        /// associations
+        this.clientTransactionDatas = clientTransactionDatas;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientChargeDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientChargeDataValidator.java
new file mode 100644
index 0000000..9310c76
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientChargeDataValidator.java
@@ -0,0 +1,125 @@
+/**
+ * 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.client.data;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class ClientChargeDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ClientChargeDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateAdd(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                ClientApiConstants.CLIENT_CHARGES_ADD_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(ClientApiConstants.amountParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.amountParamName).value(amount).notNull().positiveAmount();
+
+        final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.dueAsOfDateParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.dueAsOfDateParamName).value(dueDate).notNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                ClientApiConstants.CLIENT_CHARGES_ADD_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.amountParamName, element)) {
+            final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(ClientApiConstants.amountParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.amountParamName).value(amount).notNull().positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.dueAsOfDateParamName, element)) {
+            final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.dueAsOfDateParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.dueAsOfDateParamName).value(dueDate).notNull();
+        }
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validatePayCharge(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                ClientApiConstants.CLIENT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(ClientApiConstants.amountParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.amountParamName).value(amount).notNull().positiveAmount();
+
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.transactionDateParamName,
+                element);
+        baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName).value(transactionDate).notNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
new file mode 100644
index 0000000..1984417
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
@@ -0,0 +1,458 @@
+/**
+ * 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.client.data;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing client data.
+ */
+final public class ClientData implements Comparable<ClientData> {
+
+    private final Long id;
+    private final String accountNo;
+    private final String externalId;
+
+    private final EnumOptionData status;
+    private final CodeValueData subStatus;
+
+    @SuppressWarnings("unused")
+    private final Boolean active;
+    private final LocalDate activationDate;
+
+    private final String firstname;
+    private final String middlename;
+    private final String lastname;
+    private final String fullname;
+    private final String displayName;
+    private final String mobileNo;
+    private final LocalDate dateOfBirth;
+    private final CodeValueData gender;
+    private final CodeValueData clientType;
+    private final CodeValueData clientClassification;
+
+    private final Long officeId;
+    private final String officeName;
+    private final Long transferToOfficeId;
+    private final String transferToOfficeName;
+
+    private final Long imageId;
+    private final Boolean imagePresent;
+    private final Long staffId;
+    private final String staffName;
+    private final ClientTimelineData timeline;
+
+    private final Long savingsProductId;
+    private final String savingsProductName;
+
+    private final Long savingsAccountId;
+    private final EnumOptionData legalForm;
+
+    // associations
+    private final Collection<GroupGeneralData> groups;
+
+    // template
+    private final Collection<OfficeData> officeOptions;
+    private final Collection<StaffData> staffOptions;
+    private final Collection<CodeValueData> narrations;
+    private final Collection<SavingsProductData> savingProductOptions;
+    private final Collection<SavingsAccountData> savingAccountOptions;
+    private final Collection<CodeValueData> genderOptions;
+    private final Collection<CodeValueData> clientTypeOptions;
+    private final Collection<CodeValueData> clientClassificationOptions;    
+    private final Collection<CodeValueData> clientNonPersonConstitutionOptions;
+    private final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions;
+    private final List<EnumOptionData> clientLegalFormOptions;
+    
+    private final ClientNonPersonData clientNonPersonDetails;
+
+    public static ClientData template(final Long officeId, final LocalDate joinedDate, final Collection<OfficeData> officeOptions,
+            final Collection<StaffData> staffOptions, final Collection<CodeValueData> narrations,
+            final Collection<CodeValueData> genderOptions, final Collection<SavingsProductData> savingProductOptions,
+            final Collection<CodeValueData> clientTypeOptions, final Collection<CodeValueData> clientClassificationOptions, 
+            final Collection<CodeValueData> clientNonPersonConstitutionOptions, final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions,
+            final List<EnumOptionData> clientLegalFormOptions) {
+        final String accountNo = null;
+        final EnumOptionData status = null;
+        final CodeValueData subStatus = null;
+        final String officeName = null;
+        final Long transferToOfficeId = null;
+        final String transferToOfficeName = null;
+        final Long id = null;
+        final String firstname = null;
+        final String middlename = null;
+        final String lastname = null;
+        final String fullname = null;
+        final String displayName = null;
+        final String externalId = null;
+        final String mobileNo = null;
+        final LocalDate dateOfBirth = null;
+        final CodeValueData gender = null;
+        final Long imageId = null;
+        final Long staffId = null;
+        final String staffName = null;
+        final Collection<GroupGeneralData> groups = null;
+        final ClientTimelineData timeline = null;
+        final Long savingsProductId = null;
+        final String savingsProductName = null;
+        final Long savingsAccountId = null;
+        final Collection<SavingsAccountData> savingAccountOptions = null;
+        final CodeValueData clientType = null;
+        final CodeValueData clientClassification = null;
+        final EnumOptionData legalForm = null;
+        final ClientNonPersonData clientNonPersonDetails = null;
+        return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
+                middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, joinedDate, imageId, staffId,
+                staffName, officeOptions, groups, staffOptions, narrations, genderOptions, timeline, savingProductOptions,
+                savingsProductId, savingsProductName, savingsAccountId, savingAccountOptions, clientType, clientClassification,
+                clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions, 
+                clientNonPersonDetails, clientLegalFormOptions, legalForm);
+
+    }
+
+    public static ClientData templateOnTop(final ClientData clientData, final ClientData templateData) {
+
+        return new ClientData(clientData.accountNo, clientData.status, clientData.subStatus, clientData.officeId, clientData.officeName,
+                clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
+                clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
+                clientData.dateOfBirth, clientData.gender, clientData.activationDate, clientData.imageId, clientData.staffId,
+                clientData.staffName, templateData.officeOptions, clientData.groups, templateData.staffOptions, templateData.narrations,
+                templateData.genderOptions, clientData.timeline, templateData.savingProductOptions, clientData.savingsProductId,
+                clientData.savingsProductName, clientData.savingsAccountId, clientData.savingAccountOptions, clientData.clientType,
+                clientData.clientClassification, templateData.clientTypeOptions, templateData.clientClassificationOptions, 
+                templateData.clientNonPersonConstitutionOptions, templateData.clientNonPersonMainBusinessLineOptions, clientData.clientNonPersonDetails,
+                templateData.clientLegalFormOptions, clientData.legalForm);
+
+    }
+
+    public static ClientData templateWithSavingAccountOptions(final ClientData clientData,
+            final Collection<SavingsAccountData> savingAccountOptions) {
+
+        return new ClientData(clientData.accountNo, clientData.status, clientData.subStatus, clientData.officeId, clientData.officeName,
+                clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
+                clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
+                clientData.dateOfBirth, clientData.gender, clientData.activationDate, clientData.imageId, clientData.staffId,
+                clientData.staffName, clientData.officeOptions, clientData.groups, clientData.staffOptions, clientData.narrations,
+                clientData.genderOptions, clientData.timeline, clientData.savingProductOptions, clientData.savingsProductId,
+                clientData.savingsProductName, clientData.savingsAccountId, savingAccountOptions, clientData.clientType,
+                clientData.clientClassification, clientData.clientTypeOptions, clientData.clientClassificationOptions,
+                clientData.clientNonPersonConstitutionOptions, clientData.clientNonPersonMainBusinessLineOptions, clientData.clientNonPersonDetails,
+                clientData.clientLegalFormOptions, clientData.legalForm);
+
+    }
+
+    public static ClientData setParentGroups(final ClientData clientData, final Collection<GroupGeneralData> parentGroups) {
+        return new ClientData(clientData.accountNo, clientData.status, clientData.subStatus, clientData.officeId, clientData.officeName,
+                clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
+                clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
+                clientData.dateOfBirth, clientData.gender, clientData.activationDate, clientData.imageId, clientData.staffId,
+                clientData.staffName, clientData.officeOptions, parentGroups, clientData.staffOptions, null, null, clientData.timeline,
+                clientData.savingProductOptions, clientData.savingsProductId, clientData.savingsProductName, clientData.savingsAccountId,
+                clientData.savingAccountOptions, clientData.clientType, clientData.clientClassification, clientData.clientTypeOptions,
+                clientData.clientClassificationOptions, clientData.clientNonPersonConstitutionOptions, clientData.clientNonPersonMainBusinessLineOptions, 
+                clientData.clientNonPersonDetails, clientData.clientLegalFormOptions, clientData.legalForm);
+
+    }
+
+    public static ClientData clientIdentifier(final Long id, final String accountNo, final String firstname, final String middlename,
+            final String lastname, final String fullname, final String displayName, final Long officeId, final String officeName) {
+
+        final Long transferToOfficeId = null;
+        final String transferToOfficeName = null;
+        final String externalId = null;
+        final String mobileNo = null;
+        final LocalDate dateOfBirth = null;
+        final CodeValueData gender = null;
+        final LocalDate activationDate = null;
+        final Long imageId = null;
+        final Long staffId = null;
+        final String staffName = null;
+        final Collection<OfficeData> allowedOffices = null;
+        final Collection<GroupGeneralData> groups = null;
+        final Collection<StaffData> staffOptions = null;
+        final Collection<CodeValueData> closureReasons = null;
+        final Collection<CodeValueData> genderOptions = null;
+        final ClientTimelineData timeline = null;
+        final Collection<SavingsProductData> savingProductOptions = null;
+        final Long savingsProductId = null;
+        final String savingsProductName = null;
+        final Long savingsAccountId = null;
+        final Collection<SavingsAccountData> savingAccountOptions = null;
+        final CodeValueData clientType = null;
+        final CodeValueData clientClassification = null;
+        final Collection<CodeValueData> clientTypeOptions = null;
+        final Collection<CodeValueData> clientClassificationOptions = null;
+        final Collection<CodeValueData> clientNonPersonConstitutionOptions = null;
+        final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions = null;
+        final List<EnumOptionData> clientLegalFormOptions = null;
+        final EnumOptionData status = null;
+        final CodeValueData subStatus = null;
+        final EnumOptionData legalForm = null;
+        final ClientNonPersonData clientNonPerson = null;
+        return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
+                middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate, imageId, staffId,
+                staffName, allowedOffices, groups, staffOptions, closureReasons, genderOptions, timeline, savingProductOptions,
+                savingsProductId, savingsProductName, savingsAccountId, savingAccountOptions, clientType, clientClassification,
+                clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions, 
+                clientNonPerson, clientLegalFormOptions, legalForm);
+    }
+
+    public static ClientData lookup(final Long id, final String displayName, final Long officeId, final String officeName) {
+        final String accountNo = null;
+        final EnumOptionData status = null;
+        final CodeValueData subStatus = null;
+        final Long transferToOfficeId = null;
+        final String transferToOfficeName = null;
+        final String firstname = null;
+        final String middlename = null;
+        final String lastname = null;
+        final String fullname = null;
+        final String externalId = null;
+        final String mobileNo = null;
+        final LocalDate dateOfBirth = null;
+        final CodeValueData gender = null;
+        final LocalDate activationDate = null;
+        final Long imageId = null;
+        final Long staffId = null;
+        final String staffName = null;
+        final Collection<OfficeData> allowedOffices = null;
+        final Collection<GroupGeneralData> groups = null;
+        final Collection<StaffData> staffOptions = null;
+        final Collection<CodeValueData> closureReasons = null;
+        final Collection<CodeValueData> genderOptions = null;
+        final ClientTimelineData timeline = null;
+        final Collection<SavingsProductData> savingProductOptions = null;
+        final Long savingsProductId = null;
+        final String savingsProductName = null;
+        final Long savingsAccountId = null;
+        final Collection<SavingsAccountData> savingAccountOptions = null;
+        final CodeValueData clientType = null;
+        final CodeValueData clientClassification = null;
+        final Collection<CodeValueData> clientTypeOptions = null;
+        final Collection<CodeValueData> clientClassificationOptions = null;
+        final Collection<CodeValueData> clientNonPersonConstitutionOptions = null;
+        final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions = null;
+        final List<EnumOptionData> clientLegalFormOptions = null;
+        final EnumOptionData legalForm = null;
+        final ClientNonPersonData clientNonPerson = null;
+        return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
+                middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate, imageId, staffId,
+                staffName, allowedOffices, groups, staffOptions, closureReasons, genderOptions, timeline, savingProductOptions,
+                savingsProductId, savingsProductName, savingsAccountId, savingAccountOptions, clientType, clientClassification,
+                clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions, 
+                clientNonPerson, clientLegalFormOptions, legalForm);
+
+    }
+
+    public static ClientData instance(final String accountNo, final EnumOptionData status, final CodeValueData subStatus,
+            final Long officeId, final String officeName, final Long transferToOfficeId, final String transferToOfficeName, final Long id,
+            final String firstname, final String middlename, final String lastname, final String fullname, final String displayName,
+            final String externalId, final String mobileNo, final LocalDate dateOfBirth, final CodeValueData gender,
+            final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,
+            final ClientTimelineData timeline, final Long savingsProductId, final String savingsProductName, final Long savingsAccountId,
+            final CodeValueData clientType, final CodeValueData clientClassification, final EnumOptionData legalForm, final ClientNonPersonData clientNonPerson) {
+
+        final Collection<OfficeData> allowedOffices = null;
+        final Collection<GroupGeneralData> groups = null;
+        final Collection<StaffData> staffOptions = null;
+        final Collection<CodeValueData> closureReasons = null;
+        final Collection<CodeValueData> genderOptions = null;
+        final Collection<SavingsProductData> savingProductOptions = null;
+        final Collection<CodeValueData> clientTypeOptions = null;
+        final Collection<CodeValueData> clientClassificationOptions = null;
+        final Collection<CodeValueData> clientNonPersonConstitutionOptions = null;
+        final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions = null;
+        final List<EnumOptionData> clientLegalFormOptions = null;
+        return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
+                middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate, imageId, staffId,
+                staffName, allowedOffices, groups, staffOptions, closureReasons, genderOptions, timeline, savingProductOptions,
+                savingsProductId, savingsProductName, savingsAccountId, null, clientType, clientClassification, clientTypeOptions,
+                clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions, clientNonPerson,
+                clientLegalFormOptions, legalForm);
+
+    }
+
+    private ClientData(final String accountNo, final EnumOptionData status, final CodeValueData subStatus, final Long officeId,
+            final String officeName, final Long transferToOfficeId, final String transferToOfficeName, final Long id,
+            final String firstname, final String middlename, final String lastname, final String fullname, final String displayName,
+            final String externalId, final String mobileNo, final LocalDate dateOfBirth, final CodeValueData gender,
+            final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,
+            final Collection<OfficeData> allowedOffices, final Collection<GroupGeneralData> groups,
+            final Collection<StaffData> staffOptions, final Collection<CodeValueData> narrations,
+            final Collection<CodeValueData> genderOptions, final ClientTimelineData timeline,
+            final Collection<SavingsProductData> savingProductOptions, final Long savingsProductId, final String savingsProductName,
+            final Long savingsAccountId, final Collection<SavingsAccountData> savingAccountOptions, final CodeValueData clientType,
+            final CodeValueData clientClassification, final Collection<CodeValueData> clientTypeOptions,
+            final Collection<CodeValueData> clientClassificationOptions, final Collection<CodeValueData> clientNonPersonConstitutionOptions,
+            final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions, final ClientNonPersonData clientNonPerson,
+            final List<EnumOptionData> clientLegalFormOptions, final EnumOptionData legalForm) {
+        this.accountNo = accountNo;
+        this.status = status;
+        if (status != null) {
+            this.active = status.getId().equals(300L);
+        } else {
+            this.active = null;
+        }
+        this.subStatus = subStatus;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.transferToOfficeId = transferToOfficeId;
+        this.transferToOfficeName = transferToOfficeName;
+        this.id = id;
+        this.firstname = StringUtils.defaultIfEmpty(firstname, null);
+        this.middlename = StringUtils.defaultIfEmpty(middlename, null);
+        this.lastname = StringUtils.defaultIfEmpty(lastname, null);
+        this.fullname = StringUtils.defaultIfEmpty(fullname, null);
+        this.displayName = StringUtils.defaultIfEmpty(displayName, null);
+        this.externalId = StringUtils.defaultIfEmpty(externalId, null);
+        this.mobileNo = StringUtils.defaultIfEmpty(mobileNo, null);
+        this.activationDate = activationDate;
+        this.dateOfBirth = dateOfBirth;
+        this.gender = gender;
+        this.clientClassification = clientClassification;
+        this.clientType = clientType;
+        this.imageId = imageId;
+        if (imageId != null) {
+            this.imagePresent = Boolean.TRUE;
+        } else {
+            this.imagePresent = null;
+        }
+        this.staffId = staffId;
+        this.staffName = staffName;
+
+        // associations
+        this.groups = groups;
+
+        // template
+        this.officeOptions = allowedOffices;
+        this.staffOptions = staffOptions;
+        this.narrations = narrations;
+
+        this.genderOptions = genderOptions;
+        this.clientClassificationOptions = clientClassificationOptions;
+        this.clientTypeOptions = clientTypeOptions;
+        
+        this.clientNonPersonConstitutionOptions = clientNonPersonConstitutionOptions;
+        this.clientNonPersonMainBusinessLineOptions = clientNonPersonMainBusinessLineOptions;
+        this.clientLegalFormOptions = clientLegalFormOptions;
+
+        this.timeline = timeline;
+        this.savingProductOptions = savingProductOptions;
+        this.savingsProductId = savingsProductId;
+        this.savingsProductName = savingsProductName;
+        this.savingsAccountId = savingsAccountId;
+        this.savingAccountOptions = savingAccountOptions;
+        this.legalForm = legalForm;
+        this.clientNonPersonDetails = clientNonPerson;
+
+    }
+
+    public Long id() {
+        return this.id;
+    }
+
+    public String displayName() {
+        return this.displayName;
+    }
+    
+    public String accountNo() {
+    	return this.accountNo;
+    }
+
+    public Long officeId() {
+        return this.officeId;
+    }
+
+    public String officeName() {
+        return this.officeName;
+    }
+
+    public Long getImageId() {
+        return this.imageId;
+    }
+
+    public Boolean getImagePresent() {
+        return this.imagePresent;
+    }
+
+    public ClientTimelineData getTimeline() {
+        return this.timeline;
+    }
+
+    @Override
+    public int compareTo(final ClientData obj) {
+        if (obj == null) { return -1; }
+        return new CompareToBuilder() //
+                .append(this.id, obj.id) //
+                .append(this.displayName, obj.displayName) //
+                .append(this.mobileNo, obj.mobileNo) //
+                .toComparison();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final ClientData rhs = (ClientData) obj;
+        return new EqualsBuilder() //
+                .append(this.id, rhs.id) //
+                .append(this.displayName, rhs.displayName) //
+                .append(this.mobileNo, rhs.mobileNo) //
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37) //
+                .append(this.id) //
+                .append(this.displayName) //
+                .toHashCode();
+    }
+
+    public String getExternalId() {
+        return this.externalId;
+    }
+
+    public String getFirstname() {
+        return this.firstname;
+    }
+
+    public String getLastname() {
+        return this.lastname;
+    }
+
+    public LocalDate getActivationDate() {
+        return this.activationDate;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java
new file mode 100644
index 0000000..3ddf911
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java
@@ -0,0 +1,723 @@
+/**
+ * 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.client.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+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.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.joda.time.LocalDate;
+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 ClientDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ClientDataValidator(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, ClientApiConstants.CLIENT_CREATE_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.clientNonPersonDetailsParamName, element)) {
+	        final String clientNonPersonJson = this.fromApiJsonHelper.toJson(element.getAsJsonObject().get(ClientApiConstants.clientNonPersonDetailsParamName));
+	        if(clientNonPersonJson != null)
+	        	this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, clientNonPersonJson, ClientApiConstants.CLIENT_NON_PERSON_CREATE_REQUEST_DATA_PARAMETERS);
+        }
+        
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.officeIdParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.officeIdParamName).value(officeId).notNull().integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.groupIdParamName, element)) {
+            final Long groupId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.groupIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.groupIdParamName).value(groupId).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.staffIdParamName, element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.staffIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.staffIdParamName).value(staffId).ignoreIfNull().longGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.accountNoParamName, element)) {
+            final String accountNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.accountNoParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.savingsProductIdParamName, element)) {
+            final Long savingsProductId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.savingsProductIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.savingsProductIdParamName).value(savingsProductId).ignoreIfNull()
+                    .longGreaterThanZero();
+        }
+
+        if (isFullnameProvided(element) || isIndividualNameProvided(element)) {
+
+            // 1. No individual name part provided and fullname provided
+            if (isFullnameProvided(element) && !isIndividualNameProvided(element)) {
+                fullnameCannotBeBlank(element, baseDataValidator);
+            }
+
+            // 2. no fullname provided and individual name part provided
+            if (isIndividualNameProvided(element) && !isFullnameProvided(element)) {
+                validateRequiredIndividualNamePartsExist(element, baseDataValidator);
+            }
+
+            // 3. both provided
+            if (isFullnameProvided(element) && isIndividualNameProvided(element)) {
+                validateIndividualNamePartsCannotBeUsedWithFullname(element, baseDataValidator);
+            }
+        } else {
+
+            if (isFullnameParameterPassed(element) || isIndividualNamePartParameterPassed(element)) {
+
+                // 1. No individual name parameter passed and fullname passed
+                if (isFullnameParameterPassed(element) && !isIndividualNamePartParameterPassed(element)) {
+                    fullnameCannotBeBlank(element, baseDataValidator);
+                }
+
+                // 2. no fullname passed and individual name part passed
+                if (isIndividualNamePartParameterPassed(element) && !isFullnameParameterPassed(element)) {
+                    validateRequiredIndividualNamePartsExist(element, baseDataValidator);
+                }
+
+                // 3. both parameter types passed
+                if (isFullnameParameterPassed(element) && isIndividualNamePartParameterPassed(element)) {
+                    baseDataValidator.reset().parameter(ClientApiConstants.idParamName).failWithCode(".no.name.details.passed");
+                }
+
+            } else {
+                baseDataValidator.reset().parameter(ClientApiConstants.idParamName).failWithCode(".no.name.details.passed");
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.externalIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.externalIdParamName).value(externalId).ignoreIfNull()
+                    .notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mobileNoParamName, element)) {
+            final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+        }
+
+        final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(ClientApiConstants.activeParamName, element);
+        if (active != null) {
+            if (active.booleanValue()) {
+                final LocalDate joinedDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.activationDateParamName,
+                        element);
+                baseDataValidator.reset().parameter(ClientApiConstants.activationDateParamName).value(joinedDate).notNull();
+            }
+        } else {
+            baseDataValidator.reset().parameter(ClientApiConstants.activeParamName).value(active).trueOrFalseRequired(false);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.submittedOnDateParamName, element)) {
+            final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.submittedOnDateParamName,
+                    element);
+            baseDataValidator.reset().parameter(ClientApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.dateOfBirthParamName, element)) {
+            final LocalDate dateOfBirth = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.dateOfBirthParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.dateOfBirthParamName).value(dateOfBirth).notNull()
+                    .validateDateBefore(DateUtils.getLocalDateOfTenant());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.genderIdParamName, element)) {
+            final Integer genderId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.genderIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.genderIdParamName).value(genderId).integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.clientTypeIdParamName, element)) {
+            final Integer clientType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.clientTypeIdParamName,
+                    element);
+            baseDataValidator.reset().parameter(ClientApiConstants.clientTypeIdParamName).value(clientType).integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.clientClassificationIdParamName, element)) {
+            final Integer clientClassification = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    ClientApiConstants.clientClassificationIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.clientClassificationIdParamName).value(clientClassification)
+                    .integerGreaterThanZero();
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.legalFormIdParamName, element)) {
+        	final Integer legalFormId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.legalFormIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.legalFormIdParamName).value(legalFormId).ignoreIfNull().inMinMaxRange(1, 2);
+        }
+        
+        List<ApiParameterError> dataValidationErrorsForClientNonPerson = getDataValidationErrorsForCreateOnClientNonPerson(element.getAsJsonObject().get(ClientApiConstants.clientNonPersonDetailsParamName));        
+        dataValidationErrors.addAll(dataValidationErrorsForClientNonPerson);
+        
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+    
+    List<ApiParameterError> getDataValidationErrorsForCreateOnClientNonPerson(JsonElement element)
+    {
+    	
+    	final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+        
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.incorpNumberParamName, element)) {
+            final String incorpNumber = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.incorpNumberParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.incorpNumberParamName).value(incorpNumber).ignoreIfNull()
+            		.notExceedingLengthOf(50);
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.remarksParamName, element)) {
+            final String remarks = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.remarksParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.remarksParamName).value(remarks).ignoreIfNull()
+                    .notExceedingLengthOf(150);
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.incorpValidityTillParamName, element)) {
+            final LocalDate incorpValidityTill = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.incorpValidityTillParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.incorpValidityTillParamName).value(incorpValidityTill).ignoreIfNull();
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.constitutionIdParamName, element)) {
+            final Integer constitution = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.constitutionIdParamName,
+                    element);
+            baseDataValidator.reset().parameter(ClientApiConstants.constitutionIdParamName).value(constitution).notNull().integerGreaterThanZero();
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mainBusinessLineIdParamName, element)) {
+            final Integer mainBusinessLine = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.mainBusinessLineIdParamName,
+                    element);
+            baseDataValidator.reset().parameter(ClientApiConstants.mainBusinessLineIdParamName).value(mainBusinessLine).integerGreaterThanZero();
+        }
+    	
+		return dataValidationErrors;    	
+    }
+
+    private void validateIndividualNamePartsCannotBeUsedWithFullname(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        final String firstnameParam = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.firstnameParamName, element);
+        if (StringUtils.isNotBlank(firstnameParam)) {
+            final String fullnameParam = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.fullnameParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.fullnameParamName).value(fullnameParam)
+                    .mustBeBlankWhenParameterProvided(ClientApiConstants.firstnameParamName, firstnameParam);
+        }
+
+        final String middlenameParam = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.middlenameParamName, element);
+        if (StringUtils.isNotBlank(middlenameParam)) {
+            final String fullnameParam = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.fullnameParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.fullnameParamName).value(fullnameParam)
+                    .mustBeBlankWhenParameterProvided(ClientApiConstants.middlenameParamName, middlenameParam);
+        }
+
+        final String lastnameParamName = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.lastnameParamName, element);
+        if (StringUtils.isNotBlank(lastnameParamName)) {
+            final String fullnameParam = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.fullnameParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.fullnameParamName).value(fullnameParam)
+                    .mustBeBlankWhenParameterProvided(ClientApiConstants.lastnameParamName, lastnameParamName);
+        }
+    }
+
+    private void validateRequiredIndividualNamePartsExist(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        final String firstnameParam = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.firstnameParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.firstnameParamName).value(firstnameParam).notBlank()
+                .notExceedingLengthOf(50);
+
+        final String middlenameParam = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.middlenameParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.middlenameParamName).value(middlenameParam).ignoreIfNull()
+                .notExceedingLengthOf(50);
+
+        final String lastnameParamName = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.lastnameParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.lastnameParamName).value(lastnameParamName).notBlank()
+                .notExceedingLengthOf(50);
+    }
+
+    private void fullnameCannotBeBlank(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        final String fullnameParam = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.fullnameParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.fullnameParamName).value(fullnameParam).notBlank().notExceedingLengthOf(100);
+    }
+
+    private boolean isIndividualNamePartParameterPassed(final JsonElement element) {
+        return this.fromApiJsonHelper.parameterExists(ClientApiConstants.firstnameParamName, element)
+                || this.fromApiJsonHelper.parameterExists(ClientApiConstants.middlenameParamName, element)
+                || this.fromApiJsonHelper.parameterExists(ClientApiConstants.lastnameParamName, element);
+    }
+
+    private boolean isFullnameParameterPassed(final JsonElement element) {
+        return this.fromApiJsonHelper.parameterExists(ClientApiConstants.fullnameParamName, element);
+    }
+
+    private boolean isIndividualNameProvided(final JsonElement element) {
+        final String firstname = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.firstnameParamName, element);
+        final String middlename = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.middlenameParamName, element);
+        final String lastname = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.lastnameParamName, element);
+
+        return StringUtils.isNotBlank(firstname) || StringUtils.isNotBlank(middlename) || StringUtils.isNotBlank(lastname);
+    }
+
+    private boolean isFullnameProvided(final JsonElement element) {
+        final String fullname = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.fullnameParamName, element);
+        return StringUtils.isNotBlank(fullname);
+    }
+
+    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, ClientApiConstants.CLIENT_UPDATE_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.clientNonPersonDetailsParamName, element)) {
+	        final String clientNonPersonJson = this.fromApiJsonHelper.toJson(element.getAsJsonObject().get(ClientApiConstants.clientNonPersonDetailsParamName));
+	        if(clientNonPersonJson != null)
+	        	this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, clientNonPersonJson, ClientApiConstants.CLIENT_NON_PERSON_UPDATE_REQUEST_DATA_PARAMETERS);
+        }
+	        
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        boolean atLeastOneParameterPassedForUpdate = false;
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.accountNoParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String accountNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.accountNoParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20);
+        }
+
+        if (isFullnameProvided(element) || isIndividualNameProvided(element)) {
+
+            // 1. No individual name part provided and fullname provided
+            if (isFullnameProvided(element) && !isIndividualNameProvided(element)) {
+                fullnameCannotBeBlank(element, baseDataValidator);
+            }
+
+            // 2. no fullname provided and individual name part provided
+            if (isIndividualNameProvided(element) && !isFullnameProvided(element)) {
+                validateRequiredIndividualNamePartsExist(element, baseDataValidator);
+            }
+
+            // 3. both provided
+            if (isFullnameProvided(element) && isIndividualNameProvided(element)) {
+                validateIndividualNamePartsCannotBeUsedWithFullname(element, baseDataValidator);
+            }
+        } else {
+
+            if (isFullnameParameterPassed(element) || isIndividualNamePartParameterPassed(element)) {
+
+                // 1. No individual name parameter passed and fullname passed
+                if (isFullnameParameterPassed(element) && !isIndividualNamePartParameterPassed(element)) {
+                    fullnameCannotBeBlank(element, baseDataValidator);
+                }
+
+                // 2. no fullname passed and individual name part passed
+                if (isIndividualNamePartParameterPassed(element) && !isFullnameParameterPassed(element)) {
+                    validateRequiredIndividualNamePartsExist(element, baseDataValidator);
+                }
+
+                // 3. both parameter types passed
+                if (isFullnameParameterPassed(element) && isIndividualNamePartParameterPassed(element)) {
+                    baseDataValidator.reset().parameter(ClientApiConstants.idParamName).failWithCode(".no.name.details.passed");
+                }
+
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.fullnameParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.lastnameParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.middlenameParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.firstnameParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.externalIdParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.externalIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mobileNoParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).notExceedingLengthOf(50);
+        }
+
+        final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(ClientApiConstants.activeParamName, element);
+        if (active != null) {
+            atLeastOneParameterPassedForUpdate = true;
+            if (active.booleanValue()) {
+                final LocalDate joinedDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.activationDateParamName,
+                        element);
+                baseDataValidator.reset().parameter(ClientApiConstants.activationDateParamName).value(joinedDate).notNull();
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.staffIdParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.staffIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.staffIdParamName).value(staffId).ignoreIfNull().longGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.savingsProductIdParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Long savingsProductId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.savingsProductIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.savingsProductIdParamName).value(savingsProductId).ignoreIfNull()
+                    .longGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.dateOfBirthParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final LocalDate dateOfBirth = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.dateOfBirthParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.dateOfBirthParamName).value(dateOfBirth).notNull()
+                    .validateDateBefore(DateUtils.getLocalDateOfTenant());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.genderIdParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer genderId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.genderIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.genderIdParamName).value(genderId).integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.clientTypeIdParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer clientType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.clientTypeIdParamName,
+                    element);
+            baseDataValidator.reset().parameter(ClientApiConstants.clientTypeIdParamName).value(clientType).integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.clientClassificationIdParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer clientClassification = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    ClientApiConstants.clientClassificationIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.clientClassificationIdParamName).value(clientClassification)
+                    .integerGreaterThanZero();
+        }
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.submittedOnDateParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final LocalDate submittedDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.submittedOnDateParamName,
+                    element);
+            baseDataValidator.reset().parameter(ClientApiConstants.submittedOnDateParamName).value(submittedDate).notNull();
+
+        }
+        
+        if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.legalFormIdParamName, element)) {
+        	atLeastOneParameterPassedForUpdate = true;
+        	final Integer legalFormId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.legalFormIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.legalFormIdParamName).value(legalFormId).ignoreIfNull().inMinMaxRange(1, 2);
+        }
+
+        Map<String, Object> parameterUpdateStatusDetails = getParameterUpdateStatusAndDataValidationErrorsForUpdateOnClientNonPerson(element.getAsJsonObject().get(ClientApiConstants.clientNonPersonDetailsParamName));
+        boolean atLeastOneParameterPassedForClientNonPersonUpdate = (boolean) parameterUpdateStatusDetails.get("parameterUpdateStatus");
+               
+        if (!atLeastOneParameterPassedForUpdate && !atLeastOneParameterPassedForClientNonPersonUpdate) {
+            final Object forceError = null;
+            baseDataValidator.reset().anyOfNotNull(forceError);
+        }
+        
+        @SuppressWarnings("unchecked")
+		List<ApiParameterError> dataValidationErrorsForClientNonPerson = (List<ApiParameterError>) parameterUpdateStatusDetails.get("dataValidationErrors");        
+        dataValidationErrors.addAll(dataValidationErrorsForClientNonPerson);
+        
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+    
+    Map<String, Object> getParameterUpdateStatusAndDataValidationErrorsForUpdateOnClientNonPerson(JsonElement element)
+    {
+    	boolean atLeastOneParameterPassedForUpdate = false;
+    	final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+    	
+    	if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.incorpNumberParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String incorpNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.incorpNumberParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.incorpNumberParamName).value(incorpNo).ignoreIfNull().notExceedingLengthOf(50);
+        }
+    	
+    	if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.remarksParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String remarks = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.remarksParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.remarksParamName).value(remarks).notExceedingLengthOf(150);
+        }
+    	
+    	if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.incorpValidityTillParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final LocalDate incorpValidityTill = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.incorpValidityTillParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.incorpValidityTillParamName).value(incorpValidityTill);
+        }
+    	
+    	if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.constitutionIdParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer constitutionId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.constitutionIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.constitutionIdParamName).value(constitutionId).notNull().integerGreaterThanZero();
+        }
+    	
+    	if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mainBusinessLineIdParamName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer mainBusinessLineId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.mainBusinessLineIdParamName, element);
+            baseDataValidator.reset().parameter(ClientApiConstants.mainBusinessLineIdParamName).value(mainBusinessLineId).integerGreaterThanZero();
+        }
+    	
+    	Map<String, Object> parameterUpdateStatusDetails = new HashMap<>();
+    	parameterUpdateStatusDetails.put("parameterUpdateStatus", atLeastOneParameterPassedForUpdate);
+    	parameterUpdateStatusDetails.put("dataValidationErrors", dataValidationErrors);
+    	
+		return parameterUpdateStatusDetails;
+    	
+    }
+
+    public void validateActivation(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, ClientApiConstants.ACTIVATION_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.activationDateParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.activationDateParamName).value(activationDate).notNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            //
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    public void validateForUnassignStaff(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Set<String> supportedParametersUnassignStaff = new HashSet<>(Arrays.asList(ClientApiConstants.staffIdParamName));
+
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersUnassignStaff);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final String staffIdParameterName = ClientApiConstants.staffIdParamName;
+        final Long staffId = this.fromApiJsonHelper.extractLongNamed(staffIdParameterName, element);
+        baseDataValidator.reset().parameter(staffIdParameterName).value(staffId).notNull().longGreaterThanZero();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+
+    public void validateForAssignStaff(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Set<String> supportedParametersUnassignStaff = new HashSet<>(Arrays.asList(ClientApiConstants.staffIdParamName));
+
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersUnassignStaff);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final String staffIdParameterName = ClientApiConstants.staffIdParamName;
+        final Long staffId = this.fromApiJsonHelper.extractLongNamed(staffIdParameterName, element);
+        baseDataValidator.reset().parameter(staffIdParameterName).value(staffId).notNull().longGreaterThanZero();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+
+    public void validateClose(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, ClientApiConstants.CLIENT_CLOSE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate closureDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.closureDateParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.closureDateParamName).value(closureDate).notNull();
+
+        final Long closureReasonId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.closureReasonIdParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.closureReasonIdParamName).value(closureReasonId).notNull()
+                .longGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForSavingsAccount(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Set<String> supportedParameters = new HashSet<>(Arrays.asList(ClientApiConstants.savingsAccountIdParamName));
+
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final String savingsIdParameterName = ClientApiConstants.savingsAccountIdParamName;
+        final Long savingsId = this.fromApiJsonHelper.extractLongNamed(savingsIdParameterName, element);
+        baseDataValidator.reset().parameter(savingsIdParameterName).value(savingsId).notNull().longGreaterThanZero();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+
+    public void validateRejection(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, ClientApiConstants.CLIENT_REJECT_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate rejectionDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.rejectionDateParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.rejectionDateParamName).value(rejectionDate).notNull();
+
+        final Long rejectionReasonId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.rejectionReasonIdParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.rejectionReasonIdParamName).value(rejectionReasonId).notNull()
+                .longGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    public void validateWithdrawn(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, ClientApiConstants.CLIENT_WITHDRAW_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate withdrawalDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.withdrawalDateParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.withdrawalDateParamName).value(withdrawalDate).notNull();
+
+        final Long withdrawalReasonId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.withdrawalReasonIdParamName, element);
+        baseDataValidator.reset().parameter(ClientApiConstants.withdrawalReasonIdParamName).value(withdrawalReasonId).notNull()
+                .longGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    public void validateReactivate(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, ClientApiConstants.REACTIVATION_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate reactivationDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.reactivationDateParamName,
+                element);
+        baseDataValidator.reset().parameter(ClientApiConstants.reactivationDateParamName).value(reactivationDate).notNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientIdentifierData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientIdentifierData.java
new file mode 100644
index 0000000..2f3edb4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientIdentifierData.java
@@ -0,0 +1,60 @@
+/**
+ * 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.client.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+
+/**
+ * Immutable data object represent client identity data.
+ */
+public class ClientIdentifierData {
+
+    private final Long id;
+    private final Long clientId;
+    private final CodeValueData documentType;
+    private final String documentKey;
+    private final String description;
+    @SuppressWarnings("unused")
+    private final Collection<CodeValueData> allowedDocumentTypes;
+
+    public static ClientIdentifierData singleItem(final Long id, final Long clientId, final CodeValueData documentType,
+            final String documentKey, final String description) {
+        return new ClientIdentifierData(id, clientId, documentType, documentKey, description, null);
+    }
+
+    public static ClientIdentifierData template(final Collection<CodeValueData> codeValues) {
+        return new ClientIdentifierData(null, null, null, null, null, codeValues);
+    }
+
+    public static ClientIdentifierData template(final ClientIdentifierData data, final Collection<CodeValueData> codeValues) {
+        return new ClientIdentifierData(data.id, data.clientId, data.documentType, data.documentKey, data.description, codeValues);
+    }
+
+    public ClientIdentifierData(final Long id, final Long clientId, final CodeValueData documentType, final String documentKey,
+            final String description, final Collection<CodeValueData> allowedDocumentTypes) {
+        this.id = id;
+        this.clientId = clientId;
+        this.documentType = documentType;
+        this.documentKey = documentKey;
+        this.description = description;
+        this.allowedDocumentTypes = allowedDocumentTypes;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientNonPersonData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientNonPersonData.java
new file mode 100644
index 0000000..d6ffdb9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientNonPersonData.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.data;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing the ClientNonPerson
+ */
+@SuppressWarnings("unused")
+public class ClientNonPersonData {
+	
+	private final CodeValueData constitution;
+	private final String incorpNumber;
+	private final LocalDate incorpValidityTillDate;
+    private final CodeValueData mainBusinessLine;
+    private final String remarks;
+    
+	public ClientNonPersonData(CodeValueData constitution, String incorpNo, LocalDate incorpValidityTillDate,
+			CodeValueData mainBusinessLine, String remarks) {
+		super();
+		this.constitution = constitution;
+		this.incorpNumber = incorpNo;
+		this.incorpValidityTillDate = incorpValidityTillDate;
+		this.mainBusinessLine = mainBusinessLine;
+		this.remarks = remarks;
+	}
+        
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientTimelineData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientTimelineData.java
new file mode 100644
index 0000000..2189ffd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientTimelineData.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.client.data;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
+import org.joda.time.LocalDate;
+
+import java.math.BigDecimal;
+
+/**
+ * Immutable data object represent the important time-line events of a loan
+ * application and loan.
+ */
+@SuppressWarnings("unused")
+public class ClientTimelineData {
+
+    private final LocalDate submittedOnDate;
+    private final String submittedByUsername;
+    private final String submittedByFirstname;
+    private final String submittedByLastname;
+
+    private final LocalDate activatedOnDate;
+    private final String activatedByUsername;
+    private final String activatedByFirstname;
+    private final String activatedByLastname;
+
+    private final LocalDate closedOnDate;
+    private final String closedByUsername;
+    private final String closedByFirstname;
+    private final String closedByLastname;
+
+    public ClientTimelineData(final LocalDate submittedOnDate, final String submittedByUsername, final String submittedByFirstname,
+            final String submittedByLastname, final LocalDate activatedOnDate, final String activatedByUsername,
+            final String activatedByFirstname, final String activatedByLastname, final LocalDate closedOnDate,
+            final String closedByUsername, final String closedByFirstname, final String closedByLastname) {
+        this.submittedOnDate = submittedOnDate;
+        this.submittedByUsername = submittedByUsername;
+        this.submittedByFirstname = submittedByFirstname;
+        this.submittedByLastname = submittedByLastname;
+
+        this.activatedOnDate = activatedOnDate;
+        this.activatedByUsername = activatedByUsername;
+        this.activatedByFirstname = activatedByFirstname;
+        this.activatedByLastname = activatedByLastname;
+
+        this.closedOnDate = closedOnDate;
+        this.closedByUsername = closedByUsername;
+        this.closedByFirstname = closedByFirstname;
+        this.closedByLastname = closedByLastname;
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientTransactionData.java
new file mode 100644
index 0000000..ff6fe26
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientTransactionData.java
@@ -0,0 +1,74 @@
+/**
+ * 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.client.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.joda.time.LocalDate;
+
+@SuppressWarnings("unused")
+public class ClientTransactionData {
+
+    private final Long id;
+    private final Long officeId;
+    private final String officeName;
+    private final EnumOptionData type;
+    private final LocalDate date;
+    private final CurrencyData currency;
+    private final PaymentDetailData paymentDetailData;
+    private final BigDecimal amount;
+    private final String externalId;
+    private final LocalDate submittedOnDate;
+    private final boolean reversed;
+
+    // templates
+    final Collection<PaymentTypeData> paymentTypeOptions;
+
+    public static ClientTransactionData create(Long id, Long officeId, String officeName, EnumOptionData type, LocalDate date,
+            CurrencyData currency, PaymentDetailData paymentDetailData, BigDecimal amount, String externalId, LocalDate submittedOnDate,
+            boolean reversed) {
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        return new ClientTransactionData(id, officeId, officeName, type, date, currency, paymentDetailData, amount, externalId,
+                submittedOnDate, reversed, paymentTypeOptions);
+    }
+
+    private ClientTransactionData(Long id, Long officeId, String officeName, EnumOptionData type, LocalDate date, CurrencyData currency,
+            PaymentDetailData paymentDetailData, BigDecimal amount, String externalId, LocalDate submittedOnDate, boolean reversed,
+            Collection<PaymentTypeData> paymentTypeOptions) {
+        super();
+        this.id = id;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.type = type;
+        this.date = date;
+        this.currency = currency;
+        this.paymentDetailData = paymentDetailData;
+        this.amount = amount;
+        this.externalId = externalId;
+        this.submittedOnDate = submittedOnDate;
+        this.reversed = reversed;
+        this.paymentTypeOptions = paymentTypeOptions;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/AccountNumberGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/AccountNumberGenerator.java
new file mode 100644
index 0000000..c251d1a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/AccountNumberGenerator.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.portfolio.client.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatEnumerations.AccountNumberPrefixType;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.springframework.stereotype.Component;
+
+/**
+ * Example {@link AccountNumberGenerator} for clients that takes an entities
+ * auto generated database id and zero fills it ensuring the identifier is
+ * always of a given <code>maxLength</code>.
+ */
+@Component
+public class AccountNumberGenerator {
+
+    private final static int maxLength = 9;
+
+    private final static String ID = "id";
+    private final static String CLIENT_TYPE = "clientType";
+    private final static String OFFICE_NAME = "officeName";
+    private final static String LOAN_PRODUCT_SHORT_NAME = "loanProductShortName";
+    private final static String SAVINGS_PRODUCT_SHORT_NAME = "savingsProductShortName";
+
+    public String generate(Client client, AccountNumberFormat accountNumberFormat) {
+        Map<String, String> propertyMap = new HashMap<>();
+        propertyMap.put(ID, client.getId().toString());
+        propertyMap.put(OFFICE_NAME, client.getOffice().getName());
+        CodeValue clientType = client.clientType();
+        if (clientType != null) {
+            propertyMap.put(CLIENT_TYPE, clientType.label());
+        }
+        return generateAccountNumber(propertyMap, accountNumberFormat);
+    }
+
+    public String generate(Loan loan, AccountNumberFormat accountNumberFormat) {
+        Map<String, String> propertyMap = new HashMap<>();
+        propertyMap.put(ID, loan.getId().toString());
+        propertyMap.put(OFFICE_NAME, loan.getOffice().getName());
+        propertyMap.put(LOAN_PRODUCT_SHORT_NAME, loan.loanProduct().getShortName());
+        return generateAccountNumber(propertyMap, accountNumberFormat);
+    }
+
+    public String generate(SavingsAccount savingsAccount, AccountNumberFormat accountNumberFormat) {
+        Map<String, String> propertyMap = new HashMap<>();
+        propertyMap.put(ID, savingsAccount.getId().toString());
+        propertyMap.put(OFFICE_NAME, savingsAccount.office().getName());
+        propertyMap.put(SAVINGS_PRODUCT_SHORT_NAME, savingsAccount.savingsProduct().getShortName());
+        return generateAccountNumber(propertyMap, accountNumberFormat);
+    }
+
+    private String generateAccountNumber(Map<String, String> propertyMap, AccountNumberFormat accountNumberFormat) {
+        String accountNumber = StringUtils.leftPad(propertyMap.get(ID), AccountNumberGenerator.maxLength, '0');
+        if (accountNumberFormat != null && accountNumberFormat.getPrefixEnum() != null) {
+            AccountNumberPrefixType accountNumberPrefixType = AccountNumberPrefixType.fromInt(accountNumberFormat.getPrefixEnum());
+            String prefix = null;
+            switch (accountNumberPrefixType) {
+                case CLIENT_TYPE:
+                    prefix = propertyMap.get(CLIENT_TYPE);
+                break;
+
+                case OFFICE_NAME:
+                    prefix = propertyMap.get(OFFICE_NAME);
+                break;
+
+                case LOAN_PRODUCT_SHORT_NAME:
+                    prefix = propertyMap.get(LOAN_PRODUCT_SHORT_NAME);
+                break;
+
+                case SAVINGS_PRODUCT_SHORT_NAME:
+                    prefix = propertyMap.get(SAVINGS_PRODUCT_SHORT_NAME);
+                break;
+
+                default:
+                break;
+
+            }
+            accountNumber = StringUtils.overlay(accountNumber, prefix, 0, 0);
+        }
+        return accountNumber;
+    }
+    
+    public String generateGroupAccountNumber(Group group, AccountNumberFormat accountNumberFormat) {
+    	Map<String, String> propertyMap = new HashMap<>();
+        propertyMap.put(ID, group.getId().toString());
+        propertyMap.put(OFFICE_NAME, group.getOffice().getName());        
+        return generateAccountNumber(propertyMap, accountNumberFormat);
+    }
+    
+    public String generateCenterAccountNumber(Group group, AccountNumberFormat accountNumberFormat) {
+    	Map<String, String> propertyMap = new HashMap<>();
+        propertyMap.put(ID, group.getId().toString());
+        propertyMap.put(OFFICE_NAME, group.getOffice().getName());        
+        return generateAccountNumber(propertyMap, accountNumberFormat);
+    }
+
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
new file mode 100644
index 0000000..6020c31
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
@@ -0,0 +1,963 @@
+/**
+ * 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.client.domain;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
+import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_client", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "account_no_UNIQUE"), //
+        @UniqueConstraint(columnNames = { "mobile_no" }, name = "mobile_no_UNIQUE") })
+public final class Client extends AbstractPersistable<Long> {
+
+    @Column(name = "account_no", length = 20, unique = true, nullable = false)
+    private String accountNumber;
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @ManyToOne
+    @JoinColumn(name = "transfer_to_office_id", nullable = true)
+    private Office transferToOffice;
+
+    @OneToOne(optional = true)
+    @JoinColumn(name = "image_id", nullable = true)
+    private Image image;
+
+    /**
+     * A value from {@link ClientStatus}.
+     */
+    @Column(name = "status_enum", nullable = false)
+    private Integer status;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "sub_status", nullable = true)
+    private CodeValue subStatus;
+    
+    @Column(name = "activation_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date activationDate;
+
+    @Column(name = "office_joining_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date officeJoiningDate;
+
+    @Column(name = "firstname", length = 50, nullable = true)
+    private String firstname;
+
+    @Column(name = "middlename", length = 50, nullable = true)
+    private String middlename;
+
+    @Column(name = "lastname", length = 50, nullable = true)
+    private String lastname;
+
+    @Column(name = "fullname", length = 100, nullable = true)
+    private String fullname;
+
+    @Column(name = "display_name", length = 100, nullable = false)
+    private String displayName;
+
+    @Column(name = "mobile_no", length = 50, nullable = false, unique = true)
+    private String mobileNo;
+
+    @Column(name = "external_id", length = 100, nullable = true, unique = true)
+    private String externalId;
+
+    @Column(name = "date_of_birth", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date dateOfBirth;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "gender_cv_id", nullable = true)
+    private CodeValue gender;
+
+    @ManyToOne
+    @JoinColumn(name = "staff_id")
+    private Staff staff;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @ManyToMany
+    @JoinTable(name = "m_group_client", joinColumns = @JoinColumn(name = "client_id"), inverseJoinColumns = @JoinColumn(name = "group_id"))
+    private Set<Group> groups;
+
+    @Transient
+    private boolean accountNumberRequiresAutoGeneration = false;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "closure_reason_cv_id", nullable = true)
+    private CodeValue closureReason;
+
+    @Column(name = "closedon_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date closureDate;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "reject_reason_cv_id", nullable = true)
+    private CodeValue rejectionReason;
+
+    @Column(name = "rejectedon_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date rejectionDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "rejectedon_userid", nullable = true)
+    private AppUser rejectedBy;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "withdraw_reason_cv_id", nullable = true)
+    private CodeValue withdrawalReason;
+
+    @Column(name = "withdrawn_on_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date withdrawalDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "withdraw_on_userid", nullable = true)
+    private AppUser withdrawnBy;
+
+    @Column(name = "reactivated_on_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date reactivateDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "reactivated_on_userid", nullable = true)
+    private AppUser reactivatedBy;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "closedon_userid", nullable = true)
+    private AppUser closedBy;
+
+    @Column(name = "submittedon_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date submittedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "submittedon_userid", nullable = true)
+    private AppUser submittedBy;
+
+    @Column(name = "updated_on", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date updatedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "updated_by", nullable = true)
+    private AppUser updatedBy;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "activatedon_userid", nullable = true)
+    private AppUser activatedBy;
+
+    @ManyToOne
+    @JoinColumn(name = "default_savings_product", nullable = true)
+    private SavingsProduct savingsProduct;
+
+    @ManyToOne
+    @JoinColumn(name = "default_savings_account", nullable = true)
+    private SavingsAccount savingsAccount;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "client_type_cv_id", nullable = true)
+    private CodeValue clientType;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "client_classification_cv_id", nullable = true)
+    private CodeValue clientClassification;
+    
+    @Column(name = "legal_form_enum", nullable = true)
+    private Integer legalForm;
+
+    public static Client createNew(final AppUser currentUser, final Office clientOffice, final Group clientParentGroup, final Staff staff,
+            final SavingsProduct savingsProduct, final CodeValue gender, final CodeValue clientType, final CodeValue clientClassification,
+            final Integer legalForm, final JsonCommand command) {
+
+        final String accountNo = command.stringValueOfParameterNamed(ClientApiConstants.accountNoParamName);
+        final String externalId = command.stringValueOfParameterNamed(ClientApiConstants.externalIdParamName);
+        final String mobileNo = command.stringValueOfParameterNamed(ClientApiConstants.mobileNoParamName);
+
+        final String firstname = command.stringValueOfParameterNamed(ClientApiConstants.firstnameParamName);
+        final String middlename = command.stringValueOfParameterNamed(ClientApiConstants.middlenameParamName);
+        final String lastname = command.stringValueOfParameterNamed(ClientApiConstants.lastnameParamName);
+        final String fullname = command.stringValueOfParameterNamed(ClientApiConstants.fullnameParamName);
+
+        final LocalDate dataOfBirth = command.localDateValueOfParameterNamed(ClientApiConstants.dateOfBirthParamName);
+
+        ClientStatus status = ClientStatus.PENDING;
+        boolean active = false;
+        if (command.hasParameter("active")) {
+            active = command.booleanPrimitiveValueOfParameterNamed(ClientApiConstants.activeParamName);
+        }
+
+        LocalDate activationDate = null;
+        LocalDate officeJoiningDate = null;
+        if (active) {
+            status = ClientStatus.ACTIVE;
+            activationDate = command.localDateValueOfParameterNamed(ClientApiConstants.activationDateParamName);
+            officeJoiningDate = activationDate;
+        }
+
+        LocalDate submittedOnDate = new LocalDate();
+        if (active && submittedOnDate.isAfter(activationDate)) {
+            submittedOnDate = activationDate;
+        }
+        if (command.hasParameter(ClientApiConstants.submittedOnDateParamName)) {
+            submittedOnDate = command.localDateValueOfParameterNamed(ClientApiConstants.submittedOnDateParamName);
+        }
+        final SavingsAccount account = null;
+        return new Client(currentUser, status, clientOffice, clientParentGroup, accountNo, firstname, middlename, lastname, fullname,
+                activationDate, officeJoiningDate, externalId, mobileNo, staff, submittedOnDate, savingsProduct, account, dataOfBirth,
+                gender, clientType, clientClassification, legalForm);
+    }
+
+    protected Client() {
+    	this.setLegalForm(null);
+    }
+
+    private Client(final AppUser currentUser, final ClientStatus status, final Office office, final Group clientParentGroup,
+            final String accountNo, final String firstname, final String middlename, final String lastname, final String fullname,
+            final LocalDate activationDate, final LocalDate officeJoiningDate, final String externalId, final String mobileNo,
+            final Staff staff, final LocalDate submittedOnDate, final SavingsProduct savingsProduct, final SavingsAccount savingsAccount,
+            final LocalDate dateOfBirth, final CodeValue gender, final CodeValue clientType, final CodeValue clientClassification, final Integer legalForm) {
+
+        if (StringUtils.isBlank(accountNo)) {
+            this.accountNumber = new RandomPasswordGenerator(19).generate();
+            this.accountNumberRequiresAutoGeneration = true;
+        } else {
+            this.accountNumber = accountNo;
+        }
+
+        this.submittedOnDate = submittedOnDate.toDate();
+        this.submittedBy = currentUser;
+
+        this.status = status.getValue();
+        this.office = office;
+        if (StringUtils.isNotBlank(externalId)) {
+            this.externalId = externalId.trim();
+        } else {
+            this.externalId = null;
+        }
+
+        if (StringUtils.isNotBlank(mobileNo)) {
+            this.mobileNo = mobileNo.trim();
+        } else {
+            this.mobileNo = null;
+        }
+
+        if (activationDate != null) {
+            this.activationDate = activationDate.toDateTimeAtStartOfDay().toDate();
+            this.activatedBy = currentUser;
+        }
+        if (officeJoiningDate != null) {
+            this.officeJoiningDate = officeJoiningDate.toDateTimeAtStartOfDay().toDate();
+        }
+        if (StringUtils.isNotBlank(firstname)) {
+            this.firstname = firstname.trim();
+        } else {
+            this.firstname = null;
+        }
+
+        if (StringUtils.isNotBlank(middlename)) {
+            this.middlename = middlename.trim();
+        } else {
+            this.middlename = null;
+        }
+
+        if (StringUtils.isNotBlank(lastname)) {
+            this.lastname = lastname.trim();
+        } else {
+            this.lastname = null;
+        }
+
+        if (StringUtils.isNotBlank(fullname)) {
+            this.fullname = fullname.trim();
+        } else {
+            this.fullname = null;
+        }
+
+        if (clientParentGroup != null) {
+            this.groups = new HashSet<>();
+            this.groups.add(clientParentGroup);
+        }
+
+        this.staff = staff;
+        this.savingsProduct = savingsProduct;
+        this.savingsAccount = savingsAccount;
+
+        if (gender != null) {
+            this.gender = gender;
+        }
+        if (dateOfBirth != null) {
+            this.dateOfBirth = dateOfBirth.toDateTimeAtStartOfDay().toDate();
+        }
+        this.clientType = clientType;
+        this.clientClassification = clientClassification;
+        this.setLegalForm(legalForm);
+
+        deriveDisplayName();
+        validate();
+    }
+
+    private void validate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        validateNameParts(dataValidationErrors);
+        validateActivationDate(dataValidationErrors);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+    
+    private void validateUpdate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        //Not validating name parts while update request as firstname/lastname can be along with fullname 
+        //when we change clientType from Individual to Organisation or vice-cersa
+        validateActivationDate(dataValidationErrors);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+
+    public boolean isAccountNumberRequiresAutoGeneration() {
+        return this.accountNumberRequiresAutoGeneration;
+    }
+
+    public void setAccountNumberRequiresAutoGeneration(final boolean accountNumberRequiresAutoGeneration) {
+        this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
+    }
+
+    public boolean identifiedBy(final String identifier) {
+        return identifier.equalsIgnoreCase(this.externalId);
+    }
+
+    public boolean identifiedBy(final Long clientId) {
+        return getId().equals(clientId);
+    }
+
+    public void updateAccountNo(final String accountIdentifier) {
+        this.accountNumber = accountIdentifier;
+        this.accountNumberRequiresAutoGeneration = false;
+    }
+
+    public void activate(final AppUser currentUser, final DateTimeFormatter formatter, final LocalDate activationLocalDate) {
+
+        if (isActive()) {
+            final String defaultUserMessage = "Cannot activate client. Client is already active.";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.already.active", defaultUserMessage,
+                    ClientApiConstants.activationDateParamName, activationLocalDate.toString(formatter));
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        this.activationDate = activationLocalDate.toDate();
+        this.activatedBy = currentUser;
+        this.officeJoiningDate = this.activationDate;
+        this.status = ClientStatus.ACTIVE.getValue();
+
+        validate();
+    }
+
+    public boolean isNotActive() {
+        return !isActive();
+    }
+
+    public boolean isActive() {
+        return ClientStatus.fromInt(this.status).isActive();
+    }
+
+    public boolean isClosed() {
+        return ClientStatus.fromInt(this.status).isClosed();
+    }
+
+    public boolean isTransferInProgress() {
+        return ClientStatus.fromInt(this.status).isTransferInProgress();
+    }
+
+    public boolean isTransferOnHold() {
+        return ClientStatus.fromInt(this.status).isTransferOnHold();
+    }
+
+    public boolean isTransferInProgressOrOnHold() {
+        return isTransferInProgress() || isTransferOnHold();
+    }
+
+    public boolean isNotPending() {
+        return !isPending();
+    }
+
+    public boolean isPending() {
+        return ClientStatus.fromInt(this.status).isPending();
+    }
+
+    private boolean isDateInTheFuture(final LocalDate localDate) {
+        return localDate.isAfter(DateUtils.getLocalDateOfTenant());
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+        if (command.isChangeInIntegerParameterNamed(ClientApiConstants.statusParamName, this.status)) {
+            final Integer newValue = command.integerValueOfParameterNamed(ClientApiConstants.statusParamName);
+            actualChanges.put(ClientApiConstants.statusParamName, ClientEnumerations.status(newValue));
+            this.status = ClientStatus.fromInt(newValue).getValue();
+        }
+
+        if (command.isChangeInStringParameterNamed(ClientApiConstants.accountNoParamName, this.accountNumber)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.accountNoParamName);
+            actualChanges.put(ClientApiConstants.accountNoParamName, newValue);
+            this.accountNumber = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(ClientApiConstants.externalIdParamName, this.externalId)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.externalIdParamName);
+            actualChanges.put(ClientApiConstants.externalIdParamName, newValue);
+            this.externalId = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(ClientApiConstants.mobileNoParamName, this.mobileNo)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.mobileNoParamName);
+            actualChanges.put(ClientApiConstants.mobileNoParamName, newValue);
+            this.mobileNo = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(ClientApiConstants.firstnameParamName, this.firstname)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.firstnameParamName);
+            actualChanges.put(ClientApiConstants.firstnameParamName, newValue);
+            this.firstname = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(ClientApiConstants.middlenameParamName, this.middlename)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.middlenameParamName);
+            actualChanges.put(ClientApiConstants.middlenameParamName, newValue);
+            this.middlename = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(ClientApiConstants.lastnameParamName, this.lastname)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.lastnameParamName);
+            actualChanges.put(ClientApiConstants.lastnameParamName, newValue);
+            this.lastname = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(ClientApiConstants.fullnameParamName, this.fullname)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.fullnameParamName);
+            actualChanges.put(ClientApiConstants.fullnameParamName, newValue);
+            this.fullname = newValue;
+        }
+
+        if (command.isChangeInLongParameterNamed(ClientApiConstants.staffIdParamName, staffId())) {
+            final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.staffIdParamName);
+            actualChanges.put(ClientApiConstants.staffIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(ClientApiConstants.genderIdParamName, genderId())) {
+            final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.genderIdParamName);
+            actualChanges.put(ClientApiConstants.genderIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(ClientApiConstants.savingsProductIdParamName, savingsProductId())) {
+            final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.savingsProductIdParamName);
+            actualChanges.put(ClientApiConstants.savingsProductIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(ClientApiConstants.genderIdParamName, genderId())) {
+            final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.genderIdParamName);
+            actualChanges.put(ClientApiConstants.genderIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(ClientApiConstants.clientTypeIdParamName, clientTypeId())) {
+            final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.clientTypeIdParamName);
+            actualChanges.put(ClientApiConstants.clientTypeIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(ClientApiConstants.clientClassificationIdParamName, clientClassificationId())) {
+            final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.clientClassificationIdParamName);
+            actualChanges.put(ClientApiConstants.clientClassificationIdParamName, newValue);
+        }
+        
+        if (command.isChangeInIntegerParameterNamed(ClientApiConstants.legalFormIdParamName, this.getLegalForm())) {
+            final Integer newValue = command.integerValueOfParameterNamed(ClientApiConstants.legalFormIdParamName);
+            if(newValue != null)
+            {
+            	LegalForm legalForm = LegalForm.fromInt(newValue);
+            	if(legalForm != null)
+            	{
+            		actualChanges.put(ClientApiConstants.legalFormIdParamName, ClientEnumerations.legalForm(newValue));
+                    this.setLegalForm(legalForm.getValue());
+            	}
+            	else
+            	{
+            		actualChanges.put(ClientApiConstants.legalFormIdParamName, null);
+                    this.setLegalForm(null);
+            	}
+            }
+            else
+            {
+            	actualChanges.put(ClientApiConstants.legalFormIdParamName, null);
+                this.setLegalForm(null);
+            }
+        }
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+
+        if (command.isChangeInLocalDateParameterNamed(ClientApiConstants.activationDateParamName, getActivationLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(ClientApiConstants.activationDateParamName);
+            actualChanges.put(ClientApiConstants.activationDateParamName, valueAsInput);
+            actualChanges.put(ClientApiConstants.dateFormatParamName, dateFormatAsInput);
+            actualChanges.put(ClientApiConstants.localeParamName, localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(ClientApiConstants.activationDateParamName);
+            this.activationDate = newValue.toDate();
+            this.officeJoiningDate = this.activationDate;
+        }
+
+        if (command.isChangeInLocalDateParameterNamed(ClientApiConstants.dateOfBirthParamName, dateOfBirthLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(ClientApiConstants.dateOfBirthParamName);
+            actualChanges.put(ClientApiConstants.dateOfBirthParamName, valueAsInput);
+            actualChanges.put(ClientApiConstants.dateFormatParamName, dateFormatAsInput);
+            actualChanges.put(ClientApiConstants.localeParamName, localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(ClientApiConstants.dateOfBirthParamName);
+            this.dateOfBirth = newValue.toDate();
+        }
+
+        if (command.isChangeInLocalDateParameterNamed(ClientApiConstants.submittedOnDateParamName, getSubmittedOnDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(ClientApiConstants.submittedOnDateParamName);
+            actualChanges.put(ClientApiConstants.submittedOnDateParamName, valueAsInput);
+            actualChanges.put(ClientApiConstants.dateFormatParamName, dateFormatAsInput);
+            actualChanges.put(ClientApiConstants.localeParamName, localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(ClientApiConstants.submittedOnDateParamName);
+            this.submittedOnDate = newValue.toDate();
+        }
+
+        validateUpdate();
+
+        deriveDisplayName();
+
+        return actualChanges;
+    }
+
+    private void validateNameParts(final List<ApiParameterError> dataValidationErrors) {
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("client");
+
+        if (StringUtils.isNotBlank(this.fullname)) {
+
+            baseDataValidator.reset().parameter(ClientApiConstants.firstnameParamName).value(this.firstname)
+                    .mustBeBlankWhenParameterProvided(ClientApiConstants.fullnameParamName, this.fullname);
+
+            baseDataValidator.reset().parameter(ClientApiConstants.middlenameParamName).value(this.middlename)
+                    .mustBeBlankWhenParameterProvided(ClientApiConstants.fullnameParamName, this.fullname);
+
+            baseDataValidator.reset().parameter(ClientApiConstants.lastnameParamName).value(this.lastname)
+                    .mustBeBlankWhenParameterProvided(ClientApiConstants.fullnameParamName, this.fullname);
+        } else {
+
+            baseDataValidator.reset().parameter(ClientApiConstants.firstnameParamName).value(this.firstname).notBlank()
+                    .notExceedingLengthOf(50);
+            baseDataValidator.reset().parameter(ClientApiConstants.middlenameParamName).value(this.middlename).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+            baseDataValidator.reset().parameter(ClientApiConstants.lastnameParamName).value(this.lastname).notBlank()
+                    .notExceedingLengthOf(50);
+        }
+    }
+
+    private void validateActivationDate(final List<ApiParameterError> dataValidationErrors) {
+
+        if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) {
+
+            final String defaultUserMessage = "submitted date cannot be in the future.";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.in.the.future",
+                    defaultUserMessage, ClientApiConstants.submittedOnDateParamName, this.submittedOnDate);
+
+            dataValidationErrors.add(error);
+        }
+
+        if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) {
+
+            final String defaultUserMessage = "submitted date cannot be after the activation date";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.after.activation.date",
+                    defaultUserMessage, ClientApiConstants.submittedOnDateParamName, this.submittedOnDate);
+
+            dataValidationErrors.add(error);
+        }
+
+        if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) {
+
+            final String defaultUserMessage = "Activation date cannot be in the future.";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.activationDate.in.the.future",
+                    defaultUserMessage, ClientApiConstants.activationDateParamName, getActivationLocalDate());
+
+            dataValidationErrors.add(error);
+        }
+
+        if (getActivationLocalDate() != null) {
+            if (this.office.isOpeningDateAfter(getActivationLocalDate())) {
+                final String defaultUserMessage = "Client activation date cannot be a date before the office opening date.";
+                final ApiParameterError error = ApiParameterError.parameterError(
+                        "error.msg.clients.activationDate.cannot.be.before.office.activation.date", defaultUserMessage,
+                        ClientApiConstants.activationDateParamName, getActivationLocalDate());
+                dataValidationErrors.add(error);
+            }
+        }
+    }
+
+    private void deriveDisplayName() {
+
+        StringBuilder nameBuilder = new StringBuilder();
+        Integer legalForm = this.getLegalForm();
+        if(legalForm == null || LegalForm.fromInt(legalForm).isPerson())
+        {
+        	if (StringUtils.isNotBlank(this.firstname)) {
+                nameBuilder.append(this.firstname).append(' ');
+            }
+
+            if (StringUtils.isNotBlank(this.middlename)) {
+                nameBuilder.append(this.middlename).append(' ');
+            }
+
+            if (StringUtils.isNotBlank(this.lastname)) {
+                nameBuilder.append(this.lastname);
+            }
+        }
+        else if(LegalForm.fromInt(legalForm).isEntity())
+        {
+        	if (StringUtils.isNotBlank(this.fullname)) {
+                nameBuilder = new StringBuilder(this.fullname);
+            }
+        }
+        
+        this.displayName = nameBuilder.toString();
+    }
+
+    public LocalDate getSubmittedOnDate() {
+        return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.submittedOnDate), null);
+    }
+
+    public LocalDate getActivationLocalDate() {
+        LocalDate activationLocalDate = null;
+        if (this.activationDate != null) {
+            activationLocalDate = LocalDate.fromDateFields(this.activationDate);
+        }
+        return activationLocalDate;
+    }
+
+    public LocalDate getOfficeJoiningLocalDate() {
+        LocalDate officeJoiningLocalDate = null;
+        if (this.officeJoiningDate != null) {
+            officeJoiningLocalDate = LocalDate.fromDateFields(this.officeJoiningDate);
+        }
+        return officeJoiningLocalDate;
+    }
+
+    public boolean isOfficeIdentifiedBy(final Long officeId) {
+        return this.office.identifiedBy(officeId);
+    }
+
+    public Long officeId() {
+        return this.office.getId();
+    }
+
+    public void setImage(final Image image) {
+        this.image = image;
+    }
+
+    public Image getImage() {
+        return this.image;
+    }
+
+    public String mobileNo() {
+        return this.mobileNo;
+    }
+
+    public void setMobileNo(final String mobileNo) {
+        this.mobileNo = mobileNo;
+    }
+
+    public String getDisplayName() {
+        return this.displayName;
+    }
+
+    public void setDisplayName(final String displayName) {
+        this.displayName = displayName;
+    }
+
+    public Office getOffice() {
+        return this.office;
+    }
+
+    public Office getTransferToOffice() {
+        return this.transferToOffice;
+    }
+
+    public void updateOffice(final Office office) {
+        this.office = office;
+    }
+
+    public void updateTransferToOffice(final Office office) {
+        this.transferToOffice = office;
+    }
+
+    public void updateOfficeJoiningDate(final Date date) {
+        this.officeJoiningDate = date;
+    }
+
+    private Long staffId() {
+        Long staffId = null;
+        if (this.staff != null) {
+            staffId = this.staff.getId();
+        }
+        return staffId;
+    }
+
+    public void updateStaff(final Staff staff) {
+        this.staff = staff;
+    }
+
+    public Staff getStaff() {
+        return this.staff;
+    }
+
+    public void unassignStaff() {
+        this.staff = null;
+    }
+
+    public void assignStaff(final Staff staff) {
+        this.staff = staff;
+    }
+
+    public Set<Group> getGroups() {
+        return this.groups;
+    }
+
+    public void close(final AppUser currentUser, final CodeValue closureReason, final Date closureDate) {
+        this.closureReason = closureReason;
+        this.closureDate = closureDate;
+        this.closedBy = currentUser;
+        this.status = ClientStatus.CLOSED.getValue();
+    }
+
+    public Integer getStatus() {
+        return this.status;
+    }
+    
+    public CodeValue subStatus() {
+        return this.subStatus;
+    }
+    
+    public Long subStatusId() {
+        Long subStatusId = null;
+        if (this.subStatus != null) {
+            subStatusId = this.subStatus.getId();
+        }
+        return subStatusId;
+    }
+
+    public void setStatus(final Integer status) {
+        this.status = status;
+    }
+
+    public boolean isActivatedAfter(final LocalDate submittedOn) {
+        return getActivationLocalDate().isAfter(submittedOn);
+    }
+
+    public boolean isChildOfGroup(final Long groupId) {
+        if (groupId != null && this.groups != null && !this.groups.isEmpty()) {
+            for (final Group group : this.groups) {
+                if (group.getId().equals(groupId)) { return true; }
+            }
+        }
+        return false;
+    }
+
+    private Long savingsProductId() {
+        Long savingsProductId = null;
+        if (this.savingsProduct != null) {
+            savingsProductId = this.savingsProduct.getId();
+        }
+        return savingsProductId;
+    }
+
+    public SavingsProduct SavingsProduct() {
+        return this.savingsProduct;
+    }
+
+    public void updateSavingsProduct(SavingsProduct savingsProduct) {
+        this.savingsProduct = savingsProduct;
+    }
+
+    public AppUser activatedBy() {
+        return this.activatedBy;
+    }
+
+    public SavingsAccount savingsAccount() {
+        return this.savingsAccount;
+    }
+
+    public void updateSavingsAccount(SavingsAccount savingsAccount) {
+        this.savingsAccount = savingsAccount;
+    }
+
+    public Long genderId() {
+        Long genderId = null;
+        if (this.gender != null) {
+            genderId = this.gender.getId();
+        }
+        return genderId;
+    }
+
+    public Long clientTypeId() {
+        Long clientTypeId = null;
+        if (this.clientType != null) {
+            clientTypeId = this.clientType.getId();
+        }
+        return clientTypeId;
+    }
+
+    public Long clientClassificationId() {
+        Long clientClassificationId = null;
+        if (this.clientClassification != null) {
+            clientClassificationId = this.clientClassification.getId();
+        }
+        return clientClassificationId;
+    }
+
+    public LocalDate getClosureDate() {
+        return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.closureDate), null);
+    }
+
+    public CodeValue gender() {
+        return this.gender;
+    }
+
+    public CodeValue clientType() {
+        return this.clientType;
+    }
+
+    public void updateClientType(CodeValue clientType) {
+        this.clientType = clientType;
+    }
+
+    public CodeValue clientClassification() {
+        return this.clientClassification;
+    }
+
+    public void updateClientClassification(CodeValue clientClassification) {
+        this.clientClassification = clientClassification;
+    }
+
+    public void updateGender(CodeValue gender) {
+        this.gender = gender;
+    }
+
+    public Date dateOfBirth() {
+        return this.dateOfBirth;
+    }
+
+    public LocalDate dateOfBirthLocalDate() {
+        LocalDate dateOfBirth = null;
+        if (this.dateOfBirth != null) {
+            dateOfBirth = LocalDate.fromDateFields(this.dateOfBirth);
+        }
+        return dateOfBirth;
+    }
+
+    public void reject(AppUser currentUser, CodeValue rejectionReason, Date rejectionDate) {
+        this.rejectionReason = rejectionReason;
+        this.rejectionDate = rejectionDate;
+        this.rejectedBy = currentUser;
+        this.updatedBy = currentUser;
+        this.updatedOnDate = rejectionDate;
+        this.status = ClientStatus.REJECTED.getValue();
+
+    }
+
+    public void withdraw(AppUser currentUser, CodeValue withdrawalReason, Date withdrawalDate) {
+        this.withdrawalReason = withdrawalReason;
+        this.withdrawalDate = withdrawalDate;
+        this.withdrawnBy = currentUser;
+        this.updatedBy = currentUser;
+        this.updatedOnDate = withdrawalDate;
+        this.status = ClientStatus.WITHDRAWN.getValue();
+
+    }
+
+    public void reActivate(AppUser currentUser, Date reactivateDate) {
+        this.closureDate = null;
+        this.closureReason = null;
+        this.reactivateDate = reactivateDate;
+        this.reactivatedBy = currentUser;
+        this.updatedBy = currentUser;
+        this.updatedOnDate = reactivateDate;
+        this.status = ClientStatus.PENDING.getValue();
+
+    }
+
+	public Integer getLegalForm() {
+		return legalForm;
+	}
+
+	public void setLegalForm(Integer legalForm) {
+		this.legalForm = legalForm;
+	}
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientCharge.java
new file mode 100644
index 0000000..1410c5f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientCharge.java
@@ -0,0 +1,315 @@
+/**
+ * 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.client.domain;
+
+import java.math.BigDecimal;
+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 javax.persistence.Transient;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.office.domain.OrganisationCurrency;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_client_charge")
+public class ClientCharge extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "client_id", referencedColumnName = "id", nullable = false)
+    private Client client;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "charge_id", referencedColumnName = "id", nullable = false)
+    private Charge charge;
+
+    @Column(name = "charge_time_enum", nullable = false)
+    private Integer chargeTime;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "charge_due_date")
+    private Date dueDate;
+
+    @Column(name = "charge_calculation_enum")
+    private Integer chargeCalculation;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountPaid;
+
+    @Column(name = "amount_waived_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountWaived;
+
+    @Column(name = "amount_writtenoff_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountWrittenOff;
+
+    @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amountOutstanding;
+
+    @Column(name = "is_penalty", nullable = false)
+    private boolean penaltyCharge = false;
+
+    @Column(name = "is_paid_derived", nullable = false)
+    private boolean paid = false;
+
+    @Column(name = "waived", nullable = false)
+    private boolean waived = false;
+
+    @Column(name = "is_active", nullable = false)
+    private boolean status = true;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "inactivated_on_date")
+    private Date inactivationDate;
+
+    @Transient
+    private OrganisationCurrency currency;
+
+    protected ClientCharge() {
+        //
+    }
+
+    public static ClientCharge createNew(final Client client, final Charge charge, final JsonCommand command) {
+        BigDecimal amount = command.bigDecimalValueOfParameterNamed(ClientApiConstants.amountParamName);
+        final LocalDate dueDate = command.localDateValueOfParameterNamed(ClientApiConstants.dueAsOfDateParamName);
+        final boolean status = true;
+        // Derive from charge definition if not passed in as a parameter
+        amount = (amount == null) ? charge.getAmount() : amount;
+        return new ClientCharge(client, charge, amount, dueDate, status);
+    }
+
+    private ClientCharge(final Client client, final Charge charge, final BigDecimal amount, final LocalDate dueDate, final boolean status) {
+
+        this.client = client;
+        this.charge = charge;
+        this.penaltyCharge = charge.isPenalty();
+        this.chargeTime = charge.getChargeTimeType();
+        this.dueDate = (dueDate == null) ? null : dueDate.toDate();
+        this.chargeCalculation = charge.getChargeCalculation();
+
+        BigDecimal chargeAmount = charge.getAmount();
+        if (amount != null) {
+            chargeAmount = amount;
+        }
+
+        populateDerivedFields(chargeAmount);
+
+        this.paid = determineIfFullyPaid();
+        this.status = status;
+    }
+
+    public Money pay(final Money amountPaid) {
+        Money amountPaidToDate = Money.of(this.getCurrency(), this.amountPaid);
+        Money amountOutstanding = Money.of(this.getCurrency(), this.amountOutstanding);
+        amountPaidToDate = amountPaidToDate.plus(amountPaid);
+        amountOutstanding = amountOutstanding.minus(amountPaid);
+        this.amountPaid = amountPaidToDate.getAmount();
+        this.amountOutstanding = amountOutstanding.getAmount();
+        this.paid = determineIfFullyPaid();
+        return Money.of(this.getCurrency(), this.amountOutstanding);
+    }
+
+    public void undoPayment(final Money transactionAmount) {
+        Money amountPaid = getAmountPaid();
+        amountPaid = amountPaid.minus(transactionAmount);
+        this.amountPaid = amountPaid.getAmount();
+        this.amountOutstanding = calculateOutstanding();
+        this.paid = false;
+        this.status = true;
+    }
+
+    public Money waive() {
+        Money amountWaivedToDate = getAmountWaived();
+        Money amountOutstanding = getAmountOutstanding();
+        Money totalAmountWaived = amountWaivedToDate.plus(amountOutstanding);
+        this.amountWaived = totalAmountWaived.getAmount();
+        this.amountOutstanding = BigDecimal.ZERO;
+        this.waived = true;
+        return totalAmountWaived;
+    }
+
+    public void undoWaiver(final Money transactionAmount) {
+        Money amountWaived = getAmountWaived();
+        amountWaived = amountWaived.minus(transactionAmount);
+        this.amountWaived = amountWaived.getAmount();
+        this.amountOutstanding = calculateOutstanding();
+        this.waived = false;
+        this.status = true;
+    }
+
+    private void populateDerivedFields(final BigDecimal amount) {
+        switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
+            case INVALID:
+                this.amount = null;
+                this.amountPaid = null;
+                this.amountOutstanding = BigDecimal.ZERO;
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+            case FLAT:
+                this.amount = amount;
+                this.amountPaid = null;
+                this.amountOutstanding = amount;
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+            default:
+            break;
+        }
+    }
+
+    public boolean isOnSpecifiedDueDate() {
+        return ChargeTimeType.fromInt(this.chargeTime).isOnSpecifiedDueDate();
+    }
+
+    private boolean determineIfFullyPaid() {
+        return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
+    }
+
+    private BigDecimal calculateOutstanding() {
+        BigDecimal amountPaidLocal = BigDecimal.ZERO;
+        if (this.amountPaid != null) {
+            amountPaidLocal = this.amountPaid;
+        }
+
+        BigDecimal amountWaivedLocal = BigDecimal.ZERO;
+        if (this.amountWaived != null) {
+            amountWaivedLocal = this.amountWaived;
+        }
+
+        BigDecimal amountWrittenOffLocal = BigDecimal.ZERO;
+        if (this.amountWrittenOff != null) {
+            amountWrittenOffLocal = this.amountWrittenOff;
+        }
+
+        final BigDecimal totalAccountedFor = amountPaidLocal.add(amountWaivedLocal).add(amountWrittenOffLocal);
+
+        return this.amount.subtract(totalAccountedFor);
+    }
+
+    public LocalDate getDueLocalDate() {
+        LocalDate dueDate = null;
+        if (this.dueDate != null) {
+            dueDate = new LocalDate(this.dueDate);
+        }
+        return dueDate;
+    }
+
+    public Client getClient() {
+        return this.client;
+    }
+
+    public Charge getCharge() {
+        return this.charge;
+    }
+
+    public Integer getChargeTime() {
+        return this.chargeTime;
+    }
+
+    public Date getDueDate() {
+        return this.dueDate;
+    }
+
+    public Integer getChargeCalculation() {
+        return this.chargeCalculation;
+    }
+
+    public boolean isPenaltyCharge() {
+        return this.penaltyCharge;
+    }
+
+    public boolean isPaid() {
+        return this.paid;
+    }
+
+    public boolean isWaived() {
+        return this.waived;
+    }
+
+    public boolean isActive() {
+        return this.status;
+    }
+
+    public boolean isNotActive() {
+        return !this.status;
+    }
+
+    public Date getInactivationDate() {
+        return this.inactivationDate;
+    }
+
+    public Long getClientId() {
+        return client.getId();
+    }
+
+    public Long getOfficeId() {
+        return this.client.getOffice().getId();
+    }
+
+    public void setCurrency(OrganisationCurrency currency) {
+        this.currency = currency;
+    }
+
+    public MonetaryCurrency getCurrency() {
+        return this.currency.toMonetaryCurrency();
+    }
+
+    public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) {
+        final Money amountWaivedOrWrittenOff = getAmountWaived().plus(getAmountWrittenOff());
+        return Money.of(currency, this.amountPaid).plus(amountWaivedOrWrittenOff).isGreaterThanZero();
+    }
+
+    public Money getAmount() {
+        return Money.of(getCurrency(), this.amount);
+    }
+
+    public Money getAmountPaid() {
+        return Money.of(getCurrency(), this.amountPaid);
+    }
+
+    public Money getAmountWaived() {
+        return Money.of(getCurrency(), this.amountWaived);
+    }
+
+    public Money getAmountWrittenOff() {
+        return Money.of(getCurrency(), this.amountWrittenOff);
+    }
+
+    public Money getAmountOutstanding() {
+        return Money.of(getCurrency(), this.amountOutstanding);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientChargePaidBy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientChargePaidBy.java
new file mode 100644
index 0000000..de036a7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientChargePaidBy.java
@@ -0,0 +1,82 @@
+/**
+ * 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.client.domain;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_client_charge_paid_by")
+public class ClientChargePaidBy extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "client_transaction_id", nullable = false)
+    private ClientTransaction clientTransaction;
+
+    @ManyToOne
+    @JoinColumn(name = "client_charge_id", nullable = false)
+    private ClientCharge clientCharge;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    protected ClientChargePaidBy() {
+
+    }
+
+    public static ClientChargePaidBy instance(final ClientTransaction clientTransaction, final ClientCharge clientCharge,
+            final BigDecimal amount) {
+        return new ClientChargePaidBy(clientTransaction, clientCharge, amount);
+    }
+
+    public ClientChargePaidBy(final ClientTransaction clientTransaction, final ClientCharge clientCharge, final BigDecimal amount) {
+        this.clientTransaction = clientTransaction;
+        this.clientCharge = clientCharge;
+        this.amount = amount;
+    }
+
+    public ClientTransaction getClientTransaction() {
+        return this.clientTransaction;
+    }
+
+    public void setClientTransaction(ClientTransaction clientTransaction) {
+        this.clientTransaction = clientTransaction;
+    }
+
+    public ClientCharge getClientCharge() {
+        return this.clientCharge;
+    }
+
+    public void setClientCharge(ClientCharge clientCharge) {
+        this.clientCharge = clientCharge;
+
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientChargeRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientChargeRepository.java
new file mode 100644
index 0000000..e266e4c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientChargeRepository.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.client.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ClientChargeRepository extends JpaRepository<ClientCharge, Long>, JpaSpecificationExecutor<ClientCharge> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientChargeRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientChargeRepositoryWrapper.java
new file mode 100644
index 0000000..d7880b9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientChargeRepositoryWrapper.java
@@ -0,0 +1,60 @@
+/**
+ * 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.client.domain;
+
+import org.apache.fineract.organisation.office.domain.OrganisationCurrencyRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.exception.ChargeNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class ClientChargeRepositoryWrapper {
+
+    private final ClientChargeRepository repository;
+    private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository;
+
+    @Autowired
+    public ClientChargeRepositoryWrapper(final ClientChargeRepository repository,
+            final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepositoryWrapper) {
+        this.repository = repository;
+        this.organisationCurrencyRepository = organisationCurrencyRepositoryWrapper;
+    }
+
+    public ClientCharge findOneWithNotFoundDetection(final Long id) {
+        final ClientCharge clientCharge = this.repository.findOne(id);
+        if (clientCharge == null) { throw new ChargeNotFoundException(id); }
+        // enrich Client charge with details of Organizational currency
+        clientCharge.setCurrency(organisationCurrencyRepository.findOneWithNotFoundDetection(clientCharge.getCharge().getCurrencyCode()));
+        return clientCharge;
+    }
+
+    public void save(final ClientCharge clientCharge) {
+        this.repository.save(clientCharge);
+    }
+
+    public void saveAndFlush(final ClientCharge clientCharge) {
+        this.repository.saveAndFlush(clientCharge);
+    }
+
+    public void delete(final ClientCharge clientCharge) {
+        this.repository.delete(clientCharge);
+        this.repository.flush();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientEnumerations.java
new file mode 100644
index 0000000..a8bffc4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientEnumerations.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class ClientEnumerations {
+
+    public static EnumOptionData status(final Integer statusId) {
+        return status(ClientStatus.fromInt(statusId));
+    }
+
+    public static EnumOptionData status(final ClientStatus status) {
+        EnumOptionData optionData = new EnumOptionData(ClientStatus.INVALID.getValue().longValue(), ClientStatus.INVALID.getCode(),
+                "Invalid");
+        switch (status) {
+            case INVALID:
+                optionData = new EnumOptionData(ClientStatus.INVALID.getValue().longValue(), ClientStatus.INVALID.getCode(), "Invalid");
+            break;
+            case PENDING:
+                optionData = new EnumOptionData(ClientStatus.PENDING.getValue().longValue(), ClientStatus.PENDING.getCode(), "Pending");
+            break;
+            case ACTIVE:
+                optionData = new EnumOptionData(ClientStatus.ACTIVE.getValue().longValue(), ClientStatus.ACTIVE.getCode(), "Active");
+            break;
+            case CLOSED:
+                optionData = new EnumOptionData(ClientStatus.CLOSED.getValue().longValue(), ClientStatus.CLOSED.getCode(), "Closed");
+            break;
+            case REJECTED:
+                optionData = new EnumOptionData(ClientStatus.REJECTED.getValue().longValue(), ClientStatus.REJECTED.getCode(), "Rejected");
+            break;
+            case WITHDRAWN:
+                optionData = new EnumOptionData(ClientStatus.WITHDRAWN.getValue().longValue(), ClientStatus.WITHDRAWN.getCode(),
+                        "Withdrawn");
+            break;
+            case TRANSFER_IN_PROGRESS:
+                optionData = new EnumOptionData(ClientStatus.TRANSFER_IN_PROGRESS.getValue().longValue(),
+                        ClientStatus.TRANSFER_IN_PROGRESS.getCode(), "Transfer in progress");
+            break;
+            case TRANSFER_ON_HOLD:
+                optionData = new EnumOptionData(ClientStatus.TRANSFER_ON_HOLD.getValue().longValue(),
+                        ClientStatus.TRANSFER_ON_HOLD.getCode(), "Transfer on hold");
+            break;
+            default:
+            break;
+        }
+
+        return optionData;
+    }
+    
+    public static EnumOptionData legalForm(final Integer statusId) {
+        return legalForm(LegalForm.fromInt(statusId));
+    }
+    
+    public static EnumOptionData legalForm(final LegalForm legalForm) {
+    	final EnumOptionData optionData = new EnumOptionData(legalForm.getValue().longValue(), legalForm.getCode(),
+                legalForm.toString());
+        return optionData;
+    }
+    
+    public static List<EnumOptionData> legalForm(final LegalForm[] legalForms) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final LegalForm legalForm : legalForms) {
+            optionDatas.add(legalForm(legalForm));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData clientTransactionType(final int id) {
+        return clientTransactionType(ClientTransactionType.fromInt(id));
+    }
+
+    public static EnumOptionData clientTransactionType(final ClientTransactionType clientTransactionType) {
+        final EnumOptionData optionData = new EnumOptionData(clientTransactionType.getValue().longValue(), clientTransactionType.getCode(),
+                clientTransactionType.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> clientTransactionType(final ClientTransactionType[] clientTransactionTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final ClientTransactionType clientTransaction : clientTransactionTypes) {
+            optionDatas.add(clientTransactionType(clientTransaction));
+        }
+        return optionDatas;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientIdentifier.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientIdentifier.java
new file mode 100644
index 0000000..f1243bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientIdentifier.java
@@ -0,0 +1,112 @@
+/**
+ * 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.client.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Entity
+@Table(name = "m_client_identifier", uniqueConstraints = {
+        @UniqueConstraint(columnNames = { "document_type_id", "document_key" }, name = "unique_identifier_key"),
+        @UniqueConstraint(columnNames = { "client_id", "document_type_id" }, name = "unique_client_identifier") })
+public class ClientIdentifier extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "client_id", nullable = false)
+    private Client client;
+
+    @ManyToOne
+    @JoinColumn(name = "document_type_id", nullable = false)
+    private CodeValue documentType;
+
+    @Column(name = "document_key", length = 1000)
+    private String documentKey;
+
+    @Column(name = "description", length = 1000)
+    private String description;
+
+    public static ClientIdentifier fromJson(final Client client, final CodeValue documentType, final JsonCommand command) {
+        final String documentKey = command.stringValueOfParameterNamed("documentKey");
+        final String description = command.stringValueOfParameterNamed("description");
+        return new ClientIdentifier(client, documentType, documentKey, description);
+    }
+
+    protected ClientIdentifier() {
+        //
+    }
+
+    private ClientIdentifier(final Client client, final CodeValue documentType, final String documentKey, final String description) {
+        this.client = client;
+        this.documentType = documentType;
+        this.documentKey = StringUtils.defaultIfEmpty(documentKey, null);
+        this.description = StringUtils.defaultIfEmpty(description, null);
+    }
+
+    public void update(final CodeValue documentType) {
+        this.documentType = documentType;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String documentTypeIdParamName = "documentTypeId";
+        if (command.isChangeInLongParameterNamed(documentTypeIdParamName, this.documentType.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(documentTypeIdParamName);
+            actualChanges.put(documentTypeIdParamName, newValue);
+        }
+
+        final String documentKeyParamName = "documentKey";
+        if (command.isChangeInStringParameterNamed(documentKeyParamName, this.documentKey)) {
+            final String newValue = command.stringValueOfParameterNamed(documentKeyParamName);
+            actualChanges.put(documentKeyParamName, newValue);
+            this.documentKey = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String descriptionParamName = "description";
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        return actualChanges;
+    }
+
+    public String documentKey() {
+        return this.documentKey;
+    }
+
+    public Long documentTypeId() {
+        return this.documentType.getId();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientIdentifierRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientIdentifierRepository.java
new file mode 100644
index 0000000..a7e13e4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientIdentifierRepository.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.client.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ClientIdentifierRepository extends JpaRepository<ClientIdentifier, Long>, JpaSpecificationExecutor<ClientIdentifier> {
+    // no behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPerson.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPerson.java
new file mode 100644
index 0000000..541d3bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPerson.java
@@ -0,0 +1,208 @@
+/**
+ * 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.client.domain;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.joda.time.LocalDate;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_client_non_person")
+public class ClientNonPerson extends AbstractPersistable<Long> {
+	
+	@OneToOne(optional = false)
+    @JoinColumn(name = "client_id", referencedColumnName = "id", nullable = false, unique = true)
+    private Client client;
+	
+	@ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "constitution_cv_id", nullable = false)
+    private CodeValue constitution;
+	
+	@Column(name = "incorp_no", length = 50, nullable = true)
+	private String incorpNumber;
+	
+	@Column(name = "incorp_validity_till", nullable = true)
+	@Temporal(TemporalType.DATE)
+	private Date incorpValidityTill;
+	
+	@ManyToOne(fetch = FetchType.LAZY)
+	@JoinColumn(name = "main_business_line_cv_id", nullable = true)
+    private CodeValue mainBusinessLine;
+	
+	@Column(name = "remarks", length = 150, nullable = true)
+	private String remarks;
+	
+
+	public static ClientNonPerson createNew(final Client client, final CodeValue constitution, final CodeValue mainBusinessLine, String incorpNumber, LocalDate incorpValidityTill, String remarks)
+	{				       
+		return new ClientNonPerson(client, constitution, mainBusinessLine, incorpNumber, incorpValidityTill, remarks);		
+	}
+	
+	protected ClientNonPerson() {
+        //
+    }
+	
+	private ClientNonPerson(final Client client, final CodeValue constitution, final CodeValue mainBusinessLine, final String incorpNumber, final LocalDate incorpValidityTill, final String remarks)
+	{
+		if(client != null)
+			this.client = client;
+		
+		if(constitution != null)
+			this.constitution = constitution;
+		
+		if(mainBusinessLine != null)
+			this.mainBusinessLine = mainBusinessLine;
+		
+		if (StringUtils.isNotBlank(incorpNumber)) {
+            this.incorpNumber = incorpNumber.trim();
+        } else {
+            this.incorpNumber = null;
+        }
+		
+		if (incorpValidityTill != null) {
+            this.incorpValidityTill = incorpValidityTill.toDateTimeAtStartOfDay().toDate();
+        }
+		
+		if (StringUtils.isNotBlank(remarks)) {
+            this.remarks = remarks.trim();
+        } else {
+            this.remarks = null;
+        }
+		
+		validate(client);
+	}
+	
+	private void validate(final Client client) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        validateIncorpValidityTillDate(client, dataValidationErrors);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+	
+	private void validateIncorpValidityTillDate(final Client client, final List<ApiParameterError> dataValidationErrors) {
+        if (getIncorpValidityTillLocalDate() != null && client.dateOfBirthLocalDate() != null && client.dateOfBirthLocalDate().isAfter(getIncorpValidityTillLocalDate())) {
+
+            final String defaultUserMessage = "incorpvaliditytill date cannot be after the incorporation date";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.incorpValidityTill.after.incorp.date",
+                    defaultUserMessage, ClientApiConstants.incorpValidityTillParamName, this.incorpValidityTill);
+
+            dataValidationErrors.add(error);
+        }
+    }
+	
+	public LocalDate getIncorpValidityTillLocalDate() {
+        LocalDate incorpValidityTillLocalDate = null;
+        if (this.incorpValidityTill != null) {
+            incorpValidityTillLocalDate = LocalDate.fromDateFields(this.incorpValidityTill);
+        }
+        return incorpValidityTillLocalDate;
+    }
+	
+	public Long constitutionId() {
+        Long constitutionId = null;
+        if (this.constitution != null) {
+            constitutionId = this.constitution.getId();
+        }
+        return constitutionId;
+    }
+	
+	public Long mainBusinessLineId() {
+        Long mainBusinessLineId = null;
+        if (this.mainBusinessLine != null) {
+            mainBusinessLineId = this.mainBusinessLine.getId();
+        }
+        return mainBusinessLineId;
+    }
+	
+	public void updateConstitution(CodeValue constitution) {
+        this.constitution = constitution;
+    }
+	
+	public void updateMainBusinessLine(CodeValue mainBusinessLine) {
+        this.mainBusinessLine = mainBusinessLine;
+    }
+	
+	public Map<String, Object> update(final JsonCommand command) {
+		
+		final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+		
+		if (command.isChangeInStringParameterNamed(ClientApiConstants.incorpNumberParamName, this.incorpNumber)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.incorpNumberParamName);
+            actualChanges.put(ClientApiConstants.incorpNumberParamName, newValue);
+            this.incorpNumber = StringUtils.defaultIfEmpty(newValue, null);
+        }
+		
+		if (command.isChangeInStringParameterNamed(ClientApiConstants.remarksParamName, this.remarks)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.remarksParamName);
+            actualChanges.put(ClientApiConstants.remarksParamName, newValue);
+            this.remarks = StringUtils.defaultIfEmpty(newValue, null);
+        }
+		
+		final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+        
+		if (command.isChangeInLocalDateParameterNamed(ClientApiConstants.incorpValidityTillParamName, getIncorpValidityTillLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(ClientApiConstants.incorpValidityTillParamName);
+            actualChanges.put(ClientApiConstants.incorpValidityTillParamName, valueAsInput);
+            actualChanges.put(ClientApiConstants.dateFormatParamName, dateFormatAsInput);
+            actualChanges.put(ClientApiConstants.localeParamName, localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(ClientApiConstants.incorpValidityTillParamName);
+            this.incorpValidityTill = newValue.toDate();
+        }
+		
+		if (command.isChangeInLongParameterNamed(ClientApiConstants.constitutionIdParamName, constitutionId())) {
+            final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.constitutionIdParamName);
+            actualChanges.put(ClientApiConstants.constitutionIdParamName, newValue);
+        }
+		
+		if (command.isChangeInLongParameterNamed(ClientApiConstants.mainBusinessLineIdParamName, mainBusinessLineId())) {
+            final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.mainBusinessLineIdParamName);
+            actualChanges.put(ClientApiConstants.mainBusinessLineIdParamName, newValue);
+        }
+		
+		//validate();
+
+        return actualChanges;
+			
+	}
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPersonRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPersonRepository.java
new file mode 100644
index 0000000..c9723f8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPersonRepository.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.client.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 ClientNonPersonRepository extends JpaRepository<ClientNonPerson, Long>, JpaSpecificationExecutor<ClientNonPerson>{
+
+	@Query("from ClientNonPerson clientNonPerson where clientNonPerson.client.id = :clientId")	
+	ClientNonPerson findByClientId(@Param("clientId") Long clientId);	
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPersonRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPersonRepositoryWrapper.java
new file mode 100644
index 0000000..d564947
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPersonRepositoryWrapper.java
@@ -0,0 +1,73 @@
+/**
+ * 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.client.domain;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.exception.ClientNonPersonNotFoundByClientIdException;
+import org.apache.fineract.portfolio.client.exception.ClientNonPersonNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+
+/**
+ * <p>
+ * Wrapper for {@link ClientNonPersonRepository} that adds NULL checking and Error
+ * handling capabilities
+ * </p>
+ */
+@Service
+public class ClientNonPersonRepositoryWrapper {
+	
+	private final ClientNonPersonRepository repository;
+    private final PlatformSecurityContext context;
+	
+    @Autowired
+    public ClientNonPersonRepositoryWrapper(final ClientNonPersonRepository repository, final PlatformSecurityContext context) {
+        this.repository = repository;
+        this.context = context;
+    }
+
+    public ClientNonPerson findOneWithNotFoundDetection(final Long id) {
+        final ClientNonPerson clientNonPerson = this.repository.findOne(id);
+        if (clientNonPerson == null) { throw new ClientNonPersonNotFoundException(id); }
+        return clientNonPerson;
+    }
+    
+    public ClientNonPerson findOneByClientId(final Long clientId) {
+    	return this.repository.findByClientId(clientId);
+    }
+    
+    public ClientNonPerson findOneByClientIdWithNotFoundDetection(final Long clientId) {
+        final ClientNonPerson clientNonPerson = this.repository.findByClientId(clientId);
+        if (clientNonPerson == null) { throw new ClientNonPersonNotFoundByClientIdException(clientId); }
+        return clientNonPerson;
+    }
+
+    public void save(final ClientNonPerson clientNonPerson) {
+        this.repository.save(clientNonPerson);
+    }
+
+    public void saveAndFlush(final ClientNonPerson clientNonPerson) {
+        this.repository.saveAndFlush(clientNonPerson);
+    }
+
+    public void delete(final ClientNonPerson clientNonPerson) {
+        this.repository.delete(clientNonPerson);
+    }
+}
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
new file mode 100644
index 0000000..c1f9c27
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.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.client.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecificationExecutor<Client> {
+    // no added behaviour
+}
\ 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
new file mode 100755
index 0000000..27840ee
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepositoryWrapper.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.client.domain;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link ClientRepository} that adds NULL checking and Error
+ * handling capabilities
+ * </p>
+ */
+@Service
+public class ClientRepositoryWrapper {
+
+    private final ClientRepository repository;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public ClientRepositoryWrapper(final ClientRepository repository, final PlatformSecurityContext context) {
+        this.repository = repository;
+        this.context = context;
+    }
+
+    public Client findOneWithNotFoundDetection(final Long id) {
+        final Client client = this.repository.findOne(id);
+        if (client == null) { throw new ClientNotFoundException(id); }
+        return client;
+    }
+
+    public void save(final Client client) {
+        this.repository.save(client);
+    }
+
+    public void saveAndFlush(final Client client) {
+        this.repository.saveAndFlush(client);
+    }
+
+    public void delete(final Client client) {
+        this.repository.delete(client);
+    }
+
+    public Client getActiveClientInUserScope(Long clientId) {
+        final Client client = this.findOneWithNotFoundDetection(clientId);
+        if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); }
+        this.context.validateAccessRights(client.getOffice().getHierarchy());
+        return client;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientStatus.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientStatus.java
new file mode 100644
index 0000000..fbdd96d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientStatus.java
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.domain;
+
+/**
+ * Enum representation of client status states.
+ */
+public enum ClientStatus {
+
+    INVALID(0, "clientStatusType.invalid"), //
+    PENDING(100, "clientStatusType.pending"), //
+    ACTIVE(300, "clientStatusType.active"), //
+    TRANSFER_IN_PROGRESS(303, "clientStatusType.transfer.in.progress"), //
+    TRANSFER_ON_HOLD(304, "clientStatusType.transfer.on.hold"), //
+    CLOSED(600, "clientStatusType.closed"),
+    REJECTED(700,"clientStatusType.rejected"),
+    WITHDRAWN(800,"clientStatusType.withdraw");
+    
+
+    private final Integer value;
+    private final String code;
+
+    public static ClientStatus fromInt(final Integer statusValue) {
+
+        ClientStatus enumeration = ClientStatus.INVALID;
+        switch (statusValue) {
+            case 100:
+                enumeration = ClientStatus.PENDING;
+            break;
+            case 300:
+                enumeration = ClientStatus.ACTIVE;
+            break;
+            case 303:
+                enumeration = ClientStatus.TRANSFER_IN_PROGRESS;
+            break;
+            case 304:
+                enumeration = ClientStatus.TRANSFER_ON_HOLD;
+            break;
+            case 600:
+                enumeration = ClientStatus.CLOSED;
+            break;
+            case 700:
+            	enumeration = ClientStatus.REJECTED;
+            break;
+            case 800:
+            	enumeration = ClientStatus.WITHDRAWN;
+            break;
+           
+        }
+        return enumeration;
+    }
+
+    private ClientStatus(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final ClientStatus state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isPending() {
+        return this.value.equals(ClientStatus.PENDING.getValue());
+    }
+
+    public boolean isActive() {
+        return this.value.equals(ClientStatus.ACTIVE.getValue());
+    }
+  
+    public boolean isClosed() {
+        return this.value.equals(ClientStatus.CLOSED.getValue());
+    }
+    public boolean isRejected(){
+    	return this.value.equals(ClientStatus.REJECTED.getValue());
+    }
+    public boolean isWithdrawn(){
+    	return this.value.equals(ClientStatus.WITHDRAWN.getValue());
+    }
+    public boolean isTransferInProgress() {
+        return this.value.equals(ClientStatus.TRANSFER_IN_PROGRESS.getValue());
+    }
+
+    public boolean isTransferOnHold() {
+        return this.value.equals(ClientStatus.TRANSFER_ON_HOLD.getValue());
+    }
+
+    public boolean isUnderTransfer() {
+        return isTransferInProgress() || isTransferOnHold();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java
new file mode 100644
index 0000000..e3f56f6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java
@@ -0,0 +1,234 @@
+/**
+ * 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.client.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OrganisationCurrency;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_client_transaction", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
+public class ClientTransaction extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "client_id", nullable = false)
+    private Client client;
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "payment_detail_id", nullable = true)
+    private PaymentDetail paymentDetail;
+
+    @Column(name = "currency_code", length = 3)
+    private String currencyCode;
+
+    @Column(name = "transaction_type_enum", nullable = false)
+    private Integer typeOf;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "transaction_date", nullable = false)
+    private Date dateOf;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "is_reversed", nullable = false)
+    private boolean reversed;
+
+    @Column(name = "external_id", length = 100, nullable = true, unique = true)
+    private String externalId;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "created_date", nullable = false)
+    private Date createdDate;
+
+    @ManyToOne
+    @JoinColumn(name = "appuser_id", nullable = true)
+    private AppUser appUser;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "clientTransaction", orphanRemoval = true)
+    private Set<ClientChargePaidBy> clientChargePaidByCollection = new HashSet<>();
+
+    @Transient
+    private OrganisationCurrency currency;
+
+    protected ClientTransaction() {}
+
+    public static ClientTransaction payCharge(final Client client, final Office office, PaymentDetail paymentDetail, final LocalDate date,
+            final Money amount, final String currencyCode, final AppUser appUser) {
+        final boolean isReversed = false;
+        final String externalId = null;
+        return new ClientTransaction(client, office, paymentDetail, ClientTransactionType.PAY_CHARGE.getValue(), date, amount, isReversed,
+                externalId, DateUtils.getDateOfTenant(), currencyCode, appUser);
+    }
+
+    public static ClientTransaction waiver(final Client client, final Office office, final LocalDate date, final Money amount,
+            final String currencyCode, final AppUser appUser) {
+        final boolean isReversed = false;
+        final String externalId = null;
+        final PaymentDetail paymentDetail = null;
+        return new ClientTransaction(client, office, paymentDetail, ClientTransactionType.WAIVE_CHARGE.getValue(), date, amount, isReversed,
+                externalId, DateUtils.getDateOfTenant(), currencyCode, appUser);
+    }
+
+    public ClientTransaction(Client client, Office office, PaymentDetail paymentDetail, Integer typeOf, LocalDate transactionLocalDate,
+            Money amount, boolean reversed, String externalId, Date createdDate, String currencyCode, AppUser appUser) {
+        super();
+        this.client = client;
+        this.office = office;
+        this.paymentDetail = paymentDetail;
+        this.typeOf = typeOf;
+        this.dateOf = transactionLocalDate.toDate();
+        this.amount = amount.getAmount();
+        this.reversed = reversed;
+        this.externalId = externalId;
+        this.createdDate = createdDate;
+        this.currencyCode = currencyCode;
+        this.appUser = appUser;
+    }
+
+    public void reverse() {
+        this.reversed = true;
+    }
+
+    /**
+     * Converts the content of this Client Transaction to a map which can be
+     * passed to the accounting module
+     * 
+     * @param currencyData
+     * @return
+     */
+    public Map<String, Object> toMapData() {
+        final Map<String, Object> thisTransactionData = new LinkedHashMap<>();
+
+        final EnumOptionData transactionType = ClientEnumerations.clientTransactionType(this.typeOf);
+        Boolean accountingEnabledForAtleastOneCharge = false;
+
+        thisTransactionData.put("id", getId());
+        thisTransactionData.put("clientId", getClientId());
+        thisTransactionData.put("officeId", this.office.getId());
+        thisTransactionData.put("type", transactionType);
+        thisTransactionData.put("reversed", Boolean.valueOf(this.reversed));
+        thisTransactionData.put("date", getTransactionDate());
+        thisTransactionData.put("currencyCode", this.currencyCode);
+        thisTransactionData.put("amount", this.amount);
+
+        if (this.paymentDetail != null) {
+            thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId());
+        }
+
+        if (!this.clientChargePaidByCollection.isEmpty()) {
+            final List<Map<String, Object>> clientChargesPaidData = new ArrayList<>();
+            for (final ClientChargePaidBy clientChargePaidBy : this.clientChargePaidByCollection) {
+                final Map<String, Object> clientChargePaidData = new LinkedHashMap<>();
+                clientChargePaidData.put("chargeId", clientChargePaidBy.getClientCharge().getCharge().getId());
+                clientChargePaidData.put("isPenalty", clientChargePaidBy.getClientCharge().getCharge().isPenalty());
+                clientChargePaidData.put("clientChargeId", clientChargePaidBy.getClientCharge().getId());
+                clientChargePaidData.put("amount", clientChargePaidBy.getAmount());
+                GLAccount glAccount = clientChargePaidBy.getClientCharge().getCharge().getAccount();
+                if (glAccount != null) {
+                    accountingEnabledForAtleastOneCharge = true;
+                    clientChargePaidData.put("incomeAccountId", glAccount.getId());
+                }
+                clientChargesPaidData.add(clientChargePaidData);
+            }
+            thisTransactionData.put("clientChargesPaid", clientChargesPaidData);
+        }
+
+        thisTransactionData.put("accountingEnabled", accountingEnabledForAtleastOneCharge);
+
+        return thisTransactionData;
+    }
+
+    public boolean isPayChargeTransaction() {
+        return ClientTransactionType.PAY_CHARGE.getValue().equals(this.typeOf);
+    }
+
+    public boolean isWaiveChargeTransaction() {
+        return ClientTransactionType.WAIVE_CHARGE.getValue().equals(this.typeOf);
+    }
+
+    public Set<ClientChargePaidBy> getClientChargePaidByCollection() {
+        return this.clientChargePaidByCollection;
+    }
+
+    public Long getClientId() {
+        return client.getId();
+    }
+
+    public Money getAmount() {
+        return Money.of(getCurrency(), this.amount);
+    }
+
+    public MonetaryCurrency getCurrency() {
+        return this.currency.toMonetaryCurrency();
+    }
+
+    public void setCurrency(OrganisationCurrency currency) {
+        this.currency = currency;
+    }
+
+    public String getCurrencyCode() {
+        return this.currencyCode;
+    }
+
+    public boolean isReversed() {
+        return this.reversed;
+    }
+
+    public LocalDate getTransactionDate() {
+        return new LocalDate(this.dateOf);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransactionRepository.java
new file mode 100644
index 0000000..f00498c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransactionRepository.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.client.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ClientTransactionRepository extends JpaRepository<ClientTransaction, Long>, JpaSpecificationExecutor<ClientTransaction> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransactionRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransactionRepositoryWrapper.java
new file mode 100644
index 0000000..028b5c6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransactionRepositoryWrapper.java
@@ -0,0 +1,60 @@
+/**
+ * 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.client.domain;
+
+import org.apache.fineract.organisation.office.domain.OrganisationCurrencyRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientTransactionNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class ClientTransactionRepositoryWrapper {
+
+    private final ClientTransactionRepository repository;
+    private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository;
+
+    @Autowired
+    public ClientTransactionRepositoryWrapper(final ClientTransactionRepository repository,
+            final OrganisationCurrencyRepositoryWrapper currencyRepositoryWrapper) {
+        this.repository = repository;
+        this.organisationCurrencyRepository = currencyRepositoryWrapper;
+    }
+
+    public ClientTransaction findOneWithNotFoundDetection(final Long clientId, final Long transactionId) {
+        final ClientTransaction clientTransaction = this.repository.findOne(transactionId);
+        if (clientTransaction == null
+                || clientTransaction.getClientId() != clientId) { throw new ClientTransactionNotFoundException(clientId, transactionId); }
+        // enrich Client charge with details of Organizational currency
+        clientTransaction.setCurrency(organisationCurrencyRepository.findOneWithNotFoundDetection(clientTransaction.getCurrencyCode()));
+        return clientTransaction;
+    }
+
+    public void save(final ClientTransaction clientTransaction) {
+        this.repository.save(clientTransaction);
+    }
+
+    public void saveAndFlush(final ClientTransaction clientTransaction) {
+        this.repository.saveAndFlush(clientTransaction);
+    }
+
+    public void delete(final ClientTransaction clientTransaction) {
+        this.repository.delete(clientTransaction);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransactionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransactionType.java
new file mode 100644
index 0000000..952644c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransactionType.java
@@ -0,0 +1,84 @@
+/**
+ * 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.client.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum ClientTransactionType {
+
+    PAY_CHARGE(1, "clientTransactionType.payCharge"), //
+    WAIVE_CHARGE(2, "clientTransactionType.waiveCharge");
+
+    private final Integer value;
+    private final String code;
+
+    private ClientTransactionType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, ClientTransactionType> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+
+    static {
+        int i = 0;
+        for (final ClientTransactionType type : ClientTransactionType.values()) {
+            if (i == 0) {
+                minValue = type.value;
+            }
+            intToEnumMap.put(type.value, type);
+            if (minValue >= type.value) {
+                minValue = type.value;
+            }
+            if (maxValue < type.value) {
+                maxValue = type.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static ClientTransactionType fromInt(final int i) {
+        final ClientTransactionType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/LegalForm.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/LegalForm.java
new file mode 100644
index 0000000..3a9a4ff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/LegalForm.java
@@ -0,0 +1,67 @@
+/**
+ * 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.client.domain;
+
+/**
+ * Type used to differentiate the type of client
+ */
+public enum LegalForm {
+	
+	PERSON(1, "legalFormType.person"),
+	
+	ENTITY(2, "legalFormType.entity");
+	
+	private final Integer value;
+    private final String code;
+	
+    private LegalForm(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+    
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+	
+    public static LegalForm fromInt(final Integer type) {
+
+    	LegalForm legalForm = null;
+        switch (type) {
+            case 1:
+                legalForm = LegalForm.PERSON;
+            break;
+            case 2:
+                legalForm = LegalForm.ENTITY;
+            break;           
+        }
+        return legalForm;
+    }
+    
+    public boolean isPerson() {
+        return this.value.equals(LegalForm.PERSON.getValue());
+    }
+    
+    public boolean isEntity() {
+        return this.value.equals(LegalForm.ENTITY.getValue());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientActiveForUpdateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientActiveForUpdateException.java
new file mode 100755
index 0000000..23d6e26
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientActiveForUpdateException.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.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ClientActiveForUpdateException extends AbstractPlatformDomainRuleException {
+
+    public ClientActiveForUpdateException(final Long clientId, final String parameterName) {
+        super("error.msg.client.active.for.update.parameter." + parameterName, "The Client with id `" + clientId
+                + "` is active,can't update parameter " + parameterName, clientId, parameterName);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientChargeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientChargeNotFoundException.java
new file mode 100644
index 0000000..6c2af03
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientChargeNotFoundException.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.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ClientChargeNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ClientChargeNotFoundException(final Long id) {
+        super("error.msg.client.charge.id.invalid", "Client charge with identifier " + id + " does not exist", id);
+    }
+
+    public ClientChargeNotFoundException(final Long id, final Long clientId) {
+        super("error.msg.client.charge.id.invalid.for.given.client",
+                "Client charge with identifier " + id + " does not exist for client with id " + clientId, id, clientId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientHasBeenClosedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientHasBeenClosedException.java
new file mode 100644
index 0000000..dde841a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientHasBeenClosedException.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.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ClientHasBeenClosedException extends AbstractPlatformDomainRuleException {
+
+    public ClientHasBeenClosedException(final Long clientId) {
+        super("error.msg.client.closed", "Client with identifier " + clientId + " has already been closed");
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientHasNoStaffException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientHasNoStaffException.java
new file mode 100644
index 0000000..6273890
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientHasNoStaffException.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.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ClientHasNoStaffException extends AbstractPlatformResourceNotFoundException {
+
+    public ClientHasNoStaffException(final Long clientId) {
+        super("error.msg.client.has.no.staff", "Client with identifier " + clientId + " does not have staff", clientId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientIdentifierNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientIdentifierNotFoundException.java
new file mode 100644
index 0000000..da961a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientIdentifierNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when client Identifier resources are not
+ * found.
+ */
+public class ClientIdentifierNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ClientIdentifierNotFoundException(final Long id) {
+        super("error.msg.clientIdentifier.id.invalid", "Client Identifier with the primary key " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientMustBePendingToBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientMustBePendingToBeDeletedException.java
new file mode 100644
index 0000000..5ae1bbc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientMustBePendingToBeDeletedException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when attempting to delete clients
+ */
+public class ClientMustBePendingToBeDeletedException extends AbstractPlatformDomainRuleException {
+
+    public ClientMustBePendingToBeDeletedException(final Long id) {
+        super("error.msg.clients.cannot.be.deleted",
+                "Client with identifier " + id + " cannot be deleted as it is not in `Pending` state.", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNonPersonNotFoundByClientIdException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNonPersonNotFoundByClientIdException.java
new file mode 100644
index 0000000..ed717bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNonPersonNotFoundByClientIdException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when clientNonPerson resources are not found.
+ */
+public class ClientNonPersonNotFoundByClientIdException extends AbstractPlatformResourceNotFoundException {
+	
+	public ClientNonPersonNotFoundByClientIdException(final Long id) {
+        super("error.msg.clientnonperson.id.invalid", "ClientNonPerson with client identifier " + id + " does not exist", id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNonPersonNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNonPersonNotFoundException.java
new file mode 100644
index 0000000..15e5ada
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNonPersonNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when clientNonPerson resources are not found.
+ */
+public class ClientNonPersonNotFoundException extends AbstractPlatformResourceNotFoundException {
+	
+	public ClientNonPersonNotFoundException(final Long id) {
+        super("error.msg.clientnonperson.id.invalid", "ClientNonPerson with identifier " + id + " does not exist", id);
+    }
+	
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotActiveException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotActiveException.java
new file mode 100644
index 0000000..e964059
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotActiveException.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.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ClientNotActiveException extends AbstractPlatformDomainRuleException {
+
+    public ClientNotActiveException(final Long clientId) {
+        super("error.msg.client.not.active.exception", "The Client with id `" + clientId + "` is not active", clientId);
+    }
+
+}
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
new file mode 100644
index 0000000..7a6f034
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when client resources are not found.
+ */
+public class ClientNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ClientNotFoundException(final Long id) {
+        super("error.msg.client.id.invalid", "Client with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientTransactionCannotBeUndoneException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientTransactionCannotBeUndoneException.java
new file mode 100644
index 0000000..3fe0c33
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientTransactionCannotBeUndoneException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when attempting to delete clients
+ */
+public class ClientTransactionCannotBeUndoneException extends AbstractPlatformDomainRuleException {
+
+    public ClientTransactionCannotBeUndoneException(final long clientId, final Long transactionId) {
+        super("error.msg.clients.transaction.cannot.be.undone", "Client transaction with identifier " + transactionId
+                + " for client with identifier " + clientId + " has already been reversed", transactionId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientTransactionNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientTransactionNotFoundException.java
new file mode 100644
index 0000000..1feeee9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientTransactionNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ClientTransactionNotFoundException extends AbstractPlatformDomainRuleException {
+
+    public ClientTransactionNotFoundException(final Long clientId, final Long transactionId) {
+        super("error.msg.client.transaction.not.found.exception",
+                "The Transaction with id `" + transactionId + "` does not exist for a Client with id `" + clientId, transactionId,
+                clientId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/DuplicateClientIdentifierException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/DuplicateClientIdentifierException.java
new file mode 100644
index 0000000..898e810
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/DuplicateClientIdentifierException.java
@@ -0,0 +1,66 @@
+/**
+ * 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.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when a client identifier of the particular
+ * type is already present
+ */
+public class DuplicateClientIdentifierException extends AbstractPlatformDomainRuleException {
+
+    private Long documentTypeId;
+    private String identifierKey;
+    private final String identifierType;
+
+    public DuplicateClientIdentifierException(final String identifierType) {
+        super("error.msg.clientIdentifier.type.duplicate", "Client identifier of type " + identifierType
+                + " is already present for this client", identifierType);
+        this.identifierType = identifierType;
+    }
+
+    public DuplicateClientIdentifierException(final Long documentTypeId, final String identifierType, final String identifierKey) {
+        super("error.msg.clientIdentifier.identityKey.duplicate", "Client identifier of type " + identifierType + " with value of "
+                + identifierKey + " already exists.", identifierType, identifierKey);
+        this.documentTypeId = documentTypeId;
+        this.identifierType = identifierType;
+        this.identifierKey = identifierKey;
+    }
+
+    public DuplicateClientIdentifierException(final String clientName, final String officeName, final String identifierType,
+            final String identifierKey) {
+        super("error.msg.clientIdentifier.identityKey.duplicate", "Client " + clientName + "under " + officeName + " Branch already has a "
+                + identifierType + " with unique key " + identifierKey, clientName, officeName, identifierType, identifierKey);
+        this.identifierType = identifierType;
+        this.identifierKey = identifierKey;
+    }
+
+    public Long getDocumentTypeId() {
+        return this.documentTypeId;
+    }
+
+    public String getIdentifierKey() {
+        return this.identifierKey;
+    }
+
+    public String getIdentifierType() {
+        return this.identifierType;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ImageNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ImageNotFoundException.java
new file mode 100644
index 0000000..691b73f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ImageNotFoundException.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.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ImageNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ImageNotFoundException(final String resource, final Long resourceId) {
+        super("error.msg.entity.image.invalid", "Image for resource " + resource + " with Identifier " + resourceId + " does not exist",
+                resource, resourceId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/InvalidClientSavingProductException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/InvalidClientSavingProductException.java
new file mode 100755
index 0000000..e1d468d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/InvalidClientSavingProductException.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.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown an action to transition a
+ * loan from one state to another violates a domain rule.
+ */
+public class InvalidClientSavingProductException extends AbstractPlatformDomainRuleException {
+
+    public InvalidClientSavingProductException(final String action, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.client." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/InvalidClientStateTransitionException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/InvalidClientStateTransitionException.java
new file mode 100644
index 0000000..1a1df93
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/InvalidClientStateTransitionException.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.portfolio.client.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown an action to transition a
+ * loan from one state to another violates a domain rule.
+ */
+public class InvalidClientStateTransitionException extends AbstractPlatformDomainRuleException {
+
+    public InvalidClientStateTransitionException(final String action, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.client." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
+        // TODO Auto-generated constructor stub
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/ActivateClientCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/ActivateClientCommandHandler.java
new file mode 100644
index 0000000..25322d9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/ActivateClientCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "ACTIVATE")
+public class ActivateClientCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public ActivateClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.activateClient(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/AssignClientStaffCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/AssignClientStaffCommandHandler.java
new file mode 100644
index 0000000..088abcf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/AssignClientStaffCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "ASSIGNSTAFF")
+public class AssignClientStaffCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public AssignClientStaffCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.clientWritePlatformService.assignClientStaff(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CloseClientCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CloseClientCommandHandler.java
new file mode 100644
index 0000000..02978b9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CloseClientCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "CLOSE")
+public class CloseClientCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public CloseClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.closeClient(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CreateClientChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CreateClientChargeCommandHandler.java
new file mode 100644
index 0000000..559d292
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CreateClientChargeCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.service.ClientChargeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME, action = ClientApiConstants.CLIENT_CHARGE_ACTION_CREATE)
+public class CreateClientChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientChargeWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateClientChargeCommandHandler(final ClientChargeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.addCharge(command.getClientId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CreateClientCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CreateClientCommandHandler.java
new file mode 100644
index 0000000..6ef8271
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CreateClientCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "CREATE")
+public class CreateClientCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public CreateClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.createClient(command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CreateClientIdentifierCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CreateClientIdentifierCommandHandler.java
new file mode 100644
index 0000000..e35428f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/CreateClientIdentifierCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientIdentifierWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENTIDENTIFIER", action = "CREATE")
+public class CreateClientIdentifierCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientIdentifierWritePlatformService clientIdentifierWritePlatformService;
+
+    @Autowired
+    public CreateClientIdentifierCommandHandler(final ClientIdentifierWritePlatformService clientIdentifierWritePlatformService) {
+        this.clientIdentifierWritePlatformService = clientIdentifierWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientIdentifierWritePlatformService.addClientIdentifier(command.getClientId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/DeleteClientChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/DeleteClientChargeCommandHandler.java
new file mode 100644
index 0000000..64b7258
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/DeleteClientChargeCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.service.ClientChargeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME, action = ClientApiConstants.CLIENT_CHARGE_ACTION_DELETE)
+public class DeleteClientChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientChargeWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteClientChargeCommandHandler(final ClientChargeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteCharge(command.getClientId(), command.entityId());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/DeleteClientCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/DeleteClientCommandHandler.java
new file mode 100644
index 0000000..9502747
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/DeleteClientCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "DELETE")
+public class DeleteClientCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public DeleteClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.deleteClient(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/DeleteClientIdentifierCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/DeleteClientIdentifierCommandHandler.java
new file mode 100644
index 0000000..e202dd2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/DeleteClientIdentifierCommandHandler.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.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientIdentifierWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENTIDENTIFIER", action = "DELETE")
+public class DeleteClientIdentifierCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientIdentifierWritePlatformService clientIdentifierWritePlatformService;
+
+    @Autowired
+    public DeleteClientIdentifierCommandHandler(final ClientIdentifierWritePlatformService clientIdentifierWritePlatformService) {
+        this.clientIdentifierWritePlatformService = clientIdentifierWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientIdentifierWritePlatformService.deleteClientIdentifier(command.getClientId(), command.entityId(),
+                command.commandId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/PayClientChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/PayClientChargeCommandHandler.java
new file mode 100644
index 0000000..341d3ad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/PayClientChargeCommandHandler.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.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.service.ClientChargeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME, action = ClientApiConstants.CLIENT_CHARGE_ACTION_PAY)
+public class PayClientChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientChargeWritePlatformService writePlatformService;
+
+    @Autowired
+    public PayClientChargeCommandHandler(final ClientChargeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.payCharge(command.getClientId(), command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/ReActivateClientCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/ReActivateClientCommandHandler.java
new file mode 100644
index 0000000..774926e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/ReActivateClientCommandHandler.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.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "REACTIVATE")
+public class ReActivateClientCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public ReActivateClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.reActivateClient(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/RejectClientCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/RejectClientCommandHandler.java
new file mode 100644
index 0000000..86dce39
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/RejectClientCommandHandler.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.client.handler;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+@Service
+@CommandType(entity = "CLIENT", action = "REJECT")
+public class RejectClientCommandHandler implements NewCommandSourceHandler {
+	
+		private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public RejectClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.rejectClient(command.entityId(), command);
+    }
+
+}
+
+    
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UnassignClientStaffCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UnassignClientStaffCommandHandler.java
new file mode 100644
index 0000000..02aa30e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UnassignClientStaffCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "UNASSIGNSTAFF")
+public class UnassignClientStaffCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public UnassignClientStaffCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.clientWritePlatformService.unassignClientStaff(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UndoClientTransactionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UndoClientTransactionCommandHandler.java
new file mode 100644
index 0000000..590ad00
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UndoClientTransactionCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.service.ClientTransactionWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = ClientApiConstants.CLIENT_RESOURCE_NAME, action = ClientApiConstants.CLIENT_TRANSACTION_ACTION_UNDO)
+public class UndoClientTransactionCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientTransactionWritePlatformService writePlatformService;
+
+    @Autowired
+    public UndoClientTransactionCommandHandler(final ClientTransactionWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.undo(command.getClientId(), command.entityId());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UpdateClientCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UpdateClientCommandHandler.java
new file mode 100644
index 0000000..65a7aaa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UpdateClientCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "UPDATE")
+public class UpdateClientCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public UpdateClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.clientWritePlatformService.updateClient(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UpdateClientIdentifierCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UpdateClientIdentifierCommandHandler.java
new file mode 100644
index 0000000..0961def
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UpdateClientIdentifierCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientIdentifierWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENTIDENTIFIER", action = "UPDATE")
+public class UpdateClientIdentifierCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientIdentifierWritePlatformService clientIdentifierWritePlatformService;
+
+    @Autowired
+    public UpdateClientIdentifierCommandHandler(final ClientIdentifierWritePlatformService clientIdentifierWritePlatformService) {
+        this.clientIdentifierWritePlatformService = clientIdentifierWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.clientIdentifierWritePlatformService.updateClientIdentifier(command.getClientId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UpdateClientSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UpdateClientSavingsAccountCommandHandler.java
new file mode 100755
index 0000000..ef75400
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/UpdateClientSavingsAccountCommandHandler.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.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "UPDATESAVINGSACCOUNT")
+public class UpdateClientSavingsAccountCommandHandler implements NewCommandSourceHandler {
+    
+    private final ClientWritePlatformService clientWritePlatformService;
+
+    @Autowired
+    public UpdateClientSavingsAccountCommandHandler(final ClientWritePlatformService clientWritePlatformService){
+        this.clientWritePlatformService = clientWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.clientWritePlatformService.updateDefaultSavingsAccount(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/WaiveClientChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/WaiveClientChargeCommandHandler.java
new file mode 100644
index 0000000..986c1b1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/WaiveClientChargeCommandHandler.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.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.service.ClientChargeWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME, action = ClientApiConstants.CLIENT_CHARGE_ACTION_WAIVE)
+public class WaiveClientChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final ClientChargeWritePlatformService writePlatformService;
+
+    @Autowired
+    public WaiveClientChargeCommandHandler(final ClientChargeWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.waiveCharge(command.getClientId(), command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/WithdrawClientCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/WithdrawClientCommandHandler.java
new file mode 100644
index 0000000..54080f5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/handler/WithdrawClientCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.client.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.service.ClientWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+
+@Service
+@CommandType(entity = "CLIENT", action = "WITHDRAW")
+public class WithdrawClientCommandHandler  implements NewCommandSourceHandler {
+	
+	private final ClientWritePlatformService clientWritePlatformService;
+
+@Autowired
+public WithdrawClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) {
+    this.clientWritePlatformService = clientWritePlatformService;
+}
+
+@Transactional
+@Override
+public CommandProcessingResult processCommand(final JsonCommand command) {
+
+    return this.clientWritePlatformService.withdrawClient(command.entityId(), command);
+}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/serialization/ClientIdentifierCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/serialization/ClientIdentifierCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..8855bba
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/serialization/ClientIdentifierCommandFromApiJsonDeserializer.java
@@ -0,0 +1,73 @@
+/**
+ * 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.client.serialization;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.client.command.ClientIdentifierCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link ClientIdentifierCommandFromApiJsonDeserializer} 's.
+ */
+@Component
+public final class ClientIdentifierCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<ClientIdentifierCommand> {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("documentTypeId", "documentKey", "description"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ClientIdentifierCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public ClientIdentifierCommand commandFromApiJson(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 JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Long documentTypeId = this.fromApiJsonHelper.extractLongNamed("documentTypeId", element);
+        final String documentKey = this.fromApiJsonHelper.extractStringNamed("documentKey", element);
+        final String documentDescription = this.fromApiJsonHelper.extractStringNamed("documentDescription", element);
+
+        return new ClientIdentifierCommand(documentTypeId, documentKey, documentDescription);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformService.java
new file mode 100644
index 0000000..89b39a5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.service;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.portfolio.client.data.ClientChargeData;
+
+public interface ClientChargeReadPlatformService {
+
+    Page<ClientChargeData> retrieveClientCharges(Long clientId, String status, Boolean pendingPayment, SearchParameters parameters);
+
+    ClientChargeData retrieveClientCharge(Long clientId, Long clientChargeId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java
new file mode 100644
index 0000000..03a2598
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformServiceImpl.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.portfolio.client.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeEnumerations;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.data.ClientChargeData;
+import org.apache.fineract.portfolio.client.exception.ClientChargeNotFoundException;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ClientChargeReadPlatformServiceImpl implements ClientChargeReadPlatformService {
+
+    private final PaginationHelper<ClientChargeData> paginationHelper = new PaginationHelper<>();
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final ClientChargeMapper clientChargeMapper;
+
+    @Autowired
+    public ClientChargeReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.clientChargeMapper = new ClientChargeMapper();
+    }
+
+    public static final class ClientChargeMapper implements RowMapper<ClientChargeData> {
+
+        @Override
+        public ClientChargeData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final Long chargeId = rs.getLong("chargeId");
+            final Long clientId = rs.getLong("clientId");
+            final String name = rs.getString("name");
+            final BigDecimal amount = rs.getBigDecimal("amountDue");
+            final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPaid");
+            final BigDecimal amountWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWaived");
+            final BigDecimal amountWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWrittenOff");
+            final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final int chargeTime = rs.getInt("chargeTime");
+            final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
+
+            final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
+            final int chargeCalculation = rs.getInt("chargeCalculation");
+            final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
+            final boolean penalty = rs.getBoolean("penalty");
+            final Boolean isPaid = rs.getBoolean("isPaid");
+            final Boolean isWaived = rs.getBoolean("waived");
+            final Boolean isActive = rs.getBoolean("isActive");
+            final LocalDate inactivationDate = JdbcSupport.getLocalDate(rs, "inactivationDate");
+
+            final Collection<ChargeData> chargeOptions = null;
+
+            return ClientChargeData.instance(id, clientId, chargeId, name, chargeTimeType, dueDate, chargeCalculationType, currency, amount,
+                    amountPaid, amountWaived, amountWrittenOff, amountOutstanding, penalty, isPaid, isWaived, isActive, inactivationDate,
+                    chargeOptions);
+
+        }
+
+        public String schema() {
+            return " cc.id as id, c.id as chargeId, cc.client_id as clientId, c.name as name, cc.amount as amountDue, "
+                    + "cc.amount_paid_derived as amountPaid, cc.amount_waived_derived as amountWaived, "
+                    + "cc.amount_writtenoff_derived as amountWrittenOff, cc.amount_outstanding_derived as amountOutstanding, "
+                    + "cc.charge_time_enum as chargeTime, cc.is_penalty as penalty, cc.charge_due_date as dueAsOfDate, "
+                    + "cc.charge_calculation_enum as chargeCalculation, cc.is_paid_derived as isPaid, cc.waived as waived, "
+                    + "cc.is_active as isActive, cc.inactivated_on_date as inactivationDate, "
+                    + "c.currency_code as currencyCode, oc.name as currencyName, "
+                    + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+                    + "oc.internationalized_name_code as currencyNameCode from m_charge c "
+                    + "join m_organisation_currency oc on c.currency_code = oc.code join m_client_charge cc on cc.charge_id = c.id ";
+        }
+
+    }
+
+    @Override
+    public ClientChargeData retrieveClientCharge(Long clientId, Long clientChargeId) {
+        try {
+            this.context.authenticatedUser();
+
+            final ClientChargeMapper rm = new ClientChargeMapper();
+
+            final String sql = "select " + rm.schema() + " where cc.client_id=? and cc.id=? ";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { clientId, clientChargeId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new ClientChargeNotFoundException(clientChargeId, clientId);
+        }
+    }
+
+    @Override
+    public Page<ClientChargeData> retrieveClientCharges(Long clientId, String status, Boolean pendingPayment,
+            SearchParameters searchParameters) {
+        final ClientChargeMapper rm = new ClientChargeMapper();
+        final StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ").append(rm.schema()).append(" where cc.client_id=? ");
+
+        // filter for active charges
+        if (status.equalsIgnoreCase(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE)) {
+            sqlBuilder.append(" and cc.is_active = 1 ");
+        } else if (status.equalsIgnoreCase(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE)) {
+            sqlBuilder.append(" and cc.is_active = 0 ");
+        }
+
+        // filter for paid charges
+        if (pendingPayment != null && pendingPayment) {
+            sqlBuilder.append(" and ( cc.is_paid_derived = 0 and cc.waived = 0) ");
+        } else if (pendingPayment != null && !pendingPayment) {
+            sqlBuilder.append(" and (cc.is_paid_derived = 1 or cc.waived = 1) ");
+        }
+
+        sqlBuilder.append(" order by cc.charge_time_enum ASC, cc.charge_due_date DESC, cc.is_penalty ASC ");
+
+        // apply limit and offsets
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), new Object[] { clientId },
+                this.clientChargeMapper);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformService.java
new file mode 100644
index 0000000..27352a0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformService.java
@@ -0,0 +1,45 @@
+/**
+ * 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.client.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.springframework.transaction.annotation.Transactional;
+
+public interface ClientChargeWritePlatformService {
+
+    @Transactional
+    CommandProcessingResult addCharge(Long clientId, JsonCommand command);
+
+    @Transactional
+    CommandProcessingResult updateCharge(Long clientId, JsonCommand command);
+
+    @Transactional
+    CommandProcessingResult deleteCharge(Long clientId, Long clientChargeId);
+
+    @Transactional
+    CommandProcessingResult waiveCharge(Long clientId, Long clientChargeId);
+
+    @Transactional
+    CommandProcessingResult payCharge(Long clientId, Long clientChargeId, JsonCommand command);
+
+    @Transactional
+    CommandProcessingResult inactivateCharge(Long clientId, Long clientChargeId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..09ee4f16
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,437 @@
+/**
+ * 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.client.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeAppliedToException;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.data.ClientChargeDataValidator;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientCharge;
+import org.apache.fineract.portfolio.client.domain.ClientChargePaidBy;
+import org.apache.fineract.portfolio.client.domain.ClientChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.ClientTransaction;
+import org.apache.fineract.portfolio.client.domain.ClientTransactionRepository;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements ClientChargeWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ClientChargeWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final ChargeRepositoryWrapper chargeRepository;
+    private final ClientRepositoryWrapper clientRepository;
+    private final ClientChargeDataValidator clientChargeDataValidator;
+    private final ConfigurationDomainService configurationDomainService;
+    private final HolidayRepositoryWrapper holidayRepository;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+    private final ClientChargeRepositoryWrapper clientChargeRepository;
+    private final ClientTransactionRepository clientTransactionRepository;
+    private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+
+    @Autowired
+    public ClientChargeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final ChargeRepositoryWrapper chargeRepository, final ClientChargeDataValidator clientChargeDataValidator,
+            final ClientRepositoryWrapper clientRepository, final HolidayRepositoryWrapper holidayRepositoryWrapper,
+            final ConfigurationDomainService configurationDomainService, final ClientChargeRepositoryWrapper clientChargeRepository,
+            final WorkingDaysRepositoryWrapper workingDaysRepository, final ClientTransactionRepository clientTransactionRepository,
+            final PaymentDetailWritePlatformService paymentDetailWritePlatformService,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService) {
+        this.context = context;
+        this.chargeRepository = chargeRepository;
+        this.clientChargeDataValidator = clientChargeDataValidator;
+        this.clientRepository = clientRepository;
+        this.holidayRepository = holidayRepositoryWrapper;
+        this.configurationDomainService = configurationDomainService;
+        this.clientChargeRepository = clientChargeRepository;
+        this.workingDaysRepository = workingDaysRepository;
+        this.clientTransactionRepository = clientTransactionRepository;
+        this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult addCharge(Long clientId, JsonCommand command) {
+        try {
+            this.clientChargeDataValidator.validateAdd(command.json());
+
+            final Client client = clientRepository.getActiveClientInUserScope(clientId);
+
+            final Long chargeDefinitionId = command.longValueOfParameterNamed(ClientApiConstants.chargeIdParamName);
+            final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
+
+            // validate for client charge
+            if (!charge.isClientCharge()) {
+                final String errorMessage = "Charge with identifier " + charge.getId() + " cannot be applied to a Client";
+                throw new ChargeCannotBeAppliedToException("client", errorMessage, charge.getId());
+            }
+
+            final ClientCharge clientCharge = ClientCharge.createNew(client, charge, command);
+
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat());
+            validateDueDateOnWorkingDay(clientCharge, fmt);
+
+            this.clientChargeRepository.save(clientCharge);
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(clientCharge.getId()) //
+                    .withOfficeId(clientCharge.getClient().getOffice().getId()) //
+                    .withClientId(clientCharge.getClient().getId()) //
+                    .build();
+        } catch (DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(clientId, null, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    public CommandProcessingResult payCharge(Long clientId, Long clientChargeId, JsonCommand command) {
+        try {
+            this.clientChargeDataValidator.validatePayCharge(command.json());
+
+            final Client client = this.clientRepository.getActiveClientInUserScope(clientId);
+
+            final ClientCharge clientCharge = this.clientChargeRepository.findOneWithNotFoundDetection(clientChargeId);
+
+            final Locale locale = command.extractLocale();
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+            final LocalDate transactionDate = command.localDateValueOfParameterNamed(ClientApiConstants.transactionDateParamName);
+            final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(ClientApiConstants.amountParamName);
+            final Money chargePaid = Money.of(clientCharge.getCurrency(), amountPaid);
+
+            // Validate business rules for payment
+            validatePaymentTransaction(client, clientCharge, fmt, transactionDate, amountPaid);
+
+            // pay the charge
+            clientCharge.pay(chargePaid);
+
+            // create Payment Transaction
+            final Map<String, Object> changes = new LinkedHashMap<>();
+            final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+            ClientTransaction clientTransaction = ClientTransaction.payCharge(client, client.getOffice(), paymentDetail, transactionDate,
+                    chargePaid, clientCharge.getCurrency().getCode(), getAppUserIfPresent());
+            this.clientTransactionRepository.saveAndFlush(clientTransaction);
+
+            // update charge paid by associations
+            final ClientChargePaidBy chargePaidBy = ClientChargePaidBy.instance(clientTransaction, clientCharge, amountPaid);
+            clientTransaction.getClientChargePaidByCollection().add(chargePaidBy);
+
+            // generate accounting entries
+            generateAccountingEntries(clientTransaction);
+
+            return new CommandProcessingResultBuilder() //
+                    .withTransactionId(clientTransaction.getId().toString())//
+                    .withEntityId(clientCharge.getId()) //
+                    .withOfficeId(clientCharge.getClient().getOffice().getId()) //
+                    .withClientId(clientCharge.getClient().getId()).build();
+        } catch (DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(clientId, clientChargeId, dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+
+    private void generateAccountingEntries(ClientTransaction clientTransaction) {
+        Map<String, Object> accountingBridgeData = clientTransaction.toMapData();
+        journalEntryWritePlatformService.createJournalEntriesForClientTransactions(accountingBridgeData);
+    }
+
+    @Override
+    public CommandProcessingResult waiveCharge(Long clientId, Long clientChargeId) {
+        try {
+            final Client client = this.clientRepository.getActiveClientInUserScope(clientId);
+            final ClientCharge clientCharge = this.clientChargeRepository.findOneWithNotFoundDetection(clientChargeId);
+            final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
+
+            // Validate business rules for payment
+            validateWaiverTransaction(client, clientCharge);
+
+            // waive the charge
+            Money waivedAmount = clientCharge.waive();
+
+            // create Waiver Transaction
+            ClientTransaction clientTransaction = ClientTransaction.waiver(client, client.getOffice(), transactionDate, waivedAmount,
+                    clientCharge.getCurrency().getCode(), getAppUserIfPresent());
+            this.clientTransactionRepository.save(clientTransaction);
+
+            // update charge paid by associations
+            final ClientChargePaidBy chargePaidBy = ClientChargePaidBy.instance(clientTransaction, clientCharge, waivedAmount.getAmount());
+            clientTransaction.getClientChargePaidByCollection().add(chargePaidBy);
+
+            return new CommandProcessingResultBuilder().withTransactionId(clientTransaction.getId().toString())//
+                    .withEntityId(clientCharge.getId()) //
+                    .withOfficeId(clientCharge.getClient().getOffice().getId()) //
+                    .withClientId(clientCharge.getClient().getId()) //
+                    .build();
+        } catch (DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(clientId, clientChargeId, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    public CommandProcessingResult deleteCharge(Long clientId, Long clientChargeId) {
+        try {
+            final Client client = this.clientRepository.getActiveClientInUserScope(clientId);
+            final ClientCharge clientCharge = this.clientChargeRepository.findOneWithNotFoundDetection(clientChargeId);
+
+            // Validate business rules for charge deletion
+            validateChargeDeletion(client, clientCharge);
+
+            // delete the charge
+            clientChargeRepository.delete(clientCharge);
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(clientCharge.getId()) //
+                    .withOfficeId(clientCharge.getClient().getOffice().getId()) //
+                    .withClientId(clientCharge.getClient().getId()) //
+                    .build();
+        } catch (DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(clientId, clientChargeId, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    /**
+     * Validates transaction to ensure that <br>
+     * charge is active <br>
+     * transaction date is valid (between client activation and todays date)
+     * <br>
+     * charge is not already paid or waived <br>
+     * amount is not more than total due
+     * 
+     * @param client
+     * @param clientCharge
+     * @param fmt
+     * @param transactionDate
+     * @param amountPaid
+     * @param requiresTransactionDateValidation
+     *            if set to false, transaction date specific validation is
+     *            skipped
+     * @param requiresTransactionAmountValidation
+     *            if set to false transaction amount validation is skipped
+     * @return
+     */
+    private void validatePaymentDateAndAmount(final Client client, final ClientCharge clientCharge, final DateTimeFormatter fmt,
+            final LocalDate transactionDate, final BigDecimal amountPaid, final boolean requiresTransactionDateValidation,
+            final boolean requiresTransactionAmountValidation) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+
+        if (clientCharge.isNotActive()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.not.active");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (requiresTransactionDateValidation) {
+            validateTransactionDateOnWorkingDay(transactionDate, clientCharge, fmt);
+
+            if (client.getActivationLocalDate() != null && transactionDate.isBefore(client.getActivationLocalDate())) {
+                baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName).value(transactionDate.toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate");
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            }
+
+            if (DateUtils.isDateInTheFuture(transactionDate)) {
+                baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName).value(transactionDate.toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate");
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            }
+        }
+
+        // validate charge is not already paid or waived
+        if (clientCharge.isWaived()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.already.waived");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        } else if (clientCharge.isPaid()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.paid");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (requiresTransactionAmountValidation) {
+            final Money chargePaid = Money.of(clientCharge.getCurrency(), amountPaid);
+            if (!clientCharge.getAmountOutstanding().isGreaterThanOrEqualTo(chargePaid)) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+    }
+
+    public void validateWaiverTransaction(final Client client, final ClientCharge clientCharge) {
+        DateTimeFormatter fmt = null;
+        LocalDate transactionDate = null;
+        BigDecimal amountPaid = null;
+        boolean requiresTransactionDateValidation = false;
+        boolean requiresTransactionAmountValidation = false;
+        validatePaymentDateAndAmount(client, clientCharge, fmt, transactionDate, amountPaid, requiresTransactionDateValidation,
+                requiresTransactionAmountValidation);
+    }
+
+    public void validatePaymentTransaction(final Client client, final ClientCharge clientCharge, final DateTimeFormatter fmt,
+            final LocalDate transactionDate, final BigDecimal amountPaid) {
+        boolean requiresTransactionDateValidation = true;
+        boolean requiresTransactionAmountValidation = true;
+        validatePaymentDateAndAmount(client, clientCharge, fmt, transactionDate, amountPaid, requiresTransactionDateValidation,
+                requiresTransactionAmountValidation);
+    }
+
+    public void validateChargeDeletion(final Client client, final ClientCharge clientCharge) {
+        DateTimeFormatter fmt = null;
+        LocalDate transactionDate = null;
+        BigDecimal amountPaid = null;
+        boolean requiresTransactionDateValidation = false;
+        boolean requiresTransactionAmountValidation = false;
+        validatePaymentDateAndAmount(client, clientCharge, fmt, transactionDate, amountPaid, requiresTransactionDateValidation,
+                requiresTransactionAmountValidation);
+    }
+
+    /**
+     * @param clientId
+     * @return
+     */
+    @Override
+    public CommandProcessingResult updateCharge(@SuppressWarnings("unused") Long clientId,
+            @SuppressWarnings("unused") JsonCommand command) {
+        // functionality not yet supported
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("unused")
+    public CommandProcessingResult inactivateCharge(Long clientId, Long clientChargeId) {
+        // functionality not yet supported
+        return null;
+    }
+
+    /**
+     * Ensures that the charge due date is not on a holiday or a non working day
+     * 
+     * @param clientCharge
+     * @param fmt
+     */
+    private void validateDueDateOnWorkingDay(final ClientCharge clientCharge, final DateTimeFormatter fmt) {
+        validateActivityDateFallOnAWorkingDay(clientCharge.getDueLocalDate(), clientCharge.getOfficeId(),
+                ClientApiConstants.dueAsOfDateParamName, "charge.due.date.is.on.holiday", "charge.due.date.is.a.non.workingday", fmt);
+    }
+
+    /**
+     * Ensures that the charge transaction date (for payments) is not on a
+     * holiday or a non working day
+     * 
+     * @param savingsAccountCharge
+     * @param fmt
+     */
+    private void validateTransactionDateOnWorkingDay(final LocalDate transactionDate, final ClientCharge clientCharge,
+            final DateTimeFormatter fmt) {
+        validateActivityDateFallOnAWorkingDay(transactionDate, clientCharge.getOfficeId(), ClientApiConstants.transactionDateParamName,
+                "transaction.not.allowed.transaction.date.is.on.holiday", "transaction.not.allowed.transaction.date.is.a.non.workingday",
+                fmt);
+    }
+
+    /**
+     * @param date
+     * @param officeId
+     * @param jsonPropertyName
+     * @param errorMessageFragment
+     * @param fmt
+     */
+    private void validateActivityDateFallOnAWorkingDay(final LocalDate date, final Long officeId, final String jsonPropertyName,
+            final String errorMessageFragmentForActivityOnHoliday, final String errorMessageFragmentForActivityOnNonWorkingDay,
+            final DateTimeFormatter fmt) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
+        if (date != null) {
+            // transaction date should not be on a holiday or non working day
+            if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled() && this.holidayRepository.isHoliday(officeId, date)) {
+                baseDataValidator.reset().parameter(jsonPropertyName).value(date.toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode(errorMessageFragmentForActivityOnHoliday);
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+
+            if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled()
+                    && !this.workingDaysRepository.isWorkingDay(date)) {
+                baseDataValidator.reset().parameter(jsonPropertyName).value(date.toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode(errorMessageFragmentForActivityOnNonWorkingDay);
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+    }
+
+    private AppUser getAppUserIfPresent() {
+        AppUser user = null;
+        if (this.context != null) {
+            user = this.context.getAuthenticatedUserIfPresent();
+        }
+        return user;
+    }
+
+    private void handleDataIntegrityIssues(@SuppressWarnings("unused") final Long clientId, final Long clientChargeId,
+            final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("FK_m_client_charge_paid_by_m_client_charge")) {
+
+        throw new PlatformDataIntegrityException("error.msg.client.charge.cannot.be.deleted",
+                "Client charge with id `" + clientChargeId + "` cannot be deleted as transactions have been made on the same",
+                "clientChargeId", clientChargeId); }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.client.charges.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierReadPlatformService.java
new file mode 100755
index 0000000..9345105
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierReadPlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.client.data.ClientIdentifierData;
+
+public interface ClientIdentifierReadPlatformService {
+
+    Collection<ClientIdentifierData> retrieveClientIdentifiers(Long clientId);
+
+    ClientIdentifierData retrieveClientIdentifier(Long clientId, Long clientIdentifierId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierReadPlatformServiceImpl.java
new file mode 100755
index 0000000..304c227
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierReadPlatformServiceImpl.java
@@ -0,0 +1,118 @@
+/**
+ * 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.client.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+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.portfolio.client.data.ClientIdentifierData;
+import org.apache.fineract.portfolio.client.exception.ClientIdentifierNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+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 ClientIdentifierReadPlatformServiceImpl implements ClientIdentifierReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public ClientIdentifierReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public Collection<ClientIdentifierData> retrieveClientIdentifiers(final Long clientId) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final ClientIdentityMapper rm = new ClientIdentityMapper();
+
+        String sql = "select " + rm.schema();
+
+        sql += " order by ci.id";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { clientId, hierarchySearchString });
+    }
+
+    @Override
+    public ClientIdentifierData retrieveClientIdentifier(final Long clientId, final Long clientIdentifierId) {
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+            final String hierarchy = currentUser.getOffice().getHierarchy();
+            final String hierarchySearchString = hierarchy + "%";
+
+            final ClientIdentityMapper rm = new ClientIdentityMapper();
+
+            String sql = "select " + rm.schema();
+
+            sql += " and ci.id = ?";
+
+            final ClientIdentifierData clientIdentifierData = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { clientId,
+                    hierarchySearchString, clientIdentifierId });
+
+            return clientIdentifierData;
+        } catch (final EmptyResultDataAccessException e) {
+            throw new ClientIdentifierNotFoundException(clientIdentifierId);
+        }
+
+    }
+
+    private static final class ClientIdentityMapper implements RowMapper<ClientIdentifierData> {
+
+        public ClientIdentityMapper() {}
+
+        public String schema() {
+            return "ci.id as id, ci.client_id as clientId, ci.document_type_id as documentTypeId, ci.document_key as documentKey,"
+                    + " ci.description as description, cv.code_value as documentType "
+                    + " from m_client_identifier ci, m_client c, m_office o, m_code_value cv"
+                    + " where ci.client_id=c.id and c.office_id=o.id" + " and ci.document_type_id=cv.id"
+                    + " and ci.client_id = ? and o.hierarchy like ? ";
+        }
+
+        @Override
+        public ClientIdentifierData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final Long documentTypeId = JdbcSupport.getLong(rs, "documentTypeId");
+            final String documentKey = rs.getString("documentKey");
+            final String description = rs.getString("description");
+            final String documentTypeName = rs.getString("documentType");
+
+            final CodeValueData documentType = CodeValueData.instance(documentTypeId, documentTypeName);
+
+            return ClientIdentifierData.singleItem(id, clientId, documentType, documentKey, description);
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformService.java
new file mode 100755
index 0000000..f763e73
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.client.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface ClientIdentifierWritePlatformService {
+
+    CommandProcessingResult addClientIdentifier(Long clientId, JsonCommand command);
+
+    CommandProcessingResult updateClientIdentifier(Long clientId, Long clientIdentifierId, JsonCommand command);
+
+    CommandProcessingResult deleteClientIdentifier(Long clientId, Long clientIdentifierId, Long commandId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..349c318
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,197 @@
+/**
+ * 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.client.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.codes.exception.CodeValueNotFoundException;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.command.ClientIdentifierCommand;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientIdentifier;
+import org.apache.fineract.portfolio.client.domain.ClientIdentifierRepository;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientIdentifierNotFoundException;
+import org.apache.fineract.portfolio.client.exception.DuplicateClientIdentifierException;
+import org.apache.fineract.portfolio.client.serialization.ClientIdentifierCommandFromApiJsonDeserializer;
+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 ClientIdentifierWritePlatformServiceJpaRepositoryImpl implements ClientIdentifierWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ClientIdentifierWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final ClientRepositoryWrapper clientRepository;
+    private final ClientIdentifierRepository clientIdentifierRepository;
+    private final CodeValueRepositoryWrapper codeValueRepository;
+    private final ClientIdentifierCommandFromApiJsonDeserializer clientIdentifierCommandFromApiJsonDeserializer;
+
+    @Autowired
+    public ClientIdentifierWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final ClientRepositoryWrapper clientRepository, final ClientIdentifierRepository clientIdentifierRepository,
+            final CodeValueRepositoryWrapper codeValueRepository,
+            final ClientIdentifierCommandFromApiJsonDeserializer clientIdentifierCommandFromApiJsonDeserializer) {
+        this.context = context;
+        this.clientRepository = clientRepository;
+        this.clientIdentifierRepository = clientIdentifierRepository;
+        this.codeValueRepository = codeValueRepository;
+        this.clientIdentifierCommandFromApiJsonDeserializer = clientIdentifierCommandFromApiJsonDeserializer;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult addClientIdentifier(final Long clientId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+        final ClientIdentifierCommand clientIdentifierCommand = this.clientIdentifierCommandFromApiJsonDeserializer
+                .commandFromApiJson(command.json());
+        clientIdentifierCommand.validateForCreate();
+
+        final String documentKey = clientIdentifierCommand.getDocumentKey();
+        String documentTypeLabel = null;
+        Long documentTypeId = null;
+        try {
+            final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+
+            final CodeValue documentType = this.codeValueRepository.findOneWithNotFoundDetection(clientIdentifierCommand
+                    .getDocumentTypeId());
+            documentTypeId = documentType.getId();
+            documentTypeLabel = documentType.label();
+
+            final ClientIdentifier clientIdentifier = ClientIdentifier.fromJson(client, documentType, command);
+
+            this.clientIdentifierRepository.save(clientIdentifier);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withOfficeId(client.officeId()) //
+                    .withClientId(clientId) //
+                    .withEntityId(clientIdentifier.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleClientIdentifierDataIntegrityViolation(documentTypeLabel, documentTypeId, documentKey, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateClientIdentifier(final Long clientId, final Long identifierId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+        final ClientIdentifierCommand clientIdentifierCommand = this.clientIdentifierCommandFromApiJsonDeserializer
+                .commandFromApiJson(command.json());
+        clientIdentifierCommand.validateForUpdate();
+
+        String documentTypeLabel = null;
+        String documentKey = null;
+        Long documentTypeId = clientIdentifierCommand.getDocumentTypeId();
+        try {
+            CodeValue documentType = null;
+
+            final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            final ClientIdentifier clientIdentifierForUpdate = this.clientIdentifierRepository.findOne(identifierId);
+            if (clientIdentifierForUpdate == null) { throw new ClientIdentifierNotFoundException(identifierId); }
+
+            final Map<String, Object> changes = clientIdentifierForUpdate.update(command);
+
+            if (changes.containsKey("documentTypeId")) {
+                documentType = this.codeValueRepository.findOneWithNotFoundDetection(documentTypeId);
+                if (documentType == null) { throw new CodeValueNotFoundException(documentTypeId); }
+
+                documentTypeId = documentType.getId();
+                documentTypeLabel = documentType.label();
+                clientIdentifierForUpdate.update(documentType);
+            }
+
+            if (changes.containsKey("documentTypeId") && changes.containsKey("documentKey")) {
+                documentTypeId = clientIdentifierCommand.getDocumentTypeId();
+                documentKey = clientIdentifierCommand.getDocumentKey();
+            } else if (changes.containsKey("documentTypeId") && !changes.containsKey("documentKey")) {
+                documentTypeId = clientIdentifierCommand.getDocumentTypeId();
+                documentKey = clientIdentifierForUpdate.documentKey();
+            } else if (!changes.containsKey("documentTypeId") && changes.containsKey("documentKey")) {
+                documentTypeId = clientIdentifierForUpdate.documentTypeId();
+                documentKey = clientIdentifierForUpdate.documentKey();
+            }
+
+            if (!changes.isEmpty()) {
+                this.clientIdentifierRepository.saveAndFlush(clientIdentifierForUpdate);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withOfficeId(client.officeId()) //
+                    .withClientId(clientId) //
+                    .withEntityId(identifierId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleClientIdentifierDataIntegrityViolation(documentTypeLabel, documentTypeId, documentKey, dve);
+            return new CommandProcessingResult(Long.valueOf(-1));
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteClientIdentifier(final Long clientId, final Long identifierId, final Long commandId) {
+
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+
+        final ClientIdentifier clientIdentifier = this.clientIdentifierRepository.findOne(identifierId);
+        if (clientIdentifier == null) { throw new ClientIdentifierNotFoundException(identifierId); }
+        this.clientIdentifierRepository.delete(clientIdentifier);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(commandId) //
+                .withOfficeId(client.officeId()) //
+                .withClientId(clientId) //
+                .withEntityId(identifierId) //
+                .build();
+    }
+
+    private void handleClientIdentifierDataIntegrityViolation(final String documentTypeLabel, final Long documentTypeId,
+            final String documentKey, final DataIntegrityViolationException dve) {
+
+        if (dve.getMostSpecificCause().getMessage().contains("unique_client_identifier")) {
+            throw new DuplicateClientIdentifierException(documentTypeLabel);
+        } else if (dve.getMostSpecificCause().getMessage().contains("unique_identifier_key")) { throw new DuplicateClientIdentifierException(
+                documentTypeId, documentTypeLabel, documentKey); }
+
+        logAsErrorUnexpectedDataIntegrityException(dve);
+        throw new PlatformDataIntegrityException("error.msg.clientIdentifier.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
+        logger.error(dve.getMessage(), dve);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformService.java
new file mode 100644
index 0000000..fc9f72f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformService.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.client.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.portfolio.client.data.ClientData;
+
+public interface ClientReadPlatformService {
+
+    ClientData retrieveTemplate(Long officeId, boolean staffInSelectedOfficeOnly);
+
+    Page<ClientData> retrieveAll(SearchParameters searchParameters);
+
+    ClientData retrieveOne(Long clientId);
+
+    Collection<ClientData> retrieveAllForLookup(String extraCriteria);
+
+    Collection<ClientData> retrieveAllForLookupByOfficeId(Long officeId);
+
+    ClientData retrieveClientByIdentifier(Long identifierTypeId, String identifierKey);
+
+    Collection<ClientData> retrieveClientMembersOfGroup(Long groupId);
+
+    Collection<ClientData> retrieveActiveClientMembersOfGroup(Long groupId);
+
+    Collection<ClientData> retrieveActiveClientMembersOfCenter(final Long centerId);
+
+    ClientData retrieveAllNarrations(String clientNarrations);
+}
\ 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
new file mode 100644
index 0000000..1cc3355
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
@@ -0,0 +1,766 @@
+/**
+ * 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.client.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.data.ClientNonPersonData;
+import org.apache.fineract.portfolio.client.data.ClientTimelineData;
+import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
+import org.apache.fineract.portfolio.client.domain.ClientStatus;
+import org.apache.fineract.portfolio.client.domain.LegalForm;
+import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class ClientReadPlatformServiceImpl implements ClientReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+    private final SavingsProductReadPlatformService savingsProductReadPlatformService;
+    // data mappers
+    private final PaginationHelper<ClientData> paginationHelper = new PaginationHelper<>();
+    private final ClientMapper clientMapper = new ClientMapper();
+    private final ClientLookupMapper lookupMapper = new ClientLookupMapper();
+    private final ClientMembersOfGroupMapper membersOfGroupMapper = new ClientMembersOfGroupMapper();
+    private final ParentGroupsMapper clientGroupsMapper = new ParentGroupsMapper();
+
+    @Autowired
+    public ClientReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final OfficeReadPlatformService officeReadPlatformService, final StaffReadPlatformService staffReadPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService,
+            final SavingsProductReadPlatformService savingsProductReadPlatformService) {
+        this.context = context;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+        this.savingsProductReadPlatformService = savingsProductReadPlatformService;
+    }
+
+    @Override
+    public ClientData retrieveTemplate(final Long officeId, final boolean staffInSelectedOfficeOnly) {
+        this.context.authenticatedUser();
+
+        final Long defaultOfficeId = defaultToUsersOfficeIfNull(officeId);
+
+        final Collection<OfficeData> offices = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+        final Collection<SavingsProductData> savingsProductDatas = this.savingsProductReadPlatformService.retrieveAllForLookupByType(null);
+
+        Collection<StaffData> staffOptions = null;
+
+        final boolean loanOfficersOnly = false;
+        if (staffInSelectedOfficeOnly) {
+            staffOptions = this.staffReadPlatformService.retrieveAllStaffForDropdown(defaultOfficeId);
+        } else {
+            staffOptions = this.staffReadPlatformService.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(defaultOfficeId,
+                    loanOfficersOnly);
+        }
+        if (CollectionUtils.isEmpty(staffOptions)) {
+            staffOptions = null;
+        }
+        final List<CodeValueData> genderOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.GENDER));
+
+        final List<CodeValueData> clientTypeOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_TYPE));
+
+        final List<CodeValueData> clientClassificationOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_CLASSIFICATION));
+        
+        final List<CodeValueData> clientNonPersonConstitutionOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_NON_PERSON_CONSTITUTION));
+        
+        final List<CodeValueData> clientNonPersonMainBusinessLineOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_NON_PERSON_MAIN_BUSINESS_LINE));
+        
+        final List<EnumOptionData> clientLegalFormOptions = ClientEnumerations.legalForm(LegalForm.values());
+
+        return ClientData.template(defaultOfficeId, new LocalDate(), offices, staffOptions, null, genderOptions, savingsProductDatas,
+                clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions,
+                clientLegalFormOptions);
+    }
+
+    @Override
+    public Page<ClientData> retrieveAll(final SearchParameters searchParameters) {
+
+        final String userOfficeHierarchy = this.context.officeHierarchy();
+        final String underHierarchySearchString = userOfficeHierarchy + "%";
+        final String appUserID = String.valueOf(context.authenticatedUser().getId());
+
+        // if (searchParameters.isScopedByOfficeHierarchy()) {
+        // this.context.validateAccessRights(searchParameters.getHierarchy());
+        // underHierarchySearchString = searchParameters.getHierarchy() + "%";
+        // }
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.clientMapper.schema());
+        sqlBuilder.append(" where (o.hierarchy like ? or transferToOffice.hierarchy like ?) ");
+        
+        if(searchParameters.isSelfUser()){
+        	sqlBuilder.append(" and c.id in (select umap.client_id from m_selfservice_user_client_mapping as umap where umap.appuser_id = ? ) ");
+        }
+
+        final String extraCriteria = buildSqlStringFromClientCriteria(searchParameters);
+
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            sqlBuilder.append(" and (").append(extraCriteria).append(")");
+        }
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        Object[] params = new Object[] {underHierarchySearchString, underHierarchySearchString };
+        if(searchParameters.isSelfUser()){
+            params = new Object[] {underHierarchySearchString, underHierarchySearchString, appUserID };
+        }
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), params, this.clientMapper);
+    }
+
+    private String buildSqlStringFromClientCriteria(final SearchParameters searchParameters) {
+
+        String sqlSearch = searchParameters.getSqlSearch();
+        final Long officeId = searchParameters.getOfficeId();
+        final String externalId = searchParameters.getExternalId();
+        final String displayName = searchParameters.getName();
+        final String firstname = searchParameters.getFirstname();
+        final String lastname = searchParameters.getLastname();
+
+        String extraCriteria = "";
+        if (sqlSearch != null) {
+            sqlSearch = sqlSearch.replaceAll(" display_name ", " c.display_name ");
+            sqlSearch = sqlSearch.replaceAll("display_name ", "c.display_name ");
+            extraCriteria = " and (" + sqlSearch + ")";
+        }
+
+        if (officeId != null) {
+            extraCriteria += " and c.office_id = " + officeId;
+        }
+
+        if (externalId != null) {
+            extraCriteria += " and c.external_id like " + ApiParameterHelper.sqlEncodeString(externalId);
+        }
+
+        if (displayName != null) {
+            //extraCriteria += " and concat(ifnull(c.firstname, ''), if(c.firstname > '',' ', '') , ifnull(c.lastname, '')) like "
+			extraCriteria += " and c.display_name like "
+                    + ApiParameterHelper.sqlEncodeString("%" + displayName + "%");
+        }
+
+        if (firstname != null) {
+            extraCriteria += " and c.firstname like " + ApiParameterHelper.sqlEncodeString(firstname);
+        }
+
+        if (lastname != null) {
+            extraCriteria += " and c.lastname like " + ApiParameterHelper.sqlEncodeString(lastname);
+        }
+
+        if (searchParameters.isScopedByOfficeHierarchy()) {
+            extraCriteria += " and o.hierarchy like " + ApiParameterHelper.sqlEncodeString(searchParameters.getHierarchy() + "%");
+        }
+        
+        if(searchParameters.isOrphansOnly()){
+        	extraCriteria += " and c.id NOT IN (select client_id from m_group_client) ";
+        }
+
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            extraCriteria = extraCriteria.substring(4);
+        }
+
+        return extraCriteria;
+    }
+
+    @Override
+    public ClientData retrieveOne(final Long clientId) {
+        try {
+            final String hierarchy = this.context.officeHierarchy();
+            final String hierarchySearchString = hierarchy + "%";
+
+            final String sql = "select " + this.clientMapper.schema()
+                    + " where ( o.hierarchy like ? or transferToOffice.hierarchy like ?) and c.id = ?";
+            final ClientData clientData = this.jdbcTemplate.queryForObject(sql, this.clientMapper, new Object[] { hierarchySearchString,
+                    hierarchySearchString, clientId });
+
+            final String clientGroupsSql = "select " + this.clientGroupsMapper.parentGroupsSchema();
+
+            final Collection<GroupGeneralData> parentGroups = this.jdbcTemplate.query(clientGroupsSql, this.clientGroupsMapper,
+                    new Object[] { clientId });
+
+            return ClientData.setParentGroups(clientData, parentGroups);
+
+        } catch (final EmptyResultDataAccessException e) {
+            throw new ClientNotFoundException(clientId);
+        }
+    }
+
+    @Override
+    public Collection<ClientData> retrieveAllForLookup(final String extraCriteria) {
+
+        String sql = "select " + this.lookupMapper.schema();
+
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            sql += " and (" + extraCriteria + ")";
+        }
+
+        return this.jdbcTemplate.query(sql, this.lookupMapper, new Object[] {});
+    }
+
+    @Override
+    public Collection<ClientData> retrieveAllForLookupByOfficeId(final Long officeId) {
+
+        final String sql = "select " + this.lookupMapper.schema() + " where c.office_id = ? and c.status_enum != ?";
+
+        return this.jdbcTemplate.query(sql, this.lookupMapper, new Object[] { officeId, ClientStatus.CLOSED.getValue() });
+    }
+
+    @Override
+    public Collection<ClientData> retrieveClientMembersOfGroup(final Long groupId) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final String sql = "select " + this.membersOfGroupMapper.schema() + " where o.hierarchy like ? and pgc.group_id = ?";
+
+        return this.jdbcTemplate.query(sql, this.membersOfGroupMapper, new Object[] { hierarchySearchString, groupId });
+    }
+
+    @Override
+    public Collection<ClientData> retrieveActiveClientMembersOfGroup(final Long groupId) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final String sql = "select " + this.membersOfGroupMapper.schema()
+                + " where o.hierarchy like ? and pgc.group_id = ? and c.status_enum = ? ";
+
+        return this.jdbcTemplate.query(sql, this.membersOfGroupMapper,
+                new Object[] { hierarchySearchString, groupId, ClientStatus.ACTIVE.getValue() });
+    }
+
+    private static final class ClientMembersOfGroupMapper implements RowMapper<ClientData> {
+
+        private final String schema;
+
+        public ClientMembersOfGroupMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(200);
+
+            sqlBuilder
+                    .append("c.id as id, c.account_no as accountNo, c.external_id as externalId, c.status_enum as statusEnum,c.sub_status as subStatus, ");
+            sqlBuilder
+                    .append("cvSubStatus.code_value as subStatusValue,cvSubStatus.code_description as subStatusDesc,c.office_id as officeId, o.name as officeName, ");
+            sqlBuilder.append("c.transfer_to_office_id as transferToOfficeId, transferToOffice.name as transferToOfficeName, ");
+            sqlBuilder.append("c.firstname as firstname, c.middlename as middlename, c.lastname as lastname, ");
+            sqlBuilder.append("c.fullname as fullname, c.display_name as displayName, ");
+            sqlBuilder.append("c.mobile_no as mobileNo, ");
+            sqlBuilder.append("c.date_of_birth as dateOfBirth, ");
+            sqlBuilder.append("c.gender_cv_id as genderId, ");
+            sqlBuilder.append("cv.code_value as genderValue, ");
+            sqlBuilder.append("c.client_type_cv_id as clienttypeId, ");
+            sqlBuilder.append("cvclienttype.code_value as clienttypeValue, ");
+            sqlBuilder.append("c.client_classification_cv_id as classificationId, ");
+            sqlBuilder.append("cvclassification.code_value as classificationValue, ");
+            sqlBuilder.append("c.legal_form_enum as legalFormEnum, ");
+            sqlBuilder.append("c.activation_date as activationDate, c.image_id as imageId, ");
+            sqlBuilder.append("c.staff_id as staffId, s.display_name as staffName,");
+            sqlBuilder.append("c.default_savings_product as savingsProductId, sp.name as savingsProductName, ");
+            sqlBuilder.append("c.default_savings_account as savingsAccountId, ");
+
+            sqlBuilder.append("c.submittedon_date as submittedOnDate, ");
+            sqlBuilder.append("sbu.username as submittedByUsername, ");
+            sqlBuilder.append("sbu.firstname as submittedByFirstname, ");
+            sqlBuilder.append("sbu.lastname as submittedByLastname, ");
+
+            sqlBuilder.append("c.closedon_date as closedOnDate, ");
+            sqlBuilder.append("clu.username as closedByUsername, ");
+            sqlBuilder.append("clu.firstname as closedByFirstname, ");
+            sqlBuilder.append("clu.lastname as closedByLastname, ");
+
+            sqlBuilder.append("acu.username as activatedByUsername, ");
+            sqlBuilder.append("acu.firstname as activatedByFirstname, ");
+            sqlBuilder.append("acu.lastname as activatedByLastname, ");
+            
+            sqlBuilder.append("cnp.constitution_cv_id as constitutionId, ");
+            sqlBuilder.append("cvConstitution.code_value as constitutionValue, ");
+            sqlBuilder.append("cnp.incorp_no as incorpNo, ");
+            sqlBuilder.append("cnp.incorp_validity_till as incorpValidityTill, ");
+            sqlBuilder.append("cnp.main_business_line_cv_id as mainBusinessLineId, ");
+            sqlBuilder.append("cvMainBusinessLine.code_value as mainBusinessLineValue, ");
+            sqlBuilder.append("cnp.remarks as remarks ");
+
+            sqlBuilder.append("from m_client c ");
+            sqlBuilder.append("join m_office o on o.id = c.office_id ");
+            sqlBuilder.append("left join m_client_non_person cnp on cnp.client_id = c.id ");
+            sqlBuilder.append("join m_group_client pgc on pgc.client_id = c.id ");
+            sqlBuilder.append("left join m_staff s on s.id = c.staff_id ");
+            sqlBuilder.append("left join m_savings_product sp on sp.id = c.default_savings_product ");
+            sqlBuilder.append("left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id ");
+
+            sqlBuilder.append("left join m_appuser sbu on sbu.id = c.submittedon_userid ");
+            sqlBuilder.append("left join m_appuser acu on acu.id = c.activatedon_userid ");
+            sqlBuilder.append("left join m_appuser clu on clu.id = c.closedon_userid ");
+            sqlBuilder.append("left join m_code_value cv on cv.id = c.gender_cv_id ");
+            sqlBuilder.append("left join m_code_value cvclienttype on cvclienttype.id = c.client_type_cv_id ");
+            sqlBuilder.append("left join m_code_value cvclassification on cvclassification.id = c.client_classification_cv_id ");
+            sqlBuilder.append("left join m_code_value cvSubStatus on cvSubStatus.id = c.sub_status ");
+            sqlBuilder.append("left join m_code_value cvConstitution on cvConstitution.id = cnp.constitution_cv_id ");
+            sqlBuilder.append("left join m_code_value cvMainBusinessLine on cvMainBusinessLine.id = cnp.main_business_line_cv_id ");
+
+            this.schema = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+
+        @Override
+        public ClientData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final String accountNo = rs.getString("accountNo");
+
+            final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final EnumOptionData status = ClientEnumerations.status(statusEnum);
+
+            final Long subStatusId = JdbcSupport.getLong(rs, "subStatus");
+            final String subStatusValue = rs.getString("subStatusValue");
+            final String subStatusDesc = rs.getString("subStatusDesc");
+            final boolean isActive = false;
+            final CodeValueData subStatus = CodeValueData.instance(subStatusId, subStatusValue, subStatusDesc, isActive);
+
+            final Long officeId = JdbcSupport.getLong(rs, "officeId");
+            final String officeName = rs.getString("officeName");
+
+            final Long transferToOfficeId = JdbcSupport.getLong(rs, "transferToOfficeId");
+            final String transferToOfficeName = rs.getString("transferToOfficeName");
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String firstname = rs.getString("firstname");
+            final String middlename = rs.getString("middlename");
+            final String lastname = rs.getString("lastname");
+            final String fullname = rs.getString("fullname");
+            final String displayName = rs.getString("displayName");
+            final String externalId = rs.getString("externalId");
+            final String mobileNo = rs.getString("mobileNo");
+            final LocalDate dateOfBirth = JdbcSupport.getLocalDate(rs, "dateOfBirth");
+            final Long genderId = JdbcSupport.getLong(rs, "genderId");
+            final String genderValue = rs.getString("genderValue");
+            final CodeValueData gender = CodeValueData.instance(genderId, genderValue);
+
+            final Long clienttypeId = JdbcSupport.getLong(rs, "clienttypeId");
+            final String clienttypeValue = rs.getString("clienttypeValue");
+            final CodeValueData clienttype = CodeValueData.instance(clienttypeId, clienttypeValue);
+
+            final Long classificationId = JdbcSupport.getLong(rs, "classificationId");
+            final String classificationValue = rs.getString("classificationValue");
+            final CodeValueData classification = CodeValueData.instance(classificationId, classificationValue);
+
+            final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate");
+            final Long imageId = JdbcSupport.getLong(rs, "imageId");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+            final String staffName = rs.getString("staffName");
+
+            final Long savingsProductId = JdbcSupport.getLong(rs, "savingsProductId");
+            final String savingsProductName = rs.getString("savingsProductName");
+
+            final Long savingsAccountId = JdbcSupport.getLong(rs, "savingsAccountId");
+
+            final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+            final String closedByUsername = rs.getString("closedByUsername");
+            final String closedByFirstname = rs.getString("closedByFirstname");
+            final String closedByLastname = rs.getString("closedByLastname");
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final String activatedByUsername = rs.getString("activatedByUsername");
+            final String activatedByFirstname = rs.getString("activatedByFirstname");
+            final String activatedByLastname = rs.getString("activatedByLastname");
+            
+            final Integer legalFormEnum = JdbcSupport.getInteger(rs, "legalFormEnum");
+            EnumOptionData legalForm = null;
+            if(legalFormEnum != null)
+            		legalForm = ClientEnumerations.legalForm(legalFormEnum);
+            
+            final Long constitutionId = JdbcSupport.getLong(rs, "constitutionId");
+            final String constitutionValue = rs.getString("constitutionValue");
+            final CodeValueData constitution = CodeValueData.instance(constitutionId, constitutionValue);
+            final String incorpNo = rs.getString("incorpNo");
+            final LocalDate incorpValidityTill = JdbcSupport.getLocalDate(rs, "incorpValidityTill");
+            final Long mainBusinessLineId = JdbcSupport.getLong(rs, "mainBusinessLineId");            
+            final String mainBusinessLineValue = rs.getString("mainBusinessLineValue");
+            final CodeValueData mainBusinessLine = CodeValueData.instance(mainBusinessLineId, mainBusinessLineValue);
+            final String remarks = rs.getString("remarks");
+            
+            final ClientNonPersonData clientNonPerson = new ClientNonPersonData(constitution, incorpNo, incorpValidityTill, mainBusinessLine, remarks);
+
+            final ClientTimelineData timeline = new ClientTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname,
+                    submittedByLastname, activationDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate,
+                    closedByUsername, closedByFirstname, closedByLastname);
+
+            return ClientData.instance(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id,
+                    firstname, middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate,
+                    imageId, staffId, staffName, timeline, savingsProductId, savingsProductName, savingsAccountId, clienttype,
+                    classification, legalForm, clientNonPerson);
+
+        }
+    }
+
+    @Override
+    public Collection<ClientData> retrieveActiveClientMembersOfCenter(final Long centerId) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final String sql = "select "
+                + this.membersOfGroupMapper.schema()
+                + " left join m_group g on pgc.group_id=g.id where o.hierarchy like ? and g.parent_id = ? and c.status_enum = ? group by c.id";
+
+        return this.jdbcTemplate.query(sql, this.membersOfGroupMapper,
+                new Object[] { hierarchySearchString, centerId, ClientStatus.ACTIVE.getValue() });
+    }
+
+    private static final class ClientMapper implements RowMapper<ClientData> {
+
+        private final String schema;
+
+        public ClientMapper() {
+            final StringBuilder builder = new StringBuilder(400);
+
+            builder.append("c.id as id, c.account_no as accountNo, c.external_id as externalId, c.status_enum as statusEnum,c.sub_status as subStatus, ");
+            builder.append("cvSubStatus.code_value as subStatusValue,cvSubStatus.code_description as subStatusDesc,c.office_id as officeId, o.name as officeName, ");
+            builder.append("c.transfer_to_office_id as transferToOfficeId, transferToOffice.name as transferToOfficeName, ");
+            builder.append("c.firstname as firstname, c.middlename as middlename, c.lastname as lastname, ");
+            builder.append("c.fullname as fullname, c.display_name as displayName, ");
+            builder.append("c.mobile_no as mobileNo, ");
+            builder.append("c.date_of_birth as dateOfBirth, ");
+            builder.append("c.gender_cv_id as genderId, ");
+            builder.append("cv.code_value as genderValue, ");
+            builder.append("c.client_type_cv_id as clienttypeId, ");
+            builder.append("cvclienttype.code_value as clienttypeValue, ");
+            builder.append("c.client_classification_cv_id as classificationId, ");
+            builder.append("cvclassification.code_value as classificationValue, ");
+            builder.append("c.legal_form_enum as legalFormEnum, ");
+
+            builder.append("c.submittedon_date as submittedOnDate, ");
+            builder.append("sbu.username as submittedByUsername, ");
+            builder.append("sbu.firstname as submittedByFirstname, ");
+            builder.append("sbu.lastname as submittedByLastname, ");
+
+            builder.append("c.closedon_date as closedOnDate, ");
+            builder.append("clu.username as closedByUsername, ");
+            builder.append("clu.firstname as closedByFirstname, ");
+            builder.append("clu.lastname as closedByLastname, ");
+
+            // builder.append("c.submittedon as submittedOnDate, ");
+            builder.append("acu.username as activatedByUsername, ");
+            builder.append("acu.firstname as activatedByFirstname, ");
+            builder.append("acu.lastname as activatedByLastname, ");
+            
+            builder.append("cnp.constitution_cv_id as constitutionId, ");
+            builder.append("cvConstitution.code_value as constitutionValue, ");
+            builder.append("cnp.incorp_no as incorpNo, ");
+            builder.append("cnp.incorp_validity_till as incorpValidityTill, ");
+            builder.append("cnp.main_business_line_cv_id as mainBusinessLineId, ");
+            builder.append("cvMainBusinessLine.code_value as mainBusinessLineValue, ");
+            builder.append("cnp.remarks as remarks, ");
+
+            builder.append("c.activation_date as activationDate, c.image_id as imageId, ");
+            builder.append("c.staff_id as staffId, s.display_name as staffName, ");
+            builder.append("c.default_savings_product as savingsProductId, sp.name as savingsProductName, ");
+            builder.append("c.default_savings_account as savingsAccountId ");
+            builder.append("from m_client c ");
+            builder.append("join m_office o on o.id = c.office_id ");
+            builder.append("left join m_client_non_person cnp on cnp.client_id = c.id ");
+            builder.append("left join m_staff s on s.id = c.staff_id ");
+            builder.append("left join m_savings_product sp on sp.id = c.default_savings_product ");
+            builder.append("left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id ");
+            builder.append("left join m_appuser sbu on sbu.id = c.submittedon_userid ");
+            builder.append("left join m_appuser acu on acu.id = c.activatedon_userid ");
+            builder.append("left join m_appuser clu on clu.id = c.closedon_userid ");
+            builder.append("left join m_code_value cv on cv.id = c.gender_cv_id ");
+            builder.append("left join m_code_value cvclienttype on cvclienttype.id = c.client_type_cv_id ");
+            builder.append("left join m_code_value cvclassification on cvclassification.id = c.client_classification_cv_id ");
+            builder.append("left join m_code_value cvSubStatus on cvSubStatus.id = c.sub_status ");
+            builder.append("left join m_code_value cvConstitution on cvConstitution.id = cnp.constitution_cv_id ");
+            builder.append("left join m_code_value cvMainBusinessLine on cvMainBusinessLine.id = cnp.main_business_line_cv_id ");
+
+            this.schema = builder.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+
+        @Override
+        public ClientData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final String accountNo = rs.getString("accountNo");
+
+            final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final EnumOptionData status = ClientEnumerations.status(statusEnum);
+
+            final Long subStatusId = JdbcSupport.getLong(rs, "subStatus");
+            final String subStatusValue = rs.getString("subStatusValue");
+            final String subStatusDesc = rs.getString("subStatusDesc");
+            final boolean isActive = false;
+            final CodeValueData subStatus = CodeValueData.instance(subStatusId, subStatusValue, subStatusDesc, isActive);
+
+            final Long officeId = JdbcSupport.getLong(rs, "officeId");
+            final String officeName = rs.getString("officeName");
+
+            final Long transferToOfficeId = JdbcSupport.getLong(rs, "transferToOfficeId");
+            final String transferToOfficeName = rs.getString("transferToOfficeName");
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String firstname = rs.getString("firstname");
+            final String middlename = rs.getString("middlename");
+            final String lastname = rs.getString("lastname");
+            final String fullname = rs.getString("fullname");
+            final String displayName = rs.getString("displayName");
+            final String externalId = rs.getString("externalId");
+            final String mobileNo = rs.getString("mobileNo");
+            final LocalDate dateOfBirth = JdbcSupport.getLocalDate(rs, "dateOfBirth");
+            final Long genderId = JdbcSupport.getLong(rs, "genderId");
+            final String genderValue = rs.getString("genderValue");
+            final CodeValueData gender = CodeValueData.instance(genderId, genderValue);
+
+            final Long clienttypeId = JdbcSupport.getLong(rs, "clienttypeId");
+            final String clienttypeValue = rs.getString("clienttypeValue");
+            final CodeValueData clienttype = CodeValueData.instance(clienttypeId, clienttypeValue);
+
+            final Long classificationId = JdbcSupport.getLong(rs, "classificationId");
+            final String classificationValue = rs.getString("classificationValue");
+            final CodeValueData classification = CodeValueData.instance(classificationId, classificationValue);
+
+            final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate");
+            final Long imageId = JdbcSupport.getLong(rs, "imageId");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+            final String staffName = rs.getString("staffName");
+
+            final Long savingsProductId = JdbcSupport.getLong(rs, "savingsProductId");
+            final String savingsProductName = rs.getString("savingsProductName");
+            final Long savingsAccountId = JdbcSupport.getLong(rs, "savingsAccountId");
+
+            final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+            final String closedByUsername = rs.getString("closedByUsername");
+            final String closedByFirstname = rs.getString("closedByFirstname");
+            final String closedByLastname = rs.getString("closedByLastname");
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final String activatedByUsername = rs.getString("activatedByUsername");
+            final String activatedByFirstname = rs.getString("activatedByFirstname");
+            final String activatedByLastname = rs.getString("activatedByLastname");
+            
+            final Integer legalFormEnum = JdbcSupport.getInteger(rs, "legalFormEnum");
+            EnumOptionData legalForm = null;
+            if(legalFormEnum != null)
+            		legalForm = ClientEnumerations.legalForm(legalFormEnum);
+            
+            final Long constitutionId = JdbcSupport.getLong(rs, "constitutionId");
+            final String constitutionValue = rs.getString("constitutionValue");
+            final CodeValueData constitution = CodeValueData.instance(constitutionId, constitutionValue);
+            final String incorpNo = rs.getString("incorpNo");
+            final LocalDate incorpValidityTill = JdbcSupport.getLocalDate(rs, "incorpValidityTill");
+            final Long mainBusinessLineId = JdbcSupport.getLong(rs, "mainBusinessLineId");            
+            final String mainBusinessLineValue = rs.getString("mainBusinessLineValue");
+            final CodeValueData mainBusinessLine = CodeValueData.instance(mainBusinessLineId, mainBusinessLineValue);
+            final String remarks = rs.getString("remarks");
+            
+            final ClientNonPersonData clientNonPerson = new ClientNonPersonData(constitution, incorpNo, incorpValidityTill, mainBusinessLine, remarks);
+
+            final ClientTimelineData timeline = new ClientTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname,
+                    submittedByLastname, activationDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate,
+                    closedByUsername, closedByFirstname, closedByLastname);
+
+            return ClientData.instance(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id,
+                    firstname, middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate,
+                    imageId, staffId, staffName, timeline, savingsProductId, savingsProductName, savingsAccountId, clienttype,
+                    classification, legalForm, clientNonPerson);
+
+        }
+    }
+
+    private static final class ParentGroupsMapper implements RowMapper<GroupGeneralData> {
+
+        public String parentGroupsSchema() {
+            return "gp.id As groupId , gp.account_no as accountNo, gp.display_name As groupName from m_client cl JOIN m_group_client gc ON cl.id = gc.client_id "
+                    + "JOIN m_group gp ON gp.id = gc.group_id WHERE cl.id  = ?";
+        }
+
+        @Override
+        public GroupGeneralData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final String groupName = rs.getString("groupName");
+            final String accountNo = rs.getString("accountNo");
+
+            return GroupGeneralData.lookup(groupId, accountNo, groupName);
+        }
+    }
+
+    private static final class ClientLookupMapper implements RowMapper<ClientData> {
+
+        private final String schema;
+
+        public ClientLookupMapper() {
+            final StringBuilder builder = new StringBuilder(200);
+
+            builder.append("c.id as id, c.display_name as displayName, ");
+            builder.append("c.office_id as officeId, o.name as officeName ");
+            builder.append("from m_client c ");
+            builder.append("join m_office o on o.id = c.office_id ");
+
+            this.schema = builder.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+
+        @Override
+        public ClientData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String displayName = rs.getString("displayName");
+            final Long officeId = rs.getLong("officeId");
+            final String officeName = rs.getString("officeName");
+
+            return ClientData.lookup(id, displayName, officeId, officeName);
+        }
+    }
+
+    @Override
+    public ClientData retrieveClientByIdentifier(final Long identifierTypeId, final String identifierKey) {
+        try {
+            final ClientIdentifierMapper mapper = new ClientIdentifierMapper();
+
+            final String sql = "select " + mapper.clientLookupByIdentifierSchema();
+
+            return this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { identifierTypeId, identifierKey });
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    private static final class ClientIdentifierMapper implements RowMapper<ClientData> {
+
+        public String clientLookupByIdentifierSchema() {
+            return "c.id as id, c.account_no as accountNo, c.firstname as firstname, c.middlename as middlename, c.lastname as lastname, "
+                    + "c.fullname as fullname, c.display_name as displayName," + "c.office_id as officeId, o.name as officeName "
+                    + " from m_client c, m_office o, m_client_identifier ci " + "where o.id = c.office_id and c.id=ci.client_id "
+                    + "and ci.document_type_id= ? and ci.document_key like ?";
+        }
+
+        @Override
+        public ClientData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+
+            final String firstname = rs.getString("firstname");
+            final String middlename = rs.getString("middlename");
+            final String lastname = rs.getString("lastname");
+            final String fullname = rs.getString("fullname");
+            final String displayName = rs.getString("displayName");
+
+            final Long officeId = rs.getLong("officeId");
+            final String officeName = rs.getString("officeName");
+
+            return ClientData.clientIdentifier(id, accountNo, firstname, middlename, lastname, fullname, displayName, officeId, officeName);
+        }
+    }
+
+    private Long defaultToUsersOfficeIfNull(final Long officeId) {
+        Long defaultOfficeId = officeId;
+        if (defaultOfficeId == null) {
+            defaultOfficeId = this.context.authenticatedUser().getOffice().getId();
+        }
+        return defaultOfficeId;
+    }
+
+    @Override
+    public ClientData retrieveAllNarrations(final String clientNarrations) {
+        final List<CodeValueData> narrations = new ArrayList<>(this.codeValueReadPlatformService.retrieveCodeValuesByCode(clientNarrations));
+        final Collection<CodeValueData> clientTypeOptions = null;
+        final Collection<CodeValueData> clientClassificationOptions = null;
+        final Collection<CodeValueData> clientNonPersonConstitutionOptions = null;
+        final Collection<CodeValueData> clientNonPersonMainBusinessLineOptions = null;
+        final List<EnumOptionData> clientLegalFormOptions = null;
+        return ClientData.template(null, null, null, null, narrations, null, null, clientTypeOptions, clientClassificationOptions, 
+        		clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions, clientLegalFormOptions);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformService.java
new file mode 100644
index 0000000..7ec2f82
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformService.java
@@ -0,0 +1,39 @@
+/**
+ * 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.client.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.portfolio.client.data.ClientTransactionData;
+import org.springframework.transaction.annotation.Transactional;
+
+public interface ClientTransactionReadPlatformService {
+
+    @Transactional(readOnly = true)
+    public Page<ClientTransactionData> retrieveAllTransactions(Long clientId, SearchParameters parameters);
+
+    @Transactional(readOnly = true)
+    public Collection<ClientTransactionData> retrieveAllTransactions(final Long clientId, final Long chargeId);
+
+    @Transactional(readOnly = true)
+    public ClientTransactionData retrieveTransaction(Long clientId, Long transactionId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java
new file mode 100644
index 0000000..0790de1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java
@@ -0,0 +1,182 @@
+/**
+ * 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.client.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.client.data.ClientTransactionData;
+import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
+import org.apache.fineract.portfolio.client.domain.ClientTransactionType;
+import org.apache.fineract.portfolio.client.exception.ClientTransactionNotFoundException;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ClientTransactionReadPlatformServiceImpl implements ClientTransactionReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final ClientTransactionMapper clientTransactionMapper;
+    private final PaginationHelper<ClientTransactionData> paginationHelper;
+
+    @Autowired
+    public ClientTransactionReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.clientTransactionMapper = new ClientTransactionMapper();
+        this.paginationHelper = new PaginationHelper<>();
+    }
+
+    private static final class ClientTransactionMapper implements RowMapper<ClientTransactionData> {
+
+        private final String schemaSql;
+
+        public ClientTransactionMapper() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType,  ");
+            sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount, ");
+            sqlBuilder.append("tr.created_date as submittedOnDate, tr.is_reversed as reversed, ");
+            sqlBuilder.append("tr.external_id as externalId, o.name as officeName, o.id as officeId, ");
+            sqlBuilder.append("c.id as clientId, c.account_no as accountNo, ccpb.client_charge_id as clientChargeId, ");
+            sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, ");
+            sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode,  ");
+            sqlBuilder.append(
+                    "tr.currency_code as currencyCode, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode,  ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol,  ");
+            sqlBuilder.append("pt.value as paymentTypeName  ");
+            sqlBuilder.append("from m_client c  ");
+            sqlBuilder.append("join m_client_transaction tr on tr.client_id = c.id ");
+            sqlBuilder.append("join m_currency curr on curr.code = tr.currency_code ");
+            sqlBuilder.append("left join m_payment_detail pd on tr.payment_detail_id = pd.id  ");
+            sqlBuilder.append("left join m_payment_type pt  on pd.payment_type_id = pt.id ");
+            sqlBuilder.append("left join m_office o on o.id = tr.office_id ");
+            sqlBuilder.append("left join m_client_charge_paid_by ccpb on ccpb.client_transaction_id = tr.id ");
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public ClientTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("transactionId");
+            final Long officeId = rs.getLong("officeId");
+            final String officeName = rs.getString("officeName");
+            final String externalId = rs.getString("externalId");
+            final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
+            final EnumOptionData transactionType = ClientEnumerations.clientTransactionType(transactionTypeInt);
+
+            final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate");
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount");
+            final boolean reversed = rs.getBoolean("reversed");
+
+            PaymentDetailData paymentDetailData = null;
+            if (ClientTransactionType.fromInt(transactionType.getId().intValue()).equals(ClientTransactionType.PAY_CHARGE)) {
+                final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType");
+                if (paymentTypeId != null) {
+                    final String typeName = rs.getString("paymentTypeName");
+                    final PaymentTypeData paymentType = PaymentTypeData.instance(paymentTypeId, typeName);
+                    final String accountNumber = rs.getString("accountNumber");
+                    final String checkNumber = rs.getString("checkNumber");
+                    final String routingCode = rs.getString("routingCode");
+                    final String receiptNumber = rs.getString("receiptNumber");
+                    final String bankNumber = rs.getString("bankNumber");
+                    paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber,
+                            bankNumber);
+                }
+            }
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol,
+                    currencyNameCode);
+
+            return ClientTransactionData.create(id, officeId, officeName, transactionType, date, currency, paymentDetailData, amount,
+                    externalId, submittedOnDate, reversed);
+        }
+    }
+
+    @Override
+    public Page<ClientTransactionData> retrieveAllTransactions(Long clientId, SearchParameters searchParameters) {
+        Object[] parameters = new Object[1];
+        final StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ").append(this.clientTransactionMapper.schema()).append(" where c.id = ? ");
+        parameters[0] = clientId;
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        sqlBuilder.append(" order by tr.transaction_date DESC, tr.created_date DESC, tr.id DESC ");
+
+        // apply limit and offsets
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), parameters,
+                this.clientTransactionMapper);
+    }
+
+    @Override
+    public Collection<ClientTransactionData> retrieveAllTransactions(Long clientId, Long chargeId) {
+        Object[] parameters = new Object[1];
+        String sql = "select " + this.clientTransactionMapper.schema() + " where c.id = ? ";
+        if (chargeId != null) {
+            parameters = new Object[2];
+            parameters[1] = chargeId;
+            sql = sql + " and ccpb.client_charge_id = ?";
+        }
+        parameters[0] = clientId;
+        sql = sql + " order by tr.transaction_date DESC, tr.created_date DESC, tr.id DESC";
+        return this.jdbcTemplate.query(sql, this.clientTransactionMapper, parameters);
+    }
+
+    @Override
+    public ClientTransactionData retrieveTransaction(Long clientId, Long transactionId) {
+        try {
+            final String sql = "select " + this.clientTransactionMapper.schema() + " where c.id = ? and tr.id= ?";
+            return this.jdbcTemplate.queryForObject(sql, this.clientTransactionMapper, new Object[] { clientId, transactionId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new ClientTransactionNotFoundException(clientId, transactionId);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionWritePlatformService.java
new file mode 100644
index 0000000..5d467d5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionWritePlatformService.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.client.service;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.springframework.transaction.annotation.Transactional;
+
+public interface ClientTransactionWritePlatformService {
+
+    @Transactional
+    CommandProcessingResult undo(Long clientId, Long transactionId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..280073a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,103 @@
+/**
+ * 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.client.service;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.organisation.office.domain.OrganisationCurrencyRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientCharge;
+import org.apache.fineract.portfolio.client.domain.ClientChargePaidBy;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.ClientTransaction;
+import org.apache.fineract.portfolio.client.domain.ClientTransactionRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientTransactionCannotBeUndoneException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ClientTransactionWritePlatformServiceJpaRepositoryImpl implements ClientTransactionWritePlatformService {
+
+    private final ClientTransactionRepositoryWrapper clientTransactionRepository;
+
+    private final ClientRepositoryWrapper clientRepository;
+    private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+
+    @Autowired
+    public ClientTransactionWritePlatformServiceJpaRepositoryImpl(final ClientTransactionRepositoryWrapper clientTransactionRepository,
+            final ClientRepositoryWrapper clientRepositoryWrapper,
+            final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepositoryWrapper,
+            JournalEntryWritePlatformService journalEntryWritePlatformService) {
+        this.clientTransactionRepository = clientTransactionRepository;
+        this.clientRepository = clientRepositoryWrapper;
+        this.organisationCurrencyRepository = organisationCurrencyRepositoryWrapper;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult undo(Long clientId, Long transactionId) {
+
+        final Client client = this.clientRepository.getActiveClientInUserScope(clientId);
+
+        final ClientTransaction clientTransaction = this.clientTransactionRepository.findOneWithNotFoundDetection(clientId, transactionId);
+
+        // validate that transaction can be undone
+        if (clientTransaction.isReversed()) { throw new ClientTransactionCannotBeUndoneException(clientId, transactionId); }
+
+        // mark transaction as reversed
+        clientTransaction.reverse();
+
+        // revert any charges paid back to their original state
+        if (clientTransaction.isPayChargeTransaction() || clientTransaction.isWaiveChargeTransaction()) {
+            // undo charge
+            final Set<ClientChargePaidBy> chargesPaidBy = clientTransaction.getClientChargePaidByCollection();
+            for (final ClientChargePaidBy clientChargePaidBy : chargesPaidBy) {
+                final ClientCharge clientCharge = clientChargePaidBy.getClientCharge();
+                clientCharge.setCurrency(
+                        organisationCurrencyRepository.findOneWithNotFoundDetection(clientCharge.getCharge().getCurrencyCode()));
+                if (clientTransaction.isPayChargeTransaction()) {
+                    clientCharge.undoPayment(clientTransaction.getAmount());
+                } else if (clientTransaction.isWaiveChargeTransaction()) {
+                    clientCharge.undoWaiver(clientTransaction.getAmount());
+                }
+            }
+        }
+
+        // generate accounting entries
+        this.clientTransactionRepository.saveAndFlush(clientTransaction);
+        generateAccountingEntries(clientTransaction);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(transactionId) //
+                .withOfficeId(client.officeId()) //
+                .withClientId(clientId) //
+                .build();
+    }
+
+    private void generateAccountingEntries(ClientTransaction clientTransaction) {
+        Map<String, Object> accountingBridgeData = clientTransaction.toMapData();
+        journalEntryWritePlatformService.createJournalEntriesForClientTransactions(accountingBridgeData);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformService.java
new file mode 100644
index 0000000..30f7213
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformService.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.client.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface ClientWritePlatformService {
+
+    CommandProcessingResult createClient(JsonCommand command);
+
+    CommandProcessingResult updateClient(Long clientId, JsonCommand command);
+
+    CommandProcessingResult activateClient(Long clientId, JsonCommand command);
+
+    CommandProcessingResult deleteClient(Long clientId);
+
+    CommandProcessingResult unassignClientStaff(Long clientId, JsonCommand command);
+
+    CommandProcessingResult closeClient(final Long clientId, final JsonCommand command);
+
+    CommandProcessingResult assignClientStaff(Long clientId, JsonCommand command);
+
+    CommandProcessingResult updateDefaultSavingsAccount(Long clientId, JsonCommand command);
+
+    CommandProcessingResult rejectClient(Long entityId, JsonCommand command);
+
+    CommandProcessingResult withdrawClient(Long entityId, JsonCommand command);
+
+    CommandProcessingResult reActivateClient(Long entityId, JsonCommand command);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..26a1d42
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,823 @@
+/**
+ * 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.client.service;
+
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandProcessingService;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.data.ClientDataValidator;
+import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientNonPerson;
+import org.apache.fineract.portfolio.client.domain.ClientNonPersonRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.ClientStatus;
+import org.apache.fineract.portfolio.client.domain.LegalForm;
+import org.apache.fineract.portfolio.client.exception.ClientActiveForUpdateException;
+import org.apache.fineract.portfolio.client.exception.ClientHasNoStaffException;
+import org.apache.fineract.portfolio.client.exception.ClientMustBePendingToBeDeletedException;
+import org.apache.fineract.portfolio.client.exception.InvalidClientSavingProductException;
+import org.apache.fineract.portfolio.client.exception.InvalidClientStateTransitionException;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepository;
+import org.apache.fineract.portfolio.group.exception.GroupMemberCountNotInPermissibleRangeException;
+import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountDataDTO;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
+import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
+import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
+import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
+import org.apache.fineract.portfolio.savings.service.SavingsApplicationProcessWritePlatformService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonElement;
+
+@Service
+public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ClientWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final ClientRepositoryWrapper clientRepository;
+    private final ClientNonPersonRepositoryWrapper clientNonPersonRepository;
+    private final OfficeRepository officeRepository;
+    private final NoteRepository noteRepository;
+    private final GroupRepository groupRepository;
+    private final ClientDataValidator fromApiJsonDeserializer;
+    private final AccountNumberGenerator accountNumberGenerator;
+    private final StaffRepositoryWrapper staffRepository;
+    private final CodeValueRepositoryWrapper codeValueRepository;
+    private final LoanRepository loanRepository;
+    private final SavingsAccountRepository savingsRepository;
+    private final SavingsProductRepository savingsProductRepository;
+    private final SavingsApplicationProcessWritePlatformService savingsApplicationProcessWritePlatformService;
+    private final CommandProcessingService commandProcessingService;
+    private final ConfigurationDomainService configurationDomainService;
+    private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
+	private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ClientWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final ClientRepositoryWrapper clientRepository, final ClientNonPersonRepositoryWrapper clientNonPersonRepository, final OfficeRepository officeRepository, final NoteRepository noteRepository,
+            final ClientDataValidator fromApiJsonDeserializer, final AccountNumberGenerator accountNumberGenerator,
+            final GroupRepository groupRepository, final StaffRepositoryWrapper staffRepository,
+            final CodeValueRepositoryWrapper codeValueRepository, final LoanRepository loanRepository,
+            final SavingsAccountRepository savingsRepository, final SavingsProductRepository savingsProductRepository,
+            final SavingsApplicationProcessWritePlatformService savingsApplicationProcessWritePlatformService,
+            final CommandProcessingService commandProcessingService, final ConfigurationDomainService configurationDomainService,
+            final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final FromJsonHelper fromApiJsonHelper) {
+        this.context = context;
+        this.clientRepository = clientRepository;
+        this.clientNonPersonRepository = clientNonPersonRepository;
+        this.officeRepository = officeRepository;
+        this.noteRepository = noteRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.accountNumberGenerator = accountNumberGenerator;
+        this.groupRepository = groupRepository;
+        this.staffRepository = staffRepository;
+        this.codeValueRepository = codeValueRepository;
+        this.loanRepository = loanRepository;
+        this.savingsRepository = savingsRepository;
+        this.savingsProductRepository = savingsProductRepository;
+        this.savingsApplicationProcessWritePlatformService = savingsApplicationProcessWritePlatformService;
+        this.commandProcessingService = commandProcessingService;
+        this.configurationDomainService = configurationDomainService;
+        this.accountNumberFormatRepository = accountNumberFormatRepository;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteClient(final Long clientId) {
+
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+
+        if (client.isNotPending()) { throw new ClientMustBePendingToBeDeletedException(clientId); }
+
+        final List<Note> relatedNotes = this.noteRepository.findByClientId(clientId);
+        this.noteRepository.deleteInBatch(relatedNotes);
+
+        final ClientNonPerson clientNonPerson = this.clientNonPersonRepository.findOneByClientId(clientId);
+        if(clientNonPerson != null)
+        	this.clientNonPersonRepository.delete(clientNonPerson);
+        
+        this.clientRepository.delete(client);
+
+        return new CommandProcessingResultBuilder() //
+                .withOfficeId(client.officeId()) //
+                .withClientId(clientId) //
+                .withEntityId(clientId) //
+                .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("external_id")) {
+
+            final String externalId = command.stringValueOfParameterNamed("externalId");
+            throw new PlatformDataIntegrityException("error.msg.client.duplicate.externalId", "Client with externalId `" + externalId
+                    + "` already exists", "externalId", externalId);
+        } else if (realCause.getMessage().contains("account_no_UNIQUE")) {
+            final String accountNo = command.stringValueOfParameterNamed("accountNo");
+            throw new PlatformDataIntegrityException("error.msg.client.duplicate.accountNo", "Client with accountNo `" + accountNo
+                    + "` already exists", "accountNo", accountNo);
+        } else if (realCause.getMessage().contains("mobile_no")) {
+            final String mobileNo = command.stringValueOfParameterNamed("mobileNo");
+            throw new PlatformDataIntegrityException("error.msg.client.duplicate.mobileNo", "Client with mobileNo `" + mobileNo
+                    + "` already exists", "mobileNo", mobileNo);
+        }
+
+        logAsErrorUnexpectedDataIntegrityException(dve);
+        throw new PlatformDataIntegrityException("error.msg.client.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createClient(final JsonCommand command) {
+
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final Long officeId = command.longValueOfParameterNamed(ClientApiConstants.officeIdParamName);
+
+            final Office clientOffice = this.officeRepository.findOne(officeId);
+            if (clientOffice == null) { throw new OfficeNotFoundException(officeId); }
+
+            final Long groupId = command.longValueOfParameterNamed(ClientApiConstants.groupIdParamName);
+
+            Group clientParentGroup = null;
+            if (groupId != null) {
+                clientParentGroup = this.groupRepository.findOne(groupId);
+                if (clientParentGroup == null) { throw new GroupNotFoundException(groupId); }
+            }
+
+            Staff staff = null;
+            final Long staffId = command.longValueOfParameterNamed(ClientApiConstants.staffIdParamName);
+            if (staffId != null) {
+                staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, clientOffice.getHierarchy());
+            }
+
+            CodeValue gender = null;
+            final Long genderId = command.longValueOfParameterNamed(ClientApiConstants.genderIdParamName);
+            if (genderId != null) {
+                gender = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(ClientApiConstants.GENDER, genderId);
+            }
+
+            CodeValue clientType = null;
+            final Long clientTypeId = command.longValueOfParameterNamed(ClientApiConstants.clientTypeIdParamName);
+            if (clientTypeId != null) {
+                clientType = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(ClientApiConstants.CLIENT_TYPE,
+                        clientTypeId);
+            }
+
+            CodeValue clientClassification = null;
+            final Long clientClassificationId = command.longValueOfParameterNamed(ClientApiConstants.clientClassificationIdParamName);
+            if (clientClassificationId != null) {
+                clientClassification = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
+                        ClientApiConstants.CLIENT_CLASSIFICATION, clientClassificationId);
+            }
+
+            SavingsProduct savingsProduct = null;
+            final Long savingsProductId = command.longValueOfParameterNamed(ClientApiConstants.savingsProductIdParamName);
+            if (savingsProductId != null) {
+                savingsProduct = this.savingsProductRepository.findOne(savingsProductId);
+                if (savingsProduct == null) { throw new SavingsProductNotFoundException(savingsProductId); }
+
+            }
+            
+            final Integer legalFormParamValue = command.integerValueOfParameterNamed(ClientApiConstants.legalFormIdParamName);
+            boolean isEntity = false;
+            Integer legalFormValue = null;
+            if(legalFormParamValue != null)
+            {
+            	LegalForm legalForm = LegalForm.fromInt(legalFormParamValue);
+            	if(legalForm != null)
+                {
+                	legalFormValue = legalForm.getValue();
+                	isEntity = legalForm.isEntity();
+                }
+            }
+            
+            final Client newClient = Client.createNew(currentUser, clientOffice, clientParentGroup, staff, savingsProduct, gender,
+                    clientType, clientClassification, legalFormValue, command);
+            boolean rollbackTransaction = false;
+            if (newClient.isActive()) {
+                validateParentGroupRulesBeforeClientActivation(newClient);
+                final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateClient(null).build();
+                rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser);
+            }
+
+            this.clientRepository.save(newClient);
+
+            if (newClient.isAccountNumberRequiresAutoGeneration()) {
+                AccountNumberFormat accountNumberFormat = this.accountNumberFormatRepository.findByAccountType(EntityAccountType.CLIENT);
+                newClient.updateAccountNo(accountNumberGenerator.generate(newClient, accountNumberFormat));
+                this.clientRepository.save(newClient);
+            }
+                        
+            final Locale locale = command.extractLocale();
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+            CommandProcessingResult result = openSavingsAccount(newClient, fmt);
+            if (result.getSavingsId() != null) {
+                this.clientRepository.save(newClient);
+            }
+            
+            if(isEntity)            
+            	extractAndCreateClientNonPerson(newClient, command);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withOfficeId(clientOffice.getId()) //
+                    .withClientId(newClient.getId()) //
+                    .withGroupId(groupId) //
+                    .withEntityId(newClient.getId()) //
+                    .withSavingsId(result.getSavingsId())//
+                    .setRollbackTransaction(rollbackTransaction)//
+                    .setRollbackTransaction(result.isRollbackTransaction())//
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+    
+    /**
+     * This method extracts ClientNonPerson details from Client command and creates a new ClientNonPerson record
+     * @param client
+     * @param command
+     */
+    public void extractAndCreateClientNonPerson(Client client, JsonCommand command)
+    {    	
+    	final JsonElement clientNonPersonElement = this.fromApiJsonHelper.parse(command.jsonFragment(ClientApiConstants.clientNonPersonDetailsParamName));
+
+		if(clientNonPersonElement != null)
+		{
+			final String incorpNumber = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.incorpNumberParamName, clientNonPersonElement);
+	        final String remarks = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.remarksParamName, clientNonPersonElement);                
+	        final LocalDate incorpValidityTill = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.incorpValidityTillParamName, clientNonPersonElement);
+	        
+	    	//JsonCommand clientNonPersonCommand = JsonCommand.fromExistingCommand(command, command.arrayOfParameterNamed(ClientApiConstants.clientNonPersonDetailsParamName).getAsJsonObject());
+	    	CodeValue clientNonPersonConstitution = null;
+	        final Long clientNonPersonConstitutionId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.constitutionIdParamName, clientNonPersonElement);
+	        if (clientNonPersonConstitutionId != null) {
+	        	clientNonPersonConstitution = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(ClientApiConstants.CLIENT_NON_PERSON_CONSTITUTION,
+	        			clientNonPersonConstitutionId);
+	        }
+	        
+	        CodeValue clientNonPersonMainBusinessLine = null;
+	        final Long clientNonPersonMainBusinessLineId = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.mainBusinessLineIdParamName, clientNonPersonElement);
+	        if (clientNonPersonMainBusinessLineId != null) {
+	        	clientNonPersonMainBusinessLine = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(ClientApiConstants.CLIENT_NON_PERSON_MAIN_BUSINESS_LINE,
+	        			clientNonPersonMainBusinessLineId);
+	        }
+	        
+	    	final ClientNonPerson newClientNonPerson = ClientNonPerson.createNew(client, clientNonPersonConstitution, clientNonPersonMainBusinessLine, incorpNumber, incorpValidityTill, remarks);
+	    	
+	    	this.clientNonPersonRepository.save(newClientNonPerson);
+		}
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateClient(final Long clientId, final JsonCommand command) {
+
+        try {
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final Client clientForUpdate = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            final String clientHierarchy = clientForUpdate.getOffice().getHierarchy();
+
+            this.context.validateAccessRights(clientHierarchy);
+
+            final Map<String, Object> changes = clientForUpdate.update(command);
+
+            if (changes.containsKey(ClientApiConstants.staffIdParamName)) {
+
+                final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.staffIdParamName);
+                Staff newStaff = null;
+                if (newValue != null) {
+                    newStaff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(newValue, clientForUpdate.getOffice()
+                            .getHierarchy());
+                }
+                clientForUpdate.updateStaff(newStaff);
+            }
+
+            if (changes.containsKey(ClientApiConstants.genderIdParamName)) {
+
+                final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.genderIdParamName);
+                CodeValue gender = null;
+                if (newValue != null) {
+                    gender = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(ClientApiConstants.GENDER, newValue);
+                }
+                clientForUpdate.updateGender(gender);
+            }
+
+            if (changes.containsKey(ClientApiConstants.savingsProductIdParamName)) {
+                if (clientForUpdate.isActive()) { throw new ClientActiveForUpdateException(clientId,
+                        ClientApiConstants.savingsProductIdParamName); }
+                SavingsProduct savingsProduct = null;
+                final Long savingsProductId = command.longValueOfParameterNamed(ClientApiConstants.savingsProductIdParamName);
+                if (savingsProductId != null) {
+                    savingsProduct = this.savingsProductRepository.findOne(savingsProductId);
+                    if (savingsProduct == null) { throw new SavingsProductNotFoundException(savingsProductId); }
+                }
+                clientForUpdate.updateSavingsProduct(savingsProduct);
+            }
+
+            if (changes.containsKey(ClientApiConstants.genderIdParamName)) {
+                final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.genderIdParamName);
+                CodeValue newCodeVal = null;
+                if (newValue != null) {
+                    newCodeVal = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(ClientApiConstants.GENDER, newValue);
+                }
+                clientForUpdate.updateGender(newCodeVal);
+            }
+
+            if (changes.containsKey(ClientApiConstants.clientTypeIdParamName)) {
+                final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.clientTypeIdParamName);
+                CodeValue newCodeVal = null;
+                if (newValue != null) {
+                    newCodeVal = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(ClientApiConstants.CLIENT_TYPE,
+                            newValue);
+                }
+                clientForUpdate.updateClientType(newCodeVal);
+            }
+
+            if (changes.containsKey(ClientApiConstants.clientClassificationIdParamName)) {
+                final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.clientClassificationIdParamName);
+                CodeValue newCodeVal = null;
+                if (newValue != null) {
+                    newCodeVal = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
+                            ClientApiConstants.CLIENT_CLASSIFICATION, newValue);
+                }
+                clientForUpdate.updateClientClassification(newCodeVal);
+            }
+
+            if (!changes.isEmpty()) {
+                this.clientRepository.saveAndFlush(clientForUpdate);
+            }
+            
+            if (changes.containsKey(ClientApiConstants.legalFormIdParamName)) {
+            	Integer legalFormValue = clientForUpdate.getLegalForm();
+            	boolean isChangedToEntity = false;
+            	if(legalFormValue != null)
+            	{
+            		LegalForm legalForm = LegalForm.fromInt(legalFormValue);
+            		if(legalForm != null)
+            			isChangedToEntity = legalForm.isEntity();
+            	}
+                
+                if(isChangedToEntity)
+                {
+                	extractAndCreateClientNonPerson(clientForUpdate, command);
+                }
+                else
+                {
+                	final ClientNonPerson clientNonPerson = this.clientNonPersonRepository.findOneByClientId(clientForUpdate.getId());
+                	if(clientNonPerson != null)
+                		this.clientNonPersonRepository.delete(clientNonPerson);
+                }
+            }
+            
+            final ClientNonPerson clientNonPersonForUpdate = this.clientNonPersonRepository.findOneByClientId(clientId);
+            if(clientNonPersonForUpdate != null)
+            {
+            	final JsonElement clientNonPersonElement = this.fromApiJsonHelper.parse(command.jsonFragment(ClientApiConstants.clientNonPersonDetailsParamName));
+            	final Map<String, Object> clientNonPersonChanges = clientNonPersonForUpdate.update(JsonCommand.fromExistingCommand(command, clientNonPersonElement));
+                
+                if (clientNonPersonChanges.containsKey(ClientApiConstants.constitutionIdParamName)) {
+
+                    final Long newValue = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.constitutionIdParamName, clientNonPersonElement);
+                    CodeValue constitution = null;
+                    if (newValue != null) {
+                        constitution = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(ClientApiConstants.CLIENT_NON_PERSON_CONSTITUTION, newValue);
+                    }
+                    clientNonPersonForUpdate.updateConstitution(constitution);
+                }
+                
+                if (clientNonPersonChanges.containsKey(ClientApiConstants.mainBusinessLineIdParamName)) {
+
+                    final Long newValue = this.fromApiJsonHelper.extractLongNamed(ClientApiConstants.mainBusinessLineIdParamName, clientNonPersonElement);
+                    CodeValue mainBusinessLine = null;
+                    if (newValue != null) {
+                        mainBusinessLine = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(ClientApiConstants.CLIENT_NON_PERSON_MAIN_BUSINESS_LINE, newValue);
+                    }
+                    clientNonPersonForUpdate.updateMainBusinessLine(mainBusinessLine);
+                }
+                
+                if (!clientNonPersonChanges.isEmpty()) {
+                    this.clientNonPersonRepository.saveAndFlush(clientNonPersonForUpdate);
+                }
+                
+                changes.putAll(clientNonPersonChanges);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withOfficeId(clientForUpdate.officeId()) //
+                    .withClientId(clientId) //
+                    .withEntityId(clientId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult activateClient(final Long clientId, final JsonCommand command) {
+        try {
+            this.fromApiJsonDeserializer.validateActivation(command);
+
+            final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            validateParentGroupRulesBeforeClientActivation(client);
+
+            final Locale locale = command.extractLocale();
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+            final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate");
+
+            final AppUser currentUser = this.context.authenticatedUser();
+            client.activate(currentUser, fmt, activationDate);
+            CommandProcessingResult result = openSavingsAccount(client, fmt);
+            this.clientRepository.saveAndFlush(client);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withOfficeId(client.officeId()) //
+                    .withClientId(clientId) //
+                    .withEntityId(clientId) //
+                    .withSavingsId(result.getSavingsId())//
+                    .setRollbackTransaction(result.isRollbackTransaction())//
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private CommandProcessingResult openSavingsAccount(final Client client, final DateTimeFormatter fmt) {
+        CommandProcessingResult commandProcessingResult = CommandProcessingResult.empty();
+        if (client.isActive() && client.SavingsProduct() != null) {
+            SavingsAccountDataDTO savingsAccountDataDTO = new SavingsAccountDataDTO(client, null, client.SavingsProduct(),
+                    client.getActivationLocalDate(), client.activatedBy(), fmt);
+            commandProcessingResult = this.savingsApplicationProcessWritePlatformService.createActiveApplication(savingsAccountDataDTO);
+            if (commandProcessingResult.getSavingsId() != null) {
+                SavingsAccount savingsAccount = this.savingsRepository.findOne(commandProcessingResult.getSavingsId());
+                client.updateSavingsAccount(savingsAccount);
+                client.updateSavingsProduct(null);
+            }
+        }
+        return commandProcessingResult;
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
+        logger.error(dve.getMessage(), dve);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult unassignClientStaff(final Long clientId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(5);
+
+        this.fromApiJsonDeserializer.validateForUnassignStaff(command.json());
+
+        final Client clientForUpdate = this.clientRepository.findOneWithNotFoundDetection(clientId);
+
+        final Staff presentStaff = clientForUpdate.getStaff();
+        Long presentStaffId = null;
+        if (presentStaff == null) { throw new ClientHasNoStaffException(clientId); }
+        presentStaffId = presentStaff.getId();
+        final String staffIdParamName = ClientApiConstants.staffIdParamName;
+        if (!command.isChangeInLongParameterNamed(staffIdParamName, presentStaffId)) {
+            clientForUpdate.unassignStaff();
+        }
+        this.clientRepository.saveAndFlush(clientForUpdate);
+
+        actualChanges.put(staffIdParamName, presentStaffId);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(clientForUpdate.officeId()) //
+                .withEntityId(clientForUpdate.getId()) //
+                .withClientId(clientId) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult assignClientStaff(final Long clientId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(5);
+
+        this.fromApiJsonDeserializer.validateForAssignStaff(command.json());
+
+        final Client clientForUpdate = this.clientRepository.findOneWithNotFoundDetection(clientId);
+
+        Staff staff = null;
+        final Long staffId = command.longValueOfParameterNamed(ClientApiConstants.staffIdParamName);
+        if (staffId != null) {
+            staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, clientForUpdate.getOffice().getHierarchy());
+            /**
+             * TODO Vishwas: We maintain history of chage of loan officer w.r.t
+             * loan in a history table, should we do the same for a client?
+             * Especially useful when the change happens due to a transfer etc
+             **/
+            clientForUpdate.assignStaff(staff);
+        }
+
+        this.clientRepository.saveAndFlush(clientForUpdate);
+
+        actualChanges.put(ClientApiConstants.staffIdParamName, staffId);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(clientForUpdate.officeId()) //
+                .withEntityId(clientForUpdate.getId()) //
+                .withClientId(clientId) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult closeClient(final Long clientId, final JsonCommand command) {
+        try {
+
+            final AppUser currentUser = this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateClose(command);
+
+            final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            final LocalDate closureDate = command.localDateValueOfParameterNamed(ClientApiConstants.closureDateParamName);
+            final Long closureReasonId = command.longValueOfParameterNamed(ClientApiConstants.closureReasonIdParamName);
+
+            final CodeValue closureReason = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
+                    ClientApiConstants.CLIENT_CLOSURE_REASON, closureReasonId);
+
+            if (ClientStatus.fromInt(client.getStatus()).isClosed()) {
+                final String errorMessage = "Client is already closed.";
+                throw new InvalidClientStateTransitionException("close", "is.already.closed", errorMessage);
+            } else if (ClientStatus.fromInt(client.getStatus()).isUnderTransfer()) {
+                final String errorMessage = "Cannot Close a Client under Transfer";
+                throw new InvalidClientStateTransitionException("close", "is.under.transfer", errorMessage);
+            }
+
+            if (client.isNotPending() && client.getActivationLocalDate().isAfter(closureDate)) {
+                final String errorMessage = "The client closureDate cannot be before the client ActivationDate.";
+                throw new InvalidClientStateTransitionException("close", "date.cannot.before.client.actvation.date", errorMessage,
+                        closureDate, client.getActivationLocalDate());
+            }
+
+            final List<Loan> clientLoans = this.loanRepository.findLoanByClientId(clientId);
+
+            for (final Loan loan : clientLoans) {
+                final LoanStatusMapper loanStatus = new LoanStatusMapper(loan.status().getValue());
+                if (loanStatus.isOpen() || loanStatus.isPendingApproval() || loanStatus.isAwaitingDisbursal()) {
+                    final String errorMessage = "Client cannot be closed because of non-closed loans.";
+                    throw new InvalidClientStateTransitionException("close", "loan.non-closed", errorMessage);
+                } else if (loanStatus.isClosed() && loan.getClosedOnDate().after(closureDate.toDate())) {
+                    final String errorMessage = "The client closureDate cannot be before the loan closedOnDate.";
+                    throw new InvalidClientStateTransitionException("close", "date.cannot.before.loan.closed.date", errorMessage,
+                            closureDate, loan.getClosedOnDate());
+                } else if (loanStatus.isOverpaid()) {
+                    final String errorMessage = "Client cannot be closed because of overpaid loans.";
+                    throw new InvalidClientStateTransitionException("close", "loan.overpaid", errorMessage);
+                }
+            }
+            final List<SavingsAccount> clientSavingAccounts = this.savingsRepository.findSavingAccountByClientId(clientId);
+
+            for (final SavingsAccount saving : clientSavingAccounts) {
+                if (saving.isActive() || saving.isSubmittedAndPendingApproval() || saving.isApproved()) {
+                    final String errorMessage = "Client cannot be closed because of non-closed savings account.";
+                    throw new InvalidClientStateTransitionException("close", "non-closed.savings.account", errorMessage);
+                }
+            }
+
+            client.close(currentUser, closureReason, closureDate.toDate());
+            this.clientRepository.saveAndFlush(client);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withClientId(clientId) //
+                    .withEntityId(clientId) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    public CommandProcessingResult updateDefaultSavingsAccount(final Long clientId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(5);
+
+        this.fromApiJsonDeserializer.validateForSavingsAccount(command.json());
+
+        final Client clientForUpdate = this.clientRepository.findOneWithNotFoundDetection(clientId);
+
+        SavingsAccount savingsAccount = null;
+        final Long savingsId = command.longValueOfParameterNamed(ClientApiConstants.savingsAccountIdParamName);
+        if (savingsId != null) {
+            savingsAccount = this.savingsRepository.findOne(savingsId);
+            if (savingsAccount == null) { throw new SavingsAccountNotFoundException(savingsId); }
+            if (!savingsAccount.getClient().identifiedBy(clientId)) {
+                String defaultUserMessage = "saving account must belongs to client";
+                throw new InvalidClientSavingProductException("saving.account", "must.belongs.to.client", defaultUserMessage, savingsId,
+                        clientForUpdate.getId());
+            }
+            clientForUpdate.updateSavingsAccount(savingsAccount);
+        }
+
+        this.clientRepository.saveAndFlush(clientForUpdate);
+
+        actualChanges.put(ClientApiConstants.savingsAccountIdParamName, savingsId);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(clientForUpdate.officeId()) //
+                .withEntityId(clientForUpdate.getId()) //
+                .withClientId(clientId) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    /*
+     * To become a part of a group, group may have set of criteria to be m et
+     * before client can become member of it.
+     */
+    private void validateParentGroupRulesBeforeClientActivation(Client client) {
+        Integer minNumberOfClients = configurationDomainService.retrieveMinAllowedClientsInGroup();
+        Integer maxNumberOfClients = configurationDomainService.retrieveMaxAllowedClientsInGroup();
+        if (client.getGroups() != null && maxNumberOfClients != null) {
+            for (Group group : client.getGroups()) {
+                /**
+                 * Since this Client has not yet been associated with the group,
+                 * reduce maxNumberOfClients by 1
+                 **/
+                final boolean validationsuccess = group.isGroupsClientCountWithinMaxRange(maxNumberOfClients - 1);
+                if (!validationsuccess) { throw new GroupMemberCountNotInPermissibleRangeException(group.getId(), minNumberOfClients,
+                        maxNumberOfClients); }
+            }
+        }
+    }
+
+    @Override
+    public CommandProcessingResult rejectClient(final Long entityId, final JsonCommand command) {
+        final AppUser currentUser = this.context.authenticatedUser();
+        this.fromApiJsonDeserializer.validateRejection(command);
+
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId);
+        final LocalDate rejectionDate = command.localDateValueOfParameterNamed(ClientApiConstants.rejectionDateParamName);
+        final Long rejectionReasonId = command.longValueOfParameterNamed(ClientApiConstants.rejectionReasonIdParamName);
+
+        final CodeValue rejectionReason = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
+                ClientApiConstants.CLIENT_REJECT_REASON, rejectionReasonId);
+
+        if (client.isNotPending()) {
+            final String errorMessage = "Only clients pending activation may be withdrawn.";
+            throw new InvalidClientStateTransitionException("rejection", "on.account.not.in.pending.activation.status", errorMessage,
+                    rejectionDate, client.getSubmittedOnDate());
+        } else if (client.getSubmittedOnDate().isAfter(rejectionDate)) {
+            final String errorMessage = "The client rejection date cannot be before the client submitted date.";
+            throw new InvalidClientStateTransitionException("rejection", "date.cannot.before.client.submitted.date", errorMessage,
+                    rejectionDate, client.getSubmittedOnDate());
+        }
+        client.reject(currentUser, rejectionReason, rejectionDate.toDate());
+        this.clientRepository.saveAndFlush(client);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withClientId(entityId) //
+                .withEntityId(entityId) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult withdrawClient(Long entityId, JsonCommand command) {
+        final AppUser currentUser = this.context.authenticatedUser();
+        this.fromApiJsonDeserializer.validateWithdrawn(command);
+
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId);
+        final LocalDate withdrawalDate = command.localDateValueOfParameterNamed(ClientApiConstants.withdrawalDateParamName);
+        final Long withdrawalReasonId = command.longValueOfParameterNamed(ClientApiConstants.withdrawalReasonIdParamName);
+
+        final CodeValue withdrawalReason = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
+                ClientApiConstants.CLIENT_WITHDRAW_REASON, withdrawalReasonId);
+
+        if (client.isNotPending()) {
+            final String errorMessage = "Only clients pending activation may be withdrawn.";
+            throw new InvalidClientStateTransitionException("withdrawal", "on.account.not.in.pending.activation.status", errorMessage,
+                    withdrawalDate, client.getSubmittedOnDate());
+        } else if (client.getSubmittedOnDate().isAfter(withdrawalDate)) {
+            final String errorMessage = "The client withdrawal date cannot be before the client submitted date.";
+            throw new InvalidClientStateTransitionException("withdrawal", "date.cannot.before.client.submitted.date", errorMessage,
+                    withdrawalDate, client.getSubmittedOnDate());
+        }
+        client.withdraw(currentUser, withdrawalReason, withdrawalDate.toDate());
+        this.clientRepository.saveAndFlush(client);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withClientId(entityId) //
+                .withEntityId(entityId) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult reActivateClient(Long entityId, JsonCommand command) {
+        final AppUser currentUser = this.context.authenticatedUser();
+        this.fromApiJsonDeserializer.validateReactivate(command);
+
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId);
+        final LocalDate reactivateDate = command.localDateValueOfParameterNamed(ClientApiConstants.reactivationDateParamName);
+
+        if (!client.isClosed()) {
+            final String errorMessage = "only closed clients may be reactivated.";
+            throw new InvalidClientStateTransitionException("reactivation", "on.nonclosed.account", errorMessage);
+        } else if (client.getClosureDate().isAfter(reactivateDate)) {
+            final String errorMessage = "The client reactivation date cannot be before the client closed date.";
+            throw new InvalidClientStateTransitionException("reactivation", "date.cannot.before.client.closed.date", errorMessage,
+                    reactivateDate, client.getClosureDate());
+        }
+        client.reActivate(currentUser, reactivateDate.toDate());
+        this.clientRepository.saveAndFlush(client);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withClientId(entityId) //
+                .withEntityId(entityId) //
+                .build();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/LoanStatusMapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/LoanStatusMapper.java
new file mode 100644
index 0000000..4fbd5ba
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/LoanStatusMapper.java
@@ -0,0 +1,56 @@
+/**
+ * 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.client.service;
+
+public class LoanStatusMapper {
+
+    private final Integer statusId;
+
+    public LoanStatusMapper(final Integer statusId) {
+        this.statusId = statusId;
+    }
+
+    public boolean isPendingApproval() {
+        return Integer.valueOf(100).equals(this.statusId);
+    }
+
+    public boolean isAwaitingDisbursal() {
+        return Integer.valueOf(200).equals(this.statusId);
+    }
+
+    public boolean isOpen() {
+        return Integer.valueOf(300).equals(this.statusId);
+    }
+
+    public boolean isWithdrawnByClient() {
+        return Integer.valueOf(400).equals(this.statusId);
+    }
+
+    public boolean isRejected() {
+        return Integer.valueOf(500).equals(this.statusId);
+    }
+
+    public boolean isClosed() {
+        return Integer.valueOf(600).equals(this.statusId) || isWithdrawnByClient() || isRejected();
+    }
+    
+    public boolean isOverpaid() {
+        return Integer.valueOf(700).equals(this.statusId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralApiConstants.java
new file mode 100755
index 0000000..063206b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralApiConstants.java
@@ -0,0 +1,60 @@
+/**
+ * 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.collateral.api;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class CollateralApiConstants {
+
+    public static final String COLLATERAL_CODE_NAME = "LoanCollateral";
+
+    /***
+     * Enum of all parameters passed in while creating/updating a collateral
+     ***/
+    public static enum COLLATERAL_JSON_INPUT_PARAMS {
+        LOAN_ID("loanId"), COLLATERAL_ID("collateralId"), COLLATERAL_TYPE_ID("collateralTypeId"), VALUE("value"), DESCRIPTION("description");
+
+        private final String value;
+
+        private COLLATERAL_JSON_INPUT_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+        static {
+            for (final COLLATERAL_JSON_INPUT_PARAMS type : COLLATERAL_JSON_INPUT_PARAMS.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;
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralsApiResource.java
new file mode 100755
index 0000000..569a2ce
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralsApiResource.java
@@ -0,0 +1,172 @@
+/**
+ * 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.collateral.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+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.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+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.apache.fineract.portfolio.collateral.data.CollateralData;
+import org.apache.fineract.portfolio.collateral.service.CollateralReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/loans/{loanId}/collaterals")
+@Component
+@Scope("singleton")
+public class CollateralsApiResource {
+
+    private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "type", "value", "description",
+            "allowedCollateralTypes", "currency"));
+
+    private final String resourceNameForPermission = "COLLATERAL";
+
+    private final CollateralReadPlatformService collateralReadPlatformService;
+    private final DefaultToApiJsonSerializer<CollateralData> apiJsonSerializerService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final PlatformSecurityContext context;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+
+    @Autowired
+    public CollateralsApiResource(final PlatformSecurityContext context, final CollateralReadPlatformService collateralReadPlatformService,
+            final DefaultToApiJsonSerializer<CollateralData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService) {
+        this.context = context;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiJsonSerializerService = toApiJsonSerializer;
+        this.collateralReadPlatformService = collateralReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String newCollateralTemplate(@Context final UriInfo uriInfo) {
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        final Collection<CodeValueData> codeValues = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanCollateral");
+        final CollateralData collateralData = CollateralData.template(codeValues);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, collateralData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCollateralDetails(@Context final UriInfo uriInfo, @PathParam("loanId") final Long loanId) {
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        final List<CollateralData> CollateralDatas = this.collateralReadPlatformService.retrieveCollateralsForValidLoan(loanId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return this.apiJsonSerializerService.serialize(settings, CollateralDatas, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{collateralId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveCollateralDetails(@Context final UriInfo uriInfo, @PathParam("loanId") final Long loanId,
+            @PathParam("collateralId") final Long CollateralId) {
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        CollateralData CollateralData = this.collateralReadPlatformService.retrieveCollateral(loanId, CollateralId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            final Collection<CodeValueData> codeValues = this.codeValueReadPlatformService
+                    .retrieveCodeValuesByCode(CollateralApiConstants.COLLATERAL_CODE_NAME);
+            CollateralData = CollateralData.template(CollateralData, codeValues);
+        }
+
+        return this.apiJsonSerializerService.serialize(settings, CollateralData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createCollateral(@PathParam("loanId") final Long loanId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createCollateral(loanId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @PUT
+    @Path("{collateralId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateCollateral(@PathParam("loanId") final Long loanId, @PathParam("collateralId") final Long collateralId,
+            final String jsonRequestBody) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCollateral(loanId, collateralId).withJson(jsonRequestBody)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @DELETE
+    @Path("{collateralId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteCollateral(@PathParam("loanId") final Long loanId, @PathParam("collateralId") final Long collateralId) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCollateral(loanId, collateralId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/command/CollateralCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/command/CollateralCommand.java
new file mode 100755
index 0000000..e0ac958
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/command/CollateralCommand.java
@@ -0,0 +1,88 @@
+/**
+ * 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.collateral.command;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.portfolio.collateral.api.CollateralApiConstants.COLLATERAL_JSON_INPUT_PARAMS;
+
+/**
+ * Immutable command for creating or updating details of a Collateral.
+ */
+public class CollateralCommand {
+
+    private final Long collateralTypeId;
+    private final BigDecimal value;
+    private final String description;
+
+    public CollateralCommand(final Long collateralTypeId, final BigDecimal value, final String description) {
+        this.collateralTypeId = collateralTypeId;
+        this.value = value;
+        this.description = description;
+    }
+
+    public Long getCollateralTypeId() {
+        return this.collateralTypeId;
+    }
+
+    public BigDecimal getValue() {
+        return this.value;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public void validateForCreate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("collateral");
+
+        baseDataValidator.reset().parameter(COLLATERAL_JSON_INPUT_PARAMS.COLLATERAL_TYPE_ID.getValue()).value(this.collateralTypeId)
+                .notNull().integerGreaterThanZero();
+        baseDataValidator.reset().parameter(COLLATERAL_JSON_INPUT_PARAMS.VALUE.getValue()).value(this.value).ignoreIfNull()
+                .positiveAmount();
+        baseDataValidator.reset().parameter(COLLATERAL_JSON_INPUT_PARAMS.DESCRIPTION.getValue()).value(this.description).ignoreIfNull()
+                .notExceedingLengthOf(500);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForUpdate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("collateral");
+
+        baseDataValidator.reset().parameter(COLLATERAL_JSON_INPUT_PARAMS.COLLATERAL_TYPE_ID.getValue()).value(this.collateralTypeId)
+                .ignoreIfNull().integerGreaterThanZero();
+        baseDataValidator.reset().parameter(COLLATERAL_JSON_INPUT_PARAMS.VALUE.getValue()).value(this.value).ignoreIfNull()
+                .positiveAmount();
+        baseDataValidator.reset().parameter(COLLATERAL_JSON_INPUT_PARAMS.DESCRIPTION.getValue()).value(this.description).ignoreIfNull()
+                .notExceedingLengthOf(500);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/data/CollateralData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/data/CollateralData.java
new file mode 100644
index 0000000..53ffeff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/data/CollateralData.java
@@ -0,0 +1,73 @@
+/**
+ * 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.collateral.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+/**
+ * Immutable data object for Collateral data.
+ */
+public class CollateralData {
+
+    private final Long id;
+    private final CodeValueData type;
+    private final BigDecimal value;
+    private final String description;
+    @SuppressWarnings("unused")
+    private final Collection<CodeValueData> allowedCollateralTypes;
+    private final CurrencyData currency;
+
+    public static CollateralData instance(final Long id, final CodeValueData type, final BigDecimal value, final String description,
+            final CurrencyData currencyData) {
+        return new CollateralData(id, type, value, description, currencyData);
+    }
+
+    public static CollateralData template(final Collection<CodeValueData> codeValues) {
+        return new CollateralData(null, null, null, null, null, codeValues);
+    }
+
+    private CollateralData(final Long id, final CodeValueData type, final BigDecimal value, final String description,
+            final CurrencyData currencyData) {
+        this.id = id;
+        this.type = type;
+        this.value = value;
+        this.description = description;
+        this.currency = currencyData;
+        this.allowedCollateralTypes = null;
+    }
+
+    private CollateralData(final Long id, final CodeValueData type, final BigDecimal value, final String description,
+            final CurrencyData currencyData, final Collection<CodeValueData> allowedCollateralTypes) {
+        this.id = id;
+        this.type = type;
+        this.value = value;
+        this.description = description;
+        this.currency = currencyData;
+        this.allowedCollateralTypes = allowedCollateralTypes;
+    }
+
+    public CollateralData template(final CollateralData collateralData, final Collection<CodeValueData> codeValues) {
+        return new CollateralData(collateralData.id, collateralData.type, collateralData.value, collateralData.description,
+                collateralData.currency, codeValues);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/domain/LoanCollateral.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/domain/LoanCollateral.java
new file mode 100644
index 0000000..1f787a5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/domain/LoanCollateral.java
@@ -0,0 +1,150 @@
+/**
+ * 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.collateral.domain;
+
+import java.math.BigDecimal;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.collateral.api.CollateralApiConstants.COLLATERAL_JSON_INPUT_PARAMS;
+import org.apache.fineract.portfolio.collateral.data.CollateralData;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_collateral")
+public class LoanCollateral extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+
+    @ManyToOne
+    @JoinColumn(name = "type_cv_id", nullable = false)
+    private CodeValue type;
+
+    @Column(name = "value", scale = 6, precision = 19)
+    private BigDecimal value;
+
+    @Column(name = "description", length = 500)
+    private String description;
+
+    public static LoanCollateral from(final CodeValue collateralType, final BigDecimal value, final String description) {
+        return new LoanCollateral(null, collateralType, value, description);
+    }
+
+    protected LoanCollateral() {
+        //
+    }
+
+    private LoanCollateral(final Loan loan, final CodeValue collateralType, final BigDecimal value, final String description) {
+        this.loan = loan;
+        this.type = collateralType;
+        this.value = value;
+        this.description = StringUtils.defaultIfEmpty(description, null);
+    }
+
+    public void assembleFrom(final CodeValue collateralType, final BigDecimal value, final String description) {
+        this.type = collateralType;
+        this.description = description;
+        this.value = value;
+    }
+
+    public void associateWith(final Loan loan) {
+        this.loan = loan;
+    }
+
+    public static LoanCollateral fromJson(final Loan loan, final CodeValue collateralType, final JsonCommand command) {
+        final String description = command.stringValueOfParameterNamed(COLLATERAL_JSON_INPUT_PARAMS.DESCRIPTION.getValue());
+        final BigDecimal value = command.bigDecimalValueOfParameterNamed(COLLATERAL_JSON_INPUT_PARAMS.VALUE.getValue());
+        return new LoanCollateral(loan, collateralType, value, description);
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String collateralTypeIdParamName = COLLATERAL_JSON_INPUT_PARAMS.COLLATERAL_TYPE_ID.getValue();
+        if (command.isChangeInLongParameterNamed(collateralTypeIdParamName, this.type.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(collateralTypeIdParamName);
+            actualChanges.put(collateralTypeIdParamName, newValue);
+        }
+
+        final String descriptionParamName = COLLATERAL_JSON_INPUT_PARAMS.DESCRIPTION.getValue();
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String valueParamName = COLLATERAL_JSON_INPUT_PARAMS.VALUE.getValue();
+        if (command.isChangeInBigDecimalParameterNamed(valueParamName, this.value)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(valueParamName);
+            actualChanges.put(valueParamName, newValue);
+            this.value = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    public CollateralData toData() {
+        final CodeValueData typeData = this.type.toData();
+        return CollateralData.instance(getId(), typeData, this.value, this.description, null);
+    }
+
+    public void setCollateralType(final CodeValue type) {
+        this.type = type;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final LoanCollateral rhs = (LoanCollateral) obj;
+        return new EqualsBuilder().appendSuper(super.equals(obj)) //
+                .append(getId(), rhs.getId()) //
+                .append(this.type.getId(), rhs.type.getId()) //
+                .append(this.description, rhs.description) //
+                .append(this.value, this.value)//
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(3, 5) //
+                .append(getId()) //
+                .append(this.type.getId()) //
+                .append(this.description) //
+                .append(this.value)//
+                .toHashCode();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/domain/LoanCollateralRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/domain/LoanCollateralRepository.java
new file mode 100644
index 0000000..d73c6c1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/domain/LoanCollateralRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.collateral.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanCollateralRepository extends JpaRepository<LoanCollateral, Long>, JpaSpecificationExecutor<LoanCollateral> {
+
+    LoanCollateral findByLoanIdAndId(Long loanId, Long id);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralCannotBeCreatedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralCannotBeCreatedException.java
new file mode 100755
index 0000000..eedde63
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralCannotBeCreatedException.java
@@ -0,0 +1,43 @@
+/**
+ * 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.collateral.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class CollateralCannotBeCreatedException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons of why Loan Charge cannot be waived **/
+    public static enum LOAN_COLLATERAL_CANNOT_BE_CREATED_REASON {
+        LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This collateral cannot be created as the loan it is associated with is not in submitted and pending approval stage"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.collateral.associated.loan.not.in.submitted.and.pending.approval.stage"; }
+            return name().toString();
+        }
+    }
+
+    public CollateralCannotBeCreatedException(final LOAN_COLLATERAL_CANNOT_BE_CREATED_REASON reason, final Long loanId) {
+        super(reason.errorCode(), reason.errorMessage(), loanId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralCannotBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralCannotBeDeletedException.java
new file mode 100755
index 0000000..a920746
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralCannotBeDeletedException.java
@@ -0,0 +1,44 @@
+/**
+ * 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.collateral.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class CollateralCannotBeDeletedException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons of why Loan Charge cannot be waived **/
+    public static enum LOAN_COLLATERAL_CANNOT_BE_DELETED_REASON {
+        LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This collateral cannot be updated as the loan it is associated with is not in submitted and pending approval stage"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.collateral.associated.loan.not.in.submitted.and.pending.approval.stage"; }
+            return name().toString();
+        }
+    }
+
+    public CollateralCannotBeDeletedException(final LOAN_COLLATERAL_CANNOT_BE_DELETED_REASON reason, final Long loanId,
+            final Long loanCollateralId) {
+        super(reason.errorCode(), reason.errorMessage(), loanId, loanCollateralId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralCannotBeUpdatedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralCannotBeUpdatedException.java
new file mode 100755
index 0000000..5f48f70
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralCannotBeUpdatedException.java
@@ -0,0 +1,43 @@
+/**
+ * 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.collateral.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class CollateralCannotBeUpdatedException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons of why Loan Charge cannot be waived **/
+    public static enum LOAN_COLLATERAL_CANNOT_BE_UPDATED_REASON {
+        LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This collateral cannot be updated as the loan it is associated with is not in submitted and pending approval stage"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.collateral.associated.loan.not.in.submitted.and.pending.approval.stage"; }
+            return name().toString();
+        }
+    }
+
+    public CollateralCannotBeUpdatedException(final LOAN_COLLATERAL_CANNOT_BE_UPDATED_REASON reason, final Long loanCollateralId) {
+        super(reason.errorCode(), reason.errorMessage(), loanCollateralId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralNotFoundException.java
new file mode 100755
index 0000000..bf35bae
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/exception/CollateralNotFoundException.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.collateral.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when guarantor resources are not found.
+ */
+public class CollateralNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public CollateralNotFoundException(final Long loanId, final Long collateralId) {
+        super("error.msg.loan.collateral.", "Collateral with Id " + collateralId + " does not exist for loan with Id " + loanId, loanId,
+                collateralId);
+    }
+
+    public CollateralNotFoundException(final Long id) {
+        super("error.msg.loan.collateral.id.invalid", "Loan collateral with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/handler/CreateCollateralCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/handler/CreateCollateralCommandHandler.java
new file mode 100755
index 0000000..80360ee
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/handler/CreateCollateralCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.collateral.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.collateral.service.CollateralWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "COLLATERAL", action = "CREATE")
+public class CreateCollateralCommandHandler implements NewCommandSourceHandler {
+
+    private final CollateralWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateCollateralCommandHandler(final CollateralWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.addCollateral(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/handler/DeleteCollateralCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/handler/DeleteCollateralCommandHandler.java
new file mode 100755
index 0000000..eef6100
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/handler/DeleteCollateralCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.collateral.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.collateral.service.CollateralWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "COLLATERAL", action = "DELETE")
+public class DeleteCollateralCommandHandler implements NewCommandSourceHandler {
+
+    private final CollateralWritePlatformService collateralWritePlatformService;
+
+    @Autowired
+    public DeleteCollateralCommandHandler(final CollateralWritePlatformService guarantorWritePlatformService) {
+        this.collateralWritePlatformService = guarantorWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.collateralWritePlatformService.deleteCollateral(command.getLoanId(), command.entityId(), command.commandId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/handler/UpdateCollateralCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/handler/UpdateCollateralCommandHandler.java
new file mode 100755
index 0000000..83d582d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/handler/UpdateCollateralCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.collateral.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.collateral.service.CollateralWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "COLLATERAL", action = "UPDATE")
+public class UpdateCollateralCommandHandler implements NewCommandSourceHandler {
+
+    private final CollateralWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateCollateralCommandHandler(final CollateralWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.updateCollateral(command.getLoanId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/serialization/CollateralCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/serialization/CollateralCommandFromApiJsonDeserializer.java
new file mode 100755
index 0000000..b15e3f5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/serialization/CollateralCommandFromApiJsonDeserializer.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.portfolio.collateral.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.collateral.api.CollateralApiConstants.COLLATERAL_JSON_INPUT_PARAMS;
+import org.apache.fineract.portfolio.collateral.command.CollateralCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link CollateralCommand}'s.
+ */
+@Component
+public final class CollateralCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<CollateralCommand> {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public CollateralCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public CollateralCommand commandFromApiJson(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        final Set<String> supportedParameters = COLLATERAL_JSON_INPUT_PARAMS.getAllValues();
+        supportedParameters.add("locale");
+        supportedParameters.add("dateFormat");
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+
+        final Long collateralTypeId = this.fromApiJsonHelper.extractLongNamed(COLLATERAL_JSON_INPUT_PARAMS.COLLATERAL_TYPE_ID.getValue(),
+                element);
+        final String description = this.fromApiJsonHelper.extractStringNamed(COLLATERAL_JSON_INPUT_PARAMS.DESCRIPTION.getValue(), element);
+        final BigDecimal value = this.fromApiJsonHelper.extractBigDecimalNamed(COLLATERAL_JSON_INPUT_PARAMS.VALUE.getValue(), element,
+                locale);
+
+        return new CollateralCommand(collateralTypeId, value, description);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralAssembler.java
new file mode 100644
index 0000000..4d8a6b5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralAssembler.java
@@ -0,0 +1,93 @@
+/**
+ * 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.collateral.service;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.collateral.domain.LoanCollateral;
+import org.apache.fineract.portfolio.collateral.domain.LoanCollateralRepository;
+import org.apache.fineract.portfolio.collateral.exception.CollateralNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class CollateralAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final CodeValueRepositoryWrapper codeValueRepository;
+    private final LoanCollateralRepository loanCollateralRepository;
+
+    @Autowired
+    public CollateralAssembler(final FromJsonHelper fromApiJsonHelper, final CodeValueRepositoryWrapper codeValueRepository,
+            final LoanCollateralRepository loanCollateralRepository) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.codeValueRepository = codeValueRepository;
+        this.loanCollateralRepository = loanCollateralRepository;
+    }
+
+    public Set<LoanCollateral> fromParsedJson(final JsonElement element) {
+
+        final Set<LoanCollateral> collateralItems = new HashSet<>();
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+
+            if (topLevelJsonElement.has("collateral") && topLevelJsonElement.get("collateral").isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get("collateral").getAsJsonArray();
+                final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+                for (int i = 0; i < array.size(); i++) {
+
+                    final JsonObject collateralItemElement = array.get(i).getAsJsonObject();
+
+                    final Long id = this.fromApiJsonHelper.extractLongNamed("id", collateralItemElement);
+                    final Long collateralTypeId = this.fromApiJsonHelper.extractLongNamed("type", collateralItemElement);
+                    final CodeValue collateralType = this.codeValueRepository.findOneWithNotFoundDetection(collateralTypeId);
+                    final String description = this.fromApiJsonHelper.extractStringNamed("description", collateralItemElement);
+                    final BigDecimal value = this.fromApiJsonHelper.extractBigDecimalNamed("value", collateralItemElement, locale);
+
+                    if (id == null) {
+                        collateralItems.add(LoanCollateral.from(collateralType, value, description));
+                    } else {
+                        final LoanCollateral loanCollateralItem = this.loanCollateralRepository.findOne(id);
+                        if (loanCollateralItem == null) { throw new CollateralNotFoundException(id); }
+
+                        loanCollateralItem.assembleFrom(collateralType, value, description);
+
+                        collateralItems.add(loanCollateralItem);
+                    }
+                }
+            } else {
+                // no collaterals passed, use existing ones against loan
+            }
+
+        }
+
+        return collateralItems;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralReadPlatformService.java
new file mode 100644
index 0000000..f5b408d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralReadPlatformService.java
@@ -0,0 +1,39 @@
+/**
+ * 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.collateral.service;
+
+import java.util.List;
+
+import org.apache.fineract.portfolio.collateral.data.CollateralData;
+
+public interface CollateralReadPlatformService {
+
+    /**
+     * Validates the passed in loanId before retrieving Collaterals for the same
+     * 
+     * @param loanId
+     * @return
+     */
+    List<CollateralData> retrieveCollateralsForValidLoan(Long loanId);
+
+    List<CollateralData> retrieveCollaterals(Long loanId);
+
+    CollateralData retrieveCollateral(Long loanId, Long collateralId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralReadPlatformServiceImpl.java
new file mode 100644
index 0000000..bf9d10e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralReadPlatformServiceImpl.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.portfolio.collateral.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+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.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.collateral.data.CollateralData;
+import org.apache.fineract.portfolio.collateral.exception.CollateralNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+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 CollateralReadPlatformServiceImpl implements CollateralReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final LoanRepository loanRepository;
+
+    @Autowired
+    public CollateralReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final LoanRepository loanRepository) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.loanRepository = loanRepository;
+    }
+
+    private static final class CollateralMapper implements RowMapper<CollateralData> {
+
+        private final StringBuilder sqlBuilder = new StringBuilder(
+                "lc.id as id, lc.description as description, lc.value as value, cv.id as typeId, cv.code_value as typeName, oc.code as currencyCode, ")
+                .append(" oc.name as currencyName,oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, oc.internationalized_name_code as currencyNameCode")
+                .append(" FROM m_loan_collateral lc") //
+                .append(" JOIN m_code_value cv on lc.type_cv_id = cv.id")//
+                .append(" JOIN m_loan loan on lc.loan_id = loan.id")//
+                .append(" JOIN m_organisation_currency oc on loan.currency_code = oc.code");
+
+        public String schema() {
+            return this.sqlBuilder.toString();
+        }
+
+        @Override
+        public CollateralData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String description = rs.getString("description");
+            final Long typeId = rs.getLong("typeId");
+            final BigDecimal value = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "value");
+            final String typeName = rs.getString("typeName");
+
+            final CodeValueData type = CodeValueData.instance(typeId, typeName);
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+
+            final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return CollateralData.instance(id, type, value, description, currencyData);
+        }
+    }
+
+    @Override
+    public List<CollateralData> retrieveCollaterals(final Long loanId) {
+        this.context.authenticatedUser();
+
+        final CollateralMapper rm = new CollateralMapper();
+
+        final String sql = "select " + rm.schema() + " where lc.loan_id=? order by id ASC";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
+    }
+
+    @Override
+    public CollateralData retrieveCollateral(final Long loanId, final Long collateralId) {
+        try {
+            final CollateralMapper rm = new CollateralMapper();
+            String sql = "select " + rm.schema();
+            sql += " where lc.loan_id=? and lc.id = ?";
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanId, collateralId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new CollateralNotFoundException(loanId, collateralId);
+        }
+
+    }
+
+    @Override
+    public List<CollateralData> retrieveCollateralsForValidLoan(final Long loanId) {
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+        return retrieveCollaterals(loanId);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformService.java
new file mode 100755
index 0000000..7d3f6a3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.collateral.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface CollateralWritePlatformService {
+
+    CommandProcessingResult addCollateral(Long loanId, JsonCommand command);
+
+    CommandProcessingResult updateCollateral(Long loanId, Long collateralId, JsonCommand command);
+
+    CommandProcessingResult deleteCollateral(Long loanId, Long collateralId, Long commandId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..f799f21
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,192 @@
+/**
+ * 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.collateral.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.collateral.api.CollateralApiConstants;
+import org.apache.fineract.portfolio.collateral.api.CollateralApiConstants.COLLATERAL_JSON_INPUT_PARAMS;
+import org.apache.fineract.portfolio.collateral.command.CollateralCommand;
+import org.apache.fineract.portfolio.collateral.domain.LoanCollateral;
+import org.apache.fineract.portfolio.collateral.domain.LoanCollateralRepository;
+import org.apache.fineract.portfolio.collateral.exception.CollateralCannotBeCreatedException;
+import org.apache.fineract.portfolio.collateral.exception.CollateralCannotBeDeletedException;
+import org.apache.fineract.portfolio.collateral.exception.CollateralCannotBeUpdatedException;
+import org.apache.fineract.portfolio.collateral.exception.CollateralNotFoundException;
+import org.apache.fineract.portfolio.collateral.exception.CollateralCannotBeCreatedException.LOAN_COLLATERAL_CANNOT_BE_CREATED_REASON;
+import org.apache.fineract.portfolio.collateral.exception.CollateralCannotBeDeletedException.LOAN_COLLATERAL_CANNOT_BE_DELETED_REASON;
+import org.apache.fineract.portfolio.collateral.exception.CollateralCannotBeUpdatedException.LOAN_COLLATERAL_CANNOT_BE_UPDATED_REASON;
+import org.apache.fineract.portfolio.collateral.serialization.CollateralCommandFromApiJsonDeserializer;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+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 CollateralWritePlatformServiceJpaRepositoryImpl implements CollateralWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(CollateralWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final LoanRepository loanRepository;
+    private final LoanCollateralRepository collateralRepository;
+    private final CodeValueRepositoryWrapper codeValueRepository;
+    private final CollateralCommandFromApiJsonDeserializer collateralCommandFromApiJsonDeserializer;
+
+    @Autowired
+    public CollateralWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final LoanRepository loanRepository,
+            final LoanCollateralRepository collateralRepository, final CodeValueRepositoryWrapper codeValueRepository,
+            final CollateralCommandFromApiJsonDeserializer collateralCommandFromApiJsonDeserializer) {
+        this.context = context;
+        this.loanRepository = loanRepository;
+        this.collateralRepository = collateralRepository;
+        this.codeValueRepository = codeValueRepository;
+        this.collateralCommandFromApiJsonDeserializer = collateralCommandFromApiJsonDeserializer;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult addCollateral(final Long loanId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+        final CollateralCommand collateralCommand = this.collateralCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
+        collateralCommand.validateForCreate();
+
+        try {
+            final Loan loan = this.loanRepository.findOne(loanId);
+            if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+            final CodeValue collateralType = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
+                    CollateralApiConstants.COLLATERAL_CODE_NAME, collateralCommand.getCollateralTypeId());
+            final LoanCollateral collateral = LoanCollateral.fromJson(loan, collateralType, command);
+
+            /**
+             * Collaterals may be added only when the loan associated with them
+             * are yet to be approved
+             **/
+            if (!loan.status().isSubmittedAndPendingApproval()) { throw new CollateralCannotBeCreatedException(
+                    LOAN_COLLATERAL_CANNOT_BE_CREATED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loan.getId()); }
+
+            this.collateralRepository.save(collateral);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withLoanId(loan.getId())//
+                    .withEntityId(collateral.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleCollateralDataIntegrityViolation(dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateCollateral(final Long loanId, final Long collateralId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+        final CollateralCommand collateralCommand = this.collateralCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
+        collateralCommand.validateForUpdate();
+
+        final Long collateralTypeId = collateralCommand.getCollateralTypeId();
+        try {
+            final Loan loan = this.loanRepository.findOne(loanId);
+            if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+            CodeValue collateralType = null;
+
+            final LoanCollateral collateralForUpdate = this.collateralRepository.findOne(collateralId);
+            if (collateralForUpdate == null) { throw new CollateralNotFoundException(loanId, collateralId); }
+
+            final Map<String, Object> changes = collateralForUpdate.update(command);
+
+            if (changes.containsKey(COLLATERAL_JSON_INPUT_PARAMS.COLLATERAL_TYPE_ID.getValue())) {
+
+                collateralType = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
+                        CollateralApiConstants.COLLATERAL_CODE_NAME, collateralTypeId);
+                collateralForUpdate.setCollateralType(collateralType);
+            }
+
+            /**
+             * Collaterals may be updated only when the loan associated with
+             * them are yet to be approved
+             **/
+            if (!loan.status().isSubmittedAndPendingApproval()) { throw new CollateralCannotBeUpdatedException(
+                    LOAN_COLLATERAL_CANNOT_BE_UPDATED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loan.getId()); }
+
+            if (!changes.isEmpty()) {
+                this.collateralRepository.saveAndFlush(collateralForUpdate);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withLoanId(command.getLoanId())//
+                    .withEntityId(collateralId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleCollateralDataIntegrityViolation(dve);
+            return new CommandProcessingResult(Long.valueOf(-1));
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteCollateral(final Long loanId, final Long collateralId, final Long commandId) {
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+        final LoanCollateral collateral = this.collateralRepository.findByLoanIdAndId(loanId, collateralId);
+        if (collateral == null) { throw new CollateralNotFoundException(loanId, collateralId); }
+
+        /**
+         * Collaterals may be deleted only when the loan associated with them
+         * are yet to be approved
+         **/
+        if (!loan.status().isSubmittedAndPendingApproval()) { throw new CollateralCannotBeDeletedException(
+                LOAN_COLLATERAL_CANNOT_BE_DELETED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanId, collateralId); }
+
+        loan.getCollateral().remove(collateral);
+        this.collateralRepository.delete(collateral);
+
+        return new CommandProcessingResultBuilder().withCommandId(commandId).withLoanId(loanId).withEntityId(collateralId).build();
+    }
+
+    private void handleCollateralDataIntegrityViolation(final DataIntegrityViolationException dve) {
+        logAsErrorUnexpectedDataIntegrityException(dve);
+        throw new PlatformDataIntegrityException("error.msg.collateral.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
+        logger.error(dve.getMessage(), dve);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/CollectionSheetConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/CollectionSheetConstants.java
new file mode 100644
index 0000000..7021849
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/CollectionSheetConstants.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.collectionsheet;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.paymentdetail.PaymentDetailConstants;
+
+public class CollectionSheetConstants {
+
+    public static final String COLLECTIONSHEET_RESOURCE_NAME = "collectionsheet";
+
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    public static final String transactionDateParamName = "transactionDate";
+    public static final String actualDisbursementDateParamName = "actualDisbursementDate";
+    public static final String bulkRepaymentTransactionsParamName = "bulkRepaymentTransactions";
+    public static final String bulkDisbursementTransactionsParamName = "bulkDisbursementTransactions";
+    public static final String bulkSavingsDueTransactionsParamName = "bulkSavingsDueTransactions";
+    public static final String noteParamName = "note";
+    public static final String calendarIdParamName = "calendarId";
+    public static final String officeIdParamName = "officeId";
+    public static final String staffIdParamName = "staffId";
+    public static final String isTransactionDateOnNonMeetingDateParamName = "isTransactionDateOnNonMeetingDate"; 
+
+    // attendance parameters
+    public static final String clientsAttendanceParamName = "clientsAttendance";
+    public static final String clientIdParamName = "clientId";
+    public static final String attendanceTypeParamName = "attendanceType";
+
+    public static final String loanIdParamName = "loanId";
+    public static final String savingsIdParamName = "savingsId";
+    public static final String transactionAmountParamName = "transactionAmount";
+
+    public static final Set<String> COLLECTIONSHEET_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, transactionDateParamName, actualDisbursementDateParamName, bulkRepaymentTransactionsParamName,
+            bulkDisbursementTransactionsParamName, noteParamName, calendarIdParamName, clientsAttendanceParamName,
+            bulkSavingsDueTransactionsParamName, PaymentDetailConstants.paymentTypeParamName,
+            PaymentDetailConstants.accountNumberParamName, PaymentDetailConstants.checkNumberParamName,
+            PaymentDetailConstants.routingCodeParamName, PaymentDetailConstants.receiptNumberParamName,
+            PaymentDetailConstants.bankNumberParamName, isTransactionDateOnNonMeetingDateParamName));
+
+    public static final Set<String> INDIVIDUAL_COLLECTIONSHEET_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, transactionDateParamName, actualDisbursementDateParamName, bulkRepaymentTransactionsParamName,
+            bulkDisbursementTransactionsParamName, noteParamName, bulkSavingsDueTransactionsParamName));
+
+    public static final Set<String> INDIVIDUAL_COLLECTIONSHEET_SUPPORTED_PARAMS = new HashSet<>(Arrays.asList(transactionDateParamName,
+            localeParamName, dateFormatParamName, officeIdParamName, staffIdParamName));
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/api/CollectionSheetApiResourse.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/api/CollectionSheetApiResourse.java
new file mode 100755
index 0000000..3de8b57
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/api/CollectionSheetApiResourse.java
@@ -0,0 +1,102 @@
+/**
+ * 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.collectionsheet.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+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.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants;
+import org.apache.fineract.portfolio.collectionsheet.data.IndividualCollectionSheetData;
+import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+
+@Path("/collectionsheet")
+@Component
+@Scope("singleton")
+public class CollectionSheetApiResourse {
+
+    private final CollectionSheetReadPlatformService collectionSheetReadPlatformService;
+    private final ToApiJsonSerializer<Object> toApiJsonSerializer;
+    private final FromJsonHelper fromJsonHelper;
+    private final ApiRequestParameterHelper apiRequestPrameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public CollectionSheetApiResourse(final CollectionSheetReadPlatformService collectionSheetReadPlatformService,
+            final ToApiJsonSerializer<Object> toApiJsonSerializer, final FromJsonHelper fromJsonHelper,
+            final ApiRequestParameterHelper apiRequestPrameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, final PlatformSecurityContext context) {
+        this.collectionSheetReadPlatformService = collectionSheetReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.fromJsonHelper = fromJsonHelper;
+        this.apiRequestPrameterHelper = apiRequestPrameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.context = context;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String generateCollectionSheet(@QueryParam("command") final String commandParam, final String apiRequestBodyAsJson,
+            @Context final UriInfo uriInfo) {
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+        CommandProcessingResult result = null;
+
+        if (is(commandParam, "generateCollectionSheet")) {
+            this.context.authenticatedUser().validateHasReadPermission(CollectionSheetConstants.COLLECTIONSHEET_RESOURCE_NAME);
+            final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
+            final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
+            final IndividualCollectionSheetData collectionSheet = this.collectionSheetReadPlatformService
+                    .generateIndividualCollectionSheet(query);
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestPrameterHelper.process(uriInfo.getQueryParameters());
+            return this.toApiJsonSerializer.serialize(settings, collectionSheet);
+        } else if (is(commandParam, "saveCollectionSheet")) {
+            final CommandWrapper commandRequest = builder.saveIndividualCollectionSheet().build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        }
+        return null;
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/CollectionSheetBulkDisbursalCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/CollectionSheetBulkDisbursalCommand.java
new file mode 100644
index 0000000..05a2542
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/CollectionSheetBulkDisbursalCommand.java
@@ -0,0 +1,51 @@
+/**
+ * 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.collectionsheet.command;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable command for loan bulk disburse.
+ */
+public class CollectionSheetBulkDisbursalCommand {
+
+    private final String note;
+    private final LocalDate transactionDate;
+    private final SingleDisbursalCommand[] disburseTransactions;
+
+    public CollectionSheetBulkDisbursalCommand(final String note, final LocalDate transactionDate,
+            final SingleDisbursalCommand[] disburseTransactions) {
+        this.note = note;
+        this.transactionDate = transactionDate;
+        this.disburseTransactions = disburseTransactions;
+    }
+
+    public String getNote() {
+        return this.note;
+    }
+
+    public SingleDisbursalCommand[] getDisburseTransactions() {
+        return this.disburseTransactions;
+    }
+
+    public LocalDate getTransactionDate() {
+        return this.transactionDate;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/CollectionSheetBulkRepaymentCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/CollectionSheetBulkRepaymentCommand.java
new file mode 100644
index 0000000..4f6543b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/CollectionSheetBulkRepaymentCommand.java
@@ -0,0 +1,51 @@
+/**
+ * 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.collectionsheet.command;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable command for loan bulk repayment.
+ */
+public class CollectionSheetBulkRepaymentCommand {
+
+    private final String note;
+    private final LocalDate transactionDate;
+    private final SingleRepaymentCommand[] repaymentTransactions;
+
+    public CollectionSheetBulkRepaymentCommand(final String note, final LocalDate transactionDate,
+            final SingleRepaymentCommand[] repaymentTransactions) {
+        this.note = note;
+        this.transactionDate = transactionDate;
+        this.repaymentTransactions = repaymentTransactions;
+    }
+
+    public String getNote() {
+        return this.note;
+    }
+
+    public SingleRepaymentCommand[] getLoanTransactions() {
+        return this.repaymentTransactions;
+    }
+
+    public LocalDate getTransactionDate() {
+        return this.transactionDate;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/SingleDisbursalCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/SingleDisbursalCommand.java
new file mode 100644
index 0000000..7037a06
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/SingleDisbursalCommand.java
@@ -0,0 +1,52 @@
+/**
+ * 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.collectionsheet.command;
+
+import java.math.BigDecimal;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable command for Single loan repayment.
+ */
+public class SingleDisbursalCommand {
+
+    private final Long loanId;
+    private final BigDecimal transactionAmount;
+    private final LocalDate transactionDate;
+
+    public SingleDisbursalCommand(final Long loanId, final BigDecimal transactionAmount, final LocalDate transactionDate) {
+        this.loanId = loanId;
+        this.transactionAmount = transactionAmount;
+        this.transactionDate = transactionDate;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public BigDecimal getTransactionAmount() {
+        return this.transactionAmount;
+    }
+
+    public LocalDate getTransactionDate() {
+        return this.transactionDate;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/SingleRepaymentCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/SingleRepaymentCommand.java
new file mode 100644
index 0000000..4813e18
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/command/SingleRepaymentCommand.java
@@ -0,0 +1,59 @@
+/**
+ * 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.collectionsheet.command;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable command for Single loan repayment.
+ */
+public class SingleRepaymentCommand {
+
+    private final Long loanId;
+    private final BigDecimal transactionAmount;
+    private final LocalDate transactionDate;
+    private final PaymentDetail paymentDetail;
+
+    public SingleRepaymentCommand(final Long loanId, final BigDecimal transactionAmount, final LocalDate transactionDate,
+            final PaymentDetail paymentDetail) {
+        this.loanId = loanId;
+        this.transactionAmount = transactionAmount;
+        this.transactionDate = transactionDate;
+        this.paymentDetail = paymentDetail;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public BigDecimal getTransactionAmount() {
+        return this.transactionAmount;
+    }
+
+    public LocalDate getTransactionDate() {
+        return this.transactionDate;
+    }
+
+    public PaymentDetail getPaymentDetail() {
+        return this.paymentDetail;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/CollectionSheetTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/CollectionSheetTransactionDataValidator.java
new file mode 100644
index 0000000..39200e7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/CollectionSheetTransactionDataValidator.java
@@ -0,0 +1,244 @@
+/**
+ * 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.collectionsheet.data;
+
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.COLLECTIONSHEET_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.COLLECTIONSHEET_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.INDIVIDUAL_COLLECTIONSHEET_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.attendanceTypeParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.bulkDisbursementTransactionsParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.bulkRepaymentTransactionsParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.bulkSavingsDueTransactionsParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.calendarIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.clientIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.clientsAttendanceParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.loanIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.noteParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.savingsIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.transactionAmountParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.transactionDateParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.paymentdetail.PaymentDetailConstants;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class CollectionSheetTransactionDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public CollectionSheetTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateTransaction(final JsonCommand command) {
+        final String json = command.json();
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, COLLECTIONSHEET_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(COLLECTIONSHEET_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
+        baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed(noteParamName, element);
+        if (StringUtils.isNotBlank(note)) {
+            baseDataValidator.reset().parameter(noteParamName).value(note).notExceedingLengthOf(1000);
+        }
+
+        final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParamName, element);
+        baseDataValidator.reset().parameter(calendarIdParamName).value(calendarId).notNull();
+
+        validateAttendanceDetails(element, baseDataValidator);
+
+        validateDisbursementTransactions(element, baseDataValidator);
+
+        validateRepaymentTransactions(element, baseDataValidator);
+
+        validateSavingsDueTransactions(element, baseDataValidator);
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        validatePaymentDetails(baseDataValidator, element, locale);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateIndividualCollectionSheet(final JsonCommand command) {
+        final String json = command.json();
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, INDIVIDUAL_COLLECTIONSHEET_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(COLLECTIONSHEET_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
+        baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed(noteParamName, element);
+        if (StringUtils.isNotBlank(note)) {
+            baseDataValidator.reset().parameter(noteParamName).value(note).notExceedingLengthOf(1000);
+        }
+
+        validateDisbursementTransactions(element, baseDataValidator);
+
+        validateRepaymentTransactions(element, baseDataValidator);
+
+        validateSavingsDueTransactions(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateAttendanceDetails(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has(clientsAttendanceParamName) && topLevelJsonElement.get(clientsAttendanceParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(clientsAttendanceParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject attendanceElement = array.get(i).getAsJsonObject();
+                    final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, attendanceElement);
+                    final Long attendanceType = this.fromApiJsonHelper.extractLongNamed(attendanceTypeParamName, attendanceElement);
+                    baseDataValidator.reset().parameter(clientsAttendanceParamName + "[" + i + "]." + clientIdParamName).value(clientId)
+                            .notNull().integerGreaterThanZero();
+                    baseDataValidator.reset().parameter(clientsAttendanceParamName + "[" + i + "]." + attendanceTypeParamName)
+                            .value(attendanceType).notNull().integerGreaterThanZero();
+                }
+            }
+        }
+    }
+
+    private void validateDisbursementTransactions(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has(bulkDisbursementTransactionsParamName)
+                    && topLevelJsonElement.get(bulkDisbursementTransactionsParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(bulkDisbursementTransactionsParamName).getAsJsonArray();
+
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject loanTransactionElement = array.get(i).getAsJsonObject();
+                    final Long loanId = this.fromApiJsonHelper.extractLongNamed(loanIdParamName, loanTransactionElement);
+                    final BigDecimal disbursementAmount = this.fromApiJsonHelper.extractBigDecimalNamed(transactionAmountParamName,
+                            loanTransactionElement, locale);
+
+                    baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].loan.id").value(loanId).notNull()
+                            .integerGreaterThanZero();
+                    baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].disbursement.amount").value(disbursementAmount)
+                            .notNull().zeroOrPositiveAmount();
+                }
+            }
+        }
+    }
+
+    private void validateRepaymentTransactions(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has(bulkRepaymentTransactionsParamName)
+                    && topLevelJsonElement.get(bulkRepaymentTransactionsParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(bulkRepaymentTransactionsParamName).getAsJsonArray();
+
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject loanTransactionElement = array.get(i).getAsJsonObject();
+                    final Long loanId = this.fromApiJsonHelper.extractLongNamed(loanIdParamName, loanTransactionElement);
+                    final BigDecimal disbursementAmount = this.fromApiJsonHelper.extractBigDecimalNamed(transactionAmountParamName,
+                            loanTransactionElement, locale);
+
+                    baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].loan.id").value(loanId).notNull()
+                            .integerGreaterThanZero();
+                    baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].disbursement.amount").value(disbursementAmount)
+                            .notNull().zeroOrPositiveAmount();
+
+                    validatePaymentDetails(baseDataValidator, loanTransactionElement, locale);
+                }
+            }
+        }
+    }
+
+    private void validateSavingsDueTransactions(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has(bulkSavingsDueTransactionsParamName)
+                    && topLevelJsonElement.get(bulkSavingsDueTransactionsParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(bulkSavingsDueTransactionsParamName).getAsJsonArray();
+
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject savingsTransactionElement = array.get(i).getAsJsonObject();
+                    final Long savingsId = this.fromApiJsonHelper.extractLongNamed(savingsIdParamName, savingsTransactionElement);
+                    final BigDecimal dueAmount = this.fromApiJsonHelper.extractBigDecimalNamed(transactionAmountParamName,
+                            savingsTransactionElement, locale);
+
+                    baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].savings.id").value(savingsId).notNull()
+                            .integerGreaterThanZero();
+                    baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].due.amount").value(dueAmount).notNull()
+                            .zeroOrPositiveAmount();
+                    validatePaymentDetails(baseDataValidator, savingsTransactionElement, locale);
+                }
+            }
+        }
+    }
+
+    private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element, final Locale locale) {
+        // Validate all string payment detail fields for max length
+        final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerNamed(PaymentDetailConstants.paymentTypeParamName, element,
+                locale);
+        baseDataValidator.reset().parameter(PaymentDetailConstants.paymentTypeParamName).value(paymentTypeId).ignoreIfNull()
+                .integerGreaterThanZero();
+        for (final String paymentDetailParameterName : PaymentDetailConstants.PAYMENT_CREATE_REQUEST_DATA_PARAMETERS) {
+            final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
+            baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+        }
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualClientData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualClientData.java
new file mode 100755
index 0000000..dcb11a4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualClientData.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.portfolio.collectionsheet.data;
+
+import java.util.Collection;
+
+/**
+ * Immutable data object for clients with loans due for disbursement or
+ * collection.
+ */
+public class IndividualClientData {
+
+    private final Long clientId;
+    private final String clientName;
+    private Collection<LoanDueData> loans;
+    private Collection<SavingsDueData> savings;
+
+    public static IndividualClientData instance(final Long clientId, final String clientName) {
+        final Collection<LoanDueData> loans = null;
+        final Collection<SavingsDueData> savings = null;
+        return new IndividualClientData(clientId, clientName, loans, savings);
+    }
+
+    public static IndividualClientData withSavings(final IndividualClientData client, final Collection<SavingsDueData> savings) {
+
+        return new IndividualClientData(client.clientId, client.clientName, client.loans, savings);
+    }
+
+    public static IndividualClientData withLoans(final IndividualClientData client, final Collection<LoanDueData> loans) {
+
+        return new IndividualClientData(client.clientId, client.clientName, loans, client.savings);
+    }
+
+    /**
+     * @param clientId
+     * @param clientName
+     * @param loans
+     * @param savings
+     */
+    private IndividualClientData(Long clientId, String clientName, Collection<LoanDueData> loans, Collection<SavingsDueData> savings) {
+        this.clientId = clientId;
+        this.clientName = clientName;
+        this.loans = loans;
+        this.savings = savings;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+    public String getClientName() {
+        return this.clientName;
+    }
+
+    public Collection<LoanDueData> getLoans() {
+        return this.loans;
+    }
+
+    public void setLoans(final Collection<LoanDueData> loans) {
+        this.loans = loans;
+    }
+
+    public void addLoans(LoanDueData loans) {
+        if (this.loans != null) {
+            this.loans.add(loans);
+        }
+    }
+
+    public Collection<SavingsDueData> getSavings() {
+        return this.savings;
+    }
+
+    public void setSavings(Collection<SavingsDueData> savings) {
+        this.savings = savings;
+    }
+
+    public void addSavings(SavingsDueData savings) {
+        if (this.savings != null) {
+            this.savings.add(savings);
+        }
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        final IndividualClientData clientData = (IndividualClientData) obj;
+        return clientData.clientId.compareTo(this.clientId) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return this.clientId.hashCode();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetData.java
new file mode 100755
index 0000000..605545a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetData.java
@@ -0,0 +1,51 @@
+/**
+ * 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.collectionsheet.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object for collection sheet.
+ */
+public class IndividualCollectionSheetData {
+
+    @SuppressWarnings("unused")
+    private final LocalDate dueDate;
+    @SuppressWarnings("unused")
+    private final Collection<IndividualClientData> clients;
+
+    @SuppressWarnings("unused")
+    private final Collection<PaymentTypeData> paymentTypeOptions;
+
+    public static IndividualCollectionSheetData instance(final LocalDate date, final Collection<IndividualClientData> clients,
+            final Collection<PaymentTypeData> paymentTypeOptions) {
+        return new IndividualCollectionSheetData(date, clients, paymentTypeOptions);
+    }
+
+    private IndividualCollectionSheetData(final LocalDate dueDate, final Collection<IndividualClientData> clients,
+            final Collection<PaymentTypeData> paymentTypeOptions) {
+        this.dueDate = dueDate;
+        this.clients = clients;
+        this.paymentTypeOptions = paymentTypeOptions;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetLoanFlatData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetLoanFlatData.java
new file mode 100755
index 0000000..d37c254
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetLoanFlatData.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.collectionsheet.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+/**
+ * Immutable data object for extracting flat data for joint liability group's
+ * collection sheet.
+ */
+public class IndividualCollectionSheetLoanFlatData {
+
+    private final String clientName;
+    private final Long clientId;
+    private final Long loanId;
+    private final String accountId;
+    private final Integer accountStatusId;
+    private final String productShortName;
+    private final Long productId;
+    private final CurrencyData currency;
+    private BigDecimal disbursementAmount = BigDecimal.ZERO;
+    private BigDecimal principalDue = BigDecimal.ZERO;
+    private BigDecimal principalPaid = BigDecimal.ZERO;
+    private BigDecimal interestDue = BigDecimal.ZERO;
+    private BigDecimal interestPaid = BigDecimal.ZERO;
+    private BigDecimal chargesDue = BigDecimal.ZERO;
+
+    public IndividualCollectionSheetLoanFlatData(final String clientName, final Long clientId, final Long loanId, final String accountId,
+            final Integer accountStatusId, final String productShortName, final Long productId, final CurrencyData currency,
+            final BigDecimal disbursementAmount, final BigDecimal principalDue, final BigDecimal principalPaid,
+            final BigDecimal interestDue, final BigDecimal interestPaid, final BigDecimal chargesDue) {
+        this.clientName = clientName;
+        this.clientId = clientId;
+        this.loanId = loanId;
+        this.accountId = accountId;
+        this.accountStatusId = accountStatusId;
+        this.productShortName = productShortName;
+        this.productId = productId;
+        this.currency = currency;
+        this.disbursementAmount = disbursementAmount;
+        this.principalDue = principalDue;
+        this.principalPaid = principalPaid;
+        this.interestDue = interestDue;
+        this.interestPaid = interestPaid;
+        this.chargesDue = chargesDue;
+    }
+
+    public String getClientName() {
+        return this.clientName;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public String getAccountId() {
+        return this.accountId;
+    }
+
+    public Integer getAccountStatusId() {
+        return this.accountStatusId;
+    }
+
+    public String getProductShortName() {
+        return this.productShortName;
+    }
+
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    public CurrencyData getCurrency() {
+        return this.currency;
+    }
+
+    public BigDecimal getDisbursementAmount() {
+        return this.disbursementAmount;
+    }
+
+    public BigDecimal getPrincipalDue() {
+        return this.principalDue;
+    }
+
+    public BigDecimal getPrincipalPaid() {
+        return this.principalPaid;
+    }
+
+    public BigDecimal getInterestDue() {
+        return this.interestDue;
+    }
+
+    public BigDecimal getInterestPaid() {
+        return this.interestPaid;
+    }
+
+    public BigDecimal getChargesDue() {
+        return this.chargesDue;
+    }
+
+    public LoanDueData getLoanDueData() {
+        return new LoanDueData(this.loanId, this.accountId, this.accountStatusId, this.productShortName, this.productId, this.currency,
+                this.disbursementAmount, this.principalDue, this.principalPaid, this.interestDue, this.interestPaid, this.chargesDue);
+    }
+
+    public IndividualClientData getClientData() {
+        return IndividualClientData.instance(this.clientId, this.clientName);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGClientData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGClientData.java
new file mode 100644
index 0000000..0d3889a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGClientData.java
@@ -0,0 +1,116 @@
+/**
+ * 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.collectionsheet.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+/**
+ * Immutable data object for clients with loans due for disbursement or
+ * collection.
+ */
+public class JLGClientData {
+
+    private final Long clientId;
+    private final String clientName;
+    private Collection<LoanDueData> loans;
+    private Collection<SavingsDueData> savings;
+    @SuppressWarnings("unused")
+    private final EnumOptionData attendanceType;
+
+    public static JLGClientData instance(final Long clientId, final String clientName, final EnumOptionData attendanceType){
+        final Collection<LoanDueData> loans = null;
+        final Collection<SavingsDueData> savings = null;
+        return new JLGClientData(clientId, clientName, loans, savings, attendanceType);
+    }
+    
+    public static JLGClientData withSavings(final JLGClientData client, final Collection<SavingsDueData> savings){
+        final Collection<LoanDueData> loans = null;
+        final EnumOptionData attendanceType = null;
+        
+        return new JLGClientData(client.clientId, client.clientName, loans, savings, attendanceType);
+    }
+    
+    /*public JLGClientData(final Long clientId, final String clientName, final Collection<LoanDueData> loans,
+            final EnumOptionData attendanceType) {
+        this.clientId = clientId;
+        this.clientName = clientName;
+        this.loans = loans;
+        this.attendanceType = attendanceType;
+    }*/
+
+    public static JLGClientData withAttendance(final Long clientId, final String clientName, final EnumOptionData attendanceType) {
+        final Collection<LoanDueData> loans = null;
+        final Collection<SavingsDueData> savings = null;
+        return new JLGClientData(clientId, clientName, loans, savings, attendanceType);
+    }
+    
+    /**
+     * @param clientId
+     * @param clientName
+     * @param loans
+     * @param savings
+     * @param attendanceType
+     */
+    private JLGClientData(Long clientId, String clientName, Collection<LoanDueData> loans, Collection<SavingsDueData> savings,
+            EnumOptionData attendanceType) {
+        this.clientId = clientId;
+        this.clientName = clientName;
+        this.loans = loans;
+        this.savings = savings;
+        this.attendanceType = attendanceType;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+    public String getClientName() {
+        return this.clientName;
+    }
+
+    public Collection<LoanDueData> getLoans() {
+        return this.loans;
+    }
+
+    public void setLoans(final Collection<LoanDueData> loans) {
+        this.loans = loans;
+    }
+        
+    public Collection<SavingsDueData> getSavings() {
+        return this.savings;
+    }
+
+    
+    public void setSavings(Collection<SavingsDueData> savings) {
+        this.savings = savings;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        final JLGClientData clientData = (JLGClientData) obj;
+        return clientData.clientId.compareTo(this.clientId) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return this.clientId.hashCode();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGCollectionSheetData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGCollectionSheetData.java
new file mode 100644
index 0000000..90c62f2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGCollectionSheetData.java
@@ -0,0 +1,80 @@
+/**
+ * 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.collectionsheet.data;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object for join liability group's collection sheet.
+ */
+public class JLGCollectionSheetData {
+
+    private final LocalDate dueDate;
+    private final Collection<LoanProductData> loanProducts;
+    @SuppressWarnings("unused")
+    private final Collection<SavingsProductData> savingsProducts;
+    private final Collection<JLGGroupData> groups;
+    private final List<EnumOptionData> attendanceTypeOptions;
+    private final Collection<PaymentTypeData> paymentTypeOptions;
+
+    public static JLGCollectionSheetData instance(final LocalDate date, final Collection<LoanProductData> loanProducts,
+            final Collection<JLGGroupData> groups, final List<EnumOptionData> attendanceTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions) {
+        return new JLGCollectionSheetData(date, loanProducts, null, groups, attendanceTypeOptions, paymentTypeOptions);
+    }
+
+    public static JLGCollectionSheetData withSavingsProducts(final JLGCollectionSheetData data,
+            final Collection<SavingsProductData> savingsProducts) {
+
+        return new JLGCollectionSheetData(data.dueDate, data.loanProducts, savingsProducts, data.groups, data.attendanceTypeOptions,
+                data.paymentTypeOptions);
+    }
+
+    private JLGCollectionSheetData(LocalDate dueDate, Collection<LoanProductData> loanProducts,
+            Collection<SavingsProductData> savingsProducts, Collection<JLGGroupData> groups, List<EnumOptionData> attendanceTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions) {
+        this.dueDate = dueDate;
+        this.loanProducts = loanProducts;
+        this.savingsProducts = savingsProducts;
+        this.groups = groups;
+        this.attendanceTypeOptions = attendanceTypeOptions;
+        this.paymentTypeOptions = paymentTypeOptions;
+    }
+
+    public LocalDate getDate() {
+        return this.dueDate;
+    }
+
+    public Collection<JLGGroupData> getGroups() {
+        return this.groups;
+    }
+
+    public Collection<LoanProductData> getLoanProducts() {
+        return this.loanProducts;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGCollectionSheetFlatData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGCollectionSheetFlatData.java
new file mode 100644
index 0000000..b476357
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGCollectionSheetFlatData.java
@@ -0,0 +1,177 @@
+/**
+ * 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.collectionsheet.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+/**
+ * Immutable data object for extracting flat data for joint liability group's
+ * collection sheet.
+ */
+public class JLGCollectionSheetFlatData {
+
+    private final String groupName;
+    private final Long groupId;
+    private final Long staffId;
+    private final String staffName;
+    private final Long levelId;
+    private final String levelName;
+    private final String clientName;
+    private final Long clientId;
+    private final Long loanId;
+    private final String accountId;
+    private final Integer accountStatusId;
+    private final String productShortName;
+    private final Long productId;
+    private final CurrencyData currency;
+    private BigDecimal disbursementAmount = BigDecimal.ZERO;
+    private BigDecimal principalDue = BigDecimal.ZERO;
+    private BigDecimal principalPaid = BigDecimal.ZERO;
+    private BigDecimal interestDue = BigDecimal.ZERO;
+    private BigDecimal interestPaid = BigDecimal.ZERO;
+    private BigDecimal chargesDue = BigDecimal.ZERO;
+    private final EnumOptionData attendanceType;
+
+    public JLGCollectionSheetFlatData(final String groupName, final Long groupId, final Long staffId, final String staffName,
+            final Long levelId, final String levelName, final String clientName, final Long clientId, final Long loanId,
+            final String accountId, final Integer accountStatusId, final String productShortName, final Long productId,
+            final CurrencyData currency, final BigDecimal disbursementAmount, final BigDecimal principalDue,
+            final BigDecimal principalPaid, final BigDecimal interestDue, final BigDecimal interestPaid, final BigDecimal chargesDue,
+            final EnumOptionData attendanceType) {
+        this.groupName = groupName;
+        this.groupId = groupId;
+        this.staffId = staffId;
+        this.staffName = staffName;
+        this.levelId = levelId;
+        this.levelName = levelName;
+        this.clientName = clientName;
+        this.clientId = clientId;
+        this.loanId = loanId;
+        this.accountId = accountId;
+        this.accountStatusId = accountStatusId;
+        this.productShortName = productShortName;
+        this.productId = productId;
+        this.currency = currency;
+        this.disbursementAmount = disbursementAmount;
+        this.principalDue = principalDue;
+        this.principalPaid = principalPaid;
+        this.interestDue = interestDue;
+        this.interestPaid = interestPaid;
+        this.chargesDue = chargesDue;
+        this.attendanceType = attendanceType;
+    }
+
+    public String getGroupName() {
+        return this.groupName;
+    }
+
+    public Long getGroupId() {
+        return this.groupId;
+    }
+
+    public Long getStaffId() {
+        return this.staffId;
+    }
+
+    public String getStaffName() {
+        return this.staffName;
+    }
+
+    public Long getLevelId() {
+        return this.levelId;
+    }
+
+    public String getLevelName() {
+        return this.levelName;
+    }
+
+    public String getClientName() {
+        return this.clientName;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public String getAccountId() {
+        return this.accountId;
+    }
+
+    public Integer getAccountStatusId() {
+        return this.accountStatusId;
+    }
+
+    public String getProductShortName() {
+        return this.productShortName;
+    }
+
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    public CurrencyData getCurrency() {
+        return this.currency;
+    }
+
+    public BigDecimal getDisbursementAmount() {
+        return this.disbursementAmount;
+    }
+
+    public BigDecimal getPrincipalDue() {
+        return this.principalDue;
+    }
+
+    public BigDecimal getPrincipalPaid() {
+        return this.principalPaid;
+    }
+
+    public BigDecimal getInterestDue() {
+        return this.interestDue;
+    }
+
+    public BigDecimal getInterestPaid() {
+        return this.interestPaid;
+    }
+
+    public BigDecimal getChargesDue() {
+        return this.chargesDue;
+    }
+
+    public LoanDueData getLoanDueData() {
+        return new LoanDueData(this.loanId, this.accountId, this.accountStatusId, this.productShortName, this.productId, this.currency,
+                this.disbursementAmount, this.principalDue, this.principalPaid, this.interestDue, this.interestPaid, this.chargesDue);
+    }
+
+    public JLGClientData getClientData() {
+        return JLGClientData.withAttendance(this.clientId, this.clientName, this.attendanceType);
+    }
+
+    public JLGGroupData getJLGGroupData() {
+
+        return JLGGroupData.instance(this.groupId, this.groupName, this.staffId, this.staffName, this.levelId, this.levelName);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGGroupData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGGroupData.java
new file mode 100644
index 0000000..06fdcec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/JLGGroupData.java
@@ -0,0 +1,99 @@
+/**
+ * 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.collectionsheet.data;
+
+import java.util.Collection;
+
+/**
+ * Immutable data object for groups with clients due for disbursement or
+ * collection.
+ */
+public class JLGGroupData {
+
+    private final Long groupId;
+    private final String groupName;
+    private final Long staffId;
+    private final String staffName;
+    private final Long levelId;
+    private final String levelName;
+    private Collection<JLGClientData> clients;
+
+    public static JLGGroupData instance(final Long groupId, final String groupName, final Long staffId, final String staffName, final Long levelId,
+            final String levelName){
+        return new JLGGroupData(groupId, groupName, staffId, staffName, levelId, levelName, null);
+    }
+    
+    public static JLGGroupData withClients(final JLGGroupData group, Collection<JLGClientData> clients){
+        return new JLGGroupData(group.groupId, group.groupName, group.staffId, group.staffName, group.levelId, group.levelName, clients);
+    }
+    
+    private JLGGroupData(final Long groupId, final String groupName, final Long staffId, final String staffName, final Long levelId,
+            final String levelName, final Collection<JLGClientData> clients) {
+        this.groupId = groupId;
+        this.groupName = groupName;
+        this.staffId = staffId;
+        this.staffName = staffName;
+        this.levelId = levelId;
+        this.levelName = levelName;
+        this.clients = clients;
+    }
+
+    public Long getGroupId() {
+        return this.groupId;
+    }
+
+    public String getGroupName() {
+        return this.groupName;
+    }
+
+    public Long getStaffId() {
+        return this.staffId;
+    }
+
+    public String getStaffName() {
+        return this.staffName;
+    }
+
+    public Long getLevelId() {
+        return this.levelId;
+    }
+
+    public String getLevelName() {
+        return this.levelName;
+    }
+
+    public Collection<JLGClientData> getClients() {
+        return this.clients;
+    }
+
+    public void setClients(final Collection<JLGClientData> clients) {
+        this.clients = clients;
+    }
+    
+    @Override
+    public boolean equals(final Object obj) {
+        final JLGGroupData groupData = (JLGGroupData) obj;
+        return groupData.groupId.compareTo(this.groupId) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return this.groupId.hashCode();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/LoanDueData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/LoanDueData.java
new file mode 100644
index 0000000..7a598e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/LoanDueData.java
@@ -0,0 +1,111 @@
+/**
+ * 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.collectionsheet.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+/**
+ * Immutable data object for representing loan with dues (example: loan is due
+ * for disbursement, repayments).
+ */
+public class LoanDueData {
+
+    private final Long loanId;
+    private final String accountId;
+    private final Integer accountStatusId;
+    private final String productShortName;
+    private final Long productId;
+    private final CurrencyData currency;
+    private BigDecimal disbursementAmount = BigDecimal.ZERO;
+    private BigDecimal principalDue = BigDecimal.ZERO;
+    private BigDecimal principalPaid = BigDecimal.ZERO;
+    private BigDecimal interestDue = BigDecimal.ZERO;
+    private BigDecimal interestPaid = BigDecimal.ZERO;
+    private BigDecimal chargesDue = BigDecimal.ZERO;
+    private BigDecimal totalDue = BigDecimal.ZERO;
+
+    public LoanDueData(final Long loanId, final String accountId, final Integer accountStatusId, final String productShortName,
+            final Long productId, final CurrencyData currency, final BigDecimal disbursementAmount, final BigDecimal principalDue,
+            final BigDecimal principalPaid, final BigDecimal interestDue, final BigDecimal interestPaid, final BigDecimal chargesDue) {
+        this.loanId = loanId;
+        this.accountId = accountId;
+        this.accountStatusId = accountStatusId;
+        this.productShortName = productShortName;
+        this.productId = productId;
+        this.currency = currency;
+        this.disbursementAmount = disbursementAmount;
+        this.principalDue = principalDue;
+        this.principalPaid = principalPaid;
+        this.interestDue = interestDue;
+        this.interestPaid = interestPaid;
+        this.chargesDue = chargesDue;
+        this.totalDue = this.totalDue.add(principalDue).add(interestDue);
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public String getAccountId() {
+        return this.accountId;
+    }
+
+    public Integer getAccountStatusId() {
+        return this.accountStatusId;
+    }
+
+    public String getProductShortName() {
+        return this.productShortName;
+    }
+
+    public Long getProductId() {
+        return this.productId;
+    }
+
+    public CurrencyData getCurrency() {
+        return this.currency;
+    }
+
+    public BigDecimal getDisbursementAmount() {
+        return this.disbursementAmount;
+    }
+
+    public BigDecimal getPrincipalDue() {
+        return this.principalDue;
+    }
+
+    public BigDecimal getPrincipalPaid() {
+        return this.principalPaid;
+    }
+
+    public BigDecimal getInterestDue() {
+        return this.interestDue;
+    }
+
+    public BigDecimal getInterestPaid() {
+        return this.interestPaid;
+    }
+
+    public BigDecimal getChargesDue() {
+        return this.chargesDue;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/SavingsDueData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/SavingsDueData.java
new file mode 100644
index 0000000..c084b3e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/SavingsDueData.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.collectionsheet.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+/**
+ * Immutable data object for representing loan with dues (example: loan is due
+ * for disbursement, repayments).
+ */
+public class SavingsDueData {
+
+    @SuppressWarnings("unused")
+    private final Long savingsId;
+    @SuppressWarnings("unused")
+    private final String accountId;
+    @SuppressWarnings("unused")
+    private final Integer accountStatusId;
+    private final String productName;
+    private final Long productId;
+    @SuppressWarnings("unused")
+    private final CurrencyData currency;
+    @SuppressWarnings("unused")
+    private BigDecimal dueAmount = BigDecimal.ZERO;
+
+    public static SavingsDueData instance(final Long savingsId, final String accountId, final Integer accountStatusId,
+            final String productName, final Long productId, final CurrencyData currency, final BigDecimal dueAmount) {
+        return new SavingsDueData(savingsId, accountId, accountStatusId, productName, productId, currency, dueAmount);
+    }
+
+    private SavingsDueData(final Long savingsId, final String accountId, final Integer accountStatusId, final String productName,
+            final Long productId, final CurrencyData currency, final BigDecimal dueAmount) {
+        this.savingsId = savingsId;
+        this.accountId = accountId;
+        this.accountStatusId = accountStatusId;
+        this.productName = productName;
+        this.productId = productId;
+        this.currency = currency;
+        this.dueAmount = dueAmount;
+    }
+    
+    public String productName() {
+        return this.productName;
+    }
+    
+    public Long productId() {
+        return this.productId;
+    }
+    
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/handler/SaveIndividualCollectionSheetCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/handler/SaveIndividualCollectionSheetCommandHandler.java
new file mode 100755
index 0000000..9437612
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/handler/SaveIndividualCollectionSheetCommandHandler.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.collectionsheet.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "COLLECTIONSHEET", action = "SAVE")
+public class SaveIndividualCollectionSheetCommandHandler implements NewCommandSourceHandler {
+
+    private final CollectionSheetWritePlatformService collectionSheetWritePlatformService;
+
+    @Autowired
+    public SaveIndividualCollectionSheetCommandHandler(final CollectionSheetWritePlatformService collectionSheetWritePlatformService) {
+        this.collectionSheetWritePlatformService = collectionSheetWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.collectionSheetWritePlatformService.saveIndividualCollectionSheet(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/handler/UpdateCollectionSheetCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/handler/UpdateCollectionSheetCommandHandler.java
new file mode 100644
index 0000000..171e804
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/handler/UpdateCollectionSheetCommandHandler.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.collectionsheet.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "COLLECTIONSHEET", action = "UPDATE")
+public class UpdateCollectionSheetCommandHandler implements NewCommandSourceHandler {
+
+    private final CollectionSheetWritePlatformService collectionSheetWritePlatformService;
+
+    @Autowired
+    public UpdateCollectionSheetCommandHandler(final CollectionSheetWritePlatformService collectionSheetWritePlatformService) {
+        this.collectionSheetWritePlatformService = collectionSheetWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.collectionSheetWritePlatformService.updateCollectionSheet(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/serialization/CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/serialization/CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..308f1a5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/serialization/CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer.java
@@ -0,0 +1,84 @@
+/**
+ * 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.collectionsheet.serialization;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
+import org.apache.fineract.portfolio.collectionsheet.command.SingleDisbursalCommand;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link CollectionSheetBulkDisbursalCommand}'s.
+ */
+@Component
+public final class CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer extends
+        AbstractFromApiJsonDeserializer<CollectionSheetBulkDisbursalCommand> {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public CollectionSheetBulkDisbursalCommand commandFromApiJson(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+
+        SingleDisbursalCommand[] loanDisbursementTransactions = null;
+
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has("bulkDisbursementTransactions")
+                    && topLevelJsonElement.get("bulkDisbursementTransactions").isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get("bulkDisbursementTransactions").getAsJsonArray();
+                loanDisbursementTransactions = new SingleDisbursalCommand[array.size()];
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject loanTransactionElement = array.get(i).getAsJsonObject();
+
+                    final Long loanId = this.fromApiJsonHelper.extractLongNamed("loanId", loanTransactionElement);
+                    final BigDecimal disbursementAmount = this.fromApiJsonHelper.extractBigDecimalNamed("transactionAmount",
+                            loanTransactionElement, locale);
+                    loanDisbursementTransactions[i] = new SingleDisbursalCommand(loanId, disbursementAmount, transactionDate);
+                }
+            }
+        }
+        return new CollectionSheetBulkDisbursalCommand(note, transactionDate, loanDisbursementTransactions);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/serialization/CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/serialization/CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..0d16d10
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/serialization/CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.collectionsheet.serialization;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
+import org.apache.fineract.portfolio.collectionsheet.command.SingleRepaymentCommand;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailAssembler;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link CollectionSheetBulkRepaymentCommand}'s.
+ */
+@Component
+public final class CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer extends
+        AbstractFromApiJsonDeserializer<CollectionSheetBulkRepaymentCommand> {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final PaymentDetailAssembler paymentDetailAssembler;
+
+    @Autowired
+    public CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper,
+            final PaymentDetailAssembler paymentDetailAssembler) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.paymentDetailAssembler = paymentDetailAssembler;
+    }
+
+    @Override
+    public CollectionSheetBulkRepaymentCommand commandFromApiJson(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final PaymentDetail paymentDetail = this.paymentDetailAssembler.fetchPaymentDetail(element.getAsJsonObject());
+
+        return commandFromApiJson(json, paymentDetail);
+    }
+
+    public CollectionSheetBulkRepaymentCommand commandFromApiJson(final String json, final PaymentDetail paymentDetail) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+
+        SingleRepaymentCommand[] loanRepaymentTransactions = null;
+
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has("bulkRepaymentTransactions") && topLevelJsonElement.get("bulkRepaymentTransactions").isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get("bulkRepaymentTransactions").getAsJsonArray();
+                loanRepaymentTransactions = new SingleRepaymentCommand[array.size()];
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject loanTransactionElement = array.get(i).getAsJsonObject();
+
+                    final Long loanId = this.fromApiJsonHelper.extractLongNamed("loanId", loanTransactionElement);
+                    final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalNamed("transactionAmount",
+                            loanTransactionElement, locale);
+                    PaymentDetail detail = paymentDetail;
+                    if (paymentDetail == null) {
+                        detail = this.paymentDetailAssembler.fetchPaymentDetail(loanTransactionElement);
+                    }
+                    if(transactionAmount != null && transactionAmount.intValue() > 0){
+                    	loanRepaymentTransactions[i] = new SingleRepaymentCommand(loanId, transactionAmount, transactionDate, detail);
+                    }
+                }
+            }
+        }
+        return new CollectionSheetBulkRepaymentCommand(note, transactionDate, loanRepaymentTransactions);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/serialization/CollectionSheetGenerateCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/serialization/CollectionSheetGenerateCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..29b17fc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/serialization/CollectionSheetGenerateCommandFromApiJsonDeserializer.java
@@ -0,0 +1,123 @@
+/**
+ * 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.collectionsheet.serialization;
+
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.INDIVIDUAL_COLLECTIONSHEET_SUPPORTED_PARAMS;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.calendarIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.dateFormatParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.localeParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.officeIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.staffIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.transactionDateParamName;
+
+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.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class CollectionSheetGenerateCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    final Set<String> supportedParameters = new HashSet<>(Arrays.asList(transactionDateParamName, localeParamName, dateFormatParamName,
+            calendarIdParamName));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public CollectionSheetGenerateCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForGenerateCollectionSheet(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 JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("collectionsheet");
+
+        final String transactionDateStr = this.fromApiJsonHelper.extractStringNamed(transactionDateParamName, element);
+        baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDateStr).notBlank();
+
+        if (!StringUtils.isBlank(transactionDateStr)) {
+            final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
+            baseDataValidator.reset().parameter(transactionDateParamName).value(dueDate).notNull();
+        }
+
+        final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParamName, element);
+        baseDataValidator.reset().parameter(calendarIdParamName).value(calendarId).notNull();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForGenerateCollectionSheetOfIndividuals(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, INDIVIDUAL_COLLECTIONSHEET_SUPPORTED_PARAMS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("collectionsheet");
+
+        final String transactionDateStr = this.fromApiJsonHelper.extractStringNamed(transactionDateParamName, element);
+        baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDateStr).notBlank();
+
+        if (!StringUtils.isBlank(transactionDateStr)) {
+            final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
+            baseDataValidator.reset().parameter(transactionDateParamName).value(dueDate).notNull();
+        }
+
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(officeIdParamName, element);
+        baseDataValidator.reset().parameter(officeIdParamName).value(officeId).longGreaterThanZero();
+
+        final Long staffId = this.fromApiJsonHelper.extractLongNamed(staffIdParamName, element);
+        baseDataValidator.reset().parameter(staffIdParamName).value(staffId).longGreaterThanZero();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformService.java
new file mode 100644
index 0000000..6d5e75a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformService.java
@@ -0,0 +1,33 @@
+/**
+ * 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.collectionsheet.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.portfolio.collectionsheet.data.IndividualCollectionSheetData;
+import org.apache.fineract.portfolio.collectionsheet.data.JLGCollectionSheetData;
+
+public interface CollectionSheetReadPlatformService {
+
+    JLGCollectionSheetData generateGroupCollectionSheet(final Long groupId, final JsonQuery query);
+
+    JLGCollectionSheetData generateCenterCollectionSheet(final Long groupId, final JsonQuery query);
+
+    IndividualCollectionSheetData generateIndividualCollectionSheet(final JsonQuery query);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java
new file mode 100644
index 0000000..76d0c09
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java
@@ -0,0 +1,861 @@
+/**
+ * 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.collectionsheet.service;
+
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.calendarIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.officeIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.staffIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.transactionDateParamName;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+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.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRepositoryWrapper;
+import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException;
+import org.apache.fineract.portfolio.collectionsheet.data.IndividualClientData;
+import org.apache.fineract.portfolio.collectionsheet.data.IndividualCollectionSheetData;
+import org.apache.fineract.portfolio.collectionsheet.data.IndividualCollectionSheetLoanFlatData;
+import org.apache.fineract.portfolio.collectionsheet.data.JLGClientData;
+import org.apache.fineract.portfolio.collectionsheet.data.JLGCollectionSheetData;
+import org.apache.fineract.portfolio.collectionsheet.data.JLGCollectionSheetFlatData;
+import org.apache.fineract.portfolio.collectionsheet.data.JLGGroupData;
+import org.apache.fineract.portfolio.collectionsheet.data.LoanDueData;
+import org.apache.fineract.portfolio.collectionsheet.data.SavingsDueData;
+import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetGenerateCommandFromApiJsonDeserializer;
+import org.apache.fineract.portfolio.group.data.CenterData;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.service.CenterReadPlatformService;
+import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.meeting.attendance.service.AttendanceDropdownReadPlatformService;
+import org.apache.fineract.portfolio.meeting.attendance.service.AttendanceEnumerations;
+import org.apache.fineract.portfolio.paymentdetail.PaymentDetailConstants;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.core.namedparam.SqlParameterSource;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final NamedParameterJdbcTemplate namedParameterjdbcTemplate;
+    private final CenterReadPlatformService centerReadPlatformService;
+    private final GroupReadPlatformService groupReadPlatformService;
+    private final CollectionSheetGenerateCommandFromApiJsonDeserializer collectionSheetGenerateCommandFromApiJsonDeserializer;
+    private final CalendarRepositoryWrapper calendarRepositoryWrapper;
+    private final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService;
+    private final MandatorySavingsCollectionsheetExtractor mandatorySavingsExtractor = new MandatorySavingsCollectionsheetExtractor();
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public CollectionSheetReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final CenterReadPlatformService centerReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
+            final CollectionSheetGenerateCommandFromApiJsonDeserializer collectionSheetGenerateCommandFromApiJsonDeserializer,
+            final CalendarRepositoryWrapper calendarRepositoryWrapper,
+            final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService, final PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.context = context;
+        this.centerReadPlatformService = centerReadPlatformService;
+        this.namedParameterjdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
+        this.collectionSheetGenerateCommandFromApiJsonDeserializer = collectionSheetGenerateCommandFromApiJsonDeserializer;
+        this.groupReadPlatformService = groupReadPlatformService;
+        this.calendarRepositoryWrapper = calendarRepositoryWrapper;
+        this.attendanceDropdownReadPlatformService = attendanceDropdownReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+    }
+
+    /*
+     * Reads all the loans which are due for disbursement or collection and
+     * builds hierarchical data structure for collections sheet with hierarchy
+     * Groups >> Clients >> Loans.
+     */
+    @SuppressWarnings("null")
+    private JLGCollectionSheetData buildJLGCollectionSheet(final LocalDate dueDate,
+            final Collection<JLGCollectionSheetFlatData> jlgCollectionSheetFlatData) {
+
+        boolean firstTime = true;
+        Long prevGroupId = null;
+        Long prevClientId = null;
+        final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+                
+
+        final List<JLGGroupData> jlgGroupsData = new ArrayList<>();
+        List<JLGClientData> clientsData = new ArrayList<>();
+        List<LoanDueData> loansDueData = new ArrayList<>();
+
+        JLGCollectionSheetData jlgCollectionSheetData = null;
+        JLGCollectionSheetFlatData prevCollectioSheetFlatData = null;
+        JLGCollectionSheetFlatData corrCollectioSheetFlatData = null;
+        final Set<LoanProductData> loanProducts = new HashSet<>();
+        if (jlgCollectionSheetFlatData != null) {
+
+            for (final JLGCollectionSheetFlatData collectionSheetFlatData : jlgCollectionSheetFlatData) {
+
+                if (collectionSheetFlatData.getProductId() != null) {
+                    loanProducts.add(LoanProductData.lookupWithCurrency(collectionSheetFlatData.getProductId(),
+                            collectionSheetFlatData.getProductShortName(), collectionSheetFlatData.getCurrency()));
+                }
+                corrCollectioSheetFlatData = collectionSheetFlatData;
+
+                if (firstTime || collectionSheetFlatData.getGroupId().equals(prevGroupId)) {
+                    if (firstTime || collectionSheetFlatData.getClientId().equals(prevClientId)) {
+                        if (collectionSheetFlatData.getLoanId() != null) {
+                            loansDueData.add(collectionSheetFlatData.getLoanDueData());
+                        }
+                    } else {
+                        final JLGClientData clientData = prevCollectioSheetFlatData.getClientData();
+                        clientData.setLoans(loansDueData);
+                        clientsData.add(clientData);
+                        loansDueData = new ArrayList<>();
+
+                        if (collectionSheetFlatData.getLoanId() != null) {
+                            loansDueData.add(collectionSheetFlatData.getLoanDueData());
+                        }
+
+                    }
+                } else {
+
+                    final JLGClientData clientData = prevCollectioSheetFlatData.getClientData();
+                    clientData.setLoans(loansDueData);
+                    clientsData.add(clientData);
+
+                    final JLGGroupData jlgGroupData = prevCollectioSheetFlatData.getJLGGroupData();
+                    jlgGroupData.setClients(clientsData);
+
+                    jlgGroupsData.add(jlgGroupData);
+
+                    loansDueData = new ArrayList<>();
+                    clientsData = new ArrayList<>();
+
+                    if (collectionSheetFlatData.getLoanId() != null) {
+                        loansDueData.add(collectionSheetFlatData.getLoanDueData());
+                    }
+                }
+
+                prevClientId = collectionSheetFlatData.getClientId();
+                prevGroupId = collectionSheetFlatData.getGroupId();
+                prevCollectioSheetFlatData = collectionSheetFlatData;
+                firstTime = false;
+            }
+
+            // FIXME Need to check last loan is added under previous
+            // client/group or new client / previous group or new client / new
+            // group
+            if (corrCollectioSheetFlatData != null) {
+                final JLGClientData lastClientData = corrCollectioSheetFlatData.getClientData();
+                lastClientData.setLoans(loansDueData);
+                clientsData.add(lastClientData);
+
+                final JLGGroupData jlgGroupData = corrCollectioSheetFlatData.getJLGGroupData();
+                jlgGroupData.setClients(clientsData);
+                jlgGroupsData.add(jlgGroupData);
+            }
+
+            jlgCollectionSheetData = JLGCollectionSheetData.instance(dueDate, loanProducts, jlgGroupsData,
+                    this.attendanceDropdownReadPlatformService.retrieveAttendanceTypeOptions(), paymentOptions);
+        }
+
+        return jlgCollectionSheetData;
+    }
+
+    private static final class JLGCollectionSheetFaltDataMapper implements RowMapper<JLGCollectionSheetFlatData> {
+
+        public String collectionSheetSchema(final boolean isCenterCollection) {
+            StringBuffer sql = new StringBuffer(400);
+            sql.append("SELECT loandata.*, sum(lc.amount_outstanding_derived) as chargesDue from ")
+                    .append("(SELECT gp.display_name As groupName, ")
+                    .append("gp.id As groupId, ")
+                    .append("cl.display_name As clientName, ")
+                    .append("sf.id As staffId, ")
+                    .append("sf.display_name As staffName, ")
+                    .append("gl.id As levelId, ")
+                    .append("gl.level_name As levelName, ")
+                    .append("cl.id As clientId, ")
+                    .append("ln.id As loanId, ")
+                    .append("ln.account_no As accountId, ")
+                    .append("ln.loan_status_id As accountStatusId, ")
+                    .append("pl.short_name As productShortName, ")
+                    .append("ln.product_id As productId, ")
+                    .append("ln.currency_code as currencyCode, ln.currency_digits as currencyDigits, ln.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, ")
+                    .append("if(ln.loan_status_id = 200 , ln.principal_amount , null) As disbursementAmount, ")
+                    .append("sum(ifnull(if(ln.loan_status_id = 300, ls.principal_amount, 0.0), 0.0) - ifnull(if(ln.loan_status_id = 300, ls.principal_completed_derived, 0.0), 0.0)) As principalDue, ")
+                    .append("ln.principal_repaid_derived As principalPaid, ")
+                    .append("sum(ifnull(if(ln.loan_status_id = 300, ls.interest_amount, 0.0), 0.0) - ifnull(if(ln.loan_status_id = 300, ls.interest_completed_derived, 0.0), 0.0)) As interestDue, ")
+                    .append("ln.interest_repaid_derived As interestPaid, ")
+                    .append("ca.attendance_type_enum as attendanceTypeId ")
+                    .append("FROM m_group gp ")
+                    .append("LEFT JOIN m_office of ON of.id = gp.office_id AND of.hierarchy like :officeHierarchy ")
+                    .append("JOIN m_group_level gl ON gl.id = gp.level_Id ")
+                    .append("LEFT JOIN m_staff sf ON sf.id = gp.staff_id ")
+                    .append("JOIN m_group_client gc ON gc.group_id = gp.id ")
+                    .append("JOIN m_client cl ON cl.id = gc.client_id ")
+                    .append("LEFT JOIN m_loan ln ON cl.id = ln.client_id  and ln.group_id=gp.id AND ln.group_id is not null AND ( ln.loan_status_id = 300 ) ")
+                    .append("LEFT JOIN m_product_loan pl ON pl.id = ln.product_id ")
+                    .append("LEFT JOIN m_currency rc on rc.`code` = ln.currency_code ")
+                    .append("LEFT JOIN m_loan_repayment_schedule ls ON ls.loan_id = ln.id AND ls.completed_derived = 0 AND ls.duedate <= :dueDate ")
+                    .append("left join m_calendar_instance ci on gp.parent_id = ci.entity_id and ci.entity_type_enum =:entityTypeId ")
+                    .append("left join m_meeting mt on ci.id = mt.calendar_instance_id and mt.meeting_date =:dueDate ")
+                    .append("left join m_client_attendance ca on ca.meeting_id=mt.id and ca.client_id=cl.id ");
+
+            if (isCenterCollection) {
+                sql.append("WHERE gp.parent_id = :centerId ");
+            } else {
+                sql.append("WHERE gp.id = :groupId ");
+            }
+            sql.append("and (ln.loan_status_id != 200 AND ln.loan_status_id != 100) ");
+            
+            sql.append("and (gp.status_enum = 300 or (gp.status_enum = 600 and gp.closedon_date >= :dueDate)) ")
+                    .append("and (cl.status_enum = 300 or (cl.status_enum = 600 and cl.closedon_date >= :dueDate)) ")
+                    .append("GROUP BY gp.id ,cl.id , ln.id ORDER BY gp.id , cl.id , ln.id ").append(") loandata ")
+                    .append("LEFT JOIN m_loan_charge lc ON lc.loan_id = loandata.loanId AND lc.is_paid_derived = 0 AND lc.is_active = 1 ")
+                    .append("AND ( lc.due_for_collection_as_of_date  <= :dueDate OR lc.charge_time_enum = 1) ")
+                    .append("GROUP BY loandata.groupId, ").append("loandata.clientId, ").append("loandata.loanId ")
+                    .append("ORDER BY loandata.groupId, ").append("loandata.clientId, ").append("loandata.loanId ");
+
+            return sql.toString();
+
+        }
+
+        @Override
+        public JLGCollectionSheetFlatData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final String groupName = rs.getString("groupName");
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+            final String staffName = rs.getString("staffName");
+            final Long levelId = JdbcSupport.getLong(rs, "levelId");
+            final String levelName = rs.getString("levelName");
+            final String clientName = rs.getString("clientName");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final Long loanId = JdbcSupport.getLong(rs, "loanId");
+            final String accountId = rs.getString("accountId");
+            final Integer accountStatusId = JdbcSupport.getInteger(rs, "accountStatusId");
+            final String productShortName = rs.getString("productShortName");
+            final Long productId = JdbcSupport.getLong(rs, "productId");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            CurrencyData currencyData = null;
+            if (currencyCode != null) {
+                currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol,
+                        currencyNameCode);
+            }
+
+            final BigDecimal disbursementAmount = rs.getBigDecimal("disbursementAmount");
+            final BigDecimal principalDue = rs.getBigDecimal("principalDue");
+            final BigDecimal principalPaid = rs.getBigDecimal("principalPaid");
+            final BigDecimal interestDue = rs.getBigDecimal("interestDue");
+            final BigDecimal interestPaid = rs.getBigDecimal("interestPaid");
+            final BigDecimal chargesDue = rs.getBigDecimal("chargesDue");
+
+            final Integer attendanceTypeId = rs.getInt("attendanceTypeId");
+            final EnumOptionData attendanceType = AttendanceEnumerations.attendanceType(attendanceTypeId);
+
+            return new JLGCollectionSheetFlatData(groupName, groupId, staffId, staffName, levelId, levelName, clientName, clientId, loanId,
+                    accountId, accountStatusId, productShortName, productId, currencyData, disbursementAmount, principalDue, principalPaid,
+                    interestDue, interestPaid, chargesDue, attendanceType);
+        }
+
+    }
+
+    @Override
+    public JLGCollectionSheetData generateGroupCollectionSheet(final Long groupId, final JsonQuery query) {
+
+        this.collectionSheetGenerateCommandFromApiJsonDeserializer.validateForGenerateCollectionSheet(query.json());
+
+        final Long calendarId = query.longValueOfParameterNamed(calendarIdParamName);
+        final LocalDate transactionDate = query.localDateValueOfParameterNamed(transactionDateParamName);
+        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+        final String transactionDateStr = df.format(transactionDate.toDate());
+
+        final Calendar calendar = this.calendarRepositoryWrapper.findOneWithNotFoundDetection(calendarId);
+        // check if transaction against calendar effective from date
+
+        if (!calendar.isValidRecurringDate(transactionDate)) { throw new NotValidRecurringDateException("collectionsheet", "The date '"
+                + transactionDate + "' is not a valid meeting date.", transactionDate); }
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String officeHierarchy = hierarchy + "%";
+
+        final GroupGeneralData group = this.groupReadPlatformService.retrieveOne(groupId);
+
+        final JLGCollectionSheetFaltDataMapper mapper = new JLGCollectionSheetFaltDataMapper();
+
+        // entityType should be center if it's within a center
+        final CalendarEntityType entityType = (group.isChildGroup()) ? CalendarEntityType.CENTERS : CalendarEntityType.GROUPS;
+
+        final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", transactionDateStr)
+                .addValue("groupId", group.getId()).addValue("officeHierarchy", officeHierarchy)
+                .addValue("entityTypeId", entityType.getValue());
+
+        final Collection<JLGCollectionSheetFlatData> collectionSheetFlatDatas = this.namedParameterjdbcTemplate.query(
+                mapper.collectionSheetSchema(false), namedParameters, mapper);
+
+        // loan data for collection sheet
+        JLGCollectionSheetData collectionSheetData = buildJLGCollectionSheet(transactionDate, collectionSheetFlatDatas);
+
+        // mandatory savings data for collection sheet
+        Collection<JLGGroupData> groupsWithSavingsData = this.namedParameterjdbcTemplate.query(
+                mandatorySavingsExtractor.collectionSheetSchema(false), namedParameters, mandatorySavingsExtractor);
+
+        // merge savings data into loan data
+        mergeSavingsGroupDataIntoCollectionsheetData(groupsWithSavingsData, collectionSheetData);
+
+        collectionSheetData = JLGCollectionSheetData.withSavingsProducts(collectionSheetData,
+                retrieveSavingsProducts(groupsWithSavingsData));
+
+        return collectionSheetData;
+    }
+
+    private void mergeSavingsGroupDataIntoCollectionsheetData(final Collection<JLGGroupData> groupsWithSavingsData,
+            final JLGCollectionSheetData collectionSheetData) {
+        final List<JLGGroupData> groupsWithLoanData = (List<JLGGroupData>) collectionSheetData.getGroups();
+        for (JLGGroupData groupSavingsData : groupsWithSavingsData) {
+            if (groupsWithLoanData.contains(groupSavingsData)) {
+                mergeGroup(groupSavingsData, groupsWithLoanData);
+            } else {
+                groupsWithLoanData.add(groupSavingsData);
+            }
+        }
+
+    }
+
+    private void mergeGroup(final JLGGroupData groupSavingsData, final List<JLGGroupData> groupsWithLoanData) {
+        final int index = groupsWithLoanData.indexOf(groupSavingsData);
+
+        if (index < 0) return;
+
+        JLGGroupData groupLoanData = groupsWithLoanData.get(index);
+        List<JLGClientData> clientsLoanData = (List<JLGClientData>) groupLoanData.getClients();
+        List<JLGClientData> clientsSavingsData = (List<JLGClientData>) groupSavingsData.getClients();
+
+        for (JLGClientData clientSavingsData : clientsSavingsData) {
+            if (clientsLoanData.contains(clientSavingsData)) {
+                mergeClient(clientSavingsData, clientsLoanData);
+            } else {
+                clientsLoanData.add(clientSavingsData);
+            }
+        }
+    }
+
+    private void mergeClient(final JLGClientData clientSavingsData, List<JLGClientData> clientsLoanData) {
+        final int index = clientsLoanData.indexOf(clientSavingsData);
+
+        if (index < 0) return;
+
+        JLGClientData clientLoanData = clientsLoanData.get(index);
+        clientLoanData.setSavings(clientSavingsData.getSavings());
+    }
+
+    private Collection<SavingsProductData> retrieveSavingsProducts(Collection<JLGGroupData> groupsWithSavingsData) {
+        List<SavingsProductData> savingsProducts = new ArrayList<>();
+        for (JLGGroupData groupSavingsData : groupsWithSavingsData) {
+            Collection<JLGClientData> clientsSavingsData = groupSavingsData.getClients();
+            for (JLGClientData clientSavingsData : clientsSavingsData) {
+                Collection<SavingsDueData> savingsDatas = clientSavingsData.getSavings();
+                for (SavingsDueData savingsDueData : savingsDatas) {
+                    final SavingsProductData savingsProduct = SavingsProductData.lookup(savingsDueData.productId(),
+                            savingsDueData.productName());
+                    if (!savingsProducts.contains(savingsProduct)) {
+                        savingsProducts.add(savingsProduct);
+                    }
+                }
+            }
+        }
+        return savingsProducts;
+    }
+
+    @Override
+    public JLGCollectionSheetData generateCenterCollectionSheet(final Long centerId, final JsonQuery query) {
+
+        this.collectionSheetGenerateCommandFromApiJsonDeserializer.validateForGenerateCollectionSheet(query.json());
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String officeHierarchy = hierarchy + "%";
+
+        final CenterData center = this.centerReadPlatformService.retrieveOne(centerId);
+
+        final LocalDate transactionDate = query.localDateValueOfParameterNamed(transactionDateParamName);
+        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+        final String dueDateStr = df.format(transactionDate.toDate());
+
+        final JLGCollectionSheetFaltDataMapper mapper = new JLGCollectionSheetFaltDataMapper();
+
+        StringBuilder sql = new StringBuilder(mapper.collectionSheetSchema(true));
+
+        final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", dueDateStr)
+                .addValue("centerId", center.getId()).addValue("officeHierarchy", officeHierarchy)
+                .addValue("entityTypeId", CalendarEntityType.CENTERS.getValue());
+
+        final Collection<JLGCollectionSheetFlatData> collectionSheetFlatDatas = this.namedParameterjdbcTemplate.query(sql.toString(),
+                namedParameters, mapper);
+
+        // loan data for collection sheet
+        JLGCollectionSheetData collectionSheetData = buildJLGCollectionSheet(transactionDate, collectionSheetFlatDatas);
+
+        // mandatory savings data for collection sheet
+        Collection<JLGGroupData> groupsWithSavingsData = this.namedParameterjdbcTemplate.query(
+                mandatorySavingsExtractor.collectionSheetSchema(true), namedParameters, mandatorySavingsExtractor);
+
+        // merge savings data into loan data
+        mergeSavingsGroupDataIntoCollectionsheetData(groupsWithSavingsData, collectionSheetData);
+
+        collectionSheetData = JLGCollectionSheetData.withSavingsProducts(collectionSheetData,
+                retrieveSavingsProducts(groupsWithSavingsData));
+
+        return collectionSheetData;
+    }
+
+    private static final class MandatorySavingsCollectionsheetExtractor implements ResultSetExtractor<Collection<JLGGroupData>> {
+
+        private final GroupSavingsDataMapper groupSavingsDataMapper = new GroupSavingsDataMapper();
+
+        public String collectionSheetSchema(final boolean isCenterCollection) {
+
+            final StringBuffer sql = new StringBuffer(400);
+            sql.append("SELECT gp.display_name As groupName, ")
+                    .append("gp.id As groupId, ")
+                    .append("cl.display_name As clientName, ")
+                    .append("cl.id As clientId, ")
+                    .append("sf.id As staffId, ")
+                    .append("sf.display_name As staffName, ")
+                    .append("gl.id As levelId, ")
+                    .append("gl.level_name As levelName, ")
+                    .append("sa.id As savingsId, ")
+                    .append("sa.account_no As accountId, ")
+                    .append("sa.status_enum As accountStatusId, ")
+                    .append("sp.short_name As productShortName, ")
+                    .append("sp.id As productId, ")
+                    .append("sa.currency_code as currencyCode, ")
+                    .append("sa.currency_digits as currencyDigits, ")
+                    .append("sa.currency_multiplesof as inMultiplesOf, ")
+                    .append("rc.`name` as currencyName, ")
+                    .append("rc.display_symbol as currencyDisplaySymbol, ")
+                    .append("rc.internationalized_name_code as currencyNameCode, ")
+                    .append("sum(ifnull(mss.deposit_amount,0) - ifnull(mss.deposit_amount_completed_derived,0)) as dueAmount ")
+
+                    .append("FROM m_group gp ")
+                    .append("LEFT JOIN m_office of ON of.id = gp.office_id AND of.hierarchy like :officeHierarchy ")
+                    .append("JOIN m_group_level gl ON gl.id = gp.level_Id ")
+                    .append("LEFT JOIN m_staff sf ON sf.id = gp.staff_id ")
+                    .append("JOIN m_group_client gc ON gc.group_id = gp.id ")
+                    .append("JOIN m_client cl ON cl.id = gc.client_id ")
+                    .append("JOIN m_savings_account sa ON sa.client_id=cl.id and sa.status_enum=300 ")
+                    .append("JOIN m_savings_product sp ON sa.product_id=sp.id ")
+                    .append("JOIN m_deposit_account_recurring_detail dard ON sa.id = dard.savings_account_id AND dard.is_mandatory = true AND dard.is_calendar_inherited = true ")
+                    .append("JOIN m_mandatory_savings_schedule mss ON mss.savings_account_id=sa.id AND mss.duedate <= :dueDate ")
+                    .append("LEFT JOIN m_currency rc on rc.`code` = sa.currency_code ");
+
+            if (isCenterCollection) {
+                sql.append("WHERE gp.parent_id = :centerId ");
+            } else {
+                sql.append("WHERE gp.id = :groupId ");
+            }
+
+            sql.append("and (gp.status_enum = 300 or (gp.status_enum = 600 and gp.closedon_date >= :dueDate)) ")
+                    .append("and (cl.status_enum = 300 or (cl.status_enum = 600 and cl.closedon_date >= :dueDate)) ")
+                    .append("GROUP BY gp.id ,cl.id , sa.id ORDER BY gp.id , cl.id , sa.id ");
+
+            return sql.toString();
+        }
+
+        @Override
+        public Collection<JLGGroupData> extractData(ResultSet rs) throws SQLException, DataAccessException {
+            List<JLGGroupData> groups = new ArrayList<>();
+
+            JLGGroupData group = null;
+            int groupIndex = 0;
+            boolean isEndOfRecords = false;
+            // move cursor to first row.
+            final boolean isNotEmtyResultSet = rs.next();
+
+            if (isNotEmtyResultSet) {
+                while (!isEndOfRecords) {
+                    group = groupSavingsDataMapper.mapRowData(rs, groupIndex++);
+                    groups.add(group);
+                    isEndOfRecords = rs.isAfterLast();
+                }
+            }
+
+            return groups;
+        }
+    }
+
+    private static final class GroupSavingsDataMapper implements RowMapper<JLGGroupData> {
+
+        private final ClientSavingsDataMapper clientSavingsDataMapper = new ClientSavingsDataMapper();
+
+        private GroupSavingsDataMapper() {}
+
+        public JLGGroupData mapRowData(ResultSet rs, int rowNum) throws SQLException {
+            final List<JLGClientData> clients = new ArrayList<>();
+            final JLGGroupData group = this.mapRow(rs, rowNum);
+            final Long previousGroupId = group.getGroupId();
+
+            // first client row of new group
+            JLGClientData client = clientSavingsDataMapper.mapRowData(rs, rowNum);
+            clients.add(client);
+
+            // if its not after last row loop
+            while (!rs.isAfterLast()) {
+                final Long groupId = JdbcSupport.getLong(rs, "groupId");
+                if (previousGroupId != null && groupId.compareTo(previousGroupId) != 0) {
+                    // return for next group details
+                    return JLGGroupData.withClients(group, clients);
+                }
+                client = clientSavingsDataMapper.mapRowData(rs, rowNum);
+                clients.add(client);
+            }
+
+            return JLGGroupData.withClients(group, clients);
+        }
+
+        @Override
+        public JLGGroupData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+
+            final String groupName = rs.getString("groupName");
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+            final String staffName = rs.getString("staffName");
+            final Long levelId = JdbcSupport.getLong(rs, "levelId");
+            final String levelName = rs.getString("levelName");
+            return JLGGroupData.instance(groupId, groupName, staffId, staffName, levelId, levelName);
+        }
+    }
+
+    private static final class ClientSavingsDataMapper implements RowMapper<JLGClientData> {
+
+        private final SavingsDueDataMapper savingsDueDataMapper = new SavingsDueDataMapper();
+
+        private ClientSavingsDataMapper() {}
+
+        public JLGClientData mapRowData(ResultSet rs, int rowNum) throws SQLException {
+
+            List<SavingsDueData> savings = new ArrayList<>();
+
+            JLGClientData client = this.mapRow(rs, rowNum);
+            final Long previousClientId = client.getClientId();
+
+            // first savings row of new client record
+            SavingsDueData saving = savingsDueDataMapper.mapRow(rs, rowNum);
+            savings.add(saving);
+
+            while (rs.next()) {
+                final Long clientId = JdbcSupport.getLong(rs, "clientId");
+                if (previousClientId != null && clientId.compareTo(previousClientId) != 0) {
+                    // client id changes then return for next client data
+                    return JLGClientData.withSavings(client, savings);
+                }
+                saving = savingsDueDataMapper.mapRow(rs, rowNum);
+                savings.add(saving);
+            }
+            return JLGClientData.withSavings(client, savings);
+        }
+
+        @Override
+        public JLGClientData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+
+            final String clientName = rs.getString("clientName");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            // final Integer attendanceTypeId = rs.getInt("attendanceTypeId");
+            // final EnumOptionData attendanceType =
+            // AttendanceEnumerations.attendanceType(attendanceTypeId);
+            final EnumOptionData attendanceType = null;
+
+            return JLGClientData.instance(clientId, clientName, attendanceType);
+        }
+    }
+
+    private static final class SavingsDueDataMapper implements RowMapper<SavingsDueData> {
+
+        private SavingsDueDataMapper() {}
+
+        @Override
+        public SavingsDueData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long savingsId = rs.getLong("savingsId");
+            final String accountId = rs.getString("accountId");
+            final Integer accountStatusId = JdbcSupport.getInteger(rs, "accountStatusId");
+            final String productName = rs.getString("productShortName");
+            final Long productId = rs.getLong("productId");
+            final BigDecimal dueAmount = rs.getBigDecimal("dueAmount");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            // currency
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return SavingsDueData.instance(savingsId, accountId, accountStatusId, productName, productId, currency, dueAmount);
+        }
+    }
+
+    @Override
+    public IndividualCollectionSheetData generateIndividualCollectionSheet(final JsonQuery query) {
+
+        this.collectionSheetGenerateCommandFromApiJsonDeserializer.validateForGenerateCollectionSheetOfIndividuals(query.json());
+
+        final LocalDate transactionDate = query.localDateValueOfParameterNamed(transactionDateParamName);
+        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+        final String transactionDateStr = df.format(transactionDate.toDate());
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String officeHierarchy = hierarchy + "%";
+
+        final Long officeId = query.longValueOfParameterNamed(officeIdParamName);
+        final Long staffId = query.longValueOfParameterNamed(staffIdParamName);
+        final boolean checkForOfficeId = officeId != null;
+        final boolean checkForStaffId = staffId != null;
+
+        final IndividualCollectionSheetFaltDataMapper mapper = new IndividualCollectionSheetFaltDataMapper(checkForOfficeId,
+                checkForStaffId);
+
+        final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", transactionDateStr).addValue(
+                "officeHierarchy", officeHierarchy);
+
+        if (checkForOfficeId) {
+            ((MapSqlParameterSource) namedParameters).addValue("officeId", officeId);
+        }
+        if (checkForStaffId) {
+            ((MapSqlParameterSource) namedParameters).addValue("staffId", staffId);
+        }
+
+        final Collection<IndividualCollectionSheetLoanFlatData> collectionSheetFlatDatas = this.namedParameterjdbcTemplate.query(
+                mapper.sqlSchema(), namedParameters, mapper);
+
+        IndividualMandatorySavingsCollectionsheetExtractor mandatorySavingsExtractor = new IndividualMandatorySavingsCollectionsheetExtractor(
+                checkForOfficeId, checkForStaffId);
+        // mandatory savings data for collection sheet
+        Collection<IndividualClientData> clientData = this.namedParameterjdbcTemplate.query(
+                mandatorySavingsExtractor.collectionSheetSchema(), namedParameters, mandatorySavingsExtractor);
+
+        // merge savings data into loan data
+        mergeLoanData(collectionSheetFlatDatas, (List<IndividualClientData>) clientData);
+        
+        final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+
+        return IndividualCollectionSheetData.instance(transactionDate, clientData, paymentOptions);
+
+    }
+
+    private static final class IndividualCollectionSheetFaltDataMapper implements RowMapper<IndividualCollectionSheetLoanFlatData> {
+
+        private final String sql;
+
+        public IndividualCollectionSheetFaltDataMapper(final boolean checkForOfficeId, final boolean checkforStaffId) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("SELECT loandata.*, sum(lc.amount_outstanding_derived) as chargesDue ");
+            sb.append("from (SELECT cl.display_name As clientName, ");
+            sb.append("cl.id As clientId, ln.id As loanId, ln.account_no As accountId, ln.loan_status_id As accountStatusId,");
+            sb.append(" pl.short_name As productShortName, ln.product_id As productId, ");
+            sb.append("ln.currency_code as currencyCode, ln.currency_digits as currencyDigits, ln.currency_multiplesof as inMultiplesOf, ");
+            sb.append("rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, ");
+            sb.append("if(ln.loan_status_id = 200 , ln.principal_amount , null) As disbursementAmount, ");
+            sb.append("sum(ifnull(if(ln.loan_status_id = 300, ls.principal_amount, 0.0), 0.0) - ifnull(if(ln.loan_status_id = 300, ls.principal_completed_derived, 0.0), 0.0)) As principalDue, ");
+            sb.append("ln.principal_repaid_derived As principalPaid, ");
+            sb.append("sum(ifnull(if(ln.loan_status_id = 300, ls.interest_amount, 0.0), 0.0) - ifnull(if(ln.loan_status_id = 300, ls.interest_completed_derived, 0.0), 0.0)) As interestDue, ");
+            sb.append("ln.interest_repaid_derived As interestPaid ");
+            sb.append("FROM m_loan ln ");
+            sb.append("JOIN m_client cl ON cl.id = ln.client_id  ");
+            sb.append("LEFT JOIN m_office of ON of.id = cl.office_id  AND of.hierarchy like :officeHierarchy ");
+            sb.append("LEFT JOIN m_product_loan pl ON pl.id = ln.product_id ");
+            sb.append("LEFT JOIN m_currency rc on rc.`code` = ln.currency_code ");
+            sb.append("JOIN m_loan_repayment_schedule ls ON ls.loan_id = ln.id AND ls.completed_derived = 0 AND ls.duedate <= :dueDate ");
+            sb.append("where ");
+            if (checkForOfficeId) {
+                sb.append("of.id = :officeId and ");
+            }
+            if (checkforStaffId) {
+                sb.append("ln.loan_officer_id = :staffId and ");
+            }
+            sb.append("(ln.loan_status_id = 300) ");
+            sb.append("and ln.group_id is null GROUP BY cl.id , ln.id ORDER BY cl.id , ln.id ) loandata ");
+            sb.append("LEFT JOIN m_loan_charge lc ON lc.loan_id = loandata.loanId AND lc.is_paid_derived = 0 AND lc.is_active = 1 AND ( lc.due_for_collection_as_of_date  <= :dueDate OR lc.charge_time_enum = 1) ");
+            sb.append("GROUP BY loandata.clientId, loandata.loanId ORDER BY loandata.clientId, loandata.loanId ");
+
+            sql = sb.toString();
+        }
+
+        public String sqlSchema() {
+            return sql;
+        }
+
+        @Override
+        public IndividualCollectionSheetLoanFlatData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final String clientName = rs.getString("clientName");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final Long loanId = JdbcSupport.getLong(rs, "loanId");
+            final String accountId = rs.getString("accountId");
+            final Integer accountStatusId = JdbcSupport.getInteger(rs, "accountStatusId");
+            final String productShortName = rs.getString("productShortName");
+            final Long productId = JdbcSupport.getLong(rs, "productId");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            CurrencyData currencyData = null;
+            if (currencyCode != null) {
+                currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol,
+                        currencyNameCode);
+            }
+
+            final BigDecimal disbursementAmount = rs.getBigDecimal("disbursementAmount");
+            final BigDecimal principalDue = rs.getBigDecimal("principalDue");
+            final BigDecimal principalPaid = rs.getBigDecimal("principalPaid");
+            final BigDecimal interestDue = rs.getBigDecimal("interestDue");
+            final BigDecimal interestPaid = rs.getBigDecimal("interestPaid");
+            final BigDecimal chargesDue = rs.getBigDecimal("chargesDue");
+
+            return new IndividualCollectionSheetLoanFlatData(clientName, clientId, loanId, accountId, accountStatusId, productShortName,
+                    productId, currencyData, disbursementAmount, principalDue, principalPaid, interestDue, interestPaid, chargesDue);
+        }
+
+    }
+
+    private static final class IndividualMandatorySavingsCollectionsheetExtractor implements
+            ResultSetExtractor<Collection<IndividualClientData>> {
+
+        private final SavingsDueDataMapper savingsDueDataMapper = new SavingsDueDataMapper();
+
+        private final String sql;
+
+        public IndividualMandatorySavingsCollectionsheetExtractor(final boolean checkForOfficeId, final boolean checkforStaffId) {
+
+            final StringBuffer sb = new StringBuffer(400);
+            sb.append("SELECT cl.display_name As clientName, cl.id As clientId, ");
+            sb.append("sa.id As savingsId, sa.account_no As accountId, sa.status_enum As accountStatusId, ");
+            sb.append("sp.short_name As productShortName, sp.id As productId, ");
+            sb.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+            sb.append("rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, ");
+            sb.append("sum(ifnull(mss.deposit_amount,0) - ifnull(mss.deposit_amount_completed_derived,0)) as dueAmount ");
+            sb.append("FROM m_savings_account sa ");
+            sb.append("JOIN m_client cl ON cl.id = sa.client_id ");
+            sb.append("JOIN m_savings_product sp ON sa.product_id=sp.id ");
+            sb.append("JOIN m_deposit_account_recurring_detail dard ON sa.id = dard.savings_account_id AND dard.is_mandatory = true AND dard.is_calendar_inherited = false ");
+            sb.append("JOIN m_mandatory_savings_schedule mss ON mss.savings_account_id=sa.id AND mss.completed_derived = 0 AND mss.duedate <= :dueDate ");
+            sb.append("LEFT JOIN m_office of ON of.id = cl.office_id AND of.hierarchy like :officeHierarchy ");
+            sb.append("LEFT JOIN m_currency rc on rc.`code` = sa.currency_code ");
+            sb.append("WHERE sa.status_enum=300 and sa.group_id is null ");
+            sb.append("and (cl.status_enum = 300 or (cl.status_enum = 600 and cl.closedon_date >= :dueDate)) ");
+            if (checkForOfficeId) {
+                sb.append("and of.id = :officeId ");
+            }
+            if (checkforStaffId) {
+                sb.append("and sa.field_officer_id = :staffId ");
+            }
+            sb.append("GROUP BY cl.id , sa.id ORDER BY cl.id , sa.id ");
+
+            this.sql = sb.toString();
+        }
+
+        public String collectionSheetSchema() {
+            return this.sql;
+        }
+
+        @SuppressWarnings("null")
+        @Override
+        public Collection<IndividualClientData> extractData(ResultSet rs) throws SQLException, DataAccessException {
+            List<IndividualClientData> clientData = new ArrayList<>();
+            int rowNum = 0;
+
+            IndividualClientData client = null;
+            Long previousClientId = null;
+
+            while (rs.next()) {
+                final Long clientId = JdbcSupport.getLong(rs, "clientId");
+                if (previousClientId == null || clientId.compareTo(previousClientId) != 0) {
+                    final String clientName = rs.getString("clientName");
+                    client = IndividualClientData.instance(clientId, clientName);
+                    client = IndividualClientData.withSavings(client, new ArrayList<SavingsDueData>());
+                    clientData.add(client);
+                    previousClientId = clientId;
+                }
+                SavingsDueData saving = savingsDueDataMapper.mapRow(rs, rowNum);
+                client.addSavings(saving);
+                rowNum++;
+            }
+
+            return clientData;
+        }
+    }
+
+    private void mergeLoanData(final Collection<IndividualCollectionSheetLoanFlatData> loanFlatDatas, List<IndividualClientData> clientDatas) {
+
+        IndividualClientData clientSavingsData = null;
+        for (IndividualCollectionSheetLoanFlatData loanFlatData : loanFlatDatas) {
+            IndividualClientData clientData = loanFlatData.getClientData();
+            if (clientSavingsData == null || !clientSavingsData.equals(clientData)) {
+                if (clientDatas.contains(clientData)) {
+                    final int index = clientDatas.indexOf(clientData);
+                    if (index < 0) return;
+                    clientSavingsData = clientDatas.get(index);
+                    clientSavingsData.setLoans(new ArrayList<LoanDueData>());
+                } else {
+                    clientSavingsData = clientData;
+                    clientSavingsData.setLoans(new ArrayList<LoanDueData>());
+                    clientDatas.add(clientSavingsData);
+                }
+            }
+            clientSavingsData.addLoans(loanFlatData.getLoanDueData());
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformService.java
new file mode 100644
index 0000000..0ebe8b1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformService.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.collectionsheet.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface CollectionSheetWritePlatformService {
+
+    CommandProcessingResult updateCollectionSheet(JsonCommand command);
+
+    CommandProcessingResult saveIndividualCollectionSheet(JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..2c91ad0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,171 @@
+/**
+ * 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.collectionsheet.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
+import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
+import org.apache.fineract.portfolio.collectionsheet.data.CollectionSheetTransactionDataValidator;
+import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer;
+import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.apache.fineract.portfolio.meeting.service.MeetingWritePlatformService;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailAssembler;
+import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CollectionSheetWritePlatformServiceJpaRepositoryImpl implements CollectionSheetWritePlatformService {
+
+    private final LoanWritePlatformService loanWritePlatformService;
+    private final CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer bulkRepaymentCommandFromApiJsonDeserializer;
+    private final CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer bulkDisbursalCommandFromApiJsonDeserializer;
+    private final CollectionSheetTransactionDataValidator transactionDataValidator;
+    private final MeetingWritePlatformService meetingWritePlatformService;
+    private final DepositAccountAssembler accountAssembler;
+    private final DepositAccountWritePlatformService accountWritePlatformService;
+    private final PaymentDetailAssembler paymentDetailAssembler;
+    private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
+
+    @Autowired
+    public CollectionSheetWritePlatformServiceJpaRepositoryImpl(final LoanWritePlatformService loanWritePlatformService,
+            final CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer bulkRepaymentCommandFromApiJsonDeserializer,
+            final CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer bulkDisbursalCommandFromApiJsonDeserializer,
+            final CollectionSheetTransactionDataValidator transactionDataValidator,
+            final MeetingWritePlatformService meetingWritePlatformService, final DepositAccountAssembler accountAssembler,
+            final DepositAccountWritePlatformService accountWritePlatformService, final PaymentDetailAssembler paymentDetailAssembler, final PaymentDetailWritePlatformService paymentDetailWritePlatformService) {
+        this.loanWritePlatformService = loanWritePlatformService;
+        this.bulkRepaymentCommandFromApiJsonDeserializer = bulkRepaymentCommandFromApiJsonDeserializer;
+        this.bulkDisbursalCommandFromApiJsonDeserializer = bulkDisbursalCommandFromApiJsonDeserializer;
+        this.transactionDataValidator = transactionDataValidator;
+        this.meetingWritePlatformService = meetingWritePlatformService;
+        this.accountAssembler = accountAssembler;
+        this.accountWritePlatformService = accountWritePlatformService;
+        this.paymentDetailAssembler = paymentDetailAssembler;
+        this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult updateCollectionSheet(final JsonCommand command) {
+
+        this.transactionDataValidator.validateTransaction(command);
+
+        final Map<String, Object> changes = new HashMap<>();
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+        }
+
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+        changes.putAll(updateBulkReapayments(command, paymentDetail));
+
+        changes.putAll(updateBulkDisbursals(command));
+
+        changes.putAll(updateBulkMandatorySavingsDuePayments(command, paymentDetail));
+
+        this.meetingWritePlatformService.updateCollectionSheetAttendance(command);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(command.entityId()) //
+                .withGroupId(command.entityId()) //
+                .with(changes).with(changes).build();
+    }
+
+    @Override
+    public CommandProcessingResult saveIndividualCollectionSheet(final JsonCommand command) {
+
+        this.transactionDataValidator.validateIndividualCollectionSheet(command);
+
+        final Map<String, Object> changes = new HashMap<>();
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+        }
+
+        final PaymentDetail paymentDetail = null;
+
+        changes.putAll(updateBulkReapayments(command, paymentDetail));
+
+        changes.putAll(updateBulkDisbursals(command));
+
+        changes.putAll(updateBulkMandatorySavingsDuePayments(command, paymentDetail));
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(command.entityId()) //
+                .withGroupId(command.entityId()) //
+                .with(changes).with(changes).build();
+    }
+
+    private Map<String, Object> updateBulkReapayments(final JsonCommand command, final PaymentDetail paymentDetail) {
+        final Map<String, Object> changes = new HashMap<>();
+        final CollectionSheetBulkRepaymentCommand bulkRepaymentCommand = this.bulkRepaymentCommandFromApiJsonDeserializer
+                .commandFromApiJson(command.json(), paymentDetail);
+        changes.putAll(this.loanWritePlatformService.makeLoanBulkRepayment(bulkRepaymentCommand));
+        return changes;
+    }
+
+    private Map<String, Object> updateBulkDisbursals(final JsonCommand command) {
+        final Map<String, Object> changes = new HashMap<>();
+        final CollectionSheetBulkDisbursalCommand bulkDisbursalCommand = this.bulkDisbursalCommandFromApiJsonDeserializer
+                .commandFromApiJson(command.json());
+        changes.putAll(this.loanWritePlatformService.bulkLoanDisbursal(command, bulkDisbursalCommand, false));
+        return changes;
+    }
+
+    private Map<String, Object> updateBulkMandatorySavingsDuePayments(final JsonCommand command, final PaymentDetail paymentDetail) {
+        final Map<String, Object> changes = new HashMap<>();
+        final Collection<SavingsAccountTransactionDTO> savingsTransactions = this.accountAssembler
+                .assembleBulkMandatorySavingsAccountTransactionDTOs(command, paymentDetail);
+        List<Long> depositTransactionIds = new ArrayList<>();
+        for (SavingsAccountTransactionDTO savingsAccountTransactionDTO : savingsTransactions) {
+            try {
+                SavingsAccountTransaction savingsAccountTransaction =  this.accountWritePlatformService.mandatorySavingsAccountDeposit(savingsAccountTransactionDTO);
+                depositTransactionIds.add(savingsAccountTransaction.getId());
+            } catch (Exception e) {
+                // TODO: handle exception
+            }
+        }
+        changes.put("SavingsTransactions", depositTransactionIds);
+        return changes;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java
new file mode 100755
index 0000000..c14bb47
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java
@@ -0,0 +1,73 @@
+/**
+ * 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.common;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class BusinessEventNotificationConstants {
+
+    public static enum BUSINESS_EVENTS {
+        LOAN_APPROVED("loan_approved"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_LASTDISBURSAL("loan_undo_lastdisbursal"),LOAN_UNDO_TRANSACTION(
+                "loan_undo_transaction"), LOAN_ADJUST_TRANSACTION("loan_adjust_transaction"), LOAN_MAKE_REPAYMENT(
+                "loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF("loan_undo_writtenoff"), LOAN_DISBURSAL(
+                "loan_disbursal"), LOAN_WAIVE_INTEREST("loan_waive_interest"), LOAN_CLOSE("loan_close"), LOAN_CLOSE_AS_RESCHEDULE(
+                "loan_close_as_reschedule"), LOAN_ADD_CHARGE("loan_add_charge"), LOAN_UPDATE_CHARGE("loan_update_charge"), LOAN_WAIVE_CHARGE(
+                "loan_waive_charge"), LOAN_DELETE_CHARGE("loan_delete_charge"), LOAN_CHARGE_PAYMENT("loan_charge_payment"), LOAN_INITIATE_TRANSFER(
+                "loan_initiate_transfer"), LOAN_ACCEPT_TRANSFER("loan_accept_transfer"), LOAN_WITHDRAW_TRANSFER("loan_withdraw_transfer"), LOAN_REJECT_TRANSFER(
+                "loan_reject_transfer"), LOAN_REASSIGN_OFFICER("loan_reassign_officer"), LOAN_REMOVE_OFFICER("loan_remove_officer"), LOAN_APPLY_OVERDUE_CHARGE(
+                "loan_apply_overdue_charge"), LOAN_INTEREST_RECALCULATION("loan_interest_recalculation"), LOAN_REFUND("loan_refund");
+
+        private final String value;
+
+        private BUSINESS_EVENTS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+        static {
+            for (final BUSINESS_EVENTS type : BUSINESS_EVENTS.values()) {
+                values.add(type.value);
+            }
+        }
+
+        public static Set<String> getAllValues() {
+            return values;
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+    public static enum BUSINESS_ENTITY {
+        LOAN("loan"), LOAN_TRANSACTION("loan_transaction"), LOAN_CHARGE("loan_charge"), LOAN_ADJUSTED_TRANSACTION(
+                "loan_adjusted_transaction");
+
+        private final String value;
+
+        private BUSINESS_ENTITY(final String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/ConditionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/ConditionType.java
new file mode 100755
index 0000000..1784e82
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/ConditionType.java
@@ -0,0 +1,97 @@
+/**
+ * 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.common.domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public enum ConditionType {
+
+    INVALID(0, "ConditionType.invalid"), //
+    LESSTHAN(1, "ConditionType.lessthan"), //
+    EQUAL(2, "ConditionType.equal"), //
+    GRETERTHAN(3, "ConditionType.greterthan"), //
+    NOT_EQUAL(4, "ConditionType.notequal");//
+
+    private final Integer value;
+    private final String code;
+
+    private static final Map<Integer, ConditionType> intToEnumMap = new HashMap<>();
+    static {
+        for (final ConditionType type : ConditionType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static ConditionType fromInt(final Integer ruleTypeValue) {
+        final ConditionType type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private ConditionType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString().replaceAll("_", " ");
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isConditionTypeEqual() {
+        return ConditionType.EQUAL.getValue().equals(this.value);
+    }
+
+    public boolean isConditionTypeGreterThan() {
+        return ConditionType.GRETERTHAN.getValue().equals(this.value);
+    }
+
+    public boolean isConditionTypeNotEqual() {
+        return ConditionType.NOT_EQUAL.getValue().equals(this.value);
+    }
+
+    public boolean isConditionTypeLessThan() {
+        return ConditionType.LESSTHAN.getValue().equals(this.value);
+    }
+
+    public boolean isInvalid() {
+        return ConditionType.INVALID.getValue().equals(this.value);
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final ConditionType enumType : values()) {
+            if (!enumType.isInvalid()) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/DayOfWeekType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/DayOfWeekType.java
new file mode 100755
index 0000000..7b44585
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/DayOfWeekType.java
@@ -0,0 +1,82 @@
+/**
+ * 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.common.domain;
+
+import org.joda.time.DateTimeConstants;
+
+public enum DayOfWeekType {
+
+	MONDAY(DateTimeConstants.MONDAY, "weekDayType.monday"),
+	TUESDAY(DateTimeConstants.TUESDAY, "weekDayType.tuesday"),
+	WEDNESDAY(DateTimeConstants.WEDNESDAY, "weekDayType.wednesday"),
+	THURSDAY(DateTimeConstants.THURSDAY, "weekDayType.thursday"),
+	FRIDAY(DateTimeConstants.FRIDAY, "weekDayType.friday"),
+	SATURDAY(DateTimeConstants.SATURDAY, "weekDayType.saturday"),
+	SUNDAY(DateTimeConstants.SUNDAY, "weekDayType.sunday"),
+	INVALID(0, "weekDayType.invalid");
+	
+	private final Integer value;
+    private final String code;
+	
+    private DayOfWeekType(Integer value, String code) {
+		this.value = value;
+		this.code = code;
+	}
+
+	public Integer getValue() {
+		return this.value;
+	}
+
+	public String getCode() {
+		return this.code;
+	}
+	
+	public static DayOfWeekType fromInt(final Integer dayOfWeek) {
+		DayOfWeekType weekDayType = INVALID;
+        if (dayOfWeek != null) {
+            switch (dayOfWeek) {
+                case 1:
+                	weekDayType = MONDAY;
+                break;
+                case 2:
+                	weekDayType = TUESDAY;
+                break;
+                case 3:
+                	weekDayType = WEDNESDAY;
+                break;
+                case 4:
+                	weekDayType = THURSDAY;
+                break;
+                case 5:
+                	weekDayType = FRIDAY;
+                break;
+                case 6:
+                	weekDayType = SATURDAY;
+                break;
+                case 7:
+                	weekDayType = SUNDAY;
+                break;
+                default:
+                break;
+            }
+        }
+        return weekDayType;
+    }
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInMonthType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInMonthType.java
new file mode 100755
index 0000000..2910eae
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInMonthType.java
@@ -0,0 +1,83 @@
+/**
+ * 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.common.domain;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <ul>
+ * People typically use either of the following settings when calculating there
+ * interest using the daily method:
+ * <li>Actual or</li>
+ * <li>30</li>
+ * </ul>
+ */
+public enum DaysInMonthType {
+
+    INVALID(0, "DaysInMonthType.invalid"), //
+    ACTUAL(1, "DaysInMonthType.actual"), //
+    DAYS_30(30, "DaysInMonthType.days360");
+
+    private final Integer value;
+    private final String code;
+
+    private DaysInMonthType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final DaysInMonthType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public static DaysInMonthType fromInt(final Integer type) {
+        DaysInMonthType repaymentFrequencyType = DaysInMonthType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 1:
+                    repaymentFrequencyType = DaysInMonthType.ACTUAL;
+                break;
+                case 30:
+                    repaymentFrequencyType = DaysInMonthType.DAYS_30;
+                break;
+
+            }
+        }
+        return repaymentFrequencyType;
+    }
+
+    public boolean isDaysInMonth_30() {
+        return DaysInMonthType.DAYS_30.getValue().equals(this.value);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInYearType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInYearType.java
new file mode 100755
index 0000000..81c8b86
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInYearType.java
@@ -0,0 +1,93 @@
+/**
+ * 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.common.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <ul>
+ * People typically use either of the following settings when calculating there
+ * interest using the daily method:
+ * <li>Actual or</li>
+ * <li>360 or</li>
+ * <li>364 or</li>
+ * <li>365</li>
+ * </ul>
+ */
+public enum DaysInYearType {
+
+    INVALID(0, "DaysInYearType.invalid"), //
+    ACTUAL(1, "DaysInYearType.actual"), //
+    DAYS_360(360, "DaysInYearType.days360"), //
+    DAYS_364(364, "DaysInYearType.days364"), //
+    DAYS_365(365, "DaysInYearType.days365");
+
+    private final Integer value;
+    private final String code;
+
+    private DaysInYearType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final DaysInYearType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public static DaysInYearType fromInt(final Integer type) {
+        DaysInYearType repaymentFrequencyType = DaysInYearType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 1:
+                    repaymentFrequencyType = DaysInYearType.ACTUAL;
+                break;
+                case 360:
+                    repaymentFrequencyType = DaysInYearType.DAYS_360;
+                break;
+                case 364:
+                    repaymentFrequencyType = DaysInYearType.DAYS_364;
+                break;
+                case 365:
+                    repaymentFrequencyType = DaysInYearType.DAYS_365;
+                break;
+            }
+        }
+        return repaymentFrequencyType;
+    }
+
+    public boolean isActual() {
+        return DaysInYearType.ACTUAL.getValue().equals(this.value);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java
new file mode 100755
index 0000000..d32b218
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java
@@ -0,0 +1,72 @@
+/**
+ * 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.common.domain;
+
+public enum NthDayType {
+	
+	ONE(1,"nthDayType.one"),
+	TWO(2,"nthDayType.two"),
+	THREE(3,"nthDayType.three"),
+	FOUR(4,"nthDayType.four"),
+	FIVE(5,"nthDayType.five"),
+	INVALID(0,"nthDayType.invalid");
+	
+	private final Integer value;
+    private final String code;
+	
+    private NthDayType(Integer value, String code) {
+		this.value = value;
+		this.code = code;
+	}
+
+	public Integer getValue() {
+		return this.value;
+	}
+
+	public String getCode() {
+		return this.code;
+	}
+	
+	public static NthDayType fromInt(final Integer frequency) {
+		NthDayType repaymentFrequencyNthDayType = NthDayType.INVALID;
+        if (frequency != null) {
+            switch (frequency) {
+                case 1:
+                    repaymentFrequencyNthDayType = NthDayType.ONE;
+                break;
+                case 2:
+                    repaymentFrequencyNthDayType = NthDayType.TWO;
+                break;
+                case 3:
+                    repaymentFrequencyNthDayType = NthDayType.THREE;
+                break;
+                case 4:
+                    repaymentFrequencyNthDayType = NthDayType.FOUR;
+                break;
+                case 5:
+                    repaymentFrequencyNthDayType = NthDayType.FIVE;
+                break;
+                default:
+                break;
+            }
+        }
+        return repaymentFrequencyNthDayType;
+    }
+   
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/PeriodFrequencyType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/PeriodFrequencyType.java
new file mode 100644
index 0000000..fba6207
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/PeriodFrequencyType.java
@@ -0,0 +1,99 @@
+/**
+ * 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.common.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public enum PeriodFrequencyType {
+    DAYS(0, "periodFrequencyType.days"), //
+    WEEKS(1, "periodFrequencyType.weeks"), //
+    MONTHS(2, "periodFrequencyType.months"), //
+    YEARS(3, "periodFrequencyType.years"), //
+    INVALID(4, "periodFrequencyType.invalid");
+
+    private final Integer value;
+    private final String code;
+
+    private PeriodFrequencyType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static PeriodFrequencyType fromInt(final Integer frequency) {
+        PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.INVALID;
+        if (frequency != null) {
+            switch (frequency) {
+                case 0:
+                    repaymentFrequencyType = PeriodFrequencyType.DAYS;
+                break;
+                case 1:
+                    repaymentFrequencyType = PeriodFrequencyType.WEEKS;
+                break;
+                case 2:
+                    repaymentFrequencyType = PeriodFrequencyType.MONTHS;
+                break;
+                case 3:
+                    repaymentFrequencyType = PeriodFrequencyType.YEARS;
+                break;
+            }
+        }
+        return repaymentFrequencyType;
+    }
+
+    public boolean isMonthly() {
+        return this.value.equals(PeriodFrequencyType.MONTHS.getValue());
+    }
+
+    public boolean isYearly() {
+        return this.value.equals(PeriodFrequencyType.YEARS.getValue());
+    }
+    
+    public boolean isWeekly() {
+        return this.value.equals(PeriodFrequencyType.WEEKS.getValue());
+    }
+
+    public boolean isDaily() {
+        return this.value.equals(PeriodFrequencyType.DAYS.getValue());
+    }
+    
+    public boolean isInvalid() {
+        return this.value.equals(PeriodFrequencyType.INVALID.getValue());
+    }
+    
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final PeriodFrequencyType enumType : values()) {
+            if (!enumType.isInvalid()) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/BusinessEventListner.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/BusinessEventListner.java
new file mode 100755
index 0000000..69d8a8b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/BusinessEventListner.java
@@ -0,0 +1,43 @@
+/**
+ * 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.common.service;
+
+import java.util.Map;
+
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+
+/**
+ * The interface to be implemented by classes that want to be informed when a
+ * Business Event executes. example: on completion of loan approval event need
+ * to block guarantor funds
+ * 
+ */
+public interface BusinessEventListner {
+
+    /**
+     * Implement this method for notifications before executing Business Event
+     */
+    public void businessEventToBeExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity);
+
+    /**
+     * Implement this method for notifications after executing Business Event
+     */
+    public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/BusinessEventNotifierService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/BusinessEventNotifierService.java
new file mode 100755
index 0000000..72d7377
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/BusinessEventNotifierService.java
@@ -0,0 +1,57 @@
+/**
+ * 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.common.service;
+
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
+
+import java.util.Map;
+
+/**
+ * Implemented class is responsible for notifying the business event to
+ * registered listeners.
+ * 
+ */
+public interface BusinessEventNotifierService {
+
+    /**
+     * Method should be called to notify listeners before Business event
+     * execution for any pre-processing of event
+     */
+    public void notifyBusinessEventToBeExecuted(BUSINESS_EVENTS businessEvent, Map<BUSINESS_ENTITY, Object> businessEventEntity);
+
+    /**
+     * Method should be called to notify listeners after Business event
+     * execution for any post-processing of event
+     */
+    public void notifyBusinessEventWasExecuted(BUSINESS_EVENTS businessEvent, Map<BUSINESS_ENTITY, Object> businessEventEntity);
+
+    /**
+     * Method is to register a class as listener for pre-processing of any
+     * Business event
+     */
+    public void addBusinessEventPreListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner);
+
+    /**
+     * Method is to register a class as listener for post-processing of any
+     * Business event
+     */
+    public void addBusinessEventPostListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/BusinessEventNotifierServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/BusinessEventNotifierServiceImpl.java
new file mode 100755
index 0000000..e6499ac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/BusinessEventNotifierServiceImpl.java
@@ -0,0 +1,116 @@
+/**
+ * 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.common.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
+import org.springframework.stereotype.Service;
+
+@Service
+public class BusinessEventNotifierServiceImpl implements BusinessEventNotifierService {
+
+    private final Map<BUSINESS_EVENTS, List<BusinessEventListner>> preListners = new HashMap<>(5);
+    private final Map<BUSINESS_EVENTS, List<BusinessEventListner>> postListners = new HashMap<>(5);
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.apache.fineract.portfolio.common.service.BusinessEventNotifierService
+     * #notifyBusinessEventToBeExecuted
+     * (org.apache.fineract.portfolio.common.BusinessEventNotificationConstants
+     * .BUSINESS_EVENTS,
+     * org.springframework.data.jpa.domain.AbstractPersistable)
+     */
+    @Override
+    public void notifyBusinessEventToBeExecuted(BUSINESS_EVENTS businessEvent, Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+        List<BusinessEventListner> businessEventListners = this.preListners.get(businessEvent);
+        if (businessEventListners != null) {
+            for (BusinessEventListner eventListner : businessEventListners) {
+                eventListner.businessEventToBeExecuted(businessEventEntity);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.apache.fineract.portfolio.common.service.BusinessEventNotifierService
+     * #notifyBusinessEventWasExecuted
+     * (org.apache.fineract.portfolio.common.BusinessEventNotificationConstants
+     * .BUSINESS_EVENTS,
+     * org.springframework.data.jpa.domain.AbstractPersistable)
+     */
+    @Override
+    public void notifyBusinessEventWasExecuted(BUSINESS_EVENTS businessEvent, Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+        List<BusinessEventListner> businessEventListners = this.postListners.get(businessEvent);
+        if (businessEventListners != null) {
+            for (BusinessEventListner eventListner : businessEventListners) {
+                eventListner.businessEventWasExecuted(businessEventEntity);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.apache.fineract.portfolio.common.service.BusinessEventNotifierService
+     * #addBusinessEventPreListners
+     * (org.apache.fineract.portfolio.common.BusinessEventNotificationConstants
+     * .BUSINESS_EVENTS,
+     * org.apache.fineract.portfolio.common.service.BusinessEventListner)
+     */
+    @Override
+    public void addBusinessEventPreListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner) {
+        addBusinessEventListners(businessEvent, businessEventListner, preListners);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.apache.fineract.portfolio.common.service.BusinessEventNotifierService
+     * #addBusinessEventPostListners
+     * (org.apache.fineract.portfolio.common.BusinessEventNotificationConstants
+     * .BUSINESS_EVENTS,
+     * org.apache.fineract.portfolio.common.service.BusinessEventListner)
+     */
+    @Override
+    public void addBusinessEventPostListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner) {
+        addBusinessEventListners(businessEvent, businessEventListner, postListners);
+    }
+
+    private void addBusinessEventListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner,
+            final Map<BUSINESS_EVENTS, List<BusinessEventListner>> businessEventListnerMap) {
+        List<BusinessEventListner> businessEventListners = businessEventListnerMap.get(businessEvent);
+        if (businessEventListners == null) {
+            businessEventListners = new ArrayList<>();
+            businessEventListnerMap.put(businessEvent, businessEventListners);
+        }
+        businessEventListners.add(businessEventListner);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/CommonEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/CommonEnumerations.java
new file mode 100755
index 0000000..735122f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/CommonEnumerations.java
@@ -0,0 +1,167 @@
+/**
+ * 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.common.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.common.domain.ConditionType;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+
+public class CommonEnumerations {
+    
+    public static final String DAYS_IN_MONTH_TYPE = "daysInMonthType";
+    public static final String DAYS_IN_YEAR_TYPE = "daysInYearType";
+
+    public static EnumOptionData commonEnumueration(final String typeName, final int id) {
+        EnumOptionData enumData = null;
+        if (typeName.equals(DAYS_IN_MONTH_TYPE)) {
+            enumData = daysInMonthType(id);
+        } else if (typeName.equals(DAYS_IN_YEAR_TYPE)) {
+            enumData = daysInYearType(id);
+        }
+        return enumData;
+    }
+
+    public static EnumOptionData termFrequencyType(final int id, final String codePrefix) {
+        return termFrequencyType(PeriodFrequencyType.fromInt(id), codePrefix);
+    }
+
+    public static EnumOptionData termFrequencyType(final PeriodFrequencyType type, final String codePrefix) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case DAYS:
+                optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+            default:
+                optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(), PeriodFrequencyType.INVALID.getCode(),
+                        "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData conditionType(final int id, final String codePrefix) {
+        return conditionType(ConditionType.fromInt(id), codePrefix);
+    }
+
+    public static EnumOptionData conditionType(final ConditionType type, final String codePrefix) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case EQUAL:
+                optionData = new EnumOptionData(ConditionType.EQUAL.getValue().longValue(), codePrefix + ConditionType.EQUAL.getCode(),
+                        "equal");
+            break;
+            case NOT_EQUAL:
+                optionData = new EnumOptionData(ConditionType.NOT_EQUAL.getValue().longValue(), codePrefix
+                        + ConditionType.NOT_EQUAL.getCode(), "notEqual");
+            break;
+            case GRETERTHAN:
+                optionData = new EnumOptionData(ConditionType.GRETERTHAN.getValue().longValue(), codePrefix
+                        + ConditionType.GRETERTHAN.getCode(), "greterthan");
+            break;
+            case LESSTHAN:
+                optionData = new EnumOptionData(ConditionType.LESSTHAN.getValue().longValue(), codePrefix
+                        + ConditionType.LESSTHAN.getCode(), "lessthan");
+            break;
+            default:
+                optionData = new EnumOptionData(ConditionType.INVALID.getValue().longValue(), ConditionType.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static List<EnumOptionData> conditionType(final ConditionType[] conditionTypes, final String codePrefix) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final ConditionType conditionType : conditionTypes) {
+            if (!conditionType.isInvalid()) {
+                optionDatas.add(conditionType(conditionType, codePrefix));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData daysInMonthType(final int id) {
+        return daysInMonthType(DaysInMonthType.fromInt(id));
+    }
+
+    public static EnumOptionData daysInMonthType(final DaysInMonthType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case ACTUAL:
+                optionData = new EnumOptionData(DaysInMonthType.ACTUAL.getValue().longValue(), DaysInMonthType.ACTUAL.getCode(), "Actual");
+            break;
+            case DAYS_30:
+                optionData = new EnumOptionData(DaysInMonthType.DAYS_30.getValue().longValue(), DaysInMonthType.DAYS_30.getCode(),
+                        "30 Days");
+            break;
+            default:
+                optionData = new EnumOptionData(DaysInMonthType.INVALID.getValue().longValue(), DaysInMonthType.INVALID.getCode(),
+                        "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData daysInYearType(final int id) {
+        return daysInYearType(DaysInYearType.fromInt(id));
+    }
+
+    public static EnumOptionData daysInYearType(final DaysInYearType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case ACTUAL:
+                optionData = new EnumOptionData(DaysInYearType.ACTUAL.getValue().longValue(), DaysInYearType.ACTUAL.getCode(), "Actual");
+            break;
+            case DAYS_360:
+                optionData = new EnumOptionData(DaysInYearType.DAYS_360.getValue().longValue(), DaysInYearType.DAYS_360.getCode(),
+                        "360 Days");
+            break;
+            case DAYS_364:
+                optionData = new EnumOptionData(DaysInYearType.DAYS_364.getValue().longValue(), DaysInYearType.DAYS_364.getCode(),
+                        "364 Days");
+            break;
+            case DAYS_365:
+                optionData = new EnumOptionData(DaysInYearType.DAYS_365.getValue().longValue(), DaysInYearType.DAYS_365.getCode(),
+                        "365 Days");
+            break;
+            default:
+                optionData = new EnumOptionData(DaysInYearType.INVALID.getValue().longValue(), DaysInYearType.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/DropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/DropdownReadPlatformService.java
new file mode 100755
index 0000000..d0f4344
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/DropdownReadPlatformService.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.portfolio.common.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public interface DropdownReadPlatformService {
+
+    List<EnumOptionData> retrievePeriodFrequencyTypeOptions();
+
+    List<EnumOptionData> retrieveConditionTypeOptions();
+
+    List<EnumOptionData> retrieveDaysInMonthTypeOptions();
+
+    List<EnumOptionData> retrieveDaysInYearTypeOptions();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/DropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/DropdownReadPlatformServiceImpl.java
new file mode 100755
index 0000000..948328d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/service/DropdownReadPlatformServiceImpl.java
@@ -0,0 +1,71 @@
+/**
+ * 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.common.service;
+
+import static org.apache.fineract.portfolio.common.service.CommonEnumerations.conditionType;
+import static org.apache.fineract.portfolio.common.service.CommonEnumerations.daysInMonthType;
+import static org.apache.fineract.portfolio.common.service.CommonEnumerations.daysInYearType;
+import static org.apache.fineract.portfolio.common.service.CommonEnumerations.termFrequencyType;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.common.domain.ConditionType;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DropdownReadPlatformServiceImpl implements DropdownReadPlatformService {
+
+    @Override
+    public List<EnumOptionData> retrievePeriodFrequencyTypeOptions() {
+        final List<EnumOptionData> loanTermFrequencyOptions = Arrays.asList(termFrequencyType(PeriodFrequencyType.DAYS, "frequency"),
+                termFrequencyType(PeriodFrequencyType.WEEKS, "frequency"), termFrequencyType(PeriodFrequencyType.MONTHS, "frequency"),
+                termFrequencyType(PeriodFrequencyType.YEARS, "frequency"));
+        return loanTermFrequencyOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveConditionTypeOptions() {
+        final List<EnumOptionData> loanTermFrequencyOptions = Arrays.asList(conditionType(ConditionType.EQUAL, "condition"),
+                conditionType(ConditionType.NOT_EQUAL, "condition"), conditionType(ConditionType.GRETERTHAN, "condition"),
+                conditionType(ConditionType.LESSTHAN, "condition"));
+        return loanTermFrequencyOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveDaysInMonthTypeOptions() {
+
+        final List<EnumOptionData> daysInMonthTypeOptions = Arrays.asList(daysInMonthType(DaysInMonthType.ACTUAL),
+                daysInMonthType(DaysInMonthType.DAYS_30));
+        return daysInMonthTypeOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveDaysInYearTypeOptions() {
+
+        final List<EnumOptionData> daysInYearTypeOptions = Arrays.asList(daysInYearType(DaysInYearType.ACTUAL),
+                daysInYearType(DaysInYearType.DAYS_360), daysInYearType(DaysInYearType.DAYS_364), daysInYearType(DaysInYearType.DAYS_365));
+        return daysInYearTypeOptions;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/api/FloatingRatesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/api/FloatingRatesApiResource.java
new file mode 100644
index 0000000..d94c116
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/api/FloatingRatesApiResource.java
@@ -0,0 +1,142 @@
+/**
+ * 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.floatingrates.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.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
+import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/floatingrates")
+@Component
+@Scope("singleton")
+public class FloatingRatesApiResource {
+
+	private static final String RESOURCE_NAME = "FLOATINGRATE";
+	private static final Set<String> LIST_FLOATING_RATES_PARAMETERS = new HashSet<>(
+			Arrays.asList("id", "name", "isBaseLendingRate", "isActive",
+					"createdby", "createdOn", "modifiedBy", "modifiedOn"));
+	private static final Set<String> INDIVIDUAL_FLOATING_RATES_PARAMETERS = new HashSet<>(
+			Arrays.asList("id", "name", "isBaseLendingRate", "isActive",
+					"createdBy", "createdOn", "modifiedBy", "modifiedOn",
+					"ratePeriods"));
+	private final PlatformSecurityContext context;
+	private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+	private final DefaultToApiJsonSerializer<FloatingRateData> toApiJsonSerializer;
+	private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
+	private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+	@Autowired
+	public FloatingRatesApiResource(
+			final PlatformSecurityContext context,
+			final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+			final DefaultToApiJsonSerializer<FloatingRateData> toApiJsonSerializer,
+			final ApiRequestParameterHelper apiRequestParameterHelper,
+			final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
+		this.context = context;
+		this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+		this.toApiJsonSerializer = toApiJsonSerializer;
+		this.apiRequestParameterHelper = apiRequestParameterHelper;
+		this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
+	}
+
+	@POST
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String createFloatingRate(final String apiRequestBodyAsJson) {
+
+		final CommandWrapper commandRequest = new CommandWrapperBuilder()
+				.createFloatingRate().withJson(apiRequestBodyAsJson).build();
+		final CommandProcessingResult result = this.commandsSourceWritePlatformService
+				.logCommandSource(commandRequest);
+		return this.toApiJsonSerializer.serialize(result);
+	}
+
+	@GET
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveAll(@Context final UriInfo uriInfo) {
+		this.context.authenticatedUser().validateHasReadPermission(
+				RESOURCE_NAME);
+		final List<FloatingRateData> floatingRates = this.floatingRatesReadPlatformService
+				.retrieveAll();
+		final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+				.process(uriInfo.getQueryParameters());
+		return this.toApiJsonSerializer.serialize(settings, floatingRates,
+				FloatingRatesApiResource.LIST_FLOATING_RATES_PARAMETERS);
+	}
+
+	@GET
+	@Path("{floatingRateId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveOne(
+			@PathParam("floatingRateId") final Long floatingRateId,
+			@Context final UriInfo uriInfo) {
+		this.context.authenticatedUser().validateHasReadPermission(
+				RESOURCE_NAME);
+		final FloatingRateData floatingRates = this.floatingRatesReadPlatformService
+				.retrieveOne(floatingRateId);
+		final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+				.process(uriInfo.getQueryParameters());
+		return this.toApiJsonSerializer.serialize(settings, floatingRates,
+				FloatingRatesApiResource.INDIVIDUAL_FLOATING_RATES_PARAMETERS);
+	}
+
+	@PUT
+	@Path("{floatingRateId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String updateFloatingRate(
+			@PathParam("floatingRateId") final Long floatingRateId,
+			final String apiRequestBodyAsJson) {
+		final CommandWrapper commandRequest = new CommandWrapperBuilder()
+				.updateFloatingRate(floatingRateId)
+				.withJson(apiRequestBodyAsJson).build();
+		final CommandProcessingResult result = this.commandsSourceWritePlatformService
+				.logCommandSource(commandRequest);
+		return this.toApiJsonSerializer.serialize(result);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateDTO.java
new file mode 100644
index 0000000..008e29f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateDTO.java
@@ -0,0 +1,75 @@
+/**
+ * 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.floatingrates.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.joda.time.LocalDate;
+
+public class FloatingRateDTO {
+
+	private final boolean isFloatingInterestRate;
+	private final LocalDate startDate;
+	private BigDecimal interestRateDiff;
+	private final Collection<FloatingRatePeriodData> baseLendingRatePeriods;
+
+	public FloatingRateDTO(final boolean isFloatingInterestRate,
+			final LocalDate startDate, final BigDecimal interestRateDiff,
+			final Collection<FloatingRatePeriodData> baseLendingRatePeriods) {
+		this.isFloatingInterestRate = isFloatingInterestRate;
+		this.startDate = startDate;
+		this.interestRateDiff = interestRateDiff;
+		this.baseLendingRatePeriods = baseLendingRatePeriods;
+	}
+
+	public BigDecimal fetchBaseRate(LocalDate date) {
+		BigDecimal rate = null;
+		for (FloatingRatePeriodData periodData : this.baseLendingRatePeriods) {
+			final LocalDate periodFromDate = new LocalDate(periodData.getFromDate());
+			if (periodFromDate.isBefore(date)
+					|| periodFromDate.isEqual(date)) {
+				rate = periodData.getInterestRate();
+				break;
+			}
+		}
+		return rate;
+	}
+
+	public void addInterestRateDiff(final BigDecimal diff) {
+		this.interestRateDiff = this.interestRateDiff.add(diff);
+	}
+
+	public boolean isFloatingInterestRate() {
+		return this.isFloatingInterestRate;
+	}
+
+	public LocalDate getStartDate() {
+		return this.startDate;
+	}
+
+	public BigDecimal getInterestRateDiff() {
+		return this.interestRateDiff;
+	}
+
+	public Collection<FloatingRatePeriodData> getBaseLendingRatePeriods() {
+		return this.baseLendingRatePeriods;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateData.java
new file mode 100644
index 0000000..ec64b03
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateData.java
@@ -0,0 +1,145 @@
+/**
+ * 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.floatingrates.data;
+
+import java.util.List;
+
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.joda.time.LocalDate;
+
+public class FloatingRateData implements Comparable<FloatingRateData> {
+
+	private final Long id;
+	private final String name;
+	private final boolean isBaseLendingRate;
+	private final boolean isActive;
+	private final String createdBy;
+	private final LocalDate createdOn;
+	private final String modifiedBy;
+	private final LocalDate modifiedOn;
+	private final List<FloatingRatePeriodData> ratePeriods;
+	@SuppressWarnings("unused")
+	private final List<EnumOptionData> interestRateFrequencyTypeOptions;
+
+	public FloatingRateData(Long id, String name, boolean isBaseLendingRate,
+			boolean isActive, String createdBy, LocalDate createdOn,
+			String modifiedBy, LocalDate modifiedOn,
+			List<FloatingRatePeriodData> ratePeriods,
+			List<EnumOptionData> interestRateFrequencyTypeOptions) {
+		this.id = id;
+		this.name = name;
+		this.isBaseLendingRate = isBaseLendingRate;
+		this.isActive = isActive;
+		this.createdBy = createdBy;
+		this.createdOn = createdOn;
+		this.modifiedBy = modifiedBy;
+		this.modifiedOn = modifiedOn;
+		this.ratePeriods = ratePeriods;
+		this.interestRateFrequencyTypeOptions = interestRateFrequencyTypeOptions;
+	}
+
+	public Long getId() {
+		return this.id;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	public boolean isBaseLendingRate() {
+		return this.isBaseLendingRate;
+	}
+
+	public boolean isActive() {
+		return this.isActive;
+	}
+
+	public String getCreatedBy() {
+		return this.createdBy;
+	}
+
+	public LocalDate getCreatedOn() {
+		return this.createdOn;
+	}
+
+	public String getModifiedBy() {
+		return this.modifiedBy;
+	}
+
+	public LocalDate getModifiedOn() {
+		return this.modifiedOn;
+	}
+
+	public List<FloatingRatePeriodData> getRatePeriods() {
+		return this.ratePeriods;
+	}
+
+	@Override
+	public int compareTo(final FloatingRateData obj) {
+		if (obj == null) {
+			return -1;
+		}
+		return new CompareToBuilder() //
+				.append(this.id, obj.id) //
+				.append(this.name, obj.name) //
+				.append(this.isBaseLendingRate, obj.isBaseLendingRate) //
+				.append(this.isActive, obj.isActive) //
+				.toComparison();
+	}
+
+	@Override
+	public boolean equals(final Object obj) {
+		if (obj == null) {
+			return false;
+		}
+		if (obj == this) {
+			return true;
+		}
+		if (obj.getClass() != getClass()) {
+			return false;
+		}
+		final FloatingRateData rhs = (FloatingRateData) obj;
+		return new EqualsBuilder() //
+				.append(this.id, rhs.id) //
+				.append(this.name, rhs.name) //
+				.append(this.isBaseLendingRate, rhs.isBaseLendingRate) //
+				.append(this.isActive, rhs.isActive) //
+				.isEquals();
+	}
+
+	@Override
+	public int hashCode() {
+		return new HashCodeBuilder(17, 37) //
+				.append(this.id) //
+				.append(this.name) //
+				.append(this.isBaseLendingRate) //
+				.append(this.isActive) //
+				.toHashCode();
+	}
+
+	public static FloatingRateData toTemplate(
+			List<EnumOptionData> interestRateFrequencyTypeOptions) {
+		// TODO Auto-generated method stub
+		return new FloatingRateData(null, null, false, true, null, null, null,
+				null, null, interestRateFrequencyTypeOptions);
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRatePeriodData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRatePeriodData.java
new file mode 100644
index 0000000..d7b03b5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRatePeriodData.java
@@ -0,0 +1,135 @@
+/**
+ * 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.floatingrates.data;
+
+import java.math.BigDecimal;
+
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.joda.time.LocalDate;
+
+public class FloatingRatePeriodData implements
+		Comparable<FloatingRatePeriodData> {
+
+	private Long id;
+	private LocalDate fromDate;
+	private BigDecimal interestRate;
+	private boolean isDifferentialToBaseLendingRate;
+	private boolean isActive;
+	private String createdBy;
+	private LocalDate createdOn;
+	private String modifiedBy;
+	private LocalDate modifiedOn;
+
+	public FloatingRatePeriodData(Long id, LocalDate fromDate,
+			BigDecimal interestRate, boolean isDifferentialToBaseLendingRate,
+			boolean isActive, String createdBy, LocalDate createdOn,
+			String modifiedBy, LocalDate modifiedOn) {
+		this.id = id;
+		this.fromDate = fromDate;
+		this.interestRate = interestRate;
+		this.isDifferentialToBaseLendingRate = isDifferentialToBaseLendingRate;
+		this.isActive = isActive;
+		this.createdBy = createdBy;
+		this.createdOn = createdOn;
+		this.modifiedBy = modifiedBy;
+		this.modifiedOn = modifiedOn;
+	}
+
+	public Long getId() {
+		return this.id;
+	}
+
+	public LocalDate getFromDate() {
+		return this.fromDate;
+	}
+
+	public LocalDate getFromDateAsLocalDate() {
+		return new LocalDate(this.fromDate);
+	}
+
+	public BigDecimal getInterestRate() {
+		return this.interestRate;
+	}
+
+	public boolean isDifferentialToBaseLendingRate() {
+		return this.isDifferentialToBaseLendingRate;
+	}
+
+	public boolean isActive() {
+		return this.isActive;
+	}
+
+	public String getCreatedBy() {
+		return this.createdBy;
+	}
+
+	public LocalDate getCreatedOn() {
+		return this.createdOn;
+	}
+
+	public String getModifiedBy() {
+		return this.modifiedBy;
+	}
+
+	public LocalDate getModifiedOn() {
+		return this.modifiedOn;
+	}
+
+	@Override
+	public int compareTo(final FloatingRatePeriodData obj) {
+		if (obj == null) {
+			return -1;
+		}
+		return new CompareToBuilder() //
+				.append(this.id, obj.id) //
+				.append(this.fromDate, obj.fromDate) //
+				.append(this.isActive, obj.isActive) //
+				.toComparison();
+	}
+
+	@Override
+	public boolean equals(final Object obj) {
+		if (obj == null) {
+			return false;
+		}
+		if (obj == this) {
+			return true;
+		}
+		if (obj.getClass() != getClass()) {
+			return false;
+		}
+		final FloatingRatePeriodData rhs = (FloatingRatePeriodData) obj;
+		return new EqualsBuilder() //
+				.append(this.id, rhs.id) //
+				.append(this.fromDate, rhs.fromDate) //
+				.append(this.isActive, rhs.isActive) //
+				.isEquals();
+	}
+
+	@Override
+	public int hashCode() {
+		return new HashCodeBuilder(17, 37) //
+				.append(this.id) //
+				.append(this.fromDate) //
+				.append(this.isActive) //
+				.toHashCode();
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/InterestRatePeriodData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/InterestRatePeriodData.java
new file mode 100644
index 0000000..e3f0185
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/data/InterestRatePeriodData.java
@@ -0,0 +1,94 @@
+/**
+ * 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.floatingrates.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class InterestRatePeriodData {
+
+	private Date fromDate;
+	private final BigDecimal interestRate;
+	private final boolean isDifferentialToBLR;
+	private final Date blrFromDate;
+	private final BigDecimal blrInterestRate;
+	private BigDecimal loanDifferentialInterestRate;
+	private BigDecimal loanProductDifferentialInterestRate;
+	private BigDecimal effectiveInterestRate;
+
+	public InterestRatePeriodData(Date fromDate, BigDecimal interestRate,
+			boolean isDifferentialToBLR, Date blrFromDate,
+			BigDecimal blrInterestRate) {
+		this.fromDate = fromDate;
+		this.interestRate = interestRate;
+		this.isDifferentialToBLR = isDifferentialToBLR;
+		this.blrFromDate = blrFromDate;
+		this.blrInterestRate = blrInterestRate;
+	}
+
+	public Date getFromDate() {
+		return this.fromDate;
+	}
+
+	public void setFromDate(Date fromDate) {
+		this.fromDate = fromDate;
+	}
+
+	public BigDecimal getInterestRate() {
+		return this.interestRate;
+	}
+
+	public boolean isDifferentialToBLR() {
+		return this.isDifferentialToBLR;
+	}
+
+	public Date getBlrFromDate() {
+		return this.blrFromDate;
+	}
+
+	public BigDecimal getBlrInterestRate() {
+		return this.blrInterestRate;
+	}
+
+	public BigDecimal getLoanDifferentialInterestRate() {
+		return this.loanDifferentialInterestRate;
+	}
+
+	public void setLoanDifferentialInterestRate(
+			BigDecimal loanDifferentialInterestRate) {
+		this.loanDifferentialInterestRate = loanDifferentialInterestRate;
+	}
+
+	public BigDecimal getLoanProductDifferentialInterestRate() {
+		return this.loanProductDifferentialInterestRate;
+	}
+
+	public void setLoanProductDifferentialInterestRate(
+			BigDecimal loanProductDifferentialInterestRate) {
+		this.loanProductDifferentialInterestRate = loanProductDifferentialInterestRate;
+	}
+
+	public BigDecimal getEffectiveInterestRate() {
+		return this.effectiveInterestRate;
+	}
+
+	public void setEffectiveInterestRate(BigDecimal effectiveInterestRate) {
+		this.effectiveInterestRate = effectiveInterestRate;
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRate.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRate.java
new file mode 100644
index 0000000..b6649a6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRate.java
@@ -0,0 +1,282 @@
+/**
+ * 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.floatingrates.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.OrderBy;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Entity
+@Table(name = "m_floating_rates", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name") })
+public class FloatingRate extends AbstractPersistable<Long> {
+
+	@Column(name = "name", length = 200, unique = true, nullable = false)
+	private String name;
+
+	@Column(name = "is_base_lending_rate", nullable = false)
+	private boolean isBaseLendingRate;
+
+	@Column(name = "is_active", nullable = false)
+	private boolean isActive;
+
+	@OrderBy(value = "fromDate,id")
+	@LazyCollection(LazyCollectionOption.FALSE)
+	@OneToMany(cascade = CascadeType.ALL, mappedBy = "floatingRate", orphanRemoval = true)
+	private Set<FloatingRatePeriod> floatingRatePeriods;
+
+	@ManyToOne(optional = true)
+	@JoinColumn(name = "createdby_id", nullable = false)
+	private AppUser createdBy;
+
+	@ManyToOne(optional = true)
+	@JoinColumn(name = "lastmodifiedby_id", nullable = false)
+	private AppUser modifiedBy;
+
+	@Column(name = "created_date", nullable = false)
+	private Date createdOn;
+
+	@Column(name = "lastmodified_date", nullable = false)
+	private Date modifiedOn;
+
+	public FloatingRate() {
+
+	}
+
+	public FloatingRate(String name, boolean isBaseLendingRate,
+			boolean isActive, Set<FloatingRatePeriod> floatingRatePeriods,
+			AppUser createdBy, AppUser modifiedBy, Date createdOn,
+			Date modifiedOn) {
+		this.name = name;
+		this.isBaseLendingRate = isBaseLendingRate;
+		this.isActive = isActive;
+		this.floatingRatePeriods = floatingRatePeriods;
+		this.createdBy = createdBy;
+		this.createdOn = createdOn;
+		this.modifiedBy = modifiedBy;
+		this.modifiedOn = modifiedOn;
+		if (floatingRatePeriods != null) {
+			for (FloatingRatePeriod ratePeriod : floatingRatePeriods) {
+				ratePeriod.updateFloatingRate(this);
+			}
+		}
+	}
+
+	public static FloatingRate createNew(AppUser currentUser,
+			JsonCommand command) {
+
+		final String name = command.stringValueOfParameterNamed("name");
+		final boolean isBaseLendingRate = command
+				.parameterExists("isBaseLendingRate") ? command
+				.booleanPrimitiveValueOfParameterNamed("isBaseLendingRate")
+				: false;
+		final boolean isActive = command.parameterExists("isActive") ? command
+				.booleanPrimitiveValueOfParameterNamed("isActive") : true;
+		final Set<FloatingRatePeriod> floatingRatePeriods = getRatePeriods(
+				currentUser, command);
+		final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+
+		return new FloatingRate(name, isBaseLendingRate, isActive,
+				floatingRatePeriods, currentUser, currentUser,
+				currentDate.toDate(), currentDate.toDate());
+	}
+
+	private static Set<FloatingRatePeriod> getRatePeriods(
+			final AppUser currentUser, final JsonCommand command) {
+		if (!command.parameterExists("ratePeriods")) {
+			return null;
+		}
+		Set<FloatingRatePeriod> ratePeriods = new HashSet<>();
+		JsonArray arrayOfParameterNamed = command
+				.arrayOfParameterNamed("ratePeriods");
+		for (final JsonElement ratePeriod : arrayOfParameterNamed) {
+			final JsonObject ratePeriodObject = ratePeriod.getAsJsonObject();
+			final JsonParserHelper helper = new JsonParserHelper();
+			final Date fromDate = helper.extractLocalDateNamed("fromDate",
+					ratePeriod, new HashSet<String>()).toDate();
+			final BigDecimal interestRate = ratePeriodObject
+					.get("interestRate").getAsBigDecimal();
+			final boolean isDifferentialToBaseLendingRate = helper
+					.parameterExists("isDifferentialToBaseLendingRate",
+							ratePeriod) ? ratePeriodObject.get(
+					"isDifferentialToBaseLendingRate").getAsBoolean() : false;
+			final boolean isActive = true;
+			final Date currentDate = DateUtils.getDateOfTenant();
+			ratePeriods.add(new FloatingRatePeriod(fromDate, interestRate,
+					isDifferentialToBaseLendingRate, isActive, currentUser,
+					currentUser, currentDate, currentDate));
+		}
+
+		return ratePeriods;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	public boolean isBaseLendingRate() {
+		return this.isBaseLendingRate;
+	}
+
+	public boolean isActive() {
+		return this.isActive;
+	}
+
+	public Set<FloatingRatePeriod> getFloatingRatePeriods() {
+		return this.floatingRatePeriods;
+	}
+
+	public AppUser getCreatedBy() {
+		return this.createdBy;
+	}
+
+	public AppUser getModifiedBy() {
+		return this.modifiedBy;
+	}
+
+	public Date getCreatedOn() {
+		return this.createdOn;
+	}
+
+	public Date getModifiedOn() {
+		return this.modifiedOn;
+	}
+
+	public Map<String, Object> update(final JsonCommand command,
+			final AppUser appUser) {
+
+		final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+		if (command.isChangeInStringParameterNamed("name", this.name)) {
+			final String newValue = command.stringValueOfParameterNamed("name");
+			actualChanges.put("name", newValue);
+			this.name = newValue;
+		}
+
+		if (command.isChangeInBooleanParameterNamed("isBaseLendingRate",
+				this.isBaseLendingRate)) {
+			final boolean newValue = command
+					.booleanPrimitiveValueOfParameterNamed("isBaseLendingRate");
+			actualChanges.put("isBaseLendingRate", newValue);
+			this.isBaseLendingRate = newValue;
+		}
+
+		if (command.isChangeInBooleanParameterNamed("isActive", this.isActive)) {
+			final boolean newValue = command
+					.booleanPrimitiveValueOfParameterNamed("isActive");
+			actualChanges.put("isActive", newValue);
+			this.isActive = newValue;
+		}
+
+		final Set<FloatingRatePeriod> newRatePeriods = getRatePeriods(appUser,
+				command);
+		if (newRatePeriods != null && !newRatePeriods.isEmpty()) {
+			updateRatePeriods(newRatePeriods, appUser);
+			actualChanges.put("ratePeriods",
+					command.jsonFragment("ratePeriods"));
+		}
+
+		return actualChanges;
+	}
+
+	private void updateRatePeriods(
+			final Set<FloatingRatePeriod> newRatePeriods, final AppUser appUser) {
+		final LocalDate today = DateUtils.getLocalDateOfTenant();
+		if (this.floatingRatePeriods != null) {
+			for (FloatingRatePeriod ratePeriod : this.floatingRatePeriods) {
+				LocalDate fromDate = LocalDate.fromDateFields(ratePeriod
+						.getFromDate());
+				if (fromDate.isAfter(today)) {
+					ratePeriod.setActive(false);
+					ratePeriod.setModifiedBy(appUser);
+					ratePeriod.setModifiedOn(today.toDate());
+				}
+			}
+		}
+		for (FloatingRatePeriod newRatePeriod : newRatePeriods) {
+			newRatePeriod.updateFloatingRate(this);
+			this.floatingRatePeriods.add(newRatePeriod);
+		}
+	}
+
+	public Collection<FloatingRatePeriodData> fetchInterestRates(
+			final FloatingRateDTO floatingRateDTO) {
+		Collection<FloatingRatePeriodData> applicableRates = new ArrayList<>();
+		FloatingRatePeriod previousPeriod = null;
+		boolean addPeriodData = false;
+		for (FloatingRatePeriod floatingRatePeriod : this.floatingRatePeriods) {
+			if (floatingRatePeriod.isActive()) {
+				// will enter
+				if (applicableRates.isEmpty()
+						&& floatingRateDTO.getStartDate().isBefore(
+								floatingRatePeriod.fetchFromDate())) {
+					if (floatingRateDTO.isFloatingInterestRate()) {
+						addPeriodData = true;
+					}
+					if (previousPeriod != null) {
+						applicableRates.add(previousPeriod
+								.toData(floatingRateDTO));
+					} else if (!addPeriodData) {
+						applicableRates.add(floatingRatePeriod
+								.toData(floatingRateDTO));
+					}
+				}
+				if (addPeriodData) {
+					applicableRates.add(floatingRatePeriod
+							.toData(floatingRateDTO));
+				}
+				previousPeriod = floatingRatePeriod;
+			}
+		}
+		if (applicableRates.isEmpty() && previousPeriod != null) {
+			applicableRates.add(previousPeriod.toData(floatingRateDTO));
+		}
+		return applicableRates;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRatePeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRatePeriod.java
new file mode 100644
index 0000000..d7dbb35
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRatePeriod.java
@@ -0,0 +1,164 @@
+/**
+ * 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.floatingrates.domain;
+
+import java.math.BigDecimal;
+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 org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_floating_rates_periods")
+public class FloatingRatePeriod extends AbstractPersistable<Long> {
+
+	@ManyToOne
+	@JoinColumn(name = "floating_rates_id", nullable = false)
+	private FloatingRate floatingRate;
+
+	@Column(name = "from_date", nullable = false)
+	private Date fromDate;
+
+	@Column(name = "interest_rate", scale = 6, precision = 19, nullable = false)
+	private BigDecimal interestRate;
+
+	@Column(name = "is_differential_to_base_lending_rate", nullable = false)
+	private boolean isDifferentialToBaseLendingRate;
+
+	@Column(name = "is_active", nullable = false)
+	private boolean isActive;
+
+	@ManyToOne(optional = true)
+	@JoinColumn(name = "createdby_id", nullable = false)
+	private AppUser createdBy;
+
+	@ManyToOne(optional = true)
+	@JoinColumn(name = "lastmodifiedby_id", nullable = false)
+	private AppUser modifiedBy;
+
+	@Column(name = "created_date", nullable = false)
+	private Date createdOn;
+
+	@Column(name = "lastmodified_date", nullable = false)
+	private Date modifiedOn;
+
+	public FloatingRatePeriod() {
+
+	}
+
+	public FloatingRatePeriod(Date fromDate, BigDecimal interestRate,
+			boolean isDifferentialToBaseLendingRate, boolean isActive,
+			AppUser createdBy, AppUser modifiedBy, Date createdOn,
+			Date modifiedOn) {
+		this.fromDate = fromDate;
+		this.interestRate = interestRate;
+		this.isDifferentialToBaseLendingRate = isDifferentialToBaseLendingRate;
+		this.isActive = isActive;
+		this.createdBy = createdBy;
+		this.modifiedBy = modifiedBy;
+		this.createdOn = createdOn;
+		this.modifiedOn = modifiedOn;
+	}
+
+	public void updateFloatingRate(FloatingRate floatingRate) {
+		this.floatingRate = floatingRate;
+	}
+
+	public FloatingRate getFloatingRatesId() {
+		return this.floatingRate;
+	}
+
+	public Date getFromDate() {
+		return this.fromDate;
+	}
+
+	public BigDecimal getInterestRate() {
+		return this.interestRate;
+	}
+
+	public boolean isDifferentialToBaseLendingRate() {
+		return this.isDifferentialToBaseLendingRate;
+	}
+
+	public boolean isActive() {
+		return this.isActive;
+	}
+
+	public AppUser getCreatedBy() {
+		return this.createdBy;
+	}
+
+	public AppUser getModifiedBy() {
+		return this.modifiedBy;
+	}
+
+	public Date getCreatedOn() {
+		return this.createdOn;
+	}
+
+	public Date getModifiedOn() {
+		return this.modifiedOn;
+	}
+
+	public void setModifiedBy(AppUser modifiedBy) {
+		this.modifiedBy = modifiedBy;
+	}
+
+	public void setModifiedOn(Date modifiedOn) {
+		this.modifiedOn = modifiedOn;
+	}
+
+	public void setActive(boolean b) {
+		this.isActive = b;
+	}
+
+	public LocalDate fetchFromDate() {
+		return new LocalDate(this.fromDate);
+	}
+
+	public FloatingRatePeriodData toData(final FloatingRateDTO floatingRateDTO) {
+
+		BigDecimal interest = getInterestRate().add(
+				floatingRateDTO.getInterestRateDiff());
+		if (isDifferentialToBaseLendingRate()) {
+			interest = interest.add(floatingRateDTO
+					.fetchBaseRate(fetchFromDate()));
+		}
+		
+		final LocalDate fromDate = new LocalDateTime(getFromDate()).toLocalDate();
+		final LocalDate createdOn = new LocalDateTime(getCreatedOn()).toLocalDate();
+		final LocalDate modidiedOn = new LocalDateTime(getModifiedOn()).toLocalDate();
+		
+		return new FloatingRatePeriodData(getId(), fromDate, interest,
+				isDifferentialToBaseLendingRate(), isActive(), getCreatedBy()
+						.getUsername(), createdOn, getModifiedBy()
+						.getUsername(), modidiedOn);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRateRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRateRepository.java
new file mode 100644
index 0000000..af5580a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRateRepository.java
@@ -0,0 +1,41 @@
+/**
+ * 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.floatingrates.domain;
+
+import java.util.Collection;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+
+public interface FloatingRateRepository extends
+		JpaRepository<FloatingRate, Long>,
+		JpaSpecificationExecutor<FloatingRate> {
+
+	@Query("from FloatingRate floatingRate where floatingRate.isBaseLendingRate = 1 and floatingRate.isActive = 1")
+	FloatingRate retrieveBaseLendingRate();
+	
+	@Query("from FloatingRate floatingRate " +
+			" inner join floatingRate.floatingRatePeriods as periods" +
+			" where floatingRate.isActive = 1 " +
+			" and periods.isActive = 1 " +
+			" and periods.isDifferentialToBaseLendingRate = 1")
+	Collection<FloatingRate> retrieveFloatingRatesLinkedToBLR();
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRateRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRateRepositoryWrapper.java
new file mode 100644
index 0000000..485c342
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRateRepositoryWrapper.java
@@ -0,0 +1,56 @@
+/**
+ * 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.floatingrates.domain;
+
+import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FloatingRateRepositoryWrapper {
+
+	private final FloatingRateRepository floatingRateRepository;
+
+	@Autowired
+	public FloatingRateRepositoryWrapper(
+			final FloatingRateRepository floatingRateRepository) {
+		this.floatingRateRepository = floatingRateRepository;
+	}
+
+	public FloatingRate retrieveBaseLendingRate() {
+		return this.floatingRateRepository.retrieveBaseLendingRate();
+	}
+
+	public FloatingRate findOneWithNotFoundDetection(final Long id) {
+		final FloatingRate floatingRate = this.floatingRateRepository
+				.findOne(id);
+		if (floatingRate == null) {
+			throw new FloatingRateNotFoundException(id);
+		}
+		return floatingRate;
+	}
+
+	public void save(FloatingRate newFloatingRate) {
+		this.floatingRateRepository.save(newFloatingRate);
+	}
+
+	public void saveAndFlush(FloatingRate floatingRateForUpdate) {
+		this.floatingRateRepository.saveAndFlush(floatingRateForUpdate);
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/exception/FloatingRateNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/exception/FloatingRateNotFoundException.java
new file mode 100644
index 0000000..938ef50
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/exception/FloatingRateNotFoundException.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.portfolio.floatingrates.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class FloatingRateNotFoundException extends
+		AbstractPlatformResourceNotFoundException {
+
+	public FloatingRateNotFoundException(final Long id) {
+		super("error.msg.floatingrate.id.invalid",
+				"Floating Rate with identifier " + id + " does not exist", id);
+	}
+
+	public FloatingRateNotFoundException(final String globalisationMessageCode) {
+		super(globalisationMessageCode, "Floating Rate does not exist");
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/handler/CreateFloatingRateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/handler/CreateFloatingRateCommandHandler.java
new file mode 100644
index 0000000..e6605fd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/handler/CreateFloatingRateCommandHandler.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.floatingrates.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.floatingrates.service.FloatingRateWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FLOATINGRATE", action = "CREATE")
+public class CreateFloatingRateCommandHandler implements
+		NewCommandSourceHandler {
+
+	private final FloatingRateWritePlatformService writePlatformService;
+
+	@Autowired
+	public CreateFloatingRateCommandHandler(
+			final FloatingRateWritePlatformService writePlatformService) {
+		this.writePlatformService = writePlatformService;
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(final JsonCommand command) {
+		return this.writePlatformService.createFloatingRate(command);
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/handler/UpdateFloatingRateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/handler/UpdateFloatingRateCommandHandler.java
new file mode 100644
index 0000000..ccc230e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/handler/UpdateFloatingRateCommandHandler.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.floatingrates.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.floatingrates.service.FloatingRateWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FLOATINGRATE", action = "UPDATE")
+public class UpdateFloatingRateCommandHandler implements
+		NewCommandSourceHandler {
+
+	private final FloatingRateWritePlatformService writePlatformService;
+
+	@Autowired
+	public UpdateFloatingRateCommandHandler(
+			final FloatingRateWritePlatformService writePlatformService) {
+		this.writePlatformService = writePlatformService;
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(final JsonCommand command) {
+		return this.writePlatformService.updateFloatingRate(command);
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/serialization/FloatingRateDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/serialization/FloatingRateDataValidator.java
new file mode 100644
index 0000000..74b017d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/serialization/FloatingRateDataValidator.java
@@ -0,0 +1,327 @@
+/**
+ * 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.floatingrates.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+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 org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
+import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepository;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class FloatingRateDataValidator {
+
+	private final Set<String> supportedParametersForFloatingRates = new HashSet<>(
+			Arrays.asList("name", "isBaseLendingRate", "isActive",
+					"ratePeriods"));
+	private final Set<String> supportedParametersForFloatingRatePeriods = new HashSet<>(
+			Arrays.asList("fromDate", "interestRate",
+					"isDifferentialToBaseLendingRate", "locale", "dateFormat"));
+
+	private final FromJsonHelper fromApiJsonHelper;
+	private final FloatingRateRepository floatingRateRepository;
+
+	@Autowired
+	public FloatingRateDataValidator(final FromJsonHelper fromApiJsonHelper,
+			final FloatingRateRepository floatingRateRepository) {
+		this.fromApiJsonHelper = fromApiJsonHelper;
+		this.floatingRateRepository = floatingRateRepository;
+	}
+
+	public void validateForCreate(String json) {
+		final Type typeOfMap = new TypeToken<Map<String, Object>>() {
+		}.getType();
+		this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+				this.supportedParametersForFloatingRates);
+
+		final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+		final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
+				dataValidationErrors).resource("floatingrate");
+
+		final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+		final String name = this.fromApiJsonHelper.extractStringNamed("name",
+				element);
+		baseDataValidator.reset().parameter("name").value(name).notBlank()
+				.notExceedingLengthOf(200);
+
+		Boolean isBaseLendingRate = null;
+		if (this.fromApiJsonHelper
+				.parameterExists("isBaseLendingRate", element)) {
+			isBaseLendingRate = this.fromApiJsonHelper
+					.extractBooleanNamed("isBaseLendingRate", element);
+
+			baseDataValidator.reset().parameter("isBaseLendingRate")
+					.value(isBaseLendingRate).notNull();
+			if (isBaseLendingRate == null) {
+				baseDataValidator.reset().parameter("isBaseLendingRate")
+						.trueOrFalseRequired(false);
+			} else if (isBaseLendingRate) {
+				FloatingRate baseLendingRate = this.floatingRateRepository
+						.retrieveBaseLendingRate();
+				if (baseLendingRate != null) {
+					baseDataValidator
+							.reset()
+							.parameter("isBaseLendingRate")
+							.value(isBaseLendingRate)
+							.failWithCode("baselendingrate.duplicate",
+									"Base Lending Rate already exists");
+				}
+			}
+		}
+
+		if (this.fromApiJsonHelper.parameterExists("isActive", element)) {
+			final Boolean isActive = this.fromApiJsonHelper
+					.extractBooleanNamed("isActive", element);
+			if (isActive == null) {
+				baseDataValidator.reset().parameter("isActive")
+						.trueOrFalseRequired(false);
+			}
+		}
+
+		if(isBaseLendingRate == null){
+			isBaseLendingRate = false;
+		}
+		validateRatePeriods(baseDataValidator, element, isBaseLendingRate, false);
+
+		throwExceptionIfValidationWarningsExist(dataValidationErrors);
+	}
+
+	private void validateRatePeriods(DataValidatorBuilder baseDataValidator,
+			JsonElement element, boolean isBaseLendingRate, boolean isBLRModifiedAsNonBLR) {
+		if (this.fromApiJsonHelper.parameterExists("ratePeriods", element)) {
+			final JsonArray ratePeriods = this.fromApiJsonHelper
+					.extractJsonArrayNamed("ratePeriods", element);
+			baseDataValidator.reset().parameter("ratePeriods")
+					.value(ratePeriods).notBlank().jsonArrayNotEmpty();
+
+			if (ratePeriods != null) {
+				List<LocalDate> fromDates = new ArrayList<>();
+				for (int i = 0; i < ratePeriods.size(); i++) {
+					final JsonElement ratePeriod = ratePeriods.get(i);
+
+					this.fromApiJsonHelper.checkForUnsupportedParameters(
+							ratePeriod.getAsJsonObject(),
+							this.supportedParametersForFloatingRatePeriods);
+
+					final LocalDate fromDate = this.fromApiJsonHelper
+							.extractLocalDateNamed("fromDate", ratePeriod);
+					baseDataValidator
+							.reset()
+							.parameter("fromDate")
+							.parameterAtIndexArray("fromDate", i + 1)
+							.value(fromDate)
+							.notBlank()
+							.validateDateAfter(
+									DateUtils.getLocalDateOfTenant()
+											.plusDays(1));
+					if (fromDate != null) {
+						fromDates.add(fromDate);
+					}
+
+					final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper
+							.extractBigDecimalWithLocaleNamed("interestRate",
+									ratePeriod);
+					baseDataValidator.reset().parameter("interestRate")
+							.parameterAtIndexArray("interestRate", i + 1)
+							.value(interestRatePerPeriod).notNull()
+							.zeroOrPositiveAmount();
+
+					if (this.fromApiJsonHelper.parameterExists(
+							"isDifferentialToBaseLendingRate", ratePeriod)) {
+						final Boolean isDifferentialToBaseLendingRate = this.fromApiJsonHelper
+								.extractBooleanNamed(
+										"isDifferentialToBaseLendingRate",
+										ratePeriod);
+						if (isDifferentialToBaseLendingRate == null) {
+							baseDataValidator
+									.reset()
+									.parameter(
+											"isDifferentialToBaseLendingRate")
+									.parameterAtIndexArray(
+											"isDifferentialToBaseLendingRate",
+											i + 1).trueOrFalseRequired(false);
+						} else if (isDifferentialToBaseLendingRate) {
+							FloatingRate baseLendingRate = this.floatingRateRepository
+									.retrieveBaseLendingRate();
+							if (baseLendingRate == null || isBLRModifiedAsNonBLR) {
+								baseDataValidator
+										.reset()
+										.parameter(
+												"isDifferentialToBaseLendingRate")
+										.parameterAtIndexArray(
+												"isDifferentialToBaseLendingRate",
+												i + 1)
+										.value(isDifferentialToBaseLendingRate)
+										.failWithCode(
+												"no.baselending.rate.defined",
+												"Base Lending Rate doesn't exists");
+							}
+							
+							if(isBaseLendingRate){
+								baseDataValidator
+								.reset()
+								.parameter(
+										"isDifferentialToBaseLendingRate")
+								.parameterAtIndexArray(
+										"isDifferentialToBaseLendingRate",
+										i + 1)
+								.value(isDifferentialToBaseLendingRate)
+								.failWithCode(
+										"cannot.be.true.for.baselendingrate",
+										"isDifferentialToBaseLendingRate cannot be true for floating rate marked as Base Lending Rate.");
+							}
+
+						}
+					}
+				}
+				Set<LocalDate> uniqueFromDates = new HashSet<>(fromDates);
+				if (fromDates.size() != uniqueFromDates.size()) {
+					baseDataValidator
+							.reset()
+							.parameter("fromDate")
+							.failWithCode("multiple.same.date",
+									"More than one entry in ratePeriods have same fromDate.");
+				}
+
+			}
+		}
+	}
+
+	public void validateForUpdate(String json,
+			FloatingRate floatingRateForUpdate) {
+		final Type typeOfMap = new TypeToken<Map<String, Object>>() {
+		}.getType();
+		this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+				this.supportedParametersForFloatingRates);
+
+		final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+		final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
+				dataValidationErrors).resource("floatingrate");
+
+		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(200);
+		}
+
+		Boolean isBaseLendingRate = null;
+		Boolean isBLRModifiedAsNonBLR = false;
+		FloatingRate baseLendingRate = this.floatingRateRepository
+				.retrieveBaseLendingRate();
+		if (this.fromApiJsonHelper
+				.parameterExists("isBaseLendingRate", element)) {
+			isBaseLendingRate = this.fromApiJsonHelper
+					.extractBooleanNamed("isBaseLendingRate", element);
+
+			baseDataValidator.reset().parameter("isBaseLendingRate")
+					.value(isBaseLendingRate).notNull();
+			if (isBaseLendingRate == null) {
+				baseDataValidator.reset().parameter("isBaseLendingRate")
+						.trueOrFalseRequired(false);
+			} else if (isBaseLendingRate) {
+				if (baseLendingRate != null
+						&& baseLendingRate.getId() != floatingRateForUpdate
+								.getId()) {
+					baseDataValidator
+							.reset()
+							.parameter("isBaseLendingRate")
+							.value(isBaseLendingRate)
+							.failWithCode("baselendingrate.duplicate",
+									"Base Lending Rate already exists");
+				}
+			}
+		}
+
+		Boolean isActive = null;
+		if (this.fromApiJsonHelper.parameterExists("isActive", element)) {
+			isActive = this.fromApiJsonHelper
+					.extractBooleanNamed("isActive", element);
+			if (isActive == null) {
+				baseDataValidator.reset().parameter("isActive")
+						.trueOrFalseRequired(false);
+			}
+		}
+
+		if(isBaseLendingRate == null){
+			isBaseLendingRate = floatingRateForUpdate.isBaseLendingRate();
+		}
+		
+		if(isActive == null){
+			isActive = floatingRateForUpdate.isActive();
+		}
+		
+		if(baseLendingRate != null
+				&& baseLendingRate.getId() == floatingRateForUpdate
+						.getId()){
+			if(!isBaseLendingRate || !isActive){
+				isBLRModifiedAsNonBLR = true;
+			}
+		}
+		
+		if(isBLRModifiedAsNonBLR){
+			Collection<FloatingRate> floatingRates = this.floatingRateRepository.retrieveFloatingRatesLinkedToBLR();
+			if(floatingRates != null && floatingRates.size() > 0){
+				baseDataValidator
+				.reset()
+				.parameter("isBaseLendingRate")
+				.value(isBaseLendingRate)
+				.failWithCode("cannot.be.marked.non.baselendingrate",
+						"There are floating rates linked to this Base Lending Rate, cannot be marked as non-Base Lending Rate.");
+			}
+		}
+
+
+		validateRatePeriods(baseDataValidator, element, isBaseLendingRate, isBLRModifiedAsNonBLR);
+
+		throwExceptionIfValidationWarningsExist(dataValidationErrors);
+	}
+
+	private void throwExceptionIfValidationWarningsExist(
+			final List<ApiParameterError> dataValidationErrors) {
+		if (!dataValidationErrors.isEmpty()) {
+			throw new PlatformApiDataValidationException(
+					"validation.msg.validation.errors.exist",
+					"Validation errors exist.", dataValidationErrors);
+		}
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformService.java
new file mode 100644
index 0000000..cbcc529
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformService.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.floatingrates.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface FloatingRateWritePlatformService {
+
+	public CommandProcessingResult createFloatingRate(JsonCommand command);
+
+	public CommandProcessingResult updateFloatingRate(JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java
new file mode 100644
index 0000000..1e372f2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java
@@ -0,0 +1,137 @@
+/**
+ * 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.floatingrates.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
+import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper;
+import org.apache.fineract.portfolio.floatingrates.serialization.FloatingRateDataValidator;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class FloatingRateWritePlatformServiceImpl implements
+		FloatingRateWritePlatformService {
+
+	private final static Logger logger = LoggerFactory
+			.getLogger(FloatingRateWritePlatformServiceImpl.class);
+	private final PlatformSecurityContext context;
+	private final FloatingRateDataValidator fromApiJsonDeserializer;
+	private final FloatingRateRepositoryWrapper floatingRateRepository;
+
+	@Autowired
+	public FloatingRateWritePlatformServiceImpl(
+			final PlatformSecurityContext context,
+			final FloatingRateDataValidator fromApiJsonDeserializer,
+			final FloatingRateRepositoryWrapper floatingRateRepository) {
+		this.context = context;
+		this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+		this.floatingRateRepository = floatingRateRepository;
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult createFloatingRate(final JsonCommand command) {
+		try {
+			this.fromApiJsonDeserializer.validateForCreate(command.json());
+			final AppUser currentUser = this.context.authenticatedUser();
+			final FloatingRate newFloatingRate = FloatingRate.createNew(
+					currentUser, command);
+			this.floatingRateRepository.save(newFloatingRate);
+			return new CommandProcessingResultBuilder() //
+					.withCommandId(command.commandId()) //
+					.withEntityId(newFloatingRate.getId()) //
+					.build();
+		} catch (final DataIntegrityViolationException dve) {
+			handleDataIntegrityIssues(command, dve);
+			return CommandProcessingResult.empty();
+		}
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult updateFloatingRate(final JsonCommand command) {
+		try {
+			final FloatingRate floatingRateForUpdate = this.floatingRateRepository
+					.findOneWithNotFoundDetection(command.entityId());
+			this.fromApiJsonDeserializer.validateForUpdate(command.json(),
+					floatingRateForUpdate);
+			final AppUser currentUser = this.context.authenticatedUser();
+			final Map<String, Object> changes = floatingRateForUpdate.update(
+					command, currentUser);
+
+			if (!changes.isEmpty()) {
+				this.floatingRateRepository.save(floatingRateForUpdate);
+			}
+
+			return new CommandProcessingResultBuilder() //
+					.withCommandId(command.commandId()) //
+					.withEntityId(command.entityId()) //
+					.with(changes) //
+					.build();
+		} catch (final DataIntegrityViolationException dve) {
+			handleDataIntegrityIssues(command, dve);
+			return CommandProcessingResult.empty();
+		}
+	}
+
+	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.floatingrates.duplicate.name",
+					"Floating Rate with name `" + name + "` already exists",
+					"name", name);
+		}
+
+		if (realCause.getMessage().contains("unq_rate_period")) {
+			throw new PlatformDataIntegrityException(
+					"error.msg.floatingrates.duplicate.active.fromdate",
+					"Attempt to add multiple floating rate periods with same fromdate",
+					"fromdate", "");
+		}
+
+		logAsErrorUnexpectedDataIntegrityException(dve);
+		throw new PlatformDataIntegrityException(
+				"error.msg.floatingrates.unknown.data.integrity.issue",
+				"Unknown data integrity issue with resource.");
+	}
+
+	private void logAsErrorUnexpectedDataIntegrityException(
+			DataIntegrityViolationException dve) {
+		logger.error(dve.getMessage(), dve);
+
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformService.java
new file mode 100644
index 0000000..24e9f3a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformService.java
@@ -0,0 +1,40 @@
+/**
+ * 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.floatingrates.service;
+
+import java.util.List;
+
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
+import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
+
+public interface FloatingRatesReadPlatformService {
+
+	List<FloatingRateData> retrieveAll();
+
+	List<FloatingRateData> retrieveLookupActive();
+
+	FloatingRateData retrieveOne(Long floatingRateId);
+
+	List<FloatingRateData> retrieveAllActive();
+
+	FloatingRateData retrieveBaseLendingRate();
+
+	List<InterestRatePeriodData> retrieveInterestRatePeriods(Long floatingRateId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java
new file mode 100644
index 0000000..798a2b1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java
@@ -0,0 +1,294 @@
+/**
+ * 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.floatingrates.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
+import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FloatingRatesReadPlatformServiceImpl implements
+		FloatingRatesReadPlatformService {
+
+	private final JdbcTemplate jdbcTemplate;
+
+	@Autowired
+	public FloatingRatesReadPlatformServiceImpl(
+			final RoutingDataSource dataSource) {
+		this.jdbcTemplate = new JdbcTemplate(dataSource);
+	}
+
+	@Override
+	public List<FloatingRateData> retrieveAll() {
+		FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(false);
+		final String sql = "select " + rateMapper.schema();
+		return this.jdbcTemplate.query(sql, rateMapper);
+	}
+
+	@Override
+	public List<FloatingRateData> retrieveAllActive() {
+		FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(false);
+		final String sql = "select " + rateMapper.schema()
+				+ " where rate.is_active = 1 ";
+		return this.jdbcTemplate.query(sql, rateMapper);
+	}
+
+	@Override
+	public List<FloatingRateData> retrieveLookupActive() {
+		FloatingRateLookupMapper rateMapper = new FloatingRateLookupMapper();
+		final String sql = "select " + rateMapper.schema()
+				+ " where rate.is_active = 1 ";
+		return this.jdbcTemplate.query(sql, rateMapper);
+	}
+
+	@Override
+	public FloatingRateData retrieveOne(final Long floatingRateId) {
+		try {
+			FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(true);
+			final String sql = "select " + rateMapper.schema()
+					+ " where rate.id = ?";
+			return this.jdbcTemplate.queryForObject(sql, rateMapper,
+					new Object[] { floatingRateId });
+		} catch (final EmptyResultDataAccessException e) {
+			throw new FloatingRateNotFoundException(floatingRateId);
+		}
+	}
+
+	@Override
+	public List<InterestRatePeriodData> retrieveInterestRatePeriods(
+			final Long floatingRateId) {
+		try {
+			FloatingInterestRatePeriodRowMapper mapper = new FloatingInterestRatePeriodRowMapper();
+			return this.jdbcTemplate.query(mapper.schema(), mapper,
+					new Object[] { floatingRateId });
+		} catch (final EmptyResultDataAccessException e) {
+			throw new FloatingRateNotFoundException(floatingRateId);
+		}
+	}
+
+	@Override
+	public FloatingRateData retrieveBaseLendingRate() {
+		try {
+			FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(true);
+			final String sql = "select "
+					+ rateMapper.schema()
+					+ " where rate.is_base_lending_rate = 1 and rate.is_active = 1";
+			return this.jdbcTemplate.queryForObject(sql, rateMapper);
+		} catch (final EmptyResultDataAccessException e) {
+			throw new FloatingRateNotFoundException(
+					"error.msg.floatingrate.base.lending.rate.not.found");
+		}
+	}
+
+	private final class FloatingRateRowMapper implements
+			RowMapper<FloatingRateData> {
+
+		private final boolean addRatePeriods;
+
+		private final StringBuilder sqlQuery = new StringBuilder()
+				.append("rate.id as id, ")
+				.append("rate.name as name, ")
+				.append("rate.is_base_lending_rate as isBaseLendingRate, ")
+				.append("rate.is_active as isActive, ")
+				.append("crappu.username as createdBy, ")
+				.append("rate.created_date as createdOn, ")
+				.append("moappu.username as modifiedBy, ")
+				.append("rate.lastmodified_date as modifiedOn ")
+				.append("FROM m_floating_rates as rate ")
+				.append("LEFT JOIN m_appuser as crappu on rate.createdby_id = crappu.id ")
+				.append("LEFT JOIN m_appuser as moappu on rate.lastmodifiedby_id = moappu.id ");
+
+		public FloatingRateRowMapper(final boolean addRatePeriods) {
+			this.addRatePeriods = addRatePeriods;
+		}
+
+		@Override
+		public FloatingRateData mapRow(final ResultSet rs,
+				@SuppressWarnings("unused") final int rowNum)
+				throws SQLException {
+			final Long id = rs.getLong("id");
+			final String name = rs.getString("name");
+			final boolean isBaseLendingRate = rs
+					.getBoolean("isBaseLendingRate");
+			final boolean isActive = rs.getBoolean("isActive");
+			final String createdBy = rs.getString("createdBy");
+			final LocalDate createdOn = JdbcSupport.getLocalDate(rs, "createdOn");
+			final String modifiedBy = rs.getString("modifiedBy");
+			final LocalDate modifiedOn = JdbcSupport.getLocalDate(rs, "modifiedOn");
+			List<FloatingRatePeriodData> ratePeriods = null;
+			if (addRatePeriods) {
+				FloatingRatePeriodRowMapper ratePeriodMapper = new FloatingRatePeriodRowMapper();
+				final String sql = "select "
+						+ ratePeriodMapper.schema()
+						+ " where period.is_active = 1 and period.floating_rates_id = ? "
+						+ " order by period.from_date desc ";
+				ratePeriods = jdbcTemplate.query(sql, ratePeriodMapper,
+						new Object[] { id });
+			}
+			return new FloatingRateData(id, name, isBaseLendingRate, isActive,
+					createdBy, createdOn, modifiedBy, modifiedOn, ratePeriods,
+					null);
+		}
+
+		public String schema() {
+			return sqlQuery.toString();
+		}
+	}
+
+	private final class FloatingRatePeriodRowMapper implements
+			RowMapper<FloatingRatePeriodData> {
+
+		private final StringBuilder sqlQuery = new StringBuilder()
+				.append("period.id as id, ")
+				.append("period.from_date as fromDate, ")
+				.append("period.interest_rate as interestRate, ")
+				.append("period.is_differential_to_base_lending_rate as isDifferentialToBaseLendingRate, ")
+				.append("period.is_active as isActive, ")
+				.append("crappu.username as createdBy, ")
+				.append("period.created_date as createdOn, ")
+				.append("moappu.username as modifiedBy, ")
+				.append("period.lastmodified_date as modifiedOn ")
+				.append("FROM m_floating_rates_periods as period ")
+				.append("LEFT JOIN m_appuser as crappu on period.createdby_id = crappu.id ")
+				.append("LEFT JOIN m_appuser as moappu on period.lastmodifiedby_id = moappu.id ");
+
+		@Override
+		public FloatingRatePeriodData mapRow(final ResultSet rs,
+				@SuppressWarnings("unused") final int rowNum)
+				throws SQLException {
+			final Long id = rs.getLong("id");
+			final LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromDate");
+			final BigDecimal interestRate = rs.getBigDecimal("interestRate");
+			final boolean isDifferentialToBaseLendingRate = rs
+					.getBoolean("isDifferentialToBaseLendingRate");
+			final boolean isActive = rs.getBoolean("isActive");
+			final String createdBy = rs.getString("createdBy");
+			final LocalDate createdOn = JdbcSupport.getLocalDate(rs, "createdOn");
+			final String modifiedBy = rs.getString("modifiedBy");
+			final LocalDate modifiedOn = JdbcSupport.getLocalDate(rs, "modifiedOn");
+			return new FloatingRatePeriodData(id, fromDate, interestRate,
+					isDifferentialToBaseLendingRate, isActive, createdBy,
+					createdOn, modifiedBy, modifiedOn);
+		}
+
+		public String schema() {
+			return sqlQuery.toString();
+		}
+	}
+
+	private final class FloatingRateLookupMapper implements
+			RowMapper<FloatingRateData> {
+
+		private final StringBuilder sqlQuery = new StringBuilder()
+				.append("rate.id as id, ").append("rate.name as name, ")
+				.append("rate.is_base_lending_rate as isBaseLendingRate ")
+				.append("FROM m_floating_rates as rate ");
+
+		@Override
+		public FloatingRateData mapRow(final ResultSet rs,
+				@SuppressWarnings("unused") final int rowNum)
+				throws SQLException {
+			final Long id = rs.getLong("id");
+			final String name = rs.getString("name");
+			final boolean isBaseLendingRate = rs
+					.getBoolean("isBaseLendingRate");
+			return new FloatingRateData(id, name, isBaseLendingRate, true,
+					null, null, null, null, null, null);
+		}
+
+		public String schema() {
+			return sqlQuery.toString();
+		}
+	}
+
+	private final class FloatingInterestRatePeriodRowMapper implements
+			RowMapper<InterestRatePeriodData> {
+
+		private final StringBuilder sqlQuery = new StringBuilder()
+				.append("select ")
+				.append("    linkedrateperiods.from_date as linkedrateperiods_from_date, ")
+				.append("    linkedrateperiods.interest_rate as linkedrateperiods_interest_rate, ")
+				.append("    linkedrateperiods.is_differential_to_base_lending_rate as linkedrateperiods_is_differential_to_base_lending_rate, ")
+				.append("    baserate.from_date as baserate_from_date, ")
+				.append("    baserate.interest_rate as baserate_interest_rate ")
+				.append("from m_floating_rates as linkedrate ")
+				.append("left join m_floating_rates_periods as linkedrateperiods on (linkedrate.id = linkedrateperiods.floating_rates_id and linkedrateperiods.is_active = 1) ")
+				.append("left join ( ")
+				.append("    select blr.name, ")
+				.append("    blr.is_base_lending_rate, ")
+				.append("    blr.is_active, ")
+				.append("    blrperiods.from_date, ")
+				.append("    blrperiods.interest_rate ")
+				.append("    from m_floating_rates as blr ")
+				.append("    left join m_floating_rates_periods as blrperiods on (blr.id = blrperiods.floating_rates_id and blrperiods.is_active = 1) ")
+				.append("    where blr.is_base_lending_rate = 1 and blr.is_active = 1 ")
+				.append(") as baserate on (linkedrateperiods.is_differential_to_base_lending_rate = 1 and linkedrate.is_base_lending_rate = 0) ")
+				.append("where (baserate.from_date is null ")
+				.append("    or baserate.from_date = (select MAX(b.from_date) ")
+				.append("        from (select blr.name, ")
+				.append("            blr.is_base_lending_rate, ")
+				.append("            blr.is_active, ")
+				.append("            blrperiods.from_date, ")
+				.append("            blrperiods.interest_rate ")
+				.append("            from m_floating_rates as blr ")
+				.append("            left join m_floating_rates_periods as blrperiods on (blr.id = blrperiods.floating_rates_id and blrperiods.is_active = 1) ")
+				.append("            where blr.is_base_lending_rate = 1 and blr.is_active = 1 ")
+				.append("        ) as b ")
+				.append("        where b.from_date <= linkedrateperiods.from_date)) ")
+				.append("and linkedrate.id = ? ")
+				.append("order by linkedratePeriods_from_date desc ");
+
+		@Override
+		public InterestRatePeriodData mapRow(final ResultSet rs,
+				@SuppressWarnings("unused") final int rowNum)
+				throws SQLException {
+			final Date fromDate = rs.getDate("linkedrateperiods_from_date");
+			final BigDecimal interestRate = rs
+					.getBigDecimal("linkedrateperiods_interest_rate");
+			final boolean isDifferentialToBLR = rs
+					.getBoolean("linkedrateperiods_is_differential_to_base_lending_rate");
+			final Date blrFromDate = rs.getDate("baserate_from_date");
+			final BigDecimal blrInterestRate = rs
+					.getBigDecimal("baserate_interest_rate");
+
+			return new InterestRatePeriodData(fromDate, interestRate,
+					isDifferentialToBLR, blrFromDate, blrInterestRate);
+		}
+
+		public String schema() {
+			return sqlQuery.toString();
+		}
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/api/FundsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/api/FundsApiResource.java
new file mode 100644
index 0000000..f2967f8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/api/FundsApiResource.java
@@ -0,0 +1,132 @@
+/**
+ * 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.fund.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;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.codes.data.CodeData;
+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.apache.fineract.portfolio.fund.data.FundData;
+import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/funds")
+@Component
+@Scope("singleton")
+public class FundsApiResource {
+
+    /**
+     * The set of parameters that are supported in response for {@link CodeData}
+     */
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "externalId"));
+
+    private final String resourceNameForPermissions = "FUND";
+
+    private final PlatformSecurityContext context;
+    private final FundReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<FundData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public FundsApiResource(final PlatformSecurityContext context, final FundReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<FundData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveFunds(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<FundData> funds = this.readPlatformService.retrieveAllFunds();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, funds, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createFund(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createFund().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{fundId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retreiveFund(@PathParam("fundId") final Long fundId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final FundData fund = this.readPlatformService.retrieveFund(fundId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, fund, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{fundId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateFund(@PathParam("fundId") final Long fundId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateFund(fundId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/data/FundData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/data/FundData.java
new file mode 100644
index 0000000..58d8a9e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/data/FundData.java
@@ -0,0 +1,44 @@
+/**
+ * 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.fund.data;
+
+import java.io.Serializable;
+
+/**
+ * Immutable data object to represent fund data.
+ */
+public class FundData implements Serializable {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String name;
+    @SuppressWarnings("unused")
+    private final String externalId;
+
+    public static FundData instance(final Long id, final String name, final String externalId) {
+        return new FundData(id, name, externalId);
+    }
+
+    private FundData(final Long id, final String name, final String externalId) {
+        this.id = id;
+        this.name = name;
+        this.externalId = externalId;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/domain/Fund.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/domain/Fund.java
new file mode 100644
index 0000000..aa6a579
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/domain/Fund.java
@@ -0,0 +1,84 @@
+/**
+ * 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.fund.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_fund", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "fund_name_org"),
+        @UniqueConstraint(columnNames = { "external_id" }, name = "fund_externalid_org") })
+public class Fund extends AbstractPersistable<Long> {
+
+    @Column(name = "name")
+    private String name;
+
+    @Column(name = "external_id", length = 100)
+    private String externalId;
+
+    public static Fund fromJson(final JsonCommand command) {
+
+        final String firstnameParamName = "name";
+        final String name = command.stringValueOfParameterNamed(firstnameParamName);
+
+        final String lastnameParamName = "externalId";
+        final String externalId = command.stringValueOfParameterNamed(lastnameParamName);
+
+        return new Fund(name, externalId);
+    }
+
+    protected Fund() {
+        //
+    }
+
+    private Fund(final String fundName, final String externalId) {
+        this.name = StringUtils.defaultIfEmpty(fundName, null);
+        this.externalId = StringUtils.defaultIfEmpty(externalId, null);
+    }
+
+    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 = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String externalIdParamName = "externalId";
+        if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) {
+            final String newValue = command.stringValueOfParameterNamed(externalIdParamName);
+            actualChanges.put(externalIdParamName, newValue);
+            this.externalId = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        return actualChanges;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/domain/FundRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/domain/FundRepository.java
new file mode 100644
index 0000000..36a522f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/domain/FundRepository.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.fund.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface FundRepository extends JpaRepository<Fund, Long>, JpaSpecificationExecutor<Fund> {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/exception/FundNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/exception/FundNotFoundException.java
new file mode 100644
index 0000000..9805af8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/exception/FundNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.fund.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when fund resources are not found.
+ */
+public class FundNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public FundNotFoundException(final Long id) {
+        super("error.msg.fund.id.invalid", "Fund with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/handler/CreateFundCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/handler/CreateFundCommandHandler.java
new file mode 100644
index 0000000..44ea750
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/handler/CreateFundCommandHandler.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.portfolio.fund.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.fund.service.FundWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FUND", action = "CREATE")
+public class CreateFundCommandHandler implements NewCommandSourceHandler {
+
+    private final FundWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateFundCommandHandler(final FundWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createFund(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/handler/UpdateFundCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/handler/UpdateFundCommandHandler.java
new file mode 100644
index 0000000..dfb8d80
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/handler/UpdateFundCommandHandler.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.portfolio.fund.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.fund.service.FundWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FUND", action = "UPDATE")
+public class UpdateFundCommandHandler implements NewCommandSourceHandler {
+
+    private final FundWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateFundCommandHandler(final FundWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateFund(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/serialization/FundCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/serialization/FundCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..75163a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/serialization/FundCommandFromApiJsonDeserializer.java
@@ -0,0 +1,103 @@
+/**
+ * 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.fund.serialization;
+
+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 FundCommandFromApiJsonDeserializer {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("name", "externalId"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public FundCommandFromApiJsonDeserializer(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("fund");
+
+        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 externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
+        baseDataValidator.reset().parameter("externalId").value(externalId).notExceedingLengthOf(100);
+
+        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("fund");
+
+        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("externalId", element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
+            baseDataValidator.reset().parameter("externalId").value(externalId).notExceedingLengthOf(100);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformService.java
new file mode 100644
index 0000000..4a6f40a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformService.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.fund.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.fund.data.FundData;
+
+public interface FundReadPlatformService {
+
+    Collection<FundData> retrieveAllFunds();
+
+    FundData retrieveFund(Long fundId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformServiceImpl.java
new file mode 100644
index 0000000..7d7ada4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformServiceImpl.java
@@ -0,0 +1,93 @@
+/**
+ * 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.fund.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.fund.data.FundData;
+import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+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 FundReadPlatformServiceImpl implements FundReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public FundReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class FundMapper implements RowMapper<FundData> {
+
+        public String schema() {
+            return " f.id as id, f.name as name, f.external_id as externalId from m_fund f ";
+        }
+
+        @Override
+        public FundData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final String externalId = rs.getString("externalId");
+
+            return FundData.instance(id, name, externalId);
+        }
+    }
+
+    @Override
+    @Cacheable(value = "funds", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('fn')")
+    public Collection<FundData> retrieveAllFunds() {
+
+        this.context.authenticatedUser();
+
+        final FundMapper rm = new FundMapper();
+        final String sql = "select " + rm.schema() + " order by f.name";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public FundData retrieveFund(final Long fundId) {
+
+        try {
+            this.context.authenticatedUser();
+
+            final FundMapper rm = new FundMapper();
+            final String sql = "select " + rm.schema() + " where f.id = ?";
+
+            final FundData selectedFund = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { fundId });
+
+            return selectedFund;
+        } catch (final EmptyResultDataAccessException e) {
+            throw new FundNotFoundException(fundId);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformService.java
new file mode 100644
index 0000000..d3c4723
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformService.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.fund.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface FundWritePlatformService {
+
+    CommandProcessingResult createFund(JsonCommand command);
+
+    CommandProcessingResult updateFund(Long fundId, JsonCommand command);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..ef72175
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,124 @@
+/**
+ * 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.fund.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.fund.domain.Fund;
+import org.apache.fineract.portfolio.fund.domain.FundRepository;
+import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
+import org.apache.fineract.portfolio.fund.serialization.FundCommandFromApiJsonDeserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class FundWritePlatformServiceJpaRepositoryImpl implements FundWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(FundWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final FundCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final FundRepository fundRepository;
+
+    @Autowired
+    public FundWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final FundCommandFromApiJsonDeserializer fromApiJsonDeserializer, final FundRepository fundRepository) {
+        this.context = context;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.fundRepository = fundRepository;
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "funds", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('fn')")
+    public CommandProcessingResult createFund(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final Fund fund = Fund.fromJson(command);
+
+            this.fundRepository.save(fund);
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(fund.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleFundDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    @CacheEvict(value = "funds", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('fn')")
+    public CommandProcessingResult updateFund(final Long fundId, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final Fund fund = this.fundRepository.findOne(fundId);
+            if (fund == null) { throw new FundNotFoundException(fundId); }
+
+            final Map<String, Object> changes = fund.update(command);
+            if (!changes.isEmpty()) {
+                this.fundRepository.saveAndFlush(fund);
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(fund.getId()).with(changes).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleFundDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleFundDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("fund_externalid_org")) {
+            final String externalId = command.stringValueOfParameterNamed("externalId");
+            throw new PlatformDataIntegrityException("error.msg.fund.duplicate.externalId", "A fund with external id '" + externalId
+                    + "' already exists", "externalId", externalId);
+        } else if (realCause.getMessage().contains("fund_name_org")) {
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.fund.duplicate.name", "A fund with name '" + name + "' already exists",
+                    "name", name);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.fund.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java
new file mode 100644
index 0000000..367cae5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java
@@ -0,0 +1,331 @@
+/**
+ * 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.group.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+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.accounting.journalentry.api.DateParam;
+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.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData;
+import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
+import org.apache.fineract.portfolio.collectionsheet.data.JLGCollectionSheetData;
+import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetReadPlatformService;
+import org.apache.fineract.portfolio.group.data.CenterData;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.data.StaffCenterData;
+import org.apache.fineract.portfolio.group.service.CenterReadPlatformService;
+import org.apache.fineract.portfolio.meeting.data.MeetingData;
+import org.apache.fineract.portfolio.meeting.service.MeetingReadPlatformService;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+
+@Path("/centers")
+@Component
+@Scope("singleton")
+public class CentersApiResource {
+
+    private final PlatformSecurityContext context;
+    private final CenterReadPlatformService centerReadPlatformService;
+    private final ToApiJsonSerializer<CenterData> centerApiJsonSerializer;
+    private final ToApiJsonSerializer<Object> toApiJsonSerializer;
+    private final ToApiJsonSerializer<AccountSummaryCollectionData> groupSummaryToApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final CollectionSheetReadPlatformService collectionSheetReadPlatformService;
+    private final FromJsonHelper fromJsonHelper;
+    private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
+    private final CalendarReadPlatformService calendarReadPlatformService;
+    private final MeetingReadPlatformService meetingReadPlatformService;
+
+    @Autowired
+    public CentersApiResource(final PlatformSecurityContext context, final CenterReadPlatformService centerReadPlatformService,
+            final ToApiJsonSerializer<CenterData> centerApiJsonSerializer, final ToApiJsonSerializer<Object> toApiJsonSerializer,
+            final ToApiJsonSerializer<AccountSummaryCollectionData> groupSummaryToApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
+            final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
+            final CalendarReadPlatformService calendarReadPlatformService, final MeetingReadPlatformService meetingReadPlatformService) {
+        this.context = context;
+        this.centerReadPlatformService = centerReadPlatformService;
+        this.centerApiJsonSerializer = centerApiJsonSerializer;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.groupSummaryToApiJsonSerializer = groupSummaryToApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.collectionSheetReadPlatformService = collectionSheetReadPlatformService;
+        this.fromJsonHelper = fromJsonHelper;
+        this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
+        this.calendarReadPlatformService = calendarReadPlatformService;
+        this.meetingReadPlatformService = meetingReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo, @QueryParam("command") final String commandParam,
+            @QueryParam("officeId") final Long officeId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly) {
+
+        this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
+
+        if (is(commandParam, "close")) {
+            final CenterData centerClosureTemplate = this.centerReadPlatformService.retrieveCenterWithClosureReasons();
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+            return this.centerApiJsonSerializer.serialize(settings, centerClosureTemplate,
+                    GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS);
+        }
+
+        final CenterData template = this.centerReadPlatformService.retrieveTemplate(officeId, staffInSelectedOfficeOnly);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.centerApiJsonSerializer.serialize(settings, template, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("officeId") final Long officeId, @QueryParam("staffId") final Long staffId,
+            @QueryParam("externalId") final String externalId, @QueryParam("name") final String name,
+            @QueryParam("underHierarchy") final String hierarchy, @QueryParam("paged") final Boolean paged,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder,
+            @QueryParam("meetingDate") final DateParam meetingDateParam, @QueryParam("dateFormat") final String dateFormat,
+            @QueryParam("locale") final String locale) {
+
+        this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (meetingDateParam != null && officeId != null) {
+            Date meetingDate = meetingDateParam.getDate("meetingDate", dateFormat, locale);
+            Collection<StaffCenterData> staffCenterDataArray = this.centerReadPlatformService.retriveAllCentersByMeetingDate(officeId,
+                    meetingDate, staffId);
+            return this.toApiJsonSerializer.serialize(settings, staffCenterDataArray,
+                    GroupingTypesApiConstants.STAFF_CENTER_RESPONSE_DATA_PARAMETERS);
+        }
+        final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder);
+        final Boolean isOrphansOnly = false;
+        final SearchParameters searchParameters = SearchParameters.forGroups(sqlSearch, officeId, staffId, externalId, name, hierarchy,
+                offset, limit, orderBy, sortOrder, isOrphansOnly);
+        if (parameters.isPaged()) {
+            final Page<CenterData> centers = this.centerReadPlatformService.retrievePagedAll(searchParameters, parameters);
+            return this.toApiJsonSerializer.serialize(settings, centers, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS);
+        }
+
+        final Collection<CenterData> centers = this.centerReadPlatformService.retrieveAll(searchParameters, parameters);
+        return this.toApiJsonSerializer.serialize(settings, centers, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{centerId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@Context final UriInfo uriInfo, @PathParam("centerId") final Long centerId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly) {
+
+        this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        CalendarData collectionMeetingCalendar = null;
+        Collection<GroupGeneralData> groups = null;
+        CenterData center = this.centerReadPlatformService.retrieveOne(centerId);
+
+        final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
+        if (template) {
+            final CenterData templateCenter = this.centerReadPlatformService.retrieveTemplate(center.officeId(), staffInSelectedOfficeOnly);
+            center = CenterData.withTemplate(templateCenter, center);
+        }
+
+        if (!associationParameters.isEmpty()) {
+            if (associationParameters.contains("groupMembers")) {
+                groups = this.centerReadPlatformService.retrieveAssociatedGroups(centerId);
+            }
+
+            if (associationParameters.contains("collectionMeetingCalendar")) {
+                collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(centerId,
+                        CalendarEntityType.CENTERS.getValue());
+                if (collectionMeetingCalendar != null) {
+                    final boolean withHistory = true;
+                    final LocalDate tillDate = null;
+                    final Collection<LocalDate> recurringDates = this.calendarReadPlatformService.generateRecurringDates(
+                            collectionMeetingCalendar, withHistory, tillDate);
+                    final Collection<LocalDate> nextTenRecurringDates = this.calendarReadPlatformService
+                            .generateNextTenRecurringDates(collectionMeetingCalendar);
+                    final MeetingData lastMeeting = this.meetingReadPlatformService.retrieveLastMeeting(collectionMeetingCalendar
+                            .getCalendarInstanceId());
+                    final LocalDate recentEligibleMeetingDate = this.calendarReadPlatformService
+                            .generateNextEligibleMeetingDateForCollection(collectionMeetingCalendar, lastMeeting);
+                    collectionMeetingCalendar = CalendarData.withRecurringDates(collectionMeetingCalendar, recurringDates,
+                            nextTenRecurringDates, recentEligibleMeetingDate);
+                }
+            }
+
+            center = CenterData.withAssociations(center, groups, collectionMeetingCalendar);
+        }
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.centerApiJsonSerializer.serialize(settings, center, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createCenter() //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @PUT
+    @Path("{centerId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("centerId") final Long centerId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateCenter(centerId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{centerId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("centerId") final Long centerId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteCenter(centerId) //
+                .build(); //
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{centerId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String activate(@PathParam("centerId") final Long centerId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson, @Context final UriInfo uriInfo) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "activate")) {
+            final CommandWrapper commandRequest = builder.activateCenter(centerId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "generateCollectionSheet")) {
+            final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
+            final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
+            final JLGCollectionSheetData collectionSheet = this.collectionSheetReadPlatformService.generateCenterCollectionSheet(centerId,
+                    query);
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+            return this.toApiJsonSerializer.serialize(settings, collectionSheet, GroupingTypesApiConstants.COLLECTIONSHEET_DATA_PARAMETERS);
+        } else if (is(commandParam, "saveCollectionSheet")) {
+            final CommandWrapper commandRequest = builder.saveCenterCollectionSheet(centerId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "close")) {
+            final CommandWrapper commandRequest = builder.closeCenter(centerId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "associateGroups")) {
+            final CommandWrapper commandRequest = builder.associateGroupsToCenter(centerId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "disassociateGroups")) {
+            final CommandWrapper commandRequest = builder.disassociateGroupsFromCenter(centerId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", "generateCollectionSheet",
+                    "saveCollectionSheet", "close", "associateGroups", "disassociateGroups" });
+        }
+
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Path("{centerId}/accounts")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveGroupAccount(@PathParam("centerId") final Long centerId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
+
+        final AccountSummaryCollectionData groupAccount = this.accountDetailsReadPlatformService.retrieveGroupAccountDetails(centerId);
+
+        final Set<String> GROUP_ACCOUNTS_DATA_PARAMETERS = new HashSet<>(Arrays.asList("loanAccounts", "savingsAccounts",
+                "memberLoanAccounts", "memberSavingsAccounts"));
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.groupSummaryToApiJsonSerializer.serialize(settings, groupAccount, GROUP_ACCOUNTS_DATA_PARAMETERS);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupingTypesApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupingTypesApiConstants.java
new file mode 100755
index 0000000..7a823e5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupingTypesApiConstants.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.portfolio.group.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.group.data.CenterData;
+
+public class GroupingTypesApiConstants {
+
+    public static final String CENTER_RESOURCE_NAME = "center";
+    public static final String GROUP_RESOURCE_NAME = "group";
+    public static final String COMMUNAL_BANK_RESOURCE_NAME = "communalbank";
+
+    // group roles
+    public static final String GROUP_ROLE_RESOURCE_NAME = "grouprole";
+    public static final String GROUP_ROLE_NAME = "GROUPROLE";
+
+    public static final String GROUP_CLOSURE_REASON = "GroupClosureReason";
+    public static final String CENTER_CLOSURE_REASON = "CenterClosureReason";
+
+    public static final String roleParamName = "role";
+    public static final String groupIdParamName = "groupId";
+    public static final String clientIdParamName = "clientId";
+    public static final String groupRolesParamName = "groupRoles";
+    public static final String accountNoParamName = "accountNo";
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // center parameters
+    public static final String idParamName = "id";
+    public static final String nameParamName = "name";
+    public static final String externalIdParamName = "externalId";
+    public static final String officeIdParamName = "officeId";
+    public static final String staffIdParamName = "staffId";
+    public static final String activeParamName = "active";
+    public static final String activationDateParamName = "activationDate";
+    public static final String groupMembersParamName = "groupMembers";
+
+    public static final String submittedOnDateParamName = "submittedOnDate";
+    public static final String inheritStaffForClientAccounts   = "inheritStaffForClientAccounts";
+
+    // group parameters
+    public static final String centerIdParamName = "centerId";
+    public static final String clientMembersParamName = "clientMembers";
+
+    // response parameters
+    public static final String statusParamName = "status";
+    public static final String hierarchyParamName = "hierarchy";
+    public static final String officeNameParamName = "officeName";
+    public static final String staffNameParamName = "staffName";
+    public static final String officeOptionsParamName = "officeOptions";
+    public static final String staffOptionsParamName = "staffOptions";
+    public static final String clientOptionsParamName = "clientOptions";
+    public static final String collectionMeetingCalendar = "collectionMeetingCalendar";
+    public static final String timeLine = "timeline";
+    public static final String closureReasons = "closureReasons";
+
+    // group close parameters
+    public static final String closureDateParamName = "closureDate";
+    public static final String closureReasonIdParamName = "closureReasonId";
+
+    // staff centres parameters
+    public static final String meetingFallCenters = "meetingFallCenters";
+
+    public static final Set<String> CENTER_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, idParamName, nameParamName, externalIdParamName, officeIdParamName, staffIdParamName, activeParamName,
+            activationDateParamName, groupMembersParamName, submittedOnDateParamName));
+
+    public static final Set<String> GROUP_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName,
+            idParamName, nameParamName, externalIdParamName, centerIdParamName, officeIdParamName, staffIdParamName, activeParamName,
+            activationDateParamName, clientMembersParamName, collectionMeetingCalendar, submittedOnDateParamName));
+
+    public static final Set<String> GROUP_ROLES_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(roleParamName,
+            clientIdParamName));
+
+    /**
+     * These parameters will match the class level parameters of
+     * {@link CenterData}. Where possible, we try to get response parameters to
+     * match those of request parameters.
+     */
+    public static final Set<String> CENTER_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, nameParamName,
+            externalIdParamName, officeIdParamName, officeNameParamName, staffIdParamName, staffNameParamName, hierarchyParamName,
+            officeOptionsParamName, staffOptionsParamName, statusParamName, activeParamName, activationDateParamName, timeLine,
+            groupMembersParamName, collectionMeetingCalendar, closureReasons));
+
+    public static final Set<String> CENTER_GROUP_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, nameParamName,
+            externalIdParamName, officeIdParamName, officeNameParamName, staffIdParamName, staffNameParamName, hierarchyParamName,
+            officeOptionsParamName, staffOptionsParamName, clientOptionsParamName));
+
+    public static final Set<String> GROUP_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, nameParamName,
+            externalIdParamName, officeIdParamName, officeNameParamName, "parentId", "parentName", staffIdParamName, staffNameParamName,
+            hierarchyParamName, officeOptionsParamName, statusParamName, activeParamName, activationDateParamName, staffOptionsParamName,
+            clientOptionsParamName, timeLine));
+
+    public static final Set<String> ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, activationDateParamName));
+
+    public static final Set<String> COLLECTIONSHEET_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList("dueDate", "loanProducts", "groups"));
+
+    public static final Set<String> GROUP_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, closureDateParamName, closureReasonIdParamName));
+
+    public static final Set<String> STAFF_CENTER_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(staffIdParamName,
+            staffNameParamName, meetingFallCenters));
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResource.java
new file mode 100755
index 0000000..033a8db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResource.java
@@ -0,0 +1,432 @@
+/**
+ * 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.group.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+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.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData;
+import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.collectionsheet.data.JLGCollectionSheetData;
+import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetReadPlatformService;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.data.GroupRoleData;
+import org.apache.fineract.portfolio.group.service.CenterReadPlatformService;
+import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
+import org.apache.fineract.portfolio.group.service.GroupRolesReadPlatformService;
+import org.apache.fineract.portfolio.meeting.data.MeetingData;
+import org.apache.fineract.portfolio.meeting.service.MeetingReadPlatformService;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import com.google.gson.JsonElement;
+
+@Path("/groups")
+@Component
+@Scope("singleton")
+public class GroupsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final GroupReadPlatformService groupReadPlatformService;
+    private final CenterReadPlatformService centerReadPlatformService;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final ToApiJsonSerializer<Object> toApiJsonSerializer;
+    private final ToApiJsonSerializer<GroupGeneralData> groupGeneralApiJsonSerializer;
+    private final ToApiJsonSerializer<AccountSummaryCollectionData> groupSummaryToApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final CollectionSheetReadPlatformService collectionSheetReadPlatformService;
+    private final FromJsonHelper fromJsonHelper;
+    private final GroupRolesReadPlatformService groupRolesReadPlatformService;
+    private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
+    private final CalendarReadPlatformService calendarReadPlatformService;
+    private final MeetingReadPlatformService meetingReadPlatformService;
+
+    @Autowired
+    public GroupsApiResource(final PlatformSecurityContext context, final GroupReadPlatformService groupReadPlatformService,
+            final CenterReadPlatformService centerReadPlatformService, final ClientReadPlatformService clientReadPlatformService,
+            final ToApiJsonSerializer<Object> toApiJsonSerializer,
+            final ToApiJsonSerializer<GroupGeneralData> groupTopOfHierarchyApiJsonSerializer,
+            final ToApiJsonSerializer<AccountSummaryCollectionData> groupSummaryToApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
+            final GroupRolesReadPlatformService groupRolesReadPlatformService,
+            final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
+            final CalendarReadPlatformService calendarReadPlatformService, final MeetingReadPlatformService meetingReadPlatformService) {
+
+        this.context = context;
+        this.groupReadPlatformService = groupReadPlatformService;
+        this.centerReadPlatformService = centerReadPlatformService;
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.groupGeneralApiJsonSerializer = groupTopOfHierarchyApiJsonSerializer;
+        this.groupSummaryToApiJsonSerializer = groupSummaryToApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.collectionSheetReadPlatformService = collectionSheetReadPlatformService;
+        this.fromJsonHelper = fromJsonHelper;
+        this.groupRolesReadPlatformService = groupRolesReadPlatformService;
+        this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
+        this.calendarReadPlatformService = calendarReadPlatformService;
+        this.meetingReadPlatformService = meetingReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId,
+            @QueryParam("center") final boolean isCenterGroup, @QueryParam("centerId") final Long centerId,
+            @QueryParam("command") final String commandParam,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly) {
+
+        this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+
+        if (is(commandParam, "close")) {
+            final GroupGeneralData groupClosureTemplate = this.groupReadPlatformService.retrieveGroupWithClosureReasons();
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+            return this.groupGeneralApiJsonSerializer.serialize(settings, groupClosureTemplate,
+                    GroupingTypesApiConstants.GROUP_RESPONSE_DATA_PARAMETERS);
+        }
+
+        if (centerId != null) {
+            final GroupGeneralData centerGroupTemplate = this.centerReadPlatformService.retrieveCenterGroupTemplate(centerId);
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+            return this.groupGeneralApiJsonSerializer.serialize(settings, centerGroupTemplate,
+                    GroupingTypesApiConstants.CENTER_GROUP_RESPONSE_DATA_PARAMETERS);
+        }
+
+        final GroupGeneralData groupTemplate = this.groupReadPlatformService.retrieveTemplate(officeId, isCenterGroup,
+                staffInSelectedOfficeOnly);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.groupGeneralApiJsonSerializer.serialize(settings, groupTemplate,
+                GroupingTypesApiConstants.GROUP_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("officeId") final Long officeId, @QueryParam("staffId") final Long staffId,
+            @QueryParam("externalId") final String externalId, @QueryParam("name") final String name,
+            @QueryParam("underHierarchy") final String hierarchy, @QueryParam("paged") final Boolean paged,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder, 
+            @QueryParam("orphansOnly") final Boolean orphansOnly) {
+
+        this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+        final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        final SearchParameters searchParameters = SearchParameters.forGroups(sqlSearch, officeId, staffId, externalId, name, hierarchy,
+                offset, limit, orderBy, sortOrder, orphansOnly);
+        if (parameters.isPaged()) {
+            final Page<GroupGeneralData> groups = this.groupReadPlatformService.retrievePagedAll(searchParameters, parameters);
+            return this.toApiJsonSerializer.serialize(settings, groups, GroupingTypesApiConstants.GROUP_RESPONSE_DATA_PARAMETERS);
+        }
+
+        final Collection<GroupGeneralData> groups = this.groupReadPlatformService.retrieveAll(searchParameters, parameters);
+        return this.toApiJsonSerializer.serialize(settings, groups, GroupingTypesApiConstants.GROUP_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{groupId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@Context final UriInfo uriInfo, @PathParam("groupId") final Long groupId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
+            @QueryParam("roleId") final Long roleId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+
+        GroupGeneralData group = this.groupReadPlatformService.retrieveOne(groupId);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        // associations
+        Collection<ClientData> membersOfGroup = null;
+        Collection<ClientData> activeClientMembers = null;
+        Collection<GroupRoleData> groupRoles = null;
+        GroupRoleData selectedRole = null;
+        Collection<CalendarData> calendars = null;
+        CalendarData collectionMeetingCalendar = null;
+
+        if (!associationParameters.isEmpty()) {
+            if (associationParameters.contains("all")) {
+                associationParameters.addAll(Arrays.asList("clientMembers", "activeClientMembers",
+                        "groupRoles", "calendars", "collectionMeetingCalendar"));
+            }
+            if (associationParameters.contains("clientMembers")) {
+                membersOfGroup = this.clientReadPlatformService.retrieveClientMembersOfGroup(groupId);
+                if (CollectionUtils.isEmpty(membersOfGroup)) {
+                    membersOfGroup = null;
+                }
+            }
+            if (associationParameters.contains("activeClientMembers")) {
+                activeClientMembers = this.clientReadPlatformService.retrieveActiveClientMembersOfGroup(groupId);
+                if (CollectionUtils.isEmpty(activeClientMembers)) {
+                    activeClientMembers = null;
+                }
+            }
+            if (associationParameters.contains("groupRoles")) {
+                groupRoles = this.groupRolesReadPlatformService.retrieveGroupRoles(groupId);
+                if (CollectionUtils.isEmpty(groupRoles)) {
+                    groupRoles = null;
+                }
+            }
+            if (associationParameters.contains("parentCalendars")) {
+                final List<Integer> calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter("all");
+                calendars = this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(),
+                        calendarTypeOptions);
+                if (CollectionUtils.isEmpty(calendars)) {
+                    calendars = null;
+                }
+            }
+            if (associationParameters.contains("collectionMeetingCalendar")) {
+                if (group.isChildGroup()) {
+                    collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(group.getParentId(),
+                            CalendarEntityType.CENTERS.getValue());
+                } else {
+                    collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(groupId,
+                            CalendarEntityType.GROUPS.getValue());
+                }
+                if (collectionMeetingCalendar != null) {
+                    final boolean withHistory = true;
+                    final LocalDate tillDate = null;
+                    final Collection<LocalDate> recurringDates = this.calendarReadPlatformService.generateRecurringDates(
+                            collectionMeetingCalendar, withHistory, tillDate);
+                    final Collection<LocalDate> nextTenRecurringDates = this.calendarReadPlatformService
+                            .generateNextTenRecurringDates(collectionMeetingCalendar);
+                    final MeetingData lastMeeting = this.meetingReadPlatformService.retrieveLastMeeting(collectionMeetingCalendar
+                            .getCalendarInstanceId());
+                    final LocalDate recentEligibleMeetingDate = this.calendarReadPlatformService
+                            .generateNextEligibleMeetingDateForCollection(collectionMeetingCalendar, lastMeeting);
+                    collectionMeetingCalendar = CalendarData.withRecurringDates(collectionMeetingCalendar, recurringDates,
+                            nextTenRecurringDates, recentEligibleMeetingDate);
+                }
+            }
+
+            group = GroupGeneralData.withAssocations(group, membersOfGroup, activeClientMembers,
+                    groupRoles, calendars, collectionMeetingCalendar);
+        }
+
+        if (roleId != null) {
+            selectedRole = this.groupRolesReadPlatformService.retrieveGroupRole(groupId, roleId);
+            if (selectedRole != null) {
+                group = GroupGeneralData.updateSelectedRole(group, selectedRole);
+            }
+        }
+
+        final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
+        if (template) {
+            final GroupGeneralData templateGroup = this.groupReadPlatformService.retrieveTemplate(group.officeId(), false,
+                    staffInSelectedOfficeOnly);
+            group = GroupGeneralData.withTemplate(templateGroup, group);
+        }
+
+        return this.groupGeneralApiJsonSerializer.serialize(settings, group, GroupingTypesApiConstants.GROUP_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createGroup() //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{groupId}/command/unassign_staff")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String unassignLoanOfficer(@PathParam("groupId") final Long groupId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .unassignGroupStaff(groupId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @PUT
+    @Path("{groupId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("groupId") final Long groupId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateGroup(groupId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build(); //
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{groupId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("groupId") final Long groupId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteGroup(groupId) //
+                .build(); //
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{groupId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String activateOrGenerateCollectionSheet(@PathParam("groupId") final Long groupId,
+            @QueryParam("command") final String commandParam, @QueryParam("roleId") final Long roleId, final String apiRequestBodyAsJson,
+            @Context final UriInfo uriInfo) {
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "activate")) {
+            final CommandWrapper commandRequest = builder.activateGroup(groupId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "associateClients")) {
+            final CommandWrapper commandRequest = builder.associateClientsToGroup(groupId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "disassociateClients")) {
+            final CommandWrapper commandRequest = builder.disassociateClientsFromGroup(groupId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "generateCollectionSheet")) {
+            final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
+            final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
+            final JLGCollectionSheetData collectionSheet = this.collectionSheetReadPlatformService.generateGroupCollectionSheet(groupId,
+                    query);
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+            return this.toApiJsonSerializer.serialize(settings, collectionSheet, GroupingTypesApiConstants.COLLECTIONSHEET_DATA_PARAMETERS);
+        } else if (is(commandParam, "saveCollectionSheet")) {
+            final CommandWrapper commandRequest = builder.saveGroupCollectionSheet(groupId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "unassignStaff")) {
+            final CommandWrapper commandRequest = builder.unassignGroupStaff(groupId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "assignStaff")) {
+            final CommandWrapper commandRequest = builder.assignGroupStaff(groupId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "assignRole")) {
+            final CommandWrapper commandRequest = builder.assignRole(groupId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "unassignRole")) {
+            final CommandWrapper commandRequest = builder.unassignRole(groupId, roleId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "updateRole")) {
+            final CommandWrapper commandRequest = builder.updateRole(groupId, roleId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "transferClients")) {
+            final CommandWrapper commandRequest = builder.transferClientsBetweenGroups(groupId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "close")) {
+            final CommandWrapper commandRequest = builder.closeGroup(groupId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", "generateCollectionSheet",
+                    "saveCollectionSheet", "unassignStaff", "assignRole", "unassignRole", "updateassignRole" });
+        }
+
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Path("{groupId}/accounts")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAccounts(@PathParam("groupId") final Long groupId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission("GROUP");
+
+        final AccountSummaryCollectionData groupAccount = this.accountDetailsReadPlatformService.retrieveGroupAccountDetails(groupId);
+
+        final Set<String> GROUP_ACCOUNTS_DATA_PARAMETERS = new HashSet<>(Arrays.asList("loanAccounts", "savingsAccounts",
+                "memberLoanAccounts", "memberSavingsAccounts"));
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.groupSummaryToApiJsonSerializer.serialize(settings, groupAccount, GROUP_ACCOUNTS_DATA_PARAMETERS);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsLevelApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsLevelApiResource.java
new file mode 100644
index 0000000..baa1889
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsLevelApiResource.java
@@ -0,0 +1,79 @@
+/**
+ * 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.group.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;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.group.data.GroupLevelData;
+import org.apache.fineract.portfolio.group.service.GroupLevelReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/grouplevels")
+@Component
+@Scope("singleton")
+public class GroupsLevelApiResource {
+
+    private static final Set<String> GROUPLEVEL_DATA_PARAMETERS = new HashSet<>(Arrays.asList("levelId", "levelName",
+            "parentLevelId", "parentLevelName", "childLevelId", "childLevelName", "superParent", "recursable", "canHaveClients"));
+
+    private final PlatformSecurityContext context;
+    private final GroupLevelReadPlatformService groupLevelReadPlatformService;
+    private final ToApiJsonSerializer<GroupLevelData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public GroupsLevelApiResource(final PlatformSecurityContext context, final GroupLevelReadPlatformService groupLevelReadPlatformService,
+            final ToApiJsonSerializer<GroupLevelData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper) {
+        this.context = context;
+        this.groupLevelReadPlatformService = groupLevelReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllGroups(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission("GROUP");
+
+        final Collection<GroupLevelData> groupLevel = this.groupLevelReadPlatformService.retrieveAllLevels();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return this.toApiJsonSerializer.serialize(settings, groupLevel, GROUPLEVEL_DATA_PARAMETERS);
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/CenterData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/CenterData.java
new file mode 100644
index 0000000..5b265b2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/CenterData.java
@@ -0,0 +1,185 @@
+/**
+ * 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.group.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing groups.
+ */
+public class CenterData {
+
+    private final Long id;
+    private String accountNo;
+    private final String name;
+    private final String externalId;
+    private final Long officeId;
+    private final String officeName;
+    private final Long staffId;
+    private final String staffName;
+    private final String hierarchy;
+
+    private final EnumOptionData status;
+    @SuppressWarnings("unused")
+    private final boolean active;
+    private final LocalDate activationDate;
+
+    private final GroupTimelineData timeline;
+    // associations
+    private final Collection<GroupGeneralData> groupMembers;
+
+    // template
+    private final Collection<GroupGeneralData> groupMembersOptions;
+    private final CalendarData collectionMeetingCalendar;
+    private final Collection<CodeValueData> closureReasons;
+    private final Collection<OfficeData> officeOptions;
+    private final Collection<StaffData> staffOptions;
+
+    public static CenterData template(final Long officeId, final String accountNo, final LocalDate activationDate, final Collection<OfficeData> officeOptions,
+            final Collection<StaffData> staffOptions, final Collection<GroupGeneralData> groupMembersOptions) {
+        final CalendarData collectionMeetingCalendar = null;
+        final Collection<CodeValueData> closureReasons = null;
+        final GroupTimelineData timeline = null;
+        return new CenterData(null, accountNo, null, null, null, activationDate, officeId, null, null, null, null, null, officeOptions, staffOptions,
+                groupMembersOptions, collectionMeetingCalendar, closureReasons, timeline);
+    }
+
+    public static CenterData withTemplate(final CenterData templateCenter, final CenterData center) {
+        return new CenterData(center.id, center.accountNo, center.name, center.externalId, center.status, center.activationDate, center.officeId,
+                center.officeName, center.staffId, center.staffName, center.hierarchy, center.groupMembers, templateCenter.officeOptions,
+                templateCenter.staffOptions, templateCenter.groupMembersOptions, templateCenter.collectionMeetingCalendar,
+                templateCenter.closureReasons, center.timeline);
+    }
+
+    public static CenterData instance(final Long id, final String accountNo, final String name, final String externalId, final EnumOptionData status,
+            final LocalDate activationDate, final Long officeId, final String officeName, final Long staffId, final String staffName,
+            final String hierarchy, final GroupTimelineData timeline, final CalendarData collectionMeetingCalendar) {
+
+        final Collection<GroupGeneralData> groupMembers = null;
+        final Collection<OfficeData> officeOptions = null;
+        final Collection<StaffData> staffOptions = null;
+        final Collection<GroupGeneralData> groupMembersOptions = null;
+        final Collection<CodeValueData> closureReasons = null;
+
+        return new CenterData(id, accountNo, name, externalId, status, activationDate, officeId, officeName, staffId, staffName, hierarchy,
+                groupMembers, officeOptions, staffOptions, groupMembersOptions, collectionMeetingCalendar, closureReasons, timeline);
+    }
+
+    public static CenterData withAssociations(final CenterData centerData, final Collection<GroupGeneralData> groupMembers,
+            final CalendarData collectionMeetingCalendar) {
+        return new CenterData(centerData.id, centerData.accountNo, centerData.name, centerData.externalId, centerData.status, centerData.activationDate,
+                centerData.officeId, centerData.officeName, centerData.staffId, centerData.staffName, centerData.hierarchy, groupMembers,
+                centerData.officeOptions, centerData.staffOptions, centerData.groupMembersOptions, collectionMeetingCalendar,
+                centerData.closureReasons, centerData.timeline);
+    }
+
+    public static CenterData withClosureReasons(final Collection<CodeValueData> closureReasons) {
+        final Long id = null;
+        final String accountNo = null;
+        final String name = null;
+        final String externalId = null;
+        final EnumOptionData status = null;
+        final LocalDate activationDate = null;
+        final Long officeId = null;
+        final String officeName = null;
+        final Long staffId = null;
+        final String staffName = null;
+        final String hierarchy = null;
+        final Collection<GroupGeneralData> groupMembers = null;
+        final Collection<OfficeData> officeOptions = null;
+        final Collection<StaffData> staffOptions = null;
+        final Collection<GroupGeneralData> groupMembersOptions = null;
+        final CalendarData collectionMeetingCalendar = null;
+        final GroupTimelineData timeline = null;
+        return new CenterData(id, accountNo, name, externalId, status, activationDate, officeId, officeName, staffId, staffName, hierarchy,
+                groupMembers, officeOptions, staffOptions, groupMembersOptions, collectionMeetingCalendar, closureReasons, timeline);
+    }
+
+    private CenterData(final Long id, final String accountNo, final String name, final String externalId, final EnumOptionData status,
+            final LocalDate activationDate, final Long officeId, final String officeName, final Long staffId, final String staffName,
+            final String hierarchy, final Collection<GroupGeneralData> groupMembers, final Collection<OfficeData> officeOptions,
+            final Collection<StaffData> staffOptions, final Collection<GroupGeneralData> groupMembersOptions,
+            final CalendarData collectionMeetingCalendar, final Collection<CodeValueData> closureReasons, final GroupTimelineData timeline) {
+        this.id = id;
+        this.accountNo = accountNo;
+        this.name = name;
+        this.externalId = externalId;
+        this.status = status;
+        if (status != null) {
+            this.active = status.getId().equals(300L);
+        } else {
+            this.active = false;
+        }
+        this.activationDate = activationDate;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.staffId = staffId;
+        this.staffName = staffName;
+        this.hierarchy = hierarchy;
+
+        this.groupMembers = groupMembers;
+
+        //
+        this.officeOptions = officeOptions;
+        this.staffOptions = staffOptions;
+        this.groupMembersOptions = groupMembersOptions;
+        this.collectionMeetingCalendar = collectionMeetingCalendar;
+        this.closureReasons = closureReasons;
+        this.timeline = timeline;
+    }
+
+    public Long officeId() {
+        return this.officeId;
+    }
+
+    public Long staffId() {
+        return this.staffId;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+    
+    public String getAccountNo(){
+    	return this.accountNo;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getHierarchy() {
+        return this.hierarchy;
+    }
+
+    public CalendarData getCollectionMeetingCalendar() {
+        return collectionMeetingCalendar;
+    }
+
+    public String getStaffName() {
+        return this.staffName;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupGeneralData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupGeneralData.java
new file mode 100644
index 0000000..942e655
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupGeneralData.java
@@ -0,0 +1,259 @@
+/**
+ * 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.group.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a general group (so may or may not have a
+ * parent).
+ */
+public class GroupGeneralData {
+
+    private final Long id;
+    private final String accountNo;
+    private final String name;
+    private final String externalId;
+
+    private final EnumOptionData status;
+    @SuppressWarnings("unused")
+    private final Boolean active;
+    private final LocalDate activationDate;
+
+    private final Long officeId;
+    private final String officeName;
+    private final Long centerId;
+    private final String centerName;
+    private final Long staffId;
+    private final String staffName;
+    private final String hierarchy;
+    private final String groupLevel;
+
+    // associations
+    private final Collection<ClientData> clientMembers;
+    private final Collection<ClientData> activeClientMembers;
+    private final Collection<GroupRoleData> groupRoles;
+    private final Collection<CalendarData> calendarsData;
+    private final CalendarData collectionMeetingCalendar;
+
+    // template
+    private final Collection<CenterData> centerOptions;
+    private final Collection<OfficeData> officeOptions;
+    private final Collection<StaffData> staffOptions;
+    private final Collection<ClientData> clientOptions;
+    private final Collection<CodeValueData> availableRoles;
+    private final GroupRoleData selectedRole;
+    private final Collection<CodeValueData> closureReasons;
+    private final GroupTimelineData timeline;
+
+    public static GroupGeneralData lookup(final Long groupId, final String accountNo, final String groupName) {
+        final Collection<ClientData> clientMembers = null;
+        final Collection<GroupRoleData> groupRoles = null;
+        final Collection<CodeValueData> closureReasons = null;
+        return new GroupGeneralData(groupId, accountNo, groupName, null, null, null, null, null, null, null, null, null, null, null, clientMembers, null, null,
+                null, null, null, groupRoles, null, null, null, null, closureReasons, null);
+    }
+
+    public static GroupGeneralData template(final Long officeId, final Long centerId, final String accountNo, final String centerName, final Long staffId,
+            final String staffName, final Collection<CenterData> centerOptions, final Collection<OfficeData> officeOptions,
+            final Collection<StaffData> staffOptions, final Collection<ClientData> clientOptions,
+            final Collection<CodeValueData> availableRoles) {
+
+        final Collection<ClientData> clientMembers = null;
+        final Collection<GroupRoleData> groupRoles = null;
+        final Collection<CodeValueData> closureReasons = null;
+
+        return new GroupGeneralData(null, accountNo , null, null, null, null, officeId, null, centerId, centerName, staffId, staffName, null, null,
+                clientMembers, null, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, null, null, null,
+                closureReasons, null);
+    }
+
+    public static GroupGeneralData withTemplate(final GroupGeneralData templatedGrouping, final GroupGeneralData grouping) {
+        return new GroupGeneralData(grouping.id, grouping.accountNo, grouping.name, grouping.externalId, grouping.status, grouping.activationDate,
+                grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
+                grouping.hierarchy, grouping.groupLevel, grouping.clientMembers, grouping.activeClientMembers, templatedGrouping.centerOptions, templatedGrouping.officeOptions,
+                templatedGrouping.staffOptions, templatedGrouping.clientOptions, grouping.groupRoles, templatedGrouping.availableRoles,
+                grouping.selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar, grouping.closureReasons,
+                templatedGrouping.timeline);
+    }
+
+    public static GroupGeneralData withAssocations(final GroupGeneralData grouping, final Collection<ClientData> membersOfGroup,
+            final Collection<ClientData> activeClientMembers, final Collection<GroupRoleData> groupRoles, final Collection<CalendarData> calendarsData,
+            final CalendarData collectionMeetingCalendar) {
+        return new GroupGeneralData(grouping.id, grouping.accountNo, grouping.name, grouping.externalId, grouping.status, grouping.activationDate,
+                grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
+                grouping.hierarchy, grouping.groupLevel, membersOfGroup, activeClientMembers, grouping.centerOptions, grouping.officeOptions, grouping.staffOptions,
+                grouping.clientOptions, groupRoles, grouping.availableRoles, grouping.selectedRole, calendarsData,
+                collectionMeetingCalendar, grouping.closureReasons, grouping.timeline);
+    }
+
+    public static GroupGeneralData instance(final Long id, final String accountNo, final String name, final String externalId, final EnumOptionData status,
+            final LocalDate activationDate, final Long officeId, final String officeName, final Long centerId, final String centerName,
+            final Long staffId, final String staffName, final String hierarchy, final String groupLevel, final GroupTimelineData timeline) {
+
+        final Collection<ClientData> clientMembers = null;
+        final Collection<ClientData> activeClientMembers = null;
+        final Collection<CenterData> centerOptions = null;
+        final Collection<OfficeData> officeOptions = null;
+        final Collection<StaffData> staffOptions = null;
+        final Collection<ClientData> clientOptions = null;
+        final Collection<GroupRoleData> groupRoles = null;
+        final Collection<CodeValueData> availableRoles = null;
+        final GroupRoleData role = null;
+        final Collection<CalendarData> calendarsData = null;
+        final CalendarData collectionMeetingCalendar = null;
+        final Collection<CodeValueData> closureReasons = null;
+
+        return new GroupGeneralData(id, accountNo, name, externalId, status, activationDate, officeId, officeName, centerId, centerName, staffId,
+                staffName, hierarchy, groupLevel, clientMembers, activeClientMembers, centerOptions, officeOptions, staffOptions,
+                clientOptions, groupRoles, availableRoles, role, calendarsData, collectionMeetingCalendar, closureReasons, timeline);
+    }
+
+    private GroupGeneralData(final Long id, final String accountNo, final String name, final String externalId, final EnumOptionData status,
+            final LocalDate activationDate, final Long officeId, final String officeName, final Long centerId, final String centerName,
+            final Long staffId, final String staffName, final String hierarchy, final String groupLevel, final Collection<ClientData> clientMembers,
+            final Collection<ClientData> activeClientMembers, final Collection<CenterData> centerOptions,
+            final Collection<OfficeData> officeOptions, final Collection<StaffData> staffOptions,
+            final Collection<ClientData> clientOptions, final Collection<GroupRoleData> groupRoles,
+            final Collection<CodeValueData> availableRoles, final GroupRoleData role,
+            final Collection<CalendarData> calendarsData, final CalendarData collectionMeetingCalendar,
+            final Collection<CodeValueData> closureReasons, final GroupTimelineData timeline) {
+        this.id = id;
+        this.accountNo = accountNo;
+        this.name = name;
+        this.externalId = externalId;
+        this.status = status;
+        if (status != null) {
+            this.active = status.getId().equals(300l);
+        } else {
+            this.active = null;
+        }
+        this.activationDate = activationDate;
+
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.centerId = centerId;
+        this.centerName = centerName;
+        this.staffId = staffId;
+        this.staffName = staffName;
+        this.hierarchy = hierarchy;
+        this.groupLevel = groupLevel;
+
+        // associations
+        this.clientMembers = clientMembers;
+        this.activeClientMembers = activeClientMembers;
+
+        // template
+        this.centerOptions = centerOptions;
+        this.officeOptions = officeOptions;
+        this.staffOptions = staffOptions;
+
+        if (clientMembers != null && clientOptions != null) {
+            clientOptions.removeAll(clientMembers);
+        }
+        this.clientOptions = clientOptions;
+        this.groupRoles = groupRoles;
+        this.availableRoles = availableRoles;
+        this.selectedRole = role;
+        this.calendarsData = calendarsData;
+        this.collectionMeetingCalendar = collectionMeetingCalendar;
+        this.closureReasons = closureReasons;
+        this.timeline = timeline;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+    
+    public String getAccountNo(){
+    	return this.accountNo;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public Long officeId() {
+        return this.officeId;
+    }
+
+    public String getHierarchy() {
+        return this.hierarchy;
+    }
+
+    public boolean isChildGroup() {
+        return this.centerId == null ? false : true;
+    }
+
+    public Long getParentId() {
+        return this.centerId;
+    }
+
+    public static GroupGeneralData updateSelectedRole(final GroupGeneralData grouping, final GroupRoleData selectedRole) {
+        return new GroupGeneralData(grouping.id, grouping.accountNo, grouping.name, grouping.externalId, grouping.status, grouping.activationDate,
+                grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
+                grouping.hierarchy, grouping.groupLevel, grouping.clientMembers, grouping.activeClientMembers, grouping.centerOptions,
+                grouping.officeOptions, grouping.staffOptions, grouping.clientOptions, grouping.groupRoles, grouping.availableRoles,
+                selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar, grouping.closureReasons, null);
+    }
+
+    public static GroupGeneralData withClosureReasons(final Collection<CodeValueData> closureReasons) {
+        final Long id = null;
+        final String accountNo = null;
+        final String name = null;
+        final String externalId = null;
+        final EnumOptionData status = null;
+        final LocalDate activationDate = null;
+        final Long officeId = null;
+        final String officeName = null;
+        final Long centerId = null;
+        final String centerName = null;
+        final Long staffId = null;
+        final String staffName = null;
+        final String hierarchy = null;
+        final String groupLevel = null;
+        final Collection<ClientData> clientMembers = null;
+        final Collection<ClientData> activeClientMembers = null;
+        final Collection<CenterData> centerOptions = null;
+        final Collection<OfficeData> officeOptions = null;
+        final Collection<StaffData> staffOptions = null;
+        final Collection<ClientData> clientOptions = null;
+        final Collection<GroupRoleData> groupRoles = null;
+        final Collection<CodeValueData> availableRoles = null;
+        final GroupRoleData role = null;
+        final Collection<CalendarData> calendarsData = null;
+        final CalendarData collectionMeetingCalendar = null;
+
+        return new GroupGeneralData(id, accountNo, name, externalId, status, activationDate, officeId, officeName, centerId, centerName, staffId,
+                staffName, hierarchy, groupLevel, clientMembers, activeClientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles,
+                availableRoles, role, calendarsData, collectionMeetingCalendar, closureReasons, null);
+    }
+
+    public Collection<ClientData> clientMembers() {
+        return this.clientMembers;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupLevelData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupLevelData.java
new file mode 100644
index 0000000..769a525
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupLevelData.java
@@ -0,0 +1,87 @@
+/**
+ * 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.group.data;
+
+/**
+ * Immutable data object representing groups.
+ */
+public class GroupLevelData {
+
+    private final Long levelId;
+    private final String levelName;
+    private final Long parentLevelId;
+    private final String parentLevelName;
+    private final Long childLevelId;
+    private final String childLevelName;
+    private final boolean superParent;
+    private final boolean recursable;
+    private final boolean canHaveClients;
+
+    public GroupLevelData(final Long levelId, final String levelName, final Long parentLevelId, final String parentLevelName,
+            final Long childLevelId, final String childLevelName, final boolean superParent, final boolean recursable,
+            final boolean canHaveClients) {
+        super();
+        this.levelId = levelId;
+        this.levelName = levelName;
+        this.parentLevelId = parentLevelId;
+        this.parentLevelName = parentLevelName;
+        this.childLevelId = childLevelId;
+        this.childLevelName = childLevelName;
+        this.superParent = superParent;
+        this.recursable = recursable;
+        this.canHaveClients = canHaveClients;
+    }
+
+    public String getParentLevelName() {
+        return this.parentLevelName;
+    }
+
+    public Long getChildLevelId() {
+        return this.childLevelId;
+    }
+
+    public String getChildLevelName() {
+        return this.childLevelName;
+    }
+
+    public Long getLevelId() {
+        return this.levelId;
+    }
+
+    public Long getParentLevelId() {
+        return this.parentLevelId;
+    }
+
+    public String getLevelName() {
+        return this.levelName;
+    }
+
+    public boolean isSuperParent() {
+        return this.superParent;
+    }
+
+    public boolean isRecursable() {
+        return this.recursable;
+    }
+
+    public boolean isCanHaveClients() {
+        return this.canHaveClients;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupRoleData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupRoleData.java
new file mode 100644
index 0000000..337924c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupRoleData.java
@@ -0,0 +1,45 @@
+/**
+ * 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.group.data;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+
+public class GroupRoleData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final CodeValueData role;
+    @SuppressWarnings("unused")
+    private final Long clientId;
+    @SuppressWarnings("unused")
+    private final String clientName;
+
+    public static final GroupRoleData template() {
+        return new GroupRoleData(null, null, null, null);
+    }
+
+    public GroupRoleData(final Long id, final CodeValueData role, final Long clientId, final String clientName) {
+        this.id = id;
+        this.role = role;
+        this.clientId = clientId;
+        this.clientName = clientName;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupSummary.java
new file mode 100644
index 0000000..cff323d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupSummary.java
@@ -0,0 +1,56 @@
+/**
+ * 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.group.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.monetary.data.MoneyData;
+
+public class GroupSummary {
+
+    private final Long totalActiveClients;
+    private final Long totalChildGroups;
+    private final Collection<MoneyData> totalLoanPortfolio;
+    private final Collection<MoneyData> totalSavings;
+
+    public GroupSummary(final Long totalActiveClients, final Long totalChildGroups, final Collection<MoneyData> totalLoanPortfolio,
+            final Collection<MoneyData> totalSavings) {
+        this.totalActiveClients = totalActiveClients;
+        this.totalChildGroups = totalChildGroups;
+        this.totalLoanPortfolio = totalLoanPortfolio;
+        this.totalSavings = totalSavings;
+    }
+
+    public Long getTotalActiveClients() {
+        return this.totalActiveClients;
+    }
+
+    public Long getTotalChildGroups() {
+        return this.totalChildGroups;
+    }
+
+    public Collection<MoneyData> getTotalLoanPortfolio() {
+        return this.totalLoanPortfolio;
+    }
+
+    public Collection<MoneyData> getTotalSavings() {
+        return this.totalSavings;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupTimelineData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupTimelineData.java
new file mode 100644
index 0000000..8abde9c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupTimelineData.java
@@ -0,0 +1,67 @@
+/**
+ * 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.group.data;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object represent the important time-line events of a
+ * group/center.
+ * 
+ */
+@SuppressWarnings("unused")
+public class GroupTimelineData {
+
+    private final LocalDate submittedOnDate;
+    private final String submittedByUsername;
+    private final String submittedByFirstname;
+    private final String submittedByLastname;
+
+    private final LocalDate activatedOnDate;
+    private final String activatedByUsername;
+    private final String activatedByFirstname;
+    private final String activatedByLastname;
+
+    private final LocalDate closedOnDate;
+    private final String closedByUsername;
+    private final String closedByFirstname;
+    private final String closedByLastname;
+
+    public GroupTimelineData(final LocalDate submittedOnDate, final String submittedByUsername, final String submittedByFirstname,
+            final String submittedByLastname, final LocalDate activatedOnDate, final String activatedByUsername,
+            final String activatedByFirstname, final String activatedByLastname, final LocalDate closedOnDate,
+            final String closedByUsername, final String closedByFirstname, final String closedByLastname) {
+        this.submittedOnDate = submittedOnDate;
+        this.submittedByUsername = submittedByUsername;
+        this.submittedByFirstname = submittedByFirstname;
+        this.submittedByLastname = submittedByLastname;
+
+        this.activatedOnDate = activatedOnDate;
+        this.activatedByUsername = activatedByUsername;
+        this.activatedByFirstname = activatedByFirstname;
+        this.activatedByLastname = activatedByLastname;
+
+        this.closedOnDate = closedOnDate;
+        this.closedByUsername = closedByUsername;
+        this.closedByFirstname = closedByFirstname;
+        this.closedByLastname = closedByLastname;
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/StaffCenterData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/StaffCenterData.java
new file mode 100644
index 0000000..8a84371
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/StaffCenterData.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.portfolio.group.data;
+
+import java.util.Collection;
+
+public class StaffCenterData {
+
+    private final Long staffId;
+    @SuppressWarnings("unused")
+    private final String staffName;
+    private final Collection<CenterData> meetingFallCenters;
+
+    private StaffCenterData(final Long staffId, final String staffName, final Collection<CenterData> meetingFallCenters) {
+        this.staffId = staffId;
+        this.staffName = staffName;
+        this.meetingFallCenters = meetingFallCenters;
+    }
+
+    public static StaffCenterData instance(final Long staffId, final String staffName, final Collection<CenterData> meetingFallCenters) {
+        return new StaffCenterData(staffId, staffName, meetingFallCenters);
+    }
+
+    public Long getStaffId() {
+        return staffId;
+    }
+
+    public Collection<CenterData> getMeetingFallCenters() {
+        return meetingFallCenters;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/Group.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/Group.java
new file mode 100644
index 0000000..325416d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/Group.java
@@ -0,0 +1,743 @@
+/**
+ * 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.group.domain;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Transient;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
+import org.apache.fineract.portfolio.group.exception.ClientExistInGroupException;
+import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
+import org.apache.fineract.portfolio.group.exception.GroupExistsInCenterException;
+import org.apache.fineract.portfolio.group.exception.GroupNotExistsInCenterException;
+import org.apache.fineract.portfolio.group.exception.InvalidGroupStateTransitionException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_group")
+public final class Group extends AbstractPersistable<Long> {
+
+    @Column(name = "external_id", length = 100, unique = true)
+    private String externalId;
+
+    /**
+     * A value from {@link GroupingTypeStatus}.
+     */
+    @Column(name = "status_enum", nullable = false)
+    private Integer status;
+
+    @Column(name = "activation_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date activationDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "activatedon_userid", nullable = true)
+    private AppUser activatedBy;
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @ManyToOne
+    @JoinColumn(name = "staff_id", nullable = true)
+    private Staff staff;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "parent_id")
+    private Group parent;
+
+    @ManyToOne
+    @JoinColumn(name = "level_id", nullable = false)
+    private GroupLevel groupLevel;
+
+    @Column(name = "display_name", length = 100, unique = true)
+    private String name;
+
+    @Column(name = "hierarchy", length = 100)
+    private String hierarchy;
+
+    @OneToMany(fetch = FetchType.EAGER)
+    @JoinColumn(name = "parent_id")
+    private final List<Group> groupMembers = new LinkedList<>();
+
+    @ManyToMany
+    @JoinTable(name = "m_group_client", joinColumns = @JoinColumn(name = "group_id"), inverseJoinColumns = @JoinColumn(name = "client_id"))
+    private Set<Client> clientMembers = new HashSet<>();
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "closure_reason_cv_id", nullable = true)
+    private CodeValue closureReason;
+
+    @Column(name = "closedon_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date closureDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "closedon_userid", nullable = true)
+    private AppUser closedBy;
+
+    @Column(name = "submittedon_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date submittedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "submittedon_userid", nullable = true)
+    private AppUser submittedBy;
+
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "center", orphanRemoval = true)
+    private Set<StaffAssignmentHistory> staffHistory;
+    
+    @Column(name = "account_no", length = 20, unique = true, nullable = false)
+    private String accountNumber;
+    
+    @Transient
+    private boolean accountNumberRequiresAutoGeneration = false;
+
+    // JPA default constructor for entity
+    protected Group() {
+        this.name = null;
+        this.externalId = null;
+        this.clientMembers = new HashSet<>();
+    }
+
+    public static Group newGroup(final Office office, final Staff staff, final Group parent, final GroupLevel groupLevel,
+            final String name, final String externalId, final boolean active, final LocalDate activationDate,
+            final Set<Client> clientMembers, final Set<Group> groupMembers, final LocalDate submittedOnDate, final AppUser currentUser,
+            final String accountNo) {
+
+        // By default new group is created in PENDING status, unless explicitly
+        // status is set to active
+        GroupingTypeStatus status = GroupingTypeStatus.PENDING;
+        LocalDate groupActivationDate = null;
+        if (active) {
+            status = GroupingTypeStatus.ACTIVE;
+            groupActivationDate = activationDate;
+        }
+        
+        return new Group(office, staff, parent, groupLevel, name, externalId, status, groupActivationDate, clientMembers, groupMembers,
+                submittedOnDate, currentUser, accountNo);
+    }
+
+    private Group(final Office office, final Staff staff, final Group parent, final GroupLevel groupLevel, final String name,
+            final String externalId, final GroupingTypeStatus status, final LocalDate activationDate, final Set<Client> clientMembers,
+            final Set<Group> groupMembers, final LocalDate submittedOnDate, final AppUser currentUser, final String accountNo) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        this.office = office;
+        this.staff = staff;
+        this.groupLevel = groupLevel;
+        this.parent = parent;
+
+        if (parent != null) {
+            this.parent.addChild(this);
+        }
+        
+        if (StringUtils.isBlank(accountNo)) {
+            this.accountNumber = new RandomPasswordGenerator(19).generate();
+            this.accountNumberRequiresAutoGeneration = true;
+        } else {
+            this.accountNumber = accountNo;
+        }
+
+        if (StringUtils.isNotBlank(name)) {
+            this.name = name.trim();
+        } else {
+            this.name = null;
+        }
+        if (StringUtils.isNotBlank(externalId)) {
+            this.externalId = externalId.trim();
+        } else {
+            this.externalId = null;
+        }
+
+        if (groupMembers != null) {
+            this.groupMembers.addAll(groupMembers);
+        }
+
+        this.submittedOnDate = submittedOnDate.toDate();
+        this.submittedBy = currentUser;
+        this.staffHistory = null;
+
+        associateClients(clientMembers);
+
+        /*
+         * Always keep status change at the bottom, as status change rule
+         * depends on the attribute's value
+         */
+
+        setStatus(activationDate, currentUser, status, dataValidationErrors);
+
+        throwExceptionIfErrors(dataValidationErrors);
+    }
+
+    private void setStatus(final LocalDate activationDate, final AppUser loginUser, final GroupingTypeStatus status,
+            final List<ApiParameterError> dataValidationErrors) {
+
+        if (status.isActive()) {
+            activate(loginUser, activationDate, dataValidationErrors);
+        } else {
+            this.status = status.getValue();
+        }
+
+    }
+
+    private void activate(final AppUser currentUser, final LocalDate activationLocalDate, final List<ApiParameterError> dataValidationErrors) {
+
+        validateStatusNotEqualToActiveAndLogError(dataValidationErrors);
+        if (dataValidationErrors.isEmpty()) {
+            this.status = GroupingTypeStatus.ACTIVE.getValue();
+            setActivationDate(activationLocalDate.toDate(), currentUser, dataValidationErrors);
+        }
+
+    }
+
+    public void activate(final AppUser currentUser, final LocalDate activationLocalDate) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        activate(currentUser, activationLocalDate, dataValidationErrors);
+        if (this.isCenter() && this.hasStaff()) {
+            Staff staff = this.getStaff();
+            this.reassignStaff(staff, activationLocalDate);
+        }
+        throwExceptionIfErrors(dataValidationErrors);
+
+    }
+
+    private void setActivationDate(final Date activationDate, final AppUser loginUser, final List<ApiParameterError> dataValidationErrors) {
+
+        if (activationDate != null) {
+            this.activationDate = activationDate;
+            this.activatedBy = loginUser;
+        }
+
+        validateActivationDate(dataValidationErrors);
+
+    }
+
+    public boolean isActivatedAfter(final LocalDate submittedOn) {
+        return getActivationLocalDate().isAfter(submittedOn);
+    }
+
+    public boolean isNotActive() {
+        return !isActive();
+    }
+
+    public boolean isActive() {
+        return this.status != null ? GroupingTypeStatus.fromInt(this.status).isActive() : false;
+    }
+
+    private boolean isDateInTheFuture(final LocalDate localDate) {
+        return localDate.isAfter(DateUtils.getLocalDateOfTenant());
+    }
+
+    public boolean isNotPending() {
+        return !isPending();
+    }
+
+    public boolean isPending() {
+        return GroupingTypeStatus.fromInt(this.status).isPending();
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+        if (command.isChangeInIntegerParameterNamed(GroupingTypesApiConstants.statusParamName, this.status)) {
+            final Integer newValue = command.integerValueOfParameterNamed(GroupingTypesApiConstants.statusParamName);
+            actualChanges.put(GroupingTypesApiConstants.statusParamName, GroupingTypeEnumerations.status(newValue));
+            this.status = GroupingTypeStatus.fromInt(newValue).getValue();
+        }
+
+        if (command.isChangeInStringParameterNamed(GroupingTypesApiConstants.externalIdParamName, this.externalId)) {
+            final String newValue = command.stringValueOfParameterNamed(GroupingTypesApiConstants.externalIdParamName);
+            actualChanges.put(GroupingTypesApiConstants.externalIdParamName, newValue);
+            this.externalId = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInLongParameterNamed(GroupingTypesApiConstants.officeIdParamName, this.office.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(GroupingTypesApiConstants.officeIdParamName);
+            actualChanges.put(GroupingTypesApiConstants.officeIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(GroupingTypesApiConstants.staffIdParamName, staffId())) {
+            final Long newValue = command.longValueOfParameterNamed(GroupingTypesApiConstants.staffIdParamName);
+            actualChanges.put(GroupingTypesApiConstants.staffIdParamName, newValue);
+        }
+
+        if (command.isChangeInStringParameterNamed(GroupingTypesApiConstants.nameParamName, this.name)) {
+            final String newValue = command.stringValueOfParameterNamed(GroupingTypesApiConstants.nameParamName);
+            actualChanges.put(GroupingTypesApiConstants.nameParamName, newValue);
+            this.name = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+
+        if (command.isChangeInLocalDateParameterNamed(GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(GroupingTypesApiConstants.activationDateParamName);
+            actualChanges.put(GroupingTypesApiConstants.activationDateParamName, valueAsInput);
+            actualChanges.put(GroupingTypesApiConstants.dateFormatParamName, dateFormatAsInput);
+            actualChanges.put(GroupingTypesApiConstants.localeParamName, localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(GroupingTypesApiConstants.activationDateParamName);
+            this.activationDate = newValue.toDate();
+        }
+        
+        if (command.isChangeInStringParameterNamed(GroupingTypesApiConstants.accountNoParamName, this.accountNumber)) {
+            final String newValue = command.stringValueOfParameterNamed(GroupingTypesApiConstants.accountNoParamName);
+            actualChanges.put(GroupingTypesApiConstants.accountNoParamName, newValue);
+            this.accountNumber = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        return actualChanges;
+    }
+
+    public LocalDate getSubmittedOnDate() {
+        return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.submittedOnDate), null);
+    }
+
+    public LocalDate getActivationLocalDate() {
+        LocalDate activationLocalDate = null;
+        if (this.activationDate != null) {
+            activationLocalDate = new LocalDate(this.activationDate);
+        }
+        return activationLocalDate;
+    }
+
+    public List<String> associateClients(final Set<Client> clientMembersSet) {
+        final List<String> differences = new ArrayList<>();
+        for (final Client client : clientMembersSet) {
+            if (hasClientAsMember(client)) { throw new ClientExistInGroupException(client.getId(), getId()); }
+            this.clientMembers.add(client);
+            differences.add(client.getId().toString());
+        }
+
+        return differences;
+    }
+
+    public List<String> disassociateClients(final Set<Client> clientMembersSet) {
+        final List<String> differences = new ArrayList<>();
+        for (final Client client : clientMembersSet) {
+            if (hasClientAsMember(client)) {
+                this.clientMembers.remove(client);
+                differences.add(client.getId().toString());
+            } else {
+                throw new ClientNotInGroupException(client.getId(), getId());
+            }
+        }
+
+        return differences;
+    }
+
+    public boolean hasClientAsMember(final Client client) {
+        return this.clientMembers.contains(client);
+    }
+
+	public void generateHierarchy() {
+		if (this.parent != null) {
+			this.hierarchy = this.parent.hierarchyOf(getId());
+		} else {
+			this.hierarchy = "." + getId() + ".";
+			for (Group group : this.groupMembers) {
+				group.setParent(this);
+				group.generateHierarchy();
+			}
+		}
+	}
+    
+	public void resetHierarchy() {
+			this.hierarchy = "." + this.getId();
+	}
+    
+    private String hierarchyOf(final Long id) {
+    	return this.hierarchy + id.toString() + ".";
+    }
+
+    public boolean isOfficeIdentifiedBy(final Long officeId) {
+        return this.office.identifiedBy(officeId);
+    }
+
+    public Long officeId() {
+        return this.office.getId();
+    }
+
+    private Long staffId() {
+        Long staffId = null;
+        if (this.staff != null) {
+            staffId = this.staff.getId();
+        }
+        return staffId;
+    }
+   
+    private void addChild(final Group group) {
+        this.groupMembers.add(group);
+    }
+
+    public void updateStaff(final Staff staff) {
+        if (this.isCenter() && this.isActive()) {
+            LocalDate updatedDate = DateUtils.getLocalDateOfTenant();
+            reassignStaff(staff, updatedDate);
+        }
+        this.staff = staff;
+    }
+
+    public void unassignStaff() {
+        if (this.isCenter() && this.isActive()) {
+            LocalDate dateOfStaffUnassigned = DateUtils.getLocalDateOfTenant();
+            removeStaff(dateOfStaffUnassigned);
+        }
+        this.staff = null;
+    }
+
+    public GroupLevel getGroupLevel() {
+        return this.groupLevel;
+    }
+
+    public Staff getStaff() {
+        return this.staff;
+    }
+
+    public void setStaff(final Staff staff) {
+        this.staff = staff;
+    }
+
+    public Group getParent() {
+        return this.parent;
+    }
+
+    public void setParent(final Group parent) {
+        this.parent = parent;
+    }
+
+    public Office getOffice() {
+        return this.office;
+    }
+
+    public boolean isCenter() {
+        return this.groupLevel.isCenter();
+    }
+
+    public boolean isGroup() {
+        return this.groupLevel.isGroup();
+    }
+
+    public boolean isTransferInProgress() {
+        return GroupingTypeStatus.fromInt(this.status).isTransferInProgress();
+    }
+
+    public boolean isTransferOnHold() {
+        return GroupingTypeStatus.fromInt(this.status).isTransferOnHold();
+    }
+
+    public boolean isTransferInProgressOrOnHold() {
+        return isTransferInProgress() || isTransferOnHold();
+    }
+
+    public boolean isChildClient(final Long clientId) {
+        if (clientId != null && this.clientMembers != null && !this.clientMembers.isEmpty()) {
+            for (final Client client : this.clientMembers) {
+                if (client.getId().equals(clientId)) { return true; }
+            }
+        }
+        return false;
+    }
+
+    public boolean isChildGroup() {
+        return this.parent == null ? false : true;
+
+    }
+
+    public boolean isClosed() {
+        return GroupingTypeStatus.fromInt(this.status).isClosed();
+    }
+
+    public void close(final AppUser currentUser, final CodeValue closureReason, final LocalDate closureDate) {
+
+        if (isClosed()) {
+            final String errorMessage = "Group with identifier " + getId() + " is alread closed.";
+            throw new InvalidGroupStateTransitionException(this.groupLevel.getLevelName(), "close", "already.closed", errorMessage, getId());
+        }
+
+        if (isNotPending() && getActivationLocalDate().isAfter(closureDate)) {
+            final String errorMessage = "The Group closure Date " + closureDate + " cannot be before the group Activation Date "
+                    + getActivationLocalDate() + ".";
+            throw new InvalidGroupStateTransitionException(this.groupLevel.getLevelName(), "close",
+                    "date.cannot.before.group.actvation.date", errorMessage, closureDate, getActivationLocalDate());
+        }
+
+        this.closureReason = closureReason;
+        this.closureDate = closureDate.toDate();
+        this.status = GroupingTypeStatus.CLOSED.getValue();
+        this.closedBy = currentUser;
+    }
+
+    public boolean hasActiveClients() {
+        for (final Client client : this.clientMembers) {
+            if (!client.isClosed()) { return true; }
+        }
+        return false;
+    }
+
+    public boolean hasActiveGroups() {
+        for (final Group group : this.groupMembers) {
+            if (!group.isClosed()) { return true; }
+        }
+        return false;
+    }
+
+    public boolean hasGroupAsMember(final Group group) {
+        return this.groupMembers.contains(group);
+    }
+
+    public boolean hasStaff() {
+        if (this.staff != null) { return true; }
+        return false;
+    }
+
+    public List<String> associateGroups(final Set<Group> groupMembersSet) {
+
+        final List<String> differences = new ArrayList<>();
+        for (final Group group : groupMembersSet) {
+
+            if (group.isCenter()) {
+                final String defaultUserMessage = "Center can not assigned as a child";
+                throw new GeneralPlatformDomainRuleException("error.msg.center.cannot.be.assigned.as.child", defaultUserMessage,
+                        group.getId());
+            }
+
+            if (hasGroupAsMember(group)) { throw new GroupExistsInCenterException(getId(), group.getId()); }
+
+            if (group.isChildGroup()) {
+                final String defaultUserMessage = "Group is already associated with a center";
+                throw new GeneralPlatformDomainRuleException("error.msg.group.already.associated.with.center", defaultUserMessage, group
+                        .getParent().getId(), group.getId());
+            }
+
+            this.groupMembers.add(group);
+            differences.add(group.getId().toString());
+            group.setParent(this);
+    		group.generateHierarchy();
+        }
+
+        return differences;
+    }
+
+    public List<String> disassociateGroups(Set<Group> groupMembersSet) {
+
+        final List<String> differences = new ArrayList<>();
+        for (final Group group : groupMembersSet) {
+            if (hasGroupAsMember(group)) {
+                this.groupMembers.remove(group);
+                differences.add(group.getId().toString());
+    			group.resetHierarchy();
+            } else {
+                throw new GroupNotExistsInCenterException(group.getId(), getId());
+            }
+        }
+
+        return differences;
+    }
+
+    public Boolean isGroupsClientCountWithinMinMaxRange(Integer minClients, Integer maxClients) {
+
+        if (maxClients == null && minClients == null) { return true; }
+
+        // set minClients or maxClients to 0 if null
+
+        if (minClients == null) {
+            minClients = 0;
+        }
+
+        if (maxClients == null) {
+            maxClients = Integer.MAX_VALUE;
+        }
+
+        Set<Client> activeClientMembers = getActiveClientMembers();
+
+        if (activeClientMembers.size() >= minClients && activeClientMembers.size() <= maxClients) { return true; }
+        return false;
+    }
+
+    public Boolean isGroupsClientCountWithinMaxRange(Integer maxClients) {
+        Set<Client> activeClientMembers = getActiveClientMembers();
+        if (maxClients == null) {
+            return true;
+        } else if (activeClientMembers.size() <= maxClients) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public Set<Client> getActiveClientMembers() {
+        Set<Client> activeClientMembers = new HashSet<>();
+        for (Client client : this.clientMembers) {
+            if (client.isActive()) {
+                activeClientMembers.add(client);
+            }
+        }
+        return activeClientMembers;
+    }
+
+    private void validateActivationDate(final List<ApiParameterError> dataValidationErrors) {
+
+        if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) {
+
+            final String defaultUserMessage = "Submitted on date cannot be in the future.";
+            final String globalisationMessageCode = "error.msg.group.submittedOnDate.in.the.future";
+            final ApiParameterError error = ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage,
+                    GroupingTypesApiConstants.submittedOnDateParamName, this.submittedOnDate);
+
+            dataValidationErrors.add(error);
+        }
+
+        if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) {
+
+            final String defaultUserMessage = "Submitted on date cannot be after the activation date";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.submittedOnDate.after.activation.date",
+                    defaultUserMessage, GroupingTypesApiConstants.submittedOnDateParamName, this.submittedOnDate);
+
+            dataValidationErrors.add(error);
+        }
+
+        if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) {
+
+            final String defaultUserMessage = "Activation date cannot be in the future.";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.activationDate.in.the.future",
+                    defaultUserMessage, GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate());
+
+            dataValidationErrors.add(error);
+        }
+
+        if (getActivationLocalDate() != null) {
+            if (this.office.isOpeningDateAfter(getActivationLocalDate())) {
+                final String defaultUserMessage = "Activation date cannot be a date before the office opening date.";
+                final ApiParameterError error = ApiParameterError.parameterError(
+                        "error.msg.group.activationDate.cannot.be.before.office.activation.date", defaultUserMessage,
+                        GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate());
+                dataValidationErrors.add(error);
+            }
+        }
+    }
+
+    private void validateStatusNotEqualToActiveAndLogError(final List<ApiParameterError> dataValidationErrors) {
+
+        if (isActive()) {
+            final String defaultUserMessage = "Cannot activate group. Group is already active.";
+            final String globalisationMessageCode = "error.msg.group.already.active";
+            final ApiParameterError error = ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage,
+                    GroupingTypesApiConstants.activeParamName, true);
+            dataValidationErrors.add(error);
+        }
+    }
+
+    private void throwExceptionIfErrors(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public Set<Client> getClientMembers() {
+        return this.clientMembers;
+    }
+
+    // StaffAssignmentHistory[during center creation]
+    public void captureStaffHistoryDuringCenterCreation(final Staff newStaff, final LocalDate assignmentDate) {
+        if (this.isCenter() && this.isActive() && staff != null) {
+            this.staff = newStaff;
+            final StaffAssignmentHistory staffAssignmentHistory = StaffAssignmentHistory.createNew(this, this.staff, assignmentDate);
+            if (staffAssignmentHistory != null) {
+                staffHistory = new HashSet<>();
+                this.staffHistory.add(staffAssignmentHistory);
+            }
+        }
+    }
+
+    // StaffAssignmentHistory[assign staff]
+    public void reassignStaff(final Staff newStaff, final LocalDate assignmentDate) {
+        this.staff = newStaff;
+        final StaffAssignmentHistory staffAssignmentHistory = StaffAssignmentHistory.createNew(this, this.staff, assignmentDate);
+        this.staffHistory.add(staffAssignmentHistory);
+    }
+
+    // StaffAssignmentHistory[unassign staff]
+    public void removeStaff(final LocalDate unassignDate) {
+        final StaffAssignmentHistory latestHistoryRecord = findLatestIncompleteHistoryRecord();
+        if (latestHistoryRecord != null) {
+            latestHistoryRecord.updateEndDate(unassignDate);
+        }
+    }
+
+    private StaffAssignmentHistory findLatestIncompleteHistoryRecord() {
+
+        StaffAssignmentHistory latestRecordWithNoEndDate = null;
+        for (final StaffAssignmentHistory historyRecord : this.staffHistory) {
+            if (historyRecord.isCurrentRecord()) {
+                latestRecordWithNoEndDate = historyRecord;
+                break;
+            }
+        }
+        return latestRecordWithNoEndDate;
+    }
+    
+    public boolean isAccountNumberRequiresAutoGeneration() {
+        return this.accountNumberRequiresAutoGeneration;
+    }
+
+    public void setAccountNumberRequiresAutoGeneration(final boolean accountNumberRequiresAutoGeneration) {
+        this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
+    }
+    
+    public void updateAccountNo(final String accountIdentifier) {
+        this.accountNumber = accountIdentifier;
+        this.accountNumberRequiresAutoGeneration = false;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupLevel.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupLevel.java
new file mode 100644
index 0000000..132fc92
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupLevel.java
@@ -0,0 +1,98 @@
+/**
+ * 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.group.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_group_level")
+public class GroupLevel extends AbstractPersistable<Long> {
+
+    @Column(name = "parent_id")
+    private final Long parentId;
+
+    @Column(name = "super_parent", nullable = false)
+    private final boolean superParent;
+
+    @Column(name = "level_name", nullable = false, length = 100, unique = true)
+    private final String levelName;
+
+    @Column(name = "recursable", nullable = false)
+    private boolean recursable = false;
+
+    @Column(name = "can_have_clients", nullable = false)
+    private boolean canHaveClients = false;
+
+    public GroupLevel() {
+
+        this.parentId = null;
+        this.superParent = false;
+        this.levelName = null;
+        this.recursable = false;
+        this.canHaveClients = false;
+
+    }
+
+    public GroupLevel(final Long parentId, final boolean isSuperParent, final String levelName, final boolean recursable,
+            final boolean canHaveClients) {
+
+        this.superParent = isSuperParent;
+        this.parentId = parentId;
+        this.levelName = levelName;
+        this.recursable = recursable;
+        this.canHaveClients = canHaveClients;
+    }
+
+    public Long getParentId() {
+        return this.parentId;
+    }
+
+    public String getLevelName() {
+        return this.levelName;
+    }
+
+    public boolean isRecursable() {
+        return this.recursable;
+    }
+
+    public boolean canHaveClients() {
+        return this.canHaveClients;
+    }
+
+    public boolean isSuperParent() {
+        return this.superParent;
+    }
+
+    public boolean isIdentifiedByParentId(final Long parentLevelId) {
+        return this.parentId.equals(parentLevelId);
+    }
+
+    public boolean isCenter() {
+        return this.levelName.equalsIgnoreCase("Center");
+    }
+
+    public boolean isGroup() {
+        return this.levelName.equalsIgnoreCase("Group");
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupLevelRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupLevelRepository.java
new file mode 100644
index 0000000..6157bfa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupLevelRepository.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.group.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface GroupLevelRepository extends JpaRepository<GroupLevel, Long>, JpaSpecificationExecutor<GroupLevel> {
+
+    GroupLevel findBySuperParent(boolean superParent);
+
+    GroupLevel findByParentId(Long parentId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRepository.java
new file mode 100644
index 0000000..5082033
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRepository.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.group.domain;
+
+import java.util.Collection;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface GroupRepository extends JpaRepository<Group, Long>, JpaSpecificationExecutor<Group> {
+
+    Collection<Group> findByParentId(Long parentId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRepositoryWrapper.java
new file mode 100644
index 0000000..a67b90e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRepositoryWrapper.java
@@ -0,0 +1,65 @@
+/**
+ * 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.group.domain;
+
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link GroupRepository} that adds NULL checking and Error
+ * handling capabilities
+ * </p>
+ */
+@Service
+public class GroupRepositoryWrapper {
+
+    private final GroupRepository repository;
+
+    @Autowired
+    public GroupRepositoryWrapper(final GroupRepository repository) {
+        this.repository = repository;
+    }
+
+    public Group findOneWithNotFoundDetection(final Long id) {
+        final Group entity = this.repository.findOne(id);
+        if (entity == null) { throw new GroupNotFoundException(id); }
+        return entity;
+    }
+
+    public Group findByOfficeWithNotFoundDetection(final Long id, final Office office) {
+        final Group group = findOneWithNotFoundDetection(id);
+        if (group.getOffice().getId() != office.getId()) { throw new GroupNotFoundException(id); }
+        return group;
+    }
+
+    public void save(final Group entity) {
+        this.repository.save(entity);
+    }
+
+    public void saveAndFlush(final Group entity) {
+        this.repository.saveAndFlush(entity);
+    }
+
+    public void delete(final Group entity) {
+        this.repository.delete(entity);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRole.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRole.java
new file mode 100644
index 0000000..137c77a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRole.java
@@ -0,0 +1,91 @@
+/**
+ * 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.group.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_group_roles")
+public class GroupRole extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "group_id")
+    private Group group;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "client_id")
+    private Client client;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "role_cv_id")
+    private CodeValue role;
+
+    public GroupRole() {
+        // TODO Auto-generated constructor stub
+    }
+
+    public static final GroupRole createGroupRole(final Group group, final Client client, final CodeValue role) {
+        return new GroupRole(group, client, role);
+    }
+
+    public GroupRole(final Group group, final Client client, final CodeValue role) {
+        this.group = group;
+        this.client = client;
+        this.role = role;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(2);
+
+        if (command.isChangeInLongParameterNamed(GroupingTypesApiConstants.clientIdParamName, this.client.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(GroupingTypesApiConstants.clientIdParamName);
+            actualChanges.put(GroupingTypesApiConstants.clientIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(GroupingTypesApiConstants.roleParamName, this.role.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(GroupingTypesApiConstants.roleParamName);
+            actualChanges.put(GroupingTypesApiConstants.roleParamName, newValue);
+        }
+
+        return actualChanges;
+    }
+
+    public void updateRole(final CodeValue role) {
+        this.role = role;
+    }
+
+    public void updateClient(final Client client) {
+        this.client = client;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRoleRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRoleRepository.java
new file mode 100644
index 0000000..2202ad2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRoleRepository.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.group.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface GroupRoleRepository extends JpaRepository<GroupRole, Long>, JpaSpecificationExecutor<GroupRole> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRoleRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRoleRepositoryWrapper.java
new file mode 100644
index 0000000..3f700aa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupRoleRepositoryWrapper.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.domain;
+
+import org.apache.fineract.portfolio.group.exception.GroupRoleNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GroupRoleRepositoryWrapper {
+
+    private final GroupRoleRepository repository;
+
+    @Autowired
+    public GroupRoleRepositoryWrapper(final GroupRoleRepository rRepository) {
+        this.repository = rRepository;
+    }
+
+    public GroupRole findOneWithNotFoundDetection(final Long id) {
+        final GroupRole entity = this.repository.findOne(id);
+        if (entity == null) { throw new GroupRoleNotFoundException(id); }
+        return entity;
+    }
+
+    public void save(final GroupRole entity) {
+        this.repository.save(entity);
+    }
+
+    public void saveAndFlush(final GroupRole entity) {
+        this.repository.saveAndFlush(entity);
+    }
+
+    public void delete(final GroupRole entity) {
+        this.repository.delete(entity);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupTypes.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupTypes.java
new file mode 100644
index 0000000..28ed8cb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupTypes.java
@@ -0,0 +1,89 @@
+/**
+ * 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.group.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum GroupTypes {
+
+    INVALID(0l, "lendingStrategy.invalid", "invalid"), //
+    CENTER(1l, "groupTypes.center", "center"), //
+    GROUP(2l, "groupTypes.group", "group"); //
+
+    private Long id;
+    private String code;
+    private String value;
+
+    private GroupTypes(final Long id, final String code, final String value) {
+        this.id = id;
+        this.code = code;
+        this.value = value;
+    }
+
+    private static final Map<Long, GroupTypes> intToEnumMap = new HashMap<>();
+    private static long minValue;
+    private static long maxValue;
+    static {
+        int i = 0;
+        for (final GroupTypes type : GroupTypes.values()) {
+            if (i == 0) {
+                minValue = type.id;
+            }
+            intToEnumMap.put(type.id, type);
+            if (minValue >= type.id) {
+                minValue = type.id;
+            }
+            if (maxValue < type.id) {
+                maxValue = type.id;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static GroupTypes fromInt(final int i) {
+        final GroupTypes type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static long getMinValue() {
+        return minValue;
+    }
+
+    public static long getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupingTypeEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupingTypeEnumerations.java
new file mode 100644
index 0000000..23729aa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupingTypeEnumerations.java
@@ -0,0 +1,61 @@
+/**
+ * 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.group.domain;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class GroupingTypeEnumerations {
+
+    public static EnumOptionData status(final Integer statusId) {
+        return status(GroupingTypeStatus.fromInt(statusId));
+    }
+
+    public static EnumOptionData status(final GroupingTypeStatus status) {
+        EnumOptionData optionData = new EnumOptionData(GroupingTypeStatus.INVALID.getValue().longValue(),
+                GroupingTypeStatus.INVALID.getCode(), "Invalid");
+        switch (status) {
+            case INVALID:
+                optionData = new EnumOptionData(GroupingTypeStatus.INVALID.getValue().longValue(), GroupingTypeStatus.INVALID.getCode(),
+                        "Invalid");
+            break;
+            case PENDING:
+                optionData = new EnumOptionData(GroupingTypeStatus.PENDING.getValue().longValue(), GroupingTypeStatus.PENDING.getCode(),
+                        "Pending");
+            break;
+            case ACTIVE:
+                optionData = new EnumOptionData(GroupingTypeStatus.ACTIVE.getValue().longValue(), GroupingTypeStatus.ACTIVE.getCode(),
+                        "Active");
+            break;
+            case CLOSED:
+                optionData = new EnumOptionData(GroupingTypeStatus.CLOSED.getValue().longValue(), GroupingTypeStatus.CLOSED.getCode(),
+                        "Closed");
+            break;
+            case TRANSFER_IN_PROGRESS:
+                optionData = new EnumOptionData(GroupingTypeStatus.TRANSFER_IN_PROGRESS.getValue().longValue(),
+                        GroupingTypeStatus.TRANSFER_IN_PROGRESS.getCode(), "Transfer in progress");
+            break;
+            case TRANSFER_ON_HOLD:
+                optionData = new EnumOptionData(GroupingTypeStatus.TRANSFER_ON_HOLD.getValue().longValue(),
+                        GroupingTypeStatus.TRANSFER_ON_HOLD.getCode(), "Transfer on hold");
+            break;
+        }
+
+        return optionData;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupingTypeStatus.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupingTypeStatus.java
new file mode 100644
index 0000000..2d9ab4d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/GroupingTypeStatus.java
@@ -0,0 +1,93 @@
+/**
+ * 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.group.domain;
+
+/**
+ * Enum representation of grouping type status states.
+ */
+public enum GroupingTypeStatus {
+
+    INVALID(0, "groupingStatusType.invalid"), //
+    PENDING(100, "groupingStatusType.pending"), //
+    ACTIVE(300, "groupingStatusType.active"), //
+    TRANSFER_IN_PROGRESS(303, "clientStatusType.transfer.in.progress"), //
+    TRANSFER_ON_HOLD(304, "clientStatusType.transfer.on.hold"), //
+    CLOSED(600, "groupingStatusType.closed");
+
+    private final Integer value;
+    private final String code;
+
+    public static GroupingTypeStatus fromInt(final Integer statusValue) {
+
+        GroupingTypeStatus enumeration = GroupingTypeStatus.INVALID;
+        switch (statusValue) {
+            case 100:
+                enumeration = GroupingTypeStatus.PENDING;
+            break;
+            case 300:
+                enumeration = GroupingTypeStatus.ACTIVE;
+            break;
+            case 600:
+                enumeration = GroupingTypeStatus.CLOSED;
+            break;
+        }
+        return enumeration;
+    }
+
+    private GroupingTypeStatus(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final GroupingTypeStatus state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isPending() {
+        return this.value.equals(GroupingTypeStatus.PENDING.getValue());
+    }
+
+    public boolean isActive() {
+        return this.value.equals(GroupingTypeStatus.ACTIVE.getValue());
+    }
+
+    public boolean isClosed() {
+        return this.value.equals(GroupingTypeStatus.CLOSED.getValue());
+    }
+
+    public boolean isTransferInProgress() {
+        return isTransferInProgress();
+    }
+
+    public boolean isTransferOnHold() {
+        return isTransferOnHold();
+    }
+
+    public boolean isUnderTransfer() {
+        return isTransferInProgress() || isTransferOnHold();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/StaffAssignmentHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/StaffAssignmentHistory.java
new file mode 100644
index 0000000..50e2741
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/domain/StaffAssignmentHistory.java
@@ -0,0 +1,94 @@
+/**
+ * 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.group.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.AbstractAuditableCustom;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+
+@Entity
+@Table(name = "m_staff_assignment_history")
+public class StaffAssignmentHistory extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "centre_id", nullable = true)
+    private Group center;
+
+    @ManyToOne
+    @JoinColumn(name = "staff_id", nullable = true)
+    private Staff staff;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "start_date")
+    private Date startDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "end_date")
+    private Date endDate;
+
+    public static StaffAssignmentHistory createNew(final Group center, final Staff staff, final LocalDate startDate) {
+        return new StaffAssignmentHistory(center, staff, startDate.toDate(), null);
+    }
+
+    protected StaffAssignmentHistory() {
+        //
+    }
+
+    private StaffAssignmentHistory(final Group center, final Staff staff, final Date startDate, final Date endDate) {
+        this.center = center;
+        this.staff = staff;
+        this.startDate = startDate;
+        this.endDate = endDate;
+    }
+
+    public void updateStaff(final Staff staff) {
+        this.staff = staff;
+    }
+
+    public void updateStartDate(final LocalDate startDate) {
+        this.startDate = startDate.toDate();
+    }
+
+    public void updateEndDate(final LocalDate endDate) {
+        this.endDate = endDate.toDate();
+    }
+
+    public boolean matchesStartDateOf(final LocalDate matchingDate) {
+        return getStartDate().isEqual(matchingDate);
+    }
+
+    public LocalDate getStartDate() {
+        return new LocalDate(this.startDate);
+    }
+
+    public boolean isCurrentRecord() {
+        return this.endDate == null;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/CenterNotActiveException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/CenterNotActiveException.java
new file mode 100644
index 0000000..e5d1146
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/CenterNotActiveException.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.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class CenterNotActiveException extends AbstractPlatformDomainRuleException {
+
+    public CenterNotActiveException(final Long centerId) {
+        super("error.msg.center.not.active.exception", "The Center with id `" + centerId + "` is not active", centerId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/CenterNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/CenterNotFoundException.java
new file mode 100644
index 0000000..31a23bc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/CenterNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when group resources are not found.
+ */
+public class CenterNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public CenterNotFoundException(final Long id) {
+        super("error.msg.center.id.invalid", "Center with identifier " + id + " does not exist", id);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/ClientExistInGroupException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/ClientExistInGroupException.java
new file mode 100644
index 0000000..9a20a6c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/ClientExistInGroupException.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.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ClientExistInGroupException extends AbstractPlatformDomainRuleException {
+
+    public ClientExistInGroupException(final Long clientId, final Long groupId) {
+        super("error.msg.group.client.exist.in.group", "Client with identifier " + clientId
+                + " is already exists in Group with identifier " + groupId, clientId, groupId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/ClientNotInGroupException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/ClientNotInGroupException.java
new file mode 100644
index 0000000..29c1b52
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/ClientNotInGroupException.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.portfolio.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ClientNotInGroupException extends AbstractPlatformDomainRuleException {
+
+    public ClientNotInGroupException(final Long clientId, final Long groupId) {
+        this("group.client.not.in.group", "Client with identifier " + clientId + " is not in Group with identifier " + groupId, clientId,
+                groupId);
+
+    }
+
+    public ClientNotInGroupException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupAccountExistsException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupAccountExistsException.java
new file mode 100644
index 0000000..3eaae4d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupAccountExistsException.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.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class GroupAccountExistsException extends AbstractPlatformDomainRuleException {
+
+    public GroupAccountExistsException(final String action, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.group." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupExistsInCenterException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupExistsInCenterException.java
new file mode 100644
index 0000000..4ba1955
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupExistsInCenterException.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.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class GroupExistsInCenterException extends AbstractPlatformDomainRuleException {
+
+    public GroupExistsInCenterException(final Long centerId, final Long groupId) {
+        super("error.msg.group.is.already.member.of.center", "Group with identifier " + groupId
+                + " is already exists in Center with identifier " + centerId, groupId, centerId);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupHasNoStaffException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupHasNoStaffException.java
new file mode 100644
index 0000000..8329ecc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupHasNoStaffException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when group resources are not found.
+ */
+public class GroupHasNoStaffException extends AbstractPlatformResourceNotFoundException {
+
+    public GroupHasNoStaffException(final Long groupId) {
+        super("error.msg.group.has.no.staff", "Group with identifier " + groupId + " does not have staff", groupId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupLevelNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupLevelNotFoundException.java
new file mode 100644
index 0000000..530ffe1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupLevelNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when group resources are not found.
+ */
+public class GroupLevelNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public GroupLevelNotFoundException(final Long id) {
+        super("error.msg.group.level.id.invalid", "Group level with identifier " + id + " does not exist", id);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupMemberCountNotInPermissibleRangeException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupMemberCountNotInPermissibleRangeException.java
new file mode 100644
index 0000000..39f929c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupMemberCountNotInPermissibleRangeException.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.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class GroupMemberCountNotInPermissibleRangeException extends AbstractPlatformDomainRuleException {
+
+    public GroupMemberCountNotInPermissibleRangeException(final Long groupId, final Integer minClients, final Integer maxClients) {
+        super("error.msg.group.members.count.must.be.in.permissible.range", "Number of members in the group with Id " + groupId
+                + " should be between " + minClients + " and " + maxClients, groupId, minClients, maxClients);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupMustBePendingToBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupMustBePendingToBeDeletedException.java
new file mode 100644
index 0000000..2cf4ec4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupMustBePendingToBeDeletedException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when attempting to delete groups.
+ */
+public class GroupMustBePendingToBeDeletedException extends AbstractPlatformDomainRuleException {
+
+    public GroupMustBePendingToBeDeletedException(final Long id) {
+        super("error.msg.group.cannot.be.deleted", "Group with identifier " + id + " cannot be deleted as it is not in `Pending` state.",
+                id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupNotActiveException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupNotActiveException.java
new file mode 100644
index 0000000..024495e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupNotActiveException.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.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class GroupNotActiveException extends AbstractPlatformDomainRuleException {
+
+    public GroupNotActiveException(final Long groupId) {
+        super("error.msg.group.not.active.exception", "The Group with id `" + groupId + "` is not active", groupId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupNotExistsInCenterException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupNotExistsInCenterException.java
new file mode 100644
index 0000000..4eb6ef6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupNotExistsInCenterException.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.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class GroupNotExistsInCenterException extends AbstractPlatformDomainRuleException {
+
+    public GroupNotExistsInCenterException(final Long groupId, final Long centerId) {
+        super("error.msg.group.not.in.center", "Group with identifier " + groupId + " is not exists in Center with identifier " + centerId,
+                groupId, centerId);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupNotFoundException.java
new file mode 100644
index 0000000..a72d5d5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when group resources are not found.
+ */
+public class GroupNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public GroupNotFoundException(final Long id) {
+        super("error.msg.group.id.invalid", "Group with identifier " + id + " does not exist", id);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupRoleNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupRoleNotFoundException.java
new file mode 100644
index 0000000..06a0975
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/GroupRoleNotFoundException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class GroupRoleNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public GroupRoleNotFoundException(final Long id) {
+        super("error.msg.group.role.id.invalid", "Group Role with identifier " + id + " does not exist", id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/InvalidGroupLevelException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/InvalidGroupLevelException.java
new file mode 100644
index 0000000..150d339
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/InvalidGroupLevelException.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.portfolio.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when parentGroup'd level
+ * is not equal to parent level of the levelid param,.
+ */
+public class InvalidGroupLevelException extends AbstractPlatformDomainRuleException {
+
+    public InvalidGroupLevelException(final String action, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.group." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/InvalidGroupStateTransitionException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/InvalidGroupStateTransitionException.java
new file mode 100644
index 0000000..65082c8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/exception/InvalidGroupStateTransitionException.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.group.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class InvalidGroupStateTransitionException extends AbstractPlatformDomainRuleException {
+
+    public InvalidGroupStateTransitionException(final String levelName, final String action, final String postFix,
+            final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg." + levelName + "." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/ActivateCenterCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/ActivateCenterCommandHandler.java
new file mode 100644
index 0000000..c9e95f2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/ActivateCenterCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CENTER", action = "ACTIVATE")
+public class ActivateCenterCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService writePlatformService;
+
+    @Autowired
+    public ActivateCenterCommandHandler(final GroupingTypesWritePlatformService clientWritePlatformService) {
+        this.writePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.activateGroupOrCenter(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/ActivateGroupCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/ActivateGroupCommandHandler.java
new file mode 100644
index 0000000..822ffb0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/ActivateGroupCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "ACTIVATE")
+public class ActivateGroupCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService writePlatformService;
+
+    @Autowired
+    public ActivateGroupCommandHandler(final GroupingTypesWritePlatformService clientWritePlatformService) {
+        this.writePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.activateGroupOrCenter(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssignGroupStaffCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssignGroupStaffCommandHandler.java
new file mode 100644
index 0000000..34da7b2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssignGroupStaffCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "ASSIGNSTAFF")
+public class AssignGroupStaffCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupWritePlatformService;
+
+    @Autowired
+    public AssignGroupStaffCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) {
+        this.groupWritePlatformService = groupWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupWritePlatformService.assignGroupOrCenterStaff(command.entityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssignRoleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssignRoleCommandHandler.java
new file mode 100644
index 0000000..494854d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssignRoleCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupRolesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "ASSIGNROLE")
+public class AssignRoleCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupRolesWritePlatformService groupRolesWritePlatformService;
+
+    @Autowired
+    public AssignRoleCommandHandler(final GroupRolesWritePlatformService groupRolesWritePlatformService) {
+        this.groupRolesWritePlatformService = groupRolesWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupRolesWritePlatformService.createRole(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssociateClientsToGroupCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssociateClientsToGroupCommandHandler.java
new file mode 100644
index 0000000..bab2f81
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssociateClientsToGroupCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "ASSOCIATECLIENTS")
+public class AssociateClientsToGroupCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService writePlatformService;
+
+    @Autowired
+    public AssociateClientsToGroupCommandHandler(final GroupingTypesWritePlatformService clientWritePlatformService) {
+        this.writePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.associateClientsToGroup(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssociateGroupsToCenterCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssociateGroupsToCenterCommandHandler.java
new file mode 100644
index 0000000..29a6cb8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/AssociateGroupsToCenterCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CENTER", action = "ASSOCIATEGROUPS")
+public class AssociateGroupsToCenterCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService writePlatformService;
+
+    @Autowired
+    public AssociateGroupsToCenterCommandHandler(final GroupingTypesWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.writePlatformService.associateGroupsToCenter(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CloseCenterCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CloseCenterCommandHandler.java
new file mode 100644
index 0000000..b222c11
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CloseCenterCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CENTER", action = "CLOSE")
+public class CloseCenterCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupingTypesWritePlatformService;
+
+    @Autowired
+    public CloseCenterCommandHandler(final GroupingTypesWritePlatformService groupingTypesWritePlatformService) {
+        this.groupingTypesWritePlatformService = groupingTypesWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupingTypesWritePlatformService.closeCenter(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CloseGroupCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CloseGroupCommandHandler.java
new file mode 100644
index 0000000..ea12b3b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CloseGroupCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "CLOSE")
+public class CloseGroupCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupingTypesWritePlatformService;
+
+    @Autowired
+    public CloseGroupCommandHandler(final GroupingTypesWritePlatformService groupingTypesWritePlatformService) {
+        this.groupingTypesWritePlatformService = groupingTypesWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupingTypesWritePlatformService.closeGroup(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CreateCenterCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CreateCenterCommandHandler.java
new file mode 100644
index 0000000..4fd986a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CreateCenterCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CENTER", action = "CREATE")
+public class CreateCenterCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupWritePlatformService;
+
+    @Autowired
+    public CreateCenterCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) {
+        this.groupWritePlatformService = groupWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupWritePlatformService.createCenter(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CreateGroupCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CreateGroupCommandHandler.java
new file mode 100644
index 0000000..aebd6be
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/CreateGroupCommandHandler.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.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "CREATE")
+public class CreateGroupCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupWritePlatformService;
+
+    @Autowired
+    public CreateGroupCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) {
+        this.groupWritePlatformService = groupWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final Long centerId = command.longValueOfParameterNamed("centerId");
+        return this.groupWritePlatformService.createGroup(centerId, command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DeleteCenterCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DeleteCenterCommandHandler.java
new file mode 100644
index 0000000..7abcf08
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DeleteCenterCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CENTER", action = "DELETE")
+public class DeleteCenterCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupWritePlatformService;
+
+    @Autowired
+    public DeleteCenterCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) {
+        this.groupWritePlatformService = groupWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupWritePlatformService.deleteGroup(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DeleteGroupCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DeleteGroupCommandHandler.java
new file mode 100644
index 0000000..a8f2d02
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DeleteGroupCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "DELETE")
+public class DeleteGroupCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupWritePlatformService;
+
+    @Autowired
+    public DeleteGroupCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) {
+        this.groupWritePlatformService = groupWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupWritePlatformService.deleteGroup(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DisassociateClientsFromGroupCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DisassociateClientsFromGroupCommandHandler.java
new file mode 100644
index 0000000..e5ab1b0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DisassociateClientsFromGroupCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "DISASSOCIATECLIENTS")
+public class DisassociateClientsFromGroupCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService writePlatformService;
+
+    @Autowired
+    public DisassociateClientsFromGroupCommandHandler(final GroupingTypesWritePlatformService clientWritePlatformService) {
+        this.writePlatformService = clientWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.disassociateClientsFromGroup(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DisassociateGroupsFromCenterCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DisassociateGroupsFromCenterCommandHandler.java
new file mode 100644
index 0000000..5ebeb83
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/DisassociateGroupsFromCenterCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CENTER", action = "DISASSOCIATEGROUPS")
+public class DisassociateGroupsFromCenterCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService writePlatformService;
+
+    @Autowired
+    public DisassociateGroupsFromCenterCommandHandler(final GroupingTypesWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.writePlatformService.disassociateGroupsToCenter(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/SaveCenterCollectionSheetCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/SaveCenterCollectionSheetCommandHandler.java
new file mode 100644
index 0000000..389c65d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/SaveCenterCollectionSheetCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CENTER", action = "SAVECOLLECTIONSHEET")
+public class SaveCenterCollectionSheetCommandHandler implements NewCommandSourceHandler {
+
+    private final CollectionSheetWritePlatformService writePlatformService;
+
+    @Autowired
+    public SaveCenterCollectionSheetCommandHandler(final CollectionSheetWritePlatformService collectionSheetWritePlatformService) {
+        this.writePlatformService = collectionSheetWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateCollectionSheet(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/SaveGroupCollectionSheetCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/SaveGroupCollectionSheetCommandHandler.java
new file mode 100644
index 0000000..efe9c8f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/SaveGroupCollectionSheetCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "SAVECOLLECTIONSHEET")
+public class SaveGroupCollectionSheetCommandHandler implements NewCommandSourceHandler {
+
+    private final CollectionSheetWritePlatformService writePlatformService;
+
+    @Autowired
+    public SaveGroupCollectionSheetCommandHandler(final CollectionSheetWritePlatformService collectionSheetWritePlatformService) {
+        this.writePlatformService = collectionSheetWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateCollectionSheet(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UnassignGroupStaffCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UnassignGroupStaffCommandHandler.java
new file mode 100644
index 0000000..9749a98
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UnassignGroupStaffCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "UNASSIGNSTAFF")
+public class UnassignGroupStaffCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupWritePlatformService;
+
+    @Autowired
+    public UnassignGroupStaffCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) {
+        this.groupWritePlatformService = groupWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupWritePlatformService.unassignGroupOrCenterStaff(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UnassignRoleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UnassignRoleCommandHandler.java
new file mode 100644
index 0000000..920f0db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UnassignRoleCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupRolesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "UNASSIGNROLE")
+public class UnassignRoleCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupRolesWritePlatformService groupRolesWritePlatformService;
+
+    @Autowired
+    public UnassignRoleCommandHandler(final GroupRolesWritePlatformService groupRolesWritePlatformService) {
+        this.groupRolesWritePlatformService = groupRolesWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupRolesWritePlatformService.deleteRole(command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UnassignStaffFromCenterCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UnassignStaffFromCenterCommandHandler.java
new file mode 100644
index 0000000..80b0b1d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UnassignStaffFromCenterCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.group.handler;
+
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class UnassignStaffFromCenterCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService writePlatformService;
+
+    @Autowired
+    public UnassignStaffFromCenterCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) {
+        this.writePlatformService = groupWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.unassignGroupOrCenterStaff(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UpdateCenterCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UpdateCenterCommandHandler.java
new file mode 100644
index 0000000..0d20a6e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UpdateCenterCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CENTER", action = "UPDATE")
+public class UpdateCenterCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupWritePlatformService;
+
+    @Autowired
+    public UpdateCenterCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) {
+        this.groupWritePlatformService = groupWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.groupWritePlatformService.updateCenter(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UpdateGroupCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UpdateGroupCommandHandler.java
new file mode 100644
index 0000000..add0b26
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UpdateGroupCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "UPDATE")
+public class UpdateGroupCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupingTypesWritePlatformService groupWritePlatformService;
+
+    @Autowired
+    public UpdateGroupCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) {
+        this.groupWritePlatformService = groupWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.groupWritePlatformService.updateGroup(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UpdateGroupRoleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UpdateGroupRoleCommandHandler.java
new file mode 100644
index 0000000..e109351
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/handler/UpdateGroupRoleCommandHandler.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.portfolio.group.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.group.service.GroupRolesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "UPDATEROLE")
+public class UpdateGroupRoleCommandHandler implements NewCommandSourceHandler {
+
+    private final GroupRolesWritePlatformService groupRolesWritePlatformService;
+
+    @Autowired
+    public UpdateGroupRoleCommandHandler(final GroupRolesWritePlatformService groupRolesWritePlatformService) {
+        this.groupRolesWritePlatformService = groupRolesWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.groupRolesWritePlatformService.updateRole(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/serialization/GroupRolesDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/serialization/GroupRolesDataValidator.java
new file mode 100644
index 0000000..8478319
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/serialization/GroupRolesDataValidator.java
@@ -0,0 +1,107 @@
+/**
+ * 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.group.serialization;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class GroupRolesDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public GroupRolesDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateForCreateGroupRole(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper
+                .checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.GROUP_ROLES_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.GROUP_ROLE_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final Long roleId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.roleParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.roleParamName).value(roleId).notNull().longGreaterThanZero();
+
+        final Long clientId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.clientIdParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.clientIdParamName).value(clientId).notNull().longGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdateRole(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper
+                .checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.GROUP_ROLES_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.GROUP_ROLE_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final Long roleId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.roleParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.roleParamName).value(roleId).ignoreIfNull().notBlank()
+                .longGreaterThanZero();
+
+        final Long clientId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.clientIdParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.clientIdParamName).value(clientId).ignoreIfNull().notBlank()
+                .longGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/serialization/GroupingTypesDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/serialization/GroupingTypesDataValidator.java
new file mode 100755
index 0000000..bf5362d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/serialization/GroupingTypesDataValidator.java
@@ -0,0 +1,537 @@
+/**
+ * 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.group.serialization;
+
+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.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
+import org.joda.time.LocalDate;
+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 GroupingTypesDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public GroupingTypesDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            //
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    public void validateForCreateCenter(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.CENTER_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.nameParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.nameParamName).value(name).notNull().notExceedingLengthOf(100);
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.externalIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.officeIdParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.officeIdParamName).value(officeId).notNull().integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.staffIdParamName, element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.staffIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.staffIdParamName).value(staffId).integerGreaterThanZero();
+        }
+
+        final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(GroupingTypesApiConstants.activeParamName, element);
+        if (active != null) {
+            if (active.booleanValue()) {
+                final LocalDate joinedDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                        GroupingTypesApiConstants.activationDateParamName, element);
+                baseDataValidator.reset().parameter(GroupingTypesApiConstants.activationDateParamName).value(joinedDate).notNull();
+            } else {
+                // TODO - KW - not using config for now - just supporting move
+                // to pending or active out of box.
+                final boolean isPendingApprovalEnabled = true;
+                if (!isPendingApprovalEnabled) {
+                    baseDataValidator.reset().parameter(GroupingTypesApiConstants.activeParamName)
+                            .failWithCode(".pending.status.not.allowed");
+                }
+            }
+        } else {
+            baseDataValidator.reset().parameter(ClientApiConstants.activeParamName).value(active).trueOrFalseRequired(false);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.submittedOnDateParamName, element)) {
+            final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                    GroupingTypesApiConstants.submittedOnDateParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForCreateCenterGroup(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.GROUP_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.nameParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.nameParamName).value(name).notNull().notExceedingLengthOf(100);
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.externalIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        final Long centerId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.centerIdParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.centerIdParamName).value(centerId).notNull().integerGreaterThanZero();
+
+        // office is inherited from center
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.officeIdParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.officeIdParamName).value(officeId).notNull().integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.staffIdParamName, element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.staffIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.staffIdParamName).value(staffId).integerGreaterThanZero();
+        }
+
+        final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(GroupingTypesApiConstants.activeParamName, element);
+        if (active != null) {
+            if (active.booleanValue()) {
+                final LocalDate joinedDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                        GroupingTypesApiConstants.activationDateParamName, element);
+                baseDataValidator.reset().parameter(GroupingTypesApiConstants.activationDateParamName).value(joinedDate).notNull();
+            } else {
+                // TODO - KW - not using config for now - just supporting move
+                // to pending or active out of box.
+                final boolean isPendingApprovalEnabled = true;
+                if (!isPendingApprovalEnabled) {
+                    baseDataValidator.reset().parameter(GroupingTypesApiConstants.activeParamName)
+                            .failWithCode(".pending.status.not.allowed");
+                }
+            }
+        } else {
+            baseDataValidator.reset().parameter(ClientApiConstants.activeParamName).value(active).trueOrFalseRequired(false);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.submittedOnDateParamName, element)) {
+            final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                    GroupingTypesApiConstants.submittedOnDateParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForCreateGroup(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.GROUP_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.nameParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.nameParamName).value(name).notNull().notExceedingLengthOf(100);
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.externalIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        // office is inherited from center
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.officeIdParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.officeIdParamName).value(officeId).notNull().integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.staffIdParamName, element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.staffIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.staffIdParamName).value(staffId).integerGreaterThanZero();
+        }
+
+        final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(GroupingTypesApiConstants.activeParamName, element);
+        if (active != null) {
+            if (active.booleanValue()) {
+                final LocalDate joinedDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                        GroupingTypesApiConstants.activationDateParamName, element);
+                baseDataValidator.reset().parameter(GroupingTypesApiConstants.activationDateParamName).value(joinedDate).notNull();
+            } else {
+                // TODO - KW - not using config for now - just supporting move
+                // to pending or active out of box.
+                final boolean isPendingApprovalEnabled = true;
+                if (!isPendingApprovalEnabled) {
+                    baseDataValidator.reset().parameter(GroupingTypesApiConstants.activeParamName)
+                            .failWithCode(".pending.status.not.allowed");
+                }
+            }
+        } else {
+            baseDataValidator.reset().parameter(ClientApiConstants.activeParamName).value(active).trueOrFalseRequired(false);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.submittedOnDateParamName, element)) {
+            final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                    GroupingTypesApiConstants.submittedOnDateParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdateCenter(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.CENTER_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.nameParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.nameParamName).value(name).notNull().notExceedingLengthOf(100);
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.externalIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        // dont support update of office at present when updating center?
+        // final Long officeId =
+        // this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.officeIdParamName,
+        // element);
+        // baseDataValidator.reset().parameter(GroupingTypesApiConstants.officeIdParamName).value(officeId).notNull().integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.staffIdParamName, element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.staffIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.staffIdParamName).value(staffId).integerGreaterThanZero();
+        }
+
+        final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(GroupingTypesApiConstants.activeParamName, element);
+        if (active != null) {
+            if (active.booleanValue()) {
+                final LocalDate joinedDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                        GroupingTypesApiConstants.activationDateParamName, element);
+                baseDataValidator.reset().parameter(GroupingTypesApiConstants.activationDateParamName).value(joinedDate).notNull();
+            }
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdateGroup(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.GROUP_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.nameParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.nameParamName).value(name).notNull().notExceedingLengthOf(100);
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(GroupingTypesApiConstants.externalIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.officeIdParamName, element)) {
+            final Long officeId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.officeIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.officeIdParamName).value(officeId).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(GroupingTypesApiConstants.staffIdParamName, element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.staffIdParamName, element);
+            baseDataValidator.reset().parameter(GroupingTypesApiConstants.staffIdParamName).value(staffId).integerGreaterThanZero();
+        }
+
+        final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(GroupingTypesApiConstants.activeParamName, element);
+        if (active != null) {
+            if (active.booleanValue()) {
+                final LocalDate joinedDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                        GroupingTypesApiConstants.activationDateParamName, element);
+                baseDataValidator.reset().parameter(GroupingTypesApiConstants.activationDateParamName).value(joinedDate).notNull();
+            }
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForActivation(final JsonCommand command, final String resourceName) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.ACTIVATION_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(resourceName);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(GroupingTypesApiConstants.activationDateParamName,
+                element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.activationDateParamName).value(activationDate).notNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUnassignStaff(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Set<String> supportedParametersUnassignStaff = new HashSet<>(Arrays.asList("staffId"));
+
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersUnassignStaff);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("group");
+
+        final String staffIdParameterName = "staffId";
+        final Long staffId = this.fromApiJsonHelper.extractLongNamed(staffIdParameterName, element);
+        baseDataValidator.reset().parameter(staffIdParameterName).value(staffId).notNull().integerGreaterThanZero();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateForAssignStaff(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Set<String> supportedParametersAssignStaff = new HashSet<>(Arrays.asList(GroupingTypesApiConstants.staffIdParamName,GroupingTypesApiConstants.inheritStaffForClientAccounts));
+
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersAssignStaff);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+
+        final String staffIdParameterName = GroupingTypesApiConstants.staffIdParamName;
+        final Long staffId = this.fromApiJsonHelper.extractLongNamed(staffIdParameterName, element);
+        baseDataValidator.reset().parameter(staffIdParameterName).value(staffId).notNull().longGreaterThanZero();
+
+        final String inheritStaffForClientAccountsParamName = GroupingTypesApiConstants.inheritStaffForClientAccounts;
+        final Boolean inheritStaffForClientAccounts= this.fromApiJsonHelper.extractBooleanNamed(inheritStaffForClientAccountsParamName,element);
+        baseDataValidator.reset().parameter(inheritStaffForClientAccountsParamName).value(inheritStaffForClientAccounts).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
+
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateForAssociateClients(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Set<String> supportedParameters = new HashSet<>(Arrays.asList(GroupingTypesApiConstants.clientMembersParamName));
+
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("group");
+        final String[] clients = this.fromApiJsonHelper.extractArrayNamed(GroupingTypesApiConstants.clientMembersParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.clientMembersParamName).value(clients).arrayNotEmpty();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForDisassociateClients(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Set<String> supportedParameters = new HashSet<>(Arrays.asList(GroupingTypesApiConstants.clientMembersParamName));
+
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("group");
+        final String[] clients = this.fromApiJsonHelper.extractArrayNamed(GroupingTypesApiConstants.clientMembersParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.clientMembersParamName).value(clients).arrayNotEmpty();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForGroupClose(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper
+                .checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.GROUP_CLOSE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate closureDate = this.fromApiJsonHelper.extractLocalDateNamed(GroupingTypesApiConstants.closureDateParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.closureDateParamName).value(closureDate).notNull();
+
+        final Long closureReasonId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.closureReasonIdParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.closureReasonIdParamName).value(closureReasonId).notNull()
+                .longGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    public void validateForCenterClose(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper
+                .checkForUnsupportedParameters(typeOfMap, json, GroupingTypesApiConstants.GROUP_CLOSE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate closureDate = this.fromApiJsonHelper.extractLocalDateNamed(GroupingTypesApiConstants.closureDateParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.closureDateParamName).value(closureDate).notNull();
+
+        final Long closureReasonId = this.fromApiJsonHelper.extractLongNamed(GroupingTypesApiConstants.closureReasonIdParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.closureReasonIdParamName).value(closureReasonId).notNull()
+                .longGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    public void validateForAssociateGroups(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Set<String> supportedParameters = new HashSet<>(Arrays.asList(GroupingTypesApiConstants.groupMembersParamName));
+
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("group");
+        final String[] groups = this.fromApiJsonHelper.extractArrayNamed(GroupingTypesApiConstants.groupMembersParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.groupMembersParamName).value(groups).arrayNotEmpty();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    public void validateForDisassociateGroups(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+
+        final Set<String> supportedParameters = new HashSet<>(Arrays.asList(GroupingTypesApiConstants.groupMembersParamName));
+
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("group");
+        final String[] groups = this.fromApiJsonHelper.extractArrayNamed(GroupingTypesApiConstants.groupMembersParamName, element);
+        baseDataValidator.reset().parameter(GroupingTypesApiConstants.groupMembersParamName).value(groups).arrayNotEmpty();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/AllGroupTypesDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/AllGroupTypesDataMapper.java
new file mode 100644
index 0000000..05d9754
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/AllGroupTypesDataMapper.java
@@ -0,0 +1,120 @@
+/**
+ * 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.group.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.data.GroupTimelineData;
+import org.joda.time.LocalDate;
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ *
+ */
+public final class AllGroupTypesDataMapper implements RowMapper<GroupGeneralData> {
+
+    private final String schemaSql;
+
+    public AllGroupTypesDataMapper() {
+        final StringBuilder sqlBuilder = new StringBuilder(400);
+        sqlBuilder.append("g.id as id, g.account_no as accountNumber, g.external_id as externalId, g.display_name as name, ");
+        sqlBuilder.append("g.office_id as officeId, o.name as officeName, ");
+        sqlBuilder.append("g.parent_id as centerId, pg.display_name as centerName, ");
+        sqlBuilder.append("g.staff_id as staffId, s.display_name as staffName, ");
+        sqlBuilder.append("g.status_enum as statusEnum, g.activation_date as activationDate, ");
+        sqlBuilder.append("g.closedon_date as closedOnDate, ");
+
+        sqlBuilder.append("g.submittedon_date as submittedOnDate, ");
+        sqlBuilder.append("sbu.username as submittedByUsername, ");
+        sqlBuilder.append("sbu.firstname as submittedByFirstname, ");
+        sqlBuilder.append("sbu.lastname as submittedByLastname, ");
+
+        sqlBuilder.append("clu.username as closedByUsername, ");
+        sqlBuilder.append("clu.firstname as closedByFirstname, ");
+        sqlBuilder.append("clu.lastname as closedByLastname, ");
+
+        sqlBuilder.append("acu.username as activatedByUsername, ");
+        sqlBuilder.append("acu.firstname as activatedByFirstname, ");
+        sqlBuilder.append("acu.lastname as activatedByLastname, ");
+
+        sqlBuilder.append("g.hierarchy as hierarchy, ");
+        sqlBuilder.append("g.level_id as groupLevel ");
+        sqlBuilder.append("from m_group g ");
+        sqlBuilder.append("join m_office o on o.id = g.office_id ");
+        sqlBuilder.append("left join m_staff s on s.id = g.staff_id ");
+        sqlBuilder.append("left join m_group pg on pg.id = g.parent_id ");
+        sqlBuilder.append("left join m_appuser sbu on sbu.id = g.submittedon_userid ");
+        sqlBuilder.append("left join m_appuser acu on acu.id = g.activatedon_userid ");
+        sqlBuilder.append("left join m_appuser clu on clu.id = g.closedon_userid ");
+
+        this.schemaSql = sqlBuilder.toString();
+    }
+
+    public String schema() {
+        return this.schemaSql;
+    }
+
+    @Override
+    public GroupGeneralData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+        final Long id = rs.getLong("id");
+        final String accountNo = rs.getString("accountNumber");
+        final String name = rs.getString("name");
+        final String externalId = rs.getString("externalId");
+
+        final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+        final EnumOptionData status = ClientEnumerations.status(statusEnum);
+        final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate");
+
+        final Long officeId = JdbcSupport.getLong(rs, "officeId");
+        final String officeName = rs.getString("officeName");
+        final Long centerId = JdbcSupport.getLong(rs, "centerId");
+        final String centerName = rs.getString("centerName");
+        final Long staffId = JdbcSupport.getLong(rs, "staffId");
+        final String staffName = rs.getString("staffName");
+        final String hierarchy = rs.getString("hierarchy");
+        final String groupLevel = rs.getString("groupLevel");
+
+        final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+        final String closedByUsername = rs.getString("closedByUsername");
+        final String closedByFirstname = rs.getString("closedByFirstname");
+        final String closedByLastname = rs.getString("closedByLastname");
+
+        final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+        final String submittedByUsername = rs.getString("submittedByUsername");
+        final String submittedByFirstname = rs.getString("submittedByFirstname");
+        final String submittedByLastname = rs.getString("submittedByLastname");
+
+        final String activatedByUsername = rs.getString("activatedByUsername");
+        final String activatedByFirstname = rs.getString("activatedByFirstname");
+        final String activatedByLastname = rs.getString("activatedByLastname");
+
+        final GroupTimelineData timeline = new GroupTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname,
+                submittedByLastname, activationDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate,
+                closedByUsername, closedByFirstname, closedByLastname);
+
+        return GroupGeneralData.instance(id, accountNo, name, externalId, status, activationDate, officeId, officeName, centerId, centerName, staffId,
+                staffName, hierarchy, groupLevel, timeline);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformService.java
new file mode 100644
index 0000000..755c528
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformService.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.group.service;
+
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.portfolio.group.data.CenterData;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.data.StaffCenterData;
+
+public interface CenterReadPlatformService {
+
+    CenterData retrieveTemplate(Long officeId, boolean staffInSelectedOfficeOnly);
+
+    CenterData retrieveOne(Long centerId);
+
+    Collection<CenterData> retrieveAllForDropdown(Long officeId);
+
+    Page<CenterData> retrievePagedAll(SearchParameters searchParameters, PaginationParameters parameters);
+
+    Collection<CenterData> retrieveAll(SearchParameters searchParameters, PaginationParameters parameters);
+
+    GroupGeneralData retrieveCenterGroupTemplate(Long centerId);
+
+    Collection<GroupGeneralData> retrieveAssociatedGroups(Long centerId);
+
+    CenterData retrieveCenterWithClosureReasons();
+
+    Collection<StaffCenterData> retriveAllCentersByMeetingDate(Long officeId, Date meetingDate, Long staffId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java
new file mode 100644
index 0000000..716d5ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java
@@ -0,0 +1,564 @@
+/**
+ * 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.group.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.calendar.service.CalendarEnumerations;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
+import org.apache.fineract.portfolio.group.data.CenterData;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.data.GroupTimelineData;
+import org.apache.fineract.portfolio.group.data.StaffCenterData;
+import org.apache.fineract.portfolio.group.domain.GroupTypes;
+import org.apache.fineract.portfolio.group.domain.GroupingTypeEnumerations;
+import org.apache.fineract.portfolio.group.exception.CenterNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class CenterReadPlatformServiceImpl implements CenterReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+
+    // data mappers
+    private final CenterDataMapper centerMapper = new CenterDataMapper();
+    private final GroupDataMapper groupDataMapper = new GroupDataMapper();
+
+    private final PaginationHelper<CenterData> paginationHelper = new PaginationHelper<>();
+    private final PaginationParametersDataValidator paginationParametersDataValidator;
+    private final static Set<String> supportedOrderByValues = new HashSet<>(Arrays.asList("id", "name", "officeId", "officeName"));
+
+    @Autowired
+    public CenterReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService,
+            final StaffReadPlatformService staffReadPlatformService, final CodeValueReadPlatformService codeValueReadPlatformService,
+            final PaginationParametersDataValidator paginationParametersDataValidator) {
+        this.context = context;
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+        this.paginationParametersDataValidator = paginationParametersDataValidator;
+    }
+
+    // 'g.' preffix because of ERROR 1052 (23000): Column 'column_name' in where
+    // clause is ambiguous
+    // caused by the same name of columns in m_office and m_group tables
+    private String getCenterExtraCriteria(final SearchParameters searchCriteria) {
+
+        StringBuffer extraCriteria = new StringBuffer(200);
+        extraCriteria.append(" and g.level_id = " + GroupTypes.CENTER.getId());
+
+        String sqlQueryCriteria = searchCriteria.getSqlSearch();
+        if (StringUtils.isNotBlank(sqlQueryCriteria)) {
+            sqlQueryCriteria = sqlQueryCriteria.replaceAll(" display_name ", " g.display_name ");
+            sqlQueryCriteria = sqlQueryCriteria.replaceAll("display_name ", "g.display_name ");
+            extraCriteria.append(" and (").append(sqlQueryCriteria).append(") ");
+        }
+
+        final Long officeId = searchCriteria.getOfficeId();
+        if (officeId != null) {
+            extraCriteria.append(" and g.office_id = ").append(officeId);
+        }
+
+        final String externalId = searchCriteria.getExternalId();
+        if (externalId != null) {
+            extraCriteria.append(" and g.external_id = ").append(ApiParameterHelper.sqlEncodeString(externalId));
+        }
+
+        final String name = searchCriteria.getName();
+        if (name != null) {
+            extraCriteria.append(" and g.display_name like ").append(ApiParameterHelper.sqlEncodeString(name + "%"));
+        }
+
+        final String hierarchy = searchCriteria.getHierarchy();
+        if (hierarchy != null) {
+            extraCriteria.append(" and o.hierarchy like ").append(ApiParameterHelper.sqlEncodeString(hierarchy + "%"));
+        }
+
+        if (StringUtils.isNotBlank(extraCriteria.toString())) {
+            extraCriteria.delete(0, 4);
+        }
+
+        final Long staffId = searchCriteria.getStaffId();
+        if (staffId != null) {
+            extraCriteria.append(" and g.staff_id = ").append(staffId);
+        }
+
+        return extraCriteria.toString();
+    }
+
+    private static final String sqlQuery = "g.id as id, g.account_no as accountNo, g.external_id as externalId, g.display_name as name, "
+            + "g.office_id as officeId, o.name as officeName, " //
+            + "g.staff_id as staffId, s.display_name as staffName, " //
+            + "g.status_enum as statusEnum, g.activation_date as activationDate, " //
+            + "g.hierarchy as hierarchy, " //
+            + "g.level_id as groupLevel," //
+            + "g.closedon_date as closedOnDate, " + "g.submittedon_date as submittedOnDate, " + "sbu.username as submittedByUsername, "
+            + "sbu.firstname as submittedByFirstname, " + "sbu.lastname as submittedByLastname, " + "clu.username as closedByUsername, "
+            + "clu.firstname as closedByFirstname, " + "clu.lastname as closedByLastname, " + "acu.username as activatedByUsername, "
+            + "acu.firstname as activatedByFirstname, "
+            + "acu.lastname as activatedByLastname "
+            + "from m_group g " //
+            + "join m_office o on o.id = g.office_id " + "left join m_staff s on s.id = g.staff_id "
+            + "left join m_group pg on pg.id = g.parent_id " + "left join m_appuser sbu on sbu.id = g.submittedon_userid "
+            + "left join m_appuser acu on acu.id = g.activatedon_userid " + "left join m_appuser clu on clu.id = g.closedon_userid ";
+
+    private static final class CenterDataMapper implements RowMapper<CenterData> {
+
+        private final String schemaSql;
+
+        public CenterDataMapper() {
+
+            this.schemaSql = sqlQuery;
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public CenterData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final String name = rs.getString("name");
+            final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final EnumOptionData status = GroupingTypeEnumerations.status(statusEnum);
+            final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate");
+            final String externalId = rs.getString("externalId");
+            final Long officeId = rs.getLong("officeId");
+            final String officeName = rs.getString("officeName");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+            final String staffName = rs.getString("staffName");
+            final String hierarchy = rs.getString("hierarchy");
+
+            final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+            final String closedByUsername = rs.getString("closedByUsername");
+            final String closedByFirstname = rs.getString("closedByFirstname");
+            final String closedByLastname = rs.getString("closedByLastname");
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final String activatedByUsername = rs.getString("activatedByUsername");
+            final String activatedByFirstname = rs.getString("activatedByFirstname");
+            final String activatedByLastname = rs.getString("activatedByLastname");
+
+            final GroupTimelineData timeline = new GroupTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname,
+                    submittedByLastname, activationDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate,
+                    closedByUsername, closedByFirstname, closedByLastname);
+
+            return CenterData.instance(id, accountNo, name, externalId, status, activationDate, officeId, officeName, staffId, staffName, hierarchy,
+                    timeline, null);
+        }
+    }
+
+    private static final class CenterCalendarDataMapper implements RowMapper<CenterData> {
+
+        private final String schemaSql;
+
+        public CenterCalendarDataMapper() {
+
+            schemaSql = " select g.id as id, g.account_no as accountNo, g.display_name as name, g.office_id as officeId, g.staff_id as staffId, s.display_name as staffName, g.external_id as externalId, "
+                    + " g.status_enum as statusEnum, g.activation_date as activationDate, g.hierarchy as hierarchy,  "
+                    + " c.id as calendarId, ci.id as calendarInstanceId, ci.entity_id as entityId,  "
+                    + " ci.entity_type_enum as entityTypeId, c.title as title,  c.description as description,  "
+                    + " c.location as location, c.start_date as startDate, c.end_date as endDate, c.recurrence as recurrence  "
+                    + " from m_calendar c join m_calendar_instance ci on ci.calendar_id=c.id and ci.entity_type_enum=4 join m_group g  "
+                    + " on g.id = ci.entity_id join m_staff s on g.staff_id = s.id where g.office_id=? ";
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public CenterData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final String name = rs.getString("name");
+            final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final EnumOptionData status = GroupingTypeEnumerations.status(statusEnum);
+            final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate");
+            final String externalId = rs.getString("externalId");
+            final Long officeId = rs.getLong("officeId");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+            final String staffName = rs.getString("staffName");
+            final String hierarchy = rs.getString("hierarchy");
+
+            final Long calendarId = rs.getLong("calendarId");
+            final Long calendarInstanceId = rs.getLong("calendarInstanceId");
+            final Long entityId = rs.getLong("entityId");
+            final Integer entityTypeId = rs.getInt("entityTypeId");
+            final EnumOptionData entityType = CalendarEnumerations.calendarEntityType(entityTypeId);
+            final String title = rs.getString("title");
+            final String description = rs.getString("description");
+            final String location = rs.getString("location");
+            final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate");
+            final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate");
+            final String recurrence = rs.getString("recurrence");
+
+            CalendarData calendarData = CalendarData.instance(calendarId, calendarInstanceId, entityId, entityType, title, description,
+                    location, startDate, endDate, null, null, false, recurrence, null, null, null, null, null, null, null, null, null,
+                    null, null, null, null);
+            return CenterData.instance(id, accountNo, name, externalId, status, activationDate, officeId, null, staffId, staffName, hierarchy, null,
+                    calendarData);
+        }
+    }
+
+    private static final class GroupDataMapper implements RowMapper<GroupGeneralData> {
+
+        private final String schemaSql;
+
+        public GroupDataMapper() {
+
+            this.schemaSql = sqlQuery;
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public GroupGeneralData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final String name = rs.getString("name");
+            final String externalId = rs.getString("externalId");
+
+            final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final EnumOptionData status = ClientEnumerations.status(statusEnum);
+            final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate");
+
+            final Long officeId = rs.getLong("officeId");
+            final String officeName = rs.getString("officeName");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+            final String staffName = rs.getString("staffName");
+            final String hierarchy = rs.getString("hierarchy");
+            final String groupLevel = rs.getString("groupLevel");
+
+            final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+            final String closedByUsername = rs.getString("closedByUsername");
+            final String closedByFirstname = rs.getString("closedByFirstname");
+            final String closedByLastname = rs.getString("closedByLastname");
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final String activatedByUsername = rs.getString("activatedByUsername");
+            final String activatedByFirstname = rs.getString("activatedByFirstname");
+            final String activatedByLastname = rs.getString("activatedByLastname");
+
+            final GroupTimelineData timeline = new GroupTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname,
+                    submittedByLastname, activationDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate,
+                    closedByUsername, closedByFirstname, closedByLastname);
+
+            return GroupGeneralData.instance(id, accountNo, name, externalId, status, activationDate, officeId, officeName, null, null, staffId,
+                    staffName, hierarchy, groupLevel, timeline);
+        }
+    }
+
+    @Override
+    public Page<CenterData> retrievePagedAll(final SearchParameters searchParameters, final PaginationParameters parameters) {
+
+        this.paginationParametersDataValidator.validateParameterValues(parameters, supportedOrderByValues, "audits");
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.centerMapper.schema());
+        sqlBuilder.append(" where o.hierarchy like ?");
+
+        final String extraCriteria = getCenterExtraCriteria(searchParameters);
+
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            sqlBuilder.append(" and (").append(extraCriteria).append(")");
+        }
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder());
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(),
+                new Object[] { hierarchySearchString }, this.centerMapper);
+    }
+
+    @Override
+    public Collection<CenterData> retrieveAll(SearchParameters searchParameters, PaginationParameters parameters) {
+
+        this.paginationParametersDataValidator.validateParameterValues(parameters, supportedOrderByValues, "audits");
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select ");
+        sqlBuilder.append(this.centerMapper.schema());
+        sqlBuilder.append(" where o.hierarchy like ?");
+
+        final String extraCriteria = getCenterExtraCriteria(searchParameters);
+
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            sqlBuilder.append(" and (").append(extraCriteria).append(")");
+        }
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder());
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), this.centerMapper, new Object[] { hierarchySearchString });
+    }
+
+    @Override
+    public Collection<CenterData> retrieveAllForDropdown(final Long officeId) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final String sql = "select " + this.centerMapper.schema()
+                + " where g.office_id = ? and g.parent_id is null and g.level_Id = ? and o.hierarchy like ? order by g.hierarchy";
+
+        return this.jdbcTemplate.query(sql, this.centerMapper, new Object[] { officeId, GroupTypes.CENTER.getId(), hierarchySearchString });
+    }
+
+    @Override
+    public CenterData retrieveTemplate(final Long officeId, final boolean staffInSelectedOfficeOnly) {
+
+        final Long officeIdDefaulted = defaultToUsersOfficeIfNull(officeId);
+
+        final Collection<OfficeData> officeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+        final boolean loanOfficersOnly = false;
+        Collection<StaffData> staffOptions = null;
+        if (staffInSelectedOfficeOnly) {
+            staffOptions = this.staffReadPlatformService.retrieveAllStaffForDropdown(officeIdDefaulted);
+        } else {
+            staffOptions = this.staffReadPlatformService.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeIdDefaulted,
+                    loanOfficersOnly);
+        }
+
+        if (CollectionUtils.isEmpty(staffOptions)) {
+            staffOptions = null;
+        }
+        final Collection<GroupGeneralData> groupMembersOptions = null;
+        final String accountNo = null;
+
+        // final boolean clientPendingApprovalAllowed =
+        // this.configurationDomainService.isClientPendingApprovalAllowedEnabled();
+
+        return CenterData.template(officeIdDefaulted, accountNo, new LocalDate(), officeOptions, staffOptions, groupMembersOptions);
+    }
+
+    private Long defaultToUsersOfficeIfNull(final Long officeId) {
+        Long defaultOfficeId = officeId;
+        if (defaultOfficeId == null) {
+            defaultOfficeId = this.context.authenticatedUser().getOffice().getId();
+        }
+        return defaultOfficeId;
+    }
+
+    @Override
+    public CenterData retrieveOne(final Long centerId) {
+
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+            final String hierarchy = currentUser.getOffice().getHierarchy();
+            final String hierarchySearchString = hierarchy + "%";
+
+            final String sql = "select " + this.centerMapper.schema() + " where g.id = ? and o.hierarchy like ?";
+            return this.jdbcTemplate.queryForObject(sql, this.centerMapper, new Object[] { centerId, hierarchySearchString });
+
+        } catch (final EmptyResultDataAccessException e) {
+            throw new CenterNotFoundException(centerId);
+        }
+    }
+
+    @Override
+    public GroupGeneralData retrieveCenterGroupTemplate(final Long centerId) {
+
+        final CenterData center = retrieveOne(centerId);
+
+        final Long centerOfficeId = center.officeId();
+        final OfficeData centerOffice = this.officeReadPlatformService.retrieveOffice(centerOfficeId);
+
+        StaffData staff = null;
+        final Long staffId = center.staffId();
+        String staffName = null;
+        if (staffId != null) {
+            staff = this.staffReadPlatformService.retrieveStaff(staffId);
+            staffName = staff.getDisplayName();
+        }
+
+        final Collection<CenterData> centerOptions = Arrays.asList(center);
+        final Collection<OfficeData> officeOptions = Arrays.asList(centerOffice);
+
+        Collection<StaffData> staffOptions = this.staffReadPlatformService.retrieveAllStaffForDropdown(centerOfficeId);
+        if (CollectionUtils.isEmpty(staffOptions)) {
+            staffOptions = null;
+        }
+
+        Collection<ClientData> clientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(centerOfficeId);
+        if (CollectionUtils.isEmpty(clientOptions)) {
+            clientOptions = null;
+        }
+
+        return GroupGeneralData.template(centerOfficeId, center.getId(), center.getAccountNo(), center.getName(), staffId, staffName, centerOptions,
+                officeOptions, staffOptions, clientOptions, null);
+    }
+
+    @Override
+    public Collection<GroupGeneralData> retrieveAssociatedGroups(final Long centerId) {
+        final String sql = "select " + this.groupDataMapper.schema() + " where g.parent_id = ? ";
+        return this.jdbcTemplate.query(sql, this.groupDataMapper, new Object[] { centerId });
+    }
+
+    @Override
+    public CenterData retrieveCenterWithClosureReasons() {
+        final List<CodeValueData> closureReasons = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(GroupingTypesApiConstants.CENTER_CLOSURE_REASON));
+        return CenterData.withClosureReasons(closureReasons);
+    }
+
+    @Override
+    public Collection<StaffCenterData> retriveAllCentersByMeetingDate(final Long officeId, final Date meetingDate, final Long staffId) {
+        validateForGenerateCollectionSheet(staffId);
+        final CenterCalendarDataMapper centerCalendarMapper = new CenterCalendarDataMapper();
+        String sql = centerCalendarMapper.schema();
+        Collection<CenterData> centerDataArray = null;
+
+        if (staffId != null) {
+            sql += " and g.staff_id=? ";
+            centerDataArray = this.jdbcTemplate.query(sql, centerCalendarMapper, new Object[] { officeId, staffId });
+        } else {
+            centerDataArray = this.jdbcTemplate.query(sql, centerCalendarMapper, new Object[] { officeId });
+        }
+
+        Collection<StaffCenterData> staffCenterDataArray = new ArrayList<>();
+        Boolean flag = false;
+        for (CenterData centerData : centerDataArray) {
+            if (centerData.getCollectionMeetingCalendar().isValidRecurringDate(new LocalDate(meetingDate))) {
+                if (staffCenterDataArray.size() <= 0) {
+                    Collection<CenterData> meetingFallCenter = new ArrayList<>();
+                    meetingFallCenter.add(centerData);
+                    staffCenterDataArray.add(StaffCenterData.instance(centerData.staffId(), centerData.getStaffName(), meetingFallCenter));
+                } else {
+                    for (StaffCenterData staffCenterData : staffCenterDataArray) {
+                        flag = false;
+                        if (staffCenterData.getStaffId().equals(centerData.staffId())) {
+                            staffCenterData.getMeetingFallCenters().add(centerData);
+                 
+                            
+                            flag = true;
+                            break;
+                        }
+                    }
+                    if (!flag) {
+                        Collection<CenterData> meetingFallCenter = new ArrayList<>();
+                        meetingFallCenter.add(centerData);
+                        staffCenterDataArray.add(StaffCenterData.instance(centerData.staffId(), centerData.getStaffName(),
+                                meetingFallCenter));
+                    }
+                }
+
+            }
+        }
+        return staffCenterDataArray;
+    }
+
+    public void validateForGenerateCollectionSheet(final Long staffId) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("productivecollectionsheet");
+        baseDataValidator.reset().parameter("staffId").value(staffId).notNull();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformService.java
new file mode 100644
index 0000000..cda67a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformService.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.group.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.group.data.GroupLevelData;
+
+public interface GroupLevelReadPlatformService {
+
+    Collection<GroupLevelData> retrieveAllLevels();
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformServiceImpl.java
new file mode 100644
index 0000000..a53e174
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformServiceImpl.java
@@ -0,0 +1,84 @@
+/**
+ * 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.group.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+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.portfolio.group.data.GroupLevelData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GroupLevelReadPlatformServiceImpl implements GroupLevelReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public GroupLevelReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public Collection<GroupLevelData> retrieveAllLevels() {
+        this.context.authenticatedUser();
+
+        final GroupLevelDataMapper rm = new GroupLevelDataMapper();
+        final String sql = "select " + rm.groupLevelSchema();
+        return this.jdbcTemplate.query(sql, rm);
+
+    }
+
+    private static final class GroupLevelDataMapper implements RowMapper<GroupLevelData> {
+
+        public String groupLevelSchema() {
+            return "gl.id as id, gl.level_name as levelName , gl.parent_id as parentLevelId , pgl.level_name as parentName , "
+                    + "cgl.id as childLevelId,cgl.level_name as childLevelName,gl.super_parent as superParent ,"
+                    + " gl.recursable as recursable , gl.can_have_clients as canHaveClients from m_group_level gl "
+                    + " left join m_group_level pgl on pgl.id = gl.parent_id left join m_group_level cgl on gl.id = cgl.parent_id";
+        }
+
+        @Override
+        public GroupLevelData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long levelId = rs.getLong("id");
+            final String levelName = rs.getString("levelName");
+            final Long parentLevelId = JdbcSupport.getLong(rs, "parentLevelId");
+            final String parentLevelName = rs.getString("parentName");
+            final Long childLevelId = JdbcSupport.getLong(rs, "childLevelId");
+            final String childLevelName = rs.getString("childLevelName");
+            final boolean superParent = rs.getBoolean("superParent");
+            final boolean recursable = rs.getBoolean("recursable");
+            final boolean canHaveClients = rs.getBoolean("canHaveClients");
+
+            return new GroupLevelData(levelId, levelName, parentLevelId, parentLevelName, childLevelId, childLevelName, superParent,
+                    recursable, canHaveClients);
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformService.java
new file mode 100644
index 0000000..9ef7bb8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformService.java
@@ -0,0 +1,41 @@
+/**
+ * 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.group.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+
+public interface GroupReadPlatformService {
+
+    GroupGeneralData retrieveTemplate(Long officeId, boolean isCenterGroup, boolean staffInSelectedOfficeOnly);
+
+    Page<GroupGeneralData> retrievePagedAll(SearchParameters searchParameters, PaginationParameters parameters);
+
+    Collection<GroupGeneralData> retrieveAll(SearchParameters searchParameters, PaginationParameters parameters);
+
+    GroupGeneralData retrieveOne(Long groupId);
+
+    Collection<GroupGeneralData> retrieveGroupsForLookup(Long officeId);
+
+    GroupGeneralData retrieveGroupWithClosureReasons();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java
new file mode 100644
index 0000000..608680a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java
@@ -0,0 +1,300 @@
+/**
+ * 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.group.service;
+
+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.List;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
+import org.apache.fineract.portfolio.group.data.CenterData;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.domain.GroupTypes;
+import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+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;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class GroupReadPlatformServiceImpl implements GroupReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final CenterReadPlatformService centerReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+
+    private final AllGroupTypesDataMapper allGroupTypesDataMapper = new AllGroupTypesDataMapper();
+    private final PaginationHelper<GroupGeneralData> paginationHelper = new PaginationHelper<>();
+    private final PaginationParametersDataValidator paginationParametersDataValidator;
+
+    private final static Set<String> supportedOrderByValues = new HashSet<>(Arrays.asList("id", "name", "officeId", "officeName"));
+
+    @Autowired
+    public GroupReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final CenterReadPlatformService centerReadPlatformService,
+            final OfficeReadPlatformService officeReadPlatformService, final StaffReadPlatformService staffReadPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService,
+            final PaginationParametersDataValidator paginationParametersDataValidator) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.centerReadPlatformService = centerReadPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+        this.paginationParametersDataValidator = paginationParametersDataValidator;
+    }
+
+    @Override
+    public GroupGeneralData retrieveTemplate(final Long officeId, final boolean isCenterGroup, final boolean staffInSelectedOfficeOnly) {
+
+        final Long defaultOfficeId = defaultToUsersOfficeIfNull(officeId);
+
+        Collection<CenterData> centerOptions = null;
+        if (isCenterGroup) {
+            centerOptions = this.centerReadPlatformService.retrieveAllForDropdown(defaultOfficeId);
+        }
+
+        final Collection<OfficeData> officeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+        final boolean loanOfficersOnly = false;
+        Collection<StaffData> staffOptions = null;
+        if (staffInSelectedOfficeOnly) {
+            staffOptions = this.staffReadPlatformService.retrieveAllStaffForDropdown(defaultOfficeId);
+        } else {
+            staffOptions = this.staffReadPlatformService.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(defaultOfficeId,
+                    loanOfficersOnly);
+        }
+
+        if (CollectionUtils.isEmpty(staffOptions)) {
+            staffOptions = null;
+        }
+
+        final Collection<CodeValueData> availableRoles = this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(GroupingTypesApiConstants.GROUP_ROLE_NAME);
+
+        final Long centerId = null;
+        final String accountNo = null;
+        final String centerName = null;
+        final Long staffId = null;
+        final String staffName = null;
+        final Collection<ClientData> clientOptions = null;
+        
+        return GroupGeneralData.template(defaultOfficeId, centerId, accountNo, centerName, staffId, staffName, centerOptions, officeOptions,
+                staffOptions, clientOptions, availableRoles);
+    }
+
+    private Long defaultToUsersOfficeIfNull(final Long officeId) {
+        Long defaultOfficeId = officeId;
+        if (defaultOfficeId == null) {
+            defaultOfficeId = this.context.authenticatedUser().getOffice().getId();
+        }
+        return defaultOfficeId;
+    }
+
+    @Override
+    public Page<GroupGeneralData> retrievePagedAll(final SearchParameters searchParameters, final PaginationParameters parameters) {
+
+        this.paginationParametersDataValidator.validateParameterValues(parameters, supportedOrderByValues, "audits");
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.allGroupTypesDataMapper.schema());
+        sqlBuilder.append(" where o.hierarchy like ?");
+
+        final String extraCriteria = getGroupExtraCriteria(searchParameters);
+
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            sqlBuilder.append(" and (").append(extraCriteria).append(")");
+        }
+
+        if (parameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder());
+        }
+
+        if (parameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(),
+                new Object[] { hierarchySearchString }, this.allGroupTypesDataMapper);
+    }
+
+    @Override
+    public Collection<GroupGeneralData> retrieveAll(SearchParameters searchParameters, final PaginationParameters parameters) {
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select ");
+        sqlBuilder.append(this.allGroupTypesDataMapper.schema());
+        sqlBuilder.append(" where o.hierarchy like ?");
+
+        final String extraCriteria = getGroupExtraCriteria(searchParameters);
+
+        if (StringUtils.isNotBlank(extraCriteria)) {
+            sqlBuilder.append(" and (").append(extraCriteria).append(")");
+        }
+
+        if (parameters.isOrderByRequested()) {
+            sqlBuilder.append(parameters.orderBySql());
+        }
+
+        if (parameters.isLimited()) {
+            sqlBuilder.append(parameters.limitSql());
+        }
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), this.allGroupTypesDataMapper, new Object[] { hierarchySearchString });
+    }
+
+    // 'g.' preffix because of ERROR 1052 (23000): Column 'column_name' in where
+    // clause is ambiguous
+    // caused by the same name of columns in m_office and m_group tables
+    private String getGroupExtraCriteria(final SearchParameters searchCriteria) {
+
+        StringBuffer extraCriteria = new StringBuffer(200);
+        extraCriteria.append(" and g.level_Id = ").append(GroupTypes.GROUP.getId());
+        String sqlSearch = searchCriteria.getSqlSearch();
+        if (sqlSearch != null) {
+            sqlSearch = sqlSearch.replaceAll(" display_name ", " g.display_name ");
+            sqlSearch = sqlSearch.replaceAll("display_name ", "g.display_name ");
+            extraCriteria.append(" and ( ").append(sqlSearch).append(") ");
+        }
+
+        final Long officeId = searchCriteria.getOfficeId();
+        if (officeId != null) {
+            extraCriteria.append(" and g.office_id = ").append(officeId);
+        }
+
+        final String externalId = searchCriteria.getExternalId();
+        if (externalId != null) {
+            extraCriteria.append(" and g.external_id = ").append(ApiParameterHelper.sqlEncodeString(externalId));
+        }
+
+        final String name = searchCriteria.getName();
+        if (name != null) {
+            extraCriteria.append(" and g.display_name like ").append(ApiParameterHelper.sqlEncodeString("%" + name + "%"));
+        }
+
+        final String hierarchy = searchCriteria.getHierarchy();
+        if (hierarchy != null) {
+            extraCriteria.append(" and o.hierarchy like ").append(ApiParameterHelper.sqlEncodeString(hierarchy + "%"));
+        }
+
+        if (searchCriteria.isStaffIdPassed()) {
+            extraCriteria.append(" and g.staff_id = ").append(searchCriteria.getStaffId());
+        }
+
+        if (StringUtils.isNotBlank(extraCriteria.toString())) {
+            extraCriteria.delete(0, 4);
+        }
+
+        final Long staffId = searchCriteria.getStaffId();
+        if (staffId != null) {
+            extraCriteria.append(" and g.staff_id = ").append(staffId);
+        }
+        
+        if(searchCriteria.isOrphansOnly()){
+        	extraCriteria.append(" and g.parent_id IS NULL");
+        }
+
+        return extraCriteria.toString();
+    }
+
+    @Override
+    public GroupGeneralData retrieveOne(final Long groupId) {
+
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+            final String hierarchy = currentUser.getOffice().getHierarchy();
+            final String hierarchySearchString = hierarchy + "%";
+
+            final String sql = "select " + this.allGroupTypesDataMapper.schema() + " where g.id = ? and o.hierarchy like ?";
+            return this.jdbcTemplate.queryForObject(sql, this.allGroupTypesDataMapper, new Object[] { groupId, hierarchySearchString });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new GroupNotFoundException(groupId);
+        }
+    }
+
+    @Override
+    public Collection<GroupGeneralData> retrieveGroupsForLookup(final Long officeId) {
+        this.context.authenticatedUser();
+        final GroupLookupDataMapper rm = new GroupLookupDataMapper();
+        final String sql = "Select " + rm.schema() + " and g.office_id=?";
+        return this.jdbcTemplate.query(sql, rm, new Object[] { officeId });
+    }
+
+    private static final class GroupLookupDataMapper implements RowMapper<GroupGeneralData> {
+
+        public final String schema() {
+            return "g.id as id, g.account_no as accountNo, g.display_name as displayName from m_group g where g.level_id = 2 ";
+        }
+
+        @Override
+        public GroupGeneralData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String accountNo = rs.getString("accountNo");
+            final String displayName = rs.getString("displayName");
+            return GroupGeneralData.lookup(id, accountNo, displayName);
+        }
+    }
+
+    @Override
+    public GroupGeneralData retrieveGroupWithClosureReasons() {
+        final List<CodeValueData> closureReasons = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(GroupingTypesApiConstants.GROUP_CLOSURE_REASON));
+        return GroupGeneralData.withClosureReasons(closureReasons);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformService.java
new file mode 100644
index 0000000..3e31a37
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformService.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.group.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.group.data.GroupRoleData;
+
+public interface GroupRolesReadPlatformService {
+
+    Collection<GroupRoleData> retrieveGroupRoles(Long groupId);
+
+    GroupRoleData retrieveGroupRole(Long groupId, Long roleId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformServiceImpl.java
new file mode 100644
index 0000000..8bb1d9c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformServiceImpl.java
@@ -0,0 +1,89 @@
+/**
+ * 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.group.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+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.portfolio.group.data.GroupRoleData;
+import org.apache.fineract.portfolio.group.exception.GroupRoleNotFoundException;
+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 GroupRolesReadPlatformServiceImpl implements GroupRolesReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public GroupRolesReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public Collection<GroupRoleData> retrieveGroupRoles(final Long groupId) {
+        this.context.authenticatedUser();
+        final GroupRolesDataMapper mapper = new GroupRolesDataMapper();
+        final String sql = "Select " + mapper.schema() + " where role.group_id=?";
+        return this.jdbcTemplate.query(sql, mapper, new Object[] { groupId });
+    }
+
+    @Override
+    public GroupRoleData retrieveGroupRole(final Long groupId, final Long roleId) {
+        try {
+            this.context.authenticatedUser();
+            final GroupRolesDataMapper mapper = new GroupRolesDataMapper();
+            final String sql = "Select " + mapper.schema() + " where role.group_id=? and role.id=?";
+            return this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { groupId, roleId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new GroupRoleNotFoundException(roleId);
+        }
+    }
+
+    private static final class GroupRolesDataMapper implements RowMapper<GroupRoleData> {
+
+        public final String schema() {
+            return " role.id AS id, role.client_id AS clientId, c.display_name as clientName, role.role_cv_id AS roleId, cv.code_value AS roleName"
+                    + " from m_code_value cv join m_group_roles role on role.role_cv_id = cv.id left join m_client c on c.id = role.client_id ";
+        }
+
+        @Override
+        public GroupRoleData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final String clientName = rs.getString("clientName");
+            final Long roleId = JdbcSupport.getLong(rs, "roleId");
+            final String roleName = rs.getString("roleName");
+            final CodeValueData role = CodeValueData.instance(roleId, roleName);
+            return new GroupRoleData(id, role, clientId, clientName);
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformService.java
new file mode 100644
index 0000000..21eb88a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformService.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface GroupRolesWritePlatformService {
+
+    CommandProcessingResult createRole(JsonCommand command);
+
+    CommandProcessingResult updateRole(JsonCommand command);
+
+    CommandProcessingResult deleteRole(Long ruleId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..a1c2aac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,165 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
+import org.apache.fineract.portfolio.group.domain.GroupRole;
+import org.apache.fineract.portfolio.group.domain.GroupRoleRepositoryWrapper;
+import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
+import org.apache.fineract.portfolio.group.serialization.GroupRolesDataValidator;
+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;
+
+@Service
+public class GroupRolesWritePlatformServiceJpaRepositoryImpl implements GroupRolesWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(GroupRolesWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final GroupRepositoryWrapper groupRepository;
+    private final GroupRolesDataValidator fromApiJsonDeserializer;
+    private final CodeValueRepositoryWrapper codeValueRepository;
+    private final ClientRepositoryWrapper clientRepository;
+    private final GroupRoleRepositoryWrapper groupRoleRepository;
+
+    @Autowired
+    public GroupRolesWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final GroupRepositoryWrapper groupRepository, final GroupRolesDataValidator fromApiJsonDeserializer,
+            final CodeValueRepositoryWrapper codeValueRepository, final ClientRepositoryWrapper clientRepository,
+            final GroupRoleRepositoryWrapper groupRoleRepository) {
+        this.context = context;
+        this.groupRepository = groupRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.codeValueRepository = codeValueRepository;
+        this.clientRepository = clientRepository;
+        this.groupRoleRepository = groupRoleRepository;
+    }
+
+    @Override
+    public CommandProcessingResult createRole(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateForCreateGroupRole(command);
+
+            final Long roleId = command.longValueOfParameterNamed(GroupingTypesApiConstants.roleParamName);
+            final CodeValue role = this.codeValueRepository.findOneWithNotFoundDetection(roleId);
+
+            final Long clientId = command.longValueOfParameterNamed(GroupingTypesApiConstants.clientIdParamName);
+            final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+
+            final Group group = this.groupRepository.findOneWithNotFoundDetection(command.getGroupId());
+            if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, command.getGroupId()); }
+            final GroupRole groupRole = GroupRole.createGroupRole(group, client, role);
+            this.groupRoleRepository.save(groupRole);
+            return new CommandProcessingResultBuilder().withClientId(client.getId()).withGroupId(group.getId())
+                    .withEntityId(groupRole.getId()).build();
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleGroupDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+
+    private void handleGroupDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+
+        if (realCause.getMessage().contains("UNIQUE_GROUP_ROLES")) {
+            final String clientId = command.stringValueOfParameterNamed(GroupingTypesApiConstants.clientIdParamName);
+            final String roleId = command.stringValueOfParameterNamed(GroupingTypesApiConstants.roleParamName);
+            final String errorMessageForUser = "Group Role with roleId `" + roleId + "`, clientId `" + clientId + "`, groupId `"
+                    + command.getGroupId() + "` already exists.";
+            final String errorMessageForMachine = "error.msg.group.role.already.exists";
+            throw new PlatformDataIntegrityException(errorMessageForMachine, errorMessageForUser,
+                    GroupingTypesApiConstants.clientIdParamName, roleId, clientId, command.getGroupId());
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    @Override
+    public CommandProcessingResult updateRole(final JsonCommand command) {
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateForUpdateRole(command);
+
+            final Group group = this.groupRepository.findOneWithNotFoundDetection(command.getGroupId());
+
+            final GroupRole groupRole = this.groupRoleRepository.findOneWithNotFoundDetection(command.entityId());
+            final Map<String, Object> actualChanges = groupRole.update(command);
+
+            if (actualChanges.containsKey(GroupingTypesApiConstants.roleParamName)) {
+                final Long newValue = command.longValueOfParameterNamed(GroupingTypesApiConstants.roleParamName);
+
+                CodeValue role = null;
+                if (newValue != null) {
+                    role = this.codeValueRepository.findOneWithNotFoundDetection(newValue);
+                }
+                groupRole.updateRole(role);
+            }
+
+            if (actualChanges.containsKey(GroupingTypesApiConstants.clientIdParamName)) {
+                final Long newValue = command.longValueOfParameterNamed(GroupingTypesApiConstants.clientIdParamName);
+
+                Client client = null;
+                if (newValue != null) {
+                    client = this.clientRepository.findOneWithNotFoundDetection(newValue);
+                    if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(newValue, command.getGroupId()); }
+                }
+                groupRole.updateClient(client);
+            }
+
+            this.groupRoleRepository.saveAndFlush(groupRole);
+            return new CommandProcessingResultBuilder().with(actualChanges).withGroupId(group.getId()).withEntityId(groupRole.getId())
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleGroupDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+
+    @Override
+    public CommandProcessingResult deleteRole(final Long ruleId) {
+        this.context.authenticatedUser();
+        final GroupRole groupRole = this.groupRoleRepository.findOneWithNotFoundDetection(ruleId);
+        this.groupRoleRepository.delete(groupRole);
+        return new CommandProcessingResultBuilder().withEntityId(groupRole.getId()).build();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupTypeEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupTypeEnumerations.java
new file mode 100644
index 0000000..2201828
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupTypeEnumerations.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.portfolio.group.service;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.group.domain.GroupTypes;
+
+public class GroupTypeEnumerations {
+
+    public static EnumOptionData groupType(final Integer id) {
+        return groupType(GroupTypes.fromInt(id));
+    }
+
+    public static EnumOptionData groupType(final GroupTypes type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case CENTER:
+                optionData = new EnumOptionData(type.getId().longValue(), type.getCode(), "Individual loan");
+            break;
+            case GROUP:
+                optionData = new EnumOptionData(type.getId().longValue(), type.getCode(), "Group loan");
+            break;
+            default:
+                optionData = new EnumOptionData(GroupTypes.INVALID.getId().longValue(), GroupTypes.INVALID.getCode(), "Invalid");
+            break;
+
+        }
+        return optionData;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformService.java
new file mode 100755
index 0000000..d3582fc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformService.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.group.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface GroupingTypesWritePlatformService {
+
+    CommandProcessingResult createCenter(JsonCommand command);
+
+    CommandProcessingResult updateCenter(Long entityId, JsonCommand command);
+
+    CommandProcessingResult createGroup(Long centerId, JsonCommand command);
+
+    CommandProcessingResult activateGroupOrCenter(Long entityId, JsonCommand command);
+
+    CommandProcessingResult updateGroup(Long groupId, JsonCommand command);
+
+    CommandProcessingResult deleteGroup(Long groupId);
+
+    CommandProcessingResult closeGroup(final Long groupId, final JsonCommand command);
+
+    CommandProcessingResult closeCenter(final Long centerId, final JsonCommand command);
+
+    CommandProcessingResult unassignGroupOrCenterStaff(Long groupId, JsonCommand command);
+
+    CommandProcessingResult assignGroupOrCenterStaff(Long groupId, JsonCommand command);
+
+    CommandProcessingResult associateClientsToGroup(Long groupId, JsonCommand command);
+
+    CommandProcessingResult disassociateClientsFromGroup(Long groupId, JsonCommand command);
+
+    CommandProcessingResult associateGroupsToCenter(final Long centerId, final JsonCommand command);
+
+    CommandProcessingResult disassociateGroupsToCenter(final Long centerId, final JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..aa65886
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,939 @@
+/**
+ * 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.group.service;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandProcessingService;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+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.GeneralPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.exception.InvalidOfficeException;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.service.LoanStatusMapper;
+import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupLevel;
+import org.apache.fineract.portfolio.group.domain.GroupLevelRepository;
+import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
+import org.apache.fineract.portfolio.group.domain.GroupTypes;
+import org.apache.fineract.portfolio.group.exception.GroupAccountExistsException;
+import org.apache.fineract.portfolio.group.exception.GroupHasNoStaffException;
+import org.apache.fineract.portfolio.group.exception.GroupMemberCountNotInPermissibleRangeException;
+import org.apache.fineract.portfolio.group.exception.GroupMustBePendingToBeDeletedException;
+import org.apache.fineract.portfolio.group.exception.InvalidGroupLevelException;
+import org.apache.fineract.portfolio.group.exception.InvalidGroupStateTransitionException;
+import org.apache.fineract.portfolio.group.serialization.GroupingTypesDataValidator;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+
+@Service
+public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements GroupingTypesWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(GroupingTypesWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final GroupRepositoryWrapper groupRepository;
+    private final ClientRepositoryWrapper clientRepositoryWrapper;
+    private final OfficeRepository officeRepository;
+    private final StaffRepositoryWrapper staffRepository;
+    private final NoteRepository noteRepository;
+    private final GroupLevelRepository groupLevelRepository;
+    private final GroupingTypesDataValidator fromApiJsonDeserializer;
+    private final LoanRepository loanRepository;
+    private final CodeValueRepositoryWrapper codeValueRepository;
+    private final SavingsAccountRepository savingsRepository;
+    private final CommandProcessingService commandProcessingService;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final SavingsAccountRepository savingsAccountRepository;
+    private final LoanRepositoryWrapper loanRepositoryWrapper;
+    private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
+    private final AccountNumberGenerator accountNumberGenerator;
+
+    @Autowired
+    public GroupingTypesWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final GroupRepositoryWrapper groupRepository, final ClientRepositoryWrapper clientRepositoryWrapper,
+            final OfficeRepository officeRepository, final StaffRepositoryWrapper staffRepository, final NoteRepository noteRepository,
+            final GroupLevelRepository groupLevelRepository, final GroupingTypesDataValidator fromApiJsonDeserializer,
+            final LoanRepository loanRepository, final SavingsAccountRepository savingsRepository,
+            final CodeValueRepositoryWrapper codeValueRepository, final CommandProcessingService commandProcessingService,
+            final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService,
+            final SavingsAccountRepository savingsAccountRepository, final LoanRepositoryWrapper loanRepositoryWrapper, 
+            final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final AccountNumberGenerator accountNumberGenerator) {
+        this.context = context;
+        this.groupRepository = groupRepository;
+        this.clientRepositoryWrapper = clientRepositoryWrapper;
+        this.officeRepository = officeRepository;
+        this.staffRepository = staffRepository;
+        this.noteRepository = noteRepository;
+        this.groupLevelRepository = groupLevelRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.loanRepository = loanRepository;
+        this.savingsRepository = savingsRepository;
+        this.codeValueRepository = codeValueRepository;
+        this.commandProcessingService = commandProcessingService;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.savingsAccountRepository = savingsAccountRepository;
+        this.loanRepositoryWrapper = loanRepositoryWrapper;
+        this.accountNumberFormatRepository = accountNumberFormatRepository;
+        this.accountNumberGenerator = accountNumberGenerator;
+    }
+
+    private CommandProcessingResult createGroupingType(final JsonCommand command, final GroupTypes groupingType, final Long centerId) {
+        try {
+            final String accountNo = command.stringValueOfParameterNamed(GroupingTypesApiConstants.accountNoParamName);
+            final String name = command.stringValueOfParameterNamed(GroupingTypesApiConstants.nameParamName);
+            final String externalId = command.stringValueOfParameterNamed(GroupingTypesApiConstants.externalIdParamName);
+
+            final AppUser currentUser = this.context.authenticatedUser();
+            Long officeId = null;
+            Group parentGroup = null;
+
+            if (centerId == null) {
+                officeId = command.longValueOfParameterNamed(GroupingTypesApiConstants.officeIdParamName);
+            } else {
+                parentGroup = this.groupRepository.findOneWithNotFoundDetection(centerId);
+                officeId = parentGroup.officeId();
+            }
+
+            final Office groupOffice = this.officeRepository.findOne(officeId);
+            if (groupOffice == null) { throw new OfficeNotFoundException(officeId); }
+
+            final LocalDate activationDate = command.localDateValueOfParameterNamed(GroupingTypesApiConstants.activationDateParamName);
+            final GroupLevel groupLevel = this.groupLevelRepository.findOne(groupingType.getId());
+
+            validateOfficeOpeningDateisAfterGroupOrCenterOpeningDate(groupOffice, groupLevel, activationDate);
+
+            Staff staff = null;
+            final Long staffId = command.longValueOfParameterNamed(GroupingTypesApiConstants.staffIdParamName);
+            if (staffId != null) {
+                staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, groupOffice.getHierarchy());
+            }
+
+            final Set<Client> clientMembers = assembleSetOfClients(officeId, command);
+
+            final Set<Group> groupMembers = assembleSetOfChildGroups(officeId, command);
+
+            final boolean active = command.booleanPrimitiveValueOfParameterNamed(GroupingTypesApiConstants.activeParamName);
+            LocalDate submittedOnDate = new LocalDate();
+            if (active && submittedOnDate.isAfter(activationDate)) {
+                submittedOnDate = activationDate;
+            }
+            if (command.hasParameter(GroupingTypesApiConstants.submittedOnDateParamName)) {
+                submittedOnDate = command.localDateValueOfParameterNamed(GroupingTypesApiConstants.submittedOnDateParamName);
+            }
+
+            final Group newGroup = Group.newGroup(groupOffice, staff, parentGroup, groupLevel, name, externalId, active, activationDate,
+                    clientMembers, groupMembers, submittedOnDate, currentUser, accountNo);
+
+            boolean rollbackTransaction = false;
+            if (newGroup.isActive()) {
+                // validate Group creation rules for Group
+                if (newGroup.isGroup()) {
+                    validateGroupRulesBeforeActivation(newGroup);
+                }
+
+                if (newGroup.isCenter()) {
+                    final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateCenter(null).build();
+                    rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser);
+                } else {
+                    final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateGroup(null).build();
+                    rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser);
+                }
+            }
+
+            if (!newGroup.isCenter() && newGroup.hasActiveClients()) {
+                final CommandWrapper commandWrapper = new CommandWrapperBuilder().associateClientsToGroup(newGroup.getId()).build();
+                rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser);
+            }
+
+            // pre-save to generate id for use in group hierarchy
+            this.groupRepository.save(newGroup);
+
+            /*
+             * Generate hierarchy for a new center/group and all the child
+             * groups if they exist
+             */
+            newGroup.generateHierarchy();
+
+            /* Generate account number if required */
+            generateAccountNumberIfRequired(newGroup);
+
+            this.groupRepository.saveAndFlush(newGroup);
+            newGroup.captureStaffHistoryDuringCenterCreation(staff, activationDate);
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withOfficeId(groupOffice.getId()) //
+                    .withGroupId(newGroup.getId()) //
+                    .withEntityId(newGroup.getId()) //
+                    .setRollbackTransaction(rollbackTransaction)//
+                    .build();
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleGroupDataIntegrityIssues(command, dve, groupingType);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private void generateAccountNumberIfRequired(Group newGroup){
+    	if (newGroup.isAccountNumberRequiresAutoGeneration()) {
+        	EntityAccountType entityAccountType = null;
+        	AccountNumberFormat accountNumberFormat = null;
+        	if(newGroup.isCenter()){
+            	entityAccountType = EntityAccountType.CENTER;
+            	accountNumberFormat = this.accountNumberFormatRepository
+                        .findByAccountType(entityAccountType);
+                newGroup.updateAccountNo(this.accountNumberGenerator.generateCenterAccountNumber(newGroup, accountNumberFormat));
+        	}else {
+            	entityAccountType = EntityAccountType.GROUP;
+            	accountNumberFormat = this.accountNumberFormatRepository
+                        .findByAccountType(entityAccountType);
+                newGroup.updateAccountNo(this.accountNumberGenerator.generateGroupAccountNumber(newGroup, accountNumberFormat));
+        	}
+            
+        }
+    }
+    @Transactional
+    @Override
+    public CommandProcessingResult createCenter(final JsonCommand command) {
+
+        this.fromApiJsonDeserializer.validateForCreateCenter(command);
+
+        final Long centerId = null;
+        return createGroupingType(command, GroupTypes.CENTER, centerId);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createGroup(final Long centerId, final JsonCommand command) {
+
+        if (centerId != null) {
+            this.fromApiJsonDeserializer.validateForCreateCenterGroup(command);
+        } else {
+            this.fromApiJsonDeserializer.validateForCreateGroup(command);
+        }
+
+        return createGroupingType(command, GroupTypes.GROUP, centerId);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult activateGroupOrCenter(final Long groupId, final JsonCommand command) {
+
+        try {
+            this.fromApiJsonDeserializer.validateForActivation(command, GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+
+            final AppUser currentUser = this.context.authenticatedUser();
+
+            final Group group = this.groupRepository.findOneWithNotFoundDetection(groupId);
+
+            if (group.isGroup()) {
+                validateGroupRulesBeforeActivation(group);
+            }
+
+            final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate");
+
+            validateOfficeOpeningDateisAfterGroupOrCenterOpeningDate(group.getOffice(), group.getGroupLevel(), activationDate);
+            group.activate(currentUser, activationDate);
+
+            this.groupRepository.saveAndFlush(group);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withOfficeId(group.officeId()) //
+                    .withGroupId(groupId) //
+                    .withEntityId(groupId) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleGroupDataIntegrityIssues(command, dve, GroupTypes.GROUP);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private void validateGroupRulesBeforeActivation(final Group group) {
+        Integer minClients = configurationDomainService.retrieveMinAllowedClientsInGroup();
+        Integer maxClients = configurationDomainService.retrieveMaxAllowedClientsInGroup();
+        boolean isGroupClientCountValid = group.isGroupsClientCountWithinMinMaxRange(minClients, maxClients);
+        if (!isGroupClientCountValid) { throw new GroupMemberCountNotInPermissibleRangeException(group.getId(), minClients, maxClients); }
+    }
+
+    public void validateGroupRulesBeforeClientAssociation(final Group group) {
+        Integer minClients = configurationDomainService.retrieveMinAllowedClientsInGroup();
+        Integer maxClients = configurationDomainService.retrieveMaxAllowedClientsInGroup();
+        boolean isGroupClientCountValid = group.isGroupsClientCountWithinMaxRange(maxClients);
+        if (!isGroupClientCountValid) { throw new GroupMemberCountNotInPermissibleRangeException(group.getId(), minClients, maxClients); }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateCenter(final Long centerId, final JsonCommand command) {
+
+        this.fromApiJsonDeserializer.validateForUpdateCenter(command);
+
+        return updateGroupingType(centerId, command, GroupTypes.CENTER);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateGroup(final Long groupId, final JsonCommand command) {
+
+        this.fromApiJsonDeserializer.validateForUpdateGroup(command);
+
+        return updateGroupingType(groupId, command, GroupTypes.GROUP);
+    }
+
+    private CommandProcessingResult updateGroupingType(final Long groupId, final JsonCommand command, final GroupTypes groupingType) {
+
+        try {
+            this.context.authenticatedUser();
+            final Group groupForUpdate = this.groupRepository.findOneWithNotFoundDetection(groupId);
+            final Long officeId = groupForUpdate.officeId();
+            final Office groupOffice = groupForUpdate.getOffice();
+            final String groupHierarchy = groupOffice.getHierarchy();
+
+            this.context.validateAccessRights(groupHierarchy);
+
+            final LocalDate activationDate = command.localDateValueOfParameterNamed(GroupingTypesApiConstants.activationDateParamName);
+
+            validateOfficeOpeningDateisAfterGroupOrCenterOpeningDate(groupOffice, groupForUpdate.getGroupLevel(), activationDate);
+
+            final Map<String, Object> actualChanges = groupForUpdate.update(command);
+
+            if (actualChanges.containsKey(GroupingTypesApiConstants.staffIdParamName)) {
+                final Long newValue = command.longValueOfParameterNamed(GroupingTypesApiConstants.staffIdParamName);
+
+                Staff newStaff = null;
+                if (newValue != null) {
+                    newStaff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(newValue, groupHierarchy);
+                }
+                groupForUpdate.updateStaff(newStaff);
+            }
+
+            final GroupLevel groupLevel = this.groupLevelRepository.findOne(groupForUpdate.getGroupLevel().getId());
+
+            /*
+             * Ignoring parentId param, if group for update is super parent.
+             * TODO Need to check: Ignoring is correct or need throw unsupported
+             * param
+             */
+            if (!groupLevel.isSuperParent()) {
+
+                Long parentId = null;
+                final Group presentParentGroup = groupForUpdate.getParent();
+
+                if (presentParentGroup != null) {
+                    parentId = presentParentGroup.getId();
+                }
+
+                if (command.isChangeInLongParameterNamed(GroupingTypesApiConstants.centerIdParamName, parentId)) {
+
+                    final Long newValue = command.longValueOfParameterNamed(GroupingTypesApiConstants.centerIdParamName);
+                    actualChanges.put(GroupingTypesApiConstants.centerIdParamName, newValue);
+                    Group newParentGroup = null;
+                    if (newValue != null) {
+                        newParentGroup = this.groupRepository.findOneWithNotFoundDetection(newValue);
+
+                        if (!newParentGroup.isOfficeIdentifiedBy(officeId)) {
+                            final String errorMessage = "Group and parent group must have the same office";
+                            throw new InvalidOfficeException("group", "attach.to.parent.group", errorMessage);
+                        }
+                        /*
+                         * If Group is not super parent then validate group
+                         * level's parent level is same as group parent's level
+                         * this check makes sure new group is added at immediate
+                         * next level in hierarchy
+                         */
+
+                        if (!groupForUpdate.getGroupLevel().isIdentifiedByParentId(newParentGroup.getGroupLevel().getId())) {
+                            final String errorMessage = "Parent group's level is  not equal to child level's parent level ";
+                            throw new InvalidGroupLevelException("add", "invalid.level", errorMessage);
+                        }
+                    }
+
+                    groupForUpdate.setParent(newParentGroup);
+
+                    // Parent has changed, re-generate 'Hierarchy' as parent is
+                    // changed
+                    groupForUpdate.generateHierarchy();
+
+                }
+            }
+
+            /*
+             * final Set<Client> clientMembers = assembleSetOfClients(officeId,
+             * command); List<String> changes =
+             * groupForUpdate.updateClientMembersIfDifferent(clientMembers); if
+             * (!changes.isEmpty()) {
+             * actualChanges.put(GroupingTypesApiConstants
+             * .clientMembersParamName, changes); }
+             */
+
+            this.groupRepository.saveAndFlush(groupForUpdate);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withOfficeId(groupForUpdate.officeId()) //
+                    .withGroupId(groupForUpdate.getId()) //
+                    .withEntityId(groupForUpdate.getId()) //
+                    .with(actualChanges) //
+                    .build();
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleGroupDataIntegrityIssues(command, dve, groupingType);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult unassignGroupOrCenterStaff(final Long grouptId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+
+        this.fromApiJsonDeserializer.validateForUnassignStaff(command.json());
+        final Group groupForUpdate = this.groupRepository.findOneWithNotFoundDetection(grouptId);
+        final Staff presentStaff = groupForUpdate.getStaff();
+        Long presentStaffId = null;
+        if (presentStaff == null) { throw new GroupHasNoStaffException(grouptId); }
+        presentStaffId = presentStaff.getId();
+        final String staffIdParamName = "staffId";
+        if (!command.isChangeInLongParameterNamed(staffIdParamName, presentStaffId)) {
+            groupForUpdate.unassignStaff();
+        }
+        this.groupRepository.saveAndFlush(groupForUpdate);
+
+        actualChanges.put(staffIdParamName, null);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(groupForUpdate.getId()) //
+                .withGroupId(groupForUpdate.officeId()) //
+                .withEntityId(groupForUpdate.getId()) //
+                .with(actualChanges) //
+                .build();
+
+    }
+
+    @Override
+    public CommandProcessingResult assignGroupOrCenterStaff(final Long groupId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(5);
+
+        this.fromApiJsonDeserializer.validateForAssignStaff(command.json());
+
+        final Group groupForUpdate = this.groupRepository.findOneWithNotFoundDetection(groupId);
+
+        Staff staff = null;
+        final Long staffId = command.longValueOfParameterNamed(GroupingTypesApiConstants.staffIdParamName);
+        final boolean inheritStaffForClientAccounts = command
+                .booleanPrimitiveValueOfParameterNamed(GroupingTypesApiConstants.inheritStaffForClientAccounts);
+        staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, groupForUpdate.getOffice().getHierarchy());
+        groupForUpdate.updateStaff(staff);
+
+        if (inheritStaffForClientAccounts) {
+            LocalDate loanOfficerReassignmentDate = LocalDate.now();
+            /*
+             * update loan officer for client and update loan officer for
+             * clients loans and savings
+             */
+            Set<Client> clients = groupForUpdate.getClientMembers();
+            if (clients != null) {
+                for (Client client : clients) {
+                    client.updateStaff(staff);
+                    if (this.loanRepository.doNonClosedLoanAccountsExistForClient(client.getId())) {
+                        for (final Loan loan : this.loanRepository.findLoanByClientId(client.getId())) {
+                            if (loan.isDisbursed() && !loan.isClosed()) {
+                                loan.reassignLoanOfficer(staff, loanOfficerReassignmentDate);
+                            }
+                        }
+                    }
+                    if (this.savingsAccountRepository.doNonClosedSavingAccountsExistForClient(client.getId())) {
+                        for (final SavingsAccount savingsAccount : this.savingsAccountRepository
+                                .findSavingAccountByClientId(client.getId())) {
+                            if (!savingsAccount.isClosed()) {
+                                savingsAccount.reassignSavingsOfficer(staff, loanOfficerReassignmentDate);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        this.groupRepository.saveAndFlush(groupForUpdate);
+
+        actualChanges.put(GroupingTypesApiConstants.staffIdParamName, staffId);
+
+        return new CommandProcessingResultBuilder() //
+                .withOfficeId(groupForUpdate.officeId()) //
+                .withEntityId(groupForUpdate.getId()) //
+                .withGroupId(groupId) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteGroup(final Long groupId) {
+
+        final Group groupForDelete = this.groupRepository.findOneWithNotFoundDetection(groupId);
+
+        if (groupForDelete.isNotPending()) { throw new GroupMustBePendingToBeDeletedException(groupId); }
+
+        final List<Note> relatedNotes = this.noteRepository.findByGroupId(groupId);
+        this.noteRepository.deleteInBatch(relatedNotes);
+
+        this.groupRepository.delete(groupForDelete);
+
+        return new CommandProcessingResultBuilder() //
+                .withOfficeId(groupForDelete.getId()) //
+                .withGroupId(groupForDelete.officeId()) //
+                .withEntityId(groupForDelete.getId()) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult closeGroup(final Long groupId, final JsonCommand command) {
+        this.fromApiJsonDeserializer.validateForGroupClose(command);
+        final Group group = this.groupRepository.findOneWithNotFoundDetection(groupId);
+        final LocalDate closureDate = command.localDateValueOfParameterNamed(GroupingTypesApiConstants.closureDateParamName);
+        final Long closureReasonId = command.longValueOfParameterNamed(GroupingTypesApiConstants.closureReasonIdParamName);
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        final CodeValue closureReason = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
+                GroupingTypesApiConstants.GROUP_CLOSURE_REASON, closureReasonId);
+
+        if (group.hasActiveClients()) {
+            final String errorMessage = group.getGroupLevel().getLevelName()
+                    + " cannot be closed because of active clients associated with it.";
+            throw new InvalidGroupStateTransitionException(group.getGroupLevel().getLevelName(), "close", "active.clients.exist",
+                    errorMessage);
+        }
+
+        validateLoansAndSavingsForGroupOrCenterClose(group, closureDate);
+
+        group.close(currentUser, closureReason, closureDate);
+
+        this.groupRepository.saveAndFlush(group);
+
+        return new CommandProcessingResultBuilder() //
+                .withGroupId(groupId) //
+                .withEntityId(groupId) //
+                .build();
+    }
+
+    private void validateLoansAndSavingsForGroupOrCenterClose(final Group groupOrCenter, final LocalDate closureDate) {
+        final Collection<Loan> groupLoans = this.loanRepository.findByGroupId(groupOrCenter.getId());
+        for (final Loan loan : groupLoans) {
+            final LoanStatusMapper loanStatus = new LoanStatusMapper(loan.status().getValue());
+            if (loanStatus.isOpen()) {
+                final String errorMessage = groupOrCenter.getGroupLevel().getLevelName() + " cannot be closed because of non-closed loans.";
+                throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close", "loan.not.closed",
+                        errorMessage);
+            } else if (loanStatus.isClosed() && loan.getClosedOnDate().after(closureDate.toDate())) {
+                final String errorMessage = groupOrCenter.getGroupLevel().getLevelName()
+                        + "closureDate cannot be before the loan closedOnDate.";
+                throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close",
+                        "date.cannot.before.loan.closed.date", errorMessage, closureDate, loan.getClosedOnDate());
+            } else if (loanStatus.isPendingApproval()) {
+                final String errorMessage = groupOrCenter.getGroupLevel().getLevelName() + " cannot be closed because of non-closed loans.";
+                throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close", "loan.not.closed",
+                        errorMessage);
+            } else if (loanStatus.isAwaitingDisbursal()) {
+                final String errorMessage = "Group cannot be closed because of non-closed loans.";
+                throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close", "loan.not.closed",
+                        errorMessage);
+            }
+        }
+
+        final List<SavingsAccount> groupSavingAccounts = this.savingsRepository.findByGroupId(groupOrCenter.getId());
+
+        for (final SavingsAccount saving : groupSavingAccounts) {
+            if (saving.isActive() || saving.isSubmittedAndPendingApproval() || saving.isApproved()) {
+                final String errorMessage = groupOrCenter.getGroupLevel().getLevelName()
+                        + " cannot be closed with active savings accounts associated.";
+                throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close",
+                        "savings.account.not.closed", errorMessage);
+            } else if (saving.isClosed() && saving.getClosedOnDate().isAfter(closureDate)) {
+                final String errorMessage = groupOrCenter.getGroupLevel().getLevelName()
+                        + " closureDate cannot be before the loan closedOnDate.";
+                throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close",
+                        "date.cannot.before.loan.closed.date", errorMessage, closureDate, saving.getClosedOnDate());
+            }
+        }
+    }
+
+    @Override
+    public CommandProcessingResult closeCenter(final Long centerId, final JsonCommand command) {
+        this.fromApiJsonDeserializer.validateForCenterClose(command);
+        final Group center = this.groupRepository.findOneWithNotFoundDetection(centerId);
+        final LocalDate closureDate = command.localDateValueOfParameterNamed(GroupingTypesApiConstants.closureDateParamName);
+        final Long closureReasonId = command.longValueOfParameterNamed(GroupingTypesApiConstants.closureReasonIdParamName);
+
+        final CodeValue closureReason = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
+                GroupingTypesApiConstants.CENTER_CLOSURE_REASON, closureReasonId);
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        if (center.hasActiveGroups()) {
+            final String errorMessage = center.getGroupLevel().getLevelName()
+                    + " cannot be closed because of active groups associated with it.";
+            throw new InvalidGroupStateTransitionException(center.getGroupLevel().getLevelName(), "close", "active.groups.exist",
+                    errorMessage);
+        }
+
+        validateLoansAndSavingsForGroupOrCenterClose(center, closureDate);
+
+        center.close(currentUser, closureReason, closureDate);
+
+        this.groupRepository.saveAndFlush(center);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(centerId) //
+                .build();
+    }
+
+    private Set<Client> assembleSetOfClients(final Long groupOfficeId, final JsonCommand command) {
+
+        final Set<Client> clientMembers = new HashSet<>();
+        final String[] clientMembersArray = command.arrayValueOfParameterNamed(GroupingTypesApiConstants.clientMembersParamName);
+
+        if (!ObjectUtils.isEmpty(clientMembersArray)) {
+            for (final String clientId : clientMembersArray) {
+                final Long id = Long.valueOf(clientId);
+                final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(id);
+                if (!client.isOfficeIdentifiedBy(groupOfficeId)) {
+                    final String errorMessage = "Client with identifier " + clientId + " must have the same office as group.";
+                    throw new InvalidOfficeException("client", "attach.to.group", errorMessage, clientId, groupOfficeId);
+                }
+                clientMembers.add(client);
+            }
+        }
+
+        return clientMembers;
+    }
+
+    private Set<Group> assembleSetOfChildGroups(final Long officeId, final JsonCommand command) {
+
+        final Set<Group> childGroups = new HashSet<>();
+        final String[] childGroupsArray = command.arrayValueOfParameterNamed(GroupingTypesApiConstants.groupMembersParamName);
+
+        if (!ObjectUtils.isEmpty(childGroupsArray)) {
+            for (final String groupId : childGroupsArray) {
+                final Long id = Long.valueOf(groupId);
+                final Group group = this.groupRepository.findOneWithNotFoundDetection(id);
+
+                if (!group.isOfficeIdentifiedBy(officeId)) {
+                    final String errorMessage = "Group and child groups must have the same office.";
+                    throw new InvalidOfficeException("group", "attach.to.parent.group", errorMessage);
+                }
+
+                childGroups.add(group);
+            }
+        }
+
+        return childGroups;
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleGroupDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve,
+            final GroupTypes groupLevel) {
+
+        String levelName = "Invalid";
+        switch (groupLevel) {
+            case CENTER:
+                levelName = "Center";
+            break;
+            case GROUP:
+                levelName = "Group";
+            break;
+            case INVALID:
+            break;
+        }
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        String errorMessageForUser = null;
+        String errorMessageForMachine = null;
+
+        if (realCause.getMessage().contains("external_id")) {
+
+            final String externalId = command.stringValueOfParameterNamed(GroupingTypesApiConstants.externalIdParamName);
+            errorMessageForUser = levelName + " with externalId `" + externalId + "` already exists.";
+            errorMessageForMachine = "error.msg." + levelName.toLowerCase() + ".duplicate.externalId";
+            throw new PlatformDataIntegrityException(errorMessageForMachine, errorMessageForUser,
+                    GroupingTypesApiConstants.externalIdParamName, externalId);
+        } else if (realCause.getMessage().contains("name")) {
+
+            final String name = command.stringValueOfParameterNamed(GroupingTypesApiConstants.nameParamName);
+            errorMessageForUser = levelName + " with name `" + name + "` already exists.";
+            errorMessageForMachine = "error.msg." + levelName.toLowerCase() + ".duplicate.name";
+            throw new PlatformDataIntegrityException(errorMessageForMachine, errorMessageForUser, GroupingTypesApiConstants.nameParamName,
+                    name);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    @Override
+    public CommandProcessingResult associateClientsToGroup(final Long groupId, final JsonCommand command) {
+
+        this.fromApiJsonDeserializer.validateForAssociateClients(command.json());
+
+        final Group groupForUpdate = this.groupRepository.findOneWithNotFoundDetection(groupId);
+        final Set<Client> clientMembers = assembleSetOfClients(groupForUpdate.officeId(), command);
+        final Map<String, Object> actualChanges = new HashMap<>();
+
+        final List<String> changes = groupForUpdate.associateClients(clientMembers);
+
+        if (groupForUpdate.isGroup()) {
+            validateGroupRulesBeforeClientAssociation(groupForUpdate);
+        }
+        if (!changes.isEmpty()) {
+            actualChanges.put(GroupingTypesApiConstants.clientMembersParamName, changes);
+        }
+
+        this.groupRepository.saveAndFlush(groupForUpdate);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(groupForUpdate.officeId()) //
+                .withGroupId(groupForUpdate.getId()) //
+                .withEntityId(groupForUpdate.getId()) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult disassociateClientsFromGroup(final Long groupId, final JsonCommand command) {
+        this.fromApiJsonDeserializer.validateForDisassociateClients(command.json());
+
+        final Group groupForUpdate = this.groupRepository.findOneWithNotFoundDetection(groupId);
+        final Set<Client> clientMembers = assembleSetOfClients(groupForUpdate.officeId(), command);
+
+        // check if any client has got group loans
+        checkForActiveJLGLoans(groupForUpdate.getId(), clientMembers);
+        validateForJLGSavings(groupForUpdate.getId(), clientMembers);
+        final Map<String, Object> actualChanges = new HashMap<>();
+
+        final List<String> changes = groupForUpdate.disassociateClients(clientMembers);
+        if (!changes.isEmpty()) {
+            actualChanges.put(GroupingTypesApiConstants.clientMembersParamName, changes);
+        }
+
+        this.groupRepository.saveAndFlush(groupForUpdate);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(groupForUpdate.officeId()) //
+                .withGroupId(groupForUpdate.getId()) //
+                .withEntityId(groupForUpdate.getId()) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult associateGroupsToCenter(final Long centerId, final JsonCommand command) {
+
+        this.fromApiJsonDeserializer.validateForAssociateGroups(command.json());
+        final Group centerForUpdate = this.groupRepository.findOneWithNotFoundDetection(centerId);
+        final Set<Group> groupMembers = assembleSetOfChildGroups(centerForUpdate.officeId(), command);
+        checkGroupMembersMeetingSyncWithCenterMeeting(centerId, groupMembers);
+
+        final Map<String, Object> actualChanges = new HashMap<>();
+
+        final List<String> changes = centerForUpdate.associateGroups(groupMembers);
+        if (!changes.isEmpty()) {
+            actualChanges.put(GroupingTypesApiConstants.groupMembersParamName, changes);
+        }
+
+        this.groupRepository.saveAndFlush(centerForUpdate);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(centerForUpdate.officeId()) //
+                .withGroupId(centerForUpdate.getId()) //
+                .withEntityId(centerForUpdate.getId()) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult disassociateGroupsToCenter(final Long centerId, final JsonCommand command) {
+        this.fromApiJsonDeserializer.validateForDisassociateGroups(command.json());
+
+        final Group centerForUpdate = this.groupRepository.findOneWithNotFoundDetection(centerId);
+        final Set<Group> groupMembers = assembleSetOfChildGroups(centerForUpdate.officeId(), command);
+
+        final Map<String, Object> actualChanges = new HashMap<>();
+
+        final List<String> changes = centerForUpdate.disassociateGroups(groupMembers);
+        if (!changes.isEmpty()) {
+            actualChanges.put(GroupingTypesApiConstants.clientMembersParamName, changes);
+        }
+
+        this.groupRepository.saveAndFlush(centerForUpdate);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(centerForUpdate.officeId()) //
+                .withGroupId(centerForUpdate.getId()) //
+                .withEntityId(centerForUpdate.getId()) //
+                .with(actualChanges) //
+                .build();
+
+    }
+
+    @Transactional
+    private void checkForActiveJLGLoans(final Long groupId, final Set<Client> clientMembers) {
+        for (final Client client : clientMembers) {
+            final Collection<Loan> loans = this.loanRepositoryWrapper.findActiveLoansByLoanIdAndGroupId(client.getId(), groupId);
+            if (!CollectionUtils.isEmpty(loans)) {
+                final String defaultUserMessage = "Client with identifier " + client.getId()
+                        + " cannot be disassociated it has group loans.";
+                throw new GroupAccountExistsException("disassociate", "client.has.group.loan", defaultUserMessage, client.getId(), groupId);
+            }
+        }
+    }
+
+    @Transactional
+    private void validateForJLGSavings(final Long groupId, final Set<Client> clientMembers) {
+        for (final Client client : clientMembers) {
+            final Collection<SavingsAccount> savings = this.savingsRepository.findByClientIdAndGroupId(client.getId(), groupId);
+            if (!CollectionUtils.isEmpty(savings)) {
+                final String defaultUserMessage = "Client with identifier " + client.getId()
+                        + " cannot be disassociated it has group savings.";
+                throw new GroupAccountExistsException("disassociate", "client.has.group.saving", defaultUserMessage, client.getId(),
+                        groupId);
+            }
+        }
+    }
+
+    public void validateOfficeOpeningDateisAfterGroupOrCenterOpeningDate(final Office groupOffice, final GroupLevel groupLevel,
+            final LocalDate activationDate) {
+        if (activationDate != null && groupOffice.getOpeningLocalDate().isAfter(activationDate)) {
+            final String levelName = groupLevel.getLevelName();
+            final String errorMessage = levelName
+                    + " activation date should be greater than or equal to the parent Office's creation date " + activationDate.toString();
+            throw new InvalidGroupStateTransitionException(levelName.toLowerCase(), "activate.date",
+                    "cannot.be.before.office.activation.date", errorMessage, activationDate, groupOffice.getOpeningLocalDate());
+        }
+    }
+
+    private void checkGroupMembersMeetingSyncWithCenterMeeting(final Long centerId, final Set<Group> groupMembers) {
+
+        /**
+         * Get parent(center) calendar
+         */
+        Calendar ceneterCalendar = null;
+        final CalendarInstance parentCalendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
+                centerId, CalendarEntityType.CENTERS.getValue(), CalendarType.COLLECTION.getValue());
+        if (parentCalendarInstance != null) {
+            ceneterCalendar = parentCalendarInstance.getCalendar();
+        }
+
+        for (final Group group : groupMembers) {
+            /**
+             * Get child(group) calendar
+             */
+            Calendar groupCalendar = null;
+            final CalendarInstance groupCalendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
+                    group.getId(), CalendarEntityType.GROUPS.getValue(), CalendarType.COLLECTION.getValue());
+            if (groupCalendarInstance != null) {
+                groupCalendar = groupCalendarInstance.getCalendar();
+            }
+
+            /**
+             * Group shouldn't have a meeting when no meeting attached for
+             * center
+             */
+            if (ceneterCalendar == null && groupCalendar != null) {
+                throw new GeneralPlatformDomainRuleException(
+                        "error.msg.center.associating.group.not.allowed.with.meeting.attached.to.group", "Group with id " + group.getId()
+                                + " is already associated with meeting", group.getId());
+            }
+            /**
+             * Group meeting recurrence should match with center meeting
+             * recurrence
+             */
+            else if (ceneterCalendar != null && groupCalendar != null) {
+
+                if (!ceneterCalendar.getRecurrence().equalsIgnoreCase(groupCalendar.getRecurrence())) { throw new GeneralPlatformDomainRuleException(
+                        "error.msg.center.associating.group.not.allowed.with.different.meeting", "Group with id " + group.getId()
+                                + " meeting recurrence doesnot matched with center meeting recurrence", group.getId()); }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/InterestIncentiveApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/InterestIncentiveApiConstants.java
new file mode 100755
index 0000000..c8894ac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/InterestIncentiveApiConstants.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.interestratechart;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public interface InterestIncentiveApiConstants {
+
+    public static final String idParamName = "id";
+    public static final String entityTypeParamName = "entityType";
+    public static final String attributeNameParamName = "attributeName";
+    public static final String conditionTypeParamName = "conditionType";
+    public static final String attributeValueParamName = "attributeValue";
+    public static final String incentiveTypeparamName = "incentiveType";
+    public static final String amountParamName = "amount";
+    public static final String deleteParamName = "delete";
+    
+    public static final String INCENTIVE_RESOURCE_NAME = "interest.rate.incentives";
+
+    public static final Set<String> INTERESTRATE_INCENTIVE_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
+            entityTypeParamName, attributeNameParamName, conditionTypeParamName, attributeValueParamName, incentiveTypeparamName,
+            amountParamName));
+
+    public static final Set<String> INTERESTRATE_INCENTIVE_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
+            entityTypeParamName, attributeNameParamName, conditionTypeParamName, attributeValueParamName, incentiveTypeparamName,
+            amountParamName));
+
+    public static final Set<String> INTERESTRATE_INCENTIVE_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
+            entityTypeParamName, attributeNameParamName, conditionTypeParamName, attributeValueParamName, incentiveTypeparamName,
+            amountParamName));
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/InterestRateChartApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/InterestRateChartApiConstants.java
new file mode 100644
index 0000000..7bce4ee
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/InterestRateChartApiConstants.java
@@ -0,0 +1,75 @@
+/**
+ * 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.interestratechart;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class InterestRateChartApiConstants {
+
+    public static final String INTERESTRATE_CHART_RESOURCE_NAME = "interestchart";
+
+    // actions
+    public static String summitalAction = ".summital";
+
+    // command
+    public static String COMMAND_UNDO_TRANSACTION = "undo";
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // interest rate chart parameters
+    public static final String idParamName = "id";
+    public static final String nameParamName = "name";
+    public static final String descriptionParamName = "description";
+    public static final String fromDateParamName = "fromDate";
+    public static final String endDateParamName = "endDate";
+    public static final String productIdParamName = "productId";
+    public static final String productNameParamName = "productName";
+
+    // interest rate chart Slabs parameters
+//    public static final String periodTypeParamName = "periodType";
+//    public static final String fromPeriodParamName = "fromPeriod";
+//    public static final String toPeriodParamName = "toPeriod";
+//    public static final String amountRangeFromParamName = "amountRangeFrom";
+//    public static final String amountRangeToParamName = "amountRangeTo";
+//    public static final String annualInterestRateParamName = "annualInterestRate";
+//    public static final String interestRateForFemaleParamName = "interestRateForFemale";
+//    public static final String interestRateForChildrenParamName = "interestRateForChildren";
+//    public static final String interestRateForSeniorCitizenParamName = "interestRateForSeniorCitizen";
+
+    // associations
+    public static final String chartSlabs = "chartSlabs";
+
+    // to delete chart Slabs from chart
+    public static final String deleteParamName = "delete";
+
+    public static final Set<String> INTERESTRATE_CHART_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, nameParamName, descriptionParamName, fromDateParamName, endDateParamName, productIdParamName,
+            chartSlabs));
+
+    public static final Set<String> INTERESTRATE_CHART_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, idParamName, nameParamName, descriptionParamName, fromDateParamName, endDateParamName, chartSlabs,
+            deleteParamName));
+
+    public static final Set<String> INTERESTRATE_CHART_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, idParamName, nameParamName, descriptionParamName, fromDateParamName, endDateParamName, chartSlabs));
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/InterestRateChartSlabApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/InterestRateChartSlabApiConstants.java
new file mode 100644
index 0000000..b25c185
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/InterestRateChartSlabApiConstants.java
@@ -0,0 +1,62 @@
+/**
+ * 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.interestratechart;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class InterestRateChartSlabApiConstants {
+
+    public static final String INTERESTRATE_CHART_SLAB_RESOURCE_NAME = "chartslab";
+
+    // actions
+
+    // command
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // interest rate chart Slabs parameters
+    public static final String idParamName = "id";
+    public static final String descriptionParamName = "description";
+    public static final String periodTypeParamName = "periodType";
+    public static final String fromPeriodParamName = "fromPeriod";
+    public static final String toPeriodParamName = "toPeriod";
+    public static final String amountRangeFromParamName = "amountRangeFrom";
+    public static final String amountRangeToParamName = "amountRangeTo";
+    public static final String annualInterestRateParamName = "annualInterestRate";
+    public static final String currencyCodeParamName = "currencyCode";
+    public static final String incentivesParamName = "incentives";
+
+    // associations
+
+    public static final Set<String> INTERESTRATE_CHART_SLAB_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+            localeParamName, idParamName, descriptionParamName, periodTypeParamName, fromPeriodParamName, toPeriodParamName,
+            amountRangeFromParamName, amountRangeToParamName, annualInterestRateParamName, currencyCodeParamName, incentivesParamName));
+
+    public static final Set<String> INTERESTRATE_CHART_SLAB_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+            localeParamName, idParamName, descriptionParamName, periodTypeParamName, fromPeriodParamName, toPeriodParamName,
+            amountRangeFromParamName, amountRangeToParamName, annualInterestRateParamName, currencyCodeParamName, incentivesParamName));
+
+    public static final Set<String> INTERESTRATE_CHART_SLAB_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            idParamName, descriptionParamName, periodTypeParamName, fromPeriodParamName, toPeriodParamName, amountRangeFromParamName,
+            amountRangeToParamName, annualInterestRateParamName, currencyCodeParamName, incentivesParamName));
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartSlabsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartSlabsApiResource.java
new file mode 100644
index 0000000..e5b1b1c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartSlabsApiResource.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.portfolio.interestratechart.api;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.INTERESTRATE_CHART_SLAB_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.INTERESTRATE_CHART_SLAB_RESPONSE_DATA_PARAMETERS;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabData;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartSlabReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/interestratecharts/{chartId}/chartslabs")
+@Component
+@Scope("singleton")
+public class InterestRateChartSlabsApiResource {
+
+    private final InterestRateChartSlabReadPlatformService interestRateChartSlabReadPlatformService;
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<InterestRateChartSlabData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public InterestRateChartSlabsApiResource(final InterestRateChartSlabReadPlatformService interestRateChartSlabReadPlatformService,
+            final PlatformSecurityContext context, final DefaultToApiJsonSerializer<InterestRateChartSlabData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper) {
+
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.interestRateChartSlabReadPlatformService = interestRateChartSlabReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@Context final UriInfo uriInfo) {
+        InterestRateChartSlabData chartSlab = this.interestRateChartSlabReadPlatformService.retrieveTemplate();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, chartSlab, INTERESTRATE_CHART_SLAB_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@PathParam("chartId") final Long chartId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(INTERESTRATE_CHART_SLAB_RESOURCE_NAME);
+        Collection<InterestRateChartSlabData> chartSlabs = this.interestRateChartSlabReadPlatformService.retrieveAll(chartId);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return this.toApiJsonSerializer.serialize(settings, chartSlabs, INTERESTRATE_CHART_SLAB_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{chartSlabId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("chartId") final Long chartId, @PathParam("chartSlabId") final Long chartSlabId,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(INTERESTRATE_CHART_SLAB_RESOURCE_NAME);
+
+        InterestRateChartSlabData chartSlab = this.interestRateChartSlabReadPlatformService.retrieveOne(chartId, chartSlabId);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            chartSlab = this.interestRateChartSlabReadPlatformService.retrieveWithTemplate(chartSlab);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, chartSlab, INTERESTRATE_CHART_SLAB_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(@PathParam("chartId") final Long chartId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createInterestRateChartSlab(chartId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{chartSlabId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("chartId") final Long chartId, @PathParam("chartSlabId") final Long chartSlabId,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateInterestRateChartSlab(chartId, chartSlabId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{chartSlabId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("chartId") final Long chartId, @PathParam("chartSlabId") final Long chartSlabId) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteInterestRateChartSlab(chartId, chartSlabId).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartsApiResource.java
new file mode 100644
index 0000000..e0279c6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartsApiResource.java
@@ -0,0 +1,165 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.interestratechart.api;
+
+import java.util.Collection;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+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.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/interestratecharts")
+@Component
+@Scope("singleton")
+public class InterestRateChartsApiResource {
+
+    private final InterestRateChartReadPlatformService chartReadPlatformService;
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<InterestRateChartData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public InterestRateChartsApiResource(final InterestRateChartReadPlatformService chartReadPlatformService,
+            final PlatformSecurityContext context, final DefaultToApiJsonSerializer<InterestRateChartData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper) {
+
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.chartReadPlatformService = chartReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@Context final UriInfo uriInfo) {
+        this.context.authenticatedUser().validateHasReadPermission(InterestRateChartApiConstants.INTERESTRATE_CHART_RESOURCE_NAME);
+
+        InterestRateChartData chartData = this.chartReadPlatformService.template();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, chartData,
+                InterestRateChartApiConstants.INTERESTRATE_CHART_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("productId") final Long productId) {
+
+        this.context.authenticatedUser().validateHasReadPermission(InterestRateChartApiConstants.INTERESTRATE_CHART_RESOURCE_NAME);
+
+        Collection<InterestRateChartData> chartDatas = this.chartReadPlatformService.retrieveAllWithSlabs(productId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, chartDatas,
+                InterestRateChartApiConstants.INTERESTRATE_CHART_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{chartId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("chartId") final Long chartId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(InterestRateChartApiConstants.INTERESTRATE_CHART_RESOURCE_NAME);
+        
+        InterestRateChartData chartData = null;
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        if (!associationParameters.isEmpty() && associationParameters.contains(InterestRateChartApiConstants.chartSlabs)) {
+            chartData = this.chartReadPlatformService.retrieveOneWithSlabs(chartId);
+        }else {
+            chartData = this.chartReadPlatformService.retrieveOne(chartId);
+        }
+        
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            chartData = this.chartReadPlatformService.retrieveWithTemplate(chartData);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, chartData,
+                InterestRateChartApiConstants.INTERESTRATE_CHART_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createInterestRateChart().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{chartId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("chartId") final Long chartId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateInterestRateChart(chartId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{chartId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("chartId") final Long chartId) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteInterestRateChart(chartId).build();
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestIncentiveData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestIncentiveData.java
new file mode 100755
index 0000000..607f867
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestIncentiveData.java
@@ -0,0 +1,88 @@
+/**
+ * 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.interestratechart.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class InterestIncentiveData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    private final EnumOptionData entityType;
+    private final EnumOptionData attributeName;
+    private final EnumOptionData conditionType;
+    private final String attributeValue;
+    private final String attributeValueDesc;
+    private final EnumOptionData incentiveType;
+    private final BigDecimal amount;
+
+    public static InterestIncentiveData instance(final Long id, final EnumOptionData entityType, final EnumOptionData attributeName,
+            final EnumOptionData conditionType, final String attributeValue, final String attributeValueDesc,
+            final EnumOptionData incentiveType, final BigDecimal amount) {
+
+        return new InterestIncentiveData(id, entityType, attributeName, conditionType, attributeValue, attributeValueDesc, incentiveType,
+                amount);
+    }
+
+    private InterestIncentiveData(final Long id, final EnumOptionData entityType, final EnumOptionData attributeName,
+            final EnumOptionData conditionType, final String attributeValue, final String attributeValueDesc,
+            final EnumOptionData incentiveType, final BigDecimal amount) {
+
+        this.id = id;
+        this.entityType = entityType;
+        this.attributeName = attributeName;
+        this.conditionType = conditionType;
+        this.attributeValue = attributeValue;
+        this.attributeValueDesc = attributeValueDesc;
+        this.incentiveType = incentiveType;
+        this.amount = amount;
+
+    }
+
+    public EnumOptionData entityType() {
+        return this.entityType;
+    }
+
+    public EnumOptionData attributeName() {
+        return this.attributeName;
+    }
+
+    public EnumOptionData conditionType() {
+        return this.conditionType;
+    }
+
+    public String attributeValue() {
+        return this.attributeValue;
+    }
+
+    public EnumOptionData incentiveType() {
+        return this.incentiveType;
+    }
+
+    public BigDecimal amount() {
+        return this.amount;
+    }
+
+    public String attributeValueDesc() {
+        return this.attributeValueDesc;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestIncentiveDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestIncentiveDataValidator.java
new file mode 100755
index 0000000..64f6b79
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestIncentiveDataValidator.java
@@ -0,0 +1,173 @@
+/**
+ * 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.interestratechart.data;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.INCENTIVE_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.INTERESTRATE_INCENTIVE_CREATE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.INTERESTRATE_INCENTIVE_UPDATE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.attributeNameParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.attributeValueParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.conditionTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.entityTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.incentiveTypeparamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.descriptionParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.common.domain.ConditionType;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveEntityType;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class InterestIncentiveDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public InterestIncentiveDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateCreate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, INTERESTRATE_INCENTIVE_CREATE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(INCENTIVE_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final JsonObject objectElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(objectElement);
+        validateIncentiveCreate(element, baseDataValidator, locale);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateIncentiveCreate(final JsonElement element, final DataValidatorBuilder baseDataValidator, final Locale locale) {
+
+        Integer entityType = this.fromApiJsonHelper.extractIntegerNamed(entityTypeParamName, element, locale);
+        baseDataValidator.reset().parameter(entityTypeParamName).value(entityType).notNull()
+                .isOneOfTheseValues(InterestIncentiveEntityType.integerValues());
+
+        Integer attributeName = this.fromApiJsonHelper.extractIntegerNamed(attributeNameParamName, element, locale);
+        baseDataValidator.reset().parameter(attributeNameParamName).value(attributeName).notNull()
+                .isOneOfTheseValues(InterestIncentiveAttributeName.integerValues());
+
+        Integer conditionType = this.fromApiJsonHelper.extractIntegerNamed(conditionTypeParamName, element, locale);
+        baseDataValidator.reset().parameter(conditionTypeParamName).value(conditionType).notNull()
+                .isOneOfTheseValues(ConditionType.integerValues());
+
+        final String attributeValue = this.fromApiJsonHelper.extractStringNamed(attributeValueParamName, element);
+        baseDataValidator.reset().parameter(attributeValueParamName).value(attributeValue).notNull();
+
+        Integer incentiveType = this.fromApiJsonHelper.extractIntegerNamed(incentiveTypeparamName, element, locale);
+        baseDataValidator.reset().parameter(incentiveTypeparamName).value(incentiveType).notNull()
+                .isOneOfTheseValues(InterestIncentiveType.integerValues());
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, element, locale);
+        baseDataValidator.reset().parameter(amountParamName).value(amount).notNull();
+
+    }
+
+    public void validateUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, INTERESTRATE_INCENTIVE_UPDATE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(INCENTIVE_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final JsonObject objectElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(objectElement);
+        validateIncentiveUpdate(element, baseDataValidator, locale);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateIncentiveUpdate(final JsonElement element, final DataValidatorBuilder baseDataValidator, final Locale locale) {
+
+        if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+            baseDataValidator.reset().parameter(descriptionParamName).value(description).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(entityTypeParamName, element)) {
+            Integer entityType = this.fromApiJsonHelper.extractIntegerNamed(entityTypeParamName, element, locale);
+            baseDataValidator.reset().parameter(entityTypeParamName).value(entityType).notNull()
+                    .isOneOfTheseValues(InterestIncentiveEntityType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(attributeNameParamName, element)) {
+            Integer attributeName = this.fromApiJsonHelper.extractIntegerNamed(attributeNameParamName, element, locale);
+            baseDataValidator.reset().parameter(attributeNameParamName).value(attributeName).notNull()
+                    .isOneOfTheseValues(InterestIncentiveAttributeName.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(conditionTypeParamName, element)) {
+            Integer conditionType = this.fromApiJsonHelper.extractIntegerNamed(conditionTypeParamName, element, locale);
+            baseDataValidator.reset().parameter(conditionTypeParamName).value(conditionType).notNull()
+                    .isOneOfTheseValues(ConditionType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(attributeValueParamName, element)) {
+            final String attributeValue = this.fromApiJsonHelper.extractStringNamed(attributeValueParamName, element);
+            baseDataValidator.reset().parameter(attributeValueParamName).value(attributeValue).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(incentiveTypeparamName, element)) {
+            Integer incentiveType = this.fromApiJsonHelper.extractIntegerNamed(incentiveTypeparamName, element, locale);
+            baseDataValidator.reset().parameter(incentiveTypeparamName).value(incentiveType).notNull()
+                    .isOneOfTheseValues(InterestIncentiveType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(amountParamName, element)) {
+            final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, element, locale);
+            baseDataValidator.reset().parameter(amountParamName).value(amount).notNull();
+        }
+
+    }
+
+    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/portfolio/interestratechart/data/InterestRateChartData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartData.java
new file mode 100644
index 0000000..f7aef96
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartData.java
@@ -0,0 +1,198 @@
+/**
+ * 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.interestratechart.data;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a InterestRateChart.
+ */
+public class InterestRateChartData {
+
+    private final Long id;
+    private final String name;
+    private final String description;
+    private final LocalDate fromDate;
+    private final LocalDate endDate;
+    private final Long productId;
+    private final String productName;
+
+    // associations
+    private Set<InterestRateChartSlabData> chartSlabs;
+
+    // template
+    private final Collection<EnumOptionData> periodTypes;
+    private final Collection<EnumOptionData> entityTypeOptions;
+    private final Collection<EnumOptionData> attributeNameOptions;
+    private final Collection<EnumOptionData> conditionTypeOptions;
+    private final Collection<EnumOptionData> incentiveTypeOptions;
+    private final Collection<CodeValueData> genderOptions;
+    private final Collection<CodeValueData> clientTypeOptions;
+    private final Collection<CodeValueData> clientClassificationOptions;
+
+    public static InterestRateChartData instance(Long id, String name, String description, LocalDate fromDate, LocalDate endDate,
+            Long savingsProductId, String savingsProductName) {
+        Collection<EnumOptionData> periodTypes = null;
+        Set<InterestRateChartSlabData> chartSlabs = null;
+        final Collection<EnumOptionData> entityTypeOptions = null;
+        final Collection<EnumOptionData> attributeNameOptions = null;
+        final Collection<EnumOptionData> conditionTypeOptions = null;
+        final Collection<EnumOptionData> incentiveTypeOptions = null;
+        final Collection<CodeValueData> genderOptions = null;
+        final Collection<CodeValueData> clientTypeOptions = null;
+        final Collection<CodeValueData> clientClassificationOptions = null;
+        return new InterestRateChartData(id, name, description, fromDate, endDate, savingsProductId, savingsProductName, chartSlabs,
+                periodTypes, entityTypeOptions, attributeNameOptions, conditionTypeOptions, incentiveTypeOptions, genderOptions,
+                clientTypeOptions, clientClassificationOptions);
+    }
+
+    public static InterestRateChartData withSlabs(InterestRateChartData interestRateChartData, Set<InterestRateChartSlabData> chartSlabs) {
+        return new InterestRateChartData(interestRateChartData.id, interestRateChartData.name, interestRateChartData.description,
+                interestRateChartData.fromDate, interestRateChartData.endDate, interestRateChartData.productId,
+                interestRateChartData.productName, chartSlabs, interestRateChartData.periodTypes, interestRateChartData.entityTypeOptions,
+                interestRateChartData.attributeNameOptions, interestRateChartData.conditionTypeOptions,
+                interestRateChartData.incentiveTypeOptions, interestRateChartData.genderOptions, interestRateChartData.clientTypeOptions,
+                interestRateChartData.clientClassificationOptions);
+    }
+
+    public static InterestRateChartData withTemplate(InterestRateChartData interestRateChartData, Collection<EnumOptionData> periodTypes,
+            final Collection<EnumOptionData> entityTypeOptions, final Collection<EnumOptionData> attributeNameOptions,
+            final Collection<EnumOptionData> conditionTypeOptions, final Collection<EnumOptionData> incentiveTypeOptions,
+            final Collection<CodeValueData> genderOptions, final Collection<CodeValueData> clientTypeOptions,
+            final Collection<CodeValueData> clientClassificationOptions) {
+        return new InterestRateChartData(interestRateChartData.id, interestRateChartData.name, interestRateChartData.description,
+                interestRateChartData.fromDate, interestRateChartData.endDate, interestRateChartData.productId,
+                interestRateChartData.productName, interestRateChartData.chartSlabs, periodTypes, entityTypeOptions, attributeNameOptions,
+                conditionTypeOptions, incentiveTypeOptions, genderOptions, clientTypeOptions, clientClassificationOptions);
+    }
+
+    public static InterestRateChartData template(Collection<EnumOptionData> periodTypes,
+            final Collection<EnumOptionData> entityTypeOptions, final Collection<EnumOptionData> attributeNameOptions,
+            final Collection<EnumOptionData> conditionTypeOptions, final Collection<EnumOptionData> incentiveTypeOptions,
+            final Collection<CodeValueData> genderOptions, final Collection<CodeValueData> clientTypeOptions,
+            final Collection<CodeValueData> clientClassificationOptions) {
+        final Long id = null;
+        final String name = null;
+        final String description = null;
+        final LocalDate fromDate = null;
+        final LocalDate endDate = null;
+        final Long savingsProductId = null;
+        final String savingsProductName = null;
+        final Set<InterestRateChartSlabData> chartSlabs = null;
+
+        return new InterestRateChartData(id, name, description, fromDate, endDate, savingsProductId, savingsProductName, chartSlabs,
+                periodTypes, entityTypeOptions, attributeNameOptions, conditionTypeOptions, incentiveTypeOptions, genderOptions,
+                clientTypeOptions, clientClassificationOptions);
+    }
+
+    private InterestRateChartData(Long id, String name, String description, LocalDate fromDate, LocalDate endDate, Long savingsProductId,
+            String savingsProductName, Set<InterestRateChartSlabData> chartSlabs, Collection<EnumOptionData> periodTypes,
+            final Collection<EnumOptionData> entityTypeOptions, final Collection<EnumOptionData> attributeNameOptions,
+            final Collection<EnumOptionData> conditionTypeOptions, final Collection<EnumOptionData> incentiveTypeOptions,
+            final Collection<CodeValueData> genderOptions, final Collection<CodeValueData> clientTypeOptions,
+            final Collection<CodeValueData> clientClassificationOptions) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.fromDate = fromDate;
+        this.endDate = endDate;
+        this.chartSlabs = chartSlabs;
+        this.productId = savingsProductId;
+        this.productName = savingsProductName;
+        this.periodTypes = periodTypes;
+        this.attributeNameOptions = attributeNameOptions;
+        this.entityTypeOptions = entityTypeOptions;
+        this.conditionTypeOptions = conditionTypeOptions;
+        this.incentiveTypeOptions = incentiveTypeOptions;
+        this.genderOptions = genderOptions;
+        this.clientTypeOptions = clientTypeOptions;
+        this.clientClassificationOptions = clientClassificationOptions;
+    }
+
+    public void addChartSlab(final InterestRateChartSlabData chartSlab) {
+        if (this.chartSlabs == null) {
+            this.chartSlabs = new HashSet<>();
+        }
+
+        this.chartSlabs.add(chartSlab);
+    }
+
+    public boolean isFromDateAfter(final LocalDate compareDate) {
+        return (compareDate == null) ? false : this.fromDate.isAfter(compareDate);
+    }
+
+    public LocalDate endDate() {
+        return this.endDate;
+    }
+
+    public LocalDate fromDate() {
+        return this.fromDate;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public String description() {
+        return this.description;
+    }
+
+    public Set<InterestRateChartSlabData> chartSlabs() {
+        return this.chartSlabs;
+    }
+
+    public Collection<EnumOptionData> periodTypes() {
+        return this.periodTypes;
+    }
+
+    public Collection<EnumOptionData> entityTypeOptions() {
+        return this.entityTypeOptions;
+    }
+
+    public Collection<EnumOptionData> attributeNameOptions() {
+        return this.attributeNameOptions;
+    }
+
+    public Collection<EnumOptionData> conditionTypeOptions() {
+        return this.conditionTypeOptions;
+    }
+
+    public Collection<EnumOptionData> incentiveTypeOptions() {
+        return this.incentiveTypeOptions;
+    }
+
+    public Collection<CodeValueData> genderOptions() {
+        return this.genderOptions;
+    }
+
+    public Collection<CodeValueData> clientTypeOptions() {
+        return this.clientTypeOptions;
+    }
+
+    public Collection<CodeValueData> clientClassificationOptions() {
+        return this.clientClassificationOptions;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartDataValidator.java
new file mode 100644
index 0000000..04c4d22
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartDataValidator.java
@@ -0,0 +1,189 @@
+/**
+ * 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.interestratechart.data;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.INTERESTRATE_CHART_CREATE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.INTERESTRATE_CHART_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.INTERESTRATE_CHART_UPDATE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.chartSlabs;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.endDateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.fromDateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.idParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.nameParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.productIdParamName;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class InterestRateChartDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final InterestRateChartSlabDataValidator chartSlabDataValidator;
+
+    @Autowired
+    public InterestRateChartDataValidator(final FromJsonHelper fromApiJsonHelper,
+            final InterestRateChartSlabDataValidator chartSlabDataValidator) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.chartSlabDataValidator = chartSlabDataValidator;
+    }
+
+    public void validateForCreate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(INTERESTRATE_CHART_RESOURCE_NAME);
+
+        validateForCreate(json, baseDataValidator);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForCreate(final String json, final DataValidatorBuilder baseDataValidator) {
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, INTERESTRATE_CHART_CREATE_REQUEST_DATA_PARAMETERS);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(nameParamName, element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
+            baseDataValidator.reset().parameter(nameParamName).value(name).notBlank();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+            baseDataValidator.reset().parameter(descriptionParamName).value(description).notNull();
+        }
+
+        final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(fromDateParamName, element);
+        baseDataValidator.reset().parameter(fromDateParamName).value(fromDate).notNull();
+
+        LocalDate toDate = null;
+        if (this.fromApiJsonHelper.parameterExists(endDateParamName, element)) {
+            toDate = this.fromApiJsonHelper.extractLocalDateNamed(endDateParamName, element);
+            baseDataValidator.reset().parameter(endDateParamName).value(toDate).notNull();
+        }
+
+        if (fromDate != null && toDate != null) {
+            if (fromDate.isAfter(toDate)) {
+                baseDataValidator.parameter(fromDateParamName).value(fromDate).failWithCode("from.date.is.after.to.date");
+            }
+        }
+
+        // validate chart Slabs
+        validateChartSlabs(element, baseDataValidator);
+    }
+
+    public void validateUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(INTERESTRATE_CHART_RESOURCE_NAME);
+        this.validateForUpdate(json, baseDataValidator);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdate(final String json, final DataValidatorBuilder baseDataValidator) {
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, INTERESTRATE_CHART_UPDATE_REQUEST_DATA_PARAMETERS);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(productIdParamName, element)) {
+            final Long savingsProductId = this.fromApiJsonHelper.extractLongNamed(productIdParamName, element);
+            baseDataValidator.reset().parameter(productIdParamName).value(savingsProductId).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(nameParamName, element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
+            baseDataValidator.reset().parameter(nameParamName).value(name).notBlank();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+            baseDataValidator.reset().parameter(descriptionParamName).value(description).notNull();
+        }
+
+        LocalDate fromDate = null;
+        if (this.fromApiJsonHelper.parameterExists(fromDateParamName, element)) {
+            fromDate = this.fromApiJsonHelper.extractLocalDateNamed(fromDateParamName, element);
+            baseDataValidator.reset().parameter(fromDateParamName).value(fromDate).notNull();
+        }
+
+        LocalDate toDate = null;
+        if (this.fromApiJsonHelper.parameterExists(endDateParamName, element)) {
+            toDate = this.fromApiJsonHelper.extractLocalDateNamed(endDateParamName, element);
+            baseDataValidator.reset().parameter(endDateParamName).value(toDate).notNull();
+        }
+
+        if (fromDate != null && toDate != null) {
+            if (fromDate.isAfter(toDate)) {
+                baseDataValidator.parameter(fromDateParamName).value(fromDate).failWithCode("from.date.is.after.to.date");
+            }
+        }
+
+        validateChartSlabs(element, baseDataValidator);
+
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private void validateChartSlabs(JsonElement element, DataValidatorBuilder baseDataValidator) {
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            if (topLevelJsonElement.has(chartSlabs) && topLevelJsonElement.get(chartSlabs).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(chartSlabs).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject interstRateChartElement = array.get(i).getAsJsonObject();
+                    if (this.fromApiJsonHelper.parameterExists(idParamName, interstRateChartElement)) {
+                        final Long id = this.fromApiJsonHelper.extractLongNamed(idParamName, interstRateChartElement);
+                        baseDataValidator.reset().parameter(idParamName).value(id).notNull().integerGreaterThanZero();
+                        this.chartSlabDataValidator.validateChartSlabsUpdate(interstRateChartElement, baseDataValidator, locale);
+                    } else {
+                        this.chartSlabDataValidator.validateChartSlabsCreate(interstRateChartElement, baseDataValidator, locale);
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartRepository.java
new file mode 100644
index 0000000..85db818
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.interestratechart.data;
+
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface InterestRateChartRepository extends JpaRepository<InterestRateChart, Long>, JpaSpecificationExecutor<InterestRateChart> {
+    
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartRepositoryWrapper.java
new file mode 100644
index 0000000..eedace5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartRepositoryWrapper.java
@@ -0,0 +1,66 @@
+/**
+ * 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.interestratechart.data;
+
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.interestratechart.exception.InterestRateChartNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link InterestRateChartRepository} that is responsible for
+ * checking if {@link InterestRateChart} is returned when using
+ * <code>findOne</code> repository method and throwing an appropriate not found
+ * exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link InterestRateChartRepository} is required.
+ * </p>
+ */
+@Service
+public class InterestRateChartRepositoryWrapper {
+
+    private final InterestRateChartRepository repository;
+
+    @Autowired
+    public InterestRateChartRepositoryWrapper(final InterestRateChartRepository repository) {
+        this.repository = repository;
+    }
+
+    public InterestRateChart findOneWithNotFoundDetection(final Long intrestRateChartId) {
+        final InterestRateChart interestRateChart = this.repository.findOne(intrestRateChartId);
+        if (interestRateChart == null) { throw new InterestRateChartNotFoundException(intrestRateChartId); }
+        return interestRateChart;
+    }
+
+    public void save(final InterestRateChart interestRateChart) {
+        this.repository.save(interestRateChart);
+    }
+
+    public void delete(final InterestRateChart interestRateChart) {
+        this.repository.delete(interestRateChart);
+    }
+
+    public void saveAndFlush(final InterestRateChart interestRateChart) {
+        this.repository.saveAndFlush(interestRateChart);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabData.java
new file mode 100644
index 0000000..7694f8f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabData.java
@@ -0,0 +1,209 @@
+/**
+ * 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.interestratechart.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+/**
+ * Immutable data object representing a InterestRateChartSlab.
+ */
+public class InterestRateChartSlabData {
+
+    private final Long id;
+    private final String description;
+    private final EnumOptionData periodType;
+    private final Integer fromPeriod;
+    private final Integer toPeriod;
+    private final BigDecimal amountRangeFrom;
+    private final BigDecimal amountRangeTo;
+    private final BigDecimal annualInterestRate;
+    private final CurrencyData currency;
+
+    // associations
+    private Set<InterestIncentiveData> incentives;
+
+    // template
+    private final Collection<EnumOptionData> periodTypes;
+    private final Collection<EnumOptionData> entityTypeOptions;
+    private final Collection<EnumOptionData> attributeNameOptions;
+    private final Collection<EnumOptionData> conditionTypeOptions;
+    private final Collection<EnumOptionData> incentiveTypeOptions;
+    private final Collection<CodeValueData> genderOptions;
+    private final Collection<CodeValueData> clientTypeOptions;
+    private final Collection<CodeValueData> clientClassificationOptions;
+
+    public static InterestRateChartSlabData instance(final Long id, final String description, final EnumOptionData periodType,
+            final Integer fromPeriod, final Integer toPeriod, final BigDecimal amountRangeFrom, final BigDecimal amountRangeTo,
+            final BigDecimal annualInterestRate, final CurrencyData currency) {
+        final Collection<EnumOptionData> periodTypes = null;
+        final Set<InterestIncentiveData> incentivesData = null;
+        final Collection<EnumOptionData> entityTypeOptions = null;
+        final Collection<EnumOptionData> attributeNameOptions = null;
+        final Collection<EnumOptionData> conditionTypeOptions = null;
+        final Collection<EnumOptionData> incentiveTypeOptions = null;
+        final Collection<CodeValueData> genderOptions = null;
+        final Collection<CodeValueData> clientTypeOptions = null;
+        final Collection<CodeValueData> clientClassificationOptions = null;
+        return new InterestRateChartSlabData(id, description, periodType, fromPeriod, toPeriod, amountRangeFrom, amountRangeTo,
+                annualInterestRate, currency, incentivesData, periodTypes, entityTypeOptions, attributeNameOptions, conditionTypeOptions,
+                incentiveTypeOptions, genderOptions, clientTypeOptions, clientClassificationOptions);
+    }
+
+    public static InterestRateChartSlabData withTemplate(final InterestRateChartSlabData chartSlab,
+            final Collection<EnumOptionData> periodTypes, final Collection<EnumOptionData> entityTypeOptions,
+            final Collection<EnumOptionData> attributeNameOptions, final Collection<EnumOptionData> conditionTypeOptions,
+            final Collection<EnumOptionData> incentiveTypeOptions, final Collection<CodeValueData> genderOptions,
+            final Collection<CodeValueData> clientTypeOptions, final Collection<CodeValueData> clientClassificationOptions) {
+        return new InterestRateChartSlabData(chartSlab.id, chartSlab.description, chartSlab.periodType, chartSlab.fromPeriod,
+                chartSlab.toPeriod, chartSlab.amountRangeFrom, chartSlab.amountRangeTo, chartSlab.annualInterestRate, chartSlab.currency,
+                chartSlab.incentives, periodTypes, entityTypeOptions, attributeNameOptions, conditionTypeOptions, incentiveTypeOptions,
+                genderOptions, clientTypeOptions, clientClassificationOptions);
+    }
+
+    public static InterestRateChartSlabData template(final Collection<EnumOptionData> periodTypes,
+            final Collection<EnumOptionData> entityTypeOptions, final Collection<EnumOptionData> attributeNameOptions,
+            final Collection<EnumOptionData> conditionTypeOptions, final Collection<EnumOptionData> incentiveTypeOptions,
+            final Collection<CodeValueData> genderOptions, final Collection<CodeValueData> clientTypeOptions,
+            final Collection<CodeValueData> clientClassificationOptions) {
+        final Long id = null;
+        final String description = null;
+        final EnumOptionData periodType = null;
+        final Integer fromPeriod = null;
+        final Integer toPeriod = null;
+        final BigDecimal amountRangeFrom = null;
+        final BigDecimal amountRangeTo = null;
+        final BigDecimal annualInterestRate = null;
+        final CurrencyData currency = null;
+        final Set<InterestIncentiveData> incentivesData = null;
+        return new InterestRateChartSlabData(id, description, periodType, fromPeriod, toPeriod, amountRangeFrom, amountRangeTo,
+                annualInterestRate, currency, incentivesData, periodTypes, entityTypeOptions, attributeNameOptions, conditionTypeOptions,
+                incentiveTypeOptions, genderOptions, clientTypeOptions, clientClassificationOptions);
+    }
+
+    private InterestRateChartSlabData(final Long id, final String description, final EnumOptionData periodType, final Integer fromPeriod,
+            final Integer toPeriod, final BigDecimal amountRangeFrom, final BigDecimal amountRangeTo, final BigDecimal annualInterestRate,
+            final CurrencyData currency, final Set<InterestIncentiveData> incentivesData, final Collection<EnumOptionData> periodTypes,
+            final Collection<EnumOptionData> entityTypeOptions, final Collection<EnumOptionData> attributeNameOptions,
+            final Collection<EnumOptionData> conditionTypeOptions, final Collection<EnumOptionData> incentiveTypeOptions,
+            final Collection<CodeValueData> genderOptions, final Collection<CodeValueData> clientTypeOptions,
+            final Collection<CodeValueData> clientClassificationOptions) {
+        this.id = id;
+        this.description = description;
+        this.periodType = periodType;
+        this.fromPeriod = fromPeriod;
+        this.toPeriod = toPeriod;
+        this.amountRangeFrom = amountRangeFrom;
+        this.amountRangeTo = amountRangeTo;
+        this.annualInterestRate = annualInterestRate;
+        this.currency = currency;
+        this.periodTypes = periodTypes;
+        this.incentives = incentivesData;
+        this.attributeNameOptions = attributeNameOptions;
+        this.entityTypeOptions = entityTypeOptions;
+        this.conditionTypeOptions = conditionTypeOptions;
+        this.incentiveTypeOptions = incentiveTypeOptions;
+        this.genderOptions = genderOptions;
+        this.clientTypeOptions = clientTypeOptions;
+        this.clientClassificationOptions = clientClassificationOptions;
+    }
+
+    public String description() {
+        return this.description;
+    }
+
+    public EnumOptionData periodType() {
+        return this.periodType;
+    }
+
+    public Integer fromPeriod() {
+        return this.fromPeriod;
+    }
+
+    public Integer toPeriod() {
+        return this.toPeriod;
+    }
+
+    public BigDecimal amountRangeFrom() {
+        return this.amountRangeFrom;
+    }
+
+    public BigDecimal amountRangeTo() {
+        return this.amountRangeTo;
+    }
+
+    public BigDecimal annualInterestRate() {
+        return this.annualInterestRate;
+    }
+
+    public CurrencyData currency() {
+        return this.currency;
+    }
+
+    public Collection<EnumOptionData> periodTypes() {
+        return this.periodTypes;
+    }
+
+    public void addIncentives(final InterestIncentiveData incentiveData) {
+        if (this.incentives == null) {
+            this.incentives = new HashSet<>();
+        }
+
+        this.incentives.add(incentiveData);
+    }
+
+    public Set<InterestIncentiveData> incentives() {
+        return this.incentives;
+    }
+
+    public Collection<EnumOptionData> entityTypeOptions() {
+        return this.entityTypeOptions;
+    }
+
+    public Collection<EnumOptionData> attributeNameOptions() {
+        return this.attributeNameOptions;
+    }
+
+    public Collection<EnumOptionData> conditionTypeOptions() {
+        return this.conditionTypeOptions;
+    }
+
+    public Collection<EnumOptionData> incentiveTypeOptions() {
+        return this.incentiveTypeOptions;
+    }
+
+    public Collection<CodeValueData> genderOptions() {
+        return this.genderOptions;
+    }
+
+    public Collection<CodeValueData> clientTypeOptions() {
+        return this.clientTypeOptions;
+    }
+
+    public Collection<CodeValueData> clientClassificationOptions() {
+        return this.clientClassificationOptions;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabDataValidator.java
new file mode 100644
index 0000000..45b0ba4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabDataValidator.java
@@ -0,0 +1,248 @@
+/**
+ * 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.interestratechart.data;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.INTERESTRATE_CHART_SLAB_CREATE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.INTERESTRATE_CHART_SLAB_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.INTERESTRATE_CHART_SLAB_UPDATE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.amountRangeFromParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.amountRangeToParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.annualInterestRateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.currencyCodeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.fromPeriodParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.incentivesParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.periodTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.toPeriodParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class InterestRateChartSlabDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final InterestIncentiveDataValidator interestIncentiveDataValidator;
+
+    @Autowired
+    public InterestRateChartSlabDataValidator(final FromJsonHelper fromApiJsonHelper,
+            final InterestIncentiveDataValidator interestIncentiveDataValidator) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.interestIncentiveDataValidator = interestIncentiveDataValidator;
+    }
+
+    public void validateCreate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, INTERESTRATE_CHART_SLAB_CREATE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(INTERESTRATE_CHART_SLAB_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final JsonObject objectElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(objectElement);
+
+        final String currencyCode = this.fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
+        baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank().notExceedingLengthOf(3);
+
+        validateChartSlabsCreate(element, baseDataValidator, locale);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateChartSlabsCreate(final JsonElement element, final DataValidatorBuilder baseDataValidator, final Locale locale) {
+
+        if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+            baseDataValidator.reset().parameter(descriptionParamName).value(description).notNull();
+        }
+
+        final Integer periodType = this.fromApiJsonHelper.extractIntegerNamed(periodTypeParamName, element, locale);
+        baseDataValidator.reset().parameter(periodTypeParamName).value(periodType).notNull()
+                .isOneOfTheseValues(PeriodFrequencyType.integerValues());
+
+        Integer toPeriod = null;
+
+        final Integer fromPeriod = this.fromApiJsonHelper.extractIntegerNamed(fromPeriodParamName, element, locale);
+        baseDataValidator.reset().parameter(fromPeriodParamName).value(fromPeriod).notNull().integerZeroOrGreater();
+
+        if (this.fromApiJsonHelper.parameterExists(toPeriodParamName, element)) {
+            toPeriod = this.fromApiJsonHelper.extractIntegerNamed(toPeriodParamName, element, locale);
+            baseDataValidator.reset().parameter(toPeriodParamName).value(toPeriod).notNull().integerZeroOrGreater();
+        }
+
+        if (fromPeriod != null && toPeriod != null) {
+            if (fromPeriod > toPeriod) {
+                baseDataValidator.parameter(fromPeriodParamName).value(fromPeriod).failWithCode("fromperiod.greater.than.to.period");
+            }
+        }
+        BigDecimal amountRangeFrom = null;
+        BigDecimal amountRangeTo = null;
+        if (this.fromApiJsonHelper.parameterExists(amountRangeFromParamName, element)) {
+            amountRangeFrom = this.fromApiJsonHelper.extractBigDecimalNamed(amountRangeFromParamName, element, locale);
+            baseDataValidator.reset().parameter(amountRangeFromParamName).value(amountRangeFrom).notNull().positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(amountRangeToParamName, element)) {
+            amountRangeTo = this.fromApiJsonHelper.extractBigDecimalNamed(amountRangeToParamName, element, locale);
+            baseDataValidator.reset().parameter(amountRangeToParamName).value(amountRangeTo).notNull().positiveAmount();
+        }
+
+        if (amountRangeFrom != null && amountRangeTo != null) {
+            if (amountRangeFrom.compareTo(amountRangeTo) > 1) {
+                baseDataValidator.parameter(fromPeriodParamName).value(fromPeriod).failWithCode("fromperiod.greater.than.toperiod");
+            }
+        }
+
+        final BigDecimal annualInterestRate = this.fromApiJsonHelper.extractBigDecimalNamed(annualInterestRateParamName, element, locale);
+        baseDataValidator.reset().parameter(annualInterestRateParamName).value(annualInterestRate).notNull().zeroOrPositiveAmount();
+
+        validateIncentives(element, baseDataValidator, locale);
+    }
+
+    public void validateUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, INTERESTRATE_CHART_SLAB_UPDATE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(INTERESTRATE_CHART_SLAB_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final JsonObject objectElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(objectElement);
+        validateChartSlabsUpdate(element, baseDataValidator, locale);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateChartSlabsUpdate(final JsonElement element, final DataValidatorBuilder baseDataValidator, final Locale locale) {
+
+        if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+            baseDataValidator.reset().parameter(descriptionParamName).value(description).ignoreIfNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(periodTypeParamName, element)) {
+            final Integer periodType = this.fromApiJsonHelper.extractIntegerNamed(periodTypeParamName, element, locale);
+            baseDataValidator.reset().parameter(periodTypeParamName).value(periodType).notNull()
+                    .isOneOfTheseValues(PeriodFrequencyType.integerValues());
+        }
+
+        Integer fromPeriod = null;
+        Integer toPeriod = null;
+
+        if (this.fromApiJsonHelper.parameterExists(fromPeriodParamName, element)) {
+            fromPeriod = this.fromApiJsonHelper.extractIntegerNamed(fromPeriodParamName, element, locale);
+            baseDataValidator.reset().parameter(fromPeriodParamName).value(fromPeriod).notNull().integerGreaterThanNumber(-1);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(toPeriodParamName, element)) {
+            toPeriod = this.fromApiJsonHelper.extractIntegerNamed(toPeriodParamName, element, locale);
+            baseDataValidator.reset().parameter(toPeriodParamName).value(toPeriod).notNull().integerGreaterThanNumber(-1);
+        }
+
+        if (fromPeriod != null && toPeriod != null) {
+            if (fromPeriod > toPeriod) {
+                baseDataValidator.parameter(fromPeriodParamName).value(fromPeriod).failWithCode("fromperiod.greater.than.toperiod");
+            }
+        }
+        BigDecimal amountRangeFrom = null;
+        BigDecimal amountRangeTo = null;
+        if (this.fromApiJsonHelper.parameterExists(amountRangeFromParamName, element)) {
+            amountRangeFrom = this.fromApiJsonHelper.extractBigDecimalNamed(amountRangeFromParamName, element, locale);
+            baseDataValidator.reset().parameter(amountRangeFromParamName).value(amountRangeFrom).notNull().positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(amountRangeToParamName, element)) {
+            amountRangeTo = this.fromApiJsonHelper.extractBigDecimalNamed(amountRangeToParamName, element, locale);
+            baseDataValidator.reset().parameter(amountRangeToParamName).value(amountRangeTo).notNull().positiveAmount();
+        }
+
+        if (amountRangeFrom != null && amountRangeTo != null) {
+            if (amountRangeFrom.compareTo(amountRangeTo) > 1) {
+                baseDataValidator.parameter(fromPeriodParamName).value(fromPeriod).failWithCode("fromperiod.greater.than.toperiod");
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(annualInterestRateParamName, element)) {
+            final BigDecimal annualInterestRate = this.fromApiJsonHelper.extractBigDecimalNamed(annualInterestRateParamName, element,
+                    locale);
+            baseDataValidator.reset().parameter(annualInterestRateParamName).value(annualInterestRate).notNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(currencyCodeParamName, element)) {
+            final String currencyCode = this.fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
+            baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank().notExceedingLengthOf(3);
+        }
+        validateIncentives(element, baseDataValidator, locale);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private void validateIncentives(JsonElement element, DataValidatorBuilder baseDataValidator, final Locale locale) {
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            if (topLevelJsonElement.has(incentivesParamName) && topLevelJsonElement.get(incentivesParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(incentivesParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject incentiveElement = array.get(i).getAsJsonObject();
+                    if (this.fromApiJsonHelper.parameterExists(InterestIncentiveApiConstants.idParamName, incentiveElement)) {
+                        final Long id = this.fromApiJsonHelper
+                                .extractLongNamed(InterestIncentiveApiConstants.idParamName, incentiveElement);
+                        baseDataValidator.reset().parameter(InterestIncentiveApiConstants.idParamName).value(id).notNull()
+                                .integerGreaterThanZero();
+                        this.interestIncentiveDataValidator.validateIncentiveUpdate(incentiveElement, baseDataValidator, locale);
+                    } else {
+                        this.interestIncentiveDataValidator.validateIncentiveCreate(incentiveElement, baseDataValidator, locale);
+                    }
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabRepository.java
new file mode 100644
index 0000000..38d7945
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.interestratechart.data;
+
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface InterestRateChartSlabRepository extends JpaRepository<InterestRateChartSlab, Long>, JpaSpecificationExecutor<InterestRateChartSlab> {
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabRepositoryWrapper.java
new file mode 100644
index 0000000..72594c1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartSlabRepositoryWrapper.java
@@ -0,0 +1,66 @@
+/**
+ * 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.interestratechart.data;
+
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab;
+import org.apache.fineract.portfolio.interestratechart.exception.InterestRateChartSlabNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link InterestRateChartSlabRepository} that is responsible for checking
+ * if {@link InterestRateChartSlab} is returned when using <code>findOne</code>
+ * repository method and throwing an appropriate not found exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link InterestRateChartSlabRepository} is required.
+ * </p>
+ */
+@Service
+public class InterestRateChartSlabRepositoryWrapper {
+
+    private final InterestRateChartSlabRepository repository;
+
+    @Autowired
+    public InterestRateChartSlabRepositoryWrapper(final InterestRateChartSlabRepository repository) {
+        this.repository = repository;
+    }
+
+    public InterestRateChartSlab findOneWithNotFoundDetection(final Long chartSlabId) {
+        final InterestRateChartSlab chartSlab = this.repository.findOne(chartSlabId);
+        if (chartSlab == null) { throw new InterestRateChartSlabNotFoundException(chartSlabId); }
+        return chartSlab;
+    }
+
+    public void save(final InterestRateChartSlab chartSlab) {
+        this.repository.save(chartSlab);
+    }
+
+    public void delete(final InterestRateChartSlab chartSlab) {
+        this.repository.delete(chartSlab);
+    }
+
+    public void saveAndFlush(final InterestRateChartSlab chartSlab) {
+        this.repository.saveAndFlush(chartSlab);
+    }
+    
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestIncentives.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestIncentives.java
new file mode 100755
index 0000000..094eadb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestIncentives.java
@@ -0,0 +1,82 @@
+/**
+ * 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.interestratechart.domain;
+
+import java.util.Locale;
+import java.util.Map;
+
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_interest_incentives")
+public class InterestIncentives extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "interest_rate_slab_id", nullable = false)
+    private InterestRateChartSlab interestRateChartSlab;
+
+    @Embedded
+    private InterestIncentivesFields interestIncentivesFields;
+
+    protected InterestIncentives() {
+
+    }
+
+    /*
+     * public InterestIncentives(final InterestRateChartSlab
+     * interestRateChartSlab, final InterestIncentivesFields
+     * interestIncentivesFields) { this.interestRateChartSlab =
+     * interestRateChartSlab; this.interestIncentivesFields =
+     * interestIncentivesFields; }
+     */
+
+    public InterestRateChartSlab interestRateChartSlab() {
+        return this.interestRateChartSlab;
+    }
+
+    public void updateInterestRateChartSlab(InterestRateChartSlab interestRateChartSlab) {
+        this.interestRateChartSlab = interestRateChartSlab;
+    }
+
+    public InterestIncentives(final InterestRateChartSlab interestRateChartSlab, final InterestIncentivesFields interestIncentivesFields) {
+        this.interestRateChartSlab = interestRateChartSlab;
+        this.interestIncentivesFields = interestIncentivesFields;
+        if (this.interestRateChartSlab != null) {
+            interestRateChartSlab.addInterestIncentive(this);
+        }
+    }
+
+    public void update(final JsonCommand command, final Map<String, Object> actualChanges, final DataValidatorBuilder baseDataValidator,
+            final Locale locale) {
+        this.interestIncentivesFields.update(command, actualChanges, baseDataValidator, locale);
+    }
+
+    public InterestIncentivesFields interestIncentivesFields() {
+        return this.interestIncentivesFields;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestIncentivesFields.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestIncentivesFields.java
new file mode 100755
index 0000000..1bd6304
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestIncentivesFields.java
@@ -0,0 +1,177 @@
+/**
+ * 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.interestratechart.domain;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.attributeNameParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.attributeValueParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.conditionTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.entityTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.incentiveTypeparamName;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.portfolio.common.domain.ConditionType;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveEntityType;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveType;
+
+@Embeddable
+public class InterestIncentivesFields {
+
+    @Column(name = "entiry_type", nullable = false)
+    private Integer entityType;
+
+    @Column(name = "attribute_name", nullable = false)
+    private Integer attributeName;
+
+    @Column(name = "condition_type", nullable = false)
+    private Integer conditionType;
+
+    @Column(name = "attribute_value", nullable = false)
+    private String attributeValue;
+
+    @Column(name = "incentive_type", nullable = false)
+    private Integer incentiveType;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    protected InterestIncentivesFields() {
+
+    }
+
+    public static InterestIncentivesFields createNew(final Integer entityType, final Integer attributeName, final Integer conditionType,
+            final String attributeValue, final Integer incentiveType, final BigDecimal amount, final DataValidatorBuilder baseDataValidator) {
+        return new InterestIncentivesFields(entityType, attributeName, conditionType, attributeValue, incentiveType, amount,
+                baseDataValidator);
+    }
+
+    private InterestIncentivesFields(final Integer entityType, final Integer attributeName, final Integer conditionType,
+            final String attributeValue, final Integer incentiveType, final BigDecimal amount, final DataValidatorBuilder baseDataValidator) {
+        this.entityType = entityType;
+        this.attributeName = attributeName;
+        this.attributeValue = attributeValue;
+        this.conditionType = conditionType;
+        this.incentiveType = incentiveType;
+        this.amount = amount;
+        validateIncentiveData(baseDataValidator);
+    }
+
+    public InterestIncentiveAttributeName attributeName() {
+        return InterestIncentiveAttributeName.fromInt(this.attributeName);
+    }
+
+    public ConditionType conditionType() {
+        return ConditionType.fromInt(this.conditionType);
+    }
+
+    public String attributeValue() {
+        return this.attributeValue;
+    }
+
+    public InterestIncentiveType incentiveType() {
+        return InterestIncentiveType.fromInt(this.incentiveType);
+    }
+
+    public BigDecimal amount() {
+        return this.amount;
+    }
+
+    public InterestIncentiveEntityType entiryType() {
+        return InterestIncentiveEntityType.fromInt(this.entityType);
+    }
+
+    public void update(final JsonCommand command, final Map<String, Object> actualChanges, final DataValidatorBuilder baseDataValidator,
+            final Locale locale) {
+        if (command.isChangeInIntegerParameterNamed(entityTypeParamName, this.entityType, locale)) {
+            final Integer newValue = command.integerValueOfParameterNamed(entityTypeParamName, locale);
+            actualChanges.put(entityTypeParamName, newValue);
+            this.entityType = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(attributeNameParamName, this.attributeName, locale)) {
+            final Integer newValue = command.integerValueOfParameterNamed(attributeNameParamName, locale);
+            actualChanges.put(attributeNameParamName, newValue);
+            this.attributeName = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(conditionTypeParamName, this.conditionType, locale)) {
+            final Integer newValue = command.integerValueOfParameterNamed(conditionTypeParamName, locale);
+            actualChanges.put(conditionTypeParamName, newValue);
+            this.conditionType = newValue;
+        }
+
+        if (command.isChangeInStringParameterNamed(attributeValueParamName, this.attributeValue)) {
+            final String newValue = command.stringValueOfParameterNamed(attributeValueParamName);
+            actualChanges.put(attributeValueParamName, newValue);
+            this.attributeValue = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(incentiveTypeparamName, this.incentiveType, locale)) {
+            final Integer newValue = command.integerValueOfParameterNamed(incentiveTypeparamName, locale);
+            actualChanges.put(incentiveTypeparamName, newValue);
+            this.incentiveType = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount, locale)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName, locale);
+            actualChanges.put(amountParamName, newValue);
+            this.amount = newValue;
+        }
+
+        validateIncentiveData(baseDataValidator);
+
+    }
+
+    public void validateIncentiveData(final DataValidatorBuilder baseDataValidator) {
+
+        switch (attributeName()) {
+            case GENDER:
+                baseDataValidator.reset().parameter(attributeValueParamName).value(this.attributeValue).longGreaterThanZero();
+                baseDataValidator.reset().parameter(conditionTypeParamName).value(this.conditionType)
+                        .isOneOfTheseValues(ConditionType.EQUAL.getValue(), ConditionType.NOT_EQUAL.getValue());
+            break;
+            case AGE:
+                baseDataValidator.reset().parameter(attributeValueParamName).value(this.attributeValue).longGreaterThanZero();
+            break;
+            case CLIENT_CLASSIFICATION:
+                baseDataValidator.reset().parameter(attributeValueParamName).value(this.attributeValue).longGreaterThanZero();
+                baseDataValidator.reset().parameter(conditionTypeParamName).value(this.conditionType)
+                        .isOneOfTheseValues(ConditionType.EQUAL.getValue(), ConditionType.NOT_EQUAL.getValue());
+            break;
+            case CLIENT_TYPE:
+                baseDataValidator.reset().parameter(attributeValueParamName).value(this.attributeValue).longGreaterThanZero();
+                baseDataValidator.reset().parameter(conditionTypeParamName).value(this.conditionType)
+                        .isOneOfTheseValues(ConditionType.EQUAL.getValue(), ConditionType.NOT_EQUAL.getValue());
+            break;
+
+            default:
+            break;
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java
new file mode 100644
index 0000000..428acb3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java
@@ -0,0 +1,276 @@
+/**
+ * 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.interestratechart.domain;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.INTERESTRATE_CHART_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.deleteParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.idParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.amountRangeFromParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.amountRangeToParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.annualInterestRateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.fromPeriodParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.periodTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.toPeriodParamName;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+@Entity
+@Table(name = "m_interest_rate_chart")
+public class InterestRateChart extends AbstractPersistable<Long> {
+
+    @Embedded
+    private InterestRateChartFields chartFields;
+
+    @OneToMany(mappedBy = "interestRateChart", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+    private Set<InterestRateChartSlab> chartSlabs = new HashSet<>();
+
+    protected InterestRateChart() {
+        //
+    }
+
+    public static InterestRateChart createNew(InterestRateChartFields chartFields, Collection<InterestRateChartSlab> interestRateChartSlabs) {
+
+        return new InterestRateChart(chartFields, new HashSet<>(interestRateChartSlabs));
+    }
+
+    private InterestRateChart(InterestRateChartFields chartFields, Set<InterestRateChartSlab> interestRateChartSlabs) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(INTERESTRATE_CHART_RESOURCE_NAME);
+
+        // validate before setting the other fields
+        this.validateChartSlabs(baseDataValidator);
+        this.throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+        this.chartFields = chartFields;
+        this.addChartSlabs(interestRateChartSlabs);
+
+    }
+
+    public void validateChartSlabs(DataValidatorBuilder baseDataValidator) {
+        Collection<InterestRateChartSlab> chartSlabs = this.setOfChartSlabs();
+
+        Integer tmpPeriodType = null;
+        List<InterestRateChartSlab> chartSlabsList = new ArrayList<>(chartSlabs);
+
+        for (int i = 0; i < chartSlabsList.size(); i++) {
+            InterestRateChartSlab iSlabs = chartSlabsList.get(i);
+            if (tmpPeriodType == null) {
+                tmpPeriodType = iSlabs.slabFields().periodType();
+            } else if (!iSlabs.slabFields().periodType().equals(tmpPeriodType)) {
+                baseDataValidator.parameter(periodTypeParamName).value(iSlabs.slabFields().periodType())
+                        .failWithCode("period.type.is.not.same", tmpPeriodType);
+            }
+            for (int j = i + 1; j < chartSlabsList.size(); j++) {
+                InterestRateChartSlab jSlabs = chartSlabsList.get(j);
+                if (iSlabs.slabFields().isPeriodOverlapping(jSlabs.slabFields())) {
+                    baseDataValidator
+                            .failWithCodeNoParameterAddedToErrorCode("chart.slabs.period.overlapping", iSlabs.slabFields().fromPeriod(),
+                                    iSlabs.slabFields().toPeriod(), jSlabs.slabFields().fromPeriod(), jSlabs.slabFields().toPeriod());
+                }
+            }
+        }
+    }
+
+    public void addChartSlabs(Collection<InterestRateChartSlab> interestRateChartSlabsSet) {
+        Set<InterestRateChartSlab> existingChartSlabs = setOfChartSlabs();
+        for (InterestRateChartSlab newChartSlabs : interestRateChartSlabsSet) {
+            newChartSlabs.setInterestRateChart(this);
+            existingChartSlabs.add(newChartSlabs);
+        }
+    }
+
+    public void addChartSlab(InterestRateChartSlab newChartSlab) {
+        newChartSlab.setInterestRateChart(this);
+        setOfChartSlabs().add(newChartSlab);
+    }
+
+    public Set<InterestRateChartSlab> setOfChartSlabs() {
+        if (this.chartSlabs == null) {
+            this.chartSlabs = new HashSet<>();
+        }
+        return this.chartSlabs;
+    }
+
+    public void update(JsonCommand command, final Map<String, Object> actualChanges) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(INTERESTRATE_CHART_RESOURCE_NAME);
+
+        this.update(command, actualChanges, baseDataValidator, null, null);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    public void update(JsonCommand command, final Map<String, Object> actualChanges, final DataValidatorBuilder baseDataValidator,
+            final Set<InterestRateChart> existingCharts, String currencyCode) {
+
+        this.chartFields.update(command, actualChanges, baseDataValidator);
+
+        // interestRateChartSlabs
+        if (command.hasParameter(InterestRateChartApiConstants.chartSlabs)) {
+            updateChartSlabs(command, actualChanges, baseDataValidator, currencyCode);
+        }
+
+        this.validateCharts(baseDataValidator, existingCharts);
+    }
+
+    private void validateCharts(final DataValidatorBuilder baseDataValidator, final Set<InterestRateChart> existingCharts) {
+
+        for (InterestRateChart existingChart : existingCharts) {
+            if (!existingChart.equals(this)) {
+                if (this.chartFields.isOverlapping(existingChart.chartFields)) {
+                    baseDataValidator.failWithCodeNoParameterAddedToErrorCode("chart.overlapping.from.and.end.dates",
+                            existingChart.getFromDateAsLocalDate(), existingChart.getEndDateAsLocalDate(), this.getFromDateAsLocalDate(),
+                            this.getEndDateAsLocalDate());
+                }
+            }
+        }
+    }
+
+    public void updateChartSlabs(JsonCommand command, final Map<String, Object> actualChanges,
+            final DataValidatorBuilder baseDataValidator, String currencyCode) {
+
+        final Map<String, Object> deleteChartSlabs = new HashMap<>();
+        final Map<String, Object> chartSlabsChanges = new HashMap<>();
+        final Locale locale = command.extractLocale();
+        if (command.hasParameter(InterestRateChartApiConstants.chartSlabs)) {
+            final JsonArray array = command.arrayOfParameterNamed(InterestRateChartApiConstants.chartSlabs);
+            if (array != null) {
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject chartSlabsElement = array.get(i).getAsJsonObject();
+                    JsonCommand chartSlabsCommand = JsonCommand.fromExistingCommand(command, chartSlabsElement);
+                    if (chartSlabsCommand.parameterExists(idParamName)) {
+                        final Long chartSlabId = chartSlabsCommand.longValueOfParameterNamed(idParamName);
+                        final InterestRateChartSlab chartSlab = this.findChartSlab(chartSlabId);
+                        if (chartSlab == null) {
+                            baseDataValidator.parameter(idParamName).value(chartSlabId).failWithCode("no.chart.slab.associated.with.id");
+                        } else if (chartSlabsCommand.parameterExists(deleteParamName)) {
+                            if (this.removeChartSlab(chartSlab)) {
+                                deleteChartSlabs.put(idParamName, chartSlabId);
+                            }
+                        } else {
+                            chartSlab.update(chartSlabsCommand, chartSlabsChanges, baseDataValidator, locale);
+                        }
+                    } else {
+
+                        /**
+                         * TODO: AA: Move this code to
+                         * InterestRateChartSlabAssembler
+                         */
+                        final String description = chartSlabsCommand.stringValueOfParameterNamed(descriptionParamName);
+                        final Integer periodTypeId = chartSlabsCommand.integerValueOfParameterNamed(periodTypeParamName, locale);
+                        final SavingsPeriodFrequencyType periodFrequencyType = SavingsPeriodFrequencyType.fromInt(periodTypeId);
+                        final Integer fromPeriod = chartSlabsCommand.integerValueOfParameterNamed(fromPeriodParamName, locale);
+                        final Integer toPeriod = chartSlabsCommand.integerValueOfParameterNamed(toPeriodParamName, locale);
+                        final BigDecimal amountRangeFrom = chartSlabsCommand.bigDecimalValueOfParameterNamed(amountRangeFromParamName,
+                                locale);
+                        final BigDecimal amountRangeTo = chartSlabsCommand.bigDecimalValueOfParameterNamed(amountRangeToParamName, locale);
+                        final BigDecimal annualInterestRate = chartSlabsCommand.bigDecimalValueOfParameterNamed(
+                                annualInterestRateParamName, locale);
+
+                        final InterestRateChartSlabFields slabFields = InterestRateChartSlabFields
+                                .createNew(description, periodFrequencyType, fromPeriod, toPeriod, amountRangeFrom, amountRangeTo,
+                                        annualInterestRate, currencyCode);
+                        final InterestRateChartSlab chartSlab = InterestRateChartSlab.createNew(slabFields, this);
+                        chartSlab.slabFields().validateChartSlabPlatformRules(chartSlabsCommand, baseDataValidator, locale);
+                        chartSlab.updateIncentives(chartSlabsCommand, actualChanges, baseDataValidator, chartSlab, locale);
+                        this.addChartSlab(chartSlab);
+                    }
+                }
+            }
+        }
+
+        // add chart slab changes to actual changes list.
+        if (!chartSlabsChanges.isEmpty()) {
+            actualChanges.put(InterestRateChartApiConstants.chartSlabs, chartSlabsChanges);
+        }
+
+        // add deleted chart Slabs to actual changes
+        if (!deleteChartSlabs.isEmpty()) {
+            actualChanges.put("deletedChartSlabs", deleteChartSlabs);
+        }
+
+        this.validateChartSlabs(baseDataValidator);
+    }
+
+    public InterestRateChartSlab findChartSlab(Long chartSlabId) {
+        final Set<InterestRateChartSlab> chartSlabs = setOfChartSlabs();
+
+        for (InterestRateChartSlab interestRateChartSlab : chartSlabs) {
+            if (interestRateChartSlab.getId().equals(chartSlabId)) { return interestRateChartSlab; }
+        }
+        return null;
+    }
+
+    private boolean removeChartSlab(InterestRateChartSlab chartSlab) {
+        final Set<InterestRateChartSlab> chartSlabs = setOfChartSlabs();
+        return chartSlabs.remove(chartSlab);
+    }
+
+    public LocalDate getFromDateAsLocalDate() {
+        return this.chartFields.getFromDateAsLocalDate();
+    }
+
+    public LocalDate getEndDateAsLocalDate() {
+        return this.chartFields.getEndDateAsLocalDate();
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public InterestRateChartFields chartFields() {
+        return this.chartFields;
+    }
+
+    public boolean isApplicableChartFor(final LocalDate target) {
+        return this.chartFields.isApplicableChartFor(target);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java
new file mode 100644
index 0000000..ca1f1b0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java
@@ -0,0 +1,163 @@
+/**
+ * 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.interestratechart.domain;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.dateFormatParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.endDateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.fromDateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.localeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.nameParamName;
+
+import java.util.Date;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.joda.time.LocalDate;
+
+@Embeddable
+public class InterestRateChartFields {
+
+    @Column(name = "name", length = 100, unique = false, nullable = false)
+    private String name;
+
+    @Column(name = "description", nullable = true)
+    private String description;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "from_date", nullable = false)
+    private Date fromDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "end_date", nullable = false)
+    private Date endDate;
+
+    protected InterestRateChartFields() {
+        //
+    }
+
+    public static InterestRateChartFields createNew(String name, String description, LocalDate fromDate, LocalDate toDate) {
+        return new InterestRateChartFields(name, description, fromDate, toDate);
+    }
+
+    private InterestRateChartFields(String name, String description, LocalDate fromDate, LocalDate toDate) {
+        this.name = name;
+        this.description = description;
+        this.fromDate = fromDate.toDate();
+        this.endDate = (toDate == null) ? null : toDate.toDate();
+    }
+
+    public void update(JsonCommand command, final Map<String, Object> actualChanges, final DataValidatorBuilder baseDataValidator) {
+
+        if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
+            final String newValue = command.stringValueOfParameterNamed(nameParamName);
+            actualChanges.put(nameParamName, newValue);
+            this.name = newValue;
+        }
+
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = newValue;
+        }
+
+        final String localeAsInput = command.locale();
+        final String dateFormat = command.dateFormat();
+
+        if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateAsLocalDate())) {
+            final LocalDate newValue = command.localDateValueOfParameterNamed(fromDateParamName);
+            final String newValueAsString = command.stringValueOfParameterNamed(fromDateParamName);
+            actualChanges.put(fromDateParamName, newValueAsString);
+            actualChanges.put(localeParamName, localeAsInput);
+            actualChanges.put(dateFormatParamName, dateFormat);
+            this.fromDate = newValue.toDate();
+        }
+
+        if (command.isChangeInLocalDateParameterNamed(endDateParamName, getEndDateAsLocalDate())) {
+            final LocalDate newValue = command.localDateValueOfParameterNamed(endDateParamName);
+            final String newValueAsString = command.stringValueOfParameterNamed(endDateParamName);
+            actualChanges.put(endDateParamName, newValueAsString);
+            actualChanges.put(localeParamName, localeAsInput);
+            actualChanges.put(dateFormatParamName, dateFormat);
+            this.endDate = newValue.toDate();
+        }
+
+        if (isFromDateAfterToDate()) {
+            baseDataValidator.parameter(fromDateParamName).value(fromDate).failWithCode("from.date.is.after.to.date");
+        }
+    }
+
+    public boolean isFromDateAfterToDate() {
+        return isFromDateAfter(getEndDateAsLocalDate());
+    }
+
+    public boolean isFromDateAfter(LocalDate compare) {
+        final LocalDate fromDate = getFromDateAsLocalDate();
+        if (fromDate != null && compare != null) { return fromDate.isAfter(compare); }
+        return false;
+    }
+
+    public LocalDate getFromDateAsLocalDate() {
+        LocalDate fromDate = null;
+        if (this.fromDate != null) {
+            fromDate = new LocalDate(this.fromDate);
+        }
+        return fromDate;
+    }
+
+    public LocalDate getEndDateAsLocalDate() {
+        LocalDate endDate = null;
+        if (this.endDate != null) {
+            endDate = new LocalDate(this.endDate);
+        }
+        return endDate;
+    }
+
+    public boolean isOverlapping(InterestRateChartFields that) {
+        final LocalDate thisFromDate = this.getFromDateAsLocalDate();
+        LocalDate thisEndDate = this.getEndDateAsLocalDate();
+        thisEndDate = thisEndDate == null ? DateUtils.getLocalDateOfTenant() : thisEndDate;
+        final LocalDate thatFromDate = that.getFromDateAsLocalDate();
+        LocalDate thatEndDate = that.getEndDateAsLocalDate();
+        thatEndDate = thatEndDate == null ? DateUtils.getLocalDateOfTenant() : thatEndDate;
+        
+        final LocalDateInterval thisInterval = LocalDateInterval.create(thisFromDate, thisEndDate);
+        final LocalDateInterval thatInterval = LocalDateInterval.create(thatFromDate, thatEndDate);
+        
+        if(thisInterval.containsPortionOf(thatInterval) || thatInterval.containsPortionOf(thisInterval)){
+            return true;
+        }
+        return false;// no overlapping
+    }
+    
+    public boolean isApplicableChartFor(final LocalDate target){
+        final LocalDate endDate = this.endDate == null ? DateUtils.getLocalDateOfTenant() : this.getEndDateAsLocalDate();
+        final LocalDateInterval interval = LocalDateInterval.create(getFromDateAsLocalDate(), endDate);
+        return interval.contains(target);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlab.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlab.java
new file mode 100644
index 0000000..39ce24d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlab.java
@@ -0,0 +1,195 @@
+/**
+ * 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.interestratechart.domain;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.attributeNameParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.attributeValueParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.conditionTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.entityTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.incentiveTypeparamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.deleteParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.idParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.INTERESTRATE_CHART_SLAB_RESOURCE_NAME;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants;
+import org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+@Entity
+@Table(name = "m_interest_rate_slab")
+public class InterestRateChartSlab extends AbstractPersistable<Long> {
+
+    @Embedded
+    private InterestRateChartSlabFields slabFields;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "interest_rate_chart_id", referencedColumnName = "id", nullable = false)
+    private InterestRateChart interestRateChart;
+
+    @OneToMany(mappedBy = "interestRateChartSlab", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+    private Set<InterestIncentives> interestIncentives = new HashSet<>();
+
+    protected InterestRateChartSlab() {
+        //
+    }
+
+    public static InterestRateChartSlab createNew(InterestRateChartSlabFields slabFields, InterestRateChart interestRateChart) {
+        return new InterestRateChartSlab(slabFields, interestRateChart);
+    }
+
+    public void update(JsonCommand command, final Map<String, Object> actualChanges, final Locale locale) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(INTERESTRATE_CHART_SLAB_RESOURCE_NAME);
+        this.update(command, actualChanges, baseDataValidator, locale);
+        this.interestRateChart.validateChartSlabs(baseDataValidator);
+        this.throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void update(JsonCommand command, final Map<String, Object> actualChanges, final DataValidatorBuilder baseDataValidator,
+            final Locale locale) {
+        this.slabFields.update(command, actualChanges, baseDataValidator, locale);
+        updateIncentives(command, actualChanges, baseDataValidator, this, locale);
+    }
+
+    private InterestRateChartSlab(InterestRateChartSlabFields slabFields, InterestRateChart interestRateChart) {
+
+        this.slabFields = slabFields;
+        this.interestRateChart = interestRateChart;
+        if (this.interestRateChart != null) {
+            interestRateChart.addChartSlab(this);
+        }
+    }
+
+    public void setInterestRateChart(InterestRateChart interestRateChart) {
+        this.interestRateChart = interestRateChart;
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public InterestRateChartSlabFields slabFields() {
+        return this.slabFields;
+    }
+
+    public Set<InterestIncentives> setOfInterestIncentives() {
+        if (this.interestIncentives == null) {
+            this.interestIncentives = new HashSet<>();
+        }
+        return this.interestIncentives;
+    }
+
+    public void addInterestIncentive(InterestIncentives interestIncentives) {
+        interestIncentives.updateInterestRateChartSlab(this);
+        setOfInterestIncentives().add(interestIncentives);
+    }
+
+    public InterestIncentives findInterestIncentive(Long interestIncentiveId) {
+        final Set<InterestIncentives> interestIncentives = setOfInterestIncentives();
+
+        for (InterestIncentives interestIncentive : interestIncentives) {
+            if (interestIncentive.getId().equals(interestIncentiveId)) { return interestIncentive; }
+        }
+        return null;
+    }
+
+    public boolean removeInterestIncentive(InterestIncentives incentive) {
+        final Set<InterestIncentives> incentives = setOfInterestIncentives();
+        return incentives.remove(incentive);
+    }
+
+    public void updateIncentives(JsonCommand command, final Map<String, Object> actualChanges,
+            final DataValidatorBuilder baseDataValidator, final InterestRateChartSlab chartSlab, final Locale locale) {
+        final Map<String, Object> deleteIncentives = new HashMap<>();
+        final Map<String, Object> IncentiveChanges = new HashMap<>();
+        if (command.hasParameter(InterestRateChartSlabApiConstants.incentivesParamName)) {
+            final JsonArray array = command.arrayOfParameterNamed(InterestRateChartSlabApiConstants.incentivesParamName);
+            if (array != null) {
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject incentiveElement = array.get(i).getAsJsonObject();
+                    JsonCommand incentivesCommand = JsonCommand.fromExistingCommand(command, incentiveElement);
+                    if (incentivesCommand.parameterExists(InterestIncentiveApiConstants.idParamName)) {
+                        final Long interestIncentiveId = incentivesCommand
+                                .longValueOfParameterNamed(InterestIncentiveApiConstants.idParamName);
+                        final InterestIncentives interestIncentives = chartSlab.findInterestIncentive(interestIncentiveId);
+                        if (interestIncentives == null) {
+                            baseDataValidator.parameter(InterestIncentiveApiConstants.idParamName).value(interestIncentiveId)
+                                    .failWithCode("no.interest.incentive.associated.with.id");
+                        } else if (incentivesCommand.parameterExists(deleteParamName)) {
+                            if (chartSlab.removeInterestIncentive(interestIncentives)) {
+                                deleteIncentives.put(idParamName, interestIncentiveId);
+                            }
+                        } else {
+                            interestIncentives.update(incentivesCommand, IncentiveChanges, baseDataValidator, locale);
+                        }
+                    } else {
+                        Integer entityType = incentivesCommand.integerValueOfParameterNamed(entityTypeParamName, locale);
+                        Integer conditionType = incentivesCommand.integerValueOfParameterNamed(conditionTypeParamName, locale);
+                        Integer attributeName = incentivesCommand.integerValueOfParameterNamed(attributeNameParamName, locale);
+                        String attributeValue = incentivesCommand.stringValueOfParameterNamed(attributeValueParamName);
+                        Integer incentiveType = incentivesCommand.integerValueOfParameterNamed(incentiveTypeparamName, locale);
+                        BigDecimal amount = incentivesCommand.bigDecimalValueOfParameterNamed(amountParamName, locale);
+                        InterestIncentivesFields incentivesFields = InterestIncentivesFields.createNew(entityType, attributeName,
+                                conditionType, attributeValue, incentiveType, amount, baseDataValidator);
+                        InterestIncentives incentives = new InterestIncentives(chartSlab, incentivesFields);
+                        chartSlab.addInterestIncentive(incentives);
+                    }
+                }
+            }
+            // add chart slab changes to actual changes list.
+            if (!IncentiveChanges.isEmpty()) {
+                actualChanges.put(InterestRateChartSlabApiConstants.incentivesParamName, IncentiveChanges);
+            }
+
+            // add deleted chart Slabs to actual changes
+            if (!deleteIncentives.isEmpty()) {
+                actualChanges.put("deletedIncentives", deleteIncentives);
+            }
+
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java
new file mode 100644
index 0000000..334772c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java
@@ -0,0 +1,242 @@
+/**
+ * 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.interestratechart.domain;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.amountRangeFromParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.amountRangeToParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.annualInterestRateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.fromPeriodParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.periodTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.toPeriodParamName;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+import org.joda.time.Months;
+import org.joda.time.Weeks;
+import org.joda.time.Years;
+
+@Embeddable
+public class InterestRateChartSlabFields {
+
+    @Column(name = "description", nullable = true)
+    private String description;
+
+    @Column(name = "period_type_enum", nullable = false)
+    private Integer periodType;
+
+    @Column(name = "from_period", nullable = false)
+    private Integer fromPeriod;
+
+    @Column(name = "to_period", nullable = true)
+    private Integer toPeriod;
+
+    @Column(name = "amount_range_from", scale = 6, precision = 19)
+    private BigDecimal amountRangeFrom;
+
+    @Column(name = "amount_range_to", scale = 6, precision = 19)
+    private BigDecimal amountRangeTo;
+
+    @Column(name = "annual_interest_rate", scale = 6, precision = 19, nullable = false)
+    private BigDecimal annualInterestRate;
+
+    @Column(name = "currency_code", nullable = false)
+    private String currencyCode;
+
+    protected InterestRateChartSlabFields() {
+        //
+    }
+
+    public static InterestRateChartSlabFields createNew(final String description, final SavingsPeriodFrequencyType periodFrequencyType,
+            final Integer fromPeriod, final Integer toPeriod, final BigDecimal amountRangeFrom, final BigDecimal amountRangeTo,
+            final BigDecimal annualInterestRate, final String currencyCode) {
+        return new InterestRateChartSlabFields(description, periodFrequencyType, fromPeriod, toPeriod, amountRangeFrom, amountRangeTo,
+                annualInterestRate, currencyCode);
+    }
+
+    private InterestRateChartSlabFields(final String description, final SavingsPeriodFrequencyType periodFrequencyType,
+            final Integer fromPeriod, final Integer toPeriod, final BigDecimal amountRangeFrom, final BigDecimal amountRangeTo,
+            final BigDecimal annualInterestRate, final String currencyCode) {
+        this.description = description;
+        this.periodType = (periodFrequencyType == null) ? null : periodFrequencyType.getValue();
+        this.fromPeriod = fromPeriod;
+        this.toPeriod = toPeriod;
+        this.amountRangeFrom = amountRangeFrom;
+        this.amountRangeTo = amountRangeTo;
+        this.annualInterestRate = annualInterestRate;
+        this.currencyCode = currencyCode;
+    }
+
+    public void update(final JsonCommand command, final Map<String, Object> actualChanges, final DataValidatorBuilder baseDataValidator,
+            final Locale locale) {
+
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(periodTypeParamName, this.periodType, locale)) {
+            final Integer newValue = command.integerValueOfParameterNamed(periodTypeParamName, locale);
+            actualChanges.put(periodTypeParamName, newValue);
+            this.periodType = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(fromPeriodParamName, this.fromPeriod, locale)) {
+            final Integer newValue = command.integerValueOfParameterNamed(fromPeriodParamName, locale);
+            actualChanges.put(fromPeriodParamName, newValue);
+            this.fromPeriod = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(toPeriodParamName, this.toPeriod, locale)) {
+            final Integer newValue = command.integerValueOfParameterNamed(toPeriodParamName, locale);
+            actualChanges.put(toPeriodParamName, newValue);
+            this.toPeriod = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(amountRangeFromParamName, this.amountRangeFrom, locale)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountRangeFromParamName, locale);
+            actualChanges.put(amountRangeFromParamName, newValue);
+            this.amountRangeFrom = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(amountRangeToParamName, this.amountRangeTo, locale)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountRangeToParamName, locale);
+            actualChanges.put(amountRangeToParamName, newValue);
+            this.amountRangeTo = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(annualInterestRateParamName, this.annualInterestRate, locale)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(annualInterestRateParamName, locale);
+            actualChanges.put(annualInterestRateParamName, newValue);
+            this.annualInterestRate = newValue;
+        }
+
+        validateChartSlabPlatformRules(command, baseDataValidator, locale);
+    }
+
+    public void validateChartSlabPlatformRules(final JsonCommand chartSlabsCommand, final DataValidatorBuilder baseDataValidator,
+            Locale locale) {
+        if (isFromPeriodGreaterThanToPeriod()) {
+            final Integer fromPeriod = chartSlabsCommand.integerValueOfParameterNamed(fromPeriodParamName, locale);
+            baseDataValidator.parameter(fromPeriodParamName).value(fromPeriod).failWithCode("from.period.is.greater.than.to.period");
+        }
+
+        if (isAmountRangeFromGreaterThanTo()) {
+            final BigDecimal amountRangeFrom = chartSlabsCommand.bigDecimalValueOfParameterNamed(amountRangeFromParamName, locale);
+            baseDataValidator.parameter(amountRangeFromParamName).value(amountRangeFrom)
+                    .failWithCode("amount.range.from.is.greater.than.amount.range.to");
+        }
+    }
+
+    public boolean isFromPeriodGreaterThanToPeriod() {
+        boolean isGreater = false;
+        if (this.toPeriod != null && this.fromPeriod.compareTo(this.toPeriod) > 1) {
+            isGreater = true;
+        }
+        return isGreater;
+    }
+
+    public boolean isAmountRangeFromGreaterThanTo() {
+        boolean isGreater = false;
+        if (this.amountRangeFrom != null && this.amountRangeTo != null && this.amountRangeFrom.compareTo(this.amountRangeTo) > 1) {
+            isGreater = true;
+        }
+        return isGreater;
+    }
+
+    public Integer periodType() {
+        return this.periodType;
+    }
+
+    public Integer fromPeriod() {
+        return this.fromPeriod;
+    }
+
+    public Integer toPeriod() {
+        return this.toPeriod;
+    }
+
+    public boolean isPeriodOverlapping(final InterestRateChartSlabFields that) {
+        if (that.toPeriod == null) {
+            if (this.toPeriod == null) { return true; }
+            return that.fromPeriod <= this.toPeriod;
+        }
+        return this.fromPeriod <= that.toPeriod && that.fromPeriod <= this.toPeriod;
+    }
+
+    public boolean isBetweenPeriod(final LocalDate periodStartDate, final LocalDate periodEndDate) {
+        final Integer compare = depositPeriod(periodStartDate, periodEndDate);
+        return (compare < this.fromPeriod || (this.toPeriod != null && compare > this.toPeriod)) ? false : true;
+    }
+
+    public boolean isAmountRangeProvided() {
+        return (this.amountRangeFrom == null) ? false : true;
+    }
+
+    public BigDecimal annualInterestRate() {
+        return this.annualInterestRate;
+    }
+
+    public Integer depositPeriod(final LocalDate periodStartDate, final LocalDate periodEndDate) {
+        Integer actualDepositPeriod = 0;
+        final SavingsPeriodFrequencyType periodFrequencyType = SavingsPeriodFrequencyType.fromInt(periodType());
+        switch (periodFrequencyType) {
+            case DAYS:
+                actualDepositPeriod = Days.daysBetween(periodStartDate, periodEndDate).getDays();
+            break;
+            case WEEKS:
+                actualDepositPeriod = Weeks.weeksBetween(periodStartDate, periodEndDate).getWeeks();
+            break;
+            case MONTHS:
+                actualDepositPeriod = Months.monthsBetween(periodStartDate, periodEndDate).getMonths();
+            break;
+            case YEARS:
+                actualDepositPeriod = Years.yearsBetween(periodStartDate, periodEndDate).getYears();
+            break;
+            case INVALID:
+                actualDepositPeriod = 0;// default value
+            break;
+        }
+        return actualDepositPeriod;
+    }
+
+    public boolean isAmountBetween(final BigDecimal depositAmount) {
+        boolean returnValue = true;
+        if (amountRangeFrom != null && amountRangeTo != null) {
+            returnValue = depositAmount.compareTo(amountRangeFrom) >= 0 && depositAmount.compareTo(amountRangeTo) <= 0;
+        } else if (amountRangeFrom != null) {
+            returnValue = depositAmount.compareTo(amountRangeFrom) >= 0;
+        } else if (amountRangeTo != null) {
+            returnValue = depositAmount.compareTo(amountRangeTo) <= 0;
+        }
+        return returnValue;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/exception/InterestRateChartNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/exception/InterestRateChartNotFoundException.java
new file mode 100644
index 0000000..6644543
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/exception/InterestRateChartNotFoundException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.interestratechart.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class InterestRateChartNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public InterestRateChartNotFoundException(final Long id) {
+        super("error.msg.interest.rate.chart.id.invalid", "Interest rate chart with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/exception/InterestRateChartSlabNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/exception/InterestRateChartSlabNotFoundException.java
new file mode 100644
index 0000000..a6843cd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/exception/InterestRateChartSlabNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.interestratechart.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class InterestRateChartSlabNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public InterestRateChartSlabNotFoundException(final Long id) {
+        super("error.msg.interest.rate.chart.slab.id.invalid", "Interest rate chart slab with identifier " + id + " does not exist", id);
+    }
+    
+    public InterestRateChartSlabNotFoundException(final Long id, final Long chartId) {
+        super("error.msg.interest.rate.chart.slab.id.invalid", "Interest rate chart slab with identifier " + id + " does not exist in interest chart with identifier " + chartId , id, chartId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/CreateInterestRateChartCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/CreateInterestRateChartCommandHandler.java
new file mode 100644
index 0000000..06cb22b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/CreateInterestRateChartCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.interestratechart.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "INTERESTRATECHART", action = "CREATE")
+public class CreateInterestRateChartCommandHandler implements NewCommandSourceHandler {
+
+    private final InterestRateChartWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateInterestRateChartCommandHandler(final InterestRateChartWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.create(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/CreateInterestRateChartSlabCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/CreateInterestRateChartSlabCommandHandler.java
new file mode 100644
index 0000000..ef01f03
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/CreateInterestRateChartSlabCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.interestratechart.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartSlabWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "CHARTSLAB", action = "CREATE")
+public class CreateInterestRateChartSlabCommandHandler implements NewCommandSourceHandler {
+
+    private final InterestRateChartSlabWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateInterestRateChartSlabCommandHandler(final InterestRateChartSlabWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.create(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/DeleteInterestRateChartCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/DeleteInterestRateChartCommandHandler.java
new file mode 100644
index 0000000..6f83f81
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/DeleteInterestRateChartCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.interestratechart.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "INTERESTRATECHART", action = "DELETE")
+public class DeleteInterestRateChartCommandHandler implements NewCommandSourceHandler {
+
+    private final InterestRateChartWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteInterestRateChartCommandHandler(final InterestRateChartWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteChart(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/DeleteInterestRateChartSlabCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/DeleteInterestRateChartSlabCommandHandler.java
new file mode 100644
index 0000000..c9debde
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/DeleteInterestRateChartSlabCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.interestratechart.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartSlabWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "CHARTSLAB", action = "DELETE")
+public class DeleteInterestRateChartSlabCommandHandler implements NewCommandSourceHandler {
+
+    private final InterestRateChartSlabWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteInterestRateChartSlabCommandHandler(final InterestRateChartSlabWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        //command.subentityId(); //returns chart id
+        return this.writePlatformService.deleteChartSlab(command.entityId(), command.subentityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/UpdateInterestRateChartCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/UpdateInterestRateChartCommandHandler.java
new file mode 100644
index 0000000..3d20956
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/UpdateInterestRateChartCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.interestratechart.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "INTERESTRATECHART", action = "UPDATE")
+public class UpdateInterestRateChartCommandHandler implements NewCommandSourceHandler {
+
+    private final InterestRateChartWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateInterestRateChartCommandHandler(final InterestRateChartWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.update(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/UpdateInterestRateChartSlabCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/UpdateInterestRateChartSlabCommandHandler.java
new file mode 100644
index 0000000..3f6e79d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/handler/UpdateInterestRateChartSlabCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.interestratechart.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartSlabWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "CHARTSLAB", action = "UPDATE")
+public class UpdateInterestRateChartSlabCommandHandler implements NewCommandSourceHandler {
+
+    private final InterestRateChartSlabWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateInterestRateChartSlabCommandHandler(final InterestRateChartSlabWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        //command.subentityId();//returns chart id
+        return this.writePlatformService.update(command.entityId(), command.subentityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/AttributeIncentiveCalculation.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/AttributeIncentiveCalculation.java
new file mode 100755
index 0000000..99376f2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/AttributeIncentiveCalculation.java
@@ -0,0 +1,51 @@
+/**
+ * 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.interestratechart.incentive;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.portfolio.common.domain.ConditionType;
+
+public abstract class AttributeIncentiveCalculation {
+
+    public abstract BigDecimal calculateIncentive(final IncentiveDTO incentiveDTO);
+
+    public boolean applyIncentive(ConditionType conditionType, Long attributeValue, Long actualValue) {
+        boolean applyIncentive = false;
+        int compareVal = actualValue.compareTo(attributeValue);
+        switch (conditionType) {
+            case LESSTHAN:
+                applyIncentive = compareVal < 0;
+            break;
+            case EQUAL:
+                applyIncentive = compareVal == 0;
+            break;
+            case NOT_EQUAL:
+                applyIncentive = compareVal != 0;
+            break;
+            case GRETERTHAN:
+                applyIncentive = compareVal > 0;
+            break;
+            default:
+                applyIncentive = false;
+            break;
+        }
+        return applyIncentive;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/AttributeIncentiveCalculationFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/AttributeIncentiveCalculationFactory.java
new file mode 100755
index 0000000..0e9652e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/AttributeIncentiveCalculationFactory.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.portfolio.interestratechart.incentive;
+
+public class AttributeIncentiveCalculationFactory {
+
+    public static AttributeIncentiveCalculation findAttributeIncentiveCalculation(InterestIncentiveEntityType entityType) {
+        AttributeIncentiveCalculation attributeIncentiveCalculation = null;
+        switch (entityType) {
+            case CUSTOMER:
+                attributeIncentiveCalculation = new ClientAttributeIncentiveCalculation();
+            break;
+            default:
+            break;
+        }
+        return attributeIncentiveCalculation;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/ClientAttributeIncentiveCalculation.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/ClientAttributeIncentiveCalculation.java
new file mode 100755
index 0000000..ca1cdfa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/ClientAttributeIncentiveCalculation.java
@@ -0,0 +1,85 @@
+/**
+ * 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.interestratechart.incentive;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestIncentivesFields;
+import org.joda.time.LocalDate;
+import org.joda.time.Years;
+
+public class ClientAttributeIncentiveCalculation extends AttributeIncentiveCalculation {
+
+    @Override
+    public BigDecimal calculateIncentive(IncentiveDTO incentiveDTO) {
+        final Client client = incentiveDTO.client();
+        BigDecimal interest = incentiveDTO.interest();
+        final InterestIncentivesFields incentivesFields = incentiveDTO.incentives();
+        boolean applyIncentive = false;
+        switch (incentivesFields.attributeName()) {
+            case GENDER:
+                if (client.genderId() != null) {
+                    applyIncentive = applyIncentive(incentivesFields.conditionType(), Long.valueOf(incentivesFields.attributeValue()),
+                            client.genderId());
+                }
+            break;
+            case AGE:
+                if (client.dateOfBirth() != null) {
+                    final LocalDate dobLacalDate = LocalDate.fromDateFields(client.dateOfBirth());
+                    final int age = Years.yearsBetween(dobLacalDate, LocalDate.now()).getYears();
+                    applyIncentive = applyIncentive(incentivesFields.conditionType(), Long.valueOf(incentivesFields.attributeValue()),
+                            Long.valueOf(age));
+                }
+            break;
+            case CLIENT_TYPE:
+                if (client.clientTypeId() != null) {
+                    applyIncentive = applyIncentive(incentivesFields.conditionType(), Long.valueOf(incentivesFields.attributeValue()),
+                            client.clientTypeId());
+                }
+            break;
+            case CLIENT_CLASSIFICATION:
+                if (client.clientClassificationId() != null) {
+                    applyIncentive = applyIncentive(incentivesFields.conditionType(), Long.valueOf(incentivesFields.attributeValue()),
+                            client.clientClassificationId());
+                }
+            break;
+
+            default:
+            break;
+
+        }
+        if (applyIncentive) {
+            switch (incentivesFields.incentiveType()) {
+                case FIXED:
+                    interest = incentivesFields.amount();
+                break;
+                case INCENTIVE:
+                    interest = interest.add(incentivesFields.amount());
+                break;
+                default:
+                break;
+
+            }
+        }
+
+        return interest;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/IncentiveDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/IncentiveDTO.java
new file mode 100755
index 0000000..b63c5d6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/IncentiveDTO.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.interestratechart.incentive;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestIncentivesFields;
+
+public class IncentiveDTO {
+
+    private final Client client;
+    private final BigDecimal interest;
+    private final InterestIncentivesFields incentives;
+
+    public IncentiveDTO(final Client client, final BigDecimal interest, final InterestIncentivesFields incentives) {
+        this.client = client;
+        this.interest = interest;
+        this.incentives = incentives;
+    }
+
+    public Client client() {
+        return this.client;
+    }
+
+    public BigDecimal interest() {
+        return this.interest;
+    }
+
+    public InterestIncentivesFields incentives() {
+        return this.incentives;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveAttributeName.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveAttributeName.java
new file mode 100755
index 0000000..35d5738
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveAttributeName.java
@@ -0,0 +1,112 @@
+/**
+ * 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.interestratechart.incentive;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public enum InterestIncentiveAttributeName {
+
+    INVALID(1, "InterestIncentiveAttributeName.invalid"), //
+    GENDER(2, "InterestIncentiveAttributeName.gender"), //
+    AGE(3, "InterestIncentiveAttributeName.age"), //
+    CLIENT_TYPE(4, "InterestIncentiveAttributeName.clientType"), //
+    CLIENT_CLASSIFICATION(5, "InterestIncentiveAttributeName.clientClassification"); //
+
+    private final Integer value;
+    private final String code;
+
+    private static final Map<Integer, InterestIncentiveAttributeName> intToEnumMap = new HashMap<>();
+    static {
+        for (final InterestIncentiveAttributeName type : InterestIncentiveAttributeName.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static InterestIncentiveAttributeName fromInt(final Integer ruleTypeValue) {
+        final InterestIncentiveAttributeName type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private InterestIncentiveAttributeName(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString().replaceAll("_", " ");
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isGender() {
+        return InterestIncentiveAttributeName.GENDER.getValue().equals(this.value);
+    }
+
+    public boolean isAge() {
+        return InterestIncentiveAttributeName.AGE.getValue().equals(this.value);
+    }
+
+    public boolean isClientType() {
+        return InterestIncentiveAttributeName.CLIENT_TYPE.getValue().equals(this.value);
+    }
+
+    public boolean isClientClassification() {
+        return InterestIncentiveAttributeName.CLIENT_CLASSIFICATION.getValue().equals(this.value);
+    }
+
+    public boolean isInvalid() {
+        return InterestIncentiveAttributeName.INVALID.getValue().equals(this.value);
+    }
+
+    public static boolean isCodeValueAttribute(InterestIncentiveAttributeName attributeName) {
+        boolean isCodeValue = false;
+        switch (attributeName) {
+            case GENDER:
+            case CLIENT_TYPE:
+            case CLIENT_CLASSIFICATION:
+                isCodeValue = true;
+            break;
+            default:
+            break;
+        }
+        return isCodeValue;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final InterestIncentiveAttributeName enumType : values()) {
+            if (!enumType.isInvalid()) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveEntityType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveEntityType.java
new file mode 100755
index 0000000..deb0fff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveEntityType.java
@@ -0,0 +1,83 @@
+/**
+ * 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.interestratechart.incentive;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public enum InterestIncentiveEntityType {
+
+    INVALID(1, "InterestIncentiveEntityType.invalid"), //
+    CUSTOMER(2, "InterestIncentiveEntityType.customer"), //
+    ACCOUNT(3, "InterestIncentiveEntityType.account"); //
+
+    private final Integer value;
+    private final String code;
+
+    private static final Map<Integer, InterestIncentiveEntityType> intToEnumMap = new HashMap<>();
+    static {
+        for (final InterestIncentiveEntityType type : InterestIncentiveEntityType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static InterestIncentiveEntityType fromInt(final Integer ruleTypeValue) {
+        final InterestIncentiveEntityType type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private InterestIncentiveEntityType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString().replaceAll("_", " ");
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isCustomer() {
+        return InterestIncentiveEntityType.CUSTOMER.getValue().equals(this.value);
+    }
+
+    public boolean isInvalid() {
+        return InterestIncentiveEntityType.INVALID.getValue().equals(this.value);
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final InterestIncentiveEntityType enumType : values()) {
+            if (!enumType.isInvalid()) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveType.java
new file mode 100755
index 0000000..1e65654
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveType.java
@@ -0,0 +1,88 @@
+/**
+ * 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.interestratechart.incentive;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public enum InterestIncentiveType {
+
+    INVALID(1, "InterestIncentiveType.invalid"), //
+    FIXED(2, "InterestIncentiveType.fixed"), //
+    INCENTIVE(3, "InterestIncentiveType.incentive"); //
+
+    private final Integer value;
+    private final String code;
+
+    private static final Map<Integer, InterestIncentiveType> intToEnumMap = new HashMap<>();
+    static {
+        for (final InterestIncentiveType type : InterestIncentiveType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static InterestIncentiveType fromInt(final Integer ruleTypeValue) {
+        final InterestIncentiveType type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private InterestIncentiveType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString().replaceAll("_", " ");
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isIncentive() {
+        return InterestIncentiveType.INCENTIVE.getValue().equals(this.value);
+    }
+
+    public boolean isFixed() {
+        return InterestIncentiveType.FIXED.getValue().equals(this.value);
+    }
+
+    public boolean isInvalid() {
+        return InterestIncentiveType.INVALID.getValue().equals(this.value);
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final InterestIncentiveType enumType : values()) {
+            if (!enumType.isInvalid()) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveAssembler.java
new file mode 100755
index 0000000..19294a4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveAssembler.java
@@ -0,0 +1,104 @@
+/**
+ * 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.interestratechart.service;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.INCENTIVE_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.attributeNameParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.attributeValueParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.conditionTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.entityTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestIncentiveApiConstants.incentiveTypeparamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.incentivesParamName;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestIncentives;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestIncentivesFields;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class InterestIncentiveAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public InterestIncentiveAssembler(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public Collection<InterestIncentives> assembleIncentivesFrom(final JsonElement element, InterestRateChartSlab interestRateChartSlab,
+            final Locale locale) {
+        final Collection<InterestIncentives> interestIncentivesSet = new HashSet<>();
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            if (topLevelJsonElement.has(incentivesParamName) && topLevelJsonElement.get(incentivesParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(incentivesParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject incentiveElement = array.get(i).getAsJsonObject();
+                    final InterestIncentives incentives = this.assembleFrom(incentiveElement, interestRateChartSlab, locale);
+                    interestIncentivesSet.add(incentives);
+                }
+            }
+        }
+
+        return interestIncentivesSet;
+    }
+
+    private InterestIncentives assembleFrom(final JsonElement element, final InterestRateChartSlab interestRateChartSlab,
+            final Locale locale) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(INCENTIVE_RESOURCE_NAME);
+        InterestIncentivesFields incentivesFields = createInterestIncentiveFields(element, baseDataValidator, locale);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+        return new InterestIncentives(interestRateChartSlab, incentivesFields);
+    }
+
+    private InterestIncentivesFields createInterestIncentiveFields(final JsonElement element, final DataValidatorBuilder baseDataValidator,
+            final Locale locale) {
+        Integer entityType = this.fromApiJsonHelper.extractIntegerNamed(entityTypeParamName, element, locale);
+        Integer conditionType = this.fromApiJsonHelper.extractIntegerNamed(conditionTypeParamName, element, locale);
+        Integer attributeName = this.fromApiJsonHelper.extractIntegerNamed(attributeNameParamName, element, locale);
+        String attributeValue = this.fromApiJsonHelper.extractStringNamed(attributeValueParamName, element);
+        Integer incentiveType = this.fromApiJsonHelper.extractIntegerNamed(incentiveTypeparamName, element, locale);
+        BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, element, locale);
+        return InterestIncentivesFields.createNew(entityType, attributeName, conditionType, attributeValue, incentiveType, amount,
+                baseDataValidator);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveDropdownReadPlatformService.java
new file mode 100755
index 0000000..f75942b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveDropdownReadPlatformService.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.portfolio.interestratechart.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public interface InterestIncentiveDropdownReadPlatformService {
+
+    Collection<EnumOptionData> retrieveEntityTypeOptions();
+
+    Collection<EnumOptionData> retrieveAttributeNameOptions();
+
+    Collection<EnumOptionData> retrieveConditionTypeOptions();
+
+    Collection<EnumOptionData> retrieveIncentiveTypeOptions();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesDropdownReadPlatformServiceImpl.java
new file mode 100755
index 0000000..c4268c7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesDropdownReadPlatformServiceImpl.java
@@ -0,0 +1,54 @@
+/**
+ * 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.interestratechart.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.common.domain.ConditionType;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveEntityType;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveType;
+import org.springframework.stereotype.Service;
+
+@Service
+public class InterestIncentivesDropdownReadPlatformServiceImpl implements InterestIncentiveDropdownReadPlatformService {
+
+    @Override
+    public Collection<EnumOptionData> retrieveEntityTypeOptions() {
+        return InterestIncentivesEnumerations.entityType(InterestIncentiveEntityType.values());
+    }
+
+    @Override
+    public Collection<EnumOptionData> retrieveAttributeNameOptions() {
+        return InterestIncentivesEnumerations.attributeName(InterestIncentiveAttributeName.values());
+    }
+
+    @Override
+    public Collection<EnumOptionData> retrieveConditionTypeOptions() {
+        return CommonEnumerations.conditionType(ConditionType.values(), "incentive");
+    }
+
+    @Override
+    public Collection<EnumOptionData> retrieveIncentiveTypeOptions() {
+        return InterestIncentivesEnumerations.incentiveType(InterestIncentiveType.values());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesEnumerations.java
new file mode 100755
index 0000000..c46474e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesEnumerations.java
@@ -0,0 +1,141 @@
+/**
+ * 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.interestratechart.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveEntityType;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveType;
+
+public class InterestIncentivesEnumerations {
+
+    public static EnumOptionData attributeName(final Integer attributeName) {
+        return attributeName(InterestIncentiveAttributeName.fromInt(attributeName));
+    }
+
+    public static EnumOptionData attributeName(final InterestIncentiveAttributeName type) {
+        EnumOptionData nameOptionData = new EnumOptionData(InterestIncentiveAttributeName.INVALID.getValue().longValue(),
+                InterestIncentiveAttributeName.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case AGE:
+                nameOptionData = new EnumOptionData(InterestIncentiveAttributeName.AGE.getValue().longValue(),
+                        InterestIncentiveAttributeName.AGE.getCode(), "age");
+            break;
+            case GENDER:
+                nameOptionData = new EnumOptionData(InterestIncentiveAttributeName.GENDER.getValue().longValue(),
+                        InterestIncentiveAttributeName.GENDER.getCode(), "Gender");
+            break;
+            case CLIENT_TYPE:
+                nameOptionData = new EnumOptionData(InterestIncentiveAttributeName.CLIENT_TYPE.getValue().longValue(),
+                        InterestIncentiveAttributeName.CLIENT_TYPE.getCode(), "Client Type");
+            break;
+            case CLIENT_CLASSIFICATION:
+                nameOptionData = new EnumOptionData(InterestIncentiveAttributeName.CLIENT_CLASSIFICATION.getValue().longValue(),
+                        InterestIncentiveAttributeName.CLIENT_CLASSIFICATION.getCode(), "Client Classification");
+            break;
+        }
+
+        return nameOptionData;
+    }
+
+    public static List<EnumOptionData> attributeName(final InterestIncentiveAttributeName[] attributeNames) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final InterestIncentiveAttributeName attributeName : attributeNames) {
+            if (!attributeName.isInvalid()) {
+                optionDatas.add(attributeName(attributeName));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData entityType(final Integer entityType) {
+        return entityType(InterestIncentiveEntityType.fromInt(entityType));
+    }
+
+    public static EnumOptionData entityType(final InterestIncentiveEntityType type) {
+        EnumOptionData nameOptionData = new EnumOptionData(InterestIncentiveEntityType.INVALID.getValue().longValue(),
+                InterestIncentiveEntityType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case ACCOUNT:
+                nameOptionData = new EnumOptionData(InterestIncentiveEntityType.ACCOUNT.getValue().longValue(),
+                        InterestIncentiveEntityType.ACCOUNT.getCode(), "account");
+            break;
+            case CUSTOMER:
+                nameOptionData = new EnumOptionData(InterestIncentiveEntityType.CUSTOMER.getValue().longValue(),
+                        InterestIncentiveEntityType.CUSTOMER.getCode(), "Customer");
+            break;
+        }
+
+        return nameOptionData;
+    }
+
+    public static List<EnumOptionData> entityType(final InterestIncentiveEntityType[] entityTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final InterestIncentiveEntityType entityType : entityTypes) {
+            if (!entityType.isInvalid()) {
+                optionDatas.add(entityType(entityType));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData incentiveType(final Integer incentiveType) {
+        return incentiveType(InterestIncentiveType.fromInt(incentiveType));
+    }
+
+    public static EnumOptionData incentiveType(final InterestIncentiveType type) {
+        EnumOptionData nameOptionData = new EnumOptionData(InterestIncentiveType.INVALID.getValue().longValue(),
+                InterestIncentiveType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case FIXED:
+                nameOptionData = new EnumOptionData(InterestIncentiveType.FIXED.getValue().longValue(),
+                        InterestIncentiveType.FIXED.getCode(), "Fixed");
+            break;
+            case INCENTIVE:
+                nameOptionData = new EnumOptionData(InterestIncentiveType.INCENTIVE.getValue().longValue(),
+                        InterestIncentiveType.INCENTIVE.getCode(), "Incentive");
+            break;
+        }
+
+        return nameOptionData;
+    }
+
+    public static List<EnumOptionData> incentiveType(final InterestIncentiveType[] incentiveTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final InterestIncentiveType incentiveType : incentiveTypes) {
+            if (!incentiveType.isInvalid()) {
+                optionDatas.add(incentiveType(incentiveType));
+            }
+        }
+        return optionDatas;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartAssembler.java
new file mode 100644
index 0000000..a2a65db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartAssembler.java
@@ -0,0 +1,107 @@
+/**
+ * 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.interestratechart.service;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.INTERESTRATE_CHART_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.endDateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.fromDateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.nameParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.currencyCodeParamName;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartRepositoryWrapper;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartFields;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonElement;
+
+@Service
+public class InterestRateChartAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper;
+    private final InterestRateChartSlabAssembler chartSlabAssembler;
+
+    @Autowired
+    public InterestRateChartAssembler(final FromJsonHelper fromApiJsonHelper,
+            final InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper,
+            final InterestRateChartSlabAssembler chartSlabAssembler) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.interestRateChartRepositoryWrapper = interestRateChartRepositoryWrapper;
+        this.chartSlabAssembler = chartSlabAssembler;
+    }
+
+    /**
+     * Assembles a new {@link InterestRateChart} from JSON Slabs passed in
+     * request
+     */
+    public InterestRateChart assembleFrom(final JsonCommand command) {
+
+        final JsonElement element = command.parsedJson();
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        @SuppressWarnings("unused")
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(INTERESTRATE_CHART_RESOURCE_NAME);
+        final String currencyCode = this.fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
+        final InterestRateChart newChart = this.assembleFrom(element, currencyCode);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+        return newChart;
+    }
+
+    public InterestRateChart assembleFrom(final JsonElement element, final String currencyCode) {
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
+        final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+        final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(fromDateParamName, element);
+        final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(endDateParamName, element);
+        
+
+        // assemble chart Slabs
+        final Collection<InterestRateChartSlab> newChartSlabs = this.chartSlabAssembler.assembleChartSlabsFrom(element,
+                currencyCode);
+
+        final InterestRateChartFields fields = InterestRateChartFields.createNew(name, description, fromDate, toDate);
+        final InterestRateChart newChart = InterestRateChart.createNew(fields, newChartSlabs);
+        return newChart;
+    }
+
+    public InterestRateChart assembleFrom(final Long interestRateChartId) {
+        final InterestRateChart interestRateChart = this.interestRateChartRepositoryWrapper
+                .findOneWithNotFoundDetection(interestRateChartId);
+        return interestRateChart;
+    }
+
+    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/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformService.java
new file mode 100644
index 0000000..ff80c35
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformService.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.interestratechart.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public interface InterestRateChartDropdownReadPlatformService {
+
+    Collection<EnumOptionData> retrievePeriodTypeOptions();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformServiceImpl.java
new file mode 100644
index 0000000..6c17671
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformServiceImpl.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.portfolio.interestratechart.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.springframework.stereotype.Service;
+
+@Service
+public class InterestRateChartDropdownReadPlatformServiceImpl implements InterestRateChartDropdownReadPlatformService {
+
+    @Override
+    public Collection<EnumOptionData> retrievePeriodTypeOptions() {
+        return InterestRateChartEnumerations.periodType(PeriodFrequencyType.values());
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartEnumerations.java
new file mode 100644
index 0000000..4929a00
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartEnumerations.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.interestratechart.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+
+public class InterestRateChartEnumerations {
+
+    public static EnumOptionData periodType(final Integer type) {
+        return periodType(PeriodFrequencyType.fromInt(type));
+    }
+
+    public static EnumOptionData periodType(final PeriodFrequencyType type) {
+        EnumOptionData optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(),
+                PeriodFrequencyType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case DAYS:
+                optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(),
+                        PeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(),
+                        PeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(),
+                        PeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(),
+                        PeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+        }
+
+        return optionData;
+    }
+
+    public static List<EnumOptionData> periodType(final PeriodFrequencyType[] periodTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final PeriodFrequencyType periodType : periodTypes) {
+            if (!periodType.isInvalid()) {
+                optionDatas.add(periodType(periodType));
+            }
+        }
+        return optionDatas;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformService.java
new file mode 100644
index 0000000..65a6598
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformService.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.interestratechart.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+
+public interface InterestRateChartReadPlatformService {
+    
+    //Collection<InterestRateChartData> retrieveAll(Long savingsProductId);
+
+    InterestRateChartData retrieveOne(Long interestChartId);
+    
+    //Collection<InterestRateChartData> retrieveAllWithSlabs();
+    
+    Collection<InterestRateChartData> retrieveAllWithSlabs(Long savingsProductId);
+    
+    Collection<InterestRateChartData> retrieveAllWithSlabsWithTemplate(Long savingsProductId);
+
+    InterestRateChartData retrieveOneWithSlabs(Long interestChartId);
+    
+    InterestRateChartData retrieveWithTemplate(InterestRateChartData interestRateChartData);
+    
+    InterestRateChartData retrieveOneWithSlabsOnProductId(Long productId);
+    
+    InterestRateChartData template();
+    
+    InterestRateChartData retrieveActiveChartWithTemplate(Long productId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformServiceImpl.java
new file mode 100644
index 0000000..86eefad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformServiceImpl.java
@@ -0,0 +1,401 @@
+/**
+ * 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.interestratechart.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+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.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.interestratechart.data.InterestIncentiveData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabData;
+import org.apache.fineract.portfolio.interestratechart.exception.InterestRateChartNotFoundException;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName;
+import org.apache.fineract.portfolio.savings.data.DepositProductData;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class InterestRateChartReadPlatformServiceImpl implements InterestRateChartReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final InterestRateChartMapper chartRowMapper = new InterestRateChartMapper();
+    private final InterestRateChartExtractor chartExtractor = new InterestRateChartExtractor();
+    private final InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService;
+    private final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+
+    @Autowired
+    public InterestRateChartReadPlatformServiceImpl(PlatformSecurityContext context, final RoutingDataSource dataSource,
+            InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService,
+            final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.chartDropdownReadPlatformService = chartDropdownReadPlatformService;
+        this.interestIncentiveDropdownReadPlatformService = interestIncentiveDropdownReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+    }
+
+    @Override
+    public InterestRateChartData retrieveOne(Long chartId) {
+        try {
+            this.context.authenticatedUser();
+            final String sql = "select " + this.chartRowMapper.schema() + " where irc.id = ?";
+            return this.jdbcTemplate.queryForObject(sql, this.chartRowMapper, new Object[] { chartId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new InterestRateChartNotFoundException(chartId);
+        }
+    }
+
+    @Override
+    public Collection<InterestRateChartData> retrieveAllWithSlabs(Long productId) {
+        this.context.authenticatedUser();
+        String sql = "select " + this.chartExtractor.schema() + " where sp.id = ? order by irc.id, ircd.id";
+        return this.jdbcTemplate.query(sql, this.chartExtractor, new Object[] { productId });
+    }
+
+    @Override
+    public Collection<InterestRateChartData> retrieveAllWithSlabsWithTemplate(Long productId) {
+        Collection<InterestRateChartData> chartDatas = new ArrayList<>();
+
+        for (InterestRateChartData chartData : retrieveAllWithSlabs(productId)) {
+            chartDatas.add(retrieveWithTemplate(chartData));
+        }
+
+        return chartDatas;
+    }
+
+    @Override
+    public InterestRateChartData retrieveActiveChartWithTemplate(Long productId) {
+        Collection<InterestRateChartData> chartDatas = this.retrieveAllWithSlabsWithTemplate(productId);
+        return DepositProductData.activeChart(chartDatas);
+    }
+
+    @Override
+    public InterestRateChartData retrieveOneWithSlabs(Long chartId) {
+        this.context.authenticatedUser();
+        final String sql = "select " + this.chartExtractor.schema() + " where irc.id = ? order by ircd.id asc";
+        Collection<InterestRateChartData> chartDatas = this.jdbcTemplate.query(sql, this.chartExtractor, new Object[] { chartId });
+        if (chartDatas == null || chartDatas.isEmpty()) { throw new InterestRateChartNotFoundException(chartId); }
+
+        return chartDatas.iterator().next();
+    }
+
+    @Override
+    public InterestRateChartData retrieveWithTemplate(InterestRateChartData chartData) {
+
+        final List<CodeValueData> genderOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.GENDER));
+
+        final List<CodeValueData> clientTypeOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_TYPE));
+
+        final List<CodeValueData> clientClassificationOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_CLASSIFICATION));
+        return InterestRateChartData.withTemplate(chartData, this.chartDropdownReadPlatformService.retrievePeriodTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveEntityTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveAttributeNameOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveConditionTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveIncentiveTypeOptions(), genderOptions, clientTypeOptions,
+                clientClassificationOptions);
+    }
+
+    @Override
+    public InterestRateChartData retrieveOneWithSlabsOnProductId(@SuppressWarnings("unused") Long productId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public InterestRateChartData template() {
+        final List<CodeValueData> genderOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.GENDER));
+
+        final List<CodeValueData> clientTypeOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_TYPE));
+
+        final List<CodeValueData> clientClassificationOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_CLASSIFICATION));
+        return InterestRateChartData.template(this.chartDropdownReadPlatformService.retrievePeriodTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveEntityTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveAttributeNameOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveConditionTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveIncentiveTypeOptions(), genderOptions, clientTypeOptions,
+                clientClassificationOptions);
+    }
+
+    private static final class InterestRateChartExtractor implements ResultSetExtractor<Collection<InterestRateChartData>> {
+
+        InterestRateChartMapper chartMapper = new InterestRateChartMapper();
+        InterestRateChartSlabExtractor chartSlabsMapper = new InterestRateChartSlabExtractor();
+
+        private final String schemaSql;
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        private InterestRateChartExtractor() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder
+                    .append("irc.id as ircId, irc.name as ircName, irc.description as ircDescription,")
+                    .append("irc.from_date as ircFromDate, irc.end_date as ircEndDate, ")
+                    .append("ircd.id as ircdId, ircd.description as ircdDescription, ircd.period_type_enum ircdPeriodTypeId, ")
+                    .append("ircd.from_period as ircdFromPeriod, ircd.to_period as ircdToPeriod, ircd.amount_range_from as ircdAmountRangeFrom, ")
+                    .append("ircd.amount_range_to as ircdAmountRangeTo, ircd.annual_interest_rate as ircdAnnualInterestRate, ")
+                    .append("curr.code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ")
+                    .append("curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
+                    .append("sp.id as savingsProductId, sp.name as savingsProductName, ").append("iri.id as iriId, ")
+                    .append(" iri.entiry_type as entityType, iri.attribute_name as attributeName ,")
+                    .append(" iri.condition_type as conditionType, iri.attribute_value as attributeValue, ")
+                    .append(" iri.incentive_type as incentiveType, iri.amount as amount, ")
+                    .append("code.code_value as attributeValueDesc ").append("from ")
+                    .append("m_interest_rate_chart irc left join m_interest_rate_slab ircd on irc.id=ircd.interest_rate_chart_id ")
+                    .append(" left join m_interest_incentives iri on iri.interest_rate_slab_id = ircd.id ")
+                    .append(" left join m_code_value code on code.id = iri.attribute_value ")
+                    .append("left join m_currency curr on ircd.currency_code= curr.code ")
+                    .append("left join m_deposit_product_interest_rate_chart dpirc on irc.id=dpirc.interest_rate_chart_id ")
+                    .append("left join m_savings_product sp on sp.id=dpirc.deposit_product_id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public Collection<InterestRateChartData> extractData(ResultSet rs) throws SQLException, DataAccessException {
+
+            List<InterestRateChartData> chartDataList = new ArrayList<>();
+
+            InterestRateChartData chartData = null;
+            Long interestRateChartId = null;
+            int ircIndex = 0;// Interest rate chart index
+
+            while (rs.next()) {
+                Long tempIrcId = rs.getLong("ircId");
+                // first row or when interest rate chart id changes
+                if (chartData == null || (interestRateChartId != null && !interestRateChartId.equals(tempIrcId))) {
+
+                    interestRateChartId = tempIrcId;
+                    chartData = chartMapper.mapRow(rs, ircIndex++);
+                    chartDataList.add(chartData);
+
+                }
+                final InterestRateChartSlabData chartSlabsData = chartSlabsMapper.extractData(rs);
+                if (chartSlabsData != null) {
+                    chartData.addChartSlab(chartSlabsData);
+                }
+            }
+            return chartDataList;
+        }
+
+    }
+
+    public static final class InterestRateChartMapper implements RowMapper<InterestRateChartData> {
+
+        private final String schemaSql;
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        private InterestRateChartMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder.append("irc.id as ircId, irc.name as ircName, irc.description as ircDescription, ")
+                    .append("irc.from_date as ircFromDate, irc.end_date as ircEndDate, ")
+                    .append("sp.id as savingsProductId, sp.name as savingsProductName ").append("from ")
+                    .append("m_interest_rate_chart irc ")
+                    .append("left join m_deposit_product_interest_rate_chart dpirc on irc.id=dpirc.interest_rate_chart_id ")
+                    .append("left join m_savings_product sp on sp.id=dpirc.deposit_product_id ");
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public InterestRateChartData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long id = rs.getLong("ircId");
+            final String name = rs.getString("ircName");
+            final String description = rs.getString("ircDescription");
+            final LocalDate fromDate = JdbcSupport.getLocalDate(rs, "ircFromDate");
+            final LocalDate endDate = JdbcSupport.getLocalDate(rs, "ircEndDate");
+            final Long savingsProductId = JdbcSupport.getLongDefaultToNullIfZero(rs, "savingsProductId");
+            final String savingsProductName = rs.getString("savingsProductName");
+
+            return InterestRateChartData.instance(id, name, description, fromDate, endDate, savingsProductId, savingsProductName);
+        }
+
+    }
+
+    private static final class InterestRateChartSlabsMapper implements RowMapper<InterestRateChartSlabData> {
+
+        private final String schemaSql;
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        private InterestRateChartSlabsMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder
+                    .append("ircd.id as ircdId, ircd.description as ircdDescription, ircd.period_type_enum ircdPeriodTypeId, ")
+                    .append("ircd.from_period as ircdFromPeriod, ircd.to_period as ircdToPeriod, ircd.amount_range_from as ircdAmountRangeFrom, ")
+                    .append("ircd.amount_range_to as ircdAmountRangeTo, ircd.annual_interest_rate as ircdAnnualInterestRate, ")
+                    .append("curr.code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ")
+                    .append("curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
+                    .append("iri.id as iriId, ").append(" iri.entiry_type as entityType, iri.attribute_name as attributeName ,")
+                    .append(" iri.condition_type as conditionType, iri.attribute_value as attributeValue, ")
+                    .append(" iri.incentive_type as incentiveType, iri.amount as amount, ")
+                    .append("code.code_value as attributeValueDesc ").append("from ").append("m_interest_rate_slab ircd ")
+                    .append(" left join m_interest_incentives iri on iri.interest_rate_slab_id = ircd.id ")
+                    .append(" left join m_code_value code on code.id = iri.attribute_value ")
+                    .append("left join m_currency curr on ircd.currency_code= curr.code ");
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public InterestRateChartSlabData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLongDefaultToNullIfZero(rs, "ircdId");
+            // If there are not chart Slabs are associated then in
+            // InterestRateChartExtractor the chart Slabs id will be null.
+            if (id == null) { return null; }
+
+            final String description = rs.getString("ircdDescription");
+            final Integer fromPeriod = JdbcSupport.getInteger(rs, "ircdFromPeriod");
+            final Integer toPeriod = JdbcSupport.getInteger(rs, "ircdToPeriod");
+            final Integer periodTypeId = JdbcSupport.getInteger(rs, "ircdPeriodTypeId");
+            final EnumOptionData periodType = InterestRateChartEnumerations.periodType(periodTypeId);
+            final BigDecimal amountRangeFrom = rs.getBigDecimal("ircdAmountRangeFrom");
+            final BigDecimal amountRangeTo = rs.getBigDecimal("ircdAmountRangeTo");
+            final BigDecimal annualInterestRate = rs.getBigDecimal("ircdAnnualInterestRate");
+
+            // currency Slabs
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            // currency
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return InterestRateChartSlabData.instance(id, description, periodType, fromPeriod, toPeriod, amountRangeFrom, amountRangeTo,
+                    annualInterestRate, currency);
+        }
+
+    }
+
+    private static final class InterestRateChartSlabExtractor implements ResultSetExtractor<InterestRateChartSlabData> {
+
+        InterestRateChartSlabsMapper chartSlabsMapper = new InterestRateChartSlabsMapper();
+        InterestIncentiveMapper incentiveMapper = new InterestIncentiveMapper();
+
+        private final String schemaSql;
+
+        @SuppressWarnings("unused")
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        private InterestRateChartSlabExtractor() {
+            this.schemaSql = chartSlabsMapper.schema();
+        }
+
+        @Override
+        public InterestRateChartSlabData extractData(ResultSet rs) throws SQLException, DataAccessException {
+
+            InterestRateChartSlabData chartSlabData = null;
+            Long interestRateChartSlabId = null;
+            int ircIndex = 0;// Interest rate chart index
+            int ircdIndex = 0;// Interest rate chart Slabs index
+            rs.previous();
+            while (rs.next()) {
+                Long tempIrcdId = rs.getLong("ircdId");
+                if (interestRateChartSlabId == null || interestRateChartSlabId.equals(tempIrcdId)) {
+                    if (chartSlabData == null) {
+                        interestRateChartSlabId = tempIrcdId;
+                        chartSlabData = chartSlabsMapper.mapRow(rs, ircIndex++);
+                    }
+                    final InterestIncentiveData incentiveData = incentiveMapper.mapRow(rs, ircdIndex++);
+                    if (incentiveData != null) {
+                        chartSlabData.addIncentives(incentiveData);
+                    }
+                } else {
+                    rs.previous();
+                    break;
+                }
+
+            }
+            return chartSlabData;
+        }
+
+        private static final class InterestIncentiveMapper implements RowMapper<InterestIncentiveData> {
+
+            @Override
+            public InterestIncentiveData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+                final Long id = JdbcSupport.getLongDefaultToNullIfZero(rs, "iriId");
+                // If there are not Incentive are associated then in
+                // InterestRateChartExtractor the incentive id will be null.
+                if (id == null) { return null; }
+
+                final String attributeValue = rs.getString("attributeValue");
+                String attributeValueDesc = null;
+                final Integer entityType = JdbcSupport.getInteger(rs, "entityType");
+
+                final EnumOptionData entityTypeData = InterestIncentivesEnumerations.entityType(entityType);
+
+                final Integer attributeName = JdbcSupport.getInteger(rs, "attributeName");
+                if (InterestIncentiveAttributeName.isCodeValueAttribute(InterestIncentiveAttributeName.fromInt(attributeName))) {
+                    attributeValueDesc = rs.getString("attributeValueDesc");
+                }
+                final EnumOptionData attributeNameData = InterestIncentivesEnumerations.attributeName(attributeName);
+                final Integer conditionType = JdbcSupport.getInteger(rs, "conditionType");
+                final EnumOptionData conditionTypeData = CommonEnumerations.conditionType(conditionType, "incentive");
+                final Integer incentiveType = JdbcSupport.getInteger(rs, "incentiveType");
+                final EnumOptionData incentiveTypeData = InterestIncentivesEnumerations.incentiveType(incentiveType);
+                final BigDecimal amount = rs.getBigDecimal("amount");
+
+                return InterestIncentiveData.instance(id, entityTypeData, attributeNameData, conditionTypeData, attributeValue,
+                        attributeValueDesc, incentiveTypeData, amount);
+
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabAssembler.java
new file mode 100644
index 0000000..ac6a88e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabAssembler.java
@@ -0,0 +1,150 @@
+/**
+ * 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.interestratechart.service;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.chartSlabs;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.INTERESTRATE_CHART_SLAB_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.amountRangeFromParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.amountRangeToParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.annualInterestRateParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.currencyCodeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.fromPeriodParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.periodTypeParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartSlabApiConstants.toPeriodParamName;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartRepositoryWrapper;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlabFields;
+import org.apache.fineract.portfolio.interestratechart.exception.InterestRateChartSlabNotFoundException;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class InterestRateChartSlabAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper;
+    private final InterestIncentiveAssembler incentiveAssembler;
+
+    @Autowired
+    public InterestRateChartSlabAssembler(final FromJsonHelper fromApiJsonHelper,
+            final InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper, final InterestIncentiveAssembler incentiveAssembler) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.interestRateChartRepositoryWrapper = interestRateChartRepositoryWrapper;
+        this.incentiveAssembler = incentiveAssembler;
+    }
+
+    /**
+     * Assembles a new {@link InterestRateChartSlab} from JSON Slabs passed in
+     * request
+     */
+    public InterestRateChartSlab assembleFrom(final JsonCommand command) {
+
+        final JsonElement element = command.parsedJson();
+        final JsonObject elementObject = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(elementObject);
+        final String currencyCode = command.stringValueOfParameterNamed(currencyCodeParamName);
+
+        final Long chartId = command.subentityId();// returns chart id
+
+        final InterestRateChart chart = this.interestRateChartRepositoryWrapper.findOneWithNotFoundDetection(chartId);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(INTERESTRATE_CHART_SLAB_RESOURCE_NAME);
+
+        final InterestRateChartSlab newChartSlab = assembleChartSlabs(chart, elementObject, currencyCode, locale);
+        // validate chart Slabs
+        newChartSlab.slabFields().validateChartSlabPlatformRules(command, baseDataValidator, locale);
+        chart.validateChartSlabs(baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+        return newChartSlab;
+    }
+
+    private InterestRateChartSlab assembleChartSlabs(final InterestRateChart interestRateChart, final JsonElement element,
+            final String currencyCode, final Locale locale) {
+        final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+        final Integer periodTypeId = this.fromApiJsonHelper.extractIntegerNamed(periodTypeParamName, element, locale);
+        final SavingsPeriodFrequencyType periodType = SavingsPeriodFrequencyType.fromInt(periodTypeId);
+        final Integer fromPeriod = this.fromApiJsonHelper.extractIntegerNamed(fromPeriodParamName, element, locale);
+        final Integer toPeriod = this.fromApiJsonHelper.extractIntegerNamed(toPeriodParamName, element, locale);
+        final BigDecimal amountRangeFrom = this.fromApiJsonHelper.extractBigDecimalNamed(amountRangeFromParamName, element, locale);
+        final BigDecimal amountRangeTo = this.fromApiJsonHelper.extractBigDecimalNamed(amountRangeToParamName, element, locale);
+        final BigDecimal annualInterestRate = this.fromApiJsonHelper.extractBigDecimalNamed(annualInterestRateParamName, element, locale);
+
+        final InterestRateChartSlabFields slabFields = InterestRateChartSlabFields.createNew(description, periodType, fromPeriod, toPeriod,
+                amountRangeFrom, amountRangeTo, annualInterestRate, currencyCode);
+        InterestRateChartSlab interestRateChartSlab = InterestRateChartSlab.createNew(slabFields, interestRateChart);
+        this.incentiveAssembler.assembleIncentivesFrom(element, interestRateChartSlab, locale);
+        return interestRateChartSlab;
+
+    }
+
+    public InterestRateChartSlab assembleFrom(final Long chartSlabId, final Long chartId) {
+        final InterestRateChart chart = this.interestRateChartRepositoryWrapper.findOneWithNotFoundDetection(chartId);
+        final InterestRateChartSlab interestRateChartSlab = chart.findChartSlab(chartSlabId);
+
+        if (interestRateChartSlab == null) throw new InterestRateChartSlabNotFoundException(chartSlabId, chartId);
+
+        return interestRateChartSlab;
+    }
+
+    public Collection<InterestRateChartSlab> assembleChartSlabsFrom(final JsonElement element, String currencyCode) {
+        final Collection<InterestRateChartSlab> interestRateChartSlabsSet = new HashSet<>();
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            if (topLevelJsonElement.has(chartSlabs) && topLevelJsonElement.get(chartSlabs).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(chartSlabs).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject interstRateChartElement = array.get(i).getAsJsonObject();
+                    final InterestRateChartSlab chartSlab = this.assembleChartSlabs(null, interstRateChartElement, currencyCode, locale);
+                    interestRateChartSlabsSet.add(chartSlab);
+                }
+            }
+        }
+
+        return interestRateChartSlabsSet;
+    }
+
+    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/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformService.java
new file mode 100644
index 0000000..eddc04f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformService.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.portfolio.interestratechart.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabData;
+
+public interface InterestRateChartSlabReadPlatformService {
+
+    Collection<InterestRateChartSlabData> retrieveAll(Long chartId);
+
+    InterestRateChartSlabData retrieveOne(Long chartId, Long chartSlabId);
+
+    InterestRateChartSlabData retrieveWithTemplate(InterestRateChartSlabData chartSlab);
+
+    InterestRateChartSlabData retrieveTemplate();
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformServiceImpl.java
new file mode 100644
index 0000000..fbc80a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformServiceImpl.java
@@ -0,0 +1,258 @@
+/**
+ * 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.interestratechart.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+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.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.interestratechart.data.InterestIncentiveData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabData;
+import org.apache.fineract.portfolio.interestratechart.exception.InterestRateChartSlabNotFoundException;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class InterestRateChartSlabReadPlatformServiceImpl implements InterestRateChartSlabReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final InterestRateChartSlabExtractor chartSlabExtractor = new InterestRateChartSlabExtractor();
+    private final InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService;
+    private final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+
+    @Autowired
+    public InterestRateChartSlabReadPlatformServiceImpl(PlatformSecurityContext context, final RoutingDataSource dataSource,
+            InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService,
+            final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.chartDropdownReadPlatformService = chartDropdownReadPlatformService;
+        this.interestIncentiveDropdownReadPlatformService = interestIncentiveDropdownReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+    }
+
+    @Override
+    public Collection<InterestRateChartSlabData> retrieveAll(Long chartId) {
+        this.context.authenticatedUser();
+        final String sql = "select " + this.chartSlabExtractor.schema() + " where ircd.interest_rate_chart_id = ? order by ircd.id";
+        return this.jdbcTemplate.query(sql, this.chartSlabExtractor, new Object[] { chartId });
+    }
+
+    @Override
+    public InterestRateChartSlabData retrieveOne(Long chartId, Long chartSlabId) {
+        this.context.authenticatedUser();
+        final String sql = "select " + this.chartSlabExtractor.schema() + " where irc.id = ? order by ircd.id asc";
+        Collection<InterestRateChartSlabData> chartDatas = this.jdbcTemplate.query(sql, this.chartSlabExtractor, new Object[] {
+                chartSlabId, chartId });
+        if (chartDatas == null || chartDatas.isEmpty()) { throw new InterestRateChartSlabNotFoundException(chartSlabId, chartId); }
+
+        return chartDatas.iterator().next();
+    }
+
+    @Override
+    public InterestRateChartSlabData retrieveWithTemplate(InterestRateChartSlabData chartSlab) {
+        final List<CodeValueData> genderOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.GENDER));
+
+        final List<CodeValueData> clientTypeOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_TYPE));
+
+        final List<CodeValueData> clientClassificationOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_CLASSIFICATION));
+
+        return InterestRateChartSlabData.withTemplate(chartSlab, this.chartDropdownReadPlatformService.retrievePeriodTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveEntityTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveAttributeNameOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveConditionTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveIncentiveTypeOptions(), genderOptions, clientTypeOptions,
+                clientClassificationOptions);
+    }
+
+    @Override
+    public InterestRateChartSlabData retrieveTemplate() {
+        final List<CodeValueData> genderOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.GENDER));
+
+        final List<CodeValueData> clientTypeOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_TYPE));
+
+        final List<CodeValueData> clientClassificationOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_CLASSIFICATION));
+        return InterestRateChartSlabData.template(this.chartDropdownReadPlatformService.retrievePeriodTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveEntityTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveAttributeNameOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveConditionTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveIncentiveTypeOptions(), genderOptions, clientTypeOptions,
+                clientClassificationOptions);
+    }
+
+    private static final class InterestRateChartSlabsMapper implements RowMapper<InterestRateChartSlabData> {
+
+        private final String schemaSql;
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        private InterestRateChartSlabsMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder
+                    .append("ircd.id as ircdId, ircd.description as ircdDescription, ircd.period_type_enum ircdPeriodTypeId, ")
+                    .append("ircd.from_period as ircdFromPeriod, ircd.to_period as ircdToPeriod, ircd.amount_range_from as ircdAmountRangeFrom, ")
+                    .append("ircd.amount_range_to as ircdAmountRangeTo, ircd.annual_interest_rate as ircdAnnualInterestRate, ")
+                    .append("curr.code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ")
+                    .append("curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
+                    .append("iri.id as iriId, ").append(" iri.entiry_type as entityType, iri.attribute_name as attributeName ,")
+                    .append(" iri.condition_type as conditionType, iri.attribute_value as attributeValue, ")
+                    .append(" iri.incentive_type as incentiveType, iri.amount as amount, ")
+                    .append("code.code_value as attributeValueDesc ").append("from ").append("m_interest_rate_slab ircd ")
+                    .append(" left join m_interest_incentives iri on iri.interest_rate_slab_id = ircd.id ")
+                    .append(" left join m_code_value code on code.id = iri.attribute_value ")
+                    .append("left join m_currency curr on ircd.currency_code= curr.code ");
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public InterestRateChartSlabData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLongDefaultToNullIfZero(rs, "ircdId");
+            // If there are not chart Slabs are associated then in
+            // InterestRateChartExtractor the chart Slabs id will be null.
+            if (id == null) { return null; }
+
+            final String description = rs.getString("ircdDescription");
+            final Integer fromPeriod = JdbcSupport.getInteger(rs, "ircdFromPeriod");
+            final Integer toPeriod = JdbcSupport.getInteger(rs, "ircdToPeriod");
+            final Integer periodTypeId = JdbcSupport.getInteger(rs, "ircdPeriodTypeId");
+            final EnumOptionData periodType = InterestRateChartEnumerations.periodType(periodTypeId);
+            final BigDecimal amountRangeFrom = rs.getBigDecimal("ircdAmountRangeFrom");
+            final BigDecimal amountRangeTo = rs.getBigDecimal("ircdAmountRangeTo");
+            final BigDecimal annualInterestRate = rs.getBigDecimal("ircdAnnualInterestRate");
+
+            // currency Slabs
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            // currency
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return InterestRateChartSlabData.instance(id, description, periodType, fromPeriod, toPeriod, amountRangeFrom, amountRangeTo,
+                    annualInterestRate, currency);
+        }
+
+    }
+
+    private static final class InterestRateChartSlabExtractor implements ResultSetExtractor<Collection<InterestRateChartSlabData>> {
+
+        InterestRateChartSlabsMapper chartSlabsMapper = new InterestRateChartSlabsMapper();
+        InterestIncentiveMapper incentiveMapper = new InterestIncentiveMapper();
+
+        private final String schemaSql;
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        private InterestRateChartSlabExtractor() {
+            this.schemaSql = chartSlabsMapper.schema();
+        }
+
+        @Override
+        public Collection<InterestRateChartSlabData> extractData(ResultSet rs) throws SQLException, DataAccessException {
+
+            List<InterestRateChartSlabData> chartDataList = new ArrayList<>();
+
+            InterestRateChartSlabData chartSlabData = null;
+            Long interestRateChartSlabId = null;
+            int ircIndex = 0;// Interest rate chart index
+            int ircdIndex = 0;// Interest rate chart Slabs index
+
+            while (rs.next()) {
+                Long tempIrcdId = rs.getLong("ircdId");
+                if (chartSlabData == null || (interestRateChartSlabId != null && !interestRateChartSlabId.equals(tempIrcdId))) {
+                    interestRateChartSlabId = tempIrcdId;
+                    chartSlabData = chartSlabsMapper.mapRow(rs, ircIndex++);
+                    chartDataList.add(chartSlabData);
+                }
+                final InterestIncentiveData incentiveData = incentiveMapper.mapRow(rs, ircdIndex++);
+                if (incentiveData != null) {
+                    chartSlabData.addIncentives(incentiveData);
+                }
+            }
+            return chartDataList;
+        }
+    }
+
+    private static final class InterestIncentiveMapper implements RowMapper<InterestIncentiveData> {
+
+        @Override
+        public InterestIncentiveData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLongDefaultToNullIfZero(rs, "iriId");
+            // If there are not Incentive are associated then in
+            // InterestRateChartExtractor the incentive id will be null.
+            if (id == null) { return null; }
+
+            final String attributeValue = rs.getString("attributeValue");
+            String attributeValueDesc = null;
+            final Integer entityType = JdbcSupport.getInteger(rs, "entityType");
+            final EnumOptionData entityTypeData = InterestIncentivesEnumerations.entityType(entityType);
+
+            final Integer attributeName = JdbcSupport.getInteger(rs, "attributeName");
+            if (InterestIncentiveAttributeName.isCodeValueAttribute(InterestIncentiveAttributeName.fromInt(attributeName))) {
+                attributeValueDesc = rs.getString("attributeValueDesc");
+            }
+            final EnumOptionData attributeNameData = InterestIncentivesEnumerations.attributeName(attributeName);
+            final Integer conditionType = JdbcSupport.getInteger(rs, "conditionType");
+            final EnumOptionData conditionTypeData = CommonEnumerations.conditionType(conditionType, "incentive");
+            final Integer incentiveType = JdbcSupport.getInteger(rs, "incentiveType");
+            final EnumOptionData incentiveTypeData = InterestIncentivesEnumerations.incentiveType(incentiveType);
+            final BigDecimal amount = rs.getBigDecimal("amount");
+
+            return InterestIncentiveData.instance(id, entityTypeData, attributeNameData, conditionTypeData, attributeValue,
+                    attributeValueDesc, incentiveTypeData, amount);
+
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformService.java
new file mode 100644
index 0000000..372e03c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.interestratechart.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface InterestRateChartSlabWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    CommandProcessingResult update(Long chartSlabId, Long interestChartId, JsonCommand command);
+
+    CommandProcessingResult deleteChartSlab(Long chartSlabId, Long interestRateChartId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..2b723a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,119 @@
+/**
+ * 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.interestratechart.service;
+
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartRepositoryWrapper;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabDataValidator;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabRepository;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab;
+import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl implements InterestRateChartSlabWritePlatformService {
+
+    @SuppressWarnings("unused")
+    private final static Logger logger = LoggerFactory.getLogger(InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl.class);
+    @SuppressWarnings("unused")
+    private final PlatformSecurityContext context;
+    private final InterestRateChartSlabDataValidator interestRateChartSlabDataValidator;
+    @SuppressWarnings("unused")
+    private final InterestRateChartAssembler interestRateChartAssembler;
+    private final InterestRateChartSlabAssembler interestRateChartSlabAssembler;
+    @SuppressWarnings("unused")
+    private final InterestRateChartRepositoryWrapper interestRateChartRepository;
+    private final InterestRateChartSlabRepository chartSlabRepository;
+    @SuppressWarnings("unused")
+    private final SavingsProductRepository savingsProductRepository;
+
+    @Autowired
+    public InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl(PlatformSecurityContext context,
+            final InterestRateChartSlabDataValidator interestRateChartSlabDataValidator,
+            final InterestRateChartAssembler interestRateChartAssembler,
+            final InterestRateChartRepositoryWrapper interestRateChartRepository, final SavingsProductRepository savingsProductRepository,
+            final InterestRateChartSlabRepository chartSlabRepository,
+            final InterestRateChartSlabAssembler interestRateChartSlabAssembler) {
+        this.context = context;
+        this.interestRateChartSlabDataValidator = interestRateChartSlabDataValidator;
+        this.interestRateChartAssembler = interestRateChartAssembler;
+        this.interestRateChartRepository = interestRateChartRepository;
+        this.savingsProductRepository = savingsProductRepository;
+        this.chartSlabRepository = chartSlabRepository;
+        this.interestRateChartSlabAssembler = interestRateChartSlabAssembler;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult create(JsonCommand command) {
+        this.interestRateChartSlabDataValidator.validateCreate(command.json());
+
+        final InterestRateChartSlab interestRateChartSlab = this.interestRateChartSlabAssembler.assembleFrom(command);
+
+        this.chartSlabRepository.save(interestRateChartSlab);
+
+        final Long interestRateChartId = interestRateChartSlab.getId();
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(interestRateChartId) //
+                .build();
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult update(Long chartSlabId, Long interestRateChartId, JsonCommand command) {
+        this.interestRateChartSlabDataValidator.validateUpdate(command.json());
+        final Map<String, Object> changes = new LinkedHashMap<>(20);
+        final InterestRateChartSlab updateChartSlabs = this.interestRateChartSlabAssembler.assembleFrom(chartSlabId,
+                interestRateChartId);
+        final Locale locale = command.extractLocale();
+        updateChartSlabs.update(command, changes,locale);
+
+        this.chartSlabRepository.saveAndFlush(updateChartSlabs);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(interestRateChartId) //
+                .with(changes).build();
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult deleteChartSlab(Long chartSlabId, Long interestRateChartId) {
+        final InterestRateChartSlab deleteChartSlabs = this.interestRateChartSlabAssembler.assembleFrom(chartSlabId,
+                interestRateChartId);
+        this.chartSlabRepository.delete(deleteChartSlabs);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(chartSlabId) //
+                .build();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformService.java
new file mode 100644
index 0000000..bb401d8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.interestratechart.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface InterestRateChartWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    CommandProcessingResult update(Long interestChartId, JsonCommand command);
+
+    CommandProcessingResult deleteChart(Long interestChartId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..79ce96c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,105 @@
+/**
+ * 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.interestratechart.service;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartDataValidator;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartRepositoryWrapper;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class InterestRateChartWritePlatformServiceJpaRepositoryImpl implements InterestRateChartWritePlatformService {
+
+    @SuppressWarnings("unused")
+    private final static Logger logger = LoggerFactory.getLogger(InterestRateChartWritePlatformServiceJpaRepositoryImpl.class);
+    @SuppressWarnings("unused")
+    private final PlatformSecurityContext context;
+    private final InterestRateChartDataValidator interestRateChartDataValidator;
+    private final InterestRateChartAssembler interestRateChartAssembler;
+    private final InterestRateChartRepositoryWrapper interestRateChartRepository;
+
+    @Autowired
+    public InterestRateChartWritePlatformServiceJpaRepositoryImpl(PlatformSecurityContext context,
+            final InterestRateChartDataValidator interestRateChartDataValidator,
+            final InterestRateChartAssembler interestRateChartAssembler,
+            final InterestRateChartRepositoryWrapper interestRateChartRepository) {
+        this.context = context;
+        this.interestRateChartDataValidator = interestRateChartDataValidator;
+        this.interestRateChartAssembler = interestRateChartAssembler;
+        this.interestRateChartRepository = interestRateChartRepository;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult create(JsonCommand command) {
+        this.interestRateChartDataValidator.validateForCreate(command.json());
+
+        final InterestRateChart interestRateChart = this.interestRateChartAssembler.assembleFrom(command);
+
+        this.interestRateChartRepository.saveAndFlush(interestRateChart);
+
+        final Long interestRateChartId = interestRateChart.getId();
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(interestRateChartId) //
+                .build();
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult update(Long interestRateChartId, JsonCommand command) {
+        this.interestRateChartDataValidator.validateUpdate(command.json());
+        final Map<String, Object> changes = new LinkedHashMap<>(20);
+        final InterestRateChart interestRateChart = this.interestRateChartAssembler.assembleFrom(interestRateChartId);
+
+        interestRateChart.update(command, changes);
+
+        this.interestRateChartRepository.saveAndFlush(interestRateChart);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(interestRateChartId) //
+                .with(changes).build();
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult deleteChart(Long chartId) {
+        final InterestRateChart chart = this.interestRateChartRepository.findOneWithNotFoundDetection(chartId);
+        // validate if chart is associated with any accounts
+
+        this.interestRateChartRepository.delete(chart);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(chartId) //
+                .build();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/BulkLoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/BulkLoansApiResource.java
new file mode 100644
index 0000000..5836f0f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/BulkLoansApiResource.java
@@ -0,0 +1,129 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.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;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.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.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.organisation.staff.data.BulkTransferLoanOfficerData;
+import org.apache.fineract.organisation.staff.data.StaffAccountSummaryCollectionData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.BulkLoansReadPlatformService;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/loans/loanreassignment")
+@Component
+@Scope("singleton")
+public class BulkLoansApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("officeId", "fromLoanOfficerId",
+            "assignmentDate", "officeOptions", "loanOfficerOptions", "accountSummaryCollection"));
+
+    private final String resourceNameForPermissions = "LOAN";
+
+    private final PlatformSecurityContext context;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final BulkLoansReadPlatformService bulkLoansReadPlatformService;
+    private final DefaultToApiJsonSerializer<BulkTransferLoanOfficerData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public BulkLoansApiResource(final PlatformSecurityContext context, final StaffReadPlatformService staffReadPlatformService,
+            final OfficeReadPlatformService officeReadPlatformService, final BulkLoansReadPlatformService bulkLoansReadPlatformService,
+            final DefaultToApiJsonSerializer<BulkTransferLoanOfficerData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.bulkLoansReadPlatformService = bulkLoansReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String loanReassignmentTemplate(@QueryParam("officeId") final Long officeId,
+            @QueryParam("fromLoanOfficerId") final Long loanOfficerId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<OfficeData> offices = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+        Collection<StaffData> loanOfficers = null;
+        StaffAccountSummaryCollectionData staffAccountSummaryCollectionData = null;
+
+        if (officeId != null) {
+            loanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersInOfficeById(officeId);
+        }
+
+        if (loanOfficerId != null) {
+            staffAccountSummaryCollectionData = this.bulkLoansReadPlatformService.retrieveLoanOfficerAccountSummary(loanOfficerId);
+        }
+
+        final BulkTransferLoanOfficerData loanReassignmentData = BulkTransferLoanOfficerData.templateForBulk(officeId, loanOfficerId,
+                new LocalDate(), offices, loanOfficers, staffAccountSummaryCollectionData);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, loanReassignmentData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String loanReassignment(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().assignLoanOfficersInBulk().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
new file mode 100644
index 0000000..61ea803
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.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.portfolio.loanaccount.api;
+
+public interface LoanApiConstants {
+
+    public static final String emiAmountParameterName = "fixedEmiAmount";
+    public static final String maxOutstandingBalanceParameterName = "maxOutstandingLoanBalance";
+    public static final String disbursementDataParameterName = "disbursementData";
+    public static final String disbursementDateParameterName = "expectedDisbursementDate";
+    public static final String disbursementPrincipalParameterName = "principal";
+    public static final String updatedDisbursementDateParameterName = "updatedExpectedDisbursementDate";
+    public static final String updatedDisbursementPrincipalParameterName = "updatedPrincipal";
+    public static final String disbursementIdParameterName = "id";
+    public static final String loanChargeIdParameterName = "loanChargeId";
+    public static final String principalDisbursedParameterName = "transactionAmount";
+    public static final String chargesParameterName = "charges";
+
+    public static final String approvedLoanAmountParameterName = "approvedLoanAmount";
+    public static final String approvedOnDateParameterName = "approvedOnDate";
+    public static final String noteParameterName = "note";
+    public static final String localeParameterName = "locale";
+    public static final String dateFormatParameterName = "dateFormat";
+    public static final String rejectedOnDateParameterName = "rejectedOnDate";
+    public static final String withdrawnOnDateParameterName = "withdrawnOnDate";
+
+    // Interest recalculation related
+    public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled";
+    public static final String daysInYearTypeParameterName = "daysInYearType";
+    public static final String daysInMonthTypeParameterName = "daysInMonthType";
+    public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod";
+    public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod";
+
+    // Floating interest rate related
+    public static final String interestRateDifferentialParameterName = "interestRateDifferential";
+    public static final String isFloatingInterestRateParameterName = "isFloatingInterestRate";
+
+    // Error codes
+    public static final String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE = "loancharge.with.calculation.type.interest.not.allowed";
+    public static final String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE = "loancharge.with.calculation.type.principal.not.allowed";
+    public static final String DISBURSEMENT_DATE_START_WITH_ERROR = "first.disbursement.date.must.start.with.expected.disbursement.date";
+    public static final String PRINCIPAL_AMOUNT_SHOULD_BE_SAME = "sum.of.multi.disburse.amounts.must.equal.with.total.principal";
+    public static final String DISBURSEMENT_DATE_UNIQUE_ERROR = "disbursement.date.must.be.unique.for.tranches";
+    public static final String ALREADY_DISBURSED = "can.not.change.disbursement.date";
+    public static final String APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES = "sum.of.multi.disburse.amounts.must.be.equal.to.or.lesser.than.approved.principal";
+    public static final String DISBURSEMENT_DATES_NOT_IN_ORDER = "disbursements.should.be.ordered.based.on.their.disbursement.dates";
+    public static final String DISBURSEMENT_DATE_BEFORE_ERROR = "disbursement.date.of.tranche.cannot.be.before.expected.disbursement.date";
+
+    public static final String isFloatingInterestRate = "isFloatingInterestRate";
+    public static final String interestRateDifferential = "interestRateDifferential";
+
+    public static final String exceptionParamName = "exceptions";
+    public static final String modifiedinstallmentsParamName = "modifiedinstallments";
+    public static final String newinstallmentsParamName = "newinstallments";
+    public static final String deletedinstallmentsParamName = "deletedinstallments";
+    public static final String dueDateParamName = "dueDate";
+    public static final String modifiedDueDateParamName = "modifiedDueDate";
+    public static final String principalParamName = "principal";
+    public static final String installmentAmountParamName = "installmentAmount";
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java
new file mode 100644
index 0000000..b5ebd3d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java
@@ -0,0 +1,214 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.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.QueryParam;
+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.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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/loans/{loanId}/charges")
+@Component
+@Scope("singleton")
+public class LoanChargesApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList("id", "chargeId", "name", "penalty", "chargeTimeType", "dueAsOfDate", "chargeCalculationType", "percentage",
+                    "amountPercentageAppliedTo", "currency", "amountWaived", "amountWrittenOff", "amountOutstanding", "amountOrPercentage",
+                    "amount", "amountPaid", "chargeOptions", "installmentChargeData"));
+
+    private final String resourceNameForPermissions = "LOAN";
+
+    private final PlatformSecurityContext context;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final LoanChargeReadPlatformService loanChargeReadPlatformService;
+    private final DefaultToApiJsonSerializer<LoanChargeData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public LoanChargesApiResource(final PlatformSecurityContext context, final ChargeReadPlatformService chargeReadPlatformService,
+            final LoanChargeReadPlatformService loanChargeReadPlatformService,
+            final DefaultToApiJsonSerializer<LoanChargeData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.loanChargeReadPlatformService = loanChargeReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllLoanCharges(@PathParam("loanId") final Long loanId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<LoanChargeData> loanCharges = this.loanChargeReadPlatformService.retrieveLoanCharges(loanId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, loanCharges, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@PathParam("loanId") final Long loanId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId,
+                new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
+        final LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, loanChargeTemplate, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final LoanChargeData loanCharge = this.loanChargeReadPlatformService.retrieveLoanChargeDetails(loanChargeId, loanId);
+
+        final Collection<LoanInstallmentChargeData> installmentChargeData = this.loanChargeReadPlatformService
+                .retrieveInstallmentLoanCharges(loanChargeId, true);
+
+        final LoanChargeData loanChargeData = new LoanChargeData(loanCharge, installmentChargeData);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, loanChargeData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String executeLoanCharge(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "pay")) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().payLoanCharge(loanId, null).withJson(apiRequestBodyAsJson)
+                    .build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().createLoanCharge(loanId).withJson(apiRequestBodyAsJson)
+                    .build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateLoanCharge(loanId, loanChargeId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String executeLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
+            @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+        CommandProcessingResult result = null;
+        if (is(commandParam, "waive")) {
+            final CommandWrapper commandRequest = builder.waiveLoanCharge(loanId, loanChargeId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "pay")) {
+            final CommandWrapper commandRequest = builder.payLoanCharge(loanId, loanChargeId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam);
+        }
+        if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{chargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteLoanCharge(loanId, loanChargeId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java
new file mode 100644
index 0000000..fe81e38
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java
@@ -0,0 +1,120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/loans/{loanId}/disbursements")
+@Component
+@Scope("singleton")
+public class LoanDisbursementDetailApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "expectedDisbursementDate",
+            "actualDisbursementDate", "principal", "approvedPrincipal"));
+
+    private final String resourceNameForPermissions = "LOAN";
+
+    private final DefaultToApiJsonSerializer<DisbursementData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final PlatformSecurityContext context;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final LoanReadPlatformService loanReadPlatformService;
+
+    @Autowired
+    public LoanDisbursementDetailApiResource(final DefaultToApiJsonSerializer<DisbursementData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, final PlatformSecurityContext context,
+            final ApiRequestParameterHelper apiRequestParameterHelper, final LoanReadPlatformService loanReadPlatformService) {
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.context = context;
+        this.loanReadPlatformService = loanReadPlatformService;
+    }
+
+    @PUT
+    @Path("{disbursementId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateDisbursementDate(@PathParam("loanId") final Long loanId, @PathParam("disbursementId") final Long disbursementId,
+            final String apiRequestBodyAsJson) {
+    	
+    	final CommandWrapper commandRequest = new CommandWrapperBuilder().updateDisbusementDate(loanId, disbursementId)
+                    .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+   
+    @PUT
+    @Path("editDisbursements")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String addAndDeleteDisbursementDetail(@PathParam("loanId") final Long loanId, final String apiRequestBodyAsJson) {
+
+    	CommandWrapper commandRequest = new CommandWrapperBuilder().addAndDeleteDisbursementDetails(loanId)
+                    .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+    
+    @GET
+    @Path("{disbursementId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retriveDetail(@PathParam("loanId") final Long loanId, @PathParam("disbursementId") final Long disbursementId,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final DisbursementData disbursementData = this.loanReadPlatformService.retrieveLoanDisbursementDetail(loanId, disbursementId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, disbursementData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanScheduleApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanScheduleApiResource.java
new file mode 100644
index 0000000..3d107f1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanScheduleApiResource.java
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.api;
+
+import java.util.HashSet;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+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.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/loans/{loanId}/schedule")
+@Component
+@Scope("singleton")
+public class LoanScheduleApiResource {
+
+    private final String resourceNameForPermissions = "LOAN";
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<LoanScheduleData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final LoanScheduleCalculationPlatformService calculationPlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public LoanScheduleApiResource(final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<LoanScheduleData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final LoanScheduleCalculationPlatformService calculationPlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.calculationPlatformService = calculationPlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String calculateLoanScheduleOrSubmitVariableSchedule(@PathParam("loanId") final Long loanId,
+            @QueryParam("command") final String commandParam, @Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
+
+        CommandWrapper commandRequest = null;
+        if (is(commandParam, "calculateLoanSchedule")) {
+            this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+            final LoanScheduleData loanSchedule = this.calculationPlatformService.generateLoanScheduleForVariableInstallmentRequest(loanId,
+                    apiRequestBodyAsJson);
+
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+            return this.toApiJsonSerializer.serialize(settings, loanSchedule, new HashSet<String>());
+        } else if (is(commandParam, "addVariations")) {
+            commandRequest = new CommandWrapperBuilder().createScheduleExceptions(loanId).withJson(apiRequestBodyAsJson).build();
+        } else if (is(commandParam, "deleteVariations")) {
+            commandRequest = new CommandWrapperBuilder().deleteScheduleExceptions(loanId).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);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
new file mode 100755
index 0000000..04603c4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -0,0 +1,214 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.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;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.journalentry.api.DateParam;
+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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/loans/{loanId}/transactions")
+@Component
+@Scope("singleton")
+public class LoanTransactionsApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount",
+            "externalId"));
+
+    private final String resourceNameForPermissions = "LOAN";
+
+    private final PlatformSecurityContext context;
+    private final LoanReadPlatformService loanReadPlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final DefaultToApiJsonSerializer<LoanTransactionData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public LoanTransactionsApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final DefaultToApiJsonSerializer<LoanTransactionData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.context = context;
+        this.loanReadPlatformService = loanReadPlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTransactionTemplate(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo, @QueryParam("dateFormat") final String dateFormat,
+            @QueryParam("transactionDate") final DateParam transactionDateParam, @QueryParam("locale") final String locale) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        LoanTransactionData transactionData = null;
+        if (is(commandParam, "repayment")) {
+            transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(loanId);
+        } else if (is(commandParam, "waiveinterest")) {
+            transactionData = this.loanReadPlatformService.retrieveWaiveInterestDetails(loanId);
+        } else if (is(commandParam, "writeoff")) {
+            transactionData = this.loanReadPlatformService.retrieveLoanWriteoffTemplate(loanId);
+        } else if (is(commandParam, "close-rescheduled")) {
+            transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
+        } else if (is(commandParam, "close")) {
+            transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
+        } else if (is(commandParam, "disburse")) {
+            transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
+        } else if (is(commandParam, "disburseToSavings")) {
+            transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false);
+        } else if (is(commandParam, "recoverypayment")) {
+            transactionData = this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(loanId);
+        } else if (is(commandParam, "prepayLoan")) {
+            LocalDate transactionDate = null;
+            if (transactionDateParam == null) {
+                transactionDate = DateUtils.getLocalDateOfTenant();
+            } else {
+                transactionDate = LocalDate.fromDateFields(transactionDateParam.getDate("transactionDate", dateFormat, locale));
+            }
+            transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanId, transactionDate);
+        } else if (is(commandParam, "refundbycash")) {
+            transactionData = this.loanReadPlatformService.retrieveRefundByCashTemplate(loanId);
+        } else if (is(commandParam, "refundbytransfer")) {
+            transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam);
+        }
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, transactionData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTransaction(@PathParam("loanId") final Long loanId, @PathParam("transactionId") final Long transactionId,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        LoanTransactionData transactionData = this.loanReadPlatformService.retrieveLoanTransaction(loanId, transactionId);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+            transactionData = LoanTransactionData.templateOnTop(transactionData, paymentTypeOptions);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, transactionData, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String executeLoanTransaction(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "repayment")) {
+            final CommandWrapper commandRequest = builder.loanRepaymentTransaction(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "waiveinterest")) {
+            final CommandWrapper commandRequest = builder.waiveInterestPortionTransaction(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "writeoff")) {
+            final CommandWrapper commandRequest = builder.writeOffLoanTransaction(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "close-rescheduled")) {
+            final CommandWrapper commandRequest = builder.closeLoanAsRescheduledTransaction(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "close")) {
+            final CommandWrapper commandRequest = builder.closeLoanTransaction(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "undowriteoff")) {
+            final CommandWrapper commandRequest = builder.undoWriteOffLoanTransaction(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "recoverypayment")) {
+            final CommandWrapper commandRequest = builder.loanRecoveryPaymentTransaction(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "refundByCash")) {
+            final CommandWrapper commandRequest = builder.refundLoanTransactionByCash(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String adjustLoanTransaction(@PathParam("loanId") final Long loanId, @PathParam("transactionId") final Long transactionId,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+        final CommandWrapper commandRequest = builder.adjustTransaction(loanId, transactionId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
new file mode 100644
index 0000000..86afeab
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
@@ -0,0 +1,703 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.api;
+
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestType;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+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.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
+import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformService;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+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.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.collateral.data.CollateralData;
+import org.apache.fineract.portfolio.collateral.service.CollateralReadPlatformService;
+import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
+import org.apache.fineract.portfolio.fund.data.FundData;
+import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
+import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanTemplateTypeRequiredException;
+import org.apache.fineract.portfolio.loanaccount.exception.NotSupportedLoanTemplateTypeException;
+import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData;
+import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+import org.apache.fineract.portfolio.note.data.NoteData;
+import org.apache.fineract.portfolio.note.domain.NoteType;
+import org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import com.google.gson.JsonElement;
+
+@Path("/loans")
+@Component
+@Scope("singleton")
+public class LoansApiResource {
+
+    private final Set<String> LOAN_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "accountNo", "status", "externalId", "clientId",
+            "group", "loanProductId", "loanProductName", "loanProductDescription", "isLoanProductLinkedToFloatingRate", "fundId",
+            "fundName", "loanPurposeId", "loanPurposeName", "loanOfficerId", "loanOfficerName", "currency", "principal", "totalOverpaid",
+            "inArrearsTolerance", "termFrequency", "termPeriodFrequencyType", "numberOfRepayments", "repaymentEvery",
+            "interestRatePerPeriod", "annualInterestRate", "repaymentFrequencyType", "transactionProcessingStrategyId",
+            "transactionProcessingStrategyName", "interestRateFrequencyType", "amortizationType", "interestType",
+            "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "expectedFirstRepaymentOnDate",
+            "graceOnPrincipalPayment", "graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "timeline",
+            "totalFeeChargesAtDisbursement", "summary", "repaymentSchedule", "transactions", "charges", "collateral", "guarantors",
+            "meeting", "productOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions",
+            "repaymentFrequencyTypeOptions", "repaymentFrequencyNthDayTypeOptions", "repaymentFrequencyDaysOfWeekTypeOptions",
+            "termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions",
+            "loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions",
+            "syncDisbursementWithMeeting", "loanCounter", "loanProductCounter", "notes", "accountLinkingOptions", "linkedAccount",
+            "interestRateDifferential", "isFloatingInterestRate", "interestRatesPeriods"));
+
+    private final Set<String> LOAN_APPROVAL_DATA_PARAMETERS = new HashSet<>(Arrays.asList("approvalDate", "approvalAmount"));
+    private final String resourceNameForPermissions = "LOAN";
+
+    private final PlatformSecurityContext context;
+    private final LoanReadPlatformService loanReadPlatformService;
+    private final LoanProductReadPlatformService loanProductReadPlatformService;
+    private final LoanDropdownReadPlatformService dropdownReadPlatformService;
+    private final FundReadPlatformService fundReadPlatformService;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final LoanChargeReadPlatformService loanChargeReadPlatformService;
+    private final CollateralReadPlatformService loanCollateralReadPlatformService;
+    private final LoanScheduleCalculationPlatformService calculationPlatformService;
+    private final GuarantorReadPlatformService guarantorReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+    private final GroupReadPlatformService groupReadPlatformService;
+    private final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<LoanApprovalData> loanApprovalDataToApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final FromJsonHelper fromJsonHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final CalendarReadPlatformService calendarReadPlatformService;
+    private final NoteReadPlatformServiceImpl noteReadPlatformService;
+    private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService;
+    private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
+    private final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService;
+
+    @Autowired
+    public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
+            final LoanProductReadPlatformService loanProductReadPlatformService,
+            final LoanDropdownReadPlatformService dropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService,
+            final ChargeReadPlatformService chargeReadPlatformService, final LoanChargeReadPlatformService loanChargeReadPlatformService,
+            final CollateralReadPlatformService loanCollateralReadPlatformService,
+            final LoanScheduleCalculationPlatformService calculationPlatformService,
+            final GuarantorReadPlatformService guarantorReadPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
+            final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer,
+            final DefaultToApiJsonSerializer<LoanApprovalData> loanApprovalDataToApiJsonSerializer,
+            final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper, final FromJsonHelper fromJsonHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformServiceImpl noteReadPlatformService,
+            final PortfolioAccountReadPlatformService portfolioAccountReadPlatformServiceImpl,
+            final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
+            final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService) {
+        this.context = context;
+        this.loanReadPlatformService = loanReadPlatformService;
+        this.loanProductReadPlatformService = loanProductReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.fundReadPlatformService = fundReadPlatformService;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.loanChargeReadPlatformService = loanChargeReadPlatformService;
+        this.loanCollateralReadPlatformService = loanCollateralReadPlatformService;
+        this.calculationPlatformService = calculationPlatformService;
+        this.guarantorReadPlatformService = guarantorReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+        this.groupReadPlatformService = groupReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.loanApprovalDataToApiJsonSerializer = loanApprovalDataToApiJsonSerializer;
+        this.loanScheduleToApiJsonSerializer = loanScheduleToApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.fromJsonHelper = fromJsonHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.calendarReadPlatformService = calendarReadPlatformService;
+        this.noteReadPlatformService = noteReadPlatformService;
+        this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformServiceImpl;
+        this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
+        this.loanScheduleHistoryReadPlatformService = loanScheduleHistoryReadPlatformService;
+    }
+
+    /*
+     * This template API is used for loan approval, ideally this should be
+     * invoked on loan that are pending for approval. But system does not
+     * validate the status of the loan, it returns the template irrespective of
+     * loan status
+     */
+
+    @GET
+    @Path("{loanId}/template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveApprovalTemplate(@PathParam("loanId") final Long loanId, @QueryParam("templateType") final String templateType,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        LoanApprovalData loanApprovalTemplate = null;
+
+        if (templateType == null) {
+            final String errorMsg = "Loan template type must be provided";
+            throw new LoanTemplateTypeRequiredException(errorMsg);
+        } else if (templateType.equals("approval")) {
+            loanApprovalTemplate = this.loanReadPlatformService.retrieveApprovalTemplate(loanId);
+        }
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.loanApprovalDataToApiJsonSerializer.serialize(settings, loanApprovalTemplate, this.LOAN_APPROVAL_DATA_PARAMETERS);
+
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId,
+            @QueryParam("productId") final Long productId, @QueryParam("templateType") final String templateType,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
+            @DefaultValue("false") @QueryParam("activeOnly") final boolean onlyActive, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        // template
+        final Collection<LoanProductData> productOptions = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup(onlyActive);
+
+        // options
+        Collection<StaffData> allowedLoanOfficers = null;
+        Collection<CodeValueData> loanCollateralOptions = null;
+        Collection<CalendarData> calendarOptions = null;
+        LoanAccountData newLoanAccount = null;
+        Long officeId = null;
+
+        if (productId != null) {
+            newLoanAccount = this.loanReadPlatformService.retrieveLoanProductDetailsTemplate(productId, clientId, groupId);
+        }
+
+        if (templateType == null) {
+            final String errorMsg = "Loan template type must be provided";
+            throw new LoanTemplateTypeRequiredException(errorMsg);
+        } else if (templateType.equals("collateral")) {
+            loanCollateralOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanCollateral");
+            newLoanAccount = LoanAccountData.collateralTemplate(loanCollateralOptions);
+        } else {
+            // for JLG loan both client and group details are required
+            if (templateType.equals("individual") || templateType.equals("jlg")) {
+
+                final LoanAccountData loanAccountClientDetails = this.loanReadPlatformService.retrieveClientDetailsTemplate(clientId);
+
+                officeId = loanAccountClientDetails.officeId();
+
+                newLoanAccount = newLoanAccount == null ? loanAccountClientDetails : LoanAccountData.populateClientDefaults(newLoanAccount,
+                        loanAccountClientDetails);
+
+                // if it's JLG loan add group details
+                if (templateType.equals("jlg")) {
+                    final GroupGeneralData group = this.groupReadPlatformService.retrieveOne(groupId);
+                    newLoanAccount = LoanAccountData.associateGroup(newLoanAccount, group);
+                    calendarOptions = this.loanReadPlatformService.retrieveCalendars(groupId);
+                }
+
+            } else if (templateType.equals("group")) {
+
+                final LoanAccountData loanAccountGroupData = this.loanReadPlatformService.retrieveGroupDetailsTemplate(groupId);
+                officeId = loanAccountGroupData.groupOfficeId();
+                calendarOptions = this.loanReadPlatformService.retrieveCalendars(groupId);
+                newLoanAccount = newLoanAccount == null ? loanAccountGroupData : LoanAccountData.populateGroupDefaults(newLoanAccount,
+                        loanAccountGroupData);
+
+            } else if (templateType.equals("jlgbulk")) {
+                // get group details along with members in that group
+                final LoanAccountData loanAccountGroupData = this.loanReadPlatformService.retrieveGroupAndMembersDetailsTemplate(groupId);
+                officeId = loanAccountGroupData.groupOfficeId();
+                calendarOptions = this.loanReadPlatformService.retrieveCalendars(groupId);
+                newLoanAccount = newLoanAccount == null ? loanAccountGroupData : LoanAccountData.populateGroupDefaults(newLoanAccount,
+                        loanAccountGroupData);
+                if (productId != null) {
+                    Map<Long, Integer> memberLoanCycle = new HashMap<>();
+                    Collection<ClientData> members = loanAccountGroupData.groupData().clientMembers();
+                    for (ClientData clientData : members) {
+                        Integer loanCounter = this.loanReadPlatformService.retriveLoanCounter(clientData.id(), productId);
+                        memberLoanCycle.put(clientData.id(), loanCounter);
+                    }
+                    newLoanAccount = LoanAccountData.associateMemberVariations(newLoanAccount, memberLoanCycle);
+                }
+
+            } else {
+                final String errorMsg = "Loan template type '" + templateType + "' is not supported";
+                throw new NotSupportedLoanTemplateTypeException(errorMsg, templateType);
+            }
+
+            allowedLoanOfficers = this.loanReadPlatformService.retrieveAllowedLoanOfficers(officeId, staffInSelectedOfficeOnly);
+
+            Collection<PortfolioAccountData> accountLinkingOptions = null;
+            if (clientId != null) {
+                final CurrencyData currencyData = newLoanAccount.currency();
+                String currencyCode = null;
+                if (currencyData != null) {
+                    currencyCode = currencyData.code();
+                }
+                final long[] accountStatus = { SavingsAccountStatusType.ACTIVE.getValue() };
+                PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(PortfolioAccountType.SAVINGS.getValue(), clientId,
+                        currencyCode, accountStatus, DepositAccountType.SAVINGS_DEPOSIT.getValue());
+                accountLinkingOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO);
+
+            }
+
+            // add product options, allowed loan officers and calendar options
+            // (calendar options will be null in individual loan)
+            newLoanAccount = LoanAccountData.associationsAndTemplate(newLoanAccount, productOptions, allowedLoanOfficers, calendarOptions,
+                    accountLinkingOptions);
+        }
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, newLoanAccount, this.LOAN_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{loanId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveLoan(@PathParam("loanId") final Long loanId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId);
+        if (loanBasicDetails.isInterestRecalculationEnabled()) {
+            Collection<CalendarData> interestRecalculationCalendarDatas = this.calendarReadPlatformService
+                    .retrieveCalendarsByEntity(loanBasicDetails.getInterestRecalculationDetailId(),
+                            CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue(), null);
+            CalendarData calendarData = null;
+            if (!CollectionUtils.isEmpty(interestRecalculationCalendarDatas)) {
+                calendarData = interestRecalculationCalendarDatas.iterator().next();
+            }
+
+            Collection<CalendarData> interestRecalculationCompoundingCalendarDatas = this.calendarReadPlatformService
+                    .retrieveCalendarsByEntity(loanBasicDetails.getInterestRecalculationDetailId(),
+                            CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue(), null);
+            CalendarData compoundingCalendarData = null;
+            if (!CollectionUtils.isEmpty(interestRecalculationCompoundingCalendarDatas)) {
+                compoundingCalendarData = interestRecalculationCompoundingCalendarDatas.iterator().next();
+            }
+            loanBasicDetails = LoanAccountData.withInterestRecalculationCalendarData(loanBasicDetails, calendarData,
+                    compoundingCalendarData);
+        }
+
+        Collection<InterestRatePeriodData> interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanId);
+
+        Collection<LoanTransactionData> loanRepayments = null;
+        LoanScheduleData repaymentSchedule = null;
+        Collection<LoanChargeData> charges = null;
+        Collection<GuarantorData> guarantors = null;
+        Collection<CollateralData> collateral = null;
+        CalendarData meeting = null;
+        Collection<NoteData> notes = null;
+        PortfolioAccountData linkedAccount = null;
+        Collection<DisbursementData> disbursementData = null;
+        Collection<LoanTermVariationsData> emiAmountVariations = null;
+
+        final Set<String> mandatoryResponseParameters = new HashSet<>();
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        if (!associationParameters.isEmpty()) {
+
+            if (associationParameters.contains("all")) {
+                associationParameters.addAll(Arrays.asList("repaymentSchedule", "futureSchedule", "originalSchedule", "transactions",
+                        "charges", "guarantors", "collateral", "notes", "linkedAccount", "multiDisburseDetails"));
+            }
+
+            ApiParameterHelper.excludeAssociationsForResponseIfProvided(uriInfo.getQueryParameters(), associationParameters);
+
+            if (associationParameters.contains("guarantors")) {
+                mandatoryResponseParameters.add("guarantors");
+                guarantors = this.guarantorReadPlatformService.retrieveGuarantorsForLoan(loanId);
+                if (CollectionUtils.isEmpty(guarantors)) {
+                    guarantors = null;
+                }
+            }
+
+            if (associationParameters.contains("transactions")) {
+                mandatoryResponseParameters.add("transactions");
+                final Collection<LoanTransactionData> currentLoanRepayments = this.loanReadPlatformService.retrieveLoanTransactions(loanId);
+                if (!CollectionUtils.isEmpty(currentLoanRepayments)) {
+                    loanRepayments = currentLoanRepayments;
+                }
+            }
+
+            if (associationParameters.contains("multiDisburseDetails") || associationParameters.contains("repaymentSchedule")) {
+                mandatoryResponseParameters.add("multiDisburseDetails");
+                disbursementData = this.loanReadPlatformService.retrieveLoanDisbursementDetails(loanId);
+            }
+
+            if (associationParameters.contains("emiAmountVariations") || associationParameters.contains("repaymentSchedule")) {
+                mandatoryResponseParameters.add("emiAmountVariations");
+                emiAmountVariations = this.loanReadPlatformService.retrieveLoanTermVariations(loanId,
+                        LoanTermVariationType.EMI_AMOUNT.getValue());
+            }
+
+            if (associationParameters.contains("repaymentSchedule")) {
+                mandatoryResponseParameters.add("repaymentSchedule");
+                final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = loanBasicDetails.repaymentScheduleRelatedData();
+                repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData,
+                        disbursementData, loanBasicDetails.isInterestRecalculationEnabled(), loanBasicDetails.getTotalPaidFeeCharges());
+
+                if (associationParameters.contains("futureSchedule") && loanBasicDetails.isInterestRecalculationEnabled()) {
+                    mandatoryResponseParameters.add("futureSchedule");
+                    this.calculationPlatformService.updateFutureSchedule(repaymentSchedule, loanId);
+                }
+
+                if (associationParameters.contains("originalSchedule") && loanBasicDetails.isInterestRecalculationEnabled()
+                        && loanBasicDetails.isActive()) {
+                    mandatoryResponseParameters.add("originalSchedule");
+                    LoanScheduleData loanScheduleData = this.loanScheduleHistoryReadPlatformService.retrieveRepaymentArchiveSchedule(
+                            loanId, repaymentScheduleRelatedData, disbursementData);
+                    loanBasicDetails = LoanAccountData.withOriginalSchedule(loanBasicDetails, loanScheduleData);
+                }
+            }
+
+            if (associationParameters.contains("charges")) {
+                mandatoryResponseParameters.add("charges");
+                charges = this.loanChargeReadPlatformService.retrieveLoanCharges(loanId);
+                if (CollectionUtils.isEmpty(charges)) {
+                    charges = null;
+                }
+            }
+
+            if (associationParameters.contains("collateral")) {
+                mandatoryResponseParameters.add("collateral");
+                collateral = this.loanCollateralReadPlatformService.retrieveCollaterals(loanId);
+                if (CollectionUtils.isEmpty(collateral)) {
+                    collateral = null;
+                }
+            }
+
+            if (associationParameters.contains("meeting")) {
+                mandatoryResponseParameters.add("meeting");
+                meeting = this.calendarReadPlatformService.retrieveLoanCalendar(loanId);
+            }
+
+            if (associationParameters.contains("notes")) {
+                mandatoryResponseParameters.add("notes");
+                notes = this.noteReadPlatformService.retrieveNotesByResource(loanId, NoteType.LOAN.getValue());
+                if (CollectionUtils.isEmpty(notes)) {
+                    notes = null;
+                }
+            }
+
+            if (associationParameters.contains("linkedAccount")) {
+                mandatoryResponseParameters.add("linkedAccount");
+                linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
+            }
+
+        }
+
+        Collection<LoanProductData> productOptions = null;
+        LoanProductData product = null;
+        Collection<EnumOptionData> loanTermFrequencyTypeOptions = null;
+        Collection<EnumOptionData> repaymentFrequencyTypeOptions = null;
+        Collection<TransactionProcessingStrategyData> repaymentStrategyOptions = null;
+        Collection<EnumOptionData> interestRateFrequencyTypeOptions = null;
+        Collection<EnumOptionData> amortizationTypeOptions = null;
+        Collection<EnumOptionData> interestTypeOptions = null;
+        Collection<EnumOptionData> interestCalculationPeriodTypeOptions = null;
+        Collection<FundData> fundOptions = null;
+        Collection<StaffData> allowedLoanOfficers = null;
+        Collection<ChargeData> chargeOptions = null;
+        ChargeData chargeTemplate = null;
+        Collection<CodeValueData> loanPurposeOptions = null;
+        Collection<CodeValueData> loanCollateralOptions = null;
+        Collection<CalendarData> calendarOptions = null;
+        Collection<PortfolioAccountData> accountLinkingOptions = null;
+        PaidInAdvanceData paidInAdvanceTemplate = null;
+
+        final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
+        if (template) {
+            productOptions = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup();
+            product = this.loanProductReadPlatformService.retrieveLoanProduct(loanBasicDetails.loanProductId());
+            loanBasicDetails.setProduct(product);
+            loanTermFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveLoanTermFrequencyTypeOptions();
+            repaymentFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions();
+            interestRateFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions();
+
+            amortizationTypeOptions = this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions();
+            if (product.isLinkedToFloatingInterestRates()) {
+                interestTypeOptions = Arrays.asList(interestType(InterestMethod.DECLINING_BALANCE));
+            } else {
+                interestTypeOptions = this.dropdownReadPlatformService.retrieveLoanInterestTypeOptions();
+            }
+            interestCalculationPeriodTypeOptions = this.dropdownReadPlatformService.retrieveLoanInterestRateCalculatedInPeriodOptions();
+
+            fundOptions = this.fundReadPlatformService.retrieveAllFunds();
+            repaymentStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies();
+            if (product.getMultiDisburseLoan()) {
+                chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId,
+                        new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
+            } else {
+                chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId, new ChargeTimeType[] {
+                        ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT });
+            }
+            chargeTemplate = this.loanChargeReadPlatformService.retrieveLoanChargeTemplate();
+
+            allowedLoanOfficers = this.loanReadPlatformService.retrieveAllowedLoanOfficers(loanBasicDetails.officeId(),
+                    staffInSelectedOfficeOnly);
+
+            loanPurposeOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanPurpose");
+            loanCollateralOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanCollateral");
+            final CurrencyData currencyData = loanBasicDetails.currency();
+            String currencyCode = null;
+            if (currencyData != null) {
+                currencyCode = currencyData.code();
+            }
+            final long[] accountStatus = { SavingsAccountStatusType.ACTIVE.getValue() };
+            PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(PortfolioAccountType.SAVINGS.getValue(),
+                    loanBasicDetails.clientId(), currencyCode, accountStatus, DepositAccountType.SAVINGS_DEPOSIT.getValue());
+            accountLinkingOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO);
+
+            if (!associationParameters.contains("linkedAccount")) {
+                mandatoryResponseParameters.add("linkedAccount");
+                linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
+            }
+            if (loanBasicDetails.groupId() != null) {
+                calendarOptions = this.loanReadPlatformService.retrieveCalendars(loanBasicDetails.groupId());
+            }
+
+        }
+
+        Collection<ChargeData> overdueCharges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanBasicDetails.loanProductId(),
+                ChargeTimeType.OVERDUE_INSTALLMENT);
+
+        paidInAdvanceTemplate = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId);
+
+        final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments,
+                charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
+                null, null, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
+                interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
+                loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
+                overdueCharges, paidInAdvanceTemplate, interestRatesPeriods);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
+                mandatoryResponseParameters);
+        return this.toApiJsonSerializer.serialize(settings, loanAccount, this.LOAN_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo,
+            @QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("externalId") final String externalId,
+            // @QueryParam("underHierarchy") final String hierarchy,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder,
+            @QueryParam("accountNo") final String accountNo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final SearchParameters searchParameters = SearchParameters.forLoans(sqlSearch, externalId, offset, limit, orderBy, sortOrder,
+                accountNo);
+
+        final Page<LoanAccountData> loanBasicDetails = this.loanReadPlatformService.retrieveAll(searchParameters);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, loanBasicDetails, this.LOAN_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String calculateLoanScheduleOrSubmitLoanApplication(@QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
+
+        if (is(commandParam, "calculateLoanSchedule")) {
+
+            final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
+            final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
+
+            final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, true);
+
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+            return this.loanScheduleToApiJsonSerializer.serialize(settings, loanSchedule.toData(), new HashSet<String>());
+        }
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createLoanApplication().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{loanId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String modifyLoanApplication(@PathParam("loanId") final Long loanId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateLoanApplication(loanId).withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{loanId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteLoanApplication(@PathParam("loanId") final Long loanId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteLoanApplication(loanId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{loanId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String stateTransitions(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+        CommandProcessingResult result = null;
+
+        if (is(commandParam, "reject")) {
+            final CommandWrapper commandRequest = builder.rejectLoanApplication(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "withdrawnByApplicant")) {
+            final CommandWrapper commandRequest = builder.withdrawLoanApplication(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "approve")) {
+            final CommandWrapper commandRequest = builder.approveLoanApplication(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "disburse")) {
+            final CommandWrapper commandRequest = builder.disburseLoanApplication(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "disburseToSavings")) {
+            final CommandWrapper commandRequest = builder.disburseLoanToSavingsApplication(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (is(commandParam, "undoapproval")) {
+            final CommandWrapper commandRequest = builder.undoLoanApplicationApproval(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "undodisbursal")) {
+            final CommandWrapper commandRequest = builder.undoLoanApplicationDisbursal(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }else if (is(commandParam, "undolastdisbursal")) {
+            final CommandWrapper commandRequest = builder.undoLastDisbursalLoanApplication(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (is(commandParam, "assignloanofficer")) {
+            final CommandWrapper commandRequest = builder.assignLoanOfficer(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "unassignloanofficer")) {
+            final CommandWrapper commandRequest = builder.unassignLoanOfficer(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "recoverGuarantees")) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder().recoverFromGuarantor(loanId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
+
+        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/portfolio/loanaccount/command/LoanChargeCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/LoanChargeCommand.java
new file mode 100644
index 0000000..a457129
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/LoanChargeCommand.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.command;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.joda.time.LocalDate;
+
+/**
+ * Java object representation of {@link LoanCharge} API JSON.
+ */
+public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    private final Long chargeId;
+    private final BigDecimal amount;
+    @SuppressWarnings("unused")
+    private final Integer chargeTimeType;
+    @SuppressWarnings("unused")
+    private final Integer chargeCalculationType;
+    @SuppressWarnings("unused")
+    private final LocalDate dueDate;
+
+    public LoanChargeCommand(final Long id, final Long chargeId, final BigDecimal amount, final Integer chargeTimeType,
+            final Integer chargeCalculationType, final LocalDate dueDate) {
+        this.id = id;
+        this.chargeId = chargeId;
+        this.amount = amount;
+        this.chargeTimeType = chargeTimeType;
+        this.chargeCalculationType = chargeCalculationType;
+        this.dueDate = dueDate;
+    }
+
+    @Override
+    public int compareTo(final LoanChargeCommand o) {
+        int comparison = this.chargeId.compareTo(o.chargeId);
+        if (comparison == 0) {
+            comparison = this.amount.compareTo(o.amount);
+        }
+        return comparison;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/LoanUpdateCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/LoanUpdateCommand.java
new file mode 100644
index 0000000..5b9a9fa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/LoanUpdateCommand.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.command;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable command for loan transactions.
+ */
+public class LoanUpdateCommand {
+
+    private final LocalDate unassignedDate;
+
+    public LoanUpdateCommand(final LocalDate unassignDate) {
+        this.unassignedDate = unassignDate;
+    }
+
+    public void validate() {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+
+        baseDataValidator.reset().parameter("unassignedDate").value(this.unassignedDate).notNull();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/UndoStateTransitionCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/UndoStateTransitionCommand.java
new file mode 100644
index 0000000..9dd2fbb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/UndoStateTransitionCommand.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.command;
+
+/**
+ * Immutable command for undo'ing a state transition.
+ */
+public class UndoStateTransitionCommand {
+
+    private final Long loanId;
+    private final String note;
+
+    public UndoStateTransitionCommand(final Long loanId, final String note) {
+        this.loanId = loanId;
+        this.note = note;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public String getNote() {
+        return this.note;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/UpdateLoanOfficerCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/UpdateLoanOfficerCommand.java
new file mode 100644
index 0000000..d46dff8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/command/UpdateLoanOfficerCommand.java
@@ -0,0 +1,86 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.command;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object for updating relationship between loan officer and a
+ * loan.
+ */
+public class UpdateLoanOfficerCommand {
+
+    private final Long fromLoanOfficerId;
+    private final Long toLoanOfficerId;
+    private final LocalDate assignmentDate;
+
+    private final String[] loans;
+
+    public UpdateLoanOfficerCommand(final Long fromLoanOfficerId, final Long toLoanOfficerId, final LocalDate assignmentDate) {
+        this.fromLoanOfficerId = fromLoanOfficerId;
+        this.toLoanOfficerId = toLoanOfficerId;
+        this.assignmentDate = assignmentDate;
+        this.loans = null;
+    }
+
+    public UpdateLoanOfficerCommand(final Long fromLoanOfficerId, final Long toLoanOfficerId, final LocalDate assignmentDate,
+            final String[] loans) {
+        this.fromLoanOfficerId = fromLoanOfficerId;
+        this.toLoanOfficerId = toLoanOfficerId;
+        this.assignmentDate = assignmentDate;
+        this.loans = loans;
+    }
+
+    public void validateForBulkLoanReassignment() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loans.reassignment");
+
+        baseDataValidator.reset().parameter("fromLoanOfficerId").value(this.fromLoanOfficerId).notNull().integerGreaterThanZero();
+        baseDataValidator.reset().parameter("toLoanOfficerId").value(this.toLoanOfficerId).notNull().integerGreaterThanZero()
+                .notSameAsParameter("fromLoanOfficerId", this.fromLoanOfficerId);
+
+        baseDataValidator.reset().parameter("assignmentDate").value(this.assignmentDate).notNull();
+
+        baseDataValidator.reset().parameter("loans").value(this.loans).arrayNotEmpty();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForLoanReassignment() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loans.reassignment");
+
+        baseDataValidator.reset().parameter("toLoanOfficerId").value(this.toLoanOfficerId).notNull().integerGreaterThanZero()
+                .notSameAsParameter("fromLoanOfficerId", this.fromLoanOfficerId);
+
+        baseDataValidator.reset().parameter("assignmentDate").value(this.assignmentDate).notNull();
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..97ab973
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java
@@ -0,0 +1,86 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing disbursement information.
+ */
+public class DisbursementData implements Comparable<DisbursementData> {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    private final LocalDate expectedDisbursementDate;
+    private final LocalDate actualDisbursementDate;
+    private final BigDecimal principal;
+    @SuppressWarnings("unused")
+    private final String loanChargeId;
+    private final BigDecimal chargeAmount;
+
+    public DisbursementData(Long id, final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
+            final BigDecimal principalDisbursed, final String loanChargeId, BigDecimal chargeAmount) {
+        this.id = id;
+        this.expectedDisbursementDate = expectedDisbursementDate;
+        this.actualDisbursementDate = actualDisbursementDate;
+        this.principal = principalDisbursed;
+        this.loanChargeId = loanChargeId;
+        this.chargeAmount = chargeAmount;
+    }
+
+    public LocalDate disbursementDate() {
+        LocalDate disbursementDate = this.expectedDisbursementDate;
+        if (this.actualDisbursementDate != null) {
+            disbursementDate = this.actualDisbursementDate;
+        }
+        return disbursementDate;
+    }
+
+    public BigDecimal amount() {
+        return this.principal;
+    }
+
+    public BigDecimal getChargeAmount() {
+        return this.chargeAmount;
+    }
+
+    public boolean isDisbursed() {
+        return this.actualDisbursementDate != null;
+    }
+
+    @Override
+    public int compareTo(final DisbursementData obj) {
+        if (obj == null) { return -1; }
+
+        return obj.expectedDisbursementDate.compareTo(this.expectedDisbursementDate);
+    }
+
+    public boolean isDueForDisbursement(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) {
+        final LocalDate dueDate = disbursementDate();
+        return occursOnDayFromAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate);
+    }
+
+    private boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive,
+            final LocalDate target) {
+        return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/HolidayDetailDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/HolidayDetailDTO.java
new file mode 100755
index 0000000..adcc5f9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/HolidayDetailDTO.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.loanaccount.data;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+
+public class HolidayDetailDTO {
+
+    final boolean isHolidayEnabled;
+    final List<Holiday> holidays;
+    final WorkingDays workingDays;
+    final boolean allowTransactionsOnHoliday;
+    final boolean allowTransactionsOnNonWorkingDay;
+
+    public HolidayDetailDTO(final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
+        this.isHolidayEnabled = isHolidayEnabled;
+        this.holidays = holidays;
+        this.workingDays = workingDays;
+        this.allowTransactionsOnHoliday = false;
+        this.allowTransactionsOnNonWorkingDay = false;
+    }
+
+    public HolidayDetailDTO(final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
+            final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay) {
+        this.isHolidayEnabled = isHolidayEnabled;
+        this.holidays = holidays;
+        this.workingDays = workingDays;
+        this.allowTransactionsOnHoliday = allowTransactionsOnHoliday;
+        this.allowTransactionsOnNonWorkingDay = allowTransactionsOnNonWorkingDay;
+    }
+
+    public boolean isHolidayEnabled() {
+        return this.isHolidayEnabled;
+    }
+
+    public List<Holiday> getHolidays() {
+        return this.holidays;
+    }
+
+    public WorkingDays getWorkingDays() {
+        return this.workingDays;
+    }
+
+    public boolean isAllowTransactionsOnHoliday() {
+        return this.allowTransactionsOnHoliday;
+    }
+
+    public boolean isAllowTransactionsOnNonWorkingDay() {
+        return this.allowTransactionsOnNonWorkingDay;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
new file mode 100644
index 0000000..6ec674d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -0,0 +1,1515 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.Transient;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.collateral.data.CollateralData;
+import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
+import org.apache.fineract.portfolio.fund.data.FundData;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
+import org.apache.fineract.portfolio.note.data.NoteData;
+import org.joda.time.LocalDate;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Immutable data object representing loan account data.
+ */
+@SuppressWarnings("unused")
+public class LoanAccountData {
+
+    // basic loan details
+
+    // identity
+    private final Long id;
+    private final String accountNo;
+    private final String externalId;
+
+    // status
+    private final LoanStatusEnumData status;
+
+    // related to
+    private final Long clientId;
+    private final String clientAccountNo;
+    private final String clientName;
+    private final Long clientOfficeId;
+    private final GroupGeneralData group;
+    private final Long loanProductId;
+    private final String loanProductName;
+    private final String loanProductDescription;
+    private final boolean isLoanProductLinkedToFloatingRate;
+    private final Long fundId;
+    private final String fundName;
+    private final Long loanPurposeId;
+    private final String loanPurposeName;
+    private final Long loanOfficerId;
+    private final String loanOfficerName;
+    private final EnumOptionData loanType;
+
+    // terms
+    private final CurrencyData currency;
+    private final BigDecimal principal;
+    private final BigDecimal approvedPrincipal;
+    private final BigDecimal proposedPrincipal;
+
+    private final Integer termFrequency;
+    private final EnumOptionData termPeriodFrequencyType;
+    private final Integer numberOfRepayments;
+    private final Integer repaymentEvery;
+    private final EnumOptionData repaymentFrequencyType;
+    private final EnumOptionData repaymentFrequencyNthDayType;
+    private final EnumOptionData repaymentFrequencyDayOfWeekType;
+    private final BigDecimal interestRatePerPeriod;
+    private final EnumOptionData interestRateFrequencyType;
+    private final BigDecimal annualInterestRate;
+    private final boolean isFloatingInterestRate;
+    private final BigDecimal interestRateDifferential;
+
+    // settings
+    private final EnumOptionData amortizationType;
+    private final EnumOptionData interestType;
+    private final EnumOptionData interestCalculationPeriodType;
+    private final Boolean allowPartialPeriodInterestCalcualtion;
+    private final BigDecimal inArrearsTolerance;
+    private final Long transactionProcessingStrategyId;
+    private final String transactionProcessingStrategyName;
+    private final Integer graceOnPrincipalPayment;
+    private final Integer graceOnInterestPayment;
+    private final Integer graceOnInterestCharged;
+    private final Integer graceOnArrearsAgeing;
+    private final LocalDate interestChargedFromDate;
+    private final LocalDate expectedFirstRepaymentOnDate;
+    private final Boolean syncDisbursementWithMeeting;
+
+    // timeline
+    private final LoanApplicationTimelineData timeline;
+
+    // totals
+    private final LoanSummaryData summary;
+
+    // associations
+    private final LoanScheduleData repaymentSchedule;
+    private final Collection<LoanTransactionData> transactions;
+    private final Collection<LoanChargeData> charges;
+    private final Collection<CollateralData> collateral;
+    private final Collection<GuarantorData> guarantors;
+    private final CalendarData meeting;
+    private final Collection<NoteData> notes;
+    private final Collection<DisbursementData> disbursementDetails;
+    private final LoanScheduleData originalSchedule;
+    // template
+    private final Collection<LoanProductData> productOptions;
+    private final Collection<StaffData> loanOfficerOptions;
+    private final Collection<CodeValueData> loanPurposeOptions;
+    private final Collection<FundData> fundOptions;
+    private final Collection<EnumOptionData> termFrequencyTypeOptions;
+    private final Collection<EnumOptionData> repaymentFrequencyTypeOptions;
+    private final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions;
+    private final Collection<EnumOptionData> repaymentFrequencyDaysOfWeekTypeOptions;
+
+    private final Collection<EnumOptionData> interestRateFrequencyTypeOptions;
+    private final Collection<EnumOptionData> amortizationTypeOptions;
+    private final Collection<EnumOptionData> interestTypeOptions;
+    private final Collection<EnumOptionData> interestCalculationPeriodTypeOptions;
+    private final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions;
+    private final Collection<ChargeData> chargeOptions;
+    private final Collection<CodeValueData> loanCollateralOptions;
+    private final Collection<CalendarData> calendarOptions;
+
+    @Transient
+    private final BigDecimal feeChargesAtDisbursementCharged;
+    private final BigDecimal totalOverpaid;
+
+    // loanCycle
+    private final Integer loanCounter;
+    private final Integer loanProductCounter;
+
+    // linkable account details
+    private final PortfolioAccountData linkedAccount;
+    private final Collection<PortfolioAccountData> accountLinkingOptions;
+
+    private final Boolean multiDisburseLoan;
+
+    private final Boolean canDefineInstallmentAmount;
+
+    private final BigDecimal fixedEmiAmount;
+
+    private final BigDecimal maxOutstandingLoanBalance;
+
+    private final Boolean canDisburse;
+
+    private final Collection<LoanTermVariationsData> emiAmountVariations;
+
+    private LoanProductData product;
+
+    private final Map<Long, LoanBorrowerCycleData> memberVariations;
+
+    private final Boolean inArrears;
+
+    private final Boolean isNPA;
+
+    private final Collection<ChargeData> overdueCharges;
+
+    private final EnumOptionData daysInMonthType;
+    private final EnumOptionData daysInYearType;
+    private final boolean isInterestRecalculationEnabled;
+
+    private final LoanInterestRecalculationData interestRecalculationData;
+    private final Boolean createStandingInstructionAtDisbursement;
+
+    // Paid In Advance
+    private final PaidInAdvanceData paidInAdvance;
+
+    private final Collection<InterestRatePeriodData> interestRatesPeriods;
+
+    // VariableInstallments
+    private final Boolean isVariableInstallmentsAllowed;
+    private final Integer minimumGap;
+    private final Integer maximumGap;
+
+    /**
+     * Used to produce a {@link LoanAccountData} with only collateral options
+     * for now.
+     */
+    public static LoanAccountData collateralTemplate(final Collection<CodeValueData> loanCollateralOptions) {
+        final Long id = null;
+        final String accountNo = null;
+        final LoanStatusEnumData status = null;
+        final String externalId = null;
+        final Long clientId = null;
+        final String clientName = null;
+        final String clientAccountNo = null;
+        final Long clientOfficeId = null;
+        final GroupGeneralData group = null;
+        final EnumOptionData loanType = null;
+        final Long loanProductId = null;
+        final String loanProductName = null;
+        final String loanProductDescription = null;
+        final boolean isLoanProductLinkedToFloatingRate = false;
+        final Long fundId = null;
+        final String fundName = null;
+        final Long loanPurposeId = null;
+        final String loanPurposeName = null;
+        final Long loanOfficerId = null;
+        final String loanOfficerName = null;
+        final CurrencyData currencyData = null;
+        final BigDecimal proposedPrincipal = null;
+        final BigDecimal principal = null;
+        final BigDecimal totalOverpaid = null;
+        final BigDecimal inArrearsTolerance = null;
+        final Integer termFrequency = null;
+        final EnumOptionData termPeriodFrequencyType = null;
+        final Integer numberOfRepayments = null;
+        final Integer repaymentEvery = null;
+        final EnumOptionData repaymentFrequencyType = null;
+        final EnumOptionData repaymentFrequencyNthDayType = null;
+        final EnumOptionData repaymentFrequencyDayOfWeekType = null;
+        final Long transactionProcessingStrategyId = null;
+        final String transactionProcessingStrategyName = null;
+        final EnumOptionData amortizationType = null;
+        final BigDecimal interestRatePerPeriod = null;
+        final EnumOptionData interestRateFrequencyType = null;
+        final BigDecimal annualInterestRate = null;
+        final EnumOptionData interestType = null;
+        final boolean isFloatingInterestRate = false;
+        final BigDecimal interestRateDifferential = null;
+        final EnumOptionData interestCalculationPeriodType = null;
+        final Boolean allowPartialPeriodInterestCalcualtion = null;
+        final LocalDate expectedFirstRepaymentOnDate = null;
+        final Integer graceOnPrincipalPayment = null;
+        final Integer graceOnInterestPayment = null;
+        final Integer graceOnInterestCharged = null;
+        final Integer graceOnArrearsAgeing = null;
+        final LocalDate interestChargedFromDate = null;
+        final LoanApplicationTimelineData timeline = null;
+        final LoanSummaryData summary = null;
+        final BigDecimal feeChargesDueAtDisbursementCharged = null;
+
+        final LoanScheduleData repaymentSchedule = null;
+        final Collection<LoanTransactionData> transactions = null;
+        final Collection<LoanChargeData> charges = null;
+        final Collection<CollateralData> collateral = null;
+        final Collection<GuarantorData> guarantors = null;
+        final Collection<NoteData> notes = null;
+        final CalendarData calendarData = null;
+        final Collection<LoanProductData> productOptions = null;
+        final Collection<EnumOptionData> termFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyDayOfWeekTypeOptions = null;
+        final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions = null;
+        final Collection<EnumOptionData> interestRateFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> amortizationTypeOptions = null;
+        final Collection<EnumOptionData> interestTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationPeriodTypeOptions = null;
+        final Collection<FundData> fundOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final ChargeData chargeTemplate = null;
+        final Collection<StaffData> loanOfficerOptions = null;
+        final Collection<CodeValueData> loanPurposeOptions = null;
+        final Collection<CalendarData> calendarOptions = null;
+        final Boolean syncDisbursementWithMeeting = null;
+        final Integer loancounter = null;
+        final Integer loanProductCounter = null;
+        final Collection<PortfolioAccountData> accountLinkingOptions = null;
+        final PortfolioAccountData linkedAccount = null;
+        final Collection<DisbursementData> disbursementData = null;
+        final Boolean multiDisburseLoan = null;
+        final Boolean canDefineInstallmentAmount = null;
+        final BigDecimal fixedEmiAmount = null;
+        final BigDecimal maxOutstandingLoanBalance = null;
+        final Collection<LoanTermVariationsData> emiAmountVariations = null;
+        final Map<Long, LoanBorrowerCycleData> memberVariations = null;
+        final LoanProductData product = null;
+        final Boolean inArrears = null;
+        final Boolean isNPA = null;
+        final Collection<ChargeData> overdueCharges = null;
+
+        final EnumOptionData daysInMonthType = null;
+        final EnumOptionData daysInYearType = null;
+        final boolean isInterestRecalculationEnabled = false;
+        final LoanInterestRecalculationData interestRecalculationData = null;
+        final LoanScheduleData originalSchedule = null;
+        final Boolean createStandingInstructionAtDisbursement = null;
+        final PaidInAdvanceData paidInAdvance = null;
+        final Collection<InterestRatePeriodData> interestRatesPeriods = null;
+        final Boolean isVariableInstallmentsAllowed = Boolean.FALSE;
+        final Integer minimumGap = null;
+        final Integer maximumGap = null;
+
+        return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
+                loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
+                loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
+                totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery,
+                repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId,
+                transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate,
+                interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
+                allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
+                graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule,
+                transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
+                repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions,
+                transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
+                interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
+                loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,
+                accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount,
+                maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
+                isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
+                createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
+                maximumGap);
+
+    }
+
+    /**
+     * Used to produce a {@link LoanAccountData} with only client information
+     * defaulted.
+     */
+    public static LoanAccountData clientDefaults(final Long clientId, final String clientAccountNo, final String clientName,
+            final Long clientOfficeId, final LocalDate expectedDisbursementDate) {
+        final Long id = null;
+        final String accountNo = null;
+        final LoanStatusEnumData status = null;
+        final String externalId = null;
+        final GroupGeneralData group = null;
+        final EnumOptionData loanType = null;
+        final String groupName = null;
+        final Long loanProductId = null;
+        final String loanProductName = null;
+        final String loanProductDescription = null;
+        final boolean isLoanProductLinkedToFloatingRate = false;
+        final Long fundId = null;
+        final String fundName = null;
+        final Long loanPurposeId = null;
+        final String loanPurposeName = null;
+        final Long loanOfficerId = null;
+        final String loanOfficerName = null;
+        final CurrencyData currencyData = null;
+        final BigDecimal proposedPrincipal = null;
+        final BigDecimal principal = null;
+        final BigDecimal totalOverpaid = null;
+        final BigDecimal inArrearsTolerance = null;
+        final Integer termFrequency = null;
+        final EnumOptionData termPeriodFrequencyType = null;
+        final Integer numberOfRepayments = null;
+        final Integer repaymentEvery = null;
+        final EnumOptionData repaymentFrequencyType = null;
+        final EnumOptionData repaymentFrequencyNthDayType = null;
+        final EnumOptionData repaymentFrequencyDayOfWeekType = null;
+        final Long transactionProcessingStrategyId = null;
+        final String transactionProcessingStrategyName = null;
+        final EnumOptionData amortizationType = null;
+        final BigDecimal interestRatePerPeriod = null;
+        final EnumOptionData interestRateFrequencyType = null;
+        final BigDecimal annualInterestRate = null;
+        final EnumOptionData interestType = null;
+        final boolean isFloatingInterestRate = false;
+        final BigDecimal interestRateDifferential = null;
+        final EnumOptionData interestCalculationPeriodType = null;
+        final Boolean allowPartialPeriodInterestCalcualtion = null;
+        final LocalDate expectedFirstRepaymentOnDate = null;
+        final Integer graceOnPrincipalPayment = null;
+        final Integer graceOnInterestPayment = null;
+        final Integer graceOnArrearsAgeing = null;
+        final Integer graceOnInterestCharged = null;
+        final LocalDate interestChargedFromDate = null;
+        final LoanApplicationTimelineData timeline = LoanApplicationTimelineData.templateDefault(expectedDisbursementDate);
+        final LoanSummaryData summary = null;
+        final BigDecimal feeChargesDueAtDisbursementCharged = null;
+
+        final LoanScheduleData repaymentSchedule = null;
+        final Collection<LoanTransactionData> transactions = null;
+        final Collection<LoanChargeData> charges = null;
+        final Collection<CollateralData> collateral = null;
+        final Collection<GuarantorData> guarantors = null;
+        final Collection<NoteData> notes = null;
+        final CalendarData calendarData = null;
+        final Collection<LoanProductData> productOptions = null;
+        final Collection<EnumOptionData> termFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyDayOfWeekTypeOptions = null;
+        final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions = null;
+        final Collection<EnumOptionData> interestRateFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> amortizationTypeOptions = null;
+        final Collection<EnumOptionData> interestTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationPeriodTypeOptions = null;
+        final Collection<FundData> fundOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final ChargeData chargeTemplate = null;
+        final Collection<StaffData> loanOfficerOptions = null;
+        final Collection<CodeValueData> loanPurposeOptions = null;
+        final Collection<CodeValueData> loanCollateralOptions = null;
+        final Collection<CalendarData> calendarOptions = null;
+        final Boolean syncDisbursementWithMeeting = null;
+        final Integer loancounter = null;
+        final Integer loanProductCounter = null;
+        final Collection<PortfolioAccountData> accountLinkingOptions = null;
+        final PortfolioAccountData linkedAccount = null;
+        final Collection<DisbursementData> disbursementData = null;
+        final Boolean multiDisburseLoan = null;
+        final Boolean canDefineInstallmentAmount = null;
+        final BigDecimal fixedEmiAmount = null;
+        final BigDecimal maxOutstandingLoanBalance = null;
+        final Collection<LoanTermVariationsData> emiAmountVariations = null;
+        final Map<Long, LoanBorrowerCycleData> memberVariations = null;
+        final LoanProductData product = null;
+        final Boolean inArrears = null;
+        final Boolean isNPA = null;
+        final Collection<ChargeData> overdueCharges = null;
+
+        final EnumOptionData daysInMonthType = null;
+        final EnumOptionData daysInYearType = null;
+        final boolean isInterestRecalculationEnabled = false;
+        final LoanInterestRecalculationData interestRecalculationData = null;
+        final LoanScheduleData originalSchedule = null;
+        final Boolean createStandingInstructionAtDisbursement = null;
+        final PaidInAdvanceData paidInAdvance = null;
+        final Collection<InterestRatePeriodData> interestRatesPeriods = null;
+
+        final Boolean isVariableInstallmentsAllowed = Boolean.FALSE;
+        final Integer minimumGap = null;
+        final Integer maximumGap = null;
+
+        return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
+                loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
+                loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
+                totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery,
+                repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId,
+                transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate,
+                interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
+                allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
+                graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule,
+                transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
+                repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions,
+                repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
+                interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
+                loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,
+                accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount,
+                maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
+                isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
+                createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
+                maximumGap);
+
+    }
+
+    public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
+
+        return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, clientAcc.clientId, clientAcc.clientAccountNo,
+                clientAcc.clientName, clientAcc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName,
+                acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId,
+                acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal,
+                acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType,
+                acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType,
+                acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName,
+                acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
+                acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
+                acc.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment,
+                acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, clientAcc.timeline, acc.summary,
+                acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
+                acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
+                acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
+                acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
+                acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
+                acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
+                acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
+                acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
+                acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
+                acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
+                acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
+                acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
+                acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
+    }
+
+    /**
+     * Used to produce a {@link LoanAccountData} with only group information
+     * defaulted.
+     */
+    public static LoanAccountData groupDefaults(final GroupGeneralData group, final LocalDate expectedDisbursementDate) {
+
+        final Long id = null;
+        final String accountNo = null;
+        final LoanStatusEnumData status = null;
+        final String externalId = null;
+        final Long clientId = null;
+        final String clientAccountNo = null;
+        final String clientName = null;
+        final Long clientOfficeId = null;
+        final EnumOptionData loanType = null;
+        final Long loanProductId = null;
+        final String loanProductName = null;
+        final String loanProductDescription = null;
+        final boolean isLoanProductLinkedToFloatingRate = false;
+        final Long fundId = null;
+        final String fundName = null;
+        final Long loanPurposeId = null;
+        final String loanPurposeName = null;
+        final Long loanOfficerId = null;
+        final String loanOfficerName = null;
+        final CurrencyData currencyData = null;
+        final BigDecimal proposedPrincipal = null;
+        final BigDecimal principal = null;
+        final BigDecimal totalOverpaid = null;
+        final BigDecimal inArrearsTolerance = null;
+        final Integer termFrequency = null;
+        final EnumOptionData termPeriodFrequencyType = null;
+        final Integer numberOfRepayments = null;
+        final Integer repaymentEvery = null;
+        final EnumOptionData repaymentFrequencyType = null;
+        final EnumOptionData repaymentFrequencyNthDayType = null;
+        final EnumOptionData repaymentFrequencyDayOfWeekType = null;
+        final Long transactionProcessingStrategyId = null;
+        final String transactionProcessingStrategyName = null;
+        final EnumOptionData amortizationType = null;
+        final BigDecimal interestRatePerPeriod = null;
+        final EnumOptionData interestRateFrequencyType = null;
+        final BigDecimal annualInterestRate = null;
+        final EnumOptionData interestType = null;
+        final boolean isFloatingInterestRate = false;
+        final BigDecimal interestRateDifferential = null;
+        final EnumOptionData interestCalculationPeriodType = null;
+        final Boolean allowPartialPeriodInterestCalcualtion = null;
+        final LocalDate expectedFirstRepaymentOnDate = null;
+        final Integer graceOnPrincipalPayment = null;
+        final Integer graceOnInterestPayment = null;
+        final Integer graceOnInterestCharged = null;
+        final Integer graceOnArrearsAgeing = null;
+        final LocalDate interestChargedFromDate = null;
+        final LoanApplicationTimelineData timeline = LoanApplicationTimelineData.templateDefault(expectedDisbursementDate);
+        final LoanSummaryData summary = null;
+        final BigDecimal feeChargesDueAtDisbursementCharged = null;
+
+        final LoanScheduleData repaymentSchedule = null;
+        final Collection<LoanTransactionData> transactions = null;
+        final Collection<LoanChargeData> charges = null;
+        final Collection<CollateralData> collateral = null;
+        final Collection<GuarantorData> guarantors = null;
+        final Collection<NoteData> notes = null;
+        final CalendarData calendarData = null;
+        final Collection<LoanProductData> productOptions = null;
+        final Collection<EnumOptionData> termFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyDayOfWeekTypeOptions = null;
+        final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions = null;
+        final Collection<EnumOptionData> interestRateFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> amortizationTypeOptions = null;
+        final Collection<EnumOptionData> interestTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationPeriodTypeOptions = null;
+        final Collection<FundData> fundOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final ChargeData chargeTemplate = null;
+        final Collection<StaffData> loanOfficerOptions = null;
+        final Collection<CodeValueData> loanPurposeOptions = null;
+        final Collection<CodeValueData> loanCollateralOptions = null;
+        final Collection<CalendarData> calendarOptions = null;
+        final Boolean syncDisbursementWithMeeting = null;
+        final Integer loancounter = null;
+        final Integer loanProductCounter = null;
+        final Collection<PortfolioAccountData> accountLinkingOptions = null;
+        final PortfolioAccountData linkedAccount = null;
+        final Collection<DisbursementData> disbursementData = null;
+        final Boolean multiDisburseLoan = null;
+        final Boolean canDefineInstallmentAmount = null;
+        final BigDecimal fixedEmiAmount = null;
+        final BigDecimal maxOutstandingBalance = null;
+        final Collection<LoanTermVariationsData> emiAmountVariations = null;
+        final Map<Long, LoanBorrowerCycleData> memberVariations = null;
+        final LoanProductData product = null;
+        final Boolean inArrears = null;
+        final Boolean isNPA = null;
+        final Collection<ChargeData> overdueCharges = null;
+
+        final EnumOptionData daysInMonthType = null;
+        final EnumOptionData daysInYearType = null;
+        final boolean isInterestRecalculationEnabled = false;
+        final LoanInterestRecalculationData interestRecalculationData = null;
+        final LoanScheduleData originalSchedule = null;
+        final Boolean createStandingInstructionAtDisbursement = null;
+        final PaidInAdvanceData paidInAdvance = null;
+        final Collection<InterestRatePeriodData> interestRatesPeriods = null;
+
+        final Boolean isVariableInstallmentsAllowed = Boolean.FALSE;
+        final Integer minimumGap = null;
+        final Integer maximumGap = null;
+
+        return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
+                loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
+                loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
+                totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery,
+                repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId,
+                transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate,
+                interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
+                allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
+                graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule,
+                transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
+                repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions,
+                repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
+                interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
+                loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,
+                accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount,
+                maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
+                isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
+                createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
+                maximumGap);
+
+    }
+
+    public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
+
+        return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
+                acc.clientOfficeId, groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
+                acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
+                acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
+                acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
+                acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
+                acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
+                acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
+                acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
+                acc.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment,
+                acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, groupAcc.timeline, acc.summary,
+                acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
+                acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
+                acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
+                acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
+                acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
+                acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
+                acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
+                acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
+                acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
+                acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
+                acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
+                acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
+                acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
+
+    }
+
+    public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
+            final Collection<EnumOptionData> termFrequencyTypeOptions, final Collection<EnumOptionData> repaymentFrequencyTypeOptions,
+            final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions,
+            final Collection<EnumOptionData> repaymentFrequencyDayOfWeekTypeOptions,
+            final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions,
+            final Collection<EnumOptionData> interestRateFrequencyTypeOptions, final Collection<EnumOptionData> amortizationTypeOptions,
+            final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
+            final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions,
+            final Collection<CodeValueData> loanPurposeOptions, final Collection<CodeValueData> loanCollateralOptions,
+            final Integer loanCycleNumber) {
+
+        final Long id = null;
+        final String accountNo = null;
+        final LoanStatusEnumData status = null;
+        final String externalId = null;
+        final Long clientId = null;
+        final String clientAccountNo = null;
+        final String clientName = null;
+        final Long clientOfficeId = null;
+        final GroupGeneralData group = null;
+        final EnumOptionData loanType = null;
+        final String groupName = null;
+        final Long fundId = null;
+        final String fundName = null;
+        final Long loanPurposeId = null;
+        final String loanPurposeName = null;
+        final Long loanOfficerId = null;
+        final String loanOfficerName = null;
+        final CurrencyData currencyData = null;
+
+        final BigDecimal totalOverpaid = null;
+        final BigDecimal inArrearsTolerance = null;
+
+        final Integer repaymentEvery = null;
+        final EnumOptionData repaymentFrequencyType = null;
+        final Long transactionProcessingStrategyId = null;
+        final String transactionProcessingStrategyName = null;
+        final EnumOptionData amortizationType = null;
+
+        final EnumOptionData interestRateFrequencyType = null;
+        final BigDecimal annualInterestRate = null;
+        final EnumOptionData interestType = null;
+        final EnumOptionData interestCalculationPeriodType = null;
+        final LocalDate expectedFirstRepaymentOnDate = null;
+        final LocalDate interestChargedFromDate = null;
+        final LoanApplicationTimelineData timeline = null;
+        final LoanSummaryData summary = null;
+        final BigDecimal feeChargesDueAtDisbursementCharged = null;
+        final ChargeData chargeTemplate = null;
+        final Boolean syncDisbursementWithMeeting = null;
+
+        final LoanScheduleData repaymentSchedule = null;
+        final Collection<LoanTransactionData> transactions = null;
+        final Collection<CollateralData> collateral = null;
+        final Collection<GuarantorData> guarantors = null;
+        final Collection<NoteData> notes = null;
+        final CalendarData calendarData = null;
+        final Collection<LoanProductData> productOptions = null;
+        final Collection<CalendarData> calendarOptions = null;
+        final Collection<StaffData> loanOfficerOptions = null;
+
+        final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
+
+        final Collection<LoanChargeData> charges = new ArrayList<LoanChargeData>();
+        for (final ChargeData charge : product.charges()) {
+            if (!charge.isOverdueInstallmentCharge()) {
+                charges.add(charge.toLoanChargeData());
+            }
+        }
+
+        final Integer loancounter = null;
+        final Integer loanProductCounter = null;
+        final PortfolioAccountData linkedAccount = null;
+        final Collection<PortfolioAccountData> accountLinkingOptions = null;
+        final Collection<DisbursementData> disbursementData = null;
+        final Collection<LoanTermVariationsData> emiAmountVariations = null;
+        BigDecimal principal = null;
+        BigDecimal proposedPrincipal = null;
+
+        BigDecimal interestRatePerPeriod = null;
+        Integer numberOfRepayments = null;
+        if (product.useBorrowerCycle() && loanCycleNumber > 0) {
+            Collection<LoanProductBorrowerCycleVariationData> principalVariationsForBorrowerCycle = product
+                    .getPrincipalVariationsForBorrowerCycle();
+            Collection<LoanProductBorrowerCycleVariationData> interestForVariationsForBorrowerCycle = product
+                    .getInterestRateVariationsForBorrowerCycle();
+            Collection<LoanProductBorrowerCycleVariationData> repaymentVariationsForBorrowerCycle = product
+                    .getNumberOfRepaymentVariationsForBorrowerCycle();
+            principal = fetchLoanCycleDefaultValue(principalVariationsForBorrowerCycle, loanCycleNumber);
+            proposedPrincipal = principal;
+            interestRatePerPeriod = fetchLoanCycleDefaultValue(interestForVariationsForBorrowerCycle, loanCycleNumber);
+            BigDecimal numberofRepaymentval = fetchLoanCycleDefaultValue(repaymentVariationsForBorrowerCycle, loanCycleNumber);
+            if (numberofRepaymentval != null) {
+                numberOfRepayments = numberofRepaymentval.intValue();
+            }
+        }
+        if (principal == null) {
+            principal = product.getPrincipal();
+            proposedPrincipal = principal;
+        }
+        if (interestRatePerPeriod == null) {
+            interestRatePerPeriod = product.getInterestRatePerPeriod();
+        }
+        if (numberOfRepayments == null) {
+            numberOfRepayments = product.getNumberOfRepayments();
+        }
+
+        final Integer termFrequency = numberOfRepayments * product.getRepaymentEvery();
+        final BigDecimal fixedEmi = null;
+        Map<Long, LoanBorrowerCycleData> memberVariations = null;
+        final Boolean inArrears = null;
+        final Boolean isNPA = null;
+        final LoanScheduleData originalSchedule = null;
+        final Boolean createStandingInstructionAtDisbursement = null;
+        final PaidInAdvanceData paidInAdvance = null;
+        final Collection<InterestRatePeriodData> interestRatesPeriods = null;
+
+        final Boolean isVariableInstallmentsAllowed = Boolean.FALSE;
+        final Integer minimumGap = null;
+        final Integer maximumGap = null;
+
+        return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
+                loanType, product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(),
+                product.getFundId(), product.getFundName(), loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
+                product.getCurrency(), proposedPrincipal, principal, principal, totalOverpaid, product.getInArrearsTolerance(),
+                termFrequency, termPeriodFrequencyType, numberOfRepayments, product.getRepaymentEvery(),
+                product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(),
+                transactionProcessingStrategyName, product.getAmortizationType(), interestRatePerPeriod,
+                product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(),
+                product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(),
+                product.getInterestCalculationPeriodType(), product.getAllowPartialPeriodInterestCalcualtion(),
+                expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getGraceOnInterestPayment(),
+                product.getGraceOnInterestCharged(), interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged,
+                repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
+                repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions,
+                repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
+                interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
+                loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,
+                accountLinkingOptions, linkedAccount, disbursementData, product.getMultiDisburseLoan(),
+                product.canDefineInstallmentAmount(), fixedEmi, product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations,
+                product, inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(),
+                product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
+                originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
+                product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
+                product.getMaximumGapBetweenInstallments());
+    }
+
+    public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
+
+        final LoanScheduleData repaymentSchedule = null;
+        final Collection<LoanTransactionData> transactions = null;
+        final Collection<CollateralData> collateral = null;
+        final Collection<GuarantorData> guarantors = null;
+        final Collection<NoteData> notes = null;
+        final CalendarData calendarData = null;
+        final Collection<LoanProductData> productOptions = null;
+        final Collection<EnumOptionData> termFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyTypeOptions = null;
+        final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions = null;
+        final Collection<EnumOptionData> interestRateFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> amortizationTypeOptions = null;
+        final Collection<EnumOptionData> interestTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationPeriodTypeOptions = null;
+        final Collection<FundData> fundOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final ChargeData chargeTemplate = null;
+        final Collection<StaffData> loanOfficerOptions = null;
+        final Collection<CodeValueData> loanPurposeOptions = null;
+        final Collection<CodeValueData> loanCollateralOptions = null;
+        final Collection<CalendarData> calendarOptions = null;
+        final PaidInAdvanceData paidInAdvance = null;
+
+        final Integer termFrequency = product.getNumberOfRepayments() * product.getRepaymentEvery();
+        final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
+
+        final Collection<LoanChargeData> charges = new ArrayList<LoanChargeData>();
+        for (final ChargeData charge : product.charges()) {
+            charges.add(charge.toLoanChargeData());
+        }
+
+        return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
+                acc.clientOfficeId, acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(),
+                product.isLinkedToFloatingInterestRates(), product.getFundId(), product.getFundName(), acc.loanPurposeId,
+                acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, product.getCurrency(), product.getPrincipal(),
+                product.getPrincipal(), product.getPrincipal(), acc.totalOverpaid, product.getInArrearsTolerance(), termFrequency,
+                termPeriodFrequencyType, product.getNumberOfRepayments(), product.getRepaymentEvery(), product.getRepaymentFrequencyType(),
+                null, null, product.getTransactionProcessingStrategyId(), product.getTransactionProcessingStrategyName(),
+                product.getAmortizationType(), product.getInterestRatePerPeriod(), product.getInterestRateFrequencyType(),
+                product.getAnnualInterestRate(), product.getInterestType(), product.isFloatingInterestRateCalculationAllowed(),
+                product.getDefaultDifferentialLendingRate(), product.getInterestCalculationPeriodType(),
+                product.getAllowPartialPeriodInterestCalcualtion(), acc.expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(),
+                product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), acc.interestChargedFromDate, acc.timeline,
+                acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors,
+                calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, null, null,
+                repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
+                interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
+                loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes,
+                acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(),
+                product.canDefineInstallmentAmount(), acc.fixedEmiAmount, product.getOutstandingLoanBalance(), acc.emiAmountVariations,
+                acc.memberVariations, product, acc.inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA,
+                product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(),
+                product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement,
+                paidInAdvance, acc.interestRatesPeriods, product.isVariableInstallmentsAllowed(),
+                product.getMinimumGapBetweenInstallments(), product.getMaximumGapBetweenInstallments());
+
+    }
+
+    /*
+     * Used to send back loan account data with the basic details coming from
+     * query.
+     */
+    public static LoanAccountData basicLoanDetails(final Long id, final String accountNo, final LoanStatusEnumData status,
+            final String externalId, final Long clientId, final String clientAccountNo, final String clientName, final Long clientOfficeId,
+            final GroupGeneralData group, final EnumOptionData loanType, final Long loanProductId, final String loanProductName,
+            final String loanProductDescription, final boolean isLoanProductLinkedToFloatingRate, final Long fundId, final String fundName,
+            final Long loanPurposeId, final String loanPurposeName, final Long loanOfficerId, final String loanOfficerName,
+            final CurrencyData currencyData, final BigDecimal proposedPrincipal, final BigDecimal principal,
+            final BigDecimal approvedPrincipal, final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance,
+            final Integer termFrequency, final EnumOptionData termPeriodFrequencyType, final Integer numberOfRepayments,
+            final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType, EnumOptionData repaymentFrequencyNthDayType,
+            EnumOptionData repaymentFrequencyDayOfWeekType, final Long transactionStrategyId, final String transactionStrategyName,
+            final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType,
+            final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate,
+            final BigDecimal interestRateDifferential, final EnumOptionData interestCalculationPeriodType,
+            Boolean allowPartialPeriodInterestCalcualtion, final LocalDate expectedFirstRepaymentOnDate,
+            final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
+            final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData loanSummary,
+            final BigDecimal feeChargesDueAtDisbursementCharged, final Boolean syncDisbursementWithMeeting, final Integer loancounter,
+            final Integer loanProductCounter, final Boolean multiDisburseLoan, Boolean canDefineInstallmentAmount,
+            final BigDecimal fixedEmiAmont, final BigDecimal outstandingLoanBalance, final Boolean inArrears,
+            final Integer graceOnArrearsAgeing, final Boolean isNPA, final EnumOptionData daysInMonthType,
+            final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
+            final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement,
+            final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap) {
+
+        final LoanScheduleData repaymentSchedule = null;
+        final Collection<LoanTransactionData> transactions = null;
+        final Collection<LoanChargeData> charges = null;
+        final Collection<CollateralData> collateral = null;
+        final Collection<GuarantorData> guarantors = null;
+        final Collection<NoteData> notes = null;
+        final CalendarData calendarData = null;
+        final Collection<LoanProductData> productOptions = null;
+        final Collection<EnumOptionData> termFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions = null;
+        final Collection<EnumOptionData> repaymentFrequencyDayOfWeekTypeOptions = null;
+        final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions = null;
+        final Collection<EnumOptionData> interestRateFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> amortizationTypeOptions = null;
+        final Collection<EnumOptionData> interestTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationPeriodTypeOptions = null;
+        final Collection<FundData> fundOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final ChargeData chargeTemplate = null;
+        final Collection<StaffData> loanOfficerOptions = null;
+        final Collection<CodeValueData> loanPurposeOptions = null;
+        final Collection<CodeValueData> loanCollateralOptions = null;
+        final Collection<CalendarData> calendarOptions = null;
+        final Collection<PortfolioAccountData> accountLinkingOptions = null;
+        final PortfolioAccountData linkedAccount = null;
+        final Collection<DisbursementData> disbursementData = null;
+        final Collection<LoanTermVariationsData> emiAmountVariations = null;
+        final Map<Long, LoanBorrowerCycleData> memberVariations = null;
+        final LoanProductData product = null;
+        final Collection<ChargeData> overdueCharges = null;
+        final LoanScheduleData originalSchedule = null;
+        final PaidInAdvanceData paidInAdvance = null;
+        final Collection<InterestRatePeriodData> interestRatesPeriods = null;
+
+        return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
+                loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
+                loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal,
+                approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
+                repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType,
+                transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType,
+                annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
+                allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
+                graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged,
+                repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
+                repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions,
+                repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
+                interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
+                loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,
+                accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmont,
+                outstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
+                isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
+                createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
+                maximumGap);
+    }
+
+    /*
+     * Used to combine the associations and template data on top of exist loan
+     * account data
+     */
+    public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final LoanScheduleData repaymentSchedule,
+            final Collection<LoanTransactionData> transactions, final Collection<LoanChargeData> charges,
+            final Collection<CollateralData> collateral, final Collection<GuarantorData> guarantors, final CalendarData calendarData,
+            final Collection<LoanProductData> productOptions, final Collection<EnumOptionData> termFrequencyTypeOptions,
+            final Collection<EnumOptionData> repaymentFrequencyTypeOptions,
+            final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions,
+            final Collection<EnumOptionData> repaymentFrequencyDayOfWeekTypeOptions,
+            final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions,
+            final Collection<EnumOptionData> interestRateFrequencyTypeOptions, final Collection<EnumOptionData> amortizationTypeOptions,
+            final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
+            final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions, final ChargeData chargeTemplate,
+            final Collection<StaffData> loanOfficerOptions, final Collection<CodeValueData> loanPurposeOptions,
+            final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions,
+            final Collection<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
+            final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
+            final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges,
+            final PaidInAdvanceData paidInAdvance, Collection<InterestRatePeriodData> interestRatesPeriods) {
+        LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
+        if (acc.product != null) {
+            loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes();
+        }
+
+        return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
+                acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
+                acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
+                acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
+                acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
+                acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
+                acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
+                acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
+                acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
+                acc.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment,
+                acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary,
+                acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
+                productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions,
+                repaymentFrequencyDayOfWeekTypeOptions, transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions,
+                amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions,
+                chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
+                acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, accountLinkingOptions, linkedAccount,
+                disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
+                acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
+                acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
+                acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
+                acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed,
+                acc.minimumGap, acc.maximumGap);
+    }
+
+    public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
+            final Collection<StaffData> allowedLoanOfficers, final Collection<CalendarData> calendarOptions,
+            final Collection<PortfolioAccountData> accountLinkingOptions) {
+        return associationsAndTemplate(acc, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
+                acc.meeting, productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
+                acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
+                acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
+                acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
+                allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions,
+                acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance,
+                acc.interestRatesPeriods);
+    }
+
+    public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
+
+        return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
+                acc.clientOfficeId, group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
+                acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
+                acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
+                acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
+                acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
+                acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
+                acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
+                acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
+                acc.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment,
+                acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary,
+                acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
+                acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
+                acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
+                acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
+                acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
+                acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
+                acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
+                acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
+                acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
+                acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
+                acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
+                acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
+                acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
+    }
+
+    public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
+
+        final Map<Long, LoanBorrowerCycleData> memberVariations = new HashMap<Long, LoanBorrowerCycleData>();
+        for (Map.Entry<Long, Integer> mapEntry : memberLoanCycle.entrySet()) {
+            BigDecimal principal = null;
+            BigDecimal interestRatePerPeriod = null;
+            Integer numberOfRepayments = null;
+            Long clientId = mapEntry.getKey();
+            Integer loanCycleNumber = mapEntry.getValue();
+            if (acc.product.useBorrowerCycle() && loanCycleNumber != null && loanCycleNumber > 0) {
+                Collection<LoanProductBorrowerCycleVariationData> principalVariationsForBorrowerCycle = acc.product
+                        .getPrincipalVariationsForBorrowerCycle();
+                Collection<LoanProductBorrowerCycleVariationData> interestForVariationsForBorrowerCycle = acc.product
+                        .getInterestRateVariationsForBorrowerCycle();
+                Collection<LoanProductBorrowerCycleVariationData> repaymentVariationsForBorrowerCycle = acc.product
+                        .getNumberOfRepaymentVariationsForBorrowerCycle();
+                principal = fetchLoanCycleDefaultValue(principalVariationsForBorrowerCycle, loanCycleNumber);
+                interestRatePerPeriod = fetchLoanCycleDefaultValue(interestForVariationsForBorrowerCycle, loanCycleNumber);
+                BigDecimal numberofRepaymentval = fetchLoanCycleDefaultValue(repaymentVariationsForBorrowerCycle, loanCycleNumber);
+                if (numberofRepaymentval != null) {
+                    numberOfRepayments = numberofRepaymentval.intValue();
+                }
+            }
+            if (principal == null) {
+                principal = acc.product.getPrincipal();
+            }
+            if (interestRatePerPeriod == null) {
+                interestRatePerPeriod = acc.product.getInterestRatePerPeriod();
+            }
+            if (numberOfRepayments == null) {
+                numberOfRepayments = acc.product.getNumberOfRepayments();
+            }
+            final Integer termFrequency = numberOfRepayments * acc.product.getRepaymentEvery();
+            LoanBorrowerCycleData borrowerCycleData = new LoanBorrowerCycleData(principal, interestRatePerPeriod, numberOfRepayments,
+                    termFrequency);
+            memberVariations.put(clientId, borrowerCycleData);
+        }
+
+        return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
+                acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
+                acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
+                acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
+                acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
+                acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
+                acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
+                acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
+                acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
+                acc.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment,
+                acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary,
+                acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
+                acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
+                acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
+                acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
+                acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
+                acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
+                acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
+                acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
+                acc.maxOutstandingLoanBalance, acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears,
+                acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
+                acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
+                acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
+                acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
+
+    }
+
+    public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData,
+            final CalendarData compoundingCalendarData) {
+
+        final LoanInterestRecalculationData interestRecalculationData = LoanInterestRecalculationData.withCalendarData(
+                acc.interestRecalculationData, calendarData, compoundingCalendarData);
+
+        return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
+                acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
+                acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
+                acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
+                acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
+                acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
+                acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
+                acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
+                acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
+                acc.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment,
+                acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary,
+                acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
+                acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
+                acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
+                acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
+                acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
+                acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
+                acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
+                acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
+                acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
+                acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
+                acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule,
+                acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
+                acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
+    }
+
+    public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
+
+        return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
+                acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
+                acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
+                acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
+                acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
+                acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
+                acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
+                acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
+                acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
+                acc.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment,
+                acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary,
+                acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
+                acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
+                acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
+                acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
+                acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
+                acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
+                acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
+                acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
+                acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
+                acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
+                acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule,
+                acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
+                acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
+    }
+
+    private LoanAccountData(
+            final Long id, //
+            final String accountNo, //
+            final LoanStatusEnumData status, //
+            final String externalId, //
+            final Long clientId,
+            final String clientAccountNo,
+            final String clientName,
+            final Long clientOfficeId, //
+            final GroupGeneralData group,
+            final EnumOptionData loanType,
+            final Long loanProductId,
+            final String loanProductName,
+            final String loanProductDescription, //
+            final boolean isLoanProductLinkedToFloatingRate,
+            final Long fundId,
+            final String fundName,
+            final Long loanPurposeId,
+            final String loanPurposeName, //
+            final Long loanOfficerId,
+            final String loanOfficerName, //
+            final CurrencyData currency,
+            BigDecimal proposedPrincipal,
+            final BigDecimal principal,
+            final BigDecimal approvedPrincipal,
+            final BigDecimal totalOverpaid, //
+            final BigDecimal inArrearsTolerance,
+            final Integer termFrequency, //
+            final EnumOptionData termPeriodFrequencyType,
+            final Integer numberOfRepayments,
+            final Integer repaymentEvery,
+            final EnumOptionData repaymentFrequencyType, //
+            final EnumOptionData repaymentFrequencyNthDayType, final EnumOptionData repaymentFrequencyDayOfWeekType,
+            final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName,
+            final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType,
+            final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate,
+            final BigDecimal interestRateDifferential, final EnumOptionData interestCalculationPeriodType,
+            final Boolean allowPartialPeriodInterestCalcualtion, final LocalDate expectedFirstRepaymentOnDate,
+            final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
+            final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData summary,
+            final BigDecimal feeChargesDueAtDisbursementCharged, final LoanScheduleData repaymentSchedule,
+            final Collection<LoanTransactionData> transactions, final Collection<LoanChargeData> charges,
+            final Collection<CollateralData> collateral, final Collection<GuarantorData> guarantors, final CalendarData meeting,
+            final Collection<LoanProductData> productOptions, final Collection<EnumOptionData> termFrequencyTypeOptions,
+            final Collection<EnumOptionData> repaymentFrequencyTypeOptions,
+            final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions,
+            final Collection<EnumOptionData> repaymentFrequencyDaysOfWeekTypeOptions,
+            final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions,
+            final Collection<EnumOptionData> interestRateFrequencyTypeOptions, final Collection<EnumOptionData> amortizationTypeOptions,
+            final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
+            final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions, final ChargeData chargeTemplate,
+            final Collection<StaffData> loanOfficerOptions, final Collection<CodeValueData> loanPurposeOptions,
+            final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions,
+            final Boolean syncDisbursementWithMeeting, final Integer loanCounter, final Integer loanProductCounter,
+            final Collection<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
+            final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
+            final Boolean multiDisburseLoan, final Boolean canDefineInstallmentAmount, BigDecimal fixedEmiAmount,
+            final BigDecimal maxOutstandingLoanBalance, final Collection<LoanTermVariationsData> emiAmountVariations,
+            final Map<Long, LoanBorrowerCycleData> memberVariations, final LoanProductData product, final Boolean inArrears,
+            final Integer graceOnArrearsAgeing, final Collection<ChargeData> overdueCharges, final Boolean isNPA,
+            final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
+            final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule,
+            final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance,
+            final Collection<InterestRatePeriodData> interestRatesPeriods, final Boolean isVariableInstallmentsAllowed,
+            final Integer minimumGap, final Integer maximumGap) {
+
+        this.id = id;
+        this.accountNo = accountNo;
+        this.status = status;
+        this.externalId = externalId;
+        this.clientId = clientId;
+        this.clientAccountNo = clientAccountNo;
+        this.clientName = clientName;
+        this.clientOfficeId = clientOfficeId;
+        this.group = group;
+        this.loanType = loanType;
+        this.loanProductId = loanProductId;
+        this.loanProductName = loanProductName;
+        this.loanProductDescription = loanProductDescription;
+        this.isLoanProductLinkedToFloatingRate = isLoanProductLinkedToFloatingRate;
+        this.fundId = fundId;
+        this.fundName = fundName;
+        this.loanPurposeId = loanPurposeId;
+        this.loanPurposeName = loanPurposeName;
+        this.loanOfficerId = loanOfficerId;
+        this.loanOfficerName = loanOfficerName;
+        this.currency = currency;
+        this.proposedPrincipal = proposedPrincipal;
+        this.principal = principal;
+        this.approvedPrincipal = approvedPrincipal;
+        this.totalOverpaid = totalOverpaid;
+        this.inArrearsTolerance = inArrearsTolerance;
+        this.termFrequency = termFrequency;
+        this.termPeriodFrequencyType = termPeriodFrequencyType;
+        this.numberOfRepayments = numberOfRepayments;
+        this.repaymentEvery = repaymentEvery;
+        this.repaymentFrequencyType = repaymentFrequencyType;
+        this.repaymentFrequencyNthDayType = repaymentFrequencyNthDayType;
+        this.repaymentFrequencyDayOfWeekType = repaymentFrequencyDayOfWeekType;
+        this.transactionProcessingStrategyId = transactionProcessingStrategyId;
+        this.transactionProcessingStrategyName = transactionProcessingStrategyName;
+        this.amortizationType = amortizationType;
+        this.interestRatePerPeriod = interestRatePerPeriod;
+        this.interestRateFrequencyType = interestRateFrequencyType;
+        this.annualInterestRate = annualInterestRate;
+        this.interestType = interestType;
+        this.isFloatingInterestRate = isFloatingInterestRate;
+        this.interestRateDifferential = interestRateDifferential;
+        this.interestCalculationPeriodType = interestCalculationPeriodType;
+        this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion;
+        this.expectedFirstRepaymentOnDate = expectedFirstRepaymentOnDate;
+        this.graceOnPrincipalPayment = graceOnPrincipalPayment;
+        this.graceOnInterestPayment = graceOnInterestPayment;
+        this.graceOnInterestCharged = graceOnInterestCharged;
+        this.interestChargedFromDate = interestChargedFromDate;
+        this.timeline = timeline;
+        this.feeChargesAtDisbursementCharged = feeChargesDueAtDisbursementCharged;
+        this.syncDisbursementWithMeeting = syncDisbursementWithMeeting;
+
+        // totals
+        this.summary = summary;
+
+        // associations
+        this.repaymentSchedule = repaymentSchedule;
+        this.transactions = transactions;
+        this.charges = charges;
+        this.collateral = collateral;
+        this.guarantors = guarantors;
+        this.meeting = meeting;
+        this.notes = notes;
+
+        // template
+        this.productOptions = productOptions;
+        this.termFrequencyTypeOptions = termFrequencyTypeOptions;
+        this.repaymentFrequencyTypeOptions = repaymentFrequencyTypeOptions;
+        this.repaymentFrequencyNthDayTypeOptions = repaymentFrequencyNthDayTypeOptions;
+        this.repaymentFrequencyDaysOfWeekTypeOptions = repaymentFrequencyDaysOfWeekTypeOptions;
+        this.interestRateFrequencyTypeOptions = interestRateFrequencyTypeOptions;
+        this.amortizationTypeOptions = amortizationTypeOptions;
+        this.interestTypeOptions = interestTypeOptions;
+        this.interestCalculationPeriodTypeOptions = interestCalculationPeriodTypeOptions;
+
+        if (CollectionUtils.isEmpty(transactionProcessingStrategyOptions)) {
+            this.transactionProcessingStrategyOptions = null;
+        } else {
+            this.transactionProcessingStrategyOptions = transactionProcessingStrategyOptions;
+        }
+
+        if (CollectionUtils.isEmpty(fundOptions)) {
+            this.fundOptions = null;
+        } else {
+            this.fundOptions = fundOptions;
+        }
+
+        if (CollectionUtils.isEmpty(chargeOptions)) {
+            this.chargeOptions = null;
+        } else {
+            this.chargeOptions = chargeOptions;
+        }
+
+        if (CollectionUtils.isEmpty(loanOfficerOptions)) {
+            this.loanOfficerOptions = null;
+        } else {
+            this.loanOfficerOptions = loanOfficerOptions;
+        }
+
+        if (CollectionUtils.isEmpty(loanPurposeOptions)) {
+            this.loanPurposeOptions = null;
+        } else {
+            this.loanPurposeOptions = loanPurposeOptions;
+        }
+
+        if (CollectionUtils.isEmpty(loanCollateralOptions)) {
+            this.loanCollateralOptions = null;
+        } else {
+            this.loanCollateralOptions = loanCollateralOptions;
+        }
+
+        if (CollectionUtils.isEmpty(calendarOptions)) {
+            this.calendarOptions = null;
+        } else {
+            this.calendarOptions = calendarOptions;
+        }
+
+        this.loanCounter = loanCounter;
+        this.loanProductCounter = loanProductCounter;
+
+        this.linkedAccount = linkedAccount;
+        this.accountLinkingOptions = accountLinkingOptions;
+        this.disbursementDetails = disbursementDetails;
+        this.multiDisburseLoan = multiDisburseLoan;
+
+        Boolean canDefineEMIAmount = canDefineInstallmentAmount;
+        if (canDefineInstallmentAmount == null) {
+            canDefineEMIAmount = multiDisburseLoan;
+        } else if (multiDisburseLoan != null) {
+            canDefineEMIAmount = canDefineInstallmentAmount || multiDisburseLoan;
+        }
+        this.canDefineInstallmentAmount = canDefineEMIAmount;
+        this.fixedEmiAmount = fixedEmiAmount;
+        this.maxOutstandingLoanBalance = maxOutstandingLoanBalance;
+
+        if (this.status != null && LoanStatus.fromInt(this.status.id().intValue()).isApproved()) {
+            this.canDisburse = true;
+        } else {
+            boolean canDisburse = false;
+            if (this.multiDisburseLoan != null && this.multiDisburseLoan && this.disbursementDetails != null) {
+                for (DisbursementData disbursementData : this.disbursementDetails) {
+                    if (!disbursementData.isDisbursed()) {
+                        canDisburse = true;
+                    }
+                }
+            }
+            this.canDisburse = canDisburse;
+        }
+        this.emiAmountVariations = emiAmountVariations;
+        this.memberVariations = memberVariations;
+        this.product = product;
+        this.inArrears = inArrears;
+        this.graceOnArrearsAgeing = graceOnArrearsAgeing;
+        this.overdueCharges = overdueCharges;
+        this.isNPA = isNPA;
+
+        this.daysInMonthType = daysInMonthType;
+        this.daysInYearType = daysInYearType;
+        this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
+        this.interestRecalculationData = interestRecalculationData;
+        this.originalSchedule = originalSchedule;
+        this.createStandingInstructionAtDisbursement = createStandingInstructionAtDisbursement;
+        this.paidInAdvance = paidInAdvance;
+        if (this.product != null) {
+            this.product.setloanProductConfigurableAttributes(product.getloanProductConfigurableAttributes());
+        }
+        this.interestRatesPeriods = interestRatesPeriods;
+        this.isVariableInstallmentsAllowed = isVariableInstallmentsAllowed;
+        this.minimumGap = minimumGap;
+        this.maximumGap = maximumGap;
+    }
+
+    public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
+        return this.timeline.repaymentScheduleRelatedData(this.currency, this.principal, this.approvedPrincipal, this.inArrearsTolerance,
+                this.feeChargesAtDisbursementCharged);
+    }
+
+    public Long officeId() {
+        Long officeId = this.clientOfficeId;
+        if (officeId == null) {
+            officeId = groupOfficeId();
+        }
+        return officeId;
+    }
+
+    public Long loanOfficerId() {
+        return this.loanOfficerId;
+    }
+
+    public Collection<LoanChargeData> charges() {
+        return this.charges;
+    }
+
+    public Long groupOfficeId() {
+        return this.group == null ? null : this.group.officeId();
+    }
+
+    public Long groupId() {
+        return this.group == null ? null : this.group.getId();
+    }
+
+    public CurrencyData currency() {
+        return this.currency;
+    }
+
+    public Long clientId() {
+        return this.clientId;
+    }
+
+    private static BigDecimal fetchLoanCycleDefaultValue(Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationData,
+            Integer loanCycleNumber) {
+        BigDecimal defaultValue = null;
+        Integer cycleNumberSelected = 0;
+        for (LoanProductBorrowerCycleVariationData data : borrowerCycleVariationData) {
+            if (isLoanCycleValuesWhenConditionEqual(loanCycleNumber, data)
+                    || isLoanCycleValuesWhenConditionGreterthan(loanCycleNumber, cycleNumberSelected, data)) {
+                cycleNumberSelected = data.getBorrowerCycleNumber();
+                defaultValue = data.getDefaultValue();
+            }
+        }
+
+        return defaultValue;
+    }
+
+    private static boolean isLoanCycleValuesWhenConditionGreterthan(Integer loanCycleNumber, Integer cycleNumberSelected,
+            LoanProductBorrowerCycleVariationData data) {
+        return data.getBorrowerCycleNumber() < loanCycleNumber
+                && data.getValueConditionType().equals(LoanProductValueConditionType.GREATERTHAN)
+                && cycleNumberSelected < data.getBorrowerCycleNumber();
+    }
+
+    private static boolean isLoanCycleValuesWhenConditionEqual(Integer loanCycleNumber, LoanProductBorrowerCycleVariationData data) {
+        return data.getBorrowerCycleNumber().equals(loanCycleNumber)
+                && data.getValueConditionType().equals(LoanProductValueConditionType.EQUAL);
+    }
+
+    public LoanProductData product() {
+        return this.product;
+    }
+
+    public void setProduct(LoanProductData product) {
+        this.product = product;
+    }
+
+    public GroupGeneralData groupData() {
+        return this.group;
+    }
+
+    public Long loanProductId() {
+        return this.loanProductId;
+    }
+
+    public BigDecimal getTotalOutstandingAmount() {
+        return this.summary.getTotalOutstanding();
+    }
+
+    public boolean isInterestRecalculationEnabled() {
+        return this.isInterestRecalculationEnabled;
+    }
+
+    public Long getInterestRecalculationDetailId() {
+        if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getId(); }
+        return null;
+    }
+
+    public boolean isActive() {
+        return LoanStatus.fromInt(this.status.id().intValue()).isActive();
+    }
+
+    public boolean isMultiDisburseLoan() {
+        return multiDisburseLoan;
+    }
+
+    public BigDecimal getTotalPaidFeeCharges() {
+        if (this.summary != null) return this.summary.getTotalPaidFeeCharges();
+        return BigDecimal.ZERO;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApplicationTimelineData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApplicationTimelineData.java
new file mode 100644
index 0000000..656d718
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApplicationTimelineData.java
@@ -0,0 +1,151 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object represent the important time-line events of a loan
+ * application and loan.
+ */
+@SuppressWarnings("unused")
+public class LoanApplicationTimelineData {
+
+    private final LocalDate submittedOnDate;
+    private final String submittedByUsername;
+    private final String submittedByFirstname;
+    private final String submittedByLastname;
+    private final LocalDate rejectedOnDate;
+    private final String rejectedByUsername;
+    private final String rejectedByFirstname;
+    private final String rejectedByLastname;
+    private final LocalDate withdrawnOnDate;
+    private final String withdrawnByUsername;
+    private final String withdrawnByFirstname;
+    private final String withdrawnByLastname;
+    private final LocalDate approvedOnDate;
+    private final String approvedByUsername;
+    private final String approvedByFirstname;
+    private final String approvedByLastname;
+    private final LocalDate expectedDisbursementDate;
+    private final LocalDate actualDisbursementDate;
+    private final String disbursedByUsername;
+    private final String disbursedByFirstname;
+    private final String disbursedByLastname;
+    private final LocalDate closedOnDate;
+    private final String closedByUsername;
+    private final String closedByFirstname;
+    private final String closedByLastname;
+    private final LocalDate expectedMaturityDate;
+    private final LocalDate writeOffOnDate;
+    private final String writeOffByUsername;
+    private final String writeOffByFirstname;
+    private final String writeOffByLastname;
+
+    public static LoanApplicationTimelineData templateDefault(final LocalDate expectedDisbursementDate) {
+
+        final LocalDate submittedOnDate = null;
+        final String submittedByUsername = null;
+        final String submittedByFirstname = null;
+        final String submittedByLastname = null;
+        final LocalDate rejectedOnDate = null;
+        final String rejectedByUsername = null;
+        final String rejectedByFirstname = null;
+        final String rejectedByLastname = null;
+        final LocalDate withdrawnOnDate = null;
+        final String withdrawnByUsername = null;
+        final String withdrawnByFirstname = null;
+        final String withdrawnByLastname = null;
+        final LocalDate approvedOnDate = null;
+        final String approvedByUsername = null;
+        final String approvedByFirstname = null;
+        final String approvedByLastname = null;
+        final LocalDate actualDisbursementDate = null;
+        final String disbursedByUsername = null;
+        final String disbursedByFirstname = null;
+        final String disbursedByLastname = null;
+        final LocalDate closedOnDate = null;
+        final String closedByUsername = null;
+        final String closedByFirstname = null;
+        final String closedByLastname = null;
+        final LocalDate expectedMaturityDate = null;
+        final LocalDate writeOffOnDate = null;
+        final String writeOffByUsername = null;
+        final String writeOffByFirstname = null;
+        final String writeOffByLastname = null;
+
+        return new LoanApplicationTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname,
+                rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername,
+                withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname,
+                expectedDisbursementDate, actualDisbursementDate, disbursedByUsername, disbursedByFirstname, disbursedByLastname,
+                closedOnDate, closedByUsername, closedByFirstname, closedByLastname, expectedMaturityDate, writeOffOnDate,
+                writeOffByUsername, writeOffByFirstname, writeOffByLastname);
+    }
+
+    public LoanApplicationTimelineData(final LocalDate submittedOnDate, final String submittedByUsername,
+            final String submittedByFirstname, final String submittedByLastname, final LocalDate rejectedOnDate,
+            final String rejectedByUsername, final String rejectedByFirstname, final String rejectedByLastname,
+            final LocalDate withdrawnOnDate, final String withdrawnByUsername, final String withdrawnByFirstname,
+            final String withdrawnByLastname, final LocalDate approvedOnDate, final String approvedByUsername,
+            final String approvedByFirstname, final String approvedByLastname, final LocalDate expectedDisbursementDate,
+            final LocalDate actualDisbursementDate, final String disbursedByUsername, final String disbursedByFirstname,
+            final String disbursedByLastname, final LocalDate closedOnDate, final String closedByUsername, final String closedByFirstname,
+            final String closedByLastname, final LocalDate expectedMaturityDate, final LocalDate writeOffOnDate,
+            final String writeOffByUsername, final String writeOffByFirstname, final String writeOffByLastname) {
+        this.submittedOnDate = submittedOnDate;
+        this.submittedByUsername = submittedByUsername;
+        this.submittedByFirstname = submittedByFirstname;
+        this.submittedByLastname = submittedByLastname;
+        this.rejectedOnDate = rejectedOnDate;
+        this.rejectedByUsername = rejectedByUsername;
+        this.rejectedByFirstname = rejectedByFirstname;
+        this.rejectedByLastname = rejectedByLastname;
+        this.withdrawnOnDate = withdrawnOnDate;
+        this.withdrawnByUsername = withdrawnByUsername;
+        this.withdrawnByFirstname = withdrawnByFirstname;
+        this.withdrawnByLastname = withdrawnByLastname;
+        this.approvedOnDate = approvedOnDate;
+        this.approvedByUsername = approvedByUsername;
+        this.approvedByFirstname = approvedByFirstname;
+        this.approvedByLastname = approvedByLastname;
+        this.expectedDisbursementDate = expectedDisbursementDate;
+        this.actualDisbursementDate = actualDisbursementDate;
+        this.disbursedByUsername = disbursedByUsername;
+        this.disbursedByFirstname = disbursedByFirstname;
+        this.disbursedByLastname = disbursedByLastname;
+        this.closedOnDate = closedOnDate;
+        this.closedByUsername = closedByUsername;
+        this.closedByFirstname = closedByFirstname;
+        this.closedByLastname = closedByLastname;
+        this.expectedMaturityDate = expectedMaturityDate;
+        this.writeOffOnDate = writeOffOnDate;
+        this.writeOffByUsername = writeOffByUsername;
+        this.writeOffByFirstname = writeOffByFirstname;
+        this.writeOffByLastname = writeOffByLastname;
+    }
+
+    public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData(final CurrencyData currency, final BigDecimal principal,
+            final BigDecimal approvedPrincipal, final BigDecimal inArrearsTolerance, final BigDecimal totalFeeChargesAtDisbursement) {
+        return new RepaymentScheduleRelatedLoanData(this.expectedDisbursementDate, this.actualDisbursementDate, currency, principal,
+                 inArrearsTolerance, totalFeeChargesAtDisbursement);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApprovalData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApprovalData.java
new file mode 100644
index 0000000..5d3a06c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApprovalData.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a loan transaction.
+ */
+public class LoanApprovalData {
+
+    private final LocalDate approvalDate;
+    private final BigDecimal approvalAmount;
+
+    public LoanApprovalData(final BigDecimal approvalAmount, final LocalDate approvalDate) {
+        this.approvalDate = approvalDate;
+        this.approvalAmount = approvalAmount;
+    }
+
+    public LocalDate getApprovalDate() {
+        return this.approvalDate;
+    }
+
+    public BigDecimal getApprovalAmount() {
+        return this.approvalAmount;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanBorrowerCycleData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanBorrowerCycleData.java
new file mode 100755
index 0000000..7a19af4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanBorrowerCycleData.java
@@ -0,0 +1,42 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+public class LoanBorrowerCycleData {
+
+    @SuppressWarnings("unused")
+    private final BigDecimal principal;
+    @SuppressWarnings("unused")
+    private final BigDecimal interestRatePerPeriod;
+    @SuppressWarnings("unused")
+    private final Integer numberOfRepayments;
+    @SuppressWarnings("unused")
+    private final Integer termFrequency;
+
+    public LoanBorrowerCycleData(final BigDecimal principal, final BigDecimal interestRatePerPeriod, final Integer numberOfRepayments,
+            final Integer termFrequency) {
+
+        this.principal = principal;
+        this.interestRatePerPeriod = interestRatePerPeriod;
+        this.numberOfRepayments = numberOfRepayments;
+        this.termFrequency = termFrequency;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java
new file mode 100755
index 0000000..720321b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java
@@ -0,0 +1,411 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object for loan charge data.
+ */
+public class LoanChargeData {
+
+    private final Long id;
+    private final Long chargeId;
+    private final String name;
+    private final EnumOptionData chargeTimeType;
+
+    private final LocalDate dueDate;
+
+    private final EnumOptionData chargeCalculationType;
+
+    private final BigDecimal percentage;
+
+    private final BigDecimal amountPercentageAppliedTo;
+
+    private final CurrencyData currency;
+
+    private final BigDecimal amount;
+
+    private final BigDecimal amountPaid;
+    private final BigDecimal amountWaived;
+    private final BigDecimal amountWrittenOff;
+
+    private final BigDecimal amountOutstanding;
+
+    private final BigDecimal amountOrPercentage;
+
+    private final Collection<ChargeData> chargeOptions;
+
+    private final boolean penalty;
+
+    private final EnumOptionData chargePaymentMode;
+
+    private final boolean paid;
+
+    private final boolean waived;
+
+    private final boolean chargePayable;
+
+    private final Long loanId;
+
+    private final BigDecimal minCap;
+
+    private final BigDecimal maxCap;
+
+    private final Collection<LoanInstallmentChargeData> installmentChargeData;
+
+    private BigDecimal amountAccrued;
+
+    private BigDecimal amountUnrecognized;
+    
+
+    public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
+        return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null, null,
+                null, null, null);
+    }
+
+    /**
+     * used when populating with details from charge definition (for crud on
+     * charges)
+     */
+    public static LoanChargeData newLoanChargeDetails(final Long chargeId, final String name, final CurrencyData currency,
+            final BigDecimal amount, final BigDecimal percentage, final EnumOptionData chargeTimeType,
+            final EnumOptionData chargeCalculationType, final boolean penalty, final EnumOptionData chargePaymentMode,
+            final BigDecimal minCap, final BigDecimal maxCap) {
+        return new LoanChargeData(null, chargeId, name, currency, amount, percentage, chargeTimeType, chargeCalculationType, null, penalty,
+                chargePaymentMode, false, false, null, minCap, maxCap, null, null);
+    }
+
+    public LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
+            final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
+            final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueDate,
+            final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo,
+            final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid, final boolean waived, final Long loanId,
+            final BigDecimal minCap, final BigDecimal maxCap, final BigDecimal amountOrPercentage,
+            Collection<LoanInstallmentChargeData> installmentChargeData) {
+        this.id = id;
+        this.chargeId = chargeId;
+        this.name = name;
+        this.currency = currency;
+        this.amount = amount;
+        this.amountPaid = amountPaid;
+        this.amountWaived = amountWaived;
+        this.amountWrittenOff = amountWrittenOff;
+        this.amountOutstanding = amountOutstanding;
+        this.chargeTimeType = chargeTimeType;
+        this.dueDate = dueDate;
+        this.chargeCalculationType = chargeCalculationType;
+        this.percentage = percentage;
+        this.amountPercentageAppliedTo = amountPercentageAppliedTo;
+        this.penalty = penalty;
+        this.chargePaymentMode = chargePaymentMode;
+        this.paid = paid;
+        this.waived = waived;
+        this.minCap = minCap;
+        this.maxCap = maxCap;
+        if (amountOrPercentage == null) {
+            if (chargeCalculationType != null && chargeCalculationType.getId().intValue() > 1) {
+                this.amountOrPercentage = this.percentage;
+            } else {
+                this.amountOrPercentage = amount;
+            }
+        } else {
+            this.amountOrPercentage = amountOrPercentage;
+        }
+
+        this.chargeOptions = null;
+        this.chargePayable = isChargePayable();
+        this.loanId = loanId;
+        this.installmentChargeData = installmentChargeData;
+        this.amountAccrued = null;
+        this.amountUnrecognized = null;
+    }
+
+    private LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
+            final BigDecimal percentage, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType,
+            final Collection<ChargeData> chargeOptions, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
+            final boolean waived, final Long loanId, final BigDecimal minCap, final BigDecimal maxCap, final BigDecimal amountOrPercentage,
+            Collection<LoanInstallmentChargeData> installmentChargeData) {
+        this.id = id;
+        this.chargeId = chargeId;
+        this.name = name;
+        this.currency = currency;
+        this.amount = amount;
+        this.amountPaid = BigDecimal.ZERO;
+        this.amountWaived = BigDecimal.ZERO;
+        this.amountWrittenOff = BigDecimal.ZERO;
+        this.amountOutstanding = amount;
+        this.chargeTimeType = chargeTimeType;
+        this.dueDate = null;
+        this.chargeCalculationType = chargeCalculationType;
+        this.percentage = percentage;
+        this.amountPercentageAppliedTo = null;
+        this.penalty = penalty;
+        this.chargePaymentMode = chargePaymentMode;
+        this.paid = paid;
+        this.waived = waived;
+
+        if (amountOrPercentage == null) {
+            if (chargeCalculationType != null && chargeCalculationType.getId().intValue() > 1) {
+                this.amountOrPercentage = this.percentage;
+            } else {
+                this.amountOrPercentage = amount;
+            }
+        } else {
+            this.amountOrPercentage = amountOrPercentage;
+        }
+
+        this.chargeOptions = chargeOptions;
+        this.chargePayable = isChargePayable();
+        this.loanId = loanId;
+        this.minCap = minCap;
+        this.maxCap = maxCap;
+        this.installmentChargeData = installmentChargeData;
+        this.amountAccrued = null;
+        this.amountUnrecognized = null;
+    }
+
+    public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOutstanding, EnumOptionData chargeTimeType,
+            final Long loanId, Collection<LoanInstallmentChargeData> installmentChargeData) {
+        this.id = id;
+        this.chargeId = null;
+        this.name = null;
+        this.currency = null;
+        this.amount = null;
+        this.amountPaid = null;
+        this.amountWaived = null;
+        this.amountWrittenOff = null;
+        this.amountOutstanding = amountOutstanding;
+        this.chargeTimeType = chargeTimeType;
+        this.dueDate = dueAsOfDate;
+        this.chargeCalculationType = null;
+        this.percentage = null;
+        this.amountPercentageAppliedTo = null;
+        this.penalty = false;
+        this.chargePaymentMode = null;
+        this.paid = false;
+        this.waived = false;
+        this.amountOrPercentage = null;
+        this.chargeOptions = null;
+        this.chargePayable = false;
+        this.loanId = loanId;
+        this.minCap = null;
+        this.maxCap = null;
+        this.installmentChargeData = installmentChargeData;
+        this.amountAccrued = null;
+        this.amountUnrecognized = null;
+    }
+
+    public LoanChargeData(final Long id, final Long chargeId, final LocalDate dueAsOfDate, EnumOptionData chargeTimeType,
+            final BigDecimal amount, final BigDecimal amountAccrued, final BigDecimal amountWaived, final boolean penalty) {
+        this.id = id;
+        this.chargeId = chargeId;
+        this.name = null;
+        this.currency = null;
+        this.amount = amount;
+        this.amountPaid = null;
+        this.amountWaived = amountWaived;
+        this.amountWrittenOff = null;
+        this.amountOutstanding = null;
+        this.chargeTimeType = chargeTimeType;
+        this.dueDate = dueAsOfDate;
+        this.chargeCalculationType = null;
+        this.percentage = null;
+        this.amountPercentageAppliedTo = null;
+        this.penalty = penalty;
+        this.chargePaymentMode = null;
+        this.paid = false;
+        this.waived = false;
+        this.amountOrPercentage = null;
+        this.chargeOptions = null;
+        this.chargePayable = false;
+        this.loanId = null;
+        this.minCap = null;
+        this.maxCap = null;
+        this.installmentChargeData = null;
+        this.amountAccrued = amountAccrued;
+        this.amountUnrecognized = null;
+    }
+
+    public LoanChargeData(final BigDecimal amountUnrecognized, final LoanChargeData chargeData) {
+        this.id = chargeData.id;
+        this.chargeId = chargeData.chargeId;
+        this.name = null;
+        this.currency = null;
+        this.amount = chargeData.amount;
+        this.amountPaid = null;
+        this.amountWaived = chargeData.amountWaived;
+        this.amountWrittenOff = null;
+        this.amountOutstanding = null;
+        this.chargeTimeType = chargeData.chargeTimeType;
+        this.dueDate = chargeData.dueDate;
+        this.chargeCalculationType = null;
+        this.percentage = null;
+        this.amountPercentageAppliedTo = null;
+        this.penalty = chargeData.penalty;
+        this.chargePaymentMode = null;
+        this.paid = false;
+        this.waived = false;
+        this.amountOrPercentage = null;
+        this.chargeOptions = null;
+        this.chargePayable = false;
+        this.loanId = null;
+        this.minCap = null;
+        this.maxCap = null;
+        this.installmentChargeData = null;
+        this.amountAccrued = chargeData.amountAccrued;
+        this.amountUnrecognized = amountUnrecognized;
+    }
+
+    public LoanChargeData(LoanChargeData chargeData, Collection<LoanInstallmentChargeData> installmentChargeData) {
+        this.id = chargeData.id;
+        this.chargeId = chargeData.chargeId;
+        this.name = chargeData.name;
+        this.currency = chargeData.currency;
+        this.amount = chargeData.amount;
+        this.amountPaid = chargeData.amountPaid;
+        this.amountWaived = chargeData.amountWaived;
+        this.amountWrittenOff = chargeData.amountWrittenOff;
+        this.amountOutstanding = chargeData.amountOutstanding;
+        this.chargeTimeType = chargeData.chargeTimeType;
+        this.dueDate = chargeData.dueDate;
+        this.chargeCalculationType = chargeData.chargeCalculationType;
+        this.percentage = chargeData.percentage;
+        this.amountPercentageAppliedTo = chargeData.amountPercentageAppliedTo;
+        this.penalty = chargeData.penalty;
+        this.chargePaymentMode = chargeData.chargePaymentMode;
+        this.paid = chargeData.paid;
+        this.waived = chargeData.waived;
+        this.minCap = chargeData.minCap;
+        this.maxCap = chargeData.maxCap;
+        this.amountOrPercentage = chargeData.amountOrPercentage;
+        this.chargeOptions = chargeData.chargeOptions;
+        this.chargePayable = chargeData.chargePayable;
+        this.loanId = chargeData.loanId;
+        this.installmentChargeData = installmentChargeData;
+        this.amountAccrued = chargeData.amountAccrued;
+        this.amountUnrecognized = chargeData.amountUnrecognized;
+    }
+
+    public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOrPercentage) {
+        this.id = id;
+        this.chargeId = null;
+        this.name = null;
+        this.currency = null;
+        this.amount = null;
+        this.amountPaid = null;
+        this.amountWaived = null;
+        this.amountWrittenOff = null;
+        this.amountOutstanding = null;
+        this.chargeTimeType = null;
+        this.dueDate = dueAsOfDate;
+        this.chargeCalculationType = null;
+        this.percentage = null;
+        this.amountPercentageAppliedTo = null;
+        this.penalty = false;
+        this.chargePaymentMode = null;
+        this.paid = false;
+        this.waived = false;
+        this.amountOrPercentage = amountOrPercentage;
+        this.chargeOptions = null;
+        this.chargePayable = false;
+        this.loanId = null;
+        this.minCap = null;
+        this.maxCap = null;
+        this.installmentChargeData = null;
+        this.amountAccrued = null;
+        this.amountUnrecognized = null;
+    }
+
+    public boolean isChargePayable() {
+        boolean isAccountTransfer = false;
+        if (this.chargePaymentMode != null) {
+            isAccountTransfer = ChargePaymentMode.fromInt(this.chargePaymentMode.getId().intValue()).isPaymentModeAccountTransfer();
+        }
+        return isAccountTransfer && !this.paid && !this.waived;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public LocalDate getDueDate() {
+        return this.dueDate;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public BigDecimal getAmountOutstanding() {
+        return this.amountOutstanding;
+    }
+
+    public boolean isInstallmentFee() {
+        boolean isInstalmentFee = false;
+        if (this.chargeTimeType != null) {
+            isInstalmentFee = ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isInstalmentFee();
+        }
+        return isInstalmentFee;
+    }
+
+    public BigDecimal amountOrPercentage() {
+        return this.amountOrPercentage;
+    }
+
+    public Collection<LoanInstallmentChargeData> getInstallmentChargeData() {
+        return this.installmentChargeData;
+    }
+
+    public boolean isPenalty() {
+        return this.penalty;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public BigDecimal getAmountAccrued() {
+        return this.amountAccrued;
+    }
+
+    public void updateAmountAccrued(BigDecimal amountAccrued) {
+        this.amountAccrued = amountAccrued;
+    }
+
+    public Long getChargeId() {
+        return this.chargeId;
+    }
+
+    public BigDecimal getAmountWaived() {
+        return this.amountWaived;
+    }
+
+    public BigDecimal getAmountUnrecognized() {
+        return this.amountUnrecognized;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java
new file mode 100755
index 0000000..9978bcd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+public class LoanChargePaidByData {
+
+    private final Long id;
+    private final BigDecimal amount;
+    private final Integer installmentNumber;
+    private final Long chargeId;
+    private final Long transactionId;
+
+    public LoanChargePaidByData(final Long id, final BigDecimal amount, final Integer installmentNumber, final Long chargeId,
+            final Long transactionId) {
+        this.id = id;
+        this.amount = amount;
+        this.installmentNumber = installmentNumber;
+        this.chargeId = chargeId;
+        this.transactionId = transactionId;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public Integer getInstallmentNumber() {
+        return this.installmentNumber;
+    }
+
+    public Long getChargeId() {
+        return this.chargeId;
+    }
+
+    public Long getTransactionId() {
+        return this.transactionId;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidDetail.java
new file mode 100755
index 0000000..b77cf14
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidDetail.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+
+
+public class LoanChargePaidDetail {
+
+    private final Money amount;
+    private final LoanRepaymentScheduleInstallment installment;
+    private final boolean isFeeCharge;
+    
+    public LoanChargePaidDetail( Money amount,LoanRepaymentScheduleInstallment installment,boolean isFeeCharge){
+        this.amount = amount;
+        this.installment = installment;
+        this.isFeeCharge = isFeeCharge;
+    }
+
+    
+    public Money getAmount() {
+        return this.amount;
+    }
+
+    
+    public LoanRepaymentScheduleInstallment getInstallment() {
+        return this.installment;
+    }
+
+    
+    public boolean isFeeCharge() {
+        return this.isFeeCharge;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanConvenienceData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanConvenienceData.java
new file mode 100644
index 0000000..765735f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanConvenienceData.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.loanaccount.data;
+
+public class LoanConvenienceData {
+
+    private final int maxSubmittedOnOffsetFromToday;
+    private final int maxApprovedOnOffsetFromToday;
+    private final int maxDisbursedOnOffsetFromToday;
+    private final int expectedLoanTermInDays;
+    private final int actualLoanTermInDays;
+    private final int expectedLoanTermInMonths;
+    private final int actualLoanTermInMonths;
+
+    public LoanConvenienceData(final int maxSubmittedOnOffsetFromToday, final int maxApprovedOnOffsetFromToday,
+            final int maxDisbursedOnOffsetFromToday, final int expectedLoanTermInDays, final int actualLoanTermInDays,
+            final int expectedLoanTermInMonths, final int actualLoanTermInMonths) {
+        this.maxSubmittedOnOffsetFromToday = maxSubmittedOnOffsetFromToday;
+        this.maxApprovedOnOffsetFromToday = maxApprovedOnOffsetFromToday;
+        this.maxDisbursedOnOffsetFromToday = maxDisbursedOnOffsetFromToday;
+        this.expectedLoanTermInDays = expectedLoanTermInDays;
+        this.actualLoanTermInDays = actualLoanTermInDays;
+        this.expectedLoanTermInMonths = expectedLoanTermInMonths;
+        this.actualLoanTermInMonths = actualLoanTermInMonths;
+    }
+
+    public int getMaxSubmittedOnOffsetFromToday() {
+        return this.maxSubmittedOnOffsetFromToday;
+    }
+
+    public int getMaxApprovedOnOffsetFromToday() {
+        return this.maxApprovedOnOffsetFromToday;
+    }
+
+    public int getMaxDisbursedOnOffsetFromToday() {
+        return this.maxDisbursedOnOffsetFromToday;
+    }
+
+    public int getExpectedLoanTermInDays() {
+        return this.expectedLoanTermInDays;
+    }
+
+    public int getActualLoanTermInDays() {
+        return this.actualLoanTermInDays;
+    }
+
+    public int getExpectedLoanTermInMonths() {
+        return this.expectedLoanTermInMonths;
+    }
+
+    public int getActualLoanTermInMonths() {
+        return this.actualLoanTermInMonths;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInstallmentChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInstallmentChargeData.java
new file mode 100755
index 0000000..008271e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInstallmentChargeData.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+import org.joda.time.LocalDate;
+
+public class LoanInstallmentChargeData {
+
+    private final Integer installmentNumber;
+    private final LocalDate dueDate;
+    private final BigDecimal amount;
+    private final BigDecimal amountOutstanding;
+    private final BigDecimal amountWaived;
+    private final boolean paid;
+    private final boolean waived;
+
+    private BigDecimal amountAccrued;
+    private BigDecimal amountUnrecognized;
+
+    public LoanInstallmentChargeData(final Integer installmentNumber, final LocalDate dueDate, final BigDecimal amount,
+            final BigDecimal amountOutstanding, BigDecimal amountWaived, final boolean paid, final boolean waived) {
+        this.installmentNumber = installmentNumber;
+        this.dueDate = dueDate;
+        this.amount = amount;
+        this.amountOutstanding = amountOutstanding;
+        this.paid = paid;
+        this.waived = waived;
+        this.amountWaived = amountWaived;
+    }
+
+    public LoanInstallmentChargeData(final Integer installmentNumber, final LocalDate dueDate, final BigDecimal amount,
+            final BigDecimal amountOutstanding, final BigDecimal amountWaived, final boolean paid, final boolean waived,
+            final BigDecimal amountAccrued) {
+        this.installmentNumber = installmentNumber;
+        this.dueDate = dueDate;
+        this.amount = amount;
+        this.amountOutstanding = amountOutstanding;
+        this.paid = paid;
+        this.waived = waived;
+        this.amountWaived = amountWaived;
+        this.amountAccrued = amountAccrued;
+    }
+
+    public LoanInstallmentChargeData(final LoanInstallmentChargeData installmentChargeData, final BigDecimal amountUnrecognized) {
+        this.installmentNumber = installmentChargeData.installmentNumber;
+        this.dueDate = installmentChargeData.dueDate;
+        this.amount = installmentChargeData.amount;
+        this.amountOutstanding = installmentChargeData.amountOutstanding;
+        this.paid = installmentChargeData.paid;
+        this.waived = installmentChargeData.waived;
+        this.amountWaived = installmentChargeData.amountWaived;
+        this.amountAccrued = installmentChargeData.amountAccrued;
+        this.amountUnrecognized = amountUnrecognized;
+    }
+
+    public Integer getInstallmentNumber() {
+        return this.installmentNumber;
+    }
+
+    public LocalDate getDueDate() {
+        return this.dueDate;
+    }
+
+    public boolean isPaymentPending() {
+        return !(this.paid || this.waived);
+    }
+
+    public BigDecimal getAmountOutstanding() {
+        return this.amountOutstanding;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public BigDecimal getAmountWaived() {
+        return this.amountWaived;
+    }
+
+    public BigDecimal getAmountAccrued() {
+        return this.amountAccrued;
+    }
+
+    public BigDecimal getAmountUnrecognized() {
+        return this.amountUnrecognized;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java
new file mode 100644
index 0000000..4b432e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.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.portfolio.loanaccount.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.joda.time.LocalDate;
+
+public class LoanInterestRecalculationData {
+
+    private final Long id;
+    private final Long loanId;
+    private final EnumOptionData interestRecalculationCompoundingType;
+    private final EnumOptionData rescheduleStrategyType;
+    @SuppressWarnings("unused")
+    private final CalendarData calendarData;
+    private final EnumOptionData recalculationRestFrequencyType;
+    private final Integer recalculationRestFrequencyInterval;
+    private final LocalDate recalculationRestFrequencyDate;
+    private final EnumOptionData recalculationCompoundingFrequencyType;
+    private final Integer recalculationCompoundingFrequencyInterval;
+    private final LocalDate recalculationCompoundingFrequencyDate;
+    @SuppressWarnings("unused")
+    private final CalendarData compoundingCalendarData;
+
+    public LoanInterestRecalculationData(final Long id, final Long loanId, final EnumOptionData interestRecalculationCompoundingType,
+            final EnumOptionData rescheduleStrategyType, final CalendarData calendarData,
+            final EnumOptionData recalculationRestFrequencyType, final Integer recalculationRestFrequencyInterval,
+            final LocalDate recalculationRestFrequencyDate, final CalendarData compoundingCalendarData,
+            final EnumOptionData recalculationCompoundingFrequencyType, final Integer recalculationCompoundingFrequencyInterval,
+            final LocalDate recalculationCompoundingFrequencyDate) {
+        this.id = id;
+        this.loanId = loanId;
+        this.interestRecalculationCompoundingType = interestRecalculationCompoundingType;
+        this.rescheduleStrategyType = rescheduleStrategyType;
+        this.calendarData = calendarData;
+        this.recalculationRestFrequencyType = recalculationRestFrequencyType;
+        this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval;
+        this.recalculationRestFrequencyDate = recalculationRestFrequencyDate;
+        this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType;
+        this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval;
+        this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate;
+        this.compoundingCalendarData = compoundingCalendarData;
+    }
+
+    public static LoanInterestRecalculationData withCalendarData(final LoanInterestRecalculationData recalculationData,
+            final CalendarData calendarData, CalendarData compoundingCalendarData) {
+        return new LoanInterestRecalculationData(recalculationData.id, recalculationData.loanId,
+                recalculationData.interestRecalculationCompoundingType, recalculationData.rescheduleStrategyType, calendarData,
+                recalculationData.recalculationRestFrequencyType, recalculationData.recalculationRestFrequencyInterval,
+                recalculationData.recalculationRestFrequencyDate, compoundingCalendarData,
+                recalculationData.recalculationCompoundingFrequencyType, recalculationData.recalculationCompoundingFrequencyInterval,
+                recalculationData.recalculationCompoundingFrequencyDate);
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java
new file mode 100755
index 0000000..7ec84c6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java
@@ -0,0 +1,194 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.joda.time.LocalDate;
+
+public class LoanScheduleAccrualData {
+
+    private final Long loanId;
+    private final Long officeId;
+    private final LocalDate accruedTill;
+    private final PeriodFrequencyType repaymentFrequency;
+    private final Integer repayEvery;
+    private final Integer installmentNumber;
+    private final LocalDate dueDate;
+    private final LocalDate fromDate;
+    private final Long repaymentScheduleId;
+    private final Long loanProductId;
+    private final BigDecimal interestIncome;
+    private final BigDecimal feeIncome;
+    private final BigDecimal penaltyIncome;
+    private final BigDecimal waivedInterestIncome;
+    private final BigDecimal accruedInterestIncome;
+    private final BigDecimal accruedFeeIncome;
+    private final BigDecimal accruedPenaltyIncome;
+    private final CurrencyData currencyData;
+    private final LocalDate interestCalculatedFrom;
+
+    private Map<LoanChargeData, BigDecimal> applicableCharges;
+    private BigDecimal dueDateFeeIncome;
+    private BigDecimal dueDatePenaltyIncome;
+    private BigDecimal accruableIncome;
+
+    public LoanScheduleAccrualData(final Long loanId, final Long officeId, final Integer installmentNumber, final LocalDate accruedTill,
+            final PeriodFrequencyType repaymentFrequency, final Integer repayEvery, final LocalDate dueDate, final LocalDate fromDate,
+            final Long repaymentScheduleId, final Long loanProductId, final BigDecimal interestIncome, final BigDecimal feeIncome,
+            final BigDecimal penaltyIncome, final BigDecimal accruedInterestIncome, final BigDecimal accruedFeeIncome,
+            final BigDecimal accruedPenaltyIncome, final CurrencyData currencyData, final LocalDate interestCalculatedFrom,
+            final BigDecimal waivedInterestIncome) {
+        this.loanId = loanId;
+        this.installmentNumber = installmentNumber;
+        this.officeId = officeId;
+        this.accruedTill = accruedTill;
+        this.dueDate = dueDate;
+        this.fromDate = fromDate;
+        this.repaymentScheduleId = repaymentScheduleId;
+        this.loanProductId = loanProductId;
+        this.interestIncome = interestIncome;
+        this.feeIncome = feeIncome;
+        this.penaltyIncome = penaltyIncome;
+        this.accruedFeeIncome = accruedFeeIncome;
+        this.accruedInterestIncome = accruedInterestIncome;
+        this.accruedPenaltyIncome = accruedPenaltyIncome;
+        this.currencyData = currencyData;
+        this.repaymentFrequency = repaymentFrequency;
+        this.repayEvery = repayEvery;
+        this.interestCalculatedFrom = interestCalculatedFrom;
+        this.waivedInterestIncome = waivedInterestIncome;
+    }
+
+    public Long getLoanId() {
+        return this.loanId;
+    }
+
+    public Long getOfficeId() {
+        return this.officeId;
+    }
+
+    public Date getDueDate() {
+        return this.dueDate.toDate();
+    }
+
+    public LocalDate getDueDateAsLocaldate() {
+        return this.dueDate;
+    }
+
+    public Long getRepaymentScheduleId() {
+        return this.repaymentScheduleId;
+    }
+
+    public Long getLoanProductId() {
+        return this.loanProductId;
+    }
+
+    public BigDecimal getInterestIncome() {
+        return this.interestIncome;
+    }
+
+    public BigDecimal getFeeIncome() {
+        return this.feeIncome;
+    }
+
+    public BigDecimal getPenaltyIncome() {
+        return this.penaltyIncome;
+    }
+
+    public BigDecimal getAccruedInterestIncome() {
+        return this.accruedInterestIncome;
+    }
+
+    public BigDecimal getAccruedFeeIncome() {
+        return this.accruedFeeIncome;
+    }
+
+    public BigDecimal getAccruedPenaltyIncome() {
+        return this.accruedPenaltyIncome;
+    }
+
+    public CurrencyData getCurrencyData() {
+        return this.currencyData;
+    }
+
+    public LocalDate getAccruedTill() {
+        return this.accruedTill;
+    }
+
+    public LocalDate getFromDateAsLocaldate() {
+        return this.fromDate;
+    }
+
+    public PeriodFrequencyType getRepaymentFrequency() {
+        return this.repaymentFrequency;
+    }
+
+    public Integer getRepayEvery() {
+        return this.repayEvery;
+    }
+
+    public LocalDate getInterestCalculatedFrom() {
+        return this.interestCalculatedFrom;
+    }
+
+    public Integer getInstallmentNumber() {
+        return this.installmentNumber;
+    }
+
+    public Map<LoanChargeData, BigDecimal> getApplicableCharges() {
+        return this.applicableCharges;
+    }
+
+    public BigDecimal getDueDateFeeIncome() {
+        return this.dueDateFeeIncome;
+    }
+
+    public BigDecimal getDueDatePenaltyIncome() {
+        return this.dueDatePenaltyIncome;
+    }
+
+    public void updateChargeDetails(final Map<LoanChargeData, BigDecimal> applicableCharges, final BigDecimal dueDateFeeIncome,
+            final BigDecimal dueDatePenaltyIncome) {
+        this.applicableCharges = applicableCharges;
+        this.dueDateFeeIncome = dueDateFeeIncome;
+        this.dueDatePenaltyIncome = dueDatePenaltyIncome;
+    }
+
+    
+    public BigDecimal getWaivedInterestIncome() {
+        return this.waivedInterestIncome;
+    }
+
+    
+    public BigDecimal getAccruableIncome () {
+        return this.accruableIncome;
+    }
+
+    
+    public void updateAccruableIncome (BigDecimal accruableIncome ) {
+        this.accruableIncome = accruableIncome ;
+    }
+
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanStatusEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanStatusEnumData.java
new file mode 100644
index 0000000..1409a47
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanStatusEnumData.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.portfolio.loanaccount.data;
+
+/**
+ * Immutable data object represent loan status enumerations.
+ */
+@SuppressWarnings("unused")
+public class LoanStatusEnumData {
+
+    private final Long id;
+    private final String code;
+    private final String value;
+    private final boolean pendingApproval;
+    private final boolean waitingForDisbursal;
+    private final boolean active;
+    private final boolean closedObligationsMet;
+    private final boolean closedWrittenOff;
+    private final boolean closedRescheduled;
+    private final boolean closed;
+    private final boolean overpaid;
+
+    public LoanStatusEnumData(final Long id, final String code, final String value) {
+        this.id = id;
+        this.code = code;
+        this.value = value;
+        this.pendingApproval = Long.valueOf(100).equals(this.id);
+        this.waitingForDisbursal = Long.valueOf(200).equals(this.id);
+        this.active = Long.valueOf(300).equals(this.id);
+        this.closedObligationsMet = Long.valueOf(600).equals(this.id);
+        this.closedWrittenOff = Long.valueOf(601).equals(this.id);
+        this.closedRescheduled = Long.valueOf(602).equals(this.id);
+        this.closed = this.closedObligationsMet || this.closedWrittenOff || this.closedRescheduled;
+        this.overpaid = Long.valueOf(700).equals(this.id);
+    }
+
+    public Long id() {
+        return this.id;
+    }
+
+    public String code() {
+        return this.code;
+    }
+
+    public String value() {
+        return this.value;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java
new file mode 100644
index 0000000..9f05608
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.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.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing loan summary information.
+ */
+@SuppressWarnings("unused")
+public class LoanSummaryData {
+
+    private final CurrencyData currency;
+    private final BigDecimal principalDisbursed;
+    private final BigDecimal principalPaid;
+    private final BigDecimal principalWrittenOff;
+    private final BigDecimal principalOutstanding;
+    private final BigDecimal principalOverdue;
+    private final BigDecimal interestCharged;
+    private final BigDecimal interestPaid;
+    private final BigDecimal interestWaived;
+    private final BigDecimal interestWrittenOff;
+    private final BigDecimal interestOutstanding;
+    private final BigDecimal interestOverdue;
+    private final BigDecimal feeChargesCharged;
+    private final BigDecimal feeChargesDueAtDisbursementCharged;
+    private final BigDecimal feeChargesPaid;
+    private final BigDecimal feeChargesWaived;
+    private final BigDecimal feeChargesWrittenOff;
+    private final BigDecimal feeChargesOutstanding;
+    private final BigDecimal feeChargesOverdue;
+    private final BigDecimal penaltyChargesCharged;
+    private final BigDecimal penaltyChargesPaid;
+    private final BigDecimal penaltyChargesWaived;
+    private final BigDecimal penaltyChargesWrittenOff;
+    private final BigDecimal penaltyChargesOutstanding;
+    private final BigDecimal penaltyChargesOverdue;
+    private final BigDecimal totalExpectedRepayment;
+    private final BigDecimal totalRepayment;
+    private final BigDecimal totalExpectedCostOfLoan;
+    private final BigDecimal totalCostOfLoan;
+    private final BigDecimal totalWaived;
+    private final BigDecimal totalWrittenOff;
+    private final BigDecimal totalOutstanding;
+    private final BigDecimal totalOverdue;
+    private final LocalDate overdueSinceDate;
+
+    public LoanSummaryData(final CurrencyData currency, final BigDecimal principalDisbursed, final BigDecimal principalPaid,
+            final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding, final BigDecimal principalOverdue,
+            final BigDecimal interestCharged, final BigDecimal interestPaid, final BigDecimal interestWaived,
+            final BigDecimal interestWrittenOff, final BigDecimal interestOutstanding, final BigDecimal interestOverdue,
+            final BigDecimal feeChargesCharged, final BigDecimal feeChargesDueAtDisbursementCharged, final BigDecimal feeChargesPaid,
+            final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff, final BigDecimal feeChargesOutstanding,
+            final BigDecimal feeChargesOverdue, final BigDecimal penaltyChargesCharged, final BigDecimal penaltyChargesPaid,
+            final BigDecimal penaltyChargesWaived, final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesOutstanding,
+            final BigDecimal penaltyChargesOverdue, final BigDecimal totalExpectedRepayment, final BigDecimal totalRepayment,
+            final BigDecimal totalExpectedCostOfLoan, final BigDecimal totalCostOfLoan, final BigDecimal totalWaived,
+            final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding, final BigDecimal totalOverdue,
+            final LocalDate overdueSinceDate) {
+        this.currency = currency;
+        this.principalDisbursed = principalDisbursed;
+        this.principalPaid = principalPaid;
+        this.principalWrittenOff = principalWrittenOff;
+        this.principalOutstanding = principalOutstanding;
+        this.principalOverdue = principalOverdue;
+        this.interestCharged = interestCharged;
+        this.interestPaid = interestPaid;
+        this.interestWaived = interestWaived;
+        this.interestWrittenOff = interestWrittenOff;
+        this.interestOutstanding = interestOutstanding;
+        this.interestOverdue = interestOverdue;
+        this.feeChargesCharged = feeChargesCharged;
+        this.feeChargesDueAtDisbursementCharged = feeChargesDueAtDisbursementCharged;
+        this.feeChargesPaid = feeChargesPaid;
+        this.feeChargesWaived = feeChargesWaived;
+        this.feeChargesWrittenOff = feeChargesWrittenOff;
+        this.feeChargesOutstanding = feeChargesOutstanding;
+        this.feeChargesOverdue = feeChargesOverdue;
+        this.penaltyChargesCharged = penaltyChargesCharged;
+        this.penaltyChargesPaid = penaltyChargesPaid;
+        this.penaltyChargesWaived = penaltyChargesWaived;
+        this.penaltyChargesWrittenOff = penaltyChargesWrittenOff;
+        this.penaltyChargesOutstanding = penaltyChargesOutstanding;
+        this.penaltyChargesOverdue = penaltyChargesOverdue;
+        this.totalExpectedRepayment = totalExpectedRepayment;
+        this.totalRepayment = totalRepayment;
+        this.totalExpectedCostOfLoan = totalExpectedCostOfLoan;
+        this.totalCostOfLoan = totalCostOfLoan;
+        this.totalWaived = totalWaived;
+        this.totalWrittenOff = totalWrittenOff;
+        this.totalOutstanding = totalOutstanding;
+        this.totalOverdue = totalOverdue;
+        this.overdueSinceDate = overdueSinceDate;
+    }
+
+    public BigDecimal getTotalOutstanding() {
+        return this.totalOutstanding;
+    }
+    
+    public BigDecimal getTotalPaidFeeCharges() {
+        return feeChargesPaid ;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java
new file mode 100644
index 0000000..2113666
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
+import org.joda.time.LocalDate;
+
+public class LoanTermVariationsData implements Comparable<LoanTermVariationsData> {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    private final EnumOptionData termType;
+    private final LocalDate termVariationApplicableFrom;
+    private final BigDecimal decimalValue;
+    private final LocalDate dateValue;
+    private final boolean isSpecificToInstallment;
+    private Boolean isProcessed;
+
+    public LoanTermVariationsData(final Long id, final EnumOptionData termType, final LocalDate termVariationApplicableFrom,
+            final BigDecimal decimalValue, final LocalDate dateValue, final boolean isSpecificToInstallment) {
+        this.id = id;
+        this.termType = termType;
+        this.termVariationApplicableFrom = termVariationApplicableFrom;
+        this.decimalValue = decimalValue;
+        this.dateValue = dateValue;
+        this.isSpecificToInstallment = isSpecificToInstallment;
+    }
+
+    public LoanTermVariationsData(final EnumOptionData termType, final LocalDate termVariationApplicableFrom,
+            final BigDecimal decimalValue, LocalDate dateValue, final boolean isSpecificToInstallment) {
+        this.id = null;
+        this.termType = termType;
+        this.termVariationApplicableFrom = termVariationApplicableFrom;
+        this.decimalValue = decimalValue;
+        this.dateValue = dateValue;
+        this.isSpecificToInstallment = isSpecificToInstallment;
+    }
+
+    public EnumOptionData getTermType() {
+        return this.termType;
+    }
+
+    public LoanTermVariationType getTermVariationType() {
+        return LoanTermVariationType.fromInt(this.termType.getId().intValue());
+    }
+
+    public LocalDate getTermApplicableFrom() {
+        return this.termVariationApplicableFrom;
+    }
+
+    public BigDecimal getDecimalValue() {
+        return this.decimalValue;
+    }
+
+    public boolean isApplicable(final LocalDate fromDate, final LocalDate dueDate) {
+        return occursOnDayFromAndUpTo(fromDate, dueDate, this.termVariationApplicableFrom);
+    }
+
+    private boolean occursOnDayFromAndUpTo(final LocalDate fromNotInclusive, final LocalDate upToInclusive, final LocalDate target) {
+        return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToInclusive);
+    }
+
+    public boolean isApplicable(final LocalDate fromDate) {
+        return occursBefore(fromDate, this.termVariationApplicableFrom);
+    }
+
+    private boolean occursBefore(final LocalDate date, final LocalDate target) {
+        return target != null && target.isBefore(date);
+    }
+
+    public LocalDate getDateValue() {
+        return this.dateValue;
+    }
+
+    public boolean isSpecificToInstallment() {
+        return this.isSpecificToInstallment;
+    }
+
+    public Boolean isProcessed() {
+        return this.isProcessed == null ? false : this.isProcessed;
+    }
+
+    public void setProcessed(Boolean isProcessed) {
+        this.isProcessed = isProcessed;
+    }
+
+    @Override
+    public int compareTo(LoanTermVariationsData o) {
+        int comparsion = getTermApplicableFrom().compareTo(o.getTermApplicableFrom());
+        if (comparsion == 0) {
+            if (o.getTermVariationType().isDueDateVariation() || o.getTermVariationType().isInsertInstallment()) {
+                comparsion = 1;
+            }
+        }
+        return comparsion;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java
new file mode 100644
index 0000000..510dd1d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java
@@ -0,0 +1,123 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.joda.time.LocalDate;
+
+public class LoanTermVariationsDataWrapper {
+
+    private final List<LoanTermVariationsData> exceptionData;
+    private final ListIterator<LoanTermVariationsData> iterator;
+    private final List<LoanTermVariationsData> interestRateChanges;
+    private final List<LoanTermVariationsData> dueDateVariation;
+    private final ListIterator<LoanTermVariationsData> dueDateIterator;
+
+    public LoanTermVariationsDataWrapper(final List<LoanTermVariationsData> exceptionData) {
+        if (exceptionData == null) {
+            this.exceptionData = new ArrayList<>(1);
+        } else {
+            this.exceptionData = exceptionData;
+            Collections.sort(this.exceptionData);
+        }
+        this.interestRateChanges = new ArrayList<>();
+        this.dueDateVariation = new ArrayList<>();
+        for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) {
+            if (loanTermVariationsData.getTermVariationType().isInterestRateVariation()) {
+                this.interestRateChanges.add(loanTermVariationsData);
+            } else if (loanTermVariationsData.getTermVariationType().isDueDateVariation()) {
+                this.dueDateVariation.add(loanTermVariationsData);
+            }
+        }
+        this.exceptionData.removeAll(this.interestRateChanges);
+        this.exceptionData.removeAll(this.dueDateVariation);
+        iterator = this.exceptionData.listIterator();
+        dueDateIterator = this.dueDateVariation.listIterator();
+    }
+
+    public boolean hasVariation(final LocalDate date) {
+        ListIterator<LoanTermVariationsData> iterator = this.iterator;
+        return hasNext(date, iterator);
+    }
+
+    private boolean hasNext(final LocalDate date, ListIterator<LoanTermVariationsData> iterator) {
+        boolean hasVariation = false;
+        if (iterator.hasNext()) {
+            LoanTermVariationsData loanTermVariationsData = iterator.next();
+            if (!loanTermVariationsData.getTermApplicableFrom().isAfter(date)) {
+                hasVariation = true;
+            }
+            iterator.previous();
+        }
+        return hasVariation;
+    }
+
+    public boolean hasDueDateVariation(final LocalDate date) {
+        ListIterator<LoanTermVariationsData> iterator = this.dueDateIterator;
+        return hasNext(date, iterator);
+    }
+
+    public LoanTermVariationsData nextVariation() {
+        return this.iterator.next();
+    }
+
+    public LoanTermVariationsData nextDueDateVariation() {
+        return this.dueDateIterator.next();
+    }
+
+    public List<LoanTermVariationsData> getInterestRateChanges() {
+        return this.interestRateChanges;
+    }
+
+    public List<LoanTermVariationsData> getDueDateVariation() {
+        return this.dueDateVariation;
+    }
+
+    public List<LoanTermVariationsData> getExceptionData() {
+        return this.exceptionData;
+    }
+
+    public int adjustNumberOfRepayments() {
+        int repaymetsForAdjust = 0;
+        for (LoanTermVariationsData loanTermVariations : this.exceptionData) {
+            if (loanTermVariations.getTermVariationType().isInsertInstallment()) {
+                repaymetsForAdjust++;
+            } else if (loanTermVariations.getTermVariationType().isDeleteInstallment()) {
+                repaymetsForAdjust--;
+            }
+        }
+        return repaymetsForAdjust;
+    }
+
+    public LoanTermVariationsData fetchLoanTermDueDateVariationsData(final LocalDate onDate) {
+        LoanTermVariationsData data = null;
+        for (LoanTermVariationsData termVariationsData : this.dueDateVariation) {
+            if (onDate.isEqual(termVariationsData.getTermApplicableFrom())) {
+                data = termVariationsData;
+                break;
+            }
+        }
+        return data;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
new file mode 100644
index 0000000..8286c2d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
@@ -0,0 +1,227 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a loan transaction.
+ */
+public class LoanTransactionData {
+
+    private final Long id;
+    private final Long officeId;
+    private final String officeName;
+
+    private final LoanTransactionEnumData type;
+
+    private final LocalDate date;
+
+    private final CurrencyData currency;
+    private final PaymentDetailData paymentDetailData;
+
+    private final BigDecimal amount;
+    private final BigDecimal principalPortion;
+    private final BigDecimal interestPortion;
+    private final BigDecimal feeChargesPortion;
+    private final BigDecimal penaltyChargesPortion;
+    private final BigDecimal overpaymentPortion;
+    private final BigDecimal unrecognizedIncomePortion;
+    private final String externalId;
+    private final AccountTransferData transfer;
+    private final BigDecimal fixedEmiAmount;
+    private final BigDecimal outstandingLoanBalance;
+    @SuppressWarnings("unused")
+    private final LocalDate submittedOnDate;
+    private final boolean manuallyReversed;
+    @SuppressWarnings("unused")
+	private final LocalDate possibleNextRepaymentDate;
+
+    // templates
+    final Collection<PaymentTypeData> paymentTypeOptions;
+
+    public static LoanTransactionData templateOnTop(final LoanTransactionData loanTransactionData,
+            final Collection<PaymentTypeData> paymentTypeOptions) {
+        return new LoanTransactionData(loanTransactionData.id, loanTransactionData.officeId, loanTransactionData.officeName,
+                loanTransactionData.type, loanTransactionData.paymentDetailData, loanTransactionData.currency, loanTransactionData.date,
+                loanTransactionData.amount, loanTransactionData.principalPortion, loanTransactionData.interestPortion,
+                loanTransactionData.feeChargesPortion, loanTransactionData.penaltyChargesPortion, loanTransactionData.overpaymentPortion,
+                loanTransactionData.unrecognizedIncomePortion, paymentTypeOptions, loanTransactionData.externalId,
+                loanTransactionData.transfer, loanTransactionData.fixedEmiAmount, loanTransactionData.outstandingLoanBalance,
+                loanTransactionData.manuallyReversed);
+
+    }
+
+    public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
+            final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
+            final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
+            final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final String externalId,
+            final AccountTransferData transfer, BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,
+            final BigDecimal unrecognizedIncomePortion,final boolean manuallyReversed) {
+        this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, principalPortion, interestPortion,
+                feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion, null, externalId, transfer,
+                fixedEmiAmount, outstandingLoanBalance,manuallyReversed);
+    }
+ 
+    public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
+            final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
+            final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
+            final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, BigDecimal unrecognizedIncomePortion,
+            final Collection<PaymentTypeData> paymentTypeOptions, final String externalId, final AccountTransferData transfer,
+            final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,boolean manuallyReversed) {
+        this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, principalPortion, interestPortion,
+                feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion, paymentTypeOptions, externalId,
+                transfer, fixedEmiAmount, outstandingLoanBalance, null,manuallyReversed);
+    }
+
+    public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
+            final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
+            final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
+            final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final BigDecimal unrecognizedIncomePortion,
+            final String externalId, final AccountTransferData transfer, BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,
+            LocalDate submittedOnDate,final boolean manuallyReversed) {
+        this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, principalPortion, interestPortion,
+                feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion, null, externalId, transfer,
+                fixedEmiAmount, outstandingLoanBalance, submittedOnDate,manuallyReversed);
+    }
+
+    public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
+            final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
+            final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
+            final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final BigDecimal unrecognizedIncomePortion,
+            final Collection<PaymentTypeData> paymentTypeOptions, final String externalId, final AccountTransferData transfer,
+            final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, final LocalDate submittedOnDate,final boolean manuallyReversed) {
+        this.id = id;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.type = transactionType;
+        this.paymentDetailData = paymentDetailData;
+        this.currency = currency;
+        this.date = date;
+        this.amount = amount;
+        this.principalPortion = principalPortion;
+        this.interestPortion = interestPortion;
+        this.feeChargesPortion = feeChargesPortion;
+        this.penaltyChargesPortion = penaltyChargesPortion;
+        this.unrecognizedIncomePortion = unrecognizedIncomePortion;
+        this.paymentTypeOptions = paymentTypeOptions;
+        this.externalId = externalId;
+        this.transfer = transfer;
+        this.overpaymentPortion = overpaymentPortion;
+        this.fixedEmiAmount = fixedEmiAmount;
+        this.outstandingLoanBalance = outstandingLoanBalance;
+        this.submittedOnDate = submittedOnDate;
+        this.manuallyReversed = manuallyReversed;
+        this.possibleNextRepaymentDate = null;
+    }
+
+    public LoanTransactionData(Long id, LoanTransactionEnumData transactionType, LocalDate date, BigDecimal totalAmount,
+            BigDecimal principalPortion, BigDecimal interestPortion, BigDecimal feeChargesPortion, BigDecimal penaltyChargesPortion,
+            BigDecimal overPaymentPortion, BigDecimal unrecognizedIncomePortion, BigDecimal outstandingLoanBalance,final boolean manuallyReversed) {
+        this(id, null, null, transactionType, null, null, date, totalAmount, principalPortion, interestPortion, feeChargesPortion,
+                penaltyChargesPortion, overPaymentPortion, unrecognizedIncomePortion, null, null, null, null, outstandingLoanBalance, null,
+                manuallyReversed);
+    }
+    
+    public static LoanTransactionData LoanTransactionDataForDisbursalTemplate(final LoanTransactionEnumData transactionType, final LocalDate expectedDisbursedOnLocalDateForTemplate, 
+			final BigDecimal disburseAmountForTemplate,	final Collection<PaymentTypeData> paymentOptions,
+			final BigDecimal retriveLastEmiAmount, final LocalDate possibleNextRepaymentDate) {
+		    final Long id = null;
+		    final Long officeId = null;
+		    final String officeName = null;
+		    final PaymentDetailData paymentDetailData = null;
+		    final CurrencyData currency = null;
+		    final BigDecimal unrecognizedIncomePortion = null;
+		    final BigDecimal principalPortion = null;;
+		    final BigDecimal interestPortion = null;
+		    final BigDecimal feeChargesPortion = null;
+		    final BigDecimal penaltyChargesPortion = null;
+		    final BigDecimal overpaymentPortion = null;
+		    final String externalId = null;
+		    final BigDecimal outstandingLoanBalance = null;
+		    final AccountTransferData transfer = null;
+		    final LocalDate submittedOnDate = null;
+		    final boolean manuallyReversed = false;
+			return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currency, expectedDisbursedOnLocalDateForTemplate,
+					disburseAmountForTemplate, principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overpaymentPortion,	unrecognizedIncomePortion, 
+					paymentOptions, transfer, externalId, retriveLastEmiAmount, outstandingLoanBalance, submittedOnDate, manuallyReversed, possibleNextRepaymentDate);
+		
+	}
+
+    private LoanTransactionData(Long id , final Long officeId, final String officeName, LoanTransactionEnumData transactionType, final PaymentDetailData paymentDetailData,
+    		final CurrencyData currency, final LocalDate date,	BigDecimal amount, final BigDecimal principalPortion, final BigDecimal interestPortion, 
+    		final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, BigDecimal unrecognizedIncomePortion,	Collection<PaymentTypeData> paymentOptions,
+    		final AccountTransferData transfer, final String externalId, final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, 
+    		final LocalDate submittedOnDate, final boolean manuallyReversed, final LocalDate possibleNextRepaymentDate) {
+    	 this.id = id;
+         this.officeId = officeId;
+         this.officeName = officeName;
+         this.type = transactionType;
+         this.paymentDetailData = paymentDetailData;
+         this.currency = currency;
+         this.date = date;
+         this.amount = amount;
+         this.principalPortion = principalPortion;
+         this.interestPortion = interestPortion;
+         this.feeChargesPortion = feeChargesPortion;
+         this.penaltyChargesPortion = penaltyChargesPortion;
+         this.unrecognizedIncomePortion = unrecognizedIncomePortion;
+         this.paymentTypeOptions = paymentOptions;
+         this.externalId = externalId;
+         this.transfer = transfer;
+         this.overpaymentPortion = overpaymentPortion;
+         this.fixedEmiAmount = fixedEmiAmount;
+         this.outstandingLoanBalance = outstandingLoanBalance;
+         this.submittedOnDate = submittedOnDate;
+         this.manuallyReversed = manuallyReversed;
+         this.possibleNextRepaymentDate = possibleNextRepaymentDate;
+	}
+
+	
+
+	public LocalDate dateOf() {
+        return this.date;
+    }
+
+    public boolean isNotDisbursement() {
+        return Integer.valueOf(1).equals(this.type.id());
+    }
+
+    
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    
+    public BigDecimal getUnrecognizedIncomePortion() {
+        return this.unrecognizedIncomePortion;
+    }
+
+    
+    public BigDecimal getInterestPortion() {
+        return this.interestPortion;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
new file mode 100644
index 0000000..ed17561
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.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.portfolio.loanaccount.data;
+
+/**
+ * Immutable data object represent loan status enumerations.
+ */
+@SuppressWarnings("unused")
+public class LoanTransactionEnumData {
+
+    private final Long id;
+    private final String code;
+    private final String value;
+
+    private final boolean disbursement;
+    private final boolean repaymentAtDisbursement;
+    private final boolean repayment;
+    private final boolean contra;
+    private final boolean waiveInterest;
+    private final boolean waiveCharges;
+    private final boolean accrual;
+    private final boolean writeOff;
+    private final boolean recoveryRepayment;
+    private final boolean initiateTransfer;
+    private final boolean approveTransfer;
+    private final boolean withdrawTransfer;
+    private final boolean rejectTransfer;
+    private final boolean chargePayment;
+    private final boolean refund;
+    private final boolean refundForActiveLoans;
+
+    public LoanTransactionEnumData(final Long id, final String code, final String value) {
+        this.id = id;
+        this.code = code;
+        this.value = value;
+        this.disbursement = Long.valueOf(1).equals(this.id);
+        this.repaymentAtDisbursement = Long.valueOf(5).equals(this.id);
+        this.repayment = Long.valueOf(2).equals(this.id);
+        this.contra = Long.valueOf(3).equals(this.id);
+        this.waiveInterest = Long.valueOf(4).equals(this.id);
+        this.waiveCharges = Long.valueOf(9).equals(this.id);
+        this.accrual = Long.valueOf(10).equals(this.id);
+        this.writeOff = Long.valueOf(6).equals(this.id);
+        this.recoveryRepayment = Long.valueOf(8).equals(this.id);
+        this.initiateTransfer = Long.valueOf(12).equals(this.id);
+        this.approveTransfer = Long.valueOf(13).equals(this.id);
+        this.withdrawTransfer = Long.valueOf(14).equals(this.id);
+        this.rejectTransfer = Long.valueOf(15).equals(this.id);
+        this.refund = Long.valueOf(16).equals(this.id);
+        this.chargePayment = Long.valueOf(17).equals(this.id);
+        this.refundForActiveLoans = Long.valueOf(18).equals(this.id);
+    }
+
+    public Long id() {
+        return this.id;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    /**
+     * Returns true if the transaction involves either a payment (disbursement)
+     * or a receipt (repayments, repayments during disbursement etc)
+     * 
+     * @return
+     */
+    public boolean isPaymentOrReceipt() {
+        if (isDisbursement() || isRepayment() || isRepaymentAtDisbursement() || isRecoveryRepayment()) { return true; }
+        return false;
+    }
+
+    public boolean isDisbursement() {
+        return this.disbursement;
+    }
+
+    public boolean isRepaymentAtDisbursement() {
+        return this.repaymentAtDisbursement;
+    }
+
+    public boolean isRepayment() {
+        return this.repayment;
+    }
+
+    public boolean isWaiveInterest() {
+        return this.waiveInterest;
+    }
+
+    public boolean isWaiveCharges() {
+        return this.waiveCharges;
+    }
+
+    public boolean isWriteOff() {
+        return this.writeOff;
+    }
+
+    public boolean isRecoveryRepayment() {
+        return this.recoveryRepayment;
+    }
+
+    public boolean isAccrual() {
+        return this.accrual;
+    }
+
+    public boolean isInitiateTransfer() {
+        return this.initiateTransfer;
+    }
+
+    public boolean isApproveTransfer() {
+        return this.approveTransfer;
+    }
+
+    public boolean isWithdrawTransfer() {
+        return this.withdrawTransfer;
+    }
+
+    public boolean isRejectTransfer() {
+        return this.rejectTransfer;
+    }
+
+    public boolean isChargePayment() {
+        return this.chargePayment;
+    }
+
+    public boolean isRefund() {
+        return this.refund;
+    }
+    
+    public boolean isRefundForActiveLoans() {
+        return this.refundForActiveLoans;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/PaidInAdvanceData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/PaidInAdvanceData.java
new file mode 100644
index 0000000..2d22625
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/PaidInAdvanceData.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.loanaccount.data;
+
+import java.math.BigDecimal;
+
+public class PaidInAdvanceData {
+        
+        private final BigDecimal paidInAdvance;
+        
+        public PaidInAdvanceData(final BigDecimal paidInAdvance) {
+                this.paidInAdvance = paidInAdvance;
+        }
+
+        public BigDecimal getPaidInAdvance() {
+                return paidInAdvance;
+        }
+        
+        
+}
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
new file mode 100644
index 0000000..446ee3c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing repayment schedule related information.
+ */
+public class RepaymentScheduleRelatedLoanData {
+
+    private final LocalDate expectedDisbursementDate;
+    private final LocalDate actualDisbursementDate;
+    private final CurrencyData currency;
+    private final BigDecimal principal;
+    private final BigDecimal inArrearsTolerance;
+    private final BigDecimal totalFeeChargesAtDisbursement;
+
+    public RepaymentScheduleRelatedLoanData(final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
+            final CurrencyData currency, final BigDecimal principal,
+            final BigDecimal inArrearsTolerance, final BigDecimal totalFeeChargesAtDisbursement) {
+        this.expectedDisbursementDate = expectedDisbursementDate;
+        this.actualDisbursementDate = actualDisbursementDate;
+        this.currency = currency;
+        this.principal = principal;
+        this.inArrearsTolerance = inArrearsTolerance;
+        this.totalFeeChargesAtDisbursement = totalFeeChargesAtDisbursement;
+    }
+
+    public LocalDate disbursementDate() {
+        LocalDate disbursementDate = this.expectedDisbursementDate;
+        if (this.actualDisbursementDate != null) {
+            disbursementDate = this.actualDisbursementDate;
+        }
+        return disbursementDate;
+    }
+
+    public BigDecimal amount() {
+        return this.principal;
+    }
+
+    public boolean isDisbursed() {
+        return this.actualDisbursementDate != null;
+    }
+
+    public CurrencyData getCurrency() {
+        return this.currency;
+    }
+
+    public BigDecimal getInArrearsTolerance() {
+        return this.inArrearsTolerance;
+    }
+
+    public BigDecimal getTotalFeeChargesAtDisbursement() {
+        return this.totalFeeChargesAtDisbursement;
+    }
+
+    public DisbursementData disbursementData() {
+        return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal, null, null);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
new file mode 100755
index 0000000..dde8599
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
@@ -0,0 +1,103 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.data;
+
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
+import org.joda.time.LocalDate;
+
+public class ScheduleGeneratorDTO {
+
+    final LoanScheduleGeneratorFactory loanScheduleFactory;
+    final ApplicationCurrency applicationCurrency;
+    final LocalDate calculatedRepaymentsStartingFromDate;
+    final HolidayDetailDTO holidayDetailDTO;
+    final CalendarInstance calendarInstanceForInterestRecalculation;
+    final CalendarInstance compoundingCalendarInstance;
+    LocalDate recalculateFrom;
+    final Long overdurPenaltyWaitPeriod;
+    final FloatingRateDTO floatingRateDTO;
+
+    public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency,
+            final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO,
+            final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance,
+            final LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod, final FloatingRateDTO floatingRateDTO) {
+
+        this.loanScheduleFactory = loanScheduleFactory;
+        this.applicationCurrency = applicationCurrency;
+        this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate;
+        this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation;
+        this.compoundingCalendarInstance = compoundingCalendarInstance;
+        this.recalculateFrom = recalculateFrom;
+        this.overdurPenaltyWaitPeriod = overdurPenaltyWaitPeriod;
+        this.holidayDetailDTO = holidayDetailDTO;
+        this.floatingRateDTO = floatingRateDTO;
+    }
+
+    public LoanScheduleGeneratorFactory getLoanScheduleFactory() {
+        return this.loanScheduleFactory;
+    }
+
+    public ApplicationCurrency getApplicationCurrency() {
+        return this.applicationCurrency;
+    }
+
+    public LocalDate getCalculatedRepaymentsStartingFromDate() {
+        return this.calculatedRepaymentsStartingFromDate;
+    }
+
+    public CalendarInstance getCalendarInstanceForInterestRecalculation() {
+        return this.calendarInstanceForInterestRecalculation;
+    }
+
+    public LocalDate getRecalculateFrom() {
+        return this.recalculateFrom;
+    }
+
+    public Long getOverdurPenaltyWaitPeriod() {
+        return this.overdurPenaltyWaitPeriod;
+    }
+
+    public int getPenaltyWaitPeriod() {
+        int penaltyWaitPeriod = 0;
+        if (this.overdurPenaltyWaitPeriod != null) {
+            penaltyWaitPeriod = this.overdurPenaltyWaitPeriod.intValue();
+        }
+        return penaltyWaitPeriod;
+    }
+
+    public HolidayDetailDTO getHolidayDetailDTO() {
+        return this.holidayDetailDTO;
+    }
+
+    public void setRecalculateFrom(LocalDate recalculateFrom) {
+        this.recalculateFrom = recalculateFrom;
+    }
+
+    public CalendarInstance getCompoundingCalendarInstance() {
+        return this.compoundingCalendarInstance;
+    }
+
+    public FloatingRateDTO getFloatingRateDTO() {
+        return this.floatingRateDTO;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java
new file mode 100755
index 0000000..4351dbd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Stores details of {@link LoanTransaction}'s that were reversed or newly
+ * created
+ * 
+ * 
+ */
+public class ChangedTransactionDetail {
+
+    private final Map<Long, LoanTransaction> newTransactionMappings = new HashMap<>();
+
+    public Map<Long, LoanTransaction> getNewTransactionMappings() {
+        return this.newTransactionMappings;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
new file mode 100644
index 0000000..6dfc60c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
@@ -0,0 +1,139 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.util.List;
+
+public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachine {
+
+    private final List<LoanStatus> allowedLoanStatuses;
+
+    public DefaultLoanLifecycleStateMachine(final List<LoanStatus> allowedLoanStatuses) {
+        this.allowedLoanStatuses = allowedLoanStatuses;
+    }
+
+    @Override
+    public LoanStatus transition(final LoanEvent loanEvent, final LoanStatus from) {
+
+        LoanStatus newState = from;
+
+        switch (loanEvent) {
+            case LOAN_CREATED:
+                if (from == null) {
+                    newState = stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_REJECTED:
+                if (from.hasStateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) {
+                    newState = stateOf(LoanStatus.REJECTED, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_APPROVED:
+                if (from.hasStateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) {
+                    newState = stateOf(LoanStatus.APPROVED, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_WITHDRAWN:
+                if (anyOfAllowedWhenComingFrom(from, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) {
+                    newState = stateOf(LoanStatus.WITHDRAWN_BY_CLIENT, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_DISBURSED:
+                if (from.hasStateOf(LoanStatus.APPROVED)) {
+                    newState = stateOf(LoanStatus.ACTIVE, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_APPROVAL_UNDO:
+                if (from.hasStateOf(LoanStatus.APPROVED)) {
+                    newState = stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_DISBURSAL_UNDO:
+                if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE)) {
+                    newState = stateOf(LoanStatus.APPROVED, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_CHARGE_PAYMENT:
+            case LOAN_REPAYMENT_OR_WAIVER:
+                if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) {
+                    newState = stateOf(LoanStatus.ACTIVE, this.allowedLoanStatuses);
+                } else {
+                    newState = from;
+                }
+            break;
+            case REPAID_IN_FULL:
+                if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.OVERPAID)) {
+                    newState = stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, this.allowedLoanStatuses);
+                }
+            break;
+            case WRITE_OFF_OUTSTANDING:
+                if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE)) {
+                    newState = stateOf(LoanStatus.CLOSED_WRITTEN_OFF, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_RESCHEDULE:
+                if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE)) {
+                    newState = stateOf(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT, this.allowedLoanStatuses);
+                }
+            break;
+            case INTERST_REBATE_OWED:
+                if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET)) {
+                    newState = stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_OVERPAYMENT:
+                if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.ACTIVE)) {
+                    newState = stateOf(LoanStatus.OVERPAID, this.allowedLoanStatuses);
+                }
+            break;
+            case LOAN_CLOSED:
+            break;
+            case WRITE_OFF_OUTSTANDING_UNDO:
+            break;
+            default:
+            break;
+        }
+
+        return newState;
+    }
+
+    private LoanStatus stateOf(final LoanStatus state, final List<LoanStatus> allowedLoanStatuses) {
+        LoanStatus match = null;
+        for (final LoanStatus loanStatus : allowedLoanStatuses) {
+            if (loanStatus.hasStateOf(state)) {
+                match = loanStatus;
+                break;
+            }
+        }
+        return match;
+    }
+
+    private boolean anyOfAllowedWhenComingFrom(final LoanStatus state, final LoanStatus... allowedStates) {
+        boolean allowed = false;
+
+        for (final LoanStatus allowedState : allowedStates) {
+            if (state.hasStateOf(allowedState)) {
+                allowed = true;
+                break;
+            }
+        }
+
+        return allowed;
+    }
+}
\ 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
new file mode 100755
index 0000000..dc37c7f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -0,0 +1,5680 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.OrderBy;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.holiday.service.HolidayUtil;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+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.monetary.domain.MoneyHelper;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.collateral.data.CollateralData;
+import org.apache.fineract.portfolio.collateral.domain.LoanCollateral;
+import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
+import org.apache.fineract.portfolio.common.domain.NthDayType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.portfolio.fund.domain.Fund;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.command.LoanChargeCommand;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
+import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
+import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
+import org.apache.fineract.portfolio.loanaccount.exception.InvalidRefundDateException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
+import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
+import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
+import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+@Entity
+@Component
+@Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"),
+        @UniqueConstraint(columnNames = { "external_id" }, name = "loan_externalid_UNIQUE") })
+public class Loan extends AbstractPersistable<Long> {
+
+    /** Disable optimistic locking till batch jobs failures can be fixed **/
+    @Version
+    int version;
+
+    @Column(name = "account_no", length = 20, unique = true, nullable = false)
+    private String accountNumber;
+
+    @Column(name = "external_id")
+    private String externalId;
+
+    @ManyToOne
+    @JoinColumn(name = "client_id", nullable = true)
+    private Client client;
+
+    @ManyToOne
+    @JoinColumn(name = "group_id", nullable = true)
+    private Group group;
+
+    @Column(name = "loan_type_enum", nullable = false)
+    private Integer loanType;
+
+    @ManyToOne
+    @JoinColumn(name = "product_id", nullable = false)
+    private LoanProduct loanProduct;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "fund_id", nullable = true)
+    private Fund fund;
+
+    @ManyToOne
+    @JoinColumn(name = "loan_officer_id", nullable = true)
+    private Staff loanOfficer;
+
+    @ManyToOne
+    @JoinColumn(name = "loanpurpose_cv_id", nullable = true)
+    private CodeValue loanPurpose;
+
+    @ManyToOne
+    @JoinColumn(name = "loan_transaction_strategy_id", nullable = true)
+    private LoanTransactionProcessingStrategy transactionProcessingStrategy;
+
+    @Embedded
+    private LoanProductRelatedDetail loanRepaymentScheduleDetail;
+
+    @Column(name = "repayment_frequency_nth_day_enum", nullable = true)
+    private Integer repaymentFrequencyNthDayType;
+
+    @Column(name = "repayment_frequency_day_of_week_enum", nullable = true)
+    private Integer repaymentFrequencyDayOfWeekType;
+
+    @Column(name = "term_frequency", nullable = false)
+    private Integer termFrequency;
+
+    @Column(name = "term_period_frequency_enum", nullable = false)
+    private Integer termPeriodFrequencyType;
+
+    @Column(name = "loan_status_id", nullable = false)
+    private Integer loanStatus;
+
+    @Column(name = "sync_disbursement_with_meeting", nullable = true)
+    private Boolean syncDisbursementWithMeeting;
+
+    // loan application states
+    @Temporal(TemporalType.DATE)
+    @Column(name = "submittedon_date")
+    private Date submittedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "submittedon_userid", nullable = true)
+    private AppUser submittedBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "rejectedon_date")
+    private Date rejectedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "rejectedon_userid", nullable = true)
+    private AppUser rejectedBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "withdrawnon_date")
+    private Date withdrawnOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "withdrawnon_userid", nullable = true)
+    private AppUser withdrawnBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "approvedon_date")
+    private Date approvedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "approvedon_userid", nullable = true)
+    private AppUser approvedBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "expected_disbursedon_date")
+    private Date expectedDisbursementDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "disbursedon_date")
+    private Date actualDisbursementDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "disbursedon_userid", nullable = true)
+    private AppUser disbursedBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "closedon_date")
+    private Date closedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "closedon_userid", nullable = true)
+    private AppUser closedBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "writtenoffon_date")
+    private Date writtenOffOnDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "rescheduledon_date")
+    private Date rescheduledOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "rescheduledon_userid", nullable = true)
+    private AppUser rescheduledByUser;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "expected_maturedon_date")
+    private Date expectedMaturityDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "maturedon_date")
+    private Date actualMaturityDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "expected_firstrepaymenton_date")
+    private Date expectedFirstRepaymentOnDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "interest_calculated_from_date")
+    private Date interestChargedFromDate;
+
+    @Column(name = "total_overpaid_derived", scale = 6, precision = 19)
+    private BigDecimal totalOverpaid;
+
+    @Column(name = "loan_counter")
+    private Integer loanCounter;
+
+    @Column(name = "loan_product_counter")
+    private Integer loanProductCounter;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
+    private Set<LoanCharge> charges = new HashSet<>();
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
+    private Set<LoanTrancheCharge> trancheCharges = new HashSet<>();
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
+    private Set<LoanCollateral> collateral = null;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
+    private Set<LoanOfficerAssignmentHistory> loanOfficerHistory;
+
+    // see
+    // http://stackoverflow.com/questions/4334970/hibernate-cannot-simultaneously-fetch-multiple-bags
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OrderBy(value = "installmentNumber")
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
+    private final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = new ArrayList<>();
+
+    // see
+    // http://stackoverflow.com/questions/4334970/hibernate-cannot-simultaneously-fetch-multiple-bags
+    @OrderBy(value = "dateOf, id")
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
+    private final List<LoanTransaction> loanTransactions = new ArrayList<>();
+
+    @Embedded
+    private LoanSummary summary;
+
+    @Transient
+    private boolean accountNumberRequiresAutoGeneration = false;
+    @Transient
+    private LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
+
+    @Transient
+    private LoanLifecycleStateMachine loanLifecycleStateMachine;
+    @Transient
+    private LoanSummaryWrapper loanSummaryWrapper;
+
+    @Column(name = "principal_amount_proposed", scale = 6, precision = 19, nullable = false)
+    private BigDecimal proposedPrincipal;
+
+    @Column(name = "approved_principal", scale = 6, precision = 19, nullable = false)
+    private BigDecimal approvedPrincipal;
+
+    @Column(name = "fixed_emi_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal fixedEmiAmount;
+
+    @Column(name = "max_outstanding_loan_balance", scale = 6, precision = 19, nullable = false)
+    private BigDecimal maxOutstandingLoanBalance;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
+    @OrderBy(value = "expectedDisbursementDate, id")
+    private Set<LoanDisbursementDetails> disbursementDetails = new HashSet<>();
+
+    @OrderBy(value = "termApplicableFrom, id")
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
+    private final List<LoanTermVariations> loanTermVariations = new ArrayList<>();
+
+    @Column(name = "total_recovered_derived", scale = 6, precision = 19)
+    private BigDecimal totalRecovered;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true)
+    private LoanInterestRecalculationDetails loanInterestRecalculationDetails;
+
+    @Column(name = "is_npa", nullable = false)
+    private boolean isNpa;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "accrued_till")
+    private Date accruedTill;
+
+    @Column(name = "create_standing_instruction_at_disbursement", nullable = true)
+    private Boolean createStandingInstructionAtDisbursement;
+
+    @Column(name = "guarantee_amount_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal guaranteeAmountDerived;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "interest_recalcualated_on")
+    private Date interestRecalculatedOn;
+
+    @Column(name = "is_floating_interest_rate", nullable = true)
+    private Boolean isFloatingInterestRate;
+
+    @Column(name = "interest_rate_differential", scale = 6, precision = 19, nullable = true)
+    private BigDecimal interestRateDifferential;
+
+    public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
+            final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
+            final LoanTransactionProcessingStrategy transactionProcessingStrategy,
+            final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
+            final Set<LoanCollateral> collateral, final BigDecimal fixedEmiAmount, final Set<LoanDisbursementDetails> disbursementDetails,
+            final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
+            final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) {
+        final LoanStatus status = null;
+        final Group group = null;
+        final Boolean syncDisbursementWithMeeting = null;
+        return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
+                loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
+                disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
+                interestRateDifferential);
+    }
+
+    public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType,
+            final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
+            final LoanTransactionProcessingStrategy transactionProcessingStrategy,
+            final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
+            final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
+            final Set<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
+            final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
+            final BigDecimal interestRateDifferential) {
+        final LoanStatus status = null;
+        final Client client = null;
+        return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
+                loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
+                disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
+                interestRateDifferential);
+    }
+
+    public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group,
+            final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
+            final LoanTransactionProcessingStrategy transactionProcessingStrategy,
+            final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
+            final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
+            final Set<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
+            final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
+            final BigDecimal interestRateDifferential) {
+        final LoanStatus status = null;
+        return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
+                loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
+                disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
+                interestRateDifferential);
+    }
+
+    protected Loan() {
+        this.client = null;
+    }
+
+    private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund,
+            final Staff loanOfficer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
+            final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus,
+            final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting,
+            final BigDecimal fixedEmiAmount, final Set<LoanDisbursementDetails> disbursementDetails,
+            final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
+            final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) {
+
+        this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
+        this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
+
+        this.isFloatingInterestRate = isFloatingInterestRate;
+        this.interestRateDifferential = interestRateDifferential;
+
+        if (StringUtils.isBlank(accountNo)) {
+            this.accountNumber = new RandomPasswordGenerator(19).generate();
+            this.accountNumberRequiresAutoGeneration = true;
+        } else {
+            this.accountNumber = accountNo;
+        }
+        this.client = client;
+        this.group = group;
+        this.loanType = loanType;
+        this.fund = fund;
+        this.loanOfficer = loanOfficer;
+        this.loanPurpose = loanPurpose;
+
+        this.transactionProcessingStrategy = transactionProcessingStrategy;
+        this.loanProduct = loanProduct;
+        if (loanStatus != null) {
+            this.loanStatus = loanStatus.getValue();
+        } else {
+            this.loanStatus = null;
+        }
+        if (loanCharges != null && !loanCharges.isEmpty()) {
+            this.charges = associateChargesWithThisLoan(loanCharges);
+            this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+        } else {
+            this.charges = null;
+            this.summary = new LoanSummary();
+        }
+        if (collateral != null && !collateral.isEmpty()) {
+            this.collateral = associateWithThisLoan(collateral);
+        } else {
+            this.collateral = null;
+        }
+        this.loanOfficerHistory = null;
+
+        this.syncDisbursementWithMeeting = syncDisbursementWithMeeting;
+        this.fixedEmiAmount = fixedEmiAmount;
+        this.maxOutstandingLoanBalance = maxOutstandingLoanBalance;
+        this.disbursementDetails = disbursementDetails;
+        this.approvedPrincipal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
+        this.createStandingInstructionAtDisbursement = createStandingInstructionAtDisbursement;
+
+        /*
+         * During loan origination stage and before loan is approved
+         * principal_amount, approved_principal and principal_amount_demanded
+         * will same amount and that amount is same as applicant loan demanded
+         * amount.
+         */
+
+        this.proposedPrincipal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
+
+    }
+
+    private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) {
+        if (this.summary == null) {
+            this.summary = LoanSummary.create(feeChargesDueAtDisbursement);
+        } else {
+            this.summary.updateTotalFeeChargesDueAtDisbursement(feeChargesDueAtDisbursement);
+        }
+        return this.summary;
+    }
+
+    private BigDecimal deriveSumTotalOfChargesDueAtDisbursement() {
+
+        Money chargesDue = Money.of(getCurrency(), BigDecimal.ZERO);
+
+        for (final LoanCharge charge : charges()) {
+            if (charge.isDueAtDisbursement()) {
+                chargesDue = chargesDue.plus(charge.amount());
+            }
+        }
+
+        return chargesDue.getAmount();
+    }
+
+    private Set<LoanCharge> associateChargesWithThisLoan(final Set<LoanCharge> loanCharges) {
+        for (final LoanCharge loanCharge : loanCharges) {
+            loanCharge.update(this);
+            if (loanCharge.getTrancheDisbursementCharge() != null) {
+                addTrancheLoanCharge(loanCharge.getCharge());
+            }
+        }
+        return loanCharges;
+    }
+
+    private Set<LoanCollateral> associateWithThisLoan(final Set<LoanCollateral> collateral) {
+        for (final LoanCollateral item : collateral) {
+            item.associateWith(this);
+        }
+        return collateral;
+    }
+
+    public boolean isAccountNumberRequiresAutoGeneration() {
+        return this.accountNumberRequiresAutoGeneration;
+    }
+
+    public void setAccountNumberRequiresAutoGeneration(final boolean accountNumberRequiresAutoGeneration) {
+        this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
+    }
+
+    public void addLoanCharge(final LoanCharge loanCharge) {
+
+        validateLoanIsNotClosed(loanCharge);
+
+        if (isChargesAdditionAllowed() && loanCharge.isDueAtDisbursement()) {
+            // Note: added this constraint to restrict adding disbursement
+            // charges to a loan
+            // after it is disbursed
+            // if the loan charge payment type is 'Disbursement'.
+            // To undo this constraint would mean resolving how charges due are
+            // disbursement are handled at present.
+            // When a loan is disbursed and has charges due at disbursement, a
+            // transaction is created to auto record
+            // payment of the charges (user has no choice in saying they were or
+            // werent paid) - so its assumed they were paid.
+
+            final String defaultUserMessage = "This charge which is due at disbursement cannot be added as the loan is already disbursed.";
+            throw new LoanChargeCannotBeAddedException("loanCharge", "due.at.disbursement.and.loan.is.disbursed", defaultUserMessage,
+                    getId(), loanCharge.name());
+        }
+
+        validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
+
+        loanCharge.update(this);
+
+        final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
+        BigDecimal chargeAmt = BigDecimal.ZERO;
+        BigDecimal totalChargeAmt = BigDecimal.ZERO;
+        if (loanCharge.getChargeCalculation().isPercentageBased()) {
+            chargeAmt = loanCharge.getPercentage();
+            if (loanCharge.isInstalmentFee()) {
+                totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
+            } else if (loanCharge.isOverdueInstallmentCharge()) {
+                totalChargeAmt = loanCharge.amountOutstanding();
+            }
+        } else {
+            chargeAmt = loanCharge.amountOrPercentage();
+        }
+        loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt);
+
+        // NOTE: must add new loan charge to set of loan charges before
+        // reporcessing the repayment schedule.
+        if (this.charges == null) {
+            this.charges = new HashSet<>();
+        }
+
+        this.charges.add(loanCharge);
+
+        this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+
+        // store Id's of existing loan transactions and existing reversed loan
+        // transactions
+        final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
+        wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
+        updateLoanSummaryDerivedFields();
+
+    }
+
+    public ChangedTransactionDetail reprocessTransactions() {
+        ChangedTransactionDetail changedTransactionDetail = null;
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+        changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
+                allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
+        for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+            mapEntry.getValue().updateLoan(this);
+        }
+        this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
+        updateLoanSummaryDerivedFields();
+        return changedTransactionDetail;
+    }
+
+    /**
+     * Creates a loanTransaction for "Apply Charge Event" with transaction date
+     * set to "suppliedTransactionDate". The newly created transaction is also
+     * added to the Loan on which this method is called.
+     * 
+     * If "suppliedTransactionDate" is not passed Id, the transaction date is
+     * set to the loans due date if the due date is lesser than todays date. If
+     * not, the transaction date is set to todays date
+     * 
+     * @param loanCharge
+     * @param suppliedTransactionDate
+     * @return
+     */
+    public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate,
+            final AppUser currentUser) {
+        final Money chargeAmount = loanCharge.getAmount(getCurrency());
+        Money feeCharges = chargeAmount;
+        Money penaltyCharges = Money.zero(loanCurrency());
+        if (loanCharge.isPenaltyCharge()) {
+            penaltyCharges = chargeAmount;
+            feeCharges = Money.zero(loanCurrency());
+        }
+
+        LocalDate transactionDate = null;
+
+        if (suppliedTransactionDate != null) {
+            transactionDate = suppliedTransactionDate;
+        } else {
+            transactionDate = loanCharge.getDueLocalDate();
+            final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+
+            // if loan charge is to be applied on a future date, the loan
+            // transaction would show todays date as applied date
+            if (transactionDate == null || currentDate.isBefore(transactionDate)) {
+                transactionDate = currentDate;
+            }
+        }
+
+        final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(), chargeAmount,
+                transactionDate, feeCharges, penaltyCharges, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        Integer installmentNumber = null;
+        final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge, loanCharge.getAmount(
+                getCurrency()).getAmount(), installmentNumber);
+        applyLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
+        this.loanTransactions.add(applyLoanChargeTransaction);
+        return applyLoanChargeTransaction;
+    }
+
+    private void handleChargePaidTransaction(final LoanCharge charge, final LoanTransaction chargesPayment,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine, final Integer installmentNumber) {
+        chargesPayment.updateLoan(this);
+        final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, chargesPayment.getAmount(getCurrency())
+                .getAmount(), installmentNumber);
+        chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
+        this.loanTransactions.add(chargesPayment);
+        final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT,
+                LoanStatus.fromInt(this.loanStatus));
+        this.loanStatus = statusEnum.getValue();
+
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
+        LocalDate startDate = getDisbursementDate();
+        for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+            if (installmentNumber == null && charge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) {
+                chargePaymentInstallments.add(installment);
+                break;
+            } else if (installmentNumber != null && installment.getInstallmentNumber().equals(installmentNumber)) {
+                chargePaymentInstallments.add(installment);
+                break;
+            }
+            startDate = installment.getDueDate();
+        }
+        final Set<LoanCharge> loanCharges = new HashSet<>(1);
+        loanCharges.add(charge);
+        loanRepaymentScheduleTransactionProcessor.handleTransaction(chargesPayment, getCurrency(), chargePaymentInstallments, loanCharges);
+        updateLoanSummaryDerivedFields();
+        doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), loanLifecycleStateMachine);
+    }
+
+    private void validateLoanIsNotClosed(final LoanCharge loanCharge) {
+        if (isClosed()) {
+            final String defaultUserMessage = "This charge cannot be added as the loan is already closed.";
+            throw new LoanChargeCannotBeAddedException("loanCharge", "loan.is.closed", defaultUserMessage, getId(), loanCharge.name());
+        }
+    }
+
+    private void validateLoanChargeIsNotWaived(final LoanCharge loanCharge) {
+        if (loanCharge.isWaived()) {
+            final String defaultUserMessage = "This loan charge cannot be removed as the charge as already been waived.";
+            throw new LoanChargeCannotBeAddedException("loanCharge", "loanCharge.is.waived", defaultUserMessage, getId(), loanCharge.name());
+        }
+    }
+
+    private void validateChargeHasValidSpecifiedDateIfApplicable(final LoanCharge loanCharge, final LocalDate disbursementDate,
+            final LocalDate lastRepaymentPeriodDueDate) {
+        if (loanCharge.isSpecifiedDueDate()
+                && !loanCharge.isDueForCollectionFromAndUpToAndIncluding(disbursementDate, lastRepaymentPeriodDueDate)) {
+            final String defaultUserMessage = "This charge with specified due date cannot be added as the it is not in schedule range.";
+            throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage,
+                    getDisbursementDate(), lastRepaymentPeriodDueDate, loanCharge.name());
+        }
+    }
+
+    private LocalDate getLastRepaymentPeriodDueDate() {
+        LocalDate lastRepaymentDate = getDisbursementDate();
+        for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+            if (!installment.isRecalculatedInterestComponent() && installment.getDueDate().isAfter(lastRepaymentDate)) {
+                lastRepaymentDate = installment.getDueDate();
+            }
+        }
+        return lastRepaymentDate;
+    }
+
+    public void removeLoanCharge(final LoanCharge loanCharge) {
+
+        validateLoanIsNotClosed(loanCharge);
+
+        // NOTE: to remove this constraint requires that loan transactions
+        // that represent the waive of charges also be removed (or reversed)
+        // if you want ability to remove loan charges that are waived.
+        validateLoanChargeIsNotWaived(loanCharge);
+
+        final boolean removed = loanCharge.isActive();
+        if (removed) {
+            loanCharge.setActive(false);
+            final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
+            wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
+            updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+        }
+
+        removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge);
+
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
+            /****
+             * TODO Vishwas Currently we do not allow removing a loan charge
+             * after a loan is approved (hence there is no need to adjust any
+             * loan transactions).
+             * 
+             * Consider removing this block of code or logically completing it
+             * for the future by getting the list of affected Transactions
+             ***/
+            final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+            loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
+                    getCurrency(), this.repaymentScheduleInstallments, charges());
+        }
+        this.charges.remove(loanCharge);
+        updateLoanSummaryDerivedFields();
+    }
+
+    private void removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(final LoanCharge loanCharge) {
+        if (loanCharge.isDueAtDisbursement()) {
+            LoanTransaction transactionToRemove = null;
+            for (final LoanTransaction transaction : this.loanTransactions) {
+                if (transaction.isRepaymentAtDisbursement() && transaction.getLoanChargesPaid().contains(loanCharge)) {
+
+                    final MonetaryCurrency currency = loanCurrency();
+                    final Money chargeAmount = Money.of(currency, loanCharge.amount());
+                    if (transaction.isGreaterThan(chargeAmount)) {
+                        final Money principalPortion = Money.zero(currency);
+                        final Money interestPortion = Money.zero(currency);
+                        final Money penaltychargesPortion = Money.zero(currency);
+
+                        final Money feeChargesPortion = chargeAmount;
+                        transaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion);
+                    } else {
+                        transactionToRemove = transaction;
+                    }
+                }
+            }
+
+            if (transactionToRemove != null) {
+                this.loanTransactions.remove(transactionToRemove);
+            }
+        }
+    }
+
+    public Map<String, Object> updateLoanCharge(final LoanCharge loanCharge, final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(3);
+
+        validateLoanIsNotClosed(loanCharge);
+        if (charges().contains(loanCharge)) {
+            final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
+            final Map<String, Object> loanChargeChanges = loanCharge.update(command, amount);
+            actualChanges.putAll(loanChargeChanges);
+            updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+        }
+
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        if (!loanCharge.isDueAtDisbursement()) {
+            /****
+             * TODO Vishwas Currently we do not allow waiving updating loan
+             * charge after a loan is approved (hence there is no need to adjust
+             * any loan transactions).
+             * 
+             * Consider removing this block of code or logically completing it
+             * for the future by getting the list of affected Transactions
+             ***/
+            final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+            loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
+                    getCurrency(), this.repaymentScheduleInstallments, charges());
+        } else {
+            // reprocess loan schedule based on charge been waived.
+            final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
+            wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
+        }
+
+        updateLoanSummaryDerivedFields();
+
+        return actualChanges;
+    }
+
+    /**
+     * @param loanCharge
+     * @return
+     */
+    private BigDecimal calculateAmountPercentageAppliedTo(final LoanCharge loanCharge) {
+        BigDecimal amount = BigDecimal.ZERO;
+        if (loanCharge.isOverdueInstallmentCharge()) { return loanCharge.getAmountPercentageAppliedTo(); }
+        switch (loanCharge.getChargeCalculation()) {
+            case PERCENT_OF_AMOUNT:
+                amount = getPrincpal().getAmount();
+            break;
+            case PERCENT_OF_AMOUNT_AND_INTEREST:
+                final BigDecimal totalInterestCharged = getTotalInterest();
+                amount = getPrincpal().getAmount().add(totalInterestCharged);
+            break;
+            case PERCENT_OF_INTEREST:
+                amount = getTotalInterest();
+            break;
+            case PERCENT_OF_DISBURSEMENT_AMOUNT:
+                if (loanCharge.getTrancheDisbursementCharge() != null) {
+                    amount = loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().principal();
+                } else {
+                    amount = getPrincpal().getAmount();
+                }
+            break;
+            default:
+            break;
+        }
+        return amount;
+    }
+
+    /**
+     * @return
+     */
+    public BigDecimal getTotalInterest() {
+        return this.loanSummaryWrapper.calculateTotalInterestCharged(this.repaymentScheduleInstallments, getCurrency()).getAmount();
+    }
+
+    private BigDecimal calculatePerInstallmentChargeAmount(final LoanCharge loanCharge) {
+        return calculatePerInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage());
+    }
+
+    public BigDecimal calculatePerInstallmentChargeAmount(final ChargeCalculationType calculationType, final BigDecimal percentage) {
+        Money amount = Money.zero(getCurrency());
+        for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+            amount = amount.plus(calculateInstallmentChargeAmount(calculationType, percentage, installment));
+        }
+        return amount.getAmount();
+    }
+
+    public BigDecimal getTotalWrittenOff() {
+        return this.summary.getTotalWrittenOff();
+    }
+
+    /**
+     * @param calculationType
+     * @param percentage
+     * @param installment
+     * @return
+     */
+    private Money calculateInstallmentChargeAmount(final ChargeCalculationType calculationType, final BigDecimal percentage,
+            final LoanRepaymentScheduleInstallment installment) {
+        Money amount = Money.zero(getCurrency());
+        Money percentOf = Money.zero(getCurrency());
+        switch (calculationType) {
+            case PERCENT_OF_AMOUNT:
+                percentOf = installment.getPrincipal(getCurrency());
+            break;
+            case PERCENT_OF_AMOUNT_AND_INTEREST:
+                percentOf = installment.getPrincipal(getCurrency()).plus(installment.getInterestCharged(getCurrency()));
+            break;
+            case PERCENT_OF_INTEREST:
+                percentOf = installment.getInterestCharged(getCurrency());
+            break;
+            default:
+            break;
+        }
+        amount = amount.plus(LoanCharge.percentageOf(percentOf.getAmount(), percentage));
+        return amount;
+    }
+
+    public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
+            final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
+            final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge,
+            final AppUser currentUser) {
+
+        validateLoanIsNotClosed(loanCharge);
+
+        final Money amountWaived = loanCharge.waive(loanCurrency(), loanInstallmentNumber);
+
+        changes.put("amount", amountWaived.getAmount());
+
+        Money unrecognizedIncome = amountWaived.zero();
+        Money chargeComponent = amountWaived;
+        if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
+            Money receivableCharge = Money.zero(getCurrency());
+            if (loanInstallmentNumber != null) {
+                receivableCharge = accruedCharge.minus(loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getAmountPaid(
+                        getCurrency()));
+            } else {
+                receivableCharge = accruedCharge.minus(loanCharge.getAmountPaid(getCurrency()));
+            }
+            if (receivableCharge.isLessThanZero()) {
+                receivableCharge = amountWaived.zero();
+            }
+            if (amountWaived.isGreaterThan(receivableCharge)) {
+                chargeComponent = receivableCharge;
+                unrecognizedIncome = amountWaived.minus(receivableCharge);
+            }
+        }
+        Money feeChargesWaived = chargeComponent;
+        Money penaltyChargesWaived = Money.zero(loanCurrency());
+        if (loanCharge.isPenaltyCharge()) {
+            penaltyChargesWaived = chargeComponent;
+            feeChargesWaived = Money.zero(loanCurrency());
+        }
+
+        LocalDate transactionDate = getDisbursementDate();
+        if (loanCharge.isSpecifiedDueDate()) {
+            transactionDate = loanCharge.getDueLocalDate();
+        }
+        scheduleGeneratorDTO.setRecalculateFrom(transactionDate);
+
+        updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+
+        final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived,
+                transactionDate, feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(),
+                currentUser);
+        final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, waiveLoanChargeTransaction
+                .getAmount(getCurrency()).getAmount(), loanInstallmentNumber);
+        waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
+        this.loanTransactions.add(waiveLoanChargeTransaction);
+
+        if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
+                && (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) {
+            regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
+        }
+        // Waive of charges whose due date falls after latest 'repayment'
+        // transaction dont require entire loan schedule to be reprocessed.
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
+            /****
+             * TODO Vishwas Currently we do not allow waiving fully paid loan
+             * charge and waiving partially paid loan charges only waives the
+             * remaining amount.
+             * 
+             * Consider removing this block of code or logically completing it
+             * for the future by getting the list of affected Transactions
+             ***/
+            final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+            loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
+                    getCurrency(), this.repaymentScheduleInstallments, charges());
+        } else {
+            // reprocess loan schedule based on charge been waived.
+            final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
+            wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
+        }
+
+        updateLoanSummaryDerivedFields();
+
+        doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine);
+
+        return waiveLoanChargeTransaction;
+    }
+
+    public Client client() {
+        return this.client;
+    }
+
+    public LoanProduct loanProduct() {
+        return this.loanProduct;
+    }
+
+    public LoanProductRelatedDetail repaymentScheduleDetail() {
+        return this.loanRepaymentScheduleDetail;
+    }
+
+    public void updateClient(final Client client) {
+        this.client = client;
+    }
+
+    public void updateLoanProduct(final LoanProduct loanProduct) {
+        this.loanProduct = loanProduct;
+    }
+
+    public void updateAccountNo(final String newAccountNo) {
+        this.accountNumber = newAccountNo;
+        this.accountNumberRequiresAutoGeneration = false;
+    }
+
+    public void updateFund(final Fund fund) {
+        this.fund = fund;
+    }
+
+    public void updateLoanPurpose(final CodeValue loanPurpose) {
+        this.loanPurpose = loanPurpose;
+    }
+
+    public void updateLoanOfficerOnLoanApplication(final Staff newLoanOfficer) {
+        if (!isSubmittedAndPendingApproval()) {
+            Long loanOfficerId = null;
+            if (this.loanOfficer != null) {
+                loanOfficerId = this.loanOfficer.getId();
+            }
+            throw new LoanOfficerAssignmentException(getId(), loanOfficerId);
+        }
+        this.loanOfficer = newLoanOfficer;
+    }
+
+    public void updateTransactionProcessingStrategy(final LoanTransactionProcessingStrategy strategy) {
+        this.transactionProcessingStrategy = strategy;
+    }
+
+    public void updateLoanCharges(final Set<LoanCharge> loanCharges) {
+        List<Long> existingCharges = fetchAllLoanChargeIds();
+
+        /** Process new and updated charges **/
+        for (final LoanCharge loanCharge : loanCharges) {
+            LoanCharge charge = loanCharge;
+            // add new charges
+            if (loanCharge.getId() == null) {
+                LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
+                loanCharge.update(this);
+                if (this.loanProduct.isMultiDisburseLoan()) {
+                    loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().updateLoan(this);
+                    for (final LoanDisbursementDetails loanDisbursementDetails : this.disbursementDetails) {
+                        if (loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().getId() == null) {
+                            if (loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().equals(loanDisbursementDetails)) {
+                                loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, loanDisbursementDetails);
+                                loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
+                            }
+
+                        }
+                    }
+                }
+                this.charges.add(loanCharge);
+
+            } else {
+                charge = fetchLoanChargesById(charge.getId());
+                if (charge != null) existingCharges.remove(charge.getId());
+            }
+            final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
+            BigDecimal chargeAmt = BigDecimal.ZERO;
+            BigDecimal totalChargeAmt = BigDecimal.ZERO;
+            if (loanCharge.getChargeCalculation().isPercentageBased()) {
+                chargeAmt = loanCharge.getPercentage();
+                if (loanCharge.isInstalmentFee()) {
+                    totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
+                }
+            } else {
+                chargeAmt = loanCharge.amountOrPercentage();
+            }
+            if (charge != null)
+                charge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt);
+
+        }
+
+        /** Updated deleted charges **/
+        for (Long id : existingCharges) {
+            fetchLoanChargesById(id).setActive(false);
+        }
+        updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+    }
+
+    public void updateLoanCollateral(final Set<LoanCollateral> loanCollateral) {
+        if (this.collateral == null) {
+            this.collateral = new HashSet<>();
+        }
+        this.collateral.clear();
+        this.collateral.addAll(associateWithThisLoan(loanCollateral));
+    }
+
+    public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule, AppUser currentUser) {
+        this.repaymentScheduleInstallments.clear();
+
+        for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
+
+            if (scheduledLoanInstallment.isRepaymentPeriod()) {
+                final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this,
+                        scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
+                        scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
+                        scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
+                        scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent());
+                addRepaymentScheduleInstallment(installment);
+            }
+        }
+
+        updateLoanScheduleDependentDerivedFields();
+        updateLoanSummaryDerivedFields();
+        applyAccurals(currentUser);
+
+    }
+
+    public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment> installments, AppUser currentUser) {
+        this.repaymentScheduleInstallments.clear();
+        for (final LoanRepaymentScheduleInstallment installment : installments) {
+            addRepaymentScheduleInstallment(installment);
+        }
+        updateLoanScheduleDependentDerivedFields();
+        updateLoanSummaryDerivedFields();
+        applyAccurals(currentUser);
+
+    }
+
+    /**
+     * method updates accrual derived fields on installments and reverse the
+     * unprocessed transactions
+     */
+    private void applyAccurals(AppUser currentUser) {
+        Collection<LoanTransaction> accruals = retreiveListOfAccrualTransactions();
+        if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
+            applyPeriodicAccruals(accruals);
+        } else if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
+            updateAccrualsForNonPeriodicAccruals(accruals, currentUser);
+        }
+    }
+
+    private void applyPeriodicAccruals(final Collection<LoanTransaction> accruals) {
+
+        for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+            Money interest = Money.zero(getCurrency());
+            Money fee = Money.zero(getCurrency());
+            Money penality = Money.zero(getCurrency());
+            for (LoanTransaction loanTransaction : accruals) {
+                if (loanTransaction.getTransactionDate().isAfter(installment.getFromDate())
+                        && !loanTransaction.getTransactionDate().isAfter(installment.getDueDate())) {
+                    interest = interest.plus(loanTransaction.getInterestPortion(getCurrency()));
+                    fee = fee.plus(loanTransaction.getFeeChargesPortion(getCurrency()));
+                    penality = penality.plus(loanTransaction.getPenaltyChargesPortion(getCurrency()));
+                    if (installment.getFeeChargesCharged(getCurrency()).isLessThan(fee)
+                            || installment.getInterestCharged(getCurrency()).isLessThan(interest)
+                            || installment.getPenaltyChargesCharged(getCurrency()).isLessThan(penality)) {
+                        interest = interest.minus(loanTransaction.getInterestPortion(getCurrency()));
+                        fee = fee.minus(loanTransaction.getFeeChargesPortion(getCurrency()));
+                        penality = penality.minus(loanTransaction.getPenaltyChargesPortion(getCurrency()));
+                        loanTransaction.reverse();
+                    }
+                }
+            }
+            installment.updateAccrualPortion(interest, fee, penality);
+        }
+    }
+
+    private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals, final AppUser currentUser) {
+
+        final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
+        for (LoanTransaction loanTransaction : accruals) {
+            if (loanTransaction.getInterestPortion(getCurrency()).isGreaterThanZero()) {
+                if (loanTransaction.getInterestPortion(getCurrency()).isNotEqualTo(interestApplied)) {
+                    loanTransaction.reverse();
+                    final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant();
+                    final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
+                            getDisbursementDate(), currentDateTime, currentUser);
+                    this.loanTransactions.add(interestAppliedTransaction);
+                }
+            } else {
+                Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
+                for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
+                    LoanCharge loanCharge = chargePaidBy.getLoanCharge();
+                    Money chargeAmount = loanCharge.getAmount(getCurrency());
+                    if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(getCurrency()))) {
+                        loanTransaction.reverse();
+                        handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate(), currentUser);
+                    }
+
+                }
+            }
+        }
+
+    }
+
+    public void updateLoanScheduleDependentDerivedFields() {
+        this.expectedMaturityDate = determineExpectedMaturityDate().toDate();
+        this.actualMaturityDate = determineExpectedMaturityDate().toDate();
+    }
+
+    private void updateLoanSummaryDerivedFields() {
+
+        if (isNotDisbursed()) {
+            this.summary.zeroFields();
+            this.totalOverpaid = null;
+        } else {
+            final Money overpaidBy = calculateTotalOverpayment();
+            this.totalOverpaid = overpaidBy.getAmountDefaultedToNullIfZero();
+
+            final Money recoveredAmount = calculateTotalRecoveredPayments();
+            this.totalRecovered = recoveredAmount.getAmountDefaultedToNullIfZero();
+
+            final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
+            this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper,
+                    isDisbursed(), this.charges);
+            updateLoanOutstandingBalaces();
+        }
+    }
+
+    public void updateLoanSummarAndStatus() {
+        updateLoanSummaryDerivedFields();
+        doPostLoanTransactionChecks(getLastUserTransactionDate(), loanLifecycleStateMachine);
+    }
+
+    public Map<String, Object> loanApplicationModification(final JsonCommand command, final Set<LoanCharge> possiblyModifedLoanCharges,
+            final Set<LoanCollateral> possiblyModifedLoanCollateralItems, final AprCalculator aprCalculator, boolean isChargesModified) {
+
+        final Map<String, Object> actualChanges = this.loanRepaymentScheduleDetail.updateLoanApplicationAttributes(command, aprCalculator);
+        if (!actualChanges.isEmpty()) {
+            final boolean recalculateLoanSchedule = !(actualChanges.size() == 1 && actualChanges.containsKey("inArrearsTolerance"));
+            actualChanges.put("recalculateLoanSchedule", recalculateLoanSchedule);
+            isChargesModified = true;
+        }
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+        final LocalDate recalculationRestFrequencyDate = command
+                .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName);
+        final LocalDate recalculationCompoundingFrequencyDate = command
+                .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName);
+        updateLoanInterestRecalculationSettings(recalculationRestFrequencyDate, recalculationCompoundingFrequencyDate, command,
+                actualChanges);
+
+        final String accountNoParamName = "accountNo";
+        if (command.isChangeInStringParameterNamed(accountNoParamName, this.accountNumber)) {
+            final String newValue = command.stringValueOfParameterNamed(accountNoParamName);
+            actualChanges.put(accountNoParamName, newValue);
+            this.accountNumber = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        final String createSiAtDisbursementParameterName = "createStandingInstructionAtDisbursement";
+        if (command.isChangeInBooleanParameterNamed(createSiAtDisbursementParameterName, shouldCreateStandingInstructionAtDisbursement())) {
+            final Boolean valueAsInput = command.booleanObjectValueOfParameterNamed(createSiAtDisbursementParameterName);
+            actualChanges.put(createSiAtDisbursementParameterName, valueAsInput);
+            this.createStandingInstructionAtDisbursement = valueAsInput;
+        }
+
+        final String externalIdParamName = "externalId";
+        if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) {
+            final String newValue = command.stringValueOfParameterNamed(externalIdParamName);
+            actualChanges.put(externalIdParamName, newValue);
+            this.externalId = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        // add clientId, groupId and loanType changes to actual changes
+
+        final String clientIdParamName = "clientId";
+        final Long clientId = this.client == null ? null : this.client.getId();
+        if (command.isChangeInLongParameterNamed(clientIdParamName, clientId)) {
+            final Long newValue = command.longValueOfParameterNamed(clientIdParamName);
+            actualChanges.put(clientIdParamName, newValue);
+        }
+
+        // FIXME: AA - We may require separate api command to move loan from one
+        // group to another
+        final String groupIdParamName = "groupId";
+        final Long groupId = this.group == null ? null : this.group.getId();
+        if (command.isChangeInLongParameterNamed(groupIdParamName, groupId)) {
+            final Long newValue = command.longValueOfParameterNamed(groupIdParamName);
+            actualChanges.put(groupIdParamName, newValue);
+        }
+
+        final String productIdParamName = "productId";
+        if (command.isChangeInLongParameterNamed(productIdParamName, this.loanProduct.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(productIdParamName);
+            actualChanges.put(productIdParamName, newValue);
+            actualChanges.put("recalculateLoanSchedule", true);
+        }
+
+        final String isFloatingInterestRateParamName = "isFloatingInterestRate";
+        if (command.isChangeInBooleanParameterNamed(isFloatingInterestRateParamName, this.isFloatingInterestRate)) {
+            final Boolean newValue = command.booleanObjectValueOfParameterNamed(isFloatingInterestRateParamName);
+            actualChanges.put(isFloatingInterestRateParamName, newValue);
+            this.isFloatingInterestRate = newValue;
+        }
+
+        final String interestRateDifferentialParamName = "interestRateDifferential";
+        if (command.isChangeInBigDecimalParameterNamed(interestRateDifferentialParamName, this.interestRateDifferential)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRateDifferentialParamName);
+            actualChanges.put(interestRateDifferentialParamName, newValue);
+            this.interestRateDifferential = newValue;
+        }
+
+        Long existingFundId = null;
+        if (this.fund != null) {
+            existingFundId = this.fund.getId();
+        }
+        final String fundIdParamName = "fundId";
+        if (command.isChangeInLongParameterNamed(fundIdParamName, existingFundId)) {
+            final Long newValue = command.longValueOfParameterNamed(fundIdParamName);
+            actualChanges.put(fundIdParamName, newValue);
+        }
+
+        Long existingLoanOfficerId = null;
+        if (this.loanOfficer != null) {
+            existingLoanOfficerId = this.loanOfficer.getId();
+        }
+        final String loanOfficerIdParamName = "loanOfficerId";
+        if (command.isChangeInLongParameterNamed(loanOfficerIdParamName, existingLoanOfficerId)) {
+            final Long newValue = command.longValueOfParameterNamed(loanOfficerIdParamName);
+            actualChanges.put(loanOfficerIdParamName, newValue);
+        }
+
+        Long existingLoanPurposeId = null;
+        if (this.loanPurpose != null) {
+            existingLoanPurposeId = this.loanPurpose.getId();
+        }
+        final String loanPurposeIdParamName = "loanPurposeId";
+        if (command.isChangeInLongParameterNamed(loanPurposeIdParamName, existingLoanPurposeId)) {
+            final Long newValue = command.longValueOfParameterNamed(loanPurposeIdParamName);
+            actualChanges.put(loanPurposeIdParamName, newValue);
+        }
+
+        final String strategyIdParamName = "transactionProcessingStrategyId";
+        if (command.isChangeInLongParameterNamed(strategyIdParamName, this.transactionProcessingStrategy.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(strategyIdParamName);
+            actualChanges.put(strategyIdParamName, newValue);
+        }
+
+        final String submittedOnDateParamName = "submittedOnDate";
+        if (command.isChangeInLocalDateParameterNamed(submittedOnDateParamName, getSubmittedOnDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(submittedOnDateParamName);
+            actualChanges.put(submittedOnDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(submittedOnDateParamName);
+            this.submittedOnDate = newValue.toDate();
+        }
+
+        final String expectedDisbursementDateParamName = "expectedDisbursementDate";
+        if (command.isChangeInLocalDateParameterNamed(expectedDisbursementDateParamName, getExpectedDisbursedOnLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(expectedDisbursementDateParamName);
+            actualChanges.put(expectedDisbursementDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+            actualChanges.put("recalculateLoanSchedule", true);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(expectedDisbursementDateParamName);
+            this.expectedDisbursementDate = newValue.toDate();
+            removeFirstDisbursementTransaction();
+        }
+
+        final String repaymentsStartingFromDateParamName = "repaymentsStartingFromDate";
+        if (command.isChangeInLocalDateParameterNamed(repaymentsStartingFromDateParamName, getExpectedFirstRepaymentOnDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(repaymentsStartingFromDateParamName);
+            actualChanges.put(repaymentsStartingFromDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+            actualChanges.put("recalculateLoanSchedule", true);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(repaymentsStartingFromDateParamName);
+            if (newValue != null) {
+                this.expectedFirstRepaymentOnDate = newValue.toDate();
+            } else {
+                this.expectedFirstRepaymentOnDate = null;
+            }
+        }
+
+        final String syncDisbursementParameterName = "syncDisbursementWithMeeting";
+        if (command.isChangeInBooleanParameterNamed(syncDisbursementParameterName, isSyncDisbursementWithMeeting())) {
+            final Boolean valueAsInput = command.booleanObjectValueOfParameterNamed(syncDisbursementParameterName);
+            actualChanges.put(syncDisbursementParameterName, valueAsInput);
+            this.syncDisbursementWithMeeting = valueAsInput;
+        }
+
+        final String interestChargedFromDateParamName = "interestChargedFromDate";
+        if (command.isChangeInLocalDateParameterNamed(interestChargedFromDateParamName, getInterestChargedFromDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(interestChargedFromDateParamName);
+            actualChanges.put(interestChargedFromDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+            actualChanges.put("recalculateLoanSchedule", true);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(interestChargedFromDateParamName);
+            if (newValue != null) {
+                this.interestChargedFromDate = newValue.toDate();
+            } else {
+                this.interestChargedFromDate = null;
+            }
+        }
+
+        // the comparison should be done with the tenant date
+        // (DateUtils.getLocalDateOfTenant()) and not the server date (new
+        // LocalDate())
+        if (getSubmittedOnDate().isAfter(DateUtils.getLocalDateOfTenant())) {
+            final String errorMessage = "The date on which a loan is submitted cannot be in the future.";
+            throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, getSubmittedOnDate());
+        }
+
+        if (!(this.client == null)) {
+            if (getSubmittedOnDate().isBefore(this.client.getActivationLocalDate())) {
+                final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date.";
+                throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage,
+                        getSubmittedOnDate());
+            }
+        } else if (!(this.group == null)) {
+            if (getSubmittedOnDate().isBefore(this.group.getActivationLocalDate())) {
+                final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
+                throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage,
+                        getSubmittedOnDate());
+            }
+        }
+
+        if (getSubmittedOnDate().isAfter(getExpectedDisbursedOnLocalDate())) {
+            final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: "
+                    + getExpectedDisbursedOnLocalDate().toString();
+            throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage,
+                    getSubmittedOnDate(), getExpectedDisbursedOnLocalDate());
+        }
+
+        final String chargesParamName = "charges";
+
+        if (isChargesModified) {
+            actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges));
+            actualChanges.put("recalculateLoanSchedule", true);
+        }
+
+        final String collateralParamName = "collateral";
+        if (command.parameterExists(collateralParamName)) {
+
+            if (!possiblyModifedLoanCollateralItems.equals(this.collateral)) {
+                actualChanges.put(collateralParamName, listOfLoanCollateralData(possiblyModifedLoanCollateralItems));
+            }
+        }
+
+        final String loanTermFrequencyParamName = "loanTermFrequency";
+        if (command.isChangeInIntegerParameterNamed(loanTermFrequencyParamName, this.termFrequency)) {
+            final Integer newValue = command.integerValueOfParameterNamed(loanTermFrequencyParamName);
+            actualChanges.put(externalIdParamName, newValue);
+            this.termFrequency = newValue;
+        }
+
+        final String loanTermFrequencyTypeParamName = "loanTermFrequencyType";
+        if (command.isChangeInIntegerParameterNamed(loanTermFrequencyTypeParamName, this.termPeriodFrequencyType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(loanTermFrequencyTypeParamName);
+            final PeriodFrequencyType newTermPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue);
+            actualChanges.put(loanTermFrequencyTypeParamName, newTermPeriodFrequencyType.getValue());
+            this.termPeriodFrequencyType = newValue;
+        }
+
+        final String principalParamName = "principal";
+        if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.approvedPrincipal)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
+            this.approvedPrincipal = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.proposedPrincipal)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
+            this.proposedPrincipal = newValue;
+        }
+
+        if (loanProduct.isMultiDisburseLoan()) {
+            updateDisbursementDetails(command, actualChanges);
+            if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
+                    this.maxOutstandingLoanBalance)) {
+                this.maxOutstandingLoanBalance = command
+                        .bigDecimalValueOfParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName);
+            }
+            final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
+
+            if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
+                final String errorMessage = "For this loan product, disbursement details must be provided";
+                throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+            }
+            if (disbursementDataArray.size() > loanProduct.maxTrancheCount()) {
+                final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
+                throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
+                        loanProduct.maxTrancheCount(), disbursementDetails.size());
+            }
+        } else {
+            this.disbursementDetails.clear();
+        }
+
+        if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount()) {
+            if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount)) {
+                this.fixedEmiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
+                actualChanges.put(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount);
+                actualChanges.put("recalculateLoanSchedule", true);
+            }
+        } else {
+            this.fixedEmiAmount = null;
+        }
+
+        return actualChanges;
+    }
+
+    public void recalculateAllCharges() {
+        Set<LoanCharge> charges = this.charges();
+        int penaltyWaitPeriod = 0;
+        for (final LoanCharge loanCharge : charges) {
+            recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
+        }
+        updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+    }
+
+    public boolean isInterestRecalculationEnabledForProduct() {
+        return this.loanProduct.isInterestRecalculationEnabled();
+    }
+
+    public boolean isMultiDisburmentLoan() {
+        return this.loanProduct.isMultiDisburseLoan();
+    }
+
+    /**
+     * Update interest recalculation settings if product configuration changes
+     */
+    public void updateLoanInterestRecalculationSettings(final LocalDate recalculationRestFrequencyDate,
+            final LocalDate recalculationCompoundingFrequencyDate, final JsonCommand command, final Map<String, Object> actualChanges) {
+
+        if (isInterestRecalculationEnabledForProduct()) {
+            Date restFrequencyDate = null;
+            if (recalculationRestFrequencyDate != null) {
+                restFrequencyDate = recalculationRestFrequencyDate.toDate();
+            }
+
+            Date compoundingFrequencyDate = null;
+            if (recalculationCompoundingFrequencyDate != null) {
+                compoundingFrequencyDate = recalculationCompoundingFrequencyDate.toDate();
+            }
+            if (this.loanInterestRecalculationDetails == null) {
+                actualChanges.put(LoanProductConstants.isInterestRecalculationEnabledParameterName, true);
+                this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct
+                        .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct
+                        .getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct
+                        .getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct
+                        .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct
+                        .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct
+                        .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate);
+                this.loanInterestRecalculationDetails.updateLoan(this);
+            } else {
+
+                this.loanInterestRecalculationDetails.update(command, actualChanges);
+            }
+        } else {
+            this.loanInterestRecalculationDetails = null;
+        }
+    }
+
+    private void updateOverdueScheduleInstallment(final LoanCharge loanCharge) {
+        if (loanCharge.isOverdueInstallmentCharge() && loanCharge.isActive()) {
+            LoanOverdueInstallmentCharge overdueInstallmentCharge = loanCharge.getOverdueInstallmentCharge();
+            if (overdueInstallmentCharge != null) {
+                Integer installmentNumber = overdueInstallmentCharge.getInstallment().getInstallmentNumber();
+                LoanRepaymentScheduleInstallment installment = fetchRepaymentScheduleInstallment(installmentNumber);
+                overdueInstallmentCharge.updateLoanRepaymentScheduleInstallment(installment);
+            }
+        }
+    }
+
+    private void recalculateLoanCharge(final LoanCharge loanCharge, final int penaltyWaitPeriod) {
+        BigDecimal amount = BigDecimal.ZERO;
+        if (loanCharge.isOverdueInstallmentCharge()) {
+            amount = calculateOverdueAmountPercentageAppliedTo(loanCharge, penaltyWaitPeriod);
+        } else {
+            amount = calculateAmountPercentageAppliedTo(loanCharge);
+        }
+
+        BigDecimal chargeAmt = BigDecimal.ZERO;
+        BigDecimal totalChargeAmt = BigDecimal.ZERO;
+        if (loanCharge.getChargeCalculation().isPercentageBased()) {
+            chargeAmt = loanCharge.getPercentage();
+            if (loanCharge.isInstalmentFee()) {
+                totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
+            }
+        } else {
+            chargeAmt = loanCharge.amountOrPercentage();
+        }
+        if (loanCharge.isActive()) {
+            loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt);
+            validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
+        }
+
+    }
+
+    private BigDecimal calculateOverdueAmountPercentageAppliedTo(final LoanCharge loanCharge, final int penaltyWaitPeriod) {
+        LoanRepaymentScheduleInstallment installment = loanCharge.getOverdueInstallmentCharge().getInstallment();
+        LocalDate graceDate = LocalDate.now().minusDays(penaltyWaitPeriod);
+        Money amount = Money.zero(getCurrency());
+        if (graceDate.isAfter(installment.getDueDate())) {
+            amount = calculateOverdueAmountPercentageAppliedTo(installment, loanCharge.getChargeCalculation());
+            if (!amount.isGreaterThanZero()) {
+                loanCharge.setActive(false);
+            }
+        } else {
+            loanCharge.setActive(false);
+        }
+        return amount.getAmount();
+    }
+
+    private Money calculateOverdueAmountPercentageAppliedTo(LoanRepaymentScheduleInstallment installment,
+            ChargeCalculationType calculationType) {
+        Money amount = Money.zero(getCurrency());
+        switch (calculationType) {
+            case PERCENT_OF_AMOUNT:
+                amount = installment.getPrincipalOutstanding(getCurrency());
+            break;
+            case PERCENT_OF_AMOUNT_AND_INTEREST:
+                amount = installment.getPrincipalOutstanding(getCurrency()).plus(installment.getInterestOutstanding(getCurrency()));
+            break;
+            case PERCENT_OF_INTEREST:
+                amount = installment.getInterestOutstanding(getCurrency());
+            break;
+            default:
+            break;
+        }
+        return amount;
+    }
+
+    // This method returns date format and locale if present in the JsonCommand
+    private Map<String, String> getDateFormatAndLocale(final JsonCommand jsonCommand) {
+        Map<String, String> returnObject = new HashMap<>();
+        JsonElement jsonElement = jsonCommand.parsedJson();
+        if (jsonElement.isJsonObject()) {
+            JsonObject topLevel = jsonElement.getAsJsonObject();
+            if (topLevel.has(LoanApiConstants.dateFormatParameterName)
+                    && topLevel.get(LoanApiConstants.dateFormatParameterName).isJsonPrimitive()) {
+                final JsonPrimitive primitive = topLevel.get(LoanApiConstants.dateFormatParameterName).getAsJsonPrimitive();
+                returnObject.put(LoanApiConstants.dateFormatParameterName, primitive.getAsString());
+            }
+            if (topLevel.has(LoanApiConstants.localeParameterName) && topLevel.get(LoanApiConstants.localeParameterName).isJsonPrimitive()) {
+                final JsonPrimitive primitive = topLevel.get(LoanApiConstants.localeParameterName).getAsJsonPrimitive();
+                String localeString = primitive.getAsString();
+                returnObject.put(LoanApiConstants.localeParameterName, localeString);
+            }
+        }
+        return returnObject;
+    }
+
+    private Map<String, Object> parseDisbursementDetails(final JsonObject jsonObject, String dateFormat, Locale locale) {
+        Map<String, Object> returnObject = new HashMap<>();
+        if (jsonObject.get(LoanApiConstants.disbursementDateParameterName) != null
+                && jsonObject.get(LoanApiConstants.disbursementDateParameterName).isJsonPrimitive()) {
+            final JsonPrimitive primitive = jsonObject.get(LoanApiConstants.disbursementDateParameterName).getAsJsonPrimitive();
+            final String valueAsString = primitive.getAsString();
+            if (StringUtils.isNotBlank(valueAsString)) {
+                LocalDate date = JsonParserHelper.convertFrom(valueAsString, LoanApiConstants.disbursementDateParameterName, dateFormat,
+                        locale);
+                if (date != null) {
+                    returnObject.put(LoanApiConstants.disbursementDateParameterName, date.toDate());
+                }
+            }
+        }
+
+        if (jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
+                && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
+            BigDecimal principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
+            returnObject.put(LoanApiConstants.disbursementPrincipalParameterName, principal);
+        }
+
+        if (jsonObject.has(LoanApiConstants.disbursementIdParameterName)
+                && jsonObject.get(LoanApiConstants.disbursementIdParameterName).isJsonPrimitive()
+                && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) {
+            Long id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong();
+            returnObject.put(LoanApiConstants.disbursementIdParameterName, id);
+        }
+
+        if (jsonObject.has(LoanApiConstants.loanChargeIdParameterName)
+                && jsonObject.get(LoanApiConstants.loanChargeIdParameterName).isJsonPrimitive()
+                && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.loanChargeIdParameterName).getAsString()))) {
+            returnObject.put(LoanApiConstants.loanChargeIdParameterName,
+                    jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString());
+        }
+        return returnObject;
+    }
+
+    public void updateDisbursementDetails(final JsonCommand jsonCommand, final Map<String, Object> actualChanges) {
+
+        List<Long> disbursementList = fetchDisbursementIds();
+        List<Long> loanChargeIds = fetchLoanChargeIds();
+        int chargeIdLength = loanChargeIds.size();
+        String chargeIds = null;
+        // From modify application page, if user removes all charges, we should
+        // get empty array.
+        // So we need to remove all charges applied for this loan
+        boolean removeAllChages = false;
+        if (jsonCommand.parameterExists(LoanApiConstants.chargesParameterName)) {
+            JsonArray chargesArray = jsonCommand.arrayOfParameterNamed(LoanApiConstants.chargesParameterName);
+            if (chargesArray.size() == 0) removeAllChages = true;
+        }
+
+        if (jsonCommand.parameterExists(LoanApiConstants.disbursementDataParameterName)) {
+            final JsonArray disbursementDataArray = jsonCommand.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
+            if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
+                String dateFormat = null;
+                Locale locale = null;
+                // Gets date format and locate
+                Map<String, String> dateAndLocale = getDateFormatAndLocale(jsonCommand);
+                dateFormat = dateAndLocale.get(LoanApiConstants.dateFormatParameterName);
+                if (dateAndLocale.containsKey(LoanApiConstants.localeParameterName)) {
+                    locale = JsonParserHelper.localeFromString(dateAndLocale.get(LoanApiConstants.localeParameterName));
+                }
+                for (JsonElement jsonElement : disbursementDataArray) {
+                    final JsonObject jsonObject = jsonElement.getAsJsonObject();
+                    Map<String, Object> parsedDisbursementData = parseDisbursementDetails(jsonObject, dateFormat, locale);
+                    Date expectedDisbursementDate = (Date) parsedDisbursementData.get(LoanApiConstants.disbursementDateParameterName);
+                    BigDecimal principal = (BigDecimal) parsedDisbursementData.get(LoanApiConstants.disbursementPrincipalParameterName);
+                    Long disbursementID = (Long) parsedDisbursementData.get(LoanApiConstants.disbursementIdParameterName);
+                    chargeIds = (String) parsedDisbursementData.get(LoanApiConstants.loanChargeIdParameterName);
+                    if (chargeIds != null) {
+                        if (chargeIds.indexOf(",") != -1) {
+                            String[] chargeId = chargeIds.split(",");
+                            for (String loanChargeId : chargeId) {
+                                loanChargeIds.remove(Long.parseLong(loanChargeId));
+                            }
+                        } else {
+                            loanChargeIds.remove(Long.parseLong(chargeIds));
+                        }
+                    }
+                    createOrUpdateDisbursementDetails(disbursementID, actualChanges, expectedDisbursementDate, principal, disbursementList);
+                }
+                removeDisbursementAndAssociatedCharges(actualChanges, disbursementList, loanChargeIds, chargeIdLength, removeAllChages);
+            }
+        }
+    }
+
+    private void removeDisbursementAndAssociatedCharges(final Map<String, Object> actualChanges, List<Long> disbursementList,
+            List<Long> loanChargeIds, int chargeIdLength, boolean removeAllChages) {
+        if (removeAllChages) {
+            LoanCharge[] tempCharges = new LoanCharge[this.charges.size()];
+            this.charges.toArray(tempCharges);
+            for (LoanCharge loanCharge : tempCharges) {
+                removeLoanCharge(loanCharge);
+            }
+            this.trancheCharges.clear();
+        } else {
+            if (loanChargeIds.size() > 0 && loanChargeIds.size() != chargeIdLength) {
+                for (Long chargeId : loanChargeIds) {
+                    LoanCharge deleteCharge = fetchLoanChargesById(chargeId);
+                    if (this.charges.contains(deleteCharge)) {
+                        removeLoanCharge(deleteCharge);
+                    }
+                }
+            }
+        }
+        for (Long id : disbursementList) {
+            removeChargesByDisbursementID(id);
+            this.disbursementDetails.remove(fetchLoanDisbursementsById(id));
+            actualChanges.put("recalculateLoanSchedule", true);
+        }
+    }
+
+    private void createOrUpdateDisbursementDetails(Long disbursementID, final Map<String, Object> actualChanges,
+            Date expectedDisbursementDate, BigDecimal principal, List<Long> existingDisbursementList) {
+
+        if (disbursementID != null) {
+            LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(disbursementID);
+            existingDisbursementList.remove(disbursementID);
+            if (loanDisbursementDetail.actualDisbursementDate() == null) {
+                Date actualDisbursementDate = null;
+                LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate,
+                        principal);
+                disbursementDetails.updateLoan(this);
+                if (!loanDisbursementDetail.equals(disbursementDetails)) {
+                    loanDisbursementDetail.copy(disbursementDetails);
+                    actualChanges.put("disbursementDetailId", disbursementID);
+                    actualChanges.put("recalculateLoanSchedule", true);
+                }
+            }
+        } else {
+            Date actualDisbursementDate = null;
+            LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate,
+                    principal);
+            disbursementDetails.updateLoan(this);
+            this.disbursementDetails.add(disbursementDetails);
+            for (LoanTrancheCharge trancheCharge : trancheCharges) {
+                Charge chargeDefinition = trancheCharge.getCharge();
+                final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, null, null, null, new LocalDate(
+                        expectedDisbursementDate), null, null);
+                loanCharge.update(this);
+                LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
+                        disbursementDetails);
+                loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
+                addLoanCharge(loanCharge);
+            }
+            actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal);
+            actualChanges.put("recalculateLoanSchedule", true);
+        }
+    }
+
+    private void removeChargesByDisbursementID(Long id) {
+        List<LoanCharge> tempCharges = new ArrayList<>();
+        for (LoanCharge charge : this.charges) {
+            LoanTrancheDisbursementCharge transCharge = charge.getTrancheDisbursementCharge();
+            if (transCharge != null && id.equals(transCharge.getloanDisbursementDetails().getId())) {
+                tempCharges.add(charge);
+            }
+        }
+        for (LoanCharge charge : tempCharges) {
+            removeLoanCharge(charge);
+        }
+    }
+
+    private List<Long> fetchLoanChargeIds() {
+        List<Long> list = new ArrayList<>();
+        for (LoanCharge charge : this.charges) {
+            list.add(charge.getId());
+        }
+        return list;
+    }
+
+    public LoanDisbursementDetails fetchLoanDisbursementsById(Long id) {
+        LoanDisbursementDetails loanDisbursementDetail = null;
+        for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
+            if (id.equals(disbursementDetail.getId())) {
+                loanDisbursementDetail = disbursementDetail;
+                break;
+            }
+        }
+        return loanDisbursementDetail;
+    }
+
+    private List<Long> fetchDisbursementIds() {
+        List<Long> list = new ArrayList<>();
+        for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
+            list.add(disbursementDetails.getId());
+        }
+        return list;
+    }
+
+    private CollateralData[] listOfLoanCollateralData(final Set<LoanCollateral> setOfLoanCollateral) {
+
+        CollateralData[] existingLoanCollateral = null;
+
+        final List<CollateralData> loanCollateralList = new ArrayList<>();
+        for (final LoanCollateral loanCollateral : setOfLoanCollateral) {
+
+            final CollateralData data = loanCollateral.toData();
+
+            loanCollateralList.add(data);
+        }
+
+        existingLoanCollateral = loanCollateralList.toArray(new CollateralData[loanCollateralList.size()]);
+
+        return existingLoanCollateral;
+    }
+
+    private LoanChargeCommand[] getLoanCharges(final Set<LoanCharge> setOfLoanCharges) {
+
+        LoanChargeCommand[] existingLoanCharges = null;
+
+        final List<LoanChargeCommand> loanChargesList = new ArrayList<>();
+        for (final LoanCharge loanCharge : setOfLoanCharges) {
+            loanChargesList.add(loanCharge.toCommand());
+        }
+
+        existingLoanCharges = loanChargesList.toArray(new LoanChargeCommand[loanChargesList.size()]);
+
+        return existingLoanCharges;
+    }
+
+    private void removeFirstDisbursementTransaction() {
+        for (final LoanTransaction loanTransaction : this.loanTransactions) {
+            if (loanTransaction.isDisbursement()) {
+                this.loanTransactions.remove(loanTransaction);
+                break;
+            }
+        }
+    }
+
+    public void loanApplicationSubmittal(final AppUser currentUser, final LoanScheduleModel loanSchedule,
+            final LoanApplicationTerms loanApplicationTerms, final LoanLifecycleStateMachine lifecycleStateMachine,
+            final LocalDate submittedOn, final String externalId, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays,
+            final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, final LocalDate recalculationRestFrequencyDate,
+            final LocalDate recalculationCompoundingFrequencyDate) {
+
+        updateLoanSchedule(loanSchedule, currentUser);
+
+        LoanStatus from = null;
+        if (this.loanStatus != null) {
+            from = LoanStatus.fromInt(this.loanStatus);
+        }
+
+        final LoanStatus statusEnum = lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, from);
+        this.loanStatus = statusEnum.getValue();
+
+        this.externalId = externalId;
+        this.termFrequency = loanApplicationTerms.getLoanTermFrequency();
+        this.termPeriodFrequencyType = loanApplicationTerms.getLoanTermPeriodFrequencyType().getValue();
+        this.submittedOnDate = submittedOn.toDate();
+        this.submittedBy = currentUser;
+        this.expectedDisbursementDate = loanApplicationTerms.getExpectedDisbursementDate().toDate();
+        this.expectedFirstRepaymentOnDate = loanApplicationTerms.getRepaymentStartFromDate();
+        this.interestChargedFromDate = loanApplicationTerms.getInterestChargedFromDate();
+
+        if (loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.MONTHS) {
+            this.repaymentFrequencyNthDayType = loanApplicationTerms.getNthDay();
+            this.repaymentFrequencyDayOfWeekType = loanApplicationTerms.getWeekDayType().getValue();
+        } else {
+            this.repaymentFrequencyNthDayType = NthDayType.INVALID.getValue();
+            this.repaymentFrequencyDayOfWeekType = DayOfWeekType.INVALID.getValue();
+        }
+
+        updateLoanScheduleDependentDerivedFields();
+
+        if (submittedOn.isAfter(DateUtils.getLocalDateOfTenant())) {
+            final String errorMessage = "The date on which a loan is submitted cannot be in the future.";
+            throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn);
+        }
+
+        if (this.client != null && this.client.isActivatedAfter(submittedOn)) {
+            final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date.";
+            throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, submittedOn);
+        }
+
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CREATED, submittedOn);
+
+        if (this.group != null && this.group.isActivatedAfter(submittedOn)) {
+            final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
+            throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, submittedOn);
+        }
+
+        if (submittedOn.isAfter(getExpectedDisbursedOnLocalDate())) {
+            final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: "
+                    + getExpectedDisbursedOnLocalDate().toString();
+            throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage,
+                    submittedOn, getExpectedDisbursedOnLocalDate());
+        }
+
+        // charges are optional
+        int penaltyWaitPeriod = 0;
+        for (final LoanCharge loanCharge : charges()) {
+            recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
+        }
+
+        updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+
+        // validate if disbursement date is a holiday or a non-working day
+        validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
+        validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
+
+        /**
+         * Copy interest recalculation settings if interest recalculation is
+         * enabled
+         */
+        if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
+            Date restFrequencyDate = null;
+            if (recalculationRestFrequencyDate != null) {
+                restFrequencyDate = recalculationRestFrequencyDate.toDate();
+            }
+
+            Date compoundingFrequencyDate = null;
+            if (recalculationCompoundingFrequencyDate != null) {
+                compoundingFrequencyDate = recalculationCompoundingFrequencyDate.toDate();
+            }
+            this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct
+                    .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct
+                    .getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct
+                    .getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct
+                    .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct
+                    .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct
+                    .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate);
+            this.loanInterestRecalculationDetails.updateLoan(this);
+        }
+
+    }
+
+    private LocalDate determineExpectedMaturityDate() {
+        final int numberOfInstallments = this.repaymentScheduleInstallments.size();
+        return this.repaymentScheduleInstallments.get(numberOfInstallments - 1).getDueDate();
+    }
+
+    public Map<String, Object> loanApplicationRejection(final AppUser currentUser, final JsonCommand command,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+
+        validateAccountStatus(LoanEvent.LOAN_REJECTED);
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, LoanStatus.fromInt(this.loanStatus));
+        if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
+            this.loanStatus = statusEnum.getValue();
+            actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
+
+            final LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate");
+
+            final Locale locale = new Locale(command.locale());
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+            this.rejectedOnDate = rejectedOn.toDate();
+            this.rejectedBy = currentUser;
+            this.closedOnDate = rejectedOn.toDate();
+            this.closedBy = currentUser;
+
+            actualChanges.put("locale", command.locale());
+            actualChanges.put("dateFormat", command.dateFormat());
+            actualChanges.put("rejectedOnDate", rejectedOn.toString(fmt));
+            actualChanges.put("closedOnDate", rejectedOn.toString(fmt));
+
+            if (rejectedOn.isBefore(getSubmittedOnDate())) {
+                final String errorMessage = "The date on which a loan is rejected cannot be before its submittal date: "
+                        + getSubmittedOnDate().toString();
+                throw new InvalidLoanStateTransitionException("reject", "cannot.be.before.submittal.date", errorMessage, rejectedOn,
+                        getSubmittedOnDate());
+            }
+
+            validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REJECTED, rejectedOn);
+
+            if (rejectedOn.isAfter(DateUtils.getLocalDateOfTenant())) {
+                final String errorMessage = "The date on which a loan is rejected cannot be in the future.";
+                throw new InvalidLoanStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, rejectedOn);
+            }
+        } else {
+            final String errorMessage = "Only the loan applications with status 'Submitted and pending approval' are allowed to be rejected.";
+            throw new InvalidLoanStateTransitionException("reject", "cannot.reject", errorMessage);
+        }
+
+        return actualChanges;
+    }
+
+    public Map<String, Object> loanApplicationWithdrawnByApplicant(final AppUser currentUser, final JsonCommand command,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAWN, LoanStatus.fromInt(this.loanStatus));
+        if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
+            this.loanStatus = statusEnum.getValue();
+            actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
+
+            LocalDate withdrawnOn = command.localDateValueOfParameterNamed("withdrawnOnDate");
+            if (withdrawnOn == null) {
+                withdrawnOn = command.localDateValueOfParameterNamed("eventDate");
+            }
+
+            final Locale locale = new Locale(command.locale());
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+            this.withdrawnOnDate = withdrawnOn.toDate();
+            this.withdrawnBy = currentUser;
+            this.closedOnDate = withdrawnOn.toDate();
+            this.closedBy = currentUser;
+
+            actualChanges.put("locale", command.locale());
+            actualChanges.put("dateFormat", command.dateFormat());
+            actualChanges.put("withdrawnOnDate", withdrawnOn.toString(fmt));
+            actualChanges.put("closedOnDate", withdrawnOn.toString(fmt));
+
+            if (withdrawnOn.isBefore(getSubmittedOnDate())) {
+                final String errorMessage = "The date on which a loan is withdrawn cannot be before its submittal date: "
+                        + getSubmittedOnDate().toString();
+                throw new InvalidLoanStateTransitionException("withdraw", "cannot.be.before.submittal.date", errorMessage, command,
+                        getSubmittedOnDate());
+            }
+
+            validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_WITHDRAWN, withdrawnOn);
+
+            if (withdrawnOn.isAfter(DateUtils.getLocalDateOfTenant())) {
+                final String errorMessage = "The date on which a loan is withdrawn cannot be in the future.";
+                throw new InvalidLoanStateTransitionException("withdraw", "cannot.be.a.future.date", errorMessage, command);
+            }
+        } else {
+            final String errorMessage = "Only the loan applications with status 'Submitted and pending approval' are allowed to be withdrawn by applicant.";
+            throw new InvalidLoanStateTransitionException("withdraw", "cannot.withdraw", errorMessage);
+        }
+
+        return actualChanges;
+    }
+
+    public Map<String, Object> loanApplicationApproval(final AppUser currentUser, final JsonCommand command,
+            final JsonArray disbursementDataArray, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+
+        validateAccountStatus(LoanEvent.LOAN_APPROVED);
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        /*
+         * statusEnum is holding the possible new status derived from
+         * loanLifecycleStateMachine.transition.
+         */
+
+        final LoanStatus newStatusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVED, LoanStatus.fromInt(this.loanStatus));
+
+        /*
+         * FIXME: There is no need to check below condition, if
+         * loanLifecycleStateMachine.transition is doing it's responsibility
+         * properly. Better implementation approach is, if code passes invalid
+         * combination of states (fromState and toState), state machine should
+         * return invalidate state and below if condition should check for not
+         * equal to invalidateState, instead of check new value is same as
+         * present value.
+         */
+
+        if (!newStatusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
+            this.loanStatus = newStatusEnum.getValue();
+            actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
+
+            // only do below if status has changed in the 'approval' case
+            LocalDate approvedOn = command.localDateValueOfParameterNamed("approvedOnDate");
+            String approvedOnDateChange = command.stringValueOfParameterNamed("approvedOnDate");
+            if (approvedOn == null) {
+                approvedOn = command.localDateValueOfParameterNamed("eventDate");
+                approvedOnDateChange = command.stringValueOfParameterNamed("eventDate");
+            }
+
+            LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate");
+
+            BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName);
+
+            if (approvedLoanAmount != null) {
+
+                // Approved amount has to be less than or equal to principal
+                // amount demanded
+
+                if (approvedLoanAmount.compareTo(this.proposedPrincipal) == -1) {
+
+                    this.approvedPrincipal = approvedLoanAmount;
+
+                    /*
+                     * All the calculations are done based on the principal
+                     * amount, so it is necessary to set principal amount to
+                     * approved amount
+                     */
+
+                    this.loanRepaymentScheduleDetail.setPrincipal(approvedLoanAmount);
+
+                    actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount);
+                    actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount);
+                } else if (approvedLoanAmount.compareTo(this.proposedPrincipal) == 1) {
+                    final String errorMessage = "Loan approved amount can't be greater than loan amount demanded.";
+                    throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded",
+                            errorMessage, this.proposedPrincipal, approvedLoanAmount);
+                }
+
+                /* Update disbursement details */
+                if (disbursementDataArray != null) {
+                    updateDisbursementDetails(command, actualChanges);
+                }
+            }
+            if (loanProduct.isMultiDisburseLoan()) {
+                recalculateAllCharges();
+
+                if (this.disbursementDetails.isEmpty()) {
+                    final String errorMessage = "For this loan product, disbursement details must be provided";
+                    throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+                }
+
+                if (this.disbursementDetails.size() > loanProduct.maxTrancheCount()) {
+                    final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
+                    throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
+                            loanProduct.maxTrancheCount(), disbursementDetails.size());
+                }
+            }
+            this.approvedOnDate = approvedOn.toDate();
+            this.approvedBy = currentUser;
+            actualChanges.put("locale", command.locale());
+            actualChanges.put("dateFormat", command.dateFormat());
+            actualChanges.put("approvedOnDate", approvedOnDateChange);
+
+            final LocalDate submittalDate = new LocalDate(this.submittedOnDate);
+            if (approvedOn.isBefore(submittalDate)) {
+                final String errorMessage = "The date on which a loan is approved cannot be before its submittal date: "
+                        + submittalDate.toString();
+                throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage,
+                        getApprovedOnDate(), submittalDate);
+            }
+
+            if (expecteddisbursementDate != null) {
+                this.expectedDisbursementDate = expecteddisbursementDate.toDate();
+                actualChanges.put("expectedDisbursementDate", expectedDisbursementDate);
+
+                if (expecteddisbursementDate.isBefore(approvedOn)) {
+                    final String errorMessage = "The expected disbursement date should be either on or after the approval date: "
+                            + approvedOn.toString();
+                    throw new InvalidLoanStateTransitionException("expecteddisbursal", "should.be.on.or.after.approval.date", errorMessage,
+                            getApprovedOnDate(), expecteddisbursementDate);
+                }
+            }
+
+            validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_APPROVED, approvedOn);
+
+            if (approvedOn.isAfter(DateUtils.getLocalDateOfTenant())) {
+                final String errorMessage = "The date on which a loan is approved cannot be in the future.";
+                throw new InvalidLoanStateTransitionException("approval", "cannot.be.a.future.date", errorMessage, getApprovedOnDate());
+            }
+
+            if (this.loanOfficer != null) {
+                final LoanOfficerAssignmentHistory loanOfficerAssignmentHistory = LoanOfficerAssignmentHistory.createNew(this,
+                        this.loanOfficer, approvedOn);
+                this.loanOfficerHistory.add(loanOfficerAssignmentHistory);
+            }
+        }
+
+        return actualChanges;
+    }
+
+    public Map<String, Object> undoApproval(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+
+        validateAccountStatus(LoanEvent.LOAN_APPROVAL_UNDO);
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus);
+        final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, currentStatus);
+        if (!statusEnum.hasStateOf(currentStatus)) {
+            this.loanStatus = statusEnum.getValue();
+            actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
+
+            this.approvedOnDate = null;
+            this.approvedBy = null;
+
+            if (this.approvedPrincipal.compareTo(this.proposedPrincipal) != 0) {
+                this.approvedPrincipal = this.proposedPrincipal;
+                this.loanRepaymentScheduleDetail.setPrincipal(this.proposedPrincipal);
+
+                actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, this.proposedPrincipal);
+                actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, this.proposedPrincipal);
+
+            }
+
+            actualChanges.put("approvedOnDate", "");
+
+            this.loanOfficerHistory.clear();
+        }
+
+        return actualChanges;
+    }
+
+    public Collection<Long> findExistingTransactionIds() {
+
+        final Collection<Long> ids = new ArrayList<>();
+
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            ids.add(transaction.getId());
+        }
+
+        return ids;
+    }
+
+    public Collection<Long> findExistingReversedTransactionIds() {
+
+        final Collection<Long> ids = new ArrayList<>();
+
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if (transaction.isReversed()) {
+                ids.add(transaction.getId());
+            }
+        }
+
+        return ids;
+    }
+
+    public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges,
+            final ScheduleGeneratorDTO scheduleGeneratorDTO) {
+
+        final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
+                LoanStatus.fromInt(this.loanStatus));
+
+        final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
+
+        this.loanStatus = statusEnum.getValue();
+        actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
+
+        this.disbursedBy = currentUser;
+        updateLoanScheduleDependentDerivedFields();
+
+        actualChanges.put("locale", command.locale());
+        actualChanges.put("dateFormat", command.dateFormat());
+        actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate"));
+
+        HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
+
+        // validate if disbursement date is a holiday or a non-working day
+        validateDisbursementDateIsOnNonWorkingDay(holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
+        validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
+
+        if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
+                && (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) {
+            regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
+        }
+
+        updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+        updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
+        LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
+        handleDisbursementTransaction(actualDisbursementDate, createdDate, currentUser);
+        updateLoanSummaryDerivedFields();
+        final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
+
+        /**
+         * Add an interest applied transaction of the interest is accrued
+         * upfront (Up front accrual), no accounting or cash based accounting is
+         * selected
+         **/
+
+        if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
+            final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
+                    actualDisbursementDate, createdDate, currentUser);
+            this.loanTransactions.add(interestAppliedTransaction);
+        }
+
+        return reprocessTransactionForDisbursement();
+
+    }
+
+    public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule,
+            final LocalDate actualDisbursementDate, BigDecimal emiAmount, final AppUser currentUser, LocalDate nextPossibleRepaymentDate, Date rescheduledRepaymentDate) {
+        boolean isEmiAmountChanged = false;
+        if ((this.loanProduct.isMultiDisburseLoan() || this.loanProduct.canDefineInstallmentAmount()) && emiAmount != null
+                && emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
+            if (this.loanProduct.isMultiDisburseLoan()) {
+                final Date dateValue = null;
+                final boolean isSpecificToInstallment = false;
+                LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
+                        actualDisbursementDate.toDate(), emiAmount, dateValue, isSpecificToInstallment, this, LoanStatus.ACTIVE.getValue());
+                this.loanTermVariations.add(loanVariationTerms);
+            } else {
+                this.fixedEmiAmount = emiAmount;
+            }
+            isEmiAmountChanged = true;
+        }
+        if(rescheduledRepaymentDate != null && this.loanProduct.isMultiDisburseLoan()){
+        	 final boolean isSpecificToInstallment = false;
+        	 LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.DUE_DATE.getValue(),
+        			 nextPossibleRepaymentDate.toDate(), emiAmount, rescheduledRepaymentDate, isSpecificToInstallment, this, LoanStatus.ACTIVE.getValue());
+             this.loanTermVariations.add(loanVariationTerms);
+        }
+
+        if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) {
+            regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
+        }
+    }
+
+    public boolean canDisburse(final LocalDate actualDisbursementDate) {
+        Date lastDusburseDate = this.actualDisbursementDate;
+        final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
+                LoanStatus.fromInt(this.loanStatus));
+
+        boolean isMultiTrancheDisburse = false;
+        if (LoanStatus.fromInt(this.loanStatus).isActive() && isAllTranchesNotDisbursed()) {
+            LoanDisbursementDetails details = fetchLastDisburseDetail();
+
+            if (details != null) {
+                lastDusburseDate = details.actualDisbursementDate();
+            }
+            if (actualDisbursementDate.toDate().before(lastDusburseDate)) {
+                final String errorMsg = "Loan can't be disbursed before " + lastDusburseDate;
+                throw new LoanDisbursalException(errorMsg, "actualdisbursementdate.before.lastdusbursedate", lastDusburseDate,
+                        actualDisbursementDate.toDate());
+            }
+            isMultiTrancheDisburse = true;
+        }
+        return (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus)) || isMultiTrancheDisburse);
+    }
+
+    public Money adjustDisburseAmount(final JsonCommand command, final LocalDate actualDisbursementDate) {
+        Money disburseAmount = this.loanRepaymentScheduleDetail.getPrincipal().zero();
+        BigDecimal principalDisbursed = command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalDisbursedParameterName);
+        if (this.actualDisbursementDate == null) {
+            this.actualDisbursementDate = actualDisbursementDate.toDate();
+        }
+        BigDecimal diff = BigDecimal.ZERO;
+        Collection<LoanDisbursementDetails> details = fetchUndisbursedDetail();
+        if (principalDisbursed == null) {
+            disburseAmount = this.loanRepaymentScheduleDetail.getPrincipal();
+            if (!details.isEmpty()) {
+                disburseAmount = disburseAmount.zero();
+                for (LoanDisbursementDetails disbursementDetails : details) {
+                    disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate());
+                    disburseAmount = disburseAmount.plus(disbursementDetails.principal());
+                }
+            }
+        } else {
+            if (this.loanProduct.isMultiDisburseLoan()) {
+                disburseAmount = Money.of(getCurrency(), principalDisbursed);
+            } else {
+                disburseAmount = disburseAmount.plus(principalDisbursed);
+            }
+
+            if (details.isEmpty()) {
+                diff = this.loanRepaymentScheduleDetail.getPrincipal().minus(principalDisbursed).getAmount();
+            } else {
+                for (LoanDisbursementDetails disbursementDetails : details) {
+                    disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate());
+                    disbursementDetails.updatePrincipal(principalDisbursed);
+                }
+            }
+            if (this.loanProduct().isMultiDisburseLoan()) {
+                Collection<LoanDisbursementDetails> loanDisburseDetails = this.getDisbursementDetails();
+                BigDecimal setPrincipalAmount = BigDecimal.ZERO;
+                BigDecimal totalAmount = BigDecimal.ZERO;
+                for (LoanDisbursementDetails disbursementDetails : loanDisburseDetails) {
+                    if (disbursementDetails.actualDisbursementDate() != null) {
+                        setPrincipalAmount = setPrincipalAmount.add(disbursementDetails.principal());
+                    }
+                    totalAmount = totalAmount.add(disbursementDetails.principal());
+                }
+                this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
+                if (totalAmount.compareTo(this.approvedPrincipal) == 1) {
+                    final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal ";
+                    throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", principalDisbursed,
+                            this.approvedPrincipal);
+                }
+            } else {
+                this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
+            }
+            if (!(this.loanProduct().isMultiDisburseLoan()) && diff.compareTo(BigDecimal.ZERO) == -1) {
+                final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved amount ";
+                throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.amount", principalDisbursed,
+                        this.loanRepaymentScheduleDetail.getPrincipal().getAmount());
+            }
+        }
+        return disburseAmount;
+    }
+
+    private ChangedTransactionDetail reprocessTransactionForDisbursement() {
+        ChangedTransactionDetail changedTransactionDetail = null;
+        if (this.loanProduct.isMultiDisburseLoan()) {
+            final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+            if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
+                final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                        .determineProcessor(this.transactionProcessingStrategy);
+                changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
+                        allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
+                for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                    mapEntry.getValue().updateLoan(this);
+                }
+
+            }
+            updateLoanSummaryDerivedFields();
+        }
+
+        return changedTransactionDetail;
+    }
+
+    private Collection<LoanDisbursementDetails> fetchUndisbursedDetail() {
+        Collection<LoanDisbursementDetails> disbursementDetails = new ArrayList<>();
+        Date date = null;
+        for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
+            if (disbursementDetail.actualDisbursementDate() == null) {
+                if (date == null || disbursementDetail.expectedDisbursementDate().equals(date)) {
+                    disbursementDetails.add(disbursementDetail);
+                    date = disbursementDetail.expectedDisbursementDate();
+                } else if (disbursementDetail.expectedDisbursementDate().before(date)) {
+                    disbursementDetails.clear();
+                    disbursementDetails.add(disbursementDetail);
+                    date = disbursementDetail.expectedDisbursementDate();
+                }
+            }
+        }
+        return disbursementDetails;
+    }
+
+    private LoanDisbursementDetails fetchLastDisburseDetail() {
+        LoanDisbursementDetails details = null;
+        Date date = this.actualDisbursementDate;
+        if (date != null) {
+            for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
+                if (disbursementDetail.actualDisbursementDate() != null) {
+                    if (disbursementDetail.actualDisbursementDate().after(date) || disbursementDetail.actualDisbursementDate().equals(date)) {
+                        date = disbursementDetail.actualDisbursementDate();
+                        details = disbursementDetail;
+                    }
+                }
+            }
+        }
+        return details;
+    }
+
+    private boolean isDisbursementMissed() {
+        boolean isDisbursementMissed = false;
+        for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
+            if (disbursementDetail.actualDisbursementDate() == null
+                    && LocalDate.now().isAfter(disbursementDetail.expectedDisbursementDateAsLocalDate())) {
+                isDisbursementMissed = true;
+                break;
+            }
+        }
+        return isDisbursementMissed;
+    }
+
+    public BigDecimal getDisbursedAmount() {
+        BigDecimal principal = BigDecimal.ZERO;
+        for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
+            if (disbursementDetail.actualDisbursementDate() != null) {
+                principal = principal.add(disbursementDetail.principal());
+            }
+        }
+        return principal;
+    }
+
+    private void removeDisbursementDetail() {
+        Set<LoanDisbursementDetails> details = new HashSet<>(this.disbursementDetails);
+        for (LoanDisbursementDetails disbursementDetail : details) {
+            if (disbursementDetail.actualDisbursementDate() == null) {
+                this.disbursementDetails.remove(disbursementDetail);
+            }
+        }
+    }
+
+    private boolean isDisbursementAllowed() {
+        boolean isAllowed = false;
+        for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
+            if (disbursementDetail.actualDisbursementDate() == null) {
+                isAllowed = true;
+                break;
+            }
+        }
+        return isAllowed;
+    }
+
+    private boolean atleastOnceDisbursed() {
+        boolean isDisbursed = false;
+        for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
+            if (disbursementDetail.actualDisbursementDate() != null) {
+                isDisbursed = true;
+                break;
+            }
+        }
+        return isDisbursed;
+    }
+
+    private void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
+
+        for (final LoanRepaymentScheduleInstallment repaymentPeriod : this.repaymentScheduleInstallments) {
+            repaymentPeriod.updateDerivedFields(loanCurrency(), actualDisbursementDate);
+        }
+    }
+
+    /*
+     * Ability to regenerate the repayment schedule based on the loans current
+     * details/state.
+     */
+    public void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO, AppUser currentUser) {
+
+        final LoanScheduleModel loanSchedule = regenerateScheduleModel(scheduleGeneratorDTO);
+
+        updateLoanSchedule(loanSchedule, currentUser);
+        Set<LoanCharge> charges = this.charges();
+        for (LoanCharge loanCharge : charges) {
+            recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod());
+        }
+    }
+
+    public LoanScheduleModel regenerateScheduleModel(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
+
+        final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
+        final MathContext mc = new MathContext(8, roundingMode);
+
+        final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
+        final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
+
+        final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod);
+        final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(),
+                scheduleGeneratorDTO.getHolidayDetailDTO());
+        return loanSchedule;
+    }
+
+    private BigDecimal constructFloatingInterestRates(final BigDecimal annualNominalInterestRate, final FloatingRateDTO floatingRateDTO,
+            final List<LoanTermVariationsData> loanTermVariations) {
+        final LocalDate dateValue = null;
+        final boolean isSpecificToInstallment = false;
+        BigDecimal interestRate = annualNominalInterestRate;
+        if (loanProduct.isLinkedToFloatingInterestRate()) {
+            Collection<FloatingRatePeriodData> applicableRates = loanProduct.fetchInterestRates(floatingRateDTO);
+            LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant();
+            for (FloatingRatePeriodData periodData : applicableRates) {
+                LoanTermVariationsData loanTermVariation = new LoanTermVariationsData(
+                        LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(),
+                        periodData.getInterestRate(), dateValue, isSpecificToInstallment);
+                if (!interestRateStartDate.isBefore(periodData.getFromDateAsLocalDate())) {
+                    interestRateStartDate = periodData.getFromDateAsLocalDate();
+                    interestRate = periodData.getInterestRate();
+                }
+                loanTermVariations.add(loanTermVariation);
+            }
+        }
+        return interestRate;
+    }
+
+    private void handleDisbursementTransaction(final LocalDate disbursedOn, final LocalDateTime createdDate, final AppUser currentUser) {
+
+        // add repayment transaction to track incoming money from client to mfi
+        // for (charges due at time of disbursement)
+
+        /***
+         * TODO Vishwas: do we need to be able to pass in payment type details
+         * for repayments at disbursements too?
+         ***/
+
+        final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(loanCurrency());
+        /**
+         * all Charges repaid at disbursal is marked as repaid and
+         * "APPLY Charge" transactions are created for all other fees ( which
+         * are created during disbursal but not repaid)
+         **/
+
+        Money disbursentMoney = Money.zero(getCurrency());
+        final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, null, disbursedOn,
+                null, createdDate, currentUser);
+        final Integer installmentNumber = null;
+        for (final LoanCharge charge : charges()) {
+            Date actualDisbursementDate = getActualDisbursementDate(charge);
+            if (charge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue()
+                    || (charge.getCharge().getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()
+                            && disbursedOn.equals(new LocalDate(actualDisbursementDate)) && actualDisbursementDate != null
+                            && !charge.isWaived() && !charge.isFullyPaid())) {
+                if (totalFeeChargesDueAtDisbursement.isGreaterThanZero() && !charge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
+                    charge.markAsFullyPaid();
+                    // Add "Loan Charge Paid By" details to this transaction
+                    final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount(),
+                            installmentNumber);
+                    chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
+                    disbursentMoney = disbursentMoney.plus(charge.amount());
+                }
+            } else if (disbursedOn.equals(new LocalDate(this.actualDisbursementDate))) {
+                /**
+                 * create a Charge applied transaction if Up front Accrual, None
+                 * or Cash based accounting is enabled
+                 **/
+                if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
+                    handleChargeAppliedTransaction(charge, disbursedOn, currentUser);
+                }
+            }
+        }
+
+        if (disbursentMoney.isGreaterThanZero()) {
+            final Money zero = Money.zero(getCurrency());
+            chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero);
+            chargesPayment.updateLoan(this);
+            this.loanTransactions.add(chargesPayment);
+            updateLoanOutstandingBalaces();
+        }
+
+        if (getApprovedOnDate() != null && disbursedOn.isBefore(getApprovedOnDate())) {
+            final String errorMessage = "The date on which a loan is disbursed cannot be before its approval date: "
+                    + getApprovedOnDate().toString();
+            throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.before.approval.date", errorMessage, disbursedOn,
+                    getApprovedOnDate());
+        }
+
+        if (getExpectedFirstRepaymentOnDate() != null
+                && (disbursedOn.isAfter(this.fetchRepaymentScheduleInstallment(1).getDueDate()) || disbursedOn
+                        .isAfter(getExpectedFirstRepaymentOnDate())) && disbursedOn.toDate().equals(this.actualDisbursementDate)) {
+            final String errorMessage = "submittedOnDate cannot be after the loans  expectedFirstRepaymentOnDate: "
+                    + getExpectedFirstRepaymentOnDate().toString();
+            throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.after.expected.first.repayment.date", errorMessage,
+                    disbursedOn, getExpectedFirstRepaymentOnDate());
+        }
+
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSED, disbursedOn);
+
+        if (disbursedOn.isAfter(new LocalDate())) {
+            final String errorMessage = "The date on which a loan with identifier : " + this.accountNumber
+                    + " is disbursed cannot be in the future.";
+            throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.a.future.date", errorMessage, disbursedOn);
+        }
+
+    }
+
+    public LoanTransaction handlePayDisbursementTransaction(final Long chargeId, final LoanTransaction chargesPayment,
+            final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+        LoanCharge charge = null;
+        for (final LoanCharge loanCharge : this.charges) {
+            if (loanCharge.isActive() && chargeId.equals(loanCharge.getId())) {
+                charge = loanCharge;
+            }
+        }
+        @SuppressWarnings("null")
+        final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount(), null);
+        chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
+        final Money zero = Money.zero(getCurrency());
+        chargesPayment.updateComponents(zero, zero, charge.getAmount(getCurrency()), zero);
+        chargesPayment.updateLoan(this);
+        this.loanTransactions.add(chargesPayment);
+        updateLoanOutstandingBalaces();
+        charge.markAsFullyPaid();
+        return chargesPayment;
+    }
+
+    public Map<String, Object> undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingTransactionIds,
+            final List<Long> existingReversedTransactionIds, AppUser currentUser) {
+
+        validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO);
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+        final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus);
+        final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, currentStatus);
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO, getDisbursementDate());
+        if (!statusEnum.hasStateOf(currentStatus)) {
+            this.loanStatus = statusEnum.getValue();
+            actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
+
+            final LocalDate actualDisbursementDate = getDisbursementDate();
+            final boolean isScheduleRegenerateRequired = isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate);
+            this.actualDisbursementDate = null;
+            this.disbursedBy = null;
+            boolean isDisbursedAmountChanged = !this.approvedPrincipal.equals(this.loanRepaymentScheduleDetail.getPrincipal());
+            this.loanRepaymentScheduleDetail.setPrincipal(this.approvedPrincipal);
+            if (this.loanProduct.isMultiDisburseLoan()) {
+                for (final LoanDisbursementDetails details : this.disbursementDetails) {
+                    details.updateActualDisbursementDate(null);
+                }
+            }
+            boolean isEmiAmountChanged = this.loanTermVariations.size() > 0;
+            updateLoanToPreDisbursalState();
+            if (isScheduleRegenerateRequired || isDisbursedAmountChanged || isEmiAmountChanged
+                    || this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                // clear off actual disbusrement date so schedule regeneration
+                // uses expected date.
+
+                regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
+                if (isDisbursedAmountChanged) {
+                    updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
+                }
+            }
+
+            actualChanges.put("actualDisbursementDate", "");
+
+            existingTransactionIds.addAll(findExistingTransactionIds());
+            existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+            this.accruedTill = null;
+            reverseExistingTransactions();
+            updateLoanSummaryDerivedFields();
+
+        }
+
+        return actualChanges;
+    }
+
+    private final void reverseExistingTransactions() {
+
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            transaction.reverse();
+        }
+    }
+
+    private void updateLoanToPreDisbursalState() {
+        this.actualDisbursementDate = null;
+
+        for (final LoanCharge charge : charges()) {
+            if (charge.isOverdueInstallmentCharge()) {
+                charge.setActive(false);
+            } else {
+                charge.resetToOriginal(loanCurrency());
+            }
+        }
+
+        for (final LoanRepaymentScheduleInstallment currentInstallment : this.repaymentScheduleInstallments) {
+            currentInstallment.resetDerivedComponents();
+        }
+        final List<LoanTermVariations> removeTerms = new ArrayList<>(this.loanTermVariations.size());
+        for (LoanTermVariations variations : this.loanTermVariations) {
+            if (variations.getOnLoanStatus().equals(LoanStatus.ACTIVE.getValue())) {
+                removeTerms.add(variations);
+            }
+        }
+        this.loanTermVariations.removeAll(removeTerms);
+        final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
+        wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
+
+        updateLoanSummaryDerivedFields();
+    }
+
+    public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
+            final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
+
+        validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER);
+
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
+                waiveInterestTransaction.getTransactionDate());
+        validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, waiveInterestTransaction.getTransactionDate());
+
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+
+        final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction,
+                loanLifecycleStateMachine, null, scheduleGeneratorDTO, currentUser);
+
+        return changedTransactionDetail;
+    }
+
+    @SuppressWarnings("null")
+    public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
+            final List<Long> existingReversedTransactionIds, boolean isRecoveryRepayment, final ScheduleGeneratorDTO scheduleGeneratorDTO,
+            final AppUser currentUser, Boolean isHolidayValidationDone) {
+        HolidayDetailDTO holidayDetailDTO = null;
+        LoanEvent event = null;
+        if (isRecoveryRepayment) {
+            event = LoanEvent.LOAN_RECOVERY_PAYMENT;
+        } else {
+            event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER;
+        }
+        if (!isHolidayValidationDone) {
+            holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
+        }
+        validateAccountStatus(event);
+        validateActivityNotBeforeClientOrGroupTransferDate(event, repaymentTransaction.getTransactionDate());
+        validateActivityNotBeforeLastTransactionDate(event, repaymentTransaction.getTransactionDate());
+        if (!isHolidayValidationDone) {
+            validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
+                    holidayDetailDTO.getHolidays());
+            validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
+                    holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
+        }
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+
+        final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction,
+                loanLifecycleStateMachine, null, scheduleGeneratorDTO, currentUser);
+
+        return changedTransactionDetail;
+    }
+
+    public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine,
+            final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
+            final HolidayDetailDTO holidayDetailDTO, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
+
+        validateAccountStatus(LoanEvent.LOAN_CHARGE_PAYMENT);
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate());
+        validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate());
+        validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
+                holidayDetailDTO.getHolidays());
+        validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
+                holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
+
+        if (paymentTransaction.getTransactionDate().isAfter(new LocalDate())) {
+            final String errorMessage = "The date on which a loan charge paid cannot be in the future.";
+            throw new InvalidLoanStateTransitionException("charge.payment", "cannot.be.a.future.date", errorMessage,
+                    paymentTransaction.getTransactionDate());
+        }
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+        LoanCharge charge = null;
+        for (final LoanCharge loanCharge : this.charges) {
+            if (loanCharge.isActive() && chargeId.equals(loanCharge.getId())) {
+                charge = loanCharge;
+            }
+        }
+        handleChargePaidTransaction(charge, paymentTransaction, loanLifecycleStateMachine, installmentNumber);
+    }
+
+    public void makeRefund(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine,
+            final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
+            final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays,
+            final boolean allowTransactionsOnNonWorkingDay) {
+
+        validateRepaymentDateIsOnHoliday(loanTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
+        validateRepaymentDateIsOnNonWorkingDay(loanTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
+
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+
+        if (status().isOverpaid()) {
+            if (this.totalOverpaid.compareTo(loanTransaction.getAmount(getCurrency()).getAmount()) == -1) {
+                final String errorMessage = "The refund amount must be less than or equal to overpaid amount ";
+                throw new InvalidLoanStateTransitionException("transaction", "is.exceeding.overpaid.amount", errorMessage,
+                        this.totalOverpaid, loanTransaction.getAmount(getCurrency()).getAmount());
+            } else if (!isAfterLatRepayment(loanTransaction, this.loanTransactions)) {
+                final String errorMessage = "Transfer funds is allowed only after last repayment date";
+                throw new InvalidLoanStateTransitionException("transaction", "is.not.after.repayment.date", errorMessage);
+            }
+        } else {
+            final String errorMessage = "Transfer funds is allowed only for loan accounts with overpaid status ";
+            throw new InvalidLoanStateTransitionException("transaction", "is.not.a.overpaid.loan", errorMessage);
+        }
+        loanTransaction.updateLoan(this);
+
+        if (loanTransaction.isNotZero(loanCurrency())) {
+            this.loanTransactions.add(loanTransaction);
+        }
+        updateLoanSummaryDerivedFields();
+        doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
+    }
+
+    private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction,
+            final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
+
+        ChangedTransactionDetail changedTransactionDetail = null;
+
+        LoanStatus statusEnum = null;
+
+        LocalDate recalculateFrom = loanTransaction.getTransactionDate();
+        if (adjustedTransaction != null && adjustedTransaction.getTransactionDate().isBefore(recalculateFrom)) {
+            recalculateFrom = adjustedTransaction.getTransactionDate();
+        }
+
+        if (loanTransaction.isRecoveryRepayment()) {
+            statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RECOVERY_PAYMENT, LoanStatus.fromInt(this.loanStatus));
+        } else {
+            statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, LoanStatus.fromInt(this.loanStatus));
+        }
+
+        this.loanStatus = statusEnum.getValue();
+
+        loanTransaction.updateLoan(this);
+
+        final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction, this.loanTransactions);
+
+        if (loanTransaction.isNotZero(loanCurrency())) {
+            this.loanTransactions.add(loanTransaction);
+        }
+
+        if (loanTransaction.isNotRepayment() && loanTransaction.isNotWaiver() && loanTransaction.isNotRecoveryRepayment()) {
+            final String errorMessage = "A transaction of type repayment or recovery repayment or waiver was expected but not received.";
+            throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.repayment.or.waiver.or.recovery.transaction",
+                    errorMessage);
+        }
+
+        final LocalDate loanTransactionDate = loanTransaction.getTransactionDate();
+        if (loanTransactionDate.isBefore(getDisbursementDate())) {
+            final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
+                    + getApprovedOnDate().toString();
+            throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.disbursement.date", errorMessage,
+                    loanTransactionDate, getDisbursementDate());
+        }
+
+        if (loanTransactionDate.isAfter(DateUtils.getLocalDateOfTenant())) {
+            final String errorMessage = "The transaction date cannot be in the future.";
+            throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, loanTransactionDate);
+        }
+
+        if (loanTransaction.isInterestWaiver()) {
+            Money totalInterestOutstandingOnLoan = getTotalInterestOutstandingOnLoan();
+            if (adjustedTransaction != null) {
+                totalInterestOutstandingOnLoan = totalInterestOutstandingOnLoan.plus(adjustedTransaction.getAmount(loanCurrency()));
+            }
+            if (loanTransaction.getAmount(loanCurrency()).isGreaterThan(totalInterestOutstandingOnLoan)) {
+                final String errorMessage = "The amount of interest to waive cannot be greater than total interest outstanding on loan.";
+                throw new InvalidLoanStateTransitionException("waive.interest", "amount.exceeds.total.outstanding.interest", errorMessage,
+                        loanTransaction.getAmount(loanCurrency()), totalInterestOutstandingOnLoan.getAmount());
+            }
+        }
+
+        if (this.loanProduct.isMultiDisburseLoan() && adjustedTransaction == null) {
+            BigDecimal totalDisbursed = getDisbursedAmount();
+            if (totalDisbursed.compareTo(this.summary.getTotalPrincipalRepaid()) < 0) {
+                final String errorMessage = "The transaction cannot be done before the loan disbursement: "
+                        + getApprovedOnDate().toString();
+                throw new InvalidLoanStateTransitionException("transaction", "cannot.be.done.before.disbursement", errorMessage);
+            }
+        }
+
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+
+        final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment(loanTransaction
+                .getTransactionDate());
+        boolean reprocess = true;
+
+        if (isTransactionChronologicallyLatest && adjustedTransaction == null
+                && loanTransaction.getTransactionDate().isEqual(LocalDate.now()) && currentInstallment != null
+                && currentInstallment.getTotalOutstanding(getCurrency()).isEqualTo(loanTransaction.getAmount(getCurrency()))) {
+            reprocess = false;
+        }
+
+        if (isTransactionChronologicallyLatest && adjustedTransaction == null
+                && (!reprocess || !this.repaymentScheduleDetail().isInterestRecalculationEnabled())) {
+            loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments,
+                    charges());
+            reprocess = false;
+            if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
+                    reprocess = true;
+                } else {
+                    final LoanRepaymentScheduleInstallment nextInstallment = fetchRepaymentScheduleInstallment(currentInstallment
+                            .getInstallmentNumber() + 1);
+                    if (nextInstallment != null && nextInstallment.getTotalPaidInAdvance(getCurrency()).isGreaterThanZero()) {
+                        reprocess = true;
+                    }
+                }
+            }
+        }
+        if (reprocess) {
+            if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
+            }
+            final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+            changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
+                    allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                mapEntry.getValue().updateLoan(this);
+            }
+            /***
+             * Commented since throwing exception if external id present for one
+             * of the transactions. for this need to save the reversed
+             * transactions first and then new transactions.
+             */
+            this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
+        }
+
+        updateLoanSummaryDerivedFields();
+
+        /**
+         * FIXME: Vishwas, skipping post loan transaction checks for Loan
+         * recoveries
+         **/
+        if (loanTransaction.isNotRecoveryRepayment()) {
+            doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
+        }
+
+        if (this.loanProduct.isMultiDisburseLoan()) {
+            BigDecimal totalDisbursed = getDisbursedAmount();
+            if (totalDisbursed.compareTo(this.summary.getTotalPrincipalRepaid()) < 0
+                    && this.repaymentScheduleDetail().getPrincipal().minus(totalDisbursed).isGreaterThanZero()) {
+                final String errorMessage = "The transaction cannot be done before the loan disbursement: "
+                        + getApprovedOnDate().toString();
+                throw new InvalidLoanStateTransitionException("transaction", "cannot.be.done.before.disbursement", errorMessage);
+            }
+        }
+
+        if (changedTransactionDetail != null) {
+            this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values());
+        }
+        return changedTransactionDetail;
+    }
+
+    private LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallment(LocalDate dueDate) {
+        LoanRepaymentScheduleInstallment installment = null;
+        for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
+            if (dueDate.equals(loanRepaymentScheduleInstallment.getDueDate())) {
+                installment = loanRepaymentScheduleInstallment;
+                break;
+            }
+        }
+        return installment;
+    }
+
+    private List<LoanTransaction> retreiveListOfTransactionsPostDisbursement() {
+        final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if (transaction.isNotReversed() && !(transaction.isDisbursement() || transaction.isNonMonetaryTransaction())) {
+                repaymentsOrWaivers.add(transaction);
+            }
+        }
+        final LoanTransactionComparator transactionComparator = new LoanTransactionComparator();
+        Collections.sort(repaymentsOrWaivers, transactionComparator);
+        return repaymentsOrWaivers;
+    }
+
+    public List<LoanTransaction> retreiveListOfTransactionsPostDisbursementExcludeAccruals() {
+        final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if (transaction.isNotReversed()
+                    && !(transaction.isDisbursement() || transaction.isAccrual() || transaction.isRepaymentAtDisbursement() || transaction
+                            .isNonMonetaryTransaction())) {
+                repaymentsOrWaivers.add(transaction);
+            }
+        }
+        final LoanTransactionComparator transactionComparator = new LoanTransactionComparator();
+        Collections.sort(repaymentsOrWaivers, transactionComparator);
+        return repaymentsOrWaivers;
+    }
+
+    private List<LoanTransaction> retreiveListOfTransactionsExcludeAccruals() {
+        final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if (transaction.isNotReversed() && !(transaction.isAccrual() || transaction.isNonMonetaryTransaction())) {
+                repaymentsOrWaivers.add(transaction);
+            }
+        }
+        final LoanTransactionComparator transactionComparator = new LoanTransactionComparator();
+        Collections.sort(repaymentsOrWaivers, transactionComparator);
+        return repaymentsOrWaivers;
+    }
+
+    private List<LoanTransaction> retreiveListOfAccrualTransactions() {
+        final List<LoanTransaction> transactions = new ArrayList<>();
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if (transaction.isNotReversed() && transaction.isAccrual()) {
+                transactions.add(transaction);
+            }
+        }
+        final LoanTransactionComparator transactionComparator = new LoanTransactionComparator();
+        Collections.sort(transactions, transactionComparator);
+        return transactions;
+    }
+
+    private void doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+
+        if (isOverPaid()) {
+            // FIXME - kw - update account balance to negative amount.
+            handleLoanOverpayment(loanLifecycleStateMachine);
+        } else if (this.summary.isRepaidInFull(loanCurrency())) {
+            handleLoanRepaymentInFull(transactionDate, loanLifecycleStateMachine);
+        }
+    }
+
+    private void handleLoanRepaymentInFull(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+
+        boolean isAllChargesPaid = true;
+        for (final LoanCharge loanCharge : this.charges) {
+            if (loanCharge.isActive() && !(loanCharge.isPaid() || loanCharge.isWaived())) {
+                isAllChargesPaid = false;
+                break;
+            }
+        }
+        if (isAllChargesPaid) {
+            final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL,
+                    LoanStatus.fromInt(this.loanStatus));
+            this.loanStatus = statusEnum.getValue();
+
+            this.closedOnDate = transactionDate.toDate();
+            this.actualMaturityDate = transactionDate.toDate();
+        } else if (LoanStatus.fromInt(this.loanStatus).isOverpaid()) {
+            final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
+                    LoanStatus.fromInt(this.loanStatus));
+            this.loanStatus = statusEnum.getValue();
+        }
+    }
+
+    private void handleLoanOverpayment(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+
+        final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, LoanStatus.fromInt(this.loanStatus));
+        this.loanStatus = statusEnum.getValue();
+
+        this.closedOnDate = null;
+        this.actualMaturityDate = null;
+    }
+
+    private boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction,
+            final List<LoanTransaction> loanTransactions) {
+
+        boolean isChronologicallyLatestRepaymentOrWaiver = true;
+
+        final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
+        for (final LoanTransaction previousTransaction : loanTransactions) {
+            if (!previousTransaction.isDisbursement() && previousTransaction.isNotReversed()) {
+                if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())
+                        || currentTransactionDate.isEqual(previousTransaction.getTransactionDate())) {
+                    isChronologicallyLatestRepaymentOrWaiver = false;
+                    break;
+                }
+            }
+        }
+
+        return isChronologicallyLatestRepaymentOrWaiver;
+    }
+
+    private boolean isAfterLatRepayment(final LoanTransaction loanTransaction, final List<LoanTransaction> loanTransactions) {
+
+        boolean isAfterLatRepayment = true;
+
+        final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
+        for (final LoanTransaction previousTransaction : loanTransactions) {
+            if (previousTransaction.isRepayment() && previousTransaction.isNotReversed()) {
+                if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
+                    isAfterLatRepayment = false;
+                    break;
+                }
+            }
+        }
+        return isAfterLatRepayment;
+    }
+
+    private boolean isChronologicallyLatestTransaction(final LoanTransaction loanTransaction, final List<LoanTransaction> loanTransactions) {
+
+        boolean isChronologicallyLatestRepaymentOrWaiver = true;
+
+        final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
+        for (final LoanTransaction previousTransaction : loanTransactions) {
+            if (previousTransaction.isNotReversed()) {
+                if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())
+                        || currentTransactionDate.isEqual(previousTransaction.getTransactionDate())) {
+                    isChronologicallyLatestRepaymentOrWaiver = false;
+                    break;
+                }
+            }
+        }
+
+        return isChronologicallyLatestRepaymentOrWaiver;
+    }
+
+    public LocalDate possibleNextRepaymentDate() {
+        LocalDate earliestUnpaidInstallmentDate = new LocalDate();
+        for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+            if (installment.isNotFullyPaidOff()) {
+                earliestUnpaidInstallmentDate = installment.getDueDate();
+                break;
+            }
+        }
+
+        LocalDate lastTransactionDate = null;
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if (transaction.isRepayment() && transaction.isNonZero()) {
+                lastTransactionDate = transaction.getTransactionDate();
+            }
+        }
+
+        LocalDate possibleNextRepaymentDate = earliestUnpaidInstallmentDate;
+        if (lastTransactionDate != null && lastTransactionDate.isAfter(earliestUnpaidInstallmentDate)) {
+            possibleNextRepaymentDate = lastTransactionDate;
+        }
+
+        return possibleNextRepaymentDate;
+    }
+
+    public LoanRepaymentScheduleInstallment possibleNextRepaymentInstallment() {
+        LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = null;
+
+        for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+            if (installment.isNotFullyPaidOff()) {
+                loanRepaymentScheduleInstallment = installment;
+                break;
+            }
+        }
+
+        return loanRepaymentScheduleInstallment;
+    }
+
+    public LoanTransaction deriveDefaultInterestWaiverTransaction(final LocalDateTime createdDate, final AppUser currentUser) {
+
+        final Money totalInterestOutstanding = getTotalInterestOutstandingOnLoan();
+        Money possibleInterestToWaive = totalInterestOutstanding.copy();
+        LocalDate transactionDate = new LocalDate();
+
+        if (totalInterestOutstanding.isGreaterThanZero()) {
+            // find earliest known instance of overdue interest and default to
+            // that
+            for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
+
+                final Money outstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency());
+                if (scheduledRepayment.isOverdueOn(new LocalDate()) && scheduledRepayment.isNotFullyPaidOff()
+                        && outstandingForPeriod.isGreaterThanZero()) {
+                    transactionDate = scheduledRepayment.getDueDate();
+                    possibleInterestToWaive = outstandingForPeriod;
+                    break;
+                }
+            }
+        }
+
+        return LoanTransaction.waiver(getOffice(), this, possibleInterestToWaive, transactionDate, possibleInterestToWaive,
+                possibleInterestToWaive.zero(), createdDate, currentUser);
+    }
+
+    public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment,
+            final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
+            final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
+
+        HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
+        validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, transactionForAdjustment.getTransactionDate());
+        validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
+                holidayDetailDTO.getHolidays());
+        validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
+                holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
+
+        ChangedTransactionDetail changedTransactionDetail = null;
+
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
+                transactionForAdjustment.getTransactionDate());
+
+        if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()) {
+            final String errorMessage = "Only transactions of type repayment or waiver can be adjusted.";
+            throw new InvalidLoanTransactionTypeException("transaction", "adjustment.is.only.allowed.to.repayment.or.waiver.transaction",
+                    errorMessage);
+        }
+
+        transactionForAdjustment.reverse();
+        transactionForAdjustment.manuallyAdjustedOrReversed();
+
+        if (isClosedWrittenOff()) {
+            // find write off transaction and reverse it
+            final LoanTransaction writeOffTransaction = findWriteOffTransaction();
+            writeOffTransaction.reverse();
+        }
+
+        if (isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutsandingAmountMarkedForReschedule()) {
+            this.loanStatus = LoanStatus.ACTIVE.getValue();
+        }
+
+        if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
+            changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
+                    transactionForAdjustment, scheduleGeneratorDTO, currentUser);
+        }
+
+        return changedTransactionDetail;
+    }
+
+    public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds,
+            final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
+
+        validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO);
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+        final LoanTransaction writeOffTransaction = findWriteOffTransaction();
+        writeOffTransaction.reverse();
+        this.loanStatus = LoanStatus.ACTIVE.getValue();
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+        if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
+        }
+        ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
+                getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
+                charges());
+        updateLoanSummaryDerivedFields();
+        return changedTransactionDetail;
+    }
+
+    public LoanTransaction findWriteOffTransaction() {
+
+        LoanTransaction writeOff = null;
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if (!transaction.isReversed() && transaction.isWriteOff()) {
+                writeOff = transaction;
+            }
+        }
+
+        return writeOff;
+    }
+
+    private boolean isOverPaid() {
+        return calculateTotalOverpayment().isGreaterThanZero();
+    }
+
+    private Money calculateTotalOverpayment() {
+
+        Money totalPaidInRepayments = getTotalPaidInRepayments();
+
+        final MonetaryCurrency currency = loanCurrency();
+        Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
+        Money cumulativeTotalWaivedOnInstallments = Money.zero(currency);
+
+        for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
+
+            cumulativeTotalPaidOnInstallments = cumulativeTotalPaidOnInstallments
+                    .plus(scheduledRepayment.getPrincipalCompleted(currency).plus(scheduledRepayment.getInterestPaid(currency)))
+                    .plus(scheduledRepayment.getFeeChargesPaid(currency)).plus(scheduledRepayment.getPenaltyChargesPaid(currency));
+
+            cumulativeTotalWaivedOnInstallments = cumulativeTotalWaivedOnInstallments.plus(scheduledRepayment.getInterestWaived(currency));
+        }
+
+        for (final LoanTransaction loanTransaction : this.loanTransactions) {
+            if ((loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) && !loanTransaction.isReversed()) {
+                totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
+            }
+        }
+
+        // if total paid in transactions doesnt match repayment schedule then
+        // theres an overpayment.
+        return totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments);
+    }
+
+    public Money calculateTotalRecoveredPayments() {
+        Money totalRecoveredPayments = getTotalRecoveredPayments();
+        // in case logic for reversing recovered payment is implemented handle
+        // subtraction from totalRecoveredPayments
+        return totalRecoveredPayments;
+    }
+
+    private MonetaryCurrency loanCurrency() {
+        return this.loanRepaymentScheduleDetail.getCurrency();
+    }
+
+    public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
+            final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
+            final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
+
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO,
+                loanRepaymentScheduleTransactionProcessor, currentUser);
+
+        validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING);
+
+        final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING,
+                LoanStatus.fromInt(this.loanStatus));
+
+        LoanTransaction loanTransaction = null;
+        if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
+            this.loanStatus = statusEnum.getValue();
+            changes.put("status", LoanEnumerations.status(this.loanStatus));
+
+            existingTransactionIds.addAll(findExistingTransactionIds());
+            existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+
+            final LocalDate writtenOffOnLocalDate = command.localDateValueOfParameterNamed("transactionDate");
+            final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
+
+            this.closedOnDate = writtenOffOnLocalDate.toDate();
+            this.writtenOffOnDate = writtenOffOnLocalDate.toDate();
+            this.closedBy = currentUser;
+            changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate"));
+            changes.put("writtenOffOnDate", command.stringValueOfParameterNamed("transactionDate"));
+
+            if (writtenOffOnLocalDate.isBefore(getDisbursementDate())) {
+                final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: "
+                        + getDisbursementDate().toString();
+                throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.before.submittal.date", errorMessage,
+                        writtenOffOnLocalDate, getDisbursementDate());
+            }
+
+            validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.WRITE_OFF_OUTSTANDING, writtenOffOnLocalDate);
+
+            if (writtenOffOnLocalDate.isAfter(DateUtils.getLocalDateOfTenant())) {
+                final String errorMessage = "The date on which a loan is written off cannot be in the future.";
+                throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate);
+            }
+
+            LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
+            loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId, createdDate, currentUser);
+            LocalDate lastTransactionDate = getLastUserTransactionDate();
+            if (lastTransactionDate.isAfter(writtenOffOnLocalDate)) {
+                final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions.";
+                throw new InvalidLoanStateTransitionException("writeoff", "must.occur.on.or.after.other.transaction.dates", errorMessage,
+                        writtenOffOnLocalDate);
+            }
+
+            this.loanTransactions.add(loanTransaction);
+
+            loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), this.repaymentScheduleInstallments);
+
+            updateLoanSummaryDerivedFields();
+        }
+        if (changedTransactionDetail == null) {
+            changedTransactionDetail = new ChangedTransactionDetail();
+        }
+        changedTransactionDetail.getNewTransactionMappings().put(0L, loanTransaction);
+        return changedTransactionDetail;
+    }
+
+    private ChangedTransactionDetail closeDisbursements(final ScheduleGeneratorDTO scheduleGeneratorDTO,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final AppUser currentUser) {
+        ChangedTransactionDetail changedTransactionDetail = null;
+        if (isDisbursementAllowed() && atleastOnceDisbursed()) {
+            this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount());
+            removeDisbursementDetail();
+            regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
+            if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
+            }
+            final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+            changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
+                    allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                mapEntry.getValue().updateLoan(this);
+                this.loanTransactions.add(mapEntry.getValue());
+            }
+            updateLoanSummaryDerivedFields();
+            LoanTransaction loanTransaction = findlatestTransaction();
+            doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
+        }
+        return changedTransactionDetail;
+    }
+
+    private LoanTransaction findlatestTransaction() {
+        LoanTransaction transaction = null;
+        for (LoanTransaction loanTransaction : this.loanTransactions) {
+            if (!loanTransaction.isReversed()
+                    && (transaction == null || transaction.getTransactionDate().isBefore(loanTransaction.getTransactionDate()))) {
+                transaction = loanTransaction;
+            }
+        }
+        return transaction;
+    }
+
+    public ChangedTransactionDetail close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
+            final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
+            final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
+
+        validateAccountStatus(LoanEvent.LOAN_CLOSED);
+
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+
+        final LocalDate closureDate = command.localDateValueOfParameterNamed("transactionDate");
+        final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
+
+        this.closedOnDate = closureDate.toDate();
+        changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate"));
+
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.REPAID_IN_FULL, closureDate);
+        if (closureDate.isBefore(getDisbursementDate())) {
+            final String errorMessage = "The date on which a loan is closed cannot be before the loan disbursement date: "
+                    + getDisbursementDate().toString();
+            throw new InvalidLoanStateTransitionException("close", "cannot.be.before.submittal.date", errorMessage, closureDate,
+                    getDisbursementDate());
+        }
+
+        if (closureDate.isAfter(DateUtils.getLocalDateOfTenant())) {
+            final String errorMessage = "The date on which a loan is closed cannot be in the future.";
+            throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate);
+        }
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO,
+                loanRepaymentScheduleTransactionProcessor, currentUser);
+
+        LoanTransaction loanTransaction = null;
+        if (isOpen()) {
+            final Money totalOutstanding = this.summary.getTotalOutstanding(loanCurrency());
+            if (totalOutstanding.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalOutstanding)) {
+
+                final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL,
+                        LoanStatus.fromInt(this.loanStatus));
+                if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
+                    this.loanStatus = statusEnum.getValue();
+                    changes.put("status", LoanEnumerations.status(this.loanStatus));
+                }
+                this.closedOnDate = closureDate.toDate();
+                loanTransaction = LoanTransaction.writeoff(this, getOffice(), closureDate, txnExternalId,
+                        DateUtils.getLocalDateTimeOfTenant(), currentUser);
+                final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, this.loanTransactions);
+                if (!isLastTransaction) {
+                    final String errorMessage = "The closing date of the loan must be on or after latest transaction date.";
+                    throw new InvalidLoanStateTransitionException("close.loan", "must.occur.on.or.after.latest.transaction.date",
+                            errorMessage, closureDate);
+                }
+
+                this.loanTransactions.add(loanTransaction);
+
+                loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(),
+                        this.repaymentScheduleInstallments);
+
+                updateLoanSummaryDerivedFields();
+            } else if (totalOutstanding.isGreaterThanZero()) {
+                final String errorMessage = "A loan with money outstanding cannot be closed";
+                throw new InvalidLoanStateTransitionException("close", "loan.has.money.outstanding", errorMessage,
+                        totalOutstanding.toString());
+            }
+        }
+
+        if (isOverPaid()) {
+            final Money totalLoanOverpayment = calculateTotalOverpayment();
+            if (totalLoanOverpayment.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
+                // TODO - KW - technically should set somewhere that this loan
+                // has
+                // 'overpaid' amount
+                final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL,
+                        LoanStatus.fromInt(this.loanStatus));
+                if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
+                    this.loanStatus = statusEnum.getValue();
+                    changes.put("status", LoanEnumerations.status(this.loanStatus));
+                }
+                this.closedOnDate = closureDate.toDate();
+            } else if (totalLoanOverpayment.isGreaterThanZero()) {
+                final String errorMessage = "The loan is marked as 'Overpaid' and cannot be moved to 'Closed (obligations met).";
+                throw new InvalidLoanStateTransitionException("close", "loan.is.overpaid", errorMessage, totalLoanOverpayment.toString());
+            }
+        }
+
+        if (changedTransactionDetail == null) {
+            changedTransactionDetail = new ChangedTransactionDetail();
+        }
+        changedTransactionDetail.getNewTransactionMappings().put(0L, loanTransaction);
+        return changedTransactionDetail;
+    }
+
+    /**
+     * Behaviour added to comply with capability of previous mifos product to
+     * support easier transition to fineract platform.
+     */
+    public void closeAsMarkedForReschedule(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
+            final Map<String, Object> changes) {
+
+        final LocalDate rescheduledOn = command.localDateValueOfParameterNamed("transactionDate");
+
+        final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, LoanStatus.fromInt(this.loanStatus));
+        if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
+            this.loanStatus = statusEnum.getValue();
+            changes.put("status", LoanEnumerations.status(this.loanStatus));
+        }
+
+        this.closedOnDate = rescheduledOn.toDate();
+        this.rescheduledOnDate = rescheduledOn.toDate();
+        changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate"));
+        changes.put("rescheduledOnDate", command.stringValueOfParameterNamed("transactionDate"));
+
+        final LocalDate rescheduledOnLocalDate = new LocalDate(this.rescheduledOnDate);
+        if (rescheduledOnLocalDate.isBefore(getDisbursementDate())) {
+            final String errorMessage = "The date on which a loan is rescheduled cannot be before the loan disbursement date: "
+                    + getDisbursementDate().toString();
+            throw new InvalidLoanStateTransitionException("close.reschedule", "cannot.be.before.submittal.date", errorMessage,
+                    rescheduledOnLocalDate, getDisbursementDate());
+        }
+
+        if (rescheduledOnLocalDate.isAfter(new LocalDate())) {
+            final String errorMessage = "The date on which a loan is rescheduled cannot be in the future.";
+            throw new InvalidLoanStateTransitionException("close.reschedule", "cannot.be.a.future.date", errorMessage,
+                    rescheduledOnLocalDate);
+        }
+    }
+
+    public boolean isNotSubmittedAndPendingApproval() {
+        return !isSubmittedAndPendingApproval();
+    }
+
+    public LoanStatus status() {
+        return LoanStatus.fromInt(this.loanStatus);
+    }
+
+    public boolean isSubmittedAndPendingApproval() {
+        return status().isSubmittedAndPendingApproval();
+    }
+
+    public boolean isApproved() {
+        return status().isApproved();
+    }
+
+    private boolean isNotDisbursed() {
+        return !isDisbursed();
+    }
+
+    public boolean isChargesAdditionAllowed() {
+        boolean isDisbursed = false;
+        if (this.loanProduct.isMultiDisburseLoan()) {
+            isDisbursed = !isDisbursementAllowed();
+        } else {
+            isDisbursed = hasDisbursementTransaction();
+        }
+        return isDisbursed;
+    }
+
+    public boolean isDisbursed() {
+        return hasDisbursementTransaction();
+    }
+
+    public boolean isClosed() {
+        return status().isClosed() || isCancelled();
+    }
+
+    private boolean isClosedObligationsMet() {
+        return status().isClosedObligationsMet();
+    }
+
+    public boolean isClosedWrittenOff() {
+        return status().isClosedWrittenOff();
+    }
+
+    private boolean isClosedWithOutsandingAmountMarkedForReschedule() {
+        return status().isClosedWithOutsandingAmountMarkedForReschedule();
+    }
+
+    private boolean isCancelled() {
+        return isRejected() || isWithdrawn();
+    }
+
+    private boolean isWithdrawn() {
+        return status().isWithdrawnByClient();
+    }
+
+    private boolean isRejected() {
+        return status().isRejected();
+    }
+
+    public boolean isOpen() {
+        return status().isActive();
+    }
+
+    private boolean isAllTranchesNotDisbursed() {
+        return this.loanProduct.isMultiDisburseLoan()
+                && (LoanStatus.fromInt(this.loanStatus).isActive() || LoanStatus.fromInt(this.loanStatus).isApproved())
+                && isDisbursementAllowed();
+    }
+
+    private boolean hasDisbursementTransaction() {
+        boolean hasRepaymentTransaction = false;
+        for (final LoanTransaction loanTransaction : this.loanTransactions) {
+            if (loanTransaction.isDisbursement() && loanTransaction.isNotReversed()) {
+                hasRepaymentTransaction = true;
+                break;
+            }
+        }
+        return hasRepaymentTransaction;
+    }
+
+    public boolean isSubmittedOnDateAfter(final LocalDate compareDate) {
+        return this.submittedOnDate == null ? false : new LocalDate(this.submittedOnDate).isAfter(compareDate);
+    }
+
+    public LocalDate getSubmittedOnDate() {
+        return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.submittedOnDate), null);
+    }
+
+    public LocalDate getApprovedOnDate() {
+        LocalDate date = null;
+        if (this.approvedOnDate != null) {
+            date = new LocalDate(this.approvedOnDate);
+        }
+        return date;
+    }
+
+    public LocalDate getExpectedDisbursedOnLocalDate() {
+        LocalDate expectedDisbursementDate = null;
+        if (this.expectedDisbursementDate != null) {
+            expectedDisbursementDate = new LocalDate(this.expectedDisbursementDate);
+        }
+        return expectedDisbursementDate;
+    }
+
+    public LocalDate getDisbursementDate() {
+        LocalDate disbursementDate = getExpectedDisbursedOnLocalDate();
+        if (this.actualDisbursementDate != null) {
+            disbursementDate = new LocalDate(this.actualDisbursementDate);
+        }
+        return disbursementDate;
+    }
+
+    public LocalDate getWrittenOffDate() {
+        LocalDate writtenOffDate = null;
+        if (this.writtenOffOnDate != null) {
+            writtenOffDate = new LocalDate(this.writtenOffOnDate);
+        }
+        return writtenOffDate;
+    }
+
+    public LocalDate getExpectedDisbursedOnLocalDateForTemplate() {
+
+        LocalDate expectedDisbursementDate = null;
+        if (this.expectedDisbursementDate != null) {
+            expectedDisbursementDate = new LocalDate(this.expectedDisbursementDate);
+        }
+
+        Collection<LoanDisbursementDetails> details = fetchUndisbursedDetail();
+        if (!details.isEmpty()) {
+            for (LoanDisbursementDetails disbursementDetails : details) {
+                expectedDisbursementDate = new LocalDate(disbursementDetails.expectedDisbursementDate());
+            }
+        }
+        return expectedDisbursementDate;
+    }
+
+    /*
+     * Reason for derving
+     */
+
+    public BigDecimal getDisburseAmountForTemplate() {
+        BigDecimal principal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
+        Collection<LoanDisbursementDetails> details = fetchUndisbursedDetail();
+        if (!details.isEmpty()) {
+            principal = BigDecimal.ZERO;
+            for (LoanDisbursementDetails disbursementDetails : details) {
+                principal = principal.add(disbursementDetails.principal());
+            }
+        }
+        return principal;
+    }
+
+    public LocalDate getExpectedFirstRepaymentOnDate() {
+        LocalDate firstRepaymentDate = null;
+        if (this.expectedFirstRepaymentOnDate != null) {
+            firstRepaymentDate = new LocalDate(this.expectedFirstRepaymentOnDate);
+        }
+        return firstRepaymentDate;
+    }
+
+    private void addRepaymentScheduleInstallment(final LoanRepaymentScheduleInstallment installment) {
+        installment.updateLoan(this);
+        this.repaymentScheduleInstallments.add(installment);
+    }
+
+    private boolean isActualDisbursedOnDateEarlierOrLaterThanExpected(final LocalDate actualDisbursedOnDate) {
+        boolean isRegenerationRequired = false;
+        if (this.loanProduct.isMultiDisburseLoan()) {
+            LoanDisbursementDetails details = fetchLastDisburseDetail();
+            if (details != null && !(details.expectedDisbursementDate().equals(details.actualDisbursementDate()))) {
+                isRegenerationRequired = true;
+            }
+        }
+        return !new LocalDate(this.expectedDisbursementDate).isEqual(actualDisbursedOnDate) || isRegenerationRequired;
+    }
+
+    private boolean isRepaymentScheduleRegenerationRequiredForDisbursement(final LocalDate actualDisbursementDate) {
+        return isActualDisbursedOnDateEarlierOrLaterThanExpected(actualDisbursementDate);
+    }
+
+    private Money getTotalPaidInRepayments() {
+        Money cumulativePaid = Money.zero(loanCurrency());
+
+        for (final LoanTransaction repayment : this.loanTransactions) {
+            if (repayment.isRepayment() && !repayment.isReversed()) {
+                cumulativePaid = cumulativePaid.plus(repayment.getAmount(loanCurrency()));
+            }
+        }
+
+        return cumulativePaid;
+    }
+
+    public Money getTotalRecoveredPayments() {
+        Money cumulativePaid = Money.zero(loanCurrency());
+
+        for (final LoanTransaction recoveredPayment : this.loanTransactions) {
+            if (recoveredPayment.isRecoveryRepayment()) {
+                cumulativePaid = cumulativePaid.plus(recoveredPayment.getAmount(loanCurrency()));
+            }
+        }
+        return cumulativePaid;
+    }
+
+    private Money getTotalInterestOutstandingOnLoan() {
+        Money cumulativeInterest = Money.zero(loanCurrency());
+
+        for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
+            cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency()));
+        }
+
+        return cumulativeInterest;
+    }
+
+    @SuppressWarnings("unused")
+    private Money getTotalInterestOverdueOnLoan() {
+        Money cumulativeInterestOverdue = Money.zero(this.loanRepaymentScheduleDetail.getPrincipal().getCurrency());
+
+        for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
+
+            final Money interestOutstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency());
+            if (scheduledRepayment.isOverdueOn(new LocalDate())) {
+                cumulativeInterestOverdue = cumulativeInterestOverdue.plus(interestOutstandingForPeriod);
+            }
+        }
+
+        return cumulativeInterestOverdue;
+    }
+
+    private Money getInArrearsTolerance() {
+        return this.loanRepaymentScheduleDetail.getInArrearsTolerance();
+    }
+
+    public boolean hasIdentifyOf(final Long loanId) {
+        return loanId.equals(getId());
+    }
+
+    public boolean hasLoanOfficer(final Staff fromLoanOfficer) {
+
+        boolean matchesCurrentLoanOfficer = false;
+        if (this.loanOfficer != null) {
+            matchesCurrentLoanOfficer = this.loanOfficer.identifiedBy(fromLoanOfficer);
+        } else {
+            matchesCurrentLoanOfficer = fromLoanOfficer == null;
+        }
+
+        return matchesCurrentLoanOfficer;
+    }
+
+    public LocalDate getInterestChargedFromDate() {
+        LocalDate interestChargedFrom = null;
+        if (this.interestChargedFromDate != null) {
+            interestChargedFrom = new LocalDate(this.interestChargedFromDate);
+        }
+        return interestChargedFrom;
+    }
+
+    public Money getPrincpal() {
+        return this.loanRepaymentScheduleDetail.getPrincipal();
+    }
+
+    public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) {
+        return getCurrencyCode().equalsIgnoreCase(matchingCurrencyCode);
+    }
+
+    public String getCurrencyCode() {
+        return this.loanRepaymentScheduleDetail.getPrincipal().getCurrencyCode();
+    }
+
+    public MonetaryCurrency getCurrency() {
+        return this.loanRepaymentScheduleDetail.getCurrency();
+    }
+
+    public void reassignLoanOfficer(final Staff newLoanOfficer, final LocalDate assignmentDate) {
+
+        final LoanOfficerAssignmentHistory latestHistoryRecord = findLatestIncompleteHistoryRecord();
+        final LoanOfficerAssignmentHistory lastAssignmentRecord = findLastAssignmentHistoryRecord(newLoanOfficer);
+
+        // assignment date should not be less than loan submitted date
+        if (isSubmittedOnDateAfter(assignmentDate)) {
+
+            final String errorMessage = "The Loan Officer assignment date (" + assignmentDate.toString()
+                    + ") cannot be before loan submitted date (" + getSubmittedOnDate().toString() + ").";
+
+            throw new LoanOfficerAssignmentDateException("cannot.be.before.loan.submittal.date", errorMessage, assignmentDate,
+                    getSubmittedOnDate());
+
+        } else if (lastAssignmentRecord != null && lastAssignmentRecord.isEndDateAfter(assignmentDate)) {
+
+            final String errorMessage = "The Loan Officer assignment date (" + assignmentDate
+                    + ") cannot be before previous Loan Officer unassigned date (" + lastAssignmentRecord.getEndDate() + ").";
+
+            throw new LoanOfficerAssignmentDateException("cannot.be.before.previous.unassignement.date", errorMessage, assignmentDate,
+                    lastAssignmentRecord.getEndDate());
+
+        } else if (DateUtils.getLocalDateOfTenant().isBefore(assignmentDate)) {
+
+            final String errorMessage = "The Loan Officer assignment date (" + assignmentDate + ") cannot be in the future.";
+
+            throw new LoanOfficerAssignmentDateException("cannot.be.a.future.date", errorMessage, assignmentDate);
+
+        } else if (latestHistoryRecord != null && this.loanOfficer.identifiedBy(newLoanOfficer)) {
+            latestHistoryRecord.updateStartDate(assignmentDate);
+        } else if (latestHistoryRecord != null && latestHistoryRecord.matchesStartDateOf(assignmentDate)) {
+            latestHistoryRecord.updateLoanOfficer(newLoanOfficer);
+            this.loanOfficer = newLoanOfficer;
+        } else if (latestHistoryRecord != null && latestHistoryRecord.hasStartDateBefore(assignmentDate)) {
+            final String errorMessage = "Loan with identifier " + getId() + " was already assigned before date " + assignmentDate;
+            throw new LoanOfficerAssignmentDateException("is.before.last.assignment.date", errorMessage, getId(), assignmentDate);
+        } else {
+            if (latestHistoryRecord != null) {
+                // loan officer correctly changed from previous loan officer to
+                // new loan officer
+                latestHistoryRecord.updateEndDate(assignmentDate);
+            }
+
+            this.loanOfficer = newLoanOfficer;
+            if (isNotSubmittedAndPendingApproval()) {
+                final LoanOfficerAssignmentHistory loanOfficerAssignmentHistory = LoanOfficerAssignmentHistory.createNew(this,
+                        this.loanOfficer, assignmentDate);
+                this.loanOfficerHistory.add(loanOfficerAssignmentHistory);
+            }
+        }
+    }
+
+    public void removeLoanOfficer(final LocalDate unassignDate) {
+
+        final LoanOfficerAssignmentHistory latestHistoryRecord = findLatestIncompleteHistoryRecord();
+
+        if (latestHistoryRecord != null) {
+            validateUnassignDate(latestHistoryRecord, unassignDate);
+            latestHistoryRecord.updateEndDate(unassignDate);
+        }
+
+        this.loanOfficer = null;
+    }
+
+    private void validateUnassignDate(final LoanOfficerAssignmentHistory latestHistoryRecord, final LocalDate unassignDate) {
+
+        final LocalDate today = DateUtils.getLocalDateOfTenant();
+
+        if (latestHistoryRecord.getStartDate().isAfter(unassignDate)) {
+
+            final String errorMessage = "The Loan officer Unassign date(" + unassignDate + ") cannot be before its assignment date ("
+                    + latestHistoryRecord.getStartDate() + ").";
+
+            throw new LoanOfficerUnassignmentDateException("cannot.be.before.assignment.date", errorMessage, getId(), getLoanOfficer()
+                    .getId(), latestHistoryRecord.getStartDate(), unassignDate);
+
+        } else if (unassignDate.isAfter(today)) {
+
+            final String errorMessage = "The Loan Officer Unassign date (" + unassignDate + ") cannot be in the future.";
+
+            throw new LoanOfficerUnassignmentDateException("cannot.be.a.future.date", errorMessage, unassignDate);
+        }
+    }
+
+    private LoanOfficerAssignmentHistory findLatestIncompleteHistoryRecord() {
+
+        LoanOfficerAssignmentHistory latestRecordWithNoEndDate = null;
+        for (final LoanOfficerAssignmentHistory historyRecord : this.loanOfficerHistory) {
+            if (historyRecord.isCurrentRecord()) {
+                latestRecordWithNoEndDate = historyRecord;
+                break;
+            }
+        }
+        return latestRecordWithNoEndDate;
+    }
+
+    private LoanOfficerAssignmentHistory findLastAssignmentHistoryRecord(final Staff newLoanOfficer) {
+
+        LoanOfficerAssignmentHistory lastAssignmentRecordLatestEndDate = null;
+        for (final LoanOfficerAssignmentHistory historyRecord : this.loanOfficerHistory) {
+
+            if (historyRecord.isCurrentRecord() && !historyRecord.isSameLoanOfficer(newLoanOfficer)) {
+                lastAssignmentRecordLatestEndDate = historyRecord;
+                break;
+            }
+
+            if (lastAssignmentRecordLatestEndDate == null) {
+                lastAssignmentRecordLatestEndDate = historyRecord;
+            } else if (historyRecord.isEndDateAfter(lastAssignmentRecordLatestEndDate.getEndDate())
+                    && !historyRecord.isSameLoanOfficer(newLoanOfficer)) {
+                lastAssignmentRecordLatestEndDate = historyRecord;
+            }
+        }
+        return lastAssignmentRecordLatestEndDate;
+    }
+
+    public Long getClientId() {
+        Long clientId = null;
+        if (this.client != null) {
+            clientId = this.client.getId();
+        }
+        return clientId;
+    }
+
+    public Long getGroupId() {
+        Long groupId = null;
+        if (this.group != null) {
+            groupId = this.group.getId();
+        }
+        return groupId;
+    }
+
+    public Long getOfficeId() {
+        Long officeId = null;
+        if (this.client != null) {
+            officeId = this.client.officeId();
+        } else {
+            officeId = this.group.officeId();
+        }
+        return officeId;
+    }
+
+    public Office getOffice() {
+        Office office = null;
+        if (this.client != null) {
+            office = this.client.getOffice();
+        } else {
+            office = this.group.getOffice();
+        }
+        return office;
+    }
+
+    private Boolean isCashBasedAccountingEnabledOnLoanProduct() {
+        return this.loanProduct.isCashBasedAccountingEnabled();
+    }
+
+    public Boolean isUpfrontAccrualAccountingEnabledOnLoanProduct() {
+        return this.loanProduct.isUpfrontAccrualAccountingEnabled();
+    }
+
+    public Boolean isAccountingDisabledOnLoanProduct() {
+        return this.loanProduct.isAccountingDisabled();
+    }
+
+    public Boolean isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct() {
+        return isCashBasedAccountingEnabledOnLoanProduct() || isUpfrontAccrualAccountingEnabledOnLoanProduct()
+                || isAccountingDisabledOnLoanProduct();
+    }
+
+    public Boolean isPeriodicAccrualAccountingEnabledOnLoanProduct() {
+        return this.loanProduct.isPeriodicAccrualAccountingEnabled();
+    }
+
+    public Long productId() {
+        return this.loanProduct.getId();
+    }
+
+    public Staff getLoanOfficer() {
+        return this.loanOfficer;
+    }
+
+    public LoanSummary getSummary() {
+        return this.summary;
+    }
+
+    public Set<LoanCollateral> getCollateral() {
+        return this.collateral;
+    }
+
+    public BigDecimal getProposedPrincipal() {
+        return this.proposedPrincipal;
+    }
+
+    public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final List<Long> existingTransactionIds,
+            final List<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
+
+        final Map<String, Object> accountingBridgeData = new LinkedHashMap<>();
+        accountingBridgeData.put("loanId", getId());
+        accountingBridgeData.put("loanProductId", productId());
+        accountingBridgeData.put("officeId", getOfficeId());
+        accountingBridgeData.put("currency", currencyData);
+        accountingBridgeData.put("calculatedInterest", this.summary.getTotalInterestCharged());
+        accountingBridgeData.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnLoanProduct());
+        accountingBridgeData.put("upfrontAccrualBasedAccountingEnabled", isUpfrontAccrualAccountingEnabledOnLoanProduct());
+        accountingBridgeData.put("periodicAccrualBasedAccountingEnabled", isPeriodicAccrualAccountingEnabledOnLoanProduct());
+        accountingBridgeData.put("isAccountTransfer", isAccountTransfer);
+
+        final List<Map<String, Object>> newLoanTransactions = new ArrayList<>();
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if (transaction.isReversed() && existingTransactionIds.contains(transaction.getId())
+                    && !existingReversedTransactionIds.contains(transaction.getId())) {
+                newLoanTransactions.add(transaction.toMapData(currencyData));
+            } else if (!existingTransactionIds.contains(transaction.getId())) {
+                newLoanTransactions.add(transaction.toMapData(currencyData));
+            }
+        }
+
+        accountingBridgeData.put("newLoanTransactions", newLoanTransactions);
+        return accountingBridgeData;
+    }
+
+    public Money getReceivableInterest(final LocalDate tillDate) {
+        Money receivableInterest = Money.zero(getCurrency());
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if (transaction.isNotReversed() && !transaction.isRepaymentAtDisbursement() && !transaction.isDisbursement()
+                    && !transaction.getTransactionDate().isAfter(tillDate)) {
+                if (transaction.isAccrual()) {
+                    receivableInterest = receivableInterest.plus(transaction.getInterestPortion(getCurrency()));
+                } else if (transaction.isRepayment() || transaction.isInterestWaiver()) {
+                    receivableInterest = receivableInterest.minus(transaction.getInterestPortion(getCurrency()));
+                }
+            }
+            if (receivableInterest.isLessThanZero()) {
+                receivableInterest = receivableInterest.zero();
+            }
+            /*
+             * if (transaction.getTransactionDate().isAfter(tillDate) &&
+             * transaction.isAccrual()) { final String errorMessage =
+             * "The date on which a loan is interest waived cannot be in after accrual transactions."
+             * ; throw new InvalidLoanStateTransitionException("waive",
+             * "cannot.be.after.accrual.date", errorMessage, tillDate); }
+             */
+        }
+        return receivableInterest;
+    }
+
+    public void setHelpers(final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanSummaryWrapper loanSummaryWrapper,
+            final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory) {
+        this.loanLifecycleStateMachine = loanLifecycleStateMachine;
+        this.loanSummaryWrapper = loanSummaryWrapper;
+        this.transactionProcessorFactory = transactionProcessorFactory;
+    }
+
+    public boolean isSyncDisbursementWithMeeting() {
+        return this.syncDisbursementWithMeeting == null ? false : this.syncDisbursementWithMeeting;
+    }
+
+    public Date getClosedOnDate() {
+        return this.closedOnDate;
+    }
+
+    public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
+            final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
+            final Boolean reschedulebasedOnMeetingDates, final LocalDate presentMeetingDate, final LocalDate newMeetingDate) {
+
+        // first repayment's from date is same as disbursement date.
+        /*
+         * meetingStartDate is used as seedDate Capture the seedDate from user
+         * and use the seedDate as meetingStart date
+         */
+
+        LocalDate tmpFromDate = getDisbursementDate();
+        final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
+        final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery();
+        final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
+
+        LocalDate newRepaymentDate = null;
+        Boolean isFirstTime = true;
+        LocalDate latestRepaymentDate = null;
+        for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
+
+            LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
+
+            if (oldDueDate.isEqual(presentMeetingDate) || oldDueDate.isAfter(presentMeetingDate)) {
+
+                if (isFirstTime) {
+
+                    isFirstTime = false;
+                    newRepaymentDate = newMeetingDate;
+
+                } else {
+                    // tmpFromDate.plusDays(1) is done to make sure
+                    // getNewRepaymentMeetingDate method returns next meeting
+                    // date and not the same as tmpFromDate
+                    newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, tmpFromDate, tmpFromDate.plusDays(1),
+                            loanRepaymentInterval, frequency, workingDays);
+                }
+
+                if (isHolidayEnabled) {
+                    newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
+                }
+                if (latestRepaymentDate == null || latestRepaymentDate.isBefore(newRepaymentDate)) {
+                    latestRepaymentDate = newRepaymentDate;
+                }
+                loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
+                // reset from date to get actual daysInPeriod
+
+                if (!isFirstTime) {
+                    loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
+                }
+
+                tmpFromDate = newRepaymentDate;// update with new repayment
+                // date
+            } else {
+                tmpFromDate = oldDueDate;
+            }
+        }
+        if (latestRepaymentDate != null) {
+            this.expectedMaturityDate = latestRepaymentDate.toDate();
+        }
+    }
+
+    public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
+            final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
+
+        // first repayment's from date is same as disbursement date.
+        LocalDate tmpFromDate = getDisbursementDate();
+        final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
+        final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery();
+        final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
+
+        LocalDate newRepaymentDate = null;
+        LocalDate seedDate = meetingStartDate;
+        LocalDate latestRepaymentDate = null;
+        for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
+
+            LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
+
+            // FIXME: AA this won't update repayment dates before current date.
+
+            if (oldDueDate.isAfter(seedDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant())) {
+
+                newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, seedDate, oldDueDate, loanRepaymentInterval,
+                        frequency, workingDays);
+
+                final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType,
+                        loanRepaymentInterval, tmpFromDate);
+
+                if (newRepaymentDate.isAfter(maxDateLimitForNewRepayment)) {
+                    newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate,
+                            loanRepaymentInterval, frequency, workingDays);
+                }
+
+                if (isHolidayEnabled) {
+                    newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
+                }
+                if (latestRepaymentDate == null || latestRepaymentDate.isBefore(newRepaymentDate)) {
+                    latestRepaymentDate = newRepaymentDate;
+                }
+
+                loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
+                // reset from date to get actual daysInPeriod
+                loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
+                tmpFromDate = newRepaymentDate;// update with new repayment
+                // date
+            } else {
+                tmpFromDate = oldDueDate;
+            }
+        }
+        if (latestRepaymentDate != null) {
+            this.expectedMaturityDate = latestRepaymentDate.toDate();
+        }
+    }
+
+    private LocalDate getMaxDateLimitForNewRepayment(final PeriodFrequencyType periodFrequencyType, final Integer loanRepaymentInterval,
+            final LocalDate startDate) {
+        LocalDate dueRepaymentPeriodDate = startDate;
+        final Integer repaidEvery = 2 * loanRepaymentInterval;
+        switch (periodFrequencyType) {
+            case DAYS:
+                dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
+            break;
+            case WEEKS:
+                dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
+            break;
+            case MONTHS:
+                dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
+            break;
+            case YEARS:
+                dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
+            break;
+            case INVALID:
+            break;
+        }
+        return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from
+                                                   // startDate
+    }
+
+    public void applyHolidayToRepaymentScheduleDates(final Holiday holiday) {
+        // first repayment's from date is same as disbursement date.
+        LocalDate tmpFromDate = getDisbursementDate();
+        // Loop through all loanRepayments
+        for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
+            final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
+
+            // update from date if it's not same as previous installament's due
+            // date.
+            if (!loanRepaymentScheduleInstallment.getFromDate().isEqual(tmpFromDate)) {
+                loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
+            }
+            if (oldDueDate.isAfter(holiday.getToDateLocalDate())) {
+                break;
+            }
+
+            if (oldDueDate.equals(holiday.getFromDateLocalDate()) || oldDueDate.equals(holiday.getToDateLocalDate())
+                    || oldDueDate.isAfter(holiday.getFromDateLocalDate()) && oldDueDate.isBefore(holiday.getToDateLocalDate())) {
+                // FIXME: AA do we need to apply non-working days.
+                // Assuming holiday's repayment reschedule to date cannot be
+                // created on a non-working day.
+                final LocalDate newRepaymentDate = holiday.getRepaymentsRescheduledToLocalDate();
+                loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
+            }
+            tmpFromDate = loanRepaymentScheduleInstallment.getDueDate();
+        }
+    }
+
+    private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
+        if (!allowTransactionsOnNonWorkingDay) {
+            if (!WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())) {
+                final String errorMessage = "Expected disbursement date cannot be on a non working day";
+                throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage,
+                        getExpectedDisbursedOnLocalDate());
+            }
+        }
+    }
+
+    private void validateDisbursementDateIsOnHoliday(final boolean allowTransactionsOnHoliday, final List<Holiday> holidays) {
+        if (!allowTransactionsOnHoliday) {
+            if (HolidayUtil.isHoliday(getDisbursementDate(), holidays)) {
+                final String errorMessage = "Expected disbursement date cannot be on a holiday";
+                throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage, getExpectedDisbursedOnLocalDate());
+            }
+        }
+    }
+
+    public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
+            final boolean allowTransactionsOnNonWorkingDay) {
+        if (!allowTransactionsOnNonWorkingDay) {
+            if (!WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
+                final String errorMessage = "Repayment date cannot be on a non working day";
+                throw new LoanApplicationDateException("repayment.date.on.non.working.day", errorMessage, repaymentDate);
+            }
+        }
+    }
+
+    public void validateRepaymentDateIsOnHoliday(final LocalDate repaymentDate, final boolean allowTransactionsOnHoliday,
+            final List<Holiday> holidays) {
+        if (!allowTransactionsOnHoliday) {
+            if (HolidayUtil.isHoliday(repaymentDate, holidays)) {
+                final String errorMessage = "Repayment date cannot be on a holiday";
+                throw new LoanApplicationDateException("repayment.date.on.holiday", errorMessage, repaymentDate);
+            }
+        }
+    }
+
+    public Group group() {
+        return this.group;
+    }
+
+    public void updateGroup(final Group newGroup) {
+        this.group = newGroup;
+    }
+
+    public Integer getCurrentLoanCounter() {
+        return this.loanCounter;
+    }
+
+    public Integer getLoanProductLoanCounter() {
+        if (this.loanProductCounter == null) { return 0; }
+        return this.loanProductCounter;
+    }
+
+    public void updateClientLoanCounter(final Integer newLoanCounter) {
+        this.loanCounter = newLoanCounter;
+    }
+
+    public void updateLoanProductLoanCounter(final Integer newLoanProductLoanCounter) {
+        this.loanProductCounter = newLoanProductLoanCounter;
+    }
+
+    public boolean isGroupLoan() {
+        return AccountType.fromInt(this.loanType).isGroupAccount();
+    }
+
+    public boolean isJLGLoan() {
+        return AccountType.fromInt(this.loanType).isJLGAccount();
+    }
+
+    public void updateInterestRateFrequencyType() {
+        this.loanRepaymentScheduleDetail.updatenterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType());
+    }
+
+    public Integer getTermFrequency() {
+        return this.termFrequency;
+    }
+
+    public Integer getTermPeriodFrequencyType() {
+        return this.termPeriodFrequencyType;
+    }
+
+    public List<LoanTransaction> getLoanTransactions() {
+        return this.loanTransactions;
+    }
+
+    public void setLoanStatus(final Integer loanStatus) {
+        this.loanStatus = loanStatus;
+    }
+
+    public void validateExpectedDisbursementForHolidayAndNonWorkingDay(final WorkingDays workingDays,
+            final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final boolean allowTransactionsOnNonWorkingDay) {
+        // validate if disbursement date is a holiday or a non-working day
+        validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
+        validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
+
+    }
+
+    private void validateActivityNotBeforeClientOrGroupTransferDate(final LoanEvent event, final LocalDate activityDate) {
+        if (this.client != null && this.client.getOfficeJoiningLocalDate() != null) {
+            final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate();
+            if (activityDate.isBefore(clientOfficeJoiningDate)) {
+                String errorMessage = null;
+                String action = null;
+                String postfix = null;
+                switch (event) {
+                    case LOAN_CREATED:
+                        errorMessage = "The date on which a loan is submitted cannot be earlier than client's transfer date to this office";
+                        action = "submittal";
+                        postfix = "cannot.be.before.client.transfer.date";
+                    break;
+                    case LOAN_APPROVED:
+                        errorMessage = "The date on which a loan is approved cannot be earlier than client's transfer date to this office";
+                        action = "approval";
+                        postfix = "cannot.be.before.client.transfer.date";
+                    break;
+                    case LOAN_APPROVAL_UNDO:
+                        errorMessage = "The date on which a loan is approved cannot be earlier than client's transfer date to this office";
+                        action = "approval";
+                        postfix = "cannot.be.undone.before.client.transfer.date";
+                    break;
+                    case LOAN_DISBURSED:
+                        errorMessage = "The date on which a loan is disbursed cannot be earlier than client's transfer date to this office";
+                        action = "disbursal";
+                        postfix = "cannot.be.before.client.transfer.date";
+                    break;
+                    case LOAN_DISBURSAL_UNDO:
+                        errorMessage = "Cannot undo a disbursal done in another branch";
+                        action = "disbursal";
+                        postfix = "cannot.be.undone.before.client.transfer.date";
+                    break;
+                    case LOAN_REPAYMENT_OR_WAIVER:
+                        errorMessage = "The date on which a repayment or waiver is made cannot be earlier than client's transfer date to this office";
+                        action = "repayment.or.waiver";
+                        postfix = "cannot.be.made.before.client.transfer.date";
+                    break;
+                    case LOAN_REJECTED:
+                        errorMessage = "The date on which a loan is rejected cannot be earlier than client's transfer date to this office";
+                        action = "reject";
+                        postfix = "cannot.be.before.client.transfer.date";
+                    break;
+                    case LOAN_WITHDRAWN:
+                        errorMessage = "The date on which a loan is withdrawn cannot be earlier than client's transfer date to this office";
+                        action = "withdraw";
+                        postfix = "cannot.be.before.client.transfer.date";
+                    break;
+                    case WRITE_OFF_OUTSTANDING:
+                        errorMessage = "The date on which a write off is made cannot be earlier than client's transfer date to this office";
+                        action = "writeoff";
+                        postfix = "cannot.be.undone.before.client.transfer.date";
+                    break;
+                    case REPAID_IN_FULL:
+                        errorMessage = "The date on which the loan is repaid in full cannot be earlier than client's transfer date to this office";
+                        action = "close";
+                        postfix = "cannot.be.undone.before.client.transfer.date";
+                    break;
+                    case LOAN_CHARGE_PAYMENT:
+                        errorMessage = "The date on which a charge payment is made cannot be earlier than client's transfer date to this office";
+                        action = "charge.payment";
+                        postfix = "cannot.be.made.before.client.transfer.date";
+                    break;
+                    case LOAN_REFUND:
+                        errorMessage = "The date on which a refund is made cannot be earlier than client's transfer date to this office";
+                        action = "refund";
+                        postfix = "cannot.be.made.before.client.transfer.date";
+                    break;
+                    case LOAN_DISBURSAL_UNDO_LAST:
+                        errorMessage = "Cannot undo a last disbursal in another branch";
+                        action = "disbursal";
+                        postfix = "cannot.be.undone.before.client.transfer.date";
+                    break;
+                    default:
+                    break;
+                }
+                throw new InvalidLoanStateTransitionException(action, postfix, errorMessage, clientOfficeJoiningDate);
+            }
+        }
+    }
+
+    private void validateActivityNotBeforeLastTransactionDate(final LoanEvent event, final LocalDate activityDate) {
+        if (!(this.repaymentScheduleDetail().isInterestRecalculationEnabled() || this.loanProduct().isHoldGuaranteeFundsEnabled())) { return; }
+        LocalDate lastTransactionDate = getLastUserTransactionDate();
+        final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate();
+        if (lastTransactionDate.isAfter(activityDate)) {
+            String errorMessage = null;
+            String action = null;
+            String postfix = null;
+            switch (event) {
+                case LOAN_REPAYMENT_OR_WAIVER:
+                    errorMessage = "The date on which a repayment or waiver is made cannot be earlier than last transaction date";
+                    action = "repayment.or.waiver";
+                    postfix = "cannot.be.made.before.last.transaction.date";
+                break;
+                case WRITE_OFF_OUTSTANDING:
+                    errorMessage = "The date on which a write off is made cannot be earlier than last transaction date";
+                    action = "writeoff";
+                    postfix = "cannot.be.made.before.last.transaction.date";
+                break;
+                case LOAN_CHARGE_PAYMENT:
+                    errorMessage = "The date on which a charge payment is made cannot be earlier than last transaction date";
+                    action = "charge.payment";
+                    postfix = "cannot.be.made.before.last.transaction.date";
+                break;
+                default:
+                break;
+            }
+            throw new InvalidLoanStateTransitionException(action, postfix, errorMessage, clientOfficeJoiningDate);
+        }
+    }
+
+    private LocalDate getLastUserTransactionDate() {
+        LocalDate currentTransactionDate = getDisbursementDate();
+        for (final LoanTransaction previousTransaction : this.loanTransactions) {
+            if (!(previousTransaction.isReversed() || previousTransaction.isAccrual())) {
+                if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
+                    currentTransactionDate = previousTransaction.getTransactionDate();
+                }
+            }
+        }
+        return currentTransactionDate;
+    }
+
+    public LocalDate getLastRepaymentDate() {
+        LocalDate currentTransactionDate = getDisbursementDate();
+        for (final LoanTransaction previousTransaction : this.loanTransactions) {
+            if (previousTransaction.isRepayment()) {
+                if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
+                    currentTransactionDate = previousTransaction.getTransactionDate();
+                }
+            }
+        }
+        return currentTransactionDate;
+    }
+
+    public LocalDate getLastUserTransactionForChargeCalc() {
+        LocalDate lastTransaction = getDisbursementDate();
+        if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            lastTransaction = getLastUserTransactionDate();
+        }
+        return lastTransaction;
+    }
+
+    public Set<LoanCharge> charges() {
+        Set<LoanCharge> loanCharges = new HashSet<>();
+        if (this.charges != null) {
+            for (LoanCharge charge : this.charges) {
+                if (charge.isActive()) {
+                    loanCharges.add(charge);
+                }
+            }
+        }
+        return loanCharges;
+    }
+
+    public Set<LoanTrancheCharge> trancheCharges() {
+        Set<LoanTrancheCharge> loanCharges = new HashSet<>();
+        if (this.trancheCharges != null) {
+            for (LoanTrancheCharge charge : this.trancheCharges) {
+                loanCharges.add(charge);
+            }
+        }
+        return loanCharges;
+    }
+
+    public Set<LoanInstallmentCharge> generateInstallmentLoanCharges(final LoanCharge loanCharge) {
+        final Set<LoanInstallmentCharge> loanChargePerInstallments = new HashSet<>();
+        if (loanCharge.isInstalmentFee()) {
+            for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+                BigDecimal amount = BigDecimal.ZERO;
+                if (loanCharge.getChargeCalculation().isFlat()) {
+                    amount = loanCharge.amountOrPercentage();
+                } else {
+                    amount = calculateInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage(), installment)
+                            .getAmount();
+                }
+                final LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment);
+                loanChargePerInstallments.add(loanInstallmentCharge);
+            }
+        }
+        return loanChargePerInstallments;
+    }
+
+    public void validateAccountStatus(final LoanEvent event) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        switch (event) {
+            case LOAN_CREATED:
+            break;
+            case LOAN_APPROVED:
+                if (!isSubmittedAndPendingApproval()) {
+                    final String defaultUserMessage = "Loan Account Approval is not allowed. Loan Account is not in submitted and pending approval state.";
+                    final ApiParameterError error = ApiParameterError.generalError(
+                            "error.msg.loan.approve.account.is.not.submitted.and.pending.state", defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_APPROVAL_UNDO:
+                if (!isApproved()) {
+                    final String defaultUserMessage = "Loan Account Undo Approval is not allowed. Loan Account is not in approved state.";
+                    final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.undo.approval.account.is.not.approved",
+                            defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_DISBURSED:
+                if ((!(isApproved() && isNotDisbursed()) && !this.loanProduct.isMultiDisburseLoan())
+                        || (this.loanProduct.isMultiDisburseLoan() && !isAllTranchesNotDisbursed())) {
+                    final String defaultUserMessage = "Loan Disbursal is not allowed. Loan Account is not in approved and not disbursed state.";
+                    final ApiParameterError error = ApiParameterError.generalError(
+                            "error.msg.loan.disbursal.account.is.not.approve.not.disbursed.state", defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_DISBURSAL_UNDO:
+                if (!isOpen()) {
+                    final String defaultUserMessage = "Loan Undo disbursal is not allowed. Loan Account is not active.";
+                    final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.undo.disbursal.account.is.not.active",
+                            defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_REPAYMENT_OR_WAIVER:
+                if (!isOpen()) {
+                    final String defaultUserMessage = "Loan Repayment or Waiver is not allowed. Loan Account is not active.";
+                    final ApiParameterError error = ApiParameterError.generalError(
+                            "error.msg.loan.repayment.or.waiver.account.is.not.active", defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_REJECTED:
+                if (!isSubmittedAndPendingApproval()) {
+                    final String defaultUserMessage = "Loan application cannot be rejected. Loan Account is not in Submitted and Pending approval state.";
+                    final ApiParameterError error = ApiParameterError.generalError(
+                            "error.msg.loan.reject.account.is.not.submitted.pending.approval.state", defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_WITHDRAWN:
+                if (!isSubmittedAndPendingApproval()) {
+                    final String defaultUserMessage = "Loan application cannot be withdrawn. Loan Account is not in Submitted and Pending approval state.";
+                    final ApiParameterError error = ApiParameterError.generalError(
+                            "error.msg.loan.withdrawn.account.is.not.submitted.pending.approval.state", defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case WRITE_OFF_OUTSTANDING:
+                if (!isOpen()) {
+                    final String defaultUserMessage = "Loan Written off is not allowed. Loan Account is not active.";
+                    final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.writtenoff.account.is.not.active",
+                            defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case WRITE_OFF_OUTSTANDING_UNDO:
+                if (!isClosedWrittenOff()) {
+                    final String defaultUserMessage = "Loan Undo Written off is not allowed. Loan Account is not Written off.";
+                    final ApiParameterError error = ApiParameterError.generalError(
+                            "error.msg.loan.undo.writtenoff.account.is.not.written.off", defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case REPAID_IN_FULL:
+            break;
+            case LOAN_CHARGE_PAYMENT:
+                if (!isOpen()) {
+                    final String defaultUserMessage = "Charge payment is not allowed. Loan Account is not Active.";
+                    final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.charge.payment.account.is.not.active",
+                            defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_CLOSED:
+                if (!isOpen()) {
+                    final String defaultUserMessage = "Closing Loan Account is not allowed. Loan Account is not Active.";
+                    final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.close.account.is.not.active",
+                            defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_EDIT_MULTI_DISBURSE_DATE:
+                if (isClosed()) {
+                    final String defaultUserMessage = "Edit disbursement is not allowed. Loan Account is not active.";
+                    final ApiParameterError error = ApiParameterError.generalError(
+                            "error.msg.loan.edit.disbursement.account.is.not.active", defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_RECOVERY_PAYMENT:
+                if (!isClosedWrittenOff()) {
+                    final String defaultUserMessage = "Recovery repayments may only be made on loans which are written off";
+                    final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.account.is.not.written.off",
+                            defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_REFUND:
+                if (!isOpen()) {
+                    final String defaultUserMessage = "Loan Refund is not allowed. Loan Account is not active.";
+                    final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.refund.account.is.not.active",
+                            defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            case LOAN_DISBURSAL_UNDO_LAST:
+                if (!isOpen()) {
+                    final String defaultUserMessage = "Loan Undo last disbursal is not allowed. Loan Account is not active.";
+                    final ApiParameterError error = ApiParameterError.generalError(
+                            "error.msg.loan.undo.last.disbursal.account.is.not.active", defaultUserMessage);
+                    dataValidationErrors.add(error);
+                }
+            break;
+            default:
+            break;
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+    }
+
+    public LoanCharge fetchLoanChargesById(Long id) {
+        LoanCharge charge = null;
+        for (LoanCharge loanCharge : this.charges) {
+            if (id.equals(loanCharge.getId())) {
+                charge = loanCharge;
+                break;
+            }
+        }
+        return charge;
+    }
+
+    private List<Long> fetchAllLoanChargeIds() {
+        List<Long> list = new ArrayList<>();
+        for (LoanCharge loanCharge : this.charges) {
+            list.add(loanCharge.getId());
+        }
+        return list;
+    }
+
+    public Set<LoanDisbursementDetails> getDisbursementDetails() {
+        return this.disbursementDetails;
+    }
+
+    public ChangedTransactionDetail updateDisbursementDateAndAmountForTranche(final LoanDisbursementDetails disbursementDetails,
+            final JsonCommand command,final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO, 
+            final AppUser currentUser) {
+        final Locale locale = command.extractLocale();
+        validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE);
+        final BigDecimal principal = command.bigDecimalValueOfParameterNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName,
+                locale);
+        final LocalDate expectedDisbursementDate = command
+                .localDateValueOfParameterNamed(LoanApiConstants.updatedDisbursementDateParameterName);
+        disbursementDetails.updateExpectedDisbursementDateAndAmount(expectedDisbursementDate.toDate(), principal);
+        actualChanges.put(LoanApiConstants.disbursementDateParameterName,
+                command.stringValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName));
+        actualChanges.put(LoanApiConstants.disbursementIdParameterName,
+                command.stringValueOfParameterNamed(LoanApiConstants.disbursementIdParameterName));
+        actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName,
+                command.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementPrincipalParameterName, locale));
+
+        Collection<LoanDisbursementDetails> loanDisburseDetails = this.getDisbursementDetails();
+        BigDecimal setPrincipalAmount = BigDecimal.ZERO;
+        for (LoanDisbursementDetails details : loanDisburseDetails) {
+            setPrincipalAmount = setPrincipalAmount.add(details.principal());
+        }
+
+        this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
+        if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
+        } else {
+            regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
+        }
+
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+        ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
+                getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
+                charges());
+        for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+            mapEntry.getValue().updateLoan(this);
+            this.loanTransactions.add(mapEntry.getValue());
+        }
+
+        return changedTransactionDetail;
+    }
+
+    public BigDecimal retriveLastEmiAmount() {
+        BigDecimal emiAmount = this.fixedEmiAmount;
+        Date startDate = this.getDisbursementDate().toDate();
+        for (LoanTermVariations loanTermVariations : this.loanTermVariations) {
+            if (loanTermVariations.getTermType().isEMIAmountVariation() && !startDate.after(loanTermVariations.getTermApplicableFrom())) {
+                startDate = loanTermVariations.getTermApplicableFrom();
+                emiAmount = loanTermVariations.getTermValue();
+            }
+        }
+        return emiAmount;
+    }
+
+    public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) {
+        LoanRepaymentScheduleInstallment installment = null;
+        if (installmentNumber == null) { return installment; }
+        for (final LoanRepaymentScheduleInstallment scheduleInstallment : this.repaymentScheduleInstallments) {
+            if (scheduleInstallment.getInstallmentNumber().equals(installmentNumber)) {
+                installment = scheduleInstallment;
+                break;
+            }
+        }
+        return installment;
+    }
+
+    public BigDecimal getApprovedPrincipal() {
+        return this.approvedPrincipal;
+    }
+
+    public BigDecimal getTotalOverpaid() {
+        return this.totalOverpaid;
+    }
+
+    public void updateIsInterestRecalculationEnabled() {
+        this.loanRepaymentScheduleDetail.updateIsInterestRecalculationEnabled(isInterestRecalculationEnabledForProduct());
+    }
+
+    public LoanInterestRecalculationDetails loanInterestRecalculationDetails() {
+        return this.loanInterestRecalculationDetails;
+    }
+
+    public Long loanInterestRecalculationDetailId() {
+        if (loanInterestRecalculationDetails() != null) { return this.loanInterestRecalculationDetails.getId(); }
+        return null;
+    }
+
+    public LocalDate getExpectedMaturityDate() {
+        LocalDate expectedMaturityDate = null;
+        if (this.expectedMaturityDate != null) {
+            expectedMaturityDate = new LocalDate(this.expectedMaturityDate);
+        }
+        return expectedMaturityDate;
+    }
+
+    public LocalDate getMaturityDate() {
+        LocalDate maturityDate = getExpectedMaturityDate();
+        if (this.actualMaturityDate != null) {
+            maturityDate = new LocalDate(this.actualMaturityDate);
+        }
+        return maturityDate;
+    }
+
+    public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final ScheduleGeneratorDTO generatorDTO,
+            final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final AppUser currentUser) {
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+        /*
+         * LocalDate recalculateFrom = null; List<LoanTransaction>
+         * loanTransactions =
+         * this.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); for
+         * (LoanTransaction loanTransaction : loanTransactions) { if
+         * (recalculateFrom == null ||
+         * loanTransaction.getTransactionDate().isAfter(recalculateFrom)) {
+         * recalculateFrom = loanTransaction.getTransactionDate(); } }
+         * generatorDTO.setRecalculateFrom(recalculateFrom);
+         */
+        if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser);
+        } else {
+            regenerateRepaymentSchedule(generatorDTO, currentUser);
+        }
+        return processTransactions();
+
+    }
+
+    public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO,
+            final AppUser currentUser) {
+        regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser);
+        return processTransactions();
+
+    }
+
+    private ChangedTransactionDetail processTransactions() {
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+        ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
+                getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
+                charges());
+        for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+            mapEntry.getValue().updateLoan(this);
+        }
+        /***
+         * Commented since throwing exception if external id present for one of
+         * the transactions. for this need to save the reversed transactions
+         * first and then new transactions.
+         */
+        this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
+
+        updateLoanSummaryDerivedFields();
+
+        this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values());
+
+        return changedTransactionDetail;
+    }
+
+    public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) {
+
+        LocalDate lastTransactionDate = getLastUserTransactionDate();
+        final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO);
+        if (loanSchedule == null) { return; }
+        updateLoanSchedule(loanSchedule.getInstallments(), currentUser);
+        this.interestRecalculatedOn = DateUtils.getDateOfTenant();
+        LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate();
+        Set<LoanCharge> charges = this.charges();
+        for (LoanCharge loanCharge : charges) {
+            if (!loanCharge.isDueAtDisbursement()) {
+                updateOverdueScheduleInstallment(loanCharge);
+                if (loanCharge.getDueLocalDate() == null || (!lastRepaymentDate.isBefore(loanCharge.getDueLocalDate()))) {
+                    if (!loanCharge.isWaived()
+                            && (loanCharge.getDueLocalDate() == null || !lastTransactionDate.isAfter(loanCharge.getDueLocalDate()))) {
+                        recalculateLoanCharge(loanCharge, generatorDTO.getPenaltyWaitPeriod());
+                        loanCharge.updateWaivedAmount(getCurrency());
+                    }
+                } else {
+                    loanCharge.setActive(false);
+                }
+            }
+        }
+
+        processPostDisbursementTransactions();
+    }
+
+    public void processPostDisbursementTransactions() {
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+        final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+        final List<LoanTransaction> copyTransactions = new ArrayList<>();
+        if (allNonContraTransactionsPostDisbursement.size() > 0) {
+            for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) {
+                copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
+            }
+            loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), copyTransactions,
+                    getCurrency(), this.repaymentScheduleInstallments, charges());
+
+            updateLoanSummaryDerivedFields();
+        }
+    }
+
+    private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
+
+        if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || !isOpen()) { return null; }
+        final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
+        final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod);
+
+        final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
+        final MathContext mc = new MathContext(8, roundingMode);
+
+        final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO);
+
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+
+        return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), generatorDTO.getHolidayDetailDTO(),
+                retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor,
+                repaymentScheduleInstallments, generatorDTO.getRecalculateFrom());
+    }
+
+    public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
+        LoanRepaymentScheduleInstallment installment = null;
+
+        if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
+            final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
+            final MathContext mc = new MathContext(8, roundingMode);
+
+            final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
+            final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
+
+            final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod);
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                    .determineProcessor(this.transactionProcessingStrategy);
+            installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, charges(),
+                    scheduleGeneratorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(),
+                    loanRepaymentScheduleTransactionProcessor, this.fetchRepaymentScheduleInstallments());
+        } else {
+            installment = this.getTotalOutstandingOnLoan();
+        }
+        return installment;
+    }
+
+    public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
+        final Integer loanTermFrequency = this.termFrequency;
+        final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
+        final NthDayType nthDayType = NthDayType.fromInt(this.repaymentFrequencyNthDayType);
+        final DayOfWeekType dayOfWeekType = DayOfWeekType.fromInt(this.repaymentFrequencyDayOfWeekType);
+        final List<DisbursementData> disbursementData = new ArrayList<>();
+        for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
+            disbursementData.add(disbursementDetails.toData());
+        }
+
+        CalendarInstance restCalendarInstance = null;
+        CalendarInstance compoundingCalendarInstance = null;
+        RecalculationFrequencyType recalculationFrequencyType = null;
+        InterestRecalculationCompoundingMethod compoundingMethod = null;
+        RecalculationFrequencyType compoundingFrequencyType = null;
+        LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
+        if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation();
+            compoundingCalendarInstance = scheduleGeneratorDTO.getCompoundingCalendarInstance();
+            recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType();
+            compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod();
+            compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType();
+            rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod();
+        }
+
+        BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
+        FloatingRateDTO floatingRateDTO = scheduleGeneratorDTO.getFloatingRateDTO();
+        List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
+        annualNominalInterestRate = constructLoanTermVariations(floatingRateDTO, annualNominalInterestRate, loanTermVariations);
+
+        final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(),
+                loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(),
+                getExpectedFirstRepaymentOnDate(), scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(),
+                this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData,
+                this.maxOutstandingLoanBalance, getInterestChargedFromDate(), this.loanProduct.getPrincipalThresholdForLastInstallment(),
+                this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod,
+                compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(),
+                rescheduleStrategyMethod, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations);
+        return loanApplicationTerms;
+    }
+
+    private BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate,
+            List<LoanTermVariationsData> loanTermVariations) {
+        for (LoanTermVariations variationTerms : this.loanTermVariations) {
+            loanTermVariations.add(variationTerms.toData());
+        }
+        annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations);
+        return annualNominalInterestRate;
+    }
+
+    private LoanRepaymentScheduleInstallment getTotalOutstandingOnLoan() {
+        Money feeCharges = Money.zero(loanCurrency());
+        Money penaltyCharges = Money.zero(loanCurrency());
+        Money totalPrincipal = Money.zero(loanCurrency());
+        Money totalInterest = Money.zero(loanCurrency());
+        for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
+            totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(loanCurrency()));
+            totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency()));
+            feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(loanCurrency()));
+            penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loanCurrency()));
+        }
+        return new LoanRepaymentScheduleInstallment(null, 0, LocalDate.now(), LocalDate.now(), totalPrincipal.getAmount(),
+                totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false);
+    }
+
+    public List<LoanRepaymentScheduleInstallment> fetchRepaymentScheduleInstallments() {
+        return this.repaymentScheduleInstallments;
+    }
+
+    public LocalDate getAccruedTill() {
+        LocalDate accruedTill = null;
+        if (this.accruedTill != null) {
+            accruedTill = new LocalDate(this.accruedTill);
+        }
+        return accruedTill;
+    }
+
+    public LocalDate fetchInterestRecalculateFromDate() {
+        LocalDate interestRecalculatedOn = null;
+        if (this.interestRecalculatedOn == null) {
+            interestRecalculatedOn = getDisbursementDate();
+        } else {
+            interestRecalculatedOn = new LocalDate(this.interestRecalculatedOn);
+        }
+        return interestRecalculatedOn;
+    }
+
+    private void updateLoanOutstandingBalaces() {
+        Money outstanding = Money.zero(getCurrency());
+        List<LoanTransaction> loanTransactions = retreiveListOfTransactionsExcludeAccruals();
+        for (LoanTransaction loanTransaction : loanTransactions) {
+            if (loanTransaction.isDisbursement()) {
+                outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()));
+                loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
+            } else {
+                outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency()));
+                loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
+            }
+        }
+    }
+
+    public LoanTransactionProcessingStrategy transactionProcessingStrategy() {
+        return this.transactionProcessingStrategy;
+    }
+
+    public boolean isNpa() {
+        return this.isNpa;
+    }
+
+    /**
+     * @return List of loan repayments schedule objects
+     **/
+    public List<LoanRepaymentScheduleInstallment> getRepaymentScheduleInstallments() {
+        return this.repaymentScheduleInstallments;
+    }
+
+    /**
+     * @return Loan product minimum repayments schedule related detail
+     **/
+    public LoanProductRelatedDetail getLoanRepaymentScheduleDetail() {
+        return this.loanRepaymentScheduleDetail;
+    }
+
+    /**
+     * @return Loan Fixed Emi amount
+     **/
+    public BigDecimal getFixedEmiAmount() {
+        return this.fixedEmiAmount;
+    }
+
+    /**
+     * @return maximum outstanding loan balance
+     **/
+    public BigDecimal getMaxOutstandingLoanBalance() {
+        return this.maxOutstandingLoanBalance;
+    }
+
+    /**
+     * @param dueDate
+     *            the due date of the installment
+     * @return a schedule installment with similar due date to the one provided
+     **/
+    public LoanRepaymentScheduleInstallment getRepaymentScheduleInstallment(LocalDate dueDate) {
+        LoanRepaymentScheduleInstallment installment = null;
+
+        if (dueDate != null) {
+            for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : this.repaymentScheduleInstallments) {
+
+                if (repaymentScheduleInstallment.getDueDate().isEqual(dueDate)) {
+                    installment = repaymentScheduleInstallment;
+
+                    break;
+                }
+            }
+        }
+
+        return installment;
+    }
+
+    /**
+     * @return loan disbursement data
+     **/
+    public List<DisbursementData> getDisbursmentData() {
+        Iterator<LoanDisbursementDetails> iterator = this.getDisbursementDetails().iterator();
+        List<DisbursementData> disbursementData = new ArrayList<>();
+
+        while (iterator.hasNext()) {
+            LoanDisbursementDetails loanDisbursementDetails = iterator.next();
+
+            LocalDate expectedDisbursementDate = null;
+            LocalDate actualDisbursementDate = null;
+
+            if (loanDisbursementDetails.expectedDisbursementDate() != null) {
+                expectedDisbursementDate = new LocalDate(loanDisbursementDetails.expectedDisbursementDate());
+            }
+
+            if (loanDisbursementDetails.actualDisbursementDate() != null) {
+                actualDisbursementDate = new LocalDate(loanDisbursementDetails.actualDisbursementDate());
+            }
+
+            disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate,
+                    loanDisbursementDetails.principal(), null, null));
+        }
+
+        return disbursementData;
+    }
+
+    /**
+     * @param restCalendarInstance
+     *            TODO
+     * @param compoundingCalendarInstance
+     *            TODO
+     * @param floatingRateDTO
+     *            TODO
+     * @param loanCalendarInstance
+     *            Used for accessing the loan's calendar object
+     * @return application terms of the Loan object
+     **/
+    @SuppressWarnings({ "unused" })
+    public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency,
+            final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar,
+            final FloatingRateDTO floatingRateDTO) {
+        LoanProduct loanProduct = loanProduct();
+        // LoanProductRelatedDetail loanProductRelatedDetail =
+        // getLoanRepaymentScheduleDetail();
+        final MonetaryCurrency currency = this.loanRepaymentScheduleDetail.getCurrency();
+
+        final Integer loanTermFrequency = getTermFrequency();
+        final PeriodFrequencyType loanTermPeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
+        final NthDayType nthDayType = NthDayType.fromInt(this.repaymentFrequencyNthDayType);
+        final DayOfWeekType dayOfWeekType = DayOfWeekType.fromInt(this.repaymentFrequencyDayOfWeekType);
+
+        final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments();
+        final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery();
+        final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
+
+        final AmortizationMethod amortizationMethod = this.loanRepaymentScheduleDetail.getAmortizationMethod();
+
+        final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
+        final InterestCalculationPeriodMethod interestCalculationPeriodMethod = this.loanRepaymentScheduleDetail
+                .getInterestCalculationPeriodMethod();
+
+        final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod();
+        final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
+
+        BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
+        final Money principalMoney = this.loanRepaymentScheduleDetail.getPrincipal();
+
+        final LocalDate expectedDisbursementDate = getExpectedDisbursedOnLocalDate();
+        final LocalDate repaymentsStartingFromDate = getExpectedFirstRepaymentOnDate();
+        LocalDate calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate;
+
+        // TODO get calender linked to loan if any exist. As of 17-07-2014,
+        // could not find a link in the database.
+        // skip for now and set the Calender object to null
+        // Calendar loanCalendar = null;
+        // The calendar instance might be null if the loan is not connected
+        // To a calendar object
+        // if (loanCalendarInstance != null) {
+        // loanCalendar = loanCalendarInstance.getCalendar();
+        // }
+
+        final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.graceOnPrincipalPayment();
+        final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.graceOnInterestPayment();
+        final Integer graceOnInterestCharged = this.loanRepaymentScheduleDetail.graceOnInterestCharged();
+        final LocalDate interestChargedFromDate = getInterestChargedFromDate();
+        final Integer graceOnArrearsAgeing = this.loanRepaymentScheduleDetail.getGraceOnDueDate();
+
+        final Money inArrearsToleranceMoney = this.loanRepaymentScheduleDetail.getInArrearsTolerance();
+        final BigDecimal emiAmount = getFixedEmiAmount();
+        final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance();
+
+        final List<DisbursementData> disbursementData = getDisbursmentData();
+
+        RecalculationFrequencyType recalculationFrequencyType = null;
+        InterestRecalculationCompoundingMethod compoundingMethod = null;
+        RecalculationFrequencyType compoundingFrequencyType = null;
+        LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
+        if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType();
+            compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod();
+            compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType();
+            rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod();
+        }
+
+        List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
+        annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations);
+
+        return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType,
+                dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate,
+                inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData,
+                maxOutstandingBalance, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(),
+                this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod,
+                compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(),
+                rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations);
+    }
+
+    /**
+     * @return Loan summary embedded object
+     **/
+    public LoanSummary getLoanSummary() {
+        return this.summary;
+    }
+
+    public void updateRescheduledByUser(AppUser rescheduledByUser) {
+        this.rescheduledByUser = rescheduledByUser;
+    }
+
+    public LoanProductRelatedDetail getLoanProductRelatedDetail() {
+        return this.loanRepaymentScheduleDetail;
+    }
+
+    public void updateNumberOfRepayments(Integer numberOfRepayments) {
+        this.loanRepaymentScheduleDetail.updateNumberOfRepayments(numberOfRepayments);
+    }
+
+    public void updateRescheduledOnDate(LocalDate rescheduledOnDate) {
+
+        if (rescheduledOnDate != null) {
+            this.rescheduledOnDate = rescheduledOnDate.toDate();
+        }
+    }
+
+    public void updateTermFrequency(Integer termFrequency) {
+
+        if (termFrequency != null) {
+            this.termFrequency = termFrequency;
+        }
+    }
+
+    public boolean isFeeCompoundingEnabledForInterestRecalculation() {
+        boolean isEnabled = false;
+        if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            isEnabled = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled();
+        }
+        return isEnabled;
+    }
+
+    public Integer getRepaymentFrequencyNthDayType() {
+        return this.repaymentFrequencyNthDayType;
+    }
+
+    public void setRepaymentFrequencyNthDayType(Integer repaymentFrequencyNthDayType) {
+        this.repaymentFrequencyNthDayType = repaymentFrequencyNthDayType;
+    }
+
+    public Integer getRepaymentFrequencyDayOfWeekType() {
+        return this.repaymentFrequencyDayOfWeekType;
+    }
+
+    public void setRepaymentFrequencyDayOfWeekType(Integer repaymentFrequencyDayOfWeekType) {
+        this.repaymentFrequencyDayOfWeekType = repaymentFrequencyDayOfWeekType;
+    }
+
+    public String getAccountNumber() {
+        return this.accountNumber;
+    }
+
+    public Client getClient() {
+        return this.client;
+    }
+
+    public Boolean shouldCreateStandingInstructionAtDisbursement() {
+        return (this.createStandingInstructionAtDisbursement != null) && this.createStandingInstructionAtDisbursement;
+    }
+
+    public Collection<LoanCharge> getLoanCharges(LocalDate dueDate) {
+        Collection<LoanCharge> loanCharges = new ArrayList<>();
+
+        for (LoanCharge loanCharge : charges) {
+
+            if ((loanCharge.getDueLocalDate() != null) && loanCharge.getDueLocalDate().equals(dueDate)) {
+                loanCharges.add(loanCharge);
+            }
+        }
+
+        return loanCharges;
+    }
+
+    public void setGuaranteeAmount(BigDecimal guaranteeAmountDerived) {
+        this.guaranteeAmountDerived = guaranteeAmountDerived;
+    }
+
+    public void updateGuaranteeAmount(BigDecimal guaranteeAmount) {
+        this.guaranteeAmountDerived = getGuaranteeAmount().add(guaranteeAmount);
+    }
+
+    public BigDecimal getGuaranteeAmount() {
+        return this.guaranteeAmountDerived == null ? BigDecimal.ZERO : this.guaranteeAmountDerived;
+    }
+
+    public ChangedTransactionDetail makeRefundForActiveLoan(final LoanTransaction loanTransaction,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
+            final List<Long> existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays,
+            final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
+
+        validateAccountStatus(LoanEvent.LOAN_REFUND);
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REFUND, loanTransaction.getTransactionDate());
+
+        validateRefundDateIsAfterLastRepayment(loanTransaction.getTransactionDate());
+
+        validateRepaymentDateIsOnHoliday(loanTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
+        validateRepaymentDateIsOnNonWorkingDay(loanTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
+
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+
+        final ChangedTransactionDetail changedTransactionDetail = handleRefundTransaction(loanTransaction, loanLifecycleStateMachine, null);
+
+        return changedTransactionDetail;
+
+    }
+
+    private void validateRefundDateIsAfterLastRepayment(final LocalDate refundTransactionDate) {
+        final LocalDate possibleNextRefundDate = possibleNextRefundDate();
+
+        if (possibleNextRefundDate == null || refundTransactionDate.isBefore(possibleNextRefundDate)) { throw new InvalidRefundDateException(
+                refundTransactionDate.toString()); }
+
+    }
+
+    private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction loanTransaction,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) {
+
+        ChangedTransactionDetail changedTransactionDetail = null;
+
+        final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REFUND, LoanStatus.fromInt(this.loanStatus));
+        this.loanStatus = statusEnum.getValue();
+
+        loanTransaction.updateLoan(this);
+
+        // final boolean isTransactionChronologicallyLatest =
+        // isChronologicallyLatestRefund(loanTransaction,
+        // this.loanTransactions);
+
+        if (status().isOverpaid() || status().isClosed()) {
+
+            final String errorMessage = "This refund option is only for active loans ";
+            throw new InvalidLoanStateTransitionException("transaction", "is.exceeding.overpaid.amount", errorMessage, this.totalOverpaid,
+                    loanTransaction.getAmount(getCurrency()).getAmount());
+
+        } else if (this.getTotalPaidInRepayments().isZero()) {
+            final String errorMessage = "Cannot refund when no payment has been made";
+            throw new InvalidLoanStateTransitionException("transaction", "no.payment.yet.made.for.loan", errorMessage);
+        }
+
+        if (loanTransaction.isNotZero(loanCurrency())) {
+            this.loanTransactions.add(loanTransaction);
+        }
+
+        if (loanTransaction.isNotRefundForActiveLoan()) {
+            final String errorMessage = "A transaction of type refund was expected but not received.";
+            throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.refund.transaction", errorMessage);
+        }
+
+        final LocalDate loanTransactionDate = loanTransaction.getTransactionDate();
+        if (loanTransactionDate.isBefore(getDisbursementDate())) {
+            final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
+                    + getApprovedOnDate().toString();
+            throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.disbursement.date", errorMessage,
+                    loanTransactionDate, getDisbursementDate());
+        }
+
+        if (loanTransactionDate.isAfter(DateUtils.getLocalDateOfTenant())) {
+            final String errorMessage = "The transaction date cannot be in the future.";
+            throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, loanTransactionDate);
+        }
+
+        if (this.loanProduct.isMultiDisburseLoan() && adjustedTransaction == null) {
+            BigDecimal totalDisbursed = getDisbursedAmount();
+            if (totalDisbursed.compareTo(this.summary.getTotalPrincipalRepaid()) < 0) {
+                final String errorMessage = "The transaction cannot be done before the loan disbursement: "
+                        + getApprovedOnDate().toString();
+                throw new InvalidLoanStateTransitionException("transaction", "cannot.be.done.before.disbursement", errorMessage);
+            }
+        }
+
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+                .determineProcessor(this.transactionProcessingStrategy);
+
+        // If is a refund
+        if (adjustedTransaction == null) {
+            loanRepaymentScheduleTransactionProcessor.handleRefund(loanTransaction, getCurrency(), this.repaymentScheduleInstallments,
+                    charges());
+        } else {
+            final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+            changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
+                    allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                mapEntry.getValue().updateLoan(this);
+            }
+
+        }
+
+        updateLoanSummaryDerivedFields();
+
+        doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
+
+        return changedTransactionDetail;
+    }
+
+    public LocalDate possibleNextRefundDate() {
+
+        final LocalDate now = new LocalDate();
+
+        LocalDate lastTransactionDate = null;
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if ((transaction.isRepayment() || transaction.isRefundForActiveLoan()) && transaction.isNonZero()) {
+                lastTransactionDate = transaction.getTransactionDate();
+            }
+        }
+
+        return lastTransactionDate == null ? now : lastTransactionDate;
+    }
+
+    private Date getActualDisbursementDate(final LoanCharge loanCharge) {
+        Date actualDisbursementDate = null;
+        if (loanCharge.isDueAtDisbursement() && loanCharge.isActive()) {
+            LoanTrancheDisbursementCharge trancheDisbursementCharge = loanCharge.getTrancheDisbursementCharge();
+            if (trancheDisbursementCharge != null) {
+                LoanDisbursementDetails details = trancheDisbursementCharge.getloanDisbursementDetails();
+                actualDisbursementDate = details.actualDisbursementDate();
+            }
+        }
+        return actualDisbursementDate;
+    }
+
+    public void addTrancheLoanCharge(Charge charge) {
+        if (!trancheCharges.contains(charge)) {
+            trancheCharges.add(new LoanTrancheCharge(charge, this));
+        }
+    }
+
+    public Map<String, Object> undoLastDisbursal(ScheduleGeneratorDTO scheduleGeneratorDTO, List<Long> existingTransactionIds,
+            List<Long> existingReversedTransactionIds, AppUser currentUser, Loan loan) {
+
+        validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO_LAST);
+        existingTransactionIds.addAll(findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO_LAST, getDisbursementDate());
+        LocalDate actualDisbursementDate = null;
+        LocalDate lastTransactionDate = getDisbursementDate();
+        List<LoanTransaction> loanTransactions = retreiveListOfTransactionsExcludeAccruals();
+        Collections.reverse(loanTransactions);
+        for (final LoanTransaction previousTransaction : loanTransactions) {
+            if (lastTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
+                if (previousTransaction.isRepayment() || previousTransaction.isWaiver() || previousTransaction.isChargePayment()) { throw new UndoLastTrancheDisbursementException(
+                        previousTransaction.getId()); }
+            }
+            if (previousTransaction.isDisbursement()) {
+                lastTransactionDate = previousTransaction.getTransactionDate();
+                break;
+            }
+        }
+        actualDisbursementDate = lastTransactionDate;
+        updateLoanToLastDisbursalState(actualDisbursementDate);
+        for (Iterator<LoanTermVariations> iterator = this.loanTermVariations.iterator(); iterator.hasNext();) {
+        	LoanTermVariations loanTermVariations = iterator.next();
+			if (loanTermVariations.fetchDateValue().isAfter(actualDisbursementDate)) {
+				iterator.remove();
+			}
+		}
+        reverseExistingTransactionsTillLastDisbursal(actualDisbursementDate);
+        loan.recalculateScheduleFromLastTransaction(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds,
+                currentUser);
+        actualChanges.put("undolastdisbursal", "true");
+        actualChanges.put("disbursedAmount", this.getDisbursedAmount());
+        updateLoanSummaryDerivedFields();
+
+        return actualChanges;
+    }
+
+    /**
+     * Reverse only disbursement, accruals, and repayments at disbursal
+     * transactions
+     * 
+     * @param actualDisbursementDate
+     * @return
+     */
+    public List<LoanTransaction> reverseExistingTransactionsTillLastDisbursal(LocalDate actualDisbursementDate) {
+        final List<LoanTransaction> reversedTransactions = new ArrayList<>();
+        for (final LoanTransaction transaction : this.loanTransactions) {
+            if ((actualDisbursementDate.equals(transaction.getTransactionDate()) || actualDisbursementDate.isBefore(transaction
+                    .getTransactionDate())) && transaction.isAllowTypeTransactionAtTheTimeOfLastUndo()) {
+                reversedTransactions.add(transaction);
+                transaction.reverse();
+            }
+        }
+        return reversedTransactions;
+    }
+
+    private void updateLoanToLastDisbursalState(LocalDate actualDisbursementDate) {
+
+        for (final LoanCharge charge : charges()) {
+            if (charge.isOverdueInstallmentCharge()) {
+                charge.setActive(false);
+            } else if (charge.isTrancheDisbursementCharge()
+                    && actualDisbursementDate.equals(new LocalDate(charge.getTrancheDisbursementCharge().getloanDisbursementDetails()
+                            .actualDisbursementDate()))) {
+                charge.resetToOriginal(loanCurrency());
+            }
+        }
+        for (final LoanDisbursementDetails details : this.disbursementDetails) {
+            if (actualDisbursementDate.equals(new LocalDate(details.actualDisbursementDate()))) {
+                this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount().subtract(details.principal()));
+                details.updateActualDisbursementDate(null);
+            }
+        }
+        updateLoanSummaryDerivedFields();
+    }
+
+    public Boolean getIsFloatingInterestRate() {
+        return this.isFloatingInterestRate;
+    }
+
+    public BigDecimal getInterestRateDifferential() {
+        return this.interestRateDifferential;
+    }
+
+    public void setIsFloatingInterestRate(Boolean isFloatingInterestRate) {
+        this.isFloatingInterestRate = isFloatingInterestRate;
+    }
+
+    public void setInterestRateDifferential(BigDecimal interestRateDifferential) {
+        this.interestRateDifferential = interestRateDifferential;
+    }
+
+    public List<LoanTermVariations> getLoanTermVariations() {
+        return this.loanTermVariations;
+    }
+
+    private int adjustNumberOfRepayments() {
+        int repaymetsForAdjust = 0;
+        for (LoanTermVariations loanTermVariations : this.loanTermVariations) {
+            if (loanTermVariations.getTermType().isInsertInstallment()) {
+                repaymetsForAdjust++;
+            } else if (loanTermVariations.getTermType().isDeleteInstallment()) {
+                repaymetsForAdjust--;
+            }
+        }
+        return repaymetsForAdjust;
+    }
+
+    public int fetchNumberOfInstallmensAfterExceptions() {
+        if(this.repaymentScheduleInstallments.size() > 0){
+            int numberOfInstallments = 0;
+            for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+                if(!installment.isRecalculatedInterestComponent()){
+                    numberOfInstallments++; 
+                }
+            }
+            return numberOfInstallments;
+        }
+        return this.repaymentScheduleDetail().getNumberOfRepayments() + adjustNumberOfRepayments();
+    }
+
+    public void setExpectedFirstRepaymentOnDate(Date expectedFirstRepaymentOnDate) {
+        this.expectedFirstRepaymentOnDate = expectedFirstRepaymentOnDate;
+    }
+    
+    /*
+ 	* get the next repayment date for rescheduling at the time of disbursement
+ 	*/
+     public LocalDate getNextPossibleRepaymentDateForRescheduling(){
+     	Set<LoanDisbursementDetails> loanDisbursementDetails = this.disbursementDetails;
+     	LocalDate nextRepaymentDate = new LocalDate();
+     	for(LoanDisbursementDetails loanDisbursementDetail : loanDisbursementDetails){
+     		if(loanDisbursementDetail.actualDisbursementDate() == null){
+     			for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+ 		            if (installment.getDueDate().isEqual(loanDisbursementDetail.expectedDisbursementDateAsLocalDate()) || 
+ 		            		installment.getDueDate().isAfter(loanDisbursementDetail.expectedDisbursementDateAsLocalDate()) 
+ 		            		&& installment.isNotFullyPaidOff()) {
+ 		            	nextRepaymentDate = installment.getDueDate();
+ 		                break;
+ 		            }   
+ 		        }
+ 			 break;
+     		}
+     	}
+ 		return nextRepaymentDate;
+     }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
new file mode 100755
index 0000000..3dfbc6a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.joda.time.LocalDate;
+
+public interface LoanAccountDomainService {
+
+    LoanTransaction makeRepayment(Loan loan, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
+            BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId,
+            final boolean isRecoveryRepayment, boolean isAccountTransfer, HolidayDetailDTO holidatDetailDto, Boolean isHolidayValidationDone);
+
+    LoanTransaction makeRefund(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
+            BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId);
+
+    void reverseTransfer(LoanTransaction loanTransaction);
+
+    LoanTransaction makeChargePayment(Loan loan, Long chargeId, LocalDate transactionDate, BigDecimal transactionAmount,
+            PaymentDetail paymentDetail, String noteText, String txnExternalId, Integer transactionType, Integer installmentNumber);
+
+    LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount,
+            PaymentDetail paymentDetail, String noteText, String txnExternalId);
+
+    LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
+            BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId);
+
+    /**
+     * This method is to recalculate and accrue the income till the last accrued
+     * date. this method is used when the schedule changes due to interest
+     * recalculation
+     * 
+     * @param loan
+     */
+    void recalculateAccruals(Loan loan);
+
+    void saveLoanWithDataIntegrityViolationChecks(Loan loan);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
new file mode 100755
index 0000000..3206610
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -0,0 +1,561 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
+import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
+import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
+import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+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 LoanAccountDomainServiceJpa implements LoanAccountDomainService {
+
+    private final LoanAssembler loanAccountAssembler;
+    private final LoanRepository loanRepository;
+    private final LoanTransactionRepository loanTransactionRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final HolidayRepository holidayRepository;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+    private final NoteRepository noteRepository;
+    private final AccountTransferRepository accountTransferRepository;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
+    private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
+    private final LoanAccrualPlatformService loanAccrualPlatformService;
+    private final PlatformSecurityContext context;
+    private final BusinessEventNotifierService businessEventNotifierService;
+    private final LoanUtilService loanUtilService;
+
+    @Autowired
+    public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository,
+            final LoanTransactionRepository loanTransactionRepository, final NoteRepository noteRepository,
+            final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository,
+            final WorkingDaysRepositoryWrapper workingDaysRepository,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService,
+            final AccountTransferRepository accountTransferRepository,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
+            final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
+            final LoanAccrualPlatformService loanAccrualPlatformService, final PlatformSecurityContext context,
+            final BusinessEventNotifierService businessEventNotifierService, final LoanUtilService loanUtilService) {
+        this.loanAccountAssembler = loanAccountAssembler;
+        this.loanRepository = loanRepository;
+        this.loanTransactionRepository = loanTransactionRepository;
+        this.noteRepository = noteRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.holidayRepository = holidayRepository;
+        this.workingDaysRepository = workingDaysRepository;
+        this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+        this.accountTransferRepository = accountTransferRepository;
+        this.applicationCurrencyRepository = applicationCurrencyRepository;
+        this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
+        this.loanAccrualPlatformService = loanAccrualPlatformService;
+        this.context = context;
+        this.businessEventNotifierService = businessEventNotifierService;
+        this.loanUtilService = loanUtilService;
+    }
+
+    @Transactional
+    @Override
+    public LoanTransaction makeRepayment(final Loan loan, final CommandProcessingResultBuilder builderResult,
+            final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
+            final String txnExternalId, final boolean isRecoveryRepayment, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto,
+            Boolean isHolidayValidationDone) {
+        AppUser currentUser = getAppUserIfPresent();
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        // TODO: Is it required to validate transaction date with meeting dates
+        // if repayments is synced with meeting?
+        /*
+         * if(loan.isSyncDisbursementWithMeeting()){ // validate actual
+         * disbursement date against meeting date CalendarInstance
+         * calendarInstance =
+         * this.calendarInstanceRepository.findCalendarInstaneByLoanId
+         * (loan.getId(), CalendarEntityType.LOANS.getValue());
+         * this.loanEventApiJsonValidator
+         * .validateRepaymentDateWithMeetingDate(transactionDate,
+         * calendarInstance); }
+         */
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
+        LoanTransaction newRepaymentTransaction = null;
+        final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant();
+        if (isRecoveryRepayment) {
+            newRepaymentTransaction = LoanTransaction.recoveryRepayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate,
+                    txnExternalId, currentDateTime, currentUser);
+        } else {
+            newRepaymentTransaction = LoanTransaction.repayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate,
+                    txnExternalId, currentDateTime, currentUser);
+        }
+
+        LocalDate recalculateFrom = null;
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            recalculateFrom = transactionDate;
+        }
+        final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom,
+                holidayDetailDto);
+
+        final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
+                defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
+                scheduleGeneratorDTO, currentUser, isHolidayValidationDone);
+
+        saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
+
+        /***
+         * TODO Vishwas Batch save is giving me a
+         * HibernateOptimisticLockingFailureException, looping and saving for
+         * the time being, not a major issue for now as this loop is entered
+         * only in edge cases (when a payment is made before the latest payment
+         * recorded against the loan)
+         ***/
+
+        saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+        if (changedTransactionDetail != null) {
+            for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                // update loan with references to the newly created transactions
+                loan.getLoanTransactions().add(mapEntry.getValue());
+                updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+            }
+        }
+
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText);
+            this.noteRepository.save(note);
+        }
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+
+        recalculateAccruals(loan);
+
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRepaymentTransaction));
+
+        builderResult.withEntityId(newRepaymentTransaction.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()); //
+
+        return newRepaymentTransaction;
+    }
+
+    private void saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction) {
+        try {
+            this.loanTransactionRepository.save(newRepaymentTransaction);
+        } catch (DataIntegrityViolationException e) {
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+            if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
+                baseDataValidator.reset().parameter("externalId").value(newRepaymentTransaction.getExternalId())
+                        .failWithCode("value.must.be.unique");
+            }
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                    "Validation errors exist.", dataValidationErrors); }
+        }
+    }
+
+    private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
+        try {
+            List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
+            for (LoanRepaymentScheduleInstallment installment : installments) {
+                if (installment.getId() == null) {
+                    this.repaymentScheduleInstallmentRepository.save(installment);
+                }
+            }
+            this.loanRepository.saveAndFlush(loan);
+        } catch (final DataIntegrityViolationException e) {
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+            if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
+                baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
+            }
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                    "Validation errors exist.", dataValidationErrors); }
+        }
+    }
+
+    @Override
+    public void saveLoanWithDataIntegrityViolationChecks(final Loan loan) {
+        try {
+            List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
+            for (LoanRepaymentScheduleInstallment installment : installments) {
+                if (installment.getId() == null) {
+                    this.repaymentScheduleInstallmentRepository.save(installment);
+                }
+            }
+            this.loanRepository.save(loan);
+        } catch (final DataIntegrityViolationException e) {
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+            if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
+                baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
+            }
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                    "Validation errors exist.", dataValidationErrors); }
+        }
+    }
+
+    @Override
+    @Transactional
+    public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate,
+            final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final String txnExternalId,
+            final Integer transactionType, Integer installmentNumber) {
+        AppUser currentUser = getAppUserIfPresent();
+        boolean isAccountTransfer = true;
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        final Money paymentAmout = Money.of(loan.getCurrency(), transactionAmount);
+        final LoanTransactionType loanTransactionType = LoanTransactionType.fromInt(transactionType);
+
+        final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail,
+                transactionDate, txnExternalId, loanTransactionType, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+
+        if (loanTransactionType.isRepaymentAtDisbursement()) {
+            loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds);
+        } else {
+            final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
+            final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
+                    transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue());
+            final WorkingDays workingDays = this.workingDaysRepository.findOne();
+            final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
+            final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+            HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
+                    allowTransactionsOnNonWorkingDay);
+
+            loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
+                    holidayDetailDTO, newPaymentTransaction, installmentNumber);
+        }
+        saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction);
+        saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note note = Note.loanTransactionNote(loan, newPaymentTransaction, noteText);
+            this.noteRepository.save(note);
+        }
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        recalculateAccruals(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newPaymentTransaction));
+        return newPaymentTransaction;
+    }
+
+    private void postJournalEntries(final Loan loanAccount, final List<Long> existingTransactionIds,
+            final List<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
+
+        final MonetaryCurrency currency = loanAccount.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
+
+        final Map<String, Object> accountingBridgeData = loanAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
+                existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
+    }
+
+    private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
+        final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
+        return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
+    }
+
+    private void checkClientOrGroupActive(final Loan loan) {
+        final Client client = loan.client();
+        if (client != null) {
+            if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); }
+        }
+        final Group group = loan.group();
+        if (group != null) {
+            if (group.isNotActive()) { throw new GroupNotActiveException(group.getId()); }
+        }
+    }
+
+    @Override
+    public LoanTransaction makeRefund(final Long accountId, final CommandProcessingResultBuilder builderResult,
+            final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
+            final String txnExternalId) {
+        AppUser currentUser = getAppUserIfPresent();
+        boolean isAccountTransfer = true;
+        final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REFUND,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
+        final LoanTransaction newRefundTransaction = LoanTransaction.refund(loan.getOffice(), refundAmount, paymentDetail, transactionDate,
+                txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
+        final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
+                transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue());
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+        final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
+
+        loan.makeRefund(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
+                allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
+
+        saveLoanTransactionWithDataIntegrityViolationChecks(newRefundTransaction);
+        this.loanRepository.save(loan);
+
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note note = Note.loanTransactionNote(loan, newRefundTransaction, noteText);
+            this.noteRepository.save(note);
+        }
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REFUND,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRefundTransaction));
+        builderResult.withEntityId(newRefundTransaction.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()); //
+
+        return newRefundTransaction;
+    }
+
+    @Transactional
+    @Override
+    public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount,
+            final PaymentDetail paymentDetail, final String noteText, final String txnExternalId) {
+        AppUser currentUser = getAppUserIfPresent();
+        final Loan loan = this.loanAccountAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        boolean isAccountTransfer = true;
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+        final Money amount = Money.of(loan.getCurrency(), transactionAmount);
+        LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail, transactionDate,
+                txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        disbursementTransaction.updateLoan(loan);
+        loan.getLoanTransactions().add(disbursementTransaction);
+        saveLoanTransactionWithDataIntegrityViolationChecks(disbursementTransaction);
+        saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note note = Note.loanTransactionNote(loan, disbursementTransaction, noteText);
+            this.noteRepository.save(note);
+        }
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        return disbursementTransaction;
+    }
+
+    @Override
+    public void reverseTransfer(final LoanTransaction loanTransaction) {
+        loanTransaction.reverse();
+        saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService
+     * #recalculateAccruals(org.apache.fineract.portfolio.loanaccount.domain.Loan)
+     */
+    @Override
+    public void recalculateAccruals(Loan loan) {
+        LocalDate accruedTill = loan.getAccruedTill();
+        if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !loan.repaymentScheduleDetail().isInterestRecalculationEnabled()
+                || accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; }
+        Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = new ArrayList<>();
+        List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
+        Long loanId = loan.getId();
+        Long officeId = loan.getOfficeId();
+        LocalDate accrualStartDate = null;
+        PeriodFrequencyType repaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType();
+        Integer repayEvery = loan.repaymentScheduleDetail().getRepayEvery();
+        LocalDate interestCalculatedFrom = loan.getInterestChargedFromDate();
+        Long loanProductId = loan.productId();
+        MonetaryCurrency currency = loan.getCurrency();
+        ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+        CurrencyData currencyData = applicationCurrency.toData();
+        Set<LoanCharge> loanCharges = loan.charges();
+
+        for (LoanRepaymentScheduleInstallment installment : installments) {
+            if (!accruedTill.isBefore(installment.getDueDate())
+                    || (accruedTill.isAfter(installment.getFromDate()) && !accruedTill.isAfter(installment.getDueDate()))) {
+                BigDecimal dueDateFeeIncome = BigDecimal.ZERO;
+                BigDecimal dueDatePenaltyIncome = BigDecimal.ZERO;
+                LocalDate chargesTillDate = installment.getDueDate();
+                if (!accruedTill.isAfter(installment.getDueDate())) {
+                    chargesTillDate = accruedTill;
+                }
+
+                for (final LoanCharge loanCharge : loanCharges) {
+                    if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate)) {
+                        if (loanCharge.isFeeCharge()) {
+                            dueDateFeeIncome = dueDateFeeIncome.add(loanCharge.amount());
+                        } else if (loanCharge.isPenaltyCharge()) {
+                            dueDatePenaltyIncome = dueDatePenaltyIncome.add(loanCharge.amount());
+                        }
+                    }
+                }
+                LoanScheduleAccrualData accrualData = new LoanScheduleAccrualData(loanId, officeId, installment.getInstallmentNumber(),
+                        accrualStartDate, repaymentFrequency, repayEvery, installment.getDueDate(), installment.getFromDate(),
+                        installment.getId(), loanProductId, installment.getInterestCharged(currency).getAmount(), installment
+                                .getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(),
+                        installment.getInterestAccrued(currency).getAmount(), installment.getFeeAccrued(currency).getAmount(), installment
+                                .getPenaltyAccrued(currency).getAmount(), currencyData, interestCalculatedFrom, installment
+                                .getInterestWaived(currency).getAmount());
+                loanScheduleAccrualDatas.add(accrualData);
+
+            }
+        }
+
+        if (!loanScheduleAccrualDatas.isEmpty()) {
+            String error = this.loanAccrualPlatformService.addPeriodicAccruals(accruedTill, loanScheduleAccrualDatas);
+            if (error.length() > 0) {
+                String globalisationMessageCode = "error.msg.accrual.exception";
+                throw new GeneralPlatformDomainRuleException(globalisationMessageCode, error, error);
+            }
+        }
+    }
+
+    private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
+        final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
+        if (transferTransaction != null) {
+            transferTransaction.updateToLoanTransaction(newLoanTransaction);
+            this.accountTransferRepository.save(transferTransaction);
+        }
+    }
+
+    private AppUser getAppUserIfPresent() {
+        AppUser user = null;
+        if (this.context != null) {
+            user = this.context.getAuthenticatedUserIfPresent();
+        }
+        return user;
+    }
+
+    @Override
+    public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
+            BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) {
+        final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REFUND,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+        AppUser currentUser = getAppUserIfPresent();
+
+        final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
+        final LoanTransaction newRefundTransaction = LoanTransaction.refundForActiveLoan(loan.getOffice(), refundAmount, paymentDetail,
+                transactionDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
+        final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
+                transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue());
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+        final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
+
+        loan.makeRefundForActiveLoan(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds,
+                existingReversedTransactionIds, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
+
+        this.loanTransactionRepository.save(newRefundTransaction);
+        this.loanRepository.save(loan);
+
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note note = Note.loanTransactionNote(loan, newRefundTransaction, noteText);
+            this.noteRepository.save(note);
+        }
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
+        recalculateAccruals(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REFUND,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRefundTransaction));
+
+        builderResult.withEntityId(newRefundTransaction.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()); //
+
+        return newRefundTransaction;
+    }
+
+    private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
+        Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
+        map.put(entityEvent, entity);
+        return map;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
new file mode 100755
index 0000000..d76cf08
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
@@ -0,0 +1,989 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
+import org.apache.fineract.portfolio.loanaccount.command.LoanChargeCommand;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_charge")
+public class LoanCharge extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_id", referencedColumnName = "id", nullable = false)
+    private Loan loan;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "charge_id", referencedColumnName = "id", nullable = false)
+    private Charge charge;
+
+    @Column(name = "charge_time_enum", nullable = false)
+    private Integer chargeTime;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "due_for_collection_as_of_date")
+    private Date dueDate;
+
+    @Column(name = "charge_calculation_enum")
+    private Integer chargeCalculation;
+
+    @Column(name = "charge_payment_mode_enum")
+    private Integer chargePaymentMode;
+
+    @Column(name = "calculation_percentage", scale = 6, precision = 19, nullable = true)
+    private BigDecimal percentage;
+
+    @Column(name = "calculation_on_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountPercentageAppliedTo;
+
+    @Column(name = "charge_amount_or_percentage", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amountOrPercentage;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountPaid;
+
+    @Column(name = "amount_waived_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountWaived;
+
+    @Column(name = "amount_writtenoff_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountWrittenOff;
+
+    @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amountOutstanding;
+
+    @Column(name = "is_penalty", nullable = false)
+    private boolean penaltyCharge = false;
+
+    @Column(name = "is_paid_derived", nullable = false)
+    private boolean paid = false;
+
+    @Column(name = "waived", nullable = false)
+    private boolean waived = false;
+
+    @Column(name = "min_cap", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minCap;
+
+    @Column(name = "max_cap", scale = 6, precision = 19, nullable = true)
+    private BigDecimal maxCap;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loancharge", orphanRemoval = true)
+    private final Set<LoanInstallmentCharge> loanInstallmentCharge = new HashSet<>();
+
+    @Column(name = "is_active", nullable = false)
+    private boolean active = true;
+
+    @OneToOne(mappedBy = "loancharge", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY)
+    private LoanOverdueInstallmentCharge overdueInstallmentCharge;
+
+    @OneToOne(mappedBy = "loancharge", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY)
+    private LoanTrancheDisbursementCharge loanTrancheDisbursementCharge;
+
+    public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
+        final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
+        return createNewFromJson(loan, chargeDefinition, command, dueDate);
+    }
+
+    public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command,
+            final LocalDate dueDate) {
+        final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
+
+        final ChargeTimeType chargeTime = null;
+        final ChargeCalculationType chargeCalculation = null;
+        final ChargePaymentMode chargePaymentMode = null;
+        BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
+        switch (ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation())) {
+            case PERCENT_OF_AMOUNT:
+                if (command.hasParameter("principal")) {
+                    amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal");
+                } else {
+                    amountPercentageAppliedTo = loan.getPrincpal().getAmount();
+                }
+            break;
+            case PERCENT_OF_AMOUNT_AND_INTEREST:
+                if (command.hasParameter("principal") && command.hasParameter("interest")) {
+                    amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal").add(
+                            command.bigDecimalValueOfParameterNamed("interest"));
+                } else {
+                    amountPercentageAppliedTo = loan.getPrincpal().getAmount().add(loan.getTotalInterest());
+                }
+            break;
+            case PERCENT_OF_INTEREST:
+                if (command.hasParameter("interest")) {
+                    amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("interest");
+                } else {
+                    amountPercentageAppliedTo = loan.getTotalInterest();
+                }
+            break;
+            default:
+            break;
+        }
+
+        BigDecimal loanCharge = BigDecimal.ZERO;
+        if (ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType()).equals(ChargeTimeType.INSTALMENT_FEE)) {
+            BigDecimal percentage = amount;
+            if (percentage == null) {
+                percentage = chargeDefinition.getAmount();
+            }
+            loanCharge = loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation()),
+                    percentage);
+        }
+
+        return new LoanCharge(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate,
+                chargePaymentMode, null, loanCharge);
+    }
+
+    /*
+     * loanPrincipal is required for charges that are percentage based
+     */
+    public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
+            final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
+            final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments) {
+        return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode,
+                numberOfRepayments, BigDecimal.ZERO);
+    }
+
+    protected LoanCharge() {
+        //
+    }
+
+    public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
+            final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
+            final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final BigDecimal loanCharge) {
+        this.loan = loan;
+        this.charge = chargeDefinition;
+        this.penaltyCharge = chargeDefinition.isPenalty();
+        this.minCap = chargeDefinition.getMinCap();
+        this.maxCap = chargeDefinition.getMaxCap();
+
+        this.chargeTime = chargeDefinition.getChargeTimeType();
+        if (chargeTime != null) {
+            this.chargeTime = chargeTime.getValue();
+        }
+
+        if (ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE)
+                || ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.OVERDUE_INSTALLMENT)) {
+
+            if (dueDate == null) {
+                final String defaultUserMessage = "Loan charge is missing due date.";
+                throw new LoanChargeWithoutMandatoryFieldException("loanCharge", "dueDate", defaultUserMessage, chargeDefinition.getId(),
+                        chargeDefinition.getName());
+            }
+
+            this.dueDate = dueDate.toDate();
+        } else {
+            this.dueDate = null;
+        }
+
+        this.chargeCalculation = chargeDefinition.getChargeCalculation();
+        if (chargeCalculation != null) {
+            this.chargeCalculation = chargeCalculation.getValue();
+        }
+
+        BigDecimal chargeAmount = chargeDefinition.getAmount();
+        if (amount != null) {
+            chargeAmount = amount;
+        }
+
+        this.chargePaymentMode = chargeDefinition.getChargePaymentMode();
+        if (chargePaymentMode != null) {
+            this.chargePaymentMode = chargePaymentMode.getValue();
+        }
+        populateDerivedFields(loanPrincipal, chargeAmount, numberOfRepayments, loanCharge);
+        this.paid = determineIfFullyPaid();
+    }
+
+    private void populateDerivedFields(final BigDecimal amountPercentageAppliedTo, final BigDecimal chargeAmount,
+            Integer numberOfRepayments, BigDecimal loanCharge) {
+
+        switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
+            case INVALID:
+                this.percentage = null;
+                this.amount = null;
+                this.amountPercentageAppliedTo = null;
+                this.amountPaid = null;
+                this.amountOutstanding = BigDecimal.ZERO;
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+            case FLAT:
+                this.percentage = null;
+                this.amountPercentageAppliedTo = null;
+                this.amountPaid = null;
+                if (isInstalmentFee()) {
+                    if (numberOfRepayments == null) {
+                        numberOfRepayments = this.loan.fetchNumberOfInstallmensAfterExceptions();
+                    }
+                    this.amount = chargeAmount.multiply(BigDecimal.valueOf(numberOfRepayments));
+                } else {
+                    this.amount = chargeAmount;
+                }
+                this.amountOutstanding = this.amount;
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+            case PERCENT_OF_AMOUNT:
+            case PERCENT_OF_AMOUNT_AND_INTEREST:
+            case PERCENT_OF_INTEREST:
+            case PERCENT_OF_DISBURSEMENT_AMOUNT:
+                this.percentage = chargeAmount;
+                this.amountPercentageAppliedTo = amountPercentageAppliedTo;
+                if (loanCharge.compareTo(BigDecimal.ZERO) == 0) {
+                    loanCharge = percentageOf(this.amountPercentageAppliedTo);
+                }
+                this.amount = minimumAndMaximumCap(loanCharge);
+                this.amountPaid = null;
+                this.amountOutstanding = calculateOutstanding();
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+        }
+        this.amountOrPercentage = chargeAmount;
+        if (this.loan != null && isInstalmentFee()) {
+            updateInstallmentCharges();
+        }
+    }
+
+    public void markAsFullyPaid() {
+        this.amountPaid = this.amount;
+        this.amountOutstanding = BigDecimal.ZERO;
+        this.paid = true;
+    }
+
+    public boolean isFullyPaid() {
+        return this.paid;
+    }
+
+    public void resetToOriginal(final MonetaryCurrency currency) {
+        this.amountPaid = BigDecimal.ZERO;
+        this.amountWaived = BigDecimal.ZERO;
+        this.amountWrittenOff = BigDecimal.ZERO;
+        this.amountOutstanding = calculateAmountOutstanding(currency);
+        this.paid = false;
+        this.waived = false;
+        for (final LoanInstallmentCharge installmentCharge : this.loanInstallmentCharge) {
+            installmentCharge.resetToOriginal(currency);
+        }
+    }
+
+    public void resetPaidAmount(final MonetaryCurrency currency) {
+        this.amountPaid = BigDecimal.ZERO;
+        this.amountOutstanding = calculateAmountOutstanding(currency);
+        this.paid = false;
+        for (final LoanInstallmentCharge installmentCharge : this.loanInstallmentCharge) {
+            installmentCharge.resetPaidAmount(currency);
+        }
+    }
+
+    public Money waive(final MonetaryCurrency currency, final Integer loanInstallmentNumber) {
+        if (isInstalmentFee()) {
+            final LoanInstallmentCharge chargePerInstallment = getInstallmentLoanCharge(loanInstallmentNumber);
+            final Money amountWaived = chargePerInstallment.waive(currency);
+            if (this.amountWaived == null) {
+                this.amountWaived = BigDecimal.ZERO;
+            }
+            this.amountWaived = this.amountWaived.add(amountWaived.getAmount());
+            this.amountOutstanding = this.amountOutstanding.subtract(amountWaived.getAmount());
+            if (determineIfFullyPaid()) {
+                this.paid = false;
+                this.waived = true;
+            }
+            return amountWaived;
+        }
+        this.amountWaived = this.amountOutstanding;
+        this.amountOutstanding = BigDecimal.ZERO;
+        this.paid = false;
+        this.waived = true;
+        return getAmountWaived(currency);
+
+    }
+
+    public BigDecimal getAmountPercentageAppliedTo() {
+        return this.amountPercentageAppliedTo;
+    }
+
+    private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
+        return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
+    }
+
+    public void update(final Loan loan) {
+        this.loan = loan;
+    }
+
+    public void update(final BigDecimal amount, final LocalDate dueDate, final BigDecimal loanPrincipal, Integer numberOfRepayments,
+            BigDecimal loanCharge) {
+        if (dueDate != null) {
+            this.dueDate = dueDate.toDate();
+        }
+
+        if (amount != null) {
+            switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
+                case INVALID:
+                break;
+                case FLAT:
+                    if (isInstalmentFee()) {
+                        if (numberOfRepayments == null) {
+                            numberOfRepayments = this.loan.fetchNumberOfInstallmensAfterExceptions();
+                        }
+                        this.amount = amount.multiply(BigDecimal.valueOf(numberOfRepayments));
+                    } else {
+                        this.amount = amount;
+                    }
+                break;
+                case PERCENT_OF_AMOUNT:
+                case PERCENT_OF_AMOUNT_AND_INTEREST:
+                case PERCENT_OF_INTEREST:
+                case PERCENT_OF_DISBURSEMENT_AMOUNT:
+                    this.percentage = amount;
+                    this.amountPercentageAppliedTo = loanPrincipal;
+                    if (loanCharge.compareTo(BigDecimal.ZERO) == 0) {
+                        loanCharge = percentageOf(this.amountPercentageAppliedTo);
+                    }
+                    this.amount = minimumAndMaximumCap(loanCharge);
+                break;
+            }
+            this.amountOrPercentage = amount;
+            this.amountOutstanding = calculateOutstanding();
+            if (this.loan != null && isInstalmentFee()) {
+                updateInstallmentCharges();
+            }
+        }
+    }
+
+    public void update(final BigDecimal amount, final LocalDate dueDate, final Integer numberOfRepayments) {
+        BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
+        if (this.loan != null) {
+            switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
+                case PERCENT_OF_AMOUNT:
+                    amountPercentageAppliedTo = this.loan.getPrincpal().getAmount();
+                break;
+                case PERCENT_OF_AMOUNT_AND_INTEREST:
+                    amountPercentageAppliedTo = this.loan.getPrincpal().getAmount().add(this.loan.getTotalInterest());
+                break;
+                case PERCENT_OF_INTEREST:
+                    amountPercentageAppliedTo = this.loan.getTotalInterest();
+                break;
+                case PERCENT_OF_DISBURSEMENT_AMOUNT:
+                    LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = this.loanTrancheDisbursementCharge;
+                    amountPercentageAppliedTo = loanTrancheDisbursementCharge.getloanDisbursementDetails().principal();
+                break;
+                default:
+                break;
+            }
+        }
+        update(amount, dueDate, amountPercentageAppliedTo, numberOfRepayments, BigDecimal.ZERO);
+    }
+
+    public Map<String, Object> update(final JsonCommand command, final BigDecimal amount) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+
+        final String dueDateParamName = "dueDate";
+        if (command.isChangeInLocalDateParameterNamed(dueDateParamName, getDueLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(dueDateParamName);
+            actualChanges.put(dueDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(dueDateParamName);
+            this.dueDate = newValue.toDate();
+        }
+
+        final String amountParamName = "amount";
+        if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
+            BigDecimal loanCharge = null;
+            actualChanges.put(amountParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
+                case INVALID:
+                break;
+                case FLAT:
+                    if (isInstalmentFee()) {
+                        this.amount = newValue.multiply(BigDecimal.valueOf(this.loan.fetchNumberOfInstallmensAfterExceptions()));
+                    } else {
+                        this.amount = newValue;
+                    }
+                    this.amountOutstanding = calculateOutstanding();
+                break;
+                case PERCENT_OF_AMOUNT:
+                case PERCENT_OF_AMOUNT_AND_INTEREST:
+                case PERCENT_OF_INTEREST:
+                case PERCENT_OF_DISBURSEMENT_AMOUNT:
+                    this.percentage = newValue;
+                    this.amountPercentageAppliedTo = amount;
+                    loanCharge = BigDecimal.ZERO;
+                    if (isInstalmentFee()) {
+                        loanCharge = this.loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(this.chargeCalculation),
+                                this.percentage);
+                    }
+                    if (loanCharge.compareTo(BigDecimal.ZERO) == 0) {
+                        loanCharge = percentageOf(this.amountPercentageAppliedTo);
+                    }
+                    this.amount = minimumAndMaximumCap(loanCharge);
+                    this.amountOutstanding = calculateOutstanding();
+                break;
+            }
+            this.amountOrPercentage = newValue;
+            if (isInstalmentFee()) {
+                updateInstallmentCharges();
+            }
+        }
+        return actualChanges;
+    }
+
+    private void updateInstallmentCharges() {
+        final Collection<LoanInstallmentCharge> remove = new HashSet<>();
+        final Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
+        if (this.loanInstallmentCharge.isEmpty()) {
+            this.loanInstallmentCharge.addAll(chargePerInstallments);
+        } else {
+            int index = 0;
+            final LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
+            final LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
+            for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) {
+                if (index == loanChargePerInstallmentArray.length) {
+                    remove.add(chargePerInstallment);
+                    chargePerInstallment.updateInstallment(null);
+                } else {
+                    chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
+                }
+            }
+            this.loanInstallmentCharge.removeAll(remove);
+            while (index < loanChargePerInstallmentArray.length - 1) {
+                this.loanInstallmentCharge.add(loanChargePerInstallmentArray[index++]);
+            }
+        }
+    }
+
+    public boolean isDueAtDisbursement() {
+        return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT)
+                || ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
+    }
+
+    public boolean isSpecifiedDueDate() {
+        return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE);
+    }
+
+    public boolean isInstalmentFee() {
+        return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.INSTALMENT_FEE);
+    }
+
+    public boolean isOverdueInstallmentCharge() {
+        return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.OVERDUE_INSTALLMENT);
+    }
+
+    private static boolean isGreaterThanZero(final BigDecimal value) {
+        return value.compareTo(BigDecimal.ZERO) == 1;
+    }
+
+    public LoanChargeCommand toCommand() {
+        return new LoanChargeCommand(getId(), this.charge.getId(), this.amount, this.chargeTime, this.chargeCalculation, getDueLocalDate());
+    }
+
+    public LocalDate getDueLocalDate() {
+        LocalDate dueDate = null;
+        if (this.dueDate != null) {
+            dueDate = new LocalDate(this.dueDate);
+        }
+        return dueDate;
+    }
+
+    private boolean determineIfFullyPaid() {
+        if (this.amount == null) { return true; }
+        return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
+    }
+
+    private BigDecimal calculateOutstanding() {
+        if (this.amount == null) { return null; }
+        BigDecimal amountPaidLocal = BigDecimal.ZERO;
+        if (this.amountPaid != null) {
+            amountPaidLocal = this.amountPaid;
+        }
+
+        BigDecimal amountWaivedLocal = BigDecimal.ZERO;
+        if (this.amountWaived != null) {
+            amountWaivedLocal = this.amountWaived;
+        }
+
+        BigDecimal amountWrittenOffLocal = BigDecimal.ZERO;
+        if (this.amountWrittenOff != null) {
+            amountWrittenOffLocal = this.amountWrittenOff;
+        }
+
+        final BigDecimal totalAccountedFor = amountPaidLocal.add(amountWaivedLocal).add(amountWrittenOffLocal);
+
+        return this.amount.subtract(totalAccountedFor);
+    }
+
+    public BigDecimal percentageOf(final BigDecimal value) {
+        return percentageOf(value, this.percentage);
+    }
+
+    public static BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage) {
+
+        BigDecimal percentageOf = BigDecimal.ZERO;
+
+        if (isGreaterThanZero(value)) {
+            final MathContext mc = new MathContext(8, MoneyHelper.getRoundingMode());
+            final BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100l), mc);
+            percentageOf = value.multiply(multiplicand, mc);
+        }
+        return percentageOf;
+    }
+
+    /**
+     * @param percentageOf
+     * @returns a minimum cap or maximum cap set on charges if the criteria fits
+     *          else it returns the percentageOf if the amount is within min and
+     *          max cap
+     */
+    private BigDecimal minimumAndMaximumCap(final BigDecimal percentageOf) {
+        BigDecimal minMaxCap = BigDecimal.ZERO;
+        if (this.minCap != null) {
+            final int minimumCap = percentageOf.compareTo(this.minCap);
+            if (minimumCap == -1) {
+                minMaxCap = this.minCap;
+                return minMaxCap;
+            }
+        }
+        if (this.maxCap != null) {
+            final int maximumCap = percentageOf.compareTo(this.maxCap);
+            if (maximumCap == 1) {
+                minMaxCap = this.maxCap;
+                return minMaxCap;
+            }
+        }
+        minMaxCap = percentageOf;
+        // this will round the amount value
+        if (this.loan != null && minMaxCap != null) {
+            minMaxCap = Money.of(this.loan.getCurrency(), minMaxCap).getAmount();
+        }
+        return minMaxCap;
+    }
+
+    public BigDecimal amount() {
+        return this.amount;
+    }
+
+    public BigDecimal amountOutstanding() {
+        return this.amountOutstanding;
+    }
+    
+    public Money getAmountOutstanding(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountOutstanding);
+    }
+
+    public boolean hasNotLoanIdentifiedBy(final Long loanId) {
+        return !hasLoanIdentifiedBy(loanId);
+    }
+
+    public boolean hasLoanIdentifiedBy(final Long loanId) {
+        return this.loan.hasIdentifyOf(loanId);
+    }
+
+    public boolean isDueForCollectionFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) {
+        final LocalDate dueDate = getDueLocalDate();
+        return occursOnDayFromAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate);
+    }
+
+    private boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive,
+            final LocalDate target) {
+        return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive);
+    }
+
+    public boolean isFeeCharge() {
+        return !this.penaltyCharge;
+    }
+
+    public boolean isPenaltyCharge() {
+        return this.penaltyCharge;
+    }
+
+    public boolean isNotFullyPaid() {
+        return !isPaid();
+    }
+    
+    public boolean isChargePending(){
+        return isNotFullyPaid() && !isWaived();
+    }
+
+    public boolean isPaid() {
+        return this.paid;
+    }
+
+    public boolean isWaived() {
+        return this.waived;
+    }
+
+    public BigDecimal getMinCap() {
+        return this.minCap;
+    }
+
+    public BigDecimal getMaxCap() {
+        return this.maxCap;
+    }
+
+    public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) {
+
+        final Money amountWaivedOrWrittenOff = getAmountWaived(currency).plus(getAmountWrittenOff(currency));
+        return Money.of(currency, this.amountPaid).plus(amountWaivedOrWrittenOff).isGreaterThanZero();
+    }
+
+    public Money getAmount(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amount);
+    }
+
+    public Money getAmountPaid(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountPaid);
+    }
+
+    public Money getAmountWaived(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountWaived);
+    }
+
+    public Money getAmountWrittenOff(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountWrittenOff);
+    }
+
+    /**
+     * @param feeAmount
+     *            TODO
+     * @param processAmount
+     *            Amount used to pay off this charge
+     * @return Actual amount paid on this charge
+     */
+    public Money updatePaidAmountBy(final Money incrementBy, final Integer installmentNumber, final Money feeAmount) {
+        Money processAmount = Money.zero(incrementBy.getCurrency());
+        if (isInstalmentFee()) {
+            if (installmentNumber == null) {
+                processAmount = getUnpaidInstallmentLoanCharge().updatePaidAmountBy(incrementBy, feeAmount);
+            } else {
+                processAmount = getInstallmentLoanCharge(installmentNumber).updatePaidAmountBy(incrementBy, feeAmount);
+            }
+        } else {
+            processAmount = incrementBy;
+        }
+        Money amountPaidToDate = Money.of(processAmount.getCurrency(), this.amountPaid);
+        final Money amountOutstanding = Money.of(processAmount.getCurrency(), this.amountOutstanding);
+
+        Money amountPaidOnThisCharge = Money.zero(processAmount.getCurrency());
+        if (processAmount.isGreaterThanOrEqualTo(amountOutstanding)) {
+            amountPaidOnThisCharge = amountOutstanding;
+            amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
+            this.amountPaid = amountPaidToDate.getAmount();
+            this.amountOutstanding = BigDecimal.ZERO;
+            Money waivedAmount = getAmountWaived(processAmount.getCurrency());
+            if (waivedAmount.isGreaterThanZero()) {
+                this.waived = true;
+            } else {
+                this.paid = true;
+            }
+
+        } else {
+            amountPaidOnThisCharge = processAmount;
+            amountPaidToDate = amountPaidToDate.plus(processAmount);
+            this.amountPaid = amountPaidToDate.getAmount();
+            this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
+        }
+        return amountPaidOnThisCharge;
+    }
+
+    public String name() {
+        return this.charge.getName();
+    }
+
+    public String currencyCode() {
+        return this.charge.getCurrencyCode();
+    }
+
+    public Charge getCharge() {
+        return this.charge;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final LoanCharge rhs = (LoanCharge) obj;
+        return new EqualsBuilder().appendSuper(super.equals(obj)) //
+                .append(getId(), rhs.getId()) //
+                .append(this.charge.getId(), rhs.charge.getId()) //
+                .append(this.amount, rhs.amount) //
+                .append(getDueLocalDate(), rhs.getDueLocalDate()) //
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return 1;
+        /*
+         * return new HashCodeBuilder(3, 5) // .append(getId()) //
+         * .append(this.charge.getId()) //
+         * .append(this.amount).append(getDueLocalDate()) // .toHashCode();
+         */
+    }
+
+    public ChargePaymentMode getChargePaymentMode() {
+        return ChargePaymentMode.fromInt(this.chargePaymentMode);
+    }
+
+    public ChargeCalculationType getChargeCalculation() {
+        return ChargeCalculationType.fromInt(this.chargeCalculation);
+    }
+
+    public BigDecimal getPercentage() {
+        return this.percentage;
+    }
+
+    public void updateAmount(final BigDecimal amount) {
+        this.amount = amount;
+        calculateOutstanding();
+    }
+
+    public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() {
+        LoanInstallmentCharge unpaidChargePerInstallment = null;
+        for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
+            if (loanChargePerInstallment.isPending()
+                    && (unpaidChargePerInstallment == null || unpaidChargePerInstallment.getRepaymentInstallment().getDueDate()
+                            .isAfter(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
+                unpaidChargePerInstallment = loanChargePerInstallment;
+            }
+        }
+        return unpaidChargePerInstallment;
+    }
+
+    public LoanInstallmentCharge getInstallmentLoanCharge(final LocalDate periodDueDate) {
+        for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
+            if (periodDueDate.isEqual(loanChargePerInstallment.getRepaymentInstallment().getDueDate())) { return loanChargePerInstallment; }
+        }
+        return null;
+    }
+
+    public LoanInstallmentCharge getInstallmentLoanCharge(final Integer installmentNumber) {
+        for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
+            if (installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber().intValue())) { return loanChargePerInstallment; }
+        }
+        return null;
+    }
+
+    public void clearLoanInstallmentCharges() {
+        this.loanInstallmentCharge.clear();
+    }
+
+    public void addLoanInstallmentCharges(final Collection<LoanInstallmentCharge> installmentCharges) {
+        this.loanInstallmentCharge.addAll(installmentCharges);
+    }
+
+    public boolean hasNoLoanInstallmentCharges() {
+        return this.loanInstallmentCharge.isEmpty();
+    }
+
+    public Set<LoanInstallmentCharge> installmentCharges() {
+        return this.loanInstallmentCharge;
+    }
+
+    public List<LoanChargePaidDetail> fetchRepaymentInstallment(final MonetaryCurrency currency) {
+        List<LoanChargePaidDetail> chargePaidDetails = new ArrayList<>();
+        for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
+            if (!loanChargePerInstallment.isChargeAmountpaid(currency)
+                    && loanChargePerInstallment.getAmountThroughChargePayment(currency).isGreaterThanZero()) {
+                LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(
+                        loanChargePerInstallment.getAmountThroughChargePayment(currency),
+                        loanChargePerInstallment.getRepaymentInstallment(), isFeeCharge());
+                chargePaidDetails.add(chargePaidDetail);
+            }
+        }
+        return chargePaidDetails;
+    }
+
+    public boolean isActive() {
+        return this.active;
+    }
+
+    public void setActive(boolean active) {
+        this.active = active;
+        if (!active) {
+            this.overdueInstallmentCharge = null;
+            this.clearLoanInstallmentCharges();
+        }
+    }
+
+    public BigDecimal amountOrPercentage() {
+        return this.amountOrPercentage;
+    }
+
+    public BigDecimal chargeAmount() {
+        BigDecimal totalChargeAmount = this.amountOutstanding;
+        if (this.amountPaid != null) {
+            totalChargeAmount = totalChargeAmount.add(this.amountPaid);
+        }
+        if (this.amountWaived != null) {
+            totalChargeAmount = totalChargeAmount.add(this.amountWaived);
+        }
+        if (this.amountWrittenOff != null) {
+            totalChargeAmount = totalChargeAmount.add(this.amountWrittenOff);
+        }
+        return totalChargeAmount;
+    }
+
+    public void updateOverdueInstallmentCharge(LoanOverdueInstallmentCharge overdueInstallmentCharge) {
+        this.overdueInstallmentCharge = overdueInstallmentCharge;
+    }
+
+    public void updateLoanTrancheDisbursementCharge(final LoanTrancheDisbursementCharge loanTrancheDisbursementCharge) {
+        this.loanTrancheDisbursementCharge = loanTrancheDisbursementCharge;
+    }
+
+    public void updateWaivedAmount(MonetaryCurrency currency) {
+        if (isInstalmentFee()) {
+            this.amountWaived = BigDecimal.ZERO;
+            for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) {
+                final Money amountWaived = chargePerInstallment.updateWaivedAndAmountPaidThroughChargePaymentAmount(currency);
+                this.amountWaived = this.amountWaived.add(amountWaived.getAmount());
+                this.amountOutstanding = this.amountOutstanding.subtract(amountWaived.getAmount());
+                if (determineIfFullyPaid() && Money.of(currency, this.amountWaived).isGreaterThanZero()) {
+                    this.paid = false;
+                    this.waived = true;
+                }
+            }
+            return;
+        }
+
+        Money waivedAmount = Money.of(currency, this.amountWaived);
+        if (waivedAmount.isGreaterThanZero()) {
+            if (waivedAmount.isGreaterThan(this.getAmount(currency))) {
+                this.amountWaived = this.getAmount(currency).getAmount();
+                this.amountOutstanding = BigDecimal.ZERO;
+                this.paid = false;
+                this.waived = true;
+            } else if (waivedAmount.isLessThan(this.getAmount(currency))) {
+                this.paid = false;
+                this.waived = false;
+            }
+        }
+
+    }
+
+    public LoanOverdueInstallmentCharge getOverdueInstallmentCharge() {
+        return this.overdueInstallmentCharge;
+    }
+
+    public LoanTrancheDisbursementCharge getTrancheDisbursementCharge() {
+        return this.loanTrancheDisbursementCharge;
+    }
+
+    public Money undoPaidOrPartiallyAmountBy(final Money incrementBy, final Integer installmentNumber, final Money feeAmount) {
+        Money processAmount = Money.zero(incrementBy.getCurrency());
+        if (isInstalmentFee()) {
+            if (installmentNumber == null) {
+                processAmount = getLastPaidOrPartiallyPaidInstallmentLoanCharge(incrementBy.getCurrency()).undoPaidAmountBy(incrementBy,
+                        feeAmount);
+            } else {
+                processAmount = getInstallmentLoanCharge(installmentNumber).undoPaidAmountBy(incrementBy, feeAmount);
+            }
+        } else {
+            processAmount = incrementBy;
+        }
+        Money amountPaidToDate = Money.of(processAmount.getCurrency(), this.amountPaid);
+
+        Money amountDeductedOnThisCharge = Money.zero(processAmount.getCurrency());
+        if (processAmount.isGreaterThanOrEqualTo(amountPaidToDate)) {
+            amountDeductedOnThisCharge = amountPaidToDate;
+            amountPaidToDate = Money.zero(processAmount.getCurrency());
+            this.amountPaid = amountPaidToDate.getAmount();
+            this.amountOutstanding = this.amount;
+            this.paid = false;
+
+        } else {
+            amountDeductedOnThisCharge = processAmount;
+            amountPaidToDate = amountPaidToDate.minus(processAmount);
+            this.amountPaid = amountPaidToDate.getAmount();
+            this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
+        }
+        return amountDeductedOnThisCharge;
+    }
+
+    public LoanInstallmentCharge getLastPaidOrPartiallyPaidInstallmentLoanCharge(MonetaryCurrency currency) {
+        LoanInstallmentCharge paidChargePerInstallment = null;
+        for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
+            Money outstanding = Money.of(currency, loanChargePerInstallment.getAmountOutstanding());
+            final boolean partiallyPaid = outstanding.isGreaterThanZero()
+                    && outstanding.isLessThan(loanChargePerInstallment.getAmount(currency));
+            if ((partiallyPaid || loanChargePerInstallment.isPaid())
+                    && (paidChargePerInstallment == null || paidChargePerInstallment.getRepaymentInstallment().getDueDate()
+                            .isBefore(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
+                paidChargePerInstallment = loanChargePerInstallment;
+            }
+        }
+        return paidChargePerInstallment;
+    }
+
+    public Loan getLoan() {
+        return this.loan;
+    }
+    
+    public boolean isTrancheDisbursementCharge() {
+        return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidBy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidBy.java
new file mode 100755
index 0000000..c2fbfef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidBy.java
@@ -0,0 +1,90 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_charge_paid_by")
+public class LoanChargePaidBy extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "loan_transaction_id", nullable = false)
+    private LoanTransaction loanTransaction;
+
+    @ManyToOne(cascade= CascadeType.ALL)
+    @JoinColumn(name = "loan_charge_id", nullable = false)
+    private LoanCharge loanCharge;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "installment_number", nullable = true)
+    private Integer installmentNumber;
+
+    protected LoanChargePaidBy() {
+
+    }
+
+    public LoanChargePaidBy(final LoanTransaction loanTransaction, final LoanCharge loanCharge, final BigDecimal amount,
+            Integer installmentNumber) {
+        this.loanTransaction = loanTransaction;
+        this.loanCharge = loanCharge;
+        this.amount = amount;
+        this.installmentNumber = installmentNumber;
+    }
+
+    public LoanTransaction getLoanTransaction() {
+        return this.loanTransaction;
+    }
+
+    public void setLoanTransaction(final LoanTransaction loanTransaction) {
+        this.loanTransaction = loanTransaction;
+    }
+
+    public LoanCharge getLoanCharge() {
+        return this.loanCharge;
+    }
+
+    public void setLoanCharge(final LoanCharge loanCharge) {
+        this.loanCharge = loanCharge;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public void setAmount(final BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    
+    public Integer getInstallmentNumber() {
+        return this.installmentNumber;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java
new file mode 100755
index 0000000..0c7df94
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.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.loanaccount.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanChargePaidByRepository extends JpaRepository<LoanChargePaidBy, Long>, JpaSpecificationExecutor<LoanCharge> {
+    // no added behaviour
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeRepository.java
new file mode 100644
index 0000000..c5ff8ef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeRepository.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.loanaccount.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanChargeRepository extends JpaRepository<LoanCharge, Long>, JpaSpecificationExecutor<LoanCharge> {
+    // no added behaviour
+}
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
new file mode 100644
index 0000000..0fba38a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java
@@ -0,0 +1,134 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+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.portfolio.loanaccount.data.DisbursementData;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_disbursement_detail")
+public class LoanDisbursementDetails extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "expected_disburse_date")
+    private Date expectedDisbursementDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "disbursedon_date")
+    private Date actualDisbursementDate;
+
+    @Column(name = "principal", scale = 6, precision = 19, nullable = false)
+    private BigDecimal principal;
+
+    protected LoanDisbursementDetails() {
+
+    }
+
+    public LoanDisbursementDetails(final Date expectedDisbursementDate, final Date actualDisbursementDate, final BigDecimal principal) {
+        this.expectedDisbursementDate = expectedDisbursementDate;
+        this.actualDisbursementDate = actualDisbursementDate;
+        this.principal = principal;
+     }
+
+    public void updateLoan(final Loan loan) {
+        this.loan = loan;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        final LoanDisbursementDetails loanDisbursementDetails = (LoanDisbursementDetails) obj;
+        if (loanDisbursementDetails.principal.equals(this.principal)
+                && loanDisbursementDetails.expectedDisbursementDate.equals(this.expectedDisbursementDate)) 
+        { return true; }
+        return false;
+    }
+
+    public void copy(final LoanDisbursementDetails disbursementDetails) {
+        this.principal = disbursementDetails.principal;
+        this.expectedDisbursementDate = disbursementDetails.expectedDisbursementDate;
+        this.actualDisbursementDate = disbursementDetails.actualDisbursementDate;
+    }
+
+    public Date expectedDisbursementDate() {
+        return this.expectedDisbursementDate;
+    }
+
+    public LocalDate expectedDisbursementDateAsLocalDate() {
+        LocalDate expectedDisburseDate = null;
+        if (this.expectedDisbursementDate != null) {
+            expectedDisburseDate = new LocalDate(this.expectedDisbursementDate);
+        }
+        return expectedDisburseDate;
+    }
+
+    public Date actualDisbursementDate() {
+        return this.actualDisbursementDate;
+    }
+
+    public BigDecimal principal() {
+        return this.principal;
+    }
+
+    public void updatePrincipal(BigDecimal principal) {
+        this.principal = principal;
+    }
+
+    public Date getDisbursementDate() {
+        Date disbursementDate = this.expectedDisbursementDate;
+        if (this.actualDisbursementDate != null) {
+            disbursementDate = this.actualDisbursementDate;
+        }
+        return disbursementDate;
+    }
+
+    public DisbursementData toData() {
+        LocalDate expectedDisburseDate = expectedDisbursementDateAsLocalDate();
+        LocalDate actualDisburseDate = null;
+        if (this.actualDisbursementDate != null) {
+            actualDisburseDate = new LocalDate(this.actualDisbursementDate);
+        }
+        return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, null, null);
+    }
+
+    public void updateActualDisbursementDate(Date actualDisbursementDate) {
+        this.actualDisbursementDate = actualDisbursementDate;
+    }
+
+    public void updateExpectedDisbursementDateAndAmount(Date expectedDisbursementDate, BigDecimal principal) {
+        this.expectedDisbursementDate = expectedDisbursementDate;
+        this.principal = principal;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
new file mode 100755
index 0000000..5b7f4c1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+/**
+ *
+ */
+public enum LoanEvent {
+
+    LOAN_CREATED, //
+    LOAN_REJECTED, //
+    LOAN_WITHDRAWN, //
+    LOAN_APPROVED, //
+    LOAN_APPROVAL_UNDO, //
+    LOAN_RECOVERY_PAYMENT, //
+    LOAN_DISBURSED, //
+    LOAN_DISBURSAL_UNDO, //
+    LOAN_DISBURSAL_UNDO_LAST, //
+    LOAN_REPAYMENT_OR_WAIVER, //
+    REPAID_IN_FULL, //
+    WRITE_OFF_OUTSTANDING, //
+    WRITE_OFF_OUTSTANDING_UNDO, //
+    LOAN_RESCHEDULE, //
+    INTERST_REBATE_OWED, //
+    LOAN_OVERPAYMENT, //
+    LOAN_CHARGE_PAYMENT, //
+    LOAN_CLOSED, //
+    LOAN_EDIT_MULTI_DISBURSE_DATE, //
+    LOAN_REFUND;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
new file mode 100755
index 0000000..5a2241b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
@@ -0,0 +1,291 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_installment_charge")
+public class LoanInstallmentCharge extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false)
+    private LoanCharge loancharge;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_schedule_id", referencedColumnName = "id", nullable = false)
+    private LoanRepaymentScheduleInstallment installment;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountPaid;
+
+    @Column(name = "amount_waived_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountWaived;
+
+    @Column(name = "amount_writtenoff_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountWrittenOff;
+
+    @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amountOutstanding;
+
+    @Column(name = "amount_through_charge_payment", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountThroughChargePayment;
+
+    @Column(name = "is_paid_derived", nullable = false)
+    private boolean paid = false;
+
+    @Column(name = "waived", nullable = false)
+    private boolean waived = false;
+
+    public LoanInstallmentCharge() {
+        // TODO Auto-generated constructor stub
+    }
+
+    public LoanInstallmentCharge(final BigDecimal amount, final LoanCharge loanCharge, final LoanRepaymentScheduleInstallment installment) {
+        this.loancharge = loanCharge;
+        this.installment = installment;
+        this.amount = amount;
+        this.amountOutstanding = amount;
+        this.amountPaid = null;
+        this.amountWaived = null;
+        this.amountWrittenOff = null;
+    }
+
+    public void copyFrom(final LoanInstallmentCharge loanChargePerInstallment) {
+        this.amount = loanChargePerInstallment.amount;
+        this.installment = loanChargePerInstallment.installment;
+        this.amountOutstanding = calculateOutstanding();
+        this.paid = determineIfFullyPaid();
+    }
+
+    public Money waive(final MonetaryCurrency currency) {
+        this.amountWaived = this.amountOutstanding;
+        this.amountOutstanding = BigDecimal.ZERO;
+        this.paid = false;
+        this.waived = true;
+        return getAmountWaived(currency);
+    }
+
+    public Money getAmountWaived(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountWaived);
+    }
+
+    private boolean determineIfFullyPaid() {
+        if (this.amount == null) { return true; }
+        return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
+    }
+
+    private BigDecimal calculateOutstanding() {
+        if (this.amount == null) { return null; }
+        BigDecimal amountPaidLocal = BigDecimal.ZERO;
+        if (this.amountPaid != null) {
+            amountPaidLocal = this.amountPaid;
+        }
+
+        BigDecimal amountWaivedLocal = BigDecimal.ZERO;
+        if (this.amountWaived != null) {
+            amountWaivedLocal = this.amountWaived;
+        }
+
+        BigDecimal amountWrittenOffLocal = BigDecimal.ZERO;
+        if (this.amountWrittenOff != null) {
+            amountWrittenOffLocal = this.amountWrittenOff;
+        }
+
+        final BigDecimal totalAccountedFor = amountPaidLocal.add(amountWaivedLocal).add(amountWrittenOffLocal);
+
+        return this.amount.subtract(totalAccountedFor);
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public Money getAmount(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amount);
+    }
+
+    public Money getAmountPaid(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountPaid);
+    }
+
+    public BigDecimal getAmountOutstanding() {
+        return this.amountOutstanding;
+    }
+
+    private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
+        return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
+    }
+
+    public boolean isPaid() {
+        return this.paid;
+    }
+
+    public boolean isWaived() {
+        return this.waived;
+    }
+
+    public boolean isPending() {
+        return !(isPaid() || isWaived());
+    }
+
+    public boolean isChargeAmountpaid(MonetaryCurrency currency) {
+        Money amounPaidThroughChargePayment = Money.of(currency, this.amountThroughChargePayment);
+        Money paid = Money.of(currency, this.amountPaid);
+        return amounPaidThroughChargePayment.isEqualTo(paid);
+    }
+
+    public LoanRepaymentScheduleInstallment getRepaymentInstallment() {
+        return this.installment;
+    }
+
+    public Money updatePaidAmountBy(final Money incrementBy, final Money feeAmount) {
+
+        Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
+        final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding);
+        Money amountPaidPreviously = amountPaidToDate;
+        Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency());
+        if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) {
+            amountPaidOnThisCharge = amountOutstanding;
+            amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
+            this.amountPaid = amountPaidToDate.getAmount();
+            this.amountOutstanding = BigDecimal.ZERO;
+        } else {
+            amountPaidOnThisCharge = incrementBy;
+            amountPaidToDate = amountPaidToDate.plus(incrementBy);
+            this.amountPaid = amountPaidToDate.getAmount();
+            this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
+        }
+        Money amountFromChargePayment = Money.of(incrementBy.getCurrency(), this.amountThroughChargePayment);
+        if (amountPaidPreviously.isGreaterThanZero()) {
+            amountFromChargePayment = amountFromChargePayment.plus(feeAmount);
+        } else {
+            amountFromChargePayment = feeAmount;
+        }
+        this.amountThroughChargePayment = amountFromChargePayment.getAmount();
+        if (determineIfFullyPaid()) {
+            Money waivedAmount = getAmountWaived(incrementBy.getCurrency());
+            if (waivedAmount.isGreaterThanZero()) {
+                this.waived = true;
+            } else {
+                this.paid = true;
+            }
+        }
+
+        return amountPaidOnThisCharge;
+    }
+
+    public Money getAmountWrittenOff(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountWrittenOff);
+    }
+
+    public void resetPaidAmount(final MonetaryCurrency currency) {
+        this.amountPaid = BigDecimal.ZERO;
+        this.amountOutstanding = calculateAmountOutstanding(currency);
+        this.paid = false;
+    }
+
+    public void resetToOriginal(final MonetaryCurrency currency) {
+        this.amountPaid = BigDecimal.ZERO;
+        this.amountWaived = BigDecimal.ZERO;
+        this.amountWrittenOff = BigDecimal.ZERO;
+        this.amountThroughChargePayment = BigDecimal.ZERO;
+        this.amountOutstanding = calculateAmountOutstanding(currency);
+        this.paid = false;
+        this.waived = false;
+
+    }
+
+    public Money getAmountThroughChargePayment(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountThroughChargePayment);
+    }
+
+    public Money getUnpaidAmountThroughChargePayment(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountThroughChargePayment).minus(this.amountPaid);
+    }
+
+    private void updateAmountThroughChargePayment(final MonetaryCurrency currency) {
+        Money amountThroughChargePayment = getAmountThroughChargePayment(currency);
+        if (amountThroughChargePayment.isGreaterThanZero() && amountThroughChargePayment.isGreaterThan(this.getAmount(currency))) {
+            this.amountThroughChargePayment = this.getAmount();
+        }
+    }
+
+    public Money updateWaivedAndAmountPaidThroughChargePaymentAmount(final MonetaryCurrency currency) {
+        updateWaivedAmount(currency);
+        updateAmountThroughChargePayment(currency);
+        return getAmountWaived(currency);
+    }
+
+    private void updateWaivedAmount(final MonetaryCurrency currency) {
+        Money waivedAmount = getAmountWaived(currency);
+        if (waivedAmount.isGreaterThanZero()) {
+            if (waivedAmount.isGreaterThan(this.getAmount(currency))) {
+                this.amountWaived = this.getAmount();
+                this.amountOutstanding = BigDecimal.ZERO;
+                this.paid = false;
+                this.waived = true;
+            } else if (waivedAmount.isLessThan(this.getAmount(currency))) {
+                this.paid = false;
+                this.waived = false;
+            }
+        }
+    }
+
+    
+    public void updateInstallment(LoanRepaymentScheduleInstallment installment) {
+        this.installment = installment;
+    }
+    
+    public Money undoPaidAmountBy(final Money incrementBy, final Money feeAmount) {
+
+        Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
+       
+        Money amountToDeductOnThisCharge = Money.zero(incrementBy.getCurrency());
+        if (incrementBy.isGreaterThanOrEqualTo(amountPaidToDate)) {
+                amountToDeductOnThisCharge = amountPaidToDate;
+            amountPaidToDate = Money.zero(incrementBy.getCurrency());
+            this.amountPaid = amountPaidToDate.getAmount();
+            this.amountOutstanding = this.amount;
+        } else {
+                amountToDeductOnThisCharge = incrementBy;
+            amountPaidToDate = amountPaidToDate.minus(incrementBy);
+            this.amountPaid = amountPaidToDate.getAmount();
+            this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
+        }
+        this.amountThroughChargePayment = feeAmount.getAmount();
+        this.paid = determineIfFullyPaid();
+
+        return amountToDeductOnThisCharge;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
new file mode 100644
index 0000000..fe071f5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
@@ -0,0 +1,194 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.util.Date;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+/**
+ * Entity for holding interest recalculation setting, details will be copied
+ * from product directly
+ * 
+ * @author conflux
+ */
+
+@Entity
+@Table(name = "m_loan_recalculation_details")
+public class LoanInterestRecalculationDetails extends AbstractPersistable<Long> {
+
+    @OneToOne
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+
+    /**
+     * {@link InterestRecalculationCompoundingMethod}
+     */
+    @Column(name = "compound_type_enum", nullable = false)
+    private Integer interestRecalculationCompoundingMethod;
+
+    /**
+     * {@link LoanRescheduleStrategyMethod}
+     */
+    @Column(name = "reschedule_strategy_enum", nullable = false)
+    private Integer rescheduleStrategyMethod;
+
+    @Column(name = "rest_frequency_type_enum", nullable = false)
+    private Integer restFrequencyType;
+
+    @Column(name = "rest_frequency_interval", nullable = false)
+    private Integer restInterval;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "rest_freqency_date")
+    private Date restFrequencyDate;
+
+    @Column(name = "compounding_frequency_type_enum", nullable = true)
+    private Integer compoundingFrequencyType;
+
+    @Column(name = "compounding_frequency_interval", nullable = true)
+    private Integer compoundingInterval;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "compounding_freqency_date")
+    private Date compoundingFrequencyDate;
+
+    protected LoanInterestRecalculationDetails() {
+        // Default constructor for jpa repository
+    }
+
+    private LoanInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod,
+            final Integer restFrequencyType, final Integer restInterval, final Date restFrequencyDate, Integer compoundingFrequencyType,
+            Integer compoundingInterval, Date compoundingFrequencyDate) {
+        this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
+        this.rescheduleStrategyMethod = rescheduleStrategyMethod;
+        this.restFrequencyDate = restFrequencyDate;
+        this.restFrequencyType = restFrequencyType;
+        this.restInterval = restInterval;
+        this.compoundingFrequencyDate = compoundingFrequencyDate;
+        this.compoundingFrequencyType = compoundingFrequencyType;
+        this.compoundingInterval = compoundingInterval;
+    }
+
+    public static LoanInterestRecalculationDetails createFrom(final Integer interestRecalculationCompoundingMethod,
+            final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval,
+            final Date restFrequencyDate, final Integer compoundingFrequencyType, final Integer compoundingInterval,
+            final Date compoundingFrequencyDate) {
+        return new LoanInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, restFrequencyType,
+                restInterval, restFrequencyDate, compoundingFrequencyType, compoundingInterval, compoundingFrequencyDate);
+    }
+
+    public void updateLoan(final Loan loan) {
+        this.loan = loan;
+    }
+
+    public void update(final JsonCommand command, final Map<String, Object> actualChanges) {
+        if (command.isChangeInLocalDateParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName,
+                getRestFrequencyLocalDate())) {
+            final String dateFormatAsInput = command.dateFormat();
+            final String localeAsInput = command.locale();
+            final String valueAsInput = command.stringValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName);
+            actualChanges.put(LoanProductConstants.recalculationRestFrequencyDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName);
+            if (newValue == null || getRestFrequencyType().isSameAsRepayment()) {
+                this.restFrequencyDate = null;
+            } else {
+                this.restFrequencyDate = newValue.toDate();
+            }
+        }
+
+        if (command.isChangeInLocalDateParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName,
+                getCompoundingFrequencyLocalDate())) {
+            final String dateFormatAsInput = command.dateFormat();
+            final String localeAsInput = command.locale();
+            final String valueAsInput = command
+                    .stringValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName);
+            actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+
+            final LocalDate newValue = command
+                    .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName);
+            if (newValue == null || !getInterestRecalculationCompoundingMethod().isCompoundingEnabled()
+                    || getCompoundingFrequencyType().isSameAsRepayment()) {
+                this.compoundingFrequencyDate = null;
+            } else {
+                this.compoundingFrequencyDate = newValue.toDate();
+            }
+        }
+    }
+
+    public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() {
+        return InterestRecalculationCompoundingMethod.fromInt(this.interestRecalculationCompoundingMethod);
+    }
+
+    public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() {
+        return LoanRescheduleStrategyMethod.fromInt(this.rescheduleStrategyMethod);
+    }
+
+    public LocalDate getRestFrequencyLocalDate() {
+        LocalDate recurrenceOnLocalDate = null;
+        if (this.restFrequencyDate != null) {
+            recurrenceOnLocalDate = new LocalDate(this.restFrequencyDate);
+        }
+        return recurrenceOnLocalDate;
+    }
+
+    public RecalculationFrequencyType getRestFrequencyType() {
+        return RecalculationFrequencyType.fromInt(this.restFrequencyType);
+    }
+
+    public Integer getRestInterval() {
+        return this.restInterval;
+    }
+
+    public LocalDate getCompoundingFrequencyLocalDate() {
+        LocalDate recurrenceOnLocalDate = null;
+        if (this.compoundingFrequencyDate != null) {
+            recurrenceOnLocalDate = new LocalDate(this.compoundingFrequencyDate);
+        }
+        return recurrenceOnLocalDate;
+    }
+
+    public RecalculationFrequencyType getCompoundingFrequencyType() {
+        return RecalculationFrequencyType.fromInt(this.compoundingFrequencyType);
+    }
+
+    public Integer getCompoundingInterval() {
+        return this.compoundingInterval;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanLifecycleStateMachine.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanLifecycleStateMachine.java
new file mode 100644
index 0000000..ad8ec1e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanLifecycleStateMachine.java
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+public interface LoanLifecycleStateMachine {
+
+    LoanStatus transition(LoanEvent loanEvent, LoanStatus from);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java
new file mode 100644
index 0000000..6f84437
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.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.commons.lang.ObjectUtils;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+
+@Entity
+@Table(name = "m_loan_officer_assignment_history")
+public class LoanOfficerAssignmentHistory extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+
+    @ManyToOne
+    @JoinColumn(name = "loan_officer_id", nullable = true)
+    private Staff loanOfficer;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "start_date")
+    private Date startDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "end_date")
+    private Date endDate;
+
+    public static LoanOfficerAssignmentHistory createNew(final Loan loan, final Staff loanOfficer, final LocalDate startDate) {
+        return new LoanOfficerAssignmentHistory(loan, loanOfficer, startDate.toDate(), null);
+    }
+
+    protected LoanOfficerAssignmentHistory() {
+        //
+    }
+
+    private LoanOfficerAssignmentHistory(final Loan loan, final Staff loanOfficer, final Date startDate, final Date endDate) {
+        this.loan = loan;
+        this.loanOfficer = loanOfficer;
+        this.startDate = startDate;
+        this.endDate = endDate;
+    }
+
+    public void updateLoanOfficer(final Staff loanOfficer) {
+        this.loanOfficer = loanOfficer;
+    }
+
+    public void updateStartDate(final LocalDate startDate) {
+        this.startDate = startDate.toDate();
+    }
+
+    public void updateEndDate(final LocalDate endDate) {
+        this.endDate = endDate.toDate();
+    }
+
+    public boolean matchesStartDateOf(final LocalDate matchingDate) {
+        return getStartDate().isEqual(matchingDate);
+    }
+
+    public LocalDate getStartDate() {
+        return new LocalDate(this.startDate);
+    }
+
+    public boolean hasStartDateBefore(final LocalDate matchingDate) {
+        return matchingDate.isBefore(getStartDate());
+    }
+
+    public boolean isCurrentRecord() {
+        return this.endDate == null;
+    }
+
+    /**
+     * If endDate is null then return false.
+     * 
+     * @param compareDate
+     * @return
+     */
+    public boolean isEndDateAfter(final LocalDate compareDate) {
+        return this.endDate == null ? false : new LocalDate(this.endDate).isAfter(compareDate);
+    }
+
+    public LocalDate getEndDate() {
+        return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.endDate), null);
+    }
+
+    public boolean isSameLoanOfficer(final Staff staff) {
+        return this.loanOfficer.identifiedBy(staff);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOverdueInstallmentCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOverdueInstallmentCharge.java
new file mode 100755
index 0000000..e1a9326
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOverdueInstallmentCharge.java
@@ -0,0 +1,63 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_overdue_installment_charge")
+public class LoanOverdueInstallmentCharge extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false)
+    private LoanCharge loancharge;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_schedule_id", referencedColumnName = "id", nullable = false)
+    private LoanRepaymentScheduleInstallment installment;
+
+    @Column(name = "frequency_number")
+    private Integer frequencyNumber;
+
+    public LoanOverdueInstallmentCharge() {
+
+    }
+
+    public LoanOverdueInstallmentCharge(final LoanCharge loanCharge, final LoanRepaymentScheduleInstallment installment,
+            final Integer frequencyNumber) {
+        this.loancharge = loanCharge;
+        this.installment = installment;
+        this.frequencyNumber = frequencyNumber;
+    }
+
+    public void updateLoanRepaymentScheduleInstallment(LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) {
+        this.installment = loanRepaymentScheduleInstallment;
+    }
+
+    public LoanRepaymentScheduleInstallment getInstallment() {
+        return this.installment;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentDataComparator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentDataComparator.java
new file mode 100644
index 0000000..18eed60
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentDataComparator.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.util.Comparator;
+
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+
+public class LoanRepaymentDataComparator implements Comparator<LoanTransactionData> {
+
+    @Override
+    public int compare(final LoanTransactionData o1, final LoanTransactionData o2) {
+        return o2.dateOf().compareTo(o1.dateOf());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
new file mode 100644
index 0000000..73aedb6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -0,0 +1,773 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+import java.util.Comparator;
+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.AbstractAuditableCustom;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+
+@Entity
+@Table(name = "m_loan_repayment_schedule")
+public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_id")
+    private Loan loan;
+
+    @Column(name = "installment", nullable = false)
+    private Integer installmentNumber;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "fromdate", nullable = true)
+    private Date fromDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "duedate", nullable = false)
+    private Date dueDate;
+
+    @Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal principal;
+
+    @Column(name = "principal_completed_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal principalCompleted;
+
+    @Column(name = "principal_writtenoff_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal principalWrittenOff;
+
+    @Column(name = "interest_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal interestCharged;
+
+    @Column(name = "interest_completed_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal interestPaid;
+
+    @Column(name = "interest_waived_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal interestWaived;
+
+    @Column(name = "interest_writtenoff_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal interestWrittenOff;
+
+    @Column(name = "accrual_interest_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal interestAccrued;
+
+    @Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal feeChargesCharged;
+
+    @Column(name = "fee_charges_completed_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal feeChargesPaid;
+
+    @Column(name = "fee_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal feeChargesWrittenOff;
+
+    @Column(name = "fee_charges_waived_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal feeChargesWaived;
+
+    @Column(name = "accrual_fee_charges_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal feeAccrued;
+
+    @Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal penaltyCharges;
+
+    @Column(name = "penalty_charges_completed_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal penaltyChargesPaid;
+
+    @Column(name = "penalty_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal penaltyChargesWrittenOff;
+
+    @Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal penaltyChargesWaived;
+
+    @Column(name = "accrual_penalty_charges_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal penaltyAccrued;
+
+    @Column(name = "total_paid_in_advance_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal totalPaidInAdvance;
+
+    @Column(name = "total_paid_late_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal totalPaidLate;
+
+    @Column(name = "completed_derived", nullable = false)
+    private boolean obligationsMet;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "obligations_met_on_date")
+    private Date obligationsMetOnDate;
+
+    @Column(name = "recalculated_interest_component", nullable = false)
+    private boolean recalculatedInterestComponent;
+
+    protected LoanRepaymentScheduleInstallment() {
+        this.installmentNumber = null;
+        this.fromDate = null;
+        this.dueDate = null;
+        this.obligationsMet = false;
+    }
+
+    public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
+            final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
+            final BigDecimal penaltyCharges, boolean recalculatedInterestComponent) {
+        this.loan = loan;
+        this.installmentNumber = installmentNumber;
+        this.fromDate = fromDate.toDateTimeAtStartOfDay().toDate();
+        this.dueDate = dueDate.toDateTimeAtStartOfDay().toDate();
+        this.principal = defaultToNullIfZero(principal);
+        this.interestCharged = defaultToNullIfZero(interest);
+        this.feeChargesCharged = defaultToNullIfZero(feeCharges);
+        this.penaltyCharges = defaultToNullIfZero(penaltyCharges);
+        this.obligationsMet = false;
+        this.recalculatedInterestComponent = recalculatedInterestComponent;
+    }
+
+    public LoanRepaymentScheduleInstallment(final Loan loan) {
+        this.loan = loan;
+        this.installmentNumber = null;
+        this.fromDate = null;
+        this.dueDate = null;
+        this.obligationsMet = false;
+    }
+
+    private BigDecimal defaultToNullIfZero(final BigDecimal value) {
+        BigDecimal result = value;
+        if (BigDecimal.ZERO.compareTo(value) == 0) {
+            result = null;
+        }
+        return result;
+    }
+
+    public Loan getLoan() {
+        return this.loan;
+    }
+
+    public Integer getInstallmentNumber() {
+        return this.installmentNumber;
+    }
+
+    public LocalDate getFromDate() {
+        LocalDate fromLocalDate = null;
+        if (this.fromDate != null) {
+            fromLocalDate = new LocalDate(this.fromDate);
+        }
+
+        return fromLocalDate;
+    }
+
+    public LocalDate getDueDate() {
+        return new LocalDate(this.dueDate);
+    }
+
+    public Money getPrincipal(final MonetaryCurrency currency) {
+        return Money.of(currency, this.principal);
+    }
+
+    public Money getPrincipalCompleted(final MonetaryCurrency currency) {
+        return Money.of(currency, this.principalCompleted);
+    }
+
+    public Money getPrincipalWrittenOff(final MonetaryCurrency currency) {
+        return Money.of(currency, this.principalWrittenOff);
+    }
+
+    public Money getPrincipalOutstanding(final MonetaryCurrency currency) {
+        final Money principalAccountedFor = getPrincipalCompleted(currency).plus(getPrincipalWrittenOff(currency));
+        return getPrincipal(currency).minus(principalAccountedFor);
+    }
+
+    public Money getInterestCharged(final MonetaryCurrency currency) {
+        return Money.of(currency, this.interestCharged);
+    }
+
+    public Money getInterestPaid(final MonetaryCurrency currency) {
+        return Money.of(currency, this.interestPaid);
+    }
+
+    public Money getInterestWaived(final MonetaryCurrency currency) {
+        return Money.of(currency, this.interestWaived);
+    }
+
+    public Money getInterestWrittenOff(final MonetaryCurrency currency) {
+        return Money.of(currency, this.interestWrittenOff);
+    }
+
+    public Money getInterestOutstanding(final MonetaryCurrency currency) {
+        final Money interestAccountedFor = getInterestPaid(currency).plus(getInterestWaived(currency))
+                .plus(getInterestWrittenOff(currency));
+        return getInterestCharged(currency).minus(interestAccountedFor);
+    }
+
+    public Money getInterestAccrued(final MonetaryCurrency currency) {
+        return Money.of(currency, this.interestAccrued);
+    }
+
+    public Money getFeeChargesCharged(final MonetaryCurrency currency) {
+        return Money.of(currency, this.feeChargesCharged);
+    }
+
+    public Money getFeeChargesPaid(final MonetaryCurrency currency) {
+        return Money.of(currency, this.feeChargesPaid);
+    }
+
+    public Money getFeeChargesWaived(final MonetaryCurrency currency) {
+        return Money.of(currency, this.feeChargesWaived);
+    }
+
+    public Money getFeeChargesWrittenOff(final MonetaryCurrency currency) {
+        return Money.of(currency, this.feeChargesWrittenOff);
+    }
+
+    public Money getFeeChargesOutstanding(final MonetaryCurrency currency) {
+        final Money feeChargesAccountedFor = getFeeChargesPaid(currency).plus(getFeeChargesWaived(currency)).plus(
+                getFeeChargesWrittenOff(currency));
+        return getFeeChargesCharged(currency).minus(feeChargesAccountedFor);
+    }
+
+    public Money getFeeAccrued(final MonetaryCurrency currency) {
+        return Money.of(currency, this.feeAccrued);
+    }
+
+    public Money getPenaltyChargesCharged(final MonetaryCurrency currency) {
+        return Money.of(currency, this.penaltyCharges);
+    }
+
+    public Money getPenaltyChargesPaid(final MonetaryCurrency currency) {
+        return Money.of(currency, this.penaltyChargesPaid);
+    }
+
+    public Money getPenaltyChargesWaived(final MonetaryCurrency currency) {
+        return Money.of(currency, this.penaltyChargesWaived);
+    }
+
+    public Money getPenaltyChargesWrittenOff(final MonetaryCurrency currency) {
+        return Money.of(currency, this.penaltyChargesWrittenOff);
+    }
+
+    public Money getPenaltyChargesOutstanding(final MonetaryCurrency currency) {
+        final Money feeChargesAccountedFor = getPenaltyChargesPaid(currency).plus(getPenaltyChargesWaived(currency)).plus(
+                getPenaltyChargesWrittenOff(currency));
+        return getPenaltyChargesCharged(currency).minus(feeChargesAccountedFor);
+    }
+
+    public Money getPenaltyAccrued(final MonetaryCurrency currency) {
+        return Money.of(currency, this.penaltyAccrued);
+    }
+
+    public boolean isInterestDue(final MonetaryCurrency currency) {
+        return getInterestOutstanding(currency).isGreaterThanZero();
+    }
+
+    public Money getTotalPrincipalAndInterest(final MonetaryCurrency currency) {
+        return getPrincipal(currency).plus(getInterestCharged(currency));
+    }
+
+    public Money getTotalOutstanding(final MonetaryCurrency currency) {
+        return getPrincipalOutstanding(currency).plus(getInterestOutstanding(currency)).plus(getFeeChargesOutstanding(currency))
+                .plus(getPenaltyChargesOutstanding(currency));
+    }
+
+    public void updateLoan(final Loan loan) {
+        this.loan = loan;
+    }
+
+    public boolean isPartlyPaid() {
+        return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null || this.principalCompleted != null);
+    }
+
+    public boolean isObligationsMet() {
+        return this.obligationsMet;
+    }
+
+    public boolean isNotFullyPaidOff() {
+        return !this.obligationsMet;
+    }
+
+    public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
+        return !isPrincipalCompleted(currency);
+    }
+
+    public boolean isPrincipalCompleted(final MonetaryCurrency currency) {
+        return getPrincipalOutstanding(currency).isZero();
+    }
+
+    public void resetDerivedComponents() {
+        this.principalCompleted = null;
+        this.principalWrittenOff = null;
+        this.interestPaid = null;
+        this.interestWaived = null;
+        this.interestWrittenOff = null;
+        this.feeChargesPaid = null;
+        this.feeChargesWaived = null;
+        this.feeChargesWrittenOff = null;
+        this.penaltyChargesPaid = null;
+        this.penaltyChargesWaived = null;
+        this.penaltyChargesWrittenOff = null;
+        this.totalPaidInAdvance = null;
+        this.totalPaidLate = null;
+
+        this.obligationsMet = false;
+        this.obligationsMetOnDate = null;
+    }
+
+    public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money penaltyPortionOfTransaction = Money.zero(currency);
+
+        final Money penaltyChargesDue = getPenaltyChargesOutstanding(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesDue)) {
+            this.penaltyChargesPaid = getPenaltyChargesPaid(currency).plus(penaltyChargesDue).getAmount();
+            penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(penaltyChargesDue);
+        } else {
+            this.penaltyChargesPaid = getPenaltyChargesPaid(currency).plus(transactionAmountRemaining).getAmount();
+            penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(transactionAmountRemaining);
+        }
+
+        this.penaltyChargesPaid = defaultToNullIfZero(this.penaltyChargesPaid);
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        return penaltyPortionOfTransaction;
+    }
+
+    public Money payFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money feePortionOfTransaction = Money.zero(currency);
+
+        final Money feeChargesDue = getFeeChargesOutstanding(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesDue)) {
+            this.feeChargesPaid = getFeeChargesPaid(currency).plus(feeChargesDue).getAmount();
+            feePortionOfTransaction = feePortionOfTransaction.plus(feeChargesDue);
+        } else {
+            this.feeChargesPaid = getFeeChargesPaid(currency).plus(transactionAmountRemaining).getAmount();
+            feePortionOfTransaction = feePortionOfTransaction.plus(transactionAmountRemaining);
+        }
+
+        this.feeChargesPaid = defaultToNullIfZero(this.feeChargesPaid);
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        trackAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, feePortionOfTransaction);
+
+        return feePortionOfTransaction;
+    }
+
+    public Money payInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money interestPortionOfTransaction = Money.zero(currency);
+
+        final Money interestDue = getInterestOutstanding(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
+            this.interestPaid = getInterestPaid(currency).plus(interestDue).getAmount();
+            interestPortionOfTransaction = interestPortionOfTransaction.plus(interestDue);
+        } else {
+            this.interestPaid = getInterestPaid(currency).plus(transactionAmountRemaining).getAmount();
+            interestPortionOfTransaction = interestPortionOfTransaction.plus(transactionAmountRemaining);
+        }
+
+        this.interestPaid = defaultToNullIfZero(this.interestPaid);
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        trackAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, interestPortionOfTransaction);
+
+        return interestPortionOfTransaction;
+    }
+
+    public Money payPrincipalComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money principalPortionOfTransaction = Money.zero(currency);
+
+        final Money principalDue = getPrincipalOutstanding(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) {
+            this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount();
+            principalPortionOfTransaction = principalPortionOfTransaction.plus(principalDue);
+        } else {
+            this.principalCompleted = getPrincipalCompleted(currency).plus(transactionAmountRemaining).getAmount();
+            principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmountRemaining);
+        }
+
+        this.principalCompleted = defaultToNullIfZero(this.principalCompleted);
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        trackAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, principalPortionOfTransaction);
+
+        return principalPortionOfTransaction;
+    }
+
+    public Money waiveInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money waivedInterestPortionOfTransaction = Money.zero(currency);
+
+        final Money interestDue = getInterestOutstanding(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
+            this.interestWaived = getInterestWaived(currency).plus(interestDue).getAmount();
+            waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(interestDue);
+        } else {
+            this.interestWaived = getInterestWaived(currency).plus(transactionAmountRemaining).getAmount();
+            waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(transactionAmountRemaining);
+        }
+
+        this.interestWaived = defaultToNullIfZero(this.interestWaived);
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        return waivedInterestPortionOfTransaction;
+    }
+
+    public Money waivePenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money waivedPenaltyChargesPortionOfTransaction = Money.zero(currency);
+
+        final Money penanltiesDue = getPenaltyChargesOutstanding(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(penanltiesDue)) {
+            this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(penanltiesDue).getAmount();
+            waivedPenaltyChargesPortionOfTransaction = waivedPenaltyChargesPortionOfTransaction.plus(penanltiesDue);
+        } else {
+            this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(transactionAmountRemaining).getAmount();
+            waivedPenaltyChargesPortionOfTransaction = waivedPenaltyChargesPortionOfTransaction.plus(transactionAmountRemaining);
+        }
+
+        this.penaltyChargesWaived = defaultToNullIfZero(this.penaltyChargesWaived);
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        return waivedPenaltyChargesPortionOfTransaction;
+    }
+
+    public Money waiveFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money waivedFeeChargesPortionOfTransaction = Money.zero(currency);
+
+        final Money feesDue = getFeeChargesOutstanding(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(feesDue)) {
+            this.feeChargesWaived = getFeeChargesWaived(currency).plus(feesDue).getAmount();
+            waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(feesDue);
+        } else {
+            this.feeChargesWaived = getFeeChargesWaived(currency).plus(transactionAmountRemaining).getAmount();
+            waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(transactionAmountRemaining);
+        }
+
+        this.feeChargesWaived = defaultToNullIfZero(this.feeChargesWaived);
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        return waivedFeeChargesPortionOfTransaction;
+    }
+
+    public Money writeOffOutstandingPrincipal(final LocalDate transactionDate, final MonetaryCurrency currency) {
+
+        final Money principalDue = getPrincipalOutstanding(currency);
+        this.principalWrittenOff = defaultToNullIfZero(principalDue.getAmount());
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        return principalDue;
+    }
+
+    public Money writeOffOutstandingInterest(final LocalDate transactionDate, final MonetaryCurrency currency) {
+
+        final Money interestDue = getInterestOutstanding(currency);
+        this.interestWrittenOff = defaultToNullIfZero(interestDue.getAmount());
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        return interestDue;
+    }
+
+    public Money writeOffOutstandingFeeCharges(final LocalDate transactionDate, final MonetaryCurrency currency) {
+        final Money feeChargesOutstanding = getFeeChargesOutstanding(currency);
+        this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesOutstanding.getAmount());
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        return feeChargesOutstanding;
+    }
+
+    public Money writeOffOutstandingPenaltyCharges(final LocalDate transactionDate, final MonetaryCurrency currency) {
+        final Money penaltyChargesOutstanding = getPenaltyChargesOutstanding(currency);
+        this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesOutstanding.getAmount());
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        return penaltyChargesOutstanding;
+    }
+
+    public boolean isOverdueOn(final LocalDate date) {
+        return getDueDate().isBefore(date);
+    }
+
+    public void updateChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
+            final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
+        this.feeChargesCharged = defaultToNullIfZero(feeChargesDue.getAmount());
+        this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.getAmount());
+        this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.getAmount());
+        this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.getAmount());
+        this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.getAmount());
+        this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount());
+    }
+
+    public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) {
+        this.interestAccrued = defaultToNullIfZero(interest.getAmount());
+        this.feeAccrued = defaultToNullIfZero(feeCharges.getAmount());
+        this.penaltyAccrued = defaultToNullIfZero(penalityCharges.getAmount());
+    }
+
+    public void updateDerivedFields(final MonetaryCurrency currency, final LocalDate actualDisbursementDate) {
+        if (!this.obligationsMet && getTotalOutstanding(currency).isZero()) {
+            this.obligationsMet = true;
+            this.obligationsMetOnDate = actualDisbursementDate.toDate();
+        }
+    }
+
+    private void trackAdvanceAndLateTotalsForRepaymentPeriod(final LocalDate transactionDate, final MonetaryCurrency currency,
+            final Money amountPaidInRepaymentPeriod) {
+        if (isInAdvance(transactionDate)) {
+            this.totalPaidInAdvance = asMoney(this.totalPaidInAdvance, currency).plus(amountPaidInRepaymentPeriod).getAmount();
+        } else if (isLatePayment(transactionDate)) {
+            this.totalPaidLate = asMoney(this.totalPaidLate, currency).plus(amountPaidInRepaymentPeriod).getAmount();
+        }
+    }
+
+    private Money asMoney(final BigDecimal decimal, final MonetaryCurrency currency) {
+        return Money.of(currency, decimal);
+    }
+
+    private boolean isInAdvance(final LocalDate transactionDate) {
+        return transactionDate.isBefore(getDueDate());
+    }
+
+    private boolean isLatePayment(final LocalDate transactionDate) {
+        return transactionDate.isAfter(getDueDate());
+    }
+
+    private void checkIfRepaymentPeriodObligationsAreMet(final LocalDate transactionDate, final MonetaryCurrency currency) {
+        this.obligationsMet = getTotalOutstanding(currency).isZero();
+        if (this.obligationsMet) {
+            this.obligationsMetOnDate = transactionDate.toDate();
+        }
+        else {
+            this.obligationsMetOnDate = null;
+        }
+    }
+
+    public void updateDueDate(final LocalDate newDueDate) {
+        if (newDueDate != null) {
+            this.dueDate = newDueDate.toDate();
+        }
+    }
+
+    public void updateFromDate(final LocalDate newFromDate) {
+        if (newFromDate != null) {
+            this.fromDate = newFromDate.toDate();
+        }
+    }
+
+    public Money getTotalPaidInAdvance(final MonetaryCurrency currency) {
+        return Money.of(currency, this.totalPaidInAdvance);
+    }
+
+    public Money getTotalPaidLate(final MonetaryCurrency currency) {
+        return Money.of(currency, this.totalPaidLate);
+    }
+
+    public boolean isRecalculatedInterestComponent() {
+        return this.recalculatedInterestComponent;
+    }
+
+    public void setRecalculatedInterestComponent(boolean recalculatedInterestComponent) {
+        this.recalculatedInterestComponent = recalculatedInterestComponent;
+    }
+
+    public void updateInstallmentNumber(final Integer installmentNumber) {
+        if (installmentNumber != null) {
+            this.installmentNumber = installmentNumber;
+        }
+    }
+
+    public void updateInterestCharged(final BigDecimal interestCharged) {
+        this.interestCharged = interestCharged;
+    }
+
+    public void updateObligationMet(final Boolean obligationMet) {
+        this.obligationsMet = obligationMet;
+    }
+
+    public void updateObligationMetOnDate(final LocalDate obligationsMetOnDate) {
+        this.obligationsMetOnDate = (obligationsMetOnDate != null) ? obligationsMetOnDate.toDate() : null;
+    }
+
+    public void updateInterestWrittenOff(final BigDecimal interestWrittenOff) {
+        this.interestWrittenOff = interestWrittenOff;
+    }
+
+    public void updatePrincipal(final BigDecimal principal) {
+        this.principal = principal;
+    }
+
+    public static Comparator<LoanRepaymentScheduleInstallment> installmentNumberComparator = new Comparator<LoanRepaymentScheduleInstallment>() {
+
+        @Override
+        public int compare(LoanRepaymentScheduleInstallment arg0, LoanRepaymentScheduleInstallment arg1) {
+
+            return arg0.getInstallmentNumber().compareTo(arg1.getInstallmentNumber());
+        }
+    };
+
+    public BigDecimal getTotalPaidInAdvance() {
+        return this.totalPaidInAdvance;
+    }
+
+    public BigDecimal getTotalPaidLate() {
+        return this.totalPaidLate;
+    }
+
+    public LocalDate getObligationsMetOnDate() {
+        LocalDate obligationsMetOnDate = null;
+
+        if (this.obligationsMetOnDate != null) {
+            obligationsMetOnDate = new LocalDate(this.obligationsMetOnDate);
+        }
+
+        return obligationsMetOnDate;
+    }
+    
+     /********** UNPAY COMPONENTS ****/
+    
+    public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money penaltyPortionOfTransactionDeducted = Money.zero(currency);
+
+        final Money penaltyChargesCompleted = getPenaltyChargesPaid(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesCompleted)) {
+            this.penaltyChargesPaid = Money.zero(currency).getAmount();
+            penaltyPortionOfTransactionDeducted = penaltyChargesCompleted;
+        } else {
+            this.penaltyChargesPaid = penaltyChargesCompleted.minus(transactionAmountRemaining).getAmount();
+            penaltyPortionOfTransactionDeducted = transactionAmountRemaining;
+        }
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        return penaltyPortionOfTransactionDeducted;
+    }
+
+    public Money unpayFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money feePortionOfTransactionDeducted = Money.zero(currency);
+
+        final Money feeChargesCompleted = getFeeChargesPaid(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesCompleted)) {
+            this.feeChargesPaid = Money.zero(currency).getAmount();
+            feePortionOfTransactionDeducted = feeChargesCompleted;
+        } else {
+            this.feeChargesPaid = feeChargesCompleted.minus(transactionAmountRemaining).getAmount();
+            feePortionOfTransactionDeducted = transactionAmountRemaining;
+        }
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, feePortionOfTransactionDeducted);
+
+        return feePortionOfTransactionDeducted;
+    }
+
+    public Money unpayInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money interestPortionOfTransactionDeducted = Money.zero(currency);
+
+        final Money interestCompleted = getInterestPaid(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestCompleted)) {
+            this.interestPaid = Money.zero(currency).getAmount();
+            interestPortionOfTransactionDeducted = interestCompleted;
+        } else {
+            this.interestPaid = interestCompleted.minus(transactionAmountRemaining).getAmount();
+            interestPortionOfTransactionDeducted = transactionAmountRemaining;
+        }
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, interestPortionOfTransactionDeducted);
+
+        return interestPortionOfTransactionDeducted;
+    }
+
+    public Money unpayPrincipalComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money principalPortionOfTransactionDeducted = Money.zero(currency);
+
+        final Money principalCompleted = getPrincipalCompleted(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalCompleted)) {
+            this.principalCompleted = Money.zero(currency).getAmount();
+            principalPortionOfTransactionDeducted = principalCompleted;
+        } else {
+            this.principalCompleted = principalCompleted.minus(transactionAmountRemaining).getAmount();
+            principalPortionOfTransactionDeducted = transactionAmountRemaining;
+        }
+
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+        reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, principalPortionOfTransactionDeducted);
+
+        return principalPortionOfTransactionDeducted;
+    }
+    
+    private void reduceAdvanceAndLateTotalsForRepaymentPeriod(final LocalDate transactionDate, final MonetaryCurrency currency,
+            final Money amountDeductedInRepaymentPeriod) {
+    
+        
+        if (isInAdvance(transactionDate)) {
+                Money mTotalPaidInAdvance = Money.of(currency,this.totalPaidInAdvance);
+            
+                if(mTotalPaidInAdvance.isLessThan(amountDeductedInRepaymentPeriod) || mTotalPaidInAdvance.isEqualTo(amountDeductedInRepaymentPeriod))
+                        this.totalPaidInAdvance = Money.zero(currency).getAmount();
+                else
+                        this.totalPaidInAdvance = mTotalPaidInAdvance.minus(amountDeductedInRepaymentPeriod).getAmount();
+        } else if (isLatePayment(transactionDate)) {
+                Money mTotalPaidLate = Money.of(currency,this.totalPaidLate);
+                
+                if(mTotalPaidLate.isLessThan(amountDeductedInRepaymentPeriod) || mTotalPaidLate.isEqualTo(amountDeductedInRepaymentPeriod))
+                        this.totalPaidLate =  Money.zero(currency).getAmount();
+                else
+                        this.totalPaidLate = mTotalPaidLate.minus(amountDeductedInRepaymentPeriod).getAmount();
+        }
+    }
+    
+    public Money getDue(MonetaryCurrency currency) {
+        return getPrincipal(currency).plus(getInterestCharged(currency)).plus(getFeeChargesCharged(currency)).plus(getPenaltyChargesCharged(currency));
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallmentRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallmentRepository.java
new file mode 100755
index 0000000..08fe459
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallmentRepository.java
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanRepaymentScheduleInstallmentRepository extends JpaRepository<LoanRepaymentScheduleInstallment, Long>,
+        JpaSpecificationExecutor<LoanRepaymentScheduleInstallment> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
new file mode 100644
index 0000000..e8a5aa3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
@@ -0,0 +1,250 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.joda.time.LocalDate;
+
+/**
+ * A wrapper around loan schedule related data exposing needed behaviour by
+ * loan.
+ */
+public class LoanRepaymentScheduleProcessingWrapper {
+
+    public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate,
+            final List<LoanRepaymentScheduleInstallment> repaymentPeriods, final Set<LoanCharge> loanCharges) {
+
+        Money totalInterest = Money.zero(currency);
+        Money totalPrincipal = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) {
+            totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
+            totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
+        }
+        LocalDate startDate = disbursementDate;
+        for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
+
+            final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
+                    currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent());
+            final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges,
+                    currency, !period.isRecalculatedInterestComponent());
+            final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(),
+                    loanCharges, currency, !period.isRecalculatedInterestComponent());
+
+            final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(),
+                    loanCharges, currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent());
+            final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(),
+                    loanCharges, currency, !period.isRecalculatedInterestComponent());
+            final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate,
+                    period.getDueDate(), loanCharges, currency, !period.isRecalculatedInterestComponent());
+
+            period.updateChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod,
+                    feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod,
+                    penaltyChargesWrittenOffForRepaymentPeriod);
+
+            startDate = period.getDueDate();
+        }
+    }
+
+    private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
+            final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal,
+            final Money totalInterest, boolean isInstallmentChargeApplicable) {
+
+        Money cumulative = Money.zero(monetaryCurrency);
+
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
+                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
+                    if (loanCharge.getChargeCalculation().isPercentageBased()) {
+                        BigDecimal amount = BigDecimal.ZERO;
+                        if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
+                            amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount()).add(
+                                    period.getInterestCharged(monetaryCurrency).getAmount());
+                        } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
+                            amount = amount.add(period.getInterestCharged(monetaryCurrency).getAmount());
+                        } else {
+                            amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount());
+                        }
+                        BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
+                        cumulative = cumulative.plus(loanChargeAmt);
+                    } else {
+                        cumulative = cumulative.plus(loanCharge.amountOrPercentage());
+                    }
+                } else if (loanCharge.isOverdueInstallmentCharge()
+                        && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
+                        && loanCharge.getChargeCalculation().isPercentageBased()) {
+                    cumulative = cumulative.plus(loanCharge.chargeAmount());
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
+                        && loanCharge.getChargeCalculation().isPercentageBased()) {
+                    BigDecimal amount = BigDecimal.ZERO;
+                    if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
+                        amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount());
+                    } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
+                        amount = amount.add(totalInterest.getAmount());
+                    } else {
+                        amount = amount.add(totalPrincipal.getAmount());
+                    }
+                    BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
+                    cumulative = cumulative.plus(loanChargeAmt);
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
+                    cumulative = cumulative.plus(loanCharge.amount());
+                }
+            }
+        }
+
+        return cumulative;
+    }
+
+    private Money cumulativeFeeChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd,
+            final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable) {
+
+        Money cumulative = Money.zero(currency);
+
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
+                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
+                    LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
+                    if (loanChargePerInstallment != null) {
+                        cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
+                    }
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
+                    cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
+                }
+            }
+        }
+
+        return cumulative;
+    }
+
+    private Money cumulativeFeeChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd,
+            final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable) {
+
+        Money cumulative = Money.zero(currency);
+
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
+                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
+                    LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
+                    if (loanChargePerInstallment != null) {
+                        cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
+                    }
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
+                    cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency));
+                }
+            }
+        }
+
+        return cumulative;
+    }
+
+    private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
+            final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period,
+            final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable) {
+
+        Money cumulative = Money.zero(currency);
+
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isPenaltyCharge()) {
+                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
+                    if (loanCharge.getChargeCalculation().isPercentageBased()) {
+                        BigDecimal amount = BigDecimal.ZERO;
+                        if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
+                            amount = amount.add(period.getPrincipal(currency).getAmount()).add(
+                                    period.getInterestCharged(currency).getAmount());
+                        } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
+                            amount = amount.add(period.getInterestCharged(currency).getAmount());
+                        } else {
+                            amount = amount.add(period.getPrincipal(currency).getAmount());
+                        }
+                        BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
+                        cumulative = cumulative.plus(loanChargeAmt);
+                    } else {
+                        cumulative = cumulative.plus(loanCharge.amountOrPercentage());
+                    }
+                } else if (loanCharge.isOverdueInstallmentCharge()
+                        && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
+                        && loanCharge.getChargeCalculation().isPercentageBased()) {
+                    cumulative = cumulative.plus(loanCharge.chargeAmount());
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
+                        && loanCharge.getChargeCalculation().isPercentageBased()) {
+                    BigDecimal amount = BigDecimal.ZERO;
+                    if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
+                        amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount());
+                    } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
+                        amount = amount.add(totalInterest.getAmount());
+                    } else {
+                        amount = amount.add(totalPrincipal.getAmount());
+                    }
+                    BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
+                    cumulative = cumulative.plus(loanChargeAmt);
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
+                    cumulative = cumulative.plus(loanCharge.amount());
+                }
+            }
+        }
+
+        return cumulative;
+    }
+
+    private Money cumulativePenaltyChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd,
+            final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable) {
+
+        Money cumulative = Money.zero(currency);
+
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isPenaltyCharge()) {
+                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
+                    LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
+                    if (loanChargePerInstallment != null) {
+                        cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
+                    }
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
+                    cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
+                }
+            }
+        }
+
+        return cumulative;
+    }
+
+    private Money cumulativePenaltyChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd,
+            final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable) {
+
+        Money cumulative = Money.zero(currency);
+
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isPenaltyCharge()) {
+                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
+                    LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
+                    if (loanChargePerInstallment != null) {
+                        cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
+                    }
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
+                    cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency));
+                }
+            }
+        }
+
+        return cumulative;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessorFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessorFactory.java
new file mode 100644
index 0000000..45d7e6c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessorFactory.java
@@ -0,0 +1,73 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.EarlyPaymentLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
+import org.springframework.stereotype.Component;
+
+@Component
+public class LoanRepaymentScheduleTransactionProcessorFactory {
+
+    public LoanRepaymentScheduleTransactionProcessor determineProcessor(
+            final LoanTransactionProcessingStrategy transactionProcessingStrategy) {
+
+        LoanRepaymentScheduleTransactionProcessor processor = new PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor();
+
+        if (transactionProcessingStrategy != null) {
+
+            if (transactionProcessingStrategy.isStandardStrategy()) {
+                processor = new FineractStyleLoanRepaymentScheduleTransactionProcessor();
+            }
+
+            if (transactionProcessingStrategy.isHeavensfamilyStrategy()) {
+                processor = new HeavensFamilyLoanRepaymentScheduleTransactionProcessor();
+            }
+
+            if (transactionProcessingStrategy.isEarlyPaymentStrategy()) {
+                processor = new EarlyPaymentLoanRepaymentScheduleTransactionProcessor();
+            }
+
+            if (transactionProcessingStrategy.isCreocoreStrategy()) {
+                processor = new CreocoreLoanRepaymentScheduleTransactionProcessor();
+            }
+
+            if (transactionProcessingStrategy.isIndianRBIStrategy()) {
+                processor = new RBILoanRepaymentScheduleTransactionProcessor();
+            }
+
+            if (transactionProcessingStrategy.isPrincipalInterestPenaltiesFeesOrderStrategy()) {
+                processor = new PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor();
+            }
+
+            if (transactionProcessingStrategy.isInterestPrincipalPenaltiesFeesOrderStrategy()) {
+                processor = new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor();
+            }
+        }
+
+        return processor;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
new file mode 100755
index 0000000..845cc67
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
@@ -0,0 +1,148 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+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 LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificationExecutor<Loan> {
+
+    public static final String FIND_GROUP_LOANS_DISBURSED_AFTER = "from Loan l where l.actualDisbursementDate > :disbursementDate and "
+            + "l.group.id = :groupId and l.loanType = :loanType order by l.actualDisbursementDate";
+
+    public static final String FIND_CLIENT_OR_JLG_LOANS_DISBURSED_AFTER = "from Loan l where l.actualDisbursementDate > :disbursementDate and "
+            + "l.client.id = :clientId order by l.actualDisbursementDate";
+
+    public static final String FIND_MAX_GROUP_LOAN_COUNTER_QUERY = "Select MAX(l.loanCounter) from Loan l where l.group.id = :groupId "
+            + "and l.loanType = :loanType";
+
+    public static final String FIND_MAX_GROUP_LOAN_PRODUCT_COUNTER_QUERY = "Select MAX(l.loanProductCounter) from Loan l where "
+            + "l.group.id = :groupId and l.loanType = :loanType and l.loanProduct.id = :productId";
+
+    public static final String FIND_MAX_CLIENT_OR_JLG_LOAN_COUNTER_QUERY = "Select MAX(l.loanCounter) from Loan l where "
+            + "l.client.id = :clientId";
+
+    public static final String FIND_MAX_CLIENT_OR_JLG_LOAN_PRODUCT_COUNTER_QUERY = "Select MAX(l.loanProductCounter) from Loan l where "
+            + "l.client.id = :clientId and l.loanProduct.id = :productId";
+
+    public static final String FIND_GROUP_LOANS_TO_UPDATE = "from Loan l where l.loanCounter > :loanCounter and "
+            + "l.group.id = :groupId and l.loanType = :groupLoanType order by l.loanCounter";
+
+    public static final String FIND_CLIENT_OR_JLG_LOANS_TO_UPDATE = "from Loan l where l.loanCounter > :loanCounter and "
+            + "l.client.id = :clientId order by l.loanCounter";
+
+    public static final String FIND_GROUP_LOANS_TO_UPDATE_LOANPRODUCT_COUNTER = "from Loan l where l.loanProductCounter > :loanProductCounter"
+            + " and l.group.id = :groupId and l.loanType = :groupLoanType and l.loanCounter is NULL order by l.loanProductCounter";
+
+    public static final String FIND_CLIENT_LOANS_TO_UPDATE_LOANPRODUCT_COUNTER = "from Loan l where l.loanProductCounter > :loanProductCounter"
+            + " and l.client.id = :clientId and l.loanCounter is NULL order by l.loanProductCounter";
+
+    public static final String FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_CLIENT = "Select loan.loanProduct.id from Loan loan where "
+            + "loan.client.id = :clientId and loan.loanStatus = :loanStatus group by loan.loanProduct.id";
+
+    public static final String FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP = "Select loan.loanProduct.id from Loan loan where "
+            + "loan.group.id = :groupId and loan.loanStatus = :loanStatus and loan.client.id is NULL group by loan.loanProduct.id";
+
+    public static final String DOES_CLIENT_HAVE_NON_CLOSED_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.client.id = :clientId and loan.loanStatus in (100,200,300,303,304,700)";
+
+    public static final String DOES_PRODUCT_HAVE_NON_CLOSED_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.loanProduct.id = :productId and loan.loanStatus in (100,200,300,303,304,700)";
+
+    @Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
+    List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
+            @Param("loanType") Integer loanType);
+
+    @Query(FIND_CLIENT_OR_JLG_LOANS_DISBURSED_AFTER)
+    List<Loan> getClientOrJLGLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("clientId") Long clientId);
+
+    @Query(FIND_MAX_GROUP_LOAN_COUNTER_QUERY)
+    Integer getMaxGroupLoanCounter(@Param("groupId") Long groupId, @Param("loanType") Integer loanType);
+
+    @Query(FIND_MAX_GROUP_LOAN_PRODUCT_COUNTER_QUERY)
+    Integer getMaxGroupLoanProductCounter(@Param("productId") Long productId, @Param("groupId") Long groupId,
+            @Param("loanType") Integer loanType);
+
+    @Query(FIND_MAX_CLIENT_OR_JLG_LOAN_COUNTER_QUERY)
+    Integer getMaxClientOrJLGLoanCounter(@Param("clientId") Long clientId);
+
+    @Query(FIND_MAX_CLIENT_OR_JLG_LOAN_PRODUCT_COUNTER_QUERY)
+    Integer getMaxClientOrJLGLoanProductCounter(@Param("productId") Long productId, @Param("clientId") Long clientId);
+
+    @Query(FIND_GROUP_LOANS_TO_UPDATE)
+    List<Loan> getGroupLoansToUpdateLoanCounter(@Param("loanCounter") Integer loanCounter, @Param("groupId") Long groupId,
+            @Param("groupLoanType") Integer groupLoanType);
+
+    @Query(FIND_CLIENT_OR_JLG_LOANS_TO_UPDATE)
+    List<Loan> getClientOrJLGLoansToUpdateLoanCounter(@Param("loanCounter") Integer loanCounter, @Param("clientId") Long clientId);
+
+    @Query(FIND_GROUP_LOANS_TO_UPDATE_LOANPRODUCT_COUNTER)
+    List<Loan> getGroupLoansToUpdateLoanProductCounter(@Param("loanProductCounter") Integer loanProductCounter,
+            @Param("groupId") Long groupId, @Param("groupLoanType") Integer groupLoanType);
+
+    @Query(FIND_CLIENT_LOANS_TO_UPDATE_LOANPRODUCT_COUNTER)
+    List<Loan> getClientLoansToUpdateLoanProductCounter(@Param("loanProductCounter") Integer loanProductCounter,
+            @Param("clientId") Long clientId);
+
+    @Query("from Loan loan where loan.client.id = :clientId and loan.group.id = :groupId")
+    List<Loan> findByClientIdAndGroupId(@Param("clientId") Long clientId, @Param("groupId") Long groupId);
+
+    @Query("from Loan loan where loan.client.id = :clientId and loan.group.id = :groupId and loan.loanStatus IN :loanStatuses")
+    List<Loan> findByClientIdAndGroupIdAndLoanStatus(@Param("clientId") Long clientId, @Param("groupId") Long groupId,
+            @Param("loanStatuses") Collection<Integer> loanStatuses);
+
+    @Query("from Loan loan where loan.client.id = :clientId")
+    List<Loan> findLoanByClientId(@Param("clientId") Long clientId);
+
+    @Query("from Loan loan where loan.group.id = :groupId and loan.client.id is null")
+    List<Loan> findByGroupId(@Param("groupId") Long groupId);
+
+    @Query("from Loan loan where loan.id IN :ids and loan.loanStatus IN :loanStatuses and loan.loanType IN :loanTypes")
+    List<Loan> findByIdsAndLoanStatusAndLoanType(@Param("ids") Collection<Long> ids,
+            @Param("loanStatuses") Collection<Integer> loanStatuses, @Param("loanTypes") Collection<Integer> loanTypes);
+
+    @Query("select loan.id from Loan loan where loan.actualDisbursementDate > :disbursalDate order by loan.actualDisbursementDate")
+    List<Long> getLoansDisbursedAfter(@Param("disbursalDate") Date disbursalDate);
+
+    @Query("from Loan loan where loan.client.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
+    List<Loan> findByClientOfficeIdsAndLoanStatus(@Param("officeIds") Collection<Long> officeIds,
+            @Param("loanStatuses") Collection<Integer> loanStatuses);
+
+    @Query("from Loan loan where loan.group.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
+    List<Loan> findByGroupOfficeIdsAndLoanStatus(@Param("officeIds") Collection<Long> officeIds,
+            @Param("loanStatuses") Collection<Integer> loanStatuses);
+
+    /*** FIXME: Add more appropriate names for the query ***/
+    @Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_CLIENT)
+    List<Long> findActiveLoansLoanProductIdsByClient(@Param("clientId") Long clientId, @Param("loanStatus") Integer loanStatus);
+
+    @Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP)
+    List<Long> findActiveLoansLoanProductIdsByGroup(@Param("groupId") Long groupId, @Param("loanStatus") Integer loanStatus);
+
+    @Query(DOES_CLIENT_HAVE_NON_CLOSED_LOANS)
+    boolean doNonClosedLoanAccountsExistForClient(@Param("clientId") Long clientId);
+    
+    @Query(DOES_PRODUCT_HAVE_NON_CLOSED_LOANS)
+    boolean doNonClosedLoanAccountsExistForProduct(@Param("productId") Long productId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
new file mode 100755
index 0000000..f2438b7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link LoanRepository} that adds NULL checking and Error handling
+ * capabilities
+ * </p>
+ */
+@Service
+public class LoanRepositoryWrapper {
+
+    private final LoanRepository repository;
+
+    @Autowired
+    public LoanRepositoryWrapper(final LoanRepository repository) {
+        this.repository = repository;
+    }
+
+    public Loan findOneWithNotFoundDetection(final Long id) {
+        final Loan loan = this.repository.findOne(id);
+        if (loan == null) { throw new LoanNotFoundException(id); }
+        return loan;
+    }
+
+    public Collection<Loan> findActiveLoansByLoanIdAndGroupId(Long clientId, Long groupId) {
+        final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
+                LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue(), LoanStatus.OVERPAID.getValue()));
+        final Collection<Loan> loans = this.repository.findByClientIdAndGroupIdAndLoanStatus(clientId, groupId, loanStatuses);
+        return loans;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanStatus.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanStatus.java
new file mode 100644
index 0000000..6be1b07
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanStatus.java
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+/**
+ * Enum representation of loan status states.
+ */
+public enum LoanStatus {
+
+    INVALID(0, "loanStatusType.invalid"), //
+    SUBMITTED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), //
+    APPROVED(200, "loanStatusType.approved"), //
+    ACTIVE(300, "loanStatusType.active"), //
+    TRANSFER_IN_PROGRESS(303, "loanStatusType.transfer.in.progress"), //
+    TRANSFER_ON_HOLD(304, "loanStatusType.transfer.on.hold"), //
+    WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), //
+    REJECTED(500, "loanStatusType.rejected"), //
+    CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), //
+    CLOSED_WRITTEN_OFF(601, "loanStatusType.closed.written.off"), //
+    CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT(602, "loanStatusType.closed.reschedule.outstanding.amount"), //
+    OVERPAID(700, "loanStatusType.overpaid");
+
+    private final Integer value;
+    private final String code;
+
+    public static LoanStatus fromInt(final Integer statusValue) {
+
+        LoanStatus enumeration = LoanStatus.INVALID;
+        switch (statusValue) {
+            case 100:
+                enumeration = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
+            break;
+            case 200:
+                enumeration = LoanStatus.APPROVED;
+            break;
+            case 300:
+                enumeration = LoanStatus.ACTIVE;
+            break;
+            case 303:
+                enumeration = LoanStatus.TRANSFER_IN_PROGRESS;
+            break;
+            case 304:
+                enumeration = LoanStatus.TRANSFER_ON_HOLD;
+            break;
+            case 400:
+                enumeration = LoanStatus.WITHDRAWN_BY_CLIENT;
+            break;
+            case 500:
+                enumeration = LoanStatus.REJECTED;
+            break;
+            case 600:
+                enumeration = LoanStatus.CLOSED_OBLIGATIONS_MET;
+            break;
+            case 601:
+                enumeration = LoanStatus.CLOSED_WRITTEN_OFF;
+            break;
+            case 602:
+                enumeration = LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT;
+            break;
+            case 700:
+                enumeration = LoanStatus.OVERPAID;
+            break;
+        }
+        return enumeration;
+    }
+
+    private LoanStatus(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final LoanStatus state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isSubmittedAndPendingApproval() {
+        return this.value.equals(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue());
+    }
+
+    public boolean isApproved() {
+        return this.value.equals(LoanStatus.APPROVED.getValue());
+    }
+
+    public boolean isActive() {
+        return this.value.equals(LoanStatus.ACTIVE.getValue());
+    }
+
+    public boolean isClosed() {
+        return isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutsandingAmountMarkedForReschedule();
+    }
+
+    public boolean isClosedObligationsMet() {
+        return this.value.equals(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue());
+    }
+
+    public boolean isClosedWrittenOff() {
+        return this.value.equals(LoanStatus.CLOSED_WRITTEN_OFF.getValue());
+    }
+
+    public boolean isClosedWithOutsandingAmountMarkedForReschedule() {
+        return this.value.equals(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getValue());
+    }
+
+    public boolean isWithdrawnByClient() {
+        return this.value.equals(LoanStatus.WITHDRAWN_BY_CLIENT.getValue());
+    }
+
+    public boolean isRejected() {
+        return this.value.equals(LoanStatus.REJECTED.getValue());
+    }
+
+    public boolean isActiveOrAwaitingApprovalOrDisbursal() {
+        return isApproved() || isSubmittedAndPendingApproval() || isActive();
+    }
+
+    public boolean isTransferInProgress() {
+        return this.value.equals(LoanStatus.TRANSFER_IN_PROGRESS.getValue());
+    }
+
+    public boolean isTransferOnHold() {
+        return this.value.equals(LoanStatus.TRANSFER_ON_HOLD.getValue());
+    }
+
+    public boolean isUnderTransfer() {
+        return isTransferInProgress() || isTransferOnHold();
+    }
+
+    public boolean isOverpaid() {
+        return this.value.equals(LoanStatus.OVERPAID.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java
new file mode 100644
index 0000000..769a7fe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java
@@ -0,0 +1,343 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+/**
+ * Encapsulates all the summary details of a {@link Loan}.
+ * 
+ * {@link LoanSummary} fields are updated through a scheduled job. see -
+ * {@link UpdateLoanSummariesScheduledJob}.
+ */
+@Embeddable
+public final class LoanSummary {
+
+    // derived totals fields
+    @Column(name = "principal_disbursed_derived", scale = 6, precision = 19)
+    private BigDecimal totalPrincipalDisbursed;
+
+    @Column(name = "principal_repaid_derived", scale = 6, precision = 19)
+    private BigDecimal totalPrincipalRepaid;
+
+    @Column(name = "principal_writtenoff_derived", scale = 6, precision = 19)
+    private BigDecimal totalPrincipalWrittenOff;
+
+    @Column(name = "principal_outstanding_derived", scale = 6, precision = 19)
+    private BigDecimal totalPrincipalOutstanding;
+
+    @Column(name = "interest_charged_derived", scale = 6, precision = 19)
+    private BigDecimal totalInterestCharged;
+
+    @Column(name = "interest_repaid_derived", scale = 6, precision = 19)
+    private BigDecimal totalInterestRepaid;
+
+    @Column(name = "interest_waived_derived", scale = 6, precision = 19)
+    private BigDecimal totalInterestWaived;
+
+    @Column(name = "interest_writtenoff_derived", scale = 6, precision = 19)
+    private BigDecimal totalInterestWrittenOff;
+
+    @Column(name = "interest_outstanding_derived", scale = 6, precision = 19)
+    private BigDecimal totalInterestOutstanding;
+
+    @Column(name = "fee_charges_charged_derived", scale = 6, precision = 19)
+    private BigDecimal totalFeeChargesCharged;
+
+    @Column(name = "total_charges_due_at_disbursement_derived", scale = 6, precision = 19)
+    private BigDecimal totalFeeChargesDueAtDisbursement;
+
+    @Column(name = "fee_charges_repaid_derived", scale = 6, precision = 19)
+    private BigDecimal totalFeeChargesRepaid;
+
+    @Column(name = "fee_charges_waived_derived", scale = 6, precision = 19)
+    private BigDecimal totalFeeChargesWaived;
+
+    @Column(name = "fee_charges_writtenoff_derived", scale = 6, precision = 19)
+    private BigDecimal totalFeeChargesWrittenOff;
+
+    @Column(name = "fee_charges_outstanding_derived", scale = 6, precision = 19)
+    private BigDecimal totalFeeChargesOutstanding;
+
+    @Column(name = "penalty_charges_charged_derived", scale = 6, precision = 19)
+    private BigDecimal totalPenaltyChargesCharged;
+
+    @Column(name = "penalty_charges_repaid_derived", scale = 6, precision = 19)
+    private BigDecimal totalPenaltyChargesRepaid;
+
+    @Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19)
+    private BigDecimal totalPenaltyChargesWaived;
+
+    @Column(name = "penalty_charges_writtenoff_derived", scale = 6, precision = 19)
+    private BigDecimal totalPenaltyChargesWrittenOff;
+
+    @Column(name = "penalty_charges_outstanding_derived", scale = 6, precision = 19)
+    private BigDecimal totalPenaltyChargesOutstanding;
+
+    @Column(name = "total_expected_repayment_derived", scale = 6, precision = 19)
+    private BigDecimal totalExpectedRepayment;
+
+    @Column(name = "total_repayment_derived", scale = 6, precision = 19)
+    private BigDecimal totalRepayment;
+
+    @Column(name = "total_expected_costofloan_derived", scale = 6, precision = 19)
+    private BigDecimal totalExpectedCostOfLoan;
+
+    @Column(name = "total_costofloan_derived", scale = 6, precision = 19)
+    private BigDecimal totalCostOfLoan;
+
+    @Column(name = "total_waived_derived", scale = 6, precision = 19)
+    private BigDecimal totalWaived;
+
+    @Column(name = "total_writtenoff_derived", scale = 6, precision = 19)
+    private BigDecimal totalWrittenOff;
+
+    @Column(name = "total_outstanding_derived", scale = 6, precision = 19)
+    private BigDecimal totalOutstanding;
+
+    public static LoanSummary create(final BigDecimal totalFeeChargesDueAtDisbursement) {
+        return new LoanSummary(totalFeeChargesDueAtDisbursement);
+    }
+
+    protected LoanSummary() {
+        //
+    }
+
+    private LoanSummary(final BigDecimal totalFeeChargesDueAtDisbursement) {
+        this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement;
+    }
+
+    public void updateTotalFeeChargesDueAtDisbursement(final BigDecimal totalFeeChargesDueAtDisbursement) {
+        this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement;
+    }
+
+    public Money getTotalFeeChargesDueAtDisbursement(final MonetaryCurrency currency) {
+        return Money.of(currency, this.totalFeeChargesDueAtDisbursement);
+    }
+
+    public Money getTotalOutstanding(final MonetaryCurrency currency) {
+        return Money.of(currency, this.totalOutstanding);
+    }
+
+    public boolean isRepaidInFull(final MonetaryCurrency currency) {
+        return getTotalOutstanding(currency).isZero();
+    }
+
+    public BigDecimal getTotalInterestCharged() {
+        return this.totalInterestCharged;
+    }
+
+    public BigDecimal getTotalPrincipalOutstanding() {
+        return this.totalPrincipalOutstanding;
+    }
+
+    public BigDecimal getTotalInterestOutstanding() {
+        return this.totalInterestOutstanding;
+    }
+
+    public BigDecimal getTotalFeeChargesOutstanding() {
+        return this.totalFeeChargesOutstanding;
+    }
+
+    public BigDecimal getTotalPenaltyChargesOutstanding() {
+        return this.totalPenaltyChargesOutstanding;
+    }
+
+    public BigDecimal getTotalOutstanding() {
+        return this.totalOutstanding;
+    }
+
+    /**
+     * All fields but <code>totalFeeChargesDueAtDisbursement</code> should be
+     * reset.
+     */
+    public void zeroFields() {
+        this.totalPrincipalDisbursed = BigDecimal.ZERO;
+        this.totalPrincipalRepaid = BigDecimal.ZERO;
+        this.totalPrincipalWrittenOff = BigDecimal.ZERO;
+        this.totalPrincipalOutstanding = BigDecimal.ZERO;
+        this.totalInterestCharged = BigDecimal.ZERO;
+        this.totalInterestRepaid = BigDecimal.ZERO;
+        this.totalInterestWaived = BigDecimal.ZERO;
+        this.totalInterestWrittenOff = BigDecimal.ZERO;
+        this.totalInterestOutstanding = BigDecimal.ZERO;
+        this.totalFeeChargesCharged = BigDecimal.ZERO;
+        this.totalFeeChargesRepaid = BigDecimal.ZERO;
+        this.totalFeeChargesWaived = BigDecimal.ZERO;
+        this.totalFeeChargesWrittenOff = BigDecimal.ZERO;
+        this.totalFeeChargesOutstanding = BigDecimal.ZERO;
+        this.totalPenaltyChargesCharged = BigDecimal.ZERO;
+        this.totalPenaltyChargesRepaid = BigDecimal.ZERO;
+        this.totalPenaltyChargesWaived = BigDecimal.ZERO;
+        this.totalPenaltyChargesWrittenOff = BigDecimal.ZERO;
+        this.totalPenaltyChargesOutstanding = BigDecimal.ZERO;
+        this.totalExpectedRepayment = BigDecimal.ZERO;
+        this.totalRepayment = BigDecimal.ZERO;
+        this.totalExpectedCostOfLoan = BigDecimal.ZERO;
+        this.totalCostOfLoan = BigDecimal.ZERO;
+        this.totalWaived = BigDecimal.ZERO;
+        this.totalWrittenOff = BigDecimal.ZERO;
+        this.totalOutstanding = BigDecimal.ZERO;
+    }
+
+    public void updateSummary(final MonetaryCurrency currency, final Money principal,
+            final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LoanSummaryWrapper summaryWrapper,
+            final Boolean disbursed, Set<LoanCharge> charges) {
+
+        this.totalPrincipalDisbursed = principal.getAmount();
+        this.totalPrincipalRepaid = summaryWrapper.calculateTotalPrincipalRepaid(repaymentScheduleInstallments, currency).getAmount();
+        this.totalPrincipalWrittenOff = summaryWrapper.calculateTotalPrincipalWrittenOff(repaymentScheduleInstallments, currency)
+                .getAmount();
+
+        this.totalPrincipalOutstanding = principal.minus(this.totalPrincipalRepaid).minus(this.totalPrincipalWrittenOff).getAmount();
+
+        final Money totalInterestCharged = summaryWrapper.calculateTotalInterestCharged(repaymentScheduleInstallments, currency);
+        this.totalInterestCharged = totalInterestCharged.getAmount();
+        this.totalInterestRepaid = summaryWrapper.calculateTotalInterestRepaid(repaymentScheduleInstallments, currency).getAmount();
+        this.totalInterestWaived = summaryWrapper.calculateTotalInterestWaived(repaymentScheduleInstallments, currency).getAmount();
+        this.totalInterestWrittenOff = summaryWrapper.calculateTotalInterestWrittenOff(repaymentScheduleInstallments, currency).getAmount();
+
+        this.totalInterestOutstanding = totalInterestCharged.minus(this.totalInterestRepaid).minus(this.totalInterestWaived)
+                .minus(this.totalInterestWrittenOff).getAmount();
+
+        final Money totalFeeChargesCharged = summaryWrapper.calculateTotalFeeChargesCharged(repaymentScheduleInstallments, currency).plus(
+                this.totalFeeChargesDueAtDisbursement);
+        this.totalFeeChargesCharged = totalFeeChargesCharged.getAmount();
+
+        Money totalFeeChargesRepaidAtDisbursement = summaryWrapper.calculateTotalChargesRepaidAtDisbursement(charges,currency);
+        this.totalFeeChargesRepaid = totalFeeChargesRepaidAtDisbursement.getAmount();
+        
+        if(charges != null) {
+            this.totalFeeChargesWaived = summaryWrapper.calculateTotalFeeChargesWaived(charges, currency).getAmount();    
+        }else {
+            this.totalFeeChargesWaived = BigDecimal.ZERO;
+        }
+        
+        this.totalFeeChargesWrittenOff = summaryWrapper.calculateTotalFeeChargesWrittenOff(repaymentScheduleInstallments, currency)
+                .getAmount();
+
+        this.totalFeeChargesOutstanding = totalFeeChargesCharged.minus(this.totalFeeChargesRepaid).minus(this.totalFeeChargesWaived)
+                .minus(this.totalFeeChargesWrittenOff).getAmount();
+
+        final Money totalPenaltyChargesCharged = summaryWrapper
+                .calculateTotalPenaltyChargesCharged(repaymentScheduleInstallments, currency);
+        this.totalPenaltyChargesCharged = totalPenaltyChargesCharged.getAmount();
+        this.totalPenaltyChargesRepaid = summaryWrapper.calculateTotalPenaltyChargesRepaid(repaymentScheduleInstallments, currency)
+                .getAmount();
+        this.totalPenaltyChargesWaived = summaryWrapper.calculateTotalPenaltyChargesWaived(repaymentScheduleInstallments, currency)
+                .getAmount();
+        this.totalPenaltyChargesWrittenOff = summaryWrapper.calculateTotalPenaltyChargesWrittenOff(repaymentScheduleInstallments, currency)
+                .getAmount();
+
+        this.totalPenaltyChargesOutstanding = totalPenaltyChargesCharged.minus(this.totalPenaltyChargesRepaid)
+                .minus(this.totalPenaltyChargesWaived).minus(this.totalPenaltyChargesWrittenOff).getAmount();
+
+        final Money totalExpectedRepayment = Money.of(currency, this.totalPrincipalDisbursed).plus(this.totalInterestCharged)
+                .plus(this.totalFeeChargesCharged).plus(this.totalPenaltyChargesCharged);
+        this.totalExpectedRepayment = totalExpectedRepayment.getAmount();
+
+        final Money totalRepayment = Money.of(currency, this.totalPrincipalRepaid).plus(this.totalInterestRepaid)
+                .plus(this.totalFeeChargesRepaid).plus(this.totalPenaltyChargesRepaid);
+        this.totalRepayment = totalRepayment.getAmount();
+
+        final Money totalExpectedCostOfLoan = Money.of(currency, this.totalInterestCharged).plus(this.totalFeeChargesCharged)
+                .plus(this.totalPenaltyChargesCharged);
+        this.totalExpectedCostOfLoan = totalExpectedCostOfLoan.getAmount();
+
+        final Money totalCostOfLoan = Money.of(currency, this.totalInterestRepaid).plus(this.totalFeeChargesRepaid)
+                .plus(this.totalPenaltyChargesRepaid);
+        this.totalCostOfLoan = totalCostOfLoan.getAmount();
+
+        final Money totalWaived = Money.of(currency, this.totalInterestWaived).plus(this.totalFeeChargesWaived)
+                .plus(this.totalPenaltyChargesWaived);
+        this.totalWaived = totalWaived.getAmount();
+
+        final Money totalWrittenOff = Money.of(currency, this.totalPrincipalWrittenOff).plus(this.totalInterestWrittenOff)
+                .plus(this.totalFeeChargesWrittenOff).plus(this.totalPenaltyChargesWrittenOff);
+        this.totalWrittenOff = totalWrittenOff.getAmount();
+
+        final Money totalOutstanding = Money.of(currency, this.totalPrincipalOutstanding).plus(this.totalInterestOutstanding)
+                .plus(this.totalFeeChargesOutstanding).plus(this.totalPenaltyChargesOutstanding);
+        this.totalOutstanding = totalOutstanding.getAmount();
+    }
+
+    public BigDecimal getTotalPrincipalDisbursed() {
+        return this.totalPrincipalDisbursed;
+    }
+
+    public BigDecimal getTotalPrincipalRepaid() {
+        return this.totalPrincipalRepaid;
+    }
+
+    public BigDecimal getTotalWrittenOff() {
+        return this.totalWrittenOff;
+    }
+
+    /**
+     * @return total interest repaid
+     **/
+    public BigDecimal getTotalInterestRepaid() {
+        return this.totalInterestRepaid;
+    }
+
+    public BigDecimal getTotalFeeChargesCharged() {
+        return this.totalFeeChargesCharged;
+    }
+
+    public BigDecimal getTotalPenaltyChargesCharged() {
+        return this.totalPenaltyChargesCharged;
+    }
+
+    public BigDecimal getTotalPrincipalWrittenOff() {
+        return this.totalPrincipalWrittenOff;
+    }
+
+    public BigDecimal getTotalInterestWaived() {
+        return this.totalInterestWaived;
+    }
+
+    public BigDecimal getTotalFeeChargesRepaid() {
+        return this.totalFeeChargesRepaid;
+    }
+
+    public BigDecimal getTotalFeeChargesWaived() {
+        return this.totalFeeChargesWaived;
+    }
+
+    public BigDecimal getTotalPenaltyChargesRepaid() {
+        return this.totalPenaltyChargesRepaid;
+    }
+
+    public BigDecimal getTotalPenaltyChargesWaived() {
+        return this.totalPenaltyChargesWaived;
+    }
+
+    
+    public BigDecimal getTotalExpectedRepayment() {
+        return this.totalExpectedRepayment;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java
new file mode 100644
index 0000000..2cf0362
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java
@@ -0,0 +1,248 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.joda.time.LocalDate;
+import org.springframework.stereotype.Component;
+
+/**
+ * A wrapper for dealing with side-effect free functionality related to a loans
+ * transactions and repayment schedule.
+ */
+@Component
+public final class LoanSummaryWrapper {
+
+    public Money calculateTotalPrincipalRepaid(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getPrincipalCompleted(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalPrincipalWrittenOff(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getPrincipalWrittenOff(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalPrincipalOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency, final LocalDate overdueAsOf) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            if (installment.isOverdueOn(overdueAsOf)) {
+                total = total.plus(installment.getPrincipalOutstanding(currency));
+            }
+        }
+        return total;
+    }
+
+    public Money calculateTotalInterestCharged(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getInterestCharged(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalInterestRepaid(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getInterestPaid(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalInterestWaived(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getInterestWaived(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalInterestWrittenOff(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getInterestWrittenOff(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalInterestOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency, final LocalDate overdueAsOf) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            if (installment.isOverdueOn(overdueAsOf)) {
+                total = total.plus(installment.getInterestOutstanding(currency));
+            }
+        }
+        return total;
+    }
+
+    public Money calculateTotalFeeChargesCharged(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getFeeChargesCharged(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalFeeChargesRepaid(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getFeeChargesPaid(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalFeeChargesWaived(Set<LoanCharge> charges,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanCharge charge : charges) {
+            if(charge.isActive() && !charge.isPenaltyCharge()){
+                total = total.plus(charge.getAmountWaived(currency));
+            }
+        }
+        return total;
+    }
+
+    public Money calculateTotalFeeChargesWrittenOff(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getFeeChargesWrittenOff(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalFeeChargesOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency, final LocalDate overdueAsOf) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            if (installment.isOverdueOn(overdueAsOf)) {
+                total = total.plus(installment.getFeeChargesOutstanding(currency));
+            }
+        }
+        return total;
+    }
+
+    public Money calculateTotalPenaltyChargesCharged(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getPenaltyChargesCharged(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalPenaltyChargesRepaid(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getPenaltyChargesPaid(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalPenaltyChargesWaived(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getPenaltyChargesWaived(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalPenaltyChargesWrittenOff(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getPenaltyChargesWrittenOff(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalPenaltyChargesOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency, final LocalDate overdueAsOf) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            if (installment.isOverdueOn(overdueAsOf)) {
+                total = total.plus(installment.getPenaltyChargesOutstanding(currency));
+            }
+        }
+        return total;
+    }
+
+    public Money calculateTotalOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency, final LocalDate overdueAsOf) {
+
+        final Money principalOverdue = calculateTotalPrincipalOverdueOn(repaymentScheduleInstallments, currency, overdueAsOf);
+        final Money interestOverdue = calculateTotalInterestOverdueOn(repaymentScheduleInstallments, currency, overdueAsOf);
+        final Money feeChargesOverdue = calculateTotalFeeChargesOverdueOn(repaymentScheduleInstallments, currency, overdueAsOf);
+        final Money penaltyChargesOverdue = calculateTotalPenaltyChargesOverdueOn(repaymentScheduleInstallments, currency, overdueAsOf);
+
+        return principalOverdue.plus(interestOverdue).plus(feeChargesOverdue).plus(penaltyChargesOverdue);
+    }
+
+    public LocalDate determineOverdueSinceDateFrom(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency, final LocalDate from) {
+
+        LocalDate overdueSince = null;
+        final Money totalOverdue = calculateTotalOverdueOn(repaymentScheduleInstallments, currency, from);
+        if (totalOverdue.isGreaterThanZero()) {
+            for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+                if (installment.isOverdueOn(from)) {
+                    if (overdueSince == null || overdueSince.isAfter(installment.getDueDate())) {
+                        overdueSince = installment.getDueDate();
+                    }
+                }
+            }
+        }
+
+        return overdueSince;
+    }
+
+    public Money calculateTotalChargesRepaidAtDisbursement(Set<LoanCharge> charges, MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        if(charges == null) return total ;
+        for (final LoanCharge loanCharge : charges) {
+            if (!loanCharge.isPenaltyCharge() && loanCharge.getAmountPaid(currency).isGreaterThanZero()) {
+                total = total.plus(loanCharge.getAmountPaid(currency));
+            }
+        }
+        return total;
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
new file mode 100644
index 0000000..c0df3bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
@@ -0,0 +1,96 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+public enum LoanTermVariationType {
+
+    INVALID(0, "loanTermType.invalid"), //
+    EMI_AMOUNT(1, "loanTermType.emiAmount"), //
+    INTEREST_RATE(2, "loanTermType.interestRate"), //
+    PRINCIPAL_AMOUNT(3, "loanTermType.principalAmount"), //
+    DUE_DATE(4, "loanTermType.dueDate"), //
+    INSERT_INSTALLMENT(5, "loanTermType.insertInstallment"), //
+    DELETE_INSTALLMENT(6, "loanTermType.deleteInstallment");
+
+    private final Integer value;
+    private final String code;
+
+    private LoanTermVariationType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static LoanTermVariationType fromInt(final Integer value) {
+
+        LoanTermVariationType enumeration = LoanTermVariationType.INVALID;
+        switch (value) {
+            case 1:
+                enumeration = LoanTermVariationType.EMI_AMOUNT;
+            break;
+            case 2:
+                enumeration = LoanTermVariationType.INTEREST_RATE;
+            break;
+            case 3:
+                enumeration = LoanTermVariationType.PRINCIPAL_AMOUNT;
+            break;
+            case 4:
+                enumeration = LoanTermVariationType.DUE_DATE;
+            break;
+            case 5:
+                enumeration = LoanTermVariationType.INSERT_INSTALLMENT;
+            break;
+            case 6:
+                enumeration = LoanTermVariationType.DELETE_INSTALLMENT;
+            break;
+        }
+        return enumeration;
+    }
+
+    public boolean isEMIAmountVariation() {
+        return this.value.equals(LoanTermVariationType.EMI_AMOUNT.getValue());
+    }
+
+    public boolean isInterestRateVariation() {
+        return this.value.equals(LoanTermVariationType.INTEREST_RATE.getValue());
+    }
+
+    public boolean isPrincipalAmountVariation() {
+        return this.value.equals(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue());
+    }
+
+    public boolean isDueDateVariation() {
+        return this.value.equals(LoanTermVariationType.DUE_DATE.getValue());
+    }
+
+    public boolean isInsertInstallment() {
+        return this.value.equals(LoanTermVariationType.INSERT_INSTALLMENT.getValue());
+    }
+
+    public boolean isDeleteInstallment() {
+        return this.value.equals(LoanTermVariationType.DELETE_INSTALLMENT.getValue());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java
new file mode 100644
index 0000000..01245b9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java
@@ -0,0 +1,139 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+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.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_term_variations")
+public class LoanTermVariations extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+
+    @Column(name = "term_type", nullable = false)
+    private Integer termType;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "applicable_date", nullable = false)
+    private Date termApplicableFrom;
+
+    @Column(name = "decimal_value", scale = 6, precision = 19)
+    private BigDecimal decimalValue;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "date_value")
+    private Date dateValue;
+
+    @Column(name = "is_specific_to_installment", nullable = false)
+    private boolean isSpecificToInstallment;
+
+    @Column(name = "applied_on_loan_status", nullable = false)
+    private Integer onLoanStatus;
+
+    public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue,
+            final boolean isSpecificToInstallment, final Loan loan) {
+        this.loan = loan;
+        this.termApplicableFrom = termApplicableFrom;
+        this.termType = termType;
+        this.decimalValue = decimalValue;
+        this.dateValue = dateValue;
+        this.isSpecificToInstallment = isSpecificToInstallment;
+        this.onLoanStatus = loan.status().getValue();
+    }
+    
+    public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue,
+            final boolean isSpecificToInstallment, final Loan loan, final Integer loanStatus) {
+        this.loan = loan;
+        this.termApplicableFrom = termApplicableFrom;
+        this.termType = termType;
+        this.decimalValue = decimalValue;
+        this.dateValue = dateValue;
+        this.isSpecificToInstallment = isSpecificToInstallment;
+        this.onLoanStatus = loanStatus;
+    }
+
+    protected LoanTermVariations() {
+
+    }
+
+    public LoanTermVariationType getTermType() {
+        return LoanTermVariationType.fromInt(this.termType);
+    }
+
+    public LoanTermVariationsData toData() {
+        LocalDate termStartDate = new LocalDate(this.termApplicableFrom);
+        LocalDate dateValue = null;
+        if (this.dateValue != null) {
+            dateValue = new LocalDate(this.dateValue);
+        }
+        EnumOptionData type = LoanEnumerations.loanvariationType(this.termType);
+        return new LoanTermVariationsData(getId(), type, termStartDate, this.decimalValue, dateValue, this.isSpecificToInstallment);
+    }
+
+    public Date getTermApplicableFrom() {
+        return this.termApplicableFrom;
+    }
+
+    public LocalDate fetchTermApplicaDate() {
+        return new LocalDate(this.termApplicableFrom);
+    }
+
+    public BigDecimal getTermValue() {
+        return this.decimalValue;
+    }
+
+    public Date getDateValue() {
+        return this.dateValue;
+    }
+
+    public LocalDate fetchDateValue() {
+        return this.dateValue == null ? null : new LocalDate(this.dateValue);
+    }
+
+    public void setTermApplicableFrom(Date termApplicableFrom) {
+        this.termApplicableFrom = termApplicableFrom;
+    }
+
+    public void setDecimalValue(BigDecimal decimalValue) {
+        this.decimalValue = decimalValue;
+    }
+
+    
+    public Integer getOnLoanStatus() {
+        return this.onLoanStatus;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationsComparator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationsComparator.java
new file mode 100644
index 0000000..67ce832
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationsComparator.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.portfolio.loanaccount.domain;
+
+import java.util.Comparator;
+
+/**
+ * Sort loan term variations
+ */
+public class LoanTermVariationsComparator implements Comparator<LoanTermVariations> {
+
+    @Override
+    public int compare(final LoanTermVariations o1, final LoanTermVariations o2) {
+        int compareResult = 0;
+        final int comparsion = o1.getTermApplicableFrom().compareTo(o2.getTermApplicableFrom());
+        /**
+         * For Terms bearing the same effective date, we sort based on modified
+         * date (when available) and new inserted installment
+         **/
+        if (comparsion == 0) {
+            if (o2.getTermType().isDueDateVariation() || o2.getTermType().isInsertInstallment()) {
+                compareResult = 1;
+            }
+        } else {
+            compareResult = comparsion;
+        }
+
+        return compareResult;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTrancheCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTrancheCharge.java
new file mode 100644
index 0000000..35860c8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTrancheCharge.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.portfolio.loanaccount.domain;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_tranche_charges")
+public class LoanTrancheCharge extends AbstractPersistable<Long> {
+
+    @ManyToOne(cascade = CascadeType.ALL, optional = false)
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+    
+    @ManyToOne(cascade = CascadeType.ALL, optional = false)
+    @JoinColumn(name = "charge_id", nullable = false)
+    private Charge charge;
+    
+    LoanTrancheCharge() {
+
+    }
+    
+    LoanTrancheCharge(Charge chargeDefinition) {
+        this.charge = chargeDefinition ;
+    }
+    public LoanTrancheCharge(Charge charge, Loan loan) {
+        this.charge = charge;
+        this.loan = loan ;
+    }
+    
+    public static LoanTrancheCharge createLoanTrancheCharge(Charge chargeDefinition) {
+        return new LoanTrancheCharge(chargeDefinition) ;
+    }
+    public static LoanTrancheCharge createLoanTrancheChargeWithLoan(Charge chargeDefinition, Loan loan) {
+        return new LoanTrancheCharge(chargeDefinition, loan) ;
+    }
+    
+    public Charge getCharge() {
+        return charge ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTrancheDisbursementCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTrancheDisbursementCharge.java
new file mode 100644
index 0000000..64830f9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTrancheDisbursementCharge.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name="m_loan_tranche_disbursement_charge")
+public class LoanTrancheDisbursementCharge extends AbstractPersistable<Long> {
+    
+    @ManyToOne(cascade = CascadeType.ALL, optional = false)
+    @JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false)
+    private LoanCharge loancharge;
+    
+    @ManyToOne(cascade = CascadeType.ALL, optional = false)
+    @JoinColumn(name="disbursement_detail_id", referencedColumnName = "id", nullable = false)
+    private LoanDisbursementDetails loanDisbursementDetails; 
+    
+    public LoanTrancheDisbursementCharge(){
+        
+    }
+    
+    public LoanTrancheDisbursementCharge(final LoanCharge loanCharge, final LoanDisbursementDetails loanDisbursementDetails){
+        this.loancharge = loanCharge;
+        this.loanDisbursementDetails = loanDisbursementDetails;
+    }
+    
+    public LoanDisbursementDetails getloanDisbursementDetails(){
+        return this.loanDisbursementDetails;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
new file mode 100755
index 0000000..6d4783a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -0,0 +1,734 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * All monetary transactions against a loan are modelled through this entity.
+ * Disbursements, Repayments, Waivers, Write-off etc
+ */
+@Entity
+@Table(name = "m_loan_transaction", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") })
+public final class LoanTransaction extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "payment_detail_id", nullable = true)
+    private PaymentDetail paymentDetail;
+
+    @Column(name = "transaction_type_enum", nullable = false)
+    private final Integer typeOf;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "transaction_date", nullable = false)
+    private final Date dateOf;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "submitted_on_date", nullable = false)
+    private final Date submittedOnDate;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "principal_portion_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal principalPortion;
+
+    @Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal interestPortion;
+
+    @Column(name = "fee_charges_portion_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal feeChargesPortion;
+
+    @Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal penaltyChargesPortion;
+
+    @Column(name = "overpayment_portion_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal overPaymentPortion;
+
+    @Column(name = "unrecognized_income_portion", scale = 6, precision = 19, nullable = true)
+    private BigDecimal unrecognizedIncomePortion;
+
+    @Column(name = "is_reversed", nullable = false)
+    private boolean reversed;
+
+    @Column(name = "external_id", length = 100, nullable = true, unique = true)
+    private String externalId;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "created_date", nullable = false)
+    private final Date createdDate;
+
+    @ManyToOne
+    @JoinColumn(name = "appuser_id", nullable = true)
+    private final AppUser appUser;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loanTransaction", orphanRemoval = true)
+    private Set<LoanChargePaidBy> loanChargesPaid = new HashSet<>();
+
+    @Column(name = "outstanding_loan_balance_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal outstandingLoanBalance;
+
+    @Column(name = "manually_adjusted_or_reversed", nullable = false)
+    private boolean manuallyAdjustedOrReversed;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL,  orphanRemoval = true)
+    @JoinColumn(name = "loan_transaction_id", referencedColumnName= "id" , nullable = false)
+    private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = new HashSet<>();
+
+    protected LoanTransaction() {
+        this.loan = null;
+        this.dateOf = null;
+        this.typeOf = null;
+        this.submittedOnDate = DateUtils.getDateOfTenant();
+        this.createdDate = new Date();
+        this.appUser = null;
+    }
+
+    public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
+            final LocalDate disbursementDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate,
+                externalId, createdDate, appUser);
+    }
+
+    public static LoanTransaction repayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
+            final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId,
+                createdDate, appUser);
+    }
+
+    public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
+            final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(null, office, LoanTransactionType.RECOVERY_REPAYMENT, paymentDetail, amount.getAmount(), paymentDate,
+                externalId, createdDate, appUser);
+    }
+
+    public static LoanTransaction loanPayment(final Loan loan, final Office office, final Money amount, final PaymentDetail paymentDetail,
+            final LocalDate paymentDate, final String externalId, final LoanTransactionType transactionType,
+            final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId, createdDate,
+                appUser);
+    }
+
+    public static LoanTransaction repaymentAtDisbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
+            final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(),
+                paymentDate, externalId, createdDate, appUser);
+    }
+
+    public static LoanTransaction waiver(final Office office, final Loan loan, final Money amount, final LocalDate waiveDate,
+            final Money waived, final Money unrecognizedPortion, final LocalDateTime createdDate, final AppUser appUser) {
+        LoanTransaction loanTransaction = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_INTEREST, amount.getAmount(),
+                waiveDate, null, createdDate, appUser);
+        loanTransaction.updateInterestComponent(waived, unrecognizedPortion);
+        return loanTransaction;
+    }
+
+    public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount,
+            final LocalDate interestAppliedDate, final LocalDateTime createdDate, final AppUser appUser) {
+        BigDecimal principalPortion = null;
+        BigDecimal feesPortion = null;
+        BigDecimal penaltiesPortion = null;
+        BigDecimal interestPortion = amount.getAmount();
+        BigDecimal overPaymentPortion = null;
+        boolean reversed = false;
+        PaymentDetail paymentDetail = null;
+        String externalId = null;
+        return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), interestAppliedDate.toDate(), interestPortion,
+                principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId,
+                createdDate, appUser);
+    }
+
+    public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate,
+            final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate.toDateTimeAtStartOfDay()
+                .toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary()
+                .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary()
+                .getTotalPenaltyChargesOutstanding(), null, false, null, null, createdDate, appUser);
+    }
+
+    public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate,
+            final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate.toDateTimeAtStartOfDay()
+                .toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary()
+                .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary()
+                .getTotalPenaltyChargesOutstanding(), null, false, null, null, createdDate, appUser);
+    }
+
+    public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate,
+            final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), transferDate.toDateTimeAtStartOfDay()
+                .toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary()
+                .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary()
+                .getTotalPenaltyChargesOutstanding(), null, false, null, null, createdDate, appUser);
+    }
+
+    public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail,
+            final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId,
+                createdDate, appUser);
+    }
+
+    public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) {
+        return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf,
+                loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion,
+                loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion,
+                loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId, new LocalDateTime(
+                        loanTransaction.createdDate), loanTransaction.appUser);
+    }
+
+    public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
+            final Money feeCharges, final Money penaltyCharges, final LocalDateTime createdDate, final AppUser appUser) {
+        String externalId = null;
+        final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate,
+                externalId, createdDate, appUser);
+        applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
+        return applyCharge;
+    }
+
+    public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail,
+            final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(null, office, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentDetail, amount.getAmount(),
+                paymentDate, externalId, createdDate, appUser);
+    }
+
+    public static boolean transactionAmountsMatch(final MonetaryCurrency currency, final LoanTransaction loanTransaction,
+            final LoanTransaction newLoanTransaction) {
+        if (loanTransaction.getAmount(currency).isEqualTo(newLoanTransaction.getAmount(currency))
+                && loanTransaction.getPrincipalPortion(currency).isEqualTo(newLoanTransaction.getPrincipalPortion(currency))
+                && loanTransaction.getInterestPortion(currency).isEqualTo(newLoanTransaction.getInterestPortion(currency))
+                && loanTransaction.getFeeChargesPortion(currency).isEqualTo(newLoanTransaction.getFeeChargesPortion(currency))
+                && loanTransaction.getPenaltyChargesPortion(currency).isEqualTo(newLoanTransaction.getPenaltyChargesPortion(currency))
+                && loanTransaction.getOverPaymentPortion(currency).isEqualTo(newLoanTransaction.getOverPaymentPortion(currency))) { return true; }
+        return false;
+    }
+
+    private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final Date dateOf, final BigDecimal amount,
+            final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
+            final BigDecimal penaltyChargesPortion, final BigDecimal overPaymentPortion, final boolean reversed,
+            final PaymentDetail paymentDetail, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
+        super();
+        this.loan = loan;
+        this.typeOf = typeOf;
+        this.dateOf = dateOf;
+        this.amount = amount;
+        this.principalPortion = principalPortion;
+        this.interestPortion = interestPortion;
+        this.feeChargesPortion = feeChargesPortion;
+        this.penaltyChargesPortion = penaltyChargesPortion;
+        this.overPaymentPortion = overPaymentPortion;
+        this.reversed = reversed;
+        this.paymentDetail = paymentDetail;
+        this.office = office;
+        this.externalId = externalId;
+        this.submittedOnDate = DateUtils.getDateOfTenant();
+        this.createdDate = createdDate.toDate();
+        this.appUser = appUser;
+    }
+
+    public static LoanTransaction waiveLoanCharge(final Loan loan, final Office office, final Money waived, final LocalDate waiveDate,
+            final Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge,
+            final LocalDateTime createdDate, final AppUser appUser) {
+        final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate,
+                null, createdDate, appUser);
+        waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge);
+
+        return waiver;
+    }
+
+    public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate, final String externalId,
+            final LocalDateTime createdDate, final AppUser appUser) {
+        return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId, createdDate, appUser);
+    }
+
+    private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final BigDecimal amount,
+            final LocalDate date, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
+        this.loan = loan;
+        this.typeOf = type.getValue();
+        this.amount = amount;
+        this.dateOf = date.toDateTimeAtStartOfDay().toDate();
+        this.externalId = externalId;
+        this.office = office;
+        this.submittedOnDate = DateUtils.getDateOfTenant();
+        this.createdDate = createdDate.toDate();
+        this.appUser = appUser;
+    }
+
+    private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail,
+            final BigDecimal amount, final LocalDate date, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
+        this.loan = loan;
+        this.typeOf = type.getValue();
+        this.paymentDetail = paymentDetail;
+        this.amount = amount;
+        this.dateOf = date.toDateTimeAtStartOfDay().toDate();
+        this.externalId = externalId;
+        this.office = office;
+        this.submittedOnDate = DateUtils.getDateOfTenant();
+        this.createdDate = createdDate.toDate();
+        this.appUser = appUser;
+    }
+
+    public void reverse() {
+        this.reversed = true;
+        this.loanTransactionToRepaymentScheduleMappings.clear();
+    }
+
+    public void resetDerivedComponents() {
+        this.principalPortion = null;
+        this.interestPortion = null;
+        this.feeChargesPortion = null;
+        this.penaltyChargesPortion = null;
+        this.overPaymentPortion = null;
+        this.outstandingLoanBalance = null;
+    }
+
+    public void updateLoan(final Loan loan) {
+        this.loan = loan;
+    }
+
+    /**
+     * This updates the derived fields of a loan transaction for the principal,
+     * interest and interest waived portions.
+     * 
+     * This accumulates the values passed to the already existent values for
+     * each of the portions.
+     */
+    public void updateComponents(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
+        final MonetaryCurrency currency = principal.getCurrency();
+        this.principalPortion = defaultToNullIfZero(getPrincipalPortion(currency).plus(principal).getAmount());
+        this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
+        updateChargesComponents(feeCharges, penaltyCharges);
+    }
+
+    public void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) {
+        final MonetaryCurrency currency = feeCharges.getCurrency();
+        this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
+        this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
+    }
+
+    private void updateChargesComponents(final Money feeCharges, final Money penaltyCharges, final Money unrecognizedCharges) {
+        final MonetaryCurrency currency = feeCharges.getCurrency();
+        this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
+        this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
+        this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedCharges).getAmount());
+    }
+
+    private void updateInterestComponent(final Money interest, final Money unrecognizedInterest) {
+        final MonetaryCurrency currency = interest.getCurrency();
+        this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
+        this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedInterest).getAmount());
+    }
+
+    public void adjustInterestComponent(final MonetaryCurrency currency) {
+        this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).minus(getUnrecognizedIncomePortion(currency)).getAmount());
+    }
+
+    public void updateComponentsAndTotal(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
+        updateComponents(principal, interest, feeCharges, penaltyCharges);
+
+        final MonetaryCurrency currency = principal.getCurrency();
+        this.amount = getPrincipalPortion(currency).plus(getInterestPortion(currency)).plus(getFeeChargesPortion(currency))
+                .plus(getPenaltyChargesPortion(currency)).getAmount();
+    }
+
+    public void updateOverPayments(final Money overPayment) {
+        final MonetaryCurrency currency = overPayment.getCurrency();
+        this.overPaymentPortion = defaultToNullIfZero(getOverPaymentPortion(currency).plus(overPayment).getAmount());
+    }
+
+    public Money getPrincipalPortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.principalPortion);
+    }
+
+    public BigDecimal getPrincipalPortion() {
+        return this.principalPortion;
+    }
+
+    public Money getInterestPortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.interestPortion);
+    }
+
+    public Money getUnrecognizedIncomePortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.unrecognizedIncomePortion);
+    }
+
+    public Money getFeeChargesPortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.feeChargesPortion);
+    }
+
+    public Money getPenaltyChargesPortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.penaltyChargesPortion);
+    }
+
+    public Money getOverPaymentPortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.overPaymentPortion);
+    }
+
+    public Money getAmount(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amount);
+    }
+
+    public LocalDate getTransactionDate() {
+        return new LocalDate(this.dateOf);
+    }
+
+    public Date getDateOf() {
+        return this.dateOf;
+    }
+
+    public LoanTransactionType getTypeOf() {
+        return LoanTransactionType.fromInt(this.typeOf);
+    }
+
+    public boolean isReversed() {
+        return this.reversed;
+    }
+
+    public boolean isNotReversed() {
+        return !isReversed();
+    }
+
+    public boolean isAnyTypeOfRepayment() {
+        return isRepayment() || isRepaymentAtDisbursement() || isRecoveryRepayment();
+    }
+
+    public boolean isRepayment() {
+        return LoanTransactionType.REPAYMENT.equals(getTypeOf()) && isNotReversed();
+    }
+
+    public boolean isNotRepayment() {
+        return !isRepayment();
+    }
+
+    public boolean isDisbursement() {
+        return LoanTransactionType.DISBURSEMENT.equals(getTypeOf()) && isNotReversed();
+    }
+
+    public boolean isRepaymentAtDisbursement() {
+        return LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.equals(getTypeOf()) && isNotReversed();
+    }
+
+    public boolean isNotRecoveryRepayment() {
+        return !isRecoveryRepayment();
+    }
+
+    public boolean isRecoveryRepayment() {
+        return LoanTransactionType.RECOVERY_REPAYMENT.equals(getTypeOf()) && isNotReversed();
+    }
+
+    public boolean isInterestWaiver() {
+        return LoanTransactionType.WAIVE_INTEREST.equals(getTypeOf()) && isNotReversed();
+    }
+
+    public boolean isChargesWaiver() {
+        return LoanTransactionType.WAIVE_CHARGES.equals(getTypeOf()) && isNotReversed();
+    }
+
+    public boolean isNotInterestWaiver() {
+        return !isInterestWaiver();
+    }
+
+    public boolean isWaiver() {
+        return isInterestWaiver() || isChargesWaiver();
+    }
+
+    public boolean isNotWaiver() {
+        return !isInterestWaiver() && !isChargesWaiver();
+    }
+
+    public boolean isChargePayment() {
+        return getTypeOf().isChargePayment() && isNotReversed();
+    }
+
+    public boolean isPenaltyPayment() {
+        boolean isPenalty = false;
+        if (isChargePayment()) {
+            for (final LoanChargePaidBy chargePaidBy : this.loanChargesPaid) {
+                isPenalty = chargePaidBy.getLoanCharge().isPenaltyCharge();
+                break;
+            }
+        }
+        return isPenalty;
+    }
+
+    public boolean isWriteOff() {
+        return getTypeOf().isWriteOff() && isNotReversed();
+    }
+
+    public boolean isIdentifiedBy(final Long identifier) {
+        return getId().equals(identifier);
+    }
+
+    public boolean isBelongingToLoanOf(final Loan check) {
+        return this.loan.getId().equals(check.getId());
+    }
+
+    public boolean isNotBelongingToLoanOf(final Loan check) {
+        return !isBelongingToLoanOf(check);
+    }
+
+    public boolean isNonZero() {
+        return this.amount.subtract(BigDecimal.ZERO).doubleValue() > 0;
+    }
+
+    public boolean isGreaterThan(final Money monetaryAmount) {
+        return getAmount(monetaryAmount.getCurrency()).isGreaterThan(monetaryAmount);
+    }
+
+    public boolean isGreaterThanZero(final MonetaryCurrency currency) {
+        return getAmount(currency).isGreaterThanZero();
+    }
+
+    public boolean isNotZero(final MonetaryCurrency currency) {
+        return !getAmount(currency).isZero();
+    }
+
+    private BigDecimal defaultToNullIfZero(final BigDecimal value) {
+        BigDecimal result = value;
+        if (BigDecimal.ZERO.compareTo(value) == 0) {
+            result = null;
+        }
+        return result;
+    }
+
+    public LoanTransactionData toData(final CurrencyData currencyData, final AccountTransferData transfer) {
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(this.typeOf);
+        PaymentDetailData paymentDetailData = null;
+        if (this.paymentDetail != null) {
+            paymentDetailData = this.paymentDetail.toData();
+        }
+        return new LoanTransactionData(getId(), this.office.getId(), this.office.getName(), transactionType, paymentDetailData,
+                currencyData, getTransactionDate(), this.amount, this.principalPortion, this.interestPortion, this.feeChargesPortion,
+                this.penaltyChargesPortion, this.overPaymentPortion, this.externalId, transfer, null, outstandingLoanBalance,
+                this.unrecognizedIncomePortion, this.manuallyAdjustedOrReversed);
+    }
+
+    public Map<String, Object> toMapData(final CurrencyData currencyData) {
+        final Map<String, Object> thisTransactionData = new LinkedHashMap<>();
+
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(this.typeOf);
+
+        thisTransactionData.put("id", getId());
+        thisTransactionData.put("officeId", this.office.getId());
+        thisTransactionData.put("type", transactionType);
+        thisTransactionData.put("reversed", Boolean.valueOf(isReversed()));
+        thisTransactionData.put("date", getTransactionDate());
+        thisTransactionData.put("currency", currencyData);
+        thisTransactionData.put("amount", this.amount);
+        thisTransactionData.put("principalPortion", this.principalPortion);
+        thisTransactionData.put("interestPortion", this.interestPortion);
+        thisTransactionData.put("feeChargesPortion", this.feeChargesPortion);
+        thisTransactionData.put("penaltyChargesPortion", this.penaltyChargesPortion);
+        thisTransactionData.put("overPaymentPortion", this.overPaymentPortion);
+
+        if (this.paymentDetail != null) {
+            thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId());
+        }
+
+        if (!this.loanChargesPaid.isEmpty()) {
+            final List<Map<String, Object>> loanChargesPaidData = new ArrayList<>();
+            for (final LoanChargePaidBy chargePaidBy : this.loanChargesPaid) {
+                final Map<String, Object> loanChargePaidData = new LinkedHashMap<>();
+                loanChargePaidData.put("chargeId", chargePaidBy.getLoanCharge().getCharge().getId());
+                loanChargePaidData.put("isPenalty", chargePaidBy.getLoanCharge().isPenaltyCharge());
+                loanChargePaidData.put("loanChargeId", chargePaidBy.getLoanCharge().getId());
+                loanChargePaidData.put("amount", chargePaidBy.getAmount());
+
+                loanChargesPaidData.add(loanChargePaidData);
+            }
+            thisTransactionData.put("loanChargesPaid", loanChargesPaidData);
+        }
+
+        return thisTransactionData;
+    }
+
+    public Loan getLoan() {
+        return this.loan;
+    }
+
+    public Set<LoanChargePaidBy> getLoanChargesPaid() {
+        return this.loanChargesPaid;
+    }
+
+    public void setLoanChargesPaid(final Set<LoanChargePaidBy> loanChargesPaid) {
+        this.loanChargesPaid = loanChargesPaid;
+    }
+
+    public String getExternalId() {
+        return this.externalId;
+    }
+
+    public boolean isRefund() {
+        return LoanTransactionType.REFUND.equals(getTypeOf()) && isNotReversed();
+    }
+
+    public void updateExternalId(final String externalId) {
+        this.externalId = externalId;
+    }
+
+    public boolean isAccrual() {
+        return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed();
+    }
+    
+    public boolean isNonMonetaryTransaction(){
+    	return isNotReversed() 
+    			&& (LoanTransactionType.CONTRA.equals(getTypeOf())
+    					|| LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
+    					|| LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf())
+    					|| LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
+    					|| LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf())
+    					|| LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf()));
+    }
+
+    public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
+        this.outstandingLoanBalance = outstandingLoanBalance;
+    }
+
+    public boolean isNotRefundForActiveLoan() {
+        // TODO Auto-generated method stub
+        return !isRefundForActiveLoan();
+    }
+
+    public boolean isRefundForActiveLoan() {
+        return LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.equals(getTypeOf()) && isNotReversed();
+    }
+
+    public boolean isManuallyAdjustedOrReversed() {
+        return this.manuallyAdjustedOrReversed;
+    }
+
+    public boolean isNotManuallyAdjustedOrReversed() {
+        return !this.manuallyAdjustedOrReversed;
+    }
+
+    public void manuallyAdjustedOrReversed() {
+        this.manuallyAdjustedOrReversed = true;
+    }
+
+    private LocalDate getCreatedDate() {
+        return new LocalDate(this.createdDate);
+    }
+
+    public LocalDateTime getCreatedDateTime() {
+        return new LocalDateTime(this.createdDate);
+    }
+
+    public boolean isLastTransaction(final LoanTransaction loanTransaction) {
+        boolean isLatest = false;
+        if (loanTransaction != null) {
+            isLatest = this.getTransactionDate().isBefore(loanTransaction.getTransactionDate())
+                    || (this.getTransactionDate().isEqual(loanTransaction.getTransactionDate()) && this.getCreatedDate().isBefore(
+                            loanTransaction.getCreatedDate()));
+        }
+        return isLatest;
+    }
+
+    public boolean isLatestTransaction(final LoanTransaction loanTransaction) {
+        boolean isLatest = false;
+        if (loanTransaction != null) {
+            isLatest = this.getCreatedDate().isBefore(loanTransaction.getCreatedDate());
+        }
+        return isLatest;
+    }
+
+    public void updateLoanTransactionToRepaymentScheduleMappings(final Collection<LoanTransactionToRepaymentScheduleMapping> mappings) {
+        Collection<LoanTransactionToRepaymentScheduleMapping> retainMappings = new ArrayList<>();
+        for (LoanTransactionToRepaymentScheduleMapping updatedrepaymentScheduleMapping : mappings) {
+            updateMapingDetail(retainMappings, updatedrepaymentScheduleMapping);
+        }
+        this.loanTransactionToRepaymentScheduleMappings.retainAll(retainMappings);
+    }
+
+    private boolean updateMapingDetail(final Collection<LoanTransactionToRepaymentScheduleMapping> retainMappings,
+            final LoanTransactionToRepaymentScheduleMapping updatedrepaymentScheduleMapping) {
+        boolean isMappingUpdated = false;
+        for (LoanTransactionToRepaymentScheduleMapping repaymentScheduleMapping : this.loanTransactionToRepaymentScheduleMappings) {
+            if (updatedrepaymentScheduleMapping.getLoanRepaymentScheduleInstallment().getId() != null
+                    && repaymentScheduleMapping.getLoanRepaymentScheduleInstallment().getDueDate()
+                            .equals(updatedrepaymentScheduleMapping.getLoanRepaymentScheduleInstallment().getDueDate())) {
+                repaymentScheduleMapping.setComponents(updatedrepaymentScheduleMapping.getPrincipalPortion(),
+                        updatedrepaymentScheduleMapping.getInterestPortion(), updatedrepaymentScheduleMapping.getFeeChargesPortion(),
+                        updatedrepaymentScheduleMapping.getPenaltyChargesPortion());
+                isMappingUpdated = true;
+                retainMappings.add(repaymentScheduleMapping);
+                break;
+            }
+        }
+        if (!isMappingUpdated) {
+            this.loanTransactionToRepaymentScheduleMappings.add(updatedrepaymentScheduleMapping);
+            retainMappings.add(updatedrepaymentScheduleMapping);
+        }
+        return isMappingUpdated;
+    }
+
+    
+    public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
+        return this.loanTransactionToRepaymentScheduleMappings;
+    }
+        
+    public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo(){
+    	return isDisbursement() || isAccrual() || isRepaymentAtDisbursement();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionComparator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionComparator.java
new file mode 100644
index 0000000..44cf15f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionComparator.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.util.Comparator;
+
+/**
+ * Sort loan transactions by transaction date, created date and transaction type
+ * placing
+ */
+public class LoanTransactionComparator implements Comparator<LoanTransaction> {
+
+    @Override
+    public int compare(final LoanTransaction o1, final LoanTransaction o2) {
+        int compareResult = 0;
+        final int comparsion = o1.getTransactionDate().compareTo(o2.getTransactionDate());
+        /**
+         * For transactions bearing the same transaction date, we sort
+         * transactions based on created date (when available) after which
+         * sorting for waivers takes place
+         **/
+        if (comparsion == 0) {
+            int comparisonBasedOnCreatedDate = 0;
+            if (o1.getCreatedDateTime() != null && o2.getCreatedDateTime() != null) {
+                comparisonBasedOnCreatedDate = o1.getCreatedDateTime().compareTo(o2.getCreatedDateTime());
+            }
+            // equal transaction dates
+            if (comparisonBasedOnCreatedDate == 0) {
+                if (o1.isWaiver() && o2.isNotWaiver()) {
+                    compareResult = -1;
+                } else if (o1.isNotWaiver() && o2.isWaiver()) {
+                    compareResult = 1;
+                } else {
+                    compareResult = 0;
+                }
+            }
+        } else {
+            compareResult = comparsion;
+        }
+
+        return compareResult;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionProcessingStrategyRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionProcessingStrategyRepository.java
new file mode 100644
index 0000000..e7c11ac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionProcessingStrategyRepository.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanTransactionProcessingStrategyRepository extends JpaRepository<LoanTransactionProcessingStrategy, Long>,
+        JpaSpecificationExecutor<LoanTransactionProcessingStrategy> {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
new file mode 100644
index 0000000..8c11f10
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.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.loanaccount.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanTransactionRepository extends JpaRepository<LoanTransaction, Long>, JpaSpecificationExecutor<LoanTransaction> {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMapping.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMapping.java
new file mode 100644
index 0000000..78d19cc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMapping.java
@@ -0,0 +1,158 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import java.math.BigDecimal;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_transaction_repayment_schedule_mapping")
+public class LoanTransactionToRepaymentScheduleMapping extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false, cascade = CascadeType.PERSIST)
+    @JoinColumn(name = "loan_repayment_schedule_id", nullable = false)
+    private LoanRepaymentScheduleInstallment installment;
+
+    @Column(name = "principal_portion_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal principalPortion;
+
+    @Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal interestPortion;
+
+    @Column(name = "fee_charges_portion_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal feeChargesPortion;
+
+    @Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal penaltyChargesPortion;
+
+    @Column(name = "amount", scale = 6, precision = 19)
+    private BigDecimal amount;
+
+    protected LoanTransactionToRepaymentScheduleMapping() {
+
+    }
+
+    private LoanTransactionToRepaymentScheduleMapping(final LoanRepaymentScheduleInstallment installment,
+            final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
+            final BigDecimal penaltyChargesPortion, final BigDecimal amount) {
+        this.installment = installment;
+        this.principalPortion = principalPortion;
+        this.interestPortion = interestPortion;
+        this.feeChargesPortion = feeChargesPortion;
+        this.penaltyChargesPortion = penaltyChargesPortion;
+        this.amount = amount;
+    }
+
+    public static LoanTransactionToRepaymentScheduleMapping createFrom(final LoanRepaymentScheduleInstallment installment,
+            final Money principalPortion, final Money interestPortion, final Money feeChargesPortion, final Money penaltyChargesPortion) {
+        return new LoanTransactionToRepaymentScheduleMapping(installment, defaultToNullIfZero(principalPortion),
+                defaultToNullIfZero(interestPortion), defaultToNullIfZero(feeChargesPortion), defaultToNullIfZero(penaltyChargesPortion),
+                defaultToNullIfZero(principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion)));
+    }
+
+    private static BigDecimal defaultToNullIfZero(final Money value) {
+        BigDecimal result = value.getAmount();
+        if (value.isZero()) {
+            result = null;
+        }
+        return result;
+    }
+
+    private BigDecimal defaultToZeroIfNull(final BigDecimal value) {
+        BigDecimal result = value;
+        if (value == null) {
+            result = BigDecimal.ZERO;
+        }
+        return result;
+    }
+
+    public LoanRepaymentScheduleInstallment getLoanRepaymentScheduleInstallment() {
+        return this.installment;
+    }
+
+    public void updateComponents(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
+        final MonetaryCurrency currency = principal.getCurrency();
+        this.principalPortion = defaultToNullIfZero(getPrincipalPortion(currency).plus(principal));
+        this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest));
+        updateChargesComponents(feeCharges, penaltyCharges);
+        updateAmount();
+    }
+
+    private void updateAmount() {
+        this.amount = defaultToZeroIfNull(getPrincipalPortion()).add(defaultToZeroIfNull(getInterestPortion()))
+                .add(defaultToZeroIfNull(getFeeChargesPortion())).add(defaultToZeroIfNull(getPenaltyChargesPortion()));
+    }
+
+    public void setComponents(final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
+            final BigDecimal penaltyCharges) {
+        this.principalPortion = principal;
+        this.interestPortion = interest;
+        this.feeChargesPortion = feeCharges;
+        this.penaltyChargesPortion = penaltyCharges;
+        updateAmount();
+    }
+
+    private void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) {
+        final MonetaryCurrency currency = feeCharges.getCurrency();
+        this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges));
+        this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges));
+    }
+
+    public Money getPrincipalPortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.principalPortion);
+    }
+
+    public Money getInterestPortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.interestPortion);
+    }
+
+    public Money getFeeChargesPortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.feeChargesPortion);
+    }
+
+    public Money getPenaltyChargesPortion(final MonetaryCurrency currency) {
+        return Money.of(currency, this.penaltyChargesPortion);
+    }
+
+    public BigDecimal getPrincipalPortion() {
+        return this.principalPortion;
+    }
+
+    public BigDecimal getInterestPortion() {
+        return this.interestPortion;
+    }
+
+    public BigDecimal getFeeChargesPortion() {
+        return this.feeChargesPortion;
+    }
+
+    public BigDecimal getPenaltyChargesPortion() {
+        return this.penaltyChargesPortion;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
new file mode 100644
index 0000000..2e1ae0d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -0,0 +1,175 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+public enum LoanTransactionType {
+
+    INVALID(0, "loanTransactionType.invalid"), //
+    DISBURSEMENT(1, "loanTransactionType.disbursement"), //
+    REPAYMENT(2, "loanTransactionType.repayment"), //
+    CONTRA(3, "loanTransactionType.contra"), //
+    WAIVE_INTEREST(4, "loanTransactionType.waiver"), //
+    REPAYMENT_AT_DISBURSEMENT(5, "loanTransactionType.repaymentAtDisbursement"), //
+    WRITEOFF(6, "loanTransactionType.writeOff"), //
+    MARKED_FOR_RESCHEDULING(7, "loanTransactionType.marked.for.rescheduling"), //
+    /**
+     * This type of transactions is allowed on written-off loans where mfi still
+     * attempts to recover payments from applicant after writing-off.
+     */
+    RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), //
+    WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), //
+    /**
+     * Transaction represents an Accrual (For either interest, charge or a
+     * penalty
+     **/
+    ACCRUAL(10, "loanTransactionType.accrual"), //
+
+    /***
+     * A Loan Transfer involves two steps, first a "initiate" Loan transfer
+     * transaction done by the Source branch followed by a "complete" loan
+     * transaction initiated by the destination branch
+     **/
+    INITIATE_TRANSFER(12, "loanTransactionType.initiateTransfer"), //
+    APPROVE_TRANSFER(13, "loanTransactionType.approveTransfer"), //
+    WITHDRAW_TRANSFER(14, "loanTransactionType.withdrawTransfer"), //
+    REJECT_TRANSFER(15, "loanTransactionType.rejectTransfer"), //
+    REFUND(16, "loanTransactionType.refund"), //
+    CHARGE_PAYMENT(17, "loanTransactionType.chargePayment"),  //
+    REFUND_FOR_ACTIVE_LOAN(18, "loanTransactionType.refund");
+
+    private final Integer value;
+    private final String code;
+
+    private LoanTransactionType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static LoanTransactionType fromInt(final Integer transactionType) {
+
+        if (transactionType == null) { return LoanTransactionType.INVALID; }
+
+        LoanTransactionType loanTransactionType = null;
+        switch (transactionType) {
+            case 1:
+                loanTransactionType = LoanTransactionType.DISBURSEMENT;
+            break;
+            case 2:
+                loanTransactionType = LoanTransactionType.REPAYMENT;
+            break;
+            case 3:
+                loanTransactionType = LoanTransactionType.CONTRA;
+            break;
+            case 4:
+                loanTransactionType = LoanTransactionType.WAIVE_INTEREST;
+            break;
+            case 5:
+                loanTransactionType = LoanTransactionType.REPAYMENT_AT_DISBURSEMENT;
+            break;
+            case 6:
+                loanTransactionType = LoanTransactionType.WRITEOFF;
+            break;
+            case 7:
+                loanTransactionType = LoanTransactionType.MARKED_FOR_RESCHEDULING;
+            break;
+            case 8:
+                loanTransactionType = LoanTransactionType.RECOVERY_REPAYMENT;
+            break;
+            case 9:
+                loanTransactionType = LoanTransactionType.WAIVE_CHARGES;
+            break;
+            case 10:
+                loanTransactionType = LoanTransactionType.ACCRUAL;
+            break;
+            case 12:
+                loanTransactionType = LoanTransactionType.INITIATE_TRANSFER;
+            break;
+            case 13:
+                loanTransactionType = LoanTransactionType.APPROVE_TRANSFER;
+            break;
+            case 14:
+                loanTransactionType = LoanTransactionType.WITHDRAW_TRANSFER;
+            break;
+            case 15:
+                loanTransactionType = LoanTransactionType.REJECT_TRANSFER;
+            break;
+            case 16:
+                loanTransactionType = LoanTransactionType.REFUND;
+            break;
+            case 17:
+                loanTransactionType = LoanTransactionType.CHARGE_PAYMENT;
+            break;
+            case 18:
+                loanTransactionType = LoanTransactionType.REFUND_FOR_ACTIVE_LOAN;
+            break;
+            default:
+                loanTransactionType = LoanTransactionType.INVALID;
+            break;
+        }
+        return loanTransactionType;
+    }
+
+    public boolean isDisbursement() {
+        return this.value.equals(LoanTransactionType.DISBURSEMENT.getValue());
+    }
+
+    public boolean isRepaymentAtDisbursement() {
+        return this.value.equals(LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue());
+    }
+
+    public boolean isRepayment() {
+        return this.value.equals(LoanTransactionType.REPAYMENT.getValue());
+    }
+
+    public boolean isRecoveryRepayment() {
+        return this.value.equals(LoanTransactionType.RECOVERY_REPAYMENT.getValue());
+    }
+
+    public boolean isWaiveInterest() {
+        return this.value.equals(LoanTransactionType.WAIVE_INTEREST.getValue());
+    }
+
+    public boolean isWaiveCharges() {
+        return this.value.equals(LoanTransactionType.WAIVE_CHARGES.getValue());
+    }
+
+    public boolean isAccrual() {
+        return this.value.equals(LoanTransactionType.ACCRUAL.getValue());
+    }
+
+    public boolean isWriteOff() {
+        return this.value.equals(LoanTransactionType.WRITEOFF.getValue());
+    }
+
+    public boolean isChargePayment() {
+        return this.value.equals(LoanTransactionType.CHARGE_PAYMENT.getValue());
+    }
+    
+    public boolean isRefundForActiveLoan() {
+        return this.value.equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/TransactionProccessingResult.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/TransactionProccessingResult.java
new file mode 100644
index 0000000..a6a16d4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/TransactionProccessingResult.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+public class TransactionProccessingResult {
+
+    private final Money overPaymentAmount;
+    private final boolean overPayment;
+
+    public TransactionProccessingResult(final Money overPaymentAmount, final boolean overPayment) {
+        this.overPaymentAmount = overPaymentAmount;
+        this.overPayment = overPayment;
+    }
+
+    public Money getOverPaymentAmount() {
+        return this.overPaymentAmount;
+    }
+
+    public boolean isOverPayment() {
+        return this.overPayment;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
new file mode 100755
index 0000000..b06275b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -0,0 +1,626 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
+import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+
+/**
+ * Abstract implementation of {@link LoanRepaymentScheduleTransactionProcessor}
+ * which is more convenient for concrete implementations to extend.
+ * 
+ * @see InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor
+ * 
+ * @see HeavensFamilyLoanRepaymentScheduleTransactionProcessor
+ * @see CreocoreLoanRepaymentScheduleTransactionProcessor
+ */
+public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implements LoanRepaymentScheduleTransactionProcessor {
+
+    /**
+     * Provides support for passing all {@link LoanTransaction}'s so it will
+     * completely re-process the entire loan schedule. This is required in cases
+     * where the {@link LoanTransaction} being processed is in the past and
+     * falls before existing transactions or and adjustment is made to an
+     * existing in which case the entire loan schedule needs to be re-processed.
+     */
+
+    @Override
+    public ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate,
+            final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
+            final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
+
+        if (charges != null) {
+            for (final LoanCharge loanCharge : charges) {
+                if (!loanCharge.isDueAtDisbursement()) {
+                    loanCharge.resetPaidAmount(currency);
+                }
+            }
+        }
+
+        for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
+            currentInstallment.resetDerivedComponents();
+            currentInstallment.updateDerivedFields(currency, disbursementDate);
+        }
+
+        // re-process loan charges over repayment periods (picking up on waived
+        // loan charges)
+        final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
+        wrapper.reprocess(currency, disbursementDate, installments, charges);
+
+        final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
+        final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<>();
+        for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
+            if (loanTransaction.isChargePayment()) {
+                List<LoanChargePaidDetail> chargePaidDetails = new ArrayList<>();
+                final Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
+                final Set<LoanCharge> transferCharges = new HashSet<>();
+                for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
+                    LoanCharge loanCharge = chargePaidBy.getLoanCharge();
+                    transferCharges.add(loanCharge);
+                    if (loanCharge.isInstalmentFee()) {
+                        chargePaidDetails.addAll(loanCharge.fetchRepaymentInstallment(currency));
+                    }
+                }
+                LocalDate startDate = disbursementDate;
+                for (final LoanRepaymentScheduleInstallment installment : installments) {
+                    for (final LoanCharge loanCharge : transferCharges) {
+                        if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) {
+                            Money amountForProcess = loanCharge.getAmount(currency);
+                            if (amountForProcess.isGreaterThan(loanTransaction.getAmount(currency))) {
+                                amountForProcess = loanTransaction.getAmount(currency);
+                            }
+                            LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(amountForProcess, installment,
+                                    loanCharge.isFeeCharge());
+                            chargePaidDetails.add(chargePaidDetail);
+                            break;
+                        }
+                    }
+                    startDate = installment.getDueDate();
+                }
+                loanTransaction.resetDerivedComponents();
+                Money unprocessed = loanTransaction.getAmount(currency);
+                for (LoanChargePaidDetail chargePaidDetail : chargePaidDetails) {
+                    final List<LoanRepaymentScheduleInstallment> processInstallments = new ArrayList<>(1);
+                    processInstallments.add(chargePaidDetail.getInstallment());
+                    Money processAmt = chargePaidDetail.getAmount();
+                    if (processAmt.isGreaterThan(unprocessed)) {
+                        processAmt = unprocessed;
+                    }
+                    unprocessed = handleTransactionAndCharges(loanTransaction, currency, processInstallments, transferCharges, processAmt,
+                            chargePaidDetail.isFeeCharge());
+                    if (!unprocessed.isGreaterThanZero()) {
+                        break;
+                    }
+                }
+
+                if (unprocessed.isGreaterThanZero()) {
+                    onLoanOverpayment(loanTransaction, unprocessed);
+                    loanTransaction.updateOverPayments(unprocessed);
+                }
+
+            } else {
+                transactionstoBeProcessed.add(loanTransaction);
+            }
+        }
+
+        for (final LoanTransaction loanTransaction : transactionstoBeProcessed) {
+
+            if (!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) {
+                final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() {
+
+                    @Override
+                    public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) {
+                        return ord1.getDueDate().compareTo(ord2.getDueDate());
+                    }
+                };
+                Collections.sort(installments, byDate);
+            }
+
+            if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
+                // pass through for new transactions
+                if (loanTransaction.getId() == null) {
+                    handleTransaction(loanTransaction, currency, installments, charges);
+                    loanTransaction.adjustInterestComponent(currency);
+                } else {
+                    /**
+                     * For existing transactions, check if the re-payment
+                     * breakup (principal, interest, fees, penalties) has
+                     * changed.<br>
+                     **/
+                    final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
+
+                    // Reset derived component of new loan transaction and
+                    // re-process transaction
+                    handleTransaction(newLoanTransaction, currency, installments, charges);
+                    newLoanTransaction.adjustInterestComponent(currency);
+                    /**
+                     * Check if the transaction amounts have changed. If so,
+                     * reverse the original transaction and update
+                     * changedTransactionDetail accordingly
+                     **/
+                    if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
+                        loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(newLoanTransaction
+                                .getLoanTransactionToRepaymentScheduleMappings());
+                    } else {
+                        loanTransaction.reverse();
+                        loanTransaction.updateExternalId(null);
+                        changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
+                    }
+                }
+
+            } else if (loanTransaction.isWriteOff()) {
+                loanTransaction.resetDerivedComponents();
+                handleWriteOff(loanTransaction, currency, installments);
+            } else if (loanTransaction.isRefundForActiveLoan()) {
+                loanTransaction.resetDerivedComponents();
+
+                handleRefund(loanTransaction, currency, installments, charges);
+            }
+        }
+        return changedTransactionDetail;
+    }
+
+    /**
+     * Provides support for processing the latest transaction (which should be
+     * latest transaction) against the loan schedule.
+     */
+    @Override
+    public void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
+            final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
+
+        final Money amountToProcess = null;
+        final boolean isChargeAmount = false;
+        handleTransaction(loanTransaction, currency, installments, charges, amountToProcess, isChargeAmount);
+
+    }
+
+    private void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
+            final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess,
+            final boolean isFeeCharge) {
+
+        Money transactionAmountUnprocessed = handleTransactionAndCharges(loanTransaction, currency, installments, charges,
+                chargeAmountToProcess, isFeeCharge);
+
+        if (transactionAmountUnprocessed.isGreaterThanZero()) {
+            if (loanTransaction.isWaiver()) {
+                loanTransaction.updateComponentsAndTotal(transactionAmountUnprocessed.zero(), transactionAmountUnprocessed.zero(),
+                        transactionAmountUnprocessed.zero(), transactionAmountUnprocessed.zero());
+            } else {
+                onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
+                loanTransaction.updateOverPayments(transactionAmountUnprocessed);
+            }
+        }
+    }
+
+    private Money handleTransactionAndCharges(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
+            final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess,
+            final boolean isFeeCharge) {
+        // to.
+        if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
+            loanTransaction.resetDerivedComponents();
+        }
+        Money transactionAmountUnprocessed = processTransaction(loanTransaction, currency, installments, chargeAmountToProcess);
+
+        final Set<LoanCharge> loanFees = extractFeeCharges(charges);
+        final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
+        Integer installmentNumber = null;
+        if (loanTransaction.isChargePayment() && installments.size() == 1) {
+            installmentNumber = installments.get(0).getInstallmentNumber();
+        }
+
+        if (loanTransaction.isNotWaiver()) {
+            Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
+            Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
+            if (chargeAmountToProcess != null && feeCharges.isGreaterThan(chargeAmountToProcess)) {
+                if (isFeeCharge) {
+                    feeCharges = chargeAmountToProcess;
+                } else {
+                    penaltyCharges = chargeAmountToProcess;
+                }
+            }
+            if (feeCharges.isGreaterThanZero()) {
+                updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
+            }
+
+            if (penaltyCharges.isGreaterThanZero()) {
+                updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
+            }
+        }
+        return transactionAmountUnprocessed;
+    }
+
+    private Money processTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
+            final List<LoanRepaymentScheduleInstallment> installments, Money amountToProcess) {
+        int installmentIndex = 0;
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
+        if (amountToProcess != null) {
+            transactionAmountUnprocessed = amountToProcess;
+        }
+        List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
+
+        for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
+            if (transactionAmountUnprocessed.isGreaterThanZero()) {
+                if (currentInstallment.isNotFullyPaidOff()) {
+
+                    // is this transaction early/late/on-time with respect to
+                    // the
+                    // current installment?
+                    if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate, transactionAmountUnprocessed)) {
+                        transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(currentInstallment,
+                                installments, loanTransaction, transactionDate, transactionAmountUnprocessed, transactionMappings);
+                    } else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments,
+                            loanTransaction.getTransactionDate())) {
+                        // does this result in a late payment of existing
+                        // installment?
+                        transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(currentInstallment, installments,
+                                loanTransaction, transactionAmountUnprocessed, transactionMappings);
+                    } else {
+                        // standard transaction
+                        transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment,
+                                loanTransaction, transactionAmountUnprocessed, transactionMappings);
+                    }
+                }
+            }
+
+            installmentIndex++;
+        }
+        loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
+        return transactionAmountUnprocessed;
+    }
+
+    private Set<LoanCharge> extractFeeCharges(final Set<LoanCharge> loanCharges) {
+        final Set<LoanCharge> feeCharges = new HashSet<>();
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isFeeCharge()) {
+                feeCharges.add(loanCharge);
+            }
+        }
+        return feeCharges;
+    }
+
+    private Set<LoanCharge> extractPenaltyCharges(final Set<LoanCharge> loanCharges) {
+        final Set<LoanCharge> penaltyCharges = new HashSet<>();
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isPenaltyCharge()) {
+                penaltyCharges.add(loanCharge);
+            }
+        }
+        return penaltyCharges;
+    }
+
+    private void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
+            final Integer installmentNumber) {
+
+        Money amountRemaining = feeCharges;
+        while (amountRemaining.isGreaterThanZero()) {
+            final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges, feeCharges.getCurrency());
+            Money feeAmount = feeCharges.zero();
+            if (loanTransaction.isChargePayment()) {
+                feeAmount = feeCharges;
+            }
+            if (unpaidCharge == null) break; // All are trache charges
+            final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining, installmentNumber, feeAmount);
+            if (!amountPaidTowardsCharge.isZero()) {
+                Set<LoanChargePaidBy> chargesPaidBies = loanTransaction.getLoanChargesPaid();
+                if (loanTransaction.isChargePayment()) {
+                    for (final LoanChargePaidBy chargePaidBy : chargesPaidBies) {
+                        LoanCharge loanCharge = chargePaidBy.getLoanCharge();
+                        if (loanCharge.getId().equals(unpaidCharge.getId())) {
+                            chargePaidBy.setAmount(amountPaidTowardsCharge.getAmount());
+                        }
+                    }
+                } else {
+                    final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, unpaidCharge,
+                            amountPaidTowardsCharge.getAmount(), installmentNumber);
+                    chargesPaidBies.add(loanChargePaidBy);
+                }
+                amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
+            }
+        }
+
+    }
+
+    private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges, final MonetaryCurrency currency) {
+        LoanCharge earliestUnpaidCharge = null;
+        LoanCharge installemntCharge = null;
+        LoanInstallmentCharge chargePerInstallment = null;
+        for (final LoanCharge loanCharge : charges) {
+            if (loanCharge.getAmountOutstanding(currency).isGreaterThanZero() && !loanCharge.isDueAtDisbursement()) {
+                if (loanCharge.isInstalmentFee()) {
+                    LoanInstallmentCharge unpaidLoanChargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
+                    if (chargePerInstallment == null
+                            || chargePerInstallment.getRepaymentInstallment().getDueDate()
+                                    .isAfter(unpaidLoanChargePerInstallment.getRepaymentInstallment().getDueDate())) {
+                        installemntCharge = loanCharge;
+                        chargePerInstallment = unpaidLoanChargePerInstallment;
+                    }
+                } else if (earliestUnpaidCharge == null || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) {
+                    earliestUnpaidCharge = loanCharge;
+                }
+            }
+        }
+        if (earliestUnpaidCharge == null
+                || (chargePerInstallment != null && earliestUnpaidCharge.getDueLocalDate().isAfter(
+                        chargePerInstallment.getRepaymentInstallment().getDueDate()))) {
+            earliestUnpaidCharge = installemntCharge;
+        }
+
+        return earliestUnpaidCharge;
+    }
+
+    @Override
+    public void handleWriteOff(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
+            final List<LoanRepaymentScheduleInstallment> installments) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        Money principalPortion = Money.zero(currency);
+        Money interestPortion = Money.zero(currency);
+        Money feeChargesPortion = Money.zero(currency);
+        Money penaltychargesPortion = Money.zero(currency);
+
+        // determine how much is written off in total and breakdown for
+        // principal, interest and charges
+        for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
+
+            if (currentInstallment.isNotFullyPaidOff()) {
+                principalPortion = principalPortion.plus(currentInstallment.writeOffOutstandingPrincipal(transactionDate, currency));
+                interestPortion = interestPortion.plus(currentInstallment.writeOffOutstandingInterest(transactionDate, currency));
+                feeChargesPortion = feeChargesPortion.plus(currentInstallment.writeOffOutstandingFeeCharges(transactionDate, currency));
+                penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.writeOffOutstandingPenaltyCharges(transactionDate,
+                        currency));
+            }
+        }
+
+        loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion);
+    }
+
+    // abstract interface
+    /**
+     * This method is responsible for checking if the current transaction is 'an
+     * advance/early payment' based on the details passed through.
+     * 
+     * Default implementation simply processes transactions as 'Late' if the
+     * transaction date is after the installment due date.
+     */
+    protected boolean isTransactionALateRepaymentOnInstallment(final int installmentIndex,
+            final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
+
+        final LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex);
+
+        return transactionDate.isAfter(currentInstallment.getDueDate());
+    }
+
+    /**
+     * For late repayments, how should components of installment be paid off
+     * 
+     * @param transactionMappings
+     *            TODO
+     */
+    protected abstract Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final Money transactionAmountUnprocessed, final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings);
+
+    /**
+     * This method is responsible for checking if the current transaction is 'an
+     * advance/early payment' based on the details passed through.
+     * 
+     * Default implementation is check transaction date is before installment
+     * due date.
+     */
+    protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
+            final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate,
+            @SuppressWarnings("unused") final Money transactionAmount) {
+
+        final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
+
+        return transactionDate.isBefore(currentInstallment.getDueDate());
+    }
+
+    /**
+     * For early/'in advance' repayments.
+     * 
+     * @param transactionMappings
+     *            TODO
+     */
+    protected abstract Money handleTransactionThatIsPaymentInAdvanceOfInstallment(
+            final LoanRepaymentScheduleInstallment currentInstallment, final List<LoanRepaymentScheduleInstallment> installments,
+            final LoanTransaction loanTransaction, final LocalDate transactionDate, final Money paymentInAdvance,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings);
+
+    /**
+     * For normal on-time repayments.
+     * 
+     * @param transactionMappings
+     *            TODO
+     */
+    protected abstract Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings);
+
+    /**
+     * Invoked when a transaction results in an over-payment of the full loan.
+     * 
+     * transaction amount is greater than the total expected principal and
+     * interest of the loan.
+     */
+    @SuppressWarnings("unused")
+    protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
+        // empty implementation by default.
+    }
+
+    @Override
+    public Money handleRepaymentSchedule(final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
+            final List<LoanRepaymentScheduleInstallment> installments) {
+        Money unProcessed = Money.zero(currency);
+        for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
+            Money amountToProcess = null;
+            if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
+                loanTransaction.resetDerivedComponents();
+            }
+            unProcessed = processTransaction(loanTransaction, currency, installments, amountToProcess);
+        }
+        return unProcessed;
+    }
+
+    @Override
+    public boolean isInterestFirstRepaymentScheduleTransactionProcessor() {
+        return false;
+    }
+
+    @Override
+    public void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
+        // TODO Auto-generated method stub
+        List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
+        final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() {
+
+            @Override
+            public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) {
+                return ord1.getDueDate().compareTo(ord2.getDueDate());
+            }
+        };
+        Collections.sort(installments, Collections.reverseOrder(byDate));
+        Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
+
+        for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
+            Money outstanding = currentInstallment.getTotalOutstanding(currency);
+            Money due = currentInstallment.getDue(currency);
+
+            if (outstanding.isLessThan(due)) {
+                transactionAmountUnprocessed = handleRefundTransactionPaymentOfInstallment(currentInstallment, loanTransaction,
+                        transactionAmountUnprocessed, transactionMappings);
+
+            }
+
+            if (transactionAmountUnprocessed.isZero()) break;
+
+        }
+
+        final Set<LoanCharge> loanFees = extractFeeCharges(charges);
+        final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
+        Integer installmentNumber = null;
+
+        final Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
+        if (feeCharges.isGreaterThanZero()) {
+            undoChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
+        }
+
+        final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
+        if (penaltyCharges.isGreaterThanZero()) {
+            undoChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
+        }
+        loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
+    }
+
+    /**
+     * Invoked when a there is a refund of an active loan or undo of an active
+     * loan
+     * 
+     * Undoes principal, interest, fees and charges of this transaction based on
+     * the repayment strategy
+     * 
+     * @param transactionMappings
+     *            TODO
+     * 
+     */
+    protected abstract Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings);
+
+    private void undoChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
+            final Integer installmentNumber) {
+
+        Money amountRemaining = feeCharges;
+        while (amountRemaining.isGreaterThanZero()) {
+            final LoanCharge paidCharge = findLatestPaidChargeFromUnOrderedSet(charges, feeCharges.getCurrency());
+
+            if (paidCharge != null) {
+                Money feeAmount = feeCharges.zero();
+
+                final Money amountDeductedTowardsCharge = paidCharge.undoPaidOrPartiallyAmountBy(amountRemaining, installmentNumber,
+                        feeAmount);
+                if (amountDeductedTowardsCharge.isGreaterThanZero()) {
+
+                    final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, paidCharge, amountDeductedTowardsCharge
+                            .getAmount().multiply(new BigDecimal(-1)), null);
+                    loanTransaction.getLoanChargesPaid().add(loanChargePaidBy);
+
+                    amountRemaining = amountRemaining.minus(amountDeductedTowardsCharge);
+                }
+            }
+        }
+
+    }
+
+    private LoanCharge findLatestPaidChargeFromUnOrderedSet(final Set<LoanCharge> charges, MonetaryCurrency currency) {
+        LoanCharge latestPaidCharge = null;
+        LoanCharge installemntCharge = null;
+        LoanInstallmentCharge chargePerInstallment = null;
+        for (final LoanCharge loanCharge : charges) {
+            boolean isPaidOrPartiallyPaid = loanCharge.isPaidOrPartiallyPaid(currency);
+            if (isPaidOrPartiallyPaid && !loanCharge.isDueAtDisbursement()) {
+                if (loanCharge.isInstalmentFee()) {
+                    LoanInstallmentCharge paidLoanChargePerInstallment = loanCharge
+                            .getLastPaidOrPartiallyPaidInstallmentLoanCharge(currency);
+                    if (chargePerInstallment == null
+                            || (paidLoanChargePerInstallment != null && chargePerInstallment.getRepaymentInstallment().getDueDate()
+                                    .isBefore(paidLoanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
+                        installemntCharge = loanCharge;
+                        chargePerInstallment = paidLoanChargePerInstallment;
+                    }
+                } else if (latestPaidCharge == null || (loanCharge.isPaidOrPartiallyPaid(currency))
+                        && loanCharge.getDueLocalDate().isAfter(latestPaidCharge.getDueLocalDate())) {
+                    latestPaidCharge = loanCharge;
+                }
+            }
+        }
+        if (latestPaidCharge == null
+                || (chargePerInstallment != null && latestPaidCharge.getDueLocalDate().isAfter(
+                        chargePerInstallment.getRepaymentInstallment().getDueDate()))) {
+            latestPaidCharge = installemntCharge;
+        }
+
+        return latestPaidCharge;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
new file mode 100644
index 0000000..b303d30
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.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.portfolio.loanaccount.domain.transactionprocessor;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.joda.time.LocalDate;
+
+public interface LoanRepaymentScheduleTransactionProcessor {
+
+    void handleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
+            Set<LoanCharge> charges);
+
+    ChangedTransactionDetail handleTransaction(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers,
+            MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
+
+    void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency,
+            List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
+
+    Money handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments);
+
+    /**
+     * Used in interest recalculation to introduce new interest only
+     * installment.
+     */
+    boolean isInterestFirstRepaymentScheduleTransactionProcessor();
+
+    void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
+            final Set<LoanCharge> charges);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java
new file mode 100644
index 0000000..59c1683
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java
@@ -0,0 +1,189 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+
+/**
+ * Creocore style {@link LoanRepaymentScheduleTransactionProcessor}.
+ * 
+ * For standard transactions, pays off components in order of interest, then
+ * principal.
+ * 
+ * If a transaction results in an advance payment or over-payment for a given
+ * installment, the over paid amount is pay off on the principal component of
+ * subsequent installments.
+ * 
+ * If the entire principal of an installment is paid in advance then the
+ * interest component is waived.
+ */
+public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
+
+    /**
+     * For creocore, early is defined as any date before the installment due
+     * date
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
+            final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate, final Money transactionAmount) {
+
+        final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
+
+        return transactionDate.isBefore(currentInstallment.getDueDate());
+    }
+
+    /**
+     * For early/'in advance' repayments, pay off in the same way as on-time
+     * payments, interest first then principal.
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final LocalDate transactionDate, final Money paymentInAdvance,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings);
+    }
+
+    /**
+     * For late repayments, pay off in the same way as on-time payments,
+     * interest first then principal.
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final Money transactionAmountUnprocessed, final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed,
+                transactionMappings);
+    }
+
+    /**
+     * For normal on-time repayments, pays off interest first, then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (loanTransaction.isChargesWaiver()) {
+            penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
+                    loanTransaction.getPenaltyChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment
+                    .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+        } else if (loanTransaction.isInterestWaiver()) {
+            interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else if (loanTransaction.isChargePayment()) {
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+        } else {
+            penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+            interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+        }
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        return transactionAmountRemaining;
+    }
+
+    @SuppressWarnings("unused")
+    @Override
+    protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
+        // dont do anything for with loan over-payment
+    }
+
+    @Override
+    protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+        transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+        }
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java
new file mode 100644
index 0000000..cfad71c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java
@@ -0,0 +1,206 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+
+/**
+ * This {@link LoanRepaymentScheduleTransactionProcessor} defaults to having the
+ * payment order of Interest first, then principal, penalties and fees.
+ */
+public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
+
+    /**
+     * For early/'in advance' repayments, pay off in the same way as on-time
+     * payments, interest first, principal, penalties and charges.
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final LocalDate transactionDate, final Money paymentInAdvance,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final MonetaryCurrency currency = paymentInAdvance.getCurrency();
+        Money transactionAmountRemaining = paymentInAdvance;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (loanTransaction.isChargesWaiver()) {
+            penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
+                    loanTransaction.getPenaltyChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment
+                    .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+        } else if (loanTransaction.isInterestWaiver()) {
+            interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else if (loanTransaction.isChargePayment()) {
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else {
+
+            // Only allocate to principal:
+            principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        }
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+
+    }
+
+    /**
+     * For late repayments, pay off in the same way as on-time payments,
+     * interest first then principal.
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed,
+                transactionMappings);
+    }
+
+    /**
+     * For normal on-time repayments, pays off interest first, then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (loanTransaction.isChargesWaiver()) {
+            // zero this type of transaction and ignore it for now.
+            transactionAmountRemaining = Money.zero(currency);
+        } else if (loanTransaction.isInterestWaiver()) {
+            interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else if (loanTransaction.isChargePayment()) {
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else {
+            interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+            penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        }
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+
+    @Override
+    protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        // final MonetaryCurrency currency =
+        // transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+        }
+
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java
new file mode 100644
index 0000000..ddc5cbc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java
@@ -0,0 +1,184 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+
+/**
+ * Old style {@link LoanRepaymentScheduleTransactionProcessor}.
+ * 
+ * For ALL types of transactions, pays off components in order of interest, then
+ * principal.
+ * 
+ * Other formulas exist on fineract where you can choose 'Declining-Balance
+ * Interest Recalculation' which simply means, recalculate the interest
+ * component based on the how much principal is outstanding at a point in time;
+ * but this isnt trying to model that option only the basic one for now.
+ */
+@SuppressWarnings("unused")
+public class FineractStyleLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
+
+    @Override
+    protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
+            final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate, final Money transactionAmount) {
+
+        final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
+
+        return transactionDate.isBefore(currentInstallment.getDueDate());
+    }
+
+    /**
+     * For early/'in advance' repayments, pay off in the same way as on-time
+     * payments, interest first then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final LocalDate transactionDate, final Money paymentInAdvance,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings);
+    }
+
+    /**
+     * For late repayments, pay off in the same way as on-time payments,
+     * interest first then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed,
+                transactionMappings);
+    }
+
+    /**
+     * For normal on-time repayments, pays off interest first, then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (loanTransaction.isChargesWaiver()) {
+            penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
+                    loanTransaction.getPenaltyChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment
+                    .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+        } else if (loanTransaction.isInterestWaiver()) {
+            interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else if (loanTransaction.isChargePayment()) {
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else {
+            penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+            interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        }
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+
+    @Override
+    protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
+        // TODO - KW - dont do anything with loan over-payment for now
+    }
+
+    @Override
+    protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+        transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+        }
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java
new file mode 100644
index 0000000..e74fd50
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java
@@ -0,0 +1,250 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+
+/**
+ * Heavensfamily style {@link LoanRepaymentScheduleTransactionProcessor}.
+ * 
+ * For standard transactions, pays off components in order of interest, then
+ * principal.
+ * 
+ * If a transaction results in an advance payment or overpayment for a given
+ * installment, the over paid amount is pay off on the principal component of
+ * subsequent installments.
+ * 
+ * If the entire principal of an installment is paid in advance then the
+ * interest component is waived.
+ */
+@SuppressWarnings("unused")
+public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
+
+    /**
+     * For late repayments, pay off in the same way as on-time payments,
+     * interest first then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed,
+                transactionMappings);
+    }
+
+    @Override
+    protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
+            final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate, final Money transactionAmount) {
+
+        boolean isInAdvance = false;
+
+        LocalDate lastInstallmentDueDate = null;
+        int previousInstallmentIndex = 0;
+        if (currentInstallmentIndex > 0) {
+            previousInstallmentIndex = currentInstallmentIndex - 1;
+        }
+
+        final LoanRepaymentScheduleInstallment previousInstallment = installments.get(previousInstallmentIndex);
+        lastInstallmentDueDate = previousInstallment.getDueDate();
+
+        isInAdvance = !(transactionDate.isAfter(lastInstallmentDueDate) || (transactionDate.isEqual(lastInstallmentDueDate)));
+
+        return isInAdvance;
+    }
+
+    /**
+     * For early/'in advance' repayments, pays off principal component only.
+     */
+    @Override
+    protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final LocalDate transactionDate, final Money paymentInAdvance,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final MonetaryCurrency currency = paymentInAdvance.getCurrency();
+        Money transactionAmountRemaining = paymentInAdvance;
+        Money principalPortion = Money.zero(currency);
+        Money interestPortion = Money.zero(currency);
+        Money feeChargesPortion = Money.zero(currency);
+        Money penaltyChargesPortion = Money.zero(currency);
+
+        if (loanTransaction.isInterestWaiver()) {
+            interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else if (loanTransaction.isChargePayment()) {
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+        } else {
+
+            if (currentInstallment.isPrincipalNotCompleted(currency)) {
+                principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+                if (currentInstallment.isPrincipalCompleted(currency)) {
+                    // FIXME - KW - if auto waiving interest need to create
+                    // another transaction to handle this.
+                    currentInstallment.waiveInterestComponent(transactionDate, currentInstallment.getInterestCharged(currency));
+                }
+
+                loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+
+                transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+            }
+
+            // 1. pay of principal with over payment.
+            principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+            interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+            penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+        }
+
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+
+    /**
+     * For normal on-time repayments, pays off interest first, then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (loanTransaction.isChargesWaiver()) {
+            penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
+                    loanTransaction.getPenaltyChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment
+                    .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+        } else if (loanTransaction.isInterestWaiver()) {
+            interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else if (loanTransaction.isChargePayment()) {
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+        } else {
+            // 1. pay of principal before interest.
+
+            principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+            interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+            penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+        }
+
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+
+    @Override
+    protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {}
+
+    @Override
+    protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+        }
+
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
new file mode 100644
index 0000000..a79ae9a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
@@ -0,0 +1,167 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+
+/**
+ * This {@link LoanRepaymentScheduleTransactionProcessor} defaults to having the
+ * payment order of Interest first, then principal, penalties and fees.
+ */
+public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor extends
+        AbstractLoanRepaymentScheduleTransactionProcessor {
+
+    /**
+     * For early/'in advance' repayments, pay off in the same way as on-time
+     * payments, interest first, principal, penalties and charges.
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final LocalDate transactionDate, final Money paymentInAdvance,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings);
+    }
+
+    /**
+     * For late repayments, pay off in the same way as on-time payments,
+     * interest first then principal.
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed,
+                transactionMappings);
+    }
+
+    /**
+     * For normal on-time repayments, pays off interest first, then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (loanTransaction.isChargesWaiver()) {
+            penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
+                    loanTransaction.getPenaltyChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment
+                    .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+        } else if (loanTransaction.isInterestWaiver()) {
+            interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else if (loanTransaction.isChargePayment()) {
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else {
+            interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+            penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        }
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+
+    @Override
+    protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+        }
+
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
new file mode 100644
index 0000000..be1ca96
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
@@ -0,0 +1,171 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+
+/**
+ * This {@link LoanRepaymentScheduleTransactionProcessor} defaults to having the
+ * payment order of principal first, then interest, penalties and fees.
+ */
+public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor extends
+        AbstractLoanRepaymentScheduleTransactionProcessor {
+
+    /**
+     * For early/'in advance' repayments, pay off in the same way as on-time
+     * payments, interest first, principal, penalties and charges.
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final LocalDate transactionDate, final Money paymentInAdvance,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings);
+    }
+
+    /**
+     * For late repayments, pay off in the same way as on-time payments,
+     * interest first then principal.
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed,
+                transactionMappings);
+    }
+
+    /**
+     * For normal on-time repayments, pays off interest first, then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (loanTransaction.isChargesWaiver()) {
+            penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
+                    loanTransaction.getPenaltyChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment
+                    .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+        } else if (loanTransaction.isInterestWaiver()) {
+            interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else if (loanTransaction.isChargePayment()) {
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        } else {
+            principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+            interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        }
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+
+        return transactionAmountRemaining;
+    }
+
+    @Override
+    protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        // final MonetaryCurrency currency =
+        // transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+        }
+
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java
new file mode 100644
index 0000000..f976874
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java
@@ -0,0 +1,302 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
+
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+
+/**
+ * Adhikar/RBI style {@link LoanRepaymentScheduleTransactionProcessor}.
+ * 
+ * From https://mifosforge.jira.com/browse/MIFOS-5636:
+ * 
+ * Per RBI regulations, all interest must be paid (both current and overdue)
+ * before principal is paid.
+ * 
+ * For example on a loan with two installments due (one current and one overdue)
+ * of 220 each (200 principal + 20 interest):
+ * 
+ * Partial Payment of 40 20 Payment to interest on Installment #1 (200 principal
+ * remaining) 20 Payment to interest on Installment #2 (200 principal remaining)
+ */
+public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
+
+    /**
+     * For creocore, early is defined as any date before the installment due
+     * date
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
+            final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate, final Money transactionAmount) {
+
+        final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
+
+        return transactionDate.isBefore(currentInstallment.getDueDate());
+    }
+
+    /**
+     * For early/'in advance' repayments, pays off principal component only.
+     */
+    @SuppressWarnings("unused")
+    @Override
+    protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final LocalDate transactionDate, final Money paymentInAdvance,
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings);
+    }
+
+    /**
+     * For late repayments, pay off in the same way as on-time payments,
+     * interest first then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
+            final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        // pay of overdue and current interest due given transaction date
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money interestWaivedPortion = Money.zero(currency);
+        Money feeChargesPortion = Money.zero(currency);
+        Money penaltyChargesPortion = Money.zero(currency);
+
+        if (loanTransaction.isInterestWaiver()) {
+            interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
+
+            final Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+            loanTransaction.updateComponents(principalPortion, interestWaivedPortion, feeChargesPortion, penaltyChargesPortion);
+            if (interestWaivedPortion.isGreaterThanZero()) {
+                transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                        interestWaivedPortion, feeChargesPortion, penaltyChargesPortion));
+            }
+        } else if (loanTransaction.isChargePayment()) {
+            final Money principalPortion = Money.zero(currency);
+            final Money interestPortion = Money.zero(currency);
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+            loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+            if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+                transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                        interestPortion, feeChargesPortion, penaltyChargesPortion));
+            }
+        } else {
+
+            final LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(
+                    loanTransaction.getTransactionDate(), installments);
+
+            for (final LoanRepaymentScheduleInstallment installment : installments) {
+                if ((installment.isInterestDue(currency) || installment.getFeeChargesOutstanding(currency).isGreaterThanZero() || installment
+                        .getPenaltyChargesOutstanding(currency).isGreaterThanZero())
+                        && (installment.isOverdueOn(loanTransaction.getTransactionDate()) || installment.getInstallmentNumber().equals(
+                                currentInstallmentBasedOnTransactionDate.getInstallmentNumber()))) {
+                    penaltyChargesPortion = installment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                    transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+                    feeChargesPortion = installment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                    transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+                    final Money interestPortion = installment.payInterestComponent(transactionDate, transactionAmountRemaining);
+                    transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+                    final Money principalPortion = Money.zero(currency);
+                    loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+                    if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+                        transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(installment, principalPortion,
+                                interestPortion, feeChargesPortion, penaltyChargesPortion));
+                    }
+                }
+            }
+
+            // With whatever is remaining, pay off principal components of
+            // installments
+            for (final LoanRepaymentScheduleInstallment installment : installments) {
+                if (installment.isPrincipalNotCompleted(currency) && transactionAmountRemaining.isGreaterThanZero()) {
+                    final Money principalPortion = installment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+                    transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+                    final Money interestPortion = Money.zero(currency);
+                    loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(currency), Money.zero(currency));
+                    boolean isMappingUpdated = false;
+                    for (LoanTransactionToRepaymentScheduleMapping repaymentScheduleMapping : transactionMappings) {
+                        if (repaymentScheduleMapping.getLoanRepaymentScheduleInstallment().getDueDate().equals(installment.getDueDate())) {
+                            repaymentScheduleMapping.updateComponents(principalPortion, principalPortion.zero(), principalPortion.zero(),
+                                    principalPortion.zero());
+                            isMappingUpdated = true;
+                            break;
+                        }
+                    }
+                    if (!isMappingUpdated
+                            && principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion)
+                                    .isGreaterThanZero()) {
+                        transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(installment, principalPortion,
+                                interestPortion, feeChargesPortion, penaltyChargesPortion));
+                    }
+                }
+            }
+        }
+
+        return transactionAmountRemaining;
+    }
+
+    private LoanRepaymentScheduleInstallment nearestInstallment(final LocalDate transactionDate,
+            final List<LoanRepaymentScheduleInstallment> installments) {
+
+        LoanRepaymentScheduleInstallment nearest = installments.get(0);
+        for (final LoanRepaymentScheduleInstallment installment : installments) {
+            if (installment.getDueDate().isBefore(transactionDate) || installment.getDueDate().isEqual(transactionDate)) {
+                nearest = installment;
+            } else if (installment.getDueDate().isAfter(transactionDate)) {
+                break;
+            }
+        }
+        return nearest;
+    }
+
+    /**
+     * For normal on-time repayments, pays off interest first, then principal.
+     */
+    @Override
+    protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (loanTransaction.isChargesWaiver()) {
+
+            penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
+                    loanTransaction.getPenaltyChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment
+                    .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+        } else if (loanTransaction.isInterestWaiver()) {
+            interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+        } else if (loanTransaction.isChargePayment()) {
+            if (loanTransaction.isPenaltyPayment()) {
+                penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+            } else {
+                feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+                transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+            }
+        } else {
+
+            penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+
+            feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+
+            interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+
+            principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+        }
+
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+
+    @SuppressWarnings("unused")
+    @Override
+    protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
+        // dont do anything for with loan over-payment
+    }
+
+    @Override
+    public boolean isInterestFirstRepaymentScheduleTransactionProcessor() {
+        return true;
+    }
+
+    @Override
+    protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+            final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
+            final List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+
+        final LocalDate transactionDate = loanTransaction.getTransactionDate();
+        // final MonetaryCurrency currency =
+        // transactionAmountUnprocessed.getCurrency();
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+        Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+        }
+
+        if (transactionAmountRemaining.isGreaterThanZero()) {
+            penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+            transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+        }
+
+        loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+        if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
+            transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(currentInstallment, principalPortion,
+                    interestPortion, feeChargesPortion, penaltyChargesPortion));
+        }
+        return transactionAmountRemaining;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/ExceedingTrancheCountException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/ExceedingTrancheCountException.java
new file mode 100644
index 0000000..3209626
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/ExceedingTrancheCountException.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.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ExceedingTrancheCountException extends AbstractPlatformDomainRuleException {
+
+    public ExceedingTrancheCountException(final String entity, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + ".exceeding.max.tranche.count", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanStateTransitionException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanStateTransitionException.java
new file mode 100644
index 0000000..63505b8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanStateTransitionException.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown an action to transition a
+ * loan from one state to another violates a domain rule.
+ */
+public class InvalidLoanStateTransitionException extends AbstractPlatformDomainRuleException {
+
+    public InvalidLoanStateTransitionException(final String action, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.loan." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTransactionTypeException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTransactionTypeException.java
new file mode 100644
index 0000000..82751d1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTransactionTypeException.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown an action to transition a
+ * loan from one state to another violates a domain rule.
+ */
+public class InvalidLoanTransactionTypeException extends AbstractPlatformDomainRuleException {
+
+    public InvalidLoanTransactionTypeException(final String action, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.loan." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidPaidInAdvanceAmountException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidPaidInAdvanceAmountException.java
new file mode 100644
index 0000000..b65089f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidPaidInAdvanceAmountException.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.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown an action to transition a
+ * loan from one state to another violates a domain rule.
+ */
+public class InvalidPaidInAdvanceAmountException extends AbstractPlatformDomainRuleException {
+    
+    public InvalidPaidInAdvanceAmountException(final String refundAmountString) {
+        super("error.msg.loan.refund.amount.invalid", "The refund amount `" + refundAmountString + "`"
+                + "` is invalid or loan is not paid in advance.", new Object[] { refundAmountString});
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidRefundDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidRefundDateException.java
new file mode 100644
index 0000000..9215016
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidRefundDateException.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class InvalidRefundDateException extends AbstractPlatformDomainRuleException {
+
+    public InvalidRefundDateException(final String refundDateAsString) {
+        super("error.msg.loan.refund.failed", "The refund date`" + refundDateAsString + "`"
+                + "` cannot be before the smallest repayment transaction date", new Object[] { refundDateAsString});
+    }
+
+    public InvalidRefundDateException(final String defaultUserMessage,final String entity,final Object... defaultUserMessageArgs) {
+        super("error.msg.loan." + entity , defaultUserMessage, defaultUserMessageArgs);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanApplicationDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanApplicationDateException.java
new file mode 100644
index 0000000..7796c66
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanApplicationDateException.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.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanApplicationDateException extends AbstractPlatformDomainRuleException {
+
+    public LoanApplicationDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.loan.application." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted.java
new file mode 100644
index 0000000..e0dadd9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted.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.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when trying to delete a
+ * loan in an invalid state.
+ */
+public class LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted extends AbstractPlatformDomainRuleException {
+
+    public LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted(final Long id) {
+        super("error.msg.loan.cannot.delete.loan.in.its.present.state", "Loan with identifier " + id
+                + " cannot be deleted in its current state.", id);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified.java
new file mode 100644
index 0000000..8386e63
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified.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.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when trying to modify a
+ * loan in an invalid state.
+ */
+public class LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified extends AbstractPlatformDomainRuleException {
+
+    public LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(final Long id) {
+        super("error.msg.loan.cannot.modify.loan.in.its.present.state", "Loan application with identifier " + id
+                + " cannot be modified in its current state.", id);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanDisbursalException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanDisbursalException.java
new file mode 100644
index 0000000..1d86a03
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanDisbursalException.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanDisbursalException extends AbstractPlatformDomainRuleException {
+
+    public LoanDisbursalException(final String currentProduct, final String restrictedProduct) {
+        super("error.msg.loan.disbursal.failed", "This loan could not be disbursed as `" + currentProduct + "` and `" + restrictedProduct
+                + "` are not allowed to co-exist", new Object[] { currentProduct, restrictedProduct });
+    }
+
+    public LoanDisbursalException(final String defaultUserMessage, final String entity, final Object... defaultUserMessageArgs) {
+        super("error.msg.loan." + entity, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanMultiDisbursementException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanMultiDisbursementException.java
new file mode 100644
index 0000000..f538e47
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanMultiDisbursementException.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.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanMultiDisbursementException extends AbstractPlatformDomainRuleException {
+
+    public LoanMultiDisbursementException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg." + defaultUserMessage, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanNotFoundException.java
new file mode 100644
index 0000000..9cde610
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when loan resources are not found.
+ */
+public class LoanNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public LoanNotFoundException(final Long id) {
+        super("error.msg.loan.id.invalid", "Loan with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerAssignmentDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerAssignmentDateException.java
new file mode 100644
index 0000000..9791437
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerAssignmentDateException.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.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanOfficerAssignmentDateException extends AbstractPlatformDomainRuleException {
+
+    public LoanOfficerAssignmentDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.loan.assignment.date." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerAssignmentException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerAssignmentException.java
new file mode 100644
index 0000000..33aa00b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerAssignmentException.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.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.joda.time.LocalDate;
+
+public class LoanOfficerAssignmentException extends AbstractPlatformDomainRuleException {
+
+    public LoanOfficerAssignmentException(final Long loanId, final Long fromLoanOfficerId) {
+        super("error.msg.loan.not.assigned.to.loan.officer", "Loan with identifier " + loanId
+                + " is not assigned to Loan Officer with identifier " + fromLoanOfficerId + ".", loanId);
+    }
+
+    public LoanOfficerAssignmentException(final Long loanId, final LocalDate date) {
+        super("error.msg.loan.assignment.date.is.before.last.assignment.date", "Loan with identifier " + loanId
+                + " was already assigned before date " + date.toString());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerUnassignmentDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerUnassignmentDateException.java
new file mode 100644
index 0000000..297433d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerUnassignmentDateException.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.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanOfficerUnassignmentDateException extends AbstractPlatformDomainRuleException {
+
+    public LoanOfficerUnassignmentDateException(final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.loan.loanofficer.unassign.date." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerUnassignmentException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerUnassignmentException.java
new file mode 100644
index 0000000..0b8afb9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanOfficerUnassignmentException.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.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanOfficerUnassignmentException extends AbstractPlatformDomainRuleException {
+
+    public LoanOfficerUnassignmentException(final Long loanId) {
+        super("error.msg.loan.not.assigned.to.loan.officer", "Loan with identifier " + loanId + " is not assigned to any loan officer.",
+                loanId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTemplateTypeRequiredException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTemplateTypeRequiredException.java
new file mode 100644
index 0000000..3acbca9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTemplateTypeRequiredException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when not supported loan
+ * template type is sent.
+ */
+public class LoanTemplateTypeRequiredException extends AbstractPlatformDomainRuleException {
+
+    public LoanTemplateTypeRequiredException(final String defaultUserMessage) {
+        super("error.msg.loan.template.type.required", defaultUserMessage);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionNotFoundException.java
new file mode 100644
index 0000000..d1d3215
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionNotFoundException.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.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when loan resources are not found.
+ */
+public class LoanTransactionNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public LoanTransactionNotFoundException(final Long id) {
+        super("error.msg.loan.id.invalid", "Transaction with identifier " + id + " does not exist", id);
+    }
+
+    public LoanTransactionNotFoundException(final Long id, final Long loanId) {
+        super("error.msg.loan.id.invalid",
+                "Transaction with identifier " + id + " does not exist for loan with identifier " + loanId + ".", id, loanId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionProcessingStrategyNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionProcessingStrategyNotFoundException.java
new file mode 100644
index 0000000..8780fd4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionProcessingStrategyNotFoundException.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when loan transactions processing strategy
+ * resources are not found.
+ */
+public class LoanTransactionProcessingStrategyNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public LoanTransactionProcessingStrategyNotFoundException(final Long id) {
+        super("error.msg.transactions.processing.strategy.id.invalid", "Loan transaction processing strategy with identifier " + id
+                + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/MinDaysBetweenDisbursalAndFirstRepaymentViolationException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/MinDaysBetweenDisbursalAndFirstRepaymentViolationException.java
new file mode 100644
index 0000000..cb08732
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/MinDaysBetweenDisbursalAndFirstRepaymentViolationException.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.joda.time.LocalDate;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when a number of days
+ * between disbursal date and firstRepayment is less than
+ * minimumDaysBetweenDisbursalAndFirstRepayment
+ * 
+ */
+public class MinDaysBetweenDisbursalAndFirstRepaymentViolationException extends AbstractPlatformDomainRuleException {
+
+    public MinDaysBetweenDisbursalAndFirstRepaymentViolationException(final LocalDate disbursalDate, final LocalDate firstRepaymentDate,
+            Integer minimumDaysBetweenDisbursalAndFirstRepayment) {
+        super("error.msg.loan.days.between.first.repayment.and.disbursal.are.less.than.minimum.allowed",
+                "Number of days between loan disbursal  (" + disbursalDate + ") and first repayment (" + firstRepaymentDate
+                        + ") can't be less than (" + minimumDaysBetweenDisbursalAndFirstRepayment + ").", disbursalDate,
+                firstRepaymentDate, minimumDaysBetweenDisbursalAndFirstRepayment);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/MultiDisbursementDataRequiredException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/MultiDisbursementDataRequiredException.java
new file mode 100644
index 0000000..456363f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/MultiDisbursementDataRequiredException.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.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class MultiDisbursementDataRequiredException extends AbstractPlatformDomainRuleException {
+
+    public MultiDisbursementDataRequiredException(final String entity, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + ".required", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/NotSupportedLoanTemplateTypeException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/NotSupportedLoanTemplateTypeException.java
new file mode 100644
index 0000000..af89730
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/NotSupportedLoanTemplateTypeException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when not supported loan
+ * template type is sent.
+ */
+public class NotSupportedLoanTemplateTypeException extends AbstractPlatformDomainRuleException {
+
+    public NotSupportedLoanTemplateTypeException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.loan.template.type.not.supported", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/UndoLastTrancheDisbursementException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/UndoLastTrancheDisbursementException.java
new file mode 100644
index 0000000..e66c272
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/UndoLastTrancheDisbursementException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class UndoLastTrancheDisbursementException extends AbstractPlatformDomainRuleException{
+
+	public UndoLastTrancheDisbursementException(final Object... defaultUserMessageArgs) {
+		super("error.msg.cannot.undo.last.disbursal.after.repayments or waivers"," Cannot undo last disbursement after repayments or waivers.",
+				defaultUserMessageArgs);
+		
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/GuarantorConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/GuarantorConstants.java
new file mode 100755
index 0000000..886bf71
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/GuarantorConstants.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.loanaccount.guarantor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class GuarantorConstants {
+
+    public static final String GUARANTOR_RELATIONSHIP_CODE_NAME = "GuarantorRelationship";
+
+    /***
+     * Enum of all parameters passed in while creating/updating a loan product
+     ***/
+    public static enum GUARANTOR_JSON_INPUT_PARAMS {
+        LOAN_ID("loanId"), CLIENT_RELATIONSHIP_TYPE_ID("clientRelationshipTypeId"), GUARANTOR_TYPE_ID("guarantorTypeId"), ENTITY_ID(
+                "entityId"), FIRSTNAME("firstname"), LASTNAME("lastname"), ADDRESS_LINE_1("addressLine1"), ADDRESS_LINE_2("addressLine2"), CITY(
+                "city"), STATE("state"), ZIP("zip"), COUNTRY("country"), MOBILE_NUMBER("mobileNumber"), PHONE_NUMBER("housePhoneNumber"), COMMENT(
+                "comment"), DATE_OF_BIRTH("dob"), AMOUNT("amount"), SAVINGS_ID("savingsId");
+
+        private final String value;
+
+        private GUARANTOR_JSON_INPUT_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+        static {
+            for (final GUARANTOR_JSON_INPUT_PARAMS type : GUARANTOR_JSON_INPUT_PARAMS.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;
+        }
+    }
+    
+    public static final String GUARANTOR_SELF_GUARANTEE_ERROR = "min.self.guarantee.required";
+    public static final String GUARANTOR_EXTERNAL_GUARANTEE_ERROR = "min.external.guarantee.required";
+    public static final String GUARANTOR_MANDATORY_GUARANTEE_ERROR = "mandated.guarantee.required";
+    public static final String GUARANTOR_INSUFFICIENT_BALANCE_ERROR = "insufficient.balance";
+    public static final String GUARANTOR_NOT_ACTIVE_ERROR = "not.active";
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/api/GuarantorsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/api/GuarantorsApiResource.java
new file mode 100644
index 0000000..96060a7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/api/GuarantorsApiResource.java
@@ -0,0 +1,215 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants;
+import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorType;
+import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorEnumerations;
+import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/loans/{loanId}/guarantors")
+@Component
+@Scope("singleton")
+public class GuarantorsApiResource {
+
+    private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "loanId", "clientRelationshipType",
+            "guarantorType", "firstname", "lastname", "entityId", "externalId", "officeName", "joinedDate", "addressLine1", "addressLine2",
+            "city", "state", "zip", "country", "mobileNumber", "housePhoneNumber", "comment", "dob", "guarantorTypeOptions",
+            "allowedClientRelationshipTypes"));
+
+    private final String resourceNameForPermission = "GUARANTOR";
+
+    private final GuarantorReadPlatformService guarantorReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+    private final DefaultToApiJsonSerializer<GuarantorData> apiJsonSerializerService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final PlatformSecurityContext context;
+    private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService;
+    private final LoanReadPlatformService loanReadPlatformService;
+
+    @Autowired
+    public GuarantorsApiResource(final PlatformSecurityContext context, final GuarantorReadPlatformService guarantorReadPlatformService,
+            final DefaultToApiJsonSerializer<GuarantorData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService,
+            final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService,
+            final LoanReadPlatformService loanReadPlatformService) {
+        this.context = context;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiJsonSerializerService = toApiJsonSerializer;
+        this.guarantorReadPlatformService = guarantorReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+        this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService;
+        this.loanReadPlatformService = loanReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String newGuarantorTemplate(@Context final UriInfo uriInfo) {
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        final List<EnumOptionData> guarantorTypeOptions = GuarantorEnumerations.guarantorType(GuarantorType.values());
+        final Collection<CodeValueData> allowedClientRelationshipTypes = this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode(GuarantorConstants.GUARANTOR_RELATIONSHIP_CODE_NAME);
+        final Collection<PortfolioAccountData> accountLinkingOptions = null;
+        final GuarantorData guarantorData = GuarantorData.template(guarantorTypeOptions, allowedClientRelationshipTypes,
+                accountLinkingOptions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, guarantorData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveGuarantorDetails(@Context final UriInfo uriInfo, @PathParam("loanId") final Long loanId) {
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        final List<GuarantorData> guarantorDatas = this.guarantorReadPlatformService.retrieveGuarantorsForValidLoan(loanId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return this.apiJsonSerializerService.serialize(settings, guarantorDatas, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{guarantorId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveGuarantorDetails(@Context final UriInfo uriInfo, @PathParam("loanId") final Long loanId,
+            @PathParam("guarantorId") final Long guarantorId) {
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        GuarantorData guarantorData = this.guarantorReadPlatformService.retrieveGuarantor(loanId, guarantorId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            final Collection<CodeValueData> allowedClientRelationshipTypes = this.codeValueReadPlatformService
+                    .retrieveCodeValuesByCode(GuarantorConstants.GUARANTOR_RELATIONSHIP_CODE_NAME);
+            final List<EnumOptionData> guarantorTypeOptions = GuarantorEnumerations.guarantorType(GuarantorType.values());
+            final Collection<PortfolioAccountData> accountLinkingOptions = null;
+            guarantorData = GuarantorData.templateOnTop(guarantorData, guarantorTypeOptions, allowedClientRelationshipTypes,
+                    accountLinkingOptions);
+        }
+
+        return this.apiJsonSerializerService.serialize(settings, guarantorData, RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createGuarantor(@PathParam("loanId") final Long loanId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createGuarantor(loanId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @PUT
+    @Path("{guarantorId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateGuarantor(@PathParam("loanId") final Long loanId, @PathParam("guarantorId") final Long guarantorId,
+            final String jsonRequestBody) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateGuarantor(loanId, guarantorId).withJson(jsonRequestBody)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @DELETE
+    @Path("{guarantorId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteGuarantor(@PathParam("loanId") final Long loanId, @PathParam("guarantorId") final Long guarantorId,
+            @QueryParam("guarantorFundingId") final Long guarantorFundingId) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteGuarantor(loanId, guarantorId, guarantorFundingId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.apiJsonSerializerService.serialize(result);
+    }
+
+    @GET
+    @Path("accounts/template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String accountsTemplate(@QueryParam("clientId") final Long clientId, @PathParam("loanId") final Long loanId,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
+
+        PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(PortfolioAccountType.SAVINGS.getValue(), clientId, null);
+        Collection<PortfolioAccountData> accountLinkingOptions = null;
+        if (this.loanReadPlatformService.isGuaranteeRequired(loanId)) {
+            accountLinkingOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO);
+        }
+        final GuarantorData guarantorData = GuarantorData.template(null, null, accountLinkingOptions);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.apiJsonSerializerService.serialize(settings, guarantorData, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/command/GuarantorCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/command/GuarantorCommand.java
new file mode 100644
index 0000000..88f0d04
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/command/GuarantorCommand.java
@@ -0,0 +1,226 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.command;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants.GUARANTOR_JSON_INPUT_PARAMS;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorType;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable command for creating or updating details of a Guarantor.
+ */
+public class GuarantorCommand {
+
+    /*** Fields for capturing relationship of Guarantor with customer **/
+    private final Long clientRelationshipTypeId;
+
+    /*** Fields for current customers serving as guarantors **/
+    private final Integer guarantorTypeId;
+    private final Long entityId;
+
+    /*** Fields for external persons serving as guarantors ***/
+    private final String firstname;
+    private final String lastname;
+    private final String addressLine1;
+    private final String addressLine2;
+    private final String city;
+    private final String state;
+    private final String zip;
+    private final String country;
+    private final String mobileNumber;
+    private final String housePhoneNumber;
+    private final String comment;
+    private final LocalDate dob;
+    private final Long savingsId;
+    private final BigDecimal amount;
+
+    public GuarantorCommand(final Long clientRelationshipTypeId, final Integer guarantorTypeId, final Long entityId,
+            final String firstname, final String lastname, final String addressLine1, final String addressLine2, final String city,
+            final String state, final String zip, final String country, final String mobileNumber, final String housePhoneNumber,
+            final String comment, final LocalDate dob, final Long savingsId, final BigDecimal amount) {
+
+        this.clientRelationshipTypeId = clientRelationshipTypeId;
+
+        /*** Fields for current entities serving as guarantors **/
+        this.guarantorTypeId = guarantorTypeId;
+        this.entityId = entityId;
+
+        /*** Fields for external persons serving as guarantors ***/
+        this.firstname = firstname;
+        this.lastname = lastname;
+        this.addressLine1 = addressLine1;
+        this.addressLine2 = addressLine2;
+        this.city = city;
+        this.state = state;
+        this.zip = zip;
+        this.country = country;
+        this.mobileNumber = mobileNumber;
+        this.housePhoneNumber = housePhoneNumber;
+        this.comment = comment;
+        this.dob = dob;
+        this.savingsId = savingsId;
+        this.amount = amount;
+    }
+
+    public boolean isExternalGuarantor() {
+        return GuarantorType.EXTERNAL.getValue().equals(this.guarantorTypeId);
+    }
+
+    public Date getDobAsDate() {
+        return this.dob.toDateTimeAtStartOfDay().toDate();
+    }
+
+    public void validateForCreate() {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = getDataValidator(dataValidationErrors);
+
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.CLIENT_RELATIONSHIP_TYPE_ID.getValue())
+                .value(this.clientRelationshipTypeId).ignoreIfNull().integerGreaterThanZero();
+
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.GUARANTOR_TYPE_ID.getValue()).value(this.guarantorTypeId).notNull()
+                .inMinMaxRange(GuarantorType.getMinValue(), GuarantorType.getMaxValue());
+
+        // validate for existing Client or Staff serving as gurantor
+        if (!isExternalGuarantor()) {
+            baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.ENTITY_ID.getValue()).value(this.entityId).notNull()
+                    .integerGreaterThanZero();
+            baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.SAVINGS_ID.getValue()).value(this.savingsId)
+                    .longGreaterThanZero();
+            if (this.savingsId != null) {
+                baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.AMOUNT.getValue()).value(this.amount).notNull()
+                        .positiveAmount();
+            }
+        } else {
+            // validate for an external guarantor
+            baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.FIRSTNAME.getValue()).value(this.firstname).notBlank()
+                    .notExceedingLengthOf(50);
+            baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.LASTNAME.getValue()).value(this.lastname).notBlank()
+                    .notExceedingLengthOf(50);
+            validateNonMandatoryFieldsForMaxLength(baseDataValidator);
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForUpdate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = getDataValidator(dataValidationErrors);
+
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.CLIENT_RELATIONSHIP_TYPE_ID.getValue())
+                .value(this.clientRelationshipTypeId).ignoreIfNull().integerGreaterThanZero();
+
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.GUARANTOR_TYPE_ID.getValue()).value(this.guarantorTypeId)
+                .ignoreIfNull().inMinMaxRange(GuarantorType.getMinValue(), GuarantorType.getMaxValue());
+
+        // validate for existing Client or Staff serving as gurantor
+        if (!isExternalGuarantor()) {
+            baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.ENTITY_ID.getValue()).value(this.entityId).ignoreIfNull()
+                    .integerGreaterThanZero();
+            baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.SAVINGS_ID.getValue()).value(this.savingsId)
+                    .longGreaterThanZero();
+            if (this.savingsId != null) {
+                baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.AMOUNT.getValue()).value(this.amount).notNull()
+                        .positiveAmount();
+            }
+        } else {
+            // TODO: Vishwas this validation is buggy (it is compulsory to
+            // update
+            // firstname and last name when a guarantor type is changed), to be
+            // corrected while
+            // refactoring for maker checker
+            baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.FIRSTNAME.getValue()).value(this.firstname).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+            baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.LASTNAME.getValue()).value(this.lastname).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+
+            validateNonMandatoryFieldsForMaxLength(baseDataValidator);
+        }
+        baseDataValidator.reset().anyOfNotNull(this.entityId, this.addressLine1, this.addressLine2, this.city, this.comment, this.country,
+                this.firstname, this.housePhoneNumber, this.lastname, this.mobileNumber, this.state, this.zip);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    /**
+     * @param baseDataValidator
+     */
+    private void validateNonMandatoryFieldsForMaxLength(final DataValidatorBuilder baseDataValidator) {
+        // validate non mandatory fields for length
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_1.getValue()).value(this.addressLine1).ignoreIfNull()
+                .notExceedingLengthOf(500);
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_2.getValue()).value(this.addressLine2).ignoreIfNull()
+                .notExceedingLengthOf(500);
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.CITY.getValue()).value(this.city).ignoreIfNull()
+                .notExceedingLengthOf(50);
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.STATE.getValue()).value(this.state).ignoreIfNull()
+                .notExceedingLengthOf(50);
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.ZIP.getValue()).value(this.zip).ignoreIfNull()
+                .notExceedingLengthOf(50);
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.COUNTRY.getValue()).value(this.country).ignoreIfNull()
+                .notExceedingLengthOf(50);
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.MOBILE_NUMBER.getValue()).value(this.mobileNumber).ignoreIfNull()
+                .notExceedingLengthOf(20).validatePhoneNumber();
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.PHONE_NUMBER.getValue()).value(this.housePhoneNumber)
+                .ignoreIfNull().notExceedingLengthOf(20).validatePhoneNumber();
+        baseDataValidator.reset().parameter(GUARANTOR_JSON_INPUT_PARAMS.COMMENT.getValue()).value(this.comment).ignoreIfNull()
+                .notExceedingLengthOf(500);
+    }
+
+    /**
+     * @param dataValidationErrors
+     * @return
+     */
+    private DataValidatorBuilder getDataValidator(final List<ApiParameterError> dataValidationErrors) {
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("Guarantor");
+        return baseDataValidator;
+    }
+
+    public Long getClientRelationshipTypeId() {
+        return this.clientRelationshipTypeId;
+    }
+
+    public Long getEntityId() {
+        return this.entityId;
+    }
+
+    public Integer getGuarantorTypeId() {
+        return this.guarantorTypeId;
+    }
+
+    public Long getSavingsId() {
+        return this.savingsId;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorDTO.java
new file mode 100755
index 0000000..04a31c6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorDTO.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+public class GuarantorDTO {
+
+    @SuppressWarnings("unused")
+    private BigDecimal paidAmount;
+    @SuppressWarnings("unused")
+    private Loan loan;
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java
new file mode 100644
index 0000000..f46b52a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java
@@ -0,0 +1,154 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.data;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorType;
+import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorEnumerations;
+import org.joda.time.LocalDate;
+
+public class GuarantorData {
+
+    private final Long id;
+    private final Long loanId;
+    private final CodeValueData clientRelationshipType;
+    private final EnumOptionData guarantorType;
+
+    private final String firstname;
+    private final String lastname;
+
+    /*** Fields for current customers/staff serving as guarantors **/
+    private final Long entityId;
+    private final String externalId;
+    private final String officeName;
+    private final LocalDate joinedDate;
+
+    /*** Fields for external persons serving as guarantors ***/
+
+    private final String addressLine1;
+    private final String addressLine2;
+    private final String city;
+    private final String state;
+    private final String zip;
+    private final String country;
+    private final String mobileNumber;
+    private final String housePhoneNumber;
+    private final String comment;
+    private final LocalDate dob;
+    private final Collection<GuarantorFundingData> guarantorFundingDetails;
+    private final boolean status;
+
+    // template
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> guarantorTypeOptions;
+    private final Collection<CodeValueData> allowedClientRelationshipTypes;
+    private final Collection<PortfolioAccountData> accountLinkingOptions;
+
+    public static GuarantorData template(final List<EnumOptionData> guarantorTypeOptions,
+            final Collection<CodeValueData> allowedClientRelationshipTypes, Collection<PortfolioAccountData> accountLinkingOptions) {
+        final Collection<GuarantorFundingData> guarantorFundingDetails = null;
+        final boolean status = false;
+        return new GuarantorData(null, null, null, null, GuarantorEnumerations.guarantorType(GuarantorType.CUSTOMER), null, null, null,
+                null, null, null, null, null, null, null, null, null, null, null, null, status, guarantorFundingDetails,
+                guarantorTypeOptions, allowedClientRelationshipTypes, accountLinkingOptions);
+    }
+
+    public static GuarantorData templateOnTop(final GuarantorData guarantorData, final List<EnumOptionData> guarantorTypeOptions,
+            final Collection<CodeValueData> allowedClientRelationshipTypes, Collection<PortfolioAccountData> accountLinkingOptions) {
+        return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId,
+                guarantorData.guarantorType, guarantorData.firstname, guarantorData.lastname, guarantorData.dob,
+                guarantorData.addressLine1, guarantorData.addressLine2, guarantorData.city, guarantorData.state, guarantorData.zip,
+                guarantorData.country, guarantorData.mobileNumber, guarantorData.housePhoneNumber, guarantorData.comment,
+                guarantorData.officeName, guarantorData.joinedDate, guarantorData.externalId, guarantorData.status,
+                guarantorData.guarantorFundingDetails, guarantorTypeOptions, allowedClientRelationshipTypes, accountLinkingOptions);
+    }
+
+    public static GuarantorData mergeClientData(final ClientData clientData, final GuarantorData guarantorData) {
+        return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId,
+                guarantorData.guarantorType, clientData.getFirstname(), clientData.getLastname(), null, null, null, null, null, null, null,
+                null, null, null, clientData.officeName(), clientData.getActivationDate(), clientData.getExternalId(),
+                guarantorData.status, guarantorData.guarantorFundingDetails, null, guarantorData.allowedClientRelationshipTypes,
+                guarantorData.accountLinkingOptions);
+    }
+
+    public static GuarantorData mergeStaffData(final StaffData staffData, final GuarantorData guarantorData) {
+        return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId,
+                guarantorData.guarantorType, staffData.getFirstname(), staffData.getLastname(), null, null, null, null, null, null, null,
+                null, null, null, staffData.getOfficeName(), null, null, guarantorData.status, guarantorData.guarantorFundingDetails, null,
+                guarantorData.allowedClientRelationshipTypes, guarantorData.accountLinkingOptions);
+    }
+
+    public GuarantorData(final Long id, final Long loanId, final CodeValueData clientRelationshipType, final Long entityId,
+            final EnumOptionData guarantorType, final String firstname, final String lastname, final LocalDate dob,
+            final String addressLine1, final String addressLine2, final String city, final String state, final String zip,
+            final String country, final String mobileNumber, final String housePhoneNumber, final String comment, final String officeName,
+            final LocalDate joinedDate, final String externalId, final boolean status,
+            Collection<GuarantorFundingData> guarantorFundingDetails, final List<EnumOptionData> guarantorTypeOptions,
+            final Collection<CodeValueData> allowedClientRelationshipTypes, final Collection<PortfolioAccountData> accountLinkingOptions) {
+        this.id = id;
+        this.loanId = loanId;
+        this.clientRelationshipType = clientRelationshipType;
+        this.guarantorType = guarantorType;
+        this.entityId = entityId;
+        this.firstname = firstname;
+        this.lastname = lastname;
+        this.dob = dob;
+        this.addressLine1 = addressLine1;
+        this.addressLine2 = addressLine2;
+        this.city = city;
+        this.state = state;
+        this.zip = zip;
+        this.country = country;
+        this.mobileNumber = mobileNumber;
+        this.housePhoneNumber = housePhoneNumber;
+        this.comment = comment;
+        this.officeName = officeName;
+        this.joinedDate = joinedDate;
+        this.externalId = externalId;
+        this.status = status;
+        this.guarantorFundingDetails = guarantorFundingDetails;
+        this.guarantorTypeOptions = guarantorTypeOptions;
+        this.allowedClientRelationshipTypes = allowedClientRelationshipTypes;
+        this.accountLinkingOptions = accountLinkingOptions;
+    }
+
+    public boolean isExternalGuarantor() {
+        return GuarantorType.EXTERNAL.getValue().equals(this.guarantorType.getId().intValue());
+    }
+
+    public boolean isExistingClient() {
+        return GuarantorType.CUSTOMER.getValue().equals(this.guarantorType.getId().intValue());
+    }
+
+    public boolean isStaffMember() {
+        return GuarantorType.STAFF.getValue().equals(this.guarantorType.getId().intValue());
+    }
+
+    public Long getEntityId() {
+        return this.entityId;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorFundingData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorFundingData.java
new file mode 100755
index 0000000..8b878c8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorFundingData.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+
+public class GuarantorFundingData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final EnumOptionData status;
+    @SuppressWarnings("unused")
+    private final PortfolioAccountData savingsAccount;
+    @SuppressWarnings("unused")
+    private final BigDecimal amount;
+    @SuppressWarnings("unused")
+    private final BigDecimal amountReleased;
+    @SuppressWarnings("unused")
+    private final BigDecimal amountRemaining;
+    @SuppressWarnings("unused")
+    private final BigDecimal amountTransfered;
+    @SuppressWarnings("unused")
+    private final Collection<GuarantorTransactionData> guarantorTransactions;
+
+    private GuarantorFundingData(final Long id, final EnumOptionData status, final PortfolioAccountData savingsAccount, final BigDecimal amount,
+            final BigDecimal amountReleased, final BigDecimal amountRemaining, final BigDecimal amountTransfered,
+            final Collection<GuarantorTransactionData> guarantorTransactions) {
+        this.id = id;
+        this.status = status;
+        this.savingsAccount = savingsAccount;
+        this.amount = amount;
+        this.amountReleased = amountReleased;
+        this.amountRemaining = amountRemaining;
+        this.amountTransfered = amountTransfered;
+        this.guarantorTransactions = guarantorTransactions;
+    }
+
+    public static GuarantorFundingData instance(final Long id, final EnumOptionData status, final PortfolioAccountData savingsAccount, final BigDecimal amount,
+            final BigDecimal amountReleased, final BigDecimal amountRemaining, final BigDecimal amountTransfered,
+            final Collection<GuarantorTransactionData> guarantorTransactions) {
+        return new GuarantorFundingData(id, status, savingsAccount, amount, amountReleased, amountRemaining, amountTransfered,
+                guarantorTransactions);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorTransactionData.java
new file mode 100755
index 0000000..6187096
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorTransactionData.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.data;
+
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.savings.data.DepositAccountOnHoldTransactionData;
+
+public class GuarantorTransactionData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final DepositAccountOnHoldTransactionData onHoldTransactionData;
+    @SuppressWarnings("unused")
+    private final LoanTransactionData loanTransactionData;
+    @SuppressWarnings("unused")
+    private final boolean reversed;
+
+    private GuarantorTransactionData(final Long id, final DepositAccountOnHoldTransactionData onHoldTransactionData,
+            final LoanTransactionData loanTransactionData, final boolean reversed) {
+
+        this.id = id;
+        this.onHoldTransactionData = onHoldTransactionData;
+        this.loanTransactionData = loanTransactionData;
+        this.reversed = reversed;
+    }
+
+    public static GuarantorTransactionData instance(final Long id, final DepositAccountOnHoldTransactionData onHoldTransactionData,
+            final LoanTransactionData loanTransactionData, final boolean reversed) {
+        return new GuarantorTransactionData(id, onHoldTransactionData, loanTransactionData, reversed);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/Guarantor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/Guarantor.java
new file mode 100755
index 0000000..2aa2405
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/Guarantor.java
@@ -0,0 +1,369 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.domain;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants.GUARANTOR_JSON_INPUT_PARAMS;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_guarantor")
+public class Guarantor extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+
+    @ManyToOne
+    @JoinColumn(name = "client_reln_cv_id", nullable = false)
+    private CodeValue clientRelationshipType;
+
+    @Column(name = "type_enum", nullable = false)
+    private Integer gurantorType;
+
+    @Column(name = "entity_id")
+    private Long entityId;
+
+    @Column(name = "firstname", length = 50)
+    private String firstname;
+
+    @Column(name = "lastname", length = 50)
+    private String lastname;
+
+    @Column(name = "dob")
+    @Temporal(TemporalType.DATE)
+    private Date dateOfBirth;
+
+    @Column(name = "address_line_1", length = 500)
+    private String addressLine1;
+
+    @Column(name = "address_line_2", length = 500)
+    private String addressLine2;
+
+    @Column(name = "city", length = 50)
+    private String city;
+
+    @Column(name = "state", length = 50)
+    private String state;
+
+    @Column(name = "country", length = 50)
+    private String country;
+
+    @Column(name = "zip", length = 20)
+    private String zip;
+
+    @Column(name = "house_phone_number", length = 20)
+    private String housePhoneNumber;
+
+    @Column(name = "mobile_number", length = 20)
+    private String mobilePhoneNumber;
+
+    @Column(name = "comment", length = 500)
+    private String comment;
+
+    @Column(name = "is_active", nullable = false)
+    private boolean active;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "guarantor", orphanRemoval = true)
+    private final List<GuarantorFundingDetails> guarantorFundDetails = new ArrayList<>();
+
+    protected Guarantor() {
+
+    }
+
+    private Guarantor(final Loan loan, final CodeValue clientRelationshipType, final Integer gurantorType, final Long entityId,
+            final String firstname, final String lastname, final Date dateOfBirth, final String addressLine1, final String addressLine2,
+            final String city, final String state, final String country, final String zip, final String housePhoneNumber,
+            final String mobilePhoneNumber, final String comment, final boolean active,
+            final List<GuarantorFundingDetails> guarantorFundDetails) {
+        this.loan = loan;
+        this.clientRelationshipType = clientRelationshipType;
+        this.gurantorType = gurantorType;
+        this.entityId = entityId;
+        this.firstname = StringUtils.defaultIfEmpty(firstname, null);
+        this.lastname = StringUtils.defaultIfEmpty(lastname, null);
+        this.dateOfBirth = dateOfBirth;
+        this.addressLine1 = StringUtils.defaultIfEmpty(addressLine1, null);
+        this.addressLine2 = StringUtils.defaultIfEmpty(addressLine2, null);
+        this.city = StringUtils.defaultIfEmpty(city, null);
+        this.state = StringUtils.defaultIfEmpty(state, null);
+        this.country = StringUtils.defaultIfEmpty(country, null);
+        this.zip = StringUtils.defaultIfEmpty(zip, null);
+        this.housePhoneNumber = StringUtils.defaultIfEmpty(housePhoneNumber, null);
+        this.mobilePhoneNumber = StringUtils.defaultIfEmpty(mobilePhoneNumber, null);
+        this.comment = StringUtils.defaultIfEmpty(comment, null);
+        this.active = active;
+        this.guarantorFundDetails.addAll(guarantorFundDetails);
+    }
+
+    public static Guarantor fromJson(final Loan loan, final CodeValue clientRelationshipType, final JsonCommand command,
+            final List<GuarantorFundingDetails> fundingDetails) {
+        final Integer gurantorType = command.integerValueSansLocaleOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.GUARANTOR_TYPE_ID
+                .getValue());
+        final Long entityId = command.longValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.ENTITY_ID.getValue());
+        final boolean active = true;
+        if (GuarantorType.EXTERNAL.getValue().equals(gurantorType)) {
+            final String firstname = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.FIRSTNAME.getValue());
+            final String lastname = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.LASTNAME.getValue());
+            final Date dateOfBirth = command.DateValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.DATE_OF_BIRTH.getValue());
+            final String addressLine1 = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_1.getValue());
+            final String addressLine2 = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_2.getValue());
+            final String city = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.CITY.getValue());
+            final String state = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.STATE.getValue());
+            final String country = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.COUNTRY.getValue());
+            final String zip = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.ZIP.getValue());
+            final String housePhoneNumber = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.PHONE_NUMBER.getValue());
+            final String mobilePhoneNumber = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.MOBILE_NUMBER.getValue());
+            final String comment = command.stringValueOfParameterNamed(GUARANTOR_JSON_INPUT_PARAMS.COMMENT.getValue());
+
+            return new Guarantor(loan, clientRelationshipType, gurantorType, entityId, firstname, lastname, dateOfBirth, addressLine1,
+                    addressLine2, city, state, country, zip, housePhoneNumber, mobilePhoneNumber, comment, active, fundingDetails);
+        }
+
+        return new Guarantor(loan, clientRelationshipType, gurantorType, entityId, null, null, null, null, null, null, null, null, null,
+                null, null, null, active, fundingDetails);
+
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.CLIENT_RELATIONSHIP_TYPE_ID.getValue(), 0, true);
+
+        if (isExternalGuarantor()) {
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.FIRSTNAME.getValue(), this.firstname);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.LASTNAME.getValue(), this.lastname);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.DATE_OF_BIRTH.getValue(), this.dateOfBirth);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_1.getValue(), this.addressLine1);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_2.getValue(), this.addressLine2);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.CITY.getValue(), this.city);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.STATE.getValue(), this.state);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.COUNTRY.getValue(), this.country);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.ZIP.getValue(), this.zip);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.PHONE_NUMBER.getValue(), this.housePhoneNumber);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.MOBILE_NUMBER.getValue(), this.mobilePhoneNumber);
+            handlePropertyUpdate(command, actualChanges, GUARANTOR_JSON_INPUT_PARAMS.COMMENT.getValue(), this.comment);
+            updateExistingEntityToNull();
+        }
+
+        return actualChanges;
+    }
+
+    public boolean isExistingCustomer() {
+        return GuarantorType.CUSTOMER.getValue().equals(this.gurantorType);
+    }
+
+    public boolean isExistingEmployee() {
+        return GuarantorType.STAFF.getValue().equals(this.gurantorType);
+    }
+
+    public boolean isExternalGuarantor() {
+        return GuarantorType.EXTERNAL.getValue().equals(this.gurantorType);
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            Integer propertyToBeUpdated, final boolean sansLocale) {
+        if (command.isChangeInIntegerParameterNamed(paramName, propertyToBeUpdated)) {
+            Integer newValue = null;
+            if (sansLocale) {
+                newValue = command.integerValueSansLocaleOfParameterNamed(paramName);
+            } else {
+                newValue = command.integerValueOfParameterNamed(paramName);
+            }
+            actualChanges.put(paramName, newValue);
+            propertyToBeUpdated = newValue;
+
+            // now update actual property
+            if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.GUARANTOR_TYPE_ID.getValue())) {
+                this.gurantorType = newValue;
+            }
+        }
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            String propertyToBeUpdated) {
+        if (command.isChangeInStringParameterNamed(paramName, propertyToBeUpdated)) {
+            final String newValue = command.stringValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            propertyToBeUpdated = newValue;
+
+            // now update actual property
+            if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.FIRSTNAME.getValue())) {
+                this.firstname = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.LASTNAME.getValue())) {
+                this.lastname = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_1.getValue())) {
+                this.addressLine1 = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_2.getValue())) {
+                this.addressLine2 = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.CITY.getValue())) {
+                this.city = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.STATE.getValue())) {
+                this.state = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.COUNTRY.getValue())) {
+                this.country = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.ZIP.getValue())) {
+                this.zip = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.PHONE_NUMBER.getValue())) {
+                this.housePhoneNumber = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.MOBILE_NUMBER.getValue())) {
+                this.mobilePhoneNumber = newValue;
+            } else if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.COMMENT.getValue())) {
+                this.comment = newValue;
+            }
+        }
+    }
+
+    private void handlePropertyUpdate(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
+            Date propertyToBeUpdated) {
+        if (command.isChangeInDateParameterNamed(paramName, propertyToBeUpdated)) {
+            final Date newValue = command.DateValueOfParameterNamed(paramName);
+            actualChanges.put(paramName, newValue);
+            propertyToBeUpdated = newValue;
+
+            // now update actual property
+            if (paramName.equals(GUARANTOR_JSON_INPUT_PARAMS.DATE_OF_BIRTH.getValue())) {
+                this.dateOfBirth = newValue;
+            }
+        }
+    }
+
+    public Long getEntityId() {
+        return this.entityId;
+    }
+
+    public Long getLoanId() {
+        return this.loan.getId();
+    }
+
+    public Long getClientId() {
+        return this.loan.getClientId();
+    }
+
+    public Long getOfficeId() {
+        return this.loan.getOfficeId();
+    }
+
+    public CodeValue getClientRelationshipType() {
+        return this.clientRelationshipType;
+    }
+
+    public void updateClientRelationshipType(final CodeValue clientRelationshipType) {
+        this.clientRelationshipType = clientRelationshipType;
+    }
+
+    private void updateExistingEntityToNull() {
+        this.entityId = null;
+    }
+
+    public Integer getGurantorType() {
+        return this.gurantorType;
+    }
+
+    public boolean isActive() {
+        return this.active;
+    }
+
+    public void updateStatus(final boolean status) {
+        this.active = status;
+    }
+
+    public void addFundingDetails(final List<GuarantorFundingDetails> fundingDetails) {
+        this.guarantorFundDetails.addAll(fundingDetails);
+    }
+
+    public void updateStatus(final GuarantorFundingDetails guarantorFundingDetails, final GuarantorFundStatusType fundStatusType) {
+        guarantorFundingDetails.updateStatus(fundStatusType);
+        updateStatus();
+    }
+
+    public GuarantorFundingDetails getGuarantorFundingDetail(final Long fundingDetailId) {
+        GuarantorFundingDetails guarantorFundingDetails = null;
+        for (GuarantorFundingDetails fundingDetails : this.guarantorFundDetails) {
+            if (fundingDetails.getId().equals(fundingDetailId)) {
+                guarantorFundingDetails = fundingDetails;
+                break;
+            }
+        }
+        return guarantorFundingDetails;
+    }
+
+    private void updateStatus() {
+        boolean isActive = false;
+        for (GuarantorFundingDetails guarantorFundingDetails : this.guarantorFundDetails) {
+            if (guarantorFundingDetails.getStatus().isActive()) {
+                isActive = true;
+                break;
+            }
+        }
+        this.active = isActive;
+    }
+
+    public Loan getLoan() {
+        return this.loan;
+    }
+
+    public List<GuarantorFundingDetails> getGuarantorFundDetails() {
+        return this.guarantorFundDetails;
+    }
+
+    public boolean hasGuarantor(Long savingsId) {
+        if (savingsId == null) { return false; }
+        boolean hasGuarantee = false;
+        for (GuarantorFundingDetails guarantorFundingDetails : this.guarantorFundDetails) {
+            if (guarantorFundingDetails.getStatus().isActive()
+                    && savingsId.equals(guarantorFundingDetails.getLinkedSavingsAccount().getId())) {
+                hasGuarantee = true;
+                break;
+            }
+        }
+        return hasGuarantee;
+    }
+
+    public boolean isSelfGuarantee() {
+        boolean isSelf = false;
+        if (isExistingCustomer() && getEntityId().equals(getClientId())) {
+            isSelf = true;
+        }
+        return isSelf;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundStatusType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundStatusType.java
new file mode 100755
index 0000000..11c7dab
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundStatusType.java
@@ -0,0 +1,91 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.domain;
+
+/**
+ * Enum representation of {@link Guarantor} status states.
+ */
+public enum GuarantorFundStatusType {
+
+    INVALID(0, "guarantorFundStatusType.invalid"), //
+    ACTIVE(100, "guarantorFundStatusType.active"), //
+    COMPLETED(200, "guarantorFundStatusType.completed"), //
+    WITHDRAWN(300, "guarantorFundStatusType.withdrawn"), //
+    DELETED(400, "guarantorFundStatusType.deleted");
+
+    private final Integer value;
+    private final String code;
+
+    public static GuarantorFundStatusType fromInt(final Integer type) {
+
+        GuarantorFundStatusType enumeration = GuarantorFundStatusType.INVALID;
+        switch (type) {
+            case 100:
+                enumeration = GuarantorFundStatusType.ACTIVE;
+            break;
+            case 200:
+                enumeration = GuarantorFundStatusType.COMPLETED;
+            break;
+            case 300:
+                enumeration = GuarantorFundStatusType.WITHDRAWN;
+            break;
+            case 400:
+                enumeration = GuarantorFundStatusType.DELETED;
+            break;
+        }
+        return enumeration;
+    }
+
+    private GuarantorFundStatusType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final GuarantorFundStatusType state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isActive() {
+        return this.value.equals(GuarantorFundStatusType.ACTIVE.getValue());
+    }
+
+    public boolean isClosed() {
+        return isWithdrawn() || isCompleted() || isDeleted();
+    }
+
+    public boolean isWithdrawn() {
+        return this.value.equals(GuarantorFundStatusType.WITHDRAWN.getValue());
+    }
+
+    public boolean isCompleted() {
+        return this.value.equals(GuarantorFundStatusType.COMPLETED.getValue());
+    }
+
+    public boolean isDeleted() {
+        return this.value.equals(GuarantorFundStatusType.DELETED.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingDetails.java
new file mode 100755
index 0000000..fe7ce85
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingDetails.java
@@ -0,0 +1,145 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.account.domain.AccountAssociations;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_guarantor_funding_details")
+public class GuarantorFundingDetails extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "guarantor_id", nullable = false)
+    private Guarantor guarantor;
+
+    @ManyToOne
+    @JoinColumn(name = "account_associations_id", nullable = false)
+    private AccountAssociations accountAssociations;
+
+    @Column(name = "status_enum", nullable = false)
+    private Integer status;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "amount_released_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountReleased;
+
+    @Column(name = "amount_remaining_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountRemaining;
+
+    @Column(name = "amount_transfered_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountTransfered;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "guarantorFundingDetails", orphanRemoval = true)
+    private final List<GuarantorFundingTransaction> guarantorFundingTransactions = new ArrayList<>();
+
+    protected GuarantorFundingDetails() {}
+
+    public GuarantorFundingDetails(final AccountAssociations accountAssociations, final Integer status, final BigDecimal amount) {
+        this.accountAssociations = accountAssociations;
+        this.status = status;
+        this.amount = amount;
+        this.amountRemaining = amount;
+    }
+
+    public void updateGuarantor(final Guarantor guarantor) {
+        this.guarantor = guarantor;
+    }
+
+    public void updateStatus(final GuarantorFundStatusType guarantorFundStatusType) {
+        this.status = guarantorFundStatusType.getValue();
+    }
+
+    public GuarantorFundStatusType getStatus() {
+        return GuarantorFundStatusType.fromInt(this.status);
+    }
+
+    public SavingsAccount getLinkedSavingsAccount() {
+        return accountAssociations.linkedSavingsAccount();
+    }
+
+    public Loan getLoanAccount() {
+        return this.guarantor.getLoan();
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public BigDecimal getAmountReleased() {
+        return this.amountReleased == null ? BigDecimal.ZERO : this.amountReleased;
+    }
+
+    public BigDecimal getAmountRemaining() {
+        return this.amountRemaining == null ? BigDecimal.ZERO : this.amountRemaining;
+    }
+
+    public BigDecimal getAmountTransfered() {
+        return this.amountTransfered == null ? BigDecimal.ZERO : this.amountTransfered;
+    }
+
+    public void releaseFunds(final BigDecimal amount) {
+        this.amountReleased = getAmountReleased().add(amount);
+        this.amountRemaining = getAmountRemaining().subtract(amount);
+        if (this.amountRemaining.compareTo(BigDecimal.ZERO) == 0) {
+            this.updateStatus(GuarantorFundStatusType.COMPLETED);
+        }
+    }
+
+    public void undoReleaseFunds(final BigDecimal amount) {
+        this.amountReleased = getAmountReleased().subtract(amount);
+        this.amountRemaining = getAmountRemaining().add(amount);
+        if (getStatus().isCompleted() && this.amountRemaining.compareTo(BigDecimal.ZERO) == 1) {
+            this.updateStatus(GuarantorFundStatusType.ACTIVE);
+        }
+    }
+
+    public void withdrawFunds(final BigDecimal amount) {
+        this.amountTransfered = amount;
+    }
+
+    public void addGuarantorFundingTransactions(final GuarantorFundingTransaction guarantorFundingTransaction) {
+        this.guarantorFundingTransactions.add(guarantorFundingTransaction);
+    }
+
+    public void undoAllTransactions() {
+        for (GuarantorFundingTransaction fundingTransaction : this.guarantorFundingTransactions) {
+            fundingTransaction.reverseTransaction();
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingRepository.java
new file mode 100755
index 0000000..f16c0a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingRepository.java
@@ -0,0 +1,25 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface GuarantorFundingRepository extends JpaRepository<GuarantorFundingDetails, Long>,
+        JpaSpecificationExecutor<GuarantorFundingDetails> {}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingTransaction.java
new file mode 100755
index 0000000..aa12fb8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingTransaction.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.domain;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransaction;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_guarantor_transaction")
+public class GuarantorFundingTransaction extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "guarantor_fund_detail_id", nullable = false)
+    private GuarantorFundingDetails guarantorFundingDetails;
+
+    @ManyToOne
+    @JoinColumn(name = "loan_transaction_id", nullable = true)
+    private LoanTransaction loanTransaction;
+
+    @OneToOne
+    @JoinColumn(name = "deposit_on_hold_transaction_id", nullable = false)
+    private DepositAccountOnHoldTransaction depositAccountOnHoldTransaction;
+
+    @Column(name = "is_reversed", nullable = false)
+    private boolean reversed;
+
+    protected GuarantorFundingTransaction() {}
+
+    public GuarantorFundingTransaction(final GuarantorFundingDetails guarantorFundingDetails, final LoanTransaction loanTransaction,
+            final DepositAccountOnHoldTransaction depositAccountOnHoldTransaction) {
+        this.depositAccountOnHoldTransaction = depositAccountOnHoldTransaction;
+        this.guarantorFundingDetails = guarantorFundingDetails;
+        this.loanTransaction = loanTransaction;
+        this.reversed = false;
+    }
+
+    public void reverseTransaction() {
+        if (!this.reversed) {
+            this.reversed = true;
+            BigDecimal amountForReverse = this.depositAccountOnHoldTransaction.getAmount();
+            this.depositAccountOnHoldTransaction.reverseTransaction();
+            if (this.depositAccountOnHoldTransaction.getTransactionType().isRelease()) {
+                this.guarantorFundingDetails.undoReleaseFunds(amountForReverse);
+            }
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingTransactionRepository.java
new file mode 100755
index 0000000..adb7523
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorFundingTransactionRepository.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.portfolio.loanaccount.guarantor.domain;
+
+import java.util.List;
+
+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 GuarantorFundingTransactionRepository extends JpaRepository<GuarantorFundingTransaction, Long>,
+        JpaSpecificationExecutor<GuarantorFundingTransaction> {
+
+    @Query("from GuarantorFundingTransaction ft where ft.loanTransaction.id in (:loanTransactions)")
+    List<GuarantorFundingTransaction> fetchGuarantorFundingTransactions(@Param("loanTransactions") List<Long> loanTransactions);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorRepository.java
new file mode 100755
index 0000000..a99be43
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorRepository.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.domain;
+
+import java.util.List;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface GuarantorRepository extends JpaRepository<Guarantor, Long>, JpaSpecificationExecutor<Guarantor> {
+
+    Guarantor findByLoanAndId(Loan loan, Long id);
+
+    List<Guarantor> findByLoan(Loan loan);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorType.java
new file mode 100755
index 0000000..2cb422b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorType.java
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum GuarantorType {
+    CUSTOMER(1, "guarantor.existing.customer"), STAFF(2, "guarantor.staff"), EXTERNAL(3, "guarantor.external");
+
+    private final Integer value;
+    private final String code;
+
+    private GuarantorType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    private static final Map<Integer, GuarantorType> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final GuarantorType type : GuarantorType.values()) {
+            if (i == 0) {
+                minValue = type.value;
+            }
+            intToEnumMap.put(type.value, type);
+            if (minValue >= type.value) {
+                minValue = type.value;
+            }
+            if (maxValue < type.value) {
+                maxValue = type.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static GuarantorType fromInt(final int i) {
+        final GuarantorType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public boolean isCustomer() {
+        return this.value.equals(GuarantorType.CUSTOMER.getValue());
+    }
+
+    public boolean isStaff() {
+        return this.value.equals(GuarantorType.STAFF.getValue());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/exception/DuplicateGuarantorException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/exception/DuplicateGuarantorException.java
new file mode 100644
index 0000000..c14901f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/exception/DuplicateGuarantorException.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.loanaccount.guarantor.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class DuplicateGuarantorException extends AbstractPlatformDomainRuleException {
+
+    public DuplicateGuarantorException(final String action, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
+        // TODO Auto-generated constructor stub
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/exception/GuarantorNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/exception/GuarantorNotFoundException.java
new file mode 100644
index 0000000..3c03f13
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/exception/GuarantorNotFoundException.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when guarantor resources are not found.
+ */
+public class GuarantorNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public GuarantorNotFoundException(final Long id) {
+        super("error.msg.loan.guarantor.not.found", "Guarantor with identifier " + id + " does not exist", id);
+    }
+
+    public GuarantorNotFoundException(final Long loanId, final Long guarantorId) {
+        super("error.msg.loan.guarantor.not.found", "Guarantor with identifier " + guarantorId
+                + " does not exist for loan with Identifier " + loanId, loanId, guarantorId);
+    }
+
+    public GuarantorNotFoundException(final Long loanId, final Long guarantorId, final Long guarantorFundingId) {
+        super("error.msg.loan.guarantor.not.found", "Guarantor with identifier " + guarantorId + "and with funding detail "
+                + guarantorFundingId + " does not exist for loan with Identifier " + loanId, loanId, guarantorId, guarantorFundingId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/exception/InvalidGuarantorException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/exception/InvalidGuarantorException.java
new file mode 100644
index 0000000..eea46df
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/exception/InvalidGuarantorException.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when a Client is set as a
+ * guarantor for his/her own loans
+ */
+public class InvalidGuarantorException extends AbstractPlatformDomainRuleException {
+
+    public InvalidGuarantorException(final Long clientId, final Long loanId) {
+        super("error.msg.invalid.guarantor", "Tried to set Client with id " + clientId
+                + " as a guarantor to his/her own loan with loan identifier =" + loanId, clientId, loanId);
+    }
+    
+    public InvalidGuarantorException(final Long clientId, final Long loanId,final String errorcode) {
+        super("error.msg."+errorcode, "Tried to set Client with id " + clientId
+                + " as a guarantor to his/her own loan with loan identifier =" + loanId, clientId, loanId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/handler/CreateGuarantorCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/handler/CreateGuarantorCommandHandler.java
new file mode 100755
index 0000000..8467f36
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/handler/CreateGuarantorCommandHandler.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.portfolio.loanaccount.guarantor.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GUARANTOR", action = "CREATE")
+public class CreateGuarantorCommandHandler implements NewCommandSourceHandler {
+
+    private final GuarantorWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateGuarantorCommandHandler(final GuarantorWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createGuarantor(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/handler/DeleteGuarantorCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/handler/DeleteGuarantorCommandHandler.java
new file mode 100755
index 0000000..661dfd7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/handler/DeleteGuarantorCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GUARANTOR", action = "DELETE")
+public class DeleteGuarantorCommandHandler implements NewCommandSourceHandler {
+
+    private final GuarantorWritePlatformService guarantorWritePlatformService;
+
+    @Autowired
+    public DeleteGuarantorCommandHandler(final GuarantorWritePlatformService guarantorWritePlatformService) {
+        this.guarantorWritePlatformService = guarantorWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.guarantorWritePlatformService.removeGuarantor(command.getLoanId(), command.entityId(), command.subentityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/handler/UpdateGuarantorCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/handler/UpdateGuarantorCommandHandler.java
new file mode 100755
index 0000000..d673f88
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/handler/UpdateGuarantorCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GUARANTOR", action = "UPDATE")
+public class UpdateGuarantorCommandHandler implements NewCommandSourceHandler {
+
+    private final GuarantorWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateGuarantorCommandHandler(final GuarantorWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.updateGuarantor(command.getLoanId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/serialization/GuarantorCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/serialization/GuarantorCommandFromApiJsonDeserializer.java
new file mode 100755
index 0000000..0abef49
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/serialization/GuarantorCommandFromApiJsonDeserializer.java
@@ -0,0 +1,103 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants.GUARANTOR_JSON_INPUT_PARAMS;
+import org.apache.fineract.portfolio.loanaccount.guarantor.command.GuarantorCommand;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link GuarantorCommand}'s.
+ */
+@Component
+public final class GuarantorCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<GuarantorCommand> {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public GuarantorCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public GuarantorCommand commandFromApiJson(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        final Set<String> supportedParameters = GUARANTOR_JSON_INPUT_PARAMS.getAllValues();
+        supportedParameters.add("locale");
+        supportedParameters.add("dateFormat");
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(element.getAsJsonObject());
+
+        return extractGuarantorCommand(element, locale, dateFormat);
+    }
+
+    private GuarantorCommand extractGuarantorCommand(final JsonElement element, final Locale locale, final String dateFormat) {
+        final Long clientRelationshipTypeId = this.fromApiJsonHelper.extractLongNamed(
+                GUARANTOR_JSON_INPUT_PARAMS.CLIENT_RELATIONSHIP_TYPE_ID.getValue(), element);
+        final Integer guarantorTypeId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                GUARANTOR_JSON_INPUT_PARAMS.GUARANTOR_TYPE_ID.getValue(), element);
+        final Long entityId = this.fromApiJsonHelper.extractLongNamed(GUARANTOR_JSON_INPUT_PARAMS.ENTITY_ID.getValue(), element);
+        final String firstname = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.FIRSTNAME.getValue(), element);
+        final String lastname = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.LASTNAME.getValue(), element);
+        final String addressLine1 = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_1.getValue(),
+                element);
+        final String addressLine2 = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.ADDRESS_LINE_2.getValue(),
+                element);
+        final String city = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.CITY.getValue(), element);
+        final String state = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.STATE.getValue(), element);
+        final String zip = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.ZIP.getValue(), element);
+        final String country = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.COUNTRY.getValue(), element);
+        final String mobileNumber = this.fromApiJsonHelper
+                .extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.MOBILE_NUMBER.getValue(), element);
+        final String housePhoneNumber = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.PHONE_NUMBER.getValue(),
+                element);
+        final String comment = this.fromApiJsonHelper.extractStringNamed(GUARANTOR_JSON_INPUT_PARAMS.COMMENT.getValue(), element);
+        final LocalDate dob = this.fromApiJsonHelper.extractLocalDateNamed(GUARANTOR_JSON_INPUT_PARAMS.DATE_OF_BIRTH.getValue(), element,
+                dateFormat, locale);
+        final Long savingsId = this.fromApiJsonHelper.extractLongNamed(GUARANTOR_JSON_INPUT_PARAMS.SAVINGS_ID.getValue(), element);
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(GUARANTOR_JSON_INPUT_PARAMS.AMOUNT.getValue(), element,
+                locale);
+
+        return new GuarantorCommand(clientRelationshipTypeId, guarantorTypeId, entityId, firstname, lastname, addressLine1, addressLine2,
+                city, state, zip, country, mobileNumber, housePhoneNumber, comment, dob, savingsId, amount);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainService.java
new file mode 100755
index 0000000..82b3cd6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainService.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.portfolio.loanaccount.guarantor.service;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingDetails;
+import org.joda.time.LocalDate;
+
+public interface GuarantorDomainService {
+
+    void releaseGuarantor(GuarantorFundingDetails guarantorFundingDetails, LocalDate transactionDate);
+
+    void validateGuarantorBusinessRules(Loan loan);
+
+    void assignGuarantor(GuarantorFundingDetails guarantorFundingDetails, LocalDate transactionDate);
+
+    void transaferFundsFromGuarantor(Loan loan);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java
new file mode 100755
index 0000000..ff45966
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java
@@ -0,0 +1,652 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
+import org.apache.fineract.portfolio.common.service.BusinessEventListner;
+import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.Guarantor;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingDetails;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingRepository;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransaction;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorRepository;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductGuaranteeDetails;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransaction;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransactionRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GuarantorDomainServiceImpl implements GuarantorDomainService {
+
+    private final GuarantorRepository guarantorRepository;
+    private final GuarantorFundingRepository guarantorFundingRepository;
+    private final GuarantorFundingTransactionRepository guarantorFundingTransactionRepository;
+    private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
+    private final BusinessEventNotifierService businessEventNotifierService;
+    private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
+    private final Map<Long, Long> releaseLoanIds = new HashMap<>(2);
+    
+
+    @Autowired
+    public GuarantorDomainServiceImpl(final GuarantorRepository guarantorRepository,
+            final GuarantorFundingRepository guarantorFundingRepository,
+            final GuarantorFundingTransactionRepository guarantorFundingTransactionRepository,
+            final AccountTransfersWritePlatformService accountTransfersWritePlatformService,
+            final BusinessEventNotifierService businessEventNotifierService,
+            final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository) {
+        this.guarantorRepository = guarantorRepository;
+        this.guarantorFundingRepository = guarantorFundingRepository;
+        this.guarantorFundingTransactionRepository = guarantorFundingTransactionRepository;
+        this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
+        this.businessEventNotifierService = businessEventNotifierService;
+        this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository;
+    }
+
+    @PostConstruct
+    public void addListners() {
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_APPROVED, new ValidateOnBusinessEvent());
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_APPROVED, new HoldFundsOnBusinessEvent());
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_APPROVAL, new UndoAllFundTransactions());
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL,
+                new ReverseAllFundsOnBusinessEvent());
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_ADJUST_TRANSACTION,
+                new AdjustFundsOnBusinessEvent());
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT,
+                new ReleaseFundsOnBusinessEvent());
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_WRITTEN_OFF, new ReleaseAllFunds());
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF,
+                new ReverseFundsOnBusinessEvent());
+    }
+
+    @Override
+    public void validateGuarantorBusinessRules(Loan loan) {
+        LoanProduct loanProduct = loan.loanProduct();
+        BigDecimal principal = loan.getPrincpal().getAmount();
+        if (loanProduct.isHoldGuaranteeFundsEnabled()) {
+            LoanProductGuaranteeDetails guaranteeData = loanProduct.getLoanProductGuaranteeDetails();
+            final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
+            BigDecimal mandatoryAmount = principal.multiply(guaranteeData.getMandatoryGuarantee()).divide(BigDecimal.valueOf(100));
+            BigDecimal minSelfAmount = principal.multiply(guaranteeData.getMinimumGuaranteeFromOwnFunds()).divide(BigDecimal.valueOf(100));
+            BigDecimal minExtGuarantee = principal.multiply(guaranteeData.getMinimumGuaranteeFromGuarantor())
+                    .divide(BigDecimal.valueOf(100));
+
+            BigDecimal actualAmount = BigDecimal.ZERO;
+            BigDecimal actualSelfAmount = BigDecimal.ZERO;
+            BigDecimal actualExtGuarantee = BigDecimal.ZERO;
+            for (Guarantor guarantor : existGuarantorList) {
+                List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
+                for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
+                    if (guarantorFundingDetails.getStatus().isActive() || guarantorFundingDetails.getStatus().isWithdrawn()
+                            || guarantorFundingDetails.getStatus().isCompleted()) {
+                        if (guarantor.isSelfGuarantee()) {
+                            actualSelfAmount = actualSelfAmount.add(guarantorFundingDetails.getAmount())
+                                    .subtract(guarantorFundingDetails.getAmountTransfered());
+                        } else {
+                            actualExtGuarantee = actualExtGuarantee.add(guarantorFundingDetails.getAmount())
+                                    .subtract(guarantorFundingDetails.getAmountTransfered());
+                        }
+                    }
+                }
+            }
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
+            if (actualSelfAmount.compareTo(minSelfAmount) == -1) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_SELF_GUARANTEE_ERROR,
+                        minSelfAmount);
+            }
+
+            if (actualExtGuarantee.compareTo(minExtGuarantee) == -1) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_EXTERNAL_GUARANTEE_ERROR,
+                        minExtGuarantee);
+            }
+            actualAmount = actualAmount.add(actualExtGuarantee).add(actualSelfAmount);
+            if (actualAmount.compareTo(mandatoryAmount) == -1) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_MANDATORY_GUARANTEE_ERROR,
+                        mandatoryAmount);
+            }
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                    "Validation errors exist.", dataValidationErrors); }
+        }
+
+    }
+
+    /**
+     * Method assigns a guarantor to loan and blocks the funds on guarantor's
+     * account
+     */
+    @Override
+    public void assignGuarantor(final GuarantorFundingDetails guarantorFundingDetails, final LocalDate transactionDate) {
+        if (guarantorFundingDetails.getStatus().isActive()) {
+            SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
+            savingsAccount.holdFunds(guarantorFundingDetails.getAmount());
+            if (savingsAccount.getWithdrawableBalance().compareTo(BigDecimal.ZERO) == -1) {
+                final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR,
+                        savingsAccount.getId());
+                throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                        dataValidationErrors);
+            }
+            DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.hold(savingsAccount,
+                    guarantorFundingDetails.getAmount(), transactionDate);
+            GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, null,
+                    onHoldTransaction);
+            guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
+            this.depositAccountOnHoldTransactionRepository.save(onHoldTransaction);
+        }
+    }
+
+    /**
+     * Method releases(withdraw) a guarantor from loan and unblocks the funds on
+     * guarantor's account
+     */
+    @Override
+    public void releaseGuarantor(final GuarantorFundingDetails guarantorFundingDetails, final LocalDate transactionDate) {
+        BigDecimal amoutForWithdraw = guarantorFundingDetails.getAmountRemaining();
+        if (amoutForWithdraw.compareTo(BigDecimal.ZERO) == 1 && (guarantorFundingDetails.getStatus().isActive())) {
+            SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
+            savingsAccount.releaseFunds(amoutForWithdraw);
+            DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, amoutForWithdraw,
+                    transactionDate);
+            GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, null,
+                    onHoldTransaction);
+            guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
+            guarantorFundingDetails.releaseFunds(amoutForWithdraw);
+            guarantorFundingDetails.withdrawFunds(amoutForWithdraw);
+            guarantorFundingDetails.getLoanAccount().updateGuaranteeAmount(amoutForWithdraw.negate());
+            this.depositAccountOnHoldTransactionRepository.save(onHoldTransaction);
+            this.guarantorFundingRepository.save(guarantorFundingDetails);
+        }
+    }
+
+    /**
+     * Method is to recover funds from guarantor's in case loan is unpaid.
+     * (Transfers guarantee amount from guarantor's account to loan account and
+     * releases guarantor)
+     */
+    @Override
+    public void transaferFundsFromGuarantor(final Loan loan) {
+        if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) != 1) { return; }
+        final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
+        final boolean isRegularTransaction = true;
+        final boolean isExceptionForBalanceCheck = true;
+        LocalDate transactionDate = LocalDate.now();
+        PortfolioAccountType fromAccountType = PortfolioAccountType.SAVINGS;
+        PortfolioAccountType toAccountType = PortfolioAccountType.LOAN;
+        final Long toAccountId = loan.getId();
+        final String description = "Payment from guarantor savings";
+        final Locale locale = null;
+        final DateTimeFormatter fmt = null;
+        final PaymentDetail paymentDetail = null;
+        final Integer fromTransferType = null;
+        final Integer toTransferType = null;
+        final Long chargeId = null;
+        final Integer loanInstallmentNumber = null;
+        final Integer transferType = AccountTransferType.LOAN_REPAYMENT.getValue();
+        final AccountTransferDetails accountTransferDetails = null;
+        final String noteText = null;
+
+        final String txnExternalId = null;
+        final SavingsAccount toSavingsAccount = null;
+
+        Long loanId = loan.getId();
+
+        for (Guarantor guarantor : existGuarantorList) {
+            final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
+            for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
+                if (guarantorFundingDetails.getStatus().isActive()) {
+                    final SavingsAccount fromSavingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
+                    final Long fromAccountId = fromSavingsAccount.getId();
+                    releaseLoanIds.put(loanId, guarantorFundingDetails.getId());
+                    try {
+                        BigDecimal remainingAmount = guarantorFundingDetails.getAmountRemaining();
+                        if (loan.getGuaranteeAmount().compareTo(loan.getPrincpal().getAmount()) == 1) {
+                            remainingAmount = remainingAmount.multiply(loan.getPrincpal().getAmount()).divide(loan.getGuaranteeAmount(),
+                                    MoneyHelper.getRoundingMode());
+                        }
+                        AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType,
+                                toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType,
+                                toTransferType, chargeId, loanInstallmentNumber, transferType, accountTransferDetails, noteText,
+                                txnExternalId, loan, toSavingsAccount, fromSavingsAccount, isRegularTransaction,
+                                isExceptionForBalanceCheck);
+                        transferAmount(accountTransferDTO);
+                    } finally {
+                        releaseLoanIds.remove(loanId);
+                    }
+                }
+            }
+        }
+
+    }
+
+    /**
+     * @param accountTransferDTO
+     */
+    private void transferAmount(final AccountTransferDTO accountTransferDTO) {
+        try {
+            this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+        } catch (final InsufficientAccountBalanceException e) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR,
+                    accountTransferDTO.getFromAccountId(), accountTransferDTO.getToAccountId());
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+
+        }
+    }
+
+    /**
+     * Method reverses all blocked fund(both hold and release) transactions.
+     * example: reverses all transactions on undo approval of loan account.
+     * 
+     */
+    private void reverseAllFundTransaction(final Loan loan) {
+
+        if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) == 1) {
+            final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
+            List<GuarantorFundingDetails> guarantorFundingDetailList = new ArrayList<>();
+            for (Guarantor guarantor : existGuarantorList) {
+                final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
+                for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
+                    guarantorFundingDetails.undoAllTransactions();
+                    guarantorFundingDetailList.add(guarantorFundingDetails);
+                }
+            }
+
+            if (!guarantorFundingDetailList.isEmpty()) {
+                loan.setGuaranteeAmount(null);
+                this.guarantorFundingRepository.save(guarantorFundingDetailList);
+            }
+        }
+    }
+
+    /**
+     * Method holds all guarantor's guarantee amount for a loan account.
+     * example: hold funds on approval of loan account.
+     * 
+     */
+    private void holdGuarantorFunds(final Loan loan) {
+        if (loan.loanProduct().isHoldGuaranteeFundsEnabled()) {
+            final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
+            List<GuarantorFundingDetails> guarantorFundingDetailList = new ArrayList<>();
+            List<DepositAccountOnHoldTransaction> onHoldTransactions = new ArrayList<>();
+            BigDecimal totalGuarantee = BigDecimal.ZERO;
+            List<Long> insufficientBalanceIds = new ArrayList<>();
+            for (Guarantor guarantor : existGuarantorList) {
+                final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
+                for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
+                    if (guarantorFundingDetails.getStatus().isActive()) {
+                        SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
+                        savingsAccount.holdFunds(guarantorFundingDetails.getAmount());
+                        totalGuarantee = totalGuarantee.add(guarantorFundingDetails.getAmount());
+                        DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.hold(savingsAccount,
+                                guarantorFundingDetails.getAmount(), loan.getApprovedOnDate());
+                        onHoldTransactions.add(onHoldTransaction);
+                        GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails,
+                                null, onHoldTransaction);
+                        guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
+                        guarantorFundingDetailList.add(guarantorFundingDetails);
+                        if (savingsAccount.getWithdrawableBalance().compareTo(BigDecimal.ZERO) == -1) {
+                            insufficientBalanceIds.add(savingsAccount.getId());
+                        }
+                    }
+                }
+            }
+            if (!insufficientBalanceIds.isEmpty()) {
+                final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR,
+                        insufficientBalanceIds);
+                throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                        dataValidationErrors);
+
+            }
+            loan.setGuaranteeAmount(totalGuarantee);
+            if (!guarantorFundingDetailList.isEmpty()) {
+                this.depositAccountOnHoldTransactionRepository.save(onHoldTransactions);
+                this.guarantorFundingRepository.save(guarantorFundingDetailList);
+            }
+        }
+    }
+
+    /**
+     * Method releases all guarantor's guarantee amount(first external guarantee
+     * and then self guarantee) for a loan account in the portion of guarantee
+     * percentage on a paid principal. example: releases funds on repayments of
+     * loan account.
+     * 
+     */
+    private void releaseGuarantorFunds(final LoanTransaction loanTransaction) {
+        final Loan loan = loanTransaction.getLoan();
+        if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) == 1) {
+            final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
+            List<GuarantorFundingDetails> externalGuarantorList = new ArrayList<>();
+            List<GuarantorFundingDetails> selfGuarantorList = new ArrayList<>();
+            BigDecimal selfGuarantee = BigDecimal.ZERO;
+            BigDecimal guarantorGuarantee = BigDecimal.ZERO;
+            for (Guarantor guarantor : existGuarantorList) {
+                final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
+                for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
+                    if (guarantorFundingDetails.getStatus().isActive()) {
+                        if (guarantor.isSelfGuarantee()) {
+                            selfGuarantorList.add(guarantorFundingDetails);
+                            selfGuarantee = selfGuarantee.add(guarantorFundingDetails.getAmountRemaining());
+                        } else if (guarantor.isExistingCustomer()) {
+                            externalGuarantorList.add(guarantorFundingDetails);
+                            guarantorGuarantee = guarantorGuarantee.add(guarantorFundingDetails.getAmountRemaining());
+                        }
+                    }
+                }
+            }
+
+            BigDecimal amountForRelease = loanTransaction.getPrincipalPortion();
+            BigDecimal totalGuaranteeAmount = loan.getGuaranteeAmount();
+            BigDecimal principal = loan.getPrincpal().getAmount();
+            if ((amountForRelease != null) && (totalGuaranteeAmount != null)) {
+                amountForRelease = amountForRelease.multiply(totalGuaranteeAmount).divide(principal, MoneyHelper.getRoundingMode());
+                List<DepositAccountOnHoldTransaction> accountOnHoldTransactions = new ArrayList<>();
+
+                BigDecimal amountLeft = calculateAndRelaseGuarantorFunds(externalGuarantorList, guarantorGuarantee, amountForRelease,
+                        loanTransaction, accountOnHoldTransactions);
+
+                if (amountLeft.compareTo(BigDecimal.ZERO) == 1) {
+                    calculateAndRelaseGuarantorFunds(selfGuarantorList, selfGuarantee, amountLeft, loanTransaction,
+                            accountOnHoldTransactions);
+                    externalGuarantorList.addAll(selfGuarantorList);
+                }
+
+                if (!externalGuarantorList.isEmpty()) {
+                    this.depositAccountOnHoldTransactionRepository.save(accountOnHoldTransactions);
+                    this.guarantorFundingRepository.save(externalGuarantorList);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Method releases all guarantor's guarantee amount. example: releases funds
+     * on write-off of a loan account.
+     * 
+     */
+    private void releaseAllGuarantors(final LoanTransaction loanTransaction) {
+        Loan loan = loanTransaction.getLoan();
+        if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) == 1) {
+            final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
+            List<GuarantorFundingDetails> saveGuarantorFundingDetails = new ArrayList<>();
+            List<DepositAccountOnHoldTransaction> onHoldTransactions = new ArrayList<>();
+            for (Guarantor guarantor : existGuarantorList) {
+                final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
+                for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
+                    BigDecimal amoutForRelease = guarantorFundingDetails.getAmountRemaining();
+                    if (amoutForRelease.compareTo(BigDecimal.ZERO) == 1 && (guarantorFundingDetails.getStatus().isActive())) {
+                        SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
+                        savingsAccount.releaseFunds(amoutForRelease);
+                        DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount,
+                                amoutForRelease, loanTransaction.getTransactionDate());
+                        onHoldTransactions.add(onHoldTransaction);
+                        GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails,
+                                loanTransaction, onHoldTransaction);
+                        guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
+                        guarantorFundingDetails.releaseFunds(amoutForRelease);
+                        saveGuarantorFundingDetails.add(guarantorFundingDetails);
+
+                    }
+                }
+
+            }
+
+            if (!saveGuarantorFundingDetails.isEmpty()) {
+                this.depositAccountOnHoldTransactionRepository.save(onHoldTransactions);
+                this.guarantorFundingRepository.save(saveGuarantorFundingDetails);
+            }
+        }
+    }
+
+    /**
+     * Method releases guarantor's guarantee amount on transferring guarantee
+     * amount to loan account. example: on recovery of guarantee funds from
+     * guarantor's.
+     */
+    private void completeGuarantorFund(final LoanTransaction loanTransaction) {
+        Loan loan = loanTransaction.getLoan();
+        GuarantorFundingDetails guarantorFundingDetails = this.guarantorFundingRepository.findOne(releaseLoanIds.get(loan.getId()));
+        if (guarantorFundingDetails != null) {
+            BigDecimal amountForRelease = loanTransaction.getAmount(loan.getCurrency()).getAmount();
+            BigDecimal guarantorGuarantee = amountForRelease;
+            List<GuarantorFundingDetails> guarantorList = Arrays.asList(guarantorFundingDetails);
+            final List<DepositAccountOnHoldTransaction> accountOnHoldTransactions = new ArrayList<>();
+            calculateAndRelaseGuarantorFunds(guarantorList, guarantorGuarantee, amountForRelease, loanTransaction,
+                    accountOnHoldTransactions);
+            this.depositAccountOnHoldTransactionRepository.save(accountOnHoldTransactions);
+            this.guarantorFundingRepository.save(guarantorFundingDetails);
+        }
+    }
+
+    private BigDecimal calculateAndRelaseGuarantorFunds(List<GuarantorFundingDetails> guarantorList, BigDecimal totalGuaranteeAmount,
+            BigDecimal amountForRelease, LoanTransaction loanTransaction,
+            final List<DepositAccountOnHoldTransaction> accountOnHoldTransactions) {
+        BigDecimal amountLeft = amountForRelease;
+        for (GuarantorFundingDetails fundingDetails : guarantorList) {
+            BigDecimal guarantorAmount = amountForRelease.multiply(fundingDetails.getAmountRemaining()).divide(totalGuaranteeAmount,
+                    MoneyHelper.getRoundingMode());
+            if (fundingDetails.getAmountRemaining().compareTo(guarantorAmount) < 1) {
+                guarantorAmount = fundingDetails.getAmountRemaining();
+            }
+            fundingDetails.releaseFunds(guarantorAmount);
+            SavingsAccount savingsAccount = fundingDetails.getLinkedSavingsAccount();
+            savingsAccount.releaseFunds(guarantorAmount);
+            DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, guarantorAmount,
+                    loanTransaction.getTransactionDate());
+            accountOnHoldTransactions.add(onHoldTransaction);
+            GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(fundingDetails, loanTransaction,
+                    onHoldTransaction);
+            fundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
+            amountLeft = amountLeft.subtract(guarantorAmount);
+        }
+        return amountLeft;
+    }
+
+    /**
+     * Method reverses the fund release transactions in case of loan transaction
+     * reversed
+     */
+    private void reverseTransaction(final List<Long> loanTransactionIds) {
+
+        List<GuarantorFundingTransaction> fundingTransactions = this.guarantorFundingTransactionRepository
+                .fetchGuarantorFundingTransactions(loanTransactionIds);
+        for (GuarantorFundingTransaction fundingTransaction : fundingTransactions) {
+            fundingTransaction.reverseTransaction();
+        }
+        if (!fundingTransactions.isEmpty()) {
+            this.guarantorFundingTransactionRepository.save(fundingTransactions);
+        }
+    }
+
+    private class ValidateOnBusinessEvent implements BusinessEventListner {
+
+        @Override
+        public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
+
+        @Override
+        public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
+            if (entity instanceof Loan) {
+                Loan loan = (Loan) entity;
+                validateGuarantorBusinessRules(loan);
+            }
+        }
+    }
+
+    private class HoldFundsOnBusinessEvent implements BusinessEventListner {
+
+        @Override
+        public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
+
+        @Override
+        public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
+            if (entity instanceof Loan) {
+                Loan loan = (Loan) entity;
+                holdGuarantorFunds(loan);
+            }
+        }
+    }
+
+    private class ReleaseFundsOnBusinessEvent implements BusinessEventListner {
+
+        @Override
+        public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
+
+        @Override
+        public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
+            if (entity instanceof LoanTransaction) {
+                LoanTransaction loanTransaction = (LoanTransaction) entity;
+                if (releaseLoanIds.containsKey(loanTransaction.getLoan().getId())) {
+                    completeGuarantorFund(loanTransaction);
+                } else {
+                    releaseGuarantorFunds(loanTransaction);
+                }
+            }
+        }
+    }
+
+    private class ReverseFundsOnBusinessEvent implements BusinessEventListner {
+
+        @Override
+        public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
+
+        @Override
+        public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
+            if (entity instanceof LoanTransaction) {
+                LoanTransaction loanTransaction = (LoanTransaction) entity;
+                List<Long> reersedTransactions = new ArrayList<>(1);
+                reersedTransactions.add(loanTransaction.getId());
+                reverseTransaction(reersedTransactions);
+            }
+        }
+    }
+
+    private class AdjustFundsOnBusinessEvent implements BusinessEventListner {
+
+        @Override
+        public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
+
+        @Override
+        public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_ADJUSTED_TRANSACTION);
+            if (entity instanceof LoanTransaction) {
+                LoanTransaction loanTransaction = (LoanTransaction) entity;
+                List<Long> reersedTransactions = new ArrayList<>(1);
+                reersedTransactions.add(loanTransaction.getId());
+                reverseTransaction(reersedTransactions);
+            }
+            Object transactionentity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
+            if (transactionentity != null && transactionentity instanceof LoanTransaction) {
+                LoanTransaction loanTransaction = (LoanTransaction) transactionentity;
+                releaseGuarantorFunds(loanTransaction);
+            }
+        }
+    }
+
+    private class ReverseAllFundsOnBusinessEvent implements BusinessEventListner {
+
+        @Override
+        public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
+
+        @Override
+        public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
+            if (entity instanceof Loan) {
+                Loan loan = (Loan) entity;
+                List<Long> reersedTransactions = new ArrayList<>(1);
+                reersedTransactions.addAll(loan.findExistingTransactionIds());
+                reverseTransaction(reersedTransactions);
+            }
+        }
+    }
+
+    private class UndoAllFundTransactions implements BusinessEventListner {
+
+        @Override
+        public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
+
+        @Override
+        public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
+            if (entity instanceof Loan) {
+                Loan loan = (Loan) entity;
+                reverseAllFundTransaction(loan);
+            }
+        }
+    }
+
+    private class ReleaseAllFunds implements BusinessEventListner {
+
+        @Override
+        public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
+
+        @Override
+        public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
+            if (entity instanceof LoanTransaction) {
+                LoanTransaction loanTransaction = (LoanTransaction) entity;
+                releaseAllGuarantors(loanTransaction);
+            }
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorEnumerations.java
new file mode 100755
index 0000000..37361ef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorEnumerations.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundStatusType;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorType;
+
+public class GuarantorEnumerations {
+
+    public static EnumOptionData guarantorType(final int id) {
+        return guarantorType(GuarantorType.fromInt(id));
+    }
+
+    public static EnumOptionData guarantorType(final GuarantorType guarantorType) {
+        final EnumOptionData optionData = new EnumOptionData(guarantorType.getValue().longValue(), guarantorType.getCode(),
+                guarantorType.toString());
+        return optionData;
+    }
+
+    public static List<EnumOptionData> guarantorType(final GuarantorType[] guarantorTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final GuarantorType guarantorType : guarantorTypes) {
+            optionDatas.add(guarantorType(guarantorType));
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData guarantorFundStatusType(final int id) {
+        return guarantorFundStatusType(GuarantorFundStatusType.fromInt(id));
+    }
+
+    public static EnumOptionData guarantorFundStatusType(final GuarantorFundStatusType guarantorFundType) {
+        final EnumOptionData optionData = new EnumOptionData(guarantorFundType.getValue().longValue(), guarantorFundType.getCode(),
+                guarantorFundType.toString());
+        return optionData;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformService.java
new file mode 100644
index 0000000..9652134
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformService.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.service;
+
+import java.util.List;
+
+import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData;
+
+public interface GuarantorReadPlatformService {
+
+    /**
+     * Validates the passed in loanId before retrieving Guarantors for the same
+     * 
+     * @param loanId
+     * @return
+     */
+    List<GuarantorData> retrieveGuarantorsForValidLoan(Long loanId);
+
+    /**
+     * Methods Returns all Guarantors for a Given loan Id (if the loan Id is
+     * valid and Exists)
+     * 
+     * @param loanId
+     * @return
+     */
+    List<GuarantorData> retrieveGuarantorsForLoan(Long loanId);
+
+    GuarantorData retrieveGuarantor(Long loanId, Long guarantorId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformServiceImpl.java
new file mode 100644
index 0000000..38b884e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformServiceImpl.java
@@ -0,0 +1,294 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+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.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationType;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData;
+import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorFundingData;
+import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorTransactionData;
+import org.apache.fineract.portfolio.savings.data.DepositAccountOnHoldTransactionData;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GuarantorReadPlatformServiceImpl implements GuarantorReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final LoanRepository loanRepository;
+
+    @Autowired
+    public GuarantorReadPlatformServiceImpl(final RoutingDataSource dataSource, final ClientReadPlatformService clientReadPlatformService,
+            final StaffReadPlatformService staffReadPlatformService, final LoanRepository loanRepository) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.loanRepository = loanRepository;
+    }
+
+    @Override
+    public List<GuarantorData> retrieveGuarantorsForValidLoan(final Long loanId) {
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+        return retrieveGuarantorsForLoan(loanId);
+    }
+
+    @Override
+    public List<GuarantorData> retrieveGuarantorsForLoan(final Long loanId) {
+        final GuarantorMapper rm = new GuarantorMapper();
+        String sql = "select " + rm.schema();
+        sql += " where loan_id = ?  group by g.id,gfd.id";
+        final List<GuarantorData> guarantorDatas = this.jdbcTemplate.query(sql, rm,
+                new Object[] { AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue(),
+                        loanId });
+
+        final List<GuarantorData> mergedGuarantorDatas = new ArrayList<>();
+
+        for (final GuarantorData guarantorData : guarantorDatas) {
+            mergedGuarantorDatas.add(mergeDetailsForClientOrStaffGuarantor(guarantorData));
+        }
+        return mergedGuarantorDatas;
+    }
+
+    @Override
+    public GuarantorData retrieveGuarantor(final Long loanId, final Long guarantorId) {
+        final GuarantorMapper rm = new GuarantorMapper();
+        String sql = "select " + rm.schema();
+        sql += " where g.loan_id = ? and g.id = ? group by g.id,gfd.id";
+        final GuarantorData guarantorData = this.jdbcTemplate.queryForObject(sql, rm,
+                new Object[] { AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue(),
+                        loanId, guarantorId });
+
+        return mergeDetailsForClientOrStaffGuarantor(guarantorData);
+    }
+
+    private static final class GuarantorMapper implements RowMapper<GuarantorData> {
+
+        private GuarantorTransactionMapper guarantorTransactionMapper = new GuarantorTransactionMapper();
+        private GuarantorFundingMapper guarantorFundingMapper = new GuarantorFundingMapper(guarantorTransactionMapper);
+
+        private final StringBuilder sqlBuilder = new StringBuilder(
+                " g.id as id, g.loan_id as loanId, g.client_reln_cv_id clientRelationshipTypeId, g.entity_id as entityId, g.type_enum guarantorType ,g.firstname as firstname, g.lastname as lastname, g.dob as dateOfBirth, g.address_line_1 as addressLine1, g.address_line_2 as addressLine2, g.city as city, g.state as state, g.country as country, g.zip as zip, g.house_phone_number as housePhoneNumber, g.mobile_number as mobilePhoneNumber, g.comment as comment, ")
+                .append(" g.is_active as guarantorStatus,")//
+                .append(" cv.code_value as typeName, ")//
+                .append("gfd.amount,")//
+                .append(this.guarantorFundingMapper.schema())//
+                .append(",")//
+                .append(this.guarantorTransactionMapper.schema())//
+                .append(" FROM m_guarantor g") //
+                .append(" left JOIN m_code_value cv on g.client_reln_cv_id = cv.id")//
+                .append(" left JOIN m_guarantor_funding_details gfd on g.id = gfd.guarantor_id")//
+                .append(" left JOIN m_portfolio_account_associations aa on gfd.account_associations_id = aa.id and aa.is_active = 1 and aa.association_type_enum = ?")//
+                .append(" left JOIN m_savings_account sa on sa.id = aa.linked_savings_account_id ")//
+                .append(" left join m_guarantor_transaction gt on gt.guarantor_fund_detail_id = gfd.id") //
+                .append(" left join m_deposit_account_on_hold_transaction oht on oht.id = gt.deposit_on_hold_transaction_id");
+
+        public String schema() {
+            return this.sqlBuilder.toString();
+        }
+
+        @Override
+        public GuarantorData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final Long loanId = rs.getLong("loanId");
+            final Long clientRelationshipTypeId = JdbcSupport.getLong(rs, "clientRelationshipTypeId");
+            CodeValueData clientRelationshipType = null;
+
+            if (clientRelationshipTypeId != null) {
+                final String typeName = rs.getString("typeName");
+                clientRelationshipType = CodeValueData.instance(clientRelationshipTypeId, typeName);
+            }
+
+            final Integer guarantorTypeId = rs.getInt("guarantorType");
+            final EnumOptionData guarantorType = GuarantorEnumerations.guarantorType(guarantorTypeId);
+            final Long entityId = rs.getLong("entityId");
+            final String firstname = rs.getString("firstname");
+            final String lastname = rs.getString("lastname");
+            final LocalDate dob = JdbcSupport.getLocalDate(rs, "dateOfBirth");
+            final String addressLine1 = rs.getString("addressLine1");
+            final String addressLine2 = rs.getString("addressLine2");
+            final String city = rs.getString("city");
+            final String state = rs.getString("state");
+            final String zip = rs.getString("zip");
+            final String country = rs.getString("country");
+            final String mobileNumber = rs.getString("mobilePhoneNumber");
+            final String housePhoneNumber = rs.getString("housePhoneNumber");
+            final String comment = rs.getString("comment");
+            final boolean status = rs.getBoolean("guarantorStatus");
+            final Collection<PortfolioAccountData> accountLinkingOptions = null;
+            List<GuarantorFundingData> guarantorFundingDetails = null;
+            GuarantorFundingData guarantorFundingData = this.guarantorFundingMapper.mapRow(rs, rowNum);
+            if (guarantorFundingData != null) {
+                guarantorFundingDetails = new ArrayList<>();
+                guarantorFundingDetails.add(guarantorFundingData);
+                while (rs.next()) {
+                    Long tempId = rs.getLong("id");
+                    if (tempId.equals(id)) {
+                        guarantorFundingData = this.guarantorFundingMapper.mapRow(rs, rowNum);
+                        guarantorFundingDetails.add(guarantorFundingData);
+                    } else {
+                        rs.previous();
+                        break;
+                    }
+
+                }
+            }
+
+           
+
+            return new GuarantorData(id, loanId, clientRelationshipType, entityId, guarantorType, firstname, lastname, dob, addressLine1,
+                    addressLine2, city, state, zip, country, mobileNumber, housePhoneNumber, comment, null, null, null, status,
+                    guarantorFundingDetails, null, null, accountLinkingOptions);
+        }
+    }
+
+    private static final class GuarantorFundingMapper implements RowMapper<GuarantorFundingData> {
+
+        private final String sql;
+        private final GuarantorTransactionMapper guarantorTransactionMapper;
+
+        public GuarantorFundingMapper(final GuarantorTransactionMapper guarantorTransactionMapper) {
+            this.guarantorTransactionMapper = guarantorTransactionMapper;
+            StringBuilder sb = new StringBuilder(" gfd.id as gfdId,");
+            sb.append(" gfd.amount as amount, gfd.amount_released_derived as amountReleased, ");
+            sb.append(" gfd.amount_remaining_derived as amountRemaining, ");
+            sb.append(" gfd.amount_transfered_derived as amountTransfered, gfd.status_enum as statusEnum, ");
+            sb.append(" sa.id as savingsId, sa.account_no as accountNumber ");
+            sql = sb.toString();
+        }
+
+        public String schema() {
+            return this.sql;
+        }
+
+        @Override
+        public GuarantorFundingData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
+            GuarantorFundingData guarantorFundingData = null;
+            final Long id = rs.getLong("gfdId");
+            if (id != null && id > 0) {
+                final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amount");
+                final BigDecimal amountReleased = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountReleased");
+                final BigDecimal amountRemaining = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountRemaining");
+                final BigDecimal amountTransfered = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountTransfered");
+                final int statusEnum = rs.getInt("statusEnum");
+                final EnumOptionData status = GuarantorEnumerations.guarantorFundStatusType(statusEnum);
+                final Long savingsId = rs.getLong("savingsId");
+                final String savingsAccountNumber = rs.getString("accountNumber");
+                final PortfolioAccountData portfolioAccountData = PortfolioAccountData.lookup(savingsId, savingsAccountNumber);
+                List<GuarantorTransactionData> guarantorTransactions = new ArrayList<>();
+                if (this.guarantorTransactionMapper != null) {
+                    GuarantorTransactionData guarantorTransactionData = this.guarantorTransactionMapper.mapRow(rs, rowNum);
+                    if (guarantorTransactionData != null) {
+                        guarantorTransactions.add(guarantorTransactionData);
+                        while (rs.next()) {
+                            final Long tempFundId = rs.getLong("gfdId");
+                            if (tempFundId != null && tempFundId.equals(id)) {
+                                guarantorTransactionData = this.guarantorTransactionMapper.mapRow(rs, rowNum);
+                                guarantorTransactions.add(guarantorTransactionData);
+                            } else {
+                                rs.previous();
+                                break;
+                            }
+                        }
+                    }
+                }
+                guarantorFundingData = GuarantorFundingData.instance(id, status, portfolioAccountData, amount, amountReleased,
+                        amountRemaining, amountTransfered, guarantorTransactions);
+            }
+            return guarantorFundingData;
+        }
+    }
+
+    private static final class GuarantorTransactionMapper implements RowMapper<GuarantorTransactionData> {
+
+        private final String sql;
+
+        public GuarantorTransactionMapper() {
+            StringBuilder sb = new StringBuilder(" gt.id as gtId,");
+            sb.append("gt.is_reversed as reversed,");
+            sb.append(" oht.id as ohtId, oht.amount as transactionAmount, ");
+            sb.append(" oht.transaction_type_enum as transactionType, ");
+            sb.append(" oht.transaction_date as transactionDate, oht.is_reversed as transactionReversed ");
+            sql = sb.toString();
+        }
+
+        public String schema() {
+            return this.sql;
+        }
+
+        @Override
+        public GuarantorTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            GuarantorTransactionData guarantorTransactionData = null;
+            final Long id = rs.getLong("gtId");
+            final Long transactionId = rs.getLong("ohtId");
+            final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount");
+            final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate");
+            final int transactionTypeEnum = rs.getInt("transactionType");
+            EnumOptionData transactionType = SavingsEnumerations.onHoldTransactionType(transactionTypeEnum);
+            final boolean reversed = rs.getBoolean("reversed");
+            final boolean transactionReversed = rs.getBoolean("transactionReversed");
+            if (id != null) {
+                DepositAccountOnHoldTransactionData onHoldTransactionData = DepositAccountOnHoldTransactionData.instance(transactionId,
+                        amount, transactionType, date, transactionReversed);
+                guarantorTransactionData = GuarantorTransactionData.instance(id, onHoldTransactionData, null, reversed);
+            }
+            return guarantorTransactionData;
+        }
+
+    }
+
+    /**
+     * @param guarantorData
+     */
+    private GuarantorData mergeDetailsForClientOrStaffGuarantor(final GuarantorData guarantorData) {
+        if (guarantorData.isExistingClient()) {
+            final ClientData clientData = this.clientReadPlatformService.retrieveOne(guarantorData.getEntityId());
+            return GuarantorData.mergeClientData(clientData, guarantorData);
+        } else if (guarantorData.isStaffMember()) {
+            final StaffData staffData = this.staffReadPlatformService.retrieveStaff(guarantorData.getEntityId());
+            return GuarantorData.mergeStaffData(staffData, guarantorData);
+        }
+        return guarantorData;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformService.java
new file mode 100644
index 0000000..260f020
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformService.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface GuarantorWritePlatformService {
+
+    CommandProcessingResult createGuarantor(final Long loanId, final JsonCommand command);
+
+    CommandProcessingResult updateGuarantor(final Long loanId, final Long guarantorId, final JsonCommand command);
+
+    CommandProcessingResult removeGuarantor(final Long loanId, final Long guarantorId, Long guarantorFundingId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java
new file mode 100644
index 0000000..bea1f31
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java
@@ -0,0 +1,332 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.guarantor.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationType;
+import org.apache.fineract.portfolio.account.domain.AccountAssociations;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants;
+import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants.GUARANTOR_JSON_INPUT_PARAMS;
+import org.apache.fineract.portfolio.loanaccount.guarantor.command.GuarantorCommand;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.Guarantor;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundStatusType;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingDetails;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorRepository;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorType;
+import org.apache.fineract.portfolio.loanaccount.guarantor.exception.DuplicateGuarantorException;
+import org.apache.fineract.portfolio.loanaccount.guarantor.exception.GuarantorNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.guarantor.exception.InvalidGuarantorException;
+import org.apache.fineract.portfolio.loanaccount.guarantor.serialization.GuarantorCommandFromApiJsonDeserializer;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
+import org.joda.time.LocalDate;
+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 GuarantorWritePlatformServiceJpaRepositoryIImpl implements GuarantorWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(GuarantorWritePlatformServiceJpaRepositoryIImpl.class);
+
+    private final ClientRepositoryWrapper clientRepositoryWrapper;
+    private final StaffRepositoryWrapper staffRepositoryWrapper;
+    private final LoanRepositoryWrapper loanRepositoryWrapper;
+    private final GuarantorRepository guarantorRepository;
+    private final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
+    private final SavingsAccountAssembler savingsAccountAssembler;
+    private final AccountAssociationsRepository accountAssociationsRepository;
+    private final GuarantorDomainService guarantorDomainService;
+
+    @Autowired
+    public GuarantorWritePlatformServiceJpaRepositoryIImpl(final LoanRepositoryWrapper loanRepositoryWrapper,
+            final GuarantorRepository guarantorRepository, final ClientRepositoryWrapper clientRepositoryWrapper,
+            final StaffRepositoryWrapper staffRepositoryWrapper, final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+            final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
+            final AccountAssociationsRepository accountAssociationsRepository, final GuarantorDomainService guarantorDomainService) {
+        this.loanRepositoryWrapper = loanRepositoryWrapper;
+        this.clientRepositoryWrapper = clientRepositoryWrapper;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.guarantorRepository = guarantorRepository;
+        this.staffRepositoryWrapper = staffRepositoryWrapper;
+        this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
+        this.savingsAccountAssembler = savingsAccountAssembler;
+        this.accountAssociationsRepository = accountAssociationsRepository;
+        this.guarantorDomainService = guarantorDomainService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult createGuarantor(final Long loanId, final JsonCommand command) {
+        final GuarantorCommand guarantorCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
+        final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
+        final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
+        return createGuarantor(loan, command, guarantorCommand, existGuarantorList);
+    }
+
+    private CommandProcessingResult createGuarantor(final Loan loan, final JsonCommand command, final GuarantorCommand guarantorCommand,
+            final Collection<Guarantor> existGuarantorList) {
+        try {
+            guarantorCommand.validateForCreate();
+            validateLoanStatus(loan);
+            final List<GuarantorFundingDetails> guarantorFundingDetails = new ArrayList<>();
+            AccountAssociations accountAssociations = null;
+            if (guarantorCommand.getSavingsId() != null) {
+                final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(guarantorCommand.getSavingsId());
+                accountAssociations = AccountAssociations.associateSavingsAccount(loan, savingsAccount,
+                        AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue(), true);
+
+                GuarantorFundingDetails fundingDetails = new GuarantorFundingDetails(accountAssociations,
+                        GuarantorFundStatusType.ACTIVE.getValue(), guarantorCommand.getAmount());
+                guarantorFundingDetails.add(fundingDetails);
+                if (loan.isDisbursed() || loan.isApproved()
+                        && (loan.getGuaranteeAmount() != null || loan.loanProduct().isHoldGuaranteeFundsEnabled())) {
+                    this.guarantorDomainService.assignGuarantor(fundingDetails, LocalDate.now());
+                    loan.updateGuaranteeAmount(fundingDetails.getAmount());
+                }
+            }
+
+            final Long clientRelationshipId = guarantorCommand.getClientRelationshipTypeId();
+            CodeValue clientRelationshipType = null;
+
+            if (clientRelationshipId != null) {
+                clientRelationshipType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
+                        GuarantorConstants.GUARANTOR_RELATIONSHIP_CODE_NAME, clientRelationshipId);
+            }
+
+            final Long entityId = guarantorCommand.getEntityId();
+            final Integer guarantorTypeId = guarantorCommand.getGuarantorTypeId();
+            Guarantor guarantor = null;
+            for (final Guarantor avilableGuarantor : existGuarantorList) {
+                if (entityId != null && avilableGuarantor.getEntityId() != null && avilableGuarantor.getEntityId().equals(entityId)
+                        && avilableGuarantor.getGurantorType().equals(guarantorTypeId) && avilableGuarantor.isActive()) {
+                    if (guarantorCommand.getSavingsId() == null || avilableGuarantor.hasGuarantor(guarantorCommand.getSavingsId())) {
+                        /** Get the right guarantor based on guarantorType **/
+                        String defaultUserMessage = null;
+                        if (guarantorTypeId.equals(GuarantorType.STAFF.getValue())) {
+                            defaultUserMessage = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId).displayName();
+                        } else {
+                            defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName();
+                        }
+
+                        defaultUserMessage = defaultUserMessage + " is already exist as a guarantor for this loan";
+                        final String action = loan.client() != null ? "client.guarantor" : "group.guarantor";
+                        throw new DuplicateGuarantorException(action, "is.already.exist.same.loan", defaultUserMessage, entityId,
+                                loan.getId());
+                    }
+                    guarantor = avilableGuarantor;
+                    break;
+                }
+            }
+
+            if (guarantor == null) {
+                guarantor = Guarantor.fromJson(loan, clientRelationshipType, command, guarantorFundingDetails);
+            } else {
+                guarantor.addFundingDetails(guarantorFundingDetails);
+            }
+            validateGuarantorBusinessRules(guarantor);
+            for (GuarantorFundingDetails fundingDetails : guarantorFundingDetails) {
+                fundingDetails.updateGuarantor(guarantor);
+            }
+
+            if (accountAssociations != null) {
+                this.accountAssociationsRepository.save(accountAssociations);
+            }
+            this.guarantorRepository.save(guarantor);
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(guarantor.getOfficeId())
+                    .withEntityId(guarantor.getId()).withLoanId(loan.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleGuarantorDataIntegrityIssues(dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult updateGuarantor(final Long loanId, final Long guarantorId, final JsonCommand command) {
+        try {
+            final GuarantorCommand guarantorCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
+            guarantorCommand.validateForUpdate();
+
+            final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
+            validateLoanStatus(loan);
+            final Guarantor guarantorForUpdate = this.guarantorRepository.findByLoanAndId(loan, guarantorId);
+            if (guarantorForUpdate == null) { throw new GuarantorNotFoundException(loanId, guarantorId); }
+
+            final Map<String, Object> changesOnly = guarantorForUpdate.update(command);
+
+            if (changesOnly.containsKey(GUARANTOR_JSON_INPUT_PARAMS.CLIENT_RELATIONSHIP_TYPE_ID.getValue())) {
+                final Long clientRelationshipId = guarantorCommand.getClientRelationshipTypeId();
+                CodeValue clientRelationshipType = null;
+                if (clientRelationshipId != null) {
+                    clientRelationshipType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
+                            GuarantorConstants.GUARANTOR_RELATIONSHIP_CODE_NAME, clientRelationshipId);
+                }
+                guarantorForUpdate.updateClientRelationshipType(clientRelationshipType);
+            }
+
+            final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
+            final Integer guarantorTypeId = guarantorCommand.getGuarantorTypeId();
+            final GuarantorType guarantorType = GuarantorType.fromInt(guarantorTypeId);
+            if (guarantorType.isCustomer() || guarantorType.isStaff()) {
+                final Long entityId = guarantorCommand.getEntityId();
+                for (final Guarantor guarantor : existGuarantorList) {
+                    if (guarantor.getEntityId().equals(entityId) && guarantor.getGurantorType().equals(guarantorTypeId)
+                            && !guarantorForUpdate.getId().equals(guarantor.getId())) {
+                        String defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName();
+                        defaultUserMessage = defaultUserMessage + " is already exist as a guarantor for this loan";
+                        final String action = loan.client() != null ? "client.guarantor" : "group.guarantor";
+                        throw new DuplicateGuarantorException(action, "is.already.exist.same.loan", defaultUserMessage, entityId, loanId);
+                    }
+                }
+            }
+
+            if (changesOnly.containsKey(GUARANTOR_JSON_INPUT_PARAMS.ENTITY_ID)
+                    || changesOnly.containsKey(GUARANTOR_JSON_INPUT_PARAMS.GUARANTOR_TYPE_ID)) {
+                validateGuarantorBusinessRules(guarantorForUpdate);
+            }
+
+            if (!changesOnly.isEmpty()) {
+                this.guarantorRepository.save(guarantorForUpdate);
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(guarantorForUpdate.getOfficeId())
+                    .withEntityId(guarantorForUpdate.getId()).withOfficeId(guarantorForUpdate.getLoanId()).with(changesOnly).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleGuarantorDataIntegrityIssues(dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult removeGuarantor(final Long loanId, final Long guarantorId, final Long guarantorFundingId) {
+        final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
+        validateLoanStatus(loan);
+        final Guarantor guarantorForDelete = this.guarantorRepository.findByLoanAndId(loan, guarantorId);
+        if (guarantorForDelete == null || (guarantorFundingId == null && !guarantorForDelete.getGuarantorFundDetails().isEmpty())) { throw new GuarantorNotFoundException(
+                loanId, guarantorId, guarantorFundingId); }
+        CommandProcessingResult commandProcessingResult = removeGuarantor(guarantorForDelete, loanId, guarantorFundingId);
+        if (loan.isApproved() || loan.isDisbursed()) {
+            this.guarantorDomainService.validateGuarantorBusinessRules(loan);
+        }
+        return commandProcessingResult;
+    }
+
+    private CommandProcessingResult removeGuarantor(final Guarantor guarantorForDelete, final Long loanId, final Long guarantorFundingId) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("Guarantor");
+
+        if (guarantorFundingId == null) {
+            if (!guarantorForDelete.isActive()) {
+                baseDataValidator.failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_NOT_ACTIVE_ERROR);
+            }
+            guarantorForDelete.updateStatus(false);
+        } else {
+            GuarantorFundingDetails guarantorFundingDetails = guarantorForDelete.getGuarantorFundingDetail(guarantorFundingId);
+            if (guarantorFundingDetails == null) { throw new GuarantorNotFoundException(loanId, guarantorForDelete.getId(),
+                    guarantorFundingId); }
+            removeguarantorFundDetails(guarantorForDelete, baseDataValidator, guarantorFundingDetails);
+
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+        this.guarantorRepository.saveAndFlush(guarantorForDelete);
+        CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder()
+                .withEntityId(guarantorForDelete.getId()).withLoanId(guarantorForDelete.getLoanId())
+                .withOfficeId(guarantorForDelete.getOfficeId());
+        if (guarantorFundingId != null) {
+            commandProcessingResultBuilder.withSubEntityId(guarantorFundingId);
+        }
+        return commandProcessingResultBuilder.build();
+    }
+
+    private void removeguarantorFundDetails(final Guarantor guarantorForDelete, final DataValidatorBuilder baseDataValidator,
+            GuarantorFundingDetails guarantorFundingDetails) {
+        if (!guarantorFundingDetails.getStatus().isActive()) {
+            baseDataValidator.failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_NOT_ACTIVE_ERROR);
+        }
+        GuarantorFundStatusType fundStatusType = GuarantorFundStatusType.DELETED;
+        if (guarantorForDelete.getLoan().isDisbursed() || guarantorForDelete.getLoan().isApproved()) {
+            fundStatusType = GuarantorFundStatusType.WITHDRAWN;
+            this.guarantorDomainService.releaseGuarantor(guarantorFundingDetails, LocalDate.now());
+        }
+        guarantorForDelete.updateStatus(guarantorFundingDetails, fundStatusType);
+    }
+
+    private void validateGuarantorBusinessRules(final Guarantor guarantor) {
+        // validate guarantor conditions
+        if (guarantor.isExistingCustomer()) {
+            // check client exists
+            this.clientRepositoryWrapper.findOneWithNotFoundDetection(guarantor.getEntityId());
+            // validate that the client is not set as a self guarantor
+            if (guarantor.getClientId() != null && guarantor.getClientId().equals(guarantor.getEntityId())) {
+                String errorCode = null;
+                if (guarantor.getGuarantorFundDetails().isEmpty()) {
+                    errorCode = "guarantor.can.not.be.own";
+                } else if (guarantor.getClientRelationshipType() != null) {
+                    errorCode = "guarantor.relation.should.be.empty.for.own";
+                }
+                if (errorCode != null) { throw new InvalidGuarantorException(guarantor.getEntityId(), guarantor.getLoanId(), errorCode); }
+            }
+
+        } else if (guarantor.isExistingEmployee()) {
+            this.staffRepositoryWrapper.findOneWithNotFoundDetection(guarantor.getEntityId());
+        }
+    }
+
+    private void validateLoanStatus(Loan loan) {
+        if (!loan.status().isActiveOrAwaitingApprovalOrDisbursal()) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.closed");
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+
+    private void handleGuarantorDataIntegrityIssues(final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.guarantor.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource Guarantor: " + realCause.getMessage());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/AddAndDeleteLoanDisburseDetailsCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/AddAndDeleteLoanDisburseDetailsCommandHandler.java
new file mode 100644
index 0000000..e96239d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/AddAndDeleteLoanDisburseDetailsCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class AddAndDeleteLoanDisburseDetailsCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public AddAndDeleteLoanDisburseDetailsCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.addAndDeleteLoanDisburseDetails(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/AddLoanChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/AddLoanChargeCommandHandler.java
new file mode 100644
index 0000000..af23427
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/AddLoanChargeCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOANCHARGE", action = "CREATE")
+public class AddLoanChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public AddLoanChargeCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.addLoanCharge(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/BulkUpdateLoanOfficerCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/BulkUpdateLoanOfficerCommandHandler.java
new file mode 100644
index 0000000..9f306e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/BulkUpdateLoanOfficerCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "BULKREASSIGN")
+public class BulkUpdateLoanOfficerCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public BulkUpdateLoanOfficerCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.bulkLoanReassignment(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/CloseLoanAsRescheduledCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/CloseLoanAsRescheduledCommandHandler.java
new file mode 100644
index 0000000..83e6d16
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/CloseLoanAsRescheduledCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "CLOSEASRESCHEDULED")
+public class CloseLoanAsRescheduledCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public CloseLoanAsRescheduledCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.closeAsRescheduled(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/CloseLoanCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/CloseLoanCommandHandler.java
new file mode 100644
index 0000000..90478eb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/CloseLoanCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "CLOSE")
+public class CloseLoanCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public CloseLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.closeLoan(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/DeleteLoanChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/DeleteLoanChargeCommandHandler.java
new file mode 100644
index 0000000..47c07e2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/DeleteLoanChargeCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOANCHARGE", action = "DELETE")
+public class DeleteLoanChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteLoanChargeCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.deleteLoanCharge(command.getLoanId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/DisburseLoanCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/DisburseLoanCommandHandler.java
new file mode 100644
index 0000000..b20308e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/DisburseLoanCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "DISBURSE")
+public class DisburseLoanCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public DisburseLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.disburseLoan(command.entityId(), command, false);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/DisburseLoanToSavingsCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/DisburseLoanToSavingsCommandHandler.java
new file mode 100755
index 0000000..07a824f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/DisburseLoanToSavingsCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "DISBURSETOSAVINGS")
+public class DisburseLoanToSavingsCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public DisburseLoanToSavingsCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.disburseLoan(command.entityId(), command, true);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationApprovalCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationApprovalCommandHandler.java
new file mode 100644
index 0000000..b1ed5fe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationApprovalCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "APPROVE")
+public class LoanApplicationApprovalCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanApplicationWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanApplicationApprovalCommandHandler(final LoanApplicationWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.approveApplication(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationApprovalUndoCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationApprovalUndoCommandHandler.java
new file mode 100644
index 0000000..b7b4839
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationApprovalUndoCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "APPROVALUNDO")
+public class LoanApplicationApprovalUndoCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanApplicationWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanApplicationApprovalUndoCommandHandler(final LoanApplicationWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.undoApplicationApproval(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationDeletionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationDeletionCommandHandler.java
new file mode 100644
index 0000000..e941faf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationDeletionCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "DELETE")
+public class LoanApplicationDeletionCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanApplicationWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanApplicationDeletionCommandHandler(final LoanApplicationWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.deleteApplication(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java
new file mode 100644
index 0000000..9836dad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "UPDATE")
+public class LoanApplicationModificationCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanApplicationWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanApplicationModificationCommandHandler(final LoanApplicationWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.modifyApplication(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationRejectedCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationRejectedCommandHandler.java
new file mode 100644
index 0000000..f3b9d7f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationRejectedCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "REJECT")
+public class LoanApplicationRejectedCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanApplicationWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanApplicationRejectedCommandHandler(final LoanApplicationWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.rejectApplication(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationSubmittalCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationSubmittalCommandHandler.java
new file mode 100644
index 0000000..515ddea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationSubmittalCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "CREATE")
+public class LoanApplicationSubmittalCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanApplicationWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanApplicationSubmittalCommandHandler(final LoanApplicationWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.submitApplication(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationWithdrawnByApplicantCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationWithdrawnByApplicantCommandHandler.java
new file mode 100644
index 0000000..2d66dbf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApplicationWithdrawnByApplicantCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "WITHDRAW")
+public class LoanApplicationWithdrawnByApplicantCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanApplicationWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanApplicationWithdrawnByApplicantCommandHandler(final LoanApplicationWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.applicantWithdrawsFromApplication(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRecoveryPaymentCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRecoveryPaymentCommandHandler.java
new file mode 100755
index 0000000..a2429ca
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRecoveryPaymentCommandHandler.java
@@ -0,0 +1,45 @@
+/**

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements. See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ * http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied. See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.fineract.portfolio.loanaccount.handler;

+

+import org.apache.fineract.commands.annotation.CommandType;

+import org.apache.fineract.commands.handler.NewCommandSourceHandler;

+import org.apache.fineract.infrastructure.core.api.JsonCommand;

+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;

+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Service;

+

+@Service

+@CommandType(entity = "LOAN", action = "RECOVERYPAYMENT")

+public class LoanRecoveryPaymentCommandHandler implements NewCommandSourceHandler {

+

+    private final LoanWritePlatformService writePlatformService;

+

+    @Autowired

+    public LoanRecoveryPaymentCommandHandler(LoanWritePlatformService writePlatformService) {

+        this.writePlatformService = writePlatformService;

+    }

+

+    @Override

+    public CommandProcessingResult processCommand(JsonCommand command) {

+        final boolean isRecoveryRepayment = true;

+        return writePlatformService.makeLoanRepayment(command.getLoanId(), command, isRecoveryRepayment);

+    }

+}

diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRefundByCashCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRefundByCashCommandHandler.java
new file mode 100644
index 0000000..e06568c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRefundByCashCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "REFUNDBYCASH")
+public class LoanRefundByCashCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanRefundByCashCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.makeLoanRefund(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentAdjustmentCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentAdjustmentCommandHandler.java
new file mode 100644
index 0000000..5616a08
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentAdjustmentCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "ADJUST")
+public class LoanRepaymentAdjustmentCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanRepaymentAdjustmentCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.adjustLoanTransaction(command.getLoanId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentCommandHandler.java
new file mode 100644
index 0000000..23d01f7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "REPAYMENT")
+public class LoanRepaymentCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public LoanRepaymentCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        boolean isRecoveryRepayment = false;
+        return this.writePlatformService.makeLoanRepayment(command.getLoanId(), command, isRecoveryRepayment);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanScheduleCreateVariationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanScheduleCreateVariationCommandHandler.java
new file mode 100644
index 0000000..41e8bb3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanScheduleCreateVariationCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "LOAN", action = "CREATESCHEDULEEXCEPTIONS")
+public class LoanScheduleCreateVariationCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanScheduleWritePlatformService loanScheduleWritePlatformService;
+
+    @Autowired
+    public LoanScheduleCreateVariationCommandHandler(final LoanScheduleWritePlatformService loanScheduleWritePlatformService) {
+        this.loanScheduleWritePlatformService = loanScheduleWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.loanScheduleWritePlatformService.addLoanScheduleVariations(command.getLoanId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanScheduleDeleteVariationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanScheduleDeleteVariationCommandHandler.java
new file mode 100644
index 0000000..4349da3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanScheduleDeleteVariationCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "LOAN", action = "DELETESCHEDULEEXCEPTIONS")
+public class LoanScheduleDeleteVariationCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanScheduleWritePlatformService loanScheduleWritePlatformService;
+
+    @Autowired
+    public LoanScheduleDeleteVariationCommandHandler(final LoanScheduleWritePlatformService loanScheduleWritePlatformService) {
+        this.loanScheduleWritePlatformService = loanScheduleWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.loanScheduleWritePlatformService.deleteLoanScheduleVariations(command.getLoanId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/PayLoanChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/PayLoanChargeCommandHandler.java
new file mode 100755
index 0000000..eba1870
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/PayLoanChargeCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOANCHARGE", action = "PAY")
+public class PayLoanChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public PayLoanChargeCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.payLoanCharge(command.getLoanId(), command.entityId(), command, command.entityId() == null);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/RecoverFromGuarantorCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/RecoverFromGuarantorCommandHandler.java
new file mode 100755
index 0000000..7098d18
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/RecoverFromGuarantorCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "LOAN", action = "RECOVERGUARANTEES")
+public class RecoverFromGuarantorCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public RecoverFromGuarantorCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.writePlatformService.recoverFromGuarantor(command.getLoanId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/RemoveLoanOfficerCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/RemoveLoanOfficerCommandHandler.java
new file mode 100644
index 0000000..d9baeb0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/RemoveLoanOfficerCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "REMOVELOANOFFICER")
+public class RemoveLoanOfficerCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public RemoveLoanOfficerCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.removeLoanOfficer(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoDisbursalLoanCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoDisbursalLoanCommandHandler.java
new file mode 100644
index 0000000..cd08f17
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoDisbursalLoanCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "DISBURSALUNDO")
+public class UndoDisbursalLoanCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public UndoDisbursalLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.undoLoanDisbursal(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoLastDisbursalLoanCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoLastDisbursalLoanCommandHandler.java
new file mode 100644
index 0000000..40e25ab
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoLastDisbursalLoanCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "DISBURSALLASTUNDO")
+public class UndoLastDisbursalLoanCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public UndoLastDisbursalLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.undoLastLoanDisbursal(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoWriteOffLoanCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoWriteOffLoanCommandHandler.java
new file mode 100755
index 0000000..a6e0f85
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoWriteOffLoanCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "UNDOWRITEOFF")
+public class UndoWriteOffLoanCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public UndoWriteOffLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.undoWriteOff(command.getLoanId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UpdateLoanChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UpdateLoanChargeCommandHandler.java
new file mode 100644
index 0000000..cb6d297
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UpdateLoanChargeCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOANCHARGE", action = "UPDATE")
+public class UpdateLoanChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateLoanChargeCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateLoanCharge(command.getLoanId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java
new file mode 100644
index 0000000..215f606
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class UpdateLoanDisbuseDateCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateLoanDisbuseDateCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+    	 
+        return this.writePlatformService.updateDisbursementDateAndAmountForTranche(command.getLoanId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UpdateLoanOfficerCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UpdateLoanOfficerCommandHandler.java
new file mode 100644
index 0000000..84b6864
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UpdateLoanOfficerCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "UPDATELOANOFFICER")
+public class UpdateLoanOfficerCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateLoanOfficerCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.loanReassignment(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/WaiveInterestPortionOnLoanCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/WaiveInterestPortionOnLoanCommandHandler.java
new file mode 100644
index 0000000..00dbb38
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/WaiveInterestPortionOnLoanCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "WAIVEINTERESTPORTION")
+public class WaiveInterestPortionOnLoanCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public WaiveInterestPortionOnLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.waiveInterestOnLoan(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/WaiveLoanChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/WaiveLoanChargeCommandHandler.java
new file mode 100644
index 0000000..e4f84e2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/WaiveLoanChargeCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOANCHARGE", action = "WAIVE")
+public class WaiveLoanChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public WaiveLoanChargeCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.waiveLoanCharge(command.getLoanId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/WriteOffLoanCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/WriteOffLoanCommandHandler.java
new file mode 100644
index 0000000..6c2c2b3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/WriteOffLoanCommandHandler.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.portfolio.loanaccount.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOAN", action = "WRITEOFF")
+public class WriteOffLoanCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Autowired
+    public WriteOffLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.writeOff(command.getLoanId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleDTO.java
new file mode 100644
index 0000000..8549e04
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleDTO.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
+
+import java.util.List;
+
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+
+/**
+ * Transfer object to return the schedule after generation of schedule
+ */
+public class LoanScheduleDTO {
+
+    private final List<LoanRepaymentScheduleInstallment> installments;
+    private final LoanScheduleModel loanScheduleModel;
+
+    private LoanScheduleDTO(final List<LoanRepaymentScheduleInstallment> installments, final LoanScheduleModel loanScheduleModel) {
+        this.installments = installments;
+        this.loanScheduleModel = loanScheduleModel;
+    }
+    
+    public static LoanScheduleDTO from(final List<LoanRepaymentScheduleInstallment> installments, final LoanScheduleModel loanScheduleModel){
+        return new LoanScheduleDTO(installments, loanScheduleModel);
+    }
+    
+    public List<LoanRepaymentScheduleInstallment> getInstallments() {
+        return this.installments;
+    }
+
+    public LoanScheduleModel getLoanScheduleModel() {
+        return this.loanScheduleModel;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java
new file mode 100644
index 0000000..3a4883b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java
@@ -0,0 +1,119 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+/**
+ * Immutable data object to represent aspects of a loan schedule such as:
+ * 
+ * <ul>
+ * <li>Totals information - the totals for each part of repayment schedule
+ * monitored.</li>
+ * <li>Repayment schedule - the principal due, outstanding balance and cost of
+ * loan items such as interest and charges (both fees and penalties)</li>
+ * </ul>
+ */
+@SuppressWarnings("unused")
+public class LoanScheduleData {
+
+    /**
+     * The currency associated with all monetary values in loan schedule.
+     */
+    private final CurrencyData currency;
+    private final Integer loanTermInDays;
+    private final BigDecimal totalPrincipalDisbursed;
+    private final BigDecimal totalPrincipalExpected;
+    private final BigDecimal totalPrincipalPaid;
+    private final BigDecimal totalInterestCharged;
+    private final BigDecimal totalFeeChargesCharged;
+    private final BigDecimal totalPenaltyChargesCharged;
+    private final BigDecimal totalWaived;
+    private final BigDecimal totalWrittenOff;
+    private final BigDecimal totalRepaymentExpected;
+    private final BigDecimal totalRepayment;
+    private final BigDecimal totalPaidInAdvance;
+    private final BigDecimal totalPaidLate;
+    private final BigDecimal totalOutstanding;
+
+    /**
+     * <code>periods</code> is collection of data objects containing specific
+     * information to each period of the loan schedule including disbursement
+     * and repayment information.
+     */
+    private final Collection<LoanSchedulePeriodData> periods;
+
+    private Collection<LoanSchedulePeriodData> futurePeriods;
+
+    public LoanScheduleData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods, final Integer loanTermInDays,
+            final BigDecimal totalPrincipalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid,
+            final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged,
+            final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalRepaymentExpected,
+            final BigDecimal totalRepayment, final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate,
+            final BigDecimal totalOutstanding) {
+        this.currency = currency;
+        this.periods = periods;
+        this.loanTermInDays = loanTermInDays;
+        this.totalPrincipalDisbursed = totalPrincipalDisbursed;
+        this.totalPrincipalExpected = totalPrincipalExpected;
+        this.totalPrincipalPaid = totalPrincipalPaid;
+        this.totalInterestCharged = totalInterestCharged;
+        this.totalFeeChargesCharged = totalFeeChargesCharged;
+        this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
+        this.totalWaived = totalWaived;
+        this.totalWrittenOff = totalWrittenOff;
+        this.totalRepaymentExpected = totalRepaymentExpected;
+        this.totalRepayment = totalRepayment;
+        this.totalPaidInAdvance = totalPaidInAdvance;
+        this.totalPaidLate = totalPaidLate;
+        this.totalOutstanding = totalOutstanding;
+    }
+
+    public LoanScheduleData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods, final Integer loanTermInDays,
+            final BigDecimal totalPrincipalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalInterestCharged,
+            final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected) {
+        this.currency = currency;
+        this.periods = periods;
+        this.loanTermInDays = loanTermInDays;
+        this.totalPrincipalDisbursed = totalPrincipalDisbursed;
+        this.totalPrincipalExpected = totalPrincipalExpected;
+        this.totalPrincipalPaid = null;
+        this.totalInterestCharged = totalInterestCharged;
+        this.totalFeeChargesCharged = totalFeeChargesCharged;
+        this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
+        this.totalWaived = null;
+        this.totalWrittenOff = null;
+        this.totalRepaymentExpected = totalRepaymentExpected;
+        this.totalRepayment = null;
+        this.totalPaidInAdvance = null;
+        this.totalPaidLate = null;
+        this.totalOutstanding = null;
+    }
+
+    public Collection<LoanSchedulePeriodData> getPeriods() {
+        return this.periods;
+    }
+
+    public void updateFuturePeriods(Collection<LoanSchedulePeriodData> futurePeriods) {
+        this.futurePeriods = futurePeriods;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java
new file mode 100644
index 0000000..679964a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java
@@ -0,0 +1,449 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.RecalculationDetail;
+import org.joda.time.LocalDate;
+
+public class LoanScheduleParams {
+
+    // Actual period Number as per the schedule
+    private int periodNumber;
+    // Actual period Number plus interest only repayments
+    private int instalmentNumber;
+
+    private LocalDate periodStartDate;
+    private LocalDate actualRepaymentDate;
+
+    // variables for cumulative totals
+    private Money totalCumulativePrincipal;
+    private Money totalCumulativeInterest;
+    private Money totalFeeChargesCharged;
+    private Money totalPenaltyChargesCharged;
+    private Money totalRepaymentExpected;
+    private Money totalOutstandingInterestPaymentDueToGrace;
+
+    // early payments will be added here and as per the selected strategy
+    // action will be performed on this value
+    private Money reducePrincipal;
+
+    // principal changes will be added along with date(after applying rest)
+    // from when these amounts will effect the outstanding balance for
+    // interest calculation
+    private final Map<LocalDate, Money> principalPortionMap;
+
+    // compounding(principal) amounts will be added along with
+    // date(after applying compounding frequency)
+    // from when these amounts will effect the outstanding balance for
+    // interest calculation
+    private final Map<LocalDate, Money> latePaymentMap;
+
+    // compounding(interest/Fee) amounts will be added along with
+    // date(after applying compounding frequency)
+    // from when these amounts will effect the outstanding balance for
+    // interest calculation
+    private final TreeMap<LocalDate, Money> compoundingMap;
+    private final Map<LocalDate, TreeMap<LocalDate, Money>> compoundingDateVariations = new HashMap<>();
+
+    // disbursement map for tranche details(will added to outstanding
+    // balance as per the start date)
+    private final Map<LocalDate, Money> disburseDetailMap;
+    private Money principalToBeScheduled;
+    private Money outstandingBalance;
+
+    // total outstanding balance as per rest for interest calculation.
+    private Money outstandingBalanceAsPerRest;
+
+    private final List<LoanRepaymentScheduleInstallment> installments;
+    private final Collection<RecalculationDetail> recalculationDetails;
+    private final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor;
+    private final LocalDate scheduleTillDate;
+    private final boolean partialUpdate;
+    private int loanTermInDays;
+    private final MonetaryCurrency currency;
+    private final boolean applyInterestRecalculation;
+
+    private LoanScheduleParams(final int periodNumber, final int instalmentNumber, int loanTermInDays, LocalDate periodStartDate,
+            final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
+            final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected,
+            Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap,
+            final Map<LocalDate, Money> latePaymentMap, final TreeMap<LocalDate, Money> compoundingMap,
+            final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance,
+            final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
+            final Collection<RecalculationDetail> recalculationDetails,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
+            final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
+        this.periodNumber = periodNumber;
+        this.instalmentNumber = instalmentNumber;
+        this.loanTermInDays = loanTermInDays;
+        this.periodStartDate = periodStartDate;
+        this.actualRepaymentDate = actualRepaymentDate;
+        this.totalCumulativePrincipal = totalCumulativePrincipal;
+        this.totalCumulativeInterest = totalCumulativeInterest;
+        this.totalFeeChargesCharged = totalFeeChargesCharged;
+        this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
+        this.totalRepaymentExpected = totalRepaymentExpected;
+        this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
+        this.reducePrincipal = reducePrincipal;
+        this.principalPortionMap = principalPortionMap;
+        this.latePaymentMap = latePaymentMap;
+        this.compoundingMap = compoundingMap;
+        this.disburseDetailMap = disburseDetailMap;
+        this.principalToBeScheduled = principalToBeScheduled;
+        this.outstandingBalance = outstandingBalance;
+        this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
+        this.installments = installments;
+        this.recalculationDetails = recalculationDetails;
+        this.loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessor;
+        this.scheduleTillDate = scheduleTillDate;
+        this.partialUpdate = partialUpdate;
+        this.currency = currency;
+        this.applyInterestRecalculation = applyInterestRecalculation;
+    }
+
+    public static LoanScheduleParams createLoanScheduleParamsForPartialUpdate(final int periodNumber, final int instalmentNumber,
+            int loanTermInDays, LocalDate periodStartDate, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal,
+            final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
+            final Money totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal,
+            final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
+            final TreeMap<LocalDate, Money> compoundingMap, final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled,
+            final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
+            final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
+            final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
+        final boolean partialUpdate = true;
+        return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
+                totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
+                totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
+                compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments,
+                recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency,
+                applyInterestRecalculation);
+    }
+
+    public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection<RecalculationDetail> recalculationDetails,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
+            final boolean applyInterestRecalculation) {
+        final int periodNumber = 1;
+        final int instalmentNumber = 1;
+        final LocalDate periodStartDate = null;
+        final LocalDate actualRepaymentDate = null;
+        final Money totalCumulativePrincipal = null;
+        final Money totalCumulativeInterest = null;
+        final Money totalFeeChargesCharged = null;
+        final Money totalPenaltyChargesCharged = null;
+        final Money totalRepaymentExpected = null;
+        final Money reducePrincipal = null;
+        final Map<LocalDate, Money> principalPortionMap = null;
+        final Map<LocalDate, Money> latePaymentMap = null;
+        final TreeMap<LocalDate, Money> compoundingMap = null;
+        final Map<LocalDate, Money> disburseDetailMap = null;
+        final Money principalToBeScheduled = null;
+        final Money outstandingBalance = null;
+        final Money outstandingBalanceAsPerRest = null;
+        final List<LoanRepaymentScheduleInstallment> installments = null;
+        final boolean partialUpdate = false;
+        final int loanTermInDays = 0;
+        final Money totalOutstandingInterestPaymentDueToGrace = null;
+        final MonetaryCurrency currency = null;
+        return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
+                totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
+                totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
+                compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments,
+                recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency,
+                applyInterestRecalculation);
+    }
+
+    public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
+            final LocalDate periodStartDate, final Money principalToBeScheduled) {
+        final int loanTermInDays = 0;
+        final int periodNumber = 1;
+        final int instalmentNumber = 1;
+        final Money totalCumulativePrincipal = Money.zero(currency);
+        final Money totalCumulativeInterest = Money.zero(currency);
+        final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
+        final LocalDate actualRepaymentDate = periodStartDate;
+        final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
+        final Money totalPenaltyChargesCharged = Money.zero(currency);
+        final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
+        final Money reducePrincipal = Money.zero(currency);
+        final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
+        final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
+        final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>();
+        final Map<LocalDate, Money> disburseDetailMap = new TreeMap<>();
+        final Money outstandingBalance = principalToBeScheduled;
+        final Money outstandingBalanceAsPerRest = principalToBeScheduled;
+        final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
+        final boolean partialUpdate = false;
+        final Collection<RecalculationDetail> recalculationDetails = null;
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
+        final LocalDate scheduleTillDate = null;
+        final boolean applyInterestRecalculation = false;
+        return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
+                totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
+                totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
+                compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments,
+                recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency,
+                applyInterestRecalculation);
+    }
+
+    public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
+            final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams) {
+        final int loanTermInDays = 0;
+        final int periodNumber = 1;
+        final int instalmentNumber = 1;
+        final Money totalCumulativePrincipal = Money.zero(currency);
+        final Money totalCumulativeInterest = Money.zero(currency);
+        final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
+        final LocalDate actualRepaymentDate = periodStartDate;
+        final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
+        final Money totalPenaltyChargesCharged = Money.zero(currency);
+        final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
+        final Money reducePrincipal = Money.zero(currency);
+        final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
+        final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
+        final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>();
+        final Map<LocalDate, Money> disburseDetailMap = new TreeMap<>();
+        final Money outstandingBalance = principalToBeScheduled;
+        final Money outstandingBalanceAsPerRest = principalToBeScheduled;
+        final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
+        final boolean partialUpdate = false;
+        final Collection<RecalculationDetail> recalculationDetails = loanScheduleParams.recalculationDetails;
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleParams.loanRepaymentScheduleTransactionProcessor;
+        final LocalDate scheduleTillDate = loanScheduleParams.scheduleTillDate;
+        final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation;
+        return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
+                totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
+                totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
+                compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments,
+                recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency,
+                applyInterestRecalculation);
+    }
+
+    public int getPeriodNumber() {
+        return this.periodNumber;
+    }
+
+    public int getInstalmentNumber() {
+        return this.instalmentNumber;
+    }
+
+    public LocalDate getActualRepaymentDate() {
+        return this.actualRepaymentDate;
+    }
+
+    public Money getTotalCumulativePrincipal() {
+        return this.totalCumulativePrincipal;
+    }
+
+    public void addTotalCumulativePrincipal(final Money totalCumulativePrincipal) {
+        this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal);
+    }
+
+    public Money getTotalCumulativeInterest() {
+        return this.totalCumulativeInterest;
+    }
+
+    public void addTotalCumulativeInterest(final Money totalCumulativeInterest) {
+        this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest);
+    }
+
+    public Money getTotalFeeChargesCharged() {
+        return this.totalFeeChargesCharged;
+    }
+
+    public void addTotalFeeChargesCharged(final Money totalFeeChargesCharged) {
+        this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged);
+    }
+
+    public Money getTotalPenaltyChargesCharged() {
+        return this.totalPenaltyChargesCharged;
+    }
+
+    public void addTotalPenaltyChargesCharged(final Money totalPenaltyChargesCharged) {
+        this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged);
+    }
+
+    public Money getTotalRepaymentExpected() {
+        return this.totalRepaymentExpected;
+    }
+
+    public void addTotalRepaymentExpected(final Money totalRepaymentExpected) {
+        this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected);
+    }
+
+    public Money getReducePrincipal() {
+        return this.reducePrincipal;
+    }
+
+    public Map<LocalDate, Money> getPrincipalPortionMap() {
+        return this.principalPortionMap;
+    }
+
+    public Map<LocalDate, Money> getLatePaymentMap() {
+        return this.latePaymentMap;
+    }
+
+    public TreeMap<LocalDate, Money> getCompoundingMap() {
+        return this.compoundingMap;
+    }
+
+    public Map<LocalDate, Money> getDisburseDetailMap() {
+        return this.disburseDetailMap;
+    }
+
+    public Money getOutstandingBalance() {
+        return this.outstandingBalance;
+    }
+
+    public Money getOutstandingBalanceAsPerRest() {
+        return this.outstandingBalanceAsPerRest;
+    }
+
+    public List<LoanRepaymentScheduleInstallment> getInstallments() {
+        return this.installments;
+    }
+
+    public Collection<RecalculationDetail> getRecalculationDetails() {
+        return this.recalculationDetails;
+    }
+
+    public LoanRepaymentScheduleTransactionProcessor getLoanRepaymentScheduleTransactionProcessor() {
+        return this.loanRepaymentScheduleTransactionProcessor;
+    }
+
+    public LocalDate getScheduleTillDate() {
+        return this.scheduleTillDate;
+    }
+
+    public boolean isPartialUpdate() {
+        return this.partialUpdate;
+    }
+
+    public LocalDate getPeriodStartDate() {
+        return this.periodStartDate;
+    }
+
+    public Money getPrincipalToBeScheduled() {
+        return this.principalToBeScheduled;
+    }
+
+    public void setPeriodNumber(int periodNumber) {
+        this.periodNumber = periodNumber;
+    }
+
+    public void incrementPeriodNumber() {
+        this.periodNumber++;
+    }
+
+    public void incrementInstalmentNumber() {
+        this.instalmentNumber++;
+    }
+
+    public void setPeriodStartDate(LocalDate periodStartDate) {
+        this.periodStartDate = periodStartDate;
+    }
+
+    public void setActualRepaymentDate(LocalDate actualRepaymentDate) {
+        this.actualRepaymentDate = actualRepaymentDate;
+    }
+
+    public void setReducePrincipal(Money reducePrincipal) {
+        this.reducePrincipal = reducePrincipal;
+    }
+
+    public void addReducePrincipal(Money reducePrincipal) {
+        this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal);
+    }
+
+    public void reduceReducePrincipal(Money reducePrincipal) {
+        this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal);
+    }
+
+    public void setPrincipalToBeScheduled(Money principalToBeScheduled) {
+        this.principalToBeScheduled = principalToBeScheduled;
+    }
+
+    public void addPrincipalToBeScheduled(Money principalToBeScheduled) {
+        this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled);
+    }
+
+    public void setOutstandingBalance(Money outstandingBalance) {
+        this.outstandingBalance = outstandingBalance;
+    }
+
+    public void addOutstandingBalance(Money outstandingBalance) {
+        this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance);
+    }
+
+    public void reduceOutstandingBalance(Money outstandingBalance) {
+        this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance);
+    }
+
+    public void setOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
+        this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
+    }
+
+    public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
+        this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest);
+    }
+
+    public void reduceOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
+        this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest);
+    }
+
+    public int getLoanTermInDays() {
+        return this.loanTermInDays;
+    }
+
+    public void addLoanTermInDays(int loanTermInDays) {
+        this.loanTermInDays += loanTermInDays;
+    }
+
+    public Money getTotalOutstandingInterestPaymentDueToGrace() {
+        return this.totalOutstandingInterestPaymentDueToGrace;
+    }
+
+    public void setTotalOutstandingInterestPaymentDueToGrace(Money totalOutstandingInterestPaymentDueToGrace) {
+        this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
+    }
+
+    public Map<LocalDate, TreeMap<LocalDate, Money>> getCompoundingDateVariations() {
+        return this.compoundingDateVariations;
+    }
+
+    public MonetaryCurrency getCurrency() {
+        return this.currency;
+    }
+
+    public boolean applyInterestRecalculation() {
+        return this.applyInterestRecalculation;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java
new file mode 100644
index 0000000..025ef08
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java
@@ -0,0 +1,453 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
+
+import java.math.BigDecimal;
+
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object that represents a period of a loan schedule.
+ * 
+ */
+public class LoanSchedulePeriodData {
+
+    private final Integer period;
+    private final LocalDate fromDate;
+    private final LocalDate dueDate;
+    private final LocalDate obligationsMetOnDate;
+    private final Boolean complete;
+    private final Integer daysInPeriod;
+    private final BigDecimal principalDisbursed;
+    private final BigDecimal principalOriginalDue;
+    private final BigDecimal principalDue;
+    private final BigDecimal principalPaid;
+    private final BigDecimal principalWrittenOff;
+    private final BigDecimal principalOutstanding;
+    private final BigDecimal principalLoanBalanceOutstanding;
+    @SuppressWarnings("unused")
+    private final BigDecimal interestOriginalDue;
+    private final BigDecimal interestDue;
+    private final BigDecimal interestPaid;
+    private final BigDecimal interestWaived;
+    private final BigDecimal interestWrittenOff;
+    private final BigDecimal interestOutstanding;
+    private final BigDecimal feeChargesDue;
+    private final BigDecimal feeChargesPaid;
+    private final BigDecimal feeChargesWaived;
+    private final BigDecimal feeChargesWrittenOff;
+    private final BigDecimal feeChargesOutstanding;
+    private final BigDecimal penaltyChargesDue;
+    private final BigDecimal penaltyChargesPaid;
+    private final BigDecimal penaltyChargesWaived;
+    private final BigDecimal penaltyChargesWrittenOff;
+    private final BigDecimal penaltyChargesOutstanding;
+
+    @SuppressWarnings("unused")
+    private final BigDecimal totalOriginalDueForPeriod;
+    private final BigDecimal totalDueForPeriod;
+    private final BigDecimal totalPaidForPeriod;
+    private final BigDecimal totalPaidInAdvanceForPeriod;
+    private final BigDecimal totalPaidLateForPeriod;
+    private final BigDecimal totalWaivedForPeriod;
+    private final BigDecimal totalWrittenOffForPeriod;
+    private final BigDecimal totalOutstandingForPeriod;
+    private final BigDecimal totalOverdue;
+    private final BigDecimal totalActualCostOfLoanForPeriod;
+    private final BigDecimal totalInstallmentAmountForPeriod;
+
+    public static LoanSchedulePeriodData disbursementOnlyPeriod(final LocalDate disbursementDate, final BigDecimal principalDisbursed,
+            final BigDecimal feeChargesDueAtTimeOfDisbursement, final boolean isDisbursed) {
+        final Integer periodNumber = null;
+        final LocalDate from = null;
+        return new LoanSchedulePeriodData(periodNumber, from, disbursementDate, principalDisbursed, feeChargesDueAtTimeOfDisbursement,
+                isDisbursed);
+    }
+
+    public static LoanSchedulePeriodData repaymentOnlyPeriod(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
+            final BigDecimal principalDue, final BigDecimal principalOutstanding, final BigDecimal interestDueOnPrincipalOutstanding,
+            final BigDecimal feeChargesDueForPeriod, final BigDecimal penaltyChargesDueForPeriod, final BigDecimal totalDueForPeriod,
+            final BigDecimal totalInstallmentAmountForPeriod) {
+
+        return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, principalDue, principalOutstanding,
+                interestDueOnPrincipalOutstanding, feeChargesDueForPeriod, penaltyChargesDueForPeriod, totalDueForPeriod,
+                totalInstallmentAmountForPeriod);
+    }
+
+    public static LoanSchedulePeriodData repaymentPeriodWithPayments(@SuppressWarnings("unused") final Long loanId,
+            final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate, final LocalDate obligationsMetOnDate,
+            final boolean complete, final BigDecimal principalOriginalDue, final BigDecimal principalPaid,
+            final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding,
+            final BigDecimal outstandingPrincipalBalanceOfLoan, final BigDecimal interestDueOnPrincipalOutstanding,
+            final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
+            final BigDecimal interestOutstanding, final BigDecimal feeChargesDue, final BigDecimal feeChargesPaid,
+            final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff, final BigDecimal feeChargesOutstanding,
+            final BigDecimal penaltyChargesDue, final BigDecimal penaltyChargesPaid, final BigDecimal penaltyChargesWaived,
+            final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesOutstanding, final BigDecimal totalDueForPeriod,
+            final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod, final BigDecimal totalPaidLateForPeriod,
+            final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding,
+            final BigDecimal totalActualCostOfLoanForPeriod, final BigDecimal totalInstallmentAmountForPeriod) {
+
+        return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue,
+                principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
+                interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, feeChargesDue,
+                feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid,
+                penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid,
+                totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding,
+                totalActualCostOfLoanForPeriod, totalInstallmentAmountForPeriod);
+    }
+
+    public static LoanSchedulePeriodData WithPaidDetail(final LoanSchedulePeriodData loanSchedulePeriodData, final boolean complete,
+            final BigDecimal principalPaid, final BigDecimal interestPaid, final BigDecimal feeChargesPaid,
+            final BigDecimal penaltyChargesPaid) {
+
+        return new LoanSchedulePeriodData(loanSchedulePeriodData.period, loanSchedulePeriodData.fromDate, loanSchedulePeriodData.dueDate,
+                loanSchedulePeriodData.obligationsMetOnDate, complete, loanSchedulePeriodData.principalOriginalDue, principalPaid,
+                loanSchedulePeriodData.principalWrittenOff, loanSchedulePeriodData.principalOutstanding,
+                loanSchedulePeriodData.principalLoanBalanceOutstanding, loanSchedulePeriodData.interestDue, interestPaid,
+                loanSchedulePeriodData.interestWaived, loanSchedulePeriodData.interestWrittenOff,
+                loanSchedulePeriodData.interestOutstanding, loanSchedulePeriodData.feeChargesDue, feeChargesPaid,
+                loanSchedulePeriodData.feeChargesWaived, loanSchedulePeriodData.feeChargesWrittenOff,
+                loanSchedulePeriodData.feeChargesOutstanding, loanSchedulePeriodData.penaltyChargesDue, penaltyChargesPaid,
+                loanSchedulePeriodData.penaltyChargesWaived, loanSchedulePeriodData.penaltyChargesWrittenOff,
+                loanSchedulePeriodData.penaltyChargesOutstanding, loanSchedulePeriodData.totalDueForPeriod,
+                loanSchedulePeriodData.totalPaidForPeriod, loanSchedulePeriodData.totalPaidInAdvanceForPeriod,
+                loanSchedulePeriodData.totalPaidLateForPeriod, loanSchedulePeriodData.totalWaivedForPeriod,
+                loanSchedulePeriodData.totalWrittenOffForPeriod, loanSchedulePeriodData.totalOutstandingForPeriod,
+                loanSchedulePeriodData.totalActualCostOfLoanForPeriod, loanSchedulePeriodData.totalInstallmentAmountForPeriod);
+    }
+
+    /*
+     * constructor used for creating period on loan schedule that is only a
+     * disbursement (typically first period)
+     */
+    private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
+            final BigDecimal principalDisbursed, final BigDecimal chargesDueAtTimeOfDisbursement, final boolean isDisbursed) {
+        this.period = periodNumber;
+        this.fromDate = fromDate;
+        this.dueDate = dueDate;
+        this.obligationsMetOnDate = null;
+        this.complete = null;
+        if (fromDate != null) {
+            this.daysInPeriod = Days.daysBetween(this.fromDate, this.dueDate).getDays();
+        } else {
+            this.daysInPeriod = null;
+        }
+        this.principalDisbursed = principalDisbursed;
+        this.principalOriginalDue = null;
+        this.principalDue = null;
+        this.principalPaid = null;
+        this.principalWrittenOff = null;
+        this.principalOutstanding = null;
+        this.principalLoanBalanceOutstanding = principalDisbursed;
+
+        this.interestOriginalDue = null;
+        this.interestDue = null;
+        this.interestPaid = null;
+        this.interestWaived = null;
+        this.interestWrittenOff = null;
+        this.interestOutstanding = null;
+
+        this.feeChargesDue = chargesDueAtTimeOfDisbursement;
+        if (isDisbursed) {
+            this.feeChargesPaid = chargesDueAtTimeOfDisbursement;
+            this.feeChargesWaived = null;
+            this.feeChargesWrittenOff = null;
+            this.feeChargesOutstanding = null;
+        } else {
+            this.feeChargesPaid = null;
+            this.feeChargesWaived = null;
+            this.feeChargesWrittenOff = null;
+            this.feeChargesOutstanding = chargesDueAtTimeOfDisbursement;
+        }
+
+        this.penaltyChargesDue = null;
+        this.penaltyChargesPaid = null;
+        this.penaltyChargesWaived = null;
+        this.penaltyChargesWrittenOff = null;
+        this.penaltyChargesOutstanding = null;
+
+        this.totalOriginalDueForPeriod = chargesDueAtTimeOfDisbursement;
+        this.totalDueForPeriod = chargesDueAtTimeOfDisbursement;
+        this.totalPaidForPeriod = this.feeChargesPaid;
+        this.totalPaidInAdvanceForPeriod = null;
+        this.totalPaidLateForPeriod = null;
+        this.totalWaivedForPeriod = null;
+        this.totalWrittenOffForPeriod = null;
+        this.totalOutstandingForPeriod = this.feeChargesOutstanding;
+        this.totalActualCostOfLoanForPeriod = this.feeChargesDue;
+        this.totalInstallmentAmountForPeriod = null;
+        if (dueDate.isBefore(new LocalDate())) {
+            this.totalOverdue = this.totalOutstandingForPeriod;
+        } else {
+            this.totalOverdue = null;
+        }
+    }
+
+    /*
+     * used for repayment only period when creating an empty loan schedule for
+     * preview etc
+     */
+    private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
+            final BigDecimal principalOriginalDue, final BigDecimal principalOutstanding,
+            final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal feeChargesDueForPeriod,
+            final BigDecimal penaltyChargesDueForPeriod, final BigDecimal totalDueForPeriod, BigDecimal totalInstallmentAmountForPeriod) {
+        this.period = periodNumber;
+        this.fromDate = fromDate;
+        this.dueDate = dueDate;
+        this.obligationsMetOnDate = null;
+        this.complete = null;
+        if (fromDate != null) {
+            this.daysInPeriod = Days.daysBetween(this.fromDate, this.dueDate).getDays();
+        } else {
+            this.daysInPeriod = null;
+        }
+        this.principalDisbursed = null;
+        this.principalOriginalDue = principalOriginalDue;
+        this.principalDue = principalOriginalDue;
+        this.principalPaid = null;
+        this.principalWrittenOff = null;
+        this.principalOutstanding = principalOriginalDue;
+        this.principalLoanBalanceOutstanding = principalOutstanding;
+
+        this.interestOriginalDue = interestDueOnPrincipalOutstanding;
+        this.interestDue = interestDueOnPrincipalOutstanding;
+        this.interestPaid = null;
+        this.interestWaived = null;
+        this.interestWrittenOff = null;
+        this.interestOutstanding = interestDueOnPrincipalOutstanding;
+
+        this.feeChargesDue = feeChargesDueForPeriod;
+        this.feeChargesPaid = null;
+        this.feeChargesWaived = null;
+        this.feeChargesWrittenOff = null;
+        this.feeChargesOutstanding = null;
+
+        this.penaltyChargesDue = penaltyChargesDueForPeriod;
+        this.penaltyChargesPaid = null;
+        this.penaltyChargesWaived = null;
+        this.penaltyChargesWrittenOff = null;
+        this.penaltyChargesOutstanding = null;
+
+        this.totalOriginalDueForPeriod = totalDueForPeriod;
+        this.totalDueForPeriod = totalDueForPeriod;
+        this.totalPaidForPeriod = BigDecimal.ZERO;
+        this.totalPaidInAdvanceForPeriod = null;
+        this.totalPaidLateForPeriod = null;
+        this.totalWaivedForPeriod = null;
+        this.totalWrittenOffForPeriod = null;
+        this.totalOutstandingForPeriod = totalDueForPeriod;
+        this.totalActualCostOfLoanForPeriod = interestDueOnPrincipalOutstanding.add(feeChargesDueForPeriod);
+        this.totalInstallmentAmountForPeriod = totalInstallmentAmountForPeriod;
+
+        if (dueDate.isBefore(new LocalDate())) {
+            this.totalOverdue = this.totalOutstandingForPeriod;
+        } else {
+            this.totalOverdue = null;
+        }
+    }
+
+    /*
+     * Used for creating loan schedule periods with full information on expected
+     * principal, interest & charges along with what portion of each is paid.
+     */
+    private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
+            final LocalDate obligationsMetOnDate, final boolean complete, final BigDecimal principalOriginalDue,
+            final BigDecimal principalPaid, final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding,
+            final BigDecimal principalLoanBalanceOutstanding, final BigDecimal interestDueOnPrincipalOutstanding,
+            final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
+            final BigDecimal interestOutstanding, final BigDecimal feeChargesDue, final BigDecimal feeChargesPaid,
+            final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff, final BigDecimal feeChargesOutstanding,
+            final BigDecimal penaltyChargesDue, final BigDecimal penaltyChargesPaid, final BigDecimal penaltyChargesWaived,
+            final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesOutstanding, final BigDecimal totalDueForPeriod,
+            final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod, final BigDecimal totalPaidLateForPeriod,
+            final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding,
+            final BigDecimal totalActualCostOfLoanForPeriod, final BigDecimal totalInstallmentAmountForPeriod) {
+        this.period = periodNumber;
+        this.fromDate = fromDate;
+        this.dueDate = dueDate;
+        this.obligationsMetOnDate = obligationsMetOnDate;
+        this.complete = complete;
+        if (fromDate != null) {
+            this.daysInPeriod = Days.daysBetween(this.fromDate, this.dueDate).getDays();
+        } else {
+            this.daysInPeriod = null;
+        }
+        this.principalDisbursed = null;
+        this.principalOriginalDue = principalOriginalDue;
+        this.principalDue = principalOriginalDue;
+        this.principalPaid = principalPaid;
+        this.principalWrittenOff = principalWrittenOff;
+        this.principalOutstanding = principalOutstanding;
+        this.principalLoanBalanceOutstanding = principalLoanBalanceOutstanding;
+
+        this.interestOriginalDue = interestDueOnPrincipalOutstanding;
+        this.interestDue = interestDueOnPrincipalOutstanding;
+        this.interestPaid = interestPaid;
+        this.interestWaived = interestWaived;
+        this.interestWrittenOff = interestWrittenOff;
+        this.interestOutstanding = interestOutstanding;
+
+        this.feeChargesDue = feeChargesDue;
+        this.feeChargesPaid = feeChargesPaid;
+        this.feeChargesWaived = feeChargesWaived;
+        this.feeChargesWrittenOff = feeChargesWrittenOff;
+        this.feeChargesOutstanding = feeChargesOutstanding;
+
+        this.penaltyChargesDue = penaltyChargesDue;
+        this.penaltyChargesPaid = penaltyChargesPaid;
+        this.penaltyChargesWaived = penaltyChargesWaived;
+        this.penaltyChargesWrittenOff = penaltyChargesWrittenOff;
+        this.penaltyChargesOutstanding = penaltyChargesOutstanding;
+
+        this.totalOriginalDueForPeriod = totalDueForPeriod;
+        this.totalDueForPeriod = totalDueForPeriod;
+        this.totalPaidForPeriod = totalPaid;
+        this.totalPaidInAdvanceForPeriod = totalPaidInAdvanceForPeriod;
+        this.totalPaidLateForPeriod = totalPaidLateForPeriod;
+        this.totalWaivedForPeriod = totalWaived;
+        this.totalWrittenOffForPeriod = totalWrittenOff;
+        this.totalOutstandingForPeriod = totalOutstanding;
+        this.totalActualCostOfLoanForPeriod = totalActualCostOfLoanForPeriod;
+        this.totalInstallmentAmountForPeriod = totalInstallmentAmountForPeriod;
+
+        if (dueDate.isBefore(new LocalDate())) {
+            this.totalOverdue = this.totalOutstandingForPeriod;
+        } else {
+            this.totalOverdue = null;
+        }
+    }
+
+    private BigDecimal defaultToZeroIfNull(final BigDecimal possibleNullValue) {
+        BigDecimal value = BigDecimal.ZERO;
+        if (possibleNullValue != null) {
+            value = possibleNullValue;
+        }
+        return value;
+    }
+
+    public Integer periodNumber() {
+        return this.period;
+    }
+
+    public LocalDate periodFromDate() {
+        return this.fromDate;
+    }
+
+    public LocalDate periodDueDate() {
+        return this.dueDate;
+    }
+
+    public Integer daysInPeriod() {
+        return this.daysInPeriod;
+    }
+
+    public BigDecimal principalDisbursed() {
+        return defaultToZeroIfNull(this.principalDisbursed);
+    }
+
+    public BigDecimal principalDue() {
+        return defaultToZeroIfNull(this.principalDue);
+    }
+
+    public BigDecimal principalPaid() {
+        return defaultToZeroIfNull(this.principalPaid);
+    }
+
+    public BigDecimal principalWrittenOff() {
+        return defaultToZeroIfNull(this.principalWrittenOff);
+    }
+
+    public BigDecimal principalOutstanding() {
+        return defaultToZeroIfNull(this.principalOutstanding);
+    }
+
+    public BigDecimal interestDue() {
+        return defaultToZeroIfNull(this.interestDue);
+    }
+
+    public BigDecimal interestPaid() {
+        return defaultToZeroIfNull(this.interestPaid);
+    }
+
+    public BigDecimal interestWaived() {
+        return defaultToZeroIfNull(this.interestWaived);
+    }
+
+    public BigDecimal interestWrittenOff() {
+        return defaultToZeroIfNull(this.interestWrittenOff);
+    }
+
+    public BigDecimal interestOutstanding() {
+        return defaultToZeroIfNull(this.interestOutstanding);
+    }
+
+    public BigDecimal feeChargesDue() {
+        return defaultToZeroIfNull(this.feeChargesDue);
+    }
+
+    public BigDecimal feeChargesWaived() {
+        return defaultToZeroIfNull(this.feeChargesWaived);
+    }
+
+    public BigDecimal feeChargesWrittenOff() {
+        return defaultToZeroIfNull(this.feeChargesWrittenOff);
+    }
+
+    public BigDecimal feeChargesPaid() {
+        return defaultToZeroIfNull(this.feeChargesPaid);
+    }
+
+    public BigDecimal feeChargesOutstanding() {
+        return defaultToZeroIfNull(this.feeChargesOutstanding);
+    }
+
+    public BigDecimal penaltyChargesDue() {
+        return defaultToZeroIfNull(this.penaltyChargesDue);
+    }
+
+    public BigDecimal penaltyChargesWaived() {
+        return defaultToZeroIfNull(this.penaltyChargesWaived);
+    }
+
+    public BigDecimal penaltyChargesWrittenOff() {
+        return defaultToZeroIfNull(this.penaltyChargesWrittenOff);
+    }
+
+    public BigDecimal penaltyChargesPaid() {
+        return defaultToZeroIfNull(this.penaltyChargesPaid);
+    }
+
+    public BigDecimal penaltyChargesOutstanding() {
+        return defaultToZeroIfNull(this.penaltyChargesOutstanding);
+    }
+
+    public BigDecimal totalOverdue() {
+        return defaultToZeroIfNull(this.totalOverdue);
+    }
+
+    public BigDecimal principalLoanBalanceOutstanding() {
+        return this.principalLoanBalanceOutstanding;
+    }
+
+    public Boolean getComplete() {
+        return this.complete;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/OverdueLoanScheduleData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/OverdueLoanScheduleData.java
new file mode 100755
index 0000000..3103a4e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/OverdueLoanScheduleData.java
@@ -0,0 +1,85 @@
+/**

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements. See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership. The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License. You may obtain a copy of the License at

+ *

+ * http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied. See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.fineract.portfolio.loanaccount.loanschedule.data;

+

+import java.math.BigDecimal;

+

+public class OverdueLoanScheduleData {

+

+    private final Long loanId;

+    private final Long chargeId;

+    private final String locale;

+    private final BigDecimal amount;

+    private final String dateFormat;

+    private final String dueDate;

+    private final BigDecimal principalOverdue;

+    private final BigDecimal interestOverdue;

+    private final Integer periodNumber;

+

+    public OverdueLoanScheduleData(final Long loanId, final Long chargeId, final String dueDate, final BigDecimal amount,

+            final String dateFormat, final String locale, final BigDecimal principalOverdue, final BigDecimal interestOverdue,

+            final Integer periodNumber) {

+        this.loanId = loanId;

+        this.chargeId = chargeId;

+        this.dueDate = dueDate;

+        this.amount = amount;

+        this.dateFormat = dateFormat;

+        this.locale = locale;

+        this.principalOverdue = principalOverdue;

+        this.interestOverdue = interestOverdue;

+        this.periodNumber = periodNumber;

+    }

+

+    public Long getLoanId() {

+        return this.loanId;

+    }

+

+    public Long getChargeId() {

+        return this.chargeId;

+    }

+

+    public String getDueDate() {

+        return this.dueDate;

+    }

+

+    public BigDecimal getAmount() {

+        return this.amount;

+    }

+

+    public String getDateFormat() {

+        return this.dateFormat;

+    }

+

+    public String getLocale() {

+        return this.locale;

+    }

+

+

+    public Integer getPeriodNumber() {

+        return this.periodNumber;

+    }

+    

+    @Override

+    public String toString() {

+        return "{" + "chargeId:" + this.chargeId + ", locale:'" + this.locale + '\'' + ", amount:" + this.amount + ", dateFormat:'"

+                + this.dateFormat + '\'' + ", dueDate:'" + this.dueDate + '\'' + ", principal:'" + this.principalOverdue + '\''

+                + ", interest:'" + this.interestOverdue + '\'' + '}';

+    }

+

+}

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
new file mode 100644
index 0000000..1556329
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -0,0 +1,2665 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+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.domain.RepaymentRescheduleType;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+
+public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGenerator {
+
+    private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
+    private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
+
+    @Override
+    public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
+            final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
+        final LoanScheduleParams loanScheduleRecalculationDTO = null;
+        return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, loanScheduleRecalculationDTO);
+    }
+
+    private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
+            final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final LoanScheduleParams loanScheduleParams) {
+
+        final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
+        // generate list of proposed schedule due dates
+        LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
+        LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations().fetchLoanTermDueDateVariationsData(
+                loanEndDate);
+        if (lastDueDateVariation != null) {
+            loanEndDate = lastDueDateVariation.getDateValue();
+        }
+        loanApplicationTerms.updateLoanEndDate(loanEndDate);
+
+        // determine the total charges due at time of disbursement
+        final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
+
+        // setup variables for tracking important facts required for loan
+        // schedule generation.
+
+        final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
+        final int numberOfRepayments = loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions();
+
+        LoanScheduleParams scheduleParams = null;
+        if (loanScheduleParams == null) {
+            scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
+                    loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms));
+        } else if (!loanScheduleParams.isPartialUpdate()) {
+            scheduleParams = LoanScheduleParams
+                    .createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
+                            loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms),
+                            loanScheduleParams);
+        } else {
+            scheduleParams = loanScheduleParams;
+        }
+
+        final Collection<RecalculationDetail> transactions = scheduleParams.getRecalculationDetails();
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = scheduleParams
+                .getLoanRepaymentScheduleTransactionProcessor();
+
+        final Collection<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments,
+                loanApplicationTerms, chargesDueAtTimeOfDisbursement);
+
+        // Determine the total interest owed over the full loan for FLAT
+        // interest method .
+        final Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged(
+                this.paymentPeriodsInOneYearCalculator, mc);
+
+        boolean isFirstRepayment = true;
+        LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
+                loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+        final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
+                loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate);
+
+        if (!scheduleParams.isPartialUpdate()) {
+            // Set Fixed Principal Amount
+            updateAmortization(mc, loanApplicationTerms, scheduleParams.getPeriodNumber(), scheduleParams.getOutstandingBalance());
+
+            if (loanApplicationTerms.isMultiDisburseLoan()) {
+                // fetches the first tranche amount and also updates other
+                // tranche
+                // details to map
+                BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(), periods,
+                        chargesDueAtTimeOfDisbursement, scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation());
+                scheduleParams.setPrincipalToBeScheduled(Money.of(currency, disburseAmt));
+                loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt));
+                scheduleParams.setOutstandingBalance(Money.of(currency, disburseAmt));
+                scheduleParams.setOutstandingBalanceAsPerRest(Money.of(currency, disburseAmt));
+            }
+        }
+
+        // charges which depends on total loan interest will be added to this
+        // set and handled separately after all installments generated
+        final Set<LoanCharge> nonCompoundingCharges = seperateTotalCompoundingPercentageCharges(loanCharges);
+
+        LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+        LocalDate lastRestDate = currentDate;
+        if (loanApplicationTerms.getRestCalendarInstance() != null) {
+            lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
+        }
+
+        boolean isNextRepaymentAvailable = true;
+        Boolean extendTermForDailyRepayments = false;
+
+        if (holidayDetailDTO.getWorkingDays().getExtendTermForDailyRepayments() == true
+                && loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.DAYS
+                && loanApplicationTerms.getRepaymentEvery() == 1) {
+            holidayDetailDTO.getWorkingDays().setRepaymentReschedulingType(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue());
+            extendTermForDailyRepayments = true;
+        }
+
+        final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
+
+        // this block is to start the schedule generation from specified date
+        if (scheduleParams.isPartialUpdate()) {
+            if (loanApplicationTerms.isMultiDisburseLoan()) {
+                loanApplicationTerms.setPrincipal(scheduleParams.getPrincipalToBeScheduled());
+            }
+
+            applyLoanVariationsForPartialScheduleGenerate(loanApplicationTerms, scheduleParams, interestRates);
+
+            isFirstRepayment = false;
+        }
+        while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) {
+            LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate();
+            scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(
+                    scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO));
+            isFirstRepayment = false;
+            LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(),
+                    loanApplicationTerms, holidayDetailDTO);
+
+            // calculated interest start date for the period
+            LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
+                    scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate);
+
+            // Loan Schedule Exceptions that need to be applied for Loan Account
+            LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams,
+                    previousRepaymentDate, scheduledDueDate);
+
+            scheduledDueDate = termVariationParams.getScheduledDueDate();
+            // Updates total days in term
+            scheduleParams.addLoanTermInDays(Days.daysBetween(scheduleParams.getPeriodStartDate(), scheduledDueDate).getDays());
+            if (termVariationParams.isSkipPeriod()) {
+                continue;
+            }
+
+            if (scheduleParams.getPeriodStartDate().isAfter(scheduledDueDate)) { throw new ScheduleDateException(
+                    "Due date can't be before period start date", scheduledDueDate); }
+
+            if (!scheduleParams.getLatePaymentMap().isEmpty()) {
+                populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, currentDate, loanApplicationTerms,
+                        holidayDetailDTO, scheduleParams.getCompoundingMap(), loanCharges, currency);
+                scheduleParams.getCompoundingDateVariations().put(scheduleParams.getPeriodStartDate(),
+                        new TreeMap<>(scheduleParams.getCompoundingMap()));
+            }
+
+            if (extendTermForDailyRepayments) {
+                scheduleParams.setActualRepaymentDate(scheduledDueDate);
+            }
+
+            // this block is to generate the schedule till the specified
+            // date(used for calculating preclosure)
+            if (scheduleParams.getScheduleTillDate() != null && !scheduledDueDate.isBefore(scheduleParams.getScheduleTillDate())) {
+                scheduledDueDate = scheduleParams.getScheduleTillDate();
+                isNextRepaymentAvailable = false;
+            }
+
+            // populates the collection with transactions till the due date of
+            // the period for interest recalculation enabled loans
+            Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(
+                    scheduleParams.applyInterestRecalculation(), scheduledDueDate, transactions);
+
+            final double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
+                    .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate,
+                            loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(),
+                            loanApplicationTerms.getRepaymentEvery());
+            ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency,
+                    interestCalculationGraceOnRepaymentPeriodFraction);
+
+            if (loanApplicationTerms.isMultiDisburseLoan()) {
+                updateBalanceBasedOnDisbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods,
+                        scheduledDueDate);
+            }
+
+            // process repayments to the schedule as per the repayment
+            // transaction processor configuration
+            // will add a new schedule with interest till the transaction date
+            // for a loan repayment which falls between the
+            // two periods for interest first repayment strategies
+            handleRecalculationForNonDueDateTransactions(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, scheduleParams, periods,
+                    totalInterestChargedForFullLoanTerm, idealDisbursementDate, firstRepaymentdate, lastRestDate, scheduledDueDate,
+                    periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams);
+
+            if (currentPeriodParams.isSkipCurrentLoop()) {
+                continue;
+            }
+            periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
+                    scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate);
+
+            // backup for pre-close transaction
+            updateCompoundingDetails(scheduleParams, periodStartDateApplicableForInterest);
+
+            // 5 determine principal,interest of repayment period
+            PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
+                    this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(),
+                    scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()),
+                    scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm,
+                    scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(),
+                    loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams),
+                    scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, scheduledDueDate, interestRates);
+
+            // will check for EMI amount greater than interest calculated
+            if (loanApplicationTerms.getFixedEmiAmount() != null
+                    && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) == -1) {
+                String errorMsg = "EMI amount must be greater than : " + principalInterestForThisPeriod.interest().getAmount();
+                throw new MultiDisbursementEmiAmountException(errorMsg, principalInterestForThisPeriod.interest().getAmount(),
+                        loanApplicationTerms.getFixedEmiAmount());
+            }
+
+            // update cumulative fields for principal & interest
+            currentPeriodParams.setInterestForThisPeriod(principalInterestForThisPeriod.interest());
+            Money lastTotalOutstandingInterestPaymentDueToGrace = scheduleParams.getTotalOutstandingInterestPaymentDueToGrace();
+            scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(principalInterestForThisPeriod.interestPaymentDueToGrace());
+            currentPeriodParams.setPrincipalForThisPeriod(principalInterestForThisPeriod.principal());
+
+            // applies early payments on principal portion
+            updatePrincipalPortionBasedOnPreviousEarlyPayments(currency, scheduleParams, currentPeriodParams);
+
+            // updates amounts with current earlyPaidAmount
+            updateAmountsBasedOnCurrentEarlyPayments(mc, loanApplicationTerms, scheduleParams, currentPeriodParams);
+
+            if (scheduleParams.getOutstandingBalance().isLessThanZero() || !isNextRepaymentAvailable) {
+                currentPeriodParams.plusPrincipalForThisPeriod(scheduleParams.getOutstandingBalance());
+                scheduleParams.setOutstandingBalance(Money.zero(currency));
+            }
+
+            if (!isNextRepaymentAvailable) {
+                scheduleParams.getDisburseDetailMap().clear();
+            }
+
+            // applies charges for the period
+            applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams);
+
+            // sum up real totalInstallmentDue from components
+            final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod();
+
+            // if previous installment is last then add interest to same
+            // installment
+            if (currentPeriodParams.getLastInstallment() != null && currentPeriodParams.getPrincipalForThisPeriod().isZero()) {
+                currentPeriodParams.getLastInstallment().addInterestAmount(currentPeriodParams.getInterestForThisPeriod());
+                continue;
+            }
+
+            // create repayment period from parts
+            LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
+                    scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(),
+                    scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
+                    currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
+                    totalInstallmentDue, false);
+
+            // apply loan transactions on installments to identify early/late
+            // payments for interest recalculation
+            installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams,
+                    loanRepaymentScheduleTransactionProcessor, totalInterestChargedForFullLoanTerm, lastRestDate, scheduledDueDate,
+                    periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams,
+                    lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges);
+            periods.add(installment);
+
+            // Updates principal paid map with efective date for reducing
+            // the amount from outstanding balance(interest calculation)
+            updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams,
+                    installment);
+
+            // handle cumulative fields
+
+            scheduleParams.addTotalCumulativePrincipal(currentPeriodParams.getPrincipalForThisPeriod());
+            scheduleParams.addTotalRepaymentExpected(totalInstallmentDue);
+            scheduleParams.addTotalCumulativeInterest(currentPeriodParams.getInterestForThisPeriod());
+            scheduleParams.setPeriodStartDate(scheduledDueDate);
+            scheduleParams.incrementInstalmentNumber();
+            scheduleParams.incrementPeriodNumber();
+            scheduleParams.getCompoundingDateVariations().clear();
+            if (termVariationParams.isRecalculateAmounts()) {
+                loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null);
+                loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null);
+                adjustInstallmentOrPrincipalAmount(loanApplicationTerms, scheduleParams.getTotalCumulativePrincipal(),
+                        scheduleParams.getPeriodNumber(), mc);
+            }
+        }
+
+        // this condition is to add the interest from grace period if not
+        // already applied.
+        if (scheduleParams.getTotalOutstandingInterestPaymentDueToGrace().isGreaterThanZero()) {
+            LoanScheduleModelPeriod installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
+            installment.addInterestAmount(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace());
+            scheduleParams.addTotalRepaymentExpected(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace());
+            scheduleParams.addTotalCumulativeInterest(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace());
+            scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(Money.zero(currency));
+        }
+
+        // determine fees and penalties for charges which depends on total
+        // loan interest
+        updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges);
+
+        // this block is to add extra re-payment schedules with interest portion
+        // if the loan not paid with in loan term
+
+        if (scheduleParams.getScheduleTillDate() != null) {
+            currentDate = scheduleParams.getScheduleTillDate();
+        }
+        if (scheduleParams.applyInterestRecalculation() && scheduleParams.getLatePaymentMap().size() > 0
+                && currentDate.isAfter(scheduleParams.getPeriodStartDate())) {
+            Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentdate(mc, loanApplicationTerms, holidayDetailDTO, currency,
+                    periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges, scheduleParams);
+            scheduleParams.addTotalCumulativeInterest(totalInterest);
+        }
+
+        loanApplicationTerms.resetFixedEmiAmount();
+        final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
+        final BigDecimal totalOutstanding = BigDecimal.ZERO;
+
+        return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(),
+                scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativePrincipal().getAmount(), totalPrincipalPaid,
+                scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(),
+                scheduleParams.getTotalPenaltyChargesCharged().getAmount(), scheduleParams.getTotalRepaymentExpected().getAmount(),
+                totalOutstanding);
+    }
+
+    private void applyChargesForCurrentPeriod(final Set<LoanCharge> loanCharges, final MonetaryCurrency currency,
+            LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams) {
+        PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(),
+                currentPeriodParams.getInterestForThisPeriod(), null);
+        currentPeriodParams.setFeeChargesForInstallment(cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(),
+                scheduledDueDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
+                scheduleParams.getTotalCumulativeInterest(), true));
+        currentPeriodParams.setPenaltyChargesForInstallment(cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(),
+                scheduledDueDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
+                scheduleParams.getTotalCumulativeInterest(), true));
+        scheduleParams.addTotalFeeChargesCharged(currentPeriodParams.getFeeChargesForInstallment());
+        scheduleParams.addTotalPenaltyChargesCharged(currentPeriodParams.getPenaltyChargesForInstallment());
+    }
+
+    private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams,
+            final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges) {
+        for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
+            if (loanScheduleModelPeriod.isRepaymentPeriod()) {
+                PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()),
+                        Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
+                Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
+                        loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
+                        scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
+                        !loanScheduleModelPeriod.isRecalculatedInterestComponent());
+                Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
+                        loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
+                        scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
+                        !loanScheduleModelPeriod.isRecalculatedInterestComponent());
+                scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment);
+                scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment);
+                scheduleParams.addTotalRepaymentExpected(feeChargesForInstallment.plus(penaltyChargesForInstallment));
+                loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount());
+            }
+        }
+    }
+
+    private void updateAmountsWithEffectiveDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
+            LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
+            LoanScheduleModelPeriod installment) {
+        LocalDate amountApplicableDate = installment.periodDueDate();
+        if (loanApplicationTerms.isInterestRecalculationEnabled()) {
+            amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO);
+        }
+        updateMapWithAmount(scheduleParams.getPrincipalPortionMap(),
+                currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()), amountApplicableDate);
+
+        // update outstanding balance for interest calculation
+        updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate);
+    }
+
+    private LoanScheduleModelPeriod handleRecalculationForTransactions(final MathContext mc,
+            final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency,
+            final LoanScheduleParams scheduleParams,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            final Money totalInterestChargedForFullLoanTerm, final LocalDate lastRestDate, final LocalDate scheduledDueDate,
+            final LocalDate periodStartDateApplicableForInterest, final Collection<RecalculationDetail> applicableTransactions,
+            final ScheduleCurrentPeriodParams currentPeriodParams, final Money lastTotalOutstandingInterestPaymentDueToGrace,
+            final LoanScheduleModelPeriod installment, Set<LoanCharge> loanCharges) {
+        LoanScheduleModelPeriod modifiedInstallment = installment;
+        if (scheduleParams.applyInterestRecalculation() && loanRepaymentScheduleTransactionProcessor != null) {
+            Money principalProcessed = Money.zero(currency);
+            addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), modifiedInstallment);
+            for (RecalculationDetail detail : applicableTransactions) {
+                if (!detail.isProcessed()) {
+                    LocalDate transactionDate = detail.getTransactionDate();
+                    List<LoanTransaction> currentTransactions = new ArrayList<>(2);
+                    currentTransactions.add(detail.getTransaction());
+                    // applies the transaction as per transaction strategy
+                    // on scheduled installments to identify the
+                    // unprocessed(early payment ) amounts
+                    Money unprocessed = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency,
+                            scheduleParams.getInstallments());
+
+                    if (unprocessed.isGreaterThanZero()) {
+                        scheduleParams.reduceOutstandingBalance(unprocessed);
+                        // pre closure check and processing
+                        modifiedInstallment = handlePrepaymentOfLoan(mc, loanApplicationTerms, holidayDetailDTO, scheduleParams,
+                                totalInterestChargedForFullLoanTerm, scheduledDueDate, periodStartDateApplicableForInterest,
+                                currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), currentPeriodParams,
+                                lastTotalOutstandingInterestPaymentDueToGrace, transactionDate, modifiedInstallment, loanCharges);
+
+                        Money addToPrinciapal = Money.zero(currency);
+                        if (scheduleParams.getOutstandingBalance().isLessThanZero()) {
+                            addToPrinciapal = addToPrinciapal.plus(scheduleParams.getOutstandingBalance());
+                            scheduleParams.setOutstandingBalance(Money.zero(currency));
+                        }
+                        updateAmountsBasedOnEarlyPayment(loanApplicationTerms, holidayDetailDTO, scheduleParams, modifiedInstallment,
+                                detail, unprocessed, addToPrinciapal);
+
+                        scheduleParams.addReducePrincipal(unprocessed);
+                        currentPeriodParams.plusPrincipalForThisPeriod(unprocessed.plus(addToPrinciapal));
+                        principalProcessed = principalProcessed.plus(unprocessed.plus(addToPrinciapal));
+                        BigDecimal fixedEmiAmount = loanApplicationTerms.getFixedEmiAmount();
+                        scheduleParams.setReducePrincipal(applyEarlyPaymentStrategy(
+                                loanApplicationTerms,
+                                scheduleParams.getReducePrincipal(),
+                                scheduleParams.getTotalCumulativePrincipal().plus(
+                                        currentPeriodParams.getPrincipalForThisPeriod().minus(principalProcessed)),
+                                scheduleParams.getPeriodNumber() + 1, mc));
+                        if (loanApplicationTerms.getAmortizationMethod().isEqualInstallment()
+                                && fixedEmiAmount.compareTo(loanApplicationTerms.getFixedEmiAmount()) != 0) {
+                            currentPeriodParams.setEmiAmountChanged(true);
+                        }
+
+                    }
+                }
+            }
+            updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(), scheduledDueDate,
+                    scheduleParams.getInstallments(), true, lastRestDate, scheduleParams.getCompoundingMap());
+            currentPeriodParams.minusPrincipalForThisPeriod(principalProcessed);
+        }
+        return modifiedInstallment;
+    }
+
+    private LoanScheduleModelPeriod handlePrepaymentOfLoan(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
+            final HolidayDetailDTO holidayDetailDTO, final LoanScheduleParams scheduleParams,
+            final Money totalInterestChargedForFullLoanTerm, final LocalDate scheduledDueDate,
+            LocalDate periodStartDateApplicableForInterest, final double interestCalculationGraceOnRepaymentPeriodFraction,
+            final ScheduleCurrentPeriodParams currentPeriodParams, final Money lastTotalOutstandingInterestPaymentDueToGrace,
+            final LocalDate transactionDate, final LoanScheduleModelPeriod installment, Set<LoanCharge> loanCharges) {
+        LoanScheduleModelPeriod modifiedInstallment = installment;
+        if (!scheduleParams.getOutstandingBalance().isGreaterThan(currentPeriodParams.getInterestForThisPeriod())
+                && !scheduledDueDate.equals(transactionDate)) {
+            final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
+            LocalDate calculateTill = transactionDate;
+            if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) {
+                calculateTill = getNextRestScheduleDate(calculateTill.minusDays(1), loanApplicationTerms, holidayDetailDTO);
+            }
+            if (scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) {
+                scheduleParams.getCompoundingMap().clear();
+                scheduleParams.getCompoundingMap().putAll(
+                        scheduleParams.getCompoundingDateVariations().get(periodStartDateApplicableForInterest));
+            }
+            if (currentPeriodParams.isEmiAmountChanged()) {
+                updateFixedInstallmentAmount(mc, loanApplicationTerms, scheduleParams.getPeriodNumber(), loanApplicationTerms
+                        .getPrincipal().minus(scheduleParams.getTotalCumulativePrincipal()));
+            }
+            PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod(this.paymentPeriodsInOneYearCalculator,
+                    interestCalculationGraceOnRepaymentPeriodFraction, scheduleParams.getTotalCumulativePrincipal(),
+                    scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm,
+                    lastTotalOutstandingInterestPaymentDueToGrace, scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms,
+                    scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), scheduleParams.getCompoundingMap(),
+                    periodStartDateApplicableForInterest, calculateTill, interestRates);
+            // applies charges for the period
+            final ScheduleCurrentPeriodParams tempPeriod = new ScheduleCurrentPeriodParams(
+                    totalInterestChargedForFullLoanTerm.getCurrency(), interestCalculationGraceOnRepaymentPeriodFraction);
+            tempPeriod.setInterestForThisPeriod(interestTillDate.interest());
+            applyChargesForCurrentPeriod(loanCharges, totalInterestChargedForFullLoanTerm.getCurrency(), scheduleParams, calculateTill,
+                    tempPeriod);
+            Money interestDiff = currentPeriodParams.getInterestForThisPeriod().minus(tempPeriod.getInterestForThisPeriod());
+            Money chargeDiff = currentPeriodParams.getFeeChargesForInstallment().minus(tempPeriod.getFeeChargesForInstallment());
+            Money penaltyDiff = currentPeriodParams.getPenaltyChargesForInstallment().minus(tempPeriod.getPenaltyChargesForInstallment());
+
+            Money diff = interestDiff.plus(chargeDiff).plus(penaltyDiff);
+            if (!scheduleParams.getOutstandingBalance().minus(diff).isGreaterThanZero()) {
+                scheduleParams.reduceOutstandingBalance(diff);
+                currentPeriodParams.minusInterestForThisPeriod(interestDiff);
+                currentPeriodParams.minusFeeChargesForInstallment(chargeDiff);
+                currentPeriodParams.minusPenaltyChargesForInstallment(penaltyDiff);
+                currentPeriodParams.plusPrincipalForThisPeriod(diff);
+
+                // create and replaces repayment period
+                // from parts
+                modifiedInstallment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
+                        scheduleParams.getPeriodStartDate(), transactionDate, currentPeriodParams.getPrincipalForThisPeriod(),
+                        scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
+                        currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
+                        currentPeriodParams.fetchTotalAmountForPeriod(), false);
+                scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(interestTillDate.interestPaymentDueToGrace());
+            }
+
+        }
+        return modifiedInstallment;
+    }
+
+    private void updateAmountsBasedOnCurrentEarlyPayments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
+            LoanScheduleParams scheduleParams, ScheduleCurrentPeriodParams currentPeriodParams) {
+        currentPeriodParams.setReducedBalance(currentPeriodParams.getEarlyPaidAmount());
+        currentPeriodParams.minusEarlyPaidAmount(currentPeriodParams.getPrincipalForThisPeriod());
+        if (currentPeriodParams.getEarlyPaidAmount().isGreaterThanZero()) {
+            scheduleParams.addReducePrincipal(currentPeriodParams.getEarlyPaidAmount());
+            BigDecimal fixedEmiAmount = loanApplicationTerms.getFixedEmiAmount();
+            scheduleParams.setReducePrincipal(applyEarlyPaymentStrategy(
+                    loanApplicationTerms,
+                    scheduleParams.getReducePrincipal(),
+                    scheduleParams.getTotalCumulativePrincipal().plus(currentPeriodParams.getPrincipalForThisPeriod())
+                            .plus(currentPeriodParams.getEarlyPaidAmount()), scheduleParams.getPeriodNumber() + 1, mc));
+            if (loanApplicationTerms.getAmortizationMethod().isEqualInstallment()
+                    && fixedEmiAmount.compareTo(loanApplicationTerms.getFixedEmiAmount()) != 0) {
+                currentPeriodParams.setEmiAmountChanged(true);
+            }
+            currentPeriodParams.plusPrincipalForThisPeriod(currentPeriodParams.getEarlyPaidAmount());
+        }
+
+        // update outstandingLoanBlance using current period
+        // 'principalDue'
+        scheduleParams.reduceOutstandingBalance(currentPeriodParams.getPrincipalForThisPeriod().minus(
+                currentPeriodParams.getReducedBalance()));
+    }
+
+    private void updatePrincipalPortionBasedOnPreviousEarlyPayments(final MonetaryCurrency currency,
+            final LoanScheduleParams scheduleParams, final ScheduleCurrentPeriodParams currentPeriodParams) {
+        if (currentPeriodParams.getPrincipalForThisPeriod().isGreaterThan(scheduleParams.getReducePrincipal())) {
+            currentPeriodParams.minusPrincipalForThisPeriod(scheduleParams.getReducePrincipal());
+            scheduleParams.setReducePrincipal(Money.zero(currency));
+        } else {
+            scheduleParams.reduceReducePrincipal(currentPeriodParams.getPrincipalForThisPeriod());
+            currentPeriodParams.setPrincipalForThisPeriod(Money.zero(currency));
+        }
+    }
+
+    private void updateCompoundingDetails(LoanScheduleParams scheduleParams, LocalDate periodStartDateApplicableForInterest) {
+        if (scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) {
+            scheduleParams.getCompoundingMap().clear();
+            scheduleParams.getCompoundingMap().putAll(
+                    scheduleParams.getCompoundingDateVariations().get(periodStartDateApplicableForInterest));
+        } else {
+            scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest,
+                    new TreeMap<>(scheduleParams.getCompoundingMap()));
+        }
+    }
+
+    private void handleRecalculationForNonDueDateTransactions(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
+            final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, LoanScheduleParams scheduleParams,
+            final Collection<LoanScheduleModelPeriod> periods, final Money totalInterestChargedForFullLoanTerm,
+            final LocalDate idealDisbursementDate, LocalDate firstRepaymentdate, final LocalDate lastRestDate,
+            final LocalDate scheduledDueDate, final LocalDate periodStartDateForInterest,
+            final Collection<RecalculationDetail> applicableTransactions, final ScheduleCurrentPeriodParams currentPeriodParams) {
+        if (scheduleParams.applyInterestRecalculation()) {
+            final MonetaryCurrency currency = scheduleParams.getCurrency();
+            final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
+            boolean checkForOutstanding = true;
+            List<RecalculationDetail> unprocessedTransactions = new ArrayList<>();
+            LoanScheduleModelPeriod installment = null;
+            LocalDate periodStartDateApplicableForInterest = periodStartDateForInterest;
+            for (RecalculationDetail detail : applicableTransactions) {
+                if (detail.isProcessed()) {
+                    continue;
+                }
+                boolean updateLatePaymentMap = false;
+                final LocalDate transactionDate = detail.getTransactionDate();
+                if (transactionDate.isBefore(scheduledDueDate)) {
+                    if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null
+                            && scheduleParams.getLoanRepaymentScheduleTransactionProcessor()
+                                    .isInterestFirstRepaymentScheduleTransactionProcessor()) {
+                        List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail);
+                        if (!transactionDate.isEqual(scheduleParams.getPeriodStartDate()) || scheduleParams.getInstalmentNumber() == 1) {
+
+                            int periodDays = Days.daysBetween(scheduleParams.getPeriodStartDate(), transactionDate).getDays();
+                            // calculates period start date for interest
+                            // calculation as per the configuration
+                            periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
+                                    scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate);
+
+                            int daysInPeriodApplicable = Days.daysBetween(periodStartDateApplicableForInterest, transactionDate).getDays();
+                            Money interestForThisinstallment = Money.zero(currency);
+                            if (daysInPeriodApplicable > 0) {
+                                // 5 determine interest till the transaction
+                                // date
+                                if (!scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) {
+                                    scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest,
+                                            new TreeMap<>(scheduleParams.getCompoundingMap()));
+                                }
+                                PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
+                                        this.paymentPeriodsInOneYearCalculator,
+                                        currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams
+                                                .getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()),
+                                        scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm,
+                                        scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(),
+                                        scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms,
+                                        scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams),
+                                        scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, transactionDate,
+                                        interestRates);
+                                interestForThisinstallment = principalInterestForThisPeriod.interest();
+
+                                scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(principalInterestForThisPeriod
+                                        .interestPaymentDueToGrace());
+                            }
+
+                            Money principalForThisPeriod = Money.zero(currency);
+
+                            // applies all the applicable charges to the
+                            // newly
+                            // created installment
+                            PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod, interestForThisinstallment,
+                                    null);
+                            Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(),
+                                    transactionDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
+                                    scheduleParams.getTotalCumulativeInterest(), false);
+                            Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(),
+                                    transactionDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
+                                    scheduleParams.getTotalCumulativeInterest(), false);
+
+                            // sum up real totalInstallmentDue from
+                            // components
+                            final Money totalInstallmentDue = principalForThisPeriod.plus(interestForThisinstallment)
+                                    .plus(feeChargesForInstallment).plus(penaltyChargesForInstallment);
+                            // create repayment period from parts
+                            installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
+                                    scheduleParams.getPeriodStartDate(), transactionDate, principalForThisPeriod,
+                                    scheduleParams.getOutstandingBalance(), interestForThisinstallment, feeChargesForInstallment,
+                                    penaltyChargesForInstallment, totalInstallmentDue, true);
+                            periods.add(installment);
+
+                            // update outstanding balance for interest
+                            // calculation as per the rest
+                            updateOutstandingBalanceAsPerRest(scheduleParams, transactionDate);
+
+                            // handle cumulative fields
+                            scheduleParams.addLoanTermInDays(periodDays);
+                            scheduleParams.addTotalRepaymentExpected(totalInstallmentDue);
+                            scheduleParams.addTotalCumulativeInterest(interestForThisinstallment);
+                            scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment);
+                            scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment);
+
+                            scheduleParams.setPeriodStartDate(transactionDate);
+                            periodStartDateApplicableForInterest = scheduleParams.getPeriodStartDate();
+                            updateLatePaymentMap = true;
+                            scheduleParams.incrementInstalmentNumber();
+                            // creates and insert Loan repayment schedule
+                            // for
+                            // the period
+                            addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
+                        } else if (installment == null) {
+                            installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
+                        }
+                        // applies the transaction as per transaction
+                        // strategy
+                        // on scheduled installments to identify the
+                        // unprocessed(early payment ) amounts
+                        Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor().handleRepaymentSchedule(
+                                currentTransactions, currency, scheduleParams.getInstallments());
+                        if (unprocessed.isGreaterThanZero()) {
+
+                            if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) {
+                                LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms,
+                                        holidayDetailDTO);
+                                checkForOutstanding = transactionDate.isEqual(applicableDate);
+
+                            }
+                            // reduces actual outstanding balance
+                            scheduleParams.reduceOutstandingBalance(unprocessed);
+                            // if outstanding balance becomes less than zero
+                            // then adjusts the princiapal
+                            Money addToPrinciapal = Money.zero(currency);
+                            if (!scheduleParams.getOutstandingBalance().isGreaterThanZero()) {
+                                addToPrinciapal = addToPrinciapal.plus(scheduleParams.getOutstandingBalance());
+                                scheduleParams.setOutstandingBalance(Money.zero(currency));
+                                currentPeriodParams.setLastInstallment(installment);
+                            }
+                            // updates principal portion map with the early
+                            // payment amounts and applicable date as per
+                            // rest
+                            updateAmountsBasedOnEarlyPayment(loanApplicationTerms, holidayDetailDTO, scheduleParams, installment, detail,
+                                    unprocessed, addToPrinciapal);
+
+                            // method applies early payment strategy
+                            scheduleParams.addReducePrincipal(unprocessed);
+                            scheduleParams.setReducePrincipal(applyEarlyPaymentStrategy(loanApplicationTerms,
+                                    scheduleParams.getReducePrincipal(), scheduleParams.getTotalCumulativePrincipal(),
+                                    scheduleParams.getPeriodNumber(), mc));
+                        }
+                        // identify late payments and add compounding
+                        // details to
+                        // map for interest calculation
+                        handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate,
+                                periodStartDateApplicableForInterest, detail);
+                        if (updateLatePaymentMap) {
+                            updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(),
+                                    scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate,
+                                    scheduleParams.getCompoundingMap());
+                        }
+                    } else if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null) {
+                        LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms,
+                                holidayDetailDTO);
+                        if (applicableDate.isBefore(scheduledDueDate)) {
+                            List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail);
+                            Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor().handleRepaymentSchedule(
+                                    currentTransactions, currency, scheduleParams.getInstallments());
+                            Money arrears = fetchCompoundedArrears(loanApplicationTerms, currency, detail.getTransaction());
+                            if (unprocessed.isGreaterThanZero()) {
+                                arrears = getTotalAmount(scheduleParams.getLatePaymentMap(), currency);
+                                updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed, applicableDate);
+                                currentPeriodParams.plusEarlyPaidAmount(unprocessed);
+
+                                // this check is to identify pre-closure and
+                                // apply interest calculation as per
+                                // configuration for non due date payments
+                                if (!scheduleParams.getOutstandingBalance().isGreaterThan(unprocessed)
+                                        && !loanApplicationTerms.getPreClosureInterestCalculationStrategy()
+                                                .calculateTillRestFrequencyEnabled()) {
+
+                                    LocalDate calculateTill = transactionDate;
+                                    if (!scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) {
+                                        scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest,
+                                                new TreeMap<>(scheduleParams.getCompoundingMap()));
+                                    }
+                                    PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
+                                            this.paymentPeriodsInOneYearCalculator,
+                                            currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams
+                                                    .getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()),
+                                            scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm,
+                                            scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(),
+                                            scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms,
+                                            scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams),
+                                            scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, calculateTill,
+                                            interestRates);
+                                    if (!principalInterestForThisPeriod.interest()
+                                            .plus(principalInterestForThisPeriod.interestPaymentDueToGrace())
+                                            .plus(scheduleParams.getOutstandingBalance()).isGreaterThan(unprocessed)) {
+                                        currentPeriodParams.minusEarlyPaidAmount(unprocessed);
+                                        updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed.negated(), applicableDate);
+                                        LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null,
+                                                transactionDate, null, DateUtils.getLocalDateTimeOfTenant(), null);
+                                        RecalculationDetail recalculationDetail = new RecalculationDetail(transactionDate, loanTransaction);
+                                        unprocessedTransactions.add(recalculationDetail);
+                                        break;
+                                    }
+                                }
+                                LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null, scheduledDueDate,
+                                        null, DateUtils.getLocalDateTimeOfTenant(), null);
+                                RecalculationDetail recalculationDetail = new RecalculationDetail(scheduledDueDate, loanTransaction);
+                                unprocessedTransactions.add(recalculationDetail);
+                                checkForOutstanding = false;
+
+                                scheduleParams.reduceOutstandingBalance(unprocessed);
+                                // if outstanding balance becomes less than
+                                // zero
+                                // then adjusts the princiapal
+                                Money addToPrinciapal = Money.zero(currency);
+                                if (scheduleParams.getOutstandingBalance().isLessThanZero()) {
+                                    addToPrinciapal = addToPrinciapal.plus(scheduleParams.getOutstandingBalance());
+                                    scheduleParams.setOutstandingBalance(Money.zero(currency));
+                                    updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), addToPrinciapal, applicableDate);
+                                    currentPeriodParams.plusEarlyPaidAmount(addToPrinciapal);
+                                }
+
+                            }
+                            if (arrears.isGreaterThanZero() && applicableDate.isBefore(lastRestDate)) {
+                                handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate,
+                                        periodStartDateApplicableForInterest, detail);
+                            }
+                        }
+
+                    }
+                }
+
+            }
+            applicableTransactions.addAll(unprocessedTransactions);
+            if (checkForOutstanding && scheduleParams.getOutstandingBalance().isZero() && scheduleParams.getDisburseDetailMap().isEmpty()) {
+                currentPeriodParams.setSkipCurrentLoop(true);
+            }
+        }
+    }
+
+    /**
+     * @param loanApplicationTerms
+     * @param holidayDetailDTO
+     * @param currency
+     * @param scheduleParams
+     * @param lastRestDate
+     * @param periodStartDateApplicableForInterest
+     * @param detail
+     */
+    private void handleLatePayments(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
+            final MonetaryCurrency currency, LoanScheduleParams scheduleParams, LocalDate lastRestDate,
+            LocalDate periodStartDateApplicableForInterest, RecalculationDetail detail) {
+        updateLatePaidAmountsToPrincipalMap(detail.getTransaction(), loanApplicationTerms, currency, holidayDetailDTO, lastRestDate,
+                scheduleParams);
+        scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest,
+                new TreeMap<>(scheduleParams.getCompoundingMap()));
+    }
+
+    private void updateAmountsBasedOnEarlyPayment(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
+            LoanScheduleParams scheduleParams, final LoanScheduleModelPeriod installment, RecalculationDetail detail, Money unprocessed,
+            Money addToPrinciapal) {
+        updatePrincipalPaidPortionToMap(loanApplicationTerms, holidayDetailDTO, scheduleParams.getPrincipalPortionMap(), installment,
+                detail, unprocessed.plus(addToPrinciapal), scheduleParams.getInstallments());
+        scheduleParams.addTotalRepaymentExpected(unprocessed.plus(addToPrinciapal));
+        scheduleParams.addTotalCumulativePrincipal(unprocessed.plus(addToPrinciapal));
+    }
+
+    private void updateOutstandingBalanceAsPerRest(final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) {
+        scheduleParams.setOutstandingBalanceAsPerRest(updateBalanceForInterestCalculation(scheduleParams.getPrincipalPortionMap(),
+                scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest(), false));
+        scheduleParams.setOutstandingBalanceAsPerRest(updateBalanceForInterestCalculation(scheduleParams.getDisburseDetailMap(),
+                scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest(), true));
+    }
+
+    /**
+     * Method updates outstanding balance of the loan for interest calculation
+     * 
+     */
+    private void updateBalanceBasedOnDisbursement(final LoanApplicationTerms loanApplicationTerms,
+            final BigDecimal chargesDueAtTimeOfDisbursement, LoanScheduleParams scheduleParams,
+            final Collection<LoanScheduleModelPeriod> periods, final LocalDate scheduledDueDate) {
+        for (Map.Entry<LocalDate, Money> disburseDetail : scheduleParams.getDisburseDetailMap().entrySet()) {
+            if (disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate()) && !disburseDetail.getKey().isAfter(scheduledDueDate)) {
+                // validation check for amount not exceeds specified max
+                // amount as per the configuration
+                if (loanApplicationTerms.getMaxOutstandingBalance() != null
+                        && scheduleParams.getOutstandingBalance().plus(disburseDetail.getValue())
+                                .isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) {
+                    String errorMsg = "Outstanding balance must not exceed the amount: " + loanApplicationTerms.getMaxOutstandingBalance();
+                    throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance()
+                            .getAmount(), disburseDetail.getValue());
+                }
+
+                // creates and add disbursement detail to the repayments
+                // period
+                final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
+                        disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
+                periods.add(disbursementPeriod);
+                // updates actual outstanding balance with new
+                // disbursement detail
+                scheduleParams.addOutstandingBalance(disburseDetail.getValue());
+                scheduleParams.addPrincipalToBeScheduled(disburseDetail.getValue());
+                loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseDetail.getValue()));
+            }
+        }
+    }
+
+    /**
+     * @param loanApplicationTerms
+     * @param scheduleParams
+     * @param priviousScheduledDueDate
+     * @param previousRepaymentDate
+     * @param scheduledDueDate
+     * @param scheduleDateForReversal
+     * @return
+     */
+    private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms,
+            final LoanScheduleParams scheduleParams, final LocalDate previousRepaymentDate, final LocalDate scheduledDueDate) {
+        boolean skipPeriod = false;
+        boolean recalculateAmounts = false;
+        LocalDate modifiedScheduledDueDate = scheduledDueDate;
+
+        // due date changes should be applied only for that dueDate
+        if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) {
+            LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
+            if (loanTermVariationsData.getTermApplicableFrom().isEqual(modifiedScheduledDueDate)) {
+                modifiedScheduledDueDate = loanTermVariationsData.getDateValue();
+                if (!loanTermVariationsData.isSpecificToInstallment()) {
+                    scheduleParams.setActualRepaymentDate(modifiedScheduledDueDate);
+                }
+                loanTermVariationsData.setProcessed(true);
+            }
+        }
+
+        while (loanApplicationTerms.getLoanTermVariations().hasVariation(modifiedScheduledDueDate)) {
+            LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextVariation();
+            if (loanTermVariationsData.isProcessed()) {
+                continue;
+            }
+            switch (loanTermVariationsData.getTermVariationType()) {
+                case INSERT_INSTALLMENT:
+                    scheduleParams.setActualRepaymentDate(previousRepaymentDate);
+                    modifiedScheduledDueDate = loanTermVariationsData.getTermApplicableFrom();
+                    if (loanTermVariationsData.getDecimalValue() != null) {
+                        if (loanApplicationTerms.getInterestMethod().isDecliningBalnce()
+                                && loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) {
+                            loanApplicationTerms.setCurrentPeriodFixedEmiAmount(loanTermVariationsData.getDecimalValue());
+                        } else {
+                            loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(loanTermVariationsData.getDecimalValue());
+                        }
+                        recalculateAmounts = true;
+                    }
+                    loanTermVariationsData.setProcessed(true);
+                break;
+                case DELETE_INSTALLMENT:
+                    if (loanTermVariationsData.getTermApplicableFrom().isEqual(modifiedScheduledDueDate)) {
+                        skipPeriod = true;
+                        loanTermVariationsData.setProcessed(true);
+                    }
+                break;
+                case EMI_AMOUNT:
+                    if (loanTermVariationsData.isSpecificToInstallment()) {
+                        loanApplicationTerms.setCurrentPeriodFixedEmiAmount(loanTermVariationsData.getDecimalValue());
+                        recalculateAmounts = true;
+                    } else {
+                        loanApplicationTerms.setFixedEmiAmount(loanTermVariationsData.getDecimalValue());
+                    }
+                    loanTermVariationsData.setProcessed(true);
+                break;
+                case PRINCIPAL_AMOUNT:
+                    if (loanTermVariationsData.isSpecificToInstallment()) {
+                        loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(loanTermVariationsData.getDecimalValue());
+                        recalculateAmounts = true;
+                    } else {
+                        loanApplicationTerms.setFixedPrincipalAmount(loanTermVariationsData.getDecimalValue());
+                    }
+                    loanTermVariationsData.setProcessed(true);
+                break;
+                default:
+                break;
+
+            }
+        }
+        LoanTermVariationParams termVariationParams = new LoanTermVariationParams(skipPeriod, recalculateAmounts, modifiedScheduledDueDate);
+        return termVariationParams;
+    }
+
+    /**
+     * @param loanApplicationTerms
+     * @param scheduleParams
+     * @param interestRates
+     */
+    private void applyLoanVariationsForPartialScheduleGenerate(final LoanApplicationTerms loanApplicationTerms,
+            LoanScheduleParams scheduleParams, final Collection<LoanTermVariationsData> interestRates) {
+        // Applies loan variations
+        while (loanApplicationTerms.getLoanTermVariations().hasVariation(scheduleParams.getPeriodStartDate())) {
+            LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextVariation();
+            if (!variation.isSpecificToInstallment()) {
+                switch (variation.getTermVariationType()) {
+                    case EMI_AMOUNT:
+                        loanApplicationTerms.setFixedEmiAmount(variation.getDecimalValue());
+                    break;
+                    case PRINCIPAL_AMOUNT:
+                        loanApplicationTerms.setFixedPrincipalAmount(variation.getDecimalValue());
+                    break;
+                    default:
+                    break;
+                }
+            }
+
+            variation.setProcessed(true);
+        }
+
+        // Applies interest rate changes
+        for (LoanTermVariationsData variation : interestRates) {
+            if (variation.getTermVariationType().isInterestRateVariation() && variation.isApplicable(scheduleParams.getPeriodStartDate())
+                    && variation.getDecimalValue() != null) {
+                loanApplicationTerms.updateAnnualNominalInterestRate(variation.getDecimalValue());
+            }
+        }
+    }
+
+    /**
+     * this method calculates the principal amount for generating the repayment
+     * schedule.
+     */
+    private Money getPrincipalToBeScheduled(final LoanApplicationTerms loanApplicationTerms) {
+        Money principalToBeScheduled;
+        if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) {
+            principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal();
+        } else {
+            principalToBeScheduled = loanApplicationTerms.getPrincipal();
+        }
+        return principalToBeScheduled;
+    }
+
+    private boolean updateFixedInstallmentAmount(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, int periodNumber,
+            Money outstandingBalance) {
+        boolean isAmountChanged = false;
+        if (loanApplicationTerms.getActualFixedEmiAmount() == null && loanApplicationTerms.getInterestMethod().isDecliningBalnce()
+                && loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) {
+            if (periodNumber < loanApplicationTerms.getPrincipalGrace() + 1) {
+                periodNumber = loanApplicationTerms.getPrincipalGrace() + 1;
+            }
+            Money emiAmount = loanApplicationTerms.pmtForInstallment(this.paymentPeriodsInOneYearCalculator, outstandingBalance,
+                    periodNumber, mc);
+            loanApplicationTerms.setFixedEmiAmount(emiAmount.getAmount());
+            isAmountChanged = true;
+        }
+        return isAmountChanged;
+    }
+
+    private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency,
+            final LoanTransaction transaction) {
+        Money arrears = transaction.getPrincipalPortion(currency);
+        if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
+            arrears = arrears.plus(transaction.getInterestPortion(currency));
+        }
+
+        if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled()) {
+            arrears = arrears.plus(transaction.getFeeChargesPortion(currency)).plus(transaction.getPenaltyChargesPortion(currency));
+        }
+        return arrears;
+    }
+
+    /**
+     * Method calculates interest on not paid outstanding principal and interest
+     * (if compounding is enabled) till current date and adds new repayment
+     * schedule detail
+     * 
+     * @param compoundingMap
+     *            TODO
+     * @param loanCharges
+     *            TODO
+     * @param principalPortioMap
+     *            TODO
+     * 
+     */
+    private Money addInterestOnlyRepaymentScheduleForCurrentdate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
+            final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency, final Collection<LoanScheduleModelPeriod> periods,
+            final LocalDate currentDate, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            final Collection<RecalculationDetail> transactions, final Set<LoanCharge> loanCharges, final LoanScheduleParams params) {
+        boolean isFirstRepayment = false;
+        LocalDate startDate = params.getPeriodStartDate();
+        Money outstanding = Money.zero(currency);
+        Money totalInterest = Money.zero(currency);
+        Money totalCumulativeInterest = Money.zero(currency);
+        double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0);
+        int periodNumberTemp = 1;
+        LocalDate lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
+        Collection<LoanTermVariationsData> applicableVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
+
+        do {
+
+            params.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(params.getActualRepaymentDate(),
+                    loanApplicationTerms, isFirstRepayment, holidayDetailDTO));
+            if (params.getActualRepaymentDate().isAfter(currentDate)) {
+                params.setActualRepaymentDate(currentDate);
+            }
+            outstanding = updateOutstandingFromLatePayment(params.getPeriodStartDate(), params.getLatePaymentMap(), outstanding);
+
+            Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(
+                    params.applyInterestRecalculation(), params.getActualRepaymentDate(), transactions);
+
+            if (!params.getLatePaymentMap().isEmpty()) {
+                populateCompoundingDatesInPeriod(params.getPeriodStartDate(), params.getActualRepaymentDate(), currentDate,
+                        loanApplicationTerms, holidayDetailDTO, params.getCompoundingMap(), loanCharges, currency);
+            }
+
+            for (RecalculationDetail detail : applicableTransactions) {
+                if (detail.isProcessed()) {
+                    continue;
+                }
+                LocalDate transactionDate = detail.getTransactionDate();
+                List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail);
+
+                if (!params.getPeriodStartDate().isEqual(transactionDate)) {
+                    PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
+                            this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction,
+                            totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding,
+                            loanApplicationTerms, periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(),
+                            params.getPeriodStartDate(), transactionDate, applicableVariations);
+
+                    Money interest = principalInterestForThisPeriod.interest();
+                    totalInterest = totalInterest.plus(interest);
+
+                    LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(),
+                            startDate, transactionDate, totalInterest.zero(), totalInterest.zero(), totalInterest, totalInterest.zero(),
+                            totalInterest.zero(), totalInterest, true);
+                    params.incrementInstalmentNumber();
+                    periods.add(installment);
+                    totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest);
+                    totalInterest = totalInterest.zero();
+                    addLoanRepaymentScheduleInstallment(params.getInstallments(), installment);
+                    params.setPeriodStartDate(transactionDate);
+                    startDate = transactionDate;
+                }
+                loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, params.getInstallments());
+                updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, params.getLatePaymentMap(), currentDate,
+                        params.getInstallments(), false, lastRestDate, params.getCompoundingMap());
+                outstanding = outstanding.zero();
+                outstanding = updateOutstandingFromLatePayment(params.getPeriodStartDate(), params.getLatePaymentMap(), outstanding);
+                outstanding = updateBalanceForInterestCalculation(params.getPrincipalPortionMap(), params.getPeriodStartDate(),
+                        outstanding, false);
+                if (params.getLatePaymentMap().isEmpty() && !outstanding.isGreaterThanZero()) {
+                    break;
+                }
+            }
+
+            if (outstanding.isGreaterThanZero()) {
+                PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
+                        this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(),
+                        totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms,
+                        periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(), params.getPeriodStartDate(),
+                        params.getActualRepaymentDate(), applicableVariations);
+                Money interest = principalInterestForThisPeriod.interest();
+                totalInterest = totalInterest.plus(interest);
+                if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
+                    LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(params.getActualRepaymentDate().minusDays(1),
+                            loanApplicationTerms, holidayDetailDTO);
+                    params.getLatePaymentMap().put(compoundingEffectiveDate, interest);
+
+                }
+            }
+            params.setPeriodStartDate(params.getActualRepaymentDate());
+        } while (params.getActualRepaymentDate().isBefore(currentDate) && outstanding.isGreaterThanZero());
+
+        if (totalInterest.isGreaterThanZero()) {
+            LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(),
+                    startDate, params.getActualRepaymentDate(), totalInterest.zero(), totalInterest.zero(), totalInterest,
+                    totalInterest.zero(), totalInterest.zero(), totalInterest, true);
+            params.incrementInstalmentNumber();
+            periods.add(installment);
+            totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest);
+        }
+        return totalCumulativeInterest;
+    }
+
+    private Collection<RecalculationDetail> getApplicableTransactionsForPeriod(final boolean applyInterestRecalculation,
+            LocalDate repaymentDate, final Collection<RecalculationDetail> transactions) {
+        Collection<RecalculationDetail> applicableTransactions = new ArrayList<>();
+        if (applyInterestRecalculation) {
+            for (RecalculationDetail detail : transactions) {
+                if (!detail.getTransactionDate().isAfter(repaymentDate)) {
+                    applicableTransactions.add(detail);
+                }
+            }
+            transactions.removeAll(applicableTransactions);
+        }
+        return applicableTransactions;
+    }
+
+    private Collection<LoanTermVariationsData> getApplicableTermVariationsForPeriod(final LocalDate fromDate, final LocalDate dueDate,
+            final Collection<LoanTermVariationsData> variations) {
+        Collection<LoanTermVariationsData> applicableVariations = new ArrayList<>();
+        for (LoanTermVariationsData detail : variations) {
+            if (detail.isApplicable(fromDate, dueDate)) {
+                applicableVariations.add(detail);
+            }
+        }
+        variations.removeAll(applicableVariations);
+        return applicableVariations;
+    }
+
+    private List<LoanTransaction> createCurrentTransactionList(RecalculationDetail detail) {
+        List<LoanTransaction> currentTransactions = new ArrayList<>(2);
+        currentTransactions.add(detail.getTransaction());
+        detail.setProcessed(true);
+        return currentTransactions;
+    }
+
+    private Money updateOutstandingFromLatePayment(LocalDate periodStartDate, Map<LocalDate, Money> latePaymentMap, Money outstanding) {
+        Map<LocalDate, Money> retainEntries = new HashMap<>();
+        for (Map.Entry<LocalDate, Money> mapEntry : latePaymentMap.entrySet()) {
+            if (!mapEntry.getKey().isAfter(periodStartDate)) {
+                outstanding = outstanding.plus(mapEntry.getValue());
+            } else {
+                retainEntries.put(mapEntry.getKey(), mapEntry.getValue());
+            }
+        }
+        latePaymentMap.clear();
+        latePaymentMap.putAll(retainEntries);
+        retainEntries.clear();
+        return outstanding;
+    }
+
+    /**
+     * method applies early payment strategy as per the configurations provided
+     */
+    private Money applyEarlyPaymentStrategy(final LoanApplicationTerms loanApplicationTerms, Money reducePrincipal,
+            final Money totalCumulativePrincipal, int periodNumber, final MathContext mc) {
+        if (reducePrincipal.isGreaterThanZero()) {
+            switch (loanApplicationTerms.getRescheduleStrategyMethod()) {
+                case REDUCE_EMI_AMOUNT:
+                    adjustInstallmentOrPrincipalAmount(loanApplicationTerms, totalCumulativePrincipal, periodNumber, mc);
+                    reducePrincipal = reducePrincipal.zero();
+                break;
+                case REDUCE_NUMBER_OF_INSTALLMENTS:
+                    // number of installments will reduce but emi amount won't
+                    // get effected
+                    reducePrincipal = reducePrincipal.zero();
+                break;
+                case RESCHEDULE_NEXT_REPAYMENTS:
+                // will reduce principal from the reduce Principal for each
+                // installment(means installments will have less emi amount)
+                // until this
+                // amount becomes zero
+                break;
+                default:
+                break;
+            }
+        }
+        return reducePrincipal;
+    }
+
+    private void adjustInstallmentOrPrincipalAmount(final LoanApplicationTerms loanApplicationTerms, final Money totalCumulativePrincipal,
+            int periodNumber, final MathContext mc) {
+        // in this case emi amount will be reduced but number of
+        // installments won't change
+        Money principal = getPrincipalToBeScheduled(loanApplicationTerms);
+        if (!principal.minus(totalCumulativePrincipal).isGreaterThanZero()) { return; }
+        if (loanApplicationTerms.getAmortizationMethod().isEqualPrincipal()) {
+            loanApplicationTerms.updateFixedPrincipalAmount(mc, periodNumber, principal.minus(totalCumulativePrincipal));
+        } else if (loanApplicationTerms.getActualFixedEmiAmount() == null) {
+            loanApplicationTerms.setFixedEmiAmount(null);
+            updateFixedInstallmentAmount(mc, loanApplicationTerms, periodNumber, principal.minus(totalCumulativePrincipal));
+        }
+
+    }
+
+    /**
+     * Identifies all the past date principal changes and apply them on
+     * outstanding balance for future calculations
+     */
+    private Money updateBalanceForInterestCalculation(final Map<LocalDate, Money> principalPortionMap, final LocalDate scheduledDueDate,
+            final Money outstandingBalanceAsPerRest, boolean addMapDetails) {
+        List<LocalDate> removeFromprincipalPortionMap = new ArrayList<>();
+        Money outstandingBalance = outstandingBalanceAsPerRest;
+        for (Map.Entry<LocalDate, Money> principal : principalPortionMap.entrySet()) {
+            if (!principal.getKey().isAfter(scheduledDueDate)) {
+                if (addMapDetails) {
+                    outstandingBalance = outstandingBalance.plus(principal.getValue());
+                } else {
+                    outstandingBalance = outstandingBalance.minus(principal.getValue());
+                }
+                removeFromprincipalPortionMap.add(principal.getKey());
+            }
+        }
+        for (LocalDate date : removeFromprincipalPortionMap) {
+            principalPortionMap.remove(date);
+        }
+        return outstandingBalance;
+    }
+
+    // this is to make sure even paid late payments(principal and compounded
+    // interest/fee) should be reduced as per rest date
+    private void updateLatePaidAmountsToPrincipalMap(final LoanTransaction loanTransaction, final LoanApplicationTerms applicationTerms,
+            final MonetaryCurrency currency, final HolidayDetailDTO holidayDetailDTO, final LocalDate lastRestDate,
+            final LoanScheduleParams params) {
+        LocalDate applicableDate = getNextRestScheduleDate(loanTransaction.getTransactionDate().minusDays(1), applicationTerms,
+                holidayDetailDTO);
+
+        Money principalPortion = loanTransaction.getPrincipalPortion(currency);
+        Money compoundedLatePayments = Money.zero(currency);
+        if (applicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
+            compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getInterestPortion(currency));
+        }
+        if (applicationTerms.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled()) {
+            compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getFeeChargesPortion(currency)).plus(
+                    loanTransaction.getPenaltyChargesPortion(currency));
+        }
+
+        updateCompoundingAmount(params.getPrincipalPortionMap(), params.getLatePaymentMap(), currency, lastRestDate, principalPortion,
+                applicableDate);
+        updateCompoundingAmount(params.getPrincipalPortionMap(), params.getCompoundingMap(), currency, lastRestDate,
+                compoundedLatePayments, applicableDate);
+    }
+
+    private void updateCompoundingAmount(final Map<LocalDate, Money> principalVariationMap,
+            final Map<LocalDate, Money> latePaymentCompoundingMap, final MonetaryCurrency currency, final LocalDate lastRestDate,
+            Money compoundedPortion, final LocalDate applicableDate) {
+        Money appliedOnPrincipalVariationMap = Money.zero(currency);
+        Map<LocalDate, Money> temp = new HashMap<>();
+        for (LocalDate date : latePaymentCompoundingMap.keySet()) {
+            if (date.isBefore(lastRestDate)) {
+                Money money = latePaymentCompoundingMap.get(date);
+                appliedOnPrincipalVariationMap = appliedOnPrincipalVariationMap.plus(money);
+                if (appliedOnPrincipalVariationMap.isLessThan(compoundedPortion)) {
+                    if (date.isBefore(applicableDate)) {
+                        updateMapWithAmount(principalVariationMap, money.negated(), date);
+                        updateMapWithAmount(principalVariationMap, money, applicableDate);
+                    }
+                } else if (temp.isEmpty()) {
+                    Money diff = money.minus(appliedOnPrincipalVariationMap.minus(compoundedPortion));
+                    updateMapWithAmount(principalVariationMap, diff.negated(), date);
+                    updateMapWithAmount(principalVariationMap, diff, applicableDate);
+                    updateMapWithAmount(temp, money.minus(diff), date);
+                    updateMapWithAmount(temp, money.minus(diff).negated(), lastRestDate);
+                } else {
+                    updateMapWithAmount(temp, money, date);
+                    updateMapWithAmount(temp, money.negated(), lastRestDate);
+                }
+            }
+        }
+        latePaymentCompoundingMap.clear();
+        latePaymentCompoundingMap.putAll(temp);
+    }
+
+    /**
+     * this Method updates late/ not paid installment components to Map with
+     * effective date as per REST(for principal portion ) and compounding
+     * (interest or fee or interest and fee portions) frequency
+     * 
+     */
+    private void updateLatePaymentsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
+            final MonetaryCurrency currency, final Map<LocalDate, Money> latePaymentMap, final LocalDate scheduledDueDate,
+            List<LoanRepaymentScheduleInstallment> installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate,
+            final TreeMap<LocalDate, Money> compoundingMap) {
+        latePaymentMap.clear();
+        LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+
+        Money totalCompoundingAmount = Money.zero(currency);
+        Money compoundedMoney = Money.zero(currency);
+        if (!compoundingMap.isEmpty()) {
+            compoundedMoney = compoundingMap.get(lastRestDate);
+        }
+        boolean clearCompoundingMap = true;
+        for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
+            if (loanRepaymentScheduleInstallment.isNotFullyPaidOff()
+                    && !loanRepaymentScheduleInstallment.getDueDate().isAfter(scheduledDueDate)
+                    && !loanRepaymentScheduleInstallment.isRecalculatedInterestComponent()) {
+                LocalDate principalEffectiveDate = loanRepaymentScheduleInstallment.getDueDate();
+                if (applyRestFrequencyForPrincipal) {
+                    principalEffectiveDate = getNextRestScheduleDate(loanRepaymentScheduleInstallment.getDueDate().minusDays(1),
+                            loanApplicationTerms, holidayDetailDTO);
+                }
+                if (principalEffectiveDate.isBefore(currentDate)) {
+                    updateMapWithAmount(latePaymentMap, loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency),
+                            principalEffectiveDate);
+                    totalCompoundingAmount = totalCompoundingAmount
+                            .plus(loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency));
+                }
+
+                final Money changedCompoundedMoney = updateMapWithCompoundingDetails(loanApplicationTerms, holidayDetailDTO, currency,
+                        compoundingMap, loanRepaymentScheduleInstallment, lastRestDate, compoundedMoney, scheduledDueDate);
+                if (compoundedMoney.isZero() || !compoundedMoney.isEqualTo(changedCompoundedMoney)) {
+                    compoundedMoney = changedCompoundedMoney;
+                    clearCompoundingMap = false;
+                }
+            }
+        }
+        if (totalCompoundingAmount.isGreaterThanZero()) {
+            updateMapWithAmount(latePaymentMap, totalCompoundingAmount.negated(), lastRestDate);
+        }
+        if (clearCompoundingMap) {
+            compoundingMap.clear();
+        }
+    }
+
+    private Money updateMapWithCompoundingDetails(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
+            final MonetaryCurrency currency, final TreeMap<LocalDate, Money> compoundingMap,
+            final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final LocalDate lastRestDate,
+            final Money compoundedMoney, final LocalDate scheduledDueDate) {
+        Money ignoreMoney = compoundedMoney;
+        if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
+            LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(loanRepaymentScheduleInstallment.getDueDate().minusDays(1),
+                    loanApplicationTerms, holidayDetailDTO);
+
+            if (compoundingEffectiveDate.isBefore(DateUtils.getLocalDateOfTenant())) {
+                Money amount = Money.zero(currency);
+                switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) {
+                    case INTEREST:
+                        amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency));
+                    break;
+                    case FEE:
+                        amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency));
+                        amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
+                    break;
+                    case INTEREST_AND_FEE:
+                        amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency));
+                        amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency));
+                        amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
+                    break;
+                    default:
+                    break;
+                }
+                if (compoundingEffectiveDate.isBefore(scheduledDueDate)) {
+                    ignoreMoney = ignoreMoney.plus(amount);
+                    if (ignoreMoney.isGreaterThanZero()) {
+                        updateMapWithAmount(compoundingMap, ignoreMoney, compoundingEffectiveDate);
+                        updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate);
+                        ignoreMoney = ignoreMoney.zero();
+                    }
+                } else {
+                    if (ignoreMoney.isLessThanZero()) {
+                        LocalDate firstKey = compoundingMap.firstKey();
+                        updateMapWithAmount(compoundingMap, ignoreMoney, firstKey);
+                        updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate);
+                        ignoreMoney = ignoreMoney.zero();
+                    }
+                    updateMapWithAmount(compoundingMap, amount, compoundingEffectiveDate);
+                    updateMapWithAmount(compoundingMap, amount.negated(), lastRestDate);
+                }
+            }
+        }
+        return ignoreMoney;
+    }
+
+    private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LocalDate currentDate,
+            final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
+            final Map<LocalDate, Money> compoundingMap, final Set<LoanCharge> charges, MonetaryCurrency currency) {
+        if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
+            LocalDate lastCompoundingDate = startDate;
+            LocalDate compoundingDate = startDate;
+            while (compoundingDate.isBefore(endDate) && compoundingDate.isBefore(currentDate)) {
+                compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO);
+                if (!compoundingDate.isBefore(currentDate)) {
+                    break;
+                } else if (compoundingDate.isAfter(endDate)) {
+                    updateMapWithAmount(compoundingMap, Money.zero(currency), compoundingDate);
+                } else {
+                    Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency,
+                            null, loanApplicationTerms.getPrincipal(), null, false);
+                    Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(lastCompoundingDate, compoundingDate, charges,
+                            currency, null, loanApplicationTerms.getPrincipal(), null, false);
+                    updateMapWithAmount(compoundingMap, feeChargesForInstallment.plus(penaltyChargesForInstallment), compoundingDate);
+                }
+                lastCompoundingDate = compoundingDate;
+            }
+        }
+    }
+
+    protected void clearMapDetails(final LocalDate startDate, final Map<LocalDate, Money> compoundingMap) {
+        Map<LocalDate, Money> temp = new HashMap<>();
+        for (LocalDate date : compoundingMap.keySet()) {
+            if (!date.isBefore(startDate)) {
+                temp.put(date, compoundingMap.get(date));
+            }
+        }
+        compoundingMap.clear();
+        compoundingMap.putAll(temp);
+    }
+
+    /**
+     * This Method updates principal paid component to map with effective date
+     * as per the REST
+     * 
+     */
+    private void updatePrincipalPaidPortionToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
+            Map<LocalDate, Money> principalPortionMap, final LoanScheduleModelPeriod installment, final RecalculationDetail detail,
+            final Money unprocessed, final List<LoanRepaymentScheduleInstallment> installments) {
+        LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms, holidayDetailDTO);
+        updateMapWithAmount(principalPortionMap, unprocessed, applicableDate);
+        installment.addPrincipalAmount(unprocessed);
+        LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1);
+        lastInstallment.updatePrincipal(lastInstallment.getPrincipal(unprocessed.getCurrency()).plus(unprocessed).getAmount());
+        lastInstallment.payPrincipalComponent(detail.getTransactionDate(), unprocessed);
+    }
+
+    /**
+     * merges all the applicable amounts(compounding dates, disbursements, late
+     * payment compounding and principal change as per rest) changes to single
+     * map for interest calculation
+     * 
+     */
+    private TreeMap<LocalDate, Money> mergeVariationsToMap(final LoanScheduleParams params) {
+        TreeMap<LocalDate, Money> map = new TreeMap<>();
+        map.putAll(params.getLatePaymentMap());
+        for (Map.Entry<LocalDate, Money> mapEntry : params.getDisburseDetailMap().entrySet()) {
+            Money value = mapEntry.getValue();
+            if (map.containsKey(mapEntry.getKey())) {
+                value = value.plus(map.get(mapEntry.getKey()));
+            }
+            map.put(mapEntry.getKey(), value);
+        }
+
+        for (Map.Entry<LocalDate, Money> mapEntry : params.getPrincipalPortionMap().entrySet()) {
+            Money value = mapEntry.getValue().negated();
+            if (map.containsKey(mapEntry.getKey())) {
+                value = value.plus(map.get(mapEntry.getKey()));
+            }
+            map.put(mapEntry.getKey(), value);
+        }
+
+        for (Map.Entry<LocalDate, Money> mapEntry : params.getCompoundingMap().entrySet()) {
+            Money value = mapEntry.getValue();
+            if (!map.containsKey(mapEntry.getKey())) {
+                map.put(mapEntry.getKey(), value.zero());
+            }
+        }
+
+        return map;
+    }
+
+    /**
+     * calculates Interest stating date as per the settings
+     * 
+     * @param firstRepaymentdate
+     *            TODO
+     */
+    private LocalDate calculateInterestStartDateForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate periodStartDate,
+            final LocalDate idealDisbursementDate, final LocalDate firstRepaymentdate) {
+        LocalDate periodStartDateApplicableForInterest = periodStartDate;
+        if (periodStartDate.isBefore(idealDisbursementDate) || firstRepaymentdate.isAfter(periodStartDate)) {
+            if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) {
+                if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate())
+                        || loanApplicationTerms.getInterestChargedFromLocalDate().isAfter(periodStartDate)) {
+                    periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate();
+                }
+            } else if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate())) {
+                periodStartDateApplicableForInterest = idealDisbursementDate;
+            }
+        }
+        return periodStartDateApplicableForInterest;
+    }
+
+    private void updateMapWithAmount(final Map<LocalDate, Money> map, final Money amount, final LocalDate amountApplicableDate) {
+        Money principalPaid = amount;
+        if (map.containsKey(amountApplicableDate)) {
+            principalPaid = map.get(amountApplicableDate).plus(principalPaid);
+        }
+        map.put(amountApplicableDate, principalPaid);
+    }
+
+    private Money getTotalAmount(final Map<LocalDate, Money> map, final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (Map.Entry<LocalDate, Money> mapEntry : map.entrySet()) {
+            if (mapEntry.getKey().isBefore(DateUtils.getLocalDateOfTenant())) {
+                total = total.plus(mapEntry.getValue());
+            }
+        }
+        return total;
+    }
+
+    @Override
+    public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
+            final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO,
+            final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar,
+            final FloatingRateDTO floatingRateDTO) {
+
+        final Loan loan = loanRescheduleRequest.getLoan();
+        final LoanSummary loanSummary = loan.getSummary();
+        final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
+        final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
+
+        // create an archive of the current loan schedule installments
+        Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = null;
+
+        // get the initial list of repayment installments
+        List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
+
+        // sort list by installment number in ASC order
+        Collections.sort(repaymentScheduleInstallments, LoanRepaymentScheduleInstallment.installmentNumberComparator);
+
+        final Collection<LoanRescheduleModelRepaymentPeriod> periods = new ArrayList<>();
+
+        Money outstandingLoanBalance = loan.getPrincpal();
+
+        for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
+
+            Integer oldPeriodNumber = repaymentScheduleInstallment.getInstallmentNumber();
+            LocalDate fromDate = repaymentScheduleInstallment.getFromDate();
+            LocalDate dueDate = repaymentScheduleInstallment.getDueDate();
+            Money principalDue = repaymentScheduleInstallment.getPrincipal(currency);
+            Money interestDue = repaymentScheduleInstallment.getInterestCharged(currency);
+            Money feeChargesDue = repaymentScheduleInstallment.getFeeChargesCharged(currency);
+            Money penaltyChargesDue = repaymentScheduleInstallment.getPenaltyChargesCharged(currency);
+            Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);
+
+            outstandingLoanBalance = outstandingLoanBalance.minus(principalDue);
+
+            LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod
+                    .instance(oldPeriodNumber, oldPeriodNumber, fromDate, dueDate, principalDue, outstandingLoanBalance, interestDue,
+                            feeChargesDue, penaltyChargesDue, totalDue, false);
+
+            periods.add(period);
+        }
+
+        Money outstandingBalance = loan.getPrincpal();
+        Money totalCumulativePrincipal = Money.zero(currency);
+        Money totalCumulativeInterest = Money.zero(currency);
+        Money actualTotalCumulativeInterest = Money.zero(currency);
+        Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
+        Money totalPrincipalBeforeReschedulePeriod = Money.zero(currency);
+
+        LocalDate installmentDueDate = null;
+        LocalDate adjustedInstallmentDueDate = null;
+        LocalDate installmentFromDate = null;
+        Integer rescheduleFromInstallmentNo = defaultToZeroIfNull(loanRescheduleRequest.getRescheduleFromInstallment());
+        Integer installmentNumber = rescheduleFromInstallmentNo;
+        Integer graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
+        Integer graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
+        Integer extraTerms = defaultToZeroIfNull(loanRescheduleRequest.getExtraTerms());
+        final boolean recalculateInterest = loanRescheduleRequest.getRecalculateInterest();
+        Integer numberOfRepayments = repaymentScheduleInstallments.size();
+        Integer rescheduleNumberOfRepayments = numberOfRepayments;
+        final Money principal = loan.getPrincpal();
+        final Money totalPrincipalOutstanding = Money.of(currency, loanSummary.getTotalPrincipalOutstanding());
+        LocalDate adjustedDueDate = loanRescheduleRequest.getAdjustedDueDate();
+        BigDecimal newInterestRate = loanRescheduleRequest.getInterestRate();
+        int loanTermInDays = Integer.valueOf(0);
+
+        if (rescheduleFromInstallmentNo > 0) {
+            // this will hold the loan repayment installment that is before the
+            // reschedule start installment
+            // (rescheduleFrominstallment)
+            LoanRepaymentScheduleInstallment previousInstallment = null;
+
+            // get the install number of the previous installment
+            int previousInstallmentNo = rescheduleFromInstallmentNo - 1;
+
+            // only fetch the installment if the number is greater than 0
+            if (previousInstallmentNo > 0) {
+                previousInstallment = loan.fetchRepaymentScheduleInstallment(previousInstallmentNo);
+            }
+
+            LoanRepaymentScheduleInstallment firstInstallment = loan.fetchRepaymentScheduleInstallment(1);
+
+            // the "installment from date" is equal to the due date of the
+            // previous installment, if it exists
+            if (previousInstallment != null) {
+                installmentFromDate = previousInstallment.getDueDate();
+            }
+
+            else {
+                installmentFromDate = firstInstallment.getFromDate();
+            }
+
+            installmentDueDate = installmentFromDate;
+            LocalDate periodStartDateApplicableForInterest = installmentFromDate;
+            Integer periodNumber = 1;
+            outstandingLoanBalance = loan.getPrincpal();
+
+            for (LoanRescheduleModelRepaymentPeriod period : periods) {
+
+                if (period.periodDueDate().isBefore(loanRescheduleRequest.getRescheduleFromDate())) {
+
+                    totalPrincipalBeforeReschedulePeriod = totalPrincipalBeforeReschedulePeriod.plus(period.principalDue());
+                    actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
+                    rescheduleNumberOfRepayments--;
+                    outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
+                    outstandingBalance = outstandingBalance.minus(period.principalDue());
+                }
+            }
+
+            while (graceOnPrincipal > 0 || graceOnInterest > 0) {
+
+                LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
+                        new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
+                        Money.zero(currency), Money.zero(currency), true);
+
+                periods.add(period);
+
+                if (graceOnPrincipal > 0) {
+                    graceOnPrincipal--;
+                }
+
+                if (graceOnInterest > 0) {
+                    graceOnInterest--;
+                }
+
+                rescheduleNumberOfRepayments++;
+                numberOfRepayments++;
+            }
+
+            while (extraTerms > 0) {
+
+                LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
+                        new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
+                        Money.zero(currency), Money.zero(currency), true);
+
+                periods.add(period);
+
+                extraTerms--;
+                rescheduleNumberOfRepayments++;
+                numberOfRepayments++;
+            }
+
+            // get the loan application terms from the Loan object
+            final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance,
+                    compoundingCalendarInstance, loanCalendar, floatingRateDTO);
+
+            // for applying variations
+            Collection<LoanTermVariationsData> loanTermVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
+
+            // update the number of repayments
+            loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments);
+
+            LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
+            LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations().fetchLoanTermDueDateVariationsData(
+                    loanEndDate);
+            if (lastDueDateVariation != null) {
+                loanEndDate = lastDueDateVariation.getDateValue();
+            }
+            loanApplicationTerms.updateLoanEndDate(loanEndDate);
+
+            if (newInterestRate != null) {
+                loanApplicationTerms.updateAnnualNominalInterestRate(newInterestRate);
+                loanApplicationTerms.updateInterestRatePerPeriod(newInterestRate);
+            }
+
+            graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
+            graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
+
+            loanApplicationTerms.updateInterestPaymentGrace(graceOnInterest);
+            loanApplicationTerms.updatePrincipalGrace(graceOnPrincipal);
+
+            loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
+            loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments);
+            loanApplicationTerms.updateLoanTermFrequency(rescheduleNumberOfRepayments);
+            loanApplicationTerms.updateInterestChargedFromDate(periodStartDateApplicableForInterest);
+
+            Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged(
+                    this.paymentPeriodsInOneYearCalculator, mathContext);
+
+            if (!recalculateInterest && newInterestRate == null) {
+                totalInterestChargedForFullLoanTerm = Money.of(currency, loanSummary.getTotalInterestCharged());
+                totalInterestChargedForFullLoanTerm = totalInterestChargedForFullLoanTerm.minus(actualTotalCumulativeInterest);
+
+                loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm);
+            }
+
+            for (LoanRescheduleModelRepaymentPeriod period : periods) {
+
+                if (period.periodDueDate().isEqual(loanRescheduleRequest.getRescheduleFromDate())
+                        || period.periodDueDate().isAfter(loanRescheduleRequest.getRescheduleFromDate()) || period.isNew()) {
+
+                    installmentDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(installmentDueDate, loanApplicationTerms,
+                            false, holidayDetailDTO);
+
+                    if (adjustedDueDate != null && periodNumber == 1) {
+                        installmentDueDate = adjustedDueDate;
+                    }
+
+                    adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms,
+                            holidayDetailDTO);
+
+                    final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate).getDays();
+
+                    period.updatePeriodNumber(installmentNumber);
+                    period.updatePeriodFromDate(installmentFromDate);
+                    period.updatePeriodDueDate(adjustedInstallmentDueDate);
+
+                    double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
+                            .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest,
+                                    adjustedInstallmentDueDate, periodStartDateApplicableForInterest,
+                                    loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery());
+
+                    // ========================= Calculate the interest due
+                    // ========================================
+
+                    // change the principal to => Principal Disbursed - Total
+                    // Principal Paid
+                    // interest calculation is always based on the total
+                    // principal outstanding
+                    loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
+
+                    // for applying variations
+                    Collection<LoanTermVariationsData> applicableVariations = getApplicableTermVariationsForPeriod(installmentFromDate,
+                            adjustedInstallmentDueDate, loanTermVariations);
+
+                    // determine the interest & principal for the period
+                    PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
+                            this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction,
+                            totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
+                            totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms, periodNumber, mathContext,
+                            null, null, installmentFromDate, adjustedInstallmentDueDate, applicableVariations);
+
+                    // update the interest due for the period
+                    period.updateInterestDue(principalInterestForThisPeriod.interest());
+
+                    // =============================================================================================
+
+                    // ========================== Calculate the principal due
+                    // ======================================
+
+                    // change the principal to => Principal Disbursed - Total
+                    // cumulative Principal Amount before the reschedule
+                    // installment
+                    loanApplicationTerms.setPrincipal(principal.minus(totalPrincipalBeforeReschedulePeriod));
+
+                    principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(this.paymentPeriodsInOneYearCalculator,
+                            interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest,
+                            totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalance,
+                            loanApplicationTerms, periodNumber, mathContext, null, null, installmentFromDate, adjustedInstallmentDueDate,
+                            applicableVariations);
+
+                    period.updatePrincipalDue(principalInterestForThisPeriod.principal());
+
+                    // ==============================================================================================
+
+                    outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
+                    period.updateOutstandingLoanBalance(outstandingLoanBalance);
+
+                    Money principalDue = Money.of(currency, period.principalDue());
+                    Money interestDue = Money.of(currency, period.interestDue());
+
+                    if (principalDue.isZero() && interestDue.isZero()) {
+                        period.updateFeeChargesDue(Money.zero(currency));
+                        period.updatePenaltyChargesDue(Money.zero(currency));
+                    }
+
+                    Money feeChargesDue = Money.of(currency, period.feeChargesDue());
+                    Money penaltyChargesDue = Money.of(currency, period.penaltyChargesDue());
+
+                    Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);
+
+                    period.updateTotalDue(totalDue);
+
+                    // update cumulative fields for principal & interest
+                    totalCumulativePrincipal = totalCumulativePrincipal.plus(period.principalDue());
+                    totalCumulativeInterest = totalCumulativeInterest.plus(period.interestDue());
+                    actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
+                    totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
+
+                    installmentFromDate = adjustedInstallmentDueDate;
+                    installmentNumber++;
+                    periodNumber++;
+                    loanTermInDays += daysInInstallment;
+
+                    outstandingBalance = outstandingBalance.minus(period.principalDue());
+                }
+            }
+        }
+
+        final Money totalRepaymentExpected = principal // get the loan Principal
+                                                       // amount
+                .plus(actualTotalCumulativeInterest) // add the actual total
+                                                     // cumulative interest
+                .plus(loanSummary.getTotalFeeChargesCharged()) // add the total
+                                                               // fees charged
+                .plus(loanSummary.getTotalPenaltyChargesCharged()); // finally
+                                                                    // add the
+                                                                    // total
+                                                                    // penalty
+                                                                    // charged
+
+        return LoanRescheduleModel.instance(periods, loanRepaymentScheduleHistoryList, applicationCurrency, loanTermInDays,
+                loan.getPrincpal(), loan.getPrincpal().getAmount(), loanSummary.getTotalPrincipalRepaid(),
+                actualTotalCumulativeInterest.getAmount(), loanSummary.getTotalFeeChargesCharged(),
+                loanSummary.getTotalPenaltyChargesCharged(), totalRepaymentExpected.getAmount(), loanSummary.getTotalOutstanding());
+    }
+
+    public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator,
+            double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest,
+            Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance,
+            LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap<LocalDate, Money> principalVariation,
+            Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
+            Collection<LoanTermVariationsData> termVariations);
+
+    protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) {
+        return periodNumber == numberOfRepayments;
+    }
+
+    private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
+        BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isDueAtDisbursement()) {
+                chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
+            }
+        }
+        return chargesDueAtTimeOfDisbursement;
+    }
+
+    private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms, LocalDate disbursementDate,
+            final Collection<LoanScheduleModelPeriod> periods, final BigDecimal chargesDueAtTimeOfDisbursement,
+            final Map<LocalDate, Money> disurseDetail, final boolean excludePastUndisbursed) {
+        BigDecimal principal = BigDecimal.ZERO;
+        MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
+        for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
+            if (disbursementData.disbursementDate().equals(disbursementDate)) {
+                final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
+                        disbursementData.disbursementDate(), Money.of(currency, disbursementData.amount()), chargesDueAtTimeOfDisbursement);
+                periods.add(disbursementPeriod);
+                principal = principal.add(disbursementData.amount());
+            } else if (!excludePastUndisbursed || disbursementData.isDisbursed()
+                    || !disbursementData.disbursementDate().isBefore(DateUtils.getLocalDateOfTenant())) {
+                disurseDetail.put(disbursementData.disbursementDate(), Money.of(currency, disbursementData.amount()));
+            }
+        }
+        return principal;
+    }
+
+    private Collection<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(final int numberOfRepayments,
+            final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement) {
+
+        Collection<LoanScheduleModelPeriod> periods = null;
+        if (loanApplicationTerms.isMultiDisburseLoan()) {
+            periods = new ArrayList<>(numberOfRepayments + loanApplicationTerms.getDisbursementDatas().size());
+        } else {
+            periods = new ArrayList<>(numberOfRepayments + 1);
+            final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
+                    loanApplicationTerms, chargesDueAtTimeOfDisbursement);
+            periods.add(disbursementPeriod);
+        }
+
+        return periods;
+    }
+
+    private Set<LoanCharge> seperateTotalCompoundingPercentageCharges(final Set<LoanCharge> loanCharges) {
+        Set<LoanCharge> interestCharges = new HashSet<>();
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isSpecifiedDueDate()
+                    && (loanCharge.getChargeCalculation().isPercentageOfInterest() || loanCharge.getChargeCalculation()
+                            .isPercentageOfAmountAndInterest())) {
+                interestCharges.add(loanCharge);
+            }
+        }
+        loanCharges.removeAll(interestCharges);
+        return interestCharges;
+    }
+
+    private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
+            final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod,
+            final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable) {
+
+        Money cumulative = Money.zero(monetaryCurrency);
+
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
+                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
+                    cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge);
+                } else if (loanCharge.isOverdueInstallmentCharge()
+                        && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
+                        && loanCharge.getChargeCalculation().isPercentageBased()) {
+                    cumulative = cumulative.plus(loanCharge.chargeAmount());
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
+                        && loanCharge.getChargeCalculation().isPercentageBased()) {
+                    cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm,
+                            cumulative, loanCharge);
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
+                    cumulative = cumulative.plus(loanCharge.amount());
+                }
+            }
+        }
+
+        return cumulative;
+    }
+
+    private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed,
+            final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge) {
+        BigDecimal amount = BigDecimal.ZERO;
+        if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
+            amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
+        } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
+            amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount());
+        } else {
+            amount = amount.add(principalDisbursed.getAmount());
+        }
+        BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
+        cumulative = cumulative.plus(loanChargeAmt);
+        return cumulative;
+    }
+
+    private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod, Money cumulative,
+            final LoanCharge loanCharge) {
+        if (loanCharge.getChargeCalculation().isPercentageBased()) {
+            BigDecimal amount = BigDecimal.ZERO;
+            if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
+                amount = amount.add(principalInterestForThisPeriod.principal().getAmount()).add(
+                        principalInterestForThisPeriod.interest().getAmount());
+            } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
+                amount = amount.add(principalInterestForThisPeriod.interest().getAmount());
+            } else {
+                amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
+            }
+            BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
+            cumulative = cumulative.plus(loanChargeAmt);
+        } else {
+            cumulative = cumulative.plus(loanCharge.amountOrPercentage());
+        }
+        return cumulative;
+    }
+
+    private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
+            final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
+            final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
+            final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable) {
+
+        Money cumulative = Money.zero(monetaryCurrency);
+
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isPenaltyCharge()) {
+                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
+                    cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge);
+                } else if (loanCharge.isOverdueInstallmentCharge()
+                        && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
+                        && loanCharge.getChargeCalculation().isPercentageBased()) {
+                    cumulative = cumulative.plus(loanCharge.chargeAmount());
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
+                        && loanCharge.getChargeCalculation().isPercentageBased()) {
+                    cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm,
+                            cumulative, loanCharge);
+                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
+                    cumulative = cumulative.plus(loanCharge.amount());
+                }
+            }
+        }
+
+        return cumulative;
+    }
+
+    /**
+     * Method preprocess the installments and transactions and sets the required
+     * fields to generate the schedule
+     */
+    @Override
+    public LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
+            final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom) {
+
+        // Fixed schedule End Date for generating schedule
+        final LocalDate scheduleTillDate = null;
+        return rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, transactions,
+                loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, rescheduleFrom, scheduleTillDate);
+
+    }
+
+    private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
+            final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom,
+            final LocalDate scheduleTillDate) {
+        // Loan transactions to process and find the variation on payments
+        Collection<RecalculationDetail> recalculationDetails = new ArrayList<>();
+        for (LoanTransaction loanTransaction : transactions) {
+            recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction
+                    .copyTransactionProperties(loanTransaction)));
+        }
+        final boolean applyInterestRecalculation = loanApplicationTerms.isInterestRecalculationEnabled();
+
+        LoanScheduleParams loanScheduleParams = null;
+        Collection<LoanScheduleModelPeriod> periods = new ArrayList<>();
+        final List<LoanRepaymentScheduleInstallment> retainedInstallments = new ArrayList<>();
+
+        // this block is to retain the schedule installments prior to the
+        // provided date and creates late and early payment details for further
+        // calculations
+        if (rescheduleFrom != null) {
+            Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms);
+            // actual outstanding balance for interest calculation
+            Money outstandingBalance = principalToBeScheduled;
+            // total outstanding balance as per rest for interest calculation.
+            Money outstandingBalanceAsPerRest = outstandingBalance;
+
+            // this is required to update total fee amounts in the
+            // LoanScheduleModel
+            final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
+            periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(),
+                    loanApplicationTerms, chargesDueAtTimeOfDisbursement);
+            final List<LoanRepaymentScheduleInstallment> newRepaymentScheduleInstallments = new ArrayList<>();
+            MonetaryCurrency currency = outstandingBalance.getCurrency();
+
+            // early payments will be added here and as per the selected
+            // strategy
+            // action will be performed on this value
+            Money reducePrincipal = outstandingBalanceAsPerRest.zero();
+
+            // principal changes will be added along with date(after applying
+            // rest)
+            // from when these amounts will effect the outstanding balance for
+            // interest calculation
+            final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
+            // compounding(principal) amounts will be added along with
+            // date(after applying compounding frequency)
+            // from when these amounts will effect the outstanding balance for
+            // interest calculation
+            final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
+
+            // compounding(interest/Fee) amounts will be added along with
+            // date(after applying compounding frequency)
+            // from when these amounts will effect the outstanding balance for
+            // interest calculation
+            final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>();
+            LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+            LocalDate lastRestDate = currentDate;
+            if (loanApplicationTerms.getRestCalendarInstance() != null) {
+                lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
+            }
+            LocalDate actualRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
+            boolean isFirstRepayment = true;
+
+            // cumulative fields
+            Money totalCumulativePrincipal = principalToBeScheduled.zero();
+            Money totalCumulativeInterest = principalToBeScheduled.zero();
+            Money totalFeeChargesCharged = principalToBeScheduled.zero().plus(chargesDueAtTimeOfDisbursement);
+            Money totalPenaltyChargesCharged = principalToBeScheduled.zero();
+            Money totalRepaymentExpected = principalToBeScheduled.zero();
+
+            // Actual period Number as per the schedule
+            int periodNumber = 1;
+            // Actual period Number plus interest only repayments
+            int instalmentNumber = 1;
+            LocalDate lastInstallmentDate = actualRepaymentDate;
+            LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate();
+            // Set fixed Amortization Amounts(either EMI or Principal )
+            updateAmortization(mc, loanApplicationTerms, periodNumber, outstandingBalance);
+
+            final Map<LocalDate, Money> disburseDetailMap = new HashMap<>();
+            if (loanApplicationTerms.isMultiDisburseLoan()) {
+                // fetches the first tranche amount and also updates other
+                // tranche
+                // details to map
+                BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(),
+                        periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true);
+                outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
+                outstandingBalanceAsPerRest = outstandingBalance;
+                principalToBeScheduled = principalToBeScheduled.zero().plus(disburseAmt);
+            }
+            int loanTermInDays = 0;
+
+            // Block process the installment and creates the period if it falls
+            // before reschedule from date
+            // This will create the recalculation details by applying the
+            // transactions
+            for (LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+                // this will generate the next schedule due date and allows to
+                // process the installment only if recalculate from date is
+                // greater than due date
+                if (installment.getDueDate().isAfter(lastInstallmentDate)) {
+                    LocalDate previousRepaymentDate = actualRepaymentDate;
+                    actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms,
+                            isFirstRepayment, holidayDetailDTO);
+                    isFirstRepayment = false;
+                    lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
+                            holidayDetailDTO);
+                    if (!lastInstallmentDate.isBefore(rescheduleFrom)) {
+                        actualRepaymentDate = previousRepaymentDate;
+                        break;
+                    }
+                    periodNumber++;
+                    // check for date changes
+                    while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) {
+                        LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
+                        if (!variation.isSpecificToInstallment()) {
+                            actualRepaymentDate = variation.getDateValue();
+                        }
+                        variation.setProcessed(true);
+                    }
+                }
+
+                for (Map.Entry<LocalDate, Money> disburseDetail : disburseDetailMap.entrySet()) {
+                    if (disburseDetail.getKey().isAfter(installment.getFromDate())
+                            && !disburseDetail.getKey().isAfter(installment.getDueDate())) {
+                        // creates and add disbursement detail to the repayments
+                        // period
+                        final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
+                                disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
+                        periods.add(disbursementPeriod);
+                        // updates actual outstanding balance with new
+                        // disbursement detail
+                        outstandingBalance = outstandingBalance.plus(disburseDetail.getValue());
+                        principalToBeScheduled = principalToBeScheduled.plus(disburseDetail.getValue());
+                    }
+                }
+
+                // calculation of basic fields to start the schedule generation
+                // from the middle
+                periodStartDate = installment.getDueDate();
+                installment.resetDerivedComponents();
+                newRepaymentScheduleInstallments.add(installment);
+                outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency));
+                final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance);
+                periods.add(loanScheduleModelPeriod);
+                totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency));
+                totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency));
+                totalFeeChargesCharged = totalFeeChargesCharged.plus(installment.getFeeChargesCharged(currency));
+                totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(installment.getPenaltyChargesCharged(currency));
+                instalmentNumber++;
+                loanTermInDays = Days.daysBetween(installment.getFromDate(), installment.getDueDate()).getDays();
+
+                // populates the collection with transactions till the due date
+                // of
+                // the period for interest recalculation enabled loans
+                Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(applyInterestRecalculation,
+                        installment.getDueDate(), recalculationDetails);
+
+                // calculates the expected principal value for this repayment
+                // schedule
+                Money principalPortionCalculated = principalToBeScheduled.zero();
+                if (!installment.isRecalculatedInterestComponent()) {
+                    principalPortionCalculated = calculateExpectedPrincipalPortion(installment.getInterestCharged(currency),
+                            loanApplicationTerms);
+                }
+
+                // expected principal considering the previously paid excess
+                // amount
+                Money actualPrincipalPortion = principalPortionCalculated.minus(reducePrincipal);
+                if (actualPrincipalPortion.isLessThanZero()) {
+                    actualPrincipalPortion = principalPortionCalculated.zero();
+                }
+
+                Money unprocessed = updateEarlyPaidAmountsToMap(loanApplicationTerms, holidayDetailDTO,
+                        loanRepaymentScheduleTransactionProcessor, newRepaymentScheduleInstallments, currency, principalPortionMap,
+                        installment, applicableTransactions, actualPrincipalPortion);
+
+                // this block is to adjust the period number based on the actual
+                // schedule due date and installment due date
+                // recalculatedInterestComponent installment shouldn't be
+                // considered while calculating fixed EMI amounts
+                int period = periodNumber;
+                if (!lastInstallmentDate.isEqual(installment.getDueDate())) {
+                    period--;
+                }
+                reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal,
+                        loanApplicationTerms, totalCumulativePrincipal, period, mc);
+                // Updates principal paid map with efective date for reducing
+                // the amount from outstanding balance(interest calculation)
+                LocalDate amountApplicableDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms,
+                        holidayDetailDTO);
+                // updates map with the installment principal amount excluding
+                // unprocessed amount since this amount is already accounted.
+                updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate);
+                // update outstanding balance for interest calculation
+                outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(),
+                        outstandingBalanceAsPerRest, false);
+                outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(),
+                        outstandingBalanceAsPerRest, true);
+
+            }
+            totalRepaymentExpected = totalCumulativePrincipal.plus(totalCumulativeInterest).plus(totalFeeChargesCharged)
+                    .plus(totalPenaltyChargesCharged);
+
+            // updates the map with over due amounts
+            updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate,
+                    newRepaymentScheduleInstallments, true, lastRestDate, compoundingMap);
+
+            // for partial schedule generation
+            if (!newRepaymentScheduleInstallments.isEmpty() && totalCumulativeInterest.isGreaterThanZero()) {
+                Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
+                loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForPartialUpdate(periodNumber, instalmentNumber,
+                        loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest,
+                        totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
+                        totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap,
+                        disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest,
+                        newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
+                        scheduleTillDate, currency, applyInterestRecalculation);
+                retainedInstallments.addAll(newRepaymentScheduleInstallments);
+            }
+
+        }
+        // for complete schedule generation
+        if (loanScheduleParams == null) {
+            loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails,
+                    loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation);
+        }
+
+        LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, loanScheduleParams);
+        for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) {
+            if (loanScheduleModelPeriod.isRepaymentPeriod()) {
+                // adding newly created repayment periods to installments
+                addLoanRepaymentScheduleInstallment(retainedInstallments, loanScheduleModelPeriod);
+            }
+        }
+        periods.addAll(loanScheduleModel.getPeriods());
+        LoanScheduleModel loanScheduleModelwithPeriodChanges = LoanScheduleModel.withLoanScheduleModelPeriods(periods, loanScheduleModel);
+        return LoanScheduleDTO.from(retainedInstallments, loanScheduleModelwithPeriodChanges);
+    }
+
+    /**
+     * Method identifies the early paid amounts for a installment and update the
+     * principal map for further calculations
+     */
+    private Money updateEarlyPaidAmountsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            final List<LoanRepaymentScheduleInstallment> newRepaymentScheduleInstallments, MonetaryCurrency currency,
+            final Map<LocalDate, Money> principalPortionMap, LoanRepaymentScheduleInstallment installment,
+            Collection<RecalculationDetail> applicableTransactions, Money actualPrincipalPortion) {
+        Money unprocessed = Money.zero(currency);
+        for (RecalculationDetail detail : applicableTransactions) {
+            if (!detail.isProcessed()) {
+                Money principalProcessed = installment.getPrincipalCompleted(currency);
+                List<LoanTransaction> currentTransactions = new ArrayList<>(2);
+                currentTransactions.add(detail.getTransaction());
+                // applies the transaction as per transaction strategy
+                // on scheduled installments to identify the
+                // unprocessed(early payment ) amounts
+                loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency,
+                        newRepaymentScheduleInstallments);
+
+                // Identifies totalEarlyPayment and early paid amount with this
+                // transaction
+                Money principalPaidWithTransaction = installment.getPrincipalCompleted(currency).minus(principalProcessed);
+                Money totalEarlyPayment = installment.getPrincipalCompleted(currency).minus(actualPrincipalPortion);
+
+                if (totalEarlyPayment.isGreaterThanZero()) {
+                    unprocessed = principalPaidWithTransaction;
+                    // will execute this block if partial amount paid as
+                    // early
+                    if (principalPaidWithTransaction.isGreaterThan(totalEarlyPayment)) {
+                        unprocessed = totalEarlyPayment;
+                    }
+                }
+                // updates principal portion map with the early
+                // payment amounts and applicable date as per rest
+                LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms,
+                        holidayDetailDTO);
+                updateMapWithAmount(principalPortionMap, unprocessed, applicableDate);
+
+            }
+        }
+        return unprocessed;
+    }
+
+    private void updateAmortization(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, int periodNumber,
+            Money outstandingBalance) {
+        if (loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) {
+            updateFixedInstallmentAmount(mc, loanApplicationTerms, periodNumber, outstandingBalance);
+        } else {
+            loanApplicationTerms.updateFixedPrincipalAmount(mc, periodNumber, outstandingBalance);
+        }
+    }
+
+    /**
+     * Method identifies early paid amount and applies the early payment
+     * strategy
+     */
+    private Money fetchEarlyPaidAmount(final Money principalPortion, final Money principalPortionCalculated, final Money reducePrincipal,
+            final LoanApplicationTerms applicationTerms, final Money totalCumulativePrincipal, int periodNumber, final MathContext mc) {
+        Money existingEarlyPayment = reducePrincipal.minus(principalPortionCalculated);
+        Money earlyPaidAmount = principalPortion.plus(existingEarlyPayment);
+        if (existingEarlyPayment.isLessThanZero()) {
+            existingEarlyPayment = existingEarlyPayment.zero();
+        }
+        boolean isEarlyPaid = earlyPaidAmount.isGreaterThan(existingEarlyPayment);
+
+        if (earlyPaidAmount.isLessThanZero()) {
+            earlyPaidAmount = earlyPaidAmount.zero();
+        }
+
+        if (isEarlyPaid) {
+            switch (applicationTerms.getRescheduleStrategyMethod()) {
+                case REDUCE_EMI_AMOUNT:
+                    adjustInstallmentOrPrincipalAmount(applicationTerms, totalCumulativePrincipal, periodNumber, mc);
+                    earlyPaidAmount = earlyPaidAmount.zero();
+                break;
+                case REDUCE_NUMBER_OF_INSTALLMENTS:
+                    // number of installments will reduce but emi amount won't
+                    // get effected
+                    earlyPaidAmount = earlyPaidAmount.zero();
+                break;
+                case RESCHEDULE_NEXT_REPAYMENTS:
+                // will reduce principal from the reduce Principal for each
+                // installment(means installments will have less emi amount)
+                // until this
+                // amount becomes zero
+                break;
+                default:
+                break;
+            }
+        }
+
+        return earlyPaidAmount;
+    }
+
+    private Money calculateExpectedPrincipalPortion(final Money interestPortion, final LoanApplicationTerms applicationTerms) {
+        Money principalPortionCalculated = interestPortion.zero();
+        if (applicationTerms.getAmortizationMethod().isEqualInstallment()) {
+            principalPortionCalculated = principalPortionCalculated.plus(applicationTerms.getFixedEmiAmount()).minus(interestPortion);
+        } else {
+            principalPortionCalculated = principalPortionCalculated.plus(applicationTerms.getFixedPrincipalAmount());
+        }
+        return principalPortionCalculated;
+    }
+
+    private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
+            final LoanScheduleModelPeriod scheduledLoanInstallment) {
+        LoanRepaymentScheduleInstallment installment = null;
+        if (scheduledLoanInstallment.isRepaymentPeriod()) {
+            installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
+                    scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
+                    scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
+                    scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
+                    scheduledLoanInstallment.isRecalculatedInterestComponent());
+            installments.add(installment);
+        }
+        return installment;
+    }
+
+    private LoanScheduleModelPeriod createLoanScheduleModelPeriod(final LoanRepaymentScheduleInstallment installment,
+            final Money outstandingPrincipal) {
+        final MonetaryCurrency currency = outstandingPrincipal.getCurrency();
+        LoanScheduleModelPeriod scheduledLoanInstallment = LoanScheduleModelRepaymentPeriod
+                .repayment(installment.getInstallmentNumber(), installment.getFromDate(), installment.getDueDate(),
+                        installment.getPrincipal(currency), outstandingPrincipal, installment.getInterestCharged(currency),
+                        installment.getFeeChargesCharged(currency), installment.getPenaltyChargesCharged(currency),
+                        installment.getDue(currency), installment.isRecalculatedInterestComponent());
+        return scheduledLoanInstallment;
+    }
+
+    private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
+            final HolidayDetailDTO holidayDetailDTO) {
+        LocalDate nextScheduleDate = null;
+        if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
+            nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate,
+                    loanApplicationTerms, holidayDetailDTO);
+        } else {
+            CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
+            nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
+        }
+
+        return nextScheduleDate;
+    }
+
+    private LocalDate getNextCompoundScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
+            final HolidayDetailDTO holidayDetailDTO) {
+        LocalDate nextScheduleDate = null;
+        if (!loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { return null; }
+        if (loanApplicationTerms.getCompoundingFrequencyType().isSameAsRepayment()) {
+            nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate,
+                    loanApplicationTerms, holidayDetailDTO);
+        } else {
+            CalendarInstance calendarInstance = loanApplicationTerms.getCompoundingCalendarInstance();
+            nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
+        }
+
+        return nextScheduleDate;
+    }
+
+    /**
+     * Method returns the amount payable to close the loan account as of today.
+     */
+    @Override
+    public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final MonetaryCurrency currency, final LocalDate onDate,
+            final LoanApplicationTerms loanApplicationTerms, final MathContext mc, final Set<LoanCharge> charges,
+            final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> loanTransactions,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments) {
+
+        LocalDate calculateTill = onDate;
+        if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) {
+            calculateTill = getNextRestScheduleDate(onDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
+        }
+
+        LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, charges, holidayDetailDTO, loanTransactions,
+                loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, onDate, calculateTill);
+
+        loanRepaymentScheduleTransactionProcessor.handleTransaction(loanApplicationTerms.getExpectedDisbursementDate(), loanTransactions,
+                currency, loanScheduleDTO.getInstallments(), charges);
+        Money feeCharges = Money.zero(currency);
+        Money penaltyCharges = Money.zero(currency);
+        Money totalPrincipal = Money.zero(currency);
+        Money totalInterest = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment currentInstallment : loanScheduleDTO.getInstallments()) {
+            if (currentInstallment.isNotFullyPaidOff()) {
+                totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency));
+                totalInterest = totalInterest.plus(currentInstallment.getInterestOutstanding(currency));
+                feeCharges = feeCharges.plus(currentInstallment.getFeeChargesOutstanding(currency));
+                penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
+            }
+        }
+
+        return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(),
+                feeCharges.getAmount(), penaltyCharges.getAmount(), false);
+    }
+
+    /**
+     * set the value to zero if the provided value is null
+     * 
+     * @return integer value equal/greater than 0
+     **/
+    private Integer defaultToZeroIfNull(Integer value) {
+
+        return (value != null) ? value : 0;
+    }
+
+    private final class LoanTermVariationParams {
+
+        private final boolean skipPeriod;
+        private final boolean recalculateAmounts;
+        private final LocalDate scheduledDueDate;
+
+        public LoanTermVariationParams(final boolean skipPeriod, final boolean recalculateAmounts, final LocalDate scheduledDueDate) {
+            this.skipPeriod = skipPeriod;
+            this.recalculateAmounts = recalculateAmounts;
+            this.scheduledDueDate = scheduledDueDate;
+        }
+
+        public boolean isSkipPeriod() {
+            return this.skipPeriod;
+        }
+
+        public boolean isRecalculateAmounts() {
+            return this.recalculateAmounts;
+        }
+
+        public LocalDate getScheduledDueDate() {
+            return this.scheduledDueDate;
+        }
+
+    }
+
+    private final class ScheduleCurrentPeriodParams {
+
+        Money earlyPaidAmount;
+        LoanScheduleModelPeriod lastInstallment;
+        boolean skipCurrentLoop;
+        Money interestForThisPeriod;
+        Money principalForThisPeriod;
+        Money feeChargesForInstallment;
+        Money penaltyChargesForInstallment;
+        // for adjusting outstandingBalances
+        Money reducedBalance;
+        boolean isEmiAmountChanged;
+        double interestCalculationGraceOnRepaymentPeriodFraction;
+
+        public ScheduleCurrentPeriodParams(final MonetaryCurrency currency, double interestCalculationGraceOnRepaymentPeriodFraction) {
+            this.earlyPaidAmount = Money.zero(currency);
+            this.lastInstallment = null;
+            this.skipCurrentLoop = false;
+            this.interestForThisPeriod = Money.zero(currency);
+            this.principalForThisPeriod = Money.zero(currency);
+            this.reducedBalance = Money.zero(currency);
+            this.feeChargesForInstallment = Money.zero(currency);
+            this.penaltyChargesForInstallment = Money.zero(currency);
+            this.isEmiAmountChanged = false;
+            this.interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction;
+        }
+
+        public Money getEarlyPaidAmount() {
+            return this.earlyPaidAmount;
+        }
+
+        public void plusEarlyPaidAmount(Money earlyPaidAmount) {
+            this.earlyPaidAmount = this.earlyPaidAmount.plus(earlyPaidAmount);
+        }
+
+        public void minusEarlyPaidAmount(Money earlyPaidAmount) {
+            this.earlyPaidAmount = this.earlyPaidAmount.minus(earlyPaidAmount);
+        }
+
+        public LoanScheduleModelPeriod getLastInstallment() {
+            return this.lastInstallment;
+        }
+
+        public void setLastInstallment(LoanScheduleModelPeriod lastInstallment) {
+            this.lastInstallment = lastInstallment;
+        }
+
+        public boolean isSkipCurrentLoop() {
+            return this.skipCurrentLoop;
+        }
+
+        public void setSkipCurrentLoop(boolean skipCurrentLoop) {
+            this.skipCurrentLoop = skipCurrentLoop;
+        }
+
+        public Money getInterestForThisPeriod() {
+            return this.interestForThisPeriod;
+        }
+
+        public void setInterestForThisPeriod(Money interestForThisPeriod) {
+            this.interestForThisPeriod = interestForThisPeriod;
+        }
+
+        public void minusInterestForThisPeriod(Money interestForThisPeriod) {
+            this.interestForThisPeriod = this.interestForThisPeriod.minus(interestForThisPeriod);
+        }
+
+        public Money getPrincipalForThisPeriod() {
+            return this.principalForThisPeriod;
+        }
+
+        public void setPrincipalForThisPeriod(Money principalForThisPeriod) {
+            this.principalForThisPeriod = principalForThisPeriod;
+        }
+
+        public void plusPrincipalForThisPeriod(Money principalForThisPeriod) {
+            this.principalForThisPeriod = this.principalForThisPeriod.plus(principalForThisPeriod);
+        }
+
+        public void minusPrincipalForThisPeriod(Money principalForThisPeriod) {
+            this.principalForThisPeriod = this.principalForThisPeriod.minus(principalForThisPeriod);
+        }
+
+        public Money getReducedBalance() {
+            return this.reducedBalance;
+        }
+
+        public void setReducedBalance(Money reducedBalance) {
+            this.reducedBalance = reducedBalance;
+        }
+
+        public Money getFeeChargesForInstallment() {
+            return this.feeChargesForInstallment;
+        }
+
+        public void setFeeChargesForInstallment(Money feeChargesForInstallment) {
+            this.feeChargesForInstallment = feeChargesForInstallment;
+        }
+
+        public void minusFeeChargesForInstallment(Money feeChargesForInstallment) {
+            this.feeChargesForInstallment = this.feeChargesForInstallment.minus(feeChargesForInstallment);
+        }
+
+        public Money getPenaltyChargesForInstallment() {
+            return this.penaltyChargesForInstallment;
+        }
+
+        public void setPenaltyChargesForInstallment(Money penaltyChargesForInstallment) {
+            this.penaltyChargesForInstallment = penaltyChargesForInstallment;
+        }
+
+        public void minusPenaltyChargesForInstallment(Money penaltyChargesForInstallment) {
+            this.penaltyChargesForInstallment = this.penaltyChargesForInstallment.minus(penaltyChargesForInstallment);
+        }
+
+        public Money fetchTotalAmountForPeriod() {
+            return this.principalForThisPeriod.plus(interestForThisPeriod).plus(feeChargesForInstallment)
+                    .plus(penaltyChargesForInstallment);
+        }
+
+        public boolean isEmiAmountChanged() {
+            return this.isEmiAmountChanged;
+        }
+
+        public void setEmiAmountChanged(boolean isEmiAmountChanged) {
+            this.isEmiAmountChanged = isEmiAmountChanged;
+        }
+
+        public double getInterestCalculationGraceOnRepaymentPeriodFraction() {
+            return this.interestCalculationGraceOnRepaymentPeriodFraction;
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculator.java
new file mode 100644
index 0000000..730949b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculator.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.loanaccount.loanschedule.domain;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AprCalculator {
+
+    public BigDecimal calculateFrom(final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal interestRatePerPeriod) {
+        BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO;
+        switch (interestPeriodFrequencyType) {
+            case DAYS:
+                defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(365));
+            break;
+            case WEEKS:
+                defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52));
+            break;
+            case MONTHS:
+                defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(12));
+            break;
+            case YEARS:
+                defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(1));
+            break;
+            case INVALID:
+            break;
+        }
+
+        return defaultAnnualNominalInterestRate;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
new file mode 100644
index 0000000..f53a50e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
@@ -0,0 +1,167 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+
+/**
+ * <p>
+ * Declining balance can be amortized (see {@link AmortizationMethod}) in two
+ * ways at present:
+ * <ol>
+ * <li>Equal principal payments</li>
+ * <li>Equal installment payments</li>
+ * </ol>
+ * </p>
+ * 
+ * <p>
+ * When amortized using <i>equal principal payments</i>, the <b>principal
+ * component</b> of each installment is fixed and <b>interest due</b> is
+ * calculated from the <b>outstanding principal balance</b> resulting in a
+ * different <b>total payment due</b> for each installment.
+ * </p>
+ * 
+ * <p>
+ * When amortized using <i>equal installments</i>, the <b>total payment due</b>
+ * for each installment is fixed and is calculated using the excel like
+ * <code>pmt</code> function. The <b>interest due</b> is calculated from the
+ * <b>outstanding principal balance</b> which results in a <b>principal
+ * component</b> that is <b>total payment due</b> minus <b>interest due</b>.
+ * </p>
+ */
+public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator {
+
+    @Override
+    public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
+            final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
+            @SuppressWarnings("unused") final Money totalCumulativeInterest,
+            @SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
+            final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
+            final TreeMap<LocalDate, Money> principalVariation, final Map<LocalDate, Money> compoundingMap,
+            final LocalDate periodStartDate, final LocalDate periodEndDate, final Collection<LoanTermVariationsData> termVariations) {
+
+        LocalDate interestStartDate = periodStartDate;
+        Money interestForThisInstallment = totalCumulativePrincipal.zero();
+        Money compoundedMoney = totalCumulativePrincipal.zero();
+        Money compoundedInterest = totalCumulativePrincipal.zero();
+        Money balanceForInterestCalculation = outstandingBalance;
+        Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace;
+        Map<LocalDate, BigDecimal> interestRates = new HashMap<>(termVariations.size());
+        for (LoanTermVariationsData loanTermVariation : termVariations) {
+            if (loanTermVariation.getTermVariationType().isInterestRateVariation()
+                    && loanTermVariation.isApplicable(periodStartDate, periodEndDate)) {
+                LocalDate fromDate = loanTermVariation.getTermApplicableFrom();
+                if (fromDate == null) {
+                    fromDate = periodStartDate;
+                }
+                interestRates.put(fromDate, loanTermVariation.getDecimalValue());
+                if (!principalVariation.containsKey(fromDate)) {
+                    principalVariation.put(fromDate, balanceForInterestCalculation.zero());
+                }
+            }
+        }
+        if (principalVariation != null) {
+            // identifies rest date after current date for reducing all
+            // compounding
+            // values
+            LocalDate compoundingEndDate = principalVariation.ceilingKey(DateUtils.getLocalDateOfTenant());
+            if (compoundingEndDate == null) {
+                compoundingEndDate = DateUtils.getLocalDateOfTenant();
+            }
+
+            for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) {
+
+                if (!principal.getKey().isAfter(periodEndDate)) {
+                    int interestForDays = Days.daysBetween(interestStartDate, principal.getKey()).getDays();
+                    if (interestForDays > 0) {
+                        final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
+                                interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace,
+                                balanceForInterestCalculation, interestStartDate, principal.getKey());
+                        interestForThisInstallment = interestForThisInstallment.plus(result.interest());
+                        cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
+                        interestStartDate = principal.getKey();
+                    }
+                    Money compoundFee = totalCumulativePrincipal.zero();
+                    if (compoundingMap.containsKey(principal.getKey())) {
+                        Money interestToBeCompounded = totalCumulativePrincipal.zero();
+                        // for interest compounding
+                        if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
+                            interestToBeCompounded = interestForThisInstallment.minus(compoundedInterest);
+                            balanceForInterestCalculation = balanceForInterestCalculation.plus(interestToBeCompounded);
+                            compoundedInterest = interestForThisInstallment;
+                        }
+                        // fee compounding will be done after calculation
+                        compoundFee = compoundingMap.get(principal.getKey());
+                        compoundedMoney = compoundedMoney.plus(interestToBeCompounded).plus(compoundFee);
+                    }
+                    balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()).plus(compoundFee);
+                    if (interestRates.containsKey(principal.getKey())) {
+                        loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey()));
+                    }
+                }
+
+            }
+            if (!periodEndDate.isBefore(compoundingEndDate)) {
+                balanceForInterestCalculation = balanceForInterestCalculation.minus(compoundedMoney);
+                compoundingMap.clear();
+            } else if (compoundedMoney.isGreaterThanZero()) {
+                compoundingMap.put(periodEndDate, compoundedMoney);
+                compoundingMap.put(compoundingEndDate, compoundedMoney.negated());
+                clearMapDetails(periodEndDate, compoundingMap);
+            }
+        }
+
+        final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
+                interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace,
+                balanceForInterestCalculation, interestStartDate, periodEndDate);
+        interestForThisInstallment = interestForThisInstallment.plus(result.interest());
+        cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
+
+        Money interestForPeriod = interestForThisInstallment;
+        if (interestForPeriod.isGreaterThanZero()) {
+            interestForPeriod = interestForPeriod.minus(cumulatingInterestPaymentDueToGrace);
+        } else {
+            interestForPeriod = cumulatingInterestDueToGrace.minus(cumulatingInterestPaymentDueToGrace);
+        }
+        Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance,
+                periodNumber, mc, interestForPeriod);
+
+        // update cumulative fields for principal & interest
+        final Money interestBroughtFowardDueToGrace = cumulatingInterestDueToGrace;
+        final Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment);
+
+        // adjust if needed
+        principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
+                totalCumulativePrincipalToDate, periodNumber);
+
+        return new PrincipalInterest(principalForThisInstallment, interestForThisInstallment, interestBroughtFowardDueToGrace);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java
new file mode 100644
index 0000000..df26d7b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DefaultLoanScheduleGeneratorFactory implements LoanScheduleGeneratorFactory {
+
+    @Override
+    public LoanScheduleGenerator create(final InterestMethod interestMethod) {
+
+        LoanScheduleGenerator loanScheduleGenerator = null;
+
+        switch (interestMethod) {
+            case FLAT:
+                loanScheduleGenerator = new FlatInterestLoanScheduleGenerator();
+            break;
+            case DECLINING_BALANCE:
+                loanScheduleGenerator = new DecliningBalanceInterestLoanScheduleGenerator();
+            break;
+            case INVALID:
+            break;
+        }
+
+        return loanScheduleGenerator;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java
new file mode 100644
index 0000000..10c6eec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java
@@ -0,0 +1,97 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+
+public class DefaultPaymentPeriodsInOneYearCalculator implements PaymentPeriodsInOneYearCalculator {
+
+    @Override
+    public Integer calculate(final PeriodFrequencyType repaymentFrequencyType) {
+
+        Integer paymentPeriodsInOneYear = Integer.valueOf(0);
+        switch (repaymentFrequencyType) {
+            case DAYS:
+                paymentPeriodsInOneYear = Integer.valueOf(365);
+            break;
+            case WEEKS:
+                paymentPeriodsInOneYear = Integer.valueOf(52);
+            break;
+            case MONTHS:
+                paymentPeriodsInOneYear = Integer.valueOf(12);
+            break;
+            case YEARS:
+                paymentPeriodsInOneYear = Integer.valueOf(1);
+            break;
+            case INVALID:
+                paymentPeriodsInOneYear = Integer.valueOf(0);
+            break;
+        }
+        return paymentPeriodsInOneYear;
+    }
+
+    @Override
+    public double calculatePortionOfRepaymentPeriodInterestChargingGrace(final LocalDate repaymentPeriodStartDate,
+            final LocalDate scheduledDueDate, final LocalDate interestChargedFromLocalDate,
+            final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer repaidEvery) {
+
+        Double periodFraction = Double.valueOf("0.0");
+
+        final LocalDateInterval repaymentPeriod = new LocalDateInterval(repaymentPeriodStartDate, scheduledDueDate);
+
+        if (interestChargedFromLocalDate != null && repaymentPeriod.fallsBefore(interestChargedFromLocalDate.plusDays(1))) {
+            periodFraction = Double.valueOf("1.0");
+        } else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) {
+
+            final int numberOfDaysInterestCalculationGraceInPeriod = Days.daysBetween(repaymentPeriodStartDate,
+                    interestChargedFromLocalDate).getDays();
+            periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery,
+                    numberOfDaysInterestCalculationGraceInPeriod);
+        }
+
+        return periodFraction;
+    }
+
+    private double calculateRepaymentPeriodFraction(final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer every,
+            final Integer numberOfDaysInterestCalculationGrace) {
+
+        Double fraction = Double.valueOf("0");
+        switch (repaymentPeriodFrequencyType) {
+            case DAYS:
+                fraction = numberOfDaysInterestCalculationGrace.doubleValue() * every.doubleValue();
+            break;
+            case WEEKS:
+                fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.valueOf("7.0") * every.doubleValue());
+            break;
+            case MONTHS:
+                fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.valueOf("30.0") * every.doubleValue());
+            break;
+            case YEARS:
+                fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.valueOf("365.0") * every.doubleValue());
+            break;
+            case INVALID:
+                fraction = Double.valueOf("0");
+            break;
+        }
+        return fraction;
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..0a8ac12
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -0,0 +1,238 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import org.apache.fineract.organisation.holiday.service.HolidayUtil;
+import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
+import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+import org.joda.time.Months;
+import org.joda.time.Weeks;
+import org.joda.time.Years;
+
+public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
+
+    @Override
+    public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
+
+        final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
+
+        LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
+        boolean isFirstRepayment = true;
+        for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
+            lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+            isFirstRepayment = false;
+        }
+        lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO);
+        return lastRepaymentDate;
+    }
+
+    @Override
+    public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms,
+            boolean isFirstRepayment, final HolidayDetailDTO holidayDetailDTO) {
+        final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
+        LocalDate dueRepaymentPeriodDate = null;
+        if (isFirstRepayment && firstRepaymentPeriodDate != null) {
+            dueRepaymentPeriodDate = firstRepaymentPeriodDate;
+        } else {
+            Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
+            dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
+                    loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, loanApplicationTerms.getNthDay(),
+                    loanApplicationTerms.getWeekDayType());
+            dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
+                    loanApplicationTerms.getRepaymentPeriodFrequencyType());
+            if (currentCalendar != null) {
+                // If we have currentCalendar object, this means there is a
+                // calendar associated with
+                // the loan, and we should use it in order to calculate next
+                // repayment
+                LocalDate seedDate = currentCalendar.getStartDateLocalDate();
+                String reccuringString = currentCalendar.getRecurrence();
+                dueRepaymentPeriodDate = CalendarUtils.getNewRepaymentMeetingDate(reccuringString, seedDate, dueRepaymentPeriodDate,
+                        loanApplicationTerms.getRepaymentEvery(),
+                        CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
+                        holidayDetailDTO.getWorkingDays());
+            }
+        }
+        return dueRepaymentPeriodDate;
+    }
+
+    @Override
+    public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms,
+            final HolidayDetailDTO holidayDetailDTO) {
+
+        LocalDate adjustedDate = dueRepaymentPeriodDate;
+
+        LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
+                loanApplicationTerms.getRepaymentEvery(), adjustedDate, loanApplicationTerms.getNthDay(),
+                loanApplicationTerms.getWeekDayType());
+
+        final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(holidayDetailDTO.getWorkingDays()
+                .getRepaymentReschedulingType());
+
+        /**
+         * Fix for https://mifosforge.jira.com/browse/MIFOSX-1357
+         */
+        // 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());
+
+        }
+        adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate,
+                holidayDetailDTO.getWorkingDays());
+
+        if (holidayDetailDTO.isHolidayEnabled()) {
+            adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidayDetailDTO.getHolidays());
+        }
+
+        return adjustedDate;
+    }
+
+    @Override
+    public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate,
+            Integer nthDay, DayOfWeekType dayOfWeek) {
+        LocalDate dueRepaymentPeriodDate = startDate;
+        switch (frequency) {
+            case DAYS:
+                dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
+            break;
+            case WEEKS:
+                dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
+            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);
+            break;
+            case INVALID:
+            break;
+        }
+        return dueRepaymentPeriodDate;
+    }
+
+    private LocalDate adjustToNthWeekDay(LocalDate dueRepaymentPeriodDate, int nthDay, int dayOfWeek) {
+        // adjust date to start of month
+        dueRepaymentPeriodDate = dueRepaymentPeriodDate.withDayOfMonth(1);
+        // adjust date to next week if current day is past specified day of
+        // week.
+        if (dueRepaymentPeriodDate.getDayOfWeek() > dayOfWeek) {
+            dueRepaymentPeriodDate = dueRepaymentPeriodDate.plusWeeks(1);
+        }
+        // adjust date to specified date of week
+        dueRepaymentPeriodDate = dueRepaymentPeriodDate.withDayOfWeek(dayOfWeek);
+        // adjust to specified nth week day
+        dueRepaymentPeriodDate = dueRepaymentPeriodDate.plusWeeks(nthDay - 1);
+        return dueRepaymentPeriodDate;
+    }
+
+    @Override
+    public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate,
+            final LocalDate date) {
+        boolean isScheduledDate = false;
+        switch (frequency) {
+            case DAYS:
+                int diff = Days.daysBetween(startDate, date).getDays();
+                isScheduledDate = (diff % repaidEvery) == 0;
+            break;
+            case WEEKS:
+                int weekDiff = Weeks.weeksBetween(startDate, date).getWeeks();
+                isScheduledDate = (weekDiff % repaidEvery) == 0;
+                if (isScheduledDate) {
+                    LocalDate modifiedDate = startDate.plusWeeks(weekDiff);
+                    isScheduledDate = modifiedDate.isEqual(date);
+                }
+            break;
+            case MONTHS:
+                int monthDiff = Months.monthsBetween(startDate, date).getMonths();
+                isScheduledDate = (monthDiff % repaidEvery) == 0;
+                if (isScheduledDate) {
+                    LocalDate modifiedDate = startDate.plusMonths(monthDiff);
+                    isScheduledDate = modifiedDate.isEqual(date);
+                }
+            break;
+            case YEARS:
+                int yearDiff = Years.yearsBetween(startDate, date).getYears();
+                isScheduledDate = (yearDiff % repaidEvery) == 0;
+                if (isScheduledDate) {
+                    LocalDate modifiedDate = startDate.plusYears(yearDiff);
+                    isScheduledDate = modifiedDate.isEqual(date);
+                }
+            break;
+            case INVALID:
+            break;
+        }
+        return isScheduledDate;
+    }
+
+    @Override
+    public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFrequencyType repaymentPeriodFrequencyType,
+            final int repaidEvery, final LocalDate firstRepaymentDate) {
+
+        LocalDate idealDisbursementDate = null;
+
+        switch (repaymentPeriodFrequencyType) {
+            case DAYS:
+                idealDisbursementDate = firstRepaymentDate.minusDays(repaidEvery);
+            break;
+            case WEEKS:
+                idealDisbursementDate = firstRepaymentDate.minusWeeks(repaidEvery);
+            break;
+            case MONTHS:
+                idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery);
+            break;
+            case YEARS:
+                idealDisbursementDate = firstRepaymentDate.minusYears(repaidEvery);
+            break;
+            case INVALID:
+            break;
+        }
+
+        return idealDisbursementDate;
+    }
+
+    @Override
+    public LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate,
+            LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
+
+        LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
+        boolean isFirstRepayment = true;
+        while (!generatedDate.isAfter(lastRepaymentDate)) {
+            generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+            isFirstRepayment = false;
+        }
+        generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO);
+        return generatedDate;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FinanicalFunctions.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FinanicalFunctions.java
new file mode 100644
index 0000000..7de97c9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FinanicalFunctions.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+public class FinanicalFunctions {
+
+    /**
+     * PMT calculates a fixed monthly payment to be paid by borrower every
+     * 'period' to ensure loan is paid off in full (with interest).
+     * 
+     * This monthly payment c depends upon the monthly interest rate r
+     * (expressed as a fraction, not a percentage, i.e., divide the quoted
+     * yearly percentage rate by 100 and by 12 to obtain the monthly interest
+     * rate), the number of monthly payments N called the loan's term, and the
+     * amount borrowed P known as the loan's principal; c is given by the
+     * formula:
+     * 
+     * c = (r / (1 - (1 + r)^-N))P
+     * 
+     * @param interestRateFraction
+     * @param numberOfPayments
+     * @param principal
+     * @param futureValue
+     * @param type
+     */
+    public static double pmt(final double interestRateFraction, final double numberOfPayments, final double principal,
+            final double futureValue, final boolean type) {
+        double payment = 0;
+        if (interestRateFraction == 0) {
+            payment = -1 * (futureValue + principal) / numberOfPayments;
+        } else {
+            final double r1 = interestRateFraction + 1;
+            payment = (futureValue + principal * Math.pow(r1, numberOfPayments)) * interestRateFraction
+                    / ((type ? r1 : 1) * (1 - Math.pow(r1, numberOfPayments)));
+        }
+        return payment;
+    }
+
+    public static int nop(final double interestRateFraction, final double emiAmount, final double principal, final double futureValue,
+            final boolean type) {
+        double numberOfPayments = 0;
+        if (interestRateFraction == 0) {
+            numberOfPayments = ((Double) (-1 * (futureValue + principal) / emiAmount)).intValue();
+        } else {
+            final double r1 = interestRateFraction + 1;
+            numberOfPayments = (futureValue + principal * Math.pow(r1, emiAmount)) * interestRateFraction
+                    / ((type ? r1 : 1) * (1 - Math.pow(r1, emiAmount)));
+        }
+        return Double.valueOf(numberOfPayments).intValue();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java
new file mode 100644
index 0000000..31cdbe5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java
@@ -0,0 +1,62 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.MathContext;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.joda.time.LocalDate;
+
+public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator {
+
+    @Override
+    public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
+            final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
+            final Money totalCumulativeInterest, final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
+            final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
+            @SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation,
+            @SuppressWarnings("unused") Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
+            @SuppressWarnings("unused") Collection<LoanTermVariationsData> termVariations) {
+        Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance,
+                periodNumber, mc, null);
+
+        final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
+                interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestPaymentDueToGrace,
+                outstandingBalance, periodStartDate, periodEndDate);
+        Money interestForThisInstallment = result.interest();
+
+        // update cumulative fields for principal & interest
+        final Money interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace();
+        final Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment);
+        final Money totalCumulativeInterestToDate = totalCumulativeInterest.plus(interestForThisInstallment);
+
+        // adjust if needed
+        principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
+                totalCumulativePrincipalToDate, periodNumber);
+
+        interestForThisInstallment = loanApplicationTerms.adjustInterestIfLastRepaymentPeriod(interestForThisInstallment,
+                totalCumulativeInterestToDate, totalInterestDueForLoan, periodNumber);
+
+        return new PrincipalInterest(principalForThisInstallment, interestForThisInstallment, interestBroughtForwardDueToGrace);
+    }
+}
\ 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
new file mode 100755
index 0000000..c33091c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -0,0 +1,1425 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.Date;
+import java.util.List;
+
+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.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import org.apache.fineract.portfolio.common.domain.NthDayType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
+import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+import org.joda.time.Months;
+import org.joda.time.Period;
+import org.joda.time.PeriodType;
+import org.joda.time.Weeks;
+import org.joda.time.Years;
+
+public final class LoanApplicationTerms {
+
+    private final ApplicationCurrency currency;
+
+    private final Calendar loanCalendar;
+    private Integer loanTermFrequency;
+    private final PeriodFrequencyType loanTermPeriodFrequencyType;
+    private Integer numberOfRepayments;
+    private Integer actualNumberOfRepayments;
+    private final Integer repaymentEvery;
+    private final PeriodFrequencyType repaymentPeriodFrequencyType;
+    private final Integer nthDay;
+
+    private final DayOfWeekType weekDayType;
+    private final AmortizationMethod amortizationMethod;
+
+    private final InterestMethod interestMethod;
+    private BigDecimal interestRatePerPeriod;
+    private final PeriodFrequencyType interestRatePeriodFrequencyType;
+    private BigDecimal annualNominalInterestRate;
+    private final InterestCalculationPeriodMethod interestCalculationPeriodMethod;
+    private final boolean allowPartialPeriodInterestCalcualtion;
+
+    private Money principal;
+    private final LocalDate expectedDisbursementDate;
+    private final LocalDate repaymentsStartingFromDate;
+    private final LocalDate calculatedRepaymentsStartingFromDate;
+    /**
+     * Integer representing the number of 'repayment frequencies' or
+     * installments where 'grace' should apply to the principal component of a
+     * loans repayment period (installment).
+     */
+    private Integer principalGrace;
+
+    /**
+     * Integer representing the number of 'repayment frequencies' or
+     * installments where 'grace' should apply to the payment of interest in a
+     * loans repayment period (installment).
+     * 
+     * <b>Note:</b> Interest is still calculated taking into account the full
+     * loan term, the interest is simply offset to a later period.
+     */
+    private Integer interestPaymentGrace;
+
+    /**
+     * Integer representing the number of 'repayment frequencies' or
+     * installments where 'grace' should apply to the charging of interest in a
+     * loans repayment period (installment).
+     * 
+     * <b>Note:</b> The loan is <i>interest-free</i> for the period of time
+     * indicated.
+     */
+    private final Integer interestChargingGrace;
+
+    /**
+     * Legacy method of support 'grace' on the charging of interest on a loan.
+     * 
+     * <p>
+     * For the typical structured loan, its reasonable to use an integer to
+     * indicate the number of 'repayment frequency' periods the 'grace' should
+     * apply to but for slightly <b>irregular</b> loans where the period between
+     * disbursement and the date of the 'first repayment period' isnt doest
+     * match the 'repayment frequency' but can be less (15days instead of 1
+     * month) or more (6 weeks instead of 1 month) - The idea was to use a date
+     * to indicate from whence interest should be charged.
+     * </p>
+     */
+    private LocalDate interestChargedFromDate;
+    private final Money inArrearsTolerance;
+
+    private final Integer graceOnArrearsAgeing;
+
+    // added
+    private LocalDate loanEndDate;
+
+    private final List<DisbursementData> disbursementDatas;
+
+    private final boolean multiDisburseLoan;
+
+    private BigDecimal fixedEmiAmount;
+
+    private BigDecimal fixedPrincipalAmount;
+
+    private BigDecimal currentPeriodFixedEmiAmount;
+
+    private BigDecimal currentPeriodFixedPrincipalAmount;
+
+    private final BigDecimal actualFixedEmiAmount;
+
+    private final BigDecimal maxOutstandingBalance;
+
+    private Money totalInterestDue;
+
+    private final DaysInMonthType daysInMonthType;
+
+    private final DaysInYearType daysInYearType;
+
+    private final boolean interestRecalculationEnabled;
+
+    private final LoanRescheduleStrategyMethod rescheduleStrategyMethod;
+
+    private final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod;
+
+    private final CalendarInstance restCalendarInstance;
+
+    private final RecalculationFrequencyType recalculationFrequencyType;
+
+    private final CalendarInstance compoundingCalendarInstance;
+
+    private final RecalculationFrequencyType compoundingFrequencyType;
+
+    private final BigDecimal principalThresholdForLastInstalment;
+    private final Integer installmentAmountInMultiplesOf;
+
+    private final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy;
+
+    private Money approvedPrincipal = null;
+
+    private final LoanTermVariationsDataWrapper variationsDataWrapper;
+
+    private Money adjustPrincipalForFlatLoans;
+
+    private final LocalDate seedDate;
+
+    public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
+            final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
+            final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType,
+            final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod,
+            final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualNominalInterestRate,
+            final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion,
+            final Money principalMoney, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate,
+            final LocalDate calculatedRepaymentsStartingFromDate, final Integer graceOnPrincipalPayment,
+            final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate,
+            final Money inArrearsTolerance, final boolean multiDisburseLoan, final BigDecimal emiAmount,
+            final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance, final Integer graceOnArrearsAgeing,
+            final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled,
+            final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance,
+            final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
+            final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
+            final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar,
+            BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations) {
+
+        final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
+        final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
+        return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery,
+                repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod,
+                interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
+                allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
+                calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
+                interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance,
+                graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod,
+                interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
+                compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
+                preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
+    }
+
+    public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
+            final PeriodFrequencyType loanTermPeriodFrequencyType, NthDayType nthDay, DayOfWeekType dayOfWeek,
+            final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate,
+            final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance,
+            final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount,
+            final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
+            final LocalDate interestChargedFromDate, final BigDecimal principalThresholdForLastInstalment,
+            final Integer installmentAmountInMultiplesOf, final RecalculationFrequencyType recalculationFrequencyType,
+            final CalendarInstance restCalendarInstance, final InterestRecalculationCompoundingMethod compoundingMethod,
+            final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
+            final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy,
+            final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount, BigDecimal annualNominalInterestRate,
+            List<LoanTermVariationsData> loanTermVariations) {
+        final Calendar loanCalendar = null;
+
+        return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek,
+                expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsTolerance,
+                loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, interestChargedFromDate,
+                principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, restCalendarInstance,
+                compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, loanPreClosureInterestCalculationStrategy,
+                rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate, loanTermVariations);
+    }
+
+    public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
+            final PeriodFrequencyType loanTermPeriodFrequencyType, NthDayType nthDay, DayOfWeekType dayOfWeek,
+            final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate,
+            final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance,
+            final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount,
+            final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
+            final LocalDate interestChargedFromDate, final BigDecimal principalThresholdForLastInstalment,
+            final Integer installmentAmountInMultiplesOf, final RecalculationFrequencyType recalculationFrequencyType,
+            final CalendarInstance restCalendarInstance, final InterestRecalculationCompoundingMethod compoundingMethod,
+            final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
+            final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy,
+            final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar, BigDecimal approvedAmount,
+            BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations) {
+
+        final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
+        final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
+        final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
+        final AmortizationMethod amortizationMethod = loanProductRelatedDetail.getAmortizationMethod();
+        final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod();
+        final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod();
+        final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType();
+        final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail
+                .getInterestCalculationPeriodMethod();
+        final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion();
+        final Money principalMoney = loanProductRelatedDetail.getPrincipal();
+
+        //
+        final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment();
+        final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment();
+        final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged();
+
+        // Interest recalculation settings
+        final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType();
+        final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType();
+        final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled();
+        return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
+                repaymentEvery, repaymentPeriodFrequencyType, nthDay.getValue(), dayOfWeek, amortizationMethod, interestMethod,
+                interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
+                allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
+                calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
+                interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance,
+                loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
+                rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
+                compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
+                loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
+    }
+
+    public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
+            final PeriodFrequencyType loanTermPeriodFrequencyType, final LocalDate expectedDisbursementDate,
+            final LocalDate repaymentsStartingFromDate, final LocalDate calculatedRepaymentsStartingFromDate,
+            final Money inArrearsTolerance, final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan,
+            final BigDecimal emiAmount, final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
+            final LocalDate interestChargedFromDate, final LoanInterestRecalculationDetails interestRecalculationDetails,
+            final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType,
+            final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
+            final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
+            final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, BigDecimal approvedAmount,
+            final BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations) {
+
+        final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
+        final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
+        final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
+        final AmortizationMethod amortizationMethod = loanProductRelatedDetail.getAmortizationMethod();
+        final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod();
+        final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod();
+        final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType();
+        final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail
+                .getInterestCalculationPeriodMethod();
+        final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion();
+        final Money principalMoney = loanProductRelatedDetail.getPrincipal();
+
+        //
+        final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment();
+        final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment();
+        final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged();
+
+        // Interest recalculation settings
+        final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType();
+        final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType();
+        final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled();
+        LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
+        InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
+        if (isInterestRecalculationEnabled) {
+            rescheduleStrategyMethod = interestRecalculationDetails.getRescheduleStrategyMethod();
+            interestRecalculationCompoundingMethod = interestRecalculationDetails.getInterestRecalculationCompoundingMethod();
+        }
+        final Calendar loanCalendar = null;
+        return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
+                repaymentEvery, repaymentPeriodFrequencyType, null, null, amortizationMethod, interestMethod, interestRatePerPeriod,
+                interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
+                allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
+                calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
+                interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance,
+                loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
+                rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType,
+                compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
+                loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
+    }
+
+    public static LoanApplicationTerms assembleFrom(final LoanApplicationTerms applicationTerms,
+            final List<LoanTermVariationsData> loanTermVariations) {
+        return new LoanApplicationTerms(applicationTerms.currency, applicationTerms.loanTermFrequency,
+                applicationTerms.loanTermPeriodFrequencyType, applicationTerms.numberOfRepayments, applicationTerms.repaymentEvery,
+                applicationTerms.repaymentPeriodFrequencyType, applicationTerms.nthDay, applicationTerms.weekDayType,
+                applicationTerms.amortizationMethod, applicationTerms.interestMethod, applicationTerms.interestRatePerPeriod,
+                applicationTerms.interestRatePeriodFrequencyType, applicationTerms.annualNominalInterestRate,
+                applicationTerms.interestCalculationPeriodMethod, applicationTerms.allowPartialPeriodInterestCalcualtion,
+                applicationTerms.principal, applicationTerms.expectedDisbursementDate, applicationTerms.repaymentsStartingFromDate,
+                applicationTerms.calculatedRepaymentsStartingFromDate, applicationTerms.principalGrace,
+                applicationTerms.interestPaymentGrace, applicationTerms.interestChargingGrace, applicationTerms.interestChargedFromDate,
+                applicationTerms.inArrearsTolerance, applicationTerms.multiDisburseLoan, applicationTerms.actualFixedEmiAmount,
+                applicationTerms.disbursementDatas, applicationTerms.maxOutstandingBalance, applicationTerms.graceOnArrearsAgeing,
+                applicationTerms.daysInMonthType, applicationTerms.daysInYearType, applicationTerms.interestRecalculationEnabled,
+                applicationTerms.rescheduleStrategyMethod, applicationTerms.interestRecalculationCompoundingMethod,
+                applicationTerms.restCalendarInstance, applicationTerms.recalculationFrequencyType,
+                applicationTerms.compoundingCalendarInstance, applicationTerms.compoundingFrequencyType,
+                applicationTerms.principalThresholdForLastInstalment, applicationTerms.installmentAmountInMultiplesOf,
+                applicationTerms.preClosureInterestCalculationStrategy, applicationTerms.loanCalendar,
+                applicationTerms.approvedPrincipal.getAmount(), loanTermVariations);
+    }
+
+    private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
+            final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
+            final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer nthDay, final DayOfWeekType weekDayType,
+            final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod,
+            final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualNominalInterestRate,
+            final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion,
+            final Money principal, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate,
+            final LocalDate calculatedRepaymentsStartingFromDate, final Integer principalGrace, final Integer interestPaymentGrace,
+            final Integer interestChargingGrace, final LocalDate interestChargedFromDate, final Money inArrearsTolerance,
+            final boolean multiDisburseLoan, final BigDecimal emiAmount, final List<DisbursementData> disbursementDatas,
+            final BigDecimal maxOutstandingBalance, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType,
+            final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled,
+            final LoanRescheduleStrategyMethod rescheduleStrategyMethod,
+            final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod,
+            final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType,
+            final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
+            final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
+            final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar,
+            BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations) {
+        this.currency = currency;
+        this.loanTermFrequency = loanTermFrequency;
+        this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
+        this.numberOfRepayments = numberOfRepayments;
+        this.repaymentEvery = repaymentEvery;
+        this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType;
+        this.nthDay = nthDay;
+        this.weekDayType = weekDayType;
+        this.amortizationMethod = amortizationMethod;
+
+        this.interestMethod = interestMethod;
+        this.interestRatePerPeriod = interestRatePerPeriod;
+        this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType;
+        this.annualNominalInterestRate = annualNominalInterestRate;
+        this.interestCalculationPeriodMethod = interestCalculationPeriodMethod;
+        this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion;
+
+        this.principal = principal;
+        this.expectedDisbursementDate = expectedDisbursementDate;
+        this.repaymentsStartingFromDate = repaymentsStartingFromDate;
+        this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate;
+
+        this.principalGrace = principalGrace;
+        this.interestPaymentGrace = interestPaymentGrace;
+        this.interestChargingGrace = interestChargingGrace;
+        this.interestChargedFromDate = interestChargedFromDate;
+
+        this.inArrearsTolerance = inArrearsTolerance;
+        this.multiDisburseLoan = multiDisburseLoan;
+        this.fixedEmiAmount = emiAmount;
+        this.actualFixedEmiAmount = emiAmount;
+        this.disbursementDatas = disbursementDatas;
+        this.maxOutstandingBalance = maxOutstandingBalance;
+        this.graceOnArrearsAgeing = graceOnArrearsAgeing;
+        this.daysInMonthType = daysInMonthType;
+        this.daysInYearType = daysInYearType;
+        this.interestRecalculationEnabled = isInterestRecalculationEnabled;
+        this.rescheduleStrategyMethod = rescheduleStrategyMethod;
+        this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
+        this.restCalendarInstance = restCalendarInstance;
+        this.compoundingCalendarInstance = compoundingCalendarInstance;
+        this.recalculationFrequencyType = recalculationFrequencyType;
+        this.compoundingFrequencyType = compoundingFrequencyType;
+        this.principalThresholdForLastInstalment = principalThresholdForLastInstalment;
+        this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
+        this.preClosureInterestCalculationStrategy = preClosureInterestCalculationStrategy;
+
+        this.loanCalendar = loanCalendar;
+        this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount);
+        this.variationsDataWrapper = new LoanTermVariationsDataWrapper(loanTermVariations);
+        this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments();
+        this.adjustPrincipalForFlatLoans = principal.zero();
+        if (this.calculatedRepaymentsStartingFromDate == null) {
+            this.seedDate = this.expectedDisbursementDate;
+        } else {
+            this.seedDate = this.calculatedRepaymentsStartingFromDate;
+        }
+    }
+
+    public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
+            final int periodNumber) {
+
+        Money adjusted = principalForPeriod;
+
+        final Money totalPrincipalRemaining = this.principal.minus(totalCumulativePrincipalToDate);
+        if (totalPrincipalRemaining.isLessThanZero()) {
+            // paid too much principal, subtract amount that overpays from
+            // principal paid for period.
+            adjusted = principalForPeriod.minus(totalPrincipalRemaining.abs());
+        } else if (this.actualFixedEmiAmount != null) {
+            final Money difference = this.principal.minus(totalCumulativePrincipalToDate);
+            final Money principalThreshold = principalForPeriod.multipliedBy(this.principalThresholdForLastInstalment).dividedBy(100,
+                    MoneyHelper.getRoundingMode());
+            if (difference.isLessThan(principalThreshold)) {
+                adjusted = principalForPeriod.plus(difference.abs());
+            }
+        } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, periodNumber)) {
+
+            final Money difference = totalCumulativePrincipalToDate.minus(this.principal);
+            if (difference.isLessThanZero()) {
+                adjusted = principalForPeriod.plus(difference.abs());
+            } else if (difference.isGreaterThanZero()) {
+                adjusted = principalForPeriod.minus(difference.abs());
+            }
+        }
+
+        return adjusted;
+    }
+
+    public Money adjustInterestIfLastRepaymentPeriod(final Money interestForThisPeriod, final Money totalCumulativeInterestToDate,
+            final Money totalInterestDueForLoan, final int periodNumber) {
+
+        Money adjusted = interestForThisPeriod;
+
+        final Money totalInterestRemaining = totalInterestDueForLoan.minus(totalCumulativeInterestToDate);
+        if (totalInterestRemaining.isLessThanZero()) {
+            // paid too much interest, subtract amount that overpays from
+            // interest paid for period.
+            adjusted = interestForThisPeriod.minus(totalInterestRemaining.abs());
+        } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, periodNumber)) {
+            final Money interestDifference = totalCumulativeInterestToDate.minus(totalInterestDueForLoan);
+            if (interestDifference.isLessThanZero()) {
+                adjusted = interestForThisPeriod.plus(interestDifference.abs());
+            } else if (interestDifference.isGreaterThanZero()) {
+                adjusted = interestForThisPeriod.minus(interestDifference.abs());
+            }
+        }
+        if (adjusted.isLessThanZero()) {
+            adjusted = adjusted.plus(adjusted);
+        }
+        return adjusted;
+    }
+
+    /**
+     * Calculates the total interest to be charged on loan taking into account
+     * grace settings.
+     * 
+     */
+    public Money calculateTotalInterestCharged(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
+
+        Money totalInterestCharged = this.principal.zero();
+
+        switch (this.interestMethod) {
+            case FLAT:
+                final Money totalInterestChargedForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
+
+                final Money totalInterestPerInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
+
+                final Money totalGraceOnInterestCharged = totalInterestPerInstallment.multiplyRetainScale(getInterestChargingGrace(),
+                        mc.getRoundingMode());
+
+                totalInterestCharged = totalInterestChargedForLoanTerm.minus(totalGraceOnInterestCharged);
+            break;
+            case DECLINING_BALANCE:
+            case INVALID:
+            break;
+        }
+
+        return totalInterestCharged;
+    }
+
+    public Money calculateTotalPrincipalForPeriod(final PaymentPeriodsInOneYearCalculator calculator, final Money outstandingBalance,
+            final int periodNumber, final MathContext mc, Money interestForThisInstallment) {
+
+        Money principalForInstallment = this.principal.zero();
+
+        switch (this.interestMethod) {
+            case FLAT:
+                principalForInstallment = calculateTotalPrincipalPerPeriodWithoutGrace(mc, periodNumber);
+            break;
+            case DECLINING_BALANCE:
+                switch (this.amortizationMethod) {
+                    case EQUAL_INSTALLMENTS:
+                        Money totalPmtForThisInstallment = pmtForInstallment(calculator, outstandingBalance, periodNumber, mc);
+                        principalForInstallment = calculatePrincipalDueForInstallment(periodNumber, totalPmtForThisInstallment,
+                                interestForThisInstallment);
+                    break;
+                    case EQUAL_PRINCIPAL:
+                        principalForInstallment = calculateEqualPrincipalDueForInstallment(mc, periodNumber);
+                    break;
+                    case INVALID:
+                    break;
+                }
+            break;
+            case INVALID:
+            break;
+        }
+
+        return principalForInstallment;
+    }
+
+    public Money pmtForInstallment(final PaymentPeriodsInOneYearCalculator calculator, final Money outstandingBalance,
+            final int periodNumber, final MathContext mc) {
+        // Calculate exact period from disbursement date
+        final LocalDate periodStartDate = getExpectedDisbursementDate().withDayOfMonth(1);
+        final LocalDate periodEndDate = getPeriodEndDate(periodStartDate);
+        // equal installments
+        final int periodsElapsed = periodNumber - 1;
+        // with periodic interest for default month and year for
+        // equal installment
+        final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc, DaysInMonthType.DAYS_30,
+                DaysInYearType.DAYS_365, periodStartDate, periodEndDate);
+        Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod(periodicInterestRateForRepaymentPeriod,
+                outstandingBalance, periodsElapsed);
+        return totalPmtForThisInstallment;
+    }
+
+    private LocalDate getPeriodEndDate(final LocalDate startDate) {
+        LocalDate dueRepaymentPeriodDate = startDate;
+        switch (this.repaymentPeriodFrequencyType) {
+            case DAYS:
+                dueRepaymentPeriodDate = startDate.plusDays(this.repaymentEvery);
+            break;
+            case WEEKS:
+                dueRepaymentPeriodDate = startDate.plusWeeks(this.repaymentEvery);
+            break;
+            case MONTHS:
+                dueRepaymentPeriodDate = startDate.plusMonths(this.repaymentEvery);
+            break;
+            case YEARS:
+                dueRepaymentPeriodDate = startDate.plusYears(this.repaymentEvery);
+            break;
+            case INVALID:
+            break;
+        }
+        return dueRepaymentPeriodDate;
+    }
+
+    public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
+            final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc,
+            final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LocalDate periodStartDate,
+            final LocalDate periodEndDate) {
+
+        Money interestForInstallment = this.principal.zero();
+        Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy();
+
+        switch (this.interestMethod) {
+            case FLAT:
+
+                switch (this.amortizationMethod) {
+                    case EQUAL_INSTALLMENTS:
+                        // average out outstanding interest over remaining
+                        // instalments where interest is applicable
+                        interestForInstallment = calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(calculator, periodNumber,
+                                mc);
+                    break;
+                    case EQUAL_PRINCIPAL:
+                        // interest follows time-value of money and is brought
+                        // forward to next applicable interest payment period
+                        final PrincipalInterest result = calculateTotalFlatInterestForPeriod(calculator, periodNumber, mc,
+                                interestBroughtForwardDueToGrace);
+                        interestForInstallment = result.interest();
+                        interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace();
+                    break;
+                    case INVALID:
+                    break;
+                }
+            break;
+            case DECLINING_BALANCE:
+
+                final Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(
+                        calculator, mc, outstandingBalance, periodStartDate, periodEndDate);
+
+                final Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace(
+                        calculator, interestCalculationGraceOnRepaymentPeriodFraction, mc, outstandingBalance, periodNumber,
+                        periodStartDate, periodEndDate);
+
+                interestForInstallment = interestForThisInstallmentAfterGrace;
+                if (interestForThisInstallmentAfterGrace.isGreaterThanZero()) {
+                    interestForInstallment = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentAfterGrace);
+                    interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero();
+                } else if (isInterestFreeGracePeriod(periodNumber)) {
+                    interestForInstallment = interestForInstallment.zero();
+                } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
+                    interestForInstallment = interestForThisInstallmentAfterGrace;
+                } else {
+                    interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentBeforeGrace);
+                }
+            break;
+            case INVALID:
+            break;
+        }
+
+        return new PrincipalInterest(null, interestForInstallment, interestBroughtForwardDueToGrace);
+    }
+
+    private final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) {
+        return periodNumber == numberOfRepayments;
+    }
+
+    /**
+     * general method to calculate totalInterestDue discounting any grace
+     * settings
+     */
+    private Money calculateTotalFlatInterestDueWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
+
+        Money totalInterestDue = this.principal.zero();
+
+        switch (this.interestMethod) {
+            case FLAT:
+                final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc);
+                totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode());
+
+            break;
+            case DECLINING_BALANCE:
+            break;
+            case INVALID:
+            break;
+        }
+
+        if (this.totalInterestDue != null) {
+            totalInterestDue = this.totalInterestDue;
+        }
+
+        return totalInterestDue;
+    }
+
+    private BigDecimal calculateFlatInterestRateForLoanTerm(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
+
+        final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0"));
+
+        final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
+        final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
+
+        final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm();
+
+        return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc)
+                .multiply(loanTermFrequencyBigDecimal);
+    }
+
+    private BigDecimal calculatePeriodsInLoanTerm() {
+
+        BigDecimal periodsInLoanTerm = BigDecimal.valueOf(this.loanTermFrequency);
+        switch (this.interestCalculationPeriodMethod) {
+            case DAILY:
+                // number of days from 'ideal disbursement' to final date
+
+                LocalDate loanStartDate = getExpectedDisbursementDate();
+                if (getInterestChargedFromDate() != null && loanStartDate.isBefore(getInterestChargedFromLocalDate())) {
+                    loanStartDate = getInterestChargedFromLocalDate();
+                }
+
+                final int periodsInLoanTermInteger = Days.daysBetween(loanStartDate, this.loanEndDate).getDays();
+                periodsInLoanTerm = BigDecimal.valueOf(periodsInLoanTermInteger);
+            break;
+            case INVALID:
+            break;
+            case SAME_AS_REPAYMENT_PERIOD:
+                LocalDate startDate = getExpectedDisbursementDate();
+                periodsInLoanTerm = calculatePeriodsBetweenDates(startDate, this.loanEndDate);
+            break;
+        }
+
+        return periodsInLoanTerm;
+    }
+
+    public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final LocalDate endDate) {
+        BigDecimal numberOfPeriods = BigDecimal.ZERO;
+        switch (this.repaymentPeriodFrequencyType) {
+            case DAYS:
+                int numberOfDays = Days.daysBetween(startDate, endDate).getDays();
+                numberOfPeriods = BigDecimal.valueOf((double) numberOfDays);
+            break;
+            case WEEKS:
+                int numberOfWeeks = Weeks.weeksBetween(startDate, endDate).getWeeks();
+                int daysLeftAfterWeeks = Days.daysBetween(startDate.plusWeeks(numberOfWeeks), endDate).getDays();
+                numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfWeeks)).add(
+                        BigDecimal.valueOf((double) daysLeftAfterWeeks / 7));
+            break;
+            case MONTHS:
+                int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
+                LocalDate startDateAfterConsideringMonths = startDate.plusMonths(numberOfMonths);
+                LocalDate endDateAfterConsideringMonths = startDate.plusMonths(numberOfMonths + 1);
+                int daysLeftAfterMonths = Days.daysBetween(startDateAfterConsideringMonths, endDate).getDays();
+                int daysInPeriodAfterMonths = Days.daysBetween(startDateAfterConsideringMonths, endDateAfterConsideringMonths).getDays();
+                numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfMonths)).add(
+                        BigDecimal.valueOf((double) daysLeftAfterMonths / daysInPeriodAfterMonths));
+            break;
+            case YEARS:
+                int numberOfYears = Years.yearsBetween(startDate, endDate).getYears();
+                LocalDate startDateAfterConsideringYears = startDate.plusYears(numberOfYears);
+                LocalDate endDateAfterConsideringYears = startDate.plusYears(numberOfYears + 1);
+                int daysLeftAfterYears = Days.daysBetween(startDateAfterConsideringYears, endDate).getDays();
+                int daysInPeriodAfterYears = Days.daysBetween(startDateAfterConsideringYears, endDateAfterConsideringYears).getDays();
+                numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfYears)).add(
+                        BigDecimal.valueOf((double) daysLeftAfterYears / daysInPeriodAfterYears));
+            break;
+            default:
+            break;
+        }
+        return numberOfPeriods;
+    }
+
+    public void updateLoanEndDate(final LocalDate loanEndDate) {
+        this.loanEndDate = loanEndDate;
+    }
+
+    private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
+
+        final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
+
+        return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments), mc.getRoundingMode());
+    }
+
+    private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc, final int periodNumber) {
+        final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment();
+        Money principalPerPeriod = this.principal.dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus(
+                this.adjustPrincipalForFlatLoans);
+        if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
+            principalPerPeriod = principalPerPeriod.zero();
+        }
+        if (!isPrincipalGraceApplicableForThisPeriod(periodNumber) && currentPeriodFixedPrincipalAmount != null) {
+            this.adjustPrincipalForFlatLoans = this.adjustPrincipalForFlatLoans.plus(principalPerPeriod.minus(
+                    currentPeriodFixedPrincipalAmount).dividedBy(this.actualNumberOfRepayments - periodNumber, mc.getRoundingMode()));
+            principalPerPeriod = this.principal.zero().plus(currentPeriodFixedPrincipalAmount);
+
+        }
+        return principalPerPeriod;
+    }
+
+    private PrincipalInterest calculateTotalFlatInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
+            final int periodNumber, final MathContext mc, final Money cumulatingInterestPaymentDueToGrace) {
+
+        Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy();
+
+        Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
+        if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) {
+            interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForInstallment);
+            interestForInstallment = interestForInstallment.zero();
+        } else if (isInterestFreeGracePeriod(periodNumber)) {
+            interestForInstallment = interestForInstallment.zero();
+        } else if (isFirstPeriodAfterInterestPaymentGracePeriod(periodNumber)) {
+            interestForInstallment = cumulatingInterestPaymentDueToGrace.plus(interestForInstallment);
+            interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero();
+        }
+
+        return new PrincipalInterest(null, interestForInstallment, interestBroughtForwardDueToGrace);
+    }
+
+    /*
+     * calculates the interest that should be due for a given scheduled loan
+     * repayment period. It takes into account GRACE periods and calculates how
+     * much interest is due per period by averaging the number of periods where
+     * interest is due and should be paid against the total known interest that
+     * is due without grace.
+     */
+    private Money calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(final PaymentPeriodsInOneYearCalculator calculator,
+            final int periodNumber, final MathContext mc) {
+
+        Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
+        if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) {
+            interestForInstallment = interestForInstallment.zero();
+        } else if (isInterestFreeGracePeriod(periodNumber)) {
+            interestForInstallment = interestForInstallment.zero();
+        } else {
+
+            final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
+
+            final Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
+
+            final Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace());
+            final Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree);
+
+            final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.actualNumberOfRepayments);
+
+            interestForInstallment = realTotalInterestForLoan
+                    .dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode());
+        }
+
+        return interestForInstallment;
+    }
+
+    private BigDecimal periodicInterestRate(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc,
+            final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, LocalDate periodStartDate, LocalDate periodEndDate) {
+
+        final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
+
+        final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0"));
+        final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
+
+        BigDecimal periodicInterestRate = BigDecimal.ZERO;
+        BigDecimal loanTermFrequencyBigDecimal = calculateLoanTermFrequency(periodStartDate, periodEndDate);
+        switch (this.interestCalculationPeriodMethod) {
+            case INVALID:
+            break;
+            case DAILY:
+                // For daily work out number of days in the period
+                BigDecimal numberOfDaysInPeriod = BigDecimal.valueOf(Days.daysBetween(periodStartDate, periodEndDate).getDays());
+
+                final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc)
+                        .divide(divisor, mc);
+
+                switch (this.repaymentPeriodFrequencyType) {
+                    case INVALID:
+                    break;
+                    case DAYS:
+                        periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
+                    break;
+                    case WEEKS:
+                        periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
+                    break;
+                    case MONTHS:
+                        if (daysInMonthType.isDaysInMonth_30()) {
+                            numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(30), mc);
+                        }
+                        periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
+                    break;
+                    case YEARS:
+                        switch (daysInYearType) {
+                            case DAYS_360:
+                                numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(360), mc);
+                            break;
+                            case DAYS_364:
+                                numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(364), mc);
+                            break;
+                            case DAYS_365:
+                                numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(365), mc);
+                            break;
+                            default:
+                            break;
+                        }
+                        periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
+                    break;
+                }
+            break;
+            case SAME_AS_REPAYMENT_PERIOD:
+                periodicInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc)
+                        .multiply(loanTermFrequencyBigDecimal);
+            break;
+        }
+
+        return periodicInterestRate;
+    }
+
+    private BigDecimal calculateLoanTermFrequency(final LocalDate periodStartDate, final LocalDate periodEndDate) {
+        BigDecimal loanTermFrequencyBigDecimal = BigDecimal.valueOf(this.repaymentEvery);
+        if (this.interestCalculationPeriodMethod.isDaily() || this.allowPartialPeriodInterestCalcualtion) {
+            loanTermFrequencyBigDecimal = calculatePeriodsBetweenDates(periodStartDate, periodEndDate);
+        }
+        return loanTermFrequencyBigDecimal;
+    }
+
+    public BigDecimal interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc,
+            final Money outstandingBalance, final LocalDate fromDate, final LocalDate toDate) {
+
+        long loanTermPeriodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue();
+        int repaymentEvery = Days.daysBetween(fromDate, toDate).getDays();
+        if (isFallingInRepaymentPeriod(fromDate, toDate)) {
+            loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
+            repaymentEvery = getPeriodsBetween(fromDate, toDate);
+        }
+
+        final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0"));
+        final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
+        final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(
+                divisor, mc);
+        BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(repaymentEvery), mc);
+        return outstandingBalance.getAmount().multiply(interestRate, mc);
+    }
+
+    private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) {
+
+        // check if daysInYears is set if so change periodsInOneYear to days set
+        // in db
+        long periodsInOneYear;
+        boolean daysInYearToUse = (this.repaymentPeriodFrequencyType.getCode().equalsIgnoreCase("periodFrequencyType.days") && !this.daysInYearType
+                .getCode().equalsIgnoreCase("DaysInYearType.actual"));
+        if (daysInYearToUse) {
+            periodsInOneYear = this.daysInYearType.getValue().longValue();
+        } else {
+            periodsInOneYear = calculator.calculate(this.repaymentPeriodFrequencyType).longValue();
+        }
+        switch (this.interestCalculationPeriodMethod) {
+            case DAILY:
+                periodsInOneYear = (!this.daysInYearType.getCode().equalsIgnoreCase("DaysInYearType.actual")) ? this.daysInYearType
+                        .getValue().longValue() : calculator.calculate(PeriodFrequencyType.DAYS).longValue();
+            break;
+            case INVALID:
+            break;
+            case SAME_AS_REPAYMENT_PERIOD:
+            break;
+        }
+
+        return periodsInOneYear;
+    }
+
+    private int calculateNumberOfRepaymentsWithPrincipalPayment() {
+        return this.actualNumberOfRepayments - getPrincipalGrace();
+    }
+
+    private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(final Integer totalNumberOfRepaymentPeriods) {
+        return totalNumberOfRepaymentPeriods - Math.max(getInterestChargingGrace(), getInterestPaymentGrace());
+    }
+
+    private Integer calculateNumberOfPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods) {
+        return totalNumberOfRepaymentPeriods - getPrincipalGrace();
+    }
+
+    public boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) {
+        return periodNumber > 0 && periodNumber <= getPrincipalGrace();
+    }
+
+    private boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) {
+        return periodNumber > 0 && periodNumber <= getInterestPaymentGrace();
+    }
+
+    private boolean isFirstPeriodAfterInterestPaymentGracePeriod(final int periodNumber) {
+        return periodNumber > 0 && periodNumber == getInterestPaymentGrace() + 1;
+    }
+
+    private boolean isInterestFreeGracePeriod(final int periodNumber) {
+        return periodNumber > 0 && periodNumber <= getInterestChargingGrace();
+    }
+
+    public Integer getPrincipalGrace() {
+        Integer graceOnPrincipalPayments = Integer.valueOf(0);
+        if (this.principalGrace != null) {
+            graceOnPrincipalPayments = this.principalGrace;
+        }
+        return graceOnPrincipalPayments;
+    }
+
+    public Integer getInterestPaymentGrace() {
+        Integer graceOnInterestPayments = Integer.valueOf(0);
+        if (this.interestPaymentGrace != null) {
+            graceOnInterestPayments = this.interestPaymentGrace;
+        }
+        return graceOnInterestPayments;
+    }
+
+    public Integer getInterestChargingGrace() {
+        Integer graceOnInterestCharged = Integer.valueOf(0);
+        if (this.interestChargingGrace != null) {
+            graceOnInterestCharged = this.interestChargingGrace;
+        }
+        return graceOnInterestCharged;
+    }
+
+    private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) {
+
+        if (getFixedEmiAmount() == null) {
+            final double futureValue = 0;
+            final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
+
+            final Integer periodsRemaining = this.actualNumberOfRepayments - periodsElapsed;
+
+            double installmentAmount = FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(),
+                    principalDouble, futureValue, false);
+
+            if (this.installmentAmountInMultiplesOf != null) {
+                installmentAmount = Money.roundToMultiplesOf(installmentAmount, this.installmentAmountInMultiplesOf);
+            }
+            setFixedEmiAmount(BigDecimal.valueOf(installmentAmount));
+        }
+        return getFixedEmiAmount().doubleValue();
+    }
+
+    private Money calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
+            final MathContext mc, final Money outstandingBalance, LocalDate periodStartDate, LocalDate periodEndDate) {
+
+        Money interestDue = Money.zero(outstandingBalance.getCurrency());
+
+        final BigDecimal periodicInterestRate = periodicInterestRate(calculator, mc, this.daysInMonthType, this.daysInYearType,
+                periodStartDate, periodEndDate);
+        interestDue = outstandingBalance.multiplyRetainScale(periodicInterestRate, mc.getRoundingMode());
+
+        return interestDue;
+    }
+
+    private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
+            final double interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance,
+            final int periodNumber, LocalDate periodStartDate, LocalDate periodEndDate) {
+
+        Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, outstandingBalance,
+                periodStartDate, periodEndDate);
+
+        if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) {
+            interest = interest.zero();
+        }
+
+        Double fraction = interestCalculationGraceOnRepaymentPeriodFraction;
+
+        if (isInterestFreeGracePeriod(periodNumber)) {
+            interest = interest.zero();
+        } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
+
+            if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) {
+                interest = interest.zero();
+                fraction = fraction - Integer.valueOf(1).doubleValue();
+
+            } else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25")
+                    && interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
+
+                final Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
+                interest = interest.minus(graceOnInterestForRepaymentPeriod);
+                fraction = Double.valueOf("0");
+            }
+        }
+
+        return interest;
+    }
+
+    private boolean isInterestFreeGracePeriodFromDate(final double interestCalculationGraceOnRepaymentPeriodFraction) {
+        return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.0");
+    }
+
+    private Money calculateEqualPrincipalDueForInstallment(final MathContext mc, final int periodNumber) {
+        Money principal = this.principal;
+        if (this.fixedPrincipalAmount == null) {
+            final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfPrincipalPaymentPeriods(this.actualNumberOfRepayments);
+            principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode());
+            this.fixedPrincipalAmount = principal.getAmount();
+        }
+        principal = Money.of(getCurrency(), getFixedPrincipalAmount());
+
+        if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
+            principal = principal.zero();
+        }
+        return principal;
+    }
+
+    public void updateFixedPrincipalAmount(final MathContext mc, final int periodNumber, final Money outstandingAmount) {
+        final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfPrincipalPaymentPeriods(this.actualNumberOfRepayments);
+        Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods - periodNumber + 1, mc.getRoundingMode());
+        this.fixedPrincipalAmount = principal.getAmount();
+    }
+
+    public void setFixedPrincipalAmount(BigDecimal fixedPrincipalAmount) {
+        this.fixedPrincipalAmount = fixedPrincipalAmount;
+    }
+
+    private Money calculatePrincipalDueForInstallment(final int periodNumber, final Money totalDuePerInstallment, final Money periodInterest) {
+
+        Money principal = totalDuePerInstallment.minus(periodInterest);
+        if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
+            principal = principal.zero();
+        }
+        return principal;
+    }
+
+    private Money calculateTotalDueForEqualInstallmentRepaymentPeriod(final BigDecimal periodicInterestRate, final Money balance,
+            final int periodsElapsed) {
+
+        final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, balance, periodsElapsed);
+
+        return Money.of(balance.getCurrency(), BigDecimal.valueOf(paymentPerRepaymentPeriod));
+    }
+
+    public LoanProductRelatedDetail toLoanProductRelatedDetail() {
+        final MonetaryCurrency currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDecimalPlaces(),
+                this.currency.getCurrencyInMultiplesOf());
+
+        return LoanProductRelatedDetail.createFrom(currency, this.principal.getAmount(), this.interestRatePerPeriod,
+                this.interestRatePeriodFrequencyType, this.annualNominalInterestRate, this.interestMethod,
+                this.interestCalculationPeriodMethod, this.allowPartialPeriodInterestCalcualtion, this.repaymentEvery,
+                this.repaymentPeriodFrequencyType, this.numberOfRepayments, this.principalGrace, this.interestPaymentGrace,
+                this.interestChargingGrace, this.amortizationMethod, this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing,
+                this.daysInMonthType.getValue(), this.daysInYearType.getValue(), this.interestRecalculationEnabled);
+    }
+
+    public Integer getLoanTermFrequency() {
+        return this.loanTermFrequency;
+    }
+
+    public PeriodFrequencyType getLoanTermPeriodFrequencyType() {
+        return this.loanTermPeriodFrequencyType;
+    }
+
+    public Integer getRepaymentEvery() {
+        return this.repaymentEvery;
+    }
+
+    public PeriodFrequencyType getRepaymentPeriodFrequencyType() {
+        return this.repaymentPeriodFrequencyType;
+    }
+
+    public Date getRepaymentStartFromDate() {
+        Date dateValue = null;
+        if (this.repaymentsStartingFromDate != null) {
+            dateValue = this.repaymentsStartingFromDate.toDate();
+        }
+        return dateValue;
+    }
+
+    public Date getInterestChargedFromDate() {
+        Date dateValue = null;
+        if (this.interestChargedFromDate != null) {
+            dateValue = this.interestChargedFromDate.toDate();
+        }
+        return dateValue;
+    }
+
+    public void setPrincipal(Money principal) {
+        this.principal = principal;
+    }
+
+    public LocalDate getInterestChargedFromLocalDate() {
+        return this.interestChargedFromDate;
+    }
+
+    public InterestMethod getInterestMethod() {
+        return this.interestMethod;
+    }
+
+    public AmortizationMethod getAmortizationMethod() {
+        return this.amortizationMethod;
+    }
+
+    public MonetaryCurrency getCurrency() {
+        return this.principal.getCurrency();
+    }
+
+    public Integer getNumberOfRepayments() {
+        return this.numberOfRepayments;
+    }
+
+    public LocalDate getExpectedDisbursementDate() {
+        return this.expectedDisbursementDate;
+    }
+
+    public LocalDate getRepaymentsStartingFromLocalDate() {
+        return this.repaymentsStartingFromDate;
+    }
+
+    public LocalDate getCalculatedRepaymentsStartingFromLocalDate() {
+        return this.calculatedRepaymentsStartingFromDate;
+    }
+
+    public Money getPrincipal() {
+        return this.principal;
+    }
+
+    public Money getApprovedPrincipal() {
+        return this.approvedPrincipal;
+    }
+
+    public List<DisbursementData> getDisbursementDatas() {
+        return this.disbursementDatas;
+    }
+
+    public boolean isMultiDisburseLoan() {
+        return this.multiDisburseLoan;
+    }
+
+    public Money getMaxOutstandingBalance() {
+        return Money.of(getCurrency(), this.maxOutstandingBalance);
+    }
+
+    public BigDecimal getFixedEmiAmount() {
+        BigDecimal fixedEmiAmount = this.fixedEmiAmount;
+        if (getCurrentPeriodFixedEmiAmount() != null) {
+            fixedEmiAmount = getCurrentPeriodFixedEmiAmount();
+        }
+        return fixedEmiAmount;
+    }
+
+    public Integer getNthDay() {
+        return this.nthDay;
+    }
+
+    public DayOfWeekType getWeekDayType() {
+        return this.weekDayType;
+    }
+
+    public void setFixedEmiAmount(BigDecimal fixedEmiAmount) {
+        this.fixedEmiAmount = fixedEmiAmount;
+    }
+
+    public void resetFixedEmiAmount() {
+        this.fixedEmiAmount = this.actualFixedEmiAmount;
+    }
+
+    public LoanRescheduleStrategyMethod getLoanRescheduleStrategyMethod() {
+        return LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT;
+    }
+
+    public boolean isInterestRecalculationEnabled() {
+        return this.interestRecalculationEnabled;
+    }
+
+    public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() {
+        return this.rescheduleStrategyMethod;
+    }
+
+    public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() {
+        return this.interestRecalculationCompoundingMethod;
+    }
+
+    public CalendarInstance getRestCalendarInstance() {
+        return this.restCalendarInstance;
+    }
+
+    private boolean isFallingInRepaymentPeriod(LocalDate fromDate, LocalDate toDate) {
+        boolean isSameAsRepaymentPeriod = false;
+        if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) {
+            switch (this.repaymentPeriodFrequencyType) {
+                case WEEKS:
+                    int days = Days.daysBetween(fromDate, toDate).getDays();
+                    isSameAsRepaymentPeriod = (days % 7) == 0;
+                break;
+                case MONTHS:
+                    boolean isFromDateOnEndDate = false;
+                    if (fromDate.getDayOfMonth() > fromDate.plusDays(1).getDayOfMonth()) {
+                        isFromDateOnEndDate = true;
+                    }
+                    boolean isToDateOnEndDate = false;
+                    if (toDate.getDayOfMonth() > toDate.plusDays(1).getDayOfMonth()) {
+                        isToDateOnEndDate = true;
+                    }
+
+                    if (isFromDateOnEndDate && isToDateOnEndDate) {
+                        isSameAsRepaymentPeriod = true;
+                    } else {
+
+                        int months = getPeriodsBetween(fromDate, toDate);
+                        fromDate = fromDate.plusMonths(months);
+                        isSameAsRepaymentPeriod = fromDate.isEqual(toDate);
+                    }
+
+                break;
+                default:
+                break;
+            }
+        }
+        return isSameAsRepaymentPeriod;
+    }
+
+    private Integer getPeriodsBetween(LocalDate fromDate, LocalDate toDate) {
+        Integer numberOfPeriods = 0;
+        PeriodType periodType = PeriodType.yearMonthDay();
+        Period difference = new Period(fromDate, toDate, periodType);
+        switch (this.repaymentPeriodFrequencyType) {
+            case DAYS:
+                numberOfPeriods = difference.getDays();
+            break;
+            case WEEKS:
+                periodType = PeriodType.weeks();
+                difference = new Period(fromDate, toDate, periodType);
+                numberOfPeriods = difference.getWeeks();
+            break;
+            case MONTHS:
+                numberOfPeriods = difference.getMonths();
+            break;
+            case YEARS:
+                numberOfPeriods = difference.getYears();
+            break;
+            default:
+            break;
+        }
+        return numberOfPeriods;
+    }
+
+    public RecalculationFrequencyType getRecalculationFrequencyType() {
+        return this.recalculationFrequencyType;
+    }
+
+    public void updateNumberOfRepayments(final Integer numberOfRepayments) {
+        this.numberOfRepayments = numberOfRepayments;
+        this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments();
+
+    }
+
+    public void updatePrincipalGrace(final Integer principalGrace) {
+        this.principalGrace = principalGrace;
+    }
+
+    public void updateInterestPaymentGrace(final Integer interestPaymentGrace) {
+        this.interestPaymentGrace = interestPaymentGrace;
+    }
+
+    public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) {
+        if (interestRatePerPeriod != null) {
+            this.interestRatePerPeriod = interestRatePerPeriod;
+        }
+    }
+
+    public void updateAnnualNominalInterestRate(BigDecimal annualNominalInterestRate) {
+        if (annualNominalInterestRate != null) {
+            this.annualNominalInterestRate = annualNominalInterestRate;
+        }
+    }
+
+    public BigDecimal getAnnualNominalInterestRate() {
+        return this.annualNominalInterestRate;
+    }
+
+    public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) {
+        if (interestChargedFromDate != null) {
+            this.interestChargedFromDate = interestChargedFromDate;
+        }
+    }
+
+    public void updateLoanTermFrequency(Integer loanTermFrequency) {
+        if (loanTermFrequency != null) {
+            this.loanTermFrequency = loanTermFrequency;
+        }
+    }
+
+    public void updateTotalInterestDue(Money totalInterestDue) {
+
+        if (totalInterestDue != null) {
+            this.totalInterestDue = totalInterestDue;
+        }
+    }
+
+    public ApplicationCurrency getApplicationCurrency() {
+        return this.currency;
+    }
+
+    public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() {
+        return this.interestCalculationPeriodMethod;
+    }
+
+    public LoanPreClosureInterestCalculationStrategy getPreClosureInterestCalculationStrategy() {
+        return this.preClosureInterestCalculationStrategy;
+    }
+
+    public CalendarInstance getCompoundingCalendarInstance() {
+        return this.compoundingCalendarInstance;
+    }
+
+    public RecalculationFrequencyType getCompoundingFrequencyType() {
+        return this.compoundingFrequencyType;
+    }
+
+    public BigDecimal getActualFixedEmiAmount() {
+        return this.actualFixedEmiAmount;
+    }
+
+    public Calendar getLoanCalendar() {
+        return loanCalendar;
+    }
+
+    public BigDecimal getFixedPrincipalAmount() {
+        BigDecimal fixedPrincipalAmount = this.fixedPrincipalAmount;
+        if (getCurrentPeriodFixedPrincipalAmount() != null) {
+            fixedPrincipalAmount = getCurrentPeriodFixedPrincipalAmount();
+        }
+        return fixedPrincipalAmount;
+    }
+
+    public LoanTermVariationsDataWrapper getLoanTermVariations() {
+        return this.variationsDataWrapper;
+    }
+
+    public BigDecimal getCurrentPeriodFixedEmiAmount() {
+        return this.currentPeriodFixedEmiAmount;
+    }
+
+    public void setCurrentPeriodFixedEmiAmount(BigDecimal currentPeriodFixedEmiAmount) {
+        this.currentPeriodFixedEmiAmount = currentPeriodFixedEmiAmount;
+    }
+
+    public BigDecimal getCurrentPeriodFixedPrincipalAmount() {
+        return this.currentPeriodFixedPrincipalAmount;
+    }
+
+    public void setCurrentPeriodFixedPrincipalAmount(BigDecimal currentPeriodFixedPrincipalAmount) {
+        this.currentPeriodFixedPrincipalAmount = currentPeriodFixedPrincipalAmount;
+    }
+
+    public Integer fetchNumberOfRepaymentsAfterExceptions() {
+        return this.actualNumberOfRepayments;
+    }
+
+    public LocalDate getSeedDate() {
+        return this.seedDate;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistory.java
new file mode 100644
index 0000000..4f962aa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistory.java
@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_repayment_schedule_history")
+public class LoanRepaymentScheduleHistory extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "loan_id")
+    private Loan loan;
+
+    @OneToOne(optional = true)
+    @JoinColumn(name = "loan_reschedule_request_id")
+    private LoanRescheduleRequest loanRescheduleRequest;
+
+    @Column(name = "installment", nullable = false)
+    private Integer installmentNumber;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "fromdate", nullable = true)
+    private Date fromDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "duedate", nullable = false)
+    private Date dueDate;
+
+    @Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal principal;
+
+    @Column(name = "interest_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal interestCharged;
+
+    @Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal feeChargesCharged;
+
+    @Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal penaltyCharges;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "created_date")
+    private Date createdOnDate;
+
+    @ManyToOne
+    @JoinColumn(name = "createdby_id")
+    private AppUser createdByUser;
+
+    @ManyToOne
+    @JoinColumn(name = "lastmodifiedby_id")
+    private AppUser lastModifiedByUser;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "lastmodified_date")
+    private Date lastModifiedOnDate;
+
+    @Column(name = "version")
+    private Integer version;
+
+    /**
+     * LoanRepaymentScheduleHistory constructor
+     **/
+    protected LoanRepaymentScheduleHistory() {}
+
+    /**
+     * LoanRepaymentScheduleHistory constructor
+     **/
+    private LoanRepaymentScheduleHistory(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
+            final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
+            final BigDecimal interestCharged, final BigDecimal feeChargesCharged, final BigDecimal penaltyCharges,
+            final Date createdOnDate, final AppUser createdByUser, final AppUser lastModifiedByUser, final Date lastModifiedOnDate,
+            final Integer version) {
+
+        this.loan = loan;
+        this.loanRescheduleRequest = loanRescheduleRequest;
+        this.installmentNumber = installmentNumber;
+        this.fromDate = fromDate;
+        this.dueDate = dueDate;
+        this.principal = principal;
+        this.interestCharged = interestCharged;
+        this.feeChargesCharged = feeChargesCharged;
+        this.penaltyCharges = penaltyCharges;
+        this.createdOnDate = createdOnDate;
+        this.createdByUser = createdByUser;
+        this.lastModifiedByUser = lastModifiedByUser;
+        this.lastModifiedOnDate = lastModifiedOnDate;
+        this.version = version;
+    }
+
+    /**
+     * @return an instance of the LoanRepaymentScheduleHistory class
+     **/
+    public static LoanRepaymentScheduleHistory instance(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
+            final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
+            final BigDecimal interestCharged, final BigDecimal feeChargesCharged, final BigDecimal penaltyCharges,
+            final Date createdOnDate, final AppUser createdByUser, final AppUser lastModifiedByUser, final Date lastModifiedOnDate,
+            final Integer version) {
+
+        return new LoanRepaymentScheduleHistory(loan, loanRescheduleRequest, installmentNumber, fromDate, dueDate, principal,
+                interestCharged, feeChargesCharged, penaltyCharges, createdOnDate, createdByUser, lastModifiedByUser, lastModifiedOnDate,
+                version);
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistoryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistoryRepository.java
new file mode 100644
index 0000000..948e720
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistoryRepository.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.loanaccount.loanschedule.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanRepaymentScheduleHistoryRepository extends JpaRepository<LoanRepaymentScheduleHistory, Long>, JpaSpecificationExecutor<LoanRepaymentScheduleHistory> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java
new file mode 100644
index 0000000..822632d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.MathContext;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import org.joda.time.LocalDate;
+
+public interface LoanScheduleGenerator {
+
+    LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
+            final HolidayDetailDTO holidayDetailDTO);
+
+    LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
+            final HolidayDetailDTO holidayDetailDTO, List<LoanTransaction> transactions,
+            LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, LocalDate rescheduleFrom);
+
+    LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
+            LoanApplicationTerms loanApplicationTerms, MathContext mc, Set<LoanCharge> charges, HolidayDetailDTO holidayDetailDTO,
+            List<LoanTransaction> loanTransactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
+
+    LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
+            final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance,
+            CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, FloatingRateDTO floatingRateDTO);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorFactory.java
new file mode 100644
index 0000000..8ce768f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorFactory.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.loanaccount.loanschedule.domain;
+
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+
+public interface LoanScheduleGeneratorFactory {
+
+    LoanScheduleGenerator create(InterestMethod interestMethod);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java
new file mode 100644
index 0000000..22f8bac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java
@@ -0,0 +1,123 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+
+/**
+ * Domain representation of a Loan Schedule (not used for persistence)
+ */
+public final class LoanScheduleModel {
+
+    private final Collection<LoanScheduleModelPeriod> periods;
+    private final ApplicationCurrency applicationCurrency;
+    private final int loanTermInDays;
+    private final Money totalPrincipalDisbursed;
+    private final BigDecimal totalPrincipalExpected;
+    private final BigDecimal totalPrincipalPaid;
+    private final BigDecimal totalInterestCharged;
+    private final BigDecimal totalFeeChargesCharged;
+    private final BigDecimal totalPenaltyChargesCharged;
+    private final BigDecimal totalRepaymentExpected;
+    private final BigDecimal totalOutstanding;
+
+    public static LoanScheduleModel from(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
+            final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
+            final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
+            final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
+
+        return new LoanScheduleModel(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
+                totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
+                totalOutstanding);
+    }
+
+    public static LoanScheduleModel withOverdueChargeUpdation(final Collection<LoanScheduleModelPeriod> periods,
+            final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) {
+
+        return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
+                loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid,
+                loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, totalPenaltyChargesCharged,
+                loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
+    }
+
+    public static LoanScheduleModel withLoanScheduleModelPeriods(final Collection<LoanScheduleModelPeriod> periods,
+            final LoanScheduleModel loanScheduleModel) {
+
+        return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
+                loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid,
+                loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged,
+                loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
+    }
+
+    private LoanScheduleModel(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
+            final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
+            final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
+            final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
+        this.periods = periods;
+        this.applicationCurrency = applicationCurrency;
+        this.loanTermInDays = loanTermInDays;
+        this.totalPrincipalDisbursed = principalDisbursed;
+        this.totalPrincipalExpected = totalPrincipalExpected;
+        this.totalPrincipalPaid = totalPrincipalPaid;
+        this.totalInterestCharged = totalInterestCharged;
+        this.totalFeeChargesCharged = totalFeeChargesCharged;
+        this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
+        this.totalRepaymentExpected = totalRepaymentExpected;
+        this.totalOutstanding = totalOutstanding;
+    }
+
+    public LoanScheduleData toData() {
+
+        final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal();
+        final Integer inMultiplesOf = this.totalPrincipalDisbursed.getCurrencyInMultiplesOf();
+        final CurrencyData currency = this.applicationCurrency.toData(decimalPlaces, inMultiplesOf);
+
+        final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<>();
+        for (final LoanScheduleModelPeriod modelPeriod : this.periods) {
+            periodsData.add(modelPeriod.toData());
+        }
+
+        final BigDecimal totalWaived = null;
+        final BigDecimal totalWrittenOff = null;
+        final BigDecimal totalRepayment = null;
+        final BigDecimal totalPaidInAdvance = null;
+        final BigDecimal totalPaidLate = null;
+
+        return new LoanScheduleData(currency, periodsData, this.loanTermInDays, this.totalPrincipalDisbursed.getAmount(),
+                this.totalPrincipalExpected, this.totalPrincipalPaid, this.totalInterestCharged, this.totalFeeChargesCharged,
+                this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment,
+                totalPaidInAdvance, totalPaidLate, this.totalOutstanding);
+    }
+
+    public Collection<LoanScheduleModelPeriod> getPeriods() {
+        return this.periods;
+    }
+
+    public BigDecimal getTotalPenaltyChargesCharged() {
+        return this.totalPenaltyChargesCharged;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
new file mode 100644
index 0000000..acb33a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
@@ -0,0 +1,125 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.joda.time.LocalDate;
+
+/**
+ * Domain representation of a Loan Schedule Disbursement Period (not used for
+ * persistence)
+ */
+public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleModelPeriod {
+
+    @SuppressWarnings("unused")
+    private final Integer periodNumber;
+    private final LocalDate disbursementDate;
+    private final Money principalDisbursed;
+    private final BigDecimal chargesDueAtTimeOfDisbursement;
+
+    public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
+            final BigDecimal chargesDueAtTimeOfDisbursement) {
+
+        final int periodNumber = 0;
+        return new LoanScheduleModelDisbursementPeriod(periodNumber, loanApplicationTerms.getExpectedDisbursementDate(),
+                loanApplicationTerms.getPrincipal(), chargesDueAtTimeOfDisbursement);
+    }
+
+    public static LoanScheduleModelDisbursementPeriod disbursement(final LocalDate disbursementDate, final Money principalDisbursed,
+            final BigDecimal chargesDueAtTimeOfDisbursement) {
+        return new LoanScheduleModelDisbursementPeriod(null, disbursementDate, principalDisbursed, chargesDueAtTimeOfDisbursement);
+    }
+
+    private LoanScheduleModelDisbursementPeriod(final Integer periodNumber, final LocalDate disbursementDate,
+            final Money principalDisbursed, final BigDecimal chargesDueAtTimeOfDisbursement) {
+        this.periodNumber = periodNumber;
+        this.disbursementDate = disbursementDate;
+        this.principalDisbursed = principalDisbursed;
+        this.chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement;
+    }
+
+    @Override
+    public LoanSchedulePeriodData toData() {
+        return LoanSchedulePeriodData.disbursementOnlyPeriod(this.disbursementDate, this.principalDisbursed.getAmount(),
+                this.chargesDueAtTimeOfDisbursement, false);
+    }
+
+    @Override
+    public boolean isRepaymentPeriod() {
+        return false;
+    }
+
+    @Override
+    public Integer periodNumber() {
+        return null;
+    }
+
+    @Override
+    public LocalDate periodFromDate() {
+        return null;
+    }
+
+    @Override
+    public LocalDate periodDueDate() {
+        return null;
+    }
+
+    @Override
+    public BigDecimal principalDue() {
+        return null;
+    }
+
+    @Override
+    public BigDecimal interestDue() {
+        return null;
+    }
+
+    @Override
+    public BigDecimal feeChargesDue() {
+        return null;
+    }
+
+    @Override
+    public BigDecimal penaltyChargesDue() {
+        return null;
+    }
+
+    @Override
+    public void addLoanCharges(@SuppressWarnings("unused") BigDecimal feeCharge, @SuppressWarnings("unused") BigDecimal penaltyCharge) {
+        return;
+    }
+
+    @Override
+    public boolean isRecalculatedInterestComponent() {
+        return false;
+    }
+
+    @Override
+    public void addPrincipalAmount(@SuppressWarnings("unused") Money principalDue) {
+        return;
+    }
+
+    @Override
+    public void addInterestAmount(@SuppressWarnings("unused") Money principalDue) {
+        return;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java
new file mode 100644
index 0000000..7792416
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.joda.time.LocalDate;
+
+public interface LoanScheduleModelPeriod {
+
+    LoanSchedulePeriodData toData();
+
+    boolean isRepaymentPeriod();
+
+    Integer periodNumber();
+
+    LocalDate periodFromDate();
+
+    LocalDate periodDueDate();
+
+    BigDecimal principalDue();
+
+    BigDecimal interestDue();
+
+    BigDecimal feeChargesDue();
+
+    BigDecimal penaltyChargesDue();
+
+    void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge);
+    
+    boolean isRecalculatedInterestComponent();
+
+    void addPrincipalAmount(Money principalDue);
+    
+    void addInterestAmount(Money interestDue);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
new file mode 100644
index 0000000..5b8208e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
@@ -0,0 +1,157 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.joda.time.LocalDate;
+
+/**
+ * Domain representation of a Loan Schedule Repayment Period (not used for
+ * persistence)
+ */
+public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModelPeriod {
+
+    private final int periodNumber;
+    private final LocalDate fromDate;
+    private final LocalDate dueDate;
+    private Money principalDue;
+    private final Money outstandingLoanBalance;
+    private Money interestDue;
+    private Money feeChargesDue;
+    private Money penaltyChargesDue;
+    private Money totalDue;
+    private final boolean recalculatedInterestComponent;
+
+    public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
+            final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
+            final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent) {
+
+        return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance,
+                interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent);
+    }
+
+    public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate,
+            final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue,
+            final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent) {
+        this.periodNumber = periodNumber;
+        this.fromDate = fromDate;
+        this.dueDate = dueDate;
+        this.principalDue = principalDue;
+        this.outstandingLoanBalance = outstandingLoanBalance;
+        this.interestDue = interestDue;
+        this.feeChargesDue = feeChargesDue;
+        this.penaltyChargesDue = penaltyChargesDue;
+        this.totalDue = totalDue;
+        this.recalculatedInterestComponent = recalculatedInterestComponent;
+    }
+
+    @Override
+    public LoanSchedulePeriodData toData() {
+        return LoanSchedulePeriodData.repaymentOnlyPeriod(this.periodNumber, this.fromDate, this.dueDate, this.principalDue.getAmount(),
+                this.outstandingLoanBalance.getAmount(), this.interestDue.getAmount(), this.feeChargesDue.getAmount(),
+                this.penaltyChargesDue.getAmount(), this.totalDue.getAmount(), this.principalDue.plus(this.interestDue).getAmount());
+    }
+
+    @Override
+    public boolean isRepaymentPeriod() {
+        return true;
+    }
+
+    @Override
+    public Integer periodNumber() {
+        return this.periodNumber;
+    }
+
+    @Override
+    public LocalDate periodFromDate() {
+        return this.fromDate;
+    }
+
+    @Override
+    public LocalDate periodDueDate() {
+        return this.dueDate;
+    }
+
+    @Override
+    public BigDecimal principalDue() {
+        BigDecimal value = null;
+        if (this.principalDue != null) {
+            value = this.principalDue.getAmount();
+        }
+
+        return value;
+    }
+
+    @Override
+    public BigDecimal interestDue() {
+        BigDecimal value = null;
+        if (this.interestDue != null) {
+            value = this.interestDue.getAmount();
+        }
+
+        return value;
+    }
+
+    @Override
+    public BigDecimal feeChargesDue() {
+        BigDecimal value = null;
+        if (this.feeChargesDue != null) {
+            value = this.feeChargesDue.getAmount();
+        }
+
+        return value;
+    }
+
+    @Override
+    public BigDecimal penaltyChargesDue() {
+        BigDecimal value = null;
+        if (this.penaltyChargesDue != null) {
+            value = this.penaltyChargesDue.getAmount();
+        }
+
+        return value;
+    }
+
+    @Override
+    public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
+        this.feeChargesDue = this.feeChargesDue.plus(feeCharge);
+        this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge);
+        this.totalDue = this.totalDue.plus(feeCharge).plus(penaltyCharge);
+    }
+
+    @Override
+    public void addPrincipalAmount(final Money principalDue) {
+        this.principalDue = this.principalDue.plus(principalDue);
+        this.totalDue = this.totalDue.plus(principalDue);
+    }
+
+    @Override
+    public boolean isRecalculatedInterestComponent() {
+        return this.recalculatedInterestComponent;
+    }
+
+    @Override
+    public void addInterestAmount(Money interestDue) {
+        this.interestDue = this.interestDue.plus(interestDue);
+        this.totalDue = this.totalDue.plus(principalDue);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PaymentPeriodsInOneYearCalculator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PaymentPeriodsInOneYearCalculator.java
new file mode 100644
index 0000000..7568fb0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PaymentPeriodsInOneYearCalculator.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.joda.time.LocalDate;
+
+public interface PaymentPeriodsInOneYearCalculator {
+
+    Integer calculate(PeriodFrequencyType repaymentFrequencyType);
+
+    double calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate,
+            LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaidEvery);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PrincipalInterest.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PrincipalInterest.java
new file mode 100644
index 0000000..63c77aa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PrincipalInterest.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+public class PrincipalInterest {
+
+    private final Money principal;
+    private final Money interest;
+    private final Money interestPaymentDueToGrace;
+
+    public PrincipalInterest(final Money principal, final Money interest, final Money interestPaymentDueToGrace) {
+        this.principal = principal;
+        this.interest = interest;
+        this.interestPaymentDueToGrace = interestPaymentDueToGrace;
+    }
+
+    public Money principal() {
+        return this.principal;
+    }
+
+    public Money interest() {
+        return this.interest;
+    }
+
+    public Money interestPaymentDueToGrace() {
+        return this.interestPaymentDueToGrace;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/RecalculatedSchedule.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/RecalculatedSchedule.java
new file mode 100755
index 0000000..a959c60
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/RecalculatedSchedule.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+public class RecalculatedSchedule {
+
+    private final LoanScheduleModel loanScheduleModel;
+    private final Integer installmentNumber;
+
+    public RecalculatedSchedule(final LoanScheduleModel loanScheduleModel, final Integer installmentNumber) {
+        this.loanScheduleModel = loanScheduleModel;
+        this.installmentNumber = installmentNumber;
+    }
+
+    public LoanScheduleModel getLoanScheduleModel() {
+        return this.loanScheduleModel;
+    }
+
+    public Integer getInstallmentNumber() {
+        return this.installmentNumber;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java
new file mode 100755
index 0000000..40a5336
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.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.loanaccount.loanschedule.domain;
+
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.joda.time.LocalDate;
+
+public class RecalculationDetail {
+
+    private LocalDate transactionDate;
+    private boolean isProcessed;
+    private LoanTransaction transaction;
+
+    public RecalculationDetail(final LocalDate transactionDate, final LoanTransaction transaction) {
+        this.transactionDate = transactionDate;
+        this.transaction = transaction;
+    }
+
+    public LocalDate getTransactionDate() {
+        return this.transactionDate;
+    }
+
+    public LoanTransaction getTransaction() {
+        return this.transaction;
+    }
+
+    public boolean isProcessed() {
+        return this.isProcessed;
+    }
+
+    public void setProcessed(boolean isProcessed) {
+        this.isProcessed = isProcessed;
+    }
+}
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
new file mode 100644
index 0000000..3761ce0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+
+import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.joda.time.LocalDate;
+
+public interface ScheduledDateGenerator {
+
+    LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO);
+
+    LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
+            final LocalDate firstRepaymentDate);
+
+    LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment,
+            final HolidayDetailDTO holidayDetailDTO);
+
+    LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms,
+            final HolidayDetailDTO holidayDetailDTO);
+
+    LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, Integer nthDay,
+            DayOfWeekType dayOfWeek);
+
+    Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date);
+
+    LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms,
+            final HolidayDetailDTO holidayDetailDTO);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/MultiDisbursementDisbursementDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/MultiDisbursementDisbursementDateException.java
new file mode 100644
index 0000000..10d514c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/MultiDisbursementDisbursementDateException.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.loanaccount.loanschedule.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class MultiDisbursementDisbursementDateException extends AbstractPlatformDomainRuleException {
+
+    public MultiDisbursementDisbursementDateException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.loanschedule.out.of.schedule.dusbursement.date", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/MultiDisbursementEmiAmountException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/MultiDisbursementEmiAmountException.java
new file mode 100644
index 0000000..7e99f0e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/MultiDisbursementEmiAmountException.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.loanaccount.loanschedule.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class MultiDisbursementEmiAmountException extends AbstractPlatformDomainRuleException {
+
+    public MultiDisbursementEmiAmountException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.loanschedule.emi.amount.must.be.greter.than.interest", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/MultiDisbursementOutstandingAmoutException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/MultiDisbursementOutstandingAmoutException.java
new file mode 100644
index 0000000..d2755d6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/MultiDisbursementOutstandingAmoutException.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.loanaccount.loanschedule.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class MultiDisbursementOutstandingAmoutException extends AbstractPlatformDomainRuleException {
+
+    public MultiDisbursementOutstandingAmoutException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.loanschedule.exceeding.max.outstanding.balance", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/ScheduleDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/ScheduleDateException.java
new file mode 100644
index 0000000..fea7c06
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/exception/ScheduleDateException.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.loanaccount.loanschedule.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ScheduleDateException extends AbstractPlatformDomainRuleException {
+
+    public ScheduleDateException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.loanschedule.schedule.due.date.not.valid", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
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
new file mode 100644
index 0000000..f6db0d2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -0,0 +1,1021 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.service;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
+import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
+import org.apache.fineract.organisation.holiday.service.HolidayUtil;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.exception.CalendarNotFoundException;
+import org.apache.fineract.portfolio.calendar.exception.MeetingFrequencyMismatchException;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
+import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+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.LoanDisbursementDetails;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationsComparator;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
+import org.apache.fineract.portfolio.loanaccount.exception.MinDaysBetweenDisbursalAndFirstRepaymentViolationException;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
+import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductInterestRecalculationDetails;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductVariableInstallmentConfig;
+import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class LoanScheduleAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final LoanProductRepository loanProductRepository;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
+    private final LoanChargeAssembler loanChargeAssembler;
+    private final LoanScheduleGeneratorFactory loanScheduleFactory;
+    private final AprCalculator aprCalculator;
+    private final CalendarRepository calendarRepository;
+    private final HolidayRepository holidayRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final ClientRepositoryWrapper clientRepository;
+    private final GroupRepositoryWrapper groupRepository;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+    private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
+    private final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final PlatformSecurityContext context;
+    private final LoanUtilService loanUtilService;
+
+    @Autowired
+    public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
+            final LoanScheduleGeneratorFactory loanScheduleFactory, final AprCalculator aprCalculator,
+            final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository,
+            final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
+            final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository,
+            final WorkingDaysRepositoryWrapper workingDaysRepository,
+            final FloatingRatesReadPlatformService floatingRatesReadPlatformService,
+            final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator,
+            final CalendarInstanceRepository calendarInstanceRepository, final PlatformSecurityContext context,
+            final LoanUtilService loanUtilService) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.loanProductRepository = loanProductRepository;
+        this.applicationCurrencyRepository = applicationCurrencyRepository;
+        this.loanScheduleFactory = loanScheduleFactory;
+        this.aprCalculator = aprCalculator;
+        this.loanChargeAssembler = loanChargeAssembler;
+        this.calendarRepository = calendarRepository;
+        this.holidayRepository = holidayRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.clientRepository = clientRepository;
+        this.groupRepository = groupRepository;
+        this.workingDaysRepository = workingDaysRepository;
+        this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
+        this.variableLoanScheduleFromApiJsonValidator = variableLoanScheduleFromApiJsonValidator;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.context = context;
+        this.loanUtilService = loanUtilService;
+    }
+
+    public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
+        final Long loanProductId = this.fromApiJsonHelper.extractLongNamed("productId", element);
+
+        final LoanProduct loanProduct = this.loanProductRepository.findOne(loanProductId);
+        if (loanProduct == null) { throw new LoanProductNotFoundException(loanProductId); }
+
+        return assembleLoanApplicationTermsFrom(element, loanProduct);
+    }
+
+    private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement element, final LoanProduct loanProduct) {
+
+        final MonetaryCurrency currency = loanProduct.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+
+        // loan terms
+        final Integer loanTermFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequency", element);
+        final Integer loanTermFrequencyType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequencyType", element);
+        final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(loanTermFrequencyType);
+
+        final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
+        final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element);
+        final Integer repaymentFrequencyType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element);
+        final PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyType);
+        final Integer nthDay = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyNthDayType", element);
+        final Integer dayOfWeek = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyDayOfWeekType", element);
+        final DayOfWeekType weekDayType = DayOfWeekType.fromInt(dayOfWeek);
+
+        final Integer amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element);
+        final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(amortizationType);
+
+        // interest terms
+        final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element);
+        final InterestMethod interestMethod = InterestMethod.fromInt(interestType);
+
+        final Integer interestCalculationPeriodType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType",
+                element);
+        final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod
+                .fromInt(interestCalculationPeriodType);
+        Boolean allowPartialPeriodInterestCalcualtion = this.fromApiJsonHelper.extractBooleanNamed(
+                LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, element);
+        if (allowPartialPeriodInterestCalcualtion == null) {
+            allowPartialPeriodInterestCalcualtion = loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalcualtion();
+        }
+
+        final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element);
+        final PeriodFrequencyType interestRatePeriodFrequencyType = loanProduct.getInterestPeriodFrequencyType();
+
+        BigDecimal annualNominalInterestRate = BigDecimal.ZERO;
+        if (interestRatePerPeriod != null) {
+            annualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, interestRatePerPeriod);
+        }
+
+        // disbursement details
+        final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
+        final Money principalMoney = Money.of(currency, principal);
+
+        final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
+        final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element);
+        LocalDate calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate;
+
+        final Boolean synchDisbursement = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
+        final Long calendarId = this.fromApiJsonHelper.extractLongNamed("calendarId", element);
+        Calendar calendar = null;
+
+        final String loanTypeParameterName = "loanType";
+        final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
+
+        final AccountType loanType = AccountType.fromName(loanTypeStr);
+
+        /*
+         * If it is JLG loan/Group Loan then make sure loan frequency is same as
+         * Group/Center meeting frequency or multiple of it. TODO: Check should
+         * be either same frequency or loan freq is multiple of center/group
+         * meeting freq multiples
+         */
+        if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendarId != null) {
+            calendar = this.calendarRepository.findOne(calendarId);
+            if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
+            final PeriodFrequencyType meetingPeriodFrequency = CalendarUtils.getMeetingPeriodFrequencyType(calendar.getRecurrence());
+            validateRepaymentFrequencyIsSameAsMeetingFrequency(meetingPeriodFrequency.getValue(), repaymentFrequencyType,
+                    CalendarUtils.getInterval(calendar.getRecurrence()), repaymentEvery);
+        }
+
+        /*
+         * If user has not passed the first repayments date then then derive the
+         * same based on loan type.
+         */
+        if (calculatedRepaymentsStartingFromDate == null) {
+            calculatedRepaymentsStartingFromDate = deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
+                    repaymentPeriodFrequencyType, loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar);
+        }
+
+        /*
+         * If it is JLG loan/Group Loan synched with a meeting, then make sure
+         * first repayment falls on meeting date
+         */
+        if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendar != null) {
+            validateRepaymentsStartDateWithMeetingDates(calculatedRepaymentsStartingFromDate, calendar);
+            /*
+             * If disbursement is synced on meeting, make sure disbursement date
+             * is on a meeting date
+             */
+            if (synchDisbursement != null && synchDisbursement.booleanValue()) {
+                validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar);
+            }
+        }
+
+        validateMinimumDaysBetweenDisbursalAndFirstRepayment(expectedDisbursementDate, calculatedRepaymentsStartingFromDate,
+                loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment());
+
+        // grace details
+        final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
+        final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
+        final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
+        final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element);
+
+        final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                LoanProductConstants.graceOnArrearsAgeingParameterName, element);
+
+        // other
+        final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
+        final Money inArrearsToleranceMoney = Money.of(currency, inArrearsTolerance);
+
+        final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
+                element);
+        final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                LoanApiConstants.maxOutstandingBalanceParameterName, element);
+
+        final List<DisbursementData> disbursementDatas = fetchDisbursementData(element.getAsJsonObject());
+
+        /**
+         * Interest recalculation settings copy from product definition
+         */
+        final DaysInMonthType daysInMonthType = loanProduct.fetchDaysInMonthType();
+
+        final DaysInYearType daysInYearType = loanProduct.fetchDaysInYearType();
+
+        final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
+        RecalculationFrequencyType recalculationFrequencyType = null;
+        CalendarInstance restCalendarInstance = null;
+        RecalculationFrequencyType compoundingFrequencyType = null;
+        CalendarInstance compoundingCalendarInstance = null;
+        if (isInterestRecalculationEnabled) {
+            LoanProductInterestRecalculationDetails loanProductInterestRecalculationDetails = loanProduct
+                    .getProductInterestRecalculationDetails();
+            recalculationFrequencyType = loanProductInterestRecalculationDetails.getRestFrequencyType();
+            if (recalculationFrequencyType.isSameAsRepayment()) {
+                restCalendarInstance = createCalendarForSameAsRepayment(repaymentEvery, repaymentPeriodFrequencyType,
+                        expectedDisbursementDate);
+            } else {
+                LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                        LoanProductConstants.recalculationRestFrequencyDateParamName, element);
+                if (calendarStartDate == null) {
+                    calendarStartDate = expectedDisbursementDate;
+                }
+                restCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, recalculationFrequencyType,
+                        loanProductInterestRecalculationDetails.getRestInterval());
+            }
+            InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod
+                    .fromInt(loanProductInterestRecalculationDetails.getInterestRecalculationCompoundingMethod());
+            if (compoundingMethod.isCompoundingEnabled()) {
+                compoundingFrequencyType = loanProductInterestRecalculationDetails.getCompoundingFrequencyType();
+                if (compoundingFrequencyType.isSameAsRepayment()) {
+                    compoundingCalendarInstance = createCalendarForSameAsRepayment(repaymentEvery, repaymentPeriodFrequencyType,
+                            expectedDisbursementDate);
+                } else {
+                    LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                            LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element);
+                    if (calendarStartDate == null) {
+                        calendarStartDate = expectedDisbursementDate;
+                    }
+                    compoundingCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, compoundingFrequencyType,
+                            loanProductInterestRecalculationDetails.getCompoundingInterval());
+                }
+
+            }
+        }
+
+        final BigDecimal principalThresholdForLastInstalment = loanProduct.getPrincipalThresholdForLastInstallment();
+
+        final Integer installmentAmountInMultiplesOf = loanProduct.getInstallmentAmountInMultiplesOf();
+
+        List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
+        if (loanProduct.isLinkedToFloatingInterestRate()) {
+            final BigDecimal interestRateDiff = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    LoanApiConstants.interestRateDifferentialParameterName, element);
+            final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(
+                    LoanApiConstants.isFloatingInterestRateParameterName, element);
+            List<FloatingRatePeriodData> baseLendingRatePeriods = null;
+            try {
+                baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
+            } catch (final FloatingRateNotFoundException ex) {
+                // Do not do anything
+            }
+            FloatingRateDTO floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, expectedDisbursementDate, interestRateDiff,
+                    baseLendingRatePeriods);
+            Collection<FloatingRatePeriodData> applicableRates = loanProduct.fetchInterestRates(floatingRateDTO);
+
+            LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant();
+            final LocalDate dateValue = null;
+            final boolean isSpecificToInstallment = false;
+            for (FloatingRatePeriodData periodData : applicableRates) {
+                LoanTermVariationsData loanTermVariation = new LoanTermVariationsData(
+                        LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(),
+                        periodData.getInterestRate(), dateValue, isSpecificToInstallment);
+                if (!interestRateStartDate.isBefore(periodData.getFromDateAsLocalDate())) {
+                    interestRateStartDate = periodData.getFromDateAsLocalDate();
+                    annualNominalInterestRate = periodData.getInterestRate();
+                }
+                loanTermVariations.add(loanTermVariation);
+            }
+        }
+
+        return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
+                repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
+                interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
+                allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
+                calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
+                interestChargedFromDate, inArrearsToleranceMoney, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas,
+                maxOutstandingBalance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
+                recalculationFrequencyType, restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType,
+                principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(),
+                calendar, BigDecimal.ZERO, loanTermVariations);
+    }
+
+    private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
+            final PeriodFrequencyType repaymentPeriodFrequencyType, final LocalDate expectedDisbursementDate) {
+
+        CalendarInstance restCalendarInstance = createInterestRecalculationCalendarInstance(expectedDisbursementDate, repaymentEvery,
+                CalendarFrequencyType.from(repaymentPeriodFrequencyType));
+        return restCalendarInstance;
+    }
+
+    private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate,
+            final RecalculationFrequencyType recalculationFrequencyType, final Integer frequency) {
+
+        CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID;
+        switch (recalculationFrequencyType) {
+            case DAILY:
+                calendarFrequencyType = CalendarFrequencyType.DAILY;
+            break;
+            case MONTHLY:
+                calendarFrequencyType = CalendarFrequencyType.MONTHLY;
+            break;
+            case WEEKLY:
+                calendarFrequencyType = CalendarFrequencyType.WEEKLY;
+            break;
+            default:
+            break;
+        }
+
+        return createInterestRecalculationCalendarInstance(calendarStartDate, frequency, calendarFrequencyType);
+    }
+
+    private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate, final Integer frequency,
+            CalendarFrequencyType calendarFrequencyType) {
+        final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
+        final String title = "loan_recalculation_detail";
+        final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(),
+                calendarFrequencyType, frequency, repeatsOnDay);
+        return CalendarInstance.from(calendar, null, CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
+    }
+
+    private List<DisbursementData> fetchDisbursementData(final JsonObject command) {
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
+        final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
+        List<DisbursementData> disbursementDatas = new ArrayList<>();
+        if (command.has(LoanApiConstants.disbursementDataParameterName)) {
+            final JsonArray disbursementDataArray = command.getAsJsonArray(LoanApiConstants.disbursementDataParameterName);
+            if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject();
+                    LocalDate expectedDisbursementDate = null;
+                    BigDecimal principal = null;
+
+                    if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)) {
+                        expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                                LoanApiConstants.disbursementDateParameterName, jsonObject, dateFormat, locale);
+                    }
+                    if (jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)
+                            && jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
+                            && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
+                        principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
+                    }
+                    disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null, null));
+                    i++;
+                } while (i < disbursementDataArray.size());
+            }
+        }
+        return disbursementDatas;
+    }
+
+    private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar) {
+        if (repaymentsStartingFromDate != null
+                && !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(),
+                        repaymentsStartingFromDate)) {
+            final String errorMessage = "First repayment date '" + repaymentsStartingFromDate + "' do not fall on a meeting date";
+            throw new LoanApplicationDateException("first.repayment.date.do.not.match.meeting.date", errorMessage,
+                    repaymentsStartingFromDate);
+        }
+    }
+
+    public void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar) {
+        // disbursement date should fall on a meeting date
+        if (!calendar.isValidRecurringDate(expectedDisbursementDate)) {
+            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);
+        }
+
+    }
+
+    private void validateRepaymentFrequencyIsSameAsMeetingFrequency(final Integer meetingFrequency, final Integer repaymentFrequency,
+            final Integer meetingInterval, final Integer repaymentInterval) {
+        // meeting with daily frequency should allow loan products with any
+        // frequency.
+        if (!PeriodFrequencyType.DAYS.getValue().equals(meetingFrequency)) {
+            // repayment frequency must match with meeting frequency
+            if (!meetingFrequency.equals(repaymentFrequency)) {
+                throw new MeetingFrequencyMismatchException("loanapplication.repayment.frequency",
+                        "Loan repayment frequency period must match that of meeting frequency period", repaymentFrequency);
+            } else if (meetingFrequency.equals(repaymentFrequency)) {
+                // repayment frequency is same as meeting frequency repayment
+                // interval should be same or multiple of meeting interval
+                if (repaymentInterval % meetingInterval != 0) {
+                    // throw exception: Loan product frequency/interval
+                    throw new MeetingFrequencyMismatchException("loanapplication.repayment.interval",
+                            "Loan repayment repaid every # must equal or multiple of meeting interval " + meetingInterval, meetingInterval,
+                            repaymentInterval);
+                }
+            }
+        }
+    }
+
+    public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element) {
+        final LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
+        return loanApplicationTerms.toLoanProductRelatedDetail();
+    }
+
+    public LoanScheduleModel assembleLoanScheduleFrom(final JsonElement element) {
+        // This method is getting called from calculate loan schedule.
+        final LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
+        // Get holiday details
+        final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+
+        final Long clientId = this.fromApiJsonHelper.extractLongNamed("clientId", element);
+        final Long groupId = this.fromApiJsonHelper.extractLongNamed("groupId", element);
+
+        Client client = null;
+        Group group = null;
+        Long officeId = null;
+        if (clientId != null) {
+            client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            officeId = client.getOffice().getId();
+        } else if (groupId != null) {
+            group = this.groupRepository.findOneWithNotFoundDetection(groupId);
+            officeId = group.getOffice().getId();
+        }
+
+        final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
+        final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, expectedDisbursementDate.toDate(),
+                HolidayStatusType.ACTIVE.getValue());
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+
+        validateDisbursementDateIsOnNonWorkingDay(loanApplicationTerms.getExpectedDisbursementDate(), workingDays);
+        validateDisbursementDateIsOnHoliday(loanApplicationTerms.getExpectedDisbursementDate(), isHolidayEnabled, holidays);
+
+        return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element, null);
+    }
+
+    public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
+            final List<Holiday> holidays, final WorkingDays workingDays, final JsonElement element,
+            Set<LoanDisbursementDetails> disbursementDetails) {
+
+        final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails);
+
+        final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
+
+        final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
+        final MathContext mc = new MathContext(8, roundingMode);
+
+        HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
+
+        return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
+    }
+
+    public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId,
+            List<LoanTransaction> transactions, final Set<LoanCharge> loanCharges,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom) {
+        final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
+        final MathContext mc = new MathContext(8, roundingMode);
+        final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+
+        final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms
+                .getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+
+        final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
+        HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
+        return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions,
+                loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, rescheduleFrom).getLoanScheduleModel();
+    }
+
+    public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
+            LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final Long officeId,
+            List<LoanTransaction> loanTransactions,
+            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments) {
+        final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
+        final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
+        final MathContext mc = new MathContext(8, roundingMode);
+
+        final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+        final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms
+                .getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+        HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
+
+        return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loanCharges, holidayDetailDTO,
+                loanTransactions, loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments);
+
+    }
+
+    public void assempleVariableScheduleFrom(final Loan loan, final String json) {
+        this.variableLoanScheduleFromApiJsonValidator.validateSchedule(json, loan);
+
+        List<LoanTermVariations> variations = loan.getLoanTermVariations();
+        List<LoanTermVariations> newVariations = new ArrayList<>();
+        extractLoanTermVariations(loan, json, newVariations);
+
+        final Map<LocalDate, LocalDate> adjustDueDateVariations = new HashMap<>();
+
+        if (!variations.isEmpty()) {
+            List<LoanTermVariations> retainVariations = adjustExistingVariations(variations, newVariations, adjustDueDateVariations);
+            newVariations = retainVariations;
+        }
+        variations.addAll(newVariations);
+        Collections.sort(variations, new LoanTermVariationsComparator());
+
+        /*
+         * List<LoanTermVariationsData> loanTermVariationsDatas = new
+         * ArrayList<>();
+         * loanTermVariationsDatas.addAll(loanApplicationTerms.getLoanTermVariations
+         * ().getExceptionData()); loanApplicationTerms =
+         * LoanApplicationTerms.assembleFrom(loanApplicationTerms,
+         * loanTermVariationsDatas);
+         */
+
+        // date validations
+        List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
+        Set<LocalDate> dueDates = new TreeSet<>();
+        LocalDate graceApplicable = loan.getExpectedDisbursedOnLocalDate();
+        Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().graceOnPrincipalPayment();
+        if (graceOnPrincipal == null) {
+            graceOnPrincipal = 0;
+        }
+        LocalDate lastDate = loan.getExpectedDisbursedOnLocalDate();
+        for (LoanRepaymentScheduleInstallment installment : installments) {
+            dueDates.add(installment.getDueDate());
+            if (lastDate.isBefore(installment.getDueDate())) {
+                lastDate = installment.getDueDate();
+            }
+            if (graceOnPrincipal.equals(installment.getInstallmentNumber())) {
+                graceApplicable = installment.getDueDate();
+            }
+        }
+        Collection<LocalDate> keySet = adjustDueDateVariations.keySet();
+        dueDates.addAll(keySet);
+        for (final LocalDate date : keySet) {
+            LocalDate removeDate = adjustDueDateVariations.get(date);
+            if (removeDate != null) {
+                dueDates.remove(removeDate);
+            }
+        }
+
+        Set<LocalDate> actualDueDates = new TreeSet<>(dueDates);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+        List<LocalDate> overlappings = new ArrayList<>();
+        for (LoanTermVariations termVariations : variations) {
+            switch (termVariations.getTermType()) {
+                case INSERT_INSTALLMENT:
+                    if (dueDates.contains(termVariations.fetchTermApplicaDate())) {
+                        overlappings.add(termVariations.fetchTermApplicaDate());
+                    } else {
+                        dueDates.add(termVariations.fetchTermApplicaDate());
+                    }
+                    if (!graceApplicable.isBefore(termVariations.fetchTermApplicaDate())) {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                                "variable.schedule.insert.not.allowed.before.grace.period", "Loan schedule insert request invalid");
+                    }
+                    if (termVariations.fetchTermApplicaDate().isAfter(lastDate)) {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                                "variable.schedule.insert.not.allowed.after.last.period.date", "Loan schedule insert request invalid");
+                    } else if (termVariations.fetchTermApplicaDate().isBefore(loan.getExpectedDisbursedOnLocalDate())) {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                                "variable.schedule.insert.not.allowed.before.disbursement.date", "Loan schedule insert request invalid");
+                    }
+                break;
+                case DELETE_INSTALLMENT:
+                    if (dueDates.contains(termVariations.fetchTermApplicaDate())) {
+                        dueDates.remove(termVariations.fetchTermApplicaDate());
+                    } else {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.remove.date.invalid",
+                                "Loan schedule remove request invalid");
+                    }
+                    if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                                "variable.schedule.delete.not.allowed.for.last.period.date", "Loan schedule remove request invalid");
+                    }
+                break;
+                case DUE_DATE:
+                    if (dueDates.contains(termVariations.fetchTermApplicaDate())) {
+
+                        if (overlappings.contains(termVariations.fetchTermApplicaDate())) {
+                            overlappings.remove(termVariations.fetchTermApplicaDate());
+                        } else {
+                            dueDates.remove(termVariations.fetchTermApplicaDate());
+                        }
+                    } else {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.modify.date.invalid",
+                                "Loan schedule modify due date request invalid");
+                    }
+                    if (dueDates.contains(termVariations.fetchDateValue())) {
+                        overlappings.add(termVariations.fetchDateValue());
+                    } else {
+                        dueDates.add(termVariations.fetchDateValue());
+                    }
+                    if (termVariations.fetchDateValue().isBefore(loan.getExpectedDisbursedOnLocalDate())) {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                                "variable.schedule.insert.not.allowed.before.disbursement.date", "Loan schedule insert request invalid");
+                    }
+                    if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) {
+                        lastDate = termVariations.fetchDateValue();
+                    }
+                break;
+                case PRINCIPAL_AMOUNT:
+                case EMI_AMOUNT:
+                    if (!graceApplicable.isBefore(termVariations.fetchTermApplicaDate())) {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                                "variable.schedule.amount.update.not.allowed.before.grace.period", "Loan schedule modify request invalid");
+                    }
+                    if (!dueDates.contains(termVariations.fetchTermApplicaDate())) {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                                "variable.schedule.amount.update.from.date.invalid", "Loan schedule modify request invalid");
+                    }
+                    if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                                "variable.schedule.amount.update.not.allowed.for.last.period", "Loan schedule modify request invalid");
+                    }
+                break;
+
+                default:
+                break;
+
+            }
+
+        }
+        if (!overlappings.isEmpty()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.modify.date.can.not.be.due.date",
+                    overlappings);
+        }
+        LoanProductVariableInstallmentConfig installmentConfig = loan.loanProduct().loanProductVariableInstallmentConfig();
+        final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
+                CalendarEntityType.LOANS.getValue());
+        Calendar loanCalendar = null;
+        if (loanCalendarInstance != null) {
+            loanCalendar = loanCalendarInstance.getCalendar();
+        }
+        final Integer minGap = installmentConfig.getMinimumGap();
+        final Integer maxGap = installmentConfig.getMaximumGap();
+
+        LocalDate previousDate = loan.getDisbursementDate();
+        for (LocalDate duedate : dueDates) {
+            int gap = Days.daysBetween(previousDate, duedate).getDays();
+            previousDate = duedate;
+            if (gap < minGap || (maxGap != null && gap > maxGap)) {
+                baseDataValidator
+                        .reset()
+                        .value(duedate)
+                        .failWithCodeNoParameterAddedToErrorCode("variable.schedule.date.must.be.in.min.max.range",
+                                "Loan schedule date invalid");
+            } else if (loanCalendar != null && !actualDueDates.contains(duedate) && !loanCalendar.isValidRecurringDate(duedate)) {
+                baseDataValidator
+                        .reset()
+                        .value(duedate)
+                        .failWithCodeNoParameterAddedToErrorCode("variable.schedule.date.not.meeting.date",
+                                "Loan schedule date not in sync with meeting date");
+            }
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        if (loan.getExpectedFirstRepaymentOnDate() == null) {
+            loan.setExpectedFirstRepaymentOnDate(loan.fetchRepaymentScheduleInstallment(1).getDueDate().toDate());
+        }
+        final LocalDate recalculateFrom = null;
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+        AppUser currentUser = this.context.getAuthenticatedUserIfPresent();
+        loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
+    }
+
+    private List<LoanTermVariations> adjustExistingVariations(List<LoanTermVariations> variations, List<LoanTermVariations> newVariations,
+            final Map<LocalDate, LocalDate> adjustDueDateVariations) {
+        Map<LocalDate, LoanTermVariations> amountVariations = new HashMap<>();
+        Map<LocalDate, LoanTermVariations> dueDateVariations = new HashMap<>();
+        Map<LocalDate, LoanTermVariations> insertVariations = new HashMap<>();
+        for (LoanTermVariations loanTermVariations : variations) {
+            switch (loanTermVariations.getTermType()) {
+                case EMI_AMOUNT:
+                case PRINCIPAL_AMOUNT:
+                    amountVariations.put(loanTermVariations.fetchTermApplicaDate(), loanTermVariations);
+                break;
+                case DUE_DATE:
+                    dueDateVariations.put(loanTermVariations.fetchDateValue(), loanTermVariations);
+                    adjustDueDateVariations.put(loanTermVariations.fetchTermApplicaDate(), loanTermVariations.fetchDateValue());
+                break;
+                case INSERT_INSTALLMENT:
+                    insertVariations.put(loanTermVariations.fetchTermApplicaDate(), loanTermVariations);
+                    adjustDueDateVariations.put(loanTermVariations.fetchTermApplicaDate(), loanTermVariations.fetchTermApplicaDate());
+                break;
+                case DELETE_INSTALLMENT:
+                    adjustDueDateVariations.put(loanTermVariations.fetchTermApplicaDate(), null);
+                break;
+                default:
+                break;
+            }
+        }
+        List<LoanTermVariations> retainVariations = new ArrayList<>();
+        for (LoanTermVariations loanTermVariations : newVariations) {
+            boolean retain = true;
+            switch (loanTermVariations.getTermType()) {
+                case DUE_DATE:
+                    if (amountVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
+                        amountVariations.get(loanTermVariations.fetchTermApplicaDate()).setTermApplicableFrom(
+                                loanTermVariations.getDateValue());
+                    } else if (insertVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
+                        insertVariations.get(loanTermVariations.fetchTermApplicaDate()).setTermApplicableFrom(
+                                loanTermVariations.getDateValue());
+                        retain = false;
+                    }
+                    if (dueDateVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
+                        LoanTermVariations existingVariation = dueDateVariations.get(loanTermVariations.fetchTermApplicaDate());
+                        if (existingVariation.fetchTermApplicaDate().isEqual(loanTermVariations.fetchDateValue())) {
+                            variations.remove(existingVariation);
+                        } else {
+                            existingVariation.setTermApplicableFrom(loanTermVariations.getDateValue());
+                        }
+                        retain = false;
+                    }
+                break;
+                case EMI_AMOUNT:
+                case PRINCIPAL_AMOUNT:
+                    if (amountVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
+                        amountVariations.get(loanTermVariations.fetchTermApplicaDate()).setDecimalValue(loanTermVariations.getTermValue());
+                        retain = false;
+                    } else if (insertVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
+                        insertVariations.get(loanTermVariations.fetchTermApplicaDate()).setDecimalValue(loanTermVariations.getTermValue());
+                        retain = false;
+                    }
+                break;
+                case DELETE_INSTALLMENT:
+                    if (amountVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
+                        variations.remove(amountVariations.get(loanTermVariations.fetchTermApplicaDate()));
+
+                    } else if (insertVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
+                        variations.remove(insertVariations.get(loanTermVariations.fetchTermApplicaDate()));
+                        retain = false;
+                    }
+                    if (dueDateVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
+                        variations.remove(amountVariations.get(loanTermVariations.fetchTermApplicaDate()));
+                    }
+                break;
+                default:
+                break;
+            }
+            if (retain) {
+                retainVariations.add(loanTermVariations);
+            }
+        }
+        return retainVariations;
+    }
+
+    private void extractLoanTermVariations(final Loan loan, final String json, final List<LoanTermVariations> loanTermVariations) {
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        if (loan.loanProduct().allowVariabeInstallments()) {
+            if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.exceptionParamName, element)) {
+                final JsonObject topLevelJsonElement = element.getAsJsonObject();
+                final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
+                final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+                final JsonObject exceptionObject = topLevelJsonElement.getAsJsonObject(LoanApiConstants.exceptionParamName);
+                if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.modifiedinstallmentsParamName, exceptionObject)
+                        && exceptionObject.get(LoanApiConstants.modifiedinstallmentsParamName).isJsonArray()) {
+                    final JsonArray modificationsArray = exceptionObject.get(LoanApiConstants.modifiedinstallmentsParamName)
+                            .getAsJsonArray();
+                    extractLoanTermVariations(loan, dateFormat, locale, modificationsArray, false, false, loanTermVariations);
+                }
+                if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.newinstallmentsParamName, exceptionObject)
+                        && exceptionObject.get(LoanApiConstants.newinstallmentsParamName).isJsonArray()) {
+                    final JsonArray array = exceptionObject.get(LoanApiConstants.newinstallmentsParamName).getAsJsonArray();
+                    extractLoanTermVariations(loan, dateFormat, locale, array, true, false, loanTermVariations);
+                }
+                if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.deletedinstallmentsParamName, exceptionObject)
+                        && exceptionObject.get(LoanApiConstants.deletedinstallmentsParamName).isJsonArray()) {
+                    final JsonArray array = exceptionObject.get(LoanApiConstants.deletedinstallmentsParamName).getAsJsonArray();
+                    extractLoanTermVariations(loan, dateFormat, locale, array, false, true, loanTermVariations);
+                }
+            }
+        }
+    }
+
+    private void extractLoanTermVariations(final Loan loan, final String dateFormat, final Locale locale,
+            final JsonArray modificationsArray, final boolean isInsertInstallment, final boolean isDeleteInstallment,
+            final List<LoanTermVariations> loanTermVariations) {
+        for (int i = 1; i <= modificationsArray.size(); i++) {
+            final JsonObject arrayElement = modificationsArray.get(i - 1).getAsJsonObject();
+            BigDecimal decimalValue = null;
+            LoanTermVariationType decimalValueVariationType = LoanTermVariationType.INVALID;
+            if (loan.getLoanProductRelatedDetail().getAmortizationMethod().isEqualInstallment()
+                    && loan.getLoanProductRelatedDetail().getInterestMethod().isDecliningBalnce()) {
+                decimalValue = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.installmentAmountParamName, arrayElement,
+                        locale);
+                decimalValueVariationType = LoanTermVariationType.EMI_AMOUNT;
+            } else {
+                decimalValue = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.principalParamName, arrayElement, locale);
+                decimalValueVariationType = LoanTermVariationType.PRINCIPAL_AMOUNT;
+            }
+
+            LocalDate duedateLocalDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.dueDateParamName, arrayElement,
+                    dateFormat, locale);
+            Date dueDate = duedateLocalDate.toDate();
+
+            LocalDate modifiedDuedateLocalDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.modifiedDueDateParamName,
+                    arrayElement, dateFormat, locale);
+            Date modifiedDuedate = null;
+            if (modifiedDuedateLocalDate != null) {
+                modifiedDuedate = modifiedDuedateLocalDate.toDate();
+            }
+            boolean isSpecificToInstallment = true;
+            if (isInsertInstallment) {
+                LoanTermVariations data = new LoanTermVariations(LoanTermVariationType.INSERT_INSTALLMENT.getValue(), dueDate,
+                        decimalValue, modifiedDuedate, isSpecificToInstallment, loan);
+                loanTermVariations.add(data);
+            } else if (isDeleteInstallment) {
+                LoanTermVariations data = new LoanTermVariations(LoanTermVariationType.DELETE_INSTALLMENT.getValue(), dueDate,
+                        decimalValue, modifiedDuedate, isSpecificToInstallment, loan);
+                loanTermVariations.add(data);
+            } else {
+                if (modifiedDuedate != null) {
+                    BigDecimal amountData = null;
+                    LoanTermVariations data = new LoanTermVariations(LoanTermVariationType.DUE_DATE.getValue(), dueDate, amountData,
+                            modifiedDuedate, isSpecificToInstallment, loan);
+                    loanTermVariations.add(data);
+                }
+                if (decimalValue != null) {
+                    if (modifiedDuedate == null) {
+                        modifiedDuedate = dueDate;
+                    }
+                    Date date = null;
+                    LoanTermVariations data = new LoanTermVariations(decimalValueVariationType.getValue(), modifiedDuedate, decimalValue,
+                            date, isSpecificToInstallment, loan);
+                    loanTermVariations.add(data);
+                }
+            }
+
+        }
+    }
+
+    private void validateDisbursementDateIsOnNonWorkingDay(final LocalDate disbursementDate, final WorkingDays workingDays) {
+        if (!WorkingDaysUtil.isWorkingDay(workingDays, disbursementDate)) {
+            final String errorMessage = "The expected disbursement date cannot be on a non working day";
+            throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage, disbursementDate);
+        }
+    }
+
+    private void validateDisbursementDateIsOnHoliday(final LocalDate disbursementDate, final boolean isHolidayEnabled,
+            final List<Holiday> holidays) {
+        if (isHolidayEnabled) {
+            if (HolidayUtil.isHoliday(disbursementDate, holidays)) {
+                final String errorMessage = "The expected disbursement date cannot be on a holiday";
+                throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage, disbursementDate);
+            }
+        }
+    }
+
+    private LocalDate deriveFirstRepaymentDate(final AccountType loanType, final Integer repaymentEvery,
+            final LocalDate expectedDisbursementDate, final PeriodFrequencyType repaymentPeriodFrequencyType,
+            final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar) {
+
+        LocalDate derivedFirstRepayment = null;
+
+        final LocalDate dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = expectedDisbursementDate
+                .plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
+
+        if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendar != null) {
+
+            final LocalDate refernceDateForCalculatingFirstRepaymentDate = expectedDisbursementDate;
+            derivedFirstRepayment = deriveFirstRepaymentDateForJLGLoans(repaymentEvery, expectedDisbursementDate,
+                    refernceDateForCalculatingFirstRepaymentDate, repaymentPeriodFrequencyType,
+                    minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
+
+        }/*** Individual or group account, or JLG not linked to a meeting ***/
+        else {
+            LocalDate dateBasedOnRepaymentFrequency;
+            // Derive the first repayment date as greater date among
+            // (disbursement date + plus frequency) or
+            // (disbursement date + minimum between disbursal and first
+            // repayment )
+            if (repaymentPeriodFrequencyType.isDaily()) {
+                dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusDays(repaymentEvery);
+            } else if (repaymentPeriodFrequencyType.isWeekly()) {
+                dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusWeeks(repaymentEvery);
+            } else if (repaymentPeriodFrequencyType.isMonthly()) {
+                dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusMonths(repaymentEvery);
+            }/** yearly loan **/
+            else {
+                dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusYears(repaymentEvery);
+            }
+            derivedFirstRepayment = dateBasedOnRepaymentFrequency.isAfter(dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment) ? dateBasedOnRepaymentFrequency
+                    : dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment;
+        }
+
+        return derivedFirstRepayment;
+    }
+
+    private LocalDate deriveFirstRepaymentDateForJLGLoans(final Integer repaymentEvery, final LocalDate expectedDisbursementDate,
+            final LocalDate refernceDateForCalculatingFirstRepaymentDate, final PeriodFrequencyType repaymentPeriodFrequencyType,
+            final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar) {
+
+        final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
+        final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar,
+                refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency);
+        final LocalDate minimumFirstRepaymentDate = expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
+        return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment : deriveFirstRepaymentDateForJLGLoans(
+                repaymentEvery, expectedDisbursementDate, derivedFirstRepayment, repaymentPeriodFrequencyType,
+                minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
+    }
+
+    private void validateMinimumDaysBetweenDisbursalAndFirstRepayment(final LocalDate disbursalDate, final LocalDate firstRepaymentDate,
+            final Integer minimumDaysBetweenDisbursalAndFirstRepayment) {
+
+        final LocalDate minimumFirstRepaymentDate = disbursalDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
+        if (firstRepaymentDate.isBefore(minimumFirstRepaymentDate)) { throw new MinDaysBetweenDisbursalAndFirstRepaymentViolationException(
+                disbursalDate, firstRepaymentDate, minimumDaysBetweenDisbursalAndFirstRepayment); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformService.java
new file mode 100644
index 0000000..91a5a84
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformService.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+
+public interface LoanScheduleCalculationPlatformService {
+
+    LoanScheduleModel calculateLoanSchedule(JsonQuery query, Boolean validateParams);
+
+    void updateFutureSchedule(LoanScheduleData loanScheduleData, Long loanId);
+
+    LoanScheduleData generateLoanScheduleForVariableInstallmentRequest(Long loanId, String json);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java
new file mode 100644
index 0000000..6d1aecd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java
@@ -0,0 +1,286 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleCalculationPlatformService {
+
+    private final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer;
+    private final LoanScheduleAssembler loanScheduleAssembler;
+    private final FromJsonHelper fromJsonHelper;
+    private final LoanProductRepository loanProductRepository;
+    private final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer;
+    private final LoanReadPlatformService loanReadPlatformService;
+    private final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer;
+    private final LoanAssembler loanAssembler;
+    private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
+    private final ConfigurationDomainService configurationDomainService;
+    private final CurrencyReadPlatformService currencyReadPlatformService;
+    private final LoanUtilService loanUtilService;
+
+    @Autowired
+    public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer,
+            final LoanScheduleAssembler loanScheduleAssembler, final FromJsonHelper fromJsonHelper,
+            final LoanProductRepository loanProductRepository, final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer,
+            final LoanReadPlatformService loanReadPlatformService, final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer,
+            final LoanAssembler loanAssembler,
+            final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
+            final ConfigurationDomainService configurationDomainService, final CurrencyReadPlatformService currencyReadPlatformService,
+            final LoanUtilService loanUtilService) {
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.loanScheduleAssembler = loanScheduleAssembler;
+        this.fromJsonHelper = fromJsonHelper;
+        this.loanProductRepository = loanProductRepository;
+        this.loanProductCommandFromApiJsonDeserializer = loanProductCommandFromApiJsonDeserializer;
+        this.loanReadPlatformService = loanReadPlatformService;
+        this.loanApiJsonDeserializer = loanApiJsonDeserializer;
+        this.loanAssembler = loanAssembler;
+        this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
+        this.configurationDomainService = configurationDomainService;
+        this.currencyReadPlatformService = currencyReadPlatformService;
+        this.loanUtilService = loanUtilService;
+    }
+
+    @Override
+    public LoanScheduleModel calculateLoanSchedule(final JsonQuery query, Boolean validateParams) {
+
+        /***
+         * TODO: Vishwas, this is probably not required, test and remove the
+         * same
+         **/
+        final Long productId = this.fromJsonHelper.extractLongNamed("productId", query.parsedJson());
+        final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
+        if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
+
+        if (validateParams) {
+            boolean isMeetingMandatoryForJLGLoans = configurationDomainService.isMeetingMandatoryForJLGLoans();
+            this.loanApiJsonDeserializer.validateForCreate(query.json(), isMeetingMandatoryForJLGLoans, loanProduct);
+        }
+        this.fromApiJsonDeserializer.validate(query.json());
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+
+        if (loanProduct.useBorrowerCycle()) {
+            final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", query.parsedJson());
+            final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", query.parsedJson());
+            Integer cycleNumber = 0;
+            if (clientId != null) {
+                cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, loanProduct.getId());
+            } else if (groupId != null) {
+                cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue(), loanProduct.getId());
+            }
+            this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(query.parsedJson(), baseDataValidator, loanProduct,
+                    cycleNumber);
+        } else {
+            this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(query.parsedJson(), baseDataValidator, loanProduct);
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+        return this.loanScheduleAssembler.assembleLoanScheduleFrom(query.parsedJson());
+    }
+
+    @Override
+    public void updateFutureSchedule(LoanScheduleData loanScheduleData, final Long loanId) {
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+
+        LocalDate today = DateUtils.getLocalDateOfTenant();
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
+                .determineProcessor(loan.transactionProcessingStrategy());
+
+        if (!loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || loan.isNpa() || !loan.status().isActive()
+                || !loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) { return; }
+
+        if (loan.loanProduct().isMultiDisburseLoan()) {
+            BigDecimal disbursedAmount = loan.getDisbursedAmount();
+            BigDecimal principalRepaid = loan.getLoanSummary().getTotalPrincipalRepaid();
+            BigDecimal principalWrittenOff = loan.getLoanSummary().getTotalPrincipalWrittenOff();
+            if (disbursedAmount.subtract(principalWrittenOff).subtract(principalRepaid).compareTo(BigDecimal.ZERO) != 1) { return; }
+        }
+        MonetaryCurrency currency = loan.getCurrency();
+        Money totalPrincipal = Money.zero(currency);
+        final List<LoanSchedulePeriodData> futureInstallments = new ArrayList<>();
+        for (final LoanRepaymentScheduleInstallment currentInstallment : loan.fetchRepaymentScheduleInstallments()) {
+            if (currentInstallment.isNotFullyPaidOff()) {
+                if (!currentInstallment.getDueDate().isAfter(today)) {
+                    totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency));
+                }
+            }
+        }
+        LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(loan);
+        LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(currency,
+                today, loanApplicationTerms, loan.charges(), loan.getOfficeId(),
+                loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor,
+                loan.fetchRepaymentScheduleInstallments());
+        Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus(
+                loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
+        Money interestDue = Money.zero(currency);
+        if (loanRepaymentScheduleInstallment.isInterestDue(currency)) {
+            interestDue = loanRepaymentScheduleInstallment.getInterestOutstanding(currency);
+            totalAmount = totalAmount.plus(interestDue);
+        }
+        boolean isNewPaymentRequired = loanRepaymentScheduleInstallment.isInterestDue(currency) || totalPrincipal.isGreaterThanZero();
+        final List<LoanTransaction> modifiedTransactions = new ArrayList<>();
+        List<LoanTransaction> transactions = loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals();
+        for (LoanTransaction loanTransaction : transactions) {
+            modifiedTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
+        }
+        if (isNewPaymentRequired) {
+            LoanTransaction ondayPaymentTransaction = LoanTransaction.repayment(null, totalAmount, null, today, null,
+                    DateUtils.getLocalDateTimeOfTenant(), null);
+            modifiedTransactions.add(ondayPaymentTransaction);
+        }
+
+        LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(),
+                modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor, loan.fetchRepaymentScheduleInstallments(),
+                loan.fetchInterestRecalculateFromDate());
+        LoanScheduleData scheduleDate = model.toData();
+        Collection<LoanSchedulePeriodData> periodDatas = scheduleDate.getPeriods();
+        for (LoanSchedulePeriodData periodData : periodDatas) {
+            if ((periodData.periodDueDate().isEqual(today) || periodData.periodDueDate().isAfter(today)) && isNewPaymentRequired) {
+                LoanSchedulePeriodData loanSchedulePeriodData = LoanSchedulePeriodData.repaymentOnlyPeriod(periodData.periodNumber(),
+                        periodData.periodFromDate(), periodData.periodDueDate(), totalPrincipal.getAmount(), periodData
+                                .principalLoanBalanceOutstanding(), interestDue.getAmount(), loanRepaymentScheduleInstallment
+                                .getFeeChargesCharged(currency).getAmount(),
+                        loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency).getAmount(), totalAmount.getAmount(),
+                        totalPrincipal.plus(interestDue).getAmount());
+                futureInstallments.add(loanSchedulePeriodData);
+                isNewPaymentRequired = false;
+            } else if (periodData.periodDueDate().isAfter(today)) {
+                futureInstallments.add(periodData);
+            }
+
+        }
+        loanScheduleData.updateFuturePeriods(futureInstallments);
+    }
+
+    @Override
+    public LoanScheduleData generateLoanScheduleForVariableInstallmentRequest(Long loanId, final String json) {
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        this.loanScheduleAssembler.assempleVariableScheduleFrom(loan, json);
+        return constructLoanScheduleData(loan);
+    }
+
+    private LoanScheduleData constructLoanScheduleData(Loan loan) {
+        Collection<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
+        final List<LoanSchedulePeriodData> installmentData = new ArrayList<>();
+        final MonetaryCurrency currency = loan.getCurrency();
+        Money outstanding = loan.getPrincpal();
+
+        Set<LoanDisbursementDetails> disbursementDetails = new HashSet<>();
+        if (loan.isMultiDisburmentLoan()) {
+            disbursementDetails = loan.getDisbursementDetails();
+            outstanding = outstanding.zero();
+        }
+        Money principal = outstanding;
+        Iterator<LoanDisbursementDetails> disbursementItr = disbursementDetails.iterator();
+        LoanDisbursementDetails loanDisbursementDetails = null;
+        if (disbursementItr.hasNext()) {
+            loanDisbursementDetails = disbursementItr.next();
+        }
+
+        Money totalInterest = principal.zero();
+        Money totalCharge = principal.zero();
+        Money totalPenalty = principal.zero();
+
+        for (LoanRepaymentScheduleInstallment installment : installments) {
+            if (loanDisbursementDetails != null
+                    && !loanDisbursementDetails.expectedDisbursementDateAsLocalDate().isAfter(installment.getDueDate())) {
+                outstanding = outstanding.plus(loanDisbursementDetails.principal());
+                principal = principal.plus(loanDisbursementDetails.principal());
+                if (disbursementItr.hasNext()) {
+                    loanDisbursementDetails = disbursementItr.next();
+                } else {
+                    loanDisbursementDetails = null;
+                }
+            }
+            outstanding = outstanding.minus(installment.getPrincipal(currency));
+            LoanSchedulePeriodData loanSchedulePeriodData = LoanSchedulePeriodData.repaymentOnlyPeriod(installment.getInstallmentNumber(),
+                    installment.getFromDate(), installment.getDueDate(), installment.getPrincipal(currency).getAmount(),
+                    outstanding.getAmount(), installment.getInterestCharged(currency).getAmount(),
+                    installment.getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(),
+                    installment.getDue(currency).getAmount(), installment.getTotalPrincipalAndInterest(currency).getAmount());
+            installmentData.add(loanSchedulePeriodData);
+            totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
+            totalCharge = totalCharge.plus(installment.getFeeChargesCharged(currency));
+            totalPenalty = totalPenalty.plus(installment.getPenaltyChargesCharged(currency));
+        }
+
+        CurrencyData currencyData = this.currencyReadPlatformService.retrieveCurrency(currency.getCode());
+
+        LoanScheduleData scheduleData = new LoanScheduleData(currencyData, installmentData, loan.getLoanRepaymentScheduleDetail()
+                .getNumberOfRepayments(), principal.getAmount(), principal.getAmount(), totalInterest.getAmount(), totalCharge.getAmount(),
+                totalPenalty.getAmount(), principal.plus(totalCharge).plus(totalInterest).plus(totalPenalty).getAmount());
+
+        return scheduleData;
+    }
+
+    private LoanApplicationTerms constructLoanApplicationTerms(final Loan loan) {
+        final LocalDate recalculateFrom = null;
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+        LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
+        return loanApplicationTerms;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java
new file mode 100755
index 0000000..bf558db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+
+public interface LoanScheduleHistoryReadPlatformService {
+
+    Integer fetchCurrentVersionNumber(Long loanId);
+
+    LoanScheduleData retrieveRepaymentArchiveSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
+            Collection<DisbursementData> disbursementData);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java
new file mode 100755
index 0000000..2e7d060
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java
@@ -0,0 +1,218 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+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.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+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.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanScheduleHistoryReadPlatformServiceImpl implements LoanScheduleHistoryReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public LoanScheduleHistoryReadPlatformServiceImpl(final RoutingDataSource dataSource, final PlatformSecurityContext context) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public Integer fetchCurrentVersionNumber(Long loanId) {
+        final String sql = "select MAX(lrs.version) from m_loan_repayment_schedule_history lrs where lrs.loan_id = ?";
+        return this.jdbcTemplate.queryForInt(sql, loanId);
+    }
+
+    @Override
+    public LoanScheduleData retrieveRepaymentArchiveSchedule(final Long loanId,
+            final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData) {
+
+        try {
+            this.context.authenticatedUser();
+            Integer versionNumber = fetchCurrentVersionNumber(loanId);
+            if (versionNumber == 0) { return null; }
+            final LoanScheduleArchiveResultSetExtractor fullResultsetExtractor = new LoanScheduleArchiveResultSetExtractor(
+                    repaymentScheduleRelatedLoanData, disbursementData);
+            final String sql = "select " + fullResultsetExtractor.schema()
+                    + " where ls.loan_id = ? and ls.version = ? order by ls.loan_id, ls.installment";
+
+            return this.jdbcTemplate.query(sql, fullResultsetExtractor, new Object[] { loanId, versionNumber });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new LoanNotFoundException(loanId);
+        }
+    }
+
+    private static final class LoanScheduleArchiveResultSetExtractor implements ResultSetExtractor<LoanScheduleData> {
+
+        private final CurrencyData currency;
+        private final DisbursementData disbursement;
+        private final BigDecimal totalFeeChargesDueAtDisbursement;
+        private final Collection<DisbursementData> disbursementData;
+        private LocalDate lastDueDate;
+        private BigDecimal outstandingLoanPrincipalBalance;
+
+        public LoanScheduleArchiveResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
+                Collection<DisbursementData> disbursementData) {
+            this.currency = repaymentScheduleRelatedLoanData.getCurrency();
+            this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
+            this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
+            this.lastDueDate = this.disbursement.disbursementDate();
+            this.outstandingLoanPrincipalBalance = this.disbursement.amount();
+            this.disbursementData = disbursementData;
+        }
+
+        public String schema() {
+            StringBuilder stringBuilder = new StringBuilder(200);
+            stringBuilder.append(" ls.installment as period, ls.fromdate as fromDate, ls.duedate as dueDate, ");
+            stringBuilder
+                    .append("ls.principal_amount as principalDue, ls.interest_amount as interestDue, ls.fee_charges_amount as feeChargesDue, ls.penalty_charges_amount as penaltyChargesDue ");
+            stringBuilder.append(" from m_loan_repayment_schedule_history ls ");
+            return stringBuilder.toString();
+        }
+
+        @Override
+        public LoanScheduleData extractData(final ResultSet rs) throws SQLException, DataAccessException {
+
+            final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(
+                    this.disbursement.disbursementDate(), this.disbursement.amount(), this.totalFeeChargesDueAtDisbursement,
+                    this.disbursement.isDisbursed());
+
+            final Collection<LoanSchedulePeriodData> periods = new ArrayList<>();
+            final MonetaryCurrency monCurrency = new MonetaryCurrency(this.currency.code(), this.currency.decimalPlaces(),
+                    this.currency.currencyInMultiplesOf());
+            BigDecimal totalPrincipalDisbursed = BigDecimal.ZERO;
+            if (disbursementData == null || disbursementData.isEmpty()) {
+                periods.add(disbursementPeriod);
+                totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.amount()).getAmount();
+            } else {
+                this.outstandingLoanPrincipalBalance = BigDecimal.ZERO;
+            }
+
+            Money totalPrincipalExpected = Money.zero(monCurrency);
+            Money totalInterestCharged = Money.zero(monCurrency);
+            Money totalFeeChargesCharged = Money.zero(monCurrency);
+            Money totalPenaltyChargesCharged = Money.zero(monCurrency);
+            Money totalRepaymentExpected = Money.zero(monCurrency);
+
+            // update totals with details of fees charged during disbursement
+            totalFeeChargesCharged = totalFeeChargesCharged.plus(disbursementPeriod.feeChargesDue());
+            totalRepaymentExpected = totalRepaymentExpected.plus(disbursementPeriod.feeChargesDue());
+
+            Integer loanTermInDays = Integer.valueOf(0);
+            while (rs.next()) {
+                final Integer period = JdbcSupport.getInteger(rs, "period");
+                LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromDate");
+                final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
+                if (disbursementData != null) {
+                    BigDecimal principal = BigDecimal.ZERO;
+                    for (DisbursementData data : disbursementData) {
+                        if (fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate)) {
+                            principal = principal.add(data.amount());
+                            final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
+                                    data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed());
+                            periods.add(periodData);
+                            this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
+                        } else if (data.isDueForDisbursement(fromDate, dueDate)
+                                && this.outstandingLoanPrincipalBalance.compareTo(BigDecimal.ZERO) == 1) {
+                            principal = principal.add(data.amount());
+                            final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
+                                    data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed());
+                            periods.add(periodData);
+                            this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
+                        }
+                    }
+                    totalPrincipalDisbursed = totalPrincipalDisbursed.add(principal);
+                }
+
+                Integer daysInPeriod = Integer.valueOf(0);
+                if (fromDate != null) {
+                    daysInPeriod = Days.daysBetween(fromDate, dueDate).getDays();
+                    loanTermInDays = Integer.valueOf(loanTermInDays.intValue() + daysInPeriod.intValue());
+                }
+
+                final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDue");
+                totalPrincipalExpected = totalPrincipalExpected.plus(principalDue);
+
+                final BigDecimal interestExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue");
+                totalInterestCharged = totalInterestCharged.plus(interestExpectedDue);
+
+                final BigDecimal totalInstallmentAmount = totalPrincipalExpected.zero().plus(principalDue).plus(interestExpectedDue)
+                        .getAmount();
+
+                final BigDecimal feeChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesDue");
+                totalFeeChargesCharged = totalFeeChargesCharged.plus(feeChargesExpectedDue);
+
+                final BigDecimal penaltyChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesDue");
+                totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(penaltyChargesExpectedDue);
+
+                final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add(
+                        penaltyChargesExpectedDue);
+
+                final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod);
+
+                totalRepaymentExpected = totalRepaymentExpected.plus(totalDueForPeriod);
+
+                if (fromDate == null) {
+                    fromDate = this.lastDueDate;
+                }
+                final BigDecimal outstandingPrincipalBalanceOfLoan = this.outstandingLoanPrincipalBalance.subtract(principalDue);
+
+                // update based on current period values
+                this.lastDueDate = dueDate;
+                this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.subtract(principalDue);
+
+                final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.repaymentOnlyPeriod(period, fromDate, dueDate,
+                        principalDue, outstandingPrincipalBalanceOfLoan, interestExpectedDue, feeChargesExpectedDue,
+                        penaltyChargesExpectedDue, totalDueForPeriod, totalInstallmentAmount);
+
+                periods.add(periodData);
+            }
+
+            return new LoanScheduleData(this.currency, periods, loanTermInDays, totalPrincipalDisbursed,
+                    totalPrincipalExpected.getAmount(), totalInterestCharged.getAmount(), totalFeeChargesCharged.getAmount(),
+                    totalPenaltyChargesCharged.getAmount(), totalRepaymentExpected.getAmount());
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformService.java
new file mode 100755
index 0000000..f69100c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformService.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.service;
+
+import java.util.List;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+
+public interface LoanScheduleHistoryWritePlatformService {
+
+    List<LoanRepaymentScheduleHistory> createLoanScheduleArchive(
+            final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final Loan loan,
+            final LoanRescheduleRequest loanRescheduleRequest);
+
+    void createAndSaveLoanScheduleArchive(List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Loan loan,
+            LoanRescheduleRequest loanRescheduleRequest);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java
new file mode 100755
index 0000000..c190017
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java
@@ -0,0 +1,107 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanScheduleHistoryWritePlatformServiceImpl implements LoanScheduleHistoryWritePlatformService {
+
+    private final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService;
+    private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository;
+
+    @Autowired
+    public LoanScheduleHistoryWritePlatformServiceImpl(final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService,
+            final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository) {
+        this.loanScheduleHistoryReadPlatformService = loanScheduleHistoryReadPlatformService;
+        this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository;
+
+    }
+
+    @Override
+    public List<LoanRepaymentScheduleHistory> createLoanScheduleArchive(
+            List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Loan loan, LoanRescheduleRequest loanRescheduleRequest) {
+        Integer version = this.loanScheduleHistoryReadPlatformService.fetchCurrentVersionNumber(loan.getId()) + 1;
+        final MonetaryCurrency currency = loan.getCurrency();
+        final List<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = new ArrayList<>();
+
+        for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
+            final Integer installmentNumber = repaymentScheduleInstallment.getInstallmentNumber();
+            Date fromDate = null;
+            Date dueDate = null;
+
+            if (repaymentScheduleInstallment.getFromDate() != null) {
+                fromDate = repaymentScheduleInstallment.getFromDate().toDate();
+            }
+
+            if (repaymentScheduleInstallment.getDueDate() != null) {
+                dueDate = repaymentScheduleInstallment.getDueDate().toDate();
+            }
+
+            final BigDecimal principal = repaymentScheduleInstallment.getPrincipal(currency).getAmount();
+            final BigDecimal interestCharged = repaymentScheduleInstallment.getInterestCharged(currency).getAmount();
+            final BigDecimal feeChargesCharged = repaymentScheduleInstallment.getFeeChargesCharged(currency).getAmount();
+            final BigDecimal penaltyCharges = repaymentScheduleInstallment.getPenaltyChargesCharged(currency).getAmount();
+
+            Date createdOnDate = null;
+            if (repaymentScheduleInstallment.getCreatedDate() != null) {
+                createdOnDate = repaymentScheduleInstallment.getCreatedDate().toDate();
+            }
+
+            final AppUser createdByUser = repaymentScheduleInstallment.getCreatedBy();
+            final AppUser lastModifiedByUser = repaymentScheduleInstallment.getLastModifiedBy();
+
+            Date lastModifiedOnDate = null;
+
+            if (repaymentScheduleInstallment.getLastModifiedDate() != null) {
+                lastModifiedOnDate = repaymentScheduleInstallment.getLastModifiedDate().toDate();
+            }
+
+            LoanRepaymentScheduleHistory loanRepaymentScheduleHistory = LoanRepaymentScheduleHistory.instance(loan, loanRescheduleRequest,
+                    installmentNumber, fromDate, dueDate, principal, interestCharged, feeChargesCharged, penaltyCharges, createdOnDate,
+                    createdByUser, lastModifiedByUser, lastModifiedOnDate, version);
+
+            loanRepaymentScheduleHistoryList.add(loanRepaymentScheduleHistory);
+        }
+        return loanRepaymentScheduleHistoryList;
+    }
+
+    @Override
+    public void createAndSaveLoanScheduleArchive(List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Loan loan,
+            LoanRescheduleRequest loanRescheduleRequest) {
+        List<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = createLoanScheduleArchive(repaymentScheduleInstallments,
+                loan, loanRescheduleRequest);
+        this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistoryList);
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformService.java
new file mode 100644
index 0000000..90b5518
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformService.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.loanaccount.loanschedule.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface LoanScheduleWritePlatformService {
+
+    public CommandProcessingResult addLoanScheduleVariations(final Long loanId, final JsonCommand command);
+
+    CommandProcessingResult deleteLoanScheduleVariations(final Long loanId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java
new file mode 100644
index 0000000..7191671
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java
@@ -0,0 +1,115 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.loanschedule.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePlatformService {
+
+    private final LoanAccountDomainService loanAccountDomainService;
+    private final LoanAssembler loanAssembler;
+    private final LoanScheduleAssembler loanScheduleAssembler;
+    private final PlatformSecurityContext context;
+    private final LoanUtilService loanUtilService;
+
+    @Autowired
+    public LoanScheduleWritePlatformServiceImpl(final LoanAccountDomainService loanAccountDomainService,
+            final LoanScheduleAssembler loanScheduleAssembler, final LoanAssembler loanAssembler, final PlatformSecurityContext context,
+            final LoanUtilService loanUtilService) {
+        this.loanAccountDomainService = loanAccountDomainService;
+        this.loanScheduleAssembler = loanScheduleAssembler;
+        this.loanAssembler = loanAssembler;
+        this.context = context;
+        this.loanUtilService = loanUtilService;
+    }
+
+    @Override
+    public CommandProcessingResult addLoanScheduleVariations(final Long loanId, final JsonCommand command) {
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        Map<Long, LoanTermVariations> loanTermVariations = new HashMap<>();
+        for (LoanTermVariations termVariations : loan.getLoanTermVariations()) {
+            loanTermVariations.put(termVariations.getId(), termVariations);
+        }
+        this.loanScheduleAssembler.assempleVariableScheduleFrom(loan, command.json());
+
+        this.loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan);
+        final Map<String, Object> changes = new HashMap<>();
+        List<LoanTermVariationsData> newVariationsData = new ArrayList<>();
+        List<LoanTermVariations> modifiedVariations = loan.getLoanTermVariations();
+        for (LoanTermVariations termVariations : modifiedVariations) {
+            if (loanTermVariations.containsKey(termVariations.getId())) {
+                loanTermVariations.remove(termVariations.getId());
+            } else {
+                newVariationsData.add(termVariations.toData());
+            }
+        }
+        if (!loanTermVariations.isEmpty()) {
+            changes.put("removedVariations", loanTermVariations.keySet());
+        }
+        changes.put("loanTermVariations", newVariationsData);
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult deleteLoanScheduleVariations(final Long loanId) {
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        List<LoanTermVariations> variations = loan.getLoanTermVariations();
+        List<Long> deletedVariations = new ArrayList<>(variations.size());
+        for (LoanTermVariations loanTermVariations : variations) {
+            deletedVariations.add(loanTermVariations.getId());
+        }
+        final Map<String, Object> changes = new HashMap<>();
+        changes.put("removedEntityIds", deletedVariations);
+        loan.getLoanTermVariations().clear();
+        final LocalDate recalculateFrom = null;
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+        AppUser currentUser = this.context.getAuthenticatedUserIfPresent();
+        loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
+        this.loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan);
+        return new CommandProcessingResultBuilder() //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
new file mode 100644
index 0000000..1a3da8e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class RescheduleLoansApiConstants {
+
+    public final static String ENTITY_NAME = "RESCHEDULELOAN";
+
+    public static final String LOAN_RESCHEDULE_REASON = "LoanRescheduleReason";
+    
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // create action request parameters
+    public static final String loanIdParamName = "loanId";
+    public static final String graceOnPrincipalParamName = "graceOnPrincipal";
+    public static final String graceOnInterestParamName = "graceOnInterest";
+    public static final String extraTermsParamName = "extraTerms";
+    public static final String rescheduleFromDateParamName = "rescheduleFromDate";
+    public static final String recalculateInterestParamName = "recalculateInterest";
+    public static final String newInterestRateParamName = "newInterestRate";
+    public static final String rescheduleReasonIdParamName = "rescheduleReasonId";
+    public static final String rescheduleReasonCommentParamName = "rescheduleReasonComment";
+    public static final String submittedOnDateParamName = "submittedOnDate";
+    public static final String adjustedDueDateParamName = "adjustedDueDate";
+    public static final String resheduleForMultiDisbursementNotSupportedErrorCode = "loan.reschedule.multidisbursement.error.code";
+    public static final String resheduleWithInterestRecalculationNotSupportedErrorCode = "loan.reschedule.interestrecalculation.error.code";
+    
+    public static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName,
+            graceOnPrincipalParamName, graceOnInterestParamName, extraTermsParamName, rescheduleFromDateParamName,
+            newInterestRateParamName, rescheduleReasonIdParamName, rescheduleReasonCommentParamName, submittedOnDateParamName,
+            loanIdParamName, adjustedDueDateParamName, recalculateInterestParamName));
+
+    // reject action request parameters
+    public static final String rejectedOnDateParam = "rejectedOnDate";
+
+    public static final Set<String> REJECT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName,
+            rejectedOnDateParam));
+
+    // approve action request parameters
+    public static final String approvedOnDateParam = "approvedOnDate";
+
+    public static final Set<String> APPROVE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName,
+            approvedOnDateParam));
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java
new file mode 100644
index 0000000..2094a05
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java
@@ -0,0 +1,173 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.api;
+
+import java.util.HashSet;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestData;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanReschedulePreviewPlatformService;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/rescheduleloans")
+@Component
+@Scope("singleton")
+public class RescheduleLoansApiResource {
+
+    private final DefaultToApiJsonSerializer<LoanRescheduleRequestData> loanRescheduleRequestToApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<LoanScheduleData> loanRescheduleToApiJsonSerializer;
+    private final PlatformSecurityContext platformSecurityContext;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService;
+    private final LoanReschedulePreviewPlatformService loanReschedulePreviewPlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Autowired
+    public RescheduleLoansApiResource(final DefaultToApiJsonSerializer<LoanRescheduleRequestData> loanRescheduleRequestToApiJsonSerializer,
+            final PlatformSecurityContext platformSecurityContext,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final DefaultToApiJsonSerializer<LoanScheduleData> loanRescheduleToApiJsonSerializer,
+            final LoanReschedulePreviewPlatformService loanReschedulePreviewPlatformService) {
+        this.loanRescheduleRequestToApiJsonSerializer = loanRescheduleRequestToApiJsonSerializer;
+        this.platformSecurityContext = platformSecurityContext;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.loanRescheduleRequestReadPlatformService = loanRescheduleRequestReadPlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.loanRescheduleToApiJsonSerializer = loanRescheduleToApiJsonSerializer;
+        this.loanReschedulePreviewPlatformService = loanReschedulePreviewPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo) {
+
+        this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        LoanRescheduleRequestData loanRescheduleReasons = null;
+        loanRescheduleReasons = this.loanRescheduleRequestReadPlatformService
+                .retrieveAllRescheduleReasons(RescheduleLoansApiConstants.LOAN_RESCHEDULE_REASON);
+
+        return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleReasons);
+    }
+
+    @GET
+    @Path("{scheduleId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String readLoanRescheduleRequest(@Context final UriInfo uriInfo, @PathParam("scheduleId") final Long scheduleId,
+            @QueryParam("command") final String command) {
+        this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (compareIgnoreCase(command, "previewLoanReschedule")) {
+            final LoanRescheduleModel loanRescheduleModel = this.loanReschedulePreviewPlatformService.previewLoanReschedule(scheduleId);
+
+            return this.loanRescheduleToApiJsonSerializer.serialize(settings, loanRescheduleModel.toData(), new HashSet<String>());
+        }
+
+        final LoanRescheduleRequestData loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
+                .readLoanRescheduleRequest(scheduleId);
+
+        return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleRequestData);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createLoanRescheduleRequest(final String apiRequestBodyAsJson) {
+        final CommandWrapper commandWrapper = new CommandWrapperBuilder()
+                .createLoanRescheduleRequest(RescheduleLoansApiConstants.ENTITY_NAME).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+
+        return this.loanRescheduleRequestToApiJsonSerializer.serialize(commandProcessingResult);
+    }
+
+    @POST
+    @Path("{scheduleId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateLoanRescheduleRequest(@PathParam("scheduleId") final Long scheduleId, @QueryParam("command") final String command,
+            final String apiRequestBodyAsJson) {
+        CommandWrapper commandWrapper = null;
+
+        if (compareIgnoreCase(command, "approve")) {
+            commandWrapper = new CommandWrapperBuilder().approveLoanRescheduleRequest(RescheduleLoansApiConstants.ENTITY_NAME, scheduleId)
+                    .withJson(apiRequestBodyAsJson).build();
+        }
+
+        else if (compareIgnoreCase(command, "reject")) {
+            commandWrapper = new CommandWrapperBuilder().rejectLoanRescheduleRequest(RescheduleLoansApiConstants.ENTITY_NAME, scheduleId)
+                    .withJson(apiRequestBodyAsJson).build();
+        }
+
+        else {
+            throw new UnrecognizedQueryParamException("command", command, new Object[] { "approve", "reject" });
+        }
+
+        final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+
+        return this.loanRescheduleRequestToApiJsonSerializer.serialize(commandProcessingResult);
+    }
+
+    /**
+     * Compares two strings, ignoring differences in case
+     * 
+     * @param firstString
+     *            the first string
+     * @param secondString
+     *            the second string
+     * @return true if the two strings are equal, else false
+     **/
+    private boolean compareIgnoreCase(String firstString, String secondString) {
+        return StringUtils.isNotBlank(firstString) && firstString.trim().equalsIgnoreCase(secondString);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java
new file mode 100644
index 0000000..b74388c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java
@@ -0,0 +1,218 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing loan reschedule request data.
+ **/
+public class LoanRescheduleRequestData {
+
+    private final Long id;
+    private final Long loanId;
+    private final Long clientId;
+    private final String clientName;
+    private final String loanAccountNumber;
+    private final LoanRescheduleRequestStatusEnumData statusEnum;
+    private final Integer rescheduleFromInstallment;
+    private final Integer graceOnPrincipal;
+    private final Integer graceOnInterest;
+    private final LocalDate rescheduleFromDate;
+    private final Integer extraTerms;
+    private final BigDecimal interestRate;
+    private final Boolean recalculateInterest;
+    private final CodeValueData rescheduleReasonCodeValue;
+    private final LoanRescheduleRequestTimelineData timeline;
+    private final String rescheduleReasonComment;
+    private final LocalDate adjustedDueDate;
+    private final Collection<CodeValueData> rescheduleReasons;
+    
+    /**
+     * LoanRescheduleRequestData constructor
+     **/
+    private LoanRescheduleRequestData(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum,
+            Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest, LocalDate rescheduleFromDate,
+            LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate, CodeValueData rescheduleReasonCodeValue,
+            String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName,
+            final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, Collection<CodeValueData> rescheduleReasons) {
+
+        this.id = id;
+        this.loanId = loanId;
+        this.statusEnum = statusEnum;
+        this.rescheduleFromInstallment = rescheduleFromInstallment;
+        this.graceOnPrincipal = graceOnPrincipal;
+        this.graceOnInterest = graceOnInterest;
+        this.rescheduleFromDate = rescheduleFromDate;
+        this.extraTerms = extraTerms;
+        this.interestRate = interestRate;
+        this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
+        this.rescheduleReasonComment = rescheduleReasonComment;
+        this.adjustedDueDate = adjustedDueDate;
+        this.timeline = timeline;
+        this.clientName = clientName;
+        this.loanAccountNumber = loanAccountNumber;
+        this.clientId = clientId;
+        this.recalculateInterest = recalculateInterest;
+        this.rescheduleReasons = rescheduleReasons ;
+    }
+
+    /**
+     * @return an instance of the LoanRescheduleRequestData class
+     **/
+    public static LoanRescheduleRequestData instance(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum,
+            Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest, LocalDate rescheduleFromDate,
+            LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate, CodeValueData rescheduleReasonCodeValue,
+            String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName,
+            final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, Collection<CodeValueData> rescheduleReasons) {
+
+        return new LoanRescheduleRequestData(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
+                rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment,
+                timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons);
+    }
+
+    /**
+     * @return the id
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * @return the loanId
+     */
+    public Long getLoanId() {
+        return loanId;
+    }
+
+    /**
+     * @return the statusEnum
+     */
+    public LoanRescheduleRequestStatusEnumData getStatusEnum() {
+        return statusEnum;
+    }
+
+    /**
+     * @return the reschedule from installment number
+     */
+    public Integer getRescheduleFromInstallment() {
+        return rescheduleFromInstallment;
+    }
+
+    /**
+     * @return the graceOnPrincipal
+     */
+    public Integer getGraceOnPrincipal() {
+        return graceOnPrincipal;
+    }
+
+    /**
+     * @return the graceOnInterest
+     */
+    public Integer getGraceOnInterest() {
+        return graceOnInterest;
+    }
+
+    /**
+     * @return the reschedule from date
+     */
+    public LocalDate getRescheduleFromDate() {
+        return rescheduleFromDate;
+    }
+
+    /**
+     * @return the extraTerms
+     */
+    public Integer getExtraTerms() {
+        return extraTerms;
+    }
+
+    /**
+     * @return the interestRate
+     */
+    public BigDecimal getInterestRate() {
+        return interestRate;
+    }
+
+    /**
+     * @return the rescheduleReasonCodeValueId
+     */
+    public CodeValueData getRescheduleReasonCodeValueId() {
+        return rescheduleReasonCodeValue;
+    }
+
+    /**
+     * @return the rescheduleReasonText
+     */
+    public String getRescheduleReasonComment() {
+        return rescheduleReasonComment;
+    }
+
+    /**
+     * @return the timeline
+     **/
+    public LoanRescheduleRequestTimelineData getTimeline() {
+        return this.timeline;
+    }
+
+    /**
+     * @return the adjustedDueDate
+     */
+    public LocalDate getAdjustedDueDate() {
+        return adjustedDueDate;
+    }
+
+    /**
+     * @return the clientName
+     */
+    public String getClientName() {
+        return clientName;
+    }
+
+    /**
+     * @return the loanAccountNumber
+     */
+    public String getLoanAccountNumber() {
+        return loanAccountNumber;
+    }
+
+    /**
+     * @return the clientId
+     */
+    public Long getClientId() {
+        return clientId;
+    }
+
+    /**
+     * @return the recalculateInterest
+     */
+    public Boolean getRecalculateInterest() {
+        boolean value = false;
+
+        if (recalculateInterest != null) {
+            value = recalculateInterest;
+        }
+
+        return value;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
new file mode 100644
index 0000000..5254187
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
@@ -0,0 +1,312 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class LoanRescheduleRequestDataValidator {
+
+    private final FromJsonHelper fromJsonHelper;
+    private final LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService;
+
+    @Autowired
+    public LoanRescheduleRequestDataValidator(FromJsonHelper fromJsonHelper,
+            LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService) {
+        this.fromJsonHelper = fromJsonHelper;
+        this.loanRescheduleRequestReadPlatformService = loanRescheduleRequestReadPlatformService;
+    }
+
+    /**
+     * Validates the request to create a new loan reschedule entry
+     * 
+     * @param jsonCommand
+     *            the JSON command object (instance of the JsonCommand class)
+     * @return void
+     **/
+    public void validateForCreateAction(final JsonCommand jsonCommand, final Loan loan) {
+
+        final String jsonString = jsonCommand.json();
+
+        if (StringUtils.isBlank(jsonString)) { throw new InvalidJsonException(); }
+
+        final Type typeToken = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromJsonHelper
+                .checkForUnsupportedParameters(typeToken, jsonString, RescheduleLoansApiConstants.CREATE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).resource(StringUtils
+                .lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+
+        final JsonElement jsonElement = jsonCommand.parsedJson();
+
+        if (!loan.status().isActive()) {
+            dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active", "Loan is not active");
+        }
+
+        final Long loanId = this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.loanIdParamName, jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.loanIdParamName).value(loanId).notNull()
+                .integerGreaterThanZero();
+
+        final LocalDate submittedOnDate = this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.submittedOnDateParamName,
+                jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
+
+        if (submittedOnDate != null && loan.getDisbursementDate().isAfter(submittedOnDate)) {
+            dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName)
+                    .failWithCode("before.loan.disbursement.date", "Submission date cannot be before the loan disbursement date");
+        }
+
+        final LocalDate rescheduleFromDate = this.fromJsonHelper.extractLocalDateNamed(
+                RescheduleLoansApiConstants.rescheduleFromDateParamName, jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).value(rescheduleFromDate).notNull();
+
+        final Integer graceOnPrincipal = this.fromJsonHelper.extractIntegerWithLocaleNamed(
+                RescheduleLoansApiConstants.graceOnPrincipalParamName, jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).value(graceOnPrincipal)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Integer graceOnInterest = this.fromJsonHelper.extractIntegerWithLocaleNamed(
+                RescheduleLoansApiConstants.graceOnInterestParamName, jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnInterestParamName).value(graceOnInterest).ignoreIfNull()
+                .integerGreaterThanZero();
+
+        final Integer extraTerms = this.fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.extraTermsParamName,
+                jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.extraTermsParamName).value(extraTerms).ignoreIfNull()
+                .integerGreaterThanZero();
+
+        final Long rescheduleReasonId = this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName,
+                jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonIdParamName).value(rescheduleReasonId).notNull()
+                .integerGreaterThanZero();
+
+        final String rescheduleReasonComment = this.fromJsonHelper.extractStringNamed(
+                RescheduleLoansApiConstants.rescheduleReasonCommentParamName, jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName).value(rescheduleReasonComment)
+                .ignoreIfNull().notExceedingLengthOf(500);
+
+        final LocalDate adjustedDueDate = this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adjustedDueDateParamName,
+                jsonElement);
+
+        if (adjustedDueDate != null && rescheduleFromDate != null && adjustedDueDate.isBefore(rescheduleFromDate)) {
+            dataValidatorBuilder
+                    .reset()
+                    .parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
+                    .failWithCode("adjustedDueDate.before.rescheduleFromDate",
+                            "Adjusted due date cannot be before the reschedule from date");
+        }
+
+        // at least one of the following must be provided => graceOnPrincipal,
+        // graceOnInterest, extraTerms, newInterestRate
+        if (!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnPrincipalParamName, jsonElement)
+                && !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnInterestParamName, jsonElement)
+                && !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTermsParamName, jsonElement)
+                && !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInterestRateParamName, jsonElement)
+                && !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjustedDueDateParamName, jsonElement)) {
+            dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).notNull();
+        }
+
+        if (rescheduleFromDate != null) {
+            LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate);
+
+            if (installment == null) {
+                dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
+                        .failWithCode("repayment.schedule.installment.does.not.exist", "Repayment schedule installment does not exist");
+            }
+
+            if (installment != null && installment.isObligationsMet()) {
+                dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
+                        .failWithCode("repayment.schedule.installment.obligation.met", "Repayment schedule installment obligation met");
+            }
+
+            if (installment != null && installment.isPartlyPaid()) {
+                dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
+                        .failWithCode("repayment.schedule.installment.partly.paid", "Repayment schedule installment is partly paid");
+            }
+        }
+
+        if (loanId != null) {
+            List<LoanRescheduleRequestData> loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
+                    .readLoanRescheduleRequests(loanId, LoanStatus.APPROVED.getValue());
+
+            if (loanRescheduleRequestData.size() > 0) {
+                dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled",
+                        "The loan can only be rescheduled once.");
+            }
+        }
+        if(loan.isMultiDisburmentLoan()) {
+            dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(RescheduleLoansApiConstants.resheduleForMultiDisbursementNotSupportedErrorCode,
+                    "Loan rescheduling is not supported for multidisbursement loans");
+        }
+        
+        if(loan.isInterestRecalculationEnabledForProduct()) {
+            dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(RescheduleLoansApiConstants.resheduleWithInterestRecalculationNotSupportedErrorCode,
+                    "Loan rescheduling is not supported for the loan product with interest recalculation enabled");
+        }
+        
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    /**
+     * Validates a user request to approve a loan reschedule request
+     * 
+     * @param jsonCommand
+     *            the JSON command object (instance of the JsonCommand class)
+     * @return void
+     **/
+    public void validateForApproveAction(final JsonCommand jsonCommand, LoanRescheduleRequest loanRescheduleRequest) {
+        final String jsonString = jsonCommand.json();
+
+        if (StringUtils.isBlank(jsonString)) { throw new InvalidJsonException(); }
+
+        final Type typeToken = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromJsonHelper.checkForUnsupportedParameters(typeToken, jsonString,
+                RescheduleLoansApiConstants.APPROVE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).resource(StringUtils
+                .lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+
+        final JsonElement jsonElement = jsonCommand.parsedJson();
+
+        final LocalDate approvedOnDate = this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.approvedOnDateParam,
+                jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).value(approvedOnDate).notNull();
+
+        if (approvedOnDate != null && loanRescheduleRequest.getSubmittedOnDate().isAfter(approvedOnDate)) {
+            dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam)
+                    .failWithCode("before.submission.date", "Approval date cannot be before the request submission date.");
+        }
+
+        LoanRescheduleRequestStatusEnumData loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
+                .status(loanRescheduleRequest.getStatusEnum());
+
+        if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) {
+            dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
+                    "request.is.not.in.submitted.and.pending.state",
+                    "Loan reschedule request approval is not allowed. "
+                            + "Loan reschedule request is not in submitted and pending approval state.");
+        }
+
+        LocalDate rescheduleFromDate = loanRescheduleRequest.getRescheduleFromDate();
+        final Loan loan = loanRescheduleRequest.getLoan();
+
+        if (loan != null) {
+            Long loanId = loan.getId();
+
+            if (!loan.status().isActive()) {
+                dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active", "Loan is not active");
+            }
+
+            if (rescheduleFromDate != null) {
+                LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate);
+
+                if (installment == null) {
+                    dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
+                            "loan.repayment.schedule.installment.does.not.exist", "Repayment schedule installment does not exist");
+                }
+
+                if (installment != null && installment.isObligationsMet()) {
+                    dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
+                            "loan.repayment.schedule.installment." + "obligation.met", "Repayment schedule installment obligation met");
+                }
+            }
+
+            if (loanId != null) {
+                List<LoanRescheduleRequestData> loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
+                        .readLoanRescheduleRequests(loanId, LoanStatus.APPROVED.getValue());
+
+                if (loanRescheduleRequestData.size() > 0) {
+                    dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled",
+                            "The loan can only be rescheduled once.");
+                }
+            }
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    /**
+     * Validates a user request to reject a loan reschedule request
+     * 
+     * @param jsonCommand
+     *            the JSON command object (instance of the JsonCommand class)
+     * @return void
+     **/
+    public void validateForRejectAction(final JsonCommand jsonCommand, LoanRescheduleRequest loanRescheduleRequest) {
+        final String jsonString = jsonCommand.json();
+
+        if (StringUtils.isBlank(jsonString)) { throw new InvalidJsonException(); }
+
+        final Type typeToken = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromJsonHelper
+                .checkForUnsupportedParameters(typeToken, jsonString, RescheduleLoansApiConstants.REJECT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).resource(StringUtils
+                .lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+
+        final JsonElement jsonElement = jsonCommand.parsedJson();
+
+        final LocalDate rejectedOnDate = this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.rejectedOnDateParam,
+                jsonElement);
+        dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam).value(rejectedOnDate).notNull();
+
+        if (rejectedOnDate != null && loanRescheduleRequest.getSubmittedOnDate().isAfter(rejectedOnDate)) {
+            dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam)
+                    .failWithCode("before.submission.date", "Rejection date cannot be before the request submission date.");
+        }
+
+        LoanRescheduleRequestStatusEnumData loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
+                .status(loanRescheduleRequest.getStatusEnum());
+
+        if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) {
+            dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
+                    "request.is.not.in.submitted.and.pending.state",
+                    "Loan reschedule request rejection is not allowed. "
+                            + "Loan reschedule request is not in submitted and pending approval state.");
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestEnumerations.java
new file mode 100644
index 0000000..8e58e8c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestEnumerations.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.loanaccount.rescheduleloan.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+
+public class LoanRescheduleRequestEnumerations {
+	
+	public static EnumOptionData status(final LoanRescheduleRequestStatusEnumData status) {
+		Long id = status.id();
+        String code = status.code();
+        String value = status.value();
+
+        return new EnumOptionData(id, code, value);
+	}
+
+	public static LoanRescheduleRequestStatusEnumData status(final Integer statusId) {
+		return status(LoanStatus.fromInt(statusId));
+	}
+	
+	public static LoanRescheduleRequestStatusEnumData status(final LoanStatus status) {
+		LoanRescheduleRequestStatusEnumData enumData = new LoanRescheduleRequestStatusEnumData(
+				LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue().longValue(),
+				LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
+		
+		switch(status) {
+			case SUBMITTED_AND_PENDING_APPROVAL:
+				enumData = new LoanRescheduleRequestStatusEnumData(
+						LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue().longValue(),
+						LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
+				break;
+				
+			case APPROVED:
+				enumData = new LoanRescheduleRequestStatusEnumData(
+						LoanStatus.APPROVED.getValue().longValue(),
+						LoanStatus.APPROVED.getCode(), "Approved");
+				break;
+				
+			case REJECTED:
+				enumData = new LoanRescheduleRequestStatusEnumData(
+						LoanStatus.REJECTED.getValue().longValue(),
+						LoanStatus.REJECTED.getCode(), "Rejected");
+				break;
+				
+			default:
+				break;
+		}
+		
+		return enumData;
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestStatusEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestStatusEnumData.java
new file mode 100644
index 0000000..d29782a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestStatusEnumData.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data;
+
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+
+/**
+ * Immutable data object represent loan reschedule request status enumerations.
+ **/
+public class LoanRescheduleRequestStatusEnumData {
+
+    private final Long id;
+    private final String code;
+    private final String value;
+    private final boolean pendingApproval;
+    private final boolean approved;
+    private final boolean rejected;
+
+    /**
+     * LoanRescheduleRequestStatusEnumData constructor
+     * 
+     * Note: Same status types/states for loan accounts are used in here
+     **/
+    public LoanRescheduleRequestStatusEnumData(Long id, String code, String value) {
+        this.id = id;
+        this.code = code;
+        this.value = value;
+        this.pendingApproval = Long.valueOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue()).equals(this.id);
+        this.approved = Long.valueOf(LoanStatus.APPROVED.getValue()).equals(this.id);
+        this.rejected = Long.valueOf(LoanStatus.REJECTED.getValue()).equals(this.id);
+    }
+
+    public Long id() {
+        return this.id;
+    }
+
+    public String code() {
+        return this.code;
+    }
+
+    public String value() {
+        return this.value;
+    }
+
+    public boolean isPendingApproval() {
+        return this.pendingApproval;
+    }
+
+    public boolean isApproved() {
+        return this.approved;
+    }
+
+    public boolean isRejected() {
+        return this.rejected;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestTimelineData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestTimelineData.java
new file mode 100644
index 0000000..9fe8bcd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestTimelineData.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.portfolio.loanaccount.rescheduleloan.data;
+
+import org.joda.time.LocalDate;
+
+/** 
+ * Immutable data object represent the timeline events of a loan reschedule request 
+ **/
+@SuppressWarnings("unused")
+public class LoanRescheduleRequestTimelineData {
+	private final LocalDate submittedOnDate;
+    private final String submittedByUsername;
+    private final String submittedByFirstname;
+    private final String submittedByLastname;
+    
+    private final LocalDate approvedOnDate;
+    private final String approvedByUsername;
+    private final String approvedByFirstname;
+    private final String approvedByLastname;
+    
+    private final LocalDate rejectedOnDate;
+    private final String rejectedByUsername;
+    private final String rejectedByFirstname;
+    private final String rejectedByLastname;
+    
+    public LoanRescheduleRequestTimelineData(final LocalDate submittedOnDate, final String submittedByUsername, final String submittedByFirstname,
+            final String submittedByLastname, final LocalDate approvedOnDate, final String approvedByUsername, final String approvedByFirstname,
+            final String approvedByLastname, final LocalDate rejectedOnDate, final String rejectedByUsername, final String rejectedByFirstname,
+            final String rejectedByLastname) {
+    	
+    	this.submittedOnDate = submittedOnDate;
+        this.submittedByUsername = submittedByUsername;
+        this.submittedByFirstname = submittedByFirstname;
+        this.submittedByLastname = submittedByLastname;
+        
+        this.approvedOnDate = approvedOnDate;
+        this.approvedByUsername = approvedByUsername;
+        this.approvedByFirstname = approvedByFirstname;
+        this.approvedByLastname = approvedByLastname;
+        
+        this.rejectedOnDate = rejectedOnDate;
+        this.rejectedByUsername = rejectedByUsername;
+        this.rejectedByFirstname = rejectedByFirstname;
+        this.rejectedByLastname = rejectedByLastname;
+    	
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java
new file mode 100644
index 0000000..d04a8a1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
+
+import java.math.MathContext;
+
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+
+public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory {
+
+    @Override
+    public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
+            final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
+            final HolidayDetailDTO holidayDetailDTO, final CalendarInstance restCalendarInstance,
+            final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, final FloatingRateDTO floatingRateDTO) {
+
+        LoanRescheduleModel loanRescheduleModel = null;
+
+        switch (interestMethod) {
+            case DECLINING_BALANCE:
+                loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
+                        applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
+                        floatingRateDTO);
+            break;
+
+            case FLAT:
+                loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
+                        applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
+                        floatingRateDTO);
+            break;
+
+            case INVALID:
+            break;
+        }
+
+        return loanRescheduleModel;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModalPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModalPeriod.java
new file mode 100644
index 0000000..0ae239b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModalPeriod.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.portfolio.loanaccount.rescheduleloan.domain;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.joda.time.LocalDate;
+
+public interface LoanRescheduleModalPeriod {
+	
+	LoanSchedulePeriodData toData();
+	
+	Integer periodNumber();
+	
+	Integer oldPeriodNumber();
+
+    LocalDate periodFromDate();
+
+    LocalDate periodDueDate();
+
+    BigDecimal principalDue();
+
+    BigDecimal interestDue();
+
+    BigDecimal feeChargesDue();
+
+    BigDecimal penaltyChargesDue();
+    
+    boolean isNew();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java
new file mode 100644
index 0000000..4c1714c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java
@@ -0,0 +1,118 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
+
+public final class LoanRescheduleModel {
+
+    private final Collection<LoanRescheduleModelRepaymentPeriod> periods;
+    private final Collection<LoanRepaymentScheduleHistory> oldPeriods;
+    private final ApplicationCurrency applicationCurrency;
+    private final int loanTermInDays;
+    private final Money totalPrincipalDisbursed;
+    private final BigDecimal totalPrincipalExpected;
+    private final BigDecimal totalPrincipalPaid;
+    private final BigDecimal totalInterestCharged;
+    private final BigDecimal totalFeeChargesCharged;
+    private final BigDecimal totalPenaltyChargesCharged;
+    private final BigDecimal totalRepaymentExpected;
+    private final BigDecimal totalOutstanding;
+
+    private LoanRescheduleModel(final Collection<LoanRescheduleModelRepaymentPeriod> periods,
+            final Collection<LoanRepaymentScheduleHistory> oldPeriods, final ApplicationCurrency applicationCurrency,
+            final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
+            final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
+            final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
+        this.periods = periods;
+        this.oldPeriods = oldPeriods;
+        this.applicationCurrency = applicationCurrency;
+        this.loanTermInDays = loanTermInDays;
+        this.totalPrincipalDisbursed = principalDisbursed;
+        this.totalPrincipalExpected = totalPrincipalExpected;
+        this.totalPrincipalPaid = totalPrincipalPaid;
+        this.totalInterestCharged = totalInterestCharged;
+        this.totalFeeChargesCharged = totalFeeChargesCharged;
+        this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
+        this.totalRepaymentExpected = totalRepaymentExpected;
+        this.totalOutstanding = totalOutstanding;
+    }
+
+    public static LoanRescheduleModel instance(final Collection<LoanRescheduleModelRepaymentPeriod> periods,
+            final Collection<LoanRepaymentScheduleHistory> oldPeriods, final ApplicationCurrency applicationCurrency,
+            final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
+            final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
+            final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
+
+        return new LoanRescheduleModel(periods, oldPeriods, applicationCurrency, loanTermInDays, principalDisbursed,
+                totalPrincipalExpected, totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged,
+                totalRepaymentExpected, totalOutstanding);
+    }
+
+    public static LoanRescheduleModel createWithSchedulehistory(LoanRescheduleModel loanRescheduleModel,
+            final Collection<LoanRepaymentScheduleHistory> oldPeriods) {
+
+        return new LoanRescheduleModel(loanRescheduleModel.periods, oldPeriods, loanRescheduleModel.applicationCurrency,
+                loanRescheduleModel.loanTermInDays, loanRescheduleModel.totalPrincipalDisbursed,
+                loanRescheduleModel.totalPrincipalExpected, loanRescheduleModel.totalPrincipalPaid,
+                loanRescheduleModel.totalInterestCharged, loanRescheduleModel.totalFeeChargesCharged,
+                loanRescheduleModel.totalPenaltyChargesCharged, loanRescheduleModel.totalRepaymentExpected,
+                loanRescheduleModel.totalOutstanding);
+    }
+
+    public LoanScheduleData toData() {
+
+        final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal();
+        final Integer inMultiplesOf = this.totalPrincipalDisbursed.getCurrencyInMultiplesOf();
+        final CurrencyData currency = this.applicationCurrency.toData(decimalPlaces, inMultiplesOf);
+
+        final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<>();
+        for (final LoanRescheduleModalPeriod modelPeriod : this.periods) {
+            periodsData.add(modelPeriod.toData());
+        }
+
+        final BigDecimal totalWaived = null;
+        final BigDecimal totalWrittenOff = null;
+        final BigDecimal totalRepayment = null;
+        final BigDecimal totalPaidInAdvance = null;
+        final BigDecimal totalPaidLate = null;
+
+        return new LoanScheduleData(currency, periodsData, this.loanTermInDays, this.totalPrincipalDisbursed.getAmount(),
+                this.totalPrincipalExpected, this.totalPrincipalPaid, this.totalInterestCharged, this.totalFeeChargesCharged,
+                this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment,
+                totalPaidInAdvance, totalPaidLate, this.totalOutstanding);
+    }
+
+    public Collection<LoanRescheduleModelRepaymentPeriod> getPeriods() {
+        return this.periods;
+    }
+
+    public Collection<LoanRepaymentScheduleHistory> getOldPeriods() {
+        return this.oldPeriods;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModelRepaymentPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModelRepaymentPeriod.java
new file mode 100644
index 0000000..23685ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModelRepaymentPeriod.java
@@ -0,0 +1,184 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.joda.time.LocalDate;
+
+public final class LoanRescheduleModelRepaymentPeriod implements LoanRescheduleModalPeriod {
+
+    private int periodNumber;
+    private int oldPeriodNumber;
+    private LocalDate fromDate;
+    private LocalDate dueDate;
+    private Money principalDue;
+    private Money outstandingLoanBalance;
+    private Money interestDue;
+    private Money feeChargesDue;
+    private Money penaltyChargesDue;
+    private Money totalDue;
+    private boolean isNew;
+
+    public LoanRescheduleModelRepaymentPeriod(final int periodNumber, final int oldPeriodNumber, LocalDate fromDate,
+            final LocalDate dueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
+            final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, final boolean isNew) {
+        this.periodNumber = periodNumber;
+        this.oldPeriodNumber = oldPeriodNumber;
+        this.fromDate = fromDate;
+        this.dueDate = dueDate;
+        this.principalDue = principalDue;
+        this.outstandingLoanBalance = outstandingLoanBalance;
+        this.interestDue = interestDue;
+        this.feeChargesDue = feeChargesDue;
+        this.penaltyChargesDue = penaltyChargesDue;
+        this.totalDue = totalDue;
+        this.isNew = isNew;
+    }
+
+    public static LoanRescheduleModelRepaymentPeriod instance(final int periodNumber, final int oldPeriodNumber, LocalDate fromDate,
+            final LocalDate dueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
+            final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, final boolean isNew) {
+
+        return new LoanRescheduleModelRepaymentPeriod(periodNumber, oldPeriodNumber, fromDate, dueDate, principalDue,
+                outstandingLoanBalance, interestDue, feeChargesDue, penaltyChargesDue, totalDue, isNew);
+    }
+
+    @Override
+    public LoanSchedulePeriodData toData() {
+        return LoanSchedulePeriodData.repaymentOnlyPeriod(this.periodNumber, this.fromDate, this.dueDate, this.principalDue.getAmount(),
+                this.outstandingLoanBalance.getAmount(), this.interestDue.getAmount(), this.feeChargesDue.getAmount(),
+                this.penaltyChargesDue.getAmount(), this.totalDue.getAmount(), this.principalDue.plus(this.interestDue).getAmount());
+    }
+
+    @Override
+    public Integer periodNumber() {
+        return this.periodNumber;
+    }
+
+    @Override
+    public Integer oldPeriodNumber() {
+        return this.oldPeriodNumber;
+    }
+
+    @Override
+    public LocalDate periodFromDate() {
+        return this.fromDate;
+    }
+
+    @Override
+    public LocalDate periodDueDate() {
+        return this.dueDate;
+    }
+
+    @Override
+    public BigDecimal principalDue() {
+        BigDecimal value = null;
+
+        if (this.principalDue != null) {
+            value = this.principalDue.getAmount();
+        }
+
+        return value;
+    }
+
+    @Override
+    public BigDecimal interestDue() {
+        BigDecimal value = null;
+
+        if (this.interestDue != null) {
+            value = this.interestDue.getAmount();
+        }
+
+        return value;
+    }
+
+    @Override
+    public BigDecimal feeChargesDue() {
+        BigDecimal value = null;
+
+        if (this.feeChargesDue != null) {
+            value = this.feeChargesDue.getAmount();
+        }
+
+        return value;
+    }
+
+    @Override
+    public BigDecimal penaltyChargesDue() {
+        BigDecimal value = null;
+
+        if (this.penaltyChargesDue != null) {
+            value = this.penaltyChargesDue.getAmount();
+        }
+
+        return value;
+    }
+
+    @Override
+    public boolean isNew() {
+        return isNew;
+    }
+
+    public void updatePeriodNumber(Integer periodNumber) {
+        this.periodNumber = periodNumber;
+    }
+
+    public void updateOldPeriodNumber(Integer oldPeriodNumber) {
+        this.oldPeriodNumber = oldPeriodNumber;
+    }
+
+    public void updatePeriodFromDate(LocalDate periodFromDate) {
+        this.fromDate = periodFromDate;
+    }
+
+    public void updatePeriodDueDate(LocalDate periodDueDate) {
+        this.dueDate = periodDueDate;
+    }
+
+    public void updatePrincipalDue(Money principalDue) {
+        this.principalDue = principalDue;
+    }
+
+    public void updateInterestDue(Money interestDue) {
+        this.interestDue = interestDue;
+    }
+
+    public void updateFeeChargesDue(Money feeChargesDue) {
+        this.feeChargesDue = feeChargesDue;
+    }
+
+    public void updatePenaltyChargesDue(Money penaltyChargesDue) {
+        this.penaltyChargesDue = penaltyChargesDue;
+    }
+
+    public void updateOutstandingLoanBalance(Money outstandingLoanBalance) {
+        this.outstandingLoanBalance = outstandingLoanBalance;
+    }
+
+    public void updateTotalDue(Money totalDue) {
+        this.totalDue = totalDue;
+    }
+
+    public void updateIsNew(boolean isNew) {
+        this.isNew = isNew;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
new file mode 100644
index 0000000..5ec4750
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
@@ -0,0 +1,351 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
+
+import java.math.BigDecimal;
+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.codes.domain.CodeValue;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_loan_reschedule_request")
+public class LoanRescheduleRequest extends AbstractPersistable<Long> {
+	
+	@ManyToOne
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+	
+	@Column(name = "status_enum", nullable = false)
+	private Integer statusEnum;
+	
+	@Column(name = "reschedule_from_installment")
+	private Integer rescheduleFromInstallment;
+	
+	@Column(name = "grace_on_principal")
+	private Integer graceOnPrincipal;
+	
+	@Column(name = "grace_on_interest")
+	private Integer graceOnInterest;
+	
+	@Temporal(TemporalType.DATE)
+    @Column(name = "reschedule_from_date")
+	private Date rescheduleFromDate;
+	
+	@Temporal(TemporalType.DATE)
+    @Column(name = "adjusted_due_date")
+	private Date adjustedDueDate;
+	
+	@Column(name = "extra_terms")
+	private Integer extraTerms;
+	
+	@Column(name = "recalculate_interest")
+	private Boolean recalculateInterest;
+	
+	@Column(name = "interest_rate", scale = 6, precision = 19)
+	private BigDecimal interestRate;
+	
+	@ManyToOne
+    @JoinColumn(name = "reschedule_reason_cv_id")
+	private CodeValue rescheduleReasonCodeValue;
+	
+	@Column(name = "reschedule_reason_comment")
+	private String rescheduleReasonComment;
+	
+	@Temporal(TemporalType.DATE)
+    @Column(name = "submitted_on_date")
+	private Date submittedOnDate;
+	
+	@ManyToOne
+    @JoinColumn(name = "submitted_by_user_id")
+	private AppUser submittedByUser;
+	
+	@Temporal(TemporalType.DATE)
+    @Column(name = "approved_on_date")
+	private Date approvedOnDate;
+	
+	@ManyToOne
+    @JoinColumn(name = "approved_by_user_id")
+	private AppUser approvedByUser;
+	
+	@Temporal(TemporalType.DATE)
+    @Column(name = "rejected_on_date")
+	private Date rejectedOnDate;
+	
+	@ManyToOne
+    @JoinColumn(name = "rejected_by_user_id")
+	private AppUser rejectedByUser;
+	
+	/** 
+	 * LoanRescheduleRequest constructor
+	 **/
+	protected LoanRescheduleRequest() {}
+	
+	/** 
+	 * LoanRescheduleRequest constructor
+	 **/
+	private LoanRescheduleRequest(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment, 
+			final Integer graceOnPrincipal, final Integer graceOnInterest, final Date rescheduleFromDate, final Date adjustedDueDate,
+			final Integer extraTerms, final Boolean recalculateInterest, final BigDecimal interestRate, final CodeValue rescheduleReasonCodeValue, 
+			final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser, 
+			final Date approvedOnDate, final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
+		this.loan = loan;
+		this.statusEnum = statusEnum;
+		this.rescheduleFromInstallment = rescheduleFromInstallment;
+		this.graceOnPrincipal = graceOnPrincipal;
+		this.graceOnInterest = graceOnInterest;
+		this.rescheduleFromDate = rescheduleFromDate;
+		this.extraTerms = extraTerms;
+		this.interestRate = interestRate;
+		this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
+		this.rescheduleReasonComment = rescheduleReasonComment;
+		this.submittedOnDate = submittedOnDate;
+		this.submittedByUser = submittedByUser;
+		this.approvedOnDate = approvedOnDate;
+		this.approvedByUser = approvedByUser;
+		this.rejectedOnDate = rejectedOnDate;
+		this.rejectedByUser = rejectedByUser; 
+		this.adjustedDueDate = adjustedDueDate;
+		this.recalculateInterest = recalculateInterest;
+	}
+	
+	/** 
+	 * @return a new instance of the LoanRescheduleRequest class
+	 **/
+	public static LoanRescheduleRequest instance(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment, 
+			final Integer graceOnPrincipal, final Integer graceOnInterest, final Date rescheduleFromDate, final Date adjustedDueDate,
+			final Integer extraTerms, final Boolean recalculateInterest, final BigDecimal interestRate, final CodeValue rescheduleReasonCodeValue, 
+			final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser, 
+			final Date approvedOnDate, final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
+		
+		return new LoanRescheduleRequest(loan, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, 
+				rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate,
+				submittedByUser, approvedOnDate, approvedByUser, rejectedOnDate, rejectedByUser);
+	}
+	
+	/** 
+	 * @return the reschedule request loan object 
+	 **/
+	public Loan getLoan() {
+		return this.loan;
+	}
+	
+	/** 
+	 * @return the status enum 
+	 **/
+	public Integer getStatusEnum() {
+		return this.statusEnum;
+	}
+	
+	/** 
+	 * @return installment number of the rescheduling start point
+	 **/
+	public Integer getRescheduleFromInstallment() {
+		return this.rescheduleFromInstallment;
+	}
+	
+	/** 
+	 * @return the grace on principal 
+	 **/
+	public Integer getGraceOnPrincipal() {
+		return this.graceOnPrincipal;
+	}
+	
+	/** 
+	 * @return the grace on interest 
+	 **/
+	public Integer getGraceOnInterest() {
+		return this.graceOnInterest;
+	}
+	
+	/** 
+	 * @return due date of the rescheduling start point 
+	 **/
+	public LocalDate getRescheduleFromDate() {
+		
+		LocalDate localDate = null;
+		
+		if(this.rescheduleFromDate != null) {
+			localDate = new LocalDate(this.rescheduleFromDate);
+		}
+		
+		return localDate;
+	}
+	
+	/** 
+	 * @return due date of the first rescheduled installment
+	 **/
+	public LocalDate getAdjustedDueDate() {
+		
+		LocalDate localDate = null;
+		
+		if(this.adjustedDueDate != null) {
+			localDate = new LocalDate(this.adjustedDueDate);
+		}
+		
+		return localDate;
+	}
+	
+	/** 
+	 * @return extra terms to be added after the last loan installment 
+	 **/
+	public Integer getExtraTerms() {
+		return this.extraTerms;
+	}
+	
+	/** 
+	 * @return the new interest rate to be applied to unpaid installments 
+	 **/
+	public BigDecimal getInterestRate() {
+		return this.interestRate;
+	}
+	
+	/** 
+	 * @return the reschedule reason code value object 
+	 **/
+	public CodeValue getRescheduleReasonCodeValue() {
+		return this.rescheduleReasonCodeValue;
+	}
+	
+	/** 
+	 * @return the reschedule reason comment added by the "submittedByUser" 
+	 **/
+	public String getRescheduleReasonComment() {
+		return this.rescheduleReasonComment;
+	}
+	
+	/** 
+	 * @return the date the request was submitted 
+	 **/
+	public LocalDate getSubmittedOnDate() {
+		LocalDate localDate = null;
+		
+		if(this.submittedOnDate != null) {
+			localDate = new LocalDate(this.submittedOnDate);
+		}
+		
+		return localDate;
+	}
+	
+	/** 
+	 * @return the user that submitted the request 
+	 **/
+	public AppUser getSubmittedByUser() {
+		return this.submittedByUser;
+	}
+	
+	/** 
+	 * @return the date the request was approved 
+	 **/
+	public LocalDate getApprovedOnDate() {
+		LocalDate localDate = null;
+		
+		if(this.approvedOnDate != null) {
+			localDate = new LocalDate(this.approvedOnDate);
+		}
+		
+		return localDate;
+	}
+	
+	/** 
+	 * @return the user that approved the request 
+	 **/
+	public AppUser getApprovedByUser() {
+		return this.approvedByUser;
+	}
+	
+	/** 
+	 * @return the date the request was rejected 
+	 **/
+	public LocalDate getRejectedOnDate() {
+		LocalDate localDate = null;
+		
+		if(this.rejectedOnDate != null) {
+			localDate = new LocalDate(this.rejectedOnDate);
+		}
+		
+		return localDate;
+	}
+	
+	/** 
+	 * @return the recalculate interest option (true/false) 
+	 **/
+	public Boolean getRecalculateInterest() {
+		boolean recalculateInterest = false;
+		
+		if(this.recalculateInterest != null) {
+			recalculateInterest = this.recalculateInterest;
+		}
+		
+		return recalculateInterest;
+	}
+	
+	/** 
+	 * @return the user that rejected the request 
+	 **/
+	public AppUser getRejectedByUser() {
+		return this.approvedByUser;
+	}
+	
+	/** 
+	 * change the status of the loan reschedule request to approved, also updating the 
+	 * approvedByUser and approvedOnDate properties 
+	 * 
+	 * @param approvedByUser the user who approved the request
+	 * @param approvedOnDate the date of the approval
+	 * @return void
+	 **/
+	public void approve(final AppUser approvedByUser, final LocalDate approvedOnDate) {
+		
+		if(approvedOnDate != null) {
+			this.approvedByUser = approvedByUser;
+			this.approvedOnDate = approvedOnDate.toDate();
+			this.statusEnum = LoanStatus.APPROVED.getValue();
+		}
+	}
+	
+	/** 
+	 * change the status of the loan reschedule request to rejected, also updating the 
+	 * approvedByUser and approvedOnDate properties 
+	 * 
+	 * @param approvedByUser the user who approved the request
+	 * @param approvedOnDate the date of the approval
+	 * @return void
+	 **/
+	public void reject(final AppUser approvedByUser, final LocalDate approvedOnDate) {
+		
+		if(approvedOnDate != null) {
+			this.rejectedByUser = approvedByUser;
+			this.rejectedOnDate = approvedOnDate.toDate();
+			this.statusEnum = LoanStatus.REJECTED.getValue();
+		}
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequestRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequestRepository.java
new file mode 100644
index 0000000..0822279
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequestRepository.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.loanaccount.rescheduleloan.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanRescheduleRequestRepository extends JpaRepository<LoanRescheduleRequest, Long>, JpaSpecificationExecutor<LoanRescheduleRequest> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java
new file mode 100644
index 0000000..8356e1b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.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.loanaccount.rescheduleloan.domain;
+
+import java.math.MathContext;
+
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+
+public interface LoanReschedulerFactory {
+
+    public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
+            final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
+            final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance,
+            final Calendar loanCalendar, FloatingRateDTO floatingRateDTO);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/exception/LoanRescheduleRequestNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/exception/LoanRescheduleRequestNotFoundException.java
new file mode 100644
index 0000000..9a8afce
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/exception/LoanRescheduleRequestNotFoundException.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when loan reschedule request resources are not found.
+ **/
+public class LoanRescheduleRequestNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+	/** 
+	 * LoanRescheduleRequestNotFoundException constructor 
+	 * 
+	 * @param requestId the loan reschedule request identifier
+	 * @return void
+	 **/
+	public LoanRescheduleRequestNotFoundException(final Long requestId) {
+		super("error.msg.loan.reschedule.request.id.invalid", 
+				"Loan reschedule request with identifier " + requestId + " does not exist", requestId);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/ApproveLoanRescheduleRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/ApproveLoanRescheduleRequestCommandHandler.java
new file mode 100644
index 0000000..974a811
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/ApproveLoanRescheduleRequestCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RESCHEDULELOAN", action = "APPROVE")
+public class ApproveLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
+	private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
+	
+	@Autowired
+	public ApproveLoanRescheduleRequestCommandHandler(
+			LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) {
+		this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService;
+	}
+	
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+		return this.loanRescheduleRequestWritePlatformService.approve(jsonCommand);
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/CreateLoanRescheduleRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/CreateLoanRescheduleRequestCommandHandler.java
new file mode 100644
index 0000000..b76e316
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/CreateLoanRescheduleRequestCommandHandler.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.loanaccount.rescheduleloan.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RESCHEDULELOAN", action = "CREATE")
+public class CreateLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
+	
+	private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
+	
+	@Autowired
+	public CreateLoanRescheduleRequestCommandHandler(
+			LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) {
+		this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService;
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+		return this.loanRescheduleRequestWritePlatformService.create(jsonCommand);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/RejectLoanRescheduleRequestCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/RejectLoanRescheduleRequestCommandHandler.java
new file mode 100644
index 0000000..4e97a07
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/RejectLoanRescheduleRequestCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RESCHEDULELOAN", action = "REJECT")
+public class RejectLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
+	private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
+	
+	@Autowired
+	public RejectLoanRescheduleRequestCommandHandler(
+			LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) {
+		this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService;
+	}
+	
+	@Transactional
+	@Override
+	public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+		return this.loanRescheduleRequestWritePlatformService.reject(jsonCommand);
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformService.java
new file mode 100644
index 0000000..b300b4d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformService.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.loanaccount.rescheduleloan.service;
+
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
+
+public interface LoanReschedulePreviewPlatformService {
+	
+	public LoanRescheduleModel previewLoanReschedule(Long requestId);
+}
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
new file mode 100644
index 0000000..e3d6e4e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java
@@ -0,0 +1,151 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
+import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
+import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanReschedulePreviewPlatformServiceImpl implements LoanReschedulePreviewPlatformService {
+
+    private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final HolidayRepository holidayRepository;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+    private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
+
+    @Autowired
+    public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
+            final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository,
+            final WorkingDaysRepositoryWrapper workingDaysRepository,
+            final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
+            final CalendarInstanceRepository calendarInstanceRepository,
+            final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
+        this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
+        this.applicationCurrencyRepository = applicationCurrencyRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.holidayRepository = holidayRepository;
+        this.workingDaysRepository = workingDaysRepository;
+        this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
+    }
+
+    @Override
+    public LoanRescheduleModel previewLoanReschedule(Long requestId) {
+        final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(requestId);
+
+        if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(requestId); }
+
+        Loan loan = loanRescheduleRequest.getLoan();
+
+        final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+        final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
+                .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+        final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
+        final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+
+        final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
+        final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
+        final MathContext mathContext = new MathContext(8, roundingMode);
+        List<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive(
+                loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
+        HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
+        CalendarInstance restCalendarInstance = null;
+        CalendarInstance compoundingCalendarInstance = null;
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
+                    CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
+            compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
+                    loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
+        }
+        final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
+                CalendarEntityType.LOANS.getValue());
+        Calendar loanCalendar = null;
+        if (loanCalendarInstance != null) {
+            loanCalendar = loanCalendarInstance.getCalendar();
+        }
+        final FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
+        LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
+                loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
+                loanCalendar, floatingRateDTO);
+        LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel,
+                oldPeriods);
+        return loanRescheduleModelWithOldPeriods;
+    }
+
+    private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
+        FloatingRateDTO floatingRateDTO = null;
+        if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
+            boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
+            BigDecimal interestRateDiff = loan.getInterestRateDifferential();
+            List<FloatingRatePeriodData> baseLendingRatePeriods = null;
+            try{
+            	baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
+            								.getRatePeriods();
+            }catch(final FloatingRateNotFoundException ex){
+            	// Do not do anything
+            }
+            floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
+                    baseLendingRatePeriods);
+        }
+        return floatingRateDTO;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformService.java
new file mode 100644
index 0000000..14232a9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformService.java
@@ -0,0 +1,62 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
+
+import java.util.List;
+
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestData;
+
+public interface LoanRescheduleRequestReadPlatformService {
+
+    /**
+     * get all loan reschedule requests by loan ID
+     * 
+     * @param loanId
+     *            the loan identifier
+     * @return list of LoanRescheduleRequestData objects
+     **/
+    public List<LoanRescheduleRequestData> readLoanRescheduleRequests(Long loanId);
+
+    /**
+     * get a single loan reschedule request by ID (primary key)
+     * 
+     * @param requestId
+     *            the loan reschedule request identifier
+     * @return a LoanRescheduleRequestData object
+     **/
+    public LoanRescheduleRequestData readLoanRescheduleRequest(Long requestId);
+
+    /**
+     * get all loan reschedule requests filter by loan ID and status enum
+     * 
+     * @param loanId
+     *            the loan identifier
+     * @return list of LoanRescheduleRequestData objects
+     **/
+    public List<LoanRescheduleRequestData> readLoanRescheduleRequests(Long loanId, Integer statusEnum);
+
+    /**
+     * get all loan reschedule reasons
+     * 
+     * @param loanRescheduleReason
+     *            the loan reschedule reason
+     * @return list of LoanRescheduleRequestData objects
+     **/
+    public LoanRescheduleRequestData retrieveAllRescheduleReasons(String loanRescheduleReason);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformServiceImpl.java
new file mode 100644
index 0000000..c880364
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformServiceImpl.java
@@ -0,0 +1,230 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestData;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestEnumerations;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestStatusEnumData;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestTimelineData;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanRescheduleRequestReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final LoanRepository loanRepository;
+    private final LoanRescheduleRequestRowMapper loanRescheduleRequestRowMapper = new LoanRescheduleRequestRowMapper();
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+
+    @Autowired
+    public LoanRescheduleRequestReadPlatformServiceImpl(final RoutingDataSource dataSource, LoanRepository loanRepository,
+            final CodeValueReadPlatformService codeValueReadPlatformService) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.loanRepository = loanRepository;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+    }
+
+    private static final class LoanRescheduleRequestRowMapper implements RowMapper<LoanRescheduleRequestData> {
+
+        private final String schema;
+
+        public LoanRescheduleRequestRowMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(200);
+
+            sqlBuilder.append("lr.id as id, lr.loan_id as loanId, lr.status_enum as statusEnum, ");
+            sqlBuilder.append("mc.display_name as clientName, ");
+            sqlBuilder.append("mc.id as clientId, ");
+            sqlBuilder.append("ml.account_no as loanAccountNumber, ");
+            sqlBuilder.append("lr.reschedule_from_installment as rescheduleFromInstallment, ");
+            sqlBuilder.append("lr.grace_on_principal as graceOnPrincipal, ");
+            sqlBuilder.append("lr.grace_on_interest as graceOnInterest, ");
+            sqlBuilder.append("lr.reschedule_from_date as rescheduleFromDate, ");
+            sqlBuilder.append("lr.adjusted_due_date as adjustedDueDate, ");
+            sqlBuilder.append("lr.extra_terms as extraTerms, ");
+            sqlBuilder.append("lr.recalculate_interest as recalculateInterest, ");
+            sqlBuilder.append("lr.interest_rate as interestRate, ");
+            sqlBuilder.append("lr.reschedule_reason_cv_id as rescheduleReasonCvId, ");
+            sqlBuilder.append("cv.code_value as rescheduleReasonCvValue, ");
+            sqlBuilder.append("lr.reschedule_reason_comment as rescheduleReasonComment, ");
+
+            sqlBuilder.append("lr.submitted_on_date as submittedOnDate, ");
+            sqlBuilder.append("sbu.username as submittedByUsername, ");
+            sqlBuilder.append("sbu.firstname as submittedByFirstname, ");
+            sqlBuilder.append("sbu.lastname as submittedByLastname, ");
+
+            sqlBuilder.append("lr.approved_on_date as approvedOnDate, ");
+            sqlBuilder.append("abu.username as approvedByUsername, ");
+            sqlBuilder.append("abu.firstname as approvedByFirstname, ");
+            sqlBuilder.append("abu.lastname as approvedByLastname, ");
+
+            sqlBuilder.append("lr.rejected_on_date as rejectedOnDate, ");
+            sqlBuilder.append("rbu.username as rejectedByUsername, ");
+            sqlBuilder.append("rbu.firstname as rejectedByFirstname, ");
+            sqlBuilder.append("rbu.lastname as rejectedByLastname ");
+
+            sqlBuilder.append("from " + loanRescheduleRequestTableName() + " lr ");
+            sqlBuilder.append("left join m_code_value cv on cv.id = lr.reschedule_reason_cv_id ");
+            sqlBuilder.append("left join m_appuser sbu on sbu.id = lr.submitted_by_user_id ");
+            sqlBuilder.append("left join m_appuser abu on abu.id = lr.approved_by_user_id ");
+            sqlBuilder.append("left join m_appuser rbu on rbu.id = lr.rejected_by_user_id ");
+            sqlBuilder.append("left join m_loan ml on ml.id = lr.loan_id ");
+            sqlBuilder.append("left join m_client mc on mc.id = ml.client_id ");
+
+            this.schema = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+
+        public String loanRescheduleRequestTableName() {
+            return "m_loan_reschedule_request";
+        }
+
+        @Override
+        @SuppressWarnings("unused")
+        public LoanRescheduleRequestData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final Long loanId = rs.getLong("loanId");
+            final Integer statusEnumId = JdbcSupport.getInteger(rs, "statusEnum");
+            final LoanRescheduleRequestStatusEnumData statusEnum = LoanRescheduleRequestEnumerations.status(statusEnumId);
+            final String clientName = rs.getString("clientName");
+            final String loanAccountNumber = rs.getString("loanAccountNumber");
+            final Long clientId = rs.getLong("clientId");
+            final Integer rescheduleFromInstallment = JdbcSupport.getInteger(rs, "rescheduleFromInstallment");
+            final Integer graceOnPrincipal = JdbcSupport.getInteger(rs, "graceOnPrincipal");
+            final Integer graceOnInterest = JdbcSupport.getInteger(rs, "graceOnInterest");
+            final LocalDate rescheduleFromDate = JdbcSupport.getLocalDate(rs, "rescheduleFromDate");
+            final LocalDate adjustedDueDate = JdbcSupport.getLocalDate(rs, "adjustedDueDate");
+            final Integer extraTerms = JdbcSupport.getInteger(rs, "extraTerms");
+            final BigDecimal interestRate = rs.getBigDecimal("interestRate");
+            final Long rescheduleReasonCvId = JdbcSupport.getLong(rs, "rescheduleReasonCvId");
+            final String rescheduleReasonCvValue = rs.getString("rescheduleReasonCvValue");
+            final CodeValueData rescheduleReasonCodeValue = CodeValueData.instance(rescheduleReasonCvId, rescheduleReasonCvValue);
+            final String rescheduleReasonComment = rs.getString("rescheduleReasonComment");
+            final Boolean recalculateInterest = rs.getBoolean("recalculateInterest");
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
+            final String approvedByUsername = rs.getString("approvedByUsername");
+            final String approvedByFirstname = rs.getString("approvedByFirstname");
+            final String approvedByLastname = rs.getString("approvedByLastname");
+
+            final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate");
+            final String rejectedByUsername = rs.getString("rejectedByUsername");
+            final String rejectedByFirstname = rs.getString("rejectedByFirstname");
+            final String rejectedByLastname = rs.getString("rejectedByLastname");
+            final Collection<CodeValueData> rescheduleReasons = null;
+            final LoanRescheduleRequestTimelineData timeline = new LoanRescheduleRequestTimelineData(submittedOnDate, submittedByUsername,
+                    submittedByFirstname, submittedByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname,
+                    rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname);
+
+            return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
+                    rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment,
+                    timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons);
+        }
+
+    }
+
+    @Override
+    public List<LoanRescheduleRequestData> readLoanRescheduleRequests(Long loanId) {
+        final Loan loan = this.loanRepository.findOne(loanId);
+
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+        final String sql = "select " + this.loanRescheduleRequestRowMapper.schema() + " where lr.loan_id = ?";
+
+        return this.jdbcTemplate.query(sql, this.loanRescheduleRequestRowMapper, new Object[] { loanId });
+    }
+
+    @Override
+    public LoanRescheduleRequestData readLoanRescheduleRequest(Long requestId) {
+
+        try {
+            final String sql = "select " + this.loanRescheduleRequestRowMapper.schema() + " where lr.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, this.loanRescheduleRequestRowMapper, new Object[] { requestId });
+        }
+
+        catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public List<LoanRescheduleRequestData> readLoanRescheduleRequests(Long loanId, Integer statusEnum) {
+        final Loan loan = this.loanRepository.findOne(loanId);
+
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+        final String sql = "select " + this.loanRescheduleRequestRowMapper.schema() + " where lr.loan_id = ?" + " and lr.status_enum = ?";
+
+        return this.jdbcTemplate.query(sql, this.loanRescheduleRequestRowMapper, new Object[] { loanId, statusEnum });
+    }
+
+    @Override
+    public LoanRescheduleRequestData retrieveAllRescheduleReasons(String loanRescheduleReason) {
+        final List<CodeValueData> rescheduleReasons = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(loanRescheduleReason));
+        final Long id = null;
+        final Long loanId = null;
+        final LoanRescheduleRequestStatusEnumData statusEnum = null;
+        final Integer rescheduleFromInstallment = null;
+        final Integer graceOnPrincipal = null;
+        final Integer graceOnInterest = null;
+        final LocalDate rescheduleFromDate = null;
+        final LocalDate adjustedDueDate = null;
+        final Integer extraTerms = null;
+        final BigDecimal interestRate = null;
+        final CodeValueData rescheduleReasonCodeValue = null;
+        final String rescheduleReasonComment = null;
+        final LoanRescheduleRequestTimelineData timeline = null;
+        final String clientName = null;
+        final String loanAccountNumber = null;
+        final Long clientId = null;
+        final Boolean recalculateInterest = null;
+
+        return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
+                rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment,
+                timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformService.java
new file mode 100644
index 0000000..b2dccf9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface LoanRescheduleRequestWritePlatformService {
+	
+	CommandProcessingResult create(JsonCommand jsonCommand);
+
+	CommandProcessingResult approve(JsonCommand jsonCommand);
+	
+	CommandProcessingResult reject(JsonCommand jsonCommand);
+}
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
new file mode 100644
index 0000000..b1de9a9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
@@ -0,0 +1,602 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
+import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
+import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
+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.LoanLifecycleStateMachine;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
+import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanRescheduleRequestWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class);
+
+    private final LoanRepositoryWrapper loanRepositoryWrapper;
+    private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
+    private final PlatformSecurityContext platformSecurityContext;
+    private final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator;
+    private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final HolidayRepositoryWrapper holidayRepository;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+    private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository;
+    private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final LoanChargeReadPlatformService loanChargeReadPlatformService;
+    private final LoanTransactionRepository loanTransactionRepository;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+    private final LoanRepository loanRepository;
+    private final LoanAssembler loanAssembler;
+    private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
+    private final LoanUtilService loanUtilService;
+
+    /**
+     * LoanRescheduleRequestWritePlatformServiceImpl constructor
+     * 
+     * @return void
+     **/
+    @Autowired
+    public LoanRescheduleRequestWritePlatformServiceImpl(LoanRepositoryWrapper loanRepositoryWrapper,
+            CodeValueRepositoryWrapper codeValueRepositoryWrapper, PlatformSecurityContext platformSecurityContext,
+            LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator,
+            LoanRescheduleRequestRepository loanRescheduleRequestRepository,
+            ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, ConfigurationDomainService configurationDomainService,
+            HolidayRepositoryWrapper holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository,
+            LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository,
+            final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
+            final CalendarInstanceRepository calendarInstanceRepository, final LoanChargeReadPlatformService loanChargeReadPlatformService,
+            final LoanTransactionRepository loanTransactionRepository,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanRepository loanRepository,
+            final LoanAssembler loanAssembler, final FloatingRatesReadPlatformService floatingRatesReadPlatformService,
+            final LoanUtilService loanUtilService) {
+        this.loanRepositoryWrapper = loanRepositoryWrapper;
+        this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
+        this.platformSecurityContext = platformSecurityContext;
+        this.loanRescheduleRequestDataValidator = loanRescheduleRequestDataValidator;
+        this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
+        this.applicationCurrencyRepository = applicationCurrencyRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.holidayRepository = holidayRepository;
+        this.workingDaysRepository = workingDaysRepository;
+        this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository;
+        this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.loanChargeReadPlatformService = loanChargeReadPlatformService;
+        this.loanTransactionRepository = loanTransactionRepository;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+        this.loanRepository = loanRepository;
+        this.loanAssembler = loanAssembler;
+        this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
+        this.loanUtilService = loanUtilService;
+    }
+
+    /**
+     * create a new instance of the LoanRescheduleRequest object from the
+     * JsonCommand object and persist
+     * 
+     * @return CommandProcessingResult object
+     **/
+    @Override
+    @Transactional
+    public CommandProcessingResult create(JsonCommand jsonCommand) {
+
+        try {
+            // get the loan id from the JsonCommand object
+            final Long loanId = jsonCommand.longValueOfParameterNamed(RescheduleLoansApiConstants.loanIdParamName);
+
+            // use the loan id to get a Loan entity object
+            final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
+
+            // validate the request in the JsonCommand object passed as
+            // parameter
+            this.loanRescheduleRequestDataValidator.validateForCreateAction(jsonCommand, loan);
+
+            // get the reschedule reason code value id from the JsonCommand
+            // object
+            final Long rescheduleReasonId = jsonCommand.longValueOfParameterNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName);
+
+            // use the reschedule reason code value id to get a CodeValue entity
+            // object
+            final CodeValue rescheduleReasonCodeValue = this.codeValueRepositoryWrapper.findOneWithNotFoundDetection(rescheduleReasonId);
+
+            // get the grace on principal integer value from the JsonCommand
+            // object
+            final Integer graceOnPrincipal = jsonCommand
+                    .integerValueOfParameterNamed(RescheduleLoansApiConstants.graceOnPrincipalParamName);
+
+            // get the grace on interest integer value from the JsonCommand
+            // object
+            final Integer graceOnInterest = jsonCommand.integerValueOfParameterNamed(RescheduleLoansApiConstants.graceOnInterestParamName);
+
+            // get the extra terms to be added at the end of the new schedule
+            // from the JsonCommand object
+            final Integer extraTerms = jsonCommand.integerValueOfParameterNamed(RescheduleLoansApiConstants.extraTermsParamName);
+
+            // get the new interest rate that would be applied to the new loan
+            // schedule
+            final BigDecimal interestRate = jsonCommand
+                    .bigDecimalValueOfParameterNamed(RescheduleLoansApiConstants.newInterestRateParamName);
+
+            // get the reschedule reason comment text from the JsonCommand
+            // object
+            final String rescheduleReasonComment = jsonCommand
+                    .stringValueOfParameterNamed(RescheduleLoansApiConstants.rescheduleReasonCommentParamName);
+
+            // get the recalculate interest option
+            final Boolean recalculateInterest = jsonCommand
+                    .booleanObjectValueOfParameterNamed(RescheduleLoansApiConstants.recalculateInterestParamName);
+
+            // initialize set the value to null
+            Date submittedOnDate = null;
+
+            // check if the parameter is in the JsonCommand object
+            if (jsonCommand.hasParameter(RescheduleLoansApiConstants.submittedOnDateParamName)) {
+                // create a LocalDate object from the "submittedOnDate" Date
+                // string
+                LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.submittedOnDateParamName);
+
+                if (localDate != null) {
+                    // update the value of the "submittedOnDate" variable
+                    submittedOnDate = localDate.toDate();
+                }
+            }
+
+            // initially set the value to null
+            Date rescheduleFromDate = null;
+
+            // start point of the rescheduling exercise
+            Integer rescheduleFromInstallment = null;
+
+            // initially set the value to null
+            Date adjustedDueDate = null;
+
+            // check if the parameter is in the JsonCommand object
+            if (jsonCommand.hasParameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)) {
+                // create a LocalDate object from the "rescheduleFromDate" Date
+                // string
+                LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName);
+
+                if (localDate != null) {
+                    // get installment by due date
+                    LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(localDate);
+                    rescheduleFromInstallment = installment.getInstallmentNumber();
+
+                    // update the value of the "rescheduleFromDate" variable
+                    rescheduleFromDate = localDate.toDate();
+                }
+            }
+
+            if (jsonCommand.hasParameter(RescheduleLoansApiConstants.adjustedDueDateParamName)) {
+                // create a LocalDate object from the "adjustedDueDate" Date
+                // string
+                LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.adjustedDueDateParamName);
+
+                if (localDate != null) {
+                    // update the value of the "adjustedDueDate"variable
+                    adjustedDueDate = localDate.toDate();
+                }
+            }
+
+            final LoanRescheduleRequest loanRescheduleRequest = LoanRescheduleRequest.instance(loan,
+                    LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
+                    rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue,
+                    rescheduleReasonComment, submittedOnDate, this.platformSecurityContext.authenticatedUser(), null, null, null, null);
+
+            // create a new entry in the m_loan_reschedule_request table
+            this.loanRescheduleRequestRepository.save(loanRescheduleRequest);
+
+            return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequest.getId())
+                    .withLoanId(loan.getId()).build();
+        }
+
+        catch (final DataIntegrityViolationException dve) {
+            // handle the data integrity violation
+            handleDataIntegrityViolation(dve);
+
+            // return an empty command processing result object
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult approve(JsonCommand jsonCommand) {
+
+        try {
+            final Long loanRescheduleRequestId = jsonCommand.entityId();
+
+            final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(loanRescheduleRequestId);
+
+            if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(loanRescheduleRequestId); }
+
+            // validate the request in the JsonCommand object passed as
+            // parameter
+            this.loanRescheduleRequestDataValidator.validateForApproveAction(jsonCommand, loanRescheduleRequest);
+
+            final AppUser appUser = this.platformSecurityContext.authenticatedUser();
+            final Map<String, Object> changes = new LinkedHashMap<>();
+
+            LocalDate approvedOnDate = jsonCommand.localDateValueOfParameterNamed("approvedOnDate");
+            final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale(
+                    jsonCommand.extractLocale());
+
+            changes.put("locale", jsonCommand.locale());
+            changes.put("dateFormat", jsonCommand.dateFormat());
+            changes.put("approvedOnDate", approvedOnDate.toString(dateTimeFormatter));
+            changes.put("approvedByUserId", appUser.getId());
+
+            if (!changes.isEmpty()) {
+                Loan loan = loanRescheduleRequest.getLoan();
+                final LoanSummary loanSummary = loan.getSummary();
+
+                final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+                final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
+                        .getDisbursementDate().toDate());
+                final WorkingDays workingDays = this.workingDaysRepository.findOne();
+                final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
+                final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
+                final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+
+                final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
+                final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
+                final MathContext mathContext = new MathContext(8, roundingMode);
+
+                Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService
+                        .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
+
+                HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
+                CalendarInstance restCalendarInstance = null;
+                CalendarInstance compoundingCalendarInstance = null;
+                if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                    restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
+                            loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
+                    compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
+                            loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
+                }
+                final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
+                        CalendarEntityType.LOANS.getValue());
+                Calendar loanCalendar = null;
+                if (loanCalendarInstance != null) {
+                    loanCalendar = loanCalendarInstance.getCalendar();
+                }
+                FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
+                LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
+                        loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
+                        loanCalendar, floatingRateDTO);
+
+                final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
+                List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
+                Collection<LoanCharge> waiveLoanCharges = new ArrayList<>();
+
+                for (LoanRescheduleModelRepaymentPeriod period : periods) {
+
+                    if (period.isNew()) {
+                        LoanRepaymentScheduleInstallment repaymentScheduleInstallment = new LoanRepaymentScheduleInstallment(loan,
+                                period.periodNumber(), period.periodFromDate(), period.periodDueDate(), period.principalDue(),
+                                period.interestDue(), BigDecimal.ZERO, BigDecimal.ZERO, false);
+
+                        repaymentScheduleInstallments.add(repaymentScheduleInstallment);
+                    }
+
+                    else {
+                        for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
+
+                            if (repaymentScheduleInstallment.getInstallmentNumber().equals(period.oldPeriodNumber())) {
+
+                                LocalDate periodDueDate = repaymentScheduleInstallment.getDueDate();
+                                Money zeroAmount = Money.of(currency, new BigDecimal(0));
+
+                                repaymentScheduleInstallment.updateInstallmentNumber(period.periodNumber());
+                                repaymentScheduleInstallment.updateFromDate(period.periodFromDate());
+                                repaymentScheduleInstallment.updateDueDate(period.periodDueDate());
+                                repaymentScheduleInstallment.updatePrincipal(period.principalDue());
+                                repaymentScheduleInstallment.updateInterestCharged(period.interestDue());
+
+                                if (Money.of(currency, period.principalDue()).isZero() && Money.of(currency, period.interestDue()).isZero()
+                                        && repaymentScheduleInstallment.isNotFullyPaidOff()) {
+
+                                    if (repaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).isGreaterThan(zeroAmount)
+                                            || repaymentScheduleInstallment.getFeeChargesOutstanding(currency).isGreaterThan(zeroAmount)) {
+
+                                        waiveLoanCharges.addAll(loan.getLoanCharges(periodDueDate));
+                                    }
+                                }
+
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                for (LoanRepaymentScheduleHistory loanRepaymentScheduleHistory : loanRepaymentScheduleHistoryList) {
+                    this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistory);
+                }
+
+                loan.updateRescheduledByUser(appUser);
+                loan.updateRescheduledOnDate(new LocalDate());
+
+                // waive all loan charges of zero instalments
+                waiveLoanCharges(loan, waiveLoanCharges);
+
+                // update the Loan summary
+                loanSummary
+                        .updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true, null);
+
+                // update the total number of schedule repayments
+                loan.updateNumberOfRepayments(periods.size());
+
+                // update the loan term frequency (loan term frequency = number
+                // of repayments)
+                loan.updateTermFrequency(periods.size());
+
+                // update the status of the request
+                loanRescheduleRequest.approve(appUser, approvedOnDate);
+
+                // update the derived fields of each loan repayments schedule
+                // instalments
+                for (final LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
+                    repaymentScheduleInstallment.updateDerivedFields(currency, new LocalDate());
+                }
+
+                // updates maturity date
+                loan.updateLoanScheduleDependentDerivedFields();
+                // update the loan object
+                this.loanRepository.save(loan);
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
+                    .withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).build();
+        }
+
+        catch (final DataIntegrityViolationException dve) {
+            // handle the data integrity violation
+            handleDataIntegrityViolation(dve);
+
+            // return an empty command processing result object
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    /**
+     * waive all charges in the collection
+     * 
+     * @param loan
+     *            Loan object
+     * @param loanCharges
+     *            collection of LoanCharge objects
+     * @return void
+     **/
+    private void waiveLoanCharges(Loan loan, Collection<LoanCharge> loanCharges) {
+        AppUser currentUser = this.platformSecurityContext.authenticatedUser();
+        this.loanAssembler.setHelpers(loan);
+
+        for (LoanCharge loanCharge : loanCharges) {
+
+            if (loanCharge.isChargePending()) {
+                Integer loanInstallmentNumber = null;
+
+                if (loanCharge.isInstalmentFee()) {
+                    LoanInstallmentCharge chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
+
+                    if (chargePerInstallment != null) {
+                        loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
+                    }
+                }
+
+                final Map<String, Object> changes = new LinkedHashMap<>(3);
+
+                final List<Long> existingTransactionIds = new ArrayList<>();
+                final List<Long> existingReversedTransactionIds = new ArrayList<>();
+                LocalDate recalculateFrom = null;
+                if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                    recalculateFrom = DateUtils.getLocalDateOfTenant();
+                }
+
+                ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+
+                Money accruedCharge = Money.zero(loan.getCurrency());
+                if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
+                    Collection<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy(
+                            loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber);
+                    for (LoanChargePaidByData chargePaidByData : chargePaidByDatas) {
+                        accruedCharge = accruedCharge.plus(chargePaidByData.getAmount());
+                    }
+                }
+
+                final LoanTransaction loanTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
+                        existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge,
+                        currentUser);
+
+                this.loanTransactionRepository.save(loanTransaction);
+
+                postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+            }
+        }
+    }
+
+    private void postJournalEntries(Loan loan, List<Long> existingTransactionIds, List<Long> existingReversedTransactionIds) {
+        final MonetaryCurrency currency = loan.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+        boolean isAccountTransfer = false;
+        final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
+                existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
+    }
+
+    private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
+        final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
+        return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult reject(JsonCommand jsonCommand) {
+
+        try {
+            final Long loanRescheduleRequestId = jsonCommand.entityId();
+
+            final LoanRescheduleRequest loanRescheduleRequest = loanRescheduleRequestRepository.findOne(loanRescheduleRequestId);
+
+            if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(loanRescheduleRequestId); }
+
+            // validate the request in the JsonCommand object passed as
+            // parameter
+            this.loanRescheduleRequestDataValidator.validateForRejectAction(jsonCommand, loanRescheduleRequest);
+
+            final AppUser appUser = this.platformSecurityContext.authenticatedUser();
+            final Map<String, Object> changes = new LinkedHashMap<>();
+
+            LocalDate rejectedOnDate = jsonCommand.localDateValueOfParameterNamed("rejectedOnDate");
+            final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale(
+                    jsonCommand.extractLocale());
+
+            changes.put("locale", jsonCommand.locale());
+            changes.put("dateFormat", jsonCommand.dateFormat());
+            changes.put("rejectedOnDate", rejectedOnDate.toString(dateTimeFormatter));
+            changes.put("rejectedByUserId", appUser.getId());
+
+            if (!changes.isEmpty()) {
+                loanRescheduleRequest.reject(appUser, rejectedOnDate);
+            }
+
+            return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
+                    .withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).build();
+        }
+
+        catch (final DataIntegrityViolationException dve) {
+            // handle the data integrity violation
+            handleDataIntegrityViolation(dve);
+
+            // return an empty command processing result object
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    /**
+     * handles the data integrity violation exception for loan reschedule write
+     * services
+     *
+     * @param dve
+     *            data integrity violation exception
+     * @return void
+     **/
+    private void handleDataIntegrityViolation(final DataIntegrityViolationException dve) {
+
+        logger.error(dve.getMessage(), dve);
+
+        throw new PlatformDataIntegrityException("error.msg.loan.reschedule.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
+        FloatingRateDTO floatingRateDTO = null;
+        if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
+            boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
+            BigDecimal interestRateDiff = loan.getInterestRateDifferential();
+            List<FloatingRatePeriodData> baseLendingRatePeriods = null;
+            try {
+                baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
+            } catch (final FloatingRateNotFoundException ex) {
+                // Do not do anything
+            }
+            floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
+                    baseLendingRatePeriods);
+        }
+        return floatingRateDTO;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
new file mode 100644
index 0000000..f0f9a75
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
@@ -0,0 +1,156 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.serialization;
+
+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.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.joda.time.LocalDate;
+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 CalculateLoanScheduleQueryFromApiJsonHelper {
+
+    /**
+     * The parameters supported for this command.
+     */
+    final Set<String> supportedParameters = new HashSet<>(Arrays.asList("id", "clientId", "groupId", "loanType", "calendarId", "productId",
+            "accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId", "principal",
+            "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency",
+            "loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType",
+            LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "interestRateFrequencyType", "expectedDisbursementDate",
+            "repaymentsStartingFromDate", "graceOnPrincipalPayment", "graceOnInterestPayment", "graceOnInterestCharged",
+            "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", "charges", "collateral",
+            "syncDisbursementWithMeeting", "linkAccountId", LoanApiConstants.disbursementDataParameterName,
+            LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName,
+            LoanProductConstants.graceOnArrearsAgeingParameterName, LoanProductConstants.recalculationRestFrequencyDateParamName,
+            "createStandingInstructionAtDisbursement", LoanApiConstants.isFloatingInterestRateParameterName,
+            LoanApiConstants.interestRateDifferentialParameterName));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public CalculateLoanScheduleQueryFromApiJsonHelper(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validate(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 JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String loanTermFrequencyParameterName = "loanTermFrequency";
+        final Integer loanTermFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyParameterName, element);
+
+        final String loanTermFrequencyTypeParameterName = "loanTermFrequencyType";
+        final Integer loanTermFrequencyType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyTypeParameterName,
+                element);
+
+        final String numberOfRepaymentsParameterName = "numberOfRepayments";
+        final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName, element);
+
+        final String repaymentEveryParameterName = "repaymentEvery";
+        final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryParameterName, element);
+
+        final String repaymentEveryFrequencyTypeParameterName = "repaymentFrequencyType";
+        final Integer repaymentEveryType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryFrequencyTypeParameterName,
+                element);
+
+        // FIXME - KW - this constraint doesnt really need to be here. should be
+        // possible to express loan term as say 12 months whilst also saying
+        // - that the repayment structure is 6 repayments every bi-monthly.
+        validateSelectedPeriodFrequencyTypeIsTheSame(dataValidationErrors, loanTermFrequency, loanTermFrequencyType, numberOfRepayments,
+                repaymentEvery, repaymentEveryType);
+
+        final String expectedDisbursementDateParameterName = "expectedDisbursementDate";
+        final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName,
+                element);
+
+        LocalDate repaymentsStartingFromDate = null;
+        final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate";
+        if (this.fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) {
+            repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed(repaymentsStartingFromDateParameterName, element);
+        }
+
+        validateRepaymentsStartingFromDateIsAfterDisbursementDate(dataValidationErrors, expectedDisbursementDate,
+                repaymentsStartingFromDate);
+
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateSelectedPeriodFrequencyTypeIsTheSame(final List<ApiParameterError> dataValidationErrors,
+            final Integer loanTermFrequency, final Integer loanTermFrequencyType, final Integer numberOfRepayments,
+            final Integer repaymentEvery, final Integer repaymentEveryType) {
+        if (loanTermFrequencyType != null && !loanTermFrequencyType.equals(repaymentEveryType)) {
+            final ApiParameterError error = ApiParameterError.parameterError(
+                    "validation.msg.loan.loanTermFrequencyType.not.the.same.as.repaymentFrequencyType",
+                    "The parameters loanTermFrequencyType and repaymentFrequencyType must be the same.", "loanTermFrequencyType",
+                    loanTermFrequencyType, repaymentEveryType);
+            dataValidationErrors.add(error);
+        } else {
+            if (loanTermFrequency != null && repaymentEvery != null && numberOfRepayments != null) {
+                final int suggestsedLoanTerm = repaymentEvery * numberOfRepayments;
+                if (loanTermFrequency.intValue() < suggestsedLoanTerm) {
+                    final ApiParameterError error = ApiParameterError
+                            .parameterError(
+                                    "validation.msg.loan.loanTermFrequency.less.than.repayment.structure.suggests",
+                                    "The parameter loanTermFrequency is less than the suggest loan term as indicated by numberOfRepayments and repaymentEvery.",
+                                    "loanTermFrequency", loanTermFrequency, numberOfRepayments, repaymentEvery);
+                    dataValidationErrors.add(error);
+                }
+            }
+        }
+    }
+
+    private void validateRepaymentsStartingFromDateIsAfterDisbursementDate(final List<ApiParameterError> dataValidationErrors,
+            final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate) {
+        if (expectedDisbursementDate != null) {
+            if (repaymentsStartingFromDate != null && expectedDisbursementDate.isAfter(repaymentsStartingFromDate)) {
+                final ApiParameterError error = ApiParameterError.parameterError(
+                        "validation.msg.loan.expectedDisbursementDate.cannot.be.after.first.repayment.date",
+                        "The parameter expectedDisbursementDate has a date which falls after the date for repaymentsStartingFromDate.",
+                        "expectedDisbursementDate", expectedDisbursementDate, repaymentsStartingFromDate);
+                dataValidationErrors.add(error);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
new file mode 100644
index 0000000..c55c16a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
@@ -0,0 +1,1215 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+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.exception.UnsupportedParameterException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public final class LoanApplicationCommandFromApiJsonHelper {
+
+    /**
+     * The parameters supported for this command.
+     */
+    final Set<String> supportedParameters = new HashSet<>(Arrays.asList("dateFormat", "locale", "id", "clientId", "groupId", "loanType",
+            "productId", "principal", "loanTermFrequency", "loanTermFrequencyType", "numberOfRepayments", "repaymentEvery",
+            "repaymentFrequencyType", "repaymentFrequencyNthDayType", "repaymentFrequencyDayOfWeekType", "interestRatePerPeriod",
+            "amortizationType", "interestType", LoanApiConstants.isFloatingInterestRate, LoanApiConstants.interestRateDifferential,
+            "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName,
+            "interestRateFrequencyType", "expectedDisbursementDate", "repaymentsStartingFromDate",
+            "graceOnPrincipalPayment",
+            "graceOnInterestPayment",
+            "graceOnInterestCharged",
+            "interestChargedFromDate",
+            "submittedOnDate",
+            "submittedOnNote",
+            "accountNo",
+            "externalId",
+            "fundId",
+            "loanOfficerId", // optional
+            "loanPurposeId",
+            "inArrearsTolerance",
+            "charges",
+            "collateral", // optional
+            "transactionProcessingStrategyId", // settings
+            "calendarId", // optional
+            "syncDisbursementWithMeeting",// optional
+            "linkAccountId", LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName,
+            LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName,
+            LoanProductConstants.recalculationRestFrequencyDateParamName,
+            LoanProductConstants.recalculationCompoundingFrequencyDateParamName, "createStandingInstructionAtDisbursement"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper;
+
+    @Autowired
+    public LoanApplicationCommandFromApiJsonHelper(final FromJsonHelper fromApiJsonHelper,
+            final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.apiJsonHelper = apiJsonHelper;
+    }
+
+    public void validateForCreate(final String json, final boolean isMeetingMandatoryForJLGLoans, final LoanProduct loanProduct) {
+        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("loan");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String loanTypeParameterName = "loanType";
+        final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
+        baseDataValidator.reset().parameter(loanTypeParameterName).value(loanTypeStr).notNull();
+
+        if (!StringUtils.isBlank(loanTypeStr)) {
+            final AccountType loanType = AccountType.fromName(loanTypeStr);
+            baseDataValidator.reset().parameter(loanTypeParameterName).value(loanType.getValue()).inMinMaxRange(1, 3);
+
+            final Long clientId = this.fromApiJsonHelper.extractLongNamed("clientId", element);
+            final Long groupId = this.fromApiJsonHelper.extractLongNamed("groupId", element);
+            if (loanType.isIndividualAccount()) {
+                baseDataValidator.reset().parameter("clientId").value(clientId).notNull().longGreaterThanZero();
+                baseDataValidator.reset().parameter("groupId").value(groupId).mustBeBlankWhenParameterProvided("clientId", clientId);
+            }
+
+            if (loanType.isGroupAccount()) {
+                baseDataValidator.reset().parameter("groupId").value(groupId).notNull().longGreaterThanZero();
+                baseDataValidator.reset().parameter("clientId").value(clientId).mustBeBlankWhenParameterProvided("groupId", groupId);
+            }
+
+            if (loanType.isJLGAccount()) {
+                baseDataValidator.reset().parameter("clientId").value(clientId).notNull().integerGreaterThanZero();
+                baseDataValidator.reset().parameter("groupId").value(groupId).notNull().longGreaterThanZero();
+
+                // if it is JLG loan that must have meeting details
+                if (isMeetingMandatoryForJLGLoans) {
+                    final String calendarIdParameterName = "calendarId";
+                    final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element);
+                    baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).notNull().integerGreaterThanZero();
+
+                    // if it is JLG loan then must have a value for
+                    // syncDisbursement passed in
+                    String syncDisbursementParameterName = "syncDisbursementWithMeeting";
+                    final Boolean syncDisbursement = this.fromApiJsonHelper.extractBooleanNamed(syncDisbursementParameterName, element);
+
+                    if (syncDisbursement == null) {
+                        baseDataValidator.reset().parameter(syncDisbursementParameterName).value(syncDisbursement)
+                                .trueOrFalseRequired(false);
+                    }
+                }
+
+            }
+
+        }
+
+        final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element);
+        baseDataValidator.reset().parameter("productId").value(productId).notNull().integerGreaterThanZero();
+
+        final String accountNoParameterName = "accountNo";
+        if (this.fromApiJsonHelper.parameterExists(accountNoParameterName, element)) {
+            final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParameterName, element);
+            baseDataValidator.reset().parameter(accountNoParameterName).value(accountNo).ignoreIfNull().notExceedingLengthOf(20);
+        }
+
+        final String externalIdParameterName = "externalId";
+        if (this.fromApiJsonHelper.parameterExists(externalIdParameterName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParameterName, element);
+            baseDataValidator.reset().parameter(externalIdParameterName).value(externalId).ignoreIfNull().notExceedingLengthOf(100);
+        }
+
+        final String fundIdParameterName = "fundId";
+        if (this.fromApiJsonHelper.parameterExists(fundIdParameterName, element)) {
+            final Long fundId = this.fromApiJsonHelper.extractLongNamed(fundIdParameterName, element);
+            baseDataValidator.reset().parameter(fundIdParameterName).value(fundId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        final String loanOfficerIdParameterName = "loanOfficerId";
+        if (this.fromApiJsonHelper.parameterExists(loanOfficerIdParameterName, element)) {
+            final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed(loanOfficerIdParameterName, element);
+            baseDataValidator.reset().parameter(loanOfficerIdParameterName).value(loanOfficerId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        final String loanPurposeIdParameterName = "loanPurposeId";
+        if (this.fromApiJsonHelper.parameterExists(loanPurposeIdParameterName, element)) {
+            final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed(loanPurposeIdParameterName, element);
+            baseDataValidator.reset().parameter(loanPurposeIdParameterName).value(loanPurposeId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
+        baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount();
+
+        final String loanTermFrequencyParameterName = "loanTermFrequency";
+        final Integer loanTermFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyParameterName, element);
+        baseDataValidator.reset().parameter(loanTermFrequencyParameterName).value(loanTermFrequency).notNull().integerGreaterThanZero();
+
+        final String loanTermFrequencyTypeParameterName = "loanTermFrequencyType";
+        final Integer loanTermFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(loanTermFrequencyTypeParameterName,
+                element);
+        baseDataValidator.reset().parameter(loanTermFrequencyTypeParameterName).value(loanTermFrequencyType).notNull().inMinMaxRange(0, 3);
+
+        final String numberOfRepaymentsParameterName = "numberOfRepayments";
+        final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName, element);
+        baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments).notNull().integerGreaterThanZero();
+
+        final String repaymentEveryParameterName = "repaymentEvery";
+        final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryParameterName, element);
+        baseDataValidator.reset().parameter(repaymentEveryParameterName).value(repaymentEvery).notNull().integerGreaterThanZero();
+
+        final String repaymentEveryFrequencyTypeParameterName = "repaymentFrequencyType";
+        final Integer repaymentEveryType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(repaymentEveryFrequencyTypeParameterName,
+                element);
+        baseDataValidator.reset().parameter(repaymentEveryFrequencyTypeParameterName).value(repaymentEveryType).notNull()
+                .inMinMaxRange(0, 3);
+
+        final String interestTypeParameterName = "interestType";
+        final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestTypeParameterName, element);
+        baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1);
+
+        final String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType";
+        final Integer interestCalculationPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                interestCalculationPeriodTypeParameterName, element);
+        baseDataValidator.reset().parameter(interestCalculationPeriodTypeParameterName).value(interestCalculationPeriodType).notNull()
+                .inMinMaxRange(0, 1);
+
+        if (loanProduct.isLinkedToFloatingInterestRate()) {
+            if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("interestRatePerPeriod")
+                        .failWithCode("not.supported.loanproduct.linked.to.floating.rate",
+                                "interestRatePerPeriod param is not supported, selected Loan Product is linked with floating interest rate.");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
+                final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate,
+                        element);
+                if (isFloatingInterestRate != null && isFloatingInterestRate
+                        && !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()) {
+                    baseDataValidator
+                            .reset()
+                            .parameter(LoanApiConstants.isFloatingInterestRate)
+                            .failWithCode("true.not.supported.for.selected.loanproduct",
+                                    "isFloatingInterestRate value of true not supported for selected Loan Product.");
+                }
+            } else {
+                baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false);
+            }
+
+            if (interestType != null && interestType.equals(InterestMethod.FLAT.getValue())) {
+                baseDataValidator
+                        .reset()
+                        .parameter(interestTypeParameterName)
+                        .failWithCode("should.be.0.for.selected.loan.product",
+                                "interestType should be DECLINING_BALANCE for selected Loan Product as it is linked to floating rates.");
+            }
+
+            final String interestRateDifferentialParameterName = LoanApiConstants.interestRateDifferential;
+            final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    interestRateDifferentialParameterName, element);
+            baseDataValidator
+                    .reset()
+                    .parameter(interestRateDifferentialParameterName)
+                    .value(interestRateDifferential)
+                    .notNull()
+                    .zeroOrPositiveAmount()
+                    .inMinAndMaxAmountRange(loanProduct.getFloatingRates().getMinDifferentialLendingRate(),
+                            loanProduct.getFloatingRates().getMaxDifferentialLendingRate());
+
+        } else {
+
+            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter(LoanApiConstants.isFloatingInterestRate)
+                        .failWithCode("not.supported.loanproduct.not.linked.to.floating.rate",
+                                "isFloatingInterestRate param is not supported, selected Loan Product is not linked with floating interest rate.");
+            }
+            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRateDifferential, element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter(LoanApiConstants.interestRateDifferential)
+                        .failWithCode("not.supported.loanproduct.not.linked.to.floating.rate",
+                                "interestRateDifferential param is not supported, selected Loan Product is not linked with floating interest rate.");
+            }
+
+            final String interestRatePerPeriodParameterName = "interestRatePerPeriod";
+            final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    interestRatePerPeriodParameterName, element);
+            baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull()
+                    .zeroOrPositiveAmount();
+
+        }
+
+        final String amortizationTypeParameterName = "amortizationType";
+        final Integer amortizationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(amortizationTypeParameterName, element);
+        baseDataValidator.reset().parameter(amortizationTypeParameterName).value(amortizationType).notNull().inMinMaxRange(0, 1);
+
+        final String expectedDisbursementDateParameterName = "expectedDisbursementDate";
+        final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName,
+                element);
+        baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDate).notNull();
+
+        // grace validation
+        final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
+        baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment).zeroOrPositiveAmount();
+
+        final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
+        baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment).zeroOrPositiveAmount();
+
+        final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
+        baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged).zeroOrPositiveAmount();
+
+        final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                LoanProductConstants.graceOnArrearsAgeingParameterName, element);
+        baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing)
+                .zeroOrPositiveAmount();
+
+        final String interestChargedFromDateParameterName = "interestChargedFromDate";
+        if (this.fromApiJsonHelper.parameterExists(interestChargedFromDateParameterName, element)) {
+            final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed(interestChargedFromDateParameterName,
+                    element);
+            baseDataValidator.reset().parameter(interestChargedFromDateParameterName).value(interestChargedFromDate).ignoreIfNull()
+                    .notNull();
+        }
+
+        final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate";
+        if (this.fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) {
+            final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                    repaymentsStartingFromDateParameterName, element);
+            baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull()
+                    .notNull();
+        }
+
+        final String inArrearsToleranceParameterName = "inArrearsTolerance";
+        if (this.fromApiJsonHelper.parameterExists(inArrearsToleranceParameterName, element)) {
+            final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(inArrearsToleranceParameterName,
+                    element);
+            baseDataValidator.reset().parameter(inArrearsToleranceParameterName).value(inArrearsTolerance).ignoreIfNull()
+                    .zeroOrPositiveAmount();
+        }
+
+        final String submittedOnDateParameterName = "submittedOnDate";
+        final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParameterName, element);
+        if (submittedOnDate == null) {
+            baseDataValidator.reset().parameter(submittedOnDateParameterName).value(submittedOnDate).notNull();
+        }
+
+        final String submittedOnNoteParameterName = "submittedOnNote";
+        if (this.fromApiJsonHelper.parameterExists(submittedOnNoteParameterName, element)) {
+            final String submittedOnNote = this.fromApiJsonHelper.extractStringNamed(submittedOnNoteParameterName, element);
+            baseDataValidator.reset().parameter(submittedOnNoteParameterName).value(submittedOnNote).ignoreIfNull()
+                    .notExceedingLengthOf(500);
+        }
+
+        final String transactionProcessingStrategyIdParameterName = "transactionProcessingStrategyId";
+        final Long transactionProcessingStrategyId = this.fromApiJsonHelper.extractLongNamed(transactionProcessingStrategyIdParameterName,
+                element);
+        baseDataValidator.reset().parameter(transactionProcessingStrategyIdParameterName).value(transactionProcessingStrategyId).notNull()
+                .integerGreaterThanZero();
+
+        final String linkAccountIdParameterName = "linkAccountId";
+        if (this.fromApiJsonHelper.parameterExists(linkAccountIdParameterName, element)) {
+            final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(linkAccountIdParameterName, element);
+            baseDataValidator.reset().parameter(linkAccountIdParameterName).value(linkAccountId).ignoreIfNull().longGreaterThanZero();
+        }
+
+        final String createSiAtDisbursementParameterName = "createStandingInstructionAtDisbursement";
+        if (this.fromApiJsonHelper.parameterExists(createSiAtDisbursementParameterName, element)) {
+            final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper.extractBooleanNamed(
+                    createSiAtDisbursementParameterName, element);
+            final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(linkAccountIdParameterName, element);
+
+            if (createStandingInstructionAtDisbursement) {
+                baseDataValidator.reset().parameter(linkAccountIdParameterName).value(linkAccountId).notNull().longGreaterThanZero();
+            }
+        }
+
+        // charges
+        final String chargesParameterName = "charges";
+        if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(chargesParameterName, element)) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+
+            if (topLevelJsonElement.get(chargesParameterName).isJsonArray()) {
+                final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+                final Set<String> supportedParameters = new HashSet<>(Arrays.asList("id", "chargeId", "amount", "chargeTimeType",
+                        "chargeCalculationType", "dueDate"));
+
+                final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
+                for (int i = 1; i <= array.size(); i++) {
+
+                    final JsonObject loanChargeElement = array.get(i - 1).getAsJsonObject();
+                    final String arrayObjectJson = this.fromApiJsonHelper.toJson(loanChargeElement);
+                    this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap, arrayObjectJson,
+                            supportedParameters);
+
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement);
+                    baseDataValidator.reset().parameter("charges").parameterAtIndexArray("chargeId", i).value(chargeId).notNull()
+                            .integerGreaterThanZero();
+
+                    final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale);
+                    baseDataValidator.reset().parameter("charges").parameterAtIndexArray("amount", i).value(amount).notNull()
+                            .positiveAmount();
+
+                    this.fromApiJsonHelper.extractLocalDateNamed("dueDate", loanChargeElement, dateFormat, locale);
+                }
+            }
+        }
+
+        // collateral
+        final String collateralParameterName = "collateral";
+        if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(collateralParameterName, element)) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            if (topLevelJsonElement.get("collateral").isJsonArray()) {
+
+                final Type collateralParameterTypeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+                final Set<String> supportedParameters = new HashSet<>(Arrays.asList("id", "type", "value", "description"));
+                final JsonArray array = topLevelJsonElement.get("collateral").getAsJsonArray();
+                for (int i = 1; i <= array.size(); i++) {
+                    final JsonObject collateralItemElement = array.get(i - 1).getAsJsonObject();
+
+                    final String collateralJson = this.fromApiJsonHelper.toJson(collateralItemElement);
+                    this.fromApiJsonHelper.checkForUnsupportedParameters(collateralParameterTypeOfMap, collateralJson, supportedParameters);
+
+                    final Long collateralTypeId = this.fromApiJsonHelper.extractLongNamed("type", collateralItemElement);
+                    baseDataValidator.reset().parameter("collateral").parameterAtIndexArray("type", i).value(collateralTypeId).notNull()
+                            .integerGreaterThanZero();
+
+                    final BigDecimal collateralValue = this.fromApiJsonHelper
+                            .extractBigDecimalNamed("value", collateralItemElement, locale);
+                    baseDataValidator.reset().parameter("collateral").parameterAtIndexArray("value", i).value(collateralValue)
+                            .ignoreIfNull().positiveAmount();
+
+                    final String description = this.fromApiJsonHelper.extractStringNamed("description", collateralItemElement);
+                    baseDataValidator.reset().parameter("collateral").parameterAtIndexArray("description", i).value(description).notBlank()
+                            .notExceedingLengthOf(500);
+
+                }
+            } else {
+                baseDataValidator.reset().parameter(collateralParameterName).expectedArrayButIsNot();
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.emiAmountParameterName, element)) {
+            if (!(loanProduct.canDefineInstallmentAmount() || loanProduct.isMultiDisburseLoan())) {
+                List<String> unsupportedParameterList = new ArrayList<>();
+                unsupportedParameterList.add(LoanApiConstants.emiAmountParameterName);
+                throw new UnsupportedParameterException(unsupportedParameterList);
+            }
+            final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
+                    element);
+            baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAnount).ignoreIfNull().positiveAmount();
+        }
+        if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.maxOutstandingBalanceParameterName, element)) {
+            final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    LoanApiConstants.maxOutstandingBalanceParameterName, element);
+            baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance)
+                    .ignoreIfNull().positiveAmount();
+        }
+        validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
+        validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateForModify(final String json, final LoanProduct loanProduct, final Loan existingLoanApplication) {
+        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("loan");
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        boolean atLeastOneParameterPassedForUpdate = false;
+
+        final String clientIdParameterName = "clientId";
+        if (this.fromApiJsonHelper.parameterExists(clientIdParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParameterName, element);
+            baseDataValidator.reset().parameter(clientIdParameterName).value(clientId).notNull().integerGreaterThanZero();
+        }
+
+        final String groupIdParameterName = "groupId";
+        if (this.fromApiJsonHelper.parameterExists(groupIdParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Long groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParameterName, element);
+            baseDataValidator.reset().parameter(groupIdParameterName).value(groupId).notNull().integerGreaterThanZero();
+        }
+
+        final String productIdParameterName = "productId";
+        if (this.fromApiJsonHelper.parameterExists(productIdParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Long productId = this.fromApiJsonHelper.extractLongNamed(productIdParameterName, element);
+            baseDataValidator.reset().parameter(productIdParameterName).value(productId).notNull().integerGreaterThanZero();
+        }
+
+        final String accountNoParameterName = "accountNo";
+        if (this.fromApiJsonHelper.parameterExists(accountNoParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParameterName, element);
+            baseDataValidator.reset().parameter(accountNoParameterName).value(accountNo).notBlank().notExceedingLengthOf(20);
+        }
+
+        final String externalIdParameterName = "externalId";
+        if (this.fromApiJsonHelper.parameterExists(externalIdParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParameterName, element);
+            baseDataValidator.reset().parameter(externalIdParameterName).value(externalId).ignoreIfNull().notExceedingLengthOf(100);
+        }
+
+        final String fundIdParameterName = "fundId";
+        if (this.fromApiJsonHelper.parameterExists(fundIdParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Long fundId = this.fromApiJsonHelper.extractLongNamed(fundIdParameterName, element);
+            baseDataValidator.reset().parameter(fundIdParameterName).value(fundId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        final String loanOfficerIdParameterName = "loanOfficerId";
+        if (this.fromApiJsonHelper.parameterExists(loanOfficerIdParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed(loanOfficerIdParameterName, element);
+            baseDataValidator.reset().parameter(loanOfficerIdParameterName).value(loanOfficerId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        final String transactionProcessingStrategyIdParameterName = "transactionProcessingStrategyId";
+        if (this.fromApiJsonHelper.parameterExists(transactionProcessingStrategyIdParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Long transactionProcessingStrategyId = this.fromApiJsonHelper.extractLongNamed(
+                    transactionProcessingStrategyIdParameterName, element);
+            baseDataValidator.reset().parameter(transactionProcessingStrategyIdParameterName).value(transactionProcessingStrategyId)
+                    .notNull().integerGreaterThanZero();
+        }
+
+        final String principalParameterName = "principal";
+        BigDecimal principal = null;
+        if (this.fromApiJsonHelper.parameterExists(principalParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element);
+            baseDataValidator.reset().parameter(principalParameterName).value(principal).notNull().positiveAmount();
+        }
+
+        final String inArrearsToleranceParameterName = "inArrearsTolerance";
+        if (this.fromApiJsonHelper.parameterExists(inArrearsToleranceParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(inArrearsToleranceParameterName,
+                    element);
+            baseDataValidator.reset().parameter(inArrearsToleranceParameterName).value(inArrearsTolerance).ignoreIfNull()
+                    .zeroOrPositiveAmount();
+        }
+
+        final String loanTermFrequencyParameterName = "loanTermFrequency";
+        if (this.fromApiJsonHelper.parameterExists(loanTermFrequencyParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer loanTermFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyParameterName, element);
+            baseDataValidator.reset().parameter(loanTermFrequencyParameterName).value(loanTermFrequency).notNull().integerGreaterThanZero();
+        }
+
+        final String loanTermFrequencyTypeParameterName = "loanTermFrequencyType";
+        if (this.fromApiJsonHelper.parameterExists(loanTermFrequencyTypeParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer loanTermFrequencyType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyTypeParameterName,
+                    element);
+            baseDataValidator.reset().parameter(loanTermFrequencyTypeParameterName).value(loanTermFrequencyType).notNull()
+                    .inMinMaxRange(0, 3);
+        }
+
+        final String numberOfRepaymentsParameterName = "numberOfRepayments";
+        if (this.fromApiJsonHelper.parameterExists(numberOfRepaymentsParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName,
+                    element);
+            baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        final String repaymentEveryParameterName = "repaymentEvery";
+        if (this.fromApiJsonHelper.parameterExists(repaymentEveryParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryParameterName, element);
+            baseDataValidator.reset().parameter(repaymentEveryParameterName).value(repaymentEvery).notNull().integerGreaterThanZero();
+        }
+
+        final String repaymentEveryTypeParameterName = "repaymentFrequencyType";
+        if (this.fromApiJsonHelper.parameterExists(repaymentEveryTypeParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer repaymentEveryType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryTypeParameterName,
+                    element);
+            baseDataValidator.reset().parameter(repaymentEveryTypeParameterName).value(repaymentEveryType).notNull().inMinMaxRange(0, 3);
+        }
+
+        final String interestTypeParameterName = "interestType";
+        Integer interestType = null;
+        if (this.fromApiJsonHelper.parameterExists(interestTypeParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(interestTypeParameterName, element);
+            baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1);
+        }
+
+        if (loanProduct.isLinkedToFloatingInterestRate()) {
+            if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("interestRatePerPeriod")
+                        .failWithCode("not.supported.loanproduct.linked.to.floating.rate",
+                                "interestRatePerPeriod param is not supported, selected Loan Product is linked with floating interest rate.");
+            }
+
+            Boolean isFloatingInterestRate = existingLoanApplication.getIsFloatingInterestRate();
+            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
+                isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate, element);
+                atLeastOneParameterPassedForUpdate = true;
+            }
+            if (isFloatingInterestRate != null) {
+                if (isFloatingInterestRate && !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()) {
+                    baseDataValidator
+                            .reset()
+                            .parameter(LoanApiConstants.isFloatingInterestRate)
+                            .failWithCode("true.not.supported.for.selected.loanproduct",
+                                    "isFloatingInterestRate value of true not supported for selected Loan Product.");
+                }
+            } else {
+                baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false);
+            }
+
+            if (interestType == null) {
+                interestType = existingLoanApplication.getLoanProductRelatedDetail().getInterestMethod().getValue();
+            }
+            if (interestType != null && interestType.equals(InterestMethod.FLAT.getValue())) {
+                baseDataValidator
+                        .reset()
+                        .parameter(interestTypeParameterName)
+                        .failWithCode("should.be.0.for.selected.loan.product",
+                                "interestType should be DECLINING_BALANCE for selected Loan Product as it is linked to floating rates.");
+            }
+
+            final String interestRateDifferentialParameterName = LoanApiConstants.interestRateDifferential;
+            BigDecimal interestRateDifferential = existingLoanApplication.getInterestRateDifferential();
+            if (this.fromApiJsonHelper.parameterExists(interestRateDifferentialParameterName, element)) {
+                interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRateDifferentialParameterName,
+                        element);
+                atLeastOneParameterPassedForUpdate = true;
+            }
+            baseDataValidator
+                    .reset()
+                    .parameter(interestRateDifferentialParameterName)
+                    .value(interestRateDifferential)
+                    .notNull()
+                    .zeroOrPositiveAmount()
+                    .inMinAndMaxAmountRange(loanProduct.getFloatingRates().getMinDifferentialLendingRate(),
+                            loanProduct.getFloatingRates().getMaxDifferentialLendingRate());
+
+        } else {
+
+            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter(LoanApiConstants.isFloatingInterestRate)
+                        .failWithCode("not.supported.loanproduct.not.linked.to.floating.rate",
+                                "isFloatingInterestRate param is not supported, selected Loan Product is not linked with floating interest rate.");
+            }
+            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRateDifferential, element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter(LoanApiConstants.interestRateDifferential)
+                        .failWithCode("not.supported.loanproduct.not.linked.to.floating.rate",
+                                "interestRateDifferential param is not supported, selected Loan Product is not linked with floating interest rate.");
+            }
+
+            final String interestRatePerPeriodParameterName = "interestRatePerPeriod";
+            BigDecimal interestRatePerPeriod = existingLoanApplication.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
+            if (this.fromApiJsonHelper.parameterExists(interestRatePerPeriodParameterName, element)) {
+                this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRatePerPeriodParameterName, element);
+                atLeastOneParameterPassedForUpdate = true;
+            }
+            baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull()
+                    .zeroOrPositiveAmount();
+
+        }
+
+        Integer interestCalculationPeriodType = loanProduct.getLoanProductRelatedDetail().getInterestCalculationPeriodMethod().getValue();
+        final String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType";
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationPeriodTypeParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            interestCalculationPeriodType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                    interestCalculationPeriodTypeParameterName, element);
+            baseDataValidator.reset().parameter(interestCalculationPeriodTypeParameterName).value(interestCalculationPeriodType).notNull()
+                    .inMinMaxRange(0, 1);
+        }
+
+        final String amortizationTypeParameterName = "amortizationType";
+        if (this.fromApiJsonHelper.parameterExists(amortizationTypeParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Integer amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(amortizationTypeParameterName, element);
+            baseDataValidator.reset().parameter(amortizationTypeParameterName).value(amortizationType).notNull().inMinMaxRange(0, 1);
+        }
+
+        final String expectedDisbursementDateParameterName = "expectedDisbursementDate";
+        LocalDate expectedDisbursementDate = null;
+        if (this.fromApiJsonHelper.parameterExists(expectedDisbursementDateParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+
+            final String expectedDisbursementDateStr = this.fromApiJsonHelper.extractStringNamed(expectedDisbursementDateParameterName,
+                    element);
+            baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDateStr).notBlank();
+
+            expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName, element);
+            baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDate).notNull();
+        }
+
+        // grace validation
+        if (this.fromApiJsonHelper.parameterExists("graceOnPrincipalPayment", element)) {
+            final Integer graceOnPrincipalPayment = this.fromApiJsonHelper
+                    .extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
+            baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment).zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("graceOnInterestPayment", element)) {
+            final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
+            baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment).zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("graceOnInterestCharged", element)) {
+            final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
+            baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged).zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.graceOnArrearsAgeingParameterName, element)) {
+            final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                    LoanProductConstants.graceOnArrearsAgeingParameterName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing)
+                    .zeroOrPositiveAmount();
+        }
+
+        final String interestChargedFromDateParameterName = "interestChargedFromDate";
+        if (this.fromApiJsonHelper.parameterExists(interestChargedFromDateParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed(interestChargedFromDateParameterName,
+                    element);
+            baseDataValidator.reset().parameter(interestChargedFromDateParameterName).value(interestChargedFromDate).ignoreIfNull();
+        }
+
+        final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate";
+        if (this.fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                    repaymentsStartingFromDateParameterName, element);
+            baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull();
+            if (!existingLoanApplication.getLoanTermVariations().isEmpty()) {
+                baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate)
+                        .failWithCode("invalid.due.to.variable.installments");
+            }
+        }
+
+        final String submittedOnDateParameterName = "submittedOnDate";
+        if (this.fromApiJsonHelper.parameterExists(submittedOnDateParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParameterName, element);
+            baseDataValidator.reset().parameter(submittedOnDateParameterName).value(submittedOnDate).notNull();
+        }
+
+        final String submittedOnNoteParameterName = "submittedOnNote";
+        if (this.fromApiJsonHelper.parameterExists(submittedOnNoteParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final String submittedOnNote = this.fromApiJsonHelper.extractStringNamed(submittedOnNoteParameterName, element);
+            baseDataValidator.reset().parameter(submittedOnNoteParameterName).value(submittedOnNote).ignoreIfNull()
+                    .notExceedingLengthOf(500);
+        }
+
+        final String linkAccountIdParameterName = "linkAccountId";
+        if (this.fromApiJsonHelper.parameterExists(submittedOnNoteParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+            final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(linkAccountIdParameterName, element);
+            baseDataValidator.reset().parameter(linkAccountIdParameterName).value(linkAccountId).ignoreIfNull().longGreaterThanZero();
+        }
+
+        // charges
+        final String chargesParameterName = "charges";
+        if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(chargesParameterName, element)) {
+            atLeastOneParameterPassedForUpdate = true;
+
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+
+            if (topLevelJsonElement.get(chargesParameterName).isJsonArray()) {
+                final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+                final Set<String> supportedParameters = new HashSet<>(Arrays.asList("id", "chargeId", "amount", "chargeTimeType",
+                        "chargeCalculationType", "dueDate"));
+
+                final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
+                for (int i = 1; i <= array.size(); i++) {
+
+                    final JsonObject loanChargeElement = array.get(i - 1).getAsJsonObject();
+                    final String arrayObjectJson = this.fromApiJsonHelper.toJson(loanChargeElement);
+                    this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap, arrayObjectJson,
+                            supportedParameters);
+
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement);
+                    baseDataValidator.reset().parameter("charges").parameterAtIndexArray("chargeId", i).value(chargeId).notNull()
+                            .integerGreaterThanZero();
+
+                    final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale);
+                    baseDataValidator.reset().parameter("charges").parameterAtIndexArray("amount", i).value(amount).notNull()
+                            .positiveAmount();
+
+                    this.fromApiJsonHelper.extractLocalDateNamed("dueDate", loanChargeElement, dateFormat, locale);
+                }
+            }
+        }
+
+        // collateral
+        final String collateralParameterName = "collateral";
+        if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(collateralParameterName, element)) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            if (topLevelJsonElement.get("collateral").isJsonArray()) {
+
+                final Type collateralParameterTypeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+                final Set<String> supportedParameters = new HashSet<>(Arrays.asList("id", "type", "value", "description"));
+                final JsonArray array = topLevelJsonElement.get("collateral").getAsJsonArray();
+                for (int i = 1; i <= array.size(); i++) {
+                    final JsonObject collateralItemElement = array.get(i - 1).getAsJsonObject();
+
+                    final String collateralJson = this.fromApiJsonHelper.toJson(collateralItemElement);
+                    this.fromApiJsonHelper.checkForUnsupportedParameters(collateralParameterTypeOfMap, collateralJson, supportedParameters);
+
+                    final Long collateralTypeId = this.fromApiJsonHelper.extractLongNamed("type", collateralItemElement);
+                    baseDataValidator.reset().parameter("collateral").parameterAtIndexArray("type", i).value(collateralTypeId).notNull()
+                            .integerGreaterThanZero();
+
+                    final BigDecimal collateralValue = this.fromApiJsonHelper
+                            .extractBigDecimalNamed("value", collateralItemElement, locale);
+                    baseDataValidator.reset().parameter("collateral").parameterAtIndexArray("value", i).value(collateralValue)
+                            .ignoreIfNull().positiveAmount();
+
+                    final String description = this.fromApiJsonHelper.extractStringNamed("description", collateralItemElement);
+                    baseDataValidator.reset().parameter("collateral").parameterAtIndexArray("description", i).value(description).notBlank()
+                            .notExceedingLengthOf(500);
+
+                }
+            } else {
+                baseDataValidator.reset().parameter(collateralParameterName).expectedArrayButIsNot();
+            }
+        }
+
+        boolean meetingIdRequired = false;
+        // validate syncDisbursement
+        final String syncDisbursementParameterName = "syncDisbursementWithMeeting";
+        if (this.fromApiJsonHelper.parameterExists(syncDisbursementParameterName, element)) {
+            final Boolean syncDisbursement = this.fromApiJsonHelper.extractBooleanNamed(syncDisbursementParameterName, element);
+            if (syncDisbursement == null) {
+                baseDataValidator.reset().parameter(syncDisbursementParameterName).value(syncDisbursement).trueOrFalseRequired(false);
+            } else if (syncDisbursement.booleanValue()) {
+                meetingIdRequired = true;
+            }
+        }
+
+        final String calendarIdParameterName = "calendarId";
+        // if disbursement is synced then must have a meeting (calendar)
+        if (meetingIdRequired || this.fromApiJsonHelper.parameterExists(calendarIdParameterName, element)) {
+            final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element);
+            baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).notNull().integerGreaterThanZero();
+        }
+
+        if (!atLeastOneParameterPassedForUpdate) {
+            final Object forceError = null;
+            baseDataValidator.reset().anyOfNotNull(forceError);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.emiAmountParameterName, element)) {
+            if (!(loanProduct.canDefineInstallmentAmount() || loanProduct.isMultiDisburseLoan())) {
+                List<String> unsupportedParameterList = new ArrayList<>();
+                unsupportedParameterList.add(LoanApiConstants.emiAmountParameterName);
+                throw new UnsupportedParameterException(unsupportedParameterList);
+            }
+            final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
+                    element);
+            baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAnount).ignoreIfNull().positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.maxOutstandingBalanceParameterName, element)) {
+            final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    LoanApiConstants.maxOutstandingBalanceParameterName, element);
+            baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance)
+                    .ignoreIfNull().positiveAmount();
+        }
+        validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
+        validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateForUndo(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> undoSupportedParameters = new HashSet<>(Arrays.asList("note"));
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanapplication.undo");
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String note = "note";
+        if (this.fromApiJsonHelper.parameterExists(note, element)) {
+            final String noteText = this.fromApiJsonHelper.extractStringNamed(note, element);
+            baseDataValidator.reset().parameter(note).value(noteText).notExceedingLengthOf(1000);
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+
+        final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount();
+        final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount();
+        final String principalParameterName = "principal";
+
+        if (this.fromApiJsonHelper.parameterExists(principalParameterName, element)) {
+            final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element);
+            baseDataValidator.reset().parameter(principalParameterName).value(principal).notNull().positiveAmount()
+                    .inMinAndMaxAmountRange(minPrincipal, maxPrincipal);
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateLoanTermAndRepaidEveryValues(final Integer loanTermFrequency, final Integer loanTermFrequencyType,
+            final Integer numberOfRepayments, final Integer repaymentEvery, final Integer repaymentEveryType, final Loan loan) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        this.apiJsonHelper.validateSelectedPeriodFrequencyTypeIsTheSame(dataValidationErrors, loanTermFrequency, loanTermFrequencyType,
+                numberOfRepayments, repaymentEvery, repaymentEveryType);
+
+        /**
+         * For multi-disbursal loans where schedules are auto-generated based on
+         * a fixed EMI, ensure the number of repayments is within the
+         * permissible range defined by the loan product
+         **/
+        if (loan.getFixedEmiAmount() != null) {
+            Integer minimumNoOfRepayments = loan.loanProduct().getMinNumberOfRepayments();
+            Integer maximumNoOfRepayments = loan.loanProduct().getMaxNumberOfRepayments();
+            Integer actualNumberOfRepayments = loan.getRepaymentScheduleInstallments().size();
+            // validate actual number of repayments is > minimum number of
+            // repayments
+            if (minimumNoOfRepayments != null && minimumNoOfRepayments != 0 && actualNumberOfRepayments < minimumNoOfRepayments) {
+                final ApiParameterError error = ApiParameterError.generalError(
+                        "validation.msg.loan.numberOfRepayments.lesser.than.minimumNumberOfRepayments",
+                        "The total number of calculated repayments for this loan " + actualNumberOfRepayments
+                                + " is lesser than the allowed minimum of " + minimumNoOfRepayments, actualNumberOfRepayments,
+                        minimumNoOfRepayments);
+                dataValidationErrors.add(error);
+            }
+
+            // validate actual number of repayments is < maximum number of
+            // repayments
+            if (maximumNoOfRepayments != null && maximumNoOfRepayments != 0 && actualNumberOfRepayments > maximumNoOfRepayments) {
+                final ApiParameterError error = ApiParameterError.generalError(
+                        "validation.msg.loan.numberOfRepayments.greater.than.maximumNumberOfRepayments",
+                        "The total number of calculated repayments for this loan " + actualNumberOfRepayments
+                                + " is greater than the allowed maximum of " + maximumNoOfRepayments, actualNumberOfRepayments,
+                        maximumNoOfRepayments);
+                dataValidationErrors.add(error);
+            }
+
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validatelinkedSavingsAccount(final SavingsAccount savingsAccount, final Loan loanApplication) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        if (savingsAccount.isNotActive()) {
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.linked.savings.account.is.not.active",
+                    "Linked Savings account with id:" + savingsAccount.getId() + " is not in active state", "linkAccountId",
+                    savingsAccount.getId());
+            dataValidationErrors.add(error);
+        } else if (loanApplication.getClientId() != savingsAccount.clientId()) {
+            final ApiParameterError error = ApiParameterError.parameterError(
+                    "validation.msg.loan.linked.savings.account.not.belongs.to.same.client", "Linked Savings account with id:"
+                            + savingsAccount.getId() + " is not belongs to the same client", "linkAccountId", savingsAccount.getId());
+            dataValidationErrors.add(error);
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    private void validateDisbursementsAreDatewiseOrdered(JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+        final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
+        final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName,
+                element);
+        if (variationArray != null) {
+            for (int i = 0; i < variationArray.size(); i++) {
+                final JsonObject jsonObject1 = variationArray.get(i).getAsJsonObject();
+                if (jsonObject1.has(LoanApiConstants.disbursementDateParameterName)) {
+                    LocalDate date1 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName,
+                            jsonObject1, dateFormat, locale);
+
+                    for (int j = i + 1; j < variationArray.size(); j++) {
+                        final JsonObject jsonObject2 = variationArray.get(j).getAsJsonObject();
+                        if (jsonObject2.has(LoanApiConstants.disbursementDateParameterName)) {
+                            LocalDate date2 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName,
+                                    jsonObject2, dateFormat, locale);
+                            if (date1.isAfter(date2)) {
+                                baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName)
+                                        .failWithCode(LoanApiConstants.DISBURSEMENT_DATES_NOT_IN_ORDER);
+                            }
+                        }
+                    }
+                }
+
+            }
+        }
+    }
+
+    public void validateLoanMultiDisbursementdate(final JsonElement element, final DataValidatorBuilder baseDataValidator,
+            LocalDate expectedDisbursement, BigDecimal totalPrincipal) {
+
+        this.validateDisbursementsAreDatewiseOrdered(element, baseDataValidator);
+
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+        final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
+        if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.disbursementDataParameterName, element) && expectedDisbursement != null
+                && totalPrincipal != null) {
+            BigDecimal tatalDisbursement = BigDecimal.ZERO;
+            boolean isFirstinstallmentOnExpectedDisbursementDate = false;
+            final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName,
+                    element);
+            List<LocalDate> expectedDisbursementDates = new ArrayList<>();
+            if (variationArray != null && variationArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
+                    if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)
+                            && jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)) {
+                        LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                                LoanApiConstants.disbursementDateParameterName, jsonObject, dateFormat, locale);
+                        if (expectedDisbursementDates.contains(expectedDisbursementDate)) {
+                            baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName)
+                                    .failWithCode(LoanApiConstants.DISBURSEMENT_DATE_UNIQUE_ERROR);
+                        }
+                        if (expectedDisbursementDate.isBefore(expectedDisbursement)) {
+                            baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName)
+                                    .failWithCode(LoanApiConstants.DISBURSEMENT_DATE_BEFORE_ERROR);
+                        }
+                        expectedDisbursementDates.add(expectedDisbursementDate);
+
+                        BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalNamed(
+                                LoanApiConstants.disbursementPrincipalParameterName, jsonObject, locale);
+                        baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName)
+                                .parameterAtIndexArray(LoanApiConstants.disbursementPrincipalParameterName, i).value(principal).notBlank();
+                        if (principal != null) {
+                            tatalDisbursement = tatalDisbursement.add(principal);
+                        }
+
+                        baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName)
+                                .parameterAtIndexArray(LoanApiConstants.disbursementDateParameterName, i).value(expectedDisbursementDate)
+                                .notNull();
+
+                        if (expectedDisbursement.equals(expectedDisbursementDate)) {
+                            isFirstinstallmentOnExpectedDisbursementDate = true;
+                        }
+
+                    }
+                    i++;
+                } while (i < variationArray.size());
+                if (!isFirstinstallmentOnExpectedDisbursementDate) {
+                    baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName)
+                            .failWithCode(LoanApiConstants.DISBURSEMENT_DATE_START_WITH_ERROR);
+                }
+
+                if (tatalDisbursement.compareTo(totalPrincipal) == 1) {
+                    baseDataValidator.reset().parameter(LoanApiConstants.disbursementPrincipalParameterName)
+                            .failWithCode(LoanApiConstants.APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES);
+                }
+                final String interestTypeParameterName = "interestType";
+                final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestTypeParameterName, element);
+                baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).ignoreIfNull()
+                        .integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
+
+            }
+
+        }
+
+    }
+
+    public void validateRecalcuationFrequency(final LocalDate recalculationFrequencyDate, final LocalDate expectedDisbursementDate,
+            final List<ApiParameterError> dataValidationErrors, final String paramName) {
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+        baseDataValidator.reset().parameter(paramName).value(recalculationFrequencyDate).notNull()
+                .validateDateBeforeOrEqual(expectedDisbursementDate);
+    }
+
+    public void validateLoanCharges(final Set<LoanCharge> charges, final List<ApiParameterError> dataValidationErrors) {
+        if (charges == null) { return; }
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+        for (LoanCharge loanCharge : charges) {
+            String errorcode = null;
+            switch (loanCharge.getChargeCalculation()) {
+                case PERCENT_OF_AMOUNT:
+                    if (loanCharge.isInstalmentFee()) {
+                        errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
+
+                    }
+                break;
+                case PERCENT_OF_AMOUNT_AND_INTEREST:
+                    if (loanCharge.isInstalmentFee()) {
+                        errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
+                    } else if (loanCharge.isSpecifiedDueDate()) {
+                        errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
+                    }
+                break;
+                case PERCENT_OF_INTEREST:
+                    if (loanCharge.isSpecifiedDueDate()) {
+                        errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
+                    }
+                break;
+
+                default:
+                break;
+            }
+            if (errorcode != null) {
+                baseDataValidator.reset().parameter("charges").failWithCode(errorcode);
+            }
+        }
+    }
+
+    public void validateLoanForInterestRecalculation(final Loan loan) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        LoanInterestRecalculationDetails interestRecalculationDetails = loan.loanInterestRecalculationDetails();
+        if (!interestRecalculationDetails.getRestFrequencyType().isSameAsRepayment()) {
+            String paramName = LoanProductConstants.recalculationRestFrequencyDateParamName;
+            validateRecalcuationFrequency(interestRecalculationDetails.getRestFrequencyLocalDate(), loan.getExpectedDisbursedOnLocalDate(),
+                    dataValidationErrors, paramName);
+        }
+
+        if (interestRecalculationDetails.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()
+                && !interestRecalculationDetails.getCompoundingFrequencyType().isSameAsRepayment()) {
+            String paramName = LoanProductConstants.recalculationCompoundingFrequencyDateParamName;
+            validateCompoundingFrequency(interestRecalculationDetails.getCompoundingFrequencyLocalDate(),
+                    loan.getExpectedDisbursedOnLocalDate(), dataValidationErrors, paramName);
+        }
+
+        validateLoanCharges(loan.charges(), dataValidationErrors);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateCompoundingFrequency(final LocalDate recalculationFrequencyDate, final LocalDate expectedDisbursementDate,
+            final List<ApiParameterError> dataValidationErrors, final String paramName) {
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+        baseDataValidator.reset().parameter(paramName).value(recalculationFrequencyDate).notNull()
+                .validateDateForEqual(expectedDisbursementDate);
+    }
+
+    private void validatePartialPeriodSupport(final Integer interestCalculationPeriodType, final DataValidatorBuilder baseDataValidator,
+            final JsonElement element, final LoanProduct loanProduct) {
+        if (interestCalculationPeriodType != null) {
+            final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod
+                    .fromInt(interestCalculationPeriodType);
+            boolean considerPartialPeriodUpdates = interestCalculationPeriodMethod.isDaily() ? interestCalculationPeriodMethod.isDaily()
+                    : loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalcualtion();
+            if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, element)) {
+                final Boolean considerPartialInterestEnabled = this.fromApiJsonHelper.extractBooleanNamed(
+                        LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, element);
+                baseDataValidator.reset().parameter(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName)
+                        .value(considerPartialInterestEnabled).notNull().isOneOfTheseValues(true, false);
+                boolean considerPartialPeriods = considerPartialInterestEnabled == null ? false : considerPartialInterestEnabled;
+                if (interestCalculationPeriodMethod.isDaily()) {
+                    if (considerPartialPeriods) {
+                        baseDataValidator.reset().parameter(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName)
+                                .failWithCode("not.supported.for.daily.calcualtions");
+                    }
+                } else {
+                    considerPartialPeriodUpdates = considerPartialPeriods;
+                }
+            }
+
+            if (!considerPartialPeriodUpdates) {
+                if (loanProduct.isInterestRecalculationEnabled()) {
+                    baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName)
+                            .failWithCode("not.supported.for.selected.interest.calcualtion.type");
+                }
+
+                if (loanProduct.isMultiDisburseLoan()) {
+                    baseDataValidator.reset().parameter(LoanProductConstants.multiDisburseLoanParameterName)
+                            .failWithCode("not.supported.for.selected.interest.calcualtion.type");
+                }
+
+                if (loanProduct.allowVariabeInstallments()) {
+                    baseDataValidator.reset().parameter(LoanProductConstants.allowVariableInstallmentsParamName)
+                            .failWithCode("not.supported.for.selected.interest.calcualtion.type");
+                }
+
+                if (loanProduct.isLinkedToFloatingInterestRate()) {
+                    baseDataValidator.reset().parameter("isLinkedToFloatingInterestRates")
+                            .failWithCode("not.supported.for.selected.interest.calcualtion.type");
+                }
+            }
+
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationTransitionApiJsonValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationTransitionApiJsonValidator.java
new file mode 100644
index 0000000..7bcda34
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationTransitionApiJsonValidator.java
@@ -0,0 +1,139 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+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.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.joda.time.LocalDate;
+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 LoanApplicationTransitionApiJsonValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public LoanApplicationTransitionApiJsonValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateApproval(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList(LoanApiConstants.approvedLoanAmountParameterName,
+                LoanApiConstants.approvedOnDateParameterName, LoanApiConstants.noteParameterName, LoanApiConstants.localeParameterName,
+                LoanApiConstants.dateFormatParameterName,LoanApiConstants.disbursementDataParameterName,LoanApiConstants.disbursementDateParameterName));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanapplication");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                LoanApiConstants.approvedLoanAmountParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.approvedLoanAmountParameterName).value(principal).ignoreIfNull()
+                .positiveAmount();
+
+        final LocalDate approvedOnDate = this.fromApiJsonHelper
+                .extractLocalDateNamed(LoanApiConstants.approvedOnDateParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.approvedOnDateParameterName).value(approvedOnDate).notNull();
+
+        final LocalDate expectedDisbursementDate = this.fromApiJsonHelper
+                .extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName).value(expectedDisbursementDate).ignoreIfNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.noteParameterName).value(note).notExceedingLengthOf(1000);
+
+       throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateRejection(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList(LoanApiConstants.rejectedOnDateParameterName,
+                LoanApiConstants.noteParameterName, LoanApiConstants.localeParameterName, LoanApiConstants.dateFormatParameterName));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanapplication");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate rejectedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.rejectedOnDateParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.rejectedOnDateParameterName).value(rejectedOnDate).notNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.noteParameterName).value(note).notExceedingLengthOf(1000);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateApplicantWithdrawal(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList(LoanApiConstants.withdrawnOnDateParameterName,
+                LoanApiConstants.noteParameterName, LoanApiConstants.localeParameterName, LoanApiConstants.dateFormatParameterName));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanapplication");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate withdrawnOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.withdrawnOnDateParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.withdrawnOnDateParameterName).value(withdrawnOnDate).notNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.noteParameterName).value(note).notExceedingLengthOf(1000);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
new file mode 100644
index 0000000..fdda0de
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
@@ -0,0 +1,449 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+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.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
+import org.joda.time.LocalDate;
+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 LoanEventApiJsonValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
+
+
+    @Autowired
+    public LoanEventApiJsonValidator(final FromJsonHelper fromApiJsonHelper, 
+    		 final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void validateDisbursement(final String json, boolean isAccountTransfer) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        Set<String> disbursementParameters = null;
+
+        if (isAccountTransfer) {
+            disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale",
+                    "dateFormat", LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName));
+        } else {
+            disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale",
+                    "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "adjustRepaymentDate", 
+                    LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName));
+        }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.disbursement");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("actualDisbursementDate", element);
+        baseDataValidator.reset().parameter("actualDisbursementDate").value(actualDisbursementDate).notNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+        baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
+
+        final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                LoanApiConstants.principalDisbursedParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.principalDisbursedParameterName).value(principal).ignoreIfNull()
+                .positiveAmount();
+
+        final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
+                element);
+        baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount()
+                .notGreaterThanMax(principal);
+
+        validatePaymentDetails(baseDataValidator, element);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance) {
+        if (null != calendarInstance) {
+            final Calendar calendar = calendarInstance.getCalendar();
+            if (!calendar.isValidRecurringDate(actualDisbursementDate)) {
+                // Disbursement date should fall on a meeting date
+                final String errorMessage = "Expected disbursement date '" + actualDisbursementDate.toString()
+                        + "' does not fall on a meeting date.";
+                throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage, actualDisbursementDate.toString(),
+                        calendar.getTitle());
+            }
+        }
+    }
+
+    public void validateTransaction(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> transactionParameters = new HashSet<>(Arrays.asList("transactionDate", "transactionAmount", "externalId",
+                "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber",
+                "bankNumber"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
+        baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
+
+        final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
+        baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().zeroOrPositiveAmount();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+        baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
+
+        validatePaymentDetails(baseDataValidator, element);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateNewRepaymentTransaction(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> transactionParameters = new HashSet<>(Arrays.asList("transactionDate", "transactionAmount", "externalId",
+                "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber",
+                "bankNumber"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
+        baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
+
+        final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
+        baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+        baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
+
+        validatePaymentDetails(baseDataValidator, element);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateRepaymentDateWithMeetingDate(final LocalDate repaymentDate, final CalendarInstance calendarInstance) {
+        if (null != calendarInstance) {
+            final Calendar calendar = calendarInstance.getCalendar();
+            if (calendar != null && repaymentDate != null) {
+                // Disbursement date should fall on a meeting date
+                if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
+                    final String errorMessage = "Transaction date '" + repaymentDate.toString() + "' does not fall on a meeting date.";
+                    throw new NotValidRecurringDateException("loan.transaction.date", errorMessage, repaymentDate.toString(),
+                            calendar.getTitle());
+                }
+
+            }
+        }
+    }
+
+    private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        // Validate all string payment detail fields for max length
+        final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
+        baseDataValidator.reset().parameter("paymentTypeId").value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
+        final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList("accountNumber", "checkNumber", "routingCode",
+                "receiptNumber", "bankNumber"));
+        for (final String paymentDetailParameterName : paymentDetailParameters) {
+            final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
+            baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+        }
+    }
+
+    public void validateTransactionWithNoAmount(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("transactionDate", "note", "locale", "dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
+        baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+        baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateAddLoanCharge(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("chargeId", "amount", "dueDate", "locale",
+                "dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanCharge");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", element);
+        baseDataValidator.reset().parameter("chargeId").value(chargeId).notNull().integerGreaterThanZero();
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
+        baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
+
+        if (this.fromApiJsonHelper.parameterExists("dueDate", element)) {
+            this.fromApiJsonHelper.extractLocalDateNamed("dueDate", element);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateUpdateOfLoanCharge(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("amount", "dueDate", "locale", "dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanCharge");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
+        baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
+
+        if (this.fromApiJsonHelper.parameterExists("dueDate", element)) {
+            this.fromApiJsonHelper.extractLocalDateNamed("dueDate", element);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateUpdateOfLoanOfficer(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("assignmentDate", "fromLoanOfficerId",
+                "toLoanOfficerId", "locale", "dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanOfficer");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long toLoanOfficerId = this.fromApiJsonHelper.extractLongNamed("toLoanOfficerId", element);
+        baseDataValidator.reset().parameter("toLoanOfficerId").value(toLoanOfficerId).notNull().integerGreaterThanZero();
+
+        final String assignmentDateStr = this.fromApiJsonHelper.extractStringNamed("assignmentDate", element);
+        baseDataValidator.reset().parameter("assignmentDate").value(assignmentDateStr).notBlank();
+
+        if (!StringUtils.isBlank(assignmentDateStr)) {
+            final LocalDate assignmentDate = this.fromApiJsonHelper.extractLocalDateNamed("assignmentDate", element);
+            baseDataValidator.reset().parameter("assignmentDate").value(assignmentDate).notNull();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForBulkLoanReassignment(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> supportedParameters = new HashSet<>(Arrays.asList("assignmentDate", "fromLoanOfficerId", "toLoanOfficerId",
+                "loans", "locale", "dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanOfficer");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final LocalDate assignmentDate = this.fromApiJsonHelper.extractLocalDateNamed("assignmentDate", element);
+        baseDataValidator.reset().parameter("assignmentDate").value(assignmentDate).notNull();
+        final Long fromLoanOfficerId = this.fromApiJsonHelper.extractLongNamed("fromLoanOfficerId", element);
+        baseDataValidator.reset().parameter("fromLoanOfficerId").value(fromLoanOfficerId).notNull().longGreaterThanZero();
+        final Long toLoanOfficerId = this.fromApiJsonHelper.extractLongNamed("toLoanOfficerId", element);
+        baseDataValidator.reset().parameter("toLoanOfficerId").value(toLoanOfficerId).notNull().longGreaterThanZero();
+        final String[] loans = this.fromApiJsonHelper.extractArrayNamed("loans", element);
+        baseDataValidator.reset().parameter("loans").value(loans).arrayNotEmpty();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateChargePaymentTransaction(final String json, final boolean isChargeIdIncluded) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+        Set<String> transactionParameters = null;
+        if (isChargeIdIncluded) {
+            transactionParameters = new HashSet<>(Arrays.asList("transactionDate", "locale", "dateFormat", "chargeId", "dueDate",
+                    "installmentNumber"));
+        } else {
+            transactionParameters = new HashSet<>(Arrays.asList("transactionDate", "locale", "dateFormat", "dueDate",
+                    "installmentNumber"));
+        }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource("loan.charge.payment.transaction");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
+        if (isChargeIdIncluded) {
+            final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", element);
+            baseDataValidator.reset().parameter("chargeId").value(chargeId).notNull().integerGreaterThanZero();
+        }
+        baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
+        final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("installmentNumber", element);
+        baseDataValidator.reset().parameter("installmentNumber").value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateInstallmentChargeTransaction(final String json) {
+
+        if (StringUtils.isBlank(json)) { return; }
+        Set<String> transactionParameters = new HashSet<>(Arrays.asList("dueDate", "locale", "dateFormat", "installmentNumber"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource("loan.charge.waive.transaction");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("installmentNumber", element);
+        baseDataValidator.reset().parameter("installmentNumber").value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateUpdateDisbursementDateAndAmount(final String json, LoanDisbursementDetails loanDisbursementDetails) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("locale", "dateFormat", LoanApiConstants.disbursementDataParameterName,
+                LoanApiConstants.approvedLoanAmountParameterName, LoanApiConstants.updatedDisbursementDateParameterName, LoanApiConstants.updatedDisbursementPrincipalParameterName, 
+                LoanApiConstants.disbursementDateParameterName));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.update.disbursement");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                LoanApiConstants.disbursementDateParameterName, element);
+        baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName).value(actualDisbursementDate).notNull();
+        
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName, 
+        		element,locale);
+        baseDataValidator.reset().parameter(LoanApiConstants.disbursementPrincipalParameterName).value(principal).notNull();
+
+        final BigDecimal approvedPrincipal = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.approvedLoanAmountParameterName, 
+        		element,locale);
+        if (loanDisbursementDetails.actualDisbursementDate() != null) {
+            baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName)
+                    .failWithCode(LoanApiConstants.ALREADY_DISBURSED);
+        }
+
+        
+        fromApiJsonDeserializer.validateLoanMultiDisbursementdate(element, baseDataValidator, actualDisbursementDate, approvedPrincipal);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+    
+    public void validateNewRefundTransaction(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> transactionParameters = new HashSet<>(Arrays.asList("transactionDate", "transactionAmount", "externalId",
+                "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber",
+                "bankNumber"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
+        baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
+
+        final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
+        baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+        baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
+
+        validatePaymentDetails(baseDataValidator, element);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanUpdateCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanUpdateCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..f1d3762
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanUpdateCommandFromApiJsonDeserializer.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.serialization;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.command.LoanUpdateCommand;
+import org.joda.time.LocalDate;
+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 LoanUpdateCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<LoanUpdateCommand> {
+
+    /**
+     * The parameters supported for this command.
+     */
+    final Set<String> supportedParameters = new HashSet<>(Arrays.asList("unassignedDate", "locale", "dateFormat"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public LoanUpdateCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public LoanUpdateCommand commandFromApiJson(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 JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate unassignedDate = this.fromApiJsonHelper.extractLocalDateNamed("unassignedDate", element);
+
+        return new LoanUpdateCommand(unassignedDate);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/VariableLoanScheduleFromApiJsonValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/VariableLoanScheduleFromApiJsonValidator.java
new file mode 100644
index 0000000..f62aff7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/VariableLoanScheduleFromApiJsonValidator.java
@@ -0,0 +1,164 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+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.exception.UnsupportedParameterException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class VariableLoanScheduleFromApiJsonValidator {
+
+    final Set<String> variableSchedulesupportedParameters = new HashSet<>(Arrays.asList(LoanApiConstants.exceptionParamName,
+            LoanApiConstants.localeParameterName, LoanApiConstants.dateFormatParameterName));
+    final Set<String> variableSchedulesupportedArrayParameters = new HashSet<>(Arrays.asList(
+            LoanApiConstants.modifiedinstallmentsParamName, LoanApiConstants.newinstallmentsParamName,
+            LoanApiConstants.deletedinstallmentsParamName));
+    final Set<String> variableScheduleModifiedParameters = new HashSet<>(Arrays.asList(LoanApiConstants.dueDateParamName,
+            LoanApiConstants.modifiedDueDateParamName, LoanApiConstants.principalParamName, LoanApiConstants.installmentAmountParamName));
+    final Set<String> variableScheduleNewInstallmentParameters = new HashSet<>(Arrays.asList(LoanApiConstants.dueDateParamName,
+            LoanApiConstants.principalParamName, LoanApiConstants.installmentAmountParamName));
+    final Set<String> variableScheduleDeleteInstallmentParameters = new HashSet<>(Arrays.asList(LoanApiConstants.dueDateParamName));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public VariableLoanScheduleFromApiJsonValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateSchedule(final String json, final Loan loan) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.variableSchedulesupportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+
+        if (!loan.isSubmittedAndPendingApproval()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("account.is.not.submitted.and.pending.state",
+                    "Loan is not in submited state");
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        if (loan.loanProduct().allowVariabeInstallments()) {
+            if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.exceptionParamName, element)) {
+                final JsonObject topLevelJsonElement = element.getAsJsonObject();
+                final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
+                final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+                final JsonObject exceptionObject = topLevelJsonElement.getAsJsonObject(LoanApiConstants.exceptionParamName);
+                this.fromApiJsonHelper.checkForUnsupportedParameters(exceptionObject, this.variableSchedulesupportedArrayParameters);
+                if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.modifiedinstallmentsParamName, exceptionObject)
+                        && exceptionObject.get(LoanApiConstants.modifiedinstallmentsParamName).isJsonArray()) {
+                    final JsonArray modificationsArray = exceptionObject.get(LoanApiConstants.modifiedinstallmentsParamName)
+                            .getAsJsonArray();
+                    validateLoanTermVariations(loan, baseDataValidator, dateFormat, locale, modificationsArray,
+                            this.variableScheduleModifiedParameters, LoanApiConstants.modifiedinstallmentsParamName);
+                }
+                if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.newinstallmentsParamName, exceptionObject)
+                        && exceptionObject.get(LoanApiConstants.newinstallmentsParamName).isJsonArray()) {
+                    final JsonArray array = exceptionObject.get(LoanApiConstants.newinstallmentsParamName).getAsJsonArray();
+                    validateLoanTermVariations(loan, baseDataValidator, dateFormat, locale, array,
+                            this.variableScheduleNewInstallmentParameters, LoanApiConstants.newinstallmentsParamName);
+                }
+                if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.deletedinstallmentsParamName, exceptionObject)
+                        && exceptionObject.get(LoanApiConstants.deletedinstallmentsParamName).isJsonArray()) {
+                    final JsonArray array = exceptionObject.get(LoanApiConstants.deletedinstallmentsParamName).getAsJsonArray();
+                    validateLoanTermVariations(loan, baseDataValidator, dateFormat, locale, array,
+                            this.variableScheduleDeleteInstallmentParameters, LoanApiConstants.deletedinstallmentsParamName);
+                }
+            }
+        } else {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.not.supported",
+                    "Loan schedule modification not allowed");
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private void validateLoanTermVariations(final Loan loan, final DataValidatorBuilder baseDataValidator, final String dateFormat,
+            final Locale locale, final JsonArray modificationsArray, final Set<String> supportParams, final String arrayName) {
+        for (int i = 1; i <= modificationsArray.size(); i++) {
+
+            final JsonObject arrayElement = modificationsArray.get(i - 1).getAsJsonObject();
+            this.fromApiJsonHelper.checkForUnsupportedParameters(arrayElement, supportParams);
+            final BigDecimal installmentAmount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.installmentAmountParamName,
+                    arrayElement, locale);
+            baseDataValidator.reset().parameter(arrayName).parameterAtIndexArray(LoanApiConstants.installmentAmountParamName, i)
+                    .value(installmentAmount).positiveAmount();
+            final BigDecimal principalAmount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.principalParamName,
+                    arrayElement, locale);
+            baseDataValidator.reset().parameter(arrayName).parameterAtIndexArray(LoanApiConstants.principalParamName, i)
+                    .value(principalAmount).zeroOrPositiveAmount();
+
+            if (loan.getLoanProductRelatedDetail().getInterestMethod().isDecliningBalnce()
+                    && loan.getLoanProductRelatedDetail().getAmortizationMethod().isEqualInstallment() && principalAmount != null) {
+                List<String> unsupportedParams = new ArrayList<>(1);
+                unsupportedParams.add(LoanApiConstants.principalParamName);
+                throw new UnsupportedParameterException(unsupportedParams);
+            } else if ((!loan.getLoanProductRelatedDetail().getInterestMethod().isDecliningBalnce() || loan.getLoanProductRelatedDetail()
+                    .getAmortizationMethod().isEqualPrincipal())
+                    && installmentAmount != null) {
+                List<String> unsupportedParams = new ArrayList<>(1);
+                unsupportedParams.add(LoanApiConstants.installmentAmountParamName);
+                throw new UnsupportedParameterException(unsupportedParams);
+            }
+
+            LocalDate duedate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.dueDateParamName, arrayElement, dateFormat,
+                    locale);
+            baseDataValidator.reset().parameter(arrayName).parameterAtIndexArray(LoanApiConstants.dueDateParamName, i).value(duedate)
+                    .notNull().validateDateAfter(loan.getExpectedDisbursedOnLocalDate());
+
+            LocalDate modifiedDuedate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.modifiedDueDateParamName,
+                    arrayElement, dateFormat, locale);
+            baseDataValidator.reset().parameter(arrayName).parameterAtIndexArray(LoanApiConstants.modifiedDueDateParamName, i)
+                    .value(modifiedDuedate).validateDateAfter(loan.getExpectedDisbursedOnLocalDate());
+            if (arrayName.equals(LoanApiConstants.modifiedinstallmentsParamName) && modifiedDuedate == null && installmentAmount == null
+                    && principalAmount == null) {
+                baseDataValidator.reset().parameter(arrayName).failWithCode("variation.required", "At least one vario");
+            }
+
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformService.java
new file mode 100644
index 0000000..6ac9c18
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformService.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.loanaccount.service;
+
+import org.apache.fineract.organisation.staff.data.StaffAccountSummaryCollectionData;
+
+public interface BulkLoansReadPlatformService {
+
+    StaffAccountSummaryCollectionData retrieveLoanOfficerAccountSummary(final Long loanOfficerId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java
new file mode 100644
index 0000000..14477f6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java
@@ -0,0 +1,123 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+
+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.staff.data.StaffAccountSummaryCollectionData;
+import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData;
+import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService;
+import org.apache.fineract.portfolio.client.domain.ClientStatus;
+import org.apache.fineract.portfolio.group.domain.GroupingTypeStatus;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class BulkLoansReadPlatformServiceImpl implements BulkLoansReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
+
+    @Autowired
+    public BulkLoansReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final AccountDetailsReadPlatformService accountDetailsReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
+    }
+
+    @Override
+    public StaffAccountSummaryCollectionData retrieveLoanOfficerAccountSummary(final Long loanOfficerId) {
+
+        this.context.authenticatedUser();
+
+        final StaffClientMapper staffClientMapper = new StaffClientMapper();
+        final String clientSql = "select distinct " + staffClientMapper.schema() + " and c.status_enum=?";
+
+        final StaffGroupMapper staffGroupMapper = new StaffGroupMapper();
+        final String groupSql = "select distinct " + staffGroupMapper.schema() + " and g.status_enum=?";
+
+        final List<StaffAccountSummaryCollectionData.LoanAccountSummary> clientSummaryList = this.jdbcTemplate.query(clientSql,
+                staffClientMapper, new Object[] { loanOfficerId, ClientStatus.ACTIVE.getValue() });
+
+        for (final StaffAccountSummaryCollectionData.LoanAccountSummary clientSummary : clientSummaryList) {
+
+            final Collection<LoanAccountSummaryData> clientLoanAccounts = this.accountDetailsReadPlatformService
+                    .retrieveClientLoanAccountsByLoanOfficerId(clientSummary.getId(), loanOfficerId);
+
+            clientSummary.setLoans(clientLoanAccounts);
+        }
+
+        final List<StaffAccountSummaryCollectionData.LoanAccountSummary> groupSummaryList = this.jdbcTemplate.query(groupSql,
+                staffGroupMapper, new Object[] { loanOfficerId, GroupingTypeStatus.ACTIVE.getValue() });
+
+        for (final StaffAccountSummaryCollectionData.LoanAccountSummary groupSummary : groupSummaryList) {
+
+            final Collection<LoanAccountSummaryData> groupLoanAccounts = this.accountDetailsReadPlatformService
+                    .retrieveGroupLoanAccountsByLoanOfficerId(groupSummary.getId(), loanOfficerId);
+
+            groupSummary.setLoans(groupLoanAccounts);
+        }
+
+        return new StaffAccountSummaryCollectionData(clientSummaryList, groupSummaryList);
+    }
+
+    private static final class StaffClientMapper implements RowMapper<StaffAccountSummaryCollectionData.LoanAccountSummary> {
+
+        public String schema() {
+            return " c.id as id, c.display_name as displayName from m_client c "
+                    + " join m_loan l on c.id = l.client_id where l.loan_officer_id = ? ";
+        }
+
+        @Override
+        public StaffAccountSummaryCollectionData.LoanAccountSummary mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String displayName = rs.getString("displayName");
+
+            return new StaffAccountSummaryCollectionData.LoanAccountSummary(id, displayName);
+        }
+    }
+
+    private static final class StaffGroupMapper implements RowMapper<StaffAccountSummaryCollectionData.LoanAccountSummary> {
+
+        public String schema() {
+            return " g.id as id, g.display_name as name from m_group g"
+                    + " join m_loan l on g.id = l.group_id where l.loan_officer_id = ? ";
+        }
+
+        @Override
+        public StaffAccountSummaryCollectionData.LoanAccountSummary mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String name = rs.getString("name");
+
+            return new StaffAccountSummaryCollectionData.LoanAccountSummary(id, name);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformService.java
new file mode 100644
index 0000000..8f48eeb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformService.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
+import org.joda.time.LocalDate;
+
+public interface LoanAccrualPlatformService {
+
+    String addPeriodicAccruals(LocalDate tilldate);
+
+    String addPeriodicAccruals(LocalDate tilldate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas);
+
+    void addAccrualAccounting() throws JobExecutionException;
+
+    void addPeriodicAccruals() throws JobExecutionException;
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java
new file mode 100644
index 0000000..a790f4e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java
@@ -0,0 +1,120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformService {
+
+    private final LoanReadPlatformService loanReadPlatformService;
+    private final LoanAccrualWritePlatformService loanAccrualWritePlatformService;
+
+    @Autowired
+    public LoanAccrualPlatformServiceImpl(final LoanReadPlatformService loanReadPlatformService,
+            final LoanAccrualWritePlatformService loanAccrualWritePlatformService) {
+        this.loanReadPlatformService = loanReadPlatformService;
+        this.loanAccrualWritePlatformService = loanAccrualWritePlatformService;
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.ADD_ACCRUAL_ENTRIES)
+    public void addAccrualAccounting() throws JobExecutionException {
+        Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retriveScheduleAccrualData();
+        StringBuilder sb = new StringBuilder();
+        Map<Long, Collection<LoanScheduleAccrualData>> loanDataMap = new HashMap<>();
+        for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
+            if (loanDataMap.containsKey(accrualData.getLoanId())) {
+                loanDataMap.get(accrualData.getLoanId()).add(accrualData);
+            } else {
+                Collection<LoanScheduleAccrualData> accrualDatas = new ArrayList<>();
+                accrualDatas.add(accrualData);
+                loanDataMap.put(accrualData.getLoanId(), accrualDatas);
+            }
+        }
+
+        for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> mapEntry : loanDataMap.entrySet()) {
+            try {
+                this.loanAccrualWritePlatformService.addAccrualAccounting(mapEntry.getKey(), mapEntry.getValue());
+            } catch (Exception e) {
+                Throwable realCause = e;
+                if (e.getCause() != null) {
+                    realCause = e.getCause();
+                }
+                sb.append("failed to add accural transaction for loan " + mapEntry.getKey() + " with message " + realCause.getMessage());
+            }
+        }
+
+        if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.ADD_PERIODIC_ACCRUAL_ENTRIES)
+    public void addPeriodicAccruals() throws JobExecutionException {
+        String errors = addPeriodicAccruals(LocalDate.now());
+        if (errors.length() > 0) { throw new JobExecutionException(errors); }
+    }
+
+    @Override
+    public String addPeriodicAccruals(final LocalDate tilldate) {
+        Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retrivePeriodicAccrualData(tilldate);
+        return addPeriodicAccruals(tilldate, loanScheduleAccrualDatas);
+    }
+
+    @Override
+    public String addPeriodicAccruals(final LocalDate tilldate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) {
+        StringBuilder sb = new StringBuilder();
+        Map<Long, Collection<LoanScheduleAccrualData>> loanDataMap = new HashMap<>();
+        for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
+            if (loanDataMap.containsKey(accrualData.getLoanId())) {
+                loanDataMap.get(accrualData.getLoanId()).add(accrualData);
+            } else {
+                Collection<LoanScheduleAccrualData> accrualDatas = new ArrayList<>();
+                accrualDatas.add(accrualData);
+                loanDataMap.put(accrualData.getLoanId(), accrualDatas);
+            }
+        }
+
+        for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> mapEntry : loanDataMap.entrySet()) {
+            try {
+                this.loanAccrualWritePlatformService.addPeriodicAccruals(tilldate, mapEntry.getKey(), mapEntry.getValue());
+            } catch (Exception e) {
+                Throwable realCause = e;
+                if (e.getCause() != null) {
+                    realCause = e.getCause();
+                }
+                sb.append("failed to add accural transaction for loan " + mapEntry.getKey() + " with message " + realCause.getMessage());
+            }
+        }
+
+        return sb.toString();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java
new file mode 100755
index 0000000..1e63a7c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
+import org.joda.time.LocalDate;
+
+public interface LoanAccrualWritePlatformService {
+
+    void addAccrualAccounting(Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws Exception;
+
+    void addPeriodicAccruals(LocalDate tilldate, Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws Exception;
+
+}
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
new file mode 100755
index 0000000..79c54ff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
@@ -0,0 +1,472 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+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
+public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlatformService {
+
+    private final LoanReadPlatformService loanReadPlatformService;
+    private final LoanChargeReadPlatformService loanChargeReadPlatformService;
+    private final JdbcTemplate jdbcTemplate;
+    private final DataSource dataSource;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+
+    @Autowired
+    public LoanAccrualWritePlatformServiceImpl(final RoutingDataSource dataSource, final LoanReadPlatformService loanReadPlatformService,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService,
+            final LoanChargeReadPlatformService loanChargeReadPlatformService) {
+        this.loanReadPlatformService = loanReadPlatformService;
+        this.dataSource = dataSource;
+        this.jdbcTemplate = new JdbcTemplate(this.dataSource);
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+        this.loanChargeReadPlatformService = loanChargeReadPlatformService;
+    }
+
+    @Override
+    @Transactional
+    public void addAccrualAccounting(final Long loanId, final Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas)
+            throws Exception {
+        Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccural(loanId);
+        Collection<LoanSchedulePeriodData> loanWaiverScheduleData = new ArrayList<>(1);
+        Collection<LoanTransactionData> loanWaiverTansactionData = new ArrayList<>(1);
+
+        for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
+            if (accrualData.getWaivedInterestIncome() != null && loanWaiverScheduleData.isEmpty()) {
+                loanWaiverScheduleData = this.loanReadPlatformService.fetchWaiverInterestRepaymentData(accrualData.getLoanId());
+                loanWaiverTansactionData = this.loanReadPlatformService.retrieveWaiverLoanTransactions(accrualData.getLoanId());
+            }
+            updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), accrualData.getDueDateAsLocaldate());
+            updateInterestIncome(accrualData, loanWaiverTansactionData, loanWaiverScheduleData, accrualData.getDueDateAsLocaldate());
+            addAccrualAccounting(accrualData);
+        }
+    }
+
+    @Override
+    @Transactional
+    public void addPeriodicAccruals(final LocalDate tilldate, Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas)
+            throws Exception {
+        boolean firstTime = true;
+        LocalDate accruredTill = null;
+        Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccural(loanId);
+        Collection<LoanSchedulePeriodData> loanWaiverScheduleData = new ArrayList<>(1);
+        Collection<LoanTransactionData> loanWaiverTansactionData = new ArrayList<>(1);
+        for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
+            if (accrualData.getWaivedInterestIncome() != null && loanWaiverScheduleData.isEmpty()) {
+                loanWaiverScheduleData = this.loanReadPlatformService.fetchWaiverInterestRepaymentData(accrualData.getLoanId());
+                loanWaiverTansactionData = this.loanReadPlatformService.retrieveWaiverLoanTransactions(accrualData.getLoanId());
+            }
+
+            if (accrualData.getDueDateAsLocaldate().isAfter(tilldate)) {
+                if (accruredTill == null || firstTime) {
+                    accruredTill = accrualData.getAccruedTill();
+                    firstTime = false;
+                }
+                if (accruredTill == null || accruredTill.isBefore(tilldate)) {
+                    updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), tilldate);
+                    updateInterestIncome(accrualData, loanWaiverTansactionData, loanWaiverScheduleData, tilldate);
+                    addAccrualTillSpecificDate(tilldate, accrualData);
+                }
+            } else {
+                updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), accrualData.getDueDateAsLocaldate());
+                updateInterestIncome(accrualData, loanWaiverTansactionData, loanWaiverScheduleData, tilldate);
+                addAccrualAccounting(accrualData);
+                accruredTill = accrualData.getDueDateAsLocaldate();
+            }
+        }
+    }
+
+    private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanScheduleAccrualData accrualData) throws Exception {
+        LocalDate interestStartDate = accrualData.getFromDateAsLocaldate();
+        if (accrualData.getInterestCalculatedFrom() != null
+                && accrualData.getFromDateAsLocaldate().isBefore(accrualData.getInterestCalculatedFrom())) {
+            if (accrualData.getInterestCalculatedFrom().isBefore(accrualData.getDueDateAsLocaldate())) {
+                interestStartDate = accrualData.getInterestCalculatedFrom();
+            } else {
+                interestStartDate = accrualData.getDueDateAsLocaldate();
+            }
+        }
+
+        int totalNumberOfDays = Days.daysBetween(interestStartDate, accrualData.getDueDateAsLocaldate()).getDays();
+        LocalDate startDate = accrualData.getFromDateAsLocaldate();
+        if (accrualData.getInterestCalculatedFrom() != null && startDate.isBefore(accrualData.getInterestCalculatedFrom())) {
+            if (accrualData.getInterestCalculatedFrom().isBefore(tilldate)) {
+                startDate = accrualData.getInterestCalculatedFrom();
+            } else {
+                startDate = tilldate;
+            }
+        }
+        int daysToBeAccrued = Days.daysBetween(startDate, tilldate).getDays();
+        double interestPerDay = accrualData.getAccruableIncome().doubleValue() / totalNumberOfDays;
+        BigDecimal amount = BigDecimal.ZERO;
+        BigDecimal interestportion = null;
+        BigDecimal feeportion = accrualData.getDueDateFeeIncome();
+        BigDecimal penaltyportion = accrualData.getDueDatePenaltyIncome();
+        if (daysToBeAccrued >= totalNumberOfDays) {
+            interestportion = accrualData.getAccruableIncome();
+        } else {
+            double iterest = interestPerDay * daysToBeAccrued;
+            interestportion = BigDecimal.valueOf(iterest);
+        }
+        interestportion = interestportion.setScale(accrualData.getCurrencyData().decimalPlaces(), MoneyHelper.getRoundingMode());
+
+        BigDecimal totalAccInterest = accrualData.getAccruedInterestIncome();
+        BigDecimal totalAccPenalty = accrualData.getAccruedPenaltyIncome();
+        BigDecimal totalAccFee = accrualData.getAccruedFeeIncome();
+
+        if (interestportion != null) {
+            if (totalAccInterest == null) {
+                totalAccInterest = BigDecimal.ZERO;
+            }
+            interestportion = interestportion.subtract(totalAccInterest);
+            amount = amount.add(interestportion);
+            totalAccInterest = totalAccInterest.add(interestportion);
+            if (interestportion.compareTo(BigDecimal.ZERO) == 0) {
+                interestportion = null;
+            }
+        }
+        if (feeportion != null) {
+            if (totalAccFee == null) {
+                totalAccFee = BigDecimal.ZERO;
+            }
+            feeportion = feeportion.subtract(totalAccFee);
+            amount = amount.add(feeportion);
+            totalAccFee = totalAccFee.add(feeportion);
+            if (feeportion.compareTo(BigDecimal.ZERO) == 0) {
+                feeportion = null;
+            }
+        }
+
+        if (penaltyportion != null) {
+            if (totalAccPenalty == null) {
+                totalAccPenalty = BigDecimal.ZERO;
+            }
+            penaltyportion = penaltyportion.subtract(totalAccPenalty);
+            amount = amount.add(penaltyportion);
+            totalAccPenalty = totalAccPenalty.add(penaltyportion);
+            if (penaltyportion.compareTo(BigDecimal.ZERO) == 0) {
+                penaltyportion = null;
+            }
+        }
+        if (amount.compareTo(BigDecimal.ZERO) == 1) {
+            addAccrualAccounting(accrualData, amount, interestportion, totalAccInterest, feeportion, totalAccFee, penaltyportion,
+                    totalAccPenalty, tilldate);
+        }
+    }
+
+    @Transactional
+    public void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData) throws Exception {
+
+        BigDecimal amount = BigDecimal.ZERO;
+        BigDecimal interestportion = null;
+        BigDecimal totalAccInterest = null;
+        if (scheduleAccrualData.getAccruableIncome() != null) {
+            interestportion = scheduleAccrualData.getAccruableIncome();
+            totalAccInterest = interestportion;
+            if (scheduleAccrualData.getAccruedInterestIncome() != null) {
+                interestportion = interestportion.subtract(scheduleAccrualData.getAccruedInterestIncome());
+            }
+            amount = amount.add(interestportion);
+            if (interestportion.compareTo(BigDecimal.ZERO) == 0) {
+                interestportion = null;
+            }
+        }
+
+        BigDecimal feeportion = null;
+        BigDecimal totalAccFee = null;
+        if (scheduleAccrualData.getDueDateFeeIncome() != null) {
+            feeportion = scheduleAccrualData.getDueDateFeeIncome();
+            totalAccFee = feeportion;
+            if (scheduleAccrualData.getAccruedFeeIncome() != null) {
+                feeportion = feeportion.subtract(scheduleAccrualData.getAccruedFeeIncome());
+            }
+            amount = amount.add(feeportion);
+            if (feeportion.compareTo(BigDecimal.ZERO) == 0) {
+                feeportion = null;
+            }
+        }
+
+        BigDecimal penaltyportion = null;
+        BigDecimal totalAccPenalty = null;
+        if (scheduleAccrualData.getDueDatePenaltyIncome() != null) {
+            penaltyportion = scheduleAccrualData.getDueDatePenaltyIncome();
+            totalAccPenalty = penaltyportion;
+            if (scheduleAccrualData.getAccruedPenaltyIncome() != null) {
+                penaltyportion = penaltyportion.subtract(scheduleAccrualData.getAccruedPenaltyIncome());
+            }
+            amount = amount.add(penaltyportion);
+            if (penaltyportion.compareTo(BigDecimal.ZERO) == 0) {
+                penaltyportion = null;
+            }
+        }
+        if (amount.compareTo(BigDecimal.ZERO) == 1) {
+            addAccrualAccounting(scheduleAccrualData, amount, interestportion, totalAccInterest, feeportion, totalAccFee, penaltyportion,
+                    totalAccPenalty, scheduleAccrualData.getDueDateAsLocaldate());
+        }
+    }
+
+    private void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData, BigDecimal amount, BigDecimal interestportion,
+            BigDecimal totalAccInterest, BigDecimal feeportion, BigDecimal totalAccFee, BigDecimal penaltyportion,
+            BigDecimal totalAccPenalty, final LocalDate accruedTill) throws Exception {
+        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(),
+                LoanTransactionType.ACCRUAL.getValue(), accruedTill.toDate(), amount, interestportion, feeportion, penaltyportion,
+                DateUtils.getDateOfTenant());
+        @SuppressWarnings("deprecation")
+        final Long transactonId = this.jdbcTemplate.queryForLong("SELECT LAST_INSERT_ID()");
+
+        Map<LoanChargeData, BigDecimal> applicableCharges = scheduleAccrualData.getApplicableCharges();
+        String chargespaidSql = "INSERT INTO m_loan_charge_paid_by (loan_transaction_id, loan_charge_id, amount,installment_number) VALUES (?,?,?,?)";
+        for (Map.Entry<LoanChargeData, BigDecimal> entry : applicableCharges.entrySet()) {
+            LoanChargeData chargeData = entry.getKey();
+            this.jdbcTemplate.update(chargespaidSql, transactonId, chargeData.getId(), entry.getValue(),
+                    scheduleAccrualData.getInstallmentNumber());
+        }
+
+        Map<String, Object> transactionMap = toMapData(transactonId, amount, interestportion, feeportion, penaltyportion,
+                scheduleAccrualData, accruedTill);
+
+        String repaymetUpdatesql = "UPDATE m_loan_repayment_schedule SET accrual_interest_derived=?, accrual_fee_charges_derived=?, "
+                + "accrual_penalty_charges_derived=? WHERE  id=?";
+        this.jdbcTemplate.update(repaymetUpdatesql, totalAccInterest, totalAccFee, totalAccPenalty,
+                scheduleAccrualData.getRepaymentScheduleId());
+
+        String updateLoan = "UPDATE m_loan  SET accrued_till=?  WHERE  id=?";
+        this.jdbcTemplate.update(updateLoan, accruedTill.toDate(), scheduleAccrualData.getLoanId());
+        final Map<String, Object> accountingBridgeData = deriveAccountingBridgeData(scheduleAccrualData, transactionMap);
+        this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
+    }
+
+    public Map<String, Object> deriveAccountingBridgeData(final LoanScheduleAccrualData loanScheduleAccrualData,
+            final Map<String, Object> transactionMap) {
+
+        final Map<String, Object> accountingBridgeData = new LinkedHashMap<>();
+        accountingBridgeData.put("loanId", loanScheduleAccrualData.getLoanId());
+        accountingBridgeData.put("loanProductId", loanScheduleAccrualData.getLoanProductId());
+        accountingBridgeData.put("officeId", loanScheduleAccrualData.getOfficeId());
+        accountingBridgeData.put("currency", loanScheduleAccrualData.getCurrencyData());
+        accountingBridgeData.put("cashBasedAccountingEnabled", false);
+        accountingBridgeData.put("upfrontAccrualBasedAccountingEnabled", false);
+        accountingBridgeData.put("periodicAccrualBasedAccountingEnabled", true);
+        accountingBridgeData.put("isAccountTransfer", false);
+
+        final List<Map<String, Object>> newLoanTransactions = new ArrayList<>();
+        newLoanTransactions.add(transactionMap);
+
+        accountingBridgeData.put("newLoanTransactions", newLoanTransactions);
+        return accountingBridgeData;
+    }
+
+    public Map<String, Object> toMapData(final Long id, final BigDecimal amount, final BigDecimal interestportion,
+            final BigDecimal feeportion, final BigDecimal penaltyportion, final LoanScheduleAccrualData loanScheduleAccrualData,
+            final LocalDate accruredTill) {
+        final Map<String, Object> thisTransactionData = new LinkedHashMap<>();
+
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.ACCRUAL);
+
+        thisTransactionData.put("id", id);
+        thisTransactionData.put("officeId", loanScheduleAccrualData.getOfficeId());
+        thisTransactionData.put("type", transactionType);
+        thisTransactionData.put("reversed", false);
+        thisTransactionData.put("date", accruredTill);
+        thisTransactionData.put("currency", loanScheduleAccrualData.getCurrencyData());
+        thisTransactionData.put("amount", amount);
+        thisTransactionData.put("principalPortion", null);
+        thisTransactionData.put("interestPortion", interestportion);
+        thisTransactionData.put("feeChargesPortion", feeportion);
+        thisTransactionData.put("penaltyChargesPortion", penaltyportion);
+        thisTransactionData.put("overPaymentPortion", null);
+
+        Map<LoanChargeData, BigDecimal> applicableCharges = loanScheduleAccrualData.getApplicableCharges();
+        if (applicableCharges != null && !applicableCharges.isEmpty()) {
+            final List<Map<String, Object>> loanChargesPaidData = new ArrayList<>();
+            for (Map.Entry<LoanChargeData, BigDecimal> entry : applicableCharges.entrySet()) {
+                LoanChargeData chargeData = entry.getKey();
+                final Map<String, Object> loanChargePaidData = new LinkedHashMap<>();
+                loanChargePaidData.put("chargeId", chargeData.getChargeId());
+                loanChargePaidData.put("isPenalty", chargeData.isPenalty());
+                loanChargePaidData.put("loanChargeId", chargeData.getId());
+                loanChargePaidData.put("amount", entry.getValue());
+
+                loanChargesPaidData.add(loanChargePaidData);
+            }
+            thisTransactionData.put("loanChargesPaid", loanChargesPaidData);
+        }
+
+        return thisTransactionData;
+    }
+
+    private void updateCharges(final Collection<LoanChargeData> chargesData, final LoanScheduleAccrualData accrualData,
+            final LocalDate startDate, final LocalDate endDate) {
+
+        final Map<LoanChargeData, BigDecimal> applicableCharges = new HashMap<>();
+        BigDecimal dueDateFeeIncome = BigDecimal.ZERO;
+        BigDecimal dueDatePenaltyIncome = BigDecimal.ZERO;
+        for (LoanChargeData loanCharge : chargesData) {
+            BigDecimal chargeAmount = BigDecimal.ZERO;
+            if (loanCharge.getDueDate() == null) {
+                if (loanCharge.isInstallmentFee() && accrualData.getDueDateAsLocaldate().isEqual(endDate)) {
+                    Collection<LoanInstallmentChargeData> installmentData = loanCharge.getInstallmentChargeData();
+                    for (LoanInstallmentChargeData installmentChargeData : installmentData) {
+
+                        if (installmentChargeData.getInstallmentNumber().equals(accrualData.getInstallmentNumber())) {
+                            BigDecimal accruableForInstallment = installmentChargeData.getAmount();
+                            if (installmentChargeData.getAmountUnrecognized() != null) {
+                                accruableForInstallment = accruableForInstallment.subtract(installmentChargeData.getAmountUnrecognized());
+                            }
+                            chargeAmount = accruableForInstallment;
+                            boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1;
+                            if (canAddCharge
+                                    && (installmentChargeData.getAmountAccrued() == null || chargeAmount.compareTo(installmentChargeData
+                                            .getAmountAccrued()) != 0)) {
+                                BigDecimal amountForAccrual = chargeAmount;
+                                if (installmentChargeData.getAmountAccrued() != null) {
+                                    amountForAccrual = chargeAmount.subtract(installmentChargeData.getAmountAccrued());
+                                }
+                                applicableCharges.put(loanCharge, amountForAccrual);
+                                BigDecimal amountAccrued = chargeAmount;
+                                if (loanCharge.getAmountAccrued() != null) {
+                                    amountAccrued = amountAccrued.add(loanCharge.getAmountAccrued());
+                                }
+                                loanCharge.updateAmountAccrued(amountAccrued);
+                            }
+                            break;
+                        }
+                    }
+                }
+            } else if (loanCharge.getDueDate().isAfter(startDate) && !loanCharge.getDueDate().isAfter(endDate)) {
+                chargeAmount = loanCharge.getAmount();
+                if (loanCharge.getAmountUnrecognized() != null) {
+                    chargeAmount = chargeAmount.subtract(loanCharge.getAmountUnrecognized());
+                }
+                boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1;
+                if (canAddCharge && (loanCharge.getAmountAccrued() == null || chargeAmount.compareTo(loanCharge.getAmountAccrued()) != 0)) {
+                    BigDecimal amountForAccrual = chargeAmount;
+                    if (loanCharge.getAmountAccrued() != null) {
+                        amountForAccrual = chargeAmount.subtract(loanCharge.getAmountAccrued());
+                    }
+                    applicableCharges.put(loanCharge, amountForAccrual);
+                }
+            }
+
+            if (loanCharge.isPenalty()) {
+                dueDatePenaltyIncome = dueDatePenaltyIncome.add(chargeAmount);
+            } else {
+                dueDateFeeIncome = dueDateFeeIncome.add(chargeAmount);
+            }
+        }
+
+        if (dueDateFeeIncome.compareTo(BigDecimal.ZERO) == 0) {
+            dueDateFeeIncome = null;
+        }
+
+        if (dueDatePenaltyIncome.compareTo(BigDecimal.ZERO) == 0) {
+            dueDatePenaltyIncome = null;
+        }
+
+        accrualData.updateChargeDetails(applicableCharges, dueDateFeeIncome, dueDatePenaltyIncome);
+    }
+
+    private void updateInterestIncome(final LoanScheduleAccrualData accrualData,
+            final Collection<LoanTransactionData> loanWaiverTansactions, final Collection<LoanSchedulePeriodData> loanSchedulePeriodDatas,
+            final LocalDate tilldate) {
+
+        BigDecimal interestIncome = accrualData.getInterestIncome();
+        if (accrualData.getWaivedInterestIncome() != null) {
+            BigDecimal recognized = BigDecimal.ZERO;
+            BigDecimal unrecognized = BigDecimal.ZERO;
+            BigDecimal remainingAmt = BigDecimal.ZERO;
+            Collection<LoanTransactionData> loanTransactionDatas = new ArrayList<>();
+
+            for (LoanTransactionData loanTransactionData : loanWaiverTansactions) {
+                if (!loanTransactionData.dateOf().isAfter(accrualData.getFromDateAsLocaldate())
+                        || (loanTransactionData.dateOf().isAfter(accrualData.getFromDateAsLocaldate())
+                                && !loanTransactionData.dateOf().isAfter(accrualData.getDueDateAsLocaldate()) && !loanTransactionData
+                                .dateOf().isAfter(tilldate))) {
+                    loanTransactionDatas.add(loanTransactionData);
+                }
+            }
+
+            Iterator<LoanTransactionData> iterator = loanTransactionDatas.iterator();
+            for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) {
+                if (recognized.compareTo(BigDecimal.ZERO) != 1 && unrecognized.compareTo(BigDecimal.ZERO) != 1 && iterator.hasNext()) {
+                    LoanTransactionData loanTransactionData = iterator.next();
+                    recognized = recognized.add(loanTransactionData.getInterestPortion());
+                    unrecognized = unrecognized.add(loanTransactionData.getUnrecognizedIncomePortion());
+                }
+                if (loanSchedulePeriodData.periodDueDate().isBefore(accrualData.getDueDateAsLocaldate())) {
+                    remainingAmt = remainingAmt.add(loanSchedulePeriodData.interestWaived());
+                    if (recognized.compareTo(remainingAmt) == 1) {
+                        recognized = recognized.subtract(remainingAmt);
+                        remainingAmt = BigDecimal.ZERO;
+                    } else {
+                        remainingAmt = remainingAmt.subtract(recognized);
+                        recognized = BigDecimal.ZERO;
+                        if (unrecognized.compareTo(remainingAmt) >= 0) {
+                            unrecognized = unrecognized.subtract(remainingAmt);
+                            remainingAmt = BigDecimal.ZERO;
+                        } else if (iterator.hasNext()) {
+                            remainingAmt = remainingAmt.subtract(unrecognized);
+                            unrecognized = BigDecimal.ZERO;
+                        }
+                    }
+
+                }
+            }
+
+            BigDecimal interestWaived = accrualData.getWaivedInterestIncome();
+            if (interestWaived.compareTo(recognized) == 1) {
+                interestIncome = interestIncome.subtract(interestWaived.subtract(recognized));
+            }
+        }
+
+        accrualData.updateAccruableIncome(interestIncome);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformService.java
new file mode 100644
index 0000000..96ae831
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformService.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface LoanApplicationWritePlatformService {
+
+    CommandProcessingResult submitApplication(JsonCommand command);
+
+    CommandProcessingResult modifyApplication(Long loanId, JsonCommand command);
+
+    CommandProcessingResult deleteApplication(Long loanId);
+
+    CommandProcessingResult approveApplication(Long loanId, JsonCommand command);
+
+    CommandProcessingResult undoApplicationApproval(Long loanId, JsonCommand command);
+
+    CommandProcessingResult rejectApplication(Long loanId, JsonCommand command);
+
+    CommandProcessingResult applicantWithdrawsFromApplication(Long loanId, JsonCommand command);
+}
\ No newline at end of file
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
new file mode 100755
index 0000000..e859b0d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,1131 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+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.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationType;
+import org.apache.fineract.portfolio.account.domain.AccountAssociations;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.exception.CalendarNotFoundException;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.collateral.domain.LoanCollateral;
+import org.apache.fineract.portfolio.collateral.service.CollateralAssembler;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
+import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.fund.domain.Fund;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
+import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
+import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+
+@Service
+public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements LoanApplicationWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(LoanApplicationWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final FromJsonHelper fromJsonHelper;
+    private final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator;
+    private final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer;
+    private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
+    private final LoanRepository loanRepository;
+    private final NoteRepository noteRepository;
+    private final LoanScheduleCalculationPlatformService calculationPlatformService;
+    private final LoanAssembler loanAssembler;
+    private final ClientRepositoryWrapper clientRepository;
+    private final LoanProductRepository loanProductRepository;
+    private final LoanChargeAssembler loanChargeAssembler;
+    private final CollateralAssembler loanCollateralAssembler;
+    private final AprCalculator aprCalculator;
+    private final AccountNumberGenerator accountNumberGenerator;
+    private final LoanSummaryWrapper loanSummaryWrapper;
+    private final GroupRepositoryWrapper groupRepository;
+    private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
+    private final CalendarRepository calendarRepository;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final SavingsAccountAssembler savingsAccountAssembler;
+    private final AccountAssociationsRepository accountAssociationsRepository;
+    private final LoanReadPlatformService loanReadPlatformService;
+    private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
+    private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
+    private final BusinessEventNotifierService businessEventNotifierService;
+    private final ConfigurationDomainService configurationDomainService;
+    private final LoanScheduleAssembler loanScheduleAssembler;
+    private final LoanUtilService loanUtilService;
+
+    @Autowired
+    public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
+            final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator,
+            final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer,
+            final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer, final AprCalculator aprCalculator,
+            final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler,
+            final CollateralAssembler loanCollateralAssembler, final LoanRepository loanRepository, final NoteRepository noteRepository,
+            final LoanScheduleCalculationPlatformService calculationPlatformService, final ClientRepositoryWrapper clientRepository,
+            final LoanProductRepository loanProductRepository, final AccountNumberGenerator accountNumberGenerator,
+            final LoanSummaryWrapper loanSummaryWrapper, final GroupRepositoryWrapper groupRepository,
+            final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
+            final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository,
+            final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository,
+            final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
+            final LoanReadPlatformService loanReadPlatformService,
+            final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
+            final BusinessEventNotifierService businessEventNotifierService, final ConfigurationDomainService configurationDomainService,
+            final LoanScheduleAssembler loanScheduleAssembler, final LoanUtilService loanUtilService) {
+        this.context = context;
+        this.fromJsonHelper = fromJsonHelper;
+        this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.loanProductCommandFromApiJsonDeserializer = loanProductCommandFromApiJsonDeserializer;
+        this.aprCalculator = aprCalculator;
+        this.loanAssembler = loanAssembler;
+        this.loanChargeAssembler = loanChargeAssembler;
+        this.loanCollateralAssembler = loanCollateralAssembler;
+        this.loanRepository = loanRepository;
+        this.noteRepository = noteRepository;
+        this.calculationPlatformService = calculationPlatformService;
+        this.clientRepository = clientRepository;
+        this.loanProductRepository = loanProductRepository;
+        this.accountNumberGenerator = accountNumberGenerator;
+        this.loanSummaryWrapper = loanSummaryWrapper;
+        this.groupRepository = groupRepository;
+        this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
+        this.calendarRepository = calendarRepository;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.savingsAccountAssembler = savingsAccountAssembler;
+        this.accountAssociationsRepository = accountAssociationsRepository;
+        this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
+        this.loanReadPlatformService = loanReadPlatformService;
+        this.accountNumberFormatRepository = accountNumberFormatRepository;
+        this.businessEventNotifierService = businessEventNotifierService;
+        this.configurationDomainService = configurationDomainService;
+        this.loanScheduleAssembler = loanScheduleAssembler;
+        this.loanUtilService = loanUtilService;
+    }
+
+    private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
+        final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
+        return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult submitApplication(final JsonCommand command) {
+
+        try {
+            final AppUser currentUser = getAppUserIfPresent();
+            boolean isMeetingMandatoryForJLGLoans = configurationDomainService.isMeetingMandatoryForJLGLoans();
+            final Long productId = this.fromJsonHelper.extractLongNamed("productId", command.parsedJson());
+            final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
+            if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json(), isMeetingMandatoryForJLGLoans, loanProduct);
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+
+            if (loanProduct.useBorrowerCycle()) {
+                final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", command.parsedJson());
+                final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", command.parsedJson());
+                Integer cycleNumber = 0;
+                if (clientId != null) {
+                    cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, loanProduct.getId());
+                } else if (groupId != null) {
+                    cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue(),
+                            loanProduct.getId());
+                }
+                this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
+                        loanProduct, cycleNumber);
+            } else {
+                this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
+                        loanProduct);
+            }
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+            final Loan newLoanApplication = this.loanAssembler.assembleFrom(command, currentUser);
+
+            validateSubmittedOnDate(newLoanApplication);
+
+            final LoanProductRelatedDetail productRelatedDetail = newLoanApplication.repaymentScheduleDetail();
+
+            if (loanProduct.getLoanProductConfigurableAttributes() != null) {
+                updateProductRelatedDetails(productRelatedDetail, newLoanApplication);
+            }
+
+            this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(newLoanApplication.getTermFrequency(),
+                    newLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(),
+                    productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue(),
+                    newLoanApplication);
+
+            this.loanRepository.save(newLoanApplication);
+
+            if (loanProduct.isInterestRecalculationEnabled()) {
+                this.fromApiJsonDeserializer.validateLoanForInterestRecalculation(newLoanApplication);
+                createAndPersistCalendarInstanceForInterestRecalculation(newLoanApplication);
+            }
+
+            if (newLoanApplication.isAccountNumberRequiresAutoGeneration()) {
+                final AccountNumberFormat accountNumberFormat = this.accountNumberFormatRepository
+                        .findByAccountType(EntityAccountType.LOAN);
+                newLoanApplication.updateAccountNo(this.accountNumberGenerator.generate(newLoanApplication, accountNumberFormat));
+                this.loanRepository.save(newLoanApplication);
+            }
+
+            final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote");
+            if (StringUtils.isNotBlank(submittedOnNote)) {
+                final Note note = Note.loanNote(newLoanApplication, submittedOnNote);
+                this.noteRepository.save(note);
+            }
+
+            // Save calendar instance
+            final Long calendarId = command.longValueOfParameterNamed("calendarId");
+            Calendar calendar = null;
+
+            if (calendarId != null && calendarId != 0) {
+                calendar = this.calendarRepository.findOne(calendarId);
+                if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
+
+                final CalendarInstance calendarInstance = new CalendarInstance(calendar, newLoanApplication.getId(),
+                        CalendarEntityType.LOANS.getValue());
+                this.calendarInstanceRepository.save(calendarInstance);
+            }
+
+            // Save linked account information
+            final Long savingsAccountId = command.longValueOfParameterNamed("linkAccountId");
+            if (savingsAccountId != null) {
+                final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
+                this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication);
+                boolean isActive = true;
+                final AccountAssociations accountAssociations = AccountAssociations.associateSavingsAccount(newLoanApplication,
+                        savingsAccount, AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue(), isActive);
+                this.accountAssociationsRepository.save(accountAssociations);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(newLoanApplication.getId()) //
+                    .withOfficeId(newLoanApplication.getOfficeId()) //
+                    .withClientId(newLoanApplication.getClientId()) //
+                    .withGroupId(newLoanApplication.getGroupId()) //
+                    .withLoanId(newLoanApplication.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private void updateProductRelatedDetails(LoanProductRelatedDetail productRelatedDetail, Loan loan) {
+        final Boolean amortization = loan.loanProduct().getLoanProductConfigurableAttributes().getAmortizationBoolean();
+        final Boolean arrearsTolerance = loan.loanProduct().getLoanProductConfigurableAttributes().getArrearsToleranceBoolean();
+        final Boolean graceOnArrearsAging = loan.loanProduct().getLoanProductConfigurableAttributes().getGraceOnArrearsAgingBoolean();
+        final Boolean interestCalcPeriod = loan.loanProduct().getLoanProductConfigurableAttributes().getInterestCalcPeriodBoolean();
+        final Boolean interestMethod = loan.loanProduct().getLoanProductConfigurableAttributes().getInterestMethodBoolean();
+        final Boolean graceOnPrincipalAndInterestPayment = loan.loanProduct().getLoanProductConfigurableAttributes()
+                .getGraceOnPrincipalAndInterestPaymentBoolean();
+        final Boolean repaymentEvery = loan.loanProduct().getLoanProductConfigurableAttributes().getRepaymentEveryBoolean();
+        final Boolean transactionProcessingStrategy = loan.loanProduct().getLoanProductConfigurableAttributes()
+                .getTransactionProcessingStrategyBoolean();
+
+        if (!amortization) {
+            productRelatedDetail.setAmortizationMethod(loan.loanProduct().getLoanProductRelatedDetail().getAmortizationMethod());
+        }
+        if (!arrearsTolerance) {
+            productRelatedDetail.setInArrearsTolerance(loan.loanProduct().getLoanProductRelatedDetail().getArrearsTolerance());
+        }
+        if (!graceOnArrearsAging) {
+            productRelatedDetail.setGraceOnArrearsAgeing(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing());
+        }
+        if (!interestCalcPeriod) {
+            productRelatedDetail.setInterestCalculationPeriodMethod(loan.loanProduct().getLoanProductRelatedDetail()
+                    .getInterestCalculationPeriodMethod());
+        }
+        if (!interestMethod) {
+            productRelatedDetail.setInterestMethod(loan.loanProduct().getLoanProductRelatedDetail().getInterestMethod());
+        }
+        if (!graceOnPrincipalAndInterestPayment) {
+            productRelatedDetail.setGraceOnInterestPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnInterestPayment());
+            productRelatedDetail.setGraceOnPrincipalPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnPrincipalPayment());
+        }
+        if (!repaymentEvery) {
+            productRelatedDetail.setRepayEvery(loan.loanProduct().getLoanProductRelatedDetail().getRepayEvery());
+        }
+        if (!transactionProcessingStrategy) {
+            loan.updateTransactionProcessingStrategy(loan.loanProduct().getRepaymentStrategy());
+        }
+    }
+
+    private void createAndPersistCalendarInstanceForInterestRecalculation(final Loan loan) {
+
+        LocalDate calendarStartDate = loan.loanInterestRecalculationDetails().getRestFrequencyLocalDate();
+        if (calendarStartDate == null) {
+            calendarStartDate = loan.getExpectedDisbursedOnLocalDate();
+        }
+        final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
+        final RecalculationFrequencyType recalculationFrequencyType = loan.loanInterestRecalculationDetails().getRestFrequencyType();
+
+        Integer frequency = loan.loanInterestRecalculationDetails().getRestInterval();
+        CalendarEntityType calendarEntityType = CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL;
+        final String title = "loan_recalculation_detail_" + loan.loanInterestRecalculationDetails().getId();
+
+        createCalendar(loan, calendarStartDate, repeatsOnDay, recalculationFrequencyType, frequency, calendarEntityType, title);
+
+        if (loan.loanInterestRecalculationDetails().getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
+            LocalDate compoundingStartDate = loan.loanInterestRecalculationDetails().getCompoundingFrequencyLocalDate();
+            if (compoundingStartDate == null) {
+                compoundingStartDate = loan.getExpectedDisbursedOnLocalDate();
+            }
+            final Integer compoundingRepeatsOnDay = compoundingStartDate.getDayOfWeek();
+            final RecalculationFrequencyType recalculationCompoundingFrequencyType = loan.loanInterestRecalculationDetails()
+                    .getCompoundingFrequencyType();
+
+            Integer compoundingFrequency = loan.loanInterestRecalculationDetails().getCompoundingInterval();
+            CalendarEntityType compoundingCalendarEntityType = CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL;
+            final String compoundingCalendarTitle = "loan_recalculation_detail_compounding_frequency"
+                    + loan.loanInterestRecalculationDetails().getId();
+
+            createCalendar(loan, compoundingStartDate, compoundingRepeatsOnDay, recalculationCompoundingFrequencyType,
+                    compoundingFrequency, compoundingCalendarEntityType, compoundingCalendarTitle);
+        }
+
+    }
+
+    private void createCalendar(final Loan loan, LocalDate calendarStartDate, final Integer repeatsOnDay,
+            final RecalculationFrequencyType recalculationFrequencyType, Integer frequency, CalendarEntityType calendarEntityType,
+            final String title) {
+        CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID;
+        switch (recalculationFrequencyType) {
+            case DAILY:
+                calendarFrequencyType = CalendarFrequencyType.DAILY;
+            break;
+            case MONTHLY:
+                calendarFrequencyType = CalendarFrequencyType.MONTHLY;
+            break;
+            case SAME_AS_REPAYMENT_PERIOD:
+                frequency = loan.repaymentScheduleDetail().getRepayEvery();
+                calendarFrequencyType = CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType());
+                calendarStartDate = loan.getExpectedDisbursedOnLocalDate();
+            break;
+            case WEEKLY:
+                calendarFrequencyType = CalendarFrequencyType.WEEKLY;
+            break;
+            default:
+            break;
+        }
+
+        final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(),
+                calendarFrequencyType, frequency, repeatsOnDay);
+        final CalendarInstance calendarInstance = CalendarInstance.from(calendar, loan.loanInterestRecalculationDetails().getId(),
+                calendarEntityType.getValue());
+        this.calendarInstanceRepository.save(calendarInstance);
+    }
+
+    private void updateRestCalendarDetailsForInterestRecalculation(final CalendarInstance calendarInstance, final Loan loan) {
+
+        Calendar interestRecalculationRecurrings = calendarInstance.getCalendar();
+        LocalDate calendarStartDate = loan.loanInterestRecalculationDetails().getRestFrequencyLocalDate();
+        if (calendarStartDate == null) {
+            calendarStartDate = loan.getExpectedDisbursedOnLocalDate();
+        }
+        final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
+        final RecalculationFrequencyType recalculationFrequencyType = loan.loanInterestRecalculationDetails().getRestFrequencyType();
+
+        Integer frequency = loan.loanInterestRecalculationDetails().getRestInterval();
+
+        updateCalendar(loan, interestRecalculationRecurrings, calendarStartDate, repeatsOnDay, recalculationFrequencyType, frequency);
+
+    }
+
+    private void updateCompoundingCalendarDetailsForInterestRecalculation(final CalendarInstance calendarInstance, final Loan loan) {
+
+        Calendar interestRecalculationRecurrings = calendarInstance.getCalendar();
+        LocalDate calendarStartDate = loan.loanInterestRecalculationDetails().getCompoundingFrequencyLocalDate();
+        if (calendarStartDate == null) {
+            calendarStartDate = loan.getExpectedDisbursedOnLocalDate();
+        }
+        final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
+        final RecalculationFrequencyType recalculationFrequencyType = loan.loanInterestRecalculationDetails().getCompoundingFrequencyType();
+
+        Integer frequency = loan.loanInterestRecalculationDetails().getCompoundingInterval();
+
+        updateCalendar(loan, interestRecalculationRecurrings, calendarStartDate, repeatsOnDay, recalculationFrequencyType, frequency);
+
+    }
+
+    private void updateCalendar(final Loan loan, Calendar interestRecalculationRecurrings, LocalDate calendarStartDate,
+            final Integer repeatsOnDay, final RecalculationFrequencyType recalculationFrequencyType, Integer frequency) {
+        CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID;
+        switch (recalculationFrequencyType) {
+            case DAILY:
+                calendarFrequencyType = CalendarFrequencyType.DAILY;
+            break;
+
+            case MONTHLY:
+                calendarFrequencyType = CalendarFrequencyType.MONTHLY;
+            break;
+            case SAME_AS_REPAYMENT_PERIOD:
+                calendarStartDate = loan.getExpectedDisbursedOnLocalDate();
+                frequency = loan.repaymentScheduleDetail().getRepayEvery();
+                calendarFrequencyType = CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType());
+            break;
+            case WEEKLY:
+                calendarFrequencyType = CalendarFrequencyType.WEEKLY;
+            break;
+            default:
+            break;
+        }
+
+        interestRecalculationRecurrings.updateRepeatingCalendar(calendarStartDate, calendarFrequencyType, frequency, repeatsOnDay);
+        this.calendarRepository.save(interestRecalculationRecurrings);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult modifyApplication(final Long loanId, final JsonCommand command) {
+
+        try {
+            AppUser currentUser = getAppUserIfPresent();
+            final Loan existingLoanApplication = retrieveLoanBy(loanId);
+            if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(
+                    loanId); }
+
+            final String productIdParamName = "productId";
+            LoanProduct newLoanProduct = null;
+            if (command.isChangeInLongParameterNamed(productIdParamName, existingLoanApplication.loanProduct().getId())) {
+                final Long productId = command.longValueOfParameterNamed(productIdParamName);
+                newLoanProduct = this.loanProductRepository.findOne(productId);
+                if (newLoanProduct == null) { throw new LoanProductNotFoundException(productId); }
+            }
+
+            LoanProduct loanProductForValidations = newLoanProduct == null ? existingLoanApplication.loanProduct() : newLoanProduct;
+
+            this.fromApiJsonDeserializer.validateForModify(command.json(), loanProductForValidations, existingLoanApplication);
+
+            checkClientOrGroupActive(existingLoanApplication);
+
+            final Set<LoanCharge> existingCharges = existingLoanApplication.charges();
+            Map<Long, LoanChargeData> chargesMap = new HashMap<>();
+            for (LoanCharge charge : existingCharges) {
+                LoanChargeData chargeData = new LoanChargeData(charge.getId(), charge.getDueLocalDate(), charge.amountOrPercentage());
+                chargesMap.put(charge.getId(), chargeData);
+            }
+            Set<LoanDisbursementDetails> disbursementDetails = this.loanAssembler.fetchDisbursementData(command.parsedJson()
+                    .getAsJsonObject());
+
+            /**
+             * Stores all charges which are passed in during modify loan
+             * application
+             **/
+            final Set<LoanCharge> possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),
+                    disbursementDetails);
+            /** Boolean determines if any charge has been modified **/
+            boolean isChargeModified = false;
+
+            Set<Charge> newTrancheChages = this.loanChargeAssembler.getNewLoanTrancheCharges(command.parsedJson());
+            for (Charge charge : newTrancheChages) {
+                existingLoanApplication.addTrancheLoanCharge(charge);
+            }
+
+            /**
+             * If there are any charges already present, which are now not
+             * passed in as a part of the request, deem the charges as modified
+             **/
+            if (!possiblyModifedLoanCharges.isEmpty()) {
+                if (!possiblyModifedLoanCharges.containsAll(existingCharges)) {
+                    isChargeModified = true;
+                }
+            }
+
+            /**
+             * If any new charges are added or values of existing charges are
+             * modified
+             **/
+            for (LoanCharge loanCharge : possiblyModifedLoanCharges) {
+                if (loanCharge.getId() == null) {
+                    isChargeModified = true;
+                } else {
+                    LoanChargeData chargeData = chargesMap.get(loanCharge.getId());
+                    if (loanCharge.amountOrPercentage().compareTo(chargeData.amountOrPercentage()) != 0
+                            || (loanCharge.isSpecifiedDueDate() && !loanCharge.getDueLocalDate().equals(chargeData.getDueDate()))) {
+                        isChargeModified = true;
+                    }
+                }
+            }
+
+            final Set<LoanCollateral> possiblyModifedLoanCollateralItems = this.loanCollateralAssembler
+                    .fromParsedJson(command.parsedJson());
+
+            final Map<String, Object> changes = existingLoanApplication.loanApplicationModification(command, possiblyModifedLoanCharges,
+                    possiblyModifedLoanCollateralItems, this.aprCalculator, isChargeModified);
+
+            if (changes.containsKey("expectedDisbursementDate")) {
+                this.loanAssembler.validateExpectedDisbursementForHolidayAndNonWorkingDay(existingLoanApplication);
+            }
+
+            final String clientIdParamName = "clientId";
+            if (changes.containsKey(clientIdParamName)) {
+                final Long clientId = command.longValueOfParameterNamed(clientIdParamName);
+                final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+                if (client.isNotActive()) { throw new ClientNotActiveException(clientId); }
+
+                existingLoanApplication.updateClient(client);
+            }
+
+            final String groupIdParamName = "groupId";
+            if (changes.containsKey(groupIdParamName)) {
+                final Long groupId = command.longValueOfParameterNamed(groupIdParamName);
+                final Group group = this.groupRepository.findOneWithNotFoundDetection(groupId);
+                if (group.isNotActive()) { throw new GroupNotActiveException(groupId); }
+
+                existingLoanApplication.updateGroup(group);
+            }
+
+            if (newLoanProduct != null) {
+                existingLoanApplication.updateLoanProduct(newLoanProduct);
+                if (!changes.containsKey("interestRateFrequencyType")) {
+                    existingLoanApplication.updateInterestRateFrequencyType();
+                }
+                final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+                if (newLoanProduct.useBorrowerCycle()) {
+                    final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", command.parsedJson());
+                    final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", command.parsedJson());
+                    Integer cycleNumber = 0;
+                    if (clientId != null) {
+                        cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, newLoanProduct.getId());
+                    } else if (groupId != null) {
+                        cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue(),
+                                newLoanProduct.getId());
+                    }
+                    this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
+                            newLoanProduct, cycleNumber);
+                } else {
+                    this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
+                            newLoanProduct);
+                }
+                if (newLoanProduct.isLinkedToFloatingInterestRate()) {
+                    existingLoanApplication.getLoanProductRelatedDetail().updateForFloatingInterestRates();
+                } else {
+                    existingLoanApplication.setInterestRateDifferential(null);
+                    existingLoanApplication.setIsFloatingInterestRate(null);
+                }
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+
+            existingLoanApplication.updateIsInterestRecalculationEnabled();
+            validateSubmittedOnDate(existingLoanApplication);
+
+            final LoanProductRelatedDetail productRelatedDetail = existingLoanApplication.repaymentScheduleDetail();
+            if (existingLoanApplication.loanProduct().getLoanProductConfigurableAttributes() != null) {
+                updateProductRelatedDetails(productRelatedDetail, existingLoanApplication);
+            }
+
+            final String fundIdParamName = "fundId";
+            if (changes.containsKey(fundIdParamName)) {
+                final Long fundId = command.longValueOfParameterNamed(fundIdParamName);
+                final Fund fund = this.loanAssembler.findFundByIdIfProvided(fundId);
+
+                existingLoanApplication.updateFund(fund);
+            }
+
+            final String loanPurposeIdParamName = "loanPurposeId";
+            if (changes.containsKey(loanPurposeIdParamName)) {
+                final Long loanPurposeId = command.longValueOfParameterNamed(loanPurposeIdParamName);
+                final CodeValue loanPurpose = this.loanAssembler.findCodeValueByIdIfProvided(loanPurposeId);
+                existingLoanApplication.updateLoanPurpose(loanPurpose);
+            }
+
+            final String loanOfficerIdParamName = "loanOfficerId";
+            if (changes.containsKey(loanOfficerIdParamName)) {
+                final Long loanOfficerId = command.longValueOfParameterNamed(loanOfficerIdParamName);
+                final Staff newValue = this.loanAssembler.findLoanOfficerByIdIfProvided(loanOfficerId);
+                existingLoanApplication.updateLoanOfficerOnLoanApplication(newValue);
+            }
+
+            final String strategyIdParamName = "transactionProcessingStrategyId";
+            if (changes.containsKey(strategyIdParamName)) {
+                final Long strategyId = command.longValueOfParameterNamed(strategyIdParamName);
+                final LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(strategyId);
+
+                existingLoanApplication.updateTransactionProcessingStrategy(strategy);
+            }
+
+            final String collateralParamName = "collateral";
+            if (changes.containsKey(collateralParamName)) {
+                final Set<LoanCollateral> loanCollateral = this.loanCollateralAssembler.fromParsedJson(command.parsedJson());
+                existingLoanApplication.updateLoanCollateral(loanCollateral);
+            }
+
+            final String chargesParamName = "charges";
+            if (changes.containsKey(chargesParamName)) {
+                existingLoanApplication.updateLoanCharges(possiblyModifedLoanCharges);
+            }
+
+            if (changes.containsKey("recalculateLoanSchedule")) {
+                changes.remove("recalculateLoanSchedule");
+
+                final JsonElement parsedQuery = this.fromJsonHelper.parse(command.json());
+                final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper);
+
+                final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false);
+                existingLoanApplication.updateLoanSchedule(loanSchedule, currentUser);
+                existingLoanApplication.recalculateAllCharges();
+            }
+
+            this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(existingLoanApplication.getTermFrequency(),
+                    existingLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(),
+                    productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue(),
+                    existingLoanApplication);
+
+            saveAndFlushLoanWithDataIntegrityViolationChecks(existingLoanApplication);
+
+            final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote");
+            if (StringUtils.isNotBlank(submittedOnNote)) {
+                final Note note = Note.loanNote(existingLoanApplication, submittedOnNote);
+                this.noteRepository.save(note);
+            }
+
+            final Long calendarId = command.longValueOfParameterNamed("calendarId");
+            Calendar calendar = null;
+            if (calendarId != null && calendarId != 0) {
+                calendar = this.calendarRepository.findOne(calendarId);
+                if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
+            }
+
+            final List<CalendarInstance> ciList = (List<CalendarInstance>) this.calendarInstanceRepository.findByEntityIdAndEntityTypeId(
+                    loanId, CalendarEntityType.LOANS.getValue());
+            if (calendar != null) {
+
+                // For loans, allow to attach only one calendar instance per
+                // loan
+                if (ciList != null && !ciList.isEmpty()) {
+                    final CalendarInstance calendarInstance = ciList.get(0);
+                    if (calendarInstance.getCalendar().getId() != calendar.getId()) {
+                        calendarInstance.updateCalendar(calendar);
+                        this.calendarInstanceRepository.saveAndFlush(calendarInstance);
+                    }
+                } else {
+                    // attaching new calendar
+                    final CalendarInstance calendarInstance = new CalendarInstance(calendar, existingLoanApplication.getId(),
+                            CalendarEntityType.LOANS.getValue());
+                    this.calendarInstanceRepository.save(calendarInstance);
+                }
+
+            } else if (ciList != null && !ciList.isEmpty()) {
+                final CalendarInstance calendarInstance = ciList.get(0);
+                this.calendarInstanceRepository.delete(calendarInstance);
+            }
+
+            // Save linked account information
+            final String linkAccountIdParamName = "linkAccountId";
+            final Long savingsAccountId = command.longValueOfParameterNamed(linkAccountIdParamName);
+            AccountAssociations accountAssociations = this.accountAssociationsRepository.findByLoanIdAndType(loanId,
+                    AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
+            boolean isLinkedAccPresent = false;
+            if (savingsAccountId == null) {
+                if (accountAssociations != null) {
+                    if (this.fromJsonHelper.parameterExists(linkAccountIdParamName, command.parsedJson())) {
+                        this.accountAssociationsRepository.delete(accountAssociations);
+                        changes.put(linkAccountIdParamName, null);
+                    } else {
+                        isLinkedAccPresent = true;
+                    }
+                }
+            } else {
+                isLinkedAccPresent = true;
+                boolean isModified = false;
+                if (accountAssociations == null) {
+                    isModified = true;
+                } else {
+                    final SavingsAccount savingsAccount = accountAssociations.linkedSavingsAccount();
+                    if (savingsAccount == null || !savingsAccount.getId().equals(savingsAccountId)) {
+                        isModified = true;
+                    }
+                }
+                if (isModified) {
+                    final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
+                    this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, existingLoanApplication);
+                    if (accountAssociations == null) {
+                        boolean isActive = true;
+                        accountAssociations = AccountAssociations.associateSavingsAccount(existingLoanApplication, savingsAccount,
+                                AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue(), isActive);
+                    } else {
+                        accountAssociations.updateLinkedSavingsAccount(savingsAccount);
+                    }
+                    changes.put(linkAccountIdParamName, savingsAccountId);
+                    this.accountAssociationsRepository.save(accountAssociations);
+                }
+            }
+
+            if (!isLinkedAccPresent) {
+                final Set<LoanCharge> charges = existingLoanApplication.charges();
+                for (final LoanCharge loanCharge : charges) {
+                    if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
+                        final String errorMessage = "one of the charges requires linked savings account for payment";
+                        throw new LinkedAccountRequiredException("loanCharge", errorMessage);
+                    }
+                }
+            }
+
+            // updating loan interest recalculation details throwing null
+            // pointer exception after saveAndFlush
+            // http://stackoverflow.com/questions/17151757/hibernate-cascade-update-gives-null-pointer/17334374#17334374
+            this.loanRepository.save(existingLoanApplication);
+
+            if (productRelatedDetail.isInterestRecalculationEnabled()) {
+                this.fromApiJsonDeserializer.validateLoanForInterestRecalculation(existingLoanApplication);
+                if (changes.containsKey(LoanProductConstants.isInterestRecalculationEnabledParameterName)) {
+                    createAndPersistCalendarInstanceForInterestRecalculation(existingLoanApplication);
+                } else {
+                    if (changes.containsKey(LoanProductConstants.recalculationRestFrequencyDateParamName)) {
+
+                        CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
+                                existingLoanApplication.loanInterestRecalculationDetailId(),
+                                CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue(), CalendarType.COLLECTION.getValue());
+                        updateRestCalendarDetailsForInterestRecalculation(calendarInstance, existingLoanApplication);
+                    }
+                    if (changes.containsKey(LoanProductConstants.recalculationCompoundingFrequencyDateParamName)) {
+                        CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
+                                existingLoanApplication.loanInterestRecalculationDetailId(),
+                                CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue(), CalendarType.COLLECTION.getValue());
+                        updateCompoundingCalendarDetailsForInterestRecalculation(calendarInstance, existingLoanApplication);
+                    }
+                }
+
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(loanId) //
+                    .withOfficeId(existingLoanApplication.getOfficeId()) //
+                    .withClientId(existingLoanApplication.getClientId()) //
+                    .withGroupId(existingLoanApplication.getGroupId()) //
+                    .withLoanId(existingLoanApplication.getId()) //
+                    .with(changes).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    /*
+     * 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("loan_account_no_UNIQUE")) {
+
+            final String accountNo = command.stringValueOfParameterNamed("accountNo");
+            throw new PlatformDataIntegrityException("error.msg.loan.duplicate.accountNo", "Loan with accountNo `" + accountNo
+                    + "` already exists", "accountNo", accountNo);
+        } else if (realCause.getMessage().contains("loan_externalid_UNIQUE")) {
+
+            final String externalId = command.stringValueOfParameterNamed("externalId");
+            throw new PlatformDataIntegrityException("error.msg.loan.duplicate.externalId", "Loan with externalId `" + externalId
+                    + "` already exists", "externalId", externalId);
+        }
+
+        logAsErrorUnexpectedDataIntegrityException(dve);
+        throw new PlatformDataIntegrityException("error.msg.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 deleteApplication(final Long loanId) {
+
+        final Loan loan = retrieveLoanBy(loanId);
+        checkClientOrGroupActive(loan);
+
+        if (loan.isNotSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted(loanId); }
+
+        final List<Note> relatedNotes = this.noteRepository.findByLoanId(loan.getId());
+        this.noteRepository.deleteInBatch(relatedNotes);
+
+        this.loanRepository.delete(loanId);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(loanId) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loan.getId()) //
+                .build();
+    }
+
+    public void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate) {
+        final String json = command.json();
+        final JsonElement element = this.fromJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+        final BigDecimal principal = this.fromJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element);
+        fromApiJsonDeserializer.validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult approveApplication(final Long loanId, final JsonCommand command) {
+
+        final AppUser currentUser = getAppUserIfPresent();
+        LocalDate expectedDisbursementDate = null;
+
+        this.loanApplicationTransitionApiJsonValidator.validateApproval(command.json());
+
+        final Loan loan = retrieveLoanBy(loanId);
+
+        final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
+
+        expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName);
+        if (expectedDisbursementDate == null) {
+            expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
+        }
+        if (loan.loanProduct().isMultiDisburseLoan()) {
+            this.validateMultiDisbursementData(command, expectedDisbursementDate);
+        }
+
+        checkClientOrGroupActive(loan);
+
+        // validate expected disbursement date against meeting date
+        if (loan.isSyncDisbursementWithMeeting() && (loan.isGroupLoan() || loan.isJLGLoan())) {
+            final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
+                    CalendarEntityType.LOANS.getValue());
+            final Calendar calendar = calendarInstance.getCalendar();
+            this.loanScheduleAssembler.validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar);
+        }
+
+        final Map<String, Object> changes = loan.loanApplicationApproval(currentUser, command, disbursementDataArray,
+                defaultLoanLifecycleStateMachine());
+
+        if (!changes.isEmpty()) {
+
+            // If loan approved amount less than loan demanded amount, then need
+            // to recompute the schedule
+            if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName) || changes.containsKey("recalculateLoanSchedule")
+                    || changes.containsKey("expectedDisbursementDate")) {
+                LocalDate recalculateFrom = null;
+                ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+                loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
+            }
+
+            saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.loanNote(loan, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_APPROVED,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loan.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult undoApplicationApproval(final Long loanId, final JsonCommand command) {
+
+        AppUser currentUser = getAppUserIfPresent();
+
+        this.fromApiJsonDeserializer.validateForUndo(command.json());
+
+        final Loan loan = retrieveLoanBy(loanId);
+        checkClientOrGroupActive(loan);
+
+        final Map<String, Object> changes = loan.undoApproval(defaultLoanLifecycleStateMachine());
+        if (!changes.isEmpty()) {
+
+            // If loan approved amount is not same as loan amount demanded, then
+            // during undo, restore the demand amount to principal amount.
+
+            if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName)
+                    || changes.containsKey(LoanApiConstants.disbursementPrincipalParameterName)) {
+                LocalDate recalculateFrom = null;
+                ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+                loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
+            }
+
+            saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.loanNote(loan, noteText);
+                this.noteRepository.save(note);
+            }
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_APPROVAL,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loan.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult rejectApplication(final Long loanId, final JsonCommand command) {
+
+        final AppUser currentUser = getAppUserIfPresent();
+
+        this.loanApplicationTransitionApiJsonValidator.validateRejection(command.json());
+
+        final Loan loan = retrieveLoanBy(loanId);
+        checkClientOrGroupActive(loan);
+
+        final Map<String, Object> changes = loan.loanApplicationRejection(currentUser, command, defaultLoanLifecycleStateMachine());
+        if (!changes.isEmpty()) {
+            this.loanRepository.save(loan);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.loanNote(loan, noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loan.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult applicantWithdrawsFromApplication(final Long loanId, final JsonCommand command) {
+
+        final AppUser currentUser = getAppUserIfPresent();
+
+        this.loanApplicationTransitionApiJsonValidator.validateApplicantWithdrawal(command.json());
+
+        final Loan loan = retrieveLoanBy(loanId);
+        checkClientOrGroupActive(loan);
+
+        final Map<String, Object> changes = loan.loanApplicationWithdrawnByApplicant(currentUser, command,
+                defaultLoanLifecycleStateMachine());
+        if (!changes.isEmpty()) {
+            this.loanRepository.save(loan);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.loanNote(loan, noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loan.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    private Loan retrieveLoanBy(final Long loanId) {
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+        loan.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
+        return loan;
+    }
+
+    private void validateSubmittedOnDate(final Loan loan) {
+        final LocalDate startDate = loan.loanProduct().getStartDate();
+        final LocalDate closeDate = loan.loanProduct().getCloseDate();
+        final LocalDate expectedFirstRepaymentOnDate = loan.getExpectedFirstRepaymentOnDate();
+        final LocalDate submittedOnDate = loan.getSubmittedOnDate();
+
+        String defaultUserMessage = "";
+        if (startDate != null && submittedOnDate.isBefore(startDate)) {
+            defaultUserMessage = "submittedOnDate cannot be before the loan product startDate.";
+            throw new LoanApplicationDateException("submitted.on.date.cannot.be.before.the.loan.product.start.date", defaultUserMessage,
+                    submittedOnDate.toString(), startDate.toString());
+        }
+
+        if (closeDate != null && submittedOnDate.isAfter(closeDate)) {
+            defaultUserMessage = "submittedOnDate cannot be after the loan product closeDate.";
+            throw new LoanApplicationDateException("submitted.on.date.cannot.be.after.the.loan.product.close.date", defaultUserMessage,
+                    submittedOnDate.toString(), closeDate.toString());
+        }
+
+        if (expectedFirstRepaymentOnDate != null && submittedOnDate.isAfter(expectedFirstRepaymentOnDate)) {
+            defaultUserMessage = "submittedOnDate cannot be after the loans  expectedFirstRepaymentOnDate.";
+            throw new LoanApplicationDateException("submitted.on.date.cannot.be.after.the.loan.expected.first.repayment.date",
+                    defaultUserMessage, submittedOnDate.toString(), expectedFirstRepaymentOnDate.toString());
+        }
+    }
+
+    private void checkClientOrGroupActive(final Loan loan) {
+        final Client client = loan.client();
+        if (client != null) {
+            if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); }
+        }
+        final Group group = loan.group();
+        if (group != null) {
+            if (group.isNotActive()) { throw new GroupNotActiveException(group.getId()); }
+        }
+    }
+
+    private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
+        try {
+            List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
+            for (LoanRepaymentScheduleInstallment installment : installments) {
+                if (installment.getId() == null) {
+                    this.repaymentScheduleInstallmentRepository.save(installment);
+                }
+            }
+            this.loanRepository.saveAndFlush(loan);
+        } catch (final DataIntegrityViolationException e) {
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.application");
+            if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
+                baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
+            }
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                    "Validation errors exist.", dataValidationErrors); }
+        }
+    }
+
+    private AppUser getAppUserIfPresent() {
+        AppUser user = null;
+        if (this.context != null) {
+            user = this.context.getAuthenticatedUserIfPresent();
+        }
+        return user;
+    }
+
+    private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
+        Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
+        map.put(entityEvent, entity);
+        return map;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingService.java
new file mode 100755
index 0000000..b6becf5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+public interface LoanArrearsAgingService {
+
+    void updateLoanArrearsAgeingDetails();
+
+    void updateLoanArrearsAgeingDetailsWithOriginalSchedule(Loan loan);
+
+    void updateLoanArrearsAgeingDetails(Loan loan);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java
new file mode 100755
index 0000000..d006cd5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java
@@ -0,0 +1,518 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+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.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
+import org.apache.fineract.portfolio.common.service.BusinessEventListner;
+import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.apache.fineract.scheduledjobs.service.ScheduledJobRunnerServiceImpl;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService, BusinessEventListner {
+
+    private final static Logger logger = LoggerFactory.getLogger(ScheduledJobRunnerServiceImpl.class);
+    private final BusinessEventNotifierService businessEventNotifierService;
+    private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public LoanArrearsAgingServiceImpl(final RoutingDataSource dataSource, final BusinessEventNotifierService businessEventNotifierService) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.businessEventNotifierService = businessEventNotifierService;
+    }
+
+    @PostConstruct
+    public void registerForNotification() {
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_REFUND, this);
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_ADJUST_TRANSACTION, this);
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, this);
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF, this);
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_WAIVE_INTEREST, this);
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_ADD_CHARGE, this);
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_WAIVE_CHARGE, this);
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT, this);
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE, this);
+        this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_DISBURSAL, new DisbursementEventListner());
+    }
+
+    @Transactional
+    @Override
+    @CronTarget(jobName = JobName.UPDATE_LOAN_ARREARS_AGEING)
+    public void updateLoanArrearsAgeingDetails() {
+
+        this.jdbcTemplate.execute("truncate table m_loan_arrears_aging");
+
+        final StringBuilder updateSqlBuilder = new StringBuilder(900);
+
+        updateSqlBuilder
+                .append("INSERT INTO m_loan_arrears_aging(`loan_id`,`principal_overdue_derived`,`interest_overdue_derived`,`fee_charges_overdue_derived`,`penalty_charges_overdue_derived`,`total_overdue_derived`,`overdue_since_date_derived`)");
+        updateSqlBuilder.append("select ml.id as loanId,");
+        updateSqlBuilder
+                .append("SUM((ifnull(mr.principal_amount,0) - ifnull(mr.principal_completed_derived, 0))) as principal_overdue_derived,");
+        updateSqlBuilder
+                .append("SUM((ifnull(mr.interest_amount,0)  - ifnull(mr.interest_completed_derived, 0))) as interest_overdue_derived,");
+        updateSqlBuilder
+                .append("SUM((ifnull(mr.fee_charges_amount,0)  - ifnull(mr.fee_charges_completed_derived, 0))) as fee_charges_overdue_derived,");
+        updateSqlBuilder
+                .append("SUM((ifnull(mr.penalty_charges_amount,0)  - ifnull(mr.penalty_charges_completed_derived, 0))) as penalty_charges_overdue_derived,");
+        updateSqlBuilder.append("SUM((ifnull(mr.principal_amount,0) - ifnull(mr.principal_completed_derived, 0))) +");
+        updateSqlBuilder.append("SUM((ifnull(mr.interest_amount,0)  - ifnull(mr.interest_completed_derived, 0))) +");
+        updateSqlBuilder.append("SUM((ifnull(mr.fee_charges_amount,0)  - ifnull(mr.fee_charges_completed_derived, 0))) +");
+        updateSqlBuilder
+                .append("SUM((ifnull(mr.penalty_charges_amount,0)  - ifnull(mr.penalty_charges_completed_derived, 0))) as total_overdue_derived,");
+        updateSqlBuilder.append("MIN(mr.duedate) as overdue_since_date_derived ");
+        updateSqlBuilder.append(" FROM m_loan ml ");
+        updateSqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
+        updateSqlBuilder.append(" left join m_product_loan_recalculation_details prd on prd.product_id = ml.product_id ");
+        updateSqlBuilder.append(" WHERE ml.loan_status_id = 300 "); // active
+        updateSqlBuilder.append(" and mr.completed_derived is false ");
+        updateSqlBuilder.append(" and mr.duedate < SUBDATE(CURDATE(),INTERVAL  ifnull(ml.grace_on_arrears_ageing,0) day) ");
+        updateSqlBuilder.append(" and (prd.arrears_based_on_original_schedule = 0 or prd.arrears_based_on_original_schedule is null) ");
+        updateSqlBuilder.append(" GROUP BY ml.id");
+
+        List<String> insertStatements = updateLoanArrearsAgeingDetailsWithOriginalSchedule();
+        insertStatements.add(0, updateSqlBuilder.toString());
+        final int[] results = this.jdbcTemplate.batchUpdate(insertStatements.toArray(new String[0]));
+        int result = 0;
+        for (int i : results) {
+            result += i;
+        }
+
+        logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result);
+    }
+
+    @Override
+    public void updateLoanArrearsAgeingDetailsWithOriginalSchedule(final Loan loan) {
+        int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?",
+                Integer.class, loan.getId());
+        List<String> updateStatement = new ArrayList<>();
+        OriginalScheduleExtractor originalScheduleExtractor = new OriginalScheduleExtractor(loan.getId().toString());
+        Map<Long, List<LoanSchedulePeriodData>> scheduleDate = this.jdbcTemplate.query(originalScheduleExtractor.schema,
+                originalScheduleExtractor);
+        if (scheduleDate.size() > 0) {
+            List<Map<String, Object>> transactions = getLoanSummary(loan.getId(), loan.getLoanSummary());
+            updateSchheduleWithPaidDetail(scheduleDate, transactions);
+            createInsertStatements(updateStatement, scheduleDate, count == 0);
+            if (updateStatement.size() == 1) {
+                this.jdbcTemplate.update(updateStatement.get(0));
+            } else {
+                String deletestatement = "DELETE FROM `m_loan_arrears_aging` WHERE  `loan_id`=" + loan.getId();
+                this.jdbcTemplate.update(deletestatement);
+            }
+        }
+    }
+
+    @Override
+    public void updateLoanArrearsAgeingDetails(final Loan loan) {
+        int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?",
+                Integer.class, loan.getId());
+        String updateStatement = constructUpdateStatement(loan, count == 0);
+        if (updateStatement == null) {
+            String deletestatement = "DELETE FROM `m_loan_arrears_aging` WHERE  `loan_id`=" + loan.getId();
+            this.jdbcTemplate.update(deletestatement);
+        } else {
+            this.jdbcTemplate.update(updateStatement);
+        }
+    }
+
+    private String constructUpdateStatement(final Loan loan, boolean isInsertStatement) {
+        String updateSql = null;
+        List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
+        BigDecimal principalOverdue = BigDecimal.ZERO;
+        BigDecimal interestOverdue = BigDecimal.ZERO;
+        BigDecimal feeOverdue = BigDecimal.ZERO;
+        BigDecimal penaltyOverdue = BigDecimal.ZERO;
+        LocalDate overDueSince = LocalDate.now();
+        for (LoanRepaymentScheduleInstallment installment : installments) {
+            if (installment.getDueDate().isBefore(LocalDate.now())) {
+                principalOverdue = principalOverdue.add(installment.getPrincipalOutstanding(loan.getCurrency()).getAmount());
+                interestOverdue = interestOverdue.add(installment.getInterestOutstanding(loan.getCurrency()).getAmount());
+                feeOverdue = feeOverdue.add(installment.getFeeChargesOutstanding(loan.getCurrency()).getAmount());
+                penaltyOverdue = penaltyOverdue.add(installment.getPenaltyChargesOutstanding(loan.getCurrency()).getAmount());
+                if (installment.isNotFullyPaidOff() && overDueSince.isAfter(installment.getDueDate())) {
+                    overDueSince = installment.getDueDate();
+                }
+            }
+        }
+
+        BigDecimal totalOverDue = principalOverdue.add(interestOverdue).add(feeOverdue).add(penaltyOverdue);
+        if (totalOverDue.compareTo(BigDecimal.ZERO) == 1) {
+            if (isInsertStatement) {
+                updateSql = constructInsertStatement(loan.getId(), principalOverdue, interestOverdue, feeOverdue, penaltyOverdue,
+                        overDueSince);
+            } else {
+                updateSql = constructUpdateStatement(loan.getId(), principalOverdue, interestOverdue, feeOverdue, penaltyOverdue,
+                        overDueSince);
+            }
+        }
+        return updateSql;
+    }
+
+    private List<String> updateLoanArrearsAgeingDetailsWithOriginalSchedule() {
+        List<String> insertStatement = new ArrayList<>();
+
+        final StringBuilder loanIdentifier = new StringBuilder();
+        loanIdentifier.append("select ml.id as loanId FROM m_loan ml  ");
+        loanIdentifier.append("INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
+        loanIdentifier
+                .append("inner join m_product_loan_recalculation_details prd on prd.product_id = ml.product_id and prd.arrears_based_on_original_schedule = 1  ");
+        loanIdentifier
+                .append("WHERE ml.loan_status_id = 300  and mr.completed_derived is false  and mr.duedate < SUBDATE(CURDATE(),INTERVAL  ifnull(ml.grace_on_arrears_ageing,0) day) group by ml.id");
+        List<Long> loanIds = this.jdbcTemplate.queryForList(loanIdentifier.toString(), Long.class);
+        if (!loanIds.isEmpty()) {
+            String loanIdsAsString = loanIds.toString();
+            loanIdsAsString = loanIdsAsString.substring(1, loanIdsAsString.length() - 1);
+            OriginalScheduleExtractor originalScheduleExtractor = new OriginalScheduleExtractor(loanIdsAsString);
+            Map<Long, List<LoanSchedulePeriodData>> scheduleDate = this.jdbcTemplate.query(originalScheduleExtractor.schema,
+                    originalScheduleExtractor);
+
+            List<Map<String, Object>> loanSummary = getLoanSummary(loanIdsAsString);
+            updateSchheduleWithPaidDetail(scheduleDate, loanSummary);
+            createInsertStatements(insertStatement, scheduleDate, true);
+        }
+
+        return insertStatement;
+
+    }
+
+    private List<Map<String, Object>> getLoanSummary(final String loanIdsAsString) {
+        final StringBuilder transactionsSql = new StringBuilder();
+        transactionsSql.append("select ml.id as loanId, ");
+        transactionsSql
+                .append("ml.principal_repaid_derived as principalAmtPaid, ml.principal_writtenoff_derived as  principalAmtWrittenoff, ");
+        transactionsSql.append(" ml.interest_repaid_derived as interestAmtPaid, ml.interest_waived_derived as interestAmtWaived, ");
+        transactionsSql.append("ml.fee_charges_repaid_derived as feeAmtPaid, ml.fee_charges_waived_derived as feeAmtWaived, ");
+        transactionsSql
+                .append("ml.penalty_charges_repaid_derived as penaltyAmtPaid, ml.penalty_charges_waived_derived as penaltyAmtWaived ");
+        transactionsSql.append("from m_loan ml ");
+        transactionsSql.append("where ml.id IN (").append(loanIdsAsString).append(") order by ml.id");
+
+        List<Map<String, Object>> loanSummary = this.jdbcTemplate.queryForList(transactionsSql.toString());
+        return loanSummary;
+    }
+
+    private List<Map<String, Object>> getLoanSummary(final Long loanId, final LoanSummary loanSummary) {
+        List<Map<String, Object>> transactionDetail = new ArrayList<>();
+        Map<String, Object> transactionMap = new HashMap<>();
+
+        transactionMap.put("loanId", loanId);
+        transactionMap.put("principalAmtPaid", loanSummary.getTotalPrincipalRepaid());
+        transactionMap.put("principalAmtWrittenoff", loanSummary.getTotalPrincipalWrittenOff());
+        transactionMap.put("interestAmtPaid", loanSummary.getTotalInterestRepaid());
+        transactionMap.put("interestAmtWaived", loanSummary.getTotalInterestWaived());
+        transactionMap.put("feeAmtPaid", loanSummary.getTotalFeeChargesRepaid());
+        transactionMap.put("feeAmtWaived", loanSummary.getTotalFeeChargesWaived());
+        transactionMap.put("penaltyAmtPaid", loanSummary.getTotalPenaltyChargesRepaid());
+        transactionMap.put("penaltyAmtWaived", loanSummary.getTotalPenaltyChargesWaived());
+        transactionDetail.add(transactionMap);
+        return transactionDetail;
+
+    }
+
+    private void createInsertStatements(List<String> insertStatement, Map<Long, List<LoanSchedulePeriodData>> scheduleDate,
+            boolean isInsertStatement) {
+        for (Map.Entry<Long, List<LoanSchedulePeriodData>> entry : scheduleDate.entrySet()) {
+            final Long loanId = entry.getKey();
+            BigDecimal principalOverdue = BigDecimal.ZERO;
+            BigDecimal interestOverdue = BigDecimal.ZERO;
+            BigDecimal feeOverdue = BigDecimal.ZERO;
+            BigDecimal penaltyOverdue = BigDecimal.ZERO;
+            LocalDate overDueSince = LocalDate.now();
+
+            for (LoanSchedulePeriodData loanSchedulePeriodData : entry.getValue()) {
+                if (!loanSchedulePeriodData.getComplete()) {
+                    principalOverdue = principalOverdue.add(loanSchedulePeriodData.principalDue().subtract(
+                            loanSchedulePeriodData.principalPaid()));
+                    interestOverdue = interestOverdue.add(loanSchedulePeriodData.interestDue().subtract(
+                            loanSchedulePeriodData.interestPaid()));
+                    feeOverdue = feeOverdue.add(loanSchedulePeriodData.feeChargesDue().subtract(loanSchedulePeriodData.feeChargesPaid()));
+                    penaltyOverdue = penaltyOverdue.add(loanSchedulePeriodData.penaltyChargesDue().subtract(
+                            loanSchedulePeriodData.penaltyChargesPaid()));
+                    if (overDueSince.isAfter(loanSchedulePeriodData.periodDueDate())
+                            && loanSchedulePeriodData.principalDue().subtract(loanSchedulePeriodData.principalPaid())
+                                    .compareTo(BigDecimal.ZERO) == 1) {
+                        overDueSince = loanSchedulePeriodData.periodDueDate();
+                    }
+                }
+            }
+            if (principalOverdue.compareTo(BigDecimal.ZERO) == 1) {
+                String sqlStatement = null;
+                if (isInsertStatement) {
+                    sqlStatement = constructInsertStatement(loanId, principalOverdue, interestOverdue, feeOverdue, penaltyOverdue,
+                            overDueSince);
+                } else {
+                    sqlStatement = constructUpdateStatement(loanId, principalOverdue, interestOverdue, feeOverdue, penaltyOverdue,
+                            overDueSince);
+                }
+                insertStatement.add(sqlStatement);
+            }
+
+        }
+    }
+
+    private String constructInsertStatement(final Long loanId, BigDecimal principalOverdue, BigDecimal interestOverdue,
+            BigDecimal feeOverdue, BigDecimal penaltyOverdue, LocalDate overDueSince) {
+        final StringBuilder insertStatementBuilder = new StringBuilder(900);
+        insertStatementBuilder
+                .append("INSERT INTO m_loan_arrears_aging(`loan_id`,`principal_overdue_derived`,`interest_overdue_derived`,")
+                .append("`fee_charges_overdue_derived`,`penalty_charges_overdue_derived`,`total_overdue_derived`,`overdue_since_date_derived`) VALUES(");
+        insertStatementBuilder.append(loanId).append(",");
+        insertStatementBuilder.append(principalOverdue).append(",");
+        insertStatementBuilder.append(interestOverdue).append(",");
+        insertStatementBuilder.append(feeOverdue).append(",");
+        insertStatementBuilder.append(penaltyOverdue).append(",");
+        BigDecimal totalOverDue = principalOverdue.add(interestOverdue).add(feeOverdue).add(penaltyOverdue);
+        insertStatementBuilder.append(totalOverDue).append(",'");
+        insertStatementBuilder.append(this.formatter.print(overDueSince)).append("')");
+        return insertStatementBuilder.toString();
+    }
+
+    private String constructUpdateStatement(final Long loanId, BigDecimal principalOverdue, BigDecimal interestOverdue,
+            BigDecimal feeOverdue, BigDecimal penaltyOverdue, LocalDate overDueSince) {
+        final StringBuilder insertStatementBuilder = new StringBuilder(900);
+        insertStatementBuilder.append("UPDATE m_loan_arrears_aging mla SET mla.principal_overdue_derived=");
+        insertStatementBuilder.append(principalOverdue).append(", mla.interest_overdue_derived=");
+        insertStatementBuilder.append(interestOverdue).append(", mla.fee_charges_overdue_derived=");
+        insertStatementBuilder.append(feeOverdue).append(", mla.penalty_charges_overdue_derived=");
+        insertStatementBuilder.append(penaltyOverdue).append(", mla.total_overdue_derived=");
+        BigDecimal totalOverDue = principalOverdue.add(interestOverdue).add(feeOverdue).add(penaltyOverdue);
+        insertStatementBuilder.append(totalOverDue).append(",mla.overdue_since_date_derived= '");
+        insertStatementBuilder.append(this.formatter.print(overDueSince)).append("' ");
+        insertStatementBuilder.append("WHERE  mla.loan_id=").append(loanId);
+        return insertStatementBuilder.toString();
+    }
+
+    private void updateSchheduleWithPaidDetail(Map<Long, List<LoanSchedulePeriodData>> scheduleDate, List<Map<String, Object>> loanSummary) {
+        for (Map<String, Object> transactionMap : loanSummary) {
+            Long loanId = (Long) transactionMap.get("loanId");
+
+            BigDecimal principalAmtPaid = (BigDecimal) transactionMap.get("principalAmtPaid");
+            BigDecimal principalAmtWrittenoff = (BigDecimal) transactionMap.get("principalAmtWrittenoff");
+            BigDecimal interestAmtPaid = (BigDecimal) transactionMap.get("interestAmtPaid");
+            BigDecimal interestAmtWaived = (BigDecimal) transactionMap.get("interestAmtWaived");
+            BigDecimal feeAmtPaid = (BigDecimal) transactionMap.get("feeAmtPaid");
+            BigDecimal feeAmtWaived = (BigDecimal) transactionMap.get("feeAmtWaived");
+            BigDecimal penaltyAmtPaid = (BigDecimal) transactionMap.get("penaltyAmtPaid");
+            BigDecimal penaltyAmtWaived = (BigDecimal) transactionMap.get("penaltyAmtWaived");
+
+            BigDecimal principalAmt = principalAmtPaid.add(principalAmtWrittenoff);
+            BigDecimal interestAmt = interestAmtPaid.add(interestAmtWaived);
+            BigDecimal feeAmt = feeAmtPaid.add(feeAmtWaived);
+            BigDecimal penaltyAmt = penaltyAmtPaid.add(penaltyAmtWaived);
+
+            List<LoanSchedulePeriodData> loanSchedulePeriodDatas = scheduleDate.get(loanId);
+            if (loanSchedulePeriodDatas != null) {
+                List<LoanSchedulePeriodData> updatedPeriodData = new ArrayList<>(loanSchedulePeriodDatas.size());
+                for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) {
+                    BigDecimal principalPaid = null;
+                    BigDecimal interestPaid = null;
+                    BigDecimal feeChargesPaid = null;
+                    BigDecimal penaltyChargesPaid = null;
+                    Boolean isComplete = true;
+                    if (loanSchedulePeriodData.principalDue().compareTo(principalAmt) == 1) {
+                        principalPaid = principalAmt;
+                        principalAmt = BigDecimal.ZERO;
+                        isComplete = false;
+                    } else {
+                        principalPaid = loanSchedulePeriodData.principalDue();
+                        principalAmt = principalAmt.subtract(loanSchedulePeriodData.principalDue());
+                    }
+
+                    if (loanSchedulePeriodData.interestDue().compareTo(interestAmt) == 1) {
+                        interestPaid = interestAmt;
+                        interestAmt = BigDecimal.ZERO;
+                        isComplete = false;
+                    } else {
+                        interestPaid = loanSchedulePeriodData.interestDue();
+                        interestAmt = interestAmt.subtract(loanSchedulePeriodData.interestDue());
+                    }
+                    if (loanSchedulePeriodData.feeChargesDue().compareTo(feeAmt) == 1) {
+                        feeChargesPaid = feeAmt;
+                        feeAmt = BigDecimal.ZERO;
+                        isComplete = false;
+                    } else {
+                        feeChargesPaid = loanSchedulePeriodData.feeChargesDue();
+                        feeAmt = feeAmt.subtract(loanSchedulePeriodData.feeChargesDue());
+                    }
+                    if (loanSchedulePeriodData.penaltyChargesDue().compareTo(penaltyAmt) == 1) {
+                        penaltyChargesPaid = penaltyAmt;
+                        penaltyAmt = BigDecimal.ZERO;
+                        isComplete = false;
+                    } else {
+                        penaltyChargesPaid = loanSchedulePeriodData.penaltyChargesDue();
+                        penaltyAmt = penaltyAmt.subtract(loanSchedulePeriodData.penaltyChargesDue());
+                    }
+
+                    LoanSchedulePeriodData periodData = LoanSchedulePeriodData.WithPaidDetail(loanSchedulePeriodData, isComplete,
+                            principalPaid, interestPaid, feeChargesPaid, penaltyChargesPaid);
+                    updatedPeriodData.add(periodData);
+                }
+                loanSchedulePeriodDatas.clear();
+                loanSchedulePeriodDatas.addAll(updatedPeriodData);
+            }
+        }
+    }
+
+    private static final class OriginalScheduleExtractor implements ResultSetExtractor<Map<Long, List<LoanSchedulePeriodData>>> {
+
+        private final String schema;
+
+        public OriginalScheduleExtractor(final String loanIdsAsString) {
+            final StringBuilder scheduleDetail = new StringBuilder();
+            scheduleDetail.append("select ml.id as loanId, mr.duedate as dueDate, mr.principal_amount as principalAmount, ");
+            scheduleDetail
+                    .append("mr.interest_amount as interestAmount, mr.fee_charges_amount as feeAmount, mr.penalty_charges_amount as penaltyAmount  ");
+            scheduleDetail.append("from m_loan ml  INNER JOIN m_loan_repayment_schedule_history mr on mr.loan_id = ml.id ");
+            scheduleDetail.append("where mr.duedate  < SUBDATE(CURDATE(),INTERVAL  ifnull(ml.grace_on_arrears_ageing,0) day) and ");
+            scheduleDetail.append("ml.id IN(").append(loanIdsAsString).append(") and  mr.version = (");
+            scheduleDetail.append("select max(lrs.version) from m_loan_repayment_schedule_history lrs where mr.loan_id = lrs.loan_id");
+            scheduleDetail.append(") order by ml.id,mr.duedate");
+            this.schema = scheduleDetail.toString();
+        }
+
+        @Override
+        public Map<Long, List<LoanSchedulePeriodData>> extractData(ResultSet rs) throws SQLException, DataAccessException {
+            Map<Long, List<LoanSchedulePeriodData>> scheduleDate = new HashMap<>();
+
+            while (rs.next()) {
+                Long loanId = rs.getLong("loanId");
+                List<LoanSchedulePeriodData> periodDatas = new ArrayList<>();
+                LoanSchedulePeriodData loanSchedulePeriodData = fetchLoanSchedulePeriodData(rs);
+                periodDatas.add(loanSchedulePeriodData);
+                while (rs.next()) {
+                    Long tempLoanId = rs.getLong("loanId");
+                    if (loanId.equals(tempLoanId)) {
+                        periodDatas.add(fetchLoanSchedulePeriodData(rs));
+                    } else {
+                        rs.previous();
+                        break;
+                    }
+                }
+                scheduleDate.put(loanId, periodDatas);
+            }
+
+            return scheduleDate;
+        }
+
+        private LoanSchedulePeriodData fetchLoanSchedulePeriodData(ResultSet rs) throws SQLException {
+            final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
+            final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalAmount");
+            final BigDecimal interestDueOnPrincipalOutstanding = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestAmount");
+            final BigDecimal totalInstallmentAmount = principalDue.add(interestDueOnPrincipalOutstanding);
+            final BigDecimal feeChargesDueForPeriod = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeAmount");
+            final BigDecimal penaltyChargesDueForPeriod = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyAmount");
+            final Integer periodNumber = null;
+            final LocalDate fromDate = null;
+            final BigDecimal principalOutstanding = null;
+            final BigDecimal totalDueForPeriod = null;
+            return LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, fromDate, dueDate, principalDue, principalOutstanding,
+                    interestDueOnPrincipalOutstanding, feeChargesDueForPeriod, penaltyChargesDueForPeriod, totalDueForPeriod,
+                    totalInstallmentAmount);
+
+        }
+    }
+
+    @SuppressWarnings("unused")
+    @Override
+    public void businessEventToBeExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+        Loan loan = null;
+        Object loanEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
+        Object loanTransactionEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
+        Object loanAdjustTransactionEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_ADJUSTED_TRANSACTION);
+        Object loanChargeEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_CHARGE);
+        if (loanEntity != null) {
+            loan = (Loan) loanEntity;
+        } else if (loanTransactionEntity != null) {
+            LoanTransaction loanTransaction = (LoanTransaction) loanTransactionEntity;
+            loan = loanTransaction.getLoan();
+        } else if (loanAdjustTransactionEntity != null) {
+            LoanTransaction loanTransaction = (LoanTransaction) loanAdjustTransactionEntity;
+            loan = loanTransaction.getLoan();
+        } else if (loanChargeEntity != null) {
+            LoanCharge loanCharge = (LoanCharge) loanChargeEntity;
+            loan = loanCharge.getLoan();
+        }
+        if (loan != null && loan.isOpen() && loan.repaymentScheduleDetail().isInterestRecalculationEnabled()
+                && loan.loanProduct().isArrearsBasedOnOriginalSchedule()) {
+            updateLoanArrearsAgeingDetailsWithOriginalSchedule(loan);
+        } else {
+            updateLoanArrearsAgeingDetails(loan);
+        }
+    }
+
+    private class DisbursementEventListner implements BusinessEventListner {
+
+        @SuppressWarnings("unused")
+        @Override
+        public void businessEventToBeExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            // TODO Auto-generated method stub
+
+        }
+
+        @Override
+        public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
+            Object loanEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
+            if (loanEntity != null) {
+                Loan loan = (Loan) loanEntity;
+                updateLoanArrearsAgeingDetails(loan);
+            }
+
+        }
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
new file mode 100644
index 0000000..a5f9af5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
@@ -0,0 +1,398 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
+import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepository;
+import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
+import org.apache.fineract.organisation.staff.exception.StaffRoleException;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.portfolio.accountdetails.service.AccountEnumerations;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.collateral.domain.LoanCollateral;
+import org.apache.fineract.portfolio.collateral.service.CollateralAssembler;
+import org.apache.fineract.portfolio.fund.domain.Fund;
+import org.apache.fineract.portfolio.fund.domain.FundRepository;
+import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepository;
+import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository;
+import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
+import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
+import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class LoanAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final LoanRepositoryWrapper loanRepository;
+    private final LoanProductRepository loanProductRepository;
+    private final ClientRepositoryWrapper clientRepository;
+    private final GroupRepository groupRepository;
+    private final FundRepository fundRepository;
+    private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository;
+    private final StaffRepository staffRepository;
+    private final CodeValueRepositoryWrapper codeValueRepository;
+    private final LoanScheduleAssembler loanScheduleAssembler;
+    private final LoanChargeAssembler loanChargeAssembler;
+    private final CollateralAssembler loanCollateralAssembler;
+    private final LoanSummaryWrapper loanSummaryWrapper;
+    private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
+    private final HolidayRepository holidayRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+
+    @Autowired
+    public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanRepositoryWrapper loanRepository,
+            final LoanProductRepository loanProductRepository, final ClientRepositoryWrapper clientRepository,
+            final GroupRepository groupRepository, final FundRepository fundRepository,
+            final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository,
+            final StaffRepository staffRepository, final CodeValueRepositoryWrapper codeValueRepository,
+            final LoanScheduleAssembler loanScheduleAssembler, final LoanChargeAssembler loanChargeAssembler,
+            final CollateralAssembler loanCollateralAssembler, final LoanSummaryWrapper loanSummaryWrapper,
+            final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
+            final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
+            final WorkingDaysRepositoryWrapper workingDaysRepository) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.loanRepository = loanRepository;
+        this.loanProductRepository = loanProductRepository;
+        this.clientRepository = clientRepository;
+        this.groupRepository = groupRepository;
+        this.fundRepository = fundRepository;
+        this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
+        this.staffRepository = staffRepository;
+        this.codeValueRepository = codeValueRepository;
+        this.loanScheduleAssembler = loanScheduleAssembler;
+        this.loanChargeAssembler = loanChargeAssembler;
+        this.loanCollateralAssembler = loanCollateralAssembler;
+        this.loanSummaryWrapper = loanSummaryWrapper;
+        this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
+        this.holidayRepository = holidayRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.workingDaysRepository = workingDaysRepository;
+    }
+
+    public Loan assembleFrom(final Long accountId) {
+        final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId);
+        loanAccount.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper,
+                this.loanRepaymentScheduleTransactionProcessorFactory);
+
+        return loanAccount;
+    }
+
+    public void setHelpers(final Loan loanAccount) {
+        loanAccount.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper,
+                this.loanRepaymentScheduleTransactionProcessorFactory);
+    }
+
+    public Loan assembleFrom(final JsonCommand command, final AppUser currentUser) {
+        final JsonElement element = command.parsedJson();
+
+        final Long clientId = this.fromApiJsonHelper.extractLongNamed("clientId", element);
+        final Long groupId = this.fromApiJsonHelper.extractLongNamed("groupId", element);
+
+        return assembleApplication(element, clientId, groupId, currentUser);
+    }
+
+    private Loan assembleApplication(final JsonElement element, final Long clientId, final Long groupId, final AppUser currentUser) {
+
+        final String accountNo = this.fromApiJsonHelper.extractStringNamed("accountNo", element);
+        final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element);
+        final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", element);
+        final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed("loanOfficerId", element);
+        final Long transactionProcessingStrategyId = this.fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element);
+        final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed("loanPurposeId", element);
+        final Boolean syncDisbursementWithMeeting = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
+        final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper.extractBooleanNamed(
+                "createStandingInstructionAtDisbursement", element);
+
+        final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
+        if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
+
+        final Fund fund = findFundByIdIfProvided(fundId);
+        final Staff loanOfficer = findLoanOfficerByIdIfProvided(loanOfficerId);
+        final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(transactionProcessingStrategyId);
+        CodeValue loanPurpose = null;
+        if (loanPurposeId != null) {
+            loanPurpose = this.codeValueRepository.findOneWithNotFoundDetection(loanPurposeId);
+        }
+        Set<LoanDisbursementDetails> disbursementDetails = null;
+        BigDecimal fixedEmiAmount = null;
+        if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount()) {
+            fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
+        }
+        BigDecimal maxOutstandingLoanBalance = null;
+        if (loanProduct.isMultiDisburseLoan()) {
+            disbursementDetails = fetchDisbursementData(element.getAsJsonObject());
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+            maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
+                    element, locale);
+            if (disbursementDetails.isEmpty()) {
+                final String errorMessage = "For this loan product, disbursement details must be provided";
+                throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+            }
+
+            if (disbursementDetails.size() > loanProduct.maxTrancheCount()) {
+                final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
+                throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
+                        loanProduct.maxTrancheCount(), disbursementDetails.size());
+            }
+        }
+        final Set<LoanCollateral> collateral = this.loanCollateralAssembler.fromParsedJson(element);
+        final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element,disbursementDetails);
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (!loanProduct.hasCurrencyCodeOf(loanCharge.currencyCode())) {
+                final String errorMessage = "Charge and Loan must have the same currency.";
+                throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
+            }
+            if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
+                final Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed("linkAccountId", element);
+                if (savingsAccountId == null) {
+                    final String errorMessage = "one of the charges requires linked savings account for payment";
+                    throw new LinkedAccountRequiredException("loanCharge", errorMessage);
+                }
+            }
+        }
+
+        Loan loanApplication = null;
+        Client client = null;
+        Group group = null;
+
+        final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element);
+        
+        final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element);
+        final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRateParameterName, element);
+
+        final String loanTypeParameterName = "loanType";
+        final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
+        final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeStr);
+       
+
+        if (clientId != null) {
+            client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            if (client.isNotActive()) { throw new ClientNotActiveException(clientId); }
+        }
+
+        if (groupId != null) {
+            group = this.groupRepository.findOne(groupId);
+            if (group == null) { throw new GroupNotFoundException(groupId); }
+            if (group.isNotActive()) { throw new GroupNotActiveException(groupId); }
+        }
+
+        if (client != null && group != null) {
+
+            if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); }
+
+            loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(),
+                    loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges,
+                    collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
+                    createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential);
+
+        } else if (group != null) {
+
+            loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
+                    loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral,
+                    syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
+                    createStandingInstructionAtDisbursement,isFloatingInterestRate, interestRateDifferential);
+
+        } else if (client != null) {
+
+            loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund,
+                    loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral,
+                    fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement,
+                    isFloatingInterestRate, interestRateDifferential);
+
+        }
+
+        final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
+        final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element);
+
+        if (loanApplication == null) { throw new IllegalStateException("No loan application exists for either a client or group (or both)."); }
+        loanApplication.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper,
+                this.loanRepaymentScheduleTransactionProcessorFactory);
+
+        if (loanProduct.isMultiDisburseLoan()) {
+            for (final LoanDisbursementDetails loanDisbursementDetails : loanApplication.getDisbursementDetails()) {
+                loanDisbursementDetails.updateLoan(loanApplication);
+            }
+        }
+
+        final LocalDate recalculationRestFrequencyDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                LoanProductConstants.recalculationRestFrequencyDateParamName, element);
+        final LocalDate recalculationCompoundingFrequencyDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element);
+
+        final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);
+        final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+        final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(),
+                loanApplicationTerms.getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+        final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
+        final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
+        final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms,
+                isHolidayEnabled, holidays, workingDays, element,disbursementDetails);
+        loanApplication.loanApplicationSubmittal(currentUser, loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(),
+                submittedOnDate, externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay,
+                recalculationRestFrequencyDate, recalculationCompoundingFrequencyDate);
+
+        return loanApplication;
+    }
+
+    public Set<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
+        final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
+        Set<LoanDisbursementDetails> disbursementDatas = new HashSet<>();
+        if (command.has(LoanApiConstants.disbursementDataParameterName)) {
+            final JsonArray disbursementDataArray = command.getAsJsonArray(LoanApiConstants.disbursementDataParameterName);
+            if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject();
+                    Date expectedDisbursementDate = null;
+                    Date actualDisbursementDate = null;
+                    BigDecimal principal = null;
+
+                    if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)) {
+                        LocalDate date = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName,
+                                jsonObject, dateFormat, locale);
+                        if (date != null) {
+                            expectedDisbursementDate = date.toDate();
+                        }
+                    }
+                    if (jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)
+                            && jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
+                            && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
+                        principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
+                    }
+
+                    disbursementDatas.add(new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal));
+                    i++;
+                } while (i < disbursementDataArray.size());
+            }
+        }
+        return disbursementDatas;
+    }
+
+    private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
+        final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
+        return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
+    }
+
+    public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) {
+        CodeValue codeValue = null;
+        if (codeValueId != null) {
+            codeValue = this.codeValueRepository.findOneWithNotFoundDetection(codeValueId);
+        }
+        return codeValue;
+    }
+
+    public Fund findFundByIdIfProvided(final Long fundId) {
+        Fund fund = null;
+        if (fundId != null) {
+            fund = this.fundRepository.findOne(fundId);
+            if (fund == null) { throw new FundNotFoundException(fundId); }
+        }
+        return fund;
+    }
+
+    public Staff findLoanOfficerByIdIfProvided(final Long loanOfficerId) {
+        Staff staff = null;
+        if (loanOfficerId != null) {
+            staff = this.staffRepository.findOne(loanOfficerId);
+            if (staff == null) {
+                throw new StaffNotFoundException(loanOfficerId);
+            } else if (staff.isNotLoanOfficer()) { throw new StaffRoleException(loanOfficerId, StaffRoleException.STAFF_ROLE.LOAN_OFFICER); }
+        }
+        return staff;
+    }
+
+    public LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) {
+        LoanTransactionProcessingStrategy strategy = null;
+        if (transactionProcessingStrategyId != null) {
+            strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId);
+            if (strategy == null) { throw new LoanTransactionProcessingStrategyNotFoundException(transactionProcessingStrategyId); }
+        }
+        return strategy;
+    }
+
+    public void validateExpectedDisbursementForHolidayAndNonWorkingDay(final Loan loanApplication) {
+
+        final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
+        final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(),
+                loanApplication.getExpectedDisbursedOnLocalDate().toDate(), HolidayStatusType.ACTIVE.getValue());
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+        final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
+
+        loanApplication.validateExpectedDisbursementForHolidayAndNonWorkingDay(workingDays, allowTransactionsOnHoliday, holidays,
+                allowTransactionsOnNonWorkingDay);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java
new file mode 100644
index 0000000..8095142
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java
@@ -0,0 +1,239 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class LoanChargeAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final ChargeRepositoryWrapper chargeRepository;
+    private final LoanChargeRepository loanChargeRepository;
+    private final LoanProductRepository loanProductRepository;
+
+    @Autowired
+    public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
+            final LoanChargeRepository loanChargeRepository, final LoanProductRepository loanProductRepository) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.chargeRepository = chargeRepository;
+        this.loanChargeRepository = loanChargeRepository;
+        this.loanProductRepository = loanProductRepository;
+    }
+
+    public Set<LoanCharge> fromParsedJson(final JsonElement element, Set<LoanDisbursementDetails> disbursementDetails) {
+        JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
+        List<Long> disbursementChargeIds = new ArrayList<>();
+
+        if (jsonDisbursement != null && jsonDisbursement.size() > 0) {
+            for (int i = 0; i < jsonDisbursement.size(); i++) {
+                final JsonObject jsonObject = jsonDisbursement.get(i).getAsJsonObject();
+                if (jsonObject != null && jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName) != null) {
+                    String chargeIds = jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString();
+                    if (chargeIds != null) {
+                        if (chargeIds.indexOf(",") != -1) {
+                            String[] chargeId = chargeIds.split(",");
+                            for (String loanChargeId : chargeId) {
+                                disbursementChargeIds.add(Long.parseLong(loanChargeId));
+                            }
+                        } else {
+                            disbursementChargeIds.add(Long.parseLong(chargeIds));
+                        }
+                    }
+
+                }
+            }
+        }
+
+        final Set<LoanCharge> loanCharges = new HashSet<>();
+        final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
+        final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
+        final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element);
+        final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
+        if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
+        final boolean isMultiDisbursal = loanProduct.isMultiDisburseLoan();
+        LocalDate expectedDisbursementDate = null;
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+
+                    final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
+
+                    final Long id = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement);
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement);
+                    final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale);
+                    final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale);
+                    final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed("chargeCalculationType",
+                            loanChargeElement, locale);
+                    final LocalDate dueDate = this.fromApiJsonHelper
+                            .extractLocalDateNamed("dueDate", loanChargeElement, dateFormat, locale);
+                    final Integer chargePaymentMode = this.fromApiJsonHelper.extractIntegerNamed("chargePaymentMode", loanChargeElement,
+                            locale);
+                    if (id == null) {
+                        final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
+
+                        if (chargeDefinition.isOverdueInstallment()) {
+
+                            final String defaultUserMessage = "Installment charge cannot be added to the loan.";
+                            throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null,
+                                    chargeDefinition.getName());
+                        }
+
+                        ChargeTimeType chargeTime = null;
+                        if (chargeTimeType != null) {
+                            chargeTime = ChargeTimeType.fromInt(chargeTimeType);
+                        }
+                        ChargeCalculationType chargeCalculation = null;
+                        if (chargeCalculationType != null) {
+                            chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType);
+                        }
+                        ChargePaymentMode chargePaymentModeEnum = null;
+                        if (chargePaymentMode != null) {
+                            chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode);
+                        }
+                        if (!isMultiDisbursal) {
+                            final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
+                                    chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments);
+                            loanCharges.add(loanCharge);
+                        } else {
+                            if (topLevelJsonElement.has("disbursementData") && topLevelJsonElement.get("disbursementData").isJsonArray()) {
+                                final JsonArray disbursementArray = topLevelJsonElement.get("disbursementData").getAsJsonArray();
+                                if (disbursementArray.size() > 0) {
+                                    JsonObject disbursementDataElement = disbursementArray.get(0).getAsJsonObject();
+                                    expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                                            LoanApiConstants.disbursementDateParameterName, disbursementDataElement, dateFormat, locale);
+                                }
+                            }
+                            
+                            if ( ChargeTimeType.DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
+                                for (LoanDisbursementDetails disbursementDetail : disbursementDetails) {
+                                    LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
+                                    if (chargeDefinition.isPercentageOfApprovedAmount()
+                                            && disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) {
+                                        final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount,
+                                                chargeTime, chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments);
+                                        loanCharges.add(loanCharge);
+                                        loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail);
+                                        loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
+                                    } else {
+                                        if (disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) {
+                                            final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition,
+                                                    disbursementDetail.principal(), amount, chargeTime, chargeCalculation,
+                                                    disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum,
+                                                    numberOfRepayments);
+                                            loanCharges.add(loanCharge);
+                                            loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
+                                                    disbursementDetail);
+                                            loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
+                                        }
+                                    }
+                                }
+                            } else if (ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
+                                LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
+                                for (LoanDisbursementDetails disbursementDetail : disbursementDetails) {
+                                    if (ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
+                                        final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition,
+                                                disbursementDetail.principal(), amount, chargeTime, chargeCalculation,
+                                                disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum,
+                                                numberOfRepayments);
+                                        loanCharges.add(loanCharge);
+                                        loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail);
+                                        loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
+                                    }
+                                }
+                            } else {
+                                final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount,
+                                        chargeTime, chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments);
+                                loanCharges.add(loanCharge);
+                            }
+                        }
+                    } else {
+                        final Long loanChargeId = id;
+                        final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId);
+                        if (disbursementChargeIds.contains(loanChargeId) && loanCharge == null) {
+                            // throw new
+                            // LoanChargeNotFoundException(loanChargeId);
+                        }
+                        if (loanCharge != null) {
+                            loanCharge.update(amount, dueDate, numberOfRepayments);
+                            loanCharges.add(loanCharge);
+                        }
+                    }
+                }
+            }
+        }
+
+        return loanCharges;
+    }
+
+    public Set<Charge> getNewLoanTrancheCharges(final JsonElement element) {
+        final Set<Charge> associatedChargesForLoan = new HashSet<>();
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
+                    final Long id = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement);
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement);
+                    if (id == null) {
+                        final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
+                        if (chargeDefinition.getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()) {
+                            associatedChargesForLoan.add(chargeDefinition);
+                        }
+                    }
+                }
+            }
+        }
+        return associatedChargesForLoan;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformService.java
new file mode 100644
index 0000000..cf76279
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformService.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+
+public interface LoanChargeReadPlatformService {
+
+    ChargeData retrieveLoanChargeTemplate();
+
+    Collection<LoanChargeData> retrieveLoanCharges(Long loanId);
+
+    LoanChargeData retrieveLoanChargeDetails(Long loanChargeId, Long loanId);
+
+    Collection<LoanChargeData> retrieveLoanChargesForFeePayment(Integer paymentMode, Integer loanStatus);
+
+    Collection<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges);
+
+    Collection<Integer> retrieveOverdueInstallmentChargeFrequencyNumber(Long loanId, Long chargeId, Integer periodNumber);
+    
+    Collection<LoanChargeData> retrieveLoanChargesForAccural(Long loanId);
+
+    Collection<LoanChargePaidByData> retriveLoanChargesPaidBy(Long chargeId, LoanTransactionType transactionType, Integer installmentNumber);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java
new file mode 100755
index 0000000..f38d87e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java
@@ -0,0 +1,548 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+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.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeDropdownReadPlatformService;
+import org.apache.fineract.portfolio.charge.service.ChargeEnumerations;
+import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService;
+    private final DropdownReadPlatformService dropdownReadPlatformService;
+
+    @Autowired
+    public LoanChargeReadPlatformServiceImpl(final PlatformSecurityContext context,
+            final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource,
+            final DropdownReadPlatformService dropdownReadPlatformService) {
+        this.context = context;
+        this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+    }
+
+    private static final class LoanChargeMapper implements RowMapper<LoanChargeData> {
+
+        public String schema() {
+            return "lc.id as id, c.id as chargeId, c.name as name, " + "lc.amount as amountDue, " + "lc.amount_paid_derived as amountPaid, "
+                    + "lc.amount_waived_derived as amountWaived, " + "lc.amount_writtenoff_derived as amountWrittenOff, "
+                    + "lc.amount_outstanding_derived as amountOutstanding, "
+                    + "lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, "
+                    + "lc.charge_time_enum as chargeTime, " + "lc.is_penalty as penalty, "
+                    + "lc.due_for_collection_as_of_date as dueAsOfDate, " + "lc.charge_calculation_enum as chargeCalculation, "
+                    + "lc.charge_payment_mode_enum as chargePaymentMode, " + "lc.is_paid_derived as paid, " + "lc.waived as waied, "
+                    + "lc.min_cap as minCap, lc.max_cap as maxCap, " + "lc.charge_amount_or_percentage as amountOrPercentage, "
+                    + "c.currency_code as currencyCode, oc.name as currencyName, "
+                    + "date(ifnull(dd.disbursedon_date,dd.expected_disburse_date)) as disbursementDate, "
+                    + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+                    + "oc.internationalized_name_code as currencyNameCode from m_charge c "
+                    + "join m_organisation_currency oc on c.currency_code = oc.code " + "join m_loan_charge lc on lc.charge_id = c.id "
+                    + "left join m_loan_tranche_disbursement_charge dc on dc.loan_charge_id=lc.id left join m_loan_disbursement_detail dd on dd.id=dc.disbursement_detail_id ";
+        }
+
+        @Override
+        public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long chargeId = rs.getLong("chargeId");
+            final String name = rs.getString("name");
+            final BigDecimal amount = rs.getBigDecimal("amountDue");
+            final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPaid");
+            final BigDecimal amountWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWaived");
+            final BigDecimal amountWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWrittenOff");
+            final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
+
+            final BigDecimal percentageOf = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "percentageOf");
+            final BigDecimal amountPercentageAppliedTo = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPercentageAppliedTo");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final int chargeTime = rs.getInt("chargeTime");
+            final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
+
+            LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
+
+            final int chargeCalculation = rs.getInt("chargeCalculation");
+            final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
+            final boolean penalty = rs.getBoolean("penalty");
+
+            final int chargePaymentMode = rs.getInt("chargePaymentMode");
+            final EnumOptionData paymentMode = ChargeEnumerations.chargePaymentMode(chargePaymentMode);
+            final boolean paid = rs.getBoolean("paid");
+            final boolean waived = rs.getBoolean("waied");
+            final BigDecimal minCap = rs.getBigDecimal("minCap");
+            final BigDecimal maxCap = rs.getBigDecimal("maxCap");
+            final BigDecimal amountOrPercentage = rs.getBigDecimal("amountOrPercentage");
+            final LocalDate disbursementDate = JdbcSupport.getLocalDate(rs, "disbursementDate");
+
+            if (disbursementDate != null) {
+                dueAsOfDate = disbursementDate;
+            }
+
+            return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding,
+                    chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty, paymentMode, paid,
+                    waived, null, minCap, maxCap, amountOrPercentage, null);
+        }
+    }
+
+    @Override
+    public ChargeData retrieveLoanChargeTemplate() {
+        this.context.authenticatedUser();
+
+        final List<EnumOptionData> allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes();
+        final List<EnumOptionData> allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes();
+        final List<EnumOptionData> loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveLoanCalculationTypes();
+        final List<EnumOptionData> loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes();
+        final List<EnumOptionData> savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveSavingsCalculationTypes();
+        final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveSavingsCollectionTimeTypes();
+        final List<EnumOptionData> clientChargeCalculationTypeOptions = null;
+        final List<EnumOptionData> clientChargeTimeTypeOptions = null;
+
+        final List<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
+        // this field is applicable only for client charges
+        final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
+
+        return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null,
+                loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
+                savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions,
+                incomeOrLiabilityAccountOptions);
+    }
+
+    @Override
+    public LoanChargeData retrieveLoanChargeDetails(final Long id, final Long loanId) {
+        this.context.authenticatedUser();
+
+        final LoanChargeMapper rm = new LoanChargeMapper();
+
+        final String sql = "select " + rm.schema() + " where lc.id=? and lc.loan_id=?";
+
+        return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { id, loanId });
+    }
+
+    @Override
+    public Collection<LoanChargeData> retrieveLoanCharges(final Long loanId) {
+        this.context.authenticatedUser();
+
+        final LoanChargeMapper rm = new LoanChargeMapper();
+
+        final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = 1"
+                + " order by ifnull(lc.due_for_collection_as_of_date,date(ifnull(dd.disbursedon_date,dd.expected_disburse_date))),lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
+    }
+
+    @Override
+    public Collection<LoanChargeData> retrieveLoanChargesForFeePayment(final Integer paymentMode, final Integer loanStatus) {
+        final LoanChargeMapperWithLoanId rm = new LoanChargeMapperWithLoanId();
+        final String sql = "select " + rm.schema()
+                + "where loan.loan_status_id= ? and lc.charge_payment_mode_enum=? and lc.waived =0 and lc.is_paid_derived=0 and lc.is_active = 1";
+        return this.jdbcTemplate.query(sql, rm, new Object[] { loanStatus, paymentMode });
+    }
+
+    private static final class LoanChargeMapperWithLoanId implements RowMapper<LoanChargeData> {
+
+        public String schema() {
+            return "lc.id as id, lc.due_for_collection_as_of_date as dueAsOfDate, " + "lc.amount_outstanding_derived as amountOutstanding, "
+                    + "lc.charge_time_enum as chargeTime, " + "loan.id as loanId " + "from  m_loan_charge lc "
+                    + "join m_loan loan on loan.id = lc.loan_id ";
+        }
+
+        @Override
+        public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
+            final Long loanId = rs.getLong("loanId");
+            final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
+            final int chargeTime = rs.getInt("chargeTime");
+            final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
+
+            return new LoanChargeData(id, dueAsOfDate, amountOutstanding, chargeTimeType, loanId, null);
+        }
+    }
+
+    @Override
+    public Collection<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges) {
+        final LoanInstallmentChargeMapper rm = new LoanInstallmentChargeMapper();
+        String sql = "select " + rm.schema() + "where lic.loan_charge_id= ? ";
+        if (onlyPaymentPendingCharges) {
+            sql = sql + "and lic.waived =0 and lic.is_paid_derived=0";
+        }
+        return this.jdbcTemplate.query(sql, rm, new Object[] { loanChargeId });
+    }
+
+    private static final class LoanInstallmentChargeMapper implements RowMapper<LoanInstallmentChargeData> {
+
+        public String schema() {
+            return " lsi.installment as installmentNumber, lsi.duedate as dueAsOfDate, "
+                    + "lic.amount_outstanding_derived as amountOutstanding," + "lic.amount as  amount, " + "lic.is_paid_derived as paid, "
+                    + "lic.amount_waived_derived as amountWaived, " + "lic.waived as waied " + "from  m_loan_installment_charge lic "
+                    + "join m_loan_repayment_schedule lsi on lsi.id = lic.loan_schedule_id ";
+        }
+
+        @Override
+        public LoanInstallmentChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Integer installmentNumber = rs.getInt("installmentNumber");
+            final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
+            final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
+            final BigDecimal amount = rs.getBigDecimal("amount");
+            final BigDecimal amountWaived = rs.getBigDecimal("amountWaived");
+            final boolean paid = rs.getBoolean("paid");
+            final boolean waived = rs.getBoolean("waied");
+
+            return new LoanInstallmentChargeData(installmentNumber, dueAsOfDate, amount, amountOutstanding, amountWaived, paid, waived);
+        }
+    }
+
+    @Override
+    public Collection<Integer> retrieveOverdueInstallmentChargeFrequencyNumber(final Long loanId, final Long chargeId,
+            final Integer periodNumber) {
+        String sql = "select oic.frequency_number from m_loan_overdue_installment_charge oic  inner join m_loan_charge lc on lc.id=oic.loan_charge_id inner join m_loan_repayment_schedule rs on rs.id = oic.loan_schedule_id inner join m_loan loan on loan.id=rs.loan_id "
+                + "where lc.is_active = 1 and loan.id = ? and rs.installment=?";
+        Object[] params = { loanId, periodNumber };
+        if (chargeId != null) {
+            sql += " and lc.charge_id = ? ";
+            params = new Object[] { loanId, periodNumber, chargeId };
+        }
+        return this.jdbcTemplate.queryForList(sql, Integer.class, params);
+    }
+
+    @Override
+    public Collection<LoanChargeData> retrieveLoanChargesForAccural(final Long loanId) {
+
+        final LoanChargeAccrualMapper rm = new LoanChargeAccrualMapper();
+
+        final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = 1 group by  lc.id "
+                + " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC";
+
+        Collection<LoanChargeData> charges = this.jdbcTemplate.query(sql, rm,
+                new Object[] { LoanTransactionType.ACCRUAL.getValue(), loanId });
+        charges = updateLoanChargesWithUnrecognizedIncome(loanId, charges);
+
+        Collection<LoanChargeData> removeCharges = new ArrayList<>();
+        for (LoanChargeData loanChargeData : charges) {
+            if (loanChargeData.isInstallmentFee()) {
+                removeCharges.add(loanChargeData);
+            }
+        }
+        charges.removeAll(removeCharges);
+        for (LoanChargeData loanChargeData : removeCharges) {
+            if (loanChargeData.isInstallmentFee()) {
+                Collection<LoanInstallmentChargeData> installmentChargeDatas = retrieveInstallmentLoanChargesForAccrual(
+                        loanChargeData.getId());
+                LoanChargeData modifiedChargeData = new LoanChargeData(loanChargeData, installmentChargeDatas);
+                charges.add(modifiedChargeData);
+            }
+        }
+
+        return charges;
+    }
+
+    private static final class LoanChargeAccrualMapper implements RowMapper<LoanChargeData> {
+
+        private final String schemaSql;
+
+        public LoanChargeAccrualMapper() {
+            StringBuilder sb = new StringBuilder(50);
+            sb.append("lc.id as id, lc.charge_id as chargeId, ");
+            sb.append("lc.amount as amountDue, ");
+            sb.append("lc.amount_waived_derived as amountWaived, ");
+            sb.append("lc.charge_time_enum as chargeTime, ");
+            sb.append(" sum(cp.amount) as amountAccrued, ");
+            sb.append("lc.is_penalty as penalty, ");
+            sb.append("lc.due_for_collection_as_of_date as dueAsOfDate ");
+            sb.append(" from m_loan_charge lc ");
+            sb.append("left join (");
+            sb.append("select lcp.loan_charge_id, lcp.amount");
+            sb.append(" from m_loan_charge_paid_by lcp ");
+            sb.append(
+                    "inner join m_loan_transaction lt on lt.id = lcp.loan_transaction_id and lt.is_reversed = 0 and lt.transaction_type_enum = ?");
+            sb.append(") cp on  cp.loan_charge_id= lc.id  ");
+
+            schemaSql = sb.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long chargeId = rs.getLong("chargeId");
+            final BigDecimal amount = rs.getBigDecimal("amountDue");
+            final BigDecimal amountAccrued = rs.getBigDecimal("amountAccrued");
+            final BigDecimal amountWaived = rs.getBigDecimal("amountWaived");
+
+            final int chargeTime = rs.getInt("chargeTime");
+            final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
+
+            final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
+            final boolean penalty = rs.getBoolean("penalty");
+
+            return new LoanChargeData(id, chargeId, dueAsOfDate, chargeTimeType, amount, amountAccrued, amountWaived, penalty);
+        }
+    }
+
+    private Collection<LoanChargeData> updateLoanChargesWithUnrecognizedIncome(final Long loanId,
+            Collection<LoanChargeData> loanChargeDatas) {
+
+        final LoanChargeUnRecognizedIncomeMapper rm = new LoanChargeUnRecognizedIncomeMapper(loanChargeDatas);
+
+        final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = 1 group by  lc.id "
+                + " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { LoanTransactionType.WAIVE_CHARGES.getValue(), loanId });
+
+    }
+
+    private static final class LoanChargeUnRecognizedIncomeMapper implements RowMapper<LoanChargeData> {
+
+        private final String schemaSql;
+        private final Map<Long, LoanChargeData> chargeDataMap;
+
+        public LoanChargeUnRecognizedIncomeMapper(final Collection<LoanChargeData> datas) {
+            this.chargeDataMap = new HashMap<>();
+            for (LoanChargeData chargeData : datas) {
+                this.chargeDataMap.put(chargeData.getId(), chargeData);
+            }
+
+            StringBuilder sb = new StringBuilder(50);
+            sb.append("lc.id as id,  ");
+            sb.append(" sum(wt.unrecognized_income_portion) as amountUnrecognized ");
+            sb.append(" from m_loan_charge lc ");
+            sb.append("left join (");
+            sb.append("select cpb.loan_charge_id, lt.unrecognized_income_portion");
+            sb.append(" from m_loan_charge_paid_by cpb ");
+            sb.append(
+                    "inner join m_loan_transaction lt on lt.id = cpb.loan_transaction_id and lt.is_reversed = 0 and lt.transaction_type_enum = ?");
+            sb.append(") wt on  wt.loan_charge_id= lc.id  ");
+
+            schemaSql = sb.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final BigDecimal amountUnrecognized = rs.getBigDecimal("amountUnrecognized");
+
+            LoanChargeData chargeData = this.chargeDataMap.get(id);
+            return new LoanChargeData(amountUnrecognized, chargeData);
+        }
+    }
+
+    private Collection<LoanInstallmentChargeData> retrieveInstallmentLoanChargesForAccrual(Long loanChargeId) {
+        final LoanInstallmentChargeAccrualMapper rm = new LoanInstallmentChargeAccrualMapper();
+        String sql = "select " + rm.schema() + " where lic.loan_charge_id= ?  group by lsi.installment";
+        Collection<LoanInstallmentChargeData> chargeDatas = this.jdbcTemplate.query(sql, rm,
+                new Object[] { LoanTransactionType.ACCRUAL.getValue(), loanChargeId });
+        final Map<Integer, LoanInstallmentChargeData> installmentChargeDatas = new HashMap<>();
+        for (LoanInstallmentChargeData installmentChargeData : chargeDatas) {
+            installmentChargeDatas.put(installmentChargeData.getInstallmentNumber(), installmentChargeData);
+        }
+        chargeDatas = updateInstallmentLoanChargesWithUnrecognizedIncome(loanChargeId, installmentChargeDatas);
+        for (LoanInstallmentChargeData installmentChargeData : chargeDatas) {
+            installmentChargeDatas.put(installmentChargeData.getInstallmentNumber(), installmentChargeData);
+        }
+        return installmentChargeDatas.values();
+
+    }
+
+    private static final class LoanInstallmentChargeAccrualMapper implements RowMapper<LoanInstallmentChargeData> {
+
+        private final String schemaSql;
+
+        public LoanInstallmentChargeAccrualMapper() {
+            StringBuilder sb = new StringBuilder(50);
+            sb.append(" lsi.installment as installmentNumber, lsi.duedate as dueAsOfDate, ");
+            sb.append("lic.amount_outstanding_derived as amountOutstanding,");
+            sb.append("lic.amount as  amount, ");
+            sb.append("lic.is_paid_derived as paid, ");
+            sb.append("lic.amount_waived_derived as amountWaived, ");
+            sb.append(" sum(cp.amount) as amountAccrued, ");
+            sb.append("lic.waived as waied ");
+            sb.append("from  m_loan_installment_charge lic ");
+            sb.append("join m_loan_repayment_schedule lsi on lsi.id = lic.loan_schedule_id ");
+            sb.append("left join (");
+            sb.append("select lcp.loan_charge_id, lcp.amount as amount, lcp.installment_number ");
+            sb.append(" from m_loan_charge_paid_by lcp ");
+            sb.append(
+                    "inner join m_loan_transaction lt on lt.id = lcp.loan_transaction_id and lt.is_reversed = 0 and lt.transaction_type_enum = ?");
+            sb.append(") cp on  cp.loan_charge_id= lic.loan_charge_id and  cp.installment_number = lsi.installment ");
+            schemaSql = sb.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public LoanInstallmentChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Integer installmentNumber = rs.getInt("installmentNumber");
+            final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
+            final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
+            final BigDecimal amount = rs.getBigDecimal("amount");
+            final BigDecimal amountWaived = rs.getBigDecimal("amountWaived");
+            final boolean paid = rs.getBoolean("paid");
+            final boolean waived = rs.getBoolean("waied");
+            final BigDecimal amountAccrued = rs.getBigDecimal("amountAccrued");
+
+            return new LoanInstallmentChargeData(installmentNumber, dueAsOfDate, amount, amountOutstanding, amountWaived, paid, waived,
+                    amountAccrued);
+        }
+    }
+
+    private Collection<LoanInstallmentChargeData> updateInstallmentLoanChargesWithUnrecognizedIncome(final Long loanChargeId,
+            final Map<Integer, LoanInstallmentChargeData> installmentChargeDatas) {
+        final LoanInstallmentChargeUnRecognizedIncomeMapper rm = new LoanInstallmentChargeUnRecognizedIncomeMapper(installmentChargeDatas);
+        String sql = "select " + rm.schema() + " where cpb.loan_charge_id = ? group by cpb.installment_number  ";
+        return this.jdbcTemplate.query(sql, rm, new Object[] { LoanTransactionType.WAIVE_CHARGES.getValue(), loanChargeId });
+    }
+
+    private static final class LoanInstallmentChargeUnRecognizedIncomeMapper implements RowMapper<LoanInstallmentChargeData> {
+
+        private final String schemaSql;
+        private final Map<Integer, LoanInstallmentChargeData> installmentChargeDatas;
+
+        public LoanInstallmentChargeUnRecognizedIncomeMapper(final Map<Integer, LoanInstallmentChargeData> installmentChargeDatas) {
+            this.installmentChargeDatas = installmentChargeDatas;
+            StringBuilder sb = new StringBuilder(50);
+            sb.append(" cpb.installment_number as installmentNumber, ");
+            sb.append("  sum(lt.unrecognized_income_portion) as amountUnrecognized ");
+            sb.append(" from m_loan_charge_paid_by cpb ");
+            sb.append(
+                    "inner join m_loan_transaction lt on lt.id = cpb.loan_transaction_id and lt.is_reversed = 0 and lt.transaction_type_enum = ?");
+            schemaSql = sb.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public LoanInstallmentChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Integer installmentNumber = rs.getInt("installmentNumber");
+            final BigDecimal amountUnrecognized = rs.getBigDecimal("amountUnrecognized");
+            LoanInstallmentChargeData installmentChargeData = this.installmentChargeDatas.get(installmentNumber);
+            return new LoanInstallmentChargeData(installmentChargeData, amountUnrecognized);
+        }
+    }
+
+    @Override
+    public Collection<LoanChargePaidByData> retriveLoanChargesPaidBy(Long chargeId, final LoanTransactionType transactionType,
+            final Integer installmentNumber) {
+
+        LoanChargesPaidByMapper rm = new LoanChargesPaidByMapper();
+        StringBuilder sb = new StringBuilder(100);
+        sb.append("select ");
+        sb.append(rm.schema());
+        sb.append(" where lcp.loan_charge_id = ?");
+        List<Object> args = new ArrayList<>(3);
+        args.add(chargeId);
+        if (transactionType != null) {
+            sb.append(" and lt.transaction_type_enum = ?");
+            args.add(transactionType.getValue());
+        }
+        if (installmentNumber != null) {
+            sb.append(" and lcp.installment_number = ?");
+            args.add(installmentNumber);
+        }
+
+        return this.jdbcTemplate.query(sb.toString(), rm, args.toArray());
+    }
+
+    private static final class LoanChargesPaidByMapper implements RowMapper<LoanChargePaidByData> {
+
+        private final String schemaSql;
+
+        public LoanChargesPaidByMapper() {
+            StringBuilder sb = new StringBuilder(100);
+            sb.append("lcp.id as id, lcp.loan_charge_id as chargeId, ");
+            sb.append("lcp.amount as amount, ");
+            sb.append("lcp.loan_transaction_id as transactionId, ");
+            sb.append("lcp.installment_number as installmentNumber ");
+            sb.append(" from m_loan_charge_paid_by lcp ");
+            sb.append(" join m_loan_transaction lt on lt.id = lcp.loan_transaction_id and lt.is_reversed=0");
+
+            schemaSql = sb.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public LoanChargePaidByData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long chargeId = rs.getLong("chargeId");
+            final BigDecimal amount = rs.getBigDecimal("amount");
+            final Long transactionId = rs.getLong("transactionId");
+            final Integer installmentNumber = rs.getInt("installmentNumber");
+
+            return new LoanChargePaidByData(id, amount, installmentNumber, chargeId, transactionId);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
new file mode 100755
index 0000000..e41860d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
@@ -0,0 +1,129 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
+import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
+import org.joda.time.LocalDate;
+
+public interface LoanReadPlatformService {
+
+    LoanAccountData retrieveOne(Long loanId);
+
+    LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData,
+            Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges);
+
+    Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId);
+
+    LoanAccountData retrieveTemplateWithClientAndProductDetails(Long clientId, Long productId);
+
+    LoanAccountData retrieveTemplateWithGroupAndProductDetails(Long groupId, Long productId);
+
+    LoanTransactionData retrieveLoanTransactionTemplate(Long loanId);
+
+    LoanTransactionData retrieveWaiveInterestDetails(Long loanId);
+
+    LoanTransactionData retrieveLoanTransaction(Long loanId, Long transactionId);
+
+    LoanTransactionData retrieveNewClosureDetails();
+
+    LoanTransactionData retrieveDisbursalTemplate(Long loanId, boolean paymentDetailsRequired);
+
+    LoanApprovalData retrieveApprovalTemplate(Long loanId);
+
+    LoanAccountData retrieveTemplateWithCompleteGroupAndProductDetails(Long groupId, Long productId);
+
+    LoanAccountData retrieveLoanProductDetailsTemplate(Long productId, Long clientId, Long groupId);
+
+    LoanAccountData retrieveClientDetailsTemplate(Long clientId);
+
+    LoanAccountData retrieveGroupDetailsTemplate(Long groupId);
+
+    LoanAccountData retrieveGroupAndMembersDetailsTemplate(Long groupId);
+
+    Collection<CalendarData> retrieveCalendars(Long groupId);
+
+    Page<LoanAccountData> retrieveAll(SearchParameters searchParameters);
+
+    Collection<StaffData> retrieveAllowedLoanOfficers(Long selectedOfficeId, boolean staffInSelectedOfficeOnly);
+
+    /*
+     * musoni-specific at present - will find overdue scheduled installments
+     * that have a special 'overdue charge' associated with the loan product.
+     * 
+     * The 'overdue-charge' is only ever applied once to an installment and as a
+     * result overdue installments with this charge already applied are not
+     * returned.
+     */
+    Collection<OverdueLoanScheduleData> retrieveAllLoansWithOverdueInstallments(final Long penaltyWaitPeriod, final Boolean backdatePenalties);
+
+    Integer retriveLoanCounter(Long groupId, Integer loanType, Long productId);
+
+    Integer retriveLoanCounter(Long clientId, Long productId);
+
+    Collection<DisbursementData> retrieveLoanDisbursementDetails(Long loanId);
+
+    DisbursementData retrieveLoanDisbursementDetail(Long loanId, Long disbursementId);
+
+    Collection<LoanTermVariationsData> retrieveLoanTermVariations(Long loanId, Integer termType);
+
+    Collection<LoanScheduleAccrualData> retriveScheduleAccrualData();
+
+    LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId);
+
+    LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId);
+
+    Collection<LoanScheduleAccrualData> retrivePeriodicAccrualData(LocalDate tillDate);
+
+    Collection<Long> fetchLoansForInterestRecalculation();
+
+    LoanTransactionData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate onDate);
+
+    Collection<LoanTransactionData> retrieveWaiverLoanTransactions(Long loanId);
+
+    Collection<LoanSchedulePeriodData> fetchWaiverInterestRepaymentData(Long loanId);
+
+    boolean isGuaranteeRequired(Long loanId);
+
+    Date retrieveMinimumDateOfRepaymentTransaction(Long loanId);
+
+    PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId);
+
+    LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
+    
+    Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(Long loanId);
+}
\ No newline at end of file
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
new file mode 100755
index 0000000..5c03080
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -0,0 +1,2016 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestType;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.accountdetails.service.AccountEnumerations;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+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.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
+import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
+import org.apache.fineract.portfolio.fund.data.FundData;
+import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.data.GroupRoleData;
+import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
+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.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final LoanRepository loanRepository;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
+    private final LoanProductReadPlatformService loanProductReadPlatformService;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final GroupReadPlatformService groupReadPlatformService;
+    private final LoanTransactionRepository loanTransactionRepository;
+    private final LoanDropdownReadPlatformService loanDropdownReadPlatformService;
+    private final FundReadPlatformService fundReadPlatformService;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+    private final CalendarReadPlatformService calendarReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final PaginationHelper<LoanAccountData> paginationHelper = new PaginationHelper<>();
+    private final LoanMapper loaanLoanMapper = new LoanMapper();
+    private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+    private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+    private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
+    private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
+    private final LoanUtilService loanUtilService;
+
+    @Autowired
+    public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository,
+            final LoanTransactionRepository loanTransactionRepository,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
+            final LoanProductReadPlatformService loanProductReadPlatformService, final ClientReadPlatformService clientReadPlatformService,
+            final GroupReadPlatformService groupReadPlatformService, final LoanDropdownReadPlatformService loanDropdownReadPlatformService,
+            final FundReadPlatformService fundReadPlatformService, final ChargeReadPlatformService chargeReadPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService, final RoutingDataSource dataSource,
+            final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService,
+            final PaymentTypeReadPlatformService paymentTypeReadPlatformService,
+            final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
+            final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService) {
+        this.context = context;
+        this.loanRepository = loanRepository;
+        this.loanTransactionRepository = loanTransactionRepository;
+        this.applicationCurrencyRepository = applicationCurrencyRepository;
+        this.loanProductReadPlatformService = loanProductReadPlatformService;
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.groupReadPlatformService = groupReadPlatformService;
+        this.loanDropdownReadPlatformService = loanDropdownReadPlatformService;
+        this.fundReadPlatformService = fundReadPlatformService;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+        this.calendarReadPlatformService = calendarReadPlatformService;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+        this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
+        this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
+        this.loanUtilService = loanUtilService;
+    }
+
+    @Override
+    public LoanAccountData retrieveOne(final Long loanId) {
+
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+            final String hierarchy = currentUser.getOffice().getHierarchy();
+            final String hierarchySearchString = hierarchy + "%";
+
+            final LoanMapper rm = new LoanMapper();
+
+            final StringBuilder sqlBuilder = new StringBuilder();
+            sqlBuilder.append("select ");
+            sqlBuilder.append(rm.loanSchema());
+            sqlBuilder.append(" join m_office o on (o.id = c.office_id or o.id = g.office_id) ");
+            sqlBuilder.append(" left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id ");
+            sqlBuilder.append(" where l.id=? and ( o.hierarchy like ? or transferToOffice.hierarchy like ?)");
+
+            return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), rm, new Object[] { loanId, hierarchySearchString,
+                    hierarchySearchString });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new LoanNotFoundException(loanId);
+        }
+    }
+
+    @Override
+    public LoanScheduleData retrieveRepaymentSchedule(final Long loanId,
+            final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData,
+            boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) {
+
+        try {
+            this.context.authenticatedUser();
+
+            final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor(
+                    repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled, totalPaidFeeCharges);
+            final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment";
+
+            return this.jdbcTemplate.query(sql, fullResultsetExtractor, new Object[] { loanId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new LoanNotFoundException(loanId);
+        }
+    }
+
+    @Override
+    public Collection<LoanTransactionData> retrieveLoanTransactions(final Long loanId) {
+        try {
+            this.context.authenticatedUser();
+
+            final LoanTransactionsMapper rm = new LoanTransactionsMapper();
+
+            // retrieve all loan transactions that are not invalid and have not
+            // been 'contra'ed by another transaction
+            // repayments at time of disbursement (e.g. charges)
+
+            /***
+             * TODO Vishwas: Remove references to "Contra" from the codebase
+             ***/
+            final String sql = "select "
+                    + rm.LoanPaymentsSchema()
+                    + " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) and  (tr.is_reversed=0 or tr.manually_adjusted_or_reversed = 1) order by tr.transaction_date ASC,id ";
+            return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public Page<LoanAccountData> retrieveAll(final SearchParameters searchParameters) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.loaanLoanMapper.loanSchema());
+
+        // TODO - for time being this will data scope list of loans returned to
+        // only loans that have a client associated.
+        // to support senario where loan has group_id only OR client_id will
+        // probably require a UNION query
+        // but that at present is an edge case
+        sqlBuilder.append(" join m_office o on o.id = c.office_id");
+        sqlBuilder.append(" left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id ");
+        sqlBuilder.append(" where ( o.hierarchy like ? or transferToOffice.hierarchy like ?)");
+
+        int arrayPos = 2;
+        List<Object> extraCriterias = new ArrayList<>();
+        extraCriterias.add(hierarchySearchString);
+        extraCriterias.add(hierarchySearchString);
+
+        String sqlQueryCriteria = searchParameters.getSqlSearch();
+        if (StringUtils.isNotBlank(sqlQueryCriteria)) {
+            sqlQueryCriteria = sqlQueryCriteria.replaceAll("accountNo", "l.account_no");
+            sqlBuilder.append(" and (").append(sqlQueryCriteria).append(")");
+        }
+
+        if (StringUtils.isNotBlank(searchParameters.getExternalId())) {
+            sqlBuilder.append(" and l.external_id = ?");
+            extraCriterias.add(searchParameters.getExternalId());
+            arrayPos = arrayPos + 1;
+        }
+
+        if (StringUtils.isNotBlank(searchParameters.getAccountNo())) {
+            sqlBuilder.append(" and l.account_no = ?");
+            extraCriterias.add(searchParameters.getAccountNo());
+            arrayPos = arrayPos + 1;
+        }
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final Object[] objectArray = extraCriterias.toArray();
+        final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
+                this.loaanLoanMapper);
+    }
+
+    @Override
+    public LoanAccountData retrieveTemplateWithClientAndProductDetails(final Long clientId, final Long productId) {
+
+        this.context.authenticatedUser();
+
+        final ClientData clientAccount = this.clientReadPlatformService.retrieveOne(clientId);
+        final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant();
+        LoanAccountData loanTemplateDetails = LoanAccountData.clientDefaults(clientAccount.id(), clientAccount.accountNo(),
+                clientAccount.displayName(), clientAccount.officeId(), expectedDisbursementDate);
+
+        if (productId != null) {
+            final LoanProductData selectedProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
+            loanTemplateDetails = LoanAccountData.populateLoanProductDefaults(loanTemplateDetails, selectedProduct);
+        }
+
+        return loanTemplateDetails;
+    }
+
+    @Override
+    public LoanAccountData retrieveTemplateWithGroupAndProductDetails(final Long groupId, final Long productId) {
+
+        this.context.authenticatedUser();
+
+        final GroupGeneralData groupAccount = this.groupReadPlatformService.retrieveOne(groupId);
+        final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant();
+        LoanAccountData loanDetails = LoanAccountData.groupDefaults(groupAccount, expectedDisbursementDate);
+
+        if (productId != null) {
+            final LoanProductData selectedProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
+            loanDetails = LoanAccountData.populateLoanProductDefaults(loanDetails, selectedProduct);
+        }
+
+        return loanDetails;
+    }
+
+    @Override
+    public LoanAccountData retrieveTemplateWithCompleteGroupAndProductDetails(final Long groupId, final Long productId) {
+
+        this.context.authenticatedUser();
+
+        GroupGeneralData groupAccount = this.groupReadPlatformService.retrieveOne(groupId);
+        // get group associations
+        final Collection<ClientData> membersOfGroup = this.clientReadPlatformService.retrieveClientMembersOfGroup(groupId);
+        if (!CollectionUtils.isEmpty(membersOfGroup)) {
+            final Collection<ClientData> activeClientMembers = null;
+            final Collection<CalendarData> calendarsData = null;
+            final CalendarData collectionMeetingCalendar = null;
+            final Collection<GroupRoleData> groupRoles = null;
+            groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, activeClientMembers, groupRoles, calendarsData,
+                    collectionMeetingCalendar);
+        }
+
+        final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant();
+        LoanAccountData loanDetails = LoanAccountData.groupDefaults(groupAccount, expectedDisbursementDate);
+
+        if (productId != null) {
+            final LoanProductData selectedProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
+            loanDetails = LoanAccountData.populateLoanProductDefaults(loanDetails, selectedProduct);
+        }
+
+        return loanDetails;
+    }
+
+    @Override
+    public LoanTransactionData retrieveLoanTransactionTemplate(final Long loanId) {
+
+        this.context.authenticatedUser();
+
+        // TODO - KW - OPTIMIZE - write simple sql query to fetch back date of
+        // possible next transaction date.
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+        final MonetaryCurrency currency = loan.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+
+        final CurrencyData currencyData = applicationCurrency.toData();
+
+        final LocalDate earliestUnpaidInstallmentDate = loan.possibleNextRepaymentDate();
+
+        final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.possibleNextRepaymentInstallment();
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
+        final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+        final BigDecimal outstandingLoanBalance = null;
+        final BigDecimal unrecognizedIncomePortion = null;
+        return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
+                loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment
+                        .getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency)
+                        .getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(),
+                loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion,
+                paymentOptions, null, null, null, outstandingLoanBalance, false);
+    }
+
+    @Override
+    public LoanTransactionData retrieveLoanPrePaymentTemplate(final Long loanId, LocalDate onDate) {
+
+        this.context.authenticatedUser();
+
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+        loan.setHelpers(null, null, loanRepaymentScheduleTransactionProcessorFactory);
+
+        final MonetaryCurrency currency = loan.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+
+        final CurrencyData currencyData = applicationCurrency.toData();
+
+        final LocalDate earliestUnpaidInstallmentDate = LocalDate.now();
+        final LocalDate recalculateFrom = null;
+        final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+        final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchPrepaymentDetail(scheduleGeneratorDTO, onDate);
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
+        final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+        final BigDecimal outstandingLoanBalance = loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount();
+        final BigDecimal unrecognizedIncomePortion = null;
+        return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
+                loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment
+                        .getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency)
+                        .getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(),
+                loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion,
+                paymentOptions, null, null, null, outstandingLoanBalance, false);
+    }
+
+    @Override
+    public LoanTransactionData retrieveWaiveInterestDetails(final Long loanId) {
+
+        AppUser currentUser = this.context.authenticatedUser();
+
+        // TODO - KW -OPTIMIZE - write simple sql query to fetch back overdue
+        // interest that can be waived along with the date of repayment period
+        // interest is overdue.
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+        final MonetaryCurrency currency = loan.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+        final CurrencyData currencyData = applicationCurrency.toData();
+
+        final LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction(DateUtils.getLocalDateTimeOfTenant(),
+                currentUser);
+
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST);
+
+        final BigDecimal amount = waiveOfInterest.getAmount(currency).getAmount();
+        final BigDecimal outstandingLoanBalance = null;
+        final BigDecimal unrecognizedIncomePortion = null;
+        return new LoanTransactionData(null, null, null, transactionType, null, currencyData, waiveOfInterest.getTransactionDate(), amount,
+                null, null, null, null, null, null, null, null, outstandingLoanBalance, unrecognizedIncomePortion, false);
+    }
+
+    @Override
+    public LoanTransactionData retrieveNewClosureDetails() {
+
+        this.context.authenticatedUser();
+        final BigDecimal outstandingLoanBalance = null;
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF);
+        final BigDecimal unrecognizedIncomePortion = null;
+        return new LoanTransactionData(null, null, null, transactionType, null, null, DateUtils.getLocalDateOfTenant(), null, null, null,
+                null, null, null, null, null, null, outstandingLoanBalance, unrecognizedIncomePortion, false);
+
+    }
+
+    @Override
+    public LoanApprovalData retrieveApprovalTemplate(final Long loanId) {
+
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+        return new LoanApprovalData(loan.getProposedPrincipal(), DateUtils.getLocalDateOfTenant());
+
+    }
+
+    @Override
+    public LoanTransactionData retrieveDisbursalTemplate(final Long loanId, boolean paymentDetailsRequired) {
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.DISBURSEMENT);
+        Collection<PaymentTypeData> paymentOptions = null;
+        if (paymentDetailsRequired) {
+            paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+        }
+        return LoanTransactionData.LoanTransactionDataForDisbursalTemplate(transactionType, loan.getExpectedDisbursedOnLocalDateForTemplate(), loan.getDisburseAmountForTemplate(), 
+        		paymentOptions, loan.retriveLastEmiAmount(), loan.getNextPossibleRepaymentDateForRescheduling());
+
+    }
+
+    @Override
+    public LoanTransactionData retrieveLoanTransaction(final Long loanId, final Long transactionId) {
+
+        this.context.authenticatedUser();
+
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+        final MonetaryCurrency currency = loan.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+        final CurrencyData currencyData = applicationCurrency.toData();
+
+        final LoanTransaction transaction = this.loanTransactionRepository.findOne(transactionId);
+        if (transaction == null) { throw new LoanTransactionNotFoundException(transactionId); }
+
+        if (transaction.isNotBelongingToLoanOf(loan)) { throw new LoanTransactionNotFoundException(transactionId, loanId); }
+
+        final LoanTransactionsAccountTransferMapper trasfermapper = new LoanTransactionsAccountTransferMapper();
+        final String sql = "select " + trasfermapper.accountTransferSchema() + " where tr.loan_id = ? and tr.id = ?";
+        final AccountTransferData accountTransferData = this.jdbcTemplate.queryForObject(sql, trasfermapper, loanId, transactionId);
+        return transaction.toData(currencyData, accountTransferData);
+    }
+
+    private static final class LoanMapper implements RowMapper<LoanAccountData> {
+
+        public String loanSchema() {
+            return "l.id as id, l.account_no as accountNo, l.external_id as externalId, l.fund_id as fundId, f.name as fundName,"
+                    + " l.loan_type_enum as loanType, l.loanpurpose_cv_id as loanPurposeId, cv.code_value as loanPurposeName,"
+                    + " lp.id as loanProductId, lp.name as loanProductName, lp.description as loanProductDescription,"
+                    + " lp.is_linked_to_floating_interest_rates as isLoanProductLinkedToFloatingRate, "
+                    + " lp.allow_variabe_installments as isvariableInstallmentsAllowed, "
+                    + " lp.allow_multiple_disbursals as multiDisburseLoan,"
+                    + " lp.can_define_fixed_emi_amount as canDefineInstallmentAmount,"
+                    + " c.id as clientId, c.account_no as clientAccountNo, c.display_name as clientName, c.office_id as clientOfficeId,"
+                    + " g.id as groupId, g.account_no as groupAccountNo, g.display_name as groupName,"
+                    + " g.office_id as groupOfficeId, g.staff_id As groupStaffId , g.parent_id as groupParentId, (select mg.display_name from m_group mg where mg.id = g.parent_id) as centerName, "
+                    + " g.hierarchy As groupHierarchy , g.level_id as groupLevel, g.external_id As groupExternalId, "
+                    + " g.status_enum as statusEnum, g.activation_date as activationDate, "
+                    + " l.submittedon_date as submittedOnDate, sbu.username as submittedByUsername, sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"
+                    + " l.rejectedon_date as rejectedOnDate, rbu.username as rejectedByUsername, rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,"
+                    + " l.withdrawnon_date as withdrawnOnDate, wbu.username as withdrawnByUsername, wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,"
+                    + " l.approvedon_date as approvedOnDate, abu.username as approvedByUsername, abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,"
+                    + " l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate, dbu.username as disbursedByUsername, dbu.firstname as disbursedByFirstname, dbu.lastname as disbursedByLastname,"
+                    + " l.closedon_date as closedOnDate, cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, l.writtenoffon_date as writtenOffOnDate, "
+                    + " l.expected_firstrepaymenton_date as expectedFirstRepaymentOnDate, l.interest_calculated_from_date as interestChargedFromDate, l.expected_maturedon_date as expectedMaturityDate, "
+                    + " l.principal_amount_proposed as proposedPrincipal, l.principal_amount as principal, l.approved_principal as approvedPrincipal, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery,"
+                    + " l.grace_on_principal_periods as graceOnPrincipalPayment, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,l.grace_on_arrears_ageing as graceOnArrearsAgeing,"
+                    + " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, "
+                    + " l.repayment_period_frequency_enum as repaymentFrequencyType, l.repayment_frequency_nth_day_enum as repaymentFrequencyNthDayType, l.repayment_frequency_day_of_week_enum as repaymentFrequencyDayOfWeekType, l.interest_period_frequency_enum as interestRateFrequencyType, "
+                    + " l.term_frequency as termFrequency, l.term_period_frequency_enum as termPeriodFrequencyType, "
+                    + " l.amortization_method_enum as amortizationType, l.interest_method_enum as interestType, l.interest_calculated_in_period_enum as interestCalculationPeriodType,"
+                    + " l.allow_partial_period_interest_calcualtion as allowPartialPeriodInterestCalcualtion,"
+                    + " l.loan_status_id as lifeCycleStatusId, l.loan_transaction_strategy_id as transactionStrategyId, "
+                    + " lps.name as transactionStrategyName, "
+                    + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, "
+                    + " l.loan_officer_id as loanOfficerId, s.display_name as loanOfficerName, "
+                    + " l.principal_disbursed_derived as principalDisbursed,"
+                    + " l.principal_repaid_derived as principalPaid,"
+                    + " l.principal_writtenoff_derived as principalWrittenOff,"
+                    + " l.principal_outstanding_derived as principalOutstanding,"
+                    + " l.interest_charged_derived as interestCharged,"
+                    + " l.interest_repaid_derived as interestPaid,"
+                    + " l.interest_waived_derived as interestWaived,"
+                    + " l.interest_writtenoff_derived as interestWrittenOff,"
+                    + " l.interest_outstanding_derived as interestOutstanding,"
+                    + " l.fee_charges_charged_derived as feeChargesCharged,"
+                    + " l.total_charges_due_at_disbursement_derived as feeChargesDueAtDisbursementCharged,"
+                    + " l.fee_charges_repaid_derived as feeChargesPaid,"
+                    + " l.fee_charges_waived_derived as feeChargesWaived,"
+                    + " l.fee_charges_writtenoff_derived as feeChargesWrittenOff,"
+                    + " l.fee_charges_outstanding_derived as feeChargesOutstanding,"
+                    + " l.penalty_charges_charged_derived as penaltyChargesCharged,"
+                    + " l.penalty_charges_repaid_derived as penaltyChargesPaid,"
+                    + " l.penalty_charges_waived_derived as penaltyChargesWaived,"
+                    + " l.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff,"
+                    + " l.penalty_charges_outstanding_derived as penaltyChargesOutstanding,"
+                    + " l.total_expected_repayment_derived as totalExpectedRepayment,"
+                    + " l.total_repayment_derived as totalRepayment,"
+                    + " l.total_expected_costofloan_derived as totalExpectedCostOfLoan,"
+                    + " l.total_costofloan_derived as totalCostOfLoan,"
+                    + " l.total_waived_derived as totalWaived,"
+                    + " l.total_writtenoff_derived as totalWrittenOff,"
+                    + " l.total_outstanding_derived as totalOutstanding,"
+                    + " l.total_overpaid_derived as totalOverpaid,"
+                    + " l.fixed_emi_amount as fixedEmiAmount,"
+                    + " l.max_outstanding_loan_balance as outstandingLoanBalance,"
+                    + " la.principal_overdue_derived as principalOverdue,"
+                    + " la.interest_overdue_derived as interestOverdue,"
+                    + " la.fee_charges_overdue_derived as feeChargesOverdue,"
+                    + " la.penalty_charges_overdue_derived as penaltyChargesOverdue,"
+                    + " la.total_overdue_derived as totalOverdue,"
+                    + " la.overdue_since_date_derived as overdueSinceDate,"
+                    + " l.sync_disbursement_with_meeting as syncDisbursementWithMeeting,"
+                    + " l.loan_counter as loanCounter, l.loan_product_counter as loanProductCounter,"
+                    + " l.is_npa as isNPA, l.days_in_month_enum as daysInMonth, l.days_in_year_enum as daysInYear, "
+                    + " l.interest_recalculation_enabled as isInterestRecalculationEnabled, "
+                    + " lir.id as lirId, lir.loan_id as loanId, lir.compound_type_enum as compoundType, lir.reschedule_strategy_enum as rescheduleStrategy, "
+                    + " lir.rest_frequency_type_enum as restFrequencyEnum, lir.rest_frequency_interval as restFrequencyInterval, "
+                    + " lir.rest_freqency_date as restFrequencyDate, "
+                    + " lir.compounding_frequency_type_enum as compoundingFrequencyEnum, lir.compounding_frequency_interval as compoundingInterval, "
+                    + " lir.compounding_freqency_date as compoundingFrequencyDate, "
+                    + " l.is_floating_interest_rate as isFloatingInterestRate, "
+                    + " l.interest_rate_differential as interestRateDifferential, "
+                    + " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement, "
+                    + " lpvi.minimum_gap as minimuminstallmentgap, lpvi.maximum_gap as maximuminstallmentgap "
+                    + " from m_loan l" //
+                    + " join m_product_loan lp on lp.id = l.product_id" //
+                    + " left join m_loan_recalculation_details lir on lir.loan_id = l.id "
+                    + " join m_currency rc on rc.`code` = l.currency_code" //
+                    + " left join m_client c on c.id = l.client_id" //
+                    + " left join m_group g on g.id = l.group_id" //
+                    + " left join m_loan_arrears_aging la on la.loan_id = l.id" //
+                    + " left join m_fund f on f.id = l.fund_id" //
+                    + " left join m_staff s on s.id = l.loan_officer_id" //
+                    + " left join m_appuser sbu on sbu.id = l.submittedon_userid"
+                    + " left join m_appuser rbu on rbu.id = l.rejectedon_userid"
+                    + " left join m_appuser wbu on wbu.id = l.withdrawnon_userid"
+                    + " left join m_appuser abu on abu.id = l.approvedon_userid"
+                    + " left join m_appuser dbu on dbu.id = l.disbursedon_userid"
+                    + " left join m_appuser cbu on cbu.id = l.closedon_userid"
+                    + " left join m_code_value cv on cv.id = l.loanpurpose_cv_id"
+                    + " left join ref_loan_transaction_processing_strategy lps on lps.id = l.loan_transaction_strategy_id"
+                    + " left join m_product_loan_variable_installment_config lpvi on lpvi.loan_product_id = l.product_id";
+
+        }
+
+        @Override
+        public LoanAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final String externalId = rs.getString("externalId");
+
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final String clientAccountNo = rs.getString("clientAccountNo");
+            final Long clientOfficeId = JdbcSupport.getLong(rs, "clientOfficeId");
+            final String clientName = rs.getString("clientName");
+
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final String groupName = rs.getString("groupName");
+            final String groupAccountNo = rs.getString("groupAccountNo");
+            final String groupExternalId = rs.getString("groupExternalId");
+            final Long groupOfficeId = JdbcSupport.getLong(rs, "groupOfficeId");
+            final Long groupStaffId = JdbcSupport.getLong(rs, "groupStaffId");
+            final Long groupParentId = JdbcSupport.getLong(rs, "groupParentId");
+            final String centerName = rs.getString("centerName");
+            final String groupHierarchy = rs.getString("groupHierarchy");
+            final String groupLevel = rs.getString("groupLevel");
+
+            final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType");
+            final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeId);
+
+            final Long fundId = JdbcSupport.getLong(rs, "fundId");
+            final String fundName = rs.getString("fundName");
+
+            final Long loanOfficerId = JdbcSupport.getLong(rs, "loanOfficerId");
+            final String loanOfficerName = rs.getString("loanOfficerName");
+
+            final Long loanPurposeId = JdbcSupport.getLong(rs, "loanPurposeId");
+            final String loanPurposeName = rs.getString("loanPurposeName");
+
+            final Long loanProductId = JdbcSupport.getLong(rs, "loanProductId");
+            final String loanProductName = rs.getString("loanProductName");
+            final String loanProductDescription = rs.getString("loanProductDescription");
+            final boolean isLoanProductLinkedToFloatingRate = rs.getBoolean("isLoanProductLinkedToFloatingRate");
+            final Boolean multiDisburseLoan = rs.getBoolean("multiDisburseLoan");
+            final Boolean canDefineInstallmentAmount = rs.getBoolean("canDefineInstallmentAmount");
+            final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance");
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate");
+            final String rejectedByUsername = rs.getString("rejectedByUsername");
+            final String rejectedByFirstname = rs.getString("rejectedByFirstname");
+            final String rejectedByLastname = rs.getString("rejectedByLastname");
+
+            final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate");
+            final String withdrawnByUsername = rs.getString("withdrawnByUsername");
+            final String withdrawnByFirstname = rs.getString("withdrawnByFirstname");
+            final String withdrawnByLastname = rs.getString("withdrawnByLastname");
+
+            final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
+            final String approvedByUsername = rs.getString("approvedByUsername");
+            final String approvedByFirstname = rs.getString("approvedByFirstname");
+            final String approvedByLastname = rs.getString("approvedByLastname");
+
+            final LocalDate expectedDisbursementDate = JdbcSupport.getLocalDate(rs, "expectedDisbursementDate");
+            final LocalDate actualDisbursementDate = JdbcSupport.getLocalDate(rs, "actualDisbursementDate");
+            final String disbursedByUsername = rs.getString("disbursedByUsername");
+            final String disbursedByFirstname = rs.getString("disbursedByFirstname");
+            final String disbursedByLastname = rs.getString("disbursedByLastname");
+
+            final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+            final String closedByUsername = rs.getString("closedByUsername");
+            final String closedByFirstname = rs.getString("closedByFirstname");
+            final String closedByLastname = rs.getString("closedByLastname");
+
+            final LocalDate writtenOffOnDate = JdbcSupport.getLocalDate(rs, "writtenOffOnDate");
+
+            final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate");
+
+            final Boolean isvariableInstallmentsAllowed = rs.getBoolean("isvariableInstallmentsAllowed");
+            final Integer minimumGap = rs.getInt("minimuminstallmentgap");
+            final Integer maximumGap = rs.getInt("maximuminstallmentgap");
+
+            final LoanApplicationTimelineData timeline = new LoanApplicationTimelineData(submittedOnDate, submittedByUsername,
+                    submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname,
+                    withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername,
+                    approvedByFirstname, approvedByLastname, expectedDisbursementDate, actualDisbursementDate, disbursedByUsername,
+                    disbursedByFirstname, disbursedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname,
+                    expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname);
+
+            final BigDecimal principal = rs.getBigDecimal("principal");
+            final BigDecimal approvedPrincipal = rs.getBigDecimal("approvedPrincipal");
+            final BigDecimal proposedPrincipal = rs.getBigDecimal("proposedPrincipal");
+            final BigDecimal totalOverpaid = rs.getBigDecimal("totalOverpaid");
+            final BigDecimal inArrearsTolerance = rs.getBigDecimal("inArrearsTolerance");
+
+            final Integer numberOfRepayments = JdbcSupport.getInteger(rs, "numberOfRepayments");
+            final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaymentEvery");
+            final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod");
+            final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate");
+            final BigDecimal interestRateDifferential = rs.getBigDecimal("interestRateDifferential");
+            final boolean isFloatingInterestRate = rs.getBoolean("isFloatingInterestRate");
+
+            final Integer graceOnPrincipalPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnPrincipalPayment");
+            final Integer graceOnInterestPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestPayment");
+            final Integer graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged");
+            final Integer graceOnArrearsAgeing = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnArrearsAgeing");
+
+            final Integer termFrequency = JdbcSupport.getInteger(rs, "termFrequency");
+            final Integer termPeriodFrequencyTypeInt = JdbcSupport.getInteger(rs, "termPeriodFrequencyType");
+            final EnumOptionData termPeriodFrequencyType = LoanEnumerations.termFrequencyType(termPeriodFrequencyTypeInt);
+
+            final int repaymentFrequencyTypeInt = JdbcSupport.getInteger(rs, "repaymentFrequencyType");
+            final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(repaymentFrequencyTypeInt);
+
+            final Integer repaymentFrequencyNthDayTypeInt = JdbcSupport.getInteger(rs, "repaymentFrequencyNthDayType");
+            final EnumOptionData repaymentFrequencyNthDayType = LoanEnumerations
+                    .repaymentFrequencyNthDayType(repaymentFrequencyNthDayTypeInt);
+
+            final Integer repaymentFrequencyDayOfWeekTypeInt = JdbcSupport.getInteger(rs, "repaymentFrequencyDayOfWeekType");
+            final EnumOptionData repaymentFrequencyDayOfWeekType = LoanEnumerations
+                    .repaymentFrequencyDayOfWeekType(repaymentFrequencyDayOfWeekTypeInt);
+
+            final int interestRateFrequencyTypeInt = JdbcSupport.getInteger(rs, "interestRateFrequencyType");
+            final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(interestRateFrequencyTypeInt);
+
+            final Long transactionStrategyId = JdbcSupport.getLong(rs, "transactionStrategyId");
+            final String transactionStrategyName = rs.getString("transactionStrategyName");
+
+            final int amortizationTypeInt = JdbcSupport.getInteger(rs, "amortizationType");
+            final int interestTypeInt = JdbcSupport.getInteger(rs, "interestType");
+            final int interestCalculationPeriodTypeInt = JdbcSupport.getInteger(rs, "interestCalculationPeriodType");
+
+            final EnumOptionData amortizationType = LoanEnumerations.amortizationType(amortizationTypeInt);
+            final EnumOptionData interestType = LoanEnumerations.interestType(interestTypeInt);
+            final EnumOptionData interestCalculationPeriodType = LoanEnumerations
+                    .interestCalculationPeriodType(interestCalculationPeriodTypeInt);
+            final Boolean allowPartialPeriodInterestCalcualtion = rs.getBoolean("allowPartialPeriodInterestCalcualtion");
+
+            final Integer lifeCycleStatusId = JdbcSupport.getInteger(rs, "lifeCycleStatusId");
+            final LoanStatusEnumData status = LoanEnumerations.status(lifeCycleStatusId);
+
+            // settings
+            final LocalDate expectedFirstRepaymentOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstRepaymentOnDate");
+            final LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate");
+
+            final Boolean syncDisbursementWithMeeting = rs.getBoolean("syncDisbursementWithMeeting");
+
+            final BigDecimal feeChargesDueAtDisbursementCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
+                    "feeChargesDueAtDisbursementCharged");
+            LoanSummaryData loanSummary = null;
+            Boolean inArrears = false;
+            if (status.id().intValue() >= 300) {
+
+                // loan summary
+                final BigDecimal principalDisbursed = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDisbursed");
+                final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid");
+                final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
+                final BigDecimal principalOutstanding = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalOutstanding");
+                final BigDecimal principalOverdue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalOverdue");
+
+                final BigDecimal interestCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestCharged");
+                final BigDecimal interestPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestPaid");
+                final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
+                final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff");
+                final BigDecimal interestOutstanding = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestOutstanding");
+                final BigDecimal interestOverdue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestOverdue");
+
+                final BigDecimal feeChargesCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesCharged");
+                final BigDecimal feeChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesPaid");
+                final BigDecimal feeChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWaived");
+                final BigDecimal feeChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWrittenOff");
+                final BigDecimal feeChargesOutstanding = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesOutstanding");
+                final BigDecimal feeChargesOverdue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesOverdue");
+
+                final BigDecimal penaltyChargesCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesCharged");
+                final BigDecimal penaltyChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesPaid");
+                final BigDecimal penaltyChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWaived");
+                final BigDecimal penaltyChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWrittenOff");
+                final BigDecimal penaltyChargesOutstanding = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesOutstanding");
+                final BigDecimal penaltyChargesOverdue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesOverdue");
+
+                final BigDecimal totalExpectedRepayment = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalExpectedRepayment");
+                final BigDecimal totalRepayment = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalRepayment");
+                final BigDecimal totalExpectedCostOfLoan = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalExpectedCostOfLoan");
+                final BigDecimal totalCostOfLoan = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalCostOfLoan");
+                final BigDecimal totalWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalWaived");
+                final BigDecimal totalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalWrittenOff");
+                final BigDecimal totalOutstanding = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalOutstanding");
+                final BigDecimal totalOverdue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalOverdue");
+
+                final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate");
+                if (overdueSinceDate != null) {
+                    inArrears = true;
+                }
+
+                loanSummary = new LoanSummaryData(currencyData, principalDisbursed, principalPaid, principalWrittenOff,
+                        principalOutstanding, principalOverdue, interestCharged, interestPaid, interestWaived, interestWrittenOff,
+                        interestOutstanding, interestOverdue, feeChargesCharged, feeChargesDueAtDisbursementCharged, feeChargesPaid,
+                        feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, feeChargesOverdue, penaltyChargesCharged,
+                        penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding,
+                        penaltyChargesOverdue, totalExpectedRepayment, totalRepayment, totalExpectedCostOfLoan, totalCostOfLoan,
+                        totalWaived, totalWrittenOff, totalOutstanding, totalOverdue, overdueSinceDate);
+            }
+
+            GroupGeneralData groupData = null;
+            if (groupId != null) {
+                final Integer groupStatusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+                final EnumOptionData groupStatus = ClientEnumerations.status(groupStatusEnum);
+                final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate");
+                groupData = GroupGeneralData.instance(groupId, groupAccountNo, groupName, groupExternalId, groupStatus, activationDate,
+                        groupOfficeId, null, groupParentId, centerName, groupStaffId, null, groupHierarchy, groupLevel, null);
+            }
+
+            final Integer loanCounter = JdbcSupport.getInteger(rs, "loanCounter");
+            final Integer loanProductCounter = JdbcSupport.getInteger(rs, "loanProductCounter");
+            final BigDecimal fixedEmiAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "fixedEmiAmount");
+            final Boolean isNPA = rs.getBoolean("isNPA");
+
+            final int daysInMonth = JdbcSupport.getInteger(rs, "daysInMonth");
+            final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(daysInMonth);
+            final int daysInYear = JdbcSupport.getInteger(rs, "daysInYear");
+            final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(daysInYear);
+            final boolean isInterestRecalculationEnabled = rs.getBoolean("isInterestRecalculationEnabled");
+            final Boolean createStandingInstructionAtDisbursement = rs.getBoolean("createStandingInstructionAtDisbursement");
+
+            LoanInterestRecalculationData interestRecalculationData = null;
+            if (isInterestRecalculationEnabled) {
+
+                final Long lprId = JdbcSupport.getLong(rs, "lirId");
+                final Long productId = JdbcSupport.getLong(rs, "loanId");
+                final int compoundTypeEnumValue = JdbcSupport.getInteger(rs, "compoundType");
+                final EnumOptionData interestRecalculationCompoundingType = LoanEnumerations
+                        .interestRecalculationCompoundingType(compoundTypeEnumValue);
+                final int rescheduleStrategyEnumValue = JdbcSupport.getInteger(rs, "rescheduleStrategy");
+                final EnumOptionData rescheduleStrategyType = LoanEnumerations.rescheduleStrategyType(rescheduleStrategyEnumValue);
+                final CalendarData calendarData = null;
+                final int restFrequencyEnumValue = JdbcSupport.getInteger(rs, "restFrequencyEnum");
+                final EnumOptionData restFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(restFrequencyEnumValue);
+                final int restFrequencyInterval = JdbcSupport.getInteger(rs, "restFrequencyInterval");
+                final LocalDate restFrequencyDate = JdbcSupport.getLocalDate(rs, "restFrequencyDate");
+                final CalendarData compoundingCalendarData = null;
+                final Integer compoundingFrequencyEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyEnum");
+                EnumOptionData compoundingFrequencyType = null;
+                if (compoundingFrequencyEnumValue != null) {
+                    compoundingFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(compoundingFrequencyEnumValue);
+                }
+                final Integer compoundingInterval = JdbcSupport.getInteger(rs, "compoundingInterval");
+                final LocalDate compoundingFrequencyDate = JdbcSupport.getLocalDate(rs, "compoundingFrequencyDate");
+
+                interestRecalculationData = new LoanInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType,
+                        rescheduleStrategyType, calendarData, restFrequencyType, restFrequencyInterval, restFrequencyDate,
+                        compoundingCalendarData, compoundingFrequencyType, compoundingInterval, compoundingFrequencyDate);
+            }
+
+            return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName,
+                    clientOfficeId, groupData, loanType, loanProductId, loanProductName, loanProductDescription,
+                    isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
+                    currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency,
+                    termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType,
+                    repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, amortizationType,
+                    interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate,
+                    interestRateDifferential, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion,
+                    expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
+                    interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting,
+                    loanCounter, loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance,
+                    inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
+                    interestRecalculationData, createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap,
+                    maximumGap);
+        }
+    }
+
+    private static final class MusoniOverdueLoanScheduleMapper implements RowMapper<OverdueLoanScheduleData> {
+
+        public String schema() {
+            return " ls.loan_id as loanId, ls.installment as period, ls.fromdate as fromDate, ls.duedate as dueDate, ls.obligations_met_on_date as obligationsMetOnDate, ls.completed_derived as complete,"
+                    + " ls.principal_amount as principalDue, ls.principal_completed_derived as principalPaid, ls.principal_writtenoff_derived as principalWrittenOff, "
+                    + " ls.interest_amount as interestDue, ls.interest_completed_derived as interestPaid, ls.interest_waived_derived as interestWaived, ls.interest_writtenoff_derived as interestWrittenOff, "
+                    + " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, "
+                    + " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff, "
+                    + " ls.total_paid_in_advance_derived as totalPaidInAdvanceForPeriod, ls.total_paid_late_derived as totalPaidLateForPeriod, "
+                    + " mc.amount,mc.id as chargeId "
+                    + " from m_loan_repayment_schedule ls "
+                    + " inner join m_loan ml on ml.id = ls.loan_id "
+                    + " join m_product_loan_charge plc on plc.product_loan_id = ml.product_id "
+                    + " join m_charge mc on mc.id = plc.charge_id ";
+
+        }
+
+        @Override
+        public OverdueLoanScheduleData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long chargeId = rs.getLong("chargeId");
+            final Long loanId = rs.getLong("loanId");
+            final BigDecimal amount = rs.getBigDecimal("amount");
+            final String dateFormat = "yyyy-MM-dd";
+            final String dueDate = rs.getString("dueDate");
+            final String locale = "en_GB";
+
+            final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDue");
+            final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid");
+            final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
+
+            final BigDecimal principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff);
+
+            final BigDecimal interestExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue");
+            final BigDecimal interestPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestPaid");
+            final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
+            final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff");
+
+            final BigDecimal interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff);
+            final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid);
+
+            final Integer installmentNumber = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "period");
+            final OverdueLoanScheduleData overdueLoanScheduleData = new OverdueLoanScheduleData(loanId, chargeId, dueDate, amount,
+                    dateFormat, locale, principalOutstanding, interestOutstanding, installmentNumber);
+
+            return overdueLoanScheduleData;
+        }
+    }
+
+    private static final class LoanScheduleResultSetExtractor implements ResultSetExtractor<LoanScheduleData> {
+
+        private final CurrencyData currency;
+        private final DisbursementData disbursement;
+        private final BigDecimal totalFeeChargesDueAtDisbursement;
+        private final Collection<DisbursementData> disbursementData;
+        private LocalDate lastDueDate;
+        private BigDecimal outstandingLoanPrincipalBalance;
+        private boolean excludePastUndisbursed;
+        private BigDecimal totalPaidFeeCharges;
+
+        public LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
+                Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) {
+            this.currency = repaymentScheduleRelatedLoanData.getCurrency();
+            this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
+            this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
+            this.lastDueDate = this.disbursement.disbursementDate();
+            this.outstandingLoanPrincipalBalance = this.disbursement.amount();
+            this.disbursementData = disbursementData;
+            this.excludePastUndisbursed = isInterestRecalculationEnabled;
+            this.totalPaidFeeCharges = totalPaidFeeCharges;
+        }
+
+        public String schema() {
+
+            return " ls.loan_id as loanId, ls.installment as period, ls.fromdate as fromDate, ls.duedate as dueDate, ls.obligations_met_on_date as obligationsMetOnDate, ls.completed_derived as complete,"
+                    + " ls.principal_amount as principalDue, ls.principal_completed_derived as principalPaid, ls.principal_writtenoff_derived as principalWrittenOff, "
+                    + " ls.interest_amount as interestDue, ls.interest_completed_derived as interestPaid, ls.interest_waived_derived as interestWaived, ls.interest_writtenoff_derived as interestWrittenOff, "
+                    + " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, "
+                    + " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff, "
+                    + " ls.total_paid_in_advance_derived as totalPaidInAdvanceForPeriod, ls.total_paid_late_derived as totalPaidLateForPeriod "
+                    + " from m_loan_repayment_schedule ls ";
+        }
+
+        @Override
+        public LoanScheduleData extractData(final ResultSet rs) throws SQLException, DataAccessException {
+
+            final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(
+                    this.disbursement.disbursementDate(), this.disbursement.amount(), this.totalFeeChargesDueAtDisbursement,
+                    this.disbursement.isDisbursed());
+
+            final Collection<LoanSchedulePeriodData> periods = new ArrayList<>();
+            final MonetaryCurrency monCurrency = new MonetaryCurrency(this.currency.code(), this.currency.decimalPlaces(),
+                    this.currency.currencyInMultiplesOf());
+            BigDecimal totalPrincipalDisbursed = BigDecimal.ZERO;
+            if (disbursementData == null || disbursementData.isEmpty()) {
+                periods.add(disbursementPeriod);
+                totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.amount()).getAmount();
+            } else {
+                if (!this.disbursement.isDisbursed()) {
+                    excludePastUndisbursed = false;
+                }
+                this.outstandingLoanPrincipalBalance = BigDecimal.ZERO;
+            }
+
+            Money totalPrincipalExpected = Money.zero(monCurrency);
+            Money totalPrincipalPaid = Money.zero(monCurrency);
+            Money totalInterestCharged = Money.zero(monCurrency);
+            Money totalFeeChargesCharged = Money.zero(monCurrency);
+            Money totalPenaltyChargesCharged = Money.zero(monCurrency);
+            Money totalWaived = Money.zero(monCurrency);
+            Money totalWrittenOff = Money.zero(monCurrency);
+            Money totalRepaymentExpected = Money.zero(monCurrency);
+            Money totalRepayment = Money.zero(monCurrency);
+            Money totalPaidInAdvance = Money.zero(monCurrency);
+            Money totalPaidLate = Money.zero(monCurrency);
+            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());
+            totalOutstanding = totalOutstanding.plus(disbursementPeriod.feeChargesDue()).minus(disbursementPeriod.feeChargesPaid());
+
+            Integer loanTermInDays = Integer.valueOf(0);
+            while (rs.next()) {
+
+                final Long loanId = rs.getLong("loanId");
+                final Integer period = JdbcSupport.getInteger(rs, "period");
+                LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromDate");
+                final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
+                final LocalDate obligationsMetOnDate = JdbcSupport.getLocalDate(rs, "obligationsMetOnDate");
+                final boolean complete = rs.getBoolean("complete");
+                if (disbursementData != null) {
+                    BigDecimal principal = BigDecimal.ZERO;
+                    for (DisbursementData data : disbursementData) {
+                        if (fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate)) {
+                            principal = principal.add(data.amount());
+                            if (data.getChargeAmount() == null) {
+                                final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
+                                        data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed());
+                                periods.add(periodData);
+                            } else {
+                                final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
+                                        data.disbursementDate(), data.amount(), data.getChargeAmount(), data.isDisbursed());
+                                periods.add(periodData);
+                            }
+                            this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
+                        } else if (data.isDueForDisbursement(fromDate, dueDate)) {
+                            if (!excludePastUndisbursed
+                                    || (excludePastUndisbursed && (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) {
+                                principal = principal.add(data.amount());
+                                if (data.getChargeAmount() == null) {
+                                    final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
+                                            data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed());
+                                    periods.add(periodData);
+                                } else {
+                                    final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
+                                            data.disbursementDate(), data.amount(), data.getChargeAmount(), data.isDisbursed());
+                                    periods.add(periodData);
+                                }
+                                this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
+                            }
+                        }
+                    }
+                    totalPrincipalDisbursed = totalPrincipalDisbursed.add(principal);
+                }
+
+                Integer daysInPeriod = Integer.valueOf(0);
+                if (fromDate != null) {
+                    daysInPeriod = Days.daysBetween(fromDate, dueDate).getDays();
+                    loanTermInDays = Integer.valueOf(loanTermInDays.intValue() + daysInPeriod.intValue());
+                }
+
+                final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDue");
+                totalPrincipalExpected = totalPrincipalExpected.plus(principalDue);
+                final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid");
+                totalPrincipalPaid = totalPrincipalPaid.plus(principalPaid);
+                final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
+
+                final BigDecimal principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff);
+
+                final BigDecimal interestExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue");
+                totalInterestCharged = totalInterestCharged.plus(interestExpectedDue);
+                final BigDecimal interestPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestPaid");
+                final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
+                final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff");
+                final BigDecimal totalInstallmentAmount = totalPrincipalPaid.zero().plus(principalDue).plus(interestExpectedDue)
+                        .getAmount();
+
+                final BigDecimal interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff);
+                final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid);
+
+                final BigDecimal feeChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesDue");
+                totalFeeChargesCharged = totalFeeChargesCharged.plus(feeChargesExpectedDue);
+                final BigDecimal feeChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesPaid");
+                final BigDecimal feeChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWaived");
+                final BigDecimal feeChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWrittenOff");
+
+                final BigDecimal feeChargesActualDue = feeChargesExpectedDue.subtract(feeChargesWaived).subtract(feeChargesWrittenOff);
+                final BigDecimal feeChargesOutstanding = feeChargesActualDue.subtract(feeChargesPaid);
+
+                final BigDecimal penaltyChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesDue");
+                totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(penaltyChargesExpectedDue);
+                final BigDecimal penaltyChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesPaid");
+                final BigDecimal penaltyChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWaived");
+                final BigDecimal penaltyChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWrittenOff");
+
+                final BigDecimal totalPaidInAdvanceForPeriod = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
+                        "totalPaidInAdvanceForPeriod");
+                final BigDecimal totalPaidLateForPeriod = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalPaidLateForPeriod");
+
+                final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived).subtract(
+                        penaltyChargesWrittenOff);
+                final BigDecimal penaltyChargesOutstanding = penaltyChargesActualDue.subtract(penaltyChargesPaid);
+
+                final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add(
+                        penaltyChargesExpectedDue);
+
+                final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod);
+                final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid).add(feeChargesPaid).add(penaltyChargesPaid);
+                final BigDecimal totalWaivedForPeriod = interestWaived.add(feeChargesWaived).add(penaltyChargesWaived);
+                totalWaived = totalWaived.plus(totalWaivedForPeriod);
+                final BigDecimal totalWrittenOffForPeriod = principalWrittenOff.add(interestWrittenOff).add(feeChargesWrittenOff)
+                        .add(penaltyChargesWrittenOff);
+                totalWrittenOff = totalWrittenOff.plus(totalWrittenOffForPeriod);
+                final BigDecimal totalOutstandingForPeriod = principalOutstanding.add(interestOutstanding).add(feeChargesOutstanding)
+                        .add(penaltyChargesOutstanding);
+
+                final BigDecimal totalActualCostOfLoanForPeriod = interestActualDue.add(feeChargesActualDue).add(penaltyChargesActualDue);
+
+                totalRepaymentExpected = totalRepaymentExpected.plus(totalDueForPeriod);
+                totalRepayment = totalRepayment.plus(totalPaidForPeriod);
+                totalPaidInAdvance = totalPaidInAdvance.plus(totalPaidInAdvanceForPeriod);
+                totalPaidLate = totalPaidLate.plus(totalPaidLateForPeriod);
+                totalOutstanding = totalOutstanding.plus(totalOutstandingForPeriod);
+
+                if (fromDate == null) {
+                    fromDate = this.lastDueDate;
+                }
+                final BigDecimal outstandingPrincipalBalanceOfLoan = this.outstandingLoanPrincipalBalance.subtract(principalDue);
+
+                // update based on current period values
+                this.lastDueDate = dueDate;
+                this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.subtract(principalDue);
+
+                final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate,
+                        dueDate, obligationsMetOnDate, complete, principalDue, principalPaid, principalWrittenOff, principalOutstanding,
+                        outstandingPrincipalBalanceOfLoan, interestExpectedDue, interestPaid, interestWaived, interestWrittenOff,
+                        interestOutstanding, feeChargesExpectedDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff,
+                        feeChargesOutstanding, penaltyChargesExpectedDue, penaltyChargesPaid, penaltyChargesWaived,
+                        penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaidForPeriod,
+                        totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod,
+                        totalOutstandingForPeriod, totalActualCostOfLoanForPeriod, totalInstallmentAmount);
+
+                periods.add(periodData);
+            }
+
+            return new LoanScheduleData(this.currency, periods, loanTermInDays, totalPrincipalDisbursed,
+                    totalPrincipalExpected.getAmount(), totalPrincipalPaid.getAmount(), totalInterestCharged.getAmount(),
+                    totalFeeChargesCharged.getAmount(), totalPenaltyChargesCharged.getAmount(), totalWaived.getAmount(),
+                    totalWrittenOff.getAmount(), totalRepaymentExpected.getAmount(), totalRepayment.getAmount(),
+                    totalPaidInAdvance.getAmount(), totalPaidLate.getAmount(), totalOutstanding.getAmount());
+        }
+
+    }
+
+    private static final class LoanTransactionsMapper implements RowMapper<LoanTransactionData> {
+
+        public String LoanPaymentsSchema() {
+
+            return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as `date`, tr.amount as total, "
+                    + " tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, "
+                    + " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, "
+                    + " tr.overpayment_portion_derived as overpayment, tr.outstanding_loan_balance_derived as outstandingLoanBalance, "
+                    + " tr.unrecognized_income_portion as unrecognizedIncome,"
+                    + " tr.submitted_on_date as submittedOnDate, "
+                    + " tr.manually_adjusted_or_reversed as manuallyReversed, "
+                    + " pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "
+                    + " pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "
+                    + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, "
+                    + " rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, "
+                    + " pt.value as paymentTypeName, tr.external_id as externalId, tr.office_id as officeId, office.name as officeName, "
+                    + " fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,"
+                    + " fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,"
+                    + " fromtran.description as fromTransferDescription,"
+                    + " totran.id as toTransferId, totran.is_reversed as toTransferReversed,"
+                    + " totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,"
+                    + " totran.description as toTransferDescription " + " from m_loan l join m_loan_transaction tr on tr.loan_id = l.id"
+                    + " join m_currency rc on rc.`code` = l.currency_code "
+                    + " left JOIN m_payment_detail pd ON tr.payment_detail_id = pd.id"
+                    + " left join m_payment_type pt on pd.payment_type_id = pt.id" + " left join m_office office on office.id=tr.office_id"
+                    + " left join m_account_transfer_transaction fromtran on fromtran.from_loan_transaction_id = tr.id "
+                    + " left join m_account_transfer_transaction totran on totran.to_loan_transaction_id = tr.id ";
+        }
+
+        @Override
+        public LoanTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final Long id = rs.getLong("id");
+            final Long officeId = rs.getLong("officeId");
+            final String officeName = rs.getString("officeName");
+            final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
+            final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(transactionTypeInt);
+            final boolean manuallyReversed = rs.getBoolean("manuallyReversed");
+
+            PaymentDetailData paymentDetailData = null;
+
+            if (transactionType.isPaymentOrReceipt()) {
+                final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType");
+                if (paymentTypeId != null) {
+                    final String typeName = rs.getString("paymentTypeName");
+                    final PaymentTypeData paymentType = PaymentTypeData.instance(paymentTypeId, typeName);
+                    final String accountNumber = rs.getString("accountNumber");
+                    final String checkNumber = rs.getString("checkNumber");
+                    final String routingCode = rs.getString("routingCode");
+                    final String receiptNumber = rs.getString("receiptNumber");
+                    final String bankNumber = rs.getString("bankNumber");
+                    paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber,
+                            bankNumber);
+                }
+            }
+            final LocalDate date = JdbcSupport.getLocalDate(rs, "date");
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final BigDecimal totalAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "total");
+            final BigDecimal principalPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principal");
+            final BigDecimal interestPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interest");
+            final BigDecimal feeChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fees");
+            final BigDecimal penaltyChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalties");
+            final BigDecimal overPaymentPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overpayment");
+            final BigDecimal unrecognizedIncomePortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "unrecognizedIncome");
+            final BigDecimal outstandingLoanBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "outstandingLoanBalance");
+            final String externalId = rs.getString("externalId");
+
+            AccountTransferData transfer = null;
+            final Long fromTransferId = JdbcSupport.getLong(rs, "fromTransferId");
+            final Long toTransferId = JdbcSupport.getLong(rs, "toTransferId");
+            if (fromTransferId != null) {
+                final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, "fromTransferDate");
+                final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fromTransferAmount");
+                final boolean fromTransferReversed = rs.getBoolean("fromTransferReversed");
+                final String fromTransferDescription = rs.getString("fromTransferDescription");
+
+                transfer = AccountTransferData.transferBasicDetails(fromTransferId, currencyData, fromTransferAmount, fromTransferDate,
+                        fromTransferDescription, fromTransferReversed);
+            } else if (toTransferId != null) {
+                final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, "toTransferDate");
+                final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "toTransferAmount");
+                final boolean toTransferReversed = rs.getBoolean("toTransferReversed");
+                final String toTransferDescription = rs.getString("toTransferDescription");
+
+                transfer = AccountTransferData.transferBasicDetails(toTransferId, currencyData, toTransferAmount, toTransferDate,
+                        toTransferDescription, toTransferReversed);
+            }
+            return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currencyData, date, totalAmount,
+                    principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion,
+                    unrecognizedIncomePortion, externalId, transfer, null, outstandingLoanBalance, submittedOnDate, manuallyReversed);
+        }
+    }
+
+    private static final class LoanTransactionsAccountTransferMapper implements RowMapper<AccountTransferData> {
+
+        public String accountTransferSchema() {
+
+            return " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, "
+                    + " rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, "
+                    + " fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,"
+                    + " fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,"
+                    + " fromtran.description as fromTransferDescription,"
+                    + " totran.id as toTransferId, totran.is_reversed as toTransferReversed,"
+                    + " totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,"
+                    + " totran.description as toTransferDescription "
+                    + " from m_loan l join m_loan_transaction tr on tr.loan_id = l.id"
+                    + " join m_currency rc on rc.`code` = l.currency_code "
+                    + " left join m_account_transfer_transaction fromtran on fromtran.from_loan_transaction_id = tr.id "
+                    + " left join m_account_transfer_transaction totran on totran.to_loan_transaction_id = tr.id ";
+        }
+
+        @Override
+        public AccountTransferData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            AccountTransferData transfer = null;
+            final Long fromTransferId = JdbcSupport.getLong(rs, "fromTransferId");
+            final Long toTransferId = JdbcSupport.getLong(rs, "toTransferId");
+            if (fromTransferId != null) {
+                final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, "fromTransferDate");
+                final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fromTransferAmount");
+                final boolean fromTransferReversed = rs.getBoolean("fromTransferReversed");
+                final String fromTransferDescription = rs.getString("fromTransferDescription");
+
+                transfer = AccountTransferData.transferBasicDetails(fromTransferId, currencyData, fromTransferAmount, fromTransferDate,
+                        fromTransferDescription, fromTransferReversed);
+            } else if (toTransferId != null) {
+                final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, "toTransferDate");
+                final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "toTransferAmount");
+                final boolean toTransferReversed = rs.getBoolean("toTransferReversed");
+                final String toTransferDescription = rs.getString("toTransferDescription");
+
+                transfer = AccountTransferData.transferBasicDetails(toTransferId, currencyData, toTransferAmount, toTransferDate,
+                        toTransferDescription, toTransferReversed);
+            }
+            return transfer;
+        }
+    }
+
+    @Override
+    public LoanAccountData retrieveLoanProductDetailsTemplate(final Long productId, final Long clientId, final Long groupId) {
+
+        this.context.authenticatedUser();
+
+        final LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
+        final Collection<EnumOptionData> loanTermFrequencyTypeOptions = this.loanDropdownReadPlatformService
+                .retrieveLoanTermFrequencyTypeOptions();
+        final Collection<EnumOptionData> repaymentFrequencyTypeOptions = this.loanDropdownReadPlatformService
+                .retrieveRepaymentFrequencyTypeOptions();
+        final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions = this.loanDropdownReadPlatformService
+                .retrieveRepaymentFrequencyOptionsForNthDayOfMonth();
+        final Collection<EnumOptionData> repaymentFrequencyDaysOfWeekTypeOptions = this.loanDropdownReadPlatformService
+                .retrieveRepaymentFrequencyOptionsForDaysOfWeek();
+        final Collection<EnumOptionData> interestRateFrequencyTypeOptions = this.loanDropdownReadPlatformService
+                .retrieveInterestRateFrequencyTypeOptions();
+        final Collection<EnumOptionData> amortizationTypeOptions = this.loanDropdownReadPlatformService
+                .retrieveLoanAmortizationTypeOptions();
+        Collection<EnumOptionData> interestTypeOptions = null;
+        if (loanProduct.isLinkedToFloatingInterestRates()) {
+            interestTypeOptions = Arrays.asList(interestType(InterestMethod.DECLINING_BALANCE));
+        } else {
+            interestTypeOptions = this.loanDropdownReadPlatformService.retrieveLoanInterestTypeOptions();
+        }
+        final Collection<EnumOptionData> interestCalculationPeriodTypeOptions = this.loanDropdownReadPlatformService
+                .retrieveLoanInterestRateCalculatedInPeriodOptions();
+        final Collection<FundData> fundOptions = this.fundReadPlatformService.retrieveAllFunds();
+        final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions = this.loanDropdownReadPlatformService
+                .retreiveTransactionProcessingStrategies();
+        final Collection<CodeValueData> loanPurposeOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanPurpose");
+        final Collection<CodeValueData> loanCollateralOptions = this.codeValueReadPlatformService
+                .retrieveCodeValuesByCode("LoanCollateral");
+        Collection<ChargeData> chargeOptions = null;
+        if (loanProduct.getMultiDisburseLoan()) {
+            chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId,
+                    new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
+        } else {
+            chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, new ChargeTimeType[] {
+                    ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT });
+        }
+
+        Integer loanCycleCounter = null;
+        if (loanProduct.useBorrowerCycle()) {
+            if (clientId == null) {
+                loanCycleCounter = retriveLoanCounter(groupId, AccountType.GROUP.getValue(), loanProduct.getId());
+            } else {
+                loanCycleCounter = retriveLoanCounter(clientId, loanProduct.getId());
+            }
+        }
+
+        return LoanAccountData.loanProductWithTemplateDefaults(loanProduct, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
+                repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDaysOfWeekTypeOptions, repaymentStrategyOptions,
+                interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
+                fundOptions, chargeOptions, loanPurposeOptions, loanCollateralOptions, loanCycleCounter);
+    }
+
+    @Override
+    public LoanAccountData retrieveClientDetailsTemplate(final Long clientId) {
+
+        this.context.authenticatedUser();
+
+        final ClientData clientAccount = this.clientReadPlatformService.retrieveOne(clientId);
+        final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant();
+
+        return LoanAccountData.clientDefaults(clientAccount.id(), clientAccount.accountNo(), clientAccount.displayName(),
+                clientAccount.officeId(), expectedDisbursementDate);
+    }
+
+    @Override
+    public LoanAccountData retrieveGroupDetailsTemplate(final Long groupId) {
+        this.context.authenticatedUser();
+        final GroupGeneralData groupAccount = this.groupReadPlatformService.retrieveOne(groupId);
+        final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant();
+        return LoanAccountData.groupDefaults(groupAccount, expectedDisbursementDate);
+    }
+
+    @Override
+    public LoanAccountData retrieveGroupAndMembersDetailsTemplate(final Long groupId) {
+        GroupGeneralData groupAccount = this.groupReadPlatformService.retrieveOne(groupId);
+        final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant();
+
+        // get group associations
+        final Collection<ClientData> membersOfGroup = this.clientReadPlatformService.retrieveActiveClientMembersOfGroup(groupId);
+        if (!CollectionUtils.isEmpty(membersOfGroup)) {
+            final Collection<ClientData> activeClientMembers = null;
+            final Collection<CalendarData> calendarsData = null;
+            final CalendarData collectionMeetingCalendar = null;
+            final Collection<GroupRoleData> groupRoles = null;
+            groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, activeClientMembers, groupRoles, calendarsData,
+                    collectionMeetingCalendar);
+        }
+
+        return LoanAccountData.groupDefaults(groupAccount, expectedDisbursementDate);
+    }
+
+    @Override
+    public Collection<CalendarData> retrieveCalendars(final Long groupId) {
+        Collection<CalendarData> calendarsData = new ArrayList<>();
+        calendarsData.addAll(this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId,
+                CalendarEntityType.GROUPS.getValue(), null));
+        calendarsData
+                .addAll(this.calendarReadPlatformService.retrieveCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), null));
+        calendarsData = this.calendarReadPlatformService.updateWithRecurringDates(calendarsData);
+        return calendarsData;
+    }
+
+    @Override
+    public Collection<StaffData> retrieveAllowedLoanOfficers(final Long selectedOfficeId, final boolean staffInSelectedOfficeOnly) {
+        if (selectedOfficeId == null) { return null; }
+
+        Collection<StaffData> allowedLoanOfficers = null;
+
+        if (staffInSelectedOfficeOnly) {
+            // only bring back loan officers in selected branch/office
+            allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersInOfficeById(selectedOfficeId);
+        } else {
+            // by default bring back all loan officers in selected
+            // branch/office as well as loan officers in officer above
+            // this office
+            final boolean restrictToLoanOfficersOnly = true;
+            allowedLoanOfficers = this.staffReadPlatformService.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(selectedOfficeId,
+                    restrictToLoanOfficersOnly);
+        }
+
+        return allowedLoanOfficers;
+    }
+
+    @Override
+    public Collection<OverdueLoanScheduleData> retrieveAllLoansWithOverdueInstallments(final Long penaltyWaitPeriod,
+            final Boolean backdatePenalties) {
+        final MusoniOverdueLoanScheduleMapper rm = new MusoniOverdueLoanScheduleMapper();
+
+        final StringBuilder sqlBuilder = new StringBuilder(400);
+        sqlBuilder.append("select ").append(rm.schema()).append(" where DATE_SUB(CURDATE(),INTERVAL ? DAY) > ls.duedate ")
+                .append(" and ls.completed_derived <> 1 and mc.charge_applies_to_enum =1 ")
+                .append(" and ls.recalculated_interest_component <> 1 ")
+                .append(" and mc.charge_time_enum = 9 and ml.loan_status_id = 300 ");
+
+        if (backdatePenalties) { return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { penaltyWaitPeriod }); }
+        // Only apply for duedate = yesterday (so that we don't apply
+        // penalties on the duedate itself)
+        sqlBuilder.append(" and ls.duedate >= DATE_SUB(CURDATE(),INTERVAL (? + 1) DAY)");
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { penaltyWaitPeriod, penaltyWaitPeriod });
+
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public Integer retriveLoanCounter(final Long groupId, final Integer loanType, Long productId) {
+        final String sql = "Select MAX(l.loan_product_counter) from m_loan l where l.group_id = ?  and l.loan_type_enum = ? and l.product_id=?";
+        return this.jdbcTemplate.queryForInt(sql, groupId, loanType, productId);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public Integer retriveLoanCounter(final Long clientId, Long productId) {
+        final String sql = "Select MAX(l.loan_product_counter) from m_loan l where l.client_id = ? and l.product_id=?";
+        return this.jdbcTemplate.queryForInt(sql, clientId, productId);
+    }
+
+    @Override
+    public Collection<DisbursementData> retrieveLoanDisbursementDetails(final Long loanId) {
+        final LoanDisbursementDetailMapper rm = new LoanDisbursementDetailMapper();
+        final String sql = "select " + rm.schema() + " where dd.loan_id=? group by dd.id order by dd.expected_disburse_date";
+        return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
+    }
+
+    private static final class LoanDisbursementDetailMapper implements RowMapper<DisbursementData> {
+
+        public String schema() {
+            return "dd.id as id,dd.expected_disburse_date as expectedDisbursementdate, dd.disbursedon_date as actualDisbursementdate,dd.principal as principal,sum(lc.amount) chargeAmount, lc.amount_waived_derived waivedAmount,group_concat(lc.id) loanChargeId "
+                    + "from m_loan l inner join m_loan_disbursement_detail dd on dd.loan_id = l.id left join m_loan_tranche_disbursement_charge tdc on tdc.disbursement_detail_id=dd.id "
+                    + "left join m_loan_charge lc on  lc.id=tdc.loan_charge_id and lc.is_active=1";
+        }
+
+        @Override
+        public DisbursementData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final LocalDate expectedDisbursementdate = JdbcSupport.getLocalDate(rs, "expectedDisbursementdate");
+            final LocalDate actualDisbursementdate = JdbcSupport.getLocalDate(rs, "actualDisbursementdate");
+            final BigDecimal principal = rs.getBigDecimal("principal");
+            final String loanChargeId = rs.getString("loanChargeId");
+            BigDecimal chargeAmount = rs.getBigDecimal("chargeAmount");
+            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);
+            return disbursementData;
+        }
+
+    }
+
+    @Override
+    public DisbursementData retrieveLoanDisbursementDetail(Long loanId, Long disbursementId) {
+        final LoanDisbursementDetailMapper rm = new LoanDisbursementDetailMapper();
+        final String sql = "select " + rm.schema() + " where dd.loan_id=? and dd.id=?";
+        return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanId, disbursementId });
+    }
+
+    @Override
+    public Collection<LoanTermVariationsData> retrieveLoanTermVariations(Long loanId, Integer termType) {
+        final LoanTermVariationsMapper rm = new LoanTermVariationsMapper();
+        final String sql = "select " + rm.schema() + " where tv.loan_id=? and tv.term_type=?";
+        return this.jdbcTemplate.query(sql, rm, new Object[] { loanId, termType });
+    }
+
+    private static final class LoanTermVariationsMapper implements RowMapper<LoanTermVariationsData> {
+
+        public String schema() {
+            return "tv.id as id,tv.applicable_date as variationApplicableFrom,tv.decimal_value as decimalValue, tv.date_value as dateValue, tv.is_specific_to_installment as isSpecificToInstallment "
+                    + "from m_loan_term_variations tv";
+        }
+
+        @Override
+        public LoanTermVariationsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("id");
+            final LocalDate variationApplicableFrom = JdbcSupport.getLocalDate(rs, "variationApplicableFrom");
+            final BigDecimal decimalValue = rs.getBigDecimal("decimalValue");
+            final LocalDate dateValue = JdbcSupport.getLocalDate(rs, "dateValue");
+            final boolean isSpecificToInstallment = rs.getBoolean("isSpecificToInstallment");
+
+            final LoanTermVariationsData loanTermVariationsData = new LoanTermVariationsData(id,
+                    LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), variationApplicableFrom, decimalValue, dateValue,
+                    isSpecificToInstallment);
+            return loanTermVariationsData;
+        }
+
+    }
+
+    @Override
+    public Collection<LoanScheduleAccrualData> retriveScheduleAccrualData() {
+
+        LoanScheduleAccrualMapper mapper = new LoanScheduleAccrualMapper();
+        final StringBuilder sqlBuilder = new StringBuilder(400);
+        sqlBuilder
+                .append("select ")
+                .append(mapper.schema())
+                .append(" where ((ls.fee_charges_amount <> if(ls.accrual_fee_charges_derived is null,0, ls.accrual_fee_charges_derived))")
+                .append(" or ( ls.penalty_charges_amount <> if(ls.accrual_penalty_charges_derived is null,0,ls.accrual_penalty_charges_derived))")
+                .append(" or ( ls.interest_amount <> if(ls.accrual_interest_derived is null,0,ls.accrual_interest_derived)))")
+                .append("  and loan.loan_status_id=? and mpl.accounting_type=? and loan.is_npa=0 and ls.duedate <= CURDATE() order by loan.id,ls.duedate");
+        return this.jdbcTemplate.query(sqlBuilder.toString(), mapper, new Object[] { LoanStatus.ACTIVE.getValue(),
+                AccountingRuleType.ACCRUAL_PERIODIC.getValue() });
+    }
+
+    @Override
+    public Collection<LoanScheduleAccrualData> retrivePeriodicAccrualData(final LocalDate tillDate) {
+
+        LoanSchedulePeriodicAccrualMapper mapper = new LoanSchedulePeriodicAccrualMapper();
+        final StringBuilder sqlBuilder = new StringBuilder(400);
+        sqlBuilder
+                .append("select ")
+                .append(mapper.schema())
+                .append(" where ((ls.fee_charges_amount <> if(ls.accrual_fee_charges_derived is null,0, ls.accrual_fee_charges_derived))")
+                .append(" or (ls.penalty_charges_amount <> if(ls.accrual_penalty_charges_derived is null,0,ls.accrual_penalty_charges_derived))")
+                .append(" or (ls.interest_amount <> if(ls.accrual_interest_derived is null,0,ls.accrual_interest_derived)))")
+                .append("  and loan.loan_status_id=:active and mpl.accounting_type=:type and loan.is_npa=0 and (ls.duedate <= :tilldate or (ls.duedate > :tilldate and ls.fromdate < :tilldate)) order by loan.id,ls.duedate");
+        Map<String, Object> paramMap = new HashMap<>(3);
+        paramMap.put("active", LoanStatus.ACTIVE.getValue());
+        paramMap.put("type", AccountingRuleType.ACCRUAL_PERIODIC.getValue());
+        paramMap.put("tilldate", formatter.print(tillDate));
+
+        return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper);
+    }
+
+    private static final class LoanSchedulePeriodicAccrualMapper implements RowMapper<LoanScheduleAccrualData> {
+
+        public String schema() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder
+                    .append("loan.id as loanId ,if(loan.client_id is null,mg.office_id,mc.office_id) as officeId,")
+                    .append("loan.accrued_till as accruedTill, loan.repayment_period_frequency_enum as frequencyEnum, ")
+                    .append("loan.interest_calculated_from_date as interestCalculatedFrom, ")
+                    .append("loan.repay_every as repayEvery,")
+                    .append("ls.installment as installmentNumber, ")
+                    .append("ls.duedate as duedate,ls.fromdate as fromdate ,ls.id as scheduleId,loan.product_id as productId,")
+                    .append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,")
+                    .append("ls.penalty_charges_amount as penalty, ")
+                    .append("ls.fee_charges_amount as charges, ")
+                    .append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,")
+                    .append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,")
+                    .append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode")
+                    .append(" from m_loan_repayment_schedule ls ").append(" left join m_loan loan on loan.id=ls.loan_id ")
+                    .append(" left join m_product_loan mpl on mpl.id = loan.product_id")
+                    .append(" left join m_client mc on mc.id = loan.client_id ").append(" left join m_group mg on mg.id = loan.group_id")
+                    .append(" left join m_currency curr on curr.code = loan.currency_code");
+            return sqlBuilder.toString();
+        }
+
+        @Override
+        public LoanScheduleAccrualData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+
+            final Long loanId = rs.getLong("loanId");
+            final Long officeId = rs.getLong("officeId");
+            final LocalDate accruedTill = JdbcSupport.getLocalDate(rs, "accruedTill");
+            final LocalDate interestCalculatedFrom = JdbcSupport.getLocalDate(rs, "interestCalculatedFrom");
+            final Integer installmentNumber = JdbcSupport.getInteger(rs, "installmentNumber");
+
+            final Integer frequencyEnum = JdbcSupport.getInteger(rs, "frequencyEnum");
+            final Integer repayEvery = JdbcSupport.getInteger(rs, "repayEvery");
+            final PeriodFrequencyType frequency = PeriodFrequencyType.fromInt(frequencyEnum);
+            final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "duedate");
+            final LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromdate");
+            final Long repaymentScheduleId = rs.getLong("scheduleId");
+            final Long loanProductId = rs.getLong("productId");
+            final BigDecimal interestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interest");
+            final BigDecimal feeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "charges");
+            final BigDecimal penaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "penalty");
+            final BigDecimal interestIncomeWaived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interestWaived");
+            final BigDecimal accruedInterestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accinterest");
+            final BigDecimal accruedFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accfeecharege");
+            final BigDecimal accruedPenaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accpenalty");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromDate,
+                    repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
+                    accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived);
+        }
+
+    }
+
+    private static final class LoanScheduleAccrualMapper implements RowMapper<LoanScheduleAccrualData> {
+
+        public String schema() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder
+                    .append("loan.id as loanId ,if(loan.client_id is null,mg.office_id,mc.office_id) as officeId,")
+                    .append("ls.duedate as duedate,ls.fromdate as fromdate,ls.id as scheduleId,loan.product_id as productId,")
+                    .append("ls.installment as installmentNumber, ")
+                    .append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,")
+                    .append("ls.penalty_charges_amount as penalty, ")
+                    .append("ls.fee_charges_amount as charges, ")
+                    .append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,")
+                    .append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,")
+                    .append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode")
+                    .append(" from m_loan_repayment_schedule ls ").append(" left join m_loan loan on loan.id=ls.loan_id ")
+                    .append(" left join m_product_loan mpl on mpl.id = loan.product_id")
+                    .append(" left join m_client mc on mc.id = loan.client_id ").append(" left join m_group mg on mg.id = loan.group_id")
+                    .append(" left join m_currency curr on curr.code = loan.currency_code");
+            return sqlBuilder.toString();
+        }
+
+        @Override
+        public LoanScheduleAccrualData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+
+            final Long loanId = rs.getLong("loanId");
+            final Long officeId = rs.getLong("officeId");
+            final Integer installmentNumber = JdbcSupport.getInteger(rs, "installmentNumber");
+            final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "duedate");
+            final LocalDate fromdate = JdbcSupport.getLocalDate(rs, "fromdate");
+            final Long repaymentScheduleId = rs.getLong("scheduleId");
+            final Long loanProductId = rs.getLong("productId");
+            final BigDecimal interestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interest");
+            final BigDecimal feeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "charges");
+            final BigDecimal penaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "penalty");
+            final BigDecimal interestIncomeWaived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interestWaived");
+            final BigDecimal accruedInterestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accinterest");
+            final BigDecimal accruedFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accfeecharege");
+            final BigDecimal accruedPenaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accpenalty");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+            final LocalDate accruedTill = null;
+            final PeriodFrequencyType frequency = null;
+            final Integer repayEvery = null;
+            final LocalDate interestCalculatedFrom = null;
+            return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromdate,
+                    repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
+                    accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived);
+        }
+    }
+
+    @Override
+    public LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId) {
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.RECOVERY_REPAYMENT);
+        final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+        BigDecimal outstandingLoanBalance = null;
+        final BigDecimal unrecognizedIncomePortion = null;
+        return new LoanTransactionData(null, null, null, transactionType, null, null, null, loan.getTotalWrittenOff(), null, null, null,
+                null, null, unrecognizedIncomePortion, paymentOptions, null, null, null, outstandingLoanBalance, false);
+
+    }
+
+    @Override
+    public LoanTransactionData retrieveLoanWriteoffTemplate(final Long loanId) {
+
+        final LoanAccountData loan = this.retrieveOne(loanId);
+        final BigDecimal outstandingLoanBalance = null;
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF);
+        final BigDecimal unrecognizedIncomePortion = null;
+        return new LoanTransactionData(null, null, null, transactionType, null, loan.currency(), DateUtils.getLocalDateOfTenant(),
+                loan.getTotalOutstandingAmount(), null, null, null, null, null, null, null, null, outstandingLoanBalance,
+                unrecognizedIncomePortion, false);
+    }
+
+    @Override
+    public Collection<Long> fetchLoansForInterestRecalculation() {
+        StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append("SELECT ml.id FROM m_loan ml ");
+        sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
+        sqlBuilder.append(" LEFT JOIN m_loan_disbursement_detail dd on dd.loan_id=ml.id and dd.disbursedon_date is null ");
+        // For Floating rate changes
+        sqlBuilder
+                .append(" left join m_product_loan_floating_rates pfr on ml.product_id = pfr.loan_product_id and ml.is_floating_interest_rate = 1");
+        sqlBuilder.append(" left join m_floating_rates fr on  pfr.floating_rates_id = fr.id");
+        sqlBuilder.append(" left join m_floating_rates_periods frp on fr.id = frp.floating_rates_id ");
+        sqlBuilder.append(" left join m_loan_reschedule_request lrr on lrr.loan_id = ml.id");
+        // this is to identify the applicable rates when base rate is changed
+        sqlBuilder.append(" left join  m_floating_rates bfr on  bfr.is_base_lending_rate = 1");
+        sqlBuilder.append(" left join  m_floating_rates_periods bfrp on  bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?");
+        sqlBuilder.append(" WHERE ml.loan_status_id = ? ");
+        sqlBuilder.append(" and ml.is_npa = 0 ");
+        sqlBuilder.append(" and ((");
+        sqlBuilder.append("ml.interest_recalculation_enabled = 1 ");
+        sqlBuilder.append(" and (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ?)");
+        sqlBuilder.append(" and ((");
+        sqlBuilder.append(" mr.completed_derived is false ");
+        sqlBuilder.append(" and mr.duedate < ? )");
+        sqlBuilder.append(" or dd.expected_disburse_date < ? )) ");
+        sqlBuilder.append(" or (");
+        sqlBuilder.append(" fr.is_active = 1 and  frp.is_active = 1");
+        sqlBuilder.append(" and (frp.created_date >= ?  or ");
+        sqlBuilder.append("(bfrp.id is not null and frp.is_differential_to_base_lending_rate = 1 and frp.from_date >= bfrp.from_date)) ");
+        sqlBuilder.append("and lrr.loan_id is null");
+        sqlBuilder.append(" ))");
+        sqlBuilder.append(" group by ml.id");
+        try {
+            String currentdate = formatter.print(DateUtils.getLocalDateOfTenant());
+            // will look only for yesterday modified rates
+            String yesterday = formatter.print(DateUtils.getLocalDateOfTenant().minusDays(1));
+            return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, new Object[] { yesterday,
+                    LoanStatus.ACTIVE.getValue(), currentdate, currentdate, currentdate, yesterday });
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public Collection<LoanTransactionData> retrieveWaiverLoanTransactions(final Long loanId) {
+        try {
+
+            final LoanTransactionDerivedComponentMapper rm = new LoanTransactionDerivedComponentMapper();
+
+            final String sql = "select " + rm.schema()
+                    + " where tr.loan_id = ? and tr.transaction_type_enum = ? and tr.is_reversed=0 order by tr.transaction_date ASC,id ";
+            return this.jdbcTemplate.query(sql, rm, new Object[] { loanId, LoanTransactionType.WAIVE_INTEREST.getValue() });
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean isGuaranteeRequired(final Long loanId) {
+        final String sql = "select pl.hold_guarantee_funds from m_loan ml inner join m_product_loan pl on pl.id = ml.product_id where ml.id=?";
+        return this.jdbcTemplate.queryForObject(sql, Boolean.class, loanId);
+    }
+
+    private static final class LoanTransactionDerivedComponentMapper implements RowMapper<LoanTransactionData> {
+
+        public String schema() {
+
+            return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as `date`, tr.amount as total, "
+                    + " tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, "
+                    + " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, "
+                    + " tr.overpayment_portion_derived as overpayment, tr.outstanding_loan_balance_derived as outstandingLoanBalance, "
+                    + " tr.unrecognized_income_portion as unrecognizedIncome " + " from m_loan_transaction tr ";
+        }
+
+        @Override
+        public LoanTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
+            final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(transactionTypeInt);
+
+            final LocalDate date = JdbcSupport.getLocalDate(rs, "date");
+            final BigDecimal totalAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "total");
+            final BigDecimal principalPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principal");
+            final BigDecimal interestPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interest");
+            final BigDecimal feeChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fees");
+            final BigDecimal penaltyChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalties");
+            final BigDecimal overPaymentPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overpayment");
+            final BigDecimal unrecognizedIncomePortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "unrecognizedIncome");
+            final BigDecimal outstandingLoanBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "outstandingLoanBalance");
+
+            return new LoanTransactionData(id, transactionType, date, totalAmount, principalPortion, interestPortion, feeChargesPortion,
+                    penaltyChargesPortion, overPaymentPortion, unrecognizedIncomePortion, outstandingLoanBalance, false);
+        }
+    }
+
+    @Override
+    public Collection<LoanSchedulePeriodData> fetchWaiverInterestRepaymentData(final Long loanId) {
+        try {
+
+            final LoanRepaymentWaiverMapper rm = new LoanRepaymentWaiverMapper();
+
+            final String sql = "select " + rm.getSchema()
+                    + " where lrs.loan_id = ? and lrs.interest_waived_derived is not null order by lrs.installment ASC ";
+            return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+
+    }
+
+    private static final class LoanRepaymentWaiverMapper implements RowMapper<LoanSchedulePeriodData> {
+
+        private final String sqlSchema;
+
+        public String getSchema() {
+            return this.sqlSchema;
+        }
+
+        public LoanRepaymentWaiverMapper() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("lrs.duedate as dueDate,lrs.interest_waived_derived interestWaived, lrs.installment as installment");
+            sb.append(" from m_loan_repayment_schedule lrs ");
+            sqlSchema = sb.toString();
+        }
+
+        @Override
+        public LoanSchedulePeriodData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+
+            final Integer period = JdbcSupport.getInteger(rs, "installment");
+            final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
+            final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
+
+            final LocalDate fromDate = null;
+            final LocalDate obligationsMetOnDate = null;
+            final Boolean complete = false;
+            final BigDecimal principalOriginalDue = null;
+            final BigDecimal principalPaid = null;
+            final BigDecimal principalWrittenOff = null;
+            final BigDecimal principalOutstanding = null;
+            final BigDecimal interestPaid = null;
+            final BigDecimal interestWrittenOff = null;
+            final BigDecimal interestOutstanding = null;
+            final BigDecimal feeChargesDue = null;
+            final BigDecimal feeChargesPaid = null;
+            final BigDecimal feeChargesWaived = null;
+            final BigDecimal feeChargesWrittenOff = null;
+            final BigDecimal feeChargesOutstanding = null;
+            final BigDecimal penaltyChargesDue = null;
+            final BigDecimal penaltyChargesPaid = null;
+            final BigDecimal penaltyChargesWaived = null;
+            final BigDecimal penaltyChargesWrittenOff = null;
+            final BigDecimal penaltyChargesOutstanding = null;
+
+            final BigDecimal totalDueForPeriod = null;
+            final BigDecimal totalPaidInAdvanceForPeriod = null;
+            final BigDecimal totalPaidLateForPeriod = null;
+            final BigDecimal totalActualCostOfLoanForPeriod = null;
+            final BigDecimal outstandingPrincipalBalanceOfLoan = null;
+            final BigDecimal interestDueOnPrincipalOutstanding = null;
+            Long loanId = null;
+            final BigDecimal totalWaived = null;
+            final BigDecimal totalWrittenOff = null;
+            final BigDecimal totalOutstanding = null;
+            final BigDecimal totalPaid = null;
+            final BigDecimal totalInstallmentAmount = null;
+
+            return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete,
+                    principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
+                    interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding,
+                    feeChargesDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue,
+                    penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod,
+                    totalPaid, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding,
+                    totalActualCostOfLoanForPeriod, totalInstallmentAmount);
+        }
+    }
+
+    @Override
+    public Date retrieveMinimumDateOfRepaymentTransaction(Long loanId) {
+        // TODO Auto-generated method stub
+        Date date = this.jdbcTemplate.queryForObject(
+                "select min(transaction_date) from m_loan_transaction where loan_id=? and transaction_type_enum=2",
+                new Object[] { loanId }, Date.class);
+
+        return date;
+    }
+
+    @Override
+    public PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId) {
+        // TODO Auto-generated method stub
+        try {
+            final String sql = "  select (SUM(ifnull(mr.principal_completed_derived, 0)) +"
+                    + " + SUM(ifnull(mr.interest_completed_derived, 0)) " + " + SUM(ifnull(mr.fee_charges_completed_derived, 0)) "
+                    + " + SUM(ifnull(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived "
+                    + " from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id "
+                    + " where ml.id=? and  mr.duedate >= CURDATE() group by ml.id having "
+                    + " (SUM(ifnull(mr.principal_completed_derived, 0))  " + " + SUM(ifnull(mr.interest_completed_derived, 0)) "
+                    + " + SUM(ifnull(mr.fee_charges_completed_derived, 0)) "
+                    + "+  SUM(ifnull(mr.penalty_charges_completed_derived, 0))) > 0";
+            BigDecimal bigDecimal = this.jdbcTemplate.queryForObject(sql, BigDecimal.class, loanId);
+            return new PaidInAdvanceData(bigDecimal);
+        } catch (DataAccessException e) {
+            // TODO Auto-generated catch block
+            return new PaidInAdvanceData(new BigDecimal(0));
+        }
+    }
+
+    @Override
+    public LoanTransactionData retrieveRefundByCashTemplate(Long loanId) {
+        // TODO Auto-generated method stub
+        this.context.authenticatedUser();
+
+        // TODO - KW - OPTIMIZE - write simple sql query to fetch back date of
+        // possible next transaction date.
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+        final MonetaryCurrency currency = loan.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+
+        final CurrencyData currencyData = applicationCurrency.toData();
+
+        final LocalDate earliestUnpaidInstallmentDate = new LocalDate();
+
+        final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN);
+        final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+
+        return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
+                retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, null, null, null, null, null, paymentOptions, null,
+                null, null, null, false);
+    }
+
+    @Override
+    public Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(Long loanId) {
+        this.context.authenticatedUser();
+
+        final Loan loan = this.loanRepository.findOne(loanId);
+        if (loan == null) { throw new LoanNotFoundException(loanId); }
+
+        if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
+            final Collection<InterestRatePeriodData> intRatePeriodData = new ArrayList<>();
+            final Collection<InterestRatePeriodData> intRates = this.floatingRatesReadPlatformService.retrieveInterestRatePeriods(loan
+                    .loanProduct().getFloatingRates().getFloatingRate().getId());
+            for (final InterestRatePeriodData rate : intRates) {
+                if (rate.getFromDate().compareTo(loan.getDisbursementDate().toDate()) > 0 && loan.getIsFloatingInterestRate()) {
+                    updateInterestRatePeriodData(rate, loan);
+                    intRatePeriodData.add(rate);
+                } else if (rate.getFromDate().compareTo(loan.getDisbursementDate().toDate()) <= 0) {
+                    updateInterestRatePeriodData(rate, loan);
+                    intRatePeriodData.add(rate);
+                    break;
+                }
+            }
+
+            return intRatePeriodData;
+        }
+        return null;
+    }
+
+    private void updateInterestRatePeriodData(InterestRatePeriodData rate, Loan loan) {
+        rate.setLoanProductDifferentialInterestRate(loan.loanProduct().getFloatingRates().getInterestRateDifferential());
+        rate.setLoanDifferentialInterestRate(loan.getInterestRateDifferential());
+
+        BigDecimal effectiveInterestRate = BigDecimal.ZERO;
+        effectiveInterestRate = effectiveInterestRate.add(rate.getLoanDifferentialInterestRate());
+        effectiveInterestRate = effectiveInterestRate.add(rate.getLoanProductDifferentialInterestRate());
+        effectiveInterestRate = effectiveInterestRate.add(rate.getInterestRate());
+        if (rate.getBlrInterestRate() != null && rate.isDifferentialToBLR()) {
+            effectiveInterestRate = effectiveInterestRate.add(rate.getBlrInterestRate());
+        }
+        rate.setEffectiveInterestRate(effectiveInterestRate);
+
+        if (rate.getFromDate().compareTo(loan.getDisbursementDate().toDate()) < 0) {
+            rate.setFromDate(loan.getDisbursementDate().toDate());
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularService.java
new file mode 100644
index 0000000..056447c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularService.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.loanaccount.service;
+
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+
+
+public interface LoanSchedularService {
+
+    void applyChargeForOverdueLoans() throws JobExecutionException;
+
+    void recalculateInterest() throws JobExecutionException;
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java
new file mode 100644
index 0000000..8c5309c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java
@@ -0,0 +1,186 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.CannotAcquireLockException;
+import org.springframework.orm.ObjectOptimisticLockingFailureException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanSchedularServiceImpl implements LoanSchedularService {
+
+    private final static Logger logger = LoggerFactory.getLogger(LoanSchedularServiceImpl.class);
+    private final ConfigurationDomainService configurationDomainService;
+    private final LoanReadPlatformService loanReadPlatformService;
+    private final LoanWritePlatformService loanWritePlatformService;
+
+    @Autowired
+    public LoanSchedularServiceImpl(final ConfigurationDomainService configurationDomainService,
+            final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService) {
+        this.configurationDomainService = configurationDomainService;
+        this.loanReadPlatformService = loanReadPlatformService;
+        this.loanWritePlatformService = loanWritePlatformService;
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT)
+    public void applyChargeForOverdueLoans() throws JobExecutionException {
+
+        final Long penaltyWaitPeriodValue = this.configurationDomainService.retrievePenaltyWaitPeriod();
+        final Boolean backdatePenalties = this.configurationDomainService.isBackdatePenaltiesEnabled();
+        final Collection<OverdueLoanScheduleData> overdueLoanScheduledInstallments = this.loanReadPlatformService
+                .retrieveAllLoansWithOverdueInstallments(penaltyWaitPeriodValue,backdatePenalties);
+
+        if (!overdueLoanScheduledInstallments.isEmpty()) {
+            final StringBuilder sb = new StringBuilder();
+            final Map<Long, Collection<OverdueLoanScheduleData>> overdueScheduleData = new HashMap<>();
+            for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduledInstallments) {
+                if (overdueScheduleData.containsKey(overdueInstallment.getLoanId())) {
+                    overdueScheduleData.get(overdueInstallment.getLoanId()).add(overdueInstallment);
+                } else {
+                    Collection<OverdueLoanScheduleData> loanData = new ArrayList<>();
+                    loanData.add(overdueInstallment);
+                    overdueScheduleData.put(overdueInstallment.getLoanId(), loanData);
+                }
+            }
+
+            for (final Long loanId : overdueScheduleData.keySet()) {
+                try {
+                    this.loanWritePlatformService.applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId));
+
+                } catch (final PlatformApiDataValidationException e) {
+                    final List<ApiParameterError> errors = e.getErrors();
+                    for (final ApiParameterError error : errors) {
+                        logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message "
+                                + error.getDeveloperMessage());
+                        sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ")
+                                .append(error.getDeveloperMessage());
+                    }
+                } catch (final AbstractPlatformDomainRuleException ex) {
+                    logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message "
+                            + ex.getDefaultUserMessage());
+                    sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ")
+                            .append(ex.getDefaultUserMessage());
+                } catch (Exception e) {
+                    Throwable realCause = e;
+                    if (e.getCause() != null) {
+                        realCause = e.getCause();
+                    }
+                    logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message "
+                            + realCause.getMessage());
+                    sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ")
+                            .append(realCause.getMessage());
+                }
+            }
+            if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
+        }
+    }
+
+	@Override
+	@CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN)
+	public void recalculateInterest() throws JobExecutionException {
+		Integer maxNumberOfRetries = ThreadLocalContextUtil.getTenant()
+				.getConnection().getMaxRetriesOnDeadlock();
+		Integer maxIntervalBetweenRetries = ThreadLocalContextUtil.getTenant()
+				.getConnection().getMaxIntervalBetweenRetries();
+		Collection<Long> loanIds = this.loanReadPlatformService
+				.fetchLoansForInterestRecalculation();
+		int i = 0;
+		if (!loanIds.isEmpty()) {
+			final StringBuilder sb = new StringBuilder();
+			for (Long loanId : loanIds) {
+				logger.info("Loan ID " + loanId);
+				Integer numberOfRetries = 0;
+				while (numberOfRetries <= maxNumberOfRetries) {
+					try {
+						this.loanWritePlatformService
+								.recalculateInterest(loanId);
+						numberOfRetries = maxNumberOfRetries + 1;
+					} catch (CannotAcquireLockException
+							| ObjectOptimisticLockingFailureException exception) {
+						logger.info("Recalulate interest job has been retried  "
+								+ numberOfRetries + " time(s)");
+						/***
+						 * Fail if the transaction has been retired for
+						 * maxNumberOfRetries
+						 **/
+						if (numberOfRetries >= maxNumberOfRetries) {
+							logger.warn("Recalulate interest job has been retried for the max allowed attempts of "
+									+ numberOfRetries
+									+ " and will be rolled back");
+							sb.append("Recalulate interest job has been retried for the max allowed attempts of "
+									+ numberOfRetries
+									+ " and will be rolled back");
+							break;
+						}
+						/***
+						 * Else sleep for a random time (between 1 to 10
+						 * seconds) and continue
+						 **/
+						try {
+							Random random = new Random();
+							int randomNum = random
+									.nextInt(maxIntervalBetweenRetries + 1);
+							Thread.sleep(1000 + (randomNum * 1000));
+							numberOfRetries = numberOfRetries + 1;
+						} catch (InterruptedException e) {
+							sb.append("Interest recalculation for loans failed " + exception.getMessage()) ;
+							break;
+						}
+					} catch (Exception e) {
+						Throwable realCause = e;
+						if (e.getCause() != null) {
+							realCause = e.getCause();
+						}
+						logger.error("Interest recalculation for loans failed for account:"	+ loanId + " with message "
+								+ realCause.getMessage());
+						sb.append("Interest recalculation for loans failed for account:").append(loanId).append(" with message ")
+                        .append(realCause.getMessage());
+					}
+					i++;
+				}
+				logger.info("Loans count " + i);
+			}
+			if (sb.length() > 0) {
+				throw new JobExecutionException(sb.toString());
+			}
+		}
+
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
new file mode 100644
index 0000000..423e346
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
@@ -0,0 +1,186 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
+import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
+import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class LoanUtilService {
+
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final HolidayRepository holidayRepository;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+    private final LoanScheduleGeneratorFactory loanScheduleFactory;
+    private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
+
+    @Autowired
+    public LoanUtilService(final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
+            final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService,
+            final HolidayRepository holidayRepository, final WorkingDaysRepositoryWrapper workingDaysRepository,
+            final LoanScheduleGeneratorFactory loanScheduleFactory, final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
+        this.applicationCurrencyRepository = applicationCurrencyRepository;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.holidayRepository = holidayRepository;
+        this.workingDaysRepository = workingDaysRepository;
+        this.loanScheduleFactory = loanScheduleFactory;
+        this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
+    }
+
+    public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) {
+        final HolidayDetailDTO holidayDetailDTO = null;
+        return buildScheduleGeneratorDTO(loan, recalculateFrom, holidayDetailDTO);
+    }
+
+    public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom,
+            final HolidayDetailDTO holidayDetailDTO) {
+        HolidayDetailDTO holidayDetails = holidayDetailDTO;
+        if (holidayDetailDTO == null) {
+            holidayDetails = constructHolidayDTO(loan);
+        }
+        final MonetaryCurrency currency = loan.getCurrency();
+        ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+        final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
+                CalendarEntityType.LOANS.getValue());
+        LocalDate calculatedRepaymentsStartingFromDate = this.getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan,
+                calendarInstance);
+        CalendarInstance restCalendarInstance = null;
+        CalendarInstance compoundingCalendarInstance = null;
+        Long overdurPenaltyWaitPeriod = null;
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
+                    CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
+            compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
+                    loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
+            overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
+        }
+        FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
+        ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
+                calculatedRepaymentsStartingFromDate, holidayDetails, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
+                overdurPenaltyWaitPeriod, floatingRateDTO);
+
+        return scheduleGeneratorDTO;
+    }
+
+    public LocalDate getCalculatedRepaymentsStartingFromDate(final Loan loan) {
+        final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
+                CalendarEntityType.LOANS.getValue());
+        return this.getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, calendarInstance);
+    }
+
+    private HolidayDetailDTO constructHolidayDTO(final Loan loan) {
+        final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+        final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
+                .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+        final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
+        final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
+
+        HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
+                allowTransactionsOnNonWorkingDay);
+        return holidayDetailDTO;
+    }
+
+    private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
+        FloatingRateDTO floatingRateDTO = null;
+        if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
+            boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
+            BigDecimal interestRateDiff = loan.getInterestRateDifferential();
+            List<FloatingRatePeriodData> baseLendingRatePeriods = null;
+            try {
+                baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
+            } catch (final FloatingRateNotFoundException ex) {
+                // Do not do anything
+            }
+
+            floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
+                    baseLendingRatePeriods);
+        }
+        return floatingRateDTO;
+    }
+
+    private LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan,
+            final CalendarInstance calendarInstance) {
+        final Calendar calendar = calendarInstance == null ? null : calendarInstance.getCalendar();
+        return calculateRepaymentStartingFromDate(actualDisbursementDate, loan, calendar);
+    }
+
+    public LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan,
+            final Calendar calendar) {
+        if (calendar == null) { return getCalculatedRepaymentsStartingFromDate(loan); }
+        return calculateRepaymentStartingFromDate(actualDisbursementDate, loan, calendar);
+
+    }
+
+    private LocalDate calculateRepaymentStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, final Calendar calendar) {
+        LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
+        if (calendar != null) {// sync repayments
+
+            // TODO: AA - user provided first repayment date takes precedence
+            // over recalculated meeting date
+            if (calculatedRepaymentsStartingFromDate == null) {
+                // FIXME: AA - Possibility of having next meeting date
+                // immediately after disbursement date,
+                // need to have minimum number of days gap between disbursement
+                // and first repayment date.
+                final LoanProductRelatedDetail repaymentScheduleDetails = loan.repaymentScheduleDetail();
+                if (repaymentScheduleDetails != null) {// Not expecting to be
+                                                       // null
+                    final Integer repayEvery = repaymentScheduleDetails.getRepayEvery();
+                    final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
+                            .getRepaymentPeriodFrequencyType());
+                    calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate,
+                            repayEvery, frequency);
+                }
+            }
+        }
+        return calculatedRepaymentsStartingFromDate;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
new file mode 100755
index 0000000..9189718
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.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.portfolio.loanaccount.service;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
+import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
+import org.joda.time.LocalDate;
+
+public interface LoanWritePlatformService {
+
+    CommandProcessingResult disburseLoan(Long loanId, JsonCommand command, Boolean isAccountTransfer);
+
+    Map<String, Object> bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDisbursalCommand bulkDisbursalCommand,
+            Boolean isAccountTransfer);
+
+    CommandProcessingResult undoLoanDisbursal(Long loanId, JsonCommand command);
+
+    CommandProcessingResult makeLoanRepayment(Long loanId, JsonCommand command, boolean isRecoveryRepayment);
+
+    Map<String, Object> makeLoanBulkRepayment(CollectionSheetBulkRepaymentCommand bulkRepaymentCommand);
+
+    CommandProcessingResult adjustLoanTransaction(Long loanId, Long transactionId, JsonCommand command);
+
+    CommandProcessingResult waiveInterestOnLoan(Long loanId, JsonCommand command);
+
+    CommandProcessingResult writeOff(Long loanId, JsonCommand command);
+
+    CommandProcessingResult closeLoan(Long loanId, JsonCommand command);
+
+    CommandProcessingResult closeAsRescheduled(Long loanId, JsonCommand command);
+
+    CommandProcessingResult addLoanCharge(Long loanId, JsonCommand command);
+
+    CommandProcessingResult updateLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
+
+    CommandProcessingResult deleteLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
+
+    CommandProcessingResult waiveLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
+
+    CommandProcessingResult loanReassignment(Long loanId, JsonCommand command);
+
+    CommandProcessingResult bulkLoanReassignment(JsonCommand command);
+
+    CommandProcessingResult removeLoanOfficer(Long loanId, JsonCommand command);
+
+    void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances,
+            Boolean reschedulebasedOnMeetingDates, LocalDate presentMeetingDate, LocalDate newMeetingDate);
+
+    void applyHolidaysToLoans();
+
+    LoanTransaction initiateLoanTransfer(Long accountId, LocalDate transferDate);
+
+    LoanTransaction withdrawLoanTransfer(Long accountId, LocalDate transferDate);
+
+    void rejectLoanTransfer(Long accountId);
+
+    LoanTransaction acceptLoanTransfer(Long accountId, LocalDate transferDate, Office acceptedInOffice, Staff loanOfficer);
+
+    CommandProcessingResult payLoanCharge(Long loanId, Long loanChargeId, JsonCommand command, boolean isChargeIdIncludedInJson);
+
+    void transferFeeCharges() throws JobExecutionException;
+
+    CommandProcessingResult undoWriteOff(Long loanId);
+
+    CommandProcessingResult updateDisbursementDateAndAmountForTranche(Long loanId, Long disbursementId, JsonCommand command);
+
+    CommandProcessingResult recoverFromGuarantor(Long loanId);
+
+    void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
+
+    CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command);
+
+	CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command);
+
+    void applyOverdueChargesForLoan(Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDatas);
+
+    void recalculateInterest(long loanId);
+
+	CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command);
+
+}
\ No newline at end of file
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
new file mode 100755
index 0000000..06ae1b1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,2846 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationType;
+import org.apache.fineract.portfolio.account.domain.AccountAssociations;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository;
+import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
+import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
+import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
+import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
+import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
+import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
+import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeUpdatedException;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeDeletedException;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBePayedException;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeUpdatedException;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeWaivedException;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeDeletedException.LOAN_CHARGE_CANNOT_BE_DELETED_REASON;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBePayedException.LOAN_CHARGE_CANNOT_BE_PAYED_REASON;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeUpdatedException.LOAN_CHARGE_CANNOT_BE_UPDATED_REASON;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeWaivedException.LOAN_CHARGE_CANNOT_BE_WAIVED_REASON;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
+import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
+import org.apache.fineract.portfolio.collectionsheet.command.SingleDisbursalCommand;
+import org.apache.fineract.portfolio.collectionsheet.command.SingleRepaymentCommand;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.command.LoanUpdateCommand;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
+import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
+import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
+import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
+import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursementException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
+import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
+import org.apache.fineract.portfolio.loanproduct.data.LoanOverdueDTO;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
+import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+
+@Service
+public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(LoanWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final LoanEventApiJsonValidator loanEventApiJsonValidator;
+    private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer;
+    private final LoanRepository loanRepository;
+    private final LoanAccountDomainService loanAccountDomainService;
+    private final NoteRepository noteRepository;
+    private final LoanTransactionRepository loanTransactionRepository;
+    private final LoanAssembler loanAssembler;
+    private final ChargeRepositoryWrapper chargeRepository;
+    private final LoanChargeRepository loanChargeRepository;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
+    private final HolidayRepositoryWrapper holidayRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+    private final LoanProductReadPlatformService loanProductReadPlatformService;
+    private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
+    private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
+    private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
+    private final LoanChargeReadPlatformService loanChargeReadPlatformService;
+    private final LoanReadPlatformService loanReadPlatformService;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final AccountTransferRepository accountTransferRepository;
+    private final CalendarRepository calendarRepository;
+    private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
+    private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
+    private final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper;
+    private final AccountAssociationsRepository accountAssociationRepository;
+    private final AccountTransferDetailRepository accountTransferDetailRepository;
+    private final BusinessEventNotifierService businessEventNotifierService;
+    private final GuarantorDomainService guarantorDomainService;
+    private final LoanUtilService loanUtilService;
+
+    @Autowired
+    public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final LoanEventApiJsonValidator loanEventApiJsonValidator,
+            final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler,
+            final LoanRepository loanRepository, final LoanAccountDomainService loanAccountDomainService,
+            final LoanTransactionRepository loanTransactionRepository, final NoteRepository noteRepository,
+            final ChargeRepositoryWrapper chargeRepository, final LoanChargeRepository loanChargeRepository,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService,
+            final CalendarInstanceRepository calendarInstanceRepository,
+            final PaymentDetailWritePlatformService paymentDetailWritePlatformService, final HolidayRepositoryWrapper holidayRepository,
+            final ConfigurationDomainService configurationDomainService, final WorkingDaysRepositoryWrapper workingDaysRepository,
+            final LoanProductReadPlatformService loanProductReadPlatformService,
+            final AccountTransfersWritePlatformService accountTransfersWritePlatformService,
+            final AccountTransfersReadPlatformService accountTransfersReadPlatformService,
+            final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
+            final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanReadPlatformService loanReadPlatformService,
+            final FromJsonHelper fromApiJsonHelper, final AccountTransferRepository accountTransferRepository,
+            final CalendarRepository calendarRepository,
+            final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
+            final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
+            final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper,
+            final AccountAssociationsRepository accountAssociationRepository,
+            final AccountTransferDetailRepository accountTransferDetailRepository,
+            final BusinessEventNotifierService businessEventNotifierService, final GuarantorDomainService guarantorDomainService,
+            final LoanUtilService loanUtilService) {
+        this.context = context;
+        this.loanEventApiJsonValidator = loanEventApiJsonValidator;
+        this.loanAssembler = loanAssembler;
+        this.loanRepository = loanRepository;
+        this.loanAccountDomainService = loanAccountDomainService;
+        this.loanTransactionRepository = loanTransactionRepository;
+        this.noteRepository = noteRepository;
+        this.chargeRepository = chargeRepository;
+        this.loanChargeRepository = loanChargeRepository;
+        this.applicationCurrencyRepository = applicationCurrencyRepository;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+        this.loanUpdateCommandFromApiJsonDeserializer = loanUpdateCommandFromApiJsonDeserializer;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
+        this.holidayRepository = holidayRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.workingDaysRepository = workingDaysRepository;
+        this.loanProductReadPlatformService = loanProductReadPlatformService;
+        this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
+        this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
+        this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
+        this.loanChargeReadPlatformService = loanChargeReadPlatformService;
+        this.loanReadPlatformService = loanReadPlatformService;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.accountTransferRepository = accountTransferRepository;
+        this.calendarRepository = calendarRepository;
+        this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
+        this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
+        this.loanApplicationCommandFromApiJsonHelper = loanApplicationCommandFromApiJsonHelper;
+        this.accountAssociationRepository = accountAssociationRepository;
+        this.accountTransferDetailRepository = accountTransferDetailRepository;
+        this.businessEventNotifierService = businessEventNotifierService;
+        this.guarantorDomainService = guarantorDomainService;
+        this.loanUtilService = loanUtilService;
+    }
+
+    private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
+        final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
+        return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand command, Boolean isAccountTransfer) {
+
+        final AppUser currentUser = getAppUserIfPresent();
+
+        this.loanEventApiJsonValidator.validateDisbursement(command.json(), isAccountTransfer);
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        
+        final LocalDate nextPossibleRepaymentDate = loan.getNextPossibleRepaymentDateForRescheduling();
+        final Date rescheduledRepaymentDate = command.DateValueOfParameterNamed("adjustRepaymentDate");
+
+        // check for product mix validations
+        checkForProductMixRestrictions(loan);
+
+        // validate actual disbursement date against meeting date
+        final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
+                CalendarEntityType.LOANS.getValue());
+        if (loan.isSyncDisbursementWithMeeting()) {
+
+            final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
+            this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance);
+        }
+
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        // Recalculate first repayment date based in actual disbursement date.
+        final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
+        updateLoanCounters(loan, actualDisbursementDate);
+        Money amountBeforeAdjust = loan.getPrincpal();
+        loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
+        boolean canDisburse = loan.canDisburse(actualDisbursementDate);
+        ChangedTransactionDetail changedTransactionDetail = null;
+        if (canDisburse) {
+            Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
+            boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
+            final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
+            if (isAccountTransfer) {
+                disburseLoanToSavings(loan, command, disburseAmount, paymentDetail);
+                existingTransactionIds.addAll(loan.findExistingTransactionIds());
+                existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
+            } else {
+                existingTransactionIds.addAll(loan.findExistingTransactionIds());
+                existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
+                LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail,
+                        actualDisbursementDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+                disbursementTransaction.updateLoan(loan);
+                loan.getLoanTransactions().add(disbursementTransaction);
+            }
+
+            LocalDate recalculateFrom = null;
+            ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+
+            regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate, rescheduledRepaymentDate);
+            if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.fetchRepaymentScheduleInstallments(),
+                        loan, null);
+            }
+
+            changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO);
+        }
+        if (!changes.isEmpty()) {
+            saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.loanNote(loan, noteText);
+                this.noteRepository.save(note);
+            }
+
+            if (changedTransactionDetail != null) {
+                for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                    this.loanTransactionRepository.save(mapEntry.getValue());
+                    this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+                }
+            }
+
+            // auto create standing instruction
+            createStandingInstruction(loan);
+
+            postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+
+        }
+
+        final Set<LoanCharge> loanCharges = loan.charges();
+        final Map<Long, BigDecimal> disBuLoanCharges = new HashMap<>();
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()
+                    && loanCharge.isChargePending()) {
+                disBuLoanCharges.put(loanCharge.getId(), loanCharge.amountOutstanding());
+            }
+        }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) {
+            final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
+            final SavingsAccount fromSavingsAccount = null;
+            final boolean isRegularTransaction = true;
+            final boolean isExceptionForBalanceCheck = false;
+            final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
+                    PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loanId, "Loan Charge Payment",
+                    locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey(), null,
+                    AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
+                    isExceptionForBalanceCheck);
+            this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+        }
+
+        updateRecurringCalendarDatesForInterestRecalculation(loan);
+        this.loanAccountDomainService.recalculateAccruals(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loan.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    /**
+     * create standing instruction for disbursed loan
+     * 
+     * @param loan
+     *            the disbursed loan
+     * @return void
+     **/
+    private void createStandingInstruction(Loan loan) {
+
+        if (loan.shouldCreateStandingInstructionAtDisbursement()) {
+            AccountAssociations accountAssociations = this.accountAssociationRepository.findByLoanIdAndType(loan.getId(),
+                    AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
+
+            if (accountAssociations != null) {
+
+                SavingsAccount linkedSavingsAccount = accountAssociations.linkedSavingsAccount();
+
+                // name is auto-generated
+                final String name = "To loan " + loan.getAccountNumber() + " from savings " + linkedSavingsAccount.getAccountNumber();
+                final Office fromOffice = loan.getOffice();
+                final Client fromClient = loan.getClient();
+                final Office toOffice = loan.getOffice();
+                final Client toClient = loan.getClient();
+                final Integer priority = StandingInstructionPriority.MEDIUM.getValue();
+                final Integer transferType = AccountTransferType.LOAN_REPAYMENT.getValue();
+                final Integer instructionType = StandingInstructionType.DUES.getValue();
+                final Integer status = StandingInstructionStatus.ACTIVE.getValue();
+                final Integer recurrenceType = AccountTransferRecurrenceType.AS_PER_DUES.getValue();
+                final LocalDate validFrom = new LocalDate();
+
+                AccountTransferDetails accountTransferDetails = AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient,
+                        linkedSavingsAccount, toOffice, toClient, loan, transferType);
+
+                AccountTransferStandingInstruction accountTransferStandingInstruction = AccountTransferStandingInstruction.create(
+                        accountTransferDetails, name, priority, instructionType, status, null, validFrom, null, recurrenceType, null, null,
+                        null);
+                accountTransferDetails.updateAccountTransferStandingInstruction(accountTransferStandingInstruction);
+
+                this.accountTransferDetailRepository.save(accountTransferDetails);
+            }
+        }
+    }
+
+    private void updateRecurringCalendarDatesForInterestRecalculation(final Loan loan) {
+
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()
+                && loan.loanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment()) {
+            final CalendarInstance calendarInstanceForInterestRecalculation = this.calendarInstanceRepository
+                    .findByEntityIdAndEntityTypeIdAndCalendarTypeId(loan.loanInterestRecalculationDetailId(),
+                            CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue(), CalendarType.COLLECTION.getValue());
+
+            Calendar calendarForInterestRecalculation = calendarInstanceForInterestRecalculation.getCalendar();
+            calendarForInterestRecalculation.updateStartAndEndDate(loan.getDisbursementDate(), loan.getMaturityDate());
+            this.calendarRepository.save(calendarForInterestRecalculation);
+        }
+
+    }
+
+    private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
+        try {
+            List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
+            for (LoanRepaymentScheduleInstallment installment : installments) {
+                if (installment.getId() == null) {
+                    this.repaymentScheduleInstallmentRepository.save(installment);
+                }
+            }
+            this.loanRepository.saveAndFlush(loan);
+        } catch (final DataIntegrityViolationException e) {
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+            if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
+                baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
+            }
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                    "Validation errors exist.", dataValidationErrors); }
+        }
+    }
+
+    private void saveLoanWithDataIntegrityViolationChecks(final Loan loan) {
+        try {
+            List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
+            for (LoanRepaymentScheduleInstallment installment : installments) {
+                if (installment.getId() == null) {
+                    this.repaymentScheduleInstallmentRepository.save(installment);
+                }
+            }
+            this.loanRepository.save(loan);
+        } catch (final DataIntegrityViolationException e) {
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+            if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
+                baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
+            }
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                    "Validation errors exist.", dataValidationErrors); }
+        }
+    }
+
+    /****
+     * TODO Vishwas: Pair with Ashok and re-factor collection sheet code-base
+     * 
+     * May of the changes made to disburseLoan aren't being made here, should
+     * refactor to reuse disburseLoan ASAP
+     *****/
+    @Transactional
+    @Override
+    public Map<String, Object> bulkLoanDisbursal(final JsonCommand command, final CollectionSheetBulkDisbursalCommand bulkDisbursalCommand,
+            Boolean isAccountTransfer) {
+        final AppUser currentUser = getAppUserIfPresent();
+
+        final SingleDisbursalCommand[] disbursalCommand = bulkDisbursalCommand.getDisburseTransactions();
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        if (disbursalCommand == null) { return changes; }
+        
+        final LocalDate nextPossibleRepaymentDate = null;
+        final Date rescheduledRepaymentDate = null;
+
+        for (int i = 0; i < disbursalCommand.length; i++) {
+            final SingleDisbursalCommand singleLoanDisbursalCommand = disbursalCommand[i];
+
+            final Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId());
+            checkClientOrGroupActive(loan);
+            this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+            final List<Long> existingTransactionIds = new ArrayList<>();
+            final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+            final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+            // Bulk disbursement should happen on meeting date (mostly from
+            // collection sheet).
+            // FIXME: AA - this should be first meeting date based on
+            // disbursement date and next available meeting dates
+            // assuming repayment schedule won't regenerate because expected
+            // disbursement and actual disbursement happens on same date
+            final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
+            loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
+            updateLoanCounters(loan, actualDisbursementDate);
+            boolean canDisburse = loan.canDisburse(actualDisbursementDate);
+            ChangedTransactionDetail changedTransactionDetail = null;
+            if (canDisburse) {
+                Money amountBeforeAdjust = loan.getPrincpal();
+                Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
+                boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
+                final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
+                if (isAccountTransfer) {
+                    disburseLoanToSavings(loan, command, disburseAmount, paymentDetail);
+                    existingTransactionIds.addAll(loan.findExistingTransactionIds());
+                    existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
+
+                } else {
+                    existingTransactionIds.addAll(loan.findExistingTransactionIds());
+                    existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
+                    LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail,
+                            actualDisbursementDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+                    disbursementTransaction.updateLoan(loan);
+                    loan.getLoanTransactions().add(disbursementTransaction);
+                }
+                LocalDate recalculateFrom = null;
+                final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+                regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate, rescheduledRepaymentDate);
+                if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                    this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(
+                            loan.fetchRepaymentScheduleInstallments(), loan, null);
+                }
+
+                changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO);
+            }
+            if (!changes.isEmpty()) {
+
+                saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+                final String noteText = command.stringValueOfParameterNamed("note");
+                if (StringUtils.isNotBlank(noteText)) {
+                    final Note note = Note.loanNote(loan, noteText);
+                    this.noteRepository.save(note);
+                }
+                if (changedTransactionDetail != null) {
+                    for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                        this.loanTransactionRepository.save(mapEntry.getValue());
+                        this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+                    }
+                }
+                postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+            }
+            final Set<LoanCharge> loanCharges = loan.charges();
+            final Map<Long, BigDecimal> disBuLoanCharges = new HashMap<>();
+            for (final LoanCharge loanCharge : loanCharges) {
+                if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()
+                        && loanCharge.isChargePending()) {
+                    disBuLoanCharges.put(loanCharge.getId(), loanCharge.amountOutstanding());
+                }
+            }
+            final Locale locale = command.extractLocale();
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+            for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) {
+                final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService
+                        .retriveLoanLinkedAssociation(loan.getId());
+                final SavingsAccount fromSavingsAccount = null;
+                final boolean isRegularTransaction = true;
+                final boolean isExceptionForBalanceCheck = false;
+                final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
+                        PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loan.getId(),
+                        "Loan Charge Payment", locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(),
+                        entrySet.getKey(), null, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null,
+                        fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
+                this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+            }
+            updateRecurringCalendarDatesForInterestRecalculation(loan);
+            this.loanAccountDomainService.recalculateAccruals(loan);
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        }
+
+        return changes;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult undoLoanDisbursal(final Long loanId, final JsonCommand command) {
+
+        final AppUser currentUser = getAppUserIfPresent();
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        removeLoanCycle(loan);
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+        //
+        final MonetaryCurrency currency = loan.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+
+        final LocalDate recalculateFrom = null;
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+
+        final Map<String, Object> changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds,
+                existingReversedTransactionIds, currentUser);
+
+        if (!changes.isEmpty()) {
+            saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+            this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN);
+            String noteText = null;
+            if (command.hasParameter("note")) {
+                noteText = command.stringValueOfParameterNamed("note");
+                if (StringUtils.isNotBlank(noteText)) {
+                    final Note note = Note.loanNote(loan, noteText);
+                    this.noteRepository.save(note);
+                }
+            }
+            boolean isAccountTransfer = false;
+            final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
+                    existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+            this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loan.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult makeLoanRepayment(final Long loanId, final JsonCommand command, final boolean isRecoveryRepayment) {
+
+        this.loanEventApiJsonValidator.validateNewRepaymentTransaction(command.json());
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
+        final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
+        changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+        changes.put("paymentTypeId", command.stringValueOfParameterNamed("paymentTypeId"));
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+        }
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+        final Boolean isHolidayValidationDone = false;
+        final HolidayDetailDTO holidayDetailDto = null;
+        boolean isAccountTransfer = false;
+        final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
+        this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, transactionDate, transactionAmount,
+                paymentDetail, noteText, txnExternalId, isRecoveryRepayment, isAccountTransfer, holidayDetailDto, isHolidayValidationDone);
+
+        return commandProcessingResultBuilder.withCommandId(command.commandId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public Map<String, Object> makeLoanBulkRepayment(final CollectionSheetBulkRepaymentCommand bulkRepaymentCommand) {
+
+        final SingleRepaymentCommand[] repaymentCommand = bulkRepaymentCommand.getLoanTransactions();
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final boolean isRecoveryRepayment = false;
+
+        if (repaymentCommand == null) { return changes; }
+        List<Long> transactionIds = new ArrayList<>();
+        boolean isAccountTransfer = false;
+        HolidayDetailDTO holidayDetailDTO = null;
+        Boolean isHolidayValidationDone = false;
+        final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
+        for (final SingleRepaymentCommand singleLoanRepaymentCommand : repaymentCommand) {
+            if (singleLoanRepaymentCommand != null) {
+                Loan loans = this.loanRepository.findOne(singleLoanRepaymentCommand.getLoanId());
+                final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loans.getOfficeId(),
+                        singleLoanRepaymentCommand.getTransactionDate().toDate());
+                final WorkingDays workingDays = this.workingDaysRepository.findOne();
+                final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
+                boolean isHolidayEnabled = false;
+                isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+                holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
+                        allowTransactionsOnNonWorkingDay);
+                loans.validateRepaymentDateIsOnHoliday(singleLoanRepaymentCommand.getTransactionDate(),
+                        holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
+                loans.validateRepaymentDateIsOnNonWorkingDay(singleLoanRepaymentCommand.getTransactionDate(),
+                        holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
+                isHolidayValidationDone = true;
+                break;
+            }
+
+        }
+        for (final SingleRepaymentCommand singleLoanRepaymentCommand : repaymentCommand) {
+            if (singleLoanRepaymentCommand != null) {
+                final Loan loan = this.loanAssembler.assembleFrom(singleLoanRepaymentCommand.getLoanId());
+                final PaymentDetail paymentDetail = singleLoanRepaymentCommand.getPaymentDetail();
+                if (paymentDetail != null && paymentDetail.getId() == null) {
+                    this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
+                }
+                final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
+                LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder,
+                        bulkRepaymentCommand.getTransactionDate(), singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail,
+                        bulkRepaymentCommand.getNote(), null, isRecoveryRepayment, isAccountTransfer, holidayDetailDTO,
+                        isHolidayValidationDone);
+                transactionIds.add(loanTransaction.getId());
+            }
+        }
+        changes.put("loanTransactions", transactionIds);
+        return changes;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) {
+
+        AppUser currentUser = getAppUserIfPresent();
+
+        this.loanEventApiJsonValidator.validateTransaction(command.json());
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(transactionId);
+        if (transactionToAdjust == null) { throw new LoanTransactionNotFoundException(transactionId); }
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADJUST_TRANSACTION,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_ADJUSTED_TRANSACTION, transactionToAdjust));
+        if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) { throw new PlatformServiceUnavailableException(
+                "error.msg.loan.transfer.transaction.update.not.allowed", "Loan transaction:" + transactionId
+                        + " update not allowed as it involves in account transfer", transactionId); }
+        if (loan.isClosedWrittenOff()) { throw new PlatformServiceUnavailableException("error.msg.loan.written.off.update.not.allowed",
+                "Loan transaction:" + transactionId + " update not allowed as loan status is written off", transactionId); }
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
+        final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
+        changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+        changes.put("paymentTypeId", command.stringValueOfParameterNamed("paymentTypeId"));
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount);
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createPaymentDetail(command, changes);
+        LoanTransaction newTransactionDetail = LoanTransaction.repayment(loan.getOffice(), transactionAmountAsMoney, paymentDetail,
+                transactionDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        if (transactionToAdjust.isInterestWaiver()) {
+            Money unrecognizedIncome = transactionAmountAsMoney.zero();
+            Money interestComponent = transactionAmountAsMoney;
+            if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
+                Money receivableInterest = loan.getReceivableInterest(transactionDate);
+                if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
+                    interestComponent = receivableInterest;
+                    unrecognizedIncome = transactionAmountAsMoney.minus(receivableInterest);
+                }
+            }
+            newTransactionDetail = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, transactionDate,
+                    interestComponent, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        }
+
+        LocalDate recalculateFrom = null;
+
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            recalculateFrom = transactionToAdjust.getTransactionDate().isAfter(transactionDate) ? transactionDate : transactionToAdjust
+                    .getTransactionDate();
+        }
+
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+
+        final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail,
+                defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
+                scheduleGeneratorDTO, currentUser);
+
+        if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
+            if (paymentDetail != null) {
+                this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
+            }
+            this.loanTransactionRepository.save(newTransactionDetail);
+        }
+
+        /***
+         * TODO Vishwas Batch save is giving me a
+         * HibernateOptimisticLockingFailureException, looping and saving for
+         * the time being, not a major issue for now as this loop is entered
+         * only in edge cases (when a adjustment is made before the latest
+         * payment recorded against the loan)
+         ***/
+        saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+        if (changedTransactionDetail != null) {
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                this.loanTransactionRepository.save(mapEntry.getValue());
+                // update loan with references to the newly created transactions
+                loan.getLoanTransactions().add(mapEntry.getValue());
+                this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+            }
+        }
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+            Note note = null;
+            /**
+             * If a new transaction is not created, associate note with the
+             * transaction to be adjusted
+             **/
+            if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
+                note = Note.loanTransactionNote(loan, newTransactionDetail, noteText);
+            } else {
+                note = Note.loanTransactionNote(loan, transactionToAdjust, noteText);
+            }
+            this.noteRepository.save(note);
+        }
+
+        Collection<Long> transactionIds = new ArrayList<>();
+        for (LoanTransaction transaction : loan.getLoanTransactions()) {
+            if (transaction.isRefund() && transaction.isNotReversed()) {
+                transactionIds.add(transaction.getId());
+            }
+        }
+
+        if (!transactionIds.isEmpty()) {
+            this.accountTransfersWritePlatformService
+                    .reverseTransfersWithFromAccountTransactions(transactionIds, PortfolioAccountType.LOAN);
+            loan.updateLoanSummarAndStatus();
+        }
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+
+        this.loanAccountDomainService.recalculateAccruals(loan);
+        Map<BUSINESS_ENTITY, Object> entityMap = constructEntityMap(BUSINESS_ENTITY.LOAN_ADJUSTED_TRANSACTION, transactionToAdjust);
+        if (newTransactionDetail.isRepayment() && newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
+            entityMap.put(BUSINESS_ENTITY.LOAN_TRANSACTION, newTransactionDetail);
+        }
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_ADJUST_TRANSACTION, entityMap);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(transactionId) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final JsonCommand command) {
+
+        AppUser currentUser = getAppUserIfPresent();
+
+        this.loanEventApiJsonValidator.validateTransaction(command.json());
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
+        changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount);
+        Money unrecognizedIncome = transactionAmountAsMoney.zero();
+        Money interestComponent = transactionAmountAsMoney;
+        if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
+            Money receivableInterest = loan.getReceivableInterest(transactionDate);
+            if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
+                interestComponent = receivableInterest;
+                unrecognizedIncome = transactionAmountAsMoney.minus(receivableInterest);
+            }
+        }
+        final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney,
+                transactionDate, interestComponent, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WAIVE_INTEREST,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, waiveInterestTransaction));
+        LocalDate recalculateFrom = null;
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            recalculateFrom = transactionDate;
+        }
+
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+        final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction,
+                defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO,
+                currentUser);
+
+        this.loanTransactionRepository.save(waiveInterestTransaction);
+
+        /***
+         * TODO Vishwas Batch save is giving me a
+         * HibernateOptimisticLockingFailureException, looping and saving for
+         * the time being, not a major issue for now as this loop is entered
+         * only in edge cases (when a waiver is made before the latest payment
+         * recorded against the loan)
+         ***/
+        saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+        if (changedTransactionDetail != null) {
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                this.loanTransactionRepository.save(mapEntry.getValue());
+                // update loan with references to the newly created transactions
+                loan.getLoanTransactions().add(mapEntry.getValue());
+                this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+            }
+        }
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+            final Note note = Note.loanTransactionNote(loan, waiveInterestTransaction, noteText);
+            this.noteRepository.save(note);
+        }
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+        this.loanAccountDomainService.recalculateAccruals(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WAIVE_INTEREST,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, waiveInterestTransaction));
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(waiveInterestTransaction.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult writeOff(final Long loanId, final JsonCommand command) {
+        final AppUser currentUser = getAppUserIfPresent();
+
+        this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json());
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WRITTEN_OFF,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        removeLoanCycle(loan);
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        updateLoanCounters(loan, loan.getDisbursementDate());
+
+        LocalDate recalculateFrom = null;
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            recalculateFrom = command.localDateValueOfParameterNamed("transactionDate");
+        }
+
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+
+        final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(),
+                changes, existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
+        LoanTransaction writeoff = changedTransactionDetail.getNewTransactionMappings().remove(0L);
+        this.loanTransactionRepository.save(writeoff);
+        for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+            this.loanTransactionRepository.save(mapEntry.getValue());
+            this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+        }
+        saveLoanWithDataIntegrityViolationChecks(loan);
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+            final Note note = Note.loanTransactionNote(loan, writeoff, noteText);
+            this.noteRepository.save(note);
+        }
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+        this.loanAccountDomainService.recalculateAccruals(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WRITTEN_OFF,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, writeoff));
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(writeoff.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult closeLoan(final Long loanId, final JsonCommand command) {
+
+        AppUser currentUser = getAppUserIfPresent();
+
+        this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json());
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CLOSE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        updateLoanCounters(loan, loan.getDisbursementDate());
+
+        LocalDate recalculateFrom = null;
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            recalculateFrom = command.localDateValueOfParameterNamed("transactionDate");
+        }
+
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+        ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes,
+                existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser);
+        final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L);
+        if (possibleClosingTransaction != null) {
+            this.loanTransactionRepository.save(possibleClosingTransaction);
+        }
+        for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+            this.loanTransactionRepository.save(mapEntry.getValue());
+            this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+        }
+        saveLoanWithDataIntegrityViolationChecks(loan);
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+            final Note note = Note.loanNote(loan, noteText);
+            this.noteRepository.save(note);
+        }
+
+        if (possibleClosingTransaction != null) {
+            postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+        }
+        this.loanAccountDomainService.recalculateAccruals(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CLOSE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        CommandProcessingResult result = null;
+        if (possibleClosingTransaction != null) {
+
+            result = new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(possibleClosingTransaction.getId()) //
+                    .withOfficeId(loan.getOfficeId()) //
+                    .withClientId(loan.getClientId()) //
+                    .withGroupId(loan.getGroupId()) //
+                    .withLoanId(loanId) //
+                    .with(changes) //
+                    .build();
+        } else {
+            result = new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(loanId) //
+                    .withOfficeId(loan.getOfficeId()) //
+                    .withClientId(loan.getClientId()) //
+                    .withGroupId(loan.getGroupId()) //
+                    .withLoanId(loanId) //
+                    .with(changes) //
+                    .build();
+        }
+
+        return result;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult closeAsRescheduled(final Long loanId, final JsonCommand command) {
+
+        this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json());
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        removeLoanCycle(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CLOSE_AS_RESCHEDULE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+
+        loan.closeAsMarkedForReschedule(command, defaultLoanLifecycleStateMachine(), changes);
+
+        saveLoanWithDataIntegrityViolationChecks(loan);
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+            final Note note = Note.loanNote(loan, noteText);
+            this.noteRepository.save(note);
+        }
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CLOSE_AS_RESCHEDULE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loanId) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    private void validateAddingNewChargeAllowed(Set<LoanDisbursementDetails> loanDisburseDetails) {
+        boolean pendingDisbursementAvailable = false;
+        for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) {
+            if (disbursementDetail.actualDisbursementDate() == null) {
+                pendingDisbursementAvailable = true;
+                break;
+            }
+        }
+        if (!pendingDisbursementAvailable) { throw new ChargeCannotBeUpdatedException(
+                "error.msg.charge.cannot.be.updated.no.pending.disbursements.in.loan",
+                "This charge cannot be added, No disbursement is pending"); }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult addLoanCharge(final Long loanId, final JsonCommand command) {
+
+        this.loanEventApiJsonValidator.validateAddLoanCharge(command.json());
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+
+        Set<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
+        final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId");
+        final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
+
+        if (loan.isDisbursed() && chargeDefinition.isDisbursementCharge()) {
+            validateAddingNewChargeAllowed(loanDisburseDetails); // validates
+                                                                 // whether any
+                                                                 // pending
+                                                                 // disbursements
+                                                                 // are
+                                                                 // available to
+                                                                 // apply this
+                                                                 // charge
+        }
+        final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
+        final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
+
+        boolean isAppliedOnBackDate = false;
+        LoanCharge loanCharge = null;
+        LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate();
+        if (chargeDefinition.isPercentageOfDisbursementAmount()) {
+            LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
+            for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) {
+                if (disbursementDetail.actualDisbursementDate() == null) {
+                    loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, disbursementDetail.principal(), null, null, null,
+                            disbursementDetail.expectedDisbursementDateAsLocalDate(), null, null);
+                    loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail);
+                    loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
+                    this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
+                            constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
+                    validateAddLoanCharge(loan, chargeDefinition, loanCharge);
+                    addCharge(loan, chargeDefinition, loanCharge);
+                    isAppliedOnBackDate = true;
+                    if (recalculateFrom.isAfter(disbursementDetail.expectedDisbursementDateAsLocalDate())) {
+                        recalculateFrom = disbursementDetail.expectedDisbursementDateAsLocalDate();
+                    }
+                }
+            }
+            loan.addTrancheLoanCharge(chargeDefinition);
+        } else {
+            loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command);
+            this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
+
+            validateAddLoanCharge(loan, chargeDefinition, loanCharge);
+            isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
+            if (loanCharge.getDueLocalDate() == null || recalculateFrom.isAfter(loanCharge.getDueLocalDate())) {
+                isAppliedOnBackDate = true;
+                recalculateFrom = loanCharge.getDueLocalDate();
+            }
+        }
+
+        boolean reprocessRequired = true;
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
+
+                runScheduleRecalculation(loan, recalculateFrom);
+                reprocessRequired = false;
+            }
+            updateOriginalSchedule(loan);
+        }
+        if (reprocessRequired) {
+            ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
+            if (changedTransactionDetail != null) {
+                for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                    this.loanTransactionRepository.save(mapEntry.getValue());
+                    // update loan with references to the newly created
+                    // transactions
+                    loan.getLoanTransactions().add(mapEntry.getValue());
+                    this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+                }
+            }
+            saveLoanWithDataIntegrityViolationChecks(loan);
+        }
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && isAppliedOnBackDate
+                && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
+            this.loanAccountDomainService.recalculateAccruals(loan);
+        }
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loanCharge.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .build();
+    }
+
+    private void validateAddLoanCharge(final Loan loan, final Charge chargeDefinition, final LoanCharge loanCharge) {
+        if (chargeDefinition.isOverdueInstallment()) {
+            final String defaultUserMessage = "Installment charge cannot be added to the loan.";
+            throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null, chargeDefinition.getName());
+        } else if (loanCharge.getDueLocalDate() != null
+                && loanCharge.getDueLocalDate().isBefore(loan.getLastUserTransactionForChargeCalc())) {
+            final String defaultUserMessage = "charge with date before last transaction date can not be added to loan.";
+            throw new LoanChargeCannotBeAddedException("loanCharge", "date.is.before.last.transaction.date", defaultUserMessage, null,
+                    chargeDefinition.getName());
+        } else if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+
+            if (loanCharge.isInstalmentFee() && loan.status().isActive()) {
+                final String defaultUserMessage = "installment charge addition not allowed after disbursement";
+                throw new LoanChargeCannotBeAddedException("loanCharge", "installment.charge", defaultUserMessage, null,
+                        chargeDefinition.getName());
+            }
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final Set<LoanCharge> loanCharges = new HashSet<>(1);
+            loanCharges.add(loanCharge);
+            this.loanApplicationCommandFromApiJsonHelper.validateLoanCharges(loanCharges, dataValidationErrors);
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+    }
+
+    public void runScheduleRecalculation(final Loan loan, final LocalDate recalculateFrom) {
+        AppUser currentUser = getAppUserIfPresent();
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+            ChangedTransactionDetail changedTransactionDetail = loan.handleRegenerateRepaymentScheduleWithInterestRecalculation(
+                    generatorDTO, currentUser);
+            saveLoanWithDataIntegrityViolationChecks(loan);
+            if (changedTransactionDetail != null) {
+                for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                    this.loanTransactionRepository.save(mapEntry.getValue());
+                    // update loan with references to the newly created
+                    // transactions
+                    loan.getLoanTransactions().add(mapEntry.getValue());
+                    this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+                }
+            }
+
+        }
+    }
+
+    public void updateOriginalSchedule(Loan loan) {
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            final LocalDate recalculateFrom = null;
+            ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+            createLoanScheduleArchive(loan, scheduleGeneratorDTO);
+        }
+
+    }
+
+    private boolean addCharge(final Loan loan, final Charge chargeDefinition, final LoanCharge loanCharge) {
+
+        AppUser currentUser = getAppUserIfPresent();
+        if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) {
+            final String errorMessage = "Charge and Loan must have the same currency.";
+            throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
+        }
+
+        if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
+            final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loan
+                    .getId());
+            if (portfolioAccountData == null) {
+                final String errorMessage = loanCharge.name() + "Charge  requires linked savings account for payment";
+                throw new LinkedAccountRequiredException("loanCharge.add", errorMessage, loanCharge.name());
+            }
+        }
+
+        loan.addLoanCharge(loanCharge);
+
+        this.loanChargeRepository.save(loanCharge);
+
+        /**
+         * we want to apply charge transactions only for those loans charges
+         * that are applied when a loan is active and the loan product uses
+         * Upfront Accruals
+         **/
+        if (loan.status().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
+            final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null, currentUser);
+            this.loanTransactionRepository.save(applyLoanChargeTransaction);
+        }
+        boolean isAppliedOnBackDate = false;
+        if (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate())) {
+            isAppliedOnBackDate = true;
+        }
+        return isAppliedOnBackDate;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateLoanCharge(final Long loanId, final Long loanChargeId, final JsonCommand command) {
+
+        this.loanEventApiJsonValidator.validateUpdateOfLoanCharge(command.json());
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
+
+        // Charges may be edited only when the loan associated with them are
+        // yet to be approved (are in submitted and pending status)
+        if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeUpdatedException(
+                LOAN_CHARGE_CANNOT_BE_UPDATED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
+
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UPDATE_CHARGE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
+
+        final Map<String, Object> changes = loan.updateLoanCharge(loanCharge, command);
+
+        saveLoanWithDataIntegrityViolationChecks(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UPDATE_CHARGE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loanChargeId) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult waiveLoanCharge(final Long loanId, final Long loanChargeId, final JsonCommand command) {
+
+        AppUser currentUser = getAppUserIfPresent();
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        this.loanEventApiJsonValidator.validateInstallmentChargeTransaction(command.json());
+        final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
+
+        // Charges may be waived only when the loan associated with them are
+        // active
+        if (!loan.status().isActive()) { throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.LOAN_INACTIVE,
+                loanCharge.getId()); }
+
+        // validate loan charge is not already paid or waived
+        if (loanCharge.isWaived()) {
+            throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_WAIVED, loanCharge.getId());
+        } else if (loanCharge.isPaid()) { throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_PAID,
+                loanCharge.getId()); }
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WAIVE_CHARGE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
+        Integer loanInstallmentNumber = null;
+        if (loanCharge.isInstalmentFee()) {
+            LoanInstallmentCharge chargePerInstallment = null;
+            if (!StringUtils.isBlank(command.json())) {
+                final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
+                final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber");
+                if (dueDate != null) {
+                    chargePerInstallment = loanCharge.getInstallmentLoanCharge(dueDate);
+                } else if (installmentNumber != null) {
+                    chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber);
+                }
+            }
+            if (chargePerInstallment == null) {
+                chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
+            }
+            if (chargePerInstallment.isWaived()) {
+                throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
+            } else if (chargePerInstallment.isPaid()) { throw new LoanChargeCannotBePayedException(
+                    LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID, loanCharge.getId()); }
+            loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
+        }
+
+        final Map<String, Object> changes = new LinkedHashMap<>(3);
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+        LocalDate recalculateFrom = null;
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+
+        Money accruedCharge = Money.zero(loan.getCurrency());
+        if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
+            Collection<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy(
+                    loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber);
+            for (LoanChargePaidByData chargePaidByData : chargePaidByDatas) {
+                accruedCharge = accruedCharge.plus(chargePaidByData.getAmount());
+            }
+        }
+
+        final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
+                existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge,
+                currentUser);
+
+        this.loanTransactionRepository.save(waiveTransaction);
+        saveLoanWithDataIntegrityViolationChecks(loan);
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WAIVE_CHARGE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loanChargeId) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteLoanCharge(final Long loanId, final Long loanChargeId, final JsonCommand command) {
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
+
+        // Charges may be deleted only when the loan associated with them are
+        // yet to be approved (are in submitted and pending status)
+        if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeDeletedException(
+                LOAN_CHARGE_CANNOT_BE_DELETED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DELETE_CHARGE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
+
+        loan.removeLoanCharge(loanCharge);
+        saveLoanWithDataIntegrityViolationChecks(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_DELETE_CHARGE,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loanChargeId) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .build();
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult payLoanCharge(final Long loanId, Long loanChargeId, final JsonCommand command,
+            final boolean isChargeIdIncludedInJson) {
+
+        this.loanEventApiJsonValidator.validateChargePaymentTransaction(command.json(), isChargeIdIncludedInJson);
+        if (isChargeIdIncludedInJson) {
+            loanChargeId = command.longValueOfParameterNamed("chargeId");
+        }
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
+
+        // Charges may be waived only when the loan associated with them are
+        // active
+        if (!loan.status().isActive()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.LOAN_INACTIVE,
+                loanCharge.getId()); }
+
+        // validate loan charge is not already paid or waived
+        if (loanCharge.isWaived()) {
+            throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
+        } else if (loanCharge.isPaid()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID,
+                loanCharge.getId()); }
+
+        if (!loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) { throw new LoanChargeCannotBePayedException(
+                LOAN_CHARGE_CANNOT_BE_PAYED_REASON.CHARGE_NOT_ACCOUNT_TRANSFER, loanCharge.getId()); }
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        Integer loanInstallmentNumber = null;
+        BigDecimal amount = loanCharge.amountOutstanding();
+        if (loanCharge.isInstalmentFee()) {
+            LoanInstallmentCharge chargePerInstallment = null;
+            final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
+            final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber");
+            if (dueDate != null) {
+                chargePerInstallment = loanCharge.getInstallmentLoanCharge(dueDate);
+            } else if (installmentNumber != null) {
+                chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber);
+            }
+            if (chargePerInstallment == null) {
+                chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
+            }
+            if (chargePerInstallment.isWaived()) {
+                throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
+            } else if (chargePerInstallment.isPaid()) { throw new LoanChargeCannotBePayedException(
+                    LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID, loanCharge.getId()); }
+            loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
+            amount = chargePerInstallment.getAmountOutstanding();
+        }
+
+        final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
+        if (portfolioAccountData == null) {
+            final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment";
+            throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId);
+        }
+        final SavingsAccount fromSavingsAccount = null;
+        final boolean isRegularTransaction = true;
+        final boolean isExceptionForBalanceCheck = false;
+        final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, PortfolioAccountType.SAVINGS,
+                PortfolioAccountType.LOAN, portfolioAccountData.accountId(), loanId, "Loan Charge Payment", locale, fmt, null, null,
+                LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber,
+                AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
+                isExceptionForBalanceCheck);
+        this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loanChargeId) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .withSavingsId(portfolioAccountData.accountId()).build();
+    }
+
+    public void disburseLoanToSavings(final Loan loan, final JsonCommand command, final Money amount, final PaymentDetail paymentDetail) {
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
+        final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loan
+                .getId());
+        if (portfolioAccountData == null) {
+            final String errorMessage = "Disburse Loan with id:" + loan.getId() + " requires linked savings account for payment";
+            throw new LinkedAccountRequiredException("loan.disburse.to.savings", errorMessage, loan.getId());
+        }
+        final SavingsAccount fromSavingsAccount = null;
+        final boolean isExceptionForBalanceCheck = false;
+        final boolean isRegularTransaction = true;
+        final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount.getAmount(),
+                PortfolioAccountType.LOAN, PortfolioAccountType.SAVINGS, loan.getId(), portfolioAccountData.accountId(),
+                "Loan Disbursement", locale, fmt, paymentDetail, LoanTransactionType.DISBURSEMENT.getValue(), null, null, null,
+                AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, txnExternalId, loan, null, fromSavingsAccount,
+                isRegularTransaction, isExceptionForBalanceCheck);
+        this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.TRANSFER_FEE_CHARGE_FOR_LOANS)
+    public void transferFeeCharges() throws JobExecutionException {
+        final Collection<LoanChargeData> chargeDatas = this.loanChargeReadPlatformService.retrieveLoanChargesForFeePayment(
+                ChargePaymentMode.ACCOUNT_TRANSFER.getValue(), LoanStatus.ACTIVE.getValue());
+        final boolean isRegularTransaction = true;
+        final StringBuilder sb = new StringBuilder();
+        if (chargeDatas != null) {
+            for (final LoanChargeData chargeData : chargeDatas) {
+                if (chargeData.isInstallmentFee()) {
+                    final Collection<LoanInstallmentChargeData> chargePerInstallments = this.loanChargeReadPlatformService
+                            .retrieveInstallmentLoanCharges(chargeData.getId(), true);
+                    PortfolioAccountData portfolioAccountData = null;
+                    for (final LoanInstallmentChargeData installmentChargeData : chargePerInstallments) {
+                        if (!installmentChargeData.getDueDate().isAfter(new LocalDate())) {
+                            if (portfolioAccountData == null) {
+                                portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(chargeData
+                                        .getLoanId());
+                            }
+                            final SavingsAccount fromSavingsAccount = null;
+                            final boolean isExceptionForBalanceCheck = false;
+                            final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(new LocalDate(),
+                                    installmentChargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
+                                    portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null,
+                                    null, LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(),
+                                    installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null,
+                                    null, null, null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
+                            transferFeeCharge(sb, accountTransferDTO);
+                        }
+                    }
+                } else if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(new LocalDate())) {
+                    final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
+                            .retriveLoanLinkedAssociation(chargeData.getLoanId());
+                    final SavingsAccount fromSavingsAccount = null;
+                    final boolean isExceptionForBalanceCheck = false;
+                    final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(new LocalDate(),
+                            chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
+                            portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
+                            LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null,
+                            AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount,
+                            isRegularTransaction, isExceptionForBalanceCheck);
+                    transferFeeCharge(sb, accountTransferDTO);
+                }
+            }
+        }
+        if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
+    }
+
+    /**
+     * @param sb
+     * @param accountTransferDTO
+     */
+    private void transferFeeCharge(final StringBuilder sb, final AccountTransferDTO accountTransferDTO) {
+        try {
+            this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+        } catch (final PlatformApiDataValidationException e) {
+            sb.append("Validation exception while paying charge ").append(accountTransferDTO.getChargeId()).append(" for loan id:")
+                    .append(accountTransferDTO.getToAccountId()).append("--------");
+        } catch (final InsufficientAccountBalanceException e) {
+            sb.append("InsufficientAccountBalance Exception while paying charge ").append(accountTransferDTO.getChargeId())
+                    .append("for loan id:").append(accountTransferDTO.getToAccountId()).append("--------");
+
+        }
+    }
+
+    private LoanCharge retrieveLoanChargeBy(final Long loanId, final Long loanChargeId) {
+        final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId);
+        if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); }
+
+        if (loanCharge.hasNotLoanIdentifiedBy(loanId)) { throw new LoanChargeNotFoundException(loanChargeId, loanId); }
+        return loanCharge;
+    }
+
+    @Transactional
+    @Override
+    public LoanTransaction initiateLoanTransfer(final Long accountId, final LocalDate transferDate) {
+
+        AppUser currentUser = getAppUserIfPresent();
+
+        final Loan loan = this.loanAssembler.assembleFrom(accountId);
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_INITIATE_TRANSFER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
+        final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
+
+        final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, transferDate,
+                DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        loan.getLoanTransactions().add(newTransferTransaction);
+        loan.setLoanStatus(LoanStatus.TRANSFER_IN_PROGRESS.getValue());
+
+        this.loanTransactionRepository.save(newTransferTransaction);
+        saveLoanWithDataIntegrityViolationChecks(loan);
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_INITIATE_TRANSFER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        return newTransferTransaction;
+    }
+
+    @Transactional
+    @Override
+    public LoanTransaction acceptLoanTransfer(final Long accountId, final LocalDate transferDate, final Office acceptedInOffice,
+            final Staff loanOfficer) {
+
+        AppUser currentUser = getAppUserIfPresent();
+
+        final Loan loan = this.loanAssembler.assembleFrom(accountId);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ACCEPT_TRANSFER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
+        final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
+
+        final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate,
+                DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        loan.getLoanTransactions().add(newTransferAcceptanceTransaction);
+        if (loan.getTotalOverpaid() != null) {
+            loan.setLoanStatus(LoanStatus.OVERPAID.getValue());
+        } else {
+            loan.setLoanStatus(LoanStatus.ACTIVE.getValue());
+        }
+        if (loanOfficer != null) {
+            loan.reassignLoanOfficer(loanOfficer, transferDate);
+        }
+
+        this.loanTransactionRepository.save(newTransferAcceptanceTransaction);
+        saveLoanWithDataIntegrityViolationChecks(loan);
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_ACCEPT_TRANSFER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        return newTransferAcceptanceTransaction;
+    }
+
+    @Transactional
+    @Override
+    public LoanTransaction withdrawLoanTransfer(final Long accountId, final LocalDate transferDate) {
+
+        AppUser currentUser = getAppUserIfPresent();
+
+        final Loan loan = this.loanAssembler.assembleFrom(accountId);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WITHDRAW_TRANSFER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
+        final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
+
+        final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, transferDate,
+                DateUtils.getLocalDateTimeOfTenant(), currentUser);
+        loan.getLoanTransactions().add(newTransferAcceptanceTransaction);
+        loan.setLoanStatus(LoanStatus.ACTIVE.getValue());
+
+        this.loanTransactionRepository.save(newTransferAcceptanceTransaction);
+        saveLoanWithDataIntegrityViolationChecks(loan);
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WITHDRAW_TRANSFER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        return newTransferAcceptanceTransaction;
+    }
+
+    @Transactional
+    @Override
+    public void rejectLoanTransfer(final Long accountId) {
+        final Loan loan = this.loanAssembler.assembleFrom(accountId);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REJECT_TRANSFER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        loan.setLoanStatus(LoanStatus.TRANSFER_ON_HOLD.getValue());
+        saveLoanWithDataIntegrityViolationChecks(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REJECT_TRANSFER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult loanReassignment(final Long loanId, final JsonCommand command) {
+
+        this.loanEventApiJsonValidator.validateUpdateOfLoanOfficer(command.json());
+
+        final Long fromLoanOfficerId = command.longValueOfParameterNamed("fromLoanOfficerId");
+        final Long toLoanOfficerId = command.longValueOfParameterNamed("toLoanOfficerId");
+
+        final Staff fromLoanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(fromLoanOfficerId);
+        final Staff toLoanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(toLoanOfficerId);
+        final LocalDate dateOfLoanOfficerAssignment = command.localDateValueOfParameterNamed("assignmentDate");
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); }
+
+        loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
+
+        saveLoanWithDataIntegrityViolationChecks(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loanId) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult bulkLoanReassignment(final JsonCommand command) {
+
+        this.loanEventApiJsonValidator.validateForBulkLoanReassignment(command.json());
+
+        final Long fromLoanOfficerId = command.longValueOfParameterNamed("fromLoanOfficerId");
+        final Long toLoanOfficerId = command.longValueOfParameterNamed("toLoanOfficerId");
+        final String[] loanIds = command.arrayValueOfParameterNamed("loans");
+
+        final LocalDate dateOfLoanOfficerAssignment = command.localDateValueOfParameterNamed("assignmentDate");
+
+        final Staff fromLoanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(fromLoanOfficerId);
+        final Staff toLoanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(toLoanOfficerId);
+
+        for (final String loanIdString : loanIds) {
+            final Long loanId = Long.valueOf(loanIdString);
+            final Loan loan = this.loanAssembler.assembleFrom(loanId);
+            this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+            checkClientOrGroupActive(loan);
+
+            if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); }
+
+            loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
+            saveLoanWithDataIntegrityViolationChecks(loan);
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        }
+        this.loanRepository.flush();
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult removeLoanOfficer(final Long loanId, final JsonCommand command) {
+
+        final LoanUpdateCommand loanUpdateCommand = this.loanUpdateCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
+
+        loanUpdateCommand.validate();
+
+        final LocalDate dateOfLoanOfficerunAssigned = command.localDateValueOfParameterNamed("unassignedDate");
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+
+        if (loan.getLoanOfficer() == null) { throw new LoanOfficerUnassignmentException(loanId); }
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REMOVE_OFFICER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        loan.removeLoanOfficer(dateOfLoanOfficerunAssigned);
+
+        saveLoanWithDataIntegrityViolationChecks(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REMOVE_OFFICER,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loanId) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .build();
+    }
+
+    private void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
+            final List<Long> existingReversedTransactionIds) {
+
+        final MonetaryCurrency currency = loan.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+        boolean isAccountTransfer = false;
+        final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
+                existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
+    }
+
+    @Transactional
+    @Override
+    public void applyMeetingDateChanges(final Calendar calendar, final Collection<CalendarInstance> loanCalendarInstances) {
+
+        final Boolean reschedulebasedOnMeetingDates = null;
+        final LocalDate presentMeetingDate = null;
+        final LocalDate newMeetingDate = null;
+
+        applyMeetingDateChanges(calendar, loanCalendarInstances, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
+
+    }
+
+    @Transactional
+    @Override
+    public void applyMeetingDateChanges(final Calendar calendar, final Collection<CalendarInstance> loanCalendarInstances,
+            final Boolean reschedulebasedOnMeetingDates, final LocalDate presentMeetingDate, final LocalDate newMeetingDate) {
+
+        final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+        final WorkingDays workingDays = this.workingDaysRepository.findOne();
+        final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
+                LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue()));
+        final Collection<Integer> loanTypes = new ArrayList<>(Arrays.asList(AccountType.GROUP.getValue(), AccountType.JLG.getValue()));
+        final Collection<Long> loanIds = new ArrayList<>(loanCalendarInstances.size());
+        // loop through loanCalendarInstances to get loan ids
+        for (final CalendarInstance calendarInstance : loanCalendarInstances) {
+            loanIds.add(calendarInstance.getEntityId());
+        }
+
+        final List<Loan> loans = this.loanRepository.findByIdsAndLoanStatusAndLoanType(loanIds, loanStatuses, loanTypes);
+        List<Holiday> holidays = null;
+        // loop through each loan to reschedule the repayment dates
+        for (final Loan loan : loans) {
+            if (loan != null) {
+                if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                    final String defaultUserMessage = "Meeting calendar type update is not supported";
+                    throw new CalendarParameterUpdateNotSupportedException("jlg.loan.recalculation", defaultUserMessage);
+                }
+                holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
+
+                if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) {
+                    loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
+                            holidays, workingDays, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
+                } else {
+                    loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
+                            holidays, workingDays);
+                }
+
+                saveLoanWithDataIntegrityViolationChecks(loan);
+            }
+        }
+    }
+
+    private void removeLoanCycle(final Loan loan) {
+        final List<Loan> loansToUpdate;
+        if (loan.isGroupLoan()) {
+            if (loan.loanProduct().isIncludeInBorrowerCycle()) {
+                loansToUpdate = this.loanRepository.getGroupLoansToUpdateLoanCounter(loan.getCurrentLoanCounter(), loan.getGroupId(),
+                        AccountType.GROUP.getValue());
+            } else {
+                loansToUpdate = this.loanRepository.getGroupLoansToUpdateLoanProductCounter(loan.getLoanProductLoanCounter(),
+                        loan.getGroupId(), AccountType.GROUP.getValue());
+            }
+
+        } else {
+            if (loan.loanProduct().isIncludeInBorrowerCycle()) {
+                loansToUpdate = this.loanRepository
+                        .getClientOrJLGLoansToUpdateLoanCounter(loan.getCurrentLoanCounter(), loan.getClientId());
+            } else {
+                loansToUpdate = this.loanRepository.getClientLoansToUpdateLoanProductCounter(loan.getLoanProductLoanCounter(),
+                        loan.getClientId());
+            }
+
+        }
+        if (loansToUpdate != null) {
+            updateLoanCycleCounter(loansToUpdate, loan);
+        }
+        loan.updateClientLoanCounter(null);
+        loan.updateLoanProductLoanCounter(null);
+
+    }
+
+    private void updateLoanCounters(final Loan loan, final LocalDate actualDisbursementDate) {
+
+        if (loan.isGroupLoan()) {
+            final List<Loan> loansToUpdateForLoanCounter = this.loanRepository.getGroupLoansDisbursedAfter(actualDisbursementDate.toDate(),
+                    loan.getGroupId(), AccountType.GROUP.getValue());
+            final Integer newLoanCounter = getNewGroupLoanCounter(loan);
+            final Integer newLoanProductCounter = getNewGroupLoanProductCounter(loan);
+            updateLoanCounter(loan, loansToUpdateForLoanCounter, newLoanCounter, newLoanProductCounter);
+        } else {
+            final List<Loan> loansToUpdateForLoanCounter = this.loanRepository.getClientOrJLGLoansDisbursedAfter(
+                    actualDisbursementDate.toDate(), loan.getClientId());
+            final Integer newLoanCounter = getNewClientOrJLGLoanCounter(loan);
+            final Integer newLoanProductCounter = getNewClientOrJLGLoanProductCounter(loan);
+            updateLoanCounter(loan, loansToUpdateForLoanCounter, newLoanCounter, newLoanProductCounter);
+        }
+    }
+
+    private Integer getNewGroupLoanCounter(final Loan loan) {
+
+        Integer maxClientLoanCounter = this.loanRepository.getMaxGroupLoanCounter(loan.getGroupId(), AccountType.GROUP.getValue());
+        if (maxClientLoanCounter == null) {
+            maxClientLoanCounter = 1;
+        } else {
+            maxClientLoanCounter = maxClientLoanCounter + 1;
+        }
+        return maxClientLoanCounter;
+    }
+
+    private Integer getNewGroupLoanProductCounter(final Loan loan) {
+
+        Integer maxLoanProductLoanCounter = this.loanRepository.getMaxGroupLoanProductCounter(loan.loanProduct().getId(),
+                loan.getGroupId(), AccountType.GROUP.getValue());
+        if (maxLoanProductLoanCounter == null) {
+            maxLoanProductLoanCounter = 1;
+        } else {
+            maxLoanProductLoanCounter = maxLoanProductLoanCounter + 1;
+        }
+        return maxLoanProductLoanCounter;
+    }
+
+    private void updateLoanCounter(final Loan loan, final List<Loan> loansToUpdateForLoanCounter, Integer newLoanCounter,
+            Integer newLoanProductCounter) {
+
+        final boolean includeInBorrowerCycle = loan.loanProduct().isIncludeInBorrowerCycle();
+        for (final Loan loanToUpdate : loansToUpdateForLoanCounter) {
+            // Update client loan counter if loan product includeInBorrowerCycle
+            // is true
+            if (loanToUpdate.loanProduct().isIncludeInBorrowerCycle()) {
+                Integer currentLoanCounter = loanToUpdate.getCurrentLoanCounter() == null ? 1 : loanToUpdate.getCurrentLoanCounter();
+                if (newLoanCounter > currentLoanCounter) {
+                    newLoanCounter = currentLoanCounter;
+                }
+                loanToUpdate.updateClientLoanCounter(++currentLoanCounter);
+            }
+
+            if (loanToUpdate.loanProduct().getId().equals(loan.loanProduct().getId())) {
+                Integer loanProductLoanCounter = loanToUpdate.getLoanProductLoanCounter();
+                if (newLoanProductCounter > loanProductLoanCounter) {
+                    newLoanProductCounter = loanProductLoanCounter;
+                }
+                loanToUpdate.updateLoanProductLoanCounter(++loanProductLoanCounter);
+            }
+        }
+
+        if (includeInBorrowerCycle) {
+            loan.updateClientLoanCounter(newLoanCounter);
+        } else {
+            loan.updateClientLoanCounter(null);
+        }
+        loan.updateLoanProductLoanCounter(newLoanProductCounter);
+        this.loanRepository.save(loansToUpdateForLoanCounter);
+    }
+
+    private Integer getNewClientOrJLGLoanCounter(final Loan loan) {
+
+        Integer maxClientLoanCounter = this.loanRepository.getMaxClientOrJLGLoanCounter(loan.getClientId());
+        if (maxClientLoanCounter == null) {
+            maxClientLoanCounter = 1;
+        } else {
+            maxClientLoanCounter = maxClientLoanCounter + 1;
+        }
+        return maxClientLoanCounter;
+    }
+
+    private Integer getNewClientOrJLGLoanProductCounter(final Loan loan) {
+
+        Integer maxLoanProductLoanCounter = this.loanRepository.getMaxClientOrJLGLoanProductCounter(loan.loanProduct().getId(),
+                loan.getClientId());
+        if (maxLoanProductLoanCounter == null) {
+            maxLoanProductLoanCounter = 1;
+        } else {
+            maxLoanProductLoanCounter = maxLoanProductLoanCounter + 1;
+        }
+        return maxLoanProductLoanCounter;
+    }
+
+    private void updateLoanCycleCounter(final List<Loan> loansToUpdate, final Loan loan) {
+
+        final Integer currentLoancounter = loan.getCurrentLoanCounter();
+        final Integer currentLoanProductCounter = loan.getLoanProductLoanCounter();
+
+        for (final Loan loanToUpdate : loansToUpdate) {
+            if (loan.loanProduct().isIncludeInBorrowerCycle()) {
+                Integer runningLoancounter = loanToUpdate.getCurrentLoanCounter();
+                if (runningLoancounter > currentLoancounter) {
+                    loanToUpdate.updateClientLoanCounter(--runningLoancounter);
+                }
+            }
+            if (loan.loanProduct().getId().equals(loanToUpdate.loanProduct().getId())) {
+                Integer runningLoanProductCounter = loanToUpdate.getLoanProductLoanCounter();
+                if (runningLoanProductCounter > currentLoanProductCounter) {
+                    loanToUpdate.updateLoanProductLoanCounter(--runningLoanProductCounter);
+                }
+            }
+        }
+        this.loanRepository.save(loansToUpdate);
+    }
+
+    @Transactional
+    @Override
+    @CronTarget(jobName = JobName.APPLY_HOLIDAYS_TO_LOANS)
+    public void applyHolidaysToLoans() {
+
+        final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
+
+        if (!isHolidayEnabled) { return; }
+
+        final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
+                LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue()));
+        // Get all Holidays which are active and not processed
+        final List<Holiday> holidays = this.holidayRepository.findUnprocessed();
+
+        // Loop through all holidays
+        for (final Holiday holiday : holidays) {
+            // All offices to which holiday is applied
+            final Set<Office> offices = holiday.getOffices();
+            final Collection<Long> officeIds = new ArrayList<>(offices.size());
+            for (final Office office : offices) {
+                officeIds.add(office.getId());
+            }
+
+            // get all loans
+            final List<Loan> loans = new ArrayList<>();
+            // get all individual and jlg loans
+            loans.addAll(this.loanRepository.findByClientOfficeIdsAndLoanStatus(officeIds, loanStatuses));
+            // FIXME: AA optimize to get all client and group loans belongs to a
+            // office id
+            // get all group loans
+            loans.addAll(this.loanRepository.findByGroupOfficeIdsAndLoanStatus(officeIds, loanStatuses));
+
+            for (final Loan loan : loans) {
+                // apply holiday
+                loan.applyHolidayToRepaymentScheduleDates(holiday);
+            }
+            this.loanRepository.save(loans);
+            holiday.processed();
+        }
+        this.holidayRepository.save(holidays);
+    }
+
+    private void checkForProductMixRestrictions(final Loan loan) {
+
+        final List<Long> activeLoansLoanProductIds;
+        final Long productId = loan.loanProduct().getId();
+
+        if (loan.isGroupLoan()) {
+            activeLoansLoanProductIds = this.loanRepository.findActiveLoansLoanProductIdsByGroup(loan.getGroupId(),
+                    LoanStatus.ACTIVE.getValue());
+        } else {
+            activeLoansLoanProductIds = this.loanRepository.findActiveLoansLoanProductIdsByClient(loan.getClientId(),
+                    LoanStatus.ACTIVE.getValue());
+        }
+        checkForProductMixRestrictions(activeLoansLoanProductIds, productId, loan.loanProduct().productName());
+    }
+
+    private void checkForProductMixRestrictions(final List<Long> activeLoansLoanProductIds, final Long productId, final String productName) {
+
+        if (!CollectionUtils.isEmpty(activeLoansLoanProductIds)) {
+            final Collection<LoanProductData> restrictedPrdouctsList = this.loanProductReadPlatformService
+                    .retrieveRestrictedProductsForMix(productId);
+            for (final LoanProductData restrictedProduct : restrictedPrdouctsList) {
+                if (activeLoansLoanProductIds.contains(restrictedProduct.getId())) { throw new LoanDisbursalException(productName,
+                        restrictedProduct.getName()); }
+            }
+        }
+    }
+
+    private void checkClientOrGroupActive(final Loan loan) {
+        final Client client = loan.client();
+        if (client != null) {
+            if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); }
+        }
+        final Group group = loan.group();
+        if (group != null) {
+            if (group.isNotActive()) { throw new GroupNotActiveException(group.getId()); }
+        }
+    }
+
+    @Override
+    @Transactional
+    public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDatas) {
+
+        Loan loan = null;
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+        boolean runInterestRecalculation = false;
+        LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant();
+        LocalDate lastChargeDate = null;
+        for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduleDatas) {
+
+            final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString());
+            final JsonCommand command = JsonCommand.from(overdueInstallment.toString(), parsedCommand, this.fromApiJsonHelper, null, null,
+                    null, null, null, loanId, null, null, null, null);
+            LoanOverdueDTO overdueDTO = applyChargeToOverdueLoanInstallment(loanId, overdueInstallment.getChargeId(),
+                    overdueInstallment.getPeriodNumber(), command, loan, existingTransactionIds, existingReversedTransactionIds);
+            loan = overdueDTO.getLoan();
+            runInterestRecalculation = runInterestRecalculation || overdueDTO.isRunInterestRecalculation();
+            if (recalculateFrom.isAfter(overdueDTO.getRecalculateFrom())) {
+                recalculateFrom = overdueDTO.getRecalculateFrom();
+            }
+            if (lastChargeDate == null || overdueDTO.getLastChargeAppliedDate().isAfter(lastChargeDate)) {
+                lastChargeDate = overdueDTO.getLastChargeAppliedDate();
+            }
+        }
+        if (loan != null) {
+            boolean reprocessRequired = true;
+            LocalDate recalculatedTill = loan.fetchInterestRecalculateFromDate();
+            if (recalculateFrom.isAfter(recalculatedTill)) {
+                recalculateFrom = recalculatedTill;
+            }
+
+            if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                if (runInterestRecalculation && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
+                    runScheduleRecalculation(loan, recalculateFrom);
+                    reprocessRequired = false;
+                }
+                updateOriginalSchedule(loan);
+            }
+
+            if (reprocessRequired) {
+                addInstallmentIfPenaltyAppliedAfterLastDueDate(loan, lastChargeDate);
+                ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
+                if (changedTransactionDetail != null) {
+                    for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                        this.loanTransactionRepository.save(mapEntry.getValue());
+                        // update loan with references to the newly created
+                        // transactions
+                        loan.getLoanTransactions().add(mapEntry.getValue());
+                        this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+                    }
+                }
+                saveLoanWithDataIntegrityViolationChecks(loan);
+            }
+
+            postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+
+            if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && runInterestRecalculation
+                    && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
+                this.loanAccountDomainService.recalculateAccruals(loan);
+            }
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        }
+    }
+
+    private void addInstallmentIfPenaltyAppliedAfterLastDueDate(Loan loan, LocalDate lastChargeDate) {
+        if (lastChargeDate != null) {
+            List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
+            LoanRepaymentScheduleInstallment lastInstallment = loan.fetchRepaymentScheduleInstallment(installments.size());
+            if (lastChargeDate.isAfter(lastInstallment.getDueDate())) {
+                if (lastInstallment.isRecalculatedInterestComponent()) {
+                    installments.remove(lastInstallment);
+                    lastInstallment = loan.fetchRepaymentScheduleInstallment(installments.size());
+                }
+                boolean recalculatedInterestComponent = true;
+                BigDecimal principal = BigDecimal.ZERO;
+                BigDecimal interest = BigDecimal.ZERO;
+                BigDecimal feeCharges = BigDecimal.ZERO;
+                BigDecimal penaltyCharges = BigDecimal.ONE;
+                LoanRepaymentScheduleInstallment newEntry = new LoanRepaymentScheduleInstallment(loan, installments.size() + 1,
+                        lastInstallment.getDueDate(), lastChargeDate, principal, interest, feeCharges, penaltyCharges,
+                        recalculatedInterestComponent);
+                installments.add(newEntry);
+            }
+        }
+    }
+
+    public LoanOverdueDTO applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final Integer periodNumber,
+            final JsonCommand command, Loan loan, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
+        boolean runInterestRecalculation = false;
+        final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(loanChargeId);
+
+        Collection<Integer> frequencyNumbers = loanChargeReadPlatformService.retrieveOverdueInstallmentChargeFrequencyNumber(loanId,
+                chargeDefinition.getId(), periodNumber);
+
+        Integer feeFrequency = chargeDefinition.feeFrequency();
+        final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
+        Map<Integer, LocalDate> scheduleDates = new HashMap<>();
+        final Long penaltyWaitPeriodValue = this.configurationDomainService.retrievePenaltyWaitPeriod();
+        final Long penaltyPostingWaitPeriodValue = this.configurationDomainService.retrieveGraceOnPenaltyPostingPeriod();
+        final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
+        Long diff = penaltyWaitPeriodValue + 1 - penaltyPostingWaitPeriodValue;
+        if (diff < 0) {
+            diff = 0L;
+        }
+        LocalDate startDate = dueDate.plusDays(penaltyWaitPeriodValue.intValue() + 1);
+        Integer frequencyNunber = 1;
+        if (feeFrequency == null) {
+            scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
+        } else {
+            while (DateUtils.getLocalDateOfTenant().isAfter(startDate)) {
+                scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
+                LocalDate scheduleDate = scheduledDateGenerator.getRepaymentPeriodDate(PeriodFrequencyType.fromInt(feeFrequency),
+                        chargeDefinition.feeInterval(), startDate, null, null);
+
+                startDate = scheduleDate;
+            }
+        }
+
+        for (Integer frequency : frequencyNumbers) {
+            scheduleDates.remove(frequency);
+        }
+
+        LoanRepaymentScheduleInstallment installment = null;
+        LocalDate lastChargeAppliedDate = dueDate;
+        if (!scheduleDates.isEmpty()) {
+            if (loan == null) {
+                loan = this.loanAssembler.assembleFrom(loanId);
+                checkClientOrGroupActive(loan);
+                existingTransactionIds.addAll(loan.findExistingTransactionIds());
+                existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
+            }
+            installment = loan.fetchRepaymentScheduleInstallment(periodNumber);
+            lastChargeAppliedDate = installment.getDueDate();
+        }
+        LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant();
+
+        if (loan != null) {
+            this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+            for (Map.Entry<Integer, LocalDate> entry : scheduleDates.entrySet()) {
+
+                final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue());
+
+                LoanOverdueInstallmentCharge overdueInstallmentCharge = new LoanOverdueInstallmentCharge(loanCharge, installment,
+                        entry.getKey());
+                loanCharge.updateOverdueInstallmentCharge(overdueInstallmentCharge);
+
+                boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
+                runInterestRecalculation = runInterestRecalculation || isAppliedOnBackDate;
+                if (entry.getValue().isBefore(recalculateFrom)) {
+                    recalculateFrom = entry.getValue();
+                }
+                if (entry.getValue().isAfter(lastChargeAppliedDate)) {
+                    lastChargeAppliedDate = entry.getValue();
+                }
+            }
+        }
+
+        return new LoanOverdueDTO(loan, runInterestRecalculation, recalculateFrom, lastChargeAppliedDate);
+    }
+
+    @Override
+    public CommandProcessingResult undoWriteOff(Long loanId) {
+        final AppUser currentUser = getAppUserIfPresent();
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+        if (!loan.isClosedWrittenOff()) { throw new PlatformServiceUnavailableException(
+                "error.msg.loan.status.not.written.off.update.not.allowed", "Loan :" + loanId
+                        + " update not allowed as loan status is not written off", loanId); }
+        LocalDate recalculateFrom = null;
+        LoanTransaction writeOffTransaction = loan.findWriteOffTransaction();
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF,
+                constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, writeOffTransaction));
+
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+
+        ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds,
+                scheduleGeneratorDTO, currentUser);
+        if (changedTransactionDetail != null) {
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                this.loanTransactionRepository.save(mapEntry.getValue());
+                this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+            }
+        }
+        saveLoanWithDataIntegrityViolationChecks(loan);
+
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+        this.loanAccountDomainService.recalculateAccruals(loan);
+        if (writeOffTransaction != null) {
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, writeOffTransaction));
+        }
+        return new CommandProcessingResultBuilder() //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .build();
+    }
+
+    private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate) {
+        final String json = command.json();
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
+        final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
+        if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
+            final String errorMessage = "For this loan product, disbursement details must be provided";
+            throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+        }
+        final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element);
+
+        loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate,
+                principal);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private void validateForAddAndDeleteTranche(final Loan loan) {
+
+        BigDecimal totalDisbursedAmount = BigDecimal.ZERO;
+        Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
+        for (LoanDisbursementDetails disbursementDetails : loanDisburseDetails) {
+            if (disbursementDetails.actualDisbursementDate() != null) {
+                totalDisbursedAmount = totalDisbursedAmount.add(disbursementDetails.principal());
+            }
+        }
+        if (totalDisbursedAmount.compareTo(loan.getApprovedPrincipal()) == 0) {
+            final String errorMessage = "loan.disbursement.cannot.be.a.edited";
+            throw new LoanMultiDisbursementException(errorMessage);
+        }
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command) {
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+        LocalDate expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
+        if (!loan.loanProduct().isMultiDisburseLoan()) {
+            final String errorMessage = "loan.product.does.not.support.multiple.disbursals";
+            throw new LoanMultiDisbursementException(errorMessage);
+        }
+        if (loan.isSubmittedAndPendingApproval() || loan.isClosed() || loan.isClosedWrittenOff() || loan.status().isClosedObligationsMet()
+                || loan.status().isOverpaid()) {
+            final String errorMessage = "cannot.modify.tranches.if.loan.is.pendingapproval.closed.overpaid.writtenoff";
+            throw new LoanMultiDisbursementException(errorMessage);
+        }
+        validateMultiDisbursementData(command, expectedDisbursementDate);
+
+        this.validateForAddAndDeleteTranche(loan);
+
+        loan.updateDisbursementDetails(command, actualChanges);
+
+        if (loan.getDisbursementDetails().isEmpty()) {
+            final String errorMessage = "For this loan product, disbursement details must be provided";
+            throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+        }
+
+        if (loan.getDisbursementDetails().size() > loan.loanProduct().maxTrancheCount()) {
+            final String errorMessage = "Number of tranche shouldn't be greter than " + loan.loanProduct().maxTrancheCount();
+            throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage, loan.loanProduct()
+                    .maxTrancheCount(), loan.getDisbursementDetails().size());
+        }
+        LoanDisbursementDetails updateDetails = null;
+        return processLoanDisbursementDetail(loan, loanId, command, updateDetails);
+
+    }
+
+    private CommandProcessingResult processLoanDisbursementDetail(final Loan loan, Long loanId, JsonCommand command,
+            LoanDisbursementDetails loanDisbursementDetails) {
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+        existingTransactionIds.addAll(loan.findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        LocalDate recalculateFrom = null;
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+
+        ChangedTransactionDetail changedTransactionDetail = null;
+        AppUser currentUser = getAppUserIfPresent();
+
+        if (command.entityId() != null) {
+
+            changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command,
+                    changes, scheduleGeneratorDTO, currentUser);
+        } else {
+            // BigDecimal setAmount = loan.getApprovedPrincipal();
+            Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
+            BigDecimal setAmount = BigDecimal.ZERO;
+            for (LoanDisbursementDetails details : loanDisburseDetails) {
+                if (details.expectedDisbursementDate() != null) {
+                    setAmount = setAmount.add(details.principal());
+                }
+            }
+
+            loan.repaymentScheduleDetail().setPrincipal(setAmount);
+
+            if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+                loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
+            } else {
+                loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
+                loan.processPostDisbursementTransactions();
+            }
+        }
+
+        saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+        if (command.entityId() != null && changedTransactionDetail != null) {
+            for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+            }
+        }
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            createLoanScheduleArchive(loan, scheduleGeneratorDTO);
+        }
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+        this.loanAccountDomainService.recalculateAccruals(loan);
+        return new CommandProcessingResultBuilder() //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes).build();
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult updateDisbursementDateAndAmountForTranche(final Long loanId, final Long disbursementId,
+            final JsonCommand command) {
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
+        this.loanEventApiJsonValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails);
+
+        return processLoanDisbursementDetail(loan, loanId, command, loanDisbursementDetails);
+
+    }
+
+    public LoanTransaction disburseLoanAmountToSavings(final Long loanId, Long loanChargeId, final JsonCommand command,
+            final boolean isChargeIdIncludedInJson) {
+
+        LoanTransaction transaction = null;
+
+        this.loanEventApiJsonValidator.validateChargePaymentTransaction(command.json(), isChargeIdIncludedInJson);
+        if (isChargeIdIncludedInJson) {
+            loanChargeId = command.longValueOfParameterNamed("chargeId");
+        }
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+        final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
+
+        // Charges may be waived only when the loan associated with them are
+        // active
+        if (!loan.status().isActive()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.LOAN_INACTIVE,
+                loanCharge.getId()); }
+
+        // validate loan charge is not already paid or waived
+        if (loanCharge.isWaived()) {
+            throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
+        } else if (loanCharge.isPaid()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID,
+                loanCharge.getId()); }
+
+        if (!loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) { throw new LoanChargeCannotBePayedException(
+                LOAN_CHARGE_CANNOT_BE_PAYED_REASON.CHARGE_NOT_ACCOUNT_TRANSFER, loanCharge.getId()); }
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        Integer loanInstallmentNumber = null;
+        BigDecimal amount = loanCharge.amountOutstanding();
+        if (loanCharge.isInstalmentFee()) {
+            LoanInstallmentCharge chargePerInstallment = null;
+            final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
+            final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber");
+            if (dueDate != null) {
+                chargePerInstallment = loanCharge.getInstallmentLoanCharge(dueDate);
+            } else if (installmentNumber != null) {
+                chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber);
+            }
+            if (chargePerInstallment == null) {
+                chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
+            }
+            if (chargePerInstallment.isWaived()) {
+                throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
+            } else if (chargePerInstallment.isPaid()) { throw new LoanChargeCannotBePayedException(
+                    LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID, loanCharge.getId()); }
+            loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
+            amount = chargePerInstallment.getAmountOutstanding();
+        }
+
+        final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
+        if (portfolioAccountData == null) {
+            final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment";
+            throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId);
+        }
+        final SavingsAccount fromSavingsAccount = null;
+        final boolean isRegularTransaction = true;
+        final boolean isExceptionForBalanceCheck = false;
+        final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, PortfolioAccountType.SAVINGS,
+                PortfolioAccountType.LOAN, portfolioAccountData.accountId(), loanId, "Loan Charge Payment", locale, fmt, null, null,
+                LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber,
+                AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
+                isExceptionForBalanceCheck);
+        this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+
+        return transaction;
+    }
+
+    @Transactional
+    @Override
+    public void recalculateInterest(final long loanId) {
+        Loan loan = this.loanAssembler.assembleFrom(loanId);
+        LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate();
+        AppUser currentUser = getAppUserIfPresent();
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
+
+        ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO,
+                existingTransactionIds, existingReversedTransactionIds, currentUser);
+
+        saveLoanWithDataIntegrityViolationChecks(loan);
+
+        if (changedTransactionDetail != null) {
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                this.loanTransactionRepository.save(mapEntry.getValue());
+                // update loan with references to the newly created
+                // transactions
+                loan.getLoanTransactions().add(mapEntry.getValue());
+                this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+            }
+        }
+        postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+        this.loanAccountDomainService.recalculateAccruals(loan);
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+    }
+
+    @Override
+    public CommandProcessingResult recoverFromGuarantor(final Long loanId) {
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        this.guarantorDomainService.transaferFundsFromGuarantor(loan);
+        return new CommandProcessingResultBuilder().withLoanId(loanId).build();
+    }
+
+    private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
+        final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
+        if (transferTransaction != null) {
+            transferTransaction.updateToLoanTransaction(newLoanTransaction);
+            this.accountTransferRepository.save(transferTransaction);
+        }
+    }
+
+    private void createLoanScheduleArchive(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
+        LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO);
+        List<LoanRepaymentScheduleInstallment> installments = retrieveRepaymentScheduleFromModel(loanScheduleModel);
+        this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(installments, loan, null);
+
+    }
+
+    private void regenerateScheduleOnDisbursement(final JsonCommand command, final Loan loan, final boolean recalculateSchedule,
+            final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate nextPossibleRepaymentDate, final Date rescheduledRepaymentDate) {
+        AppUser currentUser = getAppUserIfPresent();
+        final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
+        BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
+        loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount, currentUser, 
+        		nextPossibleRepaymentDate, rescheduledRepaymentDate);
+    }
+
+    private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) {
+        final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
+        for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
+            if (scheduledLoanInstallment.isRepaymentPeriod()) {
+                final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
+                        scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
+                        scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
+                        scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
+                        scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent());
+                installments.add(installment);
+            }
+        }
+        return installments;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) {
+        // TODO Auto-generated method stub
+
+        this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+
+        // checkRefundDateIsAfterAtLeastOneRepayment(loanId, transactionDate);
+
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
+        checkIfLoanIsPaidInAdvance(loanId, transactionAmount);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
+        changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+        }
+
+        final PaymentDetail paymentDetail = null;
+
+        final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
+
+        this.loanAccountDomainService.makeRefundForActiveLoan(loanId, commandProcessingResultBuilder, transactionDate, transactionAmount,
+                paymentDetail, noteText, null);
+
+        return commandProcessingResultBuilder.withCommandId(command.commandId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+
+    }
+
+    private void checkIfLoanIsPaidInAdvance(final Long loanId, final BigDecimal transactionAmount) {
+        BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId).getPaidInAdvance();
+
+        if (overpaid == null || overpaid.equals(new BigDecimal(0)) || transactionAmount.floatValue() > overpaid.floatValue()) {
+            if (overpaid == null) overpaid = BigDecimal.ZERO;
+            throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString());
+        }
+    }
+
+    private AppUser getAppUserIfPresent() {
+        AppUser user = null;
+        if (this.context != null) {
+            user = this.context.getAuthenticatedUserIfPresent();
+        }
+        return user;
+    }
+
+    private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
+        Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
+        map.put(entityEvent, entity);
+        return map;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command) {
+        final AppUser currentUser = getAppUserIfPresent();
+
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        final LocalDate recalculateFromDate = loan.getLastRepaymentDate();
+        validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(loan);
+        checkClientOrGroupActive(loan);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UNDO_LASTDISBURSAL,
+                constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+
+        final MonetaryCurrency currency = loan.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFromDate);
+
+        final Map<String, Object> changes = loan.undoLastDisbursal(scheduleGeneratorDTO, existingTransactionIds,
+                existingReversedTransactionIds, currentUser, loan);
+        if (!changes.isEmpty()) {
+            saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+            String noteText = null;
+            if (command.hasParameter("note")) {
+                noteText = command.stringValueOfParameterNamed("note");
+                if (StringUtils.isNotBlank(noteText)) {
+                    final Note note = Note.loanNote(loan, noteText);
+                    this.noteRepository.save(note);
+                }
+            }
+            boolean isAccountTransfer = false;
+            final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
+                    existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+            this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_LASTDISBURSAL,
+                    constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loan.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(loanId) //
+                .with(changes) //
+                .build();
+    }
+
+    private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) {
+        if (!loan.isMultiDisburmentLoan()) {
+            final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
+            throw new LoanMultiDisbursementException(errorMessage);
+        }
+        Integer trancheDisbursedCount = 0;
+        for (LoanDisbursementDetails disbursementDetails : loan.getDisbursementDetails()) {
+            if (disbursementDetails.actualDisbursementDate() != null) {
+                trancheDisbursedCount++;
+            }
+        }
+        if (trancheDisbursedCount <= 1) {
+            final String errorMessage = "tranches.should.be.disbursed.more.than.one.to.undo.last.disbursal";
+            throw new LoanMultiDisbursementException(errorMessage);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
new file mode 100755
index 0000000..f476e46
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
@@ -0,0 +1,128 @@
+/**
+ * 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.loanproduct;
+
+import java.math.BigDecimal;
+
+public interface LoanProductConstants {
+
+    public static final String useBorrowerCycleParameterName = "useBorrowerCycle";
+
+    public static final String principalVariationsForBorrowerCycleParameterName = "principalVariationsForBorrowerCycle";
+    public static final String interestRateVariationsForBorrowerCycleParameterName = "interestRateVariationsForBorrowerCycle";
+    public static final String numberOfRepaymentVariationsForBorrowerCycleParameterName = "numberOfRepaymentVariationsForBorrowerCycle";
+
+    public static final String defaultValueParameterName = "defaultValue";
+    public static final String minValueParameterName = "minValue";
+    public static final String maxValueParameterName = "maxValue";
+    public static final String valueConditionTypeParamName = "valueConditionType";
+    public static final String borrowerCycleNumberParamName = "borrowerCycleNumber";
+    public static final String borrowerCycleIdParameterName = "id";
+
+    public static final String principalPerCycleParameterName = "principalPerCycle";
+    public static final String minPrincipalPerCycleParameterName = "minPrincipalPerCycle";
+    public static final String maxPrincipalPerCycleParameterName = "maxPrincipalPerCycle";
+    public static final String principalValueUsageConditionParamName = "principalValueUsageCondition";
+    public static final String principalCycleNumbersParamName = "principalCycleNumbers";
+
+    public static final String numberOfRepaymentsPerCycleParameterName = "numberOfRepaymentsPerCycle";
+    public static final String minNumberOfRepaymentsPerCycleParameterName = "minNumberOfRepaymentsPerCycle";
+    public static final String maxNumberOfRepaymentsPerCycleParameterName = "maxNumberOfRepaymentsPerCycle";
+    public static final String repaymentValueUsageConditionParamName = "repaymentValueUsageCondition";
+    public static final String repaymentCycleNumberParamName = "repaymentCycleNumber";
+
+    public static final String interestRatePerPeriodPerCycleParameterName = "interestRatePerPeriodPerCycle";
+    public static final String minInterestRatePerPeriodPerCycleParameterName = "minInterestRatePerPeriodPerCycle";
+    public static final String maxInterestRatePerPeriodPerCycleParameterName = "maxInterestRatePerPeriodPerCycle";
+    public static final String interestRateValueUsageConditionParamName = "interestRateValueUsageCondition";
+    public static final String interestRateCycleNumberParamName = "interestRateCycleNumber";
+
+    public static final String principal = "principal";
+    public static final String minPrincipal = "minPrincipal";
+    public static final String maxPrincipal = "maxPrincipalValue";
+
+    public static final String interestRatePerPeriod = "interestRatePerPeriod";
+    public static final String minInterestRatePerPeriod = "minInterestRatePerPeriod";
+    public static final String maxInterestRatePerPeriod = "maxInterestRatePerPeriod";
+
+    public static final String numberOfRepayments = "numberOfRepayments";
+    public static final String minNumberOfRepayments = "minNumberOfRepayments";
+    public static final String maxNumberOfRepayments = "maxNumberOfRepayments";
+
+    public static final String VALUE_CONDITION_END_WITH_ERROR = "condition.type.must.end.with.greterthan";
+    public static final String VALUE_CONDITION_START_WITH_ERROR = "condition.type.must.start.with.equal";
+    public static final String shortName = "shortName";
+
+    public static final String multiDisburseLoanParameterName = "multiDisburseLoan";
+    public static final String maxTrancheCountParameterName = "maxTrancheCount";
+    public static final String outstandingLoanBalanceParameterName = "outstandingLoanBalance";
+
+    public static final String graceOnArrearsAgeingParameterName = "graceOnArrearsAgeing";
+    public static final String overdueDaysForNPAParameterName = "overdueDaysForNPA";
+    public static final String minimumDaysBetweenDisbursalAndFirstRepayment = "minimumDaysBetweenDisbursalAndFirstRepayment";
+    public static final String accountMovesOutOfNPAOnlyOnArrearsCompletionParamName = "accountMovesOutOfNPAOnlyOnArrearsCompletion";
+
+    // Interest recalculation related
+    public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled";
+    public static final String daysInYearTypeParameterName = "daysInYearType";
+    public static final String daysInMonthTypeParameterName = "daysInMonthType";
+    public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod";
+    public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod";
+    public static final String recalculationRestFrequencyTypeParameterName = "recalculationRestFrequencyType";
+    public static final String recalculationRestFrequencyIntervalParameterName = "recalculationRestFrequencyInterval";
+    public static final String recalculationRestFrequencyDateParamName = "recalculationRestFrequencyDate";
+    public static final String isArrearsBasedOnOriginalScheduleParamName = "isArrearsBasedOnOriginalSchedule";
+    public static final String preClosureInterestCalculationStrategyParamName = "preClosureInterestCalculationStrategy";
+    public static final String recalculationCompoundingFrequencyTypeParameterName = "recalculationCompoundingFrequencyType";
+    public static final String recalculationCompoundingFrequencyIntervalParameterName = "recalculationCompoundingFrequencyInterval";
+    public static final String recalculationCompoundingFrequencyDateParamName = "recalculationCompoundingFrequencyDate";
+
+    // Guarantee related
+    public static final String holdGuaranteeFundsParamName = "holdGuaranteeFunds";
+    public static final String mandatoryGuaranteeParamName = "mandatoryGuarantee";
+    public static final String minimumGuaranteeFromOwnFundsParamName = "minimumGuaranteeFromOwnFunds";
+    public static final String minimumGuaranteeFromGuarantorParamName = "minimumGuaranteeFromGuarantor";
+
+    public static final String principalThresholdForLastInstallmentParamName = "principalThresholdForLastInstallment";
+    public static final BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_MULTI_DISBURSE_LOAN = BigDecimal.valueOf(50);
+    public static final BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_SINGLE_DISBURSE_LOAN = BigDecimal.valueOf(0);
+    // Fixed installment configuration related
+    public static final String canDefineEmiAmountParamName = "canDefineInstallmentAmount";
+    public static final String installmentAmountInMultiplesOfParamName = "installmentAmountInMultiplesOf";
+    
+    //Loan Configurable Attributes
+    public static final String allowAttributeOverridesParamName = "allowAttributeOverrides";
+    public static final String amortizationTypeParamName = "amortizationType";
+    public static final String interestTypeParamName = "interestType";
+    public static final String transactionProcessingStrategyIdParamName = "transactionProcessingStrategyId";
+    public static final String interestCalculationPeriodTypeParamName = "interestCalculationPeriodType";
+    public static final String inArrearsToleranceParamName = "inArrearsTolerance";
+    public static final String repaymentEveryParamName = "repaymentEvery";
+    public static final String graceOnPrincipalAndInterestPaymentParamName = "graceOnPrincipalAndInterestPayment";
+    
+    //Variable Installments Settings
+    public static final String allowVariableInstallmentsParamName = "allowVariableInstallments" ;
+    public static final String minimumGapBetweenInstallments = "minimumGap" ;
+    public static final String maximumGapBetweenInstallments = "maximumGap" ;
+    
+    
+    public static final String allowPartialPeriodInterestCalcualtionParamName = "allowPartialPeriodInterestCalcualtion";
+    
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
new file mode 100644
index 0000000..5a1db4d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
@@ -0,0 +1,313 @@
+/**
+ * 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.loanproduct.api;
+
+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.ws.rs.Consumes;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService;
+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.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
+import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
+import org.apache.fineract.portfolio.fund.data.FundData;
+import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
+import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixData;
+import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/loanproducts")
+@Component
+@Scope("singleton")
+public class LoanProductsApiResource {
+
+    private final Set<String> LOAN_PRODUCT_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "shortName", "description",
+            "fundId", "fundName", "includeInBorrowerCycle", "currency", "principal", "minPrincipal", "maxPrincipal", "numberOfRepayments",
+            "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentEvery", "repaymentFrequencyType", "graceOnPrincipalPayment",
+            "graceOnInterestPayment", "graceOnInterestCharged", "interestRatePerPeriod", "minInterestRatePerPeriod",
+            "maxInterestRatePerPeriod", "interestRateFrequencyType", "annualInterestRate", "amortizationType", "interestType",
+            "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "inArrearsTolerance",
+            "transactionProcessingStrategyId", "transactionProcessingStrategyName", "charges", "accountingRule", "externalId",
+            "accountingMappings", "paymentChannelToFundSourceMappings", "fundOptions", "paymentTypeOptions", "currencyOptions",
+            "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "amortizationTypeOptions", "interestTypeOptions",
+            "interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions",
+            "accountingRuleOptions", "accountingMappingOptions", "floatingRateOptions", "isLinkedToFloatingInterestRates",
+            "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate",
+            "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed"));
+
+    private final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(Arrays.asList("productId", "productName", "restrictedProducts",
+            "allowedProducts", "productOptions"));
+
+    private final String resourceNameForPermissions = "LOANPRODUCT";
+
+    private final PlatformSecurityContext context;
+    private final LoanProductReadPlatformService loanProductReadPlatformService;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final CurrencyReadPlatformService currencyReadPlatformService;
+    private final FundReadPlatformService fundReadPlatformService;
+    private final DefaultToApiJsonSerializer<LoanProductData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final LoanDropdownReadPlatformService dropdownReadPlatformService;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService;
+    private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
+    private final DefaultToApiJsonSerializer<ProductMixData> productMixDataApiJsonSerializer;
+    private final ProductMixReadPlatformService productMixReadPlatformService;
+    private final DropdownReadPlatformService commonDropdownReadPlatformService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+    private final FloatingRatesReadPlatformService floatingRateReadPlatformService;
+
+    @Autowired
+    public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService,
+            final ChargeReadPlatformService chargeReadPlatformService, final CurrencyReadPlatformService currencyReadPlatformService,
+            final FundReadPlatformService fundReadPlatformService, final LoanDropdownReadPlatformService dropdownReadPlatformService,
+            final DefaultToApiJsonSerializer<LoanProductData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService,
+            final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService,
+            final DefaultToApiJsonSerializer<ProductMixData> productMixDataApiJsonSerializer,
+            final ProductMixReadPlatformService productMixReadPlatformService,
+            final DropdownReadPlatformService commonDropdownReadPlatformService,
+            PaymentTypeReadPlatformService paymentTypeReadPlatformService,
+            final FloatingRatesReadPlatformService floatingRateReadPlatformService) {
+        this.context = context;
+        this.loanProductReadPlatformService = readPlatformService;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.currencyReadPlatformService = currencyReadPlatformService;
+        this.fundReadPlatformService = fundReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.accountMappingReadPlatformService = accountMappingReadPlatformService;
+        this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
+        this.productMixDataApiJsonSerializer = productMixDataApiJsonSerializer;
+        this.productMixReadPlatformService = productMixReadPlatformService;
+        this.commonDropdownReadPlatformService = commonDropdownReadPlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+        this.floatingRateReadPlatformService = floatingRateReadPlatformService;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createLoanProduct(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createLoanProduct().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllLoanProducts(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (!associationParameters.isEmpty()) {
+            if (associationParameters.contains("productMixes")) {
+                this.context.authenticatedUser().validateHasReadPermission("PRODUCTMIX");
+                final Collection<ProductMixData> productMixes = this.productMixReadPlatformService.retrieveAllProductMixes();
+                return this.productMixDataApiJsonSerializer.serialize(settings, productMixes, this.PRODUCT_MIX_DATA_PARAMETERS);
+            }
+        }
+
+        final Collection<LoanProductData> products = this.loanProductReadPlatformService.retrieveAllLoanProducts();
+
+        return this.toApiJsonSerializer.serialize(settings, products, this.LOAN_PRODUCT_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo, @QueryParam("isProductMixTemplate") final boolean isProductMixTemplate) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (isProductMixTemplate) {
+            this.context.authenticatedUser().validateHasReadPermission("PRODUCTMIX");
+
+            final Collection<LoanProductData> productOptions = this.loanProductReadPlatformService.retrieveAvailableLoanProductsForMix();
+            final ProductMixData productMixData = ProductMixData.template(productOptions);
+            return this.productMixDataApiJsonSerializer.serialize(settings, productMixData, this.PRODUCT_MIX_DATA_PARAMETERS);
+        }
+
+        LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveNewLoanProductDetails();
+        loanProduct = handleTemplate(loanProduct);
+
+        return this.toApiJsonSerializer.serialize(settings, loanProduct, this.LOAN_PRODUCT_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveLoanProductDetails(@PathParam("productId") final Long productId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
+
+        Map<String, Object> accountingMappings = null;
+        Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+        Collection<ChargeToGLAccountMapper> feeToGLAccountMappings = null;
+        Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings = null;
+        if (loanProduct.hasAccountingEnabled()) {
+            accountingMappings = this.accountMappingReadPlatformService.fetchAccountMappingDetailsForLoanProduct(productId, loanProduct
+                    .accountingRuleType().getId().intValue());
+            paymentChannelToFundSourceMappings = this.accountMappingReadPlatformService
+                    .fetchPaymentTypeToFundSourceMappingsForLoanProduct(productId);
+            feeToGLAccountMappings = this.accountMappingReadPlatformService
+                    .fetchFeeToIncomeOrLiabilityAccountMappingsForLoanProduct(productId);
+            penaltyToGLAccountMappings = this.accountMappingReadPlatformService
+                    .fetchPenaltyToIncomeAccountMappingsForLoanProduct(productId);
+            loanProduct = LoanProductData.withAccountingDetails(loanProduct, accountingMappings, paymentChannelToFundSourceMappings,
+                    feeToGLAccountMappings, penaltyToGLAccountMappings);
+        }
+
+        if (settings.isTemplate()) {
+            loanProduct = handleTemplate(loanProduct);
+        }
+        return this.toApiJsonSerializer.serialize(settings, loanProduct, this.LOAN_PRODUCT_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateLoanProduct(@PathParam("productId") final Long productId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateLoanProduct(productId).withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    private LoanProductData handleTemplate(final LoanProductData productData) {
+
+        Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableFees();
+        if (chargeOptions.isEmpty()) {
+            chargeOptions = null;
+        }
+
+        Collection<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveLoanApplicablePenalties();
+        if (penaltyOptions.isEmpty()) {
+            penaltyOptions = null;
+        }
+
+        final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
+        final List<EnumOptionData> amortizationTypeOptions = this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions();
+        final List<EnumOptionData> interestTypeOptions = this.dropdownReadPlatformService.retrieveLoanInterestTypeOptions();
+        final List<EnumOptionData> interestCalculationPeriodTypeOptions = this.dropdownReadPlatformService
+                .retrieveLoanInterestRateCalculatedInPeriodOptions();
+        final List<EnumOptionData> repaymentFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions();
+        final List<EnumOptionData> interestRateFrequencyTypeOptions = this.dropdownReadPlatformService
+                .retrieveInterestRateFrequencyTypeOptions();
+        final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+
+        Collection<FundData> fundOptions = this.fundReadPlatformService.retrieveAllFunds();
+        if (fundOptions.isEmpty()) {
+            fundOptions = null;
+        }
+        final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions = this.dropdownReadPlatformService
+                .retreiveTransactionProcessingStrategies();
+
+        final Map<String, List<GLAccountData>> accountOptions = this.accountingDropdownReadPlatformService
+                .retrieveAccountMappingOptionsForLoanProducts();
+
+        final List<EnumOptionData> accountingRuleTypeOptions = this.accountingDropdownReadPlatformService
+                .retrieveAccountingRuleTypeOptions();
+
+        final List<EnumOptionData> loanCycleValueConditionTypeOptions = this.dropdownReadPlatformService
+                .retrieveLoanCycleValueConditionTypeOptions();
+
+        final List<EnumOptionData> daysInMonthTypeOptions = commonDropdownReadPlatformService.retrieveDaysInMonthTypeOptions();
+        final List<EnumOptionData> daysInYearTypeOptions = commonDropdownReadPlatformService.retrieveDaysInYearTypeOptions();
+        final List<EnumOptionData> interestRecalculationCompoundingTypeOptions = dropdownReadPlatformService
+                .retrieveInterestRecalculationCompoundingTypeOptions();
+        final List<EnumOptionData> rescheduleStrategyTypeOptions = dropdownReadPlatformService.retrieveRescheduleStrategyTypeOptions();
+        final List<EnumOptionData> interestRecalculationFrequencyTypeOptions = dropdownReadPlatformService
+                .retrieveInterestRecalculationFrequencyTypeOptions();
+        final List<EnumOptionData> preCloseInterestCalculationStrategyOptions = dropdownReadPlatformService
+                .retrivePreCloseInterestCalculationStrategyOptions();
+        final List<FloatingRateData> floatingRateOptions = this.floatingRateReadPlatformService.retrieveLookupActive();
+
+        return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions,
+                amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions,
+                interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions,
+                accountingRuleTypeOptions, loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions,
+                interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions,
+                preCloseInterestCalculationStrategyOptions, floatingRateOptions);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanOverdueDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanOverdueDTO.java
new file mode 100755
index 0000000..d4aae8e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanOverdueDTO.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.portfolio.loanproduct.data;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.joda.time.LocalDate;
+
+public class LoanOverdueDTO {
+
+    private final Loan loan;
+    private final boolean runInterestRecalculation;
+    private final LocalDate recalculateFrom;
+    private final LocalDate lastChargeAppliedDate;
+
+    public LoanOverdueDTO(final Loan loan, final boolean runInterestRecalculation, final LocalDate recalculateFrom,
+            final LocalDate lastChargeAppliedDate) {
+        this.loan = loan;
+        this.runInterestRecalculation = runInterestRecalculation;
+        this.recalculateFrom = recalculateFrom;
+        this.lastChargeAppliedDate = lastChargeAppliedDate;
+    }
+
+    public boolean isRunInterestRecalculation() {
+        return this.runInterestRecalculation;
+    }
+
+    public Loan getLoan() {
+        return this.loan;
+    }
+
+    public LocalDate getRecalculateFrom() {
+        return this.recalculateFrom;
+    }
+
+    
+    public LocalDate getLastChargeAppliedDate() {
+        return this.lastChargeAppliedDate;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductBorrowerCycleVariationData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductBorrowerCycleVariationData.java
new file mode 100755
index 0000000..e97f61f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductBorrowerCycleVariationData.java
@@ -0,0 +1,67 @@
+/**
+ * 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.loanproduct.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductParamType;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
+
+public class LoanProductBorrowerCycleVariationData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    private final Integer borrowerCycleNumber;
+    private final EnumOptionData paramType;
+    private final EnumOptionData valueConditionType;
+    @SuppressWarnings("unused")
+    private final BigDecimal minValue;
+    @SuppressWarnings("unused")
+    private final BigDecimal maxValue;
+    private final BigDecimal defaultValue;
+
+    public LoanProductBorrowerCycleVariationData(final Long id, final Integer borrowerCycleNumber, final EnumOptionData paramType,
+            final EnumOptionData valueConditionType, final BigDecimal defaultValue, final BigDecimal minValue, final BigDecimal maxValue) {
+        this.id = id;
+        this.borrowerCycleNumber = borrowerCycleNumber;
+        this.paramType = paramType;
+        this.valueConditionType = valueConditionType;
+        this.minValue = minValue;
+        this.maxValue = maxValue;
+        this.defaultValue = defaultValue;
+    }
+
+    public LoanProductParamType getParamType() {
+        return LoanProductParamType.fromInt(this.paramType.getId().intValue());
+    }
+
+    public Integer getBorrowerCycleNumber() {
+        return this.borrowerCycleNumber;
+    }
+
+    public LoanProductValueConditionType getValueConditionType() {
+        return LoanProductValueConditionType.fromInt(this.valueConditionType.getId().intValue());
+    }
+
+    public BigDecimal getDefaultValue() {
+        return this.defaultValue;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
new file mode 100644
index 0000000..f7ff948
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -0,0 +1,1031 @@
+/**
+ * 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.loanproduct.data;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
+import org.apache.fineract.portfolio.fund.data.FundData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
+import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.joda.time.LocalDate;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Immutable data object to represent loan products.
+ */
+public class LoanProductData {
+
+    private final Long id;
+    private final String name;
+    private final String shortName;
+    private final String description;
+    private final Long fundId;
+    private final String fundName;
+    private final boolean includeInBorrowerCycle;
+    private final boolean useBorrowerCycle;
+    private final LocalDate startDate;
+    private final LocalDate closeDate;
+    private final String status;
+    private final String externalId;
+    // terms
+    private final CurrencyData currency;
+    private final BigDecimal principal;
+    private final BigDecimal minPrincipal;
+    private final BigDecimal maxPrincipal;
+    private final Integer numberOfRepayments;
+    private final Integer minNumberOfRepayments;
+    private final Integer maxNumberOfRepayments;
+    private final Integer repaymentEvery;
+    private final EnumOptionData repaymentFrequencyType;
+    private final BigDecimal interestRatePerPeriod;
+    private final BigDecimal minInterestRatePerPeriod;
+    private final BigDecimal maxInterestRatePerPeriod;
+    private final EnumOptionData interestRateFrequencyType;
+    private final BigDecimal annualInterestRate;
+
+    private final boolean isLinkedToFloatingInterestRates;
+    private final Integer floatingRateId;
+    private final String floatingRateName;
+    private final BigDecimal interestRateDifferential;
+    private final BigDecimal minDifferentialLendingRate;
+    private final BigDecimal defaultDifferentialLendingRate;
+    private final BigDecimal maxDifferentialLendingRate;
+    private final boolean isFloatingInterestRateCalculationAllowed;
+
+    // Variable Installments Settings
+    private final boolean allowVariableInstallments;
+    private final Integer minimumGap;
+    private final Integer maximumGap;
+
+    // settings
+    private final EnumOptionData amortizationType;
+    private final EnumOptionData interestType;
+    private final EnumOptionData interestCalculationPeriodType;
+    private final Boolean allowPartialPeriodInterestCalcualtion;
+    private final BigDecimal inArrearsTolerance;
+    private final Long transactionProcessingStrategyId;
+    private final String transactionProcessingStrategyName;
+    private final Integer graceOnPrincipalPayment;
+    private final Integer graceOnInterestPayment;
+    private final Integer graceOnInterestCharged;
+    private final Integer graceOnArrearsAgeing;
+    private final Integer overdueDaysForNPA;
+    private final EnumOptionData daysInMonthType;
+    private final EnumOptionData daysInYearType;
+    private final boolean isInterestRecalculationEnabled;
+    private final LoanProductInterestRecalculationData interestRecalculationData;
+    private final Integer minimumDaysBetweenDisbursalAndFirstRepayment;
+    private final boolean canDefineInstallmentAmount;
+    private final Integer installmentAmountInMultiplesOf;
+
+    // charges
+    private final Collection<ChargeData> charges;
+
+    private final Collection<LoanProductBorrowerCycleVariationData> principalVariationsForBorrowerCycle;
+    private final Collection<LoanProductBorrowerCycleVariationData> interestRateVariationsForBorrowerCycle;
+    private final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariationsForBorrowerCycle;
+    // accounting
+    private final EnumOptionData accountingRule;
+    private Map<String, Object> accountingMappings;
+    private Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings;
+    private Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
+    private Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
+
+    // template related
+    private final Collection<FundData> fundOptions;
+    @SuppressWarnings("unused")
+    private final Collection<PaymentTypeData> paymentTypeOptions;
+    @SuppressWarnings("unused")
+    private final Collection<CurrencyData> currencyOptions;
+    private final List<EnumOptionData> repaymentFrequencyTypeOptions;
+    private final List<EnumOptionData> interestRateFrequencyTypeOptions;
+    private final List<EnumOptionData> amortizationTypeOptions;
+    private final List<EnumOptionData> interestTypeOptions;
+    private final List<EnumOptionData> interestCalculationPeriodTypeOptions;
+    private final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions;
+    private final Collection<ChargeData> chargeOptions;
+    @SuppressWarnings("unused")
+    private final Collection<ChargeData> penaltyOptions;
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> accountingRuleOptions;
+    @SuppressWarnings("unused")
+    private final Map<String, List<GLAccountData>> accountingMappingOptions;
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> valueConditionTypeOptions;
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> daysInMonthTypeOptions;
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> daysInYearTypeOptions;
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> interestRecalculationCompoundingTypeOptions;
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> rescheduleStrategyTypeOptions;
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> preClosureInterestCalculationStrategyOptions;
+
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> interestRecalculationFrequencyTypeOptions;
+    @SuppressWarnings("unused")
+    private final List<FloatingRateData> floatingRateOptions;
+
+    private final Boolean multiDisburseLoan;
+    private final Integer maxTrancheCount;
+    private final BigDecimal outstandingLoanBalance;
+    private final BigDecimal principalThresholdForLastInstallment;
+
+    private final Boolean holdGuaranteeFunds;
+    private final LoanProductGuaranteeData productGuaranteeData;
+    private final Boolean accountMovesOutOfNPAOnlyOnArrearsCompletion;
+    private LoanProductConfigurableAttributes allowAttributeOverrides;
+
+    /**
+     * Used when returning lookup information about loan product for dropdowns.
+     */
+    public static LoanProductData lookup(final Long id, final String name) {
+        final String shortName = null;
+        final String description = null;
+        final CurrencyData currency = null;
+        final BigDecimal principal = null;
+        final BigDecimal minPrincipal = null;
+        final BigDecimal maxPrincipal = null;
+        final BigDecimal tolerance = null;
+        final Integer numberOfRepayments = null;
+        final Integer minNumberOfRepayments = null;
+        final Integer maxNumberOfRepayments = null;
+        final Integer repaymentEvery = null;
+        final BigDecimal interestRatePerPeriod = null;
+        final BigDecimal minInterestRatePerPeriod = null;
+        final BigDecimal maxInterestRatePerPeriod = null;
+        final BigDecimal annualInterestRate = null;
+        final boolean isLinkedToFloatingInterestRates = false;
+        final Integer floatingRateId = null;
+        final String floatingRateName = null;
+        final BigDecimal interestRateDifferential = null;
+        final BigDecimal minDifferentialLendingRate = null;
+        final BigDecimal defaultDifferentialLendingRate = null;
+        final BigDecimal maxDifferentialLendingRate = null;
+        final boolean isFloatingInterestRateCalculationAllowed = false;
+        final boolean isVariableInstallmentsAllowed = false;
+        final Integer minimumGap = null;
+        final Integer maximumGap = null;
+        final EnumOptionData repaymentFrequencyType = null;
+        final EnumOptionData interestRateFrequencyType = null;
+        final EnumOptionData amortizationType = null;
+        final EnumOptionData interestType = null;
+        final EnumOptionData interestCalculationPeriodType = null;
+        final Boolean allowPartialPeriodInterestCalcualtion = null;
+        final Long fundId = null;
+        final String fundName = null;
+        final Long transactionProcessingStrategyId = null;
+        final String transactionProcessingStrategyName = null;
+        final Integer graceOnPrincipalPayment = null;
+        final Integer graceOnInterestPayment = null;
+        final Integer graceOnInterestCharged = null;
+        final Integer graceOnArrearsAgeing = null;
+        final Integer overdueDaysForNPA = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<LoanProductBorrowerCycleVariationData> principalVariations = new ArrayList<>(1);
+        final Collection<LoanProductBorrowerCycleVariationData> interestRateVariations = new ArrayList<>(1);
+        final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations = new ArrayList<>(1);
+        final EnumOptionData accountingType = null;
+        final boolean includeInBorrowerCycle = false;
+        final boolean useBorrowerCycle = false;
+        final LocalDate startDate = null;
+        final LocalDate closeDate = null;
+        final String status = null;
+        final String externalId = null;
+        final Boolean multiDisburseLoan = null;
+        final Integer maxTrancheCount = null;
+        final BigDecimal outstandingLoanBalance = null;
+        final LoanProductGuaranteeData productGuaranteeData = null;
+        final Boolean holdGuaranteeFunds = false;
+        final BigDecimal principalThresholdForLastInstallment = null;
+        final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
+
+        final EnumOptionData daysInMonthType = null;
+        final EnumOptionData daysInYearType = null;
+        final boolean isInterestRecalculationEnabled = false;
+        final LoanProductInterestRecalculationData interestRecalculationData = null;
+        final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
+        final boolean canDefineInstallmentAmount = false;
+        final Integer installmentAmountInMultiplesOf = null;
+        final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
+
+        return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
+                numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
+                minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
+                amortizationType, interestType, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, fundId, fundName,
+                transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment,
+                graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status,
+                externalId, principalVariations, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount,
+                outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
+                isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment,
+                holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment,
+                accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf,
+                loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName,
+                interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate,
+                isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap);
+
+    }
+
+    public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
+        final String shortName = null;
+        final String description = null;
+        final BigDecimal principal = null;
+        final BigDecimal minPrincipal = null;
+        final BigDecimal maxPrincipal = null;
+        final BigDecimal tolerance = null;
+        final Integer numberOfRepayments = null;
+        final Integer minNumberOfRepayments = null;
+        final Integer maxNumberOfRepayments = null;
+        final Integer repaymentEvery = null;
+        final BigDecimal interestRatePerPeriod = null;
+        final BigDecimal minInterestRatePerPeriod = null;
+        final BigDecimal maxInterestRatePerPeriod = null;
+        final BigDecimal annualInterestRate = null;
+        final boolean isLinkedToFloatingInterestRates = false;
+        final Integer floatingRateId = null;
+        final String floatingRateName = null;
+        final BigDecimal interestRateDifferential = null;
+        final BigDecimal minDifferentialLendingRate = null;
+        final BigDecimal defaultDifferentialLendingRate = null;
+        final BigDecimal maxDifferentialLendingRate = null;
+        final boolean isFloatingInterestRateCalculationAllowed = false;
+        final boolean isVariableInstallmentsAllowed = false;
+        final Integer minimumGap = null;
+        final Integer maximumGap = null;
+        final EnumOptionData repaymentFrequencyType = null;
+        final EnumOptionData interestRateFrequencyType = null;
+        final EnumOptionData amortizationType = null;
+        final EnumOptionData interestType = null;
+        final EnumOptionData interestCalculationPeriodType = null;
+        final Boolean allowPartialPeriodInterestCalcualtion = null;
+        final Long fundId = null;
+        final String fundName = null;
+        final Long transactionProcessingStrategyId = null;
+        final String transactionProcessingStrategyName = null;
+        final Integer graceOnPrincipalPayment = null;
+        final Integer graceOnInterestPayment = null;
+        final Integer graceOnInterestCharged = null;
+        final Integer graceOnArrearsAgeing = null;
+        final Integer overdueDaysForNPA = null;
+
+        final Collection<ChargeData> charges = null;
+        final EnumOptionData accountingType = null;
+        final boolean includeInBorrowerCycle = false;
+        final boolean useBorrowerCycle = false;
+        final LocalDate startDate = null;
+        final LocalDate closeDate = null;
+        final String status = null;
+        final String externalId = null;
+
+        final Collection<LoanProductBorrowerCycleVariationData> principalVariations = new ArrayList<>(1);
+        final Collection<LoanProductBorrowerCycleVariationData> interestRateVariations = new ArrayList<>(1);
+        final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations = new ArrayList<>(1);
+        final Boolean multiDisburseLoan = null;
+        final Integer maxTrancheCount = null;
+        final BigDecimal outstandingLoanBalance = null;
+
+        final EnumOptionData daysInMonthType = null;
+        final EnumOptionData daysInYearType = null;
+        final boolean isInterestRecalculationEnabled = false;
+        final LoanProductInterestRecalculationData interestRecalculationData = null;
+        final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
+        final Boolean holdGuaranteeFunds = false;
+        final LoanProductGuaranteeData productGuaranteeData = null;
+        final BigDecimal principalThresholdForLastInstallment = null;
+        final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
+        final boolean canDefineInstallmentAmount = false;
+        final Integer installmentAmountInMultiplesOf = null;
+        final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
+
+        return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
+                numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
+                minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
+                amortizationType, interestType, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, fundId, fundName,
+                transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment,
+                graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status,
+                externalId, principalVariations, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount,
+                outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
+                isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment,
+                holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment,
+                accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf,
+                loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName,
+                interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate,
+                isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap);
+
+    }
+
+    public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
+        final Long id = null;
+        final String name = null;
+        final String shortName = null;
+        final String description = null;
+        final CurrencyData currency = CurrencyData.blank();
+        final BigDecimal principal = null;
+        final BigDecimal minPrincipal = null;
+        final BigDecimal maxPrincipal = null;
+        final BigDecimal tolerance = null;
+        final Integer numberOfRepayments = null;
+        final Integer minNumberOfRepayments = null;
+        final Integer maxNumberOfRepayments = null;
+
+        final Integer repaymentEvery = null;
+        final BigDecimal interestRatePerPeriod = null;
+        final BigDecimal minInterestRatePerPeriod = null;
+        final BigDecimal maxInterestRatePerPeriod = null;
+        final BigDecimal annualInterestRate = null;
+        final boolean isLinkedToFloatingInterestRates = false;
+        final Integer floatingRateId = null;
+        final String floatingRateName = null;
+        final BigDecimal interestRateDifferential = null;
+        final BigDecimal minDifferentialLendingRate = null;
+        final BigDecimal defaultDifferentialLendingRate = null;
+        final BigDecimal maxDifferentialLendingRate = null;
+        final boolean isFloatingInterestRateCalculationAllowed = false;
+        final boolean isVariableInstallmentsAllowed = false;
+        final Integer minimumGap = null;
+        final Integer maximumGap = null;
+        final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(PeriodFrequencyType.MONTHS);
+        final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(PeriodFrequencyType.MONTHS);
+        final EnumOptionData amortizationType = LoanEnumerations.amortizationType(AmortizationMethod.EQUAL_INSTALLMENTS);
+        final EnumOptionData interestType = LoanEnumerations.interestType(InterestMethod.DECLINING_BALANCE);
+        final EnumOptionData interestCalculationPeriodType = LoanEnumerations
+                .interestCalculationPeriodType(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD);
+        final Boolean allowPartialPeriodInterestCalcualtion = null;
+        final Long fundId = null;
+        final String fundName = null;
+        final Long transactionProcessingStrategyId = null;
+        final String transactionProcessingStrategyName = null;
+
+        final Integer graceOnPrincipalPayment = null;
+        final Integer graceOnInterestPayment = null;
+        final Integer graceOnInterestCharged = null;
+        final Integer graceOnArrearsAgeing = null;
+        final Integer overdueDaysForNPA = null;
+
+        final Collection<ChargeData> charges = null;
+        final Collection<LoanProductBorrowerCycleVariationData> principalVariationsForBorrowerCycle = new ArrayList<>(1);
+        final Collection<LoanProductBorrowerCycleVariationData> interestRateVariationsForBorrowerCycle = new ArrayList<>(1);
+        final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>(1);
+
+        final EnumOptionData accountingType = AccountingEnumerations.accountingRuleType(AccountingRuleType.NONE);
+        final boolean includeInBorrowerCycle = false;
+        final boolean useBorrowerCycle = false;
+        final LocalDate startDate = null;
+        final LocalDate closeDate = null;
+        final String status = null;
+        final String externalId = null;
+        final Boolean multiDisburseLoan = null;
+        final Integer maxTrancheCount = null;
+        final BigDecimal outstandingLoanBalance = null;
+
+        final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(DaysInMonthType.ACTUAL);
+        final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(DaysInYearType.ACTUAL);
+        final boolean isInterestRecalculationEnabled = false;
+        final LoanProductInterestRecalculationData interestRecalculationData = LoanProductInterestRecalculationData
+                .sensibleDefaultsForNewLoanProductCreation();
+        final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
+        final Boolean holdGuaranteeFunds = false;
+        final LoanProductGuaranteeData productGuaranteeData = null;
+        final BigDecimal principalThresholdForLastInstallment = null;
+        final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
+        final boolean canDefineInstallmentAmount = false;
+        final Integer installmentAmountInMultiplesOf = null;
+        final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
+
+        return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
+                numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
+                minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
+                amortizationType, interestType, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, fundId, fundName,
+                transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment,
+                graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status,
+                externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
+                numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
+                graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
+                interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
+                principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
+                installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId,
+                floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
+                maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap);
+
+    }
+
+    public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
+            final Collection<ChargeToGLAccountMapper> feeToGLAccountMappings,
+            final Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings) {
+        productData.accountingMappings = accountingMappings;
+        productData.paymentChannelToFundSourceMappings = paymentChannelToFundSourceMappings;
+        productData.feeToIncomeAccountMappings = feeToGLAccountMappings;
+        productData.penaltyToIncomeAccountMappings = penaltyToGLAccountMappings;
+        return productData;
+    }
+
+    public LoanProductData(final Long id, final String name, final String shortName, final String description, final CurrencyData currency,
+            final BigDecimal principal, final BigDecimal minPrincipal, final BigDecimal maxPrincipal, final BigDecimal tolerance,
+            final Integer numberOfRepayments, final Integer minNumberOfRepayments, final Integer maxNumberOfRepayments,
+            final Integer repaymentEvery, final BigDecimal interestRatePerPeriod, final BigDecimal minInterestRatePerPeriod,
+            final BigDecimal maxInterestRatePerPeriod, final BigDecimal annualInterestRate, final EnumOptionData repaymentFrequencyType,
+            final EnumOptionData interestRateFrequencyType, final EnumOptionData amortizationType, final EnumOptionData interestType,
+            final EnumOptionData interestCalculationPeriodType, final Boolean allowPartialPeriodInterestCalcualtion, final Long fundId,
+            final String fundName, final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName,
+            final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
+            final Collection<ChargeData> charges, final EnumOptionData accountingType, final boolean includeInBorrowerCycle,
+            boolean useBorrowerCycle, final LocalDate startDate, final LocalDate closeDate, final String status, final String externalId,
+            Collection<LoanProductBorrowerCycleVariationData> principalVariations,
+            Collection<LoanProductBorrowerCycleVariationData> interestRateVariations,
+            Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations, Boolean multiDisburseLoan,
+            Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing,
+            final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType,
+            final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData,
+            final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds,
+            final LoanProductGuaranteeData loanProductGuaranteeData, final BigDecimal principalThresholdForLastInstallment,
+            final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, boolean canDefineInstallmentAmount,
+            Integer installmentAmountInMultiplesOf, LoanProductConfigurableAttributes allowAttributeOverrides,
+            boolean isLinkedToFloatingInterestRates, Integer floatingRateId, String floatingRateName, BigDecimal interestRateDifferential,
+            BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, BigDecimal maxDifferentialLendingRate,
+            boolean isFloatingInterestRateCalculationAllowed, final boolean isVariableInstallmentsAllowed,
+            final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments) {
+        this.id = id;
+        this.name = name;
+        this.shortName = shortName;
+        this.description = description;
+        this.currency = currency;
+        this.principal = principal;
+        this.minPrincipal = minPrincipal;
+        this.maxPrincipal = maxPrincipal;
+        this.inArrearsTolerance = tolerance;
+        this.numberOfRepayments = numberOfRepayments;
+        this.minNumberOfRepayments = minNumberOfRepayments;
+        this.maxNumberOfRepayments = maxNumberOfRepayments;
+        this.graceOnPrincipalPayment = graceOnPrincipalPayment;
+        this.graceOnInterestPayment = graceOnInterestPayment;
+        this.graceOnInterestCharged = graceOnInterestCharged;
+        this.repaymentEvery = repaymentEvery;
+        this.interestRatePerPeriod = interestRatePerPeriod;
+        this.minInterestRatePerPeriod = minInterestRatePerPeriod;
+        this.maxInterestRatePerPeriod = maxInterestRatePerPeriod;
+        this.annualInterestRate = annualInterestRate;
+        this.isLinkedToFloatingInterestRates = isLinkedToFloatingInterestRates;
+        this.floatingRateId = floatingRateId;
+        this.floatingRateName = floatingRateName;
+        this.interestRateDifferential = interestRateDifferential;
+        this.minDifferentialLendingRate = minDifferentialLendingRate;
+        this.defaultDifferentialLendingRate = defaultDifferentialLendingRate;
+        this.maxDifferentialLendingRate = maxDifferentialLendingRate;
+        this.isFloatingInterestRateCalculationAllowed = isFloatingInterestRateCalculationAllowed;
+        this.allowVariableInstallments = isVariableInstallmentsAllowed;
+        this.minimumGap = minimumGapBetweenInstallments;
+        this.maximumGap = maximumGapBetweenInstallments;
+        this.repaymentFrequencyType = repaymentFrequencyType;
+        this.interestRateFrequencyType = interestRateFrequencyType;
+        this.amortizationType = amortizationType;
+        this.interestType = interestType;
+        this.interestCalculationPeriodType = interestCalculationPeriodType;
+        this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion;
+        this.fundId = fundId;
+        this.fundName = fundName;
+        this.transactionProcessingStrategyId = transactionProcessingStrategyId;
+        this.transactionProcessingStrategyName = transactionProcessingStrategyName;
+        this.charges = charges;
+        this.accountingRule = accountingType;
+        this.includeInBorrowerCycle = includeInBorrowerCycle;
+        this.useBorrowerCycle = useBorrowerCycle;
+        this.startDate = startDate;
+        this.closeDate = closeDate;
+        this.status = status;
+        this.externalId = externalId;
+        this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
+
+        this.chargeOptions = null;
+        this.penaltyOptions = null;
+        this.paymentTypeOptions = null;
+        this.currencyOptions = null;
+        this.fundOptions = null;
+        this.transactionProcessingStrategyOptions = null;
+        this.amortizationTypeOptions = null;
+        this.interestTypeOptions = null;
+        this.interestCalculationPeriodTypeOptions = null;
+        this.repaymentFrequencyTypeOptions = null;
+        this.interestRateFrequencyTypeOptions = null;
+        this.floatingRateOptions = null;
+
+        this.accountingMappingOptions = null;
+        this.accountingRuleOptions = null;
+        this.accountingMappings = null;
+        this.paymentChannelToFundSourceMappings = null;
+        this.feeToIncomeAccountMappings = null;
+        this.penaltyToIncomeAccountMappings = null;
+        this.valueConditionTypeOptions = null;
+        this.principalVariationsForBorrowerCycle = principalVariations;
+        this.interestRateVariationsForBorrowerCycle = interestRateVariations;
+        this.numberOfRepaymentVariationsForBorrowerCycle = numberOfRepaymentVariations;
+        this.multiDisburseLoan = multiDisburseLoan;
+        this.outstandingLoanBalance = outstandingLoanBalance;
+        this.maxTrancheCount = maxTrancheCount;
+        this.graceOnArrearsAgeing = graceOnArrearsAgeing;
+        this.overdueDaysForNPA = overdueDaysForNPA;
+        this.daysInMonthType = daysInMonthType;
+        this.daysInYearType = daysInYearType;
+        this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
+        this.interestRecalculationData = interestRecalculationData;
+        this.holdGuaranteeFunds = holdGuaranteeFunds;
+        this.productGuaranteeData = loanProductGuaranteeData;
+        this.principalThresholdForLastInstallment = principalThresholdForLastInstallment;
+        this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion;
+        this.allowAttributeOverrides = allowAttributeOverrides;
+
+        this.daysInMonthTypeOptions = null;
+        this.daysInYearTypeOptions = null;
+        this.interestRecalculationCompoundingTypeOptions = null;
+        this.rescheduleStrategyTypeOptions = null;
+        this.interestRecalculationFrequencyTypeOptions = null;
+
+        this.canDefineInstallmentAmount = canDefineInstallmentAmount;
+        this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
+        this.preClosureInterestCalculationStrategyOptions = null;
+
+    }
+
+    public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
+            final Collection<ChargeData> penaltyOptions, final Collection<PaymentTypeData> paymentTypeOptions,
+            final Collection<CurrencyData> currencyOptions, final List<EnumOptionData> amortizationTypeOptions,
+            final List<EnumOptionData> interestTypeOptions, final List<EnumOptionData> interestCalculationPeriodTypeOptions,
+            final List<EnumOptionData> repaymentFrequencyTypeOptions, final List<EnumOptionData> interestRateFrequencyTypeOptions,
+            final Collection<FundData> fundOptions, final Collection<TransactionProcessingStrategyData> transactionStrategyOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final List<EnumOptionData> accountingRuleOptions,
+            final List<EnumOptionData> valueConditionTypeOptions, final List<EnumOptionData> daysInMonthTypeOptions,
+            final List<EnumOptionData> daysInYearTypeOptions, final List<EnumOptionData> interestRecalculationCompoundingTypeOptions,
+            final List<EnumOptionData> rescheduleStrategyTypeOptions, final List<EnumOptionData> interestRecalculationFrequencyTypeOptions,
+            final List<EnumOptionData> preCloseInterestCalculationStrategyOptions, final List<FloatingRateData> floatingRateOptions) {
+        this.id = productData.id;
+        this.name = productData.name;
+        this.shortName = productData.shortName;
+        this.description = productData.description;
+        this.fundId = productData.fundId;
+        this.fundName = productData.fundName;
+
+        this.principal = productData.principal;
+        this.minPrincipal = productData.minPrincipal;
+        this.maxPrincipal = productData.maxPrincipal;
+        this.inArrearsTolerance = productData.inArrearsTolerance;
+        this.numberOfRepayments = productData.numberOfRepayments;
+        this.minNumberOfRepayments = productData.minNumberOfRepayments;
+        this.maxNumberOfRepayments = productData.maxNumberOfRepayments;
+        this.repaymentEvery = productData.repaymentEvery;
+        this.interestRatePerPeriod = productData.interestRatePerPeriod;
+        this.minInterestRatePerPeriod = productData.minInterestRatePerPeriod;
+        this.maxInterestRatePerPeriod = productData.maxInterestRatePerPeriod;
+        this.annualInterestRate = productData.annualInterestRate;
+        this.isLinkedToFloatingInterestRates = productData.isLinkedToFloatingInterestRates;
+        this.floatingRateId = productData.floatingRateId;
+        this.floatingRateName = productData.floatingRateName;
+        this.interestRateDifferential = productData.interestRateDifferential;
+        this.minDifferentialLendingRate = productData.minDifferentialLendingRate;
+        this.defaultDifferentialLendingRate = productData.defaultDifferentialLendingRate;
+        this.maxDifferentialLendingRate = productData.maxDifferentialLendingRate;
+        this.isFloatingInterestRateCalculationAllowed = productData.isFloatingInterestRateCalculationAllowed;
+        this.allowVariableInstallments = productData.allowVariableInstallments;
+        this.minimumGap = productData.minimumGap;
+        this.maximumGap = productData.maximumGap;
+        this.repaymentFrequencyType = productData.repaymentFrequencyType;
+        this.interestRateFrequencyType = productData.interestRateFrequencyType;
+        this.amortizationType = productData.amortizationType;
+        this.interestType = productData.interestType;
+        this.interestCalculationPeriodType = productData.interestCalculationPeriodType;
+        this.allowPartialPeriodInterestCalcualtion = productData.allowPartialPeriodInterestCalcualtion;
+        this.startDate = productData.startDate;
+        this.closeDate = productData.closeDate;
+        this.status = productData.status;
+        this.externalId = productData.externalId;
+
+        this.charges = nullIfEmpty(productData.charges());
+        this.principalVariationsForBorrowerCycle = productData.principalVariationsForBorrowerCycle;
+        this.interestRateVariationsForBorrowerCycle = productData.interestRateVariationsForBorrowerCycle;
+        this.numberOfRepaymentVariationsForBorrowerCycle = productData.numberOfRepaymentVariationsForBorrowerCycle;
+        this.accountingRule = productData.accountingRule;
+        this.accountingMappings = productData.accountingMappings;
+        this.paymentChannelToFundSourceMappings = productData.paymentChannelToFundSourceMappings;
+        this.feeToIncomeAccountMappings = productData.feeToIncomeAccountMappings;
+        this.penaltyToIncomeAccountMappings = productData.penaltyToIncomeAccountMappings;
+
+        this.chargeOptions = chargeOptions;
+        this.penaltyOptions = penaltyOptions;
+        this.paymentTypeOptions = paymentTypeOptions;
+        this.currencyOptions = currencyOptions;
+        this.currency = productData.currency;
+        this.fundOptions = fundOptions;
+        this.transactionProcessingStrategyOptions = transactionStrategyOptions;
+        this.floatingRateOptions = floatingRateOptions;
+        if (this.transactionProcessingStrategyOptions != null && this.transactionProcessingStrategyOptions.size() == 1) {
+            final List<TransactionProcessingStrategyData> listOfOptions = new ArrayList<>(this.transactionProcessingStrategyOptions);
+
+            this.transactionProcessingStrategyId = listOfOptions.get(0).id();
+            this.transactionProcessingStrategyName = listOfOptions.get(0).name();
+        } else {
+            this.transactionProcessingStrategyId = productData.transactionProcessingStrategyId;
+            this.transactionProcessingStrategyName = productData.transactionProcessingStrategyName;
+        }
+
+        this.graceOnPrincipalPayment = productData.graceOnPrincipalPayment;
+        this.graceOnInterestPayment = productData.graceOnInterestPayment;
+        this.graceOnInterestCharged = productData.graceOnInterestCharged;
+        this.includeInBorrowerCycle = productData.includeInBorrowerCycle;
+        this.useBorrowerCycle = productData.useBorrowerCycle;
+        this.multiDisburseLoan = productData.multiDisburseLoan;
+        this.maxTrancheCount = productData.maxTrancheCount;
+        this.outstandingLoanBalance = productData.outstandingLoanBalance;
+        this.minimumDaysBetweenDisbursalAndFirstRepayment = productData.minimumDaysBetweenDisbursalAndFirstRepayment;
+
+        this.amortizationTypeOptions = amortizationTypeOptions;
+        this.interestTypeOptions = interestTypeOptions;
+        this.interestCalculationPeriodTypeOptions = interestCalculationPeriodTypeOptions;
+        this.repaymentFrequencyTypeOptions = repaymentFrequencyTypeOptions;
+        this.interestRateFrequencyTypeOptions = interestRateFrequencyTypeOptions;
+
+        this.accountingMappingOptions = accountingMappingOptions;
+        this.accountingRuleOptions = accountingRuleOptions;
+        this.valueConditionTypeOptions = valueConditionTypeOptions;
+        this.graceOnArrearsAgeing = productData.graceOnArrearsAgeing;
+        this.overdueDaysForNPA = productData.overdueDaysForNPA;
+
+        this.daysInMonthType = productData.daysInMonthType;
+        this.daysInYearType = productData.daysInYearType;
+        this.isInterestRecalculationEnabled = productData.isInterestRecalculationEnabled;
+        this.interestRecalculationData = productData.interestRecalculationData;
+        this.holdGuaranteeFunds = productData.holdGuaranteeFunds;
+        this.productGuaranteeData = productData.productGuaranteeData;
+        this.principalThresholdForLastInstallment = productData.principalThresholdForLastInstallment;
+        this.accountMovesOutOfNPAOnlyOnArrearsCompletion = productData.accountMovesOutOfNPAOnlyOnArrearsCompletion;
+
+        this.daysInMonthTypeOptions = daysInMonthTypeOptions;
+        this.daysInYearTypeOptions = daysInYearTypeOptions;
+        this.interestRecalculationCompoundingTypeOptions = interestRecalculationCompoundingTypeOptions;
+        this.rescheduleStrategyTypeOptions = rescheduleStrategyTypeOptions;
+        this.allowAttributeOverrides = productData.allowAttributeOverrides;
+
+        if (CollectionUtils.isEmpty(interestRecalculationFrequencyTypeOptions)) {
+            this.interestRecalculationFrequencyTypeOptions = null;
+        } else {
+            this.interestRecalculationFrequencyTypeOptions = interestRecalculationFrequencyTypeOptions;
+        }
+
+        this.canDefineInstallmentAmount = productData.canDefineInstallmentAmount;
+        this.installmentAmountInMultiplesOf = productData.installmentAmountInMultiplesOf;
+        this.preClosureInterestCalculationStrategyOptions = preCloseInterestCalculationStrategyOptions;
+    }
+
+    private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
+        Collection<ChargeData> chargesLocal = charges;
+        if (charges == null || charges.isEmpty()) {
+            chargesLocal = null;
+        }
+        return chargesLocal;
+    }
+
+    public Collection<ChargeData> charges() {
+        Collection<ChargeData> chargesLocal = new ArrayList<>();
+        if (this.charges != null) {
+            chargesLocal = this.charges;
+        }
+        return chargesLocal;
+    }
+
+    public EnumOptionData accountingRuleType() {
+        return this.accountingRule;
+    }
+
+    public boolean hasAccountingEnabled() {
+        return this.accountingRule.getId() > AccountingRuleType.NONE.getValue();
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public Long getFundId() {
+        return this.fundId;
+    }
+
+    public String getFundName() {
+        return this.fundName;
+    }
+
+    public Long getTransactionProcessingStrategyId() {
+        return this.transactionProcessingStrategyId;
+    }
+
+    public String getTransactionProcessingStrategyName() {
+        return this.transactionProcessingStrategyName;
+    }
+
+    public CurrencyData getCurrency() {
+        return this.currency;
+    }
+
+    public BigDecimal getPrincipal() {
+        return this.principal;
+    }
+
+    public BigDecimal getMinPrincipal() {
+        return this.minPrincipal;
+    }
+
+    public BigDecimal getMaxPrincipal() {
+        return this.maxPrincipal;
+    }
+
+    public BigDecimal getInArrearsTolerance() {
+        return this.inArrearsTolerance;
+    }
+
+    public Integer getNumberOfRepayments() {
+        return this.numberOfRepayments;
+    }
+
+    public Integer getRepaymentEvery() {
+        return this.repaymentEvery;
+    }
+
+    public BigDecimal getInterestRatePerPeriod() {
+        return this.interestRatePerPeriod;
+    }
+
+    public BigDecimal getAnnualInterestRate() {
+        return this.annualInterestRate;
+    }
+
+    public EnumOptionData getRepaymentFrequencyType() {
+        return this.repaymentFrequencyType;
+    }
+
+    public Integer getGraceOnPrincipalPayment() {
+        return this.graceOnPrincipalPayment;
+    }
+
+    public Integer getGraceOnInterestPayment() {
+        return this.graceOnInterestPayment;
+    }
+
+    public Integer getGraceOnInterestCharged() {
+        return this.graceOnInterestCharged;
+    }
+
+    public EnumOptionData getInterestRateFrequencyType() {
+        return this.interestRateFrequencyType;
+    }
+
+    public EnumOptionData getAmortizationType() {
+        return this.amortizationType;
+    }
+
+    public EnumOptionData getInterestType() {
+        return this.interestType;
+    }
+
+    public EnumOptionData getInterestCalculationPeriodType() {
+        return this.interestCalculationPeriodType;
+    }
+
+    public Collection<FundData> getFundOptions() {
+        return this.fundOptions;
+    }
+
+    public List<EnumOptionData> getAmortizationTypeOptions() {
+        return this.amortizationTypeOptions;
+    }
+
+    public List<EnumOptionData> getInterestTypeOptions() {
+        return this.interestTypeOptions;
+    }
+
+    public List<EnumOptionData> getInterestCalculationPeriodTypeOptions() {
+        return this.interestCalculationPeriodTypeOptions;
+    }
+
+    public List<EnumOptionData> getRepaymentFrequencyTypeOptions() {
+        return this.repaymentFrequencyTypeOptions;
+    }
+
+    public List<EnumOptionData> getInterestRateFrequencyTypeOptions() {
+        return this.interestRateFrequencyTypeOptions;
+    }
+
+    public Collection<ChargeData> getChargeOptions() {
+        return this.chargeOptions;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        final LoanProductData loanProductData = (LoanProductData) obj;
+        return loanProductData.id.equals(this.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return this.id.hashCode();
+    }
+
+    public boolean useBorrowerCycle() {
+        return this.useBorrowerCycle;
+    }
+
+    public Collection<LoanProductBorrowerCycleVariationData> getPrincipalVariationsForBorrowerCycle() {
+        return this.principalVariationsForBorrowerCycle;
+    }
+
+    public Collection<LoanProductBorrowerCycleVariationData> getInterestRateVariationsForBorrowerCycle() {
+        return this.interestRateVariationsForBorrowerCycle;
+    }
+
+    public Collection<LoanProductBorrowerCycleVariationData> getNumberOfRepaymentVariationsForBorrowerCycle() {
+        return this.numberOfRepaymentVariationsForBorrowerCycle;
+    }
+
+    public Boolean getMultiDisburseLoan() {
+        return this.multiDisburseLoan;
+    }
+
+    public BigDecimal getOutstandingLoanBalance() {
+        return this.outstandingLoanBalance;
+    }
+
+    public Integer getGraceOnArrearsAgeing() {
+        return this.graceOnArrearsAgeing;
+    }
+
+    public EnumOptionData getDaysInMonthType() {
+        return this.daysInMonthType;
+    }
+
+    public EnumOptionData getDaysInYearType() {
+        return this.daysInYearType;
+    }
+
+    public boolean isInterestRecalculationEnabled() {
+        return this.isInterestRecalculationEnabled;
+    }
+
+    public LoanProductInterestRecalculationData getInterestRecalculationData() {
+        return this.interestRecalculationData;
+    }
+
+    public Collection<ChargeData> overdueFeeCharges() {
+        Collection<ChargeData> overdueFeeCharges = new ArrayList<>();
+        Collection<ChargeData> charges = charges();
+        for (ChargeData chargeData : charges) {
+            if (chargeData.isOverdueInstallmentCharge()) {
+                overdueFeeCharges.add(chargeData);
+            }
+        }
+        return overdueFeeCharges;
+    }
+
+    public LoanInterestRecalculationData toLoanInterestRecalculationData() {
+        final Long id = null;
+        final Long loanId = null;
+        final CalendarData calendarData = null;
+        final CalendarData compoundingCalendarData = null;
+        return new LoanInterestRecalculationData(id, loanId, getInterestRecalculationCompoundingType(), getRescheduleStrategyType(),
+                calendarData, getRecalculationRestFrequencyType(), getRecalculationRestFrequencyInterval(),
+                getRecalculationRestFrequencyDate(), compoundingCalendarData, getRecalculationCompoundingFrequencyType(),
+                getRecalculationCompoundingFrequencyInterval(), getRecalculationCompoundingFrequencyDate());
+    }
+
+    private EnumOptionData getRescheduleStrategyType() {
+        if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRescheduleStrategyType(); }
+        return null;
+    }
+
+    private EnumOptionData getInterestRecalculationCompoundingType() {
+        if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getInterestRecalculationCompoundingType(); }
+        return null;
+    }
+
+    private LocalDate getRecalculationRestFrequencyDate() {
+        if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyDate(); }
+        return null;
+    }
+
+    private EnumOptionData getRecalculationRestFrequencyType() {
+        if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyType(); }
+        return null;
+    }
+
+    private Integer getRecalculationRestFrequencyInterval() {
+        if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyInterval(); }
+        return null;
+    }
+
+    private LocalDate getRecalculationCompoundingFrequencyDate() {
+        if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyDate(); }
+        return null;
+    }
+
+    private EnumOptionData getRecalculationCompoundingFrequencyType() {
+        if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyType(); }
+        return null;
+    }
+
+    private Integer getRecalculationCompoundingFrequencyInterval() {
+        if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyInterval(); }
+        return null;
+    }
+
+    public boolean canDefineInstallmentAmount() {
+        return this.canDefineInstallmentAmount;
+    }
+
+    public LoanProductConfigurableAttributes getloanProductConfigurableAttributes() {
+        return this.allowAttributeOverrides;
+    }
+
+    public void setloanProductConfigurableAttributes(LoanProductConfigurableAttributes loanProductConfigurableAttributes) {
+        this.allowAttributeOverrides = loanProductConfigurableAttributes;
+    }
+
+    public boolean isLinkedToFloatingInterestRates() {
+        return this.isLinkedToFloatingInterestRates;
+    }
+
+    public BigDecimal getMinDifferentialLendingRate() {
+        return this.minDifferentialLendingRate;
+    }
+
+    public BigDecimal getDefaultDifferentialLendingRate() {
+        return this.defaultDifferentialLendingRate;
+    }
+
+    public BigDecimal getMaxDifferentialLendingRate() {
+        return this.maxDifferentialLendingRate;
+    }
+
+    public boolean isFloatingInterestRateCalculationAllowed() {
+        return this.isFloatingInterestRateCalculationAllowed;
+    }
+
+    public boolean isVariableInstallmentsAllowed() {
+        return this.allowVariableInstallments;
+    }
+
+    public Integer getMinimumGapBetweenInstallments() {
+        return this.minimumGap;
+    }
+
+    public Integer getMaximumGapBetweenInstallments() {
+        return this.maximumGap;
+    }
+
+    
+    public Boolean getAllowPartialPeriodInterestCalcualtion() {
+        return this.allowPartialPeriodInterestCalcualtion;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductGuaranteeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductGuaranteeData.java
new file mode 100755
index 0000000..c71fb90
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductGuaranteeData.java
@@ -0,0 +1,54 @@
+/**
+ * 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.loanproduct.data;
+
+import java.math.BigDecimal;
+
+public class LoanProductGuaranteeData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final Long productId;
+    @SuppressWarnings("unused")
+    private final BigDecimal mandatoryGuarantee;
+    @SuppressWarnings("unused")
+    private final BigDecimal minimumGuaranteeFromOwnFunds;
+    @SuppressWarnings("unused")
+    private final BigDecimal minimumGuaranteeFromGuarantor;
+
+    public static LoanProductGuaranteeData instance(final Long id, final Long productId, final BigDecimal mandatoryGuarantee,
+            final BigDecimal minimumGuaranteeFromOwnFunds, final BigDecimal minimumGuaranteeFromGuarantor) {
+        return new LoanProductGuaranteeData(id, productId, mandatoryGuarantee, minimumGuaranteeFromOwnFunds, minimumGuaranteeFromGuarantor);
+    }
+
+    public static LoanProductGuaranteeData sensibleDefaultsForNewLoanProductCreation() {
+        return new LoanProductGuaranteeData(null, null, null, null, null);
+    }
+
+    private LoanProductGuaranteeData(final Long id, final Long productId, final BigDecimal mandatoryGuarantee,
+            final BigDecimal minimumGuaranteeFromOwnFunds, final BigDecimal minimumGuaranteeFromGuarantor) {
+        this.id = id;
+        this.productId = productId;
+        this.mandatoryGuarantee = mandatoryGuarantee;
+        this.minimumGuaranteeFromGuarantor = minimumGuaranteeFromGuarantor;
+        this.minimumGuaranteeFromOwnFunds = minimumGuaranteeFromOwnFunds;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
new file mode 100644
index 0000000..42b3683
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
@@ -0,0 +1,121 @@
+/**
+ * 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.loanproduct.data;
+
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationCompoundingType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.preCloseInterestCalculationStrategy;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.rescheduleStrategyType;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
+import org.joda.time.LocalDate;
+
+public class LoanProductInterestRecalculationData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final Long productId;
+    private final EnumOptionData interestRecalculationCompoundingType;
+    private final EnumOptionData rescheduleStrategyType;
+    private final EnumOptionData recalculationRestFrequencyType;
+    private final Integer recalculationRestFrequencyInterval;
+    private final LocalDate recalculationRestFrequencyDate;
+    private final EnumOptionData recalculationCompoundingFrequencyType;
+    private final Integer recalculationCompoundingFrequencyInterval;
+    private final LocalDate recalculationCompoundingFrequencyDate;
+    @SuppressWarnings("unused")
+    private final boolean isArrearsBasedOnOriginalSchedule;
+    @SuppressWarnings("unused")
+    private final EnumOptionData preClosureInterestCalculationStrategy;
+
+    public LoanProductInterestRecalculationData(final Long id, final Long productId,
+            final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType,
+            final EnumOptionData recalculationRestFrequencyType, final Integer recalculationRestFrequencyInterval,
+            final LocalDate recalculationRestFrequencyDate, final EnumOptionData recalculationCompoundingFrequencyType,
+            final Integer recalculationCompoundingFrequencyInterval, final LocalDate recalculationCompoundingFrequencyDate,
+            final boolean isArrearsBasedOnOriginalSchedule, final EnumOptionData preCloseInterestCalculationStrategy) {
+        this.id = id;
+        this.productId = productId;
+        this.interestRecalculationCompoundingType = interestRecalculationCompoundingType;
+        this.rescheduleStrategyType = rescheduleStrategyType;
+        this.recalculationRestFrequencyType = recalculationRestFrequencyType;
+        this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval;
+        this.recalculationRestFrequencyDate = recalculationRestFrequencyDate;
+        this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType;
+        this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval;
+        this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate;
+        this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule;
+        this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy;
+    }
+
+    public static LoanProductInterestRecalculationData sensibleDefaultsForNewLoanProductCreation() {
+        final Long id = null;
+        final Long productId = null;
+        final EnumOptionData interestRecalculationCompoundingType = interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.NONE);
+        final EnumOptionData rescheduleStrategyType = rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT);
+        final EnumOptionData recalculationRestFrequencyType = null;
+        final Integer recalculationRestFrequencyInterval = null;
+        final LocalDate recalculationRestFrequencyDate = null;
+        final EnumOptionData recalculationCompoundingFrequencyType = null;
+        final Integer recalculationCompoundingFrequencyInterval = null;
+        final LocalDate recalculationCompoundingFrequencyDate = null;
+        final boolean isArrearsBasedOnOriginalSchedule = false;
+        final EnumOptionData preCloseInterestCalculationStrategy = preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE);
+        return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType,
+                recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate,
+                recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate,
+                isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy);
+    }
+
+    public EnumOptionData getInterestRecalculationCompoundingType() {
+        return this.interestRecalculationCompoundingType;
+    }
+
+    public EnumOptionData getRescheduleStrategyType() {
+        return this.rescheduleStrategyType;
+    }
+
+    public LocalDate getRecalculationRestFrequencyDate() {
+        return this.recalculationRestFrequencyDate;
+    }
+
+    public EnumOptionData getRecalculationRestFrequencyType() {
+        return this.recalculationRestFrequencyType;
+    }
+
+    public Integer getRecalculationRestFrequencyInterval() {
+        return this.recalculationRestFrequencyInterval;
+    }
+
+    public EnumOptionData getRecalculationCompoundingFrequencyType() {
+        return this.recalculationCompoundingFrequencyType;
+    }
+
+    public Integer getRecalculationCompoundingFrequencyInterval() {
+        return this.recalculationCompoundingFrequencyInterval;
+    }
+
+    public LocalDate getRecalculationCompoundingFrequencyDate() {
+        return this.recalculationCompoundingFrequencyDate;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/TransactionProcessingStrategyData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/TransactionProcessingStrategyData.java
new file mode 100644
index 0000000..786fedc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/TransactionProcessingStrategyData.java
@@ -0,0 +1,44 @@
+/**
+ * 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.loanproduct.data;
+
+/**
+ * Immutable data object representing a transaction strategy option for a loan.
+ */
+public final class TransactionProcessingStrategyData {
+
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String code;
+    private final String name;
+
+    public TransactionProcessingStrategyData(final Long id, final String code, final String name) {
+        this.id = id;
+        this.code = code;
+        this.name = name;
+    }
+
+    public Long id() {
+        return this.id;
+    }
+
+    public String name() {
+        return this.name;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/AmortizationMethod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/AmortizationMethod.java
new file mode 100644
index 0000000..2de139c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/AmortizationMethod.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.loanproduct.domain;
+
+public enum AmortizationMethod {
+    EQUAL_PRINCIPAL(0, "amortizationType.equal.principal"), //
+    EQUAL_INSTALLMENTS(1, "amortizationType.equal.installments"), //
+    INVALID(2, "amortizationType.invalid");
+
+    private final Integer value;
+    private final String code;
+
+    private AmortizationMethod(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static AmortizationMethod fromInt(final Integer selectedMethod) {
+
+        if (selectedMethod == null) { return null; }
+
+        AmortizationMethod repaymentMethod = null;
+        switch (selectedMethod) {
+            case 0:
+                repaymentMethod = AmortizationMethod.EQUAL_PRINCIPAL;
+            break;
+            case 1:
+                repaymentMethod = AmortizationMethod.EQUAL_INSTALLMENTS;
+            break;
+            default:
+                repaymentMethod = AmortizationMethod.INVALID;
+            break;
+        }
+        return repaymentMethod;
+    }
+
+    public boolean isEqualInstallment() {
+        return this.value.equals(AmortizationMethod.EQUAL_INSTALLMENTS.getValue());
+    }
+    
+    public boolean isEqualPrincipal() {
+        return this.value.equals(AmortizationMethod.EQUAL_PRINCIPAL.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestCalculationPeriodMethod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestCalculationPeriodMethod.java
new file mode 100644
index 0000000..60c15bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestCalculationPeriodMethod.java
@@ -0,0 +1,63 @@
+/**
+ * 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.loanproduct.domain;
+
+public enum InterestCalculationPeriodMethod {
+    DAILY(0, "interestCalculationPeriodType.daily"), //
+    SAME_AS_REPAYMENT_PERIOD(1, "interestCalculationPeriodType.same.as.repayment.period"), //
+    INVALID(2, "interestCalculationPeriodType.invalid");
+
+    private final Integer value;
+    private final String code;
+
+    private InterestCalculationPeriodMethod(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static InterestCalculationPeriodMethod fromInt(final Integer selectedMethod) {
+
+        InterestCalculationPeriodMethod repaymentMethod = null;
+        switch (selectedMethod) {
+            case 0:
+                repaymentMethod = InterestCalculationPeriodMethod.DAILY;
+            break;
+            case 1:
+                repaymentMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
+            break;
+            default:
+                repaymentMethod = InterestCalculationPeriodMethod.INVALID;
+            break;
+        }
+        return repaymentMethod;
+    }
+
+    public boolean isDaily() {
+        return this.value.equals(InterestCalculationPeriodMethod.DAILY.getValue());
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestMethod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestMethod.java
new file mode 100644
index 0000000..118ed6d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestMethod.java
@@ -0,0 +1,61 @@
+/**
+ * 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.loanproduct.domain;
+
+
+public enum InterestMethod {
+    DECLINING_BALANCE(0, "interestType.declining.balance"), FLAT(1, "interestType.flat"), INVALID(2, "interestType.invalid");
+
+    private final Integer value;
+    private final String code;
+
+    private InterestMethod(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static InterestMethod fromInt(final Integer selectedMethod) {
+
+        InterestMethod repaymentMethod = null;
+        switch (selectedMethod) {
+            case 0:
+                repaymentMethod = InterestMethod.DECLINING_BALANCE;
+            break;
+            case 1:
+                repaymentMethod = InterestMethod.FLAT;
+            break;
+            default:
+                repaymentMethod = InterestMethod.INVALID;
+            break;
+        }
+        return repaymentMethod;
+    }
+
+    public boolean isDecliningBalnce() {
+        return this.value.equals(InterestMethod.DECLINING_BALANCE.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java
new file mode 100644
index 0000000..2cea623
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java
@@ -0,0 +1,82 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/***
+ * <ul>
+ * People typically use either of the following settings when defining interest
+ * recalculation method:
+ * <li>NONE</li>
+ * <li>INTEREST</li>
+ * <li>FEE</li>
+ * <li>INTEREST_AND_FEE</li>
+ * </ul>
+ */
+public enum InterestRecalculationCompoundingMethod {
+
+    NONE(0, "interestRecalculationCompoundingMethod.none"), //
+    INTEREST(1, "interestRecalculationCompoundingMethod.interest"), //
+    FEE(2, "interestRecalculationCompoundingMethod.fee"), //
+    INTEREST_AND_FEE(3, "interestRecalculationCompoundingMethod.interest.and.fee");
+
+    private final Integer value;
+    private final String code;
+
+    private static final Map<Integer, InterestRecalculationCompoundingMethod> intToEnumMap = new HashMap<>();
+    static {
+        for (final InterestRecalculationCompoundingMethod type : InterestRecalculationCompoundingMethod.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static InterestRecalculationCompoundingMethod fromInt(final Integer ruleTypeValue) {
+        final InterestRecalculationCompoundingMethod type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private InterestRecalculationCompoundingMethod(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isFeeCompoundingEnabled() {
+        return this.getValue().equals(InterestRecalculationCompoundingMethod.FEE.getValue())
+                || this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue());
+    }
+
+    public boolean isCompoundingEnabled() {
+        return !this.getValue().equals(InterestRecalculationCompoundingMethod.NONE.getValue());
+    }
+
+    public boolean isInterestCompoundingEnabled() {
+        return this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST.getValue())
+                || this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestRecalculationPeriodMethod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestRecalculationPeriodMethod.java
new file mode 100644
index 0000000..48090ef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/InterestRecalculationPeriodMethod.java
@@ -0,0 +1,71 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/***
+ * <ul>
+ * People typically use either of the following settings when defining interest
+ * recalculation method:
+ * <li>SAME_AS_REPAYMENT_PERIOD</li>
+ * <li>DAILY</li>
+ * <li>WEEKLY</li>
+ * <li>FORTNIGHTLY</li>
+ * <li>MONTHLY</li>
+ * </ul>
+ */
+
+public enum InterestRecalculationPeriodMethod {
+    INVALID(0, "interestRecalculationPeriodMethod.invalid"), //
+    DAILY(1, "interestRecalculationPeriodMethod.daily"), //
+    WEEKLY(2, "interestRecalculationPeriodMethod.weekly"), //
+    FORTNIGHTLY(3, "interestRecalculationPeriodMethod.fortnightly"), //
+    MONTHLY(4, "interestRecalculationPeriodMethod.monthly"), //
+    SAME_AS_REPAYMENT_PERIOD(5, "interestRecalculationPeriodMethod.same.as.repayment.period");
+
+    private final Integer value;
+    private final String code;
+    private static final Map<Integer, InterestRecalculationPeriodMethod> intToEnumMap = new HashMap<>();
+
+    static {
+        for (final InterestRecalculationPeriodMethod type : InterestRecalculationPeriodMethod.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static InterestRecalculationPeriodMethod fromInt(final Integer ruleTypeValue) {
+        final InterestRecalculationPeriodMethod type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private InterestRecalculationPeriodMethod(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LendingStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LendingStrategy.java
new file mode 100644
index 0000000..0af8690
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LendingStrategy.java
@@ -0,0 +1,101 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * INDIVIDUAL_LOAN: Individual loans are applicable only to clients. GROUP_LOAN:
+ * * Group loans are applicable only to groups. And tracked only at group level.
+ * JOINT_LIABILITY_LOAN: Joint liability loans are applicable only to clients
+ * within a group. LINKED_LOAN: Loan is given to group, then later loan amount
+ * is split into individual loans. Loan is tracked at both individual and group
+ * level
+ * 
+ */
+public enum LendingStrategy {
+
+    INDIVIDUAL_LOAN(100, "lendingStrategy.individaulLoan", "individaulLoan"), //
+    GROUP_LOAN(200, "lendingStrategy.groupLoan", "groupLoan"), //
+    JOINT_LIABILITY_LOAN(300, "lendingStrategy.joinLiabilityLoan", "joinLiabilityLoan"), //
+    LINKED_LOAN(400, "lendingStrategy.linkedLoan", "linkedLoan"), //
+    INVALID(900, "lendingStrategy.invalid", "invalid");
+
+    private Integer id;
+    private String code;
+    private String value;
+
+    LendingStrategy(final Integer id, final String code, final String value) {
+        this.id = id;
+        this.code = code;
+        this.value = value;
+    }
+
+    private static final Map<Integer, LendingStrategy> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final LendingStrategy type : LendingStrategy.values()) {
+            if (i == 0) {
+                minValue = type.id;
+            }
+            intToEnumMap.put(type.id, type);
+            if (minValue >= type.id) {
+                minValue = type.id;
+            }
+            if (maxValue < type.id) {
+                maxValue = type.id;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static LendingStrategy fromInt(final int i) {
+        final LendingStrategy type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    public Integer getId() {
+        return this.id;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanPreClosureInterestCalculationStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanPreClosureInterestCalculationStrategy.java
new file mode 100644
index 0000000..e8d428e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanPreClosureInterestCalculationStrategy.java
@@ -0,0 +1,89 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum LoanPreClosureInterestCalculationStrategy {
+
+    NONE(0, "loanPreClosureInterestCalculationStrategy.none"), //
+    TILL_PRE_CLOSURE_DATE(1, "loanPreClosureInterestCalculationStrategy.tillPreClosureDate"), //
+    TILL_REST_FREQUENCY_DATE(2, "loanPreClosureInterestCalculationStrategy.tillRestFrequencyDate");
+
+    // REPAYMENT_PERIOD_DATE(3,
+    // "loanPreClosureInterestCalculationStrategy.repaymentPeriodDate")
+
+    private Integer value;
+    private String code;
+
+    private static final Map<Integer, LoanPreClosureInterestCalculationStrategy> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final LoanPreClosureInterestCalculationStrategy type : LoanPreClosureInterestCalculationStrategy.values()) {
+            if (i == 0) {
+                minValue = type.value;
+            }
+            intToEnumMap.put(type.value, type);
+            if (minValue >= type.value) {
+                minValue = type.value;
+            }
+            if (maxValue < type.value) {
+                maxValue = type.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static LoanPreClosureInterestCalculationStrategy fromInt(final Integer ruleTypeValue) {
+        final LoanPreClosureInterestCalculationStrategy type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private LoanPreClosureInterestCalculationStrategy(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    public boolean calculateTillRestFrequencyEnabled() {
+        return this.getValue().equals(LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE.getValue());
+    }
+
+    public boolean calculateTillPreClosureDateEnabled() {
+        return this.getValue().equals(LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE.getValue());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
new file mode 100644
index 0000000..9116117
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
@@ -0,0 +1,1333 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
+import org.apache.fineract.portfolio.fund.domain.Fund;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+/**
+ * Loan products allow for categorisation of an organisations loans into
+ * something meaningful to them.
+ * 
+ * They provide a means of simplifying creation/maintenance of loans. They can
+ * also allow for product comparison to take place when reporting.
+ * 
+ * They allow for constraints to be added at product level.
+ */
+@Entity
+@Table(name = "m_product_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name"),
+        @UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE"),
+        @UniqueConstraint(columnNames = { "short_name" }, name = "unq_short_name") })
+public class LoanProduct extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "fund_id", nullable = true)
+    private Fund fund;
+
+    @ManyToOne
+    @JoinColumn(name = "loan_transaction_strategy_id", nullable = true)
+    private LoanTransactionProcessingStrategy transactionProcessingStrategy;
+
+    @Column(name = "name", nullable = false, unique = true)
+    private String name;
+
+    @Column(name = "short_name", nullable = false, unique = true)
+    private String shortName;
+
+    @Column(name = "description")
+    private String description;
+
+    @ManyToMany(fetch = FetchType.EAGER)
+    @JoinTable(name = "m_product_loan_charge", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "charge_id"))
+    private List<Charge> charges;
+
+    @Embedded
+    private final LoanProductRelatedDetail loanProductRelatedDetail;
+
+    @Embedded
+    private LoanProductMinMaxConstraints loanProductMinMaxConstraints;
+
+    @Column(name = "accounting_type", nullable = false)
+    private Integer accountingRule;
+
+    @Column(name = "include_in_borrower_cycle")
+    private boolean includeInBorrowerCycle;
+
+    @Column(name = "use_borrower_cycle")
+    private boolean useBorrowerCycle;
+
+    @Embedded
+    private LoanProductTrancheDetails loanProducTrancheDetails;
+
+    @Column(name = "start_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date startDate;
+
+    @Column(name = "close_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date closeDate;
+
+    @Column(name = "external_id", length = 100, nullable = true, unique = true)
+    private String externalId;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "loanProduct", orphanRemoval = true)
+    private Set<LoanProductBorrowerCycleVariations> borrowerCycleVariations = new HashSet<>();
+
+    @Column(name = "overdue_days_for_npa", nullable = true)
+    private Integer overdueDaysForNPA;
+
+    @Column(name = "min_days_between_disbursal_and_first_repayment", nullable = true)
+    private Integer minimumDaysBetweenDisbursalAndFirstRepayment;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
+    private LoanProductInterestRecalculationDetails productInterestRecalculationDetails;
+
+    @Column(name = "hold_guarantee_funds")
+    private boolean holdGuaranteeFunds;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
+    private LoanProductGuaranteeDetails loanProductGuaranteeDetails;
+
+    @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
+    private LoanProductConfigurableAttributes loanConfigurableAttributes;
+
+    @Column(name = "principal_threshold_for_last_installment", scale = 2, precision = 5, nullable = false)
+    private BigDecimal principalThresholdForLastInstallment;
+
+    @Column(name = "account_moves_out_of_npa_only_on_arrears_completion")
+    private boolean accountMovesOutOfNPAOnlyOnArrearsCompletion;
+
+    @Column(name = "can_define_fixed_emi_amount")
+    private boolean canDefineInstallmentAmount;
+
+    @Column(name = "instalment_amount_in_multiples_of", nullable = true)
+    private Integer installmentAmountInMultiplesOf;
+
+    @Column(name = "is_linked_to_floating_interest_rates", nullable = false)
+    private boolean isLinkedToFloatingInterestRate;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
+    private LoanProductFloatingRates floatingRates;
+
+    @Column(name = "allow_variabe_installments", nullable = false)
+    private boolean allowVariabeInstallments;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
+    private LoanProductVariableInstallmentConfig variableInstallmentConfig;
+
+    public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
+            final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) {
+
+        final String name = command.stringValueOfParameterNamed("name");
+        final String shortName = command.stringValueOfParameterNamed(LoanProductConstants.shortName);
+        final String description = command.stringValueOfParameterNamed("description");
+        final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
+        final Integer digitsAfterDecimal = command.integerValueOfParameterNamed("digitsAfterDecimal");
+        final Integer inMultiplesOf = command.integerValueOfParameterNamed("inMultiplesOf");
+
+        final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
+        final BigDecimal principal = command.bigDecimalValueOfParameterNamed("principal");
+        final BigDecimal minPrincipal = command.bigDecimalValueOfParameterNamed("minPrincipal");
+        final BigDecimal maxPrincipal = command.bigDecimalValueOfParameterNamed("maxPrincipal");
+
+        final InterestMethod interestMethod = InterestMethod.fromInt(command.integerValueOfParameterNamed("interestType"));
+        final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command
+                .integerValueOfParameterNamed("interestCalculationPeriodType"));
+        final boolean allowPartialPeriodInterestCalcualtion = command
+                .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName);
+        final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.integerValueOfParameterNamed("amortizationType"));
+        final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command
+                .integerValueOfParameterNamed("repaymentFrequencyType"));
+        PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.INVALID;
+        BigDecimal interestRatePerPeriod = null;
+        BigDecimal minInterestRatePerPeriod = null;
+        BigDecimal maxInterestRatePerPeriod = null;
+        BigDecimal annualInterestRate = null;
+        BigDecimal interestRateDifferential = null;
+        BigDecimal minDifferentialLendingRate = null;
+        BigDecimal maxDifferentialLendingRate = null;
+        BigDecimal defaultDifferentialLendingRate = null;
+        Boolean isFloatingInterestRateCalculationAllowed = null;
+
+        Integer minimumGapBetweenInstallments = null;
+        Integer maximumGapBetweenInstallments = null;
+
+        final Boolean isLinkedToFloatingInterestRates = command.booleanObjectValueOfParameterNamed("isLinkedToFloatingInterestRates");
+        if (isLinkedToFloatingInterestRates != null && isLinkedToFloatingInterestRates) {
+            interestRateDifferential = command.bigDecimalValueOfParameterNamed("interestRateDifferential");
+            minDifferentialLendingRate = command.bigDecimalValueOfParameterNamed("minDifferentialLendingRate");
+            maxDifferentialLendingRate = command.bigDecimalValueOfParameterNamed("maxDifferentialLendingRate");
+            defaultDifferentialLendingRate = command.bigDecimalValueOfParameterNamed("defaultDifferentialLendingRate");
+            isFloatingInterestRateCalculationAllowed = command
+                    .booleanObjectValueOfParameterNamed("isFloatingInterestRateCalculationAllowed");
+        } else {
+            interestFrequencyType = PeriodFrequencyType.fromInt(command.integerValueOfParameterNamed("interestRateFrequencyType"));
+            interestRatePerPeriod = command.bigDecimalValueOfParameterNamed("interestRatePerPeriod");
+            minInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("minInterestRatePerPeriod");
+            maxInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("maxInterestRatePerPeriod");
+            annualInterestRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod);
+        }
+
+        final Boolean isVariableInstallmentsAllowed = command
+                .booleanObjectValueOfParameterNamed(LoanProductConstants.allowVariableInstallmentsParamName);
+        if (isVariableInstallmentsAllowed != null && isVariableInstallmentsAllowed) {
+            minimumGapBetweenInstallments = command.integerValueOfParameterNamed(LoanProductConstants.minimumGapBetweenInstallments);
+            maximumGapBetweenInstallments = command.integerValueOfParameterNamed(LoanProductConstants.maximumGapBetweenInstallments);
+        }
+
+        final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery");
+        final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments");
+        final Integer minNumberOfRepayments = command.integerValueOfParameterNamed("minNumberOfRepayments");
+        final Integer maxNumberOfRepayments = command.integerValueOfParameterNamed("maxNumberOfRepayments");
+        final BigDecimal inArrearsTolerance = command.bigDecimalValueOfParameterNamed("inArrearsTolerance");
+
+        // grace details
+        final Integer graceOnPrincipalPayment = command.integerValueOfParameterNamed("graceOnPrincipalPayment");
+        final Integer graceOnInterestPayment = command.integerValueOfParameterNamed("graceOnInterestPayment");
+        final Integer graceOnInterestCharged = command.integerValueOfParameterNamed("graceOnInterestCharged");
+        final Integer minimumDaysBetweenDisbursalAndFirstRepayment = command
+                .integerValueOfParameterNamed(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment);
+
+        final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
+        final boolean includeInBorrowerCycle = command.booleanPrimitiveValueOfParameterNamed("includeInBorrowerCycle");
+
+        final LocalDate startDate = command.localDateValueOfParameterNamed("startDate");
+        final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate");
+        final String externalId = command.stringValueOfParameterNamedAllowingNull("externalId");
+
+        final boolean useBorrowerCycle = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.useBorrowerCycleParameterName);
+        final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations = new HashSet<>();
+
+        if (useBorrowerCycle) {
+            populateBorrowerCyclevariations(command, loanProductBorrowerCycleVariations);
+        }
+
+        final boolean multiDisburseLoan = command
+                .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.multiDisburseLoanParameterName);
+        Integer maxTrancheCount = null;
+        BigDecimal outstandingLoanBalance = null;
+        if (multiDisburseLoan) {
+            outstandingLoanBalance = command.bigDecimalValueOfParameterNamed(LoanProductConstants.outstandingLoanBalanceParameterName);
+            maxTrancheCount = command.integerValueOfParameterNamed(LoanProductConstants.maxTrancheCountParameterName);
+        }
+
+        final Integer graceOnArrearsAgeing = command.integerValueOfParameterNamed(LoanProductConstants.graceOnArrearsAgeingParameterName);
+
+        final Integer overdueDaysForNPA = command.integerValueOfParameterNamed(LoanProductConstants.overdueDaysForNPAParameterName);
+
+        // Interest recalculation settings
+        final boolean isInterestRecalculationEnabled = command
+                .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isInterestRecalculationEnabledParameterName);
+        final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(command
+                .integerValueOfParameterNamed(LoanProductConstants.daysInMonthTypeParameterName));
+
+        final DaysInYearType daysInYearType = DaysInYearType.fromInt(command
+                .integerValueOfParameterNamed(LoanProductConstants.daysInYearTypeParameterName));
+
+        LoanProductInterestRecalculationDetails interestRecalculationSettings = null;
+
+        if (isInterestRecalculationEnabled) {
+            interestRecalculationSettings = LoanProductInterestRecalculationDetails.createFrom(command);
+        }
+
+        final boolean holdGuarantorFunds = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.holdGuaranteeFundsParamName);
+        LoanProductGuaranteeDetails loanProductGuaranteeDetails = null;
+        if (holdGuarantorFunds) {
+            loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command);
+        }
+
+        LoanProductConfigurableAttributes loanConfigurableAttributes = null;
+        if (command.parameterExists(LoanProductConstants.allowAttributeOverridesParamName)) {
+            loanConfigurableAttributes = LoanProductConfigurableAttributes.createFrom(command);
+        } else {
+            loanConfigurableAttributes = LoanProductConfigurableAttributes.populateDefaultsForConfigurableAttributes();
+        }
+
+        BigDecimal principalThresholdForLastInstallment = command
+                .bigDecimalValueOfParameterNamed(LoanProductConstants.principalThresholdForLastInstallmentParamName);
+
+        if (principalThresholdForLastInstallment == null) {
+            principalThresholdForLastInstallment = multiDisburseLoan ? LoanProductConstants.DEFAULT_PRINCIPAL_THRESHOLD_FOR_MULTI_DISBURSE_LOAN
+                    : LoanProductConstants.DEFAULT_PRINCIPAL_THRESHOLD_FOR_SINGLE_DISBURSE_LOAN;
+        }
+        final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = command
+                .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName);
+        final boolean canDefineEmiAmount = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.canDefineEmiAmountParamName);
+        final Integer installmentAmountInMultiplesOf = command
+                .integerValueOfParameterNamed(LoanProductConstants.installmentAmountInMultiplesOfParamName);
+
+        return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
+                maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
+                annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery,
+                repaymentFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment,
+                graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType,
+                includeInBorrowerCycle, startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations,
+                multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
+                daysInYearType, isInterestRecalculationEnabled, interestRecalculationSettings,
+                minimumDaysBetweenDisbursalAndFirstRepayment, holdGuarantorFunds, loanProductGuaranteeDetails,
+                principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineEmiAmount,
+                installmentAmountInMultiplesOf, loanConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRate,
+                interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate,
+                isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGapBetweenInstallments,
+                maximumGapBetweenInstallments);
+
+    }
+
+    public void updateLoanProductInRelatedClasses() {
+        if (this.isInterestRecalculationEnabled()) {
+            this.productInterestRecalculationDetails.updateProduct(this);
+        }
+        if (this.holdGuaranteeFunds) {
+            this.loanProductGuaranteeDetails.updateProduct(this);
+        }
+    }
+
+    /**
+     * @param command
+     * @param loanProductBorrowerCycleVariations
+     */
+    private static void populateBorrowerCyclevariations(final JsonCommand command,
+            final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
+        assemblePrincipalVariations(command, loanProductBorrowerCycleVariations);
+
+        assembleRepaymentVariations(command, loanProductBorrowerCycleVariations);
+
+        assembleInterestRateVariations(command, loanProductBorrowerCycleVariations);
+    }
+
+    /**
+     * @param command
+     * @param loanProductBorrowerCycleVariations
+     */
+    private static void assembleInterestRateVariations(final JsonCommand command,
+            final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
+        assembleVaritions(command, loanProductBorrowerCycleVariations, LoanProductParamType.INTERESTRATE.getValue(),
+                LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName);
+
+    }
+
+    /**
+     * @param command
+     * @param loanProductBorrowerCycleVariations
+     */
+    private static void assembleRepaymentVariations(final JsonCommand command,
+            final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
+        assembleVaritions(command, loanProductBorrowerCycleVariations, LoanProductParamType.REPAYMENT.getValue(),
+                LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName);
+
+    }
+
+    /**
+     * @param command
+     * @param loanProductBorrowerCycleVariations
+     */
+    private static void assemblePrincipalVariations(final JsonCommand command,
+            final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
+        assembleVaritions(command, loanProductBorrowerCycleVariations, LoanProductParamType.PRINCIPAL.getValue(),
+                LoanProductConstants.principalVariationsForBorrowerCycleParameterName);
+    }
+
+    private static void assembleVaritions(final JsonCommand command,
+            final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations, Integer paramType,
+            String variationParameterName) {
+        if (command.parameterExists(variationParameterName)) {
+            final JsonArray variationArray = command.arrayOfParameterNamed(variationParameterName);
+            if (variationArray != null && variationArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
+                    BigDecimal defaultValue = null;
+                    BigDecimal minValue = null;
+                    BigDecimal maxValue = null;
+                    Integer cycleNumber = null;
+                    Integer valueUsageCondition = null;
+                    if (jsonObject.has(LoanProductConstants.defaultValueParameterName)
+                            && jsonObject.get(LoanProductConstants.defaultValueParameterName).isJsonPrimitive()) {
+                        defaultValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.defaultValueParameterName).getAsBigDecimal();
+                    }
+                    if (jsonObject.has(LoanProductConstants.minValueParameterName)
+                            && jsonObject.get(LoanProductConstants.minValueParameterName).isJsonPrimitive()
+                            && StringUtils.isNotBlank((jsonObject.get(LoanProductConstants.minValueParameterName).getAsString()))) {
+                        minValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.minValueParameterName).getAsBigDecimal();
+                    }
+                    if (jsonObject.has(LoanProductConstants.maxValueParameterName)
+                            && jsonObject.get(LoanProductConstants.maxValueParameterName).isJsonPrimitive()
+                            && StringUtils.isNotBlank((jsonObject.get(LoanProductConstants.maxValueParameterName).getAsString()))) {
+                        maxValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.maxValueParameterName).getAsBigDecimal();
+                    }
+                    if (jsonObject.has(LoanProductConstants.borrowerCycleNumberParamName)
+                            && jsonObject.get(LoanProductConstants.borrowerCycleNumberParamName).isJsonPrimitive()) {
+                        cycleNumber = jsonObject.getAsJsonPrimitive(LoanProductConstants.borrowerCycleNumberParamName).getAsInt();
+                    }
+                    if (jsonObject.has(LoanProductConstants.valueConditionTypeParamName)
+                            && jsonObject.get(LoanProductConstants.valueConditionTypeParamName).isJsonPrimitive()) {
+                        valueUsageCondition = jsonObject.getAsJsonPrimitive(LoanProductConstants.valueConditionTypeParamName).getAsInt();
+                    }
+                    LoanProductBorrowerCycleVariations borrowerCycleVariations = new LoanProductBorrowerCycleVariations(cycleNumber,
+                            paramType, valueUsageCondition, minValue, maxValue, defaultValue);
+                    loanProductBorrowerCycleVariations.add(borrowerCycleVariations);
+                    i++;
+                } while (i < variationArray.size());
+            }
+        }
+    }
+
+    private Map<String, Object> updateBorrowerCycleVariations(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
+        List<Long> variationIds = fetchAllVariationIds();
+        updateBorrowerCycleVaritions(command, LoanProductParamType.PRINCIPAL.getValue(),
+                LoanProductConstants.principalVariationsForBorrowerCycleParameterName, actualChanges, variationIds);
+        updateBorrowerCycleVaritions(command, LoanProductParamType.INTERESTRATE.getValue(),
+                LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName, actualChanges, variationIds);
+        updateBorrowerCycleVaritions(command, LoanProductParamType.REPAYMENT.getValue(),
+                LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName, actualChanges, variationIds);
+        for (Long id : variationIds) {
+            this.borrowerCycleVariations.remove(fetchLoanProductBorrowerCycleVariationById(id));
+        }
+        return actualChanges;
+    }
+
+    private List<Long> fetchAllVariationIds() {
+        List<Long> list = new ArrayList<>();
+        for (LoanProductBorrowerCycleVariations cycleVariation : this.borrowerCycleVariations) {
+            list.add(cycleVariation.getId());
+        }
+        return list;
+    }
+
+    private void updateBorrowerCycleVaritions(final JsonCommand command, Integer paramType, String variationParameterName,
+            final Map<String, Object> actualChanges, List<Long> variationIds) {
+        if (command.parameterExists(variationParameterName)) {
+            final JsonArray variationArray = command.arrayOfParameterNamed(variationParameterName);
+            if (variationArray != null && variationArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
+                    BigDecimal defaultValue = null;
+                    BigDecimal minValue = null;
+                    BigDecimal maxValue = null;
+                    Integer cycleNumber = null;
+                    Integer valueUsageCondition = null;
+                    Long id = null;
+                    if (jsonObject.has(LoanProductConstants.defaultValueParameterName)
+                            && jsonObject.get(LoanProductConstants.defaultValueParameterName).isJsonPrimitive()) {
+                        defaultValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.defaultValueParameterName).getAsBigDecimal();
+                    }
+                    if (jsonObject.has(LoanProductConstants.minValueParameterName)
+                            && jsonObject.get(LoanProductConstants.minValueParameterName).isJsonPrimitive()
+                            && StringUtils.isNotBlank((jsonObject.get(LoanProductConstants.minValueParameterName).getAsString()))) {
+                        minValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.minValueParameterName).getAsBigDecimal();
+                    }
+                    if (jsonObject.has(LoanProductConstants.maxValueParameterName)
+                            && jsonObject.get(LoanProductConstants.maxValueParameterName).isJsonPrimitive()
+                            && StringUtils.isNotBlank((jsonObject.get(LoanProductConstants.maxValueParameterName).getAsString()))) {
+                        maxValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.maxValueParameterName).getAsBigDecimal();
+                    }
+                    if (jsonObject.has(LoanProductConstants.borrowerCycleNumberParamName)
+                            && jsonObject.get(LoanProductConstants.borrowerCycleNumberParamName).isJsonPrimitive()) {
+                        cycleNumber = jsonObject.getAsJsonPrimitive(LoanProductConstants.borrowerCycleNumberParamName).getAsInt();
+                    }
+                    if (jsonObject.has(LoanProductConstants.valueConditionTypeParamName)
+                            && jsonObject.get(LoanProductConstants.valueConditionTypeParamName).isJsonPrimitive()) {
+                        valueUsageCondition = jsonObject.getAsJsonPrimitive(LoanProductConstants.valueConditionTypeParamName).getAsInt();
+                    }
+                    if (jsonObject.has(LoanProductConstants.borrowerCycleIdParameterName)
+                            && jsonObject.get(LoanProductConstants.borrowerCycleIdParameterName).isJsonPrimitive()
+                            && StringUtils.isNotBlank((jsonObject.get(LoanProductConstants.borrowerCycleIdParameterName).getAsString()))) {
+                        id = jsonObject.getAsJsonPrimitive(LoanProductConstants.borrowerCycleIdParameterName).getAsLong();
+                    }
+                    LoanProductBorrowerCycleVariations borrowerCycleVariations = new LoanProductBorrowerCycleVariations(cycleNumber,
+                            paramType, valueUsageCondition, minValue, maxValue, defaultValue);
+                    if (id == null) {
+                        borrowerCycleVariations.updateLoanProduct(this);
+                        this.borrowerCycleVariations.add(borrowerCycleVariations);
+                        actualChanges.put("borrowerCycleParamType", paramType);
+                    } else {
+                        variationIds.remove(id);
+                        LoanProductBorrowerCycleVariations existingCycleVariation = fetchLoanProductBorrowerCycleVariationById(id);
+                        if (!existingCycleVariation.equals(borrowerCycleVariations)) {
+                            existingCycleVariation.copy(borrowerCycleVariations);
+                            actualChanges.put("borrowerCycleId", id);
+                        }
+                    }
+                    i++;
+                } while (i < variationArray.size());
+            }
+        }
+    }
+
+    private void clearVariations(LoanProductParamType paramType, boolean clearAll) {
+        if (clearAll) {
+            this.borrowerCycleVariations.clear();
+        } else {
+            Set<LoanProductBorrowerCycleVariations> remove = new HashSet<>();
+            for (LoanProductBorrowerCycleVariations borrowerCycleVariations : this.borrowerCycleVariations) {
+                if (paramType.equals(borrowerCycleVariations.getParamType())) {
+                    remove.add(borrowerCycleVariations);
+                }
+            }
+            this.borrowerCycleVariations.removeAll(remove);
+        }
+    }
+
+    protected LoanProduct() {
+        this.loanProductRelatedDetail = null;
+        this.loanProductMinMaxConstraints = null;
+    }
+
+    public LoanProduct(final Fund fund, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final String name,
+            final String shortName, final String description, final MonetaryCurrency currency, final BigDecimal defaultPrincipal,
+            final BigDecimal defaultMinPrincipal, final BigDecimal defaultMaxPrincipal,
+            final BigDecimal defaultNominalInterestRatePerPeriod, final BigDecimal defaultMinNominalInterestRatePerPeriod,
+            final BigDecimal defaultMaxNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType,
+            final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
+            final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean considerPartialPeriodInterest,
+            final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfInstallments,
+            final Integer defaultMinNumberOfInstallments, final Integer defaultMaxNumberOfInstallments,
+            final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
+            final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List<Charge> charges,
+            final AccountingRuleType accountingRuleType, final boolean includeInBorrowerCycle, final LocalDate startDate,
+            final LocalDate closeDate, final String externalId, final boolean useBorrowerCycle,
+            final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations, final boolean multiDisburseLoan,
+            final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing,
+            final Integer overdueDaysForNPA, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType,
+            final boolean isInterestRecalculationEnabled,
+            final LoanProductInterestRecalculationDetails productInterestRecalculationDetails,
+            final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds,
+            final LoanProductGuaranteeDetails loanProductGuaranteeDetails, final BigDecimal principalThresholdForLastInstallment,
+            final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, final boolean canDefineEmiAmount,
+            final Integer installmentAmountInMultiplesOf, final LoanProductConfigurableAttributes loanProductConfigurableAttributes,
+            Boolean isLinkedToFloatingInterestRates, FloatingRate floatingRate, BigDecimal interestRateDifferential,
+            BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate,
+            Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed,
+            final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments) {
+        this.fund = fund;
+        this.transactionProcessingStrategy = transactionProcessingStrategy;
+        this.name = name.trim();
+        this.shortName = shortName.trim();
+        if (StringUtils.isNotBlank(description)) {
+            this.description = description.trim();
+        } else {
+            this.description = null;
+        }
+
+        if (charges != null) {
+            this.charges = charges;
+        }
+
+        this.isLinkedToFloatingInterestRate = isLinkedToFloatingInterestRates == null ? false : isLinkedToFloatingInterestRates;
+        if (isLinkedToFloatingInterestRate) {
+            this.floatingRates = new LoanProductFloatingRates(floatingRate, this, interestRateDifferential, minDifferentialLendingRate,
+                    maxDifferentialLendingRate, defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed);
+        }
+
+        this.allowVariabeInstallments = isVariableInstallmentsAllowed == null ? false : isVariableInstallmentsAllowed;
+
+        if (allowVariabeInstallments) {
+            this.variableInstallmentConfig = new LoanProductVariableInstallmentConfig(this, minimumGapBetweenInstallments,
+                    maximumGapBetweenInstallments);
+        }
+
+        this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod,
+                interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod,
+                considerPartialPeriodInterest, repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment,
+                graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing,
+                daysInMonthType.getValue(), daysInYearType.getValue(), isInterestRecalculationEnabled);
+
+        this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings();
+
+        this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal,
+                defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments,
+                defaultMaxNumberOfInstallments);
+
+        if (accountingRuleType != null) {
+            this.accountingRule = accountingRuleType.getValue();
+        }
+        this.includeInBorrowerCycle = includeInBorrowerCycle;
+        this.useBorrowerCycle = useBorrowerCycle;
+
+        if (startDate != null) {
+            this.startDate = startDate.toDateTimeAtStartOfDay().toDate();
+        }
+
+        if (closeDate != null) {
+            this.closeDate = closeDate.toDateTimeAtStartOfDay().toDate();
+        }
+
+        this.externalId = externalId;
+        this.borrowerCycleVariations = loanProductBorrowerCycleVariations;
+        for (LoanProductBorrowerCycleVariations borrowerCycleVariations : this.borrowerCycleVariations) {
+            borrowerCycleVariations.updateLoanProduct(this);
+        }
+        if (loanProductConfigurableAttributes != null) {
+            this.loanConfigurableAttributes = loanProductConfigurableAttributes;
+            loanConfigurableAttributes.updateLoanProduct(this);
+        }
+
+        this.loanProducTrancheDetails = new LoanProductTrancheDetails(multiDisburseLoan, maxTrancheCount, outstandingLoanBalance);
+        this.overdueDaysForNPA = overdueDaysForNPA;
+        this.productInterestRecalculationDetails = productInterestRecalculationDetails;
+        this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
+        this.holdGuaranteeFunds = holdGuarantorFunds;
+        this.loanProductGuaranteeDetails = loanProductGuaranteeDetails;
+        this.principalThresholdForLastInstallment = principalThresholdForLastInstallment;
+        this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion;
+        this.canDefineInstallmentAmount = canDefineEmiAmount;
+        this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
+    }
+
+    public MonetaryCurrency getCurrency() {
+        return this.loanProductRelatedDetail.getCurrency();
+    }
+
+    public void update(final Fund fund) {
+        this.fund = fund;
+    }
+
+    public void update(final LoanTransactionProcessingStrategy strategy) {
+        this.transactionProcessingStrategy = strategy;
+    }
+
+    public LoanTransactionProcessingStrategy getRepaymentStrategy() {
+        return this.transactionProcessingStrategy;
+    }
+
+    public boolean hasCurrencyCodeOf(final String currencyCode) {
+        return this.loanProductRelatedDetail.hasCurrencyCodeOf(currencyCode);
+    }
+
+    public boolean update(final List<Charge> newProductCharges) {
+        if (newProductCharges == null) { return false; }
+
+        boolean updated = false;
+        if (this.charges != null) {
+            final Set<Charge> currentSetOfCharges = new HashSet<>(this.charges);
+            final Set<Charge> newSetOfCharges = new HashSet<>(newProductCharges);
+
+            if (!currentSetOfCharges.equals(newSetOfCharges)) {
+                updated = true;
+                this.charges = newProductCharges;
+            }
+        } else {
+            updated = true;
+            this.charges = newProductCharges;
+        }
+        return updated;
+    }
+
+    public Integer getAccountingType() {
+        return this.accountingRule;
+    }
+
+    public List<Charge> getLoanProductCharges() {
+        return this.charges;
+    }
+
+    public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) {
+        this.loanConfigurableAttributes = loanConfigurableAttributes;
+    }
+
+    public LoanProductConfigurableAttributes getLoanProductConfigurableAttributes() {
+        return this.loanConfigurableAttributes;
+    }
+
+    public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) {
+
+        final Map<String, Object> actualChanges = this.loanProductRelatedDetail.update(command, aprCalculator);
+        actualChanges.putAll(loanProductMinMaxConstraints().update(command));
+
+        final String isLinkedToFloatingInterestRates = "isLinkedToFloatingInterestRates";
+        if (command.isChangeInBooleanParameterNamed(isLinkedToFloatingInterestRates, this.isLinkedToFloatingInterestRate)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isLinkedToFloatingInterestRates);
+            actualChanges.put(isLinkedToFloatingInterestRates, newValue);
+            this.isLinkedToFloatingInterestRate = newValue;
+        }
+
+        if (this.isLinkedToFloatingInterestRate) {
+            actualChanges.putAll(loanProductFloatingRates().update(command, floatingRate));
+            this.loanProductRelatedDetail.updateForFloatingInterestRates();
+            this.loanProductMinMaxConstraints.updateForFloatingInterestRates();
+        } else {
+            this.floatingRates = null;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(LoanProductConstants.allowVariableInstallmentsParamName, this.allowVariabeInstallments)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.allowVariableInstallmentsParamName);
+            actualChanges.put(LoanProductConstants.allowVariableInstallmentsParamName, newValue);
+            this.allowVariabeInstallments = newValue;
+        }
+
+        if (this.allowVariabeInstallments) {
+            actualChanges.putAll(loanProductVariableInstallmentConfig().update(command));
+        } else {
+            this.variableInstallmentConfig = null;
+        }
+
+        final String accountingTypeParamName = "accountingRule";
+        if (command.isChangeInIntegerParameterNamed(accountingTypeParamName, this.accountingRule)) {
+            final Integer newValue = command.integerValueOfParameterNamed(accountingTypeParamName);
+            actualChanges.put(accountingTypeParamName, newValue);
+            this.accountingRule = newValue;
+        }
+
+        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 shortNameParamName = LoanProductConstants.shortName;
+        if (command.isChangeInStringParameterNamed(shortNameParamName, this.shortName)) {
+            final String newValue = command.stringValueOfParameterNamed(shortNameParamName);
+            actualChanges.put(shortNameParamName, newValue);
+            this.shortName = newValue;
+        }
+
+        final String descriptionParamName = "description";
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = newValue;
+        }
+
+        Long existingFundId = null;
+        if (this.fund != null) {
+            existingFundId = this.fund.getId();
+        }
+        final String fundIdParamName = "fundId";
+        if (command.isChangeInLongParameterNamed(fundIdParamName, existingFundId)) {
+            final Long newValue = command.longValueOfParameterNamed(fundIdParamName);
+            actualChanges.put(fundIdParamName, newValue);
+        }
+
+        Long existingStrategyId = null;
+        if (this.transactionProcessingStrategy != null) {
+            existingStrategyId = this.transactionProcessingStrategy.getId();
+        }
+        final String transactionProcessingStrategyParamName = "transactionProcessingStrategyId";
+        if (command.isChangeInLongParameterNamed(transactionProcessingStrategyParamName, existingStrategyId)) {
+            final Long newValue = command.longValueOfParameterNamed(transactionProcessingStrategyParamName);
+            actualChanges.put(transactionProcessingStrategyParamName, newValue);
+        }
+
+        final String chargesParamName = "charges";
+        if (command.hasParameter(chargesParamName)) {
+            final JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName);
+            if (jsonArray != null) {
+                actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName));
+            }
+        }
+
+        final String includeInBorrowerCycleParamName = "includeInBorrowerCycle";
+        if (command.isChangeInBooleanParameterNamed(includeInBorrowerCycleParamName, this.includeInBorrowerCycle)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(includeInBorrowerCycleParamName);
+            actualChanges.put(includeInBorrowerCycleParamName, newValue);
+            this.includeInBorrowerCycle = newValue;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(LoanProductConstants.useBorrowerCycleParameterName, this.useBorrowerCycle)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.useBorrowerCycleParameterName);
+            actualChanges.put(LoanProductConstants.useBorrowerCycleParameterName, newValue);
+            this.useBorrowerCycle = newValue;
+        }
+
+        if (this.useBorrowerCycle) {
+            actualChanges.putAll(updateBorrowerCycleVariations(command));
+        } else {
+            clearVariations(null, true);
+        }
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+
+        final String localeParamName = "locale";
+        final String dateFormatParamName = "dateFormat";
+
+        final String startDateParamName = "startDate";
+        if (command.isChangeInLocalDateParameterNamed(startDateParamName, getStartDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName);
+            actualChanges.put(startDateParamName, valueAsInput);
+            actualChanges.put(dateFormatParamName, dateFormatAsInput);
+            actualChanges.put(localeParamName, localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName);
+            if (newValue != null) {
+                this.startDate = newValue.toDate();
+            } else {
+                this.startDate = null;
+            }
+        }
+
+        final String closeDateParamName = "closeDate";
+        if (command.isChangeInLocalDateParameterNamed(closeDateParamName, getCloseDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(closeDateParamName);
+            actualChanges.put(closeDateParamName, valueAsInput);
+            actualChanges.put(dateFormatParamName, dateFormatAsInput);
+            actualChanges.put(localeParamName, localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(closeDateParamName);
+            if (newValue != null) {
+                this.closeDate = newValue.toDate();
+            } else {
+                this.closeDate = null;
+            }
+        }
+
+        final String externalIdTypeParamName = "externalId";
+        if (command.isChangeInStringParameterNamed(externalIdTypeParamName, this.externalId)) {
+            final String newValue = command.stringValueOfParameterNamed(externalIdTypeParamName);
+            actualChanges.put(accountingTypeParamName, newValue);
+            this.externalId = newValue;
+        }
+        loanProducTrancheDetails.update(command, actualChanges, localeAsInput);
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.overdueDaysForNPAParameterName, this.overdueDaysForNPA)) {
+            final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.overdueDaysForNPAParameterName);
+            actualChanges.put(LoanProductConstants.overdueDaysForNPAParameterName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.overdueDaysForNPA = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment,
+                this.minimumDaysBetweenDisbursalAndFirstRepayment)) {
+            final Integer newValue = command
+                    .integerValueOfParameterNamed(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment);
+            actualChanges.put(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.minimumDaysBetweenDisbursalAndFirstRepayment = newValue;
+        }
+
+        /**
+         * Update interest recalculation settings
+         */
+        final boolean isInterestRecalculationEnabledChanged = actualChanges
+                .containsKey(LoanProductConstants.isInterestRecalculationEnabledParameterName);
+
+        if (isInterestRecalculationEnabledChanged) {
+            if (this.isInterestRecalculationEnabled()) {
+                this.productInterestRecalculationDetails = LoanProductInterestRecalculationDetails.createFrom(command);
+                this.productInterestRecalculationDetails.updateProduct(this);
+                actualChanges.put(LoanProductConstants.interestRecalculationCompoundingMethodParameterName,
+                        command.integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName));
+                actualChanges.put(LoanProductConstants.rescheduleStrategyMethodParameterName,
+                        command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName));
+            } else {
+                this.productInterestRecalculationDetails = null;
+            }
+        }
+
+        if (!isInterestRecalculationEnabledChanged && this.isInterestRecalculationEnabled()) {
+            this.productInterestRecalculationDetails.update(command, actualChanges, localeAsInput);
+        }
+
+        if (command.isChangeInBooleanParameterNamed(LoanProductConstants.holdGuaranteeFundsParamName, this.holdGuaranteeFunds)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.holdGuaranteeFundsParamName);
+            actualChanges.put(LoanProductConstants.holdGuaranteeFundsParamName, newValue);
+            this.holdGuaranteeFunds = newValue;
+        }
+
+        final String configurableAttributesChanges = LoanProductConstants.allowAttributeOverridesParamName;
+        if (command.hasParameter(configurableAttributesChanges)) {
+            if (!command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName).isJsonNull()) {
+                actualChanges.put(configurableAttributesChanges, command.jsonFragment(configurableAttributesChanges));
+
+                if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                        .getAsJsonPrimitive(LoanProductConstants.amortizationTypeParamName).getAsBoolean() != this.loanConfigurableAttributes
+                        .getAmortizationBoolean()) {
+                    this.loanConfigurableAttributes.setAmortizationType(command.parsedJson().getAsJsonObject()
+                            .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                            .getAsJsonPrimitive(LoanProductConstants.amortizationTypeParamName).getAsBoolean());
+                }
+
+                if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                        .getAsJsonPrimitive(LoanProductConstants.interestTypeParamName).getAsBoolean() != this.loanConfigurableAttributes
+                        .getInterestMethodBoolean()) {
+                    this.loanConfigurableAttributes.setInterestType(command.parsedJson().getAsJsonObject()
+                            .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                            .getAsJsonPrimitive(LoanProductConstants.interestTypeParamName).getAsBoolean());
+                }
+
+                if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                        .getAsJsonPrimitive(LoanProductConstants.transactionProcessingStrategyIdParamName).getAsBoolean() != this.loanConfigurableAttributes
+                        .getTransactionProcessingStrategyBoolean()) {
+                    this.loanConfigurableAttributes.setTransactionProcessingStrategyId(command.parsedJson().getAsJsonObject()
+                            .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                            .getAsJsonPrimitive(LoanProductConstants.transactionProcessingStrategyIdParamName).getAsBoolean());
+                }
+
+                if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                        .getAsJsonPrimitive(LoanProductConstants.interestCalculationPeriodTypeParamName).getAsBoolean() != this.loanConfigurableAttributes
+                        .getInterestCalcPeriodBoolean()) {
+                    this.loanConfigurableAttributes.setInterestCalculationPeriodType(command.parsedJson().getAsJsonObject()
+                            .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                            .getAsJsonPrimitive(LoanProductConstants.interestCalculationPeriodTypeParamName).getAsBoolean());
+                }
+
+                if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                        .getAsJsonPrimitive(LoanProductConstants.inArrearsToleranceParamName).getAsBoolean() != this.loanConfigurableAttributes
+                        .getArrearsToleranceBoolean()) {
+                    this.loanConfigurableAttributes.setInArrearsTolerance(command.parsedJson().getAsJsonObject()
+                            .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                            .getAsJsonPrimitive(LoanProductConstants.inArrearsToleranceParamName).getAsBoolean());
+                }
+
+                if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                        .getAsJsonPrimitive(LoanProductConstants.repaymentEveryParamName).getAsBoolean() != this.loanConfigurableAttributes
+                        .getRepaymentEveryBoolean()) {
+                    this.loanConfigurableAttributes.setRepaymentEvery(command.parsedJson().getAsJsonObject()
+                            .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                            .getAsJsonPrimitive(LoanProductConstants.repaymentEveryParamName).getAsBoolean());
+                }
+
+                if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                        .getAsJsonPrimitive(LoanProductConstants.graceOnPrincipalAndInterestPaymentParamName).getAsBoolean() != this.loanConfigurableAttributes
+                        .getGraceOnPrincipalAndInterestPaymentBoolean()) {
+                    this.loanConfigurableAttributes.setGraceOnPrincipalAndInterestPayment(command.parsedJson().getAsJsonObject()
+                            .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                            .getAsJsonPrimitive(LoanProductConstants.graceOnPrincipalAndInterestPaymentParamName).getAsBoolean());
+                }
+
+                if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                        .getAsJsonPrimitive(LoanProductConstants.graceOnArrearsAgeingParameterName).getAsBoolean() != this.loanConfigurableAttributes
+                        .getGraceOnArrearsAgingBoolean()) {
+                    this.loanConfigurableAttributes.setGraceOnArrearsAgeing(command.parsedJson().getAsJsonObject()
+                            .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                            .getAsJsonPrimitive(LoanProductConstants.graceOnArrearsAgeingParameterName).getAsBoolean());
+                }
+            } else {
+                this.loanConfigurableAttributes = LoanProductConfigurableAttributes.populateDefaultsForConfigurableAttributes();
+                this.loanConfigurableAttributes.updateLoanProduct(this);
+            }
+        }
+
+        if (actualChanges.containsKey(LoanProductConstants.holdGuaranteeFundsParamName)) {
+            if (this.holdGuaranteeFunds) {
+                this.loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command);
+                this.loanProductGuaranteeDetails.updateProduct(this);
+                actualChanges.put(LoanProductConstants.mandatoryGuaranteeParamName,
+                        this.loanProductGuaranteeDetails.getMandatoryGuarantee());
+                actualChanges.put(LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
+                        this.loanProductGuaranteeDetails.getMinimumGuaranteeFromGuarantor());
+                actualChanges.put(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName,
+                        this.loanProductGuaranteeDetails.getMinimumGuaranteeFromOwnFunds());
+            } else {
+                this.loanProductGuaranteeDetails = null;
+            }
+
+        } else if (this.holdGuaranteeFunds) {
+            this.loanProductGuaranteeDetails.update(command, actualChanges);
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.principalThresholdForLastInstallmentParamName,
+                this.principalThresholdForLastInstallment)) {
+            BigDecimal newValue = command
+                    .bigDecimalValueOfParameterNamed(LoanProductConstants.principalThresholdForLastInstallmentParamName);
+            actualChanges.put(LoanProductConstants.principalThresholdForLastInstallmentParamName, newValue);
+            this.principalThresholdForLastInstallment = newValue;
+        }
+        if (command.isChangeInBooleanParameterNamed(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName,
+                this.accountMovesOutOfNPAOnlyOnArrearsCompletion)) {
+            final boolean newValue = command
+                    .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName);
+            actualChanges.put(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, newValue);
+            this.accountMovesOutOfNPAOnlyOnArrearsCompletion = newValue;
+        }
+        if (command.isChangeInBooleanParameterNamed(LoanProductConstants.canDefineEmiAmountParamName, this.canDefineInstallmentAmount)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.canDefineEmiAmountParamName);
+            actualChanges.put(LoanProductConstants.canDefineEmiAmountParamName, newValue);
+            this.canDefineInstallmentAmount = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamedWithNullCheck(LoanProductConstants.installmentAmountInMultiplesOfParamName,
+                this.installmentAmountInMultiplesOf)) {
+            final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.installmentAmountInMultiplesOfParamName);
+            actualChanges.put(LoanProductConstants.installmentAmountInMultiplesOfParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.installmentAmountInMultiplesOf = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    private LoanProductFloatingRates loanProductFloatingRates() {
+        this.floatingRates = this.floatingRates == null ? new LoanProductFloatingRates(null, this, null, null, null, null, false)
+                : this.floatingRates;
+        return this.floatingRates;
+    }
+
+    public LoanProductVariableInstallmentConfig loanProductVariableInstallmentConfig() {
+        this.variableInstallmentConfig = this.variableInstallmentConfig == null ? new LoanProductVariableInstallmentConfig(this, null, null)
+                : this.variableInstallmentConfig;
+        return this.variableInstallmentConfig;
+    }
+
+    public boolean isAccountingDisabled() {
+        return AccountingRuleType.NONE.getValue().equals(this.accountingRule);
+    }
+
+    public boolean isCashBasedAccountingEnabled() {
+        return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingRule);
+    }
+
+    public boolean isAccrualBasedAccountingEnabled() {
+        return isUpfrontAccrualAccountingEnabled() || isPeriodicAccrualAccountingEnabled();
+    }
+
+    public boolean isUpfrontAccrualAccountingEnabled() {
+        return AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(this.accountingRule);
+    }
+
+    public boolean isPeriodicAccrualAccountingEnabled() {
+        return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(this.accountingRule);
+    }
+
+    public Money getPrincipalAmount() {
+        return this.loanProductRelatedDetail.getPrincipal();
+    }
+
+    public Money getMinPrincipalAmount() {
+        return Money.of(this.loanProductRelatedDetail.getCurrency(), loanProductMinMaxConstraints().getMinPrincipal());
+    }
+
+    public Money getMaxPrincipalAmount() {
+        return Money.of(this.loanProductRelatedDetail.getCurrency(), loanProductMinMaxConstraints().getMaxPrincipal());
+    }
+
+    public BigDecimal getNominalInterestRatePerPeriod() {
+        return this.loanProductRelatedDetail.getNominalInterestRatePerPeriod();
+    }
+
+    public PeriodFrequencyType getInterestPeriodFrequencyType() {
+        return this.loanProductRelatedDetail.getInterestPeriodFrequencyType();
+    }
+
+    public BigDecimal getMinNominalInterestRatePerPeriod() {
+        return loanProductMinMaxConstraints().getMinNominalInterestRatePerPeriod();
+    }
+
+    public BigDecimal getMaxNominalInterestRatePerPeriod() {
+        return loanProductMinMaxConstraints().getMaxNominalInterestRatePerPeriod();
+    }
+
+    public Integer getNumberOfRepayments() {
+        return this.loanProductRelatedDetail.getNumberOfRepayments();
+    }
+
+    public Integer getMinNumberOfRepayments() {
+        return loanProductMinMaxConstraints().getMinNumberOfRepayments();
+    }
+
+    public Integer getMaxNumberOfRepayments() {
+        return loanProductMinMaxConstraints().getMaxNumberOfRepayments();
+    }
+
+    public LoanProductMinMaxConstraints loanProductMinMaxConstraints() {
+        // If all min and max fields are null then loanProductMinMaxConstraints
+        // initialising to null
+        // Reset LoanProductMinMaxConstraints with null values.
+        this.loanProductMinMaxConstraints = this.loanProductMinMaxConstraints == null ? new LoanProductMinMaxConstraints(null, null, null,
+                null, null, null) : this.loanProductMinMaxConstraints;
+        return this.loanProductMinMaxConstraints;
+    }
+
+    public boolean isIncludeInBorrowerCycle() {
+        return this.includeInBorrowerCycle;
+    }
+
+    public LocalDate getStartDate() {
+        LocalDate startLocalDate = null;
+        if (this.startDate != null) {
+            startLocalDate = LocalDate.fromDateFields(this.startDate);
+        }
+        return startLocalDate;
+    }
+
+    public LocalDate getCloseDate() {
+        LocalDate closeLocalDate = null;
+        if (this.closeDate != null) {
+            closeLocalDate = LocalDate.fromDateFields(this.closeDate);
+        }
+        return closeLocalDate;
+    }
+
+    public String productName() {
+        return this.name;
+    }
+
+    public String getExternalId() {
+        return this.externalId;
+    }
+
+    public boolean useBorrowerCycle() {
+        return this.useBorrowerCycle;
+    }
+
+    public boolean isMultiDisburseLoan() {
+        return this.loanProducTrancheDetails.isMultiDisburseLoan();
+    }
+
+    public BigDecimal outstandingLoanBalance() {
+        return this.loanProducTrancheDetails.outstandingLoanBalance();
+    }
+
+    public Integer maxTrancheCount() {
+        return this.loanProducTrancheDetails.maxTrancheCount();
+    }
+
+    public boolean isInterestRecalculationEnabled() {
+        return this.loanProductRelatedDetail.isInterestRecalculationEnabled();
+    }
+
+    public Integer getMinimumDaysBetweenDisbursalAndFirstRepayment() {
+        return this.minimumDaysBetweenDisbursalAndFirstRepayment == null ? 0 : this.minimumDaysBetweenDisbursalAndFirstRepayment;
+    }
+
+    public LoanProductBorrowerCycleVariations fetchLoanProductBorrowerCycleVariationById(Long id) {
+        LoanProductBorrowerCycleVariations borrowerCycleVariation = null;
+        for (LoanProductBorrowerCycleVariations cycleVariation : this.borrowerCycleVariations) {
+            if (id.equals(cycleVariation.getId())) {
+                borrowerCycleVariation = cycleVariation;
+                break;
+            }
+        }
+        return borrowerCycleVariation;
+    }
+
+    public Map<String, BigDecimal> fetchBorrowerCycleVariationsForCycleNumber(final Integer cycleNumber) {
+        Map<String, BigDecimal> borrowerCycleVariations = new HashMap<>();
+        borrowerCycleVariations.put(LoanProductConstants.principal, this.loanProductRelatedDetail.getPrincipal().getAmount());
+        borrowerCycleVariations.put(LoanProductConstants.interestRatePerPeriod,
+                this.loanProductRelatedDetail.getNominalInterestRatePerPeriod());
+        if (this.loanProductRelatedDetail.getNumberOfRepayments() != null) {
+            borrowerCycleVariations.put(LoanProductConstants.numberOfRepayments,
+                    BigDecimal.valueOf(this.loanProductRelatedDetail.getNumberOfRepayments()));
+        }
+
+        if (this.loanProductMinMaxConstraints != null) {
+            borrowerCycleVariations.put(LoanProductConstants.minPrincipal, this.loanProductMinMaxConstraints.getMinPrincipal());
+            borrowerCycleVariations.put(LoanProductConstants.maxPrincipal, this.loanProductMinMaxConstraints.getMaxPrincipal());
+            borrowerCycleVariations.put(LoanProductConstants.minInterestRatePerPeriod,
+                    this.loanProductMinMaxConstraints.getMinNominalInterestRatePerPeriod());
+            borrowerCycleVariations.put(LoanProductConstants.maxInterestRatePerPeriod,
+                    this.loanProductMinMaxConstraints.getMaxNominalInterestRatePerPeriod());
+
+            if (this.loanProductMinMaxConstraints.getMinNumberOfRepayments() != null) {
+                borrowerCycleVariations.put(LoanProductConstants.minNumberOfRepayments,
+                        BigDecimal.valueOf(this.loanProductMinMaxConstraints.getMinNumberOfRepayments()));
+            }
+
+            if (this.loanProductMinMaxConstraints.getMaxNumberOfRepayments() != null) {
+                borrowerCycleVariations.put(LoanProductConstants.maxNumberOfRepayments,
+                        BigDecimal.valueOf(this.loanProductMinMaxConstraints.getMaxNumberOfRepayments()));
+            }
+        }
+        if (cycleNumber > 0) {
+            Integer principalCycleUsed = 0;
+            Integer interestCycleUsed = 0;
+            Integer repaymentCycleUsed = 0;
+            for (LoanProductBorrowerCycleVariations cycleVariation : this.borrowerCycleVariations) {
+                if (cycleVariation.getBorrowerCycleNumber() == cycleNumber
+                        && cycleVariation.getValueConditionType().equals(LoanProductValueConditionType.EQUAL)) {
+                    switch (cycleVariation.getParamType()) {
+                        case PRINCIPAL:
+                            borrowerCycleVariations.put(LoanProductConstants.principal, cycleVariation.getDefaultValue());
+                            borrowerCycleVariations.put(LoanProductConstants.minPrincipal, cycleVariation.getMinValue());
+                            borrowerCycleVariations.put(LoanProductConstants.maxPrincipal, cycleVariation.getMaxValue());
+                            principalCycleUsed = cycleVariation.getBorrowerCycleNumber();
+                        break;
+                        case INTERESTRATE:
+                            borrowerCycleVariations.put(LoanProductConstants.interestRatePerPeriod, cycleVariation.getDefaultValue());
+                            borrowerCycleVariations.put(LoanProductConstants.minInterestRatePerPeriod, cycleVariation.getMinValue());
+                            borrowerCycleVariations.put(LoanProductConstants.maxInterestRatePerPeriod, cycleVariation.getMaxValue());
+                            interestCycleUsed = cycleVariation.getBorrowerCycleNumber();
+                        break;
+                        case REPAYMENT:
+                            borrowerCycleVariations.put(LoanProductConstants.numberOfRepayments, cycleVariation.getDefaultValue());
+                            borrowerCycleVariations.put(LoanProductConstants.minNumberOfRepayments, cycleVariation.getMinValue());
+                            borrowerCycleVariations.put(LoanProductConstants.maxNumberOfRepayments, cycleVariation.getMaxValue());
+                            repaymentCycleUsed = cycleVariation.getBorrowerCycleNumber();
+                        break;
+                        default:
+                        break;
+                    }
+                } else if (cycleVariation.getBorrowerCycleNumber() < cycleNumber
+                        && cycleVariation.getValueConditionType().equals(LoanProductValueConditionType.GREATERTHAN)) {
+                    switch (cycleVariation.getParamType()) {
+                        case PRINCIPAL:
+                            if (principalCycleUsed < cycleVariation.getBorrowerCycleNumber()) {
+                                borrowerCycleVariations.put(LoanProductConstants.principal, cycleVariation.getDefaultValue());
+                                borrowerCycleVariations.put(LoanProductConstants.minPrincipal, cycleVariation.getMinValue());
+                                borrowerCycleVariations.put(LoanProductConstants.maxPrincipal, cycleVariation.getMaxValue());
+                                principalCycleUsed = cycleVariation.getBorrowerCycleNumber();
+                            }
+                        break;
+                        case INTERESTRATE:
+                            if (interestCycleUsed < cycleVariation.getBorrowerCycleNumber()) {
+                                borrowerCycleVariations.put(LoanProductConstants.interestRatePerPeriod, cycleVariation.getDefaultValue());
+                                borrowerCycleVariations.put(LoanProductConstants.minInterestRatePerPeriod, cycleVariation.getMinValue());
+                                borrowerCycleVariations.put(LoanProductConstants.maxInterestRatePerPeriod, cycleVariation.getMaxValue());
+                                interestCycleUsed = cycleVariation.getBorrowerCycleNumber();
+                            }
+                        break;
+                        case REPAYMENT:
+                            if (repaymentCycleUsed < cycleVariation.getBorrowerCycleNumber()) {
+                                borrowerCycleVariations.put(LoanProductConstants.numberOfRepayments, cycleVariation.getDefaultValue());
+                                borrowerCycleVariations.put(LoanProductConstants.minNumberOfRepayments, cycleVariation.getMinValue());
+                                borrowerCycleVariations.put(LoanProductConstants.maxNumberOfRepayments, cycleVariation.getMaxValue());
+                                repaymentCycleUsed = cycleVariation.getBorrowerCycleNumber();
+                            }
+                        break;
+                        default:
+                        break;
+                    }
+                }
+            }
+        }
+        return borrowerCycleVariations;
+    }
+
+    public DaysInMonthType fetchDaysInMonthType() {
+        return this.loanProductRelatedDetail.fetchDaysInMonthType();
+    }
+
+    public DaysInYearType fetchDaysInYearType() {
+        return this.loanProductRelatedDetail.fetchDaysInYearType();
+    }
+
+    public LoanProductInterestRecalculationDetails getProductInterestRecalculationDetails() {
+        return this.productInterestRecalculationDetails;
+    }
+
+    public boolean isHoldGuaranteeFundsEnabled() {
+        return this.holdGuaranteeFunds;
+    }
+
+    public LoanProductGuaranteeDetails getLoanProductGuaranteeDetails() {
+        return this.loanProductGuaranteeDetails;
+    }
+
+    public String getShortName() {
+        return this.shortName;
+    }
+
+    public BigDecimal getPrincipalThresholdForLastInstallment() {
+        return this.principalThresholdForLastInstallment;
+    }
+
+    public boolean isArrearsBasedOnOriginalSchedule() {
+        boolean isBasedOnOriginalSchedule = false;
+        if (getProductInterestRecalculationDetails() != null) {
+            isBasedOnOriginalSchedule = getProductInterestRecalculationDetails().isArrearsBasedOnOriginalSchedule();
+        }
+        return isBasedOnOriginalSchedule;
+    }
+
+    public boolean canDefineInstallmentAmount() {
+        return this.canDefineInstallmentAmount;
+    }
+
+    public Integer getInstallmentAmountInMultiplesOf() {
+        return this.installmentAmountInMultiplesOf;
+    }
+
+    public LoanPreClosureInterestCalculationStrategy preCloseInterestCalculationStrategy() {
+        LoanPreClosureInterestCalculationStrategy preCloseInterestCalculationStrategy = LoanPreClosureInterestCalculationStrategy.NONE;
+        if (this.isInterestRecalculationEnabled()) {
+            preCloseInterestCalculationStrategy = getProductInterestRecalculationDetails().preCloseInterestCalculationStrategy();
+        }
+        return preCloseInterestCalculationStrategy;
+    }
+
+    public LoanProductRelatedDetail getLoanProductRelatedDetail() {
+        return loanProductRelatedDetail;
+    }
+
+    public boolean isLinkedToFloatingInterestRate() {
+        return this.isLinkedToFloatingInterestRate;
+    }
+
+    public LoanProductFloatingRates getFloatingRates() {
+        return this.floatingRates;
+    }
+
+    public Collection<FloatingRatePeriodData> fetchInterestRates(final FloatingRateDTO floatingRateDTO) {
+        Collection<FloatingRatePeriodData> applicableRates = new ArrayList<>(1);
+        if (isLinkedToFloatingInterestRate()) {
+            applicableRates = getFloatingRates().fetchInterestRates(floatingRateDTO);
+        }
+        return applicableRates;
+    }
+
+    public boolean allowVariabeInstallments() {
+        return this.allowVariabeInstallments;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductBorrowerCycleVariations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductBorrowerCycleVariations.java
new file mode 100755
index 0000000..676377f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductBorrowerCycleVariations.java
@@ -0,0 +1,130 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_product_loan_variations_borrower_cycle")
+public class LoanProductBorrowerCycleVariations extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "loan_product_id", nullable = false)
+    private LoanProduct loanProduct;
+
+    @Column(name = "borrower_cycle_number", nullable = false)
+    private Integer borrowerCycleNumber;
+
+    @Column(name = "param_type", nullable = false)
+    private Integer paramType;
+
+    @Column(name = "value_condition", nullable = false)
+    private Integer valueConditionType;
+
+    @Column(name = "min_value", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minValue;
+
+    @Column(name = "max_value", scale = 6, precision = 19, nullable = true)
+    private BigDecimal maxValue;
+
+    @Column(name = "default_value", scale = 6, precision = 19, nullable = false)
+    private BigDecimal defaultValue;
+
+    protected LoanProductBorrowerCycleVariations() {
+
+    }
+
+    public LoanProductBorrowerCycleVariations(final Integer borrowerCycleNumber, final Integer paramType, final Integer valueConditionType,
+            final BigDecimal minValue, final BigDecimal maxValue, final BigDecimal defaultValue) {
+        this.borrowerCycleNumber = borrowerCycleNumber;
+        this.paramType = paramType;
+        this.valueConditionType = valueConditionType;
+        this.minValue = minValue;
+        this.maxValue = maxValue;
+        this.defaultValue = defaultValue;
+    }
+
+    public void updateLoanProduct(final LoanProduct loanProduct) {
+        this.loanProduct = loanProduct;
+    }
+
+    public LoanProductParamType getParamType() {
+        return LoanProductParamType.fromInt(this.paramType);
+    }
+
+    public LoanProductValueConditionType getValueConditionType() {
+        return LoanProductValueConditionType.fromInt(this.valueConditionType);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        final LoanProductBorrowerCycleVariations borrowerCycleVariations = (LoanProductBorrowerCycleVariations) obj;
+        boolean minValequal = false;
+        if (borrowerCycleVariations.minValue == null && this.minValue == null) {
+            minValequal = true;
+        } else if (borrowerCycleVariations.minValue != null && this.minValue != null) {
+            minValequal = borrowerCycleVariations.minValue.equals(this.minValue);
+        }
+
+        boolean maxValequal = false;
+        if (borrowerCycleVariations.maxValue == null && this.maxValue == null) {
+            maxValequal = true;
+        } else if (borrowerCycleVariations.maxValue != null && this.maxValue != null) {
+            maxValequal = borrowerCycleVariations.maxValue.equals(this.maxValue);
+        }
+        if (borrowerCycleVariations.borrowerCycleNumber.equals(this.borrowerCycleNumber)
+                && borrowerCycleVariations.defaultValue.equals(this.defaultValue) && minValequal && maxValequal
+                && borrowerCycleVariations.valueConditionType.equals(this.valueConditionType)
+                && borrowerCycleVariations.paramType.equals(this.paramType)) { return true; }
+        return false;
+    }
+
+    public void copy(final LoanProductBorrowerCycleVariations borrowerCycleVariations) {
+        this.defaultValue = borrowerCycleVariations.defaultValue;
+        this.minValue = borrowerCycleVariations.minValue;
+        this.maxValue = borrowerCycleVariations.maxValue;
+        this.valueConditionType = borrowerCycleVariations.valueConditionType;
+        this.borrowerCycleNumber = borrowerCycleVariations.borrowerCycleNumber;
+    }
+
+    public Integer getBorrowerCycleNumber() {
+        return this.borrowerCycleNumber;
+    }
+
+    public BigDecimal getMinValue() {
+        return this.minValue;
+    }
+
+    public BigDecimal getMaxValue() {
+        return this.maxValue;
+    }
+
+    public BigDecimal getDefaultValue() {
+        return this.defaultValue;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductConfigurableAttributes.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductConfigurableAttributes.java
new file mode 100644
index 0000000..3e2a8f2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductConfigurableAttributes.java
@@ -0,0 +1,209 @@
+/**
+ * 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.loanproduct.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_product_loan_configurable_attributes")
+public class LoanProductConfigurableAttributes extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "loan_product_id", nullable = true)
+    private LoanProduct loanProduct;
+
+    @Column(name = "amortization_method_enum", nullable = true)
+    private Boolean amortizationType;
+
+    @Column(name = "interest_method_enum", nullable = true)
+    private Boolean interestType;
+
+    @Column(name = "loan_transaction_strategy_id", nullable = true)
+    private Boolean transactionProcessingStrategyId;
+
+    @Column(name = "interest_calculated_in_period_enum", nullable = true)
+    private Boolean interestCalculationPeriodType;
+
+    @Column(name = "arrearstolerance_amount", nullable = true)
+    private Boolean inArrearsTolerance;
+
+    @Column(name = "repay_every", nullable = true)
+    private Boolean repaymentEvery;
+
+    @Column(name = "moratorium", nullable = true)
+    private Boolean graceOnPrincipalAndInterestPayment;
+
+    @Column(name = "grace_on_arrears_ageing", nullable = true)
+    private Boolean graceOnArrearsAgeing;
+
+    public static String[] supportedloanConfigurableAttributes = { "amortizationType", "interestType", "transactionProcessingStrategyId",
+            "interestCalculationPeriodType", "inArrearsTolerance", "repaymentEvery", "graceOnPrincipalAndInterestPayment",
+            "graceOnArrearsAgeing" };
+
+    public static LoanProductConfigurableAttributes createFrom(JsonCommand command) {
+
+        final Boolean amortization = command.parsedJson().getAsJsonObject()
+                .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                .getAsJsonPrimitive(LoanProductConstants.amortizationTypeParamName).getAsBoolean();
+        final Boolean interestMethod = command.parsedJson().getAsJsonObject()
+                .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                .getAsJsonPrimitive(LoanProductConstants.interestTypeParamName).getAsBoolean();
+        final Boolean transactionProcessingStrategy = command.parsedJson().getAsJsonObject()
+                .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                .getAsJsonPrimitive(LoanProductConstants.transactionProcessingStrategyIdParamName).getAsBoolean();
+        final Boolean interestCalcPeriod = command.parsedJson().getAsJsonObject()
+                .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                .getAsJsonPrimitive(LoanProductConstants.interestCalculationPeriodTypeParamName).getAsBoolean();
+        final Boolean arrearsTolerance = command.parsedJson().getAsJsonObject()
+                .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                .getAsJsonPrimitive(LoanProductConstants.inArrearsToleranceParamName).getAsBoolean();
+        final Boolean repaymentEvery = command.parsedJson().getAsJsonObject()
+                .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                .getAsJsonPrimitive(LoanProductConstants.repaymentEveryParamName).getAsBoolean();
+        final Boolean graceOnPrincipalAndInterestPayment = command.parsedJson().getAsJsonObject()
+                .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                .getAsJsonPrimitive(LoanProductConstants.graceOnPrincipalAndInterestPaymentParamName).getAsBoolean();
+        final Boolean graceOnArrearsAging = command.parsedJson().getAsJsonObject()
+                .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
+                .getAsJsonPrimitive(LoanProductConstants.graceOnArrearsAgeingParameterName).getAsBoolean();
+
+        return new LoanProductConfigurableAttributes(amortization, interestMethod, transactionProcessingStrategy, interestCalcPeriod,
+                arrearsTolerance, repaymentEvery, graceOnPrincipalAndInterestPayment, graceOnArrearsAging);
+    }
+
+    public void updateLoanProduct(final LoanProduct loanProduct) {
+        this.loanProduct = loanProduct;
+    }
+
+    public static LoanProductConfigurableAttributes populateDefaultsForConfigurableAttributes() {
+        final Boolean amortization = true;
+        final Boolean interestMethod = true;
+        final Boolean transactionProcessingStrategy = true;
+        final Boolean interestCalcPeriod = true;
+        final Boolean arrearsTolerance = true;
+        final Boolean repaymentEvery = true;
+        final Boolean graceOnPrincipalAndInterestPayment = true;
+        final Boolean graceOnArrearsAging = true;
+
+        return new LoanProductConfigurableAttributes(amortization, interestMethod, transactionProcessingStrategy, interestCalcPeriod,
+                arrearsTolerance, repaymentEvery, graceOnPrincipalAndInterestPayment, graceOnArrearsAging);
+    }
+
+    public LoanProductConfigurableAttributes(Boolean amortization, Boolean interestMethod, Boolean transactionProcessingStrategy,
+            Boolean interestCalcPeriod, Boolean arrearsTolerance, Boolean repaymentEvery, Boolean graceOnPrincipalAndInterestPayment,
+            Boolean graceOnArrearsAging) {
+        this.amortizationType = amortization;
+        this.interestType = interestMethod;
+        this.inArrearsTolerance = arrearsTolerance;
+        this.graceOnArrearsAgeing = graceOnArrearsAging;
+        this.interestCalculationPeriodType = interestCalcPeriod;
+        this.graceOnPrincipalAndInterestPayment = graceOnPrincipalAndInterestPayment;
+        this.repaymentEvery = repaymentEvery;
+        this.transactionProcessingStrategyId = transactionProcessingStrategy;
+    }
+
+    protected LoanProductConfigurableAttributes() {
+
+    }
+
+    public static String[] getAllowedLoanConfigurableAttributes() {
+        return supportedloanConfigurableAttributes;
+    }
+
+    public LoanProduct getLoanProduct() {
+        return loanProduct;
+    }
+
+    public Boolean getAmortizationBoolean() {
+        return amortizationType;
+    }
+
+    public Boolean getInterestMethodBoolean() {
+        return interestType;
+    }
+
+    public Boolean getTransactionProcessingStrategyBoolean() {
+        return transactionProcessingStrategyId;
+    }
+
+    public Boolean getInterestCalcPeriodBoolean() {
+        return interestCalculationPeriodType;
+    }
+
+    public Boolean getArrearsToleranceBoolean() {
+        return inArrearsTolerance;
+    }
+
+    public Boolean getRepaymentEveryBoolean() {
+        return repaymentEvery;
+    }
+
+    public Boolean getGraceOnPrincipalAndInterestPaymentBoolean() {
+        return graceOnPrincipalAndInterestPayment;
+    }
+
+    public Boolean getGraceOnArrearsAgingBoolean() {
+        return graceOnArrearsAgeing;
+    }
+
+    public void setLoanProduct(LoanProduct loanProduct) {
+        this.loanProduct = loanProduct;
+    }
+
+    public void setAmortizationType(Boolean amortizationType) {
+        this.amortizationType = amortizationType;
+    }
+
+    public void setInterestType(Boolean interestType) {
+        this.interestType = interestType;
+    }
+
+    public void setTransactionProcessingStrategyId(Boolean transactionProcessingStrategyId) {
+        this.transactionProcessingStrategyId = transactionProcessingStrategyId;
+    }
+
+    public void setInterestCalculationPeriodType(Boolean interestCalculationPeriodType) {
+        this.interestCalculationPeriodType = interestCalculationPeriodType;
+    }
+
+    public void setInArrearsTolerance(Boolean inArrearsTolerance) {
+        this.inArrearsTolerance = inArrearsTolerance;
+    }
+
+    public void setRepaymentEvery(Boolean repaymentEvery) {
+        this.repaymentEvery = repaymentEvery;
+    }
+
+    public void setGraceOnPrincipalAndInterestPayment(Boolean graceOnPrincipalAndInterestPayment) {
+        this.graceOnPrincipalAndInterestPayment = graceOnPrincipalAndInterestPayment;
+    }
+
+    public void setGraceOnArrearsAgeing(Boolean graceOnArrearsAgeing) {
+        this.graceOnArrearsAgeing = graceOnArrearsAgeing;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductFloatingRates.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductFloatingRates.java
new file mode 100644
index 0000000..0c2f476
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductFloatingRates.java
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanproduct.domain;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
+import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
+import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_product_loan_floating_rates")
+public class LoanProductFloatingRates extends AbstractPersistable<Long> {
+
+    @OneToOne
+    @JoinColumn(name = "loan_product_id", nullable = false)
+    private LoanProduct loanProduct;
+
+    @ManyToOne
+    @JoinColumn(name = "floating_rates_id", nullable = false)
+    private FloatingRate floatingRate;
+
+    @Column(name = "interest_rate_differential", nullable = false)
+    private BigDecimal interestRateDifferential;
+
+    @Column(name = "min_differential_lending_rate", nullable = false)
+    private BigDecimal minDifferentialLendingRate;
+
+    @Column(name = "default_differential_lending_rate", nullable = false)
+    private BigDecimal defaultDifferentialLendingRate;
+
+    @Column(name = "max_differential_lending_rate", nullable = false)
+    private BigDecimal maxDifferentialLendingRate;
+
+    @Column(name = "is_floating_interest_rate_calculation_allowed", nullable = false)
+	private boolean isFloatingInterestRateCalculationAllowed;
+    
+	public LoanProductFloatingRates(){
+		
+	}
+	public LoanProductFloatingRates(FloatingRate floatingRate, LoanProduct loanProduct, BigDecimal interestRateDifferential, 
+            BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, 
+            boolean isFloatingInterestRateCalculationAllowed){
+		this.floatingRate = floatingRate;
+		this.loanProduct = loanProduct;
+		this.interestRateDifferential = interestRateDifferential;
+		this.minDifferentialLendingRate = minDifferentialLendingRate;
+		this.maxDifferentialLendingRate = maxDifferentialLendingRate;
+		this.defaultDifferentialLendingRate = defaultDifferentialLendingRate;
+		this.isFloatingInterestRateCalculationAllowed = isFloatingInterestRateCalculationAllowed;
+	}
+
+    public LoanProduct getLoanProduct() {
+        return this.loanProduct;
+    }
+
+    public FloatingRate getFloatingRate() {
+        return this.floatingRate;
+    }
+
+    public BigDecimal getInterestRateDifferential() {
+        return this.interestRateDifferential;
+    }
+
+    public BigDecimal getMinDifferentialLendingRate() {
+        return this.minDifferentialLendingRate;
+    }
+
+    public BigDecimal getDefaultDifferentialLendingRate() {
+        return this.defaultDifferentialLendingRate;
+    }
+
+    public BigDecimal getMaxDifferentialLendingRate() {
+        return this.maxDifferentialLendingRate;
+    }
+
+    public boolean isFloatingInterestRateCalculationAllowed() {
+        return this.isFloatingInterestRateCalculationAllowed;
+    }
+
+    public Map<? extends String, ? extends Object> update(JsonCommand command, FloatingRate floatingRate) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
+        if (floatingRate != null) {
+            final String floatingRatesId = "floatingRatesId";
+            if (this.floatingRate == null || command.isChangeInLongParameterNamed(floatingRatesId, this.floatingRate.getId())) {
+                final long newValue = command.longValueOfParameterNamed(floatingRatesId);
+                actualChanges.put(floatingRatesId, newValue);
+                this.floatingRate = floatingRate;
+            }
+        }
+
+        final String interestRateDifferential = "interestRateDifferential";
+        if (command.isChangeInBigDecimalParameterNamed(interestRateDifferential, this.interestRateDifferential)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRateDifferential);
+            actualChanges.put(interestRateDifferential, newValue);
+            this.interestRateDifferential = newValue;
+        }
+        final String minDifferentialLendingRate = "minDifferentialLendingRate";
+        if (command.isChangeInBigDecimalParameterNamed(minDifferentialLendingRate, this.minDifferentialLendingRate)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minDifferentialLendingRate);
+            actualChanges.put(minDifferentialLendingRate, newValue);
+            this.minDifferentialLendingRate = newValue;
+        }
+        final String defaultDifferentialLendingRate = "defaultDifferentialLendingRate";
+        if (command.isChangeInBigDecimalParameterNamed(defaultDifferentialLendingRate, this.defaultDifferentialLendingRate)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(defaultDifferentialLendingRate);
+            actualChanges.put(defaultDifferentialLendingRate, newValue);
+            this.defaultDifferentialLendingRate = newValue;
+        }
+        final String maxDifferentialLendingRate = "maxDifferentialLendingRate";
+        if (command.isChangeInBigDecimalParameterNamed(maxDifferentialLendingRate, this.maxDifferentialLendingRate)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxDifferentialLendingRate);
+            actualChanges.put(maxDifferentialLendingRate, newValue);
+            this.maxDifferentialLendingRate = newValue;
+        }
+        final String isFloatingInterestRateCalculationAllowed = "isFloatingInterestRateCalculationAllowed";
+        if (command
+                .isChangeInBooleanParameterNamed(isFloatingInterestRateCalculationAllowed, this.isFloatingInterestRateCalculationAllowed)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isFloatingInterestRateCalculationAllowed);
+            actualChanges.put(isFloatingInterestRateCalculationAllowed, newValue);
+            this.isFloatingInterestRateCalculationAllowed = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    public Collection<FloatingRatePeriodData> fetchInterestRates(final FloatingRateDTO floatingRateDTO) {
+        floatingRateDTO.addInterestRateDiff(this.interestRateDifferential);
+        return floatingRate.fetchInterestRates(floatingRateDTO);
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductGuaranteeDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductGuaranteeDetails.java
new file mode 100755
index 0000000..bc590d3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductGuaranteeDetails.java
@@ -0,0 +1,120 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+/**
+ * Entity for capturing interest recalculation settings
+ * 
+ * @author conflux
+ */
+
+@Entity
+@Table(name = "m_product_loan_guarantee_details")
+public class LoanProductGuaranteeDetails extends AbstractPersistable<Long> {
+
+    @OneToOne
+    @JoinColumn(name = "loan_product_id", nullable = false)
+    private LoanProduct loanProduct;
+
+    @Column(name = "mandatory_guarantee", scale = 6, precision = 19, nullable = false)
+    private BigDecimal mandatoryGuarantee;
+
+    @Column(name = "minimum_guarantee_from_own_funds", scale = 6, precision = 19, nullable = false)
+    private BigDecimal minimumGuaranteeFromOwnFunds;
+
+    @Column(name = "minimum_guarantee_from_guarantor_funds", scale = 6, precision = 19, nullable = false)
+    private BigDecimal minimumGuaranteeFromGuarantor;
+
+    protected LoanProductGuaranteeDetails() {
+        //
+    }
+
+    public static LoanProductGuaranteeDetails createFrom(final JsonCommand command) {
+
+        final BigDecimal mandatoryGuarantee = command.bigDecimalValueOfParameterNamed(LoanProductConstants.mandatoryGuaranteeParamName);
+        final BigDecimal minimumGuaranteeFromGuarantor = command
+                .bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromGuarantorParamName);
+        final BigDecimal minimumGuaranteeFromOwnFunds = command
+                .bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName);
+
+        return new LoanProductGuaranteeDetails(mandatoryGuarantee, minimumGuaranteeFromOwnFunds, minimumGuaranteeFromGuarantor);
+    }
+
+    private LoanProductGuaranteeDetails(final BigDecimal mandatoryGuarantee, final BigDecimal minimumGuaranteeFromOwnFunds,
+            final BigDecimal minimumGuaranteeFromGuarantor) {
+        this.mandatoryGuarantee = mandatoryGuarantee;
+        this.minimumGuaranteeFromGuarantor = minimumGuaranteeFromGuarantor;
+        this.minimumGuaranteeFromOwnFunds = minimumGuaranteeFromOwnFunds;
+    }
+
+    public void updateProduct(final LoanProduct loanProduct) {
+        this.loanProduct = loanProduct;
+    }
+
+    public void update(final JsonCommand command, final Map<String, Object> actualChanges) {
+
+        if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.mandatoryGuaranteeParamName, this.mandatoryGuarantee)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.mandatoryGuaranteeParamName);
+            actualChanges.put(LoanProductConstants.mandatoryGuaranteeParamName, newValue);
+            this.mandatoryGuarantee = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
+                this.minimumGuaranteeFromGuarantor)) {
+            final BigDecimal newValue = command
+                    .bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromGuarantorParamName);
+            actualChanges.put(LoanProductConstants.minimumGuaranteeFromGuarantorParamName, newValue);
+            this.minimumGuaranteeFromGuarantor = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName,
+                this.minimumGuaranteeFromOwnFunds)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName);
+            actualChanges.put(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, newValue);
+            this.minimumGuaranteeFromOwnFunds = newValue;
+        }
+
+    }
+
+    public BigDecimal getMandatoryGuarantee() {
+        return this.mandatoryGuarantee;
+    }
+
+    public BigDecimal getMinimumGuaranteeFromOwnFunds() {
+        return this.minimumGuaranteeFromOwnFunds;
+    }
+
+    public BigDecimal getMinimumGuaranteeFromGuarantor() {
+        return this.minimumGuaranteeFromGuarantor;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
new file mode 100644
index 0000000..4456054
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
@@ -0,0 +1,337 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.util.Date;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+/**
+ * Entity for capturing interest recalculation settings
+ * 
+ * @author conflux
+ */
+
+@Entity
+@Table(name = "m_product_loan_recalculation_details")
+public class LoanProductInterestRecalculationDetails extends AbstractPersistable<Long> {
+
+    @OneToOne
+    @JoinColumn(name = "product_id", nullable = false)
+    private LoanProduct loanProduct;
+
+    /**
+     * {@link InterestRecalculationCompoundingMethod}
+     */
+    @Column(name = "compound_type_enum", nullable = false)
+    private Integer interestRecalculationCompoundingMethod;
+
+    /**
+     * {@link LoanRescheduleStrategyMethod}
+     */
+    @Column(name = "reschedule_strategy_enum", nullable = false)
+    private Integer rescheduleStrategyMethod;
+
+    @Column(name = "rest_frequency_type_enum", nullable = false)
+    private Integer restFrequencyType;
+
+    @Column(name = "rest_frequency_interval", nullable = false)
+    private Integer restInterval;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "rest_freqency_date")
+    private Date restFrequencyDate;
+
+    @Column(name = "compounding_frequency_type_enum", nullable = true)
+    private Integer compoundingFrequencyType;
+
+    @Column(name = "compounding_frequency_interval", nullable = true)
+    private Integer compoundingInterval;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "compounding_freqency_date")
+    private Date compoundingFrequencyDate;
+
+    @Column(name = "arrears_based_on_original_schedule")
+    private boolean isArrearsBasedOnOriginalSchedule;
+
+    @Column(name = "pre_close_interest_calculation_strategy")
+    private Integer preClosureInterestCalculationStrategy;
+
+    protected LoanProductInterestRecalculationDetails() {
+        //
+    }
+
+    public static LoanProductInterestRecalculationDetails createFrom(final JsonCommand command) {
+
+        final Integer interestRecalculationCompoundingMethod = InterestRecalculationCompoundingMethod.fromInt(
+                command.integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName)).getValue();
+
+        final Integer loanRescheduleStrategyMethod = LoanRescheduleStrategyMethod.fromInt(
+                command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName)).getValue();
+
+        final Integer recurrenceFrequency = command
+                .integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyTypeParameterName);
+        final LocalDate recurrenceOnLocalDate = command
+                .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName);
+        Integer recurrenceInterval = command
+                .integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName);
+        final boolean isArrearsBasedOnOriginalSchedule = command
+                .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName);
+        RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(recurrenceFrequency);
+        Date recurrenceOnDate = null;
+        if (recurrenceOnLocalDate != null) {
+            if (!frequencyType.isSameAsRepayment()) {
+                recurrenceOnDate = recurrenceOnLocalDate.toDate();
+            }
+        }
+        if (frequencyType.isSameAsRepayment()) {
+            recurrenceInterval = 0;
+        }
+
+        InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod
+                .fromInt(interestRecalculationCompoundingMethod);
+        Integer compoundingRecurrenceFrequency = null;
+        Integer compoundingInterval = null;
+        Date recurrenceOnCompoundingDate = null;
+        if (compoundingMethod.isCompoundingEnabled()) {
+            compoundingRecurrenceFrequency = command
+                    .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName);
+            compoundingInterval = command
+                    .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName);
+            RecalculationFrequencyType compoundingFrequencyType = RecalculationFrequencyType.fromInt(compoundingRecurrenceFrequency);
+            if (compoundingFrequencyType.isSameAsRepayment()) {
+                recurrenceInterval = 0;
+            }
+            final LocalDate compoundingRecurrenceOnLocalDate = command
+                    .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName);
+
+            if (compoundingRecurrenceOnLocalDate != null) {
+                if (!compoundingFrequencyType.isSameAsRepayment()) {
+                    recurrenceOnCompoundingDate = compoundingRecurrenceOnLocalDate.toDate();
+                }
+            }
+        }
+
+        Integer preCloseInterestCalculationStrategy = command
+                .integerValueOfParameterNamed(LoanProductConstants.preClosureInterestCalculationStrategyParamName);
+        if (preCloseInterestCalculationStrategy == null) {
+            preCloseInterestCalculationStrategy = LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE.getValue();
+        }
+
+        return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod,
+                recurrenceFrequency, recurrenceInterval, recurrenceOnDate, compoundingRecurrenceFrequency, compoundingInterval,
+                recurrenceOnCompoundingDate, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy);
+    }
+
+    private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod,
+            final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval,
+            final Date restFrequencyDate, Integer compoundingFrequencyType, Integer compoundingInterval, Date compoundingFrequencyDate,
+            final boolean isArrearsBasedOnOriginalSchedule, final Integer preCloseInterestCalculationStrategy) {
+        this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
+        this.rescheduleStrategyMethod = rescheduleStrategyMethod;
+        this.restFrequencyType = restFrequencyType;
+        this.restInterval = restInterval;
+        this.restFrequencyDate = restFrequencyDate;
+        this.compoundingFrequencyDate = compoundingFrequencyDate;
+        this.compoundingFrequencyType = compoundingFrequencyType;
+        this.compoundingInterval = compoundingInterval;
+        this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule;
+        this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy;
+    }
+
+    public void updateProduct(final LoanProduct loanProduct) {
+        this.loanProduct = loanProduct;
+    }
+
+    public Integer getInterestRecalculationCompoundingMethod() {
+        return this.interestRecalculationCompoundingMethod;
+    }
+
+    public Integer getRescheduleStrategyMethod() {
+        return this.rescheduleStrategyMethod;
+    }
+
+    public void update(final JsonCommand command, final Map<String, Object> actualChanges, final String localeAsInput) {
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName,
+                this.interestRecalculationCompoundingMethod)) {
+            final Integer newValue = command
+                    .integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName);
+            actualChanges.put(LoanProductConstants.interestRecalculationCompoundingMethodParameterName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.interestRecalculationCompoundingMethod = InterestRecalculationCompoundingMethod.fromInt(newValue).getValue();
+        }
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName,
+                this.rescheduleStrategyMethod)) {
+            final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName);
+            actualChanges.put(LoanProductConstants.rescheduleStrategyMethodParameterName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.rescheduleStrategyMethod = LoanRescheduleStrategyMethod.fromInt(newValue).getValue();
+        }
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyTypeParameterName,
+                this.restFrequencyType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyTypeParameterName);
+            actualChanges.put(LoanProductConstants.recalculationRestFrequencyTypeParameterName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.restFrequencyType = RecalculationFrequencyType.fromInt(newValue).getValue();
+        }
+        RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(this.restFrequencyType);
+        if (frequencyType.isSameAsRepayment()) {
+            this.restInterval = 0;
+            this.restFrequencyDate = null;
+        } else {
+            if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName,
+                    this.restInterval)) {
+                Integer newValue = command
+                        .integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName);
+                actualChanges.put(LoanProductConstants.recalculationRestFrequencyIntervalParameterName, newValue);
+                actualChanges.put("locale", localeAsInput);
+                this.restInterval = newValue;
+            }
+
+            if (command.isChangeInLocalDateParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName,
+                    getRestFrequencyLocalDate())) {
+                final LocalDate newValue = command
+                        .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName);
+                Date recurrenceOnDate = null;
+                if (newValue != null) {
+                    recurrenceOnDate = newValue.toDate();
+                }
+                actualChanges.put(LoanProductConstants.recalculationRestFrequencyDateParamName, newValue);
+                this.restFrequencyDate = recurrenceOnDate;
+            }
+        }
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName,
+                this.compoundingFrequencyType)) {
+            final Integer newValue = command
+                    .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName);
+            actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName, newValue);
+            this.compoundingFrequencyType = RecalculationFrequencyType.fromInt(newValue).getValue();
+        }
+
+        InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod
+                .fromInt(this.interestRecalculationCompoundingMethod);
+        if (compoundingMethod.isCompoundingEnabled()) {
+            RecalculationFrequencyType compoundingfrequencyType = RecalculationFrequencyType.fromInt(this.compoundingFrequencyType);
+            if (compoundingfrequencyType.isSameAsRepayment()) {
+                this.compoundingInterval = null;
+                this.compoundingFrequencyDate = null;
+            } else {
+                if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName,
+                        this.compoundingInterval)) {
+                    Integer newValue = command
+                            .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName);
+                    actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, newValue);
+                    this.compoundingInterval = newValue;
+                }
+
+                if (command.isChangeInLocalDateParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName,
+                        getCompoundingFrequencyLocalDate())) {
+                    final LocalDate newValue = command
+                            .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName);
+                    Date recurrenceOnDate = null;
+                    if (newValue != null) {
+                        recurrenceOnDate = newValue.toDate();
+                    }
+                    actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyDateParamName, newValue);
+                    this.compoundingFrequencyDate = recurrenceOnDate;
+                }
+            }
+        } else {
+            this.compoundingFrequencyType = null;
+            this.compoundingInterval = null;
+            this.compoundingFrequencyDate = null;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName,
+                this.isArrearsBasedOnOriginalSchedule)) {
+            final boolean newValue = command
+                    .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName);
+            actualChanges.put(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, newValue);
+            this.isArrearsBasedOnOriginalSchedule = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.preClosureInterestCalculationStrategyParamName,
+                this.preClosureInterestCalculationStrategy)) {
+            Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.preClosureInterestCalculationStrategyParamName);
+            if (newValue == null) {
+                newValue = LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE.getValue();
+            }
+            actualChanges.put(LoanProductConstants.preClosureInterestCalculationStrategyParamName, newValue);
+            this.preClosureInterestCalculationStrategy = newValue;
+        }
+
+    }
+
+    public LocalDate getRestFrequencyLocalDate() {
+        LocalDate recurrenceOnLocalDate = null;
+        if (this.restFrequencyDate != null) {
+            recurrenceOnLocalDate = new LocalDate(this.restFrequencyDate);
+        }
+        return recurrenceOnLocalDate;
+    }
+
+    public RecalculationFrequencyType getRestFrequencyType() {
+        return RecalculationFrequencyType.fromInt(this.restFrequencyType);
+    }
+
+    public Integer getRestInterval() {
+        return this.restInterval;
+    }
+
+    public LocalDate getCompoundingFrequencyLocalDate() {
+        LocalDate recurrenceOnLocalDate = null;
+        if (this.compoundingFrequencyDate != null) {
+            recurrenceOnLocalDate = new LocalDate(this.compoundingFrequencyDate);
+        }
+        return recurrenceOnLocalDate;
+    }
+
+    public RecalculationFrequencyType getCompoundingFrequencyType() {
+        return RecalculationFrequencyType.fromInt(this.compoundingFrequencyType);
+    }
+
+    public Integer getCompoundingInterval() {
+        return this.compoundingInterval;
+    }
+
+    public boolean isArrearsBasedOnOriginalSchedule() {
+        return this.isArrearsBasedOnOriginalSchedule;
+    }
+
+    public LoanPreClosureInterestCalculationStrategy preCloseInterestCalculationStrategy() {
+        return LoanPreClosureInterestCalculationStrategy.fromInt(this.preClosureInterestCalculationStrategy);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java
new file mode 100644
index 0000000..bc8dbe2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java
@@ -0,0 +1,168 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.math.BigDecimal;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+
+/**
+ * LoanProductMinMaxConstraints encapsulates all the Min and Max details of a
+ * {@link LoanProduct}.
+ */
+@Embeddable
+public class LoanProductMinMaxConstraints {
+
+    @Column(name = "min_principal_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minPrincipal;
+
+    @Column(name = "max_principal_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal maxPrincipal;
+
+    @Column(name = "min_nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minNominalInterestRatePerPeriod;
+
+    @Column(name = "max_nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = true)
+    private BigDecimal maxNominalInterestRatePerPeriod;
+
+    @Column(name = "min_number_of_repayments", nullable = true)
+    private Integer minNumberOfRepayments;
+
+    @Column(name = "max_number_of_repayments", nullable = true)
+    private Integer maxNumberOfRepayments;
+
+    public static LoanProductMinMaxConstraints createFrom(final BigDecimal minPrincipal, final BigDecimal maxPrincipal,
+            final BigDecimal minNominalInterestRatePerPeriod, final BigDecimal maxNominalInterestRatePerPeriod,
+            final Integer minNumberOfRepayments, final Integer maxNumberOfRepayments) {
+
+        return new LoanProductMinMaxConstraints(minPrincipal, maxPrincipal, minNominalInterestRatePerPeriod,
+                maxNominalInterestRatePerPeriod, minNumberOfRepayments, maxNumberOfRepayments);
+    }
+
+    protected LoanProductMinMaxConstraints() {
+        //
+    }
+
+    public LoanProductMinMaxConstraints(final BigDecimal defaultMinPrincipal, final BigDecimal defaultMaxPrincipal,
+            final BigDecimal defaultMinNominalInterestRatePerPeriod, final BigDecimal defaultMaxNominalInterestRatePerPeriod,
+            final Integer defaultMinNumberOfRepayments, final Integer defaultMaxNumberOfRepayments) {
+        this.minPrincipal = defaultMinPrincipal;
+        this.maxPrincipal = defaultMaxPrincipal;
+        this.minNominalInterestRatePerPeriod = defaultMinNominalInterestRatePerPeriod;
+        this.maxNominalInterestRatePerPeriod = defaultMaxNominalInterestRatePerPeriod;
+        this.minNumberOfRepayments = defaultMinNumberOfRepayments;
+        this.maxNumberOfRepayments = defaultMaxNumberOfRepayments;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
+
+        final String localeAsInput = command.locale();
+
+        final String minPrincipalParamName = "minPrincipal";
+        if (command.isChangeInBigDecimalParameterNamedWithNullCheck(minPrincipalParamName, this.minPrincipal)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minPrincipalParamName);
+            actualChanges.put(minPrincipalParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.minPrincipal = newValue;
+        }
+
+        final String maxPrincipalParamName = "maxPrincipal";
+        if (command.isChangeInBigDecimalParameterNamedWithNullCheck(maxPrincipalParamName, this.maxPrincipal)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxPrincipalParamName);
+            actualChanges.put(maxPrincipalParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.maxPrincipal = newValue;
+        }
+
+        final String minNumberOfRepaymentsParamName = "minNumberOfRepayments";
+        if (command.isChangeInIntegerParameterNamed(minNumberOfRepaymentsParamName, this.minNumberOfRepayments)) {
+            final Integer newValue = command.integerValueOfParameterNamed(minNumberOfRepaymentsParamName);
+            actualChanges.put(minNumberOfRepaymentsParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.minNumberOfRepayments = newValue;
+        }
+
+        final String maxNumberOfRepaymentsParamName = "maxNumberOfRepayments";
+        if (command.isChangeInIntegerParameterNamed(maxNumberOfRepaymentsParamName, this.maxNumberOfRepayments)) {
+            final Integer newValue = command.integerValueOfParameterNamed(maxNumberOfRepaymentsParamName);
+            actualChanges.put(maxNumberOfRepaymentsParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.maxNumberOfRepayments = newValue;
+        }
+
+        final String minInterestRatePerPeriodParamName = "minInterestRatePerPeriod";
+        if (command
+                .isChangeInBigDecimalParameterNamedWithNullCheck(minInterestRatePerPeriodParamName, this.minNominalInterestRatePerPeriod)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minInterestRatePerPeriodParamName);
+            actualChanges.put(minInterestRatePerPeriodParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.minNominalInterestRatePerPeriod = newValue;
+        }
+
+        final String maxInterestRatePerPeriodParamName = "maxInterestRatePerPeriod";
+        if (command
+                .isChangeInBigDecimalParameterNamedWithNullCheck(maxInterestRatePerPeriodParamName, this.maxNominalInterestRatePerPeriod)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxInterestRatePerPeriodParamName);
+            actualChanges.put(maxInterestRatePerPeriodParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.maxNominalInterestRatePerPeriod = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    public BigDecimal getMinPrincipal() {
+        return this.minPrincipal;
+    }
+
+    public BigDecimal getMaxPrincipal() {
+        return this.maxPrincipal;
+    }
+
+    public BigDecimal getMinNominalInterestRatePerPeriod() {
+        return this.minNominalInterestRatePerPeriod == null ? null : BigDecimal.valueOf(Double.valueOf(this.minNominalInterestRatePerPeriod
+                .stripTrailingZeros().toString()));
+    }
+
+    public BigDecimal getMaxNominalInterestRatePerPeriod() {
+        return this.maxNominalInterestRatePerPeriod == null ? null : BigDecimal.valueOf(Double.valueOf(this.maxNominalInterestRatePerPeriod
+                .stripTrailingZeros().toString()));
+    }
+
+    public Integer getMinNumberOfRepayments() {
+        return this.minNumberOfRepayments;
+    }
+
+    public Integer getMaxNumberOfRepayments() {
+        return this.maxNumberOfRepayments;
+    }
+
+	public void updateForFloatingInterestRates() {
+		this.minNominalInterestRatePerPeriod = null;
+		this.maxNominalInterestRatePerPeriod = null;
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java
new file mode 100644
index 0000000..15cf538
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.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.portfolio.loanproduct.domain;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+
+/**
+ * Represents the bare minimum repayment details needed for activities related
+ * to generating repayment schedules.
+ */
+public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
+
+	MonetaryCurrency getCurrency();
+	
+	Money getPrincipal();
+	
+	Integer graceOnInterestCharged();
+	
+	Integer graceOnInterestPayment();
+	
+	Integer graceOnPrincipalPayment();
+	
+	Money getInArrearsTolerance();
+	
+	BigDecimal getNominalInterestRatePerPeriod();
+	
+	PeriodFrequencyType getInterestPeriodFrequencyType();
+	
+	BigDecimal getAnnualNominalInterestRate();
+	
+	InterestMethod getInterestMethod();
+	
+	InterestCalculationPeriodMethod getInterestCalculationPeriodMethod();
+	
+    Integer getRepayEvery();
+
+    PeriodFrequencyType getRepaymentPeriodFrequencyType();
+
+    Integer getNumberOfRepayments();
+    
+    AmortizationMethod getAmortizationMethod();
+    
+    Integer getGraceOnDueDate();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductParamType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductParamType.java
new file mode 100755
index 0000000..d9146bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductParamType.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.portfolio.loanproduct.domain;
+
+public enum LoanProductParamType {
+
+    INVALID(0, "LoanProductParamType.invalid"), //
+    PRINCIPAL(1, "LoanProductParamType.principal"), //
+    INTERESTRATE(2, "LoanProductParamType.interestrate"), //
+    REPAYMENT(3, "LoanProductParamType.repayment"); //
+
+    private final Integer value;
+    private final String code;
+
+    private LoanProductParamType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static LoanProductParamType fromInt(final Integer chargeTime) {
+        LoanProductParamType loanProductParamType = LoanProductParamType.INVALID;
+        if (chargeTime != null) {
+            switch (chargeTime) {
+                case 1:
+                    loanProductParamType = PRINCIPAL;
+                break;
+                case 2:
+                    loanProductParamType = INTERESTRATE;
+                break;
+                case 3:
+                    loanProductParamType = REPAYMENT;
+                break;
+                default:
+                    loanProductParamType = INVALID;
+                break;
+            }
+        }
+        return loanProductParamType;
+    }
+
+    public boolean isParamTypePrincipal() {
+        return LoanProductParamType.PRINCIPAL.getValue().equals(this.value);
+    }
+
+    public boolean isParamTypeInterestTate() {
+        return LoanProductParamType.INTERESTRATE.getValue().equals(this.value);
+    }
+
+    public boolean isParamTypeRepayment() {
+        return LoanProductParamType.REPAYMENT.getValue().equals(this.value);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
new file mode 100644
index 0000000..8f7a8d2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
@@ -0,0 +1,614 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Embedded;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+
+/**
+ * LoanRepaymentScheduleDetail encapsulates all the details of a
+ * {@link LoanProduct} that are also used and persisted by a {@link Loan}.
+ */
+@Embeddable
+public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentScheduleRelatedDetail {
+
+    @Embedded
+    private MonetaryCurrency currency;
+
+    @Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal principal;
+
+    @Column(name = "nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = true)
+    private BigDecimal nominalInterestRatePerPeriod;
+
+    // FIXME - move away form JPA ordinal use for enums using just integer -
+    // requires sql patch for existing users of software.
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "interest_period_frequency_enum", nullable = true)
+    private PeriodFrequencyType interestPeriodFrequencyType;
+
+    @Column(name = "annual_nominal_interest_rate", scale = 6, precision = 19, nullable = true)
+    private BigDecimal annualNominalInterestRate;
+
+    // FIXME - move away form JPA ordinal use for enums using just integer -
+    // requires sql patch for existing users of software.
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "interest_method_enum", nullable = false)
+    private InterestMethod interestMethod;
+
+    // FIXME - move away form JPA ordinal use for enums using just integer -
+    // requires sql patch for existing users of software.
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "interest_calculated_in_period_enum", nullable = false)
+    private InterestCalculationPeriodMethod interestCalculationPeriodMethod;
+
+    @Column(name = "allow_partial_period_interest_calcualtion", nullable = false)
+    private boolean allowPartialPeriodInterestCalcualtion;
+
+    @Column(name = "repay_every", nullable = false)
+    private Integer repayEvery;
+
+    // FIXME - move away form JPA ordinal use for enums using just integer -
+    // requires sql patch for existing users of software.
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "repayment_period_frequency_enum", nullable = false)
+    private PeriodFrequencyType repaymentPeriodFrequencyType;
+
+    @Column(name = "number_of_repayments", nullable = false)
+    private Integer numberOfRepayments;
+
+    @Column(name = "grace_on_principal_periods", nullable = true)
+    private Integer graceOnPrincipalPayment;
+
+    @Column(name = "grace_on_interest_periods", nullable = true)
+    private Integer graceOnInterestPayment;
+
+    @Column(name = "grace_interest_free_periods", nullable = true)
+    private Integer graceOnInterestCharged;
+
+    // FIXME - move away form JPA ordinal use for enums using just integer -
+    // requires sql patch for existing users of software.
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "amortization_method_enum", nullable = false)
+    private AmortizationMethod amortizationMethod;
+
+    @Column(name = "arrearstolerance_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal inArrearsTolerance;
+
+    @Column(name = "grace_on_arrears_ageing", nullable = true)
+    private Integer graceOnArrearsAgeing;
+
+    @Column(name = "days_in_month_enum", nullable = false)
+    private Integer daysInMonthType;
+
+    @Column(name = "days_in_year_enum", nullable = false)
+    private Integer daysInYearType;
+
+    @Column(name = "interest_recalculation_enabled")
+    private boolean isInterestRecalculationEnabled;
+
+    public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
+            final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
+            final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
+            final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion,
+            final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments,
+            final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
+            final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing,
+            final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled) {
+
+        return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
+                nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion,
+                repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment,
+                graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
+                isInterestRecalculationEnabled);
+    }
+
+    protected LoanProductRelatedDetail() {
+        //
+    }
+
+    public LoanProductRelatedDetail(final MonetaryCurrency currency, final BigDecimal defaultPrincipal,
+            final BigDecimal defaultNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType,
+            final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
+            final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion,
+            final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments,
+            final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
+            final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing,
+            final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled) {
+        this.currency = currency;
+        this.principal = defaultPrincipal;
+        this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
+        this.interestPeriodFrequencyType = interestPeriodFrequencyType;
+        this.annualNominalInterestRate = defaultAnnualNominalInterestRate;
+        this.interestMethod = interestMethod;
+        this.interestCalculationPeriodMethod = interestCalculationPeriodMethod;
+        this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion;
+        this.repayEvery = repayEvery;
+        this.repaymentPeriodFrequencyType = repaymentFrequencyType;
+        this.numberOfRepayments = defaultNumberOfRepayments;
+        this.graceOnPrincipalPayment = defaultToNullIfZero(graceOnPrincipalPayment);
+        this.graceOnInterestPayment = defaultToNullIfZero(graceOnInterestPayment);
+        this.graceOnInterestCharged = defaultToNullIfZero(graceOnInterestCharged);
+        this.amortizationMethod = amortizationMethod;
+        if (inArrearsTolerance != null && BigDecimal.ZERO.compareTo(inArrearsTolerance) == 0) {
+            this.inArrearsTolerance = null;
+        } else {
+            this.inArrearsTolerance = inArrearsTolerance;
+        }
+        this.graceOnArrearsAgeing = graceOnArrearsAgeing;
+        this.daysInMonthType = daysInMonthType;
+        this.daysInYearType = daysInYearType;
+        this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
+    }
+
+    private Integer defaultToNullIfZero(final Integer value) {
+        Integer defaultTo = value;
+        if (value != null && Integer.valueOf(0).equals(value)) {
+            defaultTo = null;
+        }
+        return defaultTo;
+    }
+
+    @Override
+    public MonetaryCurrency getCurrency() {
+        return this.currency.copy();
+    }
+
+    @Override
+    public Money getPrincipal() {
+        return Money.of(this.currency, this.principal);
+    }
+
+    public void setPrincipal(BigDecimal principal) {
+        this.principal = principal;
+    }
+
+    @Override
+    public Integer graceOnInterestCharged() {
+        return this.graceOnInterestCharged;
+    }
+
+    @Override
+    public Integer graceOnInterestPayment() {
+        return this.graceOnInterestPayment;
+    }
+
+    @Override
+    public Integer graceOnPrincipalPayment() {
+        return this.graceOnPrincipalPayment;
+    }
+
+    @Override
+    public Money getInArrearsTolerance() {
+        return Money.of(this.currency, this.inArrearsTolerance);
+    }
+
+    @Override
+    public BigDecimal getNominalInterestRatePerPeriod() {
+        return this.nominalInterestRatePerPeriod == null ? null : BigDecimal.valueOf(Double.valueOf(this.nominalInterestRatePerPeriod
+                .stripTrailingZeros().toString()));
+    }
+
+    @Override
+    public PeriodFrequencyType getInterestPeriodFrequencyType() {
+        return this.interestPeriodFrequencyType == null ? PeriodFrequencyType.INVALID : this.interestPeriodFrequencyType;
+    }
+
+    @Override
+    public BigDecimal getAnnualNominalInterestRate() {
+        return this.annualNominalInterestRate == null ? null : BigDecimal.valueOf(Double.valueOf(this.annualNominalInterestRate
+                .stripTrailingZeros().toString()));
+    }
+
+    @Override
+    public InterestMethod getInterestMethod() {
+        return this.interestMethod;
+    }
+
+    @Override
+    public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() {
+        return this.interestCalculationPeriodMethod;
+    }
+
+    @Override
+    public Integer getRepayEvery() {
+        return this.repayEvery;
+    }
+
+    @Override
+    public PeriodFrequencyType getRepaymentPeriodFrequencyType() {
+        return this.repaymentPeriodFrequencyType;
+    }
+
+    @Override
+    public Integer getNumberOfRepayments() {
+        return this.numberOfRepayments;
+    }
+
+    @Override
+    public AmortizationMethod getAmortizationMethod() {
+        return this.amortizationMethod;
+    }
+
+    public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
+
+        final String localeAsInput = command.locale();
+
+        String currencyCode = this.currency.getCode();
+        Integer digitsAfterDecimal = this.currency.getDigitsAfterDecimal();
+        Integer inMultiplesOf = this.currency.getCurrencyInMultiplesOf();
+
+        final String digitsAfterDecimalParamName = "digitsAfterDecimal";
+        if (command.isChangeInIntegerParameterNamed(digitsAfterDecimalParamName, digitsAfterDecimal)) {
+            final Integer newValue = command.integerValueOfParameterNamed(digitsAfterDecimalParamName);
+            actualChanges.put(digitsAfterDecimalParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            digitsAfterDecimal = newValue;
+            this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
+        }
+
+        final String currencyCodeParamName = "currencyCode";
+        if (command.isChangeInStringParameterNamed(currencyCodeParamName, currencyCode)) {
+            final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName);
+            actualChanges.put(currencyCodeParamName, newValue);
+            currencyCode = newValue;
+            this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
+        }
+
+        final String inMultiplesOfParamName = "inMultiplesOf";
+        if (command.isChangeInStringParameterNamed(inMultiplesOfParamName, currencyCode)) {
+            final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfParamName);
+            actualChanges.put(inMultiplesOfParamName, newValue);
+            inMultiplesOf = newValue;
+            this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
+        }
+
+        final Map<String, Object> loanApplicationAttributeChanges = updateLoanApplicationAttributes(command, aprCalculator);
+
+        actualChanges.putAll(loanApplicationAttributeChanges);
+
+        return actualChanges;
+    }
+
+    public Map<String, Object> updateLoanApplicationAttributes(final JsonCommand command, final AprCalculator aprCalculator) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
+
+        final String localeAsInput = command.locale();
+
+        final String principalParamName = "principal";
+        if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.principal)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
+            actualChanges.put(principalParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.principal = newValue;
+        }
+
+        final String repaymentEveryParamName = "repaymentEvery";
+        if (command.isChangeInIntegerParameterNamed(repaymentEveryParamName, this.repayEvery)) {
+            final Integer newValue = command.integerValueOfParameterNamed(repaymentEveryParamName);
+            actualChanges.put(repaymentEveryParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.repayEvery = newValue;
+        }
+
+        final String repaymentFrequencyTypeParamName = "repaymentFrequencyType";
+        if (command.isChangeInIntegerParameterNamed(repaymentFrequencyTypeParamName, this.repaymentPeriodFrequencyType.getValue())) {
+            Integer newValue = command.integerValueOfParameterNamed(repaymentFrequencyTypeParamName);
+            actualChanges.put(repaymentFrequencyTypeParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue);
+
+            if (this.repaymentPeriodFrequencyType == PeriodFrequencyType.MONTHS) {
+                final String repaymentFrequencyNthDayTypeParamName = "repaymentFrequencyNthDayType";
+                newValue = command.integerValueOfParameterNamed(repaymentFrequencyNthDayTypeParamName);
+                actualChanges.put(repaymentFrequencyNthDayTypeParamName, newValue);
+
+                final String repaymentFrequencyDayOfWeekTypeParamName = "repaymentFrequencyDayOfWeekType";
+                newValue = command.integerValueOfParameterNamed(repaymentFrequencyDayOfWeekTypeParamName);
+                actualChanges.put(repaymentFrequencyDayOfWeekTypeParamName, newValue);
+
+                actualChanges.put("locale", localeAsInput);
+            }
+        }
+
+        final String numberOfRepaymentsParamName = "numberOfRepayments";
+        if (command.isChangeInIntegerParameterNamed(numberOfRepaymentsParamName, this.numberOfRepayments)) {
+            final Integer newValue = command.integerValueOfParameterNamed(numberOfRepaymentsParamName);
+            actualChanges.put(numberOfRepaymentsParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.numberOfRepayments = newValue;
+        }
+
+        final String amortizationTypeParamName = "amortizationType";
+        if (command.isChangeInIntegerParameterNamed(amortizationTypeParamName, this.amortizationMethod.getValue())) {
+            final Integer newValue = command.integerValueOfParameterNamed(amortizationTypeParamName);
+            actualChanges.put(amortizationTypeParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.amortizationMethod = AmortizationMethod.fromInt(newValue);
+        }
+
+        final String inArrearsToleranceParamName = "inArrearsTolerance";
+        if (command.isChangeInBigDecimalParameterNamed(inArrearsToleranceParamName, this.inArrearsTolerance)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(inArrearsToleranceParamName);
+            actualChanges.put(inArrearsToleranceParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.inArrearsTolerance = newValue;
+        }
+
+        final String interestRatePerPeriodParamName = "interestRatePerPeriod";
+        if (command.isChangeInBigDecimalParameterNamed(interestRatePerPeriodParamName, this.nominalInterestRatePerPeriod)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRatePerPeriodParamName);
+            actualChanges.put(interestRatePerPeriodParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.nominalInterestRatePerPeriod = newValue;
+            updateInterestRateDerivedFields(aprCalculator);
+        }
+
+        final String interestRateFrequencyTypeParamName = "interestRateFrequencyType";
+        final int interestPeriodFrequencyType = this.interestPeriodFrequencyType == null ? PeriodFrequencyType.INVALID.getValue()
+                : this.interestPeriodFrequencyType.getValue();
+        if (command.isChangeInIntegerParameterNamed(interestRateFrequencyTypeParamName, interestPeriodFrequencyType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(interestRateFrequencyTypeParamName);
+            actualChanges.put(interestRateFrequencyTypeParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.interestPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue);
+            updateInterestRateDerivedFields(aprCalculator);
+        }
+
+        final String interestTypeParamName = "interestType";
+        if (command.isChangeInIntegerParameterNamed(interestTypeParamName, this.interestMethod.getValue())) {
+            final Integer newValue = command.integerValueOfParameterNamed(interestTypeParamName);
+            actualChanges.put(interestTypeParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.interestMethod = InterestMethod.fromInt(newValue);
+        }
+
+        final String interestCalculationPeriodTypeParamName = "interestCalculationPeriodType";
+        if (command
+                .isChangeInIntegerParameterNamed(interestCalculationPeriodTypeParamName, this.interestCalculationPeriodMethod.getValue())) {
+            final Integer newValue = command.integerValueOfParameterNamed(interestCalculationPeriodTypeParamName);
+            actualChanges.put(interestCalculationPeriodTypeParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(newValue);
+        }
+
+        if (command.isChangeInBooleanParameterNamed(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName,
+                this.allowPartialPeriodInterestCalcualtion)) {
+            final boolean newValue = command
+                    .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName);
+            actualChanges.put(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, newValue);
+            this.allowPartialPeriodInterestCalcualtion = newValue;
+        }
+
+        if (this.interestCalculationPeriodMethod.isDaily()) {
+            this.allowPartialPeriodInterestCalcualtion = false;
+        }
+
+        final String graceOnPrincipalPaymentParamName = "graceOnPrincipalPayment";
+        if (command.isChangeInIntegerParameterNamed(graceOnPrincipalPaymentParamName, this.graceOnPrincipalPayment)) {
+            final Integer newValue = command.integerValueOfParameterNamed(graceOnPrincipalPaymentParamName);
+            actualChanges.put(graceOnPrincipalPaymentParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.graceOnPrincipalPayment = newValue;
+        }
+
+        final String graceOnInterestPaymentParamName = "graceOnInterestPayment";
+        if (command.isChangeInIntegerParameterNamed(graceOnInterestPaymentParamName, this.graceOnInterestPayment)) {
+            final Integer newValue = command.integerValueOfParameterNamed(graceOnInterestPaymentParamName);
+            actualChanges.put(graceOnInterestPaymentParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.graceOnInterestPayment = newValue;
+        }
+
+        final String graceOnInterestChargedParamName = "graceOnInterestCharged";
+        if (command.isChangeInIntegerParameterNamed(graceOnInterestChargedParamName, this.graceOnInterestCharged)) {
+            final Integer newValue = command.integerValueOfParameterNamed(graceOnInterestChargedParamName);
+            actualChanges.put(graceOnInterestChargedParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.graceOnInterestCharged = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.graceOnArrearsAgeingParameterName, this.graceOnArrearsAgeing)) {
+            final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.graceOnArrearsAgeingParameterName);
+            actualChanges.put(LoanProductConstants.graceOnArrearsAgeingParameterName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.graceOnArrearsAgeing = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.daysInMonthTypeParameterName, this.daysInMonthType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.daysInMonthTypeParameterName);
+            actualChanges.put(LoanProductConstants.daysInMonthTypeParameterName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.daysInMonthType = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.daysInYearTypeParameterName, this.daysInYearType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.daysInYearTypeParameterName);
+            actualChanges.put(LoanProductConstants.daysInYearTypeParameterName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.daysInYearType = newValue;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isInterestRecalculationEnabledParameterName,
+                this.isInterestRecalculationEnabled)) {
+            final boolean newValue = command
+                    .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isInterestRecalculationEnabledParameterName);
+            actualChanges.put(LoanProductConstants.isInterestRecalculationEnabledParameterName, newValue);
+            this.isInterestRecalculationEnabled = newValue;
+        }
+
+        validateRepaymentPeriodWithGraceSettings();
+
+        return actualChanges;
+    }
+
+    public void validateRepaymentPeriodWithGraceSettings() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanproduct");
+
+        if (this.numberOfRepayments <= defaultToZeroIfNull(this.graceOnPrincipalPayment)) {
+            baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(this.graceOnPrincipalPayment)
+                    .failWithCode(".mustBeLessThan.numberOfRepayments");
+        }
+
+        if (this.numberOfRepayments <= defaultToZeroIfNull(this.graceOnInterestPayment)) {
+            baseDataValidator.reset().parameter("graceOnInterestPayment").value(this.graceOnInterestPayment)
+                    .failWithCode(".mustBeLessThan.numberOfRepayments");
+        }
+
+        if (this.numberOfRepayments < defaultToZeroIfNull(this.graceOnInterestCharged)) {
+            baseDataValidator.reset().parameter("graceOnInterestCharged").value(this.graceOnInterestCharged)
+                    .failWithCode(".mustBeLessThan.numberOfRepayments");
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private Integer defaultToZeroIfNull(final Integer value) {
+        Integer result = value;
+        if (value == null) {
+            result = Integer.valueOf(0);
+        }
+        return result;
+    }
+
+    private void updateInterestRateDerivedFields(final AprCalculator aprCalculator) {
+        this.annualNominalInterestRate = aprCalculator.calculateFrom(this.interestPeriodFrequencyType, this.nominalInterestRatePerPeriod);
+    }
+
+    public boolean hasCurrencyCodeOf(final String currencyCode) {
+        return this.currency.getCode().equalsIgnoreCase(currencyCode);
+    }
+
+    public void updatenterestPeriodFrequencyType(final PeriodFrequencyType interestPeriodFrequencyType) {
+        this.interestPeriodFrequencyType = interestPeriodFrequencyType;
+    }
+
+    @Override
+    public Integer getGraceOnDueDate() {
+        return this.graceOnArrearsAgeing;
+    }
+
+    public DaysInMonthType fetchDaysInMonthType() {
+        return DaysInMonthType.fromInt(this.daysInMonthType);
+    }
+
+    public DaysInYearType fetchDaysInYearType() {
+        return DaysInYearType.fromInt(this.daysInYearType);
+    }
+
+    public boolean isInterestRecalculationEnabled() {
+        return this.isInterestRecalculationEnabled;
+    }
+
+    public void updateIsInterestRecalculationEnabled(final boolean isInterestRecalculationEnabled) {
+        this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
+    }
+
+    public void updateNumberOfRepayments(Integer numberOfRepayments) {
+        this.numberOfRepayments = numberOfRepayments;
+    }
+
+    public Integer getGraceOnPrincipalPayment() {
+        return graceOnPrincipalPayment;
+    }
+
+    public void setGraceOnPrincipalPayment(Integer graceOnPrincipalPayment) {
+        this.graceOnPrincipalPayment = graceOnPrincipalPayment;
+    }
+
+    public Integer getGraceOnInterestPayment() {
+        return graceOnInterestPayment;
+    }
+
+    public void setGraceOnInterestPayment(Integer graceOnInterestPayment) {
+        this.graceOnInterestPayment = graceOnInterestPayment;
+    }
+
+    public Integer getGraceOnArrearsAgeing() {
+        return graceOnArrearsAgeing;
+    }
+
+    public void setGraceOnArrearsAgeing(Integer graceOnArrearsAgeing) {
+        this.graceOnArrearsAgeing = graceOnArrearsAgeing;
+    }
+
+    public void setInterestMethod(InterestMethod interestMethod) {
+        this.interestMethod = interestMethod;
+    }
+
+    public void setInterestCalculationPeriodMethod(InterestCalculationPeriodMethod interestCalculationPeriodMethod) {
+        this.interestCalculationPeriodMethod = interestCalculationPeriodMethod;
+    }
+
+    public void setRepayEvery(Integer repayEvery) {
+        this.repayEvery = repayEvery;
+    }
+
+    public void setRepaymentPeriodFrequencyType(PeriodFrequencyType repaymentPeriodFrequencyType) {
+        this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType;
+    }
+
+    public void setAmortizationMethod(AmortizationMethod amortizationMethod) {
+        this.amortizationMethod = amortizationMethod;
+    }
+
+    public void setInArrearsTolerance(BigDecimal inArrearsTolerance) {
+        this.inArrearsTolerance = inArrearsTolerance;
+    }
+
+    public BigDecimal getArrearsTolerance() {
+        return this.inArrearsTolerance;
+    }
+
+    public void updateForFloatingInterestRates() {
+        this.nominalInterestRatePerPeriod = null;
+        this.interestPeriodFrequencyType = PeriodFrequencyType.INVALID;
+        this.annualNominalInterestRate = null;
+    }
+
+    public boolean isAllowPartialPeriodInterestCalcualtion() {
+        return this.allowPartialPeriodInterestCalcualtion;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java
new file mode 100644
index 0000000..2d540ce
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanproduct.domain;
+
+import java.util.List;
+
+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 LoanProductRepository extends JpaRepository<LoanProduct, Long>, JpaSpecificationExecutor<LoanProduct> {
+
+    @Query("select loanProduct from LoanProduct loanProduct, IN(loanProduct.charges) charge where charge.id = :chargeId")
+    List<LoanProduct> retrieveLoanProductsByChargeId(@Param("chargeId") Long chargeId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductTrancheDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductTrancheDetails.java
new file mode 100644
index 0000000..c12e742
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductTrancheDetails.java
@@ -0,0 +1,92 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+
+@Embeddable
+public class LoanProductTrancheDetails {
+
+    @Column(name = "allow_multiple_disbursals")
+    private boolean multiDisburseLoan;
+
+    @Column(name = "max_disbursals", nullable = false)
+    private Integer maxTrancheCount;
+
+    @Column(name = "max_outstanding_loan_balance", scale = 6, precision = 19, nullable = false)
+    private BigDecimal outstandingLoanBalance;
+
+    protected LoanProductTrancheDetails() {
+        // TODO Auto-generated constructor stub
+    }
+
+    public LoanProductTrancheDetails(final boolean multiDisburseLoan, final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance) {
+        this.multiDisburseLoan = multiDisburseLoan;
+        this.maxTrancheCount = maxTrancheCount;
+        this.outstandingLoanBalance = outstandingLoanBalance;
+    }
+
+    public void update(final JsonCommand command, final Map<String, Object> actualChanges, final String localeAsInput) {
+        if (command.isChangeInBooleanParameterNamed(LoanProductConstants.multiDisburseLoanParameterName, this.multiDisburseLoan)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.multiDisburseLoanParameterName);
+            actualChanges.put(LoanProductConstants.multiDisburseLoanParameterName, newValue);
+            this.multiDisburseLoan = newValue;
+        }
+
+        if (this.multiDisburseLoan) {
+            if (command.isChangeInIntegerParameterNamed(LoanProductConstants.maxTrancheCountParameterName, this.maxTrancheCount)) {
+                final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.maxTrancheCountParameterName);
+                actualChanges.put(LoanProductConstants.maxTrancheCountParameterName, newValue);
+                this.maxTrancheCount = newValue;
+            }
+
+            if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.outstandingLoanBalanceParameterName,
+                    this.outstandingLoanBalance)) {
+                final BigDecimal newValue = command
+                        .bigDecimalValueOfParameterNamed(LoanProductConstants.outstandingLoanBalanceParameterName);
+                actualChanges.put(LoanProductConstants.outstandingLoanBalanceParameterName, newValue);
+                actualChanges.put(LoanProductConstants.outstandingLoanBalanceParameterName, localeAsInput);
+                this.outstandingLoanBalance = newValue;
+            }
+        } else {
+            this.maxTrancheCount = null;
+            this.outstandingLoanBalance = null;
+        }
+    }
+
+    public boolean isMultiDisburseLoan() {
+        return this.multiDisburseLoan;
+    }
+
+    public BigDecimal outstandingLoanBalance() {
+        return this.outstandingLoanBalance;
+    }
+
+    public Integer maxTrancheCount() {
+        return this.maxTrancheCount;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductValueConditionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductValueConditionType.java
new file mode 100755
index 0000000..5c701a9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductValueConditionType.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.loanproduct.domain;
+
+public enum LoanProductValueConditionType {
+
+    INVALID(0, "LoanProductParamType.invalid"), //
+    // LESSTHAN(1,"LoanProductValueConditionType.lessthan"),//
+    EQUAL(2, "LoanProductValueConditionType.equal"), //
+    GREATERTHAN(3, "LoanProductValueConditionType.greaterThan"); //
+
+    private final Integer value;
+    private final String code;
+
+    private LoanProductValueConditionType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static LoanProductValueConditionType fromInt(final Integer type) {
+        LoanProductValueConditionType loanProductParamType = LoanProductValueConditionType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 2:
+                    loanProductParamType = EQUAL;
+                break;
+                case 3:
+                    loanProductParamType = GREATERTHAN;
+                break;
+                default:
+                    loanProductParamType = INVALID;
+                break;
+            }
+        }
+        return loanProductParamType;
+    }
+
+    public boolean isValueConditionTypeEqual() {
+        return LoanProductValueConditionType.EQUAL.getValue().equals(this.value);
+    }
+
+    public boolean isValueConditionTypeGreterThan() {
+        return LoanProductValueConditionType.GREATERTHAN.getValue().equals(this.value);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductVariableInstallmentConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductVariableInstallmentConfig.java
new file mode 100644
index 0000000..538e452
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductVariableInstallmentConfig.java
@@ -0,0 +1,88 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_product_loan_variable_installment_config")
+public class LoanProductVariableInstallmentConfig extends AbstractPersistable<Long> {
+
+    @OneToOne
+    @JoinColumn(name = "loan_product_id", nullable = false)
+    private LoanProduct loanProduct;
+
+    @Column(name = "minimum_gap")
+    private Integer minimumGap;
+
+    @Column(name = "maximum_gap")
+    private Integer maximumGap;
+
+    protected LoanProductVariableInstallmentConfig() {
+
+    }
+
+    public LoanProductVariableInstallmentConfig(final LoanProduct loanProduct, final Integer minimumGap, final Integer maximumGap) {
+        this.loanProduct = loanProduct;
+        this.minimumGap = minimumGap;
+        this.maximumGap = maximumGap;
+    }
+
+    public void setLoanProduct(final LoanProduct loanProduct) {
+        this.loanProduct = loanProduct;
+    }
+
+    public Map<? extends String, ? extends Object> update(JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(3);
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.minimumGapBetweenInstallments, this.minimumGap)) {
+            final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.minimumGapBetweenInstallments);
+            actualChanges.put(LoanProductConstants.minimumGapBetweenInstallments, newValue);
+            this.minimumGap = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(LoanProductConstants.maximumGapBetweenInstallments, this.maximumGap)) {
+            final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.maximumGapBetweenInstallments);
+            actualChanges.put(LoanProductConstants.maximumGapBetweenInstallments, newValue);
+            this.maximumGap = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    public Integer getMinimumGap() {
+        return this.minimumGap;
+    }
+
+    public Integer getMaximumGap() {
+        return this.maximumGap;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java
new file mode 100644
index 0000000..0c81e99
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.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.loanproduct.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/***
+ * <ul>
+ * People typically use either of the following settings when defining interest
+ * recalculation method:
+ * <li>RESCHEDULE_NEXT_REPAYMENTS</li>
+ * <li>REDUCE_NUMBER_OF_INSTALLMENTS</li>
+ * <li>REDUCE_EMI_AMOUNT</li>
+ * </ul>
+ */
+
+public enum LoanRescheduleStrategyMethod {
+
+    INVALID(0, "loanRescheduleStrategyMethod.invalid"), //
+    RESCHEDULE_NEXT_REPAYMENTS(1, "loanRescheduleStrategyMethod.reschedule.next.repayments"), //
+    REDUCE_NUMBER_OF_INSTALLMENTS(2, "loanRescheduleStrategyMethod.reduce.number.of.installments"), //
+    REDUCE_EMI_AMOUNT(3, "loanRescheduleStrategyMethod.reduce.emi.amount");
+
+    private final Integer value;
+    private final String code;
+    private static final Map<Integer, LoanRescheduleStrategyMethod> intToEnumMap = new HashMap<>();
+
+    static {
+        for (final LoanRescheduleStrategyMethod type : LoanRescheduleStrategyMethod.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static LoanRescheduleStrategyMethod fromInt(final Integer ruleTypeValue) {
+        final LoanRescheduleStrategyMethod type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private LoanRescheduleStrategyMethod(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanTransactionProcessingStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanTransactionProcessingStrategy.java
new file mode 100644
index 0000000..775f4b9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanTransactionProcessingStrategy.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.portfolio.loanproduct.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "ref_loan_transaction_processing_strategy")
+public class LoanTransactionProcessingStrategy extends AbstractPersistable<Long> {
+
+    @Column(name = "code", unique = true)
+    private String code;
+
+    @Column(name = "name")
+    private String name;
+
+    @Column(name = "sort_order")
+    private Integer sortOrder ; //Don't change this name as this property name is used as sort order while retrieving this objects
+   
+    protected LoanTransactionProcessingStrategy() {
+        //
+    }
+
+    public TransactionProcessingStrategyData toData() {
+        return new TransactionProcessingStrategyData(getId(), this.code, this.name);
+    }
+
+    public boolean isStandardStrategy() {
+        return "mifos-standard-strategy".equalsIgnoreCase(this.code);
+    }
+
+    public boolean isHeavensfamilyStrategy() {
+        return "heavensfamily-strategy".equalsIgnoreCase(this.code);
+    }
+
+    public boolean isEarlyPaymentStrategy() {
+        return "early-repayment-strategy".equalsIgnoreCase(this.code);
+    }
+
+    public boolean isCreocoreStrategy() {
+        return "creocore-strategy".equalsIgnoreCase(this.code);
+    }
+
+    public boolean isIndianRBIStrategy() {
+        return "rbi-india-strategy".equalsIgnoreCase(this.code);
+    }
+
+    public boolean isPrincipalInterestPenaltiesFeesOrderStrategy() {
+        return "principal-interest-penalties-fees-order-strategy".equalsIgnoreCase(this.code);
+    }
+
+    public boolean isInterestPrincipalPenaltiesFeesOrderStrategy() {
+        return "interest-principal-penalties-fees-order-strategy".equalsIgnoreCase(this.code);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/RecalculationFrequencyType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/RecalculationFrequencyType.java
new file mode 100644
index 0000000..75375c4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/RecalculationFrequencyType.java
@@ -0,0 +1,96 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+
+public enum RecalculationFrequencyType {
+    INVALID(0, "interestRecalculationFrequencyType.invalid"), //
+    SAME_AS_REPAYMENT_PERIOD(1, "interestRecalculationFrequencyType.same.as.repayment.period"), //
+    DAILY(2, "interestRecalculationFrequencyType.daily"), //
+    WEEKLY(3, "interestRecalculationFrequencyType.weekly"), //
+    MONTHLY(4, "interestRecalculationFrequencyType.monthly");
+
+    private final Integer value;
+    private final String code;
+    private static final Map<Integer, RecalculationFrequencyType> intToEnumMap = new HashMap<>();
+
+    static {
+        for (final RecalculationFrequencyType type : RecalculationFrequencyType.values()) {
+            intToEnumMap.put(type.value, type);
+        }
+    }
+
+    public static RecalculationFrequencyType fromInt(final Integer ruleTypeValue) {
+        if (ruleTypeValue == null) { return RecalculationFrequencyType.INVALID; }
+        final RecalculationFrequencyType type = intToEnumMap.get(ruleTypeValue);
+        return type;
+    }
+
+    private RecalculationFrequencyType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isSameAsRepayment() {
+        return this.value.equals(RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD.getValue());
+    }
+
+    public boolean isDaily() {
+        return this.value.equals(RecalculationFrequencyType.DAILY.getValue());
+    }
+
+    public boolean isWeekly() {
+        return this.value.equals(RecalculationFrequencyType.WEEKLY.getValue());
+    }
+
+    public boolean isMonthly() {
+        return this.value.equals(RecalculationFrequencyType.MONTHLY.getValue());
+    }
+
+    public boolean isSameFrequency(final PeriodFrequencyType frequencyType) {
+        boolean isSameFre = false;
+        switch (this) {
+            case DAILY:
+                isSameFre = frequencyType.isDaily();
+            break;
+            case MONTHLY:
+                isSameFre = frequencyType.isMonthly();
+            break;
+            case WEEKLY:
+                isSameFre = frequencyType.isWeekly();
+            break;
+            default:
+            break;
+        }
+
+        return isSameFre;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/InvalidCurrencyException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/InvalidCurrencyException.java
new file mode 100644
index 0000000..545f41d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/InvalidCurrencyException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.loanproduct.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when currency mismatch
+ * occurs
+ */
+public class InvalidCurrencyException extends AbstractPlatformDomainRuleException {
+
+    public InvalidCurrencyException(final String entity, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + "." + postFix + ".invalid.currency", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/InvalidLendingStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/InvalidLendingStrategy.java
new file mode 100644
index 0000000..7d4853d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/InvalidLendingStrategy.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanproduct.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when lending strategy
+ * mismatch occurs
+ */
+public class InvalidLendingStrategy extends AbstractPlatformDomainRuleException {
+
+    public InvalidLendingStrategy(final Integer strategyId) {
+        super("error.msg.unsupported.lending.strategy", "Stratagy code [" + strategyId + "] passed is not valid.");
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LinkedAccountRequiredException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LinkedAccountRequiredException.java
new file mode 100755
index 0000000..f81816b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LinkedAccountRequiredException.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.loanproduct.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LinkedAccountRequiredException extends AbstractPlatformDomainRuleException {
+
+    public LinkedAccountRequiredException(final String entity, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + ".requires.linked.account", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LoanProductCannotBeModifiedDueToNonClosedLoansException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LoanProductCannotBeModifiedDueToNonClosedLoansException.java
new file mode 100644
index 0000000..5b68a4f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LoanProductCannotBeModifiedDueToNonClosedLoansException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanproduct.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanProductCannotBeModifiedDueToNonClosedLoansException  extends AbstractPlatformDomainRuleException {
+    public LoanProductCannotBeModifiedDueToNonClosedLoansException(final Long id) {
+        super("error.msg.loanproduct.not.modifiable.due.to.non.closed.loans", 
+        		"Loan product with identifier " + id + " cannot be modified due to non closed loans associated with it.", id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LoanProductDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LoanProductDateException.java
new file mode 100644
index 0000000..cdb08ea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LoanProductDateException.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.loanproduct.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class LoanProductDateException extends AbstractPlatformDomainRuleException {
+
+    public LoanProductDateException(final Object... defaultUserMessageArgs) {
+        super("error.msg.loan.product.close.date.cannot.be.before.start.date.close.date",
+                "Loan product close date cannot be before the start date", defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LoanProductNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LoanProductNotFoundException.java
new file mode 100644
index 0000000..9483037
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/LoanProductNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanproduct.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when loan product resources are not found.
+ */
+public class LoanProductNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public LoanProductNotFoundException(final Long id) {
+        super("error.msg.loanproduct.id.invalid", "Loan product with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/NotInMinMaxRangeException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/NotInMinMaxRangeException.java
new file mode 100644
index 0000000..4b36b5e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/exception/NotInMinMaxRangeException.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.portfolio.loanproduct.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when a value is not within
+ * minimum and maximum values.
+ * 
+ */
+public class NotInMinMaxRangeException extends AbstractPlatformDomainRuleException {
+
+    public NotInMinMaxRangeException(final String entity, final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("validation.msg." + entity + "." + postFix + ".is.not.within.min.max.range", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/CreateLoanProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/CreateLoanProductCommandHandler.java
new file mode 100644
index 0000000..0561cef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/CreateLoanProductCommandHandler.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.portfolio.loanproduct.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOANPRODUCT", action = "CREATE")
+public class CreateLoanProductCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanProductWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateLoanProductCommandHandler(final LoanProductWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createLoanProduct(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/UpdateLoanProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/UpdateLoanProductCommandHandler.java
new file mode 100644
index 0000000..9cafd8e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/UpdateLoanProductCommandHandler.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.portfolio.loanproduct.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "LOANPRODUCT", action = "UPDATE")
+public class UpdateLoanProductCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanProductWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateLoanProductCommandHandler(final LoanProductWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateLoanProduct(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/api/ProductMixApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/api/ProductMixApiResource.java
new file mode 100644
index 0000000..2ea8a82
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/api/ProductMixApiResource.java
@@ -0,0 +1,142 @@
+/**
+ * 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.loanproduct.productmix.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.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.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixData;
+import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixReadPlatformService;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/loanproducts/{productId}/productmix")
+@Component
+@Scope("singleton")
+public class ProductMixApiResource {
+
+    private final String resourceNameForPermissions = "PRODUCTMIX";
+
+    private final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(Arrays.asList("productId", "productName",
+            "restrictedProducts", "allowedProducts", "productOptions"));
+
+    private final PlatformSecurityContext context;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final DefaultToApiJsonSerializer<ProductMixData> toApiJsonSerializer;
+
+    private final ProductMixReadPlatformService productMixReadPlatformService;
+    private final LoanProductReadPlatformService loanProductReadPlatformService;
+
+    @Autowired
+    public ProductMixApiResource(final PlatformSecurityContext context,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final DefaultToApiJsonSerializer<ProductMixData> toApiJsonSerializer,
+            final ProductMixReadPlatformService productMixReadPlatformService,
+            final LoanProductReadPlatformService loanProductReadPlatformService) {
+        this.context = context;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.productMixReadPlatformService = productMixReadPlatformService;
+        this.loanProductReadPlatformService = loanProductReadPlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@PathParam("productId") final Long productId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        ProductMixData productMixData = this.productMixReadPlatformService.retrieveLoanProductMixDetails(productId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            final Collection<LoanProductData> productOptions = this.loanProductReadPlatformService.retrieveAvailableLoanProductsForMix();
+            productMixData = ProductMixData.withTemplateOptions(productMixData, productOptions);
+        }
+        return this.toApiJsonSerializer.serialize(settings, productMixData, this.PRODUCT_MIX_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createProductMix(@PathParam("productId") final Long productId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createProductMix(productId).withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateProductMix(@PathParam("productId") final Long productId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateProductMix(productId).withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteProductMix(@PathParam("productId") final Long productId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteProductMix(productId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixData.java
new file mode 100644
index 0000000..8ca4212
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/data/ProductMixData.java
@@ -0,0 +1,62 @@
+/**
+ * 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.loanproduct.productmix.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+
+public class ProductMixData {
+
+    private final Long productId;
+    private final String productName;
+    private final Collection<LoanProductData> restrictedProducts;
+    private final Collection<LoanProductData> allowedProducts;
+    @SuppressWarnings("unused")
+    private final Collection<LoanProductData> productOptions;
+
+    public ProductMixData(final Long productId, final String productName, final Collection<LoanProductData> restrictedProducts,
+            final Collection<LoanProductData> allowedProducts, final Collection<LoanProductData> productOptions) {
+        this.productId = productId;
+        this.productName = productName;
+        this.restrictedProducts = restrictedProducts;
+        this.allowedProducts = allowedProducts;
+        this.productOptions = productOptions;
+    }
+
+    public static ProductMixData template(final Collection<LoanProductData> productOptions) {
+        return new ProductMixData(null, null, null, null, productOptions);
+    }
+
+    public static ProductMixData withTemplateOptions(final ProductMixData productMixData, final Collection<LoanProductData> productOptions) {
+        return new ProductMixData(productMixData.productId, productMixData.productName, productMixData.restrictedProducts,
+                productMixData.allowedProducts, productOptions);
+    }
+
+    public static ProductMixData withDetails(final Long productId, final String productName,
+            final Collection<LoanProductData> restrictedProducts, final Collection<LoanProductData> allowedProducts) {
+        return new ProductMixData(productId, productName, restrictedProducts, allowedProducts, null);
+    }
+
+    public static ProductMixData withRestrictedOptions(final Collection<LoanProductData> restrictedProducts,
+            final Collection<LoanProductData> allowedProducts) {
+        return new ProductMixData(null, null, restrictedProducts, allowedProducts, null);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/domain/ProductMix.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/domain/ProductMix.java
new file mode 100644
index 0000000..c042761
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/domain/ProductMix.java
@@ -0,0 +1,62 @@
+/**
+ * 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.loanproduct.productmix.domain;
+
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_product_mix")
+public class ProductMix extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "product_id", nullable = false)
+    private LoanProduct product;
+
+    @ManyToOne
+    @JoinColumn(name = "restricted_product_id", nullable = false)
+    private LoanProduct restrictedProduct;
+
+    public ProductMix() {
+        //
+    }
+
+    private ProductMix(final LoanProduct product, final LoanProduct restrictedProduct) {
+        this.product = product;
+        this.restrictedProduct = restrictedProduct;
+    }
+
+    public static ProductMix createNew(final LoanProduct product, final LoanProduct restrictedProduct) {
+        return new ProductMix(product, restrictedProduct);
+    }
+
+    public Long getRestrictedProductId() {
+        return this.restrictedProduct.getId();
+    }
+
+    public Long getProductId() {
+        return this.product.getId();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/domain/ProductMixRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/domain/ProductMixRepository.java
new file mode 100644
index 0000000..f4fb6d9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/domain/ProductMixRepository.java
@@ -0,0 +1,43 @@
+/**
+ * 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.loanproduct.productmix.domain;
+
+import java.util.List;
+
+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 ProductMixRepository extends JpaRepository<ProductMix, Long>, JpaSpecificationExecutor<ProductMix> {
+
+    public static final String GET_PRODUCTMIXES_BY_PRODUCTID_SCHEMA = "from ProductMix pm where pm.product.id = :productId";
+    public static final String GET_RESTRICTED_PRODUCTIDS_SCHEMA = "Select pm.restrictedProduct.id from ProductMix pm where pm.product.id = :productId";
+    public static final String GET_RESTRICTED_PRODUCTS_SCHEMA = "from ProductMix pm where pm.restrictedProduct.id = :restrictedProductId";
+
+    @Query(GET_PRODUCTMIXES_BY_PRODUCTID_SCHEMA)
+    List<ProductMix> findByProductId(@Param("productId") Long productId);
+
+    @Query(GET_RESTRICTED_PRODUCTIDS_SCHEMA)
+    List<Long> findRestrictedProductIdsByProductId(@Param("productId") Long productId);
+
+    @Query(GET_RESTRICTED_PRODUCTS_SCHEMA)
+    List<ProductMix> findRestrictedProducts(@Param("restrictedProductId") Long restrictedProductId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/exception/ProductMixNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/exception/ProductMixNotFoundException.java
new file mode 100644
index 0000000..6606d63
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/exception/ProductMixNotFoundException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.loanproduct.productmix.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when no product mixes found with the
+ * productId.
+ */
+public class ProductMixNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ProductMixNotFoundException(final Long productId) {
+        super("error.msg.no.product.mixes.exists", "No product mixes are defined with the productId `" + productId + "`.", productId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/CreateProductMixCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/CreateProductMixCommandHandler.java
new file mode 100644
index 0000000..425af7d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/CreateProductMixCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.loanproduct.productmix.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PRODUCTMIX", action = "CREATE")
+public class CreateProductMixCommandHandler implements NewCommandSourceHandler {
+
+    private final ProductMixWritePlatformService productMixWritePlatformService;
+
+    @Autowired
+    public CreateProductMixCommandHandler(final ProductMixWritePlatformService productMixWritePlatformService) {
+
+        this.productMixWritePlatformService = productMixWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.productMixWritePlatformService.createProductMix(command.getProductId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/DeleteProductMixCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/DeleteProductMixCommandHandler.java
new file mode 100644
index 0000000..6fc544e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/DeleteProductMixCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.loanproduct.productmix.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PRODUCTMIX", action = "DELETE")
+public class DeleteProductMixCommandHandler implements NewCommandSourceHandler {
+
+    private final ProductMixWritePlatformService productMixWritePlatformService;
+
+    @Autowired
+    public DeleteProductMixCommandHandler(final ProductMixWritePlatformService productMixWritePlatformService) {
+
+        this.productMixWritePlatformService = productMixWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.productMixWritePlatformService.deleteProductMix(command.getProductId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/UpdateProductMixCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/UpdateProductMixCommandHandler.java
new file mode 100644
index 0000000..784b4e1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/handler/UpdateProductMixCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.loanproduct.productmix.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PRODUCTMIX", action = "UPDATE")
+public class UpdateProductMixCommandHandler implements NewCommandSourceHandler {
+
+    private final ProductMixWritePlatformService productMixWritePlatformService;
+
+    @Autowired
+    public UpdateProductMixCommandHandler(final ProductMixWritePlatformService productMixWritePlatformService) {
+
+        this.productMixWritePlatformService = productMixWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.productMixWritePlatformService.updateProductMix(command.getProductId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/serialization/ProductMixDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/serialization/ProductMixDataValidator.java
new file mode 100644
index 0000000..0df9bcb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/serialization/ProductMixDataValidator.java
@@ -0,0 +1,104 @@
+/**
+ * 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.loanproduct.productmix.serialization;
+
+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 ProductMixDataValidator {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("restrictedProducts"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ProductMixDataValidator(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("productmix");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String[] restrictedProducts = this.fromApiJsonHelper.extractArrayNamed("restrictedProducts", element);
+        baseDataValidator.reset().parameter("restrictedProducts").value(restrictedProducts).arrayNotEmpty();
+        if (restrictedProducts != null) {
+            validateRestrictedProducts(restrictedProducts, baseDataValidator);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateRestrictedProducts(final String[] restrictedProducts, final DataValidatorBuilder baseDataValidator) {
+        for (final String restrictedId : restrictedProducts) {
+            baseDataValidator.reset().parameter("restrictedProduct").value(restrictedId).notBlank().longGreaterThanZero();
+        }
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", 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("productmix");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final String[] restrictedProducts = this.fromApiJsonHelper.extractArrayNamed("restrictedProducts", element);
+        validateRestrictedProducts(restrictedProducts, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixReadPlatformService.java
new file mode 100644
index 0000000..68730b6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixReadPlatformService.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.loanproduct.productmix.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixData;
+
+public interface ProductMixReadPlatformService {
+
+    ProductMixData retrieveLoanProductMixDetails(Long productId);
+
+    Collection<ProductMixData> retrieveAllProductMixes();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixReadPlatformServiceImpl.java
new file mode 100644
index 0000000..8d4deb4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixReadPlatformServiceImpl.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.loanproduct.productmix.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixData;
+import org.apache.fineract.portfolio.loanproduct.productmix.exception.ProductMixNotFoundException;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ProductMixReadPlatformServiceImpl implements ProductMixReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final LoanProductReadPlatformService loanProductReadPlatformService;
+
+    @Autowired
+    public ProductMixReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final LoanProductReadPlatformService loanProductReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.loanProductReadPlatformService = loanProductReadPlatformService;
+    }
+
+    @Override
+    public ProductMixData retrieveLoanProductMixDetails(final Long productId) {
+        try {
+
+            this.context.authenticatedUser();
+
+            final ProductMixDataExtractor extractor = new ProductMixDataExtractor(this.loanProductReadPlatformService, productId);
+
+            final String sql = "Select " + extractor.schema() + " where pm.product_id=? group by pm.product_id";
+
+            final Map<Long, ProductMixData> productMixData = this.jdbcTemplate.query(sql, extractor, new Object[] { productId });
+
+            return productMixData.get(productId);
+
+        } catch (final EmptyResultDataAccessException e) {
+            throw new ProductMixNotFoundException(productId);
+        }
+    }
+
+    @Override
+    public Collection<ProductMixData> retrieveAllProductMixes() {
+
+        this.context.authenticatedUser();
+
+        final ProductMixDataExtractor extractor = new ProductMixDataExtractor(this.loanProductReadPlatformService, null);
+
+        final String sql = "Select " + extractor.schema() + " group by pm.product_id";
+
+        final Map<Long, ProductMixData> productMixData = this.jdbcTemplate.query(sql, extractor, new Object[] {});
+
+        return productMixData.values();
+    }
+
+    private static final class ProductMixDataExtractor implements ResultSetExtractor<Map<Long, ProductMixData>> {
+
+        private final LoanProductReadPlatformService loanProductReadPlatformService;
+        private final Long productId;
+
+        public String schema() {
+            return "pm.product_id as productId, lp.name as name from m_product_mix pm join m_product_loan lp on lp.id=pm.product_id";
+        }
+
+        public ProductMixDataExtractor(final LoanProductReadPlatformService loanProductReadPlatformService, final Long productId) {
+            this.loanProductReadPlatformService = loanProductReadPlatformService;
+            this.productId = productId;
+        }
+
+        @Override
+        public Map<Long, ProductMixData> extractData(final ResultSet rs) throws SQLException, DataAccessException {
+            final Map<Long, ProductMixData> extractedData = new HashMap<>();
+
+            if (!rs.next()) {
+                final Collection<LoanProductData> restrictedProducts = this.loanProductReadPlatformService
+                        .retrieveRestrictedProductsForMix(this.productId);
+                final Collection<LoanProductData> allowedProducts = this.loanProductReadPlatformService
+                        .retrieveAllowedProductsForMix(this.productId);
+                final ProductMixData productMixData = ProductMixData.withRestrictedOptions(restrictedProducts, allowedProducts);
+                extractedData.put(this.productId, productMixData);
+                return extractedData;
+            }
+            /* move the cursor to starting of resultset */
+            rs.beforeFirst();
+            while (rs.next()) {
+                final Long productId = rs.getLong("productId");
+                final String name = rs.getString("name");
+                final Collection<LoanProductData> restrictedProducts = this.loanProductReadPlatformService
+                        .retrieveRestrictedProductsForMix(productId);
+                final Collection<LoanProductData> allowedProducts = this.loanProductReadPlatformService
+                        .retrieveAllowedProductsForMix(productId);
+                final ProductMixData productMixData = ProductMixData.withDetails(productId, name, restrictedProducts, allowedProducts);
+                extractedData.put(productId, productMixData);
+            }
+            return extractedData;
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformService.java
new file mode 100644
index 0000000..a5926d3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformService.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanproduct.productmix.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface ProductMixWritePlatformService {
+
+    CommandProcessingResult createProductMix(Long productId, JsonCommand command);
+
+    CommandProcessingResult updateProductMix(Long productId, JsonCommand command);
+
+    CommandProcessingResult deleteProductMix(Long productId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..de93d1a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/productmix/service/ProductMixWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,244 @@
+/**
+ * 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.loanproduct.productmix.service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.apache.fineract.portfolio.loanproduct.productmix.domain.ProductMix;
+import org.apache.fineract.portfolio.loanproduct.productmix.domain.ProductMixRepository;
+import org.apache.fineract.portfolio.loanproduct.productmix.exception.ProductMixNotFoundException;
+import org.apache.fineract.portfolio.loanproduct.productmix.serialization.ProductMixDataValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class ProductMixWritePlatformServiceJpaRepositoryImpl implements ProductMixWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ProductMixWritePlatformServiceJpaRepositoryImpl.class);
+    private final PlatformSecurityContext context;
+    private final ProductMixDataValidator fromApiJsonDeserializer;
+    private final ProductMixRepository productMixRepository;
+    private final LoanProductRepository productRepository;
+
+    @Autowired
+    public ProductMixWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final ProductMixDataValidator fromApiJsonDeserializer, final ProductMixRepository productMixRepository,
+            final LoanProductRepository productRepository) {
+        this.context = context;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.productMixRepository = productMixRepository;
+        this.productRepository = productRepository;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createProductMix(final Long productId, final JsonCommand command) {
+
+        try {
+
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final Set<String> restrictedIds = new HashSet<>(Arrays.asList(command.arrayValueOfParameterNamed("restrictedProducts")));
+
+            // remove the existed restriction if it is not exists in
+            // restrictedIds.
+            final List<Long> removedRestrictions = updateRestrictionsForProduct(productId, restrictedIds);
+            final Map<Long, LoanProduct> restrictedProductsAsMap = getRestrictedProducts(restrictedIds);
+            final List<ProductMix> productMixes = new ArrayList<>();
+
+            createNewProductMix(restrictedProductsAsMap, productId, productMixes);
+
+            this.productMixRepository.save(productMixes);
+
+            final Map<String, Object> changes = new LinkedHashMap<>();
+            changes.put("restrictedProductsForMix", restrictedProductsAsMap.keySet());
+            changes.put("removedProductsForMix", removedRestrictions);
+            return new CommandProcessingResultBuilder().withProductId(productId).with(changes).withCommandId(command.commandId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+
+            handleDataIntegrityIssues(dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private List<Long> updateRestrictionsForProduct(final Long productId, final Set<String> restrictedIds) {
+
+        final List<Long> removedRestrictions = new ArrayList<>();
+        final List<ProductMix> mixesToRemove = new ArrayList<>();
+
+        final List<ProductMix> existedProductMixes = this.productMixRepository.findRestrictedProducts(productId);
+        for (final ProductMix productMix : existedProductMixes) {
+            if (!restrictedIds.contains(productMix.getProductId().toString())) {
+                mixesToRemove.add(productMix);
+                removedRestrictions.add(productMix.getId());
+            }
+        }
+        if (!CollectionUtils.isEmpty(mixesToRemove)) {
+            this.productMixRepository.delete(mixesToRemove);
+        }
+        return removedRestrictions;
+    }
+
+    private void createNewProductMix(final Map<Long, LoanProduct> restrictedProductsAsMap, final Long productId,
+            final List<ProductMix> productMixes) {
+
+        final LoanProduct productMixInstance = findByProductIdIfProvided(productId);
+        for (final LoanProduct restrictedProduct : restrictedProductsAsMap.values()) {
+            final ProductMix productMix = ProductMix.createNew(productMixInstance, restrictedProduct);
+            productMixes.add(productMix);
+        }
+    }
+
+    @Override
+    public CommandProcessingResult updateProductMix(final Long productId, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+            final Map<String, Object> changes = new LinkedHashMap<>();
+
+            final List<ProductMix> existedProductMixes = this.productMixRepository.findByProductId(productId);
+            if (CollectionUtils.isEmpty(existedProductMixes)) { throw new ProductMixNotFoundException(productId); }
+            final Set<String> restrictedIds = new HashSet<>(Arrays.asList(command.arrayValueOfParameterNamed("restrictedProducts")));
+
+            // updating with empty array means deleting the existed records.
+            if (restrictedIds.isEmpty()) {
+                final List<Long> removedRestrictedProductIds = this.productMixRepository.findRestrictedProductIdsByProductId(productId);
+                this.productMixRepository.delete(existedProductMixes);
+                changes.put("removedProductsForMix", removedRestrictedProductIds);
+                return new CommandProcessingResultBuilder().with(changes).withProductId(productId).withCommandId(command.commandId())
+                        .build();
+            }
+
+            /*
+             * if restrictedProducts array is not empty delete the duplicate ids
+             * which are already exists and update existedProductMixes
+             */
+            final List<ProductMix> productMixesToRemove = updateRestrictedIds(restrictedIds, existedProductMixes);
+            final Map<Long, LoanProduct> restrictedProductsAsMap = getRestrictedProducts(restrictedIds);
+            createNewProductMix(restrictedProductsAsMap, productId, existedProductMixes);
+
+            this.productMixRepository.save(existedProductMixes);
+            changes.put("restrictedProductsForMix", getProductIdsFromCollection(existedProductMixes));
+
+            if (!CollectionUtils.isEmpty(productMixesToRemove)) {
+                this.productMixRepository.delete(productMixesToRemove);
+                changes.put("removedProductsForMix", getProductIdsFromCollection(productMixesToRemove));
+            }
+            return new CommandProcessingResultBuilder().with(changes).withProductId(productId).withCommandId(command.commandId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+
+            handleDataIntegrityIssues(dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private LoanProduct findByProductIdIfProvided(final Long productId) {
+
+        final LoanProduct product = this.productRepository.findOne(productId);
+        if (product == null) { throw new LoanProductNotFoundException(productId); }
+        return product;
+    }
+
+    private Map<Long, LoanProduct> getRestrictedProducts(final Set<String> restrictedIds) {
+
+        final Map<Long, LoanProduct> restricrtedProducts = new HashMap<>();
+
+        for (final String restrictedId : restrictedIds) {
+            final Long restrictedIdAsLong = Long.valueOf(restrictedId);
+            final LoanProduct restrictedProduct = findByProductIdIfProvided(Long.valueOf(restrictedId));
+            restricrtedProducts.put(restrictedIdAsLong, restrictedProduct);
+        }
+        return restricrtedProducts;
+    }
+
+    private void handleDataIntegrityIssues(final DataIntegrityViolationException dve) {
+        logAsErrorUnexpectedDataIntegrityException(dve);
+        throw new PlatformDataIntegrityException("error.msg.product.loan.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
+        logger.error(dve.getMessage(), dve);
+    }
+
+    private List<ProductMix> updateRestrictedIds(final Set<String> restrictedIds, final List<ProductMix> existedProductMixes) {
+
+        final List<ProductMix> productMixesToRemove = new ArrayList<>();
+        for (final ProductMix productMix : existedProductMixes) {
+            final String currentMixId = productMix.getRestrictedProductId().toString();
+            if (restrictedIds.contains(currentMixId)) {
+                restrictedIds.remove(currentMixId);
+            } else {
+                productMixesToRemove.add(productMix);
+            }
+        }
+        existedProductMixes.removeAll(productMixesToRemove);
+        return productMixesToRemove;
+    }
+
+    @Override
+    public CommandProcessingResult deleteProductMix(final Long productId) {
+        try {
+            this.context.authenticatedUser();
+            final Map<String, Object> changes = new LinkedHashMap<>();
+
+            final List<ProductMix> existedProductMixes = this.productMixRepository.findByProductId(productId);
+            if (CollectionUtils.isEmpty(existedProductMixes)) { throw new ProductMixNotFoundException(productId); }
+            this.productMixRepository.delete(existedProductMixes);
+            changes.put("removedProductsForMix", getProductIdsFromCollection(existedProductMixes));
+            return new CommandProcessingResultBuilder().with(changes).withProductId(productId).build();
+        } catch (final DataIntegrityViolationException dve) {
+
+            handleDataIntegrityIssues(dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private List<Long> getProductIdsFromCollection(final List<ProductMix> collection) {
+        final List<Long> productIds = new ArrayList<>();
+        for (final ProductMix productMix : collection) {
+            productIds.add(productMix.getRestrictedProductId());
+        }
+        return productIds;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
new file mode 100755
index 0000000..39b4d62
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -0,0 +1,2022 @@
+/**
+ * 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.loanproduct.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
+import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public final class LoanProductDataValidator {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("locale", "dateFormat", "name", "description", "fundId",
+            "currencyCode", "digitsAfterDecimal", "inMultiplesOf", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery",
+            "numberOfRepayments", "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentFrequencyType", "interestRatePerPeriod",
+            "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType",
+            "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "inArrearsTolerance",
+            "transactionProcessingStrategyId", "graceOnPrincipalPayment", "graceOnInterestPayment", "graceOnInterestCharged", "charges",
+            "accountingRule", "includeInBorrowerCycle", "startDate", "closeDate", "externalId", "isLinkedToFloatingInterestRates",
+            "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate",
+            "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed",
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(),
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(),
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(),
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(),
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(),
+            LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), LoanProductConstants.useBorrowerCycleParameterName,
+            LoanProductConstants.principalVariationsForBorrowerCycleParameterName,
+            LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName,
+            LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName, LoanProductConstants.shortName,
+            LoanProductConstants.multiDisburseLoanParameterName, LoanProductConstants.outstandingLoanBalanceParameterName,
+            LoanProductConstants.maxTrancheCountParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName,
+            LoanProductConstants.overdueDaysForNPAParameterName, LoanProductConstants.isInterestRecalculationEnabledParameterName,
+            LoanProductConstants.daysInYearTypeParameterName, LoanProductConstants.daysInMonthTypeParameterName,
+            LoanProductConstants.rescheduleStrategyMethodParameterName,
+            LoanProductConstants.interestRecalculationCompoundingMethodParameterName,
+            LoanProductConstants.recalculationRestFrequencyDateParamName,
+            LoanProductConstants.recalculationRestFrequencyIntervalParameterName,
+            LoanProductConstants.recalculationRestFrequencyTypeParameterName,
+            LoanProductConstants.recalculationCompoundingFrequencyDateParamName,
+            LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName,
+            LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName,
+            LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName,
+            LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, LoanProductConstants.mandatoryGuaranteeParamName,
+            LoanProductConstants.holdGuaranteeFundsParamName, LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
+            LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, LoanProductConstants.principalThresholdForLastInstallmentParamName,
+            LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, LoanProductConstants.canDefineEmiAmountParamName,
+            LoanProductConstants.installmentAmountInMultiplesOfParamName,
+            LoanProductConstants.preClosureInterestCalculationStrategyParamName, LoanProductConstants.allowAttributeOverridesParamName,
+            LoanProductConstants.allowVariableInstallmentsParamName, LoanProductConstants.minimumGapBetweenInstallments,
+            LoanProductConstants.maximumGapBetweenInstallments));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public LoanProductDataValidator(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("loanproduct");
+
+        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 shortName = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.shortName, element);
+        baseDataValidator.reset().parameter(LoanProductConstants.shortName).value(shortName).notBlank().notExceedingLengthOf(4);
+
+        final String description = this.fromApiJsonHelper.extractStringNamed("description", element);
+        baseDataValidator.reset().parameter("description").value(description).notExceedingLengthOf(500);
+
+        if (this.fromApiJsonHelper.parameterExists("fundId", element)) {
+            final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", element);
+            baseDataValidator.reset().parameter("fundId").value(fundId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, element)) {
+            final Long minimumDaysBetweenDisbursalAndFirstRepayment = this.fromApiJsonHelper.extractLongNamed(
+                    LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment)
+                    .value(minimumDaysBetweenDisbursalAndFirstRepayment).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        final Boolean includeInBorrowerCycle = this.fromApiJsonHelper.extractBooleanNamed("includeInBorrowerCycle", element);
+        baseDataValidator.reset().parameter("includeInBorrowerCycle").value(includeInBorrowerCycle).ignoreIfNull()
+                .validateForBooleanValue();
+
+        // terms
+        final String currencyCode = this.fromApiJsonHelper.extractStringNamed("currencyCode", element);
+        baseDataValidator.reset().parameter("currencyCode").value(currencyCode).notBlank().notExceedingLengthOf(3);
+
+        final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerNamed("digitsAfterDecimal", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("digitsAfterDecimal").value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
+
+        final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerNamed("inMultiplesOf", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("inMultiplesOf").value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
+
+        final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
+        baseDataValidator.reset().parameter("principal").value(principal).positiveAmount();
+
+        final String minPrincipalParameterName = "minPrincipal";
+        BigDecimal minPrincipalAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(minPrincipalParameterName, element)) {
+            minPrincipalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minPrincipalParameterName, element);
+            baseDataValidator.reset().parameter(minPrincipalParameterName).value(minPrincipalAmount).ignoreIfNull().positiveAmount();
+        }
+
+        final String maxPrincipalParameterName = "maxPrincipal";
+        BigDecimal maxPrincipalAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(maxPrincipalParameterName, element)) {
+            maxPrincipalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxPrincipalParameterName, element);
+            baseDataValidator.reset().parameter(maxPrincipalParameterName).value(maxPrincipalAmount).ignoreIfNull().positiveAmount();
+        }
+
+        if (maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) != -1) {
+
+            if (minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) != -1) {
+                baseDataValidator.reset().parameter(maxPrincipalParameterName).value(maxPrincipalAmount).notLessThanMin(minPrincipalAmount);
+                if (minPrincipalAmount.compareTo(maxPrincipalAmount) <= 0 && principal != null) {
+                    baseDataValidator.reset().parameter("principal").value(principal)
+                            .inMinAndMaxAmountRange(minPrincipalAmount, maxPrincipalAmount);
+                }
+            } else if (principal != null) {
+                baseDataValidator.reset().parameter("principal").value(principal).notGreaterThanMax(maxPrincipalAmount);
+            }
+        } else if (minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) != -1 && principal != null) {
+            baseDataValidator.reset().parameter("principal").value(principal).notLessThanMin(minPrincipalAmount);
+        }
+
+        final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
+        baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments).notNull().integerGreaterThanZero();
+
+        final String minNumberOfRepaymentsParameterName = "minNumberOfRepayments";
+        Integer minNumberOfRepayments = null;
+        if (this.fromApiJsonHelper.parameterExists(minNumberOfRepaymentsParameterName, element)) {
+            minNumberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(minNumberOfRepaymentsParameterName, element);
+            baseDataValidator.reset().parameter(minNumberOfRepaymentsParameterName).value(minNumberOfRepayments).ignoreIfNull()
+                    .integerGreaterThanZero();
+        }
+
+        final String maxNumberOfRepaymentsParameterName = "maxNumberOfRepayments";
+        Integer maxNumberOfRepayments = null;
+        if (this.fromApiJsonHelper.parameterExists(maxNumberOfRepaymentsParameterName, element)) {
+            maxNumberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(maxNumberOfRepaymentsParameterName, element);
+            baseDataValidator.reset().parameter(maxNumberOfRepaymentsParameterName).value(maxNumberOfRepayments).ignoreIfNull()
+                    .integerGreaterThanZero();
+        }
+
+        if (maxNumberOfRepayments != null && maxNumberOfRepayments.compareTo(0) == 1) {
+            if (minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1) {
+                baseDataValidator.reset().parameter(maxNumberOfRepaymentsParameterName).value(maxNumberOfRepayments)
+                        .notLessThanMin(minNumberOfRepayments);
+                if (minNumberOfRepayments.compareTo(maxNumberOfRepayments) <= 0) {
+                    baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments)
+                            .inMinMaxRange(minNumberOfRepayments, maxNumberOfRepayments);
+                }
+            } else {
+                baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments)
+                        .notGreaterThanMax(maxNumberOfRepayments);
+            }
+        } else if (minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1) {
+            baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments).notLessThanMin(minNumberOfRepayments);
+        }
+
+        final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element);
+        baseDataValidator.reset().parameter("repaymentEvery").value(repaymentEvery).notNull().integerGreaterThanZero();
+
+        final Integer repaymentFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("repaymentFrequencyType", element,
+                Locale.getDefault());
+        baseDataValidator.reset().parameter("repaymentFrequencyType").value(repaymentFrequencyType).notNull().inMinMaxRange(0, 3);
+
+        // settings
+        final Integer amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("amortizationType").value(amortizationType).notNull().inMinMaxRange(0, 1);
+
+        final Integer interestType = this.fromApiJsonHelper.extractIntegerNamed("interestType", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("interestType").value(interestType).notNull().inMinMaxRange(0, 1);
+
+        final Integer interestCalculationPeriodType = this.fromApiJsonHelper.extractIntegerNamed("interestCalculationPeriodType", element,
+                Locale.getDefault());
+        baseDataValidator.reset().parameter("interestCalculationPeriodType").value(interestCalculationPeriodType).notNull()
+                .inMinMaxRange(0, 1);
+
+        final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
+        baseDataValidator.reset().parameter("inArrearsTolerance").value(inArrearsTolerance).ignoreIfNull().zeroOrPositiveAmount();
+
+        final Long transactionProcessingStrategyId = this.fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element);
+        baseDataValidator.reset().parameter("transactionProcessingStrategyId").value(transactionProcessingStrategyId).notNull()
+                .integerGreaterThanZero();
+
+        // grace validation
+        final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
+        baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment).zeroOrPositiveAmount();
+
+        final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
+        baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment).zeroOrPositiveAmount();
+
+        final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
+        baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged).zeroOrPositiveAmount();
+
+        final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                LoanProductConstants.graceOnArrearsAgeingParameterName, element);
+        baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing)
+                .integerZeroOrGreater();
+
+        final Integer overdueDaysForNPA = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                LoanProductConstants.overdueDaysForNPAParameterName, element);
+        baseDataValidator.reset().parameter(LoanProductConstants.overdueDaysForNPAParameterName).value(overdueDaysForNPA)
+                .integerZeroOrGreater();
+
+        /**
+         * { @link DaysInYearType }
+         */
+        final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInYearTypeParameterName,
+                element, Locale.getDefault());
+        baseDataValidator.reset().parameter(LoanProductConstants.daysInYearTypeParameterName).value(daysInYearType).notNull()
+                .isOneOfTheseValues(1, 360, 364, 365);
+
+        /**
+         * { @link DaysInMonthType }
+         */
+        final Integer daysInMonthType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInMonthTypeParameterName,
+                element, Locale.getDefault());
+        baseDataValidator.reset().parameter(LoanProductConstants.daysInMonthTypeParameterName).value(daysInMonthType).notNull()
+                .isOneOfTheseValues(1, 30);
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, element)) {
+            Boolean npaChangeConfig = this.fromApiJsonHelper.extractBooleanNamed(
+                    LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName)
+                    .value(npaChangeConfig).notNull().isOneOfTheseValues(true, false);
+        }
+
+        // Interest recalculation settings
+        final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
+                LoanProductConstants.isInterestRecalculationEnabledParameterName, element);
+        baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName)
+                .value(isInterestRecalculationEnabled).notNull().isOneOfTheseValues(true, false);
+
+        if (isInterestRecalculationEnabled != null) {
+            if (isInterestRecalculationEnabled.booleanValue()) {
+                validateInterestRecalculationParams(element, baseDataValidator, null);
+            }
+        }
+
+        // interest rates
+        if (this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)
+                && this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true) {
+            if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("interestRatePerPeriod")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
+                                "interestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("minInterestRatePerPeriod", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("minInterestRatePerPeriod")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
+                                "minInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("maxInterestRatePerPeriod", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("maxInterestRatePerPeriod")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
+                                "maxInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("interestRateFrequencyType")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
+                                "interestRateFrequencyType param is not supported when isLinkedToFloatingInterestRates is true");
+            }
+            if ((interestType == null || interestType != InterestMethod.DECLINING_BALANCE.getValue())
+                    || (isInterestRecalculationEnabled == null || isInterestRecalculationEnabled == false)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("isLinkedToFloatingInterestRates")
+                        .failWithCode("supported.only.for.declining.balance.interest.recalculation.enabled",
+                                "Floating interest rates are supported only for declining balance and interest recalculation enabled loan products");
+            }
+
+            final Integer floatingRatesId = this.fromApiJsonHelper.extractIntegerNamed("floatingRatesId", element, Locale.getDefault());
+            baseDataValidator.reset().parameter("floatingRatesId").value(floatingRatesId).notNull();
+
+            final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRateDifferential",
+                    element);
+            baseDataValidator.reset().parameter("interestRateDifferential").value(interestRateDifferential).notNull()
+                    .zeroOrPositiveAmount();
+
+            final String minDifferentialLendingRateParameterName = "minDifferentialLendingRate";
+            BigDecimal minDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minDifferentialLendingRateParameterName, element);
+            baseDataValidator.reset().parameter(minDifferentialLendingRateParameterName).value(minDifferentialLendingRate).notNull()
+                    .zeroOrPositiveAmount();
+
+            final String defaultDifferentialLendingRateParameterName = "defaultDifferentialLendingRate";
+            BigDecimal defaultDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    defaultDifferentialLendingRateParameterName, element);
+            baseDataValidator.reset().parameter(defaultDifferentialLendingRateParameterName).value(defaultDifferentialLendingRate)
+                    .notNull().zeroOrPositiveAmount();
+
+            final String maxDifferentialLendingRateParameterName = "maxDifferentialLendingRate";
+            BigDecimal maxDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    maxDifferentialLendingRateParameterName, element);
+            baseDataValidator.reset().parameter(maxDifferentialLendingRateParameterName).value(maxDifferentialLendingRate).notNull()
+                    .zeroOrPositiveAmount();
+
+            if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                    baseDataValidator.reset().parameter("defaultDifferentialLendingRate").value(defaultDifferentialLendingRate)
+                            .notLessThanMin(minDifferentialLendingRate);
+                }
+            }
+
+            if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                    baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate)
+                            .notLessThanMin(minDifferentialLendingRate);
+                }
+            }
+
+            if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                    baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate)
+                            .notLessThanMin(defaultDifferentialLendingRate);
+                }
+            }
+
+            final Boolean isFloatingInterestRateCalculationAllowed = this.fromApiJsonHelper.extractBooleanNamed(
+                    "isFloatingInterestRateCalculationAllowed", element);
+            baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed").value(isFloatingInterestRateCalculationAllowed)
+                    .notNull().isOneOfTheseValues(true, false);
+        } else {
+            if (this.fromApiJsonHelper.parameterExists("floatingRatesId", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("floatingRatesId")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "floatingRatesId param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("interestRateDifferential")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "interestRateDifferential param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("minDifferentialLendingRate", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("minDifferentialLendingRate")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "minDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("defaultDifferentialLendingRate", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("defaultDifferentialLendingRate")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "defaultDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("maxDifferentialLendingRate", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("maxDifferentialLendingRate")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "maxDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("isFloatingInterestRateCalculationAllowed")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod",
+                    element);
+            baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount();
+
+            final String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod";
+            BigDecimal minInterestRatePerPeriod = null;
+            if (this.fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) {
+                minInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName,
+                        element);
+                baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull()
+                        .zeroOrPositiveAmount();
+            }
+
+            final String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod";
+            BigDecimal maxInterestRatePerPeriod = null;
+            if (this.fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)) {
+                maxInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName,
+                        element);
+                baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull()
+                        .zeroOrPositiveAmount();
+            }
+
+            if (maxInterestRatePerPeriod != null && maxInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1) {
+                if (minInterestRatePerPeriod != null && minInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1) {
+                    baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod)
+                            .notLessThanMin(minInterestRatePerPeriod);
+                    if (minInterestRatePerPeriod.compareTo(maxInterestRatePerPeriod) <= 0) {
+                        baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod)
+                                .inMinAndMaxAmountRange(minInterestRatePerPeriod, maxInterestRatePerPeriod);
+                    }
+                } else {
+                    baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod)
+                            .notGreaterThanMax(maxInterestRatePerPeriod);
+                }
+            } else if (minInterestRatePerPeriod != null && minInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1) {
+                baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod)
+                        .notLessThanMin(minInterestRatePerPeriod);
+            }
+
+            final Integer interestRateFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("interestRateFrequencyType", element,
+                    Locale.getDefault());
+            baseDataValidator.reset().parameter("interestRateFrequencyType").value(interestRateFrequencyType).notNull().inMinMaxRange(0, 3);
+        }
+
+        // Guarantee Funds
+        Boolean holdGuaranteeFunds = false;
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) {
+            holdGuaranteeFunds = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.holdGuaranteeFundsParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.holdGuaranteeFundsParamName).value(holdGuaranteeFunds).notNull()
+                    .isOneOfTheseValues(true, false);
+        }
+
+        if (holdGuaranteeFunds != null) {
+            if (holdGuaranteeFunds) {
+                validateGuaranteeParams(element, baseDataValidator, null);
+            }
+        }
+
+        BigDecimal principalThresholdForLastInstallment = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                LoanProductConstants.principalThresholdForLastInstallmentParamName, element);
+        baseDataValidator.reset().parameter(LoanProductConstants.principalThresholdForLastInstallmentParamName)
+                .value(principalThresholdForLastInstallment).notLessThanMin(BigDecimal.ZERO).notGreaterThanMax(BigDecimal.valueOf(100));
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.canDefineEmiAmountParamName, element)) {
+            final Boolean canDefineInstallmentAmount = this.fromApiJsonHelper.extractBooleanNamed(
+                    LoanProductConstants.canDefineEmiAmountParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.canDefineEmiAmountParamName).value(canDefineInstallmentAmount)
+                    .isOneOfTheseValues(true, false);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.installmentAmountInMultiplesOfParamName, element)) {
+            final Integer installmentAmountInMultiplesOf = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                    LoanProductConstants.installmentAmountInMultiplesOfParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.installmentAmountInMultiplesOfParamName)
+                    .value(installmentAmountInMultiplesOf).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        // accounting related data validation
+        final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4);
+
+        if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) {
+
+            final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(fundAccountId).notNull()
+                    .integerGreaterThanZero();
+
+            final Long loanPortfolioAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue())
+                    .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
+
+            final Long incomeFromInterestId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue())
+                    .value(incomeFromRecoveryAccountId).notNull().integerGreaterThanZero();
+
+            final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue()).value(overpaymentAccountId)
+                    .notNull().integerGreaterThanZero();
+
+            validatePaymentChannelFundSourceMappings(baseDataValidator, element);
+            validateChargeToIncomeAccountMappings(baseDataValidator, element);
+
+        }
+
+        if (isAccrualBasedAccounting(accountingRuleType)) {
+
+            final Long receivableInterestAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue())
+                    .value(receivableInterestAccountId).notNull().integerGreaterThanZero();
+
+            final Long receivableFeeAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue()).value(receivableFeeAccountId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long receivablePenaltyAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element);
+            baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue())
+                    .value(receivablePenaltyAccountId).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.useBorrowerCycleParameterName, element)) {
+            final Boolean useBorrowerCycle = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.useBorrowerCycleParameterName,
+                    element);
+            baseDataValidator.reset().parameter(LoanProductConstants.useBorrowerCycleParameterName).value(useBorrowerCycle).ignoreIfNull()
+                    .validateForBooleanValue();
+            if (useBorrowerCycle) {
+                validateBorrowerCycleVariations(element, baseDataValidator);
+            }
+        }
+
+        validateMultiDisburseLoanData(baseDataValidator, element);
+
+        validateLoanConfigurableAttributes(baseDataValidator, element);
+
+        validateVariableInstallmentSettings(baseDataValidator, element);
+
+        validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, null);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateVariableInstallmentSettings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowVariableInstallmentsParamName, element)
+                && this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.allowVariableInstallmentsParamName, element)) {
+
+            Long minimumGapBetweenInstallments = null;
+            if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumGapBetweenInstallments, element)) {
+                minimumGapBetweenInstallments = this.fromApiJsonHelper.extractLongNamed(LoanProductConstants.minimumGapBetweenInstallments,
+                        element);
+                baseDataValidator.reset().parameter(LoanProductConstants.minimumGapBetweenInstallments)
+                        .value(minimumGapBetweenInstallments).notNull();
+            } else {
+                baseDataValidator
+                        .reset()
+                        .parameter(LoanProductConstants.minimumGapBetweenInstallments)
+                        .failWithCode("is.mandatory.when.allowVariableInstallments.is.true",
+                                "minimumGap param is mandatory when allowVariableInstallments is true");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.maximumGapBetweenInstallments, element)) {
+                final Long maximumGapBetweenInstallments = this.fromApiJsonHelper.extractLongNamed(
+                        LoanProductConstants.maximumGapBetweenInstallments, element);
+                baseDataValidator.reset().parameter(LoanProductConstants.minimumGapBetweenInstallments)
+                        .value(maximumGapBetweenInstallments).notNull();
+                baseDataValidator.reset().parameter(LoanProductConstants.maximumGapBetweenInstallments)
+                        .value(maximumGapBetweenInstallments).notNull().longGreaterThanNumber(minimumGapBetweenInstallments);
+            }
+
+        } else {
+            if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumGapBetweenInstallments, element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter(LoanProductConstants.minimumGapBetweenInstallments)
+                        .failWithCode("not.supported.when.allowVariableInstallments.is.false",
+                                "minimumGap param is not supported when allowVariableInstallments is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.maximumGapBetweenInstallments, element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter(LoanProductConstants.maximumGapBetweenInstallments)
+                        .failWithCode("not.supported.when.allowVariableInstallments.is.false",
+                                "maximumGap param is not supported when allowVariableInstallments is not supplied or false");
+            }
+
+        }
+    }
+
+    private void validateLoanConfigurableAttributes(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowAttributeOverridesParamName, element)) {
+
+            final JsonObject object = element.getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName);
+
+            // Validate that parameter names are allowed
+            Set<String> supportedConfigurableAttributes = new HashSet<>();
+            Collections.addAll(supportedConfigurableAttributes, LoanProductConfigurableAttributes.supportedloanConfigurableAttributes);
+            this.fromApiJsonHelper.checkForUnsupportedNestedParameters(LoanProductConstants.allowAttributeOverridesParamName, object,
+                    supportedConfigurableAttributes);
+
+            Integer length = LoanProductConfigurableAttributes.supportedloanConfigurableAttributes.length;
+
+            for (int i = 0; i < length; i++) {
+                /* Validate the attribute names */
+                if (this.fromApiJsonHelper
+                        .parameterExists(LoanProductConfigurableAttributes.supportedloanConfigurableAttributes[i], object)) {
+                    Boolean loanConfigurationAttributeValue = this.fromApiJsonHelper.extractBooleanNamed(
+                            LoanProductConfigurableAttributes.supportedloanConfigurableAttributes[i], object);
+                    /* Validate the boolean value */
+                    baseDataValidator.reset().parameter(LoanProductConstants.allowAttributeOverridesParamName)
+                            .value(loanConfigurationAttributeValue).notNull().validateForBooleanValue();
+                }
+
+            }
+        }
+    }
+
+    private void validateMultiDisburseLoanData(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        Boolean multiDisburseLoan = false;
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.multiDisburseLoanParameterName, element)) {
+            multiDisburseLoan = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.multiDisburseLoanParameterName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.multiDisburseLoanParameterName).value(multiDisburseLoan)
+                    .ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (multiDisburseLoan) {
+            if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.outstandingLoanBalanceParameterName, element)) {
+                final BigDecimal outstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        LoanProductConstants.outstandingLoanBalanceParameterName, element);
+                baseDataValidator.reset().parameter(LoanProductConstants.outstandingLoanBalanceParameterName).value(outstandingLoanBalance)
+                        .ignoreIfNull().zeroOrPositiveAmount();
+            }
+
+            final Integer maxTrancheCount = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.maxTrancheCountParameterName,
+                    element, Locale.getDefault());
+            baseDataValidator.reset().parameter(LoanProductConstants.maxTrancheCountParameterName).value(maxTrancheCount).notNull()
+                    .integerGreaterThanZero();
+
+            final Integer interestType = this.fromApiJsonHelper.extractIntegerNamed("interestType", element, Locale.getDefault());
+            baseDataValidator.reset().parameter("interestType").value(interestType).ignoreIfNull()
+                    .integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
+        }
+    }
+
+    private void validateInterestRecalculationParams(final JsonElement element, final DataValidatorBuilder baseDataValidator,
+            final LoanProduct loanProduct) {
+
+        /**
+         * { @link InterestRecalculationCompoundingMethod }
+         */
+        InterestRecalculationCompoundingMethod compoundingMethod = null;
+
+        if (loanProduct == null
+                || this.fromApiJsonHelper
+                        .parameterExists(LoanProductConstants.interestRecalculationCompoundingMethodParameterName, element)) {
+            final Integer interestRecalculationCompoundingMethod = this.fromApiJsonHelper.extractIntegerNamed(
+                    LoanProductConstants.interestRecalculationCompoundingMethodParameterName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(LoanProductConstants.interestRecalculationCompoundingMethodParameterName)
+                    .value(interestRecalculationCompoundingMethod).notNull().inMinMaxRange(0, 3);
+            if (interestRecalculationCompoundingMethod != null) {
+                compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(interestRecalculationCompoundingMethod);
+            }
+        }
+
+        if (compoundingMethod == null) {
+            if (loanProduct == null) {
+                compoundingMethod = InterestRecalculationCompoundingMethod.NONE;
+            } else {
+                compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(loanProduct.getProductInterestRecalculationDetails()
+                        .getInterestRecalculationCompoundingMethod());
+            }
+        }
+
+        /**
+         * { @link LoanRescheduleStrategyMethod }
+         */
+        if (loanProduct == null
+                || this.fromApiJsonHelper.parameterExists(LoanProductConstants.rescheduleStrategyMethodParameterName, element)) {
+            final Integer rescheduleStrategyMethod = this.fromApiJsonHelper.extractIntegerNamed(
+                    LoanProductConstants.rescheduleStrategyMethodParameterName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).value(rescheduleStrategyMethod)
+                    .notNull().inMinMaxRange(1, 3);
+        }
+
+        RecalculationFrequencyType frequencyType = null;
+
+        if (loanProduct == null
+                || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationRestFrequencyTypeParameterName, element)) {
+            final Integer recalculationRestFrequencyType = this.fromApiJsonHelper.extractIntegerNamed(
+                    LoanProductConstants.recalculationRestFrequencyTypeParameterName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(LoanProductConstants.recalculationRestFrequencyTypeParameterName)
+                    .value(recalculationRestFrequencyType).notNull().inMinMaxRange(1, 4);
+            if (recalculationRestFrequencyType != null) {
+                frequencyType = RecalculationFrequencyType.fromInt(recalculationRestFrequencyType);
+            }
+        }
+
+        if (frequencyType == null) {
+            if (loanProduct == null) {
+                frequencyType = RecalculationFrequencyType.INVALID;
+            } else {
+                frequencyType = loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType();
+            }
+        }
+
+        if (!frequencyType.isSameAsRepayment()) {
+            if (loanProduct == null
+                    || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationRestFrequencyDateParamName, element)) {
+                final LocalDate recurrenceOnLocalDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                        LoanProductConstants.recalculationRestFrequencyDateParamName, element);
+                baseDataValidator.reset().parameter(LoanProductConstants.recalculationRestFrequencyDateParamName)
+                        .value(recurrenceOnLocalDate).notNull();
+            }
+            if (loanProduct == null
+                    || this.fromApiJsonHelper
+                            .parameterExists(LoanProductConstants.recalculationRestFrequencyIntervalParameterName, element)) {
+                final Integer recurrenceInterval = this.fromApiJsonHelper.extractIntegerNamed(
+                        LoanProductConstants.recalculationRestFrequencyIntervalParameterName, element, Locale.getDefault());
+                baseDataValidator.reset().parameter(LoanProductConstants.recalculationRestFrequencyIntervalParameterName)
+                        .value(recurrenceInterval).notNull();
+            }
+        }
+
+        if (compoundingMethod.isCompoundingEnabled()) {
+            RecalculationFrequencyType compoundingfrequencyType = null;
+
+            if (loanProduct == null
+                    || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName,
+                            element)) {
+                final Integer recalculationCompoundingFrequencyType = this.fromApiJsonHelper.extractIntegerNamed(
+                        LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName, element, Locale.getDefault());
+                baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName)
+                        .value(recalculationCompoundingFrequencyType).notNull().inMinMaxRange(1, 4);
+                if (recalculationCompoundingFrequencyType != null) {
+                    compoundingfrequencyType = RecalculationFrequencyType.fromInt(recalculationCompoundingFrequencyType);
+                    if (!compoundingfrequencyType.isSameAsRepayment()) {
+                        PeriodFrequencyType repaymentFrequencyType = null;
+                        if (loanProduct == null) {
+                            Integer repaymentFrequencyTypeVal = this.fromApiJsonHelper.extractIntegerNamed("repaymentFrequencyType",
+                                    element, Locale.getDefault());
+                            repaymentFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyTypeVal);
+                        } else {
+                            repaymentFrequencyType = loanProduct.getLoanProductRelatedDetail().getRepaymentPeriodFrequencyType();
+                        }
+                        if (!compoundingfrequencyType.isSameFrequency(repaymentFrequencyType)) {
+                            baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName)
+                                    .value(recalculationCompoundingFrequencyType).failWithCode("must.be.same.as.repayment.frequency");
+                        }
+                    }
+                }
+            }
+
+            if (compoundingfrequencyType == null) {
+                if (loanProduct == null) {
+                    compoundingfrequencyType = RecalculationFrequencyType.INVALID;
+                } else {
+                    compoundingfrequencyType = loanProduct.getProductInterestRecalculationDetails().getCompoundingFrequencyType();
+                }
+            }
+
+            if (!compoundingfrequencyType.isSameAsRepayment()) {
+                if (loanProduct == null
+                        || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationCompoundingFrequencyDateParamName,
+                                element)) {
+                    final LocalDate recurrenceOnLocalDate = this.fromApiJsonHelper.extractLocalDateNamed(
+                            LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element);
+                    baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyDateParamName)
+                            .value(recurrenceOnLocalDate).notNull();
+                }
+                if (loanProduct == null
+                        || this.fromApiJsonHelper.parameterExists(
+                                LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, element)) {
+                    final Integer recurrenceInterval = this.fromApiJsonHelper.extractIntegerNamed(
+                            LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, element, Locale.getDefault());
+                    Integer repaymentEvery = null;
+                    if (loanProduct == null) {
+                        repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element);
+                    } else {
+                        repaymentEvery = loanProduct.getLoanProductRelatedDetail().getRepayEvery();
+                    }
+
+                    baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName)
+                            .value(recurrenceInterval).notNull().integerInMultiplesOfNumber(repaymentEvery);
+                }
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, element)) {
+            final Boolean isArrearsBasedOnOriginalSchedule = this.fromApiJsonHelper.extractBooleanNamed(
+                    LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName)
+                    .value(isArrearsBasedOnOriginalSchedule).notNull().isOneOfTheseValues(true, false);
+        }
+
+        final Integer preCloseInterestCalculationStrategy = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                LoanProductConstants.preClosureInterestCalculationStrategyParamName, element);
+        baseDataValidator
+                .reset()
+                .parameter(LoanProductConstants.preClosureInterestCalculationStrategyParamName)
+                .value(preCloseInterestCalculationStrategy)
+                .ignoreIfNull()
+                .inMinMaxRange(LoanPreClosureInterestCalculationStrategy.getMinValue(),
+                        LoanPreClosureInterestCalculationStrategy.getMaxValue());
+    }
+
+    public void validateForUpdate(final String json, final LoanProduct loanProduct) {
+        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("loanproduct");
+
+        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(LoanProductConstants.shortName, element)) {
+            final String shortName = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.shortName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.shortName).value(shortName).notBlank().notExceedingLengthOf(4);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("description", element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed("description", element);
+            baseDataValidator.reset().parameter("description").value(description).notExceedingLengthOf(500);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("fundId", element)) {
+            final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", element);
+            baseDataValidator.reset().parameter("fundId").value(fundId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("includeInBorrowerCycle", element)) {
+            final Boolean includeInBorrowerCycle = this.fromApiJsonHelper.extractBooleanNamed("includeInBorrowerCycle", element);
+            baseDataValidator.reset().parameter("includeInBorrowerCycle").value(includeInBorrowerCycle).ignoreIfNull()
+                    .validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("currencyCode", element)) {
+            final String currencyCode = this.fromApiJsonHelper.extractStringNamed("currencyCode", element);
+            baseDataValidator.reset().parameter("currencyCode").value(currencyCode).notBlank().notExceedingLengthOf(3);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("digitsAfterDecimal", element)) {
+            final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerNamed("digitsAfterDecimal", element,
+                    Locale.getDefault());
+            baseDataValidator.reset().parameter("digitsAfterDecimal").value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("inMultiplesOf", element)) {
+            final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerNamed("inMultiplesOf", element, Locale.getDefault());
+            baseDataValidator.reset().parameter("inMultiplesOf").value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
+        }
+
+        final String minPrincipalParameterName = "minPrincipal";
+        BigDecimal minPrincipalAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(minPrincipalParameterName, element)) {
+            minPrincipalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minPrincipalParameterName, element);
+            baseDataValidator.reset().parameter(minPrincipalParameterName).value(minPrincipalAmount).ignoreIfNull().positiveAmount();
+        }
+
+        final String maxPrincipalParameterName = "maxPrincipal";
+        BigDecimal maxPrincipalAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(maxPrincipalParameterName, element)) {
+            maxPrincipalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxPrincipalParameterName, element);
+            baseDataValidator.reset().parameter(maxPrincipalParameterName).value(maxPrincipalAmount).ignoreIfNull().positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("principal", element)) {
+            final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
+            baseDataValidator.reset().parameter("principal").value(principal).positiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("inArrearsTolerance", element)) {
+            final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
+            baseDataValidator.reset().parameter("inArrearsTolerance").value(inArrearsTolerance).ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        final String minNumberOfRepaymentsParameterName = "minNumberOfRepayments";
+        Integer minNumberOfRepayments = null;
+        if (this.fromApiJsonHelper.parameterExists(minNumberOfRepaymentsParameterName, element)) {
+            minNumberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(minNumberOfRepaymentsParameterName, element);
+            baseDataValidator.reset().parameter(minNumberOfRepaymentsParameterName).value(minNumberOfRepayments).ignoreIfNull()
+                    .integerGreaterThanZero();
+        }
+
+        final String maxNumberOfRepaymentsParameterName = "maxNumberOfRepayments";
+        Integer maxNumberOfRepayments = null;
+        if (this.fromApiJsonHelper.parameterExists(maxNumberOfRepaymentsParameterName, element)) {
+            maxNumberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(maxNumberOfRepaymentsParameterName, element);
+            baseDataValidator.reset().parameter(maxNumberOfRepaymentsParameterName).value(maxNumberOfRepayments).ignoreIfNull()
+                    .integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("numberOfRepayments", element)) {
+            final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
+            baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("repaymentEvery", element)) {
+            final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element);
+            baseDataValidator.reset().parameter("repaymentEvery").value(repaymentEvery).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("repaymentFrequencyType", element)) {
+            final Integer repaymentFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("repaymentFrequencyType", element,
+                    Locale.getDefault());
+            baseDataValidator.reset().parameter("repaymentFrequencyType").value(repaymentFrequencyType).notNull().inMinMaxRange(0, 3);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("transactionProcessingStrategyId", element)) {
+            final Long transactionProcessingStrategyId = this.fromApiJsonHelper
+                    .extractLongNamed("transactionProcessingStrategyId", element);
+            baseDataValidator.reset().parameter("transactionProcessingStrategyId").value(transactionProcessingStrategyId).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        // grace validation
+        if (this.fromApiJsonHelper.parameterExists("graceOnPrincipalPayment", element)) {
+            final Integer graceOnPrincipalPayment = this.fromApiJsonHelper
+                    .extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
+            baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment).zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("graceOnInterestPayment", element)) {
+            final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
+            baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment).zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("graceOnInterestCharged", element)) {
+            final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
+            baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged).zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.graceOnArrearsAgeingParameterName, element)) {
+            final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                    LoanProductConstants.graceOnArrearsAgeingParameterName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing)
+                    .integerZeroOrGreater();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.overdueDaysForNPAParameterName, element)) {
+            final Integer overdueDaysForNPA = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                    LoanProductConstants.overdueDaysForNPAParameterName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.overdueDaysForNPAParameterName).value(overdueDaysForNPA)
+                    .integerZeroOrGreater();
+        }
+
+        //
+        if (this.fromApiJsonHelper.parameterExists("amortizationType", element)) {
+            final Integer amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault());
+            baseDataValidator.reset().parameter("amortizationType").value(amortizationType).notNull().inMinMaxRange(0, 1);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("interestType", element)) {
+            final Integer interestType = this.fromApiJsonHelper.extractIntegerNamed("interestType", element, Locale.getDefault());
+            baseDataValidator.reset().parameter("interestType").value(interestType).notNull().inMinMaxRange(0, 1);
+        }
+        Integer interestCalculationPeriodType = loanProduct.getLoanProductRelatedDetail().getInterestCalculationPeriodMethod().getValue();
+        if (this.fromApiJsonHelper.parameterExists("interestCalculationPeriodType", element)) {
+            interestCalculationPeriodType = this.fromApiJsonHelper.extractIntegerNamed("interestCalculationPeriodType", element,
+                    Locale.getDefault());
+            baseDataValidator.reset().parameter("interestCalculationPeriodType").value(interestCalculationPeriodType).notNull()
+                    .inMinMaxRange(0, 1);
+        }
+
+        /**
+         * { @link DaysInYearType }
+         */
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.daysInYearTypeParameterName, element)) {
+            final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInYearTypeParameterName,
+                    element, Locale.getDefault());
+            baseDataValidator.reset().parameter(LoanProductConstants.daysInYearTypeParameterName).value(daysInYearType).notNull()
+                    .isOneOfTheseValues(1, 360, 364, 365);
+        }
+
+        /**
+         * { @link DaysInMonthType }
+         */
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.daysInMonthTypeParameterName, element)) {
+            final Integer daysInMonthType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInMonthTypeParameterName,
+                    element, Locale.getDefault());
+            baseDataValidator.reset().parameter(LoanProductConstants.daysInMonthTypeParameterName).value(daysInMonthType).notNull()
+                    .isOneOfTheseValues(1, 30);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, element)) {
+            Boolean npaChangeConfig = this.fromApiJsonHelper.extractBooleanNamed(
+                    LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName)
+                    .value(npaChangeConfig).notNull().isOneOfTheseValues(true, false);
+        }
+
+        // Interest recalculation settings
+        Boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) {
+            isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
+                    LoanProductConstants.isInterestRecalculationEnabledParameterName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName)
+                    .value(isInterestRecalculationEnabled).notNull().isOneOfTheseValues(true, false);
+        }
+
+        if (isInterestRecalculationEnabled != null) {
+            if (isInterestRecalculationEnabled) {
+                validateInterestRecalculationParams(element, baseDataValidator, loanProduct);
+            }
+        }
+
+        // interest rates
+        boolean isLinkedToFloatingInterestRates = loanProduct.isLinkedToFloatingInterestRate();
+        if (this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)) {
+            isLinkedToFloatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element);
+        }
+        if (isLinkedToFloatingInterestRates) {
+            if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("interestRatePerPeriod")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
+                                "interestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("minInterestRatePerPeriod", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("minInterestRatePerPeriod")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
+                                "minInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("maxInterestRatePerPeriod", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("maxInterestRatePerPeriod")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
+                                "maxInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("interestRateFrequencyType")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
+                                "interestRateFrequencyType param is not supported when isLinkedToFloatingInterestRates is true");
+            }
+
+            Integer interestType = this.fromApiJsonHelper.parameterExists("interestType", element) ? this.fromApiJsonHelper
+                    .extractIntegerNamed("interestType", element, Locale.getDefault()) : loanProduct.getLoanProductRelatedDetail()
+                    .getInterestMethod().getValue();
+            if ((interestType == null || interestType != InterestMethod.DECLINING_BALANCE.getValue())
+                    || (isInterestRecalculationEnabled == null || isInterestRecalculationEnabled == false)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("isLinkedToFloatingInterestRates")
+                        .failWithCode("supported.only.for.declining.balance.interest.recalculation.enabled",
+                                "Floating interest rates are supported only for declining balance and interest recalculation enabled loan products");
+            }
+
+            Long floatingRatesId = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates().getFloatingRate().getId();
+            if (this.fromApiJsonHelper.parameterExists("floatingRatesId", element)) {
+                floatingRatesId = this.fromApiJsonHelper.extractLongNamed("floatingRatesId", element);
+            }
+            baseDataValidator.reset().parameter("floatingRatesId").value(floatingRatesId).notNull();
+
+            BigDecimal interestRateDifferential = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates()
+                    .getInterestRateDifferential();
+            if (this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)) {
+                interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRateDifferential", element);
+            }
+            baseDataValidator.reset().parameter("interestRateDifferential").value(interestRateDifferential).notNull()
+                    .zeroOrPositiveAmount();
+
+            final String minDifferentialLendingRateParameterName = "minDifferentialLendingRate";
+            BigDecimal minDifferentialLendingRate = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates()
+                    .getMinDifferentialLendingRate();
+            if (this.fromApiJsonHelper.parameterExists(minDifferentialLendingRateParameterName, element)) {
+                minDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        minDifferentialLendingRateParameterName, element);
+            }
+            baseDataValidator.reset().parameter(minDifferentialLendingRateParameterName).value(minDifferentialLendingRate).notNull()
+                    .zeroOrPositiveAmount();
+
+            final String defaultDifferentialLendingRateParameterName = "defaultDifferentialLendingRate";
+            BigDecimal defaultDifferentialLendingRate = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates()
+                    .getDefaultDifferentialLendingRate();
+            if (this.fromApiJsonHelper.parameterExists(defaultDifferentialLendingRateParameterName, element)) {
+                defaultDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        defaultDifferentialLendingRateParameterName, element);
+            }
+            baseDataValidator.reset().parameter(defaultDifferentialLendingRateParameterName).value(defaultDifferentialLendingRate)
+                    .notNull().zeroOrPositiveAmount();
+
+            final String maxDifferentialLendingRateParameterName = "maxDifferentialLendingRate";
+            BigDecimal maxDifferentialLendingRate = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates()
+                    .getMaxDifferentialLendingRate();
+            if (this.fromApiJsonHelper.parameterExists(maxDifferentialLendingRateParameterName, element)) {
+                maxDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        maxDifferentialLendingRateParameterName, element);
+            }
+            baseDataValidator.reset().parameter(maxDifferentialLendingRateParameterName).value(maxDifferentialLendingRate).notNull()
+                    .zeroOrPositiveAmount();
+
+            if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                    baseDataValidator.reset().parameter("defaultDifferentialLendingRate").value(defaultDifferentialLendingRate)
+                            .notLessThanMin(minDifferentialLendingRate);
+                }
+            }
+
+            if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                    baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate)
+                            .notLessThanMin(minDifferentialLendingRate);
+                }
+            }
+
+            if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
+                    baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate)
+                            .notLessThanMin(defaultDifferentialLendingRate);
+                }
+            }
+
+            Boolean isFloatingInterestRateCalculationAllowed = loanProduct.getFloatingRates() == null ? null : loanProduct
+                    .getFloatingRates().isFloatingInterestRateCalculationAllowed();
+            if (this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)) {
+                isFloatingInterestRateCalculationAllowed = this.fromApiJsonHelper.extractBooleanNamed(
+                        "isFloatingInterestRateCalculationAllowed", element);
+            }
+            baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed").value(isFloatingInterestRateCalculationAllowed)
+                    .notNull().isOneOfTheseValues(true, false);
+        } else {
+            if (this.fromApiJsonHelper.parameterExists("floatingRatesId", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("floatingRatesId")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "floatingRatesId param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("interestRateDifferential")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "interestRateDifferential param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("minDifferentialLendingRate", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("minDifferentialLendingRate")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "minDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("defaultDifferentialLendingRate", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("defaultDifferentialLendingRate")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "defaultDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("maxDifferentialLendingRate", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("maxDifferentialLendingRate")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "maxDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            if (this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)) {
+                baseDataValidator
+                        .reset()
+                        .parameter("isFloatingInterestRateCalculationAllowed")
+                        .failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
+                                "isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
+            }
+
+            final String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod";
+            BigDecimal minInterestRatePerPeriod = loanProduct.getMinNominalInterestRatePerPeriod();
+            if (this.fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) {
+                minInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName,
+                        element);
+            }
+            baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull()
+                    .zeroOrPositiveAmount();
+
+            final String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod";
+            BigDecimal maxInterestRatePerPeriod = loanProduct.getMaxNominalInterestRatePerPeriod();
+            if (this.fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)) {
+                maxInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName,
+                        element);
+            }
+            baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull()
+                    .zeroOrPositiveAmount();
+
+            BigDecimal interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
+            if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
+                interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element);
+            }
+            baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount();
+
+            Integer interestRateFrequencyType = loanProduct.getLoanProductRelatedDetail().getInterestPeriodFrequencyType().getValue();
+            if (this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)) {
+                interestRateFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("interestRateFrequencyType", element,
+                        Locale.getDefault());
+            }
+            baseDataValidator.reset().parameter("interestRateFrequencyType").value(interestRateFrequencyType).notNull().inMinMaxRange(0, 3);
+        }
+
+        // Guarantee Funds
+        Boolean holdGuaranteeFunds = loanProduct.isHoldGuaranteeFundsEnabled();
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) {
+            holdGuaranteeFunds = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.holdGuaranteeFundsParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.holdGuaranteeFundsParamName).value(holdGuaranteeFunds).notNull()
+                    .isOneOfTheseValues(true, false);
+        }
+
+        if (holdGuaranteeFunds != null) {
+            if (holdGuaranteeFunds) {
+                validateGuaranteeParams(element, baseDataValidator, null);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.principalThresholdForLastInstallmentParamName, element)) {
+            BigDecimal principalThresholdForLastInstallment = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    LoanProductConstants.principalThresholdForLastInstallmentParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.principalThresholdForLastInstallmentParamName)
+                    .value(principalThresholdForLastInstallment).notNull().notLessThanMin(BigDecimal.ZERO)
+                    .notGreaterThanMax(BigDecimal.valueOf(100));
+        }
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.canDefineEmiAmountParamName, element)) {
+            final Boolean canDefineInstallmentAmount = this.fromApiJsonHelper.extractBooleanNamed(
+                    LoanProductConstants.canDefineEmiAmountParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.canDefineEmiAmountParamName).value(canDefineInstallmentAmount)
+                    .isOneOfTheseValues(true, false);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.installmentAmountInMultiplesOfParamName, element)) {
+            final Integer installmentAmountInMultiplesOf = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                    LoanProductConstants.installmentAmountInMultiplesOfParamName, element);
+            baseDataValidator.reset().parameter(LoanProductConstants.installmentAmountInMultiplesOfParamName)
+                    .value(installmentAmountInMultiplesOf).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).ignoreIfNull().inMinMaxRange(1, 4);
+
+        final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(fundAccountId).ignoreIfNull()
+                .integerGreaterThanZero();
+
+        final Long loanPortfolioAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue())
+                .value(transfersInSuspenseAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromInterestId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
+                element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue())
+                .value(incomeFromRecoveryAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(),
+                element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue()).value(overpaymentAccountId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long receivableInterestAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue())
+                .value(receivableInterestAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long receivableFeeAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue()).value(receivableFeeAccountId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long receivablePenaltyAccountId = this.fromApiJsonHelper.extractLongNamed(
+                LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element);
+        baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue())
+                .value(receivablePenaltyAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        validatePaymentChannelFundSourceMappings(baseDataValidator, element);
+        validateChargeToIncomeAccountMappings(baseDataValidator, element);
+
+        validateMinMaxConstraints(element, baseDataValidator, loanProduct);
+
+        if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.useBorrowerCycleParameterName, element)) {
+            final Boolean useBorrowerCycle = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.useBorrowerCycleParameterName,
+                    element);
+            baseDataValidator.reset().parameter(LoanProductConstants.useBorrowerCycleParameterName).value(useBorrowerCycle).ignoreIfNull()
+                    .validateForBooleanValue();
+            if (useBorrowerCycle) {
+                validateBorrowerCycleVariations(element, baseDataValidator);
+            }
+        }
+
+        validateMultiDisburseLoanData(baseDataValidator, element);
+
+        // validateLoanConfigurableAttributes(baseDataValidator,element);
+
+        validateVariableInstallmentSettings(baseDataValidator, element);
+
+        validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    /*
+     * Validation for advanced accounting options
+     */
+    private void validatePaymentChannelFundSourceMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        if (this.fromApiJsonHelper.parameterExists(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element)) {
+            final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(
+                    LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
+            if (paymentChannelMappingArray != null && paymentChannelMappingArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject();
+                    final Long paymentTypeId = this.fromApiJsonHelper.extractLongNamed(
+                            LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue(), jsonObject);
+                    final Long paymentSpecificFundAccountId = this.fromApiJsonHelper.extractLongNamed(
+                            LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), jsonObject);
+
+                    baseDataValidator
+                            .reset()
+                            .parameter(
+                                    LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
+                                            + LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).value(paymentTypeId).notNull()
+                            .integerGreaterThanZero();
+                    baseDataValidator
+                            .reset()
+                            .parameter(
+                                    LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
+                                            + LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(paymentSpecificFundAccountId)
+                            .notNull().integerGreaterThanZero();
+                    i++;
+                } while (i < paymentChannelMappingArray.size());
+            }
+        }
+    }
+
+    private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        // validate for both fee and penalty charges
+        validateChargeToIncomeAccountMappings(baseDataValidator, element, true);
+        validateChargeToIncomeAccountMappings(baseDataValidator, element, true);
+    }
+
+    private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element,
+            final boolean isPenalty) {
+        String parameterName;
+        if (isPenalty) {
+            parameterName = LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
+        } else {
+            parameterName = LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(parameterName, element)) {
+            final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
+            if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue(),
+                            jsonObject);
+                    final Long incomeAccountId = this.fromApiJsonHelper.extractLongNamed(
+                            LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(), jsonObject);
+                    baseDataValidator.reset()
+                            .parameter(parameterName + "[" + i + "]." + LOAN_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue())
+                            .value(chargeId).notNull().integerGreaterThanZero();
+                    baseDataValidator.reset()
+                            .parameter(parameterName + "[" + i + "]." + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue())
+                            .value(incomeAccountId).notNull().integerGreaterThanZero();
+                    i++;
+                } while (i < chargeToIncomeAccountMappingArray.size());
+            }
+        }
+    }
+
+    public void validateMinMaxConstraints(final JsonElement element, final DataValidatorBuilder baseDataValidator,
+            final LoanProduct loanProduct) {
+
+        validatePrincipalMinMaxConstraint(element, loanProduct, baseDataValidator);
+
+        validateNumberOfRepaymentsMinMaxConstraint(element, loanProduct, baseDataValidator);
+
+        validateNominalInterestRatePerPeriodMinMaxConstraint(element, loanProduct, baseDataValidator);
+    }
+
+    public void validateMinMaxConstraints(final JsonElement element, final DataValidatorBuilder baseDataValidator,
+            final LoanProduct loanProduct, Integer cycleNumber) {
+
+        final Map<String, BigDecimal> minmaxValues = loanProduct.fetchBorrowerCycleVariationsForCycleNumber(cycleNumber);
+        final String principalParameterName = "principal";
+        BigDecimal principalAmount = null;
+        BigDecimal minPrincipalAmount = null;
+        BigDecimal maxPrincipalAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(principalParameterName, element)) {
+            principalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element);
+            minPrincipalAmount = minmaxValues.get(LoanProductConstants.minPrincipal);
+            maxPrincipalAmount = minmaxValues.get(LoanProductConstants.maxPrincipal);
+        }
+
+        if ((minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) == 1)
+                && (maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) == 1)) {
+            baseDataValidator.reset().parameter(principalParameterName).value(principalAmount)
+                    .inMinAndMaxAmountRange(minPrincipalAmount, maxPrincipalAmount);
+        } else {
+            if (minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) == 1) {
+                baseDataValidator.reset().parameter(principalParameterName).value(principalAmount).notLessThanMin(minPrincipalAmount);
+            } else if (maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) == 1) {
+                baseDataValidator.reset().parameter(principalParameterName).value(principalAmount).notGreaterThanMax(maxPrincipalAmount);
+            }
+        }
+
+        final String numberOfRepaymentsParameterName = "numberOfRepayments";
+        Integer maxNumberOfRepayments = null;
+        Integer minNumberOfRepayments = null;
+        Integer numberOfRepayments = null;
+        if (this.fromApiJsonHelper.parameterExists(numberOfRepaymentsParameterName, element)) {
+            numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName, element);
+            if (minmaxValues.get(LoanProductConstants.minNumberOfRepayments) != null) {
+                minNumberOfRepayments = minmaxValues.get(LoanProductConstants.minNumberOfRepayments).intValueExact();
+            }
+            if (minmaxValues.get(LoanProductConstants.maxNumberOfRepayments) != null) {
+                maxNumberOfRepayments = minmaxValues.get(LoanProductConstants.maxNumberOfRepayments).intValueExact();
+            }
+        }
+
+        if (maxNumberOfRepayments != null && maxNumberOfRepayments.compareTo(0) == 1) {
+            if (minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1) {
+                baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments)
+                        .inMinMaxRange(minNumberOfRepayments, maxNumberOfRepayments);
+            } else {
+                baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments)
+                        .notGreaterThanMax(maxNumberOfRepayments);
+            }
+        } else if (minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1) {
+            baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments)
+                    .notLessThanMin(minNumberOfRepayments);
+        }
+
+        final String interestRatePerPeriodParameterName = "interestRatePerPeriod";
+        BigDecimal interestRatePerPeriod = null;
+        BigDecimal minInterestRatePerPeriod = null;
+        BigDecimal maxInterestRatePerPeriod = null;
+        if (this.fromApiJsonHelper.parameterExists(interestRatePerPeriodParameterName, element)) {
+            interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRatePerPeriodParameterName, element);
+            minInterestRatePerPeriod = minmaxValues.get(LoanProductConstants.minInterestRatePerPeriod);
+            maxInterestRatePerPeriod = minmaxValues.get(LoanProductConstants.maxInterestRatePerPeriod);
+        }
+        if (maxInterestRatePerPeriod != null) {
+            if (minInterestRatePerPeriod != null) {
+                baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod)
+                        .inMinAndMaxAmountRange(minInterestRatePerPeriod, maxInterestRatePerPeriod);
+            } else {
+                baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod)
+                        .notGreaterThanMax(maxInterestRatePerPeriod);
+            }
+        } else if (minInterestRatePerPeriod != null) {
+            baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod)
+                    .notLessThanMin(minInterestRatePerPeriod);
+        }
+
+    }
+
+    private void validatePrincipalMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct,
+            final DataValidatorBuilder baseDataValidator) {
+
+        boolean principalUpdated = false;
+        boolean minPrincipalUpdated = false;
+        boolean maxPrincipalUpdated = false;
+        final String principalParameterName = "principal";
+        BigDecimal principalAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(principalParameterName, element)) {
+            principalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element);
+            principalUpdated = true;
+        } else {
+            principalAmount = loanProduct.getPrincipalAmount().getAmount();
+        }
+
+        final String minPrincipalParameterName = "minPrincipal";
+        BigDecimal minPrincipalAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(minPrincipalParameterName, element)) {
+            minPrincipalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minPrincipalParameterName, element);
+            minPrincipalUpdated = true;
+        } else {
+            minPrincipalAmount = loanProduct.getMinPrincipalAmount().getAmount();
+        }
+
+        final String maxPrincipalParameterName = "maxPrincipal";
+        BigDecimal maxPrincipalAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(maxPrincipalParameterName, element)) {
+            maxPrincipalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxPrincipalParameterName, element);
+            maxPrincipalUpdated = true;
+        } else {
+            maxPrincipalAmount = loanProduct.getMaxPrincipalAmount().getAmount();
+        }
+
+        if (minPrincipalUpdated) {
+            baseDataValidator.reset().parameter(minPrincipalParameterName).value(minPrincipalAmount).notGreaterThanMax(maxPrincipalAmount);
+        }
+
+        if (maxPrincipalUpdated) {
+            baseDataValidator.reset().parameter(maxPrincipalParameterName).value(maxPrincipalAmount).notLessThanMin(minPrincipalAmount);
+        }
+
+        if ((principalUpdated || minPrincipalUpdated || maxPrincipalUpdated)) {
+
+            if ((minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) == 1)
+                    && (maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) == 1)) {
+                baseDataValidator.reset().parameter(principalParameterName).value(principalAmount)
+                        .inMinAndMaxAmountRange(minPrincipalAmount, maxPrincipalAmount);
+            } else {
+                if (minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) == 1) {
+                    baseDataValidator.reset().parameter(principalParameterName).value(principalAmount).notLessThanMin(minPrincipalAmount);
+                } else if (maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) == 1) {
+                    baseDataValidator.reset().parameter(principalParameterName).value(principalAmount)
+                            .notGreaterThanMax(maxPrincipalAmount);
+                }
+            }
+        }
+    }
+
+    private void validateNumberOfRepaymentsMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct,
+            final DataValidatorBuilder baseDataValidator) {
+        boolean numberOfRepaymentsUpdated = false;
+        boolean minNumberOfRepaymentsUpdated = false;
+        boolean maxNumberOfRepaymentsUpdated = false;
+
+        final String numberOfRepaymentsParameterName = "numberOfRepayments";
+        Integer numberOfRepayments = null;
+        if (this.fromApiJsonHelper.parameterExists(numberOfRepaymentsParameterName, element)) {
+            numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName, element);
+            numberOfRepaymentsUpdated = true;
+        } else {
+            numberOfRepayments = loanProduct.getNumberOfRepayments();
+        }
+
+        final String minNumberOfRepaymentsParameterName = "minNumberOfRepayments";
+        Integer minNumberOfRepayments = null;
+        if (this.fromApiJsonHelper.parameterExists(minNumberOfRepaymentsParameterName, element)) {
+            minNumberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(minNumberOfRepaymentsParameterName, element);
+            minNumberOfRepaymentsUpdated = true;
+        } else {
+            minNumberOfRepayments = loanProduct.getMinNumberOfRepayments();
+        }
+
+        final String maxNumberOfRepaymentsParameterName = "maxNumberOfRepayments";
+        Integer maxNumberOfRepayments = null;
+        if (this.fromApiJsonHelper.parameterExists(maxNumberOfRepaymentsParameterName, element)) {
+            maxNumberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(maxNumberOfRepaymentsParameterName, element);
+            maxNumberOfRepaymentsUpdated = true;
+        } else {
+            maxNumberOfRepayments = loanProduct.getMaxNumberOfRepayments();
+        }
+
+        if (minNumberOfRepaymentsUpdated) {
+            baseDataValidator.reset().parameter(minNumberOfRepaymentsParameterName).value(minNumberOfRepayments).ignoreIfNull()
+                    .notGreaterThanMax(maxNumberOfRepayments);
+        }
+
+        if (maxNumberOfRepaymentsUpdated) {
+            baseDataValidator.reset().parameter(maxNumberOfRepaymentsParameterName).value(maxNumberOfRepayments)
+                    .notLessThanMin(minNumberOfRepayments);
+        }
+
+        if (numberOfRepaymentsUpdated || minNumberOfRepaymentsUpdated || maxNumberOfRepaymentsUpdated) {
+            if (maxNumberOfRepayments != null && maxNumberOfRepayments.compareTo(0) == 1) {
+                if (minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1) {
+                    baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments)
+                            .inMinMaxRange(minNumberOfRepayments, maxNumberOfRepayments);
+                } else {
+                    baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments)
+                            .notGreaterThanMax(maxNumberOfRepayments);
+                }
+            } else if (minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1) {
+                baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments)
+                        .notLessThanMin(minNumberOfRepayments);
+            }
+        }
+    }
+
+    private void validateNominalInterestRatePerPeriodMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct,
+            final DataValidatorBuilder baseDataValidator) {
+
+        if ((this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element) && this.fromApiJsonHelper
+                .extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true) || loanProduct.isLinkedToFloatingInterestRate()) { return; }
+        boolean iRPUpdated = false;
+        boolean minIRPUpdated = false;
+        boolean maxIRPUpdated = false;
+        final String interestRatePerPeriodParameterName = "interestRatePerPeriod";
+        BigDecimal interestRatePerPeriod = null;
+
+        if (this.fromApiJsonHelper.parameterExists(interestRatePerPeriodParameterName, element)) {
+            interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRatePerPeriodParameterName, element);
+            iRPUpdated = true;
+        } else {
+            interestRatePerPeriod = loanProduct.getNominalInterestRatePerPeriod();
+        }
+
+        final String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod";
+        BigDecimal minInterestRatePerPeriod = null;
+        if (this.fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) {
+            minInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName,
+                    element);
+            minIRPUpdated = true;
+        } else {
+            minInterestRatePerPeriod = loanProduct.getMinNominalInterestRatePerPeriod();
+        }
+
+        final String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod";
+        BigDecimal maxInterestRatePerPeriod = null;
+        if (this.fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)) {
+            maxInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName,
+                    element);
+            maxIRPUpdated = true;
+        } else {
+            maxInterestRatePerPeriod = loanProduct.getMaxNominalInterestRatePerPeriod();
+        }
+
+        if (minIRPUpdated) {
+            baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull()
+                    .notGreaterThanMax(maxInterestRatePerPeriod);
+        }
+
+        if (maxIRPUpdated) {
+            baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull()
+                    .notLessThanMin(minInterestRatePerPeriod);
+        }
+
+        if (iRPUpdated || minIRPUpdated || maxIRPUpdated) {
+            if (maxInterestRatePerPeriod != null) {
+                if (minInterestRatePerPeriod != null) {
+                    baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod)
+                            .inMinAndMaxAmountRange(minInterestRatePerPeriod, maxInterestRatePerPeriod);
+                } else {
+                    baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod)
+                            .notGreaterThanMax(maxInterestRatePerPeriod);
+                }
+            } else if (minInterestRatePerPeriod != null) {
+                baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod)
+                        .notLessThanMin(minInterestRatePerPeriod);
+            }
+        }
+    }
+
+    private boolean isCashBasedAccounting(final Integer accountingRuleType) {
+        return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType);
+    }
+
+    private boolean isAccrualBasedAccounting(final Integer accountingRuleType) {
+        return isUpfrontAccrualAccounting(accountingRuleType) || isPeriodicAccounting(accountingRuleType);
+    }
+
+    private boolean isUpfrontAccrualAccounting(final Integer accountingRuleType) {
+        return AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType);
+    }
+
+    private boolean isPeriodicAccounting(final Integer accountingRuleType) {
+        return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    private void validateBorrowerCycleVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        validateBorrowerCyclePrincipalVariations(element, baseDataValidator);
+        validateBorrowerCycleRepaymentVariations(element, baseDataValidator);
+        validateBorrowerCycleInterestVariations(element, baseDataValidator);
+    }
+
+    private void validateBorrowerCyclePrincipalVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+
+        validateBorrowerCycleVariations(element, baseDataValidator, LoanProductConstants.principalVariationsForBorrowerCycleParameterName,
+                LoanProductConstants.principalPerCycleParameterName, LoanProductConstants.minPrincipalPerCycleParameterName,
+                LoanProductConstants.maxPrincipalPerCycleParameterName, LoanProductConstants.principalValueUsageConditionParamName,
+                LoanProductConstants.principalCycleNumbersParamName);
+    }
+
+    private void validateBorrowerCycleRepaymentVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        validateBorrowerCycleVariations(element, baseDataValidator,
+                LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName,
+                LoanProductConstants.numberOfRepaymentsPerCycleParameterName,
+                LoanProductConstants.minNumberOfRepaymentsPerCycleParameterName,
+                LoanProductConstants.maxNumberOfRepaymentsPerCycleParameterName,
+                LoanProductConstants.repaymentValueUsageConditionParamName, LoanProductConstants.repaymentCycleNumberParamName);
+    }
+
+    private void validateBorrowerCycleInterestVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        validateBorrowerCycleVariations(element, baseDataValidator,
+                LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName,
+                LoanProductConstants.interestRatePerPeriodPerCycleParameterName,
+                LoanProductConstants.minInterestRatePerPeriodPerCycleParameterName,
+                LoanProductConstants.maxInterestRatePerPeriodPerCycleParameterName,
+                LoanProductConstants.interestRateValueUsageConditionParamName, LoanProductConstants.interestRateCycleNumberParamName);
+    }
+
+    private void validateBorrowerCycleVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator,
+            final String variationParameterName, final String defaultParameterName, final String minParameterName,
+            final String maxParameterName, final String valueUsageConditionParamName, final String cycleNumbersParamName) {
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+        Integer lastCycleNumber = 0;
+        LoanProductValueConditionType lastConditionType = LoanProductValueConditionType.EQUAL;
+        if (this.fromApiJsonHelper.parameterExists(variationParameterName, element)) {
+            final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(variationParameterName, element);
+            if (variationArray != null && variationArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
+
+                    BigDecimal defaultValue = this.fromApiJsonHelper.extractBigDecimalNamed(LoanProductConstants.defaultValueParameterName,
+                            jsonObject, locale);
+                    BigDecimal minValue = this.fromApiJsonHelper.extractBigDecimalNamed(LoanProductConstants.minValueParameterName,
+                            jsonObject, locale);
+                    BigDecimal maxValue = this.fromApiJsonHelper.extractBigDecimalNamed(LoanProductConstants.maxValueParameterName,
+                            jsonObject, locale);
+                    Integer cycleNumber = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.borrowerCycleNumberParamName,
+                            jsonObject, locale);
+                    Integer valueUsageCondition = this.fromApiJsonHelper.extractIntegerNamed(
+                            LoanProductConstants.valueConditionTypeParamName, jsonObject, locale);
+
+                    baseDataValidator.reset().parameter(defaultParameterName).value(defaultValue).notBlank();
+                    if (minValue != null) {
+                        baseDataValidator.reset().parameter(minParameterName).value(minValue).notGreaterThanMax(maxValue);
+                    }
+
+                    if (maxValue != null) {
+                        baseDataValidator.reset().parameter(maxParameterName).value(maxValue).notLessThanMin(minValue);
+                    }
+                    if ((minValue != null && minValue.compareTo(BigDecimal.ZERO) == 1)
+                            && (maxValue != null && maxValue.compareTo(BigDecimal.ZERO) == 1)) {
+                        baseDataValidator.reset().parameter(defaultParameterName).value(defaultValue)
+                                .inMinAndMaxAmountRange(minValue, maxValue);
+                    } else {
+                        if (minValue != null && minValue.compareTo(BigDecimal.ZERO) == 1) {
+                            baseDataValidator.reset().parameter(defaultParameterName).value(defaultValue).notLessThanMin(minValue);
+                        } else if (maxValue != null && maxValue.compareTo(BigDecimal.ZERO) == 1) {
+                            baseDataValidator.reset().parameter(defaultParameterName).value(defaultValue).notGreaterThanMax(maxValue);
+                        }
+                    }
+
+                    LoanProductValueConditionType conditionType = LoanProductValueConditionType.INVALID;
+                    if (valueUsageCondition != null) {
+                        conditionType = LoanProductValueConditionType.fromInt(valueUsageCondition);
+                    }
+                    baseDataValidator
+                            .reset()
+                            .parameter(valueUsageConditionParamName)
+                            .value(valueUsageCondition)
+                            .notNull()
+                            .inMinMaxRange(LoanProductValueConditionType.EQUAL.getValue(),
+                                    LoanProductValueConditionType.GREATERTHAN.getValue());
+                    if (lastConditionType.equals(LoanProductValueConditionType.EQUAL)
+                            && conditionType.equals(LoanProductValueConditionType.GREATERTHAN)) {
+                        if (lastCycleNumber == 0) {
+                            baseDataValidator.reset().parameter(cycleNumbersParamName)
+                                    .failWithCode(LoanProductConstants.VALUE_CONDITION_START_WITH_ERROR);
+                            lastCycleNumber = 1;
+                        }
+                        baseDataValidator.reset().parameter(cycleNumbersParamName).value(cycleNumber).notNull()
+                                .integerSameAsNumber(lastCycleNumber);
+                    } else if (lastConditionType.equals(LoanProductValueConditionType.EQUAL)) {
+                        baseDataValidator.reset().parameter(cycleNumbersParamName).value(cycleNumber).notNull()
+                                .integerSameAsNumber(lastCycleNumber + 1);
+                    } else if (lastConditionType.equals(LoanProductValueConditionType.GREATERTHAN)) {
+                        baseDataValidator.reset().parameter(cycleNumbersParamName).value(cycleNumber).notNull()
+                                .integerGreaterThanNumber(lastCycleNumber);
+                    }
+                    if (conditionType != null) {
+                        lastConditionType = conditionType;
+                    }
+                    if (cycleNumber != null) {
+                        lastCycleNumber = cycleNumber;
+                    }
+                    i++;
+                } while (i < variationArray.size());
+                if (!lastConditionType.equals(LoanProductValueConditionType.GREATERTHAN)) {
+                    baseDataValidator.reset().parameter(cycleNumbersParamName)
+                            .failWithCode(LoanProductConstants.VALUE_CONDITION_END_WITH_ERROR);
+                }
+            }
+
+        }
+
+    }
+
+    private void validateGuaranteeParams(final JsonElement element, final DataValidatorBuilder baseDataValidator,
+            final LoanProduct loanProduct) {
+        BigDecimal mandatoryGuarantee = BigDecimal.ZERO;
+        BigDecimal minimumGuaranteeFromOwnFunds = BigDecimal.ZERO;
+        BigDecimal minimumGuaranteeFromGuarantor = BigDecimal.ZERO;
+        if (loanProduct != null) {
+            mandatoryGuarantee = loanProduct.getLoanProductGuaranteeDetails().getMandatoryGuarantee();
+            minimumGuaranteeFromOwnFunds = loanProduct.getLoanProductGuaranteeDetails().getMinimumGuaranteeFromOwnFunds();
+            minimumGuaranteeFromGuarantor = loanProduct.getLoanProductGuaranteeDetails().getMinimumGuaranteeFromGuarantor();
+        }
+
+        if (loanProduct == null || this.fromApiJsonHelper.parameterExists(LoanProductConstants.mandatoryGuaranteeParamName, element)) {
+            mandatoryGuarantee = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanProductConstants.mandatoryGuaranteeParamName,
+                    element);
+            baseDataValidator.reset().parameter(LoanProductConstants.mandatoryGuaranteeParamName).value(mandatoryGuarantee).notNull();
+            if (mandatoryGuarantee == null) {
+                mandatoryGuarantee = BigDecimal.ZERO;
+            }
+        }
+
+        if (loanProduct == null
+                || this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumGuaranteeFromGuarantorParamName, element)) {
+            minimumGuaranteeFromGuarantor = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    LoanProductConstants.minimumGuaranteeFromGuarantorParamName, element);
+            if (minimumGuaranteeFromGuarantor == null) {
+                minimumGuaranteeFromGuarantor = BigDecimal.ZERO;
+            }
+        }
+
+        if (loanProduct == null
+                || this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, element)) {
+            minimumGuaranteeFromOwnFunds = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, element);
+            if (minimumGuaranteeFromOwnFunds == null) {
+                minimumGuaranteeFromOwnFunds = BigDecimal.ZERO;
+            }
+        }
+
+        if (mandatoryGuarantee.compareTo(minimumGuaranteeFromOwnFunds.add(minimumGuaranteeFromGuarantor)) == -1) {
+            baseDataValidator.parameter(LoanProductConstants.mandatoryGuaranteeParamName).failWithCode(
+                    "must.be.greter.than.sum.of.min.funds");
+        }
+
+    }
+
+    private void validatePartialPeriodSupport(final Integer interestCalculationPeriodType, final DataValidatorBuilder baseDataValidator,
+            final JsonElement element, final LoanProduct loanProduct) {
+        if (interestCalculationPeriodType != null) {
+            final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod
+                    .fromInt(interestCalculationPeriodType);
+            boolean considerPartialPeriodUpdates = interestCalculationPeriodMethod.isDaily();
+
+            if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, element)) {
+                final Boolean considerPartialInterestEnabled = this.fromApiJsonHelper.extractBooleanNamed(
+                        LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, element);
+                baseDataValidator.reset().parameter(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName)
+                        .value(considerPartialInterestEnabled).notNull().isOneOfTheseValues(true, false);
+                final boolean considerPartialPeriods = considerPartialInterestEnabled == null ? false : considerPartialInterestEnabled;
+                if (interestCalculationPeriodMethod.isDaily()) {
+                    if (considerPartialPeriods) {
+                        baseDataValidator.reset().parameter(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName)
+                                .failWithCode("not.supported.for.daily.calcualtions");
+                    }
+                } else {
+                    considerPartialPeriodUpdates = considerPartialPeriods;
+                }
+            }
+
+            if (!considerPartialPeriodUpdates) {
+                Boolean isInterestRecalculationEnabled = null;
+                if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) {
+                    isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
+                            LoanProductConstants.isInterestRecalculationEnabledParameterName, element);
+                } else if (loanProduct != null) {
+                    isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
+                }
+                if (isInterestRecalculationEnabled != null && isInterestRecalculationEnabled) {
+                    baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName)
+                            .failWithCode("not.supported.for.selected.interest.calcualtion.type");
+                }
+
+                Boolean multiDisburseLoan = null;
+                if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.multiDisburseLoanParameterName, element)) {
+                    multiDisburseLoan = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.multiDisburseLoanParameterName,
+                            element);
+                } else if (loanProduct != null) {
+                    isInterestRecalculationEnabled = loanProduct.isMultiDisburseLoan();
+                }
+                if (multiDisburseLoan != null && multiDisburseLoan) {
+                    baseDataValidator.reset().parameter(LoanProductConstants.multiDisburseLoanParameterName)
+                            .failWithCode("not.supported.for.selected.interest.calcualtion.type");
+                }
+
+                Boolean variableInstallments = null;
+                if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowVariableInstallmentsParamName, element)) {
+                    variableInstallments = this.fromApiJsonHelper.extractBooleanNamed(
+                            LoanProductConstants.allowVariableInstallmentsParamName, element);
+                } else if (loanProduct != null) {
+                    isInterestRecalculationEnabled = loanProduct.allowVariabeInstallments();
+                }
+                if (variableInstallments != null && variableInstallments) {
+                    baseDataValidator.reset().parameter(LoanProductConstants.allowVariableInstallmentsParamName)
+                            .failWithCode("not.supported.for.selected.interest.calcualtion.type");
+                }
+
+                Boolean floatingInterestRates = null;
+                if (this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)) {
+                    floatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element);
+                } else if (loanProduct != null) {
+                    isInterestRecalculationEnabled = loanProduct.isLinkedToFloatingInterestRate();
+                }
+                if (floatingInterestRates != null && floatingInterestRates) {
+                    baseDataValidator.reset().parameter("isLinkedToFloatingInterestRates")
+                            .failWithCode("not.supported.for.selected.interest.calcualtion.type");
+                }
+            }
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LendingStrategyEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LendingStrategyEnumerations.java
new file mode 100644
index 0000000..98c79dd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LendingStrategyEnumerations.java
@@ -0,0 +1,54 @@
+/**
+ * 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.loanproduct.service;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanproduct.domain.LendingStrategy;
+
+public class LendingStrategyEnumerations {
+
+    public static EnumOptionData lendingStrategy(final Integer id) {
+        return lendingStrategy(LendingStrategy.fromInt(id));
+    }
+
+    public static EnumOptionData lendingStrategy(final LendingStrategy type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case INDIVIDUAL_LOAN:
+                optionData = new EnumOptionData(type.getId().longValue(), type.getCode(), "Individual loan");
+            break;
+            case GROUP_LOAN:
+                optionData = new EnumOptionData(type.getId().longValue(), type.getCode(), "Group loan");
+            break;
+            case JOINT_LIABILITY_LOAN:
+                optionData = new EnumOptionData(type.getId().longValue(), type.getCode(), "Joint liability loan");
+            break;
+            case LINKED_LOAN:
+                optionData = new EnumOptionData(type.getId().longValue(), type.getCode(), "Linked loan");
+            break;
+
+            default:
+                optionData = new EnumOptionData(LendingStrategy.INVALID.getId().longValue(), LendingStrategy.INVALID.getCode(), "Invalid");
+            break;
+
+        }
+        return optionData;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java
new file mode 100644
index 0000000..376e092
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java
@@ -0,0 +1,57 @@
+/**
+ * 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.loanproduct.service;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
+
+public interface LoanDropdownReadPlatformService {
+
+    List<EnumOptionData> retrieveLoanAmortizationTypeOptions();
+
+    List<EnumOptionData> retrieveLoanInterestTypeOptions();
+
+    List<EnumOptionData> retrieveLoanInterestRateCalculatedInPeriodOptions();
+
+    List<EnumOptionData> retrieveLoanTermFrequencyTypeOptions();
+
+    List<EnumOptionData> retrieveRepaymentFrequencyTypeOptions();
+    
+    List<EnumOptionData> retrieveRepaymentFrequencyOptionsForNthDayOfMonth();
+    
+    List<EnumOptionData> retrieveRepaymentFrequencyOptionsForDaysOfWeek();
+
+    List<EnumOptionData> retrieveInterestRateFrequencyTypeOptions();
+
+    Collection<TransactionProcessingStrategyData> retreiveTransactionProcessingStrategies();
+
+    List<EnumOptionData> retrieveLoanCycleValueConditionTypeOptions();
+
+    List<EnumOptionData> retrieveInterestRecalculationCompoundingTypeOptions();
+
+    List<EnumOptionData> retrieveRescheduleStrategyTypeOptions();
+    
+    List<EnumOptionData> retrieveInterestRecalculationFrequencyTypeOptions();
+    
+    List<EnumOptionData> retrivePreCloseInterestCalculationStrategyOptions();
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
new file mode 100644
index 0000000..e2d8ed5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
@@ -0,0 +1,199 @@
+/**
+ * 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.loanproduct.service;
+
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.amortizationType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestCalculationPeriodType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestRateFrequencyType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationCompoundingType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationFrequencyType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.loanCycleValueConditionType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.loanTermFrequencyType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.preCloseInterestCalculationStrategy;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyDayOfWeekType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyNthDayType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyType;
+import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.rescheduleStrategyType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
+import org.apache.fineract.portfolio.common.domain.NthDayType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository;
+import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
+import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
+import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlatformService {
+
+    private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository;
+
+    @Autowired
+    public LoanDropdownReadPlatformServiceImpl(final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository) {
+        this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveLoanAmortizationTypeOptions() {
+
+        final List<EnumOptionData> allowedAmortizationMethods = Arrays.asList(amortizationType(AmortizationMethod.EQUAL_INSTALLMENTS),
+                amortizationType(AmortizationMethod.EQUAL_PRINCIPAL));
+
+        return allowedAmortizationMethods;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveLoanInterestTypeOptions() {
+        final List<EnumOptionData> allowedRepaymentScheduleCalculationMethods = Arrays.asList(interestType(InterestMethod.FLAT),
+                interestType(InterestMethod.DECLINING_BALANCE));
+
+        return allowedRepaymentScheduleCalculationMethods;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveLoanInterestRateCalculatedInPeriodOptions() {
+
+        final List<EnumOptionData> allowedOptions = Arrays.asList(interestCalculationPeriodType(InterestCalculationPeriodMethod.DAILY),
+                interestCalculationPeriodType(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD));
+
+        return allowedOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveLoanTermFrequencyTypeOptions() {
+        final List<EnumOptionData> loanTermFrequencyOptions = Arrays.asList(loanTermFrequencyType(PeriodFrequencyType.DAYS),
+                loanTermFrequencyType(PeriodFrequencyType.WEEKS), loanTermFrequencyType(PeriodFrequencyType.MONTHS),
+                loanTermFrequencyType(PeriodFrequencyType.YEARS));
+        return loanTermFrequencyOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveRepaymentFrequencyTypeOptions() {
+
+        final List<EnumOptionData> repaymentFrequencyOptions = Arrays.asList(repaymentFrequencyType(PeriodFrequencyType.DAYS),
+                repaymentFrequencyType(PeriodFrequencyType.WEEKS), repaymentFrequencyType(PeriodFrequencyType.MONTHS));
+        return repaymentFrequencyOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveRepaymentFrequencyOptionsForNthDayOfMonth() {
+        final List<EnumOptionData> repaymentFrequencyOptions = Arrays.asList(repaymentFrequencyNthDayType(NthDayType.ONE),
+                repaymentFrequencyNthDayType(NthDayType.TWO), repaymentFrequencyNthDayType(NthDayType.THREE),
+                repaymentFrequencyNthDayType(NthDayType.FOUR));
+        return repaymentFrequencyOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveRepaymentFrequencyOptionsForDaysOfWeek() {
+
+        final List<EnumOptionData> repaymentFrequencyOptions = Arrays.asList(repaymentFrequencyDayOfWeekType(DayOfWeekType.SUNDAY),
+                repaymentFrequencyDayOfWeekType(DayOfWeekType.MONDAY), repaymentFrequencyDayOfWeekType(DayOfWeekType.TUESDAY),
+                repaymentFrequencyDayOfWeekType(DayOfWeekType.WEDNESDAY), repaymentFrequencyDayOfWeekType(DayOfWeekType.THURSDAY),
+                repaymentFrequencyDayOfWeekType(DayOfWeekType.FRIDAY), repaymentFrequencyDayOfWeekType(DayOfWeekType.SATURDAY));
+        return repaymentFrequencyOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveInterestRateFrequencyTypeOptions() {
+        // support for monthly and annual percentage rate (MPR) and (APR)
+        final List<EnumOptionData> interestRateFrequencyTypeOptions = Arrays.asList(interestRateFrequencyType(PeriodFrequencyType.MONTHS),
+                interestRateFrequencyType(PeriodFrequencyType.YEARS));
+        return interestRateFrequencyTypeOptions;
+    }
+
+    @Override
+    public Collection<TransactionProcessingStrategyData> retreiveTransactionProcessingStrategies() {
+
+        final Collection<TransactionProcessingStrategyData> strategyOptions = new ArrayList<>();
+        Sort sort = new Sort("sortOrder") ;
+        final List<LoanTransactionProcessingStrategy> strategies = this.loanTransactionProcessingStrategyRepository.findAll(sort);
+        for (final LoanTransactionProcessingStrategy strategy : strategies) {
+            strategyOptions.add(strategy.toData());
+        }
+
+        return strategyOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveLoanCycleValueConditionTypeOptions() {
+
+        final List<EnumOptionData> repaymentFrequencyOptions = Arrays.asList(
+                loanCycleValueConditionType(LoanProductValueConditionType.EQUAL),
+                loanCycleValueConditionType(LoanProductValueConditionType.GREATERTHAN));
+        return repaymentFrequencyOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveInterestRecalculationCompoundingTypeOptions() {
+
+        final List<EnumOptionData> interestRecalculationCompoundingTypeOptions = Arrays.asList(
+                interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.NONE),
+                interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.FEE),
+                interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.INTEREST),
+                interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE));
+        return interestRecalculationCompoundingTypeOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveRescheduleStrategyTypeOptions() {
+
+        final List<EnumOptionData> rescheduleStrategyTypeOptions = Arrays.asList(
+                rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT),
+                rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS),
+                rescheduleStrategyType(LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS));
+        return rescheduleStrategyTypeOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveInterestRecalculationFrequencyTypeOptions() {
+
+        final List<EnumOptionData> interestRecalculationFrequencyTypeOptions = Arrays.asList(
+                interestRecalculationFrequencyType(RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD),
+                interestRecalculationFrequencyType(RecalculationFrequencyType.DAILY),
+                interestRecalculationFrequencyType(RecalculationFrequencyType.WEEKLY),
+                interestRecalculationFrequencyType(RecalculationFrequencyType.MONTHLY));
+        return interestRecalculationFrequencyTypeOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrivePreCloseInterestCalculationStrategyOptions() {
+
+        final List<EnumOptionData> preCloseInterestCalculationStrategyOptions = Arrays.asList(
+                preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE),
+                preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE));
+        return preCloseInterestCalculationStrategyOptions;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
new file mode 100644
index 0000000..c5466e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
@@ -0,0 +1,664 @@
+/**
+ * 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.loanproduct.service;
+
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.accountdetails.service.AccountEnumerations;
+import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
+import org.apache.fineract.portfolio.common.domain.NthDayType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductParamType;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
+
+public class LoanEnumerations {
+
+    public static final String LOAN_TERM_FREQUENCY_TYPE = "loanTermFrequencyType";
+    public static final String TERM_FREQUENCY_TYPE = "termFrequencyType";
+    public static final String REPAYMENT_FREQUENCY_TYPE = "repaymentFrequencyType";
+    public static final String INTEREST_RATE_FREQUENCY_TYPE = "interestRateFrequencyType";
+    public static final String AMORTIZATION_TYPE = "amortizationType";
+    public static final String INTEREST_TYPE = "interestType";
+    public static final String INTEREST_CALCULATION_PERIOD_TYPE = "interestCalculationPeriodType";
+    public static final String PAYMENT_TYPE = "paymentType";
+    public static final String ACCOUNTING_RULE_TYPE = "accountingRule";
+    public static final String LOAN_TYPE = "loanType";
+    public static final String INTEREST_RECALCULATION_COMPOUNDING_TYPE = "interestRecalculationCompoundingType";
+    public static final String RESCHEDULE_STRATEGY_TYPE = "rescheduleStrategyType";
+
+    public static EnumOptionData loanEnumueration(final String typeName, final int id) {
+        if (typeName.equals(LOAN_TERM_FREQUENCY_TYPE)) {
+            return loanTermFrequencyType(id);
+        } else if (typeName.equals(TERM_FREQUENCY_TYPE)) {
+            return termFrequencyType(id);
+        } else if (typeName.equals(REPAYMENT_FREQUENCY_TYPE)) {
+            return repaymentFrequencyType(id);
+        } else if (typeName.equals(INTEREST_RATE_FREQUENCY_TYPE)) {
+            return interestRateFrequencyType(id);
+        } else if (typeName.equals(AMORTIZATION_TYPE)) {
+            return amortizationType(id);
+        } else if (typeName.equals(INTEREST_TYPE)) {
+            return interestType(id);
+        } else if (typeName.equals(INTEREST_CALCULATION_PERIOD_TYPE)) {
+            return interestCalculationPeriodType(id);
+        } else if (typeName.equals(ACCOUNTING_RULE_TYPE)) {
+            return AccountingEnumerations.accountingRuleType(id);
+        } else if (typeName.equals(LOAN_TYPE)) {
+            return AccountEnumerations.loanType(id);
+        } else if (typeName.equals(INTEREST_RECALCULATION_COMPOUNDING_TYPE)) {
+            return interestRecalculationCompoundingType(id);
+        } else if (typeName.equals(RESCHEDULE_STRATEGY_TYPE)) { return rescheduleStrategyType(id); }
+        return null;
+    }
+
+    public static EnumOptionData loanTermFrequencyType(final int id) {
+        return loanTermFrequencyType(PeriodFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData loanTermFrequencyType(final PeriodFrequencyType type) {
+        final String codePrefix = "loanTermFrequency.";
+        EnumOptionData optionData = null;
+        switch (type) {
+            case DAYS:
+                optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+            default:
+                optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(), PeriodFrequencyType.INVALID.getCode(),
+                        "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData termFrequencyType(final int id) {
+        return termFrequencyType(PeriodFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData termFrequencyType(final PeriodFrequencyType type) {
+        final String codePrefix = "termFrequency.";
+        EnumOptionData optionData = null;
+        switch (type) {
+            case DAYS:
+                optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+            default:
+                optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(), PeriodFrequencyType.INVALID.getCode(),
+                        "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData repaymentFrequencyType(final int id) {
+        return repaymentFrequencyType(PeriodFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData repaymentFrequencyNthDayType(final Integer id) {
+        if (id == null) { return null; }
+        return repaymentFrequencyNthDayType(NthDayType.fromInt(id));
+    }
+
+    public static EnumOptionData repaymentFrequencyNthDayType(final NthDayType type) {
+        final String codePrefix = "repaymentFrequency.";
+        long nthDayValue = type.getValue().longValue();
+        EnumOptionData optionData = null;
+        switch (type) {
+            case ONE:
+                optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "first");
+            break;
+            case TWO:
+                optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "second");
+            break;
+            case THREE:
+                optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "third");
+            break;
+            case FOUR:
+                optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "fourth");
+            break;
+            case FIVE:
+                optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "fifth");
+            break;
+            default:
+                optionData = new EnumOptionData(new Integer(0).longValue(), codePrefix + type.getCode(), "invalid");
+            break;
+        }
+
+        return optionData;
+    }
+
+    public static EnumOptionData repaymentFrequencyDayOfWeekType(final Integer id) {
+        if (id == null) { return null; }
+        return repaymentFrequencyDayOfWeekType(DayOfWeekType.fromInt(id));
+    }
+
+    public static EnumOptionData repaymentFrequencyDayOfWeekType(final DayOfWeekType type) {
+        final String codePrefix = "repaymentFrequency.";
+        EnumOptionData optionData = new EnumOptionData(type.getValue().longValue(), codePrefix + type.getCode(), type.toString());
+
+        return optionData;
+    }
+
+    public static EnumOptionData repaymentFrequencyType(final PeriodFrequencyType type) {
+        final String codePrefix = "repaymentFrequency.";
+        EnumOptionData optionData = null;
+        switch (type) {
+            case DAYS:
+                optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+            default:
+                optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(), PeriodFrequencyType.INVALID.getCode(),
+                        "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData interestRateFrequencyType(final Integer id) {
+        return interestRateFrequencyType(PeriodFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData interestRateFrequencyType(final PeriodFrequencyType type) {
+        final String codePrefix = "interestRateFrequency.";
+        EnumOptionData optionData = null;
+        switch (type) {
+            case MONTHS:
+                optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.MONTHS.getCode(), "Per month");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + PeriodFrequencyType.YEARS.getCode(), "Per year");
+            break;
+            default:
+                optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(), PeriodFrequencyType.INVALID.getCode(),
+                        "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData amortizationType(final Integer id) {
+        return amortizationType(AmortizationMethod.fromInt(id));
+    }
+
+    public static EnumOptionData amortizationType(final AmortizationMethod amortizationMethod) {
+        EnumOptionData optionData = null;
+        switch (amortizationMethod) {
+            case EQUAL_INSTALLMENTS:
+                optionData = new EnumOptionData(AmortizationMethod.EQUAL_INSTALLMENTS.getValue().longValue(),
+                        AmortizationMethod.EQUAL_INSTALLMENTS.getCode(), "Equal installments");
+            break;
+            case EQUAL_PRINCIPAL:
+                optionData = new EnumOptionData(AmortizationMethod.EQUAL_PRINCIPAL.getValue().longValue(),
+                        AmortizationMethod.EQUAL_PRINCIPAL.getCode(), "Equal principal payments");
+            break;
+            default:
+                optionData = new EnumOptionData(AmortizationMethod.INVALID.getValue().longValue(), AmortizationMethod.INVALID.getCode(),
+                        "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData interestType(final Integer id) {
+        return interestType(InterestMethod.fromInt(id));
+    }
+
+    public static EnumOptionData interestType(final InterestMethod type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case FLAT:
+                optionData = new EnumOptionData(InterestMethod.FLAT.getValue().longValue(), InterestMethod.FLAT.getCode(), "Flat");
+            break;
+            case DECLINING_BALANCE:
+                optionData = new EnumOptionData(InterestMethod.DECLINING_BALANCE.getValue().longValue(),
+                        InterestMethod.DECLINING_BALANCE.getCode(), "Declining Balance");
+            break;
+            default:
+                optionData = new EnumOptionData(InterestMethod.INVALID.getValue().longValue(), InterestMethod.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData interestCalculationPeriodType(final Integer id) {
+        return interestCalculationPeriodType(InterestCalculationPeriodMethod.fromInt(id));
+    }
+
+    public static EnumOptionData interestCalculationPeriodType(final InterestCalculationPeriodMethod type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case DAILY:
+                optionData = new EnumOptionData(InterestCalculationPeriodMethod.DAILY.getValue().longValue(),
+                        InterestCalculationPeriodMethod.DAILY.getCode(), "Daily");
+            break;
+            case SAME_AS_REPAYMENT_PERIOD:
+                optionData = new EnumOptionData(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue().longValue(),
+                        InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getCode(), "Same as repayment period");
+            break;
+            default:
+                optionData = new EnumOptionData(InterestCalculationPeriodMethod.INVALID.getValue().longValue(),
+                        InterestCalculationPeriodMethod.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static LoanTransactionEnumData transactionType(final Integer id) {
+        return transactionType(LoanTransactionType.fromInt(id));
+    }
+
+    public static LoanTransactionEnumData transactionType(final LoanTransactionType type) {
+        LoanTransactionEnumData optionData = null;
+        switch (type) {
+            case INVALID:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.INVALID.getValue().longValue(),
+                        LoanTransactionType.INVALID.getCode(), "Invalid");
+            break;
+            case DISBURSEMENT:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.DISBURSEMENT.getValue().longValue(),
+                        LoanTransactionType.DISBURSEMENT.getCode(), "Disbursement");
+            break;
+            case REPAYMENT:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.REPAYMENT.getValue().longValue(),
+                        LoanTransactionType.REPAYMENT.getCode(), "Repayment");
+            break;
+            case REPAYMENT_AT_DISBURSEMENT:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue().longValue(),
+                        LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getCode(), "Repayment (at time of disbursement)");
+            break;
+            case CONTRA:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.CONTRA.getValue().longValue(),
+                        LoanTransactionType.CONTRA.getCode(), "Reversal");
+            break;
+            case WAIVE_INTEREST:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.WAIVE_INTEREST.getValue().longValue(),
+                        LoanTransactionType.WAIVE_INTEREST.getCode(), "Waive interest");
+            break;
+            case MARKED_FOR_RESCHEDULING:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.MARKED_FOR_RESCHEDULING.getValue().longValue(),
+                        LoanTransactionType.MARKED_FOR_RESCHEDULING.getCode(), "Close (as rescheduled)");
+            break;
+            case WRITEOFF:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.WRITEOFF.getValue().longValue(),
+                        LoanTransactionType.WRITEOFF.getCode(), "Close (as written-off)");
+            break;
+            case RECOVERY_REPAYMENT:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.RECOVERY_REPAYMENT.getValue().longValue(),
+                        LoanTransactionType.RECOVERY_REPAYMENT.getCode(), "Repayment (after write-off)");
+            break;
+            case WAIVE_CHARGES:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.WAIVE_CHARGES.getValue().longValue(),
+                        LoanTransactionType.WAIVE_CHARGES.getCode(), "Waive loan charges");
+            break;
+            case ACCRUAL:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.ACCRUAL.getValue().longValue(),
+                        LoanTransactionType.ACCRUAL.getCode(), "Accrual");
+            break;
+            case APPROVE_TRANSFER:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.APPROVE_TRANSFER.getValue().longValue(),
+                        LoanTransactionType.APPROVE_TRANSFER.getCode(), "Transfer approved");
+            break;
+            case INITIATE_TRANSFER:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.INITIATE_TRANSFER.getValue().longValue(),
+                        LoanTransactionType.INITIATE_TRANSFER.getCode(), "Transfer initiated");
+            break;
+            case WITHDRAW_TRANSFER:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.WITHDRAW_TRANSFER.getValue().longValue(),
+                        LoanTransactionType.WITHDRAW_TRANSFER.getCode(), "Transfer Withdrawn");
+            break;
+            case REJECT_TRANSFER:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.REJECT_TRANSFER.getValue().longValue(),
+                        LoanTransactionType.REJECT_TRANSFER.getCode(), "Transfer Rejected");
+            break;
+            case REFUND:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.REFUND.getValue().longValue(),
+                        LoanTransactionType.REFUND.getCode(), "Transfer Refund");
+            break;
+            case CHARGE_PAYMENT:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.CHARGE_PAYMENT.getValue().longValue(),
+                        LoanTransactionType.CHARGE_PAYMENT.getCode(), "Charge Payment");
+            break;
+            case REFUND_FOR_ACTIVE_LOAN:
+                optionData = new LoanTransactionEnumData(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getValue().longValue(),
+                        LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getCode(), "Refund");
+            break;
+            default:
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData status(final LoanStatusEnumData status) {
+
+        Long id = status.id();
+        String code = status.code();
+        String value = status.value();
+
+        return new EnumOptionData(id, code, value);
+    }
+
+    public static LoanStatusEnumData status(final Integer statusId) {
+        return status(LoanStatus.fromInt(statusId));
+    }
+
+    public static LoanStatusEnumData status(final LoanStatus status) {
+        LoanStatusEnumData optionData = new LoanStatusEnumData(LoanStatus.INVALID.getValue().longValue(), LoanStatus.INVALID.getCode(),
+                "Invalid");
+        switch (status) {
+            case INVALID:
+                optionData = new LoanStatusEnumData(LoanStatus.INVALID.getValue().longValue(), LoanStatus.INVALID.getCode(), "Invalid");
+            break;
+            case SUBMITTED_AND_PENDING_APPROVAL:
+                optionData = new LoanStatusEnumData(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue().longValue(),
+                        LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
+            break;
+            case APPROVED:
+                optionData = new LoanStatusEnumData(LoanStatus.APPROVED.getValue().longValue(), LoanStatus.APPROVED.getCode(), "Approved");
+            break;
+            case ACTIVE:
+                optionData = new LoanStatusEnumData(LoanStatus.ACTIVE.getValue().longValue(), LoanStatus.ACTIVE.getCode(), "Active");
+            break;
+            case REJECTED:
+                optionData = new LoanStatusEnumData(LoanStatus.REJECTED.getValue().longValue(), LoanStatus.REJECTED.getCode(), "Rejected");
+            break;
+            case WITHDRAWN_BY_CLIENT:
+                optionData = new LoanStatusEnumData(LoanStatus.WITHDRAWN_BY_CLIENT.getValue().longValue(),
+                        LoanStatus.WITHDRAWN_BY_CLIENT.getCode(), "Withdrawn by applicant");
+            break;
+            case CLOSED_OBLIGATIONS_MET:
+                optionData = new LoanStatusEnumData(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue().longValue(),
+                        LoanStatus.CLOSED_OBLIGATIONS_MET.getCode(), "Closed (obligations met)");
+            break;
+            case CLOSED_WRITTEN_OFF:
+                optionData = new LoanStatusEnumData(LoanStatus.CLOSED_WRITTEN_OFF.getValue().longValue(),
+                        LoanStatus.CLOSED_WRITTEN_OFF.getCode(), "Closed (written off)");
+            break;
+            case CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT:
+                optionData = new LoanStatusEnumData(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getValue().longValue(),
+                        LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getCode(), "Closed (rescheduled)");
+            break;
+            case OVERPAID:
+                optionData = new LoanStatusEnumData(LoanStatus.OVERPAID.getValue().longValue(), LoanStatus.OVERPAID.getCode(), "Overpaid");
+            break;
+            case TRANSFER_IN_PROGRESS:
+                optionData = new LoanStatusEnumData(LoanStatus.TRANSFER_IN_PROGRESS.getValue().longValue(),
+                        LoanStatus.TRANSFER_IN_PROGRESS.getCode(), "Transfer in progress");
+            break;
+            case TRANSFER_ON_HOLD:
+                optionData = new LoanStatusEnumData(LoanStatus.TRANSFER_ON_HOLD.getValue().longValue(),
+                        LoanStatus.TRANSFER_ON_HOLD.getCode(), "Transfer on hold");
+            break;
+            default:
+            break;
+        }
+
+        return optionData;
+    }
+
+    public static EnumOptionData loanCycleValueConditionType(final int id) {
+        return loanCycleValueConditionType(LoanProductValueConditionType.fromInt(id));
+    }
+
+    public static EnumOptionData loanCycleValueConditionType(final LoanProductValueConditionType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case EQUAL:
+                optionData = new EnumOptionData(LoanProductValueConditionType.EQUAL.getValue().longValue(),
+                        LoanProductValueConditionType.EQUAL.getCode(), "equals");
+            break;
+            case GREATERTHAN:
+                optionData = new EnumOptionData(LoanProductValueConditionType.GREATERTHAN.getValue().longValue(),
+                        LoanProductValueConditionType.GREATERTHAN.getCode(), "greater than");
+            break;
+            default:
+                optionData = new EnumOptionData(LoanProductValueConditionType.INVALID.getValue().longValue(),
+                        LoanProductValueConditionType.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData loanCycleParamType(final int id) {
+        return loanCycleParamType(LoanProductParamType.fromInt(id));
+    }
+
+    public static EnumOptionData loanCycleParamType(final LoanProductParamType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case PRINCIPAL:
+                optionData = new EnumOptionData(LoanProductParamType.PRINCIPAL.getValue().longValue(),
+                        LoanProductParamType.PRINCIPAL.getCode(), "principal");
+            break;
+            case INTERESTRATE:
+                optionData = new EnumOptionData(LoanProductParamType.INTERESTRATE.getValue().longValue(),
+                        LoanProductParamType.INTERESTRATE.getCode(), "Interest rate");
+            break;
+            case REPAYMENT:
+                optionData = new EnumOptionData(LoanProductParamType.REPAYMENT.getValue().longValue(),
+                        LoanProductParamType.REPAYMENT.getCode(), "repayment");
+            break;
+            default:
+                optionData = new EnumOptionData(LoanProductParamType.INVALID.getValue().longValue(),
+                        LoanProductParamType.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData loanvariationType(final int id) {
+        return loanvariationType(LoanTermVariationType.fromInt(id));
+    }
+
+    public static EnumOptionData loanvariationType(final LoanTermVariationType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case EMI_AMOUNT:
+                optionData = new EnumOptionData(LoanTermVariationType.EMI_AMOUNT.getValue().longValue(),
+                        LoanTermVariationType.EMI_AMOUNT.getCode(), "emiAmount");
+            break;
+            case INTEREST_RATE:
+                optionData = new EnumOptionData(LoanTermVariationType.INTEREST_RATE.getValue().longValue(),
+                        LoanTermVariationType.INTEREST_RATE.getCode(), "interestRate");
+            break;
+            case DELETE_INSTALLMENT:
+                optionData = new EnumOptionData(LoanTermVariationType.DELETE_INSTALLMENT.getValue().longValue(),
+                        LoanTermVariationType.DELETE_INSTALLMENT.getCode(), "deleteInstallment");
+            break;
+            case DUE_DATE:
+                optionData = new EnumOptionData(LoanTermVariationType.DUE_DATE.getValue().longValue(),
+                        LoanTermVariationType.DUE_DATE.getCode(), "dueDate");
+            break;
+            case INSERT_INSTALLMENT:
+                optionData = new EnumOptionData(LoanTermVariationType.INSERT_INSTALLMENT.getValue().longValue(),
+                        LoanTermVariationType.DUE_DATE.getCode(), "insertInstallment");
+            break;
+            case PRINCIPAL_AMOUNT:
+                optionData = new EnumOptionData(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue().longValue(),
+                        LoanTermVariationType.PRINCIPAL_AMOUNT.getCode(), "principalAmount");
+            break;
+            default:
+                optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(),
+                        LoanTermVariationType.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData interestRecalculationCompoundingType(final int id) {
+        return interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.fromInt(id));
+    }
+
+    public static EnumOptionData interestRecalculationCompoundingType(final InterestRecalculationCompoundingMethod type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case FEE:
+                optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.FEE.getValue().longValue(),
+                        InterestRecalculationCompoundingMethod.FEE.getCode(), "Fee");
+            break;
+            case INTEREST:
+                optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.INTEREST.getValue().longValue(),
+                        InterestRecalculationCompoundingMethod.INTEREST.getCode(), "Interest");
+            break;
+            case INTEREST_AND_FEE:
+                optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue().longValue(),
+                        InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getCode(), "Fee and Interest");
+            break;
+            default:
+                optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.NONE.getValue().longValue(),
+                        InterestRecalculationCompoundingMethod.NONE.getCode(), "None");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData rescheduleStrategyType(final int id) {
+        return rescheduleStrategyType(LoanRescheduleStrategyMethod.fromInt(id));
+    }
+
+    public static EnumOptionData rescheduleStrategyType(final LoanRescheduleStrategyMethod type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case REDUCE_EMI_AMOUNT:
+                optionData = new EnumOptionData(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT.getValue().longValue(),
+                        LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT.getCode(), "Reduce EMI amount");
+            break;
+            case REDUCE_NUMBER_OF_INSTALLMENTS:
+                optionData = new EnumOptionData(LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS.getValue().longValue(),
+                        LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS.getCode(), "Reduce number of installments");
+            break;
+            case RESCHEDULE_NEXT_REPAYMENTS:
+                optionData = new EnumOptionData(LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS.getValue().longValue(),
+                        LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS.getCode(), "Reschedule next repayments");
+            break;
+            default:
+                optionData = new EnumOptionData(LoanRescheduleStrategyMethod.INVALID.getValue().longValue(),
+                        LoanRescheduleStrategyMethod.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData interestRecalculationFrequencyType(final int id) {
+        return interestRecalculationFrequencyType(RecalculationFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData interestRecalculationFrequencyType(final RecalculationFrequencyType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case DAILY:
+                optionData = new EnumOptionData(RecalculationFrequencyType.DAILY.getValue().longValue(),
+                        RecalculationFrequencyType.DAILY.getCode(), "Daily");
+            break;
+            case MONTHLY:
+                optionData = new EnumOptionData(RecalculationFrequencyType.MONTHLY.getValue().longValue(),
+                        RecalculationFrequencyType.MONTHLY.getCode(), "Monthly");
+            break;
+            case SAME_AS_REPAYMENT_PERIOD:
+                optionData = new EnumOptionData(RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD.getValue().longValue(),
+                        RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD.getCode(), "Same as repayment period");
+            break;
+            case WEEKLY:
+                optionData = new EnumOptionData(RecalculationFrequencyType.WEEKLY.getValue().longValue(),
+                        RecalculationFrequencyType.WEEKLY.getCode(), "Weekly");
+            break;
+            default:
+                optionData = new EnumOptionData(RecalculationFrequencyType.INVALID.getValue().longValue(),
+                        RecalculationFrequencyType.INVALID.getCode(), "Invalid");
+            break;
+
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData preCloseInterestCalculationStrategy(final int id) {
+        return preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.fromInt(id));
+    }
+
+    public static EnumOptionData preCloseInterestCalculationStrategy(final LoanPreClosureInterestCalculationStrategy type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case TILL_PRE_CLOSURE_DATE:
+                optionData = new EnumOptionData(LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE.getValue().longValue(),
+                        LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE.getCode(), "Till preclose Date");
+            break;
+            case TILL_REST_FREQUENCY_DATE:
+                optionData = new EnumOptionData(LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE.getValue().longValue(),
+                        LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE.getCode(), "Till rest Frequency Date");
+            break;
+            case NONE:
+            break;
+            default:
+            break;
+        }
+        return optionData;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformService.java
new file mode 100644
index 0000000..4808eef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformService.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.loanproduct.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+
+public interface LoanProductReadPlatformService {
+
+    Collection<LoanProductData> retrieveAllLoanProducts();
+
+    Collection<LoanProductData> retrieveAllLoanProductsForLookup(String inClass);
+    
+    Collection<LoanProductData> retrieveAllLoanProductsForLookup();
+
+    Collection<LoanProductData> retrieveAllLoanProductsForLookup(boolean activeOnly);
+
+    LoanProductData retrieveLoanProduct(Long productId);
+
+    LoanProductData retrieveNewLoanProductDetails();
+
+    Collection<LoanProductData> retrieveAllLoanProductsForCurrency(String currencyCode);
+
+    Collection<LoanProductData> retrieveAvailableLoanProductsForMix();
+
+    Collection<LoanProductData> retrieveRestrictedProductsForMix(Long productId);
+
+    Collection<LoanProductData> retrieveAllowedProductsForMix(Long productId);
+
+    Collection<LoanProductBorrowerCycleVariationData> retrieveLoanProductBorrowerCycleVariations(Long loanProductId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
new file mode 100644
index 0000000..c71286a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
@@ -0,0 +1,581 @@
+/**
+ * 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.loanproduct.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType;
+import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductGuaranteeData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductInterestRecalculationData;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductParamType;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final FineractEntityAccessUtil fineractEntityAccessUtil;
+
+    @Autowired
+    public LoanProductReadPlatformServiceImpl(final PlatformSecurityContext context,
+            final ChargeReadPlatformService chargeReadPlatformService, final RoutingDataSource dataSource,
+            final FineractEntityAccessUtil fineractEntityAccessUtil) {
+        this.context = context;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.fineractEntityAccessUtil = fineractEntityAccessUtil;
+    }
+
+    @Override
+    public LoanProductData retrieveLoanProduct(final Long loanProductId) {
+
+        try {
+            final Collection<ChargeData> charges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanProductId);
+            final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas = retrieveLoanProductBorrowerCycleVariations(loanProductId);
+            final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas);
+            final String sql = "select " + rm.loanProductSchema() + " where lp.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanProductId });
+
+        } catch (final EmptyResultDataAccessException e) {
+            throw new LoanProductNotFoundException(loanProductId);
+        }
+    }
+
+    @Override
+    public Collection<LoanProductBorrowerCycleVariationData> retrieveLoanProductBorrowerCycleVariations(final Long loanProductId) {
+        final LoanProductBorrowerCycleMapper rm = new LoanProductBorrowerCycleMapper();
+        final String sql = "select " + rm.schema() + " where bc.loan_product_id=?  order by bc.borrower_cycle_number,bc.value_condition";
+        return this.jdbcTemplate.query(sql, rm, new Object[] { loanProductId });
+    }
+
+    @Override
+    public Collection<LoanProductData> retrieveAllLoanProducts() {
+
+        this.context.authenticatedUser();
+
+        final LoanProductMapper rm = new LoanProductMapper(null, null);
+
+        String sql = "select " + rm.loanProductSchema();
+
+        // Check if branch specific products are enabled. If yes, fetch only
+        // products mapped to current user's office
+        String inClause = fineractEntityAccessUtil
+                .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.LOAN_PRODUCT);
+        if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+            sql += " where lp.id in ( " + inClause + " ) ";
+        }
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public Collection<LoanProductData> retrieveAllLoanProductsForLookup(String inClause) {
+
+        this.context.authenticatedUser();
+
+        final LoanProductLookupMapper rm = new LoanProductLookupMapper();
+
+        String sql = "select " + rm.schema();
+
+        if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+            sql += " where lp.id in ( " + inClause + " ) ";
+        }
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public Collection<LoanProductData> retrieveAllLoanProductsForLookup() {
+        return retrieveAllLoanProductsForLookup(false);
+    }
+
+    @Override
+    public Collection<LoanProductData> retrieveAllLoanProductsForLookup(final boolean activeOnly) {
+        this.context.authenticatedUser();
+
+        final LoanProductLookupMapper rm = new LoanProductLookupMapper();
+
+        String sql = "select ";
+        if (activeOnly) {
+            sql += rm.activeOnlySchema();
+        } else {
+            sql += rm.schema();
+        }
+
+        // Check if branch specific products are enabled. If yes, fetch only
+        // products mapped to current user's office
+        String inClause = fineractEntityAccessUtil
+                .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.LOAN_PRODUCT);
+        if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+            if (activeOnly) {
+                sql += " and id in ( " + inClause + " )";
+            } else {
+                sql += " where id in ( " + inClause + " ) ";
+            }
+        }
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public LoanProductData retrieveNewLoanProductDetails() {
+        return LoanProductData.sensibleDefaultsForNewLoanProductCreation();
+    }
+
+    private static final class LoanProductMapper implements RowMapper<LoanProductData> {
+
+        private final Collection<ChargeData> charges;
+
+        private final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas;
+
+        public LoanProductMapper(final Collection<ChargeData> charges,
+                final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas) {
+            this.charges = charges;
+            this.borrowerCycleVariationDatas = borrowerCycleVariationDatas;
+        }
+
+        public String loanProductSchema() {
+            return "lp.id as id, lp.fund_id as fundId, f.name as fundName, lp.loan_transaction_strategy_id as transactionStrategyId, ltps.name as transactionStrategyName, "
+                    + "lp.name as name, lp.short_name as shortName, lp.description as description, "
+                    + "lp.principal_amount as principal, lp.min_principal_amount as minPrincipal, lp.max_principal_amount as maxPrincipal, lp.currency_code as currencyCode, lp.currency_digits as currencyDigits, lp.currency_multiplesof as inMultiplesOf, "
+                    + "lp.nominal_interest_rate_per_period as interestRatePerPeriod, lp.min_nominal_interest_rate_per_period as minInterestRatePerPeriod, lp.max_nominal_interest_rate_per_period as maxInterestRatePerPeriod, lp.interest_period_frequency_enum as interestRatePerPeriodFreq, "
+                    + "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod,lp.allow_partial_period_interest_calcualtion as allowPartialPeriodInterestCalcualtion, "
+                    + "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, "
+                    + "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,lp.grace_on_arrears_ageing as graceOnArrearsAgeing,lp.overdue_days_for_npa as overdueDaysForNPA, "
+                    + "lp.min_days_between_disbursal_and_first_repayment As minimumDaysBetweenDisbursalAndFirstRepayment, "
+                    + "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, "
+                    + "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle,lp.use_borrower_cycle as useBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate,  "
+                    + "lp.allow_multiple_disbursals as multiDisburseLoan, lp.max_disbursals as maxTrancheCount, lp.max_outstanding_loan_balance as outstandingLoanBalance, "
+                    + "lp.days_in_month_enum as daysInMonth, lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as isInterestRecalculationEnabled, "
+                    + "lp.can_define_fixed_emi_amount as canDefineInstallmentAmount, lp.instalment_amount_in_multiples_of as installmentAmountInMultiplesOf, "
+                    + "lpr.pre_close_interest_calculation_strategy as preCloseInterestCalculationStrategy, "
+                    + "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, "
+                    + "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, "
+                    + "lpr.rest_freqency_date as restFrequencyDate, lpr.arrears_based_on_original_schedule as isArrearsBasedOnOriginalSchedule, "
+                    + "lpr.compounding_frequency_type_enum as compoundingFrequencyTypeEnum, lpr.compounding_frequency_interval as compoundingInterval, "
+                    + "lpr.compounding_freqency_date as compoundingFrequencyDate,  "
+                    + "lp.hold_guarantee_funds as holdGuaranteeFunds, "
+                    + "lp.principal_threshold_for_last_installment as principalThresholdForLastInstallment, "
+                    + "lpg.id as lpgId, lpg.mandatory_guarantee as mandatoryGuarantee, "
+                    + "lpg.minimum_guarantee_from_own_funds as minimumGuaranteeFromOwnFunds, lpg.minimum_guarantee_from_guarantor_funds as minimumGuaranteeFromGuarantor, "
+                    + "lp.account_moves_out_of_npa_only_on_arrears_completion as accountMovesOutOfNPAOnlyOnArrearsCompletion, "
+                    + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId, "
+                    + "lca.id as lcaId, lca.amortization_method_enum as amortizationBoolean, lca.interest_method_enum as interestMethodConfigBoolean, "
+                    + "lca.loan_transaction_strategy_id as transactionProcessingStrategyBoolean,lca.interest_calculated_in_period_enum as interestCalcPeriodBoolean, lca.arrearstolerance_amount as arrearsToleranceBoolean, "
+                    + "lca.repay_every as repaymentFrequencyBoolean, lca.moratorium as graceOnPrincipalAndInterestBoolean, lca.grace_on_arrears_ageing as graceOnArrearsAgingBoolean, "
+                    + "lp.is_linked_to_floating_interest_rates as isLinkedToFloatingInterestRates, "
+                    + "lfr.floating_rates_id as floatingRateId, "
+                    + "fr.name as floatingRateName, "
+                    + "lfr.interest_rate_differential as interestRateDifferential, "
+                    + "lfr.min_differential_lending_rate as minDifferentialLendingRate, "
+                    + "lfr.default_differential_lending_rate as defaultDifferentialLendingRate, "
+                    + "lfr.max_differential_lending_rate as maxDifferentialLendingRate, "
+                    + "lfr.is_floating_interest_rate_calculation_allowed as isFloatingInterestRateCalculationAllowed, "
+                    + "lp.allow_variabe_installments as isVariableIntallmentsAllowed, "
+                    + "lvi.minimum_gap as minimumGap, "
+                    + "lvi.maximum_gap as maximumGap "
+                    + " from m_product_loan lp "
+                    + " left join m_fund f on f.id = lp.fund_id "
+                    + " left join m_product_loan_recalculation_details lpr on lpr.product_id=lp.id "
+                    + " left join m_product_loan_guarantee_details lpg on lpg.loan_product_id=lp.id "
+                    + " left join ref_loan_transaction_processing_strategy ltps on ltps.id = lp.loan_transaction_strategy_id"
+                    + " left join m_product_loan_configurable_attributes lca on lca.loan_product_id = lp.id "
+                    + " left join m_product_loan_floating_rates as lfr on lfr.loan_product_id = lp.id "
+                    + " left join m_floating_rates as fr on lfr.floating_rates_id = fr.id "
+                    + " left join m_product_loan_variable_installment_config as lvi on lvi.loan_product_id = lp.id "
+                    + " join m_currency curr on curr.code = lp.currency_code";
+
+        }
+
+        @Override
+        public LoanProductData 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 shortName = rs.getString("shortName");
+            final String description = rs.getString("description");
+            final Long fundId = JdbcSupport.getLong(rs, "fundId");
+            final String fundName = rs.getString("fundName");
+            final Long transactionStrategyId = JdbcSupport.getLong(rs, "transactionStrategyId");
+            final String transactionStrategyName = rs.getString("transactionStrategyName");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final BigDecimal principal = rs.getBigDecimal("principal");
+            final BigDecimal minPrincipal = rs.getBigDecimal("minPrincipal");
+            final BigDecimal maxPrincipal = rs.getBigDecimal("maxPrincipal");
+            final BigDecimal tolerance = rs.getBigDecimal("tolerance");
+
+            final Integer numberOfRepayments = JdbcSupport.getInteger(rs, "numberOfRepayments");
+            final Integer minNumberOfRepayments = JdbcSupport.getInteger(rs, "minNumberOfRepayments");
+            final Integer maxNumberOfRepayments = JdbcSupport.getInteger(rs, "maxNumberOfRepayments");
+            final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaidEvery");
+
+            final Integer graceOnPrincipalPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnPrincipalPayment");
+            final Integer graceOnInterestPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestPayment");
+            final Integer graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged");
+            final Integer graceOnArrearsAgeing = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnArrearsAgeing");
+            final Integer overdueDaysForNPA = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "overdueDaysForNPA");
+            final Integer minimumDaysBetweenDisbursalAndFirstRepayment = JdbcSupport.getInteger(rs,
+                    "minimumDaysBetweenDisbursalAndFirstRepayment");
+
+            final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType");
+            final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId);
+
+            final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod");
+            final BigDecimal minInterestRatePerPeriod = rs.getBigDecimal("minInterestRatePerPeriod");
+            final BigDecimal maxInterestRatePerPeriod = rs.getBigDecimal("maxInterestRatePerPeriod");
+            final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate");
+
+            final boolean isLinkedToFloatingInterestRates = rs.getBoolean("isLinkedToFloatingInterestRates");
+            final Integer floatingRateId = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "floatingRateId");
+            final String floatingRateName = rs.getString("floatingRateName");
+            final BigDecimal interestRateDifferential = rs.getBigDecimal("interestRateDifferential");
+            final BigDecimal minDifferentialLendingRate = rs.getBigDecimal("minDifferentialLendingRate");
+            final BigDecimal defaultDifferentialLendingRate = rs.getBigDecimal("defaultDifferentialLendingRate");
+            final BigDecimal maxDifferentialLendingRate = rs.getBigDecimal("maxDifferentialLendingRate");
+            final boolean isFloatingInterestRateCalculationAllowed = rs.getBoolean("isFloatingInterestRateCalculationAllowed");
+
+            final boolean isVariableIntallmentsAllowed = rs.getBoolean("isVariableIntallmentsAllowed");
+            final Integer minimumGap = rs.getInt("minimumGap");
+            final Integer maximumGap = rs.getInt("maximumGap");
+
+            final int repaymentFrequencyTypeId = JdbcSupport.getInteger(rs, "repaymentPeriodFrequency");
+            final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(repaymentFrequencyTypeId);
+
+            final int amortizationTypeId = JdbcSupport.getInteger(rs, "amortizationMethod");
+            final EnumOptionData amortizationType = LoanEnumerations.amortizationType(amortizationTypeId);
+
+            final Integer interestRateFrequencyTypeId = JdbcSupport.getInteger(rs, "interestRatePerPeriodFreq");
+            final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(interestRateFrequencyTypeId);
+
+            final int interestTypeId = JdbcSupport.getInteger(rs, "interestMethod");
+            final EnumOptionData interestType = LoanEnumerations.interestType(interestTypeId);
+
+            final int interestCalculationPeriodTypeId = JdbcSupport.getInteger(rs, "interestCalculationInPeriodMethod");
+            final Boolean allowPartialPeriodInterestCalcualtion = rs.getBoolean("allowPartialPeriodInterestCalcualtion");
+            final EnumOptionData interestCalculationPeriodType = LoanEnumerations
+                    .interestCalculationPeriodType(interestCalculationPeriodTypeId);
+
+            final boolean includeInBorrowerCycle = rs.getBoolean("includeInBorrowerCycle");
+            final boolean useBorrowerCycle = rs.getBoolean("useBorrowerCycle");
+            final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate");
+            final LocalDate closeDate = JdbcSupport.getLocalDate(rs, "closeDate");
+            String status = "";
+            if (closeDate != null && closeDate.isBefore(DateUtils.getLocalDateOfTenant())) {
+                status = "loanProduct.inActive";
+            } else {
+                status = "loanProduct.active";
+            }
+            final String externalId = rs.getString("externalId");
+            final Collection<LoanProductBorrowerCycleVariationData> principalVariationsForBorrowerCycle = new ArrayList<>();
+            final Collection<LoanProductBorrowerCycleVariationData> interestRateVariationsForBorrowerCycle = new ArrayList<>();
+            final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>();
+            if (this.borrowerCycleVariationDatas != null) {
+                for (final LoanProductBorrowerCycleVariationData borrowerCycleVariationData : this.borrowerCycleVariationDatas) {
+                    final LoanProductParamType loanProductParamType = borrowerCycleVariationData.getParamType();
+                    if (loanProductParamType.isParamTypePrincipal()) {
+                        principalVariationsForBorrowerCycle.add(borrowerCycleVariationData);
+                    } else if (loanProductParamType.isParamTypeInterestTate()) {
+                        interestRateVariationsForBorrowerCycle.add(borrowerCycleVariationData);
+                    } else if (loanProductParamType.isParamTypeRepayment()) {
+                        numberOfRepaymentVariationsForBorrowerCycle.add(borrowerCycleVariationData);
+                    }
+                }
+            }
+
+            final Boolean multiDisburseLoan = rs.getBoolean("multiDisburseLoan");
+            final Integer maxTrancheCount = rs.getInt("maxTrancheCount");
+            final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance");
+
+            final int daysInMonth = JdbcSupport.getInteger(rs, "daysInMonth");
+            final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(daysInMonth);
+            final int daysInYear = JdbcSupport.getInteger(rs, "daysInYear");
+            final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(daysInYear);
+            final Integer installmentAmountInMultiplesOf = JdbcSupport.getInteger(rs, "installmentAmountInMultiplesOf");
+            final boolean canDefineInstallmentAmount = rs.getBoolean("canDefineInstallmentAmount");
+            final boolean isInterestRecalculationEnabled = rs.getBoolean("isInterestRecalculationEnabled");
+
+            LoanProductInterestRecalculationData interestRecalculationData = null;
+            if (isInterestRecalculationEnabled) {
+
+                final Long lprId = JdbcSupport.getLong(rs, "lprId");
+                final Long productId = JdbcSupport.getLong(rs, "productId");
+                final int compoundTypeEnumValue = JdbcSupport.getInteger(rs, "compoundType");
+                final EnumOptionData interestRecalculationCompoundingType = LoanEnumerations
+                        .interestRecalculationCompoundingType(compoundTypeEnumValue);
+                final int rescheduleStrategyEnumValue = JdbcSupport.getInteger(rs, "rescheduleStrategy");
+                final EnumOptionData rescheduleStrategyType = LoanEnumerations.rescheduleStrategyType(rescheduleStrategyEnumValue);
+                final int restFrequencyEnumValue = JdbcSupport.getInteger(rs, "restFrequencyEnum");
+                final EnumOptionData restFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(restFrequencyEnumValue);
+                final int restFrequencyInterval = JdbcSupport.getInteger(rs, "restFrequencyInterval");
+                final LocalDate restFrequencyDate = JdbcSupport.getLocalDate(rs, "restFrequencyDate");
+                final Integer compoundingFrequencyEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyTypeEnum");
+                EnumOptionData compoundingFrequencyType = null;
+                if (compoundingFrequencyEnumValue != null) {
+                    compoundingFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(compoundingFrequencyEnumValue);
+                }
+                final Integer compoundingInterval = JdbcSupport.getInteger(rs, "compoundingInterval");
+                final LocalDate compoundingFrequencyDate = JdbcSupport.getLocalDate(rs, "compoundingFrequencyDate");
+                final boolean isArrearsBasedOnOriginalSchedule = rs.getBoolean("isArrearsBasedOnOriginalSchedule");
+                final int preCloseInterestCalculationStrategyEnumValue = JdbcSupport.getInteger(rs, "preCloseInterestCalculationStrategy");
+                final EnumOptionData preCloseInterestCalculationStrategy = LoanEnumerations
+                        .preCloseInterestCalculationStrategy(preCloseInterestCalculationStrategyEnumValue);
+
+                interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId,
+                        interestRecalculationCompoundingType, rescheduleStrategyType, restFrequencyType, restFrequencyInterval,
+                        restFrequencyDate, compoundingFrequencyType, compoundingInterval, compoundingFrequencyDate,
+                        isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy);
+            }
+
+            final boolean amortization = rs.getBoolean("amortizationBoolean");
+            final boolean interestMethod = rs.getBoolean("interestMethodConfigBoolean");
+            final boolean transactionProcessingStrategy = rs.getBoolean("transactionProcessingStrategyBoolean");
+            final boolean interestCalcPeriod = rs.getBoolean("interestCalcPeriodBoolean");
+            final boolean arrearsTolerance = rs.getBoolean("arrearsToleranceBoolean");
+            final boolean repaymentFrequency = rs.getBoolean("repaymentFrequencyBoolean");
+            final boolean graceOnPrincipalAndInterest = rs.getBoolean("graceOnPrincipalAndInterestBoolean");
+            final boolean graceOnArrearsAging = rs.getBoolean("graceOnArrearsAgingBoolean");
+
+            LoanProductConfigurableAttributes allowAttributeOverrides = null;
+
+            allowAttributeOverrides = new LoanProductConfigurableAttributes(amortization, interestMethod, transactionProcessingStrategy,
+                    interestCalcPeriod, arrearsTolerance, repaymentFrequency, graceOnPrincipalAndInterest, graceOnArrearsAging);
+
+            final boolean holdGuaranteeFunds = rs.getBoolean("holdGuaranteeFunds");
+            LoanProductGuaranteeData loanProductGuaranteeData = null;
+            if (holdGuaranteeFunds) {
+                final Long lpgId = JdbcSupport.getLong(rs, "lpgId");
+                final BigDecimal mandatoryGuarantee = rs.getBigDecimal("mandatoryGuarantee");
+                final BigDecimal minimumGuaranteeFromOwnFunds = rs.getBigDecimal("minimumGuaranteeFromOwnFunds");
+                final BigDecimal minimumGuaranteeFromGuarantor = rs.getBigDecimal("minimumGuaranteeFromGuarantor");
+                loanProductGuaranteeData = LoanProductGuaranteeData.instance(lpgId, id, mandatoryGuarantee, minimumGuaranteeFromOwnFunds,
+                        minimumGuaranteeFromGuarantor);
+            }
+
+            final BigDecimal principalThresholdForLastInstallment = rs.getBigDecimal("principalThresholdForLastInstallment");
+            final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = rs.getBoolean("accountMovesOutOfNPAOnlyOnArrearsCompletion");
+
+            return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
+                    numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
+                    minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
+                    interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType,
+                    allowPartialPeriodInterestCalcualtion, fundId, fundName, transactionStrategyId, transactionStrategyName,
+                    graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, this.charges, accountingRuleType,
+                    includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId,
+                    principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
+                    numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
+                    graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
+                    interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData,
+                    principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
+                    installmentAmountInMultiplesOf, allowAttributeOverrides, isLinkedToFloatingInterestRates, floatingRateId,
+                    floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
+                    maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap,
+                    maximumGap);
+        }
+    }
+
+    private static final class LoanProductLookupMapper implements RowMapper<LoanProductData> {
+
+        public String schema() {
+            return "lp.id as id, lp.name as name from m_product_loan lp";
+        }
+
+        public String activeOnlySchema() {
+            return schema() + " where (close_date is null or close_date >= CURDATE())";
+        }
+
+        public String productMixSchema() {
+            return "lp.id as id, lp.name as name FROM m_product_loan lp left join m_product_mix pm on pm.product_id=lp.id where lp.id not IN("
+                    + "select lp.id from m_product_loan lp inner join m_product_mix pm on pm.product_id=lp.id)";
+        }
+
+        public String restrictedProductsSchema() {
+            return "pm.restricted_product_id as id, rp.name as name from m_product_mix pm join m_product_loan rp on rp.id = pm.restricted_product_id ";
+        }
+
+        public String derivedRestrictedProductsSchema() {
+            return "pm.product_id as id, lp.name as name from m_product_mix pm join m_product_loan lp on lp.id=pm.product_id";
+        }
+
+        @Override
+        public LoanProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+
+            return LoanProductData.lookup(id, name);
+        }
+    }
+
+    private static final class LoanProductBorrowerCycleMapper implements RowMapper<LoanProductBorrowerCycleVariationData> {
+
+        public String schema() {
+            return "bc.id as id,bc.borrower_cycle_number as cycleNumber,bc.value_condition as conditionType,bc.param_type as paramType,"
+                    + "bc.default_value as defaultValue,bc.max_value as maxVal,bc.min_value as minVal "
+                    + "from m_product_loan_variations_borrower_cycle bc";
+        }
+
+        @Override
+        public LoanProductBorrowerCycleVariationData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+            final Long id = rs.getLong("id");
+            final Integer cycleNumber = JdbcSupport.getInteger(rs, "cycleNumber");
+            final Integer conditionType = JdbcSupport.getInteger(rs, "conditionType");
+            final EnumOptionData conditionTypeData = LoanEnumerations.loanCycleValueConditionType(conditionType);
+            final Integer paramType = JdbcSupport.getInteger(rs, "paramType");
+            final EnumOptionData paramTypeData = LoanEnumerations.loanCycleParamType(paramType);
+            final BigDecimal defaultValue = rs.getBigDecimal("defaultValue");
+            final BigDecimal maxValue = rs.getBigDecimal("maxVal");
+            final BigDecimal minValue = rs.getBigDecimal("minVal");
+
+            final LoanProductBorrowerCycleVariationData borrowerCycleVariationData = new LoanProductBorrowerCycleVariationData(id,
+                    cycleNumber, paramTypeData, conditionTypeData, defaultValue, minValue, maxValue);
+            return borrowerCycleVariationData;
+        }
+
+    }
+
+    @Override
+    public Collection<LoanProductData> retrieveAllLoanProductsForCurrency(String currencyCode) {
+        this.context.authenticatedUser();
+
+        final LoanProductMapper rm = new LoanProductMapper(null, null);
+
+        String sql = "select " + rm.loanProductSchema() + " where lp.currency_code='" + currencyCode + "'";
+
+        // Check if branch specific products are enabled. If yes, fetch only
+        // products mapped to current user's office
+        String inClause = fineractEntityAccessUtil
+                .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.LOAN_PRODUCT);
+        if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+            sql += " and id in ( " + inClause + " ) ";
+        }
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public Collection<LoanProductData> retrieveAvailableLoanProductsForMix() {
+
+        this.context.authenticatedUser();
+
+        final LoanProductLookupMapper rm = new LoanProductLookupMapper();
+
+        String sql = "Select " + rm.productMixSchema();
+
+        // Check if branch specific products are enabled. If yes, fetch only
+        // products mapped to current user's office
+        String inClause = fineractEntityAccessUtil
+                .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.LOAN_PRODUCT);
+        if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+            sql += " and lp.id in ( " + inClause + " ) ";
+        }
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] {});
+    }
+
+    @Override
+    public Collection<LoanProductData> retrieveRestrictedProductsForMix(final Long productId) {
+
+        this.context.authenticatedUser();
+
+        final LoanProductLookupMapper rm = new LoanProductLookupMapper();
+
+        String sql = "Select " + rm.restrictedProductsSchema() + " where pm.product_id=? ";
+        // Check if branch specific products are enabled. If yes, fetch only
+        // products mapped to current user's office
+        String inClause1 = fineractEntityAccessUtil
+                .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.LOAN_PRODUCT);
+        if ((inClause1 != null) && (!(inClause1.trim().isEmpty()))) {
+            sql += " and rp.id in ( " + inClause1 + " ) ";
+        }
+
+        sql += " UNION Select " + rm.derivedRestrictedProductsSchema() + " where pm.restricted_product_id=?";
+
+        // Check if branch specific products are enabled. If yes, fetch only
+        // products mapped to current user's office
+        String inClause2 = fineractEntityAccessUtil
+                .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.LOAN_PRODUCT);
+        if ((inClause2 != null) && (!(inClause2.trim().isEmpty()))) {
+            sql += " and lp.id in ( " + inClause2 + " ) ";
+        }
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId });
+    }
+
+    @Override
+    public Collection<LoanProductData> retrieveAllowedProductsForMix(final Long productId) {
+
+        this.context.authenticatedUser();
+
+        final LoanProductLookupMapper rm = new LoanProductLookupMapper();
+
+        String sql = "Select " + rm.schema() + " where ";
+
+        // Check if branch specific products are enabled. If yes, fetch only
+        // products mapped to current user's office
+        String inClause = fineractEntityAccessUtil
+                .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.LOAN_PRODUCT);
+        if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+            sql += " lp.id in ( " + inClause + " ) and ";
+        }
+
+        sql += "lp.id not in (" + "Select pm.restricted_product_id from m_product_mix pm where pm.product_id=? " + "UNION "
+                + "Select pm.product_id from m_product_mix pm where pm.restricted_product_id=?)";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId });
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformService.java
new file mode 100644
index 0000000..16acb1d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformService.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.loanproduct.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface LoanProductWritePlatformService {
+
+    CommandProcessingResult createLoanProduct(JsonCommand command);
+
+    CommandProcessingResult updateLoanProduct(Long loanProductId, JsonCommand command);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..905cc2d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,328 @@
+/**
+ * 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.loanproduct.service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
+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.entityaccess.domain.FineractEntityAccessType;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType;
+import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
+import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper;
+import org.apache.fineract.portfolio.fund.domain.Fund;
+import org.apache.fineract.portfolio.fund.domain.FundRepository;
+import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
+import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductCannotBeModifiedDueToNonClosedLoansException;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductDateException;
+import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
+import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+@Service
+public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanProductWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(LoanProductWritePlatformServiceJpaRepositoryImpl.class);
+    private final PlatformSecurityContext context;
+    private final LoanProductDataValidator fromApiJsonDeserializer;
+    private final LoanProductRepository loanProductRepository;
+    private final AprCalculator aprCalculator;
+    private final FundRepository fundRepository;
+    private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository;
+    private final ChargeRepositoryWrapper chargeRepository;
+    private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
+    private final FineractEntityAccessUtil fineractEntityAccessUtil;
+    private final FloatingRateRepositoryWrapper floatingRateRepository;
+    private final LoanRepository loanRepository;
+
+    @Autowired
+    public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final LoanProductDataValidator fromApiJsonDeserializer, final LoanProductRepository loanProductRepository,
+            final AprCalculator aprCalculator, final FundRepository fundRepository,
+            final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository,
+            final ChargeRepositoryWrapper chargeRepository,
+            final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
+            final FineractEntityAccessUtil fineractEntityAccessUtil,
+            final FloatingRateRepositoryWrapper floatingRateRepository,
+            final LoanRepository loanRepository) {
+        this.context = context;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.loanProductRepository = loanProductRepository;
+        this.aprCalculator = aprCalculator;
+        this.fundRepository = fundRepository;
+        this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
+        this.chargeRepository = chargeRepository;
+        this.accountMappingWritePlatformService = accountMappingWritePlatformService;
+        this.fineractEntityAccessUtil = fineractEntityAccessUtil;
+        this.floatingRateRepository = floatingRateRepository;
+        this.loanRepository = loanRepository;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createLoanProduct(final JsonCommand command) {
+
+        try {
+
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+            validateInputDates(command);
+
+            final Fund fund = findFundByIdIfProvided(command.longValueOfParameterNamed("fundId"));
+
+            final Long transactionProcessingStrategyId = command.longValueOfParameterNamed("transactionProcessingStrategyId");
+            final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(transactionProcessingStrategyId);
+
+            final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
+            final List<Charge> charges = assembleListOfProductCharges(command, currencyCode);
+
+            FloatingRate floatingRate = null;
+            if(command.parameterExists("floatingRatesId")){
+            	floatingRate = this.floatingRateRepository
+            			.findOneWithNotFoundDetection(command.longValueOfParameterNamed("floatingRatesId"));
+            }
+            final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command,
+                    this.aprCalculator, floatingRate);
+            loanproduct.updateLoanProductInRelatedClasses();
+
+            this.loanProductRepository.save(loanproduct);
+
+            // save accounting mappings
+            this.accountMappingWritePlatformService.createLoanProductToGLAccountMapping(loanproduct.getId(), command);
+            // check if the office specific products are enabled. If yes, then save this savings product against a specific office
+            // i.e. this savings product is specific for this office.
+            fineractEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice(
+            		FineractEntityAccessType.OFFICE_ACCESS_TO_LOAN_PRODUCTS, 
+            		FineractEntityType.LOAN_PRODUCT, 
+            		loanproduct.getId());
+            
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(loanproduct.getId()) //
+                    .build();
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+
+    private LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) {
+        LoanTransactionProcessingStrategy strategy = null;
+        if (transactionProcessingStrategyId != null) {
+            strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId);
+            if (strategy == null) { throw new LoanTransactionProcessingStrategyNotFoundException(transactionProcessingStrategyId); }
+        }
+        return strategy;
+    }
+
+    private Fund findFundByIdIfProvided(final Long fundId) {
+        Fund fund = null;
+        if (fundId != null) {
+            fund = this.fundRepository.findOne(fundId);
+            if (fund == null) { throw new FundNotFoundException(fundId); }
+        }
+        return fund;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateLoanProduct(final Long loanProductId, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            final LoanProduct product = this.loanProductRepository.findOne(loanProductId);
+            if (product == null) { throw new LoanProductNotFoundException(loanProductId); }
+
+            this.fromApiJsonDeserializer.validateForUpdate(command.json(), product);
+            validateInputDates(command);
+
+            if(anyChangeInCriticalFloatingRateLinkedParams(command, product) 
+            		&& this.loanRepository.doNonClosedLoanAccountsExistForProduct(product.getId())){
+            	throw new LoanProductCannotBeModifiedDueToNonClosedLoansException(product.getId());
+            }
+            
+            FloatingRate floatingRate = null;
+            if(command.parameterExists("floatingRatesId")){
+            	floatingRate = this.floatingRateRepository
+            			.findOneWithNotFoundDetection(command.longValueOfParameterNamed("floatingRatesId"));
+            }
+
+            final Map<String, Object> changes = product.update(command, this.aprCalculator, floatingRate);
+
+            if (changes.containsKey("fundId")) {
+                final Long fundId = (Long) changes.get("fundId");
+                final Fund fund = findFundByIdIfProvided(fundId);
+                product.update(fund);
+            }
+
+            if (changes.containsKey("transactionProcessingStrategyId")) {
+                final Long transactionProcessingStrategyId = (Long) changes.get("transactionProcessingStrategyId");
+                final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(transactionProcessingStrategyId);
+                product.update(loanTransactionProcessingStrategy);
+            }
+
+            if (changes.containsKey("charges")) {
+                final List<Charge> productCharges = assembleListOfProductCharges(command, product.getCurrency().getCode());
+                final boolean updated = product.update(productCharges);
+                if (!updated) {
+                    changes.remove("charges");
+                }
+            }
+
+            // accounting related changes
+            final boolean accountingTypeChanged = changes.containsKey("accountingRule");
+            final Map<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
+                    .updateLoanProductToGLAccountMapping(product.getId(), command, accountingTypeChanged, product.getAccountingType());
+            changes.putAll(accountingMappingChanges);
+
+            if (!changes.isEmpty()) {
+                this.loanProductRepository.saveAndFlush(product);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(loanProductId) //
+                    .with(changes) //
+                    .build();
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return new CommandProcessingResult(Long.valueOf(-1));
+        }
+
+    }
+
+    private boolean anyChangeInCriticalFloatingRateLinkedParams(JsonCommand command, LoanProduct product) {
+        final boolean isChangeFromFloatingToFlatOrViceVersa = command.isChangeInBooleanParameterNamed("isLinkedToFloatingInterestRates", product.isLinkedToFloatingInterestRate());
+    	final boolean isChangeInCriticalFloatingRateParams = product.getFloatingRates() != null
+    			&& (command.isChangeInLongParameterNamed("floatingRatesId", product.getFloatingRates().getFloatingRate().getId())
+    					|| command.isChangeInBigDecimalParameterNamed("interestRateDifferential", product.getFloatingRates().getInterestRateDifferential()));
+		return isChangeFromFloatingToFlatOrViceVersa || isChangeInCriticalFloatingRateParams;
+	}
+
+	private List<Charge> assembleListOfProductCharges(final JsonCommand command, final String currencyCode) {
+
+        final List<Charge> charges = new ArrayList<>();
+
+        String loanProductCurrencyCode = command.stringValueOfParameterNamed("currencyCode");
+        if (loanProductCurrencyCode == null) {
+            loanProductCurrencyCode = currencyCode;
+        }
+
+        if (command.parameterExists("charges")) {
+            final JsonArray chargesArray = command.arrayOfParameterNamed("charges");
+            if (chargesArray != null) {
+                for (int i = 0; i < chargesArray.size(); i++) {
+
+                    final JsonObject jsonObject = chargesArray.get(i).getAsJsonObject();
+                    if (jsonObject.has("id")) {
+                        final Long id = jsonObject.get("id").getAsLong();
+
+                        final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(id);
+
+                        if (!loanProductCurrencyCode.equals(charge.getCurrencyCode())) {
+                            final String errorMessage = "Charge and Loan Product must have the same currency.";
+                            throw new InvalidCurrencyException("charge", "attach.to.loan.product", errorMessage);
+                        }
+                        charges.add(charge);
+                    }
+                }
+            }
+        }
+
+        return charges;
+    }
+
+    /*
+     * 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("external_id")) {
+
+            final String externalId = command.stringValueOfParameterNamed("externalId");
+            throw new PlatformDataIntegrityException("error.msg.product.loan.duplicate.externalId", "Loan Product with externalId `"
+                    + externalId + "` already exists", "externalId", externalId);
+        } else if (realCause.getMessage().contains("unq_name")) {
+
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.product.loan.duplicate.name", "Loan product with name `" + name
+                    + "` already exists", "name", name);
+        } else if (realCause.getMessage().contains("unq_short_name")) {
+
+            final String shortName = command.stringValueOfParameterNamed("shortName");
+            throw new PlatformDataIntegrityException("error.msg.product.loan.duplicate.short.name", "Loan product with short name `"
+                    + shortName + "` already exists", "shortName", shortName);
+        } else if (realCause.getMessage().contains("Duplicate entry")) {
+            final Object[] args = null;
+            throw new PlatformDataIntegrityException("error.msg.product.loan.duplicate.charge",
+                    "Loan product may only have one charge of each type.`", "charges", args);
+        }
+
+        logAsErrorUnexpectedDataIntegrityException(dve);
+        throw new PlatformDataIntegrityException("error.msg.product.loan.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private void validateInputDates(final JsonCommand command) {
+        final LocalDate startDate = command.localDateValueOfParameterNamed("startDate");
+        final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate");
+
+        if (startDate != null && closeDate != null) {
+            if (closeDate.isBefore(startDate)) { throw new LoanProductDateException(startDate.toString(), closeDate.toString()); }
+        }
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
+        logger.error(dve.getMessage(), dve);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/MeetingApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/MeetingApiConstants.java
new file mode 100644
index 0000000..51e281c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/MeetingApiConstants.java
@@ -0,0 +1,57 @@
+/**
+ * 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.meeting;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class MeetingApiConstants {
+
+    public static final String MEETING_RESOURCE_NAME = "meeting";
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // meetings parameters
+    public static final String idParamName = "id";
+    public static final String meetingDateParamName = "meetingDate";
+    public static final String calendarIdParamName = "calendarId";
+    public static final String clientsAttendanceParamName = "clientsAttendance";
+
+    // attendance parameters
+    public static final String clientIdParamName = "clientId";
+    public static final String attendanceTypeParamName = "attendanceType";
+
+    // attendance response parameters
+    public static final String clientsAttendance = "clientsAttendance";
+
+    // template response parameters
+    public static final String clients = "clients";
+    public static final String calendarData = "calendarData";
+    public static final String attendanceTypeOptions = "attendanceTypeOptions";
+
+    public static final Set<String> MEETING_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(meetingDateParamName,
+            localeParamName, dateFormatParamName, calendarIdParamName, clientsAttendanceParamName));
+
+    public static final Set<String> MEETING_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, meetingDateParamName,
+            clientsAttendance, clients, calendarData, attendanceTypeOptions));
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/api/MeetingsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/api/MeetingsApiResource.java
new file mode 100644
index 0000000..b3d551d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/api/MeetingsApiResource.java
@@ -0,0 +1,276 @@
+/**
+ * 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.meeting.api;
+
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.MEETING_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.MEETING_RESPONSE_DATA_PARAMETERS;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+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.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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.exception.CalendarEntityTypeNotSupportedException;
+import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.meeting.attendance.data.ClientAttendanceData;
+import org.apache.fineract.portfolio.meeting.attendance.service.AttendanceDropdownReadPlatformService;
+import org.apache.fineract.portfolio.meeting.attendance.service.ClientAttendanceReadPlatformService;
+import org.apache.fineract.portfolio.meeting.data.MeetingData;
+import org.apache.fineract.portfolio.meeting.exception.MeetingNotSupportedResourceException;
+import org.apache.fineract.portfolio.meeting.service.MeetingReadPlatformService;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/{entityType}/{entityId}/meetings")
+@Component
+@Scope("singleton")
+public class MeetingsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final MeetingReadPlatformService readPlatformService;
+    private final ClientAttendanceReadPlatformService attendanceReadPlatformService;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final CalendarReadPlatformService calendarReadPlatformService;
+    private final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService;
+    private final DefaultToApiJsonSerializer<MeetingData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public MeetingsApiResource(final PlatformSecurityContext context, final MeetingReadPlatformService readPlatformService,
+            final ClientAttendanceReadPlatformService attendanceReadPlatformService,
+            final ClientReadPlatformService clientReadPlatformService, final CalendarReadPlatformService calendarReadPlatformService,
+            final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService,
+            final DefaultToApiJsonSerializer<MeetingData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.attendanceReadPlatformService = attendanceReadPlatformService;
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.calendarReadPlatformService = calendarReadPlatformService;
+        this.attendanceDropdownReadPlatformService = attendanceDropdownReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @QueryParam("calendarId") final Long calendarId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(MEETING_RESOURCE_NAME);
+        final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue();
+        Collection<ClientData> clients = null;
+        CalendarData calendarData = null;
+
+        if (CalendarEntityType.isGroup(entityType)) {
+            clients = this.clientReadPlatformService.retrieveActiveClientMembersOfGroup(entityId);
+        } else if (CalendarEntityType.isCenter(entityType)) {
+            clients = this.clientReadPlatformService.retrieveActiveClientMembersOfCenter(entityId);
+        } else {
+            final String defaultUserMessage = "Meeting attendance is not supported for the resource " + entityType
+                    + ". The supported resources are [" + CalendarEntityType.GROUPS.name() + ", " + CalendarEntityType.CENTERS.name() + "]";
+            throw new MeetingNotSupportedResourceException(defaultUserMessage, CalendarEntityType.GROUPS.name(),
+                    CalendarEntityType.CENTERS.name());
+        }
+
+        if (calendarId != null) {
+            calendarData = this.calendarReadPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId);
+            final boolean withHistory = true;
+            final Collection<LocalDate> recurringDates = this.calendarReadPlatformService.generateRecurringDates(calendarData, withHistory,
+                    DateUtils.getLocalDateOfTenant());
+            final Collection<LocalDate> nextTenRecurringDates = this.calendarReadPlatformService
+                    .generateNextTenRecurringDates(calendarData);
+            final LocalDate recentEligibleMeetingDate = null;
+            calendarData = CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
+        }
+
+        final MeetingData meetingData = MeetingData.template(clients, calendarData,
+                this.attendanceDropdownReadPlatformService.retrieveAttendanceTypeOptions());
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, meetingData, MEETING_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveMeetings(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @QueryParam("limit") final Integer limit, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(MEETING_RESOURCE_NAME);
+
+        final Collection<MeetingData> meetingsData = this.readPlatformService.retrieveMeetingsByEntity(entityId, CalendarEntityType
+                .valueOf(entityType.toUpperCase()).getValue(), limit);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, meetingsData, MEETING_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{meetingId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveMeeting(@PathParam("meetingId") final Long meetingId, @PathParam("entityType") final String entityType,
+            @PathParam("entityId") final Long entityId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(MEETING_RESOURCE_NAME);
+        final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue();
+        MeetingData meetingData = this.readPlatformService.retrieveMeeting(meetingId, entityId, entityTypeId);
+        final Collection<ClientAttendanceData> clientsAttendance = this.attendanceReadPlatformService
+                .retrieveClientAttendanceByMeetingId(meetingId);
+        meetingData = MeetingData.withClientsAttendanceAndAttendanceTypeOptions(meetingData, clientsAttendance,
+                this.attendanceDropdownReadPlatformService.retrieveAttendanceTypeOptions());
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, meetingData, MEETING_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createMeeting(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            final String apiRequestBodyAsJson) {
+
+        final CalendarEntityType calendarEntityType = CalendarEntityType.getEntityType(entityType);
+        if (calendarEntityType == null) { throw new CalendarEntityTypeNotSupportedException(entityType); }
+
+        final CommandWrapper resourceDetails = getResourceDetails(calendarEntityType, entityId);
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createMeeting(resourceDetails, entityType, entityId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @POST
+    @Path("{meetingId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String performMeetingCommands(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @PathParam("meetingId") final Long meetingId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "saveOrUpdateAttendance")) {
+            final CommandWrapper commandRequest = builder.saveOrUpdateAttendance(meetingId, entityType, entityId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "saveOrUpdateAttendance" });
+        }
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @PUT
+    @Path("{meetingId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateMeeting(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @PathParam("meetingId") final Long meetingId, final String jsonRequestBody) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateMeeting(entityType, entityId, meetingId)
+                .withJson(jsonRequestBody).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{meetingId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteMeeting(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
+            @PathParam("meetingId") final Long meetingId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteMeeting(entityType, entityId, meetingId).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);
+    }
+
+    private CommandWrapper getResourceDetails(final CalendarEntityType type, final Long entityId) {
+        CommandWrapperBuilder resourceDetails = new CommandWrapperBuilder();
+        switch (type) {
+            case CENTERS:
+                resourceDetails.withGroupId(entityId);
+            break;
+            case CLIENTS:
+                resourceDetails.withClientId(entityId);
+            break;
+            case GROUPS:
+                resourceDetails.withGroupId(entityId);
+            break;
+            case LOANS:
+                resourceDetails.withLoanId(entityId);
+            break;
+            case SAVINGS:
+                resourceDetails.withSavingsId(entityId);
+            break;
+            case INVALID:
+            break;
+            case LOAN_RECALCULATION_REST_DETAIL:
+            break;
+            default:
+            break;
+        }
+        return resourceDetails.build();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/AttendanceType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/AttendanceType.java
new file mode 100644
index 0000000..941d26d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/AttendanceType.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.portfolio.meeting.attendance;
+
+import org.apache.fineract.portfolio.meeting.attendance.domain.ClientAttendance;
+
+/**
+ * An enumeration of {@link ClientAttendance} type.
+ */
+public enum AttendanceType {
+
+    INVALID(0, "attendanceType.invalid"), //
+    PRESENT(1, "attendanceType.present"), //
+    ABSENT(2, "attendanceType.absent"), //
+    APPROVED(3, "attendanceType.approved"), //
+    LEAVE(4, "attendanceType.leave"), //
+    LATE(5, "attendanceType.late");
+
+    private final Integer value;
+    private final String code;
+
+    private AttendanceType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static AttendanceType fromInt(final Integer attendanceTypeId) {
+
+        if (attendanceTypeId == null) { return AttendanceType.INVALID; }
+
+        AttendanceType attendanceType = AttendanceType.INVALID;
+        switch (attendanceTypeId) {
+            case 1:
+                attendanceType = AttendanceType.PRESENT;
+            break;
+            case 2:
+                attendanceType = AttendanceType.ABSENT;
+            break;
+            case 3:
+                attendanceType = AttendanceType.APPROVED;
+            break;
+            case 4:
+                attendanceType = AttendanceType.LEAVE;
+            break;
+            case 5:
+                attendanceType = AttendanceType.LATE;
+            break;
+        }
+        return attendanceType;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/data/ClientAttendanceData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/data/ClientAttendanceData.java
new file mode 100644
index 0000000..db2a6e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/data/ClientAttendanceData.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.meeting.attendance.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+/**
+ * Immutable data object representing a ClientAttendance.
+ */
+public class ClientAttendanceData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final Long clientId;
+    @SuppressWarnings("unused")
+    private final String clientName;
+    @SuppressWarnings("unused")
+    private final EnumOptionData attendanceType;
+
+    public static ClientAttendanceData instance(final Long id, final Long clientId, final String clientName,
+            final EnumOptionData attendanceType) {
+        return new ClientAttendanceData(id, clientId, clientName, attendanceType);
+    }
+
+    private ClientAttendanceData(final Long id, final Long clientId, final String clientName, final EnumOptionData attendanceType) {
+        this.id = id;
+        this.clientId = clientId;
+        this.clientName = clientName;
+        this.attendanceType = attendanceType;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/domain/ClientAttendance.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/domain/ClientAttendance.java
new file mode 100644
index 0000000..d463190
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/domain/ClientAttendance.java
@@ -0,0 +1,72 @@
+/**
+ * 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.meeting.attendance.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.meeting.domain.Meeting;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_client_attendance", uniqueConstraints = { @UniqueConstraint(columnNames = { "client_id", "meeting_id" }, name = "unique_client_meeting_attendance") })
+public class ClientAttendance extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "client_id", nullable = false)
+    private Client client;
+
+    @ManyToOne
+    @JoinColumn(name = "meeting_id", nullable = false)
+    private Meeting meeting;
+
+    @Column(name = "attendance_type_enum", nullable = false)
+    private Integer attendanceTypeId;
+
+    protected ClientAttendance() {
+
+    }
+
+    public static ClientAttendance createClientAttendance(final Client client, final Meeting meeting, final Integer attendanceTypeId) {
+        return new ClientAttendance(client, meeting, attendanceTypeId);
+    }
+
+    private ClientAttendance(final Client client, final Meeting meeting, final Integer attendanceTypeId) {
+        this.client = client;
+        this.meeting = meeting;
+        this.attendanceTypeId = attendanceTypeId;
+    }
+
+    public Long clientId() {
+        return this.client.getId();
+    }
+
+    public void updateAttendanceTypeId(final Integer attendanceTypeId) {
+        this.attendanceTypeId = attendanceTypeId;
+    }
+
+    public Integer getAttendanceTypeId() {
+        return this.attendanceTypeId;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/domain/ClientAttendanceRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/domain/ClientAttendanceRepository.java
new file mode 100644
index 0000000..201f903
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/domain/ClientAttendanceRepository.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.meeting.attendance.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ClientAttendanceRepository extends JpaRepository<ClientAttendance, Long>, JpaSpecificationExecutor<ClientAttendance> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java
new file mode 100644
index 0000000..f22f53f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.meeting.attendance.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public interface AttendanceDropdownReadPlatformService {
+
+    List<EnumOptionData> retrieveAttendanceTypeOptions();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.java
new file mode 100644
index 0000000..baad705
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.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.portfolio.meeting.attendance.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.meeting.attendance.AttendanceType;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AttendanceDropdownReadPlatformServiceImpl implements AttendanceDropdownReadPlatformService {
+
+    @Override
+    public List<EnumOptionData> retrieveAttendanceTypeOptions() {
+        return AttendanceEnumerations.attendanceType(AttendanceType.values());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/AttendanceEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/AttendanceEnumerations.java
new file mode 100644
index 0000000..93f9c09
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/AttendanceEnumerations.java
@@ -0,0 +1,72 @@
+/**
+ * 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.meeting.attendance.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.meeting.attendance.AttendanceType;
+
+public class AttendanceEnumerations {
+
+    public static EnumOptionData attendanceType(final int attendanceType) {
+        return attendanceType(AttendanceType.fromInt(attendanceType));
+    }
+
+    public static EnumOptionData attendanceType(final AttendanceType attendanceType) {
+
+        EnumOptionData optionData = new EnumOptionData(AttendanceType.INVALID.getValue().longValue(), AttendanceType.INVALID.getCode(),
+                "Invalid");
+
+        switch (attendanceType) {
+            case INVALID:
+                optionData = new EnumOptionData(AttendanceType.INVALID.getValue().longValue(), AttendanceType.INVALID.getCode(), "Invalid");
+            break;
+            case PRESENT:
+                optionData = new EnumOptionData(AttendanceType.PRESENT.getValue().longValue(), AttendanceType.PRESENT.getCode(), "Present");
+            break;
+            case ABSENT:
+                optionData = new EnumOptionData(AttendanceType.ABSENT.getValue().longValue(), AttendanceType.ABSENT.getCode(), "Absent");
+            break;
+            case APPROVED:
+                optionData = new EnumOptionData(AttendanceType.APPROVED.getValue().longValue(), AttendanceType.APPROVED.getCode(),
+                        "Approved");
+            break;
+            case LEAVE:
+                optionData = new EnumOptionData(AttendanceType.LEAVE.getValue().longValue(), AttendanceType.LEAVE.getCode(), "Leave");
+            break;
+            case LATE:
+                optionData = new EnumOptionData(AttendanceType.LATE.getValue().longValue(), AttendanceType.LATE.getCode(), "Late");
+            break;
+        }
+        return optionData;
+    }
+
+    public static List<EnumOptionData> attendanceType(final AttendanceType[] attendanceTypes) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final AttendanceType attendanceType : attendanceTypes) {
+            if (attendanceType.getValue().equals(AttendanceType.INVALID.getValue())) {
+                continue;
+            }
+            optionDatas.add(attendanceType(attendanceType));
+        }
+        return optionDatas;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java
new file mode 100644
index 0000000..0a2d9d3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.meeting.attendance.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.meeting.attendance.data.ClientAttendanceData;
+
+public interface ClientAttendanceReadPlatformService {
+
+    Collection<ClientAttendanceData> retrieveClientAttendanceByMeetingId(final Long meetingId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java
new file mode 100644
index 0000000..eedd692
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java
@@ -0,0 +1,72 @@
+/**
+ * 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.meeting.attendance.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.portfolio.meeting.attendance.data.ClientAttendanceData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ClientAttendanceReadPlatformServiceImpl implements ClientAttendanceReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public ClientAttendanceReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class ClientAttendanceDataMapper implements RowMapper<ClientAttendanceData> {
+
+        public String schema() {
+
+            return " select ca.id as id, ca.client_id as clientId, ca.attendance_type_enum as attendanceTypeId, "
+                    + " c.display_name as clientName from m_meeting m inner join m_client_attendance ca on m.id = ca.meeting_id "
+                    + " inner join m_client c on ca.client_id=c.id ";
+        }
+
+        @Override
+        public ClientAttendanceData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long clientId = rs.getLong("clientId");
+            final Integer attendanceTypeId = rs.getInt("attendanceTypeId");
+            final String clientName = rs.getString("clientName");
+            final EnumOptionData attendanceType = AttendanceEnumerations.attendanceType(attendanceTypeId);
+            return ClientAttendanceData.instance(id, clientId, clientName, attendanceType);
+        }
+    }
+
+    @Override
+    public Collection<ClientAttendanceData> retrieveClientAttendanceByMeetingId(final Long meetingId) {
+        final ClientAttendanceDataMapper rm = new ClientAttendanceDataMapper();
+        final String sql = rm.schema() + " where m.id = ? ";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { meetingId });
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/data/MeetingData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/data/MeetingData.java
new file mode 100644
index 0000000..7018e68
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/data/MeetingData.java
@@ -0,0 +1,87 @@
+/**
+ * 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.meeting.data;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.meeting.attendance.data.ClientAttendanceData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a Meeting.
+ */
+public class MeetingData {
+
+    private final Long id;
+    private final LocalDate meetingDate;
+    private final Collection<ClientAttendanceData> clientsAttendance;
+
+    // template data
+    private final Collection<ClientData> clients;
+    private final CalendarData calendarData;
+    @SuppressWarnings("unused")
+    private final List<EnumOptionData> attendanceTypeOptions;
+
+    public static MeetingData instance(final Long id, final LocalDate meetingDate) {
+        final Collection<ClientAttendanceData> clientsAttendance = null;
+        final Collection<ClientData> clients = null;
+        final CalendarData calendarData = null;
+        final List<EnumOptionData> attendanceTypeOptions = null;
+        return new MeetingData(id, meetingDate, clientsAttendance, clients, calendarData, attendanceTypeOptions);
+    }
+
+    public static MeetingData withClientsAttendanceAndAttendanceTypeOptions(final MeetingData meetingData,
+            final Collection<ClientAttendanceData> clientsAttendance, final List<EnumOptionData> attendanceTypesOptions) {
+        return new MeetingData(meetingData.id, meetingData.meetingDate, clientsAttendance, meetingData.clients, meetingData.calendarData,
+                attendanceTypesOptions);
+    }
+
+    public static MeetingData template(final Collection<ClientData> clients, final CalendarData calendarData,
+            final List<EnumOptionData> attendanceTypeOptions) {
+        final Long id = null;
+        final LocalDate meetingDate = null;
+        final Collection<ClientAttendanceData> clientsAttendance = null;
+        return new MeetingData(id, meetingDate, clientsAttendance, clients, calendarData, attendanceTypeOptions);
+    }
+
+    public static MeetingData withAttendanceTypeOptions(final MeetingData meetingData, final List<EnumOptionData> attendanceTypeOptions) {
+
+        return new MeetingData(meetingData.id, meetingData.meetingDate, meetingData.clientsAttendance, meetingData.clients,
+                meetingData.calendarData, attendanceTypeOptions);
+    }
+
+    private MeetingData(final Long id, final LocalDate meetingDate, final Collection<ClientAttendanceData> clientsAttendance,
+            final Collection<ClientData> clients, final CalendarData calendarData, final List<EnumOptionData> attendanceTypeOptions) {
+        this.id = id;
+        this.meetingDate = meetingDate;
+        this.clientsAttendance = clientsAttendance;
+        this.clients = clients;
+        this.calendarData = calendarData;
+        this.attendanceTypeOptions = attendanceTypeOptions;
+    }
+
+    public LocalDate getMeetingDate() {
+        return this.meetingDate;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/data/MeetingDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/data/MeetingDataValidator.java
new file mode 100644
index 0000000..abbd12e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/data/MeetingDataValidator.java
@@ -0,0 +1,155 @@
+/**
+ * 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.meeting.data;
+
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.MEETING_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.MEETING_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.attendanceTypeParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.calendarIdParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.clientIdParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.clientsAttendanceParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.meetingDateParamName;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class MeetingDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public MeetingDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForCreate(final JsonCommand command) {
+        final String json = command.json();
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(MEETING_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String meetingDateStr = this.fromApiJsonHelper.extractStringNamed(meetingDateParamName, element);
+        baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDateStr).notBlank();
+
+        if (!StringUtils.isBlank(meetingDateStr)) {
+            final LocalDate meetingDate = this.fromApiJsonHelper.extractLocalDateNamed(meetingDateParamName, element);
+            baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDate).notNull();
+        }
+
+        final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParamName, element);
+        baseDataValidator.reset().parameter(calendarIdParamName).value(calendarId).notNull();
+
+        validateAttendanceDetails(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdate(final JsonCommand command) {
+        final String json = command.json();
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(MEETING_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(meetingDateParamName, element)) {
+            final String meetingDateStr = this.fromApiJsonHelper.extractStringNamed(meetingDateParamName, element);
+            baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDateStr).notBlank();
+
+            if (!StringUtils.isBlank(meetingDateStr)) {
+                final LocalDate meetingDate = this.fromApiJsonHelper.extractLocalDateNamed(meetingDateParamName, element);
+                baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDate).notNull();
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(calendarIdParamName, element)) {
+            final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParamName, element);
+            baseDataValidator.reset().parameter(calendarIdParamName).value(calendarId).notNull();
+        }
+
+        validateAttendanceDetails(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdateAttendance(final JsonCommand command) {
+        final String json = command.json();
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(MEETING_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateAttendanceDetails(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private void validateAttendanceDetails(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has(clientsAttendanceParamName) && topLevelJsonElement.get(clientsAttendanceParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(clientsAttendanceParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject attendanceElement = array.get(i).getAsJsonObject();
+                    final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, attendanceElement);
+                    final Long attendanceType = this.fromApiJsonHelper.extractLongNamed(attendanceTypeParamName, attendanceElement);
+                    baseDataValidator.reset().parameter(clientsAttendanceParamName + "[" + i + "]." + clientIdParamName).value(clientId)
+                            .notNull().integerGreaterThanZero();
+                    baseDataValidator.reset().parameter(clientsAttendanceParamName + "[" + i + "]." + attendanceTypeParamName)
+                            .value(attendanceType).notNull().integerGreaterThanZero();
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/Meeting.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/Meeting.java
new file mode 100644
index 0000000..be914ed
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/Meeting.java
@@ -0,0 +1,199 @@
+/**
+ * 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.meeting.domain;
+
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.attendanceTypeParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.clientIdParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.clientsAttendanceParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.meetingDateParamName;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException;
+import org.apache.fineract.portfolio.meeting.attendance.domain.ClientAttendance;
+import org.apache.fineract.portfolio.meeting.exception.MeetingDateException;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_meeting", uniqueConstraints = { @UniqueConstraint(columnNames = { "calendar_instance_id", "meeting_date" }, name = "unique_calendar_instance_id_meeting_date") })
+public class Meeting extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "calendar_instance_id", nullable = false)
+    private CalendarInstance calendarInstance;
+
+    @Column(name = "meeting_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date meetingDate;
+
+    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "meeting", orphanRemoval = true)
+    private Set<ClientAttendance> clientsAttendance;
+
+    protected Meeting() {
+        //
+    }
+
+    private Meeting(final CalendarInstance calendarInstance, final Date meetingDate) {
+        this.calendarInstance = calendarInstance;
+        this.meetingDate = meetingDate;
+    }
+
+    public static Meeting createNew(final CalendarInstance calendarInstance, final Date meetingDate, Boolean isTransactionDateOnNonMeetingDate) {
+    	
+    	if (!isTransactionDateOnNonMeetingDate && !isValidMeetingDate(calendarInstance, meetingDate)) { throw new NotValidRecurringDateException("meeting", "The date '"
+                + meetingDate + "' is not a valid meeting date.", meetingDate); }
+        return new Meeting(calendarInstance, meetingDate);
+    }
+
+    public void associateClientsAttendance(final Collection<ClientAttendance> clientsAttendance) {
+        // do not allow to capture attendance in advance.
+        if (isMeetingDateAfter(DateUtils.getLocalDateOfTenant())) {
+            final String errorMessage = "Attendance cannot be in the future.";
+            throw new MeetingDateException("cannot.be.a.future.date", errorMessage, getMeetingDateLocalDate());
+        }
+        this.clientsAttendance = new HashSet<>(clientsAttendance);
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+
+        if (command.isChangeInLocalDateParameterNamed(meetingDateParamName, getMeetingDateLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(meetingDateParamName);
+            final LocalDate newValue = command.localDateValueOfParameterNamed(meetingDateParamName);
+            actualChanges.put(meetingDateParamName, valueAsInput);
+            actualChanges.put("dateFormat", dateFormatAsInput);
+            actualChanges.put("locale", localeAsInput);
+            this.meetingDate = newValue.toDate();
+
+            if (!isValidMeetingDate(this.calendarInstance, this.meetingDate)) { throw new NotValidRecurringDateException("meeting",
+                    "Not a valid meeting date", this.meetingDate); }
+
+        }
+
+        return actualChanges;
+    }
+
+    public Map<String, Object> updateAttendance(final Collection<ClientAttendance> clientsAttendance) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(1);
+        final Map<String, Object> clientAttendanceChanges = new LinkedHashMap<>(clientsAttendance.size());
+
+        updateAttendanceLoop: for (final ClientAttendance clientAttendance : clientsAttendance) {
+            if (this.clientsAttendance == null) {
+                this.clientsAttendance = new HashSet<>();
+            }
+            for (final ClientAttendance clientAttendanceOriginal : this.clientsAttendance) {
+                if (clientAttendanceOriginal.clientId().equals(clientAttendance.clientId())) {
+                    final Integer newValue = clientAttendance.getAttendanceTypeId();
+                    if (!newValue.equals(clientAttendanceOriginal.getAttendanceTypeId())) {
+                        clientAttendanceOriginal.updateAttendanceTypeId(newValue);
+                        final Map<String, Object> clientAttendanceChange = new LinkedHashMap<>(2);
+                        clientAttendanceChange.put(clientIdParamName, clientAttendanceOriginal.clientId());
+                        clientAttendanceChange.put(attendanceTypeParamName, newValue);
+                        clientAttendanceChanges.put(clientAttendanceOriginal.clientId().toString(), clientAttendanceChange);
+                    }
+                    continue updateAttendanceLoop;
+                }
+            }
+
+            final Map<String, Object> clientAttendanceChange = new LinkedHashMap<>();
+            clientAttendanceChange.put(clientIdParamName, clientAttendance.clientId());
+            clientAttendanceChange.put(attendanceTypeParamName, clientAttendance.getAttendanceTypeId());
+            clientAttendanceChanges.put(clientAttendance.clientId().toString(), clientAttendanceChange);
+            // New attendance record
+            this.clientsAttendance.add(clientAttendance);
+        }
+
+        actualChanges.put(clientsAttendanceParamName, clientAttendanceChanges);
+
+        return actualChanges;
+    }
+
+    public Long entityId() {
+        return this.calendarInstance.getEntityId();
+    }
+
+    public boolean isCenterEntity() {
+        return CalendarEntityType.isCenter(this.calendarInstance.getEntityTypeId());
+    }
+
+    public boolean isGroupEntity() {
+        return CalendarEntityType.isGroup(this.calendarInstance.getEntityTypeId());
+    }
+
+    public LocalDate getMeetingDateLocalDate() {
+        LocalDate meetingDateLocalDate = null;
+        if (this.meetingDate != null) {
+            meetingDateLocalDate = LocalDate.fromDateFields(this.meetingDate);
+        }
+        return meetingDateLocalDate;
+    }
+
+    public Date getMeetingDate() {
+        return this.meetingDate;
+    }
+
+    public boolean isMeetingDateBefore(final LocalDate newStartDate) {
+        return this.meetingDate != null && newStartDate != null && getMeetingDateLocalDate().isBefore(newStartDate) ? true : false;
+    }
+
+    private static boolean isValidMeetingDate(final CalendarInstance calendarInstance, final Date meetingDate) {
+        final Calendar calendar = calendarInstance.getCalendar();
+        LocalDate meetingDateLocalDate = null;
+        if (meetingDate != null) {
+            meetingDateLocalDate = LocalDate.fromDateFields(meetingDate);
+        }
+
+        if (meetingDateLocalDate == null || !calendar.isValidRecurringDate(meetingDateLocalDate)) { return false; }
+        return true;
+    }
+
+    private boolean isMeetingDateAfter(final LocalDate date) {
+        return getMeetingDateLocalDate().isAfter(date);
+    }
+
+    public Collection<ClientAttendance> getClientsAttendance() {
+        return this.clientsAttendance;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/MeetingRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/MeetingRepository.java
new file mode 100644
index 0000000..a0108b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/MeetingRepository.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.meeting.domain;
+
+import java.util.Date;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface MeetingRepository extends JpaRepository<Meeting, Long>, JpaSpecificationExecutor<Meeting> {
+
+    Meeting findByCalendarInstanceIdAndMeetingDate(Long calendarInstanceId, Date meetingDate);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/MeetingRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/MeetingRepositoryWrapper.java
new file mode 100644
index 0000000..a80e263
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/MeetingRepositoryWrapper.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.portfolio.meeting.domain;
+
+import org.apache.fineract.portfolio.meeting.exception.MeetingNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link MeetingRepository} that is responsible for checking if
+ * {@link Meeting} is returned when using <code>findOne</code> repository method
+ * and throwing an appropriate not found exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link MeetingRepository} is required.
+ * </p>
+ */
+@Service
+public class MeetingRepositoryWrapper {
+
+    private final MeetingRepository repository;
+
+    @Autowired
+    public MeetingRepositoryWrapper(final MeetingRepository repository) {
+        this.repository = repository;
+    }
+
+    public Meeting findOneWithNotFoundDetection(final Long meetingId) {
+        final Meeting meeting = this.repository.findOne(meetingId);
+        if (meeting == null) { throw new MeetingNotFoundException(meetingId); }
+        return meeting;
+    }
+
+    public void save(final Meeting meeting) {
+        this.repository.save(meeting);
+    }
+
+    public void delete(final Meeting meeting) {
+        this.repository.delete(meeting);
+    }
+
+    public void saveAndFlush(final Meeting meeting) {
+        this.repository.saveAndFlush(meeting);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/exception/MeetingDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/exception/MeetingDateException.java
new file mode 100644
index 0000000..854b0c4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/exception/MeetingDateException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.meeting.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when Meeting date violates a domain rule.
+ */
+public class MeetingDateException extends AbstractPlatformDomainRuleException {
+
+    public MeetingDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.meeting.date." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/exception/MeetingNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/exception/MeetingNotFoundException.java
new file mode 100644
index 0000000..fa85f3d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/exception/MeetingNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.meeting.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when Meeting resources are not found.
+ */
+public class MeetingNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public MeetingNotFoundException(final Long id) {
+        super("error.msg.meeting.id.invalid", "Meeting with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/exception/MeetingNotSupportedResourceException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/exception/MeetingNotSupportedResourceException.java
new file mode 100644
index 0000000..a986678
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/exception/MeetingNotSupportedResourceException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.meeting.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when meeting is captured
+ * against not supported resource.
+ */
+public class MeetingNotSupportedResourceException extends AbstractPlatformDomainRuleException {
+
+    public MeetingNotSupportedResourceException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.meeting.not.supported.resource", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/CreateMeetingCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/CreateMeetingCommandHandler.java
new file mode 100644
index 0000000..d2ea011
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/CreateMeetingCommandHandler.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.portfolio.meeting.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.meeting.service.MeetingWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "MEETING", action = "CREATE")
+public class CreateMeetingCommandHandler implements NewCommandSourceHandler {
+
+    private final MeetingWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateMeetingCommandHandler(final MeetingWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.createMeeting(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/DeleteMeetingCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/DeleteMeetingCommandHandler.java
new file mode 100644
index 0000000..8405ca8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/DeleteMeetingCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.meeting.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.meeting.service.MeetingWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "MEETING", action = "DELETE")
+public class DeleteMeetingCommandHandler implements NewCommandSourceHandler {
+
+    private final MeetingWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteMeetingCommandHandler(final MeetingWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteMeeting(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.java
new file mode 100644
index 0000000..880bc8b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.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.meeting.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.meeting.service.MeetingWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "MEETING", action = "SAVEORUPDATEATTENDANCE")
+public class UpdateMeetingAttendanceCommandHandler implements NewCommandSourceHandler {
+
+    private final MeetingWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateMeetingAttendanceCommandHandler(final MeetingWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.saveOrUpdateAttendance(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/UpdateMeetingCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/UpdateMeetingCommandHandler.java
new file mode 100644
index 0000000..7d119da
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/handler/UpdateMeetingCommandHandler.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.meeting.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.meeting.service.MeetingWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "MEETING", action = "UPDATE")
+public class UpdateMeetingCommandHandler implements NewCommandSourceHandler {
+
+    private final MeetingWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateMeetingCommandHandler(final MeetingWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateMeeting(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingReadPlatformService.java
new file mode 100644
index 0000000..5176ae3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingReadPlatformService.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.meeting.service;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.portfolio.meeting.data.MeetingData;
+
+public interface MeetingReadPlatformService {
+
+    MeetingData retrieveMeeting(final Long meetingId, Long entityId, Integer entityTypeId);
+
+    Collection<MeetingData> retrieveMeetingsByEntity(final Long entityId, final Integer entityTypeId, Integer limit);
+
+    Collection<MeetingData> retrieveMeetingsByEntityByCalendarType(final Long entityId, final Integer entityTypeId,
+            final List<Integer> calendarTypeOptions);
+
+    MeetingData retrieveLastMeeting(Long calendarInstanceId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java
new file mode 100644
index 0000000..c627350
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java
@@ -0,0 +1,116 @@
+/**
+ * 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.meeting.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.meeting.data.MeetingData;
+import org.apache.fineract.portfolio.meeting.exception.MeetingNotFoundException;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MeetingReadPlatformServiceImpl implements MeetingReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public MeetingReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class MeetingDataMapper implements RowMapper<MeetingData> {
+
+        public String schema() {
+
+            return " select m.id as id, m.meeting_date as meetingDate from m_meeting m "
+                    + "inner join m_calendar_instance ci on m.calendar_instance_id = ci.id ";
+        }
+
+        @Override
+        public MeetingData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final LocalDate meetingDate = JdbcSupport.getLocalDate(rs, "meetingDate");
+
+            return MeetingData.instance(id, meetingDate);
+        }
+    }
+
+    @Override
+    public MeetingData retrieveMeeting(final Long meetingId, final Long entityId, final Integer entityTypeId) {
+
+        try {
+            final MeetingDataMapper rm = new MeetingDataMapper();
+
+            final String sql = rm.schema() + " where m.id = ? and ci.entity_id = ? and ci.entity_type_enum = ? ";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { meetingId, entityId, entityTypeId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new MeetingNotFoundException(meetingId);
+        }
+    }
+
+    @Override
+    public Collection<MeetingData> retrieveMeetingsByEntity(final Long entityId, final Integer entityTypeId, final Integer limit) {
+        final MeetingDataMapper rm = new MeetingDataMapper();
+        String sql = rm.schema() + " where ci.entity_id = ? and ci.entity_type_enum = ? ";
+        if (limit != null && limit > 0) {
+            sql = sql + " order by m.meeting_date desc " + " limit " + limit;
+        }
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId });
+    }
+
+    @Override
+    public Collection<MeetingData> retrieveMeetingsByEntityByCalendarType(final Long entityId, final Integer entityTypeId,
+            final List<Integer> calendarTypeOptions) {
+        final MeetingDataMapper rm = new MeetingDataMapper();
+        final String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions);
+        final String sql = rm.schema()
+                + " inner join m_calendar c on ci.calendar_id=c.id  where ci.entity_id = ? and ci.entity_type_enum = ? and c.calendar_type_enum in ("
+                + sqlCalendarTypeOptions + ") order by c.start_date ";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId });
+    }
+
+    @Override
+    public MeetingData retrieveLastMeeting(Long calendarInstanceId) {
+        try {
+            final MeetingDataMapper rm = new MeetingDataMapper();
+
+            final String sql = rm.schema() + " where ci.id = ? order by m.meeting_date desc, m.id desc limit 1";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { calendarInstanceId });
+        } catch (final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingWritePlatformService.java
new file mode 100644
index 0000000..be346b7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingWritePlatformService.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.portfolio.meeting.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface MeetingWritePlatformService {
+
+    CommandProcessingResult createMeeting(JsonCommand command);
+
+    void updateCollectionSheetAttendance(JsonCommand command);
+
+    CommandProcessingResult updateMeeting(JsonCommand command);
+
+    CommandProcessingResult deleteMeeting(Long meetingId);
+
+    CommandProcessingResult saveOrUpdateAttendance(JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..a781129
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,300 @@
+/**
+ * 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.meeting.service;
+
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.isTransactionDateOnNonMeetingDateParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.transactionDateParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.attendanceTypeParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.calendarIdParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.clientIdParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.clientsAttendanceParamName;
+import static org.apache.fineract.portfolio.meeting.MeetingApiConstants.meetingDateParamName;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarRepository;
+import org.apache.fineract.portfolio.calendar.exception.CalendarInstanceNotFoundException;
+import org.apache.fineract.portfolio.calendar.exception.CalendarNotFoundException;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepository;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepository;
+import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
+import org.apache.fineract.portfolio.meeting.attendance.domain.ClientAttendance;
+import org.apache.fineract.portfolio.meeting.data.MeetingDataValidator;
+import org.apache.fineract.portfolio.meeting.domain.Meeting;
+import org.apache.fineract.portfolio.meeting.domain.MeetingRepository;
+import org.apache.fineract.portfolio.meeting.domain.MeetingRepositoryWrapper;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class MeetingWritePlatformServiceJpaRepositoryImpl implements MeetingWritePlatformService {
+
+    private final MeetingRepositoryWrapper meetingRepositoryWrapper;
+    private final MeetingRepository meetingRepository;
+    private final MeetingDataValidator meetingDataValidator;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final CalendarRepository calendarRepository;
+    private final ClientRepository clientRepository;
+    private final GroupRepository groupRepository;
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public MeetingWritePlatformServiceJpaRepositoryImpl(final MeetingRepositoryWrapper meetingRepositoryWrapper,
+            final MeetingRepository meetingRepository, final MeetingDataValidator meetingDataValidator,
+            final CalendarInstanceRepository calendarInstanceRepository, final CalendarRepository calendarRepository,
+            final ClientRepository clientRepository, final GroupRepository groupRepository, final FromJsonHelper fromApiJsonHelper) {
+        this.meetingRepositoryWrapper = meetingRepositoryWrapper;
+        this.meetingRepository = meetingRepository;
+        this.meetingDataValidator = meetingDataValidator;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.calendarRepository = calendarRepository;
+        this.clientRepository = clientRepository;
+        this.groupRepository = groupRepository;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public CommandProcessingResult createMeeting(final JsonCommand command) {
+
+        this.meetingDataValidator.validateForCreate(command);
+
+        final Date meetingDate = command.DateValueOfParameterNamed(meetingDateParamName);
+        final Boolean isTransactionDateOnNonMeetingDate = false;
+
+        try {
+            final CalendarInstance calendarInstance = getCalendarInstance(command);
+            // create new meeting
+            final Meeting newMeeting = Meeting.createNew(calendarInstance, meetingDate, isTransactionDateOnNonMeetingDate);
+
+            final Collection<ClientAttendance> clientsAttendance = getClientsAttendance(newMeeting, command);
+            if (clientsAttendance != null && !clientsAttendance.isEmpty()) {
+                newMeeting.associateClientsAttendance(clientsAttendance);
+            }
+            // save meeting details
+            this.meetingRepositoryWrapper.save(newMeeting);
+            final Long groupId = newMeeting.isGroupEntity() ? newMeeting.entityId() : null;
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(newMeeting.getId()) //
+                    .withGroupId(groupId).build();
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleMeetingDataIntegrityIssues(meetingDate, dve);
+            return new CommandProcessingResultBuilder() //
+                    .build();
+        }
+    }
+
+    private CalendarInstance getCalendarInstance(final JsonCommand command) {
+
+        final Long calendarId = command.longValueOfParameterNamed(calendarIdParamName);
+        final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
+        if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); }
+
+        Long entityId = null;// command.getSupportedEntityId();
+        CalendarEntityType entityType = CalendarEntityType.INVALID;// CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase());
+        if (command.getLoanId() != null) {
+            entityId = command.getLoanId();
+            entityType = CalendarEntityType.LOANS;
+        } else if (command.getClientId() != null) {
+            entityId = command.getClientId();
+            entityType = CalendarEntityType.CLIENTS;
+        } else if (command.getGroupId() != null) {
+            entityId = command.getGroupId();
+            entityType = CalendarEntityType.GROUPS;
+            /*
+             * If group is within a center then center entityType should be
+             * passed for retrieving CalendarInstance.
+             */
+            final Group group = this.groupRepository.findOne(entityId);
+            if (group.isCenter()) {
+                entityType = CalendarEntityType.CENTERS;
+            } else if (group.isChildGroup()) {
+                entityType = CalendarEntityType.CENTERS;
+                entityId = group.getParent().getId();
+            }
+        }
+
+        final CalendarInstance calendarInstance = this.calendarInstanceRepository.findByCalendarIdAndEntityIdAndEntityTypeId(
+                calendarForUpdate.getId(), entityId, entityType.getValue());
+        if (calendarInstance == null) {
+            final String postFix = "for." + entityType.name().toLowerCase() + "not.found";
+            final String defaultUserMessage = "No Calendar Instance details found for group with identifier " + entityId
+                    + " and calendar with identifier " + calendarId;
+            throw new CalendarInstanceNotFoundException(postFix, defaultUserMessage, entityId, calendarId);
+        }
+        return calendarInstance;
+    }
+
+    private Collection<ClientAttendance> getClientsAttendance(final Meeting meeting, final JsonCommand command) {
+        final Collection<ClientAttendance> clientsAttendance = new ArrayList<>();
+
+        Collection<Group> childGroups = null;
+        if (meeting.isCenterEntity()) {
+            childGroups = this.groupRepository.findByParentId(meeting.entityId());
+        }
+
+        final String json = command.json();
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has(clientsAttendanceParamName) && topLevelJsonElement.get(clientsAttendanceParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(clientsAttendanceParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject attendanceElement = array.get(i).getAsJsonObject();
+                    final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, attendanceElement);
+                    final Integer attendanceTypeId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(attendanceTypeParamName,
+                            attendanceElement);
+
+                    final Client client = this.clientRepository.findOne(clientId);
+
+                    if (meeting.isGroupEntity() && !client.isChildOfGroup(meeting.entityId())) {
+                        throw new ClientNotInGroupException(clientId, meeting.entityId());
+                    } else if (meeting.isCenterEntity()) {
+                        if (childGroups != null && !childGroups.isEmpty()) {
+                            boolean isChildClient = false;
+                            for (final Group group : childGroups) {
+                                if (group.isChildClient(clientId)) {
+                                    isChildClient = true;
+                                    break;
+                                }
+                            }
+                            if (!isChildClient) {
+                                final String defaultUserMessage = "Client with identifier " + clientId + " is not in center "
+                                        + meeting.entityId();
+                                throw new ClientNotInGroupException("client.not.in.center", defaultUserMessage, clientId,
+                                        meeting.entityId());
+                            }
+                        }
+                    }
+
+                    final ClientAttendance clientAttendance = ClientAttendance.createClientAttendance(client, meeting, attendanceTypeId);
+                    clientsAttendance.add(clientAttendance);
+                }
+            }
+        }
+        return clientsAttendance;
+    }
+
+    @Override
+    public CommandProcessingResult updateMeeting(final JsonCommand command) {
+        this.meetingDataValidator.validateForUpdate(command);
+
+        final Meeting meetingForUpdate = this.meetingRepositoryWrapper.findOneWithNotFoundDetection(command.entityId());
+        final Map<String, Object> changes = meetingForUpdate.update(command);
+
+        try {
+            if (!changes.isEmpty()) {
+                this.meetingRepositoryWrapper.saveAndFlush(meetingForUpdate);
+            }
+        } catch (final DataIntegrityViolationException dve) {
+            handleMeetingDataIntegrityIssues(meetingForUpdate.getMeetingDate(), dve);
+            return new CommandProcessingResultBuilder() //
+                    .build();
+        }
+        final Long groupId = meetingForUpdate.isGroupEntity() ? meetingForUpdate.entityId() : null;
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(meetingForUpdate.getId()) //
+                .withGroupId(groupId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult deleteMeeting(final Long meetingId) {
+        final Meeting meetingForDelete = this.meetingRepositoryWrapper.findOneWithNotFoundDetection(meetingId);
+        this.meetingRepositoryWrapper.delete(meetingForDelete);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(meetingId) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult saveOrUpdateAttendance(final JsonCommand command) {
+        this.meetingDataValidator.validateForUpdateAttendance(command);
+
+        final Meeting meetingForUpdate = this.meetingRepositoryWrapper.findOneWithNotFoundDetection(command.entityId());
+        final Collection<ClientAttendance> clientsAttendance = getClientsAttendance(meetingForUpdate, command);
+        final Map<String, Object> changes = meetingForUpdate.updateAttendance(clientsAttendance);
+
+        this.meetingRepositoryWrapper.saveAndFlush(meetingForUpdate);
+        final Long groupId = meetingForUpdate.isGroupEntity() ? meetingForUpdate.entityId() : null;
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(meetingForUpdate.getId()) //
+                .withGroupId(groupId) //
+                .with(changes) //
+                .build();
+    }
+
+    private void handleMeetingDataIntegrityIssues(final Date meetingDate, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("unique_calendar_instance_id_meeting_date")) {
+            final LocalDate meetingDateLocal = LocalDate.fromDateFields(meetingDate);
+            throw new PlatformDataIntegrityException("error.msg.meeting.duplicate", "A meeting with date '" + meetingDateLocal
+                    + "' already exists", meetingDateParamName, meetingDateLocal);
+        }
+
+        throw new PlatformDataIntegrityException("error.msg.meeting.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+
+    @Override
+    public void updateCollectionSheetAttendance(final JsonCommand command) {
+        final Date meetingDate = command.DateValueOfParameterNamed(transactionDateParamName);
+        final Boolean isTransactionDateOnNonMeetingDate = command.booleanPrimitiveValueOfParameterNamed(isTransactionDateOnNonMeetingDateParamName);
+
+        try {
+            final CalendarInstance calendarInstance = getCalendarInstance(command);
+            final Meeting meeting = this.meetingRepository.findByCalendarInstanceIdAndMeetingDate(calendarInstance.getId(), meetingDate);
+
+            // create new meeting
+            final Meeting newMeeting = (meeting != null) ? meeting : Meeting.createNew(calendarInstance, meetingDate, isTransactionDateOnNonMeetingDate);
+
+            final Collection<ClientAttendance> clientsAttendance = getClientsAttendance(newMeeting, command);
+            if (clientsAttendance != null && !clientsAttendance.isEmpty()) {
+                newMeeting.updateAttendance(clientsAttendance);
+            }
+            // save meeting details
+            this.meetingRepositoryWrapper.save(newMeeting);
+        } catch (final DataIntegrityViolationException dve) {
+            handleMeetingDataIntegrityIssues(meetingDate, dve);
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResource.java
new file mode 100644
index 0000000..2179689
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResource.java
@@ -0,0 +1,214 @@
+/**
+ * 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.note.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.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.apache.fineract.portfolio.note.data.NoteData;
+import org.apache.fineract.portfolio.note.domain.NoteType;
+import org.apache.fineract.portfolio.note.exception.NoteResourceNotSupportedException;
+import org.apache.fineract.portfolio.note.service.NoteReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/{resourceType}/{resourceId}/notes")
+@Component
+@Scope("singleton")
+public class NotesApiResource {
+
+    private final Set<String> NOTE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "clientId", "groupId", "loanId",
+            "loanTransactionId", "depositAccountId", "savingAccountId", "noteType", "note", "createdById", "createdByUsername",
+            "createdOn", "updatedById", "updatedByUsername", "updatedOn"));
+
+    private final PlatformSecurityContext context;
+    private final NoteReadPlatformService readPlatformService;
+    private final DefaultToApiJsonSerializer<NoteData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public NotesApiResource(final PlatformSecurityContext context, final NoteReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<NoteData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveNotesByResource(@PathParam("resourceType") final String resourceType,
+            @PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
+
+        final NoteType noteType = NoteType.fromApiUrl(resourceType);
+
+        if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); }
+
+        this.context.authenticatedUser().validateHasReadPermission(getResourceDetails(noteType, resourceId).entityName());
+
+        final Integer noteTypeId = noteType.getValue();
+
+        final Collection<NoteData> notes = this.readPlatformService.retrieveNotesByResource(resourceId, noteTypeId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, notes, this.NOTE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{noteId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveNote(@PathParam("resourceType") final String resourceType, @PathParam("resourceId") final Long resourceId,
+            @PathParam("noteId") final Long noteId, @Context final UriInfo uriInfo) {
+
+        final NoteType noteType = NoteType.fromApiUrl(resourceType);
+
+        if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); }
+
+        this.context.authenticatedUser().validateHasReadPermission(getResourceDetails(noteType, resourceId).entityName());
+
+        final Integer noteTypeId = noteType.getValue();
+
+        final NoteData note = this.readPlatformService.retrieveNote(noteId, resourceId, noteTypeId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, note, this.NOTE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String addNewNote(@PathParam("resourceType") final String resourceType, @PathParam("resourceId") final Long resourceId,
+            final String apiRequestBodyAsJson) {
+
+        final NoteType noteType = NoteType.fromApiUrl(resourceType);
+
+        if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); }
+
+        final CommandWrapper resourceDetails = getResourceDetails(noteType, resourceId);
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createNote(resourceDetails, resourceType, resourceId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{noteId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateNote(@PathParam("resourceType") final String resourceType, @PathParam("resourceId") final Long resourceId,
+            @PathParam("noteId") final Long noteId, final String apiRequestBodyAsJson) {
+
+        final NoteType noteType = NoteType.fromApiUrl(resourceType);
+
+        if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); }
+
+        final CommandWrapper resourceDetails = getResourceDetails(noteType, resourceId);
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateNote(resourceDetails, resourceType, resourceId, noteId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{noteId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteNote(@PathParam("resourceType") final String resourceType, @PathParam("resourceId") final Long resourceId,
+            @PathParam("noteId") final Long noteId) {
+
+        final NoteType noteType = NoteType.fromApiUrl(resourceType);
+
+        if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); }
+
+        final CommandWrapper resourceDetails = getResourceDetails(noteType, resourceId);
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteNote(resourceDetails, resourceType, resourceId, noteId)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    private CommandWrapper getResourceDetails(final NoteType type, final Long resourceId) {
+        CommandWrapperBuilder resourceDetails = new CommandWrapperBuilder();
+        String resourceNameForPermissions = "INVALIDNOTE";
+        switch (type) {
+            case CLIENT:
+                resourceNameForPermissions = "CLIENTNOTE";
+                resourceDetails.withClientId(resourceId);
+            break;
+            case LOAN:
+                resourceNameForPermissions = "LOANNOTE";
+                resourceDetails.withLoanId(resourceId);
+            break;
+            case LOAN_TRANSACTION:
+                resourceNameForPermissions = "LOANTRANSACTIONNOTE";
+                // updating loanId, to distinguish saving transaction note and loan transaction note as we are using subEntityId for both.
+                resourceDetails.withLoanId(resourceId);
+                resourceDetails.withSubEntityId(resourceId);
+            break;
+            case SAVING_ACCOUNT:
+                resourceNameForPermissions = "SAVINGNOTE";
+                resourceDetails.withSavingsId(resourceId);
+            break;
+            case GROUP:
+                resourceNameForPermissions = "GROUPNOTE";
+                resourceDetails.withGroupId(resourceId);
+            break;
+
+        }
+
+        return resourceDetails.withEntityName(resourceNameForPermissions).build();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
new file mode 100644
index 0000000..5b5ca66
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
@@ -0,0 +1,33 @@
+/**
+ * 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.note.command;
+
+/**
+ * Immutable command used for create or update of notes.
+ */
+public class NoteCommand {
+
+    @SuppressWarnings("unused")
+    private final String note;
+
+    public NoteCommand(final String note) {
+        this.note = note;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteData.java
new file mode 100644
index 0000000..99e8d5e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteData.java
@@ -0,0 +1,81 @@
+/**
+ * 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.note.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.joda.time.DateTime;
+
+/**
+ * Immutable data object represent note or case information about a client, loan
+ * or loan transaction.
+ */
+public class NoteData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final Long clientId;
+    @SuppressWarnings("unused")
+    private final Long groupId;
+    @SuppressWarnings("unused")
+    private final Long loanId;
+    @SuppressWarnings("unused")
+    private final Long loanTransactionId;
+    @SuppressWarnings("unused")
+    private final Long depositAccountId;
+    @SuppressWarnings("unused")
+    private final Long savingAccountId;
+    @SuppressWarnings("unused")
+    private final EnumOptionData noteType;
+    @SuppressWarnings("unused")
+    private final String note;
+    @SuppressWarnings("unused")
+    private final Long createdById;
+    @SuppressWarnings("unused")
+    private final String createdByUsername;
+    @SuppressWarnings("unused")
+    private final DateTime createdOn;
+    @SuppressWarnings("unused")
+    private final Long updatedById;
+    @SuppressWarnings("unused")
+    private final String updatedByUsername;
+    @SuppressWarnings("unused")
+    private final DateTime updatedOn;
+
+    public NoteData(final Long id, final Long clientId, final Long groupId, final Long loanId, final Long transactionId,
+            final Long depositAccountId, final Long savingAccountId, final EnumOptionData noteType, final String note,
+            final DateTime createdDate, final Long createdById, final String createdByUsername, final DateTime lastModifiedDate,
+            final Long lastModifiedById, final String updatedByUsername) {
+        this.id = id;
+        this.clientId = clientId;
+        this.groupId = groupId;
+        this.loanId = loanId;
+        this.loanTransactionId = transactionId;
+        this.depositAccountId = depositAccountId;
+        this.savingAccountId = savingAccountId;
+        this.noteType = noteType;
+        this.note = note;
+        this.createdOn = createdDate;
+        this.createdById = createdById;
+        this.createdByUsername = createdByUsername;
+        this.updatedOn = lastModifiedDate;
+        this.updatedById = lastModifiedById;
+        this.updatedByUsername = updatedByUsername;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java
new file mode 100644
index 0000000..e18c238
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java
@@ -0,0 +1,152 @@
+/**
+ * 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.note.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Entity
+@Table(name = "m_note")
+public class Note extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "client_id", nullable = true)
+    private final Client client;
+
+    @ManyToOne
+    @JoinColumn(name = "group_id", nullable = true)
+    private Group group;
+
+    @ManyToOne
+    @JoinColumn(name = "loan_id", nullable = true)
+    private Loan loan;
+
+    @ManyToOne
+    @JoinColumn(name = "loan_transaction_id", nullable = true)
+    private LoanTransaction loanTransaction;
+
+    @Column(name = "note", length = 1000)
+    private String note;
+
+    @Column(name = "note_type_enum")
+    private final Integer noteTypeId;
+
+    @ManyToOne
+    @JoinColumn(name = "savings_account_id", nullable = true)
+    private SavingsAccount savingsAccount;
+
+    public static Note clientNoteFromJson(final Client client, final JsonCommand command) {
+        final String note = command.stringValueOfParameterNamed("note");
+        return new Note(client, note);
+    }
+
+    public static Note groupNoteFromJson(final Group group, final JsonCommand command) {
+        final String note = command.stringValueOfParameterNamed("note");
+        return new Note(group, note);
+    }
+
+    public static Note loanNote(final Loan loan, final String note) {
+        return new Note(loan, note);
+    }
+
+    public static Note loanTransactionNote(final Loan loan, final LoanTransaction loanTransaction, final String note) {
+        return new Note(loan, loanTransaction, note);
+    }
+
+    public static Note savingNote(final SavingsAccount account, final String note) {
+        return new Note(account, note);
+    }
+
+    public Note(final Client client, final String note) {
+        this.client = client;
+        this.note = note;
+        this.noteTypeId = NoteType.CLIENT.getValue();
+    }
+
+    private Note(final Group group, final String note) {
+        this.group = group;
+        this.note = note;
+        this.client = null;
+        this.noteTypeId = NoteType.GROUP.getValue();
+    }
+
+    private Note(final Loan loan, final String note) {
+        this.loan = loan;
+        this.client = loan.client();
+        this.note = note;
+        this.noteTypeId = NoteType.LOAN.getValue();
+    }
+
+    private Note(final Loan loan, final LoanTransaction loanTransaction, final String note) {
+        this.loan = loan;
+        this.loanTransaction = loanTransaction;
+        this.client = loan.client();
+        this.note = note;
+        this.noteTypeId = NoteType.LOAN_TRANSACTION.getValue();
+    }
+
+    protected Note() {
+        this.client = null;
+        this.group = null;
+        this.loan = null;
+        this.loanTransaction = null;
+        this.note = null;
+        this.noteTypeId = null;
+    }
+
+    public Note(final SavingsAccount account, final String note) {
+        this.savingsAccount = account;
+        this.client = account.getClient();
+        this.note = note;
+        this.noteTypeId = NoteType.SAVING_ACCOUNT.getValue();
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String noteParamName = "note";
+        if (command.isChangeInStringParameterNamed(noteParamName, this.note)) {
+            final String newValue = command.stringValueOfParameterNamed(noteParamName);
+            actualChanges.put(noteParamName, newValue);
+            this.note = StringUtils.defaultIfEmpty(newValue, null);
+        }
+        return actualChanges;
+    }
+
+    public boolean isNotAgainstClientWithIdOf(final Long clientId) {
+        return !this.client.identifiedBy(clientId);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/NoteRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/NoteRepository.java
new file mode 100644
index 0000000..6e90544
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/NoteRepository.java
@@ -0,0 +1,45 @@
+/**
+ * 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.note.domain;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface NoteRepository extends JpaRepository<Note, Long>, JpaSpecificationExecutor<Note> {
+
+    List<Note> findByLoanId(Long id);
+
+    List<Note> findByClientId(Long id);
+
+    List<Note> findByGroupId(Long groupId);
+
+    Note findByLoanIdAndId(Long loanId, Long id);
+
+    Note findByClientIdAndId(Long clientId, Long id);
+
+    Note findByGroupIdAndId(Long groupId, Long id);
+
+    Note findByLoanTransactionIdAndId(Long loanTransactionId, Long id);
+
+    List<Note> findBySavingsAccountId(Long savingAccountId);
+
+    // Note findBySavingsAccountIdAndId(Long savingAccountId, Long id);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/NoteType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/NoteType.java
new file mode 100644
index 0000000..01bed9f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/NoteType.java
@@ -0,0 +1,105 @@
+/**
+ * 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.note.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum NoteType {
+
+    CLIENT(100, "noteType.client", "clients"), //
+    LOAN(200, "noteType.loan", "loans"), //
+    LOAN_TRANSACTION(300, "noteType.loan.transaction", "loanTransactions"), //
+    SAVING_ACCOUNT(500, "noteType.saving", "savings"), //
+    GROUP(600, "noteType.group", "groups");
+
+    private Integer value;
+    private String code;
+    private String apiUrl;
+
+    NoteType(final Integer value, final String code, final String apiUrl) {
+        this.value = value;
+        this.code = code;
+        this.apiUrl = apiUrl;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public String getApiUrl() {
+        return this.apiUrl;
+    }
+
+    private static final Map<Integer, NoteType> intToEnumMap = new HashMap<>();
+    private static int minValue;
+    private static int maxValue;
+    static {
+        int i = 0;
+        for (final NoteType type : NoteType.values()) {
+            if (i == 0) {
+                minValue = type.value;
+            }
+            intToEnumMap.put(type.value, type);
+            if (minValue >= type.value) {
+                minValue = type.value;
+            }
+            if (maxValue < type.value) {
+                maxValue = type.value;
+            }
+            i = i + 1;
+        }
+    }
+
+    public static NoteType fromInt(final int i) {
+        final NoteType type = intToEnumMap.get(Integer.valueOf(i));
+        return type;
+    }
+
+    public static int getMinValue() {
+        return minValue;
+    }
+
+    public static int getMaxValue() {
+        return maxValue;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString();
+    }
+
+    private static final Map<String, NoteType> apiUrlToEnumMap = new HashMap<>();
+
+    static {
+        for (final NoteType type : NoteType.values()) {
+            apiUrlToEnumMap.put(type.apiUrl, type);
+        }
+    }
+
+    public static NoteType fromApiUrl(final String url) {
+        final NoteType type = apiUrlToEnumMap.get(url);
+        return type;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/exception/NoteNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/exception/NoteNotFoundException.java
new file mode 100644
index 0000000..5c95644
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/exception/NoteNotFoundException.java
@@ -0,0 +1,37 @@
+/**
+ * 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.note.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when note resources are not found.
+ */
+public class NoteNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public NoteNotFoundException(final Long id) {
+        super("error.msg.note.id.invalid", "Note with identifier " + id + " does not exist", id);
+    }
+
+    public NoteNotFoundException(final Long id, final Long resourceId, final String resource) {
+        super("error.msg." + resource + ".note.id.invalid", "Note with identifier " + id + " does not exist for " + resource
+                + " with identifier " + resourceId, id, resourceId);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/exception/NoteResourceNotSupportedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/exception/NoteResourceNotSupportedException.java
new file mode 100644
index 0000000..9192d57
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/exception/NoteResourceNotSupportedException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.note.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when Note does not support a resource.
+ */
+public class NoteResourceNotSupportedException extends AbstractPlatformResourceNotFoundException {
+
+    public NoteResourceNotSupportedException(final String resource) {
+        super("error.msg.note.resource.not.supported", "Note does not support resource " + resource);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/CreateNoteCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/CreateNoteCommandHandler.java
new file mode 100644
index 0000000..351714e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/CreateNoteCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.note.handler;
+
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class CreateNoteCommandHandler implements NewCommandSourceHandler {
+
+    private final NoteWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateNoteCommandHandler(final NoteWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.createNote(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/DeleteNoteCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/DeleteNoteCommandHandler.java
new file mode 100644
index 0000000..fd18333
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/DeleteNoteCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.note.handler;
+
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class DeleteNoteCommandHandler implements NewCommandSourceHandler {
+
+    private final NoteWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteNoteCommandHandler(final NoteWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteNote(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/UpdateNoteCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/UpdateNoteCommandHandler.java
new file mode 100644
index 0000000..69df2a0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/UpdateNoteCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.note.handler;
+
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class UpdateNoteCommandHandler implements NewCommandSourceHandler {
+
+    private final NoteWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateNoteCommandHandler(final NoteWritePlatformService noteWritePlatformService) {
+        this.writePlatformService = noteWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateNote(command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/serialization/NoteCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/serialization/NoteCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..515ca35
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/serialization/NoteCommandFromApiJsonDeserializer.java
@@ -0,0 +1,94 @@
+/**
+ * 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.note.serialization;
+
+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.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.note.command.NoteCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for {@link NoteCommand} 's.
+ */
+@Component
+public final class NoteCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<NoteCommand> {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("note"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public NoteCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public NoteCommand commandFromApiJson(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 JsonElement element = this.fromApiJsonHelper.parse(json);
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+
+        return new NoteCommand(note);
+    }
+
+    public void validateNote(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 JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("note");
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+        baseDataValidator.reset().parameter("note").value(note).notBlank().notExceedingLengthOf(1000);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteEnumerations.java
new file mode 100644
index 0000000..6b559ea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteEnumerations.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.portfolio.note.service;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.note.domain.NoteType;
+
+public class NoteEnumerations {
+
+    public static EnumOptionData noteType(final Integer id) {
+        return noteType(NoteType.fromInt(id));
+    }
+
+    public static EnumOptionData noteType(final NoteType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case CLIENT:
+                optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Client note");
+            break;
+            case LOAN:
+                optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Loan note");
+            break;
+            case LOAN_TRANSACTION:
+                optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Loan transaction note");
+            break;
+            case SAVING_ACCOUNT:
+                optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Saving account note");
+            break;
+            case GROUP:
+                optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), "Group note");
+            break;
+            default:
+            break;
+
+        }
+        return optionData;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformService.java
new file mode 100644
index 0000000..87dbf58
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformService.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.note.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.note.data.NoteData;
+
+public interface NoteReadPlatformService {
+
+    NoteData retrieveNote(final Long noteId, Long resourceId, Integer noteTypeId);
+
+    Collection<NoteData> retrieveNotesByResource(final Long resourceId, final Integer noteTypeId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformServiceImpl.java
new file mode 100644
index 0000000..4d1788a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformServiceImpl.java
@@ -0,0 +1,138 @@
+/**
+ * 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.note.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.commons.lang.StringUtils;
+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.portfolio.note.data.NoteData;
+import org.apache.fineract.portfolio.note.domain.NoteType;
+import org.apache.fineract.portfolio.note.exception.NoteNotFoundException;
+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 NoteReadPlatformServiceImpl implements NoteReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public NoteReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class NoteMapper implements RowMapper<NoteData> {
+
+        public String schema() {
+            return " select n.id as id, n.client_id as clientId, n.group_id as groupId, n.loan_id as loanId, n.loan_transaction_id as transactionId, "
+                    + " n.note_type_enum as noteTypeEnum, n.note as note, n.created_date as createdDate, n.createdby_id as createdById, "
+                    + " cb.username as createdBy, n.lastmodified_date as lastModifiedDate, n.lastmodifiedby_id as lastModifiedById, mb.username as modifiedBy "
+                    + " from m_note n left join m_appuser cb on cb.id=n.createdby_id left join m_appuser mb on mb.id=n.lastmodifiedby_id ";
+        }
+
+        @Override
+        public NoteData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final Long loanId = JdbcSupport.getLong(rs, "loanId");
+            final Long transactionId = JdbcSupport.getLong(rs, "transactionId");
+            // final Long depositAccountId = JdbcSupport.getLong(rs,
+            // "depositAccountId");
+            // final Long savingAccountId = JdbcSupport.getLong(rs,
+            // "savingAccountId");
+            final Integer noteTypeId = JdbcSupport.getInteger(rs, "noteTypeEnum");
+            final EnumOptionData noteType = NoteEnumerations.noteType(noteTypeId);
+            final String note = rs.getString("note");
+            final DateTime createdDate = JdbcSupport.getDateTime(rs, "createdDate");
+            final Long createdById = JdbcSupport.getLong(rs, "createdById");
+            final DateTime lastModifiedDate = JdbcSupport.getDateTime(rs, "lastModifiedDate");
+            final Long lastModifiedById = JdbcSupport.getLong(rs, "lastModifiedById");
+            final String createdByUsername = rs.getString("createdBy");
+            final String updatedByUsername = rs.getString("modifiedBy");
+            return new NoteData(id, clientId, groupId, loanId, transactionId, null, null, noteType, note, createdDate, createdById,
+                    createdByUsername, lastModifiedDate, lastModifiedById, updatedByUsername);
+        }
+    }
+
+    @Override
+    public NoteData retrieveNote(final Long noteId, final Long resourceId, final Integer noteTypeId) {
+        final NoteType noteType = NoteType.fromInt(noteTypeId);
+        try {
+            final NoteMapper rm = new NoteMapper();
+            String conditionSql = getResourceCondition(noteType);
+            if (StringUtils.isNotBlank(conditionSql)) {
+                conditionSql = " and " + conditionSql;
+            }
+
+            final String sql = rm.schema() + " where n.id = ? " + conditionSql + " order by n.created_date DESC";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { noteId, resourceId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new NoteNotFoundException(noteId, resourceId, noteType.name().toLowerCase());
+        }
+    }
+
+    @Override
+    public Collection<NoteData> retrieveNotesByResource(final Long resourceId, final Integer noteTypeId) {
+        final NoteType noteType = NoteType.fromInt(noteTypeId);
+        final NoteMapper rm = new NoteMapper();
+        final String conditionSql = getResourceCondition(noteType);
+
+        final String sql = rm.schema() + " where " + conditionSql + " order by n.created_date DESC";
+
+        return this.jdbcTemplate.query(sql, rm, new Object[] { resourceId });
+    }
+
+    public static String getResourceCondition(final NoteType noteType) {
+        String conditionSql = "";
+        switch (noteType) {
+            case CLIENT:
+                conditionSql = " n.client_id = ? and note_type_enum = " + NoteType.CLIENT.getValue();
+            break;
+            case LOAN:
+                conditionSql = " n.loan_id = ? and ( n.note_type_enum = " + NoteType.LOAN.getValue() + " or n.note_type_enum = "
+                        + NoteType.LOAN_TRANSACTION.getValue() + " )";
+            break;
+            case LOAN_TRANSACTION:
+                conditionSql = " n.loan_transaction_id = ? ";
+            break;
+            case SAVING_ACCOUNT:
+                conditionSql = " n.saving_account_id = ? ";
+            break;
+            case GROUP:
+                conditionSql = " n.group_id = ? ";
+            break;
+            default:
+            break;
+        }
+
+        return conditionSql;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java
new file mode 100644
index 0000000..1265d53
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.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.portfolio.note.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.client.domain.Client;
+
+public interface NoteWritePlatformService {
+
+    CommandProcessingResult createNote(JsonCommand command);
+
+    CommandProcessingResult updateNote(JsonCommand command);
+
+    CommandProcessingResult deleteNote(JsonCommand command);
+
+    void createAndPersistClientNote(Client client, JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..ec77f82
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,452 @@
+/**
+ * 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.note.service;
+
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.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.group.domain.GroupRepository;
+import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.note.domain.NoteType;
+import org.apache.fineract.portfolio.note.exception.NoteNotFoundException;
+import org.apache.fineract.portfolio.note.exception.NoteResourceNotSupportedException;
+import org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class NoteWritePlatformServiceJpaRepositoryImpl implements NoteWritePlatformService {
+
+    private final NoteRepository noteRepository;
+    private final ClientRepositoryWrapper clientRepository;
+    private final GroupRepository groupRepository;
+    // private final SavingAccountRepository savingAccountRepository;
+    private final LoanRepository loanRepository;
+    private final LoanTransactionRepository loanTransactionRepository;
+    private final NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+
+    @Autowired
+    public NoteWritePlatformServiceJpaRepositoryImpl(final NoteRepository noteRepository, final ClientRepositoryWrapper clientRepository,
+            final GroupRepository groupRepository, final LoanRepository loanRepository,
+            final LoanTransactionRepository loanTransactionRepository, final NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer) {
+        this.noteRepository = noteRepository;
+        this.clientRepository = clientRepository;
+        this.groupRepository = groupRepository;
+        this.loanRepository = loanRepository;
+        this.loanTransactionRepository = loanTransactionRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+    }
+
+    private CommandProcessingResult createClientNote(final JsonCommand command) {
+
+        final Long resourceId = command.getClientId();
+
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(resourceId);
+        if (client == null) { throw new ClientNotFoundException(resourceId); }
+        final Note newNote = Note.clientNoteFromJson(client, command);
+
+        this.noteRepository.save(newNote);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(newNote.getId()) //
+                .withClientId(client.getId()) //
+                .withOfficeId(client.officeId()) //
+                .build();
+
+    }
+
+    @Override
+    public void createAndPersistClientNote(final Client client, final JsonCommand command) {
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note newNote = new Note(client, noteText);
+            this.noteRepository.save(newNote);
+        }
+    }
+
+    private CommandProcessingResult createGroupNote(final JsonCommand command) {
+
+        final Long resourceId = command.getGroupId();
+
+        final Group group = this.groupRepository.findOne(resourceId);
+        if (group == null) { throw new GroupNotFoundException(resourceId); }
+        final Note newNote = Note.groupNoteFromJson(group, command);
+
+        this.noteRepository.save(newNote);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(newNote.getId()) //
+                .withGroupId(group.getId()) //
+                .withOfficeId(group.officeId()) //
+                .build();
+    }
+
+    private CommandProcessingResult createLoanNote(final JsonCommand command) {
+
+        final Long resourceId = command.getLoanId();
+
+        final Loan loan = this.loanRepository.findOne(resourceId);
+        if (loan == null) { throw new LoanNotFoundException(resourceId); }
+
+        final String note = command.stringValueOfParameterNamed("note");
+        final Note newNote = Note.loanNote(loan, note);
+
+        this.noteRepository.save(newNote);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(newNote.getId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withLoanId(loan.getId()) //
+                .build();
+    }
+
+    private CommandProcessingResult createLoanTransactionNote(final JsonCommand command) {
+
+        final Long resourceId = command.subentityId();
+
+        final LoanTransaction loanTransaction = this.loanTransactionRepository.findOne(resourceId);
+        if (loanTransaction == null) { throw new LoanTransactionNotFoundException(resourceId); }
+
+        final Loan loan = loanTransaction.getLoan();
+
+        final String note = command.stringValueOfParameterNamed("note");
+        final Note newNote = Note.loanTransactionNote(loan, loanTransaction, note);
+
+        this.noteRepository.save(newNote);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(newNote.getId()) //
+                .withOfficeId(loan.getOfficeId())//
+                .withLoanId(loan.getId())// Loan can be associated
+                .build();
+    }
+
+    // private CommandProcessingResult createSavingAccountNote(final JsonCommand
+    // command) {
+    //
+    // final Long resourceId = command.getSupportedEntityId();
+    //
+    // final SavingAccount savingAccount =
+    // this.savingAccountRepository.findOne(resourceId);
+    // if (savingAccount == null) { throw new
+    // SavingAccountNotFoundException(resourceId); }
+    //
+    // final String note = command.stringValueOfParameterNamed("note");
+    // final Note newNote = Note.savingNote(savingAccount, note);
+    //
+    // this.noteRepository.save(newNote);
+    //
+    // return new CommandProcessingResultBuilder() //
+    // .withCommandId(command.commandId()) //
+    // .withEntityId(newNote.getId()) //
+    // .withClientId(savingAccount.getClient().getId()).withOfficeId(savingAccount.getClient().getOffice().getId()).build();
+    // }
+
+    @Override
+    public CommandProcessingResult createNote(final JsonCommand command) {
+
+        this.fromApiJsonDeserializer.validateNote(command.json());
+
+        final String resourceUrl = getResourceUrlFromCommand(command); //command.getSupportedEntityType();
+        final NoteType type = NoteType.fromApiUrl(resourceUrl);
+        switch (type) {
+            case CLIENT: {
+                return createClientNote(command);
+            }
+            case GROUP: {
+                return createGroupNote(command);
+            }
+            case LOAN: {
+                return createLoanNote(command);
+            }
+            case LOAN_TRANSACTION: {
+                return createLoanTransactionNote(command);
+            }
+            // case SAVING_ACCOUNT: {
+            // return createSavingAccountNote(command);
+            // }
+            default:
+                throw new NoteResourceNotSupportedException(resourceUrl);
+        }
+
+    }
+
+    private String getResourceUrlFromCommand(JsonCommand command) {
+
+        final String resourceUrl;
+
+        if (command.getClientId() != null) {
+            resourceUrl = NoteType.CLIENT.getApiUrl();
+        } else if (command.getGroupId() != null) {
+            resourceUrl = NoteType.GROUP.getApiUrl();
+        } else if (command.getLoanId() != null) {
+            if (command.subentityId() != null) {
+                resourceUrl = NoteType.LOAN_TRANSACTION.getApiUrl();
+            } else {
+                resourceUrl = NoteType.LOAN.getApiUrl();
+            }
+        } else if (command.getSavingsId() != null) {
+            //TODO: SAVING_TRANSACTION type need to be add.
+            resourceUrl = NoteType.SAVING_ACCOUNT.getApiUrl();
+        } else {
+            resourceUrl = "";
+        }
+
+        return resourceUrl;
+    }
+
+    private CommandProcessingResult updateClientNote(final JsonCommand command) {
+
+        final Long resourceId = command.getClientId();
+        final Long noteId = command.entityId();
+
+        final NoteType type = NoteType.CLIENT;
+
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(resourceId);
+
+        final Note noteForUpdate = this.noteRepository.findByClientIdAndId(resourceId, noteId);
+        if (noteForUpdate == null) { throw new NoteNotFoundException(noteId, resourceId, type.name().toLowerCase()); }
+
+        final Map<String, Object> changes = noteForUpdate.update(command);
+
+        if (!changes.isEmpty()) {
+            this.noteRepository.saveAndFlush(noteForUpdate);
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(noteForUpdate.getId()) //
+                .withClientId(client.getId()) //
+                .withOfficeId(client.officeId()) //
+                .with(changes) //
+                .build();
+    }
+
+    private CommandProcessingResult updateGroupNote(final JsonCommand command) {
+
+        final Long resourceId = command.getGroupId();
+        final Long noteId = command.entityId();
+
+        final NoteType type = NoteType.GROUP;
+
+        final Group group = this.groupRepository.findOne(resourceId);
+        if (group == null) { throw new GroupNotFoundException(resourceId); }
+        final Note noteForUpdate = this.noteRepository.findByGroupIdAndId(resourceId, noteId);
+
+        if (noteForUpdate == null) { throw new NoteNotFoundException(noteId, resourceId, type.name().toLowerCase()); }
+
+        final Map<String, Object> changes = noteForUpdate.update(command);
+
+        if (!changes.isEmpty()) {
+            this.noteRepository.saveAndFlush(noteForUpdate);
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(noteForUpdate.getId()) //
+                .withGroupId(group.getId()) //
+                .withOfficeId(group.officeId()) //
+                .with(changes).build();
+    }
+
+    private CommandProcessingResult updateLoanNote(final JsonCommand command) {
+
+        final Long resourceId = command.getLoanId();
+        final Long noteId = command.entityId();
+
+        final NoteType type = NoteType.LOAN;
+
+        final Loan loan = this.loanRepository.findOne(resourceId);
+        if (loan == null) { throw new LoanNotFoundException(resourceId); }
+        final Note noteForUpdate = this.noteRepository.findByLoanIdAndId(resourceId, noteId);
+
+        if (noteForUpdate == null) { throw new NoteNotFoundException(noteId, resourceId, type.name().toLowerCase()); }
+
+        final Map<String, Object> changes = noteForUpdate.update(command);
+
+        if (!changes.isEmpty()) {
+            this.noteRepository.saveAndFlush(noteForUpdate);
+        }
+
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(noteForUpdate.getId())
+                .withLoanId(loan.getId()).withOfficeId(loan.getOfficeId()).with(changes).build();
+    }
+
+    private CommandProcessingResult updateLoanTransactionNote(final JsonCommand command) {
+
+        final Long resourceId = command.subentityId();
+        final Long noteId = command.entityId();
+
+        final NoteType type = NoteType.LOAN_TRANSACTION;
+
+        final LoanTransaction loanTransaction = this.loanTransactionRepository.findOne(resourceId);
+        if (loanTransaction == null) { throw new LoanTransactionNotFoundException(resourceId); }
+        final Loan loan = loanTransaction.getLoan();
+
+        final Note noteForUpdate = this.noteRepository.findByLoanTransactionIdAndId(resourceId, noteId);
+
+        if (noteForUpdate == null) { throw new NoteNotFoundException(noteId, resourceId, type.name().toLowerCase()); }
+
+        final Map<String, Object> changes = noteForUpdate.update(command);
+
+        if (!changes.isEmpty()) {
+            this.noteRepository.saveAndFlush(noteForUpdate);
+        }
+
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(noteForUpdate.getId())
+                .withLoanId(loan.getId()).withOfficeId(loan.getOfficeId()).with(changes).build();
+    }
+
+    // private CommandProcessingResult updateSavingAccountNote(final JsonCommand
+    // command) {
+    //
+    // final Long resourceId = command.getSupportedEntityId();
+    // final Long noteId = command.entityId();
+    // final String resourceUrl = command.getSupportedEntityType();
+    //
+    // final NoteType type = NoteType.fromApiUrl(resourceUrl);
+    //
+    // final SavingAccount savingAccount =
+    // this.savingAccountRepository.findOne(resourceId);
+    // if (savingAccount == null) { throw new
+    // SavingAccountNotFoundException(resourceId); }
+    //
+    // final Note noteForUpdate =
+    // this.noteRepository.findBySavingAccountIdAndId(resourceId, noteId);
+    //
+    // if (noteForUpdate == null) { throw new NoteNotFoundException(noteId,
+    // resourceId, type.name().toLowerCase()); }
+    //
+    // final Map<String, Object> changes = noteForUpdate.update(command);
+    //
+    // if (!changes.isEmpty()) {
+    // this.noteRepository.saveAndFlush(noteForUpdate);
+    // }
+    //
+    // return new CommandProcessingResultBuilder()
+    // //
+    // .withCommandId(command.commandId())
+    // //
+    // .withEntityId(noteForUpdate.getId())
+    // //
+    // .withClientId(savingAccount.getClient().getId()).withOfficeId(savingAccount.getClient().getOffice().getId()).with(changes)
+    // .build();
+    // }
+
+    @Override
+    public CommandProcessingResult updateNote(final JsonCommand command) {
+
+        this.fromApiJsonDeserializer.validateNote(command.json());
+
+        final String resourceUrl = getResourceUrlFromCommand(command); //command.getSupportedEntityType();
+        final NoteType type = NoteType.fromApiUrl(resourceUrl);
+
+        switch (type) {
+            case CLIENT: {
+                return updateClientNote(command);
+            }
+            case GROUP: {
+                return updateGroupNote(command);
+            }
+            case LOAN: {
+                return updateLoanNote(command);
+            }
+            case LOAN_TRANSACTION: {
+                return updateLoanTransactionNote(command);
+            }
+            // case SAVING_ACCOUNT: {
+            // return updateSavingAccountNote(command);
+            // }
+            default:
+                throw new NoteResourceNotSupportedException(resourceUrl);
+        }
+    }
+
+    @Override
+    public CommandProcessingResult deleteNote(final JsonCommand command) {
+
+        final Note noteForDelete = getNoteForDelete(command);
+
+        this.noteRepository.delete(noteForDelete);
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(null) //
+                .withEntityId(command.entityId()) //
+                .build();
+    }
+
+    private Note getNoteForDelete(final JsonCommand command) {
+        final String resourceUrl = getResourceUrlFromCommand(command);// command.getSupportedEntityType();
+        final Long noteId = command.entityId();
+        final NoteType type = NoteType.fromApiUrl(resourceUrl);
+        Long resourceId = null;
+        Note noteForUpdate = null;
+        switch (type) {
+            case CLIENT: {
+                resourceId = command.getClientId();
+                noteForUpdate = this.noteRepository.findByClientIdAndId(resourceId, noteId);
+            }
+            break;
+            case GROUP: {
+                resourceId = command.getGroupId();
+                noteForUpdate = this.noteRepository.findByGroupIdAndId(resourceId, noteId);
+            }
+            break;
+            case LOAN: {
+                resourceId = command.getLoanId();
+                noteForUpdate = this.noteRepository.findByLoanIdAndId(resourceId, noteId);
+            }
+            break;
+            case LOAN_TRANSACTION: {
+                resourceId = command.subentityId();
+                noteForUpdate = this.noteRepository.findByLoanTransactionIdAndId(resourceId, noteId);
+            }
+            break;
+            // case SAVING_ACCOUNT: {
+            // noteForUpdate =
+            // this.noteRepository.findBySavingAccountIdAndId(resourceId,
+            // noteId);
+            // }
+            // break;
+            case SAVING_ACCOUNT:
+            break;
+        }
+        if (noteForUpdate == null) { throw new NoteNotFoundException(noteId, resourceId, type.name().toLowerCase()); }
+        return noteForUpdate;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/PaymentDetailConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/PaymentDetailConstants.java
new file mode 100755
index 0000000..cf8e081
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/PaymentDetailConstants.java
@@ -0,0 +1,44 @@
+/**
+ * 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.paymentdetail;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class PaymentDetailConstants {
+
+    // Code representing Payment Details
+    public static final String paymentTypeCodeName = "PaymentType";
+
+    // request parameters
+    public static final String paymentTypeParamName = "paymentTypeId";
+    public static final String accountNumberParamName = "accountNumber";
+    public static final String checkNumberParamName = "checkNumber";
+    public static final String routingCodeParamName = "routingCode";
+    public static final String receiptNumberParamName = "receiptNumber";
+    public static final String bankNumberParamName = "bankNumber";
+
+    // template related part of response
+    public static final String officeOptionsParamName = "paymentTypeOptions";
+
+    public static final Set<String> PAYMENT_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(accountNumberParamName,
+            checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName));
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/data/PaymentDetailData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/data/PaymentDetailData.java
new file mode 100755
index 0000000..cc519da
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/data/PaymentDetailData.java
@@ -0,0 +1,56 @@
+/**
+ * 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.paymentdetail.data;
+
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+
+/**
+ * Immutable data object representing a payment.
+ */
+public class PaymentDetailData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final PaymentTypeData paymentType;
+    @SuppressWarnings("unused")
+    private final String accountNumber;
+    @SuppressWarnings("unused")
+    private final String checkNumber;
+    @SuppressWarnings("unused")
+    private final String routingCode;
+    @SuppressWarnings("unused")
+    private final String receiptNumber;
+    @SuppressWarnings("unused")
+    private final String bankNumber;
+
+    public PaymentDetailData(final Long id, final PaymentTypeData paymentType, final String accountNumber, final String checkNumber,
+            final String routingCode, final String receiptNumber, final String bankNumber) {
+        this.id = id;
+        this.paymentType = paymentType;
+        this.accountNumber = accountNumber;
+        this.checkNumber = checkNumber;
+        this.routingCode = routingCode;
+        this.receiptNumber = receiptNumber;
+        this.bankNumber = bankNumber;
+    }
+    
+    
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
new file mode 100755
index 0000000..e660221
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
@@ -0,0 +1,118 @@
+/**
+ * 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.paymentdetail.domain;
+
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.paymentdetail.PaymentDetailConstants;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_payment_detail")
+public final class PaymentDetail extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "payment_type_id", nullable = false)
+    private PaymentType paymentType;
+
+    @Column(name = "account_number", length = 50)
+    private String accountNumber;
+
+    @Column(name = "check_number", length = 50)
+    private String checkNumber;
+
+    @Column(name = "routing_code", length = 50)
+    private String routingCode;
+
+    @Column(name = "receipt_number", length = 50)
+    private String receiptNumber;
+
+    @Column(name = "bank_number", length = 50)
+    private String bankNumber;
+
+    protected PaymentDetail() {
+
+    }
+
+    public static PaymentDetail generatePaymentDetail(final PaymentType paymentType, final JsonCommand command,
+            final Map<String, Object> changes) {
+        final String accountNumber = command.stringValueOfParameterNamed(PaymentDetailConstants.accountNumberParamName);
+        final String checkNumber = command.stringValueOfParameterNamed(PaymentDetailConstants.checkNumberParamName);
+        final String routingCode = command.stringValueOfParameterNamed(PaymentDetailConstants.routingCodeParamName);
+        final String receiptNumber = command.stringValueOfParameterNamed(PaymentDetailConstants.receiptNumberParamName);
+        final String bankNumber = command.stringValueOfParameterNamed(PaymentDetailConstants.bankNumberParamName);
+
+        if (StringUtils.isNotBlank(accountNumber)) {
+            changes.put(PaymentDetailConstants.accountNumberParamName, accountNumber);
+        }
+        if (StringUtils.isNotBlank(checkNumber)) {
+            changes.put(PaymentDetailConstants.checkNumberParamName, checkNumber);
+        }
+        if (StringUtils.isNotBlank(routingCode)) {
+            changes.put(PaymentDetailConstants.routingCodeParamName, routingCode);
+        }
+        if (StringUtils.isNotBlank(receiptNumber)) {
+            changes.put(PaymentDetailConstants.receiptNumberParamName, receiptNumber);
+        }
+        if (StringUtils.isNotBlank(bankNumber)) {
+            changes.put(PaymentDetailConstants.bankNumberParamName, bankNumber);
+        }
+        final PaymentDetail paymentDetail = new PaymentDetail(paymentType, accountNumber, checkNumber, routingCode, receiptNumber,
+                bankNumber);
+        return paymentDetail;
+    }
+
+    public static PaymentDetail instance(final PaymentType paymentType, final String accountNumber, final String checkNumber,
+            final String routingCode, final String receiptNumber, final String bankNumber) {
+        return new PaymentDetail(paymentType, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber);
+    }
+
+    private PaymentDetail(final PaymentType paymentType, final String accountNumber, final String checkNumber, final String routingCode,
+            final String receiptNumber, final String bankNumber) {
+        this.paymentType = paymentType;
+        this.accountNumber = accountNumber;
+        this.checkNumber = checkNumber;
+        this.routingCode = routingCode;
+        this.receiptNumber = receiptNumber;
+        this.bankNumber = bankNumber;
+    }
+
+    public PaymentDetailData toData() {
+        final PaymentTypeData paymentTypeData = this.paymentType.toData();
+        final PaymentDetailData paymentDetailData = new PaymentDetailData(getId(), paymentTypeData, this.accountNumber, this.checkNumber,
+                this.routingCode, this.receiptNumber, this.bankNumber);
+        return paymentDetailData;
+    }
+
+    public PaymentType getPaymentType() {
+        return this.paymentType;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetailAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetailAssembler.java
new file mode 100755
index 0000000..051dc54
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetailAssembler.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.portfolio.paymentdetail.domain;
+
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.paymentdetail.PaymentDetailConstants;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonObject;
+
+@Service
+public class PaymentDetailAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final PaymentTypeRepositoryWrapper repositoryWrapper;
+
+    @Autowired
+    public PaymentDetailAssembler(final FromJsonHelper fromApiJsonHelper, final PaymentTypeRepositoryWrapper repositoryWrapper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.repositoryWrapper = repositoryWrapper;
+    }
+
+    public PaymentDetail fetchPaymentDetail(final JsonObject json) {
+        final Long paymentTypeId = this.fromApiJsonHelper.extractLongNamed(PaymentDetailConstants.paymentTypeParamName, json);
+        if (paymentTypeId == null) { return null; }
+
+        final PaymentType paymentType = this.repositoryWrapper.findOneWithNotFoundDetection(paymentTypeId);
+
+        final String accountNumber = this.fromApiJsonHelper.extractStringNamed(PaymentDetailConstants.accountNumberParamName, json);
+        final String checkNumber = this.fromApiJsonHelper.extractStringNamed(PaymentDetailConstants.checkNumberParamName, json);
+        final String routingCode = this.fromApiJsonHelper.extractStringNamed(PaymentDetailConstants.routingCodeParamName, json);
+        final String receiptNumber = this.fromApiJsonHelper.extractStringNamed(PaymentDetailConstants.receiptNumberParamName, json);
+        final String bankNumber = this.fromApiJsonHelper.extractStringNamed(PaymentDetailConstants.bankNumberParamName, json);
+        return PaymentDetail.instance(paymentType, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetailRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetailRepository.java
new file mode 100755
index 0000000..a40f6b9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetailRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.paymentdetail.domain;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface PaymentDetailRepository extends JpaRepository<PaymentDetail, Long>, JpaSpecificationExecutor<Loan> {
+    // no added behaviour
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformService.java
new file mode 100755
index 0000000..2500533
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformService.java
@@ -0,0 +1,33 @@
+/**
+ * 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.paymentdetail.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+
+public interface PaymentDetailWritePlatformService {
+
+    PaymentDetail createAndPersistPaymentDetail(final JsonCommand command, Map<String, Object> changes);
+
+    PaymentDetail createPaymentDetail(final JsonCommand command, Map<String, Object> changes);
+
+    PaymentDetail persistPaymentDetail(PaymentDetail paymentDetail);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..4dcf6cc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,71 @@
+/**
+ * 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.paymentdetail.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.paymentdetail.PaymentDetailConstants;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailRepository;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class PaymentDetailWritePlatformServiceJpaRepositoryImpl implements PaymentDetailWritePlatformService {
+
+    private final PaymentDetailRepository paymentDetailRepository;
+    // private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
+    private final PaymentTypeRepositoryWrapper paymentTyperepositoryWrapper;
+
+    @Autowired
+    public PaymentDetailWritePlatformServiceJpaRepositoryImpl(final PaymentDetailRepository paymentDetailRepository,
+            final PaymentTypeRepositoryWrapper paymentTyperepositoryWrapper) {
+        this.paymentDetailRepository = paymentDetailRepository;
+        this.paymentTyperepositoryWrapper = paymentTyperepositoryWrapper;
+    }
+
+    @Override
+    public PaymentDetail createPaymentDetail(final JsonCommand command, final Map<String, Object> changes) {
+        final Long paymentTypeId = command.longValueOfParameterNamed(PaymentDetailConstants.paymentTypeParamName);
+        if (paymentTypeId == null) { return null; }
+
+        final PaymentType paymentType = this.paymentTyperepositoryWrapper.findOneWithNotFoundDetection(paymentTypeId);
+        final PaymentDetail paymentDetail = PaymentDetail.generatePaymentDetail(paymentType, command, changes);
+        return paymentDetail;
+
+    }
+
+    @Override
+    @Transactional
+    public PaymentDetail persistPaymentDetail(final PaymentDetail paymentDetail) {
+        return this.paymentDetailRepository.save(paymentDetail);
+    }
+
+    @Override
+    @Transactional
+    public PaymentDetail createAndPersistPaymentDetail(final JsonCommand command, final Map<String, Object> changes) {
+        final PaymentDetail paymentDetail = createPaymentDetail(command, changes);
+        if (paymentDetail != null) { return persistPaymentDetail(paymentDetail); }
+        return paymentDetail;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java
new file mode 100644
index 0000000..ccdcd70
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java
@@ -0,0 +1,138 @@
+/**
+ * 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.paymenttype.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Path("/paymenttypes")
+@Component
+public class PaymentTypeApiResource {
+
+    private final PlatformSecurityContext securityContext;
+    private final DefaultToApiJsonSerializer<PaymentTypeData> jsonSerializer;
+    private final PaymentTypeReadPlatformService readPlatformService;
+    private final PortfolioCommandSourceWritePlatformService commandWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    // private final String resourceNameForPermissions = "PAYMENT_TYPE";
+    private final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper;
+
+    // private final Set<String> RESPONSE_DATA_PARAMETERS = new
+    // HashSet<>(Arrays.asList("id", "value", "description", "isCashPayment"));
+
+    @Autowired
+    public PaymentTypeApiResource(PlatformSecurityContext securityContext, DefaultToApiJsonSerializer<PaymentTypeData> jsonSerializer,
+            PaymentTypeReadPlatformService readPlatformService, PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper,
+            ApiRequestParameterHelper apiRequestParameterHelper, PortfolioCommandSourceWritePlatformService commandWritePlatformService) {
+        super();
+        this.securityContext = securityContext;
+        this.jsonSerializer = jsonSerializer;
+        this.readPlatformService = readPlatformService;
+        this.paymentTypeRepositoryWrapper = paymentTypeRepositoryWrapper;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandWritePlatformService = commandWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getAllPaymentTypes(@Context final UriInfo uriInfo) {
+        this.securityContext.authenticatedUser().validateHasReadPermission(PaymentTypeApiResourceConstants.resourceNameForPermissions);
+        final Collection<PaymentTypeData> paymentTypes = this.readPlatformService.retrieveAllPaymentTypes();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.jsonSerializer.serialize(settings, paymentTypes, PaymentTypeApiResourceConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{paymentTypeId}")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    public String retrieveOnePaymentType(@PathParam("paymentTypeId") final Long paymentTypeId, @Context final UriInfo uriInfo) {
+        this.securityContext.authenticatedUser().validateHasReadPermission(PaymentTypeApiResourceConstants.resourceNameForPermissions);
+        this.paymentTypeRepositoryWrapper.findOneWithNotFoundDetection(paymentTypeId);
+        final PaymentTypeData paymentTypes = this.readPlatformService.retrieveOne(paymentTypeId);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.jsonSerializer.serialize(settings, paymentTypes, PaymentTypeApiResourceConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createPaymentType(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createPaymentType().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(commandRequest);
+
+        return this.jsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{paymentTypeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updatePaymentType(@PathParam("paymentTypeId") final Long paymentTypeId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updatePaymentType(paymentTypeId).withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(commandRequest);
+
+        return this.jsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{paymentTypeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteCode(@PathParam("paymentTypeId") final Long paymentTypeId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deletePaymentType(paymentTypeId).build();
+
+        final CommandProcessingResult result = this.commandWritePlatformService.logCommandSource(commandRequest);
+
+        return this.jsonSerializer.serialize(result);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java
new file mode 100644
index 0000000..a3e67ff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java
@@ -0,0 +1,44 @@
+/**
+ * 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.paymenttype.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class PaymentTypeApiResourceConstants {
+
+    public static final String RESOURCE_NAME = "paymenttype";
+    public static final String ENTITY_NAME = "PAYMENTTYPE";
+
+    public static final String resourceNameForPermissions = "PAYMENT_TYPE";
+    public static final String ID = "id";
+    public static final String NAME = "name";
+    public static final String DESCRIPTION = "description";
+    public static final String ISCASHPAYMENT = "isCashPayment";
+    public static final String POSITION = "position";
+
+    public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(ID, NAME, DESCRIPTION, ISCASHPAYMENT));
+
+    public static final Set<String> CREATE_PAYMENT_TYPE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(NAME, DESCRIPTION,
+            ISCASHPAYMENT, POSITION));
+
+    public static final Set<String> UPDATE_PAYMENT_TYPE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(NAME, DESCRIPTION,
+            ISCASHPAYMENT, POSITION));
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java
new file mode 100644
index 0000000..03d8f92
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.paymenttype.data;
+
+public class PaymentTypeData {
+
+    @SuppressWarnings("unused")
+    private Long id;
+    @SuppressWarnings("unused")
+    private String name;
+    @SuppressWarnings("unused")
+    private String description;
+    @SuppressWarnings("unused")
+    private Boolean isCashPayment;
+    @SuppressWarnings("unused")
+    private Long position;
+
+    public PaymentTypeData(final Long id, final String name, final String description, final Boolean isCashPayment, final Long position) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.isCashPayment = isCashPayment;
+        this.position = position;
+    }
+
+    public static PaymentTypeData instance(final Long id, final String name, final String description, final Boolean isCashPayment,
+            final Long position) {
+        return new PaymentTypeData(id, name, description, isCashPayment, position);
+    }
+
+    public static PaymentTypeData instance(final Long id, final String name) {
+        String description = null;
+        Boolean isCashPayment = null;
+        Long position = null;
+        return new PaymentTypeData(id, name, description, isCashPayment, position);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java
new file mode 100644
index 0000000..a476849
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java
@@ -0,0 +1,134 @@
+/**
+ * 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.paymenttype.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class PaymentTypeDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public PaymentTypeDataValidator(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,
+                PaymentTypeApiResourceConstants.CREATE_PAYMENT_TYPE_REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(PaymentTypeApiResourceConstants.resourceNameForPermissions);
+
+        if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.NAME, element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed(PaymentTypeApiResourceConstants.NAME, element);
+            baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.NAME).value(name).notBlank();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.DESCRIPTION, element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(PaymentTypeApiResourceConstants.DESCRIPTION, element);
+            baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.DESCRIPTION).value(description).ignoreIfNull().notExceedingLengthOf(500);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.ISCASHPAYMENT, element)) {
+            final Boolean isCashPayment = this.fromApiJsonHelper
+                    .extractBooleanNamed(PaymentTypeApiResourceConstants.ISCASHPAYMENT, element);
+            baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.ISCASHPAYMENT).value(isCashPayment).validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.POSITION, element)) {
+            final Long position = this.fromApiJsonHelper.extractLongNamed(PaymentTypeApiResourceConstants.POSITION, element);
+            baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.POSITION).value(position).ignoreIfNull()
+                    .integerZeroOrGreater();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            //
+            throw new PlatformApiDataValidationException(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,
+                PaymentTypeApiResourceConstants.UPDATE_PAYMENT_TYPE_REQUEST_DATA_PARAMETERS);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(PaymentTypeApiResourceConstants.resourceNameForPermissions);
+
+        if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.NAME, element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed(PaymentTypeApiResourceConstants.NAME, element);
+            baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.NAME).value(name);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.DESCRIPTION, element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(PaymentTypeApiResourceConstants.DESCRIPTION, element);
+            baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.DESCRIPTION).value(description).ignoreIfNull().notExceedingLengthOf(500);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.ISCASHPAYMENT, element)) {
+            final Boolean isCashPayment = this.fromApiJsonHelper
+                    .extractBooleanNamed(PaymentTypeApiResourceConstants.ISCASHPAYMENT, element);
+            baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.ISCASHPAYMENT).value(isCashPayment).validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.POSITION, element)) {
+            final Long position = this.fromApiJsonHelper.extractLongNamed(PaymentTypeApiResourceConstants.POSITION, element);
+            baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.POSITION).value(position).ignoreIfNull()
+                    .integerZeroOrGreater();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java
new file mode 100644
index 0000000..0a5dc08
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java
@@ -0,0 +1,97 @@
+/**
+ * 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.paymenttype.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.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceConstants;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_payment_type")
+public class PaymentType extends AbstractPersistable<Long> {
+
+    @Column(name = "value")
+    private String name;
+
+    @Column(name = "description")
+    private String description;
+
+    @Column(name = "is_cash_payment")
+    private Boolean isCashPayment;
+
+    @Column(name = "order_position")
+    private Long position;
+
+    protected PaymentType() {}
+
+    public PaymentType(final String name, final String description, final Boolean isCashPayment, final Long position) {
+        this.name = name;
+        this.description = description;
+        this.isCashPayment = isCashPayment;
+        this.position = position;
+    }
+
+    public static PaymentType create(String name, String description, Boolean isCashPayment, Long position) {
+        return new PaymentType(name, description, isCashPayment, position);
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(3);
+
+        if (command.isChangeInStringParameterNamed(PaymentTypeApiResourceConstants.NAME, this.name)) {
+            final String newValue = command.stringValueOfParameterNamed(PaymentTypeApiResourceConstants.NAME);
+            actualChanges.put(PaymentTypeApiResourceConstants.NAME, newValue);
+            this.name = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(PaymentTypeApiResourceConstants.DESCRIPTION, this.description)) {
+            final String newDescription = command.stringValueOfParameterNamed(PaymentTypeApiResourceConstants.DESCRIPTION);
+            actualChanges.put(PaymentTypeApiResourceConstants.DESCRIPTION, newDescription);
+            this.description = StringUtils.defaultIfEmpty(newDescription, null);
+        }
+
+        if (command.isChangeInBooleanParameterNamed(PaymentTypeApiResourceConstants.ISCASHPAYMENT, this.isCashPayment)) {
+            final Boolean newCashPaymentType = command.booleanObjectValueOfParameterNamed(PaymentTypeApiResourceConstants.ISCASHPAYMENT);
+            actualChanges.put(PaymentTypeApiResourceConstants.ISCASHPAYMENT, newCashPaymentType);
+            this.isCashPayment = newCashPaymentType.booleanValue();
+        }
+
+        if (command.isChangeInLongParameterNamed(PaymentTypeApiResourceConstants.POSITION, this.position)) {
+            final Long newPosition = command.longValueOfParameterNamed(PaymentTypeApiResourceConstants.POSITION);
+            actualChanges.put(PaymentTypeApiResourceConstants.POSITION, newPosition);
+            this.position = newPosition;
+        }
+
+        return actualChanges;
+    }
+
+    public PaymentTypeData toData() {
+        return PaymentTypeData.instance(getId(), this.name, this.description, this.isCashPayment, this.position);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
new file mode 100644
index 0000000..d4119bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.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.paymenttype.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface PaymentTypeRepository extends JpaRepository<PaymentType, Long>, JpaSpecificationExecutor<PaymentType> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java
new file mode 100644
index 0000000..cb59af2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java
@@ -0,0 +1,41 @@
+/**
+ * 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.paymenttype.domain;
+
+import org.apache.fineract.portfolio.paymenttype.exception.PaymentTypeNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PaymentTypeRepositoryWrapper {
+    
+    private final PaymentTypeRepository repository;
+
+    @Autowired
+    public PaymentTypeRepositoryWrapper(final PaymentTypeRepository repository) {
+        this.repository = repository;
+    }
+
+    public PaymentType findOneWithNotFoundDetection(final Long id) {
+        final PaymentType paymentType = this.repository.findOne(id);
+        if (paymentType == null) { throw new PaymentTypeNotFoundException(id); }
+        return paymentType;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/exception/PaymentTypeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/exception/PaymentTypeNotFoundException.java
new file mode 100644
index 0000000..3e0a829
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/exception/PaymentTypeNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.paymenttype.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+
+public class PaymentTypeNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public PaymentTypeNotFoundException(final Long id) {
+        super("error.msg.payment.type.invalid", "PaymentType with " + id + " does not exist", id);
+    }
+} 
+    
+
+
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/handler/CreatePaymentTypeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/handler/CreatePaymentTypeCommandHandler.java
new file mode 100644
index 0000000..bac7f24
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/handler/CreatePaymentTypeCommandHandler.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.portfolio.paymenttype.handler;
+
+import javax.transaction.Transactional;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeWriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "PAYMENTTYPE", action = "CREATE")
+public class CreatePaymentTypeCommandHandler implements NewCommandSourceHandler {
+
+    private final PaymentTypeWriteService paymentTypeWriteService;
+
+    @Autowired
+    public CreatePaymentTypeCommandHandler(final PaymentTypeWriteService paymentTypeWriteService) {
+        this.paymentTypeWriteService = paymentTypeWriteService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.paymentTypeWriteService.createPaymentType(command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/handler/DeletePaymentTypeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/handler/DeletePaymentTypeCommandHandler.java
new file mode 100644
index 0000000..50a21dc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/handler/DeletePaymentTypeCommandHandler.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.portfolio.paymenttype.handler;
+
+import javax.transaction.Transactional;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeWriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "PAYMENTTYPE", action = "DELETE")
+public class DeletePaymentTypeCommandHandler implements NewCommandSourceHandler {
+
+    private final PaymentTypeWriteService paymentTypeWriteService;
+
+    @Autowired
+    public DeletePaymentTypeCommandHandler(final PaymentTypeWriteService paymentTypeWriteService) {
+        this.paymentTypeWriteService = paymentTypeWriteService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.paymentTypeWriteService.deletePaymentType(command.entityId());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/handler/UpdatePaymentTypeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/handler/UpdatePaymentTypeCommandHandler.java
new file mode 100644
index 0000000..381155d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/handler/UpdatePaymentTypeCommandHandler.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.portfolio.paymenttype.handler;
+
+import javax.transaction.Transactional;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeWriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "PAYMENTTYPE", action = "UPDATE")
+public class UpdatePaymentTypeCommandHandler implements NewCommandSourceHandler {
+
+    private final PaymentTypeWriteService paymentTypeWriteService;
+
+    @Autowired
+    public UpdatePaymentTypeCommandHandler(final PaymentTypeWriteService paymentTypeWriteService) {
+        this.paymentTypeWriteService = paymentTypeWriteService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.paymentTypeWriteService.updatePaymentType(command.entityId(),command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
new file mode 100644
index 0000000..b0450c7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.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.paymenttype.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+
+public interface PaymentTypeReadPlatformService {
+
+    Collection<PaymentTypeData> retrieveAllPaymentTypes();
+    PaymentTypeData retrieveOne(Long paymentTypeId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
new file mode 100644
index 0000000..143026d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
@@ -0,0 +1,87 @@
+/**
+ * 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.paymenttype.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PaymentTypeReadPlatformServiceImpl implements PaymentTypeReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public PaymentTypeReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public Collection<PaymentTypeData> retrieveAllPaymentTypes() {
+        // TODO Auto-generated method stub
+        this.context.authenticatedUser();
+
+        final PaymentTypeMapper ptm = new PaymentTypeMapper();
+        final String sql = "select " + ptm.schema() + "order by position";
+
+        return this.jdbcTemplate.query(sql, ptm, new Object[] {});
+    }
+
+    @Override
+    public PaymentTypeData retrieveOne(Long paymentTypeId) {
+        // TODO Auto-generated method stub
+        this.context.authenticatedUser();
+
+        final PaymentTypeMapper ptm = new PaymentTypeMapper();
+        final String sql = "select " + ptm.schema() + "where pt.id = ?";
+
+        return this.jdbcTemplate.queryForObject(sql, ptm, new Object[] { paymentTypeId });
+    }
+
+    private static final class PaymentTypeMapper implements RowMapper<PaymentTypeData> {
+
+        public String schema() {
+            return " pt.id as id, pt.value as name, pt.description as description,pt.is_cash_payment as isCashPayment,pt.order_position as position from m_payment_type pt ";
+        }
+
+        @Override
+        public PaymentTypeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final String description = rs.getString("description");
+            final boolean isCashPayment = rs.getBoolean("isCashPayment");
+            final Long position = rs.getLong("position");
+
+            return PaymentTypeData.instance(id, name, description, isCashPayment, position);
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteService.java
new file mode 100644
index 0000000..258f621
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.paymenttype.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface PaymentTypeWriteService {
+
+    CommandProcessingResult createPaymentType(final JsonCommand command);
+
+    CommandProcessingResult updatePaymentType(final Long id, final JsonCommand command);
+
+    CommandProcessingResult deletePaymentType(final Long id);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
new file mode 100644
index 0000000..a4ced78
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
@@ -0,0 +1,103 @@
+/**
+ * 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.paymenttype.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceConstants;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeDataValidator;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PaymentTypeWriteServiceImpl implements PaymentTypeWriteService {
+
+    private final PaymentTypeRepository repository;
+    private final PaymentTypeRepositoryWrapper repositoryWrapper;
+    private final PaymentTypeDataValidator fromApiJsonDeserializer;
+
+    @Autowired
+    public PaymentTypeWriteServiceImpl(PaymentTypeRepository repository, PaymentTypeRepositoryWrapper repositoryWrapper,
+            PaymentTypeDataValidator fromApiJsonDeserializer) {
+        this.repository = repository;
+        this.repositoryWrapper = repositoryWrapper;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+
+    }
+
+    @Override
+    public CommandProcessingResult createPaymentType(JsonCommand command) {
+        this.fromApiJsonDeserializer.validateForCreate(command.json());
+        String name = command.stringValueOfParameterNamed(PaymentTypeApiResourceConstants.NAME);
+        String description = command.stringValueOfParameterNamed(PaymentTypeApiResourceConstants.DESCRIPTION);
+        Boolean isCashPayment = command.booleanObjectValueOfParameterNamed(PaymentTypeApiResourceConstants.ISCASHPAYMENT);
+        Long position = command.longValueOfParameterNamed(PaymentTypeApiResourceConstants.POSITION);
+
+        PaymentType newPaymentType = PaymentType.create(name, description, isCashPayment, position);
+        this.repository.save(newPaymentType);
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(newPaymentType.getId()).build();
+    }
+
+    @Override
+    public CommandProcessingResult updatePaymentType(Long paymentTypeId, JsonCommand command) {
+
+        this.fromApiJsonDeserializer.validateForUpdate(command.json());
+        final PaymentType paymentType = this.repositoryWrapper.findOneWithNotFoundDetection(paymentTypeId);
+        final Map<String, Object> changes = paymentType.update(command);
+
+        if (!changes.isEmpty()) {
+            this.repository.save(paymentType);
+        }
+
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(command.entityId()).build();
+    }
+
+    @Override
+    public CommandProcessingResult deletePaymentType(Long paymentTypeId) {
+        final PaymentType paymentType = this.repositoryWrapper.findOneWithNotFoundDetection(paymentTypeId);
+        try {
+            this.repository.delete(paymentType);
+            this.repository.flush();
+        } catch (final DataIntegrityViolationException e) {
+            handleDataIntegrityIssues(e);
+        }
+        return new CommandProcessingResultBuilder().withEntityId(paymentType.getId()).build();
+    }
+
+    private void handleDataIntegrityIssues(final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("acc_product_mapping")) {
+            throw new PlatformDataIntegrityException("error.msg.payment.type.association.exist",
+                    "cannot.delete.payment.type.with.association");
+        } else if (realCause.getMessage().contains("payment_type_id")) { throw new PlatformDataIntegrityException(
+                "error.msg.payment.type.association.exist", "cannot.delete.payment.type.with.association"); }
+
+        throw new PlatformDataIntegrityException("error.msg.paymenttypes.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/api/ProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/api/ProductsApiResource.java
new file mode 100644
index 0000000..5f58b36
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/api/ProductsApiResource.java
@@ -0,0 +1,157 @@
+/**
+ * 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.products.api;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import javax.ws.rs.Consumes;
+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.QueryParam;
+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.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.apache.fineract.portfolio.products.constants.ProductsApiConstants;
+import org.apache.fineract.portfolio.products.data.ProductData;
+import org.apache.fineract.portfolio.products.service.ProductCommandsService;
+import org.apache.fineract.portfolio.products.service.ProductReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+
+@Path("/products/{type}")
+@Component
+@Scope("singleton")
+public class ProductsApiResource {
+
+    private final ApplicationContext applicationContext ;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final DefaultToApiJsonSerializer<ProductData> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<Object> toApiObjectJsonSerializer ;
+    private final PlatformSecurityContext platformSecurityContext;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    
+    @Autowired
+    public ProductsApiResource(final ApplicationContext applicationContext,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final DefaultToApiJsonSerializer<ProductData> toApiJsonSerializer,
+            final PlatformSecurityContext platformSecurityContext,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final DefaultToApiJsonSerializer<Object> toApiDividendsJsonSerializer) {
+        this.applicationContext = applicationContext ;
+        this.apiRequestParameterHelper = apiRequestParameterHelper ;
+        this.toApiJsonSerializer = toApiJsonSerializer ;
+        this.platformSecurityContext = platformSecurityContext ; 
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService ;
+        this.toApiObjectJsonSerializer = toApiDividendsJsonSerializer ;
+    }
+    
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@PathParam("type") final String productType, @Context final UriInfo uriInfo) {
+        String serviceName = productType+ProductsApiConstants.READPLATFORM_NAME ;
+        ProductReadPlatformService service = (ProductReadPlatformService) this.applicationContext.getBean(serviceName) ;
+        ProductData data = service.retrieveTemplate() ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, data, service.getResponseDataParams());
+    }
+    
+    @GET
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveProduct(@PathParam("productId") final Long productId, @PathParam("type") final String productType,
+            @Context final UriInfo uriInfo) {
+        String serviceName = productType+ProductsApiConstants.READPLATFORM_NAME ;
+        ProductReadPlatformService service = (ProductReadPlatformService) this.applicationContext.getBean(serviceName) ;
+        ProductData data = service.retrieveOne(productId) ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, data, service.getResponseDataParams());
+    }
+    
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllProducts(@PathParam("type") final String productType, @Context final UriInfo uriInfo) {
+        String serviceName = productType+ProductsApiConstants.READPLATFORM_NAME ;
+        ProductReadPlatformService service = (ProductReadPlatformService) this.applicationContext.getBean(serviceName) ;
+        Collection<ProductData> data = service.retrieveAllProducts() ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, data, service.getResponseDataParams()); 
+    }
+    
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createProduct(@PathParam("type") final String productType, final String apiRequestBodyAsJson) {
+        CommandWrapper commandWrapper = null;
+        this.platformSecurityContext.authenticatedUser();
+        commandWrapper = new CommandWrapperBuilder().createProduct(productType).withJson(apiRequestBodyAsJson).build();
+        final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+        return this.toApiJsonSerializer.serialize(commandProcessingResult);
+    }
+    
+    @POST
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String handleCommands(@PathParam("type") final String productType, @PathParam("productId") final Long productId, @QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
+        String serviceName = productType.toUpperCase()+ProductsApiConstants.PRODUCT_COMMANDSERVICE ;
+        ProductCommandsService service = (ProductCommandsService) this.applicationContext.getBean(serviceName) ;
+        final Object obj = service.handleCommand(productId, commandParam, apiRequestBodyAsJson) ;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiObjectJsonSerializer.serialize(settings, obj, new HashSet<String>());
+    }
+    
+    @PUT
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateProduct(@PathParam("type") final String productType, @PathParam("productId") final Long productId, final String apiRequestBodyAsJson) {
+        this.platformSecurityContext.authenticatedUser();
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateProduct(productType, productId)
+                .withJson(apiRequestBodyAsJson).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);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/constants/ProductsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/constants/ProductsApiConstants.java
new file mode 100644
index 0000000..58a90d0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/constants/ProductsApiConstants.java
@@ -0,0 +1,27 @@
+/**
+ * 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.products.constants;
+
+
+public interface ProductsApiConstants {
+
+    public final String READPLATFORM_NAME = "ReadPlatformService" ;
+    public final String PRODUCT_COMMANDSERVICE = "PRODUCT_COMMANDSERVICE" ;
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/data/ProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/data/ProductData.java
new file mode 100644
index 0000000..733c7d1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/data/ProductData.java
@@ -0,0 +1,24 @@
+/**
+ * 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.products.data;
+
+
+public interface ProductData {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/exception/ProductNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/exception/ProductNotFoundException.java
new file mode 100644
index 0000000..62955fc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/exception/ProductNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.products.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when loan product resources are not found.
+ */
+public class ProductNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public ProductNotFoundException(final Long id, String type) {
+        super("error.msg.product.id.invalid", type + " product with identifier " + id + " does not exist" , id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/service/ProductCommandsService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/service/ProductCommandsService.java
new file mode 100644
index 0000000..4a17a82
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/service/ProductCommandsService.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.products.service;
+
+
+
+public interface ProductCommandsService {
+
+    public Object handleCommand(final Long productId, final String command, final String jsonBody) ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/service/ProductReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/service/ProductReadPlatformService.java
new file mode 100644
index 0000000..5c93948
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/service/ProductReadPlatformService.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.portfolio.products.service;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.products.data.ProductData;
+
+public interface ProductReadPlatformService {
+
+    public Collection<ProductData> retrieveAllProducts();
+
+    public ProductData retrieveOne(final Long productId);
+
+    public ProductData retrieveTemplate();
+
+    public Set<String> getResponseDataParams();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java
new file mode 100644
index 0000000..c3f19a3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java
@@ -0,0 +1,99 @@
+/**
+ * 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.savings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.portfolio.savings.domain.FixedDepositAccount;
+import org.apache.fineract.portfolio.savings.domain.RecurringDepositAccount;
+
+/**
+ * An enumeration of different options available on account closure
+ * {@link FixedDepositAccount} & {@link RecurringDepositAccount}.
+ */
+public enum DepositAccountOnClosureType {
+
+    INVALID(0, "depositAccountClosureType.invalid"), //
+    WITHDRAW_DEPOSIT(100, "depositAccountClosureType.withdrawDeposit"), //
+    TRANSFER_TO_SAVINGS(200, "depositAccountClosureType.transferToSavings"), //
+    REINVEST(300, "depositAccountClosureType.reinvest"); //
+
+    private final Integer value;
+    private final String code;
+
+    private DepositAccountOnClosureType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static DepositAccountOnClosureType fromInt(final Integer closureTypeValue) {
+
+        if (closureTypeValue == null) { return DepositAccountOnClosureType.INVALID; }
+
+        DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.INVALID;
+        switch (closureTypeValue) {
+            case 100:
+                accountOnClosureType = DepositAccountOnClosureType.WITHDRAW_DEPOSIT;
+            break;
+            case 200:
+                accountOnClosureType = DepositAccountOnClosureType.TRANSFER_TO_SAVINGS;
+            break;
+            case 300:
+                accountOnClosureType = DepositAccountOnClosureType.REINVEST;
+            break;
+        }
+        return accountOnClosureType;
+    }
+
+    public boolean isWithdarwDeposit() {
+        return this.value.equals(DepositAccountOnClosureType.WITHDRAW_DEPOSIT.getValue());
+    }
+
+    public boolean isTransferToSavings() {
+        return this.value.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue());
+    }
+
+    public boolean isReinvest() {
+        return this.value.equals(DepositAccountOnClosureType.REINVEST.getValue());
+    }
+
+    public boolean isInvalid() {
+        return this.value.equals(DepositAccountOnClosureType.INVALID.getValue());
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final DepositAccountOnClosureType enumType : values()) {
+            if (!enumType.isInvalid()) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnHoldTransactionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnHoldTransactionType.java
new file mode 100755
index 0000000..cbaea21
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnHoldTransactionType.java
@@ -0,0 +1,74 @@
+/**
+ * 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.savings;
+
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+
+/**
+ * An enumeration of on hold transactions that can occur on a
+ * {@link SavingsAccount}.
+ */
+public enum DepositAccountOnHoldTransactionType {
+
+    INVALID(0, "deposutAccountOnHoldTransactionType.invalid"), //
+    HOLD(1, "deposutAccountOnHoldTransactionType.hold"), //
+    RELEASE(2, "deposutAccountOnHoldTransactionType.release");
+
+    private final Integer value;
+    private final String code;
+
+    private DepositAccountOnHoldTransactionType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static DepositAccountOnHoldTransactionType fromInt(final Integer transactionType) {
+
+        if (transactionType == null) { return DepositAccountOnHoldTransactionType.INVALID; }
+
+        DepositAccountOnHoldTransactionType savingsAccountTransactionType = DepositAccountOnHoldTransactionType.INVALID;
+        switch (transactionType) {
+            case 1:
+                savingsAccountTransactionType = DepositAccountOnHoldTransactionType.HOLD;
+            break;
+            case 2:
+                savingsAccountTransactionType = DepositAccountOnHoldTransactionType.RELEASE;
+            break;
+
+        }
+        return savingsAccountTransactionType;
+    }
+
+    public boolean isHold() {
+        return this.value.equals(DepositAccountOnHoldTransactionType.HOLD.getValue());
+    }
+
+    public boolean isRelease() {
+        return this.value.equals(DepositAccountOnHoldTransactionType.RELEASE.getValue());
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountType.java
new file mode 100644
index 0000000..4ed9740
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountType.java
@@ -0,0 +1,121 @@
+/**
+ * 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.savings;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+
+/**
+ * An enumeration of different transactions that can occur on a
+ * {@link SavingsAccount}.
+ */
+public enum DepositAccountType {
+
+    INVALID(0, "depositAccountType.invalid"), //
+    SAVINGS_DEPOSIT(100, "depositAccountType.savingsDeposit"), //
+    FIXED_DEPOSIT(200, "depositAccountType.fixedDeposit"), //
+    RECURRING_DEPOSIT(300, "depositAccountType.recurringDeposit"), //
+    CURRENT_DEPOSIT(400, "depositAccountType.currentDeposit");
+
+    private final Integer value;
+    private final String code;
+    private static final String SPACE = " ";
+
+    private DepositAccountType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static DepositAccountType fromInt(final Integer transactionType) {
+
+        if (transactionType == null) { return DepositAccountType.INVALID; }
+
+        DepositAccountType depositAccountType = DepositAccountType.INVALID;
+        switch (transactionType) {
+            case 100:
+                depositAccountType = DepositAccountType.SAVINGS_DEPOSIT;
+            break;
+            case 200:
+                depositAccountType = DepositAccountType.FIXED_DEPOSIT;
+            break;
+            case 300:
+                depositAccountType = DepositAccountType.RECURRING_DEPOSIT;
+            break;
+            case 400:
+                depositAccountType = DepositAccountType.CURRENT_DEPOSIT;
+            break;
+        }
+        return depositAccountType;
+    }
+
+    public boolean isSavingsDeposit() {
+        return this.value.equals(DepositAccountType.SAVINGS_DEPOSIT.getValue());
+    }
+
+    public boolean isFixedDeposit() {
+        return this.value.equals(DepositAccountType.FIXED_DEPOSIT.getValue());
+    }
+
+    public boolean isRecurringDeposit() {
+        return this.value.equals(DepositAccountType.RECURRING_DEPOSIT.getValue());
+    }
+
+    public boolean isCurrentDeposit() {
+        return this.value.equals(DepositAccountType.CURRENT_DEPOSIT.getValue());
+    }
+
+    @Override
+    public String toString() {
+        return StringUtils.replace(code, "_", SPACE);
+    }
+
+    public String resourceName() {
+
+        String resourceName = "INVALID";
+
+        switch (this) {
+            case FIXED_DEPOSIT:
+                resourceName = DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME;
+            break;
+            case RECURRING_DEPOSIT:
+                resourceName = DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME;
+            break;
+            case SAVINGS_DEPOSIT:
+                resourceName = DepositsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
+            break;
+            default:
+                resourceName = "INVALID";
+            break;
+        }
+
+        return resourceName;
+    }
+
+    public boolean isInvalid() {
+        return this.value.equals(DepositAccountType.INVALID.value);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountUtils.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountUtils.java
new file mode 100644
index 0000000..c9ae0c4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountUtils.java
@@ -0,0 +1,60 @@
+/**
+ * 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.savings;
+
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.joda.time.LocalDate;
+
+public class DepositAccountUtils {
+
+    public static final int GENERATE_MINIMUM_NUMBER_OF_FUTURE_INSTALMENTS = 5;
+
+    public static LocalDate calculateNextDepositDate(final LocalDate lastDepositDate, final PeriodFrequencyType frequency,
+            final int recurringEvery) {
+        LocalDate nextDepositDate = lastDepositDate;
+
+        switch (frequency) {
+            case DAYS:
+                nextDepositDate = lastDepositDate.plusDays(recurringEvery);
+            break;
+            case WEEKS:
+                nextDepositDate = lastDepositDate.plusWeeks(recurringEvery);
+            break;
+            case MONTHS:
+                nextDepositDate = lastDepositDate.plusMonths(recurringEvery);
+            break;
+            case YEARS:
+                nextDepositDate = lastDepositDate.plusYears(recurringEvery);
+            break;
+            case INVALID:
+            break;
+        }
+        return nextDepositDate;
+    }
+
+    public static LocalDate calculateNextDepositDate(final LocalDate lastDepositDate, final String recurrence) {
+        final PeriodFrequencyType frequencyType = CalendarFrequencyType.from(CalendarUtils.getFrequency(recurrence));
+        Integer frequency = CalendarUtils.getInterval(recurrence);
+        frequency = frequency == -1 ? 1 : frequency;
+        return calculateNextDepositDate(lastDepositDate, frequencyType, frequency);
+    }
+
+}
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
new file mode 100644
index 0000000..e2ebfa2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
@@ -0,0 +1,385 @@
+/**
+ * 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.savings;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS;
+
+public class DepositsApiConstants {
+
+    // Deposit products
+    public static final String FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME = "fixeddeposit";
+    public static final String RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME = "recurringdeposit";
+
+    // Deposit accounts
+    public static final String FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME = "fixeddepositaccount";
+    public static final String RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME = "recurringdepositaccount";
+
+    public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount";
+    public static final String SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME = "savingsaccount.transaction";
+    public static final String SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME = "savingsaccountcharge";
+
+    // deposit product actions
+    public static String summitalAction = ".summital";
+    public static String approvalAction = ".approval";
+    public static String undoApprovalAction = ".undoApproval";
+    public static String rejectAction = ".reject";
+    public static String withdrawnByApplicantAction = ".withdrawnByApplicant";
+    public static String activateAction = ".activate";
+    public static String modifyApplicationAction = ".modify";
+    public static String deleteApplicationAction = ".delete";
+    public static String undoTransactionAction = ".undotransaction";
+    public static String applyAnnualFeeTransactionAction = ".applyannualfee";
+    public static String adjustTransactionAction = ".adjusttransaction";
+    public static String closeAction = ".close";
+    public static String preMatureCloseAction = ".preMatureClose";
+    public static String payChargeTransactionAction = ".paycharge";
+    public static String waiveChargeTransactionAction = ".waivecharge";
+
+    // command
+    public static String COMMAND_UNDO_TRANSACTION = "undo";
+    public static String COMMAND_ADJUST_TRANSACTION = "modify";
+    public static String COMMAND_WAIVE_CHARGE = "waive";
+    public static String COMMAND_PAY_CHARGE = "paycharge";
+    public static String UPDATE_DEPOSIT_AMOUNT = "updateDepositAmount";
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+    public static final String monthDayFormatParamName = "monthDayFormat";
+
+    // deposit product and account parameters
+    public static final String idParamName = "id";
+    public static final String accountNoParamName = "accountNo";
+    public static final String externalIdParamName = "externalId";
+    public static final String statusParamName = "status";
+    public static final String clientIdParamName = "clientId";
+    public static final String groupIdParamName = "groupId";
+    public static final String productIdParamName = "productId";
+    public static final String fieldOfficerIdParamName = "fieldOfficerId";
+
+    public static final String submittedOnDateParamName = "submittedOnDate";
+    public static final String rejectedOnDateParamName = "rejectedOnDate";
+    public static final String withdrawnOnDateParamName = "withdrawnOnDate";
+    public static final String approvedOnDateParamName = "approvedOnDate";
+    public static final String activatedOnDateParamName = "activatedOnDate";
+    public static final String closedOnDateParamName = "closedOnDate";
+    public static final String expectedFirstDepositOnDateParamName = "expectedFirstDepositOnDate";
+
+    public static final String activeParamName = "active";
+    public static final String nameParamName = "name";
+    public static final String shortNameParamName = "shortName";
+    public static final String descriptionParamName = "description";
+    public static final String currencyCodeParamName = "currencyCode";
+    public static final String digitsAfterDecimalParamName = "digitsAfterDecimal";
+    public static final String inMultiplesOfParamName = "inMultiplesOf";
+    public static final String nominalAnnualInterestRateParamName = "nominalAnnualInterestRate";
+    public static final String interestCompoundingPeriodTypeParamName = "interestCompoundingPeriodType";
+    public static final String interestPostingPeriodTypeParamName = "interestPostingPeriodType";
+    public static final String interestCalculationTypeParamName = "interestCalculationType";
+    public static final String interestCalculationDaysInYearTypeParamName = "interestCalculationDaysInYearType";
+    public static final String lockinPeriodFrequencyParamName = "lockinPeriodFrequency";
+    public static final String lockinPeriodFrequencyTypeParamName = "lockinPeriodFrequencyType";
+    public static final String feeAmountParamName = "feeAmount";// to be deleted
+    public static final String feeOnMonthDayParamName = "feeOnMonthDay";
+    public static final String feeIntervalParamName = "feeInterval";
+    public static final String accountingRuleParamName = "accountingRule";
+    public static final String paymentTypeIdParamName = "paymentTypeId";
+    public static final String transactionAccountNumberParamName = "accountNumber";
+    public static final String checkNumberParamName = "checkNumber";
+    public static final String routingCodeParamName = "routingCode";
+    public static final String receiptNumberParamName = "receiptNumber";
+    public static final String bankNumberParamName = "bankNumber";
+
+    // Preclosure parameters
+    public static final String preClosurePenalApplicableParamName = "preClosurePenalApplicable";
+    public static final String preClosurePenalInterestParamName = "preClosurePenalInterest";
+    public static final String preClosurePenalInterestOnTypeIdParamName = "preClosurePenalInterestOnTypeId";
+    public static final String interestFreePeriodFrequencyType = "interestFreePeriodFrequencyType";
+    public static final String preClosurePenalInterestOnType = "preClosurePenalInterestOnType";
+
+    // term paramters
+    public static final String minDepositTermParamName = "minDepositTerm";
+    public static final String maxDepositTermParamName = "maxDepositTerm";
+    public static final String minDepositTermTypeIdParamName = "minDepositTermTypeId";
+    public static final String maxDepositTermTypeIdParamName = "maxDepositTermTypeId";
+    public static final String minDepositTermType = "minDepositTermType";
+    public static final String maxDepositTermType = "maxDepositTermType";
+    public static final String inMultiplesOfDepositTermParamName = "inMultiplesOfDepositTerm";
+    public static final String inMultiplesOfDepositTermTypeIdParamName = "inMultiplesOfDepositTermTypeId";
+    public static final String inMultiplesOfDepositTermType = "inMultiplesOfDepositTermType";
+
+    public static final String depositAmountParamName = "depositAmount";
+    public static final String depositMinAmountParamName = "minDepositAmount";
+    public static final String depositMaxAmountParamName = "maxDepositAmount";
+    public static final String depositPeriodParamName = "depositPeriod";
+    public static final String depositPeriodFrequencyIdParamName = "depositPeriodFrequencyId";
+
+    // recurring parameters
+    public static final String mandatoryRecommendedDepositAmountParamName = "mandatoryRecommendedDepositAmount";
+    public static final String isMandatoryDepositParamName = "isMandatoryDeposit";
+    public static final String allowWithdrawalParamName = "allowWithdrawal";
+    public static final String adjustAdvanceTowardsFuturePaymentsParamName = "adjustAdvanceTowardsFuturePayments";
+
+    public static final String recurringFrequencyTypeParamName = "recurringFrequencyType";
+    public static final String recurringFrequencyParamName = "recurringFrequency";
+    public static final String isCalendarInheritedParamName = "isCalendarInherited";
+
+    // transaction parameters
+    public static final String transactionDateParamName = "transactionDate";
+    public static final String transactionAmountParamName = "transactionAmount";
+    public static final String paymentDetailDataParamName = "paymentDetailData";
+    public static final String runningBalanceParamName = "runningBalance";
+    public static final String reversedParamName = "reversed";
+    public static final String dateParamName = "date";
+
+    // recurring deposits update parameters
+    public static final String effectiveDateParamName = "effectiveDate";
+
+    // charges parameters
+    public static final String chargeIdParamName = "chargeId";
+    public static final String chargesParamName = "charges";
+    public static final String savingsAccountChargeIdParamName = "savingsAccountChargeId";
+    public static final String chargeNameParamName = "name";
+    public static final String penaltyParamName = "penalty";
+    public static final String chargeTimeTypeParamName = "chargeTimeType";
+    public static final String dueAsOfDateParamName = "dueDate";
+    public static final String chargeCalculationTypeParamName = "chargeCalculationType";
+    public static final String percentageParamName = "percentage";
+    public static final String amountPercentageAppliedToParamName = "amountPercentageAppliedTo";
+    public static final String currencyParamName = "currency";
+    public static final String amountWaivedParamName = "amountWaived";
+    public static final String amountWrittenOffParamName = "amountWrittenOff";
+    public static final String amountOutstandingParamName = "amountOutstanding";
+    public static final String amountOrPercentageParamName = "amountOrPercentage";
+    public static final String amountParamName = "amount";
+    public static final String amountPaidParamName = "amountPaid";
+    public static final String chargeOptionsParamName = "chargeOptions";
+    public static final String chargePaymentModeParamName = "chargePaymentMode";
+
+    public static final String noteParamName = "note";
+    public static final String chartsParamName = "charts";
+    public static final String chartIdParamName = "chartId";
+
+    // deposit account associations
+    public static final String transactions = "transactions";
+    public static final String charges = "charges";
+    public static final String activeChart = "activeChart";
+
+    // account closure
+    public static final String onAccountClosureIdParamName = "onAccountClosureId";
+    public static final String transferDescriptionParamName = "transferDescription";
+    public static final String toSavingsAccountIdParamName = "toSavingsAccountId";
+    public static final String savingsAccounts = "savingsAccounts";
+
+    public static final String preMatureCloseOnDateParamName = "preMatureCloseOnDate";
+
+    public static final String linkedAccountParamName = "linkAccountId";
+    public static final String transferInterestToSavingsParamName = "transferInterestToSavings";
+
+    // 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
+     */
+    private static final Set<String> DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
+            digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName,
+            interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
+            interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
+            accountingRuleParamName, chargesParamName, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), chartsParamName));
+
+    private static final Set<String> PRECLOSURE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(preClosurePenalApplicableParamName,
+            preClosurePenalInterestParamName, preClosurePenalInterestOnTypeIdParamName));
+
+    private static final Set<String> PRECLOSURE_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(preClosurePenalApplicableParamName,
+            preClosurePenalInterestParamName, preClosurePenalInterestOnType));
+
+    private static final Set<String> DEPOSIT_TERM_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(minDepositTermParamName,
+            maxDepositTermParamName, minDepositTermTypeIdParamName, maxDepositTermTypeIdParamName, inMultiplesOfDepositTermParamName,
+            inMultiplesOfDepositTermTypeIdParamName, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName));
+
+    private static final Set<String> DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(minDepositTermParamName,
+            maxDepositTermParamName, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTermParamName,
+            inMultiplesOfDepositTermType, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName));
+
+    private static final Set<String> RECURRING_DETAILS_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+            mandatoryRecommendedDepositAmountParamName, isMandatoryDepositParamName, allowWithdrawalParamName,
+            adjustAdvanceTowardsFuturePaymentsParamName, recurringFrequencyTypeParamName, recurringFrequencyParamName,
+            isCalendarInheritedParamName));
+
+    private static final Set<String> RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+            mandatoryRecommendedDepositAmountParamName, isMandatoryDepositParamName, allowWithdrawalParamName,
+            adjustAdvanceTowardsFuturePaymentsParamName, recurringFrequencyTypeParamName, recurringFrequencyParamName,
+            isCalendarInheritedParamName));
+
+    public 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();
+    public static final Set<String> FIXED_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS = fixedDepositProductResponseData();
+
+    public static final Set<String> RECURRING_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = recurringDepositProductRequestData();
+    public static final Set<String> RECURRING_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS = recurringDepositProductResponseData();
+
+    private static Set<String> fixedDepositProductRequestData() {
+        final Set<String> fixedDepositRequestData = new HashSet<>();
+        fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
+        fixedDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS);
+        fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS);
+        return fixedDepositRequestData;
+    }
+
+    private static Set<String> fixedDepositProductResponseData() {
+        final Set<String> fixedDepositRequestData = new HashSet<>();
+        fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
+        fixedDepositRequestData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS);
+        fixedDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
+        return fixedDepositRequestData;
+    }
+
+    private static Set<String> recurringDepositProductRequestData() {
+        final Set<String> recurringDepositRequestData = new HashSet<>();
+        recurringDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
+        recurringDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS);
+        recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS);
+        recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS);
+        recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
+        return recurringDepositRequestData;
+    }
+
+    private static Set<String> recurringDepositProductResponseData() {
+        final Set<String> recurringDepositRequestData = new HashSet<>();
+        recurringDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
+        recurringDepositRequestData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS);
+        recurringDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
+        recurringDepositRequestData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS);
+        recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
+        return recurringDepositRequestData;
+    }
+
+    /**
+     * 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));
+
+    public static final Set<String> FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = fixedDepositAccountRequestData();
+    public static final Set<String> FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = fixedDepositAccountResponseData();
+
+    public static final Set<String> RECURRING_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = recurringDepositAccountRequestData();
+    public static final Set<String> RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = recurringDepositAccountResponseData();
+
+    private static Set<String> fixedDepositAccountRequestData() {
+        final Set<String> fixedDepositRequestData = new HashSet<>();
+        fixedDepositRequestData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS);
+        fixedDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS);
+        fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS);
+        fixedDepositRequestData.add(linkedAccountParamName);
+        fixedDepositRequestData.add(transferInterestToSavingsParamName);
+        return fixedDepositRequestData;
+    }
+
+    private static Set<String> fixedDepositAccountResponseData() {
+        final Set<String> fixedDepositResponseData = new HashSet<>();
+        fixedDepositResponseData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS);
+        fixedDepositResponseData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS);
+        fixedDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
+        fixedDepositResponseData.add(linkedAccountParamName);
+        fixedDepositResponseData.add(transferInterestToSavingsParamName);
+        return fixedDepositResponseData;
+    }
+
+    private static Set<String> recurringDepositAccountRequestData() {
+        final Set<String> recurringDepositRequestData = new HashSet<>();
+        recurringDepositRequestData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS);
+        recurringDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS);
+        recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS);
+        recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS);
+        return recurringDepositRequestData;
+    }
+
+    private static Set<String> recurringDepositAccountResponseData() {
+        final Set<String> recurringDepositResponseData = new HashSet<>();
+        recurringDepositResponseData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS);
+        recurringDepositResponseData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS);
+        recurringDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
+        recurringDepositResponseData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS);
+        return recurringDepositResponseData;
+    }
+
+    public static final Set<String> DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName,
+            transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName));
+
+    public static final Set<String> DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(localeParamName, dateFormatParamName, mandatoryRecommendedDepositAmountParamName, effectiveDateParamName));
+
+    public static final Set<String> FIXED_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,
+            dateFormatParamName, activatedOnDateParamName));
+
+    public static final Set<String> DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, closedOnDateParamName, noteParamName, onAccountClosureIdParamName, paymentTypeIdParamName,
+            transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName,
+            transferDescriptionParamName, toSavingsAccountIdParamName));
+
+    public static final Set<String> DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+            localeParamName, dateFormatParamName, closedOnDateParamName));
+
+    public 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,
+            amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, feeOnMonthDayParamName, monthDayFormatParamName,
+            feeIntervalParamName));
+
+    public 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/PreClosurePenalInterestOnType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/PreClosurePenalInterestOnType.java
new file mode 100644
index 0000000..150fee2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/PreClosurePenalInterestOnType.java
@@ -0,0 +1,84 @@
+/**
+ * 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.savings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An enumeration of supported calendar periods used in savings.
+ */
+public enum PreClosurePenalInterestOnType {
+    INVALID(0, "preClosurePenalInterestOnType.invalid"), WHOLE_TERM(1, "preClosurePenalInterestOnType.wholeTerm"), //
+    TILL_PREMATURE_WITHDRAWAL(2, "preClosurePenalInterestOnType.tillPrematureWithdrawal"); //
+
+    private final Integer value;
+    private final String code;
+
+    private PreClosurePenalInterestOnType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static PreClosurePenalInterestOnType fromInt(final Integer type) {
+        PreClosurePenalInterestOnType penalInterestType = PreClosurePenalInterestOnType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 1:
+                    penalInterestType = PreClosurePenalInterestOnType.WHOLE_TERM;
+                break;
+                case 2:
+                    penalInterestType = PreClosurePenalInterestOnType.TILL_PREMATURE_WITHDRAWAL;
+                break;
+            }
+        }
+        return penalInterestType;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final PreClosurePenalInterestOnType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public boolean isInvalid() {
+        return this.value.equals(PreClosurePenalInterestOnType.INVALID.value);
+    }
+
+    public boolean isWholeTerm() {
+        return this.value.equals(PreClosurePenalInterestOnType.WHOLE_TERM.getValue());
+    }
+
+    public boolean isTillPrematureWithdrawal() {
+        return this.value.equals(PreClosurePenalInterestOnType.TILL_PREMATURE_WITHDRAWAL.getValue());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/RecurringDepositType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/RecurringDepositType.java
new file mode 100644
index 0000000..a3eff1e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/RecurringDepositType.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.portfolio.savings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An enumeration of supported calendar periods used in savings.
+ */
+public enum RecurringDepositType {
+    INVALID(0, "recurringDepositType.invalid"), VOLUNTARY(1, "recurringDepositType.voluntary"), //
+    MANDATORY(2, "recurringDepositType.mandatory"); //
+
+    private final Integer value;
+    private final String code;
+
+    private RecurringDepositType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static RecurringDepositType fromInt(final Integer type) {
+        RecurringDepositType rdType = RecurringDepositType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 1:
+                    rdType = RecurringDepositType.VOLUNTARY;
+                break;
+                case 2:
+                    rdType = RecurringDepositType.MANDATORY;
+                break;
+            }
+        }
+        return rdType;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final RecurringDepositType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public boolean isInvalid() {
+        return this.value.equals(RecurringDepositType.INVALID.value);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java
new file mode 100644
index 0000000..6ff57f7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java
@@ -0,0 +1,171 @@
+/**
+ * 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.savings;
+
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+
+/**
+ * An enumeration of different transactions that can occur on a
+ * {@link SavingsAccount}.
+ */
+public enum SavingsAccountTransactionType {
+
+    INVALID(0, "savingsAccountTransactionType.invalid"), //
+    DEPOSIT(1, "savingsAccountTransactionType.deposit"), //
+    WITHDRAWAL(2, "savingsAccountTransactionType.withdrawal"), //
+    INTEREST_POSTING(3, "savingsAccountTransactionType.interestPosting"), //
+    WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee"), //
+    ANNUAL_FEE(5, "savingsAccountTransactionType.annualFee"), //
+    WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharge"), //
+    PAY_CHARGE(7, "savingsAccountTransactionType.payCharge"), //
+    INITIATE_TRANSFER(12, "savingsAccountTransactionType.initiateTransfer"), //
+    APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), //
+    WITHDRAW_TRANSFER(14, "savingsAccountTransactionType.withdrawTransfer"), //
+    REJECT_TRANSFER(15, "savingsAccountTransactionType.rejectTransfer"), WRITTEN_OFF(16, "savingsAccountTransactionType.writtenoff"), //
+    OVERDRAFT_INTEREST(17, "savingsAccountTransactionType.overdraftInterest"); //
+
+    private final Integer value;
+    private final String code;
+
+    private SavingsAccountTransactionType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static SavingsAccountTransactionType fromInt(final Integer transactionType) {
+
+        if (transactionType == null) { return SavingsAccountTransactionType.INVALID; }
+
+        SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.INVALID;
+        switch (transactionType) {
+            case 1:
+                savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT;
+            break;
+            case 2:
+                savingsAccountTransactionType = SavingsAccountTransactionType.WITHDRAWAL;
+            break;
+            case 3:
+                savingsAccountTransactionType = SavingsAccountTransactionType.INTEREST_POSTING;
+            break;
+            case 4:
+                savingsAccountTransactionType = SavingsAccountTransactionType.WITHDRAWAL_FEE;
+            break;
+            case 5:
+                savingsAccountTransactionType = SavingsAccountTransactionType.ANNUAL_FEE;
+            break;
+            case 6:
+                savingsAccountTransactionType = SavingsAccountTransactionType.WAIVE_CHARGES;
+            break;
+            case 7:
+                savingsAccountTransactionType = SavingsAccountTransactionType.PAY_CHARGE;
+            break;
+            case 12:
+                savingsAccountTransactionType = SavingsAccountTransactionType.INITIATE_TRANSFER;
+            break;
+            case 13:
+                savingsAccountTransactionType = SavingsAccountTransactionType.APPROVE_TRANSFER;
+            break;
+            case 14:
+                savingsAccountTransactionType = SavingsAccountTransactionType.WITHDRAW_TRANSFER;
+            break;
+            case 15:
+                savingsAccountTransactionType = SavingsAccountTransactionType.REJECT_TRANSFER;
+            break;
+            case 16:
+                savingsAccountTransactionType = SavingsAccountTransactionType.WRITTEN_OFF;
+            break;
+            case 17:
+                savingsAccountTransactionType = SavingsAccountTransactionType.OVERDRAFT_INTEREST;
+            break;
+        }
+        return savingsAccountTransactionType;
+    }
+
+    public boolean isDeposit() {
+        return this.value.equals(SavingsAccountTransactionType.DEPOSIT.getValue());
+    }
+
+    public boolean isWithdrawal() {
+        return this.value.equals(SavingsAccountTransactionType.WITHDRAWAL.getValue());
+    }
+
+    public boolean isInterestPosting() {
+        return this.value.equals(SavingsAccountTransactionType.INTEREST_POSTING.getValue());
+    }
+
+    public boolean isWithdrawalFee() {
+        return this.value.equals(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue());
+    }
+
+    public boolean isAnnualFee() {
+        return this.value.equals(SavingsAccountTransactionType.ANNUAL_FEE.getValue());
+    }
+
+    public boolean isPayCharge() {
+        return this.value.equals(SavingsAccountTransactionType.PAY_CHARGE.getValue());
+    }
+
+    public boolean isChargeTransaction() {
+        return isPayCharge() || isWithdrawalFee() || isAnnualFee();
+    }
+
+    public boolean isWaiveCharge() {
+        return this.value.equals(SavingsAccountTransactionType.WAIVE_CHARGES.getValue());
+    }
+
+    public boolean isTransferInitiation() {
+        return this.value.equals(SavingsAccountTransactionType.INITIATE_TRANSFER.getValue());
+    }
+
+    public boolean isTransferApproval() {
+        return this.value.equals(SavingsAccountTransactionType.APPROVE_TRANSFER.getValue());
+    }
+
+    public boolean isTransferRejection() {
+        return this.value.equals(SavingsAccountTransactionType.REJECT_TRANSFER.getValue());
+    }
+
+    public boolean isTransferWithdrawal() {
+        return this.value.equals(SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue());
+    }
+
+    public boolean isWrittenoff() {
+        return this.value.equals(SavingsAccountTransactionType.WRITTEN_OFF.getValue());
+    }
+
+    public boolean isIncomeFromInterest() {
+        return this.value.equals(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue());
+    }
+
+    public boolean isDebit() {
+        return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isPayCharge() || isIncomeFromInterest();
+    }
+
+    public boolean isCredit() {
+        return isDeposit() || isInterestPosting();
+    }
+}
\ 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
new file mode 100644
index 0000000..7648b61
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
@@ -0,0 +1,255 @@
+/**
+ * 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.savings;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+
+public class SavingsApiConstants {
+
+    public static final String SAVINGS_PRODUCT_RESOURCE_NAME = "savingsproduct";
+    public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount";
+    public static final String SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME = "savingsaccount.transaction";
+    public static final String SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME = "savingsaccountcharge";
+
+    // actions
+    public static String summitalAction = ".summital";
+    public static String approvalAction = ".approval";
+    public static String undoApprovalAction = ".undoApproval";
+    public static String rejectAction = ".reject";
+    public static String withdrawnByApplicantAction = ".withdrawnByApplicant";
+    public static String activateAction = ".activate";
+    public static String modifyApplicationAction = ".modify";
+    public static String deleteApplicationAction = ".delete";
+    public static String undoTransactionAction = ".undotransaction";
+    public static String applyAnnualFeeTransactionAction = ".applyannualfee";
+    public static String adjustTransactionAction = ".adjusttransaction";
+    public static String closeAction = ".close";
+    public static String payChargeTransactionAction = ".paycharge";
+    public static String waiveChargeTransactionAction = ".waivecharge";
+    public static String updateMaturityDetailsAction = ".updateMaturityDetails";
+
+    // command
+    public static String COMMAND_UNDO_TRANSACTION = "undo";
+    public static String COMMAND_ADJUST_TRANSACTION = "modify";
+    public static String COMMAND_WAIVE_CHARGE = "waive";
+    public static String COMMAND_PAY_CHARGE = "paycharge";
+    public static String COMMAND_INACTIVATE_CHARGE = "inactivate";
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+    public static final String monthDayFormatParamName = "monthDayFormat";
+    public static final String staffIdParamName = "savingsOfficerId";
+
+    // savings product and account parameters
+    public static final String idParamName = "id";
+    public static final String accountNoParamName = "accountNo";
+    public static final String externalIdParamName = "externalId";
+    public static final String statusParamName = "status";
+    public static final String clientIdParamName = "clientId";
+    public static final String groupIdParamName = "groupId";
+    public static final String productIdParamName = "productId";
+    public static final String fieldOfficerIdParamName = "fieldOfficerId";
+
+    public static final String submittedOnDateParamName = "submittedOnDate";
+    public static final String rejectedOnDateParamName = "rejectedOnDate";
+    public static final String withdrawnOnDateParamName = "withdrawnOnDate";
+    public static final String approvedOnDateParamName = "approvedOnDate";
+    public static final String activatedOnDateParamName = "activatedOnDate";
+    public static final String closedOnDateParamName = "closedOnDate";
+
+    public static final String activeParamName = "active";
+    public static final String nameParamName = "name";
+    public static final String shortNameParamName = "shortName";
+    public static final String descriptionParamName = "description";
+    public static final String currencyCodeParamName = "currencyCode";
+    public static final String digitsAfterDecimalParamName = "digitsAfterDecimal";
+    public static final String inMultiplesOfParamName = "inMultiplesOf";
+    public static final String nominalAnnualInterestRateParamName = "nominalAnnualInterestRate";
+    public static final String interestCompoundingPeriodTypeParamName = "interestCompoundingPeriodType";
+    public static final String interestPostingPeriodTypeParamName = "interestPostingPeriodType";
+    public static final String interestCalculationTypeParamName = "interestCalculationType";
+    public static final String interestCalculationDaysInYearTypeParamName = "interestCalculationDaysInYearType";
+    public static final String minRequiredOpeningBalanceParamName = "minRequiredOpeningBalance";
+    public static final String lockinPeriodFrequencyParamName = "lockinPeriodFrequency";
+    public static final String lockinPeriodFrequencyTypeParamName = "lockinPeriodFrequencyType";
+    public static final String withdrawalFeeAmountParamName = "withdrawalFeeAmount";
+    public static final String withdrawalFeeTypeParamName = "withdrawalFeeType";
+    public static final String withdrawalFeeForTransfersParamName = "withdrawalFeeForTransfers";
+    public static final String feeAmountParamName = "feeAmount";// to be deleted
+    public static final String feeOnMonthDayParamName = "feeOnMonthDay";
+    public static final String feeIntervalParamName = "feeInterval";
+    public static final String accountingRuleParamName = "accountingRule";
+    public static final String paymentTypeIdParamName = "paymentTypeId";
+    public static final String transactionAccountNumberParamName = "accountNumber";
+    public static final String checkNumberParamName = "checkNumber";
+    public static final String routingCodeParamName = "routingCode";
+    public static final String receiptNumberParamName = "receiptNumber";
+    public static final String bankNumberParamName = "bankNumber";
+    public static final String allowOverdraftParamName = "allowOverdraft";
+    public static final String overdraftLimitParamName = "overdraftLimit";
+    public static final String nominalAnnualInterestRateOverdraftParamName = "nominalAnnualInterestRateOverdraft";
+    public static final String minOverdraftForInterestCalculationParamName = "minOverdraftForInterestCalculation";
+    public static final String minRequiredBalanceParamName = "minRequiredBalance";
+    public static final String enforceMinRequiredBalanceParamName = "enforceMinRequiredBalance";
+    public static final String minBalanceForInterestCalculationParamName = "minBalanceForInterestCalculation";
+    public static final String withdrawBalanceParamName = "withdrawBalance";
+    public static final String onHoldFundsParamName = "onHoldFunds";
+
+    // transaction parameters
+    public static final String transactionDateParamName = "transactionDate";
+    public static final String transactionAmountParamName = "transactionAmount";
+    public static final String paymentDetailDataParamName = "paymentDetailData";
+    public static final String runningBalanceParamName = "runningBalance";
+    public static final String reversedParamName = "reversed";
+    public static final String dateParamName = "date";
+
+    // charges parameters
+    public static final String chargeIdParamName = "chargeId";
+    public static final String chargesParamName = "charges";
+    public static final String savingsAccountChargeIdParamName = "savingsAccountChargeId";
+    public static final String chargeNameParamName = "name";
+    public static final String penaltyParamName = "penalty";
+    public static final String chargeTimeTypeParamName = "chargeTimeType";
+    public static final String dueAsOfDateParamName = "dueDate";
+    public static final String chargeCalculationTypeParamName = "chargeCalculationType";
+    public static final String percentageParamName = "percentage";
+    public static final String amountPercentageAppliedToParamName = "amountPercentageAppliedTo";
+    public static final String currencyParamName = "currency";
+    public static final String amountWaivedParamName = "amountWaived";
+    public static final String amountWrittenOffParamName = "amountWrittenOff";
+    public static final String amountOutstandingParamName = "amountOutstanding";
+    public static final String amountOrPercentageParamName = "amountOrPercentage";
+    public static final String amountParamName = "amount";
+    public static final String amountPaidParamName = "amountPaid";
+    public static final String chargeOptionsParamName = "chargeOptions";
+    public static final String chargePaymentModeParamName = "chargePaymentMode";
+
+    public static final String noteParamName = "note";
+
+    // Savings account associations
+    public static final String transactions = "transactions";
+    public static final String charges = "charges";
+    public static final String linkedAccount = "linkedAccount";
+
+    // Savings on hold transaction
+    public static final String onHoldTransactionTypeParamName = "transactionType";
+    public static final String onHoldTransactionDateParamName = "transactionDate";
+    public static final String onHoldReversedParamName = "reversed";
+
+    public static final Set<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
+            digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName,
+            interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
+            interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName,
+            lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
+            withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, accountingRuleParamName, chargesParamName,
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
+            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), allowOverdraftParamName, overdraftLimitParamName,
+            nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
+            minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, minBalanceForInterestCalculationParamName));
+
+    /**
+     * These parameters will match the class level parameters of
+     * {@link SavingsProductData}. Where possible, we try to get response
+     * parameters to match those of request parameters.
+     */
+    public static final Set<String> SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, nameParamName,
+            shortNameParamName, descriptionParamName, "currency", digitsAfterDecimalParamName, inMultiplesOfParamName,
+            nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
+            interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName,
+            lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
+            withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, "currencyOptions",
+            "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions",
+            "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions",
+            nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName));
+
+    public static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, monthDayFormatParamName, staffIdParamName, accountNoParamName, externalIdParamName, clientIdParamName,
+            groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName,
+            interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
+            interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName,
+            lockinPeriodFrequencyTypeParamName,
+            // withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
+            withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, chargesParamName, allowOverdraftParamName,
+            overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName,
+            nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName));
+
+    /**
+     * These parameters will match the class level parameters of
+     * {@link SavingsAccountData}. Where possible, we try to get response
+     * parameters to match those of request parameters.
+     */
+    public static final Set<String> SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, accountNoParamName,
+            externalIdParamName, statusParamName, activatedOnDateParamName, staffIdParamName, clientIdParamName, "clientName",
+            groupIdParamName, "groupName", "savingsProductId", "savingsProductName", "currency", nominalAnnualInterestRateParamName,
+            interestCompoundingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
+            minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
+            withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName,
+            feeOnMonthDayParamName, "summary", "transactions", "productOptions", "interestCompoundingPeriodTypeOptions",
+            "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions",
+            "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "annualFee", onHoldFundsParamName,
+            nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName));
+
+    public static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName,
+            transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName));
+
+    public static final Set<String> SAVINGS_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,
+            dateFormatParamName, activatedOnDateParamName));
+
+    public static final Set<String> SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, closedOnDateParamName, noteParamName, paymentTypeIdParamName, withdrawBalanceParamName,
+            transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName));
+
+    public 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,
+            amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, feeOnMonthDayParamName, monthDayFormatParamName,
+            feeIntervalParamName));
+
+    public static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+            amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
+
+    public static final Set<String> SAVINGS_ACCOUNT_ON_HOLD_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
+            amountParamName, onHoldTransactionTypeParamName, onHoldTransactionDateParamName, onHoldReversedParamName));
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsCompoundingInterestPeriodType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsCompoundingInterestPeriodType.java
new file mode 100644
index 0000000..01e5ce5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsCompoundingInterestPeriodType.java
@@ -0,0 +1,107 @@
+/**
+ * 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.savings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * The compounding interest period is the span of time at the end of which
+ * savings in a client's account earn interest.
+ * </p>
+ */
+public enum SavingsCompoundingInterestPeriodType {
+
+    INVALID(0, "savingsCompoundingInterestPeriodType.invalid"), //
+    DAILY(1, "savingsCompoundingInterestPeriodType.daily"), //
+    // WEEKLY(2, "savingsCompoundingInterestPeriodType.weekly"), //
+    // BIWEEKLY(3, "savingsCompoundingInterestPeriodType.biweekly"), //
+    MONTHLY(4, "savingsCompoundingInterestPeriodType.monthly"),
+
+    QUATERLY(5, "savingsCompoundingInterestPeriodType.quarterly"), //
+    BI_ANNUAL(6, "savingsCompoundingInterestPeriodType.biannual"), //
+    ANNUAL(7, "savingsCompoundingInterestPeriodType.annual"); //
+
+    // NO_COMPOUNDING_SIMPLE_INTEREST(8,
+    // "savingsCompoundingInterestPeriodType.nocompounding");
+
+    private final Integer value;
+    private final String code;
+
+    private SavingsCompoundingInterestPeriodType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final SavingsCompoundingInterestPeriodType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public static SavingsCompoundingInterestPeriodType fromInt(final Integer type) {
+        SavingsCompoundingInterestPeriodType repaymentFrequencyType = SavingsCompoundingInterestPeriodType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 1:
+                    repaymentFrequencyType = SavingsCompoundingInterestPeriodType.DAILY;
+                break;
+                case 2:
+                // repaymentFrequencyType =
+                // SavingsCompoundingInterestPeriodType.WEEKLY;
+                break;
+                case 3:
+                // repaymentFrequencyType =
+                // SavingsCompoundingInterestPeriodType.BIWEEKLY;
+                break;
+                case 4:
+                    repaymentFrequencyType = SavingsCompoundingInterestPeriodType.MONTHLY;
+                break;
+                case 5:
+                    repaymentFrequencyType = SavingsCompoundingInterestPeriodType.QUATERLY;
+                break;
+                case 6:
+                    repaymentFrequencyType = SavingsCompoundingInterestPeriodType.BI_ANNUAL;
+                break;
+                case 7:
+                    repaymentFrequencyType = SavingsCompoundingInterestPeriodType.ANNUAL;
+                break;
+                case 8:
+                // repaymentFrequencyType =
+                // SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST;
+                break;
+            }
+        }
+        return repaymentFrequencyType;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationDaysInYearType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationDaysInYearType.java
new file mode 100644
index 0000000..0630f98
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationDaysInYearType.java
@@ -0,0 +1,79 @@
+/**
+ * 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.savings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <ul>
+ * People typically use either of the following settings when calculating there
+ * interest using the daily method:
+ * <li>360 and</li>
+ * <li>365</li>
+ * </ul>
+ */
+public enum SavingsInterestCalculationDaysInYearType {
+
+    INVALID(0, "savingsInterestCalculationDaysInYearType.invalid"), //
+    DAYS_360(360, "savingsInterestCalculationDaysInYearType.days360"), //
+    DAYS_365(365, "savingsInterestCalculationDaysInYearType.days365");
+
+    private final Integer value;
+    private final String code;
+
+    private SavingsInterestCalculationDaysInYearType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final SavingsInterestCalculationDaysInYearType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public static SavingsInterestCalculationDaysInYearType fromInt(final Integer type) {
+        SavingsInterestCalculationDaysInYearType repaymentFrequencyType = SavingsInterestCalculationDaysInYearType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 360:
+                    repaymentFrequencyType = SavingsInterestCalculationDaysInYearType.DAYS_360;
+                break;
+                case 365:
+                    repaymentFrequencyType = SavingsInterestCalculationDaysInYearType.DAYS_365;
+                break;
+            }
+        }
+        return repaymentFrequencyType;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationType.java
new file mode 100644
index 0000000..d9800f8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationType.java
@@ -0,0 +1,93 @@
+/**
+ * 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.savings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <ul>
+ * There are two methods to calculate the interest on a savings account:
+ * <li>The daily balance method; and</li>
+ * <li>The average daily balance method.</li>
+ * </ul>
+ * 
+ * <p>
+ * The interest calculation must be based on a point in time for determining the
+ * balance in the account, such as:
+ * </p>
+ * <ul>
+ * <li>beginning-of-day balance</li>
+ * <li>end-of-day balance</li>
+ * <li>close-of-business-day balance</li>
+ * </ul>
+ * 
+ * <p>
+ * Any one of the three may be used, but must be applied consistently.
+ * End-of-day balance is used by default at present.
+ * </p>
+ */
+public enum SavingsInterestCalculationType {
+
+    INVALID(0, "savingsInterestCalculationType.invalid"), //
+    DAILY_BALANCE(1, "savingsInterestCalculationType.dailybalance"), //
+    AVERAGE_DAILY_BALANCE(2, "savingsInterestCalculationType.averagedailybalance");
+
+    private final Integer value;
+    private final String code;
+
+    private SavingsInterestCalculationType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final SavingsInterestCalculationType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public static SavingsInterestCalculationType fromInt(final Integer type) {
+        SavingsInterestCalculationType repaymentFrequencyType = SavingsInterestCalculationType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 1:
+                    repaymentFrequencyType = SavingsInterestCalculationType.DAILY_BALANCE;
+                break;
+                case 2:
+                    repaymentFrequencyType = SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE;
+                break;
+            }
+        }
+        return repaymentFrequencyType;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPeriodFrequencyType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPeriodFrequencyType.java
new file mode 100644
index 0000000..70190b8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPeriodFrequencyType.java
@@ -0,0 +1,85 @@
+/**
+ * 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.savings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An enumeration of supported calendar periods used in savings.
+ */
+public enum SavingsPeriodFrequencyType {
+    DAYS(0, "savingsPeriodFrequencyType.days"), //
+    WEEKS(1, "savingsPeriodFrequencyType.weeks"), //
+    MONTHS(2, "savingsPeriodFrequencyType.months"), //
+    YEARS(3, "savingsPeriodFrequencyType.years"), //
+    INVALID(4, "savingsPeriodFrequencyType.invalid");
+
+    private final Integer value;
+    private final String code;
+
+    private SavingsPeriodFrequencyType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static SavingsPeriodFrequencyType fromInt(final Integer type) {
+        SavingsPeriodFrequencyType repaymentFrequencyType = SavingsPeriodFrequencyType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 0:
+                    repaymentFrequencyType = SavingsPeriodFrequencyType.DAYS;
+                break;
+                case 1:
+                    repaymentFrequencyType = SavingsPeriodFrequencyType.WEEKS;
+                break;
+                case 2:
+                    repaymentFrequencyType = SavingsPeriodFrequencyType.MONTHS;
+                break;
+                case 3:
+                    repaymentFrequencyType = SavingsPeriodFrequencyType.YEARS;
+                break;
+            }
+        }
+        return repaymentFrequencyType;
+    }
+
+    public boolean isInvalid() {
+        return this.value.equals(SavingsPeriodFrequencyType.INVALID.value);
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final SavingsPeriodFrequencyType enumType : values()) {
+            if (!enumType.isInvalid()) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java
new file mode 100644
index 0000000..3dd09a7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java
@@ -0,0 +1,82 @@
+/**
+ * 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.savings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The interest posting period is the span of time at the end of which savings
+ * earned but not yet credited/posted in a client's account is credited/posted.
+ */
+public enum SavingsPostingInterestPeriodType {
+
+    INVALID(0, "savingsPostingInterestPeriodType.invalid"), //
+    MONTHLY(4, "savingsPostingInterestPeriodType.monthly"), //
+    QUATERLY(5, "savingsPostingInterestPeriodType.quarterly"), //
+    BIANNUAL(6, "savingsPostingInterestPeriodType.biannual"), ANNUAL(7, "savingsPostingInterestPeriodType.annual");
+
+    private final Integer value;
+    private final String code;
+
+    private SavingsPostingInterestPeriodType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final SavingsPostingInterestPeriodType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public static SavingsPostingInterestPeriodType fromInt(final Integer type) {
+        SavingsPostingInterestPeriodType repaymentFrequencyType = SavingsPostingInterestPeriodType.INVALID;
+        if (type != null) {
+            switch (type) {
+                case 4:
+                    repaymentFrequencyType = SavingsPostingInterestPeriodType.MONTHLY;
+                break;
+                case 5:
+                    repaymentFrequencyType = SavingsPostingInterestPeriodType.QUATERLY;
+                break;
+                case 6:
+                    repaymentFrequencyType = SavingsPostingInterestPeriodType.BIANNUAL;
+                break;
+                case 7:
+                    repaymentFrequencyType = SavingsPostingInterestPeriodType.ANNUAL;
+                break;
+            }
+        }
+        return repaymentFrequencyType;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java
new file mode 100644
index 0000000..2cef4ad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java
@@ -0,0 +1,59 @@
+/**
+ * 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.savings;
+
+public class SavingsTransactionBooleanValues {
+
+    private final boolean isAccountTransfer;
+    private final boolean isRegularTransaction;
+    private final boolean isApplyWithdrawFee;
+    private final boolean isInterestTransfer;
+    private final boolean isExceptionForBalanceCheck;
+
+    public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction,
+            final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck) {
+
+        this.isAccountTransfer = isAccountTransfer;
+        this.isRegularTransaction = isRegularTransaction;
+        this.isApplyWithdrawFee = isApplyWithdrawFee;
+        this.isInterestTransfer = isInterestTransfer;
+        this.isExceptionForBalanceCheck = isExceptionForBalanceCheck;
+    }
+
+    public boolean isAccountTransfer() {
+        return this.isAccountTransfer;
+    }
+
+    public boolean isRegularTransaction() {
+        return this.isRegularTransaction;
+    }
+
+    public boolean isApplyWithdrawFee() {
+        return this.isApplyWithdrawFee;
+    }
+
+    public boolean isInterestTransfer() {
+        return this.isInterestTransfer;
+    }
+
+    public boolean isExceptionForBalanceCheck() {
+        return this.isExceptionForBalanceCheck;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsWithdrawalFeesType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsWithdrawalFeesType.java
new file mode 100644
index 0000000..ab97aad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsWithdrawalFeesType.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.savings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public enum SavingsWithdrawalFeesType {
+
+    INVALID(0, "savingsWithdrawalFeesType.invalid"), //
+    FLAT(1, "savingsWithdrawalFeesType.flat"), //
+    PERCENT_OF_AMOUNT(2, "savingsWithdrawalFeesType.percent.of.amount");
+
+    private final Integer value;
+    private final String code;
+
+    private SavingsWithdrawalFeesType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static Object[] integerValues() {
+        final List<Integer> values = new ArrayList<>();
+        for (final SavingsWithdrawalFeesType enumType : values()) {
+            if (enumType.getValue() > 0) {
+                values.add(enumType.getValue());
+            }
+        }
+
+        return values.toArray();
+    }
+
+    public static SavingsWithdrawalFeesType fromInt(final Integer type) {
+
+        SavingsWithdrawalFeesType withdrawalFeeType = SavingsWithdrawalFeesType.INVALID;
+        switch (type) {
+            case 1:
+                withdrawalFeeType = FLAT;
+            break;
+            case 2:
+                withdrawalFeeType = PERCENT_OF_AMOUNT;
+            break;
+        }
+        return withdrawalFeeType;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java
new file mode 100755
index 0000000..8f7ae4f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java
@@ -0,0 +1,85 @@
+/**
+ * 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.savings.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.DepositAccountOnHoldTransactionData;
+import org.apache.fineract.portfolio.savings.service.DepositAccountOnHoldTransactionReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/savingsaccounts/{savingsId}/onholdtransactions")
+@Component
+@Scope("singleton")
+public class DepositAccountOnHoldFundTransactionsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<DepositAccountOnHoldTransactionData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final DepositAccountOnHoldTransactionReadPlatformService depositAccountOnHoldTransactionReadPlatformService;
+
+    @Autowired
+    public DepositAccountOnHoldFundTransactionsApiResource(final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<DepositAccountOnHoldTransactionData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final DepositAccountOnHoldTransactionReadPlatformService depositAccountOnHoldTransactionReadPlatformService) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.depositAccountOnHoldTransactionReadPlatformService = depositAccountOnHoldTransactionReadPlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@PathParam("savingsId") final Long savingsId,
+            @QueryParam("guarantorFundingId") final Long guarantorFundingId, @Context final UriInfo uriInfo,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder);
+
+        final Page<DepositAccountOnHoldTransactionData> transfers = this.depositAccountOnHoldTransactionReadPlatformService.retriveAll(
+                savingsId, guarantorFundingId, searchParameters);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer
+                .serialize(settings, transfers, SavingsApiConstants.SAVINGS_ACCOUNT_ON_HOLD_RESPONSE_DATA_PARAMETERS);
+    }
+
+}
\ 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
new file mode 100644
index 0000000..354ffae
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java
@@ -0,0 +1,184 @@
+/**
+ * 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.savings.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/fixeddepositaccounts/{fixedDepositAccountId}/transactions")
+@Component
+@Scope("singleton")
+public class FixedDepositAccountTransactionsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<SavingsAccountTransactionData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public FixedDepositAccountTransactionsApiResource(final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<SavingsAccountTransactionData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final SavingsAccountReadPlatformService savingsAccountReadPlatformService,
+            PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@PathParam("fixedDepositAccountId") final Long fixedDepositAccountId,
+    // @QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+
+        SavingsAccountTransactionData savingsAccount = this.savingsAccountReadPlatformService.retrieveDepositTransactionTemplate(
+                fixedDepositAccountId, DepositAccountType.FIXED_DEPOSIT);
+        final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+
+        savingsAccount = SavingsAccountTransactionData.templateOnTop(savingsAccount, paymentTypeOptions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, savingsAccount,
+                SavingsApiConstants.SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("fixedDepositAccountId") final Long fixedDepositAccountId,
+            @PathParam("transactionId") final Long transactionId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        SavingsAccountTransactionData transactionData = this.savingsAccountReadPlatformService.retrieveSavingsTransaction(
+                fixedDepositAccountId, transactionId, DepositAccountType.FIXED_DEPOSIT);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+            transactionData = SavingsAccountTransactionData.templateOnTop(transactionData, paymentTypeOptions);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, transactionData,
+                DepositsApiConstants.FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String transaction(@PathParam("fixedDepositAccountId") final Long fixedDepositAccountId,
+            @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+        CommandProcessingResult result = null;
+
+        if (is(commandParam, "deposit")) {
+            final CommandWrapper commandRequest = builder.fixedDepositAccountDeposit(fixedDepositAccountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "withdrawal")) {
+            final CommandWrapper commandRequest = builder.fixedDepositAccountWithdrawal(fixedDepositAccountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (result == null) {
+            //
+            throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "deposit", "withdrawal" });
+        }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String adjustTransaction(@PathParam("fixedDepositAccountId") final Long fixedDepositAccountId,
+            @PathParam("transactionId") final Long transactionId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        String jsonApiRequest = apiRequestBodyAsJson;
+        if (StringUtils.isBlank(jsonApiRequest)) {
+            jsonApiRequest = "{}";
+        }
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, DepositsApiConstants.COMMAND_UNDO_TRANSACTION)) {
+            final CommandWrapper commandRequest = builder.undoSavingsAccountTransaction(fixedDepositAccountId, transactionId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, DepositsApiConstants.COMMAND_ADJUST_TRANSACTION)) {
+            final CommandWrapper commandRequest = builder.adjustSavingsAccountTransaction(fixedDepositAccountId, transactionId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (result == null) {
+            //
+            throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "undo", "modify" });
+        }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java
new file mode 100644
index 0000000..ae8f87d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java
@@ -0,0 +1,347 @@
+/**
+ * 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.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.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+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.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.DepositAccountData;
+import org.apache.fineract.portfolio.savings.data.FixedDepositAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService;
+import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import com.google.gson.JsonElement;
+
+@Path("/fixeddepositaccounts")
+@Component
+@Scope("singleton")
+public class FixedDepositAccountsApiResource {
+
+    private final DepositAccountReadPlatformService depositAccountReadPlatformService;
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<DepositAccountData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
+    private final FromJsonHelper fromJsonHelper;
+    private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService;
+    private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
+
+    @Autowired
+    public FixedDepositAccountsApiResource(final DepositAccountReadPlatformService depositAccountReadPlatformService,
+            final PlatformSecurityContext context, final DefaultToApiJsonSerializer<DepositAccountData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService, final FromJsonHelper fromJsonHelper,
+            final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService,
+            final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService) {
+        this.depositAccountReadPlatformService = depositAccountReadPlatformService;
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.savingsAccountChargeReadPlatformService = savingsAccountChargeReadPlatformService;
+        this.fromJsonHelper = fromJsonHelper;
+        this.accountPreMatureCalculationPlatformService = accountPreMatureCalculationPlatformService;
+        this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId,
+            @QueryParam("productId") final Long productId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+
+        final DepositAccountData account = this.depositAccountReadPlatformService.retrieveTemplate(DepositAccountType.FIXED_DEPOSIT,
+                clientId, groupId, productId, staffInSelectedOfficeOnly);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, account, DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("paged") final Boolean paged,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        final PaginationParameters paginationParameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (paginationParameters.isPaged()) {
+            final Page<DepositAccountData> account = this.depositAccountReadPlatformService.retrieveAllPaged(
+                    DepositAccountType.FIXED_DEPOSIT, paginationParameters);
+            return this.toApiJsonSerializer.serialize(settings, account,
+                    DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+        }
+
+        final Collection<DepositAccountData> account = this.depositAccountReadPlatformService.retrieveAll(DepositAccountType.FIXED_DEPOSIT,
+                paginationParameters);
+
+        return this.toApiJsonSerializer.serialize(settings, account, DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String submitApplication(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createFixedDepositAccount().withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("accountId") final Long accountId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
+            @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+
+        if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException(
+                "status", chargeStatus, new Object[] { "all", "active", "inactive" }); }
+
+        final FixedDepositAccountData account = (FixedDepositAccountData) this.depositAccountReadPlatformService.retrieveOneWithChartSlabs(
+                DepositAccountType.FIXED_DEPOSIT, accountId);
+
+        final Set<String> mandatoryResponseParameters = new HashSet<>();
+        final FixedDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly,
+                chargeStatus, uriInfo, mandatoryResponseParameters);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
+                mandatoryResponseParameters);
+        return this.toApiJsonSerializer.serialize(settings, accountTemplate,
+                DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    private FixedDepositAccountData populateTemplateAndAssociations(final Long accountId, final FixedDepositAccountData savingsAccount,
+            final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo,
+            final Set<String> mandatoryResponseParameters) {
+
+        Collection<SavingsAccountTransactionData> transactions = null;
+        Collection<SavingsAccountChargeData> charges = null;
+        PortfolioAccountData linkedAccount = null;
+
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        if (!associationParameters.isEmpty()) {
+
+            if (associationParameters.contains("all")) {
+                associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges,
+                        SavingsApiConstants.linkedAccount));
+            }
+
+            if (associationParameters.contains(SavingsApiConstants.transactions)) {
+                mandatoryResponseParameters.add(SavingsApiConstants.transactions);
+                final Collection<SavingsAccountTransactionData> currentTransactions = this.depositAccountReadPlatformService
+                        .retrieveAllTransactions(DepositAccountType.FIXED_DEPOSIT, accountId);
+                if (!CollectionUtils.isEmpty(currentTransactions)) {
+                    transactions = currentTransactions;
+                }
+            }
+
+            if (associationParameters.contains(SavingsApiConstants.charges)) {
+                mandatoryResponseParameters.add(SavingsApiConstants.charges);
+                final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
+                        .retrieveSavingsAccountCharges(accountId, chargeStatus);
+                if (!CollectionUtils.isEmpty(currentCharges)) {
+                    charges = currentCharges;
+                }
+            }
+
+            if (associationParameters.contains(SavingsApiConstants.linkedAccount)) {
+                mandatoryResponseParameters.add(SavingsApiConstants.linkedAccount);
+                linkedAccount = this.accountAssociationsReadPlatformService.retriveSavingsLinkedAssociation(accountId);
+            }
+        }
+
+        FixedDepositAccountData templateData = null;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (settings.isTemplate()) {
+            templateData = (FixedDepositAccountData) this.depositAccountReadPlatformService.retrieveTemplate(
+                    DepositAccountType.FIXED_DEPOSIT, savingsAccount.clientId(), savingsAccount.groupId(), savingsAccount.productId(),
+                    staffInSelectedOfficeOnly);
+        }
+
+        return FixedDepositAccountData.associationsAndTemplate(savingsAccount, templateData, transactions, charges, linkedAccount);
+    }
+
+    @PUT
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("accountId") final Long accountId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateFixedDepositAccount(accountId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String handleCommands(@PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
+
+        String jsonApiRequest = apiRequestBodyAsJson;
+        if (StringUtils.isBlank(jsonApiRequest)) {
+            jsonApiRequest = "{}";
+        }
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "reject")) {
+            final CommandWrapper commandRequest = builder.rejectFixedDepositAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "withdrawnByApplicant")) {
+            final CommandWrapper commandRequest = builder.withdrawFixedDepositAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "approve")) {
+            final CommandWrapper commandRequest = builder.approveFixedDepositAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "undoapproval")) {
+            final CommandWrapper commandRequest = builder.undoFixedDepositAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "activate")) {
+            final CommandWrapper commandRequest = builder.fixedDepositAccountActivation(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "calculateInterest")) {
+            final CommandWrapper commandRequest = builder.withNoJsonBody().fixedDepositAccountInterestCalculation(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "postInterest")) {
+            final CommandWrapper commandRequest = builder.fixedDepositAccountInterestPosting(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "close")) {
+            final CommandWrapper commandRequest = builder.closeFixedDepositAccount(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "prematureClose")) {
+            final CommandWrapper commandRequest = builder.prematureCloseFixedDepositAccount(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "calculatePrematureAmount")) {
+            final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
+            final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
+            final DepositAccountData account = this.accountPreMatureCalculationPlatformService.calculatePreMatureAmount(accountId, query,
+                    DepositAccountType.FIXED_DEPOSIT);
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+            return this.toApiJsonSerializer.serialize(settings, account,
+                    DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+        }
+
+        if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "reject",
+                "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", "postInterest", "close",
+                "prematureClose", "calculatePrematureAmount" }); }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @DELETE
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("accountId") final Long accountId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteFixedDepositAccount(accountId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{accountId}/template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String accountClosureTemplate(@PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        DepositAccountData account = null;
+        if (is(commandParam, "close")) {
+            account = this.depositAccountReadPlatformService.retrieveOneWithClosureTemplate(DepositAccountType.FIXED_DEPOSIT, accountId);
+        }
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, account, DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java
new file mode 100644
index 0000000..d6aa668
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java
@@ -0,0 +1,325 @@
+/**
+ * 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.savings.api;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.FixedDepositProductData;
+import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.DepositsDropdownReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsDropdownReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+@Path("/fixeddepositproducts")
+@Component
+@Scope("singleton")
+public class FixedDepositProductsApiResource {
+
+    private final DepositProductReadPlatformService depositProductReadPlatformService;
+    private final SavingsDropdownReadPlatformService savingsDropdownReadPlatformService;
+    private final CurrencyReadPlatformService currencyReadPlatformService;
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<FixedDepositProductData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
+    private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final InterestRateChartReadPlatformService chartReadPlatformService;
+    private final InterestRateChartReadPlatformService interestRateChartReadPlatformService;
+    private final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService;
+    private final DropdownReadPlatformService dropdownReadPlatformService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public FixedDepositProductsApiResource(final DepositProductReadPlatformService depositProductReadPlatformService,
+            final SavingsDropdownReadPlatformService savingsDropdownReadPlatformService,
+            final CurrencyReadPlatformService currencyReadPlatformService, final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<FixedDepositProductData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService,
+            final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService,
+            final ChargeReadPlatformService chargeReadPlatformService, final InterestRateChartReadPlatformService chartReadPlatformService,
+            final InterestRateChartReadPlatformService interestRateChartReadPlatformService,
+            final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService,
+            final DropdownReadPlatformService dropdownReadPlatformService,
+            final PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.depositProductReadPlatformService = depositProductReadPlatformService;
+        this.savingsDropdownReadPlatformService = savingsDropdownReadPlatformService;
+        this.currencyReadPlatformService = currencyReadPlatformService;
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
+        this.accountMappingReadPlatformService = accountMappingReadPlatformService;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.chartReadPlatformService = chartReadPlatformService;
+        this.interestRateChartReadPlatformService = interestRateChartReadPlatformService;
+        this.depositsDropdownReadPlatformService = depositsDropdownReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createFixedDepositProduct().withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("productId") final Long productId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateFixedDepositProduct(productId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        final Collection<FixedDepositProductData> products = (Collection) this.depositProductReadPlatformService
+                .retrieveAll(DepositAccountType.FIXED_DEPOSIT);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, products, DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("productId") final Long productId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        FixedDepositProductData fixedDepositProductData = (FixedDepositProductData) this.depositProductReadPlatformService.retrieveOne(
+                DepositAccountType.FIXED_DEPOSIT, productId);
+
+        final Collection<ChargeData> charges = this.chargeReadPlatformService.retrieveSavingsProductCharges(productId);
+        fixedDepositProductData = FixedDepositProductData.withCharges(fixedDepositProductData, charges);
+
+        final Collection<InterestRateChartData> charts = this.chartReadPlatformService.retrieveAllWithSlabsWithTemplate(productId);
+        fixedDepositProductData = FixedDepositProductData.withInterestChart(fixedDepositProductData, charts);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (fixedDepositProductData.hasAccountingEnabled()) {
+            final Map<String, Object> accountingMappings = this.accountMappingReadPlatformService
+                    .fetchAccountMappingDetailsForSavingsProduct(productId, fixedDepositProductData.accountingRuleTypeId());
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = this.accountMappingReadPlatformService
+                    .fetchPaymentTypeToFundSourceMappingsForSavingsProduct(productId);
+            Collection<ChargeToGLAccountMapper> feeToGLAccountMappings = this.accountMappingReadPlatformService
+                    .fetchFeeToIncomeAccountMappingsForSavingsProduct(productId);
+            Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings = this.accountMappingReadPlatformService
+                    .fetchPenaltyToIncomeAccountMappingsForSavingsProduct(productId);
+            fixedDepositProductData = FixedDepositProductData.withAccountingDetails(fixedDepositProductData, accountingMappings,
+                    paymentChannelToFundSourceMappings, feeToGLAccountMappings, penaltyToGLAccountMappings);
+        }
+
+        if (settings.isTemplate()) {
+            fixedDepositProductData = handleTemplateRelatedData(fixedDepositProductData);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, fixedDepositProductData,
+                DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        final FixedDepositProductData fixedDepositProduct = handleTemplateRelatedData(null);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, fixedDepositProduct,
+                DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    private FixedDepositProductData handleTemplateRelatedData(final FixedDepositProductData savingsProduct) {
+
+        final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations
+                .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.DAILY);
+
+        final EnumOptionData interestPostingPeriodType = SavingsEnumerations
+                .interestPostingPeriodType(SavingsPostingInterestPeriodType.MONTHLY);
+
+        final EnumOptionData interestCalculationType = SavingsEnumerations
+                .interestCalculationType(SavingsInterestCalculationType.DAILY_BALANCE);
+
+        final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations
+                .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_365);
+
+        final EnumOptionData accountingRule = AccountingEnumerations.accountingRuleType(AccountingRuleType.NONE);
+
+        CurrencyData currency = CurrencyData.blank();
+        final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
+        if (currencyOptions.size() == 1) {
+            currency = new ArrayList<>(currencyOptions).get(0);
+        }
+
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveCompoundingInterestPeriodTypeOptions();
+
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveInterestPostingPeriodTypeOptions();
+
+        final Collection<EnumOptionData> interestCalculationTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveInterestCalculationTypeOptions();
+
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveInterestCalculationDaysInYearTypeOptions();
+
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveLockinPeriodFrequencyTypeOptions();
+
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = this.savingsDropdownReadPlatformService
+                .retrievewithdrawalFeeTypeOptions();
+
+        final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+
+        final Collection<EnumOptionData> accountingRuleOptions = this.accountingDropdownReadPlatformService
+                .retrieveAccountingRuleTypeOptions();
+
+        final Map<String, List<GLAccountData>> accountingMappingOptions = this.accountingDropdownReadPlatformService
+                .retrieveAccountMappingOptionsForSavingsProducts();
+
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = this.depositsDropdownReadPlatformService
+                .retrievePreClosurePenalInterestOnTypeOptions();
+
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
+
+        // charges
+        final boolean feeChargesOnly = true;
+        Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly);
+        chargeOptions = CollectionUtils.isEmpty(chargeOptions) ? null : chargeOptions;
+
+        Collection<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties();
+        penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions;
+
+        // interest rate chart template
+        final InterestRateChartData chartTemplate = this.interestRateChartReadPlatformService.template();
+
+        FixedDepositProductData fixedDepositProductToReturn = null;
+        if (savingsProduct != null) {
+            fixedDepositProductToReturn = FixedDepositProductData.withTemplate(savingsProduct, currencyOptions,
+                    interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                    interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
+                    paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
+                    preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions);
+        } else {
+            fixedDepositProductToReturn = FixedDepositProductData.template(currency, interestCompoundingPeriodType,
+                    interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions,
+                    interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                    interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
+                    paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
+                    preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions);
+        }
+
+        return fixedDepositProductToReturn;
+    }
+
+    @DELETE
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("productId") final Long productId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteFixedDepositProduct(productId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..13ba320
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java
@@ -0,0 +1,206 @@
+/**
+ * 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.savings.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/recurringdepositaccounts/{recurringDepositAccountId}/transactions")
+@Component
+@Scope("singleton")
+public class RecurringDepositAccountTransactionsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<SavingsAccountTransactionData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
+    private final DepositAccountReadPlatformService depositAccountReadPlatformService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public RecurringDepositAccountTransactionsApiResource(final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<SavingsAccountTransactionData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final SavingsAccountReadPlatformService savingsAccountReadPlatformService,
+            final DepositAccountReadPlatformService depositAccountReadPlatformService,
+            final PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
+        this.depositAccountReadPlatformService = depositAccountReadPlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@PathParam("recurringDepositAccountId") final Long recurringDepositAccountId,
+            @QueryParam("command") final String commandParam, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+
+        /***
+         * Check @Param commandParam value for deposit or withdrawal
+         */
+        if (!(is(commandParam, "deposit") || is(commandParam, "withdrawal"))) { throw new UnrecognizedQueryParamException("command",
+                commandParam, new Object[] { "deposit", "withdrawal" }); }
+
+        /***
+         * By default get the deposit template for deposits and withdrawal
+         * transactions
+         */
+        SavingsAccountTransactionData savingsAccount = this.depositAccountReadPlatformService
+                .retrieveRecurringAccountDepositTransactionTemplate(recurringDepositAccountId);
+
+        /***
+         * Update transaction date and transaction type if transaction type is
+         * withdrawal
+         */
+        if (is(commandParam, "withdrawal")) {
+            savingsAccount = SavingsAccountTransactionData.withWithDrawalTransactionDetails(savingsAccount);
+        }
+
+        final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+        savingsAccount = SavingsAccountTransactionData.templateOnTop(savingsAccount, paymentTypeOptions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, savingsAccount,
+                SavingsApiConstants.SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("recurringDepositAccountId") final Long recurringDepositAccountId,
+            @PathParam("transactionId") final Long transactionId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        SavingsAccountTransactionData transactionData = this.savingsAccountReadPlatformService.retrieveSavingsTransaction(
+                recurringDepositAccountId, transactionId, DepositAccountType.RECURRING_DEPOSIT);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+            transactionData = SavingsAccountTransactionData.templateOnTop(transactionData, paymentTypeOptions);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, transactionData,
+                DepositsApiConstants.FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String transaction(@PathParam("recurringDepositAccountId") final Long recurringDepositAccountId,
+            @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+        CommandProcessingResult result = null;
+
+        if (is(commandParam, "deposit")) {
+            final CommandWrapper commandRequest = builder.recurringAccountDeposit(recurringDepositAccountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "withdrawal")) {
+            final CommandWrapper commandRequest = builder.recurringAccountWithdrawal(recurringDepositAccountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (result == null) {
+            //
+            throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "deposit", "withdrawal" });
+        }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String handleTransactionCommands(@PathParam("recurringDepositAccountId") final Long recurringDepositAccountId,
+            @PathParam("transactionId") final Long transactionId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        String jsonApiRequest = apiRequestBodyAsJson;
+        if (StringUtils.isBlank(jsonApiRequest)) {
+            jsonApiRequest = "{}";
+        }
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, DepositsApiConstants.COMMAND_UNDO_TRANSACTION)) {
+            final CommandWrapper commandRequest = builder.undoRecurringAccountTransaction(recurringDepositAccountId, transactionId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, DepositsApiConstants.COMMAND_ADJUST_TRANSACTION)) {
+            final CommandWrapper commandRequest = builder.adjustRecurringAccountTransaction(recurringDepositAccountId, transactionId)
+                    .build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (result == null) {
+            //
+            throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "undo", "modify" });
+        }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java
new file mode 100644
index 0000000..bedfadc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java
@@ -0,0 +1,344 @@
+/**
+ * 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.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.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+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.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.DepositAccountData;
+import org.apache.fineract.portfolio.savings.data.RecurringDepositAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService;
+import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import com.google.gson.JsonElement;
+
+@Path("/recurringdepositaccounts")
+@Component
+@Scope("singleton")
+public class RecurringDepositAccountsApiResource {
+
+    private final DepositAccountReadPlatformService depositAccountReadPlatformService;
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<DepositAccountData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
+    private final FromJsonHelper fromJsonHelper;
+    private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService;
+
+    @Autowired
+    public RecurringDepositAccountsApiResource(final DepositAccountReadPlatformService depositAccountReadPlatformService,
+            final PlatformSecurityContext context, final DefaultToApiJsonSerializer<DepositAccountData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService, final FromJsonHelper fromJsonHelper,
+            final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService) {
+        this.depositAccountReadPlatformService = depositAccountReadPlatformService;
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.savingsAccountChargeReadPlatformService = savingsAccountChargeReadPlatformService;
+        this.fromJsonHelper = fromJsonHelper;
+        this.accountPreMatureCalculationPlatformService = accountPreMatureCalculationPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId,
+            @QueryParam("productId") final Long productId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+
+        final DepositAccountData account = this.depositAccountReadPlatformService.retrieveTemplate(DepositAccountType.RECURRING_DEPOSIT,
+                clientId, groupId, productId, staffInSelectedOfficeOnly);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, account,
+                DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("paged") final Boolean paged,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        final PaginationParameters paginationParameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (paginationParameters.isPaged()) {
+            final Page<DepositAccountData> account = this.depositAccountReadPlatformService.retrieveAllPaged(
+                    DepositAccountType.RECURRING_DEPOSIT, paginationParameters);
+            return this.toApiJsonSerializer.serialize(settings, account,
+                    DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+        }
+
+        final Collection<DepositAccountData> account = this.depositAccountReadPlatformService.retrieveAll(
+                DepositAccountType.RECURRING_DEPOSIT, paginationParameters);
+
+        return this.toApiJsonSerializer.serialize(settings, account,
+                DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String submitApplication(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createRecurringDepositAccount().withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("accountId") final Long accountId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
+            @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+
+        if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException(
+                "status", chargeStatus, new Object[] { "all", "active", "inactive" }); }
+
+        final RecurringDepositAccountData account = (RecurringDepositAccountData) this.depositAccountReadPlatformService
+                .retrieveOneWithChartSlabs(DepositAccountType.RECURRING_DEPOSIT, accountId);
+
+        final Set<String> mandatoryResponseParameters = new HashSet<>();
+        final RecurringDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly,
+                chargeStatus, uriInfo, mandatoryResponseParameters);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
+                mandatoryResponseParameters);
+        return this.toApiJsonSerializer.serialize(settings, accountTemplate,
+                DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    private RecurringDepositAccountData populateTemplateAndAssociations(final Long accountId,
+            final RecurringDepositAccountData savingsAccount, final boolean staffInSelectedOfficeOnly, final String chargeStatus,
+            final UriInfo uriInfo, final Set<String> mandatoryResponseParameters) {
+
+        Collection<SavingsAccountTransactionData> transactions = null;
+        Collection<SavingsAccountChargeData> charges = null;
+
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        if (!associationParameters.isEmpty()) {
+
+            if (associationParameters.contains("all")) {
+                associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges));
+            }
+
+            if (associationParameters.contains(SavingsApiConstants.transactions)) {
+                mandatoryResponseParameters.add(SavingsApiConstants.transactions);
+                final Collection<SavingsAccountTransactionData> currentTransactions = this.depositAccountReadPlatformService
+                        .retrieveAllTransactions(DepositAccountType.RECURRING_DEPOSIT, accountId);
+                if (!CollectionUtils.isEmpty(currentTransactions)) {
+                    transactions = currentTransactions;
+                }
+            }
+
+            if (associationParameters.contains(SavingsApiConstants.charges)) {
+                mandatoryResponseParameters.add(SavingsApiConstants.charges);
+                final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
+                        .retrieveSavingsAccountCharges(accountId, chargeStatus);
+                if (!CollectionUtils.isEmpty(currentCharges)) {
+                    charges = currentCharges;
+                }
+            }
+        }
+
+        RecurringDepositAccountData templateData = null;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (settings.isTemplate()) {
+            templateData = (RecurringDepositAccountData) this.depositAccountReadPlatformService.retrieveTemplate(
+                    DepositAccountType.RECURRING_DEPOSIT, savingsAccount.clientId(), savingsAccount.groupId(), savingsAccount.productId(),
+                    staffInSelectedOfficeOnly);
+        }
+
+        return RecurringDepositAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges);
+    }
+
+    @PUT
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("accountId") final Long accountId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRecurringDepositAccount(accountId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String handleCommands(@PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
+
+        String jsonApiRequest = apiRequestBodyAsJson;
+        if (StringUtils.isBlank(jsonApiRequest)) {
+            jsonApiRequest = "{}";
+        }
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "reject")) {
+            final CommandWrapper commandRequest = builder.rejectRecurringDepositAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "withdrawnByApplicant")) {
+            final CommandWrapper commandRequest = builder.withdrawRecurringDepositAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "approve")) {
+            final CommandWrapper commandRequest = builder.approveRecurringDepositAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "undoapproval")) {
+            final CommandWrapper commandRequest = builder.undoRecurringDepositAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "activate")) {
+            final CommandWrapper commandRequest = builder.recurringDepositAccountActivation(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "calculateInterest")) {
+            final CommandWrapper commandRequest = builder.withNoJsonBody().recurringDepositAccountInterestCalculation(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, DepositsApiConstants.UPDATE_DEPOSIT_AMOUNT)) {
+            final CommandWrapper commandRequest = builder.updateDepositAmountForRecurringDepositAccount(accountId)
+                    .withJson(apiRequestBodyAsJson).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "postInterest")) {
+            final CommandWrapper commandRequest = builder.recurringDepositAccountInterestPosting(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "close")) {
+            final CommandWrapper commandRequest = builder.closeRecurringDepositAccount(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "prematureClose")) {
+            final CommandWrapper commandRequest = builder.prematureCloseRecurringDepositAccount(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "calculatePrematureAmount")) {
+            final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
+            final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
+            final DepositAccountData account = this.accountPreMatureCalculationPlatformService.calculatePreMatureAmount(accountId, query,
+                    DepositAccountType.RECURRING_DEPOSIT);
+            final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+            return this.toApiJsonSerializer.serialize(settings, account,
+                    DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+        }
+
+        if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "reject",
+                "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", "postInterest", "close",
+                "prematureClose", "calculatePrematureAmount" }); }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @DELETE
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("accountId") final Long accountId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteRecurringDepositAccount(accountId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{accountId}/template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String accountClosureTemplate(@PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        DepositAccountData account = null;
+        if (is(commandParam, "close")) {
+            account = this.depositAccountReadPlatformService
+                    .retrieveOneWithClosureTemplate(DepositAccountType.RECURRING_DEPOSIT, accountId);
+
+        }
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, account,
+                DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java
new file mode 100644
index 0000000..1fecc1b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java
@@ -0,0 +1,323 @@
+/**
+ * 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.savings.api;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.RecurringDepositProductData;
+import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.DepositsDropdownReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsDropdownReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+@Path("/recurringdepositproducts")
+@Component
+@Scope("singleton")
+public class RecurringDepositProductsApiResource {
+
+    private final DepositProductReadPlatformService depositProductReadPlatformService;
+    private final SavingsDropdownReadPlatformService savingsDropdownReadPlatformService;
+    private final CurrencyReadPlatformService currencyReadPlatformService;
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<RecurringDepositProductData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
+    private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final InterestRateChartReadPlatformService chartReadPlatformService;
+    private final InterestRateChartReadPlatformService interestRateChartReadPlatformService;
+    private final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService;
+    private final DropdownReadPlatformService dropdownReadPlatformService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public RecurringDepositProductsApiResource(final DepositProductReadPlatformService depositProductReadPlatformService,
+            final SavingsDropdownReadPlatformService savingsDropdownReadPlatformService,
+            final CurrencyReadPlatformService currencyReadPlatformService, final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<RecurringDepositProductData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService,
+            final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService,
+            final ChargeReadPlatformService chargeReadPlatformService, final InterestRateChartReadPlatformService chartReadPlatformService,
+            final InterestRateChartReadPlatformService interestRateChartReadPlatformService,
+            final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService,
+            final DropdownReadPlatformService dropdownReadPlatformService,
+            final PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.depositProductReadPlatformService = depositProductReadPlatformService;
+        this.savingsDropdownReadPlatformService = savingsDropdownReadPlatformService;
+        this.currencyReadPlatformService = currencyReadPlatformService;
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
+        this.accountMappingReadPlatformService = accountMappingReadPlatformService;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.chartReadPlatformService = chartReadPlatformService;
+        this.interestRateChartReadPlatformService = interestRateChartReadPlatformService;
+        this.depositsDropdownReadPlatformService = depositsDropdownReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createRecurringDepositProduct().withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("productId") final Long productId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRecurringDepositProduct(productId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        final Collection<RecurringDepositProductData> products = (Collection) this.depositProductReadPlatformService
+                .retrieveAll(DepositAccountType.RECURRING_DEPOSIT);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, products,
+                DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("productId") final Long productId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        RecurringDepositProductData recurringDepositProductData = (RecurringDepositProductData) this.depositProductReadPlatformService
+                .retrieveOne(DepositAccountType.RECURRING_DEPOSIT, productId);
+
+        final Collection<ChargeData> charges = this.chargeReadPlatformService.retrieveSavingsProductCharges(productId);
+        recurringDepositProductData = RecurringDepositProductData.withCharges(recurringDepositProductData, charges);
+
+        final Collection<InterestRateChartData> charts = this.chartReadPlatformService.retrieveAllWithSlabsWithTemplate(productId);
+        recurringDepositProductData = RecurringDepositProductData.withInterestChart(recurringDepositProductData, charts);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (recurringDepositProductData.hasAccountingEnabled()) {
+            final Map<String, Object> accountingMappings = this.accountMappingReadPlatformService
+                    .fetchAccountMappingDetailsForSavingsProduct(productId, recurringDepositProductData.accountingRuleTypeId());
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = this.accountMappingReadPlatformService
+                    .fetchPaymentTypeToFundSourceMappingsForSavingsProduct(productId);
+            Collection<ChargeToGLAccountMapper> feeToGLAccountMappings = this.accountMappingReadPlatformService
+                    .fetchFeeToIncomeAccountMappingsForSavingsProduct(productId);
+            Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings = this.accountMappingReadPlatformService
+                    .fetchPenaltyToIncomeAccountMappingsForSavingsProduct(productId);
+            recurringDepositProductData = RecurringDepositProductData.withAccountingDetails(recurringDepositProductData,
+                    accountingMappings, paymentChannelToFundSourceMappings, feeToGLAccountMappings, penaltyToGLAccountMappings);
+        }
+
+        if (settings.isTemplate()) {
+            recurringDepositProductData = handleTemplateRelatedData(recurringDepositProductData);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, recurringDepositProductData,
+                DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        final RecurringDepositProductData recurringDepositProduct = handleTemplateRelatedData(null);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, recurringDepositProduct,
+                DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    private RecurringDepositProductData handleTemplateRelatedData(final RecurringDepositProductData savingsProduct) {
+
+        final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations
+                .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.DAILY);
+
+        final EnumOptionData interestPostingPeriodType = SavingsEnumerations
+                .interestPostingPeriodType(SavingsPostingInterestPeriodType.MONTHLY);
+
+        final EnumOptionData interestCalculationType = SavingsEnumerations
+                .interestCalculationType(SavingsInterestCalculationType.DAILY_BALANCE);
+
+        final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations
+                .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_365);
+
+        final EnumOptionData accountingRule = AccountingEnumerations.accountingRuleType(AccountingRuleType.NONE);
+
+        CurrencyData currency = CurrencyData.blank();
+        final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
+        if (currencyOptions.size() == 1) {
+            currency = new ArrayList<>(currencyOptions).get(0);
+        }
+
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveCompoundingInterestPeriodTypeOptions();
+
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveInterestPostingPeriodTypeOptions();
+
+        final Collection<EnumOptionData> interestCalculationTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveInterestCalculationTypeOptions();
+
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveInterestCalculationDaysInYearTypeOptions();
+
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = this.savingsDropdownReadPlatformService
+                .retrieveLockinPeriodFrequencyTypeOptions();
+
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = this.savingsDropdownReadPlatformService
+                .retrievewithdrawalFeeTypeOptions();
+
+        final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+        final Collection<EnumOptionData> accountingRuleOptions = this.accountingDropdownReadPlatformService
+                .retrieveAccountingRuleTypeOptions();
+        final Map<String, List<GLAccountData>> accountingMappingOptions = this.accountingDropdownReadPlatformService
+                .retrieveAccountMappingOptionsForSavingsProducts();
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = this.depositsDropdownReadPlatformService
+                .retrievePreClosurePenalInterestOnTypeOptions();
+
+        // charges
+        final boolean feeChargesOnly = true;
+        Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly);
+        chargeOptions = CollectionUtils.isEmpty(chargeOptions) ? null : chargeOptions;
+
+        Collection<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties();
+        penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions;
+
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
+
+        // interest rate chart template
+        final InterestRateChartData chartTemplate = this.interestRateChartReadPlatformService.template();
+
+        RecurringDepositProductData recurringDepositProductToReturn = null;
+        if (savingsProduct != null) {
+            recurringDepositProductToReturn = RecurringDepositProductData.withTemplate(savingsProduct, currencyOptions,
+                    interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                    interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
+                    paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
+                    preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions);
+        } else {
+            recurringDepositProductToReturn = RecurringDepositProductData.template(currency, interestCompoundingPeriodType,
+                    interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions,
+                    interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                    interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
+                    paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
+                    preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions);
+        }
+
+        return recurringDepositProductToReturn;
+    }
+
+    @DELETE
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("productId") final Long productId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteRecurringDepositProduct(productId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResource.java
new file mode 100644
index 0000000..ae767ac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResource.java
@@ -0,0 +1,223 @@
+/**
+ * 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.savings.api;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.COMMAND_INACTIVATE_CHARGE;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.COMMAND_PAY_CHARGE;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.COMMAND_WAIVE_CHARGE;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+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.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.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/savingsaccounts/{savingsAccountId}/charges")
+@Component
+@Scope("singleton")
+public class SavingsAccountChargesApiResource {
+
+    private final PlatformSecurityContext context;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
+    private final DefaultToApiJsonSerializer<SavingsAccountChargeData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public SavingsAccountChargesApiResource(final PlatformSecurityContext context,
+            final ChargeReadPlatformService chargeReadPlatformService,
+            final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService,
+            final DefaultToApiJsonSerializer<SavingsAccountChargeData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.savingsAccountChargeReadPlatformService = savingsAccountChargeReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllSavingsAccountCharges(@PathParam("savingsAccountId") final Long savingsAccountId,
+            @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+        if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException(
+                "status", chargeStatus, new Object[] { "all", "active", "inactive" }); }
+
+        final Collection<SavingsAccountChargeData> savingsAccountCharges = this.savingsAccountChargeReadPlatformService
+                .retrieveSavingsAccountCharges(savingsAccountId, chargeStatus);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, savingsAccountCharges,
+                SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@PathParam("savingsAccountId") final Long savingsAccountId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+        final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsAccountApplicableCharges(savingsAccountId);
+        final SavingsAccountChargeData savingsAccountChargeTemplate = SavingsAccountChargeData.template(chargeOptions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, savingsAccountChargeTemplate, SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{savingsAccountChargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
+            @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+        final SavingsAccountChargeData savingsAccountCharge = this.savingsAccountChargeReadPlatformService
+                .retrieveSavingsAccountChargeDetails(savingsAccountChargeId, savingsAccountId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, savingsAccountCharge, SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String addSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createSavingsAccountCharge(savingsAccountId)
+                .withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{savingsAccountChargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
+            @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder()
+                .updateSavingsAccountCharge(savingsAccountId, savingsAccountChargeId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{savingsAccountChargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String payOrWaiveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
+            @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        String json = "";
+        if (is(commandParam, COMMAND_WAIVE_CHARGE)) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder()
+                    .waiveSavingsAccountCharge(savingsAccountId, savingsAccountChargeId).withJson(apiRequestBodyAsJson).build();
+
+            final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+            json = this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, COMMAND_PAY_CHARGE)) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder()
+                    .paySavingsAccountCharge(savingsAccountId, savingsAccountChargeId).withJson(apiRequestBodyAsJson).build();
+
+            final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+            json = this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, COMMAND_INACTIVATE_CHARGE)) {
+            final CommandWrapper commandRequest = new CommandWrapperBuilder()
+                    .inactivateSavingsAccountCharge(savingsAccountId, savingsAccountChargeId).withJson(apiRequestBodyAsJson).build();
+
+            final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+            json = this.toApiJsonSerializer.serialize(result);
+        } else {
+            throw new UnrecognizedQueryParamException("command", commandParam, COMMAND_PAY_CHARGE, COMMAND_WAIVE_CHARGE,
+                    COMMAND_INACTIVATE_CHARGE);
+        }
+
+        return json;
+    }
+
+    @DELETE
+    @Path("{savingsAccountChargeId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
+            @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSavingsAccountCharge(savingsAccountId,
+                savingsAccountChargeId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java
new file mode 100644
index 0000000..2356319
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java
@@ -0,0 +1,192 @@
+/**
+ * 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.savings.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+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.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.dao.CannotAcquireLockException;
+import org.springframework.orm.ObjectOptimisticLockingFailureException;
+import org.springframework.stereotype.Component;
+
+@Path("/savingsaccounts/{savingsId}/transactions")
+@Component
+@Scope("singleton")
+public class SavingsAccountTransactionsApiResource {
+
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<SavingsAccountTransactionData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public SavingsAccountTransactionsApiResource(final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<SavingsAccountTransactionData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final SavingsAccountReadPlatformService savingsAccountReadPlatformService,
+            PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@PathParam("savingsId") final Long savingsId,
+    // @QueryParam("command") final String commandParam,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        // FIXME - KW - for now just send back generic default information for
+        // both deposit/withdrawal templates
+        SavingsAccountTransactionData savingsAccount = this.savingsAccountReadPlatformService.retrieveDepositTransactionTemplate(savingsId,
+                DepositAccountType.SAVINGS_DEPOSIT);
+        final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+        savingsAccount = SavingsAccountTransactionData.templateOnTop(savingsAccount, paymentTypeOptions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, savingsAccount,
+                SavingsApiConstants.SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+        SavingsAccountTransactionData transactionData = this.savingsAccountReadPlatformService.retrieveSavingsTransaction(savingsId,
+                transactionId, DepositAccountType.SAVINGS_DEPOSIT);
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+            transactionData = SavingsAccountTransactionData.templateOnTop(transactionData, paymentTypeOptions);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, transactionData,
+                SavingsApiConstants.SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String transaction(@PathParam("savingsId") final Long savingsId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+        try {
+            final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+            CommandProcessingResult result = null;
+            if (is(commandParam, "deposit")) {
+                final CommandWrapper commandRequest = builder.savingsAccountDeposit(savingsId).build();
+                result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            } else if (is(commandParam, "withdrawal")) {
+                final CommandWrapper commandRequest = builder.savingsAccountWithdrawal(savingsId).build();
+                result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            }
+
+            if (result == null) {
+                //
+                throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "deposit", "withdrawal" });
+            }
+
+            return this.toApiJsonSerializer.serialize(result);
+        } catch (ObjectOptimisticLockingFailureException lockingFailureException) {
+            throw new PlatformDataIntegrityException("error.msg.savings.concurrent.operations",
+                    "Concurrent Transactions being made on this savings account: " + lockingFailureException.getMessage());
+        } catch (CannotAcquireLockException cannotAcquireLockException) {
+            throw new PlatformDataIntegrityException("error.msg.savings.concurrent.operations.unable.to.acquire.lock",
+                    "Unable to acquir lock for this transaction: " + cannotAcquireLockException.getMessage());
+        }
+    }
+
+    @POST
+    @Path("{transactionId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String adjustTransaction(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId,
+            @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) {
+
+        String jsonApiRequest = apiRequestBodyAsJson;
+        if (StringUtils.isBlank(jsonApiRequest)) {
+            jsonApiRequest = "{}";
+        }
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, SavingsApiConstants.COMMAND_UNDO_TRANSACTION)) {
+            final CommandWrapper commandRequest = builder.undoSavingsAccountTransaction(savingsId, transactionId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, SavingsApiConstants.COMMAND_ADJUST_TRANSACTION)) {
+            final CommandWrapper commandRequest = builder.adjustSavingsAccountTransaction(savingsId, transactionId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+
+        if (result == null) {
+            //
+            throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "undo" });
+        }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java
new file mode 100644
index 0000000..74e644c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java
@@ -0,0 +1,300 @@
+/**
+ * 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.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.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+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.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+@Path("/savingsaccounts")
+@Component
+@Scope("singleton")
+public class SavingsAccountsApiResource {
+
+    private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<SavingsAccountData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
+
+    @Autowired
+    public SavingsAccountsApiResource(final SavingsAccountReadPlatformService savingsAccountReadPlatformService,
+            final PlatformSecurityContext context, final DefaultToApiJsonSerializer<SavingsAccountData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService) {
+        this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.savingsAccountChargeReadPlatformService = savingsAccountChargeReadPlatformService;
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId,
+            @QueryParam("productId") final Long productId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
+            @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final SavingsAccountData savingsAccount = this.savingsAccountReadPlatformService.retrieveTemplate(clientId, groupId, productId,
+                staffInSelectedOfficeOnly);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, savingsAccount, SavingsApiConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("externalId") final String externalId,
+            // @QueryParam("underHierarchy") final String hierarchy,
+            @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
+            @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final SearchParameters searchParameters = SearchParameters.forSavings(sqlSearch, externalId, offset, limit, orderBy, sortOrder);
+
+        final Page<SavingsAccountData> products = this.savingsAccountReadPlatformService.retrieveAll(searchParameters);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, products, SavingsApiConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String submitApplication(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createSavingsAccount().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("accountId") final Long accountId,
+            @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
+            @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException(
+                "status", chargeStatus, new Object[] { "all", "active", "inactive" }); }
+
+        final SavingsAccountData savingsAccount = this.savingsAccountReadPlatformService.retrieveOne(accountId);
+
+        final Set<String> mandatoryResponseParameters = new HashSet<>();
+        final SavingsAccountData savingsAccountTemplate = populateTemplateAndAssociations(accountId, savingsAccount,
+                staffInSelectedOfficeOnly, chargeStatus, uriInfo, mandatoryResponseParameters);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
+                mandatoryResponseParameters);
+        return this.toApiJsonSerializer.serialize(settings, savingsAccountTemplate,
+                SavingsApiConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    private SavingsAccountData populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount,
+            final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo,
+            final Set<String> mandatoryResponseParameters) {
+
+        Collection<SavingsAccountTransactionData> transactions = null;
+        Collection<SavingsAccountChargeData> charges = null;
+
+        final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
+        if (!associationParameters.isEmpty()) {
+
+            if (associationParameters.contains("all")) {
+                associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges));
+            }
+
+            if (associationParameters.contains(SavingsApiConstants.transactions)) {
+                mandatoryResponseParameters.add(SavingsApiConstants.transactions);
+                final Collection<SavingsAccountTransactionData> currentTransactions = this.savingsAccountReadPlatformService
+                        .retrieveAllTransactions(accountId, DepositAccountType.SAVINGS_DEPOSIT);
+                if (!CollectionUtils.isEmpty(currentTransactions)) {
+                    transactions = currentTransactions;
+                }
+            }
+
+            if (associationParameters.contains(SavingsApiConstants.charges)) {
+                mandatoryResponseParameters.add(SavingsApiConstants.charges);
+                final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
+                        .retrieveSavingsAccountCharges(accountId, chargeStatus);
+                if (!CollectionUtils.isEmpty(currentCharges)) {
+                    charges = currentCharges;
+                }
+            }
+        }
+
+        SavingsAccountData templateData = null;
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        if (settings.isTemplate()) {
+            templateData = this.savingsAccountReadPlatformService.retrieveTemplate(savingsAccount.clientId(), savingsAccount.groupId(),
+                    savingsAccount.productId(), staffInSelectedOfficeOnly);
+        }
+
+        return SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges);
+    }
+
+    @PUT
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("accountId") final Long accountId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSavingsAccount(accountId).withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String handleCommands(@PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        String jsonApiRequest = apiRequestBodyAsJson;
+        if (StringUtils.isBlank(jsonApiRequest)) {
+            jsonApiRequest = "{}";
+        }
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
+
+        CommandProcessingResult result = null;
+        if (is(commandParam, "reject")) {
+            final CommandWrapper commandRequest = builder.rejectSavingsAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "withdrawnByApplicant")) {
+            final CommandWrapper commandRequest = builder.withdrawSavingsAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "approve")) {
+            final CommandWrapper commandRequest = builder.approveSavingsAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "undoapproval")) {
+            final CommandWrapper commandRequest = builder.undoSavingsAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "activate")) {
+            final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "calculateInterest")) {
+            final CommandWrapper commandRequest = builder.withNoJsonBody().savingsAccountInterestCalculation(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "postInterest")) {
+            final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "applyAnnualFees")) {
+            final CommandWrapper commandRequest = builder.savingsAccountApplyAnnualFees(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "close")) {
+            final CommandWrapper commandRequest = builder.closeSavingsAccountApplication(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "assignSavingsOfficer")) {
+            final CommandWrapper commandRequest = builder.assignSavingsOfficer(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        } else if (is(commandParam, "unassignSavingsOfficer")) {
+            final CommandWrapper commandRequest = builder.unassignSavingsOfficer(accountId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+            return this.toApiJsonSerializer.serialize(result);
+        }
+
+        if (result == null) {
+            //
+            throw new UnrecognizedQueryParamException("command", commandParam,
+                    new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest",
+                            "postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer" });
+        }
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+    @DELETE
+    @Path("{accountId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("accountId") final Long accountId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSavingsAccount(accountId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java
new file mode 100644
index 0000000..5397340
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java
@@ -0,0 +1,289 @@
+/**
+ * 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.savings.api;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.apache.fineract.portfolio.savings.service.SavingsDropdownReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+@Path("/savingsproducts")
+@Component
+@Scope("singleton")
+public class SavingsProductsApiResource {
+
+    private final SavingsProductReadPlatformService savingProductReadPlatformService;
+    private final SavingsDropdownReadPlatformService dropdownReadPlatformService;
+    private final CurrencyReadPlatformService currencyReadPlatformService;
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<SavingsProductData> toApiJsonSerializer;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
+    private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public SavingsProductsApiResource(final SavingsProductReadPlatformService savingProductReadPlatformService,
+            final SavingsDropdownReadPlatformService dropdownReadPlatformService,
+            final CurrencyReadPlatformService currencyReadPlatformService, final PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<SavingsProductData> toApiJsonSerializer,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService,
+            final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService,
+            final ChargeReadPlatformService chargeReadPlatformService, PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.savingProductReadPlatformService = savingProductReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.currencyReadPlatformService = currencyReadPlatformService;
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
+        this.accountMappingReadPlatformService = accountMappingReadPlatformService;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createSavingProduct().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("productId") final Long productId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSavingProduct(productId).withJson(apiRequestBodyAsJson)
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME);
+
+        final Collection<SavingsProductData> products = this.savingProductReadPlatformService.retrieveAll();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, products, SavingsApiConstants.SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveOne(@PathParam("productId") final Long productId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME);
+
+        SavingsProductData savingProductData = this.savingProductReadPlatformService.retrieveOne(productId);
+
+        final Collection<ChargeData> charges = this.chargeReadPlatformService.retrieveSavingsProductCharges(productId);
+
+        savingProductData = SavingsProductData.withCharges(savingProductData, charges);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        if (savingProductData.hasAccountingEnabled()) {
+            final Map<String, Object> accountingMappings = this.accountMappingReadPlatformService
+                    .fetchAccountMappingDetailsForSavingsProduct(productId, savingProductData.accountingRuleTypeId());
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = this.accountMappingReadPlatformService
+                    .fetchPaymentTypeToFundSourceMappingsForSavingsProduct(productId);
+            Collection<ChargeToGLAccountMapper> feeToGLAccountMappings = this.accountMappingReadPlatformService
+                    .fetchFeeToIncomeAccountMappingsForSavingsProduct(productId);
+            Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings = this.accountMappingReadPlatformService
+                    .fetchPenaltyToIncomeAccountMappingsForSavingsProduct(productId);
+            savingProductData = SavingsProductData.withAccountingDetails(savingProductData, accountingMappings,
+                    paymentChannelToFundSourceMappings, feeToGLAccountMappings, penaltyToGLAccountMappings);
+        }
+
+        if (settings.isTemplate()) {
+            savingProductData = handleTemplateRelatedData(savingProductData);
+        }
+
+        return this.toApiJsonSerializer
+                .serialize(settings, savingProductData, SavingsApiConstants.SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveTemplate(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME);
+
+        final SavingsProductData savingProduct = handleTemplateRelatedData(null);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, savingProduct, SavingsApiConstants.SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS);
+    }
+
+    private SavingsProductData handleTemplateRelatedData(final SavingsProductData savingsProduct) {
+
+        final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations
+                .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.DAILY);
+
+        final EnumOptionData interestPostingPeriodType = SavingsEnumerations
+                .interestPostingPeriodType(SavingsPostingInterestPeriodType.MONTHLY);
+
+        final EnumOptionData interestCalculationType = SavingsEnumerations
+                .interestCalculationType(SavingsInterestCalculationType.DAILY_BALANCE);
+
+        final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations
+                .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_365);
+
+        final EnumOptionData accountingRule = AccountingEnumerations.accountingRuleType(AccountingRuleType.NONE);
+
+        CurrencyData currency = CurrencyData.blank();
+        final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
+        if (currencyOptions.size() == 1) {
+            currency = new ArrayList<>(currencyOptions).get(0);
+        }
+
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = this.dropdownReadPlatformService
+                .retrieveCompoundingInterestPeriodTypeOptions();
+
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = this.dropdownReadPlatformService
+                .retrieveInterestPostingPeriodTypeOptions();
+
+        final Collection<EnumOptionData> interestCalculationTypeOptions = this.dropdownReadPlatformService
+                .retrieveInterestCalculationTypeOptions();
+
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService
+                .retrieveInterestCalculationDaysInYearTypeOptions();
+
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = this.dropdownReadPlatformService
+                .retrieveLockinPeriodFrequencyTypeOptions();
+
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions();
+
+        final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+
+        final Collection<EnumOptionData> accountingRuleOptions = this.accountingDropdownReadPlatformService
+                .retrieveAccountingRuleTypeOptions();
+        final Map<String, List<GLAccountData>> accountingMappingOptions = this.accountingDropdownReadPlatformService
+                .retrieveAccountMappingOptionsForSavingsProducts();
+
+        // charges
+        final boolean feeChargesOnly = true;
+        Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly);
+        chargeOptions = CollectionUtils.isEmpty(chargeOptions) ? null : chargeOptions;
+
+        Collection<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties();
+        penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions;
+
+        SavingsProductData savingsProductToReturn = null;
+        if (savingsProduct != null) {
+            savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions,
+                    interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                    lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                    accountingMappingOptions, chargeOptions, penaltyOptions);
+        } else {
+            savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType,
+                    interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions,
+                    interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                    interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
+                    paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions);
+        }
+
+        return savingsProductToReturn;
+    }
+
+    @DELETE
+    @Path("{productId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("productId") final Long productId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSavingProduct(productId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java
new file mode 100644
index 0000000..08f1cf2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java
@@ -0,0 +1,298 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+
+/**
+ * Immutable data object representing abstract for Fixed and Recurring Deposit
+ * Accounts Accounts.
+ */
+public class DepositAccountData {
+
+    protected final Long id;
+    protected final String accountNo;
+    protected final String externalId;
+    protected final Long groupId;
+    protected final String groupName;
+    protected final Long clientId;
+    protected final String clientName;
+    protected final Long depositProductId;
+    protected final String depositProductName;
+    protected final Long fieldOfficerId;
+    protected final String fieldOfficerName;
+    protected final SavingsAccountStatusEnumData status;
+    protected final SavingsAccountApplicationTimelineData timeline;
+    protected final CurrencyData currency;
+    protected final BigDecimal nominalAnnualInterestRate;
+    protected final EnumOptionData interestCompoundingPeriodType;
+    protected final EnumOptionData interestPostingPeriodType;
+    protected final EnumOptionData interestCalculationType;
+    protected final EnumOptionData interestCalculationDaysInYearType;
+    protected final BigDecimal minRequiredOpeningBalance;
+    protected final Integer lockinPeriodFrequency;
+    protected final EnumOptionData lockinPeriodFrequencyType;
+    protected final boolean withdrawalFeeForTransfers;
+    protected final EnumOptionData depositType;
+    protected final BigDecimal minBalanceForInterestCalculation;
+
+    // associations
+    protected final SavingsAccountSummaryData summary;
+    protected final Collection<SavingsAccountTransactionData> transactions;
+
+    protected final Collection<SavingsAccountChargeData> charges;
+    protected final DepositAccountInterestRateChartData accountChart;
+
+    // template
+    protected final Collection<DepositProductData> productOptions;
+    protected final Collection<StaffData> fieldOfficerOptions;
+    protected final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions;
+    protected final Collection<EnumOptionData> interestPostingPeriodTypeOptions;
+    protected final Collection<EnumOptionData> interestCalculationTypeOptions;
+    protected final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions;
+    protected final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions;
+    protected final Collection<EnumOptionData> withdrawalFeeTypeOptions;
+    protected final Collection<ChargeData> chargeOptions;
+
+    protected final SavingsAccountChargeData withdrawalFee;
+    protected final SavingsAccountChargeData annualFee;
+
+    protected final DepositAccountInterestRateChartData chartTemplate;
+
+    public static DepositAccountData instance(final Long id, final String accountNo, final String externalId, final Long groupId,
+            final String groupName, final Long clientId, final String clientName, final Long productId, final String productName,
+            final Long fieldOfficerId, final String fieldOfficerName, final SavingsAccountStatusEnumData status,
+            final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final BigDecimal interestRate,
+            final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType,
+            final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType,
+            final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
+            final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
+            final SavingsAccountSummaryData summary, final EnumOptionData depositType, final BigDecimal minBalanceForInterestCalculation) {
+
+        final Collection<DepositProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountTransactionData> transactions = null;
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final DepositAccountInterestRateChartData accountChart = null;
+        final DepositAccountInterestRateChartData chartTemplate = null;
+
+        return new DepositAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
+                fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions,
+                fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, accountChart, chartTemplate, depositType,
+                minBalanceForInterestCalculation);
+    }
+
+    public static DepositAccountData lookup(final Long id, final String accountNo, final EnumOptionData depositType) {
+
+        final String externalId = null;
+        final Long groupId = null;
+        final String groupName = null;
+        final Long clientId = null;
+        final String clientName = null;
+        final Long productId = null;
+        final String productName = null;
+        final Long fieldOfficerId = null;
+        final String fieldOfficerName = null;
+        final SavingsAccountStatusEnumData status = null;
+        final SavingsAccountApplicationTimelineData timeline = null;
+        final CurrencyData currency = null;
+        final BigDecimal interestRate = null;
+        final EnumOptionData interestCompoundingPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        final SavingsAccountSummaryData summary = null;
+        final Collection<DepositProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountTransactionData> transactions = null;
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final DepositAccountInterestRateChartData accountChart = null;
+        final DepositAccountInterestRateChartData chartTemplate = null;
+        final BigDecimal minBalanceForInterestCalculation = null;
+
+        return new DepositAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
+                fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions,
+                fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, accountChart, chartTemplate, depositType,
+                minBalanceForInterestCalculation);
+    }
+
+    protected DepositAccountData(final Long id, final String accountNo, final String externalId, final Long groupId,
+            final String groupName, final Long clientId, final String clientName, final Long productId, final String productName,
+            final Long fieldofficerId, final String fieldofficerName, final SavingsAccountStatusEnumData status,
+            final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final BigDecimal nominalAnnualInterestRate,
+            final EnumOptionData interestPeriodType, final EnumOptionData interestPostingPeriodType,
+            final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType,
+            final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
+            final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
+            final SavingsAccountSummaryData summary, final Collection<SavingsAccountTransactionData> transactions,
+            final Collection<DepositProductData> productOptions, final Collection<StaffData> fieldOfficerOptions,
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> chargeOptions,
+            final DepositAccountInterestRateChartData accountChart, final DepositAccountInterestRateChartData chartTemplate,
+            final EnumOptionData depositType, final BigDecimal minBalanceForInterestCalculation) {
+        this.id = id;
+        this.accountNo = accountNo;
+        this.externalId = externalId;
+        this.groupId = groupId;
+        this.groupName = groupName;
+        this.clientId = clientId;
+        this.clientName = clientName;
+        this.depositProductId = productId;
+        this.depositProductName = productName;
+        this.fieldOfficerId = fieldofficerId;
+        this.fieldOfficerName = fieldofficerName;
+        this.status = status;
+        this.timeline = timeline;
+        this.currency = currency;
+        this.nominalAnnualInterestRate = nominalAnnualInterestRate;
+        this.interestCompoundingPeriodType = interestPeriodType;
+        this.interestPostingPeriodType = interestPostingPeriodType;
+        this.interestCalculationType = interestCalculationType;
+        this.interestCalculationDaysInYearType = interestCalculationDaysInYearType;
+        this.minRequiredOpeningBalance = minRequiredOpeningBalance;
+        this.lockinPeriodFrequency = lockinPeriodFrequency;
+        this.lockinPeriodFrequencyType = lockinPeriodFrequencyType;
+        this.withdrawalFeeForTransfers = withdrawalFeeForTransfers;
+        this.summary = summary;
+        this.transactions = transactions;
+        this.productOptions = productOptions;
+        this.fieldOfficerOptions = fieldOfficerOptions;
+        this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions;
+        this.interestPostingPeriodTypeOptions = interestPostingPeriodTypeOptions;
+        this.interestCalculationTypeOptions = interestCalculationTypeOptions;
+        this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions;
+        this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions;
+        this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions;
+
+        this.charges = charges;// charges associated with Savings account
+        // charges available for adding to Savings account
+        this.chargeOptions = chargeOptions;
+
+        this.withdrawalFee = getWithdrawalFee();
+
+        this.annualFee = getAnnualFee();
+
+        this.accountChart = accountChart;
+        this.chartTemplate = chartTemplate;
+        this.depositType = depositType;
+        this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
+    }
+
+    private SavingsAccountChargeData getWithdrawalFee() {
+        for (SavingsAccountChargeData charge : this.charges()) {
+            if (charge.isWithdrawalFee()) return charge;
+        }
+        return null;
+    }
+
+    private SavingsAccountChargeData getAnnualFee() {
+        for (SavingsAccountChargeData charge : this.charges()) {
+            if (charge.isAnnualFee()) return charge;
+        }
+        return null;
+    }
+
+    public Long id() {
+        return this.id;
+    }
+
+    public Long clientId() {
+        return this.clientId;
+    }
+
+    public Long groupId() {
+        return this.groupId;
+    }
+
+    public Long productId() {
+        return this.depositProductId;
+    }
+
+    public CurrencyData currency() {
+        return this.currency;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final DepositAccountData rhs = (DepositAccountData) obj;
+        return new EqualsBuilder().append(this.id, rhs.id).append(this.accountNo, rhs.accountNo).isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).toHashCode();
+    }
+
+    public Collection<SavingsAccountChargeData> charges() {
+        return (this.charges == null) ? new HashSet<SavingsAccountChargeData>() : this.charges;
+    }
+
+    public EnumOptionData depositType() {
+        return depositType;
+    }
+
+    public String accountNo() {
+        return accountNo;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
new file mode 100644
index 0000000..588f724
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
@@ -0,0 +1,722 @@
+/**
+ * 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.savings.data;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.adjustAdvanceTowardsFuturePaymentsParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.allowWithdrawalParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodFrequencyIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isCalendarInheritedParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.linkedAccountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.mandatoryRecommendedDepositAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalApplicableParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestOnTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.recurringFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.recurringFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.externalIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.groupIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class DepositAccountDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final DepositProductDataValidator productDataValidator;
+
+    @Autowired
+    public DepositAccountDataValidator(final FromJsonHelper fromApiJsonHelper, final DepositProductDataValidator productDataValidator) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.productDataValidator = productDataValidator;
+    }
+
+    public void validateFixedDepositForSubmit(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateDepositDetailsForSubmit(element, baseDataValidator);
+        validatePreClosureDetailForSubmit(element, baseDataValidator);
+        validateDepositTermDeatilForSubmit(element, baseDataValidator, DepositAccountType.FIXED_DEPOSIT);
+        validateSavingsCharges(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateFixedDepositForUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateDepositDetailsForUpdate(element, baseDataValidator);
+        validatePreClosureDetailForUpdate(element, baseDataValidator);
+        validateDepositTermDeatilForUpdate(element, baseDataValidator, DepositAccountType.FIXED_DEPOSIT);
+        // validateSavingsCharges(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateRecurringDepositForSubmit(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateDepositDetailsForSubmit(element, baseDataValidator);
+        validatePreClosureDetailForSubmit(element, baseDataValidator);
+        validateDepositTermDeatilForSubmit(element, baseDataValidator, DepositAccountType.RECURRING_DEPOSIT);
+        validateRecurringDetailForSubmit(element, baseDataValidator);
+        validateSavingsCharges(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateRecurringDepositForUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateDepositDetailsForUpdate(element, baseDataValidator);
+        validatePreClosureDetailForUpdate(element, baseDataValidator);
+        validateDepositTermDeatilForUpdate(element, baseDataValidator, DepositAccountType.RECURRING_DEPOSIT);
+        validateRecurringDetailForUpdate(element, baseDataValidator);
+        // validateSavingsCharges(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    private void validateDepositDetailsForSubmit(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+
+        final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element);
+        if (clientId != null) {
+            baseDataValidator.reset().parameter(clientIdParamName).value(clientId).longGreaterThanZero();
+        }
+
+        final Long groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element);
+        if (groupId != null) {
+            baseDataValidator.reset().parameter(groupIdParamName).value(groupId).longGreaterThanZero();
+        }
+
+        if (clientId == null && groupId == null) {
+            baseDataValidator.reset().parameter(clientIdParamName).value(clientId).notNull().integerGreaterThanZero();
+        }
+
+        final Long productId = this.fromApiJsonHelper.extractLongNamed(productIdParamName, element);
+        baseDataValidator.reset().parameter(productIdParamName).value(productId).notNull().integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists(fieldOfficerIdParamName, element)) {
+            final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element);
+            baseDataValidator.reset().parameter(fieldOfficerIdParamName).value(fieldOfficerId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element);
+        baseDataValidator.reset().parameter(submittedOnDateParamName).value(submittedOnDate).notNull();
+
+        if (this.fromApiJsonHelper.parameterExists(accountNoParamName, element)) {
+            final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParamName, element);
+            baseDataValidator.reset().parameter(accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParamName, element);
+            baseDataValidator.reset().parameter(externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
+            final BigDecimal interestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName,
+                    element);
+            baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(interestRate).notNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
+            final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCompoundingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestPostingPeriodTypeParamName, element)) {
+            final Integer interestPostingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestPostingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestPostingPeriodTypeParamName).value(interestPostingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsPostingInterestPeriodType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
+            final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName,
+                    element);
+            baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull()
+                    .isOneOfTheseValues(SavingsInterestCalculationType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
+            final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCalculationDaysInYearTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType)
+                    .notNull().isOneOfTheseValues(SavingsInterestCalculationDaysInYearType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
+            final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minRequiredOpeningBalanceParamName, element);
+            baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).zeroOrPositiveAmount();
+        }
+
+        boolean isLockinPeriodFrequencyValidated = false;
+        boolean isLockinPeriodFrequencyTypeValidated = false;
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
+            isLockinPeriodFrequencyValidated = true;
+            final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                    element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).integerZeroOrGreater();
+
+            if (lockinPeriodFrequency != null) {
+                isLockinPeriodFrequencyTypeValidated = true;
+                final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                        lockinPeriodFrequencyTypeParamName, element);
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull()
+                        .inMinMaxRange(0, 3);
+            }
+        }
+
+        if (!isLockinPeriodFrequencyTypeValidated && this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
+            final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    lockinPeriodFrequencyTypeParamName, element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
+
+            if (lockinPeriodFrequencyType != null && !isLockinPeriodFrequencyValidated) {
+                final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                        element);
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull()
+                        .integerZeroOrGreater();
+            }
+        }
+
+        boolean isLinkedAccRequired = false;
+        if (fromApiJsonHelper.parameterExists(transferInterestToSavingsParamName, element)) {
+            isLinkedAccRequired = fromApiJsonHelper.extractBooleanNamed(transferInterestToSavingsParamName, element);
+        }
+
+        final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(linkedAccountParamName, element);
+        if (isLinkedAccRequired) {
+            baseDataValidator.reset().parameter(linkedAccountParamName).value(linkAccountId).notNull().longGreaterThanZero();
+        } else {
+            baseDataValidator.reset().parameter(linkedAccountParamName).value(linkAccountId).ignoreIfNull().longGreaterThanZero();
+        }
+    }
+
+    private void validateDepositDetailsForUpdate(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        Long clientId = null;
+        if (this.fromApiJsonHelper.parameterExists(clientIdParamName, element)) {
+            clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element);
+            baseDataValidator.reset().parameter(clientIdParamName).value(clientId).ignoreIfNull().longGreaterThanZero();
+
+            Long groupId = null;
+            if (this.fromApiJsonHelper.parameterExists(productIdParamName, element)) {
+                groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element);
+                baseDataValidator.reset().parameter(groupIdParamName).value(groupId).ignoreIfNull().longGreaterThanZero();
+            }
+
+            if (clientId == null && groupId == null) {
+                // either clientId or groupId must exists if param passed for
+                // update.
+                baseDataValidator.reset().parameter(clientIdParamName).value(clientId).notNull().integerGreaterThanZero();
+            }
+        }
+
+        Long groupId = null;
+        if (this.fromApiJsonHelper.parameterExists(groupIdParamName, element)) {
+            groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element);
+            baseDataValidator.reset().parameter(groupIdParamName).value(groupId).ignoreIfNull().longGreaterThanZero();
+
+            if (this.fromApiJsonHelper.parameterExists(clientIdParamName, element)) {
+                clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element);
+                baseDataValidator.reset().parameter(clientIdParamName).value(clientId).ignoreIfNull().longGreaterThanZero();
+            }
+
+            if (clientId == null && groupId == null) {
+                // either clientId or groupId must exists if param passed for
+                // update.
+                baseDataValidator.reset().parameter(clientIdParamName).value(clientId).notNull().integerGreaterThanZero();
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(productIdParamName, element)) {
+            final Long productId = this.fromApiJsonHelper.extractLongNamed(productIdParamName, element);
+            baseDataValidator.reset().parameter(productIdParamName).value(productId).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(fieldOfficerIdParamName, element)) {
+            final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element);
+            baseDataValidator.reset().parameter(fieldOfficerIdParamName).value(fieldOfficerId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(submittedOnDateParamName, element)) {
+            final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element);
+            baseDataValidator.reset().parameter(submittedOnDateParamName).value(submittedOnDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(accountNoParamName, element)) {
+            final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParamName, element);
+            baseDataValidator.reset().parameter(accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParamName, element);
+            baseDataValidator.reset().parameter(externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
+            final BigDecimal interestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName,
+                    element);
+            baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(interestRate).notNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
+            final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCompoundingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestPostingPeriodTypeParamName, element)) {
+            final Integer interestPostingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestPostingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestPostingPeriodTypeParamName).value(interestPostingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsPostingInterestPeriodType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
+            final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName,
+                    element);
+            baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull()
+                    .isOneOfTheseValues(SavingsInterestCalculationType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
+            final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCalculationDaysInYearTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType)
+                    .notNull().isOneOfTheseValues(SavingsInterestCalculationDaysInYearType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
+            final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minRequiredOpeningBalanceParamName, element);
+            baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).ignoreIfNull()
+                    .zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
+            final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                    element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull()
+                    .integerZeroOrGreater();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
+            final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    lockinPeriodFrequencyTypeParamName, element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
+        }
+
+        boolean isLinkedAccRequired = false;
+        if (fromApiJsonHelper.parameterExists(transferInterestToSavingsParamName, element)) {
+            isLinkedAccRequired = fromApiJsonHelper.extractBooleanNamed(transferInterestToSavingsParamName, element);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(linkedAccountParamName, element)) {
+            final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(linkedAccountParamName, element);
+            if (isLinkedAccRequired) {
+                baseDataValidator.reset().parameter(linkedAccountParamName).value(linkAccountId).notNull().longGreaterThanZero();
+            } else {
+                baseDataValidator.reset().parameter(linkedAccountParamName).value(linkAccountId).ignoreIfNull().longGreaterThanZero();
+            }
+        }
+
+    }
+
+    private void validatePreClosureDetailForSubmit(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+
+        if (fromApiJsonHelper.parameterExists(preClosurePenalApplicableParamName, element)) {
+            final boolean preClosurePenalApplicable = fromApiJsonHelper.extractBooleanNamed(preClosurePenalApplicableParamName, element);
+
+            if (preClosurePenalApplicable) {
+                final BigDecimal penalInterestRate = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(preClosurePenalInterestParamName,
+                        element);
+                baseDataValidator.reset().parameter(preClosurePenalInterestParamName).value(penalInterestRate)
+                        .cantBeBlankWhenParameterProvidedIs(preClosurePenalApplicableParamName, preClosurePenalApplicable)
+                        .zeroOrPositiveAmount();
+
+                final Integer preClosurePenalInterestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                        preClosurePenalInterestOnTypeIdParamName, element);
+                baseDataValidator.reset().parameter(preClosurePenalInterestOnTypeIdParamName).value(preClosurePenalInterestType)
+                        .cantBeBlankWhenParameterProvidedIs(preClosurePenalApplicableParamName, preClosurePenalApplicable)
+                        .isOneOfTheseValues(PreClosurePenalInterestOnType.integerValues());
+            }
+        }
+    }
+
+    private void validatePreClosureDetailForUpdate(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        this.productDataValidator.validatePreClosureDetailForUpdate(element, baseDataValidator);
+    }
+
+    private void validateDepositTermDeatilForSubmit(final JsonElement element, final DataValidatorBuilder baseDataValidator,
+            final DepositAccountType depositType) {
+
+        Integer minTerm = null;
+        if (fromApiJsonHelper.parameterExists(minDepositTermParamName, element)) {
+            minTerm = fromApiJsonHelper.extractIntegerSansLocaleNamed(minDepositTermParamName, element);
+            baseDataValidator.reset().parameter(minDepositTermParamName).value(minTerm).integerGreaterThanZero();
+        }
+
+        if (fromApiJsonHelper.parameterExists(maxDepositTermParamName, element)) {
+            final Integer maxTerm = fromApiJsonHelper.extractIntegerSansLocaleNamed(maxDepositTermParamName, element);
+            baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm).integerGreaterThanZero();
+        }
+
+        if (fromApiJsonHelper.parameterExists(minDepositTermTypeIdParamName, element)) {
+            final Integer minDepositTermType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(minDepositTermTypeIdParamName, element);
+            baseDataValidator.reset().parameter(minDepositTermTypeIdParamName).value(minDepositTermType)
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+
+        if (fromApiJsonHelper.parameterExists(maxDepositTermTypeIdParamName, element)) {
+            final Integer maxDepositTermType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(maxDepositTermTypeIdParamName, element);
+            baseDataValidator.reset().parameter(maxDepositTermTypeIdParamName).value(maxDepositTermType)
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+
+        if (fromApiJsonHelper.parameterExists(inMultiplesOfDepositTermParamName, element)) {
+            final Integer inMultiplesOfDepositTerm = fromApiJsonHelper.extractIntegerSansLocaleNamed(inMultiplesOfDepositTermParamName,
+                    element);
+            baseDataValidator.reset().parameter(inMultiplesOfDepositTermParamName).value(inMultiplesOfDepositTerm).integerGreaterThanZero();
+            final Integer inMultiplesOfDepositTermType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    inMultiplesOfDepositTermTypeIdParamName, element);
+            baseDataValidator.reset().parameter(inMultiplesOfDepositTermTypeIdParamName).value(inMultiplesOfDepositTermType)
+                    .cantBeBlankWhenParameterProvidedIs(inMultiplesOfDepositTermParamName, inMultiplesOfDepositTerm)
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+
+        // for recurring deposit the total deposit amount is derived from
+        // recurring deposit amount * number of deposits.
+        if (depositType.isFixedDeposit()) {
+            final BigDecimal depositAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
+            baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
+        }
+
+        if (depositType.isFixedDeposit() || fromApiJsonHelper.parameterExists(depositPeriodParamName, element)) {
+            final Integer depositPeriod = fromApiJsonHelper.extractIntegerSansLocaleNamed(depositPeriodParamName, element);
+            baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull().integerGreaterThanZero();
+        }
+
+        final Integer depositPeriodFrequencyId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(depositPeriodFrequencyIdParamName,
+                element);
+        baseDataValidator.reset().parameter(depositPeriodFrequencyIdParamName).value(depositPeriodFrequencyId)
+                .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+    }
+
+    private void validateDepositTermDeatilForUpdate(final JsonElement element, final DataValidatorBuilder baseDataValidator,
+            final DepositAccountType depositAccountType) {
+        this.productDataValidator.validateDepositTermDetailForUpdate(element, baseDataValidator);
+
+        if (fromApiJsonHelper.parameterExists(depositAmountParamName, element)) {
+            final BigDecimal depositAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
+            baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
+        }
+
+        if (fromApiJsonHelper.parameterExists(depositPeriodParamName, element)) {
+            final Integer depositPeriod = fromApiJsonHelper.extractIntegerSansLocaleNamed(depositPeriodParamName, element);
+            if (depositAccountType.isFixedDeposit()) {
+                baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull().integerGreaterThanZero();
+            } else {
+                baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull().integerGreaterThanZero();
+            }
+        }
+
+        if (fromApiJsonHelper.parameterExists(depositPeriodFrequencyIdParamName, element)) {
+            final Integer depositPeriodFrequencyId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    depositPeriodFrequencyIdParamName, element);
+            baseDataValidator.reset().parameter(depositPeriodFrequencyIdParamName).value(depositPeriodFrequencyId)
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+    }
+
+    private void validateRecurringDetailForSubmit(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+
+        final BigDecimal mandatoryRecommendedDepositAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                mandatoryRecommendedDepositAmountParamName, element);
+        baseDataValidator.reset().parameter(mandatoryRecommendedDepositAmountParamName).value(mandatoryRecommendedDepositAmount).notNull()
+                .positiveAmount();
+
+        if (this.fromApiJsonHelper.parameterExists(isMandatoryDepositParamName, element)) {
+            final Boolean isMandatoryDeposit = this.fromApiJsonHelper.extractBooleanNamed(isMandatoryDepositParamName, element);
+            baseDataValidator.reset().parameter(isMandatoryDepositParamName).value(isMandatoryDeposit).ignoreIfNull()
+                    .validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(allowWithdrawalParamName, element)) {
+            final Boolean allowWithdrawal = this.fromApiJsonHelper.extractBooleanNamed(allowWithdrawalParamName, element);
+            baseDataValidator.reset().parameter(allowWithdrawalParamName).value(allowWithdrawal).ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(adjustAdvanceTowardsFuturePaymentsParamName, element)) {
+            final Boolean adjustAdvanceTowardsFuturePayments = this.fromApiJsonHelper.extractBooleanNamed(
+                    adjustAdvanceTowardsFuturePaymentsParamName, element);
+            baseDataValidator.reset().parameter(adjustAdvanceTowardsFuturePaymentsParamName).value(adjustAdvanceTowardsFuturePayments)
+                    .ignoreIfNull().validateForBooleanValue();
+        }
+
+        // First validate if string is empty
+        final String isCalendarInheritedString = this.fromApiJsonHelper.extractStringNamed(isCalendarInheritedParamName, element);
+        if (StringUtils.isBlank(isCalendarInheritedString)) {
+            baseDataValidator.reset().parameter(isCalendarInheritedParamName).value(isCalendarInheritedString).notBlank();
+        } else {
+            // validate the boolean value
+            final Boolean isCalendarInherited = this.fromApiJsonHelper.extractBooleanNamed(isCalendarInheritedParamName, element);
+            baseDataValidator.reset().parameter(isCalendarInheritedParamName).value(isCalendarInherited).notNull()
+                    .validateForBooleanValue();
+            if (!isCalendarInherited) {
+                final Integer frequencyType = this.fromApiJsonHelper
+                        .extractIntegerSansLocaleNamed(recurringFrequencyTypeParamName, element);
+                baseDataValidator.reset().parameter(recurringFrequencyTypeParamName).value(frequencyType).notBlank()
+                        .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+
+                final Integer frequency = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(recurringFrequencyParamName, element);
+                baseDataValidator.reset().parameter(recurringFrequencyParamName).value(frequency).notNull().integerGreaterThanZero();
+
+                /*
+                 * if (CalendarFrequencyType.fromInt(frequency).isWeekly()) {
+                 * final Integer repeatsOnDay =
+                 * this.fromApiJsonHelper.extractIntegerSansLocaleNamed
+                 * (repeatsOnDayParamName, element);
+                 * baseDataValidator.reset().parameter
+                 * (repeatsOnDayParamName).value(repeatsOnDay).notBlank()
+                 * .inMinMaxRange(CalendarWeekDaysType.getMinValue(),
+                 * CalendarWeekDaysType.getMaxValue()); }
+                 */
+            }
+        }
+    }
+
+    private void validateRecurringDetailForUpdate(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+        if (this.fromApiJsonHelper.parameterExists(isMandatoryDepositParamName, element)) {
+            final Boolean isMandatoryDeposit = this.fromApiJsonHelper.extractBooleanNamed(isMandatoryDepositParamName, element);
+            baseDataValidator.reset().parameter(isMandatoryDepositParamName).value(isMandatoryDeposit).ignoreIfNull()
+                    .validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(allowWithdrawalParamName, element)) {
+            final Boolean allowWithdrawal = this.fromApiJsonHelper.extractBooleanNamed(allowWithdrawalParamName, element);
+            baseDataValidator.reset().parameter(allowWithdrawalParamName).value(allowWithdrawal).ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(adjustAdvanceTowardsFuturePaymentsParamName, element)) {
+            final Boolean adjustAdvanceTowardsFuturePayments = this.fromApiJsonHelper.extractBooleanNamed(
+                    adjustAdvanceTowardsFuturePaymentsParamName, element);
+            baseDataValidator.reset().parameter(adjustAdvanceTowardsFuturePaymentsParamName).value(adjustAdvanceTowardsFuturePayments)
+                    .ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(isCalendarInheritedParamName, element)) {
+            final Boolean isCalendarInherited = this.fromApiJsonHelper.extractBooleanNamed(isCalendarInheritedParamName, element);
+            baseDataValidator.reset().parameter(isCalendarInheritedParamName).value(isCalendarInherited).notNull()
+                    .validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(recurringFrequencyTypeParamName, element)) {
+            final Integer frequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(recurringFrequencyTypeParamName, element);
+            baseDataValidator.reset().parameter(recurringFrequencyTypeParamName).value(frequencyType).notBlank()
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(recurringFrequencyParamName, element)) {
+            final Integer frequency = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(recurringFrequencyParamName, element);
+            baseDataValidator.reset().parameter(recurringFrequencyParamName).value(frequency).notNull().integerGreaterThanZero();
+            /*
+             * if (this.fromApiJsonHelper.parameterExists(repeatsOnDayParamName,
+             * element)) { if
+             * (CalendarFrequencyType.fromInt(frequency).isWeekly()) { final
+             * Integer repeatsOnDay =
+             * this.fromApiJsonHelper.extractIntegerSansLocaleNamed
+             * (repeatsOnDayParamName, element);
+             * baseDataValidator.reset().parameter
+             * (repeatsOnDayParamName).value(repeatsOnDay).notBlank()
+             * .inMinMaxRange(CalendarWeekDaysType.getMinValue(),
+             * CalendarWeekDaysType.getMaxValue()); } }
+             */
+        }
+    }
+
+    public void validatelinkedSavingsAccount(final SavingsAccount linkedSavingsAccount, final SavingsAccount savingsAccount) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        if (linkedSavingsAccount.isNotActive()) {
+            final ApiParameterError error = ApiParameterError.parameterError("validation.msg.deposit.linked.savings.account.is.not.active",
+                    "Linked Savings account with id:" + linkedSavingsAccount.getId() + " is not in active state", "linkAccountId",
+                    linkedSavingsAccount.getId());
+            dataValidationErrors.add(error);
+        } else if (savingsAccount.clientId() != linkedSavingsAccount.clientId()) {
+            final ApiParameterError error = ApiParameterError.parameterError(
+                    "validation.msg.deposit.linked.savings.account.not.belongs.to.same.client", "Linked Savings account with id:"
+                            + linkedSavingsAccount.getId() + " is not belongs to the same client", "linkAccountId",
+                    linkedSavingsAccount.getId());
+            dataValidationErrors.add(error);
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }
+    }
+
+    public void throwLinkedAccountRequiredError() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final ApiParameterError error = ApiParameterError.parameterError(
+                "validation.msg.fixeddepositaccount.linkAccountId.cannot.be.blank", "Linked Savings account required", "linkAccountId");
+        dataValidationErrors.add(error);
+        throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                dataValidationErrors);
+    }
+
+    private void validateSavingsCharges(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            final String monthDayFormat = this.fromApiJsonHelper.extractMonthDayFormatParameter(topLevelJsonElement);
+            if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+
+                    final JsonObject savingsChargeElement = array.get(i).getAsJsonObject();
+
+                    // final Long id =
+                    // this.fromApiJsonHelper.extractLongNamed(idParamName,
+                    // savingsChargeElement);
+
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement);
+                    baseDataValidator.reset().parameter(chargeIdParamName).value(chargeId).longGreaterThanZero();
+
+                    final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale);
+                    baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
+
+                    if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, savingsChargeElement)) {
+                        final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement,
+                                monthDayFormat, locale);
+                        baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDay).notNull();
+                    }
+
+                    if (this.fromApiJsonHelper.parameterExists(feeIntervalParamName, savingsChargeElement)) {
+                        final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, savingsChargeElement,
+                                Locale.getDefault());
+                        baseDataValidator.reset().parameter(feeIntervalParamName).value(feeInterval).notNull().inMinMaxRange(1, 12);
+                    }
+                }
+            }
+        }
+    }
+
+    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/portfolio/savings/data/DepositAccountInterestIncentiveData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestIncentiveData.java
new file mode 100755
index 0000000..7df1357
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestIncentiveData.java
@@ -0,0 +1,75 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestIncentiveData;
+
+public class DepositAccountInterestIncentiveData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final EnumOptionData entityType;
+    @SuppressWarnings("unused")
+    private final EnumOptionData attributeName;
+    @SuppressWarnings("unused")
+    private final EnumOptionData conditionType;
+    @SuppressWarnings("unused")
+    private final String attributeValue;
+    @SuppressWarnings("unused")
+    private final String attributeValueDesc;
+    @SuppressWarnings("unused")
+    private final EnumOptionData incentiveType;
+    @SuppressWarnings("unused")
+    private final BigDecimal amount;
+
+    public static DepositAccountInterestIncentiveData instance(final Long id, final EnumOptionData entityType,
+            final EnumOptionData attributeName, final EnumOptionData conditionType, final String attributeValue,
+            final String attributeValueDesc, final EnumOptionData incentiveType, final BigDecimal amount) {
+
+        return new DepositAccountInterestIncentiveData(id, entityType, attributeName, conditionType, attributeValue, attributeValueDesc,
+                incentiveType, amount);
+    }
+
+    public static DepositAccountInterestIncentiveData from(final InterestIncentiveData incentiveData) {
+        final Long id = null;
+        return new DepositAccountInterestIncentiveData(id, incentiveData.entityType(), incentiveData.attributeName(),
+                incentiveData.conditionType(), incentiveData.attributeValue(), incentiveData.attributeValueDesc(),
+                incentiveData.incentiveType(), incentiveData.amount());
+    }
+
+    private DepositAccountInterestIncentiveData(final Long id, final EnumOptionData entityType, final EnumOptionData attributeName,
+            final EnumOptionData conditionType, final String attributeValue, final String attributeValueDesc,
+            final EnumOptionData incentiveType, final BigDecimal amount) {
+
+        this.id = id;
+        this.entityType = entityType;
+        this.attributeName = attributeName;
+        this.conditionType = conditionType;
+        this.attributeValue = attributeValue;
+        this.attributeValueDesc = attributeValueDesc;
+        this.incentiveType = incentiveType;
+        this.amount = amount;
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartData.java
new file mode 100644
index 0000000..7b78f03
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartData.java
@@ -0,0 +1,175 @@
+/**
+ * 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.savings.data;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabData;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a deposit account interest rate chart.
+ */
+public class DepositAccountInterestRateChartData {
+
+    private final Long id;
+    private final String name;
+    private final String description;
+    private final LocalDate fromDate;
+    private final LocalDate endDate;
+    private final Long accountId;
+    private final String accountNumber;
+    // associations
+    private Set<DepositAccountInterestRateChartSlabData> chartSlabs;
+
+    // template
+    private final Collection<EnumOptionData> periodTypes;
+    private final Collection<EnumOptionData> entityTypeOptions;
+    private final Collection<EnumOptionData> attributeNameOptions;
+    private final Collection<EnumOptionData> conditionTypeOptions;
+    private final Collection<EnumOptionData> incentiveTypeOptions;
+    private final Collection<CodeValueData> genderOptions;
+    private final Collection<CodeValueData> clientTypeOptions;
+    private final Collection<CodeValueData> clientClassificationOptions;
+
+    public static DepositAccountInterestRateChartData instance(Long id, String name, String description, LocalDate fromDate,
+            LocalDate endDate, Long accountId, String accountNumber, Set<DepositAccountInterestRateChartSlabData> chartSlabs,
+            Collection<EnumOptionData> periodTypes) {
+
+        final Collection<EnumOptionData> entityTypeOptions = null;
+        final Collection<EnumOptionData> attributeNameOptions = null;
+        final Collection<EnumOptionData> conditionTypeOptions = null;
+        final Collection<EnumOptionData> incentiveTypeOptions = null;
+        final Collection<CodeValueData> genderOptions = null;
+        final Collection<CodeValueData> clientTypeOptions = null;
+        final Collection<CodeValueData> clientClassificationOptions = null;
+        return new DepositAccountInterestRateChartData(id, name, description, fromDate, endDate, accountId, accountNumber, chartSlabs,
+                periodTypes, entityTypeOptions, attributeNameOptions, conditionTypeOptions, incentiveTypeOptions, genderOptions,
+                clientTypeOptions, clientClassificationOptions);
+    }
+
+    public static DepositAccountInterestRateChartData from(InterestRateChartData productChartData) {
+        final Long id = null;
+        final Long accountId = null;
+        final String accountNumber = null;
+        Set<DepositAccountInterestRateChartSlabData> fromProdChartSlabs = new HashSet<>();
+        Set<InterestRateChartSlabData> productChartSlabDatas = productChartData.chartSlabs();
+        if (productChartSlabDatas != null) {
+            for (InterestRateChartSlabData productChartSlabData : productChartSlabDatas) {
+                fromProdChartSlabs.add(DepositAccountInterestRateChartSlabData.from(productChartSlabData));
+            }
+        }
+
+        return new DepositAccountInterestRateChartData(id, productChartData.name(), productChartData.description(),
+                productChartData.fromDate(), productChartData.endDate(), accountId, accountNumber, fromProdChartSlabs,
+                productChartData.periodTypes(), productChartData.entityTypeOptions(), productChartData.attributeNameOptions(),
+                productChartData.conditionTypeOptions(), productChartData.incentiveTypeOptions(), productChartData.genderOptions(),
+                productChartData.clientTypeOptions(), productChartData.clientClassificationOptions());
+    }
+
+    public static DepositAccountInterestRateChartData withSlabs(DepositAccountInterestRateChartData interestRateChartData,
+            Set<DepositAccountInterestRateChartSlabData> chartSlabs) {
+        return new DepositAccountInterestRateChartData(interestRateChartData.id, interestRateChartData.name,
+                interestRateChartData.description, interestRateChartData.fromDate, interestRateChartData.endDate,
+                interestRateChartData.accountId, interestRateChartData.accountNumber, chartSlabs, interestRateChartData.periodTypes,
+                interestRateChartData.entityTypeOptions, interestRateChartData.attributeNameOptions,
+                interestRateChartData.conditionTypeOptions, interestRateChartData.incentiveTypeOptions,
+                interestRateChartData.genderOptions, interestRateChartData.clientTypeOptions,
+                interestRateChartData.clientClassificationOptions);
+    }
+
+    public static DepositAccountInterestRateChartData withTemplate(DepositAccountInterestRateChartData interestRateChartData,
+            Collection<EnumOptionData> periodTypes, final Collection<EnumOptionData> entityTypeOptions,
+            final Collection<EnumOptionData> attributeNameOptions, final Collection<EnumOptionData> conditionTypeOptions,
+            final Collection<EnumOptionData> incentiveTypeOptions, final Collection<CodeValueData> genderOptions,
+            final Collection<CodeValueData> clientTypeOptions, final Collection<CodeValueData> clientClassificationOptions) {
+        return new DepositAccountInterestRateChartData(interestRateChartData.id, interestRateChartData.name,
+                interestRateChartData.description, interestRateChartData.fromDate, interestRateChartData.endDate,
+                interestRateChartData.accountId, interestRateChartData.accountNumber, interestRateChartData.chartSlabs, periodTypes,
+                entityTypeOptions, attributeNameOptions, conditionTypeOptions, incentiveTypeOptions, genderOptions, clientTypeOptions,
+                clientClassificationOptions);
+    }
+
+    public static DepositAccountInterestRateChartData template(Collection<EnumOptionData> periodTypes,
+            final Collection<EnumOptionData> entityTypeOptions, final Collection<EnumOptionData> attributeNameOptions,
+            final Collection<EnumOptionData> conditionTypeOptions, final Collection<EnumOptionData> incentiveTypeOptions,
+            final Collection<CodeValueData> genderOptions, final Collection<CodeValueData> clientTypeOptions,
+            final Collection<CodeValueData> clientClassificationOptions) {
+        final Long id = null;
+        final String name = null;
+        final String description = null;
+        final LocalDate fromDate = null;
+        final LocalDate endDate = null;
+        final Long accountId = null;
+        final String accountNumber = null;
+        final Set<DepositAccountInterestRateChartSlabData> chartSlabs = null;
+        return new DepositAccountInterestRateChartData(id, name, description, fromDate, endDate, accountId, accountNumber, chartSlabs,
+                periodTypes, entityTypeOptions, attributeNameOptions, conditionTypeOptions, incentiveTypeOptions, genderOptions,
+                clientTypeOptions, clientClassificationOptions);
+    }
+
+    private DepositAccountInterestRateChartData(Long id, String name, String description, LocalDate fromDate, LocalDate endDate,
+            Long accountId, String accountNumber, Set<DepositAccountInterestRateChartSlabData> chartSlabs,
+            Collection<EnumOptionData> periodTypes, final Collection<EnumOptionData> entityTypeOptions,
+            final Collection<EnumOptionData> attributeNameOptions, final Collection<EnumOptionData> conditionTypeOptions,
+            final Collection<EnumOptionData> incentiveTypeOptions, final Collection<CodeValueData> genderOptions,
+            final Collection<CodeValueData> clientTypeOptions, final Collection<CodeValueData> clientClassificationOptions) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.fromDate = fromDate;
+        this.endDate = endDate;
+        this.chartSlabs = chartSlabs;
+        this.accountId = accountId;
+        this.accountNumber = accountNumber;
+        this.periodTypes = periodTypes;
+        this.attributeNameOptions = attributeNameOptions;
+        this.entityTypeOptions = entityTypeOptions;
+        this.conditionTypeOptions = conditionTypeOptions;
+        this.incentiveTypeOptions = incentiveTypeOptions;
+        this.genderOptions = genderOptions;
+        this.clientTypeOptions = clientTypeOptions;
+        this.clientClassificationOptions = clientClassificationOptions;
+    }
+
+    public void addChartSlab(final DepositAccountInterestRateChartSlabData chartSlab) {
+        if (this.chartSlabs == null) {
+            this.chartSlabs = new HashSet<>();
+        }
+
+        this.chartSlabs.add(chartSlab);
+    }
+
+    public boolean isFromDateAfter(final LocalDate compareDate) {
+        return (compareDate == null) ? false : this.fromDate.isAfter(compareDate);
+    }
+
+    public LocalDate endDate() {
+        return this.endDate;
+    }
+
+    public LocalDate fromDate() {
+        return this.fromDate;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartSlabData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartSlabData.java
new file mode 100644
index 0000000..d9c6e5e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartSlabData.java
@@ -0,0 +1,147 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestIncentiveData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabData;
+
+/**
+ * Immutable data object representing deposit accounts Interest rate Slabs.
+ */
+public class DepositAccountInterestRateChartSlabData {
+
+    private final Long id;
+    private final String description;
+    private final EnumOptionData periodType;
+    private final Integer fromPeriod;
+    private final Integer toPeriod;
+    private final BigDecimal amountRangeFrom;
+    private final BigDecimal amountRangeTo;
+    private final BigDecimal annualInterestRate;
+    private final CurrencyData currency;
+
+    // associations
+    private Collection<DepositAccountInterestIncentiveData> incentives;
+
+    // template
+    @SuppressWarnings("unused")
+    private final Collection<EnumOptionData> periodTypes;
+    @SuppressWarnings("unused")
+    private final Collection<EnumOptionData> entityTypeOptions;
+    @SuppressWarnings("unused")
+    private final Collection<EnumOptionData> attributeNameOptions;
+    @SuppressWarnings("unused")
+    private final Collection<EnumOptionData> conditionTypeOptions;
+    @SuppressWarnings("unused")
+    private final Collection<EnumOptionData> incentiveTypeOptions;
+    @SuppressWarnings("unused")
+    private final Collection<CodeValueData> genderOptions;
+    @SuppressWarnings("unused")
+    private final Collection<CodeValueData> clientTypeOptions;
+    @SuppressWarnings("unused")
+    private final Collection<CodeValueData> clientClassificationOptions;
+
+    public static DepositAccountInterestRateChartSlabData instance(final Long id, final String description,
+            final EnumOptionData periodType, final Integer fromPeriod, final Integer toPeriod, final BigDecimal amountRangeFrom,
+            final BigDecimal amountRangeTo, final BigDecimal annualInterestRate, final CurrencyData currency) {
+        final Collection<EnumOptionData> periodTypes = null;
+        final Collection<EnumOptionData> entityTypeOptions = null;
+        final Collection<EnumOptionData> attributeNameOptions = null;
+        final Collection<EnumOptionData> conditionTypeOptions = null;
+        final Collection<EnumOptionData> incentiveTypeOptions = null;
+        final Collection<CodeValueData> genderOptions = null;
+        final Collection<CodeValueData> clientTypeOptions = null;
+        final Collection<CodeValueData> clientClassificationOptions = null;
+        final Collection<DepositAccountInterestIncentiveData> incentives = null;
+        return new DepositAccountInterestRateChartSlabData(id, description, periodType, fromPeriod, toPeriod, amountRangeFrom,
+                amountRangeTo, annualInterestRate, incentives, currency, periodTypes, entityTypeOptions, attributeNameOptions,
+                conditionTypeOptions, incentiveTypeOptions, genderOptions, clientTypeOptions, clientClassificationOptions);
+    }
+
+    public static DepositAccountInterestRateChartSlabData from(final InterestRateChartSlabData chartSlabData) {
+        final Long id = null;
+        Set<DepositAccountInterestIncentiveData> fromProdIncentives = new HashSet<>();
+        Set<InterestIncentiveData> productIncentiveData = chartSlabData.incentives();
+        if (productIncentiveData != null) {
+            for (InterestIncentiveData incentive : productIncentiveData) {
+                fromProdIncentives.add(DepositAccountInterestIncentiveData.from(incentive));
+            }
+        }
+        return new DepositAccountInterestRateChartSlabData(id, chartSlabData.description(), chartSlabData.periodType(),
+                chartSlabData.fromPeriod(), chartSlabData.toPeriod(), chartSlabData.amountRangeFrom(), chartSlabData.amountRangeTo(),
+                chartSlabData.annualInterestRate(), fromProdIncentives, chartSlabData.currency(), chartSlabData.periodTypes(),
+                chartSlabData.entityTypeOptions(), chartSlabData.attributeNameOptions(), chartSlabData.conditionTypeOptions(),
+                chartSlabData.incentiveTypeOptions(), chartSlabData.genderOptions(), chartSlabData.clientTypeOptions(),
+                chartSlabData.clientClassificationOptions());
+    }
+
+    public static DepositAccountInterestRateChartSlabData withTemplate(final DepositAccountInterestRateChartSlabData chartSlab,
+            final Collection<EnumOptionData> periodTypes, final Collection<EnumOptionData> entityTypeOptions,
+            final Collection<EnumOptionData> attributeNameOptions, final Collection<EnumOptionData> conditionTypeOptions,
+            final Collection<EnumOptionData> incentiveTypeOptions, final Collection<CodeValueData> genderOptions,
+            final Collection<CodeValueData> clientTypeOptions, final Collection<CodeValueData> clientClassificationOptions) {
+        return new DepositAccountInterestRateChartSlabData(chartSlab.id, chartSlab.description, chartSlab.periodType, chartSlab.fromPeriod,
+                chartSlab.toPeriod, chartSlab.amountRangeFrom, chartSlab.amountRangeTo, chartSlab.annualInterestRate, chartSlab.incentives,
+                chartSlab.currency, periodTypes, entityTypeOptions, attributeNameOptions, conditionTypeOptions, incentiveTypeOptions,
+                genderOptions, clientTypeOptions, clientClassificationOptions);
+    }
+
+    private DepositAccountInterestRateChartSlabData(final Long id, final String description, final EnumOptionData periodType,
+            final Integer fromPeriod, final Integer toPeriod, final BigDecimal amountRangeFrom, final BigDecimal amountRangeTo,
+            final BigDecimal annualInterestRate, final Collection<DepositAccountInterestIncentiveData> incentivesData,
+            final CurrencyData currency, final Collection<EnumOptionData> periodTypes, final Collection<EnumOptionData> entityTypeOptions,
+            final Collection<EnumOptionData> attributeNameOptions, final Collection<EnumOptionData> conditionTypeOptions,
+            final Collection<EnumOptionData> incentiveTypeOptions, final Collection<CodeValueData> genderOptions,
+            final Collection<CodeValueData> clientTypeOptions, final Collection<CodeValueData> clientClassificationOptions) {
+        this.id = id;
+        this.description = description;
+        this.periodType = periodType;
+        this.fromPeriod = fromPeriod;
+        this.toPeriod = toPeriod;
+        this.amountRangeFrom = amountRangeFrom;
+        this.amountRangeTo = amountRangeTo;
+        this.annualInterestRate = annualInterestRate;
+        this.currency = currency;
+        this.periodTypes = periodTypes;
+        this.incentives = incentivesData;
+        this.attributeNameOptions = attributeNameOptions;
+        this.entityTypeOptions = entityTypeOptions;
+        this.conditionTypeOptions = conditionTypeOptions;
+        this.incentiveTypeOptions = incentiveTypeOptions;
+        this.genderOptions = genderOptions;
+        this.clientTypeOptions = clientTypeOptions;
+        this.clientClassificationOptions = clientClassificationOptions;
+    }
+
+    public void addIncentives(final DepositAccountInterestIncentiveData incentive) {
+        if (this.incentives == null) {
+            this.incentives = new HashSet<>();
+        }
+        this.incentives.add(incentive);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountOnHoldTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountOnHoldTransactionData.java
new file mode 100755
index 0000000..16df44b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountOnHoldTransactionData.java
@@ -0,0 +1,94 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.joda.time.LocalDate;
+
+public class DepositAccountOnHoldTransactionData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final BigDecimal amount;
+    @SuppressWarnings("unused")
+    private EnumOptionData transactionType;
+    @SuppressWarnings("unused")
+    private final LocalDate transactionDate;
+    @SuppressWarnings("unused")
+    private final boolean reversed;
+    @SuppressWarnings("unused")
+    private final Long savingsId;
+    @SuppressWarnings("unused")
+    private final String savingsAccountNo;
+    @SuppressWarnings("unused")
+    private final String savingsClientName;
+    @SuppressWarnings("unused")
+    private final Long loanId;
+    @SuppressWarnings("unused")
+    private final String loanAccountNo;
+    @SuppressWarnings("unused")
+    private final String loanClientName;
+
+    private DepositAccountOnHoldTransactionData(final Long id, final BigDecimal amount, final EnumOptionData transactionType,
+            final LocalDate transactionDate, final boolean reversed, final Long savingsId, final String savingsAccNo,
+            final String savingsClientName, final Long loanId, final String loanAccNo, final String loanClientName) {
+        this.id = id;
+        this.amount = amount;
+        this.transactionType = transactionType;
+        this.transactionDate = transactionDate;
+        this.reversed = reversed;
+        this.savingsId = savingsId;
+        this.savingsAccountNo = savingsAccNo;
+        this.savingsClientName = savingsClientName;
+        this.loanId = loanId;
+        this.loanAccountNo = loanAccNo;
+        this.loanClientName = loanClientName;
+    }
+
+    private DepositAccountOnHoldTransactionData(final Long id, final BigDecimal amount, final EnumOptionData transactionType,
+            final LocalDate transactionDate, final boolean reversed) {
+        this.id = id;
+        this.amount = amount;
+        this.transactionType = transactionType;
+        this.transactionDate = transactionDate;
+        this.reversed = reversed;
+        this.savingsAccountNo = null;
+        this.savingsId = 0L;
+        this.savingsClientName = null;
+        this.loanId = 0L;
+        this.loanAccountNo = null;
+        this.loanClientName = null;
+    }
+
+    public static DepositAccountOnHoldTransactionData instance(final Long id, final BigDecimal amount,
+            final EnumOptionData transactionType, final LocalDate transactionDate, final boolean reversed, final Long savingsId,
+            final String savingsAccountNo, final String savingsClientName, final Long loanId, final String loanAccountNo,
+            final String loanClientName) {
+        return new DepositAccountOnHoldTransactionData(id, amount, transactionType, transactionDate, reversed, savingsId, savingsAccountNo,
+                savingsClientName, loanId, loanAccountNo, loanClientName);
+    }
+
+    public static DepositAccountOnHoldTransactionData instance(Long transactionId, BigDecimal amount, EnumOptionData transactionType,
+            LocalDate date, boolean transactionReversed) {
+        return new DepositAccountOnHoldTransactionData(transactionId, amount, transactionType, date, transactionReversed);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountTransactionDataValidator.java
new file mode 100644
index 0000000..7353083
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountTransactionDataValidator.java
@@ -0,0 +1,233 @@
+/**
+ * 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.savings.data;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.activatedOnDateParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.bankNumberParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.checkNumberParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.closedOnDateParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.paymentTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.receiptNumberParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.routingCodeParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.toSavingsAccountIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAccountNumberParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionDateParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+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.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class DepositAccountTransactionDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public DepositAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validate(final JsonCommand command, DepositAccountType depositAccountType) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                DepositsApiConstants.DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(depositAccountType
+                .resourceName());
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
+        baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull();
+
+        final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element);
+        baseDataValidator.reset().parameter(transactionAmountParamName).value(transactionAmount).notNull().positiveAmount();
+
+        // Validate all string payment detail fields for max length
+        final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
+        baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
+        final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
+                routingCodeParamName, receiptNumberParamName, bankNumberParamName));
+        for (final String paymentDetailParameterName : paymentDetailParameters) {
+            final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
+            baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateDepositAmountUpdate(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                DepositsApiConstants.DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate effectiveDate = this.fromApiJsonHelper.extractLocalDateNamed(DepositsApiConstants.effectiveDateParamName, element);
+        baseDataValidator.reset().parameter(DepositsApiConstants.effectiveDateParamName).value(effectiveDate).notNull();
+
+        final BigDecimal mandatoryRecommendedDepositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                DepositsApiConstants.mandatoryRecommendedDepositAmountParamName, element);
+        baseDataValidator.reset().parameter(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName)
+                .value(mandatoryRecommendedDepositAmount).notNull().positiveAmount();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateActivation(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                SavingsApiConstants.SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(activatedOnDateParamName, element);
+        baseDataValidator.reset().parameter(activatedOnDateParamName).value(activationDate).notNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validatePreMatureAmountCalculation(final String json, final DepositAccountType depositAccountType) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                DepositsApiConstants.DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(depositAccountType
+                .resourceName());
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final LocalDate closeDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
+        baseDataValidator.reset().parameter(closedOnDateParamName).value(closeDate).notNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateClosing(final JsonCommand command, DepositAccountType depositAccountType, final boolean isPreMatureClose) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                DepositsApiConstants.DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(depositAccountType
+                .resourceName());
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
+        baseDataValidator.reset().parameter(closedOnDateParamName).value(activationDate).notNull();
+
+        final Integer onAccountClosureId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(onAccountClosureIdParamName, element);
+        baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).notBlank()
+                .isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
+
+        if (onAccountClosureId != null) {
+            final DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
+            if (accountOnClosureType.isTransferToSavings()) {
+                final Long toSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(toSavingsAccountIdParamName, element);
+                baseDataValidator
+                        .reset()
+                        .parameter(toSavingsAccountIdParamName)
+                        .value(toSavingsAccountId)
+                        .cantBeBlankWhenParameterProvidedIs(onAccountClosureIdParamName,
+                                DepositAccountOnClosureType.fromInt(onAccountClosureId).getCode());
+            } else if (accountOnClosureType.isReinvest() && isPreMatureClose) {
+                baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId)
+                        .failWithCode("reinvest.not.allowed", "Re-Invest is not supported for account pre mature close");
+            }
+        }
+
+        // Validate all string payment detail fields for max length
+        final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
+        baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
+        final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
+                routingCodeParamName, receiptNumberParamName, bankNumberParamName));
+        for (final String paymentDetailParameterName : paymentDetailParameters) {
+            final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
+            baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            //
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductData.java
new file mode 100644
index 0000000..c388df0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductData.java
@@ -0,0 +1,389 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+
+/**
+ * Immutable data object represents a partial data of Deposit Proucts.
+ */
+public class DepositProductData {
+
+    protected final Long id;
+    protected final String name;
+    protected final String shortName;
+    protected final String description;
+    protected final CurrencyData currency;
+    protected final BigDecimal nominalAnnualInterestRate;
+    protected final EnumOptionData interestCompoundingPeriodType;
+    protected final EnumOptionData interestPostingPeriodType;
+    protected final EnumOptionData interestCalculationType;
+    protected final EnumOptionData interestCalculationDaysInYearType;
+    // protected final BigDecimal minRequiredOpeningBalance;
+    protected final Integer lockinPeriodFrequency;
+    protected final EnumOptionData lockinPeriodFrequencyType;
+    // protected final boolean withdrawalFeeForTransfers;
+    protected final BigDecimal minBalanceForInterestCalculation;
+
+    // accounting
+    protected final EnumOptionData accountingRule;
+    protected final Map<String, Object> accountingMappings;
+    protected final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings;
+    protected final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
+    protected final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
+
+    // charges
+    protected final Collection<ChargeData> charges;
+
+    // interest rate charts
+    protected final Collection<InterestRateChartData> interestRateCharts;
+    protected final InterestRateChartData activeChart;
+
+    // template
+    protected final Collection<CurrencyData> currencyOptions;
+    protected final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions;
+    protected final Collection<EnumOptionData> interestPostingPeriodTypeOptions;
+    protected final Collection<EnumOptionData> interestCalculationTypeOptions;
+    protected final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions;
+    protected final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions;
+    protected final Collection<EnumOptionData> withdrawalFeeTypeOptions;
+    protected final Collection<PaymentTypeData> paymentTypeOptions;
+    protected final Collection<EnumOptionData> accountingRuleOptions;
+    protected final Map<String, List<GLAccountData>> accountingMappingOptions;
+    protected final Collection<ChargeData> chargeOptions;
+    protected final Collection<ChargeData> penaltyOptions;
+    protected final InterestRateChartData chartTemplate;
+
+    public static DepositProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
+            final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate) {
+
+        final Long id = null;
+        final String name = null;
+        final String shortName = null;
+        final String description = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final Collection<InterestRateChartData> interestRateCharts = null;
+
+        return new DepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, accountingRule, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions,
+                interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
+                accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation);
+    }
+
+    public static DepositProductData withCharges(final DepositProductData existingProduct, final Collection<ChargeData> charges) {
+        return new DepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
+                existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
+                existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
+                existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency,
+                existingProduct.lockinPeriodFrequencyType, existingProduct.accountingRule, existingProduct.accountingMappings,
+                existingProduct.paymentChannelToFundSourceMappings, existingProduct.currencyOptions,
+                existingProduct.interestCompoundingPeriodTypeOptions, existingProduct.interestPostingPeriodTypeOptions,
+                existingProduct.interestCalculationTypeOptions, existingProduct.interestCalculationDaysInYearTypeOptions,
+                existingProduct.lockinPeriodFrequencyTypeOptions, existingProduct.withdrawalFeeTypeOptions,
+                existingProduct.paymentTypeOptions, existingProduct.accountingRuleOptions, existingProduct.accountingMappingOptions,
+                charges, existingProduct.chargeOptions, existingProduct.penaltyOptions, existingProduct.feeToIncomeAccountMappings,
+                existingProduct.penaltyToIncomeAccountMappings, existingProduct.interestRateCharts, existingProduct.chartTemplate,
+                existingProduct.minBalanceForInterestCalculation);
+    }
+
+    /**
+     * Returns a {@link DepositProductData} that contains and exist
+     * {@link DepositProductData} data with further template data for dropdowns.
+     */
+    public static DepositProductData withTemplate(final DepositProductData existingProduct, final Collection<CurrencyData> currencyOptions,
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
+            final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate) {
+
+        return new DepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
+                existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
+                existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
+                existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency,
+                existingProduct.lockinPeriodFrequencyType, existingProduct.accountingRule, existingProduct.accountingMappings,
+                existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions,
+                existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings,
+                existingProduct.interestRateCharts, chartTemplate, existingProduct.minBalanceForInterestCalculation);
+    }
+
+    public static DepositProductData withAccountingDetails(final DepositProductData existingProduct,
+            final Map<String, Object> accountingMappings,
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
+            final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
+            final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings) {
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+
+        return new DepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
+                existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
+                existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
+                existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency,
+                existingProduct.lockinPeriodFrequencyType, existingProduct.accountingRule, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, existingProduct.interestRateCharts, existingProduct.chartTemplate,
+                existingProduct.minBalanceForInterestCalculation);
+    }
+
+    public static DepositProductData instance(final Long id, final String name, final String shortName, final String description,
+            final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final Integer lockinPeriodFrequency,
+            final EnumOptionData lockinPeriodFrequencyType, final EnumOptionData accountingType,
+            final BigDecimal minBalanceForInterestCalculation) {
+
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final Collection<InterestRateChartData> interestRateCharts = null;
+        final InterestRateChartData chartTemplate = null;
+
+        return new DepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, accountingType, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions,
+                interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
+                accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation);
+    }
+
+    public static DepositProductData lookup(final Long id, final String name) {
+
+        final String shortName = null;
+        final CurrencyData currency = null;
+        final String description = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestCompoundingPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final BigDecimal minBalanceForInterestCalculation = null;
+
+        final EnumOptionData accountingType = null;
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final Collection<InterestRateChartData> interestRateCharts = null;
+        final InterestRateChartData chartTemplate = null;
+
+        return new DepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, accountingType, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions,
+                interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
+                accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation);
+    }
+
+    public static DepositProductData withInterestChart(final DepositProductData existingProduct,
+            final Collection<InterestRateChartData> interestRateCharts) {
+        return new DepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
+                existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
+                existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
+                existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency,
+                existingProduct.lockinPeriodFrequencyType, existingProduct.accountingRule, existingProduct.accountingMappings,
+                existingProduct.paymentChannelToFundSourceMappings, existingProduct.currencyOptions,
+                existingProduct.interestCompoundingPeriodTypeOptions, existingProduct.interestPostingPeriodTypeOptions,
+                existingProduct.interestCalculationTypeOptions, existingProduct.interestCalculationDaysInYearTypeOptions,
+                existingProduct.lockinPeriodFrequencyTypeOptions, existingProduct.withdrawalFeeTypeOptions,
+                existingProduct.paymentTypeOptions, existingProduct.accountingRuleOptions, existingProduct.accountingMappingOptions,
+                existingProduct.charges, existingProduct.chargeOptions, existingProduct.penaltyOptions,
+                existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, interestRateCharts,
+                existingProduct.chartTemplate, existingProduct.minBalanceForInterestCalculation);
+    }
+
+    protected DepositProductData(final Long id, final String name, final String shortName, final String description,
+            final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final Integer lockinPeriodFrequency,
+            final EnumOptionData lockinPeriodFrequencyType, final EnumOptionData accountingType,
+            final Map<String, Object> accountingMappings,
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> charges,
+            final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions,
+            final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
+            final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings,
+            final Collection<InterestRateChartData> interestRateCharts, final InterestRateChartData chartTemplate,
+            final BigDecimal minBalanceForInterestCalculation) {
+
+        this.id = id;
+        this.name = name;
+        this.shortName = shortName;
+        this.description = description;
+        this.currency = currency;
+        this.nominalAnnualInterestRate = nominalAnnualInterestRate;
+        this.interestCompoundingPeriodType = interestCompoundingPeriodType;
+        this.interestPostingPeriodType = interestPostingPeriodType;
+        this.interestCalculationType = interestCalculationType;
+        this.interestCalculationDaysInYearType = interestCalculationDaysInYearType;
+        this.accountingRule = accountingType;
+        this.lockinPeriodFrequency = lockinPeriodFrequency;
+        this.lockinPeriodFrequencyType = lockinPeriodFrequencyType;
+
+        this.currencyOptions = currencyOptions;
+        this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions;
+        this.interestPostingPeriodTypeOptions = interestPostingPeriodTypeOptions;
+        this.interestCalculationTypeOptions = interestCalculationTypeOptions;
+        this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions;
+        this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions;
+        this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions;
+
+        this.paymentTypeOptions = paymentTypeOptions;
+        this.accountingMappingOptions = accountingMappingOptions;
+        this.accountingRuleOptions = accountingRuleOptions;
+        if (accountingMappings == null || accountingMappings.isEmpty()) {
+            this.accountingMappings = null;
+        } else {
+            this.accountingMappings = accountingMappings;
+        }
+        this.paymentChannelToFundSourceMappings = paymentChannelToFundSourceMappings;
+
+        this.charges = charges;// charges associated with Savings product
+        this.chargeOptions = chargeOptions;// charges available for adding to
+                                           // Savings product
+        this.penaltyOptions = penaltyOptions;// penalties available for adding
+                                             // to Savings product
+
+        this.feeToIncomeAccountMappings = feeToIncomeAccountMappings;
+        this.penaltyToIncomeAccountMappings = penaltyToIncomeAccountMappings;
+        this.interestRateCharts = interestRateCharts;
+        this.activeChart = activeChart(this.interestRateCharts);
+        this.chartTemplate = chartTemplate;
+        this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
+    }
+
+    public static InterestRateChartData activeChart(Collection<InterestRateChartData> interestRateCharts) {
+        InterestRateChartData activeChart = null;
+        if (interestRateCharts != null) {
+            for (InterestRateChartData chartData : interestRateCharts) {
+                if (activeChart == null) {
+                    activeChart = chartData;
+                } else {
+                    if (!activeChart.isFromDateAfter(chartData.endDate())) {
+                        activeChart = chartData;
+                    }
+                }
+            }
+        }
+        return activeChart;
+    }
+
+    public boolean hasAccountingEnabled() {
+        return this.accountingRule.getId() > AccountingRuleType.NONE.getValue();
+    }
+
+    public int accountingRuleTypeId() {
+        return this.accountingRule.getId().intValue();
+    }
+
+    public String getName() {
+        return this.name;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java
new file mode 100644
index 0000000..8b0e426
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java
@@ -0,0 +1,781 @@
+/**
+ * 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.savings.data;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.idParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.adjustAdvanceTowardsFuturePaymentsParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.allowWithdrawalParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.chartsParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositMaxAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositMinAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalApplicableParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestOnTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeAmountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartDataValidator;
+import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class DepositProductDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final InterestRateChartDataValidator chartDataValidator;
+
+    @Autowired
+    public DepositProductDataValidator(FromJsonHelper fromApiJsonHelper, InterestRateChartDataValidator chartDataValidator) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.chartDataValidator = chartDataValidator;
+    }
+
+    public void validateForFixedDepositCreate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, FIXED_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateDepositDetailForCreate(element, this.fromApiJsonHelper, baseDataValidator);
+
+        validatePreClosureDetailForCreate(element, baseDataValidator);
+
+        validateDepositTermDeatilForCreate(element, baseDataValidator);
+
+        validateChartsData(element, baseDataValidator);
+
+        validateDepositAmountForCreate(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForFixedDepositUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, FIXED_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateDepositDetailForUpdate(element, fromApiJsonHelper, baseDataValidator);
+
+        validatePreClosureDetailForUpdate(element, baseDataValidator);
+
+        validateDepositTermDetailForUpdate(element, baseDataValidator);
+
+        validateChartsData(element, baseDataValidator);
+
+        validateDepositAmountForUpdate(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForRecurringDepositCreate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, RECURRING_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateDepositDetailForCreate(element, this.fromApiJsonHelper, baseDataValidator);
+
+        validatePreClosureDetailForCreate(element, baseDataValidator);
+
+        validateDepositTermDeatilForCreate(element, baseDataValidator);
+
+        validateRecurringDetailForCreate(element, baseDataValidator);
+
+        validateChartsData(element, baseDataValidator);
+
+        validateDepositAmountForCreate(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForRecurringDepositUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, RECURRING_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateDepositDetailForUpdate(element, fromApiJsonHelper, baseDataValidator);
+
+        validatePreClosureDetailForUpdate(element, baseDataValidator);
+
+        validateDepositTermDetailForUpdate(element, baseDataValidator);
+
+        validateRecurringDepositUpdate(element, baseDataValidator);
+
+        validateChartsData(element, baseDataValidator);
+
+        validateDepositAmountForUpdate(element, baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateDepositDetailForCreate(final JsonElement element, final FromJsonHelper fromApiJsonHelper,
+            final DataValidatorBuilder baseDataValidator) {
+        final String name = fromApiJsonHelper.extractStringNamed(nameParamName, element);
+        baseDataValidator.reset().parameter(nameParamName).value(name).notBlank().notExceedingLengthOf(100);
+
+        final String shortName = fromApiJsonHelper.extractStringNamed(shortNameParamName, element);
+        baseDataValidator.reset().parameter(shortNameParamName).value(shortName).notBlank().notExceedingLengthOf(4);
+
+        final String description = fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+        baseDataValidator.reset().parameter(descriptionParamName).value(description).notBlank().notExceedingLengthOf(500);
+
+        final String currencyCode = fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
+        baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank();
+
+        final Integer digitsAfterDecimal = fromApiJsonHelper.extractIntegerSansLocaleNamed(digitsAfterDecimalParamName, element);
+        baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
+
+        if (fromApiJsonHelper.parameterExists(inMultiplesOfParamName, element)) {
+            final Integer inMultiplesOf = fromApiJsonHelper.extractIntegerNamed(inMultiplesOfParamName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
+        }
+
+        if (fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
+            final BigDecimal nominalAnnualInterestRate = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    nominalAnnualInterestRateParamName, element);
+            baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate).notNull()
+                    .zeroOrPositiveAmount();
+        }
+        final Integer interestCompoundingPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                interestCompoundingPeriodTypeParamName, element);
+        baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
+                .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
+
+        final Integer interestPostingPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestPostingPeriodTypeParamName,
+                element);
+        baseDataValidator.reset().parameter(interestPostingPeriodTypeParamName).value(interestPostingPeriodType).notNull()
+                .isOneOfTheseValues(SavingsPostingInterestPeriodType.integerValues());
+
+        final Integer interestCalculationType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName, element);
+        baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull()
+                .isOneOfTheseValues(SavingsInterestCalculationType.integerValues());
+
+        final Integer interestCalculationDaysInYearType = fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                interestCalculationDaysInYearTypeParamName, element);
+        baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType).notNull()
+                .isOneOfTheseValues(SavingsInterestCalculationDaysInYearType.integerValues());
+
+        /*
+         * if
+         * (fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName
+         * , element)) { final BigDecimal minOpeningBalance =
+         * fromApiJsonHelper.extractBigDecimalWithLocaleNamed
+         * (minRequiredOpeningBalanceParamName, element);
+         * baseDataValidator.reset
+         * ().parameter(minRequiredOpeningBalanceParamName
+         * ).value(minOpeningBalance).zeroOrPositiveAmount(); }
+         */
+
+        if (fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
+
+            final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).integerZeroOrGreater();
+
+            if (lockinPeriodFrequency != null) {
+                final Integer lockinPeriodFrequencyType = fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                        lockinPeriodFrequencyTypeParamName, element);
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull()
+                        .inMinMaxRange(0, 3);
+            }
+        }
+
+        if (fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
+            final Integer lockinPeriodFrequencyType = fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName,
+                    element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
+
+            if (lockinPeriodFrequencyType != null) {
+                final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                        element);
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull()
+                        .integerZeroOrGreater();
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
+            final BigDecimal minBalanceForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minBalanceForInterestCalculationParamName, element);
+            baseDataValidator.reset().parameter(minBalanceForInterestCalculationParamName).value(minBalanceForInterestCalculation)
+                    .ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        // accounting related data validation
+        final Integer accountingRuleType = fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
+
+        if (isCashBasedAccounting(accountingRuleType)) {
+
+            final Long savingsControlAccountId = fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue())
+                    .value(savingsControlAccountId).notNull().integerGreaterThanZero();
+
+            final Long savingsReferenceAccountId = fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue())
+                    .value(savingsReferenceAccountId).notNull().integerGreaterThanZero();
+
+            final Long transfersInSuspenseAccountId = fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue())
+                    .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
+
+            final Long interestOnSavingsAccountId = fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue())
+                    .value(interestOnSavingsAccountId).notNull().integerGreaterThanZero();
+
+            final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
+                    element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromPenaltyId = fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue())
+                    .value(incomeFromPenaltyId).notNull().integerGreaterThanZero();
+
+            validatePaymentChannelFundSourceMappings(fromApiJsonHelper, baseDataValidator, element);
+            validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element);
+        }
+    }
+
+    public void validatePreClosureDetailForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) {
+        if (fromApiJsonHelper.parameterExists(preClosurePenalApplicableParamName, element)) {
+            final boolean preClosurePenalApplicable = fromApiJsonHelper.extractBooleanNamed(preClosurePenalApplicableParamName, element);
+
+            if (preClosurePenalApplicable) {
+                final BigDecimal penalInterestRate = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(preClosurePenalInterestParamName,
+                        element);
+                baseDataValidator.reset().parameter(preClosurePenalInterestParamName).value(penalInterestRate)
+                        .cantBeBlankWhenParameterProvidedIs(preClosurePenalApplicableParamName, preClosurePenalApplicable)
+                        .zeroOrPositiveAmount();
+
+                final Integer preClosurePenalInterestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                        preClosurePenalInterestOnTypeIdParamName, element);
+                baseDataValidator.reset().parameter(preClosurePenalInterestOnTypeIdParamName).value(preClosurePenalInterestType)
+                        .cantBeBlankWhenParameterProvidedIs(preClosurePenalApplicableParamName, preClosurePenalApplicable)
+                        .isOneOfTheseValues(PreClosurePenalInterestOnType.integerValues());
+            }
+        }
+    }
+
+    public void validateDepositTermDeatilForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) {
+
+        final Integer minTerm = fromApiJsonHelper.extractIntegerSansLocaleNamed(minDepositTermParamName, element);
+        baseDataValidator.reset().parameter(minDepositTermParamName).value(minTerm).notNull().integerGreaterThanZero();
+
+        if (fromApiJsonHelper.parameterExists(maxDepositTermParamName, element)) {
+            final Integer maxTerm = fromApiJsonHelper.extractIntegerSansLocaleNamed(maxDepositTermParamName, element);
+            baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm).integerGreaterThanZero();
+        }
+
+        final Integer minDepositTermType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(minDepositTermTypeIdParamName, element);
+        baseDataValidator.reset().parameter(minDepositTermTypeIdParamName).value(minDepositTermType).ignoreIfNull()
+                .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+
+        if (fromApiJsonHelper.parameterExists(maxDepositTermTypeIdParamName, element)) {
+            final Integer maxDepositTermType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(maxDepositTermTypeIdParamName, element);
+            baseDataValidator.reset().parameter(maxDepositTermTypeIdParamName).value(maxDepositTermType)
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+
+        if (fromApiJsonHelper.parameterExists(inMultiplesOfDepositTermParamName, element)) {
+            final Integer inMultiplesOfDepositTerm = fromApiJsonHelper.extractIntegerSansLocaleNamed(inMultiplesOfDepositTermParamName,
+                    element);
+            baseDataValidator.reset().parameter(inMultiplesOfDepositTermParamName).value(inMultiplesOfDepositTerm).integerGreaterThanZero();
+            final Integer inMultiplesOfDepositTermType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    inMultiplesOfDepositTermTypeIdParamName, element);
+            baseDataValidator.reset().parameter(inMultiplesOfDepositTermTypeIdParamName).value(inMultiplesOfDepositTermType)
+                    .cantBeBlankWhenParameterProvidedIs(inMultiplesOfDepositTermParamName, inMultiplesOfDepositTerm)
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+    }
+
+    private void validateChartsData(JsonElement element, DataValidatorBuilder baseDataValidator) {
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            if (topLevelJsonElement.has(chartsParamName) && topLevelJsonElement.get(chartsParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(chartsParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject interestRateChartElement = array.get(i).getAsJsonObject();
+                    final String json = this.fromApiJsonHelper.toJson(interestRateChartElement);
+                    // chart for create
+                    if (!this.fromApiJsonHelper.parameterExists(idParamName, interestRateChartElement)) {
+                        this.chartDataValidator.validateForCreate(json, baseDataValidator);
+                    } else { // chart for update
+                        this.chartDataValidator.validateForUpdate(json, baseDataValidator);
+                    }
+                }
+            }
+        }
+    }
+
+    public void validateDepositDetailForUpdate(final JsonElement element, final FromJsonHelper fromApiJsonHelper,
+            final DataValidatorBuilder baseDataValidator) {
+        if (fromApiJsonHelper.parameterExists(nameParamName, element)) {
+            final String name = fromApiJsonHelper.extractStringNamed(nameParamName, element);
+            baseDataValidator.reset().parameter(nameParamName).value(name).notBlank().notExceedingLengthOf(100);
+        }
+
+        if (fromApiJsonHelper.parameterExists(shortNameParamName, element)) {
+            final String shortName = fromApiJsonHelper.extractStringNamed(shortNameParamName, element);
+            baseDataValidator.reset().parameter(shortNameParamName).value(shortName).notBlank().notExceedingLengthOf(4);
+        }
+
+        if (fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
+            final String description = fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+            baseDataValidator.reset().parameter(descriptionParamName).value(description).notBlank().notExceedingLengthOf(500);
+        }
+
+        if (fromApiJsonHelper.parameterExists(currencyCodeParamName, element)) {
+            final String currencyCode = fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
+            baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank();
+        }
+
+        if (fromApiJsonHelper.parameterExists(digitsAfterDecimalParamName, element)) {
+            final Integer digitsAfterDecimal = fromApiJsonHelper.extractIntegerSansLocaleNamed(digitsAfterDecimalParamName, element);
+            baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
+        }
+
+        if (fromApiJsonHelper.parameterExists(inMultiplesOfParamName, element)) {
+            final Integer inMultiplesOf = fromApiJsonHelper.extractIntegerNamed(inMultiplesOfParamName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
+        }
+
+        if (fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
+            final BigDecimal interestRate = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName, element);
+            baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(interestRate).notNull().zeroOrPositiveAmount();
+        }
+
+        if (fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
+            final Integer interestCompoundingPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCompoundingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
+        }
+
+        if (fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
+            final Integer interestCalculationType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName,
+                    element);
+            baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull()
+                    .inMinMaxRange(1, 2);
+        }
+
+        if (fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
+            final Integer interestCalculationDaysInYearType = fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCalculationDaysInYearTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType)
+                    .notNull().isOneOfTheseValues(360, 365);
+        }
+
+        if (fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
+            final BigDecimal minOpeningBalance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName,
+                    element);
+            baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).ignoreIfNull()
+                    .zeroOrPositiveAmount();
+        }
+
+        if (fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
+            final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull()
+                    .integerZeroOrGreater();
+        }
+
+        if (fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
+            final Integer lockinPeriodFrequencyType = fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName,
+                    element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
+        }
+
+        if (fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
+            final Boolean isWithdrawalFeeApplicableForTransfers = fromApiJsonHelper.extractBooleanNamed(withdrawalFeeForTransfersParamName,
+                    element);
+            baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers)
+                    .ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (fromApiJsonHelper.parameterExists(feeAmountParamName, element)) {
+            final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element);
+            baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        if (fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
+            final MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
+            baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
+            final BigDecimal minBalanceForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minBalanceForInterestCalculationParamName, element);
+            baseDataValidator.reset().parameter(minBalanceForInterestCalculationParamName).value(minBalanceForInterestCalculation)
+                    .ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        final Long savingsControlAccountId = fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue()).value(savingsControlAccountId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long savingsReferenceAccountId = fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue())
+                .value(savingsReferenceAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long transfersInSuspenseAccountId = fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue())
+                .value(transfersInSuspenseAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long interestOnSavingsAccountId = fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue())
+                .value(interestOnSavingsAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
+                element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromPenaltyId = fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        validatePaymentChannelFundSourceMappings(fromApiJsonHelper, baseDataValidator, element);
+        validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element);
+    }
+
+    public void validatePreClosureDetailForUpdate(JsonElement element, DataValidatorBuilder baseDataValidator) {
+        if (fromApiJsonHelper.parameterExists(preClosurePenalApplicableParamName, element)) {
+            final Boolean preClosurePenalApplicable = fromApiJsonHelper.extractBooleanNamed(preClosurePenalApplicableParamName, element);
+            baseDataValidator.reset().parameter(preClosurePenalApplicableParamName).value(preClosurePenalApplicable).notNull();
+        }
+
+        if (fromApiJsonHelper.parameterExists(preClosurePenalInterestParamName, element)) {
+            final BigDecimal penalInterestRate = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(preClosurePenalInterestParamName,
+                    element);
+            baseDataValidator.reset().parameter(preClosurePenalInterestParamName).value(penalInterestRate).notNull().zeroOrPositiveAmount();
+        }
+
+        if (fromApiJsonHelper.parameterExists(preClosurePenalInterestOnTypeIdParamName, element)) {
+            final Integer preClosurePenalInterestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    preClosurePenalInterestOnTypeIdParamName, element);
+            baseDataValidator.reset().parameter(preClosurePenalInterestOnTypeIdParamName).value(preClosurePenalInterestType).notNull()
+                    .isOneOfTheseValues(PreClosurePenalInterestOnType.integerValues());
+        }
+    }
+
+    public void validateDepositTermDetailForUpdate(JsonElement element, DataValidatorBuilder baseDataValidator) {
+
+        if (fromApiJsonHelper.parameterExists(minDepositTermParamName, element)) {
+            final Integer minTerm = fromApiJsonHelper.extractIntegerSansLocaleNamed(minDepositTermParamName, element);
+            baseDataValidator.reset().parameter(minDepositTermParamName).value(minTerm).integerGreaterThanZero();
+        }
+
+        if (fromApiJsonHelper.parameterExists(maxDepositTermParamName, element)) {
+            final Integer maxTerm = fromApiJsonHelper.extractIntegerSansLocaleNamed(maxDepositTermParamName, element);
+            baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm).integerGreaterThanZero();
+        }
+
+        if (fromApiJsonHelper.parameterExists(minDepositTermTypeIdParamName, element)) {
+            final Integer minDepositTermType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(minDepositTermTypeIdParamName, element);
+            baseDataValidator.reset().parameter(minDepositTermTypeIdParamName).value(minDepositTermType)
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+
+        if (fromApiJsonHelper.parameterExists(maxDepositTermTypeIdParamName, element)) {
+            final Integer maxDepositTermType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(maxDepositTermTypeIdParamName, element);
+            baseDataValidator.reset().parameter(maxDepositTermTypeIdParamName).value(maxDepositTermType)
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+
+        if (fromApiJsonHelper.parameterExists(inMultiplesOfDepositTermParamName, element)) {
+            final Integer inMultiplesOfDepositTerm = fromApiJsonHelper.extractIntegerSansLocaleNamed(inMultiplesOfDepositTermParamName,
+                    element);
+            baseDataValidator.reset().parameter(inMultiplesOfDepositTermParamName).value(inMultiplesOfDepositTerm).integerGreaterThanZero();
+        }
+
+        if (fromApiJsonHelper.parameterExists(inMultiplesOfDepositTermTypeIdParamName, element)) {
+            final Integer inMultiplesOfDepositTermType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    inMultiplesOfDepositTermTypeIdParamName, element);
+            baseDataValidator.reset().parameter(inMultiplesOfDepositTermTypeIdParamName).value(inMultiplesOfDepositTermType)
+                    .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
+        }
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private boolean isCashBasedAccounting(final Integer accountingRuleType) {
+        return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType);
+    }
+
+    /**
+     * Validation for advanced accounting options
+     */
+    private void validatePaymentChannelFundSourceMappings(final FromJsonHelper fromApiJsonHelper,
+            final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        if (fromApiJsonHelper.parameterExists(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element)) {
+            final JsonArray paymentChannelMappingArray = fromApiJsonHelper.extractJsonArrayNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
+            if (paymentChannelMappingArray != null && paymentChannelMappingArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject();
+                    final Long paymentTypeId = jsonObject.get(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).getAsLong();
+                    final Long paymentSpecificFundAccountId = jsonObject.get(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue())
+                            .getAsLong();
+                    baseDataValidator
+                            .reset()
+                            .parameter(
+                                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
+                                            + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.toString()).value(paymentTypeId).notNull()
+                            .integerGreaterThanZero();
+                    baseDataValidator
+                            .reset()
+                            .parameter(
+                                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
+                                            + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(paymentSpecificFundAccountId)
+                            .notNull().integerGreaterThanZero();
+                    i++;
+                } while (i < paymentChannelMappingArray.size());
+            }
+        }
+    }
+
+    private void validateChargeToIncomeAccountMappings(final FromJsonHelper fromApiJsonHelper,
+            final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        // validate for both fee and penalty charges
+        validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element, true);
+        validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element, true);
+    }
+
+    private void validateChargeToIncomeAccountMappings(final FromJsonHelper fromApiJsonHelper,
+            final DataValidatorBuilder baseDataValidator, final JsonElement element, final boolean isPenalty) {
+        String parameterName;
+        if (isPenalty) {
+            parameterName = SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
+        } else {
+            parameterName = SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue();
+        }
+
+        if (fromApiJsonHelper.parameterExists(parameterName, element)) {
+            final JsonArray chargeToIncomeAccountMappingArray = fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
+            if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
+                    final Long chargeId = fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue(),
+                            jsonObject);
+                    final Long incomeAccountId = fromApiJsonHelper.extractLongNamed(
+                            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(), jsonObject);
+                    baseDataValidator.reset()
+                            .parameter(parameterName + "[" + i + "]." + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue())
+                            .value(chargeId).notNull().integerGreaterThanZero();
+                    baseDataValidator.reset()
+                            .parameter(parameterName + "[" + i + "]." + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue())
+                            .value(incomeAccountId).notNull().integerGreaterThanZero();
+                    i++;
+                } while (i < chargeToIncomeAccountMappingArray.size());
+            }
+        }
+    }
+
+    public void validateRecurringDetailForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) {
+
+        final Boolean isMandatoryDeposit = this.fromApiJsonHelper.extractBooleanNamed(isMandatoryDepositParamName, element);
+        baseDataValidator.reset().parameter(isMandatoryDepositParamName).value(isMandatoryDeposit).ignoreIfNull().validateForBooleanValue();
+        final Boolean allowWithdrawal = this.fromApiJsonHelper.extractBooleanNamed(allowWithdrawalParamName, element);
+        baseDataValidator.reset().parameter(allowWithdrawalParamName).value(allowWithdrawal).ignoreIfNull().validateForBooleanValue();
+        final Boolean adjustAdvanceTowardsFuturePayments = this.fromApiJsonHelper.extractBooleanNamed(
+                adjustAdvanceTowardsFuturePaymentsParamName, element);
+        baseDataValidator.reset().parameter(adjustAdvanceTowardsFuturePaymentsParamName).value(adjustAdvanceTowardsFuturePayments)
+                .ignoreIfNull().validateForBooleanValue();
+    }
+
+    public void validateRecurringDepositUpdate(JsonElement element, DataValidatorBuilder baseDataValidator) {
+
+        if (this.fromApiJsonHelper.parameterExists(isMandatoryDepositParamName, element)) {
+            final Boolean isMandatoryDeposit = this.fromApiJsonHelper.extractBooleanNamed(isMandatoryDepositParamName, element);
+            baseDataValidator.reset().parameter(isMandatoryDepositParamName).value(isMandatoryDeposit).ignoreIfNull()
+                    .validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(allowWithdrawalParamName, element)) {
+            final Boolean allowWithdrawal = this.fromApiJsonHelper.extractBooleanNamed(allowWithdrawalParamName, element);
+            baseDataValidator.reset().parameter(allowWithdrawalParamName).value(allowWithdrawal).ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(adjustAdvanceTowardsFuturePaymentsParamName, element)) {
+            final Boolean adjustAdvanceTowardsFuturePayments = this.fromApiJsonHelper.extractBooleanNamed(
+                    adjustAdvanceTowardsFuturePaymentsParamName, element);
+            baseDataValidator.reset().parameter(adjustAdvanceTowardsFuturePaymentsParamName).value(adjustAdvanceTowardsFuturePayments)
+                    .ignoreIfNull().validateForBooleanValue();
+        }
+    }
+
+    private void validateDepositAmountForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) {
+        final BigDecimal depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
+        baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
+
+        BigDecimal depositMinAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(depositMinAmountParamName, element)) {
+            depositMinAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositMinAmountParamName, element);
+            baseDataValidator.reset().parameter(depositMinAmountParamName).value(depositMinAmount).notNull().positiveAmount();
+        }
+
+        BigDecimal depositMaxAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(depositMaxAmountParamName, element)) {
+            depositMaxAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositMaxAmountParamName, element);
+            baseDataValidator.reset().parameter(depositMaxAmountParamName).value(depositMaxAmount).notNull().positiveAmount();
+        }
+
+        if (depositMaxAmount != null && depositMaxAmount.compareTo(BigDecimal.ZERO) != -1) {
+            if (depositMinAmount != null && depositMinAmount.compareTo(BigDecimal.ZERO) != -1) {
+                baseDataValidator.reset().parameter(depositMaxAmountParamName).value(depositMaxAmount).notLessThanMin(depositMinAmount);
+                if (depositMinAmount.compareTo(depositMaxAmount) <= 0) {
+                    baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount)
+                            .inMinAndMaxAmountRange(depositMinAmount, depositMaxAmount);
+                }
+            } else {
+                baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notGreaterThanMax(depositMaxAmount);
+            }
+        } else if (depositMinAmount != null && depositMinAmount.compareTo(BigDecimal.ZERO) != -1) {
+            baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notLessThanMin(depositMinAmount);
+        }
+    }
+
+    private void validateDepositAmountForUpdate(JsonElement element, DataValidatorBuilder baseDataValidator) {
+        BigDecimal depositAmount = null;
+
+        if (this.fromApiJsonHelper.parameterExists(depositAmountParamName, element)) {
+            depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
+            baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
+        }
+
+        BigDecimal depositMinAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(depositMinAmountParamName, element)) {
+            depositMinAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositMinAmountParamName, element);
+            baseDataValidator.reset().parameter(depositMinAmountParamName).value(depositMinAmount).notNull().positiveAmount();
+        }
+
+        BigDecimal depositMaxAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(depositMaxAmountParamName, element)) {
+            depositMaxAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositMaxAmountParamName, element);
+            baseDataValidator.reset().parameter(depositMaxAmountParamName).value(depositMaxAmount).notNull().positiveAmount();
+        }
+
+        if (depositAmount != null) {
+            if (depositMaxAmount != null && depositMaxAmount.compareTo(BigDecimal.ZERO) != -1) {
+                if (depositMinAmount != null && depositMinAmount.compareTo(BigDecimal.ZERO) != -1) {
+                    baseDataValidator.reset().parameter(depositMaxAmountParamName).value(depositMaxAmount).notLessThanMin(depositMinAmount);
+                    if (depositMinAmount.compareTo(depositMaxAmount) <= 0) {
+                        baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount)
+                                .inMinAndMaxAmountRange(depositMinAmount, depositMaxAmount);
+                    }
+                } else {
+                    baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notGreaterThanMax(depositMaxAmount);
+                }
+            } else if (depositMinAmount != null && depositMinAmount.compareTo(BigDecimal.ZERO) != -1) {
+                baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notLessThanMin(depositMinAmount);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java
new file mode 100644
index 0000000..13eaeab
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java
@@ -0,0 +1,445 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a Fixed Deposit account.
+ */
+public class FixedDepositAccountData extends DepositAccountData {
+
+    private boolean preClosurePenalApplicable;
+    private BigDecimal preClosurePenalInterest;
+    private EnumOptionData preClosurePenalInterestOnType;
+    private Integer minDepositTerm;
+    private Integer maxDepositTerm;
+    private EnumOptionData minDepositTermType;
+    private EnumOptionData maxDepositTermType;
+    private Integer inMultiplesOfDepositTerm;
+    private EnumOptionData inMultiplesOfDepositTermType;
+    private BigDecimal depositAmount;
+    private BigDecimal maturityAmount;
+    private LocalDate maturityDate;
+    private Integer depositPeriod;
+    private EnumOptionData depositPeriodFrequency;
+
+    // used for account close
+    private EnumOptionData onAccountClosure;
+
+    private final PortfolioAccountData linkedAccount;
+    private final Boolean transferInterestToSavings;
+
+    private Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions;
+    private Collection<EnumOptionData> periodFrequencyTypeOptions;
+    private Collection<SavingsAccountData> savingsAccounts;
+
+    // for account close
+    private Collection<EnumOptionData> onAccountClosureOptions;
+    private Collection<PaymentTypeData> paymentTypeOptions;
+
+    public static FixedDepositAccountData instance(final DepositAccountData depositAccountData, final boolean preClosurePenalApplicable,
+            final BigDecimal preClosurePenalInterest, final EnumOptionData preClosurePenalInterestOnType, final Integer minDepositTerm,
+            final Integer maxDepositTerm, final EnumOptionData minDepositTermType, final EnumOptionData maxDepositTermType,
+            final Integer inMultiplesOfDepositTerm, final EnumOptionData inMultiplesOfDepositTermType, final BigDecimal depositAmount,
+            final BigDecimal maturityAmount, final LocalDate maturityDate, final Integer depositPeriod,
+            final EnumOptionData depositPeriodFrequency, final EnumOptionData onAccountClosure, final Boolean transferInterestToSavings) {
+
+        final PortfolioAccountData linkedAccount = null;
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+
+        final EnumOptionData depositType = SavingsEnumerations.depositType(DepositAccountType.FIXED_DEPOSIT.getValue());
+        final Collection<EnumOptionData> onAccountClosureOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<SavingsAccountData> savingsAccountDatas = null;
+
+        return new FixedDepositAccountData(depositAccountData.id, depositAccountData.accountNo, depositAccountData.externalId,
+                depositAccountData.groupId, depositAccountData.groupName, depositAccountData.clientId, depositAccountData.clientName,
+                depositAccountData.depositProductId, depositAccountData.depositProductName, depositAccountData.fieldOfficerId,
+                depositAccountData.fieldOfficerName, depositAccountData.status, depositAccountData.timeline, depositAccountData.currency,
+                depositAccountData.nominalAnnualInterestRate, depositAccountData.interestCompoundingPeriodType,
+                depositAccountData.interestPostingPeriodType, depositAccountData.interestCalculationType,
+                depositAccountData.interestCalculationDaysInYearType, depositAccountData.minRequiredOpeningBalance,
+                depositAccountData.lockinPeriodFrequency, depositAccountData.lockinPeriodFrequencyType,
+                depositAccountData.withdrawalFeeForTransfers, depositAccountData.minBalanceForInterestCalculation,
+                depositAccountData.summary, depositAccountData.transactions, depositAccountData.productOptions,
+                depositAccountData.fieldOfficerOptions, depositAccountData.interestCompoundingPeriodTypeOptions,
+                depositAccountData.interestPostingPeriodTypeOptions, depositAccountData.interestCalculationTypeOptions,
+                depositAccountData.interestCalculationDaysInYearTypeOptions, depositAccountData.lockinPeriodFrequencyTypeOptions,
+                depositAccountData.withdrawalFeeTypeOptions, depositAccountData.charges, depositAccountData.chargeOptions,
+                depositAccountData.accountChart, depositAccountData.chartTemplate, preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate,
+                depositPeriod, depositPeriodFrequency, periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions,
+                paymentTypeOptions, savingsAccountDatas, linkedAccount, transferInterestToSavings);
+    }
+
+    public static FixedDepositAccountData withInterestChart(final FixedDepositAccountData account,
+            final DepositAccountInterestRateChartData accountChart) {
+        return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
+                account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
+                account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
+                account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
+                account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
+                account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.minBalanceForInterestCalculation,
+                account.summary, account.transactions, account.productOptions, account.fieldOfficerOptions,
+                account.interestCompoundingPeriodTypeOptions, account.interestPostingPeriodTypeOptions,
+                account.interestCalculationTypeOptions, account.interestCalculationDaysInYearTypeOptions,
+                account.lockinPeriodFrequencyTypeOptions, account.withdrawalFeeTypeOptions, account.charges, account.chargeOptions,
+                accountChart, account.chartTemplate, account.preClosurePenalApplicable, account.preClosurePenalInterest,
+                account.preClosurePenalInterestOnType, account.preClosurePenalInterestOnTypeOptions, account.minDepositTerm,
+                account.maxDepositTerm, account.minDepositTermType, account.maxDepositTermType, account.inMultiplesOfDepositTerm,
+                account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
+                account.depositPeriod, account.depositPeriodFrequency, account.periodFrequencyTypeOptions, account.depositType,
+                account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, account.savingsAccounts,
+                account.linkedAccount, account.transferInterestToSavings);
+    }
+
+    public static FixedDepositAccountData associationsAndTemplate(final FixedDepositAccountData account, FixedDepositAccountData template,
+            final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges,
+            final PortfolioAccountData linkedAccount) {
+
+        if (template == null) {
+            template = account;
+        }
+
+        return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
+                account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
+                account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
+                account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
+                account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
+                account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.minBalanceForInterestCalculation,
+                account.summary, transactions, template.productOptions, template.fieldOfficerOptions,
+                template.interestCompoundingPeriodTypeOptions, template.interestPostingPeriodTypeOptions,
+                template.interestCalculationTypeOptions, template.interestCalculationDaysInYearTypeOptions,
+                template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions,
+                account.accountChart, account.chartTemplate, account.preClosurePenalApplicable, account.preClosurePenalInterest,
+                account.preClosurePenalInterestOnType, template.preClosurePenalInterestOnTypeOptions, account.minDepositTerm,
+                account.maxDepositTerm, account.minDepositTermType, account.maxDepositTermType, account.inMultiplesOfDepositTerm,
+                account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
+                account.depositPeriod, account.depositPeriodFrequency, template.periodFrequencyTypeOptions, account.depositType,
+                account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, template.savingsAccounts,
+                linkedAccount, account.transferInterestToSavings);
+    }
+
+    public static FixedDepositAccountData withTemplateOptions(final FixedDepositAccountData account,
+            final Collection<DepositProductData> productOptions, final Collection<StaffData> fieldOfficerOptions,
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges,
+            final Collection<ChargeData> chargeOptions, final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<SavingsAccountData> savingsAccounts) {
+
+        return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
+                account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
+                account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
+                account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
+                account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
+                account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.minBalanceForInterestCalculation,
+                account.summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, account.accountChart,
+                account.chartTemplate, account.preClosurePenalApplicable, account.preClosurePenalInterest,
+                account.preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, account.minDepositTerm,
+                account.maxDepositTerm, account.minDepositTermType, account.maxDepositTermType, account.inMultiplesOfDepositTerm,
+                account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
+                account.depositPeriod, account.depositPeriodFrequency, periodFrequencyTypeOptions, account.depositType,
+                account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, savingsAccounts,
+                account.linkedAccount, account.transferInterestToSavings);
+    }
+
+    public static FixedDepositAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
+            final String groupName) {
+
+        final Long id = null;
+        final String accountNo = null;
+        final String externalId = null;
+        final Long productId = null;
+        final String productName = null;
+        final Long fieldOfficerId = null;
+        final String fieldOfficerName = null;
+        final SavingsAccountStatusEnumData status = null;
+        final SavingsAccountApplicationTimelineData timeline = null;
+        final CurrencyData currency = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final SavingsAccountSummaryData summary = null;
+        final Collection<SavingsAccountTransactionData> transactions = null;
+
+        final Collection<DepositProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+
+        final DepositAccountInterestRateChartData accountChart = null;
+        final DepositAccountInterestRateChartData chartTemplate = null;
+        final boolean preClosurePenalApplicable = false;
+        final BigDecimal preClosurePenalInterest = null;
+        final EnumOptionData preClosurePenalInterestOnType = null;
+        final Integer minDepositTerm = null;
+        final Integer maxDepositTerm = null;
+        final EnumOptionData minDepositTermType = null;
+        final EnumOptionData maxDepositTermType = null;
+        final Integer inMultiplesOfDepositTerm = null;
+        final EnumOptionData inMultiplesOfDepositTermType = null;
+        final BigDecimal depositAmount = null;
+        final BigDecimal maturityAmount = null;
+        final LocalDate maturityDate = null;
+        final Integer depositPeriod = null;
+        final EnumOptionData depositPeriodFrequency = null;
+        final EnumOptionData onAccountClosure = null;
+        final PortfolioAccountData linkedAccount = null;
+        final Boolean transferInterestToSavings = null;
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+
+        final EnumOptionData depositType = SavingsEnumerations.depositType(DepositAccountType.FIXED_DEPOSIT.getValue());
+        final Collection<EnumOptionData> onAccountClosureOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<SavingsAccountData> savingsAccountDatas = null;
+
+        return new FixedDepositAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
+                fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, minBalanceForInterestCalculation, summary,
+                transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, accountChart, chartTemplate, preClosurePenalApplicable,
+                preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm,
+                maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType,
+                depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, periodFrequencyTypeOptions,
+                depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, linkedAccount,
+                transferInterestToSavings);
+    }
+
+    public static FixedDepositAccountData preClosureDetails(final Long accountId, BigDecimal maturityAmount,
+            final Collection<EnumOptionData> onAccountClosureOptions, final Collection<PaymentTypeData> paymentTypeOptions,
+            final Collection<SavingsAccountData> savingsAccountDatas) {
+
+        final Long groupId = null;
+        final String groupName = null;
+        final Long clientId = null;
+        final String clientName = null;
+        final String accountNo = null;
+        final String externalId = null;
+        final Long productId = null;
+        final String productName = null;
+        final Long fieldOfficerId = null;
+        final String fieldOfficerName = null;
+        final SavingsAccountStatusEnumData status = null;
+        final SavingsAccountApplicationTimelineData timeline = null;
+        final CurrencyData currency = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final SavingsAccountSummaryData summary = null;
+        final Collection<SavingsAccountTransactionData> transactions = null;
+        final Collection<DepositProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+
+        final DepositAccountInterestRateChartData accountChart = null;
+        final DepositAccountInterestRateChartData chartTemplate = null;
+        final boolean preClosurePenalApplicable = false;
+        final BigDecimal preClosurePenalInterest = null;
+        final EnumOptionData preClosurePenalInterestOnType = null;
+        final Integer minDepositTerm = null;
+        final Integer maxDepositTerm = null;
+        final EnumOptionData minDepositTermType = null;
+        final EnumOptionData maxDepositTermType = null;
+        final Integer inMultiplesOfDepositTerm = null;
+        final EnumOptionData inMultiplesOfDepositTermType = null;
+        final BigDecimal depositAmount = null;
+        final LocalDate maturityDate = null;
+        final Integer depositPeriod = null;
+        final EnumOptionData depositPeriodFrequency = null;
+        final EnumOptionData onAccountClosure = null;
+        final Boolean transferInterestToSavings = null;
+
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+
+        final EnumOptionData depositType = SavingsEnumerations.depositType(DepositAccountType.FIXED_DEPOSIT.getValue());
+        final PortfolioAccountData linkedAccount = null;
+
+        return new FixedDepositAccountData(accountId, accountNo, externalId, groupId, groupName, clientId, clientName, productId,
+                productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, minBalanceForInterestCalculation, summary,
+                transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, accountChart, chartTemplate, preClosurePenalApplicable,
+                preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm,
+                maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType,
+                depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, periodFrequencyTypeOptions,
+                depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, linkedAccount,
+                transferInterestToSavings);
+    }
+
+    public static FixedDepositAccountData withClosureTemplateDetails(final FixedDepositAccountData account,
+            final Collection<EnumOptionData> onAccountClosureOptions, final Collection<PaymentTypeData> paymentTypeOptions,
+            final Collection<SavingsAccountData> savingsAccountDatas) {
+
+        return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
+                account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
+                account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
+                account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
+                account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
+                account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.minBalanceForInterestCalculation,
+                account.summary, account.transactions, account.productOptions, account.fieldOfficerOptions,
+                account.interestCompoundingPeriodTypeOptions, account.interestPostingPeriodTypeOptions,
+                account.interestCalculationTypeOptions, account.interestCalculationDaysInYearTypeOptions,
+                account.lockinPeriodFrequencyTypeOptions, account.withdrawalFeeTypeOptions, account.charges, account.chargeOptions,
+                account.accountChart, account.chartTemplate, account.preClosurePenalApplicable, account.preClosurePenalInterest,
+                account.preClosurePenalInterestOnType, account.preClosurePenalInterestOnTypeOptions, account.minDepositTerm,
+                account.maxDepositTerm, account.minDepositTermType, account.maxDepositTermType, account.inMultiplesOfDepositTerm,
+                account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
+                account.depositPeriod, account.depositPeriodFrequency, account.periodFrequencyTypeOptions, account.depositType,
+                account.onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, account.linkedAccount,
+                account.transferInterestToSavings);
+
+    }
+
+    private FixedDepositAccountData(final Long id, final String accountNo, final String externalId, final Long groupId,
+            final String groupName, final Long clientId, final String clientName, final Long productId, final String productName,
+            final Long fieldofficerId, final String fieldofficerName, final SavingsAccountStatusEnumData status,
+            final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final BigDecimal nominalAnnualInterestRate,
+            final EnumOptionData interestPeriodType, final EnumOptionData interestPostingPeriodType,
+            final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType,
+            final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
+            final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
+            final BigDecimal minBalanceForInterestCalculation, final SavingsAccountSummaryData summary,
+            final Collection<SavingsAccountTransactionData> transactions, final Collection<DepositProductData> productOptions,
+            final Collection<StaffData> fieldOfficerOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> chargeOptions,
+            final DepositAccountInterestRateChartData accountChart, final DepositAccountInterestRateChartData chartTemplate,
+            final boolean preClosurePenalApplicable, final BigDecimal preClosurePenalInterest,
+            final EnumOptionData preClosurePenalInterestOnType, final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Integer minDepositTerm, final Integer maxDepositTerm, final EnumOptionData minDepositTermType,
+            final EnumOptionData maxDepositTermType, final Integer inMultiplesOfDepositTerm,
+            final EnumOptionData inMultiplesOfDepositTermType, final BigDecimal depositAmount, final BigDecimal maturityAmount,
+            final LocalDate maturityDate, final Integer depositPeriod, final EnumOptionData depositPeriodFrequency,
+            final Collection<EnumOptionData> periodFrequencyTypeOptions, final EnumOptionData depositType,
+            final EnumOptionData onAccountClosure, final Collection<EnumOptionData> onAccountClosureOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<SavingsAccountData> savingsAccountDatas,
+            final PortfolioAccountData linkedAccount, final Boolean transferInterestToSavings) {
+
+        super(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldofficerId,
+                fieldofficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType,
+                interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, fieldOfficerOptions,
+                interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges,
+                chargeOptions, accountChart, chartTemplate, depositType, minBalanceForInterestCalculation);
+
+        this.preClosurePenalApplicable = preClosurePenalApplicable;
+        this.preClosurePenalInterest = preClosurePenalInterest;
+        this.preClosurePenalInterestOnType = preClosurePenalInterestOnType;
+        this.minDepositTerm = minDepositTerm;
+        this.maxDepositTerm = maxDepositTerm;
+        this.minDepositTermType = minDepositTermType;
+        this.maxDepositTermType = maxDepositTermType;
+        this.inMultiplesOfDepositTerm = inMultiplesOfDepositTerm;
+        this.inMultiplesOfDepositTermType = inMultiplesOfDepositTermType;
+        this.depositAmount = depositAmount;
+        this.maturityAmount = maturityAmount;
+        this.maturityDate = maturityDate;
+        this.depositPeriod = depositPeriod;
+        this.depositPeriodFrequency = depositPeriodFrequency;
+        this.onAccountClosure = onAccountClosure;
+        this.linkedAccount = linkedAccount;
+        this.transferInterestToSavings = transferInterestToSavings;
+
+        // template
+        this.preClosurePenalInterestOnTypeOptions = preClosurePenalInterestOnTypeOptions;
+        this.periodFrequencyTypeOptions = periodFrequencyTypeOptions;
+
+        // account close template options
+        this.onAccountClosureOptions = onAccountClosureOptions;
+        this.paymentTypeOptions = paymentTypeOptions;
+        this.savingsAccounts = savingsAccountDatas;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final FixedDepositAccountData rhs = (FixedDepositAccountData) obj;
+        return new EqualsBuilder().append(this.id, rhs.id).append(this.accountNo, rhs.accountNo).isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).toHashCode();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java
new file mode 100644
index 0000000..3aec5d0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java
@@ -0,0 +1,391 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+
+/**
+ * Immutable data object representing a Fixed Deposit product.
+ */
+public class FixedDepositProductData extends DepositProductData {
+
+    // additional fields
+    private boolean preClosurePenalApplicable;
+    protected BigDecimal preClosurePenalInterest;
+    protected EnumOptionData preClosurePenalInterestOnType;
+    protected Integer minDepositTerm;
+    protected Integer maxDepositTerm;
+    private EnumOptionData minDepositTermType;
+    private EnumOptionData maxDepositTermType;
+    protected Integer inMultiplesOfDepositTerm;
+    protected EnumOptionData inMultiplesOfDepositTermType;
+    protected BigDecimal minDepositAmount;
+    protected BigDecimal depositAmount;
+    protected BigDecimal maxDepositAmount;
+
+    private Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions;
+    private Collection<EnumOptionData> periodFrequencyTypeOptions;
+
+    public static FixedDepositProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
+            final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate,
+            final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Collection<EnumOptionData> periodFrequencyTypeOptions) {
+
+        final Long id = null;
+        final String name = null;
+        final String shortName = null;
+        final String description = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final Collection<InterestRateChartData> interestRateCharts = null;
+
+        final boolean preClosurePenalApplicable = false;
+        final BigDecimal preClosurePenalInterest = null;
+        final EnumOptionData preClosurePenalInterestOnType = null;
+        final Integer minDepositTerm = null;
+        final Integer maxDepositTerm = null;
+        final EnumOptionData minDepositTermType = null;
+        final EnumOptionData maxDepositTermType = null;
+        final Integer inMultiplesOfDepositTerm = null;
+        final EnumOptionData inMultiplesOfDepositTermType = null;
+        final BigDecimal minDepositAmount = null;
+        final BigDecimal depositAmount = null;
+        final BigDecimal maxDepositAmount = null;
+
+        return new FixedDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate,
+                interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, minBalanceForInterestCalculation, accountingRule, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount,
+                maxDepositAmount, periodFrequencyTypeOptions);
+    }
+
+    public static FixedDepositProductData withCharges(final FixedDepositProductData existingProduct, final Collection<ChargeData> charges) {
+        return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
+                existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
+                existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
+                existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
+                existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
+                existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, existingProduct.accountingMappings,
+                existingProduct.paymentChannelToFundSourceMappings, existingProduct.currencyOptions,
+                existingProduct.interestCompoundingPeriodTypeOptions, existingProduct.interestPostingPeriodTypeOptions,
+                existingProduct.interestCalculationTypeOptions, existingProduct.interestCalculationDaysInYearTypeOptions,
+                existingProduct.lockinPeriodFrequencyTypeOptions, existingProduct.withdrawalFeeTypeOptions,
+                existingProduct.paymentTypeOptions, existingProduct.accountingRuleOptions, existingProduct.accountingMappingOptions,
+                charges, existingProduct.chargeOptions, existingProduct.penaltyOptions, existingProduct.feeToIncomeAccountMappings,
+                existingProduct.penaltyToIncomeAccountMappings, existingProduct.interestRateCharts, existingProduct.chartTemplate,
+                existingProduct.preClosurePenalApplicable, existingProduct.preClosurePenalInterest,
+                existingProduct.preClosurePenalInterestOnType, existingProduct.preClosurePenalInterestOnTypeOptions,
+                existingProduct.minDepositTerm, existingProduct.maxDepositTerm, existingProduct.minDepositTermType,
+                existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType,
+                existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount,
+                existingProduct.periodFrequencyTypeOptions);
+    }
+
+    /**
+     * Returns a {@link FixedDepositProductData} that contains and exist
+     * {@link FixedDepositProductData} data with further template data for
+     * dropdowns.
+     */
+    public static FixedDepositProductData withTemplate(final FixedDepositProductData existingProduct,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
+            final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate,
+            final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Collection<EnumOptionData> periodFrequencyTypeOptions) {
+
+        return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
+                existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
+                existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
+                existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
+                existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
+                existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, existingProduct.accountingMappings,
+                existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions,
+                existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings,
+                existingProduct.interestRateCharts, chartTemplate, existingProduct.preClosurePenalApplicable,
+                existingProduct.preClosurePenalInterest, existingProduct.preClosurePenalInterestOnType,
+                preClosurePenalInterestOnTypeOptions, existingProduct.minDepositTerm, existingProduct.maxDepositTerm,
+                existingProduct.minDepositTermType, existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm,
+                existingProduct.inMultiplesOfDepositTermType, existingProduct.minDepositAmount, existingProduct.depositAmount,
+                existingProduct.maxDepositAmount, periodFrequencyTypeOptions);
+    }
+
+    public static FixedDepositProductData withAccountingDetails(final FixedDepositProductData existingProduct,
+            final Map<String, Object> accountingMappings,
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
+            final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
+            final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings) {
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+
+        return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
+                existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
+                existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
+                existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
+                existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
+                existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, existingProduct.interestRateCharts, existingProduct.chartTemplate,
+                existingProduct.preClosurePenalApplicable, existingProduct.preClosurePenalInterest,
+                existingProduct.preClosurePenalInterestOnType, existingProduct.preClosurePenalInterestOnTypeOptions,
+                existingProduct.minDepositTerm, existingProduct.maxDepositTerm, existingProduct.minDepositTermType,
+                existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType,
+                existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount,
+                existingProduct.periodFrequencyTypeOptions);
+    }
+
+    public static FixedDepositProductData instance(final DepositProductData depositProductData, final boolean preClosurePenalApplicable,
+            final BigDecimal preClosurePenalInterest, final EnumOptionData preClosurePenalInterestOnType, final Integer minDepositTerm,
+            final Integer maxDepositTerm, final EnumOptionData minDepositTermType, final EnumOptionData maxDepositTermType,
+            final Integer inMultiplesOfDepositTerm, final EnumOptionData inMultiplesOfDepositTermType, final BigDecimal minDepositAmount,
+            final BigDecimal depositAmount, final BigDecimal maxDepositAmount) {
+
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final Collection<InterestRateChartData> interestRateCharts = null;
+        final InterestRateChartData chartTemplate = null;
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+
+        return new FixedDepositProductData(depositProductData.id, depositProductData.name, depositProductData.shortName,
+                depositProductData.description, depositProductData.currency, depositProductData.nominalAnnualInterestRate,
+                depositProductData.interestCompoundingPeriodType, depositProductData.interestPostingPeriodType,
+                depositProductData.interestCalculationType, depositProductData.interestCalculationDaysInYearType,
+                depositProductData.lockinPeriodFrequency, depositProductData.lockinPeriodFrequencyType,
+                depositProductData.minBalanceForInterestCalculation, depositProductData.accountingRule, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount,
+                maxDepositAmount, periodFrequencyTypeOptions);
+    }
+
+    public static FixedDepositProductData lookup(final Long id, final String name) {
+
+        final String shortName = null;
+        final CurrencyData currency = null;
+        final String description = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestCompoundingPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final EnumOptionData accountingType = null;
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final Collection<InterestRateChartData> interestRateCharts = null;
+        final InterestRateChartData chartTemplate = null;
+        final boolean preClosurePenalApplicable = false;
+        final BigDecimal preClosurePenalInterest = null;
+        final EnumOptionData preClosurePenalInterestOnType = null;
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final Integer minDepositTerm = null;
+        final Integer maxDepositTerm = null;
+        final EnumOptionData minDepositTermType = null;
+        final EnumOptionData maxDepositTermType = null;
+        final Integer inMultiplesOfDepositTerm = null;
+        final EnumOptionData inMultiplesOfDepositTermType = null;
+        final BigDecimal minDepositAmount = null;
+        final BigDecimal depositAmount = null;
+        final BigDecimal maxDepositAmount = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+
+        return new FixedDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate,
+                interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, minBalanceForInterestCalculation, accountingType, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount,
+                maxDepositAmount, periodFrequencyTypeOptions);
+    }
+
+    public static FixedDepositProductData withInterestChart(final FixedDepositProductData existingProduct,
+            final Collection<InterestRateChartData> interestRateCharts) {
+        return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
+                existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
+                existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
+                existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
+                existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
+                existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, existingProduct.accountingMappings,
+                existingProduct.paymentChannelToFundSourceMappings, existingProduct.currencyOptions,
+                existingProduct.interestCompoundingPeriodTypeOptions, existingProduct.interestPostingPeriodTypeOptions,
+                existingProduct.interestCalculationTypeOptions, existingProduct.interestCalculationDaysInYearTypeOptions,
+                existingProduct.lockinPeriodFrequencyTypeOptions, existingProduct.withdrawalFeeTypeOptions,
+                existingProduct.paymentTypeOptions, existingProduct.accountingRuleOptions, existingProduct.accountingMappingOptions,
+                existingProduct.charges, existingProduct.chargeOptions, existingProduct.penaltyOptions,
+                existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, interestRateCharts,
+                existingProduct.chartTemplate, existingProduct.preClosurePenalApplicable, existingProduct.preClosurePenalInterest,
+                existingProduct.preClosurePenalInterestOnType, existingProduct.preClosurePenalInterestOnTypeOptions,
+                existingProduct.minDepositTerm, existingProduct.maxDepositTerm, existingProduct.minDepositTermType,
+                existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType,
+                existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount,
+                existingProduct.periodFrequencyTypeOptions);
+
+    }
+
+    private FixedDepositProductData(final Long id, final String name, final String shortName, final String description,
+            final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final Integer lockinPeriodFrequency,
+            final EnumOptionData lockinPeriodFrequencyType, final BigDecimal minBalanceForInterestCalculation,
+            final EnumOptionData accountingType, final Map<String, Object> accountingMappings,
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> charges,
+            final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions,
+            final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
+            final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings,
+            final Collection<InterestRateChartData> interestRateCharts, final InterestRateChartData chartTemplate,
+            final boolean preClosurePenalApplicable, final BigDecimal preClosurePenalInterest,
+            final EnumOptionData preClosurePenalInterestOnType, final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Integer minDepositTerm, final Integer maxDepositTerm, final EnumOptionData minDepositTermType,
+            final EnumOptionData maxDepositTermType, final Integer inMultiplesOfDepositTerm,
+            final EnumOptionData inMultiplesOfDepositTermType, final BigDecimal minDepositAmount, final BigDecimal depositAmount,
+            final BigDecimal maxDepositAmount, final Collection<EnumOptionData> periodFrequencyTypeOptions) {
+
+        super(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, accountingType, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions,
+                interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
+                accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation);
+
+        // fixed deposit additional fields
+        this.preClosurePenalApplicable = preClosurePenalApplicable;
+        this.preClosurePenalInterest = preClosurePenalInterest;
+        this.preClosurePenalInterestOnType = preClosurePenalInterestOnType;
+        this.minDepositTerm = minDepositTerm;
+        this.maxDepositTerm = maxDepositTerm;
+        this.minDepositTermType = minDepositTermType;
+        this.maxDepositTermType = maxDepositTermType;
+        this.inMultiplesOfDepositTerm = inMultiplesOfDepositTerm;
+        this.inMultiplesOfDepositTermType = inMultiplesOfDepositTermType;
+        this.minDepositAmount = minDepositAmount;
+        this.depositAmount = depositAmount;
+        this.maxDepositAmount = maxDepositAmount;
+
+        // template
+        this.preClosurePenalInterestOnTypeOptions = preClosurePenalInterestOnTypeOptions;
+        this.periodFrequencyTypeOptions = periodFrequencyTypeOptions;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositAccountData.java
new file mode 100644
index 0000000..3385692
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositAccountData.java
@@ -0,0 +1,520 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a Recurring Deposit account.
+ */
+public class RecurringDepositAccountData extends DepositAccountData {
+
+    // additional fields
+    private final boolean preClosurePenalApplicable;
+    private final BigDecimal preClosurePenalInterest;
+    private final EnumOptionData preClosurePenalInterestOnType;
+    private final Integer minDepositTerm;
+    private final Integer maxDepositTerm;
+    private final EnumOptionData minDepositTermType;
+    private final EnumOptionData maxDepositTermType;
+    private final Integer inMultiplesOfDepositTerm;
+    private final EnumOptionData inMultiplesOfDepositTermType;
+    private final BigDecimal depositAmount;
+    private final BigDecimal maturityAmount;
+    private final LocalDate maturityDate;
+    private final Integer depositPeriod;
+    private final EnumOptionData depositPeriodFrequency;
+    private final BigDecimal mandatoryRecommendedDepositAmount;
+    private final BigDecimal totalOverdueAmount;
+    private final Integer noOfOverdueInstallments;
+    private final boolean isMandatoryDeposit;
+    private final boolean allowWithdrawal;
+    private final boolean adjustAdvanceTowardsFuturePayments;
+    private final LocalDate expectedFirstDepositOnDate;
+    private final boolean isCalendarInherited;
+    private final Integer recurringFrequency;
+    private final EnumOptionData recurringFrequencyType;
+
+    // used for account close
+    private final EnumOptionData onAccountClosure;
+
+    private final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions;
+    private final Collection<EnumOptionData> periodFrequencyTypeOptions;
+    private final Collection<SavingsAccountData> savingsAccounts;
+
+    // for account close
+    private final Collection<EnumOptionData> onAccountClosureOptions;
+    private final Collection<PaymentTypeData> paymentTypeOptions;
+
+    public static RecurringDepositAccountData instance(final DepositAccountData depositAccountData,
+            final boolean preClosurePenalApplicable, final BigDecimal preClosurePenalInterest,
+            final EnumOptionData preClosurePenalInterestOnType, final Integer minDepositTerm, final Integer maxDepositTerm,
+            final EnumOptionData minDepositTermType, final EnumOptionData maxDepositTermType, final Integer inMultiplesOfDepositTerm,
+            final EnumOptionData inMultiplesOfDepositTermType, final BigDecimal depositAmount, final BigDecimal maturityAmount,
+            final LocalDate maturityDate, final Integer depositPeriod, final EnumOptionData depositPeriodFrequency,
+            final BigDecimal mandatoryRecommendedDepositAmount, final EnumOptionData onAccountClosure,
+            final LocalDate expectedFirstDepositOnDate, final BigDecimal totalOverdueAmount, final Integer noOfOverdueInstallments,
+            final boolean isMandatoryDeposit, final boolean allowWithdrawal, final boolean adjustAdvanceTowardsFuturePayments,
+            final boolean isCalendarInherited) {
+
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+
+        final EnumOptionData depositType = SavingsEnumerations.depositType(DepositAccountType.FIXED_DEPOSIT.getValue());
+        final Collection<EnumOptionData> onAccountClosureOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<SavingsAccountData> savingsAccountDatas = null;
+        final Integer recurringFrequency = null;
+        final EnumOptionData recurringFrequencyType = null;
+
+        return new RecurringDepositAccountData(depositAccountData.id, depositAccountData.accountNo, depositAccountData.externalId,
+                depositAccountData.groupId, depositAccountData.groupName, depositAccountData.clientId, depositAccountData.clientName,
+                depositAccountData.depositProductId, depositAccountData.depositProductName, depositAccountData.fieldOfficerId,
+                depositAccountData.fieldOfficerName, depositAccountData.status, depositAccountData.timeline, depositAccountData.currency,
+                depositAccountData.nominalAnnualInterestRate, depositAccountData.interestCompoundingPeriodType,
+                depositAccountData.interestPostingPeriodType, depositAccountData.interestCalculationType,
+                depositAccountData.interestCalculationDaysInYearType, depositAccountData.minRequiredOpeningBalance,
+                depositAccountData.lockinPeriodFrequency, depositAccountData.lockinPeriodFrequencyType,
+                depositAccountData.withdrawalFeeForTransfers, depositAccountData.minBalanceForInterestCalculation,
+                depositAccountData.summary, depositAccountData.transactions, depositAccountData.productOptions,
+                depositAccountData.fieldOfficerOptions, depositAccountData.interestCompoundingPeriodTypeOptions,
+                depositAccountData.interestPostingPeriodTypeOptions, depositAccountData.interestCalculationTypeOptions,
+                depositAccountData.interestCalculationDaysInYearTypeOptions, depositAccountData.lockinPeriodFrequencyTypeOptions,
+                depositAccountData.withdrawalFeeTypeOptions, depositAccountData.charges, depositAccountData.chargeOptions,
+                depositAccountData.accountChart, depositAccountData.chartTemplate, preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate,
+                depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount, periodFrequencyTypeOptions, depositType,
+                onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, expectedFirstDepositOnDate,
+                totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments,
+                isCalendarInherited, recurringFrequency, recurringFrequencyType);
+    }
+
+    public static RecurringDepositAccountData withInterestChartAndRecurringDetails(final RecurringDepositAccountData account,
+            final DepositAccountInterestRateChartData accountChart, final Integer recurringFrequency,
+            final EnumOptionData recurringFrequencyType) {
+        return new RecurringDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
+                account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
+                account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
+                account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
+                account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
+                account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.minBalanceForInterestCalculation,
+                account.summary, account.transactions, account.productOptions, account.fieldOfficerOptions,
+                account.interestCompoundingPeriodTypeOptions, account.interestPostingPeriodTypeOptions,
+                account.interestCalculationTypeOptions, account.interestCalculationDaysInYearTypeOptions,
+                account.lockinPeriodFrequencyTypeOptions, account.withdrawalFeeTypeOptions, account.charges, account.chargeOptions,
+                accountChart, account.chartTemplate, account.preClosurePenalApplicable, account.preClosurePenalInterest,
+                account.preClosurePenalInterestOnType, account.preClosurePenalInterestOnTypeOptions, account.minDepositTerm,
+                account.maxDepositTerm, account.minDepositTermType, account.maxDepositTermType, account.inMultiplesOfDepositTerm,
+                account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
+                account.depositPeriod, account.depositPeriodFrequency, account.mandatoryRecommendedDepositAmount,
+                account.periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions,
+                account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
+                account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
+                account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, recurringFrequency, recurringFrequencyType);
+    }
+
+    public static RecurringDepositAccountData withTemplateOptions(final RecurringDepositAccountData account,
+            final RecurringDepositAccountData template, final Collection<SavingsAccountTransactionData> transactions,
+            final Collection<SavingsAccountChargeData> charges) {
+
+        if (template == null) {
+            final Collection<DepositProductData> productOptions = null;
+            final Collection<StaffData> fieldOfficerOptions = null;
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+            final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+            final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+            final Collection<ChargeData> chargeOptions = null;
+
+            final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+            final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+
+            return withTemplateOptions(account, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
+                    interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                    lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions,
+                    preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions);
+        }
+
+        return new RecurringDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
+                account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
+                account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
+                account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
+                account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
+                account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.minBalanceForInterestCalculation,
+                account.summary, transactions, template.productOptions, template.fieldOfficerOptions,
+                template.interestCompoundingPeriodTypeOptions, template.interestPostingPeriodTypeOptions,
+                template.interestCalculationTypeOptions, template.interestCalculationDaysInYearTypeOptions,
+                template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions,
+                account.accountChart, account.chartTemplate, account.preClosurePenalApplicable, account.preClosurePenalInterest,
+                account.preClosurePenalInterestOnType, template.preClosurePenalInterestOnTypeOptions, account.minDepositTerm,
+                account.maxDepositTerm, account.minDepositTermType, account.maxDepositTermType, account.inMultiplesOfDepositTerm,
+                account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
+                account.depositPeriod, account.depositPeriodFrequency, account.mandatoryRecommendedDepositAmount,
+                template.periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions,
+                account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
+                account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
+                account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency,
+                account.recurringFrequencyType);
+
+    }
+
+    public static RecurringDepositAccountData withTemplateOptions(final RecurringDepositAccountData account,
+            final Collection<DepositProductData> productOptions, final Collection<StaffData> fieldOfficerOptions,
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges,
+            final Collection<ChargeData> chargeOptions, final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Collection<EnumOptionData> periodFrequencyTypeOptions) {
+
+        return new RecurringDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
+                account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
+                account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
+                account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
+                account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
+                account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.minBalanceForInterestCalculation,
+                account.summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, account.accountChart,
+                account.chartTemplate, account.preClosurePenalApplicable, account.preClosurePenalInterest,
+                account.preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, account.minDepositTerm,
+                account.maxDepositTerm, account.minDepositTermType, account.maxDepositTermType, account.inMultiplesOfDepositTerm,
+                account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
+                account.depositPeriod, account.depositPeriodFrequency, account.mandatoryRecommendedDepositAmount,
+                periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions,
+                account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
+                account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
+                account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency,
+                account.recurringFrequencyType);
+    }
+
+    public static RecurringDepositAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
+            final String groupName) {
+
+        final Long id = null;
+        final String accountNo = null;
+        final String externalId = null;
+        final Long productId = null;
+        final String productName = null;
+        final Long fieldOfficerId = null;
+        final String fieldOfficerName = null;
+        final SavingsAccountStatusEnumData status = null;
+        final SavingsAccountApplicationTimelineData timeline = null;
+        final CurrencyData currency = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final SavingsAccountSummaryData summary = null;
+        final Collection<SavingsAccountTransactionData> transactions = null;
+
+        final Collection<DepositProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+
+        final DepositAccountInterestRateChartData accountChart = null;
+        final DepositAccountInterestRateChartData chartTemplate = null;
+        final boolean preClosurePenalApplicable = false;
+        final BigDecimal preClosurePenalInterest = null;
+        final EnumOptionData preClosurePenalInterestOnType = null;
+        final Integer minDepositTerm = null;
+        final Integer maxDepositTerm = null;
+        final EnumOptionData minDepositTermType = null;
+        final EnumOptionData maxDepositTermType = null;
+        final Integer inMultiplesOfDepositTerm = null;
+        final EnumOptionData inMultiplesOfDepositTermType = null;
+        final BigDecimal depositAmount = null;
+        final BigDecimal maturityAmount = null;
+        final LocalDate maturityDate = null;
+        final Integer depositPeriod = null;
+        final EnumOptionData depositPeriodFrequency = null;
+        final BigDecimal mandatoryRecommendedDepositAmount = null;
+        final EnumOptionData onAccountClosure = null;
+
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+
+        final EnumOptionData depositType = SavingsEnumerations.depositType(DepositAccountType.RECURRING_DEPOSIT.getValue());
+        final Collection<EnumOptionData> onAccountClosureOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<SavingsAccountData> savingsAccountDatas = null;
+        final LocalDate expectedFirstDepositOnDate = null;
+
+        final BigDecimal totalOverdueAmount = null;
+        final Integer noOfOverdueInstallments = null;
+        final boolean isMandatoryDeposit = false;
+        final boolean allowWithdrawal = false;
+        final boolean adjustAdvanceTowardsFuturePayments = false;
+
+        final boolean isCalendarInherited = false;
+        final Integer recurringFrequency = null;
+        final EnumOptionData recurringFrequencyType = null;
+
+        return new RecurringDepositAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
+                fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, minBalanceForInterestCalculation, summary,
+                transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, accountChart, chartTemplate, preClosurePenalApplicable,
+                preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm,
+                maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType,
+                depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount,
+                periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions,
+                savingsAccountDatas, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit,
+                allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType);
+    }
+
+    public static RecurringDepositAccountData preClosureDetails(final Long accountId, final BigDecimal maturityAmount,
+            final Collection<EnumOptionData> onAccountClosureOptions, final Collection<PaymentTypeData> paymentTypeOptions,
+            final Collection<SavingsAccountData> savingsAccountDatas) {
+
+        final String accountNo = null;
+        final String externalId = null;
+        final Long productId = null;
+        final String productName = null;
+        final Long fieldOfficerId = null;
+        final String fieldOfficerName = null;
+        final SavingsAccountStatusEnumData status = null;
+        final SavingsAccountApplicationTimelineData timeline = null;
+        final CurrencyData currency = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final SavingsAccountSummaryData summary = null;
+        final Collection<SavingsAccountTransactionData> transactions = null;
+
+        final Collection<DepositProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+
+        final DepositAccountInterestRateChartData accountChart = null;
+        final DepositAccountInterestRateChartData chartTemplate = null;
+        final boolean preClosurePenalApplicable = false;
+        final BigDecimal preClosurePenalInterest = null;
+        final EnumOptionData preClosurePenalInterestOnType = null;
+        final Integer minDepositTerm = null;
+        final Integer maxDepositTerm = null;
+        final EnumOptionData minDepositTermType = null;
+        final EnumOptionData maxDepositTermType = null;
+        final Integer inMultiplesOfDepositTerm = null;
+        final EnumOptionData inMultiplesOfDepositTermType = null;
+        final BigDecimal depositAmount = null;
+        final LocalDate maturityDate = null;
+        final Integer depositPeriod = null;
+        final EnumOptionData depositPeriodFrequency = null;
+        final BigDecimal mandatoryRecommendedDepositAmount = null;
+        final EnumOptionData onAccountClosure = null;
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+        final EnumOptionData depositType = SavingsEnumerations.depositType(DepositAccountType.RECURRING_DEPOSIT.getValue());
+
+        final Long groupId = null;
+        final String groupName = null;
+        final Long clientId = null;
+        final String clientName = null;
+        final LocalDate expectedFirstDepositOnDate = null;
+
+        final BigDecimal totalOverdueAmount = null;
+        final Integer noOfOverdueInstallments = null;
+        final boolean isMandatoryDeposit = false;
+        final boolean allowWithdrawal = false;
+        final boolean adjustAdvanceTowardsFuturePayments = false;
+        final boolean isCalendarInherited = false;
+        final Integer recurringFrequency = null;
+        final EnumOptionData recurringFrequencyType = null;
+
+        return new RecurringDepositAccountData(accountId, accountNo, externalId, groupId, groupName, clientId, clientName, productId,
+                productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, minBalanceForInterestCalculation, summary,
+                transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, accountChart, chartTemplate, preClosurePenalApplicable,
+                preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm,
+                maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType,
+                depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount,
+                periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions,
+                savingsAccountDatas, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit,
+                allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType);
+    }
+
+    public static RecurringDepositAccountData withClosureTemplateDetails(final RecurringDepositAccountData account,
+            final Collection<EnumOptionData> onAccountClosureOptions, final Collection<PaymentTypeData> paymentTypeOptions,
+            final Collection<SavingsAccountData> savingsAccountDatas) {
+
+        return new RecurringDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
+                account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
+                account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
+                account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
+                account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
+                account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.minBalanceForInterestCalculation,
+                account.summary, account.transactions, account.productOptions, account.fieldOfficerOptions,
+                account.interestCompoundingPeriodTypeOptions, account.interestPostingPeriodTypeOptions,
+                account.interestCalculationTypeOptions, account.interestCalculationDaysInYearTypeOptions,
+                account.lockinPeriodFrequencyTypeOptions, account.withdrawalFeeTypeOptions, account.charges, account.chargeOptions,
+                account.accountChart, account.chartTemplate, account.preClosurePenalApplicable, account.preClosurePenalInterest,
+                account.preClosurePenalInterestOnType, account.preClosurePenalInterestOnTypeOptions, account.minDepositTerm,
+                account.maxDepositTerm, account.minDepositTermType, account.maxDepositTermType, account.inMultiplesOfDepositTerm,
+                account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
+                account.depositPeriod, account.depositPeriodFrequency, account.mandatoryRecommendedDepositAmount,
+                account.periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, onAccountClosureOptions,
+                paymentTypeOptions, savingsAccountDatas, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
+                account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
+                account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency,
+                account.recurringFrequencyType);
+    }
+
+    private RecurringDepositAccountData(final Long id, final String accountNo, final String externalId, final Long groupId,
+            final String groupName, final Long clientId, final String clientName, final Long productId, final String productName,
+            final Long fieldofficerId, final String fieldofficerName, final SavingsAccountStatusEnumData status,
+            final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final BigDecimal nominalAnnualInterestRate,
+            final EnumOptionData interestPeriodType, final EnumOptionData interestPostingPeriodType,
+            final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType,
+            final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
+            final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
+            final BigDecimal minBalanceForInterestCalculation, final SavingsAccountSummaryData summary,
+            final Collection<SavingsAccountTransactionData> transactions, final Collection<DepositProductData> productOptions,
+            final Collection<StaffData> fieldOfficerOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> chargeOptions,
+            final DepositAccountInterestRateChartData accountChart, final DepositAccountInterestRateChartData chartTemplate,
+            final boolean preClosurePenalApplicable, final BigDecimal preClosurePenalInterest,
+            final EnumOptionData preClosurePenalInterestOnType, final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Integer minDepositTerm, final Integer maxDepositTerm, final EnumOptionData minDepositTermType,
+            final EnumOptionData maxDepositTermType, final Integer inMultiplesOfDepositTerm,
+            final EnumOptionData inMultiplesOfDepositTermType, final BigDecimal depositAmount, final BigDecimal maturityAmount,
+            final LocalDate maturityDate, final Integer depositPeriod, final EnumOptionData depositPeriodFrequency,
+            final BigDecimal mandatoryRecommendedDepositAmount, final Collection<EnumOptionData> periodFrequencyTypeOptions,
+            final EnumOptionData depositType, final EnumOptionData onAccountClosure,
+            final Collection<EnumOptionData> onAccountClosureOptions, final Collection<PaymentTypeData> paymentTypeOptions,
+            final Collection<SavingsAccountData> savingsAccountDatas, final LocalDate expectedFirstDepositOnDate,
+            final BigDecimal totalOverdueAmount, final Integer noOfOverdueInstallments, final boolean isMandatoryDeposit,
+            final boolean allowWithdrawal, final boolean adjustAdvanceTowardsFuturePayments, final boolean isCalendarInherited,
+            final Integer recurringFrequency, final EnumOptionData recurringFrequencyType) {
+
+        super(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldofficerId,
+                fieldofficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType,
+                interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, fieldOfficerOptions,
+                interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges,
+                chargeOptions, accountChart, chartTemplate, depositType, minBalanceForInterestCalculation);
+
+        this.preClosurePenalApplicable = preClosurePenalApplicable;
+        this.preClosurePenalInterest = preClosurePenalInterest;
+        this.preClosurePenalInterestOnType = preClosurePenalInterestOnType;
+        this.minDepositTerm = minDepositTerm;
+        this.maxDepositTerm = maxDepositTerm;
+        this.minDepositTermType = minDepositTermType;
+        this.maxDepositTermType = maxDepositTermType;
+        this.inMultiplesOfDepositTerm = inMultiplesOfDepositTerm;
+        this.inMultiplesOfDepositTermType = inMultiplesOfDepositTermType;
+        this.depositAmount = depositAmount;
+        this.maturityAmount = maturityAmount;
+        this.maturityDate = maturityDate;
+        this.depositPeriod = depositPeriod;
+        this.depositPeriodFrequency = depositPeriodFrequency;
+        this.expectedFirstDepositOnDate = expectedFirstDepositOnDate;
+        this.mandatoryRecommendedDepositAmount = mandatoryRecommendedDepositAmount;
+        this.totalOverdueAmount = totalOverdueAmount;
+        this.noOfOverdueInstallments = noOfOverdueInstallments;
+        this.isMandatoryDeposit = isMandatoryDeposit;
+        this.allowWithdrawal = allowWithdrawal;
+        this.adjustAdvanceTowardsFuturePayments = adjustAdvanceTowardsFuturePayments;
+
+        this.isCalendarInherited = isCalendarInherited;
+        this.recurringFrequency = recurringFrequency;
+        this.recurringFrequencyType = recurringFrequencyType;
+
+        this.preClosurePenalInterestOnTypeOptions = preClosurePenalInterestOnTypeOptions;
+        this.periodFrequencyTypeOptions = periodFrequencyTypeOptions;
+        this.onAccountClosure = onAccountClosure;
+        this.savingsAccounts = savingsAccountDatas;
+
+        // account close template options
+        this.onAccountClosureOptions = onAccountClosureOptions;
+        this.paymentTypeOptions = paymentTypeOptions;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final RecurringDepositAccountData rhs = (RecurringDepositAccountData) obj;
+        return new EqualsBuilder().append(this.id, rhs.id).append(this.accountNo, rhs.accountNo).isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).toHashCode();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositProductData.java
new file mode 100644
index 0000000..c17ce43
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositProductData.java
@@ -0,0 +1,409 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+
+/**
+ * Immutable data object represent a Recurring Deposit product.
+ */
+public class RecurringDepositProductData extends DepositProductData {
+
+    private boolean preClosurePenalApplicable;
+    private BigDecimal preClosurePenalInterest;
+    private EnumOptionData preClosurePenalInterestOnType;
+    private Integer minDepositTerm;
+    private Integer maxDepositTerm;
+    private EnumOptionData minDepositTermType;
+    private EnumOptionData maxDepositTermType;
+    private Integer inMultiplesOfDepositTerm;
+    private EnumOptionData inMultiplesOfDepositTermType;
+    private BigDecimal minDepositAmount;
+    private BigDecimal depositAmount;
+    private BigDecimal maxDepositAmount;
+    private boolean isMandatoryDeposit;
+    private boolean allowWithdrawal;
+    private boolean adjustAdvanceTowardsFuturePayments;
+
+    private Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions;
+    private Collection<EnumOptionData> periodFrequencyTypeOptions;
+
+    public static RecurringDepositProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
+            final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate,
+            final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Collection<EnumOptionData> periodFrequencyTypeOptions) {
+
+        final Long id = null;
+        final String name = null;
+        final String shortName = null;
+        final String description = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final BigDecimal minBalanceForInterestCalculation = null;
+
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final Collection<InterestRateChartData> interestRateCharts = null;
+        final boolean preClosurePenalApplicable = false;
+        final BigDecimal preClosurePenalInterest = null;
+        final EnumOptionData preClosurePenalInterestOnType = null;
+        final boolean isMandatoryDeposit = false;
+        final boolean allowWithdrawal = false;
+        final boolean adjustAdvanceTowardsFuturePayments = false;
+        final Integer minDepositTerm = null;
+        final Integer maxDepositTerm = null;
+        final EnumOptionData minDepositTermType = null;
+        final EnumOptionData maxDepositTermType = null;
+        final Integer inMultiplesOfDepositTerm = null;
+        final EnumOptionData inMultiplesOfDepositTermType = null;
+        final BigDecimal minDepositAmount = null;
+        final BigDecimal depositAmount = null;
+        final BigDecimal maxDepositAmount = null;
+
+        return new RecurringDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate,
+                interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, minBalanceForInterestCalculation, accountingRule, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, isMandatoryDeposit, allowWithdrawal,
+                adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, minDepositAmount, depositAmount, maxDepositAmount);
+    }
+
+    public static RecurringDepositProductData withCharges(final RecurringDepositProductData existingProduct,
+            final Collection<ChargeData> charges) {
+        return new RecurringDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
+                existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
+                existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
+                existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
+                existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
+                existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, existingProduct.accountingMappings,
+                existingProduct.paymentChannelToFundSourceMappings, existingProduct.currencyOptions,
+                existingProduct.interestCompoundingPeriodTypeOptions, existingProduct.interestPostingPeriodTypeOptions,
+                existingProduct.interestCalculationTypeOptions, existingProduct.interestCalculationDaysInYearTypeOptions,
+                existingProduct.lockinPeriodFrequencyTypeOptions, existingProduct.withdrawalFeeTypeOptions,
+                existingProduct.paymentTypeOptions, existingProduct.accountingRuleOptions, existingProduct.accountingMappingOptions,
+                charges, existingProduct.chargeOptions, existingProduct.penaltyOptions, existingProduct.feeToIncomeAccountMappings,
+                existingProduct.penaltyToIncomeAccountMappings, existingProduct.interestRateCharts, existingProduct.chartTemplate,
+                existingProduct.preClosurePenalApplicable, existingProduct.preClosurePenalInterest,
+                existingProduct.preClosurePenalInterestOnType, existingProduct.preClosurePenalInterestOnTypeOptions,
+                existingProduct.minDepositTerm, existingProduct.maxDepositTerm, existingProduct.minDepositTermType,
+                existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType,
+                existingProduct.isMandatoryDeposit, existingProduct.allowWithdrawal, existingProduct.adjustAdvanceTowardsFuturePayments,
+                existingProduct.periodFrequencyTypeOptions, existingProduct.minDepositAmount, existingProduct.depositAmount,
+                existingProduct.maxDepositAmount);
+    }
+
+    /**
+     * Returns a {@link RecurringDepositProductData} that contains and exist
+     * {@link RecurringDepositProductData} data with further template data for
+     * dropdowns.
+     */
+    public static RecurringDepositProductData withTemplate(final RecurringDepositProductData existingProduct,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
+            final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate,
+            final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Collection<EnumOptionData> periodFrequencyTypeOptions) {
+
+        return new RecurringDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
+                existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
+                existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
+                existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
+                existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
+                existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, existingProduct.accountingMappings,
+                existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions,
+                existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings,
+                existingProduct.interestRateCharts, chartTemplate, existingProduct.preClosurePenalApplicable,
+                existingProduct.preClosurePenalInterest, existingProduct.preClosurePenalInterestOnType,
+                preClosurePenalInterestOnTypeOptions, existingProduct.minDepositTerm, existingProduct.maxDepositTerm,
+                existingProduct.minDepositTermType, existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm,
+                existingProduct.inMultiplesOfDepositTermType, existingProduct.isMandatoryDeposit, existingProduct.allowWithdrawal,
+                existingProduct.adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, existingProduct.minDepositAmount,
+                existingProduct.depositAmount, existingProduct.maxDepositAmount);
+
+    }
+
+    public static RecurringDepositProductData withAccountingDetails(final RecurringDepositProductData existingProduct,
+            final Map<String, Object> accountingMappings,
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
+            final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
+            final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings) {
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+
+        return new RecurringDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
+                existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
+                existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
+                existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
+                existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
+                existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, existingProduct.interestRateCharts, existingProduct.chartTemplate,
+                existingProduct.preClosurePenalApplicable, existingProduct.preClosurePenalInterest,
+                existingProduct.preClosurePenalInterestOnType, existingProduct.preClosurePenalInterestOnTypeOptions,
+                existingProduct.minDepositTerm, existingProduct.maxDepositTerm, existingProduct.minDepositTermType,
+                existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType,
+                existingProduct.isMandatoryDeposit, existingProduct.allowWithdrawal, existingProduct.adjustAdvanceTowardsFuturePayments,
+                existingProduct.periodFrequencyTypeOptions, existingProduct.minDepositAmount, existingProduct.depositAmount,
+                existingProduct.maxDepositAmount);
+    }
+
+    public static RecurringDepositProductData instance(final DepositProductData depositProductData,
+            final boolean preClosurePenalApplicable, final BigDecimal preClosurePenalInterest,
+            final EnumOptionData preClosurePenalInterestOnType, final Integer minDepositTerm, final Integer maxDepositTerm,
+            final EnumOptionData minDepositTermType, final EnumOptionData maxDepositTermType, final Integer inMultiplesOfDepositTerm,
+            final EnumOptionData inMultiplesOfDepositTermType, final boolean isMandatoryDeposit, boolean allowWithdrawal,
+            boolean adjustAdvanceTowardsFuturePayments, final BigDecimal minDepositAmount, final BigDecimal depositAmount,
+            final BigDecimal maxDepositAmount) {
+
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final Collection<InterestRateChartData> interestRateCharts = null;
+        final InterestRateChartData chartTemplate = null;
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+
+        return new RecurringDepositProductData(depositProductData.id, depositProductData.name, depositProductData.shortName,
+                depositProductData.description, depositProductData.currency, depositProductData.nominalAnnualInterestRate,
+                depositProductData.interestCompoundingPeriodType, depositProductData.interestPostingPeriodType,
+                depositProductData.interestCalculationType, depositProductData.interestCalculationDaysInYearType,
+                depositProductData.lockinPeriodFrequency, depositProductData.lockinPeriodFrequencyType,
+                depositProductData.minBalanceForInterestCalculation, depositProductData.accountingRule, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, isMandatoryDeposit, allowWithdrawal,
+                adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, minDepositAmount, depositAmount, maxDepositAmount);
+    }
+
+    public static RecurringDepositProductData lookup(final Long id, final String name) {
+
+        final String shortName = null;
+        final CurrencyData currency = null;
+        final String description = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestCompoundingPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final BigDecimal minBalanceForInterestCalculation = null;
+
+        final EnumOptionData accountingType = null;
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final Collection<InterestRateChartData> interestRateCharts = null;
+        final InterestRateChartData chartTemplate = null;
+        final boolean preClosurePenalApplicable = false;
+        final BigDecimal preClosurePenalInterest = null;
+        final EnumOptionData preClosurePenalInterestOnType = null;
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
+        final boolean isMandatoryDeposit = false;
+        final boolean allowWithdrawal = false;
+        final boolean adjustAdvanceTowardsFuturePayments = false;
+        final Integer minDepositTerm = null;
+        final Integer maxDepositTerm = null;
+        final EnumOptionData minDepositTermType = null;
+        final EnumOptionData maxDepositTermType = null;
+        final Integer inMultiplesOfDepositTerm = null;
+        final EnumOptionData inMultiplesOfDepositTermType = null;
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
+        final BigDecimal minDepositAmount = null;
+        final BigDecimal depositAmount = null;
+        final BigDecimal maxDepositAmount = null;
+
+        return new RecurringDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate,
+                interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, minBalanceForInterestCalculation, accountingType, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, isMandatoryDeposit, allowWithdrawal,
+                adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, minDepositAmount, depositAmount, maxDepositAmount);
+    }
+
+    public static RecurringDepositProductData withInterestChart(final RecurringDepositProductData product,
+            final Collection<InterestRateChartData> interestRateCharts) {
+        return new RecurringDepositProductData(product.id, product.name, product.shortName, product.description, product.currency,
+                product.nominalAnnualInterestRate, product.interestCompoundingPeriodType, product.interestPostingPeriodType,
+                product.interestCalculationType, product.interestCalculationDaysInYearType, product.lockinPeriodFrequency,
+                product.lockinPeriodFrequencyType, product.minBalanceForInterestCalculation, product.accountingRule,
+                product.accountingMappings, product.paymentChannelToFundSourceMappings, product.currencyOptions,
+                product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions,
+                product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions,
+                product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions,
+                product.accountingRuleOptions, product.accountingMappingOptions, product.charges, product.chargeOptions,
+                product.penaltyOptions, product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings, interestRateCharts,
+                product.chartTemplate, product.preClosurePenalApplicable, product.preClosurePenalInterest,
+                product.preClosurePenalInterestOnType, product.preClosurePenalInterestOnTypeOptions, product.minDepositTerm,
+                product.maxDepositTerm, product.minDepositTermType, product.maxDepositTermType, product.inMultiplesOfDepositTerm,
+                product.inMultiplesOfDepositTermType, product.isMandatoryDeposit, product.allowWithdrawal,
+                product.adjustAdvanceTowardsFuturePayments, product.periodFrequencyTypeOptions, product.minDepositAmount,
+                product.depositAmount, product.maxDepositAmount);
+
+    }
+
+    private RecurringDepositProductData(final Long id, final String name, final String shortName, final String description,
+            final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final Integer lockinPeriodFrequency,
+            final EnumOptionData lockinPeriodFrequencyType, final BigDecimal minBalanceForInterestCalculation,
+            final EnumOptionData accountingType, final Map<String, Object> accountingMappings,
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> charges,
+            final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions,
+            final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
+            final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings,
+            final Collection<InterestRateChartData> interestRateCharts, final InterestRateChartData chartTemplate,
+            final boolean preClosurePenalApplicable, final BigDecimal preClosurePenalInterest,
+            final EnumOptionData preClosurePenalInterestOnType, final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
+            final Integer minDepositTerm, final Integer maxDepositTerm, final EnumOptionData minDepositTermType,
+            final EnumOptionData maxDepositTermType, final Integer inMultiplesOfDepositTerm,
+            final EnumOptionData inMultiplesOfDepositTermType, final boolean isMandatoryDeposit, boolean allowWithdrawal,
+            boolean adjustAdvanceTowardsFuturePayments, final Collection<EnumOptionData> periodFrequencyTypeOptions,
+            final BigDecimal minDepositAmount, final BigDecimal depositAmount, final BigDecimal maxDepositAmount) {
+
+        super(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, accountingType, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions,
+                interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
+                accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation);
+
+        this.preClosurePenalApplicable = preClosurePenalApplicable;
+        this.preClosurePenalInterest = preClosurePenalInterest;
+        this.preClosurePenalInterestOnType = preClosurePenalInterestOnType;
+        this.minDepositTerm = minDepositTerm;
+        this.maxDepositTerm = maxDepositTerm;
+        this.minDepositTermType = minDepositTermType;
+        this.maxDepositTermType = maxDepositTermType;
+        this.inMultiplesOfDepositTerm = inMultiplesOfDepositTerm;
+        this.inMultiplesOfDepositTermType = inMultiplesOfDepositTermType;
+        this.minDepositAmount = minDepositAmount;
+        this.depositAmount = depositAmount;
+        this.maxDepositAmount = maxDepositAmount;
+        this.isMandatoryDeposit = isMandatoryDeposit;
+        this.allowWithdrawal = allowWithdrawal;
+        this.adjustAdvanceTowardsFuturePayments = adjustAdvanceTowardsFuturePayments;
+
+        // template data
+        this.preClosurePenalInterestOnTypeOptions = preClosurePenalInterestOnTypeOptions;
+        this.periodFrequencyTypeOptions = periodFrequencyTypeOptions;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountAnnualFeeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountAnnualFeeData.java
new file mode 100644
index 0000000..920f8c4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountAnnualFeeData.java
@@ -0,0 +1,57 @@
+/**
+ * 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.savings.data;
+
+import org.joda.time.LocalDate;
+
+public class SavingsAccountAnnualFeeData {
+
+    private final Long id;
+    private final Long accountId;
+    private final String accountNo;
+    private final LocalDate nextAnnualFeeDueDate;
+
+    public static SavingsAccountAnnualFeeData instance(final Long id, final Long accountId, final String accountNo,
+            final LocalDate nextAnnualFeeDueDate) {
+        return new SavingsAccountAnnualFeeData(id, accountId, accountNo, nextAnnualFeeDueDate);
+    }
+
+    private SavingsAccountAnnualFeeData(final Long id, final Long accountId, final String accountNo, final LocalDate nextAnnualFeeDueDate) {
+        this.id = id;
+        this.accountId = accountId;
+        this.accountNo = accountNo;
+        this.nextAnnualFeeDueDate = nextAnnualFeeDueDate;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public Long getAccountId() {
+        return this.accountId;
+    }
+
+    public LocalDate getNextAnnualFeeDueDate() {
+        return this.nextAnnualFeeDueDate;
+    }
+
+    public String getAccountNo() {
+        return this.accountNo;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountApplicationTimelineData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountApplicationTimelineData.java
new file mode 100644
index 0000000..d24973b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountApplicationTimelineData.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.portfolio.savings.data;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object represent the important time-line events of a savings
+ * account application.
+ */
+@SuppressWarnings("unused")
+public class SavingsAccountApplicationTimelineData {
+
+    private final LocalDate submittedOnDate;
+    private final String submittedByUsername;
+    private final String submittedByFirstname;
+    private final String submittedByLastname;
+    private final LocalDate rejectedOnDate;
+    private final String rejectedByUsername;
+    private final String rejectedByFirstname;
+    private final String rejectedByLastname;
+    private final LocalDate withdrawnOnDate;
+    private final String withdrawnByUsername;
+    private final String withdrawnByFirstname;
+    private final String withdrawnByLastname;
+    private final LocalDate approvedOnDate;
+    private final String approvedByUsername;
+    private final String approvedByFirstname;
+    private final String approvedByLastname;
+    private final LocalDate activatedOnDate;
+    private final String activatedByUsername;
+    private final String activatedByFirstname;
+    private final String activatedByLastname;
+    private final LocalDate closedOnDate;
+    private final String closedByUsername;
+    private final String closedByFirstname;
+    private final String closedByLastname;
+
+    public static SavingsAccountApplicationTimelineData templateDefault() {
+
+        final LocalDate submittedOnDate = null;
+        final String submittedByUsername = null;
+        final String submittedByFirstname = null;
+        final String submittedByLastname = null;
+        final LocalDate rejectedOnDate = null;
+        final String rejectedByUsername = null;
+        final String rejectedByFirstname = null;
+        final String rejectedByLastname = null;
+        final LocalDate withdrawnOnDate = null;
+        final String withdrawnByUsername = null;
+        final String withdrawnByFirstname = null;
+        final String withdrawnByLastname = null;
+        final LocalDate approvedOnDate = null;
+        final String approvedByUsername = null;
+        final String approvedByFirstname = null;
+        final String approvedByLastname = null;
+        final LocalDate activatedOnDate = null;
+        final String activatedByUsername = null;
+        final String activatedByFirstname = null;
+        final String activatedByLastname = null;
+        final LocalDate closedOnDate = null;
+        final String closedByUsername = null;
+        final String closedByFirstname = null;
+        final String closedByLastname = null;
+
+        return new SavingsAccountApplicationTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname,
+                rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername,
+                withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname,
+                activatedOnDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername,
+                closedByFirstname, closedByLastname);
+    }
+
+    public SavingsAccountApplicationTimelineData(final LocalDate submittedOnDate, final String submittedByUsername,
+            final String submittedByFirstname, final String submittedByLastname, final LocalDate rejectedOnDate,
+            final String rejectedByUsername, final String rejectedByFirstname, final String rejectedByLastname,
+            final LocalDate withdrawnOnDate, final String withdrawnByUsername, final String withdrawnByFirstname,
+            final String withdrawnByLastname, final LocalDate approvedOnDate, final String approvedByUsername,
+            final String approvedByFirstname, final String approvedByLastname, final LocalDate activatedOnDate,
+            final String activatedByUsername, final String activatedByFirstname, final String activatedByLastname,
+            final LocalDate closedOnDate, final String closedByUsername, final String closedByFirstname, final String closedByLastname) {
+        this.submittedOnDate = submittedOnDate;
+        this.submittedByUsername = submittedByUsername;
+        this.submittedByFirstname = submittedByFirstname;
+        this.submittedByLastname = submittedByLastname;
+        this.rejectedOnDate = rejectedOnDate;
+        this.rejectedByUsername = rejectedByUsername;
+        this.rejectedByFirstname = rejectedByFirstname;
+        this.rejectedByLastname = rejectedByLastname;
+        this.withdrawnOnDate = withdrawnOnDate;
+        this.withdrawnByUsername = withdrawnByUsername;
+        this.withdrawnByFirstname = withdrawnByFirstname;
+        this.withdrawnByLastname = withdrawnByLastname;
+        this.approvedOnDate = approvedOnDate;
+        this.approvedByUsername = approvedByUsername;
+        this.approvedByFirstname = approvedByFirstname;
+        this.approvedByLastname = approvedByLastname;
+        this.activatedOnDate = activatedOnDate;
+        this.activatedByUsername = activatedByUsername;
+        this.activatedByFirstname = activatedByFirstname;
+        this.activatedByLastname = activatedByLastname;
+        this.closedOnDate = closedOnDate;
+        this.closedByUsername = closedByUsername;
+        this.closedByFirstname = closedByFirstname;
+        this.closedByLastname = closedByLastname;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeData.java
new file mode 100644
index 0000000..18f207b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeData.java
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+
+/**
+ * Immutable data object for Savings Account charge data.
+ */
+@SuppressWarnings("unused")
+public class SavingsAccountChargeData {
+
+    private final Long id;
+
+    private final Long chargeId;
+
+    private final Long accountId;
+
+    private final String name;
+
+    private final EnumOptionData chargeTimeType;
+
+    private final LocalDate dueDate;
+
+    private final MonthDay feeOnMonthDay;
+
+    private final Integer feeInterval;
+
+    private final EnumOptionData chargeCalculationType;
+
+    private final BigDecimal percentage;
+
+    private final BigDecimal amountPercentageAppliedTo;
+
+    private final CurrencyData currency;
+
+    private final BigDecimal amount;
+
+    private final BigDecimal amountPaid;
+
+    private final BigDecimal amountWaived;
+
+    private final BigDecimal amountWrittenOff;
+
+    private final BigDecimal amountOutstanding;
+
+    private final BigDecimal amountOrPercentage;
+
+    private final boolean penalty;
+
+    private final Boolean isActive;
+
+    private final LocalDate inactivationDate;
+
+    private final Collection<ChargeData> chargeOptions;
+
+    public static SavingsAccountChargeData template(final Collection<ChargeData> chargeOptions) {
+        final Long id = null;
+        final Long chargeId = null;
+        final Long accountId = null;
+        final String name = null;
+        final CurrencyData currency = null;
+        final BigDecimal amount = BigDecimal.ZERO;
+        final BigDecimal amountPaid = BigDecimal.ZERO;
+        final BigDecimal amountWaived = BigDecimal.ZERO;
+        final BigDecimal amountWrittenOff = BigDecimal.ZERO;
+        final BigDecimal amountOutstanding = BigDecimal.ZERO;
+        final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
+        final EnumOptionData chargeTimeType = null;
+        final EnumOptionData chargeCalculationType = null;
+        final BigDecimal percentage = BigDecimal.ZERO;
+        final boolean penalty = false;
+        final LocalDate dueAsOfDate = null;
+        final MonthDay feeOnMonthDay = null;
+        final Integer feeInterval = null;
+        final Boolean isActive = null;
+        final LocalDate inactivationDate = null;
+
+        return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
+                amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
+                penalty, feeOnMonthDay, feeInterval, isActive, inactivationDate);
+    }
+
+    public static SavingsAccountChargeData instance(final Long id, final Long chargeId, final Long accountId, final String name,
+            final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived,
+            final BigDecimal amountWrittenOff, final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType,
+            final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
+            final BigDecimal amountPercentageAppliedTo, final Collection<ChargeData> chargeOptions, final boolean penalty,
+            final MonthDay feeOnMonthDay, final Integer feeInterval, final Boolean isActive, final LocalDate inactivationDate) {
+
+        return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
+                amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
+                penalty, feeOnMonthDay, feeInterval, isActive, inactivationDate);
+    }
+
+    private SavingsAccountChargeData(final Long id, final Long chargeId, final Long accountId, final String name,
+            final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType,
+            final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount,
+            final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
+            final BigDecimal amountOutstanding, final Collection<ChargeData> chargeOptions, final boolean penalty,
+            final MonthDay feeOnMonthDay, final Integer feeInterval, final Boolean isActive, final LocalDate inactivationDate) {
+        this.id = id;
+        this.chargeId = chargeId;
+        this.accountId = accountId;
+        this.name = name;
+        this.chargeTimeType = chargeTimeType;
+        this.dueDate = dueAsOfDate;
+        this.chargeCalculationType = chargeCalculationType;
+        this.percentage = percentage;
+        this.amountPercentageAppliedTo = amountPercentageAppliedTo;
+        this.currency = currency;
+        this.amount = amount;
+        this.amountPaid = amountPaid;
+        this.amountWaived = amountWaived;
+        this.amountWrittenOff = amountWrittenOff;
+        this.amountOutstanding = amountOutstanding;
+        this.amountOrPercentage = getAmountOrPercentage();
+        this.chargeOptions = chargeOptions;
+        this.penalty = penalty;
+        this.feeOnMonthDay = feeOnMonthDay;
+        this.feeInterval = feeInterval;
+        this.isActive = isActive;
+        this.inactivationDate = inactivationDate;
+    }
+
+    private BigDecimal getAmountOrPercentage() {
+        return (this.chargeCalculationType != null) && (this.chargeCalculationType.getId().intValue() > 1) ? this.percentage : this.amount;
+    }
+
+    public boolean isWithdrawalFee() {
+        return ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isWithdrawalFee();
+    }
+
+    public boolean isAnnualFee() {
+        return ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isAnnualFee();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeDataValidator.java
new file mode 100644
index 0000000..3c3c6dc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeDataValidator.java
@@ -0,0 +1,151 @@
+/**
+ * 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.savings.data;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class SavingsAccountChargeDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public SavingsAccountChargeDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateAdd(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Long chargeId = this.fromApiJsonHelper.extractLongNamed(chargeIdParamName, element);
+        baseDataValidator.reset().parameter(chargeIdParamName).value(chargeId).notNull().integerGreaterThanZero();
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element);
+        baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
+
+        if (this.fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) {
+            final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
+            baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
+            final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
+            baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDay).notNull();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element);
+        baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
+
+        if (this.fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) {
+            final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
+            baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
+            final String monthDayFormat = this.fromApiJsonHelper.extractMonthDayFormatParameter(element.getAsJsonObject());
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+            final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element.getAsJsonObject(),
+                    monthDayFormat, locale);
+            baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDay).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(feeIntervalParamName, element)) {
+            final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(feeIntervalParamName).value(feeInterval).notNull().inMinMaxRange(1, 12);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validatePayCharge(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element);
+        baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
+
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
+        baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull();
+
+        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/portfolio/savings/data/SavingsAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
new file mode 100644
index 0000000..a10ac9d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
@@ -0,0 +1,503 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+
+/**
+ * Immutable data object representing a savings account.
+ */
+public class SavingsAccountData {
+
+    private final Long id;
+    private final String accountNo;
+    private final EnumOptionData depositType;
+    private final String externalId;
+    private final Long groupId;
+    private final String groupName;
+    private final Long clientId;
+    private final String clientName;
+    private final Long savingsProductId;
+    private final String savingsProductName;
+    private final Long fieldOfficerId;
+    private final String fieldOfficerName;
+    private final SavingsAccountStatusEnumData status;
+    private final SavingsAccountApplicationTimelineData timeline;
+    private final CurrencyData currency;
+    private final BigDecimal nominalAnnualInterestRate;
+    private final EnumOptionData interestCompoundingPeriodType;
+    private final EnumOptionData interestPostingPeriodType;
+    private final EnumOptionData interestCalculationType;
+    private final EnumOptionData interestCalculationDaysInYearType;
+    private final BigDecimal minRequiredOpeningBalance;
+    private final Integer lockinPeriodFrequency;
+    private final EnumOptionData lockinPeriodFrequencyType;
+    private final boolean withdrawalFeeForTransfers;
+    private final boolean allowOverdraft;
+    private final BigDecimal overdraftLimit;
+    private final BigDecimal minRequiredBalance;
+    private final boolean enforceMinRequiredBalance;
+    private final BigDecimal minBalanceForInterestCalculation;
+    private final BigDecimal onHoldFunds;
+
+    // associations
+    private final SavingsAccountSummaryData summary;
+    @SuppressWarnings("unused")
+    private final Collection<SavingsAccountTransactionData> transactions;
+
+    private final Collection<SavingsAccountChargeData> charges;
+
+    // template
+    private final Collection<SavingsProductData> productOptions;
+    private final Collection<StaffData> fieldOfficerOptions;
+    private final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions;
+    private final Collection<EnumOptionData> interestPostingPeriodTypeOptions;
+    private final Collection<EnumOptionData> interestCalculationTypeOptions;
+    private final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions;
+    private final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions;
+    private final Collection<EnumOptionData> withdrawalFeeTypeOptions;
+    private final Collection<ChargeData> chargeOptions;
+
+    @SuppressWarnings("unused")
+    private final SavingsAccountChargeData withdrawalFee;
+    @SuppressWarnings("unused")
+    private final SavingsAccountChargeData annualFee;
+	private final BigDecimal nominalAnnualInterestRateOverdraft;
+	private final BigDecimal minOverdraftForInterestCalculation;
+
+    public static SavingsAccountData instance(final Long id, final String accountNo, final EnumOptionData depositType,
+            final String externalId, final Long groupId, final String groupName, final Long clientId, final String clientName,
+            final Long productId, final String productName, final Long fieldOfficerId, final String fieldOfficerName,
+            final SavingsAccountStatusEnumData status, final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency,
+            final BigDecimal interestRate, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
+            final SavingsAccountSummaryData summary, final boolean allowOverdraft, final BigDecimal overdraftLimit,
+            final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance,
+            final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds,
+            final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) {
+
+        final Collection<SavingsProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountTransactionData> transactions = null;
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+
+        return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
+                productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions,
+                fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance,
+                enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
+                nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    public static SavingsAccountData lookup(final Long accountId, final String accountNo, final EnumOptionData depositType) {
+
+        final String externalId = null;
+        final Long productId = null;
+        final Long groupId = null;
+        final Long clientId = null;
+        final String clientName = null;
+        final String groupName = null;
+        final String productName = null;
+        final Long fieldOfficerId = null;
+        final String fieldOfficerName = null;
+        final SavingsAccountStatusEnumData status = null;
+        final SavingsAccountApplicationTimelineData timeline = null;
+        final CurrencyData currency = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        // final BigDecimal withdrawalFeeAmount = null;
+        // final EnumOptionData withdrawalFeeType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        // final BigDecimal annualFeeAmount = null;
+        // final MonthDay annualFeeOnMonthDay = null;
+        // final LocalDate annualFeeNextDueDate = null;
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = null;
+        final BigDecimal nominalAnnualInterestRateOverdraft = null;
+        final BigDecimal minOverdraftForInterestCalculation = null;
+        final BigDecimal minRequiredBalance = null;
+        final boolean enforceMinRequiredBalance = false;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final BigDecimal onHoldFunds = null;
+
+        final SavingsAccountSummaryData summary = null;
+        final Collection<SavingsAccountTransactionData> transactions = null;
+
+        final Collection<SavingsProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+
+        return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
+                productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions,
+                fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance,
+                enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
+                nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo,
+            final EnumOptionData depositType, final Long productId, final String productName, final SavingsAccountStatusEnumData status) {
+
+        final String externalId = null;
+        final Long groupId = null;
+        final Long clientId = null;
+        final String clientName = null;
+        final String groupName = null;
+        final Long fieldOfficerId = null;
+        final String fieldOfficerName = null;
+        final SavingsAccountApplicationTimelineData timeline = null;
+        final CurrencyData currency = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        // final BigDecimal withdrawalFeeAmount = null;
+        // final EnumOptionData withdrawalFeeType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        // final BigDecimal annualFeeAmount = null;
+        // final MonthDay annualFeeOnMonthDay = null;
+        // final LocalDate annualFeeNextDueDate = null;
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = null;
+        final BigDecimal nominalAnnualInterestRateOverdraft = null;
+        final BigDecimal minOverdraftForInterestCalculation = null;
+        final BigDecimal minRequiredBalance = null;
+        final boolean enforceMinRequiredBalance = false;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final BigDecimal onHoldFunds = null;
+
+        final SavingsAccountSummaryData summary = null;
+        final Collection<SavingsAccountTransactionData> transactions = null;
+
+        final Collection<SavingsProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+
+        return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
+                productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions,
+                fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance,
+                enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
+                nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template,
+            final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges) {
+
+        if (template == null) {
+            final Collection<SavingsProductData> productOptions = null;
+            final Collection<StaffData> fieldOfficerOptions = null;
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+            final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+            final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+            final Collection<ChargeData> chargeOptions = null;
+
+            return withTemplateOptions(account, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
+                    interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                    lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions);
+        }
+
+        return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId,
+                account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName,
+                account.fieldOfficerId, account.fieldOfficerName, account.status, account.timeline, account.currency,
+                account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType,
+                account.interestCalculationType, account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance,
+                account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary,
+                transactions, template.productOptions, template.fieldOfficerOptions, template.interestCompoundingPeriodTypeOptions,
+                template.interestPostingPeriodTypeOptions, template.interestCalculationTypeOptions,
+                template.interestCalculationDaysInYearTypeOptions, template.lockinPeriodFrequencyTypeOptions,
+                template.withdrawalFeeTypeOptions, charges, template.chargeOptions, account.allowOverdraft, account.overdraftLimit,
+                account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation,
+                account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation);
+    }
+
+    public static SavingsAccountData withTemplateOptions(final SavingsAccountData account,
+            final Collection<SavingsProductData> productOptions, final Collection<StaffData> fieldOfficerOptions,
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges,
+            final Collection<ChargeData> chargeOptions) {
+
+        return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId,
+                account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName,
+                account.fieldOfficerId, account.fieldOfficerName, account.status, account.timeline, account.currency,
+                account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType,
+                account.interestCalculationType, account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance,
+                account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary,
+                transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, account.allowOverdraft, account.overdraftLimit,
+                account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation,
+                account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation);
+    }
+
+    public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
+            final String groupName) {
+
+        final Long id = null;
+        final String accountNo = null;
+        final EnumOptionData depositType = null;
+        final String externalId = null;
+        final Long productId = null;
+        final String productName = null;
+        final Long fieldOfficerId = null;
+        final String fieldOfficerName = null;
+        final SavingsAccountStatusEnumData status = null;
+        final SavingsAccountApplicationTimelineData timeline = null;
+        final CurrencyData currency = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        // final BigDecimal withdrawalFeeAmount = null;
+        // final EnumOptionData withdrawalFeeType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        // final BigDecimal annualFeeAmount = null;
+        // final MonthDay annualFeeOnMonthDay = null;
+        // final LocalDate annualFeeNextDueDate = null;
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = null;
+        final BigDecimal nominalAnnualInterestRateOverdraft = null;
+        final BigDecimal minOverdraftForInterestCalculation = null;
+        final BigDecimal minRequiredBalance = null;
+        final boolean enforceMinRequiredBalance = false;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final BigDecimal onHoldFunds = null;
+
+        final SavingsAccountSummaryData summary = null;
+        final Collection<SavingsAccountTransactionData> transactions = null;
+
+        final Collection<SavingsProductData> productOptions = null;
+        final Collection<StaffData> fieldOfficerOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+        final Collection<SavingsAccountChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+
+        return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
+                productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions,
+                fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance,
+                enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
+                nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId,
+            final Long groupId, final String groupName, final Long clientId, final String clientName, final Long productId,
+            final String productName, final Long fieldofficerId, final String fieldofficerName, final SavingsAccountStatusEnumData status,
+            final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final BigDecimal nominalAnnualInterestRate,
+            final EnumOptionData interestPeriodType, final EnumOptionData interestPostingPeriodType,
+            final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType,
+            final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
+            final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
+            final SavingsAccountSummaryData summary, final Collection<SavingsAccountTransactionData> transactions,
+            final Collection<SavingsProductData> productOptions, final Collection<StaffData> fieldOfficerOptions,
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> chargeOptions, final boolean allowOverdraft,
+            final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance,
+            final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds,
+            final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) {
+        this.id = id;
+        this.accountNo = accountNo;
+        this.depositType = depositType;
+        this.externalId = externalId;
+        this.groupId = groupId;
+        this.groupName = groupName;
+        this.clientId = clientId;
+        this.clientName = clientName;
+        this.savingsProductId = productId;
+        this.savingsProductName = productName;
+        this.fieldOfficerId = fieldofficerId;
+        this.fieldOfficerName = fieldofficerName;
+        this.status = status;
+        this.timeline = timeline;
+        this.currency = currency;
+        this.nominalAnnualInterestRate = nominalAnnualInterestRate;
+        this.interestCompoundingPeriodType = interestPeriodType;
+        this.interestPostingPeriodType = interestPostingPeriodType;
+        this.interestCalculationType = interestCalculationType;
+        this.interestCalculationDaysInYearType = interestCalculationDaysInYearType;
+        this.minRequiredOpeningBalance = minRequiredOpeningBalance;
+        this.lockinPeriodFrequency = lockinPeriodFrequency;
+        this.lockinPeriodFrequencyType = lockinPeriodFrequencyType;
+        // this.withdrawalFeeAmount = withdrawalFeeAmount;
+        // this.withdrawalFeeType = withdrawalFeeType;
+        this.withdrawalFeeForTransfers = withdrawalFeeForTransfers;
+        // this.annualFeeAmount = annualFeeAmount;
+        // this.annualFeeOnMonthDay = annualFeeOnMonthDay;
+        // this.annualFeeNextDueDate = annualFeeNextDueDate;
+
+        this.summary = summary;
+        this.transactions = transactions;
+
+        this.productOptions = productOptions;
+        this.fieldOfficerOptions = fieldOfficerOptions;
+        this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions;
+        this.interestPostingPeriodTypeOptions = interestPostingPeriodTypeOptions;
+        this.interestCalculationTypeOptions = interestCalculationTypeOptions;
+        this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions;
+        this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions;
+        this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions;
+
+        this.charges = charges;// charges associated with Savings account
+        // charges available for adding to Savings account
+        this.chargeOptions = chargeOptions;
+
+        this.withdrawalFee = getWithdrawalFee();
+
+        this.annualFee = getAnnualFee();
+        this.allowOverdraft = allowOverdraft;
+        this.overdraftLimit = overdraftLimit;
+        this.nominalAnnualInterestRateOverdraft = nominalAnnualInterestRateOverdraft;
+        this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation;
+        this.minRequiredBalance = minRequiredBalance;
+        this.enforceMinRequiredBalance = enforceMinRequiredBalance;
+        this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
+        this.onHoldFunds = onHoldFunds;
+    }
+
+    private SavingsAccountChargeData getWithdrawalFee() {
+        for (SavingsAccountChargeData charge : this.charges()) {
+            if (charge.isWithdrawalFee()) return charge;
+        }
+        return null;
+    }
+
+    private SavingsAccountChargeData getAnnualFee() {
+        for (SavingsAccountChargeData charge : this.charges()) {
+            if (charge.isAnnualFee()) return charge;
+        }
+        return null;
+    }
+
+    public Long id() {
+        return this.id;
+    }
+
+    public Long clientId() {
+        return this.clientId;
+    }
+
+    public Long groupId() {
+        return this.groupId;
+    }
+
+    public Long productId() {
+        return this.savingsProductId;
+    }
+
+    public CurrencyData currency() {
+        return this.currency;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final SavingsAccountData rhs = (SavingsAccountData) obj;
+        return new EqualsBuilder().append(this.id, rhs.id).append(this.accountNo, rhs.accountNo).isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).toHashCode();
+    }
+
+    public Collection<SavingsAccountChargeData> charges() {
+        return (this.charges == null) ? new HashSet<SavingsAccountChargeData>() : this.charges;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataDTO.java
new file mode 100755
index 0000000..c7506d3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataDTO.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.savings.data;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+
+public class SavingsAccountDataDTO {
+
+    private final Client client;
+    private final Group group;
+    private final SavingsProduct savingsProduct;
+    private final LocalDate applicationDate;
+    private final AppUser appliedBy;
+    private final DateTimeFormatter fmt;
+
+    public SavingsAccountDataDTO(final Client client, final Group group, final SavingsProduct savingsProduct,
+            final LocalDate applicationDate, final AppUser appliedBy, final DateTimeFormatter fmt) {
+        this.client = client;
+        this.group = group;
+        this.savingsProduct = savingsProduct;
+        this.applicationDate = applicationDate;
+        this.appliedBy = appliedBy;
+        this.fmt = fmt;
+    }
+
+    public Client getClient() {
+        return this.client;
+    }
+
+    public Group getGroup() {
+        return this.group;
+    }
+
+    public SavingsProduct getSavingsProduct() {
+        return this.savingsProduct;
+    }
+
+    public LocalDate getApplicationDate() {
+        return this.applicationDate;
+    }
+
+    public AppUser getAppliedBy() {
+        return this.appliedBy;
+    }
+
+    public DateTimeFormatter getFmt() {
+        return this.fmt;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java
new file mode 100644
index 0000000..1f895ad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java
@@ -0,0 +1,471 @@
+/**
+ * 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.savings.data;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.externalIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.groupIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+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.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class SavingsAccountDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public SavingsAccountDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForSubmit(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SavingsApiConstants.SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element);
+        if (clientId != null) {
+            baseDataValidator.reset().parameter(clientIdParamName).value(clientId).longGreaterThanZero();
+        }
+
+        final Long groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element);
+        if (groupId != null) {
+            baseDataValidator.reset().parameter(groupIdParamName).value(groupId).longGreaterThanZero();
+        }
+
+        if (clientId == null && groupId == null) {
+            baseDataValidator.reset().parameter(clientIdParamName).value(clientId).notNull().integerGreaterThanZero();
+        }
+
+        final Long productId = this.fromApiJsonHelper.extractLongNamed(productIdParamName, element);
+        baseDataValidator.reset().parameter(productIdParamName).value(productId).notNull().integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists(fieldOfficerIdParamName, element)) {
+            final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element);
+            baseDataValidator.reset().parameter(fieldOfficerIdParamName).value(fieldOfficerId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element);
+        baseDataValidator.reset().parameter(submittedOnDateParamName).value(submittedOnDate).notNull();
+
+        if (this.fromApiJsonHelper.parameterExists(accountNoParamName, element)) {
+            final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParamName, element);
+            baseDataValidator.reset().parameter(accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParamName, element);
+            baseDataValidator.reset().parameter(externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
+            final BigDecimal interestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName,
+                    element);
+            baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(interestRate).notNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
+            final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCompoundingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestPostingPeriodTypeParamName, element)) {
+            final Integer interestPostingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestPostingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestPostingPeriodTypeParamName).value(interestPostingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsPostingInterestPeriodType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
+            final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName,
+                    element);
+            baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull()
+                    .isOneOfTheseValues(SavingsInterestCalculationType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
+            final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCalculationDaysInYearTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType)
+                    .notNull().isOneOfTheseValues(SavingsInterestCalculationDaysInYearType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
+            final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minRequiredOpeningBalanceParamName, element);
+            baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
+
+            final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                    element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).integerZeroOrGreater();
+
+            if (lockinPeriodFrequency != null) {
+                final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                        lockinPeriodFrequencyTypeParamName, element);
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull()
+                        .inMinMaxRange(0, 3);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
+            final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    lockinPeriodFrequencyTypeParamName, element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
+
+            if (lockinPeriodFrequencyType != null) {
+                final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                        element);
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull()
+                        .integerZeroOrGreater();
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
+            final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(
+                    withdrawalFeeForTransfersParamName, element);
+            baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers)
+                    .ignoreIfNull().validateForBooleanValue();
+        }
+
+        validateSavingsCharges(element, baseDataValidator);
+
+        validateOverdraftParams(baseDataValidator, element);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateSavingsCharges(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            final String monthDayFormat = this.fromApiJsonHelper.extractMonthDayFormatParameter(topLevelJsonElement);
+            if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+
+                    final JsonObject savingsChargeElement = array.get(i).getAsJsonObject();
+
+                    // final Long id =
+                    // this.fromApiJsonHelper.extractLongNamed(idParamName,
+                    // savingsChargeElement);
+
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement);
+                    baseDataValidator.reset().parameter(chargeIdParamName).value(chargeId).longGreaterThanZero();
+
+                    final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale);
+                    baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
+
+                    if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, savingsChargeElement)) {
+                        final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement,
+                                monthDayFormat, locale);
+                        baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDay).notNull();
+                    }
+
+                    if (this.fromApiJsonHelper.parameterExists(feeIntervalParamName, savingsChargeElement)) {
+                        final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, savingsChargeElement,
+                                Locale.getDefault());
+                        baseDataValidator.reset().parameter(feeIntervalParamName).value(feeInterval).notNull().inMinMaxRange(1, 12);
+                    }
+                }
+            }
+        }
+    }
+
+    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, SavingsApiConstants.SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        Long clientId = null;
+        if (this.fromApiJsonHelper.parameterExists(clientIdParamName, element)) {
+            clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element);
+            baseDataValidator.reset().parameter(clientIdParamName).value(clientId).ignoreIfNull().longGreaterThanZero();
+
+            Long groupId = null;
+            if (this.fromApiJsonHelper.parameterExists(productIdParamName, element)) {
+                groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element);
+                baseDataValidator.reset().parameter(groupIdParamName).value(groupId).ignoreIfNull().longGreaterThanZero();
+            }
+
+            if (clientId == null && groupId == null) {
+                // either clientId or groupId must exists if param passed for
+                // update.
+                baseDataValidator.reset().parameter(clientIdParamName).value(clientId).notNull().integerGreaterThanZero();
+            }
+        }
+
+        Long groupId = null;
+        if (this.fromApiJsonHelper.parameterExists(groupIdParamName, element)) {
+            groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element);
+            baseDataValidator.reset().parameter(groupIdParamName).value(groupId).ignoreIfNull().longGreaterThanZero();
+
+            if (this.fromApiJsonHelper.parameterExists(clientIdParamName, element)) {
+                clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element);
+                baseDataValidator.reset().parameter(clientIdParamName).value(clientId).ignoreIfNull().longGreaterThanZero();
+            }
+
+            if (clientId == null && groupId == null) {
+                // either clientId or groupId must exists if param passed for
+                // update.
+                baseDataValidator.reset().parameter(clientIdParamName).value(clientId).notNull().integerGreaterThanZero();
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(productIdParamName, element)) {
+            final Long productId = this.fromApiJsonHelper.extractLongNamed(productIdParamName, element);
+            baseDataValidator.reset().parameter(productIdParamName).value(productId).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(fieldOfficerIdParamName, element)) {
+            final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element);
+            baseDataValidator.reset().parameter(fieldOfficerIdParamName).value(fieldOfficerId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(submittedOnDateParamName, element)) {
+            final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element);
+            baseDataValidator.reset().parameter(submittedOnDateParamName).value(submittedOnDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(accountNoParamName, element)) {
+            final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParamName, element);
+            baseDataValidator.reset().parameter(accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(externalIdParamName, element)) {
+            final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParamName, element);
+            baseDataValidator.reset().parameter(externalIdParamName).value(externalId).notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
+            final BigDecimal interestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName,
+                    element);
+            baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(interestRate).notNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
+            final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCompoundingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestPostingPeriodTypeParamName, element)) {
+            final Integer interestPostingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestPostingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestPostingPeriodTypeParamName).value(interestPostingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsPostingInterestPeriodType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
+            final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName,
+                    element);
+            baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull()
+                    .isOneOfTheseValues(SavingsInterestCalculationType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
+            final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCalculationDaysInYearTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType)
+                    .notNull().isOneOfTheseValues(SavingsInterestCalculationDaysInYearType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
+            final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minRequiredOpeningBalanceParamName, element);
+            baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).ignoreIfNull()
+                    .zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
+            final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                    element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull()
+                    .integerZeroOrGreater();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
+            final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    lockinPeriodFrequencyTypeParamName, element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
+            final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(
+                    withdrawalFeeForTransfersParamName, element);
+            baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers)
+                    .ignoreIfNull().validateForBooleanValue();
+        }
+
+        validateOverdraftParams(baseDataValidator, element);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private void validateOverdraftParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        if (this.fromApiJsonHelper.parameterExists(allowOverdraftParamName, element)) {
+            final Boolean allowOverdraft = this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element);
+            baseDataValidator.reset().parameter(allowOverdraftParamName).value(allowOverdraft).ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(overdraftLimitParamName, element)) {
+            final BigDecimal overdraftLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(overdraftLimitParamName, element);
+            baseDataValidator.reset().parameter(overdraftLimitParamName).value(overdraftLimit).ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateOverdraftParamName, element)) {
+            final BigDecimal nominalAnnualInterestRateOverdraft = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateOverdraftParamName, element);
+            baseDataValidator.reset().parameter(nominalAnnualInterestRateOverdraftParamName).value(nominalAnnualInterestRateOverdraft).ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(minOverdraftForInterestCalculationParamName, element)) {
+            final BigDecimal minOverdraftForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minOverdraftForInterestCalculationParamName, element);
+            baseDataValidator.reset().parameter(minOverdraftForInterestCalculationParamName).value(minOverdraftForInterestCalculation).ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+    }
+
+    public void validateForAssignSavingsOfficer(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+        
+        final Set<String> supportedParameters = new HashSet<>(Arrays.asList("fromSavingsOfficerId","toSavingsOfficerId","assignmentDate","locale","dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long toSavingsOfficerId = this.fromApiJsonHelper.extractLongNamed("toSavingsOfficerId", element);
+        baseDataValidator.reset().parameter("toSavingsOfficerId").value(toSavingsOfficerId).notNull().integerGreaterThanZero();
+
+        final String assignmentDateStr = this.fromApiJsonHelper.extractStringNamed("assignmentDate", element);
+        baseDataValidator.reset().parameter("assignmentDate").value(assignmentDateStr).notBlank();
+
+        if (!StringUtils.isBlank(assignmentDateStr)) {
+            final LocalDate assignmentDate = this.fromApiJsonHelper.extractLocalDateNamed("assignmentDate", element);
+            baseDataValidator.reset().parameter("assignmentDate").value(assignmentDate).notNull();
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }      
+
+    }
+
+    public void validateForUnAssignSavingsOfficer(final String json) {
+    	if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+        
+        final Set<String> supportedParameters = new HashSet<>(Arrays.asList("unassignedDate","locale","dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String unassignedDateStr = this.fromApiJsonHelper.extractStringNamed("unassignedDate", element);
+        baseDataValidator.reset().parameter("unassignedDate").value(unassignedDateStr).notBlank();
+
+        if (!StringUtils.isBlank(unassignedDateStr)) {
+            final LocalDate unassignedDate = this.fromApiJsonHelper.extractLocalDateNamed("unassignedDate", element);
+            baseDataValidator.reset().parameter("unassignedDate").value(unassignedDate).notNull();
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                "Validation errors exist.", dataValidationErrors); }     
+}
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountStatusEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountStatusEnumData.java
new file mode 100644
index 0000000..215a475
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountStatusEnumData.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.savings.data;
+
+/**
+ * Immutable data object represent savings account status enumerations.
+ */
+public class SavingsAccountStatusEnumData {
+
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String code;
+    @SuppressWarnings("unused")
+    private final String value;
+    @SuppressWarnings("unused")
+    private final boolean submittedAndPendingApproval;
+    @SuppressWarnings("unused")
+    private final boolean approved;
+    @SuppressWarnings("unused")
+    private final boolean rejected;
+    @SuppressWarnings("unused")
+    private final boolean withdrawnByApplicant;
+    @SuppressWarnings("unused")
+    private final boolean active;
+    @SuppressWarnings("unused")
+    private final boolean closed;
+    @SuppressWarnings("unused")
+    private final boolean prematureClosed;
+    @SuppressWarnings("unused")
+    private final boolean transferInProgress;
+    @SuppressWarnings("unused")
+    private final boolean transferOnHold;
+
+    public SavingsAccountStatusEnumData(final Long id, final String code, final String value, final boolean submittedAndPendingApproval,
+            final boolean approved, final boolean rejected, final boolean withdrawnByApplicant, final boolean active, final boolean closed,
+            final boolean prematureClosed, final boolean transferInProgress, final boolean transferOnHold) {
+        this.id = id;
+        this.code = code;
+        this.value = value;
+        this.submittedAndPendingApproval = submittedAndPendingApproval;
+        this.approved = approved;
+        this.rejected = rejected;
+        this.withdrawnByApplicant = withdrawnByApplicant;
+        this.active = active;
+        this.closed = closed;
+        this.prematureClosed = prematureClosed;
+        this.transferInProgress = transferInProgress;
+        this.transferOnHold = transferOnHold;
+    }
+
+    public Long id() {
+        return this.id;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java
new file mode 100644
index 0000000..627169a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java
@@ -0,0 +1,59 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+/**
+ * Immutable data object representing Savings Account summary information.
+ */
+@SuppressWarnings("unused")
+public class SavingsAccountSummaryData {
+
+    private final CurrencyData currency;
+    private final BigDecimal totalDeposits;
+    private final BigDecimal totalWithdrawals;
+    private final BigDecimal totalWithdrawalFees;
+    private final BigDecimal totalAnnualFees;
+    private final BigDecimal totalInterestEarned;
+    private final BigDecimal totalInterestPosted;
+    private final BigDecimal accountBalance;
+    private final BigDecimal totalFeeCharge;
+    private final BigDecimal totalPenaltyCharge;
+    private final BigDecimal totalOverdraftInterestDerived;
+
+    public SavingsAccountSummaryData(final CurrencyData currency, final BigDecimal totalDeposits, final BigDecimal totalWithdrawals,
+            final BigDecimal totalWithdrawalFees, final BigDecimal totalAnnualFees, final BigDecimal totalInterestEarned,
+            final BigDecimal totalInterestPosted, final BigDecimal accountBalance, final BigDecimal totalFeeCharge,
+            final BigDecimal totalPenaltyCharge, final BigDecimal totalOverdraftInterestDerived) {
+        this.currency = currency;
+        this.totalDeposits = totalDeposits;
+        this.totalWithdrawals = totalWithdrawals;
+        this.totalWithdrawalFees = totalWithdrawalFees;
+        this.totalAnnualFees = totalAnnualFees;
+        this.totalInterestEarned = totalInterestEarned;
+        this.totalInterestPosted = totalInterestPosted;
+        this.accountBalance = accountBalance;
+        this.totalFeeCharge = totalFeeCharge;
+        this.totalPenaltyCharge = totalPenaltyCharge;
+        this.totalOverdraftInterestDerived = totalOverdraftInterestDerived;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTO.java
new file mode 100755
index 0000000..fb91c85
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTO.java
@@ -0,0 +1,98 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+
+public class SavingsAccountTransactionDTO {
+
+    private final DateTimeFormatter formatter;
+    private final LocalDate transactionDate;
+    private final BigDecimal transactionAmount;
+    private final PaymentDetail paymentDetail;
+    private final Date createdDate;
+    private final Long savingsAccountId;
+    private final AppUser appUser;
+
+    public SavingsAccountTransactionDTO(final DateTimeFormatter formatter, final LocalDate transactionDate,
+            final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final Date createdDate, final AppUser appUser) {
+        this.formatter = formatter;
+        this.transactionDate = transactionDate;
+        this.transactionAmount = transactionAmount;
+        this.paymentDetail = paymentDetail;
+        this.createdDate = createdDate;
+        this.savingsAccountId = null;
+        this.appUser = appUser;
+    }
+
+    /**
+     * This constructor is used for bulk deposit transactions
+     * 
+     * @param formatter
+     * @param transactionDate
+     * @param transactionAmount
+     * @param paymentDetail
+     * @param createdDate
+     * @param savingsAccountId
+     */
+    public SavingsAccountTransactionDTO(DateTimeFormatter formatter, LocalDate transactionDate, BigDecimal transactionAmount,
+            PaymentDetail paymentDetail, Date createdDate, Long savingsAccountId, AppUser appUser) {
+        this.formatter = formatter;
+        this.transactionDate = transactionDate;
+        this.transactionAmount = transactionAmount;
+        this.paymentDetail = paymentDetail;
+        this.createdDate = createdDate;
+        this.savingsAccountId = savingsAccountId;
+        this.appUser = appUser;
+    }
+
+    public DateTimeFormatter getFormatter() {
+        return this.formatter;
+    }
+
+    public LocalDate getTransactionDate() {
+        return this.transactionDate;
+    }
+
+    public BigDecimal getTransactionAmount() {
+        return this.transactionAmount;
+    }
+
+    public PaymentDetail getPaymentDetail() {
+        return this.paymentDetail;
+    }
+
+    public Date getCreatedDate() {
+        return this.createdDate;
+    }
+
+    public Long getSavingsAccountId() {
+        return this.savingsAccountId;
+    }
+
+    public AppUser getAppUser() {
+        return this.appUser;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java
new file mode 100644
index 0000000..75b1cc9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java
@@ -0,0 +1,140 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing a savings account transaction.
+ */
+@SuppressWarnings("unused")
+public class SavingsAccountTransactionData {
+
+    private final Long id;
+    private final SavingsAccountTransactionEnumData transactionType;
+    private final Long accountId;
+    private final String accountNo;
+    private final LocalDate date;
+    private final CurrencyData currency;
+    private final PaymentDetailData paymentDetailData;
+    private final BigDecimal amount;
+    private final BigDecimal runningBalance;
+    private final boolean reversed;
+    private final AccountTransferData transfer;
+    private final LocalDate submittedOnDate;
+
+    // templates
+    final Collection<PaymentTypeData> paymentTypeOptions;
+
+    public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
+            final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
+            final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed,
+            final AccountTransferData transfer) {
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
+                amount, runningBalance, reversed, transfer, paymentTypeOptions);
+    }
+
+    public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
+                                                       final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
+                                                       final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed,
+                                                       final AccountTransferData transfer,final LocalDate submittedOnDate) {
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
+                amount, runningBalance, reversed, transfer, paymentTypeOptions,submittedOnDate);
+    }
+
+    public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo,
+            final LocalDate defaultLocalDate, final CurrencyData currency) {
+        final Long id = null;
+        final SavingsAccountTransactionEnumData transactionType = null;
+        final BigDecimal amount = null;
+        final BigDecimal runningBalance = null;
+        final boolean reversed = false;
+        final PaymentDetailData paymentDetailData = null;
+        final Collection<CodeValueData> paymentTypeOptions = null;
+        return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate,
+                currency, amount, runningBalance, reversed, null, null);
+    }
+
+    public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData,
+            final Collection<PaymentTypeData> paymentTypeOptions) {
+        return new SavingsAccountTransactionData(savingsAccountTransactionData.id, savingsAccountTransactionData.transactionType,
+                savingsAccountTransactionData.paymentDetailData, savingsAccountTransactionData.accountId,
+                savingsAccountTransactionData.accountNo, savingsAccountTransactionData.date, savingsAccountTransactionData.currency,
+                savingsAccountTransactionData.amount, savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.reversed,
+                savingsAccountTransactionData.transfer, paymentTypeOptions);
+    }
+
+    private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
+            final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
+            final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed,
+            final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions) {
+
+        this(id,transactionType,paymentDetailData,savingsId, savingsAccountNo,date,
+        currency,amount,runningBalance, reversed,
+        transfer, paymentTypeOptions,null);
+
+    }
+
+    private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
+                                          final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
+                                          final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed,
+                                          final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions,final LocalDate submittedOnDate) {
+        this.id = id;
+        this.transactionType = transactionType;
+        this.paymentDetailData = paymentDetailData;
+        this.accountId = savingsId;
+        this.accountNo = savingsAccountNo;
+        this.date = date;
+        this.currency = currency;
+        this.amount = amount;
+        this.runningBalance = runningBalance;
+        this.reversed = reversed;
+        this.transfer = transfer;
+        this.paymentTypeOptions = paymentTypeOptions;
+        this.submittedOnDate = submittedOnDate;
+    }
+
+    public static SavingsAccountTransactionData withWithDrawalTransactionDetails(
+            final SavingsAccountTransactionData savingsAccountTransactionData) {
+
+        final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
+        final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations
+                .transactionType(SavingsAccountTransactionType.WITHDRAWAL.getValue());
+
+        return new SavingsAccountTransactionData(savingsAccountTransactionData.id, transactionType,
+                savingsAccountTransactionData.paymentDetailData, savingsAccountTransactionData.accountId,
+                savingsAccountTransactionData.accountNo, currentDate, savingsAccountTransactionData.currency,
+                savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.runningBalance,
+                savingsAccountTransactionData.reversed, savingsAccountTransactionData.transfer,
+                savingsAccountTransactionData.paymentTypeOptions);
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..ca855b6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
@@ -0,0 +1,171 @@
+/**
+ * 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.savings.data;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.activatedOnDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.bankNumberParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.checkNumberParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.closedOnDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.paymentTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.receiptNumberParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.routingCodeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.transactionAccountNumberParamName;
+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 java.lang.reflect.Type;
+import java.math.BigDecimal;
+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.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class SavingsAccountTransactionDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public SavingsAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validate(final JsonCommand command) {
+
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                SavingsApiConstants.SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SavingsApiConstants.SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
+        baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull();
+
+        final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element);
+        baseDataValidator.reset().parameter(transactionAmountParamName).value(transactionAmount).notNull().positiveAmount();
+
+        validatePaymentTypeDetails(baseDataValidator, element);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateActivation(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                SavingsApiConstants.SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(activatedOnDateParamName, element);
+        baseDataValidator.reset().parameter(activatedOnDateParamName).value(activationDate).notNull();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateClosing(final JsonCommand command) {
+        final String json = command.json();
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                SavingsApiConstants.SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final JsonElement element = command.parsedJson();
+
+        final LocalDate closedonDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
+        baseDataValidator.reset().parameter(closedOnDateParamName).value(closedonDate).notNull();
+
+        if (this.fromApiJsonHelper.parameterExists(withdrawBalanceParamName, element)) {
+            final Boolean withdrawBalance = this.fromApiJsonHelper.extractBooleanNamed(withdrawBalanceParamName, element);
+            baseDataValidator.reset().parameter(withdrawBalanceParamName).value(withdrawBalance).isOneOfTheseValues(true, false);
+        }
+
+        validatePaymentTypeDetails(baseDataValidator, element);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validatePaymentTypeDetails(final DataValidatorBuilder baseDataValidator, JsonElement element) {
+        // Validate all string payment detail fields for max length
+        boolean checkPaymentTypeDetails = false;
+        final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
+        baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
+        final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
+                routingCodeParamName, receiptNumberParamName, bankNumberParamName));
+        for (final String paymentDetailParameterName : paymentDetailParameters) {
+            final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
+            baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
+                    .notExceedingLengthOf(50);
+            if(paymentDetailParameterValue != null && !paymentDetailParameterValue.equals("")){
+                checkPaymentTypeDetails = true;
+            }
+        }
+        if(checkPaymentTypeDetails){
+            baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).notBlank().integerGreaterThanZero();
+        }
+
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            //
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java
new file mode 100644
index 0000000..014bc32
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java
@@ -0,0 +1,125 @@
+/**
+ * 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.savings.data;
+
+import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
+
+/**
+ * Immutable data object represent savings account transaction type
+ * enumerations.
+ */
+public class SavingsAccountTransactionEnumData {
+
+    private final Long id;
+    private final String code;
+    private final String value;
+
+    private final boolean deposit;
+    private final boolean withdrawal;
+    private final boolean interestPosting;
+    private final boolean feeDeduction;
+    private final boolean initiateTransfer;
+    private final boolean approveTransfer;
+    private final boolean withdrawTransfer;
+    private final boolean rejectTransfer;
+    private final boolean overdraftInterest;
+    private final boolean writtenoff;
+    private final boolean overdraftFee = true;
+
+    public SavingsAccountTransactionEnumData(final Long id, final String code, final String value) {
+        this.id = id;
+        this.code = code;
+        this.value = value;
+        this.deposit = Long.valueOf(SavingsAccountTransactionType.DEPOSIT.getValue()).equals(this.id);
+        this.withdrawal = Long.valueOf(SavingsAccountTransactionType.WITHDRAWAL.getValue()).equals(this.id);
+        this.interestPosting = Long.valueOf(SavingsAccountTransactionType.INTEREST_POSTING.getValue()).equals(this.id);
+        this.feeDeduction = Long.valueOf(SavingsAccountTransactionType.ANNUAL_FEE.getValue()).equals(this.id)
+                || Long.valueOf(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue()).equals(this.id)
+                || Long.valueOf(SavingsAccountTransactionType.PAY_CHARGE.getValue()).equals(this.id);
+        this.initiateTransfer = Long.valueOf(SavingsAccountTransactionType.INITIATE_TRANSFER.getValue()).equals(this.id);
+        this.approveTransfer = Long.valueOf(SavingsAccountTransactionType.APPROVE_TRANSFER.getValue()).equals(this.id);
+        this.withdrawTransfer = Long.valueOf(SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue()).equals(this.id);
+        this.rejectTransfer = Long.valueOf(SavingsAccountTransactionType.REJECT_TRANSFER.getValue()).equals(this.id);
+        this.writtenoff = Long.valueOf(SavingsAccountTransactionType.WRITTEN_OFF.getValue()).equals(this.id);
+        this.overdraftInterest = Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id);
+        // this.overdraftFee =
+        // Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id);
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    public boolean isDeposit() {
+        return this.deposit;
+    }
+
+    public boolean isWithdrawal() {
+        return this.withdrawal;
+    }
+
+    public boolean isDepositOrWithdrawal() {
+        return this.deposit || this.withdrawal;
+    }
+
+    public boolean isInterestPosting() {
+        return this.interestPosting;
+    }
+
+    public boolean isFeeDeduction() {
+        return this.feeDeduction;
+    }
+
+    public boolean isInitiateTransfer() {
+        return this.initiateTransfer;
+    }
+
+    public boolean isApproveTransfer() {
+        return this.approveTransfer;
+    }
+
+    public boolean isWithdrawTransfer() {
+        return this.withdrawTransfer;
+    }
+
+    public boolean isRejectTransfer() {
+        return this.rejectTransfer;
+    }
+
+    public boolean isOverdraftInterest() {
+        return this.overdraftInterest;
+    }
+
+    public boolean isWrittenoff() {
+        return this.writtenoff;
+    }
+
+    public boolean isOverdraftFee() {
+        return this.overdraftFee;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java
new file mode 100644
index 0000000..94c6ed6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java
@@ -0,0 +1,396 @@
+/**
+ * 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.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+
+/**
+ * Immutable data object represent a savings product.
+ */
+public class SavingsProductData {
+
+    private final Long id;
+    private final String name;
+    private final String shortName;
+    private final String description;
+    private final CurrencyData currency;
+    private final BigDecimal nominalAnnualInterestRate;
+    private final EnumOptionData interestCompoundingPeriodType;
+    private final EnumOptionData interestPostingPeriodType;
+    private final EnumOptionData interestCalculationType;
+    private final EnumOptionData interestCalculationDaysInYearType;
+    private final BigDecimal minRequiredOpeningBalance;
+    private final Integer lockinPeriodFrequency;
+    private final EnumOptionData lockinPeriodFrequencyType;
+    private final boolean withdrawalFeeForTransfers;
+    private final boolean allowOverdraft;
+    private final BigDecimal overdraftLimit;
+    private final BigDecimal minRequiredBalance;
+    private final boolean enforceMinRequiredBalance;
+    private final BigDecimal minBalanceForInterestCalculation;
+
+    // accounting
+    private final EnumOptionData accountingRule;
+    private final Map<String, Object> accountingMappings;
+    private final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings;
+    private final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
+    private final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
+
+    // charges
+    private final Collection<ChargeData> charges;
+
+    // template
+    private final Collection<CurrencyData> currencyOptions;
+    private final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions;
+    private final Collection<EnumOptionData> interestPostingPeriodTypeOptions;
+    private final Collection<EnumOptionData> interestCalculationTypeOptions;
+    private final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions;
+    private final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions;
+    private final Collection<EnumOptionData> withdrawalFeeTypeOptions;
+    private final Collection<PaymentTypeData> paymentTypeOptions;
+    private final Collection<EnumOptionData> accountingRuleOptions;
+    private final Map<String, List<GLAccountData>> accountingMappingOptions;
+    private final Collection<ChargeData> chargeOptions;
+    private final Collection<ChargeData> penaltyOptions;
+	private final BigDecimal nominalAnnualInterestRateOverdraft;
+	private final BigDecimal minOverdraftForInterestCalculation;
+
+    public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
+            final Collection<ChargeData> penaltyOptions) {
+
+        final Long id = null;
+        final String name = null;
+        final String shortName = null;
+        final String description = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = null;
+        final BigDecimal minRequiredBalance = null;
+        final boolean enforceMinRequiredBalance = false;
+        final BigDecimal minBalanceForInterestCalculation = null;
+        final BigDecimal nominalAnnualInterestRateOverdraft = null;
+        final BigDecimal minOverdraftForInterestCalculation = null;
+
+
+        return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingRule, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
+                minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    public static SavingsProductData withCharges(final SavingsProductData product, final Collection<ChargeData> charges) {
+        return new SavingsProductData(product.id, product.name, product.shortName, product.description, product.currency,
+                product.nominalAnnualInterestRate, product.interestCompoundingPeriodType, product.interestPostingPeriodType,
+                product.interestCalculationType, product.interestCalculationDaysInYearType, product.minRequiredOpeningBalance,
+                product.lockinPeriodFrequency, product.lockinPeriodFrequencyType, product.withdrawalFeeForTransfers,
+                product.accountingRule, product.accountingMappings, product.paymentChannelToFundSourceMappings, product.currencyOptions,
+                product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions,
+                product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions,
+                product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions,
+                product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions,
+                product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings, product.allowOverdraft, product.overdraftLimit,
+                product.minRequiredBalance, product.enforceMinRequiredBalance, product.minBalanceForInterestCalculation,
+                product.nominalAnnualInterestRateOverdraft, product.minOverdraftForInterestCalculation);
+    }
+
+    /**
+     * Returns a {@link SavingsProductData} that contains and exist
+     * {@link SavingsProductData} data with further template data for dropdowns.
+     */
+    public static SavingsProductData withTemplate(final SavingsProductData existingProduct, final Collection<CurrencyData> currencyOptions,
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
+            final Collection<ChargeData> penaltyOptions) {
+
+        return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
+                existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
+                existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
+                existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance,
+                existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
+                existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule, existingProduct.accountingMappings,
+                existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions,
+                existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, existingProduct.allowOverdraft,
+                existingProduct.overdraftLimit, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance,
+                existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft,
+                existingProduct.minOverdraftForInterestCalculation);
+    }
+
+    public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct,
+            final Map<String, Object> accountingMappings,
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
+            final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
+            final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings) {
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+
+        return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
+                existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
+                existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
+                existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance,
+                existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
+                existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, existingProduct.allowOverdraft, existingProduct.overdraftLimit,
+                existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance,
+                existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft,
+                existingProduct.minOverdraftForInterestCalculation);
+    }
+
+    public static SavingsProductData instance(final Long id, final String name, final String shortName, final String description,
+            final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
+            final EnumOptionData accountingType, final boolean allowOverdraft, final BigDecimal overdraftLimit,
+            final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) {
+
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+
+        return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingType, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
+                minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    public static SavingsProductData lookup(final Long id, final String name) {
+
+        final String shortName = null;
+        final CurrencyData currency = null;
+        final String description = null;
+        final BigDecimal nominalAnnualInterestRate = null;
+        final EnumOptionData interestCompoundingPeriodType = null;
+        final EnumOptionData interestPostingPeriodType = null;
+        final EnumOptionData interestCalculationType = null;
+        final EnumOptionData interestCalculationDaysInYearType = null;
+        final BigDecimal minRequiredOpeningBalance = null;
+        final Integer lockinPeriodFrequency = null;
+        final EnumOptionData lockinPeriodFrequencyType = null;
+        final boolean withdrawalFeeForTransfers = false;
+        final EnumOptionData accountingType = null;
+        final Map<String, Object> accountingMappings = null;
+        final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = null;
+        final BigDecimal nominalAnnualInterestRateOverdraft = null;
+        final BigDecimal minOverdraftForInterestCalculation = null;
+        final BigDecimal minRequiredBalance = null;
+        final boolean enforceMinRequiredBalance = false;
+        final BigDecimal minBalanceForInterestCalculation = null;
+
+        final Collection<CurrencyData> currencyOptions = null;
+        final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+        final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+        final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+        final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+        final Collection<PaymentTypeData> paymentTypeOptions = null;
+        final Collection<EnumOptionData> accountingRuleOptions = null;
+        final Map<String, List<GLAccountData>> accountingMappingOptions = null;
+        final Collection<ChargeData> charges = null;
+        final Collection<ChargeData> chargeOptions = null;
+        final Collection<ChargeData> penaltyOptions = null;
+        final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
+        final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
+
+        return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingType, accountingMappings,
+                paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
+                interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
+                lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
+                accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
+                penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
+                minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    private SavingsProductData(final Long id, final String name, final String shortName, final String description,
+            final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType,
+            final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
+            final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
+            final EnumOptionData accountingType, final Map<String, Object> accountingMappings,
+            final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
+            final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
+            final Collection<EnumOptionData> interestCalculationTypeOptions,
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
+            final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
+            final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> charges,
+            final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions,
+            final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
+            final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings, final boolean allowOverdraft,
+            final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance,
+            final BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) {
+        this.id = id;
+        this.name = name;
+        this.shortName = shortName;
+        this.description = description;
+        this.currency = currency;
+        this.nominalAnnualInterestRate = nominalAnnualInterestRate;
+        this.interestCompoundingPeriodType = interestCompoundingPeriodType;
+        this.interestPostingPeriodType = interestPostingPeriodType;
+        this.interestCalculationType = interestCalculationType;
+        this.interestCalculationDaysInYearType = interestCalculationDaysInYearType;
+        this.accountingRule = accountingType;
+        this.minRequiredOpeningBalance = minRequiredOpeningBalance;
+        this.lockinPeriodFrequency = lockinPeriodFrequency;
+        this.lockinPeriodFrequencyType = lockinPeriodFrequencyType;
+        this.withdrawalFeeForTransfers = withdrawalFeeForTransfers;
+
+        this.currencyOptions = currencyOptions;
+        this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions;
+        this.interestPostingPeriodTypeOptions = interestPostingPeriodTypeOptions;
+        this.interestCalculationTypeOptions = interestCalculationTypeOptions;
+        this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions;
+        this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions;
+        this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions;
+
+        this.paymentTypeOptions = paymentTypeOptions;
+        this.accountingMappingOptions = accountingMappingOptions;
+        this.accountingRuleOptions = accountingRuleOptions;
+        if (accountingMappings == null || accountingMappings.isEmpty()) {
+            this.accountingMappings = null;
+        } else {
+            this.accountingMappings = accountingMappings;
+        }
+        this.paymentChannelToFundSourceMappings = paymentChannelToFundSourceMappings;
+
+        this.charges = charges;// charges associated with Savings product
+        this.chargeOptions = chargeOptions;// charges available for adding to
+                                           // Savings product
+        this.penaltyOptions = penaltyOptions;// penalties available for adding
+                                             // to Savings product
+
+        this.feeToIncomeAccountMappings = feeToIncomeAccountMappings;
+        this.penaltyToIncomeAccountMappings = penaltyToIncomeAccountMappings;
+        this.allowOverdraft = allowOverdraft;
+        this.overdraftLimit = overdraftLimit;
+        this.minRequiredBalance = minRequiredBalance;
+        this.enforceMinRequiredBalance = enforceMinRequiredBalance;
+        this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
+        this.nominalAnnualInterestRateOverdraft = nominalAnnualInterestRateOverdraft;
+        this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation;
+    }
+
+    public boolean hasAccountingEnabled() {
+        return this.accountingRule.getId() > AccountingRuleType.NONE.getValue();
+    }
+
+    public int accountingRuleTypeId() {
+        return this.accountingRule.getId().intValue();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        final SavingsProductData productData = (SavingsProductData) obj;
+        return productData.id.compareTo(this.id) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return this.id.hashCode();
+    }
+
+    public String getName() {
+        return this.name;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java
new file mode 100644
index 0000000..af0b124
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java
@@ -0,0 +1,567 @@
+/**
+ * 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.savings.data;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeAmountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class SavingsProductDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public SavingsProductDataValidator(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, SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_PRODUCT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
+        baseDataValidator.reset().parameter(nameParamName).value(name).notBlank().notExceedingLengthOf(100);
+
+        final String shortName = this.fromApiJsonHelper.extractStringNamed(shortNameParamName, element);
+        baseDataValidator.reset().parameter(shortNameParamName).value(shortName).notBlank().notExceedingLengthOf(4);
+
+        final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+        baseDataValidator.reset().parameter(descriptionParamName).value(description).notBlank().notExceedingLengthOf(500);
+
+        final String currencyCode = this.fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
+        baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank();
+
+        final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(digitsAfterDecimalParamName, element);
+        baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
+
+        if (this.fromApiJsonHelper.parameterExists(inMultiplesOfParamName, element)) {
+            final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerNamed(inMultiplesOfParamName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
+        }
+        final BigDecimal nominalAnnualInterestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                nominalAnnualInterestRateParamName, element);
+        baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate).notNull()
+                .zeroOrPositiveAmount();
+
+        final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                interestCompoundingPeriodTypeParamName, element);
+        baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
+                .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
+
+        final Integer interestPostingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestPostingPeriodTypeParamName,
+                element);
+        baseDataValidator.reset().parameter(interestPostingPeriodTypeParamName).value(interestPostingPeriodType).notNull()
+                .isOneOfTheseValues(SavingsPostingInterestPeriodType.integerValues());
+
+        final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName,
+                element);
+        baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull()
+                .isOneOfTheseValues(SavingsInterestCalculationType.integerValues());
+
+        final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                interestCalculationDaysInYearTypeParamName, element);
+        baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType).notNull()
+                .isOneOfTheseValues(SavingsInterestCalculationDaysInYearType.integerValues());
+
+        if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
+            final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minRequiredOpeningBalanceParamName, element);
+            baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
+
+            final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                    element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).integerZeroOrGreater();
+
+            if (lockinPeriodFrequency != null) {
+                final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                        lockinPeriodFrequencyTypeParamName, element);
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull()
+                        .inMinMaxRange(0, 3);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
+            final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    lockinPeriodFrequencyTypeParamName, element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
+
+            if (lockinPeriodFrequencyType != null) {
+                final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                        element);
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull()
+                        .integerZeroOrGreater();
+            }
+        }
+
+        /*
+         * if
+         * (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName,
+         * element)) {
+         * 
+         * final BigDecimal withdrawalFeeAmount =
+         * this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed
+         * (withdrawalFeeAmountParamName, element);
+         * baseDataValidator.reset().parameter
+         * (withdrawalFeeAmountParamName).value
+         * (withdrawalFeeAmount).zeroOrPositiveAmount();
+         * 
+         * if (withdrawalFeeAmount != null) { final Integer withdrawalFeeType =
+         * this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+         * withdrawalFeeTypeParamName, element);
+         * baseDataValidator.reset().parameter
+         * (withdrawalFeeTypeParamName).value(withdrawalFeeType)
+         * .isOneOfTheseValues(SavingsWithdrawalFeesType.integerValues()); } }
+         * 
+         * if
+         * (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName,
+         * element)) { final Integer withdrawalFeeType =
+         * this.fromApiJsonHelper.extractIntegerSansLocaleNamed
+         * (withdrawalFeeTypeParamName, element);
+         * baseDataValidator.reset().parameter
+         * (withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull()
+         * .isOneOfTheseValues(1, 2);
+         * 
+         * if (withdrawalFeeType != null) { final BigDecimal withdrawalFeeAmount
+         * = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+         * withdrawalFeeAmountParamName, element);
+         * baseDataValidator.reset().parameter
+         * (withdrawalFeeAmountParamName).value(withdrawalFeeAmount).notNull()
+         * .zeroOrPositiveAmount(); } }
+         */
+        if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
+            final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(
+                    withdrawalFeeForTransfersParamName, element);
+            baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers)
+                    .ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(feeAmountParamName, element)) {
+            final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element);
+            baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).zeroOrPositiveAmount();
+
+            if (annualFeeAmount != null) {
+                final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
+                baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).notNull();
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
+
+            final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
+            if (monthDayOfAnnualFee != null) {
+                final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element);
+                baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).notNull().zeroOrPositiveAmount();
+            }
+        }
+
+        // accounting related data validation
+        final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
+        baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
+
+        if (isCashBasedAccounting(accountingRuleType)) {
+
+            final Long savingsControlAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue())
+                    .value(savingsControlAccountId).notNull().integerGreaterThanZero();
+
+            final Long savingsReferenceAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue())
+                    .value(savingsReferenceAccountId).notNull().integerGreaterThanZero();
+
+            final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue())
+                    .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
+
+            final Long interestOnSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue())
+                    .value(interestOnSavingsAccountId).notNull().integerGreaterThanZero();
+
+            final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
+                    .notNull().integerGreaterThanZero();
+
+            final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue())
+                    .value(incomeFromPenaltyId).notNull().integerGreaterThanZero();
+
+            final Long overdraftControlAccountId = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue())
+                    .value(overdraftControlAccountId).notNull().integerGreaterThanZero();
+
+            final Long incomeFromInterest = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue())
+                    .value(incomeFromInterest).notNull().integerGreaterThanZero();
+
+            final Long writtenoff = this.fromApiJsonHelper.extractLongNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
+            baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff)
+                    .notNull().integerGreaterThanZero();
+
+            validatePaymentChannelFundSourceMappings(baseDataValidator, element);
+            validateChargeToIncomeAccountMappings(baseDataValidator, element);
+        }
+
+        validateOverdraftParams(baseDataValidator, element);
+
+        if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
+            final BigDecimal minBalanceForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minBalanceForInterestCalculationParamName, element);
+            baseDataValidator.reset().parameter(minBalanceForInterestCalculationParamName).value(minBalanceForInterestCalculation)
+                    .ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        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, SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_PRODUCT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(nameParamName, element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
+            baseDataValidator.reset().parameter(nameParamName).value(name).notBlank().notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(shortNameParamName, element)) {
+            final String shortName = this.fromApiJsonHelper.extractStringNamed(shortNameParamName, element);
+            baseDataValidator.reset().parameter(shortNameParamName).value(shortName).notBlank().notExceedingLengthOf(4);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
+            baseDataValidator.reset().parameter(descriptionParamName).value(description).notBlank().notExceedingLengthOf(500);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(currencyCodeParamName, element)) {
+            final String currencyCode = this.fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
+            baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(digitsAfterDecimalParamName, element)) {
+            final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(digitsAfterDecimalParamName, element);
+            baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(inMultiplesOfParamName, element)) {
+            final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerNamed(inMultiplesOfParamName, element, Locale.getDefault());
+            baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
+            final BigDecimal interestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName,
+                    element);
+            baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(interestRate).notNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
+            final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCompoundingPeriodTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
+                    .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
+            final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName,
+                    element);
+            baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull()
+                    .inMinMaxRange(1, 2);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
+            final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    interestCalculationDaysInYearTypeParamName, element);
+            baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType)
+                    .notNull().isOneOfTheseValues(360, 365);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
+            final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minRequiredOpeningBalanceParamName, element);
+            baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).ignoreIfNull()
+                    .zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
+            final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName,
+                    element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull()
+                    .integerZeroOrGreater();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
+            final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
+                    lockinPeriodFrequencyTypeParamName, element);
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
+            final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(
+                    withdrawalFeeForTransfersParamName, element);
+            baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers)
+                    .ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(feeAmountParamName, element)) {
+            final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element);
+            baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
+            final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
+            baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull();
+        }
+
+        final Long savingsControlAccountId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue()).value(savingsControlAccountId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long savingsReferenceAccountId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue())
+                .value(savingsReferenceAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue())
+                .value(transfersInSuspenseAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long interestOnSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue())
+                .value(interestOnSavingsAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
+                element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long overdraftAccountId = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue())
+                .value(overdraftAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromInterest = this.fromApiJsonHelper.extractLongNamed(
+                SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue()).value(incomeFromInterest)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long writtenoff = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
+                element);
+        baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        validatePaymentChannelFundSourceMappings(baseDataValidator, element);
+        validateChargeToIncomeAccountMappings(baseDataValidator, element);
+        validateOverdraftParams(baseDataValidator, element);
+
+        if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
+            final BigDecimal minBalanceForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    minBalanceForInterestCalculationParamName, element);
+            baseDataValidator.reset().parameter(minBalanceForInterestCalculationParamName).value(minBalanceForInterestCalculation)
+                    .ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private boolean isCashBasedAccounting(final Integer accountingRuleType) {
+        return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType);
+    }
+
+    /**
+     * Validation for advanced accounting options
+     */
+    private void validatePaymentChannelFundSourceMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        if (this.fromApiJsonHelper.parameterExists(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
+                element)) {
+            final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(
+                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
+            if (paymentChannelMappingArray != null && paymentChannelMappingArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject();
+                    final Long paymentTypeId = jsonObject.get(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).getAsLong();
+                    final Long paymentSpecificFundAccountId = jsonObject.get(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue())
+                            .getAsLong();
+                    baseDataValidator
+                            .reset()
+                            .parameter(
+                                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
+                                            + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.toString()).value(paymentTypeId).notNull()
+                            .integerGreaterThanZero();
+                    baseDataValidator
+                            .reset()
+                            .parameter(
+                                    SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
+                                            + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(paymentSpecificFundAccountId)
+                            .notNull().integerGreaterThanZero();
+                    i++;
+                } while (i < paymentChannelMappingArray.size());
+            }
+        }
+    }
+
+    private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        // validate for both fee and penalty charges
+        validateChargeToIncomeAccountMappings(baseDataValidator, element, true);
+        validateChargeToIncomeAccountMappings(baseDataValidator, element, true);
+    }
+
+    private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element,
+            final boolean isPenalty) {
+        String parameterName;
+        if (isPenalty) {
+            parameterName = SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
+        } else {
+            parameterName = SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(parameterName, element)) {
+            final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
+            if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) {
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue(),
+                            jsonObject);
+                    final Long incomeAccountId = this.fromApiJsonHelper.extractLongNamed(
+                            SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(), jsonObject);
+                    baseDataValidator.reset()
+                            .parameter(parameterName + "[" + i + "]." + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue())
+                            .value(chargeId).notNull().integerGreaterThanZero();
+                    baseDataValidator.reset()
+                            .parameter(parameterName + "[" + i + "]." + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue())
+                            .value(incomeAccountId).notNull().integerGreaterThanZero();
+                    i++;
+                } while (i < chargeToIncomeAccountMappingArray.size());
+            }
+        }
+    }
+
+    private void validateOverdraftParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        if (this.fromApiJsonHelper.parameterExists(allowOverdraftParamName, element)) {
+            final Boolean allowOverdraft = this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element);
+            baseDataValidator.reset().parameter(allowOverdraftParamName).value(allowOverdraft).ignoreIfNull().validateForBooleanValue();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(overdraftLimitParamName, element)) {
+            final BigDecimal overdraftLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(overdraftLimitParamName, element);
+            baseDataValidator.reset().parameter(overdraftLimitParamName).value(overdraftLimit).ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateOverdraftParamName, element)) {
+            final BigDecimal nominalAnnualInterestRateOverdraft = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateOverdraftParamName, element);
+            baseDataValidator.reset().parameter(nominalAnnualInterestRateOverdraftParamName).value(nominalAnnualInterestRateOverdraft).ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(minOverdraftForInterestCalculationParamName, element)) {
+            final BigDecimal minOverdraftForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minOverdraftForInterestCalculationParamName, element);
+            baseDataValidator.reset().parameter(minOverdraftForInterestCalculationParamName).value(minOverdraftForInterestCalculation).ignoreIfNull().zeroOrPositiveAmount();
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/TransactionDateData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/TransactionDateData.java
new file mode 100644
index 0000000..0a3cace
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/TransactionDateData.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.portfolio.savings.data;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object representing date
+ */
+@SuppressWarnings("unused")
+public class TransactionDateData {
+
+    private final LocalDate date;
+
+    public TransactionDateData(final LocalDate date) {
+        this.date = date;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java
new file mode 100644
index 0000000..c8b7605
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java
@@ -0,0 +1,456 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.bulkSavingsDueTransactionsParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.savingsIdParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.transactionAmountParamName;
+import static org.apache.fineract.portfolio.collectionsheet.CollectionSheetConstants.transactionDateParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.adjustAdvanceTowardsFuturePaymentsParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.allowWithdrawalParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.chartIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodFrequencyIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.expectedFirstDepositOnDateParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isCalendarInheritedParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.mandatoryRecommendedDepositAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.externalIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.groupIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
+import org.apache.fineract.portfolio.group.exception.CenterNotActiveException;
+import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailAssembler;
+import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
+import org.apache.fineract.portfolio.savings.exception.FixedDepositProductNotFoundException;
+import org.apache.fineract.portfolio.savings.exception.RecurringDepositProductNotFoundException;
+import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class DepositAccountAssembler {
+
+    private final PlatformSecurityContext context;
+    private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
+    private final SavingsHelper savingsHelper;
+    private final ClientRepositoryWrapper clientRepository;
+    private final GroupRepositoryWrapper groupRepository;
+    private final StaffRepositoryWrapper staffRepository;
+    private final FixedDepositProductRepository fixedDepositProductRepository;
+    private final RecurringDepositProductRepository recurringDepositProductRepository;
+    private final SavingsAccountRepositoryWrapper savingsAccountRepository;
+    private final SavingsAccountChargeAssembler savingsAccountChargeAssembler;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final DepositProductAssembler depositProductAssembler;
+    private final PaymentDetailAssembler paymentDetailAssembler;
+
+    @Autowired
+    public DepositAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
+            final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository,
+            final StaffRepositoryWrapper staffRepository, final FixedDepositProductRepository fixedDepositProductRepository,
+            final SavingsAccountRepositoryWrapper savingsAccountRepository,
+            final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper,
+            final DepositProductAssembler depositProductAssembler,
+            final RecurringDepositProductRepository recurringDepositProductRepository,
+            final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final PlatformSecurityContext context,
+            final PaymentDetailAssembler paymentDetailAssembler) {
+
+        this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
+        this.clientRepository = clientRepository;
+        this.groupRepository = groupRepository;
+        this.staffRepository = staffRepository;
+        this.fixedDepositProductRepository = fixedDepositProductRepository;
+        this.savingsAccountRepository = savingsAccountRepository;
+        this.savingsAccountChargeAssembler = savingsAccountChargeAssembler;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.depositProductAssembler = depositProductAssembler;
+        this.recurringDepositProductRepository = recurringDepositProductRepository;
+        this.savingsHelper = new SavingsHelper(accountTransfersReadPlatformService);
+        this.context = context;
+        this.paymentDetailAssembler = paymentDetailAssembler;
+    }
+
+    /**
+     * Assembles a new {@link SavingsAccount} from JSON details passed in
+     * request inheriting details where relevant from chosen
+     * {@link SavingsProduct}.
+     */
+    public SavingsAccount assembleFrom(final JsonCommand command, final AppUser submittedBy, final DepositAccountType depositAccountType) {
+
+        final JsonElement element = command.parsedJson();
+
+        final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParamName, element);
+        final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParamName, element);
+        final Long productId = this.fromApiJsonHelper.extractLongNamed(productIdParamName, element);
+
+        SavingsProduct product = null;
+        if (depositAccountType.isFixedDeposit()) {
+            product = this.fixedDepositProductRepository.findOne(productId);
+            if (product == null) { throw new FixedDepositProductNotFoundException(productId); }
+        } else if (depositAccountType.isRecurringDeposit()) {
+            product = this.recurringDepositProductRepository.findOne(productId);
+            if (product == null) { throw new RecurringDepositProductNotFoundException(productId); }
+        }
+
+        if (product == null) { throw new SavingsProductNotFoundException(productId); }
+
+        Client client = null;
+        Group group = null;
+        Staff fieldOfficer = null;
+        AccountType accountType = AccountType.INVALID;
+        final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element);
+        if (clientId != null) {
+            client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            accountType = AccountType.INDIVIDUAL;
+            if (client.isNotActive()) { throw new ClientNotActiveException(clientId); }
+        }
+
+        final Long groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element);
+        if (groupId != null) {
+            group = this.groupRepository.findOneWithNotFoundDetection(groupId);
+            accountType = AccountType.GROUP;
+        }
+
+        if (group != null && client != null) {
+            if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); }
+            accountType = AccountType.JLG;
+            if (group.isNotActive()) {
+                if (group.isCenter()) { throw new CenterNotActiveException(groupId); }
+                throw new GroupNotActiveException(groupId);
+            }
+        }
+
+        final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element);
+        if (fieldOfficerId != null) {
+            fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId);
+        }
+
+        final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element);
+
+        BigDecimal interestRate = null;
+        if (command.parameterExists(nominalAnnualInterestRateParamName)) {
+            interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
+        } else {
+            interestRate = product.nominalAnnualInterestRate();
+        }
+
+        SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null;
+        final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
+        if (interestPeriodTypeValue != null) {
+            interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue);
+        } else {
+            interestCompoundingPeriodType = product.interestCompoundingPeriodType();
+        }
+
+        SavingsPostingInterestPeriodType interestPostingPeriodType = null;
+        final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
+        if (interestPostingPeriodTypeValue != null) {
+            interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue);
+        } else {
+            interestPostingPeriodType = product.interestPostingPeriodType();
+        }
+
+        SavingsInterestCalculationType interestCalculationType = null;
+        final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
+        if (interestCalculationTypeValue != null) {
+            interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue);
+        } else {
+            interestCalculationType = product.interestCalculationType();
+        }
+
+        SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = null;
+        final Integer interestCalculationDaysInYearTypeValue = command
+                .integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
+        if (interestCalculationDaysInYearTypeValue != null) {
+            interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue);
+        } else {
+            interestCalculationDaysInYearType = product.interestCalculationDaysInYearType();
+        }
+
+        BigDecimal minRequiredOpeningBalance = null;
+        if (command.parameterExists(minRequiredOpeningBalanceParamName)) {
+            minRequiredOpeningBalance = command.bigDecimalValueOfParameterNamed(minRequiredOpeningBalanceParamName);
+        } else {
+            minRequiredOpeningBalance = product.minRequiredOpeningBalance();
+        }
+
+        Integer lockinPeriodFrequency = null;
+        if (command.parameterExists(lockinPeriodFrequencyParamName)) {
+            lockinPeriodFrequency = command.integerValueOfParameterNamed(lockinPeriodFrequencyParamName);
+        } else {
+            lockinPeriodFrequency = product.lockinPeriodFrequency();
+        }
+
+        SavingsPeriodFrequencyType lockinPeriodFrequencyType = null;
+
+        if (command.parameterExists(lockinPeriodFrequencyTypeParamName)) {
+            Integer lockinPeriodFrequencyTypeValue = null;
+            lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
+            if (lockinPeriodFrequencyTypeValue != null) {
+                lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+            }
+        } else {
+            lockinPeriodFrequencyType = product.lockinPeriodFrequencyType();
+        }
+        boolean iswithdrawalFeeApplicableForTransfer = false;
+        if (command.parameterExists(withdrawalFeeForTransfersParamName)) {
+            iswithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
+        }
+
+        final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(element, product.currency().getCode());
+
+        DepositAccountInterestRateChart accountChart = null;
+        InterestRateChart productChart = null;
+
+        if (command.parameterExists(chartIdParamName)) {
+            Long chartId = command.longValueOfParameterNamed(chartIdParamName);
+            productChart = product.findChart(chartId);
+
+        } else {
+            productChart = product.applicableChart(submittedOnDate);
+        }
+
+        if (productChart != null) {
+            accountChart = DepositAccountInterestRateChart.from(productChart);
+        }
+
+        SavingsAccount account = null;
+        if (depositAccountType.isFixedDeposit()) {
+            final DepositProductTermAndPreClosure prodTermAndPreClosure = ((FixedDepositProduct) product).depositProductTermAndPreClosure();
+            final DepositAccountTermAndPreClosure accountTermAndPreClosure = this.assembleAccountTermAndPreClosure(command,
+                    prodTermAndPreClosure);
+
+            FixedDepositAccount fdAccount = FixedDepositAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer,
+                    accountNo, externalId, accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType,
+                    interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                    lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges,
+                    accountTermAndPreClosure, accountChart);
+            accountTermAndPreClosure.updateAccountReference(fdAccount);
+            fdAccount.validateDomainRules();
+            account = fdAccount;
+        } else if (depositAccountType.isRecurringDeposit()) {
+            final DepositProductTermAndPreClosure prodTermAndPreClosure = ((RecurringDepositProduct) product)
+                    .depositProductTermAndPreClosure();
+            final DepositAccountTermAndPreClosure accountTermAndPreClosure = this.assembleAccountTermAndPreClosure(command,
+                    prodTermAndPreClosure);
+
+            final DepositProductRecurringDetail prodRecurringDetail = ((RecurringDepositProduct) product).depositRecurringDetail();
+            final DepositAccountRecurringDetail accountRecurringDetail = this.assembleAccountRecurringDetail(command,
+                    prodRecurringDetail.recurringDetail());
+
+            RecurringDepositAccount rdAccount = RecurringDepositAccount.createNewApplicationForSubmittal(client, group, product,
+                    fieldOfficer, accountNo, externalId, accountType, submittedOnDate, submittedBy, interestRate,
+                    interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                    minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer,
+                    charges, accountTermAndPreClosure, accountRecurringDetail, accountChart);
+
+            accountTermAndPreClosure.updateAccountReference(rdAccount);
+            accountRecurringDetail.updateAccountReference(rdAccount);
+            rdAccount.validateDomainRules();
+            account = rdAccount;
+        }
+
+        if (account != null) {
+            account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
+            account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), depositAccountType.resourceName());
+        }
+
+        return account;
+    }
+
+    public SavingsAccount assembleFrom(final Long savingsId, DepositAccountType depositAccountType) {
+        final SavingsAccount account = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId, depositAccountType);
+        account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
+        return account;
+    }
+
+    public void assignSavingAccountHelpers(final SavingsAccount savingsAccount) {
+        savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
+    }
+
+    public DepositAccountTermAndPreClosure assembleAccountTermAndPreClosure(final JsonCommand command,
+            final DepositProductTermAndPreClosure productTermAndPreclosure) {
+        final DepositPreClosureDetail productPreClosure = (productTermAndPreclosure == null) ? null : productTermAndPreclosure
+                .depositPreClosureDetail();
+        final DepositTermDetail productTerm = (productTermAndPreclosure == null) ? null : productTermAndPreclosure.depositTermDetail();
+
+        final DepositPreClosureDetail updatedProductPreClosure = this.depositProductAssembler.assemblePreClosureDetail(command,
+                productPreClosure);
+        final DepositTermDetail updatedProductTerm = this.depositProductAssembler.assembleDepositTermDetail(command, productTerm);
+
+        final BigDecimal depositAmount = command.bigDecimalValueOfParameterNamed(depositAmountParamName);
+        final Integer depositPeriod = command.integerValueOfParameterNamed(depositPeriodParamName);
+        final Integer depositPeriodFrequencyId = command.integerValueOfParameterNamed(depositPeriodFrequencyIdParamName);
+        final SavingsPeriodFrequencyType depositPeriodFrequency = SavingsPeriodFrequencyType.fromInt(depositPeriodFrequencyId);
+        final SavingsAccount account = null;
+        final LocalDate expectedFirstDepositOnDate = command.localDateValueOfParameterNamed(expectedFirstDepositOnDateParamName);
+        final Boolean trasferInterest = command.booleanPrimitiveValueOfParameterNamed(transferInterestToSavingsParamName);
+
+        // calculate maturity amount
+        final BigDecimal maturityAmount = null;// calculated and updated in
+                                               // account
+        final LocalDate maturityDate = null;// calculated and updated in account
+        final DepositAccountOnClosureType accountOnClosureType = null;
+        return DepositAccountTermAndPreClosure.createNew(updatedProductPreClosure, updatedProductTerm, account, depositAmount,
+                maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, expectedFirstDepositOnDate, accountOnClosureType,
+                trasferInterest);
+    }
+
+    public DepositAccountRecurringDetail assembleAccountRecurringDetail(final JsonCommand command,
+            final DepositRecurringDetail prodRecurringDetail) {
+
+        final BigDecimal recurringDepositAmount = command.bigDecimalValueOfParameterNamed(mandatoryRecommendedDepositAmountParamName);
+        boolean isMandatoryDeposit;
+        boolean allowWithdrawal;
+        boolean adjustAdvanceTowardsFuturePayments;
+        boolean isCalendarInherited;
+
+        if (command.parameterExists(isMandatoryDepositParamName)) {
+            isMandatoryDeposit = command.booleanObjectValueOfParameterNamed(isMandatoryDepositParamName);
+        } else {
+            isMandatoryDeposit = prodRecurringDetail.isMandatoryDeposit();
+        }
+
+        if (command.parameterExists(allowWithdrawalParamName)) {
+            allowWithdrawal = command.booleanObjectValueOfParameterNamed(allowWithdrawalParamName);
+        } else {
+            allowWithdrawal = prodRecurringDetail.allowWithdrawal();
+        }
+
+        if (command.parameterExists(adjustAdvanceTowardsFuturePaymentsParamName)) {
+            adjustAdvanceTowardsFuturePayments = command.booleanObjectValueOfParameterNamed(adjustAdvanceTowardsFuturePaymentsParamName);
+        } else {
+            adjustAdvanceTowardsFuturePayments = prodRecurringDetail.adjustAdvanceTowardsFuturePayments();
+        }
+
+        if (command.parameterExists(isCalendarInheritedParamName)) {
+            isCalendarInherited = command.booleanObjectValueOfParameterNamed(isCalendarInheritedParamName);
+        } else {
+            isCalendarInherited = false;
+        }
+
+        final DepositRecurringDetail depositRecurringDetail = DepositRecurringDetail.createFrom(isMandatoryDeposit, allowWithdrawal,
+                adjustAdvanceTowardsFuturePayments);
+        final DepositAccountRecurringDetail depositAccountRecurringDetail = DepositAccountRecurringDetail.createNew(recurringDepositAmount,
+                depositRecurringDetail, null, isCalendarInherited);
+        return depositAccountRecurringDetail;
+    }
+
+    public Collection<SavingsAccountTransactionDTO> assembleBulkMandatorySavingsAccountTransactionDTOs(final JsonCommand command,final PaymentDetail paymentDetail) {
+        AppUser user = getAppUserIfPresent();
+        final String json = command.json();
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Collection<SavingsAccountTransactionDTO> savingsAccountTransactions = new ArrayList<>();
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
+        final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(element.getAsJsonObject());
+        final JsonObject topLevelJsonElement = element.getAsJsonObject();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+        final DateTimeFormatter formatter = DateTimeFormat.forPattern(dateFormat).withLocale(locale);
+
+        if (element.isJsonObject()) {
+            if (topLevelJsonElement.has(bulkSavingsDueTransactionsParamName)
+                    && topLevelJsonElement.get(bulkSavingsDueTransactionsParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(bulkSavingsDueTransactionsParamName).getAsJsonArray();
+
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject savingsTransactionElement = array.get(i).getAsJsonObject();
+                    final Long savingsId = this.fromApiJsonHelper.extractLongNamed(savingsIdParamName, savingsTransactionElement);
+                    final BigDecimal dueAmount = this.fromApiJsonHelper.extractBigDecimalNamed(transactionAmountParamName,
+                            savingsTransactionElement, locale);
+                    PaymentDetail detail = paymentDetail;
+                    if (paymentDetail == null) {
+                        detail = this.paymentDetailAssembler.fetchPaymentDetail(savingsTransactionElement);
+                    }
+                    final SavingsAccountTransactionDTO savingsAccountTransactionDTO = new SavingsAccountTransactionDTO(formatter,
+                            transactionDate, dueAmount, detail, new Date(), savingsId, user);
+                    savingsAccountTransactions.add(savingsAccountTransactionDTO);
+                }
+            }
+        }
+
+        return savingsAccountTransactions;
+    }
+
+    private AppUser getAppUserIfPresent() {
+        AppUser user = null;
+        if (this.context != null) {
+            user = this.context.getAuthenticatedUserIfPresent();
+        }
+        return user;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java
new file mode 100644
index 0000000..9df5b63
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java
@@ -0,0 +1,52 @@
+/**
+ * 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.savings.domain;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+
+public interface DepositAccountDomainService {
+
+    SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
+            BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean applyWithdrawFee, boolean isRegularTransaction);
+
+    SavingsAccountTransaction handleFDDeposit(FixedDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
+            BigDecimal transactionAmount, PaymentDetail paymentDetail);
+
+    SavingsAccountTransaction handleRDDeposit(RecurringDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
+            BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isRegularTransaction);
+
+    Long handleFDAccountClosure(FixedDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command,
+            LocalDate tenantsTodayDate, Map<String, Object> changes);
+
+    Long handleRDAccountClosure(RecurringDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command,
+            LocalDate tenantsTodayDate, Map<String, Object> changes);
+
+    Long handleFDAccountPreMatureClosure(FixedDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command,
+            LocalDate tenantsTodayDate, Map<String, Object> changes);
+
+    Long handleRDAccountPreMatureClosure(RecurringDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command,
+            LocalDate tenantsTodayDate, Map<String, Object> changes);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java
new file mode 100644
index 0000000..ffaf648
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java
@@ -0,0 +1,429 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.toSavingsAccountIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferDescriptionParamName;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
+import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class DepositAccountDomainServiceJpa implements DepositAccountDomainService {
+
+    private final SavingsAccountRepositoryWrapper savingsAccountRepository;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+    private final AccountNumberGenerator accountNumberGenerator;
+    private final DepositAccountAssembler depositAccountAssembler;
+    private final SavingsAccountDomainService savingsAccountDomainService;
+    private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
+    private final ConfigurationDomainService configurationDomainService;
+    private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
+
+    @Autowired
+    public DepositAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService, final AccountNumberGenerator accountNumberGenerator,
+            final DepositAccountAssembler depositAccountAssembler, final SavingsAccountDomainService savingsAccountDomainService,
+            final AccountTransfersWritePlatformService accountTransfersWritePlatformService,
+            final ConfigurationDomainService configurationDomainService,
+            final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository) {
+        this.savingsAccountRepository = savingsAccountRepository;
+        this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+        this.accountNumberGenerator = accountNumberGenerator;
+        this.depositAccountAssembler = depositAccountAssembler;
+        this.savingsAccountDomainService = savingsAccountDomainService;
+        this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
+        this.configurationDomainService = configurationDomainService;
+        this.accountNumberFormatRepository = accountNumberFormatRepository;
+    }
+
+    @Transactional
+    @Override
+    public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
+            final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
+            final boolean applyWithdrawFee, final boolean isRegularTransaction) {
+        boolean isAccountTransfer = false;
+        boolean isInterestTransfer = false;
+        boolean isWithdrawBalance = false;
+
+        SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
+                isRegularTransaction, applyWithdrawFee, isInterestTransfer, isWithdrawBalance);
+        return this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate, transactionAmount, paymentDetail,
+                transactionBooleanValues);
+    }
+
+    @Transactional
+    @Override
+    public SavingsAccountTransaction handleFDDeposit(final FixedDepositAccount account, final DateTimeFormatter fmt,
+            final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) {
+        boolean isAccountTransfer = false;
+        boolean isRegularTransaction = false;
+        return this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail,
+                isAccountTransfer, isRegularTransaction);
+    }
+
+    @Transactional
+    @Override
+    public SavingsAccountTransaction handleRDDeposit(final RecurringDepositAccount account, final DateTimeFormatter fmt,
+            final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
+            final boolean isRegularTransaction) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        boolean isAccountTransfer = false;
+        final boolean isPreMatureClosure = false;
+        final MathContext mc = MathContext.DECIMAL64;
+        final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate,
+                transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction);
+
+        account.handleScheduleInstallments(deposit);
+        account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        account.updateOverduePayments(DateUtils.getLocalDateOfTenant());
+        return deposit;
+    }
+
+    @Transactional
+    @Override
+    public Long handleFDAccountClosure(final FixedDepositAccount account, final PaymentDetail paymentDetail, final AppUser user,
+            final JsonCommand command, final LocalDate tenantsTodayDate, final Map<String, Object> changes) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        boolean isRegularTransaction = false;
+        boolean isAccountTransfer = false;
+        final boolean isPreMatureClosure = false;
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        /*
+         * <<<<<<< HEAD final SavingsAccountTransactionDTO transactionDTO = new
+         * SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
+         * paymentDetail, new Date()); final SavingsAccountTransaction deposit =
+         * account.deposit(transactionDTO); boolean isInterestTransfer = false;
+         * final MathContext mc = MathContext.DECIMAL64; if
+         * (account.isBeforeLastPostingPeriod(transactionDate)) { final
+         * LocalDate today = DateUtils.getLocalDateOfTenant();
+         * account.postInterest(mc, today, isInterestTransfer); } else { final
+         * LocalDate today = DateUtils.getLocalDateOfTenant();
+         * account.calculateInterestUsing(mc, today, isInterestTransfer);
+         * =======
+         */
+        final MathContext mc = MathContext.DECIMAL64;
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+        Long savingsTransactionId = null;
+        account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
+        final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
+        if (onClosureType.isReinvest()) {
+            FixedDepositAccount reinvestedDeposit = account.reInvest(account.getAccountBalance());
+            this.depositAccountAssembler.assignSavingAccountHelpers(reinvestedDeposit);
+            reinvestedDeposit.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure,
+                    isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+            this.savingsAccountRepository.save(reinvestedDeposit);
+            autoGenerateAccountNumber(reinvestedDeposit);
+            final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
+                    paymentDetail, false, isRegularTransaction);
+            savingsTransactionId = withdrawal.getId();
+        } else if (onClosureType.isTransferToSavings()) {
+            final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
+            final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
+            final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
+                    DepositAccountType.SAVINGS_DEPOSIT);
+            final boolean isExceptionForBalanceCheck = false;
+            final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
+                    PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
+                    null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account,
+                    isAccountTransfer, isExceptionForBalanceCheck);
+            this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+        } else {
+            final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
+                    paymentDetail, false, isRegularTransaction);
+            savingsTransactionId = withdrawal.getId();
+        }
+
+        /***
+         * Update account transactionIds for post journal entries.
+         */
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        account.close(user, command, tenantsTodayDate, changes);
+        this.savingsAccountRepository.save(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+
+        return savingsTransactionId;
+    }
+
+    @Transactional
+    @Override
+    public Long handleRDAccountClosure(final RecurringDepositAccount account, final PaymentDetail paymentDetail, final AppUser user,
+            final JsonCommand command, final LocalDate tenantsTodayDate, final Map<String, Object> changes) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        boolean isRegularTransaction = false;
+        boolean isAccountTransfer = false;
+        final boolean isPreMatureClosure = false;
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        final MathContext mc = MathContext.DECIMAL64;
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+        Long savingsTransactionId = null;
+        account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, closedDate);
+        final BigDecimal transactionAmount = account.getAccountBalance();
+        final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
+        final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
+        if (onClosureType.isReinvest()) {
+            RecurringDepositAccount reinvestedDeposit = account.reInvest(transactionAmount);
+            depositAccountAssembler.assignSavingAccountHelpers(reinvestedDeposit);
+            reinvestedDeposit.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+            reinvestedDeposit.processAccountUponActivation(fmt, user);
+            reinvestedDeposit.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+            this.savingsAccountRepository.save(reinvestedDeposit);
+
+            Money amountForDeposit = reinvestedDeposit.activateWithBalance();
+            if (amountForDeposit.isGreaterThanZero()) {
+                handleRDDeposit(reinvestedDeposit, fmt, reinvestedDeposit.getActivationLocalDate(), amountForDeposit.getAmount(),
+                        paymentDetail, isRegularTransaction);
+            }
+            reinvestedDeposit.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+            this.savingsAccountRepository.save(reinvestedDeposit);
+            autoGenerateAccountNumber(reinvestedDeposit);
+
+            final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
+                    paymentDetail, false, isRegularTransaction);
+            savingsTransactionId = withdrawal.getId();
+
+        } else if (onClosureType.isTransferToSavings()) {
+            final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
+            final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
+            final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
+                    DepositAccountType.SAVINGS_DEPOSIT);
+            final boolean isExceptionForBalanceCheck = false;
+            final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, transactionAmount,
+                    PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
+                    null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account,
+                    isRegularTransaction, isExceptionForBalanceCheck);
+            this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+        } else {
+            final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
+                    paymentDetail, false, isRegularTransaction);
+            savingsTransactionId = withdrawal.getId();
+        }
+
+        /***
+         * Update account transactionIds for post journal entries.
+         */
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        account.close(user, command, tenantsTodayDate, changes);
+
+        this.savingsAccountRepository.save(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+
+        return savingsTransactionId;
+    }
+
+    private void autoGenerateAccountNumber(final SavingsAccount account) {
+        if (account.isAccountNumberRequiresAutoGeneration()) {
+            final AccountNumberFormat accountNumberFormat = this.accountNumberFormatRepository.findByAccountType(EntityAccountType.SAVINGS);
+            account.updateAccountNo(this.accountNumberGenerator.generate(account, accountNumberFormat));
+            this.savingsAccountRepository.save(account);
+        }
+    }
+
+    @Transactional
+    @Override
+    public Long handleFDAccountPreMatureClosure(final FixedDepositAccount account, final PaymentDetail paymentDetail, final AppUser user,
+            final JsonCommand command, final LocalDate tenantsTodayDate, final Map<String, Object> changes) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        boolean isAccountTransfer = false;
+        boolean isRegularTransaction = false;
+        final boolean isPreMatureClosure = true;
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        Long savingsTransactionId = null;
+        // post interest
+        account.postPreMaturityInterest(closedDate, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        final Integer closureTypeValue = command.integerValueOfParameterNamed(DepositsApiConstants.onAccountClosureIdParamName);
+        DepositAccountOnClosureType closureType = DepositAccountOnClosureType.fromInt(closureTypeValue);
+
+        if (closureType.isTransferToSavings()) {
+            final boolean isExceptionForBalanceCheck = false;
+            final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
+            final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
+            final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
+                    DepositAccountType.SAVINGS_DEPOSIT);
+            final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
+                    PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
+                    null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account,
+                    isRegularTransaction, isExceptionForBalanceCheck);
+            this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+        } else {
+            final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
+                    paymentDetail, false, isRegularTransaction);
+            savingsTransactionId = withdrawal.getId();
+        }
+
+        /***
+         * Update account transactionIds for post journal entries.
+         */
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        account.prematureClosure(user, command, tenantsTodayDate, changes);
+
+        this.savingsAccountRepository.save(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        return savingsTransactionId;
+    }
+
+    @Transactional
+    @Override
+    public Long handleRDAccountPreMatureClosure(final RecurringDepositAccount account, final PaymentDetail paymentDetail,
+            final AppUser user, final JsonCommand command, final LocalDate tenantsTodayDate, final Map<String, Object> changes) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        boolean isAccountTransfer = false;
+        final boolean isPreMatureClosure = true;
+        boolean isRegularTransaction = false;
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        Long savingsTransactionId = null;
+        // post interest
+        account.postPreMaturityInterest(closedDate, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        final Integer closureTypeValue = command.integerValueOfParameterNamed(DepositsApiConstants.onAccountClosureIdParamName);
+        DepositAccountOnClosureType closureType = DepositAccountOnClosureType.fromInt(closureTypeValue);
+
+        if (closureType.isTransferToSavings()) {
+            final boolean isExceptionForBalanceCheck = false;
+            final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
+            final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
+            final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
+                    DepositAccountType.SAVINGS_DEPOSIT);
+            final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
+                    PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
+                    null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account,
+                    isRegularTransaction, isExceptionForBalanceCheck);
+            this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+        } else {
+            final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
+                    paymentDetail, false, isRegularTransaction);
+            savingsTransactionId = withdrawal.getId();
+        }
+
+        /***
+         * Update account transactionIds for post journal entries.
+         */
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        account.prematureClosure(user, command, tenantsTodayDate, changes);
+        this.savingsAccountRepository.save(account);
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        return savingsTransactionId;
+    }
+
+    private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
+            Set<Long> existingReversedTransactionIds) {
+        existingTransactionIds.addAll(account.findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
+    }
+
+    private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
+            final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
+
+        final MonetaryCurrency currency = savingsAccount.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
+
+        final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
+                existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestIncentive.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestIncentive.java
new file mode 100755
index 0000000..73ba98f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestIncentive.java
@@ -0,0 +1,51 @@
+/**
+ * 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.savings.domain;
+
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.interestratechart.domain.InterestIncentivesFields;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_deposit_account_interest_incentives")
+public class DepositAccountInterestIncentive extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "deposit_account_interest_rate_slab_id", nullable = false)
+    private DepositAccountInterestRateChartSlabs depositAccountInterestRateChartSlabs;
+
+    @Embedded
+    private InterestIncentivesFields interestIncentivesFields;
+
+    protected DepositAccountInterestIncentive() {
+        // TODO Auto-generated constructor stub
+    }
+
+    public DepositAccountInterestIncentive(final DepositAccountInterestRateChartSlabs depositAccountInterestRateChartSlabs,
+            final InterestIncentivesFields interestIncentivesFields) {
+        this.depositAccountInterestRateChartSlabs = depositAccountInterestRateChartSlabs;
+        this.interestIncentivesFields = interestIncentivesFields;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestIncentives.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestIncentives.java
new file mode 100755
index 0000000..868968c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestIncentives.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.portfolio.savings.domain;
+
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.interestratechart.domain.InterestIncentivesFields;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_savings_interest_incentives")
+public class DepositAccountInterestIncentives extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "deposit_account_interest_rate_slab_id", nullable = false)
+    private DepositAccountInterestRateChartSlabs depositAccountInterestRateChartSlabs;
+
+    @Embedded
+    private InterestIncentivesFields interestIncentivesFields;
+
+    protected DepositAccountInterestIncentives() {
+
+    }
+
+    private DepositAccountInterestIncentives(final DepositAccountInterestRateChartSlabs depositAccountInterestRateChartSlabs,
+            final InterestIncentivesFields interestIncentivesFields) {
+        this.depositAccountInterestRateChartSlabs = depositAccountInterestRateChartSlabs;
+        this.interestIncentivesFields = interestIncentivesFields;
+    }
+
+    public static DepositAccountInterestIncentives from(final DepositAccountInterestRateChartSlabs depositAccountInterestRateChartSlabs,
+            final InterestIncentivesFields interestIncentivesFields) {
+        return new DepositAccountInterestIncentives(depositAccountInterestRateChartSlabs, interestIncentivesFields);
+    }
+
+    public void updateDepositAccountInterestRateChartSlabs(DepositAccountInterestRateChartSlabs depositAccountInterestRateChartSlabs) {
+        this.depositAccountInterestRateChartSlabs = depositAccountInterestRateChartSlabs;
+    }
+
+    public InterestIncentivesFields interestIncentivesFields() {
+        return this.interestIncentivesFields;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChart.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChart.java
new file mode 100644
index 0000000..25aab57
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChart.java
@@ -0,0 +1,156 @@
+/**
+ * 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.savings.domain;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartFields;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab;
+import org.apache.fineract.portfolio.interestratechart.incentive.AttributeIncentiveCalculation;
+import org.apache.fineract.portfolio.interestratechart.incentive.AttributeIncentiveCalculationFactory;
+import org.apache.fineract.portfolio.interestratechart.incentive.IncentiveDTO;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_savings_account_interest_rate_chart")
+public class DepositAccountInterestRateChart extends AbstractPersistable<Long> {
+
+    @Embedded
+    private InterestRateChartFields chartFields;
+
+    @OneToOne
+    @JoinColumn(name = "savings_account_id", nullable = false)
+    private SavingsAccount account;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(mappedBy = "depositAccountInterestRateChart", cascade = CascadeType.ALL, orphanRemoval = true)
+    private Set<DepositAccountInterestRateChartSlabs> chartSlabs = new HashSet<>();
+
+    protected DepositAccountInterestRateChart() {
+        //
+    }
+
+    public static DepositAccountInterestRateChart from(InterestRateChart productChart) {
+        final Set<InterestRateChartSlab> chartSlabs = productChart.setOfChartSlabs();
+        final Set<DepositAccountInterestRateChartSlabs> depostiChartSlabs = new HashSet<>();
+        for (InterestRateChartSlab interestRateChartSlab : chartSlabs) {
+            depostiChartSlabs.add(DepositAccountInterestRateChartSlabs.from(interestRateChartSlab, null));
+        }
+        final DepositAccountInterestRateChart depositChart = new DepositAccountInterestRateChart(productChart.chartFields(), null,
+                depostiChartSlabs);
+        // update deposit account interest rate chart ference to chart Slabs
+        depositChart.updateChartSlabsReference();
+        return depositChart;
+    }
+
+    private DepositAccountInterestRateChart(InterestRateChartFields chartFields, SavingsAccount account,
+            Set<DepositAccountInterestRateChartSlabs> chartSlabs) {
+        this.chartFields = chartFields;
+        this.account = account;
+        this.chartSlabs = chartSlabs;
+    }
+
+    private void updateChartSlabsReference() {
+        final Set<DepositAccountInterestRateChartSlabs> chartSlabs = setOfChartSlabs();
+        for (DepositAccountInterestRateChartSlabs chartSlab : chartSlabs) {
+            chartSlab.updateChartReference(this);
+        }
+    }
+
+    public Set<DepositAccountInterestRateChartSlabs> setOfChartSlabs() {
+        if (this.chartSlabs == null) {
+            this.chartSlabs = new HashSet<>();
+        }
+        return this.chartSlabs;
+    }
+
+    public DepositAccountInterestRateChartSlabs findChartSlab(Long chartSlabId) {
+        final Set<DepositAccountInterestRateChartSlabs> chartSlabs = setOfChartSlabs();
+
+        for (DepositAccountInterestRateChartSlabs interestRateChartSlab : chartSlabs) {
+            if (interestRateChartSlab.getId().equals(chartSlabId)) { return interestRateChartSlab; }
+        }
+        return null;
+    }
+
+    public LocalDate getFromDateAsLocalDate() {
+        return this.chartFields.getFromDateAsLocalDate();
+    }
+
+    public LocalDate getEndDateAsLocalDate() {
+        return this.chartFields.getEndDateAsLocalDate();
+    }
+
+    public Long savingsAccountId() {
+        return this.account == null ? null : this.account.getId();
+    }
+
+    public SavingsAccount savingsAccount() {
+        return this.account;
+    }
+
+    public InterestRateChartFields chartFields() {
+        return this.chartFields;
+    }
+
+    public void updateDepositAccountReference(final SavingsAccount account) {
+        this.account = account;
+    }
+
+    public BigDecimal getApplicableInterestRate(final BigDecimal depositAmount, final LocalDate periodStartDate,
+            final LocalDate periodEndDate, final Client client) {
+        BigDecimal effectiveInterestRate = BigDecimal.ZERO;
+        for (DepositAccountInterestRateChartSlabs slab : setOfChartSlabs()) {
+            if (slab.slabFields().isBetweenPeriod(periodStartDate, periodEndDate) && slab.slabFields().isAmountBetween(depositAmount)) {
+
+                effectiveInterestRate = slab.slabFields().annualInterestRate();
+                Set<DepositAccountInterestIncentives> depositInterestIncentives = slab.setOfIncentives();
+                for (DepositAccountInterestIncentives incentives : depositInterestIncentives) {
+                    AttributeIncentiveCalculation attributeIncentiveCalculation = AttributeIncentiveCalculationFactory
+                            .findAttributeIncentiveCalculation(incentives.interestIncentivesFields().entiryType());
+                    IncentiveDTO incentiveDTO = new IncentiveDTO(client, effectiveInterestRate, incentives.interestIncentivesFields());
+                    effectiveInterestRate = attributeIncentiveCalculation.calculateIncentive(incentiveDTO);
+                }
+
+                // effectiveInterestRate is zero or null then reset to default
+                // interest rate.
+                if (effectiveInterestRate == null || effectiveInterestRate.compareTo(BigDecimal.ZERO) == 0) {
+                    effectiveInterestRate = slab.slabFields().annualInterestRate();
+                }
+            }
+        }
+
+        return effectiveInterestRate;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChartSlabs.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChartSlabs.java
new file mode 100644
index 0000000..02f88ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChartSlabs.java
@@ -0,0 +1,108 @@
+/**
+ * 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.savings.domain;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.interestratechart.domain.InterestIncentives;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlabFields;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_savings_account_interest_rate_slab")
+public class DepositAccountInterestRateChartSlabs extends AbstractPersistable<Long> {
+
+    @Embedded
+    private InterestRateChartSlabFields slabFields;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "savings_account_interest_rate_chart_id", referencedColumnName = "id", nullable = false)
+    private DepositAccountInterestRateChart depositAccountInterestRateChart;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(mappedBy = "depositAccountInterestRateChartSlabs", cascade = CascadeType.ALL, orphanRemoval = true)
+    private Set<DepositAccountInterestIncentives> interestIncentives = new HashSet<>();
+
+    protected DepositAccountInterestRateChartSlabs() {
+        //
+    }
+
+    private DepositAccountInterestRateChartSlabs(InterestRateChartSlabFields slabFields,
+            DepositAccountInterestRateChart depositAccountInterestRateChart, final Set<DepositAccountInterestIncentives> interestIncentives) {
+        this.slabFields = slabFields;
+        this.depositAccountInterestRateChart = depositAccountInterestRateChart;
+        this.interestIncentives = interestIncentives;
+    }
+
+    public void setDepositAccountInterestRateChart(DepositAccountInterestRateChart depositAccountInterestRateChart) {
+        this.depositAccountInterestRateChart = depositAccountInterestRateChart;
+    }
+
+    public Long savingsProductId() {
+        return this.depositAccountInterestRateChart.savingsAccountId();
+    }
+
+    public InterestRateChartSlabFields slabFields() {
+        return this.slabFields;
+    }
+
+    public static DepositAccountInterestRateChartSlabs from(InterestRateChartSlab interestRateChartSlab,
+            DepositAccountInterestRateChart depositAccountInterestRateChart) {
+        InterestRateChartSlabFields slabFields = interestRateChartSlab.slabFields();
+        Set<DepositAccountInterestIncentives> depositInterestIncentives = new HashSet<>();
+        Set<InterestIncentives> incentives = interestRateChartSlab.setOfInterestIncentives();
+        for (InterestIncentives incentive : incentives) {
+            depositInterestIncentives.add(DepositAccountInterestIncentives.from(null, incentive.interestIncentivesFields()));
+        }
+        DepositAccountInterestRateChartSlabs chartSlabs = new DepositAccountInterestRateChartSlabs(slabFields,
+                depositAccountInterestRateChart, depositInterestIncentives);
+        chartSlabs.updateIncentiveReference();
+        return chartSlabs;
+    }
+
+    private void updateIncentiveReference() {
+        final Set<DepositAccountInterestIncentives> incentives = setOfIncentives();
+        for (DepositAccountInterestIncentives depositInterestIncentives : incentives) {
+            depositInterestIncentives.updateDepositAccountInterestRateChartSlabs(this);
+        }
+    }
+
+    public Set<DepositAccountInterestIncentives> setOfIncentives() {
+        if (this.interestIncentives == null) {
+            this.interestIncentives = new HashSet<>();
+        }
+        return this.interestIncentives;
+    }
+
+    public void updateChartReference(final DepositAccountInterestRateChart chart) {
+        this.depositAccountInterestRateChart = chart;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransaction.java
new file mode 100755
index 0000000..de0c76f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransaction.java
@@ -0,0 +1,123 @@
+/**
+ * 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.savings.domain;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransaction;
+import org.apache.fineract.portfolio.savings.DepositAccountOnHoldTransactionType;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Entity
+@Table(name = "m_deposit_account_on_hold_transaction")
+public class DepositAccountOnHoldTransaction extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "savings_account_id", nullable = true)
+    private SavingsAccount savingsAccount;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "transaction_type_enum", nullable = false)
+    private Integer transactionType;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "transaction_date", nullable = false)
+    private Date transactionDate;
+
+    @Column(name = "is_reversed", nullable = false)
+    private boolean reversed;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "created_date", nullable = false)
+    private Date createdDate;
+
+    @OneToOne(cascade = CascadeType.ALL, mappedBy = "depositAccountOnHoldTransaction", optional = true, orphanRemoval = true)
+    private GuarantorFundingTransaction guarantorFundingTransaction;
+
+    protected DepositAccountOnHoldTransaction() {}
+
+    private DepositAccountOnHoldTransaction(final SavingsAccount savingsAccount, final BigDecimal amount,
+            final DepositAccountOnHoldTransactionType transactionType, final LocalDate transactionDate, final boolean reversed) {
+        this.savingsAccount = savingsAccount;
+        this.amount = amount;
+        this.transactionType = transactionType.getValue();
+        this.transactionDate = transactionDate.toDate();
+        this.createdDate = new Date();
+        this.reversed = reversed;
+    }
+
+    public static DepositAccountOnHoldTransaction hold(final SavingsAccount savingsAccount, final BigDecimal amount,
+            final LocalDate transactionDate) {
+        boolean reversed = false;
+        return new DepositAccountOnHoldTransaction(savingsAccount, amount, DepositAccountOnHoldTransactionType.HOLD, transactionDate,
+                reversed);
+    }
+
+    public static DepositAccountOnHoldTransaction release(final SavingsAccount savingsAccount, final BigDecimal amount,
+            final LocalDate transactionDate) {
+        boolean reversed = false;
+        return new DepositAccountOnHoldTransaction(savingsAccount, amount, DepositAccountOnHoldTransactionType.RELEASE, transactionDate,
+                reversed);
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public Money getAmountMoney(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amount);
+    }
+
+    public void reverseTransaction() {
+        this.reversed = true;
+        if (this.getTransactionType().isHold()) {
+            this.savingsAccount.releaseFunds(this.amount);
+        } else {
+            this.savingsAccount.holdFunds(this.amount);
+        }
+    }
+
+    public DepositAccountOnHoldTransactionType getTransactionType() {
+        return DepositAccountOnHoldTransactionType.fromInt(this.transactionType);
+    }
+
+    public LocalDate getTransactionDate() {
+        LocalDate transactionDate = null;
+        if(this.transactionDate !=null){
+            transactionDate = LocalDate.fromDateFields(this.transactionDate);
+        }
+        return transactionDate;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransactionRepository.java
new file mode 100755
index 0000000..ecdf347
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransactionRepository.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.savings.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import java.util.List;
+
+public interface DepositAccountOnHoldTransactionRepository extends JpaRepository<DepositAccountOnHoldTransaction, Long>,
+        JpaSpecificationExecutor<DepositAccountOnHoldTransaction> {
+
+        List<DepositAccountOnHoldTransaction> findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(SavingsAccount account);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountRecurringDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountRecurringDetail.java
new file mode 100644
index 0000000..ff719c5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountRecurringDetail.java
@@ -0,0 +1,178 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.mandatoryRecommendedDepositAmountParamName;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_deposit_account_recurring_detail")
+public class DepositAccountRecurringDetail extends AbstractPersistable<Long> {
+
+    @Column(name = "mandatory_recommended_deposit_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal mandatoryRecommendedDepositAmount;
+
+    @Column(name = "total_overdue_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal totalOverdueAmount;
+
+    @Column(name = "is_calendar_inherited", nullable = false)
+    private final boolean isCalendarInherited;
+
+    @Column(name = "no_of_overdue_installments", nullable = false)
+    private Integer noOfOverdueInstallments;
+
+    @Embedded
+    private DepositRecurringDetail recurringDetail;
+
+    @OneToOne
+    @JoinColumn(name = "savings_account_id", nullable = false)
+    private SavingsAccount account;
+
+    /**
+     * 
+     */
+    public DepositAccountRecurringDetail() {
+        this.noOfOverdueInstallments = 0;
+        this.isCalendarInherited = false;
+    }
+
+    public static DepositAccountRecurringDetail createNew(final BigDecimal mandatoryRecommendedDepositAmount,
+            final DepositRecurringDetail recurringDetail, final SavingsAccount account, final boolean isCalendarInherited) {
+        final BigDecimal totalOverdueAmount = null;
+        final Integer noOfOverdueInstallments = null;
+        return new DepositAccountRecurringDetail(mandatoryRecommendedDepositAmount, totalOverdueAmount, noOfOverdueInstallments,
+                recurringDetail, account, isCalendarInherited);
+    }
+
+    /**
+     * @param mandatoryRecommendedDepositAmount
+     * @param totalOverdueAmount
+     * @param noOfOverdueInstallments
+     * @param recurringDetail
+     * @param account
+     */
+    protected DepositAccountRecurringDetail(final BigDecimal mandatoryRecommendedDepositAmount, final BigDecimal totalOverdueAmount,
+            final Integer noOfOverdueInstallments, final DepositRecurringDetail recurringDetail, final SavingsAccount account,
+            final boolean isCalendarInherited) {
+        this.mandatoryRecommendedDepositAmount = mandatoryRecommendedDepositAmount;
+        this.totalOverdueAmount = totalOverdueAmount;
+        this.noOfOverdueInstallments = noOfOverdueInstallments;
+        this.recurringDetail = recurringDetail;
+        this.account = account;
+        this.isCalendarInherited = isCalendarInherited;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+        if (command.isChangeInBigDecimalParameterNamed(mandatoryRecommendedDepositAmountParamName, this.mandatoryRecommendedDepositAmount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(mandatoryRecommendedDepositAmountParamName);
+            actualChanges.put(mandatoryRecommendedDepositAmountParamName, newValue);
+            this.mandatoryRecommendedDepositAmount = newValue;
+        }
+        if (this.recurringDetail != null) {
+            actualChanges.putAll(this.recurringDetail.update(command));
+        }
+        return actualChanges;
+    }
+
+    public Map<String, Object> updateMandatoryRecommendedDepositAmount(BigDecimal newMandatoryRecommendedDepositAmount,
+            LocalDate effectiveDate, Boolean isSavingsInterestPostingAtCurrentPeriodEnd, Integer financialYearBeginningMonth) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+        actualChanges.put(mandatoryRecommendedDepositAmountParamName, newMandatoryRecommendedDepositAmount);
+        this.mandatoryRecommendedDepositAmount = newMandatoryRecommendedDepositAmount;
+        RecurringDepositAccount depositAccount = (RecurringDepositAccount) this.account;
+        if (depositAccount.isNotActive()) {
+            final String defaultUserMessage = "Updates to the recommended deposit amount are allowed only when the underlying account is active.";
+            final ApiParameterError error = ApiParameterError.generalError("error.msg."
+                    + DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + ".is.not.active", defaultUserMessage);
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+        depositAccount.updateScheduleInstallmentsWithNewRecommendedDepositAmount(newMandatoryRecommendedDepositAmount, effectiveDate);
+        depositAccount.updateOverduePayments(DateUtils.getLocalDateOfTenant());
+        MathContext mc = MathContext.DECIMAL64;
+        Boolean isPreMatureClosure = false;
+        depositAccount.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+        return actualChanges;
+    }
+
+    public DepositRecurringDetail recurringDetail() {
+        return this.recurringDetail;
+    }
+
+    public void updateAccountReference(final SavingsAccount account) {
+        this.account = account;
+    }
+
+    public boolean isMandatoryDeposit() {
+        return this.recurringDetail.isMandatoryDeposit();
+    }
+
+    public boolean allowWithdrawal() {
+        return this.recurringDetail.allowWithdrawal();
+    }
+
+    public boolean adjustAdvanceTowardsFuturePayments() {
+        return this.recurringDetail.adjustAdvanceTowardsFuturePayments();
+    }
+
+    public BigDecimal mandatoryRecommendedDepositAmount() {
+        return this.mandatoryRecommendedDepositAmount;
+    }
+
+    public boolean isCalendarInherited() {
+        return this.isCalendarInherited;
+    }
+
+    public DepositAccountRecurringDetail copy() {
+        final BigDecimal mandatoryRecommendedDepositAmount = this.mandatoryRecommendedDepositAmount;
+        final DepositRecurringDetail recurringDetail = this.recurringDetail.copy();
+        final boolean isCalendarInherited = this.isCalendarInherited;
+        return DepositAccountRecurringDetail.createNew(mandatoryRecommendedDepositAmount, recurringDetail, null, isCalendarInherited);
+    }
+
+    public void updateOverdueDetails(final int noOfOverdueInstallments, final Money totalOverdueAmount) {
+        this.noOfOverdueInstallments = noOfOverdueInstallments;
+        this.totalOverdueAmount = totalOverdueAmount.getAmount();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java
new file mode 100644
index 0000000..b656afa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java
@@ -0,0 +1,316 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.dateFormatParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodFrequencyIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.expectedFirstDepositOnDateParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.localeParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+import org.joda.time.Months;
+import org.joda.time.Weeks;
+import org.joda.time.Years;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_deposit_account_term_and_preclosure")
+public class DepositAccountTermAndPreClosure extends AbstractPersistable<Long> {
+
+    @Column(name = "deposit_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal depositAmount;
+
+    @Column(name = "maturity_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal maturityAmount;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "maturity_date", nullable = true)
+    private Date maturityDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "expected_firstdepositon_date")
+    private Date expectedFirstDepositOnDate;
+
+    @Column(name = "deposit_period", nullable = true)
+    private Integer depositPeriod;
+
+    @Column(name = "deposit_period_frequency_enum", nullable = true)
+    private Integer depositPeriodFrequency;
+
+    @Column(name = "on_account_closure_enum", nullable = false)
+    private Integer onAccountClosureType;
+
+    @Embedded
+    private DepositPreClosureDetail preClosureDetail;
+
+    @Embedded
+    protected DepositTermDetail depositTermDetail;
+
+    @OneToOne
+    @JoinColumn(name = "savings_account_id", nullable = false)
+    private SavingsAccount account;
+
+    @Column(name = "transfer_interest_to_linked_account", nullable = false)
+    private boolean transferInterestToLinkedAccount;
+
+    protected DepositAccountTermAndPreClosure() {
+        super();
+    }
+
+    public static DepositAccountTermAndPreClosure createNew(DepositPreClosureDetail preClosureDetail, DepositTermDetail depositTermDetail,
+            SavingsAccount account, BigDecimal depositAmount, BigDecimal maturityAmount, final LocalDate maturityDate,
+            Integer depositPeriod, final SavingsPeriodFrequencyType depositPeriodFrequency, final LocalDate expectedFirstDepositOnDate,
+            final DepositAccountOnClosureType accountOnClosureType, Boolean trasferInterest) {
+
+        return new DepositAccountTermAndPreClosure(preClosureDetail, depositTermDetail, account, depositAmount, maturityAmount,
+                maturityDate, depositPeriod, depositPeriodFrequency, expectedFirstDepositOnDate, accountOnClosureType, trasferInterest);
+    }
+
+    private DepositAccountTermAndPreClosure(DepositPreClosureDetail preClosureDetail, DepositTermDetail depositTermDetail,
+            SavingsAccount account, BigDecimal depositAmount, BigDecimal maturityAmount, final LocalDate maturityDate,
+            Integer depositPeriod, final SavingsPeriodFrequencyType depositPeriodFrequency, final LocalDate expectedFirstDepositOnDate,
+            final DepositAccountOnClosureType accountOnClosureType, Boolean transferInterest) {
+        this.depositAmount = depositAmount;
+        this.maturityAmount = maturityAmount;
+        this.maturityDate = (maturityDate == null) ? null : maturityDate.toDate();
+        this.depositPeriod = depositPeriod;
+        this.depositPeriodFrequency = (depositPeriodFrequency == null) ? null : depositPeriodFrequency.getValue();
+        this.preClosureDetail = preClosureDetail;
+        this.depositTermDetail = depositTermDetail;
+        this.account = account;
+        this.expectedFirstDepositOnDate = expectedFirstDepositOnDate == null ? null : expectedFirstDepositOnDate.toDate();
+        this.onAccountClosureType = (accountOnClosureType == null) ? null : accountOnClosureType.getValue();
+        this.transferInterestToLinkedAccount = transferInterest;
+    }
+
+    public Map<String, Object> update(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+
+        if (command.isChangeInBigDecimalParameterNamed(depositAmountParamName, this.depositAmount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(depositAmountParamName);
+            actualChanges.put(depositAmountParamName, newValue);
+            this.depositAmount = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(depositPeriodParamName, this.depositPeriod)) {
+            final Integer newValue = command.integerValueOfParameterNamed(depositPeriodParamName);
+            actualChanges.put(depositPeriodParamName, newValue);
+            this.depositPeriod = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(depositPeriodFrequencyIdParamName, this.depositPeriodFrequency)) {
+            final Integer newValue = command.integerValueOfParameterNamed(depositPeriodFrequencyIdParamName);
+            actualChanges.put(depositPeriodFrequencyIdParamName, SavingsEnumerations.depositTermFrequencyType(newValue));
+            this.depositPeriodFrequency = newValue;
+        }
+
+        final String localeAsInput = command.locale();
+        final String dateFormat = command.dateFormat();
+        if (command.isChangeInLocalDateParameterNamed(expectedFirstDepositOnDateParamName, this.getExpectedFirstDepositOnDate())) {
+            final LocalDate newValue = command.localDateValueOfParameterNamed(expectedFirstDepositOnDateParamName);
+            final String newValueAsString = command.stringValueOfParameterNamed(expectedFirstDepositOnDateParamName);
+            actualChanges.put(expectedFirstDepositOnDateParamName, newValueAsString);
+            actualChanges.put(localeParamName, localeAsInput);
+            actualChanges.put(dateFormatParamName, dateFormat);
+            this.expectedFirstDepositOnDate = newValue.toDate();
+        }
+
+        if (command.isChangeInBooleanParameterNamed(transferInterestToSavingsParamName, this.transferInterestToLinkedAccount)) {
+            final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed(transferInterestToSavingsParamName);
+            actualChanges.put(transferInterestToSavingsParamName, newValue);
+            this.transferInterestToLinkedAccount = newValue;
+        }
+
+        if (this.preClosureDetail != null) {
+            actualChanges.putAll(this.preClosureDetail.update(command, baseDataValidator));
+        }
+
+        if (this.depositTermDetail != null) {
+            actualChanges.putAll(this.depositTermDetail.update(command, baseDataValidator));
+        }
+        return actualChanges;
+    }
+
+    public DepositPreClosureDetail depositPreClosureDetail() {
+        return this.preClosureDetail;
+    }
+
+    public DepositTermDetail depositTermDetail() {
+        return this.depositTermDetail;
+    }
+
+    public BigDecimal depositAmount() {
+        return this.depositAmount;
+    }
+
+    public Integer depositPeriod() {
+        return this.depositPeriod;
+    }
+
+    public Integer depositPeriodFrequency() {
+        return this.depositPeriodFrequency;
+    }
+
+    public SavingsPeriodFrequencyType depositPeriodFrequencyType() {
+        return SavingsPeriodFrequencyType.fromInt(depositPeriodFrequency);
+    }
+
+    public void updateAccountReference(final SavingsAccount account) {
+        this.account = account;
+    }
+
+    public void updateMaturityDetails(final BigDecimal maturityAmount, final LocalDate maturityDate) {
+        this.maturityAmount = maturityAmount;
+        this.maturityDate = maturityDate.toDate();
+    }
+
+    public void updateMaturityDetails(final BigDecimal depositAmount, final BigDecimal interestPayable, final LocalDate maturityDate) {
+        this.depositAmount = depositAmount;
+        this.maturityAmount = this.depositAmount.add(interestPayable);
+        this.maturityDate = maturityDate.toDate();
+    }
+    
+    public void updateDepositAmount(final BigDecimal depositAmount) {
+        this.depositAmount = depositAmount;
+    }
+
+
+    public LocalDate getMaturityLocalDate() {
+        LocalDate maturityLocalDate = null;
+        if (this.maturityDate != null) {
+            maturityLocalDate = new LocalDate(this.maturityDate);
+        }
+        return maturityLocalDate;
+    }
+
+    public LocalDate getExpectedFirstDepositOnDate() {
+        LocalDate expectedFirstDepositOnLocalDate = null;
+        if (this.expectedFirstDepositOnDate != null) {
+            expectedFirstDepositOnLocalDate = new LocalDate(this.expectedFirstDepositOnDate);
+        }
+        return expectedFirstDepositOnLocalDate;
+    }
+
+    public boolean isPreClosurePenalApplicable() {
+        if (this.preClosureDetail != null) { return this.preClosureDetail.preClosurePenalApplicable(); }
+        return false;
+    }
+
+    public Integer getActualDepositPeriod(final LocalDate interestPostingUpToDate, final SavingsPeriodFrequencyType periodFrequencyType) {
+        LocalDate depositFromDate = getExpectedFirstDepositOnDate();
+
+        if (depositFromDate == null) depositFromDate = this.account.accountSubmittedOrActivationDate();
+
+        Integer actualDepositPeriod = this.depositPeriod;
+        if (depositFromDate == null || getMaturityLocalDate() == null || interestPostingUpToDate.isEqual(getMaturityLocalDate())) { return actualDepositPeriod; }
+
+        final SavingsPeriodFrequencyType depositPeriodFrequencyType = periodFrequencyType;
+        switch (depositPeriodFrequencyType) {
+            case DAYS:
+                actualDepositPeriod = Days.daysBetween(depositFromDate, interestPostingUpToDate).getDays();
+            break;
+            case WEEKS:
+                actualDepositPeriod = Weeks.weeksBetween(depositFromDate, interestPostingUpToDate).getWeeks();
+            break;
+            case MONTHS:
+                actualDepositPeriod = Months.monthsBetween(depositFromDate, interestPostingUpToDate).getMonths();
+            break;
+            case YEARS:
+                actualDepositPeriod = Years.yearsBetween(depositFromDate, interestPostingUpToDate).getYears();
+            break;
+            case INVALID:
+                actualDepositPeriod = this.depositPeriod;// default value
+            break;
+        }
+        return actualDepositPeriod;
+    }
+
+    public BigDecimal maturityAmount() {
+        return this.maturityAmount;
+    }
+
+    public void updateOnAccountClosureStatus(final DepositAccountOnClosureType onClosureType) {
+        this.onAccountClosureType = onClosureType.getValue();
+    }
+
+    public boolean isReinvestOnClosure() {
+        return DepositAccountOnClosureType.fromInt(this.onAccountClosureType).isReinvest();
+    }
+
+    public boolean isTransferToSavingsOnClosure() {
+        return DepositAccountOnClosureType.fromInt(this.onAccountClosureType).isTransferToSavings();
+    }
+
+    public DepositAccountTermAndPreClosure copy(BigDecimal depositAmount) {
+        final SavingsAccount account = null;
+        final BigDecimal maturityAmount = null;
+        final BigDecimal actualDepositAmount = depositAmount;
+        final LocalDate maturityDate = null;
+        final Integer depositPeriod = this.depositPeriod;
+        final SavingsPeriodFrequencyType depositPeriodFrequency = SavingsPeriodFrequencyType.fromInt(this.depositPeriodFrequency);
+        final DepositPreClosureDetail preClosureDetail = this.preClosureDetail.copy();
+        final DepositTermDetail depositTermDetail = this.depositTermDetail.copy();
+        final LocalDate expectedFirstDepositOnDate = null;
+        final Boolean transferInterestToLinkedAccount = false;
+
+        final DepositAccountOnClosureType accountOnClosureType = null;
+        return DepositAccountTermAndPreClosure.createNew(preClosureDetail, depositTermDetail, account, actualDepositAmount, maturityAmount,
+                maturityDate, depositPeriod, depositPeriodFrequency, expectedFirstDepositOnDate, accountOnClosureType,
+                transferInterestToLinkedAccount);
+    }
+
+    public void updateExpectedFirstDepositDate(final LocalDate expectedFirstDepositOnDate) {
+        this.expectedFirstDepositOnDate = expectedFirstDepositOnDate.toDate();
+    }
+
+    public boolean isTransferInterestToLinkedAccount() {
+        return this.transferInterestToLinkedAccount;
+    }
+
+    public boolean isAfterExpectedFirstDepositDate(final LocalDate compareDate) {
+        boolean isAfterExpectedFirstDepositDate = false;
+        if (this.expectedFirstDepositOnDate != null) {
+            isAfterExpectedFirstDepositDate = compareDate.isAfter(getExpectedFirstDepositOnDate());
+        }
+        return isAfterExpectedFirstDepositDate;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositPreClosureDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositPreClosureDetail.java
new file mode 100644
index 0000000..e1e5329
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositPreClosureDetail.java
@@ -0,0 +1,139 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalApplicableParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestOnTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName;
+
+import java.math.BigDecimal;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+
+/**
+ * DepositPreClosureDetail encapsulates all the details of a
+ * {@link FixedDepositProduct} that are also used and persisted by a
+ * {@link FixedDepositAccount}.
+ */
+@Embeddable
+public class DepositPreClosureDetail {
+
+    @Column(name = "pre_closure_penal_applicable")
+    private boolean preClosurePenalApplicable;
+
+    @Column(name = "pre_closure_penal_interest", scale = 6, precision = 19, nullable = true)
+    private BigDecimal preClosurePenalInterest;
+
+    @Column(name = "pre_closure_penal_interest_on_enum", nullable = true)
+    private Integer preClosurePenalInterestOnType;
+
+    public static DepositPreClosureDetail createFrom(final boolean preClosurePenalApplicable, final BigDecimal preClosurePenalInterest,
+            final PreClosurePenalInterestOnType preClosurePenalInterestType) {
+
+        return new DepositPreClosureDetail(preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestType);
+    }
+
+    protected DepositPreClosureDetail() {
+        //
+    }
+
+    private DepositPreClosureDetail(final boolean preClosurePenalApplicable, final BigDecimal preClosurePenalInterest,
+            final PreClosurePenalInterestOnType preClosurePenalInterestType) {
+        this.preClosurePenalApplicable = preClosurePenalApplicable;
+        this.preClosurePenalInterest = preClosurePenalInterest;
+        this.preClosurePenalInterestOnType = (preClosurePenalInterestType == null) ? null : preClosurePenalInterestType.getValue();
+    }
+
+    protected Map<String, Object> update(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+
+        final String localeAsInput = command.locale();
+
+        if (command.isChangeInBooleanParameterNamed(preClosurePenalApplicableParamName, this.preClosurePenalApplicable)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(preClosurePenalApplicableParamName);
+            actualChanges.put(preClosurePenalApplicableParamName, newValue);
+            this.preClosurePenalApplicable = newValue;
+        }
+
+        if (this.preClosurePenalApplicable) {
+            if (command.isChangeInBigDecimalParameterNamed(preClosurePenalInterestParamName, this.preClosurePenalInterest)) {
+                final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(preClosurePenalInterestParamName);
+                actualChanges.put(preClosurePenalInterestParamName, newValue);
+                actualChanges.put(localeParamName, localeAsInput);
+                this.preClosurePenalInterest = newValue;
+            }
+
+            if (command.isChangeInIntegerParameterNamed(preClosurePenalInterestOnTypeIdParamName, this.preClosurePenalInterestOnType)) {
+                final Integer newValue = command.integerValueOfParameterNamed(preClosurePenalInterestOnTypeIdParamName);
+                actualChanges.put(preClosurePenalInterestOnTypeIdParamName, SavingsEnumerations.preClosurePenaltyInterestOnType(newValue));
+                actualChanges.put(localeParamName, localeAsInput);
+                this.preClosurePenalInterestOnType = newValue;
+            }
+
+            if (this.preClosurePenalInterest == null) {
+                baseDataValidator.parameter(preClosurePenalInterestParamName).value(this.preClosurePenalInterest)
+                        .cantBeBlankWhenParameterProvidedIs(preClosurePenalApplicableParamName, this.preClosurePenalApplicable);
+            }
+
+            if (this.preClosurePenalInterestOnType == null) {
+                baseDataValidator.parameter(preClosurePenalInterestOnTypeIdParamName).value(this.preClosurePenalInterestOnType)
+                        .cantBeBlankWhenParameterProvidedIs(preClosurePenalApplicableParamName, this.preClosurePenalApplicable);
+            }
+        } else { // reset if pre-closure penal interest is not applicable
+            this.preClosurePenalInterest = null;
+            this.preClosurePenalInterestOnType = null;
+        }
+
+        return actualChanges;
+    }
+
+    public boolean preClosurePenalApplicable() {
+        return this.preClosurePenalApplicable;
+    }
+
+    public BigDecimal preClosurePenalInterest() {
+        return this.preClosurePenalInterest;
+    }
+
+    public Integer preClosurePenalInterestOnTypeId() {
+        return this.preClosurePenalInterestOnType;
+    }
+
+    public PreClosurePenalInterestOnType preClosurePenalInterestOnType() {
+        return PreClosurePenalInterestOnType.fromInt(preClosurePenalInterestOnType);
+    }
+
+    public DepositPreClosureDetail copy() {
+        final boolean preClosurePenalApplicable = this.preClosurePenalApplicable;
+        final BigDecimal preClosurePenalInterest = this.preClosurePenalInterest;
+        final PreClosurePenalInterestOnType preClosurePenalInterestType = PreClosurePenalInterestOnType
+                .fromInt(this.preClosurePenalInterestOnType);
+
+        return DepositPreClosureDetail.createFrom(preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestType);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAmountDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAmountDetails.java
new file mode 100644
index 0000000..7091e90
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAmountDetails.java
@@ -0,0 +1,107 @@
+/**
+ * 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.savings.domain;
+
+import java.math.BigDecimal;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+
+/**
+ * RecurringDepositProductAmountDetails encapsulates all recurring Deposit
+ * Amount of a {@link RecurringDepositProduct}.
+ */
+@Embeddable
+public class DepositProductAmountDetails {
+
+    @Column(name = "min_deposit_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minDepositAmount;
+
+    @Column(name = "max_deposit_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal maxDepositAmount;
+
+    @Column(name = "deposit_amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal depositAmount;
+
+    public static DepositProductAmountDetails createFrom(final BigDecimal minDepositAmount, final BigDecimal depositAmount,
+            final BigDecimal maxDepositAmount) {
+
+        return new DepositProductAmountDetails(minDepositAmount, depositAmount, maxDepositAmount);
+    }
+
+    protected DepositProductAmountDetails() {
+        //
+    }
+
+    public DepositProductAmountDetails(final BigDecimal minDepositAmount, final BigDecimal depositAmount, final BigDecimal maxDepositAmount) {
+        this.minDepositAmount = minDepositAmount;
+        this.depositAmount = depositAmount;
+        this.maxDepositAmount = maxDepositAmount;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
+
+        final String localeAsInput = command.locale();
+
+        final String minDepositAmountParamName = "minDepositAmount";
+        if (command.isChangeInBigDecimalParameterNamedWithNullCheck(minDepositAmountParamName, this.minDepositAmount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minDepositAmountParamName);
+            actualChanges.put(minDepositAmountParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.minDepositAmount = newValue;
+        }
+
+        final String maxDepositAmountParamName = "maxDepositAmount";
+        if (command.isChangeInBigDecimalParameterNamedWithNullCheck(maxDepositAmountParamName, this.maxDepositAmount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxDepositAmountParamName);
+            actualChanges.put(maxDepositAmountParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.maxDepositAmount = newValue;
+        }
+
+        final String depositAmountParamName = "depositAmount";
+        if (command.isChangeInBigDecimalParameterNamedWithNullCheck(depositAmountParamName, this.depositAmount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(depositAmountParamName);
+            actualChanges.put(depositAmountParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.depositAmount = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    public BigDecimal getMinDepositAmount() {
+        return this.minDepositAmount;
+    }
+
+    public BigDecimal getMaxDepositAmount() {
+        return this.maxDepositAmount;
+    }
+
+    public BigDecimal getDepositAmount() {
+        return this.depositAmount;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java
new file mode 100644
index 0000000..83b0e10
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java
@@ -0,0 +1,476 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.adjustAdvanceTowardsFuturePaymentsParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.allowWithdrawalParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.chartsParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositMaxAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositMinAmountParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalApplicableParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestOnTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.idParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeAppliedToException;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler;
+import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
+import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+@Service
+public class DepositProductAssembler {
+
+    private final ChargeRepositoryWrapper chargeRepository;
+    private final InterestRateChartAssembler chartAssembler;
+
+    @Autowired
+    public DepositProductAssembler(final ChargeRepositoryWrapper chargeRepository, final InterestRateChartAssembler chartAssembler) {
+        this.chargeRepository = chargeRepository;
+        this.chartAssembler = chartAssembler;
+    }
+
+    public FixedDepositProduct assembleFixedDepositProduct(final JsonCommand command) {
+
+        final String name = command.stringValueOfParameterNamed(nameParamName);
+        final String shortName = command.stringValueOfParameterNamed(shortNameParamName);
+        final String description = command.stringValueOfParameterNamed(descriptionParamName);
+
+        final String currencyCode = command.stringValueOfParameterNamed(currencyCodeParamName);
+        final Integer digitsAfterDecimal = command.integerValueOfParameterNamed(digitsAfterDecimalParamName);
+        final Integer inMultiplesOf = command.integerValueOfParameterNamed(inMultiplesOfParamName);
+        final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
+
+        BigDecimal interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
+
+        SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null;
+        final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
+        if (interestPeriodTypeValue != null) {
+            interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue);
+        }
+
+        SavingsPostingInterestPeriodType interestPostingPeriodType = null;
+        final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
+        if (interestPostingPeriodTypeValue != null) {
+            interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue);
+        }
+
+        SavingsInterestCalculationType interestCalculationType = null;
+        final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
+        if (interestCalculationTypeValue != null) {
+            interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue);
+        }
+
+        SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = null;
+        final Integer interestCalculationDaysInYearTypeValue = command
+                .integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
+        if (interestCalculationDaysInYearTypeValue != null) {
+            interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue);
+        }
+
+        final Integer lockinPeriodFrequency = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName);
+        SavingsPeriodFrequencyType lockinPeriodFrequencyType = null;
+        final Integer lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
+        if (lockinPeriodFrequencyTypeValue != null) {
+            lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+        }
+
+        final BigDecimal minBalanceForInterestCalculation = command
+                .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName);
+
+        final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
+
+        final DepositPreClosureDetail preClosureDetail = this.assemblePreClosureDetail(command);
+        final DepositTermDetail depositTermDetail = this.assembleDepositTermDetail(command);
+        final DepositProductAmountDetails depositProductAmountDetails = this.assembleDepositAmountDetails(command);
+        final DepositProductTermAndPreClosure productTermAndPreClosure = DepositProductTermAndPreClosure.createNew(preClosureDetail,
+                depositTermDetail, depositProductAmountDetails, null);
+
+        // Savings product charges
+        final Set<Charge> charges = assembleListOfSavingsProductCharges(command, currencyCode);
+        // Interest rate charts
+        final Set<InterestRateChart> charts = assembleListOfCharts(command, currency.getCode());
+        if (interestRate == null) {
+            interestRate = BigDecimal.ZERO;
+        }
+        FixedDepositProduct fixedDepositProduct = FixedDepositProduct.createNew(name, shortName, description, currency, interestRate,
+                interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, accountingRuleType, charges, productTermAndPreClosure, charts,
+                minBalanceForInterestCalculation);
+
+        // update product reference
+        productTermAndPreClosure.updateProductReference(fixedDepositProduct);
+
+        fixedDepositProduct.validateDomainRules();
+
+        return fixedDepositProduct;
+    }
+
+    public RecurringDepositProduct assembleRecurringDepositProduct(final JsonCommand command) {
+
+        final String name = command.stringValueOfParameterNamed(nameParamName);
+        final String shortName = command.stringValueOfParameterNamed(shortNameParamName);
+        final String description = command.stringValueOfParameterNamed(descriptionParamName);
+
+        final String currencyCode = command.stringValueOfParameterNamed(currencyCodeParamName);
+        final Integer digitsAfterDecimal = command.integerValueOfParameterNamed(digitsAfterDecimalParamName);
+        final Integer inMultiplesOf = command.integerValueOfParameterNamed(inMultiplesOfParamName);
+        final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
+
+        BigDecimal interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
+
+        SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null;
+        final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
+        if (interestPeriodTypeValue != null) {
+            interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue);
+        }
+
+        SavingsPostingInterestPeriodType interestPostingPeriodType = null;
+        final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
+        if (interestPostingPeriodTypeValue != null) {
+            interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue);
+        }
+
+        SavingsInterestCalculationType interestCalculationType = null;
+        final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
+        if (interestCalculationTypeValue != null) {
+            interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue);
+        }
+
+        SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = null;
+        final Integer interestCalculationDaysInYearTypeValue = command
+                .integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
+        if (interestCalculationDaysInYearTypeValue != null) {
+            interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue);
+        }
+
+        final Integer lockinPeriodFrequency = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName);
+        SavingsPeriodFrequencyType lockinPeriodFrequencyType = null;
+        final Integer lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
+        if (lockinPeriodFrequencyTypeValue != null) {
+            lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+        }
+
+        final BigDecimal minBalanceForInterestCalculation = command
+                .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName);
+
+        final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
+
+        final DepositPreClosureDetail preClosureDetail = this.assemblePreClosureDetail(command);
+        final DepositTermDetail depositTermDetail = this.assembleDepositTermDetail(command);
+        final DepositProductAmountDetails depositProductAmountDetails = this.assembleDepositAmountDetails(command);
+        final DepositProductTermAndPreClosure productTermAndPreClosure = DepositProductTermAndPreClosure.createNew(preClosureDetail,
+                depositTermDetail, depositProductAmountDetails, null);
+        final DepositRecurringDetail recurringDetail = this.assembleRecurringDetail(command);
+        final DepositProductRecurringDetail productRecurringDetail = DepositProductRecurringDetail.createNew(recurringDetail, null);
+
+        // Savings product charges
+        final Set<Charge> charges = assembleListOfSavingsProductCharges(command, currencyCode);
+        // Interest rate charts
+        final Set<InterestRateChart> charts = assembleListOfCharts(command, currency.getCode());
+
+        if (interestRate == null) {
+            interestRate = BigDecimal.ZERO;
+        }
+
+        RecurringDepositProduct recurringDepositProduct = RecurringDepositProduct.createNew(name, shortName, description, currency,
+                interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
+                interestCalculationDaysInYearType, lockinPeriodFrequency, lockinPeriodFrequencyType, accountingRuleType, charges,
+                productTermAndPreClosure, productRecurringDetail, charts, minBalanceForInterestCalculation);
+
+        // update product reference
+        productTermAndPreClosure.updateProductReference(recurringDepositProduct);
+        productRecurringDetail.updateProductReference(recurringDepositProduct);
+
+        recurringDepositProduct.validateDomainRules();
+
+        return recurringDepositProduct;
+    }
+
+    public DepositPreClosureDetail assemblePreClosureDetail(final JsonCommand command) {
+
+        boolean preClosurePenalApplicable = false;
+        BigDecimal preClosurePenalInterest = null;
+        PreClosurePenalInterestOnType preClosurePenalInterestType = null;
+
+        if (command.parameterExists(preClosurePenalApplicableParamName)) {
+            preClosurePenalApplicable = command.booleanObjectValueOfParameterNamed(preClosurePenalApplicableParamName);
+            if (preClosurePenalApplicable) {
+                preClosurePenalInterest = command.bigDecimalValueOfParameterNamed(preClosurePenalInterestParamName);
+                final Integer preClosurePenalInterestOnTypeId = command
+                        .integerValueOfParameterNamed(preClosurePenalInterestOnTypeIdParamName);
+                preClosurePenalInterestType = preClosurePenalInterestOnTypeId == null ? null : PreClosurePenalInterestOnType
+                        .fromInt(preClosurePenalInterestOnTypeId);
+            }
+        }
+
+        DepositPreClosureDetail preClosureDetail = DepositPreClosureDetail.createFrom(preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestType);
+
+        return preClosureDetail;
+    }
+
+    public DepositPreClosureDetail assemblePreClosureDetail(final JsonCommand command, DepositPreClosureDetail produPreClosureDetail) {
+        boolean preClosurePenalApplicable = false;
+        BigDecimal preClosurePenalInterest = null;
+        PreClosurePenalInterestOnType preClosurePenalInterestType = null;
+        Integer preClosurePenalInterestOnTypeId = null;
+        if (command.parameterExists(preClosurePenalApplicableParamName)) {
+            preClosurePenalApplicable = command.booleanObjectValueOfParameterNamed(preClosurePenalApplicableParamName);
+            if (preClosurePenalApplicable) {
+                if (command.parameterExists(preClosurePenalInterestParamName)) {
+                    preClosurePenalInterest = command.bigDecimalValueOfParameterNamed(preClosurePenalInterestParamName);
+                } else {
+                    preClosurePenalInterest = produPreClosureDetail.preClosurePenalInterest();
+                }
+
+                if (command.parameterExists(preClosurePenalInterestParamName)) {
+                    preClosurePenalInterestOnTypeId = command.integerValueOfParameterNamed(preClosurePenalInterestOnTypeIdParamName);
+                } else {
+                    preClosurePenalInterestOnTypeId = produPreClosureDetail.preClosurePenalInterestOnTypeId();
+                }
+            }
+        } else {
+            preClosurePenalApplicable = produPreClosureDetail.preClosurePenalApplicable();
+            preClosurePenalInterest = produPreClosureDetail.preClosurePenalInterest();
+            preClosurePenalInterestOnTypeId = produPreClosureDetail.preClosurePenalInterestOnTypeId();
+        }
+
+        preClosurePenalInterestType = preClosurePenalInterestOnTypeId == null ? null : PreClosurePenalInterestOnType
+                .fromInt(preClosurePenalInterestOnTypeId);
+
+        DepositPreClosureDetail preClosureDetail1 = DepositPreClosureDetail.createFrom(preClosurePenalApplicable, preClosurePenalInterest,
+                preClosurePenalInterestType);
+
+        return preClosureDetail1;
+    }
+
+    public DepositTermDetail assembleDepositTermDetail(final JsonCommand command) {
+
+        final Integer minDepositTerm = command.integerValueOfParameterNamed(minDepositTermParamName);
+        final Integer maxDepositTerm = command.integerValueOfParameterNamed(maxDepositTermParamName);
+        final Integer minDepositTermTypeId = command.integerValueOfParameterNamed(minDepositTermTypeIdParamName);
+        final SavingsPeriodFrequencyType minDepositTermType = (minDepositTermTypeId == null) ? null : SavingsPeriodFrequencyType
+                .fromInt(minDepositTermTypeId);
+        final Integer maxDepositTermTypeId = command.integerValueOfParameterNamed(maxDepositTermTypeIdParamName);
+        final SavingsPeriodFrequencyType maxDepositTermType = (maxDepositTermTypeId == null) ? null : SavingsPeriodFrequencyType
+                .fromInt(maxDepositTermTypeId);
+        final Integer inMultiplesOfDepositTerm = command.integerValueOfParameterNamed(inMultiplesOfDepositTermParamName);
+        final Integer inMultiplesOfDepositTermTypeId = command.integerValueOfParameterNamed(inMultiplesOfDepositTermTypeIdParamName);
+        final SavingsPeriodFrequencyType inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null
+                : SavingsPeriodFrequencyType.fromInt(inMultiplesOfDepositTermTypeId);
+
+        final DepositTermDetail depositTermDetail = DepositTermDetail.createFrom(minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType);
+
+        return depositTermDetail;
+    }
+
+    public DepositTermDetail assembleDepositTermDetail(final JsonCommand command, final DepositTermDetail prodDepositTermDetail) {
+
+        Integer minDepositTerm = null;
+        Integer maxDepositTerm = null;
+        Integer minDepositTermTypeId = null;
+        Integer maxDepositTermTypeId = null;
+        Integer inMultiplesOfDepositTerm = null;
+        Integer inMultiplesOfDepositTermTypeId = null;
+
+        if (command.parameterExists(minDepositTermParamName)) {
+            minDepositTerm = command.integerValueOfParameterNamed(minDepositTermParamName);
+        } else if (prodDepositTermDetail != null) {
+            minDepositTerm = prodDepositTermDetail.minDepositTerm();
+        }
+
+        if (command.parameterExists(maxDepositTermParamName)) {
+            maxDepositTerm = command.integerValueOfParameterNamed(maxDepositTermParamName);
+        } else if (prodDepositTermDetail != null) {
+            maxDepositTerm = prodDepositTermDetail.maxDepositTerm();
+        }
+
+        if (command.parameterExists(minDepositTermTypeIdParamName)) {
+            minDepositTermTypeId = command.integerValueOfParameterNamed(minDepositTermTypeIdParamName);
+        } else if (prodDepositTermDetail != null) {
+            minDepositTermTypeId = prodDepositTermDetail.minDepositTermType();
+        }
+
+        if (command.parameterExists(maxDepositTermTypeIdParamName)) {
+            maxDepositTermTypeId = command.integerValueOfParameterNamed(maxDepositTermTypeIdParamName);
+        } else if (prodDepositTermDetail != null) {
+            maxDepositTermTypeId = prodDepositTermDetail.maxDepositTermType();
+        }
+
+        final SavingsPeriodFrequencyType minDepositTermType = (minDepositTermTypeId == null) ? null : SavingsPeriodFrequencyType
+                .fromInt(minDepositTermTypeId);
+
+        final SavingsPeriodFrequencyType maxDepositTermType = (maxDepositTermTypeId == null) ? null : SavingsPeriodFrequencyType
+                .fromInt(maxDepositTermTypeId);
+
+        if (command.parameterExists(inMultiplesOfDepositTermParamName)) {
+            inMultiplesOfDepositTerm = command.integerValueOfParameterNamed(inMultiplesOfDepositTermParamName);
+        } else if (prodDepositTermDetail != null) {
+            inMultiplesOfDepositTerm = prodDepositTermDetail.inMultiplesOfDepositTerm();
+        }
+
+        if (command.parameterExists(preClosurePenalApplicableParamName)) {
+            inMultiplesOfDepositTermTypeId = command.integerValueOfParameterNamed(inMultiplesOfDepositTermTypeIdParamName);
+        } else if (prodDepositTermDetail != null) {
+            inMultiplesOfDepositTermTypeId = prodDepositTermDetail.inMultiplesOfDepositTermType();
+        }
+
+        final SavingsPeriodFrequencyType inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null
+                : SavingsPeriodFrequencyType.fromInt(inMultiplesOfDepositTermTypeId);
+
+        final DepositTermDetail depositTermDetail = DepositTermDetail.createFrom(minDepositTerm, maxDepositTerm, minDepositTermType,
+                maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType);
+
+        return depositTermDetail;
+    }
+
+    public DepositRecurringDetail assembleRecurringDetail(final JsonCommand command) {
+
+        Boolean isMandatoryDeposit = command.booleanObjectValueOfParameterNamed(isMandatoryDepositParamName);
+        Boolean allowWithdrawal = command.booleanObjectValueOfParameterNamed(allowWithdrawalParamName);
+        Boolean adjustAdvanceTowardsFuturePayments = command
+                .booleanObjectValueOfParameterNamed(adjustAdvanceTowardsFuturePaymentsParamName);
+
+        if (isMandatoryDeposit == null) isMandatoryDeposit = false;
+        if (allowWithdrawal == null) allowWithdrawal = false;
+        if (adjustAdvanceTowardsFuturePayments == null) adjustAdvanceTowardsFuturePayments = false;
+
+        final DepositRecurringDetail depositRecurringDetail = DepositRecurringDetail.createFrom(isMandatoryDeposit, allowWithdrawal,
+                adjustAdvanceTowardsFuturePayments);
+
+        return depositRecurringDetail;
+    }
+
+    public Set<Charge> assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) {
+
+        final Set<Charge> charges = new HashSet<>();
+
+        if (command.parameterExists(chargesParamName)) {
+            final JsonArray chargesArray = command.arrayOfParameterNamed(chargesParamName);
+            if (chargesArray != null) {
+                for (int i = 0; i < chargesArray.size(); i++) {
+
+                    final JsonObject jsonObject = chargesArray.get(i).getAsJsonObject();
+                    if (jsonObject.has(idParamName)) {
+                        final Long id = jsonObject.get(idParamName).getAsLong();
+
+                        final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(id);
+
+                        if (!charge.isSavingsCharge()) {
+                            final String errorMessage = "Charge with identifier " + charge.getId()
+                                    + " cannot be applied to Savings product.";
+                            throw new ChargeCannotBeAppliedToException("savings.product", errorMessage, charge.getId());
+                        }
+
+                        if (!savingsProductCurrencyCode.equals(charge.getCurrencyCode())) {
+                            final String errorMessage = "Charge and Savings Product must have the same currency.";
+                            throw new InvalidCurrencyException("charge", "attach.to.savings.product", errorMessage);
+                        }
+                        charges.add(charge);
+                    }
+                }
+            }
+        }
+
+        return charges;
+    }
+
+    private Set<InterestRateChart> assembleListOfCharts(JsonCommand command, String currencyCode) {
+        final Set<InterestRateChart> charts = new HashSet<>();
+
+        if (command.parameterExists(chartsParamName)) {
+            final JsonArray chartsArray = command.arrayOfParameterNamed(chartsParamName);
+            if (chartsArray != null) {
+                for (int i = 0; i < chartsArray.size(); i++) {
+                    final JsonObject interstRateChartElement = chartsArray.get(i).getAsJsonObject();
+                    InterestRateChart chart = this.chartAssembler.assembleFrom(interstRateChartElement, currencyCode);
+                    charts.add(chart);
+                }
+            }
+        }
+        return charts;
+    }
+
+    public DepositProductAmountDetails assembleDepositAmountDetails(final JsonCommand command) {
+
+        BigDecimal minDepositAmount = null;
+        if (command.parameterExists(depositMinAmountParamName)) {
+            minDepositAmount = command.bigDecimalValueOfParameterNamed(depositMinAmountParamName);
+        }
+
+        BigDecimal maxDepositAmount = null;
+        if (command.parameterExists(depositMaxAmountParamName)) {
+            maxDepositAmount = command.bigDecimalValueOfParameterNamed(depositMaxAmountParamName);
+        }
+
+        BigDecimal depositAmount = null;
+        if (command.parameterExists(depositAmountParamName)) {
+            depositAmount = command.bigDecimalValueOfParameterNamed(depositAmountParamName);
+        }
+
+        final DepositProductAmountDetails depositRecurringDetail = new DepositProductAmountDetails(minDepositAmount, depositAmount,
+                maxDepositAmount);
+
+        return depositRecurringDetail;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductRecurringDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductRecurringDetail.java
new file mode 100644
index 0000000..1fd2908
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductRecurringDetail.java
@@ -0,0 +1,73 @@
+/**
+ * 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.savings.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_deposit_product_recurring_detail")
+public class DepositProductRecurringDetail extends AbstractPersistable<Long> {
+
+    @Embedded
+    private DepositRecurringDetail recurringDetail;
+
+    @OneToOne
+    @JoinColumn(name = "savings_product_id", nullable = false)
+    private RecurringDepositProduct product;
+
+    protected DepositProductRecurringDetail() {
+        super();
+    }
+
+    public static DepositProductRecurringDetail createNew(DepositRecurringDetail recurringDetail, SavingsProduct product) {
+
+        return new DepositProductRecurringDetail(recurringDetail, product);
+    }
+
+    private DepositProductRecurringDetail(DepositRecurringDetail recurringDetail, SavingsProduct product) {
+        this.recurringDetail = recurringDetail;
+        this.product = (RecurringDepositProduct) product;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+        if (this.recurringDetail != null) {
+            actualChanges.putAll(this.recurringDetail.update(command));
+        }
+        return actualChanges;
+    }
+
+    public DepositRecurringDetail recurringDetail() {
+        return this.recurringDetail;
+    }
+
+    public void updateProductReference(final SavingsProduct product) {
+        this.product = (RecurringDepositProduct) product;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductTermAndPreClosure.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductTermAndPreClosure.java
new file mode 100644
index 0000000..60fb99a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductTermAndPreClosure.java
@@ -0,0 +1,100 @@
+/**
+ * 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.savings.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_deposit_product_term_and_preclosure")
+public class DepositProductTermAndPreClosure extends AbstractPersistable<Long> {
+
+    @Embedded
+    private DepositPreClosureDetail preClosureDetail;
+
+    @Embedded
+    protected DepositTermDetail depositTermDetail;
+
+    @OneToOne
+    @JoinColumn(name = "savings_product_id", nullable = false)
+    private FixedDepositProduct product;
+
+    @Embedded
+    private DepositProductAmountDetails depositProductAmountDetails;
+
+    protected DepositProductTermAndPreClosure() {
+        super();
+    }
+
+    public static DepositProductTermAndPreClosure createNew(DepositPreClosureDetail preClosureDetail, DepositTermDetail depositTermDetail,
+            DepositProductAmountDetails depositProductMinMaxAmountDetails, SavingsProduct product) {
+
+        return new DepositProductTermAndPreClosure(preClosureDetail, depositTermDetail, depositProductMinMaxAmountDetails, product);
+    }
+
+    private DepositProductTermAndPreClosure(DepositPreClosureDetail preClosureDetail, DepositTermDetail depositTermDetail,
+            DepositProductAmountDetails depositProductMinMaxAmountDetails, SavingsProduct product) {
+        this.preClosureDetail = preClosureDetail;
+        this.depositTermDetail = depositTermDetail;
+        this.depositProductAmountDetails = depositProductMinMaxAmountDetails;
+        this.product = (FixedDepositProduct) product;
+    }
+
+    public Map<String, Object> update(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+        if (this.preClosureDetail != null) {
+            actualChanges.putAll(this.preClosureDetail.update(command, baseDataValidator));
+        }
+
+        if (this.depositTermDetail != null) {
+            actualChanges.putAll(this.depositTermDetail.update(command, baseDataValidator));
+        }
+
+        if (this.depositProductAmountDetails != null) {
+            actualChanges.putAll(this.depositProductAmountDetails.update(command));
+        }
+        return actualChanges;
+    }
+
+    public DepositPreClosureDetail depositPreClosureDetail() {
+        return this.preClosureDetail;
+    }
+
+    public DepositTermDetail depositTermDetail() {
+        return this.depositTermDetail;
+    }
+
+    public DepositProductAmountDetails depositProductAmountDetails() {
+        return this.depositProductAmountDetails;
+    }
+
+    public void updateProductReference(final SavingsProduct product) {
+        this.product = (FixedDepositProduct) product;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositRecurringDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositRecurringDetail.java
new file mode 100644
index 0000000..293f81a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositRecurringDetail.java
@@ -0,0 +1,105 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.adjustAdvanceTowardsFuturePaymentsParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.allowWithdrawalParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+
+/**
+ * DepositRecurringDetail encapsulates all the details of a
+ * {@link RecurringDepositProduct} that are also used and persisted by a
+ * {@link RecurringDepositAccount}.
+ */
+@Embeddable
+public class DepositRecurringDetail {
+
+    @Column(name = "is_mandatory", nullable = true)
+    private boolean isMandatoryDeposit;
+
+    @Column(name = "allow_withdrawal", nullable = true)
+    private boolean allowWithdrawal;
+
+    @Column(name = "adjust_advance_towards_future_payments", nullable = true)
+    private boolean adjustAdvanceTowardsFuturePayments;
+
+    protected DepositRecurringDetail() {
+        //
+    }
+
+    public static DepositRecurringDetail createFrom(final boolean isMandatoryDeposit, boolean allowWithdrawal,
+            boolean adjustAdvanceTowardsFuturePayments) {
+
+        return new DepositRecurringDetail(isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments);
+    }
+
+    private DepositRecurringDetail(final boolean isMandatoryDeposit, boolean allowWithdrawal, boolean adjustAdvanceTowardsFuturePayments) {
+        this.isMandatoryDeposit = isMandatoryDeposit;
+        this.allowWithdrawal = allowWithdrawal;
+        this.adjustAdvanceTowardsFuturePayments = adjustAdvanceTowardsFuturePayments;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+
+        if (command.isChangeInBooleanParameterNamed(isMandatoryDepositParamName, this.isMandatoryDeposit)) {
+            final boolean newValue = command.booleanObjectValueOfParameterNamed(isMandatoryDepositParamName);
+            actualChanges.put(isMandatoryDepositParamName, newValue);
+            this.isMandatoryDeposit = newValue;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(allowWithdrawalParamName, this.allowWithdrawal)) {
+            final boolean newValue = command.booleanObjectValueOfParameterNamed(allowWithdrawalParamName);
+            actualChanges.put(allowWithdrawalParamName, newValue);
+            this.allowWithdrawal = newValue;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(adjustAdvanceTowardsFuturePaymentsParamName, this.adjustAdvanceTowardsFuturePayments)) {
+            final boolean newValue = command.booleanObjectValueOfParameterNamed(adjustAdvanceTowardsFuturePaymentsParamName);
+            actualChanges.put(adjustAdvanceTowardsFuturePaymentsParamName, newValue);
+            this.adjustAdvanceTowardsFuturePayments = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    public boolean isMandatoryDeposit() {
+        return this.isMandatoryDeposit;
+    }
+
+    public boolean allowWithdrawal() {
+        return this.allowWithdrawal;
+    }
+
+    public boolean adjustAdvanceTowardsFuturePayments() {
+        return this.adjustAdvanceTowardsFuturePayments;
+    }
+
+    public DepositRecurringDetail copy() {
+        return DepositRecurringDetail.createFrom(this.isMandatoryDeposit, this.allowWithdrawal, this.adjustAdvanceTowardsFuturePayments);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java
new file mode 100644
index 0000000..ff27554
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java
@@ -0,0 +1,265 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermTypeIdParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermTypeIdParamName;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.joda.time.DateTimeConstants;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+import org.joda.time.Months;
+import org.joda.time.Weeks;
+import org.joda.time.Years;
+
+/**
+ * DepositTermDetail encapsulates all the details of a
+ * {@link FixedDepositProduct} that are also used and persisted by a
+ * {@link FixedDepositAccount}.
+ */
+@Embeddable
+public class DepositTermDetail {
+
+    @Column(name = "min_deposit_term", nullable = true)
+    private Integer minDepositTerm;
+
+    @Column(name = "max_deposit_term", nullable = true)
+    private Integer maxDepositTerm;
+
+    @Column(name = "min_deposit_term_type_enum", nullable = true)
+    private Integer minDepositTermType;
+
+    @Column(name = "max_deposit_term_type_enum", nullable = true)
+    private Integer maxDepositTermType;
+
+    @Column(name = "in_multiples_of_deposit_term", nullable = true)
+    private Integer inMultiplesOfDepositTerm;
+
+    @Column(name = "in_multiples_of_deposit_term_type_enum", nullable = true)
+    private Integer inMultiplesOfDepositTermType;
+
+    public static DepositTermDetail createFrom(final Integer minDepositTerm, final Integer maxDepositTerm,
+            final SavingsPeriodFrequencyType minDepositTermType, final SavingsPeriodFrequencyType maxDepositTermType,
+            final Integer inMultiplesOfDepositTerm, final SavingsPeriodFrequencyType inMultiplesOfDepositTermType) {
+
+        return new DepositTermDetail(minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm,
+                inMultiplesOfDepositTermType);
+    }
+
+    protected DepositTermDetail() {
+        //
+    }
+
+    private DepositTermDetail(final Integer minDepositTerm, final Integer maxDepositTerm,
+            final SavingsPeriodFrequencyType minDepositTermType, final SavingsPeriodFrequencyType maxDepositTermType,
+            final Integer inMultiplesOfDepositTerm, final SavingsPeriodFrequencyType inMultiplesOfDepositTermType) {
+        this.minDepositTerm = minDepositTerm;
+        this.maxDepositTerm = maxDepositTerm;
+        this.minDepositTermType = (minDepositTermType == null) ? null : minDepositTermType.getValue();
+        this.maxDepositTermType = (maxDepositTermType == null) ? null : maxDepositTermType.getValue();
+        this.inMultiplesOfDepositTerm = inMultiplesOfDepositTerm;
+        this.inMultiplesOfDepositTermType = (inMultiplesOfDepositTermType == null) ? null : inMultiplesOfDepositTermType.getValue();
+    }
+
+    public Map<String, Object> update(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+
+        if (command.isChangeInIntegerParameterNamed(minDepositTermParamName, this.minDepositTerm)) {
+            final Integer newValue = command.integerValueOfParameterNamed(minDepositTermParamName);
+            actualChanges.put(minDepositTermParamName, newValue);
+            this.minDepositTerm = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(maxDepositTermParamName, this.maxDepositTerm)) {
+            final Integer newValue = command.integerValueOfParameterNamed(maxDepositTermParamName);
+            actualChanges.put(maxDepositTermParamName, newValue);
+            this.maxDepositTerm = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(minDepositTermTypeIdParamName, this.minDepositTermType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(minDepositTermTypeIdParamName);
+            actualChanges.put(minDepositTermTypeIdParamName, SavingsEnumerations.depositTermFrequencyType(newValue));
+            this.minDepositTermType = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(maxDepositTermTypeIdParamName, this.maxDepositTermType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(maxDepositTermTypeIdParamName);
+            actualChanges.put(maxDepositTermTypeIdParamName, SavingsEnumerations.depositTermFrequencyType(newValue));
+            this.maxDepositTermType = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(inMultiplesOfDepositTermParamName, this.inMultiplesOfDepositTerm)) {
+            final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfDepositTermParamName);
+            actualChanges.put(inMultiplesOfDepositTermParamName, newValue);
+            this.inMultiplesOfDepositTerm = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(inMultiplesOfDepositTermTypeIdParamName, this.inMultiplesOfDepositTermType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfDepositTermTypeIdParamName);
+            actualChanges.put(inMultiplesOfDepositTermTypeIdParamName, SavingsEnumerations.inMultiplesOfDepositTermFrequencyType(newValue));
+            this.inMultiplesOfDepositTermType = newValue;
+        }
+
+        if (isMinDepositTermGreaterThanMaxDepositTerm()) {
+            baseDataValidator.parameter(minDepositTermParamName).value(this.minDepositTerm)
+                    .failWithCode(".greater.than.maxDepositTerm", this.minDepositTerm, this.maxDepositTerm);
+        }
+
+        return actualChanges;
+    }
+
+    public boolean isMinDepositTermGreaterThanMaxDepositTerm() {
+        if (this.minDepositTerm != null && this.maxDepositTerm != null) {
+            final Integer minDepositInDays = this.convertToSafeDays(minDepositTerm, SavingsPeriodFrequencyType.fromInt(minDepositTermType));
+            final Integer maxDepositInDays = this.convertToSafeDays(maxDepositTerm, SavingsPeriodFrequencyType.fromInt(maxDepositTermType));
+            if (minDepositInDays.compareTo(maxDepositInDays) > 0) { return true; }
+        }
+        return false;
+    }
+
+    public Integer minDepositTerm() {
+        return this.minDepositTerm;
+    }
+
+    public Integer maxDepositTerm() {
+        return this.maxDepositTerm;
+    }
+
+    public Integer minDepositTermType() {
+        return this.minDepositTermType;
+    }
+
+    public Integer maxDepositTermType() {
+        return this.maxDepositTermType;
+    }
+
+    public Integer inMultiplesOfDepositTerm() {
+        return this.inMultiplesOfDepositTerm;
+    }
+
+    public Integer inMultiplesOfDepositTermType() {
+        return this.inMultiplesOfDepositTermType;
+    }
+
+    public boolean isDepositBetweenMinAndMax(LocalDate depositStartDate, LocalDate depositEndDate) {
+        return isEqualOrGreaterThanMin(depositStartDate, depositEndDate) && isEqualOrLessThanMax(depositStartDate, depositEndDate);
+    }
+
+    public boolean isValidInMultiplesOfPeriod(final Integer depositPeriod, final SavingsPeriodFrequencyType depositPeriodFrequencyType) {
+
+        boolean isValidInMultiplesOfPeriod = true;
+        final Integer depositPeriodInDays = this.convertToSafeDays(depositPeriod, depositPeriodFrequencyType);
+        if (this.inMultiplesOfDepositTerm() != null) {
+            final Integer inMultiplesOfInDays = this.convertToSafeDays(this.inMultiplesOfDepositTerm(),
+                    SavingsPeriodFrequencyType.fromInt(this.inMultiplesOfDepositTermType()));
+            final Integer minDepositInDays = this.convertToSafeDays(minDepositTerm, SavingsPeriodFrequencyType.fromInt(minDepositTermType));
+            isValidInMultiplesOfPeriod = ((depositPeriodInDays - minDepositInDays) % inMultiplesOfInDays == 0);
+        }
+
+        return isValidInMultiplesOfPeriod;
+    }
+
+    private boolean isEqualOrGreaterThanMin(LocalDate depositStartDate, LocalDate depositEndDate) {
+        if (minDepositTerm() == null) return true;
+        final SavingsPeriodFrequencyType periodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.minDepositTermType());
+        final Integer depositPeriod = depositPeriod(depositStartDate, depositEndDate, periodFrequencyType);
+        return minDepositTerm() == null || depositPeriod.compareTo(minDepositTerm()) >= 0;
+    }
+
+    private boolean isEqualOrLessThanMax(LocalDate depositStartDate, LocalDate depositEndDate) {
+        if (maxDepositTerm() == null) return true;
+        final SavingsPeriodFrequencyType periodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.maxDepositTermType());
+        final Integer depositPeriod = depositPeriod(depositStartDate, depositEndDate, periodFrequencyType);
+        return maxDepositTerm() == null || depositPeriod.compareTo(maxDepositTerm()) <= 0;
+    }
+
+    public Integer depositPeriod(final LocalDate periodStartDate, final LocalDate periodEndDate,
+            final SavingsPeriodFrequencyType periodFrequencyType) {
+        Integer actualDepositPeriod = 0;
+
+        switch (periodFrequencyType) {
+            case DAYS:
+                actualDepositPeriod = Days.daysBetween(periodStartDate, periodEndDate).getDays();
+            break;
+            case WEEKS:
+                actualDepositPeriod = Weeks.weeksBetween(periodStartDate, periodEndDate).getWeeks();
+            break;
+            case MONTHS:
+                actualDepositPeriod = Months.monthsBetween(periodStartDate, periodEndDate).getMonths();
+            break;
+            case YEARS:
+                actualDepositPeriod = Years.yearsBetween(periodStartDate, periodEndDate).getYears();
+            break;
+            case INVALID:
+                actualDepositPeriod = 0;// default value
+            break;
+        }
+        return actualDepositPeriod;
+    }
+
+    private Integer convertToSafeDays(final Integer period, final SavingsPeriodFrequencyType periodFrequencyType) {
+        Integer toDays = 0;
+        switch (periodFrequencyType) {
+            case DAYS:
+                toDays = period;
+            break;
+            case WEEKS:
+                toDays = period * DateTimeConstants.DAYS_PER_WEEK;
+            break;
+            case MONTHS:
+                toDays = period * 30;// converting to stard 30 days
+            break;
+            case YEARS:
+                toDays = period * 365;
+            break;
+            case INVALID:
+                toDays = 0;// default value
+            break;
+        }
+        return toDays;
+    }
+
+    public DepositTermDetail copy() {
+
+        final Integer minDepositTerm = this.minDepositTerm;
+        final Integer maxDepositTerm = this.maxDepositTerm;
+        final SavingsPeriodFrequencyType minDepositTermType = SavingsPeriodFrequencyType.fromInt(this.minDepositTermType);
+        final SavingsPeriodFrequencyType maxDepositTermType = SavingsPeriodFrequencyType.fromInt(this.maxDepositTermType);
+        final Integer inMultiplesOfDepositTerm = this.inMultiplesOfDepositTerm;
+        final SavingsPeriodFrequencyType inMultiplesOfDepositTermType = SavingsPeriodFrequencyType.fromInt(this
+                .inMultiplesOfDepositTermType());
+
+        return DepositTermDetail.createFrom(minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
+                inMultiplesOfDepositTerm, inMultiplesOfDepositTermType);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java
new file mode 100644
index 0000000..f713836
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java
@@ -0,0 +1,817 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.OneToOne;
+import javax.persistence.Transient;
+
+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.LocalDateInterval;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler;
+import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+@Entity
+@DiscriminatorValue("200")
+public class FixedDepositAccount extends SavingsAccount {
+
+    @OneToOne(mappedBy = "account", cascade = CascadeType.ALL)
+    private DepositAccountTermAndPreClosure accountTermAndPreClosure;
+
+    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account")
+    protected DepositAccountInterestRateChart chart;
+
+    @Transient
+    protected InterestRateChartAssembler chartAssembler;
+
+    protected FixedDepositAccount() {
+        //
+    }
+
+    public static FixedDepositAccount createNewApplicationForSubmittal(final Client client, final Group group,
+            final SavingsProduct product, final Staff fieldOfficer, final String accountNo, final String externalId,
+            final AccountType accountType, final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges,
+            final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountInterestRateChart chart) {
+
+        final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL;
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = new BigDecimal(0);
+        FixedDepositAccount account = new FixedDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status,
+                accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
+                interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, chart,
+                allowOverdraft, overdraftLimit);
+
+        return account;
+    }
+
+    private FixedDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer,
+            final String accountNo, final String externalId, final SavingsAccountStatusType status, final AccountType accountType,
+            final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges,
+            final DepositAccountTermAndPreClosure accountTermAndPreClosure, DepositAccountInterestRateChart chart,
+            final boolean allowOverdraft, final BigDecimal overdraftLimit) {
+
+        super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy,
+                nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
+                interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
+                withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit);
+
+        this.accountTermAndPreClosure = accountTermAndPreClosure;
+        this.chart = chart;
+        if (this.chart != null) {
+            this.chart.updateDepositAccountReference(this);
+        }
+    }
+
+    @Override
+    public void modifyApplication(final JsonCommand command, final Map<String, Object> actualChanges) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction);
+        super.modifyApplication(command, actualChanges, baseDataValidator);
+        final Map<String, Object> termAndPreClosureChanges = accountTermAndPreClosure.update(command, baseDataValidator);
+        actualChanges.putAll(termAndPreClosureChanges);
+        validateDomainRules(baseDataValidator);
+        super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    @Override
+    protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate) {
+        boolean isPreMatureClosure = false;
+        return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, isPreMatureClosure);
+    }
+
+    protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate,
+            final boolean isPreMatureClosure) {
+
+        // default it to nominalAnnualInterst rate. interest chart overrrides
+        // this value.
+        BigDecimal applicableInterestRate = this.nominalAnnualInterestRate;
+        if (this.chart != null) {
+            boolean applyPreMaturePenalty = false;
+            BigDecimal penalInterest = BigDecimal.ZERO;
+            LocalDate depositCloseDate = calculateMaturityDate();
+            if (isPreMatureClosure) {
+                if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) {
+                    applyPreMaturePenalty = true;
+                    penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest();
+                    final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure
+                            .depositPreClosureDetail().preClosurePenalInterestOnType();
+                    if (preClosurePenalInterestOnType.isWholeTerm()) {
+                        depositCloseDate = interestCalculatedUpto();
+                    } else if (preClosurePenalInterestOnType.isTillPrematureWithdrawal()) {
+                        depositCloseDate = interestPostingUpToDate;
+                    }
+                }
+            }
+
+            final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount();
+            applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, this.client);
+
+            if (applyPreMaturePenalty) {
+                applicableInterestRate = applicableInterestRate.subtract(penalInterest);
+                applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) == -1 ? BigDecimal.ZERO : applicableInterestRate;
+            }
+        }
+        this.nominalAnnualInterestRate = applicableInterestRate;
+
+        return applicableInterestRate.divide(BigDecimal.valueOf(100l), mc);
+    }
+
+    public void updateMaturityDateAndAmountBeforeAccountActivation(final MathContext mc, final boolean isPreMatureClosure,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        List<SavingsAccountTransaction> allTransactions = new ArrayList<>();
+        final Money transactionAmountMoney = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount());
+        final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null,
+                this.accountSubmittedOrActivationDate(), transactionAmountMoney, new Date(), null); // TODO:
+                                                                                                    // verify
+                                                                                                    // if
+                                                                                                    // it
+                                                                                                    // is
+                                                                                                    // ok
+                                                                                                    // to
+                                                                                                    // pass
+                                                                                                    // null
+                                                                                                    // for
+                                                                                                    // AppUser
+        transaction.updateRunningBalance(transactionAmountMoney);
+        transaction.updateCumulativeBalanceAndDates(this.getCurrency(), interestCalculatedUpto());
+        allTransactions.add(transaction);
+        updateMaturityDateAndAmount(mc, allTransactions, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+    }
+
+    public void updateMaturityDateAndAmount(final MathContext mc, final boolean isPreMatureClosure,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        updateMaturityDateAndAmount(mc, retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure,
+                isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+    }
+
+    public void updateMaturityDateAndAmount(final MathContext mc, final List<SavingsAccountTransaction> transactions,
+            final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd,
+            final Integer financialYearBeginningMonth) {
+        final LocalDate maturityDate = calculateMaturityDate();
+        final LocalDate interestCalculationUpto = maturityDate.minusDays(1);
+
+        // set end of day balance to maturity date for maturity interest
+        // calculation
+        this.resetAccountTransactionsEndOfDayBalances(transactions, maturityDate);
+
+        final List<PostingPeriod> postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, transactions, isPreMatureClosure,
+                isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+        // reset end of day balance back to today's date
+        this.resetAccountTransactionsEndOfDayBalances(transactions, DateUtils.getLocalDateOfTenant());
+
+        Money totalInterestPayable = Money.zero(getCurrency());
+        for (PostingPeriod postingPeriod : postingPeriods) {
+            totalInterestPayable = totalInterestPayable.plus(postingPeriod.getInterestEarned());
+        }
+        final Money depositAmount = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount());
+        final Money maturityAmount = depositAmount.plus(totalInterestPayable);
+
+        this.accountTermAndPreClosure.updateMaturityDetails(maturityAmount.getAmount(), maturityDate);
+    }
+
+    public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.updateMaturityDetailsAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final LocalDate todayDate = DateUtils.getLocalDateOfTenant();
+        if (!this.maturityDate().isAfter(todayDate)) {
+            // update account status
+            this.status = SavingsAccountStatusType.MATURED.getValue();
+            postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        }
+    }
+
+    public LocalDate calculateMaturityDate() {
+
+        final LocalDate startDate = accountSubmittedOrActivationDate();
+        LocalDate maturityDate = null;
+        final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod();
+        switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) {
+            case DAYS:
+                maturityDate = startDate.plusDays(depositPeriod);
+            break;
+            case WEEKS:
+                maturityDate = startDate.plusWeeks(depositPeriod);
+            break;
+            case MONTHS:
+                maturityDate = startDate.plusMonths(depositPeriod);
+            break;
+            case YEARS:
+                maturityDate = startDate.plusYears(depositPeriod);
+            break;
+            case INVALID:
+            break;
+        }
+
+        return maturityDate;
+    }
+
+    private List<PostingPeriod> calculateInterestPayable(final MathContext mc, final LocalDate maturityDate,
+            final List<SavingsAccountTransaction> transactions, final boolean isPreMatureClosure,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+
+        final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType);
+
+        final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType
+                .fromInt(this.interestCompoundingPeriodType);
+
+        final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType
+                .fromInt(this.interestCalculationDaysInYearType);
+
+        final List<LocalDateInterval> postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(
+                accountSubmittedOrActivationDate(), maturityDate, postingPeriodType, financialYearBeginningMonth);
+
+        final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
+
+        Money periodStartingBalance = Money.zero(currency);
+
+        final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType);
+        final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure);
+        final Collection<Long> interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId());
+        boolean isInterestTransfer = false;
+        final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation());
+        for (final LocalDateInterval periodInterval : postingPeriodIntervals) {
+
+            final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, transactions,
+                    this.currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(),
+                    maturityDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation,
+                    isSavingsInterestPostingAtCurrentPeriodEnd);
+
+            periodStartingBalance = postingPeriod.closingBalance();
+
+            allPostingPeriods.add(postingPeriod);
+        }
+
+        this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods);
+        this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, this.getLockedInUntilLocalDate(),
+                isTransferInterestToOtherAccount());
+        return allPostingPeriods;
+    }
+
+    public void prematureClosure(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate,
+            final Map<String, Object> actualChanges) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+
+        if (closedDate.isBefore(getActivationLocalDate())) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.after.activation.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (isAccountLocked(closedDate)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.after.lockin.period");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (closedDate.isAfter(maturityDate())) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.before.maturity.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (closedDate.isAfter(tenantsTodayDate)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("cannot.be.a.future.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+        if (savingsAccountTransactions.size() > 0) {
+            final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
+            if (accountTransaction.isAfter(closedDate)) {
+                baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                        .failWithCode("must.be.after.last.transaction.date");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate);
+        this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue();
+
+        final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
+        final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
+        this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType);
+
+        /*
+         * // withdraw deposit amount before closing the account final Money
+         * transactionAmountMoney = Money.of(this.currency,
+         * this.getAccountBalance()); final SavingsAccountTransaction withdraw =
+         * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail,
+         * closedDate, transactionAmountMoney, new Date());
+         * this.transactions.add(withdraw);
+         */
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+        actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
+        actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
+        actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt));
+
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = closedDate.toDate();
+        this.closedBy = currentUser;
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+    }
+
+    @Override
+    public Money activateWithBalance() {
+        return Money.of(this.currency, this.accountTermAndPreClosure.depositAmount());
+    }
+
+    public void close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate,
+            final Map<String, Object> actualChanges) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus)) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.matured.state");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+
+        if (closedDate.isBefore(getActivationLocalDate())) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.after.activation.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        if (closedDate.isBefore(maturityDate())) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.after.account.maturity.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        if (closedDate.isAfter(tenantsTodayDate)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("cannot.be.a.future.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+        if (savingsAccountTransactions.size() > 0) {
+            final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
+            if (accountTransaction.isAfter(closedDate)) {
+                baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                        .failWithCode("must.be.after.last.transaction.date");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate);
+        this.status = SavingsAccountStatusType.CLOSED.getValue();
+
+        final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
+        final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
+        this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType);
+
+        // // withdraw deposit amount before closing the account
+        // final Money transactionAmountMoney = Money.of(this.currency,
+        // this.getAccountBalance());
+        // final SavingsAccountTransaction withdraw =
+        // SavingsAccountTransaction.withdrawal(this, office(), paymentDetail,
+        // closedDate,
+        // transactionAmountMoney, new Date());
+        // this.transactions.add(withdraw);
+
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+        actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
+        actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
+        actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt));
+
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = closedDate.toDate();
+        this.closedBy = currentUser;
+        // this.summary.updateSummary(this.currency,
+        // this.savingsAccountTransactionSummaryWrapper, this.transactions);
+    }
+
+    public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        final LocalDate interestPostingUpToDate = maturityDate();
+        final MathContext mc = MathContext.DECIMAL64;
+        final boolean isInterestTransfer = false;
+        final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
+                isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+        Money interestPostedToDate = Money.zero(this.currency);
+
+        boolean recalucateDailyBalanceDetails = false;
+
+        for (final PostingPeriod interestPostingPeriod : postingPeriods) {
+
+            LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction();
+
+            interestPostingTransactionDate = interestPostingTransactionDate.isAfter(interestPostingUpToDate) ? interestPostingUpToDate
+                    : interestPostingTransactionDate;
+
+            final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned();
+
+            interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod);
+
+            final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate);
+            if (postingTransaction == null) {
+                final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(),
+                        interestPostingTransactionDate, interestEarnedToBePostedForPeriod);
+                this.transactions.add(newPostingTransaction);
+                recalucateDailyBalanceDetails = true;
+            } else {
+                final boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod);
+                if (correctionRequired) {
+                    postingTransaction.reverse();
+                    final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(),
+                            interestPostingTransactionDate, interestEarnedToBePostedForPeriod);
+                    this.transactions.add(newPostingTransaction);
+                    recalucateDailyBalanceDetails = true;
+                }
+            }
+        }
+
+        if (recalucateDailyBalanceDetails) {
+            // update existing transactions so derived balance fields are
+            // correct.
+            recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate);
+        }
+
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+    }
+
+    public void postPreMaturityInterest(final LocalDate accountCloseDate, final boolean isPreMatureClosure,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+
+        Money interestPostedToDate = totalInterestPosted();
+        // calculate interest before one day of closure date
+        final LocalDate interestCalculatedToDate = accountCloseDate.minusDays(1);
+        final Money interestOnMaturity = calculatePreMatureInterest(interestCalculatedToDate,
+                retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+        boolean recalucateDailyBalance = false;
+
+        // post remaining interest
+        final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate);
+        if (!remainigInterestToBePosted.isZero()) {
+            final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(),
+                    accountCloseDate, remainigInterestToBePosted);
+            this.transactions.add(newPostingTransaction);
+            recalucateDailyBalance = true;
+        }
+
+        if (recalucateDailyBalance) {
+            // update existing transactions so derived balance fields are
+            // correct.
+            recalculateDailyBalances(Money.zero(this.currency), accountCloseDate);
+        }
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+        this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate);
+
+    }
+
+    public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, final boolean isPreMatureClosure,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+
+        final Money interestPostedToDate = totalInterestPosted().copy();
+
+        final Money interestEarnedTillDate = calculatePreMatureInterest(preMatureDate, retreiveOrderedNonInterestPostingTransactions(),
+                isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+        final Money accountBalance = Money.of(getCurrency(), getAccountBalance());
+        final Money maturityAmount = accountBalance.minus(interestPostedToDate).plus(interestEarnedTillDate);
+
+        return maturityAmount.getAmount();
+    }
+
+    private Money calculatePreMatureInterest(final LocalDate preMatureDate, final List<SavingsAccountTransaction> transactions,
+            final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd,
+            final Integer financialYearBeginningMonth) {
+        final MathContext mc = MathContext.DECIMAL64;
+        final List<PostingPeriod> postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, isPreMatureClosure,
+                isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+        Money interestOnMaturity = Money.zero(this.currency);
+
+        for (final PostingPeriod interestPostingPeriod : postingPeriods) {
+            final Money interestEarnedForPeriod = interestPostingPeriod.getInterestEarned();
+            interestOnMaturity = interestOnMaturity.plus(interestEarnedForPeriod);
+        }
+
+        return interestOnMaturity;
+    }
+
+    @Override
+    public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
+        super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+    }
+
+    @Override
+    public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
+        return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+    }
+
+    private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) {
+        LocalDate interestPostingUpToDate = interestPostingDate;
+        final LocalDate uptoMaturityDate = interestCalculatedUpto();
+        if (uptoMaturityDate != null && uptoMaturityDate.isBefore(interestPostingDate)) {
+            interestPostingUpToDate = uptoMaturityDate;
+        }
+        return interestPostingUpToDate;
+    }
+
+    public LocalDate maturityDate() {
+        return this.accountTermAndPreClosure.getMaturityLocalDate();
+    }
+
+    public BigDecimal maturityAmount() {
+        return this.accountTermAndPreClosure.maturityAmount();
+    }
+
+    private Money totalInterestPosted() {
+        Money interestPostedToDate = Money.zero(this.currency);
+        for (final SavingsAccountTransaction transaction : this.transactions) {
+            if (transaction.isInterestPostingAndNotReversed()) {
+                interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency));
+            }
+        }
+
+        return interestPostedToDate;
+    }
+
+    @Override
+    public Map<String, Object> activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
+        final Map<String, Object> actualChanges = super.activate(currentUser, command, tenantsTodayDate);
+
+        // if (isAccountLocked(calculateMaturityDate())) {
+        // final List<ApiParameterError> dataValidationErrors = new
+        // ArrayList<ApiParameterError>();
+        // final DataValidatorBuilder baseDataValidator = new
+        // DataValidatorBuilder(dataValidationErrors)
+        // .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        // baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("deposit.period.must.be.greater.than.lock.in.period",
+        // "Deposit period must be greater than account lock-in period.");
+        // if (!dataValidationErrors.isEmpty()) { throw new
+        // PlatformApiDataValidationException(dataValidationErrors); }
+        // }
+        return actualChanges;
+    }
+
+    private LocalDate depositStartDate() {
+        // TODO: Support to add deposit start date which can be a date after
+        // account activation date.
+        final LocalDate depositStartDate = accountSubmittedOrActivationDate();
+        return depositStartDate;
+    }
+
+    private LocalDate interestCalculatedUpto() {
+        LocalDate uptoMaturityDate = calculateMaturityDate();
+        if (uptoMaturityDate != null) {
+            // interest should not be calculated for maturity day
+            uptoMaturityDate = uptoMaturityDate.minusDays(1);
+        }
+        return uptoMaturityDate;
+    }
+
+    public void validateDomainRules() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        validateDomainRules(baseDataValidator);
+        super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private void validateDomainRules(final DataValidatorBuilder baseDataValidator) {
+
+        final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure.depositTermDetail()
+                .isMinDepositTermGreaterThanMaxDepositTerm();
+        final boolean isValidDepositPeriod = this.accountTermAndPreClosure.depositTermDetail().isDepositBetweenMinAndMax(
+                depositStartDate(), calculateMaturityDate());
+        // deposit period should be within min and max deposit term
+        if (isMinTermGreaterThanMax) {
+            final Integer maxTerm = this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm();
+            baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm)
+                    .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term");
+        }
+        final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod();
+        final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure.depositPeriodFrequencyType();
+
+        if (!isValidDepositPeriod) {
+            baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod)
+                    .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.between.min.and.max.deposit.term");
+        } else {
+            final Integer inMultiplesOf = this.accountTermAndPreClosure.depositTermDetail().inMultiplesOfDepositTerm();
+            if (inMultiplesOf != null) {
+                final boolean isValid = this.accountTermAndPreClosure.depositTermDetail().isValidInMultiplesOfPeriod(depositPeriod,
+                        depositPeriodFrequencyType);
+                if (!isValid) {
+                    baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod)
+                            .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.multiple.of.term");
+                }
+            }
+        }
+
+        if (this.chart != null) {
+            final LocalDate chartFromDate = this.chart.getFromDateAsLocalDate();
+            LocalDate chartEndDate = this.chart.getEndDateAsLocalDate();
+            chartEndDate = chartEndDate == null ? DateUtils.getLocalDateOfTenant() : chartEndDate;
+
+            final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate);
+            if (!chartInterval.contains(accountSubmittedOrActivationDate())) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.valid.interest.rate.slab.available");
+            }
+
+            final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount();
+            BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(),
+                    calculateMaturityDate(), this.client);
+
+            if (applicableInterestRate.equals(BigDecimal.ZERO)) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                        "no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period");
+            }
+
+        } else if (this.nominalAnnualInterestRate == null || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) {
+            baseDataValidator.reset().parameter(DepositsApiConstants.nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate)
+                    .failWithCodeNoParameterAddedToErrorCode("valid.interest.chart.or.nominal.interest.rate.required");
+        }
+
+    }
+
+    public boolean isReinvestOnClosure() {
+        return this.accountTermAndPreClosure.isReinvestOnClosure();
+    }
+
+    public boolean isTransferToSavingsOnClosure() {
+        return this.accountTermAndPreClosure.isTransferToSavingsOnClosure();
+    }
+
+    public FixedDepositAccount reInvest(BigDecimal depositAmount) {
+
+        final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount);
+        final SavingsProduct product = this.product;
+        final InterestRateChart productChart = product.applicableChart(getClosedOnDate());
+        final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart);
+
+        final AccountType accountType = AccountType.fromInt(this.accountType);
+        final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType);
+        final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType
+                .fromInt(this.interestCompoundingPeriodType);
+        final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType);
+        final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType
+                .fromInt(this.interestCalculationDaysInYearType);
+        final BigDecimal minRequiredOpeningBalance = null;
+        final BigDecimal interestRate = BigDecimal.ZERO;
+        final Set<SavingsAccountCharge> savingsAccountCharges = null;
+        final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType);
+        final Integer lockinPeriodFrequency = this.lockinPeriodFrequency;
+        final boolean withdrawalFeeApplicableForTransfer = false;
+        final String accountNumber = null;
+        final FixedDepositAccount reInvestedAccount = FixedDepositAccount
+                .createNewApplicationForSubmittal(client, group, product, savingsOfficer, accountNumber, externalId, accountType,
+                        getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, postingPeriodType, interestCalculationType,
+                        daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
+                        withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, newChart);
+
+        newAccountTermAndPreClosure.updateAccountReference(reInvestedAccount);
+        newChart.updateDepositAccountReference(reInvestedAccount);
+
+        return reInvestedAccount;
+
+    }
+
+    @Override
+    protected boolean isTransferInterestToOtherAccount() {
+        return this.accountTermAndPreClosure.isTransferInterestToLinkedAccount();
+    }
+
+    @Override
+    public boolean allowDeposit() {
+        return false;
+    }
+
+    @Override
+    public boolean allowWithdrawal() {
+        return false;
+    }
+
+    @Override
+    public boolean allowModify() {
+        return false;
+    }
+
+    @Override
+    public boolean isTransactionsAllowed() {
+        return isActive() || isAccountMatured();
+    }
+
+    private boolean isAccountMatured() {
+        return SavingsAccountStatusType.fromInt(status).isMatured();
+    }
+
+    @Override
+    public BigDecimal minBalanceForInterestCalculation() {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccountRepository.java
new file mode 100644
index 0000000..3e427ba
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccountRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.savings.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface FixedDepositAccountRepository extends JpaRepository<FixedDepositAccount, Long>,
+        JpaSpecificationExecutor<FixedDepositAccount> {
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java
new file mode 100644
index 0000000..5ab9cea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java
@@ -0,0 +1,347 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.deleteParamName;
+import static org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants.idParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Transient;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.joda.time.LocalDate;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+@Entity
+@DiscriminatorValue("200")
+public class FixedDepositProduct extends SavingsProduct {
+
+    @OneToOne(mappedBy = "product", cascade = CascadeType.ALL)
+    private DepositProductTermAndPreClosure productTermAndPreClosure;
+
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+    @JoinTable(name = "m_deposit_product_interest_rate_chart", joinColumns = @JoinColumn(name = "deposit_product_id"), inverseJoinColumns = @JoinColumn(name = "interest_rate_chart_id", unique = true))
+    protected Set<InterestRateChart> charts;
+
+    @Transient
+    protected InterestRateChartAssembler chartAssembler;
+
+    protected FixedDepositProduct() {
+        super();
+    }
+
+    public static FixedDepositProduct createNew(final String name, final String shortName, final String description,
+            final MonetaryCurrency currency, final BigDecimal interestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final Integer lockinPeriodFrequency,
+            final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final AccountingRuleType accountingRuleType,
+            final Set<Charge> charges, final DepositProductTermAndPreClosure productTermAndPreClosure, final Set<InterestRateChart> charts,
+            BigDecimal minBalanceForInterestCalculation) {
+
+        final BigDecimal minRequiredOpeningBalance = null;
+        final boolean withdrawalFeeApplicableForTransfer = false;
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = null;
+
+        return new FixedDepositProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges,
+                productTermAndPreClosure, charts, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation);
+    }
+
+    protected FixedDepositProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency,
+            final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> charges,
+            final DepositProductTermAndPreClosure productTermAndPreClosure, final Set<InterestRateChart> charts,
+            final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minBalanceForInterestCalculation) {
+
+        super(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
+                interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit,
+                minBalanceForInterestCalculation);
+
+        if (charts != null) {
+            this.charts = charts;
+        }
+
+        this.productTermAndPreClosure = productTermAndPreClosure;
+    }
+
+    public void addCharts(final Set<InterestRateChart> newCharts) {
+        final Set<InterestRateChart> existingCharts = setOfCharts();
+        existingCharts.addAll(newCharts);
+    }
+
+    public void addChart(final InterestRateChart newChart) {
+        final Set<InterestRateChart> existingCharts = setOfCharts();
+        existingCharts.add(newChart);
+    }
+
+    public Set<InterestRateChart> setOfCharts() {
+        if (this.charts == null) {
+            this.charts = new HashSet<>();
+        }
+        return this.charts;
+    }
+
+    @Override
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        actualChanges.putAll(this.update(command, baseDataValidator));
+
+        validateDomainRules(baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+        return actualChanges;
+    }
+
+    protected Map<String, Object> update(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+
+        actualChanges.putAll(super.update(command));
+
+        if (this.productTermAndPreClosure != null) {
+            actualChanges.putAll(this.productTermAndPreClosure.update(command, baseDataValidator));
+        }
+
+        // update chart Slabs
+        if (command.hasParameter(DepositsApiConstants.chartsParamName)) {
+            updateCharts(command, actualChanges, baseDataValidator);
+        }
+
+        return actualChanges;
+    }
+
+    private void updateCharts(JsonCommand command, Map<String, Object> actualChanges, final DataValidatorBuilder baseDataValidator) {
+        final Map<String, Object> deletedCharts = new HashMap<>();
+        final Map<String, Object> chartsChanges = new HashMap<>();
+
+        if (command.hasParameter(DepositsApiConstants.chartsParamName)) {
+            final JsonArray array = command.arrayOfParameterNamed(DepositsApiConstants.chartsParamName);
+            if (array != null) {
+                for (int i = 0; i < array.size(); i++) {
+                    final JsonObject chartElement = array.get(i).getAsJsonObject();
+                    JsonCommand chartCommand = JsonCommand.fromExistingCommand(command, chartElement);
+                    if (chartCommand.parameterExists(idParamName)) {
+                        final Long chartId = chartCommand.longValueOfParameterNamed(idParamName);
+                        final InterestRateChart chart = this.findChart(chartId);
+                        if (chart == null) {
+                            baseDataValidator.parameter(idParamName).value(chartId).failWithCode("no.chart.associated.with.id");
+                        } else if (chartCommand.parameterExists(deleteParamName)) {
+                            if (this.removeChart(chart)) {
+                                deletedCharts.put(idParamName, chartId);
+                            }
+                        } else {
+                            chart.update(chartCommand, chartsChanges, baseDataValidator, this.setOfCharts(), this.currency().getCode());
+                        }
+                    } else {
+                        // assemble chart
+                        final InterestRateChart newChart = this.chartAssembler.assembleFrom(chartElement, this.currency().getCode());
+                        this.addChart(newChart);
+                    }
+                }
+            }
+        }
+
+        // this.validateCharts(baseDataValidator);
+
+        // add chart changes to actual changes list.
+        if (!chartsChanges.isEmpty()) {
+            actualChanges.put(InterestRateChartApiConstants.chartSlabs, chartsChanges);
+        }
+
+        // add deleted chart to actual changes
+        if (!deletedCharts.isEmpty()) {
+            actualChanges.put("deletedChartSlabs", deletedCharts);
+        }
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    @Override
+    public InterestRateChart findChart(Long chartId) {
+        final Set<InterestRateChart> charts = setOfCharts();
+
+        for (InterestRateChart chart : charts) {
+            if (chart.getId().equals(chartId)) { return chart; }
+        }
+        return null;
+    }
+
+    private boolean removeChart(InterestRateChart chart) {
+        Set<InterestRateChart> charts = setOfCharts();
+        return charts.remove(chart);
+    }
+
+    public void setHelpers(final InterestRateChartAssembler chartAssembler) {
+        this.chartAssembler = chartAssembler;
+    }
+
+    public void validateCharts(final DataValidatorBuilder baseDataValidator) {
+        final Set<InterestRateChart> charts = this.setOfCharts();
+        for (InterestRateChart existingChart : charts) {
+            this.validateChart(baseDataValidator, existingChart);
+        }
+    }
+
+    public void validateChart(final DataValidatorBuilder baseDataValidator, final InterestRateChart comparingChart) {
+        final Set<InterestRateChart> charts = this.setOfCharts();
+        for (InterestRateChart existingChart : charts) {
+            if (!existingChart.equals(comparingChart)) {
+                if (existingChart.chartFields().isOverlapping(comparingChart.chartFields())) {
+                    baseDataValidator.failWithCodeNoParameterAddedToErrorCode("chart.overlapping.from.and.end.dates",
+                            existingChart.getFromDateAsLocalDate(), existingChart.getEndDateAsLocalDate(),
+                            comparingChart.getFromDateAsLocalDate(), comparingChart.getEndDateAsLocalDate());
+                }
+            }
+        }
+    }
+
+    @Override
+    public InterestRateChart applicableChart(final LocalDate target) {
+        InterestRateChart applicableChart = null;
+        if (this.charts != null) {
+            for (InterestRateChart chart : this.charts) {
+                if (chart.isApplicableChartFor(target)) {
+                    applicableChart = chart;
+                    break;
+                }
+            }
+        }
+        return applicableChart;
+    }
+
+    public DepositProductTermAndPreClosure depositProductTermAndPreClosure() {
+        return this.productTermAndPreClosure;
+    }
+
+    public void validateInterestPostingAndCompoundingPeriodTypes(final DataValidatorBuilder baseDataValidator) {
+        Map<SavingsPostingInterestPeriodType, List<SavingsCompoundingInterestPeriodType>> postingtoCompoundMap = new HashMap<>();
+        postingtoCompoundMap.put(
+                SavingsPostingInterestPeriodType.MONTHLY,
+                Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY,
+                        SavingsCompoundingInterestPeriodType.MONTHLY }));
+
+        postingtoCompoundMap.put(
+                SavingsPostingInterestPeriodType.QUATERLY,
+                Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY,
+                        SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY }));
+
+        postingtoCompoundMap.put(
+                SavingsPostingInterestPeriodType.BIANNUAL,
+                Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY,
+                        SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY,
+                        SavingsCompoundingInterestPeriodType.BI_ANNUAL }));
+
+        postingtoCompoundMap.put(
+                SavingsPostingInterestPeriodType.ANNUAL,
+                Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY,
+                        SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY,
+                        SavingsCompoundingInterestPeriodType.BI_ANNUAL, SavingsCompoundingInterestPeriodType.ANNUAL }));
+
+        SavingsPostingInterestPeriodType savingsPostingInterestPeriodType = SavingsPostingInterestPeriodType
+                .fromInt(interestPostingPeriodType);
+        SavingsCompoundingInterestPeriodType savingsCompoundingInterestPeriodType = SavingsCompoundingInterestPeriodType
+                .fromInt(interestCompoundingPeriodType);
+
+        if (postingtoCompoundMap.get(savingsPostingInterestPeriodType) == null
+                || !postingtoCompoundMap.get(savingsPostingInterestPeriodType).contains(savingsCompoundingInterestPeriodType)) {
+            baseDataValidator.failWithCodeNoParameterAddedToErrorCode("posting.period.type.is.less.than.compound.period.type",
+                    savingsPostingInterestPeriodType.name(), savingsCompoundingInterestPeriodType.name());
+
+        }
+    }
+
+    public void validateDomainRules() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
+        validateDomainRules(baseDataValidator);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateDomainRules(final DataValidatorBuilder baseDataValidator) {
+
+        final DepositTermDetail termDetails = this.depositProductTermAndPreClosure().depositTermDetail();
+        final boolean isMinTermGreaterThanMax = termDetails.isMinDepositTermGreaterThanMaxDepositTerm();
+        if (isMinTermGreaterThanMax) {
+            final Integer maxTerm = termDetails.maxDepositTerm();
+            baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm)
+                    .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term");
+        }
+
+        if (this.charts != null) {
+            validateCharts(baseDataValidator);
+        } else if (this.nominalAnnualInterestRate == null || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) {
+            baseDataValidator.reset().parameter(DepositsApiConstants.nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate)
+                    .failWithCodeNoParameterAddedToErrorCode("interest.chart.or.nominal.interest.rate.required");
+        }
+
+        this.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProductRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProductRepository.java
new file mode 100644
index 0000000..cc09aa9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProductRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.savings.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface FixedDepositProductRepository extends JpaRepository<FixedDepositProduct, Long>,
+        JpaSpecificationExecutor<FixedDepositProduct> {
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java
new file mode 100644
index 0000000..0d6f857
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java
@@ -0,0 +1,1179 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+
+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.LocalDateInterval;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
+import org.apache.fineract.portfolio.savings.DepositAccountUtils;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
+import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+@Entity
+@DiscriminatorValue("300")
+public class RecurringDepositAccount extends SavingsAccount {
+
+    @OneToOne(mappedBy = "account", cascade = CascadeType.ALL)
+    private DepositAccountTermAndPreClosure accountTermAndPreClosure;
+
+    @OneToOne(mappedBy = "account", cascade = CascadeType.ALL)
+    private DepositAccountRecurringDetail recurringDetail;
+
+    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account")
+    private DepositAccountInterestRateChart chart;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true)
+    private List<RecurringDepositScheduleInstallment> depositScheduleInstallments = new ArrayList<>();
+
+    protected RecurringDepositAccount() {
+        //
+    }
+
+    public static RecurringDepositAccount createNewApplicationForSubmittal(final Client client, final Group group,
+            final SavingsProduct product, final Staff fieldOfficer, final String accountNo, final String externalId,
+            final AccountType accountType, final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges,
+            final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail,
+            final DepositAccountInterestRateChart chart) {
+
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = new BigDecimal(0);
+
+        final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL;
+        return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType,
+                submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
+                interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure,
+                recurringDetail, chart, allowOverdraft, overdraftLimit);
+    }
+
+    public static RecurringDepositAccount createNewActivatedAccount(final Client client, final Group group, final SavingsProduct product,
+            final Staff fieldOfficer, final String accountNo, final String externalId, final AccountType accountType,
+            final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges,
+            final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail,
+            final DepositAccountInterestRateChart chart) {
+
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = new BigDecimal(0);
+
+        final SavingsAccountStatusType status = SavingsAccountStatusType.ACTIVE;
+        return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType,
+                submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
+                interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure,
+                recurringDetail, chart, allowOverdraft, overdraftLimit);
+    }
+
+    private RecurringDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer,
+            final String accountNo, final String externalId, final SavingsAccountStatusType status, final AccountType accountType,
+            final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges,
+            final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail,
+            final DepositAccountInterestRateChart chart, final boolean allowOverdraft, final BigDecimal overdraftLimit) {
+
+        super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy,
+                nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
+                interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
+                withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit);
+
+        this.accountTermAndPreClosure = accountTermAndPreClosure;
+        this.recurringDetail = recurringDetail;
+        this.chart = chart;
+        if (this.chart != null) {
+            this.chart.updateDepositAccountReference(this);
+        }
+    }
+
+    @Override
+    public void modifyApplication(final JsonCommand command, final Map<String, Object> actualChanges) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction);
+        super.modifyApplication(command, actualChanges, baseDataValidator);
+        final Map<String, Object> termAndPreClosureChanges = accountTermAndPreClosure.update(command, baseDataValidator);
+        actualChanges.putAll(termAndPreClosureChanges);
+        recurringDetail.update(command);
+
+        validateDomainRules(baseDataValidator);
+        super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    @Override
+    protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate) {
+        boolean isPreMatureClosure = false;
+        return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, isPreMatureClosure);
+    }
+
+    protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate,
+            final boolean isPreMatureClosure) {
+
+        boolean applyPreMaturePenalty = false;
+        BigDecimal penalInterest = BigDecimal.ZERO;
+        LocalDate depositCloseDate = calculateMaturityDate();
+        if (isPreMatureClosure) {
+            if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) {
+                applyPreMaturePenalty = true;
+                penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest();
+                final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure.depositPreClosureDetail()
+                        .preClosurePenalInterestOnType();
+                if (preClosurePenalInterestOnType.isWholeTerm()) {
+                    depositCloseDate = interestCalculatedUpto();
+                } else if (preClosurePenalInterestOnType.isTillPrematureWithdrawal()) {
+                    depositCloseDate = interestPostingUpToDate;
+                }
+            }
+        }
+
+        if (depositCloseDate == null) {
+            depositCloseDate = LocalDate.now();
+        }
+
+        final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount();
+        BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate,
+                this.client);
+
+        if (applyPreMaturePenalty) {
+            applicableInterestRate = applicableInterestRate.subtract(penalInterest);
+            applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) == -1 ? BigDecimal.ZERO : applicableInterestRate;
+        }
+
+        this.nominalAnnualInterestRate = applicableInterestRate;
+
+        return applicableInterestRate.divide(BigDecimal.valueOf(100l), mc);
+    }
+
+    public void updateMaturityDateAndAmount(final MathContext mc, final boolean isPreMatureClosure,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        final LocalDate maturityDate = calculateMaturityDate();
+        LocalDate interestCalculationUpto = null;
+        List<SavingsAccountTransaction> allTransactions = null;
+        if (maturityDate == null) {
+            interestCalculationUpto = DateUtils.getLocalDateOfTenant();
+            allTransactions = getTransactions(interestCalculationUpto, false);
+        } else {
+            interestCalculationUpto = maturityDate.minusDays(1);
+            allTransactions = getTransactions(interestCalculationUpto, true);
+
+        }
+
+        final List<PostingPeriod> postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, allTransactions,
+                isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        Money totalInterestPayable = Money.zero(getCurrency());
+        Money totalDepositAmount = Money.zero(getCurrency());
+        for (PostingPeriod postingPeriod : postingPeriods) {
+            totalInterestPayable = totalInterestPayable.plus(postingPeriod.getInterestEarned());
+            totalDepositAmount = totalDepositAmount.plus(postingPeriod.closingBalance()).minus(postingPeriod.openingBalance());
+        }
+        if (maturityDate == null) {
+            this.accountTermAndPreClosure.updateDepositAmount(totalDepositAmount.getAmount());
+        } else {
+            this.accountTermAndPreClosure.updateMaturityDetails(totalDepositAmount.getAmount(), totalInterestPayable.getAmount(),
+                    maturityDate);
+        }
+    }
+
+    public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.updateMaturityDetailsAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final LocalDate todayDate = DateUtils.getLocalDateOfTenant();
+        if (!this.maturityDate().isAfter(todayDate)) {
+            // update account status
+            this.status = SavingsAccountStatusType.MATURED.getValue();
+            postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, todayDate);
+        }
+    }
+
+    public LocalDate calculateMaturityDate() {
+
+        final LocalDate startDate = depositStartDate();
+        LocalDate maturityDate = null;
+        final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod();
+        if (depositPeriod == null) { return maturityDate; }
+        switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) {
+            case DAYS:
+                maturityDate = startDate.plusDays(depositPeriod);
+            break;
+            case WEEKS:
+                maturityDate = startDate.plusWeeks(depositPeriod);
+            break;
+            case MONTHS:
+                maturityDate = startDate.plusMonths(depositPeriod);
+            break;
+            case YEARS:
+                maturityDate = startDate.plusYears(depositPeriod);
+            break;
+            case INVALID:
+            break;
+        }
+
+        return maturityDate;
+    }
+
+    private List<PostingPeriod> calculateInterestPayable(final MathContext mc, final LocalDate maturityDate,
+            final List<SavingsAccountTransaction> transactions, final boolean isPreMatureClosure,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+
+        // 1. default to calculate interest based on entire history OR
+        // 2. determine latest 'posting period' and find interest credited to
+        // that period
+
+        // A generate list of EndOfDayBalances (not including interest postings)
+        final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType);
+
+        final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType
+                .fromInt(this.interestCompoundingPeriodType);
+
+        final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType
+                .fromInt(this.interestCalculationDaysInYearType);
+
+        final List<LocalDateInterval> postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(depositStartDate(),
+                maturityDate, postingPeriodType, financialYearBeginningMonth);
+
+        final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
+
+        Money periodStartingBalance = Money.zero(currency);
+
+        final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType);
+        final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure);
+        final Collection<Long> interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId());
+        boolean isInterestTransfer = false;
+        final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation());
+        for (final LocalDateInterval periodInterval : postingPeriodIntervals) {
+            final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, transactions,
+                    this.currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(),
+                    maturityDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation,
+                    isSavingsInterestPostingAtCurrentPeriodEnd);
+
+            periodStartingBalance = postingPeriod.closingBalance();
+
+            allPostingPeriods.add(postingPeriod);
+        }
+
+        this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, this.getLockedInUntilLocalDate(),
+                isTransferInterestToOtherAccount());
+        // this.summary.updateFromInterestPeriodSummaries(this.currency,
+        // allPostingPeriods);
+        return allPostingPeriods;
+    }
+
+    private List<SavingsAccountTransaction> getTransactions(final LocalDate depositEndDate, final boolean generateFutureTransactions) {
+        List<SavingsAccountTransaction> allTransactions = new ArrayList<>();
+        // add existing transactions
+        allTransactions.addAll(retreiveOrderedNonInterestPostingTransactions());
+        if (generateFutureTransactions) {
+            for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) {
+                if (installment.isPrincipalNotCompleted(getCurrency())) {
+                    final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null,
+                            installment.dueDate(), installment.getDepositAmountOutstanding(getCurrency()), installment.dueDate().toDate(),
+                            null);
+                    allTransactions.add(transaction);
+                }
+            }
+        }
+
+        allTransactions = sortTransactions(allTransactions);
+        Money runningBalance = Money.zero(getCurrency());
+        for (final SavingsAccountTransaction transaction : allTransactions) {
+            if (transaction.isReversed()) {
+                transaction.zeroBalanceFields();
+            } else {
+
+                Money transactionAmount = Money.zero(this.currency);
+                if (transaction.isCredit()) {
+                    transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency));
+                } else if (transaction.isDebit()) {
+                    transactionAmount = transactionAmount.minus(transaction.getAmount(this.currency));
+                }
+
+                runningBalance = runningBalance.plus(transactionAmount);
+                transaction.updateRunningBalance(runningBalance);
+            }
+        }
+        // loop over transactions in reverse
+        LocalDate endOfBalanceDate = depositEndDate;
+        for (int i = allTransactions.size() - 1; i >= 0; i--) {
+            final SavingsAccountTransaction transaction = allTransactions.get(i);
+            if (transaction.isNotReversed() && !transaction.isInterestPostingAndNotReversed()) {
+                transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate);
+                endOfBalanceDate = transaction.transactionLocalDate().minusDays(1);
+            }
+        }
+        return allTransactions;
+    }
+
+    public LocalDate depositStartDate() {
+        final LocalDate depositStartDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate();
+        if (depositStartDate == null) return accountSubmittedOrActivationDate();
+        return depositStartDate;
+    }
+
+    public void prematureClosure(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate,
+            final Map<String, Object> actualChanges) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+
+        if (closedDate.isBefore(getActivationLocalDate())) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.after.activation.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (isAccountLocked(closedDate)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.after.lockin.period");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (closedDate.isAfter(maturityDate())) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.before.maturity.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (closedDate.isAfter(tenantsTodayDate)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("cannot.be.a.future.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (isAccountLocked(calculateMaturityDate())) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("deposit.period.must.be.greater.than.lock.in.period",
+                    "Deposit period must be greater than account lock-in period.");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+        if (savingsAccountTransactions.size() > 0) {
+            final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
+            if (accountTransaction.isAfter(closedDate)) {
+                baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                        .failWithCode("must.be.after.last.transaction.date");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate);
+        this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue();
+
+        final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
+        final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
+        this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType);
+
+        /*
+         * // withdraw deposit amount before closing the account final Money
+         * transactionAmountMoney = Money.of(this.currency,
+         * this.getAccountBalance()); final SavingsAccountTransaction withdraw =
+         * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail,
+         * closedDate, transactionAmountMoney, new Date());
+         * this.transactions.add(withdraw);
+         */
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+        actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
+        actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
+        actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt));
+
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = closedDate.toDate();
+        this.closedBy = currentUser;
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+
+    }
+
+    @Override
+    public Money activateWithBalance() {
+        return Money.of(this.currency, this.minRequiredOpeningBalance);
+    }
+
+    protected void processAccountUponActivation(final DateTimeFormatter fmt, final AppUser user) {
+        final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance);
+        if (minRequiredOpeningBalance.isGreaterThanZero()) {
+            final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationLocalDate(),
+                    minRequiredOpeningBalance.getAmount(), null, new Date(), user);
+            deposit(transactionDTO);
+
+            // update existing transactions so derived balance fields are
+            // correct.
+            recalculateDailyBalances(Money.zero(this.currency), DateUtils.getLocalDateOfTenant());
+        }
+    }
+
+    public void close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate,
+            final Map<String, Object> actualChanges) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus) && this.maturityDate() != null) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.matured.state");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+
+        if (closedDate.isBefore(getActivationLocalDate())) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.after.activation.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        if (maturityDate() != null && closedDate.isBefore(maturityDate())) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.after.account.maturity.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        if (closedDate.isAfter(tenantsTodayDate)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("cannot.be.a.future.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+        if (savingsAccountTransactions.size() > 0) {
+            final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
+            if (accountTransaction.isAfter(closedDate)) {
+                baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                        .failWithCode("must.be.after.last.transaction.date");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate);
+        this.status = SavingsAccountStatusType.CLOSED.getValue();
+
+        final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
+        final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
+        this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType);
+
+        /*
+         * // withdraw deposit amount before closing the account final Money
+         * transactionAmountMoney = Money.of(this.currency,
+         * this.getAccountBalance()); final SavingsAccountTransaction withdraw =
+         * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail,
+         * closedDate, transactionAmountMoney, new Date());
+         * this.transactions.add(withdraw);
+         */
+
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+        actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
+        actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
+        actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt));
+
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = closedDate.toDate();
+        this.closedBy = currentUser;
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+    }
+
+    public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
+            final LocalDate closeDate) {
+        LocalDate interestPostingUpToDate = maturityDate();
+        if (interestPostingUpToDate == null) {
+            interestPostingUpToDate = closeDate;
+        }
+        final MathContext mc = MathContext.DECIMAL64;
+        boolean isInterestTransfer = false;
+        final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate.minusDays(1), isInterestTransfer,
+                isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+        Money interestPostedToDate = Money.zero(this.currency);
+
+        boolean recalucateDailyBalanceDetails = false;
+
+        for (final PostingPeriod interestPostingPeriod : postingPeriods) {
+
+            LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction();
+            interestPostingTransactionDate = interestPostingTransactionDate.isAfter(interestPostingUpToDate) ? interestPostingUpToDate
+                    : interestPostingTransactionDate;
+            final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned();
+
+            interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod);
+
+            final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate);
+            if (postingTransaction == null) {
+                final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(),
+                        interestPostingTransactionDate, interestEarnedToBePostedForPeriod);
+                this.transactions.add(newPostingTransaction);
+                recalucateDailyBalanceDetails = true;
+            } else {
+                final boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod);
+                if (correctionRequired) {
+                    postingTransaction.reverse();
+                    final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(),
+                            interestPostingTransactionDate, interestEarnedToBePostedForPeriod);
+                    this.transactions.add(newPostingTransaction);
+                    recalucateDailyBalanceDetails = true;
+                }
+            }
+        }
+
+        if (recalucateDailyBalanceDetails) {
+            // update existing transactions so derived balance fields are
+            // correct.
+            recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate);
+        }
+
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+    }
+
+    public void postPreMaturityInterest(final LocalDate accountCloseDate, final boolean isPreMatureClosure,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+
+        final Money interestPostedToDate = totalInterestPosted();
+        // calculate interest before one day of closure date
+        final LocalDate interestCalculatedToDate = accountCloseDate.minusDays(1);
+        final Money interestOnMaturity = calculatePreMatureInterest(interestCalculatedToDate,
+                retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        boolean recalucateDailyBalance = false;
+
+        // post remaining interest
+        final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate);
+        if (!remainigInterestToBePosted.isZero()) {
+            final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(),
+                    accountCloseDate, remainigInterestToBePosted);
+            this.transactions.add(newPostingTransaction);
+            recalucateDailyBalance = true;
+        }
+
+        if (recalucateDailyBalance) {
+            // update existing transactions so derived balance fields are
+            // correct.
+            recalculateDailyBalances(Money.zero(this.currency), accountCloseDate);
+        }
+
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+        this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate);
+    }
+
+    public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, final boolean isPreMatureClosure,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+
+        final Money interestPostedToDate = totalInterestPosted().copy();
+
+        final Money interestEarnedTillDate = calculatePreMatureInterest(preMatureDate, retreiveOrderedNonInterestPostingTransactions(),
+                isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+        final Money accountBalance = Money.of(getCurrency(), getAccountBalance());
+        final Money maturityAmount = accountBalance.minus(interestPostedToDate).plus(interestEarnedTillDate);
+
+        return maturityAmount.getAmount();
+    }
+
+    private Money calculatePreMatureInterest(final LocalDate preMatureDate, final List<SavingsAccountTransaction> transactions,
+            final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd,
+            final Integer financialYearBeginningMonth) {
+        final MathContext mc = MathContext.DECIMAL64;
+        final List<PostingPeriod> postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, isPreMatureClosure,
+                isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+        Money interestOnMaturity = Money.zero(this.currency);
+
+        for (final PostingPeriod interestPostingPeriod : postingPeriods) {
+            final Money interestEarnedForPeriod = interestPostingPeriod.getInterestEarned();
+            interestOnMaturity = interestOnMaturity.plus(interestEarnedForPeriod);
+        }
+        this.summary.updateFromInterestPeriodSummaries(this.currency, postingPeriods);
+        return interestOnMaturity;
+    }
+
+    @Override
+    public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
+        super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+    }
+
+    @Override
+    public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+        final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
+        return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+    }
+
+    private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) {
+        LocalDate interestPostingUpToDate = interestPostingDate;
+        final LocalDate uptoMaturityDate = interestCalculatedUpto();
+        if (uptoMaturityDate != null && uptoMaturityDate.isBefore(interestPostingDate)) {
+            interestPostingUpToDate = uptoMaturityDate;
+        }
+        return interestPostingUpToDate;
+    }
+
+    public LocalDate maturityDate() {
+        return this.accountTermAndPreClosure.getMaturityLocalDate();
+    }
+
+    public BigDecimal maturityAmount() {
+        return this.accountTermAndPreClosure.maturityAmount();
+    }
+
+    private LocalDate interestCalculatedUpto() {
+        LocalDate uptoMaturityDate = calculateMaturityDate();
+        if (uptoMaturityDate != null) {
+            // interest should not be calculated for maturity day
+            uptoMaturityDate = uptoMaturityDate.minusDays(1);
+        }
+        return uptoMaturityDate;
+    }
+
+    private Money totalInterestPosted() {
+        Money interestPostedToDate = Money.zero(this.currency);
+        for (final SavingsAccountTransaction transaction : this.transactions) {
+            if (transaction.isInterestPostingAndNotReversed()) {
+                interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency));
+            }
+        }
+
+        return interestPostedToDate;
+    }
+
+    @Override
+    public Map<String, Object> activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
+
+        final Map<String, Object> actualChanges = super.activate(currentUser, command, tenantsTodayDate);
+
+        if (accountTermAndPreClosure.isAfterExpectedFirstDepositDate(getActivationLocalDate())) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                    .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+            final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
+            final String dateAsString = formatter.print(this.accountTermAndPreClosure.getExpectedFirstDepositOnDate());
+            baseDataValidator.reset().parameter(DepositsApiConstants.activatedOnDateParamName).value(dateAsString)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.expected.first.deposit.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        return actualChanges;
+    }
+
+    protected List<SavingsAccountTransaction> sortTransactions(final List<SavingsAccountTransaction> transactions) {
+        final List<SavingsAccountTransaction> listOfTransactionsSorted = new ArrayList<>();
+        listOfTransactionsSorted.addAll(transactions);
+
+        final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator();
+        Collections.sort(listOfTransactionsSorted, transactionComparator);
+        return listOfTransactionsSorted;
+    }
+
+    @Override
+    public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO) {
+
+        if (isAccountMatured()) {
+            final String defaultUserMessage = "Transaction is not allowed. Account is matured.";
+            final ApiParameterError error = ApiParameterError.parameterError(
+                    "error.msg.recurring.deposit.account.transaction.account.is.matured", defaultUserMessage, "transactionDate",
+                    transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter()));
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (!isBeforeMaturityDate(transactionDTO.getTransactionDate())) {
+            final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account maturity date.";
+            final ApiParameterError error = ApiParameterError.parameterError(
+                    "error.msg.recurring.deposit.account.transaction.date.is.after.account.maturity.date", defaultUserMessage,
+                    "transactionDate", transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter()));
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (isBeforeDepositStartDate(transactionDTO.getTransactionDate())) {
+            final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account activation and deposit start date.";
+            final ApiParameterError error = ApiParameterError.parameterError(
+                    "error.msg.recurring.deposit.account.transaction.date.is.before.account.activation.or.deposit.date",
+                    defaultUserMessage, "transactionDate", transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter()));
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        final SavingsAccountTransaction transaction = super.deposit(transactionDTO);
+
+        return transaction;
+    }
+
+    public void handleScheduleInstallments(final SavingsAccountTransaction transaction) {
+
+        final LocalDate transactionDate = transaction.transactionLocalDate();
+        Money transactionAmountUnprocessed = transaction.getAmount(getCurrency());
+
+        for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) {
+            if (currentInstallment.isNotFullyPaidOff() && transactionAmountUnprocessed.isGreaterThanZero()) {
+                if (!this.adjustAdvanceTowardsFuturePayments() && currentInstallment.dueDate().isAfter(transactionDate)) {
+                    transactionAmountUnprocessed = Money.zero(getCurrency());
+                }
+                transactionAmountUnprocessed = handleInstallmentTransaction(currentInstallment, transactionAmountUnprocessed,
+                        transactionDate);
+            }
+        }
+
+    }
+
+    public void updateScheduleInstallments() {
+
+        // reset all installments to process from the beginning
+        for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) {
+            currentInstallment.resetDerivedFields();
+        }
+
+        final List<SavingsAccountTransaction> orderedDepositTransactions = retreiveOrderedDepositTransactions();
+        for (SavingsAccountTransaction transaction : orderedDepositTransactions) {
+            handleScheduleInstallments(transaction);
+        }
+    }
+
+    public void updateScheduleInstallmentsWithNewRecommendedDepositAmount(BigDecimal newDepositAmount,
+            LocalDate depositAmountupdatedFromDate) {
+        // reset all installments to process from the beginning, also update
+        // deposit amount as necessary
+        for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) {
+            if (currentInstallment.dueDate().isAfter(depositAmountupdatedFromDate)
+                    || currentInstallment.dueDate().isEqual(depositAmountupdatedFromDate)) {
+                currentInstallment.updateDepositAmountAndResetDerivedFields(newDepositAmount);
+            } else {
+                currentInstallment.resetDerivedFields();
+            }
+        }
+
+        final List<SavingsAccountTransaction> orderedDepositTransactions = retreiveOrderedDepositTransactions();
+        for (SavingsAccountTransaction transaction : orderedDepositTransactions) {
+            handleScheduleInstallments(transaction);
+        }
+    }
+
+    private List<SavingsAccountTransaction> retreiveOrderedDepositTransactions() {
+        final List<SavingsAccountTransaction> listOfTransactionsSorted = retreiveListOfTransactions();
+
+        final List<SavingsAccountTransaction> orderedDepositTransactions = new ArrayList<>();
+
+        for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) {
+            if (transaction.isDepositAndNotReversed()) {
+                orderedDepositTransactions.add(transaction);
+            }
+        }
+
+        return orderedDepositTransactions;
+    }
+
+    /**
+     * This method is responsible for checking if the current transaction is 'an
+     * advance/early payment' based on the details passed through.
+     * 
+     * Default implementation is check transaction date is before installment
+     * due date.
+     */
+    protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
+            final List<RecurringDepositScheduleInstallment> installments, final LocalDate transactionDate) {
+
+        final RecurringDepositScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
+
+        return transactionDate.isBefore(currentInstallment.dueDate());
+    }
+
+    private Money handleInstallmentTransaction(final RecurringDepositScheduleInstallment currentInstallment,
+            final Money transactionAmountUnprocessed, final LocalDate transactionDate) {
+
+        Money transactionAmountRemaining = transactionAmountUnprocessed;
+        Money depositAmountPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+        depositAmountPortion = currentInstallment.payInstallment(transactionDate, transactionAmountRemaining);
+        transactionAmountRemaining = transactionAmountRemaining.minus(depositAmountPortion);
+
+        return transactionAmountRemaining;
+
+    }
+
+    private boolean isAccountMatured() {
+        return SavingsAccountStatusType.fromInt(status).isMatured();
+    }
+
+    private boolean isBeforeMaturityDate(final LocalDate compareDate) {
+        final LocalDate maturityDate = this.maturityDate();
+        return maturityDate == null ? true : compareDate.isBefore(maturityDate);
+    }
+
+    private boolean isBeforeDepositStartDate(LocalDate compareDate) {
+        return compareDate.isBefore(depositStartDate());
+    }
+
+    public void validateDomainRules() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        validateDomainRules(baseDataValidator);
+        super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    private void validateDomainRules(final DataValidatorBuilder baseDataValidator) {
+
+        final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure.depositTermDetail()
+                .isMinDepositTermGreaterThanMaxDepositTerm();
+        // deposit period should be within min and max deposit term
+        if (isMinTermGreaterThanMax) {
+            final Integer maxTerm = this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm();
+            baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm)
+                    .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term");
+        }
+
+        final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod();
+        if (this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm() != null) {
+            baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull();
+        }
+        if (depositPeriod != null) {
+            final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure.depositPeriodFrequencyType();
+            final boolean isValidDepositPeriod = this.accountTermAndPreClosure.depositTermDetail().isDepositBetweenMinAndMax(
+                    depositStartDate(), calculateMaturityDate());
+            if (!isValidDepositPeriod) {
+                baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod)
+                        .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.between.min.and.max.deposit.term");
+            } else {
+                final Integer inMultiplesOf = this.accountTermAndPreClosure.depositTermDetail().inMultiplesOfDepositTerm();
+                if (inMultiplesOf != null) {
+                    final boolean isValid = this.accountTermAndPreClosure.depositTermDetail().isValidInMultiplesOfPeriod(depositPeriod,
+                            depositPeriodFrequencyType);
+                    if (!isValid) {
+                        baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod)
+                                .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.multiple.of.term");
+                    }
+                }
+            }
+            if (isAccountLocked(calculateMaturityDate())) {
+                baseDataValidator
+                        .reset()
+                        .parameter(depositPeriodParamName)
+                        .value(depositPeriod)
+                        .failWithCode("deposit.period.must.be.greater.than.lock.in.period",
+                                "Deposit period must be greater than account lock-in period.");
+            }
+        }
+
+        if (firstDepositDateBeforeAccountSubmittedOrActivationDate()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                    "expected.first.deposit.date.must.be.after.account.submitted.or.activation.date");
+        }
+
+        // FIXME: Handle this scenario
+        /*
+         * //final boolean recurringFrequencyBeforeDepositPeriod =
+         * recurringFrequencyBeforeDepositPeriod();
+         * 
+         * if (!recurringFrequencyBeforeDepositPeriod) {
+         * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+         * "recurring.frequency.not.before.deposit.period"); }
+         */
+    }
+
+    public void validateApplicableInterestRate() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        LocalDate maturityDate = calculateMaturityDate();
+        if (this.chart != null && maturityDate != null) {
+            final LocalDate chartFromDate = this.chart.getFromDateAsLocalDate();
+            LocalDate chartEndDate = this.chart.getEndDateAsLocalDate();
+            chartEndDate = chartEndDate == null ? DateUtils.getLocalDateOfTenant() : chartEndDate;
+
+            final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate);
+            if (!chartInterval.contains(accountSubmittedOrActivationDate())) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.valid.interest.rate.slab.available.for.date.range");
+            }
+
+            final BigDecimal maturityAmount = this.accountTermAndPreClosure.maturityAmount();
+            BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(maturityAmount, depositStartDate(), maturityDate,
+                    this.client);
+
+            if (applicableInterestRate.equals(BigDecimal.ZERO)) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                        "no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period");
+            }
+
+        } else if (this.nominalAnnualInterestRate == null || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) {
+            baseDataValidator.reset().parameter(DepositsApiConstants.nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate)
+                    .failWithCodeNoParameterAddedToErrorCode("interest.chart.or.nominal.interest.rate.required");
+        }
+
+        /**
+         * final boolean recurringFrequencyBeforeDepositPeriod =
+         * recurringFrequencyBeforeDepositPeriod();
+         * 
+         * if (!recurringFrequencyBeforeDepositPeriod) {
+         * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+         * "recurring.frequency.not.before.deposit.period"); }
+         **/
+    }
+
+    public boolean isReinvestOnClosure() {
+        return this.accountTermAndPreClosure.isReinvestOnClosure();
+    }
+
+    public boolean isTransferToSavingsOnClosure() {
+        return this.accountTermAndPreClosure.isTransferToSavingsOnClosure();
+    }
+
+    public RecurringDepositAccount reInvest(BigDecimal depositAmount) {
+
+        final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount);
+        final DepositAccountRecurringDetail recurringDetail = this.recurringDetail.copy();
+        final SavingsProduct product = this.product;
+        final InterestRateChart productChart = product.applicableChart(getClosedOnDate());
+        final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart);
+        final String accountNumber = null;
+        final String externalId = this.externalId;
+        final AccountType accountType = AccountType.fromInt(this.accountType);
+        final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType);
+        final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType
+                .fromInt(this.interestCompoundingPeriodType);
+        final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType);
+        final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType
+                .fromInt(this.interestCalculationDaysInYearType);
+        final BigDecimal minRequiredOpeningBalance = depositAmount;
+        final BigDecimal interestRate = BigDecimal.ZERO;
+        final Set<SavingsAccountCharge> savingsAccountCharges = null;
+        final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType);
+        final Integer lockinPeriodFrequency = this.lockinPeriodFrequency;
+        final boolean withdrawalFeeApplicableForTransfer = false;
+
+        LocalDate now = DateUtils.getLocalDateOfTenant();
+
+        newAccountTermAndPreClosure.updateExpectedFirstDepositDate(now);
+
+        RecurringDepositAccount rdAccount = RecurringDepositAccount.createNewActivatedAccount(client, group, product, savingsOfficer,
+                accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType,
+                postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure,
+                recurringDetail, newChart);
+
+        rdAccount.setDatesFrom(now);
+        newAccountTermAndPreClosure.updateAccountReference(rdAccount);
+        recurringDetail.updateAccountReference(rdAccount);
+
+        return rdAccount;
+
+    }
+
+    private boolean firstDepositDateBeforeAccountSubmittedOrActivationDate() {
+        final LocalDate expectedFirstDepositLocalDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate();
+        if (expectedFirstDepositLocalDate == null) return false;
+        return expectedFirstDepositLocalDate.isBefore(accountSubmittedOrActivationDate());
+    }
+
+    public void setDatesFrom(final LocalDate now) {
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = null;
+        this.closedBy = null;
+
+        this.activatedBy = null;
+        this.lockedInUntilDate = null;
+
+        this.activatedOnDate = now.toDate();
+    }
+
+    @Override
+    protected boolean isTransferInterestToOtherAccount() {
+        return this.accountTermAndPreClosure.isTransferInterestToLinkedAccount();
+    }
+
+    public void generateSchedule(final PeriodFrequencyType frequency, final Integer recurringEvery, final Calendar calendar) {
+        final List<RecurringDepositScheduleInstallment> depositScheduleInstallments = depositScheduleInstallments();
+        depositScheduleInstallments.clear();
+        LocalDate installmentDate = null;
+        if (this.isCalendarInherited()) {
+            installmentDate = CalendarUtils.getNextScheduleDate(calendar, accountSubmittedOrActivationDate());
+        } else {
+            installmentDate = depositStartDate();
+        }
+
+        int installmentNumber = 1;
+        final LocalDate maturityDate = calcualteScheduleTillDate(frequency, recurringEvery);
+        final BigDecimal depositAmount = this.recurringDetail.mandatoryRecommendedDepositAmount();
+        while (maturityDate.isAfter(installmentDate)) {
+            final RecurringDepositScheduleInstallment installment = RecurringDepositScheduleInstallment.installment(this,
+                    installmentNumber, installmentDate.toDate(), depositAmount);
+            depositScheduleInstallments.add(installment);
+            installmentDate = DepositAccountUtils.calculateNextDepositDate(installmentDate, frequency, recurringEvery);
+            installmentNumber += 1;
+        }
+    }
+
+    private LocalDate calcualteScheduleTillDate(final PeriodFrequencyType frequency, final Integer recurringEvery) {
+        LocalDate tillDate = calculateMaturityDate();
+        if (tillDate == null) {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            tillDate = DepositAccountUtils.calculateNextDepositDate(today, frequency, recurringEvery
+                    * (DepositAccountUtils.GENERATE_MINIMUM_NUMBER_OF_FUTURE_INSTALMENTS + 1));
+        }
+        return tillDate;
+    }
+
+    private List<RecurringDepositScheduleInstallment> depositScheduleInstallments() {
+        if (this.depositScheduleInstallments == null) {
+            this.depositScheduleInstallments = new ArrayList<>();
+        }
+        return this.depositScheduleInstallments;
+    }
+
+    public boolean isCalendarInherited() {
+        return this.recurringDetail.isCalendarInherited();
+    }
+
+    public void updateOverduePayments(final LocalDate todayDate) {
+        LocalDate overdueUptoDate = this.maturityDate();
+        if (overdueUptoDate == null || overdueUptoDate.isAfter(todayDate)) {
+            overdueUptoDate = todayDate;
+        }
+
+        final List<RecurringDepositScheduleInstallment> installments = depositScheduleInstallments();
+        int noOfOverdueInstallments = 0;
+        Money totalOverdueAmount = Money.zero(getCurrency());
+        for (RecurringDepositScheduleInstallment installment : installments) {
+            if (installment.isNotFullyPaidOff() && overdueUptoDate.isAfter(installment.dueDate())) {
+                noOfOverdueInstallments++;
+                totalOverdueAmount = totalOverdueAmount.plus(installment.getDepositAmountOutstanding(getCurrency()));
+            }
+        }
+        this.recurringDetail.updateOverdueDetails(noOfOverdueInstallments, totalOverdueAmount);
+    }
+
+    @Override
+    public boolean allowWithdrawal() {
+        return this.recurringDetail.allowWithdrawal();
+    }
+
+    public boolean adjustAdvanceTowardsFuturePayments() {
+        return this.recurringDetail.adjustAdvanceTowardsFuturePayments();
+    }
+
+    @Override
+    public boolean isTransactionsAllowed() {
+        return isActive() || isAccountMatured();
+    }
+
+    public DepositAccountRecurringDetail getRecurringDetail() {
+        return this.recurringDetail;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccountRepository.java
new file mode 100644
index 0000000..2ed6b17
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccountRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.savings.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface RecurringDepositAccountRepository extends JpaRepository<RecurringDepositAccount, Long>,
+        JpaSpecificationExecutor<RecurringDepositAccount> {
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProduct.java
new file mode 100644
index 0000000..0410a8b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProduct.java
@@ -0,0 +1,168 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+import javax.persistence.OneToOne;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+
+@Entity
+@DiscriminatorValue("300")
+public class RecurringDepositProduct extends FixedDepositProduct {
+
+    @OneToOne(mappedBy = "product", cascade = CascadeType.ALL)
+    private DepositProductRecurringDetail recurringDetail;
+
+    protected RecurringDepositProduct() {
+        super();
+    }
+
+    public static RecurringDepositProduct createNew(final String name, final String shortName, final String description,
+            final MonetaryCurrency currency, final BigDecimal interestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final Integer lockinPeriodFrequency,
+            final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final AccountingRuleType accountingRuleType,
+            final Set<Charge> charges, final DepositProductTermAndPreClosure productTermAndPreClosure,
+            final DepositProductRecurringDetail recurringDetail, final Set<InterestRateChart> charts,
+            BigDecimal minBalanceForInterestCalculation) {
+
+        final BigDecimal minRequiredOpeningBalance = null;
+        final boolean withdrawalFeeApplicableForTransfer = false;
+        final boolean allowOverdraft = false;
+        final BigDecimal overdraftLimit = null;
+
+        return new RecurringDepositProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges,
+                productTermAndPreClosure, recurringDetail, charts, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation);
+    }
+
+    protected RecurringDepositProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency,
+            final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> charges,
+            final DepositProductTermAndPreClosure productTermAndPreClosure, final DepositProductRecurringDetail recurringDetail,
+            final Set<InterestRateChart> charts, final boolean allowOverdraft, final BigDecimal overdraftLimit,
+            final BigDecimal minBalanceForInterestCalculation) {
+
+        super(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
+                interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, productTermAndPreClosure,
+                charts, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation);
+
+        this.recurringDetail = recurringDetail;
+    }
+
+    @Override
+    public Map<String, Object> update(final JsonCommand command) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        actualChanges.putAll(this.update(command, baseDataValidator));
+
+        validateDomainRules(baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+        return actualChanges;
+    }
+
+    @Override
+    protected Map<String, Object> update(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+
+        actualChanges.putAll(super.update(command, baseDataValidator));
+
+        if (this.recurringDetail != null) {
+            actualChanges.putAll(this.recurringDetail.update(command));
+        }
+
+        return actualChanges;
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public DepositProductRecurringDetail depositRecurringDetail() {
+        return this.recurringDetail;
+    }
+
+    @Override
+    public void validateDomainRules() {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
+
+        validateDomainRules(baseDataValidator);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateDomainRules(final DataValidatorBuilder baseDataValidator) {
+        final DepositTermDetail termDetails = this.depositProductTermAndPreClosure().depositTermDetail();
+        final boolean isMinTermGreaterThanMax = termDetails.isMinDepositTermGreaterThanMaxDepositTerm();
+        if (isMinTermGreaterThanMax) {
+            final Integer maxTerm = termDetails.maxDepositTerm();
+            baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm)
+                    .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term");
+        }
+
+        if (this.charts != null) {
+            validateCharts(baseDataValidator);
+        } else if (this.nominalAnnualInterestRate == null || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) {
+            baseDataValidator.reset().parameter(DepositsApiConstants.nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate)
+                    .failWithCodeNoParameterAddedToErrorCode("interest.chart.or.nominal.interest.rate.required");
+        }
+
+        this.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProductRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProductRepository.java
new file mode 100644
index 0000000..c46f6fe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProductRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.savings.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface RecurringDepositProductRepository extends JpaRepository<RecurringDepositProduct, Long>,
+        JpaSpecificationExecutor<RecurringDepositProduct> {
+    //
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositScheduleInstallment.java
new file mode 100644
index 0000000..b7cf0d6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositScheduleInstallment.java
@@ -0,0 +1,244 @@
+/**
+ * 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.savings.domain;
+
+import java.math.BigDecimal;
+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.AbstractAuditableCustom;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+
+@Entity
+@Table(name = "m_mandatory_savings_schedule")
+public class RecurringDepositScheduleInstallment extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "savings_account_id")
+    private RecurringDepositAccount account;
+
+    @Column(name = "installment", nullable = false)
+    private final Integer installmentNumber;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "fromdate", nullable = true)
+    private final Date fromDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "duedate", nullable = false)
+    private final Date dueDate;
+
+    @Column(name = "deposit_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal depositAmount;
+
+    @Column(name = "deposit_amount_completed_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal depositAmountCompleted;
+
+    @Column(name = "total_paid_in_advance_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal totalPaidInAdvance;
+
+    @Column(name = "total_paid_late_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal totalPaidLate;
+
+    @Column(name = "completed_derived", nullable = false)
+    private boolean obligationsMet;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "obligations_met_on_date")
+    private Date obligationsMetOnDate;
+
+    /**
+     * 
+     */
+    protected RecurringDepositScheduleInstallment() {
+        this.installmentNumber = null;
+        this.fromDate = null;
+        this.dueDate = null;
+        this.obligationsMet = false;
+    }
+
+    /**
+     * @param account
+     * @param installmentNumber
+     * @param fromDate
+     * @param dueDate
+     * @param depositAmount
+     * @param depositAmountCompleted
+     * @param totalPaidInAdvance
+     * @param totalPaidLate
+     * @param obligationsMet
+     * @param obligationsMetOnDate
+     */
+    private RecurringDepositScheduleInstallment(final RecurringDepositAccount account, final Integer installmentNumber,
+            final Date fromDate, final Date dueDate, final BigDecimal depositAmount, final BigDecimal depositAmountCompleted,
+            final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate, final boolean obligationsMet,
+            final Date obligationsMetOnDate) {
+        this.account = account;
+        this.installmentNumber = installmentNumber;
+        this.fromDate = fromDate;
+        this.dueDate = dueDate;
+        this.depositAmount = defaultToNullIfZero(depositAmount);
+        this.depositAmountCompleted = depositAmountCompleted;
+        this.totalPaidInAdvance = totalPaidInAdvance;
+        this.totalPaidLate = totalPaidLate;
+        this.obligationsMet = obligationsMet;
+        this.obligationsMetOnDate = obligationsMetOnDate;
+    }
+
+    public static RecurringDepositScheduleInstallment from(final RecurringDepositAccount account, final Integer installmentNumber,
+            final Date fromDate, final Date dueDate, final BigDecimal depositAmount, final BigDecimal depositAmountCompleted,
+            final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate, final boolean obligationsMet,
+            final Date obligationsMetOnDate) {
+        return new RecurringDepositScheduleInstallment(account, installmentNumber, fromDate, dueDate, depositAmount,
+                depositAmountCompleted, totalPaidInAdvance, totalPaidLate, obligationsMet, obligationsMetOnDate);
+    }
+
+    public static RecurringDepositScheduleInstallment installment(final RecurringDepositAccount account, final Integer installmentNumber,
+            final Date dueDate, final BigDecimal depositAmount) {
+
+        final Date fromDate = null;
+        final BigDecimal depositAmountCompleted = null;
+        final BigDecimal totalPaidInAdvance = null;
+        final BigDecimal totalPaidLate = null;
+        final boolean obligationsMet = false;
+        final Date obligationsMetOnDate = null;
+
+        return new RecurringDepositScheduleInstallment(account, installmentNumber, fromDate, dueDate, depositAmount,
+                depositAmountCompleted, totalPaidInAdvance, totalPaidLate, obligationsMet, obligationsMetOnDate);
+    }
+
+    private BigDecimal defaultToNullIfZero(final BigDecimal value) {
+        BigDecimal result = value;
+        if (BigDecimal.ZERO.compareTo(value) == 0) {
+            result = null;
+        }
+        return result;
+    }
+
+    public boolean isObligationsMet() {
+        return this.obligationsMet;
+    }
+
+    public boolean isNotFullyPaidOff() {
+        return !this.obligationsMet;
+    }
+
+    public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
+        return !isPrincipalCompleted(currency);
+    }
+
+    public boolean isPrincipalCompleted(final MonetaryCurrency currency) {
+        return getDepositAmountOutstanding(currency).isZero();
+    }
+
+    public Money getDepositAmountOutstanding(final MonetaryCurrency currency) {
+        final Money depositAmountAccountedFor = getDepositAmountCompleted(currency);
+        return getDepositAmount(currency).minus(depositAmountAccountedFor);
+    }
+
+    public Money getDepositAmountCompleted(final MonetaryCurrency currency) {
+        return Money.of(currency, this.depositAmountCompleted);
+    }
+
+    public Money getDepositAmount(final MonetaryCurrency currency) {
+        return Money.of(currency, this.depositAmount);
+    }
+
+    public LocalDate dueDate() {
+        return (this.dueDate == null) ? null : new LocalDate(this.dueDate);
+    }
+
+    public Money payInstallment(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+        final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+        Money depositAmountPortionOfTransaction = Money.zero(currency);
+
+        final Money depositAmount = getDepositAmountOutstanding(currency);
+        if (transactionAmountRemaining.isGreaterThanOrEqualTo(depositAmount)) {
+            this.depositAmountCompleted = getDepositAmountCompleted(currency).plus(depositAmount).getAmount();
+            depositAmountPortionOfTransaction = depositAmountPortionOfTransaction.plus(depositAmount);
+        } else {
+            this.depositAmountCompleted = getDepositAmountCompleted(currency).plus(transactionAmountRemaining).getAmount();
+            depositAmountPortionOfTransaction = depositAmountPortionOfTransaction.plus(transactionAmountRemaining);
+        }
+
+        this.depositAmountCompleted = defaultToNullIfZero(this.depositAmountCompleted);
+
+        checkIfInstallmentObligationsAreMet(transactionDate, currency);
+
+        trackAdvanceAndLateTotalsForInstallment(transactionDate, currency, depositAmountPortionOfTransaction);
+
+        return depositAmountPortionOfTransaction;
+    }
+
+    private void checkIfInstallmentObligationsAreMet(final LocalDate transactionDate, final MonetaryCurrency currency) {
+        this.obligationsMet = getTotalOutstanding(currency).isZero();
+        if (this.obligationsMet) {
+            this.obligationsMetOnDate = transactionDate.toDate();
+        }
+    }
+
+    public Money getTotalOutstanding(final MonetaryCurrency currency) {
+        return getDepositAmountOutstanding(currency);
+    }
+
+    private void trackAdvanceAndLateTotalsForInstallment(final LocalDate transactionDate, final MonetaryCurrency currency,
+            final Money amountPaidInInstallment) {
+        if (isInAdvance(transactionDate)) {
+            this.totalPaidInAdvance = asMoney(this.totalPaidInAdvance, currency).plus(amountPaidInInstallment).getAmount();
+        } else if (isLatePayment(transactionDate)) {
+            this.totalPaidLate = asMoney(this.totalPaidLate, currency).plus(amountPaidInInstallment).getAmount();
+        }
+    }
+
+    private boolean isInAdvance(final LocalDate transactionDate) {
+        return transactionDate.isBefore(dueDate());
+    }
+
+    private boolean isLatePayment(final LocalDate transactionDate) {
+        return transactionDate.isAfter(dueDate());
+    }
+
+    private Money asMoney(final BigDecimal decimal, final MonetaryCurrency currency) {
+        return Money.of(currency, decimal);
+    }
+
+    public void resetDerivedFields() {
+        this.depositAmountCompleted = null;
+        this.totalPaidInAdvance = null;
+        this.totalPaidLate = null;
+        this.obligationsMet = false;
+        this.obligationsMetOnDate = null;
+    }
+
+    public void updateDepositAmountAndResetDerivedFields(BigDecimal newDepositAmount) {
+        this.depositAmount = newDepositAmount;
+        this.resetDerivedFields();
+    }
+}
\ No newline at end of file
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
new file mode 100755
index 0000000..d7493d5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
@@ -0,0 +1,2608 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.DiscriminatorColumn;
+import javax.persistence.DiscriminatorType;
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.OrderBy;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
+import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
+import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
+import org.apache.fineract.portfolio.savings.exception.SavingsAccountTransactionNotFoundException;
+import org.apache.fineract.portfolio.savings.exception.SavingsActivityPriorToClientTransferException;
+import org.apache.fineract.portfolio.savings.exception.SavingsOfficerAssignmentDateException;
+import org.apache.fineract.portfolio.savings.exception.SavingsOfficerUnassignmentDateException;
+import org.apache.fineract.portfolio.savings.exception.SavingsTransferTransactionsCannotBeUndoneException;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+import org.springframework.util.CollectionUtils;
+
+import com.google.gson.JsonArray;
+
+@Entity
+@Table(name = "m_savings_account", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "sa_account_no_UNIQUE"),
+        @UniqueConstraint(columnNames = { "external_id" }, name = "sa_external_id_UNIQUE") })
+@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
+@DiscriminatorColumn(name = "deposit_type_enum", discriminatorType = DiscriminatorType.INTEGER)
+@DiscriminatorValue("100")
+public class SavingsAccount extends AbstractPersistable<Long> {
+
+    @Version
+    int version;
+
+    @Column(name = "account_no", length = 20, unique = true, nullable = false)
+    protected String accountNumber;
+
+    @Column(name = "external_id", nullable = true)
+    protected String externalId;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "client_id", nullable = true)
+    protected Client client;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "group_id", nullable = true)
+    protected Group group;
+
+    @ManyToOne
+    @JoinColumn(name = "product_id", nullable = false)
+    protected SavingsProduct product;
+
+    @ManyToOne
+    @JoinColumn(name = "field_officer_id", nullable = true)
+    protected Staff savingsOfficer;
+
+    @Column(name = "status_enum", nullable = false)
+    protected Integer status;
+
+    @Column(name = "account_type_enum", nullable = false)
+    protected Integer accountType;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "submittedon_date", nullable = true)
+    protected Date submittedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "submittedon_userid", nullable = true)
+    protected AppUser submittedBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "rejectedon_date")
+    protected Date rejectedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "rejectedon_userid", nullable = true)
+    protected AppUser rejectedBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "withdrawnon_date")
+    protected Date withdrawnOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "withdrawnon_userid", nullable = true)
+    protected AppUser withdrawnBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "approvedon_date")
+    protected Date approvedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "approvedon_userid", nullable = true)
+    protected AppUser approvedBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "activatedon_date", nullable = true)
+    protected Date activatedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "activatedon_userid", nullable = true)
+    protected AppUser activatedBy;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "closedon_date")
+    protected Date closedOnDate;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "closedon_userid", nullable = true)
+    protected AppUser closedBy;
+
+    @Embedded
+    protected MonetaryCurrency currency;
+
+    @Column(name = "nominal_annual_interest_rate", scale = 6, precision = 19, nullable = false)
+    protected BigDecimal nominalAnnualInterestRate;
+
+    /**
+     * The interest period is the span of time at the end of which savings in a
+     * client's account earn interest.
+     * 
+     * A value from the {@link SavingsCompoundingInterestPeriodType}
+     * enumeration.
+     */
+    @Column(name = "interest_compounding_period_enum", nullable = false)
+    protected Integer interestCompoundingPeriodType;
+
+    /**
+     * A value from the {@link SavingsPostingInterestPeriodType} enumeration.
+     */
+    @Column(name = "interest_posting_period_enum", nullable = false)
+    protected Integer interestPostingPeriodType;
+
+    /**
+     * A value from the {@link SavingsInterestCalculationType} enumeration.
+     */
+    @Column(name = "interest_calculation_type_enum", nullable = false)
+    protected Integer interestCalculationType;
+
+    /**
+     * A value from the {@link SavingsInterestCalculationDaysInYearType}
+     * enumeration.
+     */
+    @Column(name = "interest_calculation_days_in_year_type_enum", nullable = false)
+    protected Integer interestCalculationDaysInYearType;
+
+    @Column(name = "min_required_opening_balance", scale = 6, precision = 19, nullable = true)
+    protected BigDecimal minRequiredOpeningBalance;
+
+    @Column(name = "lockin_period_frequency", nullable = true)
+    protected Integer lockinPeriodFrequency;
+
+    @Column(name = "lockin_period_frequency_enum", nullable = true)
+    protected Integer lockinPeriodFrequencyType;
+
+    /**
+     * When account becomes <code>active</code> this field is derived if
+     * <code>lockinPeriodFrequency</code> and
+     * <code>lockinPeriodFrequencyType</code> details are present.
+     */
+    @Temporal(TemporalType.DATE)
+    @Column(name = "lockedin_until_date_derived", nullable = true)
+    protected Date lockedInUntilDate;
+
+    @Column(name = "withdrawal_fee_for_transfer", nullable = true)
+    protected boolean withdrawalFeeApplicableForTransfer;
+
+    @Column(name = "allow_overdraft")
+    private boolean allowOverdraft;
+
+    @Column(name = "overdraft_limit", scale = 6, precision = 19, nullable = true)
+    private BigDecimal overdraftLimit;
+
+    @Column(name = "nominal_annual_interest_rate_overdraft", scale = 6, precision = 19, nullable = true)
+    protected BigDecimal nominalAnnualInterestRateOverdraft;
+
+    @Column(name = "min_overdraft_for_interest_calculation", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minOverdraftForInterestCalculation;
+
+    @Column(name = "enforce_min_required_balance")
+    private boolean enforceMinRequiredBalance;
+
+    @Column(name = "min_required_balance", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minRequiredBalance;
+
+    @Column(name = "on_hold_funds_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal onHoldFunds;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "start_interest_calculation_date")
+    protected Date startInterestCalculationDate;
+
+    @Embedded
+    protected SavingsAccountSummary summary;
+
+    @OrderBy(value = "dateOf, createdDate, id")
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true)
+    protected final List<SavingsAccountTransaction> transactions = new ArrayList<>();
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true)
+    protected Set<SavingsAccountCharge> charges = new HashSet<>();
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true)
+    private Set<SavingsOfficerAssignmentHistory> savingsOfficerHistory;
+
+    @Transient
+    protected boolean accountNumberRequiresAutoGeneration = false;
+    @Transient
+    protected SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
+    @Transient
+    protected SavingsHelper savingsHelper;
+
+    @Column(name = "deposit_type_enum", insertable = false, updatable = false)
+    private Integer depositType;
+
+    @Column(name = "min_balance_for_interest_calculation", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minBalanceForInterestCalculation;
+
+    protected SavingsAccount() {
+        //
+    }
+
+    public static SavingsAccount createNewApplicationForSubmittal(final Client client, final Group group, final SavingsProduct product,
+            final Staff fieldOfficer, final String accountNo, final String externalId, final AccountType accountType,
+            final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges,
+            final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance,
+            final BigDecimal minRequiredBalance, final BigDecimal nominalAnnualInterestRateOverdraft,
+            final BigDecimal minOverdraftForInterestCalculation) {
+
+        final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL;
+        return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate,
+                submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
+                interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
+                withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, enforceMinRequiredBalance,
+                minRequiredBalance, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    protected SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer,
+            final String accountNo, final String externalId, final SavingsAccountStatusType status, final AccountType accountType,
+            final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges,
+            final boolean allowOverdraft, final BigDecimal overdraftLimit) {
+        this(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy,
+                nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
+                interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
+                withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, false, null, null, null);
+    }
+
+    protected SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff savingsOfficer,
+            final String accountNo, final String externalId, final SavingsAccountStatusType status, final AccountType accountType,
+            final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges,
+            final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance,
+            final BigDecimal minRequiredBalance, final BigDecimal nominalAnnualInterestRateOverdraft,
+            final BigDecimal minOverdraftForInterestCalculation) {
+        this.client = client;
+        this.group = group;
+        this.product = product;
+        this.savingsOfficer = savingsOfficer;
+        if (StringUtils.isBlank(accountNo)) {
+            this.accountNumber = new RandomPasswordGenerator(19).generate();
+            this.accountNumberRequiresAutoGeneration = true;
+        } else {
+            this.accountNumber = accountNo;
+        }
+
+        this.currency = product.currency();
+        this.externalId = externalId;
+        this.status = status.getValue();
+        this.accountType = accountType.getValue();
+        this.submittedOnDate = submittedOnDate.toDate();
+        this.submittedBy = submittedBy;
+        this.nominalAnnualInterestRate = nominalAnnualInterestRate;
+        this.interestCompoundingPeriodType = interestCompoundingPeriodType.getValue();
+        this.interestPostingPeriodType = interestPostingPeriodType.getValue();
+        this.interestCalculationType = interestCalculationType.getValue();
+        this.interestCalculationDaysInYearType = interestCalculationDaysInYearType.getValue();
+        this.minRequiredOpeningBalance = minRequiredOpeningBalance;
+        this.lockinPeriodFrequency = lockinPeriodFrequency;
+        if (lockinPeriodFrequencyType != null) {
+            this.lockinPeriodFrequencyType = lockinPeriodFrequencyType.getValue();
+        }
+        this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer;
+
+        if (!CollectionUtils.isEmpty(savingsAccountCharges)) {
+            this.charges = associateChargesWithThisSavingsAccount(savingsAccountCharges);
+        }
+
+        this.summary = new SavingsAccountSummary();
+        this.allowOverdraft = allowOverdraft;
+        this.overdraftLimit = overdraftLimit;
+        this.nominalAnnualInterestRateOverdraft = nominalAnnualInterestRateOverdraft;
+        this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation;
+        esnureOverdraftLimitsSetForOverdraftAccounts();
+
+        this.enforceMinRequiredBalance = enforceMinRequiredBalance;
+        this.minRequiredBalance = minRequiredBalance;
+        this.minBalanceForInterestCalculation = product.minBalanceForInterestCalculation();
+        this.savingsOfficerHistory = null;
+    }
+
+    /**
+     * Used after fetching/hydrating a {@link SavingsAccount} object to inject
+     * helper services/components used for update summary details after
+     * events/transactions on a {@link SavingsAccount}.
+     */
+    public void setHelpers(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
+            final SavingsHelper savingsHelper) {
+        this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
+        this.savingsHelper = savingsHelper;
+    }
+
+    public boolean isNotActive() {
+        return !isActive();
+    }
+
+    public boolean isActive() {
+        return SavingsAccountStatusType.fromInt(this.status).isActive();
+    }
+
+    public boolean isNotSubmittedAndPendingApproval() {
+        return !isSubmittedAndPendingApproval();
+    }
+
+    public boolean isSubmittedAndPendingApproval() {
+        return SavingsAccountStatusType.fromInt(this.status).isSubmittedAndPendingApproval();
+    }
+
+    public boolean isApproved() {
+        return SavingsAccountStatusType.fromInt(this.status).isApproved();
+    }
+
+    public boolean isActivated() {
+        boolean isActive = false;
+        if (this.activatedOnDate != null) {
+            isActive = true;
+        }
+        return isActive;
+    }
+
+    public boolean isClosed() {
+        return SavingsAccountStatusType.fromInt(this.status).isClosed();
+    }
+
+    public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final boolean isInterestTransfer,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+
+        final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
+                isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+        Money interestPostedToDate = Money.zero(this.currency);
+
+        boolean recalucateDailyBalanceDetails = false;
+
+        for (final PostingPeriod interestPostingPeriod : postingPeriods) {
+
+            final LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction();
+            final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned();
+
+            if (!interestPostingTransactionDate.isAfter(interestPostingUpToDate)) {
+
+                interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod);
+
+                final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate);
+                if (postingTransaction == null) {
+                    SavingsAccountTransaction newPostingTransaction;
+                    if (interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(currency))) {
+                        newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), interestPostingTransactionDate,
+                                interestEarnedToBePostedForPeriod);
+                    } else {
+                        newPostingTransaction = SavingsAccountTransaction.overdraftInterest(this, office(), interestPostingTransactionDate,
+                                interestEarnedToBePostedForPeriod.negated());
+                    }
+                    this.transactions.add(newPostingTransaction);
+                    recalucateDailyBalanceDetails = true;
+                } else {
+                    boolean correctionRequired = false;
+                    if (postingTransaction.isInterestPostingAndNotReversed()) {
+                        correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod);
+                    } else {
+                        correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod.negated());
+                    }
+                    if (correctionRequired) {
+                        postingTransaction.reverse();
+                        SavingsAccountTransaction newPostingTransaction;
+                        if (interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(currency))) {
+                            newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(),
+                                    interestPostingTransactionDate, interestEarnedToBePostedForPeriod);
+                        } else {
+                            newPostingTransaction = SavingsAccountTransaction.overdraftInterest(this, office(),
+                                    interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated());
+                        }
+                        this.transactions.add(newPostingTransaction);
+                        recalucateDailyBalanceDetails = true;
+                    }
+                }
+            }
+        }
+
+        if (recalucateDailyBalanceDetails) {
+            // no openingBalance concept supported yet but probably will to
+            // allow
+            // for migrations.
+            final Money openingAccountBalance = Money.zero(this.currency);
+
+            // update existing transactions so derived balance fields are
+            // correct.
+            recalculateDailyBalances(openingAccountBalance, interestPostingUpToDate);
+        }
+
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+    }
+
+    protected SavingsAccountTransaction findInterestPostingTransactionFor(final LocalDate postingDate) {
+
+        SavingsAccountTransaction postingTransation = null;
+
+        for (final SavingsAccountTransaction transaction : this.transactions) {
+            if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
+                    && transaction.occursOn(postingDate)) {
+                postingTransation = transaction;
+                break;
+            }
+        }
+
+        return postingTransation;
+    }
+
+    // Determine the last transaction for given day
+    protected SavingsAccountTransaction findLastTransaction(final LocalDate date) {
+
+        SavingsAccountTransaction savingsTransaction = null;
+
+        for (final SavingsAccountTransaction transaction : this.transactions) {
+            if (transaction.isNotReversed() && transaction.occursOn(date)) {
+                savingsTransaction = transaction;
+                break;
+            }
+        }
+
+        return savingsTransaction;
+    }
+
+    /**
+     * All interest calculation based on END-OF-DAY-BALANCE.
+     * 
+     * Interest calculation is performed on-the-fly over all account
+     * transactions.
+     * 
+     * 
+     * 1. Calculate Interest From Beginning Of Account 1a. determine the
+     * 'crediting' periods that exist for this savings acccount 1b. determine
+     * the 'compounding' periods that exist within each 'crediting' period
+     * calculate the amount of interest due at the end of each 'crediting'
+     * period check if an existing 'interest posting' transaction exists for
+     * date and matches the amount posted
+     * 
+     * @param isInterestTransfer
+     *            TODO
+     */
+    public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate,
+            boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
+
+        // no openingBalance concept supported yet but probably will to allow
+        // for migrations.
+        final Money openingAccountBalance = Money.zero(this.currency);
+
+        // update existing transactions so derived balance fields are
+        // correct.
+        recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate);
+
+        // 1. default to calculate interest based on entire history OR
+        // 2. determine latest 'posting period' and find interest credited to
+        // that period
+
+        // A generate list of EndOfDayBalances (not including interest postings)
+        final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType);
+
+        final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType
+                .fromInt(this.interestCompoundingPeriodType);
+
+        final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType
+                .fromInt(this.interestCalculationDaysInYearType);
+
+        final List<LocalDateInterval> postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(
+                getStartInterestCalculationDate(), upToInterestCalculationDate, postingPeriodType, financialYearBeginningMonth);
+
+        final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
+
+        Money periodStartingBalance;
+        if (this.startInterestCalculationDate != null) {
+            LocalDate startInterestCalculationDate = new LocalDate(this.startInterestCalculationDate);
+            final SavingsAccountTransaction transaction = findLastTransaction(startInterestCalculationDate);
+
+            if (transaction == null) {
+                final String defaultUserMessage = "No transactions were found on the specified date "
+                        + getStartInterestCalculationDate().toString() + " for account number " + this.accountNumber.toString()
+                        + " and resource id " + getId();
+
+                final ApiParameterError error = ApiParameterError.parameterError(
+                        "error.msg.savingsaccount.transaction.incorrect.start.interest.calculation.date", defaultUserMessage,
+                        "transactionDate", getStartInterestCalculationDate().toString());
+
+                final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                dataValidationErrors.add(error);
+
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            }
+
+            periodStartingBalance = transaction.getRunningBalance(this.currency);
+        } else
+            periodStartingBalance = Money.zero(this.currency);
+
+        final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType);
+        final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, upToInterestCalculationDate);
+        final BigDecimal overdraftInterestRateAsFraction = getEffectiveOverdraftInterestRateAsFraction(mc);
+        final Collection<Long> interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId());
+        final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation());
+        final Money minOverdraftForInterestCalculation = Money.of(getCurrency(), this.minOverdraftForInterestCalculation);
+
+        for (final LocalDateInterval periodInterval : postingPeriodIntervals) {
+
+            final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance,
+                    retreiveOrderedNonInterestPostingTransactions(), this.currency, compoundingPeriodType, interestCalculationType,
+                    interestRateAsFraction, daysInYearType.getValue(), upToInterestCalculationDate, interestPostTransactions,
+                    isInterestTransfer, minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+
+            periodStartingBalance = postingPeriod.closingBalance();
+
+            allPostingPeriods.add(postingPeriod);
+        }
+
+        this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, getLockedInUntilLocalDate(),
+                isTransferInterestToOtherAccount());
+
+        this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods);
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+
+        return allPostingPeriods;
+    }
+
+    private BigDecimal getEffectiveOverdraftInterestRateAsFraction(MathContext mc) {
+        return this.nominalAnnualInterestRateOverdraft.divide(BigDecimal.valueOf(100l), mc);
+    }
+
+    @SuppressWarnings("unused")
+    protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate upToInterestCalculationDate) {
+        return this.nominalAnnualInterestRate.divide(BigDecimal.valueOf(100l), mc);
+    }
+
+    protected List<SavingsAccountTransaction> retreiveOrderedNonInterestPostingTransactions() {
+        final List<SavingsAccountTransaction> listOfTransactionsSorted = retreiveListOfTransactions();
+
+        final List<SavingsAccountTransaction> orderedNonInterestPostingTransactions = new ArrayList<>();
+
+        for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) {
+            if (!(transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
+                    && transaction.isNotReversed()) {
+                orderedNonInterestPostingTransactions.add(transaction);
+            }
+        }
+
+        return orderedNonInterestPostingTransactions;
+    }
+
+    protected List<SavingsAccountTransaction> retreiveListOfTransactions() {
+        final List<SavingsAccountTransaction> listOfTransactionsSorted = new ArrayList<>();
+        listOfTransactionsSorted.addAll(this.transactions);
+
+        final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator();
+        Collections.sort(listOfTransactionsSorted, transactionComparator);
+        return listOfTransactionsSorted;
+    }
+
+    protected void recalculateDailyBalances(final Money openingAccountBalance, final LocalDate interestPostingUpToDate) {
+
+        Money runningBalance = openingAccountBalance.copy();
+
+        List<SavingsAccountTransaction> accountTransactionsSorted = retreiveListOfTransactions();
+        boolean isTransactionsModified = false;
+        for (final SavingsAccountTransaction transaction : accountTransactionsSorted) {
+            if (transaction.isReversed()) {
+                transaction.zeroBalanceFields();
+            } else {
+                Money overdraftAmount = Money.zero(this.currency);
+                Money transactionAmount = Money.zero(this.currency);
+                if (transaction.isCredit()) {
+                    if (runningBalance.isLessThanZero()) {
+                        Money diffAmount = transaction.getAmount(this.currency).plus(runningBalance);
+                        if (diffAmount.isGreaterThanZero()) {
+                            overdraftAmount = transaction.getAmount(this.currency).minus(diffAmount);
+                        } else {
+                            overdraftAmount = transaction.getAmount(this.currency);
+                        }
+                    }
+                    transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency));
+                } else if (transaction.isDebit()) {
+                    if (runningBalance.isLessThanZero()) {
+                        overdraftAmount = transaction.getAmount(this.currency);
+                    }
+                    transactionAmount = transactionAmount.minus(transaction.getAmount(this.currency));
+                }
+
+                runningBalance = runningBalance.plus(transactionAmount);
+                transaction.updateRunningBalance(runningBalance);
+                if (overdraftAmount.isZero() && runningBalance.isLessThanZero()) {
+                    overdraftAmount = overdraftAmount.plus(runningBalance.getAmount().negate());
+                }
+                if (transaction.getId() == null && overdraftAmount.isGreaterThanZero()) {
+                    transaction.updateOverdraftAmount(overdraftAmount.getAmount());
+                } else if (overdraftAmount.isNotEqualTo(transaction.getOverdraftAmount(getCurrency()))) {
+                    SavingsAccountTransaction accountTransaction = SavingsAccountTransaction.copyTransaction(transaction);
+                    transaction.reverse();
+                    if (overdraftAmount.isGreaterThanZero()) {
+                        accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount());
+                    }
+                    accountTransaction.updateRunningBalance(runningBalance);
+                    this.transactions.add(accountTransaction);
+                    isTransactionsModified = true;
+                }
+
+            }
+        }
+
+        if (isTransactionsModified) {
+            accountTransactionsSorted = retreiveListOfTransactions();
+        }
+        resetAccountTransactionsEndOfDayBalances(accountTransactionsSorted, interestPostingUpToDate);
+    }
+
+    protected void resetAccountTransactionsEndOfDayBalances(final List<SavingsAccountTransaction> accountTransactionsSorted,
+            final LocalDate interestPostingUpToDate) {
+        // loop over transactions in reverse
+        LocalDate endOfBalanceDate = interestPostingUpToDate;
+        for (int i = accountTransactionsSorted.size() - 1; i >= 0; i--) {
+            final SavingsAccountTransaction transaction = accountTransactionsSorted.get(i);
+            if (transaction.isNotReversed()
+                    && !(transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())) {
+                transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate);
+                // this transactions transaction date is end of balance date for
+                // previous transaction.
+                endOfBalanceDate = transaction.transactionLocalDate().minusDays(1);
+            }
+        }
+    }
+
+    public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO) {
+        final String resourceTypeName = depositAccountType().resourceName();
+        if (isNotActive()) {
+            final String defaultUserMessage = "Transaction is not allowed. Account is not active.";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg." + resourceTypeName
+                    + ".transaction.account.is.not.active", defaultUserMessage, "transactionDate", transactionDTO.getTransactionDate()
+                    .toString(transactionDTO.getFormatter()));
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (isDateInTheFuture(transactionDTO.getTransactionDate())) {
+            final String defaultUserMessage = "Transaction date cannot be in the future.";
+            final ApiParameterError error = ApiParameterError.parameterError(
+                    "error.msg." + resourceTypeName + ".transaction.in.the.future", defaultUserMessage, "transactionDate", transactionDTO
+                            .getTransactionDate().toString(transactionDTO.getFormatter()));
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (transactionDTO.getTransactionDate().isBefore(getActivationLocalDate())) {
+            final Object[] defaultUserArgs = Arrays.asList(transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter()),
+                    getActivationLocalDate().toString(transactionDTO.getFormatter())).toArray();
+            final String defaultUserMessage = "Transaction date cannot be before accounts activation date.";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg." + resourceTypeName
+                    + ".transaction.before.activation.date", defaultUserMessage, "transactionDate", defaultUserArgs);
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_DEPOSIT, transactionDTO.getTransactionDate());
+
+        final Money amount = Money.of(this.currency, transactionDTO.getTransactionAmount());
+
+        final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, office(), transactionDTO.getPaymentDetail(),
+                transactionDTO.getTransactionDate(), amount, transactionDTO.getCreatedDate(), transactionDTO.getAppUser());
+        this.transactions.add(transaction);
+
+        this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
+
+        return transaction;
+    }
+
+    public LocalDate getActivationLocalDate() {
+        LocalDate activationLocalDate = null;
+        if (this.activatedOnDate != null) {
+            activationLocalDate = new LocalDate(this.activatedOnDate);
+        }
+        return activationLocalDate;
+    }
+
+    // startInterestCalculationDate is set during migration so that there is no
+    // interference with interest posting of previous system
+    public LocalDate getStartInterestCalculationDate() {
+        LocalDate startInterestCalculationLocalDate = null;
+        if (this.startInterestCalculationDate != null) {
+            startInterestCalculationLocalDate = new LocalDate(this.startInterestCalculationDate);
+        } else
+            startInterestCalculationLocalDate = getActivationLocalDate();
+        return startInterestCalculationLocalDate;
+    }
+
+    public SavingsAccountTransaction withdraw(final SavingsAccountTransactionDTO transactionDTO, final boolean applyWithdrawFee) {
+
+        if (!isTransactionsAllowed()) {
+
+            final String defaultUserMessage = "Transaction is not allowed. Account is not active.";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.account.is.not.active",
+                    defaultUserMessage, "transactionDate", transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter()));
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (isDateInTheFuture(transactionDTO.getTransactionDate())) {
+            final String defaultUserMessage = "Transaction date cannot be in the future.";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.in.the.future",
+                    defaultUserMessage, "transactionDate", transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter()));
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (transactionDTO.getTransactionDate().isBefore(getActivationLocalDate())) {
+            final Object[] defaultUserArgs = Arrays.asList(transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter()),
+                    getActivationLocalDate().toString(transactionDTO.getFormatter())).toArray();
+            final String defaultUserMessage = "Transaction date cannot be before accounts activation date.";
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.before.activation.date",
+                    defaultUserMessage, "transactionDate", defaultUserArgs);
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (isAccountLocked(transactionDTO.getTransactionDate())) {
+            final String defaultUserMessage = "Withdrawal is not allowed. No withdrawals are allowed until after "
+                    + getLockedInUntilLocalDate().toString(transactionDTO.getFormatter());
+            final ApiParameterError error = ApiParameterError.parameterError(
+                    "error.msg.savingsaccount.transaction.withdrawals.blocked.during.lockin.period", defaultUserMessage, "transactionDate",
+                    transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter()),
+                    getLockedInUntilLocalDate().toString(transactionDTO.getFormatter()));
+
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_WITHDRAWAL, transactionDTO.getTransactionDate());
+
+        final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount());
+        final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, office(),
+                transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney,
+                transactionDTO.getCreatedDate(), transactionDTO.getAppUser());
+        this.transactions.add(transaction);
+
+        if (applyWithdrawFee) {
+            // auto pay withdrawal fee
+            payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser());
+        }
+        return transaction;
+    }
+
+    private void payWithdrawalFee(final BigDecimal transactionAmoount, final LocalDate transactionDate, final AppUser user) {
+        for (SavingsAccountCharge charge : this.charges()) {
+            if (charge.isWithdrawalFee() && charge.isActive()) {
+                charge.updateWithdralFeeAmount(transactionAmoount);
+                this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user);
+            }
+        }
+    }
+
+    public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate) {
+
+        boolean transactionBeforeLastInterestPosting = false;
+
+        for (final SavingsAccountTransaction transaction : retreiveListOfTransactions()) {
+            if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
+                    && transaction.isAfter(transactionDate)) {
+                transactionBeforeLastInterestPosting = true;
+                break;
+            }
+        }
+
+        return transactionBeforeLastInterestPosting;
+    }
+
+    public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isException,
+            final List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions) {
+        final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
+        Money runningBalance = Money.zero(this.currency);
+        Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
+        LocalDate lastSavingsDate = 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 {
+                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
+             * was made:
+             */
+            if (depositAccountOnHoldTransactions != null) {
+                for (final DepositAccountOnHoldTransaction onHoldTransaction : depositAccountOnHoldTransactions) {
+                    // Compare the balance of the on hold:
+                    if ((onHoldTransaction.getTransactionDate().isBefore(transaction.transactionLocalDate()) || onHoldTransaction
+                            .getTransactionDate().isEqual(transaction.transactionLocalDate()))
+                            && (lastSavingsDate == null || onHoldTransaction.getTransactionDate().isAfter(lastSavingsDate))) {
+                        if (onHoldTransaction.getTransactionType().isHold()) {
+                            minRequiredBalance = minRequiredBalance.plus(onHoldTransaction.getAmountMoney(this.currency));
+                        } else {
+                            minRequiredBalance = minRequiredBalance.minus(onHoldTransaction.getAmountMoney(this.currency));
+                        }
+                    }
+                }
+            }
+
+            // deal with potential minRequiredBalance and
+            // enforceMinRequiredBalance
+            if (!isException && transaction.canProcessBalanceCheck()) {
+                if (runningBalance.minus(minRequiredBalance).isLessThanZero()) { throw new InsufficientAccountBalanceException(
+                        "transactionAmount", getAccountBalance(), withdrawalFee, transactionAmount); }
+            }
+            lastSavingsDate = transaction.transactionLocalDate();
+
+        }
+    }
+
+    public void validateAccountBalanceDoesNotBecomeNegative(final String transactionAction,
+            final List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions) {
+
+        final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
+        Money runningBalance = Money.zero(this.currency);
+        Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
+        LocalDate lastSavingsDate = 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));
+            }
+
+            /*
+             * 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
+             * was made:
+             */
+            if (depositAccountOnHoldTransactions != null) {
+                for (final DepositAccountOnHoldTransaction onHoldTransaction : depositAccountOnHoldTransactions) {
+                    // Compare the balance of the on hold:
+                    if ((onHoldTransaction.getTransactionDate().isBefore(transaction.transactionLocalDate()) || onHoldTransaction
+                            .getTransactionDate().isEqual(transaction.transactionLocalDate()))
+                            && (lastSavingsDate == null || onHoldTransaction.getTransactionDate().isAfter(lastSavingsDate))) {
+                        if (onHoldTransaction.getTransactionType().isHold()) {
+                            minRequiredBalance = minRequiredBalance.plus(onHoldTransaction.getAmountMoney(this.currency));
+                        } else {
+                            minRequiredBalance = minRequiredBalance.minus(onHoldTransaction.getAmountMoney(this.currency));
+                        }
+                    }
+                }
+            }
+
+            // enforceMinRequiredBalance
+            if (transaction.canProcessBalanceCheck()) {
+                if (runningBalance.minus(minRequiredBalance).isLessThanZero()) {
+                    final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+                    final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                            .resource(depositAccountType().resourceName() + transactionAction);
+                    if (this.allowOverdraft) {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.exceeding.overdraft.limit");
+                    } else {
+                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.going.negative");
+                    }
+                    if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+                }
+
+            }
+            lastSavingsDate = transaction.transactionLocalDate();
+
+        }
+    }
+
+    protected boolean isAccountLocked(final LocalDate transactionDate) {
+        boolean isLocked = false;
+        final boolean accountHasLockedInSetting = this.lockedInUntilDate != null;
+        if (accountHasLockedInSetting) {
+            isLocked = getLockedInUntilLocalDate().isAfter(transactionDate);
+        }
+        return isLocked;
+    }
+
+    protected LocalDate getLockedInUntilLocalDate() {
+        LocalDate lockedInUntilLocalDate = null;
+        if (this.lockedInUntilDate != null) {
+            lockedInUntilLocalDate = new LocalDate(this.lockedInUntilDate);
+        }
+        return lockedInUntilLocalDate;
+    }
+
+    private boolean isDateInTheFuture(final LocalDate transactionDate) {
+        return transactionDate.isAfter(DateUtils.getLocalDateOfTenant());
+    }
+
+    protected BigDecimal getAccountBalance() {
+        return this.summary.getAccountBalance(this.currency).getAmount();
+    }
+
+    public void modifyApplication(final JsonCommand command, final Map<String, Object> actualChanges) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction);
+        this.modifyApplication(command, actualChanges, baseDataValidator);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void modifyApplication(final JsonCommand command, final Map<String, Object> actualChanges,
+            final DataValidatorBuilder baseDataValidator) {
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
+            return;
+        }
+
+        final String localeAsInput = command.locale();
+        final String dateFormat = command.dateFormat();
+
+        if (command.isChangeInLocalDateParameterNamed(SavingsApiConstants.submittedOnDateParamName, getSubmittedOnLocalDate())) {
+            final LocalDate newValue = command.localDateValueOfParameterNamed(SavingsApiConstants.submittedOnDateParamName);
+            final String newValueAsString = command.stringValueOfParameterNamed(SavingsApiConstants.submittedOnDateParamName);
+            actualChanges.put(SavingsApiConstants.submittedOnDateParamName, newValueAsString);
+            actualChanges.put(SavingsApiConstants.localeParamName, localeAsInput);
+            actualChanges.put(SavingsApiConstants.dateFormatParamName, dateFormat);
+            this.submittedOnDate = newValue.toDate();
+        }
+
+        if (command.isChangeInStringParameterNamed(SavingsApiConstants.accountNoParamName, this.accountNumber)) {
+            final String newValue = command.stringValueOfParameterNamed(SavingsApiConstants.accountNoParamName);
+            actualChanges.put(SavingsApiConstants.accountNoParamName, newValue);
+            this.accountNumber = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInStringParameterNamed(SavingsApiConstants.externalIdParamName, this.externalId)) {
+            final String newValue = command.stringValueOfParameterNamed(SavingsApiConstants.externalIdParamName);
+            actualChanges.put(SavingsApiConstants.externalIdParamName, newValue);
+            this.externalId = StringUtils.defaultIfEmpty(newValue, null);
+        }
+
+        if (command.isChangeInLongParameterNamed(SavingsApiConstants.clientIdParamName, clientId())) {
+            final Long newValue = command.longValueOfParameterNamed(SavingsApiConstants.clientIdParamName);
+            actualChanges.put(SavingsApiConstants.clientIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(SavingsApiConstants.groupIdParamName, groupId())) {
+            final Long newValue = command.longValueOfParameterNamed(SavingsApiConstants.groupIdParamName);
+            actualChanges.put(SavingsApiConstants.groupIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(SavingsApiConstants.productIdParamName, this.product.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(SavingsApiConstants.productIdParamName);
+            actualChanges.put(SavingsApiConstants.productIdParamName, newValue);
+        }
+
+        if (command.isChangeInLongParameterNamed(SavingsApiConstants.fieldOfficerIdParamName, hasSavingsOfficerId())) {
+            final Long newValue = command.longValueOfParameterNamed(SavingsApiConstants.fieldOfficerIdParamName);
+            actualChanges.put(SavingsApiConstants.fieldOfficerIdParamName, newValue);
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(SavingsApiConstants.nominalAnnualInterestRateParamName,
+                this.nominalAnnualInterestRate)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(SavingsApiConstants.nominalAnnualInterestRateParamName);
+            actualChanges.put(SavingsApiConstants.nominalAnnualInterestRateParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.nominalAnnualInterestRate = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.interestCompoundingPeriodTypeParamName,
+                this.interestCompoundingPeriodType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.interestCompoundingPeriodTypeParamName);
+            this.interestCompoundingPeriodType = newValue != null ? SavingsCompoundingInterestPeriodType.fromInt(newValue).getValue()
+                    : newValue;
+            actualChanges.put(SavingsApiConstants.interestCompoundingPeriodTypeParamName, this.interestCompoundingPeriodType);
+        }
+
+        if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.interestPostingPeriodTypeParamName, this.interestPostingPeriodType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.interestPostingPeriodTypeParamName);
+            this.interestPostingPeriodType = newValue != null ? SavingsPostingInterestPeriodType.fromInt(newValue).getValue() : newValue;
+            actualChanges.put(SavingsApiConstants.interestPostingPeriodTypeParamName, this.interestPostingPeriodType);
+        }
+
+        if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.interestCalculationTypeParamName, this.interestCalculationType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.interestCalculationTypeParamName);
+            this.interestCalculationType = newValue != null ? SavingsInterestCalculationType.fromInt(newValue).getValue() : newValue;
+            actualChanges.put(SavingsApiConstants.interestCalculationTypeParamName, this.interestCalculationType);
+        }
+
+        if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.interestCalculationDaysInYearTypeParamName,
+                this.interestCalculationDaysInYearType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.interestCalculationDaysInYearTypeParamName);
+            this.interestCalculationDaysInYearType = newValue != null ? SavingsInterestCalculationDaysInYearType.fromInt(newValue)
+                    .getValue() : newValue;
+            actualChanges.put(SavingsApiConstants.interestCalculationDaysInYearTypeParamName, this.interestCalculationDaysInYearType);
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(SavingsApiConstants.minRequiredOpeningBalanceParamName,
+                this.minRequiredOpeningBalance)) {
+            final BigDecimal newValue = command
+                    .bigDecimalValueOfParameterNamedDefaultToNullIfZero(SavingsApiConstants.minRequiredOpeningBalanceParamName);
+            actualChanges.put(SavingsApiConstants.minRequiredOpeningBalanceParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.minRequiredOpeningBalance = Money.of(this.currency, newValue).getAmount();
+        }
+
+        if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(SavingsApiConstants.lockinPeriodFrequencyParamName,
+                this.lockinPeriodFrequency)) {
+            final Integer newValue = command
+                    .integerValueOfParameterNamedDefaultToNullIfZero(SavingsApiConstants.lockinPeriodFrequencyParamName);
+            actualChanges.put(SavingsApiConstants.lockinPeriodFrequencyParamName, newValue);
+            actualChanges.put("locale", localeAsInput);
+            this.lockinPeriodFrequency = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.lockinPeriodFrequencyTypeParamName, this.lockinPeriodFrequencyType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.lockinPeriodFrequencyTypeParamName);
+            actualChanges.put(SavingsApiConstants.lockinPeriodFrequencyTypeParamName, newValue);
+            this.lockinPeriodFrequencyType = newValue != null ? SavingsPeriodFrequencyType.fromInt(newValue).getValue() : newValue;
+        }
+
+        // set period type to null if frequency is null
+        if (this.lockinPeriodFrequency == null) {
+            this.lockinPeriodFrequencyType = null;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(withdrawalFeeForTransfersParamName, this.withdrawalFeeApplicableForTransfer)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
+            actualChanges.put(withdrawalFeeForTransfersParamName, newValue);
+            this.withdrawalFeeApplicableForTransfer = newValue;
+        }
+
+        // charges
+        final String chargesParamName = "charges";
+        if (command.hasParameter(chargesParamName)) {
+            final JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName);
+            if (jsonArray != null) {
+                actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName));
+            }
+        }
+
+        if (command.isChangeInBooleanParameterNamed(allowOverdraftParamName, this.allowOverdraft)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
+            actualChanges.put(allowOverdraftParamName, newValue);
+            this.allowOverdraft = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(overdraftLimitParamName, this.overdraftLimit)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(overdraftLimitParamName);
+            actualChanges.put(overdraftLimitParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.overdraftLimit = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(nominalAnnualInterestRateOverdraftParamName,
+                this.nominalAnnualInterestRateOverdraft)) {
+            final BigDecimal newValue = command
+                    .bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName);
+            actualChanges.put(nominalAnnualInterestRateOverdraftParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.nominalAnnualInterestRateOverdraft = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minOverdraftForInterestCalculationParamName,
+                this.minOverdraftForInterestCalculation)) {
+            final BigDecimal newValue = command
+                    .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName);
+            actualChanges.put(minOverdraftForInterestCalculationParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.minOverdraftForInterestCalculation = newValue;
+        }
+
+        if (!this.allowOverdraft) {
+            this.overdraftLimit = null;
+            this.nominalAnnualInterestRateOverdraft = null;
+            this.minOverdraftForInterestCalculation = null;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(enforceMinRequiredBalanceParamName, this.enforceMinRequiredBalance)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName);
+            actualChanges.put(enforceMinRequiredBalanceParamName, newValue);
+            this.enforceMinRequiredBalance = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minRequiredBalanceParamName, this.minRequiredBalance)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName);
+            actualChanges.put(minRequiredBalanceParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.minRequiredBalance = newValue;
+        }
+
+        validateLockinDetails(baseDataValidator);
+        esnureOverdraftLimitsSetForOverdraftAccounts();
+    }
+
+    /**
+     * If overdrafts are allowed and the overdraft limit is not set, set the
+     * same to Zero
+     **/
+    private void esnureOverdraftLimitsSetForOverdraftAccounts() {
+
+        this.overdraftLimit = this.overdraftLimit == null ? BigDecimal.ZERO : this.overdraftLimit;
+        this.nominalAnnualInterestRateOverdraft = this.nominalAnnualInterestRateOverdraft == null ? BigDecimal.ZERO
+                : this.nominalAnnualInterestRateOverdraft;
+        this.minOverdraftForInterestCalculation = this.minOverdraftForInterestCalculation == null ? BigDecimal.ZERO
+                : this.minOverdraftForInterestCalculation;
+    }
+
+    private void validateLockinDetails(final DataValidatorBuilder baseDataValidator) {
+
+        /*
+         * final List<ApiParameterError> dataValidationErrors = new
+         * ArrayList<ApiParameterError>(); final DataValidatorBuilder
+         * baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+         * .resource(resourceName);
+         */
+
+        if (this.lockinPeriodFrequency == null) {
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(this.lockinPeriodFrequencyType).ignoreIfNull()
+                    .inMinMaxRange(0, 3);
+
+            if (this.lockinPeriodFrequencyType != null) {
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(this.lockinPeriodFrequency).notNull()
+                        .integerZeroOrGreater();
+            }
+        } else {
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(this.lockinPeriodFrequencyType)
+                    .integerZeroOrGreater();
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(this.lockinPeriodFrequencyType).notNull()
+                    .inMinMaxRange(0, 3);
+        }
+    }
+
+    public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final Set<Long> existingTransactionIds,
+            final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
+
+        final Map<String, Object> accountingBridgeData = new LinkedHashMap<>();
+        accountingBridgeData.put("savingsId", getId());
+        accountingBridgeData.put("savingsProductId", productId());
+        accountingBridgeData.put("currency", currencyData);
+        accountingBridgeData.put("officeId", officeId());
+        accountingBridgeData.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnSavingsProduct());
+        accountingBridgeData.put("accrualBasedAccountingEnabled", isAccrualBasedAccountingEnabledOnSavingsProduct());
+        accountingBridgeData.put("isAccountTransfer", isAccountTransfer);
+
+        final List<Map<String, Object>> newSavingsTransactions = new ArrayList<>();
+        for (final SavingsAccountTransaction transaction : this.transactions) {
+            if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) {
+                newSavingsTransactions.add(transaction.toMapData(currencyData));
+            } else if (!existingTransactionIds.contains(transaction.getId())) {
+                newSavingsTransactions.add(transaction.toMapData(currencyData));
+            }
+        }
+
+        accountingBridgeData.put("newSavingsTransactions", newSavingsTransactions);
+        return accountingBridgeData;
+    }
+
+    public Collection<Long> findExistingTransactionIds() {
+
+        final Collection<Long> ids = new ArrayList<>();
+
+        for (final SavingsAccountTransaction transaction : this.transactions) {
+            ids.add(transaction.getId());
+        }
+
+        return ids;
+    }
+
+    public Collection<Long> findExistingReversedTransactionIds() {
+
+        final Collection<Long> ids = new ArrayList<>();
+
+        for (final SavingsAccountTransaction transaction : this.transactions) {
+            if (transaction.isReversed()) {
+                ids.add(transaction.getId());
+            }
+        }
+
+        return ids;
+    }
+
+    public void update(final Client client) {
+        this.client = client;
+    }
+
+    public void update(final Group group) {
+        this.group = group;
+    }
+
+    public void update(final SavingsProduct product) {
+        this.product = product;
+        this.minBalanceForInterestCalculation = product.minBalanceForInterestCalculation();
+    }
+
+    public void update(final Staff savingsOfficer) {
+        this.savingsOfficer = savingsOfficer;
+    }
+
+    public void updateAccountNo(final String newAccountNo) {
+        this.accountNumber = newAccountNo;
+        this.accountNumberRequiresAutoGeneration = false;
+    }
+
+    public boolean isAccountNumberRequiresAutoGeneration() {
+        return this.accountNumberRequiresAutoGeneration;
+    }
+
+    public Long productId() {
+        return this.product.getId();
+    }
+
+    public SavingsProduct savingsProduct() {
+        return this.product;
+    }
+
+    private Boolean isCashBasedAccountingEnabledOnSavingsProduct() {
+        return this.product.isCashBasedAccountingEnabled();
+    }
+
+    private Boolean isAccrualBasedAccountingEnabledOnSavingsProduct() {
+        return this.product.isAccrualBasedAccountingEnabled();
+    }
+
+    public Long officeId() {
+        Long officeId = null;
+        if (this.client != null) {
+            officeId = this.client.officeId();
+        } else {
+            officeId = this.group.officeId();
+        }
+        return officeId;
+    }
+
+    public Office office() {
+        Office office = null;
+        if (this.client != null) {
+            office = this.client.getOffice();
+        } else {
+            office = this.group.getOffice();
+        }
+        return office;
+    }
+
+    public Staff getSavingsOfficer() {
+        return this.savingsOfficer;
+    }
+
+    public void unassignSavingsOfficer() {
+        this.savingsOfficer = null;
+    }
+
+    public void assignSavingsOfficer(final Staff fieldOfficer) {
+        this.savingsOfficer = fieldOfficer;
+    }
+
+    public Long clientId() {
+        Long id = null;
+        if (this.client != null) {
+            id = this.client.getId();
+        }
+        return id;
+    }
+
+    public Long groupId() {
+        Long id = null;
+        if (this.group != null) {
+            id = this.group.getId();
+        }
+        return id;
+    }
+
+    public Long hasSavingsOfficerId() {
+        Long id = null;
+        if (this.savingsOfficer != null) {
+            id = this.savingsOfficer.getId();
+        }
+        return id;
+    }
+
+    public boolean hasSavingsOfficer(final Staff fromSavingsOfficer) {
+
+        boolean matchesCurrentSavingsOfficer = false;
+        if (this.savingsOfficer != null) {
+            matchesCurrentSavingsOfficer = this.savingsOfficer.identifiedBy(fromSavingsOfficer);
+        } else {
+            matchesCurrentSavingsOfficer = fromSavingsOfficer == null;
+        }
+        return matchesCurrentSavingsOfficer;
+    }
+
+    public void reassignSavingsOfficer(final Staff newSavingsOfficer, final LocalDate assignmentDate) {
+        final SavingsOfficerAssignmentHistory latestHistoryRecord = findLatestIncompleteHistoryRecord();
+        final SavingsOfficerAssignmentHistory lastAssignmentRecord = findLastAssignmentHistoryRecord(newSavingsOfficer);
+
+        // assignment date should not be less than savings account submitted
+        // date
+        if (isSubmittedOnDateAfter(assignmentDate)) {
+
+            final String errorMessage = "The Savings Officer assignment date (" + assignmentDate.toString()
+                    + ") cannot be before savings submitted date (" + getSubmittedOnDate().toString() + ").";
+
+            throw new SavingsOfficerAssignmentDateException("cannot.be.before.savings.submitted.date", errorMessage, assignmentDate,
+                    getSubmittedOnDate());
+
+        } else if (lastAssignmentRecord != null && lastAssignmentRecord.isEndDateAfter(assignmentDate)) {
+
+            final String errorMessage = "The Savings Officer assignment date (" + assignmentDate
+                    + ") cannot be before previous Savings Officer unassigned date (" + lastAssignmentRecord.getEndDate() + ").";
+
+            throw new SavingsOfficerAssignmentDateException("cannot.be.before.previous.unassignement.date", errorMessage, assignmentDate,
+                    lastAssignmentRecord.getEndDate());
+
+        } else if (DateUtils.getLocalDateOfTenant().isBefore(assignmentDate)) {
+
+            final String errorMessage = "The Savings Officer assignment date (" + assignmentDate + ") cannot be in the future.";
+
+            throw new SavingsOfficerAssignmentDateException("cannot.be.a.future.date", errorMessage, assignmentDate);
+
+        } else if (latestHistoryRecord != null && this.savingsOfficer.identifiedBy(newSavingsOfficer)) {
+            latestHistoryRecord.updateStartDate(assignmentDate);
+        } else if (latestHistoryRecord != null && latestHistoryRecord.matchesStartDateOf(assignmentDate)) {
+            latestHistoryRecord.updateSavingsOfficer(newSavingsOfficer);
+            this.savingsOfficer = newSavingsOfficer;
+        } else if (latestHistoryRecord != null && latestHistoryRecord.hasStartDateBefore(assignmentDate)) {
+            final String errorMessage = "Savings account with identifier " + getId() + " was already assigned before date "
+                    + assignmentDate;
+            throw new SavingsOfficerAssignmentDateException("is.before.last.assignment.date", errorMessage, getId(), assignmentDate);
+        } else {
+            if (latestHistoryRecord != null) {
+                // savings officer correctly changed from previous savings
+                // officer to
+                // new savings officer
+                latestHistoryRecord.updateEndDate(assignmentDate);
+            }
+            this.savingsOfficer = newSavingsOfficer;
+            if (isNotSubmittedAndPendingApproval()) {
+                final SavingsOfficerAssignmentHistory savingsOfficerAssignmentHistory = SavingsOfficerAssignmentHistory.createNew(this,
+                        this.savingsOfficer, assignmentDate);
+                this.savingsOfficerHistory.add(savingsOfficerAssignmentHistory);
+            }
+        }
+    }
+
+    private SavingsOfficerAssignmentHistory findLastAssignmentHistoryRecord(final Staff newSavingsOfficer) {
+
+        SavingsOfficerAssignmentHistory lastAssignmentRecordLatestEndDate = null;
+        for (final SavingsOfficerAssignmentHistory historyRecord : this.savingsOfficerHistory) {
+
+            if (historyRecord.isCurrentRecord() && !historyRecord.isSameSavingsOfficer(newSavingsOfficer)) {
+                lastAssignmentRecordLatestEndDate = historyRecord;
+                break;
+            }
+
+            if (lastAssignmentRecordLatestEndDate == null) {
+                lastAssignmentRecordLatestEndDate = historyRecord;
+            } else if (historyRecord.isEndDateAfter(lastAssignmentRecordLatestEndDate.getEndDate())
+                    && !historyRecord.isSameSavingsOfficer(newSavingsOfficer)) {
+                lastAssignmentRecordLatestEndDate = historyRecord;
+            }
+        }
+        return lastAssignmentRecordLatestEndDate;
+    }
+
+    public boolean isSubmittedOnDateAfter(final LocalDate compareDate) {
+        return this.submittedOnDate == null ? false : new LocalDate(this.submittedOnDate).isAfter(compareDate);
+    }
+
+    public LocalDate getSubmittedOnDate() {
+        return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.submittedOnDate), null);
+    }
+
+    public void removeSavingsOfficer(final LocalDate unassignDate) {
+
+        final SavingsOfficerAssignmentHistory latestHistoryRecord = findLatestIncompleteHistoryRecord();
+
+        if (latestHistoryRecord != null) {
+            validateUnassignDate(latestHistoryRecord, unassignDate);
+            latestHistoryRecord.updateEndDate(unassignDate);
+        }
+        this.savingsOfficer = null;
+    }
+
+    private SavingsOfficerAssignmentHistory findLatestIncompleteHistoryRecord() {
+
+        SavingsOfficerAssignmentHistory latestRecordWithNoEndDate = null;
+        for (final SavingsOfficerAssignmentHistory historyRecord : this.savingsOfficerHistory) {
+            if (historyRecord.isCurrentRecord()) {
+                latestRecordWithNoEndDate = historyRecord;
+                break;
+            }
+        }
+        return latestRecordWithNoEndDate;
+    }
+
+    private void validateUnassignDate(final SavingsOfficerAssignmentHistory latestHistoryRecord, final LocalDate unassignDate) {
+
+        final LocalDate today = DateUtils.getLocalDateOfTenant();
+
+        if (latestHistoryRecord.getStartDate().isAfter(unassignDate)) {
+
+            final String errorMessage = "The Savings officer Unassign date(" + unassignDate + ") cannot be before its assignment date ("
+                    + latestHistoryRecord.getStartDate() + ").";
+
+            throw new SavingsOfficerUnassignmentDateException("cannot.be.before.assignment.date", errorMessage, getId(),
+                    getSavingsOfficer().getId(), latestHistoryRecord.getStartDate(), unassignDate);
+
+        } else if (unassignDate.isAfter(today)) {
+
+            final String errorMessage = "The Savings Officer Unassign date (" + unassignDate + ") cannot be in the future.";
+
+            throw new SavingsOfficerUnassignmentDateException("cannot.be.a.future.date", errorMessage, unassignDate);
+        }
+    }
+
+    public MonetaryCurrency getCurrency() {
+        return this.currency;
+    }
+
+    public void validateNewApplicationState(final LocalDate todayDateOfTenant, final String resourceName) {
+
+        // validateWithdrawalFeeDetails();
+        // validateAnnualFeeDetails();
+
+        final LocalDate submittedOn = getSubmittedOnLocalDate();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(resourceName
+                + SavingsApiConstants.summitalAction);
+
+        validateLockinDetails(baseDataValidator);
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+        if (submittedOn.isAfter(todayDateOfTenant)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(submittedOn)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
+        }
+
+        if (this.client != null && this.client.isActivatedAfter(submittedOn)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(this.client.getActivationLocalDate())
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date");
+        } else if (this.group != null && this.group.isActivatedAfter(submittedOn)) {
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(this.group.getActivationLocalDate())
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date");
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    protected LocalDate getSubmittedOnLocalDate() {
+        LocalDate submittedOn = null;
+        if (this.submittedOnDate != null) {
+            submittedOn = new LocalDate(this.submittedOnDate);
+        }
+        return submittedOn;
+    }
+
+    private LocalDate getApprovedOnLocalDate() {
+        LocalDate approvedOnLocalDate = null;
+        if (this.approvedOnDate != null) {
+            approvedOnLocalDate = new LocalDate(this.approvedOnDate);
+        }
+        return approvedOnLocalDate;
+    }
+
+    public Client getClient() {
+        return this.client;
+    }
+
+    public BigDecimal getNominalAnnualInterestRate() {
+        return this.nominalAnnualInterestRate;
+    }
+
+    public BigDecimal getNominalAnnualInterestRateOverdraft() {
+        return this.nominalAnnualInterestRateOverdraft;
+    }
+
+    public Map<String, Object> approveApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.approvalAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName)
+                    .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.status = SavingsAccountStatusType.APPROVED.getValue();
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+
+        // only do below if status has changed in the 'approval' case
+        final LocalDate approvedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName);
+        final String approvedOnDateChange = command.stringValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName);
+
+        this.approvedOnDate = approvedOn.toDate();
+        this.approvedBy = currentUser;
+        actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
+        actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
+        actualChanges.put(SavingsApiConstants.approvedOnDateParamName, approvedOnDateChange);
+
+        final LocalDate submittalDate = getSubmittedOnLocalDate();
+        if (approvedOn.isBefore(submittalDate)) {
+
+            final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
+            final String submittalDateAsString = formatter.print(submittalDate);
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName).value(submittalDateAsString)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.submittal.date");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (approvedOn.isAfter(tenantsTodayDate)) {
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLICATION_APPROVED, approvedOn);
+
+        // FIXME - kw - support field officer history for savings accounts
+        // if (this.fieldOfficer != null) {
+        // final LoanOfficerAssignmentHistory loanOfficerAssignmentHistory =
+        // LoanOfficerAssignmentHistory.createNew(this,
+        // this.fieldOfficer, approvedOn);
+        // this.loanOfficerHistory.add(loanOfficerAssignmentHistory);
+        // }
+        if (this.savingsOfficer != null) {
+            final SavingsOfficerAssignmentHistory savingsOfficerAssignmentHistory = SavingsOfficerAssignmentHistory.createNew(this,
+                    this.savingsOfficer, approvedOn);
+            this.savingsOfficerHistory.add(savingsOfficerAssignmentHistory);
+        }
+        return actualChanges;
+    }
+
+    public Map<String, Object> undoApplicationApproval() {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.undoApprovalAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) {
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName)
+                    .failWithCodeNoParameterAddedToErrorCode("not.in.approved.state");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue();
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+
+        this.approvedOnDate = null;
+        this.approvedBy = null;
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = null;
+        this.closedBy = null;
+        actualChanges.put(SavingsApiConstants.approvedOnDateParamName, "");
+
+        // FIXME - kw - support field officer history for savings accounts
+        // this.loanOfficerHistory.clear();
+
+        return actualChanges;
+    }
+
+    public void undoTransaction(final Long transactionId) {
+
+        SavingsAccountTransaction transactionToUndo = null;
+        for (final SavingsAccountTransaction transaction : this.transactions) {
+            if (transaction.isIdentifiedBy(transactionId)) {
+                transactionToUndo = transaction;
+            }
+        }
+
+        if (transactionToUndo == null) { throw new SavingsAccountTransactionNotFoundException(this.getId(), transactionId); }
+
+        validateAttemptToUndoTransferRelatedTransactions(transactionToUndo);
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.transactionLocalDate());
+        transactionToUndo.reverse();
+        if (transactionToUndo.isChargeTransaction() || transactionToUndo.isWaiveCharge()) {
+            // undo charge
+            final Set<SavingsAccountChargePaidBy> chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid();
+            for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) {
+                final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge();
+                if (transactionToUndo.isChargeTransaction()) {
+                    chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
+                } else if (transactionToUndo.isWaiveCharge()) {
+                    chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
+                }
+            }
+        }
+    }
+
+    private Date findLatestAnnualFeeTransactionDueDate() {
+
+        Date nextDueDate = null;
+
+        LocalDate lastAnnualFeeTransactionDate = null;
+        for (final SavingsAccountTransaction transaction : retreiveOrderedNonInterestPostingTransactions()) {
+            if (transaction.isAnnualFeeAndNotReversed()) {
+                if (lastAnnualFeeTransactionDate == null) {
+                    lastAnnualFeeTransactionDate = transaction.transactionLocalDate();
+                    nextDueDate = lastAnnualFeeTransactionDate.toDate();
+                }
+
+                if (transaction.transactionLocalDate().isAfter(lastAnnualFeeTransactionDate)) {
+                    lastAnnualFeeTransactionDate = transaction.transactionLocalDate();
+                    nextDueDate = lastAnnualFeeTransactionDate.toDate();
+                }
+            }
+        }
+
+        return nextDueDate;
+    }
+
+    public Map<String, Object> rejectApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.rejectAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) {
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.rejectedOnDateParamName)
+                    .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.status = SavingsAccountStatusType.REJECTED.getValue();
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+
+        final LocalDate rejectedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.rejectedOnDateParamName);
+        final String rejectedOnAsString = command.stringValueOfParameterNamed(SavingsApiConstants.rejectedOnDateParamName);
+
+        this.rejectedOnDate = rejectedOn.toDate();
+        this.rejectedBy = currentUser;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = rejectedOn.toDate();
+        this.closedBy = currentUser;
+
+        actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
+        actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
+        actualChanges.put(SavingsApiConstants.rejectedOnDateParamName, rejectedOnAsString);
+        actualChanges.put(SavingsApiConstants.closedOnDateParamName, rejectedOnAsString);
+
+        final LocalDate submittalDate = getSubmittedOnLocalDate();
+
+        if (rejectedOn.isBefore(submittalDate)) {
+
+            final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
+            final String submittalDateAsString = formatter.print(submittalDate);
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.rejectedOnDateParamName).value(submittalDateAsString)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.submittal.date");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (rejectedOn.isAfter(tenantsTodayDate)) {
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.rejectedOnDateParamName).value(rejectedOn)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLICATION_REJECTED, rejectedOn);
+
+        return actualChanges;
+    }
+
+    public Map<String, Object> applicantWithdrawsFromApplication(final AppUser currentUser, final JsonCommand command,
+            final LocalDate tenantsTodayDate) {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.withdrawnByApplicantAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) {
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName)
+                    .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.status = SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue();
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+
+        final LocalDate withdrawnOn = command.localDateValueOfParameterNamed(SavingsApiConstants.withdrawnOnDateParamName);
+        final String withdrawnOnAsString = command.stringValueOfParameterNamed(SavingsApiConstants.withdrawnOnDateParamName);
+
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = withdrawnOn.toDate();
+        this.withdrawnBy = currentUser;
+        this.closedOnDate = withdrawnOn.toDate();
+        this.closedBy = currentUser;
+
+        actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
+        actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
+        actualChanges.put(SavingsApiConstants.withdrawnOnDateParamName, withdrawnOnAsString);
+        actualChanges.put(SavingsApiConstants.closedOnDateParamName, withdrawnOnAsString);
+
+        final LocalDate submittalDate = getSubmittedOnLocalDate();
+        if (withdrawnOn.isBefore(submittalDate)) {
+
+            final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
+            final String submittalDateAsString = formatter.print(submittalDate);
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName).value(submittalDateAsString)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.submittal.date");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (withdrawnOn.isAfter(tenantsTodayDate)) {
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName).value(withdrawnOn)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLICATION_WITHDRAWAL_BY_CUSTOMER, withdrawnOn);
+
+        return actualChanges;
+    }
+
+    public Map<String, Object> activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(depositAccountType()
+                .resourceName() + SavingsApiConstants.activateAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) {
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName)
+                    .failWithCodeNoParameterAddedToErrorCode("not.in.approved.state");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate activationDate = command.localDateValueOfParameterNamed(SavingsApiConstants.activatedOnDateParamName);
+
+        this.status = SavingsAccountStatusType.ACTIVE.getValue();
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+        actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
+        actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
+        actualChanges.put(SavingsApiConstants.activatedOnDateParamName, activationDate.toString(fmt));
+
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = null;
+        this.closedBy = null;
+        this.activatedOnDate = activationDate.toDate();
+        this.activatedBy = currentUser;
+        this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate());
+
+        /*
+         * if (annualFeeSettingsSet()) {
+         * updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); }
+         */
+        if (this.client != null && this.client.isActivatedAfter(activationDate)) {
+            final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
+            final String dateAsString = formatter.print(this.client.getActivationLocalDate());
+            baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (this.group != null && this.group.isActivatedAfter(activationDate)) {
+            final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
+            final String dateAsString = formatter.print(this.client.getActivationLocalDate());
+            baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.group.activation.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final LocalDate approvalDate = getApprovedOnLocalDate();
+        if (activationDate.isBefore(approvalDate)) {
+
+            final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
+            final String dateAsString = formatter.print(approvalDate);
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.approval.date");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (activationDate.isAfter(tenantsTodayDate)) {
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(activationDate)
+                    .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_ACTIVATE, activationDate);
+
+        return actualChanges;
+    }
+
+    public void processAccountUponActivation(final boolean isSavingsInterestPostingAtCurrentPeriodEnd,
+            final Integer financialYearBeginningMonth, final AppUser user) {
+
+        // update annual fee due date
+        for (SavingsAccountCharge charge : this.charges()) {
+            charge.updateToNextDueDateFrom(getActivationLocalDate());
+        }
+
+        // auto pay the activation time charges
+        this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user);
+        // TODO : AA add activation charges to actual changes list
+    }
+
+    public Money activateWithBalance() {
+        return Money.of(this.currency, this.minRequiredOpeningBalance);
+    }
+
+    public void approveAndActivateApplication(final Date appliedonDate, final AppUser appliedBy) {
+        this.status = SavingsAccountStatusType.ACTIVE.getValue();
+        this.approvedOnDate = appliedonDate;
+        this.approvedBy = appliedBy;
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = null;
+        this.closedBy = null;
+        this.activatedOnDate = appliedonDate;
+        this.activatedBy = appliedBy;
+        this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate());
+    }
+
+    private void payActivationCharges(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
+            final AppUser user) {
+        boolean isSavingsChargeApplied = false;
+        for (SavingsAccountCharge savingsAccountCharge : this.charges()) {
+            if (savingsAccountCharge.isSavingsActivation()) {
+                isSavingsChargeApplied = true;
+                payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationLocalDate(), user);
+            }
+        }
+
+        if (isSavingsChargeApplied) {
+            final MathContext mc = MathContext.DECIMAL64;
+            boolean isInterestTransfer = false;
+            if (this.isBeforeLastPostingPeriod(getActivationLocalDate())) {
+                final LocalDate today = DateUtils.getLocalDateOfTenant();
+                this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+            } else {
+                final LocalDate today = DateUtils.getLocalDateOfTenant();
+                this.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                        financialYearBeginningMonth);
+            }
+        }
+    }
+
+    public Map<String, Object> close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction);
+
+        final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
+        if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+
+        if (closedDate.isBefore(getActivationLocalDate())) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("must.be.after.activation.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        if (closedDate.isAfter(tenantsTodayDate)) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                    .failWithCode("cannot.be.a.future.date");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+        if (savingsAccountTransactions.size() > 0) {
+            final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
+            if (accountTransaction.isAfter(closedDate)) {
+                baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
+                        .failWithCode("must.be.after.last.transaction.date");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+        if (getAccountBalance().doubleValue() != 0) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.not.zero");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate);
+        this.status = SavingsAccountStatusType.CLOSED.getValue();
+        actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
+        actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
+        actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
+        actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt));
+
+        this.rejectedOnDate = null;
+        this.rejectedBy = null;
+        this.withdrawnOnDate = null;
+        this.withdrawnBy = null;
+        this.closedOnDate = closedDate.toDate();
+        this.closedBy = currentUser;
+
+        return actualChanges;
+    }
+
+    protected void validateActivityNotBeforeClientOrGroupTransferDate(final SavingsEvent event, final LocalDate activityDate) {
+        if (this.client != null && this.client.getOfficeJoiningLocalDate() != null) {
+            final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate();
+            if (activityDate.isBefore(clientOfficeJoiningDate)) { throw new SavingsActivityPriorToClientTransferException(event.toString(),
+                    clientOfficeJoiningDate); }
+        }
+    }
+
+    private void validateAttemptToUndoTransferRelatedTransactions(final SavingsAccountTransaction savingsAccountTransaction) {
+        if (savingsAccountTransaction.isTransferRelatedTransaction()) { throw new SavingsTransferTransactionsCannotBeUndoneException(
+                savingsAccountTransaction.getId()); }
+    }
+
+    private Date calculateDateAccountIsLockedUntil(final LocalDate activationLocalDate) {
+
+        Date lockedInUntilLocalDate = null;
+        final PeriodFrequencyType lockinPeriodFrequencyType = PeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType);
+        switch (lockinPeriodFrequencyType) {
+            case INVALID:
+            break;
+            case DAYS:
+                lockedInUntilLocalDate = activationLocalDate.plusDays(this.lockinPeriodFrequency).toDate();
+            break;
+            case WEEKS:
+                lockedInUntilLocalDate = activationLocalDate.plusWeeks(this.lockinPeriodFrequency).toDate();
+            break;
+            case MONTHS:
+                lockedInUntilLocalDate = activationLocalDate.plusMonths(this.lockinPeriodFrequency).toDate();
+            break;
+            case YEARS:
+                lockedInUntilLocalDate = activationLocalDate.plusYears(this.lockinPeriodFrequency).toDate();
+            break;
+        }
+
+        return lockedInUntilLocalDate;
+    }
+
+    public Group group() {
+        return this.group;
+    }
+
+    public boolean isWithdrawalFeeApplicableForTransfer() {
+        return this.withdrawalFeeApplicableForTransfer;
+    }
+
+    public void activateAccountBasedOnBalance() {
+        if (SavingsAccountStatusType.fromInt(this.status).isClosed() && !this.summary.getAccountBalance(getCurrency()).isZero()) {
+            this.status = SavingsAccountStatusType.ACTIVE.getValue();
+        }
+    }
+
+    public LocalDate getClosedOnDate() {
+        return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.closedOnDate), null);
+    }
+
+    public SavingsAccountSummary getSummary() {
+        return this.summary;
+    }
+
+    public List<SavingsAccountTransaction> getTransactions() {
+        return this.transactions;
+    }
+
+    public void setStatus(final Integer status) {
+        this.status = status;
+    }
+
+    private Set<SavingsAccountCharge> associateChargesWithThisSavingsAccount(final Set<SavingsAccountCharge> savingsAccountCharges) {
+        for (final SavingsAccountCharge savingsAccountCharge : savingsAccountCharges) {
+            savingsAccountCharge.update(this);
+        }
+        return savingsAccountCharges;
+    }
+
+    public boolean update(final Set<SavingsAccountCharge> newSavingsAccountCharges) {
+        if (newSavingsAccountCharges == null) { return false; }
+
+        if (this.charges == null) {
+            this.charges = new HashSet<>();
+        }
+        this.charges.clear();
+        this.charges.addAll(associateChargesWithThisSavingsAccount(newSavingsAccountCharges));
+        return true;
+    }
+
+    public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) {
+        if (this.currency == null) { return false; }
+        return this.currency.getCode().equalsIgnoreCase(matchingCurrencyCode);
+    }
+
+    public void removeCharge(final SavingsAccountCharge charge) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        if (isClosed()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("delete.transaction.invalid.account.is.closed");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (isActive() || isApproved()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("delete.transaction.invalid.account.is.active");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.charges.remove(charge);
+    }
+
+    public void waiveCharge(final Long savingsAccountChargeId, final AppUser user) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        if (isClosed()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (isNotActive()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId);
+
+        if (savingsAccountCharge.isNotActive()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.not.active");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (savingsAccountCharge.isWithdrawalFee()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.waiver.of.withdrawal.fee.not.supported");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        // validate charge is not already paid or waived
+        if (savingsAccountCharge.isWaived()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.already.waived");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        } else if (savingsAccountCharge.isPaid()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.paid");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        // waive charge
+        final Money amountWaived = savingsAccountCharge.waive(getCurrency());
+        handleWaiverChargeTransactions(savingsAccountCharge, amountWaived, user);
+
+    }
+
+    public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        if (isClosed()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (!hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                    "transaction.invalid.account.currency.and.charge.currency.not.same");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate();
+
+        if (savingsAccountCharge.isOnSpecifiedDueDate()) {
+            if (getActivationLocalDate() != null && chargeDueDate.isBefore(getActivationLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter))
+                        .failWithCodeNoParameterAddedToErrorCode("before.activationDate");
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            } else if (getSubmittedOnLocalDate() != null && chargeDueDate.isBefore(getSubmittedOnLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter))
+                        .failWithCodeNoParameterAddedToErrorCode("before.submittedOnDate");
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            }
+        }
+
+        if (savingsAccountCharge.isSavingsActivation() && !(isSubmittedAndPendingApproval() || (isApproved() && isNotActive()))) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.valid.account.status.cannot.add.activation.time.charge");
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        // Only one withdrawal fee is supported per account
+        if (savingsAccountCharge.isWithdrawalFee()) {
+            if (this.isWithDrawalFeeExists()) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("multiple.withdrawal.fee.per.account.not.supported");
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            }
+        }
+
+        // Only one annual fee is supported per account
+        if (savingsAccountCharge.isAnnualFee()) {
+            if (this.isAnnualFeeExists()) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("multiple.annual.fee.per.account.not.supported");
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            }
+
+        }
+
+        if (savingsAccountCharge.isAnnualFee() || savingsAccountCharge.isMonthlyFee() || savingsAccountCharge.isWeeklyFee()) {
+            // update due date
+            if (isActive()) {
+                savingsAccountCharge.updateToNextDueDateFrom(getActivationLocalDate());
+            } else if (isApproved()) {
+                savingsAccountCharge.updateToNextDueDateFrom(getApprovedOnLocalDate());
+            }
+        }
+
+        // activation charge and withdrawal charges not required this validation
+        if (savingsAccountCharge.isOnSpecifiedDueDate()) {
+            validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate);
+        }
+
+        // add new charge to savings account
+        charges().add(savingsAccountCharge);
+
+    }
+
+    private boolean isWithDrawalFeeExists() {
+        for (SavingsAccountCharge charge : this.charges()) {
+            if (charge.isWithdrawalFee()) return true;
+        }
+        return false;
+    }
+
+    private boolean isAnnualFeeExists() {
+        for (SavingsAccountCharge charge : this.charges()) {
+            if (charge.isAnnualFee()) return true;
+        }
+        return false;
+    }
+
+    public void payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, final LocalDate transactionDate,
+            final DateTimeFormatter formatter, final AppUser user) {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        if (isClosed()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (isNotActive()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (savingsAccountCharge.isNotActive()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.not.active");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())) {
+            baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter))
+                    .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate");
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (DateUtils.isDateInTheFuture(transactionDate)) {
+            baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(formatter))
+                    .failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate");
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (savingsAccountCharge.isSavingsActivation()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+                    "transaction.not.valid.cannot.pay.activation.time.charge.is.automated");
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+
+        if (savingsAccountCharge.isAnnualFee()) {
+            final LocalDate annualFeeDueDate = savingsAccountCharge.getDueLocalDate();
+            if (annualFeeDueDate == null) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.annualfee.settings");
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            }
+
+            if (!annualFeeDueDate.equals(transactionDate)) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("invalid.date");
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            }
+
+            Date currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate();
+            if (currentAnnualFeeNextDueDate != null && new LocalDate(currentAnnualFeeNextDueDate).isEqual(transactionDate)) {
+                baseDataValidator.reset().parameter("dueDate").value(transactionDate.toString(formatter))
+                        .failWithCodeNoParameterAddedToErrorCode("transaction.exists.on.date");
+
+                throw new PlatformApiDataValidationException(dataValidationErrors);
+            }
+        }
+
+        // validate charge is not already paid or waived
+        if (savingsAccountCharge.isWaived()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.already.waived");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        } else if (savingsAccountCharge.isPaid()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.paid");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final Money chargePaid = Money.of(currency, amountPaid);
+        if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(chargePaid)) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.payCharge(savingsAccountCharge, chargePaid, transactionDate, user);
+    }
+
+    public void payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, final LocalDate transactionDate,
+            final AppUser user) {
+        savingsAccountCharge.pay(getCurrency(), amountPaid);
+        handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, user);
+    }
+
+    private void handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount,
+            final LocalDate transactionDate, final AppUser user) {
+        SavingsAccountTransaction chargeTransaction = null;
+
+        if (savingsAccountCharge.isWithdrawalFee()) {
+            chargeTransaction = SavingsAccountTransaction.withdrawalFee(this, office(), transactionDate, transactionAmount, user);
+        } else if (savingsAccountCharge.isAnnualFee()) {
+            chargeTransaction = SavingsAccountTransaction.annualFee(this, office(), transactionDate, transactionAmount, user);
+        } else {
+            chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount, user);
+        }
+
+        handleChargeTransactions(savingsAccountCharge, chargeTransaction);
+    }
+
+    private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, AppUser user) {
+        final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.waiver(this, office(),
+                DateUtils.getLocalDateOfTenant(), transactionAmount, user);
+        handleChargeTransactions(savingsAccountCharge, chargeTransaction);
+    }
+
+    private void handleChargeTransactions(final SavingsAccountCharge savingsAccountCharge, final SavingsAccountTransaction transaction) {
+        // Provide a link between transaction and savings charge for which
+        // amount is waived.
+        final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(transaction, savingsAccountCharge, transaction
+                .getAmount(this.getCurrency()).getAmount());
+        transaction.getSavingsAccountChargesPaid().add(chargePaidBy);
+        this.getTransactions().add(transaction);
+    }
+
+    private SavingsAccountCharge getCharge(final Long savingsAccountChargeId) {
+        SavingsAccountCharge charge = null;
+        for (final SavingsAccountCharge existingCharge : this.charges) {
+            if (existingCharge.getId().equals(savingsAccountChargeId)) {
+                charge = existingCharge;
+                break;
+            }
+        }
+
+        if (charge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId, getId()); }
+
+        return charge;
+    }
+
+    public Set<SavingsAccountCharge> charges() {
+        return (this.charges == null) ? new HashSet<SavingsAccountCharge>() : this.charges;
+    }
+
+    public void validateAccountValuesWithProduct() {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        if (this.overdraftLimit != null && this.product.overdraftLimit() != null
+                && this.overdraftLimit.compareTo(this.product.overdraftLimit()) == 1) {
+            baseDataValidator.reset().parameter(SavingsApiConstants.overdraftLimitParamName).value(this.overdraftLimit)
+                    .failWithCode("cannot.exceed.product.value");
+        }
+
+        validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator);
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public boolean allowOverdraft() {
+        return this.allowOverdraft;
+    }
+
+    public LocalDate accountSubmittedOrActivationDate() {
+        return getActivationLocalDate() == null ? getSubmittedOnLocalDate() : getActivationLocalDate();
+    }
+
+    public DepositAccountType depositAccountType() {
+        return DepositAccountType.fromInt(depositType);
+    }
+
+    protected boolean isTransferInterestToOtherAccount() {
+        return false;
+    }
+
+    public boolean accountSubmittedAndActivationOnSameDate() {
+        if (getSubmittedOnLocalDate() == null || getActivationLocalDate() == null) { return false; }
+        return getActivationLocalDate().isEqual(getSubmittedOnLocalDate());
+
+    }
+
+    public void validateInterestPostingAndCompoundingPeriodTypes(final DataValidatorBuilder baseDataValidator) {
+        Map<SavingsPostingInterestPeriodType, List<SavingsCompoundingInterestPeriodType>> postingtoCompoundMap = new HashMap<>();
+        postingtoCompoundMap.put(
+                SavingsPostingInterestPeriodType.MONTHLY,
+                Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY,
+                        SavingsCompoundingInterestPeriodType.MONTHLY }));
+
+        postingtoCompoundMap.put(
+                SavingsPostingInterestPeriodType.QUATERLY,
+                Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY,
+                        SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY }));
+
+        postingtoCompoundMap.put(
+                SavingsPostingInterestPeriodType.BIANNUAL,
+                Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY,
+                        SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY,
+                        SavingsCompoundingInterestPeriodType.BI_ANNUAL }));
+
+        postingtoCompoundMap.put(
+                SavingsPostingInterestPeriodType.ANNUAL,
+                Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY,
+                        SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY,
+                        SavingsCompoundingInterestPeriodType.BI_ANNUAL, SavingsCompoundingInterestPeriodType.ANNUAL }));
+
+        SavingsPostingInterestPeriodType savingsPostingInterestPeriodType = SavingsPostingInterestPeriodType
+                .fromInt(interestPostingPeriodType);
+        SavingsCompoundingInterestPeriodType savingsCompoundingInterestPeriodType = SavingsCompoundingInterestPeriodType
+                .fromInt(interestCompoundingPeriodType);
+
+        if (postingtoCompoundMap.get(savingsPostingInterestPeriodType) == null
+                || !postingtoCompoundMap.get(savingsPostingInterestPeriodType).contains(savingsCompoundingInterestPeriodType)) {
+            baseDataValidator.failWithCodeNoParameterAddedToErrorCode("posting.period.type.is.less.than.compound.period.type",
+                    savingsPostingInterestPeriodType.name(), savingsCompoundingInterestPeriodType.name());
+
+        }
+    }
+
+    public boolean allowDeposit() {
+        return true;
+    }
+
+    public boolean allowWithdrawal() {
+        return true;
+    }
+
+    public boolean allowModify() {
+        return true;
+    }
+
+    public boolean isTransactionsAllowed() {
+        return isActive();
+    }
+
+    public BigDecimal minBalanceForInterestCalculation() {
+        return this.minBalanceForInterestCalculation;
+    }
+
+    public void inactivateCharge(SavingsAccountCharge savingsAccountCharge, LocalDate inactivationOnDate) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        if (isClosed()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (isNotActive()) {
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+        savingsAccountCharge.inactiavateCharge(inactivationOnDate);
+    }
+
+    public SavingsAccountCharge getUpdatedChargeDetails(SavingsAccountCharge savingsAccountCharge) {
+        for (final SavingsAccountCharge charge : this.charges) {
+            if (charge.equals(savingsAccountCharge)) {
+                savingsAccountCharge = charge;
+                break;
+            }
+        }
+        return savingsAccountCharge;
+    }
+
+    public String getAccountNumber() {
+        return this.accountNumber;
+    }
+
+    private Money minRequiredBalanceDerived(final MonetaryCurrency currency) {
+        Money minReqBalance = Money.zero(currency);
+        if (this.enforceMinRequiredBalance) {
+            minReqBalance = minReqBalance.plus(this.minRequiredBalance);
+        }
+        if (this.allowOverdraft) {
+            minReqBalance = minReqBalance.minus(this.overdraftLimit);
+        }
+        return minReqBalance;
+    }
+
+    public BigDecimal getOnHoldFunds() {
+        return this.onHoldFunds == null ? BigDecimal.ZERO : this.onHoldFunds;
+    }
+
+    public void holdFunds(BigDecimal onHoldFunds) {
+        this.onHoldFunds = getOnHoldFunds().add(onHoldFunds);
+    }
+
+    public void releaseFunds(BigDecimal onHoldFunds) {
+        this.onHoldFunds = getOnHoldFunds().subtract(onHoldFunds);
+    }
+
+    public BigDecimal getWithdrawableBalance() {
+        return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount()).subtract(this.getOnHoldFunds());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java
new file mode 100644
index 0000000..ec30ea8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java
@@ -0,0 +1,342 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.externalIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.groupIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
+
+import java.math.BigDecimal;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
+import org.apache.fineract.portfolio.group.exception.CenterNotActiveException;
+import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonElement;
+
+@Service
+public class SavingsAccountAssembler {
+
+    private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
+    private final SavingsHelper savingsHelper;
+    private final ClientRepositoryWrapper clientRepository;
+    private final GroupRepositoryWrapper groupRepository;
+    private final StaffRepositoryWrapper staffRepository;
+    private final SavingsProductRepository savingProductRepository;
+    private final SavingsAccountRepositoryWrapper savingsAccountRepository;
+    private final SavingsAccountChargeAssembler savingsAccountChargeAssembler;
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public SavingsAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
+            final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository,
+            final StaffRepositoryWrapper staffRepository, final SavingsProductRepository savingProductRepository,
+            final SavingsAccountRepositoryWrapper savingsAccountRepository,
+            final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper,
+            final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
+        this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
+        this.clientRepository = clientRepository;
+        this.groupRepository = groupRepository;
+        this.staffRepository = staffRepository;
+        this.savingProductRepository = savingProductRepository;
+        this.savingsAccountRepository = savingsAccountRepository;
+        this.savingsAccountChargeAssembler = savingsAccountChargeAssembler;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        savingsHelper = new SavingsHelper(accountTransfersReadPlatformService);
+    }
+
+    /**
+     * Assembles a new {@link SavingsAccount} from JSON details passed in
+     * request inheriting details where relevant from chosen
+     * {@link SavingsProduct}.
+     */
+    public SavingsAccount assembleFrom(final JsonCommand command, final AppUser submittedBy) {
+
+        final JsonElement element = command.parsedJson();
+
+        final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParamName, element);
+        final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParamName, element);
+        final Long productId = this.fromApiJsonHelper.extractLongNamed(productIdParamName, element);
+
+        final SavingsProduct product = this.savingProductRepository.findOne(productId);
+        if (product == null) { throw new SavingsProductNotFoundException(productId); }
+
+        Client client = null;
+        Group group = null;
+        Staff fieldOfficer = null;
+        AccountType accountType = AccountType.INVALID;
+        final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element);
+        if (clientId != null) {
+            client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            accountType = AccountType.INDIVIDUAL;
+            if (client.isNotActive()) { throw new ClientNotActiveException(clientId); }
+        }
+
+        final Long groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element);
+        if (groupId != null) {
+            group = this.groupRepository.findOneWithNotFoundDetection(groupId);
+            accountType = AccountType.GROUP;
+            if (group.isNotActive()) {
+                if (group.isCenter()) { throw new CenterNotActiveException(groupId); }
+                throw new GroupNotActiveException(groupId);
+            }
+        }
+
+        if (group != null && client != null) {
+            if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); }
+            accountType = AccountType.JLG;
+        }
+
+        final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element);
+        if (fieldOfficerId != null) {
+            fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId);
+        }
+
+        final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element);
+
+        BigDecimal interestRate = null;
+        if (command.parameterExists(nominalAnnualInterestRateParamName)) {
+            interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
+        } else {
+            interestRate = product.nominalAnnualInterestRate();
+        }
+
+        SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null;
+        final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
+        if (interestPeriodTypeValue != null) {
+            interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue);
+        } else {
+            interestCompoundingPeriodType = product.interestCompoundingPeriodType();
+        }
+
+        SavingsPostingInterestPeriodType interestPostingPeriodType = null;
+        final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
+        if (interestPostingPeriodTypeValue != null) {
+            interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue);
+        } else {
+            interestPostingPeriodType = product.interestPostingPeriodType();
+        }
+
+        SavingsInterestCalculationType interestCalculationType = null;
+        final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
+        if (interestCalculationTypeValue != null) {
+            interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue);
+        } else {
+            interestCalculationType = product.interestCalculationType();
+        }
+
+        SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = null;
+        final Integer interestCalculationDaysInYearTypeValue = command
+                .integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
+        if (interestCalculationDaysInYearTypeValue != null) {
+            interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue);
+        } else {
+            interestCalculationDaysInYearType = product.interestCalculationDaysInYearType();
+        }
+
+        BigDecimal minRequiredOpeningBalance = null;
+        if (command.parameterExists(minRequiredOpeningBalanceParamName)) {
+            minRequiredOpeningBalance = command.bigDecimalValueOfParameterNamed(minRequiredOpeningBalanceParamName);
+        } else {
+            minRequiredOpeningBalance = product.minRequiredOpeningBalance();
+        }
+
+        Integer lockinPeriodFrequency = null;
+        if (command.parameterExists(lockinPeriodFrequencyParamName)) {
+            lockinPeriodFrequency = command.integerValueOfParameterNamed(lockinPeriodFrequencyParamName);
+        } else {
+            lockinPeriodFrequency = product.lockinPeriodFrequency();
+        }
+
+        SavingsPeriodFrequencyType lockinPeriodFrequencyType = null;
+        Integer lockinPeriodFrequencyTypeValue = null;
+        if (command.parameterExists(lockinPeriodFrequencyTypeParamName)) {
+            lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
+            if (lockinPeriodFrequencyTypeValue != null) {
+                lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+            }
+        } else {
+            lockinPeriodFrequencyType = product.lockinPeriodFrequencyType();
+        }
+        boolean iswithdrawalFeeApplicableForTransfer = false;
+        if (command.parameterExists(withdrawalFeeForTransfersParamName)) {
+            iswithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
+        }
+
+        final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(element, product.currency().getCode());
+
+        boolean allowOverdraft = false;
+        if (command.parameterExists(allowOverdraftParamName)) {
+            allowOverdraft = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
+        } else {
+            allowOverdraft = product.isAllowOverdraft();
+        }
+
+        BigDecimal overdraftLimit = BigDecimal.ZERO;
+        if (command.parameterExists(overdraftLimitParamName)) {
+            overdraftLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(overdraftLimitParamName);
+        } else {
+            overdraftLimit = product.overdraftLimit();
+        }
+
+        BigDecimal nominalAnnualInterestRateOverdraft = BigDecimal.ZERO;
+        if (command.parameterExists(nominalAnnualInterestRateOverdraftParamName)) {
+        	nominalAnnualInterestRateOverdraft = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName);
+        } else {
+        	nominalAnnualInterestRateOverdraft = product.nominalAnnualInterestRateOverdraft();
+        }
+
+        BigDecimal minOverdraftForInterestCalculation = BigDecimal.ZERO;
+        if (command.parameterExists(minOverdraftForInterestCalculationParamName)) {
+        	minOverdraftForInterestCalculation = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName);
+        } else {
+        	minOverdraftForInterestCalculation = product.minOverdraftForInterestCalculation();
+        }
+
+        boolean enforceMinRequiredBalance = false;
+        if (command.parameterExists(enforceMinRequiredBalanceParamName)) {
+            enforceMinRequiredBalance = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName);
+        } else {
+            enforceMinRequiredBalance = product.isMinRequiredBalanceEnforced();
+        }
+
+        BigDecimal minRequiredBalance = BigDecimal.ZERO;
+        if (command.parameterExists(minRequiredBalanceParamName)) {
+            minRequiredBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName);
+        } else {
+            minRequiredBalance = product.minRequiredBalance();
+        }
+
+        final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo,
+                externalId, accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges, allowOverdraft,
+                overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, 
+                nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+        account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
+
+        account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        account.validateAccountValuesWithProduct();
+
+        return account;
+    }
+
+    public SavingsAccount assembleFrom(final Long savingsId) {
+        final SavingsAccount account = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId);
+        account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
+        return account;
+    }
+
+    public void setHelpers(final SavingsAccount account) {
+        account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
+    }
+
+    /**
+     * Assembles a new {@link SavingsAccount} from JSON details passed in
+     * request inheriting details where relevant from chosen
+     * {@link SavingsProduct}.
+     */
+    public SavingsAccount assembleFrom(final Client client, final Group group, final SavingsProduct product, final LocalDate appliedonDate,
+            final AppUser appliedBy) {
+
+        AccountType accountType = AccountType.INVALID;
+        if (client != null) {
+            accountType = AccountType.INDIVIDUAL;
+            if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); }
+        }
+
+        if (group != null) {
+            accountType = AccountType.GROUP;
+            if (group.isNotActive()) {
+                if (group.isCenter()) { throw new CenterNotActiveException(group.getId()); }
+                throw new GroupNotActiveException(group.getId());
+            }
+        }
+
+        if (group != null && client != null) {
+            if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(client.getId(), group.getId()); }
+            accountType = AccountType.JLG;
+        }
+
+        final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromSavingsProduct(product);
+
+        final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, null, null, null,
+                accountType, appliedonDate, appliedBy, product.nominalAnnualInterestRate(), product.interestCompoundingPeriodType(),
+                product.interestPostingPeriodType(), product.interestCalculationType(), product.interestCalculationDaysInYearType(),
+                product.minRequiredOpeningBalance(), product.lockinPeriodFrequency(), product.lockinPeriodFrequencyType(),
+                product.isWithdrawalFeeApplicableForTransfer(), charges, product.isAllowOverdraft(), product.overdraftLimit(),
+                product.isMinRequiredBalanceEnforced(), product.minRequiredBalance(), product.nominalAnnualInterestRateOverdraft(),
+                product.minOverdraftForInterestCalculation());
+        account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
+
+        account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        account.validateAccountValuesWithProduct();
+
+        return account;
+    }
+
+    public void assignSavingAccountHelpers(final SavingsAccount savingsAccount) {
+        savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java
new file mode 100644
index 0000000..395797c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java
@@ -0,0 +1,854 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dateFormatParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+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.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.exception.SavingsAccountChargeWithoutMandatoryFieldException;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+/**
+ * @author dv6
+ * 
+ */
+@Entity
+@Table(name = "m_savings_account_charge")
+public class SavingsAccountCharge extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "savings_account_id", referencedColumnName = "id", nullable = false)
+    private SavingsAccount savingsAccount;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "charge_id", referencedColumnName = "id", nullable = false)
+    private Charge charge;
+
+    @Column(name = "charge_time_enum", nullable = false)
+    private Integer chargeTime;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "charge_due_date")
+    private Date dueDate;
+
+    @Column(name = "fee_on_month", nullable = true)
+    private Integer feeOnMonth;
+
+    @Column(name = "fee_on_day", nullable = true)
+    private Integer feeOnDay;
+
+    @Column(name = "fee_interval", nullable = true)
+    private Integer feeInterval;
+
+    @Column(name = "charge_calculation_enum")
+    private Integer chargeCalculation;
+
+    @Column(name = "calculation_percentage", scale = 6, precision = 19, nullable = true)
+    private BigDecimal percentage;
+
+    // TODO AA: This field may not require for savings charges
+    @Column(name = "calculation_on_amount", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountPercentageAppliedTo;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountPaid;
+
+    @Column(name = "amount_waived_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountWaived;
+
+    @Column(name = "amount_writtenoff_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal amountWrittenOff;
+
+    @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amountOutstanding;
+
+    @Column(name = "is_penalty", nullable = false)
+    private boolean penaltyCharge = false;
+
+    @Column(name = "is_paid_derived", nullable = false)
+    private boolean paid = false;
+
+    @Column(name = "waived", nullable = false)
+    private boolean waived = false;
+
+    @Column(name = "is_active", nullable = false)
+    private boolean status = true;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "inactivated_on_date")
+    private Date inactivationDate;
+
+    public static SavingsAccountCharge createNewFromJson(final SavingsAccount savingsAccount, final Charge chargeDefinition,
+            final JsonCommand command) {
+
+        BigDecimal amount = command.bigDecimalValueOfParameterNamed(amountParamName);
+        final LocalDate dueDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
+        MonthDay feeOnMonthDay = command.extractMonthDayNamed(feeOnMonthDayParamName);
+        Integer feeInterval = command.integerValueOfParameterNamed(feeIntervalParamName);
+        final ChargeTimeType chargeTime = null;
+        final ChargeCalculationType chargeCalculation = null;
+        final boolean status = true;
+
+        // If these values is not sent as parameter, then derive from Charge
+        // definition
+        amount = (amount == null) ? chargeDefinition.getAmount() : amount;
+        feeOnMonthDay = (feeOnMonthDay == null) ? chargeDefinition.getFeeOnMonthDay() : feeOnMonthDay;
+        feeInterval = (feeInterval == null) ? chargeDefinition.getFeeInterval() : feeInterval;
+
+        return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status,
+                feeOnMonthDay, feeInterval);
+    }
+
+    public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amountPayable,
+            final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status,
+            final MonthDay feeOnMonthDay, final Integer feeInterval) {
+        return new SavingsAccountCharge(null, chargeDefinition, amountPayable, chargeTime, chargeCalculation, dueDate, status,
+                feeOnMonthDay, feeInterval);
+    }
+
+    protected SavingsAccountCharge() {
+        //
+    }
+
+    private SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount,
+            final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status,
+            MonthDay feeOnMonthDay, final Integer feeInterval) {
+
+        this.savingsAccount = savingsAccount;
+        this.charge = chargeDefinition;
+        this.penaltyCharge = chargeDefinition.isPenalty();
+        this.chargeTime = (chargeTime == null) ? chargeDefinition.getChargeTimeType() : chargeTime.getValue();
+
+        if (isOnSpecifiedDueDate()) {
+            if (dueDate == null) {
+                final String defaultUserMessage = "Savings Account charge is missing due date.";
+                throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName,
+                        defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName());
+            }
+
+        }
+
+        if (isAnnualFee() || isMonthlyFee()) {
+            feeOnMonthDay = (feeOnMonthDay == null) ? chargeDefinition.getFeeOnMonthDay() : feeOnMonthDay;
+            if (feeOnMonthDay == null) {
+                final String defaultUserMessage = "Savings Account charge is missing due date.";
+                throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName,
+                        defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName());
+            }
+
+            this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
+            this.feeOnDay = feeOnMonthDay.getDayOfMonth();
+
+        } else if (isWeeklyFee()) {
+            if (dueDate == null) {
+                final String defaultUserMessage = "Savings Account charge is missing due date.";
+                throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName,
+                        defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName());
+            }
+            /**
+             * For Weekly fee feeOnDay is ISO standard day of the week.
+             * Monday=1, Tuesday=2
+             */
+            this.feeOnDay = dueDate.getDayOfWeek();
+        } else {
+            this.feeOnDay = null;
+            this.feeOnMonth = null;
+            this.feeInterval = null;
+        }
+
+        if (isMonthlyFee() || isWeeklyFee()) {
+            this.feeInterval = (feeInterval == null) ? chargeDefinition.feeInterval() : feeInterval;
+        }
+
+        this.dueDate = (dueDate == null) ? null : dueDate.toDate();
+
+        this.chargeCalculation = chargeDefinition.getChargeCalculation();
+        if (chargeCalculation != null) {
+            this.chargeCalculation = chargeCalculation.getValue();
+        }
+
+        BigDecimal chargeAmount = chargeDefinition.getAmount();
+        if (amount != null) {
+            chargeAmount = amount;
+        }
+
+        final BigDecimal transactionAmount = new BigDecimal(0);
+
+        populateDerivedFields(transactionAmount, chargeAmount);
+
+        if (this.isWithdrawalFee()) {
+            this.amountOutstanding = BigDecimal.ZERO;
+        }
+
+        this.paid = determineIfFullyPaid();
+        this.status = status;
+    }
+
+    public void resetPropertiesForRecurringFees() {
+        if (isMonthlyFee() || isAnnualFee() || isWeeklyFee()) {
+            // FIXME: AA: If charge is percentage of x amount then need to
+            // update amount outstanding accordingly.
+            // Right now annual and monthly charges supports charge calculation
+            // type flat.
+            this.amountOutstanding = this.amount;
+            this.paid = false;// reset to false for recurring fee.
+            this.waived = false;
+        }
+    }
+
+    private void populateDerivedFields(final BigDecimal transactionAmount, final BigDecimal chargeAmount) {
+
+        switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
+            case INVALID:
+                this.percentage = null;
+                this.amount = null;
+                this.amountPercentageAppliedTo = null;
+                this.amountPaid = null;
+                this.amountOutstanding = BigDecimal.ZERO;
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+            case FLAT:
+                this.percentage = null;
+                this.amount = chargeAmount;
+                this.amountPercentageAppliedTo = null;
+                this.amountPaid = null;
+                this.amountOutstanding = chargeAmount;
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+            case PERCENT_OF_AMOUNT:
+                this.percentage = chargeAmount;
+                this.amountPercentageAppliedTo = transactionAmount;
+                this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage);
+                this.amountPaid = null;
+                this.amountOutstanding = calculateOutstanding();
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+            case PERCENT_OF_AMOUNT_AND_INTEREST:
+                this.percentage = null;
+                this.amount = null;
+                this.amountPercentageAppliedTo = null;
+                this.amountPaid = null;
+                this.amountOutstanding = BigDecimal.ZERO;
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+            case PERCENT_OF_INTEREST:
+                this.percentage = null;
+                this.amount = null;
+                this.amountPercentageAppliedTo = null;
+                this.amountPaid = null;
+                this.amountOutstanding = BigDecimal.ZERO;
+                this.amountWaived = null;
+                this.amountWrittenOff = null;
+            break;
+        }
+    }
+
+    public void markAsFullyPaid() {
+        this.amountPaid = this.amount;
+        this.amountOutstanding = BigDecimal.ZERO;
+        this.paid = true;
+    }
+
+    public void resetToOriginal(final MonetaryCurrency currency) {
+        this.amountPaid = BigDecimal.ZERO;
+        this.amountWaived = BigDecimal.ZERO;
+        this.amountWrittenOff = BigDecimal.ZERO;
+        this.amountOutstanding = calculateAmountOutstanding(currency);
+        this.paid = false;
+        this.waived = false;
+    }
+
+    public void undoPayment(final MonetaryCurrency currency, final Money transactionAmount) {
+        Money amountPaid = getAmountPaid(currency);
+        amountPaid = amountPaid.minus(transactionAmount);
+        this.amountPaid = amountPaid.getAmount();
+        this.amountOutstanding = calculateAmountOutstanding(currency);
+
+        if (this.isWithdrawalFee()) {
+            this.amountOutstanding = BigDecimal.ZERO;
+        }
+        // to reset amount outstanding for annual and monthly fee
+        resetPropertiesForRecurringFees();
+        updateToPreviousDueDate();// reset annual and monthly due date.
+        this.paid = false;
+        this.status = true;
+    }
+
+    public Money waive(final MonetaryCurrency currency) {
+        Money amountWaivedToDate = Money.of(currency, this.amountWaived);
+        Money amountOutstanding = Money.of(currency, this.amountOutstanding);
+        this.amountWaived = amountWaivedToDate.plus(amountOutstanding).getAmount();
+        this.amountOutstanding = BigDecimal.ZERO;
+        this.waived = true;
+
+        resetPropertiesForRecurringFees();
+        updateNextDueDateForRecurringFees();
+
+        return amountOutstanding;
+    }
+
+    public void undoWaiver(final MonetaryCurrency currency, final Money transactionAmount) {
+        Money amountWaived = getAmountWaived(currency);
+        amountWaived = amountWaived.minus(transactionAmount);
+        this.amountWaived = amountWaived.getAmount();
+        this.amountOutstanding = calculateAmountOutstanding(currency);
+        this.waived = false;
+        this.status = true;
+
+        resetPropertiesForRecurringFees();
+        updateToPreviousDueDate();
+    }
+
+    public Money pay(final MonetaryCurrency currency, final Money amountPaid) {
+        Money amountPaidToDate = Money.of(currency, this.amountPaid);
+        Money amountOutstanding = Money.of(currency, this.amountOutstanding);
+        amountPaidToDate = amountPaidToDate.plus(amountPaid);
+        amountOutstanding = amountOutstanding.minus(amountPaid);
+        this.amountPaid = amountPaidToDate.getAmount();
+        this.amountOutstanding = amountOutstanding.getAmount();
+        this.paid = determineIfFullyPaid();
+
+        if (BigDecimal.ZERO.compareTo(this.amountOutstanding) == 0) {
+            // full outstanding is paid, update to next due date
+            updateNextDueDateForRecurringFees();
+            resetPropertiesForRecurringFees();
+        }
+
+        return Money.of(currency, this.amountOutstanding);
+    }
+
+    private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
+        return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
+    }
+
+    public void update(final SavingsAccount savingsAccount) {
+        this.savingsAccount = savingsAccount;
+    }
+
+    public void update(final BigDecimal amount, final LocalDate dueDate, final MonthDay feeOnMonthDay, final Integer feeInterval) {
+        final BigDecimal transactionAmount = BigDecimal.ZERO;
+        if (dueDate != null) {
+            this.dueDate = dueDate.toDate();
+            if (isWeeklyFee()) {
+                this.feeOnDay = dueDate.getDayOfWeek();
+            }
+        }
+
+        if (feeOnMonthDay != null) {
+            this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
+            this.feeOnDay = feeOnMonthDay.getDayOfMonth();
+        }
+
+        if (feeInterval != null) {
+            this.feeInterval = feeInterval;
+        }
+
+        if (amount != null) {
+            switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
+                case INVALID:
+                break;
+                case FLAT:
+                    this.amount = amount;
+                break;
+                case PERCENT_OF_AMOUNT:
+                    this.percentage = amount;
+                    this.amountPercentageAppliedTo = transactionAmount;
+                    this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage);
+                    this.amountOutstanding = calculateOutstanding();
+                break;
+                case PERCENT_OF_AMOUNT_AND_INTEREST:
+                    this.percentage = amount;
+                    this.amount = null;
+                    this.amountPercentageAppliedTo = null;
+                    this.amountOutstanding = null;
+                break;
+                case PERCENT_OF_INTEREST:
+                    this.percentage = amount;
+                    this.amount = null;
+                    this.amountPercentageAppliedTo = null;
+                    this.amountOutstanding = null;
+                break;
+            }
+        }
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String dateFormatAsInput = command.dateFormat();
+        final String localeAsInput = command.locale();
+
+        if (command.isChangeInLocalDateParameterNamed(dueAsOfDateParamName, getDueLocalDate())) {
+            final String valueAsInput = command.stringValueOfParameterNamed(dueAsOfDateParamName);
+            actualChanges.put(dueAsOfDateParamName, valueAsInput);
+            actualChanges.put(dateFormatParamName, dateFormatAsInput);
+            actualChanges.put(localeParamName, localeAsInput);
+
+            final LocalDate newValue = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
+            this.dueDate = newValue.toDate();
+            if (this.isWeeklyFee()) {
+                this.feeOnDay = newValue.getDayOfWeek();
+            }
+        }
+
+        if (command.hasParameter(feeOnMonthDayParamName)) {
+            final MonthDay monthDay = command.extractMonthDayNamed(feeOnMonthDayParamName);
+            final String actualValueEntered = command.stringValueOfParameterNamed(feeOnMonthDayParamName);
+            final Integer dayOfMonthValue = monthDay.getDayOfMonth();
+            if (this.feeOnDay != dayOfMonthValue) {
+                actualChanges.put(feeOnMonthDayParamName, actualValueEntered);
+                actualChanges.put(localeParamName, localeAsInput);
+                this.feeOnDay = dayOfMonthValue;
+            }
+
+            final Integer monthOfYear = monthDay.getMonthOfYear();
+            if (this.feeOnMonth != monthOfYear) {
+                actualChanges.put(feeOnMonthDayParamName, actualValueEntered);
+                actualChanges.put(localeParamName, localeAsInput);
+                this.feeOnMonth = monthOfYear;
+            }
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
+            actualChanges.put(amountParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
+                case INVALID:
+                break;
+                case FLAT:
+                    this.amount = newValue;
+                    this.amountOutstanding = calculateOutstanding();
+                break;
+                case PERCENT_OF_AMOUNT:
+                    this.percentage = newValue;
+                    this.amountPercentageAppliedTo = null;
+                    this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage);
+                    this.amountOutstanding = calculateOutstanding();
+                break;
+                case PERCENT_OF_AMOUNT_AND_INTEREST:
+                    this.percentage = newValue;
+                    this.amount = null;
+                    this.amountPercentageAppliedTo = null;
+                    this.amountOutstanding = null;
+                break;
+                case PERCENT_OF_INTEREST:
+                    this.percentage = newValue;
+                    this.amount = null;
+                    this.amountPercentageAppliedTo = null;
+                    this.amountOutstanding = null;
+                break;
+            }
+        }
+
+        return actualChanges;
+    }
+
+    private boolean isGreaterThanZero(final BigDecimal value) {
+        return value.compareTo(BigDecimal.ZERO) == 1;
+    }
+
+    public LocalDate getDueLocalDate() {
+        LocalDate dueDate = null;
+        if (this.dueDate != null) {
+            dueDate = new LocalDate(this.dueDate);
+        }
+        return dueDate;
+    }
+
+    private boolean determineIfFullyPaid() {
+        return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
+    }
+
+    private BigDecimal calculateOutstanding() {
+
+        BigDecimal amountPaidLocal = BigDecimal.ZERO;
+        if (this.amountPaid != null) {
+            amountPaidLocal = this.amountPaid;
+        }
+
+        BigDecimal amountWaivedLocal = BigDecimal.ZERO;
+        if (this.amountWaived != null) {
+            amountWaivedLocal = this.amountWaived;
+        }
+
+        BigDecimal amountWrittenOffLocal = BigDecimal.ZERO;
+        if (this.amountWrittenOff != null) {
+            amountWrittenOffLocal = this.amountWrittenOff;
+        }
+
+        final BigDecimal totalAccountedFor = amountPaidLocal.add(amountWaivedLocal).add(amountWrittenOffLocal);
+
+        return this.amount.subtract(totalAccountedFor);
+    }
+
+    private BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage) {
+
+        BigDecimal percentageOf = BigDecimal.ZERO;
+
+        if (isGreaterThanZero(value)) {
+            final MathContext mc = new MathContext(8, MoneyHelper.getRoundingMode());
+            final BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100l), mc);
+            percentageOf = value.multiply(multiplicand, mc);
+        }
+
+        return percentageOf;
+    }
+
+    public BigDecimal amount() {
+        return this.amount;
+    }
+
+    public BigDecimal amoutOutstanding() {
+        return this.amountOutstanding;
+    }
+
+    public boolean isFeeCharge() {
+        return !this.penaltyCharge;
+    }
+
+    public boolean isPenaltyCharge() {
+        return this.penaltyCharge;
+    }
+
+    public boolean isNotFullyPaid() {
+        return !isPaid();
+    }
+
+    public boolean isPaid() {
+        return this.paid;
+    }
+
+    public boolean isWaived() {
+        return this.waived;
+    }
+
+    public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) {
+
+        final Money amountWaivedOrWrittenOff = getAmountWaived(currency).plus(getAmountWrittenOff(currency));
+        return Money.of(currency, this.amountPaid).plus(amountWaivedOrWrittenOff).isGreaterThanZero();
+    }
+
+    public Money getAmount(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amount);
+    }
+
+    private Money getAmountPaid(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountPaid);
+    }
+
+    public Money getAmountWaived(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountWaived);
+    }
+
+    public Money getAmountWrittenOff(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountWrittenOff);
+    }
+
+    public Money getAmountOutstanding(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amountOutstanding);
+    }
+
+    /**
+     * @param incrementBy
+     *            Amount used to pay off this charge
+     * @return Actual amount paid on this charge
+     */
+    public Money updatePaidAmountBy(final Money incrementBy) {
+
+        Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
+        final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding);
+
+        Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency());
+        if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) {
+            amountPaidOnThisCharge = amountOutstanding;
+            amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
+            this.amountPaid = amountPaidToDate.getAmount();
+            this.amountOutstanding = BigDecimal.ZERO;
+        } else {
+            amountPaidOnThisCharge = incrementBy;
+            amountPaidToDate = amountPaidToDate.plus(incrementBy);
+            this.amountPaid = amountPaidToDate.getAmount();
+
+            final Money amountExpected = Money.of(incrementBy.getCurrency(), this.amount);
+            this.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount();
+        }
+
+        this.paid = determineIfFullyPaid();
+
+        return amountPaidOnThisCharge;
+    }
+
+    public String name() {
+        return this.charge.getName();
+    }
+
+    public String currencyCode() {
+        return this.charge.getCurrencyCode();
+    }
+
+    public Charge getCharge() {
+        return this.charge;
+    }
+
+    public SavingsAccount savingsAccount() {
+        return this.savingsAccount;
+    }
+
+    public boolean isOnSpecifiedDueDate() {
+        return ChargeTimeType.fromInt(this.chargeTime).isOnSpecifiedDueDate();
+    }
+
+    public boolean isSavingsActivation() {
+        return ChargeTimeType.fromInt(this.chargeTime).isSavingsActivation();
+    }
+
+    public boolean isSavingsClosure() {
+        return ChargeTimeType.fromInt(this.chargeTime).isSavingsClosure();
+    }
+
+    public boolean isWithdrawalFee() {
+        return ChargeTimeType.fromInt(this.chargeTime).isWithdrawalFee();
+    }
+
+    public boolean isOverdraftFee() {
+        return ChargeTimeType.fromInt(this.chargeTime).isOverdraftFee();
+    }
+
+    public boolean isAnnualFee() {
+        return ChargeTimeType.fromInt(this.chargeTime).isAnnualFee();
+    }
+
+    public boolean isMonthlyFee() {
+        return ChargeTimeType.fromInt(this.chargeTime).isMonthlyFee();
+    }
+
+    public boolean isWeeklyFee() {
+        return ChargeTimeType.fromInt(this.chargeTime).isWeeklyFee();
+    }
+
+    public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) {
+        if (this.currencyCode() == null || matchingCurrencyCode == null) { return false; }
+        return this.currencyCode().equalsIgnoreCase(matchingCurrencyCode);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) { return false; }
+        if (obj == this) { return true; }
+        if (obj.getClass() != getClass()) { return false; }
+        final SavingsAccountCharge rhs = (SavingsAccountCharge) obj;
+        return new EqualsBuilder().appendSuper(super.equals(obj)) //
+                .append(getId(), rhs.getId()) //
+                .append(this.charge.getId(), rhs.charge.getId()) //
+                .append(this.amount, rhs.amount) //
+                .append(getDueLocalDate(), rhs.getDueLocalDate()) //
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(3, 5) //
+                .append(getId()) //
+                .append(this.charge.getId()) //
+                .append(this.amount).append(getDueLocalDate()) //
+                .toHashCode();
+    }
+
+    public BigDecimal updateWithdralFeeAmount(final BigDecimal transactionAmount) {
+        BigDecimal amountPaybale = BigDecimal.ZERO;
+        if (ChargeCalculationType.fromInt(this.chargeCalculation).isFlat()) {
+            amountPaybale = this.amount;
+        } else if (ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfAmount()) {
+            amountPaybale = transactionAmount.multiply(this.percentage).divide(BigDecimal.valueOf(100l));
+        }
+        this.amountOutstanding = amountPaybale;
+        return amountPaybale;
+    }
+
+    public void updateToNextDueDateFrom(final LocalDate startingDate) {
+        if (isAnnualFee() || isMonthlyFee() || isWeeklyFee()) {
+            this.dueDate = getNextDueDateFrom(startingDate).toDate();
+        }
+    }
+
+    public LocalDate getNextDueDateFrom(final LocalDate startingDate) {
+        LocalDate nextDueLocalDate = null;
+        if (isAnnualFee() || isMonthlyFee()) {
+            nextDueLocalDate = startingDate.withMonthOfYear(this.feeOnMonth);
+            nextDueLocalDate = setDayOfMonth(nextDueLocalDate);
+            while (startingDate.isAfter(nextDueLocalDate)) {
+                nextDueLocalDate = calculateNextDueDate(nextDueLocalDate);
+            }
+        } else if (isWeeklyFee()) {
+            nextDueLocalDate = getDueLocalDate();
+            while (startingDate.isAfter(nextDueLocalDate)) {
+                nextDueLocalDate = calculateNextDueDate(nextDueLocalDate);
+            }
+        } else {
+            nextDueLocalDate = calculateNextDueDate(startingDate);
+        }
+        return nextDueLocalDate;
+    }
+
+    private LocalDate calculateNextDueDate(final LocalDate date) {
+        LocalDate nextDueLocalDate = null;
+        if (isAnnualFee()) {
+            nextDueLocalDate = date.withMonthOfYear(this.feeOnMonth).plusYears(1);
+            nextDueLocalDate = setDayOfMonth(nextDueLocalDate);
+        } else if (isMonthlyFee()) {
+            nextDueLocalDate = date.plusMonths(this.feeInterval);
+            nextDueLocalDate = setDayOfMonth(nextDueLocalDate);
+        } else if (isWeeklyFee()) {
+            nextDueLocalDate = date.plusWeeks(this.feeInterval);
+            nextDueLocalDate = setDayOfWeek(nextDueLocalDate);
+        }
+        return nextDueLocalDate;
+    }
+
+    private LocalDate setDayOfMonth(LocalDate nextDueLocalDate) {
+        int maxDayOfMonth = nextDueLocalDate.dayOfMonth().withMaximumValue().getDayOfMonth();
+        int newDayOfMonth = (this.feeOnDay.intValue() < maxDayOfMonth) ? this.feeOnDay.intValue() : maxDayOfMonth;
+        nextDueLocalDate = nextDueLocalDate.withDayOfMonth(newDayOfMonth);
+        return nextDueLocalDate;
+    }
+
+    private LocalDate setDayOfWeek(LocalDate nextDueLocalDate) {
+        if (this.feeOnDay != nextDueLocalDate.getDayOfWeek()) {
+            nextDueLocalDate = nextDueLocalDate.withDayOfWeek(this.feeOnDay);
+        }
+        return nextDueLocalDate;
+    }
+
+    public void updateNextDueDateForRecurringFees() {
+        if (isAnnualFee() || isMonthlyFee() || isWeeklyFee()) {
+            LocalDate nextDueLocalDate = new LocalDate(dueDate);
+            nextDueLocalDate = calculateNextDueDate(nextDueLocalDate);
+            this.dueDate = nextDueLocalDate.toDate();
+        }
+    }
+
+    public void updateToPreviousDueDate() {
+        if (isAnnualFee() || isMonthlyFee() || isWeeklyFee()) {
+            LocalDate nextDueLocalDate = new LocalDate(dueDate);
+            if (isAnnualFee()) {
+                nextDueLocalDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).minusYears(1);
+                nextDueLocalDate = setDayOfMonth(nextDueLocalDate);
+            } else if (isMonthlyFee()) {
+                nextDueLocalDate = nextDueLocalDate.minusMonths(this.feeInterval);
+                nextDueLocalDate = setDayOfMonth(nextDueLocalDate);
+            } else if (isWeeklyFee()) {
+                nextDueLocalDate = nextDueLocalDate.minusDays(7 * this.feeInterval);
+                nextDueLocalDate = setDayOfWeek(nextDueLocalDate);
+            }
+
+            this.dueDate = nextDueLocalDate.toDate();
+        }
+    }
+
+    public boolean feeSettingsNotSet() {
+        return !feeSettingsSet();
+    }
+
+    public boolean feeSettingsSet() {
+        return this.feeOnDay != null && this.feeOnMonth != null;
+    }
+
+    public boolean isRecurringFee() {
+        return isWeeklyFee() || isMonthlyFee() || isAnnualFee();
+    }
+
+    public boolean isChargeIsDue(final LocalDate nextDueDate) {
+        return this.getDueLocalDate().isBefore(nextDueDate);
+    }
+
+    public boolean isChargeIsOverPaid(final LocalDate nextDueDate) {
+        final BigDecimal amountPaid = this.amountPaid == null ? BigDecimal.ZERO : amountPaid();
+        return this.getDueLocalDate().isAfter(nextDueDate) && amountPaid.compareTo(BigDecimal.ZERO) == 1;
+    }
+
+    private BigDecimal amountPaid() {
+        return this.amountPaid;
+    }
+
+    public void inactiavateCharge(final LocalDate inactivationOnDate) {
+        this.inactivationDate = inactivationOnDate.toDate();
+        this.status = false;
+        this.amountOutstanding = BigDecimal.ZERO;
+        this.paid = true;
+    }
+
+    public boolean isActive() {
+        return this.status;
+    }
+
+    public boolean isNotActive() {
+        return !isActive();
+    }
+
+    /**
+     * This method is to identify the charges which can override the savings
+     * rules(for example if there is a minimum enforced balance of 1000 on
+     * savings account with account balance of 1000, still these charges can be
+     * collected as these charges are initiated by system and it can bring down
+     * the balance below the enforced minimum balance).
+     * 
+     */
+    public boolean canOverriteSavingAccountRules() {
+        return !(this.isSavingsActivation() || this.isWithdrawalFee());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargeAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargeAssembler.java
new file mode 100644
index 0000000..87a6ef8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargeAssembler.java
@@ -0,0 +1,197 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.idParamName;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeAppliedToException;
+import org.apache.fineract.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class SavingsAccountChargeAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final ChargeRepositoryWrapper chargeRepository;
+    private final SavingsAccountChargeRepository savingsAccountChargeRepository;
+
+    @Autowired
+    public SavingsAccountChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
+            final SavingsAccountChargeRepository savingsAccountChargeRepository) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.chargeRepository = chargeRepository;
+        this.savingsAccountChargeRepository = savingsAccountChargeRepository;
+    }
+
+    public Set<SavingsAccountCharge> fromParsedJson(final JsonElement element, final String productCurrencyCode) {
+
+        final Set<SavingsAccountCharge> savingsAccountCharges = new HashSet<>();
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
+            final String monthDayFormat = this.fromApiJsonHelper.extractMonthDayFormatParameter(topLevelJsonElement);
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+
+                    final JsonObject savingsChargeElement = array.get(i).getAsJsonObject();
+
+                    final Long id = this.fromApiJsonHelper.extractLongNamed(idParamName, savingsChargeElement);
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement);
+                    final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale);
+                    final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed(chargeTimeTypeParamName,
+                            savingsChargeElement, locale);
+                    final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed(chargeCalculationTypeParamName,
+                            savingsChargeElement, locale);
+                    final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement,
+                            dateFormat, locale);
+
+                    final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName,
+                            savingsChargeElement, monthDayFormat, locale);
+                    final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, savingsChargeElement,
+                            locale);
+
+                    if (id == null) {
+                        final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
+
+                        if (!chargeDefinition.isSavingsCharge()) {
+                            final String errorMessage = "Charge with identifier " + chargeDefinition.getId()
+                                    + " cannot be applied to Savings product.";
+                            throw new ChargeCannotBeAppliedToException("savings.product", errorMessage, chargeDefinition.getId());
+                        }
+
+                        ChargeTimeType chargeTime = null;
+                        if (chargeTimeType != null) {
+                            chargeTime = ChargeTimeType.fromInt(chargeTimeType);
+                        }
+
+                        ChargeCalculationType chargeCalculation = null;
+                        if (chargeCalculationType != null) {
+                            chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType);
+                        }
+
+                        final boolean status = true;
+                        final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(
+                                chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, feeOnMonthDay, feeInterval);
+                        savingsAccountCharges.add(savingsAccountCharge);
+                    } else {
+                        final Long savingsAccountChargeId = id;
+                        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                                .findOne(savingsAccountChargeId);
+                        if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); }
+
+                        savingsAccountCharge.update(amount, dueDate, feeOnMonthDay, feeInterval);
+
+                        savingsAccountCharges.add(savingsAccountCharge);
+                    }
+                }
+            }
+        }
+
+        this.validateSavingsCharges(savingsAccountCharges, productCurrencyCode);
+        return savingsAccountCharges;
+    }
+
+    public Set<SavingsAccountCharge> fromSavingsProduct(final SavingsProduct savingsProduct) {
+
+        final Set<SavingsAccountCharge> savingsAccountCharges = new HashSet<>();
+        Set<Charge> productCharges = savingsProduct.charges();
+        for (Charge charge : productCharges) {
+            ChargeTimeType chargeTime = null;
+            if (charge.getChargeTimeType() != null) {
+                chargeTime = ChargeTimeType.fromInt(charge.getChargeTimeType());
+            }
+            if (chargeTime != null && chargeTime.isOnSpecifiedDueDate()) {
+                continue;
+            }
+
+            ChargeCalculationType chargeCalculation = null;
+            if (charge.getChargeCalculation() != null) {
+                chargeCalculation = ChargeCalculationType.fromInt(charge.getChargeCalculation());
+            }
+            final boolean status = true;
+            final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(charge,
+                    charge.getAmount(), chargeTime, chargeCalculation, null, status, charge.getFeeOnMonthDay(), charge.feeInterval());
+            savingsAccountCharges.add(savingsAccountCharge);
+        }
+        return savingsAccountCharges;
+    }
+
+    private void validateSavingsCharges(final Set<SavingsAccountCharge> charges, final String productCurrencyCode) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+        boolean isOneWithdrawalPresent = false;
+        boolean isOneAnnualPresent = false;
+        for (SavingsAccountCharge charge : charges) {
+            if (!charge.hasCurrencyCodeOf(productCurrencyCode)) {
+                baseDataValidator.reset().parameter("currency").value(charge.getCharge().getId())
+                        .failWithCodeNoParameterAddedToErrorCode("currency.and.charge.currency.not.same");
+            }
+
+            if (charge.isWithdrawalFee()) {
+                if (isOneWithdrawalPresent) {
+                    baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("multiple.withdrawal.fee.per.account.not.supported");
+                }
+                isOneWithdrawalPresent = true;
+            }
+
+            if (charge.isAnnualFee()) {
+                if (isOneAnnualPresent) {
+                    baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("multiple.annual.fee.per.account.not.supported");
+                }
+                isOneAnnualPresent = true;
+            }
+        }
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargePaidBy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargePaidBy.java
new file mode 100644
index 0000000..0d0bb17
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargePaidBy.java
@@ -0,0 +1,97 @@
+/**
+ * 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.savings.domain;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_savings_account_charge_paid_by")
+public class SavingsAccountChargePaidBy extends AbstractPersistable<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "savings_account_transaction_id", nullable = false)
+    private SavingsAccountTransaction savingsAccountTransaction;
+
+    @ManyToOne
+    @JoinColumn(name = "savings_account_charge_id", nullable = false)
+    private SavingsAccountCharge savingsAccountCharge;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    protected SavingsAccountChargePaidBy() {
+
+    }
+
+    public static SavingsAccountChargePaidBy instance(final SavingsAccountTransaction savingsAccountTransaction,
+            final SavingsAccountCharge savingsAccountCharge, final BigDecimal amount) {
+        return new SavingsAccountChargePaidBy(savingsAccountTransaction, savingsAccountCharge, amount);
+    }
+
+    private SavingsAccountChargePaidBy(final SavingsAccountTransaction savingsAccountTransaction,
+            final SavingsAccountCharge savingsAccountCharge, final BigDecimal amount) {
+        this.savingsAccountTransaction = savingsAccountTransaction;
+        this.savingsAccountCharge = savingsAccountCharge;
+        this.amount = amount;
+    }
+
+    public SavingsAccountTransaction getSavingsAccountTransaction() {
+        return this.savingsAccountTransaction;
+    }
+
+    public void setSavingsAccountTransaction(final SavingsAccountTransaction savingsAccountTransaction) {
+        this.savingsAccountTransaction = savingsAccountTransaction;
+    }
+
+    public SavingsAccountCharge getSavingsAccountCharge() {
+        return this.savingsAccountCharge;
+    }
+
+    public void setSavingsAccountCharge(final SavingsAccountCharge savingsAccountCharge) {
+        this.savingsAccountCharge = savingsAccountCharge;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+    public void setAmount(final BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public boolean isFeeCharge() {
+        return (this.savingsAccountCharge == null) ? false : this.savingsAccountCharge.isFeeCharge();
+    }
+
+    public boolean isPenaltyCharge() {
+        return (this.savingsAccountCharge == null) ? false : this.savingsAccountCharge.isPenaltyCharge();
+    }
+
+    public boolean canOverriteSavingAccountRules() {
+        return (this.savingsAccountCharge == null) ? false : this.savingsAccountCharge.canOverriteSavingAccountRules();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargeRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargeRepository.java
new file mode 100644
index 0000000..bc0652a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargeRepository.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.savings.domain;
+
+import java.util.Date;
+import java.util.List;
+
+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 SavingsAccountChargeRepository extends JpaRepository<SavingsAccountCharge, Long>,
+        JpaSpecificationExecutor<SavingsAccountCharge> {
+
+    SavingsAccountCharge findByIdAndSavingsAccountId(Long id, Long savingsAccountId);
+
+    @Query("from SavingsAccountCharge sac where sac.dueDate <=:transactionDate and sac.waived = 0 and sac.paid=0 order by sac.dueDate")
+    List<SavingsAccountCharge> findPendingCharges(@Param("transactionDate") Date transactionDate);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java
new file mode 100644
index 0000000..d785d8d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java
@@ -0,0 +1,75 @@
+/**
+ * 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.savings.domain;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.fineract.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link SavingsAccountChargeRepository} that adds NULL checking
+ * and Error handling capabilities
+ * </p>
+ */
+@Service
+public class SavingsAccountChargeRepositoryWrapper {
+
+    private final SavingsAccountChargeRepository repository;
+
+    @Autowired
+    public SavingsAccountChargeRepositoryWrapper(final SavingsAccountChargeRepository repository) {
+        this.repository = repository;
+    }
+
+    public SavingsAccountCharge findOneWithNotFoundDetection(final Long id) {
+        final SavingsAccountCharge savingsAccountCharge = this.repository.findOne(id);
+        if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(id); }
+        return savingsAccountCharge;
+    }
+
+    public SavingsAccountCharge findOneWithNotFoundDetection(final Long id, final Long savingsAccountId) {
+        final SavingsAccountCharge savingsAccountCharge = this.repository.findByIdAndSavingsAccountId(id, savingsAccountId);
+        if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(id); }
+        return savingsAccountCharge;
+    }
+
+    public List<SavingsAccountCharge> findPendingCharges(final Date transactionDate) {
+        return this.repository.findPendingCharges(transactionDate);
+    }
+
+    public void save(final SavingsAccountCharge savingsAccountCharge) {
+        this.repository.save(savingsAccountCharge);
+    }
+
+    public void save(final Iterable<SavingsAccountCharge> savingsAccountCharges) {
+        this.repository.save(savingsAccountCharges);
+    }
+
+    public void saveAndFlush(final SavingsAccountCharge savingsAccountCharge) {
+        this.repository.saveAndFlush(savingsAccountCharge);
+    }
+
+    public void delete(final SavingsAccountCharge savingsAccountCharge) {
+        this.repository.delete(savingsAccountCharge);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java
new file mode 100644
index 0000000..c050e7c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.savings.domain;
+
+import java.math.BigDecimal;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+
+public interface SavingsAccountDomainService {
+
+    SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
+            BigDecimal transactionAmount, PaymentDetail paymentDetail, SavingsTransactionBooleanValues transactionBooleanValues);
+
+    SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
+            BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer, boolean isRegularTransaction);
+
+    void postJournalEntries(SavingsAccount savingsAccount, Set<Long> existingTransactionIds, Set<Long> existingReversedTransactionIds);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java
new file mode 100755
index 0000000..3ce2f19
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java
@@ -0,0 +1,196 @@
+/**
+ * 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.savings.domain;
+
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
+import org.apache.fineract.portfolio.savings.exception.DepositAccountTransactionNotAllowedException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Service
+public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainService {
+
+    private final PlatformSecurityContext context;
+    private final SavingsAccountRepositoryWrapper savingsAccountRepository;
+    private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+    private final ConfigurationDomainService configurationDomainService;
+    private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
+
+    @Autowired
+    public SavingsAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository,
+            final SavingsAccountTransactionRepository savingsAccountTransactionRepository,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService,
+            final ConfigurationDomainService configurationDomainService, final PlatformSecurityContext context,
+            final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository) {
+        this.savingsAccountRepository = savingsAccountRepository;
+        this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
+        this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+        this.configurationDomainService = configurationDomainService;
+        this.context = context;
+        this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository;
+    }
+
+    @Transactional
+    @Override
+    public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
+            final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
+            final SavingsTransactionBooleanValues transactionBooleanValues) {
+
+        AppUser user = getAppUserIfPresent();
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        if (transactionBooleanValues.isRegularTransaction() && !account.allowWithdrawal()) { throw new DepositAccountTransactionNotAllowedException(
+                account.getId(), "withdraw", account.depositAccountType()); }
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
+                paymentDetail, new Date(), user);
+        final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee());
+
+        final MathContext mc = MathContext.DECIMAL64;
+        if (account.isBeforeLastPostingPeriod(transactionDate)) {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        } else {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(),
+                    isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        }
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+        account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck(), depositAccountOnHoldTransactions);
+        saveTransactionToGenerateTransactionId(withdrawal);
+        this.savingsAccountRepository.save(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer());
+
+        return withdrawal;
+    }
+
+    private AppUser getAppUserIfPresent() {
+        AppUser user = null;
+        if (this.context != null) {
+            user = this.context.getAuthenticatedUserIfPresent();
+        }
+        return user;
+    }
+
+    @Transactional
+    @Override
+    public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
+            final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
+            final boolean isAccountTransfer, final boolean isRegularTransaction) {
+
+        AppUser user = getAppUserIfPresent();
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        if (isRegularTransaction && !account.allowDeposit()) { throw new DepositAccountTransactionNotAllowedException(account.getId(),
+                "deposit", account.depositAccountType()); }
+
+        boolean isInterestTransfer = false;
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
+                paymentDetail, new Date(), user);
+        final SavingsAccountTransaction deposit = account.deposit(transactionDTO);
+
+        final MathContext mc = MathContext.DECIMAL64;
+        if (account.isBeforeLastPostingPeriod(transactionDate)) {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+
+        saveTransactionToGenerateTransactionId(deposit);
+
+        this.savingsAccountRepository.save(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+
+        return deposit;
+    }
+
+    private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) {
+        this.savingsAccountTransactionRepository.save(transaction);
+        return transaction.getId();
+    }
+
+    private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
+            Set<Long> existingReversedTransactionIds) {
+        existingTransactionIds.addAll(account.findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
+    }
+
+    private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
+            final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
+
+        final MonetaryCurrency currency = savingsAccount.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
+
+        final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
+                existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
+    }
+
+    @Transactional
+    @Override
+    public void postJournalEntries(final SavingsAccount account, final Set<Long> existingTransactionIds,
+            final Set<Long> existingReversedTransactionIds) {
+
+        final boolean isAccountTransfer = false;
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
new file mode 100644
index 0000000..b34e897
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.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.savings.domain;
+
+import java.util.List;
+
+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 SavingsAccountRepository extends JpaRepository<SavingsAccount, Long>, JpaSpecificationExecutor<SavingsAccount> {
+
+    @Query("from SavingsAccount s_acc where s_acc.client.id = :clientId")
+    List<SavingsAccount> findSavingAccountByClientId(@Param("clientId") Long clientId);
+
+    @Query("from SavingsAccount s_acc where s_acc.status = :status")
+    List<SavingsAccount> findSavingAccountByStatus(@Param("status") Integer status);
+
+    @Query("from SavingsAccount sa where sa.client.id = :clientId and sa.group.id = :groupId")
+    List<SavingsAccount> findByClientIdAndGroupId(@Param("clientId") Long clientId, @Param("groupId") Long groupId);
+
+    @Query("select case when (count (saving) > 0) then true else false end from SavingsAccount saving where saving.client.id = :clientId and saving.status in (100,200,300,303,304)")
+    boolean doNonClosedSavingAccountsExistForClient(@Param("clientId") Long clientId);
+
+    @Query("from SavingsAccount sa where sa.client.id is null and sa.group.id = :groupId")
+    List<SavingsAccount> findByGroupId(@Param("groupId") Long groupId);
+
+    @Query("from SavingsAccount sa where sa.id = :accountId and sa.depositType = :depositAccountTypeId")
+    SavingsAccount findByIdAndDepositAccountType(@Param("accountId") Long accountId,
+            @Param("depositAccountTypeId") Integer depositAccountTypeId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
new file mode 100644
index 0000000..e1a7a58
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
@@ -0,0 +1,71 @@
+/**
+ * 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.savings.domain;
+
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * Wrapper for {@link SavingsAccountRepository} that is responsible for checking
+ * if {@link SavingsAccount} is returned when using <code>findOne</code>
+ * repository method and throwing an appropriate not found exception.
+ * </p>
+ * 
+ * <p>
+ * This is to avoid need for checking and throwing in multiple areas of code
+ * base where {@link SavingsAccountRepository} is required.
+ * </p>
+ */
+@Service
+public class SavingsAccountRepositoryWrapper {
+
+    private final SavingsAccountRepository repository;
+
+    @Autowired
+    public SavingsAccountRepositoryWrapper(final SavingsAccountRepository repository) {
+        this.repository = repository;
+    }
+
+    public SavingsAccount findOneWithNotFoundDetection(final Long savingsId) {
+        final SavingsAccount account = this.repository.findOne(savingsId);
+        if (account == null) { throw new SavingsAccountNotFoundException(savingsId); }
+        return account;
+    }
+
+    public SavingsAccount findOneWithNotFoundDetection(final Long savingsId, final DepositAccountType depositAccountType) {
+        final SavingsAccount account = this.repository.findByIdAndDepositAccountType(savingsId, depositAccountType.getValue());
+        if (account == null) { throw new SavingsAccountNotFoundException(savingsId); }
+        return account;
+    }
+
+    public void save(final SavingsAccount account) {
+        this.repository.save(account);
+    }
+
+    public void delete(final SavingsAccount account) {
+        this.repository.delete(account);
+    }
+
+    public void saveAndFlush(final SavingsAccount account) {
+        this.repository.saveAndFlush(account);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountStatusType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountStatusType.java
new file mode 100644
index 0000000..45d2b15
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountStatusType.java
@@ -0,0 +1,142 @@
+/**
+ * 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.savings.domain;
+
+/**
+ * Enum representation of {@link SavingsAccount} status states.
+ */
+public enum SavingsAccountStatusType {
+
+    INVALID(0, "savingsAccountStatusType.invalid"), //
+    SUBMITTED_AND_PENDING_APPROVAL(100, "savingsAccountStatusType.submitted.and.pending.approval"), //
+    APPROVED(200, "savingsAccountStatusType.approved"), //
+    ACTIVE(300, "savingsAccountStatusType.active"), //
+    TRANSFER_IN_PROGRESS(303, "savingsAccountStatusType.transfer.in.progress"), //
+    TRANSFER_ON_HOLD(304, "savingsAccountStatusType.transfer.on.hold"), //
+    WITHDRAWN_BY_APPLICANT(400, "savingsAccountStatusType.withdrawn.by.applicant"), //
+    REJECTED(500, "savingsAccountStatusType.rejected"), //
+    CLOSED(600, "savingsAccountStatusType.closed"), PRE_MATURE_CLOSURE(700, "savingsAccountStatusType.pre.mature.closure"), MATURED(800,
+            "savingsAccountStatusType.matured");
+
+    private final Integer value;
+    private final String code;
+
+    public static SavingsAccountStatusType fromInt(final Integer type) {
+
+        SavingsAccountStatusType enumeration = SavingsAccountStatusType.INVALID;
+        switch (type) {
+            case 100:
+                enumeration = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL;
+            break;
+            case 200:
+                enumeration = SavingsAccountStatusType.APPROVED;
+            break;
+            case 300:
+                enumeration = SavingsAccountStatusType.ACTIVE;
+            break;
+            case 303:
+                enumeration = SavingsAccountStatusType.TRANSFER_IN_PROGRESS;
+            break;
+            case 304:
+                enumeration = SavingsAccountStatusType.TRANSFER_ON_HOLD;
+            break;
+            case 400:
+                enumeration = SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT;
+            break;
+            case 500:
+                enumeration = SavingsAccountStatusType.REJECTED;
+            break;
+            case 600:
+                enumeration = SavingsAccountStatusType.CLOSED;
+            break;
+            case 700:
+                enumeration = SavingsAccountStatusType.PRE_MATURE_CLOSURE;
+            break;
+            case 800:
+                enumeration = SavingsAccountStatusType.MATURED;
+            break;
+        }
+        return enumeration;
+    }
+
+    private SavingsAccountStatusType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public boolean hasStateOf(final SavingsAccountStatusType state) {
+        return this.value.equals(state.getValue());
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isSubmittedAndPendingApproval() {
+        return this.value.equals(SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue());
+    }
+
+    public boolean isApproved() {
+        return this.value.equals(SavingsAccountStatusType.APPROVED.getValue());
+    }
+
+    public boolean isRejected() {
+        return this.value.equals(SavingsAccountStatusType.REJECTED.getValue());
+    }
+
+    public boolean isApplicationWithdrawnByApplicant() {
+        return this.value.equals(SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue());
+    }
+
+    public boolean isActive() {
+        return this.value.equals(SavingsAccountStatusType.ACTIVE.getValue());
+    }
+
+    public boolean isActiveOrAwaitingApprovalOrDisbursal() {
+        return isApproved() || isSubmittedAndPendingApproval() || isActive();
+    }
+
+    public boolean isClosed() {
+        return this.value.equals(SavingsAccountStatusType.CLOSED.getValue()) || isRejected() || isApplicationWithdrawnByApplicant();
+    }
+
+    public boolean isTransferInProgress() {
+        return this.value.equals(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue());
+    }
+
+    public boolean isTransferOnHold() {
+        return this.value.equals(SavingsAccountStatusType.TRANSFER_ON_HOLD.getValue());
+    }
+
+    public boolean isUnderTransfer() {
+        return isTransferInProgress() || isTransferOnHold();
+    }
+
+    public boolean isMatured() {
+        return this.value.equals(SavingsAccountStatusType.MATURED.getValue());
+    }
+
+    public boolean isPreMatureClosure() {
+        return this.value.equals(SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java
new file mode 100644
index 0000000..5a93704
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java
@@ -0,0 +1,125 @@
+/**
+ * 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.savings.domain;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Transient;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
+
+/**
+ * {@link SavingsAccountSummary} encapsulates all the summary details of a
+ * {@link SavingsAccount}.
+ */
+@Embeddable
+public final class SavingsAccountSummary {
+
+    @Column(name = "total_deposits_derived", scale = 6, precision = 19)
+    private BigDecimal totalDeposits;
+
+    @Column(name = "total_withdrawals_derived", scale = 6, precision = 19)
+    private BigDecimal totalWithdrawals;
+
+    @Column(name = "total_interest_earned_derived", scale = 6, precision = 19)
+    private BigDecimal totalInterestEarned;
+
+    @Column(name = "total_interest_posted_derived", scale = 6, precision = 19)
+    private BigDecimal totalInterestPosted;
+
+    @Column(name = "total_withdrawal_fees_derived", scale = 6, precision = 19)
+    private BigDecimal totalWithdrawalFees;
+
+    @Column(name = "total_fees_charge_derived", scale = 6, precision = 19)
+    private BigDecimal totalFeeCharge;
+
+    @Column(name = "total_penalty_charge_derived", scale = 6, precision = 19)
+    private BigDecimal totalPenaltyCharge;
+
+    @Column(name = "total_annual_fees_derived", scale = 6, precision = 19)
+    private BigDecimal totalAnnualFees;
+
+    @Column(name = "account_balance_derived", scale = 6, precision = 19)
+    private BigDecimal accountBalance = BigDecimal.ZERO;
+
+    // TODO: AA do we need this data to be persisted.
+    @Transient
+    private BigDecimal totalFeeChargesWaived = BigDecimal.ZERO;
+
+    @Transient
+    private BigDecimal totalPenaltyChargesWaived = BigDecimal.ZERO;
+
+    @Column(name = "total_overdraft_interest_derived", scale = 6, precision = 19)
+	private BigDecimal totalOverdraftInterestDerived;
+
+    protected SavingsAccountSummary() {
+        //
+    }
+
+    public void updateSummary(final MonetaryCurrency currency, final SavingsAccountTransactionSummaryWrapper wrapper,
+            final List<SavingsAccountTransaction> transactions) {
+
+        this.totalDeposits = wrapper.calculateTotalDeposits(currency, transactions);
+        this.totalWithdrawals = wrapper.calculateTotalWithdrawals(currency, transactions);
+        this.totalInterestPosted = wrapper.calculateTotalInterestPosted(currency, transactions);
+        this.totalWithdrawalFees = wrapper.calculateTotalWithdrawalFees(currency, transactions);
+        this.totalAnnualFees = wrapper.calculateTotalAnnualFees(currency, transactions);
+        this.totalFeeCharge = wrapper.calculateTotalFeesCharge(currency, transactions);
+        this.totalPenaltyCharge = wrapper.calculateTotalPenaltyCharge(currency, transactions);
+        this.totalFeeChargesWaived = wrapper.calculateTotalFeesChargeWaived(currency, transactions);
+        this.totalPenaltyChargesWaived = wrapper.calculateTotalPenaltyChargeWaived(currency, transactions);
+        this.totalOverdraftInterestDerived = wrapper.calculateTotalOverdraftInterest(currency, transactions);
+
+        this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals)
+                .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge)
+                .minus(totalOverdraftInterestDerived).getAmount();
+    }
+
+    public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, final List<PostingPeriod> allPostingPeriods) {
+
+        Money totalEarned = Money.zero(currency);
+
+        for (final PostingPeriod period : allPostingPeriods) {
+            Money interestEarned = period.interest();
+            interestEarned = interestEarned == null ? Money.zero(currency) : interestEarned;
+            totalEarned = totalEarned.plus(interestEarned);
+        }
+
+        this.totalInterestEarned = totalEarned.getAmount();
+    }
+
+    public boolean isLessThanOrEqualToAccountBalance(final Money amount) {
+        final Money accountBalance = getAccountBalance(amount.getCurrency());
+        return accountBalance.isGreaterThanOrEqualTo(amount);
+    }
+
+    public Money getAccountBalance(final MonetaryCurrency currency) {
+        return Money.of(currency, this.accountBalance);
+    }
+
+    public BigDecimal getAccountBalance() {
+        return this.accountBalance;
+    }
+
+}
\ 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
new file mode 100644
index 0000000..d09c667
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
@@ -0,0 +1,640 @@
+/**
+ * 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.savings.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
+import org.apache.fineract.portfolio.savings.domain.interest.EndOfDayBalance;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * All monetary transactions against a savings account are modelled through this
+ * entity.
+ */
+@Entity
+@Table(name = "m_savings_account_transaction")
+public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "savings_account_id", nullable = false)
+    private SavingsAccount savingsAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @ManyToOne(optional = true)
+    @JoinColumn(name = "payment_detail_id", nullable = true)
+    private PaymentDetail paymentDetail;
+
+    @Column(name = "transaction_type_enum", nullable = false)
+    private final Integer typeOf;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "transaction_date", nullable = false)
+    private final Date dateOf;
+
+    @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "is_reversed", nullable = false)
+    private boolean reversed;
+
+    @Column(name = "running_balance_derived", scale = 6, precision = 19, nullable = false)
+    private BigDecimal runningBalance;
+
+    @Column(name = "cumulative_balance_derived", scale = 6, precision = 19, nullable = false)
+    private BigDecimal cumulativeBalance;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "balance_end_date_derived", nullable = false)
+    private Date balanceEndDate;
+
+    @Column(name = "balance_number_of_days_derived", nullable = false)
+    private Integer balanceNumberOfDays;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccountTransaction", orphanRemoval = true)
+    private final Set<SavingsAccountChargePaidBy> savingsAccountChargesPaid = new HashSet<>();
+
+    @Column(name = "overdraft_amount_derived", scale = 6, precision = 19, nullable = true)
+    private BigDecimal overdraftAmount;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "created_date", nullable = false)
+    private final Date createdDate;
+
+    @ManyToOne
+    @JoinColumn(name = "appuser_id", nullable = true)
+    private AppUser appUser;
+
+    protected SavingsAccountTransaction() {
+        this.dateOf = null;
+        this.typeOf = null;
+        this.createdDate = null;
+    }
+
+    public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final Office office,
+            final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) {
+        final boolean isReversed = false;
+        return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.DEPOSIT.getValue(), date,
+                createdDate, amount, isReversed, appUser);
+    }
+
+    public static SavingsAccountTransaction withdrawal(final SavingsAccount savingsAccount, final Office office,
+            final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) {
+        final boolean isReversed = false;
+        return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.WITHDRAWAL.getValue(),
+                date, createdDate, amount, isReversed, appUser);
+    }
+
+    public static SavingsAccountTransaction interestPosting(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
+            final Money amount) {
+        final boolean isReversed = false;
+        return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.INTEREST_POSTING.getValue(), date,
+                amount, isReversed, null);
+    }
+
+	public static SavingsAccountTransaction overdraftInterest(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
+            final Money amount) {
+        final boolean isReversed = false;
+        return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue(), date,
+                amount, isReversed, null);
+	}
+
+	public static SavingsAccountTransaction withdrawalFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
+            final Money amount, final AppUser appUser) {
+        final boolean isReversed = false;
+        return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue(), date, amount,
+                isReversed, appUser);
+    }
+
+    public static SavingsAccountTransaction annualFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
+            final Money amount, final AppUser appUser) {
+        final boolean isReversed = false;
+        return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount,
+                isReversed, appUser);
+    }
+
+    public static SavingsAccountTransaction charge(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
+            final Money amount, final AppUser appUser) {
+        final boolean isReversed = false;
+        return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.PAY_CHARGE.getValue(), date, amount,
+                isReversed, appUser);
+    }
+
+    public static SavingsAccountTransaction waiver(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
+            final Money amount, final AppUser appUser) {
+        final boolean isReversed = false;
+        return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WAIVE_CHARGES.getValue(), date, amount,
+                isReversed, appUser);
+    }
+
+    public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office,
+            final LocalDate date, final AppUser appUser) {
+        final boolean isReversed = false;
+        final PaymentDetail paymentDetail = null;
+        return new SavingsAccountTransaction(savingsAccount, office, paymentDetail,
+                SavingsAccountTransactionType.INITIATE_TRANSFER.getValue(), date, new Date(), savingsAccount.getSummary()
+                        .getAccountBalance(), isReversed, appUser);
+    }
+
+    public static SavingsAccountTransaction approveTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
+            final AppUser appUser) {
+        final boolean isReversed = false;
+        final PaymentDetail paymentDetail = null;
+        return new SavingsAccountTransaction(savingsAccount, office, paymentDetail,
+                SavingsAccountTransactionType.APPROVE_TRANSFER.getValue(), date, new Date(), savingsAccount.getSummary()
+                        .getAccountBalance(), isReversed, appUser);
+    }
+
+    public static SavingsAccountTransaction withdrawTransfer(final SavingsAccount savingsAccount, final Office office,
+            final LocalDate date, final AppUser appUser) {
+        final boolean isReversed = false;
+        final PaymentDetail paymentDetail = null;
+        return new SavingsAccountTransaction(savingsAccount, office, paymentDetail,
+                SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue(), date, new Date(), savingsAccount.getSummary()
+                        .getAccountBalance(), isReversed, appUser);
+    }
+
+    public static SavingsAccountTransaction copyTransaction(SavingsAccountTransaction accountTransaction) {
+        return new SavingsAccountTransaction(accountTransaction.savingsAccount, accountTransaction.office,
+                accountTransaction.paymentDetail, accountTransaction.typeOf, accountTransaction.transactionLocalDate(),
+                accountTransaction.createdDate, accountTransaction.amount, accountTransaction.reversed, accountTransaction.appUser);
+    }
+
+    private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final Integer typeOf,
+            final LocalDate transactionLocalDate, final Money amount, final boolean isReversed, final AppUser appUser) {
+        this(savingsAccount, office, null, typeOf, transactionLocalDate, new Date(), amount, isReversed, appUser);
+    }
+
+    private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail,
+            final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final Money amount,
+            final boolean isReversed, final AppUser appUser) {
+        this(savingsAccount, office, paymentDetail, typeOf, transactionLocalDate, createdDate, amount.getAmount(), isReversed, appUser);
+    }
+
+    private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail,
+            final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final BigDecimal amount,
+            final boolean isReversed, final AppUser appUser) {
+        this.savingsAccount = savingsAccount;
+        this.office = office;
+        this.typeOf = typeOf;
+        this.dateOf = transactionLocalDate.toDate();
+        this.amount = amount;
+        this.reversed = isReversed;
+        this.paymentDetail = paymentDetail;
+        this.createdDate = createdDate;
+        this.appUser = appUser;
+    }
+
+    public LocalDate transactionLocalDate() {
+        return new LocalDate(this.dateOf);
+    }
+
+    public void reverse() {
+        this.reversed = true;
+    }
+
+    public Money getAmount(final MonetaryCurrency currency) {
+        return Money.of(currency, this.amount);
+    }
+
+    public Money getRunningBalance(final MonetaryCurrency currency) {
+        return Money.of(currency, this.runningBalance);
+    }
+
+    public boolean isDeposit() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isDeposit();
+    }
+
+    public boolean isDepositAndNotReversed() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isDeposit() && isNotReversed();
+    }
+
+    public boolean isWithdrawal() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawal();
+    }
+
+    public boolean isPostInterestCalculationRequired() {
+        return this.isDeposit() || this.isChargeTransaction();
+    }
+
+    public boolean isInterestPostingAndNotReversed() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isInterestPosting() && isNotReversed();
+    }
+
+    public boolean isWithdrawalFeeAndNotReversed() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawalFee() && isNotReversed();
+    }
+
+    public boolean isWithdrawalFee() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawalFee();
+    }
+
+    public boolean isAnnualFeeAndNotReversed() {
+        return isAnnualFee() && isNotReversed();
+    }
+
+    public boolean isAnnualFee() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isAnnualFee();
+    }
+
+    public boolean isNotReversed() {
+        return !isReversed();
+    }
+
+    public boolean isReversed() {
+        return this.reversed;
+    }
+
+    public boolean isTransferInitiation() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isTransferInitiation();
+    }
+
+    public boolean isTransferApproval() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isTransferApproval();
+    }
+
+    public boolean isTransferRejection() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isTransferRejection();
+    }
+
+    public boolean isTransferWithdrawal() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isTransferWithdrawal();
+    }
+
+    public boolean isTransferRelatedTransaction() {
+        return isTransferInitiation() || isTransferApproval() || isTransferRejection() || isTransferWithdrawal();
+    }
+
+    public boolean occursOn(final LocalDate occursOnDate) {
+        return getTransactionLocalDate().isEqual(occursOnDate);
+    }
+
+    public void zeroBalanceFields() {
+        this.runningBalance = null;
+        this.cumulativeBalance = null;
+        this.balanceEndDate = null;
+        this.balanceNumberOfDays = null;
+    }
+
+    public void updateRunningBalance(final Money balance) {
+        this.runningBalance = balance.getAmount();
+    }
+
+    public void updateCumulativeBalanceAndDates(final MonetaryCurrency currency, final LocalDate endOfBalanceDate) {
+        // balance end date should not be before transaction date
+        if (endOfBalanceDate != null && endOfBalanceDate.isBefore(this.transactionLocalDate())) {
+            this.balanceEndDate = this.transactionLocalDate().toDate();
+        } else if (endOfBalanceDate != null) {
+            this.balanceEndDate = endOfBalanceDate.toDate();
+        } else {
+            this.balanceEndDate = null;
+        }
+        this.balanceNumberOfDays = LocalDateInterval.create(getTransactionLocalDate(), endOfBalanceDate).daysInPeriodInclusiveOfEndDate();
+        this.cumulativeBalance = Money.of(currency, this.runningBalance).multipliedBy(this.balanceNumberOfDays).getAmount();
+    }
+
+    private LocalDate getTransactionLocalDate() {
+        return new LocalDate(this.dateOf);
+    }
+
+    private LocalDate getEndOfBalanceLocalDate() {
+        LocalDate endDate = null;
+        if (this.balanceEndDate != null) {
+            endDate = new LocalDate(this.balanceEndDate);
+        }
+        return endDate;
+    }
+
+    public boolean isAcceptableForDailyBalance(final LocalDateInterval interestPeriodInterval) {
+        return isNotReversed() && interestPeriodInterval.contains(getTransactionLocalDate()) && isABalanceForAtLeastOneDay();
+    }
+
+    private boolean isABalanceForAtLeastOneDay() {
+        return this.balanceNumberOfDays != null && this.balanceNumberOfDays > 0;
+    }
+
+    public boolean hasNotAmount(final Money amountToCheck) {
+        final Money transactionAmount = getAmount(amountToCheck.getCurrency());
+        return transactionAmount.isNotEqualTo(amountToCheck);
+    }
+
+    public Map<String, Object> toMapData(final CurrencyData currencyData) {
+        final Map<String, Object> thisTransactionData = new LinkedHashMap<>();
+
+        final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(this.typeOf);
+
+        thisTransactionData.put("id", getId());
+        thisTransactionData.put("officeId", this.office.getId());
+        thisTransactionData.put("type", transactionType);
+        thisTransactionData.put("reversed", Boolean.valueOf(isReversed()));
+        thisTransactionData.put("date", getTransactionLocalDate());
+        thisTransactionData.put("currency", currencyData);
+        thisTransactionData.put("amount", this.amount);
+        thisTransactionData.put("overdraftAmount", this.overdraftAmount);
+
+        if (this.paymentDetail != null) {
+            thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId());
+        }
+
+        /***
+         * Sending data in a map, though in savings we currently expect a
+         * transaction to always repay a single charge (or may repay a part of a
+         * single charge too)
+         ***/
+        if (!this.savingsAccountChargesPaid.isEmpty()) {
+            final List<Map<String, Object>> savingsChargesPaidData = new ArrayList<>();
+            for (final SavingsAccountChargePaidBy chargePaidBy : this.savingsAccountChargesPaid) {
+                final Map<String, Object> savingChargePaidData = new LinkedHashMap<>();
+                savingChargePaidData.put("chargeId", chargePaidBy.getSavingsAccountCharge().getCharge().getId());
+                savingChargePaidData.put("isPenalty", chargePaidBy.getSavingsAccountCharge().getCharge().isPenalty());
+                savingChargePaidData.put("savingsChargeId", chargePaidBy.getSavingsAccountCharge().getId());
+                savingChargePaidData.put("amount", chargePaidBy.getAmount());
+
+                savingsChargesPaidData.add(savingChargePaidData);
+            }
+            thisTransactionData.put("savingsChargesPaid", savingsChargesPaidData);
+        }
+
+        return thisTransactionData;
+    }
+
+    public boolean isAfter(final LocalDate transactionDate) {
+        return getTransactionLocalDate().isAfter(transactionDate);
+    }
+
+    public EndOfDayBalance toEndOfDayBalance(final LocalDateInterval periodInterval, final MonetaryCurrency currency) {
+
+        final Money endOfDayBalance = Money.of(currency, this.runningBalance);
+        final Money openingBalance = endOfDayBalance;
+
+        LocalDate balanceDate = periodInterval.startDate();
+
+        int numberOfDays = periodInterval.daysInPeriodInclusiveOfEndDate();
+        if (periodInterval.contains(getTransactionLocalDate())) {
+            balanceDate = getTransactionLocalDate();
+            final LocalDateInterval newInterval = LocalDateInterval.create(getTransactionLocalDate(), periodInterval.endDate());
+            numberOfDays = newInterval.daysInPeriodInclusiveOfEndDate();
+        }
+
+        return EndOfDayBalance.from(balanceDate, openingBalance, endOfDayBalance, numberOfDays);
+    }
+
+    public EndOfDayBalance toEndOfDayBalance(final Money openingBalance, final LocalDate nextTransactionDate) {
+
+        final MonetaryCurrency currency = openingBalance.getCurrency();
+        Money endOfDayBalance = openingBalance.copy();
+        if (isDeposit()) {
+            endOfDayBalance = openingBalance.plus(getAmount(currency));
+        } else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
+            endOfDayBalance = openingBalance.minus(getAmount(currency));
+        }
+
+        int numberOfDays = LocalDateInterval.create(getTransactionLocalDate(), nextTransactionDate).daysInPeriodInclusiveOfEndDate();
+        if (!openingBalance.isEqualTo(endOfDayBalance) && numberOfDays > 1) {
+            numberOfDays = numberOfDays - 1;
+        }
+        return EndOfDayBalance.from(getTransactionLocalDate(), openingBalance, endOfDayBalance, numberOfDays);
+    }
+
+    public EndOfDayBalance toEndOfDayBalance(final Money openingBalance) {
+        final MonetaryCurrency currency = openingBalance.getCurrency();
+        Money endOfDayBalance = openingBalance.copy();
+        if (isDeposit()) {
+            endOfDayBalance = openingBalance.plus(getAmount(currency));
+        } else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
+
+            if (openingBalance.isGreaterThanZero()) {
+                endOfDayBalance = openingBalance.minus(getAmount(currency));
+            } else {
+                endOfDayBalance = Money.of(currency, this.runningBalance);
+            }
+        }
+
+        return EndOfDayBalance.from(getTransactionLocalDate(), openingBalance, endOfDayBalance, this.balanceNumberOfDays);
+    }
+
+    public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, final LocalDateInterval boundedBy) {
+
+        final MonetaryCurrency currency = openingBalance.getCurrency();
+        Money endOfDayBalance = openingBalance.copy();
+
+        int numberOfDaysOfBalance = this.balanceNumberOfDays;
+
+        LocalDate balanceStartDate = getTransactionLocalDate();
+        LocalDate balanceEndDate = getEndOfBalanceLocalDate();
+
+        if (boundedBy.startDate().isAfter(balanceStartDate)) {
+            balanceStartDate = boundedBy.startDate();
+            final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate);
+            numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate();
+        } else {
+            if (isDeposit()) {
+                // endOfDayBalance = openingBalance.plus(getAmount(currency));
+                // if (endOfDayBalance.isLessThanZero()) {
+                endOfDayBalance = endOfDayBalance.plus(getAmount(currency));
+                // }
+            } else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
+                // endOfDayBalance = openingBalance.minus(getAmount(currency));
+                if (endOfDayBalance.isGreaterThanZero()) {
+                    endOfDayBalance = endOfDayBalance.minus(getAmount(currency));
+                } else {
+                    endOfDayBalance = Money.of(currency, this.runningBalance);
+                }
+            }
+        }
+
+        if (balanceEndDate.isAfter(boundedBy.endDate())) {
+            balanceEndDate = boundedBy.endDate();
+            final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate);
+            numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate();
+        }
+
+        return EndOfDayBalance.from(balanceStartDate, openingBalance, endOfDayBalance, numberOfDaysOfBalance);
+    }
+
+    public boolean isBalanceInExistencesForOneDayOrMore() {
+        return this.balanceNumberOfDays != null && this.balanceNumberOfDays.intValue() >= 1;
+    }
+
+    public boolean fallsWithin(final LocalDateInterval periodInterval) {
+        final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate());
+        return periodInterval.contains(balanceInterval);
+    }
+
+    public boolean spansAnyPortionOf(final LocalDateInterval periodInterval) {
+        final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate());
+        return balanceInterval.containsPortionOf(periodInterval);
+    }
+
+    public boolean isIdentifiedBy(final Long transactionId) {
+        return getId().equals(transactionId);
+    }
+
+    public boolean isCredit() {
+        return isDeposit() || isInterestPostingAndNotReversed();
+    }
+
+    public boolean isDebit() {
+        return isWithdrawal() 
+        		|| isWithdrawalFeeAndNotReversed() 
+        		|| isAnnualFeeAndNotReversed() 
+        		|| isPayCharge()
+        		|| isOverdraftInterestAndNotReversed();
+    }
+
+    public boolean isOverdraftInterestAndNotReversed() {
+    	return SavingsAccountTransactionType.fromInt(this.typeOf).isIncomeFromInterest() && isNotReversed();
+    }
+
+	public boolean isPayCharge() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isPayCharge();
+    }
+
+    public boolean isChargeTransaction() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isChargeTransaction();
+    }
+
+    public boolean isChargeTransactionAndNotReversed() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isChargeTransaction() && isNotReversed();
+    }
+
+    public boolean isWaiveCharge() {
+        return SavingsAccountTransactionType.fromInt(this.typeOf).isWaiveCharge();
+    }
+
+    private boolean canOverriteSavingAccountRules() {
+        final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
+        return (isChargeTransaction() && chargePaidBy != null) ? chargePaidBy.canOverriteSavingAccountRules() : false;
+    }
+
+    public boolean canProcessBalanceCheck() {
+        return isDebit() && !canOverriteSavingAccountRules();
+    }
+
+    public boolean isFeeCharge() {
+        final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
+        return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false;
+    }
+
+    public boolean isPenaltyCharge() {
+        final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
+        return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false;
+    }
+
+    public boolean isFeeChargeAndNotReversed() {
+        return isFeeCharge() && isNotReversed();
+    }
+
+    public boolean isPenaltyChargeAndNotReversed() {
+        return isPenaltyCharge() && isNotReversed();
+    }
+
+    public boolean isWaiveFeeCharge() {
+        final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
+        return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false;
+    }
+
+    public boolean isWaivePenaltyCharge() {
+        final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
+        return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false;
+    }
+
+    public boolean isWaiveFeeChargeAndNotReversed() {
+        return isWaiveFeeCharge() && isNotReversed();
+    }
+
+    public boolean isWaivePenaltyChargeAndNotReversed() {
+        return isWaivePenaltyCharge() && isNotReversed();
+    }
+
+    private SavingsAccountChargePaidBy getSavingsAccountChargePaidBy() {
+        if (!CollectionUtils.isEmpty(this.savingsAccountChargesPaid)) { return this.savingsAccountChargesPaid.iterator().next(); }
+        return null;
+    }
+
+    public Set<SavingsAccountChargePaidBy> getSavingsAccountChargesPaid() {
+        return this.savingsAccountChargesPaid;
+    }
+
+    public void updateOverdraftAmount(BigDecimal overdraftAmount) {
+        this.overdraftAmount = overdraftAmount;
+    }
+
+    public Money getOverdraftAmount(final MonetaryCurrency currency) {
+        return Money.of(currency, this.overdraftAmount);
+    }
+
+    public Date createdDate() {
+        return this.createdDate;
+    }
+
+    public boolean isPaymentForCurrentCharge(final SavingsAccountCharge savingsAccountCharge) {
+
+        final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
+        final boolean isChargePaidForCurrentCharge;
+        if (chargePaidBy == null) {
+            isChargePaidForCurrentCharge = false;
+        } else if (chargePaidBy.getSavingsAccountCharge().equals(savingsAccountCharge)) {
+            isChargePaidForCurrentCharge = true;
+        } else {
+            isChargePaidForCurrentCharge = false;
+        }
+
+        return isChargePaidForCurrentCharge;
+    }
+
+    public BigDecimal getAmount() {
+        return this.amount;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionComparator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionComparator.java
new file mode 100644
index 0000000..d04600c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionComparator.java
@@ -0,0 +1,45 @@
+/**
+ * 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.savings.domain;
+
+import java.util.Comparator;
+
+/**
+ * Sort savings account transactions by transaction date and transaction type
+ * placing
+ */
+public class SavingsAccountTransactionComparator implements Comparator<SavingsAccountTransaction> {
+
+    @Override
+    public int compare(final SavingsAccountTransaction o1, final SavingsAccountTransaction o2) {
+        int compareResult = 0;
+        final int comparsion = o1.transactionLocalDate().compareTo(o2.transactionLocalDate());
+        if (comparsion == 0) {
+            compareResult = o1.createdDate().compareTo(o2.createdDate());
+            if (compareResult == 0 && o1.getId() != null && o2.getId() != null) {
+                compareResult = o1.getId().compareTo(o2.getId());
+            } else {
+                compareResult = comparsion;
+            }
+        } else {
+            compareResult = comparsion;
+        }
+        return compareResult;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionRepository.java
new file mode 100644
index 0000000..52d824e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionRepository.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.savings.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface SavingsAccountTransactionRepository extends JpaRepository<SavingsAccountTransaction, Long>,
+        JpaSpecificationExecutor<SavingsAccountTransaction> {
+
+    SavingsAccountTransaction findOneByIdAndSavingsAccountId(Long transactionId, Long savingsId);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java
new file mode 100644
index 0000000..0a367de
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java
@@ -0,0 +1,136 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.domain;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.springframework.stereotype.Component;
+
+/**
+ * A wrapper for dealing with side-effect free functionality related to a
+ * {@link SavingsAccount}'s {@link SavingsAccountTransaction}'s.
+ */
+@Component
+public final class SavingsAccountTransactionSummaryWrapper {
+
+    public BigDecimal calculateTotalDeposits(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isDeposit() && transaction.isNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+    }
+
+    public BigDecimal calculateTotalWithdrawals(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isWithdrawal() && transaction.isNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+    }
+
+    public BigDecimal calculateTotalInterestPosted(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+    }
+
+    public BigDecimal calculateTotalWithdrawalFees(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isWithdrawalFeeAndNotReversed() && transaction.isNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+    }
+
+    public BigDecimal calculateTotalAnnualFees(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isAnnualFeeAndNotReversed() && transaction.isNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+    }
+
+    public BigDecimal calculateTotalFeesCharge(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isFeeChargeAndNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+    }
+
+    public BigDecimal calculateTotalFeesChargeWaived(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isWaiveFeeChargeAndNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+    }
+
+    public BigDecimal calculateTotalPenaltyCharge(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isPenaltyChargeAndNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+    }
+
+    public BigDecimal calculateTotalPenaltyChargeWaived(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isWaivePenaltyChargeAndNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+    }
+
+	public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency,
+			List<SavingsAccountTransaction> transactions) {
+        Money total = Money.zero(currency);
+        for (final SavingsAccountTransaction transaction : transactions) {
+            if (transaction.isOverdraftInterestAndNotReversed()) {
+                total = total.plus(transaction.getAmount(currency));
+            }
+        }
+        return total.getAmountDefaultedToNullIfZero();
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsEvent.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsEvent.java
new file mode 100644
index 0000000..817bc85
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsEvent.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.portfolio.savings.domain;
+
+/**
+ *
+ */
+public enum SavingsEvent {
+
+    SAVINGS_APPLICATION_REJECTED("application.rejected"), //
+    SAVINGS_APPLICATION_WITHDRAWAL_BY_CUSTOMER("application.withdrawal"), //
+    SAVINGS_APPLICATION_APPROVED("application.approval"), //
+    SAVINGS_APPLICATION_APPROVAL_UNDO("application.approval.undo"), //
+    SAVINGS_ACTIVATE("activate"), //
+    SAVINGS_DEPOSIT("deposit"), //
+    SAVINGS_WITHDRAWAL("withdraw"), //
+    SAVINGS_POST_INTEREST("interest.post"), //
+    SAVINGS_UNDO_TRANSACTION("transaction.undo"), //
+    SAVINGS_ADJUST_TRANSACTION("transaction.adjust"), //
+    SAVINGS_APPLY_CHARGE("charge.apply"), //
+    SAVINGS_WAIVE_CHARGE("charge.waive"), //
+    SAVINGS_PAY_CHARGE("charge.pay"), //
+    SAVINGS_CLOSE_ACCOUNT("account.close");
+
+    private final String value;
+
+    private SavingsEvent(final String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    @Override
+    public String toString() {
+        return getValue();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java
new file mode 100644
index 0000000..09b7219
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java
@@ -0,0 +1,151 @@
+/**
+ * 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.savings.domain;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.domain.interest.CompoundInterestHelper;
+import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
+import org.joda.time.LocalDate;
+
+public final class SavingsHelper {
+
+    AccountTransfersReadPlatformService accountTransfersReadPlatformService = null;
+
+    public SavingsHelper(AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
+        this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
+    }
+
+    private final CompoundInterestHelper compoundInterestHelper = new CompoundInterestHelper();
+
+    public List<LocalDateInterval> determineInterestPostingPeriods(final LocalDate startInterestCalculationLocalDate,
+            final LocalDate interestPostingUpToDate, final SavingsPostingInterestPeriodType postingPeriodType,
+            final Integer financialYearBeginningMonth) {
+
+        final List<LocalDateInterval> postingPeriods = new ArrayList<>();
+
+        LocalDate periodStartDate = startInterestCalculationLocalDate;
+        LocalDate periodEndDate = periodStartDate;
+
+        while (!periodStartDate.isAfter(interestPostingUpToDate) && !periodEndDate.isAfter(interestPostingUpToDate)) {
+
+            final LocalDate interestPostingLocalDate = determineInterestPostingPeriodEndDateFrom(periodStartDate, postingPeriodType,
+                    interestPostingUpToDate, financialYearBeginningMonth);
+            periodEndDate = interestPostingLocalDate.minusDays(1);
+
+            postingPeriods.add(LocalDateInterval.create(periodStartDate, periodEndDate));
+
+            periodEndDate = interestPostingLocalDate;
+            periodStartDate = interestPostingLocalDate;
+        }
+
+        return postingPeriods;
+    }
+
+    private LocalDate determineInterestPostingPeriodEndDateFrom(final LocalDate periodStartDate,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final LocalDate interestPostingUpToDate,
+            Integer financialYearBeginningMonth) {
+
+        LocalDate periodEndDate = interestPostingUpToDate;
+        final Integer monthOfYear = periodStartDate.getMonthOfYear();
+        financialYearBeginningMonth--;
+        if (financialYearBeginningMonth == 0) financialYearBeginningMonth = 12;
+
+        final ArrayList<LocalDate> quarterlyDates = new ArrayList<>();
+        quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).dayOfMonth().withMaximumValue());
+        quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(3).withYear(periodStartDate.getYear())
+                .dayOfMonth().withMaximumValue());
+        quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(6).withYear(periodStartDate.getYear())
+                .dayOfMonth().withMaximumValue());
+        quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(9).withYear(periodStartDate.getYear())
+                .dayOfMonth().withMaximumValue());
+        Collections.sort(quarterlyDates);
+
+        final ArrayList<LocalDate> biannualDates = new ArrayList<>();
+        biannualDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).dayOfMonth().withMaximumValue());
+        biannualDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(6).withYear(periodStartDate.getYear())
+                .dayOfMonth().withMaximumValue());
+        Collections.sort(biannualDates);
+
+        boolean isEndDateSet = false;
+
+        switch (interestPostingPeriodType) {
+            case INVALID:
+            break;
+            case MONTHLY:
+                // produce period end date on last day of current month
+                periodEndDate = periodStartDate.dayOfMonth().withMaximumValue();
+            break;
+            case QUATERLY:
+                for (LocalDate quarterlyDate : quarterlyDates) {
+                    if (quarterlyDate.isAfter(periodStartDate)) {
+                        periodEndDate = quarterlyDate;
+                        isEndDateSet = true;
+                        break;
+                    }
+                }
+
+                if (!isEndDateSet) periodEndDate = quarterlyDates.get(0).plusYears(1).dayOfMonth().withMaximumValue();
+            break;
+            case BIANNUAL:
+                for (LocalDate biannualDate : biannualDates) {
+                    if (biannualDate.isAfter(periodStartDate)) {
+                        periodEndDate = biannualDate;
+                        isEndDateSet = true;
+                        break;
+                    }
+                }
+
+                if (!isEndDateSet) periodEndDate = biannualDates.get(0).plusYears(1).dayOfMonth().withMaximumValue();
+            break;
+            case ANNUAL:
+                if (financialYearBeginningMonth < monthOfYear) {
+                    periodEndDate = periodStartDate.withMonthOfYear(financialYearBeginningMonth);
+                    periodEndDate = periodEndDate.plusYears(1);
+                } else {
+                    periodEndDate = periodStartDate.withMonthOfYear(financialYearBeginningMonth);
+                }
+                periodEndDate = periodEndDate.dayOfMonth().withMaximumValue();
+            break;
+        }
+
+        // interest posting always occurs on next day after the period end date.
+        periodEndDate = periodEndDate.plusDays(1);
+
+        return periodEndDate;
+    }
+
+    public Money calculateInterestForAllPostingPeriods(final MonetaryCurrency currency, final List<PostingPeriod> allPeriods,
+            LocalDate accountLockedUntil, Boolean immediateWithdrawalOfInterest) {
+        return this.compoundInterestHelper.calculateInterestForAllPostingPeriods(currency, allPeriods, accountLockedUntil,
+                immediateWithdrawalOfInterest);
+    }
+
+    public Collection<Long> fetchPostInterestTransactionIds(Long accountId) {
+        return this.accountTransfersReadPlatformService.fetchPostInterestTransactionIds(accountId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java
new file mode 100644
index 0000000..d38df2b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java
@@ -0,0 +1,120 @@
+/**
+ * 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.savings.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.commons.lang.ObjectUtils;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+
+@Entity
+@Table(name = "m_savings_officer_assignment_history")
+public class SavingsOfficerAssignmentHistory extends AbstractAuditableCustom<AppUser, Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "account_id", nullable = false)
+    private SavingsAccount savingsAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "savings_officer_id", nullable = true)
+    private Staff savingsOfficer;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "start_date")
+    private Date startDate;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "end_date")
+    private Date endDate;
+
+    public static SavingsOfficerAssignmentHistory createNew(final SavingsAccount account, final Staff savingsOfficer,
+            final LocalDate assignmentDate) {
+        return new SavingsOfficerAssignmentHistory(account, savingsOfficer, assignmentDate.toDate(), null);
+    }
+
+    protected SavingsOfficerAssignmentHistory() {
+        //
+    }
+
+    private SavingsOfficerAssignmentHistory(final SavingsAccount account, final Staff savingsOfficer, final Date startDate,
+            final Date endDate) {
+        this.savingsAccount = account;
+        this.savingsOfficer = savingsOfficer;
+        this.startDate = startDate;
+        this.endDate = endDate;
+    }
+
+    public void updateSavingsOfficer(final Staff savingsOfficer) {
+        this.savingsOfficer = savingsOfficer;
+    }
+
+    public boolean isSameSavingsOfficer(final Staff staff) {
+        return this.savingsOfficer.identifiedBy(staff);
+    }
+
+    public void updateStartDate(final LocalDate startDate) {
+        this.startDate = startDate.toDate();
+    }
+
+    public void updateEndDate(final LocalDate endDate) {
+        this.endDate = endDate.toDate();
+    }
+
+    public boolean matchesStartDateOf(final LocalDate matchingDate) {
+        return getStartDate().isEqual(matchingDate);
+    }
+
+    public LocalDate getStartDate() {
+        return new LocalDate(this.startDate);
+    }
+
+    public boolean hasStartDateBefore(final LocalDate matchingDate) {
+        return matchingDate.isBefore(getStartDate());
+    }
+
+    public boolean isCurrentRecord() {
+        return this.endDate == null;
+    }
+
+    /**
+     * If endDate is null then return false.
+     * 
+     * @param compareDate
+     * @return
+     */
+    public boolean isEndDateAfter(final LocalDate compareDate) {
+        return this.endDate == null ? false : new LocalDate(this.endDate).isAfter(compareDate);
+    }
+
+    public LocalDate getEndDate() {
+        return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.endDate), null);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java
new file mode 100644
index 0000000..d5aaa5d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java
@@ -0,0 +1,627 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.DiscriminatorColumn;
+import javax.persistence.DiscriminatorType;
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.Inheritance;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.joda.time.LocalDate;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import com.google.gson.JsonArray;
+
+@Entity
+@Table(name = "m_savings_product", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "sp_unq_name"),
+        @UniqueConstraint(columnNames = { "short_name" }, name = "sp_unq_short_name") })
+@Inheritance
+@DiscriminatorColumn(name = "deposit_type_enum", discriminatorType = DiscriminatorType.INTEGER)
+@DiscriminatorValue("100")
+public class SavingsProduct extends AbstractPersistable<Long> {
+
+    @Column(name = "name", nullable = false, unique = true)
+    protected String name;
+
+    @Column(name = "short_name", nullable = false, unique = true)
+    protected String shortName;
+
+    @Column(name = "description", length = 500, nullable = false)
+    protected String description;
+
+    @Embedded
+    protected MonetaryCurrency currency;
+
+    @Column(name = "nominal_annual_interest_rate", scale = 6, precision = 19, nullable = false)
+    protected BigDecimal nominalAnnualInterestRate;
+
+    /**
+     * The interest period is the span of time at the end of which savings in a
+     * client's account earn interest.
+     * 
+     * A value from the {@link SavingsCompoundingInterestPeriodType}
+     * enumeration.
+     */
+    @Column(name = "interest_compounding_period_enum", nullable = false)
+    protected Integer interestCompoundingPeriodType;
+
+    /**
+     * A value from the {@link SavingsPostingInterestPeriodType} enumeration.
+     */
+    @Column(name = "interest_posting_period_enum", nullable = false)
+    protected Integer interestPostingPeriodType;
+
+    /**
+     * A value from the {@link SavingsInterestCalculationType} enumeration.
+     */
+    @Column(name = "interest_calculation_type_enum", nullable = false)
+    protected Integer interestCalculationType;
+
+    /**
+     * A value from the {@link SavingsInterestCalculationDaysInYearType}
+     * enumeration.
+     */
+    @Column(name = "interest_calculation_days_in_year_type_enum", nullable = false)
+    protected Integer interestCalculationDaysInYearType;
+
+    @Column(name = "min_required_opening_balance", scale = 6, precision = 19, nullable = true)
+    protected BigDecimal minRequiredOpeningBalance;
+
+    @Column(name = "lockin_period_frequency", nullable = true)
+    protected Integer lockinPeriodFrequency;
+
+    @Column(name = "lockin_period_frequency_enum", nullable = true)
+    protected Integer lockinPeriodFrequencyType;
+
+    /**
+     * A value from the {@link AccountingRuleType} enumeration.
+     */
+    @Column(name = "accounting_type", nullable = false)
+    protected Integer accountingRule;
+
+    @Column(name = "withdrawal_fee_for_transfer")
+    protected boolean withdrawalFeeApplicableForTransfer;
+
+    @ManyToMany
+    @JoinTable(name = "m_savings_product_charge", joinColumns = @JoinColumn(name = "savings_product_id") , inverseJoinColumns = @JoinColumn(name = "charge_id") )
+    protected Set<Charge> charges;
+
+    @Column(name = "allow_overdraft")
+    private boolean allowOverdraft;
+
+    @Column(name = "overdraft_limit", scale = 6, precision = 19, nullable = true)
+    private BigDecimal overdraftLimit;
+
+	@Column(name = "nominal_annual_interest_rate_overdraft", scale = 6, precision = 19, nullable = true)
+    private BigDecimal nominalAnnualInterestRateOverdraft;
+
+    @Column(name = "min_overdraft_for_interest_calculation", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minOverdraftForInterestCalculation;
+    
+    @Column(name = "enforce_min_required_balance")
+    private boolean enforceMinRequiredBalance;
+
+    @Column(name = "min_required_balance", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minRequiredBalance;
+
+    @Column(name = "min_balance_for_interest_calculation", scale = 6, precision = 19, nullable = true)
+    private BigDecimal minBalanceForInterestCalculation;
+
+    public static SavingsProduct createNew(final String name, final String shortName, final String description,
+            final MonetaryCurrency currency, final BigDecimal interestRate,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> charges,
+            final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance,
+            final BigDecimal minRequiredBalance, final BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) {
+
+        return new SavingsProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges,
+                allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation, 
+                nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    protected SavingsProduct() {
+        this.name = null;
+        this.description = null;
+    }
+
+    protected SavingsProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency,
+            final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> charges,
+            final boolean allowOverdraft, final BigDecimal overdraftLimit, BigDecimal minBalanceForInterestCalculation) {
+        this(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
+                interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
+                lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit,
+                false, null, minBalanceForInterestCalculation, null, null);
+    }
+
+    protected SavingsProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency,
+            final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
+            final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
+            final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
+            final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> charges,
+            final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance,
+            final BigDecimal minRequiredBalance, BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) {
+
+        this.name = name;
+        this.shortName = shortName;
+        this.description = description;
+
+        this.currency = currency;
+        this.nominalAnnualInterestRate = interestRate;
+        this.interestCompoundingPeriodType = interestCompoundingPeriodType.getValue();
+        this.interestPostingPeriodType = interestPostingPeriodType.getValue();
+        this.interestCalculationType = interestCalculationType.getValue();
+        this.interestCalculationDaysInYearType = interestCalculationDaysInYearType.getValue();
+
+        if (minRequiredOpeningBalance != null) {
+            this.minRequiredOpeningBalance = Money.of(currency, minRequiredOpeningBalance).getAmount();
+        }
+
+        this.lockinPeriodFrequency = lockinPeriodFrequency;
+        if (lockinPeriodFrequency != null && lockinPeriodFrequencyType != null) {
+            this.lockinPeriodFrequencyType = lockinPeriodFrequencyType.getValue();
+        }
+
+        this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer;
+
+        if (accountingRuleType != null) {
+            this.accountingRule = accountingRuleType.getValue();
+        }
+
+        if (charges != null) {
+            this.charges = charges;
+        }
+
+        validateLockinDetails();
+        this.allowOverdraft = allowOverdraft;
+        this.overdraftLimit = overdraftLimit;
+        this.nominalAnnualInterestRateOverdraft = nominalAnnualInterestRateOverdraft;
+        this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation;
+
+        esnureOverdraftLimitsSetForOverdraftAccounts();
+
+        this.enforceMinRequiredBalance = enforceMinRequiredBalance;
+        this.minRequiredBalance = minRequiredBalance;
+        this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
+    }
+
+    /**
+     * If overdrafts are allowed and the overdraft limit is not set, set the
+     * same to Zero
+     **/
+    private void esnureOverdraftLimitsSetForOverdraftAccounts() {
+
+        if (this.allowOverdraft) {
+            this.overdraftLimit = this.overdraftLimit == null? BigDecimal.ZERO : this.overdraftLimit;
+            this.nominalAnnualInterestRateOverdraft = this.nominalAnnualInterestRateOverdraft == null? BigDecimal.ZERO : this.nominalAnnualInterestRateOverdraft;
+            this.minOverdraftForInterestCalculation = this.minOverdraftForInterestCalculation == null? BigDecimal.ZERO : this.minOverdraftForInterestCalculation;
+        }
+    }
+
+    public MonetaryCurrency currency() {
+        return this.currency.copy();
+    }
+
+    public BigDecimal nominalAnnualInterestRate() {
+        return this.nominalAnnualInterestRate;
+    }
+
+    public SavingsCompoundingInterestPeriodType interestCompoundingPeriodType() {
+        return SavingsCompoundingInterestPeriodType.fromInt(this.interestCompoundingPeriodType);
+    }
+
+    public SavingsPostingInterestPeriodType interestPostingPeriodType() {
+        return SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType);
+    }
+
+    public SavingsInterestCalculationType interestCalculationType() {
+        return SavingsInterestCalculationType.fromInt(this.interestCalculationType);
+    }
+
+    public SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType() {
+        return SavingsInterestCalculationDaysInYearType.fromInt(this.interestCalculationDaysInYearType);
+    }
+
+    public BigDecimal minRequiredOpeningBalance() {
+        return this.minRequiredOpeningBalance;
+    }
+
+    public Integer lockinPeriodFrequency() {
+        return this.lockinPeriodFrequency;
+    }
+
+    public SavingsPeriodFrequencyType lockinPeriodFrequencyType() {
+        SavingsPeriodFrequencyType type = null;
+        if (this.lockinPeriodFrequencyType != null) {
+            type = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType);
+        }
+        return type;
+    }
+
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
+
+        final String localeAsInput = command.locale();
+
+        if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
+            final String newValue = command.stringValueOfParameterNamed(nameParamName);
+            actualChanges.put(nameParamName, newValue);
+            this.name = newValue;
+        }
+
+        if (command.isChangeInStringParameterNamed(shortNameParamName, this.name)) {
+            final String newValue = command.stringValueOfParameterNamed(shortNameParamName);
+            actualChanges.put(shortNameParamName, newValue);
+            this.shortName = newValue;
+        }
+
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = newValue;
+        }
+
+        Integer digitsAfterDecimal = this.currency.getDigitsAfterDecimal();
+        if (command.isChangeInIntegerParameterNamed(digitsAfterDecimalParamName, digitsAfterDecimal)) {
+            final Integer newValue = command.integerValueOfParameterNamed(digitsAfterDecimalParamName);
+            actualChanges.put(digitsAfterDecimalParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            digitsAfterDecimal = newValue;
+            this.currency = new MonetaryCurrency(this.currency.getCode(), digitsAfterDecimal, this.currency.getCurrencyInMultiplesOf());
+        }
+
+        String currencyCode = this.currency.getCode();
+        if (command.isChangeInStringParameterNamed(currencyCodeParamName, currencyCode)) {
+            final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName);
+            actualChanges.put(currencyCodeParamName, newValue);
+            currencyCode = newValue;
+            this.currency = new MonetaryCurrency(currencyCode, this.currency.getDigitsAfterDecimal(),
+                    this.currency.getCurrencyInMultiplesOf());
+        }
+
+        Integer inMultiplesOf = this.currency.getCurrencyInMultiplesOf();
+        if (command.isChangeInIntegerParameterNamed(inMultiplesOfParamName, inMultiplesOf)) {
+            final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfParamName);
+            actualChanges.put(inMultiplesOfParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            inMultiplesOf = newValue;
+            this.currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDigitsAfterDecimal(), inMultiplesOf);
+        }
+
+        if (command.isChangeInBigDecimalParameterNamed(nominalAnnualInterestRateParamName, this.nominalAnnualInterestRate)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
+            actualChanges.put(nominalAnnualInterestRateParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.nominalAnnualInterestRate = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(interestCompoundingPeriodTypeParamName, this.interestCompoundingPeriodType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
+            actualChanges.put(interestCompoundingPeriodTypeParamName, newValue);
+            this.interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(newValue).getValue();
+        }
+
+        if (command.isChangeInIntegerParameterNamed(interestPostingPeriodTypeParamName, this.interestPostingPeriodType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
+            actualChanges.put(interestPostingPeriodTypeParamName, newValue);
+            this.interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(newValue).getValue();
+        }
+
+        if (command.isChangeInIntegerParameterNamed(interestCalculationTypeParamName, this.interestCalculationType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
+            actualChanges.put(interestCalculationTypeParamName, newValue);
+            this.interestCalculationType = SavingsInterestCalculationType.fromInt(newValue).getValue();
+        }
+
+        if (command.isChangeInIntegerParameterNamed(interestCalculationDaysInYearTypeParamName, this.interestCalculationDaysInYearType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
+            actualChanges.put(interestCalculationDaysInYearTypeParamName, newValue);
+            this.interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(newValue).getValue();
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minRequiredOpeningBalanceParamName,
+                this.minRequiredOpeningBalance)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredOpeningBalanceParamName);
+            actualChanges.put(minRequiredOpeningBalanceParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.minRequiredOpeningBalance = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(lockinPeriodFrequencyParamName, this.lockinPeriodFrequency)) {
+            final Integer newValue = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName);
+            actualChanges.put(lockinPeriodFrequencyParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.lockinPeriodFrequency = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(lockinPeriodFrequencyTypeParamName, this.lockinPeriodFrequencyType)) {
+            final Integer newValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
+            actualChanges.put(lockinPeriodFrequencyTypeParamName, newValue);
+            this.lockinPeriodFrequencyType = newValue != null ? SavingsPeriodFrequencyType.fromInt(newValue).getValue() : newValue;
+        }
+
+        // set period type to null if frequency is null
+        if (this.lockinPeriodFrequency == null) {
+            this.lockinPeriodFrequencyType = null;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(withdrawalFeeForTransfersParamName, this.withdrawalFeeApplicableForTransfer)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
+            actualChanges.put(withdrawalFeeForTransfersParamName, newValue);
+            this.withdrawalFeeApplicableForTransfer = newValue;
+        }
+
+        if (command.isChangeInIntegerParameterNamed(accountingRuleParamName, this.accountingRule)) {
+            final Integer newValue = command.integerValueOfParameterNamed(accountingRuleParamName);
+            actualChanges.put(accountingRuleParamName, newValue);
+            this.accountingRule = newValue;
+        }
+
+        // charges
+        if (command.hasParameter(chargesParamName)) {
+            final JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName);
+            if (jsonArray != null) {
+                actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName));
+            }
+        }
+
+        if (command.isChangeInBooleanParameterNamed(allowOverdraftParamName, this.allowOverdraft)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
+            actualChanges.put(allowOverdraftParamName, newValue);
+            this.allowOverdraft = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(overdraftLimitParamName, this.overdraftLimit)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(overdraftLimitParamName);
+            actualChanges.put(overdraftLimitParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.overdraftLimit = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(nominalAnnualInterestRateOverdraftParamName, this.nominalAnnualInterestRateOverdraft)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName);
+            actualChanges.put(nominalAnnualInterestRateOverdraftParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.nominalAnnualInterestRateOverdraft = newValue;
+        }
+        
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minOverdraftForInterestCalculationParamName, this.minOverdraftForInterestCalculation)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName);
+            actualChanges.put(minOverdraftForInterestCalculationParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.minOverdraftForInterestCalculation = newValue;
+        }
+        
+        if (!this.allowOverdraft) {
+            this.overdraftLimit = null;
+            this.nominalAnnualInterestRateOverdraft = null;
+            this.minOverdraftForInterestCalculation = null;
+        }
+
+        if (command.isChangeInBooleanParameterNamed(enforceMinRequiredBalanceParamName, this.enforceMinRequiredBalance)) {
+            final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName);
+            actualChanges.put(enforceMinRequiredBalanceParamName, newValue);
+            this.enforceMinRequiredBalance = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minRequiredBalanceParamName, this.minRequiredBalance)) {
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName);
+            actualChanges.put(minRequiredBalanceParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.minRequiredBalance = newValue;
+        }
+
+        if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minBalanceForInterestCalculationParamName,
+                this.minBalanceForInterestCalculation)) {
+            final BigDecimal newValue = command
+                    .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName);
+            actualChanges.put(minBalanceForInterestCalculationParamName, newValue);
+            actualChanges.put(localeParamName, localeAsInput);
+            this.minBalanceForInterestCalculation = newValue;
+        }
+
+        validateLockinDetails();
+        esnureOverdraftLimitsSetForOverdraftAccounts();
+
+        return actualChanges;
+    }
+
+    private void validateLockinDetails() {
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_PRODUCT_RESOURCE_NAME);
+
+        if (this.lockinPeriodFrequency == null) {
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(this.lockinPeriodFrequencyType).ignoreIfNull()
+                    .inMinMaxRange(0, 3);
+
+            if (this.lockinPeriodFrequencyType != null) {
+                baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(this.lockinPeriodFrequency).notNull()
+                        .integerZeroOrGreater();
+            }
+        } else {
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(this.lockinPeriodFrequencyType)
+                    .integerZeroOrGreater();
+            baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(this.lockinPeriodFrequencyType).notNull()
+                    .inMinMaxRange(0, 3);
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public boolean isCashBasedAccountingEnabled() {
+        return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingRule);
+    }
+
+    // TODO this entire block is currently unnecessary as Savings does not have
+    // accrual accounting
+    public boolean isAccrualBasedAccountingEnabled() {
+        return isUpfrontAccrualAccounting() || isPeriodicAccrualAccounting();
+    }
+
+    public boolean isPeriodicAccrualAccounting() {
+        return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(this.accountingRule);
+    }
+
+    public boolean isUpfrontAccrualAccounting() {
+        return AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(this.accountingRule);
+    }
+
+    public Integer getAccountingType() {
+        return this.accountingRule;
+    }
+
+    public boolean update(final Set<Charge> newSavingsProductCharges) {
+        if (newSavingsProductCharges == null) { return false; }
+
+        boolean updated = false;
+        if (this.charges != null) {
+            final Set<Charge> currentSetOfCharges = new HashSet<>(this.charges);
+            final Set<Charge> newSetOfCharges = new HashSet<>(newSavingsProductCharges);
+
+            if (!(currentSetOfCharges.equals(newSetOfCharges))) {
+                updated = true;
+                this.charges = newSavingsProductCharges;
+            }
+        } else {
+            updated = true;
+            this.charges = newSavingsProductCharges;
+        }
+        return updated;
+    }
+
+    public BigDecimal overdraftLimit() {
+        return this.overdraftLimit;
+    }
+
+    public boolean isWithdrawalFeeApplicableForTransfer() {
+        return this.withdrawalFeeApplicableForTransfer;
+    }
+
+    public boolean isAllowOverdraft() {
+        return this.allowOverdraft;
+    }
+
+    public BigDecimal minRequiredBalance() {
+        return this.minRequiredBalance;
+    }
+
+    public boolean isMinRequiredBalanceEnforced() {
+        return this.enforceMinRequiredBalance;
+    }
+
+    public Set<Charge> charges() {
+        return this.charges;
+    }
+
+    public InterestRateChart applicableChart(@SuppressWarnings("unused") final LocalDate target) {
+        return null;
+    }
+
+    public InterestRateChart findChart(@SuppressWarnings("unused") Long chartId) {
+        return null;
+    }
+
+    public BigDecimal minBalanceForInterestCalculation() {
+        return this.minBalanceForInterestCalculation;
+    }
+
+    public String getShortName() {
+        return this.shortName;
+    }
+
+    public BigDecimal nominalAnnualInterestRateOverdraft() {
+		return this.nominalAnnualInterestRateOverdraft;
+	}
+
+	public BigDecimal minOverdraftForInterestCalculation() {
+		return this.minOverdraftForInterestCalculation;
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java
new file mode 100644
index 0000000..823a44f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java
@@ -0,0 +1,208 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.idParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeAppliedToException;
+import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+@Component
+public class SavingsProductAssembler {
+
+    private final ChargeRepositoryWrapper chargeRepository;
+
+    @Autowired
+    public SavingsProductAssembler(final ChargeRepositoryWrapper chargeRepository) {
+        this.chargeRepository = chargeRepository;
+    }
+
+    public SavingsProduct assemble(final JsonCommand command) {
+
+        final String name = command.stringValueOfParameterNamed(nameParamName);
+        final String shortName = command.stringValueOfParameterNamed(shortNameParamName);
+        final String description = command.stringValueOfParameterNamed(descriptionParamName);
+
+        final String currencyCode = command.stringValueOfParameterNamed(currencyCodeParamName);
+        final Integer digitsAfterDecimal = command.integerValueOfParameterNamed(digitsAfterDecimalParamName);
+        final Integer inMultiplesOf = command.integerValueOfParameterNamed(inMultiplesOfParamName);
+        final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
+
+        final BigDecimal interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
+
+        SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null;
+        final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
+        if (interestPeriodTypeValue != null) {
+            interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue);
+        }
+
+        SavingsPostingInterestPeriodType interestPostingPeriodType = null;
+        final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
+        if (interestPostingPeriodTypeValue != null) {
+            interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue);
+        }
+
+        SavingsInterestCalculationType interestCalculationType = null;
+        final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
+        if (interestCalculationTypeValue != null) {
+            interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue);
+        }
+
+        SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = null;
+        final Integer interestCalculationDaysInYearTypeValue = command
+                .integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
+        if (interestCalculationDaysInYearTypeValue != null) {
+            interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue);
+        }
+
+        final BigDecimal minRequiredOpeningBalance = command
+                .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredOpeningBalanceParamName);
+
+        final Integer lockinPeriodFrequency = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName);
+        SavingsPeriodFrequencyType lockinPeriodFrequencyType = null;
+        final Integer lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
+        if (lockinPeriodFrequencyTypeValue != null) {
+            lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+        }
+
+        boolean iswithdrawalFeeApplicableForTransfer = false;
+        if (command.parameterExists(withdrawalFeeForTransfersParamName)) {
+            iswithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
+        }
+
+        final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
+
+        // Savings product charges
+        final Set<Charge> charges = assembleListOfSavingsProductCharges(command, currencyCode);
+
+        boolean allowOverdraft = false;
+        if (command.parameterExists(allowOverdraftParamName)) {
+            allowOverdraft = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
+        }
+
+        BigDecimal overdraftLimit = BigDecimal.ZERO;
+        if(command.parameterExists(overdraftLimitParamName)){
+            overdraftLimit = command.bigDecimalValueOfParameterNamed(overdraftLimitParamName);
+        }
+
+        BigDecimal nominalAnnualInterestRateOverdraft = BigDecimal.ZERO;
+        if(command.parameterExists(nominalAnnualInterestRateOverdraftParamName)){
+        	nominalAnnualInterestRateOverdraft = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateOverdraftParamName);
+        }
+        
+        BigDecimal minOverdraftForInterestCalculation = BigDecimal.ZERO;
+        if(command.parameterExists(minOverdraftForInterestCalculationParamName)){
+        	minOverdraftForInterestCalculation = command.bigDecimalValueOfParameterNamed(minOverdraftForInterestCalculationParamName);
+        }
+
+        boolean enforceMinRequiredBalance = false;
+        if (command.parameterExists(enforceMinRequiredBalanceParamName)) {
+            enforceMinRequiredBalance = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName);
+        }
+
+        BigDecimal minRequiredBalance = BigDecimal.ZERO;
+        if(command.parameterExists(minRequiredBalanceParamName)){
+            minRequiredBalance = command.bigDecimalValueOfParameterNamed(minRequiredBalanceParamName);
+        }
+        final BigDecimal minBalanceForInterestCalculation = command
+                .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName);
+
+        return SavingsProduct.createNew(name, shortName, description, currency, interestRate, interestCompoundingPeriodType,
+                interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, accountingRuleType, charges,
+                allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation,
+                nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+    }
+
+    public Set<Charge> assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) {
+
+        final Set<Charge> charges = new HashSet<>();
+
+        if (command.parameterExists(chargesParamName)) {
+            final JsonArray chargesArray = command.arrayOfParameterNamed(chargesParamName);
+            if (chargesArray != null) {
+                for (int i = 0; i < chargesArray.size(); i++) {
+
+                    final JsonObject jsonObject = chargesArray.get(i).getAsJsonObject();
+                    if (jsonObject.has(idParamName)) {
+                        final Long id = jsonObject.get(idParamName).getAsLong();
+
+                        final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(id);
+
+                        if (!charge.isSavingsCharge()) {
+                            final String errorMessage = "Charge with identifier " + charge.getId()
+                                    + " cannot be applied to Savings product.";
+                            throw new ChargeCannotBeAppliedToException("savings.product", errorMessage, charge.getId());
+                        }
+
+                        if (!savingsProductCurrencyCode.equals(charge.getCurrencyCode())) {
+                            final String errorMessage = "Charge and Savings Product must have the same currency.";
+                            throw new InvalidCurrencyException("charge", "attach.to.savings.product", errorMessage);
+                        }
+                        charges.add(charge);
+                    }
+                }
+            }
+        }
+
+        return charges;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductChargeAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductChargeAssembler.java
new file mode 100644
index 0000000..a9417db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductChargeAssembler.java
@@ -0,0 +1,124 @@
+/**
+ * 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.savings.domain;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeCalculationTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.idParamName;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+@Service
+public class SavingsProductChargeAssembler {
+
+    private final FromJsonHelper fromApiJsonHelper;
+    private final ChargeRepositoryWrapper chargeRepository;
+    private final SavingsAccountChargeRepository savingsAccountChargeRepository;
+
+    @Autowired
+    public SavingsProductChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
+            final SavingsAccountChargeRepository savingsAccountChargeRepository) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.chargeRepository = chargeRepository;
+        this.savingsAccountChargeRepository = savingsAccountChargeRepository;
+    }
+
+    public Set<SavingsAccountCharge> fromParsedJson(final JsonElement element) {
+
+        final Set<SavingsAccountCharge> savingsAccountCharges = new HashSet<>();
+
+        if (element.isJsonObject()) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
+            final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) {
+                final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray();
+                for (int i = 0; i < array.size(); i++) {
+
+                    final JsonObject savingsChargeElement = array.get(i).getAsJsonObject();
+
+                    final Long id = this.fromApiJsonHelper.extractLongNamed(idParamName, savingsChargeElement);
+                    final Long chargeId = this.fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement);
+                    final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale);
+                    final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed(chargeTimeTypeParamName,
+                            savingsChargeElement, locale);
+                    final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed(chargeCalculationTypeParamName,
+                            savingsChargeElement, locale);
+                    final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement,
+                            dateFormat, locale);
+                    final MonthDay feeOnMonthDay = this.fromApiJsonHelper
+                            .extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement);
+                    final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, savingsChargeElement,
+                            locale);
+
+                    if (id == null) {
+                        final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
+                        final ChargeTimeType chargeTime = null;
+                        if (chargeTimeType != null) {
+                            ChargeTimeType.fromInt(chargeTimeType);
+                        }
+                        final ChargeCalculationType chargeCalculation = null;
+                        if (chargeCalculationType != null) {
+                            ChargeCalculationType.fromInt(chargeCalculationType);
+                        }
+                        final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(
+                                chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, true, feeOnMonthDay, feeInterval);
+                        savingsAccountCharges.add(savingsAccountCharge);
+                    } else {
+                        final Long savingsAccountChargeId = id;
+                        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                                .findOne(savingsAccountChargeId);
+                        if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); }
+
+                        savingsAccountCharge.update(amount, dueDate, null, null);
+
+                        savingsAccountCharges.add(savingsAccountCharge);
+                    }
+                }
+            }
+        }
+
+        return savingsAccountCharges;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductRepository.java
new file mode 100644
index 0000000..072dd9c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductRepository.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.savings.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface SavingsProductRepository extends JpaRepository<SavingsProduct, Long>, JpaSpecificationExecutor<SavingsProduct> {
+    //
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/AnnualCompoundingPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/AnnualCompoundingPeriod.java
new file mode 100644
index 0000000..bc495a3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/AnnualCompoundingPeriod.java
@@ -0,0 +1,181 @@
+/**
+ * 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.savings.domain.interest;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.joda.time.LocalDate;
+
+public class AnnualCompoundingPeriod implements CompoundingPeriod {
+
+    @SuppressWarnings("unused")
+    private final LocalDateInterval periodInterval;
+    private final List<EndOfDayBalance> endOfDayBalances;
+
+    public static AnnualCompoundingPeriod create(final LocalDateInterval periodInterval, final List<EndOfDayBalance> allEndOfDayBalances,
+            final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval, allEndOfDayBalances,
+                upToInterestCalculationDate);
+
+        return new AnnualCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod);
+    }
+
+    @Override
+    public BigDecimal calculateInterest(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestToCompound,
+            final BigDecimal interestRateAsFraction, final long daysInYear, final BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal overdraftInterestRateAsFraction, final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+
+        switch (interestCalculationType) {
+            case DAILY_BALANCE:
+                interestEarned = calculateUsingDailyBalanceMethod(compoundingInterestPeriodType, interestToCompound, interestRateAsFraction,
+                        daysInYear, minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+            break;
+            case AVERAGE_DAILY_BALANCE:
+                interestEarned = calculateUsingAverageDailyBalanceMethod(interestToCompound, interestRateAsFraction, daysInYear,
+                        minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+            break;
+            case INVALID:
+            break;
+        }
+
+        return interestEarned;
+    }
+
+    private BigDecimal calculateUsingAverageDailyBalanceMethod(final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction,
+            final long daysInYear, final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction, 
+            final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal cumulativeBalance = BigDecimal.ZERO;
+        Integer numberOfDays = Integer.valueOf(0);
+
+        for (final EndOfDayBalance balance : this.endOfDayBalances) {
+            final BigDecimal endOfDayCumulativeBalance = balance.cumulativeBalance(interestToCompound);
+            cumulativeBalance = cumulativeBalance.add(endOfDayCumulativeBalance);
+
+            final Integer balanceExistsForNumberOfDays = balance.getNumberOfDays();
+            numberOfDays = numberOfDays + balanceExistsForNumberOfDays;
+        }
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+        if (cumulativeBalance.compareTo(BigDecimal.ZERO) != 0 && numberOfDays > 0) {
+            final BigDecimal averageDailyBalance = cumulativeBalance.divide(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64)
+                    .setScale(9, MoneyHelper.getRoundingMode());
+
+            if(averageDailyBalance.compareTo(BigDecimal.ZERO) >= 0){
+                if (averageDailyBalance.compareTo(minBalanceForInterestCalculation) >= 0) {
+                    final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+                    final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+                    final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64);
+                    interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                            MoneyHelper.getRoundingMode());
+                }
+            }else{
+                if (averageDailyBalance.compareTo(minOverdraftForInterestCalculation.negate()) < 0) {
+                    final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+                    final BigDecimal dailyInterestRate = overdraftInterestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+                    final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64);
+                    interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                            MoneyHelper.getRoundingMode());
+                }
+            }
+        }
+
+        return interestEarned;
+    }
+
+    private BigDecimal calculateUsingDailyBalanceMethod(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, final long daysInYear,
+            final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction, 
+            final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+        BigDecimal interestOnBalanceUnrounded = BigDecimal.ZERO;
+        for (final EndOfDayBalance balance : this.endOfDayBalances) {
+
+            switch (compoundingInterestPeriodType) {
+                case DAILY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound, interestRateAsFraction,
+                            daysInYear, minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case MONTHLY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case QUATERLY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                // case WEEKLY:
+                // break;
+                // case BIWEEKLY:
+                // break;
+                case BI_ANNUAL:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case ANNUAL:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                // case NO_COMPOUNDING_SIMPLE_INTEREST:
+                // break;
+                case INVALID:
+                break;
+            }
+
+            interestEarned = interestEarned.add(interestOnBalanceUnrounded);
+        }
+        return interestEarned;
+    }
+
+    private static List<EndOfDayBalance> endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval,
+            final List<EndOfDayBalance> allEndOfDayBalances, final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesForPeriodInterval = new ArrayList<>();
+
+        for (final EndOfDayBalance endOfDayBalance : allEndOfDayBalances) {
+
+            if (compoundingPeriodInterval.contains(endOfDayBalance.date())) {
+                final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+                endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate);
+            } else if (endOfDayBalance.contains(compoundingPeriodInterval)) {
+                final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+                endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate);
+            }
+        }
+
+        return endOfDayBalancesForPeriodInterval;
+    }
+
+    private AnnualCompoundingPeriod(final LocalDateInterval periodInterval, final List<EndOfDayBalance> endOfDayBalances) {
+        this.periodInterval = periodInterval;
+        this.endOfDayBalances = endOfDayBalances;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/BiAnnualCompoundingPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/BiAnnualCompoundingPeriod.java
new file mode 100644
index 0000000..eeaa236
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/BiAnnualCompoundingPeriod.java
@@ -0,0 +1,181 @@
+/**
+ * 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.savings.domain.interest;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.joda.time.LocalDate;
+
+public class BiAnnualCompoundingPeriod implements CompoundingPeriod {
+
+    @SuppressWarnings("unused")
+    private final LocalDateInterval periodInterval;
+    private final List<EndOfDayBalance> endOfDayBalances;
+
+    public static BiAnnualCompoundingPeriod create(final LocalDateInterval periodInterval, final List<EndOfDayBalance> allEndOfDayBalances,
+            final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval, allEndOfDayBalances,
+                upToInterestCalculationDate);
+
+        return new BiAnnualCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod);
+    }
+
+    @Override
+    public BigDecimal calculateInterest(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestToCompound,
+            final BigDecimal interestRateAsFraction, final long daysInYear, final BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal overdraftInterestRateAsFraction, final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+
+        switch (interestCalculationType) {
+            case DAILY_BALANCE:
+                interestEarned = calculateUsingDailyBalanceMethod(compoundingInterestPeriodType, interestToCompound, interestRateAsFraction,
+                        daysInYear, minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+            break;
+            case AVERAGE_DAILY_BALANCE:
+                interestEarned = calculateUsingAverageDailyBalanceMethod(interestToCompound, interestRateAsFraction, daysInYear,
+                        minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+            break;
+            case INVALID:
+            break;
+        }
+
+        return interestEarned;
+    }
+
+    private BigDecimal calculateUsingAverageDailyBalanceMethod(final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction,
+            final long daysInYear, final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction, 
+            final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal cumulativeBalance = BigDecimal.ZERO;
+        Integer numberOfDays = Integer.valueOf(0);
+
+        for (final EndOfDayBalance balance : this.endOfDayBalances) {
+            final BigDecimal endOfDayCumulativeBalance = balance.cumulativeBalance(interestToCompound);
+            cumulativeBalance = cumulativeBalance.add(endOfDayCumulativeBalance);
+
+            final Integer balanceExistsForNumberOfDays = balance.getNumberOfDays();
+            numberOfDays = numberOfDays + balanceExistsForNumberOfDays;
+        }
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+        if (cumulativeBalance.compareTo(BigDecimal.ZERO) != 0 && numberOfDays > 0) {
+            final BigDecimal averageDailyBalance = cumulativeBalance.divide(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64)
+                    .setScale(9, MoneyHelper.getRoundingMode());
+
+            if(averageDailyBalance.compareTo(BigDecimal.ZERO) >= 0){
+                if (averageDailyBalance.compareTo(minBalanceForInterestCalculation) >= 0) {
+                    final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+                    final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+                    final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64);
+                    interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                            MoneyHelper.getRoundingMode());
+                }
+            }else{
+                if (averageDailyBalance.compareTo(minOverdraftForInterestCalculation.negate()) < 0) {
+                    final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+                    final BigDecimal dailyInterestRate = overdraftInterestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+                    final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64);
+                    interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                            MoneyHelper.getRoundingMode());
+                }
+            }
+        }
+
+        return interestEarned;
+    }
+
+    private BigDecimal calculateUsingDailyBalanceMethod(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, final long daysInYear,
+            final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction, 
+            final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+        BigDecimal interestOnBalanceUnrounded = BigDecimal.ZERO;
+        for (final EndOfDayBalance balance : this.endOfDayBalances) {
+
+            switch (compoundingInterestPeriodType) {
+                case DAILY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound, interestRateAsFraction,
+                            daysInYear, minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case MONTHLY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case QUATERLY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                // case WEEKLY:
+                // break;
+                // case BIWEEKLY:
+                // break;
+                case BI_ANNUAL:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case ANNUAL:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                // case NO_COMPOUNDING_SIMPLE_INTEREST:
+                // break;
+                case INVALID:
+                break;
+            }
+
+            interestEarned = interestEarned.add(interestOnBalanceUnrounded);
+        }
+        return interestEarned;
+    }
+
+    private static List<EndOfDayBalance> endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval,
+            final List<EndOfDayBalance> allEndOfDayBalances, final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesForPeriodInterval = new ArrayList<>();
+
+        for (final EndOfDayBalance endOfDayBalance : allEndOfDayBalances) {
+
+            if (compoundingPeriodInterval.contains(endOfDayBalance.date())) {
+                final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+                endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate);
+            } else if (endOfDayBalance.contains(compoundingPeriodInterval)) {
+                final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+                endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate);
+            }
+        }
+
+        return endOfDayBalancesForPeriodInterval;
+    }
+
+    private BiAnnualCompoundingPeriod(final LocalDateInterval periodInterval, final List<EndOfDayBalance> endOfDayBalances) {
+        this.periodInterval = periodInterval;
+        this.endOfDayBalances = endOfDayBalances;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundInterestHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundInterestHelper.java
new file mode 100644
index 0000000..40b6535
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundInterestHelper.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.domain.interest;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.joda.time.LocalDate;
+
+public class CompoundInterestHelper {
+
+    /**
+     * @param currency
+     * @param allPeriods
+     * @param lockUntil
+     *            - account locked date used with the combination of
+     *            immediateWithdrawalOfInterest to avoid exclusion of
+     *            interestEarned
+     * @param interestTransferEnabled
+     *            - boolean flag used to avoid addition of interest to next
+     *            posting period as income while calculating interest
+     * @return
+     */
+    public Money calculateInterestForAllPostingPeriods(final MonetaryCurrency currency, final List<PostingPeriod> allPeriods,
+            LocalDate lockUntil, Boolean interestTransferEnabled) {
+
+        // sum up the 'rounded' values that are posted each posting period
+        Money interestEarned = Money.zero(currency);
+
+        // total interest earned in previous periods but not yet recognised
+        BigDecimal interestEarnedButNotPosted = BigDecimal.ZERO;
+        for (final PostingPeriod postingPeriod : allPeriods) {
+
+            final BigDecimal interestEarnedThisPeriod = postingPeriod.calculateInterest(interestEarnedButNotPosted);
+
+            final Money moneyToBePostedForPeriod = Money.of(currency, interestEarnedThisPeriod);
+
+            interestEarned = interestEarned.plus(moneyToBePostedForPeriod);
+            // these checks are for fixed deposit account for not include
+            // interest for accounts which has post interest to linked savings
+            // account and if already transfered then it includes in interest
+            // calculation.
+            if (postingPeriod.isInterestTransfered() || !interestTransferEnabled
+                    || (lockUntil != null && !postingPeriod.dateOfPostingTransaction().isAfter(lockUntil))) {
+                interestEarnedButNotPosted = interestEarnedButNotPosted.add(moneyToBePostedForPeriod.getAmount());
+            }
+        }
+
+        return interestEarned;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundingPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundingPeriod.java
new file mode 100644
index 0000000..a236abb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundingPeriod.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.domain.interest;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+
+public interface CompoundingPeriod {
+
+    BigDecimal calculateInterest(SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            SavingsInterestCalculationType interestCalculationType, BigDecimal interestFromPreviousPostingPeriod,
+            BigDecimal interestRateAsFraction, long daysInYear, BigDecimal minBalanceForInterestCalculation, 
+            BigDecimal overdraftInterestRateAsFraction, BigDecimal minOverdraftForInterestCalculation);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/DailyCompoundingPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/DailyCompoundingPeriod.java
new file mode 100644
index 0000000..ade20da
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/DailyCompoundingPeriod.java
@@ -0,0 +1,100 @@
+/**
+ * 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.savings.domain.interest;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.joda.time.LocalDate;
+
+public class DailyCompoundingPeriod implements CompoundingPeriod {
+
+    @SuppressWarnings("unused")
+    private final LocalDateInterval periodInterval;
+    private final List<EndOfDayBalance> endOfDayBalances;
+
+    public static DailyCompoundingPeriod create(final LocalDateInterval periodInterval, final List<EndOfDayBalance> allEndOfDayBalances,
+            final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval,
+                allEndOfDayBalances, upToInterestCalculationDate);
+
+        return new DailyCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod);
+    }
+
+    private static List<EndOfDayBalance> endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval,
+            final List<EndOfDayBalance> allEndOfDayBalances, final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesForPeriodInterval = new ArrayList<>();
+
+        EndOfDayBalance cappedToPeriodEndDate = null;
+
+        for (final EndOfDayBalance endOfDayBalance : allEndOfDayBalances) {
+
+            if (compoundingPeriodInterval.contains(endOfDayBalance.date())) {
+                cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+            } else if (endOfDayBalance.contains(compoundingPeriodInterval)) {
+                cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+            } else {
+                final LocalDateInterval latestPeriod = LocalDateInterval.create(compoundingPeriodInterval.startDate(),
+                        upToInterestCalculationDate);
+                cappedToPeriodEndDate = endOfDayBalance.upTo(latestPeriod, upToInterestCalculationDate);
+            }
+
+            if (cappedToPeriodEndDate != null) {
+                endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate);
+            }
+        }
+
+        return endOfDayBalancesForPeriodInterval;
+    }
+
+    private DailyCompoundingPeriod(final LocalDateInterval periodInterval, final List<EndOfDayBalance> endOfDayBalances) {
+        this.periodInterval = periodInterval;
+        this.endOfDayBalances = endOfDayBalances;
+    }
+
+    @Override
+    public BigDecimal calculateInterest(
+            @SuppressWarnings("unused") final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            @SuppressWarnings("unused") final SavingsInterestCalculationType interestCalculationType,
+            final BigDecimal interestFromPreviousPostingPeriod, final BigDecimal interestRateAsFraction, final long daysInYear,
+            final BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal overdraftInterestRateAsFraction, final BigDecimal minOverdraftForInterestCalculation) {
+        BigDecimal interestEarned = BigDecimal.ZERO;
+
+        // for daily compounding - each interest calculated from previous daily
+        // calculations is 'compounded'
+        BigDecimal interestToCompound = interestFromPreviousPostingPeriod;
+        for (final EndOfDayBalance balance : this.endOfDayBalances) {
+            final BigDecimal interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound,
+                    interestRateAsFraction, daysInYear, minBalanceForInterestCalculation, overdraftInterestRateAsFraction,
+                    minOverdraftForInterestCalculation);
+            interestToCompound = interestToCompound.add(interestOnBalanceUnrounded, MathContext.DECIMAL64).setScale(9);
+            interestEarned = interestEarned.add(interestOnBalanceUnrounded);
+        }
+
+        return interestEarned;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/EndOfDayBalance.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/EndOfDayBalance.java
new file mode 100644
index 0000000..f213681
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/EndOfDayBalance.java
@@ -0,0 +1,181 @@
+/**
+ * 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.savings.domain.interest;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.joda.time.LocalDate;
+
+public class EndOfDayBalance {
+
+    private final LocalDate date;
+    private final Money openingBalance;
+    private final Money endOfDayBalance;
+    private final int numberOfDays;
+
+    public static EndOfDayBalance from(final LocalDate date, final Money openingBalance, final Money endOfDayBalance,
+            final int numberOfDays) {
+        return new EndOfDayBalance(date, openingBalance, endOfDayBalance, numberOfDays);
+    }
+
+    public EndOfDayBalance(final LocalDate date, final Money openingBalance, final Money endOfDayBalance, final int numberOfDays) {
+        this.date = date;
+        this.openingBalance = openingBalance;
+        this.endOfDayBalance = endOfDayBalance;
+        this.numberOfDays = numberOfDays;
+    }
+
+    public LocalDate date() {
+        return this.date;
+    }
+
+    public Money closingBalance() {
+        return this.endOfDayBalance;
+    }
+
+    public BigDecimal cumulativeBalance(final BigDecimal interestToCompound) {
+        final BigDecimal daysAsBigDecimal = BigDecimal.valueOf(this.numberOfDays);
+        final BigDecimal realBalanceForInterestCalculation = this.endOfDayBalance.getAmount().add(interestToCompound);
+        return realBalanceForInterestCalculation.multiply(daysAsBigDecimal, MathContext.DECIMAL64).setScale(9,
+                MoneyHelper.getRoundingMode());
+    }
+
+    public BigDecimal calculateInterestOnBalance(final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction,
+            final long daysInYear, final BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal overdraftInterestRateAsFraction, final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal interest = BigDecimal.ZERO.setScale(9, MoneyHelper.getRoundingMode());
+        final BigDecimal realBalanceForInterestCalculation = this.endOfDayBalance.getAmount().add(interestToCompound);
+        if(realBalanceForInterestCalculation.compareTo(BigDecimal.ZERO) >= 0){
+            if (realBalanceForInterestCalculation.compareTo(minBalanceForInterestCalculation) >= 0) {
+	            final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+	            final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+	            final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(this.numberOfDays), MathContext.DECIMAL64);
+                interest = realBalanceForInterestCalculation.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                        MoneyHelper.getRoundingMode());
+            }
+        } else {
+            if (realBalanceForInterestCalculation.compareTo(minOverdraftForInterestCalculation.negate()) < 0) {
+	            final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+	            final BigDecimal dailyInterestRate = overdraftInterestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+	            final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(this.numberOfDays), MathContext.DECIMAL64);
+                interest = realBalanceForInterestCalculation.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                        MoneyHelper.getRoundingMode());
+            }
+        }
+        return interest;
+    }
+
+    /**
+     * Future Value (FV) = PV x (1+r)^n
+     * 
+     * Interest = FV - PV PV = Principal or the Account Balance r = rate per
+     * compounding period (so for daily, r = nominalInterestRateAsFraction x
+     * 1/365 n = number of periods rate is compounded
+     */
+    public BigDecimal calculateInterestOnBalanceAndInterest(final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction,
+            final long daysInYear, final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction, 
+            final BigDecimal minOverdraftForInterestCalculation) {
+        final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+
+        final BigDecimal presentValue = this.endOfDayBalance.getAmount().add(interestToCompound);
+        BigDecimal futureValue = presentValue.setScale(9, MoneyHelper.getRoundingMode());
+        
+        if(presentValue.compareTo(BigDecimal.ZERO) >= 0){
+            if (presentValue.compareTo(minBalanceForInterestCalculation) >= 0) {
+                final BigDecimal r = interestRateAsFraction.multiply(multiplicand);
+
+                final BigDecimal interestRateForCompoundingPeriodPlusOne = BigDecimal.ONE.add(r);
+
+                final double interestRateForCompoundingPeriodPowered = Math.pow(interestRateForCompoundingPeriodPlusOne.doubleValue(),
+                        Integer.valueOf(this.numberOfDays).doubleValue());
+                futureValue = presentValue.multiply(BigDecimal.valueOf(interestRateForCompoundingPeriodPowered), MathContext.DECIMAL64)
+                        .setScale(9, MoneyHelper.getRoundingMode());
+            }
+        }else{
+            if (presentValue.compareTo(minOverdraftForInterestCalculation.negate()) < 0) {
+                final BigDecimal r = overdraftInterestRateAsFraction.multiply(multiplicand);
+
+                final BigDecimal interestRateForCompoundingPeriodPlusOne = BigDecimal.ONE.add(r);
+
+                final double interestRateForCompoundingPeriodPowered = Math.pow(interestRateForCompoundingPeriodPlusOne.doubleValue(),
+                        Integer.valueOf(this.numberOfDays).doubleValue());
+                futureValue = presentValue.multiply(BigDecimal.valueOf(interestRateForCompoundingPeriodPowered), MathContext.DECIMAL64)
+                        .setScale(9, MoneyHelper.getRoundingMode());
+            }
+        }
+
+        return futureValue.subtract(presentValue);
+    }
+
+    /**
+     * @param compoundingPeriodInterval
+     * @param upToInterestCalculationDate
+     *            : For calculating maturity details in advance
+     *            upToInterestCalculationDate will be maturity date else it will
+     *            be DateUtils.getLocalDateOfTenant().
+     * @return
+     */
+    public EndOfDayBalance upTo(final LocalDateInterval compoundingPeriodInterval, final LocalDate upToInterestCalculationDate) {
+
+        Money startingBalance = this.openingBalance;
+        LocalDate balanceStartDate = this.date;
+
+        LocalDate oldBalanceEndDate = this.date.plusDays(this.numberOfDays - 1);
+
+        int daysOfBalance = this.numberOfDays;
+
+        if (this.date.isBefore(compoundingPeriodInterval.startDate())) {
+            balanceStartDate = compoundingPeriodInterval.startDate();
+            startingBalance = this.endOfDayBalance;
+            final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, oldBalanceEndDate);
+            daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate();
+        }
+
+        LocalDate balanceEndDate = balanceStartDate.plusDays(daysOfBalance - 1);
+        if (balanceEndDate.isAfter(compoundingPeriodInterval.endDate())) {
+            balanceEndDate = compoundingPeriodInterval.endDate();
+            final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, balanceEndDate);
+            daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate();
+        }
+        if (balanceEndDate.isAfter(upToInterestCalculationDate)) {
+            balanceEndDate = upToInterestCalculationDate;
+            final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, balanceEndDate);
+            daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate();
+        }
+
+        return new EndOfDayBalance(balanceStartDate, startingBalance, this.endOfDayBalance, daysOfBalance);
+    }
+
+    public boolean contains(final LocalDateInterval compoundingPeriodInterval) {
+
+        final LocalDate balanceUpToDate = this.date.plusDays(this.numberOfDays - 1);
+
+        final LocalDateInterval balanceInterval = LocalDateInterval.create(this.date, balanceUpToDate);
+        return balanceInterval.containsPortionOf(compoundingPeriodInterval);
+    }
+
+    public Integer getNumberOfDays() {
+        return Integer.valueOf(this.numberOfDays);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/MonthlyCompoundingPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/MonthlyCompoundingPeriod.java
new file mode 100644
index 0000000..8ead199
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/MonthlyCompoundingPeriod.java
@@ -0,0 +1,177 @@
+/**
+ * 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.savings.domain.interest;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.joda.time.LocalDate;
+
+public class MonthlyCompoundingPeriod implements CompoundingPeriod {
+
+    @SuppressWarnings("unused")
+    private final LocalDateInterval periodInterval;
+    private final List<EndOfDayBalance> endOfDayBalances;
+
+    public static MonthlyCompoundingPeriod create(final LocalDateInterval periodInterval, final List<EndOfDayBalance> allEndOfDayBalances,
+            final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval, allEndOfDayBalances,
+                upToInterestCalculationDate);
+
+        return new MonthlyCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod);
+    }
+
+    @Override
+    public BigDecimal calculateInterest(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestToCompound,
+            final BigDecimal interestRateAsFraction, final long daysInYear, final BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal overdraftInterestRateAsFraction, final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+
+        switch (interestCalculationType) {
+            case DAILY_BALANCE:
+                interestEarned = calculateUsingDailyBalanceMethod(compoundingInterestPeriodType, interestToCompound, interestRateAsFraction,
+                        daysInYear, minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+            break;
+            case AVERAGE_DAILY_BALANCE:
+                interestEarned = calculateUsingAverageDailyBalanceMethod(interestToCompound, interestRateAsFraction, daysInYear,
+                        minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+            break;
+            case INVALID:
+            break;
+        }
+
+        return interestEarned;
+    }
+
+    private BigDecimal calculateUsingAverageDailyBalanceMethod(final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction,
+            final long daysInYear, final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction, 
+            final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal cumulativeBalance = BigDecimal.ZERO;
+        Integer numberOfDays = Integer.valueOf(0);
+
+        for (final EndOfDayBalance balance : this.endOfDayBalances) {
+            final BigDecimal endOfDayCumulativeBalance = balance.cumulativeBalance(interestToCompound);
+            cumulativeBalance = cumulativeBalance.add(endOfDayCumulativeBalance);
+
+            final Integer balanceExistsForNumberOfDays = balance.getNumberOfDays();
+            numberOfDays = numberOfDays + balanceExistsForNumberOfDays;
+        }
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+        if (cumulativeBalance.compareTo(BigDecimal.ZERO) != 0 && numberOfDays > 0) {
+            final BigDecimal averageDailyBalance = cumulativeBalance.divide(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64)
+                    .setScale(9, MoneyHelper.getRoundingMode());
+
+            if(averageDailyBalance.compareTo(BigDecimal.ZERO) >= 0){
+                if (averageDailyBalance.compareTo(minBalanceForInterestCalculation) >= 0) {
+                    final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+                    final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+                    final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64);
+                    interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                            MoneyHelper.getRoundingMode());
+                }
+            }else{
+                if (averageDailyBalance.compareTo(minOverdraftForInterestCalculation.negate()) < 0) {
+                    final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+                    final BigDecimal dailyInterestRate = overdraftInterestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+                    final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64);
+                    interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                            MoneyHelper.getRoundingMode());
+                }
+            }
+        }
+
+        return interestEarned;
+    }
+
+    private BigDecimal calculateUsingDailyBalanceMethod(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, final long daysInYear,
+            final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction, 
+            final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+        BigDecimal interestOnBalanceUnrounded = BigDecimal.ZERO;
+        for (final EndOfDayBalance balance : this.endOfDayBalances) {
+
+            switch (compoundingInterestPeriodType) {
+                case DAILY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound, interestRateAsFraction,
+                            daysInYear, minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case MONTHLY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                // case QUATERLY:
+                // break;
+                // case WEEKLY:
+                // break;
+                // case BIWEEKLY:
+                // break;
+                // case BI_ANNUAL:
+                // break;
+                // case ANNUAL:
+                // break;
+                // case NO_COMPOUNDING_SIMPLE_INTEREST:
+                // break;
+                case INVALID:
+                break;
+                default:
+                break;
+            }
+
+            interestEarned = interestEarned.add(interestOnBalanceUnrounded);
+        }
+        return interestEarned;
+    }
+
+    private static List<EndOfDayBalance> endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval,
+            final List<EndOfDayBalance> allEndOfDayBalances, final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesForPeriodInterval = new ArrayList<>();
+
+        for (final EndOfDayBalance endOfDayBalance : allEndOfDayBalances) {
+
+            if (compoundingPeriodInterval.contains(endOfDayBalance.date())) {
+                final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+                endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate);
+            } else if (endOfDayBalance.contains(compoundingPeriodInterval)) {
+                final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+                endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate);
+            }
+        }
+
+        return endOfDayBalancesForPeriodInterval;
+    }
+
+    private MonthlyCompoundingPeriod(final LocalDateInterval periodInterval, final List<EndOfDayBalance> endOfDayBalances) {
+        this.periodInterval = periodInterval;
+        this.endOfDayBalances = endOfDayBalances;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java
new file mode 100644
index 0000000..26b7310
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java
@@ -0,0 +1,413 @@
+/**
+ * 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.savings.domain.interest;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+public class PostingPeriod {
+
+    @SuppressWarnings("unused")
+    private final LocalDateInterval periodInterval;
+    private final MonetaryCurrency currency;
+    private final SavingsCompoundingInterestPeriodType interestCompoundingType;
+    private final BigDecimal interestRateAsFraction;
+    private final long daysInYear;
+    private final List<CompoundingPeriod> compoundingPeriods;
+
+    // interest posting details
+    private final LocalDate dateOfPostingTransaction;
+    private BigDecimal interestEarnedUnrounded;
+    private Money interestEarnedRounded;
+
+    // opening/closing details
+    private final Money openingBalance;
+    private final Money closingBalance;
+    private final SavingsInterestCalculationType interestCalculationType;
+
+    // include in compounding interest
+    private boolean interestTransfered = false;
+
+    // minimum balance for interest calculation
+    private final Money minBalanceForInterestCalculation;
+	private BigDecimal overdraftInterestRateAsFraction;
+	private Money minOverdraftForInterestCalculation;
+
+    public static PostingPeriod createFrom(final LocalDateInterval periodInterval, final Money periodStartingBalance,
+            final List<SavingsAccountTransaction> orderedListOfTransactions, final MonetaryCurrency currency,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear,
+            final LocalDate upToInterestCalculationDate, Collection<Long> interestPostTransactions, boolean isInterestTransfer,
+            final Money minBalanceForInterestCalculation, final boolean isSavingsInterestPostingAtCurrentPeriodEnd) {
+
+    	final BigDecimal overdraftInterestRateAsFraction = BigDecimal.ZERO;
+    	final Money minOverdraftForInterestCalculation = Money.zero(currency);
+    	
+    	return createFrom(periodInterval, periodStartingBalance, orderedListOfTransactions, currency, 
+    			interestCompoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYear, 
+    			upToInterestCalculationDate, interestPostTransactions, isInterestTransfer, 
+    			minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, 
+    			overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+    }
+
+    // isInterestTransfer boolean is to identify newly created transaction is
+    // interest transfer
+    public static PostingPeriod createFrom(final LocalDateInterval periodInterval, final Money periodStartingBalance,
+            final List<SavingsAccountTransaction> orderedListOfTransactions, final MonetaryCurrency currency,
+            final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
+            final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear,
+            final LocalDate upToInterestCalculationDate, Collection<Long> interestPostTransactions, boolean isInterestTransfer,
+            final Money minBalanceForInterestCalculation, final boolean isSavingsInterestPostingAtCurrentPeriodEnd,
+            final BigDecimal overdraftInterestRateAsFraction, final Money minOverdraftForInterestCalculation) {
+
+        final List<EndOfDayBalance> accountEndOfDayBalances = new ArrayList<>();
+        boolean interestTransfered = false;
+        Money openingDayBalance = periodStartingBalance;
+        Money closeOfDayBalance = openingDayBalance;
+        for (final SavingsAccountTransaction transaction : orderedListOfTransactions) {
+
+            if (transaction.fallsWithin(periodInterval)) {
+                // the balance of the transaction falls entirely within this
+                // period so no need to do any cropping/bounding
+                final EndOfDayBalance endOfDayBalance = transaction.toEndOfDayBalance(openingDayBalance);
+                accountEndOfDayBalances.add(endOfDayBalance);
+
+                openingDayBalance = endOfDayBalance.closingBalance();
+
+            } else if (transaction.spansAnyPortionOf(periodInterval)) {
+                final EndOfDayBalance endOfDayBalance = transaction.toEndOfDayBalanceBoundedBy(openingDayBalance, periodInterval);
+                accountEndOfDayBalances.add(endOfDayBalance);
+
+                closeOfDayBalance = endOfDayBalance.closingBalance();
+                openingDayBalance = closeOfDayBalance;
+            }
+
+            // this check is to make sure to add interest if withdrawal is
+            // happened for already
+            if (transaction.occursOn(periodInterval.endDate().plusDays(1))) {
+                if (transaction.getId() == null) {
+                    interestTransfered = isInterestTransfer;
+                } else if (interestPostTransactions.contains(transaction.getId())) {
+                    interestTransfered = true;
+                }
+            }
+
+        }
+
+        if (accountEndOfDayBalances.isEmpty()) {
+            LocalDate balanceStartDate = periodInterval.startDate();
+            LocalDate balanceEndDate = periodInterval.endDate();
+            Integer numberOfDaysOfBalance = periodInterval.daysInPeriodInclusiveOfEndDate();
+
+            if (balanceEndDate.isAfter(upToInterestCalculationDate)) {
+                balanceEndDate = upToInterestCalculationDate;
+                final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate);
+                numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate();
+            }
+
+            final EndOfDayBalance endOfDayBalance = EndOfDayBalance.from(balanceStartDate, openingDayBalance, closeOfDayBalance,
+                    numberOfDaysOfBalance);
+
+            accountEndOfDayBalances.add(endOfDayBalance);
+
+            closeOfDayBalance = endOfDayBalance.closingBalance();
+            openingDayBalance = closeOfDayBalance;
+        }
+
+        final List<CompoundingPeriod> compoundingPeriods = compoundingPeriodsInPostingPeriod(periodInterval, interestCompoundingPeriodType,
+                accountEndOfDayBalances, upToInterestCalculationDate);
+
+        return new PostingPeriod(periodInterval, currency, periodStartingBalance, openingDayBalance, interestCompoundingPeriodType,
+                interestCalculationType, interestRateAsFraction, daysInYear, compoundingPeriods, interestTransfered,
+                minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd,
+                overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+    }
+
+    private PostingPeriod(final LocalDateInterval periodInterval, final MonetaryCurrency currency, final Money openingBalance,
+            final Money closingBalance, final SavingsCompoundingInterestPeriodType interestCompoundingType,
+            final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear,
+            final List<CompoundingPeriod> compoundingPeriods, boolean interestTransfered, final Money minBalanceForInterestCalculation,
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final BigDecimal overdraftInterestRateAsFraction, 
+            final Money minOverdraftForInterestCalculation) {
+        this.periodInterval = periodInterval;
+        this.currency = currency;
+        this.openingBalance = openingBalance;
+        this.closingBalance = closingBalance;
+        this.interestCompoundingType = interestCompoundingType;
+        this.interestCalculationType = interestCalculationType;
+        this.interestRateAsFraction = interestRateAsFraction;
+        this.daysInYear = daysInYear;
+        this.compoundingPeriods = compoundingPeriods;
+
+        if (isSavingsInterestPostingAtCurrentPeriodEnd)
+            this.dateOfPostingTransaction = periodInterval.endDate();
+        else
+            this.dateOfPostingTransaction = periodInterval.endDate().plusDays(1);
+        this.interestTransfered = interestTransfered;
+        this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
+        this.overdraftInterestRateAsFraction = overdraftInterestRateAsFraction;
+        this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation;
+    }
+
+    public Money interest() {
+        return this.interestEarnedRounded;
+    }
+
+    public LocalDate dateOfPostingTransaction() {
+        return this.dateOfPostingTransaction;
+    }
+
+    public Money closingBalance() {
+        return this.closingBalance;
+    }
+
+    public Money openingBalance() {
+        return this.openingBalance;
+    }
+
+    public BigDecimal calculateInterest(final BigDecimal interestFromPreviousPostingPeriod) {
+        BigDecimal interestEarned = BigDecimal.ZERO;
+
+        // for each compounding period accumulate the amount of interest
+        // to be applied to the balanced for interest calculation
+        BigDecimal interestCompounded = interestFromPreviousPostingPeriod;
+        for (final CompoundingPeriod compoundingPeriod : this.compoundingPeriods) {
+
+            final BigDecimal interestUnrounded = compoundingPeriod.calculateInterest(this.interestCompoundingType,
+                    this.interestCalculationType, interestCompounded, this.interestRateAsFraction, this.daysInYear,
+                    this.minBalanceForInterestCalculation.getAmount(), 	this.overdraftInterestRateAsFraction,
+                    this.minOverdraftForInterestCalculation.getAmount());
+            interestCompounded = interestCompounded.add(interestUnrounded);
+            interestEarned = interestEarned.add(interestUnrounded);
+        }
+
+        this.interestEarnedUnrounded = interestEarned;
+        this.interestEarnedRounded = Money.of(this.currency, this.interestEarnedUnrounded);
+
+        return interestEarned;
+    }
+
+    public Money getInterestEarned() {
+        return this.interestEarnedRounded;
+    }
+
+    private static List<CompoundingPeriod> compoundingPeriodsInPostingPeriod(final LocalDateInterval postingPeriodInterval,
+            final SavingsCompoundingInterestPeriodType interestPeriodType, final List<EndOfDayBalance> allEndOfDayBalances,
+            final LocalDate upToInterestCalculationDate) {
+
+        final List<CompoundingPeriod> compoundingPeriods = new ArrayList<>();
+
+        CompoundingPeriod compoundingPeriod = null;
+        switch (interestPeriodType) {
+            case INVALID:
+            break;
+            case DAILY:
+                compoundingPeriod = DailyCompoundingPeriod.create(postingPeriodInterval, allEndOfDayBalances, upToInterestCalculationDate);
+                compoundingPeriods.add(compoundingPeriod);
+            break;
+            case MONTHLY:
+
+                final LocalDate postingPeriodEndDate = postingPeriodInterval.endDate();
+
+                LocalDate periodStartDate = postingPeriodInterval.startDate();
+                LocalDate periodEndDate = periodStartDate;
+
+                while (!periodStartDate.isAfter(postingPeriodEndDate) && !periodEndDate.isAfter(postingPeriodEndDate)) {
+
+                    periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate);
+                    if (periodEndDate.isAfter(postingPeriodEndDate)) {
+                        periodEndDate = postingPeriodEndDate;
+                    }
+
+                    final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate);
+                    if (postingPeriodInterval.contains(compoundingPeriodInterval)) {
+
+                        compoundingPeriod = MonthlyCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances,
+                                upToInterestCalculationDate);
+                        compoundingPeriods.add(compoundingPeriod);
+                    }
+
+                    // move periodStartDate forward to day after this period
+                    periodStartDate = periodEndDate.plusDays(1);
+                }
+            break;
+            // case WEEKLY:
+            // break;
+            // case BIWEEKLY:
+            // break;
+            case QUATERLY:
+                final LocalDate qPostingPeriodEndDate = postingPeriodInterval.endDate();
+
+                periodStartDate = postingPeriodInterval.startDate();
+                periodEndDate = periodStartDate;
+
+                while (!periodStartDate.isAfter(qPostingPeriodEndDate) && !periodEndDate.isAfter(qPostingPeriodEndDate)) {
+
+                    periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate);
+                    if (periodEndDate.isAfter(qPostingPeriodEndDate)) {
+                        periodEndDate = qPostingPeriodEndDate;
+                    }
+
+                    final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate);
+                    if (postingPeriodInterval.contains(compoundingPeriodInterval)) {
+
+                        compoundingPeriod = QuarterlyCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances,
+                                upToInterestCalculationDate);
+                        compoundingPeriods.add(compoundingPeriod);
+                    }
+
+                    // move periodStartDate forward to day after this period
+                    periodStartDate = periodEndDate.plusDays(1);
+                }
+            break;
+            case BI_ANNUAL:
+                final LocalDate bPostingPeriodEndDate = postingPeriodInterval.endDate();
+
+                periodStartDate = postingPeriodInterval.startDate();
+                periodEndDate = periodStartDate;
+
+                while (!periodStartDate.isAfter(bPostingPeriodEndDate) && !periodEndDate.isAfter(bPostingPeriodEndDate)) {
+
+                    periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate);
+                    if (periodEndDate.isAfter(bPostingPeriodEndDate)) {
+                        periodEndDate = bPostingPeriodEndDate;
+                    }
+
+                    final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate);
+                    if (postingPeriodInterval.contains(compoundingPeriodInterval)) {
+
+                        compoundingPeriod = BiAnnualCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances,
+                                upToInterestCalculationDate);
+                        compoundingPeriods.add(compoundingPeriod);
+                    }
+
+                    // move periodStartDate forward to day after this period
+                    periodStartDate = periodEndDate.plusDays(1);
+                }
+            break;
+            case ANNUAL:
+                final LocalDate aPostingPeriodEndDate = postingPeriodInterval.endDate();
+
+                periodStartDate = postingPeriodInterval.startDate();
+                periodEndDate = periodStartDate;
+
+                while (!periodStartDate.isAfter(aPostingPeriodEndDate) && !periodEndDate.isAfter(aPostingPeriodEndDate)) {
+
+                    periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate);
+                    if (periodEndDate.isAfter(aPostingPeriodEndDate)) {
+                        periodEndDate = aPostingPeriodEndDate;
+                    }
+
+                    final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate);
+                    if (postingPeriodInterval.contains(compoundingPeriodInterval)) {
+
+                        compoundingPeriod = AnnualCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances,
+                                upToInterestCalculationDate);
+                        compoundingPeriods.add(compoundingPeriod);
+                    }
+
+                    // move periodStartDate forward to day after this period
+                    periodStartDate = periodEndDate.plusDays(1);
+                }
+            break;
+        // case NO_COMPOUNDING_SIMPLE_INTEREST:
+        // break;
+        }
+
+        return compoundingPeriods;
+    }
+
+    private static LocalDate determineInterestPeriodEndDateFrom(final LocalDate periodStartDate,
+            final SavingsCompoundingInterestPeriodType interestPeriodType, final LocalDate upToInterestCalculationDate) {
+
+        LocalDate periodEndDate = upToInterestCalculationDate;
+
+        switch (interestPeriodType) {
+            case INVALID:
+            break;
+            case DAILY:
+                periodEndDate = periodStartDate;
+            break;
+            // case WEEKLY:
+            // periodEndDate = periodStartDate.dayOfWeek().withMaximumValue();
+            // break;
+            // case BIWEEKLY:
+            // final LocalDate closestEndOfWeek =
+            // periodStartDate.dayOfWeek().withMaximumValue();
+            // periodEndDate = closestEndOfWeek.plusWeeks(1);
+            // break;
+            case MONTHLY:
+                // produce period end date on last day of current month
+                periodEndDate = periodStartDate.dayOfMonth().withMaximumValue();
+            break;
+            case QUATERLY:
+                // // jan 1st to mar 31st, 1st apr to jun 30, jul 1st to sept
+                // 30,
+                // // oct 1st to dec 31
+                int year = periodStartDate.getYearOfEra();
+                int monthofYear = periodStartDate.getMonthOfYear();
+                if (monthofYear <= 3) {
+                    periodEndDate = new DateTime().withDate(year, 3, 31).toLocalDate();
+                } else if (monthofYear <= 6) {
+                    periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate();
+                } else if (monthofYear <= 9) {
+                    periodEndDate = new DateTime().withDate(year, 9, 30).toLocalDate();
+                } else if (monthofYear <= 12) {
+                    periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate();
+                }
+            break;
+            case BI_ANNUAL:
+                // // jan 1st to 30, jul 1st to dec 31
+                year = periodStartDate.getYearOfEra();
+                monthofYear = periodStartDate.getMonthOfYear();
+                if (monthofYear <= 6) {
+                    periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate();
+                } else if (monthofYear <= 12) {
+                    periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate();
+                }
+            break;
+            case ANNUAL:
+                periodEndDate = periodStartDate.monthOfYear().withMaximumValue();
+                periodEndDate = periodEndDate.dayOfMonth().withMaximumValue();
+            break;
+
+        // case NO_COMPOUNDING_SIMPLE_INTEREST:
+        // periodEndDate = periodStartDate.monthOfYear().withMaximumValue();
+        // periodEndDate = periodEndDate.dayOfMonth().withMaximumValue();
+        // break;
+        }
+
+        return periodEndDate;
+    }
+
+    public boolean isInterestTransfered() {
+        return this.interestTransfered;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/QuarterlyCompoundingPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/QuarterlyCompoundingPeriod.java
new file mode 100644
index 0000000..ddead71
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/QuarterlyCompoundingPeriod.java
@@ -0,0 +1,181 @@
+/**
+ * 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.savings.domain.interest;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.joda.time.LocalDate;
+
+public class QuarterlyCompoundingPeriod implements CompoundingPeriod {
+
+    @SuppressWarnings("unused")
+    private final LocalDateInterval periodInterval;
+    private final List<EndOfDayBalance> endOfDayBalances;
+
+    public static QuarterlyCompoundingPeriod create(final LocalDateInterval periodInterval, final List<EndOfDayBalance> allEndOfDayBalances,
+            final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval, allEndOfDayBalances,
+                upToInterestCalculationDate);
+
+        return new QuarterlyCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod);
+    }
+
+    @Override
+    public BigDecimal calculateInterest(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestToCompound,
+            final BigDecimal interestRateAsFraction, final long daysInYear, final BigDecimal minBalanceForInterestCalculation,
+            final BigDecimal overdraftInterestRateAsFraction, final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+
+        switch (interestCalculationType) {
+            case DAILY_BALANCE:
+                interestEarned = calculateUsingDailyBalanceMethod(compoundingInterestPeriodType, interestToCompound, interestRateAsFraction,
+                        daysInYear, minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+            break;
+            case AVERAGE_DAILY_BALANCE:
+                interestEarned = calculateUsingAverageDailyBalanceMethod(interestToCompound, interestRateAsFraction, daysInYear,
+                        minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+            break;
+            case INVALID:
+            break;
+        }
+
+        return interestEarned;
+    }
+
+    private BigDecimal calculateUsingAverageDailyBalanceMethod(final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction,
+            final long daysInYear, final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction, 
+            final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal cumulativeBalance = BigDecimal.ZERO;
+        Integer numberOfDays = Integer.valueOf(0);
+
+        for (final EndOfDayBalance balance : this.endOfDayBalances) {
+            final BigDecimal endOfDayCumulativeBalance = balance.cumulativeBalance(interestToCompound);
+            cumulativeBalance = cumulativeBalance.add(endOfDayCumulativeBalance);
+
+            final Integer balanceExistsForNumberOfDays = balance.getNumberOfDays();
+            numberOfDays = numberOfDays + balanceExistsForNumberOfDays;
+        }
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+        if (cumulativeBalance.compareTo(BigDecimal.ZERO) != 0 && numberOfDays > 0) {
+            final BigDecimal averageDailyBalance = cumulativeBalance.divide(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64)
+                    .setScale(9, MoneyHelper.getRoundingMode());
+
+            if(averageDailyBalance.compareTo(BigDecimal.ZERO) >= 0){
+                if (averageDailyBalance.compareTo(minBalanceForInterestCalculation) >= 0) {
+                    final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+                    final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+                    final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64);
+                    interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                            MoneyHelper.getRoundingMode());
+                }
+            }else{
+                if (averageDailyBalance.compareTo(minOverdraftForInterestCalculation.negate()) < 0) {
+                    final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64);
+                    final BigDecimal dailyInterestRate = overdraftInterestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+                    final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64);
+                    interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9,
+                            MoneyHelper.getRoundingMode());
+                }
+            }
+        }
+
+        return interestEarned;
+    }
+
+    private BigDecimal calculateUsingDailyBalanceMethod(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType,
+            final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, final long daysInYear,
+            final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction, 
+            final BigDecimal minOverdraftForInterestCalculation) {
+
+        BigDecimal interestEarned = BigDecimal.ZERO;
+        BigDecimal interestOnBalanceUnrounded = BigDecimal.ZERO;
+        for (final EndOfDayBalance balance : this.endOfDayBalances) {
+
+            switch (compoundingInterestPeriodType) {
+                case DAILY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound, interestRateAsFraction,
+                            daysInYear, minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case MONTHLY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case QUATERLY:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                // case WEEKLY:
+                // break;
+                // case BIWEEKLY:
+                // break;
+                case BI_ANNUAL:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                case ANNUAL:
+                    interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear,
+                            minBalanceForInterestCalculation, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation);
+                break;
+                // case NO_COMPOUNDING_SIMPLE_INTEREST:
+                // break;
+                case INVALID:
+                break;
+            }
+
+            interestEarned = interestEarned.add(interestOnBalanceUnrounded);
+        }
+        return interestEarned;
+    }
+
+    private static List<EndOfDayBalance> endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval,
+            final List<EndOfDayBalance> allEndOfDayBalances, final LocalDate upToInterestCalculationDate) {
+
+        final List<EndOfDayBalance> endOfDayBalancesForPeriodInterval = new ArrayList<>();
+
+        for (final EndOfDayBalance endOfDayBalance : allEndOfDayBalances) {
+
+            if (compoundingPeriodInterval.contains(endOfDayBalance.date())) {
+                final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+                endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate);
+            } else if (endOfDayBalance.contains(compoundingPeriodInterval)) {
+                final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate);
+                endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate);
+            }
+        }
+
+        return endOfDayBalancesForPeriodInterval;
+    }
+
+    private QuarterlyCompoundingPeriod(final LocalDateInterval periodInterval, final List<EndOfDayBalance> endOfDayBalances) {
+        this.periodInterval = periodInterval;
+        this.endOfDayBalances = endOfDayBalances;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/DepositAccountInterestRateChartNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/DepositAccountInterestRateChartNotFoundException.java
new file mode 100644
index 0000000..c843143
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/DepositAccountInterestRateChartNotFoundException.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.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class DepositAccountInterestRateChartNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public DepositAccountInterestRateChartNotFoundException(final Long id) {
+        super("error.msg.deposit.account.interest.rate.chart.id.invalid", "Deposit Account Interest rate chart with identifier " + id
+                + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/DepositAccountNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/DepositAccountNotFoundException.java
new file mode 100644
index 0000000..14ac55f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/DepositAccountNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.exception;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+
+public class DepositAccountNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public DepositAccountNotFoundException(final DepositAccountType accountType, final Long id) {
+        super("error.msg." + accountType.getCode().toLowerCase() + ".id.invalid", StringUtils.capitalize(accountType.toString()
+                .toLowerCase()) + " account with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/DepositAccountTransactionNotAllowedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/DepositAccountTransactionNotAllowedException.java
new file mode 100644
index 0000000..5002276
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/DepositAccountTransactionNotAllowedException.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.portfolio.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+
+/**
+ * A {@link RuntimeException} thrown when deposit account transaction not
+ * allowed.
+ */
+public class DepositAccountTransactionNotAllowedException extends AbstractPlatformServiceUnavailableException {
+
+    public DepositAccountTransactionNotAllowedException(final Long accountId, final String action, final DepositAccountType type) {
+        super("error.msg." + type.resourceName() + ".account.trasaction." + action + ".notallowed", SavingsEnumerations.depositType(type)
+                .getValue() + "account " + action + " transaction not allowed with account identifier " + accountId, accountId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/FixedDepositProductNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/FixedDepositProductNotFoundException.java
new file mode 100644
index 0000000..786a950
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/FixedDepositProductNotFoundException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class FixedDepositProductNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public FixedDepositProductNotFoundException(final Long id) {
+        super("error.msg.fixeddepositproduct.id.invalid", "Fixed deposit product with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/InsufficientAccountBalanceException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/InsufficientAccountBalanceException.java
new file mode 100644
index 0000000..dafca7e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/InsufficientAccountBalanceException.java
@@ -0,0 +1,37 @@
+/**
+ * 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.savings.exception;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * Thrown when an attempt is made to withdraw money that is greater than the
+ * account balance.
+ */
+public class InsufficientAccountBalanceException extends AbstractPlatformDomainRuleException {
+
+    public InsufficientAccountBalanceException(final String paramName, final BigDecimal accountBalance, final BigDecimal withdrawalFee,
+            final BigDecimal transactionAmount) {
+        super(withdrawalFee != null ? "error.msg.savingsaccount.transaction.insufficient.account.balance.withdraw"
+                : "error.msg.savingsaccount.transaction.insufficient.account.balance", "Insufficient account balance.", paramName,
+                accountBalance, withdrawalFee, transactionAmount);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/RecurringDepositProductNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/RecurringDepositProductNotFoundException.java
new file mode 100644
index 0000000..9e7645c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/RecurringDepositProductNotFoundException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class RecurringDepositProductNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public RecurringDepositProductNotFoundException(final Long id) {
+        super("error.msg.recurringdepositproduct.id.invalid", "Recurring deposit product with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountClosingNotAllowedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountClosingNotAllowedException.java
new file mode 100755
index 0000000..af5431f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountClosingNotAllowedException.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.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SavingsAccountClosingNotAllowedException extends AbstractPlatformDomainRuleException {
+
+    public SavingsAccountClosingNotAllowedException(final String entity, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + ".saving.account.close.notallowed", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountNotFoundException.java
new file mode 100644
index 0000000..e92347d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountNotFoundException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class SavingsAccountNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public SavingsAccountNotFoundException(final Long id) {
+        super("error.msg.saving.account.id.invalid", "Savings account with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountTransactionNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountTransactionNotFoundException.java
new file mode 100755
index 0000000..69eab87
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountTransactionNotFoundException.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.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class SavingsAccountTransactionNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public SavingsAccountTransactionNotFoundException(final Long savingsId, final Long transactionId) {
+        super("error.msg.saving.account.trasaction.id.invalid", "Savings account with savings identifier " + savingsId
+                + " and trasaction identifier " + transactionId + " does not exist", savingsId, transactionId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsActivityPriorToClientTransferException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsActivityPriorToClientTransferException.java
new file mode 100644
index 0000000..2a89dbb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsActivityPriorToClientTransferException.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.portfolio.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown an action to transition a
+ * loan from one state to another violates a domain rule.
+ */
+public class SavingsActivityPriorToClientTransferException extends AbstractPlatformDomainRuleException {
+
+    public SavingsActivityPriorToClientTransferException(final String action, final Object... defaultUserMessageArgs) {
+        super("error.msg.savings." + action + "." + "not.permitted.before.client.transfer.date",
+                "Transactions on savings account prior to the customer joining date are not permitted", defaultUserMessageArgs);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerAssignmentDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerAssignmentDateException.java
new file mode 100644
index 0000000..1449f3b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerAssignmentDateException.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.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SavingsOfficerAssignmentDateException extends AbstractPlatformDomainRuleException {
+
+    public SavingsOfficerAssignmentDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.savings.assignment.date." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerAssignmentException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerAssignmentException.java
new file mode 100644
index 0000000..80f11c1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerAssignmentException.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.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SavingsOfficerAssignmentException extends AbstractPlatformDomainRuleException {
+
+    public SavingsOfficerAssignmentException(final Long accountId, final Long fromSavingsOfficerId) {
+        super("error.msg.savings.account.not.assigned.to.savings.officer", "Savings Account Identifier " + accountId
+                + " is not assigned to Savings Officer with identifier " + fromSavingsOfficerId + ".", accountId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerUnassignmentDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerUnassignmentDateException.java
new file mode 100644
index 0000000..a6add1f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerUnassignmentDateException.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.savings.exception;
+
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SavingsOfficerUnassignmentDateException extends AbstractPlatformDomainRuleException {
+
+    public SavingsOfficerUnassignmentDateException(final String postFix, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg.savings.savingsofficer.unassign.date." + postFix, defaultUserMessage, defaultUserMessageArgs);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerUnassignmentException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerUnassignmentException.java
new file mode 100644
index 0000000..8bf9150
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsOfficerUnassignmentException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SavingsOfficerUnassignmentException extends AbstractPlatformDomainRuleException {
+
+    public SavingsOfficerUnassignmentException(final Long accountId) {
+        super("error.msg.savings.account.not.assigned.to.savings.officer", "Savings Account Identifier" + accountId + " is not assigned to any savings officer.");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsProductNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsProductNotFoundException.java
new file mode 100644
index 0000000..8f6bed3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsProductNotFoundException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class SavingsProductNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public SavingsProductNotFoundException(final Long id) {
+        super("error.msg.savingproduct.id.invalid", "Saving product with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsTransferTransactionsCannotBeUndoneException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsTransferTransactionsCannotBeUndoneException.java
new file mode 100644
index 0000000..65d7c3b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsTransferTransactionsCannotBeUndoneException.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.portfolio.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown an action to transition a
+ * loan from one state to another violates a domain rule.
+ */
+public class SavingsTransferTransactionsCannotBeUndoneException extends AbstractPlatformDomainRuleException {
+
+    public SavingsTransferTransactionsCannotBeUndoneException(final Long transactionId) {
+        super("error.msg.savings.transfer.transactions.cannot.be.undone",
+                "Transactions related to savings account transfers cannot be undone", transactionId);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/TransactionUpdateNotAllowedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/TransactionUpdateNotAllowedException.java
new file mode 100755
index 0000000..7a052c3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/TransactionUpdateNotAllowedException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.savings.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
+
+/**
+ * A {@link RuntimeException} thrown when update not allowed.
+ */
+public class TransactionUpdateNotAllowedException extends AbstractPlatformServiceUnavailableException {
+
+    public TransactionUpdateNotAllowedException(final Long savingsId, final Long transactionId) {
+        super("error.msg.saving.account.trasaction.update.notallowed",
+                "Savings Account transaction update not allowed with savings identifier " + savingsId + " and trasaction identifier "
+                        + transactionId, savingsId, transactionId);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ActivateFixedDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ActivateFixedDepositAccountCommandHandler.java
new file mode 100644
index 0000000..8b8a2c7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ActivateFixedDepositAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "ACTIVATE")
+public class ActivateFixedDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public ActivateFixedDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.activateFDAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ActivateRecurringDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ActivateRecurringDepositAccountCommandHandler.java
new file mode 100644
index 0000000..f6634ad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ActivateRecurringDepositAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "ACTIVATE")
+public class ActivateRecurringDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public ActivateRecurringDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.activateRDAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ActivateSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ActivateSavingsAccountCommandHandler.java
new file mode 100644
index 0000000..3f7d467
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ActivateSavingsAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "ACTIVATE")
+public class ActivateSavingsAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public ActivateSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) {
+        this.writePlatformService = savingAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.activate(command.getSavingsId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/AddSavingsAccountChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/AddSavingsAccountChargeCommandHandler.java
new file mode 100644
index 0000000..c14967f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/AddSavingsAccountChargeCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNTCHARGE", action = "CREATE")
+public class AddSavingsAccountChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public AddSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.addSavingsAccountCharge(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java
new file mode 100644
index 0000000..bf1253f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "APPLYANNUALFEE")
+public class ApplyAnnualFeeSavingsAccountCommandHandler implements NewCommandSourceHandler {
+
+    @SuppressWarnings("unused")
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public ApplyAnnualFeeSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        @SuppressWarnings("unused")
+        final LocalDate annualFeeTransactionDate = command.localDateValueOfParameterNamed("annualFeeTransactionDate");
+
+        // return
+        // this.writePlatformService.applyAnnualFee(command.getSavingsId(),
+        // annualFeeTransactionDate);
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CalculateInterestFixedDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CalculateInterestFixedDepositAccountCommandHandler.java
new file mode 100644
index 0000000..d154921
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CalculateInterestFixedDepositAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "CALCULATEINTEREST")
+public class CalculateInterestFixedDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public CalculateInterestFixedDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.calculateInterest(command.entityId(), DepositAccountType.FIXED_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CalculateInterestRecurringDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CalculateInterestRecurringDepositAccountCommandHandler.java
new file mode 100644
index 0000000..0d1856c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CalculateInterestRecurringDepositAccountCommandHandler.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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "CALCULATEINTEREST")
+public class CalculateInterestRecurringDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public CalculateInterestRecurringDepositAccountCommandHandler(
+            final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.calculateInterest(command.entityId(), DepositAccountType.RECURRING_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java
new file mode 100644
index 0000000..b91937b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "CALCULATEINTEREST")
+public class CalculateInterestSavingsAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public CalculateInterestSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.calculateInterest(command.getSavingsId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CloseFixedDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CloseFixedDepositAccountCommandHandler.java
new file mode 100644
index 0000000..8fba9db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CloseFixedDepositAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "CLOSE")
+public class CloseFixedDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public CloseFixedDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.closeFDAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CloseRecurringDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CloseRecurringDepositAccountCommandHandler.java
new file mode 100644
index 0000000..a2f16a3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CloseRecurringDepositAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "CLOSE")
+public class CloseRecurringDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public CloseRecurringDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.closeRDAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CloseSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CloseSavingsAccountCommandHandler.java
new file mode 100755
index 0000000..6005147
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CloseSavingsAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "CLOSE")
+public class CloseSavingsAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public CloseSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) {
+        this.writePlatformService = savingAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.close(command.getSavingsId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CreateFixedDepositProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CreateFixedDepositProductCommandHandler.java
new file mode 100644
index 0000000..75d98a3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CreateFixedDepositProductCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.FixedDepositProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITPRODUCT", action = "CREATE")
+public class CreateFixedDepositProductCommandHandler implements NewCommandSourceHandler {
+
+    private final FixedDepositProductWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateFixedDepositProductCommandHandler(final FixedDepositProductWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.create(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CreateRecurringDepositProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CreateRecurringDepositProductCommandHandler.java
new file mode 100644
index 0000000..b051efc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CreateRecurringDepositProductCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.RecurringDepositProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITPRODUCT", action = "CREATE")
+public class CreateRecurringDepositProductCommandHandler implements NewCommandSourceHandler {
+
+    private final RecurringDepositProductWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateRecurringDepositProductCommandHandler(final RecurringDepositProductWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.create(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CreateSavingsProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CreateSavingsProductCommandHandler.java
new file mode 100644
index 0000000..3df4c15
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/CreateSavingsProductCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "SAVINGSPRODUCT", action = "CREATE")
+public class CreateSavingsProductCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsProductWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateSavingsProductCommandHandler(final SavingsProductWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.create(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteFixedDepositProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteFixedDepositProductCommandHandler.java
new file mode 100644
index 0000000..57a0944
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteFixedDepositProductCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.FixedDepositProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITPRODUCT", action = "DELETE")
+public class DeleteFixedDepositProductCommandHandler implements NewCommandSourceHandler {
+
+    private final FixedDepositProductWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteFixedDepositProductCommandHandler(final FixedDepositProductWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.delete(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteRecurringDepositProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteRecurringDepositProductCommandHandler.java
new file mode 100644
index 0000000..42dba9b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteRecurringDepositProductCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.RecurringDepositProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITPRODUCT", action = "DELETE")
+public class DeleteRecurringDepositProductCommandHandler implements NewCommandSourceHandler {
+
+    private final RecurringDepositProductWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteRecurringDepositProductCommandHandler(final RecurringDepositProductWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.delete(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteSavingsAccountChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteSavingsAccountChargeCommandHandler.java
new file mode 100644
index 0000000..0524eff
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteSavingsAccountChargeCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNTCHARGE", action = "DELETE")
+public class DeleteSavingsAccountChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.deleteSavingsAccountCharge(command.getSavingsId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteSavingsProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteSavingsProductCommandHandler.java
new file mode 100644
index 0000000..a2fa5e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DeleteSavingsProductCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "SAVINGSPRODUCT", action = "DELETE")
+public class DeleteSavingsProductCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsProductWritePlatformService savingProductWritePlatformService;
+
+    @Autowired
+    public DeleteSavingsProductCommandHandler(final SavingsProductWritePlatformService savingProductWritePlatformService) {
+        this.savingProductWritePlatformService = savingProductWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.savingProductWritePlatformService.delete(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DepositSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DepositSavingsAccountCommandHandler.java
new file mode 100644
index 0000000..a71136b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/DepositSavingsAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "DEPOSIT")
+public class DepositSavingsAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public DepositSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deposit(command.getSavingsId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationApprovalCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationApprovalCommandHandler.java
new file mode 100644
index 0000000..6b7beb6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationApprovalCommandHandler.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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "APPROVE")
+public class FixedDepositAccountApplicationApprovalCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public FixedDepositAccountApplicationApprovalCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.approveApplication(command.entityId(), command, DepositAccountType.FIXED_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationApprovalUndoCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationApprovalUndoCommandHandler.java
new file mode 100644
index 0000000..2b45c7a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationApprovalUndoCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "APPROVALUNDO")
+public class FixedDepositAccountApplicationApprovalUndoCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public FixedDepositAccountApplicationApprovalUndoCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.undoApplicationApproval(command.entityId(), command,
+                DepositAccountType.FIXED_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationDeletionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationDeletionCommandHandler.java
new file mode 100644
index 0000000..b4e8a11
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationDeletionCommandHandler.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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "DELETE")
+public class FixedDepositAccountApplicationDeletionCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public FixedDepositAccountApplicationDeletionCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.deleteApplication(command.entityId(), DepositAccountType.FIXED_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationModificationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationModificationCommandHandler.java
new file mode 100644
index 0000000..7330f6f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationModificationCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "UPDATE")
+public class FixedDepositAccountApplicationModificationCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public FixedDepositAccountApplicationModificationCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.modifyFDApplication(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationRejectedCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationRejectedCommandHandler.java
new file mode 100644
index 0000000..339eec9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationRejectedCommandHandler.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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "REJECT")
+public class FixedDepositAccountApplicationRejectedCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public FixedDepositAccountApplicationRejectedCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.rejectApplication(command.entityId(), command, DepositAccountType.FIXED_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationSubmittalCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationSubmittalCommandHandler.java
new file mode 100644
index 0000000..badad9f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationSubmittalCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "CREATE")
+public class FixedDepositAccountApplicationSubmittalCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public FixedDepositAccountApplicationSubmittalCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.submitFDApplication(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationWithdrawnByApplicantCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationWithdrawnByApplicantCommandHandler.java
new file mode 100644
index 0000000..529f7fa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountApplicationWithdrawnByApplicantCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "WITHDRAW")
+public class FixedDepositAccountApplicationWithdrawnByApplicantCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public FixedDepositAccountApplicationWithdrawnByApplicantCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.applicantWithdrawsFromApplication(command.entityId(), command,
+                DepositAccountType.FIXED_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountDepositCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountDepositCommandHandler.java
new file mode 100644
index 0000000..46bba98
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositAccountDepositCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "DEPOSIT")
+public class FixedDepositAccountDepositCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public FixedDepositAccountDepositCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.depositToFDAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositTransactionAdjustmentCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositTransactionAdjustmentCommandHandler.java
new file mode 100644
index 0000000..c048b6a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/FixedDepositTransactionAdjustmentCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "ADJUSTTRANSACTION")
+public class FixedDepositTransactionAdjustmentCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public FixedDepositTransactionAdjustmentCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        final Long transactionId = Long.valueOf(command.getTransactionId());
+        return this.depositAccountWritePlatformService.adjustFDTransaction(command.entityId(), transactionId, command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/InactivateSavingsAccountChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/InactivateSavingsAccountChargeCommandHandler.java
new file mode 100644
index 0000000..bd9fc9d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/InactivateSavingsAccountChargeCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNTCHARGE", action = "INACTIVATE")
+public class InactivateSavingsAccountChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public InactivateSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.writePlatformService.inactivateCharge(command.getSavingsId(), command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PaySavingsAccountChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PaySavingsAccountChargeCommandHandler.java
new file mode 100644
index 0000000..ab3a585
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PaySavingsAccountChargeCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNTCHARGE", action = "PAY")
+public class PaySavingsAccountChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public PaySavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.payCharge(command.getSavingsId(), command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestFixedDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestFixedDepositAccountCommandHandler.java
new file mode 100644
index 0000000..9a0eed1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestFixedDepositAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "POSTINTEREST")
+public class PostInterestFixedDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public PostInterestFixedDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.postInterest(command.entityId(), DepositAccountType.FIXED_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestRecurringDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestRecurringDepositAccountCommandHandler.java
new file mode 100644
index 0000000..96e05ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestRecurringDepositAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "POSTINTEREST")
+public class PostInterestRecurringDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public PostInterestRecurringDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.postInterest(command.entityId(), DepositAccountType.RECURRING_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java
new file mode 100644
index 0000000..b48b215
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "POSTINTEREST")
+public class PostInterestSavingsAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public PostInterestSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.postInterest(command.getSavingsId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PrematureCloseFixedDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PrematureCloseFixedDepositAccountCommandHandler.java
new file mode 100644
index 0000000..b403c4b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PrematureCloseFixedDepositAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "PREMATURECLOSE")
+public class PrematureCloseFixedDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public PrematureCloseFixedDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.prematureCloseFDAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PrematureCloseRecurringDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PrematureCloseRecurringDepositAccountCommandHandler.java
new file mode 100644
index 0000000..2f16431
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PrematureCloseRecurringDepositAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "PREMATURECLOSE")
+public class PrematureCloseRecurringDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public PrematureCloseRecurringDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.prematureCloseRDAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationApprovalCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationApprovalCommandHandler.java
new file mode 100644
index 0000000..9aaae92
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationApprovalCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "APPROVE")
+public class RecurringDepositAccountApplicationApprovalCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositAccountApplicationApprovalCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService
+                .approveApplication(command.entityId(), command, DepositAccountType.RECURRING_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationApprovalUndoCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationApprovalUndoCommandHandler.java
new file mode 100644
index 0000000..a869ba3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationApprovalUndoCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "APPROVALUNDO")
+public class RecurringDepositAccountApplicationApprovalUndoCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositAccountApplicationApprovalUndoCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.undoApplicationApproval(command.entityId(), command,
+                DepositAccountType.RECURRING_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationDeletionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationDeletionCommandHandler.java
new file mode 100644
index 0000000..c62d370
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationDeletionCommandHandler.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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "DELETE")
+public class RecurringDepositAccountApplicationDeletionCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositAccountApplicationDeletionCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.deleteApplication(command.entityId(), DepositAccountType.RECURRING_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationModificationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationModificationCommandHandler.java
new file mode 100644
index 0000000..34aa93c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationModificationCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "UPDATE")
+public class RecurringDepositAccountApplicationModificationCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositAccountApplicationModificationCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.modifyRDApplication(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationRejectedCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationRejectedCommandHandler.java
new file mode 100644
index 0000000..5a59d66
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationRejectedCommandHandler.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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "REJECT")
+public class RecurringDepositAccountApplicationRejectedCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositAccountApplicationRejectedCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.rejectApplication(command.entityId(), command, DepositAccountType.RECURRING_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationSubmittalCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationSubmittalCommandHandler.java
new file mode 100644
index 0000000..181c349
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationSubmittalCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "CREATE")
+public class RecurringDepositAccountApplicationSubmittalCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositAccountApplicationSubmittalCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.submitRDApplication(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationWithdrawnByApplicantCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationWithdrawnByApplicantCommandHandler.java
new file mode 100644
index 0000000..357893f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountApplicationWithdrawnByApplicantCommandHandler.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "WITHDRAW")
+public class RecurringDepositAccountApplicationWithdrawnByApplicantCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositAccountApplicationWithdrawnByApplicantCommandHandler(
+            final DepositApplicationProcessWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.applicantWithdrawsFromApplication(command.entityId(), command,
+                DepositAccountType.RECURRING_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountDepositCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountDepositCommandHandler.java
new file mode 100644
index 0000000..eeaa108
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountDepositCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "DEPOSIT")
+public class RecurringDepositAccountDepositCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositAccountDepositCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.depositToRDAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountUpdateDepositAmountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountUpdateDepositAmountCommandHandler.java
new file mode 100644
index 0000000..4419f27
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositAccountUpdateDepositAmountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "UPDATEDEPOSITAMOUNT")
+public class RecurringDepositAccountUpdateDepositAmountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositAccountUpdateDepositAmountCommandHandler(
+            final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.updateDepositAmountForRDAccount(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositTransactionAdjustmentCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositTransactionAdjustmentCommandHandler.java
new file mode 100644
index 0000000..748617e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RecurringDepositTransactionAdjustmentCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "ADJUSTTRANSACTION")
+public class RecurringDepositTransactionAdjustmentCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public RecurringDepositTransactionAdjustmentCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        final Long transactionId = Long.valueOf(command.getTransactionId());
+        return this.depositAccountWritePlatformService.adjustRDTransaction(command.entityId(), transactionId, command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RemoveSavingsOfficerCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RemoveSavingsOfficerCommandHandler.java
new file mode 100644
index 0000000..7301ac7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/RemoveSavingsOfficerCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "REMOVESAVINGSOFFICER")
+public class RemoveSavingsOfficerCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService savingsWritePlatformService;
+
+    @Autowired
+    public RemoveSavingsOfficerCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) {
+        this.savingsWritePlatformService = savingAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.savingsWritePlatformService.unassignFieldOfficer(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationApprovalCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationApprovalCommandHandler.java
new file mode 100644
index 0000000..32e8982
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationApprovalCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "APPROVE")
+public class SavingsAccountApplicationApprovalCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsApplicationProcessWritePlatformService writePlatformService;
+
+    @Autowired
+    public SavingsAccountApplicationApprovalCommandHandler(final SavingsApplicationProcessWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.approveApplication(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationApprovalUndoCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationApprovalUndoCommandHandler.java
new file mode 100644
index 0000000..d074f17
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationApprovalUndoCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "APPROVALUNDO")
+public class SavingsAccountApplicationApprovalUndoCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsApplicationProcessWritePlatformService writePlatformService;
+
+    @Autowired
+    public SavingsAccountApplicationApprovalUndoCommandHandler(final SavingsApplicationProcessWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.undoApplicationApproval(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationDeletionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationDeletionCommandHandler.java
new file mode 100644
index 0000000..47bbf1b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationDeletionCommandHandler.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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "DELETE")
+public class SavingsAccountApplicationDeletionCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService;
+
+    @Autowired
+    public SavingsAccountApplicationDeletionCommandHandler(
+            final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService) {
+        this.savingAccountWritePlatformService = savingAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.savingAccountWritePlatformService.deleteApplication(command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationModificationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationModificationCommandHandler.java
new file mode 100644
index 0000000..3450740
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationModificationCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "UPDATE")
+public class SavingsAccountApplicationModificationCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService;
+
+    @Autowired
+    public SavingsAccountApplicationModificationCommandHandler(
+            final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService) {
+        this.savingAccountWritePlatformService = savingAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.savingAccountWritePlatformService.modifyApplication(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationRejectedCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationRejectedCommandHandler.java
new file mode 100644
index 0000000..cc0ace0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationRejectedCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "REJECT")
+public class SavingsAccountApplicationRejectedCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsApplicationProcessWritePlatformService writePlatformService;
+
+    @Autowired
+    public SavingsAccountApplicationRejectedCommandHandler(final SavingsApplicationProcessWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.rejectApplication(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationSubmittalCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationSubmittalCommandHandler.java
new file mode 100644
index 0000000..c0a73ec
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationSubmittalCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "CREATE")
+public class SavingsAccountApplicationSubmittalCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService;
+
+    @Autowired
+    public SavingsAccountApplicationSubmittalCommandHandler(
+            final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService) {
+        this.savingAccountWritePlatformService = savingAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.savingAccountWritePlatformService.submitApplication(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationWithdrawnByApplicantCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationWithdrawnByApplicantCommandHandler.java
new file mode 100644
index 0000000..223e2d3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsAccountApplicationWithdrawnByApplicantCommandHandler.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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsApplicationProcessWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "WITHDRAW")
+public class SavingsAccountApplicationWithdrawnByApplicantCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsApplicationProcessWritePlatformService writePlatformService;
+
+    @Autowired
+    public SavingsAccountApplicationWithdrawnByApplicantCommandHandler(
+            final SavingsApplicationProcessWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.applicantWithdrawsFromApplication(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsTransactionAdjustmentCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsTransactionAdjustmentCommandHandler.java
new file mode 100755
index 0000000..5bc8ab4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/SavingsTransactionAdjustmentCommandHandler.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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "ADJUSTTRANSACTION")
+public class SavingsTransactionAdjustmentCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public SavingsTransactionAdjustmentCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        final Long transactionId = Long.valueOf(command.getTransactionId());
+        return this.writePlatformService.adjustSavingsTransaction(command.getSavingsId(), transactionId, command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoTransactionFixedDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoTransactionFixedDepositAccountCommandHandler.java
new file mode 100644
index 0000000..20ade55
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoTransactionFixedDepositAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "UNDOTRANSACTION")
+public class UndoTransactionFixedDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public UndoTransactionFixedDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        final Long transactionId = Long.valueOf(command.getTransactionId());
+        return this.depositAccountWritePlatformService.undoFDTransaction(command.entityId(), transactionId, false);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoTransactionRecurringDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoTransactionRecurringDepositAccountCommandHandler.java
new file mode 100644
index 0000000..53ae330
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoTransactionRecurringDepositAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "UNDOTRANSACTION")
+public class UndoTransactionRecurringDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public UndoTransactionRecurringDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        final Long transactionId = Long.valueOf(command.getTransactionId());
+        return this.depositAccountWritePlatformService.undoRDTransaction(command.entityId(), transactionId, false);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.java
new file mode 100644
index 0000000..338a854
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "UNDOTRANSACTION")
+public class UndoTransactionSavingsAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public UndoTransactionSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        final Long transactionId = Long.valueOf(command.getTransactionId());
+        return this.writePlatformService.undoTransaction(command.getSavingsId(), transactionId, false);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateFixedDepositProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateFixedDepositProductCommandHandler.java
new file mode 100644
index 0000000..8bd7a54
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateFixedDepositProductCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.FixedDepositProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITPRODUCT", action = "UPDATE")
+public class UpdateFixedDepositProductCommandHandler implements NewCommandSourceHandler {
+
+    private final FixedDepositProductWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateFixedDepositProductCommandHandler(final FixedDepositProductWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.update(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateRecurringDepositProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateRecurringDepositProductCommandHandler.java
new file mode 100644
index 0000000..f2239cb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateRecurringDepositProductCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.RecurringDepositProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITPRODUCT", action = "UPDATE")
+public class UpdateRecurringDepositProductCommandHandler implements NewCommandSourceHandler {
+
+    private final RecurringDepositProductWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateRecurringDepositProductCommandHandler(final RecurringDepositProductWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.update(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateSavingsAccountChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateSavingsAccountChargeCommandHandler.java
new file mode 100644
index 0000000..1f6182c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateSavingsAccountChargeCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNTCHARGE", action = "UPDATE")
+public class UpdateSavingsAccountChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateSavingsAccountCharge(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateSavingsOfficerCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateSavingsOfficerCommandHandler.java
new file mode 100644
index 0000000..fe0a636
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateSavingsOfficerCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "UPDATESAVINGSOFFICER")
+public class UpdateSavingsOfficerCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService savingsWritePlatformService;
+
+    @Autowired
+    public UpdateSavingsOfficerCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) {
+        this.savingsWritePlatformService = savingAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.savingsWritePlatformService.assignFieldOfficer(command.entityId(), command);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateSavingsProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateSavingsProductCommandHandler.java
new file mode 100644
index 0000000..470740e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateSavingsProductCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "SAVINGSPRODUCT", action = "UPDATE")
+public class UpdateSavingsProductCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsProductWritePlatformService savingProductWritePlatformService;
+
+    @Autowired
+    public UpdateSavingsProductCommandHandler(final SavingsProductWritePlatformService savingProductWritePlatformService) {
+        this.savingProductWritePlatformService = savingProductWritePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.savingProductWritePlatformService.update(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java
new file mode 100644
index 0000000..cd193ca
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNTCHARGE", action = "WAIVE")
+public class WaiveSavingsAccountChargeCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public WaiveSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.waiveCharge(command.getSavingsId(), command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WithdrawSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WithdrawSavingsAccountCommandHandler.java
new file mode 100644
index 0000000..2faea40
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WithdrawSavingsAccountCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SAVINGSACCOUNT", action = "WITHDRAWAL")
+public class WithdrawSavingsAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final SavingsAccountWritePlatformService writePlatformService;
+
+    @Autowired
+    public WithdrawSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.withdrawal(command.getSavingsId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WithdrawalFixedDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WithdrawalFixedDepositAccountCommandHandler.java
new file mode 100644
index 0000000..97e3a0a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WithdrawalFixedDepositAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "WITHDRAWAL")
+public class WithdrawalFixedDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public WithdrawalFixedDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.withdrawal(command.entityId(), command, DepositAccountType.FIXED_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WithdrawalRecurringDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WithdrawalRecurringDepositAccountCommandHandler.java
new file mode 100644
index 0000000..44324ac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/WithdrawalRecurringDepositAccountCommandHandler.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.portfolio.savings.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "WITHDRAWAL")
+public class WithdrawalRecurringDepositAccountCommandHandler implements NewCommandSourceHandler {
+
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public WithdrawalRecurringDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.depositAccountWritePlatformService.withdrawal(command.entityId(), command, DepositAccountType.RECURRING_DEPOSIT);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountInterestRateChartReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountInterestRateChartReadPlatformService.java
new file mode 100644
index 0000000..909d288
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountInterestRateChartReadPlatformService.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.portfolio.savings.service;
+
+import org.apache.fineract.portfolio.savings.data.DepositAccountInterestRateChartData;
+
+public interface DepositAccountInterestRateChartReadPlatformService {
+
+    DepositAccountInterestRateChartData retrieveOne(Long interestChartId);
+
+    DepositAccountInterestRateChartData retrieveOneWithSlabs(Long interestChartId);
+
+    DepositAccountInterestRateChartData retrieveWithTemplate(DepositAccountInterestRateChartData DepositAccountInterestRateChartData);
+
+    DepositAccountInterestRateChartData retrieveOneWithSlabsOnAccountId(Long accountId);
+
+    DepositAccountInterestRateChartData template();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountInterestRateChartReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountInterestRateChartReadPlatformServiceImpl.java
new file mode 100644
index 0000000..1adb84b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountInterestRateChartReadPlatformServiceImpl.java
@@ -0,0 +1,380 @@
+/**
+ * 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.savings.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+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.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName;
+import org.apache.fineract.portfolio.interestratechart.service.InterestIncentiveDropdownReadPlatformService;
+import org.apache.fineract.portfolio.interestratechart.service.InterestIncentivesEnumerations;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartDropdownReadPlatformService;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartEnumerations;
+import org.apache.fineract.portfolio.savings.data.DepositAccountInterestIncentiveData;
+import org.apache.fineract.portfolio.savings.data.DepositAccountInterestRateChartData;
+import org.apache.fineract.portfolio.savings.data.DepositAccountInterestRateChartSlabData;
+import org.apache.fineract.portfolio.savings.exception.DepositAccountInterestRateChartNotFoundException;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DepositAccountInterestRateChartReadPlatformServiceImpl implements DepositAccountInterestRateChartReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final DepositAccountInterestRateChartMapper chartRowMapper = new DepositAccountInterestRateChartMapper();
+    private final DepositAccountInterestRateChartExtractor chartExtractor = new DepositAccountInterestRateChartExtractor();
+    private final InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService;
+    private final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+
+    @Autowired
+    public DepositAccountInterestRateChartReadPlatformServiceImpl(PlatformSecurityContext context, final RoutingDataSource dataSource,
+            InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService,
+            final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService,
+            final CodeValueReadPlatformService codeValueReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.chartDropdownReadPlatformService = chartDropdownReadPlatformService;
+        this.interestIncentiveDropdownReadPlatformService = interestIncentiveDropdownReadPlatformService;
+        this.codeValueReadPlatformService = codeValueReadPlatformService;
+    }
+
+    @Override
+    public DepositAccountInterestRateChartData retrieveOne(Long chartId) {
+        try {
+            this.context.authenticatedUser();
+            final String sql = "select " + this.chartRowMapper.schema() + " where irc.id = ? ";
+            return this.jdbcTemplate.queryForObject(sql, this.chartRowMapper, new Object[] { chartId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new DepositAccountInterestRateChartNotFoundException(chartId);
+        }
+    }
+
+    @Override
+    public DepositAccountInterestRateChartData retrieveOneWithSlabs(Long chartId) {
+        this.context.authenticatedUser();
+        final String sql = "select " + this.chartExtractor.schema() + " where irc.id = ? order by ircd.id asc";
+        Collection<DepositAccountInterestRateChartData> chartDatas = this.jdbcTemplate.query(sql, this.chartExtractor,
+                new Object[] { chartId });
+        if (chartDatas == null || chartDatas.isEmpty()) { throw new DepositAccountInterestRateChartNotFoundException(chartId); }
+
+        return chartDatas.iterator().next();
+    }
+
+    @Override
+    public DepositAccountInterestRateChartData retrieveWithTemplate(DepositAccountInterestRateChartData chartData) {
+
+        final List<CodeValueData> genderOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.GENDER));
+
+        final List<CodeValueData> clientTypeOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_TYPE));
+
+        final List<CodeValueData> clientClassificationOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_CLASSIFICATION));
+
+        return DepositAccountInterestRateChartData.withTemplate(chartData,
+                this.chartDropdownReadPlatformService.retrievePeriodTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveEntityTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveAttributeNameOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveConditionTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveIncentiveTypeOptions(), genderOptions, clientTypeOptions,
+                clientClassificationOptions);
+    }
+
+    @Override
+    public DepositAccountInterestRateChartData retrieveOneWithSlabsOnAccountId(Long accountId) {
+        this.context.authenticatedUser();
+        final String sql = "select " + this.chartExtractor.schema() + " where irc.savings_account_id = ? order by ircd.id asc";
+        Collection<DepositAccountInterestRateChartData> chartDatas = this.jdbcTemplate.query(sql, this.chartExtractor,
+                new Object[] { accountId });
+        if (chartDatas == null || chartDatas.isEmpty()) { throw new DepositAccountInterestRateChartNotFoundException(accountId); }
+
+        return chartDatas.iterator().next();
+    }
+
+    @Override
+    public DepositAccountInterestRateChartData template() {
+
+        final List<CodeValueData> genderOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.GENDER));
+
+        final List<CodeValueData> clientTypeOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_TYPE));
+
+        final List<CodeValueData> clientClassificationOptions = new ArrayList<>(
+                this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_CLASSIFICATION));
+        return DepositAccountInterestRateChartData.template(this.chartDropdownReadPlatformService.retrievePeriodTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveEntityTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveAttributeNameOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveConditionTypeOptions(),
+                this.interestIncentiveDropdownReadPlatformService.retrieveIncentiveTypeOptions(), genderOptions, clientTypeOptions,
+                clientClassificationOptions);
+    }
+
+    private static final class DepositAccountInterestRateChartExtractor implements
+            ResultSetExtractor<Collection<DepositAccountInterestRateChartData>> {
+
+        DepositAccountInterestRateChartMapper chartMapper = new DepositAccountInterestRateChartMapper();
+        InterestRateChartSlabExtractor chartSlabsMapper = new InterestRateChartSlabExtractor();
+
+        private final String schemaSql;
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        private DepositAccountInterestRateChartExtractor() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder
+                    .append("irc.id as ircId, irc.name as ircName, irc.description as ircDescription,")
+                    .append("irc.from_date as ircFromDate, irc.end_date as ircEndDate, ")
+                    .append("ircd.id as ircdId, ircd.description as ircdDescription, ircd.period_type_enum ircdPeriodTypeId, ")
+                    .append("ircd.from_period as ircdFromPeriod, ircd.to_period as ircdToPeriod, ircd.amount_range_from as ircdAmountRangeFrom, ")
+                    .append("ircd.amount_range_to as ircdAmountRangeTo, ircd.annual_interest_rate as ircdAnnualInterestRate, ")
+                    .append("curr.code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ")
+                    .append("curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
+                    .append("sa.id as accountId, sa.account_no as accountNumber, ")
+                    .append("iri.id as iriId, ")
+                    .append(" iri.entiry_type as entityType, iri.attribute_name as attributeName ,")
+                    .append(" iri.condition_type as conditionType, iri.attribute_value as attributeValue, ")
+                    .append(" iri.incentive_type as incentiveType, iri.amount as amount, ")
+                    .append("code.code_value as attributeValueDesc ")
+                    .append("from ")
+                    .append("m_savings_account_interest_rate_chart irc left join m_savings_account_interest_rate_slab ircd on irc.id=ircd.savings_account_interest_rate_chart_id ")
+                    .append(" left join m_savings_interest_incentives  iri on iri.deposit_account_interest_rate_slab_id =ircd.id ")
+                    .append(" left join m_code_value code on code.id = iri.attribute_value ")
+                    .append("left join m_currency curr on ircd.currency_code= curr.code ")
+                    .append("left join m_savings_account sa on irc.savings_account_id=sa.id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public Collection<DepositAccountInterestRateChartData> extractData(ResultSet rs) throws SQLException, DataAccessException {
+
+            List<DepositAccountInterestRateChartData> chartDataList = new ArrayList<>();
+
+            DepositAccountInterestRateChartData chartData = null;
+            Long interestRateChartId = null;
+            int ircIndex = 0;// Interest rate chart index
+
+            while (rs.next()) {
+                Long tempIrcId = rs.getLong("ircId");
+                // first row or when interest rate chart id changes
+                if (chartData == null || (interestRateChartId != null && !interestRateChartId.equals(tempIrcId))) {
+
+                    interestRateChartId = tempIrcId;
+                    chartData = chartMapper.mapRow(rs, ircIndex++);
+                    chartDataList.add(chartData);
+
+                }
+                final DepositAccountInterestRateChartSlabData chartSlabsData = chartSlabsMapper.extractData(rs);
+                if (chartSlabsData != null) {
+                    chartData.addChartSlab(chartSlabsData);
+                }
+            }
+            return chartDataList;
+        }
+
+    }
+
+    public static final class DepositAccountInterestRateChartMapper implements RowMapper<DepositAccountInterestRateChartData> {
+
+        private final String schemaSql;
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        private DepositAccountInterestRateChartMapper() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+
+            sqlBuilder.append("irc.id as ircId, irc.name as ircName, irc.description as ircDescription, ")
+                    .append("irc.from_date as ircFromDate, irc.end_date as ircEndDate, ")
+                    .append("sa.id as accountId, sa.account_no as accountNumber ").append("from ")
+                    .append("m_savings_account_interest_rate_chart irc left join m_savings_account sa on irc.savings_account_id=sa.id ");
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public DepositAccountInterestRateChartData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long id = rs.getLong("ircId");
+            final String name = rs.getString("ircName");
+            final String description = rs.getString("ircDescription");
+            final LocalDate fromDate = JdbcSupport.getLocalDate(rs, "ircFromDate");
+            final LocalDate endDate = JdbcSupport.getLocalDate(rs, "ircEndDate");
+            final Long accountId = rs.getLong("accountId");
+            final String accountNumber = rs.getString("accountNumber");
+            final Collection<EnumOptionData> periodTypes = InterestRateChartEnumerations.periodType(PeriodFrequencyType.values());
+
+            return DepositAccountInterestRateChartData.instance(id, name, description, fromDate, endDate, accountId, accountNumber, null,
+                    periodTypes);
+        }
+
+    }
+
+    private static final class InterestRateChartSlabExtractor implements ResultSetExtractor<DepositAccountInterestRateChartSlabData> {
+
+        DepositAccountInterestRateChartSlabsMapper chartSlabsMapper = new DepositAccountInterestRateChartSlabsMapper();
+        InterestIncentiveMapper incentiveMapper = new InterestIncentiveMapper();
+
+        @Override
+        public DepositAccountInterestRateChartSlabData extractData(ResultSet rs) throws SQLException, DataAccessException {
+
+            DepositAccountInterestRateChartSlabData chartSlabData = null;
+            Long interestRateChartSlabId = null;
+            int ircIndex = 0;// Interest rate chart index
+            int ircdIndex = 0;// Interest rate chart Slabs index
+            rs.previous();
+            while (rs.next()) {
+                Long tempIrcdId = rs.getLong("ircdId");
+                if (interestRateChartSlabId == null || interestRateChartSlabId.equals(tempIrcdId)) {
+                    if (chartSlabData == null) {
+                        interestRateChartSlabId = tempIrcdId;
+                        chartSlabData = chartSlabsMapper.mapRow(rs, ircIndex++);
+                    }
+                    final DepositAccountInterestIncentiveData incentiveData = incentiveMapper.mapRow(rs, ircdIndex++);
+                    if (incentiveData != null) {
+                        chartSlabData.addIncentives(incentiveData);
+                    }
+                } else {
+                    rs.previous();
+                    break;
+                }
+
+            }
+            return chartSlabData;
+        }
+    }
+
+    private static final class DepositAccountInterestRateChartSlabsMapper implements RowMapper<DepositAccountInterestRateChartSlabData> {
+
+        /*
+         * private final String schemaSql;
+         * 
+         * public String schema() { return this.schemaSql; }
+         * 
+         * private DepositAccountInterestRateChartSlabsMapper() { final
+         * StringBuilder sqlBuilder = new StringBuilder(400);
+         * 
+         * sqlBuilder .append(
+         * "ircd.id as ircdId, ircd.description as ircdDescription, ircd.period_type_enum ircdPeriodTypeId, "
+         * ) .append(
+         * "ircd.from_period as ircdFromPeriod, ircd.to_period as ircdToPeriod, ircd.amount_range_from as ircdAmountRangeFrom, "
+         * ) .append(
+         * "ircd.amount_range_to as ircdAmountRangeTo, ircd.annual_interest_rate as ircdAnnualInterestRate, "
+         * ) .append(
+         * "curr.code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "
+         * ) .append(
+         * "curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf "
+         * ) .append("from ").append(
+         * "m_savings_account_interest_rate_slab ircd ")
+         * .append("left join m_currency curr on ircd.currency_code= curr.code "
+         * ); this.schemaSql = sqlBuilder.toString(); }
+         */
+
+        @Override
+        public DepositAccountInterestRateChartSlabData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLongDefaultToNullIfZero(rs, "ircdId");
+            // If there are not chart Slabs are associated then in
+            // InterestRateChartExtractor the chart Slabs id will be null.
+            if (id == null) { return null; }
+
+            final String description = rs.getString("ircdDescription");
+            final Integer fromPeriod = JdbcSupport.getInteger(rs, "ircdFromPeriod");
+            final Integer toPeriod = JdbcSupport.getInteger(rs, "ircdToPeriod");
+            final Integer periodTypeId = JdbcSupport.getInteger(rs, "ircdPeriodTypeId");
+            final EnumOptionData periodType = InterestRateChartEnumerations.periodType(periodTypeId);
+            final BigDecimal amountRangeFrom = rs.getBigDecimal("ircdAmountRangeFrom");
+            final BigDecimal amountRangeTo = rs.getBigDecimal("ircdAmountRangeTo");
+            final BigDecimal annualInterestRate = rs.getBigDecimal("ircdAnnualInterestRate");
+
+            // currency Slabs
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            // currency
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return DepositAccountInterestRateChartSlabData.instance(id, description, periodType, fromPeriod, toPeriod, amountRangeFrom,
+                    amountRangeTo, annualInterestRate, currency);
+        }
+
+    }
+
+    private static final class InterestIncentiveMapper implements RowMapper<DepositAccountInterestIncentiveData> {
+
+        @Override
+        public DepositAccountInterestIncentiveData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long id = JdbcSupport.getLongDefaultToNullIfZero(rs, "iriId");
+            // If there are not Incentive are associated then in
+            // InterestRateChartExtractor the incentive id will be null.
+            if (id == null) { return null; }
+
+            final String attributeValue = rs.getString("attributeValue");
+            String attributeValueDesc = null;
+            final Integer entityType = JdbcSupport.getInteger(rs, "entityType");
+            final EnumOptionData entityTypeData = InterestIncentivesEnumerations.entityType(entityType);
+
+            final Integer attributeName = JdbcSupport.getInteger(rs, "attributeName");
+            if (InterestIncentiveAttributeName.isCodeValueAttribute(InterestIncentiveAttributeName.fromInt(attributeName))) {
+                attributeValueDesc = rs.getString("attributeValueDesc");
+            }
+            final EnumOptionData attributeNameData = InterestIncentivesEnumerations.attributeName(attributeName);
+            final Integer conditionType = JdbcSupport.getInteger(rs, "conditionType");
+            final EnumOptionData conditionTypeData = CommonEnumerations.conditionType(conditionType, "incentive");
+            final Integer incentiveType = JdbcSupport.getInteger(rs, "incentiveType");
+            final EnumOptionData incentiveTypeData = InterestIncentivesEnumerations.incentiveType(incentiveType);
+            final BigDecimal amount = rs.getBigDecimal("amount");
+
+            return DepositAccountInterestIncentiveData.instance(id, entityTypeData, attributeNameData, conditionTypeData, attributeValue,
+                    attributeValueDesc, incentiveTypeData, amount);
+
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformService.java
new file mode 100755
index 0000000..85b8d33
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformService.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.savings.service;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.portfolio.savings.data.DepositAccountOnHoldTransactionData;
+
+public interface DepositAccountOnHoldTransactionReadPlatformService {
+
+    public Page<DepositAccountOnHoldTransactionData> retriveAll(final Long savingsId, final Long guarantorFundingId,
+            final SearchParameters searchParameters);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformServiceImpl.java
new file mode 100755
index 0000000..9be2258
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformServiceImpl.java
@@ -0,0 +1,137 @@
+/**
+ * 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.savings.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.portfolio.savings.data.DepositAccountOnHoldTransactionData;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DepositAccountOnHoldTransactionReadPlatformServiceImpl implements DepositAccountOnHoldTransactionReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PaginationHelper<DepositAccountOnHoldTransactionData> paginationHelper = new PaginationHelper<>();
+    private final DepositAccountOnHoldTransactionsMapper mapper;
+
+    @Autowired
+    public DepositAccountOnHoldTransactionReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        mapper = new DepositAccountOnHoldTransactionsMapper();
+    }
+
+    @Override
+    public Page<DepositAccountOnHoldTransactionData> retriveAll(Long savingsId, Long guarantorFundingId, SearchParameters searchParameters) {
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        List<Long> paramObj = new ArrayList<>(2);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.mapper.schema());
+
+        sqlBuilder.append(" where tr.savings_account_id = ? ");
+        paramObj.add(savingsId);
+        if (guarantorFundingId != null) {
+            sqlBuilder.append(" and gt.guarantor_fund_detail_id = ? ");
+            paramObj.add(guarantorFundingId);
+        }
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        final Object[] finalObjectArray = paramObj.toArray();
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray, this.mapper);
+
+    }
+
+    private static final class DepositAccountOnHoldTransactionsMapper implements RowMapper<DepositAccountOnHoldTransactionData> {
+
+        private final String schemaSql;
+
+        public DepositAccountOnHoldTransactionsMapper() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append(" tr.id as transactionId, tr.transaction_type_enum as transactionType, ");
+            sqlBuilder.append(" tr.transaction_date as transactionDate, tr.amount as transactionAmount,");
+            sqlBuilder.append(" tr.is_reversed as reversed, sa.account_no as savingsAccNum, ");
+            sqlBuilder.append("sc.display_name as savingsClientName, ml.id as loanid, sa.id as savingid, ");
+            sqlBuilder.append(" ml.account_no as loanAccountNum, lc.display_name as loanClientName");
+            sqlBuilder.append(" from m_savings_account sa  ");
+            sqlBuilder.append(" join m_deposit_account_on_hold_transaction tr on sa.id = tr.savings_account_id ");
+            sqlBuilder.append(" join m_client sc on sc.id = sa.client_id");
+            sqlBuilder.append(" left join m_guarantor_transaction gt on gt.deposit_on_hold_transaction_id = tr.id ");
+            sqlBuilder.append(" left join m_guarantor_funding_details mgfd on mgfd.id=gt.guarantor_fund_detail_id");
+            sqlBuilder.append(" left join m_portfolio_account_associations  pa on pa.id=mgfd.account_associations_id");
+            sqlBuilder.append(" left join m_loan ml on ml.id = pa.loan_account_id");
+            sqlBuilder.append(" left join m_client lc on lc.id = ml.client_id");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public DepositAccountOnHoldTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
+                throws SQLException {
+            final Long id = rs.getLong("transactionId");
+            final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
+            final EnumOptionData transactionType = SavingsEnumerations.onHoldTransactionType(transactionTypeInt);
+
+            final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate");
+            final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount");
+            final boolean reversed = rs.getBoolean("reversed");
+            final String savingsAccountNum = rs.getString("savingsAccNum");
+            final Long savingsId = rs.getLong("savingid");
+            final String savingsClientName = rs.getString("savingsClientName");
+            final String loanAccountNum = rs.getString("loanAccountNum");
+            final Long loanId = rs.getLong("loanid");
+            final String loanClientName = rs.getString("loanClientName");
+            return DepositAccountOnHoldTransactionData.instance(id, amount, transactionType, date, reversed, savingsId, savingsAccountNum,
+                    savingsClientName, loanId, loanAccountNum, loanClientName);
+        }
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformService.java
new file mode 100644
index 0000000..df08857
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformService.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.DepositAccountData;
+
+public interface DepositAccountPreMatureCalculationPlatformService {
+
+    DepositAccountData calculatePreMatureAmount(Long accountId, JsonQuery query, DepositAccountType depositAccountType);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformServiceImpl.java
new file mode 100644
index 0000000..ea7e5bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformServiceImpl.java
@@ -0,0 +1,113 @@
+/**
+ * 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.savings.service;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.closedOnDateParamName;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.DepositAccountData;
+import org.apache.fineract.portfolio.savings.data.DepositAccountTransactionDataValidator;
+import org.apache.fineract.portfolio.savings.data.FixedDepositAccountData;
+import org.apache.fineract.portfolio.savings.data.RecurringDepositAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountAssembler;
+import org.apache.fineract.portfolio.savings.domain.FixedDepositAccount;
+import org.apache.fineract.portfolio.savings.domain.RecurringDepositAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonElement;
+
+@Service
+public class DepositAccountPreMatureCalculationPlatformServiceImpl implements DepositAccountPreMatureCalculationPlatformService {
+
+    private final FromJsonHelper fromJsonHelper;
+    private final DepositAccountTransactionDataValidator depositAccountTransactionDataValidator;
+    private final DepositAccountAssembler depositAccountAssembler;
+    private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
+    private final ConfigurationDomainService configurationDomainService;
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public DepositAccountPreMatureCalculationPlatformServiceImpl(final FromJsonHelper fromJsonHelper,
+            final DepositAccountTransactionDataValidator depositAccountTransactionDataValidator,
+            final DepositAccountAssembler depositAccountAssembler,
+            final SavingsAccountReadPlatformService savingsAccountReadPlatformService,
+            final ConfigurationDomainService configurationDomainService, PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.fromJsonHelper = fromJsonHelper;
+        this.depositAccountTransactionDataValidator = depositAccountTransactionDataValidator;
+        this.depositAccountAssembler = depositAccountAssembler;
+        this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
+        this.configurationDomainService = configurationDomainService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+
+    }
+
+    @Transactional
+    @Override
+    public DepositAccountData calculatePreMatureAmount(final Long accountId, final JsonQuery query,
+            final DepositAccountType depositAccountType) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        this.depositAccountTransactionDataValidator.validatePreMatureAmountCalculation(query.json(), depositAccountType);
+        final SavingsAccount account = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType);
+
+        DepositAccountData accountData = null;
+        Collection<EnumOptionData> onAccountClosureOptions = SavingsEnumerations
+                .depositAccountOnClosureType(new DepositAccountOnClosureType[] { DepositAccountOnClosureType.WITHDRAW_DEPOSIT,
+                        DepositAccountOnClosureType.TRANSFER_TO_SAVINGS });
+        final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+        final Collection<SavingsAccountData> savingsAccountDatas = this.savingsAccountReadPlatformService.retrieveActiveForLookup(
+                account.clientId(), DepositAccountType.SAVINGS_DEPOSIT);
+        final JsonElement element = this.fromJsonHelper.parse(query.json());
+        final LocalDate preMaturityDate = this.fromJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
+        // calculate interest before one day of closure date
+        final LocalDate interestCalculatedToDate = preMaturityDate.minusDays(1);
+        final boolean isPreMatureClosure = true;
+
+        if (depositAccountType.isFixedDeposit()) {
+            final FixedDepositAccount fd = (FixedDepositAccount) account;
+            accountData = FixedDepositAccountData.preClosureDetails(account.getId(), fd.calculatePreMatureAmount(interestCalculatedToDate,
+                    isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth), onAccountClosureOptions,
+                    paymentTypeOptions, savingsAccountDatas);
+        } else if (depositAccountType.isRecurringDeposit()) {
+            final RecurringDepositAccount rd = (RecurringDepositAccount) account;
+            accountData = RecurringDepositAccountData.preClosureDetails(account.getId(), rd.calculatePreMatureAmount(
+                    interestCalculatedToDate, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth),
+                    onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas);
+        }
+
+        return accountData;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformService.java
new file mode 100644
index 0000000..e45e918
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformService.java
@@ -0,0 +1,57 @@
+/**
+ * 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.savings.service;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.DepositAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+
+public interface DepositAccountReadPlatformService {
+
+    Collection<DepositAccountData> retrieveAll(final DepositAccountType depositAccountType, final PaginationParameters paginationParameters);
+
+    Page<DepositAccountData> retrieveAllPaged(final DepositAccountType depositAccountType, final PaginationParameters paginationParameters);
+
+    Collection<DepositAccountData> retrieveAllForLookup(final DepositAccountType depositAccountType);
+
+    DepositAccountData retrieveOne(final DepositAccountType depositAccountType, Long accountId);
+
+    DepositAccountData retrieveOneWithClosureTemplate(final DepositAccountType depositAccountType, Long accountId);
+
+    DepositAccountData retrieveOneWithChartSlabs(final DepositAccountType depositAccountType, Long productId);
+
+    Collection<SavingsAccountTransactionData> retrieveAllTransactions(final DepositAccountType depositAccountType, Long accountId);
+
+    DepositAccountData retrieveTemplate(final DepositAccountType depositAccountType, Long clientId, Long groupId, Long productId,
+            boolean staffInSelectedOfficeOnly);
+
+    Collection<DepositAccountData> retrieveForMaturityUpdate();
+
+    SavingsAccountTransactionData retrieveRecurringAccountDepositTransactionTemplate(final Long accountId);
+
+    Collection<AccountTransferDTO> retrieveDataForInterestTransfer();
+
+    Collection<Map<String, Object>> retriveDataForRDScheduleCreation();
+}
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
new file mode 100644
index 0000000..45de68c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
@@ -0,0 +1,1453 @@
+/**
+ * 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.savings.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.data.PaginationParameters;
+import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.calendar.data.CalendarData;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.DepositAccountData;
+import org.apache.fineract.portfolio.savings.data.DepositAccountInterestRateChartData;
+import org.apache.fineract.portfolio.savings.data.DepositProductData;
+import org.apache.fineract.portfolio.savings.data.FixedDepositAccountData;
+import org.apache.fineract.portfolio.savings.data.RecurringDepositAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+import org.apache.fineract.portfolio.savings.exception.DepositAccountNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class DepositAccountReadPlatformServiceImpl implements DepositAccountReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final DepositAccountInterestRateChartReadPlatformService accountChartReadPlatformService;
+    private final InterestRateChartReadPlatformService productChartReadPlatformService;
+    private final FixedDepositAccountMapper fixedDepositAccountRowMapper = new FixedDepositAccountMapper();
+    private final RecurringDepositAccountMapper recurringDepositAccountRowMapper = new RecurringDepositAccountMapper();
+    private final DepositAccountLookupMapper depositAccountLookupsRowMapper = new DepositAccountLookupMapper();
+    private final DepositAccountForMaturityMapper depositAccountForMaturityRowMapper = new DepositAccountForMaturityMapper();
+    private final PaginationParametersDataValidator paginationParametersDataValidator;
+    private final PaginationHelper<DepositAccountData> paginationHelper = new PaginationHelper<>();
+    private final SavingsAccountTransactionsMapper transactionsMapper;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final GroupReadPlatformService groupReadPlatformService;
+    private final DepositProductReadPlatformService depositProductReadPlatformService;
+    private final SavingsDropdownReadPlatformService savingsDropdownReadPlatformService;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService;
+    private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
+    private final RecurringAccountDepositTransactionTemplateMapper rdTransactionTemplateMapper;
+    private final DropdownReadPlatformService dropdownReadPlatformService;
+    private final CalendarReadPlatformService calendarReadPlatformService;
+    private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
+    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+
+    @Autowired
+    public DepositAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final DepositAccountInterestRateChartReadPlatformService chartReadPlatformService,
+            final PaginationParametersDataValidator paginationParametersDataValidator,
+            final ClientReadPlatformService clientReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
+            final DepositProductReadPlatformService depositProductReadPlatformService,
+            final SavingsDropdownReadPlatformService savingsDropdownReadPlatformService,
+            final ChargeReadPlatformService chargeReadPlatformService, final StaffReadPlatformService staffReadPlatformService,
+            final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService,
+            final InterestRateChartReadPlatformService productChartReadPlatformService,
+            final SavingsAccountReadPlatformService savingsAccountReadPlatformService,
+            final DropdownReadPlatformService dropdownReadPlatformService, final CalendarReadPlatformService calendarReadPlatformService,
+            PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.accountChartReadPlatformService = chartReadPlatformService;
+        this.paginationParametersDataValidator = paginationParametersDataValidator;
+        this.transactionsMapper = new SavingsAccountTransactionsMapper();
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.groupReadPlatformService = groupReadPlatformService;
+        this.depositProductReadPlatformService = depositProductReadPlatformService;
+        this.savingsDropdownReadPlatformService = savingsDropdownReadPlatformService;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.depositsDropdownReadPlatformService = depositsDropdownReadPlatformService;
+        this.productChartReadPlatformService = productChartReadPlatformService;
+        this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
+        this.rdTransactionTemplateMapper = new RecurringAccountDepositTransactionTemplateMapper();
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.calendarReadPlatformService = calendarReadPlatformService;
+        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+    }
+
+    @Override
+    public Collection<DepositAccountData> retrieveAll(final DepositAccountType depositAccountType,
+            final PaginationParameters paginationParameters) {
+
+        this.context.authenticatedUser();
+        final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType);
+        if (depositAccountMapper == null) return null;
+
+        final StringBuilder sqlBuilder = new StringBuilder(400);
+        sqlBuilder.append("select ");
+        sqlBuilder.append(depositAccountMapper.schema());
+        sqlBuilder.append(" where sa.deposit_type_enum = ? ");
+        // always append at the end of a sql statement
+        sqlBuilder.append(paginationParameters.paginationSql());
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), depositAccountMapper, new Object[] { depositAccountType.getValue() });
+    }
+
+    @Override
+    public Page<DepositAccountData> retrieveAllPaged(final DepositAccountType depositAccountType,
+            final PaginationParameters paginationParameters) {
+
+        this.paginationParametersDataValidator.validateParameterValues(paginationParameters, DepositsApiConstants.supportedOrderByValues,
+                depositAccountType.resourceName());
+
+        final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType);
+        if (depositAccountMapper == null) return null;
+
+        final StringBuilder sqlBuilder = new StringBuilder(400);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(depositAccountMapper.schema());
+        sqlBuilder.append(" where sa.deposit_type_enum = ? ");
+        sqlBuilder.append(paginationParameters.paginationSql());
+
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(),
+                new Object[] { depositAccountType.getValue() }, depositAccountMapper);
+    }
+
+    @Override
+    public Collection<DepositAccountData> retrieveAllForLookup(final DepositAccountType depositAccountType) {
+
+        final StringBuilder sqlBuilder = new StringBuilder(400);
+        sqlBuilder.append("select ");
+        sqlBuilder.append(this.depositAccountLookupsRowMapper.schema());
+        sqlBuilder.append(" where sa.deposit_type_enum = ? ");
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), this.depositAccountLookupsRowMapper,
+                new Object[] { depositAccountType.getValue() });
+    }
+
+    @Override
+    public Collection<DepositAccountData> retrieveForMaturityUpdate() {
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("SELECT ");
+        sqlBuilder.append(this.depositAccountForMaturityRowMapper.schema());
+        sqlBuilder.append(" WHERE da.deposit_type_enum in (?, ?) and da.status_enum = ?");
+
+        LocalDate today = DateUtils.getLocalDateOfTenant();
+
+        return this.jdbcTemplate.query(
+                sqlBuilder.toString(),
+                this.depositAccountForMaturityRowMapper,
+                new Object[] { formatter.print(today), DepositAccountType.FIXED_DEPOSIT.getValue(),
+                        DepositAccountType.RECURRING_DEPOSIT.getValue(), SavingsAccountStatusType.ACTIVE.getValue() });
+    }
+
+    @Override
+    public DepositAccountData retrieveOne(final DepositAccountType depositAccountType, final Long accountId) {
+        try {
+            this.context.authenticatedUser();
+
+            final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType);
+            if (depositAccountMapper == null) return null;
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("select ");
+            sqlBuilder.append(depositAccountMapper.schema());
+            sqlBuilder.append(" where sa.id = ? and sa.deposit_type_enum = ? ");
+
+            return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, new Object[] { accountId,
+                    depositAccountType.getValue() });
+
+        } catch (final EmptyResultDataAccessException e) {
+            throw new DepositAccountNotFoundException(depositAccountType, accountId);
+        }
+    }
+
+    @Override
+    public DepositAccountData retrieveOneWithClosureTemplate(final DepositAccountType depositAccountType, final Long accountId) {
+        try {
+            this.context.authenticatedUser();
+
+            final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType);
+            if (depositAccountMapper == null) return null;
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("select ");
+            sqlBuilder.append(depositAccountMapper.schema());
+            sqlBuilder.append(" where sa.id = ? and sa.deposit_type_enum = ? ");
+
+            DepositAccountData account = this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, new Object[] {
+                    accountId, depositAccountType.getValue() });
+            Collection<EnumOptionData> onAccountClosureOptions = SavingsEnumerations
+                    .depositAccountOnClosureType(DepositAccountOnClosureType.values());
+            final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+            final Collection<SavingsAccountData> savingsAccountDatas = this.savingsAccountReadPlatformService.retrieveActiveForLookup(
+                    account.clientId(), DepositAccountType.SAVINGS_DEPOSIT);
+            if (depositAccountType.isFixedDeposit()) {
+                account = FixedDepositAccountData.withClosureTemplateDetails((FixedDepositAccountData) account, onAccountClosureOptions,
+                        paymentTypeOptions, savingsAccountDatas);
+            } else if (depositAccountType.isRecurringDeposit()) {
+                account = RecurringDepositAccountData.withClosureTemplateDetails((RecurringDepositAccountData) account,
+                        onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas);
+            }
+
+            return account;
+
+        } catch (final EmptyResultDataAccessException e) {
+            throw new DepositAccountNotFoundException(depositAccountType, accountId);
+        }
+    }
+
+    @Override
+    public DepositAccountData retrieveOneWithChartSlabs(final DepositAccountType depositAccountType, Long accountId) {
+        DepositAccountData depositAccount = this.retrieveOne(depositAccountType, accountId);
+        DepositAccountInterestRateChartData chart = this.accountChartReadPlatformService.retrieveOneWithSlabsOnAccountId(accountId);
+
+        if (depositAccountType.isFixedDeposit()) {
+            depositAccount = FixedDepositAccountData.withInterestChart((FixedDepositAccountData) depositAccount, chart);
+        } else if (depositAccountType.isRecurringDeposit()) {
+            CalendarData calendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(accountId,
+                    CalendarEntityType.SAVINGS.getValue());
+            final Integer frequency = calendar.interval() == -1 ? 1 : calendar.interval();
+            final CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.fromInt(calendar.frequencyType().getId().intValue());
+            final PeriodFrequencyType periodFrequencyType = CalendarFrequencyType.from(calendarFrequencyType);
+            final EnumOptionData frequencyType = CommonEnumerations.termFrequencyType(periodFrequencyType, "recurring.deposit.frequency.");
+            depositAccount = RecurringDepositAccountData.withInterestChartAndRecurringDetails((RecurringDepositAccountData) depositAccount,
+                    chart, frequency, frequencyType);
+        }
+
+        return depositAccount;
+    }
+
+    @Override
+    public Collection<SavingsAccountTransactionData> retrieveAllTransactions(final DepositAccountType depositAccountType,
+            final Long accountId) {
+
+        final String sql = "select " + this.transactionsMapper.schema()
+                + " where sa.id = ? and sa.deposit_type_enum = ? order by tr.transaction_date DESC, tr.id DESC";
+
+        return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { accountId, depositAccountType.getValue() });
+    }
+
+    @Override
+    public DepositAccountData retrieveTemplate(final DepositAccountType depositAccountType, final Long clientId, final Long groupId,
+            final Long productId, final boolean staffInSelectedOfficeOnly) {
+
+        final AppUser loggedInUser = this.context.authenticatedUser();
+        Long officeId = loggedInUser.getOffice().getId();
+
+        ClientData client = null;
+        Collection<SavingsAccountData> savingsAccountDatas = null;
+        if (clientId != null) {
+            client = this.clientReadPlatformService.retrieveOne(clientId);
+            officeId = client.officeId();
+            savingsAccountDatas = this.savingsAccountReadPlatformService.retrieveActiveForLookup(clientId,
+                    DepositAccountType.SAVINGS_DEPOSIT);
+        }
+
+        GroupGeneralData group = null;
+        if (groupId != null) {
+            group = this.groupReadPlatformService.retrieveOne(groupId);
+            officeId = group.officeId();
+        }
+
+        final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = this.depositsDropdownReadPlatformService
+                .retrievePreClosurePenalInterestOnTypeOptions();
+
+        final Collection<EnumOptionData> periodFrequencyTypeOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
+        final Collection<DepositProductData> productOptions = this.depositProductReadPlatformService
+                .retrieveAllForLookup(depositAccountType);
+        DepositAccountData template = null;
+        if (productId != null) {
+
+            final DepositAccountTemplateMapper mapper = getDepositAccountTemplaMapper(depositAccountType, client, group);
+
+            final String sql = "select " + mapper.schema() + " where sa.id = ? and sa.deposit_type_enum = ? ";
+            template = this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { productId, depositAccountType.getValue() });
+
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = this.savingsDropdownReadPlatformService
+                    .retrieveCompoundingInterestPeriodTypeOptions();
+
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions = this.savingsDropdownReadPlatformService
+                    .retrieveInterestPostingPeriodTypeOptions();
+
+            final Collection<EnumOptionData> interestCalculationTypeOptions = this.savingsDropdownReadPlatformService
+                    .retrieveInterestCalculationTypeOptions();
+
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.savingsDropdownReadPlatformService
+                    .retrieveInterestCalculationDaysInYearTypeOptions();
+
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = this.savingsDropdownReadPlatformService
+                    .retrieveLockinPeriodFrequencyTypeOptions();
+
+            final Collection<EnumOptionData> withdrawalFeeTypeOptions = this.savingsDropdownReadPlatformService
+                    .retrievewithdrawalFeeTypeOptions();
+
+            final Collection<SavingsAccountTransactionData> transactions = null;
+            final Collection<ChargeData> productCharges = this.chargeReadPlatformService.retrieveSavingsProductCharges(productId);
+            // update charges from Product charges
+            final Collection<SavingsAccountChargeData> charges = fromChargesToSavingsCharges(productCharges);
+
+            final boolean feeChargesOnly = false;
+            final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly);
+
+            Collection<StaffData> fieldOfficerOptions = null;
+
+            if (officeId != null) {
+
+                if (staffInSelectedOfficeOnly) {
+                    // only bring back loan officers in selected branch/office
+                    final Collection<StaffData> fieldOfficersInBranch = this.staffReadPlatformService
+                            .retrieveAllLoanOfficersInOfficeById(officeId);
+
+                    if (!CollectionUtils.isEmpty(fieldOfficersInBranch)) {
+                        fieldOfficerOptions = new ArrayList<>(fieldOfficersInBranch);
+                    }
+                } else {
+                    // by default bring back all officers in selected
+                    // branch/office as well as officers in office above
+                    // this office
+                    final boolean restrictToLoanOfficersOnly = true;
+                    final Collection<StaffData> loanOfficersInHierarchy = this.staffReadPlatformService
+                            .retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeId, restrictToLoanOfficersOnly);
+
+                    if (!CollectionUtils.isEmpty(loanOfficersInHierarchy)) {
+                        fieldOfficerOptions = new ArrayList<>(loanOfficersInHierarchy);
+                    }
+                }
+            }
+
+            // retrieve chart Slabs
+            final InterestRateChartData productChartData = this.productChartReadPlatformService.retrieveActiveChartWithTemplate(productId);
+            final DepositAccountInterestRateChartData accountChart = DepositAccountInterestRateChartData.from(productChartData);
+
+            if (depositAccountType.isFixedDeposit()) {
+
+                template = FixedDepositAccountData.withTemplateOptions((FixedDepositAccountData) template, productOptions,
+                        fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                        interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                        withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions,
+                        periodFrequencyTypeOptions, savingsAccountDatas);
+
+                template = FixedDepositAccountData.withInterestChart((FixedDepositAccountData) template, accountChart);
+            } else if (depositAccountType.isRecurringDeposit()) {
+                template = RecurringDepositAccountData.withTemplateOptions((RecurringDepositAccountData) template, productOptions,
+                        fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                        interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                        withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions,
+                        periodFrequencyTypeOptions);
+                template = RecurringDepositAccountData.withInterestChartAndRecurringDetails((RecurringDepositAccountData) template,
+                        accountChart, null, null);
+
+            }
+
+        } else {
+
+            String clientName = null;
+            if (client != null) {
+                clientName = client.displayName();
+            }
+
+            String groupName = null;
+            if (group != null) {
+                groupName = group.getName();
+            }
+
+            final Collection<StaffData> fieldOfficerOptions = null;
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+            final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+            final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+            final Collection<SavingsAccountTransactionData> transactions = null;
+            final Collection<SavingsAccountChargeData> charges = null;
+
+            final boolean feeChargesOnly = true;
+            final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly);
+
+            if (depositAccountType.isFixedDeposit()) {
+
+                template = FixedDepositAccountData.withClientTemplate(clientId, clientName, groupId, groupName);
+
+                template = FixedDepositAccountData.withTemplateOptions((FixedDepositAccountData) template, productOptions,
+                        fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                        interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                        withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions,
+                        periodFrequencyTypeOptions, savingsAccountDatas);
+            } else if (depositAccountType.isRecurringDeposit()) {
+
+                template = RecurringDepositAccountData.withClientTemplate(clientId, clientName, groupId, groupName);
+
+                template = RecurringDepositAccountData.withTemplateOptions((RecurringDepositAccountData) template, productOptions,
+                        fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
+                        interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
+                        withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions,
+                        periodFrequencyTypeOptions);
+            }
+        }
+
+        return template;
+    }
+
+    @Override
+    public SavingsAccountTransactionData retrieveRecurringAccountDepositTransactionTemplate(final Long accountId) {
+
+        try {
+            final String sql = "select " + this.rdTransactionTemplateMapper.schema()
+                    + " where sa.id = ? and sa.deposit_type_enum = ? order by mss.installment limit 1";
+
+            return this.jdbcTemplate.queryForObject(sql, this.rdTransactionTemplateMapper, new Object[] { accountId,
+                    DepositAccountType.RECURRING_DEPOSIT.getValue() });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new DepositAccountNotFoundException(DepositAccountType.RECURRING_DEPOSIT, accountId);
+        }
+    }
+
+    @Override
+    public Collection<AccountTransferDTO> retrieveDataForInterestTransfer() {
+        final StringBuilder sqlBuilder = new StringBuilder(300);
+        AccountTransferMapper mapper = new AccountTransferMapper();
+        sqlBuilder.append("SELECT ");
+        sqlBuilder.append(mapper.schema());
+        sqlBuilder.append(" where da.transfer_interest_to_linked_account = 1 and ");
+        sqlBuilder
+                .append("st.transaction_date > (select IFNULL(max(sat.transaction_date),sa.activatedon_date) from m_savings_account_transaction sat where sat.transaction_type_enum = ? and sat.savings_account_id = sa.id and sat.is_reversed=0) ");
+        sqlBuilder
+                .append("and st.transaction_type_enum = ? and sa.status_enum = ? and st.is_reversed=0 and st.transaction_date > IFNULL(sa.lockedin_until_date_derived,sa.activatedon_date)");
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), mapper, new Object[] { SavingsAccountTransactionType.WITHDRAWAL.getValue(),
+                SavingsAccountTransactionType.INTEREST_POSTING.getValue(), SavingsAccountStatusType.ACTIVE.getValue() });
+    }
+
+    @Override
+    public Collection<Map<String, Object>> retriveDataForRDScheduleCreation() {
+        final StringBuilder sb = new StringBuilder(300);
+        sb.append(" select rd.savings_account_id savingsId, rd.mandatory_recommended_deposit_amount as amount,");
+        sb.append(" mc.recurrence as recurrence ,");
+        sb.append(" max(ms.duedate) as dueDate , max(ms.installment) as installment,");
+        sb.append(" count(ms.installment) as futureInstallemts");
+        sb.append(" from m_deposit_account_term_and_preclosure dat ");
+        sb.append(" inner join m_savings_account sa on sa.id = dat.savings_account_id and sa.status_enum = ?");
+        sb.append(" inner join m_deposit_account_recurring_detail rd on rd.savings_account_id = dat.savings_account_id ");
+        sb.append(" inner join m_calendar_instance mci on mci.entity_type_enum = ? and mci.entity_id = dat.savings_account_id  ");
+        sb.append(" inner join m_calendar mc  on mc.id = mci.calendar_id and mc.calendar_type_enum = ?");
+        sb.append(" inner join m_mandatory_savings_schedule ms on ms.savings_account_id = dat.savings_account_id and ms.duedate > ?");
+        sb.append(" where dat.deposit_period is null");
+        sb.append(" group by ms.savings_account_id");
+
+        return this.jdbcTemplate.queryForList(sb.toString(), SavingsAccountStatusType.ACTIVE.getValue(),
+                CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue(),
+                formatter.print(DateUtils.getLocalDateOfTenant()));
+    }
+
+    private static abstract class DepositAccountMapper implements RowMapper<DepositAccountData> {
+
+        private final String selectFieldsSql;
+        private final String selectTablesSql;
+
+        @Override
+        public abstract DepositAccountData mapRow(ResultSet rs, int rowNum) throws SQLException;
+
+        public DepositAccountMapper() {
+            final StringBuilder selectFieldsSqlBuilder = new StringBuilder(400);
+            selectFieldsSqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, ");
+            selectFieldsSqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
+            selectFieldsSqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
+            selectFieldsSqlBuilder.append("sp.id as productId, sp.name as productName, ");
+            selectFieldsSqlBuilder.append("s.id fieldOfficerId, s.display_name as fieldOfficerName, ");
+            selectFieldsSqlBuilder.append("sa.status_enum as statusEnum, ");
+            selectFieldsSqlBuilder.append("sa.submittedon_date as submittedOnDate,");
+            selectFieldsSqlBuilder.append("sbu.username as submittedByUsername,");
+            selectFieldsSqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,");
+            selectFieldsSqlBuilder.append("sa.rejectedon_date as rejectedOnDate,");
+            selectFieldsSqlBuilder.append("rbu.username as rejectedByUsername,");
+            selectFieldsSqlBuilder.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,");
+            selectFieldsSqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,");
+            selectFieldsSqlBuilder.append("wbu.username as withdrawnByUsername,");
+            selectFieldsSqlBuilder.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,");
+            selectFieldsSqlBuilder.append("sa.approvedon_date as approvedOnDate,");
+            selectFieldsSqlBuilder.append("abu.username as approvedByUsername,");
+            selectFieldsSqlBuilder.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,");
+            selectFieldsSqlBuilder.append("sa.activatedon_date as activatedOnDate,");
+            selectFieldsSqlBuilder.append("avbu.username as activatedByUsername,");
+            selectFieldsSqlBuilder.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,");
+            selectFieldsSqlBuilder.append("sa.closedon_date as closedOnDate,");
+            selectFieldsSqlBuilder.append("cbu.username as closedByUsername,");
+            selectFieldsSqlBuilder.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,");
+            selectFieldsSqlBuilder
+                    .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+            selectFieldsSqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            selectFieldsSqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            selectFieldsSqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, ");
+            selectFieldsSqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, ");
+            selectFieldsSqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, ");
+            selectFieldsSqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, ");
+            selectFieldsSqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, ");
+            selectFieldsSqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, ");
+            selectFieldsSqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,");
+            selectFieldsSqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
+            selectFieldsSqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
+            selectFieldsSqlBuilder.append("sa.total_deposits_derived as totalDeposits, ");
+            selectFieldsSqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, ");
+            selectFieldsSqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, ");
+            selectFieldsSqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, ");
+            selectFieldsSqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, ");
+            selectFieldsSqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, ");
+            selectFieldsSqlBuilder.append("sa.account_balance_derived as accountBalance, ");
+            selectFieldsSqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, ");
+            selectFieldsSqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, ");
+            selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, ");
+            selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation ");
+
+            this.selectFieldsSql = selectFieldsSqlBuilder.toString();
+
+            final StringBuilder selectTablesSqlBuilder = new StringBuilder(400);
+            selectTablesSqlBuilder.append("from m_savings_account sa ");
+            selectTablesSqlBuilder.append("left join m_deposit_account_term_and_preclosure datp on sa.id = datp.savings_account_id ");
+            selectTablesSqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
+            selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
+            selectTablesSqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
+            selectTablesSqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
+            selectTablesSqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id ");
+            selectTablesSqlBuilder.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid ");
+            selectTablesSqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid ");
+            selectTablesSqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid ");
+            selectTablesSqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid ");
+            selectTablesSqlBuilder.append("left join m_appuser avbu on rbu.id = sa.activatedon_userid ");
+            selectTablesSqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid ");
+
+            this.selectTablesSql = selectTablesSqlBuilder.toString();
+        }
+
+        public String selectFieldsSql() {
+            return this.selectFieldsSql;
+        }
+
+        public String selectTablesSql() {
+            return this.selectTablesSql;
+        }
+
+        public abstract String schema();
+
+        public DepositAccountData mapRow(final ResultSet rs) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final String externalId = rs.getString("externalId");
+
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final String groupName = rs.getString("groupName");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final String clientName = rs.getString("clientName");
+
+            final Long productId = rs.getLong("productId");
+            final String productName = rs.getString("productName");
+
+            final Long fieldOfficerId = rs.getLong("fieldOfficerId");
+            final String fieldOfficerName = rs.getString("fieldOfficerName");
+
+            final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum);
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate");
+            final String rejectedByUsername = rs.getString("rejectedByUsername");
+            final String rejectedByFirstname = rs.getString("rejectedByFirstname");
+            final String rejectedByLastname = rs.getString("rejectedByLastname");
+
+            final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate");
+            final String withdrawnByUsername = rs.getString("withdrawnByUsername");
+            final String withdrawnByFirstname = rs.getString("withdrawnByFirstname");
+            final String withdrawnByLastname = rs.getString("withdrawnByLastname");
+
+            final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
+            final String approvedByUsername = rs.getString("approvedByUsername");
+            final String approvedByFirstname = rs.getString("approvedByFirstname");
+            final String approvedByLastname = rs.getString("approvedByLastname");
+
+            final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate");
+            final String activatedByUsername = rs.getString("activatedByUsername");
+            final String activatedByFirstname = rs.getString("activatedByFirstname");
+            final String activatedByLastname = rs.getString("activatedByLastname");
+
+            final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+            final String closedByUsername = rs.getString("closedByUsername");
+            final String closedByFirstname = rs.getString("closedByFirstname");
+            final String closedByLastname = rs.getString("closedByLastname");
+
+            final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate,
+                    submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername,
+                    rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname,
+                    withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate,
+                    activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname,
+                    closedByLastname);
+
+            final Integer depositTypeId = JdbcSupport.getInteger(rs, "depositTypeId");
+            final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId);
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate");
+
+            final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations
+                    .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs,
+                            "interestCompoundingPeriodType")));
+
+            final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType
+                    .fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType")));
+
+            final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType
+                    .fromInt(JdbcSupport.getInteger(rs, "interestCalculationType")));
+
+            final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations
+                    .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs,
+                            "interestCalculationDaysInYearType")));
+
+            final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance");
+
+            final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
+            EnumOptionData lockinPeriodFrequencyType = null;
+            final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
+            if (lockinPeriodFrequencyTypeValue != null) {
+                final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+                lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
+            }
+
+            final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
+            final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+                    "minBalanceForInterestCalculation");
+
+            final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits");
+            final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals");
+            final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees");
+            final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalAnnualFees");
+
+            final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned");
+            final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestPosted");
+            final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance");
+            final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge");
+            final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge");
+            final BigDecimal totalOverdraftInterestDerived = null;
+
+            final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
+                    totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge,
+                    totalPenaltyCharge, totalOverdraftInterestDerived);
+
+            return DepositAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
+                    fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
+                    interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                    lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, depositType,
+                    minBalanceForInterestCalculation);
+        }
+    }
+
+    private static class FixedDepositAccountMapper extends DepositAccountMapper {
+
+        private final String schemaSql;
+
+        public FixedDepositAccountMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append(super.selectFieldsSql());
+
+            sqlBuilder.append(", datp.pre_closure_penal_applicable as preClosurePenalApplicable, ");
+            sqlBuilder.append("datp.pre_closure_penal_interest as preClosurePenalInterest, ");
+            sqlBuilder.append("datp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, ");
+            sqlBuilder.append("datp.min_deposit_term as minDepositTerm, ");
+            sqlBuilder.append("datp.max_deposit_term as maxDepositTerm, ");
+            sqlBuilder.append("datp.min_deposit_term_type_enum as minDepositTermTypeId, ");
+            sqlBuilder.append("datp.max_deposit_term_type_enum as maxDepositTermTypeId, ");
+            sqlBuilder.append("datp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, ");
+            sqlBuilder.append("datp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, ");
+            sqlBuilder.append("datp.deposit_amount as depositAmount, ");
+            sqlBuilder.append("datp.maturity_amount as maturityAmount, ");
+            sqlBuilder.append("datp.maturity_date as maturityDate, ");
+            sqlBuilder.append("datp.deposit_period as depositPeriod, ");
+            sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, ");
+            sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, ");
+            sqlBuilder.append("datp.transfer_interest_to_linked_account as transferInterestToSavings ");
+
+            sqlBuilder.append(super.selectTablesSql());
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public FixedDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final DepositAccountData depositAccountData = super.mapRow(rs);
+            final boolean preClosurePenalApplicable = rs.getBoolean("preClosurePenalApplicable");
+            final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "preClosurePenalInterest");
+            final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, "preClosurePenalInterestOnId");
+            final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null : SavingsEnumerations
+                    .preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId);
+
+            final Integer minDepositTerm = JdbcSupport.getInteger(rs, "minDepositTerm");
+            final Integer maxDepositTerm = JdbcSupport.getInteger(rs, "maxDepositTerm");
+            final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, "minDepositTermTypeId");
+            final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(minDepositTermTypeId);
+            final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, "maxDepositTermTypeId");
+            final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(maxDepositTermTypeId);
+            final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTerm");
+            final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTermTypeId");
+            final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(inMultiplesOfDepositTermTypeId);
+
+            final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "depositAmount");
+            final BigDecimal maturityAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maturityAmount");
+            final LocalDate maturityDate = JdbcSupport.getLocalDate(rs, "maturityDate");
+            final Integer depositPeriod = JdbcSupport.getInteger(rs, "depositPeriod");
+            final Integer depositPeriodFrequencyTypeId = JdbcSupport.getInteger(rs, "depositPeriodFrequencyTypeId");
+            final EnumOptionData depositPeriodFrequencyType = (depositPeriodFrequencyTypeId == null) ? null : SavingsEnumerations
+                    .depositPeriodFrequency(depositPeriodFrequencyTypeId);
+
+            final Integer onAccountClosureId = JdbcSupport.getInteger(rs, "onAccountClosureId");
+            final EnumOptionData onAccountClosureType = (onAccountClosureId == null) ? null : SavingsEnumerations
+                    .depositAccountOnClosureType(onAccountClosureId);
+            final Boolean transferInterestToSavings = rs.getBoolean("transferInterestToSavings");
+
+            return FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
+                    preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
+                    inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
+                    depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings);
+        }
+    }
+
+    private static class RecurringDepositAccountMapper extends DepositAccountMapper {
+
+        private final String schemaSql;
+
+        public RecurringDepositAccountMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append(this.selectFieldsSql());
+
+            sqlBuilder.append(", datp.pre_closure_penal_applicable as preClosurePenalApplicable, ");
+            sqlBuilder.append("datp.pre_closure_penal_interest as preClosurePenalInterest, ");
+            sqlBuilder.append("datp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, ");
+            sqlBuilder.append("dard.mandatory_recommended_deposit_amount as mandatoryRecommendedDepositAmount, ");
+            sqlBuilder.append("dard.total_overdue_amount as totalOverdueAmount, ");
+            sqlBuilder.append("dard.no_of_overdue_installments as noOfOverdueInstallments, ");
+            sqlBuilder.append("dard.is_mandatory as isMandatoryDeposit, ");
+            sqlBuilder.append("dard.allow_withdrawal as allowWithdrawal, ");
+            sqlBuilder.append("dard.adjust_advance_towards_future_payments as adjustAdvanceTowardsFuturePayments, ");
+            sqlBuilder.append("dard.is_calendar_inherited as isCalendarInherited, ");
+            sqlBuilder.append("datp.min_deposit_term as minDepositTerm, ");
+            sqlBuilder.append("datp.max_deposit_term as maxDepositTerm, ");
+            sqlBuilder.append("datp.min_deposit_term_type_enum as minDepositTermTypeId, ");
+            sqlBuilder.append("datp.max_deposit_term_type_enum as maxDepositTermTypeId, ");
+            sqlBuilder.append("datp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, ");
+            sqlBuilder.append("datp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, ");
+            sqlBuilder.append("datp.deposit_amount as depositAmount, ");
+            sqlBuilder.append("datp.maturity_amount as maturityAmount, ");
+            sqlBuilder.append("datp.expected_firstdepositon_date as expectedFirstDepositOnDate, ");
+            sqlBuilder.append("datp.maturity_date as maturityDate, ");
+            sqlBuilder.append("datp.deposit_period as depositPeriod, ");
+            sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, ");
+            sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId ");
+
+            sqlBuilder.append(this.selectTablesSql());
+            sqlBuilder.append("left join m_deposit_account_recurring_detail dard on sa.id = dard.savings_account_id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public RecurringDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final DepositAccountData depositAccountData = super.mapRow(rs);
+
+            final boolean preClosurePenalApplicable = rs.getBoolean("preClosurePenalApplicable");
+            final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "preClosurePenalInterest");
+            final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, "preClosurePenalInterestOnId");
+            final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null : SavingsEnumerations
+                    .preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId);
+            final Integer minDepositTerm = JdbcSupport.getInteger(rs, "minDepositTerm");
+            final Integer maxDepositTerm = JdbcSupport.getInteger(rs, "maxDepositTerm");
+            final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, "minDepositTermTypeId");
+            final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(minDepositTermTypeId);
+            final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, "maxDepositTermTypeId");
+            final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(maxDepositTermTypeId);
+            final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTerm");
+            final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTermTypeId");
+            final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(inMultiplesOfDepositTermTypeId);
+
+            final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "depositAmount");
+            final BigDecimal maturityAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maturityAmount");
+            final LocalDate maturityDate = JdbcSupport.getLocalDate(rs, "maturityDate");
+            final Integer depositPeriod = JdbcSupport.getInteger(rs, "depositPeriod");
+            final Integer depositPeriodFrequencyTypeId = JdbcSupport.getInteger(rs, "depositPeriodFrequencyTypeId");
+            final EnumOptionData depositPeriodFrequencyType = (depositPeriodFrequencyTypeId == null) ? null : SavingsEnumerations
+                    .depositPeriodFrequency(depositPeriodFrequencyTypeId);
+            final BigDecimal mandatoryRecommendedDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+                    "mandatoryRecommendedDepositAmount");
+            final BigDecimal totalOverdueAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverdueAmount");
+            final Integer noOfOverdueInstallments = JdbcSupport.getInteger(rs, "noOfOverdueInstallments");
+            final boolean isMandatoryDeposit = rs.getBoolean("isMandatoryDeposit");
+            final boolean allowWithdrawal = rs.getBoolean("allowWithdrawal");
+            final boolean adjustAdvanceTowardsFuturePayments = rs.getBoolean("adjustAdvanceTowardsFuturePayments");
+            final boolean isCalendarInherited = rs.getBoolean("isCalendarInherited");
+
+            final Integer onAccountClosureId = JdbcSupport.getInteger(rs, "onAccountClosureId");
+            final EnumOptionData onAccountClosureType = (onAccountClosureId == null) ? null : SavingsEnumerations
+                    .depositAccountOnClosureType(onAccountClosureId);
+            final LocalDate expectedFirstDepositOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstDepositOnDate");
+
+            return RecurringDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
+                    preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
+                    inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
+                    depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, onAccountClosureType, expectedFirstDepositOnDate,
+                    totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments,
+                    isCalendarInherited);
+
+        }
+    }
+
+    private static final class DepositAccountLookupMapper implements RowMapper<DepositAccountData> {
+
+        public String schema() {
+            return " sa.id as id, sa.account_no as accountNumber, sa.deposit_type_enum as depositTypeId from m_savings_account sa ";
+        }
+
+        @Override
+        public DepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("accountNumber");
+            final Integer depositTypeId = JdbcSupport.getInteger(rs, "depositTypeId");
+            final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId);
+
+            return DepositAccountData.lookup(id, name, depositType);
+        }
+    }
+
+    private DepositAccountMapper getDepositAccountMapper(final DepositAccountType depositAccountType) {
+        if (depositAccountType.isFixedDeposit()) {
+            return this.fixedDepositAccountRowMapper;
+        } else if (depositAccountType.isRecurringDeposit()) { return this.recurringDepositAccountRowMapper; }
+        return null;
+    }
+
+    private static final class SavingsAccountTransactionsMapper implements RowMapper<SavingsAccountTransactionData> {
+
+        private final String schemaSql;
+
+        public SavingsAccountTransactionsMapper() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, ");
+            sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,");
+            sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,");
+            sqlBuilder.append("fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,");
+            sqlBuilder.append("fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,");
+            sqlBuilder.append("fromtran.description as fromTransferDescription,");
+            sqlBuilder.append("totran.id as toTransferId, totran.is_reversed as toTransferReversed,");
+            sqlBuilder.append("totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,");
+            sqlBuilder.append("totran.description as toTransferDescription,");
+            sqlBuilder.append("sa.id as savingsId, sa.account_no as accountNo,");
+            sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, ");
+            sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, ");
+            sqlBuilder
+                    .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            sqlBuilder.append("pt.value as paymentTypeName ");
+            sqlBuilder.append("from m_savings_account sa ");
+            sqlBuilder.append("join m_savings_account_transaction tr on tr.savings_account_id = sa.id ");
+            sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
+            sqlBuilder.append("left join m_account_transfer_transaction fromtran on fromtran.from_savings_transaction_id = tr.id ");
+            sqlBuilder.append("left join m_account_transfer_transaction totran on totran.to_savings_transaction_id = tr.id ");
+            sqlBuilder.append("left join m_payment_detail pd on tr.payment_detail_id = pd.id ");
+            sqlBuilder.append("left join m_payment_type pt on pd.payment_type_id = pt.id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("transactionId");
+            final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
+            final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt);
+
+            final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate");
+            final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount");
+            final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance");
+            final boolean reversed = rs.getBoolean("reversed");
+
+            final Long savingsId = rs.getLong("savingsId");
+            final String accountNo = rs.getString("accountNo");
+
+            PaymentDetailData paymentDetailData = null;
+            if (transactionType.isDepositOrWithdrawal()) {
+                final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType");
+                if (paymentTypeId != null) {
+                    final String typeName = rs.getString("paymentTypeName");
+                    final PaymentTypeData paymentType = PaymentTypeData.instance(paymentTypeId, typeName);
+                    final String accountNumber = rs.getString("accountNumber");
+                    final String checkNumber = rs.getString("checkNumber");
+                    final String routingCode = rs.getString("routingCode");
+                    final String receiptNumber = rs.getString("receiptNumber");
+                    final String bankNumber = rs.getString("bankNumber");
+                    paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber,
+                            bankNumber);
+                }
+            }
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            AccountTransferData transfer = null;
+            final Long fromTransferId = JdbcSupport.getLong(rs, "fromTransferId");
+            final Long toTransferId = JdbcSupport.getLong(rs, "toTransferId");
+            if (fromTransferId != null) {
+                final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, "fromTransferDate");
+                final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fromTransferAmount");
+                final boolean fromTransferReversed = rs.getBoolean("fromTransferReversed");
+                final String fromTransferDescription = rs.getString("fromTransferDescription");
+
+                transfer = AccountTransferData.transferBasicDetails(fromTransferId, currency, fromTransferAmount, fromTransferDate,
+                        fromTransferDescription, fromTransferReversed);
+            } else if (toTransferId != null) {
+                final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, "toTransferDate");
+                final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "toTransferAmount");
+                final boolean toTransferReversed = rs.getBoolean("toTransferReversed");
+                final String toTransferDescription = rs.getString("toTransferDescription");
+
+                transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate,
+                        toTransferDescription, toTransferReversed);
+            }
+
+            return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency,
+                    amount, runningBalance, reversed, transfer);
+        }
+    }
+
+    private static abstract class DepositAccountTemplateMapper implements RowMapper<DepositAccountData> {
+
+        private final String selectFieldsSql;
+        private final String selectTablesSql;
+
+        private final ClientData client;
+        private final GroupGeneralData group;
+
+        public DepositAccountTemplateMapper(final ClientData client, final GroupGeneralData group) {
+            this.client = client;
+            this.group = group;
+
+            final StringBuilder selectFieldsSqlBuilder = new StringBuilder(400);
+            selectFieldsSqlBuilder.append("sa.id as productId, sa.name as productName, ");
+            selectFieldsSqlBuilder
+                    .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+            selectFieldsSqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            selectFieldsSqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            selectFieldsSqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualIterestRate, ");
+            selectFieldsSqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, ");
+            selectFieldsSqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, ");
+            selectFieldsSqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, ");
+            selectFieldsSqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, ");
+            selectFieldsSqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, ");
+            selectFieldsSqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,");
+            selectFieldsSqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
+            selectFieldsSqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
+            selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, ");
+            selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation ");
+
+            this.selectFieldsSql = selectFieldsSqlBuilder.toString();
+
+            final StringBuilder selectTablesSqlBuilder = new StringBuilder(400);
+            selectTablesSqlBuilder.append("from m_savings_product sa ");
+            selectTablesSqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sa.id = dptp.savings_product_id ");
+            selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
+
+            this.selectTablesSql = selectTablesSqlBuilder.toString();
+        }
+
+        public String selectFieldsSql() {
+            return this.selectFieldsSql;
+        }
+
+        public String selectTablesSql() {
+            return this.selectTablesSql;
+        }
+
+        public abstract String schema();
+
+        @Override
+        public abstract DepositAccountData mapRow(final ResultSet rs, final int rowNum) throws SQLException;
+
+        public DepositAccountData mapRow(final ResultSet rs) throws SQLException {
+
+            final Integer depositTypeId = JdbcSupport.getInteger(rs, "depositTypeId");
+            final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId);
+            final Long productId = rs.getLong("productId");
+            final String productName = rs.getString("productName");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final BigDecimal nominalAnnualIterestRate = rs.getBigDecimal("nominalAnnualIterestRate");
+
+            final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations
+                    .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs,
+                            "interestCompoundingPeriodType")));
+
+            final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType
+                    .fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType")));
+
+            final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType
+                    .fromInt(JdbcSupport.getInteger(rs, "interestCalculationType")));
+
+            final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations
+                    .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs,
+                            "interestCalculationDaysInYearType")));
+
+            final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance");
+
+            final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
+            EnumOptionData lockinPeriodFrequencyType = null;
+            final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
+            if (lockinPeriodFrequencyTypeValue != null) {
+                final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+                lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
+            }
+
+            final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
+            final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+                    "minBalanceForInterestCalculation");
+
+            Long clientId = null;
+            String clientName = null;
+            if (this.client != null) {
+                clientId = this.client.id();
+                clientName = this.client.displayName();
+            }
+
+            Long groupId = null;
+            String groupName = null;
+            if (this.group != null) {
+                groupId = this.group.getId();
+                groupName = this.group.getName();
+            }
+
+            final Long fieldOfficerId = null;
+            final String fieldOfficerName = null;
+            final SavingsAccountStatusEnumData status = null;
+            final SavingsAccountSummaryData summary = null;
+            final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault();
+
+            return DepositAccountData.instance(null, null, null, groupId, groupName, clientId, clientName, productId, productName,
+                    fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate, interestCompoundingPeriodType,
+                    interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
+                    lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, depositType,
+                    minBalanceForInterestCalculation);
+        }
+    }
+
+    private static class FixedDepositAccountTemplateMapper extends DepositAccountTemplateMapper {
+
+        private final String schemaSql;
+
+        public FixedDepositAccountTemplateMapper(final ClientData client, final GroupGeneralData group) {
+            super(client, group);
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append(super.selectFieldsSql());
+
+            sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, ");
+            sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, ");
+            sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, ");
+            sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, ");
+            sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, ");
+            sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, ");
+            sqlBuilder.append("dptp.max_deposit_term_type_enum as maxDepositTermTypeId, ");
+            sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, ");
+            sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId ");
+
+            sqlBuilder.append(super.selectTablesSql());
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public FixedDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final DepositAccountData depositAccountData = super.mapRow(rs);
+
+            final boolean preClosurePenalApplicable = rs.getBoolean("preClosurePenalApplicable");
+            final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "preClosurePenalInterest");
+            final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, "preClosurePenalInterestOnId");
+            final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null : SavingsEnumerations
+                    .preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId);
+
+            final Integer minDepositTerm = JdbcSupport.getInteger(rs, "minDepositTerm");
+            final Integer maxDepositTerm = JdbcSupport.getInteger(rs, "maxDepositTerm");
+            final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, "minDepositTermTypeId");
+            final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(minDepositTermTypeId);
+            final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, "maxDepositTermTypeId");
+            final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(maxDepositTermTypeId);
+            final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTerm");
+            final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTermTypeId");
+            final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(inMultiplesOfDepositTermTypeId);
+
+            final BigDecimal depositAmount = null;
+            final BigDecimal maturityAmount = null;
+            final LocalDate maturityDate = null;
+            final Integer depositPeriod = null;
+            final EnumOptionData depositPeriodFrequencyType = null;
+            final EnumOptionData onAccountClosureType = null;
+            final Boolean transferInterestToSavings = false;
+
+            return FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
+                    preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
+                    inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
+                    depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings);
+        }
+    }
+
+    private static class RecurringDepositAccountTemplateMapper extends DepositAccountTemplateMapper {
+
+        private final String schemaSql;
+
+        public RecurringDepositAccountTemplateMapper(final ClientData client, final GroupGeneralData group) {
+            super(client, group);
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append(this.selectFieldsSql());
+
+            sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, ");
+            sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, ");
+            sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, ");
+            sqlBuilder.append("dprd.is_mandatory as isMandatoryDeposit, ");
+            sqlBuilder.append("dprd.allow_withdrawal as allowWithdrawal, ");
+            sqlBuilder.append("dprd.adjust_advance_towards_future_payments as adjustAdvanceTowardsFuturePayments, ");
+            sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, ");
+            sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, ");
+            sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, ");
+            sqlBuilder.append("dptp.max_deposit_term_type_enum as maxDepositTermTypeId, ");
+            sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, ");
+            sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId ");
+
+            sqlBuilder.append(this.selectTablesSql());
+            sqlBuilder.append("left join m_deposit_product_recurring_detail dprd on sa.id = dprd.savings_product_id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public RecurringDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final DepositAccountData depositAccountData = super.mapRow(rs);
+
+            final boolean preClosurePenalApplicable = rs.getBoolean("preClosurePenalApplicable");
+            final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "preClosurePenalInterest");
+            final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, "preClosurePenalInterestOnId");
+            final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null : SavingsEnumerations
+                    .preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId);
+
+            final Integer minDepositTerm = JdbcSupport.getInteger(rs, "minDepositTerm");
+            final Integer maxDepositTerm = JdbcSupport.getInteger(rs, "maxDepositTerm");
+            final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, "minDepositTermTypeId");
+            final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(minDepositTermTypeId);
+            final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, "maxDepositTermTypeId");
+            final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(maxDepositTermTypeId);
+            final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTerm");
+            final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTermTypeId");
+            final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(inMultiplesOfDepositTermTypeId);
+            final boolean isMandatoryDeposit = rs.getBoolean("isMandatoryDeposit");
+            final boolean allowWithdrawal = rs.getBoolean("allowWithdrawal");
+            final boolean adjustAdvanceTowardsFuturePayments = rs.getBoolean("adjustAdvanceTowardsFuturePayments");
+            final boolean isCalendarInherited = false;
+
+            final BigDecimal depositAmount = null;
+            final BigDecimal maturityAmount = null;
+            final LocalDate maturityDate = null;
+            final Integer depositPeriod = null;
+            final EnumOptionData depositPeriodFrequencyType = null;
+            final LocalDate expectedFirstDepositOnDate = null;
+            final BigDecimal mandatoryRecommendedDepositAmount = null;
+            final EnumOptionData onAccountClosureType = null;
+            final BigDecimal totalOverdueAmount = null;
+            final Integer noOfOverdueInstallments = null;
+
+            return RecurringDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
+                    preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
+                    inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
+                    depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, onAccountClosureType, expectedFirstDepositOnDate,
+                    totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments,
+                    isCalendarInherited);
+        }
+    }
+
+    private DepositAccountTemplateMapper getDepositAccountTemplaMapper(final DepositAccountType depositAccountType, ClientData client,
+            GroupGeneralData group) {
+        if (depositAccountType.isFixedDeposit()) {
+            return new FixedDepositAccountTemplateMapper(client, group);
+        } else if (depositAccountType.isRecurringDeposit()) { return new RecurringDepositAccountTemplateMapper(client, group); }
+        return null;
+    }
+
+    private Collection<SavingsAccountChargeData> fromChargesToSavingsCharges(final Collection<ChargeData> productCharges) {
+        final Collection<SavingsAccountChargeData> savingsCharges = new ArrayList<>();
+        for (final ChargeData chargeData : productCharges) {
+            final SavingsAccountChargeData savingsCharge = chargeData.toSavingsAccountChargeData();
+            savingsCharges.add(savingsCharge);
+        }
+        return savingsCharges;
+    }
+
+    private static final class DepositAccountForMaturityMapper implements RowMapper<DepositAccountData> {
+
+        private final String schemaSql;
+
+        public DepositAccountForMaturityMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(200);
+            sqlBuilder.append("da.id as id, ");
+            sqlBuilder.append("da.account_no as accountNumber, ");
+            sqlBuilder.append("da.deposit_type_enum as depositTypeId ");
+            sqlBuilder.append("FROM m_savings_account da ");
+            sqlBuilder.append("inner join m_deposit_account_term_and_preclosure dat on dat.savings_account_id = da.id ");
+            sqlBuilder.append("and dat.maturity_date is not null and dat.maturity_date <= ? ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public DepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("accountNumber");
+            final Integer depositTypeId = JdbcSupport.getInteger(rs, "depositTypeId");
+            final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId);
+
+            return DepositAccountData.lookup(id, name, depositType);
+        }
+    }
+
+    private static final class RecurringAccountDepositTransactionTemplateMapper implements RowMapper<SavingsAccountTransactionData> {
+
+        private final String schemaSql;
+
+        public RecurringAccountDepositTransactionTemplateMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("sa.id as id, sa.account_no as accountNo, ");
+            sqlBuilder
+                    .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            sqlBuilder.append("sa.account_balance_derived as runningBalance, ");
+            sqlBuilder
+                    .append("mss.duedate as duedate, (mss.deposit_amount - ifnull(mss.deposit_amount_completed_derived,0)) as dueamount ");
+            sqlBuilder.append("from m_savings_account sa ");
+            sqlBuilder.append("join m_mandatory_savings_schedule mss  on mss.savings_account_id=sa.id and mss.completed_derived = false ");
+            sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long savingsId = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final BigDecimal dueamount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "dueamount");
+            final LocalDate duedate = JdbcSupport.getLocalDate(rs, "duedate");
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+            final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations
+                    .transactionType(SavingsAccountTransactionType.DEPOSIT.getValue());
+            final PaymentDetailData paymentDetailData = null;
+            final AccountTransferData transfer = null;
+            final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "runningBalance");
+            ;
+            return SavingsAccountTransactionData.create(savingsId, transactionType, paymentDetailData, savingsId, accountNo, duedate,
+                    currency, dueamount, runningBalance, false, transfer);
+        }
+    }
+
+    private class AccountTransferMapper implements RowMapper<AccountTransferDTO> {
+
+        private final String schemaSql;
+
+        public AccountTransferMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder
+                    .append("sa.id as fromAcc ,aa.linked_savings_account_id as toAcc,st.amount as amount, st.transaction_date as transactionDate ")
+                    .append(" from m_deposit_account_term_and_preclosure da ")
+                    .append(" inner join m_savings_account sa on da.savings_account_id = sa.id")
+                    .append(" inner join m_savings_account_transaction st on st.savings_account_id = sa.id")
+                    .append(" inner join m_portfolio_account_associations aa on aa.savings_account_id=sa.id");
+            schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public AccountTransferDTO mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+            final Long fromAccountId = rs.getLong("fromAcc");
+            final Long toAccountId = rs.getLong("toAcc");
+            final BigDecimal transactionAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amount");
+            final boolean isRegularTransaction = false;
+            final boolean isExceptionForBalanceCheck = false;
+            final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, "transactionDate");
+            return new AccountTransferDTO(transactionDate, transactionAmount, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS,
+                    fromAccountId, toAccountId, "trasfer interest to savings", null, null, null, null, null, null, null,
+                    AccountTransferType.INTEREST_TRANSFER.getValue(), null, null, null, null, null, null, isRegularTransaction,
+                    isExceptionForBalanceCheck);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformService.java
new file mode 100644
index 0000000..3a37eea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformService.java
@@ -0,0 +1,93 @@
+/**
+ * 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.savings.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.joda.time.LocalDate;
+
+public interface DepositAccountWritePlatformService {
+
+    CommandProcessingResult activateFDAccount(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult activateRDAccount(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult updateDepositAmountForRDAccount(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult depositToFDAccount(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult depositToRDAccount(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult withdrawal(Long savingsId, JsonCommand command, final DepositAccountType depositAccountType);
+
+    CommandProcessingResult calculateInterest(Long savingsId, final DepositAccountType depositAccountType);
+
+    CommandProcessingResult postInterest(Long savingsId, final DepositAccountType depositAccountType);
+
+    CommandProcessingResult undoFDTransaction(Long savingsId, Long transactionId, boolean allowAccountTransferModification);
+
+    CommandProcessingResult undoRDTransaction(Long savingsId, Long transactionId, boolean allowAccountTransferModification);
+
+    CommandProcessingResult adjustFDTransaction(Long savingsId, Long transactionId, JsonCommand command);
+
+    CommandProcessingResult adjustRDTransaction(Long savingsId, Long transactionId, JsonCommand command);
+
+    CommandProcessingResult closeFDAccount(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult closeRDAccount(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult prematureCloseFDAccount(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult prematureCloseRDAccount(Long savingsId, JsonCommand command);
+
+    SavingsAccountTransaction initiateSavingsTransfer(Long accountId, LocalDate transferDate, final DepositAccountType depositAccountType);
+
+    SavingsAccountTransaction withdrawSavingsTransfer(Long accountId, LocalDate transferDate, final DepositAccountType depositAccountType);
+
+    void rejectSavingsTransfer(Long accountId, final DepositAccountType depositAccountType);
+
+    SavingsAccountTransaction acceptSavingsTransfer(Long accountId, LocalDate transferDate, Office acceptedInOffice, Staff staff,
+            final DepositAccountType depositAccountType);
+
+    CommandProcessingResult addSavingsAccountCharge(JsonCommand command, final DepositAccountType depositAccountType);
+
+    CommandProcessingResult updateSavingsAccountCharge(JsonCommand command, final DepositAccountType depositAccountType);
+
+    CommandProcessingResult deleteSavingsAccountCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command,
+            final DepositAccountType depositAccountType);
+
+    CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId, final DepositAccountType depositAccountType);
+
+    CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command,
+            final DepositAccountType depositAccountType);
+
+    void applyChargeDue(final Long savingsAccountChargeId, final Long accountId, final DepositAccountType depositAccountType);
+
+    void updateMaturityDetails(final Long depositAccountId, final DepositAccountType depositAccountType);
+
+    void transferInterestToSavings() throws JobExecutionException;
+
+    SavingsAccountTransaction mandatorySavingsAccountDeposit(final SavingsAccountTransactionDTO accountTransactionDTO);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..3a5a7a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,1360 @@
+/**
+ * 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.savings.service;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
+import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.DepositAccountTransactionDataValidator;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeDataValidator;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountAssembler;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountDomainService;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransaction;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransactionRepository;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountRecurringDetail;
+import org.apache.fineract.portfolio.savings.domain.FixedDepositAccount;
+import org.apache.fineract.portfolio.savings.domain.RecurringDepositAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountCharge;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
+import org.apache.fineract.portfolio.savings.exception.DepositAccountTransactionNotAllowedException;
+import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
+import org.apache.fineract.portfolio.savings.exception.SavingsAccountTransactionNotFoundException;
+import org.apache.fineract.portfolio.savings.exception.TransactionUpdateNotAllowedException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
+
+@Service
+public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements DepositAccountWritePlatformService {
+
+    private final PlatformSecurityContext context;
+    private final SavingsAccountRepository savingAccountRepository;
+    private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
+    private final DepositAccountAssembler depositAccountAssembler;
+    private final DepositAccountTransactionDataValidator depositAccountTransactionDataValidator;
+    private final SavingsAccountChargeDataValidator savingsAccountChargeDataValidator;
+    private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+    private final DepositAccountDomainService depositAccountDomainService;
+    private final NoteRepository noteRepository;
+    private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
+    private final ChargeRepositoryWrapper chargeRepository;
+    private final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository;
+    private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
+    private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
+    private final DepositAccountReadPlatformService depositAccountReadPlatformService;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final HolidayRepositoryWrapper holidayRepository;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+    private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
+
+
+    @Autowired
+    public DepositAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final SavingsAccountRepository savingAccountRepository,
+            final SavingsAccountTransactionRepository savingsAccountTransactionRepository,
+            final DepositAccountAssembler depositAccountAssembler,
+            final DepositAccountTransactionDataValidator depositAccountTransactionDataValidator,
+            final SavingsAccountChargeDataValidator savingsAccountChargeDataValidator,
+            final PaymentDetailWritePlatformService paymentDetailWritePlatformService,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService,
+            final DepositAccountDomainService depositAccountDomainService, final NoteRepository noteRepository,
+            final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final ChargeRepositoryWrapper chargeRepository,
+            final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository, final HolidayRepositoryWrapper holidayRepository,
+            final WorkingDaysRepositoryWrapper workingDaysRepository,
+            final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
+            final AccountTransfersWritePlatformService accountTransfersWritePlatformService,
+            final DepositAccountReadPlatformService depositAccountReadPlatformService,
+            final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService,
+            final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository) {
+
+        this.context = context;
+        this.savingAccountRepository = savingAccountRepository;
+        this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
+        this.depositAccountAssembler = depositAccountAssembler;
+        this.depositAccountTransactionDataValidator = depositAccountTransactionDataValidator;
+        this.savingsAccountChargeDataValidator = savingsAccountChargeDataValidator;
+        this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
+        this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+        this.depositAccountDomainService = depositAccountDomainService;
+        this.noteRepository = noteRepository;
+        this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
+        this.chargeRepository = chargeRepository;
+        this.savingsAccountChargeRepository = savingsAccountChargeRepository;
+        this.holidayRepository = holidayRepository;
+        this.workingDaysRepository = workingDaysRepository;
+        this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
+        this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
+        this.depositAccountReadPlatformService = depositAccountReadPlatformService;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.depositAccountOnHoldTransactionRepository =depositAccountOnHoldTransactionRepository;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult activateFDAccount(final Long savingsId, final JsonCommand command) {
+
+        final AppUser user = this.context.authenticatedUser();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        this.depositAccountTransactionDataValidator.validateActivation(command);
+        final MathContext mc = MathContext.DECIMAL64;
+        final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
+                DepositAccountType.FIXED_DEPOSIT);
+        checkClientOrGroupActive(account);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        final Map<String, Object> changes = account.activate(user, command, DateUtils.getLocalDateOfTenant());
+
+        if (!changes.isEmpty()) {
+            final Locale locale = command.extractLocale();
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+            Money amountForDeposit = account.activateWithBalance();
+            if (amountForDeposit.isGreaterThanZero()) {
+
+                final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
+                        .retriveSavingsLinkedAssociation(savingsId);
+
+                if (portfolioAccountData == null) {
+                    final PaymentDetail paymentDetail = null;
+                    this.depositAccountDomainService.handleFDDeposit(account, fmt, account.getActivationLocalDate(),
+                            amountForDeposit.getAmount(), paymentDetail);
+                } else {
+                    final SavingsAccount fromSavingsAccount = null;
+                    boolean isRegularTransaction = false;
+                    final boolean isExceptionForBalanceCheck = false;
+                    final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(),
+                            amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS,
+                            portfolioAccountData.accountId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null,
+                            null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, account, fromSavingsAccount,
+                            isRegularTransaction, isExceptionForBalanceCheck);
+                    this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+                }
+                final boolean isInterestTransfer = false;
+                if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) {
+                    final LocalDate today = DateUtils.getLocalDateOfTenant();
+                    account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                            financialYearBeginningMonth);
+                } else {
+                    final LocalDate today = DateUtils.getLocalDateOfTenant();
+                    account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                            financialYearBeginningMonth);
+                }
+
+                updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+            }
+
+            final boolean isPreMatureClosure = false;
+            account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+            List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+            if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+                depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+            }
+            account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),depositAccountOnHoldTransactions);
+            this.savingAccountRepository.save(account);
+        }
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult activateRDAccount(final Long savingsId, final JsonCommand command) {
+        boolean isRegularTransaction = false;
+
+        final AppUser user = this.context.authenticatedUser();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        this.depositAccountTransactionDataValidator.validateActivation(command);
+
+        final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
+                DepositAccountType.RECURRING_DEPOSIT);
+        checkClientOrGroupActive(account);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        final Map<String, Object> changes = account.activate(user, command, DateUtils.getLocalDateOfTenant());
+
+        if (!changes.isEmpty()) {
+            final Locale locale = command.extractLocale();
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+            Money amountForDeposit = account.activateWithBalance();
+            if (amountForDeposit.isGreaterThanZero()) {
+                final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
+                        .retriveSavingsLinkedAssociation(savingsId);
+                if (portfolioAccountData == null) {
+                    this.depositAccountDomainService.handleRDDeposit(account, fmt, account.getActivationLocalDate(),
+                            amountForDeposit.getAmount(), null, isRegularTransaction);
+                } else {
+                    final boolean isExceptionForBalanceCheck = false;
+                    final SavingsAccount fromSavingsAccount = null;
+                    final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(),
+                            amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS,
+                            portfolioAccountData.accountId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null,
+                            null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, account, fromSavingsAccount,
+                            isRegularTransaction, isExceptionForBalanceCheck);
+                    this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+                }
+                updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+            }
+
+            final MathContext mc = MathContext.DECIMAL64;
+
+            // submitted and activation date are different then recalculate
+            // maturity date and schedule
+            if (!account.accountSubmittedAndActivationOnSameDate()) {
+                final boolean isPreMatureClosure = false;
+                final CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
+                        savingsId, CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue());
+
+                final Calendar calendar = calendarInstance.getCalendar();
+                final PeriodFrequencyType frequencyType = CalendarFrequencyType.from(CalendarUtils.getFrequency(calendar.getRecurrence()));
+                Integer frequency = CalendarUtils.getInterval(calendar.getRecurrence());
+                frequency = frequency == -1 ? 1 : frequency;
+                account.generateSchedule(frequencyType, frequency, calendar);
+                account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                        financialYearBeginningMonth);
+            }
+
+            final LocalDate overdueUptoDate = DateUtils.getLocalDateOfTenant();
+            account.updateOverduePayments(overdueUptoDate);
+            final boolean isInterestTransfer = false;
+            if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) {
+                final LocalDate today = DateUtils.getLocalDateOfTenant();
+                account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                        financialYearBeginningMonth);
+            } else {
+                final LocalDate today = DateUtils.getLocalDateOfTenant();
+                account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                        financialYearBeginningMonth);
+            }
+            List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+            if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+                depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+            }
+
+            account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),depositAccountOnHoldTransactions);
+
+            this.savingAccountRepository.save(account);
+        }
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult depositToFDAccount(final Long savingsId, @SuppressWarnings("unused") final JsonCommand command) {
+        // this.context.authenticatedUser();
+        throw new DepositAccountTransactionNotAllowedException(savingsId, "deposit", DepositAccountType.FIXED_DEPOSIT);
+
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateDepositAmountForRDAccount(Long savingsId, JsonCommand command) {
+        this.depositAccountTransactionDataValidator.validateDepositAmountUpdate(command);
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final BigDecimal mandatoryRecommendedDepositAmount = command
+                .bigDecimalValueOfParameterNamed(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName);
+
+        final LocalDate depositAmountUpdateEffectiveFromDate = command
+                .localDateValueOfParameterNamed(DepositsApiConstants.effectiveDateParamName);
+
+        final RecurringDepositAccount recurringDepositAccount = (RecurringDepositAccount) this.depositAccountAssembler
+                .assembleFrom(savingsId, DepositAccountType.RECURRING_DEPOSIT);
+        DepositAccountRecurringDetail recurringDetail = recurringDepositAccount.getRecurringDetail();
+        Map<String, Object> changes = recurringDetail.updateMandatoryRecommendedDepositAmount(mandatoryRecommendedDepositAmount,
+                depositAmountUpdateEffectiveFromDate, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(recurringDepositAccount.officeId()) //
+                .withClientId(recurringDepositAccount.clientId()) //
+                .withGroupId(recurringDepositAccount.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult depositToRDAccount(final Long savingsId, final JsonCommand command) {
+        boolean isRegularTransaction = true;
+
+        this.depositAccountTransactionDataValidator.validate(command, DepositAccountType.RECURRING_DEPOSIT);
+
+        final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
+                DepositAccountType.RECURRING_DEPOSIT);
+        checkClientOrGroupActive(account);
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+        final SavingsAccountTransaction deposit = this.depositAccountDomainService.handleRDDeposit(account, fmt, transactionDate,
+                transactionAmount, paymentDetail, isRegularTransaction);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(deposit.getId()) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) {
+        this.savingsAccountTransactionRepository.save(transaction);
+        return transaction.getId();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult withdrawal(final Long savingsId, final JsonCommand command,
+            final DepositAccountType depositAccountType) {
+
+        boolean isRegularTransaction = true;
+
+        this.depositAccountTransactionDataValidator.validate(command, depositAccountType);
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        final SavingsAccount account = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType);
+
+        checkClientOrGroupActive(account);
+
+        final SavingsAccountTransaction withdrawal = this.depositAccountDomainService.handleWithdrawal(account, fmt, transactionDate,
+                transactionAmount, paymentDetail, true, isRegularTransaction);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(withdrawal.getId()) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes)//
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult calculateInterest(final Long savingsId, final DepositAccountType depositAccountType) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount account = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType);
+        checkClientOrGroupActive(account);
+
+        final LocalDate today = DateUtils.getLocalDateOfTenant();
+        final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode());
+        boolean isInterestTransfer = false;
+
+        account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        this.savingAccountRepository.save(account);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult postInterest(final Long savingsId, final DepositAccountType depositAccountType) {
+
+        final SavingsAccount account = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType);
+        checkClientOrGroupActive(account);
+        postInterest(account);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .build();
+    }
+
+    @Transactional
+    private void postInterest(final SavingsAccount account) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        final LocalDate today = DateUtils.getLocalDateOfTenant();
+        final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode());
+        boolean isInterestTransfer = false;
+        account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        this.savingAccountRepository.save(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.TRANSFER_INTEREST_TO_SAVINGS)
+    public void transferInterestToSavings() throws JobExecutionException {
+        Collection<AccountTransferDTO> accountTrasferData = this.depositAccountReadPlatformService.retrieveDataForInterestTransfer();
+        StringBuilder sb = new StringBuilder(200);
+        for (AccountTransferDTO accountTransferDTO : accountTrasferData) {
+            try {
+                this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
+            } catch (final PlatformApiDataValidationException e) {
+                sb.append("Validation exception while trasfering Interest form ").append(accountTransferDTO.getFromAccountId())
+                        .append(" to ").append(accountTransferDTO.getToAccountId()).append("--------");
+            } catch (final InsufficientAccountBalanceException e) {
+                sb.append("InsufficientAccountBalance Exception while trasfering Interest form ")
+                        .append(accountTransferDTO.getFromAccountId()).append(" to ").append(accountTransferDTO.getToAccountId())
+                        .append("--------");
+            }
+        }
+        if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
+    }
+
+    @Override
+    public CommandProcessingResult undoFDTransaction(final Long savingsId, @SuppressWarnings("unused") final Long transactionId,
+            @SuppressWarnings("unused") final boolean allowAccountTransferModification) {
+
+        throw new DepositAccountTransactionNotAllowedException(savingsId, "undo", DepositAccountType.FIXED_DEPOSIT);
+    }
+
+    @Override
+    public CommandProcessingResult undoRDTransaction(final Long savingsId, final Long transactionId,
+            final boolean allowAccountTransferModification) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
+                DepositAccountType.RECURRING_DEPOSIT);
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        final SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository
+                .findOneByIdAndSavingsAccountId(transactionId, savingsId);
+        if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); }
+
+        if (!allowAccountTransferModification && this.accountTransfersReadPlatformService.isAccountTransfer(transactionId,
+                PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException(
+                        "error.msg.recurring.deposit.account.transfer.transaction.update.not.allowed",
+                        "Recurring deposit account transaction:" + transactionId + " update not allowed as it involves in account transfer",
+                        transactionId); }
+
+        final LocalDate today = DateUtils.getLocalDateOfTenant();
+        final MathContext mc = MathContext.DECIMAL64;
+
+        if (account.isNotActive()) {
+            throwValidationForActiveStatus(SavingsApiConstants.undoTransactionAction);
+        }
+        account.undoTransaction(transactionId);
+        boolean isInterestTransfer = false;
+        checkClientOrGroupActive(account);
+        if (savingsAccountTransaction.isPostInterestCalculationRequired()
+                && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else {
+            account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+
+        account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction,depositAccountOnHoldTransactions);
+        // account.activateAccountBasedOnBalance();
+        final boolean isPreMatureClosure = false;
+        account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        final LocalDate overdueUptoDate = DateUtils.getLocalDateOfTenant();
+
+        if (savingsAccountTransaction.isDeposit()) {
+            account.updateScheduleInstallments();
+        }
+
+        account.updateOverduePayments(overdueUptoDate);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult adjustFDTransaction(final Long savingsId, @SuppressWarnings("unused") final Long transactionId,
+            @SuppressWarnings("unused") final JsonCommand command) {
+
+        throw new DepositAccountTransactionNotAllowedException(savingsId, "modify", DepositAccountType.FIXED_DEPOSIT);
+    }
+
+    @Override
+    public CommandProcessingResult adjustRDTransaction(final Long savingsId, final Long transactionId, final JsonCommand command) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        this.depositAccountTransactionDataValidator.validate(command, DepositAccountType.RECURRING_DEPOSIT);
+
+        final SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository
+                .findOneByIdAndSavingsAccountId(transactionId, savingsId);
+        if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); }
+
+        if (!(savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal())
+                || savingsAccountTransaction.isReversed()) { throw new TransactionUpdateNotAllowedException(savingsId, transactionId); }
+
+        if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId,
+                PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException(
+                        "error.msg.saving.account.transfer.transaction.update.not.allowed",
+                        "Deposit account transaction:" + transactionId + " update not allowed as it involves in account transfer",
+                        transactionId); }
+
+        final LocalDate today = DateUtils.getLocalDateOfTenant();
+
+        final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
+                DepositAccountType.RECURRING_DEPOSIT);
+        if (account.isNotActive()) {
+            throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction);
+        }
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed(SavingsApiConstants.transactionDateParamName);
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(SavingsApiConstants.transactionAmountParamName);
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode());
+        account.undoTransaction(transactionId);
+
+        SavingsAccountTransaction transaction = null;
+        if (savingsAccountTransaction.isDeposit()) {
+            final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
+                    paymentDetail, savingsAccountTransaction.createdDate(), user);
+            transaction = account.deposit(transactionDTO);
+        } else {
+            final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
+                    paymentDetail, savingsAccountTransaction.createdDate(), user);
+            transaction = account.withdraw(transactionDTO, true);
+        }
+        final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction);
+        boolean isInterestTransfer = false;
+        if (account.isBeforeLastPostingPeriod(transactionDate)
+                || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else {
+            account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+
+        account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction,depositAccountOnHoldTransactions);
+        account.activateAccountBasedOnBalance();
+
+        if (savingsAccountTransaction.isDeposit()) {
+            account.handleScheduleInstallments(savingsAccountTransaction);
+        }
+        final LocalDate overdueUptoDate = DateUtils.getLocalDateOfTenant();
+        account.updateOverduePayments(overdueUptoDate);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(newtransactionId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes)//
+                .build();
+    }
+
+    /**
+     *
+     */
+    private void throwValidationForActiveStatus(final String actionName) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + actionName);
+        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("account.is.not.active");
+        throw new PlatformApiDataValidationException(dataValidationErrors);
+    }
+
+    private void checkClientOrGroupActive(final SavingsAccount account) {
+        final Client client = account.getClient();
+        if (client != null) {
+            if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); }
+        }
+        final Group group = account.group();
+        if (group != null) {
+            if (group.isNotActive()) { throw new GroupNotActiveException(group.getId()); }
+        }
+    }
+
+    @Override
+    public CommandProcessingResult closeFDAccount(final Long savingsId, final JsonCommand command) {
+        final AppUser user = this.context.authenticatedUser();
+        final boolean isPreMatureClose = false;
+        this.depositAccountTransactionDataValidator.validateClosing(command, DepositAccountType.FIXED_DEPOSIT, isPreMatureClose);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
+                DepositAccountType.FIXED_DEPOSIT);
+        checkClientOrGroupActive(account);
+
+        this.depositAccountDomainService.handleFDAccountClosure(account, paymentDetail, user, command, DateUtils.getLocalDateOfTenant(),
+                changes);
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note note = Note.savingNote(account, noteText);
+            changes.put("note", noteText);
+            this.noteRepository.save(note);
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes)//
+                .build();
+
+    }
+
+    @Override
+    public CommandProcessingResult closeRDAccount(final Long savingsId, final JsonCommand command) {
+        final AppUser user = this.context.authenticatedUser();
+
+        this.depositAccountTransactionDataValidator.validateClosing(command, DepositAccountType.RECURRING_DEPOSIT, false);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
+                DepositAccountType.RECURRING_DEPOSIT);
+        checkClientOrGroupActive(account);
+
+        this.depositAccountDomainService.handleRDAccountClosure(account, paymentDetail, user, command, DateUtils.getLocalDateOfTenant(),
+                changes);
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note note = Note.savingNote(account, noteText);
+            changes.put("note", noteText);
+            this.noteRepository.save(note);
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes)//
+                .build();
+
+    }
+
+    @Override
+    public CommandProcessingResult prematureCloseFDAccount(final Long savingsId, final JsonCommand command) {
+        final AppUser user = this.context.authenticatedUser();
+
+        this.depositAccountTransactionDataValidator.validateClosing(command, DepositAccountType.FIXED_DEPOSIT, true);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
+                DepositAccountType.FIXED_DEPOSIT);
+        checkClientOrGroupActive(account);
+
+        this.depositAccountDomainService.handleFDAccountPreMatureClosure(account, paymentDetail, user, command,
+                DateUtils.getLocalDateOfTenant(), changes);
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note note = Note.savingNote(account, noteText);
+            changes.put("note", noteText);
+            this.noteRepository.save(note);
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes)//
+                .build();
+
+    }
+
+    @Override
+    public CommandProcessingResult prematureCloseRDAccount(final Long savingsId, final JsonCommand command) {
+        final AppUser user = this.context.authenticatedUser();
+
+        this.depositAccountTransactionDataValidator.validateClosing(command, DepositAccountType.RECURRING_DEPOSIT, true);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
+                DepositAccountType.RECURRING_DEPOSIT);
+        checkClientOrGroupActive(account);
+        if (account.maturityDate() == null) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                    .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction);
+            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("can.not.close.as.premature");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.depositAccountDomainService.handleRDAccountPreMatureClosure(account, paymentDetail, user, command,
+                DateUtils.getLocalDateOfTenant(), changes);
+
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            final Note note = Note.savingNote(account, noteText);
+            changes.put("note", noteText);
+            this.noteRepository.save(note);
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes)//
+                .build();
+
+    }
+
+    @Override
+    public SavingsAccountTransaction initiateSavingsTransfer(final Long accountId, final LocalDate transferDate,
+            final DepositAccountType depositAccountType) {
+
+        AppUser user = getAppUserIfPresent();
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        final SavingsAccountTransaction newTransferTransaction = SavingsAccountTransaction.initiateTransfer(savingsAccount,
+                savingsAccount.office(), transferDate, user);
+        savingsAccount.getTransactions().add(newTransferTransaction);
+        savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue());
+        final MathContext mc = MathContext.DECIMAL64;
+        boolean isInterestTransfer = false;
+        savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        this.savingsAccountTransactionRepository.save(newTransferTransaction);
+        this.savingAccountRepository.save(savingsAccount);
+
+        postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        return newTransferTransaction;
+    }
+
+    @Override
+    public SavingsAccountTransaction withdrawSavingsTransfer(final Long accountId, final LocalDate transferDate,
+            final DepositAccountType depositAccountType) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        final SavingsAccountTransaction withdrawtransferTransaction = SavingsAccountTransaction.withdrawTransfer(savingsAccount,
+                savingsAccount.office(), transferDate, user);
+        savingsAccount.getTransactions().add(withdrawtransferTransaction);
+        savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue());
+        final MathContext mc = MathContext.DECIMAL64;
+        boolean isInterestTransfer = false;
+        savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        this.savingsAccountTransactionRepository.save(withdrawtransferTransaction);
+        this.savingAccountRepository.save(savingsAccount);
+
+        postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        return withdrawtransferTransaction;
+    }
+
+    @Override
+    public void rejectSavingsTransfer(final Long accountId, final DepositAccountType depositAccountType) {
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType);
+        savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_ON_HOLD.getValue());
+        this.savingAccountRepository.save(savingsAccount);
+    }
+
+    @Override
+    public SavingsAccountTransaction acceptSavingsTransfer(final Long accountId, final LocalDate transferDate,
+            final Office acceptedInOffice, final Staff fieldOfficer, final DepositAccountType depositAccountType) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        final SavingsAccountTransaction acceptTransferTransaction = SavingsAccountTransaction.approveTransfer(savingsAccount,
+                acceptedInOffice, transferDate, user);
+        savingsAccount.getTransactions().add(acceptTransferTransaction);
+        savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue());
+        if (fieldOfficer != null) {
+            savingsAccount.reassignSavingsOfficer(fieldOfficer, transferDate);
+        }
+        boolean isInterestTransfer = false;
+        final MathContext mc = MathContext.DECIMAL64;
+        savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        this.savingsAccountTransactionRepository.save(acceptTransferTransaction);
+        this.savingAccountRepository.save(savingsAccount);
+
+        postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        return acceptTransferTransaction;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command, final DepositAccountType depositAccountType) {
+
+        this.context.authenticatedUser();
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final Long savingsAccountId = command.getSavingsId();
+        this.savingsAccountChargeDataValidator.validateAdd(command.json());
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsAccountId, depositAccountType);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Locale locale = command.extractLocale();
+        final String format = command.dateFormat();
+        final DateTimeFormatter fmt = StringUtils.isNotBlank(format) ? DateTimeFormat.forPattern(format).withLocale(locale)
+                : DateTimeFormat.forPattern("dd MM yyyy");
+
+        final Long chargeDefinitionId = command.longValueOfParameterNamed(chargeIdParamName);
+        final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
+
+        final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewFromJson(savingsAccount, chargeDefinition, command);
+
+        if (savingsAccountCharge.getDueLocalDate() != null) {
+            // transaction date should not be on a holiday or non working day
+            if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled()
+                    && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+
+            if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled()
+                    && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+
+        savingsAccount.addCharge(fmt, savingsAccountCharge, chargeDefinition);
+
+        this.savingAccountRepository.saveAndFlush(savingsAccount);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountCharge.getId()) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsAccountId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand command, final DepositAccountType depositAccountType) {
+
+        this.context.authenticatedUser();
+        this.savingsAccountChargeDataValidator.validateUpdate(command.json());
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final Long savingsAccountId = command.getSavingsId();
+        // SavingsAccount Charge entity
+        final Long savingsChargeId = command.entityId();
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsAccountId, depositAccountType);
+        checkClientOrGroupActive(savingsAccount);
+
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(savingsChargeId,
+                savingsAccountId);
+
+        final Map<String, Object> changes = savingsAccountCharge.update(command);
+
+        if (savingsAccountCharge.getDueLocalDate() != null) {
+            final Locale locale = command.extractLocale();
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+            // transaction date should not be on a holiday or non working day
+            if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled()
+                    && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+
+            if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled()
+                    && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+
+        this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountCharge.getId()) //
+                .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
+                .withClientId(savingsAccountCharge.savingsAccount().clientId()) //
+                .withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
+                .withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId,
+            @SuppressWarnings("unused") final DepositAccountType depositAccountType) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId);
+
+        // Get Savings account from savings charge
+        final SavingsAccount account = savingsAccountCharge.savingsAccount();
+        this.depositAccountAssembler.assignSavingAccountHelpers(account);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        account.waiveCharge(savingsAccountChargeId, user);
+        boolean isInterestTransfer = false;
+        final MathContext mc = MathContext.DECIMAL64;
+        if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+
+        account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction,depositAccountOnHoldTransactions);
+
+        this.savingAccountRepository.saveAndFlush(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountChargeId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsAccountId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId,
+            @SuppressWarnings("unused") final JsonCommand command, final DepositAccountType depositAccountType) {
+        this.context.authenticatedUser();
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsAccountId, depositAccountType);
+        checkClientOrGroupActive(savingsAccount);
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId);
+
+        savingsAccount.removeCharge(savingsAccountCharge);
+        this.savingAccountRepository.saveAndFlush(savingsAccount);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountChargeId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsAccountId) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command,
+            @SuppressWarnings("unused") final DepositAccountType depositAccountType) {
+
+        this.context.authenticatedUser();
+
+        this.savingsAccountChargeDataValidator.validatePayCharge(command.json());
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(amountParamName);
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
+
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        // transaction date should not be on a holiday or non working day
+        if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled()
+                && this.holidayRepository.isHoliday(savingsAccountCharge.savingsAccount().officeId(), transactionDate)) {
+            baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt))
+                    .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled()
+                && !this.workingDaysRepository.isWorkingDay(transactionDate)) {
+            baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt))
+                    .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.a.nonworking.day");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountCharge.getId()) //
+                .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
+                .withClientId(savingsAccountCharge.savingsAccount().clientId()) //
+                .withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
+                .withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
+                .build();
+
+    }
+
+    @Transactional
+    @Override
+    public void applyChargeDue(final Long savingsAccountChargeId, final Long accountId,
+            @SuppressWarnings("unused") final DepositAccountType depositAccountType) {
+        // always use current date as transaction date for batch job
+        final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, accountId);
+
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
+
+        while (transactionDate.isAfter(savingsAccountCharge.getDueLocalDate())) {
+            payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt);
+        }
+    }
+
+    @Transactional
+    private void payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final BigDecimal amountPaid,
+            final DateTimeFormatter formatter) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        // Get Savings account from savings charge
+        final SavingsAccount account = savingsAccountCharge.savingsAccount();
+        this.depositAccountAssembler.assignSavingAccountHelpers(account);
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user);
+        boolean isInterestTransfer = false;
+        final MathContext mc = MathContext.DECIMAL64;
+        if (account.isBeforeLastPostingPeriod(transactionDate)) {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+
+        account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(),depositAccountOnHoldTransactions);
+
+        this.savingAccountRepository.save(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+    }
+
+    @Transactional
+    @Override
+    public void updateMaturityDetails(Long depositAccountId, DepositAccountType depositAccountType) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount account = this.depositAccountAssembler.assembleFrom(depositAccountId, depositAccountType);
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        if (depositAccountType.isFixedDeposit()) {
+            ((FixedDepositAccount) account).updateMaturityStatus(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else if (depositAccountType.isRecurringDeposit()) {
+            ((RecurringDepositAccount) account).updateMaturityStatus(isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+    }
+
+    private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
+            Set<Long> existingReversedTransactionIds) {
+        existingTransactionIds.addAll(account.findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
+    }
+
+    private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
+            final Set<Long> existingReversedTransactionIds) {
+
+        final MonetaryCurrency currency = savingsAccount.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
+        boolean isAccountTransfer = false;
+        final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
+                existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
+    }
+
+    @Transactional
+    @Override
+    public SavingsAccountTransaction mandatorySavingsAccountDeposit(final SavingsAccountTransactionDTO accountTransactionDTO) {
+        boolean isRegularTransaction = false;
+        final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler
+                .assembleFrom(accountTransactionDTO.getSavingsAccountId(), DepositAccountType.RECURRING_DEPOSIT);
+        final PaymentDetail paymentDetail = accountTransactionDTO.getPaymentDetail();
+        if (paymentDetail != null && paymentDetail.getId() == null) {
+            this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
+        }
+        return this.depositAccountDomainService.handleRDDeposit(account, accountTransactionDTO.getFormatter(),
+                accountTransactionDTO.getTransactionDate(), accountTransactionDTO.getTransactionAmount(), paymentDetail,
+                isRegularTransaction);
+    }
+
+    private AppUser getAppUserIfPresent() {
+        AppUser user = null;
+        if (this.context != null) {
+            user = this.context.getAuthenticatedUserIfPresent();
+        }
+        return user;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformService.java
new file mode 100644
index 0000000..e2f9101
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformService.java
@@ -0,0 +1,44 @@
+/**
+ * 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.savings.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+
+public interface DepositApplicationProcessWritePlatformService {
+
+    CommandProcessingResult submitFDApplication(JsonCommand command);
+
+    CommandProcessingResult submitRDApplication(JsonCommand command);
+
+    CommandProcessingResult modifyFDApplication(Long accountId, JsonCommand command);
+
+    CommandProcessingResult modifyRDApplication(Long accountId, JsonCommand command);
+
+    CommandProcessingResult deleteApplication(Long accountId, DepositAccountType depositAccountType);
+
+    CommandProcessingResult approveApplication(Long accountId, JsonCommand command, DepositAccountType depositAccountType);
+
+    CommandProcessingResult undoApplicationApproval(Long accountId, JsonCommand command, DepositAccountType depositAccountType);
+
+    CommandProcessingResult rejectApplication(Long accountId, JsonCommand command, DepositAccountType depositAccountType);
+
+    CommandProcessingResult applicantWithdrawsFromApplication(Long accountId, JsonCommand command, DepositAccountType depositAccountType);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..9788bbd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,753 @@
+/**
+ * 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.savings.service;
+
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isCalendarInheritedParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.recurringFrequencyParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.recurringFrequencyTypeParamName;
+import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName;
+
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
+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.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationType;
+import org.apache.fineract.portfolio.account.domain.AccountAssociations;
+import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepository;
+import org.apache.fineract.portfolio.group.exception.CenterNotActiveException;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.DepositAccountDataValidator;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountAssembler;
+import org.apache.fineract.portfolio.savings.domain.FixedDepositAccount;
+import org.apache.fineract.portfolio.savings.domain.FixedDepositAccountRepository;
+import org.apache.fineract.portfolio.savings.domain.RecurringDepositAccount;
+import org.apache.fineract.portfolio.savings.domain.RecurringDepositAccountRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountCharge;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargeAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
+import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
+import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
+import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl implements DepositApplicationProcessWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final SavingsAccountRepositoryWrapper savingAccountRepository;
+    private final FixedDepositAccountRepository fixedDepositAccountRepository;
+    private final RecurringDepositAccountRepository recurringDepositAccountRepository;
+    private final DepositAccountAssembler depositAccountAssembler;
+    private final DepositAccountDataValidator depositAccountDataValidator;
+    private final AccountNumberGenerator accountNumberGenerator;
+    private final ClientRepositoryWrapper clientRepository;
+    private final GroupRepository groupRepository;
+    private final SavingsProductRepository savingsProductRepository;
+    private final NoteRepository noteRepository;
+    private final StaffRepositoryWrapper staffRepository;
+    private final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator;
+    private final SavingsAccountChargeAssembler savingsAccountChargeAssembler;
+    private final AccountAssociationsRepository accountAssociationsRepository;
+    private final FromJsonHelper fromJsonHelper;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
+
+    @Autowired
+    public DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final SavingsAccountRepositoryWrapper savingAccountRepository, final DepositAccountAssembler depositAccountAssembler,
+            final DepositAccountDataValidator depositAccountDataValidator, final AccountNumberGenerator accountNumberGenerator,
+            final ClientRepositoryWrapper clientRepository, final GroupRepository groupRepository,
+            final SavingsProductRepository savingsProductRepository, final NoteRepository noteRepository,
+            final StaffRepositoryWrapper staffRepository,
+            final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator,
+            final SavingsAccountChargeAssembler savingsAccountChargeAssembler,
+            final FixedDepositAccountRepository fixedDepositAccountRepository,
+            final RecurringDepositAccountRepository recurringDepositAccountRepository,
+            final AccountAssociationsRepository accountAssociationsRepository, final FromJsonHelper fromJsonHelper,
+            final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService,
+            final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository) {
+        this.context = context;
+        this.savingAccountRepository = savingAccountRepository;
+        this.depositAccountAssembler = depositAccountAssembler;
+        this.accountNumberGenerator = accountNumberGenerator;
+        this.depositAccountDataValidator = depositAccountDataValidator;
+        this.clientRepository = clientRepository;
+        this.groupRepository = groupRepository;
+        this.savingsProductRepository = savingsProductRepository;
+        this.noteRepository = noteRepository;
+        this.staffRepository = staffRepository;
+        this.savingsAccountApplicationTransitionApiJsonValidator = savingsAccountApplicationTransitionApiJsonValidator;
+        this.savingsAccountChargeAssembler = savingsAccountChargeAssembler;
+        this.fixedDepositAccountRepository = fixedDepositAccountRepository;
+        this.recurringDepositAccountRepository = recurringDepositAccountRepository;
+        this.accountAssociationsRepository = accountAssociationsRepository;
+        this.fromJsonHelper = fromJsonHelper;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.accountNumberFormatRepository = accountNumberFormatRepository;
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataAccessException dve) {
+
+        final StringBuilder errorCodeBuilder = new StringBuilder("error.msg.").append(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("sa_account_no_UNIQUE")) {
+            final String accountNo = command.stringValueOfParameterNamed("accountNo");
+            errorCodeBuilder.append(".duplicate.accountNo");
+            throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Savings account with accountNo " + accountNo
+                    + " already exists", "accountNo", accountNo);
+
+        } else if (realCause.getMessage().contains("sa_external_id_UNIQUE")) {
+
+            final String externalId = command.stringValueOfParameterNamed("externalId");
+            errorCodeBuilder.append(".duplicate.externalId");
+            throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Savings account with externalId " + externalId
+                    + " already exists", "externalId", externalId);
+        }
+
+        errorCodeBuilder.append(".unknown.data.integrity.issue");
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Unknown data integrity issue with savings account.");
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult submitFDApplication(final JsonCommand command) {
+        try {
+            this.depositAccountDataValidator.validateFixedDepositForSubmit(command.json());
+            final AppUser submittedBy = this.context.authenticatedUser();
+
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                    .isSavingsInterestPostingAtCurrentPeriodEnd();
+            final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+            final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(command, submittedBy,
+                    DepositAccountType.FIXED_DEPOSIT);
+
+            final MathContext mc = MathContext.DECIMAL64;
+            final boolean isPreMatureClosure = false;
+
+            account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+            this.fixedDepositAccountRepository.save(account);
+
+            if (account.isAccountNumberRequiresAutoGeneration()) {
+                AccountNumberFormat accountNumberFormat = this.accountNumberFormatRepository.findByAccountType(EntityAccountType.CLIENT);
+                account.updateAccountNo(this.accountNumberGenerator.generate(account, accountNumberFormat));
+
+                this.savingAccountRepository.save(account);
+            }
+
+            // Save linked account information
+            final Long savingsAccountId = command.longValueOfParameterNamed(DepositsApiConstants.linkedAccountParamName);
+            if (savingsAccountId != null) {
+                final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsAccountId,
+                        DepositAccountType.SAVINGS_DEPOSIT);
+                this.depositAccountDataValidator.validatelinkedSavingsAccount(savingsAccount, account);
+                boolean isActive = true;
+                final AccountAssociations accountAssociations = AccountAssociations.associateSavingsAccount(account, savingsAccount,
+                        AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue(), isActive);
+                this.accountAssociationsRepository.save(accountAssociations);
+            }
+
+            final Long savingsId = account.getId();
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(savingsId) //
+                    .withOfficeId(account.officeId()) //
+                    .withClientId(account.clientId()) //
+                    .withGroupId(account.groupId()) //
+                    .withSavingsId(savingsId) //
+                    .build();
+        } catch (final DataAccessException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult submitRDApplication(final JsonCommand command) {
+        try {
+            this.depositAccountDataValidator.validateRecurringDepositForSubmit(command.json());
+            final AppUser submittedBy = this.context.authenticatedUser();
+
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                    .isSavingsInterestPostingAtCurrentPeriodEnd();
+            final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+            final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(command,
+                    submittedBy, DepositAccountType.RECURRING_DEPOSIT);
+
+            this.recurringDepositAccountRepository.save(account);
+
+            if (account.isAccountNumberRequiresAutoGeneration()) {
+                final AccountNumberFormat accountNumberFormat = this.accountNumberFormatRepository
+                        .findByAccountType(EntityAccountType.SAVINGS);
+                account.updateAccountNo(this.accountNumberGenerator.generate(account, accountNumberFormat));
+            }
+
+            final Long savingsId = account.getId();
+            final CalendarInstance calendarInstance = getCalendarInstance(command, account);
+            this.calendarInstanceRepository.save(calendarInstance);
+
+            // FIXME: Avoid save separately (Calendar instance requires account
+            // details)
+            final MathContext mc = MathContext.DECIMAL64;
+            final Calendar calendar = calendarInstance.getCalendar();
+            final PeriodFrequencyType frequencyType = CalendarFrequencyType.from(CalendarUtils.getFrequency(calendar.getRecurrence()));
+            Integer frequency = CalendarUtils.getInterval(calendar.getRecurrence());
+            frequency = frequency == -1 ? 1 : frequency;
+            account.generateSchedule(frequencyType, frequency, calendar);
+            final boolean isPreMatureClosure = false;
+            account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+            account.validateApplicableInterestRate();
+            this.savingAccountRepository.save(account);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(savingsId) //
+                    .withOfficeId(account.officeId()) //
+                    .withClientId(account.clientId()) //
+                    .withGroupId(account.groupId()) //
+                    .withSavingsId(savingsId) //
+                    .build();
+        } catch (final DataAccessException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private CalendarInstance getCalendarInstance(final JsonCommand command, RecurringDepositAccount account) {
+        CalendarInstance calendarInstance = null;
+        final boolean isCalendarInherited = command.booleanPrimitiveValueOfParameterNamed(isCalendarInheritedParamName);
+
+        if (isCalendarInherited) {
+            Set<Group> groups = account.getClient().getGroups();
+            Long groupId = null;
+            if (groups.isEmpty()) {
+                final String defaultUserMessage = "Client does not belong to group/center. Cannot follow group/center meeting frequency.";
+                throw new GeneralPlatformDomainRuleException(
+                        "error.msg.recurring.deposit.account.cannot.create.not.belongs.to.any.groups.to.follow.meeting.frequency",
+                        defaultUserMessage, account.clientId());
+            } else if (groups.size() > 1) {
+                final String defaultUserMessage = "Client belongs to more than one group. Cannot support recurring deposit.";
+                throw new GeneralPlatformDomainRuleException(
+                        "error.msg.recurring.deposit.account.cannot.create.belongs.to.multiple.groups", defaultUserMessage,
+                        account.clientId());
+            } else {
+                Group group = groups.iterator().next();
+                Group parent = group.getParent();
+                Integer entityType = CalendarEntityType.GROUPS.getValue();
+                if (parent != null) {
+                    groupId = parent.getId();
+                    entityType = CalendarEntityType.CENTERS.getValue();
+                } else {
+                    groupId = group.getId();
+                }
+                CalendarInstance parentCalendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
+                        groupId, entityType, CalendarType.COLLECTION.getValue());
+                if(parentCalendarInstance == null){
+                	final String defaultUserMessage = "Meeting frequency is not attached to the Group/Center to which the client belongs to.";
+                    throw new GeneralPlatformDomainRuleException(
+                            "error.msg.meeting.frequency.not.attached.to.group.to.which.client.belongs.to",
+                            defaultUserMessage, account.clientId());
+                }
+                calendarInstance = CalendarInstance.from(parentCalendarInstance.getCalendar(), account.getId(),
+                        CalendarEntityType.SAVINGS.getValue());
+            }
+        } else {
+            LocalDate calendarStartDate = account.depositStartDate();
+            final Integer frequencyType = command.integerValueSansLocaleOfParameterNamed(recurringFrequencyTypeParamName);
+            final PeriodFrequencyType periodFrequencyType = PeriodFrequencyType.fromInt(frequencyType);
+            final Integer frequency = command.integerValueSansLocaleOfParameterNamed(recurringFrequencyParamName);
+
+            final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
+            final String title = "recurring_savings_" + account.getId();
+            final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(),
+                    CalendarFrequencyType.from(periodFrequencyType), frequency, repeatsOnDay);
+            calendarInstance = CalendarInstance.from(calendar, account.getId(), CalendarEntityType.SAVINGS.getValue());
+        }
+        if (calendarInstance == null) {
+            final String defaultUserMessage = "No valid recurring details available for recurring depost account creation.";
+            throw new GeneralPlatformDomainRuleException(
+                    "error.msg.recurring.deposit.account.cannot.create.no.valid.recurring.details.available", defaultUserMessage,
+                    account.clientId());
+        }
+        return calendarInstance;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult modifyFDApplication(final Long accountId, final JsonCommand command) {
+        try {
+            this.depositAccountDataValidator.validateFixedDepositForUpdate(command.json());
+
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                    .isSavingsInterestPostingAtCurrentPeriodEnd();
+            final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+            final Map<String, Object> changes = new LinkedHashMap<>(20);
+
+            final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(accountId,
+                    DepositAccountType.FIXED_DEPOSIT);
+            checkClientOrGroupActive(account);
+            account.modifyApplication(command, changes);
+            account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), DepositAccountType.FIXED_DEPOSIT.resourceName());
+
+            if (!changes.isEmpty()) {
+                updateFDAndRDCommonChanges(changes, command, account);
+                final MathContext mc = MathContext.DECIMAL64;
+                final boolean isPreMatureClosure = false;
+                account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure,
+                        isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+                this.savingAccountRepository.save(account);
+            }
+
+            boolean isLinkedAccRequired = command.booleanPrimitiveValueOfParameterNamed(transferInterestToSavingsParamName);
+
+            // Save linked account information
+            final Long savingsAccountId = command.longValueOfParameterNamed(DepositsApiConstants.linkedAccountParamName);
+            AccountAssociations accountAssociations = this.accountAssociationsRepository.findBySavingsIdAndType(accountId,
+                    AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
+            if (savingsAccountId == null) {
+                if (accountAssociations != null) {
+                    if (this.fromJsonHelper.parameterExists(DepositsApiConstants.linkedAccountParamName, command.parsedJson())) {
+                        this.accountAssociationsRepository.delete(accountAssociations);
+                        changes.put(DepositsApiConstants.linkedAccountParamName, null);
+                        if (isLinkedAccRequired) {
+                            this.depositAccountDataValidator.throwLinkedAccountRequiredError();
+                        }
+                    }
+                } else if (isLinkedAccRequired) {
+                    this.depositAccountDataValidator.throwLinkedAccountRequiredError();
+                }
+            } else {
+                boolean isModified = false;
+                if (accountAssociations == null) {
+                    isModified = true;
+                } else {
+                    final SavingsAccount savingsAccount = accountAssociations.linkedSavingsAccount();
+                    if (savingsAccount == null || savingsAccount.getId() != savingsAccountId) {
+                        isModified = true;
+                    }
+                }
+                if (isModified) {
+                    final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsAccountId,
+                            DepositAccountType.SAVINGS_DEPOSIT);
+                    this.depositAccountDataValidator.validatelinkedSavingsAccount(savingsAccount, account);
+                    if (accountAssociations == null) {
+                        boolean isActive = true;
+                        accountAssociations = AccountAssociations.associateSavingsAccount(account, savingsAccount,
+                                AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue(), isActive);
+                    } else {
+                        accountAssociations.updateLinkedSavingsAccount(savingsAccount);
+                    }
+                    changes.put(DepositsApiConstants.linkedAccountParamName, savingsAccountId);
+                    this.accountAssociationsRepository.save(accountAssociations);
+                }
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(accountId) //
+                    .withOfficeId(account.officeId()) //
+                    .withClientId(account.clientId()) //
+                    .withGroupId(account.groupId()) //
+                    .withSavingsId(accountId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataAccessException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return new CommandProcessingResult(Long.valueOf(-1));
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult modifyRDApplication(final Long accountId, final JsonCommand command) {
+        try {
+            this.depositAccountDataValidator.validateRecurringDepositForUpdate(command.json());
+
+            final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                    .isSavingsInterestPostingAtCurrentPeriodEnd();
+            final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+            final Map<String, Object> changes = new LinkedHashMap<>(20);
+
+            final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(accountId,
+                    DepositAccountType.RECURRING_DEPOSIT);
+            checkClientOrGroupActive(account);
+            account.modifyApplication(command, changes);
+            account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), DepositAccountType.RECURRING_DEPOSIT.resourceName());
+
+            if (!changes.isEmpty()) {
+                updateFDAndRDCommonChanges(changes, command, account);
+                final MathContext mc = MathContext.DECIMAL64;
+                final CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
+                        accountId, CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue());
+                final Calendar calendar = calendarInstance.getCalendar();
+                final PeriodFrequencyType frequencyType = CalendarFrequencyType.from(CalendarUtils.getFrequency(calendar.getRecurrence()));
+                Integer frequency = CalendarUtils.getInterval(calendar.getRecurrence());
+                frequency = frequency == -1 ? 1 : frequency;
+                account.generateSchedule(frequencyType, frequency, calendar);
+                final boolean isPreMatureClosure = false;
+                account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
+                        financialYearBeginningMonth);
+                account.validateApplicableInterestRate();
+                this.savingAccountRepository.save(account);
+
+            }
+
+            // update calendar details
+            if (!account.isCalendarInherited()) {
+                final LocalDate calendarStartDate = account.depositStartDate();
+                final Integer frequencyType = command.integerValueSansLocaleOfParameterNamed(recurringFrequencyTypeParamName);
+                final PeriodFrequencyType periodFrequencyType = PeriodFrequencyType.fromInt(frequencyType);
+                final Integer frequency = command.integerValueSansLocaleOfParameterNamed(recurringFrequencyParamName);
+                final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
+
+                CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
+                        accountId, CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue());
+                Calendar calendar = calendarInstance.getCalendar();
+                calendar.updateRepeatingCalendar(calendarStartDate, CalendarFrequencyType.from(periodFrequencyType), frequency,
+                        repeatsOnDay);
+                this.calendarInstanceRepository.save(calendarInstance);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(accountId) //
+                    .withOfficeId(account.officeId()) //
+                    .withClientId(account.clientId()) //
+                    .withGroupId(account.groupId()) //
+                    .withSavingsId(accountId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataAccessException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return new CommandProcessingResult(Long.valueOf(-1));
+        }
+    }
+
+    private void updateFDAndRDCommonChanges(final Map<String, Object> changes, final JsonCommand command, final SavingsAccount account) {
+
+        if (changes.containsKey(SavingsApiConstants.clientIdParamName)) {
+            final Long clientId = command.longValueOfParameterNamed(SavingsApiConstants.clientIdParamName);
+            if (clientId != null) {
+                final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+                if (client.isNotActive()) { throw new ClientNotActiveException(clientId); }
+                account.update(client);
+            } else {
+                final Client client = null;
+                account.update(client);
+            }
+        }
+
+        if (changes.containsKey(SavingsApiConstants.groupIdParamName)) {
+            final Long groupId = command.longValueOfParameterNamed(SavingsApiConstants.groupIdParamName);
+            if (groupId != null) {
+                final Group group = this.groupRepository.findOne(groupId);
+                if (group == null) { throw new GroupNotFoundException(groupId); }
+                if (group.isNotActive()) {
+                    if (group.isCenter()) { throw new CenterNotActiveException(groupId); }
+                    throw new GroupNotActiveException(groupId);
+                }
+                account.update(group);
+            } else {
+                final Group group = null;
+                account.update(group);
+            }
+        }
+
+        if (changes.containsKey(SavingsApiConstants.productIdParamName)) {
+            final Long productId = command.longValueOfParameterNamed(SavingsApiConstants.productIdParamName);
+            final SavingsProduct product = this.savingsProductRepository.findOne(productId);
+            if (product == null) { throw new SavingsProductNotFoundException(productId); }
+
+            account.update(product);
+        }
+
+        if (changes.containsKey(SavingsApiConstants.fieldOfficerIdParamName)) {
+            final Long fieldOfficerId = command.longValueOfParameterNamed(SavingsApiConstants.fieldOfficerIdParamName);
+            Staff fieldOfficer = null;
+            if (fieldOfficerId != null) {
+                fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId);
+            } else {
+                changes.put(SavingsApiConstants.fieldOfficerIdParamName, "");
+            }
+            account.update(fieldOfficer);
+        }
+
+        if (changes.containsKey("charges")) {
+            final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson(), account
+                    .getCurrency().getCode());
+            final boolean updated = account.update(charges);
+            if (!updated) {
+                changes.remove("charges");
+            }
+        }
+
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteApplication(final Long savingsId, final DepositAccountType depositAccountType) {
+
+        final SavingsAccount account = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType);
+        checkClientOrGroupActive(account);
+
+        if (account.isNotSubmittedAndPendingApproval()) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(depositAccountType
+                    .resourceName() + DepositsApiConstants.deleteApplicationAction);
+
+            baseDataValidator.reset().parameter(DepositsApiConstants.activatedOnDateParamName)
+                    .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final List<Note> relatedNotes = this.noteRepository.findBySavingsAccountId(savingsId);
+        this.noteRepository.deleteInBatch(relatedNotes);
+
+        this.savingAccountRepository.delete(account);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult approveApplication(final Long savingsId, final JsonCommand command,
+            final DepositAccountType depositAccountType) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        this.savingsAccountApplicationTransitionApiJsonValidator.validateApproval(command.json());
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Map<String, Object> changes = savingsAccount.approveApplication(currentUser, command, DateUtils.getLocalDateOfTenant());
+        if (!changes.isEmpty()) {
+            this.savingAccountRepository.save(savingsAccount);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.savingNote(savingsAccount, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(savingsId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult undoApplicationApproval(final Long savingsId, final JsonCommand command,
+            final DepositAccountType depositAccountType) {
+
+        this.context.authenticatedUser();
+
+        this.savingsAccountApplicationTransitionApiJsonValidator.validateForUndo(command.json());
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Map<String, Object> changes = savingsAccount.undoApplicationApproval();
+        if (!changes.isEmpty()) {
+            this.savingAccountRepository.save(savingsAccount);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.savingNote(savingsAccount, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(savingsId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult rejectApplication(final Long savingsId, final JsonCommand command,
+            final DepositAccountType depositAccountType) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        this.savingsAccountApplicationTransitionApiJsonValidator.validateRejection(command.json());
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Map<String, Object> changes = savingsAccount.rejectApplication(currentUser, command, DateUtils.getLocalDateOfTenant());
+        if (!changes.isEmpty()) {
+            this.savingAccountRepository.save(savingsAccount);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.savingNote(savingsAccount, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(savingsId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult applicantWithdrawsFromApplication(final Long savingsId, final JsonCommand command,
+            final DepositAccountType depositAccountType) {
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        this.savingsAccountApplicationTransitionApiJsonValidator.validateApplicantWithdrawal(command.json());
+
+        final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Map<String, Object> changes = savingsAccount.applicantWithdrawsFromApplication(currentUser, command,
+                DateUtils.getLocalDateOfTenant());
+        if (!changes.isEmpty()) {
+            this.savingAccountRepository.save(savingsAccount);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.savingNote(savingsAccount, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(savingsId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    private void checkClientOrGroupActive(final SavingsAccount account) {
+        final Client client = account.getClient();
+        if (client != null) {
+            if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); }
+        }
+        final Group group = account.group();
+        if (group != null) {
+            if (group.isNotActive()) {
+                if (group.isCenter()) { throw new CenterNotActiveException(group.getId()); }
+                throw new GroupNotActiveException(group.getId());
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformService.java
new file mode 100644
index 0000000..1d4ed50
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformService.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.savings.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.DepositProductData;
+
+public interface DepositProductReadPlatformService {
+
+    Collection<DepositProductData> retrieveAll(final DepositAccountType depositAccountType);
+
+    Collection<DepositProductData> retrieveAllForLookup(final DepositAccountType depositAccountType);
+
+    DepositProductData retrieveOne(final DepositAccountType depositAccountType, Long productId);
+
+    DepositProductData retrieveOneWithChartSlabs(final DepositAccountType depositAccountType, Long productId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java
new file mode 100644
index 0000000..70a65cf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java
@@ -0,0 +1,362 @@
+/**
+ * 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.savings.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+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.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.DepositProductData;
+import org.apache.fineract.portfolio.savings.data.FixedDepositProductData;
+import org.apache.fineract.portfolio.savings.data.RecurringDepositProductData;
+import org.apache.fineract.portfolio.savings.exception.FixedDepositProductNotFoundException;
+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 DepositProductReadPlatformServiceImpl implements DepositProductReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final InterestRateChartReadPlatformService chartReadPlatformService;
+    private final FixedDepositProductMapper fixedDepositProductRowMapper = new FixedDepositProductMapper();
+    private final RecurringDepositProductMapper recurringDepositProductRowMapper = new RecurringDepositProductMapper();
+    private final DepositProductLookupMapper depositProductLookupsRowMapper = new DepositProductLookupMapper();
+
+    @Autowired
+    public DepositProductReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final InterestRateChartReadPlatformService chartReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.chartReadPlatformService = chartReadPlatformService;
+    }
+
+    @Override
+    public Collection<DepositProductData> retrieveAll(final DepositAccountType depositAccountType) {
+
+        this.context.authenticatedUser();
+        final DepositProductMapper depositProductMapper = this.getDepositProductMapper(depositAccountType);
+        if (depositProductMapper == null) return null;
+
+        final StringBuilder sqlBuilder = new StringBuilder(400);
+        sqlBuilder.append("select ");
+        sqlBuilder.append(depositProductMapper.schema());
+        sqlBuilder.append(" where sp.deposit_type_enum = ? ");
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), depositProductMapper, new Object[] { depositAccountType.getValue() });
+    }
+
+    @Override
+    public Collection<DepositProductData> retrieveAllForLookup(final DepositAccountType depositAccountType) {
+
+        final StringBuilder sqlBuilder = new StringBuilder(400);
+        sqlBuilder.append("select ");
+        sqlBuilder.append(this.depositProductLookupsRowMapper.schema());
+        sqlBuilder.append(" where sp.deposit_type_enum = ? ");
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), this.depositProductLookupsRowMapper,
+                new Object[] { depositAccountType.getValue() });
+    }
+
+    @Override
+    public DepositProductData retrieveOne(final DepositAccountType depositAccountType, final Long fixedDepositProductId) {
+        try {
+            this.context.authenticatedUser();
+
+            final DepositProductMapper depositProductMapper = this.getDepositProductMapper(depositAccountType);
+            if (depositProductMapper == null) return null;
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("select ");
+            sqlBuilder.append(depositProductMapper.schema());
+            sqlBuilder.append(" where sp.id = ? and sp.deposit_type_enum = ? ");
+
+            return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositProductMapper, new Object[] { fixedDepositProductId,
+                    depositAccountType.getValue() });
+
+        } catch (final EmptyResultDataAccessException e) {
+            throw new FixedDepositProductNotFoundException(fixedDepositProductId);
+        }
+    }
+
+    @Override
+    public DepositProductData retrieveOneWithChartSlabs(final DepositAccountType depositAccountType, Long depositProductId) {
+        DepositProductData depositProduct = this.retrieveOne(depositAccountType, depositProductId);
+        Collection<InterestRateChartData> charts = this.chartReadPlatformService.retrieveAllWithSlabsWithTemplate(depositProductId);
+
+        if (depositAccountType.isFixedDeposit()) {
+            depositProduct = FixedDepositProductData.withInterestChart(depositProduct, charts);
+        } else if (depositAccountType.isRecurringDeposit()) {
+            depositProduct = RecurringDepositProductData.withInterestChart(depositProduct, charts);
+        }
+
+        return depositProduct;
+    }
+
+    private static abstract class DepositProductMapper implements RowMapper<DepositProductData> {
+
+        private final String schemaSql;
+
+        @Override
+        public abstract DepositProductData mapRow(ResultSet rs, int rowNum) throws SQLException;
+
+        public DepositProductMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("sp.id as id, sp.name as name, sp.short_name as shortName, sp.description as description, ");
+            sqlBuilder
+                    .append("sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, sp.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            sqlBuilder.append("sp.nominal_annual_interest_rate as nominalAnnualInterestRate, ");
+            sqlBuilder.append("sp.interest_compounding_period_enum as compoundingInterestPeriodType, ");
+            sqlBuilder.append("sp.interest_posting_period_enum as interestPostingPeriodType, ");
+            sqlBuilder.append("sp.interest_calculation_type_enum as interestCalculationType, ");
+            sqlBuilder.append("sp.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, ");
+            sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,");
+            sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
+            sqlBuilder.append("sp.accounting_type as accountingType, ");
+            sqlBuilder.append("sp.min_balance_for_interest_calculation as minBalanceForInterestCalculation ");
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        public DepositProductData mapRow(final ResultSet rs) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final String shortName = rs.getString("shortName");
+            final String description = rs.getString("description");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+            final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate");
+
+            final Integer compoundingInterestPeriodTypeValue = JdbcSupport.getInteger(rs, "compoundingInterestPeriodType");
+            final EnumOptionData compoundingInterestPeriodType = SavingsEnumerations
+                    .compoundingInterestPeriodType(compoundingInterestPeriodTypeValue);
+
+            final Integer interestPostingPeriodTypeValue = JdbcSupport.getInteger(rs, "interestPostingPeriodType");
+            final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(interestPostingPeriodTypeValue);
+
+            final Integer interestCalculationTypeValue = JdbcSupport.getInteger(rs, "interestCalculationType");
+            final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(interestCalculationTypeValue);
+
+            EnumOptionData interestCalculationDaysInYearType = null;
+            final Integer interestCalculationDaysInYearTypeValue = JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType");
+            if (interestCalculationDaysInYearTypeValue != null) {
+                interestCalculationDaysInYearType = SavingsEnumerations
+                        .interestCalculationDaysInYearType(interestCalculationDaysInYearTypeValue);
+            }
+
+            final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType");
+            final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId);
+
+            final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
+            EnumOptionData lockinPeriodFrequencyType = null;
+            final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
+            if (lockinPeriodFrequencyTypeValue != null) {
+                lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodFrequencyTypeValue);
+            }
+            final BigDecimal minBalanceForInterestCalculation = rs.getBigDecimal("minBalanceForInterestCalculation");
+
+            return DepositProductData.instance(id, name, shortName, description, currency, nominalAnnualInterestRate,
+                    compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                    lockinPeriodFrequency, lockinPeriodFrequencyType, accountingRuleType, minBalanceForInterestCalculation);
+        }
+    }
+
+    private static class FixedDepositProductMapper extends DepositProductMapper {
+
+        private final String schemaSql;
+
+        public FixedDepositProductMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append(super.schema());
+            sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, ");
+            sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, ");
+            sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, ");
+            sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, ");
+            sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, ");
+            sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, ");
+            sqlBuilder.append("dptp.max_deposit_term_type_enum as maxDepositTermTypeId, ");
+            sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, ");
+            sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, ");
+            sqlBuilder.append("dptp.min_deposit_amount as minDepositAmount, dptp.deposit_amount as depositAmount, ");
+            sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount ");
+            sqlBuilder.append("from m_savings_product sp ");
+            sqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sp.id=dptp.savings_product_id ");
+            sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public FixedDepositProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final DepositProductData depositProductData = super.mapRow(rs);
+
+            final boolean preClosurePenalApplicable = rs.getBoolean("preClosurePenalApplicable");
+            final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "preClosurePenalInterest");
+            final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, "preClosurePenalInterestOnId");
+            final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null : SavingsEnumerations
+                    .preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId);
+
+            final Integer minDepositTerm = JdbcSupport.getInteger(rs, "minDepositTerm");
+            final Integer maxDepositTerm = JdbcSupport.getInteger(rs, "maxDepositTerm");
+            final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, "minDepositTermTypeId");
+            final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(minDepositTermTypeId);
+            final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, "maxDepositTermTypeId");
+            final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(maxDepositTermTypeId);
+            final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTerm");
+            final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTermTypeId");
+            final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(inMultiplesOfDepositTermTypeId);
+            final BigDecimal minDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minDepositAmount");
+            final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "depositAmount");
+            final BigDecimal maxDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxDepositAmount");
+
+            return FixedDepositProductData.instance(depositProductData, preClosurePenalApplicable, preClosurePenalInterest,
+                    preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
+                    inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount, maxDepositAmount);
+        }
+    }
+
+    private static class RecurringDepositProductMapper extends DepositProductMapper {
+
+        private final String schemaSql;
+
+        public RecurringDepositProductMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append(super.schema());
+            sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, ");
+            sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, ");
+            sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, ");
+            sqlBuilder.append("dptp.min_deposit_amount as minDepositAmount, ");
+            sqlBuilder.append("dptp.deposit_amount as depositAmount, ");
+            sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount, ");
+            sqlBuilder.append("dprd.is_mandatory as isMandatoryDeposit, ");
+            sqlBuilder.append("dprd.allow_withdrawal as allowWithdrawal, ");
+            sqlBuilder.append("dprd.adjust_advance_towards_future_payments as adjustAdvanceTowardsFuturePayments, ");
+            sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, ");
+            sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, ");
+            sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, ");
+            sqlBuilder.append("dptp.max_deposit_term_type_enum as maxDepositTermTypeId, ");
+            sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, ");
+            sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId ");
+            sqlBuilder.append("from m_savings_product sp ");
+            sqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sp.id=dptp.savings_product_id ");
+            sqlBuilder.append("left join m_deposit_product_recurring_detail dprd on sp.id=dprd.savings_product_id ");
+            sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        @Override
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public RecurringDepositProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final DepositProductData depositProductData = super.mapRow(rs);
+
+            final boolean isMandatoryDeposit = rs.getBoolean("isMandatoryDeposit");
+            final boolean allowWithdrawal = rs.getBoolean("allowWithdrawal");
+            final boolean adjustAdvanceTowardsFuturePayments = rs.getBoolean("adjustAdvanceTowardsFuturePayments");
+            final boolean preClosurePenalApplicable = rs.getBoolean("preClosurePenalApplicable");
+            final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "preClosurePenalInterest");
+            final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, "preClosurePenalInterestOnId");
+            final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null : SavingsEnumerations
+                    .preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId);
+            final BigDecimal minDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minDepositAmount");
+            final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "depositAmount");
+            final BigDecimal maxDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxDepositAmount");
+            final Integer minDepositTerm = JdbcSupport.getInteger(rs, "minDepositTerm");
+            final Integer maxDepositTerm = JdbcSupport.getInteger(rs, "maxDepositTerm");
+            final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, "minDepositTermTypeId");
+            final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(minDepositTermTypeId);
+            final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, "maxDepositTermTypeId");
+            final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(maxDepositTermTypeId);
+            final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTerm");
+            final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, "inMultiplesOfDepositTermTypeId");
+            final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null : SavingsEnumerations
+                    .depositTermFrequencyType(inMultiplesOfDepositTermTypeId);
+
+            return RecurringDepositProductData.instance(depositProductData, preClosurePenalApplicable, preClosurePenalInterest,
+                    preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
+                    inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, isMandatoryDeposit, allowWithdrawal,
+                    adjustAdvanceTowardsFuturePayments, minDepositAmount, depositAmount, maxDepositAmount);
+        }
+    }
+
+    private static final class DepositProductLookupMapper implements RowMapper<DepositProductData> {
+
+        public String schema() {
+            return " sp.id as id, sp.name as name from m_savings_product sp ";
+        }
+
+        @Override
+        public DepositProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+
+            return DepositProductData.lookup(id, name);
+        }
+    }
+
+    private DepositProductMapper getDepositProductMapper(final DepositAccountType depositAccountType) {
+        if (depositAccountType.isFixedDeposit()) {
+            return this.fixedDepositProductRowMapper;
+        } else if (depositAccountType.isRecurringDeposit()) { return this.recurringDepositProductRowMapper; }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositsDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositsDropdownReadPlatformService.java
new file mode 100644
index 0000000..0696452
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositsDropdownReadPlatformService.java
@@ -0,0 +1,39 @@
+/**
+ * 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.savings.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public interface DepositsDropdownReadPlatformService {
+
+    // fixed deposit product dropdowns
+
+    Collection<EnumOptionData> retrievePreClosurePenalInterestOnTypeOptions();
+
+    /*
+     * Collection<EnumOptionData> retrieveDepositTermTypeOptions();
+     * 
+     * Collection<EnumOptionData> retrieveInMultiplesOfDepositTermTypeOptions();
+     * 
+     * Collection<EnumOptionData> retrieveDepositPeriodFrequencyOptions();
+     */
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositsDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositsDropdownReadPlatformServiceImpl.java
new file mode 100644
index 0000000..846e004
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositsDropdownReadPlatformServiceImpl.java
@@ -0,0 +1,52 @@
+/**
+ * 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.savings.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DepositsDropdownReadPlatformServiceImpl implements DepositsDropdownReadPlatformService {
+
+    @Override
+    public Collection<EnumOptionData> retrievePreClosurePenalInterestOnTypeOptions() {
+        return SavingsEnumerations.preClosurePenaltyInterestOnType(PreClosurePenalInterestOnType.values());
+    }
+
+    /*
+     * @Override public Collection<EnumOptionData>
+     * retrieveDepositTermTypeOptions() { return
+     * SavingsEnumerations.recurringDepositFrequencyType
+     * (SavingsPeriodFrequencyType.values()); }
+     * 
+     * @Override public Collection<EnumOptionData>
+     * retrieveDepositPeriodFrequencyOptions() { return
+     * SavingsEnumerations.depositPeriodFrequency
+     * (SavingsPeriodFrequencyType.values()); }
+     * 
+     * @Override public Collection<EnumOptionData>
+     * retrieveInMultiplesOfDepositTermTypeOptions() { return
+     * SavingsEnumerations
+     * .recurringDepositFrequencyType(SavingsPeriodFrequencyType.values()); }
+     */
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformService.java
new file mode 100644
index 0000000..36f4614
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface FixedDepositProductWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    CommandProcessingResult update(Long productId, JsonCommand command);
+
+    CommandProcessingResult delete(Long productId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..02c7db7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,183 @@
+/**
+ * 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.savings.service;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.DepositProductDataValidator;
+import org.apache.fineract.portfolio.savings.domain.DepositProductAssembler;
+import org.apache.fineract.portfolio.savings.domain.FixedDepositProduct;
+import org.apache.fineract.portfolio.savings.domain.FixedDepositProductRepository;
+import org.apache.fineract.portfolio.savings.exception.FixedDepositProductNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class FixedDepositProductWritePlatformServiceJpaRepositoryImpl implements FixedDepositProductWritePlatformService {
+
+    private final Logger logger;
+    private final PlatformSecurityContext context;
+    private final FixedDepositProductRepository fixedDepositProductRepository;
+    private final DepositProductDataValidator fromApiJsonDataValidator;
+    private final DepositProductAssembler depositProductAssembler;
+    private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
+    private final InterestRateChartAssembler chartAssembler;
+
+    @Autowired
+    public FixedDepositProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final FixedDepositProductRepository fixedDepositProductRepository, final DepositProductDataValidator fromApiJsonDataValidator,
+            final DepositProductAssembler depositProductAssembler,
+            final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
+            final InterestRateChartAssembler chartAssembler) {
+        this.context = context;
+        this.fixedDepositProductRepository = fixedDepositProductRepository;
+        this.fromApiJsonDataValidator = fromApiJsonDataValidator;
+        this.depositProductAssembler = depositProductAssembler;
+        this.logger = LoggerFactory.getLogger(FixedDepositProductWritePlatformServiceJpaRepositoryImpl.class);
+        this.accountMappingWritePlatformService = accountMappingWritePlatformService;
+        this.chartAssembler = chartAssembler;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult create(final JsonCommand command) {
+
+        try {
+            this.fromApiJsonDataValidator.validateForFixedDepositCreate(command.json());
+
+            final FixedDepositProduct product = this.depositProductAssembler.assembleFixedDepositProduct(command);
+
+            this.fixedDepositProductRepository.save(product);
+
+            // save accounting mappings
+            this.accountMappingWritePlatformService.createSavingProductToGLAccountMapping(product.getId(), command,
+                    DepositAccountType.FIXED_DEPOSIT);
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(product.getId()) //
+                    .build();
+        } catch (final DataAccessException e) {
+            handleDataIntegrityIssues(command, e);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult update(final Long productId, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDataValidator.validateForFixedDepositUpdate(command.json());
+
+            final FixedDepositProduct product = this.fixedDepositProductRepository.findOne(productId);
+            if (product == null) { throw new FixedDepositProductNotFoundException(productId); }
+            product.setHelpers(this.chartAssembler);
+
+            final Map<String, Object> changes = product.update(command);
+
+            if (changes.containsKey(chargesParamName)) {
+                final Set<Charge> savingsProductCharges = this.depositProductAssembler.assembleListOfSavingsProductCharges(command, product
+                        .currency().getCode());
+                final boolean updated = product.update(savingsProductCharges);
+                if (!updated) {
+                    changes.remove(chargesParamName);
+                }
+            }
+
+            // accounting related changes
+            final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName);
+            final Map<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
+                    .updateSavingsProductToGLAccountMapping(product.getId(), command, accountingTypeChanged, product.getAccountingType(),
+                            DepositAccountType.FIXED_DEPOSIT);
+            changes.putAll(accountingMappingChanges);
+
+            if (!changes.isEmpty()) {
+                this.fixedDepositProductRepository.save(product);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(product.getId()) //
+                    .with(changes).build();
+        } catch (final DataAccessException e) {
+            handleDataIntegrityIssues(command, e);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult delete(final Long productId) {
+
+        this.context.authenticatedUser();
+        final FixedDepositProduct product = this.fixedDepositProductRepository.findOne(productId);
+        if (product == null) { throw new FixedDepositProductNotFoundException(productId); }
+
+        this.fixedDepositProductRepository.delete(product);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(product.getId()) //
+                .build();
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataAccessException dae) {
+
+        final Throwable realCause = dae.getMostSpecificCause();
+        if (realCause.getMessage().contains("sp_unq_name")) {
+
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.product.savings.duplicate.name", "Savings product with name `" + name
+                    + "` already exists", "name", name);
+        } else if (realCause.getMessage().contains("sp_unq_short_name")) {
+
+            final String shortName = command.stringValueOfParameterNamed("shortName");
+            throw new PlatformDataIntegrityException("error.msg.product.savings.duplicate.short.name", "Savings product with short name `"
+                    + shortName + "` already exists", "shortName", shortName);
+        }
+
+        logAsErrorUnexpectedDataIntegrityException(dae);
+        throw new PlatformDataIntegrityException("error.msg.savingsproduct.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataAccessException dae) {
+        this.logger.error(dae.getMessage(), dae);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformService.java
new file mode 100644
index 0000000..343caae
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface RecurringDepositProductWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    CommandProcessingResult update(Long productId, JsonCommand command);
+
+    CommandProcessingResult delete(Long productId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..2ec93c6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,183 @@
+/**
+ * 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.savings.service;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.DepositProductDataValidator;
+import org.apache.fineract.portfolio.savings.domain.DepositProductAssembler;
+import org.apache.fineract.portfolio.savings.domain.RecurringDepositProduct;
+import org.apache.fineract.portfolio.savings.domain.RecurringDepositProductRepository;
+import org.apache.fineract.portfolio.savings.exception.RecurringDepositProductNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class RecurringDepositProductWritePlatformServiceJpaRepositoryImpl implements RecurringDepositProductWritePlatformService {
+
+    private final Logger logger;
+    private final PlatformSecurityContext context;
+    private final RecurringDepositProductRepository recurringDepositProductRepository;
+    private final DepositProductDataValidator fromApiJsonDataValidator;
+    private final DepositProductAssembler depositProductAssembler;
+    private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
+    private final InterestRateChartAssembler chartAssembler;
+
+    @Autowired
+    public RecurringDepositProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final RecurringDepositProductRepository recurringDepositProductRepository,
+            final DepositProductDataValidator fromApiJsonDataValidator, final DepositProductAssembler depositProductAssembler,
+            final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
+            final InterestRateChartAssembler chartAssembler) {
+        this.context = context;
+        this.recurringDepositProductRepository = recurringDepositProductRepository;
+        this.fromApiJsonDataValidator = fromApiJsonDataValidator;
+        this.depositProductAssembler = depositProductAssembler;
+        this.logger = LoggerFactory.getLogger(RecurringDepositProductWritePlatformServiceJpaRepositoryImpl.class);
+        this.accountMappingWritePlatformService = accountMappingWritePlatformService;
+        this.chartAssembler = chartAssembler;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult create(final JsonCommand command) {
+
+        try {
+            this.fromApiJsonDataValidator.validateForRecurringDepositCreate(command.json());
+
+            final RecurringDepositProduct product = this.depositProductAssembler.assembleRecurringDepositProduct(command);
+
+            this.recurringDepositProductRepository.save(product);
+
+            // save accounting mappings
+            this.accountMappingWritePlatformService.createSavingProductToGLAccountMapping(product.getId(), command,
+                    DepositAccountType.RECURRING_DEPOSIT);
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(product.getId()) //
+                    .build();
+        } catch (final DataAccessException e) {
+            handleDataIntegrityIssues(command, e);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult update(final Long productId, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDataValidator.validateForRecurringDepositUpdate(command.json());
+
+            final RecurringDepositProduct product = this.recurringDepositProductRepository.findOne(productId);
+            if (product == null) { throw new RecurringDepositProductNotFoundException(productId); }
+            product.setHelpers(this.chartAssembler);
+
+            final Map<String, Object> changes = product.update(command);
+
+            if (changes.containsKey(chargesParamName)) {
+                final Set<Charge> savingsProductCharges = this.depositProductAssembler.assembleListOfSavingsProductCharges(command, product
+                        .currency().getCode());
+                final boolean updated = product.update(savingsProductCharges);
+                if (!updated) {
+                    changes.remove(chargesParamName);
+                }
+            }
+
+            // accounting related changes
+            final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName);
+            final Map<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
+                    .updateSavingsProductToGLAccountMapping(product.getId(), command, accountingTypeChanged, product.getAccountingType(),
+                            DepositAccountType.RECURRING_DEPOSIT);
+            changes.putAll(accountingMappingChanges);
+
+            if (!changes.isEmpty()) {
+                this.recurringDepositProductRepository.save(product);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(product.getId()) //
+                    .with(changes).build();
+        } catch (final DataAccessException e) {
+            handleDataIntegrityIssues(command, e);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult delete(final Long productId) {
+
+        this.context.authenticatedUser();
+        final RecurringDepositProduct product = this.recurringDepositProductRepository.findOne(productId);
+        if (product == null) { throw new RecurringDepositProductNotFoundException(productId); }
+
+        this.recurringDepositProductRepository.delete(product);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(product.getId()) //
+                .build();
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataAccessException dae) {
+
+        final Throwable realCause = dae.getMostSpecificCause();
+        if (realCause.getMessage().contains("sp_unq_name")) {
+
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.product.savings.duplicate.name", "Recurring Deposit product with name `"
+                    + name + "` already exists", "name", name);
+        } else if (realCause.getMessage().contains("sp_unq_short_name")) {
+
+            final String shortName = command.stringValueOfParameterNamed("shortName");
+            throw new PlatformDataIntegrityException("error.msg.product.savings.duplicate.short.name",
+                    "Recurring Deposit product with short name `" + shortName + "` already exists", "shortName", shortName);
+        }
+
+        logAsErrorUnexpectedDataIntegrityException(dae);
+        throw new PlatformDataIntegrityException("error.msg.savingsproduct.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataAccessException dae) {
+        this.logger.error(dae.getMessage(), dae);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountApplicationTransitionApiJsonValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountApplicationTransitionApiJsonValidator.java
new file mode 100644
index 0000000..1049ab1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountApplicationTransitionApiJsonValidator.java
@@ -0,0 +1,142 @@
+/**
+ * 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.savings.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.joda.time.LocalDate;
+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 SavingsAccountApplicationTransitionApiJsonValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public SavingsAccountApplicationTransitionApiJsonValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateApproval(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("approvedOnDate", "note", "locale", "dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("savingsaccountapplication");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate approvedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("approvedOnDate", element);
+        baseDataValidator.reset().parameter("approvedOnDate").value(approvedOnDate).notNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+        baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateRejection(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("rejectedOnDate", "note", "locale", "dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("savingsaccountapplication");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate rejectedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("rejectedOnDate", element);
+        baseDataValidator.reset().parameter("rejectedOnDate").value(rejectedOnDate).notNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+        baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateApplicantWithdrawal(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("withdrawnOnDate", "note", "locale", "dateFormat"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("savingsaccountapplication");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final LocalDate withdrawnOnDate = this.fromApiJsonHelper.extractLocalDateNamed("withdrawnOnDate", element);
+        baseDataValidator.reset().parameter("withdrawnOnDate").value(withdrawnOnDate).notNull();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
+        baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUndo(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Set<String> undoSupportedParameters = new HashSet<>(Arrays.asList("note"));
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource("savingsaccountapplication.undo");
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String note = "note";
+        if (this.fromApiJsonHelper.parameterExists(note, element)) {
+            final String noteText = this.fromApiJsonHelper.extractStringNamed(note, element);
+            baseDataValidator.reset().parameter(note).value(noteText).notExceedingLengthOf(1000);
+        }
+
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java
new file mode 100644
index 0000000..498e44b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.savings.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountAnnualFeeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+
+public interface SavingsAccountChargeReadPlatformService {
+
+    ChargeData retrieveSavingsAccountChargeTemplate();
+
+    Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(Long savingsAccountId, String status);
+
+    SavingsAccountChargeData retrieveSavingsAccountChargeDetails(Long savingsAccountChargeId, Long savingsAccountId);
+
+    Collection<SavingsAccountAnnualFeeData> retrieveChargesWithAnnualFeeDue();
+
+    Collection<SavingsAccountAnnualFeeData> retrieveChargesWithDue();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java
new file mode 100644
index 0000000..baa3058
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java
@@ -0,0 +1,252 @@
+/**
+ * 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.savings.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+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.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
+import org.apache.fineract.portfolio.charge.service.ChargeDropdownReadPlatformService;
+import org.apache.fineract.portfolio.charge.service.ChargeEnumerations;
+import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountAnnualFeeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+import org.joda.time.LocalDate;
+import org.joda.time.MonthDay;
+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 SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccountChargeReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService;
+    private final DropdownReadPlatformService dropdownReadPlatformService;
+
+    // mappers
+    private final SavingsAccountChargeDueMapper chargeDueMapper;
+
+    @Autowired
+    public SavingsAccountChargeReadPlatformServiceImpl(final PlatformSecurityContext context,
+            final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource,
+            final DropdownReadPlatformService dropdownReadPlatformService) {
+        this.context = context;
+        this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.chargeDueMapper = new SavingsAccountChargeDueMapper();
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+    }
+
+    private static final class SavingsAccountChargeMapper implements RowMapper<SavingsAccountChargeData> {
+
+        public String schema() {
+            return "sc.id as id, c.id as chargeId, sc.savings_account_id as accountId, c.name as name, " + "sc.amount as amountDue, "
+                    + "sc.amount_paid_derived as amountPaid, " + "sc.amount_waived_derived as amountWaived, "
+                    + "sc.amount_writtenoff_derived as amountWrittenOff, " + "sc.amount_outstanding_derived as amountOutstanding, "
+                    + "sc.calculation_percentage as percentageOf, sc.calculation_on_amount as amountPercentageAppliedTo, "
+                    + "sc.charge_time_enum as chargeTime, " + "sc.is_penalty as penalty, " + "sc.charge_due_date as dueAsOfDate, "
+                    + "sc.fee_on_month as feeOnMonth, " + "sc.fee_on_day as feeOnDay, sc.fee_interval as feeInterval, "
+                    + "sc.charge_calculation_enum as chargeCalculation, "
+                    + "sc.is_active as isActive, sc.inactivated_on_date as inactivationDate, "
+                    + "c.currency_code as currencyCode, oc.name as currencyName, "
+                    + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+                    + "oc.internationalized_name_code as currencyNameCode from m_charge c "
+                    + "join m_organisation_currency oc on c.currency_code = oc.code "
+                    + "join m_savings_account_charge sc on sc.charge_id = c.id ";
+        }
+
+        @Override
+        public SavingsAccountChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long chargeId = rs.getLong("chargeId");
+            final Long accountId = rs.getLong("accountId");
+            final String name = rs.getString("name");
+            final BigDecimal amount = rs.getBigDecimal("amountDue");
+            final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPaid");
+            final BigDecimal amountWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWaived");
+            final BigDecimal amountWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWrittenOff");
+            final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
+
+            final BigDecimal percentageOf = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "percentageOf");
+            final BigDecimal amountPercentageAppliedTo = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPercentageAppliedTo");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final int chargeTime = rs.getInt("chargeTime");
+            final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
+
+            final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
+            final Integer feeInterval = JdbcSupport.getInteger(rs, "feeInterval");
+            MonthDay feeOnMonthDay = null;
+            final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth");
+            final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay");
+            if (feeOnDay != null && feeOnMonth != null) {
+                feeOnMonthDay = new MonthDay(feeOnMonth, feeOnDay);
+            }
+
+            final int chargeCalculation = rs.getInt("chargeCalculation");
+            final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
+            final boolean penalty = rs.getBoolean("penalty");
+            final Boolean isActive = rs.getBoolean("isActive");
+            final LocalDate inactivationDate = JdbcSupport.getLocalDate(rs, "inactivationDate");
+
+            final Collection<ChargeData> chargeOptions = null;
+
+            return SavingsAccountChargeData.instance(id, chargeId, accountId, name, currency, amount, amountPaid, amountWaived,
+                    amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf,
+                    amountPercentageAppliedTo, chargeOptions, penalty, feeOnMonthDay, feeInterval, isActive, inactivationDate);
+        }
+    }
+
+    @Override
+    public ChargeData retrieveSavingsAccountChargeTemplate() {
+        this.context.authenticatedUser();
+
+        final List<EnumOptionData> allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes();
+        final List<EnumOptionData> allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes();
+        final List<EnumOptionData> loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveLoanCalculationTypes();
+        final List<EnumOptionData> loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes();
+        final List<EnumOptionData> savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveSavingsCalculationTypes();
+        final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
+                .retrieveSavingsCollectionTimeTypes();
+        final List<EnumOptionData> clientChargeCalculationTypeOptions = null;
+        final List<EnumOptionData> clientChargeTimeTypeOptions = null;
+
+        final List<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
+        // this field is applicable only for client charges
+        final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
+
+        // TODO AA : revisit for merge conflict - Not sure method signature
+        return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null,
+                loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
+                savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions,
+                incomeOrLiabilityAccountOptions);
+    }
+
+    @Override
+    public SavingsAccountChargeData retrieveSavingsAccountChargeDetails(final Long id, final Long savingsAccountId) {
+        try {
+            this.context.authenticatedUser();
+
+            final SavingsAccountChargeMapper rm = new SavingsAccountChargeMapper();
+
+            final String sql = "select " + rm.schema() + " where sc.id=? and sc.savings_account_id=?";
+
+            return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { id, savingsAccountId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new SavingsAccountChargeNotFoundException(savingsAccountId);
+        }
+    }
+
+    @Override
+    public Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(final Long loanId, final String status) {
+        this.context.authenticatedUser();
+
+        final SavingsAccountChargeMapper rm = new SavingsAccountChargeMapper();
+        final StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append("select ").append(rm.schema()).append(" where sc.savings_account_id=? ");
+        if (status.equalsIgnoreCase("active")) {
+            sqlBuilder.append(" and sc.is_active = 1 ");
+        } else if (status.equalsIgnoreCase("inactive")) {
+            sqlBuilder.append(" and sc.is_active = 0 ");
+        }
+        sqlBuilder.append(" order by sc.charge_time_enum ASC, sc.charge_due_date ASC, sc.is_penalty ASC");
+
+        return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { loanId });
+    }
+
+    private static final class SavingsAccountChargeDueMapper implements RowMapper<SavingsAccountAnnualFeeData> {
+
+        private final String schemaSql;
+
+        public SavingsAccountChargeDueMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(200);
+            sqlBuilder.append("sac.id as id, ");
+            sqlBuilder.append("sa.id as accountId, ");
+            sqlBuilder.append("sa.account_no as accountNo, ");
+            sqlBuilder.append("sac.charge_due_date as dueDate ");
+            sqlBuilder.append("from m_savings_account_charge sac join m_savings_account sa on sac.savings_account_id = sa.id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountAnnualFeeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final Long accountId = rs.getLong("accountId");
+            final String accountNo = rs.getString("accountNo");
+            final LocalDate annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "dueDate");
+
+            return SavingsAccountAnnualFeeData.instance(id, accountId, accountNo, annualFeeNextDueDate);
+        }
+    }
+
+    @Override
+    public Collection<SavingsAccountAnnualFeeData> retrieveChargesWithAnnualFeeDue() {
+        final String sql = "select " + this.chargeDueMapper.schema() + " where sac.charge_due_date is not null and sac.charge_time_enum = "
+                + ChargeTimeType.ANNUAL_FEE.getValue() + " and sac.charge_due_date <= NOW() and sa.status_enum = "
+                + SavingsAccountStatusType.ACTIVE.getValue();
+
+        return this.jdbcTemplate.query(sql, this.chargeDueMapper, new Object[] {});
+    }
+
+    @Override
+    public Collection<SavingsAccountAnnualFeeData> retrieveChargesWithDue() {
+        final String sql = "select " + this.chargeDueMapper.schema()
+                + " where sac.charge_due_date is not null and sac.charge_due_date <= NOW() and sac.waived = 0 and sac.is_paid_derived=0 and sac.is_active=1 and sa.status_enum = "
+                + SavingsAccountStatusType.ACTIVE.getValue() + " order by sac.charge_due_date ";
+
+        return this.jdbcTemplate.query(sql, this.chargeDueMapper, new Object[] {});
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
new file mode 100644
index 0000000..bcc165c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
@@ -0,0 +1,51 @@
+/**
+ * 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.savings.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+
+public interface SavingsAccountReadPlatformService {
+
+    Page<SavingsAccountData> retrieveAll(SearchParameters searchParameters);
+
+    Collection<SavingsAccountData> retrieveAllForLookup(Long clientId);
+
+    Collection<SavingsAccountData> retrieveActiveForLookup(Long clientId, DepositAccountType depositAccountType);
+
+    SavingsAccountData retrieveOne(Long savingsId);
+
+    SavingsAccountData retrieveTemplate(Long clientId, Long groupId, Long productId, boolean staffInSelectedOfficeOnly);
+
+    SavingsAccountTransactionData retrieveDepositTransactionTemplate(Long savingsId, DepositAccountType depositAccountType);
+
+    Collection<SavingsAccountTransactionData> retrieveAllTransactions(Long savingsId, DepositAccountType depositAccountType);
+
+    // Collection<SavingsAccountAnnualFeeData>
+    // retrieveAccountsWithAnnualFeeDue();
+
+    SavingsAccountTransactionData retrieveSavingsTransaction(Long savingsId, Long transactionId, DepositAccountType depositAccountType);
+
+    Collection<SavingsAccountData> retrieveForLookup(Long clientId, Boolean overdraft);
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..1e76d93
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -0,0 +1,1013 @@
+/**
+ * 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.savings.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final GroupReadPlatformService groupReadPlatformService;
+    private final SavingsProductReadPlatformService savingsProductReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final SavingsDropdownReadPlatformService dropdownReadPlatformService;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+
+    // mappers
+    private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper;
+    private final SavingsAccountTransactionsMapper transactionsMapper;
+    private final SavingAccountMapper savingAccountMapper;
+    // private final SavingsAccountAnnualFeeMapper annualFeeMapper;
+
+    // pagination
+    private final PaginationHelper<SavingsAccountData> paginationHelper = new PaginationHelper<>();
+
+    @Autowired
+    public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final ClientReadPlatformService clientReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
+            final SavingsProductReadPlatformService savingProductReadPlatformService,
+            final StaffReadPlatformService staffReadPlatformService, final SavingsDropdownReadPlatformService dropdownReadPlatformService,
+            final ChargeReadPlatformService chargeReadPlatformService) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.groupReadPlatformService = groupReadPlatformService;
+        this.savingsProductReadPlatformService = savingProductReadPlatformService;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper();
+        this.transactionsMapper = new SavingsAccountTransactionsMapper();
+        this.savingAccountMapper = new SavingAccountMapper();
+        // this.annualFeeMapper = new SavingsAccountAnnualFeeMapper();
+        this.chargeReadPlatformService = chargeReadPlatformService;
+    }
+
+    @Override
+    public Collection<SavingsAccountData> retrieveAllForLookup(final Long clientId) {
+
+        final StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema());
+        sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300 ");
+
+        final Object[] queryParameters = new Object[] { clientId };
+        return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters);
+    }
+
+    @Override
+    public Collection<SavingsAccountData> retrieveActiveForLookup(final Long clientId, DepositAccountType depositAccountType) {
+
+        final StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema());
+        sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300 and sa.deposit_type_enum = ? ");
+
+        final Object[] queryParameters = new Object[] { clientId, depositAccountType.getValue() };
+        return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters);
+    }
+
+    @Override
+    public Page<SavingsAccountData> retrieveAll(final SearchParameters searchParameters) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.savingAccountMapper.schema());
+
+        sqlBuilder.append(" join m_office o on o.id = c.office_id");
+        sqlBuilder.append(" where o.hierarchy like ?");
+
+        final Object[] objectArray = new Object[2];
+        objectArray[0] = hierarchySearchString;
+        int arrayPos = 1;
+
+        String sqlQueryCriteria = searchParameters.getSqlSearch();
+        if (StringUtils.isNotBlank(sqlQueryCriteria)) {
+            sqlQueryCriteria = sqlQueryCriteria.replaceAll("accountNo", "sa.account_no");
+            sqlBuilder.append(" and (").append(sqlQueryCriteria).append(")");
+        }
+
+        if (StringUtils.isNotBlank(searchParameters.getExternalId())) {
+            sqlBuilder.append(" and sa.external_id = ?");
+            objectArray[arrayPos] = searchParameters.getExternalId();
+            arrayPos = arrayPos + 1;
+        }
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+
+        final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
+                this.savingAccountMapper);
+    }
+
+    @Override
+    public SavingsAccountData retrieveOne(final Long accountId) {
+
+        try {
+            final String sql = "select " + this.savingAccountMapper.schema() + " where sa.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, new Object[] { accountId });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new SavingsAccountNotFoundException(accountId);
+        }
+    }
+
+    private static final class SavingAccountMapper implements RowMapper<SavingsAccountData> {
+
+        private final String schemaSql;
+
+        public SavingAccountMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, ");
+            sqlBuilder.append("sa.deposit_type_enum as depositType, ");
+            sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
+            sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
+            sqlBuilder.append("sp.id as productId, sp.name as productName, ");
+            sqlBuilder.append("s.id fieldOfficerId, s.display_name as fieldOfficerName, ");
+            sqlBuilder.append("sa.status_enum as statusEnum, ");
+
+            sqlBuilder.append("sa.submittedon_date as submittedOnDate,");
+            sqlBuilder.append("sbu.username as submittedByUsername,");
+            sqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,");
+
+            sqlBuilder.append("sa.rejectedon_date as rejectedOnDate,");
+            sqlBuilder.append("rbu.username as rejectedByUsername,");
+            sqlBuilder.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,");
+
+            sqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,");
+            sqlBuilder.append("wbu.username as withdrawnByUsername,");
+            sqlBuilder.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,");
+
+            sqlBuilder.append("sa.approvedon_date as approvedOnDate,");
+            sqlBuilder.append("abu.username as approvedByUsername,");
+            sqlBuilder.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,");
+
+            sqlBuilder.append("sa.activatedon_date as activatedOnDate,");
+            sqlBuilder.append("avbu.username as activatedByUsername,");
+            sqlBuilder.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,");
+
+            sqlBuilder.append("sa.closedon_date as closedOnDate,");
+            sqlBuilder.append("cbu.username as closedByUsername,");
+            sqlBuilder.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,");
+
+            sqlBuilder
+                    .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+
+            sqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, ");
+            sqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, ");
+            sqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, ");
+            sqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, ");
+            sqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, ");
+            sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, ");
+            sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,");
+            sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
+            // sqlBuilder.append("sa.withdrawal_fee_amount as withdrawalFeeAmount,");
+            // sqlBuilder.append("sa.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, ");
+            sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
+            sqlBuilder.append("sa.allow_overdraft as allowOverdraft, ");
+            sqlBuilder.append("sa.overdraft_limit as overdraftLimit, ");
+            sqlBuilder.append("sa.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, ");
+            sqlBuilder.append("sa.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, ");
+            // sqlBuilder.append("sa.annual_fee_amount as annualFeeAmount,");
+            // sqlBuilder.append("sa.annual_fee_on_month as annualFeeOnMonth, ");
+            // sqlBuilder.append("sa.annual_fee_on_day as annualFeeOnDay, ");
+            // sqlBuilder.append("sa.annual_fee_next_due_date as annualFeeNextDueDate, ");
+            sqlBuilder.append("sa.total_deposits_derived as totalDeposits, ");
+            sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, ");
+            sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, ");
+            sqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, ");
+            sqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, ");
+            sqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, ");
+            sqlBuilder.append("sa.total_overdraft_interest_derived as totalOverdraftInterestDerived, ");
+            sqlBuilder.append("sa.account_balance_derived as accountBalance, ");
+            sqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, ");
+            sqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, ");
+            sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,");
+            sqlBuilder.append("sa.min_required_balance as minRequiredBalance, ");
+            sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, ");
+            sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds ");
+            sqlBuilder.append("from m_savings_account sa ");
+            sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
+            sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
+            sqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
+            sqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
+            sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id ");
+            sqlBuilder.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid ");
+            sqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid ");
+            sqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid ");
+            sqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid ");
+            sqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid ");
+            sqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final String externalId = rs.getString("externalId");
+            final Integer depositTypeId = rs.getInt("depositType");
+            final EnumOptionData depositType = SavingsEnumerations.depositType(depositTypeId);
+
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final String groupName = rs.getString("groupName");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final String clientName = rs.getString("clientName");
+
+            final Long productId = rs.getLong("productId");
+            final String productName = rs.getString("productName");
+
+            final Long fieldOfficerId = rs.getLong("fieldOfficerId");
+            final String fieldOfficerName = rs.getString("fieldOfficerName");
+
+            final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum);
+
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final String submittedByUsername = rs.getString("submittedByUsername");
+            final String submittedByFirstname = rs.getString("submittedByFirstname");
+            final String submittedByLastname = rs.getString("submittedByLastname");
+
+            final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate");
+            final String rejectedByUsername = rs.getString("rejectedByUsername");
+            final String rejectedByFirstname = rs.getString("rejectedByFirstname");
+            final String rejectedByLastname = rs.getString("rejectedByLastname");
+
+            final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate");
+            final String withdrawnByUsername = rs.getString("withdrawnByUsername");
+            final String withdrawnByFirstname = rs.getString("withdrawnByFirstname");
+            final String withdrawnByLastname = rs.getString("withdrawnByLastname");
+
+            final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
+            final String approvedByUsername = rs.getString("approvedByUsername");
+            final String approvedByFirstname = rs.getString("approvedByFirstname");
+            final String approvedByLastname = rs.getString("approvedByLastname");
+
+            final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate");
+            final String activatedByUsername = rs.getString("activatedByUsername");
+            final String activatedByFirstname = rs.getString("activatedByFirstname");
+            final String activatedByLastname = rs.getString("activatedByLastname");
+
+            final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
+            final String closedByUsername = rs.getString("closedByUsername");
+            final String closedByFirstname = rs.getString("closedByFirstname");
+            final String closedByLastname = rs.getString("closedByLastname");
+
+            final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate,
+                    submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername,
+                    rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname,
+                    withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate,
+                    activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname,
+                    closedByLastname);
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate");
+
+            final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations
+                    .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs,
+                            "interestCompoundingPeriodType")));
+
+            final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType
+                    .fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType")));
+
+            final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType
+                    .fromInt(JdbcSupport.getInteger(rs, "interestCalculationType")));
+
+            final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations
+                    .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs,
+                            "interestCalculationDaysInYearType")));
+
+            final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance");
+
+            final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
+            EnumOptionData lockinPeriodFrequencyType = null;
+            final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
+            if (lockinPeriodFrequencyTypeValue != null) {
+                final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+                lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
+            }
+
+            /*
+             * final BigDecimal withdrawalFeeAmount =
+             * rs.getBigDecimal("withdrawalFeeAmount");
+             * 
+             * EnumOptionData withdrawalFeeType = null; final Integer
+             * withdrawalFeeTypeValue = JdbcSupport.getInteger(rs,
+             * "withdrawalFeeTypeEnum"); if (withdrawalFeeTypeValue != null) {
+             * withdrawalFeeType =
+             * SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); }
+             */
+
+            final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
+
+            final boolean allowOverdraft = rs.getBoolean("allowOverdraft");
+            final BigDecimal overdraftLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit");
+            final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "nominalAnnualInterestRateOverdraft");
+            final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minOverdraftForInterestCalculation");
+
+            final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance");
+            final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance");
+
+            /*
+             * final BigDecimal annualFeeAmount =
+             * JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+             * "annualFeeAmount");
+             * 
+             * MonthDay annualFeeOnMonthDay = null; final Integer
+             * annualFeeOnMonth = JdbcSupport.getInteger(rs,
+             * "annualFeeOnMonth"); final Integer annualFeeOnDay =
+             * JdbcSupport.getInteger(rs, "annualFeeOnDay"); if (annualFeeAmount
+             * != null && annualFeeOnDay != null) { annualFeeOnMonthDay = new
+             * MonthDay(annualFeeOnMonth, annualFeeOnDay); }
+             * 
+             * final LocalDate annualFeeNextDueDate =
+             * JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate");
+             */
+            final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits");
+            final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals");
+            final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees");
+            final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalAnnualFees");
+
+            final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned");
+            final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestPosted");
+            final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance");
+            final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge");
+            final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge");
+            final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverdraftInterestDerived");
+            
+            final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+                    "minBalanceForInterestCalculation");
+            final BigDecimal onHoldFunds = rs.getBigDecimal("onHoldFunds");
+
+            final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
+                    totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge,
+                    totalPenaltyCharge, totalOverdraftInterestDerived);
+
+            return SavingsAccountData.instance(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
+                    productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate,
+                    interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                    minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary,
+                    allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation,
+                    onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+        }
+    }
+
+    private static final class SavingAccountMapperForLookup implements RowMapper<SavingsAccountData> {
+
+        private final String schemaSql;
+
+        public SavingAccountMapperForLookup() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("sa.id as id, sa.account_no as accountNo, ");
+            sqlBuilder.append("sa.deposit_type_enum as depositType, ");
+            sqlBuilder.append("sp.id as productId, sp.name as productName, ");
+            sqlBuilder.append("sa.status_enum as statusEnum ");
+
+            sqlBuilder.append("from m_savings_account sa ");
+            sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+            final Integer depositTypeId = rs.getInt("depositType");
+            final EnumOptionData depositType = SavingsEnumerations.depositType(depositTypeId);
+
+            final Long productId = rs.getLong("productId");
+            final String productName = rs.getString("productName");
+
+            final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum);
+
+            return SavingsAccountData.lookupWithProductDetails(id, accountNo, depositType, productId, productName, status);
+        }
+    }
+
+    @Override
+    public SavingsAccountData retrieveTemplate(final Long clientId, final Long groupId, final Long productId,
+            final boolean staffInSelectedOfficeOnly) {
+
+        final AppUser loggedInUser = this.context.authenticatedUser();
+        Long officeId = loggedInUser.getOffice().getId();
+
+        ClientData client = null;
+        if (clientId != null) {
+            client = this.clientReadPlatformService.retrieveOne(clientId);
+            officeId = client.officeId();
+        }
+
+        GroupGeneralData group = null;
+        if (groupId != null) {
+            group = this.groupReadPlatformService.retrieveOne(groupId);
+            officeId = group.officeId();
+        }
+
+        final Collection<SavingsProductData> productOptions = this.savingsProductReadPlatformService.retrieveAllForLookup();
+        SavingsAccountData template = null;
+        if (productId != null) {
+
+            final SavingAccountTemplateMapper mapper = new SavingAccountTemplateMapper(client, group);
+
+            final String sql = "select " + mapper.schema() + " where sp.id = ?";
+            template = this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { productId });
+
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = this.dropdownReadPlatformService
+                    .retrieveCompoundingInterestPeriodTypeOptions();
+
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions = this.dropdownReadPlatformService
+                    .retrieveInterestPostingPeriodTypeOptions();
+
+            final Collection<EnumOptionData> interestCalculationTypeOptions = this.dropdownReadPlatformService
+                    .retrieveInterestCalculationTypeOptions();
+
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService
+                    .retrieveInterestCalculationDaysInYearTypeOptions();
+
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = this.dropdownReadPlatformService
+                    .retrieveLockinPeriodFrequencyTypeOptions();
+
+            final Collection<EnumOptionData> withdrawalFeeTypeOptions = this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions();
+
+            final Collection<SavingsAccountTransactionData> transactions = null;
+            final Collection<ChargeData> productCharges = this.chargeReadPlatformService.retrieveSavingsProductCharges(productId);
+            // update charges from Product charges
+            final Collection<SavingsAccountChargeData> charges = fromChargesToSavingsCharges(productCharges);
+
+            final boolean feeChargesOnly = false;
+            final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly);
+
+            Collection<StaffData> fieldOfficerOptions = null;
+
+            if (officeId != null) {
+
+                if (staffInSelectedOfficeOnly) {
+                    // only bring back loan officers in selected branch/office
+                    final Collection<StaffData> fieldOfficersInBranch = this.staffReadPlatformService
+                            .retrieveAllLoanOfficersInOfficeById(officeId);
+
+                    if (!CollectionUtils.isEmpty(fieldOfficersInBranch)) {
+                        fieldOfficerOptions = new ArrayList<>(fieldOfficersInBranch);
+                    }
+                } else {
+                    // by default bring back all officers in selected
+                    // branch/office as well as officers in office above
+                    // this office
+                    final boolean restrictToLoanOfficersOnly = true;
+                    final Collection<StaffData> loanOfficersInHierarchy = this.staffReadPlatformService
+                            .retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeId, restrictToLoanOfficersOnly);
+
+                    if (!CollectionUtils.isEmpty(loanOfficersInHierarchy)) {
+                        fieldOfficerOptions = new ArrayList<>(loanOfficersInHierarchy);
+                    }
+                }
+            }
+
+            template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions,
+                    interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                    interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions,
+                    charges, chargeOptions);
+        } else {
+
+            String clientName = null;
+            if (client != null) {
+                clientName = client.displayName();
+            }
+
+            String groupName = null;
+            if (group != null) {
+                groupName = group.getName();
+            }
+
+            template = SavingsAccountData.withClientTemplate(clientId, clientName, groupId, groupName);
+
+            final Collection<StaffData> fieldOfficerOptions = null;
+            final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
+            final Collection<EnumOptionData> interestCalculationTypeOptions = null;
+            final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
+            final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+            final Collection<SavingsAccountTransactionData> transactions = null;
+            final Collection<SavingsAccountChargeData> charges = null;
+
+            final boolean feeChargesOnly = true;
+            final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly);
+
+            template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions,
+                    interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                    interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions,
+                    charges, chargeOptions);
+        }
+
+        return template;
+    }
+
+    private Collection<SavingsAccountChargeData> fromChargesToSavingsCharges(final Collection<ChargeData> productCharges) {
+        final Collection<SavingsAccountChargeData> savingsCharges = new ArrayList<>();
+        for (final ChargeData chargeData : productCharges) {
+            final SavingsAccountChargeData savingsCharge = chargeData.toSavingsAccountChargeData();
+            savingsCharges.add(savingsCharge);
+        }
+        return savingsCharges;
+    }
+
+    @Override
+    public SavingsAccountTransactionData retrieveDepositTransactionTemplate(final Long savingsId,
+            final DepositAccountType depositAccountType) {
+
+        try {
+            final String sql = "select " + this.transactionTemplateMapper.schema() + " where sa.id = ? and sa.deposit_type_enum = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, this.transactionTemplateMapper,
+                    new Object[] { savingsId, depositAccountType.getValue() });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new SavingsAccountNotFoundException(savingsId);
+        }
+    }
+
+    @Override
+    public Collection<SavingsAccountTransactionData> retrieveAllTransactions(final Long savingsId, DepositAccountType depositAccountType) {
+
+        final String sql = "select " + this.transactionsMapper.schema()
+                + " where sa.id = ? and sa.deposit_type_enum = ? order by tr.transaction_date DESC, tr.created_date DESC, tr.id DESC";
+
+        return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId, depositAccountType.getValue() });
+    }
+
+    @Override
+    public SavingsAccountTransactionData retrieveSavingsTransaction(final Long savingsId, final Long transactionId,
+            DepositAccountType depositAccountType) {
+
+        final String sql = "select " + this.transactionsMapper.schema() + " where sa.id = ? and sa.deposit_type_enum = ? and tr.id= ?";
+
+        return this.jdbcTemplate.queryForObject(sql, this.transactionsMapper, new Object[] { savingsId, depositAccountType.getValue(),
+                transactionId });
+    }
+
+    /*
+     * @Override public Collection<SavingsAccountAnnualFeeData>
+     * retrieveAccountsWithAnnualFeeDue() { final String sql = "select " +
+     * this.annualFeeMapper.schema() +
+     * " where sa.annual_fee_next_due_date is not null and sa.annual_fee_next_due_date <= NOW()"
+     * ;
+     * 
+     * return this.jdbcTemplate.query(sql, this.annualFeeMapper, new Object[]
+     * {}); }
+     */
+
+    private static final class SavingsAccountTransactionsMapper implements RowMapper<SavingsAccountTransactionData> {
+
+        private final String schemaSql;
+
+        public SavingsAccountTransactionsMapper() {
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, ");
+            sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,");
+            sqlBuilder.append("tr.created_date as submittedOnDate,");
+            sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,");
+            sqlBuilder.append("fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,");
+            sqlBuilder.append("fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,");
+            sqlBuilder.append("fromtran.description as fromTransferDescription,");
+            sqlBuilder.append("totran.id as toTransferId, totran.is_reversed as toTransferReversed,");
+            sqlBuilder.append("totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,");
+            sqlBuilder.append("totran.description as toTransferDescription,");
+            sqlBuilder.append("sa.id as savingsId, sa.account_no as accountNo,");
+            sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, ");
+            sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, ");
+            sqlBuilder
+                    .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            sqlBuilder.append("pt.value as paymentTypeName ");
+            sqlBuilder.append("from m_savings_account sa ");
+            sqlBuilder.append("join m_savings_account_transaction tr on tr.savings_account_id = sa.id ");
+            sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
+            sqlBuilder.append("left join m_account_transfer_transaction fromtran on fromtran.from_savings_transaction_id = tr.id ");
+            sqlBuilder.append("left join m_account_transfer_transaction totran on totran.to_savings_transaction_id = tr.id ");
+            sqlBuilder.append("left join m_payment_detail pd on tr.payment_detail_id = pd.id ");
+            sqlBuilder.append("left join m_payment_type pt on pd.payment_type_id = pt.id ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long id = rs.getLong("transactionId");
+            final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
+            final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt);
+
+            final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate");
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
+            final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount");
+            final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance");
+            final boolean reversed = rs.getBoolean("reversed");
+
+            final Long savingsId = rs.getLong("savingsId");
+            final String accountNo = rs.getString("accountNo");
+
+            PaymentDetailData paymentDetailData = null;
+            if (transactionType.isDepositOrWithdrawal()) {
+                final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType");
+                if (paymentTypeId != null) {
+                    final String typeName = rs.getString("paymentTypeName");
+                    final PaymentTypeData paymentType = PaymentTypeData.instance(paymentTypeId, typeName);
+                    final String accountNumber = rs.getString("accountNumber");
+                    final String checkNumber = rs.getString("checkNumber");
+                    final String routingCode = rs.getString("routingCode");
+                    final String receiptNumber = rs.getString("receiptNumber");
+                    final String bankNumber = rs.getString("bankNumber");
+                    paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber,
+                            bankNumber);
+                }
+            }
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            AccountTransferData transfer = null;
+            final Long fromTransferId = JdbcSupport.getLong(rs, "fromTransferId");
+            final Long toTransferId = JdbcSupport.getLong(rs, "toTransferId");
+            if (fromTransferId != null) {
+                final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, "fromTransferDate");
+                final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fromTransferAmount");
+                final boolean fromTransferReversed = rs.getBoolean("fromTransferReversed");
+                final String fromTransferDescription = rs.getString("fromTransferDescription");
+
+                transfer = AccountTransferData.transferBasicDetails(fromTransferId, currency, fromTransferAmount, fromTransferDate,
+                        fromTransferDescription, fromTransferReversed);
+            } else if (toTransferId != null) {
+                final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, "toTransferDate");
+                final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "toTransferAmount");
+                final boolean toTransferReversed = rs.getBoolean("toTransferReversed");
+                final String toTransferDescription = rs.getString("toTransferDescription");
+
+                transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate,
+                        toTransferDescription, toTransferReversed);
+            }
+
+            return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency,
+                    amount, runningBalance, reversed, transfer, submittedOnDate);
+        }
+    }
+
+    private static final class SavingsAccountTransactionTemplateMapper implements RowMapper<SavingsAccountTransactionData> {
+
+        private final String schemaSql;
+
+        public SavingsAccountTransactionTemplateMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("sa.id as id, sa.account_no as accountNo, ");
+            sqlBuilder
+                    .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance ");
+            sqlBuilder.append("from m_savings_account sa ");
+            sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long savingsId = rs.getLong("id");
+            final String accountNo = rs.getString("accountNo");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            return SavingsAccountTransactionData.template(savingsId, accountNo, DateUtils.getLocalDateOfTenant(), currency);
+        }
+    }
+
+    private static final class SavingAccountTemplateMapper implements RowMapper<SavingsAccountData> {
+
+        private final ClientData client;
+        private final GroupGeneralData group;
+
+        private final String schemaSql;
+
+        public SavingAccountTemplateMapper(final ClientData client, final GroupGeneralData group) {
+            this.client = client;
+            this.group = group;
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("sp.id as productId, sp.name as productName, ");
+            sqlBuilder
+                    .append("sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, sp.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            sqlBuilder.append("sp.nominal_annual_interest_rate as nominalAnnualIterestRate, ");
+            sqlBuilder.append("sp.interest_compounding_period_enum as interestCompoundingPeriodType, ");
+            sqlBuilder.append("sp.interest_posting_period_enum as interestPostingPeriodType, ");
+            sqlBuilder.append("sp.interest_calculation_type_enum as interestCalculationType, ");
+            sqlBuilder.append("sp.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, ");
+            sqlBuilder.append("sp.min_required_opening_balance as minRequiredOpeningBalance, ");
+            sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,");
+            sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
+            // sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,");
+            // sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, ");
+            sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
+            sqlBuilder.append("sp.min_balance_for_interest_calculation as minBalanceForInterestCalculation, ");
+            sqlBuilder.append("sp.allow_overdraft as allowOverdraft, ");
+            sqlBuilder.append("sp.overdraft_limit as overdraftLimit, ");
+            sqlBuilder.append("sp.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, ");
+            sqlBuilder.append("sp.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, ");
+            // sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,");
+            // sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, ");
+            // sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay ");
+            sqlBuilder.append("sp.min_required_balance as minRequiredBalance, ");
+            sqlBuilder.append("sp.enforce_min_required_balance as enforceMinRequiredBalance ");
+            sqlBuilder.append("from m_savings_product sp ");
+            sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long productId = rs.getLong("productId");
+            final String productName = rs.getString("productName");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+
+            final BigDecimal nominalAnnualIterestRate = rs.getBigDecimal("nominalAnnualIterestRate");
+
+            final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations
+                    .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs,
+                            "interestCompoundingPeriodType")));
+
+            final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType
+                    .fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType")));
+
+            final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType
+                    .fromInt(JdbcSupport.getInteger(rs, "interestCalculationType")));
+
+            final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations
+                    .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs,
+                            "interestCalculationDaysInYearType")));
+
+            final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance");
+
+            final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
+            EnumOptionData lockinPeriodFrequencyType = null;
+            final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
+            if (lockinPeriodFrequencyTypeValue != null) {
+                final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+                lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
+            }
+
+            // final BigDecimal withdrawalFeeAmount =
+            // rs.getBigDecimal("withdrawalFeeAmount");
+
+            /*
+             * EnumOptionData withdrawalFeeType = null; final Integer
+             * withdrawalFeeTypeValue = JdbcSupport.getInteger(rs,
+             * "withdrawalFeeTypeEnum"); if (withdrawalFeeTypeValue != null) {
+             * withdrawalFeeType =
+             * SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); }
+             */
+            final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
+
+            final boolean allowOverdraft = rs.getBoolean("allowOverdraft");
+            final BigDecimal overdraftLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit");
+            final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "nominalAnnualInterestRateOverdraft");
+            final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minOverdraftForInterestCalculation");
+
+            final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance");
+            final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance");
+            final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+                    "minBalanceForInterestCalculation");
+
+            // final BigDecimal annualFeeAmount =
+            // JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+            // "annualFeeAmount");
+
+            /*
+             * MonthDay annualFeeOnMonthDay = null; final Integer
+             * annualFeeOnMonth = JdbcSupport.getInteger(rs,
+             * "annualFeeOnMonth"); final Integer annualFeeOnDay =
+             * JdbcSupport.getInteger(rs, "annualFeeOnDay"); if (annualFeeAmount
+             * != null && annualFeeOnDay != null) { annualFeeOnMonthDay = new
+             * MonthDay(annualFeeOnMonth, annualFeeOnDay); }
+             */
+
+            Long clientId = null;
+            String clientName = null;
+            if (this.client != null) {
+                clientId = this.client.id();
+                clientName = this.client.displayName();
+            }
+
+            Long groupId = null;
+            String groupName = null;
+            if (this.group != null) {
+                groupId = this.group.getId();
+                groupName = this.group.getName();
+            }
+
+            final Long fieldOfficerId = null;
+            final String fieldOfficerName = null;
+            final SavingsAccountStatusEnumData status = null;
+            // final LocalDate annualFeeNextDueDate = null;
+            final SavingsAccountSummaryData summary = null;
+            final BigDecimal onHoldFunds = null;
+
+            final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault();
+            final EnumOptionData depositType = null;
+            return SavingsAccountData.instance(null, null, depositType, null, groupId, groupName, clientId, clientName, productId,
+                    productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate,
+                    interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                    minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary,
+                    allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation,
+                    onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+        }
+    }
+
+    @Override
+    public Collection<SavingsAccountData> retrieveForLookup(Long clientId, Boolean overdraft) {
+
+        SavingAccountMapperForLookup accountMapperForLookup = new SavingAccountMapperForLookup();
+        final StringBuilder sqlBuilder = new StringBuilder("select " + accountMapperForLookup.schema());
+        sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300");
+        Object[] queryParameters = null;
+        if (overdraft == null) {
+            queryParameters = new Object[] { clientId };
+        } else {
+            sqlBuilder.append(" and sa.allow_overdraft = ?");
+            queryParameters = new Object[] { clientId, overdraft };
+        }
+        return this.jdbcTemplate.query(sqlBuilder.toString(), accountMapperForLookup, queryParameters);
+
+    }
+
+    /*
+     * private static final class SavingsAccountAnnualFeeMapper implements
+     * RowMapper<SavingsAccountAnnualFeeData> {
+     * 
+     * private final String schemaSql;
+     * 
+     * public SavingsAccountAnnualFeeMapper() { final StringBuilder sqlBuilder =
+     * new StringBuilder(200);
+     * sqlBuilder.append("sa.id as id, sa.account_no as accountNo, ");
+     * sqlBuilder
+     * .append("sa.annual_fee_next_due_date as annualFeeNextDueDate ");
+     * sqlBuilder.append("from m_savings_account sa ");
+     * 
+     * this.schemaSql = sqlBuilder.toString(); }
+     * 
+     * public String schema() { return this.schemaSql; }
+     * 
+     * @Override public SavingsAccountAnnualFeeData mapRow(final ResultSet rs,
+     * 
+     * @SuppressWarnings("unused") final int rowNum) throws SQLException {
+     * 
+     * final Long id = rs.getLong("id"); final String accountNo =
+     * rs.getString("accountNo"); final LocalDate annualFeeNextDueDate =
+     * JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate");
+     * 
+     * return SavingsAccountAnnualFeeData.instance(id, accountNo,
+     * annualFeeNextDueDate); } }
+     */
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java
new file mode 100644
index 0000000..434ac09
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java
@@ -0,0 +1,82 @@
+/**
+ * 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.savings.service;
+
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+
+public interface SavingsAccountWritePlatformService {
+
+    CommandProcessingResult activate(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult deposit(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult withdrawal(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult applyAnnualFee(final Long savingsAccountChargeId, final Long accountId);
+
+    CommandProcessingResult calculateInterest(Long savingsId);
+
+    CommandProcessingResult postInterest(Long savingsId);
+
+    CommandProcessingResult undoTransaction(Long savingsId, Long transactionId, boolean allowAccountTransferModification);
+
+    CommandProcessingResult adjustSavingsTransaction(Long savingsId, Long transactionId, JsonCommand command);
+
+    CommandProcessingResult close(Long savingsId, JsonCommand command);
+
+    SavingsAccountTransaction initiateSavingsTransfer(Long accountId, LocalDate transferDate);
+
+    SavingsAccountTransaction withdrawSavingsTransfer(Long accountId, LocalDate transferDate);
+
+    void rejectSavingsTransfer(Long accountId);
+
+    SavingsAccountTransaction acceptSavingsTransfer(Long accountId, LocalDate transferDate, Office acceptedInOffice, Staff staff);
+
+    CommandProcessingResult addSavingsAccountCharge(JsonCommand command);
+
+    CommandProcessingResult updateSavingsAccountCharge(JsonCommand command);
+
+    CommandProcessingResult deleteSavingsAccountCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command);
+
+    CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId);
+
+    CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command);
+
+    CommandProcessingResult inactivateCharge(Long savingsAccountId, Long savingsAccountChargeId);
+
+    CommandProcessingResult assignFieldOfficer(Long savingsAccountId, JsonCommand command);
+
+    CommandProcessingResult unassignFieldOfficer(Long savingsAccountId, JsonCommand command);
+
+    void applyChargeDue(final Long savingsAccountChargeId, final Long accountId);
+
+    void processPostActiveActions(SavingsAccount account, DateTimeFormatter fmt, Set<Long> existingTransactionIds,
+            Set<Long> existingReversedTransactionIds);
+
+    void postInterest(SavingsAccount account);
+}
\ No newline at end of file
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
new file mode 100755
index 0000000..ae75bbf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,1207 @@
+/**
+ * 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.savings.service;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
+import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeDataValidator;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountDataValidator;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDataValidator;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransaction;
+import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransactionRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountCharge;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
+import org.apache.fineract.portfolio.savings.exception.SavingsAccountClosingNotAllowedException;
+import org.apache.fineract.portfolio.savings.exception.SavingsAccountTransactionNotFoundException;
+import org.apache.fineract.portfolio.savings.exception.SavingsOfficerAssignmentException;
+import org.apache.fineract.portfolio.savings.exception.SavingsOfficerUnassignmentException;
+import org.apache.fineract.portfolio.savings.exception.TransactionUpdateNotAllowedException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawBalanceParamName;
+
+@Service
+public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements SavingsAccountWritePlatformService {
+
+    private final PlatformSecurityContext context;
+    private final SavingsAccountRepository savingAccountRepository;
+    private final SavingsAccountDataValidator fromApiJsonDeserializer;
+    private final SavingsAccountRepositoryWrapper savingsRepository;
+    private final StaffRepositoryWrapper staffRepository;
+    private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
+    private final SavingsAccountAssembler savingAccountAssembler;
+    private final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator;
+    private final SavingsAccountChargeDataValidator savingsAccountChargeDataValidator;
+    private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
+    private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
+    private final JournalEntryWritePlatformService journalEntryWritePlatformService;
+    private final SavingsAccountDomainService savingsAccountDomainService;
+    private final NoteRepository noteRepository;
+    private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
+    private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
+    private final ChargeRepositoryWrapper chargeRepository;
+    private final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository;
+    private final HolidayRepositoryWrapper holidayRepository;
+    private final WorkingDaysRepositoryWrapper workingDaysRepository;
+    private final ConfigurationDomainService configurationDomainService;
+    private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
+
+
+    @Autowired
+    public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final SavingsAccountRepository savingAccountRepository,
+            final SavingsAccountTransactionRepository savingsAccountTransactionRepository,
+            final SavingsAccountAssembler savingAccountAssembler,
+            final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator,
+            final SavingsAccountChargeDataValidator savingsAccountChargeDataValidator,
+            final PaymentDetailWritePlatformService paymentDetailWritePlatformService,
+            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
+            final JournalEntryWritePlatformService journalEntryWritePlatformService,
+            final SavingsAccountDomainService savingsAccountDomainService, final NoteRepository noteRepository,
+            final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final HolidayRepositoryWrapper holidayRepository,
+            final WorkingDaysRepositoryWrapper workingDaysRepository,
+            final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
+            final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository,
+            final SavingsAccountDataValidator fromApiJsonDeserializer, final SavingsAccountRepositoryWrapper savingsRepository,
+            final StaffRepositoryWrapper staffRepository, final ConfigurationDomainService configurationDomainService,
+            final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository) {
+        this.context = context;
+        this.savingAccountRepository = savingAccountRepository;
+        this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
+        this.savingAccountAssembler = savingAccountAssembler;
+        this.savingsAccountTransactionDataValidator = savingsAccountTransactionDataValidator;
+        this.savingsAccountChargeDataValidator = savingsAccountChargeDataValidator;
+        this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
+        this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
+        this.journalEntryWritePlatformService = journalEntryWritePlatformService;
+        this.savingsAccountDomainService = savingsAccountDomainService;
+        this.noteRepository = noteRepository;
+        this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
+        this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
+        this.chargeRepository = chargeRepository;
+        this.savingsAccountChargeRepository = savingsAccountChargeRepository;
+        this.holidayRepository = holidayRepository;
+        this.workingDaysRepository = workingDaysRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.savingsRepository = savingsRepository;
+        this.staffRepository = staffRepository;
+        this.configurationDomainService = configurationDomainService;
+        this.depositAccountOnHoldTransactionRepository= depositAccountOnHoldTransactionRepository;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult activate(final Long savingsId, final JsonCommand command) {
+
+        final AppUser user = this.context.authenticatedUser();
+
+        this.savingsAccountTransactionDataValidator.validateActivation(command);
+
+        final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(account);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        final Map<String, Object> changes = account.activate(user, command, DateUtils.getLocalDateOfTenant());
+        if (!changes.isEmpty()) {
+            final Locale locale = command.extractLocale();
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+            processPostActiveActions(account, fmt, existingTransactionIds, existingReversedTransactionIds);
+
+            this.savingAccountRepository.save(account);
+        }
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Override
+    public void processPostActiveActions(final SavingsAccount account, final DateTimeFormatter fmt, final Set<Long> existingTransactionIds,
+            final Set<Long> existingReversedTransactionIds) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        Money amountForDeposit = account.activateWithBalance();
+        boolean isRegularTransaction = false;
+        if (amountForDeposit.isGreaterThanZero()) {
+            boolean isAccountTransfer = false;
+            this.savingsAccountDomainService.handleDeposit(account, fmt, account.getActivationLocalDate(), amountForDeposit.getAmount(),
+                    null, isAccountTransfer, isRegularTransaction);
+            updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        }
+        account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user);
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+        account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),depositAccountOnHoldTransactions);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deposit(final Long savingsId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        this.savingsAccountTransactionDataValidator.validate(command);
+
+        final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(account);
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+        boolean isAccountTransfer = false;
+        boolean isRegularTransaction = true;
+        final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate,
+                transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(deposit.getId()) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) {
+        this.savingsAccountTransactionRepository.save(transaction);
+        return transaction.getId();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult withdrawal(final Long savingsId, final JsonCommand command) {
+
+        this.savingsAccountTransactionDataValidator.validate(command);
+
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(account);
+
+        final boolean isAccountTransfer = false;
+        final boolean isRegularTransaction = true;
+        final boolean isApplyWithdrawFee = true;
+        final boolean isInterestTransfer = false;
+        final boolean isWithdrawBalance = false;
+        final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
+                isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isWithdrawBalance);
+        final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate,
+                transactionAmount, paymentDetail, transactionBooleanValues);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(withdrawal.getId()) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes)//
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult applyAnnualFee(final Long savingsAccountChargeId, final Long accountId) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, accountId);
+
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
+
+        this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(), fmt, user);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountCharge.getId()) //
+                .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
+                .withClientId(savingsAccountCharge.savingsAccount().clientId()) //
+                .withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
+                .withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult calculateInterest(final Long savingsId) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(account);
+
+        final LocalDate today = DateUtils.getLocalDateOfTenant();
+        final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode());
+        boolean isInterestTransfer = false;
+        account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        this.savingAccountRepository.save(account);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .build();
+    }
+
+
+    @Override
+    public CommandProcessingResult postInterest(final Long savingsId) {
+
+        final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(account);
+        postInterest(account);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public void postInterest(final SavingsAccount account) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        if (account.getNominalAnnualInterestRate().compareTo(BigDecimal.ZERO) > 0
+        		|| (account.allowOverdraft() && account.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) {
+            final Set<Long> existingTransactionIds = new HashSet<>();
+            final Set<Long> existingReversedTransactionIds = new HashSet<>();
+            updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode());
+            boolean isInterestTransfer = false;
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+
+            // for generating transaction id's
+            List<SavingsAccountTransaction> transactions = account.getTransactions();
+            for (SavingsAccountTransaction accountTransaction : transactions) {
+                if (accountTransaction.getId() == null) {
+                    this.savingsAccountTransactionRepository.save(accountTransaction);
+                }
+            }
+
+            this.savingAccountRepository.save(account);
+
+            postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+        }
+    }
+
+    @Override
+    public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId,
+            final boolean allowAccountTransferModification) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        final SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository
+                .findOneByIdAndSavingsAccountId(transactionId, savingsId);
+        if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); }
+
+        if (!allowAccountTransferModification
+                && this.accountTransfersReadPlatformService
+                        .isAccountTransfer(transactionId,
+                                PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException(
+                                        "error.msg.saving.account.transfer.transaction.update.not.allowed", "Savings account transaction:"
+                                                + transactionId + " update not allowed as it involves in account transfer",
+                                        transactionId); }
+
+        if (!account
+                .allowModify()) { throw new PlatformServiceUnavailableException("error.msg.saving.account.transaction.update.not.allowed",
+                        "Savings account transaction:" + transactionId + " update not allowed for this savings type", transactionId); }
+
+        final LocalDate today = DateUtils.getLocalDateOfTenant();
+        final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode());
+
+        if (account.isNotActive()) {
+            throwValidationForActiveStatus(SavingsApiConstants.undoTransactionAction);
+        }
+        account.undoTransaction(transactionId);
+
+        // undoing transaction is withdrawal then undo withdrawal fee
+        // transaction if any
+        if (savingsAccountTransaction.isWithdrawal()) {
+            final SavingsAccountTransaction nextSavingsAccountTransaction = this.savingsAccountTransactionRepository
+                    .findOneByIdAndSavingsAccountId(transactionId + 1, savingsId);
+            if (nextSavingsAccountTransaction != null && nextSavingsAccountTransaction.isWithdrawalFeeAndNotReversed()) {
+                account.undoTransaction(transactionId + 1);
+            }
+        }
+        boolean isInterestTransfer = false;
+        checkClientOrGroupActive(account);
+        if (savingsAccountTransaction.isPostInterestCalculationRequired()
+                && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else {
+            account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+        account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction,depositAccountOnHoldTransactions);
+        account.activateAccountBasedOnBalance();
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult adjustSavingsTransaction(final Long savingsId, final Long transactionId, final JsonCommand command) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository
+                .findOneByIdAndSavingsAccountId(transactionId, savingsId);
+        if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); }
+
+        if (!(savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal())
+                || savingsAccountTransaction.isReversed()) { throw new TransactionUpdateNotAllowedException(savingsId, transactionId); }
+
+        if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId,
+                PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException(
+                        "error.msg.saving.account.transfer.transaction.update.not.allowed",
+                        "Savings account transaction:" + transactionId + " update not allowed as it involves in account transfer",
+                        transactionId); }
+
+        this.savingsAccountTransactionDataValidator.validate(command);
+
+        final LocalDate today = DateUtils.getLocalDateOfTenant();
+
+        final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+        if (account.isNotActive()) {
+            throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction);
+        }
+        if (!account
+                .allowModify()) { throw new PlatformServiceUnavailableException("error.msg.saving.account.transaction.update.not.allowed",
+                        "Savings account transaction:" + transactionId + " update not allowed for this savings type", transactionId); }
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed(SavingsApiConstants.transactionDateParamName);
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(SavingsApiConstants.transactionAmountParamName);
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode());
+        account.undoTransaction(transactionId);
+
+        // for undo withdrawal fee
+        final SavingsAccountTransaction nextSavingsAccountTransaction = this.savingsAccountTransactionRepository
+                .findOneByIdAndSavingsAccountId(transactionId + 1, savingsId);
+        if (nextSavingsAccountTransaction != null && nextSavingsAccountTransaction.isWithdrawalFeeAndNotReversed()) {
+            account.undoTransaction(transactionId + 1);
+        }
+
+        SavingsAccountTransaction transaction = null;
+        boolean isInterestTransfer = false;
+        final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
+                paymentDetail, savingsAccountTransaction.createdDate(), user);
+        if (savingsAccountTransaction.isDeposit()) {
+            transaction = account.deposit(transactionDTO);
+        } else {
+            transaction = account.withdraw(transactionDTO, true);
+        }
+        final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction);
+
+        if (account.isBeforeLastPostingPeriod(transactionDate)
+                || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else {
+            account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+        account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction,depositAccountOnHoldTransactions);
+        account.activateAccountBasedOnBalance();
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(newtransactionId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes)//
+                .build();
+    }
+
+    /**
+     *
+     */
+    private void throwValidationForActiveStatus(final String actionName) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + actionName);
+        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("account.is.not.active");
+        throw new PlatformApiDataValidationException(dataValidationErrors);
+    }
+
+    private void checkClientOrGroupActive(final SavingsAccount account) {
+        final Client client = account.getClient();
+        if (client != null) {
+            if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); }
+        }
+        final Group group = account.group();
+        if (group != null) {
+            if (group.isNotActive()) { throw new GroupNotActiveException(group.getId()); }
+        }
+    }
+
+    @Override
+    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);
+        final boolean isLinkedWithAnyActiveLoan = this.accountAssociationsReadPlatformService.isLinkedWithAnyActiveAccount(savingsId);
+
+        if (isLinkedWithAnyActiveLoan) {
+            final String defaultUserMessage = "Closing savings account with id:" + savingsId
+                    + " is not allowed, since it is linked with one of the active accounts";
+            throw new SavingsAccountClosingNotAllowedException("linked", defaultUserMessage, savingsId);
+        }
+
+        final boolean isWithdrawBalance = command.booleanPrimitiveValueOfParameterNamed(withdrawBalanceParamName);
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+
+        if (isWithdrawBalance && account.getSummary().getAccountBalance(account.getCurrency()).isGreaterThanZero()) {
+
+            final BigDecimal transactionAmount = account.getSummary().getAccountBalance();
+            final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+            final boolean isAccountTransfer = false;
+            final boolean isRegularTransaction = true;
+            final boolean isApplyWithdrawFee = false;
+            final boolean isInterestTransfer = false;
+            final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
+                    isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isWithdrawBalance);
+
+            this.savingsAccountDomainService.handleWithdrawal(account, fmt, closedDate, transactionAmount, paymentDetail,
+                    transactionBooleanValues);
+
+        }
+
+        final Map<String, Object> accountChanges = account.close(user, command, DateUtils.getLocalDateOfTenant());
+        changes.putAll(accountChanges);
+        if (!changes.isEmpty()) {
+            this.savingAccountRepository.save(account);
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.savingNote(account, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+
+        }
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Override
+    public SavingsAccountTransaction initiateSavingsTransfer(final Long accountId, final LocalDate transferDate) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        final SavingsAccountTransaction newTransferTransaction = SavingsAccountTransaction.initiateTransfer(savingsAccount,
+                savingsAccount.office(), transferDate, user);
+        savingsAccount.getTransactions().add(newTransferTransaction);
+        savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue());
+        final MathContext mc = MathContext.DECIMAL64;
+        boolean isInterestTransfer = false;
+        savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        this.savingsAccountTransactionRepository.save(newTransferTransaction);
+        this.savingAccountRepository.save(savingsAccount);
+
+        postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        return newTransferTransaction;
+    }
+
+    @Override
+    public SavingsAccountTransaction withdrawSavingsTransfer(final Long accountId, final LocalDate transferDate) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        final SavingsAccountTransaction withdrawtransferTransaction = SavingsAccountTransaction.withdrawTransfer(savingsAccount,
+                savingsAccount.office(), transferDate, user);
+        savingsAccount.getTransactions().add(withdrawtransferTransaction);
+        savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue());
+        final MathContext mc = MathContext.DECIMAL64;
+        boolean isInterestTransfer = false;
+        savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        this.savingsAccountTransactionRepository.save(withdrawtransferTransaction);
+        this.savingAccountRepository.save(savingsAccount);
+
+        postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        return withdrawtransferTransaction;
+    }
+
+    @Override
+    public void rejectSavingsTransfer(final Long accountId) {
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId);
+        savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_ON_HOLD.getValue());
+        this.savingAccountRepository.save(savingsAccount);
+    }
+
+    @Override
+    public SavingsAccountTransaction acceptSavingsTransfer(final Long accountId, final LocalDate transferDate,
+            final Office acceptedInOffice, final Staff fieldOfficer) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        final SavingsAccountTransaction acceptTransferTransaction = SavingsAccountTransaction.approveTransfer(savingsAccount,
+                acceptedInOffice, transferDate, user);
+        savingsAccount.getTransactions().add(acceptTransferTransaction);
+        savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue());
+        if (fieldOfficer != null) {
+            savingsAccount.reassignSavingsOfficer(fieldOfficer, transferDate);
+        }
+        boolean isInterestTransfer = false;
+        final MathContext mc = MathContext.DECIMAL64;
+        savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                financialYearBeginningMonth);
+
+        this.savingsAccountTransactionRepository.save(acceptTransferTransaction);
+        this.savingAccountRepository.save(savingsAccount);
+
+        postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
+
+        return acceptTransferTransaction;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command) {
+
+        this.context.authenticatedUser();
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final Long savingsAccountId = command.getSavingsId();
+        this.savingsAccountChargeDataValidator.validateAdd(command.json());
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Locale locale = command.extractLocale();
+        final String format = command.dateFormat();
+        final DateTimeFormatter fmt = StringUtils.isNotBlank(format) ? DateTimeFormat.forPattern(format).withLocale(locale)
+                : DateTimeFormat.forPattern("dd MM yyyy");
+
+        final Long chargeDefinitionId = command.longValueOfParameterNamed(chargeIdParamName);
+        final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
+
+        final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewFromJson(savingsAccount, chargeDefinition, command);
+
+        if (savingsAccountCharge.getDueLocalDate() != null) {
+            // transaction date should not be on a holiday or non working day
+            if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled()
+                    && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+
+            if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled()
+                    && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+
+        savingsAccount.addCharge(fmt, savingsAccountCharge, chargeDefinition);
+
+        this.savingAccountRepository.saveAndFlush(savingsAccount);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountCharge.getId()) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsAccountId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand command) {
+
+        this.context.authenticatedUser();
+        this.savingsAccountChargeDataValidator.validateUpdate(command.json());
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final Long savingsAccountId = command.getSavingsId();
+        // SavingsAccount Charge entity
+        final Long savingsChargeId = command.entityId();
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
+        checkClientOrGroupActive(savingsAccount);
+
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(savingsChargeId,
+                savingsAccountId);
+
+        final Map<String, Object> changes = savingsAccountCharge.update(command);
+
+        if (savingsAccountCharge.getDueLocalDate() != null) {
+            final Locale locale = command.extractLocale();
+            final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+            // transaction date should not be on a holiday or non working day
+            if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled()
+                    && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+
+            if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled()
+                    && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueLocalDate())) {
+                baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
+                        .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            }
+        }
+
+        this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountCharge.getId()) //
+                .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
+                .withClientId(savingsAccountCharge.savingsAccount().clientId()) //
+                .withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
+                .withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+
+        AppUser user = getAppUserIfPresent();
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId);
+
+        // Get Savings account from savings charge
+        final SavingsAccount account = savingsAccountCharge.savingsAccount();
+        this.savingAccountAssembler.assignSavingAccountHelpers(account);
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+
+        account.waiveCharge(savingsAccountChargeId, user);
+        boolean isInterestTransfer = false;
+        final MathContext mc = MathContext.DECIMAL64;
+        if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+
+        account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction,depositAccountOnHoldTransactions);
+
+        this.savingAccountRepository.saveAndFlush(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountChargeId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsAccountId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId,
+            @SuppressWarnings("unused") final JsonCommand command) {
+        this.context.authenticatedUser();
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
+        checkClientOrGroupActive(savingsAccount);
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId);
+
+        savingsAccount.removeCharge(savingsAccountCharge);
+        this.savingAccountRepository.saveAndFlush(savingsAccount);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountChargeId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsAccountId) //
+                .build();
+    }
+
+    @Override
+    public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) {
+
+        AppUser user = getAppUserIfPresent();
+
+        this.savingsAccountChargeDataValidator.validatePayCharge(command.json());
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(amountParamName);
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
+
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        // transaction date should not be on a holiday or non working day
+        if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled()
+                && this.holidayRepository.isHoliday(savingsAccountCharge.savingsAccount().officeId(), transactionDate)) {
+            baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt))
+                    .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled()
+                && !this.workingDaysRepository.isWorkingDay(transactionDate)) {
+            baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt))
+                    .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.a.nonworking.day");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt, user);
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountCharge.getId()) //
+                .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
+                .withClientId(savingsAccountCharge.savingsAccount().clientId()) //
+                .withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
+                .withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
+                .build();
+
+    }
+
+    @Transactional
+    @Override
+    public void applyChargeDue(final Long savingsAccountChargeId, final Long accountId) {
+        // always use current date as transaction date for batch job
+        AppUser user = null;
+
+        final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, accountId);
+
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
+        fmt.withZone(DateUtils.getDateTimeZoneOfTenant());
+
+        while (transactionDate.isAfter(savingsAccountCharge.getDueLocalDate()) && savingsAccountCharge.isNotFullyPaid()) {
+            payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt, user);
+        }
+    }
+
+    @Transactional
+    private void payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final BigDecimal amountPaid,
+            final DateTimeFormatter formatter, final AppUser user) {
+
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
+
+        // Get Savings account from savings charge
+        final SavingsAccount account = savingsAccountCharge.savingsAccount();
+        this.savingAccountAssembler.assignSavingAccountHelpers(account);
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+        updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
+        account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user);
+        boolean isInterestTransfer = false;
+        final MathContext mc = MathContext.DECIMAL64;
+        if (account.isBeforeLastPostingPeriod(transactionDate)) {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
+        } else {
+            final LocalDate today = DateUtils.getLocalDateOfTenant();
+            account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
+                    financialYearBeginningMonth);
+        }
+        List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
+        if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){
+            depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
+        }
+
+        account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(),depositAccountOnHoldTransactions);
+
+        this.savingAccountRepository.save(account);
+
+        postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+    }
+
+    private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
+            Set<Long> existingReversedTransactionIds) {
+        existingTransactionIds.addAll(account.findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
+    }
+
+    private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
+            final Set<Long> existingReversedTransactionIds) {
+
+        final MonetaryCurrency currency = savingsAccount.getCurrency();
+        final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
+        boolean isAccountTransfer = false;
+        final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
+                existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
+        this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
+    }
+
+    @Override
+    public CommandProcessingResult inactivateCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+
+        this.context.authenticatedUser();
+
+        final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
+                .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId);
+
+        final SavingsAccount account = savingsAccountCharge.savingsAccount();
+        this.savingAccountAssembler.assignSavingAccountHelpers(account);
+
+        final LocalDate inactivationOnDate = DateUtils.getLocalDateOfTenant();
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+        /***
+         * Only recurring fees are allowed to inactivate
+         */
+        if (!savingsAccountCharge.isRecurringFee()) {
+            baseDataValidator.reset().parameter(null).value(savingsAccountCharge.getId())
+                    .failWithCodeNoParameterAddedToErrorCode("charge.inactivation.allowed.only.for.recurring.charges");
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+        } else {
+            final LocalDate nextDueDate = savingsAccountCharge.getNextDueDateFrom(inactivationOnDate);
+
+            if (savingsAccountCharge.isChargeIsDue(nextDueDate)) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("inactivation.of.charge.not.allowed.when.charge.is.due");
+                if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+            } else if (savingsAccountCharge.isChargeIsOverPaid(nextDueDate)) {
+
+                final List<SavingsAccountTransaction> chargePayments = new ArrayList<>();
+                SavingsAccountCharge updatedCharge = savingsAccountCharge;
+                do {
+                    chargePayments.clear();
+                    for (SavingsAccountTransaction transaction : account.getTransactions()) {
+                        if (transaction.isPayCharge() && transaction.isNotReversed()
+                                && transaction.isPaymentForCurrentCharge(savingsAccountCharge)) {
+                            chargePayments.add(transaction);
+                        }
+                    }
+                    /***
+                     * Reverse the excess payments of charge transactions
+                     */
+                    SavingsAccountTransaction lastChargePayment = getLastChargePayment(chargePayments);
+                    this.undoTransaction(savingsAccountCharge.savingsAccount().getId(), lastChargePayment.getId(), false);
+                    updatedCharge = account.getUpdatedChargeDetails(savingsAccountCharge);
+                } while (updatedCharge.isChargeIsOverPaid(nextDueDate));
+            }
+            account.inactivateCharge(savingsAccountCharge, inactivationOnDate);
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsAccountCharge.getId()) //
+                .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
+                .withClientId(savingsAccountCharge.savingsAccount().clientId()) //
+                .withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
+                .withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
+                .build();
+    }
+
+    private SavingsAccountTransaction getLastChargePayment(final List<SavingsAccountTransaction> chargePayments) {
+        if (!CollectionUtils.isEmpty(chargePayments)) { return chargePayments.get(chargePayments.size() - 1); }
+        return null;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult assignFieldOfficer(Long savingsAccountId, JsonCommand command) {
+        this.context.authenticatedUser();
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(5);
+
+        Staff fromSavingsOfficer = null;
+        Staff toSavingsOfficer = null;
+        this.fromApiJsonDeserializer.validateForAssignSavingsOfficer(command.json());
+
+        final SavingsAccount savingsForUpdate = this.savingsRepository.findOneWithNotFoundDetection(savingsAccountId);
+        final Long fromSavingsOfficerId = command.longValueOfParameterNamed("fromSavingsOfficerId");
+        final Long toSavingsOfficerId = command.longValueOfParameterNamed("toSavingsOfficerId");
+        final LocalDate dateOfSavingsOfficerAssignment = command.localDateValueOfParameterNamed("assignmentDate");
+
+        if (fromSavingsOfficerId != null) {
+            fromSavingsOfficer = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(fromSavingsOfficerId,
+                    savingsForUpdate.office().getHierarchy());
+        }
+        if (toSavingsOfficerId != null) {
+            toSavingsOfficer = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(toSavingsOfficerId,
+                    savingsForUpdate.office().getHierarchy());
+            actualChanges.put("toSavingsOfficerId", toSavingsOfficer.getId());
+        }
+        if (!savingsForUpdate.hasSavingsOfficer(
+                fromSavingsOfficer)) { throw new SavingsOfficerAssignmentException(savingsAccountId, fromSavingsOfficerId); }
+
+        savingsForUpdate.reassignSavingsOfficer(toSavingsOfficer, dateOfSavingsOfficerAssignment);
+
+        this.savingsRepository.saveAndFlush(savingsForUpdate);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(savingsForUpdate.officeId()) //
+                .withEntityId(savingsForUpdate.getId()) //
+                .withSavingsId(savingsAccountId) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult unassignFieldOfficer(Long savingsAccountId, JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(5);
+        this.fromApiJsonDeserializer.validateForUnAssignSavingsOfficer(command.json());
+
+        final SavingsAccount savingsForUpdate = this.savingsRepository.findOneWithNotFoundDetection(savingsAccountId);
+        if (savingsForUpdate.getSavingsOfficer() == null) { throw new SavingsOfficerUnassignmentException(savingsAccountId); }
+
+        final LocalDate dateOfSavingsOfficerUnassigned = command.localDateValueOfParameterNamed("unassignedDate");
+
+        savingsForUpdate.removeSavingsOfficer(dateOfSavingsOfficerUnassigned);
+
+        this.savingsRepository.saveAndFlush(savingsForUpdate);
+
+        actualChanges.put("toSavingsOfficerId", null);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withOfficeId(savingsForUpdate.officeId()) //
+                .withEntityId(savingsForUpdate.getId()) //
+                .withSavingsId(savingsAccountId) //
+                .with(actualChanges) //
+                .build();
+    }
+
+    private AppUser getAppUserIfPresent() {
+        AppUser user = null;
+        if (this.context != null) {
+            user = this.context.getAuthenticatedUserIfPresent();
+        }
+        return user;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformService.java
new file mode 100644
index 0000000..fa2dbfa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformService.java
@@ -0,0 +1,42 @@
+/**
+ * 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.savings.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountDataDTO;
+
+public interface SavingsApplicationProcessWritePlatformService {
+
+    CommandProcessingResult submitApplication(JsonCommand command);
+
+    CommandProcessingResult modifyApplication(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult deleteApplication(Long savingsId);
+
+    CommandProcessingResult approveApplication(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult undoApplicationApproval(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult rejectApplication(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult applicantWithdrawsFromApplication(Long savingsId, JsonCommand command);
+
+    CommandProcessingResult createActiveApplication(SavingsAccountDataDTO savingsAccountDataDTO);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..32e763d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,499 @@
+/**
+ * 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.savings.service;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandProcessingService;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepository;
+import org.apache.fineract.portfolio.group.exception.CenterNotActiveException;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountDataDTO;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountDataValidator;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountCharge;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargeAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
+import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
+import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
+import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl implements SavingsApplicationProcessWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final SavingsAccountRepositoryWrapper savingAccountRepository;
+    private final SavingsAccountAssembler savingAccountAssembler;
+    private final SavingsAccountDataValidator savingsAccountDataValidator;
+    private final AccountNumberGenerator accountNumberGenerator;
+    private final ClientRepositoryWrapper clientRepository;
+    private final GroupRepository groupRepository;
+    private final SavingsProductRepository savingsProductRepository;
+    private final NoteRepository noteRepository;
+    private final StaffRepositoryWrapper staffRepository;
+    private final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator;
+    private final SavingsAccountChargeAssembler savingsAccountChargeAssembler;
+    private final CommandProcessingService commandProcessingService;
+    private final SavingsAccountDomainService savingsAccountDomainService;
+    private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
+    private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
+
+    @Autowired
+    public SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final SavingsAccountRepositoryWrapper savingAccountRepository, final SavingsAccountAssembler savingAccountAssembler,
+            final SavingsAccountDataValidator savingsAccountDataValidator, final AccountNumberGenerator accountNumberGenerator,
+            final ClientRepositoryWrapper clientRepository, final GroupRepository groupRepository,
+            final SavingsProductRepository savingsProductRepository, final NoteRepository noteRepository,
+            final StaffRepositoryWrapper staffRepository,
+            final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator,
+            final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final CommandProcessingService commandProcessingService,
+            final SavingsAccountDomainService savingsAccountDomainService,
+            final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
+            final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository) {
+        this.context = context;
+        this.savingAccountRepository = savingAccountRepository;
+        this.savingAccountAssembler = savingAccountAssembler;
+        this.accountNumberGenerator = accountNumberGenerator;
+        this.savingsAccountDataValidator = savingsAccountDataValidator;
+        this.clientRepository = clientRepository;
+        this.groupRepository = groupRepository;
+        this.savingsProductRepository = savingsProductRepository;
+        this.noteRepository = noteRepository;
+        this.staffRepository = staffRepository;
+        this.savingsAccountApplicationTransitionApiJsonValidator = savingsAccountApplicationTransitionApiJsonValidator;
+        this.savingsAccountChargeAssembler = savingsAccountChargeAssembler;
+        this.commandProcessingService = commandProcessingService;
+        this.savingsAccountDomainService = savingsAccountDomainService;
+        this.accountNumberFormatRepository = accountNumberFormatRepository;
+        this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataAccessException dve) {
+
+        final StringBuilder errorCodeBuilder = new StringBuilder("error.msg.").append(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("sa_account_no_UNIQUE")) {
+            final String accountNo = command.stringValueOfParameterNamed("accountNo");
+            errorCodeBuilder.append(".duplicate.accountNo");
+            throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Savings account with accountNo " + accountNo
+                    + " already exists", "accountNo", accountNo);
+
+        } else if (realCause.getMessage().contains("sa_externalid_UNIQUE")) {
+
+            final String externalId = command.stringValueOfParameterNamed("externalId");
+            errorCodeBuilder.append(".duplicate.externalId");
+            throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Savings account with externalId " + externalId
+                    + " already exists", "externalId", externalId);
+        }
+
+        errorCodeBuilder.append(".unknown.data.integrity.issue");
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Unknown data integrity issue with savings account.");
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult submitApplication(final JsonCommand command) {
+        try {
+            this.savingsAccountDataValidator.validateForSubmit(command.json());
+            final AppUser submittedBy = this.context.authenticatedUser();
+
+            final SavingsAccount account = this.savingAccountAssembler.assembleFrom(command, submittedBy);
+            this.savingAccountRepository.save(account);
+
+            generateAccountNumber(account);
+
+            final Long savingsId = account.getId();
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(savingsId) //
+                    .withOfficeId(account.officeId()) //
+                    .withClientId(account.clientId()) //
+                    .withGroupId(account.groupId()) //
+                    .withSavingsId(savingsId) //
+                    .build();
+        } catch (final DataAccessException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private void generateAccountNumber(final SavingsAccount account) {
+        if (account.isAccountNumberRequiresAutoGeneration()) {
+            final AccountNumberFormat accountNumberFormat = this.accountNumberFormatRepository.findByAccountType(EntityAccountType.SAVINGS);
+            account.updateAccountNo(this.accountNumberGenerator.generate(account, accountNumberFormat));
+
+            this.savingAccountRepository.save(account);
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult modifyApplication(final Long savingsId, final JsonCommand command) {
+        try {
+            this.savingsAccountDataValidator.validateForUpdate(command.json());
+
+            final Map<String, Object> changes = new LinkedHashMap<>(20);
+
+            final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+            checkClientOrGroupActive(account);
+            account.modifyApplication(command, changes);
+            account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME);
+            account.validateAccountValuesWithProduct();
+
+            if (!changes.isEmpty()) {
+
+                if (changes.containsKey(SavingsApiConstants.clientIdParamName)) {
+                    final Long clientId = command.longValueOfParameterNamed(SavingsApiConstants.clientIdParamName);
+                    if (clientId != null) {
+                        final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+                        if (client.isNotActive()) { throw new ClientNotActiveException(clientId); }
+                        account.update(client);
+                    } else {
+                        final Client client = null;
+                        account.update(client);
+                    }
+                }
+
+                if (changes.containsKey(SavingsApiConstants.groupIdParamName)) {
+                    final Long groupId = command.longValueOfParameterNamed(SavingsApiConstants.groupIdParamName);
+                    if (groupId != null) {
+                        final Group group = this.groupRepository.findOne(groupId);
+                        if (group == null) { throw new GroupNotFoundException(groupId); }
+                        if (group.isNotActive()) {
+                            if (group.isCenter()) { throw new CenterNotActiveException(groupId); }
+                            throw new GroupNotActiveException(groupId);
+                        }
+                        account.update(group);
+                    } else {
+                        final Group group = null;
+                        account.update(group);
+                    }
+                }
+
+                if (changes.containsKey(SavingsApiConstants.productIdParamName)) {
+                    final Long productId = command.longValueOfParameterNamed(SavingsApiConstants.productIdParamName);
+                    final SavingsProduct product = this.savingsProductRepository.findOne(productId);
+                    if (product == null) { throw new SavingsProductNotFoundException(productId); }
+
+                    account.update(product);
+                }
+
+                if (changes.containsKey(SavingsApiConstants.fieldOfficerIdParamName)) {
+                    final Long fieldOfficerId = command.longValueOfParameterNamed(SavingsApiConstants.fieldOfficerIdParamName);
+                    Staff fieldOfficer = null;
+                    if (fieldOfficerId != null) {
+                        fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId);
+                    } else {
+                        changes.put(SavingsApiConstants.fieldOfficerIdParamName, "");
+                    }
+                    account.update(fieldOfficer);
+                }
+
+                if (changes.containsKey("charges")) {
+                    final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson(),
+                            account.getCurrency().getCode());
+                    final boolean updated = account.update(charges);
+                    if (!updated) {
+                        changes.remove("charges");
+                    }
+                }
+
+                this.savingAccountRepository.saveAndFlush(account);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(savingsId) //
+                    .withOfficeId(account.officeId()) //
+                    .withClientId(account.clientId()) //
+                    .withGroupId(account.groupId()) //
+                    .withSavingsId(savingsId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataAccessException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return new CommandProcessingResult(Long.valueOf(-1));
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteApplication(final Long savingsId) {
+
+        final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(account);
+
+        if (account.isNotSubmittedAndPendingApproval()) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                    .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.deleteApplicationAction);
+
+            baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName)
+                    .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        }
+
+        final List<Note> relatedNotes = this.noteRepository.findBySavingsAccountId(savingsId);
+        this.noteRepository.deleteInBatch(relatedNotes);
+
+        this.savingAccountRepository.delete(account);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(savingsId) //
+                .withOfficeId(account.officeId()) //
+                .withClientId(account.clientId()) //
+                .withGroupId(account.groupId()) //
+                .withSavingsId(savingsId) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult approveApplication(final Long savingsId, final JsonCommand command) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        this.savingsAccountApplicationTransitionApiJsonValidator.validateApproval(command.json());
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Map<String, Object> changes = savingsAccount.approveApplication(currentUser, command, DateUtils.getLocalDateOfTenant());
+        if (!changes.isEmpty()) {
+            this.savingAccountRepository.save(savingsAccount);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.savingNote(savingsAccount, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(savingsId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult undoApplicationApproval(final Long savingsId, final JsonCommand command) {
+
+        this.context.authenticatedUser();
+
+        this.savingsAccountApplicationTransitionApiJsonValidator.validateForUndo(command.json());
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Map<String, Object> changes = savingsAccount.undoApplicationApproval();
+        if (!changes.isEmpty()) {
+            this.savingAccountRepository.save(savingsAccount);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.savingNote(savingsAccount, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(savingsId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult rejectApplication(final Long savingsId, final JsonCommand command) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        this.savingsAccountApplicationTransitionApiJsonValidator.validateRejection(command.json());
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Map<String, Object> changes = savingsAccount.rejectApplication(currentUser, command, DateUtils.getLocalDateOfTenant());
+        if (!changes.isEmpty()) {
+            this.savingAccountRepository.save(savingsAccount);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.savingNote(savingsAccount, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(savingsId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult applicantWithdrawsFromApplication(final Long savingsId, final JsonCommand command) {
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        this.savingsAccountApplicationTransitionApiJsonValidator.validateApplicantWithdrawal(command.json());
+
+        final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId);
+        checkClientOrGroupActive(savingsAccount);
+
+        final Map<String, Object> changes = savingsAccount.applicantWithdrawsFromApplication(currentUser, command,
+                DateUtils.getLocalDateOfTenant());
+        if (!changes.isEmpty()) {
+            this.savingAccountRepository.save(savingsAccount);
+
+            final String noteText = command.stringValueOfParameterNamed("note");
+            if (StringUtils.isNotBlank(noteText)) {
+                final Note note = Note.savingNote(savingsAccount, noteText);
+                changes.put("note", noteText);
+                this.noteRepository.save(note);
+            }
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(savingsId) //
+                .withOfficeId(savingsAccount.officeId()) //
+                .withClientId(savingsAccount.clientId()) //
+                .withGroupId(savingsAccount.groupId()) //
+                .withSavingsId(savingsId) //
+                .with(changes) //
+                .build();
+    }
+
+    private void checkClientOrGroupActive(final SavingsAccount account) {
+        final Client client = account.getClient();
+        if (client != null) {
+            if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); }
+        }
+        final Group group = account.group();
+        if (group != null) {
+            if (group.isNotActive()) {
+                if (group.isCenter()) { throw new CenterNotActiveException(group.getId()); }
+                throw new GroupNotActiveException(group.getId());
+            }
+        }
+    }
+
+    @Override
+    public CommandProcessingResult createActiveApplication(final SavingsAccountDataDTO savingsAccountDataDTO) {
+
+        final CommandWrapper commandWrapper = new CommandWrapperBuilder().savingsAccountActivation(null).build();
+        boolean rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, savingsAccountDataDTO.getAppliedBy());
+
+        final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsAccountDataDTO.getClient(),
+                savingsAccountDataDTO.getGroup(), savingsAccountDataDTO.getSavingsProduct(), savingsAccountDataDTO.getApplicationDate(),
+                savingsAccountDataDTO.getAppliedBy());
+        account.approveAndActivateApplication(savingsAccountDataDTO.getApplicationDate().toDate(), savingsAccountDataDTO.getAppliedBy());
+        Money amountForDeposit = account.activateWithBalance();
+
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+
+        if (amountForDeposit.isGreaterThanZero()) {
+            this.savingAccountRepository.save(account);
+        }
+        this.savingsAccountWritePlatformService.processPostActiveActions(account, savingsAccountDataDTO.getFmt(), existingTransactionIds,
+                existingReversedTransactionIds);
+        this.savingAccountRepository.save(account);
+
+        generateAccountNumber(account);
+        // post journal entries for activation charges
+        this.savingsAccountDomainService.postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
+
+        return new CommandProcessingResultBuilder() //
+                .withSavingsId(account.getId()) //
+                .setRollbackTransaction(rollbackTransaction)//
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformService.java
new file mode 100644
index 0000000..57a150d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformService.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.savings.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public interface SavingsDropdownReadPlatformService {
+
+    Collection<EnumOptionData> retrieveLockinPeriodFrequencyTypeOptions();
+
+    Collection<EnumOptionData> retrieveCompoundingInterestPeriodTypeOptions();
+
+    Collection<EnumOptionData> retrieveInterestPostingPeriodTypeOptions();
+
+    Collection<EnumOptionData> retrieveInterestCalculationTypeOptions();
+
+    Collection<EnumOptionData> retrieveInterestCalculationDaysInYearTypeOptions();
+
+    Collection<EnumOptionData> retrievewithdrawalFeeTypeOptions();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java
new file mode 100644
index 0000000..2570424
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java
@@ -0,0 +1,111 @@
+/**
+ * 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.savings.service;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsWithdrawalFeesType;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownReadPlatformService {
+
+    @Override
+    public Collection<EnumOptionData> retrievewithdrawalFeeTypeOptions() {
+        final List<EnumOptionData> allowedOptions = Arrays.asList( //
+                SavingsEnumerations.withdrawalFeeType(SavingsWithdrawalFeesType.FLAT), //
+                SavingsEnumerations.withdrawalFeeType(SavingsWithdrawalFeesType.PERCENT_OF_AMOUNT) //
+                );
+
+        return allowedOptions;
+    }
+
+    @Override
+    public List<EnumOptionData> retrieveLockinPeriodFrequencyTypeOptions() {
+        final List<EnumOptionData> allowedLockinPeriodFrequencyTypeOptions = Arrays.asList( //
+                SavingsEnumerations.lockinPeriodFrequencyType(SavingsPeriodFrequencyType.DAYS), //
+                SavingsEnumerations.lockinPeriodFrequencyType(SavingsPeriodFrequencyType.WEEKS), //
+                SavingsEnumerations.lockinPeriodFrequencyType(SavingsPeriodFrequencyType.MONTHS), //
+                SavingsEnumerations.lockinPeriodFrequencyType(SavingsPeriodFrequencyType.YEARS) //
+                );
+
+        return allowedLockinPeriodFrequencyTypeOptions;
+    }
+
+    @Override
+    public Collection<EnumOptionData> retrieveCompoundingInterestPeriodTypeOptions() {
+        final List<EnumOptionData> allowedOptions = Arrays.asList(
+                //
+                SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.DAILY), //
+                // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.WEEKLY),
+                // //
+                // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BIWEEKLY),
+                // //
+                SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.MONTHLY),
+                SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.QUATERLY),
+                SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BI_ANNUAL),
+                SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.ANNUAL)
+        // //
+        // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST)
+        // //
+                );
+
+        return allowedOptions;
+    }
+
+    @Override
+    public Collection<EnumOptionData> retrieveInterestPostingPeriodTypeOptions() {
+        final List<EnumOptionData> allowedOptions = Arrays.asList( //
+                SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.MONTHLY), //
+                SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.QUATERLY), //
+                SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.BIANNUAL), //
+                SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.ANNUAL) //
+                );
+
+        return allowedOptions;
+    }
+
+    @Override
+    public Collection<EnumOptionData> retrieveInterestCalculationTypeOptions() {
+        final List<EnumOptionData> allowedOptions = Arrays.asList( //
+                SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType.DAILY_BALANCE), //
+                SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE) //
+                );
+
+        return allowedOptions;
+    }
+
+    @Override
+    public Collection<EnumOptionData> retrieveInterestCalculationDaysInYearTypeOptions() {
+        final List<EnumOptionData> allowedOptions = Arrays.asList( //
+                SavingsEnumerations.interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_360), //
+                SavingsEnumerations.interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_365) //
+                );
+
+        return allowedOptions;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java
new file mode 100644
index 0000000..ae40555
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java
@@ -0,0 +1,769 @@
+/**
+ * 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.savings.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
+import org.apache.fineract.portfolio.savings.DepositAccountOnHoldTransactionType;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType;
+import org.apache.fineract.portfolio.savings.RecurringDepositType;
+import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsWithdrawalFeesType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+
+public class SavingsEnumerations {
+
+    public static final String INTEREST_COMPOUNDING_PERIOD_TYPE = "interestCompoundingPeriodType";
+    public static final String INTEREST_POSTING_PERIOD_TYPE = "interestPostingPeriodType";
+    public static final String INTEREST_CALCULATION_TYPE = "interestCalculationType";
+    public static final String MIN_DEPOSIT_TERM_TYPE = "minDepositTermTypeId";
+    public static final String MAX_DEPOSIT_TERM_TYPE = "maxDepositTermTypeId";
+    public static final String IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE = "inMultiplesOfDepositTermTypeId";
+    public static final String DEPOSIT_PERIOD_FREQUNCY_TYPE = "depositPeriodFrequencyId";
+    public static final String LOCKIN_PERIOD_FREQUNCY_TYPE = "lockinPeriodFrequencyType";
+    public static final String ACCOUNTING_RULE_TYPE = "accountingRule";
+    public static final String PRE_CLOSURE_PENAL_INTEREST_TYPE = "preClosurePenalInterestOnTypeId";
+    public static final String INTEREST_CALCULATION_DAYS_IN_YEAR = "interestCalculationDaysInYearType";
+    public static final String RECURRING_FREQUENCY_TYPE = "recurringFrequencyType";
+
+    public static EnumOptionData savingEnumueration(final String typeName, final int id) {
+        if (typeName.equals(INTEREST_COMPOUNDING_PERIOD_TYPE)) {
+            return compoundingInterestPeriodType(id);
+        } else if (typeName.equals(INTEREST_POSTING_PERIOD_TYPE)) {
+            return interestPostingPeriodType(id);
+        } else if (typeName.equals(INTEREST_CALCULATION_TYPE)) {
+            return interestCalculationType(id);
+        } else if (typeName.equals(MIN_DEPOSIT_TERM_TYPE)) {
+            return depositTermFrequencyType(id);
+        } else if (typeName.equals(MAX_DEPOSIT_TERM_TYPE)) {
+            return depositTermFrequencyType(id);
+        } else if (typeName.equals(IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE)) {
+            return inMultiplesOfDepositTermFrequencyType(id);
+        } else if (typeName.equals(DEPOSIT_PERIOD_FREQUNCY_TYPE)) {
+            return depositPeriodFrequency(id);
+        } else if (typeName.equals(LOCKIN_PERIOD_FREQUNCY_TYPE)) {
+            return lockinPeriodFrequencyType(id);
+        } else if (typeName.equals(ACCOUNTING_RULE_TYPE)) {
+            return AccountingEnumerations.accountingRuleType(id);
+        } else if (typeName.equals(PRE_CLOSURE_PENAL_INTEREST_TYPE)) {
+            return preClosurePenaltyInterestOnType(id);
+        } else if (typeName.equals(INTEREST_CALCULATION_DAYS_IN_YEAR)) {
+            return interestCalculationDaysInYearType(id);
+        } else if (typeName.equals(RECURRING_FREQUENCY_TYPE)) { return depositPeriodFrequency(id); }
+        return null;
+    }
+
+    public static EnumOptionData lockinPeriodFrequencyType(final int id) {
+        return lockinPeriodFrequencyType(SavingsPeriodFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData lockinPeriodFrequencyType(final SavingsPeriodFrequencyType type) {
+        final String codePrefix = "savings.lockin.";
+        EnumOptionData optionData = new EnumOptionData(SavingsPeriodFrequencyType.INVALID.getValue().longValue(),
+                SavingsPeriodFrequencyType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case INVALID:
+            break;
+            case DAYS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+        }
+        return optionData;
+    }
+
+    public static SavingsAccountTransactionEnumData transactionType(final int transactionType) {
+        return transactionType(SavingsAccountTransactionType.fromInt(transactionType));
+    }
+
+    public static SavingsAccountTransactionEnumData transactionType(final SavingsAccountTransactionType type) {
+
+        SavingsAccountTransactionEnumData optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.INVALID
+                .getValue().longValue(), SavingsAccountTransactionType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.INVALID.getValue().longValue(),
+                        SavingsAccountTransactionType.INVALID.getCode(), "Invalid");
+            break;
+            case DEPOSIT:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.DEPOSIT.getValue().longValue(),
+                        SavingsAccountTransactionType.DEPOSIT.getCode(), "Deposit");
+            break;
+            case WITHDRAWAL:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WITHDRAWAL.getValue().longValue(),
+                        SavingsAccountTransactionType.WITHDRAWAL.getCode(), "Withdrawal");
+            break;
+            case INTEREST_POSTING:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.INTEREST_POSTING.getValue().longValue(),
+                        SavingsAccountTransactionType.INTEREST_POSTING.getCode(), "Interest posting");
+            break;
+            case WITHDRAWAL_FEE:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue().longValue(),
+                        SavingsAccountTransactionType.WITHDRAWAL_FEE.getCode(), "Withdrawal fee");
+            break;
+            case ANNUAL_FEE:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.ANNUAL_FEE.getValue().longValue(),
+                        SavingsAccountTransactionType.ANNUAL_FEE.getCode(), "Annual fee");
+            break;
+            case APPROVE_TRANSFER:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.APPROVE_TRANSFER.getValue().longValue(),
+                        SavingsAccountTransactionType.APPROVE_TRANSFER.getCode(), "Transfer approved");
+            break;
+            case INITIATE_TRANSFER:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.INITIATE_TRANSFER.getValue().longValue(),
+                        SavingsAccountTransactionType.INITIATE_TRANSFER.getCode(), "Transfer initiated");
+            break;
+            case REJECT_TRANSFER:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.REJECT_TRANSFER.getValue().longValue(),
+                        SavingsAccountTransactionType.REJECT_TRANSFER.getCode(), "Transfer Rejected");
+            break;
+            case WITHDRAW_TRANSFER:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue().longValue(),
+                        SavingsAccountTransactionType.WITHDRAW_TRANSFER.getCode(), "Transfer Withdrawn");
+            break;
+            default:
+            case PAY_CHARGE:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.PAY_CHARGE.getValue().longValue(),
+                        SavingsAccountTransactionType.PAY_CHARGE.getCode(), "Pay Charge");
+            break;
+            case WAIVE_CHARGES:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WAIVE_CHARGES.getValue().longValue(),
+                        SavingsAccountTransactionType.WAIVE_CHARGES.getCode(), "Waive Charge");
+            break;
+            case WRITTEN_OFF:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WRITTEN_OFF.getValue().longValue(),
+                        SavingsAccountTransactionType.WRITTEN_OFF.getCode(), "writtenoff");
+            break;
+            case OVERDRAFT_INTEREST:
+                optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue().longValue(),
+                        SavingsAccountTransactionType.OVERDRAFT_INTEREST.getCode(), "Overdraft Interest");
+            break;
+        }
+        return optionData;
+    }
+
+    public static SavingsAccountStatusEnumData status(final Integer statusEnum) {
+        return status(SavingsAccountStatusType.fromInt(statusEnum));
+    }
+
+    public static SavingsAccountStatusEnumData status(final SavingsAccountStatusType type) {
+
+        final boolean submittedAndPendingApproval = type.isSubmittedAndPendingApproval();
+        final boolean isApproved = type.isApproved();
+        final boolean isRejected = type.isRejected();
+        final boolean isWithdrawnByApplicant = type.isApplicationWithdrawnByApplicant();
+        final boolean isActive = type.isActive();
+        final boolean isClosed = type.isClosed();
+        final boolean isPrematureClosed = type.isPreMatureClosure();
+        final boolean isTransferInProgress = type.isTransferInProgress();
+        final boolean isTransferOnHold = type.isTransferOnHold();
+
+        SavingsAccountStatusEnumData optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.INVALID.getValue().longValue(),
+                SavingsAccountStatusType.INVALID.getCode(), "Invalid", submittedAndPendingApproval, isApproved, isRejected,
+                isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold);
+
+        switch (type) {
+            case INVALID:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.INVALID.getValue().longValue(),
+                        SavingsAccountStatusType.INVALID.getCode(), "Invalid", submittedAndPendingApproval, isApproved, isRejected,
+                        isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold);
+            break;
+            case SUBMITTED_AND_PENDING_APPROVAL:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue()
+                        .longValue(), SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval",
+                        submittedAndPendingApproval, isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed,
+                        isTransferInProgress, isTransferOnHold);
+            break;
+            case REJECTED:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.REJECTED.getValue().longValue(),
+                        SavingsAccountStatusType.REJECTED.getCode(), "Rejected", submittedAndPendingApproval, isApproved, isRejected,
+                        isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold);
+            break;
+            case WITHDRAWN_BY_APPLICANT:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue().longValue(),
+                        SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getCode(), "Withdrawn by applicant", submittedAndPendingApproval,
+                        isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress,
+                        isTransferOnHold);
+            break;
+            case APPROVED:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.APPROVED.getValue().longValue(),
+                        SavingsAccountStatusType.APPROVED.getCode(), "Approved", submittedAndPendingApproval, isApproved, isRejected,
+                        isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold);
+            break;
+            case ACTIVE:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.ACTIVE.getValue().longValue(),
+                        SavingsAccountStatusType.ACTIVE.getCode(), "Active", submittedAndPendingApproval, isApproved, isRejected,
+                        isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold);
+            break;
+            case CLOSED:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.CLOSED.getValue().longValue(),
+                        SavingsAccountStatusType.CLOSED.getCode(), "Closed", submittedAndPendingApproval, isApproved, isRejected,
+                        isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold);
+            break;
+            case TRANSFER_IN_PROGRESS:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue().longValue(),
+                        SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getCode(), "Transfer in progress", submittedAndPendingApproval,
+                        isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress,
+                        isTransferOnHold);
+            break;
+            case TRANSFER_ON_HOLD:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.TRANSFER_ON_HOLD.getValue().longValue(),
+                        SavingsAccountStatusType.TRANSFER_ON_HOLD.getCode(), "Transfer on hold", submittedAndPendingApproval,
+                        isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress,
+                        isTransferOnHold);
+            break;
+            case PRE_MATURE_CLOSURE:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue().longValue(),
+                        SavingsAccountStatusType.PRE_MATURE_CLOSURE.getCode(), "Premature Closed", submittedAndPendingApproval, isApproved,
+                        isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold);
+            break;
+            case MATURED:
+                optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.MATURED.getValue().longValue(),
+                        SavingsAccountStatusType.MATURED.getCode(), "Matured", submittedAndPendingApproval, isApproved, isRejected,
+                        isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold);
+            break;
+            default:
+            break;
+        }
+        return optionData;
+    }
+
+    public static EnumOptionData interestPostingPeriodType(final Integer type) {
+        return interestPostingPeriodType(SavingsPostingInterestPeriodType.fromInt(type));
+    }
+
+    public static EnumOptionData interestPostingPeriodType(final SavingsPostingInterestPeriodType type) {
+
+        final String codePrefix = "savings.interest.posting.period.";
+        EnumOptionData optionData = new EnumOptionData(SavingsPostingInterestPeriodType.INVALID.getValue().longValue(),
+                SavingsPostingInterestPeriodType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case MONTHLY:
+                optionData = new EnumOptionData(SavingsPostingInterestPeriodType.MONTHLY.getValue().longValue(), codePrefix
+                        + SavingsPostingInterestPeriodType.MONTHLY.getCode(), "Monthly");
+            break;
+            case QUATERLY:
+                optionData = new EnumOptionData(SavingsPostingInterestPeriodType.QUATERLY.getValue().longValue(), codePrefix
+                        + SavingsPostingInterestPeriodType.QUATERLY.getCode(), "Quarterly");
+            break;
+            case BIANNUAL:
+                optionData = new EnumOptionData(SavingsPostingInterestPeriodType.BIANNUAL.getValue().longValue(), codePrefix
+                        + SavingsPostingInterestPeriodType.BIANNUAL.getCode(), "BiAnnual");
+            break;
+            case ANNUAL:
+                optionData = new EnumOptionData(SavingsPostingInterestPeriodType.ANNUAL.getValue().longValue(), codePrefix
+                        + SavingsPostingInterestPeriodType.ANNUAL.getCode(), "Annually");
+            break;
+        }
+
+        return optionData;
+    }
+
+    public static EnumOptionData compoundingInterestPeriodType(final Integer type) {
+        return compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.fromInt(type));
+    }
+
+    public static EnumOptionData compoundingInterestPeriodType(final SavingsCompoundingInterestPeriodType type) {
+
+        final String codePrefix = "savings.interest.period.";
+        EnumOptionData optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.INVALID.getValue().longValue(),
+                SavingsCompoundingInterestPeriodType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case DAILY:
+                optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.DAILY.getValue().longValue(), codePrefix
+                        + SavingsCompoundingInterestPeriodType.DAILY.getCode(), "Daily");
+            break;
+            // case WEEKLY:
+            // optionData = new
+            // EnumOptionData(SavingsCompoundingInterestPeriodType.WEEKLY.getValue().longValue(),
+            // codePrefix
+            // + SavingsCompoundingInterestPeriodType.WEEKLY.getCode(),
+            // "Weekly");
+            // break;
+            // case BIWEEKLY:
+            // optionData = new
+            // EnumOptionData(SavingsCompoundingInterestPeriodType.BIWEEKLY.getValue().longValue(),
+            // codePrefix
+            // + SavingsCompoundingInterestPeriodType.BIWEEKLY.getCode(),
+            // "Bi-Weekly");
+            // break;
+            case MONTHLY:
+                optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.MONTHLY.getValue().longValue(), codePrefix
+                        + SavingsCompoundingInterestPeriodType.MONTHLY.getCode(), "Monthly");
+            break;
+            case QUATERLY:
+                optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.QUATERLY.getValue().longValue(), codePrefix
+                        + SavingsCompoundingInterestPeriodType.QUATERLY.getCode(), "Quarterly");
+            break;
+            case BI_ANNUAL:
+                optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.BI_ANNUAL.getValue().longValue(), codePrefix
+                        + SavingsCompoundingInterestPeriodType.BI_ANNUAL.getCode(), "Semi-Annual");
+            break;
+            case ANNUAL:
+                optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.ANNUAL.getValue().longValue(), codePrefix
+                        + SavingsCompoundingInterestPeriodType.ANNUAL.getCode(), "Annually");
+            break;
+        // case NO_COMPOUNDING_SIMPLE_INTEREST:
+        // optionData = new
+        // EnumOptionData(SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST.getValue().longValue(),
+        // codePrefix +
+        // SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST.getCode(),
+        // "No Compounding - Simple Interest");
+        // break;
+        }
+
+        return optionData;
+    }
+
+    public static EnumOptionData interestCalculationType(final Integer type) {
+        return interestCalculationType(SavingsInterestCalculationType.fromInt(type));
+    }
+
+    public static EnumOptionData interestCalculationType(final SavingsInterestCalculationType type) {
+
+        EnumOptionData optionData = new EnumOptionData(SavingsInterestCalculationType.INVALID.getValue().longValue(),
+                SavingsInterestCalculationType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case DAILY_BALANCE:
+                optionData = new EnumOptionData(SavingsInterestCalculationType.DAILY_BALANCE.getValue().longValue(),
+                        SavingsInterestCalculationType.DAILY_BALANCE.getCode(), "Daily Balance");
+            break;
+            case AVERAGE_DAILY_BALANCE:
+                optionData = new EnumOptionData(SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE.getValue().longValue(),
+                        SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE.getCode(), "Average Daily Balance");
+            break;
+        }
+
+        return optionData;
+    }
+
+    public static EnumOptionData interestCalculationDaysInYearType(final Integer type) {
+        return interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.fromInt(type));
+    }
+
+    public static EnumOptionData interestCalculationDaysInYearType(final SavingsInterestCalculationDaysInYearType type) {
+        EnumOptionData optionData = new EnumOptionData(SavingsInterestCalculationDaysInYearType.INVALID.getValue().longValue(),
+                SavingsInterestCalculationDaysInYearType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case DAYS_360:
+                optionData = new EnumOptionData(SavingsInterestCalculationDaysInYearType.DAYS_360.getValue().longValue(),
+                        SavingsInterestCalculationDaysInYearType.DAYS_360.getCode(), "360 Days");
+            break;
+            case DAYS_365:
+                optionData = new EnumOptionData(SavingsInterestCalculationDaysInYearType.DAYS_365.getValue().longValue(),
+                        SavingsInterestCalculationDaysInYearType.DAYS_365.getCode(), "365 Days");
+            break;
+        }
+
+        return optionData;
+    }
+
+    public static EnumOptionData withdrawalFeeType(final Integer type) {
+        return withdrawalFeeType(SavingsWithdrawalFeesType.fromInt(type));
+    }
+
+    public static EnumOptionData withdrawalFeeType(final SavingsWithdrawalFeesType type) {
+        EnumOptionData optionData = new EnumOptionData(SavingsWithdrawalFeesType.INVALID.getValue().longValue(),
+                SavingsWithdrawalFeesType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case FLAT:
+                optionData = new EnumOptionData(SavingsWithdrawalFeesType.FLAT.getValue().longValue(),
+                        SavingsWithdrawalFeesType.FLAT.getCode(), "Flat");
+            break;
+            case PERCENT_OF_AMOUNT:
+                optionData = new EnumOptionData(SavingsWithdrawalFeesType.PERCENT_OF_AMOUNT.getValue().longValue(),
+                        SavingsWithdrawalFeesType.PERCENT_OF_AMOUNT.getCode(), "% of Amount");
+            break;
+        }
+
+        return optionData;
+    }
+
+    public static EnumOptionData preClosurePenaltyInterestOnType(final Integer type) {
+        return preClosurePenaltyInterestOnType(PreClosurePenalInterestOnType.fromInt(type));
+    }
+
+    public static EnumOptionData preClosurePenaltyInterestOnType(final PreClosurePenalInterestOnType type) {
+        EnumOptionData optionData = new EnumOptionData(PreClosurePenalInterestOnType.INVALID.getValue().longValue(),
+                PreClosurePenalInterestOnType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case WHOLE_TERM:
+                optionData = new EnumOptionData(PreClosurePenalInterestOnType.WHOLE_TERM.getValue().longValue(),
+                        PreClosurePenalInterestOnType.WHOLE_TERM.getCode(), "Whole term");
+            break;
+            case TILL_PREMATURE_WITHDRAWAL:
+                optionData = new EnumOptionData(PreClosurePenalInterestOnType.TILL_PREMATURE_WITHDRAWAL.getValue().longValue(),
+                        PreClosurePenalInterestOnType.TILL_PREMATURE_WITHDRAWAL.getCode(), "Till Premature Withdrawal");
+            break;
+        }
+
+        return optionData;
+    }
+
+    public static List<EnumOptionData> preClosurePenaltyInterestOnType(final PreClosurePenalInterestOnType[] types) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final PreClosurePenalInterestOnType type : types) {
+            if (!type.isInvalid()) {
+                optionDatas.add(preClosurePenaltyInterestOnType(type));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData recurringDepositType(final Integer type) {
+        return recurringDepositType(RecurringDepositType.fromInt(type));
+    }
+
+    public static EnumOptionData recurringDepositType(final RecurringDepositType type) {
+        EnumOptionData optionData = new EnumOptionData(RecurringDepositType.INVALID.getValue().longValue(),
+                RecurringDepositType.INVALID.getCode(), "Invalid");
+
+        switch (type) {
+            case INVALID:
+            break;
+            case VOLUNTARY:
+                optionData = new EnumOptionData(RecurringDepositType.VOLUNTARY.getValue().longValue(),
+                        RecurringDepositType.VOLUNTARY.getCode(), "Voluntary");
+            break;
+            case MANDATORY:
+                optionData = new EnumOptionData(RecurringDepositType.MANDATORY.getValue().longValue(),
+                        RecurringDepositType.MANDATORY.getCode(), "Mandatory");
+            break;
+        }
+
+        return optionData;
+    }
+
+    public static List<EnumOptionData> recurringDepositType(final RecurringDepositType[] types) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final RecurringDepositType type : types) {
+            if (!type.isInvalid()) {
+                optionDatas.add(recurringDepositType(type));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData recurringDepositFrequencyType(final int id) {
+        return recurringDepositFrequencyType(SavingsPeriodFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData recurringDepositFrequencyType(final SavingsPeriodFrequencyType type) {
+        final String codePrefix = "recurring.deposit.";
+        EnumOptionData optionData = new EnumOptionData(SavingsPeriodFrequencyType.INVALID.getValue().longValue(),
+                SavingsPeriodFrequencyType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case INVALID:
+            break;
+            case DAYS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+        }
+        return optionData;
+    }
+
+    public static List<EnumOptionData> recurringDepositFrequencyType(final SavingsPeriodFrequencyType[] types) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final SavingsPeriodFrequencyType type : types) {
+            if (!type.isInvalid()) {
+                optionDatas.add(recurringDepositFrequencyType(type));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData depositTermFrequencyType(final int id) {
+        return depositTermFrequencyType(SavingsPeriodFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData depositTermFrequencyType(final SavingsPeriodFrequencyType type) {
+        final String codePrefix = "deposit.term.";
+        EnumOptionData optionData = new EnumOptionData(SavingsPeriodFrequencyType.INVALID.getValue().longValue(),
+                SavingsPeriodFrequencyType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case INVALID:
+            break;
+            case DAYS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+        }
+        return optionData;
+    }
+
+    public static List<EnumOptionData> depositTermFrequencyType(final SavingsPeriodFrequencyType[] types) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final SavingsPeriodFrequencyType type : types) {
+            if (!type.isInvalid()) {
+                optionDatas.add(recurringDepositFrequencyType(type));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData inMultiplesOfDepositTermFrequencyType(final int id) {
+        return depositTermFrequencyType(SavingsPeriodFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData inMultiplesOfDepositTermFrequencyType(final SavingsPeriodFrequencyType type) {
+        final String codePrefix = "inmultiples.of.deposit.term.";
+        EnumOptionData optionData = new EnumOptionData(SavingsPeriodFrequencyType.INVALID.getValue().longValue(),
+                SavingsPeriodFrequencyType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case INVALID:
+            break;
+            case DAYS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+        }
+        return optionData;
+    }
+
+    public static List<EnumOptionData> inMultiplesOfDepositTermFrequencyType(final SavingsPeriodFrequencyType[] types) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final SavingsPeriodFrequencyType type : types) {
+            if (!type.isInvalid()) {
+                optionDatas.add(recurringDepositFrequencyType(type));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData depositType(final int id) {
+        return depositType(DepositAccountType.fromInt(id));
+    }
+
+    public static EnumOptionData depositType(final DepositAccountType type) {
+        EnumOptionData optionData = new EnumOptionData(DepositAccountType.INVALID.getValue().longValue(),
+                DepositAccountType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case INVALID:
+            break;
+            case SAVINGS_DEPOSIT:
+                optionData = new EnumOptionData(DepositAccountType.SAVINGS_DEPOSIT.getValue().longValue(),
+                        DepositAccountType.SAVINGS_DEPOSIT.getCode(), "Savings");
+            break;
+            case FIXED_DEPOSIT:
+                optionData = new EnumOptionData(DepositAccountType.FIXED_DEPOSIT.getValue().longValue(),
+                        DepositAccountType.FIXED_DEPOSIT.getCode(), "Fixed Deposit");
+            break;
+            case RECURRING_DEPOSIT:
+                optionData = new EnumOptionData(DepositAccountType.RECURRING_DEPOSIT.getValue().longValue(),
+                        DepositAccountType.RECURRING_DEPOSIT.getCode(), "Recurring Deposit");
+            break;
+            case CURRENT_DEPOSIT:
+                optionData = new EnumOptionData(DepositAccountType.CURRENT_DEPOSIT.getValue().longValue(),
+                        DepositAccountType.CURRENT_DEPOSIT.getCode(), "Current Deposit");
+            break;
+        }
+        return optionData;
+    }
+
+    public static List<EnumOptionData> depositType(final DepositAccountType[] types) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final DepositAccountType type : types) {
+            if (!type.isInvalid()) {
+                optionDatas.add(depositType(type));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData depositPeriodFrequency(final int id) {
+        return depositPeriodFrequency(SavingsPeriodFrequencyType.fromInt(id));
+    }
+
+    public static EnumOptionData depositPeriodFrequency(final SavingsPeriodFrequencyType type) {
+        final String codePrefix = "deposit.period.";
+        EnumOptionData optionData = new EnumOptionData(SavingsPeriodFrequencyType.INVALID.getValue().longValue(),
+                SavingsPeriodFrequencyType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case INVALID:
+            break;
+            case DAYS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.DAYS.getCode(), "Days");
+            break;
+            case WEEKS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.WEEKS.getCode(), "Weeks");
+            break;
+            case MONTHS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.MONTHS.getCode(), "Months");
+            break;
+            case YEARS:
+                optionData = new EnumOptionData(SavingsPeriodFrequencyType.YEARS.getValue().longValue(), codePrefix
+                        + SavingsPeriodFrequencyType.YEARS.getCode(), "Years");
+            break;
+        }
+        return optionData;
+    }
+
+    public static List<EnumOptionData> depositPeriodFrequency(final SavingsPeriodFrequencyType[] types) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final SavingsPeriodFrequencyType type : types) {
+            if (!type.isInvalid()) {
+                optionDatas.add(recurringDepositFrequencyType(type));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData depositAccountOnClosureType(final int id) {
+        return depositAccountOnClosureType(DepositAccountOnClosureType.fromInt(id));
+    }
+
+    public static EnumOptionData depositAccountOnClosureType(final DepositAccountOnClosureType type) {
+        EnumOptionData optionData = new EnumOptionData(DepositAccountOnClosureType.INVALID.getValue().longValue(),
+                DepositAccountOnClosureType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case INVALID:
+            break;
+            case WITHDRAW_DEPOSIT:
+                optionData = new EnumOptionData(DepositAccountOnClosureType.WITHDRAW_DEPOSIT.getValue().longValue(),
+                        DepositAccountOnClosureType.WITHDRAW_DEPOSIT.getCode(), "Withdraw Deposit");
+            break;
+            case TRANSFER_TO_SAVINGS:
+                optionData = new EnumOptionData(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue().longValue(),
+                        DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getCode(), "Transfer to Savings");
+            break;
+            case REINVEST:
+                optionData = new EnumOptionData(DepositAccountOnClosureType.REINVEST.getValue().longValue(),
+                        DepositAccountOnClosureType.REINVEST.getCode(), "Re-Invest");
+            break;
+        }
+        return optionData;
+    }
+
+    public static List<EnumOptionData> depositAccountOnClosureType(final DepositAccountOnClosureType[] types) {
+        final List<EnumOptionData> optionDatas = new ArrayList<>();
+        for (final DepositAccountOnClosureType type : types) {
+            if (!type.isInvalid()) {
+                optionDatas.add(depositAccountOnClosureType(type));
+            }
+        }
+        return optionDatas;
+    }
+
+    public static EnumOptionData onHoldTransactionType(final int id) {
+        return onHoldTransactionType(DepositAccountOnHoldTransactionType.fromInt(id));
+    }
+
+    public static EnumOptionData onHoldTransactionType(final DepositAccountOnHoldTransactionType type) {
+        EnumOptionData optionData = new EnumOptionData(DepositAccountOnHoldTransactionType.INVALID.getValue().longValue(),
+                DepositAccountType.INVALID.getCode(), "Invalid");
+        switch (type) {
+            case INVALID:
+            break;
+            case HOLD:
+                optionData = new EnumOptionData(DepositAccountOnHoldTransactionType.HOLD.getValue().longValue(),
+                        DepositAccountOnHoldTransactionType.HOLD.getCode(), "hold");
+            break;
+            case RELEASE:
+                optionData = new EnumOptionData(DepositAccountOnHoldTransactionType.RELEASE.getValue().longValue(),
+                        DepositAccountOnHoldTransactionType.RELEASE.getCode(), "release");
+            break;
+
+        }
+        return optionData;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformService.java
new file mode 100644
index 0000000..4172442
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformService.java
@@ -0,0 +1,37 @@
+/**
+ * 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.savings.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+
+public interface SavingsProductReadPlatformService {
+
+    Collection<SavingsProductData> retrieveAll();
+
+    Collection<SavingsProductData> retrieveAllForLookup();
+
+    Collection<SavingsProductData> retrieveAllForLookupByType(Boolean isOverdraftType);
+
+    Collection<SavingsProductData> retrieveAllForCurrency(String currencyCode);
+
+    SavingsProductData retrieveOne(Long productId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java
new file mode 100644
index 0000000..9c82e16
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java
@@ -0,0 +1,272 @@
+/**
+ * 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.savings.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.accounting.common.AccountingEnumerations;
+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.entityaccess.domain.FineractEntityType;
+import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
+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 SavingsProductReadPlatformServiceImpl implements SavingsProductReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final SavingProductMapper savingsProductRowMapper = new SavingProductMapper();
+    private final SavingProductLookupMapper savingsProductLookupsRowMapper = new SavingProductLookupMapper();
+    private final FineractEntityAccessUtil fineractEntityAccessUtil;
+
+    @Autowired
+    public SavingsProductReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+    		final FineractEntityAccessUtil fineractEntityAccessUtil) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.fineractEntityAccessUtil = fineractEntityAccessUtil;
+    }
+
+    @Override
+    public Collection<SavingsProductData> retrieveAll() {
+
+        this.context.authenticatedUser();
+
+        String sql = "select " + this.savingsProductRowMapper.schema() + "where sp.deposit_type_enum = ?";
+        
+		// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
+		String inClause = fineractEntityAccessUtil.
+				getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
+						FineractEntityType.SAVINGS_PRODUCT);
+		if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
+			sql += " and sp.id in ( " + inClause + " ) ";
+		}
+
+        return this.jdbcTemplate.query(sql, this.savingsProductRowMapper, new Object[] { DepositAccountType.SAVINGS_DEPOSIT.getValue() });
+    }
+
+    @Override
+    public Collection<SavingsProductData> retrieveAllForLookup() {
+
+        String sql = "select " + this.savingsProductLookupsRowMapper.schema() + " where sp.deposit_type_enum = ? ";
+        
+        // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
+ 		String inClause = fineractEntityAccessUtil.
+ 				getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
+						FineractEntityType.SAVINGS_PRODUCT);
+    	if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
+    		sql += " and id in ( " + inClause + " ) ";
+    	}
+    
+
+        return this.jdbcTemplate.query(sql, this.savingsProductLookupsRowMapper,
+                new Object[] { DepositAccountType.SAVINGS_DEPOSIT.getValue() });
+    }
+
+    @Override
+    public SavingsProductData retrieveOne(final Long savingProductId) {
+        try {
+            this.context.authenticatedUser();
+            final String sql = "select " + this.savingsProductRowMapper.schema() + " where sp.id = ? and sp.deposit_type_enum = ?";
+            return this.jdbcTemplate.queryForObject(sql, this.savingsProductRowMapper, new Object[] { savingProductId,
+                    DepositAccountType.SAVINGS_DEPOSIT.getValue() });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new SavingsProductNotFoundException(savingProductId);
+        }
+    }
+
+    private static final class SavingProductMapper implements RowMapper<SavingsProductData> {
+
+        private final String schemaSql;
+
+        public SavingProductMapper() {
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("sp.id as id, sp.name as name, sp.short_name as shortName, sp.description as description, ");
+            sqlBuilder
+                    .append("sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, sp.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+            sqlBuilder.append("sp.nominal_annual_interest_rate as nominalAnnualInterestRate, ");
+            sqlBuilder.append("sp.interest_compounding_period_enum as compoundingInterestPeriodType, ");
+            sqlBuilder.append("sp.interest_posting_period_enum as interestPostingPeriodType, ");
+            sqlBuilder.append("sp.interest_calculation_type_enum as interestCalculationType, ");
+            sqlBuilder.append("sp.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, ");
+            sqlBuilder.append("sp.min_required_opening_balance as minRequiredOpeningBalance, ");
+            sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,");
+            sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
+            sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
+            sqlBuilder.append("sp.allow_overdraft as allowOverdraft, ");
+            sqlBuilder.append("sp.overdraft_limit as overdraftLimit, ");
+            sqlBuilder.append("sp.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, ");
+            sqlBuilder.append("sp.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, ");
+            sqlBuilder.append("sp.min_required_balance as minRequiredBalance, ");
+            sqlBuilder.append("sp.enforce_min_required_balance as enforceMinRequiredBalance, ");
+            sqlBuilder.append("sp.min_balance_for_interest_calculation as minBalanceForInterestCalculation,");
+            sqlBuilder.append("sp.accounting_type as accountingType ");
+            sqlBuilder.append("from m_savings_product sp ");
+            sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+            final String shortName = rs.getString("shortName");
+            final String description = rs.getString("description");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+                    currencyDisplaySymbol, currencyNameCode);
+            final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate");
+
+            final Integer compoundingInterestPeriodTypeValue = JdbcSupport.getInteger(rs, "compoundingInterestPeriodType");
+            final EnumOptionData compoundingInterestPeriodType = SavingsEnumerations
+                    .compoundingInterestPeriodType(compoundingInterestPeriodTypeValue);
+
+            final Integer interestPostingPeriodTypeValue = JdbcSupport.getInteger(rs, "interestPostingPeriodType");
+            final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(interestPostingPeriodTypeValue);
+
+            final Integer interestCalculationTypeValue = JdbcSupport.getInteger(rs, "interestCalculationType");
+            final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(interestCalculationTypeValue);
+
+            EnumOptionData interestCalculationDaysInYearType = null;
+            final Integer interestCalculationDaysInYearTypeValue = JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType");
+            if (interestCalculationDaysInYearTypeValue != null) {
+                interestCalculationDaysInYearType = SavingsEnumerations
+                        .interestCalculationDaysInYearType(interestCalculationDaysInYearTypeValue);
+            }
+
+            final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType");
+            final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId);
+
+            final BigDecimal minRequiredOpeningBalance = rs.getBigDecimal("minRequiredOpeningBalance");
+
+            final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
+            EnumOptionData lockinPeriodFrequencyType = null;
+            final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
+            if (lockinPeriodFrequencyTypeValue != null) {
+                lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodFrequencyTypeValue);
+            }
+
+            final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
+            final boolean allowOverdraft = rs.getBoolean("allowOverdraft");
+            final BigDecimal overdraftLimit = rs.getBigDecimal("overdraftLimit");
+            final BigDecimal nominalAnnualInterestRateOverdraft = rs.getBigDecimal("nominalAnnualInterestRateOverdraft");
+            final BigDecimal minOverdraftForInterestCalculation = rs.getBigDecimal("minOverdraftForInterestCalculation");
+
+            final BigDecimal minRequiredBalance = rs.getBigDecimal("minRequiredBalance");
+            final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance");
+            final BigDecimal minBalanceForInterestCalculation = rs.getBigDecimal("minBalanceForInterestCalculation");
+
+            return SavingsProductData.instance(id, name, shortName, description, currency, nominalAnnualInterestRate,
+                    compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
+                    minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers,
+                    accountingRuleType, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
+                    minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation);
+        }
+    }
+
+    private static final class SavingProductLookupMapper implements RowMapper<SavingsProductData> {
+
+        public String schema() {
+            return " sp.id as id, sp.name as name from m_savings_product sp";
+        }
+
+        @Override
+        public SavingsProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String name = rs.getString("name");
+
+            return SavingsProductData.lookup(id, name);
+        }
+    }
+
+    @Override
+    public Collection<SavingsProductData> retrieveAllForLookupByType(Boolean isOverdraftType) {
+        String sql = "select " + this.savingsProductLookupsRowMapper.schema();
+
+        boolean inClauseAdded = false;
+        
+        // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
+  		String inClause = fineractEntityAccessUtil.
+  				getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
+						FineractEntityType.SAVINGS_PRODUCT);
+    	if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
+    		sql += " where id in ( " + inClause + " ) ";
+    		inClauseAdded = true;
+    	}
+        
+        if (isOverdraftType != null) {
+        	if (inClauseAdded) {
+        		sql += " and sp.allow_overdraft=?";
+        	} else {
+        		sql += " where sp.allow_overdraft=?";
+        	}
+            return this.jdbcTemplate.query(sql, this.savingsProductLookupsRowMapper, isOverdraftType);
+        }
+
+        return this.jdbcTemplate.query(sql, this.savingsProductLookupsRowMapper);
+
+    }
+
+    @Override
+    public Collection<SavingsProductData> retrieveAllForCurrency(String currencyCode) {
+
+        this.context.authenticatedUser();
+
+        String sql = "select " + this.savingsProductRowMapper.schema() + " where sp.currency_code='" + currencyCode + "'";
+        
+        // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
+  		String inClause = fineractEntityAccessUtil.
+  				getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
+						FineractEntityType.SAVINGS_PRODUCT);
+    	if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
+    		sql += " and id in ( " + inClause + " ) ";
+    	}
+
+        return this.jdbcTemplate.query(sql, this.savingsProductRowMapper);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformService.java
new file mode 100644
index 0000000..df41d4d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface SavingsProductWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    CommandProcessingResult update(Long productId, JsonCommand command);
+
+    CommandProcessingResult delete(Long productId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..159d658
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.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.portfolio.savings.service;
+
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
+import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
+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.entityaccess.domain.FineractEntityAccessType;
+import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType;
+import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.SavingsProductDataValidator;
+import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
+import org.apache.fineract.portfolio.savings.domain.SavingsProductAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
+import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements SavingsProductWritePlatformService {
+
+    private final Logger logger;
+    private final PlatformSecurityContext context;
+    private final SavingsProductRepository savingProductRepository;
+    private final SavingsProductDataValidator fromApiJsonDataValidator;
+    private final SavingsProductAssembler savingsProductAssembler;
+    private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
+    private final FineractEntityAccessUtil fineractEntityAccessUtil;
+
+    @Autowired
+    public SavingsProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final SavingsProductRepository savingProductRepository, final SavingsProductDataValidator fromApiJsonDataValidator,
+            final SavingsProductAssembler savingsProductAssembler,
+            final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
+            final FineractEntityAccessUtil fineractEntityAccessUtil
+            ) {
+        this.context = context;
+        this.savingProductRepository = savingProductRepository;
+        this.fromApiJsonDataValidator = fromApiJsonDataValidator;
+        this.savingsProductAssembler = savingsProductAssembler;
+        this.logger = LoggerFactory.getLogger(SavingsProductWritePlatformServiceJpaRepositoryImpl.class);
+        this.accountMappingWritePlatformService = accountMappingWritePlatformService;
+        this.fineractEntityAccessUtil = fineractEntityAccessUtil;
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataAccessException dae) {
+
+        final Throwable realCause = dae.getMostSpecificCause();
+        if (realCause.getMessage().contains("sp_unq_name")) {
+
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.product.savings.duplicate.name", "Savings product with name `" + name
+                    + "` already exists", "name", name);
+        } else if (realCause.getMessage().contains("sp_unq_short_name")) {
+
+            final String shortName = command.stringValueOfParameterNamed("shortName");
+            throw new PlatformDataIntegrityException("error.msg.product.savings.duplicate.short.name", "Savings product with short name `"
+                    + shortName + "` already exists", "shortName", shortName);
+        }
+
+        logAsErrorUnexpectedDataIntegrityException(dae);
+        throw new PlatformDataIntegrityException("error.msg.savingsproduct.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataAccessException dae) {
+        this.logger.error(dae.getMessage(), dae);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult create(final JsonCommand command) {
+
+        try {
+            this.fromApiJsonDataValidator.validateForCreate(command.json());
+
+            final SavingsProduct product = this.savingsProductAssembler.assemble(command);
+
+            this.savingProductRepository.save(product);
+
+            // save accounting mappings
+            this.accountMappingWritePlatformService.createSavingProductToGLAccountMapping(product.getId(), command,
+                    DepositAccountType.SAVINGS_DEPOSIT);
+            
+            // check if the office specific products are enabled. If yes, then save this savings product against a specific office
+            // i.e. this savings product is specific for this office.
+            fineractEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice(
+            		FineractEntityAccessType.OFFICE_ACCESS_TO_SAVINGS_PRODUCTS, 
+            		FineractEntityType.SAVINGS_PRODUCT, 
+            		product.getId());
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(product.getId()) //
+                    .build();
+        } catch (final DataAccessException e) {
+            handleDataIntegrityIssues(command, e);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult update(final Long productId, final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDataValidator.validateForUpdate(command.json());
+
+            final SavingsProduct product = this.savingProductRepository.findOne(productId);
+            if (product == null) { throw new SavingsProductNotFoundException(productId); }
+
+            final Map<String, Object> changes = product.update(command);
+
+            if (changes.containsKey(chargesParamName)) {
+                final Set<Charge> savingsProductCharges = this.savingsProductAssembler.assembleListOfSavingsProductCharges(command, product
+                        .currency().getCode());
+                final boolean updated = product.update(savingsProductCharges);
+                if (!updated) {
+                    changes.remove(chargesParamName);
+                }
+            }
+
+            // accounting related changes
+            final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName);
+            final Map<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
+                    .updateSavingsProductToGLAccountMapping(product.getId(), command, accountingTypeChanged, product.getAccountingType(),
+                            DepositAccountType.SAVINGS_DEPOSIT);
+            changes.putAll(accountingMappingChanges);
+
+            if (!changes.isEmpty()) {
+                this.savingProductRepository.saveAndFlush(product);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(product.getId()) //
+                    .with(changes).build();
+        } catch (final DataAccessException e) {
+            handleDataIntegrityIssues(command, e);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult delete(final Long productId) {
+
+        this.context.authenticatedUser();
+        final SavingsProduct product = this.savingProductRepository.findOne(productId);
+        if (product == null) { throw new SavingsProductNotFoundException(productId); }
+
+        this.savingProductRepository.delete(product);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(product.getId()) //
+                .build();
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularService.java
new file mode 100644
index 0000000..13c3471
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularService.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.service;
+
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+
+
+public interface SavingsSchedularService {
+
+    void postInterestForAccounts() throws JobExecutionException;
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java
new file mode 100644
index 0000000..6e8e619
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java
@@ -0,0 +1,71 @@
+/**
+ * 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.savings.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SavingsSchedularServiceImpl implements SavingsSchedularService {
+
+    private final SavingsAccountAssembler savingAccountAssembler;
+    private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
+    private final SavingsAccountRepository savingAccountRepository;
+
+    @Autowired
+    public SavingsSchedularServiceImpl(final SavingsAccountAssembler savingAccountAssembler,
+            final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
+            final SavingsAccountRepository savingAccountRepository) {
+        this.savingAccountAssembler = savingAccountAssembler;
+        this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
+        this.savingAccountRepository = savingAccountRepository;
+    }
+
+    @CronTarget(jobName = JobName.POST_INTEREST_FOR_SAVINGS)
+    @Override
+    public void postInterestForAccounts() throws JobExecutionException {
+        final List<SavingsAccount> savingsAccounts = this.savingAccountRepository.findSavingAccountByStatus(SavingsAccountStatusType.ACTIVE
+                .getValue());
+        StringBuffer sb = new StringBuffer();
+        for (final SavingsAccount savingsAccount : savingsAccounts) {
+            try {
+                this.savingAccountAssembler.assignSavingAccountHelpers(savingsAccount);
+                this.savingsAccountWritePlatformService.postInterest(savingsAccount);
+            } catch (Exception e) {
+                Throwable realCause = e;
+                if (e.getCause() != null) {
+                    realCause = e.getCause();
+                }
+                sb.append("failed to post interest for Savings with id " + savingsAccount.getId() + " with message "
+                        + realCause.getMessage());
+            }
+        }
+        
+        if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/SearchConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/SearchConstants.java
new file mode 100644
index 0000000..ed47f32
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/SearchConstants.java
@@ -0,0 +1,146 @@
+/**
+ * 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.search;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class SearchConstants {
+
+    public static enum SEARCH_RESPONSE_PARAMETERS {
+        ENTITY_ID("entityId"), ENTITY_ACCOUNT_NO("entityAccountNo"), ENTITY_EXTERNAL_ID("entityExternalId"), ENTITY_NAME("entityName"), ENTITY_TYPE(
+                "entityType"), PARENT_ID("parentId"), PARENT_NAME("parentName"),ENTITY_MOBILE_NO("entityMobileNo"), ENTITY_STATUS("entityStatus");
+
+        private final String value;
+
+        private SEARCH_RESPONSE_PARAMETERS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+        static {
+            for (final SEARCH_RESPONSE_PARAMETERS param : SEARCH_RESPONSE_PARAMETERS.values()) {
+                values.add(param.value);
+            }
+        }
+
+        public static Set<String> getAllValues() {
+            return values;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+    public static enum SEARCH_SUPPORTED_PARAMETERS {
+        QUERY("query"), RESOURCE("resource"),EXACTMATCH("exactMatch");
+
+        private final String value;
+
+        private SEARCH_SUPPORTED_PARAMETERS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+        static {
+            for (final SEARCH_SUPPORTED_PARAMETERS param : SEARCH_SUPPORTED_PARAMETERS.values()) {
+                values.add(param.value);
+            }
+        }
+
+        public static Set<String> getAllValues() {
+            return values;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+    public static enum SEARCH_SUPPORTED_RESOURCES {
+        CLIENTS("clients"), GROUPS("groups"), LOANS("loans"), SAVINGS("savings"), CLIENTIDENTIFIERS("clientIdentifiers");
+
+        private final String value;
+
+        private SEARCH_SUPPORTED_RESOURCES(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+        static {
+            for (final SEARCH_SUPPORTED_RESOURCES param : SEARCH_SUPPORTED_RESOURCES.values()) {
+                values.add(param.value);
+            }
+        }
+
+        public static Set<String> getAllValues() {
+            return values;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+
+    public static enum SEARCH_LOAN_DATE {
+        APPROVAL_DATE("approvalDate"), CREATED_DATE("createdDate"), DISBURSAL_DATE("disbursalDate");
+
+        private final String value;
+
+        private SEARCH_LOAN_DATE(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+        static {
+            for (final SEARCH_LOAN_DATE param : SEARCH_LOAN_DATE.values()) {
+                values.add(param.value);
+            }
+        }
+
+        public static Set<String> getAllValues() {
+            return values;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/api/SearchApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/api/SearchApiResource.java
new file mode 100644
index 0000000..41c1123
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/api/SearchApiResource.java
@@ -0,0 +1,113 @@
+/**
+ * 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.search.api;
+
+import java.util.Collection;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.portfolio.search.SearchConstants.SEARCH_RESPONSE_PARAMETERS;
+import org.apache.fineract.portfolio.search.data.AdHocQueryDataValidator;
+import org.apache.fineract.portfolio.search.data.AdHocQuerySearchConditions;
+import org.apache.fineract.portfolio.search.data.AdHocSearchQueryData;
+import org.apache.fineract.portfolio.search.data.SearchConditions;
+import org.apache.fineract.portfolio.search.data.SearchData;
+import org.apache.fineract.portfolio.search.service.SearchReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/search")
+@Component
+@Scope("singleton")
+public class SearchApiResource {
+
+    private final Set<String> searchResponseParameters = SEARCH_RESPONSE_PARAMETERS.getAllValues();
+
+    private final SearchReadPlatformService searchReadPlatformService;
+    private final ToApiJsonSerializer<Object> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final AdHocQueryDataValidator fromApiJsonDeserializer;
+
+    @Autowired
+    public SearchApiResource(final SearchReadPlatformService searchReadPlatformService,
+            final ToApiJsonSerializer<Object> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+            final AdHocQueryDataValidator fromApiJsonDeserializer) {
+
+        this.searchReadPlatformService = searchReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+
+    }
+
+    @GET
+    @Path("/template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAdHocSearchQueryTemplate(@Context final UriInfo uriInfo) {
+
+        final AdHocSearchQueryData templateData = this.searchReadPlatformService.retrieveAdHocQueryTemplate();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, templateData);
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String searchData(@Context final UriInfo uriInfo, @QueryParam("query") final String query,
+            @QueryParam("resource") final String resource ,@DefaultValue("false") @QueryParam("exactMatch")  Boolean exactMatch) {
+    	
+        final SearchConditions searchConditions = new SearchConditions(query, resource,exactMatch);
+
+        final Collection<SearchData> searchResults = this.searchReadPlatformService.retriveMatchingData(searchConditions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, searchResults, this.searchResponseParameters);
+    }
+
+    @POST
+    @Path("/advance")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String advancedSearch(@Context final UriInfo uriInfo, final String json) {
+
+        final AdHocQuerySearchConditions searchConditions = this.fromApiJsonDeserializer.retrieveSearchConditions(json);
+
+        final Collection<AdHocSearchQueryData> searchResults = this.searchReadPlatformService
+                .retrieveAdHocQueryMatchingData(searchConditions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, searchResults);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java
new file mode 100644
index 0000000..efa8ea8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java
@@ -0,0 +1,294 @@
+/**
+ * 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.search.data;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class AdHocQueryDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public AdHocQueryDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateAdHocQueryParameters(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                AdHocQuerySearchConstants.AD_HOC_SEARCH_QUERY_REQUEST_DATA_PARAMETERS);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(AdHocQuerySearchConstants.AD_HOC_SEARCH_QUERY_RESOURCE_NAME);
+
+        final String[] entities = this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.entitiesParamName, element);
+        baseDataValidator.reset().parameter(AdHocQuerySearchConstants.entitiesParamName).value(entities).arrayNotEmpty();
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanStatusParamName, element)) {
+            final String[] loanStatus = this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.loanStatusParamName, element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanStatusParamName).value(loanStatus).arrayNotEmpty();
+            if (loanStatus != null && loanStatus.length > 0) {
+                for (String status : loanStatus) {
+                    baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanStatusParamName).value(status)
+                            .isOneOfTheseValues(AdHocQuerySearchConstants.loanStatusOptions);
+                }
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanProductsParamName, element)) {
+            final String[] loanProducts = this.fromApiJsonHelper
+                    .extractArrayNamed(AdHocQuerySearchConstants.loanProductsParamName, element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanProductsParamName).value(loanProducts).arrayNotEmpty();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.officesParamName, element)) {
+            final String[] offices = this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.officesParamName, element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.officesParamName).value(offices).arrayNotEmpty();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanDateOptionParamName, element)) {
+            final String loanDateOption = this.fromApiJsonHelper.extractStringNamed(AdHocQuerySearchConstants.loanDateOptionParamName,
+                    element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanDateOptionParamName).value(loanDateOption)
+                    .isOneOfTheseValues(AdHocQuerySearchConstants.loanDateOptions);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanFromDateParamName, element)) {
+            final LocalDate loanFromDate = this.fromApiJsonHelper.extractLocalDateNamed(AdHocQuerySearchConstants.loanFromDateParamName,
+                    element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanFromDateParamName).value(loanFromDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanToDateParamName, element)) {
+            final LocalDate loanToDate = this.fromApiJsonHelper.extractLocalDateNamed(AdHocQuerySearchConstants.loanToDateParamName,
+                    element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanToDateParamName).value(loanToDate).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName, element)) {
+            final boolean includeOutStandingAmountPercentage = this.fromApiJsonHelper.extractBooleanNamed(
+                    AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName, element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName)
+                    .value(includeOutStandingAmountPercentage).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element)) {
+            final String outStandingAmountPercentageCondition = this.fromApiJsonHelper.extractStringNamed(
+                    AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName)
+                    .value(outStandingAmountPercentageCondition)
+                    .isNotOneOfTheseValues(AdHocQuerySearchConstants.AD_HOC_SEARCH_QUERY_CONDITIONS);
+            if (outStandingAmountPercentageCondition.equals("between")) {
+                final BigDecimal minOutStandingAmountPercentage = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        AdHocQuerySearchConstants.minOutStandingAmountPercentageParamName, element);
+                baseDataValidator.reset().parameter(AdHocQuerySearchConstants.minOutStandingAmountPercentageParamName)
+                        .value(minOutStandingAmountPercentage).notNull().notLessThanMin(BigDecimal.ZERO);
+                final BigDecimal maxOutStandingAmountPercentage = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName, element);
+                baseDataValidator.reset().parameter(AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName)
+                        .value(maxOutStandingAmountPercentage).notNull().notLessThanMin(BigDecimal.ZERO);
+                baseDataValidator.reset().comapareMinAndMaxOfTwoBigDecmimalNos(minOutStandingAmountPercentage,
+                        maxOutStandingAmountPercentage);
+            } else {
+                if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageParamName, element)) {
+                    final BigDecimal outStandingAmountPercentage = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                            AdHocQuerySearchConstants.outStandingAmountPercentageParamName, element);
+                    baseDataValidator.reset().parameter(AdHocQuerySearchConstants.outStandingAmountPercentageParamName)
+                            .value(outStandingAmountPercentage).notNull().notLessThanMin(BigDecimal.ZERO);
+                }
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.includeOutstandingAmountParamName, element)) {
+            final Boolean includeOutstandingAmountParamName = this.fromApiJsonHelper.extractBooleanNamed(
+                    AdHocQuerySearchConstants.includeOutstandingAmountParamName, element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.includeOutstandingAmountParamName)
+                    .value(includeOutstandingAmountParamName).notNull();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element)) {
+            final String outstandingAmountCondition = this.fromApiJsonHelper.extractStringNamed(
+                    AdHocQuerySearchConstants.outstandingAmountConditionParamName, element);
+            baseDataValidator.reset().parameter(AdHocQuerySearchConstants.outstandingAmountConditionParamName)
+                    .value(outstandingAmountCondition).isNotOneOfTheseValues(AdHocQuerySearchConstants.AD_HOC_SEARCH_QUERY_CONDITIONS);
+            if (outstandingAmountCondition.equals("between")) {
+                final BigDecimal minOutstandingAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        AdHocQuerySearchConstants.minOutstandingAmountParamName, element);
+                baseDataValidator.reset().parameter(AdHocQuerySearchConstants.minOutstandingAmountParamName).value(minOutstandingAmount)
+                        .notNull().notLessThanMin(BigDecimal.ZERO);
+                final BigDecimal maxOutstandingAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        AdHocQuerySearchConstants.maxOutstandingAmountParamName, element);
+                baseDataValidator.reset().parameter(AdHocQuerySearchConstants.maxOutstandingAmountParamName).value(maxOutstandingAmount)
+                        .notNull().notLessThanMin(BigDecimal.ZERO);
+                baseDataValidator.reset().comapareMinAndMaxOfTwoBigDecmimalNos(minOutstandingAmount, maxOutstandingAmount);
+            } else {
+                final BigDecimal outstandingAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        AdHocQuerySearchConstants.outstandingAmountParamName, element);
+                baseDataValidator.reset().parameter(AdHocQuerySearchConstants.outstandingAmountParamName).value(outstandingAmount)
+                        .notNull().notLessThanMin(BigDecimal.ZERO);
+            }
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public AdHocQuerySearchConditions retrieveSearchConditions(String json) {
+
+        validateAdHocQueryParameters(json);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        List<String> loanStatus = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanStatusParamName, element)) {
+            loanStatus = Arrays.asList(this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.loanStatusParamName, element));
+        }
+
+        List<Long> loanProducts = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanProductsParamName, element)) {
+            loanProducts = extractLongValuesList(Arrays.asList(this.fromApiJsonHelper.extractArrayNamed(
+                    AdHocQuerySearchConstants.loanProductsParamName, element)));
+        }
+
+        List<Long> offices = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.officesParamName, element)) {
+            offices = extractLongValuesList(Arrays.asList(this.fromApiJsonHelper.extractArrayNamed(
+                    AdHocQuerySearchConstants.officesParamName, element)));
+        }
+
+        String loanDateOption = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanDateOptionParamName, element)) {
+            loanDateOption = this.fromApiJsonHelper.extractStringNamed(AdHocQuerySearchConstants.loanDateOptionParamName, element);
+        }
+
+        LocalDate loanFromDate = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanFromDateParamName, element)) {
+            loanFromDate = this.fromApiJsonHelper.extractLocalDateNamed(AdHocQuerySearchConstants.loanFromDateParamName, element);
+        }
+
+        LocalDate loanToDate = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanToDateParamName, element)) {
+            loanToDate = this.fromApiJsonHelper.extractLocalDateNamed(AdHocQuerySearchConstants.loanToDateParamName, element);
+        }
+
+        Boolean includeOutStandingAmountPercentage = false;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName, element)) {
+            includeOutStandingAmountPercentage = this.fromApiJsonHelper.extractBooleanNamed(
+                    AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName, element);
+        }
+
+        String outStandingAmountPercentageCondition = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element)) {
+            outStandingAmountPercentageCondition = this.fromApiJsonHelper.extractStringNamed(
+                    AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element);
+        }
+
+        BigDecimal minOutStandingAmountPercentage = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.minOutStandingAmountPercentageParamName, element)) {
+            minOutStandingAmountPercentage = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    AdHocQuerySearchConstants.minOutStandingAmountPercentageParamName, element);
+        }
+
+        BigDecimal maxOutStandingAmountPercentage = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName, element)) {
+            maxOutStandingAmountPercentage = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName, element);
+        }
+
+        BigDecimal outStandingAmountPercentage = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageParamName, element)) {
+            outStandingAmountPercentage = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    AdHocQuerySearchConstants.outStandingAmountPercentageParamName, element);
+        }
+
+        Boolean includeOutstandingAmountParamName = false;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.includeOutstandingAmountParamName, element)) {
+            includeOutstandingAmountParamName = this.fromApiJsonHelper.extractBooleanNamed(
+                    AdHocQuerySearchConstants.includeOutstandingAmountParamName, element);
+        }
+
+        String outstandingAmountCondition = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element)) {
+            outstandingAmountCondition = this.fromApiJsonHelper.extractStringNamed(
+                    AdHocQuerySearchConstants.outstandingAmountConditionParamName, element);
+        }
+
+        BigDecimal minOutstandingAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.minOutstandingAmountParamName, element)) {
+            minOutstandingAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    AdHocQuerySearchConstants.minOutstandingAmountParamName, element);
+        }
+
+        BigDecimal maxOutstandingAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.maxOutstandingAmountParamName, element)) {
+            maxOutstandingAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    AdHocQuerySearchConstants.maxOutstandingAmountParamName, element);
+        }
+
+        BigDecimal outstandingAmount = null;
+        if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outstandingAmountParamName, element)) {
+            outstandingAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                    AdHocQuerySearchConstants.outstandingAmountParamName, element);
+        }
+
+        return AdHocQuerySearchConditions.instance(loanStatus, loanProducts, offices, loanDateOption, loanFromDate, loanToDate,
+                includeOutStandingAmountPercentage, outStandingAmountPercentageCondition, minOutStandingAmountPercentage,
+                maxOutStandingAmountPercentage, outStandingAmountPercentage, includeOutstandingAmountParamName, outstandingAmountCondition,
+                minOutstandingAmount, maxOutstandingAmount, outstandingAmount);
+
+    }
+
+    private List<Long> extractLongValuesList(List<String> listTobeConverted) {
+        List<Long> tempList = new ArrayList<>();
+        for (String temp : listTobeConverted) {
+            tempList.add(Long.valueOf(temp));
+        }
+        return tempList;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQuerySearchConditions.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQuerySearchConditions.java
new file mode 100644
index 0000000..0cd6efd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQuerySearchConditions.java
@@ -0,0 +1,187 @@
+/**
+ * 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.search.data;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.joda.time.LocalDate;
+
+public class AdHocQuerySearchConditions {
+
+    private final List<String> loanStatus;
+    private final List<Long> loanProducts;
+    private final List<Long> offices;
+    private final String loanDateOption;
+    private final LocalDate loanFromDate;
+    private final LocalDate loanToDate;
+    private final Boolean includeOutStandingAmountPercentage;
+    private final String outStandingAmountPercentageCondition;
+    private final BigDecimal minOutStandingAmountPercentage;
+    private final BigDecimal maxOutStandingAmountPercentage;
+    private final BigDecimal outStandingAmountPercentage;
+    private final Boolean includeOutstandingAmount;
+    private final String outstandingAmountCondition;
+    private final BigDecimal minOutstandingAmount;
+    private final BigDecimal maxOutstandingAmount;
+    private final BigDecimal outstandingAmount;
+
+    public static AdHocQuerySearchConditions instance(final List<String> loanStatus, final List<Long> loanProducts,
+            final List<Long> offices, final String loanDateOption, final LocalDate loanFromDate, final LocalDate loanToDate,
+            final Boolean includeOutStandingAmountPercentage, final String outStandingAmountPercentageCondition,
+            final BigDecimal minOutStandingAmountPercentage, final BigDecimal maxOutStandingAmountPercentage,
+            final BigDecimal outStandingAmountPercentage, final Boolean includeOutstandingAmountParamName,
+            final String outstandingAmountCondition, final BigDecimal minOutstandingAmount, final BigDecimal maxOutstandingAmount,
+            final BigDecimal outstandingAmount) {
+
+        return new AdHocQuerySearchConditions(loanStatus, loanProducts, offices, loanDateOption, loanFromDate, loanToDate,
+                includeOutStandingAmountPercentage, outStandingAmountPercentageCondition, minOutStandingAmountPercentage,
+                maxOutStandingAmountPercentage, outStandingAmountPercentage, includeOutstandingAmountParamName, outstandingAmountCondition,
+                minOutstandingAmount, maxOutstandingAmount, outstandingAmount);
+
+    }
+
+    public AdHocQuerySearchConditions(final List<String> loanStatus, final List<Long> loanProducts, final List<Long> offices,
+            final String loanDateOption, final LocalDate loanFromDate, final LocalDate loanToDate,
+            final Boolean includeOutStandingAmountPercentage, final String outStandingAmountPercentageCondition,
+            final BigDecimal minOutStandingAmountPercentage, final BigDecimal maxOutStandingAmountPercentage,
+            final BigDecimal outStandingAmountPercentage, final Boolean includeOutstandingAmount, final String outstandingAmountCondition,
+            final BigDecimal minOutstandingAmount, final BigDecimal maxOutstandingAmount, final BigDecimal outstandingAmount) {
+
+        this.loanStatus = loanStatus;
+        this.loanProducts = loanProducts;
+        this.offices = offices;
+        this.loanDateOption = loanDateOption;
+        this.loanFromDate = loanFromDate;
+        this.loanToDate = loanToDate;
+        this.includeOutStandingAmountPercentage = includeOutStandingAmountPercentage;
+        this.outStandingAmountPercentageCondition = outStandingAmountPercentageCondition;
+        this.minOutStandingAmountPercentage = minOutStandingAmountPercentage;
+        this.maxOutStandingAmountPercentage = maxOutStandingAmountPercentage;
+        this.outStandingAmountPercentage = outStandingAmountPercentage;
+        this.includeOutstandingAmount = includeOutstandingAmount;
+        this.outstandingAmountCondition = outstandingAmountCondition;
+        this.minOutstandingAmount = minOutstandingAmount;
+        this.maxOutstandingAmount = maxOutstandingAmount;
+        this.outstandingAmount = outstandingAmount;
+
+    }
+
+    public List<String> getLoanStatus() {
+        return getStatusVluesFromStatusCodes();
+    }
+
+    private List<String> getStatusVluesFromStatusCodes() {
+        List<String> loanStatusValues = new ArrayList<>();
+        if (this.loanStatus != null) {
+            for (String statusCode : this.loanStatus) {
+
+                if (statusCode.equalsIgnoreCase("active")) {
+                    loanStatusValues.add(LoanStatus.ACTIVE.getValue().toString());
+                }
+
+                if (statusCode.equalsIgnoreCase("overpaid")) {
+                    loanStatusValues.add(LoanStatus.OVERPAID.getValue().toString());
+                }
+
+                if (statusCode.equalsIgnoreCase("closed")) {
+                    loanStatusValues.add(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue().toString());
+                }
+
+                if (statusCode.equalsIgnoreCase("writeoff")) {
+                    loanStatusValues.add(LoanStatus.CLOSED_WRITTEN_OFF.getValue().toString());
+                }
+
+                if (statusCode.equalsIgnoreCase("arrears")) {
+                    // TODO - No status code is there for loans which are in
+                    // active bad standing
+                }
+
+                if (statusCode.equalsIgnoreCase("all")) {
+                    loanStatusValues.add("all");
+                }
+            }
+        }
+
+        return loanStatusValues;
+    }
+
+    public List<Long> getLoanProducts() {
+        return this.loanProducts;
+    }
+
+    public List<Long> getOffices() {
+        return this.offices;
+    }
+
+    public String getLoanDateOption() {
+        return this.loanDateOption;
+    }
+
+    public LocalDate getLoanFromDate() {
+        return this.loanFromDate;
+    }
+
+    public LocalDate getLoanToDate() {
+        return this.loanToDate;
+    }
+
+    public Boolean getIncludeOutStandingAmountPercentage() {
+        return this.includeOutStandingAmountPercentage;
+    }
+
+    public String getOutStandingAmountPercentageCondition() {
+        return this.outStandingAmountPercentageCondition;
+    }
+
+    public BigDecimal getMinOutStandingAmountPercentage() {
+        return this.minOutStandingAmountPercentage;
+    }
+
+    public BigDecimal getMaxOutStandingAmountPercentage() {
+        return this.maxOutStandingAmountPercentage;
+    }
+
+    public BigDecimal getOutStandingAmountPercentage() {
+        return this.outStandingAmountPercentage;
+    }
+
+    public Boolean getIncludeOutstandingAmount() {
+        return this.includeOutstandingAmount;
+    }
+
+    public String getOutstandingAmountCondition() {
+        return this.outstandingAmountCondition;
+    }
+
+    public BigDecimal getMinOutstandingAmount() {
+        return this.minOutstandingAmount;
+    }
+
+    public BigDecimal getMaxOutstandingAmount() {
+        return this.maxOutstandingAmount;
+    }
+
+    public BigDecimal getOutstandingAmount() {
+        return this.outstandingAmount;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQuerySearchConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQuerySearchConstants.java
new file mode 100644
index 0000000..1e48c67
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQuerySearchConstants.java
@@ -0,0 +1,63 @@
+/**
+ * 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.search.data;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public interface AdHocQuerySearchConstants {
+
+    public static final String AD_HOC_SEARCH_QUERY_RESOURCE_NAME = "adHocQuery";
+
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+    public static final String entitiesParamName = "entities";
+    public static final String loanStatusParamName = "loanStatus";
+    public static final String loanProductsParamName = "loanProducts";
+    public static final String officesParamName = "offices";
+    public static final String loanDateOptionParamName = "loanDateOption";
+    public static final String loanFromDateParamName = "loanFromDate";
+    public static final String loanToDateParamName = "loanToDate";
+    public static final String includeOutStandingAmountPercentageParamName = "includeOutStandingAmountPercentage";
+    public static final String outStandingAmountPercentageConditionParamName = "outStandingAmountPercentageCondition";
+    public static final String minOutStandingAmountPercentageParamName = "minOutStandingAmountPercentage";
+    public static final String maxOutStandingAmountPercentageParamName = "maxOutStandingAmountPercentage";
+    public static final String outStandingAmountPercentageParamName = "outStandingAmountPercentage";
+    public static final String includeOutstandingAmountParamName = "includeOutstandingAmount";
+    public static final String outstandingAmountConditionParamName = "outstandingAmountCondition";
+    public static final String minOutstandingAmountParamName = "minOutstandingAmount";
+    public static final String maxOutstandingAmountParamName = "maxOutstandingAmount";
+    public static final String outstandingAmountParamName = "outstandingAmount";
+
+    public static final Set<String> AD_HOC_SEARCH_QUERY_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(entitiesParamName, loanStatusParamName,
+            loanProductsParamName, officesParamName, loanDateOptionParamName, loanFromDateParamName, loanToDateParamName,
+            includeOutStandingAmountPercentageParamName, outStandingAmountPercentageConditionParamName,
+            minOutStandingAmountPercentageParamName, maxOutStandingAmountPercentageParamName, outStandingAmountPercentageParamName,
+            includeOutstandingAmountParamName, outstandingAmountConditionParamName, minOutstandingAmountParamName,
+            maxOutstandingAmountParamName, outstandingAmountParamName, localeParamName, dateFormatParamName));
+
+    public static final Set<String> AD_HOC_SEARCH_QUERY_CONDITIONS = new HashSet<>(
+            Arrays.asList("between", "<=", ">=", "<", ">", "="));
+
+    public static final Object[] loanDateOptions = { "approvalDate", "createdDate", "disbursalDate" };
+    public static final Object[] entityTypeOptions = { "clients", "groups", "loans", "clientIdentifiers" };
+    public static final Object[] loanStatusOptions = { "all", "active", "overpaid", "arrears", "closed", "writeoff" };
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocSearchQueryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocSearchQueryData.java
new file mode 100644
index 0000000..8a29256
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocSearchQueryData.java
@@ -0,0 +1,75 @@
+/**
+ * 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.search.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+
+public class AdHocSearchQueryData {
+
+    @SuppressWarnings("unused")
+    private final String officeName;
+    @SuppressWarnings("unused")
+    private final String loanProductName;
+    @SuppressWarnings("unused")
+    private final Integer count;
+    @SuppressWarnings("unused")
+    private final BigDecimal loanOutStanding;
+    @SuppressWarnings("unused")
+    private final Double percentage;
+
+    @SuppressWarnings("unused")
+    private final Collection<LoanProductData> loanProducts;
+    @SuppressWarnings("unused")
+    private final Collection<OfficeData> offices;
+
+    public static AdHocSearchQueryData template(final Collection<LoanProductData> loanProducts, final Collection<OfficeData> offices) {
+        final String officeName = null;
+        final String loanProductName = null;
+        final Integer count = null;
+        final BigDecimal loanOutStanding = null;
+        final Double percentage = null;
+        return new AdHocSearchQueryData(officeName, loanProductName, count, loanOutStanding, percentage, loanProducts, offices);
+    }
+
+    public static AdHocSearchQueryData matchedResult(final String officeName, final String loanProductName, final Integer count,
+            final BigDecimal loanOutStanding, final Double percentage) {
+
+        final Collection<LoanProductData> loanProducts = null;
+        final Collection<OfficeData> offices = null;
+        return new AdHocSearchQueryData(officeName, loanProductName, count, loanOutStanding, percentage, loanProducts, offices);
+    }
+
+    private AdHocSearchQueryData(final String officeName, final String loanProductName, final Integer count,
+            final BigDecimal loanOutStanding, final Double percentage, final Collection<LoanProductData> loanProducts,
+            final Collection<OfficeData> offices) {
+
+        this.officeName = officeName;
+        this.loanProductName = loanProductName;
+        this.count = count;
+        this.loanOutStanding = loanOutStanding;
+        this.percentage = percentage;
+
+        this.loanProducts = loanProducts;
+        this.offices = offices;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchConditions.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchConditions.java
new file mode 100644
index 0000000..52558e0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchConditions.java
@@ -0,0 +1,92 @@
+/**
+ * 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.search.data;
+
+import org.apache.fineract.portfolio.search.SearchConstants.SEARCH_SUPPORTED_RESOURCES;
+
+public class SearchConditions {
+
+    private final String searchQuery;
+    private final String searchResource;
+    private final Boolean clientSearch;
+    private final Boolean groupSearch;
+    private final Boolean loanSeach;
+	private final Boolean savingSeach;
+    private final Boolean clientIdentifierSearch;
+    private  Boolean exactMatch;
+
+    public SearchConditions(final String searchQueryParam, final String searchResource, Boolean exactMatch) {
+        this.searchQuery = searchQueryParam;
+        this.searchResource = searchResource;
+        this.exactMatch=exactMatch;
+        this.clientSearch = (null == searchResource || searchResource.toLowerCase().contains(
+                SEARCH_SUPPORTED_RESOURCES.CLIENTS.name().toLowerCase())) ? true : false;
+        this.groupSearch = (null == searchResource || searchResource.toLowerCase().contains(
+                SEARCH_SUPPORTED_RESOURCES.GROUPS.name().toLowerCase())) ? true : false;
+        this.loanSeach = (null == searchResource || searchResource.toLowerCase().contains(
+                SEARCH_SUPPORTED_RESOURCES.LOANS.name().toLowerCase())) ? true : false;
+        this.savingSeach = (null == searchResource || searchResource.toLowerCase().contains(
+                SEARCH_SUPPORTED_RESOURCES.SAVINGS.name().toLowerCase())) ? true : false;
+  		this.clientIdentifierSearch = (null == searchResource || searchResource.toLowerCase().contains(
+                SEARCH_SUPPORTED_RESOURCES.CLIENTIDENTIFIERS.name().toLowerCase())) ? true : false;
+    }
+
+    public SearchConditions(final String searchQueryParam, final String searchResource, final Boolean clientSearch,
+            final Boolean groupSearch, final Boolean loanSeach, final Boolean savingSeach, final Boolean clientIdentifierSearch, Boolean exactMatch) {
+        this.searchQuery = searchQueryParam;
+        this.searchResource = searchResource;
+        this.clientSearch = clientSearch;
+        this.groupSearch = groupSearch;
+        this.loanSeach = loanSeach;
+		this.savingSeach = savingSeach;
+        this.clientIdentifierSearch = clientIdentifierSearch;
+        this.exactMatch=exactMatch;
+    }
+
+    public String getSearchQuery() {
+        return this.searchQuery;
+    }
+
+    public String getSearchResource() {
+        return this.searchResource;
+    }
+    public Boolean getExactMatch() {
+    	return this.exactMatch;
+    }
+    public Boolean isClientSearch() {
+        return this.clientSearch;
+    }
+
+    public Boolean isGroupSearch() {
+        return this.groupSearch;
+    }
+
+    public Boolean isLoanSeach() {
+        return this.loanSeach;
+    }
+
+    public Boolean isSavingSeach() {
+        return this.savingSeach;
+    }
+
+	public Boolean isClientIdentifierSearch() {
+        return this.clientIdentifierSearch;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchData.java
new file mode 100644
index 0000000..cfe58d7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchData.java
@@ -0,0 +1,91 @@
+/**
+ * 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.search.data;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class SearchData {
+
+    private final Long entityId;
+    private final String entityAccountNo;
+    private final String entityExternalId;
+    private final String entityName;
+    private final String entityType;
+    private final Long parentId;
+    private final String parentName;
+    private final String entityMobileNo;
+    private final EnumOptionData entityStatus;
+    private final String parentType;
+
+    public SearchData(final Long entityId, final String entityAccountNo, final String entityExternalId, final String entityName,
+            final String entityType, final Long parentId, final String parentName, final String parentType, final String entityMobileNo, final EnumOptionData entityStatus) {
+
+        this.entityId = entityId;
+        this.entityAccountNo = entityAccountNo;
+        this.entityExternalId = entityExternalId;
+        this.entityName = entityName;
+        this.entityType = entityType;
+        this.parentId = parentId;
+        this.parentName = parentName;
+        this.parentType = parentType;
+        this.entityMobileNo=entityMobileNo;
+        this.entityStatus = entityStatus;
+    }
+
+    public Long getEntityId() {
+        return this.entityId;
+    }
+
+    public String getEntityAccountNo() {
+        return this.entityAccountNo;
+    }
+
+    public String getEntityExternalId() {
+        return this.entityExternalId;
+    }
+
+    public String getEntityName() {
+        return this.entityName;
+    }
+
+    public String getEntityType() {
+        return this.entityType;
+    }
+
+    public Long getParentId() {
+        return this.parentId;
+    }
+
+    public String getParentName() {
+        return this.parentName;
+    }
+    
+    public String getParentType() {
+        return this.parentType;
+    }
+
+    public String getEntityMobileNo() {
+		return this.entityMobileNo;
+	}
+
+	public EnumOptionData getEntityStatus() {
+        return this.entityStatus;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformService.java
new file mode 100644
index 0000000..4e40c42
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformService.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.portfolio.search.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.search.data.AdHocQuerySearchConditions;
+import org.apache.fineract.portfolio.search.data.AdHocSearchQueryData;
+import org.apache.fineract.portfolio.search.data.SearchConditions;
+import org.apache.fineract.portfolio.search.data.SearchData;
+
+public interface SearchReadPlatformService {
+
+    Collection<SearchData> retriveMatchingData(SearchConditions searchConditions);
+
+    AdHocSearchQueryData retrieveAdHocQueryTemplate();
+
+    Collection<AdHocSearchQueryData> retrieveAdHocQueryMatchingData(AdHocQuerySearchConditions searchConditions);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformServiceImpl.java
new file mode 100644
index 0000000..f51dada
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformServiceImpl.java
@@ -0,0 +1,325 @@
+/**
+ * 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.search.service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.commons.lang.StringUtils;
+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.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
+import org.apache.fineract.portfolio.group.domain.GroupingTypeEnumerations;
+import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
+import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
+import org.apache.fineract.portfolio.search.SearchConstants;
+import org.apache.fineract.portfolio.search.data.AdHocQuerySearchConditions;
+import org.apache.fineract.portfolio.search.data.AdHocSearchQueryData;
+import org.apache.fineract.portfolio.search.data.SearchConditions;
+import org.apache.fineract.portfolio.search.data.SearchData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SearchReadPlatformServiceImpl implements SearchReadPlatformService {
+
+    private final NamedParameterJdbcTemplate namedParameterjdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final LoanProductReadPlatformService loanProductReadPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+
+    @Autowired
+    public SearchReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final LoanProductReadPlatformService loanProductReadPlatformService, final OfficeReadPlatformService officeReadPlatformService) {
+        this.context = context;
+        this.namedParameterjdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
+        this.loanProductReadPlatformService = loanProductReadPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+    }
+
+    @Override
+    public Collection<SearchData> retriveMatchingData(final SearchConditions searchConditions) {
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+
+        final SearchMapper rm = new SearchMapper();
+
+        final MapSqlParameterSource params = new MapSqlParameterSource();
+        params.addValue("hierarchy", hierarchy + "%");
+        if(searchConditions.getExactMatch()){
+       	 params.addValue("search", searchConditions.getSearchQuery());
+       	}else{
+       	 params.addValue("search", "%" + searchConditions.getSearchQuery() + "%");
+       	}  
+        return this.namedParameterjdbcTemplate.query(rm.searchSchema(searchConditions), params, rm);
+    }
+
+    private static final class SearchMapper implements RowMapper<SearchData> {
+
+        public String searchSchema(final SearchConditions searchConditions) {
+
+            final String union = " union ";
+            final String clientMatchSql = " (select 'CLIENT' as entityType, c.id as entityId, c.display_name as entityName, c.external_id as entityExternalId, c.account_no as entityAccountNo "
+                    + " , c.office_id as parentId, o.name as parentName, c.mobile_no as entityMobileNo,c.status_enum as entityStatusEnum, null as parentType "
+                    + " from m_client c join m_office o on o.id = c.office_id where o.hierarchy like :hierarchy and (c.account_no like :search or c.display_name like :search or c.external_id like :search or c.mobile_no like :search)) ";
+
+            final String loanMatchSql = " (select 'LOAN' as entityType, l.id as entityId, pl.name as entityName, l.external_id as entityExternalId, l.account_no as entityAccountNo "
+                    + " , IFNULL(c.id,g.id) as parentId, IFNULL(c.display_name,g.display_name) as parentName, null as entityMobileNo, l.loan_status_id as entityStatusEnum, IF(g.id is null, 'client', 'group') as parentType "
+                    + " from m_loan l left join m_client c on l.client_id = c.id left join m_group g ON l.group_id = g.id left join m_office o on o.id = c.office_id left join m_product_loan pl on pl.id=l.product_id where (o.hierarchy IS NULL OR o.hierarchy like :hierarchy) and (l.account_no like :search or l.external_id like :search)) ";
+
+
+            final String savingMatchSql = " (select 'SAVING' as entityType, s.id as entityId, sp.name as entityName, s.external_id as entityExternalId, s.account_no as entityAccountNo "
+                    + " , IFNULL(c.id,g.id) as parentId, IFNULL(c.display_name,g.display_name) as parentName, null as entityMobileNo, s.status_enum as entityStatusEnum, IF(g.id is null, 'client', 'group') as parentType "
+                    + " from m_savings_account s left join m_client c on s.client_id = c.id left join m_group g ON s.group_id = g.id left join m_office o on o.id = c.office_id left join m_savings_product sp on sp.id=s.product_id "
+                    + " where (o.hierarchy IS NULL OR o.hierarchy like :hierarchy) and (s.account_no like :search or s.external_id like :search)) ";
+            
+            final String clientIdentifierMatchSql = " (select 'CLIENTIDENTIFIER' as entityType, ci.id as entityId, ci.document_key as entityName, "
+                    + " null as entityExternalId, null as entityAccountNo, c.id as parentId, c.display_name as parentName,null as entityMobileNo, c.status_enum as entityStatusEnum, null as parentType "
+                    + " from m_client_identifier ci join m_client c on ci.client_id=c.id join m_office o on o.id = c.office_id "
+                    + " where o.hierarchy like :hierarchy and ci.document_key like :search ) ";
+            final String groupMatchSql = " (select IF(g.level_id=1,'CENTER','GROUP') as entityType, g.id as entityId, g.display_name as entityName, g.external_id as entityExternalId, g.account_no as entityAccountNo "
+                    + " , g.office_id as parentId, o.name as parentName, null as entityMobileNo, g.status_enum as entityStatusEnum, null as parentType "
+                    + " from m_group g join m_office o on o.id = g.office_id where o.hierarchy like :hierarchy and (g.account_no like :search or g.display_name like :search or g.external_id like :search or g.id like :search )) ";
+            final StringBuffer sql = new StringBuffer();
+
+            if (searchConditions.isClientSearch()) {
+                sql.append(clientMatchSql).append(union);
+            }
+
+            if (searchConditions.isLoanSeach()) {
+                sql.append(loanMatchSql).append(union);
+            }
+
+            if (searchConditions.isSavingSeach()) {
+                sql.append(savingMatchSql).append(union);
+            }
+
+            if (searchConditions.isClientIdentifierSearch()) {
+                sql.append(clientIdentifierMatchSql).append(union);
+            }
+
+            if (searchConditions.isGroupSearch()) {
+                sql.append(groupMatchSql).append(union);
+            }
+
+            
+
+            sql.replace(sql.lastIndexOf(union), sql.length(), "");
+
+            // remove last occurrence of "union all" string
+            return sql.toString();
+        }
+
+        @Override
+        public SearchData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+            final Long entityId = JdbcSupport.getLong(rs, "entityId");
+            final String entityAccountNo = rs.getString("entityAccountNo");
+            final String entityExternalId = rs.getString("entityExternalId");
+            final String entityName = rs.getString("entityName");
+            final String entityType = rs.getString("entityType");
+            final Long parentId = JdbcSupport.getLong(rs, "parentId");
+            final String parentName = rs.getString("parentName");
+            final String entityMobileNo = rs.getString("entityMobileNo");
+            final Integer entityStatusEnum = JdbcSupport.getInteger(rs, "entityStatusEnum");
+            final String parentType = rs.getString("parentType");
+            
+            EnumOptionData entityStatus = new EnumOptionData(0L, "", "");
+
+            if (entityType.equalsIgnoreCase("client") || entityType.equalsIgnoreCase("clientidentifier")) {
+                entityStatus = ClientEnumerations.status(entityStatusEnum);
+            }
+
+            else if (entityType.equalsIgnoreCase("group") || entityType.equalsIgnoreCase("center")) {
+                entityStatus = GroupingTypeEnumerations.status(entityStatusEnum);
+            }
+
+            else if (entityType.equalsIgnoreCase("loan")) {
+                LoanStatusEnumData loanStatusEnumData = LoanEnumerations.status(entityStatusEnum);
+
+                entityStatus = LoanEnumerations.status(loanStatusEnumData);
+            }
+
+            return new SearchData(entityId, entityAccountNo, entityExternalId, entityName, entityType, parentId, parentName, parentType, 
+                    entityMobileNo, entityStatus);
+        }
+
+    }
+
+    @Override
+    public AdHocSearchQueryData retrieveAdHocQueryTemplate() {
+
+        this.context.authenticatedUser();
+
+        final Collection<LoanProductData> loanProducts = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup();
+        final Collection<OfficeData> offices = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+        return AdHocSearchQueryData.template(loanProducts, offices);
+    }
+
+    @Override
+    public Collection<AdHocSearchQueryData> retrieveAdHocQueryMatchingData(final AdHocQuerySearchConditions searchConditions) {
+
+        this.context.authenticatedUser();
+
+        final AdHocQuerySearchMapper rm = new AdHocQuerySearchMapper();
+        final MapSqlParameterSource params = new MapSqlParameterSource();
+
+        return this.namedParameterjdbcTemplate.query(rm.schema(searchConditions, params), params, rm);
+    }
+
+    private static final class AdHocQuerySearchMapper implements RowMapper<AdHocSearchQueryData> {
+
+        private boolean isWhereClauseAdded = false;
+
+        // TODO- build the query dynamically based on selected entity types, for
+        // now adding query for only loan entity.
+        public String schema(final AdHocQuerySearchConditions searchConditions, final MapSqlParameterSource params) {
+            final StringBuffer sql = new StringBuffer();
+            sql.append(
+                    "Select a.name as officeName, a.Product as productName, a.cnt as 'count', a.outstandingAmt as outstanding, a.percentOut as percentOut  ")
+                    .append("from (select mo.name, mp.name Product, sum(ifnull(ml.total_expected_repayment_derived,0.0)) TotalAmt, count(*) cnt, ")
+                    .append("sum(ifnull(ml.total_outstanding_derived,0.0)) outstandingAmt,  ")
+                    .append("(sum(ifnull(ml.total_outstanding_derived,0.0)) * 100 / sum(ifnull(ml.total_expected_repayment_derived,0.0))) percentOut ")
+                    .append("from m_loan ml inner join m_product_loan mp on mp.id=ml.product_id  ")
+                    .append("inner join m_client mc on mc.id=ml.client_id  ").append("inner join m_office mo on mo.id=mc.office_id  ");
+
+            if (searchConditions.getLoanStatus() != null && searchConditions.getLoanStatus().size() > 0) {
+                // If user requests for all statuses no need to add loanStatus
+                // filter
+                if (!searchConditions.getLoanStatus().contains("all")) {
+                    checkAndUpdateWhereClause(sql);
+                    params.addValue("loanStatus", searchConditions.getLoanStatus());
+                    sql.append(" ml.loan_status_id in (:loanStatus) ");
+                }
+            }
+
+            if (searchConditions.getLoanProducts() != null && searchConditions.getLoanProducts().size() > 0) {
+                checkAndUpdateWhereClause(sql);
+                params.addValue("loanProducts", searchConditions.getLoanProducts());
+                sql.append(" mp.id in (:loanProducts) ");
+            }
+
+            if (searchConditions.getOffices() != null && searchConditions.getOffices().size() > 0) {
+                checkAndUpdateWhereClause(sql);
+                params.addValue("offices", searchConditions.getOffices());
+                sql.append(" mo.id in (:offices) ");
+            }
+
+            if (StringUtils.isNotBlank(searchConditions.getLoanDateOption())) {
+                if (searchConditions.getLoanDateOption().equals(SearchConstants.SEARCH_LOAN_DATE.APPROVAL_DATE.getValue())) {
+                    checkAndUpdateWhereClause(sql);
+                    params.addValue("loanFromDate", searchConditions.getLoanFromDate().toDate());
+                    params.addValue("loanToDate", searchConditions.getLoanToDate().toDate());
+                    sql.append(" ( ml.approvedon_date between :loanFromDate and :loanToDate ) ");
+                } else if (searchConditions.getLoanDateOption().equals(SearchConstants.SEARCH_LOAN_DATE.CREATED_DATE.getValue())) {
+                    checkAndUpdateWhereClause(sql);
+                    params.addValue("loanFromDate", searchConditions.getLoanFromDate().toDate());
+                    params.addValue("loanToDate", searchConditions.getLoanToDate().toDate());
+                    sql.append(" ( ml.submittedon_date between :loanFromDate and :loanToDate ) ");
+                } else if (searchConditions.getLoanDateOption().equals(SearchConstants.SEARCH_LOAN_DATE.DISBURSAL_DATE.getValue())) {
+                    checkAndUpdateWhereClause(sql);
+                    params.addValue("loanFromDate", searchConditions.getLoanFromDate().toDate());
+                    params.addValue("loanToDate", searchConditions.getLoanToDate().toDate());
+                    sql.append(" ( ml.disbursedon_date between :loanFromDate and :loanToDate ) ");
+                }
+            }
+
+            sql.append(" group by mo.id) a ");
+
+            // update isWhereClauseAdded to false to add filters for derived
+            // table
+            isWhereClauseAdded = false;
+
+            if (searchConditions.getIncludeOutStandingAmountPercentage()) {
+                if (searchConditions.getOutStandingAmountPercentageCondition().equals("between")) {
+                    checkAndUpdateWhereClause(sql);
+                    // params.addValue("outStandingAmountPercentageCondition",
+                    // searchConditions.getOutStandingAmountPercentageCondition());
+                    params.addValue("minOutStandingAmountPercentage", searchConditions.getMinOutStandingAmountPercentage());
+                    params.addValue("maxOutStandingAmountPercentage", searchConditions.getMaxOutStandingAmountPercentage());
+                    sql.append(" ( a.percentOut between :minOutStandingAmountPercentage and :maxOutStandingAmountPercentage ) ");
+                } else {
+                    checkAndUpdateWhereClause(sql);
+                    // params.addValue("outStandingAmountPercentageCondition",
+                    // searchConditions.getOutStandingAmountPercentageCondition());
+                    params.addValue("outStandingAmountPercentage", searchConditions.getOutStandingAmountPercentage());
+                    sql.append(" a.percentOut ").append(searchConditions.getOutStandingAmountPercentageCondition())
+                            .append(" :outStandingAmountPercentage ");
+                }
+            }
+
+            if (searchConditions.getIncludeOutstandingAmount()) {
+                if (searchConditions.getOutstandingAmountCondition().equals("between")) {
+                    checkAndUpdateWhereClause(sql);
+                    // params.addValue("outstandingAmountCondition",
+                    // searchConditions.getOutstandingAmountCondition());
+                    params.addValue("minOutstandingAmount", searchConditions.getMinOutstandingAmount());
+                    params.addValue("maxOutstandingAmount", searchConditions.getMaxOutstandingAmount());
+                    sql.append(" ( a.outstandingAmt between :minOutstandingAmount and :maxOutstandingAmount ) ");
+                } else {
+                    checkAndUpdateWhereClause(sql);
+                    // params.addValue("outstandingAmountCondition",
+                    // searchConditions.getOutstandingAmountCondition());
+                    params.addValue("outstandingAmount", searchConditions.getOutstandingAmount());
+                    sql.append(" a.outstandingAmt ").append(searchConditions.getOutstandingAmountCondition())
+                            .append(" :outstandingAmount ");
+                }
+            }
+
+            return sql.toString();
+        }
+
+        private void checkAndUpdateWhereClause(final StringBuffer sql) {
+            if (isWhereClauseAdded) {
+                sql.append(" and ");
+            } else {
+                sql.append(" where ");
+                isWhereClauseAdded = true;
+            }
+        }
+
+        @Override
+        public AdHocSearchQueryData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+
+            final String officeName = rs.getString("officeName");
+            final String loanProductName = rs.getString("productName");
+            final Integer count = JdbcSupport.getInteger(rs, "count");
+            final BigDecimal loanOutStanding = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "outstanding").setScale(2,
+                    RoundingMode.HALF_UP);
+            final Double percentage = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "percentOut").setScale(2, RoundingMode.HALF_UP)
+                    .doubleValue();
+            return AdHocSearchQueryData.matchedResult(officeName, loanProductName, count, loanOutStanding, percentage);
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResource.java
new file mode 100644
index 0000000..60b6e07
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResource.java
@@ -0,0 +1,98 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.account.api.AccountTransfersApiResource;
+import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData;
+import org.apache.fineract.portfolio.self.account.data.SelfAccountTransferData;
+import org.apache.fineract.portfolio.self.account.data.SelfAccountTransferDataValidator;
+import org.apache.fineract.portfolio.self.account.service.SelfAccountTransferReadService;
+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/accounttransfers")
+@Component
+@Scope("singleton")
+public class SelfAccountTransferApiResource {
+
+	private final PlatformSecurityContext context;
+	private final DefaultToApiJsonSerializer<SelfAccountTransferData> toApiJsonSerializer;
+	private final AccountTransfersApiResource accountTransfersApiResource;
+	private final SelfAccountTransferReadService selfAccountTransferReadService;
+	private final ApiRequestParameterHelper apiRequestParameterHelper;
+	private final SelfAccountTransferDataValidator dataValidator;
+
+	@Autowired
+	public SelfAccountTransferApiResource(
+			final PlatformSecurityContext context,
+			final DefaultToApiJsonSerializer<SelfAccountTransferData> toApiJsonSerializer,
+			final AccountTransfersApiResource accountTransfersApiResource,
+			final SelfAccountTransferReadService selfAccountTransferReadService,
+			final ApiRequestParameterHelper apiRequestParameterHelper,
+			final SelfAccountTransferDataValidator dataValidator) {
+		this.context = context;
+		this.toApiJsonSerializer = toApiJsonSerializer;
+		this.accountTransfersApiResource = accountTransfersApiResource;
+		this.selfAccountTransferReadService = selfAccountTransferReadService;
+		this.apiRequestParameterHelper = apiRequestParameterHelper;
+		this.dataValidator = dataValidator;
+	}
+
+	@GET
+	@Path("template")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String template(@Context final UriInfo uriInfo) {
+
+		AppUser user = this.context.authenticatedUser();
+		Collection<SelfAccountTemplateData> templateData = this.selfAccountTransferReadService
+				.retrieveSelfAccountTemplateData(user);
+
+		final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+				.process(uriInfo.getQueryParameters());
+		return this.toApiJsonSerializer.serialize(settings,
+				new SelfAccountTransferData(templateData));
+	}
+
+	@POST
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String create(final String apiRequestBodyAsJson) {
+		this.dataValidator.validateCreate(apiRequestBodyAsJson);
+		return this.accountTransfersApiResource.create(apiRequestBodyAsJson);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java
new file mode 100644
index 0000000..03e872b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.data;
+
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.account.service.AccountTransferEnumerations;
+
+@SuppressWarnings("unused")
+public class SelfAccountTemplateData implements
+		Comparable<SelfAccountTemplateData> {
+
+	private final Long accountId;
+	private final String accountNo;
+	private final EnumOptionData accountType;
+	private final Long clientId;
+	private final String clientName;
+	private final Long officeId;
+	private final String officeName;
+
+	public SelfAccountTemplateData(final Long accountId,
+			final String accountNo, final Integer accountType,
+			final Long clientId, final String clientName, final Long officeId,
+			final String officeName) {
+		this.accountId = accountId;
+		this.accountNo = accountNo;
+		this.accountType = AccountTransferEnumerations.accountType(accountType);
+		this.clientId = clientId;
+		this.clientName = clientName;
+		this.officeId = officeId;
+		this.officeName = officeName;
+	}
+
+	public SelfAccountTemplateData(final Long accountId,
+			final Integer accountType, final Long clientId, final Long officeId) {
+		this.accountId = accountId;
+		this.accountNo = null;
+		this.accountType = AccountTransferEnumerations.accountType(accountType);
+		this.clientId = clientId;
+		this.clientName = null;
+		this.officeId = officeId;
+		this.officeName = null;
+	}
+
+	@Override
+	public int compareTo(final SelfAccountTemplateData obj) {
+		if (obj == null) {
+			return -1;
+		}
+		return new CompareToBuilder() //
+				.append(this.accountId, obj.accountId) //
+				.append(this.accountType.getValue(), obj.accountType.getValue()) //
+				.append(this.clientId, obj.clientId) //
+				.append(this.officeId, obj.officeId) //
+				.toComparison();
+	}
+
+	@Override
+	public boolean equals(final Object obj) {
+		if (obj == null) {
+			return false;
+		}
+		if (obj == this) {
+			return true;
+		}
+		if (obj.getClass() != getClass()) {
+			return false;
+		}
+		final SelfAccountTemplateData rhs = (SelfAccountTemplateData) obj;
+		return new EqualsBuilder() //
+				.append(this.accountId, rhs.accountId) //
+				.append(this.accountType.getValue(), rhs.accountType.getValue()) //
+				.append(this.clientId, rhs.clientId) //
+				.append(this.officeId, rhs.officeId) //
+				.isEquals();
+	}
+
+	@Override
+	public int hashCode() {
+		return new HashCodeBuilder(17, 37) //
+				.append(this.accountId) //
+				.append(this.accountType.getValue()) //
+				.append(this.clientId) //
+				.append(this.officeId) //
+				.toHashCode();
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java
new file mode 100644
index 0000000..b3d6669
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.data;
+
+import java.util.Collection;
+
+@SuppressWarnings("unused")
+public class SelfAccountTransferData {
+
+	private final Collection<SelfAccountTemplateData> accountOptions;
+
+	public SelfAccountTransferData(
+			final Collection<SelfAccountTemplateData> accountOptions) {
+		this.accountOptions = accountOptions;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java
new file mode 100644
index 0000000..c78852f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.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.portfolio.self.account.data;
+
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
+import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.self.account.service.SelfAccountTransferReadService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+
+@Component
+public class SelfAccountTransferDataValidator {
+
+	private final PlatformSecurityContext context;
+	private final SelfAccountTransferReadService selfAccountTransferReadService;
+	private final FromJsonHelper fromApiJsonHelper;
+
+	@Autowired
+	public SelfAccountTransferDataValidator(
+			final PlatformSecurityContext context,
+			final SelfAccountTransferReadService selfAccountTransferReadService,
+			final FromJsonHelper fromApiJsonHelper) {
+		this.context = context;
+		this.selfAccountTransferReadService = selfAccountTransferReadService;
+		this.fromApiJsonHelper = fromApiJsonHelper;
+	}
+
+	public void validateCreate(String apiRequestBodyAsJson) {
+		if (StringUtils.isBlank(apiRequestBodyAsJson)) {
+			throw new InvalidJsonException();
+		}
+
+		JsonElement element = this.fromApiJsonHelper
+				.parse(apiRequestBodyAsJson);
+
+		final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+		final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
+				dataValidationErrors).resource(ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+		final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(
+				fromOfficeIdParamName, element);
+		baseDataValidator.reset().parameter(fromOfficeIdParamName)
+				.value(fromOfficeId).notNull().integerGreaterThanZero();
+
+		final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(
+				fromClientIdParamName, element);
+		baseDataValidator.reset().parameter(fromClientIdParamName)
+				.value(fromClientId).notNull().integerGreaterThanZero();
+
+		final Long fromAccountId = this.fromApiJsonHelper.extractLongNamed(
+				fromAccountIdParamName, element);
+		baseDataValidator.reset().parameter(fromAccountIdParamName)
+				.value(fromAccountId).notNull().integerGreaterThanZero();
+
+		final Integer fromAccountType = this.fromApiJsonHelper
+				.extractIntegerSansLocaleNamed(fromAccountTypeParamName,
+						element);
+		baseDataValidator.reset().parameter(fromAccountTypeParamName)
+				.value(fromAccountType).notNull()
+				.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
+
+		final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(
+				toOfficeIdParamName, element);
+		baseDataValidator.reset().parameter(toOfficeIdParamName)
+				.value(toOfficeId).notNull().integerGreaterThanZero();
+
+		final Long toClientId = this.fromApiJsonHelper.extractLongNamed(
+				toClientIdParamName, element);
+		baseDataValidator.reset().parameter(toClientIdParamName)
+				.value(toClientId).notNull().integerGreaterThanZero();
+
+		final Long toAccountId = this.fromApiJsonHelper.extractLongNamed(
+				toAccountIdParamName, element);
+		baseDataValidator.reset().parameter(toAccountIdParamName)
+				.value(toAccountId).notNull().integerGreaterThanZero();
+
+		final Integer toAccountType = this.fromApiJsonHelper
+				.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
+		baseDataValidator.reset().parameter(toAccountTypeParamName)
+				.value(toAccountType).notNull()
+				.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
+
+		if (fromAccountType != null && fromAccountType == 1
+				&& toAccountType != null && toAccountType == 1) {
+			baseDataValidator
+					.reset()
+					.failWithCode("loan.to.loan.transfer.not.allowed",
+							"Cannot transfer from Loan account to another Loan account.");
+		}
+
+		throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+		SelfAccountTemplateData fromAccount = new SelfAccountTemplateData(
+				fromAccountId, fromAccountType, fromClientId, fromOfficeId);
+		SelfAccountTemplateData toAccount = new SelfAccountTemplateData(
+				toAccountId, toAccountType, toClientId, toOfficeId);
+
+		validateSelfUserAccounts(fromAccount, toAccount, baseDataValidator);
+		throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+	}
+
+	private void validateSelfUserAccounts(
+			final SelfAccountTemplateData fromAccount,
+			final SelfAccountTemplateData toAccount,
+			final DataValidatorBuilder baseDataValidator) {
+		AppUser user = this.context.authenticatedUser();
+		Collection<SelfAccountTemplateData> userValidAccounts = this.selfAccountTransferReadService
+				.retrieveSelfAccountTemplateData(user);
+
+		boolean validFromAccount = false;
+		for (SelfAccountTemplateData validAccount : userValidAccounts) {
+			if (validAccount.equals(fromAccount)) {
+				validFromAccount = true;
+				break;
+			}
+		}
+
+		boolean validToAccount = false;
+		for (SelfAccountTemplateData validAccount : userValidAccounts) {
+			if (validAccount.equals(toAccount)) {
+				validToAccount = true;
+				break;
+			}
+		}
+
+		if (!validFromAccount) {
+			baseDataValidator
+					.reset()
+					.failWithCode("invalid.from.account.details",
+							"Source account details doesn't match with valid user account details.");
+		}
+
+		if (!validToAccount) {
+			baseDataValidator
+					.reset()
+					.failWithCode("invalid.to.account.details",
+							"Destination account details doesn't match with valid user account details.");
+		}
+
+		if (fromAccount.equals(toAccount)) {
+			baseDataValidator.reset().failWithCode(
+					"same.from.to.account.details",
+					"Source and Destination account details are same.");
+		}
+
+	}
+
+	private void throwExceptionIfValidationWarningsExist(
+			final List<ApiParameterError> dataValidationErrors) {
+		if (!dataValidationErrors.isEmpty()) {
+			throw new PlatformApiDataValidationException(dataValidationErrors);
+		}
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadService.java
new file mode 100644
index 0000000..9bac993
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+public interface SelfAccountTransferReadService {
+
+	Collection<SelfAccountTemplateData> retrieveSelfAccountTemplateData(
+			AppUser user);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java
new file mode 100644
index 0000000..4c4af1b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SelfAccountTransferReadServiceImpl implements
+		SelfAccountTransferReadService {
+
+	private final JdbcTemplate jdbcTemplate;
+
+	@Autowired
+	public SelfAccountTransferReadServiceImpl(final RoutingDataSource dataSource) {
+		this.jdbcTemplate = new JdbcTemplate(dataSource);
+	}
+
+	@Override
+	public Collection<SelfAccountTemplateData> retrieveSelfAccountTemplateData(
+			AppUser user) {
+		SelfAccountTemplateMapper mapper = new SelfAccountTemplateMapper();
+		StringBuffer sql = new StringBuffer()
+				.append("select s.id as accountId, ")
+				.append("s.account_no as accountNo, ")
+				.append("2 as accountType, ")
+				.append("c.id as clientId, ")
+				.append("c.display_name as clientName, ")
+				.append("o.id as officeId, ")
+				.append("o.name as officeName ")
+				.append("from m_appuser as u ")
+				.append("inner join m_selfservice_user_client_mapping as map on u.id = map.appuser_id ")
+				.append("inner join m_client as c on map.client_id = c.id ")
+				.append("inner join m_office as o on c.office_id = o.id ")
+				.append("inner join m_savings_account as s on s.client_id = c.id ")
+				.append("where u.id = ? ")
+				.append("and s.status_enum = 300 ")
+				.append("union ")
+				.append("select l.id as accountId, ")
+				.append("l.account_no as accountNo, ")
+				.append("1 as accountType, ")
+				.append("c.id as clientId, ")
+				.append("c.display_name as clientName, ")
+				.append("o.id as officeId, ")
+				.append("o.name as officeName ")
+				.append("from m_appuser as u ")
+				.append("inner join m_selfservice_user_client_mapping as map on u.id = map.appuser_id ")
+				.append("inner join m_client as c on map.client_id = c.id ")
+				.append("inner join m_office as o on c.office_id = o.id ")
+				.append("inner join m_loan as l on l.client_id = c.id ")
+				.append("where u.id = ? ")
+				.append("and l.loan_status_id = 300 ");
+		return this.jdbcTemplate.query(sql.toString(), mapper, new Object[] {
+				user.getId(), user.getId() });
+	}
+
+	private final class SelfAccountTemplateMapper implements
+			RowMapper<SelfAccountTemplateData> {
+
+		@Override
+		public SelfAccountTemplateData mapRow(final ResultSet rs,
+				@SuppressWarnings("unused") final int rowNum)
+				throws SQLException {
+			final Long accountId = rs.getLong("accountId");
+			final String accountNo = rs.getString("accountNo");
+			final Integer accountType = rs.getInt("accountType");
+			final Long clientId = rs.getLong("clientId");
+			final String clientName = rs.getString("clientName");
+			final Long officeId = rs.getLong("officeId");
+			final String officeName = rs.getString("officeName");
+
+			return new SelfAccountTemplateData(accountId, accountNo,
+					accountType, clientId, clientName, officeId, officeName);
+		}
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/api/SelfClientsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/api/SelfClientsApiResource.java
new file mode 100644
index 0000000..b50c33e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/api/SelfClientsApiResource.java
@@ -0,0 +1,221 @@
+/**
+ * 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.client.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.documentmanagement.api.ImagesApiResource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.api.ClientChargesApiResource;
+import org.apache.fineract.portfolio.client.api.ClientTransactionsApiResource;
+import org.apache.fineract.portfolio.client.api.ClientsApiResource;
+import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
+import org.apache.fineract.portfolio.self.client.data.SelfClientDataValidator;
+import org.apache.fineract.portfolio.self.client.service.AppuserClientMapperReadService;
+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/clients")
+@Component
+@Scope("singleton")
+public class SelfClientsApiResource {
+
+	private final PlatformSecurityContext context;
+	private final ClientsApiResource clientApiResource;
+	private final ImagesApiResource imagesApiResource;
+	private final ClientChargesApiResource clientChargesApiResource;
+	private final ClientTransactionsApiResource clientTransactionsApiResource;
+	private final AppuserClientMapperReadService appUserClientMapperReadService;
+	private final SelfClientDataValidator dataValidator;
+
+	@Autowired
+	public SelfClientsApiResource(
+			final PlatformSecurityContext context,
+			final ClientsApiResource clientApiResource,
+			final ImagesApiResource imagesApiResource,
+			final ClientChargesApiResource clientChargesApiResource,
+			final ClientTransactionsApiResource clientTransactionsApiResource,
+			final AppuserClientMapperReadService appUserClientMapperReadService,
+			final SelfClientDataValidator dataValidator) {
+		this.context = context;
+		this.clientApiResource = clientApiResource;
+		this.imagesApiResource = imagesApiResource;
+		this.clientChargesApiResource = clientChargesApiResource;
+		this.clientTransactionsApiResource = clientTransactionsApiResource;
+		this.appUserClientMapperReadService = appUserClientMapperReadService;
+		this.dataValidator = dataValidator;
+	}
+
+	@GET
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveAll(@Context final UriInfo uriInfo,
+			@QueryParam("displayName") final String displayName,
+			@QueryParam("firstName") final String firstname,
+			@QueryParam("lastName") final String lastname,
+			@QueryParam("offset") final Integer offset,
+			@QueryParam("limit") final Integer limit,
+			@QueryParam("orderBy") final String orderBy,
+			@QueryParam("sortOrder") final String sortOrder) {
+
+		final String sqlSearch = null;
+		final Long officeId = null;
+		final String externalId = null;
+		final String hierarchy = null;
+		final Boolean orphansOnly = null;
+		return this.clientApiResource.retrieveAll(uriInfo, sqlSearch, officeId,
+				externalId, displayName, firstname, lastname, hierarchy,
+				offset, limit, orderBy, sortOrder, orphansOnly, true);
+	}
+
+	@GET
+	@Path("{clientId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveOne(@PathParam("clientId") final Long clientId,
+			@Context final UriInfo uriInfo) {
+
+		this.dataValidator.validateRetrieveOne(uriInfo);
+
+		validateAppuserClientsMapping(clientId);
+
+		final boolean staffInSelectedOfficeOnly = false;
+		return this.clientApiResource.retrieveOne(clientId, uriInfo,
+				staffInSelectedOfficeOnly);
+	}
+
+	@GET
+	@Path("{clientId}/accounts")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveAssociatedAccounts(
+			@PathParam("clientId") final Long clientId,
+			@Context final UriInfo uriInfo) {
+
+		validateAppuserClientsMapping(clientId);
+
+		return this.clientApiResource.retrieveAssociatedAccounts(clientId,
+				uriInfo);
+	}
+
+	@GET
+	@Path("{clientId}/images")
+	@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML,
+			MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.TEXT_PLAIN })
+	public Response retrieveImage(@PathParam("clientId") final Long clientId,
+			@QueryParam("maxWidth") final Integer maxWidth,
+			@QueryParam("maxHeight") final Integer maxHeight,
+			@QueryParam("output") final String output) {
+
+		validateAppuserClientsMapping(clientId);
+
+		return this.imagesApiResource.retrieveImage("clients", clientId,
+				maxWidth, maxHeight, output);
+	}
+
+	@GET
+	@Path("{clientId}/charges")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveAllClientCharges(
+			@PathParam("clientId") final Long clientId,
+			@DefaultValue(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL) @QueryParam(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS) final String chargeStatus,
+			@QueryParam("pendingPayment") final Boolean pendingPayment,
+			@Context final UriInfo uriInfo,
+			@QueryParam("limit") final Integer limit,
+			@QueryParam("offset") final Integer offset) {
+
+		validateAppuserClientsMapping(clientId);
+
+		return this.clientChargesApiResource.retrieveAllClientCharges(clientId,
+				chargeStatus, pendingPayment, uriInfo, limit, offset);
+	}
+
+	@GET
+	@Path("{clientId}/charges/{chargeId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveClientCharge(
+			@PathParam("clientId") final Long clientId,
+			@PathParam("chargeId") final Long chargeId,
+			@Context final UriInfo uriInfo) {
+
+		this.dataValidator.validateClientCharges(uriInfo);
+
+		validateAppuserClientsMapping(clientId);
+
+		return this.clientChargesApiResource.retrieveClientCharge(clientId,
+				chargeId, uriInfo);
+	}
+
+	@GET
+	@Path("{clientId}/transactions")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveAllClientTransactions(
+			@PathParam("clientId") final Long clientId,
+			@Context final UriInfo uriInfo,
+			@QueryParam("offset") final Integer offset,
+			@QueryParam("limit") final Integer limit) {
+
+		validateAppuserClientsMapping(clientId);
+
+		return this.clientTransactionsApiResource
+				.retrieveAllClientTransactions(clientId, uriInfo, offset, limit);
+	}
+
+	@GET
+	@Path("{clientId}/transactions/{transactionId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveClientTransaction(
+			@PathParam("clientId") final Long clientId,
+			@PathParam("transactionId") final Long transactionId,
+			@Context final UriInfo uriInfo) {
+
+		validateAppuserClientsMapping(clientId);
+
+		return this.clientTransactionsApiResource.retrieveClientTransaction(
+				clientId, transactionId, uriInfo);
+	}
+
+	private void validateAppuserClientsMapping(final Long clientId) {
+		AppUser user = this.context.authenticatedUser();
+		final boolean mappedClientId = this.appUserClientMapperReadService
+				.isClientMappedToUser(clientId, user.getId());
+		if (!mappedClientId) {
+			throw new ClientNotFoundException(clientId);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/data/SelfClientDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/data/SelfClientDataValidator.java
new file mode 100644
index 0000000..730da19
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/data/SelfClientDataValidator.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.portfolio.self.client.data;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SelfClientDataValidator {
+
+	private static final Set<String> allowedChargesAssociationParameters = new HashSet<>(
+			Arrays.asList("transactions"));
+
+	public void validateClientCharges(final UriInfo uriInfo) {
+		List<String> unsupportedParams = new ArrayList<>();
+
+		Set<String> associationParameters = ApiParameterHelper
+				.extractAssociationsForResponseIfProvided(uriInfo
+						.getQueryParameters());
+		if (!associationParameters.isEmpty()) {
+			associationParameters
+					.removeAll(allowedChargesAssociationParameters);
+			if (!associationParameters.isEmpty()) {
+				unsupportedParams.addAll(associationParameters);
+			}
+		}
+
+		if (uriInfo.getQueryParameters().getFirst("exclude") != null) {
+			unsupportedParams.add("exclude");
+		}
+
+		if (unsupportedParams.size() > 0) {
+			throw new UnsupportedParameterException(unsupportedParams);
+		}
+	}
+
+	public void validateRetrieveOne(final UriInfo uriInfo) {
+		List<String> unsupportedParams = new ArrayList<>();
+
+		final boolean templateRequest = ApiParameterHelper.template(uriInfo
+				.getQueryParameters());
+		if (templateRequest) {
+			unsupportedParams.add("template");
+		}
+
+		if (unsupportedParams.size() > 0) {
+			throw new UnsupportedParameterException(unsupportedParams);
+		}
+
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadService.java
new file mode 100644
index 0000000..e69fad9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadService.java
@@ -0,0 +1,25 @@
+/**
+ * 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.client.service;
+
+public interface AppuserClientMapperReadService {
+
+	public Boolean isClientMappedToUser(Long clientId, Long appUserId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadServiceImpl.java
new file mode 100644
index 0000000..8711dfd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadServiceImpl.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.client.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 AppuserClientMapperReadServiceImpl implements
+		AppuserClientMapperReadService {
+
+	private final JdbcTemplate jdbcTemplate;
+
+	@Autowired
+	public AppuserClientMapperReadServiceImpl(final RoutingDataSource dataSource) {
+		this.jdbcTemplate = new JdbcTemplate(dataSource);
+	}
+
+	@Override
+	public Boolean isClientMappedToUser(Long clientId, Long appUserId) {
+		return this.jdbcTemplate
+				.queryForObject(
+						"select case when (count(*) > 0) then true else false end "
+								+ " from m_selfservice_user_client_mapping where client_id = ? and appuser_id = ?",
+						new Object[] { clientId, appUserId }, Boolean.class);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResource.java
new file mode 100644
index 0000000..d317b5b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResource.java
@@ -0,0 +1,137 @@
+/**
+ * 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.loanaccount.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource;
+import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionsApiResource;
+import org.apache.fineract.portfolio.loanaccount.api.LoansApiResource;
+import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.apache.fineract.portfolio.self.loanaccount.data.SelfLoansDataValidator;
+import org.apache.fineract.portfolio.self.loanaccount.service.AppuserLoansMapperReadService;
+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/loans")
+@Component
+@Scope("singleton")
+public class SelfLoansApiResource {
+
+	private final PlatformSecurityContext context;
+	private final LoansApiResource loansApiResource;
+	private final LoanTransactionsApiResource loanTransactionsApiResource;
+	private final LoanChargesApiResource loanChargesApiResource;
+	private final AppuserLoansMapperReadService appuserLoansMapperReadService;
+	private final SelfLoansDataValidator dataValidator;
+
+	@Autowired
+	public SelfLoansApiResource(final PlatformSecurityContext context,
+			final LoansApiResource loansApiResource,
+			final LoanTransactionsApiResource loanTransactionsApiResource,
+			final LoanChargesApiResource loanChargesApiResource,
+			final AppuserLoansMapperReadService appuserLoansMapperReadService,
+			final SelfLoansDataValidator dataValidator) {
+		this.context = context;
+		this.loansApiResource = loansApiResource;
+		this.loanTransactionsApiResource = loanTransactionsApiResource;
+		this.loanChargesApiResource = loanChargesApiResource;
+		this.appuserLoansMapperReadService = appuserLoansMapperReadService;
+		this.dataValidator = dataValidator;
+	}
+
+	@GET
+	@Path("{loanId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveLoan(@PathParam("loanId") final Long loanId,
+			@Context final UriInfo uriInfo) {
+
+		this.dataValidator.validateRetrieveLoan(uriInfo);
+
+		validateAppuserLoanMapping(loanId);
+
+		final boolean staffInSelectedOfficeOnly = false;
+		return this.loansApiResource.retrieveLoan(loanId,
+				staffInSelectedOfficeOnly, uriInfo);
+	}
+
+	@GET
+	@Path("{loanId}/transactions/{transactionId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveTransaction(@PathParam("loanId") final Long loanId,
+			@PathParam("transactionId") final Long transactionId,
+			@Context final UriInfo uriInfo) {
+
+		this.dataValidator.validateRetrieveTransaction(uriInfo);
+
+		validateAppuserLoanMapping(loanId);
+
+		return this.loanTransactionsApiResource.retrieveTransaction(loanId,
+				transactionId, uriInfo);
+	}
+
+	@GET
+	@Path("{loanId}/charges")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveAllLoanCharges(
+			@PathParam("loanId") final Long loanId,
+			@Context final UriInfo uriInfo) {
+
+		validateAppuserLoanMapping(loanId);
+
+		return this.loanChargesApiResource.retrieveAllLoanCharges(loanId,
+				uriInfo);
+	}
+
+	@GET
+	@Path("{loanId}/charges/{chargeId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveLoanCharge(@PathParam("loanId") final Long loanId,
+			@PathParam("chargeId") final Long loanChargeId,
+			@Context final UriInfo uriInfo) {
+
+		validateAppuserLoanMapping(loanId);
+
+		return this.retrieveLoanCharge(loanId, loanChargeId, uriInfo);
+	}
+
+	private void validateAppuserLoanMapping(final Long loanId) {
+		AppUser user = this.context.authenticatedUser();
+		final boolean isLoanMappedToUser = this.appuserLoansMapperReadService
+				.isLoanMappedToUser(loanId, user.getId());
+		if (!isLoanMappedToUser) {
+			throw new LoanNotFoundException(loanId);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/data/SelfLoansDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/data/SelfLoansDataValidator.java
new file mode 100644
index 0000000..7560fba
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/data/SelfLoansDataValidator.java
@@ -0,0 +1,87 @@
+/**
+ * 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.loanaccount.data;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SelfLoansDataValidator {
+	private static final Set<String> allowedAssociationParameters = new HashSet<>(
+			Arrays.asList("repaymentSchedule", "futureSchedule",
+					"originalSchedule", "transactions", "charges",
+					"guarantors", "collateral", "linkedAccount",
+					"multiDisburseDetails"));
+
+	public void validateRetrieveLoan(final UriInfo uriInfo) {
+		List<String> unsupportedParams = new ArrayList<>();
+
+		validateTemplate(uriInfo, unsupportedParams);
+
+		Set<String> associationParameters = ApiParameterHelper
+				.extractAssociationsForResponseIfProvided(uriInfo
+						.getQueryParameters());
+		if (!associationParameters.isEmpty()) {
+			associationParameters.removeAll(allowedAssociationParameters);
+			if (!associationParameters.isEmpty()) {
+				unsupportedParams.addAll(associationParameters);
+			}
+		}
+
+		if (uriInfo.getQueryParameters().getFirst("exclude") != null) {
+			unsupportedParams.add("exclude");
+		}
+
+		throwExceptionIfReqd(unsupportedParams);
+	}
+
+	public void validateRetrieveTransaction(UriInfo uriInfo) {
+		List<String> unsupportedParams = new ArrayList<>();
+
+		validateTemplate(uriInfo, unsupportedParams);
+
+		throwExceptionIfReqd(unsupportedParams);
+
+	}
+
+	private void throwExceptionIfReqd(List<String> unsupportedParams) {
+		if (unsupportedParams.size() > 0) {
+			throw new UnsupportedParameterException(unsupportedParams);
+		}
+	}
+
+	private void validateTemplate(final UriInfo uriInfo,
+			List<String> unsupportedParams) {
+		final boolean templateRequest = ApiParameterHelper.template(uriInfo
+				.getQueryParameters());
+		if (templateRequest) {
+			unsupportedParams.add("template");
+		}
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/service/AppuserLoansMapperReadService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/service/AppuserLoansMapperReadService.java
new file mode 100644
index 0000000..eb94f73
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/service/AppuserLoansMapperReadService.java
@@ -0,0 +1,24 @@
+/**
+ * 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.loanaccount.service;
+
+public interface AppuserLoansMapperReadService {
+
+	public Boolean isLoanMappedToUser(Long loanId, Long appUserId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/service/AppuserLoansMapperReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/service/AppuserLoansMapperReadServiceImpl.java
new file mode 100644
index 0000000..ec3ec63
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/service/AppuserLoansMapperReadServiceImpl.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.loanaccount.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 AppuserLoansMapperReadServiceImpl implements
+		AppuserLoansMapperReadService {
+
+	private final JdbcTemplate jdbcTemplate;
+
+	@Autowired
+	public AppuserLoansMapperReadServiceImpl(final RoutingDataSource dataSource) {
+		this.jdbcTemplate = new JdbcTemplate(dataSource);
+	}
+
+	@Override
+	public Boolean isLoanMappedToUser(Long loanId, Long appUserId) {
+		return this.jdbcTemplate
+				.queryForObject(
+						"select case when (count(*) > 0) then true else false end "
+								+ " from m_selfservice_user_client_mapping as m "
+								+ " left join m_loan as l on l.client_id = m.client_id "
+								+ " where l.id = ? and m.appuser_id = ? ",
+						new Object[] { loanId, appUserId }, Boolean.class);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/api/SelfSavingsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/api/SelfSavingsApiResource.java
new file mode 100644
index 0000000..0414819
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/api/SelfSavingsApiResource.java
@@ -0,0 +1,148 @@
+/**
+ * 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.savings.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.savings.api.SavingsAccountChargesApiResource;
+import org.apache.fineract.portfolio.savings.api.SavingsAccountTransactionsApiResource;
+import org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource;
+import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
+import org.apache.fineract.portfolio.self.savings.data.SelfSavingsDataValidator;
+import org.apache.fineract.portfolio.self.savings.service.AppuserSavingsMapperReadService;
+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/savingsaccounts")
+@Component
+@Scope("singleton")
+public class SelfSavingsApiResource {
+
+	private final PlatformSecurityContext context;
+	private final SavingsAccountsApiResource savingsAccountsApiResource;
+	private final SavingsAccountChargesApiResource savingsAccountChargesApiResource;
+	private final SavingsAccountTransactionsApiResource savingsAccountTransactionsApiResource;
+	private final AppuserSavingsMapperReadService appuserSavingsMapperReadService;
+	private final SelfSavingsDataValidator dataValidator;
+
+	@Autowired
+	public SelfSavingsApiResource(
+			final PlatformSecurityContext context,
+			final SavingsAccountsApiResource savingsAccountsApiResource,
+			final SavingsAccountChargesApiResource savingsAccountChargesApiResource,
+			final SavingsAccountTransactionsApiResource savingsAccountTransactionsApiResource,
+			final AppuserSavingsMapperReadService appuserSavingsMapperReadService,
+			final SelfSavingsDataValidator dataValidator) {
+		this.context = context;
+		this.savingsAccountsApiResource = savingsAccountsApiResource;
+		this.savingsAccountChargesApiResource = savingsAccountChargesApiResource;
+		this.savingsAccountTransactionsApiResource = savingsAccountTransactionsApiResource;
+		this.appuserSavingsMapperReadService = appuserSavingsMapperReadService;
+		this.dataValidator = dataValidator;
+	}
+
+	@GET
+	@Path("{accountId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveSavings(
+			@PathParam("accountId") final Long accountId,
+			@DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus,
+			@Context final UriInfo uriInfo) {
+
+		this.dataValidator.validateRetrieveSavings(uriInfo);
+
+		validateAppuserSavingsAccountMapping(accountId);
+
+		final boolean staffInSelectedOfficeOnly = false;
+		return this.savingsAccountsApiResource.retrieveOne(accountId,
+				staffInSelectedOfficeOnly, chargeStatus, uriInfo);
+	}
+
+	@GET
+	@Path("{accountId}/transactions/{transactionId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveSavingsTransaction(
+			@PathParam("savingsId") final Long accountId,
+			@PathParam("transactionId") final Long transactionId,
+			@Context final UriInfo uriInfo) {
+
+		this.dataValidator.validateRetrieveSavingsTransaction(uriInfo);
+
+		validateAppuserSavingsAccountMapping(accountId);
+
+		return this.savingsAccountTransactionsApiResource.retrieveOne(
+				accountId, transactionId, uriInfo);
+	}
+
+	@GET
+	@Path("{accountId}/charges")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveAllSavingsAccountCharges(
+			@PathParam("savingsAccountId") final Long accountId,
+			@DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus,
+			@Context final UriInfo uriInfo) {
+
+		validateAppuserSavingsAccountMapping(accountId);
+
+		return this.savingsAccountChargesApiResource
+				.retrieveAllSavingsAccountCharges(accountId, chargeStatus,
+						uriInfo);
+	}
+
+	@GET
+	@Path("{accountId}/charges/{savingsAccountChargeId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveSavingsAccountCharge(
+			@PathParam("savingsAccountId") final Long accountId,
+			@PathParam("savingsAccountChargeId") final Long savingsAccountChargeId,
+			@Context final UriInfo uriInfo) {
+
+		validateAppuserSavingsAccountMapping(accountId);
+
+		return this.savingsAccountChargesApiResource
+				.retrieveSavingsAccountCharge(accountId,
+						savingsAccountChargeId, uriInfo);
+	}
+
+	private void validateAppuserSavingsAccountMapping(final Long accountId) {
+		AppUser user = this.context.authenticatedUser();
+		final boolean isMappedSavings = this.appuserSavingsMapperReadService
+				.isSavingsMappedToUser(accountId, user.getId());
+		if (!isMappedSavings) {
+			throw new SavingsAccountNotFoundException(accountId);
+		}
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/data/SelfSavingsDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/data/SelfSavingsDataValidator.java
new file mode 100644
index 0000000..3cbadf9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/data/SelfSavingsDataValidator.java
@@ -0,0 +1,86 @@
+/**
+ * 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.savings.data;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
+import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SelfSavingsDataValidator {
+
+	private static final Set<String> allowedAssociationParameters = new HashSet<>(
+			Arrays.asList(SavingsApiConstants.transactions,
+					SavingsApiConstants.charges));
+
+	public void validateRetrieveSavings(final UriInfo uriInfo) {
+		List<String> unsupportedParams = new ArrayList<>();
+
+		validateTemplate(uriInfo, unsupportedParams);
+
+		Set<String> associationParameters = ApiParameterHelper
+				.extractAssociationsForResponseIfProvided(uriInfo
+						.getQueryParameters());
+		if (!associationParameters.isEmpty()) {
+			associationParameters.removeAll(allowedAssociationParameters);
+			if (!associationParameters.isEmpty()) {
+				unsupportedParams.addAll(associationParameters);
+			}
+		}
+
+		if (uriInfo.getQueryParameters().getFirst("exclude") != null) {
+			unsupportedParams.add("exclude");
+		}
+
+		throwExceptionIfReqd(unsupportedParams);
+	}
+
+	public void validateRetrieveSavingsTransaction(final UriInfo uriInfo) {
+		List<String> unsupportedParams = new ArrayList<>();
+
+		validateTemplate(uriInfo, unsupportedParams);
+
+		throwExceptionIfReqd(unsupportedParams);
+	}
+
+	private void throwExceptionIfReqd(final List<String> unsupportedParams) {
+		if (unsupportedParams.size() > 0) {
+			throw new UnsupportedParameterException(unsupportedParams);
+		}
+	}
+
+	private void validateTemplate(final UriInfo uriInfo,
+			List<String> unsupportedParams) {
+		final boolean templateRequest = ApiParameterHelper.template(uriInfo
+				.getQueryParameters());
+		if (templateRequest) {
+			unsupportedParams.add("template");
+		}
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/service/AppuserSavingsMapperReadService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/service/AppuserSavingsMapperReadService.java
new file mode 100644
index 0000000..3923e84
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/service/AppuserSavingsMapperReadService.java
@@ -0,0 +1,25 @@
+/**
+ * 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.savings.service;
+
+public interface AppuserSavingsMapperReadService {
+
+	public Boolean isSavingsMappedToUser(Long savingsId, Long appUserId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/service/AppuserSavingsMapperReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/service/AppuserSavingsMapperReadServiceImpl.java
new file mode 100644
index 0000000..5d6acd7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/service/AppuserSavingsMapperReadServiceImpl.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.savings.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 AppuserSavingsMapperReadServiceImpl implements
+		AppuserSavingsMapperReadService {
+
+	private final JdbcTemplate jdbcTemplate;
+
+	@Autowired
+	public AppuserSavingsMapperReadServiceImpl(
+			final RoutingDataSource dataSource) {
+		this.jdbcTemplate = new JdbcTemplate(dataSource);
+	}
+
+	@Override
+	public Boolean isSavingsMappedToUser(Long savingsId, Long appUserId) {
+		return this.jdbcTemplate
+				.queryForObject(
+						"select case when (count(*) > 0) then true else false end "
+								+ " from m_selfservice_user_client_mapping as m "
+								+ " left join m_savings_account as s on s.client_id = m.client_id "
+								+ " where s.id = ? and m.appuser_id = ? ",
+						new Object[] { savingsId, appUserId }, Boolean.class);
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfAuthenticationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfAuthenticationApiResource.java
new file mode 100644
index 0000000..7b9b0f6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfAuthenticationApiResource.java
@@ -0,0 +1,54 @@
+/**
+ * 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.security.api;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.infrastructure.security.api.AuthenticationApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/self/authentication")
+@Component
+@Profile("basicauth")
+@Scope("singleton")
+public class SelfAuthenticationApiResource {
+
+	private final AuthenticationApiResource authenticationApiResource;
+
+	@Autowired
+	public SelfAuthenticationApiResource(
+			final AuthenticationApiResource authenticationApiResource) {
+		this.authenticationApiResource = authenticationApiResource;
+	}
+
+	@POST
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String authenticate(@QueryParam("username") final String username,
+			@QueryParam("password") final String password) {
+		return this.authenticationApiResource.authenticate(username, password);
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResource.java
new file mode 100644
index 0000000..56b3489
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResource.java
@@ -0,0 +1,54 @@
+/**
+ * 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.security.api;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.infrastructure.security.api.UserDetailsApiResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/self/userdetails")
+@Component
+@Profile("oauth")
+@Scope("singleton")
+public class SelfUserDetailsApiResource {
+
+	private final UserDetailsApiResource userDetailsApiResource;
+
+	@Autowired
+	public SelfUserDetailsApiResource(
+			final UserDetailsApiResource userDetailsApiResource) {
+		this.userDetailsApiResource = userDetailsApiResource;
+	}
+
+	@GET
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String fetchAuthenticatedUserData(
+			@QueryParam("access_token") final String accessToken) {
+		return this.userDetailsApiResource
+				.fetchAuthenticatedUserData(accessToken);
+	}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/constants/ShareProductApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/constants/ShareProductApiConstants.java
new file mode 100644
index 0000000..ae384f8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/constants/ShareProductApiConstants.java
@@ -0,0 +1,52 @@
+/**
+ * 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.shares.constants;
+
+public interface ShareProductApiConstants {
+
+    //Command Strings 
+    public final String PREIEW_DIVIDENDS_COMMAND_STRING = "previewdividends" ;
+    public final String POST_DIVIDENdS_COMMAND_STRING = "postdividends" ;
+    
+    String id_paramname = "id";
+    String name_paramname = "name";
+    String shortname_paramname = "shortName";
+    String description_paramname = "description";
+    String externalid_paramname = "externalId";
+    String totalshares_paramname = "totalShares";
+    String currency_paramname = "currencyCode";
+    String digitsafterdecimal_paramname = "digitsAfterDecimal";
+    String inmultiplesof_paramname = "inMultiplesOf";
+    String totalsharesissued_paramname = "totalSharesIssued";
+    String unitprice_paramname = "unitPrice";
+    String sharecapital_paramname = "shareCapital";
+    String suspenseaccount_paramname = "suspenseAccount";
+    String equityaccount_paramname = "equityAccount";
+    String minimumshares_paramname = "minimumShares";
+    String nominaltshares_paramname = "nominaltShares";
+    String maximumshares_paramname = "maximumShares";
+    String marketprice_paramname = "marketPrice";
+    String charges_paramname = "charges";
+    String allowdividendcalculationforinactiveclients_paramname = "allowDividendCalculationForInactiveClients";
+    String lockperiod_paramname = "lockPeriod";
+    String minimumactiveperiodfordividends_paramname = "minimumActivePeriodForDividends";
+
+    String startdate_paramname = "startDate";
+    String sharevalue_paramname = "shareValue";
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/DividendsData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/DividendsData.java
new file mode 100644
index 0000000..9bba515
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/DividendsData.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.shares.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class DividendsData {
+
+    private Long clientId;
+
+    private String clientName;
+
+    private String shareAccountNo;
+
+    private String savingsAccountNo;
+
+    private Long numberOfShares;
+
+    private BigDecimal dividendAmount;
+
+    private Date dividendIssuedDate;
+
+    public DividendsData(final Long clientId, final String clientName, final String savingsAccountNo, final Long numberOfShares,
+            final BigDecimal dividendAmount, final Date dividendIssuedDate) {
+        this.clientId = clientId;
+        this.clientName = clientName;
+        this.savingsAccountNo = savingsAccountNo;
+        this.numberOfShares = numberOfShares;
+        this.dividendAmount = dividendAmount;
+        this.dividendIssuedDate = dividendIssuedDate;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/ProductDividendsData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/ProductDividendsData.java
new file mode 100644
index 0000000..f48dc8f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/ProductDividendsData.java
@@ -0,0 +1,65 @@
+/**
+ * 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.shares.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+
+
+public class ProductDividendsData {
+
+    private Long id ;
+    
+    private Long productId ;
+    
+    private String productName ;
+    
+    private Date dividendsIssuedDate ;
+    
+    private BigDecimal dividendAmount ;
+    
+    private CurrencyData currency ;
+    
+    Collection<DividendsData> dividendsData ;
+    
+    public ProductDividendsData(final Long productId, final String productName, final Date dividendsIssuedDate, 
+            final BigDecimal dividendAmount, final CurrencyData currency, final Collection<DividendsData> dividendsData) {
+        this.productId = productId ;
+        this.productName = productName ;
+        this.dividendsIssuedDate = dividendsIssuedDate ;
+        this.dividendAmount = dividendAmount ;
+        this.dividendsData = dividendsData ;
+        this.currency = currency ;
+    }
+    
+    public void setId(Long id) {
+        this.id = id ;
+    }
+    
+    public Long getId() {
+        return this.id ;
+    }
+    
+    public Long getProductId() {
+        return productId ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/ShareMarketPriceData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/ShareMarketPriceData.java
new file mode 100644
index 0000000..90fb3fd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/ShareMarketPriceData.java
@@ -0,0 +1,42 @@
+/**
+ * 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.shares.data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class ShareMarketPriceData {
+
+    private final Date startDate;
+    
+    private final BigDecimal shareValue;
+
+    public ShareMarketPriceData(final Date startDate, final BigDecimal shareValue) {
+        this.startDate = startDate ;
+        this.shareValue = shareValue ;
+    }
+    
+    public Date getStartDate() {
+        return this.startDate;
+    }
+
+    public BigDecimal getShareValue() {
+        return this.shareValue;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/ShareProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/ShareProductData.java
new file mode 100644
index 0000000..60b744f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/data/ShareProductData.java
@@ -0,0 +1,160 @@
+/**
+ * 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.shares.data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.products.data.ProductData;
+
+public class ShareProductData implements ProductData{
+
+    private final Long id;
+    private final String name;
+    private final String shortName;
+    private final String description;
+    private final String externalId;
+    private final CurrencyData currency;
+    private final Long totalShares;
+    private final Long totalSharesIssued;
+    private final BigDecimal unitPrice;
+    private final BigDecimal shareCapital;
+    private final GLAccountData suspenseAccount;
+    private final GLAccountData equityAccount;
+    private final Long minimumShares;
+    private final Long nominaltShares;
+    private final Long maximumShares;
+    private final List<ShareMarketPriceData> marketPrice;
+    private final List<ChargeData> charges;
+    private Boolean allowDividendCalculationForInactiveClients;
+    private final EnumOptionData lockPeriod;
+    private final EnumOptionData minimumActivePeriodForDividends;
+
+    public ShareProductData(final Long id, final String name, final String shortName, final String description, final String externalId,
+            final CurrencyData currency, final Long totalShares, final Long totalSharesIssued, final BigDecimal unitPrice,
+            final BigDecimal shareCapital, final GLAccountData suspenseAccount, final GLAccountData equityAccount,
+            final Long minimumShares, final Long nominaltShares, final Long maximumShares, List<ShareMarketPriceData> marketPrice,
+            final List<ChargeData> charges, final Boolean allowDividendCalculationForInactiveClients, final EnumOptionData lockPeriod,
+            final EnumOptionData minimumActivePeriodForDividends) {
+        this.id = id;
+        this.name = name;
+        this.shortName = shortName;
+        this.description = description;
+        this.externalId = externalId;
+        this.currency = currency;
+        this.totalShares = totalShares;
+        this.totalSharesIssued = totalSharesIssued;
+        this.unitPrice = unitPrice;
+        this.shareCapital = shareCapital;
+        this.suspenseAccount = suspenseAccount;
+        this.equityAccount = equityAccount;
+        this.minimumShares = minimumShares;
+        this.nominaltShares = nominaltShares;
+        this.maximumShares = maximumShares;
+        this.marketPrice = marketPrice;
+        this.charges = charges;
+        this.allowDividendCalculationForInactiveClients = allowDividendCalculationForInactiveClients;
+        this.lockPeriod = lockPeriod;
+        this.minimumActivePeriodForDividends = minimumActivePeriodForDividends;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getShortName() {
+        return this.shortName;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public String getExternalId() {
+        return this.externalId;
+    }
+
+    public CurrencyData getCurrency() {
+        return this.currency;
+    }
+
+    public Long getTotalShares() {
+        return this.totalShares;
+    }
+
+    public Long getTotalSharesIssued() {
+        return this.totalSharesIssued;
+    }
+
+    public BigDecimal getUnitPrice() {
+        return this.unitPrice;
+    }
+
+    public BigDecimal getShareCapital() {
+        return this.shareCapital;
+    }
+
+    public GLAccountData getSuspenseAccount() {
+        return this.suspenseAccount;
+    }
+
+    public GLAccountData getEquityAccount() {
+        return this.equityAccount;
+    }
+
+    public Long getMinimumShares() {
+        return this.minimumShares;
+    }
+
+    public Long getNominaltShares() {
+        return this.nominaltShares;
+    }
+
+    public Long getMaximumShares() {
+        return this.maximumShares;
+    }
+
+    public List<ShareMarketPriceData> getMarketPrice() {
+        return this.marketPrice;
+    }
+
+    public List<ChargeData> getCharges() {
+        return this.charges;
+    }
+
+    public Boolean getAllowDividendCalculationForInactiveClients() {
+        return this.allowDividendCalculationForInactiveClients;
+    }
+
+    public EnumOptionData getLockPeriod() {
+        return this.lockPeriod;
+    }
+
+    public EnumOptionData getMinimumActivePeriodForDividends() {
+        return this.minimumActivePeriodForDividends;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareMarketPrice.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareMarketPrice.java
new file mode 100644
index 0000000..8b84c55
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareMarketPrice.java
@@ -0,0 +1,65 @@
+/**
+ * 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.shares.domain;
+
+import java.math.BigDecimal;
+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.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_share_marketprice")
+public class ShareMarketPrice extends AbstractPersistable<Long> {
+    
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "product_id", referencedColumnName = "id", nullable = false)
+    private ShareProduct product;
+    
+    @Column(name = "start_date")
+    @Temporal(TemporalType.DATE)
+    private Date startDate;
+    
+    @Column(name = "share_value", nullable = false)
+    private BigDecimal shareValue ;
+    
+    public ShareMarketPrice(final Date startDate, final BigDecimal shareValue) {
+        this.startDate = startDate ;
+        this.shareValue = shareValue ;
+    }
+    
+    public void setShareProduct(final ShareProduct product) {
+        this.product = product ;
+    }
+    
+    public Date getStartDate() {
+        return this.startDate ;
+    }
+    
+    public BigDecimal getPrice() {
+        return this.shareValue ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareProduct.java
new file mode 100644
index 0000000..2c50648
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareProduct.java
@@ -0,0 +1,363 @@
+/**
+ * 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.shares.domain;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.shares.data.ShareMarketPriceData;
+import org.apache.fineract.portfolio.shares.data.ShareProductData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.DateTime;
+
+@Entity
+@Table(name = "m_shareproducts")
+public class ShareProduct extends AbstractAuditableCustom<AppUser, Long> {
+
+    @Column(name = "name", nullable = false, unique = true)
+    private String name;
+
+    @Column(name = "short_name", nullable = false, unique = true)
+    private String shortName;
+
+    @Column(name = "description")
+    private String description;
+
+    @Column(name = "start_date")
+    @Temporal(TemporalType.DATE)
+    private Date startDate;
+
+    @Column(name = "end_date")
+    @Temporal(TemporalType.DATE)
+    private Date endDate;
+
+    @Column(name = "external_id", length = 100, nullable = true, unique = true)
+    private String externalId;
+
+    @Embedded
+    private MonetaryCurrency currency;
+
+    @Column(name = "total_shares", nullable = false)
+    private Long totalShares;
+
+    @Column(name = "total_shares_issued", nullable = false)
+    private Long totalSharesIssued;
+
+    @Column(name = "unit_price", nullable = false)
+    private BigDecimal unitPrice;
+
+    @Column(name = "share_capital", nullable = false)
+    private BigDecimal shareCapital;
+
+    @ManyToOne
+    @JoinColumn(name = "suspence_account", nullable = false)
+    private GLAccount suspenseAccount;
+
+    @ManyToOne
+    @JoinColumn(name = "equity_account", nullable = false)
+    private GLAccount equityAccount;
+
+    @Column(name = "minimum_client_shares")
+    private Long minimumShares;
+
+    @Column(name = "default_client_shares", nullable = false)
+    private Long nominalShares;
+
+    @Column(name = "maximum_client_shares")
+    private Long maximumShares;
+
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true)
+    Set<ShareMarketPrice> marketPrice = new HashSet<>();
+
+    @ManyToMany(fetch = FetchType.EAGER)
+    @JoinTable(name = "m_product_charges", joinColumns = @JoinColumn(name = "product_id"), inverseJoinColumns = @JoinColumn(name = "charge_id"))
+    private Set<Charge> charges;
+
+    @Column(name = "allow_dividends_inactive_clients")
+    private Boolean allowDividendCalculationForInactiveClients;
+
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "lock_period", nullable = true)
+    private PeriodFrequencyType lockPeriod;
+
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "dividend_active_period", nullable = true)
+    private PeriodFrequencyType minimumActivePeriodForDividends;
+
+    protected ShareProduct() {
+        
+    }
+    // FIXME Remove this method
+    public void setTempId(Long id) {
+        super.setId(id);
+    }
+
+    public ShareProduct(final String name, final String shortName, final String description, final String externalId,
+            final MonetaryCurrency currency, final Long totalShares, final Long totalSharesIssued, final BigDecimal unitPrice,
+            final BigDecimal shareCapital, final GLAccount suspenseAccount, final GLAccount equityAccount, final Long minimumShares,
+            final Long nominalShares, final Long maximumShares, Set<ShareMarketPrice> marketPrice, Set<Charge> charges,
+            final Boolean allowDividendCalculationForInactiveClients, final PeriodFrequencyType lockPeriod,
+            final PeriodFrequencyType minimumActivePeriodForDividends,
+            AppUser createdBy, DateTime createdDate, AppUser lastModifiedBy, DateTime lastModifiedDate) {
+        this.name = name;
+        this.shortName = shortName;
+        this.description = description;
+        this.externalId = externalId;
+        this.currency = currency;
+        this.totalShares = totalShares;
+        this.totalSharesIssued = totalSharesIssued;
+        this.unitPrice = unitPrice;
+        this.shareCapital = shareCapital;
+        this.suspenseAccount = suspenseAccount;
+        this.equityAccount = equityAccount;
+        this.minimumShares = minimumShares;
+        this.nominalShares = nominalShares;
+        this.maximumShares = maximumShares;
+        this.marketPrice = marketPrice;
+        this.charges = charges;
+        this.allowDividendCalculationForInactiveClients = allowDividendCalculationForInactiveClients;
+        this.lockPeriod = lockPeriod;
+        this.minimumActivePeriodForDividends = minimumActivePeriodForDividends;
+        setCreatedBy(createdBy) ;
+        setCreatedDate(createdDate) ;
+        setLastModifiedBy(lastModifiedBy) ;
+        setLastModifiedDate(lastModifiedDate) ;
+    }
+
+    public ShareProductData toData() {
+        GLAccountData suspenseAccount1 = new GLAccountData(suspenseAccount.getId(), suspenseAccount.getName(), suspenseAccount.getGlCode());
+        GLAccountData equityAccount1 = new GLAccountData(equityAccount.getId(), equityAccount.getName(), equityAccount.getGlCode());
+        List<ChargeData> chargeData = new ArrayList<>();
+        for(Charge charge: this.charges) {
+            chargeData.add(ChargeData.lookup(charge.getId(), charge.getName(), charge.isPenalty())) ;
+        }
+        List<ShareMarketPriceData> marketData = new ArrayList<>() ;
+        for(ShareMarketPrice pri: marketPrice) {
+            marketData.add(new ShareMarketPriceData(pri.getStartDate(), pri.getPrice())) ;
+        }
+        EnumOptionData lock = new EnumOptionData(this.lockPeriod.getValue().longValue(), this.lockPeriod.getCode(),
+                this.lockPeriod.toString());
+        EnumOptionData mini = new EnumOptionData(this.minimumActivePeriodForDividends.getValue().longValue(), this.minimumActivePeriodForDividends.getCode(),
+                this.minimumActivePeriodForDividends.toString());;
+        CurrencyData curr = new CurrencyData(currency.getCode(), "", currency.getDigitsAfterDecimal(), currency.getCurrencyInMultiplesOf(),
+                "", "");
+        return new ShareProductData(getId(), name, shortName, description, externalId, curr, totalShares, totalSharesIssued, unitPrice,
+                shareCapital, suspenseAccount1, equityAccount1, minimumShares, nominalShares, maximumShares, marketData, chargeData,
+                allowDividendCalculationForInactiveClients, lock, mini);
+    }
+
+    public boolean setProductName(String productName) {
+        boolean returnValue = false;
+        if (!this.name.equals(productName)) {
+            this.name = productName;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public String getProductName() {
+        return this.name ;
+    }
+    
+    public boolean setShortName(String shortName) {
+        boolean returnValue = false;
+        if (!this.shortName.equals(shortName)) {
+            this.shortName = shortName;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setDescription(String description) {
+        boolean returnValue = false;
+        if (!this.description.equals(description)) {
+            this.description = description;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setExternalId(String externalId) {
+        boolean returnValue = false;
+        if (!this.externalId.equals(externalId)) {
+            this.externalId = externalId;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setTotalShares(Long totalShares) {
+        boolean returnValue = false;
+        if (!this.totalShares.equals(totalShares)) {
+            this.totalShares = totalShares;
+            returnValue = true;
+        }
+        return returnValue;
+
+    }
+
+    public Long getTotalShares() {
+        return this.totalShares ;
+    }
+    
+    public boolean setTotalIssuedShares(Long totalSharesIssued) {
+        boolean returnValue = false;
+        if (!this.totalSharesIssued.equals(totalSharesIssued)) {
+            this.totalSharesIssued = totalSharesIssued;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setMonetaryCurrency(MonetaryCurrency currency) {
+        boolean returnValue = false;
+        if (!this.currency.equals(currency)) {
+            this.currency = currency;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public MonetaryCurrency getCurrency() {
+        return this.currency ;
+    }
+    
+    public boolean setUnitPrice(BigDecimal unitPrice) {
+        boolean returnValue = false;
+        if (!this.unitPrice.equals(unitPrice)) {
+            this.unitPrice = unitPrice;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setSuspenseAccount(GLAccount suspenseAccount) {
+        boolean returnValue = false;
+        if (!this.suspenseAccount.getId().equals(suspenseAccount.getId())) {
+            this.suspenseAccount = suspenseAccount;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setEquityAccount(GLAccount equityAccount) {
+        boolean returnValue = false;
+        if (!this.equityAccount.getId().equals(equityAccount.getId())) {
+            this.equityAccount = equityAccount;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setMinimumShares(final Long minimumShares) {
+        boolean returnValue = false;
+        if (!this.minimumShares.equals(minimumShares)) {
+            this.minimumShares = minimumShares;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setNominalShares(final Long nominalShares) {
+        boolean returnValue = false;
+        if (!this.nominalShares.equals(nominalShares)) {
+            this.nominalShares = nominalShares;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setMaximumShares(final Long maximumShares) {
+        boolean returnValue = false;
+        if (!this.maximumShares.equals(maximumShares)) {
+            this.maximumShares = maximumShares;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setMarketPrice(Set<ShareMarketPrice> marketPrice) {
+        this.marketPrice = marketPrice;
+        return true;
+    }
+
+    public boolean setCharges(Set<Charge> charges) {
+        this.charges = charges;
+        return true;
+    }
+
+    public boolean setAllowDividendCalculationForInactiveClients(Boolean allowDividendCalculationForInactiveClients) {
+        boolean returnValue = false;
+        if (!this.allowDividendCalculationForInactiveClients.equals(allowDividendCalculationForInactiveClients)) {
+            this.allowDividendCalculationForInactiveClients = allowDividendCalculationForInactiveClients;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setLockPeriod(final PeriodFrequencyType lockPeriod) {
+        boolean returnValue = false;
+        if (!this.lockPeriod.equals(lockPeriod)) {
+            this.lockPeriod = lockPeriod;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+
+    public boolean setminimumActivePeriodForDividends(final PeriodFrequencyType minimumActivePeriodForDividends) {
+        boolean returnValue = false;
+        if (!this.minimumActivePeriodForDividends.equals(minimumActivePeriodForDividends)) {
+            this.minimumActivePeriodForDividends = minimumActivePeriodForDividends;
+            returnValue = true;
+        }
+        return returnValue;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareProductRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareProductRepository.java
new file mode 100644
index 0000000..e6a3693
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareProductRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.shares.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+
+public interface ShareProductRepository extends JpaRepository<ShareProduct, Long>, JpaSpecificationExecutor<ShareProduct> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareProductTempRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareProductTempRepository.java
new file mode 100644
index 0000000..f6c0fac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/domain/ShareProductTempRepository.java
@@ -0,0 +1,79 @@
+/**
+ * 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.shares.domain;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.portfolio.accounts.domain.ShareAccount;
+import org.apache.fineract.portfolio.shares.data.ProductDividendsData;
+
+
+public class ShareProductTempRepository {
+
+    Map<Long, ShareProduct> cache = new HashMap<>() ;
+    Map<Long, ArrayList<ShareAccount>> accountsCache = new HashMap<>() ;
+    Map<Long, ProductDividendsData> dividendsCahe = new HashMap<>() ;
+    
+    private final static ShareProductTempRepository instance = new ShareProductTempRepository() ;
+    
+    private ShareProductTempRepository() {
+        
+    }
+    
+    public final static ShareProductTempRepository getInstance() {
+        return instance ;
+    }
+    
+    public ShareProduct fineOne(Long productId) {
+        return this.cache.get(productId) ;
+    }
+    
+    public Collection<ShareProduct> findAll() {
+        return this.cache.values() ;
+    }
+    public void save(ShareProduct product) {
+        Long id = new Long(cache.size() + 1) ;
+        product.setTempId(id) ;
+        this.cache.put(id, product) ;
+    }
+    
+    public void addAccount(Long productId, ShareAccount account) {
+        if(accountsCache.containsKey(productId)) {
+            ArrayList<ShareAccount> list = accountsCache.get(productId) ;
+            list.add(account) ;
+        }else {
+            ArrayList<ShareAccount> list = new ArrayList<>() ;
+            list.add(account) ;
+            accountsCache.put(productId, list) ;
+        }
+    }
+    
+    public ArrayList<ShareAccount> getAllAccounts(Long productId) {
+        return accountsCache.get(productId) ; 
+    }
+    
+    public void saveDividends(ProductDividendsData data) {
+        Long id = new Long(dividendsCahe.size()+1) ;
+        data.setId(id) ;
+        dividendsCahe.put(data.getProductId(), data) ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/handler/CreateShareProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/handler/CreateShareProductCommandHandler.java
new file mode 100644
index 0000000..4ba6023
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/handler/CreateShareProductCommandHandler.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.portfolio.shares.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.shares.service.ShareProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SHAREPRODUCT", action = "CREATE")
+public class CreateShareProductCommandHandler implements NewCommandSourceHandler {
+
+    private final ShareProductWritePlatformService shareProductWritePlatformService ;
+    
+    @Autowired
+    public CreateShareProductCommandHandler(final ShareProductWritePlatformService shareProductWritePlatformService) {
+        this.shareProductWritePlatformService = shareProductWritePlatformService ;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.shareProductWritePlatformService.createShareProduct(jsonCommand);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/handler/UpdateShareProductCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/handler/UpdateShareProductCommandHandler.java
new file mode 100644
index 0000000..c2003fa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/handler/UpdateShareProductCommandHandler.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.portfolio.shares.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.shares.service.ShareProductWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "SHAREPRODUCT", action = "UPDATE")
+public class UpdateShareProductCommandHandler implements NewCommandSourceHandler {
+
+    private final ShareProductWritePlatformService shareProductWritePlatformService ;
+    
+    @Autowired
+    public UpdateShareProductCommandHandler(final ShareProductWritePlatformService shareProductWritePlatformService) {
+        this.shareProductWritePlatformService = shareProductWritePlatformService ;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+        return this.shareProductWritePlatformService.updateProduct(jsonCommand.entityId(), jsonCommand);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/serialization/ShareProductDataSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/serialization/ShareProductDataSerializer.java
new file mode 100644
index 0000000..59c65d8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/serialization/ShareProductDataSerializer.java
@@ -0,0 +1,341 @@
+/**
+ * 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.shares.serialization;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
+import org.apache.fineract.portfolio.shares.constants.ShareProductApiConstants;
+import org.apache.fineract.portfolio.shares.domain.ShareMarketPrice;
+import org.apache.fineract.portfolio.shares.domain.ShareProduct;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+//This class responsibility is to validate data and serialize to entity and return object to the caller 
+@Service
+public class ShareProductDataSerializer {
+
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList(""));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    private final ChargeRepositoryWrapper chargeRepository;
+
+    private final GLAccountRepositoryWrapper glAccountRepository;
+
+    private final PlatformSecurityContext platformSecurityContext ;
+    
+    @Autowired
+    public ShareProductDataSerializer(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
+            final GLAccountRepositoryWrapper glAccountRepository,
+            final PlatformSecurityContext platformSecurityContext) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.chargeRepository = chargeRepository;
+        this.glAccountRepository = glAccountRepository;
+        this.platformSecurityContext = platformSecurityContext ;
+    }
+
+    public ShareProduct validateAndCreate(JsonCommand jsonCommand) {
+        if (StringUtils.isBlank(jsonCommand.json())) { throw new InvalidJsonException(); }
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        //this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, jsonCommand.json(), supportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("sharesproduct");
+        JsonElement element = jsonCommand.parsedJson();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        final String productName = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.name_paramname, element);
+        baseDataValidator.reset().parameter(ShareProductApiConstants.name_paramname).value(productName).notBlank()
+                .notExceedingLengthOf(200);
+        final String shortName = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.shortname_paramname, element);
+        baseDataValidator.reset().parameter(ShareProductApiConstants.shortname_paramname).value(shortName).notBlank()
+                .notExceedingLengthOf(10);
+        String description = null;
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.description_paramname, element)) {
+            description = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.description_paramname, element);
+        }
+
+        String externalId = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.externalid_paramname, element);
+        Long totalNumberOfShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.totalshares_paramname, element);
+        final String currencyCode = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.currency_paramname, element);
+        final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                ShareProductApiConstants.digitsafterdecimal_paramname, element);
+        final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                ShareProductApiConstants.inmultiplesof_paramname, element);
+        final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
+        final Long sharesIssued = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.totalsharesissued_paramname, element);
+        final BigDecimal unitPrice = this.fromApiJsonHelper.extractBigDecimalNamed(ShareProductApiConstants.unitprice_paramname, element,
+                locale);
+        final BigDecimal shareCapitalValue = null;
+        Long suspenseAccountId = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.suspenseaccount_paramname, element);
+        Long equityAccountId = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.equityaccount_paramname, element);
+        Long minimumClientShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.minimumshares_paramname, element);
+        Long nominalClientShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.nominaltshares_paramname, element);
+        Long maximumClientShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.maximumshares_paramname, element);
+        Set<ShareMarketPrice> marketPriceSet = asembleShareMarketPrice(element);
+        Set<Charge> charges = assembleListOfProductCharges(element, currencyCode);
+        Boolean allowdividendsForInactiveClients = this.fromApiJsonHelper.extractBooleanNamed(
+                ShareProductApiConstants.allowdividendcalculationforinactiveclients_paramname, element);
+        PeriodFrequencyType lockPeriod = extractPeriodType(ShareProductApiConstants.lockperiod_paramname, element);
+        PeriodFrequencyType minimumActivePeriod = extractPeriodType(ShareProductApiConstants.minimumactiveperiodfordividends_paramname,
+                element);
+        GLAccount suspenseAccount = glAccountRepository.findOneWithNotFoundDetection(suspenseAccountId);
+        GLAccount equityAccount = glAccountRepository.findOneWithNotFoundDetection(equityAccountId);
+        AppUser modifiedBy = null;
+        DateTime modifiedOn = null;
+        AppUser createdBy = platformSecurityContext.authenticatedUser() ;
+        DateTime createdDate = DateUtils.getLocalDateTimeOfTenant().toDateTime() ;
+        ShareProduct product = new ShareProduct(productName, shortName, description, externalId, currency, totalNumberOfShares,
+                sharesIssued, unitPrice, shareCapitalValue, suspenseAccount, equityAccount, minimumClientShares, nominalClientShares,
+                maximumClientShares, marketPriceSet, charges, allowdividendsForInactiveClients, lockPeriod, minimumActivePeriod,
+                createdBy, createdDate, modifiedBy, modifiedOn);
+        return product;
+    }
+
+    private PeriodFrequencyType extractPeriodType(String paramName, final JsonElement element) {
+        PeriodFrequencyType frequencyType = PeriodFrequencyType.INVALID;
+        frequencyType = PeriodFrequencyType.fromInt(this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paramName, element));
+        return frequencyType;
+    }
+
+    private Set<ShareMarketPrice> asembleShareMarketPrice(final JsonElement element) {
+        Set<ShareMarketPrice> set = null;
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.marketprice_paramname, element)) {
+            set = new HashSet<>();
+            JsonArray array = this.fromApiJsonHelper.extractJsonArrayNamed(ShareProductApiConstants.marketprice_paramname, element);
+            for (JsonElement arrayElement : array) {
+                LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareProductApiConstants.startdate_paramname,
+                        arrayElement);
+                final BigDecimal shareValue = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+                        ShareProductApiConstants.sharevalue_paramname, arrayElement);
+                ShareMarketPrice obj = new ShareMarketPrice(localDate.toDate(), shareValue);
+                set.add(obj);
+            }
+        }
+        return set;
+    }
+
+    private Set<Charge> assembleListOfProductCharges(final JsonElement element, final String currencyCode) {
+        final Set<Charge> charges = new HashSet<>();
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.charges_paramname, element)) {
+            JsonArray chargesArray = this.fromApiJsonHelper.extractJsonArrayNamed(ShareProductApiConstants.charges_paramname, element);
+            if (chargesArray != null) {
+                for (int i = 0; i < chargesArray.size(); i++) {
+                    final JsonObject jsonObject = chargesArray.get(i).getAsJsonObject();
+                    if (jsonObject.has("id")) {
+                        final Long id = jsonObject.get("id").getAsLong();
+                        final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(id);
+                        if (!currencyCode.equals(charge.getCurrencyCode())) {
+                            final String errorMessage = "Charge and Loan Product must have the same currency.";
+                            throw new InvalidCurrencyException("charge", "attach.to.loan.product", errorMessage);
+                        }
+                        charges.add(charge);
+                    }
+                }
+            }
+        }
+        return charges;
+    }
+
+    public Map<String, Object> validateAndUpdate(JsonCommand jsonCommand, ShareProduct product) {
+        Map<String, Object> actualChanges = new HashMap<>();
+
+        if (StringUtils.isBlank(jsonCommand.json())) { throw new InvalidJsonException(); }
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        //this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, jsonCommand.json(), supportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("sharesproduct");
+
+        JsonElement element = jsonCommand.parsedJson();
+        final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.name_paramname, element)) {
+            final String productName = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.name_paramname, element);
+            if (product.setProductName(productName)) {
+                actualChanges.put(ShareProductApiConstants.name_paramname, productName);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.shortname_paramname, element)) {
+            final String shortName = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.shortname_paramname, element);
+            if (product.setShortName(shortName)) {
+                actualChanges.put(ShareProductApiConstants.shortname_paramname, shortName);
+            }
+        }
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.description_paramname, element)) {
+            String description = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.description_paramname, element);
+            if (product.setDescription(description)) {
+                actualChanges.put(ShareProductApiConstants.description_paramname, description);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.externalid_paramname, element)) {
+            String externalId = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.externalid_paramname, element);
+            if (product.setExternalId(externalId)) {
+                actualChanges.put(ShareProductApiConstants.externalid_paramname, externalId);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.totalshares_paramname, element)) {
+            Long totalShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.totalshares_paramname, element);
+            if (product.setTotalShares(totalShares)) {
+                actualChanges.put(ShareProductApiConstants.totalshares_paramname, totalShares);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.totalsharesissued_paramname, element)) {
+            final Long sharesIssued = this.fromApiJsonHelper
+                    .extractLongNamed(ShareProductApiConstants.totalsharesissued_paramname, element);
+            if (product.setTotalIssuedShares(sharesIssued)) {
+                actualChanges.put(ShareProductApiConstants.totalsharesissued_paramname, sharesIssued);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.currency_paramname, element)
+                && this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.digitsafterdecimal_paramname, element)
+                && this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.inmultiplesof_paramname, element)) {
+            final String currencyCode = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.currency_paramname, element);
+            final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                    ShareProductApiConstants.digitsafterdecimal_paramname, element);
+            final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
+                    ShareProductApiConstants.inmultiplesof_paramname, element);
+            final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
+            if (product.setMonetaryCurrency(currency)) {
+                actualChanges.put(ShareProductApiConstants.currency_paramname, currency);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.unitprice_paramname, element)) {
+            final BigDecimal unitPrice = this.fromApiJsonHelper.extractBigDecimalNamed(ShareProductApiConstants.unitprice_paramname,
+                    element, locale);
+            if (product.setUnitPrice(unitPrice)) {
+                actualChanges.put(ShareProductApiConstants.unitprice_paramname, unitPrice);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.suspenseaccount_paramname, element)) {
+            Long suspenseAccountId = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.suspenseaccount_paramname, element);
+            GLAccount suspenseAccount = glAccountRepository.findOneWithNotFoundDetection(suspenseAccountId);
+            if (product.setSuspenseAccount(suspenseAccount)) {
+                actualChanges.put(ShareProductApiConstants.suspenseaccount_paramname, suspenseAccount);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.equityaccount_paramname, element)) {
+            Long equityAccountId = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.equityaccount_paramname, element);
+            GLAccount equityAccount = glAccountRepository.findOneWithNotFoundDetection(equityAccountId);
+            if (product.setEquityAccount(equityAccount)) {
+                actualChanges.put(ShareProductApiConstants.equityaccount_paramname, equityAccount);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.minimumshares_paramname, element)) {
+            Long minimumClientShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.minimumshares_paramname, element);
+            if (product.setMinimumShares(minimumClientShares)) {
+                actualChanges.put(ShareProductApiConstants.minimumshares_paramname, minimumClientShares);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.nominaltshares_paramname, element)) {
+            Long nominalClientShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.nominaltshares_paramname, element);
+            if (product.setNominalShares(nominalClientShares)) {
+                actualChanges.put(ShareProductApiConstants.nominaltshares_paramname, nominalClientShares);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.maximumshares_paramname, element)) {
+            Long maximumClientShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.maximumshares_paramname, element);
+            if (product.setMaximumShares(maximumClientShares)) {
+                actualChanges.put(ShareProductApiConstants.maximumshares_paramname, maximumClientShares);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.marketprice_paramname, element)) {
+            Set<ShareMarketPrice> marketPrice = asembleShareMarketPrice(element);
+            if (product.setMarketPrice(marketPrice)) {
+                actualChanges.put(ShareProductApiConstants.marketprice_paramname, marketPrice);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.charges_paramname, element)) {
+            final String currencyCode = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.currency_paramname, element);
+            Set<Charge> charges = assembleListOfProductCharges(element, currencyCode);
+            if (product.setCharges(charges)) {
+                actualChanges.put(ShareProductApiConstants.charges_paramname, charges);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.allowdividendcalculationforinactiveclients_paramname, element)) {
+            Boolean allowdividendsForInactiveClients = this.fromApiJsonHelper.extractBooleanNamed(
+                    ShareProductApiConstants.allowdividendcalculationforinactiveclients_paramname, element);
+            if (product.setAllowDividendCalculationForInactiveClients(allowdividendsForInactiveClients)) {
+                actualChanges.put(ShareProductApiConstants.allowdividendcalculationforinactiveclients_paramname,
+                        allowdividendsForInactiveClients);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.lockperiod_paramname, element)) {
+            PeriodFrequencyType lockPeriod = extractPeriodType(ShareProductApiConstants.lockperiod_paramname, element);
+            if (product.setLockPeriod(lockPeriod)) {
+                actualChanges.put(ShareProductApiConstants.lockperiod_paramname, lockPeriod);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.minimumactiveperiodfordividends_paramname, element)) {
+            PeriodFrequencyType minimumActivePeriod = extractPeriodType(ShareProductApiConstants.minimumactiveperiodfordividends_paramname,
+                    element);
+            if (product.setminimumActivePeriodForDividends(minimumActivePeriod)) {
+                actualChanges.put(ShareProductApiConstants.minimumactiveperiodfordividends_paramname, minimumActivePeriod);
+            }
+        }
+        return actualChanges;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductCommandsServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductCommandsServiceImpl.java
new file mode 100644
index 0000000..3257adf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductCommandsServiceImpl.java
@@ -0,0 +1,105 @@
+/**
+ * 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.shares.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+
+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.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.accounts.domain.ShareAccount;
+import org.apache.fineract.portfolio.products.service.ProductCommandsService;
+import org.apache.fineract.portfolio.shares.constants.ShareProductApiConstants;
+import org.apache.fineract.portfolio.shares.data.DividendsData;
+import org.apache.fineract.portfolio.shares.data.ProductDividendsData;
+import org.apache.fineract.portfolio.shares.domain.ShareProduct;
+import org.apache.fineract.portfolio.shares.domain.ShareProductTempRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonElement;
+
+@Service(value = "SHAREPRODUCT_COMMANDSERVICE")
+public class ShareProductCommandsServiceImpl implements ProductCommandsService {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public ShareProductCommandsServiceImpl(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public ProductDividendsData previewDividends(Long productId, JsonCommand jsonCommand) {
+        ArrayList<ShareAccount> accounts = ShareProductTempRepository.getInstance().getAllAccounts(productId);
+        ShareProduct product = ShareProductTempRepository.getInstance().fineOne(productId);
+        Long total = product.getTotalShares();
+        JsonElement element = jsonCommand.parsedJson();
+        final BigDecimal totalDividendAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("dividendAmount", element);
+        BigDecimal perShareValue = totalDividendAmount.divide(new BigDecimal(total));
+        Date date = new Date();
+        ArrayList<DividendsData> dividends = new ArrayList<>();
+        for (ShareAccount account : accounts) {
+            if(account.getStatus().equals("Approved")) {
+                BigDecimal val = perShareValue.multiply(new BigDecimal(account.getTotalShares()));
+                DividendsData data = new DividendsData(account.getClientId(), account.getClientName(), account.getSavingsAccountNo(),
+                        account.getTotalShares(), val, date);
+                dividends.add(data);    
+            }
+        }
+        
+        MonetaryCurrency currency = product.getCurrency() ;
+        CurrencyData cur =  new CurrencyData(currency.getCode(), "", currency.getDigitsAfterDecimal(), currency.getCurrencyInMultiplesOf(),
+                "", "");
+        ProductDividendsData toReturn = new ProductDividendsData(productId, product.getProductName(), date, totalDividendAmount, cur, dividends);
+        return toReturn;
+    }
+
+    public CommandProcessingResult postDividends(Long productId, JsonCommand jsonCommand) {
+        try {
+            ProductDividendsData data = previewDividends(productId, jsonCommand);
+            ShareProductTempRepository.getInstance().saveDividends(data);
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(jsonCommand.commandId()) //
+                    .withEntityId(data.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Override
+    public Object handleCommand(Long productId, String command, String jsonBody) {
+        final JsonElement parsedCommand = this.fromApiJsonHelper.parse(jsonBody);
+        final JsonCommand jsonCommand = JsonCommand.from(jsonBody, parsedCommand, this.fromApiJsonHelper, null, null, null, null, null,
+                null, null, null, null, null);
+        if (ShareProductApiConstants.PREIEW_DIVIDENDS_COMMAND_STRING.equals(command)) {
+            return previewDividends(productId, jsonCommand);
+        } else if (ShareProductApiConstants.POST_DIVIDENdS_COMMAND_STRING.equals(command)) { return postDividends(productId,
+                jsonCommand); }
+        // throw unknow commandexception
+        return CommandProcessingResult.empty();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductReadPlatformServiceImpl.java
new file mode 100644
index 0000000..0158d06
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductReadPlatformServiceImpl.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.portfolio.shares.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.fineract.portfolio.products.data.ProductData;
+import org.apache.fineract.portfolio.products.service.ProductReadPlatformService;
+import org.apache.fineract.portfolio.shares.domain.ShareProduct;
+import org.apache.fineract.portfolio.shares.domain.ShareProductTempRepository;
+import org.springframework.stereotype.Service;
+
+
+@Service(value = "shareReadPlatformService")
+public class ShareProductReadPlatformServiceImpl implements ProductReadPlatformService{
+
+    ShareProductTempRepository repo = ShareProductTempRepository.getInstance() ;
+    
+    @Override
+    public Collection<ProductData> retrieveAllProducts() {
+        Collection<ShareProduct> entities = repo.findAll() ;
+        List<ProductData> toReturn = new ArrayList<>() ;
+        for(ShareProduct entity: entities) {
+            toReturn.add(entity.toData()) ;
+        }
+        return toReturn;
+    }
+
+    @Override
+    public ProductData retrieveOne(Long productId) {
+        ShareProduct product = repo.fineOne(productId) ;
+        return product.toData() ;
+    }
+
+    @Override
+    public ProductData retrieveTemplate() {
+        return null;
+    }
+
+    @Override
+    public Set<String> getResponseDataParams() {
+        return null;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductWritePlatformService.java
new file mode 100644
index 0000000..23a2051
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductWritePlatformService.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.shares.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+
+public interface ShareProductWritePlatformService {
+
+    CommandProcessingResult createShareProduct(JsonCommand jsonCommand) ;
+    
+    CommandProcessingResult updateProduct(Long productId, JsonCommand command);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..f245c2b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shares/service/ShareProductWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,90 @@
+/**
+ * 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.shares.service;
+
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.portfolio.products.exception.ProductNotFoundException;
+import org.apache.fineract.portfolio.shares.domain.ShareProduct;
+import org.apache.fineract.portfolio.shares.domain.ShareProductRepository;
+import org.apache.fineract.portfolio.shares.domain.ShareProductTempRepository;
+import org.apache.fineract.portfolio.shares.serialization.ShareProductDataSerializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ShareProductWritePlatformServiceJpaRepositoryImpl implements ShareProductWritePlatformService {
+
+    private final ShareProductRepository repository;
+    private final ShareProductDataSerializer serializer;
+    
+    @Autowired
+    public ShareProductWritePlatformServiceJpaRepositoryImpl(final ShareProductRepository repository,
+            final ShareProductDataSerializer serializer) {
+        this.repository = repository;
+        this.serializer = serializer;
+    }
+
+    @Override
+    public CommandProcessingResult createShareProduct(JsonCommand jsonCommand) {
+        try {
+            ShareProduct product = this.serializer.validateAndCreate(jsonCommand);
+            //this.repository.save(product);
+            ShareProductTempRepository.getInstance().save(product) ;
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(jsonCommand.commandId()) //
+                    .withEntityId(product.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(jsonCommand, dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+
+    @Override
+    public CommandProcessingResult updateProduct(Long productId, JsonCommand jsonCommand) {
+        try {
+            //ShareProduct product = this.repository.findOne(productId);
+            ShareProduct product = ShareProductTempRepository.getInstance().fineOne(productId) ;
+            if (product == null) { throw new ProductNotFoundException(productId, "share"); }
+            final Map<String, Object> changes = this.serializer.validateAndUpdate(jsonCommand, product);
+            if(!changes.isEmpty()) {
+                //this.repository.saveAndFlush(product) ;
+            }
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(jsonCommand.commandId()) //
+                    .withEntityId(productId) //
+                    .with(changes) //
+                    .build();
+        } catch (DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(jsonCommand, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/api/TransferApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/api/TransferApiConstants.java
new file mode 100644
index 0000000..0a0376d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/api/TransferApiConstants.java
@@ -0,0 +1,58 @@
+/**
+ * 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.transfer.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class TransferApiConstants {
+
+    // general
+    public static final String localeParamName = "locale";
+    public static final String dateFormatParamName = "dateFormat";
+
+    // request parameters
+    public static final String idParamName = "id";
+    public static final String destinationGroupIdParamName = "destinationGroupId";
+    public static final String clients = "clients";
+    public static final String inheritDestinationGroupLoanOfficer = "inheritDestinationGroupLoanOfficer";
+    public static final String newStaffIdParamName = "staffId";
+    public static final String transferActiveLoans = "transferActiveLoans";
+    public static final String destinationOfficeIdParamName = "destinationOfficeId";
+    public static final String note = "note";
+
+    public static final Set<String> TRANSFER_CLIENTS_BETWEEN_GROUPS_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, destinationGroupIdParamName, clients, inheritDestinationGroupLoanOfficer, newStaffIdParamName,
+            transferActiveLoans));
+
+    public static final Set<String> PROPOSE_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, destinationOfficeIdParamName, transferActiveLoans, note));
+
+    public static final Set<String> ACCEPT_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet<>(Arrays.asList(newStaffIdParamName,
+            destinationGroupIdParamName, note));
+
+    public static final Set<String> PROPOSE_AND_ACCEPT_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+            dateFormatParamName, destinationOfficeIdParamName, transferActiveLoans, newStaffIdParamName, destinationGroupIdParamName, note));
+
+    public static final Set<String> REJECT_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet<>(Arrays.asList(note));
+
+    public static final Set<String> WITHDRAW_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet<>(Arrays.asList(note));
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/data/TransfersDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/data/TransfersDataValidator.java
new file mode 100644
index 0000000..885d737
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/data/TransfersDataValidator.java
@@ -0,0 +1,214 @@
+/**
+ * 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.transfer.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
+import org.apache.fineract.portfolio.transfer.api.TransferApiConstants;
+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 TransfersDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public TransfersDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+
+    public void validateForClientsTransferBetweenGroups(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                TransferApiConstants.TRANSFER_CLIENTS_BETWEEN_GROUPS_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
+
+        final Long destinationGroupId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.destinationGroupIdParamName, element);
+        baseDataValidator.reset().parameter(TransferApiConstants.destinationGroupIdParamName).value(destinationGroupId).notNull()
+                .integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.newStaffIdParamName, element)) {
+            final Long newStaffId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.newStaffIdParamName, element);
+            baseDataValidator.reset().parameter(TransferApiConstants.newStaffIdParamName).value(newStaffId).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.inheritDestinationGroupLoanOfficer, element)) {
+            final Boolean inheritDestinationGroupLoanOfficer = this.fromApiJsonHelper.extractBooleanNamed(
+                    TransferApiConstants.inheritDestinationGroupLoanOfficer, element);
+            baseDataValidator.reset().parameter(TransferApiConstants.inheritDestinationGroupLoanOfficer)
+                    .value(inheritDestinationGroupLoanOfficer).notNull();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForProposeClientTransfer(final String json) {
+
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.PROPOSE_CLIENT_TRANSFER_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+
+        final Long destinationOfficeId = this.fromApiJsonHelper
+                .extractLongNamed(TransferApiConstants.destinationOfficeIdParamName, element);
+        baseDataValidator.reset().parameter(TransferApiConstants.destinationOfficeIdParamName).value(destinationOfficeId).notNull()
+                .integerGreaterThanZero();
+
+        validateNote(baseDataValidator, element);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForAcceptClientTransfer(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.ACCEPT_CLIENT_TRANSFER_DATA_PARAMETERS);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.newStaffIdParamName, element)) {
+            final Long newStaffId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.newStaffIdParamName, element);
+            baseDataValidator.reset().parameter(TransferApiConstants.newStaffIdParamName).value(newStaffId).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.destinationGroupIdParamName, element)) {
+            final Long destinationGroupId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.destinationGroupIdParamName,
+                    element);
+            baseDataValidator.reset().parameter(TransferApiConstants.destinationGroupIdParamName).value(destinationGroupId).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        validateNote(baseDataValidator, element);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForProposeAndAcceptClientTransfer(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                TransferApiConstants.PROPOSE_AND_ACCEPT_CLIENT_TRANSFER_DATA_PARAMETERS);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final Long destinationOfficeId = this.fromApiJsonHelper
+                .extractLongNamed(TransferApiConstants.destinationOfficeIdParamName, element);
+        baseDataValidator.reset().parameter(TransferApiConstants.destinationOfficeIdParamName).value(destinationOfficeId).notNull()
+                .integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.newStaffIdParamName, element)) {
+            final Long newStaffId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.newStaffIdParamName, element);
+            baseDataValidator.reset().parameter(TransferApiConstants.newStaffIdParamName).value(newStaffId).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.destinationGroupIdParamName, element)) {
+            final Long destinationGroupId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.destinationGroupIdParamName,
+                    element);
+            baseDataValidator.reset().parameter(TransferApiConstants.destinationGroupIdParamName).value(destinationGroupId).notNull()
+                    .integerGreaterThanZero();
+        }
+
+        validateNote(baseDataValidator, element);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForRejectClientTransfer(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.REJECT_CLIENT_TRANSFER_DATA_PARAMETERS);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateNote(baseDataValidator, element);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForWithdrawClientTransfer(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper
+                .checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.WITHDRAW_CLIENT_TRANSFER_DATA_PARAMETERS);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateNote(baseDataValidator, element);
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateNote(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+        final String note = this.fromApiJsonHelper.extractStringNamed(TransferApiConstants.note, element);
+        baseDataValidator.reset().parameter(TransferApiConstants.note).value(note).notExceedingLengthOf(1000);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalException.java
new file mode 100644
index 0000000..116463a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalException.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.transfer.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ClientNotAwaitingTransferApprovalException extends AbstractPlatformDomainRuleException {
+
+    public ClientNotAwaitingTransferApprovalException(final Long clientId) {
+        super("error.msg.client.not.awaiting.transfer.approval.exception",
+                "The Client with id `" + clientId + "` is not awaiting transfer", clientId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalOrOnHoldException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalOrOnHoldException.java
new file mode 100644
index 0000000..29d75c0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalOrOnHoldException.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.transfer.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class ClientNotAwaitingTransferApprovalOrOnHoldException extends AbstractPlatformDomainRuleException {
+
+    public ClientNotAwaitingTransferApprovalOrOnHoldException(final Long clientId) {
+        super("error.msg.client.not.awaiting.transfer.approval.or.on.hold.exception", "The Client with id `" + clientId
+                + "` is neither awaiting a transfer nor on hold", clientId);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/exception/TransferNotSupportedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/exception/TransferNotSupportedException.java
new file mode 100644
index 0000000..7c82edb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/exception/TransferNotSupportedException.java
@@ -0,0 +1,72 @@
+/**
+ * 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.transfer.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when not supported loan
+ * template type is sent.
+ */
+public class TransferNotSupportedException extends AbstractPlatformDomainRuleException {
+
+    /*** enum of reasons for invalid Journal Entry **/
+    public static enum TRANSFER_NOT_SUPPORTED_REASON {
+        CLIENT_DESTINATION_GROUP_NOT_SPECIFIED, CLIENT_BELONGS_TO_MULTIPLE_GROUPS, SOURCE_AND_DESTINATION_GROUP_CANNOT_BE_SAME, ACTIVE_SAVINGS_ACCOUNT, BULK_CLIENT_TRANSFER_ACROSS_BRANCHES, DESTINATION_GROUP_MEETING_FREQUENCY_MISMATCH, DESTINATION_GROUP_HAS_NO_MEETING;
+
+        public String errorMessage() {
+            if (name().toString().equalsIgnoreCase("ACTIVE_SAVINGS_ACCOUNT")) {
+                return "Cannot transfer Clients/Groups having active Savings accounts";
+            } else if (name().toString().equalsIgnoreCase("BULK_CLIENT_TRANSFER_ACROSS_BRANCHES")) {
+                return "Bulk Transfers of clients between Groups in different branches not allowed ";
+            } else if (name().toString().equalsIgnoreCase("CLIENT_DESTINATION_GROUP_NOT_SPECIFIED")) {
+                return "Destination Group for transfer of clients originally linked to a group not selected ";
+            } else if (name().toString().equalsIgnoreCase("CLIENT_BELONGS_TO_MULTIPLE_GROUPS")) {
+                return "Transfer of clients linked to multiple groups is not supported  ";
+            } else if (name().toString().equalsIgnoreCase("DESTINATION_GROUP_MEETING_FREQUENCY_MISMATCH")) {
+                return "Cannot transfer Clients with active accounts between groups with different meeting frequency";
+            } else if (name().toString().equalsIgnoreCase("SOURCE_AND_DESTINATION_GROUP_CANNOT_BE_SAME")) {
+                return "Source and destination groups for bulk client transfers should be different";
+            } else if (name().toString().equalsIgnoreCase("DESTINATION_GROUP_HAS_NO_MEETING")) { return "Cannot transfer Client with active accounts to a groups with no meeting frequency"; }
+            return name().toString();
+        }
+
+        public String errorCode() {
+            if (name().toString().equalsIgnoreCase("ACTIVE_SAVINGS_ACCOUNT")) {
+                return "error.msg.entity.transfers.with.active.savings.accounts";
+            } else if (name().toString().equalsIgnoreCase("BULK_CLIENT_TRANSFER_ACROSS_BRANCHES")) {
+                return "error.msg.groups.bulk.client.transfers.to.different.office";
+            } else if (name().toString().equalsIgnoreCase("CLIENT_DESTINATION_GROUP_NOT_SPECIFIED")) {
+                return "error.msg.client.transfers.destination.group.absent";
+            } else if (name().toString().equalsIgnoreCase("CLIENT_BELONGS_TO_MULTIPLE_GROUPS")) {
+                return "error.msg.client.transfers.with.multiple.group.linkages";
+            } else if (name().toString().equalsIgnoreCase("DESTINATION_GROUP_MEETING_FREQUENCY_MISMATCH")) {
+                return "error.msg.client.transfers.with.active.accounts.between.groups.with.different.meeting.frequency";
+            } else if (name().toString().equalsIgnoreCase("SOURCE_AND_DESTINATION_GROUP_CANNOT_BE_SAME")) {
+                return "error.msg.groups.bulk.client.transfers.to.same.group";
+            } else if (name().toString().equalsIgnoreCase("DESTINATION_GROUP_HAS_NO_MEETING")) { return "error.msg.client.transfers.with.active.accounts.to.group.with.no.meeting.frequencys"; }
+            return name().toString();
+        }
+    }
+
+    public TransferNotSupportedException(final TRANSFER_NOT_SUPPORTED_REASON reason, final Object... defaultUserMessageArgs) {
+        super(reason.errorCode(), reason.errorMessage(), defaultUserMessageArgs);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/AcceptClientTransferCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/AcceptClientTransferCommandHandler.java
new file mode 100644
index 0000000..67483df
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/AcceptClientTransferCommandHandler.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.portfolio.transfer.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.transfer.service.TransferWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "ACCEPTTRANSFER")
+public class AcceptClientTransferCommandHandler implements NewCommandSourceHandler {
+
+    private final TransferWritePlatformService writePlatformService;
+
+    @Autowired
+    public AcceptClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.acceptClientTransfer(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/ProposeAndAcceptClientTransferCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/ProposeAndAcceptClientTransferCommandHandler.java
new file mode 100644
index 0000000..31568a6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/ProposeAndAcceptClientTransferCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.transfer.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.transfer.service.TransferWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "PROPOSEANDACCEPTTRANSFER")
+public class ProposeAndAcceptClientTransferCommandHandler implements NewCommandSourceHandler {
+
+    private final TransferWritePlatformService writePlatformService;
+
+    @Autowired
+    public ProposeAndAcceptClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.proposeAndAcceptClientTransfer(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/ProposeClientTransferCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/ProposeClientTransferCommandHandler.java
new file mode 100644
index 0000000..f04d22e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/ProposeClientTransferCommandHandler.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.portfolio.transfer.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.transfer.service.TransferWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "PROPOSETRANSFER")
+public class ProposeClientTransferCommandHandler implements NewCommandSourceHandler {
+
+    private final TransferWritePlatformService writePlatformService;
+
+    @Autowired
+    public ProposeClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.proposeClientTransfer(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/RejectClientTransferCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/RejectClientTransferCommandHandler.java
new file mode 100644
index 0000000..bb40d8c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/RejectClientTransferCommandHandler.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.portfolio.transfer.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.transfer.service.TransferWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "REJECTTRANSFER")
+public class RejectClientTransferCommandHandler implements NewCommandSourceHandler {
+
+    private final TransferWritePlatformService writePlatformService;
+
+    @Autowired
+    public RejectClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.rejectClientTransfer(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/TransferClientsBetweenGroupsCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/TransferClientsBetweenGroupsCommandHandler.java
new file mode 100644
index 0000000..06b36ee
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/TransferClientsBetweenGroupsCommandHandler.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.portfolio.transfer.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.transfer.service.TransferWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "GROUP", action = "TRANSFERCLIENTS")
+public class TransferClientsBetweenGroupsCommandHandler implements NewCommandSourceHandler {
+
+    private final TransferWritePlatformService writePlatformService;
+
+    @Autowired
+    public TransferClientsBetweenGroupsCommandHandler(final TransferWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.transferClientsBetweenGroups(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/WithdrawClientTransferCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/WithdrawClientTransferCommandHandler.java
new file mode 100644
index 0000000..fa913b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/handler/WithdrawClientTransferCommandHandler.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.portfolio.transfer.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.transfer.service.TransferWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CLIENT", action = "WITHDRAWTRANSFER")
+public class WithdrawClientTransferCommandHandler implements NewCommandSourceHandler {
+
+    private final TransferWritePlatformService writePlatformService;
+
+    @Autowired
+    public WithdrawClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.withdrawClientTransfer(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferEventType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferEventType.java
new file mode 100644
index 0000000..fd95622
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferEventType.java
@@ -0,0 +1,45 @@
+/**
+ * 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.transfer.service;
+
+public enum TransferEventType {
+    PROPOSAL(1, "transferEvent.proposal"), ACCEPTANCE(2, "transferEvent.acceptance"), WITHDRAWAL(3, "transferEvent.withdrawal"), REJECTION(
+            4, "transferEvent.rejection");
+
+    private final Integer value;
+    private final String code;
+
+    private TransferEventType(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean isAcceptance() {
+        return value.equals(TransferEventType.ACCEPTANCE.value);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformService.java
new file mode 100644
index 0000000..7c8c17c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformService.java
@@ -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.
+ */
+package org.apache.fineract.portfolio.transfer.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface TransferWritePlatformService {
+
+    CommandProcessingResult transferClientsBetweenGroups(final Long sourceGroupId, final JsonCommand jsonCommand);
+
+    CommandProcessingResult proposeClientTransfer(final Long clientId, final JsonCommand jsonCommand);
+
+    CommandProcessingResult withdrawClientTransfer(final Long clientId, final JsonCommand jsonCommand);
+
+    CommandProcessingResult acceptClientTransfer(final Long clientId, final JsonCommand jsonCommand);
+
+    CommandProcessingResult rejectClientTransfer(final Long clientId, final JsonCommand jsonCommand);
+
+    CommandProcessingResult proposeAndAcceptClientTransfer(final Long clientId, final JsonCommand jsonCommand);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java
new file mode 100755
index 0000000..6a7442a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,512 @@
+/**
+ * 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.transfer.service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+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.service.DateUtils;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.calendar.domain.Calendar;
+import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
+import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
+import org.apache.fineract.portfolio.calendar.domain.CalendarType;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.ClientStatus;
+import org.apache.fineract.portfolio.client.exception.ClientHasBeenClosedException;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
+import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
+import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.apache.fineract.portfolio.transfer.api.TransferApiConstants;
+import org.apache.fineract.portfolio.transfer.data.TransfersDataValidator;
+import org.apache.fineract.portfolio.transfer.exception.ClientNotAwaitingTransferApprovalException;
+import org.apache.fineract.portfolio.transfer.exception.ClientNotAwaitingTransferApprovalOrOnHoldException;
+import org.apache.fineract.portfolio.transfer.exception.TransferNotSupportedException;
+import org.apache.fineract.portfolio.transfer.exception.TransferNotSupportedException.TRANSFER_NOT_SUPPORTED_REASON;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.common.collect.Iterables;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+@Service
+public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWritePlatformService {
+
+    private final ClientRepositoryWrapper clientRepository;
+    private final OfficeRepositoryWrapper officeRepository;
+    private final CalendarInstanceRepository calendarInstanceRepository;
+    private final GroupRepositoryWrapper groupRepository;
+    private final LoanWritePlatformService loanWritePlatformService;
+    private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
+    private final LoanRepository loanRepository;
+    private final SavingsAccountRepository savingsAccountRepository;
+    private final TransfersDataValidator transfersDataValidator;
+    private final NoteWritePlatformService noteWritePlatformService;
+    private final StaffRepositoryWrapper staffRepositoryWrapper;
+
+    @Autowired
+    public TransferWritePlatformServiceJpaRepositoryImpl(final ClientRepositoryWrapper clientRepository,
+            final OfficeRepositoryWrapper officeRepository, final CalendarInstanceRepository calendarInstanceRepository,
+            final LoanWritePlatformService loanWritePlatformService, final GroupRepositoryWrapper groupRepository,
+            final LoanRepository loanRepository, final TransfersDataValidator transfersDataValidator,
+            final NoteWritePlatformService noteWritePlatformService, final StaffRepositoryWrapper staffRepositoryWrapper,
+            final SavingsAccountRepository savingsAccountRepository,
+            final SavingsAccountWritePlatformService savingsAccountWritePlatformService) {
+        this.clientRepository = clientRepository;
+        this.officeRepository = officeRepository;
+        this.calendarInstanceRepository = calendarInstanceRepository;
+        this.loanWritePlatformService = loanWritePlatformService;
+        this.groupRepository = groupRepository;
+        this.loanRepository = loanRepository;
+        this.transfersDataValidator = transfersDataValidator;
+        this.noteWritePlatformService = noteWritePlatformService;
+        this.staffRepositoryWrapper = staffRepositoryWrapper;
+        this.savingsAccountRepository = savingsAccountRepository;
+        this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult transferClientsBetweenGroups(final Long sourceGroupId, final JsonCommand jsonCommand) {
+        this.transfersDataValidator.validateForClientsTransferBetweenGroups(jsonCommand.json());
+
+        final Group sourceGroup = this.groupRepository.findOneWithNotFoundDetection(sourceGroupId);
+        final Long destinationGroupId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationGroupIdParamName);
+        final Group destinationGroup = this.groupRepository.findOneWithNotFoundDetection(destinationGroupId);
+        final Long staffId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.newStaffIdParamName);
+        final Boolean inheritDestinationGroupLoanOfficer = jsonCommand
+                .booleanObjectValueOfParameterNamed(TransferApiConstants.inheritDestinationGroupLoanOfficer);
+        Staff staff = null;
+        final Office sourceOffice = sourceGroup.getOffice();
+        if (staffId != null) {
+            staff = this.staffRepositoryWrapper.findByOfficeHierarchyWithNotFoundDetection(staffId, sourceOffice.getHierarchy());
+        }
+
+        final List<Client> clients = assembleListOfClients(jsonCommand);
+
+        if (sourceGroupId == destinationGroupId) { throw new TransferNotSupportedException(
+                TRANSFER_NOT_SUPPORTED_REASON.SOURCE_AND_DESTINATION_GROUP_CANNOT_BE_SAME, sourceGroupId, destinationGroupId); }
+
+        /*** Do not allow bulk client transfers across branches ***/
+        if (!(sourceOffice.getId() == destinationGroup.getOffice().getId())) { throw new TransferNotSupportedException(
+                TRANSFER_NOT_SUPPORTED_REASON.BULK_CLIENT_TRANSFER_ACROSS_BRANCHES, sourceGroupId, destinationGroupId); }
+
+        for (final Client client : clients) {
+            transferClientBetweenGroups(sourceGroup, client, destinationGroup, inheritDestinationGroupLoanOfficer, staff);
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(sourceGroupId) //
+                .build();
+    }
+
+    /****
+     * Variables that would make sense <br/>
+     * <ul>
+     * <li>inheritDestinationGroupLoanOfficer: Default true</li>
+     * <li>newStaffId: Optional field with Id of new Loan Officer to be linked
+     * to this client and all his JLG loans for this group</li>
+     * ***/
+    @Transactional
+    public void transferClientBetweenGroups(final Group sourceGroup, final Client client, final Group destinationGroup,
+            final Boolean inheritDestinationGroupLoanOfficer, final Staff newLoanOfficer) {
+
+        // next I shall validate that the client is present in this group
+        if (!sourceGroup.hasClientAsMember(client)) { throw new ClientNotInGroupException(client.getId(), sourceGroup.getId()); }
+        // Is client active?
+        if (client.isNotActive()) { throw new ClientHasBeenClosedException(client.getId()); }
+
+        /**
+         * TODO: for now we need to ensure that only one collection sheet
+         * calendar can be linked with a center or group entity <br/>
+         **/
+        final CalendarInstance sourceGroupCalendarInstance = this.calendarInstanceRepository
+                .findByEntityIdAndEntityTypeIdAndCalendarTypeId(sourceGroup.getId(), CalendarEntityType.GROUPS.getValue(),
+                        CalendarType.COLLECTION.getValue());
+        // get all customer loans synced with this group calendar Instance
+        final List<CalendarInstance> activeLoanCalendarInstances = this.calendarInstanceRepository
+                .findCalendarInstancesForActiveLoansByGroupIdAndClientId(sourceGroup.getId(), client.getId());
+
+        /**
+         * if a calendar is present in the source group along with loans synced
+         * with it, we should ensure that the destination group also has a
+         * collection calendar
+         **/
+        if (sourceGroupCalendarInstance != null && !activeLoanCalendarInstances.isEmpty()) {
+            // get the destination calendar
+            final CalendarInstance destinationGroupCalendarInstance = this.calendarInstanceRepository
+                    .findByEntityIdAndEntityTypeIdAndCalendarTypeId(destinationGroup.getId(), CalendarEntityType.GROUPS.getValue(),
+                            CalendarType.COLLECTION.getValue());
+
+            if (destinationGroupCalendarInstance == null) { throw new TransferNotSupportedException(
+                    TRANSFER_NOT_SUPPORTED_REASON.DESTINATION_GROUP_HAS_NO_MEETING, destinationGroup.getId());
+
+            }
+            final Calendar sourceGroupCalendar = sourceGroupCalendarInstance.getCalendar();
+            final Calendar destinationGroupCalendar = destinationGroupCalendarInstance.getCalendar();
+
+            /***
+             * Ensure that the recurrence pattern are same for collection
+             * meeting in both the source and the destination calendar
+             ***/
+            if (!(CalendarUtils.isFrequencySame(sourceGroupCalendar.getRecurrence(), destinationGroupCalendar.getRecurrence()) && CalendarUtils
+                    .isIntervalSame(sourceGroupCalendar.getRecurrence(), destinationGroupCalendar.getRecurrence()))) { throw new TransferNotSupportedException(
+                    TRANSFER_NOT_SUPPORTED_REASON.DESTINATION_GROUP_MEETING_FREQUENCY_MISMATCH, sourceGroup.getId(),
+                    destinationGroup.getId()); }
+
+            /** map all JLG loans for this client to the destinationGroup **/
+            for (final CalendarInstance calendarInstance : activeLoanCalendarInstances) {
+                calendarInstance.updateCalendar(destinationGroupCalendar);
+                this.calendarInstanceRepository.save(calendarInstance);
+            }
+            // reschedule all JLG Loans to follow new Calendar
+            this.loanWritePlatformService.applyMeetingDateChanges(destinationGroupCalendar, activeLoanCalendarInstances);
+        }
+
+        /**
+         * Now Change the loan officer for this client and all his active JLG
+         * loans
+         **/
+        final Staff destinationGroupLoanOfficer = destinationGroup.getStaff();
+
+        /** In case of a loan officer transfer, set the new loan officer value **/
+        if (sourceGroup.getId().equals(destinationGroup.getId()) && newLoanOfficer != null) {
+            client.updateStaff(newLoanOfficer);
+        }/*** Else default to destination group Officer (If present) ***/
+        else if (destinationGroupLoanOfficer != null) {
+            client.updateStaff(destinationGroupLoanOfficer);
+        }
+
+        client.getGroups().add(destinationGroup);
+        this.clientRepository.saveAndFlush(client);
+
+        /**
+         * Active JLG loans are now linked to the new Group and Loan officer
+         **/
+        final List<Loan> allClientJLGLoans = this.loanRepository.findByClientIdAndGroupId(client.getId(), sourceGroup.getId());
+        for (final Loan loan : allClientJLGLoans) {
+            if (loan.status().isActiveOrAwaitingApprovalOrDisbursal()) {
+                loan.updateGroup(destinationGroup);
+                if (inheritDestinationGroupLoanOfficer != null && inheritDestinationGroupLoanOfficer == true
+                        && destinationGroupLoanOfficer != null) {
+                    loan.reassignLoanOfficer(destinationGroupLoanOfficer, DateUtils.getLocalDateOfTenant());
+                } else if (newLoanOfficer != null) {
+                    loan.reassignLoanOfficer(newLoanOfficer, DateUtils.getLocalDateOfTenant());
+                }
+                this.loanRepository.saveAndFlush(loan);
+            }
+        }
+
+        /**
+         * change client group membership (only if source group and destination
+         * group are not the same, i.e only Loan officer Transfer)
+         **/
+        if (!sourceGroup.getId().equals(destinationGroup.getId())) {
+            client.getGroups().remove(sourceGroup);
+        }
+
+    }
+
+    /**
+     * This API is meant for transferring clients between branches mainly by
+     * Organizations following an Individual lending Model <br/>
+     * 
+     * @param clientId
+     * @param destinationOfficeId
+     * @param jsonCommand
+     * @return
+     **/
+    @Transactional
+    @Override
+    public CommandProcessingResult proposeAndAcceptClientTransfer(final Long clientId, final JsonCommand jsonCommand) {
+        // validation
+        this.transfersDataValidator.validateForProposeAndAcceptClientTransfer(jsonCommand.json());
+
+        final Long destinationOfficeId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationOfficeIdParamName);
+        final Office office = this.officeRepository.findOneWithNotFoundDetection(destinationOfficeId);
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+        handleClientTransferLifecycleEvent(client, office, TransferEventType.PROPOSAL, jsonCommand);
+        this.clientRepository.saveAndFlush(client);
+        handleClientTransferLifecycleEvent(client, client.getTransferToOffice(), TransferEventType.ACCEPTANCE, jsonCommand);
+        this.clientRepository.save(client);
+
+        return new CommandProcessingResultBuilder() //
+                .withClientId(clientId) //
+                .withEntityId(clientId) //
+                .build();
+    }
+
+    /**
+     * This API is meant for transferring clients between branches mainly by
+     * Organizations following an Individual lending Model <br/>
+     * If the Client is linked to any Groups, we can optionally choose to have
+     * all the linkages broken and all JLG Loans are converted into Individual
+     * Loans
+     * 
+     * @param clientId
+     * @param destinationOfficeId
+     * @param jsonCommand
+     * @return
+     **/
+    @Transactional
+    @Override
+    public CommandProcessingResult proposeClientTransfer(final Long clientId, final JsonCommand jsonCommand) {
+        // validation
+        this.transfersDataValidator.validateForProposeClientTransfer(jsonCommand.json());
+
+        final Long destinationOfficeId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationOfficeIdParamName);
+        final Office office = this.officeRepository.findOneWithNotFoundDetection(destinationOfficeId);
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+        handleClientTransferLifecycleEvent(client, office, TransferEventType.PROPOSAL, jsonCommand);
+        this.clientRepository.save(client);
+
+        return new CommandProcessingResultBuilder() //
+                .withClientId(clientId) //
+                .withEntityId(clientId) //
+                .build();
+    }
+
+    /**
+     * This API is meant for transferring clients between branches mainly by
+     * Organizations following an Individual lending Model <br/>
+     * If the Client is linked to any Groups, we can optionally choose to have
+     * all the linkages broken and all JLG Loans are converted into Individual
+     * Loans
+     * 
+     * @param clientId
+     * @param destinationOfficeId
+     * @param jsonCommand
+     * @return
+     **/
+    @Transactional
+    @Override
+    public CommandProcessingResult acceptClientTransfer(final Long clientId, final JsonCommand jsonCommand) {
+        // validation
+        this.transfersDataValidator.validateForAcceptClientTransfer(jsonCommand.json());
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+        validateClientAwaitingTransferAcceptance(client);
+
+        handleClientTransferLifecycleEvent(client, client.getTransferToOffice(), TransferEventType.ACCEPTANCE, jsonCommand);
+        this.clientRepository.save(client);
+
+        return new CommandProcessingResultBuilder() //
+                .withClientId(clientId) //
+                .withEntityId(clientId) //
+                .build();
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult withdrawClientTransfer(final Long clientId, final JsonCommand jsonCommand) {
+        // validation
+        this.transfersDataValidator.validateForWithdrawClientTransfer(jsonCommand.json());
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+        validateClientAwaitingTransferAcceptanceOnHold(client);
+
+        handleClientTransferLifecycleEvent(client, client.getOffice(), TransferEventType.WITHDRAWAL, jsonCommand);
+        this.clientRepository.save(client);
+
+        return new CommandProcessingResultBuilder() //
+                .withClientId(clientId) //
+                .withEntityId(clientId) //
+                .build();
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult rejectClientTransfer(final Long clientId, final JsonCommand jsonCommand) {
+        // validation
+        this.transfersDataValidator.validateForRejectClientTransfer(jsonCommand.json());
+        final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+        handleClientTransferLifecycleEvent(client, client.getOffice(), TransferEventType.REJECTION, jsonCommand);
+        this.clientRepository.save(client);
+
+        return new CommandProcessingResultBuilder() //
+                .withClientId(clientId) //
+                .withEntityId(clientId) //
+                .build();
+    }
+
+    private void handleClientTransferLifecycleEvent(final Client client, final Office destinationOffice,
+            final TransferEventType transferEventType, final JsonCommand jsonCommand) {
+        final Date todaysDate = DateUtils.getDateOfTenant();
+        /** Get destination loan officer if exists **/
+        Staff staff = null;
+        Group destinationGroup = null;
+        final Long staffId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.newStaffIdParamName);
+        final Long destinationGroupId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationGroupIdParamName);
+        if (staffId != null) {
+            staff = this.staffRepositoryWrapper.findByOfficeHierarchyWithNotFoundDetection(staffId, destinationOffice.getHierarchy());
+        }
+        if (transferEventType.isAcceptance() && destinationGroupId != null) {
+            destinationGroup = this.groupRepository.findByOfficeWithNotFoundDetection(destinationGroupId, destinationOffice);
+        }
+
+        /*** Handle Active Loans ***/
+        if (this.loanRepository.doNonClosedLoanAccountsExistForClient(client.getId())) {
+            // get each individual loan for the client
+            for (final Loan loan : this.loanRepository.findLoanByClientId(client.getId())) {
+                /**
+                 * We need to create transactions etc only for loans which are
+                 * disbursed and not yet closed
+                 **/
+                if (loan.isDisbursed() && !loan.isClosed()) {
+                    switch (transferEventType) {
+                        case ACCEPTANCE:
+                            this.loanWritePlatformService.acceptLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant(),
+                                    destinationOffice, staff);
+                        break;
+                        case PROPOSAL:
+                            this.loanWritePlatformService.initiateLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant());
+                        break;
+                        case REJECTION:
+                            this.loanWritePlatformService.rejectLoanTransfer(loan.getId());
+                        break;
+                        case WITHDRAWAL:
+                            this.loanWritePlatformService.withdrawLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant());
+                    }
+                }
+            }
+        }
+
+        /*** Handle Active Savings (Currently throw and exception) ***/
+        if (this.savingsAccountRepository.doNonClosedSavingAccountsExistForClient(client.getId())) {
+            // get each individual saving account for the client
+            for (final SavingsAccount savingsAccount : this.savingsAccountRepository.findSavingAccountByClientId(client.getId())) {
+                if (savingsAccount.isActivated() && !savingsAccount.isClosed()) {
+                    switch (transferEventType) {
+                        case ACCEPTANCE:
+                            this.savingsAccountWritePlatformService.acceptSavingsTransfer(savingsAccount.getId(),
+                                    DateUtils.getLocalDateOfTenant(), destinationOffice, staff);
+                        break;
+                        case PROPOSAL:
+                            this.savingsAccountWritePlatformService.initiateSavingsTransfer(savingsAccount.getId(),
+                                    DateUtils.getLocalDateOfTenant());
+                        break;
+                        case REJECTION:
+                            this.savingsAccountWritePlatformService.rejectSavingsTransfer(savingsAccount.getId());
+                        break;
+                        case WITHDRAWAL:
+                            this.savingsAccountWritePlatformService.withdrawSavingsTransfer(savingsAccount.getId(),
+                                    DateUtils.getLocalDateOfTenant());
+                    }
+                }
+            }
+        }
+
+        switch (transferEventType) {
+            case ACCEPTANCE:
+                client.setStatus(ClientStatus.ACTIVE.getValue());
+                client.updateTransferToOffice(null);
+                client.updateOffice(destinationOffice);
+                client.updateOfficeJoiningDate(todaysDate);
+                if (client.getGroups().size() == 1) {
+                    if (destinationGroup == null) {
+                        throw new TransferNotSupportedException(TRANSFER_NOT_SUPPORTED_REASON.CLIENT_DESTINATION_GROUP_NOT_SPECIFIED,
+                                client.getId());
+                    } else if (!destinationGroup.isActive()) { throw new GroupNotActiveException(destinationGroup.getId()); }
+                    transferClientBetweenGroups(Iterables.get(client.getGroups(), 0), client, destinationGroup, true, staff);
+                } else if (client.getGroups().size() == 0 && destinationGroup != null) {
+                    client.getGroups().add(destinationGroup);
+                    client.updateStaff(destinationGroup.getStaff());
+                    if (staff != null) {
+                        client.updateStaff(staff);
+                    }
+                }else if(destinationGroup == null) { /** for individual with no groups  **/
+                    if(staff !=null){ client.updateStaff(staff);}
+                }
+            break;
+            case PROPOSAL:
+                client.setStatus(ClientStatus.TRANSFER_IN_PROGRESS.getValue());
+                client.updateTransferToOffice(destinationOffice);
+            break;
+            case REJECTION:
+                client.setStatus(ClientStatus.TRANSFER_ON_HOLD.getValue());
+                client.updateTransferToOffice(null);
+            break;
+            case WITHDRAWAL:
+                client.setStatus(ClientStatus.ACTIVE.getValue());
+                client.updateTransferToOffice(null);
+        }
+
+        this.noteWritePlatformService.createAndPersistClientNote(client, jsonCommand);
+    }
+
+    private List<Client> assembleListOfClients(final JsonCommand command) {
+
+        final List<Client> clients = new ArrayList<>();
+
+        if (command.parameterExists(TransferApiConstants.clients)) {
+            final JsonArray clientsArray = command.arrayOfParameterNamed(TransferApiConstants.clients);
+            if (clientsArray != null) {
+                for (int i = 0; i < clientsArray.size(); i++) {
+
+                    final JsonObject jsonObject = clientsArray.get(i).getAsJsonObject();
+                    if (jsonObject.has(TransferApiConstants.idParamName)) {
+                        final Long id = jsonObject.get(TransferApiConstants.idParamName).getAsLong();
+                        final Client client = this.clientRepository.findOneWithNotFoundDetection(id);
+                        clients.add(client);
+                    }
+                }
+            }
+        }
+        return clients;
+    }
+
+    private void validateClientAwaitingTransferAcceptance(final Client client) {
+        if (!client.isTransferInProgress()) { throw new ClientNotAwaitingTransferApprovalException(client.getId()); }
+    }
+
+    /**
+     * private void validateGroupAwaitingTransferAcceptance(final Group group) {
+     * if (!group.isTransferInProgress()) { throw new
+     * ClientNotAwaitingTransferApprovalException(group.getId()); } }
+     **/
+
+    private void validateClientAwaitingTransferAcceptanceOnHold(final Client client) {
+        if (!client.isTransferInProgressOrOnHold()) { throw new ClientNotAwaitingTransferApprovalOrOnHoldException(client.getId()); }
+    }
+
+    /**
+     * private void validateGroupAwaitingTransferAcceptanceOnHold(final Group
+     * group) { if (!group.isTransferInProgressOrOnHold()) { throw new
+     * ClientNotAwaitingTransferApprovalException(group.getId()); } }
+     **/
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerService.java b/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerService.java
new file mode 100644
index 0000000..6d3a8f9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerService.java
@@ -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.
+ */
+package org.apache.fineract.scheduledjobs.service;
+
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+
+public interface ScheduledJobRunnerService {
+
+    void updateLoanSummaryDetails();
+
+    void updateLoanPaidInAdvance();
+
+    void applyAnnualFeeForSavings();
+
+    void applyDueChargesForSavings() throws JobExecutionException;
+
+    void updateNPA();
+
+    void updateMaturityDetailsOfDepositAccounts();
+
+    void generateRDSchedule();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java
new file mode 100644
index 0000000..ea28f53
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java
@@ -0,0 +1,367 @@
+/**
+ * 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.scheduledjobs.service;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSourceServiceFactory;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.DepositAccountUtils;
+import org.apache.fineract.portfolio.savings.data.DepositAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountAnnualFeeData;
+import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
+import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service(value = "scheduledJobRunnerService")
+public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService {
+
+    private final static Logger logger = LoggerFactory.getLogger(ScheduledJobRunnerServiceImpl.class);
+    private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
+    private final DateTimeFormatter formatterWithTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
+
+    private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
+    private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
+    private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
+    private final DepositAccountReadPlatformService depositAccountReadPlatformService;
+    private final DepositAccountWritePlatformService depositAccountWritePlatformService;
+
+    @Autowired
+    public ScheduledJobRunnerServiceImpl(final RoutingDataSourceServiceFactory dataSourceServiceFactory,
+            final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
+            final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService,
+            final DepositAccountReadPlatformService depositAccountReadPlatformService,
+            final DepositAccountWritePlatformService depositAccountWritePlatformService) {
+        this.dataSourceServiceFactory = dataSourceServiceFactory;
+        this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
+        this.savingsAccountChargeReadPlatformService = savingsAccountChargeReadPlatformService;
+        this.depositAccountReadPlatformService = depositAccountReadPlatformService;
+        this.depositAccountWritePlatformService = depositAccountWritePlatformService;
+    }
+
+    @Transactional
+    @Override
+    @CronTarget(jobName = JobName.UPDATE_LOAN_SUMMARY)
+    public void updateLoanSummaryDetails() {
+
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
+
+        final StringBuilder updateSqlBuilder = new StringBuilder(900);
+        updateSqlBuilder.append("update m_loan ");
+        updateSqlBuilder.append("join (");
+        updateSqlBuilder.append("SELECT ml.id AS loanId,");
+        updateSqlBuilder.append("SUM(mr.principal_amount) as principal_disbursed_derived, ");
+        updateSqlBuilder.append("SUM(IFNULL(mr.principal_completed_derived,0)) as principal_repaid_derived, ");
+        updateSqlBuilder.append("SUM(IFNULL(mr.principal_writtenoff_derived,0)) as principal_writtenoff_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.interest_amount,0)) as interest_charged_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.interest_completed_derived,0)) as interest_repaid_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.interest_waived_derived,0)) as interest_waived_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.interest_writtenoff_derived,0)) as interest_writtenoff_derived,");
+        updateSqlBuilder
+                .append("SUM(IFNULL(mr.fee_charges_amount,0)) + IFNULL((select SUM(lc.amount) from  m_loan_charge lc where lc.loan_id=ml.id and lc.is_active=1 and lc.charge_time_enum=1),0) as fee_charges_charged_derived,");
+        updateSqlBuilder
+                .append("SUM(IFNULL(mr.fee_charges_completed_derived,0)) + IFNULL((select SUM(lc.amount_paid_derived) from  m_loan_charge lc where lc.loan_id=ml.id and lc.is_active=1 and lc.charge_time_enum=1),0) as fee_charges_repaid_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.fee_charges_waived_derived,0)) as fee_charges_waived_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.fee_charges_writtenoff_derived,0)) as fee_charges_writtenoff_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.penalty_charges_amount,0)) as penalty_charges_charged_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.penalty_charges_completed_derived,0)) as penalty_charges_repaid_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.penalty_charges_waived_derived,0)) as penalty_charges_waived_derived,");
+        updateSqlBuilder.append("SUM(IFNULL(mr.penalty_charges_writtenoff_derived,0)) as penalty_charges_writtenoff_derived ");
+        updateSqlBuilder.append(" FROM m_loan ml ");
+        updateSqlBuilder.append("INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
+        updateSqlBuilder.append("WHERE ml.disbursedon_date is not null ");
+        updateSqlBuilder.append("GROUP BY ml.id ");
+        updateSqlBuilder.append(") x on x.loanId = m_loan.id ");
+
+        updateSqlBuilder.append("SET m_loan.principal_disbursed_derived = x.principal_disbursed_derived,");
+        updateSqlBuilder.append("m_loan.principal_repaid_derived = x.principal_repaid_derived,");
+        updateSqlBuilder.append("m_loan.principal_writtenoff_derived = x.principal_writtenoff_derived,");
+        updateSqlBuilder
+                .append("m_loan.principal_outstanding_derived = (x.principal_disbursed_derived - (x.principal_repaid_derived + x.principal_writtenoff_derived)),");
+        updateSqlBuilder.append("m_loan.interest_charged_derived = x.interest_charged_derived,");
+        updateSqlBuilder.append("m_loan.interest_repaid_derived = x.interest_repaid_derived,");
+        updateSqlBuilder.append("m_loan.interest_waived_derived = x.interest_waived_derived,");
+        updateSqlBuilder.append("m_loan.interest_writtenoff_derived = x.interest_writtenoff_derived,");
+        updateSqlBuilder
+                .append("m_loan.interest_outstanding_derived = (x.interest_charged_derived - (x.interest_repaid_derived + x.interest_waived_derived + x.interest_writtenoff_derived)),");
+        updateSqlBuilder.append("m_loan.fee_charges_charged_derived = x.fee_charges_charged_derived,");
+        updateSqlBuilder.append("m_loan.fee_charges_repaid_derived = x.fee_charges_repaid_derived,");
+        updateSqlBuilder.append("m_loan.fee_charges_waived_derived = x.fee_charges_waived_derived,");
+        updateSqlBuilder.append("m_loan.fee_charges_writtenoff_derived = x.fee_charges_writtenoff_derived,");
+        updateSqlBuilder
+                .append("m_loan.fee_charges_outstanding_derived = (x.fee_charges_charged_derived - (x.fee_charges_repaid_derived + x.fee_charges_waived_derived + x.fee_charges_writtenoff_derived)),");
+        updateSqlBuilder.append("m_loan.penalty_charges_charged_derived = x.penalty_charges_charged_derived,");
+        updateSqlBuilder.append("m_loan.penalty_charges_repaid_derived = x.penalty_charges_repaid_derived,");
+        updateSqlBuilder.append("m_loan.penalty_charges_waived_derived = x.penalty_charges_waived_derived,");
+        updateSqlBuilder.append("m_loan.penalty_charges_writtenoff_derived = x.penalty_charges_writtenoff_derived,");
+        updateSqlBuilder
+                .append("m_loan.penalty_charges_outstanding_derived = (x.penalty_charges_charged_derived - (x.penalty_charges_repaid_derived + x.penalty_charges_waived_derived + x.penalty_charges_writtenoff_derived)),");
+        updateSqlBuilder
+                .append("m_loan.total_expected_repayment_derived = (x.principal_disbursed_derived + x.interest_charged_derived + x.fee_charges_charged_derived + x.penalty_charges_charged_derived),");
+        updateSqlBuilder
+                .append("m_loan.total_repayment_derived = (x.principal_repaid_derived + x.interest_repaid_derived + x.fee_charges_repaid_derived + x.penalty_charges_repaid_derived),");
+        updateSqlBuilder
+                .append("m_loan.total_expected_costofloan_derived = (x.interest_charged_derived + x.fee_charges_charged_derived + x.penalty_charges_charged_derived),");
+        updateSqlBuilder
+                .append("m_loan.total_costofloan_derived = (x.interest_repaid_derived + x.fee_charges_repaid_derived + x.penalty_charges_repaid_derived),");
+        updateSqlBuilder
+                .append("m_loan.total_waived_derived = (x.interest_waived_derived + x.fee_charges_waived_derived + x.penalty_charges_waived_derived),");
+        updateSqlBuilder
+                .append("m_loan.total_writtenoff_derived = (x.interest_writtenoff_derived +  x.fee_charges_writtenoff_derived + x.penalty_charges_writtenoff_derived),");
+        updateSqlBuilder.append("m_loan.total_outstanding_derived=");
+        updateSqlBuilder.append(" (x.principal_disbursed_derived - (x.principal_repaid_derived + x.principal_writtenoff_derived)) + ");
+        updateSqlBuilder
+                .append(" (x.interest_charged_derived - (x.interest_repaid_derived + x.interest_waived_derived + x.interest_writtenoff_derived)) +");
+        updateSqlBuilder
+                .append(" (x.fee_charges_charged_derived - (x.fee_charges_repaid_derived + x.fee_charges_waived_derived + x.fee_charges_writtenoff_derived)) +");
+        updateSqlBuilder
+                .append(" (x.penalty_charges_charged_derived - (x.penalty_charges_repaid_derived + x.penalty_charges_waived_derived + x.penalty_charges_writtenoff_derived))");
+
+        final int result = jdbcTemplate.update(updateSqlBuilder.toString());
+
+        logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result);
+    }
+
+    @Transactional
+    @Override
+    @CronTarget(jobName = JobName.UPDATE_LOAN_PAID_IN_ADVANCE)
+    public void updateLoanPaidInAdvance() {
+
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
+
+        jdbcTemplate.execute("truncate table m_loan_paid_in_advance");
+
+        final StringBuilder updateSqlBuilder = new StringBuilder(900);
+
+        updateSqlBuilder
+                .append("INSERT INTO m_loan_paid_in_advance(loan_id, principal_in_advance_derived, interest_in_advance_derived, fee_charges_in_advance_derived, penalty_charges_in_advance_derived, total_in_advance_derived)");
+        updateSqlBuilder.append(" select ml.id as loanId,");
+        updateSqlBuilder.append(" SUM(ifnull(mr.principal_completed_derived, 0)) as principal_in_advance_derived,");
+        updateSqlBuilder.append(" SUM(ifnull(mr.interest_completed_derived, 0)) as interest_in_advance_derived,");
+        updateSqlBuilder.append(" SUM(ifnull(mr.fee_charges_completed_derived, 0)) as fee_charges_in_advance_derived,");
+        updateSqlBuilder.append(" SUM(ifnull(mr.penalty_charges_completed_derived, 0)) as penalty_charges_in_advance_derived,");
+        updateSqlBuilder
+                .append(" (SUM(ifnull(mr.principal_completed_derived, 0)) + SUM(ifnull(mr.interest_completed_derived, 0)) + SUM(ifnull(mr.fee_charges_completed_derived, 0)) + SUM(ifnull(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived");
+        updateSqlBuilder.append(" FROM m_loan ml ");
+        updateSqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
+        updateSqlBuilder.append(" WHERE ml.loan_status_id = 300 ");
+        updateSqlBuilder.append(" and mr.duedate >= CURDATE() ");
+        updateSqlBuilder.append(" GROUP BY ml.id");
+        updateSqlBuilder
+                .append(" HAVING (SUM(ifnull(mr.principal_completed_derived, 0)) + SUM(ifnull(mr.interest_completed_derived, 0)) +");
+        updateSqlBuilder
+                .append(" SUM(ifnull(mr.fee_charges_completed_derived, 0)) + SUM(ifnull(mr.penalty_charges_completed_derived, 0))) > 0.0");
+
+        final int result = jdbcTemplate.update(updateSqlBuilder.toString());
+
+        logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result);
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.APPLY_ANNUAL_FEE_FOR_SAVINGS)
+    public void applyAnnualFeeForSavings() {
+
+        final Collection<SavingsAccountAnnualFeeData> annualFeeData = this.savingsAccountChargeReadPlatformService
+                .retrieveChargesWithAnnualFeeDue();
+
+        for (final SavingsAccountAnnualFeeData savingsAccountReference : annualFeeData) {
+            try {
+                this.savingsAccountWritePlatformService.applyAnnualFee(savingsAccountReference.getId(),
+                        savingsAccountReference.getAccountId());
+            } catch (final PlatformApiDataValidationException e) {
+                final List<ApiParameterError> errors = e.getErrors();
+                for (final ApiParameterError error : errors) {
+                    logger.error("Apply annual fee failed for account:" + savingsAccountReference.getAccountNo() + " with message "
+                            + error.getDeveloperMessage());
+                }
+            } catch (final Exception ex) {
+                // need to handle this scenario
+            }
+        }
+
+        logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + annualFeeData.size());
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES)
+    public void applyDueChargesForSavings() throws JobExecutionException {
+        final Collection<SavingsAccountAnnualFeeData> chargesDueData = this.savingsAccountChargeReadPlatformService
+                .retrieveChargesWithDue();
+        final StringBuilder errorMsg = new StringBuilder();
+
+        for (final SavingsAccountAnnualFeeData savingsAccountReference : chargesDueData) {
+            try {
+                this.savingsAccountWritePlatformService.applyChargeDue(savingsAccountReference.getId(),
+                        savingsAccountReference.getAccountId());
+            } catch (final PlatformApiDataValidationException e) {
+                final List<ApiParameterError> errors = e.getErrors();
+                for (final ApiParameterError error : errors) {
+                    logger.error("Apply Charges due for savings failed for account:" + savingsAccountReference.getAccountNo()
+                            + " with message " + error.getDeveloperMessage());
+                    errorMsg.append("Apply Charges due for savings failed for account:").append(savingsAccountReference.getAccountNo())
+                            .append(" with message ").append(error.getDeveloperMessage());
+                }
+            }
+        }
+
+        logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + chargesDueData.size());
+
+        /*
+         * throw exception if any charge payment fails.
+         */
+        if (errorMsg.length() > 0) { throw new JobExecutionException(errorMsg.toString()); }
+    }
+
+    @Transactional
+    @Override
+    @CronTarget(jobName = JobName.UPDATE_NPA)
+    public void updateNPA() {
+
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
+
+        final StringBuilder resetNPASqlBuilder = new StringBuilder(900);
+        resetNPASqlBuilder.append("update m_loan loan ");
+        resetNPASqlBuilder.append("left join m_loan_arrears_aging laa on laa.loan_id = loan.id ");
+        resetNPASqlBuilder.append("inner join m_product_loan mpl on mpl.id = loan.product_id and mpl.overdue_days_for_npa is not null ");
+        resetNPASqlBuilder.append("set loan.is_npa = 0 ");
+        resetNPASqlBuilder.append("where  loan.loan_status_id = 300 and mpl.account_moves_out_of_npa_only_on_arrears_completion = 0 ");
+        resetNPASqlBuilder
+                .append("or (mpl.account_moves_out_of_npa_only_on_arrears_completion = 1 and laa.overdue_since_date_derived is null)");
+
+        jdbcTemplate.update(resetNPASqlBuilder.toString());
+
+        final StringBuilder updateSqlBuilder = new StringBuilder(900);
+
+        updateSqlBuilder.append("UPDATE m_loan as ml,");
+        updateSqlBuilder.append(" (select loan.id ");
+        updateSqlBuilder.append("from m_loan_arrears_aging laa");
+        updateSqlBuilder.append(" INNER JOIN  m_loan loan on laa.loan_id = loan.id ");
+        updateSqlBuilder.append(" INNER JOIN m_product_loan mpl on mpl.id = loan.product_id AND mpl.overdue_days_for_npa is not null ");
+        updateSqlBuilder.append("WHERE loan.loan_status_id = 300  and ");
+        updateSqlBuilder.append("laa.overdue_since_date_derived < SUBDATE(CURDATE(),INTERVAL  ifnull(mpl.overdue_days_for_npa,0) day) ");
+        updateSqlBuilder.append("group by loan.id) as sl ");
+        updateSqlBuilder.append("SET ml.is_npa=1 where ml.id=sl.id ");
+
+        final int result = jdbcTemplate.update(updateSqlBuilder.toString());
+
+        logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result);
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS)
+    public void updateMaturityDetailsOfDepositAccounts() {
+
+        final Collection<DepositAccountData> depositAccounts = this.depositAccountReadPlatformService.retrieveForMaturityUpdate();
+
+        for (final DepositAccountData depositAccount : depositAccounts) {
+            try {
+                final DepositAccountType depositAccountType = DepositAccountType.fromInt(depositAccount.depositType().getId().intValue());
+                this.depositAccountWritePlatformService.updateMaturityDetails(depositAccount.id(), depositAccountType);
+            } catch (final PlatformApiDataValidationException e) {
+                final List<ApiParameterError> errors = e.getErrors();
+                for (final ApiParameterError error : errors) {
+                    logger.error("Update maturity details failed for account:" + depositAccount.accountNo() + " with message "
+                            + error.getDeveloperMessage());
+                }
+            } catch (final Exception ex) {
+                // need to handle this scenario
+            }
+        }
+
+        logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Deposit accounts affected by update: " + depositAccounts.size());
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.GENERATE_RD_SCEHDULE)
+    public void generateRDSchedule() {
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
+        final Collection<Map<String, Object>> scheduleDetails = this.depositAccountReadPlatformService.retriveDataForRDScheduleCreation();
+        String insertSql = "INSERT INTO `m_mandatory_savings_schedule` (`savings_account_id`, `duedate`, `installment`, `deposit_amount`, `completed_derived`, `created_date`, `lastmodified_date`) VALUES ";
+        StringBuilder sb = new StringBuilder();
+        String currentDate = formatterWithTime.print(DateUtils.getLocalDateTimeOfTenant());
+        int iterations = 0;
+        for (Map<String, Object> details : scheduleDetails) {
+            Long count = (Long) details.get("futureInstallemts");
+            if (count == null) {
+                count = 0l;
+            }
+            final Long savingsId = (Long) details.get("savingsId");
+            final BigDecimal amount = (BigDecimal) details.get("amount");
+            final String recurrence = (String) details.get("recurrence");
+            Date date = (Date) details.get("dueDate");
+            LocalDate lastDepositDate = new LocalDate(date);
+            Integer installmentNumber = (Integer) details.get("installment");
+            while (count < DepositAccountUtils.GENERATE_MINIMUM_NUMBER_OF_FUTURE_INSTALMENTS) {
+                count++;
+                installmentNumber++;
+                lastDepositDate = DepositAccountUtils.calculateNextDepositDate(lastDepositDate, recurrence);
+
+                if (sb.length() > 0) {
+                    sb.append(", ");
+                }
+                sb.append("(");
+                sb.append(savingsId);
+                sb.append(",'");
+                sb.append(formatter.print(lastDepositDate));
+                sb.append("',");
+                sb.append(installmentNumber);
+                sb.append(",");
+                sb.append(amount);
+                sb.append(", b'0','");
+                sb.append(currentDate);
+                sb.append("','");
+                sb.append(currentDate);
+                sb.append("')");
+                iterations++;
+                if (iterations > 200) {
+                    jdbcTemplate.update(insertSql + sb.toString());
+                    sb = new StringBuilder();
+                }
+
+            }
+        }
+
+        if (sb.length() > 0) {
+            jdbcTemplate.update(insertSql + sb.toString());
+        }
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/api/LookupTableApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/spm/api/LookupTableApiResource.java
new file mode 100644
index 0000000..588f24e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/api/LookupTableApiResource.java
@@ -0,0 +1,118 @@
+/**
+ * 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.spm.api;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.spm.data.LookupTableData;
+import org.apache.fineract.spm.domain.LookupTable;
+import org.apache.fineract.spm.domain.Survey;
+import org.apache.fineract.spm.exception.LookupTableNotFoundException;
+import org.apache.fineract.spm.exception.SurveyNotFoundException;
+import org.apache.fineract.spm.service.LookupTableService;
+import org.apache.fineract.spm.service.SpmService;
+import org.apache.fineract.spm.util.LookupTableMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Path("/surveys/{surveyId}/lookuptables")
+@Component
+@Scope("singleton")
+public class LookupTableApiResource {
+
+    private final PlatformSecurityContext securityContext;
+    private final SpmService spmService;
+    private final LookupTableService lookupTableService;
+
+    @Autowired
+    public LookupTableApiResource(final PlatformSecurityContext securityContext,
+                                  final SpmService spmService,
+                                  final LookupTableService lookupTableService) {
+        super();
+        this.securityContext = securityContext;
+        this.spmService = spmService;
+        this.lookupTableService = lookupTableService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public List<LookupTableData> fetchLookupTables(@PathParam("surveyId") final Long surveyId) {
+        this.securityContext.authenticatedUser();
+
+        final Survey survey = findSurvey(surveyId);
+
+        final List<LookupTable> lookupTables = this.lookupTableService.findBySurvey(survey);
+
+        if (lookupTables != null) {
+            return LookupTableMapper.map(lookupTables);
+        }
+
+        return Collections.EMPTY_LIST;
+    }
+
+    @GET
+    @Path("/{key}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public LookupTableData findLookupTable(@PathParam("surveyId") final Long surveyId,
+                                           @PathParam("key") final String key) {
+        this.securityContext.authenticatedUser();
+
+        findSurvey(surveyId);
+
+        final List<LookupTable> lookupTables = this.lookupTableService.findByKey(key);
+
+        if (lookupTables == null || lookupTables.isEmpty()) {
+            throw new LookupTableNotFoundException(key);
+        }
+
+        return LookupTableMapper.map(lookupTables).get(0);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public void createLookupTable(@PathParam("surveyId") final Long surveyId,
+                                  final LookupTableData lookupTableData) {
+        this.securityContext.authenticatedUser();
+
+        final Survey survey = findSurvey(surveyId);
+
+        this.lookupTableService.createLookupTable(LookupTableMapper.map(lookupTableData, survey));
+    }
+
+    private Survey findSurvey(final Long surveyId) {
+        final Survey survey = this.spmService.findById(surveyId);
+        if (survey == null) {
+            throw new SurveyNotFoundException(surveyId);
+        }
+        return survey;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/api/ScorecardApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/spm/api/ScorecardApiResource.java
new file mode 100644
index 0000000..b1e0fa5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/api/ScorecardApiResource.java
@@ -0,0 +1,132 @@
+/**
+ * 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.spm.api;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepository;
+import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
+import org.apache.fineract.spm.data.ScorecardData;
+import org.apache.fineract.spm.domain.Scorecard;
+import org.apache.fineract.spm.domain.Survey;
+import org.apache.fineract.spm.exception.SurveyNotFoundException;
+import org.apache.fineract.spm.service.ScorecardService;
+import org.apache.fineract.spm.service.SpmService;
+import org.apache.fineract.spm.util.ScorecardMapper;
+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;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.Collections;
+import java.util.List;
+
+@Path("/surveys/{surveyId}/scorecards")
+@Component
+@Scope("singleton")
+public class ScorecardApiResource {
+
+    private final PlatformSecurityContext securityContext;
+    private final SpmService spmService;
+    private final ScorecardService scorecardService;
+    private final ClientRepository clientRepository;
+
+    @Autowired
+    public ScorecardApiResource(final PlatformSecurityContext securityContext, final SpmService spmService,
+                                final ScorecardService scorecardService, final ClientRepository clientRepository) {
+        super();
+        this.securityContext = securityContext;
+        this.spmService = spmService;
+        this.scorecardService = scorecardService;
+        this.clientRepository = clientRepository;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public List<ScorecardData> findBySurvey(@PathParam("surveyId") final Long surveyId) {
+        this.securityContext.authenticatedUser();
+
+        final Survey survey = findSurvey(surveyId);
+
+        final List<Scorecard> scorecards = this.scorecardService.findBySurvey(survey);
+
+        if (scorecards == null) {
+            return ScorecardMapper.map(scorecards);
+        }
+
+        return Collections.EMPTY_LIST;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public void createScorecard(@PathParam("surveyId") final Long surveyId, final ScorecardData scorecardData) {
+        final AppUser appUser = this.securityContext.authenticatedUser();
+
+        final Survey survey = findSurvey(surveyId);
+
+        final Client client = this.clientRepository.findOne(scorecardData.getClientId());
+
+        if (client == null) {
+            throw new ClientNotFoundException(scorecardData.getClientId());
+        }
+
+        this.scorecardService.createScorecard(ScorecardMapper.map(scorecardData, survey, appUser, client));
+    }
+
+    @Path("/clients/{clientId}")
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public List<ScorecardData> findBySurveyClient(@PathParam("surveyId") final Long surveyId,
+                                                  @PathParam("clientId") final Long clientId) {
+        this.securityContext.authenticatedUser();
+
+        final Survey survey = findSurvey(surveyId);
+
+        final Client client = this.clientRepository.findOne(clientId);
+
+        if (client == null) {
+            throw new ClientNotFoundException(clientId);
+        }
+
+        final List<Scorecard> scorecards = this.scorecardService.findBySurveyAndClient(survey, client);
+
+        if (scorecards == null) {
+            return ScorecardMapper.map(scorecards);
+        }
+
+        return Collections.EMPTY_LIST;
+    }
+
+    private Survey findSurvey(final Long surveyId) {
+        final Survey survey = this.spmService.findById(surveyId);
+        if (survey == null) {
+            throw new SurveyNotFoundException(surveyId);
+        }
+        return survey;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/api/SpmApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/spm/api/SpmApiResource.java
new file mode 100644
index 0000000..8da5c15
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/api/SpmApiResource.java
@@ -0,0 +1,111 @@
+/**
+ * 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.spm.api;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.spm.data.SurveyData;
+import org.apache.fineract.spm.domain.Survey;
+import org.apache.fineract.spm.exception.SurveyNotFoundException;
+import org.apache.fineract.spm.service.SpmService;
+import org.apache.fineract.spm.util.SurveyMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.ArrayList;
+import java.util.List;
+
+@Path("/surveys")
+@Component
+@Scope("singleton")
+public class SpmApiResource {
+
+    private final PlatformSecurityContext securityContext;
+    private final SpmService spmService;
+
+    @Autowired
+    public SpmApiResource(final PlatformSecurityContext securityContext,
+                          final SpmService spmService) {
+        this.securityContext = securityContext;
+        this.spmService = spmService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public List<SurveyData> fetchActiveSurveys() {
+        this.securityContext.authenticatedUser();
+
+        final List<SurveyData> result = new ArrayList<>();
+
+        final List<Survey> surveys = this.spmService.fetchValidSurveys();
+
+        if (surveys != null) {
+            for (final Survey survey : surveys) {
+                result.add(SurveyMapper.map(survey));
+            }
+        }
+
+        return result;
+    }
+
+    @GET
+    @Path("/{id}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public SurveyData findSurvey(@PathParam("id") final Long id) {
+        this.securityContext.authenticatedUser();
+
+        final Survey survey = this.spmService.findById(id);
+
+        if (survey == null) {
+            throw new SurveyNotFoundException(id);
+        }
+
+        return SurveyMapper.map(survey);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public void createSurvey(final SurveyData surveyData) {
+        this.securityContext.authenticatedUser();
+
+        final Survey survey = SurveyMapper.map(surveyData);
+
+        this.spmService.createSurvey(survey);
+    }
+
+    @DELETE
+    @Path("/{id}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Transactional
+    public void deactivateSurvey(@PathParam("id") final Long id) {
+        this.securityContext.authenticatedUser();
+
+        this.spmService.deactivateSurvey(id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/data/ComponentData.java b/fineract-provider/src/main/java/org/apache/fineract/spm/data/ComponentData.java
new file mode 100644
index 0000000..21a1474
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/data/ComponentData.java
@@ -0,0 +1,78 @@
+/**
+ * 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.spm.data;
+
+public class ComponentData {
+
+    private Long id;
+    private String key;
+    private String text;
+    private String description;
+    private Integer sequenceNo;
+
+    public ComponentData() {
+        super();
+    }
+
+    public ComponentData(final Long id, final String key, final String text,
+                         final String description, final Integer sequenceNo) {
+        super();
+        this.id = id;
+        this.key = key;
+        this.text = text;
+        this.description = description;
+        this.sequenceNo = sequenceNo;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String title) {
+        this.text = title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Integer getSequenceNo() {
+        return sequenceNo;
+    }
+
+    public void setSequenceNo(Integer sequenceNo) {
+        this.sequenceNo = sequenceNo;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/data/LookupTableData.java b/fineract-provider/src/main/java/org/apache/fineract/spm/data/LookupTableData.java
new file mode 100644
index 0000000..466808a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/data/LookupTableData.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.spm.data;
+
+import java.util.List;
+
+public class LookupTableData {
+
+    private String key;
+    private String description;
+    private List<LookupTableEntry> entries;
+
+    public LookupTableData() {
+        super();
+    }
+
+    public LookupTableData(final String key, final String description,
+                           final List<LookupTableEntry> entries) {
+        super();
+        this.key = key;
+        this.description = description;
+        this.entries = entries;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public List<LookupTableEntry> getEntries() {
+        return entries;
+    }
+
+    public void setEntries(List<LookupTableEntry> entries) {
+        this.entries = entries;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/data/LookupTableEntry.java b/fineract-provider/src/main/java/org/apache/fineract/spm/data/LookupTableEntry.java
new file mode 100644
index 0000000..31a4fef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/data/LookupTableEntry.java
@@ -0,0 +1,61 @@
+/**
+ * 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.spm.data;
+
+public class LookupTableEntry {
+
+    private Integer valueFrom;
+    private Integer valueTo;
+    private Double score;
+
+    public LookupTableEntry() {
+        super();
+    }
+
+    public LookupTableEntry(final Integer valueFrom, final Integer valueTo, final Double score) {
+        super();
+        this.valueFrom = valueFrom;
+        this.valueTo = valueTo;
+        this.score = score;
+    }
+
+    public Integer getValueFrom() {
+        return valueFrom;
+    }
+
+    public void setValueFrom(Integer valueFrom) {
+        this.valueFrom = valueFrom;
+    }
+
+    public Integer getValueTo() {
+        return valueTo;
+    }
+
+    public void setValueTo(Integer valueTo) {
+        this.valueTo = valueTo;
+    }
+
+    public Double getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/data/QuestionData.java b/fineract-provider/src/main/java/org/apache/fineract/spm/data/QuestionData.java
new file mode 100644
index 0000000..f61c23f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/data/QuestionData.java
@@ -0,0 +1,100 @@
+/**
+ * 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.spm.data;
+
+import java.util.List;
+
+public class QuestionData {
+
+    private Long id;
+    private List<ResponseData> responseDatas;
+    private String componentKey;
+    private String key;
+    private String text;
+    private String description;
+    private Integer sequenceNo;
+
+    public QuestionData() {
+        super();
+    }
+
+    public QuestionData(final Long id, final List<ResponseData> responseDatas, final String componentKey, final String key,
+                        final String text, final String description, final Integer sequenceNo) {
+        super();
+        this.id = id;
+        this.responseDatas = responseDatas;
+        this.componentKey = componentKey;
+        this.key = key;
+        this.text = text;
+        this.description = description;
+        this.sequenceNo = sequenceNo;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public List<ResponseData> getResponseDatas() {
+        return responseDatas;
+    }
+
+    public void setResponseDatas(List<ResponseData> responseDatas) {
+        this.responseDatas = responseDatas;
+    }
+
+    public String getComponentKey() {
+        return componentKey;
+    }
+
+    public void setComponentKey(String componentKey) {
+        this.componentKey = componentKey;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Integer getSequenceNo() {
+        return sequenceNo;
+    }
+
+    public void setSequenceNo(Integer sequenceNo) {
+        this.sequenceNo = sequenceNo;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/data/ResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/spm/data/ResponseData.java
new file mode 100644
index 0000000..ff4be4a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/data/ResponseData.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.spm.data;
+
+public class ResponseData {
+
+    private Long id;
+    private String text;
+    private Integer value;
+    private Integer sequenceNo;
+
+    public ResponseData() {
+        super();
+    }
+
+    public ResponseData(final Long id, final String text, final Integer value,
+                        final Integer sequenceNo) {
+        super();
+        this.id = id;
+        this.text = text;
+        this.value = value;
+        this.sequenceNo = sequenceNo;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public void setValue(Integer value) {
+        this.value = value;
+    }
+
+    public Integer getSequenceNo() {
+        return sequenceNo;
+    }
+
+    public void setSequenceNo(Integer sequenceNo) {
+        this.sequenceNo = sequenceNo;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/data/ScorecardData.java b/fineract-provider/src/main/java/org/apache/fineract/spm/data/ScorecardData.java
new file mode 100644
index 0000000..b19b79e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/data/ScorecardData.java
@@ -0,0 +1,75 @@
+/**
+ * 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.spm.data;
+
+import java.util.Date;
+import java.util.List;
+
+public class ScorecardData {
+
+    private Long userId;
+    private Long clientId;
+    private Date createdOn;
+    private List<ScorecardValue> scorecardValues;
+
+    public ScorecardData() {
+        super();
+    }
+
+    public ScorecardData(final Long userId, final Long clientId, final Date createdOn,
+                         final List<ScorecardValue> scorecardValues) {
+        super();
+        this.userId = userId;
+        this.clientId = clientId;
+        this.createdOn = createdOn;
+        this.scorecardValues = scorecardValues;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public Long getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(Long clientId) {
+        this.clientId = clientId;
+    }
+
+    public Date getCreatedOn() {
+        return createdOn;
+    }
+
+    public void setCreatedOn(Date createdOn) {
+        this.createdOn = createdOn;
+    }
+
+    public List<ScorecardValue> getScorecardValues() {
+        return scorecardValues;
+    }
+
+    public void setScorecardValues(List<ScorecardValue> scorecardValues) {
+        this.scorecardValues = scorecardValues;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/data/ScorecardValue.java b/fineract-provider/src/main/java/org/apache/fineract/spm/data/ScorecardValue.java
new file mode 100644
index 0000000..9f22c2a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/data/ScorecardValue.java
@@ -0,0 +1,61 @@
+/**
+ * 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.spm.data;
+
+public class ScorecardValue {
+
+    private Long questionId;
+    private Long responseId;
+    private Integer value;
+
+    public ScorecardValue() {
+        super();
+    }
+
+    public ScorecardValue(final Long questionId, final Long responseId, final Integer value) {
+        super();
+        this.questionId = questionId;
+        this.responseId = responseId;
+        this.value = value;
+    }
+
+    public Long getQuestionId() {
+        return questionId;
+    }
+
+    public void setQuestionId(Long questionId) {
+        this.questionId = questionId;
+    }
+
+    public Long getResponseId() {
+        return responseId;
+    }
+
+    public void setResponseId(Long responseId) {
+        this.responseId = responseId;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public void setValue(Integer value) {
+        this.value = value;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/data/SurveyData.java b/fineract-provider/src/main/java/org/apache/fineract/spm/data/SurveyData.java
new file mode 100644
index 0000000..ad2c039
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/data/SurveyData.java
@@ -0,0 +1,126 @@
+/**
+ * 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.spm.data;
+
+import java.util.Date;
+import java.util.List;
+
+public class SurveyData {
+
+    private Long id;
+    private List<ComponentData> componentDatas;
+    private List<QuestionData> questionDatas;
+    private String key;
+    private String name;
+    private String description;
+    private String countryCode;
+    private Date validFrom;
+    private Date validTo;
+
+    public SurveyData() {
+        super();
+    }
+
+    public SurveyData(final Long id, final List<ComponentData> componentDatas, final List<QuestionData> questionDatas,
+                      final String key, final String name, final String description, final String countryCode,
+                      final Date validFrom, final Date validTo) {
+        super();
+        this.id = id;
+        this.componentDatas = componentDatas;
+        this.questionDatas = questionDatas;
+        this.key = key;
+        this.name = name;
+        this.description = description;
+        this.countryCode = countryCode;
+        this.validFrom = validFrom;
+        this.validTo = validTo;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public List<ComponentData> getComponentDatas() {
+        return componentDatas;
+    }
+
+    public void setComponentDatas(List<ComponentData> componentDatas) {
+        this.componentDatas = componentDatas;
+    }
+
+    public List<QuestionData> getQuestionDatas() {
+        return questionDatas;
+    }
+
+    public void setQuestionDatas(List<QuestionData> questionDatas) {
+        this.questionDatas = questionDatas;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getCountryCode() {
+        return countryCode;
+    }
+
+    public void setCountryCode(String countryCode) {
+        this.countryCode = countryCode;
+    }
+
+    public Date getValidFrom() {
+        return validFrom;
+    }
+
+    public void setValidFrom(Date validFrom) {
+        this.validFrom = validFrom;
+    }
+
+    public Date getValidTo() {
+        return validTo;
+    }
+
+    public void setValidTo(Date validTo) {
+        this.validTo = validTo;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Component.java b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Component.java
new file mode 100644
index 0000000..5e7b4a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Component.java
@@ -0,0 +1,88 @@
+/**
+ * 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.spm.domain;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "m_survey_components")
+public class Component extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "survey_id")
+    private Survey survey;
+
+    @Column(name = "a_key", length = 32)
+    private String key;
+
+    @Column(name = "a_text", length = 255)
+    private String text;
+
+    @Column(name = "description", length = 4096)
+    private String description;
+
+    @Column(name = "sequence_no", precision = 4)
+    private Integer sequenceNo;
+
+    public Component() {
+        super();
+    }
+
+    public Survey getSurvey() {
+        return survey;
+    }
+
+    public void setSurvey(Survey survey) {
+        this.survey = survey;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String title) {
+        this.text = title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Integer getSequenceNo() {
+        return sequenceNo;
+    }
+
+    public void setSequenceNo(Integer sequenceNo) {
+        this.sequenceNo = sequenceNo;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/domain/LookupTable.java b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/LookupTable.java
new file mode 100644
index 0000000..1448798
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/LookupTable.java
@@ -0,0 +1,99 @@
+/**
+ * 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.spm.domain;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "m_survey_lookup_tables")
+public class LookupTable extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "survey_id")
+    private Survey survey;
+
+    @Column(name = "a_key", length = 32)
+    private String key;
+
+    @Column(name = "description", length = 4096)
+    private String description;
+
+    @Column(name = "value_from", precision = 4)
+    private Integer valueFrom;
+
+    @Column(name = "value_to", precision = 4)
+    private Integer valueTo;
+
+    @Column(name = "score", precision = 5, scale = 2)
+    private Double score;
+
+    public LookupTable() {
+        super();
+    }
+
+    public Survey getSurvey() {
+        return survey;
+    }
+
+    public void setSurvey(Survey survey) {
+        this.survey = survey;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Integer getValueFrom() {
+        return valueFrom;
+    }
+
+    public void setValueFrom(Integer valueFrom) {
+        this.valueFrom = valueFrom;
+    }
+
+    public Integer getValueTo() {
+        return valueTo;
+    }
+
+    public void setValueTo(Integer valueTo) {
+        this.valueTo = valueTo;
+    }
+
+    public Double getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Question.java b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Question.java
new file mode 100644
index 0000000..861a5c8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Question.java
@@ -0,0 +1,112 @@
+/**
+ * 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.spm.domain;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.*;
+import java.util.List;
+
+@Entity
+@Table(name = "m_survey_questions")
+public class Question extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "survey_id")
+    private Survey survey;
+
+    @OneToMany(mappedBy = "question", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+    @OrderBy("sequenceNo")
+    private List<Response> responses;
+
+    @Column(name = "component_key", length = 32)
+    private String componentKey;
+
+    @Column(name = "a_key", length = 32)
+    private String key;
+
+    @Column(name = "a_text", length = 255)
+    private String text;
+
+    @Column(name = "description", length = 4096)
+    private String description;
+
+    @Column(name = "sequence_no", precision = 4)
+    private Integer sequenceNo;
+
+    public Question() {
+        super();
+    }
+
+    public Survey getSurvey() {
+        return survey;
+    }
+
+    public void setSurvey(Survey survey) {
+        this.survey = survey;
+    }
+
+    public List<Response> getResponses() {
+        return responses;
+    }
+
+    public void setResponses(List<Response> responses) {
+        this.responses = responses;
+    }
+
+    public String getComponentKey() {
+        return componentKey;
+    }
+
+    public void setComponentKey(String componentKey) {
+        this.componentKey = componentKey;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Integer getSequenceNo() {
+        return sequenceNo;
+    }
+
+    public void setSequenceNo(Integer sequenceNo) {
+        this.sequenceNo = sequenceNo;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Response.java b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Response.java
new file mode 100644
index 0000000..1b742d8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Response.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.spm.domain;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "m_survey_responses")
+public class Response extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "question_id")
+    private Question question;
+
+    @Column(name = "a_text", length = 255)
+    private String text;
+
+    @Column(name = "a_value", precision = 4)
+    private Integer value;
+
+    @Column(name = "sequence_no", precision = 4)
+    private Integer sequenceNo;
+
+    public Response() {
+        super();
+    }
+
+    public Question getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(Question question) {
+        this.question = question;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public void setValue(Integer value) {
+        this.value = value;
+    }
+
+    public Integer getSequenceNo() {
+        return sequenceNo;
+    }
+
+    public void setSequenceNo(Integer sequenceNo) {
+        this.sequenceNo = sequenceNo;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Scorecard.java b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Scorecard.java
new file mode 100644
index 0000000..40a9268
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Scorecard.java
@@ -0,0 +1,119 @@
+/**
+ * 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.spm.domain;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.*;
+import java.util.Date;
+
+@Entity
+@Table(name = "m_survey_scorecards")
+public class Scorecard extends AbstractPersistable<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "survey_id")
+    private Survey survey;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "question_id")
+    private Question question;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "response_id")
+    private Response response;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "user_id")
+    private AppUser appUser;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "client_id")
+    private Client client;
+
+    @Column(name = "created_on")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    @OrderBy("createdOn DESC")
+    private Date createdOn;
+
+    @Column(name = "a_value", precision = 4)
+    private Integer value;
+
+    public Scorecard() {
+        super();
+    }
+
+    public Survey getSurvey() {
+        return survey;
+    }
+
+    public void setSurvey(Survey survey) {
+        this.survey = survey;
+    }
+
+    public Question getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(Question question) {
+        this.question = question;
+    }
+
+    public Response getResponse() {
+        return response;
+    }
+
+    public void setResponse(Response response) {
+        this.response = response;
+    }
+
+    public AppUser getAppUser() {
+        return appUser;
+    }
+
+    public void setAppUser(AppUser appUser) {
+        this.appUser = appUser;
+    }
+
+    public Client getClient() {
+        return client;
+    }
+
+    public void setClient(Client client) {
+        this.client = client;
+    }
+
+    public Date getCreatedOn() {
+        return createdOn;
+    }
+
+    public void setCreatedOn(Date createdOn) {
+        this.createdOn = createdOn;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public void setValue(Integer value) {
+        this.value = value;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Survey.java b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Survey.java
new file mode 100644
index 0000000..8728cdd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/domain/Survey.java
@@ -0,0 +1,126 @@
+/**
+ * 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.spm.domain;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import javax.persistence.*;
+import java.util.Date;
+import java.util.List;
+
+@Entity
+@Table(name = "m_surveys")
+public class Survey extends AbstractPersistable<Long> {
+
+    @OneToMany(mappedBy = "survey", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+    @OrderBy("sequenceNo")
+    private List<Component> components;
+
+    @OneToMany(mappedBy = "survey", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+    @OrderBy("sequenceNo")
+    private List<Question> questions;
+
+    @Column(name = "a_key", length = 32)
+    private String key;
+
+    @Column(name = "a_name", length = 255)
+    private String name;
+
+    @Column(name = "description", length = 4096)
+    private String description;
+
+    @Column(name = "country_code", length = 2)
+    private String countryCode;
+
+    @Column(name = "valid_from")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date validFrom;
+
+    @Column(name = "valid_to")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date validTo;
+
+    public Survey() {
+        super();
+    }
+
+    public List<Component> getComponents() {
+        return components;
+    }
+
+    public void setComponents(List<Component> components) {
+        this.components = components;
+    }
+
+    public List<Question> getQuestions() {
+        return questions;
+    }
+
+    public void setQuestions(List<Question> questions) {
+        this.questions = questions;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getCountryCode() {
+        return countryCode;
+    }
+
+    public void setCountryCode(String countryCode) {
+        this.countryCode = countryCode;
+    }
+
+    public Date getValidFrom() {
+        return validFrom;
+    }
+
+    public void setValidFrom(Date validFrom) {
+        this.validFrom = validFrom;
+    }
+
+    public Date getValidTo() {
+        return validTo;
+    }
+
+    public void setValidTo(Date validTo) {
+        this.validTo = validTo;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/exception/LookupTableNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/spm/exception/LookupTableNotFoundException.java
new file mode 100644
index 0000000..1a0057a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/exception/LookupTableNotFoundException.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.spm.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class LookupTableNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public LookupTableNotFoundException(final String key) {
+        super("error.msg.survey.lookuptable.notfound", "Lookup table with id " + key + " not found!", key);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/exception/SurveyNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/spm/exception/SurveyNotFoundException.java
new file mode 100644
index 0000000..70ba60a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/exception/SurveyNotFoundException.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.spm.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class SurveyNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public SurveyNotFoundException(final Long id) {
+        super("error.msg.survey.id.notfound", "Survey with id " + id + " not found!", id);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/repository/LookupTableRepository.java b/fineract-provider/src/main/java/org/apache/fineract/spm/repository/LookupTableRepository.java
new file mode 100644
index 0000000..2b7cd66
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/repository/LookupTableRepository.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.spm.repository;
+
+import org.apache.fineract.spm.domain.LookupTable;
+import org.apache.fineract.spm.domain.Survey;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface LookupTableRepository extends JpaRepository<LookupTable, Long> {
+
+    List<LookupTable> findBySurvey(final Survey survey);
+    List<LookupTable> findByKey(final String key);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/repository/ScorecardRepository.java b/fineract-provider/src/main/java/org/apache/fineract/spm/repository/ScorecardRepository.java
new file mode 100644
index 0000000..7c7e616
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/repository/ScorecardRepository.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.spm.repository;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.spm.domain.Scorecard;
+import org.apache.fineract.spm.domain.Survey;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface ScorecardRepository extends JpaRepository<Scorecard, Long> {
+
+    List<Scorecard> findBySurvey(final Survey survey);
+    List<Scorecard> findBySurveyAndClient(final Survey survey, final Client client);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/repository/SurveyRepository.java b/fineract-provider/src/main/java/org/apache/fineract/spm/repository/SurveyRepository.java
new file mode 100644
index 0000000..6c6344a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/repository/SurveyRepository.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.spm.repository;
+
+import org.apache.fineract.spm.domain.Survey;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.Date;
+import java.util.List;
+
+public interface SurveyRepository extends JpaRepository<Survey, Long> {
+
+    @Query("select s from Survey s where :pointInTime between s.validFrom and s.validTo")
+    List<Survey> fetchActiveSurveys(@Param("pointInTime") final Date pointInTime);
+
+    @Query("select s from Survey s where s.key = :key and :pointInTime between s.validFrom and s.validTo")
+    Survey findByKey(@Param("key") final String key, @Param("pointInTime") final Date pointInTime);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/service/LookupTableService.java b/fineract-provider/src/main/java/org/apache/fineract/spm/service/LookupTableService.java
new file mode 100644
index 0000000..c8ac5ab
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/service/LookupTableService.java
@@ -0,0 +1,61 @@
+/**
+ * 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.spm.service;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.spm.domain.LookupTable;
+import org.apache.fineract.spm.domain.Survey;
+import org.apache.fineract.spm.repository.LookupTableRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class LookupTableService {
+
+    private final PlatformSecurityContext securityContext;
+    private final LookupTableRepository lookupTableRepository;
+
+    @Autowired
+    public LookupTableService(final PlatformSecurityContext securityContext,
+                              final LookupTableRepository lookupTableRepository) {
+        super();
+        this.securityContext = securityContext;
+        this.lookupTableRepository = lookupTableRepository;
+    }
+
+    public List<LookupTable> findByKey(final String key) {
+        this.securityContext.authenticatedUser();
+
+        return this.lookupTableRepository.findByKey(key);
+    }
+
+    public List<LookupTable> findBySurvey(final Survey survey) {
+        this.securityContext.authenticatedUser();
+
+        return this.lookupTableRepository.findBySurvey(survey);
+    }
+
+    public List<LookupTable> createLookupTable(final List<LookupTable> lookupTable) {
+        this.securityContext.authenticatedUser();
+
+        return this.lookupTableRepository.save(lookupTable);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardService.java b/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardService.java
new file mode 100644
index 0000000..41817cb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardService.java
@@ -0,0 +1,63 @@
+/**
+ * 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.spm.service;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.spm.domain.Scorecard;
+import org.apache.fineract.spm.domain.Survey;
+import org.apache.fineract.spm.repository.ScorecardRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+public class ScorecardService {
+
+    private final PlatformSecurityContext securityContext;
+    private final ScorecardRepository scorecardRepository;
+
+    @Autowired
+    public ScorecardService(final PlatformSecurityContext securityContext,
+                            final ScorecardRepository scorecardRepository) {
+        super();
+        this.securityContext = securityContext;
+        this.scorecardRepository = scorecardRepository;
+    }
+
+    public List<Scorecard> createScorecard(final List<Scorecard> scorecards) {
+        this.securityContext.authenticatedUser();
+
+        return this.scorecardRepository.save(scorecards);
+    }
+
+    public List<Scorecard> findBySurvey(final Survey survey) {
+        this.securityContext.authenticatedUser();
+
+        return this.scorecardRepository.findBySurvey(survey);
+    }
+
+    public List<Scorecard> findBySurveyAndClient(final Survey survey, final Client client) {
+        this.securityContext.authenticatedUser();
+
+        return this.scorecardRepository.findBySurveyAndClient(survey, client);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/service/SpmService.java b/fineract-provider/src/main/java/org/apache/fineract/spm/service/SpmService.java
new file mode 100644
index 0000000..424bdaf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/service/SpmService.java
@@ -0,0 +1,111 @@
+/**
+ * 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.spm.service;
+
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.spm.domain.Survey;
+import org.apache.fineract.spm.repository.SurveyRepository;
+import org.joda.time.DateTime;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class SpmService {
+
+    private final PlatformSecurityContext securityContext;
+    private final SurveyRepository surveyRepository;
+
+    @Autowired
+    public SpmService(final PlatformSecurityContext securityContext,
+                      final SurveyRepository surveyRepository) {
+        super();
+        this.securityContext = securityContext;
+        this.surveyRepository = surveyRepository;
+    }
+
+    public List<Survey> fetchValidSurveys() {
+        this.securityContext.authenticatedUser();
+
+        return this.surveyRepository.fetchActiveSurveys(new Date());
+    }
+
+    public Survey findById(final Long id) {
+        this.securityContext.authenticatedUser();
+
+        return this.surveyRepository.findOne(id);
+    }
+
+    public Survey createSurvey(final Survey survey) {
+        this.securityContext.authenticatedUser();
+
+        final Survey previousSurvey = this.surveyRepository.findByKey(survey.getKey(), new Date());
+
+        if (previousSurvey != null) {
+            this.deactivateSurvey(previousSurvey.getId());
+        }
+
+        // set valid from to start of today
+        final DateTime validFrom = DateTime
+                .now()
+                .withHourOfDay(0)
+                .withMinuteOfHour(0)
+                .withSecondOfMinute(0)
+                .withMillisOfSecond(0);
+
+        survey.setValidFrom(validFrom.toDate());
+
+        // set valid from to end in 100 years
+        final DateTime validTo = validFrom
+                .withDayOfMonth(31)
+                .withMonthOfYear(12)
+                .withHourOfDay(23)
+                .withMinuteOfHour(59)
+                .withSecondOfMinute(59)
+                .withMillisOfSecond(999)
+                .plusYears(100);
+
+        survey.setValidTo(validTo.toDate());
+
+        return this.surveyRepository.save(survey);
+    }
+
+    public void deactivateSurvey(final Long id) {
+        this.securityContext.authenticatedUser();
+
+        final Survey survey = this.surveyRepository.findOne(id);
+
+        if (survey != null) {
+            // set valid to to yesterday night
+            final DateTime dateTime = DateTime
+                    .now()
+                    .withHourOfDay(23)
+                    .withMinuteOfHour(59)
+                    .withSecondOfMinute(59)
+                    .withMillisOfSecond(999)
+                    .minusDays(1);
+            survey.setValidTo(dateTime.toDate());
+
+            this.surveyRepository.save(survey);
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/util/LookupTableMapper.java b/fineract-provider/src/main/java/org/apache/fineract/spm/util/LookupTableMapper.java
new file mode 100644
index 0000000..08f5865
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/util/LookupTableMapper.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.spm.util;
+
+import org.apache.fineract.spm.data.LookupTableData;
+import org.apache.fineract.spm.data.LookupTableEntry;
+import org.apache.fineract.spm.domain.LookupTable;
+import org.apache.fineract.spm.domain.Survey;
+
+import java.util.*;
+
+public class LookupTableMapper {
+
+    private LookupTableMapper() {
+        super();
+    }
+
+    public static List<LookupTableData> map(final List<LookupTable> lookupTables) {
+
+        final Map<String, LookupTableData> lookupTableDataMap = new HashMap<>();
+        LookupTableData lookupTableData = null;
+        if (lookupTables != null && !lookupTables.isEmpty()) {
+            for (LookupTable lookupTable : lookupTables) {
+                if ((lookupTableData = lookupTableDataMap.get(lookupTable.getKey())) == null) {
+                    lookupTableData = new LookupTableData();
+                    lookupTableDataMap.put(lookupTable.getKey(), lookupTableData);
+                    lookupTableData.setKey(lookupTable.getKey());
+                    lookupTableData.setDescription(lookupTable.getDescription());
+                    lookupTableData.setEntries(new ArrayList<LookupTableEntry>());
+                }
+                lookupTableData.getEntries().add(new LookupTableEntry(lookupTable.getValueFrom(),
+                        lookupTable.getValueTo(), lookupTable.getScore()));
+            }
+            return new ArrayList<>(lookupTableDataMap.values());
+        }
+
+        return Collections.EMPTY_LIST;
+    }
+
+    public static List<LookupTable> map(final LookupTableData lookupTableData, final Survey survey) {
+        final List<LookupTable> lookupTables = new ArrayList<>();
+
+        final List<LookupTableEntry> entries = lookupTableData.getEntries();
+
+        if (entries != null) {
+            for (LookupTableEntry entry : entries) {
+                final LookupTable lookupTable = new LookupTable();
+                lookupTables.add(lookupTable);
+                lookupTable.setSurvey(survey);
+                lookupTable.setKey(lookupTableData.getKey());
+                lookupTable.setDescription(lookupTableData.getDescription());
+                lookupTable.setValueFrom(entry.getValueFrom());
+                lookupTable.setValueTo(entry.getValueTo());
+                lookupTable.setScore(entry.getScore());
+            }
+        }
+
+        return lookupTables;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/util/ScorecardMapper.java b/fineract-provider/src/main/java/org/apache/fineract/spm/util/ScorecardMapper.java
new file mode 100644
index 0000000..c317ccd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/util/ScorecardMapper.java
@@ -0,0 +1,100 @@
+/**
+ * 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.spm.util;
+
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.spm.data.ScorecardData;
+import org.apache.fineract.spm.data.ScorecardValue;
+import org.apache.fineract.spm.domain.Question;
+import org.apache.fineract.spm.domain.Response;
+import org.apache.fineract.spm.domain.Scorecard;
+import org.apache.fineract.spm.domain.Survey;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+import java.util.*;
+
+public class ScorecardMapper {
+
+    private ScorecardMapper() {
+        super();
+    }
+
+    public static List<ScorecardData> map(final List<Scorecard> scorecards) {
+        final Map<Date, ScorecardData> scorecardDataMap = new HashMap<>();
+        ScorecardData scorecardData = null;
+        if (scorecards != null && scorecards.isEmpty()) {
+            for (Scorecard scorecard : scorecards) {
+                if ((scorecardData = scorecardDataMap.get(scorecard.getCreatedOn())) == null) {
+                    scorecardData = new ScorecardData();
+                    scorecardDataMap.put(scorecard.getCreatedOn(), scorecardData);
+                    scorecardData.setUserId(scorecard.getAppUser().getId());
+                    scorecardData.setClientId(scorecard.getClient().getId());
+                    scorecardData.setCreatedOn(scorecard.getCreatedOn());
+                    scorecardData.setScorecardValues(new ArrayList<ScorecardValue>());
+                }
+
+                scorecardData.getScorecardValues().add(new ScorecardValue(scorecard.getQuestion().getId(), scorecard.getResponse().getId(),
+                        scorecard.getValue()));
+            }
+
+            return new ArrayList<>(scorecardDataMap.values());
+        }
+
+        return Collections.EMPTY_LIST;
+    }
+
+    public static List<Scorecard> map(final ScorecardData scorecardData, final Survey survey,
+                                      final AppUser appUser, final Client client) {
+        final List<Scorecard> scorecards = new ArrayList<>();
+
+        final List<ScorecardValue> scorecardValues = scorecardData.getScorecardValues();
+
+        if (scorecardValues != null) {
+           for (ScorecardValue scorecardValue : scorecardValues) {
+               final Scorecard scorecard = new Scorecard();
+               scorecards.add(scorecard);
+               scorecard.setSurvey(survey);
+               ScorecardMapper.setQuestionAndResponse(scorecardValue, scorecard, survey);
+               scorecard.setAppUser(appUser);
+               scorecard.setClient(client);
+               scorecard.setCreatedOn(scorecardData.getCreatedOn());
+               scorecard.setValue(scorecardValue.getValue());
+           }
+        }
+        return scorecards;
+    }
+
+    private static void setQuestionAndResponse(final ScorecardValue scorecardValue, final Scorecard scorecard,
+                                        final Survey survey) {
+        final List<Question> questions = survey.getQuestions();
+        for (final Question question : questions) {
+            if (question.getId().equals(scorecardValue.getQuestionId())) {
+                scorecard.setQuestion(question);
+                for (final Response response : question.getResponses()) {
+                    if (response.getId().equals(scorecardValue.getResponseId())) {
+                        scorecard.setResponse(response);
+                        break;
+                    }
+                }
+                break;
+            }
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/util/SurveyMapper.java b/fineract-provider/src/main/java/org/apache/fineract/spm/util/SurveyMapper.java
new file mode 100644
index 0000000..45ebd57
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/spm/util/SurveyMapper.java
@@ -0,0 +1,145 @@
+/**
+ * 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.spm.util;
+
+import org.apache.fineract.spm.data.ComponentData;
+import org.apache.fineract.spm.data.QuestionData;
+import org.apache.fineract.spm.data.ResponseData;
+import org.apache.fineract.spm.data.SurveyData;
+import org.apache.fineract.spm.domain.Component;
+import org.apache.fineract.spm.domain.Question;
+import org.apache.fineract.spm.domain.Response;
+import org.apache.fineract.spm.domain.Survey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SurveyMapper {
+
+    private SurveyMapper() {
+        super();
+    }
+
+    public static SurveyData map(final Survey survey) {
+        final SurveyData surveyData = new SurveyData(
+                survey.getId(), SurveyMapper.mapComponents(survey.getComponents()),
+                SurveyMapper.mapQuestions(survey.getQuestions()), survey.getKey(), survey.getName(),
+                survey.getDescription(), survey.getCountryCode(), survey.getValidFrom(), survey.getValidTo()
+        );
+        return surveyData;
+    }
+
+    public static Survey map(final SurveyData surveyData) {
+        final Survey survey = new Survey();
+        survey.setComponents(SurveyMapper.mapComponentDatas(surveyData.getComponentDatas(), survey));
+        survey.setQuestions(SurveyMapper.mapQuestionDatas(surveyData.getQuestionDatas(), survey));
+        survey.setKey(surveyData.getKey());
+        survey.setName(surveyData.getName());
+        survey.setDescription(surveyData.getDescription());
+        survey.setCountryCode(surveyData.getCountryCode());
+        return survey;
+    }
+
+    private static List<ComponentData> mapComponents(final List<Component> components) {
+        final List<ComponentData> componentDatas = new ArrayList<>();
+        if (components != null) {
+            for (final Component component : components) {
+                componentDatas.add(new ComponentData(
+                        component.getId(), component.getKey(), component.getText(), component.getDescription(),
+                        component.getSequenceNo()
+                ));
+            }
+        }
+        return componentDatas;
+    }
+
+    private static List<Component> mapComponentDatas(final List<ComponentData> componentDatas, final Survey survey) {
+        final List<Component> components = new ArrayList<>();
+        if (componentDatas != null) {
+            for (final ComponentData componentData : componentDatas) {
+                final Component component = new Component();
+                component.setSurvey(survey);
+                component.setKey(componentData.getKey());
+                component.setText(componentData.getText());
+                component.setDescription(componentData.getDescription());
+                component.setSequenceNo(componentData.getSequenceNo());
+                components.add(component);
+            }
+        }
+        return components;
+    }
+
+    private static List<QuestionData> mapQuestions(final List<Question> questions) {
+        final List<QuestionData> questionDatas = new ArrayList<>();
+        if (questions != null) {
+            for (final Question question : questions) {
+                questionDatas.add(new QuestionData(question.getId(),
+                        SurveyMapper.mapResponses(question.getResponses()), question.getComponentKey(), question.getKey(),
+                        question.getText(), question.getDescription(), question.getSequenceNo()
+                ));
+            }
+        }
+        return questionDatas;
+    }
+
+    private static List<Question> mapQuestionDatas(final List<QuestionData> questionDatas, final Survey survey) {
+        final List<Question> questions = new ArrayList<>();
+        if (questionDatas != null) {
+            for (final QuestionData questionData : questionDatas) {
+                final Question question = new Question();
+                question.setSurvey(survey);
+                question.setComponentKey(questionData.getComponentKey());
+                question.setResponses(SurveyMapper.mapResponseDatas(questionData.getResponseDatas(), question));
+                question.setKey(questionData.getKey());
+                question.setText(questionData.getText());
+                question.setDescription(question.getDescription());
+                question.setSequenceNo(questionData.getSequenceNo());
+                questions.add(question);
+            }
+        }
+        return questions;
+    }
+
+    private static List<ResponseData> mapResponses(final List<Response> responses) {
+        final List<ResponseData> responseDatas = new ArrayList<>();
+        if (responses != null) {
+            for (final Response response : responses) {
+                responseDatas.add(new ResponseData(
+                    response.getId(), response.getText(), response.getValue(), response.getSequenceNo()
+                ));
+            }
+        }
+        return responseDatas;
+    }
+
+    private static List<Response> mapResponseDatas(final List<ResponseData> responseDatas, final Question question) {
+        final List<Response> responses = new ArrayList<>();
+        if (responseDatas != null) {
+            for (final ResponseData responseData : responseDatas) {
+                final Response response = new Response();
+                response.setQuestion(question);
+                response.setText(responseData.getText());
+                response.setValue(responseData.getValue());
+                response.setSequenceNo(responseData.getSequenceNo());
+                responses.add(response);
+            }
+        }
+        return responses;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/api/TemplatesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/template/api/TemplatesApiResource.java
new file mode 100644
index 0000000..442e76c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/api/TemplatesApiResource.java
@@ -0,0 +1,213 @@
+/**
+ * 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.template.api;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.template.data.TemplateData;
+import org.apache.fineract.template.domain.Template;
+import org.apache.fineract.template.domain.TemplateEntity;
+import org.apache.fineract.template.domain.TemplateType;
+import org.apache.fineract.template.service.TemplateDomainService;
+import org.apache.fineract.template.service.TemplateMergeService;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/templates")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+public class TemplatesApiResource {
+
+    private final Set<String> RESPONSE_TEMPLATES_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id"));
+    private final Set<String> RESPONSE_TEMPLATE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "entities", "types", "template"));
+    private final String RESOURCE_NAME_FOR_PERMISSION = "template";
+
+    private final PlatformSecurityContext context;
+    private final DefaultToApiJsonSerializer<Template> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<TemplateData> templateDataApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final TemplateDomainService templateService;
+    private final TemplateMergeService templateMergeService;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public TemplatesApiResource(final PlatformSecurityContext context, final DefaultToApiJsonSerializer<Template> toApiJsonSerializer,
+            final DefaultToApiJsonSerializer<TemplateData> templateDataApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper, final TemplateDomainService templateService,
+            final TemplateMergeService templateMergeService,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.templateDataApiJsonSerializer = templateDataApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.templateService = templateService;
+        this.templateMergeService = templateMergeService;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    public String retrieveAll(@DefaultValue("-1") @QueryParam("typeId") final int typeId,
+            @DefaultValue("-1") @QueryParam("entityId") final int entityId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSION);
+
+        // FIXME - we dont use the ORM when doing fetches - we write SQL and
+        // fetch through JDBC returning data to be serialized to JSON
+        List<Template> templates = new ArrayList<>();
+
+        if (typeId != -1 && entityId != -1) {
+            templates = this.templateService.getAllByEntityAndType(TemplateEntity.values()[entityId], TemplateType.values()[typeId]);
+        } else {
+            templates = this.templateService.getAll();
+        }
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, templates, this.RESPONSE_TEMPLATES_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    public String template(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSION);
+
+        final TemplateData templateData = TemplateData.template();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.templateDataApiJsonSerializer.serialize(settings, templateData, this.RESPONSE_TEMPLATES_DATA_PARAMETERS);
+    }
+
+    @POST
+    public String createTemplate(final String apiRequestBodyAsJson) {
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().createTemplate().withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{templateId}")
+    public String retrieveOne(@PathParam("templateId") final Long templateId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSION);
+
+        final Template template = this.templateService.findOneById(templateId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, template, this.RESPONSE_TEMPLATES_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{templateId}/template")
+    public String getTemplateByTemplate(@PathParam("templateId") final Long templateId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSION);
+
+        final TemplateData template = TemplateData.template(this.templateService.findOneById(templateId));
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.templateDataApiJsonSerializer.serialize(settings, template, this.RESPONSE_TEMPLATE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{templateId}")
+    public String saveTemplate(@PathParam("templateId") final Long templateId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateTemplate(templateId).withJson(apiRequestBodyAsJson).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{templateId}")
+    public String deleteTemplate(@PathParam("templateId") final Long templateId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteTemplate(templateId).build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @POST
+    @Path("{templateId}")
+    @Produces({ MediaType.TEXT_HTML })
+    public String mergeTemplate(@PathParam("templateId") final Long templateId, @Context final UriInfo uriInfo,
+            final String apiRequestBodyAsJson) throws MalformedURLException, IOException {
+
+        final Template template = this.templateService.findOneById(templateId);
+
+        @SuppressWarnings("unchecked")
+        final HashMap<String, Object> result = new ObjectMapper().readValue(apiRequestBodyAsJson, HashMap.class);
+
+        final MultivaluedMap<String, String> parameters = uriInfo.getQueryParameters();
+        final Map<String, Object> parametersMap = new HashMap<>();
+        for (final Map.Entry<String, List<String>> entry : parameters.entrySet()) {
+
+            if (entry.getValue().size() == 1) {
+                parametersMap.put(entry.getKey(), entry.getValue().get(0));
+            } else {
+                parametersMap.put(entry.getKey(), entry.getValue());
+            }
+        }
+
+        parametersMap.put("BASE_URI", uriInfo.getBaseUri());
+        parametersMap.putAll(result);
+        return this.templateMergeService.compile(template, parametersMap);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/data/TemplateData.java b/fineract-provider/src/main/java/org/apache/fineract/template/data/TemplateData.java
new file mode 100644
index 0000000..1225345
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/data/TemplateData.java
@@ -0,0 +1,74 @@
+/**
+ * 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.template.data;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.template.domain.Template;
+import org.apache.fineract.template.domain.TemplateEntity;
+import org.apache.fineract.template.domain.TemplateType;
+
+public class TemplateData {
+
+    @SuppressWarnings("unused")
+    private final List<Map<String, Object>> entities;
+    @SuppressWarnings("unused")
+    private final List<Map<String, Object>> types;
+    @SuppressWarnings("unused")
+    private final Template template;
+
+    private TemplateData(final Template template) {
+        this.template = template;
+        this.entities = getEntites();
+        this.types = getTypes();
+    }
+
+    public static TemplateData template(final Template template) {
+        return new TemplateData(template);
+    }
+
+    public static TemplateData template() {
+        return new TemplateData(null);
+    }
+
+    private List<Map<String, Object>> getEntites() {
+        final List<Map<String, Object>> l = new ArrayList<>();
+        for (final TemplateEntity e : TemplateEntity.values()) {
+            final Map<String, Object> m = new HashMap<>();
+            m.put("id", e.getId());
+            m.put("name", e.getName());
+            l.add(m);
+        }
+        return l;
+    }
+
+    private List<Map<String, Object>> getTypes() {
+        final List<Map<String, Object>> l = new ArrayList<>();
+        for (final TemplateType e : TemplateType.values()) {
+            final Map<String, Object> m = new HashMap<>();
+            m.put("id", e.getId());
+            m.put("name", e.getName());
+            l.add(m);
+        }
+        return l;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/domain/Template.java b/fineract-provider/src/main/java/org/apache/fineract/template/domain/Template.java
new file mode 100644
index 0000000..c5a066b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/domain/Template.java
@@ -0,0 +1,156 @@
+/**
+ * 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.template.domain;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Enumerated;
+import javax.persistence.FetchType;
+import javax.persistence.OneToMany;
+import javax.persistence.OrderBy;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+
+@Entity
+@Table(name = "m_template", uniqueConstraints = {@UniqueConstraint(columnNames = {"name"}, name = "unq_name")})
+public class Template extends AbstractPersistable<Long> {
+
+    @Column(name = "name", nullable = false, unique = true)
+    private String name;
+
+    @Enumerated
+    @JsonSerialize(using = TemplateEntitySerializer.class)
+    private TemplateEntity entity;
+
+    @Enumerated
+    @JsonSerialize(using = TemplateTypeSerializer.class)
+    private TemplateType type;
+
+    @Column(name = "text", columnDefinition = "longtext", nullable = false)
+    private String text;
+
+    @OrderBy(value = "mapperorder")
+    @OneToMany(targetEntity = TemplateMapper.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+    private List<TemplateMapper> mappers;
+
+    public Template(final String name, final String text,
+            final TemplateEntity entity, final TemplateType type,
+            final List<TemplateMapper> mappers) {
+        this.name = StringUtils.defaultIfEmpty(name, null);
+        this.entity = entity;
+        this.type = type;
+        this.text = StringUtils.defaultIfEmpty(text, null);
+        this.mappers = mappers;
+    }
+
+    protected Template() {
+    }
+
+    public static Template fromJson(final JsonCommand command) {
+        final String name = command.stringValueOfParameterNamed("name");
+        final String text = command.stringValueOfParameterNamed("text");
+        final TemplateEntity entity = TemplateEntity.values()[command
+                .integerValueSansLocaleOfParameterNamed("entity")];
+        final int templateTypeId = command
+                .integerValueSansLocaleOfParameterNamed("type");
+        TemplateType type = null;
+        switch (templateTypeId) {
+            case 0 :
+                type = TemplateType.DOCUMENT;
+                break;
+            case 2 :
+                type = TemplateType.SMS;
+                break;
+        }
+
+        final JsonArray array = command.arrayOfParameterNamed("mappers");
+
+        final List<TemplateMapper> mappersList = new ArrayList<>();
+
+        for (final JsonElement element : array) {
+            mappersList.add(new TemplateMapper(element.getAsJsonObject()
+                    .get("mappersorder").getAsInt(), element.getAsJsonObject()
+                    .get("mapperskey").getAsString(), element.getAsJsonObject()
+                    .get("mappersvalue").getAsString()));
+        }
+
+        return new Template(name, text, entity, type, mappersList);
+    }
+
+    public LinkedHashMap<String, String> getMappersAsMap() {
+        final LinkedHashMap<String, String> map = new LinkedHashMap<>();
+        for (final TemplateMapper mapper : getMappers()) {
+            map.put(mapper.getMapperkey(), mapper.getMappervalue());
+        }
+        return map;
+    }
+
+    public List<TemplateMapper> getMappers() {
+        return this.mappers;
+    }
+
+    public void setMappers(final List<TemplateMapper> mappers) {
+        this.mappers = mappers;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public TemplateEntity getEntity() {
+        return this.entity;
+    }
+
+    public void setEntity(final TemplateEntity entity) {
+        this.entity = entity;
+    }
+
+    public TemplateType getType() {
+        return this.type;
+    }
+
+    public void setType(final TemplateType type) {
+        this.type = type;
+    }
+
+    public String getText() {
+        return this.text;
+    }
+
+    public void setText(final String text) {
+        this.text = text;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateEntity.java b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateEntity.java
new file mode 100644
index 0000000..bddf13a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateEntity.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.template.domain;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import com.google.gson.annotations.SerializedName;
+
+@JsonSerialize(using = TemplateEntitySerializer.class)
+public enum TemplateEntity {
+
+    @SerializedName("client")
+    CLIENT(0, "client"), @SerializedName("loan")
+    LOAN(1, "loan");
+
+    private int id;
+    private String name;
+
+    private TemplateEntity(final int id, final String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public int getId() {
+        return this.id;
+    }
+
+    public void setId(final int id) {
+        this.id = id;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateEntitySerializer.java b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateEntitySerializer.java
new file mode 100644
index 0000000..961d733
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateEntitySerializer.java
@@ -0,0 +1,41 @@
+/**
+ * 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.template.domain;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+public class TemplateEntitySerializer extends JsonSerializer<TemplateEntity> {
+
+    @Override
+    public void serialize(final TemplateEntity value, final JsonGenerator generator, @SuppressWarnings("unused") final SerializerProvider provider)
+            throws IOException, JsonProcessingException {
+
+        generator.writeStartObject();
+        generator.writeFieldName("id");
+        generator.writeNumber(value.getId());
+        generator.writeFieldName("name");
+        generator.writeString(value.getName());
+        generator.writeEndObject();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateFunctions.java b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateFunctions.java
new file mode 100644
index 0000000..513c570
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateFunctions.java
@@ -0,0 +1,33 @@
+/**
+ * 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.template.domain;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class TemplateFunctions {
+
+    public static String now() {
+        final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm");
+        final Date date = new Date();
+
+        return dateFormat.format(date);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateMapper.java b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateMapper.java
new file mode 100644
index 0000000..30ad335
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateMapper.java
@@ -0,0 +1,72 @@
+/**
+ * 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.template.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_templatemappers")
+public class TemplateMapper extends AbstractPersistable<Long> {
+
+    @Column(name = "mapperorder")
+    private int mapperorder;
+
+    @Column(name = "mapperkey")
+    private String mapperkey;
+
+    @Column(name = "mappervalue")
+    private String mappervalue;
+
+    protected TemplateMapper() {}
+
+    public TemplateMapper(final int mapperorder, final String mapperkey, final String mappervalue) {
+        this.mapperorder = mapperorder;
+        this.mapperkey = mapperkey;
+        this.mappervalue = mappervalue;
+    }
+
+    public String getMapperkey() {
+        return this.mapperkey;
+    }
+
+    public int getMapperorder() {
+        return this.mapperorder;
+    }
+
+    public void setMapperorder(final int mapperorder) {
+        this.mapperorder = mapperorder;
+    }
+
+    public void setMapperkey(final String mapperkey) {
+        this.mapperkey = mapperkey;
+    }
+
+    public String getMappervalue() {
+        return this.mappervalue;
+    }
+
+    public void setMappervalue(final String mappervalue) {
+        this.mappervalue = mappervalue;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateRepository.java b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateRepository.java
new file mode 100644
index 0000000..15539ee
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateRepository.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.template.domain;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TemplateRepository extends JpaRepository<Template, Long> {
+
+    List<Template> findByEntityAndType(TemplateEntity entity, TemplateType type);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateType.java b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateType.java
new file mode 100644
index 0000000..3a3a157
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateType.java
@@ -0,0 +1,58 @@
+/**
+ * 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.template.domain;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import com.google.gson.annotations.SerializedName;
+
+@JsonSerialize(using = TemplateTypeSerializer.class)
+public enum TemplateType {
+
+    @SerializedName("Document")
+    DOCUMENT(0, "Document"), @SerializedName("SMS")
+    SMS(2, "SMS");
+
+    /**
+     * @SerializedName("E-Mail") EMAIL(1, "E-Mail")
+     */
+    private int id;
+    private String name;
+
+    private TemplateType(final int id, final String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public int getId() {
+        return this.id;
+    }
+
+    public void setId(final int id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateTypeSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateTypeSerializer.java
new file mode 100644
index 0000000..5b6590d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/domain/TemplateTypeSerializer.java
@@ -0,0 +1,41 @@
+/**
+ * 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.template.domain;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+public class TemplateTypeSerializer extends JsonSerializer<TemplateType> {
+
+    @Override
+    public void serialize(final TemplateType value, final JsonGenerator generator, @SuppressWarnings("unused") final SerializerProvider provider)
+            throws IOException, JsonProcessingException {
+
+        generator.writeStartObject();
+        generator.writeFieldName("id");
+        generator.writeNumber(value.getId());
+        generator.writeFieldName("name");
+        generator.writeString(value.getName());
+        generator.writeEndObject();
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/exception/TemplateNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/template/exception/TemplateNotFoundException.java
new file mode 100644
index 0000000..fd742f9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/exception/TemplateNotFoundException.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.template.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class TemplateNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public TemplateNotFoundException(final Long id) {
+        super("error.msg.template.id.invalid", "Template with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/handler/CreateTemplateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/template/handler/CreateTemplateCommandHandler.java
new file mode 100644
index 0000000..46f841f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/handler/CreateTemplateCommandHandler.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.template.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.template.service.TemplateDomainService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "TEMPLATE", action = "CREATE")
+public class CreateTemplateCommandHandler implements NewCommandSourceHandler {
+
+    private final TemplateDomainService templateService;
+
+    @Autowired
+    public CreateTemplateCommandHandler(final TemplateDomainService templateService) {
+
+        this.templateService = templateService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.templateService.createTemplate(command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/handler/DeleteTemplateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/template/handler/DeleteTemplateCommandHandler.java
new file mode 100644
index 0000000..27800ea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/handler/DeleteTemplateCommandHandler.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.template.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.template.service.TemplateDomainService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "TEMPLATE", action = "DELETE")
+public class DeleteTemplateCommandHandler implements NewCommandSourceHandler {
+
+    private final TemplateDomainService templateService;
+
+    @Autowired
+    public DeleteTemplateCommandHandler(final TemplateDomainService templateService) {
+
+        this.templateService = templateService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.templateService.removeTemplate(command.entityId());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/handler/UpdateTemplateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/template/handler/UpdateTemplateCommandHandler.java
new file mode 100644
index 0000000..cd6e522
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/handler/UpdateTemplateCommandHandler.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.template.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.template.service.TemplateDomainService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "TEMPLATE", action = "UPDATE")
+public class UpdateTemplateCommandHandler implements NewCommandSourceHandler {
+
+    private final TemplateDomainService templateService;
+
+    @Autowired
+    public UpdateTemplateCommandHandler(final TemplateDomainService templateService) {
+
+        this.templateService = templateService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.templateService.updateTemplate(command.entityId(), command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java
new file mode 100644
index 0000000..8b7548e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java
@@ -0,0 +1,147 @@
+/**
+ * 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.template.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.template.domain.Template;
+import org.apache.fineract.template.domain.TemplateEntity;
+import org.apache.fineract.template.domain.TemplateMapper;
+import org.apache.fineract.template.domain.TemplateRepository;
+import org.apache.fineract.template.domain.TemplateType;
+import org.apache.fineract.template.exception.TemplateNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+
+@Service
+public class JpaTemplateDomainService implements TemplateDomainService {
+
+    private static final String PROPERTY_NAME = "name";
+    private static final String PROPERTY_TEXT = "text";
+    // private static final String PROPERTY_MAPPERS = "mappers";
+    private static final String PROPERTY_ENTITY = "entity";
+    private static final String PROPERTY_TYPE = "type";
+
+    @Autowired
+    private TemplateRepository templateRepository;
+
+    @Override
+    public List<Template> getAll() {
+        return this.templateRepository.findAll();
+    }
+
+    @Override
+    public Template findOneById(final Long id) {
+        final Template template = this.templateRepository.findOne(id);
+        if (template == null) {
+            throw new TemplateNotFoundException(id);
+        }
+        return template;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createTemplate(final JsonCommand command) {
+        // FIXME - no validation here of the data in the command object, is
+        // name, text populated etc
+        // FIXME - handle cases where data integrity constraints are fired from
+        // database when saving.
+        final Template template = Template.fromJson(command);
+
+        this.templateRepository.saveAndFlush(template);
+        return new CommandProcessingResultBuilder().withEntityId(
+                template.getId()).build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateTemplate(final Long templateId,
+            final JsonCommand command) {
+        // FIXME - no validation here of the data in the command object, is
+        // name, text populated etc
+        // FIXME - handle cases where data integrity constraints are fired from
+        // database when saving.
+
+        final Template template = findOneById(templateId);
+        template.setName(command.stringValueOfParameterNamed(PROPERTY_NAME));
+        template.setText(command.stringValueOfParameterNamed(PROPERTY_TEXT));
+        template.setEntity(TemplateEntity.values()[command
+                .integerValueSansLocaleOfParameterNamed(PROPERTY_ENTITY)]);
+        final int templateTypeId = command
+                .integerValueSansLocaleOfParameterNamed(PROPERTY_TYPE);
+        TemplateType type = null;
+        switch (templateTypeId) {
+            case 0 :
+                type = TemplateType.DOCUMENT;
+                break;
+            case 2 :
+                type = TemplateType.SMS;
+                break;
+        }
+        template.setType(type);
+
+        final JsonArray array = command.arrayOfParameterNamed("mappers");
+        final List<TemplateMapper> mappersList = new ArrayList<>();
+        for (final JsonElement element : array) {
+            mappersList.add(new TemplateMapper(element.getAsJsonObject()
+                    .get("mappersorder").getAsInt(), element.getAsJsonObject()
+                    .get("mapperskey").getAsString(), element.getAsJsonObject()
+                    .get("mappersvalue").getAsString()));
+        }
+        template.setMappers(mappersList);
+
+        this.templateRepository.saveAndFlush(template);
+
+        return new CommandProcessingResultBuilder()
+                .withCommandId(command.commandId())
+                .withEntityId(template.getId()).build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult removeTemplate(final Long templateId) {
+        final Template template = findOneById(templateId);
+
+        this.templateRepository.delete(template);
+
+        return new CommandProcessingResultBuilder().withEntityId(templateId)
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public Template updateTemplate(final Template template) {
+        return this.templateRepository.saveAndFlush(template);
+    }
+
+    @Override
+    public List<Template> getAllByEntityAndType(final TemplateEntity entity,
+            final TemplateType type) {
+
+        return this.templateRepository.findByEntityAndType(entity, type);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateDomainService.java
new file mode 100644
index 0000000..6599501
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateDomainService.java
@@ -0,0 +1,44 @@
+/**
+ * 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.template.service;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.template.domain.Template;
+import org.apache.fineract.template.domain.TemplateEntity;
+import org.apache.fineract.template.domain.TemplateType;
+
+public interface TemplateDomainService {
+
+    List<Template> getAll();
+
+    List<Template> getAllByEntityAndType(TemplateEntity entity, TemplateType type);
+
+    Template findOneById(Long id);
+
+    Template updateTemplate(Template template);
+
+    CommandProcessingResult createTemplate(final JsonCommand command);
+
+    CommandProcessingResult updateTemplate(final Long templateId, final JsonCommand command);
+
+    CommandProcessingResult removeTemplate(final Long templateId);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java b/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java
new file mode 100644
index 0000000..a661333
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java
@@ -0,0 +1,213 @@
+/**
+ * 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.template.service;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.Authenticator;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.PasswordAuthentication;
+import java.net.URL;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.template.domain.Template;
+import org.apache.fineract.template.domain.TemplateFunctions;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+
+@Service
+public class TemplateMergeService {
+	private final static Logger logger = LoggerFactory.getLogger(TemplateMergeService.class);
+	
+
+    // private final FromJsonHelper fromApiJsonHelper;
+    private Map<String, Object> scopes;
+    private String authToken;
+
+    // @Autowired
+    // public TemplateMergeService(final FromJsonHelper fromApiJsonHelper) {
+    // this.fromApiJsonHelper = fromApiJsonHelper;
+    //
+
+    public void setAuthToken(final String authToken) {
+        //final String auth = ThreadLocalContextUtil.getAuthToken();
+    	this.authToken =  authToken;
+    }
+    
+
+    public String compile(final Template template, final Map<String, Object> scopes) throws MalformedURLException, IOException {
+        this.scopes = scopes;
+        this.scopes.put("static", new TemplateFunctions());
+        
+        final MustacheFactory mf = new DefaultMustacheFactory();
+        final Mustache mustache = mf.compile(new StringReader(template.getText()), template.getName());
+
+        final Map<String, Object> mappers = getCompiledMapFromMappers(template.getMappersAsMap());
+        this.scopes.putAll(mappers);
+
+        expandMapArrays(scopes);
+
+        final StringWriter stringWriter = new StringWriter();
+        mustache.execute(stringWriter, this.scopes);
+
+        return stringWriter.toString();
+    }
+
+	private Map<String, Object> getCompiledMapFromMappers(final Map<String, String> data) {
+        final MustacheFactory mf = new DefaultMustacheFactory();
+
+        if (data != null) {
+            for (final Map.Entry<String, String> entry : data.entrySet()) {
+                final Mustache mappersMustache = mf.compile(new StringReader(entry.getValue()), "");
+                final StringWriter stringWriter = new StringWriter();
+
+                mappersMustache.execute(stringWriter, this.scopes);
+                String url = stringWriter.toString();
+                if (!url.startsWith("http")) {
+                    url = this.scopes.get("BASE_URI") + url;
+                }
+                try {
+                    this.scopes.put(entry.getKey(), getMapFromUrl(url));
+                } catch (final IOException e) {
+                	logger.error("getCompiledMapFromMappers() failed", e);
+                }
+            }
+        }
+        return this.scopes;
+    }
+
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> getMapFromUrl(final String url) throws MalformedURLException, IOException {
+        final HttpURLConnection connection = getConnection(url);
+
+        final String response = getStringFromInputStream(connection.getInputStream());
+        HashMap<String, Object> result = new HashMap<>();
+        if (connection.getContentType().equals("text/plain")) {
+            result.put("src", response);
+        } else {
+            result = new ObjectMapper().readValue(response, HashMap.class);
+        }
+        return result;
+    }
+
+    private HttpURLConnection getConnection(final String url) {
+        if (this.authToken == null) {
+            final String name = SecurityContextHolder.getContext().getAuthentication().getName();
+            final String password = SecurityContextHolder.getContext().getAuthentication().getCredentials().toString();
+
+            Authenticator.setDefault(new Authenticator() {
+                @Override
+                protected PasswordAuthentication getPasswordAuthentication() {
+                    return new PasswordAuthentication(name, password.toCharArray());
+                }
+            });
+        }
+
+        HttpURLConnection connection = null;
+        try {
+            connection = (HttpURLConnection) new URL(url).openConnection();
+            if (this.authToken != null) {
+                connection.setRequestProperty("Authorization", "Basic " + this.authToken);
+            }
+            TrustModifier.relaxHostChecking(connection);
+
+            connection.setDoInput(true);
+
+        } catch (IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
+        	logger.error("getConnection() failed, return null", e);
+        }
+
+        return connection;
+    }
+
+    // TODO Replace this with appropriate alternative available in Guava
+    private static String getStringFromInputStream(final InputStream is) {
+        BufferedReader br = null;
+        final StringBuilder sb = new StringBuilder();
+
+        String line;
+        try {
+
+            br = new BufferedReader(new InputStreamReader(is));
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+            }
+
+        } catch (final IOException e) {
+        	logger.error("getStringFromInputStream() failed", e);
+        } finally {
+            if (br != null) {
+                try {
+                    br.close();
+                } catch (final IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        return sb.toString();
+    }
+    
+	@SuppressWarnings("unchecked")
+	private void expandMapArrays(Object value) {
+		if (value instanceof Map) {
+			Map<String, Object> valueAsMap = (Map<String, Object>) value;
+			//Map<String, Object> newValue = null;
+			Map<String,Object> valueAsMap_second = new HashMap<>();
+			for (Entry<String, Object> valueAsMapEntry : valueAsMap.entrySet()) {
+				Object valueAsMapEntryValue = valueAsMapEntry.getValue();
+				if (valueAsMapEntryValue instanceof Map) { // JSON Object
+					expandMapArrays(valueAsMapEntryValue);
+				} else if (valueAsMapEntryValue instanceof Iterable) { // JSON Array
+					Iterable<Object> valueAsMapEntryValueIterable = (Iterable<Object>) valueAsMapEntryValue;
+					String valueAsMapEntryKey = valueAsMapEntry.getKey();
+					int i = 0;
+					for (Object object : valueAsMapEntryValueIterable) {
+						valueAsMap_second.put(valueAsMapEntryKey + "#" + i, object);
+						++i;
+						expandMapArrays(object);
+						
+					}
+				}
+
+			}
+			valueAsMap.putAll(valueAsMap_second);
+
+		}		
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/service/TrustModifier.java b/fineract-provider/src/main/java/org/apache/fineract/template/service/TrustModifier.java
new file mode 100644
index 0000000..0ed1527
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/template/service/TrustModifier.java
@@ -0,0 +1,88 @@
+/**
+ * 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.template.service;
+
+import java.net.HttpURLConnection;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+@SuppressWarnings("unused")
+public class TrustModifier {
+
+    private static final TrustingHostnameVerifier TRUSTING_HOSTNAME_VERIFIER = new TrustingHostnameVerifier();
+    private static SSLSocketFactory factory;
+
+    /**
+     * Call this with any HttpURLConnection, and it will modify the trust
+     * settings if it is an HTTPS connection.
+     */
+    public static void relaxHostChecking(final HttpURLConnection conn) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
+
+        if (conn instanceof HttpsURLConnection) {
+            final HttpsURLConnection httpsConnection = (HttpsURLConnection) conn;
+            final SSLSocketFactory factory = prepFactory(httpsConnection);
+            httpsConnection.setSSLSocketFactory(factory);
+            httpsConnection.setHostnameVerifier(TRUSTING_HOSTNAME_VERIFIER);
+        }
+    }
+
+    static synchronized SSLSocketFactory prepFactory(final HttpsURLConnection httpsConnection) throws NoSuchAlgorithmException,
+            KeyStoreException, KeyManagementException {
+
+        if (factory == null) {
+            final SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(null, new TrustManager[] { new AlwaysTrustManager() }, null);
+            factory = ctx.getSocketFactory();
+        }
+        return factory;
+    }
+
+    private static final class TrustingHostnameVerifier implements HostnameVerifier {
+
+        @Override
+        public boolean verify(final String hostname, final SSLSession session) {
+            return true;
+        }
+    }
+
+    private static class AlwaysTrustManager implements X509TrustManager {
+
+        @Override
+        public void checkClientTrusted(final X509Certificate[] arg0, final String arg1) throws CertificateException {}
+
+        @Override
+        public void checkServerTrusted(final X509Certificate[] arg0, final String arg1) throws CertificateException {}
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/AppUserApiConstant.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/AppUserApiConstant.java
new file mode 100644
index 0000000..f1f05b7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/AppUserApiConstant.java
@@ -0,0 +1,25 @@
+/**
+ * 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.useradministration.api;
+
+public class AppUserApiConstant {
+
+    public static final int numberOfPreviousPasswords = 3;
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/PasswordPreferencesApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/PasswordPreferencesApiConstants.java
new file mode 100644
index 0000000..97c80fd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/PasswordPreferencesApiConstants.java
@@ -0,0 +1,44 @@
+/**
+ * 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.useradministration.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class PasswordPreferencesApiConstants {
+
+    public static final String RESOURCE_NAME = "passwordpreferences";
+    public static final String ENTITY_NAME = "PASSWORD_PREFERENCES";
+
+    // response parameters
+    public static final String DESCRIPTION = "description";
+
+    public static final String ACTIVE = "active";
+
+    public static final String ID_PARAM_NAME = "id";
+
+    // request parameters
+    public static final String VALIDATION_POLICY_ID = "validationPolicyId";
+
+    public static final Set<String> REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(VALIDATION_POLICY_ID));
+
+    public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(ID_PARAM_NAME, ACTIVE, DESCRIPTION));
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/PasswordPreferencesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/PasswordPreferencesApiResource.java
new file mode 100644
index 0000000..75a173e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/PasswordPreferencesApiResource.java
@@ -0,0 +1,113 @@
+/**
+ * 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.useradministration.api;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.data.PasswordValidationPolicyData;
+import org.apache.fineract.useradministration.service.PasswordValidationPolicyReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/" + PasswordPreferencesApiConstants.RESOURCE_NAME)
+@Component
+@Scope("singleton")
+public class PasswordPreferencesApiResource {
+
+    private final PlatformSecurityContext context;
+    private final PasswordValidationPolicyReadPlatformService passwordValidationPolicyReadPlatformService;
+    private final DefaultToApiJsonSerializer<PasswordValidationPolicyData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public PasswordPreferencesApiResource(final PlatformSecurityContext context,
+            final PasswordValidationPolicyReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<PasswordValidationPolicyData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.passwordValidationPolicyReadPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieve(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(PasswordPreferencesApiConstants.ENTITY_NAME);
+
+        final PasswordValidationPolicyData passwordValidationPolicyData = this.passwordValidationPolicyReadPlatformService
+                .retrieveActiveValidationPolicy();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, passwordValidationPolicyData,
+                PasswordPreferencesApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updatePasswordPreferences() //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("/template")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String template(@Context final UriInfo uriInfo) {
+        this.context.authenticatedUser().validateHasReadPermission(PasswordPreferencesApiConstants.ENTITY_NAME);
+
+        final Collection<PasswordValidationPolicyData> validationPolicies = this.passwordValidationPolicyReadPlatformService.retrieveAll();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, validationPolicies, PasswordPreferencesApiConstants.RESPONSE_DATA_PARAMETERS);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/PermissionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/PermissionsApiResource.java
new file mode 100644
index 0000000..d430caf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/PermissionsApiResource.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.useradministration.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;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.data.PermissionData;
+import org.apache.fineract.useradministration.service.PermissionReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/permissions")
+@Component
+@Scope("singleton")
+public class PermissionsApiResource {
+
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("grouping", "code", "entityName", "actionName",
+            "selected", "isMakerChecker"));
+    private final String resourceNameForPermissions = "PERMISSION";
+
+    private final PlatformSecurityContext context;
+    private final PermissionReadPlatformService permissionReadPlatformService;
+    private final DefaultToApiJsonSerializer<PermissionData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public PermissionsApiResource(final PlatformSecurityContext context, final PermissionReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<PermissionData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.permissionReadPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllPermissions(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        Collection<PermissionData> permissions = null;
+        if (settings.isMakerCheckerable()) {
+            permissions = this.permissionReadPlatformService.retrieveAllMakerCheckerablePermissions();
+        } else {
+            permissions = this.permissionReadPlatformService.retrieveAllPermissions();
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, permissions, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updatePermissionsDetails(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updatePermissions() //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/RolesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/RolesApiResource.java
new file mode 100644
index 0000000..4da979d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/RolesApiResource.java
@@ -0,0 +1,245 @@
+/**
+ * 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.useradministration.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.QueryParam;
+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.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.apache.fineract.useradministration.data.PermissionData;
+import org.apache.fineract.useradministration.data.RoleData;
+import org.apache.fineract.useradministration.data.RolePermissionsData;
+import org.apache.fineract.useradministration.service.PermissionReadPlatformService;
+import org.apache.fineract.useradministration.service.RoleReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/roles")
+@Component
+@Scope("singleton")
+public class RolesApiResource {
+
+    /**
+     * The set of parameters that are supported in response for {@link RoleData}
+     */
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "description", "availablePermissions",
+            "selectedPermissions"));
+
+    /**
+     * The set of parameters that are supported in response for {@link RoleData}
+     */
+    private final Set<String> PERMISSIONS_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "description",
+            "permissionUsageData"));
+
+    private final String resourceNameForPermissions = "ROLE";
+
+    private final PlatformSecurityContext context;
+    private final RoleReadPlatformService roleReadPlatformService;
+    private final PermissionReadPlatformService permissionReadPlatformService;
+    private final DefaultToApiJsonSerializer<RoleData> toApiJsonSerializer;
+    private final DefaultToApiJsonSerializer<RolePermissionsData> permissionsToApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public RolesApiResource(final PlatformSecurityContext context, final RoleReadPlatformService readPlatformService,
+            final PermissionReadPlatformService permissionReadPlatformService,
+            final DefaultToApiJsonSerializer<RoleData> toApiJsonSerializer,
+            final DefaultToApiJsonSerializer<RolePermissionsData> permissionsToApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.roleReadPlatformService = readPlatformService;
+        this.permissionReadPlatformService = permissionReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.permissionsToApiJsonSerializer = permissionsToApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllRoles(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<RoleData> roles = this.roleReadPlatformService.retrieveAll();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, roles, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createRole(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createRole() //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{roleId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveRole(@PathParam("roleId") final Long roleId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        final RoleData role = this.roleReadPlatformService.retrieveOne(roleId);
+
+        return this.toApiJsonSerializer.serialize(settings, role, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    /**
+     * Roles enable or disable
+     * 
+     * @param roleId
+     * @param commandParam
+     * @param apiRequestBodyAsJson
+     * @return
+     */
+    @POST
+    @Path("{roleId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String actionsOnRoles(@PathParam("roleId") final Long roleId, @QueryParam("command") final String commandParam,
+            final String apiRequestBodyAsJson) {
+
+        final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+        CommandProcessingResult result = null;
+
+        if (is(commandParam, "disable")) {
+            final CommandWrapper commandRequest = builder.disableRole(roleId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        } else if (is(commandParam, "enable")) {
+            final CommandWrapper commandRequest = builder.enableRole(roleId).build();
+            result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        }
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{roleId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateRole(@PathParam("roleId") final Long roleId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateRole(roleId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{roleId}/permissions")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveRolePermissions(@PathParam("roleId") final Long roleId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        final RoleData role = this.roleReadPlatformService.retrieveOne(roleId);
+        final Collection<PermissionData> permissionUsageData = this.permissionReadPlatformService.retrieveAllRolePermissions(roleId);
+        final RolePermissionsData permissionsData = role.toRolePermissionData(permissionUsageData);
+        return this.permissionsToApiJsonSerializer.serialize(settings, permissionsData, this.PERMISSIONS_RESPONSE_DATA_PARAMETERS);
+    }
+
+    @PUT
+    @Path("{roleId}/permissions")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateRolePermissions(@PathParam("roleId") final Long roleId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateRolePermissions(roleId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    /**
+     * Delete Role
+     * 
+     * @param roleId
+     * @return
+     */
+    @DELETE
+    @Path("{roleId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteRole(@PathParam("roleId") final Long roleId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteRole(roleId) //
+                .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/useradministration/api/UsersApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResource.java
new file mode 100755
index 0000000..481284a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResource.java
@@ -0,0 +1,169 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.useradministration.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.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.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.useradministration.data.AppUserData;
+import org.apache.fineract.useradministration.service.AppUserReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/users")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+public class UsersApiResource {
+
+    /**
+     * The set of parameters that are supported in response for
+     * {@link AppUserData}.
+     */
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "officeId", "officeName", "username",
+            "firstname", "lastname", "email", "allowedOffices", "availableRoles", "selectedRoles", "staff"));
+
+    private final String resourceNameForPermissions = "USER";
+
+    private final PlatformSecurityContext context;
+    private final AppUserReadPlatformService readPlatformService;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final DefaultToApiJsonSerializer<AppUserData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public UsersApiResource(final PlatformSecurityContext context, final AppUserReadPlatformService readPlatformService,
+            final OfficeReadPlatformService officeReadPlatformService, final DefaultToApiJsonSerializer<AppUserData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.readPlatformService = readPlatformService;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final Collection<AppUserData> users = this.readPlatformService.retrieveAllUsers();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, users, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("{userId}")
+    public String retrieveOne(@PathParam("userId") final Long userId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions, userId);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        AppUserData user = this.readPlatformService.retrieveUser(userId);
+        if (settings.isTemplate()) {
+            final Collection<OfficeData> offices = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+            user = AppUserData.template(user, offices);
+        }
+
+        return this.toApiJsonSerializer.serialize(settings, user, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @GET
+    @Path("template")
+    public String template(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final AppUserData user = this.readPlatformService.retrieveNewUserDetails();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, user, this.RESPONSE_DATA_PARAMETERS);
+    }
+
+    @POST
+    public String create(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createUser() //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @PUT
+    @Path("{userId}")
+    public String update(@PathParam("userId") final Long userId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateUser(userId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @DELETE
+    @Path("{userId}")
+    public String delete(@PathParam("userId") final Long userId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteUser(userId) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/command/PermissionsCommand.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/command/PermissionsCommand.java
new file mode 100644
index 0000000..55b59c5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/command/PermissionsCommand.java
@@ -0,0 +1,37 @@
+/**
+ * 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.useradministration.command;
+
+import java.util.Map;
+
+/**
+ * Immutable command for updating permissions (initially maker-checker).
+ */
+public class PermissionsCommand {
+
+    private final Map<String, Boolean> permissions;
+
+    public PermissionsCommand(final Map<String, Boolean> permissionsMap) {
+        this.permissions = permissionsMap;
+    }
+
+    public Map<String, Boolean> getPermissions() {
+        return this.permissions;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/AppUserData.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/AppUserData.java
new file mode 100755
index 0000000..075f4b0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/AppUserData.java
@@ -0,0 +1,124 @@
+/**
+ * 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.useradministration.data;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.portfolio.client.data.ClientData;
+
+/**
+ * Immutable data object for application user data.
+ */
+public class AppUserData {
+
+    private final Long id;
+    private final String username;
+    private final Long officeId;
+    private final String officeName;
+    private final String firstname;
+    private final String lastname;
+    private final String email;
+    private final Boolean passwordNeverExpires;
+
+    @SuppressWarnings("unused")
+    private final Collection<OfficeData> allowedOffices;
+    private final Collection<RoleData> availableRoles;
+    private final Collection<RoleData> selectedRoles;
+    private final StaffData staff;
+    private final Boolean isSelfServiceUser;
+    
+	@SuppressWarnings("unused")
+    private Set<ClientData> clients;
+
+    public static AppUserData template(final AppUserData user, final Collection<OfficeData> officesForDropdown) {
+        return new AppUserData(user.id, user.username, user.email, user.officeId, user.officeName, user.firstname, user.lastname,
+                user.availableRoles, user.selectedRoles, officesForDropdown, user.staff, user.passwordNeverExpires, user.isSelfServiceUser);
+    }
+
+    public static AppUserData template(final Collection<OfficeData> offices, final Collection<RoleData> availableRoles) {
+        return new AppUserData(null, null, null, null, null, null, null, availableRoles, null, offices, null, null, null);
+    }
+
+    public static AppUserData dropdown(final Long id, final String username) {
+        return new AppUserData(id, username, null, null, null, null, null, null, null, null, null, null, null);
+    }
+
+    public static AppUserData instance(final Long id, final String username, final String email, final Long officeId,
+            final String officeName, final String firstname, final String lastname, final Collection<RoleData> availableRoles,
+            final Collection<RoleData> selectedRoles, final StaffData staff, final Boolean passwordNeverExpire, final Boolean isSelfServiceUser) {
+        return new AppUserData(id, username, email, officeId, officeName, firstname, lastname, availableRoles, selectedRoles, null, staff,
+                passwordNeverExpire, isSelfServiceUser);
+    }
+
+    private AppUserData(final Long id, final String username, final String email, final Long officeId, final String officeName,
+            final String firstname, final String lastname, final Collection<RoleData> availableRoles,
+            final Collection<RoleData> selectedRoles, final Collection<OfficeData> allowedOffices, final StaffData staff,
+            final Boolean passwordNeverExpire, final Boolean isSelfServiceUser) {
+        this.id = id;
+        this.username = username;
+        this.officeId = officeId;
+        this.officeName = officeName;
+        this.firstname = firstname;
+        this.lastname = lastname;
+        this.email = email;
+        this.allowedOffices = allowedOffices;
+        this.availableRoles = availableRoles;
+        this.selectedRoles = selectedRoles;
+        this.staff = staff;
+        this.passwordNeverExpires = passwordNeverExpire;
+        this.isSelfServiceUser = isSelfServiceUser;
+    }
+
+    public boolean hasIdentifyOf(final Long createdById) {
+        return this.id.equals(createdById);
+    }
+
+    public String username() {
+        return this.username;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        AppUserData that = (AppUserData) o;
+
+        if (id != null ? !id.equals(that.id) : that.id != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id != null ? id.hashCode() : 0;
+    }
+    
+    public void setClients(Set<ClientData> clients){
+    	this.clients = clients;
+    }
+    
+    public boolean isSelfServiceUser() {
+		return this.isSelfServiceUser==null?false:this.isSelfServiceUser;
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/PasswordPreferencesDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/PasswordPreferencesDataValidator.java
new file mode 100644
index 0000000..0a893c3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/PasswordPreferencesDataValidator.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.useradministration.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.useradministration.api.PasswordPreferencesApiConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class PasswordPreferencesDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public PasswordPreferencesDataValidator(FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    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, PasswordPreferencesApiConstants.REQUEST_DATA_PARAMETERS);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+                .resource(PasswordPreferencesApiConstants.RESOURCE_NAME);
+
+        final Long repaymentRescheduleType = this.fromApiJsonHelper.extractLongNamed(PasswordPreferencesApiConstants.VALIDATION_POLICY_ID,
+                element);
+        baseDataValidator.reset().parameter(PasswordPreferencesApiConstants.VALIDATION_POLICY_ID).value(repaymentRescheduleType).notNull()
+                .integerGreaterThanZero();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            //
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/PasswordValidationPolicyData.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/PasswordValidationPolicyData.java
new file mode 100644
index 0000000..423b518
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/PasswordValidationPolicyData.java
@@ -0,0 +1,44 @@
+/**
+ * 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.useradministration.data;
+
+import java.io.Serializable;
+
+/**
+ * Immutable data object for role data.
+ */
+public class PasswordValidationPolicyData implements Serializable {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String description;
+    @SuppressWarnings("unused")
+    private final boolean active;
+    @SuppressWarnings("unused")
+    private final String key;
+
+    public PasswordValidationPolicyData(final Long id, final Boolean active, final String description, final String key) {
+        this.id = id;
+        this.active = active;
+        this.description = description;
+        this.key = key;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/PermissionData.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/PermissionData.java
new file mode 100644
index 0000000..eaaf6a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/PermissionData.java
@@ -0,0 +1,54 @@
+/**
+ * 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.useradministration.data;
+
+/**
+ * Immutable representation of permissions
+ */
+public class PermissionData {
+
+    @SuppressWarnings("unused")
+    private final String grouping;
+    @SuppressWarnings("unused")
+    private final String code;
+    @SuppressWarnings("unused")
+    private final String entityName;
+    @SuppressWarnings("unused")
+    private final String actionName;
+    @SuppressWarnings("unused")
+    private final Boolean selected;
+
+    public static PermissionData from(final String permissionCode, final boolean isSelected) {
+        return new PermissionData(null, permissionCode, null, null, isSelected);
+    }
+
+    public static PermissionData instance(final String grouping, final String code, final String entityName, final String actionName,
+            final Boolean selected) {
+        return new PermissionData(grouping, code, entityName, actionName, selected);
+    }
+
+    private PermissionData(final String grouping, final String code, final String entityName, final String actionName,
+            final Boolean selected) {
+        this.grouping = grouping;
+        this.code = code;
+        this.entityName = entityName;
+        this.actionName = actionName;
+        this.selected = selected;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/RoleData.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/RoleData.java
new file mode 100644
index 0000000..fc5bebc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/RoleData.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.useradministration.data;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * Immutable data object for role data.
+ */
+public class RoleData implements Serializable {
+
+    private final Long id;
+    private final String name;
+    private final String description;
+    private final Boolean disabled;
+
+    public RolePermissionsData toRolePermissionData(final Collection<PermissionData> permissionUsageData) {
+        return new RolePermissionsData(this.id, this.name, this.description, this.disabled, permissionUsageData);
+    }
+
+    public RoleData(final Long id, final String name, final String description, final Boolean disabled) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.disabled = disabled;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        final RoleData role = (RoleData) obj;
+        return this.id.equals(role.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return this.id.hashCode();
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/RolePermissionsData.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/RolePermissionsData.java
new file mode 100644
index 0000000..843a37c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/data/RolePermissionsData.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.useradministration.data;
+
+import java.util.Collection;
+
+/**
+ * Immutable data object representing a role with associated permissions.
+ */
+public class RolePermissionsData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String name;
+    @SuppressWarnings("unused")
+    private final String description;
+    @SuppressWarnings("unused")
+    private final Boolean disabled;
+
+    @SuppressWarnings("unused")
+    private final Collection<PermissionData> permissionUsageData;
+
+    public RolePermissionsData(final Long id, final String name, final String description, final Boolean disabled,
+            final Collection<PermissionData> permissionUsageData) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.disabled = disabled;
+        this.permissionUsageData = permissionUsageData;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
new file mode 100644
index 0000000..b68f794
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
@@ -0,0 +1,654 @@
+/**
+ * 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.useradministration.domain;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.domain.PlatformUser;
+import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
+import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder;
+import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.useradministration.service.AppUserConstants;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+
+@Entity
+@Table(name = "m_appuser", uniqueConstraints = @UniqueConstraint(columnNames = { "username" }, name = "username_org"))
+public class AppUser extends AbstractPersistable<Long> implements PlatformUser {
+
+    private final static Logger logger = LoggerFactory.getLogger(AppUser.class);
+
+    @Column(name = "email", nullable = false, length = 100)
+    private String email;
+
+    @Column(name = "username", nullable = false, length = 100)
+    private String username;
+
+    @Column(name = "firstname", nullable = false, length = 100)
+    private String firstname;
+
+    @Column(name = "lastname", nullable = false, length = 100)
+    private String lastname;
+
+    @Column(name = "password", nullable = false)
+    private String password;
+
+    @Column(name = "nonexpired", nullable = false)
+    private boolean accountNonExpired;
+
+    @Column(name = "nonlocked", nullable = false)
+    private final boolean accountNonLocked;
+
+    @Column(name = "nonexpired_credentials", nullable = false)
+    private final boolean credentialsNonExpired;
+
+    @Column(name = "enabled", nullable = false)
+    private boolean enabled;
+
+    @Column(name = "firsttime_login_remaining", nullable = false)
+    private boolean firstTimeLoginRemaining;
+
+    @Column(name = "is_deleted", nullable = false)
+    private boolean deleted;
+
+    @ManyToOne
+    @JoinColumn(name = "office_id", nullable = false)
+    private Office office;
+
+    @ManyToOne
+    @JoinColumn(name = "staff_id", nullable = true)
+    private Staff staff;
+
+    @ManyToMany(fetch = FetchType.EAGER)
+    @JoinTable(name = "m_appuser_role", joinColumns = @JoinColumn(name = "appuser_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
+    private Set<Role> roles;
+
+    @Column(name = "last_time_password_updated")
+    @Temporal(TemporalType.DATE)
+    private Date lastTimePasswordUpdated;
+
+    @Column(name = "password_never_expires", nullable = false)
+    private boolean passwordNeverExpires;
+
+    @Column(name = "is_self_service_user", nullable = false)
+	private boolean isSelfServiceUser;
+    
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @OneToMany(cascade = CascadeType.ALL,  orphanRemoval = true)
+    @JoinColumn(name = "appuser_id", referencedColumnName= "id", nullable = false)
+    private Set<AppUserClientMapping> appUserClientMappings = new HashSet<>();
+
+	public static AppUser fromJson(final Office userOffice, final Staff linkedStaff, final Set<Role> allRoles, 
+			final Collection<Client> clients, final JsonCommand command) {
+
+        final String username = command.stringValueOfParameterNamed("username");
+        String password = command.stringValueOfParameterNamed("password");
+        final Boolean sendPasswordToEmail = command.booleanObjectValueOfParameterNamed("sendPasswordToEmail");
+
+        if (sendPasswordToEmail.booleanValue()) {
+            password = new RandomPasswordGenerator(13).generate();
+        }
+
+        boolean passwordNeverExpire = false;
+
+        if (command.parameterExists(AppUserConstants.PASSWORD_NEVER_EXPIRES)) {
+            passwordNeverExpire = command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.PASSWORD_NEVER_EXPIRES);
+        }
+
+        final boolean userEnabled = true;
+        final boolean userAccountNonExpired = true;
+        final boolean userCredentialsNonExpired = true;
+        final boolean userAccountNonLocked = true;
+
+        final Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
+        authorities.add(new SimpleGrantedAuthority("DUMMY_ROLE_NOT_USED_OR_PERSISTED_TO_AVOID_EXCEPTION"));
+
+        final User user = new User(username, password, userEnabled, userAccountNonExpired, userCredentialsNonExpired, userAccountNonLocked,
+                authorities);
+
+        final String email = command.stringValueOfParameterNamed("email");
+        final String firstname = command.stringValueOfParameterNamed("firstname");
+        final String lastname = command.stringValueOfParameterNamed("lastname");
+        
+        final boolean isSelfServiceUser = command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER);
+
+        return new AppUser(userOffice, user, allRoles, email, firstname, lastname, linkedStaff, passwordNeverExpire,
+        		isSelfServiceUser, clients);
+    }
+
+    protected AppUser() {
+        this.accountNonLocked = false;
+        this.credentialsNonExpired = false;
+        this.roles = new HashSet<>();
+    }
+
+    public AppUser(final Office office, final User user, final Set<Role> roles, final String email, final String firstname,
+            final String lastname, final Staff staff, final boolean passwordNeverExpire, 
+            final boolean isSelfServiceUser, final Collection<Client> clients) {
+        this.office = office;
+        this.email = email.trim();
+        this.username = user.getUsername().trim();
+        this.firstname = firstname.trim();
+        this.lastname = lastname.trim();
+        this.password = user.getPassword().trim();
+        this.accountNonExpired = user.isAccountNonExpired();
+        this.accountNonLocked = user.isAccountNonLocked();
+        this.credentialsNonExpired = user.isCredentialsNonExpired();
+        this.enabled = user.isEnabled();
+        this.roles = roles;
+        this.firstTimeLoginRemaining = true;
+        this.lastTimePasswordUpdated = DateUtils.getDateOfTenant();
+        this.staff = staff;
+        this.passwordNeverExpires = passwordNeverExpire;
+        this.isSelfServiceUser = isSelfServiceUser;
+        this.appUserClientMappings = createAppUserClientMappings(clients);
+    }
+
+    public EnumOptionData organisationalRoleData() {
+        EnumOptionData organisationalRole = null;
+        if (this.staff != null) {
+            organisationalRole = this.staff.organisationalRoleData();
+        }
+        return organisationalRole;
+    }
+
+    public void updatePassword(final String encodePassword) {
+        this.password = encodePassword;
+        this.firstTimeLoginRemaining = false;
+        this.lastTimePasswordUpdated = DateUtils.getDateOfTenant();
+
+    }
+
+    public void changeOffice(final Office differentOffice) {
+        this.office = differentOffice;
+    }
+
+    public void changeStaff(final Staff differentStaff) {
+        this.staff = differentStaff;
+    }
+
+    public void updateRoles(final Set<Role> allRoles) {
+        if (!allRoles.isEmpty()) {
+            this.roles.clear();
+            this.roles = allRoles;
+        }
+    }
+
+    public Map<String, Object> update(final JsonCommand command, final PlatformPasswordEncoder platformPasswordEncoder,
+    		final Collection<Client> clients) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        // unencoded password provided
+        final String passwordParamName = "password";
+        final String passwordEncodedParamName = "passwordEncoded";
+        if (command.hasParameter(passwordParamName)) {
+            if (command.isChangeInPasswordParameterNamed(passwordParamName, this.password, platformPasswordEncoder, getId())) {
+                final String passwordEncodedValue = command.passwordValueOfParameterNamed(passwordParamName, platformPasswordEncoder,
+                        getId());
+                actualChanges.put(passwordEncodedParamName, passwordEncodedValue);
+                updatePassword(passwordEncodedValue);
+            }
+        }
+
+        if (command.hasParameter(passwordEncodedParamName)) {
+            if (command.isChangeInStringParameterNamed(passwordEncodedParamName, this.password)) {
+                final String newValue = command.stringValueOfParameterNamed(passwordEncodedParamName);
+                actualChanges.put(passwordEncodedParamName, newValue);
+                updatePassword(newValue);
+            }
+        }
+
+        final String officeIdParamName = "officeId";
+        if (command.isChangeInLongParameterNamed(officeIdParamName, this.office.getId())) {
+            final Long newValue = command.longValueOfParameterNamed(officeIdParamName);
+            actualChanges.put(officeIdParamName, newValue);
+        }
+
+        final String staffIdParamName = "staffId";
+        if (command.hasParameter(staffIdParamName)
+                && (this.staff == null || command.isChangeInLongParameterNamed(staffIdParamName, this.staff.getId()))) {
+            final Long newValue = command.longValueOfParameterNamed(staffIdParamName);
+            actualChanges.put(staffIdParamName, newValue);
+        }
+
+        final String rolesParamName = "roles";
+        if (command.isChangeInArrayParameterNamed(rolesParamName, getRolesAsIdStringArray())) {
+            final String[] newValue = command.arrayValueOfParameterNamed(rolesParamName);
+            actualChanges.put(rolesParamName, newValue);
+        }
+
+        final String usernameParamName = "username";
+        if (command.isChangeInStringParameterNamed(usernameParamName, this.username)) {
+            final String newValue = command.stringValueOfParameterNamed(usernameParamName);
+            actualChanges.put(usernameParamName, newValue);
+            this.username = newValue;
+        }
+
+        final String firstnameParamName = "firstname";
+        if (command.isChangeInStringParameterNamed(firstnameParamName, this.firstname)) {
+            final String newValue = command.stringValueOfParameterNamed(firstnameParamName);
+            actualChanges.put(firstnameParamName, newValue);
+            this.firstname = newValue;
+        }
+
+        final String lastnameParamName = "lastname";
+        if (command.isChangeInStringParameterNamed(lastnameParamName, this.lastname)) {
+            final String newValue = command.stringValueOfParameterNamed(lastnameParamName);
+            actualChanges.put(lastnameParamName, newValue);
+            this.lastname = newValue;
+        }
+
+        final String emailParamName = "email";
+        if (command.isChangeInStringParameterNamed(emailParamName, this.email)) {
+            final String newValue = command.stringValueOfParameterNamed(emailParamName);
+            actualChanges.put(emailParamName, newValue);
+            this.email = newValue;
+        }
+
+        final String passwordNeverExpire = "passwordNeverExpires";
+
+        if (command.hasParameter(passwordNeverExpire)) {
+            if (command.isChangeInBooleanParameterNamed(passwordNeverExpire, this.passwordNeverExpires)) {
+                final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(passwordNeverExpire);
+                actualChanges.put(passwordNeverExpire, newValue);
+                this.passwordNeverExpires = newValue;
+            }
+        }
+        
+        if(command.hasParameter(AppUserConstants.IS_SELF_SERVICE_USER)){
+        	if (command.isChangeInBooleanParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER, this.isSelfServiceUser)){
+                final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER);
+                actualChanges.put(AppUserConstants.IS_SELF_SERVICE_USER, newValue);
+                this.isSelfServiceUser = newValue;
+        	}
+        }
+        
+        if(this.isSelfServiceUser && command.hasParameter(AppUserConstants.CLIENTS)){
+        		actualChanges.put(AppUserConstants.CLIENTS, command.arrayValueOfParameterNamed(AppUserConstants.CLIENTS));
+        		Set<AppUserClientMapping> newClients = createAppUserClientMappings(clients); 
+        		if(this.appUserClientMappings == null){
+        			this.appUserClientMappings = new HashSet<>();
+        		}else{
+            		this.appUserClientMappings.retainAll(newClients);
+        		}
+        		this.appUserClientMappings.addAll(newClients);
+        }else if(!this.isSelfServiceUser && actualChanges.containsKey(AppUserConstants.IS_SELF_SERVICE_USER)){
+        	actualChanges.put(AppUserConstants.CLIENTS, new ArrayList<>());
+        	if(this.appUserClientMappings != null){
+        		this.appUserClientMappings = null;
+        	}
+        }
+
+        return actualChanges;
+    }
+
+    private String[] getRolesAsIdStringArray() {
+        final List<String> roleIds = new ArrayList<>();
+
+        for (final Role role : this.roles) {
+            roleIds.add(role.getId().toString());
+        }
+
+        return roleIds.toArray(new String[roleIds.size()]);
+    }
+
+    /**
+     * Delete is a <i>soft delete</i>. Updates flag so it wont appear in
+     * query/report results.
+     * 
+     * Any fields with unique constraints and prepended with id of record.
+     */
+    public void delete() {
+        this.deleted = true;
+        this.enabled = false;
+        this.accountNonExpired = false;
+        this.firstTimeLoginRemaining = true;
+        this.username = getId() + "_DELETED_" + this.username;
+        this.roles.clear();
+    }
+
+    public boolean isDeleted() {
+        return this.deleted;
+    }
+
+    @Override
+    public Collection<GrantedAuthority> getAuthorities() {
+        return populateGrantedAuthorities();
+    }
+
+    private List<GrantedAuthority> populateGrantedAuthorities() {
+        final List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
+        for (final Role role : this.roles) {
+            final Collection<Permission> permissions = role.getPermissions();
+            for (final Permission permission : permissions) {
+                grantedAuthorities.add(new SimpleGrantedAuthority(permission.getCode()));
+            }
+        }
+        return grantedAuthorities;
+    }
+
+    @Override
+    public String getPassword() {
+        return this.password;
+    }
+
+    @Override
+    public String getUsername() {
+        return this.username;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return this.accountNonExpired;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return this.accountNonLocked;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return this.credentialsNonExpired;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return this.enabled;
+    }
+
+    public String getFirstname() {
+        return this.firstname;
+    }
+
+    public String getLastname() {
+        return this.lastname;
+    }
+
+    public String getEmail() {
+        return this.email;
+    }
+
+    public Set<Role> getRoles() {
+        return this.roles;
+    }
+
+    public Office getOffice() {
+        return this.office;
+    }
+
+    public Staff getStaff() {
+        return this.staff;
+    }
+
+    public boolean getPasswordNeverExpires() {
+        return this.passwordNeverExpires;
+    }
+
+    public Date getLastTimePasswordUpdated() {
+        return this.lastTimePasswordUpdated;
+    }
+
+    public boolean canNotApproveLoanInPast() {
+        return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "APPROVEINPAST_LOAN");
+    }
+
+    public boolean canNotRejectLoanInPast() {
+        return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "REJECTINPAST_LOAN");
+    }
+
+    public boolean canNotWithdrawByClientLoanInPast() {
+        return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "WITHDRAWINPAST_LOAN");
+    }
+
+    public boolean canNotDisburseLoanInPast() {
+        return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "DISBURSEINPAST_LOAN");
+    }
+
+    public boolean canNotMakeRepaymentOnLoanInPast() {
+        return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "REPAYMENTINPAST_LOAN");
+    }
+
+    public boolean hasNotPermissionForReport(final String reportName) {
+
+        if (hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "REPORTING_SUPER_USER", "READ_" + reportName)) { return true; }
+
+        return false;
+    }
+
+    public boolean hasNotPermissionForDatatable(final String datatable, final String accessType) {
+
+        final String matchPermission = accessType + "_" + datatable;
+
+        if (accessType.equalsIgnoreCase("READ")) {
+
+            if (hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", matchPermission)) { return true; }
+
+            return false;
+        }
+
+        if (hasNotPermissionForAnyOf("ALL_FUNCTIONS", matchPermission)) { return true; }
+
+        return false;
+    }
+
+    public boolean hasNotPermissionForAnyOf(final String... permissionCodes) {
+        boolean hasNotPermission = true;
+        for (final String permissionCode : permissionCodes) {
+            final boolean checkPermission = hasPermissionTo(permissionCode);
+            if (checkPermission) {
+                hasNotPermission = false;
+                break;
+            }
+        }
+        return hasNotPermission;
+    }
+
+    public void validateHasReadPermission(final String resourceType) {
+
+        final String authorizationMessage = "User has no authority to view " + resourceType.toLowerCase() + "s";
+        final String matchPermission = "READ_" + resourceType.toUpperCase();
+
+        if (!hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", matchPermission)) { return; }
+
+        throw new NoAuthorizationException(authorizationMessage);
+    }
+
+    private boolean hasNotPermissionTo(final String permissionCode) {
+        return !hasPermissionTo(permissionCode);
+    }
+
+    private boolean hasPermissionTo(final String permissionCode) {
+        boolean hasPermission = hasAllFunctionsPermission();
+        if (!hasPermission) {
+            for (final Role role : this.roles) {
+                if (role.hasPermissionTo(permissionCode)) {
+                    hasPermission = true;
+                    break;
+                }
+            }
+        }
+        return hasPermission;
+    }
+
+    private boolean hasAllFunctionsPermission() {
+        boolean match = false;
+        for (final Role role : this.roles) {
+            if (role.hasPermissionTo("ALL_FUNCTIONS")) {
+                match = true;
+                break;
+            }
+        }
+        return match;
+    }
+
+    public boolean hasIdOf(final Long userId) {
+        return getId().equals(userId);
+    }
+
+    private boolean hasNotAnyPermission(final List<String> permissions) {
+        return !hasAnyPermission(permissions);
+    }
+
+    private boolean hasAnyPermission(final List<String> permissions) {
+        boolean hasAtLeastOneOf = false;
+
+        for (final String permissionCode : permissions) {
+            if (hasPermissionTo(permissionCode)) {
+                hasAtLeastOneOf = true;
+                break;
+            }
+        }
+
+        return hasAtLeastOneOf;
+    }
+
+    public void validateHasPermissionTo(final String function, final List<String> allowedPermissions) {
+        if (hasNotAnyPermission(allowedPermissions)) {
+            final String authorizationMessage = "User has no authority to: " + function;
+            throw new NoAuthorizationException(authorizationMessage);
+        }
+    }
+
+    public void validateHasPermissionTo(final String function) {
+        if (hasNotPermissionTo(function)) {
+            final String authorizationMessage = "User has no authority to: " + function;
+            logger.info("Unauthorized access: userId: " + getId() + " action: " + function + " allowed: " + getAuthorities());
+            throw new NoAuthorizationException(authorizationMessage);
+        }
+    }
+
+    public void validateHasReadPermission(final String function, final Long userId) {
+        if ("USER".equalsIgnoreCase(function) && userId.equals(getId())) {
+            // abstain from validation as user allowed fetch their own data no
+            // matter what permissions they have.
+        } else {
+            validateHasReadPermission(function);
+        }
+    }
+
+    public void validateHasCheckerPermissionTo(final String function) {
+        final String checkerPermissionName = function.toUpperCase() + "_CHECKER";
+        if (hasNotPermissionTo("CHECKER_SUPER_USER") && hasNotPermissionTo(checkerPermissionName)) {
+            final String authorizationMessage = "User has no authority to be a checker for: " + function;
+            throw new NoAuthorizationException(authorizationMessage);
+        }
+    }
+
+    public void validateHasDatatableReadPermission(final String datatable) {
+        if (hasNotPermissionForDatatable(datatable, "READ")) { throw new NoAuthorizationException("Not authorised to read datatable: "
+                + datatable); }
+    }
+
+    public Long getStaffId() {
+        Long staffId = null;
+        if (this.staff != null) {
+            staffId = this.staff.getId();
+        }
+        return staffId;
+    }
+
+    public String getStaffDisplayName() {
+        String staffDisplayName = null;
+        if (this.staff != null) {
+            staffDisplayName = this.staff.displayName();
+        }
+        return staffDisplayName;
+    }
+
+    public String getEncodedPassword(final JsonCommand command, final PlatformPasswordEncoder platformPasswordEncoder) {
+        final String passwordParamName = "password";
+        final String passwordEncodedParamName = "passwordEncoded";
+        String passwordEncodedValue = null;
+
+        if (command.hasParameter(passwordParamName)) {
+            if (command.isChangeInPasswordParameterNamed(passwordParamName, this.password, platformPasswordEncoder, getId())) {
+
+                passwordEncodedValue = command.passwordValueOfParameterNamed(passwordParamName, platformPasswordEncoder, getId());
+
+            }
+        } else if (command.hasParameter(passwordEncodedParamName)) {
+            if (command.isChangeInStringParameterNamed(passwordEncodedParamName, this.password)) {
+
+                passwordEncodedValue = command.stringValueOfParameterNamed(passwordEncodedParamName);
+
+            }
+        }
+
+        return passwordEncodedValue;
+    }
+
+    public boolean isNotEnabled() {
+        return !isEnabled();
+    }
+
+	public boolean isSelfServiceUser() {
+		return this.isSelfServiceUser;
+	}
+	
+    public Set<AppUserClientMapping> getAppUserClientMappings() {
+		return this.appUserClientMappings;
+	}
+
+	private Set<AppUserClientMapping> createAppUserClientMappings(Collection<Client> clients) {
+		Set<AppUserClientMapping> newAppUserClientMappings = null;
+		if(clients != null && clients.size() > 0){
+			newAppUserClientMappings = new HashSet<>();
+			for(Client client : clients){
+				newAppUserClientMappings.add(new AppUserClientMapping(client));
+			}
+		}
+		return newAppUserClientMappings;
+	}
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserClientMapping.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserClientMapping.java
new file mode 100644
index 0000000..f4119c4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserClientMapping.java
@@ -0,0 +1,80 @@
+/**
+ * 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.useradministration.domain;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_selfservice_user_client_mapping")
+public class AppUserClientMapping extends AbstractPersistable<Long> {
+	
+    @ManyToOne(optional = false, cascade = CascadeType.PERSIST)
+    @JoinColumn(name = "client_id", nullable = false)
+    private Client client;
+
+	public AppUserClientMapping(){
+		
+	}
+
+	public AppUserClientMapping(Client client){
+		this.client = client;
+	}
+
+	public Client getClient() {
+		return this.client;
+	}
+	
+	@Override
+	public boolean equals(Object obj) {
+
+		if (null == obj) {
+			return false;
+		}
+
+		if (this == obj) {
+			return true;
+		}
+
+		if (!getClass().equals(obj.getClass())) {
+			return false;
+		}
+
+		AppUserClientMapping that = (AppUserClientMapping) obj;
+
+		return null == this.client.getId() ? false : this.client.getId().equals(that.client.getId());
+	}
+	
+	@Override
+	public int hashCode() {
+
+		int hashCode = 17;
+
+		hashCode += null == this.client ? 0 : this.client.getId().hashCode() * 31;
+
+		return hashCode;
+	}
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserPreviousPassword.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserPreviousPassword.java
new file mode 100644
index 0000000..ebc61af
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserPreviousPassword.java
@@ -0,0 +1,60 @@
+/**
+ * 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.useradministration.domain;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_appuser_previous_password")
+public class AppUserPreviousPassword extends AbstractPersistable<Long> {
+
+    @Column(name = "user_id", nullable = false)
+    private Long userId;
+
+    @Column(name = "removal_date")
+    @Temporal(TemporalType.DATE)
+    private Date removalDate;
+
+    @Column(name = "password", nullable = false)
+    private String password;
+
+    protected AppUserPreviousPassword() {
+
+    }
+
+    public AppUserPreviousPassword(final AppUser user) {
+        this.userId = user.getId();
+        this.password = user.getPassword().trim();
+        this.removalDate = DateUtils.getDateOfTenant();
+    }
+
+    public String getPassword() {
+        return this.password;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserPreviousPasswordRepository.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserPreviousPasswordRepository.java
new file mode 100644
index 0000000..99c908a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserPreviousPasswordRepository.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.useradministration.domain;
+
+import java.util.List;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface AppUserPreviousPasswordRepository extends JpaRepository<AppUserPreviousPassword, Long>,
+        JpaSpecificationExecutor<AppUserPreviousPassword> {
+
+    public List<AppUserPreviousPassword> findByUserId(Long userId, Pageable pageable);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepository.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepository.java
new file mode 100644
index 0000000..48d3479
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepository.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.useradministration.domain;
+
+import org.apache.fineract.infrastructure.security.domain.PlatformUserRepository;
+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 AppUserRepository extends JpaRepository<AppUser, Long>, JpaSpecificationExecutor<AppUser>, PlatformUserRepository {
+
+    @Query("Select appUser from AppUser appUser where appUser.username = :username")
+    AppUser findAppUserByName(@Param("username") String username);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepositoryWrapper.java
new file mode 100644
index 0000000..3af7805
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepositoryWrapper.java
@@ -0,0 +1,43 @@
+/**
+ * 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.useradministration.domain;
+
+import org.apache.fineract.useradministration.exception.UserNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AppUserRepositoryWrapper {
+
+    private final AppUserRepository appUserRepository ;
+    
+    @Autowired
+    public AppUserRepositoryWrapper(final AppUserRepository appUserRepository) {
+        this.appUserRepository = appUserRepository ;
+    }
+    
+    public AppUser fetchSystemUser() {
+        String userName = "system" ;
+        AppUser user = this.appUserRepository.findAppUserByName(userName);
+        if(user == null) {
+            throw new UserNotFoundException(userName) ;
+        }
+        return user ;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/JpaUserDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/JpaUserDomainService.java
new file mode 100644
index 0000000..18b182a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/JpaUserDomainService.java
@@ -0,0 +1,67 @@
+/**
+ * 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.useradministration.domain;
+
+import org.apache.fineract.infrastructure.core.domain.EmailDetail;
+import org.apache.fineract.infrastructure.core.service.PlatformEmailService;
+import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class JpaUserDomainService implements UserDomainService {
+
+    private final AppUserRepository userRepository;
+    private final PlatformPasswordEncoder applicationPasswordEncoder;
+    private final PlatformEmailService emailService;
+
+    @Autowired
+    public JpaUserDomainService(final AppUserRepository userRepository, final PlatformPasswordEncoder applicationPasswordEncoder,
+            final PlatformEmailService emailService) {
+        this.userRepository = userRepository;
+        this.applicationPasswordEncoder = applicationPasswordEncoder;
+        this.emailService = emailService;
+    }
+
+    @Transactional
+    @Override
+    public void create(final AppUser appUser, final Boolean sendPasswordToEmail) {
+
+        generateKeyUsedForPasswordSalting(appUser);
+
+        final String unencodedPassword = appUser.getPassword();
+
+        final String encodePassword = this.applicationPasswordEncoder.encode(appUser);
+        appUser.updatePassword(encodePassword);
+
+        this.userRepository.saveAndFlush(appUser);
+
+        if (sendPasswordToEmail.booleanValue()) {
+            final EmailDetail emailDetail = new EmailDetail(appUser.getOffice().getName(), appUser.getFirstname(), appUser.getEmail(),
+                    appUser.getUsername());
+
+            this.emailService.sendToUserAccount(emailDetail, unencodedPassword);
+        }
+    }
+
+    private void generateKeyUsedForPasswordSalting(final AppUser appUser) {
+        this.userRepository.save(appUser);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/PasswordValidationPolicy.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/PasswordValidationPolicy.java
new file mode 100644
index 0000000..f633f76
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/PasswordValidationPolicy.java
@@ -0,0 +1,90 @@
+/**
+ * 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.useradministration.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_password_validation_policy")
+public class PasswordValidationPolicy extends AbstractPersistable<Long> {
+
+    // private final static Logger logger =
+    // LoggerFactory.getLogger(PasswordValidationPolicy.class);
+
+    @Column(name = "regex", nullable = false)
+    private String regex;
+
+    @Column(name = "description", nullable = false)
+    private String description;
+
+    @Column(name = "active", nullable = false)
+    private boolean active;
+
+    public PasswordValidationPolicy(final String regex, final String description, final boolean active) {
+        this.description = description;
+        this.regex = regex;
+        this.active = active;
+    }
+
+    public PasswordValidationPolicy() {
+        this.active = false;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getRegex() {
+        return regex;
+    }
+
+    public boolean getActive() {
+        return this.active;
+    }
+
+    public Map<String, Object> activate() {
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(1);
+
+        final String active = "active";
+
+        if (!this.active) {
+
+            actualChanges.put(active, true);
+            this.active = true;
+        }
+
+        return actualChanges;
+    }
+
+    public boolean isActive() {
+        return this.active;
+    }
+
+    public void deActivate() {
+        this.active = false;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/PasswordValidationPolicyRepository.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/PasswordValidationPolicyRepository.java
new file mode 100644
index 0000000..79de60d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/PasswordValidationPolicyRepository.java
@@ -0,0 +1,33 @@
+/**
+ * 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.useradministration.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+
+public interface PasswordValidationPolicyRepository extends JpaRepository<PasswordValidationPolicy, Long>,
+        JpaSpecificationExecutor<PasswordValidationPolicy> {
+
+    @Query("from PasswordValidationPolicy PVP WHERE PVP.active = 1")
+    public PasswordValidationPolicy findActivePasswordValidationPolicy();
+
+
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/Permission.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/Permission.java
new file mode 100644
index 0000000..10e8d55
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/Permission.java
@@ -0,0 +1,84 @@
+/**
+ * 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.useradministration.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_permission")
+public class Permission extends AbstractPersistable<Long> {
+
+    @Column(name = "grouping", nullable = false, length = 45)
+    private final String grouping;
+
+    @Column(name = "code", nullable = false, length = 100)
+    private final String code;
+
+    @Column(name = "entity_name", nullable = true, length = 100)
+    private final String entityName;
+
+    @Column(name = "action_name", nullable = true, length = 100)
+    private final String actionName;
+
+    @Column(name = "can_maker_checker", nullable = false)
+    private boolean canMakerChecker;
+
+    public Permission(final String grouping, final String entityName, final String actionName) {
+        this.grouping = grouping;
+        this.entityName = entityName;
+        this.actionName = actionName;
+        this.code = actionName + "_" + entityName;
+        this.canMakerChecker = false;
+    }
+
+    protected Permission() {
+        this.grouping = null;
+        this.entityName = null;
+        this.actionName = null;
+        this.code = null;
+        this.canMakerChecker = false;
+    }
+
+    public boolean hasCode(final String checkCode) {
+        return this.code.equalsIgnoreCase(checkCode);
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public boolean hasMakerCheckerEnabled() {
+        return this.canMakerChecker;
+    }
+
+    public String getGrouping() {
+        return this.grouping;
+    }
+
+    public boolean enableMakerChecker(final boolean canMakerChecker) {
+        final boolean isUpdatedValueSame = this.canMakerChecker == canMakerChecker;
+        this.canMakerChecker = canMakerChecker;
+
+        return !isUpdatedValueSame;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/PermissionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/PermissionRepository.java
new file mode 100644
index 0000000..f3fd71b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/PermissionRepository.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.useradministration.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PermissionRepository extends JpaRepository<Permission, Long> {
+
+    Permission findOneByCode(String code);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/Role.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/Role.java
new file mode 100644
index 0000000..fd27b5a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/Role.java
@@ -0,0 +1,147 @@
+/**
+ * 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.useradministration.domain;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.useradministration.data.RoleData;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_role", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name") })
+public class Role extends AbstractPersistable<Long> {
+
+    @Column(name = "name", unique = true, nullable = false, length = 100)
+    private String name;
+
+    @Column(name = "description", nullable = false, length = 500)
+    private String description;
+
+    @Column(name = "is_disabled", nullable = false)
+    private Boolean disabled;
+
+    @ManyToMany(fetch = FetchType.EAGER)
+    @JoinTable(name = "m_role_permission", joinColumns = @JoinColumn(name = "role_id"), inverseJoinColumns = @JoinColumn(name = "permission_id"))
+    private final Set<Permission> permissions = new HashSet<>();
+
+    public static Role fromJson(final JsonCommand command) {
+        final String name = command.stringValueOfParameterNamed("name");
+        final String description = command.stringValueOfParameterNamed("description");
+        return new Role(name, description);
+    }
+
+    protected Role() {
+        //
+    }
+
+    public Role(final String name, final String description) {
+        this.name = name.trim();
+        this.description = description.trim();
+        this.disabled = false;
+    }
+
+    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 = "description";
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.description = newValue;
+        }
+
+        return actualChanges;
+    }
+
+    public boolean updatePermission(final Permission permission, final boolean isSelected) {
+        boolean changed = false;
+        if (isSelected) {
+            changed = addPermission(permission);
+        } else {
+            changed = removePermission(permission);
+        }
+
+        return changed;
+    }
+
+    private boolean addPermission(final Permission permission) {
+        return this.permissions.add(permission);
+    }
+
+    private boolean removePermission(final Permission permission) {
+        return this.permissions.remove(permission);
+    }
+
+    public Collection<Permission> getPermissions() {
+        return this.permissions;
+    }
+
+    public boolean hasPermissionTo(final String permissionCode) {
+        boolean match = false;
+        for (final Permission permission : this.permissions) {
+            if (permission.hasCode(permissionCode)) {
+                match = true;
+                break;
+            }
+        }
+        return match;
+    }
+
+    public RoleData toData() {
+        return new RoleData(getId(), this.name, this.description, this.disabled);
+    }
+
+    public void disableRole() {
+        this.disabled = true;
+    }
+
+    public Boolean isDisabled() {
+        return this.disabled;
+    }
+
+    public void enableRole() {
+        this.disabled = false;
+    }
+
+    public Boolean isEnabled() {
+        return this.disabled;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/RoleRepository.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/RoleRepository.java
new file mode 100644
index 0000000..da9b68e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/RoleRepository.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.useradministration.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 RoleRepository extends JpaRepository<Role, Long>, JpaSpecificationExecutor<Role> {
+
+    @Query("SELECT COUNT(*) FROM AppUser a JOIN a.roles r WHERE r.id = :roleId AND a.deleted = false")
+    Integer getCountOfRolesAssociatedWithUsers(@Param("roleId") Long roleId);
+  
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/UserDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/UserDomainService.java
new file mode 100644
index 0000000..ac7611f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/UserDomainService.java
@@ -0,0 +1,24 @@
+/**
+ * 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.useradministration.domain;
+
+public interface UserDomainService {
+
+    void create(AppUser appUser, Boolean sendPasswordToEmail);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PasswordMustBeDifferentException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PasswordMustBeDifferentException.java
new file mode 100644
index 0000000..bb9669e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PasswordMustBeDifferentException.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.useradministration.exception;
+
+public class PasswordMustBeDifferentException extends RuntimeException {
+    // basic runtime exception
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PasswordPreviouslyUsedException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PasswordPreviouslyUsedException.java
new file mode 100644
index 0000000..feed85a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PasswordPreviouslyUsedException.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.useradministration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+
+public class PasswordPreviouslyUsedException extends PlatformApiDataValidationException {
+
+    public PasswordPreviouslyUsedException() {
+        super("error.msg.password.already.used", "The submitted password has already been used in the past",null);
+    }
+}
+
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PasswordValidationPolicyNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PasswordValidationPolicyNotFoundException.java
new file mode 100644
index 0000000..c21cb78
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PasswordValidationPolicyNotFoundException.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.useradministration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when role resources are not found.
+ */
+public class PasswordValidationPolicyNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public PasswordValidationPolicyNotFoundException(final Long id) {
+        super("error.msg.password.validation.policy.id.invalid", "Password Validation Policy with identifier " + id + " does not exist", id);
+    }
+
+    public PasswordValidationPolicyNotFoundException() {
+        super("error.msg.password.validation.policy.not.found", "An active password validation policy was not found");
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PermissionCantBeMakerCheckerableException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PermissionCantBeMakerCheckerableException.java
new file mode 100644
index 0000000..53f5f62
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PermissionCantBeMakerCheckerableException.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.useradministration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when permission is attempted to be set as
+ * maker-checker enabled.
+ */
+public class PermissionCantBeMakerCheckerableException extends AbstractPlatformResourceNotFoundException {
+
+    public PermissionCantBeMakerCheckerableException(final String code) {
+        super("error.msg.permission.code.not.makercheckerable", "Permission with Code " + code + " can't be maker-checkerable", code);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PermissionNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PermissionNotFoundException.java
new file mode 100644
index 0000000..e8c5c4b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/PermissionNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.useradministration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when permission resources are not found.
+ */
+public class PermissionNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public PermissionNotFoundException(final String code) {
+        super("error.msg.permission.code.invalid", "Permission with Code " + code + " does not exist", code);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/RoleAssociatedException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/RoleAssociatedException.java
new file mode 100644
index 0000000..2b2db6d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/RoleAssociatedException.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.useradministration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+
+public class RoleAssociatedException extends AbstractPlatformDomainRuleException {
+
+    public RoleAssociatedException(final String errorcode, final Long id) {
+        super(errorcode, "Role with identifier " + id + " associated with users", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/RoleNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/RoleNotFoundException.java
new file mode 100644
index 0000000..e0b867c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/RoleNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.useradministration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when role resources are not found.
+ */
+public class RoleNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public RoleNotFoundException(final Long id) {
+        super("error.msg.role.id.invalid", "Role with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UnAuthenticatedUserException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UnAuthenticatedUserException.java
new file mode 100644
index 0000000..5cf2828
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UnAuthenticatedUserException.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.useradministration.exception;
+
+import org.springframework.security.core.context.SecurityContext;
+
+/**
+ * A {@link RuntimeException} that is thrown in the case where no authenticated
+ * user exists within the platform {@link SecurityContext}.
+ */
+public class UnAuthenticatedUserException extends RuntimeException {
+    // no added behaviour
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UserNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UserNotFoundException.java
new file mode 100644
index 0000000..ae53497
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UserNotFoundException.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.useradministration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when user resources are not found.
+ */
+public class UserNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public UserNotFoundException(final Long id) {
+        super("error.msg.user.id.invalid", "User with identifier " + id + " does not exist", id);
+    }
+    
+    public UserNotFoundException(final String userName) {
+        super("error.msg.user.name.not.found", "User with identifier " + userName + " does not exist");
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UsernameAlreadyExistsException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UsernameAlreadyExistsException.java
new file mode 100644
index 0000000..fefa798
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UsernameAlreadyExistsException.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.useradministration.exception;
+
+public class UsernameAlreadyExistsException extends RuntimeException {
+
+    public UsernameAlreadyExistsException(final Throwable e) {
+        super(e);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UsernameMustBeDifferentException.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UsernameMustBeDifferentException.java
new file mode 100644
index 0000000..663976d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/exception/UsernameMustBeDifferentException.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.useradministration.exception;
+
+public class UsernameMustBeDifferentException extends RuntimeException {
+    // basic runtime exception
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/CreateRoleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/CreateRoleCommandHandler.java
new file mode 100644
index 0000000..968037e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/CreateRoleCommandHandler.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.useradministration.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.useradministration.service.RoleWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ROLE", action = "CREATE")
+public class CreateRoleCommandHandler implements NewCommandSourceHandler {
+
+    private final RoleWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateRoleCommandHandler(final RoleWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createRole(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/CreateUserCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/CreateUserCommandHandler.java
new file mode 100644
index 0000000..77b0a20
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/CreateUserCommandHandler.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.useradministration.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.useradministration.service.AppUserWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "USER", action = "CREATE")
+public class CreateUserCommandHandler implements NewCommandSourceHandler {
+
+    private final AppUserWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateUserCommandHandler(final AppUserWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createUser(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/DeleteRoleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/DeleteRoleCommandHandler.java
new file mode 100644
index 0000000..9d90358
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/DeleteRoleCommandHandler.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.useradministration.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.useradministration.service.RoleWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ROLE", action = "DELETE")
+public class DeleteRoleCommandHandler implements NewCommandSourceHandler {
+
+    private final RoleWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteRoleCommandHandler(final RoleWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.deleteRole(command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/DeleteUserCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/DeleteUserCommandHandler.java
new file mode 100644
index 0000000..d969f62
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/DeleteUserCommandHandler.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.useradministration.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.useradministration.service.AppUserWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "USER", action = "DELETE")
+public class DeleteUserCommandHandler implements NewCommandSourceHandler {
+
+    private final AppUserWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteUserCommandHandler(final AppUserWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.deleteUser(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/DisableRoleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/DisableRoleCommandHandler.java
new file mode 100644
index 0000000..3b72713
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/DisableRoleCommandHandler.java
@@ -0,0 +1,45 @@
+/**
+ * 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.useradministration.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.useradministration.service.RoleWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "ROLE", action = "DISABLE")
+public class DisableRoleCommandHandler implements NewCommandSourceHandler {
+
+    private final RoleWritePlatformService writePlatformService;
+
+    @Autowired
+    public DisableRoleCommandHandler(final RoleWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.writePlatformService.disableRole(command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/EnableRoleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/EnableRoleCommandHandler.java
new file mode 100644
index 0000000..25d2d22
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/EnableRoleCommandHandler.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.useradministration.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.useradministration.service.RoleWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ROLE", action = "ENABLE")
+public class EnableRoleCommandHandler implements NewCommandSourceHandler {
+
+    private final RoleWritePlatformService writePlatformService;
+
+    @Autowired
+    public EnableRoleCommandHandler(final RoleWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return this.writePlatformService.enableRole(command.entityId());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateMakerCheckerPermissionsCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateMakerCheckerPermissionsCommandHandler.java
new file mode 100644
index 0000000..9c9978d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateMakerCheckerPermissionsCommandHandler.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.useradministration.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.useradministration.service.PermissionWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PERMISSION", action = "UPDATE")
+public class UpdateMakerCheckerPermissionsCommandHandler implements NewCommandSourceHandler {
+
+    private final PermissionWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateMakerCheckerPermissionsCommandHandler(final PermissionWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateMakerCheckerPermissions(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdatePasswordPreferencesCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdatePasswordPreferencesCommandHandler.java
new file mode 100644
index 0000000..517a9f1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdatePasswordPreferencesCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.useradministration.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.useradministration.service.PasswordPreferencesWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "PASSWORD_PREFERENCES", action = "UPDATE")
+public class UpdatePasswordPreferencesCommandHandler implements NewCommandSourceHandler {
+
+    private final PasswordPreferencesWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdatePasswordPreferencesCommandHandler(final PasswordPreferencesWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.updatePreferences(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateRoleCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateRoleCommandHandler.java
new file mode 100644
index 0000000..645ddbb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateRoleCommandHandler.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.useradministration.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.useradministration.service.RoleWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ROLE", action = "UPDATE")
+public class UpdateRoleCommandHandler implements NewCommandSourceHandler {
+
+    private final RoleWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateRoleCommandHandler(final RoleWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateRole(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateRolePermissionsCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateRolePermissionsCommandHandler.java
new file mode 100644
index 0000000..5252956
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateRolePermissionsCommandHandler.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.useradministration.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.useradministration.service.RoleWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ROLE", action = "PERMISSIONS")
+public class UpdateRolePermissionsCommandHandler implements NewCommandSourceHandler {
+
+    private final RoleWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateRolePermissionsCommandHandler(final RoleWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.updateRolePermissions(command.entityId(), command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateUserCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateUserCommandHandler.java
new file mode 100644
index 0000000..d0a1725
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/handler/UpdateUserCommandHandler.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.useradministration.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.useradministration.service.AppUserWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "USER", action = "UPDATE")
+public class UpdateUserCommandHandler implements NewCommandSourceHandler {
+
+    private final AppUserWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateUserCommandHandler(final AppUserWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final Long userId = command.entityId();
+        return this.writePlatformService.updateUser(userId, command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/serialization/PermissionsCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/serialization/PermissionsCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..a374282
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/serialization/PermissionsCommandFromApiJsonDeserializer.java
@@ -0,0 +1,66 @@
+/**
+ * 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.useradministration.serialization;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.useradministration.command.PermissionsCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link PermissionsCommand}'s.
+ */
+@Component
+public final class PermissionsCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<PermissionsCommand> {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("permissions"));
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public PermissionsCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    @Override
+    public PermissionsCommand commandFromApiJson(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);
+
+        return this.fromApiJsonHelper.fromJson(json, PermissionsCommand.class);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserConstants.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserConstants.java
new file mode 100644
index 0000000..512e420
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserConstants.java
@@ -0,0 +1,27 @@
+/**
+ * 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.useradministration.service;
+
+public class AppUserConstants {
+
+    public static final String PASSWORD_NEVER_EXPIRES = "passwordNeverExpires";
+    public static final String IS_SELF_SERVICE_USER = "isSelfServiceUser";
+    public static final String CLIENTS = "clients";
+
+}
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
new file mode 100755
index 0000000..067c725
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformService.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.useradministration.service;
+
+import org.apache.fineract.useradministration.data.AppUserData;
+
+import java.util.Collection;
+
+public interface AppUserReadPlatformService {
+
+    Collection<AppUserData> retrieveAllUsers();
+
+    Collection<AppUserData> retrieveSearchTemplate();
+
+    AppUserData retrieveNewUserDetails();
+
+    AppUserData retrieveUser(Long userId);
+}
\ 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
new file mode 100755
index 0000000..21e064f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformServiceImpl.java
@@ -0,0 +1,215 @@
+/**
+ * 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.useradministration.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+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.office.data.OfficeData;
+import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.useradministration.data.AppUserData;
+import org.apache.fineract.useradministration.data.RoleData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.AppUserClientMapping;
+import org.apache.fineract.useradministration.domain.AppUserRepository;
+import org.apache.fineract.useradministration.domain.Role;
+import org.apache.fineract.useradministration.exception.UserNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AppUserReadPlatformServiceImpl implements AppUserReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final RoleReadPlatformService roleReadPlatformService;
+    private final AppUserRepository appUserRepository;
+    private final StaffReadPlatformService staffReadPlatformService;
+
+    @Autowired
+    public AppUserReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
+            final OfficeReadPlatformService officeReadPlatformService, final RoleReadPlatformService roleReadPlatformService,
+            final AppUserRepository appUserRepository, final StaffReadPlatformService staffReadPlatformService) {
+        this.context = context;
+        this.officeReadPlatformService = officeReadPlatformService;
+        this.roleReadPlatformService = roleReadPlatformService;
+        this.appUserRepository = appUserRepository;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.staffReadPlatformService = staffReadPlatformService;
+    }
+
+    /*
+     * used for caching in spring expression language.
+     */
+    public PlatformSecurityContext getContext() {
+        return this.context;
+    }
+
+    @Override
+    @Cacheable(value = "users", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy())")
+    public Collection<AppUserData> retrieveAllUsers() {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final AppUserMapper mapper = new AppUserMapper(this.roleReadPlatformService, this.staffReadPlatformService);
+        final String sql = "select " + mapper.schema();
+
+        return this.jdbcTemplate.query(sql, mapper, new Object[] { hierarchySearchString });
+    }
+
+    @Override
+    public Collection<AppUserData> retrieveSearchTemplate() {
+        final AppUser currentUser = this.context.authenticatedUser();
+        final String hierarchy = currentUser.getOffice().getHierarchy();
+        final String hierarchySearchString = hierarchy + "%";
+
+        final AppUserLookupMapper mapper = new AppUserLookupMapper();
+        final String sql = "select " + mapper.schema();
+
+        return this.jdbcTemplate.query(sql, mapper, new Object[] { hierarchySearchString });
+    }
+
+    @Override
+    public AppUserData retrieveNewUserDetails() {
+
+        final Collection<OfficeData> offices = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+        final Collection<RoleData> availableRoles = this.roleReadPlatformService.retrieveAllActiveRoles();
+
+        return AppUserData.template(offices, availableRoles);
+    }
+
+    @Override
+    public AppUserData retrieveUser(final Long userId) {
+
+        this.context.authenticatedUser();
+
+        final AppUser user = this.appUserRepository.findOne(userId);
+        if (user == null || user.isDeleted()) { throw new UserNotFoundException(userId); }
+
+        final Collection<RoleData> availableRoles = this.roleReadPlatformService.retrieveAll();
+
+        final Collection<RoleData> selectedUserRoles = new ArrayList<>();
+        final Set<Role> userRoles = user.getRoles();
+        for (final Role role : userRoles) {
+            selectedUserRoles.add(role.toData());
+        }
+
+        availableRoles.removeAll(selectedUserRoles);
+
+        final StaffData linkedStaff;
+        if (user.getStaff() != null) {
+            linkedStaff = this.staffReadPlatformService.retrieveStaff(user.getStaffId());
+        } else {
+            linkedStaff = null;
+        }
+
+        AppUserData retUser = AppUserData.instance(user.getId(), user.getUsername(), user.getEmail(), user.getOffice().getId(),
+                user.getOffice().getName(), user.getFirstname(), user.getLastname(), availableRoles, selectedUserRoles, linkedStaff,
+                user.getPasswordNeverExpires(), user.isSelfServiceUser());
+        
+        if(retUser.isSelfServiceUser()){
+        	Set<ClientData> clients = new HashSet<>();
+        	for(AppUserClientMapping clientMap : user.getAppUserClientMappings()){
+        		Client client = clientMap.getClient();
+        		clients.add(ClientData.lookup(client.getId(), client.getDisplayName(), 
+        				client.getOffice().getId(), client.getOffice().getName()));
+        	}
+        	retUser.setClients(clients);
+        }
+        
+        return retUser; 
+    }
+
+    private static final class AppUserMapper implements RowMapper<AppUserData> {
+
+        private final RoleReadPlatformService roleReadPlatformService;
+        private final StaffReadPlatformService staffReadPlatformService;
+
+        public AppUserMapper(final RoleReadPlatformService roleReadPlatformService, final StaffReadPlatformService staffReadPlatformService) {
+            this.roleReadPlatformService = roleReadPlatformService;
+            this.staffReadPlatformService = staffReadPlatformService;
+        }
+
+        @Override
+        public AppUserData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String username = rs.getString("username");
+            final String firstname = rs.getString("firstname");
+            final String lastname = rs.getString("lastname");
+            final String email = rs.getString("email");
+            final Long officeId = JdbcSupport.getLong(rs, "officeId");
+            final String officeName = rs.getString("officeName");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+            final Boolean passwordNeverExpire = rs.getBoolean("passwordNeverExpires");
+            final Boolean isSelfServiceUser = rs.getBoolean("isSelfServiceUser");
+            final Collection<RoleData> selectedRoles = this.roleReadPlatformService.retrieveAppUserRoles(id);
+
+            final StaffData linkedStaff;
+            if (staffId != null) {
+                linkedStaff = this.staffReadPlatformService.retrieveStaff(staffId);
+            } else {
+                linkedStaff = null;
+            }
+            return AppUserData.instance(id, username, email, officeId, officeName, firstname, lastname, null, selectedRoles, linkedStaff,
+                    passwordNeverExpire, isSelfServiceUser);
+        }
+
+        public String schema() {
+            return " u.id as id, u.username as username, u.firstname as firstname, u.lastname as lastname, u.email as email, u.password_never_expires as passwordNeverExpires, "
+                    + " u.office_id as officeId, o.name as officeName, u.staff_id as staffId, u.is_self_service_user as isSelfServiceUser from m_appuser u "
+                    + " join m_office o on o.id = u.office_id where o.hierarchy like ? and u.is_deleted=0 order by u.username";
+        }
+
+    }
+
+    private static final class AppUserLookupMapper implements RowMapper<AppUserData> {
+
+        @Override
+        public AppUserData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = rs.getLong("id");
+            final String username = rs.getString("username");
+
+            return AppUserData.dropdown(id, username);
+        }
+
+        public String schema() {
+            return " u.id as id, u.username as username from m_appuser u "
+                    + " join m_office o on o.id = u.office_id where o.hierarchy like ? and u.is_deleted=0 order by u.username";
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformService.java
new file mode 100644
index 0000000..7841099
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.useradministration.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface AppUserWritePlatformService {
+
+    CommandProcessingResult createUser(JsonCommand command);
+
+    CommandProcessingResult updateUser(Long userId, JsonCommand command);
+
+    CommandProcessingResult deleteUser(Long userId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..0b4a0db
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,338 @@
+/**
+ * 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.useradministration.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.PlatformEmailSendException;
+import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepository;
+import org.apache.fineract.useradministration.api.AppUserApiConstant;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.AppUserPreviousPassword;
+import org.apache.fineract.useradministration.domain.AppUserPreviousPasswordRepository;
+import org.apache.fineract.useradministration.domain.AppUserRepository;
+import org.apache.fineract.useradministration.domain.Role;
+import org.apache.fineract.useradministration.domain.RoleRepository;
+import org.apache.fineract.useradministration.domain.UserDomainService;
+import org.apache.fineract.useradministration.exception.PasswordPreviouslyUsedException;
+import org.apache.fineract.useradministration.exception.RoleNotFoundException;
+import org.apache.fineract.useradministration.exception.UserNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.ObjectUtils;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+
+@Service
+public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(AppUserWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final PlatformSecurityContext context;
+    private final UserDomainService userDomainService;
+    private final PlatformPasswordEncoder platformPasswordEncoder;
+    private final AppUserRepository appUserRepository;
+    private final OfficeRepository officeRepository;
+    private final RoleRepository roleRepository;
+    private final UserDataValidator fromApiJsonDeserializer;
+    private final AppUserPreviousPasswordRepository appUserPreviewPasswordRepository;
+    private final StaffRepositoryWrapper staffRepositoryWrapper;
+    private final ClientRepository clientRepository;
+
+    @Autowired
+    public AppUserWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final AppUserRepository appUserRepository,
+            final UserDomainService userDomainService, final OfficeRepository officeRepository, final RoleRepository roleRepository,
+            final PlatformPasswordEncoder platformPasswordEncoder, final UserDataValidator fromApiJsonDeserializer,
+            final AppUserPreviousPasswordRepository appUserPreviewPasswordRepository, final StaffRepositoryWrapper staffRepositoryWrapper,
+            final ClientRepository clientRepository) {
+        this.context = context;
+        this.appUserRepository = appUserRepository;
+        this.userDomainService = userDomainService;
+        this.officeRepository = officeRepository;
+        this.roleRepository = roleRepository;
+        this.platformPasswordEncoder = platformPasswordEncoder;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+        this.appUserPreviewPasswordRepository = appUserPreviewPasswordRepository;
+        this.staffRepositoryWrapper = staffRepositoryWrapper;
+        this.clientRepository = clientRepository;
+    }
+
+    @Transactional
+    @Override
+    @Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
+    public CommandProcessingResult createUser(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+            final String officeIdParamName = "officeId";
+            final Long officeId = command.longValueOfParameterNamed(officeIdParamName);
+
+            final Office userOffice = this.officeRepository.findOne(officeId);
+            if (userOffice == null) { throw new OfficeNotFoundException(officeId); }
+
+            final String[] roles = command.arrayValueOfParameterNamed("roles");
+            final Set<Role> allRoles = assembleSetOfRoles(roles);
+
+            AppUser appUser;
+
+            final String staffIdParamName = "staffId";
+            final Long staffId = command.longValueOfParameterNamed(staffIdParamName);
+
+            Staff linkedStaff = null;
+            if (staffId != null) {
+                linkedStaff = this.staffRepositoryWrapper.findByOfficeWithNotFoundDetection(staffId, userOffice.getId());
+            }
+            
+            Collection<Client> clients = null;
+            if(command.hasParameter(AppUserConstants.IS_SELF_SERVICE_USER)
+            		&& command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER)
+            		&& command.hasParameter(AppUserConstants.CLIENTS)){
+            	JsonArray clientsArray = command.arrayOfParameterNamed(AppUserConstants.CLIENTS);
+            	Collection<Long> clientIds = new HashSet<>();
+            	for(JsonElement clientElement : clientsArray){
+            		clientIds.add(clientElement.getAsLong());
+            	}
+            	clients = this.clientRepository.findAll(clientIds);
+            }
+
+            appUser = AppUser.fromJson(userOffice, linkedStaff, allRoles, clients, command);
+
+            final Boolean sendPasswordToEmail = command.booleanObjectValueOfParameterNamed("sendPasswordToEmail");
+            this.userDomainService.create(appUser, sendPasswordToEmail);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(appUser.getId()) //
+                    .withOfficeId(userOffice.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        } catch (final PlatformEmailSendException e) {
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+            final String email = command.stringValueOfParameterNamed("email");
+            final ApiParameterError error = ApiParameterError.parameterError("error.msg.user.email.invalid",
+                    "The parameter email is invalid.", "email", email);
+            dataValidationErrors.add(error);
+
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+
+    @Transactional
+    @Override
+    @Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
+    public CommandProcessingResult updateUser(final Long userId, final JsonCommand command) {
+
+        try {
+
+            this.context.authenticatedUser(new CommandWrapperBuilder().updateUser(null).build());
+
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final AppUser userToUpdate = this.appUserRepository.findOne(userId);
+
+            if (userToUpdate == null) { throw new UserNotFoundException(userId); }
+
+            final AppUserPreviousPassword currentPasswordToSaveAsPreview = getCurrentPasswordToSaveAsPreview(userToUpdate, command);
+            
+            Collection<Client> clients = null;
+            boolean isSelfServiceUser = userToUpdate.isSelfServiceUser();
+            if(command.hasParameter(AppUserConstants.IS_SELF_SERVICE_USER)){
+            	isSelfServiceUser = command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER); 
+            }
+            
+            if(isSelfServiceUser
+            		&& command.hasParameter(AppUserConstants.CLIENTS)){
+            	JsonArray clientsArray = command.arrayOfParameterNamed(AppUserConstants.CLIENTS);
+            	Collection<Long> clientIds = new HashSet<>();
+            	for(JsonElement clientElement : clientsArray){
+            		clientIds.add(clientElement.getAsLong());
+            	}
+            	clients = this.clientRepository.findAll(clientIds);
+            }
+
+            final Map<String, Object> changes = userToUpdate.update(command, this.platformPasswordEncoder, clients);
+
+            if (changes.containsKey("officeId")) {
+                final Long officeId = (Long) changes.get("officeId");
+                final Office office = this.officeRepository.findOne(officeId);
+                if (office == null) { throw new OfficeNotFoundException(officeId); }
+
+                userToUpdate.changeOffice(office);
+            }
+
+            if (changes.containsKey("staffId")) {
+                final Long staffId = (Long) changes.get("staffId");
+                Staff linkedStaff = null;
+                if (staffId != null) {
+                    linkedStaff = this.staffRepositoryWrapper.findByOfficeWithNotFoundDetection(staffId, userToUpdate.getOffice().getId());
+                }
+                userToUpdate.changeStaff(linkedStaff);
+            }
+
+            if (changes.containsKey("roles")) {
+                final String[] roleIds = (String[]) changes.get("roles");
+                final Set<Role> allRoles = assembleSetOfRoles(roleIds);
+
+                userToUpdate.updateRoles(allRoles);
+            }
+
+            if (!changes.isEmpty()) {
+                this.appUserRepository.saveAndFlush(userToUpdate);
+
+                if (currentPasswordToSaveAsPreview != null) {
+                    this.appUserPreviewPasswordRepository.save(currentPasswordToSaveAsPreview);
+                }
+
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withEntityId(userId) //
+                    .withOfficeId(userToUpdate.getOffice().getId()) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    /**
+     * encode the new submitted password retrieve the last n used password check
+     * if the current submitted password, match with one of them
+     * 
+     * @param user
+     * @param command
+     * @return
+     */
+    private AppUserPreviousPassword getCurrentPasswordToSaveAsPreview(final AppUser user, final JsonCommand command) {
+
+        final String passWordEncodedValue = user.getEncodedPassword(command, this.platformPasswordEncoder);
+
+        AppUserPreviousPassword currentPasswordToSaveAsPreview = null;
+
+        if (passWordEncodedValue != null) {
+
+            PageRequest pageRequest = new PageRequest(0, AppUserApiConstant.numberOfPreviousPasswords, Sort.Direction.DESC, "removalDate");
+
+            final List<AppUserPreviousPassword> nLastUsedPasswords = this.appUserPreviewPasswordRepository.findByUserId(user.getId(),
+                    pageRequest);
+
+            for (AppUserPreviousPassword aPreviewPassword : nLastUsedPasswords) {
+
+                if (aPreviewPassword.getPassword().equals(passWordEncodedValue)) {
+
+                throw new PasswordPreviouslyUsedException();
+
+                }
+            }
+
+            currentPasswordToSaveAsPreview = new AppUserPreviousPassword(user);
+
+        }
+
+        return currentPasswordToSaveAsPreview;
+
+    }
+
+    private Set<Role> assembleSetOfRoles(final String[] rolesArray) {
+
+        final Set<Role> allRoles = new HashSet<>();
+
+        if (!ObjectUtils.isEmpty(rolesArray)) {
+            for (final String roleId : rolesArray) {
+                final Long id = Long.valueOf(roleId);
+                final Role role = this.roleRepository.findOne(id);
+                if (role == null) { throw new RoleNotFoundException(id); }
+                allRoles.add(role);
+            }
+        }
+
+        return allRoles;
+    }
+
+    @Transactional
+    @Override
+    @Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
+    public CommandProcessingResult deleteUser(final Long userId) {
+
+        final AppUser user = this.appUserRepository.findOne(userId);
+        if (user == null || user.isDeleted()) { throw new UserNotFoundException(userId); }
+
+        user.delete();
+        this.appUserRepository.save(user);
+
+        return new CommandProcessingResultBuilder().withEntityId(userId).withOfficeId(user.getOffice().getId()).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("username_org")) {
+            final String username = command.stringValueOfParameterNamed("username");
+            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);
+        }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue", "Unknown data integrity issue with resource.");
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordPreferencesWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordPreferencesWritePlatformService.java
new file mode 100644
index 0000000..02cb799
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordPreferencesWritePlatformService.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.useradministration.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface PasswordPreferencesWritePlatformService {
+
+    CommandProcessingResult updatePreferences(JsonCommand command);
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordPreferencesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordPreferencesWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..e8986e4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordPreferencesWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,97 @@
+/**
+ * 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.useradministration.service;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.useradministration.api.PasswordPreferencesApiConstants;
+import org.apache.fineract.useradministration.data.PasswordPreferencesDataValidator;
+import org.apache.fineract.useradministration.domain.PasswordValidationPolicy;
+import org.apache.fineract.useradministration.domain.PasswordValidationPolicyRepository;
+import org.apache.fineract.useradministration.exception.PasswordValidationPolicyNotFoundException;
+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 PasswordPreferencesWritePlatformServiceJpaRepositoryImpl implements PasswordPreferencesWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(PasswordPreferencesWritePlatformServiceJpaRepositoryImpl.class);
+    private final PasswordValidationPolicyRepository validationRepository;
+    private final PasswordPreferencesDataValidator dataValidator;
+
+    @Autowired
+    public PasswordPreferencesWritePlatformServiceJpaRepositoryImpl(final PasswordValidationPolicyRepository validationPolicyRepository,
+            final PasswordPreferencesDataValidator dataValidator) {
+        this.validationRepository = validationPolicyRepository;
+        this.dataValidator = dataValidator;
+
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updatePreferences(final JsonCommand command) {
+
+        this.dataValidator.validateForUpdate(command.json());
+        Long validationPolicyId = command.longValueOfParameterNamed(PasswordPreferencesApiConstants.VALIDATION_POLICY_ID);
+        try {
+            final List<PasswordValidationPolicy> validationPolicies = this.validationRepository.findAll();
+
+            Map<String, Object> changes = new HashMap<>(1);
+
+            boolean found = false;
+
+            for (PasswordValidationPolicy policy : validationPolicies) {
+                if (policy.getId().equals(validationPolicyId)) {
+                    found = true;
+                    if (!policy.isActive()) {
+                        changes = policy.activate();
+                    }
+                } else if (policy.isActive() && !policy.getId().equals(validationPolicyId)) {
+                    policy.deActivate();
+                }
+            }
+
+            if (!found) { throw new PasswordValidationPolicyNotFoundException(validationPolicyId); }
+
+            if (!changes.isEmpty()) {
+                this.validationRepository.save(validationPolicies);
+                this.validationRepository.flush();
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            logger.error(dve.getMessage(), dve);
+            throw new PlatformDataIntegrityException("error.msg.password.validation.policy.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordValidationPolicyReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordValidationPolicyReadPlatformService.java
new file mode 100644
index 0000000..e746101
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordValidationPolicyReadPlatformService.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.useradministration.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.useradministration.data.PasswordValidationPolicyData;
+
+public interface PasswordValidationPolicyReadPlatformService {
+
+    Collection<PasswordValidationPolicyData> retrieveAll();
+
+    PasswordValidationPolicyData retrieveActiveValidationPolicy();
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordValidationPolicyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordValidationPolicyReadPlatformServiceImpl.java
new file mode 100644
index 0000000..cd7dab9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PasswordValidationPolicyReadPlatformServiceImpl.java
@@ -0,0 +1,83 @@
+/**
+ * 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.useradministration.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.useradministration.data.PasswordValidationPolicyData;
+import org.apache.fineract.useradministration.exception.PasswordValidationPolicyNotFoundException;
+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 PasswordValidationPolicyReadPlatformServiceImpl implements PasswordValidationPolicyReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PasswordValidationPolicyMapper passwordValidationPolicyMapper;
+
+    @Autowired
+    public PasswordValidationPolicyReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.passwordValidationPolicyMapper = new PasswordValidationPolicyMapper();
+    }
+
+    @Override
+    public Collection<PasswordValidationPolicyData> retrieveAll() {
+        final String sql = "select " + this.passwordValidationPolicyMapper.schema() + " order by pvp.id";
+
+        return this.jdbcTemplate.query(sql, this.passwordValidationPolicyMapper);
+    }
+
+    @Override
+    public PasswordValidationPolicyData retrieveActiveValidationPolicy() {
+        try {
+            final String sql = "select " + this.passwordValidationPolicyMapper.schema() + " where pvp.active = true";
+            return this.jdbcTemplate.queryForObject(sql, this.passwordValidationPolicyMapper);
+        } catch (final EmptyResultDataAccessException e) {
+            throw new PasswordValidationPolicyNotFoundException();
+        }
+    }
+
+    protected static final class PasswordValidationPolicyMapper implements RowMapper<PasswordValidationPolicyData> {
+
+        @Override
+        public PasswordValidationPolicyData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final Boolean active = rs.getBoolean("active");
+            final String description = rs.getString("description");
+            final String key = rs.getString("key");
+
+            return new PasswordValidationPolicyData(id, active, description, key);
+        }
+
+        public String schema() {
+            return " pvp.id as id, pvp.active as active, pvp.description as description, pvp.`key` as `key`"
+            		+ " from m_password_validation_policy pvp";
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionReadPlatformService.java
new file mode 100644
index 0000000..4004ee2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionReadPlatformService.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.useradministration.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.useradministration.data.PermissionData;
+
+public interface PermissionReadPlatformService {
+
+    Collection<PermissionData> retrieveAllPermissions();
+
+    Collection<PermissionData> retrieveAllMakerCheckerablePermissions();
+
+    Collection<PermissionData> retrieveAllRolePermissions(Long roleId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionReadPlatformServiceImpl.java
new file mode 100644
index 0000000..ea16860
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionReadPlatformServiceImpl.java
@@ -0,0 +1,123 @@
+/**
+ * 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.useradministration.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.data.PermissionData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PermissionReadPlatformServiceImpl implements PermissionReadPlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(PermissionReadPlatformService.class);
+
+    private final JdbcTemplate jdbcTemplate;
+    private final PlatformSecurityContext context;
+
+    @Autowired
+    public PermissionReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) {
+        this.context = context;
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public Collection<PermissionData> retrieveAllPermissions() {
+
+        this.context.authenticatedUser();
+
+        final PermissionUsageDataMapper mapper = new PermissionUsageDataMapper();
+        final String sql = mapper.permissionSchema();
+        logger.info("retrieveAllPermissions: " + sql);
+        return this.jdbcTemplate.query(sql, mapper, new Object[] {});
+    }
+
+    @Override
+    public Collection<PermissionData> retrieveAllMakerCheckerablePermissions() {
+
+        this.context.authenticatedUser();
+
+        final PermissionUsageDataMapper mapper = new PermissionUsageDataMapper();
+        final String sql = mapper.makerCheckerablePermissionSchema();
+        logger.info("retrieveAllMakerCheckerablePermissions: " + sql);
+
+        return this.jdbcTemplate.query(sql, mapper, new Object[] {});
+    }
+
+    @Override
+    public Collection<PermissionData> retrieveAllRolePermissions(final Long roleId) {
+
+        final PermissionUsageDataMapper mapper = new PermissionUsageDataMapper();
+        final String sql = mapper.rolePermissionSchema();
+        logger.info("retrieveAllRolePermissions: " + sql);
+
+        return this.jdbcTemplate.query(sql, mapper, new Object[] { roleId });
+    }
+
+    private static final class PermissionUsageDataMapper implements RowMapper<PermissionData> {
+
+        @Override
+        public PermissionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final String grouping = rs.getString("grouping");
+            final String code = rs.getString("code");
+            final String entityName = rs.getString("entityName");
+            final String actionName = rs.getString("actionName");
+            final Boolean selected = rs.getBoolean("selected");
+
+            return PermissionData.instance(grouping, code, entityName, actionName, selected);
+        }
+
+        public String permissionSchema() {
+            /* get all non-CHECKER permissions */
+            return "select p.grouping, p.code, p.entity_name as entityName, p.action_name as actionName, true as selected"
+                    + " from m_permission p " + " where code not like '%\\_CHECKER'"
+                    + " order by p.grouping, ifnull(entity_name, ''), p.code";
+        }
+
+        public String makerCheckerablePermissionSchema() {
+            /*
+             * get all 'Maker-Checkerable' permissions - Maintenance permissions
+             * (i.e. exclude the 'special' grouping, the READ permissions and
+             * the CHECKER permissions
+             */
+
+            return "select p.grouping, p.code, p.entity_name as entityName, p.action_name as actionName, p.can_maker_checker as selected"
+                    + " from m_permission p " + " where grouping != 'special' and code not like 'READ_%' and code not like '%\\_CHECKER'"
+                    + " order by p.grouping, ifnull(entity_name, ''), p.code";
+        }
+
+        public String rolePermissionSchema() {
+            return "select p.grouping, p.code, p.entity_name as entityName, p.action_name as actionName, if(isnull(rp.role_id), false, true) as selected "
+                    + " from m_permission p "
+                    + " left join m_role_permission rp on rp.permission_id = p.id and rp.role_id = ? "
+                    + " order by p.grouping, ifnull(entity_name, ''), p.code";
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformService.java
new file mode 100644
index 0000000..6bb9e2b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformService.java
@@ -0,0 +1,27 @@
+/**
+ * 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.useradministration.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface PermissionWritePlatformService {
+
+    CommandProcessingResult updateMakerCheckerPermissions(JsonCommand command);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..fd5ef80
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,100 @@
+/**
+ * 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.useradministration.service;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.command.PermissionsCommand;
+import org.apache.fineract.useradministration.domain.Permission;
+import org.apache.fineract.useradministration.domain.PermissionRepository;
+import org.apache.fineract.useradministration.exception.PermissionNotFoundException;
+import org.apache.fineract.useradministration.serialization.PermissionsCommandFromApiJsonDeserializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class PermissionWritePlatformServiceJpaRepositoryImpl implements PermissionWritePlatformService {
+
+    private final PlatformSecurityContext context;
+    private final PermissionRepository permissionRepository;
+    private final PermissionsCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+
+    @Autowired
+    public PermissionWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
+            final PermissionRepository permissionRepository, final PermissionsCommandFromApiJsonDeserializer fromApiJsonDeserializer) {
+        this.context = context;
+        this.permissionRepository = permissionRepository;
+        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+    }
+
+    @Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
+    @Transactional
+    @Override
+    public CommandProcessingResult updateMakerCheckerPermissions(final JsonCommand command) {
+        this.context.authenticatedUser();
+
+        final Collection<Permission> allPermissions = this.permissionRepository.findAll();
+
+        final PermissionsCommand permissionsCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
+
+        final Map<String, Boolean> commandPermissions = permissionsCommand.getPermissions();
+        final Map<String, Object> changes = new HashMap<>();
+        final Map<String, Boolean> changedPermissions = new HashMap<>();
+        for (final String permissionCode : commandPermissions.keySet()) {
+
+            final Permission permission = findPermissionInCollectionByCode(allPermissions, permissionCode);
+
+            if (permission.getCode().endsWith("_CHECKER") || permission.getCode().startsWith("READ_")
+                    || permission.getGrouping().equalsIgnoreCase("special")) { throw new PermissionNotFoundException(permissionCode); }
+
+            final boolean isSelected = commandPermissions.get(permissionCode).booleanValue();
+            final boolean changed = permission.enableMakerChecker(isSelected);
+            if (changed) {
+                changedPermissions.put(permissionCode, isSelected);
+                this.permissionRepository.save(permission);
+            }
+        }
+
+        if (!changedPermissions.isEmpty()) {
+            changes.put("permissions", changedPermissions);
+        }
+
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).with(changes).build();
+    }
+
+    private Permission findPermissionInCollectionByCode(final Collection<Permission> allPermissions, final String permissionCode) {
+
+        if (allPermissions != null) {
+            for (final Permission permission : allPermissions) {
+                if (permission.hasCode(permissionCode)) { return permission; }
+            }
+        }
+
+        throw new PermissionNotFoundException(permissionCode);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleDataValidator.java
new file mode 100644
index 0000000..acc401e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleDataValidator.java
@@ -0,0 +1,103 @@
+/**
+ * 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.useradministration.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 RoleDataValidator {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("id", "name", "description"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public RoleDataValidator(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("role");
+
+        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("description", element);
+        baseDataValidator.reset().parameter("description").value(description).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("role");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists("name", element)) {
+            final String username = this.fromApiJsonHelper.extractStringNamed("name", element);
+            baseDataValidator.reset().parameter("name").value(username).notBlank().notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("description", element)) {
+            final String description = this.fromApiJsonHelper.extractStringNamed("description", element);
+            baseDataValidator.reset().parameter("description").value(description).notBlank().notExceedingLengthOf(500);
+        }
+
+        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/useradministration/service/RoleReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleReadPlatformService.java
new file mode 100755
index 0000000..9e3a26b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleReadPlatformService.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.useradministration.service;
+
+import org.apache.fineract.useradministration.data.RoleData;
+
+import java.util.Collection;
+
+public interface RoleReadPlatformService {
+
+    Collection<RoleData> retrieveAll();
+
+    Collection<RoleData> retrieveAllActiveRoles();
+
+    RoleData retrieveOne(Long roleId);
+
+    Collection<RoleData> retrieveAppUserRoles(Long appUserId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleReadPlatformServiceImpl.java
new file mode 100755
index 0000000..566393b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleReadPlatformServiceImpl.java
@@ -0,0 +1,98 @@
+/**
+ * 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.useradministration.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.useradministration.data.RoleData;
+import org.apache.fineract.useradministration.exception.RoleNotFoundException;
+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 RoleReadPlatformServiceImpl implements RoleReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final RoleMapper roleRowMapper;
+
+    @Autowired
+    public RoleReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.roleRowMapper = new RoleMapper();
+    }
+
+    @Override
+    public Collection<RoleData> retrieveAll() {
+        final String sql = "select " + this.roleRowMapper.schema() + " order by r.id";
+
+        return this.jdbcTemplate.query(sql, this.roleRowMapper);
+    }
+
+    @Override
+    public Collection<RoleData> retrieveAllActiveRoles() {
+        final String sql = "select " + this.roleRowMapper.schema() + " where r.is_disabled = 0 order by r.id";
+
+        return this.jdbcTemplate.query(sql, this.roleRowMapper);
+    }
+
+    @Override
+    public RoleData retrieveOne(final Long id) {
+
+        try {
+            final String sql = "select " + this.roleRowMapper.schema() + " where r.id=?";
+
+            return this.jdbcTemplate.queryForObject(sql, this.roleRowMapper, new Object[] { id });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new RoleNotFoundException(id);
+        }
+    }
+
+    protected static final class RoleMapper implements RowMapper<RoleData> {
+
+        @Override
+        public RoleData 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 description = rs.getString("description");
+            final Boolean disabled = rs.getBoolean("disabled");
+            
+            return new RoleData(id, name, description, disabled);
+        }
+
+        public String schema() {
+            return " r.id as id, r.name as name, r.description as description, r.is_disabled as disabled from m_role r";
+        }
+    }
+
+    @Override
+    public Collection<RoleData> retrieveAppUserRoles(final Long appUserId) {
+        final String sql = "select " + this.roleRowMapper.schema() + " inner join m_appuser_role"
+                + " ar on ar.role_id = r.id where ar.appuser_id= ?";
+
+        return this.jdbcTemplate.query(sql, this.roleRowMapper, new Object[] { appUserId });
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformService.java
new file mode 100644
index 0000000..31b4cd6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformService.java
@@ -0,0 +1,37 @@
+/**
+ * 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.useradministration.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface RoleWritePlatformService {
+
+    CommandProcessingResult createRole(JsonCommand command);
+
+    CommandProcessingResult updateRole(Long roleId, JsonCommand command);
+
+    CommandProcessingResult updateRolePermissions(Long roleId, JsonCommand command);
+
+    CommandProcessingResult deleteRole(Long roleId);
+
+    CommandProcessingResult disableRole(Long roleId);
+
+    CommandProcessingResult enableRole(Long roleId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..21fa9e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,275 @@
+/**
+ * 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.useradministration.service;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.command.PermissionsCommand;
+import org.apache.fineract.useradministration.domain.Permission;
+import org.apache.fineract.useradministration.domain.PermissionRepository;
+import org.apache.fineract.useradministration.domain.Role;
+import org.apache.fineract.useradministration.domain.RoleRepository;
+import org.apache.fineract.useradministration.exception.PermissionNotFoundException;
+import org.apache.fineract.useradministration.exception.RoleAssociatedException;
+import org.apache.fineract.useradministration.exception.RoleNotFoundException;
+import org.apache.fineract.useradministration.serialization.PermissionsCommandFromApiJsonDeserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class RoleWritePlatformServiceJpaRepositoryImpl implements RoleWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(RoleWritePlatformServiceJpaRepositoryImpl.class);
+    private final PlatformSecurityContext context;
+    private final RoleRepository roleRepository;
+    private final PermissionRepository permissionRepository;
+    private final RoleDataValidator roleCommandFromApiJsonDeserializer;
+    private final PermissionsCommandFromApiJsonDeserializer permissionsFromApiJsonDeserializer;
+
+    @Autowired
+    public RoleWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final RoleRepository roleRepository,
+            final PermissionRepository permissionRepository, final RoleDataValidator roleCommandFromApiJsonDeserializer,
+            final PermissionsCommandFromApiJsonDeserializer fromApiJsonDeserializer) {
+        this.context = context;
+        this.roleRepository = roleRepository;
+        this.permissionRepository = permissionRepository;
+        this.roleCommandFromApiJsonDeserializer = roleCommandFromApiJsonDeserializer;
+        this.permissionsFromApiJsonDeserializer = fromApiJsonDeserializer;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createRole(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.roleCommandFromApiJsonDeserializer.validateForCreate(command.json());
+
+            final Role entity = Role.fromJson(command);
+            this.roleRepository.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.role.duplicate.name", "Role with name `" + name + "` already exists",
+                    "name", name);
+        }
+
+        logAsErrorUnexpectedDataIntegrityException(dve);
+        throw new PlatformDataIntegrityException("error.msg.role.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
+        logger.error(dve.getMessage(), dve);
+    }
+
+    @Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
+    @Transactional
+    @Override
+    public CommandProcessingResult updateRole(final Long roleId, final JsonCommand command) {
+        try {
+            this.context.authenticatedUser();
+
+            this.roleCommandFromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final Role role = this.roleRepository.findOne(roleId);
+            if (role == null) { throw new RoleNotFoundException(roleId); }
+
+            final Map<String, Object> changes = role.update(command);
+            if (!changes.isEmpty()) {
+                this.roleRepository.saveAndFlush(role);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(roleId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .build();
+        }
+    }
+
+    @Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
+    @Transactional
+    @Override
+    public CommandProcessingResult updateRolePermissions(final Long roleId, final JsonCommand command) {
+        this.context.authenticatedUser();
+
+        final Role role = this.roleRepository.findOne(roleId);
+        if (role == null) { throw new RoleNotFoundException(roleId); }
+
+        final Collection<Permission> allPermissions = this.permissionRepository.findAll();
+
+        final PermissionsCommand permissionsCommand = this.permissionsFromApiJsonDeserializer.commandFromApiJson(command.json());
+
+        final Map<String, Boolean> commandPermissions = permissionsCommand.getPermissions();
+        final Map<String, Object> changes = new HashMap<>();
+        final Map<String, Boolean> changedPermissions = new HashMap<>();
+        for (final String permissionCode : commandPermissions.keySet()) {
+            final boolean isSelected = commandPermissions.get(permissionCode).booleanValue();
+
+            final Permission permission = findPermissionByCode(allPermissions, permissionCode);
+            final boolean changed = role.updatePermission(permission, isSelected);
+            if (changed) {
+                changedPermissions.put(permissionCode, isSelected);
+            }
+        }
+
+        if (!changedPermissions.isEmpty()) {
+            changes.put("permissions", changedPermissions);
+            this.roleRepository.save(role);
+        }
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(roleId) //
+                .with(changes) //
+                .build();
+    }
+
+    private Permission findPermissionByCode(final Collection<Permission> allPermissions, final String permissionCode) {
+
+        if (allPermissions != null) {
+            for (final Permission permission : allPermissions) {
+                if (permission.hasCode(permissionCode)) { return permission; }
+            }
+        }
+        throw new PermissionNotFoundException(permissionCode);
+    }
+
+    /**
+     * Method for Delete Role
+     */
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteRole(Long roleId) {
+
+        try {
+            /**
+             * Checking the role present in DB or not using role_id
+             */
+            final Role role = this.roleRepository.findOne(roleId);
+            if (role == null) { throw new RoleNotFoundException(roleId); }
+            
+            /**
+             * Roles associated with users can't be deleted
+             */
+            final Integer count = this.roleRepository.getCountOfRolesAssociatedWithUsers(roleId);
+            if (count > 0) { throw new RoleAssociatedException("error.msg.role.associated.with.users.deleted", roleId); }
+            
+            this.roleRepository.delete(role);
+            return new CommandProcessingResultBuilder().withEntityId(roleId).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 role
+     */
+    @Transactional
+    @Override
+    public CommandProcessingResult disableRole(Long roleId) {
+        try {
+            /**
+             * Checking the role present in DB or not using role_id
+             */
+            final Role role = this.roleRepository.findOne(roleId);
+            if (role == null) { throw new RoleNotFoundException(roleId); }
+            //if(role.isDisabled()){throw new RoleNotFoundException(roleId);}
+            
+            /**
+             * Roles associated with users can't be disable
+             */
+            final Integer count = this.roleRepository.getCountOfRolesAssociatedWithUsers(roleId);
+            if (count > 0) { throw new RoleAssociatedException("error.msg.role.associated.with.users.disabled", roleId); }
+            
+            /**
+             * Disabling the role
+             */
+            role.disableRole();
+            this.roleRepository.save(role);
+            return new CommandProcessingResultBuilder().withEntityId(roleId).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 role
+     */
+    @Transactional
+    @Override
+    public CommandProcessingResult enableRole(Long roleId) {
+        try {
+            /**
+             * Checking the role present in DB or not using role_id
+             */
+            final Role role = this.roleRepository.findOne(roleId);
+            if (role == null) { throw new RoleNotFoundException(roleId); }
+            //if(!role.isEnabled()){throw new RoleNotFoundException(roleId);}
+            
+            role.enableRole();
+            this.roleRepository.save(role);
+            return new CommandProcessingResultBuilder().withEntityId(roleId).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/useradministration/service/UserDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/UserDataValidator.java
new file mode 100644
index 0000000..57a72c7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/UserDataValidator.java
@@ -0,0 +1,242 @@
+/**
+ * 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.useradministration.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.apache.fineract.useradministration.domain.PasswordValidationPolicy;
+import org.apache.fineract.useradministration.domain.PasswordValidationPolicyRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public final class UserDataValidator {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("username", "firstname", "lastname", "password",
+            "repeatPassword", "email", "officeId", "notSelectedRoles", "roles", "sendPasswordToEmail", "staffId", "passwordNeverExpires",
+            AppUserConstants.IS_SELF_SERVICE_USER, AppUserConstants.CLIENTS));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    private final PasswordValidationPolicyRepository passwordValidationPolicy;
+
+    @Autowired
+    public UserDataValidator(final FromJsonHelper fromApiJsonHelper, final PasswordValidationPolicyRepository passwordValidationPolicy) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.passwordValidationPolicy = passwordValidationPolicy;
+    }
+
+    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("user");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String username = this.fromApiJsonHelper.extractStringNamed("username", element);
+        baseDataValidator.reset().parameter("username").value(username).notBlank().notExceedingLengthOf(100);
+
+        final String firstname = this.fromApiJsonHelper.extractStringNamed("firstname", element);
+        baseDataValidator.reset().parameter("firstname").value(firstname).notBlank().notExceedingLengthOf(100);
+
+        final String lastname = this.fromApiJsonHelper.extractStringNamed("lastname", element);
+        baseDataValidator.reset().parameter("lastname").value(lastname).notBlank().notExceedingLengthOf(100);
+
+        final Boolean sendPasswordToEmail = this.fromApiJsonHelper.extractBooleanNamed("sendPasswordToEmail", element);
+        if (sendPasswordToEmail != null) {
+            if (sendPasswordToEmail.booleanValue()) {
+                final String email = this.fromApiJsonHelper.extractStringNamed("email", element);
+                baseDataValidator.reset().parameter("email").value(email).notBlank().notExceedingLengthOf(100);
+            } else {
+                final String password = this.fromApiJsonHelper.extractStringNamed("password", element);
+                final String repeatPassword = this.fromApiJsonHelper.extractStringNamed("repeatPassword", element);
+                final PasswordValidationPolicy validationPolicy = this.passwordValidationPolicy.findActivePasswordValidationPolicy();
+                final String regex = validationPolicy.getRegex();
+                final String description = validationPolicy.getDescription();
+                baseDataValidator.reset().parameter("password").value(password).matchesRegularExpression(regex,description);
+
+                if (StringUtils.isNotBlank(password)) {
+                    baseDataValidator.reset().parameter("password").value(password).equalToParameter("repeatPassword", repeatPassword);
+                }
+            }
+        } else {
+            baseDataValidator.reset().parameter("sendPasswordToEmail").value(sendPasswordToEmail).trueOrFalseRequired(false);
+        }
+
+        final Long officeId = this.fromApiJsonHelper.extractLongNamed("officeId", element);
+        baseDataValidator.reset().parameter("officeId").value(officeId).notNull().integerGreaterThanZero();
+
+        if (this.fromApiJsonHelper.parameterExists("staffId", element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed("staffId", element);
+            baseDataValidator.reset().parameter("staffId").value(staffId).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists(AppUserConstants.PASSWORD_NEVER_EXPIRES, element)) {
+            final boolean passwordNeverExpire = this.fromApiJsonHelper
+                    .extractBooleanNamed(AppUserConstants.PASSWORD_NEVER_EXPIRES, element);
+            baseDataValidator.reset().parameter("passwordNeverExpire").value(passwordNeverExpire).validateForBooleanValue();
+        }
+        
+        Boolean isSelfServiceUser = null;
+        if(this.fromApiJsonHelper.parameterExists(AppUserConstants.IS_SELF_SERVICE_USER, element)){
+        	isSelfServiceUser = this.fromApiJsonHelper.extractBooleanNamed(AppUserConstants.IS_SELF_SERVICE_USER, element);
+        	if(isSelfServiceUser == null){
+        		baseDataValidator.reset().parameter(AppUserConstants.IS_SELF_SERVICE_USER).trueOrFalseRequired(false);
+        	}
+        }
+        
+        if(this.fromApiJsonHelper.parameterExists(AppUserConstants.CLIENTS, element)){
+        	if(isSelfServiceUser == null || !isSelfServiceUser){
+        		baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).failWithCode("not.supported.when.isSelfServiceUser.is.false",
+        				"clients parameter is not supported when isSelfServiceUser parameter is false");
+        	}else{
+            	final JsonArray clientsArray = this.fromApiJsonHelper.extractJsonArrayNamed(AppUserConstants.CLIENTS, element);
+           		baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).value(clientsArray).jsonArrayNotEmpty();
+
+            	for(JsonElement client : clientsArray){
+            		Long clientId = client.getAsLong();
+            		baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).value(clientId).longGreaterThanZero();
+            	}
+        	}
+        }
+
+        final String[] roles = this.fromApiJsonHelper.extractArrayNamed("roles", element);
+        baseDataValidator.reset().parameter("roles").value(roles).arrayNotEmpty();
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(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("user");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists("officeId", element)) {
+            final Long officeId = this.fromApiJsonHelper.extractLongNamed("officeId", element);
+            baseDataValidator.reset().parameter("officeId").value(officeId).notNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("staffId", element)) {
+            final Long staffId = this.fromApiJsonHelper.extractLongNamed("staffId", element);
+            baseDataValidator.reset().parameter("staffId").value(staffId).ignoreIfNull().integerGreaterThanZero();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("username", element)) {
+            final String username = this.fromApiJsonHelper.extractStringNamed("username", element);
+            baseDataValidator.reset().parameter("username").value(username).notBlank().notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("firstname", element)) {
+            final String firstname = this.fromApiJsonHelper.extractStringNamed("firstname", element);
+            baseDataValidator.reset().parameter("firstname").value(firstname).notBlank().notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("lastname", element)) {
+            final String lastname = this.fromApiJsonHelper.extractStringNamed("lastname", element);
+            baseDataValidator.reset().parameter("lastname").value(lastname).notBlank().notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("email", element)) {
+            final String email = this.fromApiJsonHelper.extractStringNamed("email", element);
+            baseDataValidator.reset().parameter("email").value(email).notBlank().notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("roles", element)) {
+            final String[] roles = this.fromApiJsonHelper.extractArrayNamed("roles", element);
+            baseDataValidator.reset().parameter("roles").value(roles).arrayNotEmpty();
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("password", element)) {
+            final String password = this.fromApiJsonHelper.extractStringNamed("password", element);
+            final String repeatPassword = this.fromApiJsonHelper.extractStringNamed("repeatPassword", element);
+
+            final PasswordValidationPolicy validationPolicy = this.passwordValidationPolicy.findActivePasswordValidationPolicy();
+            final String regex = validationPolicy.getRegex();
+            final String description = validationPolicy.getDescription();
+            baseDataValidator.reset().parameter("password").value(password).matchesRegularExpression(regex,description);
+
+            if (StringUtils.isNotBlank(password)) {
+                baseDataValidator.reset().parameter("password").value(password).equalToParameter("repeatPassword", repeatPassword);
+            }
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("passwordNeverExpire", element)) {
+            final boolean passwordNeverExpire = this.fromApiJsonHelper.extractBooleanNamed("passwordNeverExpire", element);
+            baseDataValidator.reset().parameter("passwordNeverExpire").value(passwordNeverExpire).validateForBooleanValue();
+        }
+        
+        Boolean isSelfServiceUser = null;
+        if(this.fromApiJsonHelper.parameterExists(AppUserConstants.IS_SELF_SERVICE_USER, element)){
+        	isSelfServiceUser = this.fromApiJsonHelper.extractBooleanNamed(AppUserConstants.IS_SELF_SERVICE_USER, element);
+        	if(isSelfServiceUser == null){
+        		baseDataValidator.reset().parameter(AppUserConstants.IS_SELF_SERVICE_USER).trueOrFalseRequired(false);
+        	}
+        }
+        
+        if(this.fromApiJsonHelper.parameterExists(AppUserConstants.CLIENTS, element)){
+        	if(isSelfServiceUser != null && !isSelfServiceUser){
+        		baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).failWithCode("not.supported.when.isSelfServiceUser.is.false",
+        				"clients parameter is not supported when isSelfServiceUser parameter is false");
+        	}else{
+            	final JsonArray clientsArray = this.fromApiJsonHelper.extractJsonArrayNamed(AppUserConstants.CLIENTS, element);
+           		baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).value(clientsArray).jsonArrayNotEmpty();
+
+            	for(JsonElement client : clientsArray){
+            		Long clientId = client.getAsLong();
+            		baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).value(clientId).longGreaterThanZero();
+            	}
+        	}
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/META-INF/orm.xml b/fineract-provider/src/main/resources/META-INF/orm.xml
new file mode 100644
index 0000000..b28a1bd
--- /dev/null
+++ b/fineract-provider/src/main/resources/META-INF/orm.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    This Source Code Form is subject to the terms of the Mozilla Public
+    License, v. 2.0. If a copy of the MPL was not distributed with this file,
+    You can obtain one at http://mozilla.org/MPL/2.0/.
+
+-->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public 
+	License, v. 2.0. If a copy of the MPL was not distributed with this file, 
+	You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
+	version="2.0">
+
+	<persistence-unit-metadata>
+		<persistence-unit-defaults>
+			<entity-listeners>
+				<entity-listener
+					class="org.springframework.data.jpa.domain.support.AuditingEntityListener" />
+			</entity-listeners>
+		</persistence-unit-defaults>
+	</persistence-unit-metadata>
+
+</entity-mappings>
diff --git a/fineract-provider/src/main/resources/META-INF/persistence.xml b/fineract-provider/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 0000000..0b2352d
--- /dev/null
+++ b/fineract-provider/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    This Source Code Form is subject to the terms of the Mozilla Public
+    License, v. 2.0. If a copy of the MPL was not distributed with this file,
+    You can obtain one at http://mozilla.org/MPL/2.0/.
+
+-->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public 
+	License, v. 2.0. If a copy of the MPL was not distributed with this file, 
+	You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<persistence version="2.0"
+	xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
+
+	<persistence-unit name="jpa-pu" transaction-type="RESOURCE_LOCAL">
+		<provider>org.hibernate.ejb.HibernatePersistence</provider>
+		<properties>
+			<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect" />
+			<property name="hibernate.jdbc.batch_size" value="100" />
+			<property name="hibernate.order_inserts" value="true" />
+		</properties>
+	</persistence-unit>
+</persistence>
\ 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
new file mode 100644
index 0000000..9984926
--- /dev/null
+++ b/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
+	xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:sec="http://www.springframework.org/schema/security"
+	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:cache="http://www.springframework.org/schema/cache"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
+		http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
+		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
+		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
+		http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
+
+	<sec:global-method-security
+		pre-post-annotations="enabled" />
+
+	<tx:annotation-driven />
+
+	<context:component-scan
+		base-package="org.apache.fineract.accounting.*,
+									      org.apache.fineract.commands.provider.*,
+									      org.apache.fineract.commands.handler.*,
+										  org.apache.fineract.commands.service.*,
+										  org.apache.fineract.commands.*,
+										  org.apache.fineract.audit.*,
+										  org.apache.fineract.infrastructure.*,
+										  org.apache.fineract.scheduledjobs.*,
+										  org.apache.fineract.organisation.*,
+										  org.apache.fineract.portfolio.loanaccount.*,
+										  org.apache.fineract.portfolio.savingsaccount.*,
+										  org.apache.fineract.portfolio.*,
+										  org.apache.fineract.useradministration.*,
+										  org.apache.fineract.mix.*,
+										  org.apache.fineract.template.*,
+										  org.apache.fineract.template.service.*,
+										  org.apache.fineract.useradministration.*,
+										  org.apache.fineract.batch">
+		<context:exclude-filter expression="org.springframework.stereotype.Controller"
+			type="annotation" />
+
+		<!-- We do NOT want all @Configuration "beans" to be auto-detected by ComponentScan,
+			 but we want to use / import them explicitly in Tests & Spring Boot applications,
+			 or other import in other @Configuration, so that we could have mutually exclusive ones.
+		 -->
+		<context:exclude-filter expression="org.springframework.context.annotation.Configuration"
+			type="annotation" />
+	</context:component-scan>
+
+	<bean id="auditorAware"
+		class="org.apache.fineract.infrastructure.core.domain.AuditorAwareImpl" />
+	<jpa:auditing auditor-aware-ref="auditorAware" />
+
+	<jpa:repositories base-package="org.apache.fineract.commands.domain" />
+	<jpa:repositories base-package="org.apache.fineract.infrastructure.*.domain" />
+	<jpa:repositories base-package="org.apache.fineract.accounting.*.domain" />
+	<jpa:repositories base-package="org.apache.fineract.useradministration.domain" />
+	<jpa:repositories base-package="org.apache.fineract.organisation.*.domain" />
+	<jpa:repositories base-package="org.apache.fineract.portfolio.*" />
+	<jpa:repositories base-package="org.apache.fineract.mix.domain" />
+	<jpa:repositories base-package="org.apache.fineract.scheduledjobs.domain" />
+	<jpa:repositories base-package="org.apache.fineract.template.domain" />
+	
+	<import resource="infrastructure.xml" />
+
+	<import resource="securityContext.xml" />
+
+	<import resource="cache.xml" />
+
+	<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
+		<property name="taskExecutor">
+			<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
+		</property>
+	</bean>
+
+	<import resource="spmContext.xml"/>
+</beans>
diff --git a/fineract-provider/src/main/resources/META-INF/spring/cache.xml b/fineract-provider/src/main/resources/META-INF/spring/cache.xml
new file mode 100644
index 0000000..460f045
--- /dev/null
+++ b/fineract-provider/src/main/resources/META-INF/spring/cache.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    This Source Code Form is subject to the terms of the Mozilla Public
+    License, v. 2.0. If a copy of the MPL was not distributed with this file,
+    You can obtain one at http://mozilla.org/MPL/2.0/.
+
+-->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public 
+	License, v. 2.0. If a copy of the MPL was not distributed with this file, 
+	You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
+	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
+	xmlns:cache="http://www.springframework.org/schema/cache"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
+		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
+		http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
+
+	<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
+		p:cacheManager-ref="ehcacheInstance" />
+
+	<bean id="ehcacheInstance"
+		class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
+		p:configLocation="classpath:/META-INF/spring/ehcache.xml" p:shared="true" />
+
+</beans>
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/META-INF/spring/ehcache.xml b/fineract-provider/src/main/resources/META-INF/spring/ehcache.xml
new file mode 100644
index 0000000..c4601f9
--- /dev/null
+++ b/fineract-provider/src/main/resources/META-INF/spring/ehcache.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    This Source Code Form is subject to the terms of the Mozilla Public
+    License, v. 2.0. If a copy of the MPL was not distributed with this file,
+    You can obtain one at http://mozilla.org/MPL/2.0/.
+
+-->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public 
+	License, v. 2.0. If a copy of the MPL was not distributed with this file, 
+	You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
+	<defaultCache eternal="true" maxEntriesLocalHeap="100"
+		overflowToDisk="false" />
+
+	<cache name="users" maxEntriesLocalHeap="10000" eternal="true"
+		overflowToDisk="false" />
+	<cache name="usersByUsername" maxEntriesLocalHeap="10000"
+		eternal="true" overflowToDisk="false" />
+	<cache name="tenantsById" maxEntriesLocalHeap="10000" eternal="true"
+		overflowToDisk="false" />
+	<cache name="offices" maxEntriesLocalHeap="10000" eternal="true"
+		overflowToDisk="false" />
+	<cache name="officesForDropdown" maxEntriesLocalHeap="10000"
+		eternal="true" overflowToDisk="false" />
+	<cache name="officesById" maxEntriesLocalHeap="10000" eternal="true"
+		overflowToDisk="false" />
+	<cache name="charges" maxEntriesLocalHeap="10000" eternal="true"
+		overflowToDisk="false" />
+	<cache name="funds" maxEntriesLocalHeap="10000" eternal="true"
+		overflowToDisk="false" />
+	<cache name="code_values" maxEntriesLocalHeap="10000" eternal="true"
+		overflowToDisk="false" />
+	<cache name="codes" maxEntriesLocalHeap="10000" eternal="true"
+		overflowToDisk="false" />
+	<cache name="hooks" maxEntriesLocalHeap="10000" eternal="true"
+		overflowToDisk="false" />
+</ehcache>
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/META-INF/spring/infrastructure.xml b/fineract-provider/src/main/resources/META-INF/spring/infrastructure.xml
new file mode 100644
index 0000000..95f2831
--- /dev/null
+++ b/fineract-provider/src/main/resources/META-INF/spring/infrastructure.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    This Source Code Form is subject to the terms of the Mozilla Public
+    License, v. 2.0. If a copy of the MPL was not distributed with this file,
+    You can obtain one at http://mozilla.org/MPL/2.0/.
+
+-->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public 
+	License, v. 2.0. If a copy of the MPL was not distributed with this file, 
+	You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+	xmlns:jee="http://www.springframework.org/schema/jee"
+	xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
+    	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+    	http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
+
+	<bean id="entityManagerFactory"
+		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
+		depends-on="tenantDatabaseUpgradeService">
+		<property name="dataSource" ref="routingDataSource" />
+		<property name="persistenceUnitName" value="jpa-pu" />
+		<property name="jpaVendorAdapter">
+			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
+				<property name="database" value="MYSQL" />
+				<property name="showSql" value="false" />
+				<property name="generateDdl" value="false" />
+				<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
+			</bean>
+		</property>
+	</bean>
+
+	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
+		<property name="entityManagerFactory" ref="entityManagerFactory" />
+	</bean>
+	
+	<!-- Create instance of transaction template for programmatic transaction manipulation -->
+	<bean id="txTemplate" class="org.springframework.transaction.support.TransactionTemplate">
+		<property name="transactionManager" ref="transactionManager"></property>
+	</bean>	
+
+</beans>
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/META-INF/spring/jndi.xml b/fineract-provider/src/main/resources/META-INF/spring/jndi.xml
new file mode 100644
index 0000000..595b28f
--- /dev/null
+++ b/fineract-provider/src/main/resources/META-INF/spring/jndi.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    This Source Code Form is subject to the terms of the Mozilla Public
+    License, v. 2.0. If a copy of the MPL was not distributed with this file,
+    You can obtain one at http://mozilla.org/MPL/2.0/.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+	xmlns:jee="http://www.springframework.org/schema/jee"
+	xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
+    	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+    	http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
+
+	<!-- name here must match TestDataSourceConfiguration -->
+	<jee:jndi-lookup jndi-name="java:comp/env/jdbc/mifosplatform-tenants"
+		id="tenantDataSourceJndi" />
+</beans>
diff --git a/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml b/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
new file mode 100644
index 0000000..ee4fb6d
--- /dev/null
+++ b/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
+	xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
+	xsi:schemaLocation="
+   http://www.springframework.org/schema/beans
+   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
+   http://www.springframework.org/schema/security/oauth2
+   http://www.springframework.org/schema/security/spring-security-oauth2.xsd
+   http://www.springframework.org/schema/security
+   http://www.springframework.org/schema/security/spring-security-3.2.xsd">
+
+	<beans:beans profile="basicauth"> 
+		<http create-session="stateless" use-expressions="true" pattern="/api/**"
+			entry-point-ref="basicAuthenticationEntryPoint">
+			<intercept-url pattern="/api/*/authentication" access="permitAll"
+				method="POST" requires-channel="https" />
+			<intercept-url pattern="/api/*/self/authentication" access="permitAll"
+				method="POST" requires-channel="https" />
+			<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
+				method="GET" requires-channel="https" />
+			<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
+				method="POST" requires-channel="https" />
+			<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
+				method="PUT" requires-channel="https" />
+			<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
+				method="DELETE" requires-channel="https" />
+			<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
+				method="HEAD" requires-channel="https" />
+	
+			<custom-filter after="SECURITY_CONTEXT_FILTER"
+				ref="basicAuthenticationProcessingFilter" />
+		</http>
+	
+		<beans:bean id="basicAuthenticationEntryPoint"
+			class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
+			<beans:property name="realmName" value="Fineract Platform API" />
+		</beans:bean>
+	
+		<beans:bean id="passwordEncoder"
+			class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
+			<beans:constructor-arg value="256" />
+		</beans:bean>
+	
+		<beans:bean id="saltSource"
+			class="org.springframework.security.authentication.dao.ReflectionSaltSource">
+			<beans:property name="userPropertyToUse" value="id" />
+		</beans:bean>
+	
+		<beans:bean id="customAuthenticationProvider"
+			class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
+			<beans:property name="userDetailsService" ref="userDetailsService" />
+			<beans:property name="passwordEncoder" ref="passwordEncoder" />
+			<beans:property name="saltSource" ref="saltSource" />
+		</beans:bean>
+	
+		<authentication-manager alias="authenticationManager"
+			erase-credentials="false">
+			<authentication-provider ref="customAuthenticationProvider" />
+		</authentication-manager>
+	</beans:beans> 
+	<beans:beans profile="oauth">
+		<http create-session="stateless" use-expressions="true" pattern="/api/v1/**"
+			entry-point-ref="oauthAuthenticationEntryPoint"
+			access-decision-manager-ref="accessDecisionManager">
+			<anonymous enabled="false" />
+			<intercept-url pattern="/api//v1/**" method="OPTIONS"
+				access="permitAll" requires-channel="https" />
+			<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
+				method="GET" requires-channel="https" />
+			<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
+				method="POST" requires-channel="https" />
+			<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
+				method="PUT" requires-channel="https" />
+			<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
+				method="DELETE" requires-channel="https" />
+			<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
+				method="HEAD" requires-channel="https" />
+			<custom-filter ref="tenantIdentifierProcessingFilter"
+				position="FIRST" />
+			<custom-filter before="PRE_AUTH_FILTER" ref="resourceServerFilter" />
+			<access-denied-handler ref="oauthAccessDeniedHandler" />
+		</http>
+	
+		<http pattern="/api/oauth/token" create-session="stateless"
+			entry-point-ref="oauthAuthenticationEntryPoint" use-expressions="true"
+			authentication-manager-ref="clientAuthenticationManager">
+			<intercept-url pattern="/api/oauth/token" method="OPTIONS"
+				access="permitAll" requires-channel="https" />
+			<intercept-url pattern="/api/oauth/token" method="POST"
+				access="isFullyAuthenticated()" requires-channel="https" />
+			<anonymous enabled="false" />
+	
+			<custom-filter ref="tenantIdentifierProcessingFilter"
+				position="FIRST" />
+			<custom-filter ref="clientCredentialsTokenEndpointFilter"
+				before="BASIC_AUTH_FILTER" />
+			<access-denied-handler ref="oauthAccessDeniedHandler" />
+		</http>
+		<beans:bean id="oauthAuthenticationEntryPoint"
+			class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
+			<beans:property name="realmName" value="Fineract Platform API" />
+		</beans:bean>
+	
+		<beans:bean id="oauthAccessDeniedHandler"
+			class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
+	
+	
+		<beans:bean id="clientCredentialsTokenEndpointFilter"
+			class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
+			<beans:constructor-arg value="/api/oauth/token" />
+			<beans:property name="authenticationManager" ref="clientAuthenticationManager" />
+	
+		</beans:bean>
+	
+		<beans:bean id="accessDecisionManager"
+			class="org.springframework.security.access.vote.UnanimousBased">
+			<beans:constructor-arg>
+				<beans:list>
+					<beans:bean
+						class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
+					<beans:bean class="org.springframework.security.access.vote.RoleVoter" />
+					<beans:bean
+						class="org.springframework.security.access.vote.AuthenticatedVoter" />
+					<beans:bean
+						class="org.springframework.security.web.access.expression.WebExpressionVoter" />
+					<beans:bean
+						class="org.apache.fineract.infrastructure.security.vote.SelfServiceUserAccessVote" />
+				</beans:list>
+			</beans:constructor-arg>
+		</beans:bean>
+	
+	
+		<authentication-manager id="clientAuthenticationManager">
+			<authentication-provider user-service-ref="clientDetailsUserService" />
+		</authentication-manager>
+	
+		<authentication-manager alias="authenticationManager"
+			erase-credentials="false">
+			<authentication-provider ref="customAuthenticationProvider" />
+		</authentication-manager>
+	
+		<beans:bean id="clientDetailsUserService"
+			class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
+			<beans:constructor-arg ref="clientDetailsService" />
+		</beans:bean>
+	
+	
+		<beans:bean id="passwordEncoder"
+			class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
+			<beans:constructor-arg value="256" />
+		</beans:bean>
+	
+		<beans:bean id="saltSource"
+			class="org.springframework.security.authentication.dao.ReflectionSaltSource">
+			<beans:property name="userPropertyToUse" value="id" />
+		</beans:bean>
+	
+		<beans:bean id="customAuthenticationProvider"
+			class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
+			<beans:property name="userDetailsService" ref="userDetailsService" />
+			<beans:property name="passwordEncoder" ref="passwordEncoder" />
+			<beans:property name="saltSource" ref="saltSource" />
+		</beans:bean>
+	
+	
+	
+		<beans:bean id="clientDetailsService"
+			class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService">
+			<beans:constructor-arg ref="routingDataSource" />
+		</beans:bean>
+	
+	
+		<beans:bean id="tokenStore"
+			class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
+			<beans:constructor-arg ref="routingDataSource" />
+		</beans:bean>
+	
+		<beans:bean id="tokenServices"
+			class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
+			<beans:property name="tokenStore" ref="tokenStore" />
+			<beans:property name="clientDetailsService" ref="clientDetailsService" />
+			<beans:property name="supportRefreshToken" value="true" />
+			<beans:property name="refreshTokenValiditySeconds"
+				value="86400" />
+			<beans:property name="accessTokenValiditySeconds"
+				value="3600" />
+		</beans:bean>
+	
+		<beans:bean id="userApprovalHandler"
+			class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">
+			<beans:property name="tokenStore" ref="tokenStore" />
+			<beans:property name="clientDetailsService" ref="clientDetailsService" />
+			<beans:property name="requestFactory" ref="oAuth2RequestFactory" />
+		</beans:bean>
+	
+		<beans:bean id="oAuth2RequestFactory"
+			class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">
+			<beans:constructor-arg ref="clientDetailsService" />
+		</beans:bean>
+	
+		<oauth:authorization-server
+			client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
+			user-approval-handler-ref="userApprovalHandler" token-endpoint-url="/api/oauth/token"
+			authorization-endpoint-url="/api/oauth/authorize">
+			<oauth:refresh-token />
+			<oauth:password />
+		</oauth:authorization-server>
+	
+		<oauth:resource-server id="resourceServerFilter"
+			token-services-ref="tokenServices" />
+	</beans:beans>
+</beans:beans>
+
diff --git a/fineract-provider/src/main/resources/META-INF/spring/spmContext.xml b/fineract-provider/src/main/resources/META-INF/spring/spmContext.xml
new file mode 100644
index 0000000..4248110
--- /dev/null
+++ b/fineract-provider/src/main/resources/META-INF/spring/spmContext.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
+       xmlns:tx="http://www.springframework.org/schema/tx"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
+		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
+		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+    <tx:annotation-driven />
+
+    <context:component-scan base-package="org.apache.fineract.spm"/>
+
+    <jpa:repositories base-package="org.apache.fineract.spm.repository"/>
+
+</beans>
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties
new file mode 100644
index 0000000..adbef74
--- /dev/null
+++ b/fineract-provider/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+
+spring.profiles.default=basicauth
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/keystore.jks b/fineract-provider/src/main/resources/keystore.jks
new file mode 100644
index 0000000..b2455d0
--- /dev/null
+++ b/fineract-provider/src/main/resources/keystore.jks
Binary files differ
diff --git a/fineract-provider/src/main/resources/logback.xml b/fineract-provider/src/main/resources/logback.xml
new file mode 100644
index 0000000..365f98a
--- /dev/null
+++ b/fineract-provider/src/main/resources/logback.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+
+<configuration>
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<layout class="ch.qos.logback.classic.PatternLayout">
+			<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
+		</layout>
+	</appender>
+
+	<appender name="FILE" class="ch.qos.logback.core.FileAppender">
+		<file>${catalina.base}/logs/fineract-platform.log</file>
+		<append>true</append>
+		<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder 
+			by default -->
+		<encoder>
+			<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
+		</encoder>
+	</appender>
+
+
+	<logger name="org.fineract.platform" level="debug" />
+	<logger name="org.apache.fineract.infrastructure.security.filter"
+		level="info" />
+	<logger name="org.apache.fineract" level="debug" />
+	<logger name="org.springframework.web" level="info" />
+	<logger name="org.springframework.beans" level="info" />
+	<logger name="net.sf.ehcache" level="error" />
+	<logger name="org.hibernate.cache" level="error" />
+
+	<root level="info">
+		<appender-ref ref="STDOUT" />
+		<appender-ref ref="FILE" />
+	</root>
+</configuration>
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V100__Group_saving_summary_report.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V100__Group_saving_summary_report.sql
new file mode 100644
index 0000000..d98372b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V100__Group_saving_summary_report.sql
@@ -0,0 +1 @@
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('GroupSavingSummary', 'Table', NULL, NULL, 'select ifnull(cur.display_symbol, sa.currency_code) as currency,\ncount(sa.id) as totalSavingAccounts, ifnull(sum(sa.account_balance_derived),0) as totalSavings\nfrom m_group topgroup\njoin m_office o on o.id = topgroup.office_id and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_savings_account sa on sa.group_id = g.id\nleft join m_currency cur on cur.code = sa.currency_code\nwhere topgroup.id = ${groupId}\nand sa.activatedon_date is not null\ngroup by sa.currency_code', 'Utility query for getting group or center saving summary details for a group_id', 1, 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V101__add_mulitplesof_to_account_transfers_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V101__add_mulitplesof_to_account_transfers_table.sql
new file mode 100644
index 0000000..73a241d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V101__add_mulitplesof_to_account_transfers_table.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_savings_account_transfer`
+ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL DEFAULT NULL AFTER `currency_digits`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V102__client_attendance_tables.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V102__client_attendance_tables.sql
new file mode 100644
index 0000000..e55b58a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V102__client_attendance_tables.sql
@@ -0,0 +1,30 @@
+CREATE TABLE `m_meeting` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`calendar_instance_id` BIGINT(20) NOT NULL,
+	`meeting_date` DATE NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `unique_calendar_instance_id_meeting_date` (`calendar_instance_id`, `meeting_date`),
+	CONSTRAINT `FK_m_calendar_instance_m_meeting` FOREIGN KEY (`calendar_instance_id`) REFERENCES `m_calendar_instance` (`id`)
+)
+COLLATE='latin1_swedish_ci'
+ENGINE=InnoDB;
+
+CREATE TABLE `m_client_attendance` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`client_id` BIGINT(20) NOT NULL DEFAULT '0',
+	`meeting_id` BIGINT(20) NOT NULL,
+	`attendance_type_enum` SMALLINT(5) NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `unique_client_meeting_attendance` (`client_id`, `meeting_id`),
+	INDEX `FK_m_meeting_m_client_attendance` (`meeting_id`),
+	CONSTRAINT `FK_m_client_m_client_attendance` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+	CONSTRAINT `FK_m_meeting_m_client_attendance` FOREIGN KEY (`meeting_id`) REFERENCES `m_meeting` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_MEETING', 'MEETING', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_MEETING', 'MEETING', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_MEETING', 'MEETING', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'SAVEORUPDATEATTENDANCE_MEETING', 'MEETING', 'SAVEORUPDATEATTENDANCE', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V103__cluster_support_for_batch_jobs.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V103__cluster_support_for_batch_jobs.sql
new file mode 100755
index 0000000..1ec73e8
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V103__cluster_support_for_batch_jobs.sql
@@ -0,0 +1,14 @@
+ALTER TABLE `job`
+	ADD COLUMN `is_misfired` TINYINT(1) NOT NULL DEFAULT '0' AFTER `scheduler_group`;
+
+CREATE TABLE `scheduler_detail` (
+	`id` SMALLINT(2) NOT NULL AUTO_INCREMENT,
+	`is_suspended` TINYINT(1) NOT NULL DEFAULT '0',
+	`execute_misfired_jobs` TINYINT(1) NOT NULL DEFAULT '1',
+	`reset_scheduler_on_bootup` TINYINT(1) NOT NULL DEFAULT '1',
+	PRIMARY KEY (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+INSERT INTO `scheduler_detail` (`is_suspended`, `execute_misfired_jobs`, `reset_scheduler_on_bootup`) VALUES (FALSE,TRUE,TRUE);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V104__permissions_for_transfers.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V104__permissions_for_transfers.sql
new file mode 100644
index 0000000..4e428d2
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V104__permissions_for_transfers.sql
@@ -0,0 +1,11 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio_group', 'TRANSFERCLIENTS_GROUP', 'GROUP', 'TRANSFERCLIENTS', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio_group', 'TRANSFERCLIENTS_GROUP_CHECKER', 'GROUP', 'TRANSFERCLIENTS', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'TRANSFER_CLIENT', 'CLIENT', 'TRANSFER', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'TRANSFER_CLIENT_CHECKER', 'CLIENT', 'TRANSFER', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V105__track_loan_transaction_against_office.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V105__track_loan_transaction_against_office.sql
new file mode 100644
index 0000000..dd5d8e8
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V105__track_loan_transaction_against_office.sql
@@ -0,0 +1,15 @@
+ALTER TABLE `m_loan_transaction`
+    ADD COLUMN `office_id` BIGINT(20) NULL DEFAULT NULL AFTER `loan_id`;
+
+/**update client loans**/
+UPDATE m_loan_transaction lt set lt.office_id = (SELECT c.office_id AS officeId FROM m_loan l JOIN m_client c on l.client_id=c.id where l.id=lt.loan_id) where lt.loan_id in (Select l.id from m_loan l where l.client_id is not null);
+
+/**update group loans**/
+UPDATE m_loan_transaction lt set lt.office_id = (SELECT g.office_id AS officeId FROM m_loan l JOIN m_group g on l.group_id=g.id where l.id=lt.loan_id) where lt.loan_id in (Select l.id from m_loan l where l.group_id is not null);
+
+/**Add foreign key constraints**/
+ALTER TABLE `m_loan_transaction`
+    CHANGE COLUMN `office_id` `office_id` BIGINT(20) NOT NULL AFTER `loan_id`;
+
+ALTER TABLE `m_loan_transaction`
+    ADD CONSTRAINT `FK_m_loan_transaction_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V106__more_permissions_for_transfers.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V106__more_permissions_for_transfers.sql
new file mode 100644
index 0000000..b522f6e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V106__more_permissions_for_transfers.sql
@@ -0,0 +1,30 @@
+DELETE FROM `m_permission` WHERE  `code`="TRANSFER_CLIENT";
+DELETE FROM `m_permission` WHERE  `code`="TRANSFER_CLIENT_CHECKER";
+
+/**Permissions for proposing a transfer**/
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'PROPOSETRANSFER_CLIENT', 'CLIENT', 'PROPOSETRANSFER', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'PROPOSETRANSFER_CLIENT_CHECKER', 'CLIENT', 'PROPOSETRANSFER', 0);
+
+/**Permissions for accepting a transfer**/
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'ACCEPTTRANSFER_CLIENT', 'CLIENT', 'ACCEPTTRANSFER', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'ACCEPTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'ACCEPTTRANSFER', 0);
+
+/**Permissions for rejecting a transfer**/
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'REJECTTRANSFER_CLIENT', 'CLIENT', 'REJECTTRANSFER', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'REJECTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'REJECTTRANSFER', 0);
+
+/**Permissions for withdrawing a transfer proposal**/
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'WITHDRAWTRANSFER_CLIENT', 'CLIENT', 'WITHDRAWTRANSFER', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'WITHDRAWTRANSFER_CLIENT_CHECKER', 'CLIENT', 'WITHDRAWTRANSFER', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V107__datatable_code_mappings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V107__datatable_code_mappings.sql
new file mode 100755
index 0000000..6b40e21
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V107__datatable_code_mappings.sql
@@ -0,0 +1,11 @@
+CREATE TABLE `x_table_cloumn_code_mappings` (
+	`column_alias_name` VARCHAR(50) NOT NULL,
+	`code_id` INT(10) NOT NULL,
+	PRIMARY KEY (`column_alias_name`),
+	INDEX `FK_x_code_id` (`code_id`),
+	CONSTRAINT `FK_x_code_id` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+INSERT INTO `c_configuration` (`name`, `enabled`) VALUES ('constraint_approach_for_datatables', 1);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V108__client_has_transfer_office.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V108__client_has_transfer_office.sql
new file mode 100644
index 0000000..9223efb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V108__client_has_transfer_office.sql
@@ -0,0 +1,9 @@
+/***Store destination office Id while client is pending transfers and effective joining date in a particular branch**/
+ALTER TABLE `m_client`
+	ADD COLUMN `office_joining_date` DATE NULL AFTER `activation_date`,
+	ADD COLUMN `transfer_to_office_id` BIGINT(20) NULL AFTER `office_id`,
+	ADD CONSTRAINT `FK_m_client_m_office` FOREIGN KEY (`transfer_to_office_id`) REFERENCES `m_office` (`id`);
+
+
+/**For current Clients, set the office joining date to activation date**/
+update m_client set office_joining_date=activation_date;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V109__account_transfer_withdrawal_fee_configuration.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V109__account_transfer_withdrawal_fee_configuration.sql
new file mode 100755
index 0000000..e21b0d9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V109__account_transfer_withdrawal_fee_configuration.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_savings_product`
+	ADD COLUMN `withdrawal_fee_for_transfer` TINYINT NULL DEFAULT '1' AFTER `withdrawal_fee_type_enum`;
+
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `withdrawal_fee_for_transfer` TINYINT NULL DEFAULT '1' AFTER `withdrawal_fee_type_enum`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V10__interest-posting-fields-for-savings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V10__interest-posting-fields-for-savings.sql
new file mode 100644
index 0000000..d5305cb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V10__interest-posting-fields-for-savings.sql
@@ -0,0 +1,12 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES
+('transaction_savings', 'POSTINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'POSTINTEREST', '1'),
+('transaction_savings', 'POSTINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'POSTINTEREST', '0');
+
+
+ALTER TABLE `m_savings_product`
+ADD COLUMN `interest_posting_period_enum` SMALLINT(5) NOT NULL DEFAULT 4 AFTER `interest_compounding_period_enum`;
+
+
+ALTER TABLE `m_savings_account`
+ADD COLUMN `interest_posting_period_enum` SMALLINT(5) NOT NULL DEFAULT 4 AFTER `interest_compounding_period_enum`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V110__group_center_close.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V110__group_center_close.sql
new file mode 100644
index 0000000..70541fb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V110__group_center_close.sql
@@ -0,0 +1,8 @@
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) VALUES ('GroupClosureReason', 1);
+
+ALTER TABLE `m_group` ADD COLUMN `closure_reason_cv_id` INT(11) NULL DEFAULT NULL,
+ADD COLUMN `closedon_date` DATE NULL DEFAULT NULL,
+ADD CONSTRAINT `FK_m_group_m_code` FOREIGN KEY (`closure_reason_cv_id`) REFERENCES `m_code_value` (`id`);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`) VALUES ('portfolio', 'CLOSE_GROUP', 'GROUP', 'CLOSE');
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`) VALUES ('portfolio', 'CLOSE_CENTER', 'CENTER', 'CLOSE');
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V111_1__set default_transfers_in_suspense_account_for_existing_loan_products.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V111_1__set default_transfers_in_suspense_account_for_existing_loan_products.sql
new file mode 100644
index 0000000..55e6041
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V111_1__set default_transfers_in_suspense_account_for_existing_loan_products.sql
@@ -0,0 +1,4 @@
+INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`payment_type`,`charge_id`,`financial_account_type`)
+select mapping.gl_account_id,mapping.product_id,mapping.product_type,mapping.payment_type,mapping.charge_id, 10
+from acc_product_mapping mapping
+where mapping.financial_account_type = 1 and mapping.product_type=1;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V111__disable_constraint_approach_for_datatables_by_default.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V111__disable_constraint_approach_for_datatables_by_default.sql
new file mode 100644
index 0000000..af3dbda
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V111__disable_constraint_approach_for_datatables_by_default.sql
@@ -0,0 +1 @@
+update c_configuration set enabled=0 where name="constraint_approach_for_datatables";
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V112__mixreport_sql_support.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V112__mixreport_sql_support.sql
new file mode 100644
index 0000000..cd570bf
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V112__mixreport_sql_support.sql
@@ -0,0 +1,113 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
+('xbrlmapping', 'UPDATE_XBRLMAPPING', 'XBRLMAPPING', 'UPDATE', 0);
+
+DROP TABLE IF EXISTS `mix_taxonomy`;
+
+CREATE TABLE `mix_taxonomy` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `namespace_id` int(11) DEFAULT NULL,
+  `dimension` varchar(100) DEFAULT NULL,
+  `type` int(11) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `need_mapping` tinyint(1) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+LOCK TABLES `mix_taxonomy` WRITE;
+
+INSERT INTO `mix_taxonomy` (`id`, `name`, `namespace_id`, `dimension`, `type`, `description`, `need_mapping`)
+VALUES
+	(1,'AdministrativeExpense',1,NULL,3,NULL,1),
+	(2,'Assets',3,NULL,1,'All outstanding principals due for all outstanding client loans. This includes current, delinquent, and renegotiated loans, but not loans that have been written off. It does not include interest receivable.',1),
+	(3,'Assets',3,'MaturityDimension:LessThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',1),
+	(4,'Assets',3,'MaturityDimension:MoreThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',1),
+	(5,'CashAndCashEquivalents',1,NULL,1,NULL,1),
+	(6,'Deposits',3,NULL,1,'The total value of funds placed in an account with an MFI that are payable to a depositor. This item includes any current, checking, or savings accounts that are payable on demand. It also includes time deposits which have a fixed maturity date and compulsory deposits.',1),
+	(7,'Deposits',3,'DepositProductsDimension:CompulsoryMember',1,'The value of deposits that an MFI\'s clients are required to  maintain as a condition of an existing or future loan.',NULL),
+	(8,'Deposits',3,'DepositProductsDimension:VoluntaryMember',1,'The value of deposits that an MFI\'s clients are not required to  maintain as a condition of an existing or future loan.',NULL),
+	(9,'Deposits',3,'LocationDimension:RuralMember',1,'Located in rural areas. Segmentation based on location.',NULL),
+	(10,'Deposits',3,'LocationDimension:UrbanMember',1,'Located in urban areas. Segmentation based on location.',NULL),
+	(11,'Deposits',3,'MaturityDimension:LessThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',NULL),
+	(12,'Deposits',3,'MaturityDimension:MoreThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',NULL),
+	(13,'EmployeeBenefitsExpense',1,NULL,3,NULL,NULL),
+	(14,'Equity',1,NULL,1,NULL,NULL),
+	(15,'Expense',1,NULL,3,NULL,NULL),
+	(16,'FinancialExpense',3,NULL,3,'All costs All costs incurred in raising funds from third parties, fee expenses from non-financial services, net gains (losses) due to changes in fair value of financial liabilities, impairment losses net of reversals of financial assets other than loan portfolio and net gains (losses) from restatement of financial statements in terms of the measuring unit current at the end of the reporting period.',NULL),
+	(17,'FinancialRevenueOnLoans',3,NULL,2,'Interest and non-interest income generated by the provision of credit services to the clients. Fees and commissions for late payment are also included.',NULL),
+	(18,'ImpairmentLossAllowanceGrossLoanPortfolio',3,NULL,2,'An allowance for the risk of losses in the gross loan portfolio due to default .',NULL),
+	(19,'Liabilities',1,NULL,1,NULL,NULL),
+	(20,'Liabilities',3,'MaturityDimension:LessThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',NULL),
+	(21,'Liabilities',3,'MaturityDimension:MoreThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',NULL),
+	(22,'LoanPortfolioGross',3,NULL,2,'All outstanding principals due for all outstanding client loans. This includes current, delinquent, and renegotiated loans, but not loans that have been written off. It does not include interest receivable.',NULL),
+	(23,'LoanPortfolioGross',3,'CreditProductsDimension:MicroenterpriseMember',2,'Loans that finance the production or trade of goods and  services for an individual\'s microenterprise, whether or not the microenterprise is legally registered. Segmentation based on loan product.',NULL),
+	(24,'LoanPortfolioGross',3,'DelinquencyDimension:OneMonthOrMoreMember',2,'Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal  past due or renegotiated. Segmentation based on the  principal balance of all loans outstanding that have one or  more installments of principal past due or renegotiated.',NULL),
+	(25,'LoanPortfolioGross',3,'DelinquencyDimension:ThreeMonthsOrMoreMember',2,'Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal  past due or renegotiated.? Segmentation based on the  principal balance of all loans outstanding that have one or  more installments of principal past due or renegotiated.',NULL),
+	(26,'LoanPortfolioGross',3,'LocationDimension:RuralMember',2,'Located in rural areas. Segmentation based on geographic location.',NULL),
+	(27,'LoanPortfolioGross',3,'LocationDimension:UrbanMember',2,'Located in urbal areas. Segmentation based on geographic location.',NULL),
+	(28,'LoanPortfolioGross',3,'MaturityDimension:LessThanOneYearMember',2,'Segmentation based on the life of an asset or liability.',NULL),
+	(29,'LoanPortfolioGross',3,'MaturityDimension:MoreThanOneYearMember',2,'Segmentation based on the life of an asset or liability.',NULL),
+	(30,'NetLoanLoss',3,'',3,'Referred to the value of delinquency loans written off net of any principal recovery.',NULL),
+	(31,'NetLoanLossProvisionExpense',3,NULL,3,'Represent the net value of loan portfolio impairment loss considering any reversal on impairment loss and any recovery on loans written off recognized as a income during the accounting period.',NULL),
+	(32,'NetOperatingIncome',3,NULL,2,'Total operating revenue less all expenses related to the MFI\'s core financial service operation including total financial expense, impairment loss and operating expense. Donations are excluded.',NULL),
+	(33,'NetOperatingIncomeNetOfTaxExpense',3,NULL,3,'Net operating income reported incorporating the effect of taxes. Taxes include all domestic and foreign taxes which are based on taxable profits, other taxes related to personnel, financial transactions or value-added taxes are not considered in calculation of this value.',NULL),
+	(34,'NumberOfActiveBorrowers',3,NULL,0,'The number of individuals who currently have an outstanding loan balance with the MFI or are primarily responsible for repaying any portion of the gross loan portfolio. Individuals who have multiple loans with an MFI should be counted as a single borrower.',NULL),
+	(35,'NumberOfActiveBorrowers',3,'GenderDimension:FemaleMember',0,'The number of individuals who currently have an outstanding loan balance with the MFI or are primarily responsible for repaying any portion of the gross loan portfolio. Individuals who have multiple loans with an MFI should be counted as a single borrower.',NULL),
+	(36,'NumberOfBoardMembers',3,'GenderDimension:FemaleMember',0,'The number of members that comprise the board of directors at the end of the reporting period who are female.',NULL),
+	(37,'NumberOfDepositAccounts',3,NULL,0,'The number of individuals who currently have funds on deposit with the MFI on a voluntary basis; i.e., they are not required to maintain the deposit account to access a loan. This number applies only to deposits held by an MFI, not to those deposits held in other institutions by the MFI\'s clients. The number should be based on the number of individuals rather than the number of groups. A single deposit account may represent multiple depositors.',NULL),
+	(38,'NumberOfDepositors',3,'',0,'The number of deposit accounts, both voluntary and compulsory, opened at the MFI whose balances the institution is liable to repay. The number should be based on the number of individual accounts rather than on the number of groups.',NULL),
+	(39,'NumberOfEmployees',3,NULL,0,'The number of individuals who are actively employed by an entity. This number includes contract employees or advisors who dedicate a substantial portion of their time to the entity, even if they are not on the entity\'s employees roster.',NULL),
+	(40,'NumberOfEmployees',3,'GenderDimension:FemaleMember',0,'The number of individuals who are actively employed by an entity. This number includes contract employees or advisors who dedicate a substantial portion of their time to the entity, even if they are not on the entity\'s employees roster.',NULL),
+	(41,'NumberOfLoanOfficers',3,NULL,0,'The number of employees whose main activity is to manage a portion of the gross loan portfolio. A loan officer is a staff member of record who is directly responsible for arranging and monitoring client loans.',NULL),
+	(42,'NumberOfLoanOfficers',3,'GenderDimension:FemaleMember',0,'The number of employees whose main activity is to manage a portion of the gross loan portfolio. A loan officer is a staff member of record who is directly responsible for arranging and monitoring client loans.',NULL),
+	(43,'NumberOfManagers',3,'GenderDimension:FemaleMember',0,'The number of members that comprise the management of the institution who are female.',NULL),
+	(44,'NumberOfOffices',3,NULL,0,'The number of staffed points of service and administrative sites used to deliver or support the delivery of financial services to microfinance clients.',NULL),
+	(45,'NumberOfOutstandingLoans',3,NULL,0,'The number of loans in the gross loan portfolio. For MFIs using a group lending methodology, the number of loans should refer to the number of individuals receiving loans as part of a group or as part of a group loan.',NULL),
+	(46,'OperatingExpense',3,NULL,3,'Includes expenses not related to financial and credit loss impairment, such as personnel expenses, depreciation, amortization and administrative expenses.',NULL),
+	(47,'OperatingIncome',3,NULL,2,'Includes all financial income and other operating revenue which is generated from non-financial services. Operating income also includes net gains (losses) from holding financial assets (changes on their values during the period and foreign exchange differences). Donations or any revenue not related with an MFI\'s core business of making loans and providing financial services are not considered under this category.',NULL),
+	(48,'WriteOffsOnGrossLoanPortfolio',3,NULL,2,'The value of loans that have been recognized as uncollectible for accounting purposes. A write-off is an accounting procedure that removes the outstanding balance of the loan from the gross loan portfolio and impairment loss allowance. Thus, the write-off does not affect the net loan portfolio, total assets, or any equity account. If the impairment loss allowance is insufficient to cover the amount written off, the excess amount will result in an additional impairment loss on loans recognised in profit or loss of the period.',NULL);
+
+UNLOCK TABLES;
+
+
+DROP TABLE IF EXISTS `mix_taxonomy_mapping`;
+
+CREATE TABLE `mix_taxonomy_mapping` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `identifier` varchar(50) NOT NULL DEFAULT '',
+  `config` varchar(200) DEFAULT NULL,
+  `last_update_date` datetime DEFAULT NULL,
+  `currency` varchar(11) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+LOCK TABLES `mix_taxonomy_mapping` WRITE;
+
+INSERT INTO `mix_taxonomy_mapping` (`id`, `identifier`, `config`, `last_update_date`, `currency`) VALUES
+(1,'default',NULL,NULL,'');
+
+UNLOCK TABLES;
+
+
+DROP TABLE IF EXISTS `mix_xbrl_namespace`;
+
+CREATE TABLE `mix_xbrl_namespace` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `prefix` varchar(20) NOT NULL DEFAULT '',
+  `url` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UNQUE` (`prefix`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+LOCK TABLES `mix_xbrl_namespace` WRITE;
+
+INSERT INTO `mix_xbrl_namespace` (`id`, `prefix`, `url`)
+VALUES
+	(1,'ifrs','http://xbrl.iasb.org/taxonomy/2009-04-01/ifrs'),
+	(2,'iso4217','http://www.xbrl.org/2003/iso4217'),
+	(3,'mix','http://www.themix.org/int/fr/ifrs/basi/YYYY-MM-DD/mx-cor'),
+	(4,'xbrldi','http://xbrl.org/2006/xbrldi'),
+	(5,'xbrli','http://www.xbrl.org/2003/instance'),
+	(6,'link','http://www.xbrl.org/2003/linkbase'),
+	(7,'dc-all','http://www.themix.org/int/fr/ifrs/basi/2010-08-31/dc-all');
+UNLOCK TABLES;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V113__track_savings_transaction_against_office.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V113__track_savings_transaction_against_office.sql
new file mode 100644
index 0000000..c6222f9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V113__track_savings_transaction_against_office.sql
@@ -0,0 +1,15 @@
+ALTER TABLE `m_savings_account_transaction`
+    ADD COLUMN `office_id` BIGINT(20) NULL DEFAULT NULL AFTER `savings_account_id`;
+
+/**update client savings**/
+UPDATE m_savings_account_transaction st set st.office_id = (SELECT c.office_id AS officeId FROM m_savings_account sa JOIN m_client c on sa.client_id=c.id where sa.id=st.savings_account_id) where st.savings_account_id in (Select sa.id from m_savings_account sa where sa.client_id is not null);
+
+/**update group savings**/
+UPDATE m_savings_account_transaction st set st.office_id = (SELECT g.office_id AS officeId FROM m_savings_account sa JOIN m_group g on sa.group_id=g.id where sa.id=st.savings_account_id) where st.savings_account_id in (Select sa.id from m_savings_account sa where sa.group_id is not null);
+
+/**Add foreign key constraints**/
+ALTER TABLE `m_savings_account_transaction`
+    CHANGE COLUMN `office_id` `office_id` BIGINT(20) NOT NULL AFTER `savings_account_id`;
+
+ALTER TABLE `m_savings_account_transaction`
+    ADD CONSTRAINT `FK_m_savings_account_transaction_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V114__set_default_transfers_in_suspense_account_for_existing_savings_products - Copy.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V114__set_default_transfers_in_suspense_account_for_existing_savings_products - Copy.sql
new file mode 100644
index 0000000..8dd9298
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V114__set_default_transfers_in_suspense_account_for_existing_savings_products - Copy.sql
@@ -0,0 +1,4 @@
+INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`payment_type`,`charge_id`,`financial_account_type`)
+select mapping.gl_account_id,mapping.product_id,mapping.product_type,mapping.payment_type,mapping.charge_id, 10
+from acc_product_mapping mapping
+where mapping.financial_account_type = 1 and mapping.product_type=2;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V115__permissions_for_cache_api.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V115__permissions_for_cache_api.sql
new file mode 100644
index 0000000..60c0a5f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V115__permissions_for_cache_api.sql
@@ -0,0 +1,19 @@
+DROP TABLE IF EXISTS `c_cache`;
+CREATE TABLE `c_cache` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `cache_type_enum` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+INSERT INTO `c_cache`
+(`id`,`cache_type_enum`)
+VALUES
+(1, 1);
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'READ_CACHE', 'CACHE', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'UPDATE_CACHE', 'CACHE', 'UPDATE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V116__track_currency_for_journal_entries.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V116__track_currency_for_journal_entries.sql
new file mode 100644
index 0000000..b627ce1
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V116__track_currency_for_journal_entries.sql
@@ -0,0 +1,29 @@
+/**Add currency code**/
+ALTER TABLE `acc_gl_journal_entry`
+		ADD COLUMN `currency_code` VARCHAR(3) NULL DEFAULT NULL AFTER `reversal_id`;
+
+
+/**Update currency codes for loans**/
+UPDATE acc_gl_journal_entry journal_entry SET currency_code = (
+SELECT m_loan.currency_code
+FROM m_loan, m_loan_transaction
+WHERE m_loan.id = m_loan_transaction.loan_id AND m_loan_transaction.id=journal_entry.transaction_id)
+WHERE journal_entry.entity_type_enum=1;
+
+
+/**Update currency codes for savings**/
+UPDATE acc_gl_journal_entry journal_entry SET currency_code = (
+SELECT m_savings_account.currency_code
+FROM m_savings_account, m_savings_account_transaction
+WHERE m_savings_account.id = m_savings_account_transaction.savings_account_id AND m_savings_account_transaction.id=journal_entry.transaction_id)
+WHERE journal_entry.entity_type_enum=2;
+
+/**Update currency codes for manual journal entries***/
+update acc_gl_journal_entry set currency_code = (select code from m_organisation_currency limit 1)
+where acc_gl_journal_entry.currency_code is NULL;
+
+/**Make currency code not null**/
+ALTER TABLE `acc_gl_journal_entry`
+	ALTER `currency_code` DROP DEFAULT;
+ALTER TABLE `acc_gl_journal_entry`
+	CHANGE COLUMN `currency_code` `currency_code` VARCHAR(3) NOT NULL AFTER `reversal_id`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V117__loan_charge_from_savings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V117__loan_charge_from_savings.sql
new file mode 100755
index 0000000..32d3318
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V117__loan_charge_from_savings.sql
@@ -0,0 +1,26 @@
+CREATE TABLE `m_portfolio_account_associations` (
+	`id` BIGINT NOT NULL AUTO_INCREMENT,
+	`loan_account_id` BIGINT NULL DEFAULT NULL,
+	`savings_account_id` BIGINT NULL DEFAULT NULL,
+	`linked_loan_account_id` BIGINT NULL DEFAULT NULL,
+	`linked_savings_account_id` BIGINT NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `account_association_loan_fk` FOREIGN KEY (`loan_account_id`) REFERENCES `m_loan` (`id`),
+	CONSTRAINT `account_association_savings_fk` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`),
+	CONSTRAINT `linked_loan_fk` FOREIGN KEY (`linked_loan_account_id`) REFERENCES `m_loan` (`id`),
+	CONSTRAINT `linked_savings_fk` FOREIGN KEY (`linked_savings_account_id`) REFERENCES `m_savings_account` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+ALTER TABLE `m_charge`
+	ADD COLUMN `charge_payment_mode_enum` SMALLINT(5) NOT NULL DEFAULT '0' AFTER `charge_calculation_enum`;
+
+ALTER TABLE `m_loan_charge`
+	ADD COLUMN `charge_payment_mode_enum` SMALLINT(5) NOT NULL DEFAULT '0' AFTER `charge_calculation_enum`;
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_loan', 'PAY_LOANCHARGE', 'LOANCHARGE', 'PAY', 0);
+
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Transfer Fee For Loans From Savings', 'Transfer Fee For Loans From Savings', '0 1 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0);
+
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V118_1__savings_charge_patch_update.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_1__savings_charge_patch_update.sql
new file mode 100644
index 0000000..01502cb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_1__savings_charge_patch_update.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `total_fees_charge_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `total_withdrawal_fees_derived`,
+	ADD COLUMN `total_penalty_charge_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `total_fees_charge_derived`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V118_2__product_mapping_delete_duplicate_fund_source_to_account_mappings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_2__product_mapping_delete_duplicate_fund_source_to_account_mappings.sql
new file mode 100644
index 0000000..7f2a76a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_2__product_mapping_delete_duplicate_fund_source_to_account_mappings.sql
@@ -0,0 +1 @@
+delete from acc_product_mapping  where financial_account_type=10 and payment_type is not null;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V118_3__permissions_form_propose_and_accept_client_transfers.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_3__permissions_form_propose_and_accept_client_transfers.sql
new file mode 100644
index 0000000..431bccb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_3__permissions_form_propose_and_accept_client_transfers.sql
@@ -0,0 +1,6 @@
+/**Permissions for proposing and accepting a transfer**/
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'PROPOSEANDACCEPTTRANSFER_CLIENT', 'CLIENT', 'PROPOSEANDACCEPTTRANSFER', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'PROPOSEANDACCEPTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'PROPOSEANDACCEPTTRANSFER', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V118_4__reset_default_transfers_in_suspense_account_for_existing_savings_products.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_4__reset_default_transfers_in_suspense_account_for_existing_savings_products.sql
new file mode 100644
index 0000000..6fbb2e3
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_4__reset_default_transfers_in_suspense_account_for_existing_savings_products.sql
@@ -0,0 +1,8 @@
+/***Delete previously set defaults**/
+delete from acc_product_mapping where financial_account_type=10 and product_type=2;
+
+/***Set the proper defaults**/
+INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`payment_type`,`charge_id`,`financial_account_type`)
+select mapping.gl_account_id,mapping.product_id,mapping.product_type,mapping.payment_type,mapping.charge_id, 10
+from acc_product_mapping mapping
+where mapping.financial_account_type = 2 and mapping.product_type=2;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql
new file mode 100644
index 0000000..fd5fb58
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql
@@ -0,0 +1 @@
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Pay Due Savings Charges', 'Pay Due Savings Charges', '0 0 12 * * ?', '2013-09-23 00:00:00', 5, NULL, NULL, NULL, 'Pay Due Savings ChargesJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql
new file mode 100644
index 0000000..54d2372
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql
@@ -0,0 +1,5 @@
+/***Set defaults for income from Penalties for existing Saving Products**/
+INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`payment_type`,`charge_id`,`financial_account_type`)
+select mapping.gl_account_id,mapping.product_id,mapping.product_type,mapping.payment_type,mapping.charge_id, 5
+from acc_product_mapping mapping
+where mapping.financial_account_type = 4 and mapping.product_type=2;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V118_7__move_withdrawal_annual_fee_to_charges.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_7__move_withdrawal_annual_fee_to_charges.sql
new file mode 100644
index 0000000..8f03fbf
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_7__move_withdrawal_annual_fee_to_charges.sql
@@ -0,0 +1,253 @@
+ALTER TABLE `m_savings_account_charge`
+	CHANGE COLUMN `due_for_collection_as_of_date` `charge_due_date` DATE NULL DEFAULT NULL AFTER `charge_time_enum`;
+
+ALTER TABLE `m_savings_account_charge`
+	ADD COLUMN `fee_on_month` SMALLINT(5) NULL DEFAULT NULL AFTER `charge_due_date`,
+	ADD COLUMN `fee_on_day` SMALLINT(5) NULL DEFAULT NULL AFTER `fee_on_month`,
+	ADD COLUMN `is_active` TINYINT(1) NOT NULL DEFAULT '1' AFTER `waived`;
+
+
+delimiter //
+CREATE PROCEDURE migrate_withdrwal_fees()
+begin
+	declare no_more_rows boolean default false;
+	declare v_currency_code  VARCHAR(3);
+	declare v_withdrawal_fee_type_enum  SMALLINT(5);
+	declare v_withdrawal_fee_amount_charge_def DECIMAL(19,6);
+	declare v_account_id BIGINT(20);
+	declare v_withdrawal_fee_amount DECIMAL(19,6);
+	declare t_calculation_percentage DECIMAL(19,6);
+	declare t_calculation_on_amount DECIMAL(19,6);
+	declare t_withdrawal_fee_name VARCHAR(100);
+
+	declare t_charge_id BIGINT(20);
+	declare t_savings_charge_id BIGINT(20);
+
+	-- savings transaction variables
+	declare v_savings_transaction_id BIGINT(20);
+	declare v_transaction_amount DECIMAL(19,6);
+
+
+	declare cursor1 cursor for
+      select sa.currency_code, sa.withdrawal_fee_type_enum, sa.withdrawal_fee_amount
+      from m_savings_account sa where sa.withdrawal_fee_amount is not null and sa.withdrawal_fee_amount > 0 group by sa.currency_code, sa.withdrawal_fee_type_enum;
+
+	declare cursor2 cursor for
+      select sa.id, sa.withdrawal_fee_amount from m_savings_account sa where sa.currency_code=v_currency_code and sa.withdrawal_fee_type_enum=v_withdrawal_fee_type_enum and sa.withdrawal_fee_amount is not null and sa.withdrawal_fee_amount > 0;
+
+    declare cursor3 cursor for
+      select sat.id, sat.amount from m_savings_account_transaction sat
+      where sat.savings_account_id=v_account_id and sat.transaction_type_enum=4;
+
+	declare continue handler for not found
+      set no_more_rows := true;
+
+	open cursor1;
+    LOOP1: loop
+	fetch cursor1 into v_currency_code, v_withdrawal_fee_type_enum, v_withdrawal_fee_amount_charge_def;
+	if no_more_rows then
+		close cursor1;
+		leave LOOP1;
+	end if;
+	-- set withdrawal fee name
+	if(v_withdrawal_fee_type_enum = 1) then
+		set t_withdrawal_fee_name = CONCAT('Withdrawal fee-Flat-',v_currency_code);
+	else
+		set t_withdrawal_fee_name = CONCAT('Withdrawal fee-Percentage-',v_currency_code);
+	end if;
+
+	-- get charge id if already exists
+	set t_charge_id = (select id from m_charge where name = t_withdrawal_fee_name);
+
+	if t_charge_id is null then
+		-- add withdrawal fee to charges
+		INSERT INTO `m_charge` (`name`, `currency_code`, `charge_applies_to_enum`, `charge_time_enum`, `charge_calculation_enum`, `charge_payment_mode_enum`, `amount`, `is_penalty`, `is_active`, `is_deleted`) VALUES (t_withdrawal_fee_name , v_currency_code, 2, 5, v_withdrawal_fee_type_enum, 0, v_withdrawal_fee_amount_charge_def, 0, 1, 0);
+
+		-- get inserted charge id
+		set t_charge_id = last_insert_id();
+	end if;
+
+	open cursor2;
+        LOOP2: loop
+		fetch cursor2 into v_account_id, v_withdrawal_fee_amount;
+		if no_more_rows then
+		set no_more_rows := false;
+		close cursor2;
+		leave LOOP2;
+            end if;
+
+            if (v_withdrawal_fee_type_enum=1) then
+		set t_calculation_percentage = NULL;
+		set t_calculation_on_amount = NULL;
+            else
+	            set t_calculation_percentage = v_withdrawal_fee_amount;
+	            set v_withdrawal_fee_amount = 0;
+	            set t_calculation_on_amount = 0;
+            end if;
+
+            if not exists (select id from m_savings_account_charge sac where
+		sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=5) then
+
+	            -- attach withdrawal charge to savings
+	            INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_due_date`, `fee_on_month`, `fee_on_day`, `charge_calculation_enum`, `calculation_percentage`, `calculation_on_amount`, `amount`, `amount_paid_derived`, `amount_waived_derived`, `amount_writtenoff_derived`, `amount_outstanding_derived`, `is_paid_derived`, `waived`, `is_active`) VALUES (v_account_id, t_charge_id, 0, 5, NULL, NULL, NULL, v_withdrawal_fee_type_enum, t_calculation_percentage, t_calculation_on_amount, v_withdrawal_fee_amount, NULL, NULL, NULL, 0.000000, 0, 0, 1);
+
+	            -- set savings account charge id
+	            set t_savings_charge_id = last_insert_id();
+
+	        else
+
+			set t_savings_charge_id = (select id from m_savings_account_charge sac where sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=5);
+
+	        end if;
+
+
+            open cursor3;
+            LOOP3: loop
+		fetch cursor3 into v_savings_transaction_id, v_transaction_amount;
+
+			if no_more_rows then
+			set no_more_rows := false;
+			close cursor3;
+			leave LOOP3;
+	            end if;
+
+	            if not exists (select id from m_savings_account_charge_paid_by sacp where
+		sacp.savings_account_transaction_id=v_savings_transaction_id and sacp.savings_account_charge_id=t_savings_charge_id) then
+
+		            -- insert a record into savings account charge paid by
+		            INSERT INTO `m_savings_account_charge_paid_by` (`savings_account_transaction_id`, `savings_account_charge_id`, `amount`) VALUES(v_savings_transaction_id, t_savings_charge_id, v_transaction_amount);
+
+		        end if;
+
+	        end loop LOOP3;
+        end loop LOOP2;
+    end loop LOOP1;
+end //
+
+CREATE PROCEDURE migrate_annual_fees()
+begin
+	declare no_more_rows boolean default false;
+	declare v_currency_code  VARCHAR(3);
+	declare v_annual_fee_amount_charge_def DECIMAL(19,6);
+	declare v_account_id BIGINT(20);
+	declare v_annual_fee_amount DECIMAL(19,6);
+	declare v_annual_fee_on_month SMALLINT(5);
+	declare v_annual_fee_on_day SMALLINT(5);
+	declare v_annual_fee_next_due_date DATE;
+	declare t_annual_fee_name VARCHAR(100);
+	declare t_charge_id BIGINT(20);
+	declare t_savings_charge_id BIGINT(20);
+
+	-- savings transaction variables
+	declare v_savings_transaction_id BIGINT(20);
+	declare v_transaction_amount DECIMAL(19,6);
+
+
+	declare cursor1 cursor for
+      select sa.currency_code, sa.annual_fee_amount
+      from m_savings_account sa where sa.annual_fee_amount is not null and sa.annual_fee_on_month is not null and sa.annual_fee_on_day is not null group by sa.currency_code;
+
+	declare cursor2 cursor for
+      select sa.id, sa.annual_fee_amount, sa.annual_fee_on_month, sa.annual_fee_on_day, sa.annual_fee_next_due_date from m_savings_account sa where sa.currency_code=v_currency_code and sa.annual_fee_amount is not null and sa.annual_fee_on_month is not null and sa.annual_fee_on_day is not null;
+
+    declare cursor3 cursor for
+      select sat.id, sat.amount from m_savings_account_transaction sat
+      where sat.savings_account_id=v_account_id and sat.transaction_type_enum=5;
+
+	declare continue handler for not found
+      set no_more_rows := true;
+
+	open cursor1;
+    LOOP1: loop
+	fetch cursor1 into v_currency_code, v_annual_fee_amount_charge_def;
+
+	if no_more_rows then
+		close cursor1;
+		leave LOOP1;
+	end if;
+
+	-- set annual fee name
+	set t_annual_fee_name = CONCAT('Annual fee - ',v_currency_code);
+
+	-- get charge id if already exists
+	set t_charge_id = (select id from m_charge where name = t_annual_fee_name);
+
+	if t_charge_id is null then
+
+		-- add annual fee to charges
+		INSERT INTO `m_charge` (`name`, `currency_code`, `charge_applies_to_enum`, `charge_time_enum`, `charge_calculation_enum`, `charge_payment_mode_enum`, `amount`, `is_penalty`, `is_active`, `is_deleted`) VALUES (t_annual_fee_name, v_currency_code, 2, 6, 1, 0, v_annual_fee_amount_charge_def, 0, 1, 0);
+
+		-- get inserted charge id
+		set t_charge_id = last_insert_id();
+
+	end if;
+
+	open cursor2;
+        LOOP2: loop
+		fetch cursor2 into v_account_id, v_annual_fee_amount, v_annual_fee_on_month, v_annual_fee_on_day, v_annual_fee_next_due_date;
+
+		if no_more_rows then
+		set no_more_rows := false;
+		close cursor2;
+		leave LOOP2;
+            end if;
+
+            if not exists (select id from m_savings_account_charge sac where
+		sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=6) then
+
+	            -- attach annual charge to savings
+	            INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_due_date`, `fee_on_month`, `fee_on_day`, `charge_calculation_enum`, `calculation_percentage`, `calculation_on_amount`, `amount`, `amount_paid_derived`, `amount_waived_derived`, `amount_writtenoff_derived`, `amount_outstanding_derived`, `is_paid_derived`, `waived`, `is_active`) VALUES (v_account_id, t_charge_id, 0, 6, v_annual_fee_next_due_date, v_annual_fee_on_month, v_annual_fee_on_day, 1, NULL, NULL, v_annual_fee_amount, NULL, NULL, NULL, v_annual_fee_amount, 0, 0, 1);
+
+	            -- set savings account charge id
+	            set t_savings_charge_id = last_insert_id();
+
+	        else
+
+			set t_savings_charge_id = (select id from m_savings_account_charge sac where sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=6);
+
+	        end if;
+
+            open cursor3;
+            LOOP3: loop
+		fetch cursor3 into v_savings_transaction_id, v_transaction_amount;
+
+			if no_more_rows then
+			set no_more_rows := false;
+			close cursor3;
+			leave LOOP3;
+	            end if;
+
+	            if not exists (select id from m_savings_account_charge_paid_by sacp where
+		sacp.savings_account_transaction_id=v_savings_transaction_id and sacp.savings_account_charge_id=t_savings_charge_id) then
+
+		            -- insert a record into savings account charge paid by
+		            INSERT INTO `m_savings_account_charge_paid_by` (`savings_account_transaction_id`, `savings_account_charge_id`, `amount`) VALUES(v_savings_transaction_id, t_savings_charge_id, v_transaction_amount);
+
+		        end if;
+
+	        end loop LOOP3;
+        end loop LOOP2;
+    end loop LOOP1;
+end //
+
+delimiter ;
+
+call migrate_withdrwal_fees();
+call migrate_annual_fees();
+
+drop procedure if exists migrate_annual_fees;
+drop procedure if exists migrate_withdrwal_fees;
+
+
+ALTER TABLE `m_savings_account`
+	DROP COLUMN `annual_fee_amount`,
+	DROP COLUMN `annual_fee_on_month`,
+	DROP COLUMN `annual_fee_on_day`,
+	DROP COLUMN `annual_fee_next_due_date`,
+	DROP COLUMN `withdrawal_fee_amount`,
+	DROP COLUMN `withdrawal_fee_type_enum`;
+
+ALTER TABLE `m_savings_product`
+	DROP COLUMN `annual_fee_amount`,
+	DROP COLUMN `annual_fee_on_month`,
+	DROP COLUMN `annual_fee_on_day`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V118_8__track_overpayments_seperately_in_loan_transactions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_8__track_overpayments_seperately_in_loan_transactions.sql
new file mode 100644
index 0000000..8ff31ea
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V118_8__track_overpayments_seperately_in_loan_transactions.sql
@@ -0,0 +1,16 @@
+/***Set defaults for income from Penalties for existing Saving Products**/
+ALTER TABLE `m_loan_transaction`
+	ADD COLUMN `overpayment_portion_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `penalty_charges_portion_derived`;
+
+/**Add dummy liability account if organization already has a loan product with accounting enabled**/
+INSERT INTO `acc_gl_account` (`name`, `hierarchy`, `gl_code`,`account_usage`, `classification_enum`,`description`)
+select 'Loan Overpayments (Temp)', '.', '22000-Temp', 1, 2,'Temporary account to track Loan overpayments Liabilities'
+FROM m_product_loan WHERE accounting_type != 1
+limit 1;
+
+
+/**Map a liability account for every loan which has accounting enabled**/
+INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`financial_account_type`)
+select (select max(id) from acc_gl_account where classification_enum=2 and account_usage=1 LIMIT 1), mapping.product_id, mapping.product_type, 11
+from acc_product_mapping mapping
+where mapping.financial_account_type = 2 and mapping.product_type=1;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V118__savings_charge.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V118__savings_charge.sql
new file mode 100644
index 0000000..83ea756
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V118__savings_charge.sql
@@ -0,0 +1,64 @@
+CREATE TABLE `m_savings_account_charge` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`savings_account_id` BIGINT(20) NOT NULL,
+	`charge_id` BIGINT(20) NOT NULL,
+	`is_penalty` TINYINT(1) NOT NULL DEFAULT '0',
+	`charge_time_enum` SMALLINT(5) NOT NULL,
+	`due_for_collection_as_of_date` DATE NULL DEFAULT NULL,
+	`charge_calculation_enum` SMALLINT(5) NOT NULL,
+	`calculation_percentage` DECIMAL(19,6) NULL DEFAULT NULL,
+	`calculation_on_amount` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`amount_paid_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount_waived_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount_writtenoff_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount_outstanding_derived` DECIMAL(19,6) NOT NULL DEFAULT '0.000000',
+	`is_paid_derived` TINYINT(1) NOT NULL DEFAULT '0',
+	`waived` TINYINT(1) NOT NULL DEFAULT '0',
+	PRIMARY KEY (`id`),
+	INDEX `charge_id` (`charge_id`),
+	INDEX `m_savings_account_charge_ibfk_2` (`savings_account_id`),
+	CONSTRAINT `m_savings_account_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+	CONSTRAINT `m_savings_account_charge_ibfk_2` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `m_savings_product_charge` (
+	`savings_product_id` BIGINT(20) NOT NULL,
+	`charge_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`savings_product_id`, `charge_id`),
+	INDEX `charge_id` (`charge_id`),
+	CONSTRAINT `m_savings_product_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+	CONSTRAINT `m_savings_product_charge_ibfk_2` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'WAIVE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'WAIVE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'WAIVE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'WAIVE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'PAY_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'PAY', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'PAY_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'PAY', 0);
+
+
+
+CREATE TABLE `m_savings_account_charge_paid_by` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`savings_account_transaction_id` BIGINT(20) NOT NULL,
+	`savings_account_charge_id` BIGINT(20) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK__m_savings_account_transaction` (`savings_account_transaction_id`),
+	INDEX `FK__m_savings_account_charge` (`savings_account_charge_id`),
+	CONSTRAINT `FK__m_savings_account_charge` FOREIGN KEY (`savings_account_charge_id`) REFERENCES `m_savings_account_charge` (`id`),
+	CONSTRAINT `FK__m_savings_account_transaction` FOREIGN KEY (`savings_account_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V119__add_template_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V119__add_template_table.sql
new file mode 100644
index 0000000..2454a20
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V119__add_template_table.sql
@@ -0,0 +1,34 @@
+DROP TABLE IF EXISTS `m_template`;
+CREATE TABLE `m_template` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
+  `text` longtext COLLATE utf8_unicode_ci NOT NULL,
+  `entity` int(11) DEFAULT NULL,
+  `type` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `m_templatemappers`;
+CREATE TABLE `m_templatemappers` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `mapperkey` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
+  `mapperorder` int(11) DEFAULT NULL,
+  `mappervalue` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `m_template_m_templatemappers`;
+CREATE TABLE `m_template_m_templatemappers` (
+  `m_template_id` bigint(20) NOT NULL,
+  `mappers_id` bigint(20) NOT NULL,
+  UNIQUE KEY `mappers_id` (`mappers_id`),
+  KEY (`mappers_id`),
+  KEY (`m_template_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organistion', 'DELETE_TEMPLATE', 'TEMPLATE', 'DELETE', 0),
+	   ('organistion', 'CREATE_TEMPLATE', 'TEMPLATE', 'CREATE', 0),
+	   ('organistion', 'UPDATE_TEMPLATE', 'TEMPLATE', 'UPDATE', 0),
+	   ('organistion', 'READ_TEMPLATE', 'TEMPLATE', 'READ', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V11__add-payment-details.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V11__add-payment-details.sql
new file mode 100755
index 0000000..22aed1f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V11__add-payment-details.sql
@@ -0,0 +1,24 @@
+/*
+New table for storing transaction details (for both loan and savings)
+*/
+
+CREATE TABLE `m_payment_detail` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`payment_type_enum` SMALLINT(5) NOT NULL,
+	`account_number` VARCHAR(100) NULL DEFAULT NULL,
+	`check_number` VARCHAR(100) NULL DEFAULT NULL,
+	`receipt_number` VARCHAR(100) NULL DEFAULT NULL,
+	`bank_number` VARCHAR(100) NULL DEFAULT NULL,
+	`routing_code` VARCHAR(100) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+/*
+Update loan transaction to add a link to transaction detail table
+*/
+
+ALTER TABLE `m_loan_transaction`
+	ADD COLUMN `payment_detail_id` BIGINT(20) NULL AFTER `loan_id`,
+	ADD CONSTRAINT `FK_m_loan_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V120__accounting_running_balance.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V120__accounting_running_balance.sql
new file mode 100755
index 0000000..6d338d9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V120__accounting_running_balance.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `acc_gl_journal_entry`
+	ADD COLUMN `is_running_balance_caculated` TINYINT NOT NULL DEFAULT '0' AFTER `lastmodified_date`,
+	ADD COLUMN `office_running_balance` DECIMAL(19,6) NOT NULL DEFAULT '0.000000' AFTER `is_running_balance_caculated`;
+
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Update Accounting Running Balances', 'Update Accounting Running Balances', '0 1 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V121__accounting_running_balance_for_organization.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V121__accounting_running_balance_for_organization.sql
new file mode 100755
index 0000000..5946090
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V121__accounting_running_balance_for_organization.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `acc_gl_journal_entry`
+	ADD COLUMN `organization_running_balance` DECIMAL(19,6) NOT NULL DEFAULT '0.000000' AFTER `office_running_balance`;
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'UPDATERUNNINGBALANCE_JOURNALENTRY', 'JOURNALENTRY', 'UPDATERUNNINGBALANCE', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V122__recurring_fee_support_for_savings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V122__recurring_fee_support_for_savings.sql
new file mode 100644
index 0000000..85f4c80
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V122__recurring_fee_support_for_savings.sql
@@ -0,0 +1,8 @@
+ALTER TABLE `m_charge`
+	ADD COLUMN `fee_on_day` SMALLINT(5) NULL AFTER `amount`,
+	ADD COLUMN `fee_interval` SMALLINT(5) NULL AFTER `fee_on_day`,
+	ADD COLUMN `fee_on_month` SMALLINT(5) NULL AFTER `fee_interval`;
+
+
+ALTER TABLE `m_savings_account_charge`
+	ADD COLUMN `fee_interval` SMALLINT(5) NULL DEFAULT NULL AFTER `fee_on_day`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V123__remove_payment_mode_for_savings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V123__remove_payment_mode_for_savings.sql
new file mode 100644
index 0000000..9051325
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V123__remove_payment_mode_for_savings.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_charge`
+	CHANGE COLUMN `charge_payment_mode_enum` `charge_payment_mode_enum` SMALLINT(5) NULL DEFAULT NULL AFTER `charge_calculation_enum`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V124__added_min_max_cap_for_charges.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V124__added_min_max_cap_for_charges.sql
new file mode 100755
index 0000000..eac4db7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V124__added_min_max_cap_for_charges.sql
@@ -0,0 +1,7 @@
+ALTER TABLE `m_charge`
+ADD COLUMN `min_cap` DECIMAL(19,6) NULL AFTER `is_deleted`,
+ADD COLUMN `max_cap` DECIMAL(19,6) NULL AFTER `min_cap`;
+
+ALTER TABLE `m_loan_charge`
+ADD COLUMN `min_cap` DECIMAL(19,6) NULL AFTER `waived`,
+ADD COLUMN `max_cap` DECIMAL(19,6) NULL AFTER `min_cap`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V125__added_column_for_actual_fee_amount_or_percentage.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V125__added_column_for_actual_fee_amount_or_percentage.sql
new file mode 100755
index 0000000..400c658
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V125__added_column_for_actual_fee_amount_or_percentage.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan_charge`
+	ADD COLUMN `charge_amount_or_percentage` DECIMAL(19,6) NULL DEFAULT NULL AFTER `calculation_on_amount`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V126__initial_database_structure_for_sms_outbound.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V126__initial_database_structure_for_sms_outbound.sql
new file mode 100644
index 0000000..9257f49
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V126__initial_database_structure_for_sms_outbound.sql
@@ -0,0 +1,36 @@
+DROP TABLE IF EXISTS `sms_messages_outbound`;
+CREATE TABLE `sms_messages_outbound` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '100',
+  `mobile_no` varchar(50) NOT NULL,
+  `message` varchar(1000) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKGROUP000000001` (`group_id`),
+  KEY `FKCLIENT00000001` (`client_id`),
+  CONSTRAINT `FKGROUP000000001` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKCLIENT00000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSTAFF000000001` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Permissions for CRUD on SMS outbound message tracking
+DELETE FROM `m_permission` WHERE `entity_name`='SMS';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'READ_SMS', 'SMS', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'CREATE_SMS', 'SMS', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'CREATE_SMS_CHECKER', 'SMS', 'CREATE', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'UPDATE_SMS', 'SMS', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'UPDATE_SMS_CHECKER', 'SMS', 'UPDATE', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'DELETE_SMS', 'SMS', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'DELETE_SMS_CHECKER', 'SMS', 'DELETE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V127__mobile_no_fields.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V127__mobile_no_fields.sql
new file mode 100644
index 0000000..7e9f9b1
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V127__mobile_no_fields.sql
@@ -0,0 +1,8 @@
+ALTER TABLE `m_client`
+ADD COLUMN `mobile_no` VARCHAR(50) NULL DEFAULT NULL AFTER `display_name`,
+ADD UNIQUE INDEX `mobile_no_UNIQUE` (`mobile_no` ASC);
+
+
+ALTER TABLE `m_staff`
+ADD COLUMN `mobile_no` VARCHAR(50) NULL DEFAULT NULL AFTER `display_name`,
+ADD UNIQUE INDEX `mobile_no_UNIQUE` (`mobile_no` ASC);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V128__added_loan_installment_charge.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V128__added_loan_installment_charge.sql
new file mode 100755
index 0000000..ed95e72
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V128__added_loan_installment_charge.sql
@@ -0,0 +1,19 @@
+CREATE TABLE `m_loan_installment_charge` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_charge_id` BIGINT(20) NOT NULL,
+	`loan_schedule_id` BIGINT(20) NOT NULL,
+	`due_date` DATE NULL DEFAULT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`amount_paid_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount_waived_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount_writtenoff_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount_outstanding_derived` DECIMAL(19,6) NOT NULL DEFAULT '0.000000',
+	`is_paid_derived` TINYINT(1) NOT NULL DEFAULT '0',
+	`waived` TINYINT(1) NOT NULL DEFAULT '0',
+	`amount_through_charge_payment` DECIMAL(19,6) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_loan_charge_id_charge_schedule` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+	CONSTRAINT `FK_loan_schedule_id_charge_schedule` FOREIGN KEY (`loan_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V129__client_and_group_timeline.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V129__client_and_group_timeline.sql
new file mode 100644
index 0000000..1c88248
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V129__client_and_group_timeline.sql
@@ -0,0 +1,9 @@
+ALTER TABLE  `m_client` ADD  `submittedon_date` DATE NULL DEFAULT NULL ,
+ADD  `submittedon_userid` BIGINT( 20 ) NULL DEFAULT NULL ,
+ADD  `activatedon_userid` BIGINT( 20 ) NULL DEFAULT NULL ,
+ADD  `closedon_userid` BIGINT( 20 ) NULL DEFAULT NULL ;
+
+ALTER TABLE  `m_group` ADD  `activatedon_userid` BIGINT( 20 ) NULL ,
+ADD  `submittedon_date` DATE NULL ,
+ADD  `submittedon_userid` BIGINT( 20 ) NULL ,
+ADD  `closedon_userid` BIGINT( 20 ) NULL ;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V12__add_external_id_to_couple_of_tables.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V12__add_external_id_to_couple_of_tables.sql
new file mode 100644
index 0000000..9803937
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V12__add_external_id_to_couple_of_tables.sql
@@ -0,0 +1,26 @@
+
+ALTER TABLE m_group
+	ADD UNIQUE INDEX `external_id_UNIQUE` (`external_id` ASC) ;
+
+ALTER TABLE m_product_loan
+	ADD COLUMN `external_id` VARCHAR(100) NULL  AFTER `loan_transaction_strategy_id`  ,
+	ADD UNIQUE INDEX `external_id_UNIQUE` (`external_id` ASC) ;
+
+ALTER TABLE m_staff
+	ADD COLUMN `external_id` VARCHAR(100) NULL  AFTER `display_name`  ,
+	ADD UNIQUE INDEX `external_id_UNIQUE` (`external_id` ASC) ;
+
+
+/* status_id values for client and group
+0 - Invalid
+100 - Pending
+300 - Active
+600 - Closed( or Exited)
+*/
+
+ALTER TABLE m_client
+	ADD COLUMN `status_id` INT(5) NOT NULL  DEFAULT 300 AFTER `is_deleted` ;
+
+
+ALTER TABLE m_group
+	ADD COLUMN `status_id` INT(5) NOT NULL  DEFAULT 300 AFTER `is_deleted` ;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V130__calendar-history-table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V130__calendar-history-table.sql
new file mode 100644
index 0000000..4592559
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V130__calendar-history-table.sql
@@ -0,0 +1,19 @@
+CREATE TABLE `m_calendar_history` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`calendar_id` BIGINT(20) NOT NULL,
+	`title` VARCHAR(50) NOT NULL,
+	`description` VARCHAR(100) NULL DEFAULT NULL,
+	`location` VARCHAR(50) NULL DEFAULT NULL,
+	`start_date` DATE NOT NULL,
+	`end_date` DATE NULL DEFAULT NULL,
+	`duration` SMALLINT(6) NULL DEFAULT NULL,
+	`calendar_type_enum` SMALLINT(5) NOT NULL,
+	`repeating` TINYINT(1) NOT NULL DEFAULT '0',
+	`recurrence` VARCHAR(100) NULL DEFAULT NULL,
+	`remind_by_enum` SMALLINT(5) NULL DEFAULT NULL,
+	`first_reminder` SMALLINT(11) NULL DEFAULT NULL,
+	`second_reminder` SMALLINT(11) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_calendar_m_calendar_history` (`calendar_id`),
+	CONSTRAINT `FK_m_calendar_m_calendar_history` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V131__holiday-status-column-and-permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V131__holiday-status-column-and-permissions.sql
new file mode 100644
index 0000000..e5268f6
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V131__holiday-status-column-and-permissions.sql
@@ -0,0 +1,16 @@
+ALTER TABLE `m_holiday`
+	ADD COLUMN `status_enum` INT(5) NOT NULL DEFAULT '100' AFTER `repayments_rescheduled_to`;
+
+ALTER TABLE `m_holiday`
+	DROP INDEX `holiday_name`,
+	ADD UNIQUE INDEX `holiday_name` (`name`, `from_date`);
+
+UPDATE `m_holiday` set `status_enum`=300;
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'CREATE_HOLIDAY_CHECKER', 'HOLIDAY', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'ACTIVATE_HOLIDAY', 'HOLIDAY', 'ACTIVATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'ACTIVATE_HOLIDAY_CHECKER', 'HOLIDAY', 'ACTIVATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'UPDATE_HOLIDAY', 'HOLIDAY', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'UPDATE_HOLIDAY_CHECKER', 'HOLIDAY', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'DELETE_HOLIDAY', 'HOLIDAY', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'DELETE_HOLIDAY_CHECKER', 'HOLIDAY', 'DELETE', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V132__borrower_cycle_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V132__borrower_cycle_changes.sql
new file mode 100755
index 0000000..e65437a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V132__borrower_cycle_changes.sql
@@ -0,0 +1,19 @@
+CREATE TABLE `m_product_loan_variations_borrower_cycle` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_product_id` BIGINT(20) NOT NULL DEFAULT '0',
+	`borrower_cycle_number` INT(3) NOT NULL DEFAULT '0',
+	`value_condition` INT(1) NOT NULL DEFAULT '0',
+	`param_type` INT(1) NOT NULL DEFAULT '0',
+	`default_value` DECIMAL(19,6) NOT NULL DEFAULT '0.000000',
+	`max_value` DECIMAL(19,6) NULL DEFAULT NULL,
+	`min_value` DECIMAL(19,6) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `borrower_cycle_loan_product_FK` (`loan_product_id`),
+	CONSTRAINT `borrower_cycle_loan_product_FK` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `use_borrower_cycle` TINYINT(1) NOT NULL DEFAULT '0' AFTER `include_in_borrower_cycle`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V133__adding_payment_detail_with_journal_entry.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V133__adding_payment_detail_with_journal_entry.sql
new file mode 100755
index 0000000..a434861
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V133__adding_payment_detail_with_journal_entry.sql
@@ -0,0 +1,9 @@
+ALTER TABLE `acc_gl_journal_entry`
+	ADD COLUMN `loan_transaction_id` BIGINT(20) NULL AFTER `transaction_id`,
+	ADD COLUMN `savings_transaction_id` BIGINT(20) NULL AFTER `loan_transaction_id`,
+	ADD CONSTRAINT `FK_acc_gl_journal_entry_m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+	ADD CONSTRAINT `FK_acc_gl_journal_entry_m_savings_account_transaction` FOREIGN KEY (`savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`);
+
+UPDATE acc_gl_journal_entry je SET je.savings_transaction_id=je.transaction_id,je.transaction_id=Concat('S',je.transaction_id) WHERE  je.entity_type_enum=2;
+
+UPDATE acc_gl_journal_entry je SET je.loan_transaction_id=je.transaction_id,je.transaction_id=Concat('L',je.transaction_id) WHERE  je.entity_type_enum=1;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V134_1__submitted_date_updation_for_clients.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V134_1__submitted_date_updation_for_clients.sql
new file mode 100755
index 0000000..962c095
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V134_1__submitted_date_updation_for_clients.sql
@@ -0,0 +1,5 @@
+UPDATE m_client mc SET mc.submittedon_date=mc.activation_date where mc.submittedon_date is NULL;
+UPDATE m_client mc SET mc.submittedon_date=now() where mc.submittedon_date is NULL;
+
+UPDATE m_group mg SET mg.submittedon_date=mg.activation_date where mg.submittedon_date is NULL;
+UPDATE m_group mg SET mg.submittedon_date=now() where mg.submittedon_date is NULL;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V134_2__permissions_spelling_correction.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V134_2__permissions_spelling_correction.sql
new file mode 100644
index 0000000..afcd37c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V134_2__permissions_spelling_correction.sql
@@ -0,0 +1 @@
+update m_permission set grouping="organisation" where grouping = "organistion";
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V134__added_column_value_on_c_configuration.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V134__added_column_value_on_c_configuration.sql
new file mode 100755
index 0000000..b66bd6e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V134__added_column_value_on_c_configuration.sql
@@ -0,0 +1,12 @@
+ALTER TABLE `c_configuration`
+ADD COLUMN `value` INT NULL AFTER `name`;
+
+INSERT INTO `c_configuration` (
+`id` ,
+`name` ,
+`value` ,
+`enabled`
+)
+VALUES (
+NULL ,  'penalty-wait-period',  '2',  '1'
+);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V135__added_permission_for_undo_written_off.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V135__added_permission_for_undo_written_off.sql
new file mode 100755
index 0000000..7162657
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V135__added_permission_for_undo_written_off.sql
@@ -0,0 +1 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_loan', 'UNDOWRITEOFF_LOAN', 'LOAN', 'UNDOWRITEOFF', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V136_1__update_script_strechy_parameter.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V136_1__update_script_strechy_parameter.sql
new file mode 100644
index 0000000..d5b7866
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V136_1__update_script_strechy_parameter.sql
@@ -0,0 +1 @@
+UPDATE stretchy_parameter SET `parameter_sql`='select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere (p.currency_code = \'${currencyId}\' or \'-1\'= \'${currencyId}\')\r\norder by 2' WHERE parameter_name='loanProductIdSelectAll';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V137__added_is_active_column_in_m_staff.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V137__added_is_active_column_in_m_staff.sql
new file mode 100644
index 0000000..2726e08
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V137__added_is_active_column_in_m_staff.sql
@@ -0,0 +1 @@
+ALTER TABLE `m_staff` ADD COLUMN `is_active` TINYINT(1) DEFAULT '0' AFTER `organisational_role_parent_staff_id`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V138__add_short_name_for_m_product_loan_and_m_savings_product.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V138__add_short_name_for_m_product_loan_and_m_savings_product.sql
new file mode 100644
index 0000000..656d012
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V138__add_short_name_for_m_product_loan_and_m_savings_product.sql
@@ -0,0 +1,18 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `short_name` VARCHAR(4) NULL DEFAULT NULL AFTER `id`;
+update m_product_loan set short_name=concat(LEFT(name,2), RIGHT(id,2)) where short_name is null;
+ALTER TABLE `m_product_loan`
+	ALTER `short_name` DROP DEFAULT;
+ALTER TABLE `m_product_loan`
+	CHANGE COLUMN `short_name` `short_name` VARCHAR(4) NOT NULL AFTER `id`;
+ALTER TABLE `m_product_loan`
+	ADD UNIQUE INDEX `unq_short_name` (`short_name`);
+
+ALTER TABLE `m_savings_product`
+	ADD COLUMN `short_name` VARCHAR(4) NULL DEFAULT NULL AFTER `name`;
+update m_savings_product set short_name=concat(LEFT(name,2), RIGHT(id,2)) where short_name is null;
+ALTER TABLE `m_savings_product`
+	ALTER `short_name` DROP DEFAULT;
+ALTER TABLE `m_savings_product`
+	CHANGE COLUMN `short_name` `short_name` VARCHAR(4) NOT NULL AFTER `name`,
+	ADD UNIQUE INDEX `sp_unq_short_name` (`short_name`);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V139__default_value_for_is_active_updated_to_true_in_m_staff.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V139__default_value_for_is_active_updated_to_true_in_m_staff.sql
new file mode 100644
index 0000000..8706102
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V139__default_value_for_is_active_updated_to_true_in_m_staff.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `m_staff`
+	CHANGE COLUMN `is_active` `is_active` TINYINT(1) NOT NULL DEFAULT '1' AFTER `organisational_role_parent_staff_id`;
+UPDATE `m_staff` SET `is_active`=1;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V13__add_group_and_client_pending_configuration.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V13__add_group_and_client_pending_configuration.sql
new file mode 100644
index 0000000..57e6dc9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V13__add_group_and_client_pending_configuration.sql
@@ -0,0 +1,19 @@
+
+
+/*
+default client and group status is active 300.  If you want to be able to have pending as the initial client stat
+*/
+INSERT INTO c_configuration (`name`, `enabled`) VALUES ('allow-pending-client-status', '0');
+INSERT INTO c_configuration (`name`, `enabled`) VALUES ('allow-pending-group-status', '0');
+
+
+INSERT INTO .`r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+ VALUES ('status_id', '0', 'Invalid', 'Invalid');
+INSERT INTO .`r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+ VALUES ('status_id', '100', 'Pending', 'Pending');
+INSERT INTO .`r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+ VALUES ('status_id', '300', 'Active', 'Active');
+INSERT INTO .`r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+ VALUES ('status_id', '600', 'Closed', 'Closed');
+INSERT INTO .`r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+ VALUES ('loan_status_id', '0', 'Invalid', 'Invalid');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V140_1__added_payment_detail_id_in_ac_gl_journal_entry.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V140_1__added_payment_detail_id_in_ac_gl_journal_entry.sql
new file mode 100644
index 0000000..8bdc81b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V140_1__added_payment_detail_id_in_ac_gl_journal_entry.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `acc_gl_journal_entry`
+	ADD COLUMN `payment_details_id` BIGINT(20) NULL AFTER `organization_running_balance`,
+	ADD CONSTRAINT `FK_acc_gl_journal_entry_m_payment_detail` FOREIGN KEY (`payment_details_id`) REFERENCES `m_payment_detail` (`id`);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V140__added_loan_charge_status.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V140__added_loan_charge_status.sql
new file mode 100644
index 0000000..3536ffa
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V140__added_loan_charge_status.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan_charge`
+	ADD COLUMN `is_active` TINYINT(1) NOT NULL DEFAULT '1' AFTER `max_cap`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V141__add_early_repayment_strategy.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V141__add_early_repayment_strategy.sql
new file mode 100644
index 0000000..1039bfa
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V141__add_early_repayment_strategy.sql
@@ -0,0 +1,3 @@
+INSERT INTO `ref_loan_transaction_processing_strategy` ( `code`, `name`)
+VALUES
+  ( 'early-repayment-strategy', 'Early Repayment Strategy');
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V142__read_savingsaccount_charge_permission.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V142__read_savingsaccount_charge_permission.sql
new file mode 100644
index 0000000..bdda0e8
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V142__read_savingsaccount_charge_permission.sql
@@ -0,0 +1 @@
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'READ_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'READ', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V143__create_journalentry_checker_permission.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V143__create_journalentry_checker_permission.sql
new file mode 100644
index 0000000..d78bf70
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V143__create_journalentry_checker_permission.sql
@@ -0,0 +1 @@
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'CREATE_JOURNALENTRY_CHECKER', 'JOURNALENTRY', 'CREATE', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V144__spelling_mistake_corrections.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V144__spelling_mistake_corrections.sql
new file mode 100644
index 0000000..f0b5b41
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V144__spelling_mistake_corrections.sql
@@ -0,0 +1,4 @@
+RENAME TABLE `x_table_cloumn_code_mappings` TO `x_table_column_code_mappings`;
+
+ALTER TABLE `acc_gl_journal_entry`
+	CHANGE COLUMN `is_running_balance_caculated` `is_running_balance_calculated` TINYINT(4) NOT NULL DEFAULT '0' AFTER `lastmodified_date`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V145__add_force_password_reset_in_c_configuration.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V145__add_force_password_reset_in_c_configuration.sql
new file mode 100644
index 0000000..b80b1a8
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V145__add_force_password_reset_in_c_configuration.sql
@@ -0,0 +1,17 @@
+INSERT INTO `c_configuration` (`id`, `name`, `value`, `enabled`) VALUES (NULL, 'force-password-reset-days', '0', '0');
+
+ALTER TABLE  `m_appuser` ADD  `last_time_password_updated` DATE NOT NULL ,
+ADD INDEX (  `last_time_password_updated` ) ;
+
+UPDATE  `m_appuser` SET  `last_time_password_updated` =  NOW() WHERE  `m_appuser`.`last_time_password_updated` ='0000-00-00';
+
+CREATE TABLE IF NOT EXISTS `m_appuser_previous_password` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `user_id` bigint(20) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `removal_date` date NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+
+ALTER TABLE m_appuser_previous_password
+ADD FOREIGN KEY (user_id) REFERENCES m_appuser(id);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V146__tranche_loans.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V146__tranche_loans.sql
new file mode 100644
index 0000000..badd956
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V146__tranche_loans.sql
@@ -0,0 +1,38 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `multi_disburse_loan` TINYINT(1) NOT NULL DEFAULT '0' AFTER `close_date`,
+	ADD COLUMN `max_tranche_count` INT(2) NULL DEFAULT NULL AFTER `multi_disburse_loan`,
+	ADD COLUMN `outstanding_loan_balance` DECIMAL(19,6) NULL DEFAULT NULL AFTER `max_tranche_count`;
+
+CREATE TABLE `m_loan_disbursement_detail` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_id` BIGINT(20) NOT NULL,
+	`expected_disburse_date` DATETIME NOT NULL,
+	`disbursedon_date` DATETIME NULL,
+	`principal` DECIMAL(19,6) NOT NULL,
+	`approved_principal` DECIMAL(19,6) NOT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_loan_disbursement_detail_loan_id` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+ALTER TABLE `m_loan`
+	ADD COLUMN `fixed_emi_amount` DECIMAL(19,6) NULL AFTER `loan_product_counter`,
+	ADD COLUMN `approved_principal` DECIMAL(19,6) NOT NULL AFTER `principal_amount`,
+	ADD COLUMN `max_outstanding_loan_balance` DECIMAL(19,6) NULL DEFAULT NULL AFTER `fixed_emi_amount`;
+
+UPDATE m_loan ml  SET ml.approved_principal = ml.principal_amount;
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_DISBURSEMENTDETAIL', 'DISBURSEMENTDETAIL', 'UPDATE', 0);
+
+CREATE TABLE `m_loan_term_variations` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_id` BIGINT(20) NOT NULL,
+	`term_type` SMALLINT(2) NOT NULL,
+	`applicable_from` DATE NOT NULL,
+	`term_value` DECIMAL(19,6) NOT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_loan_id_m_loan_id` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V147__tranche_loans_column_name_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V147__tranche_loans_column_name_changes.sql
new file mode 100755
index 0000000..dc92a11
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V147__tranche_loans_column_name_changes.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `m_product_loan`
+	CHANGE COLUMN `multi_disburse_loan` `allow_multiple_disbursals` TINYINT(1) NOT NULL DEFAULT '0' AFTER `close_date`,
+	CHANGE COLUMN `max_tranche_count` `max_disbursals` INT(2) NULL DEFAULT NULL AFTER `allow_multiple_disbursals`,
+	CHANGE COLUMN `outstanding_loan_balance` `max_outstanding_loan_balance` DECIMAL(19,6) NULL DEFAULT NULL AFTER `max_disbursals`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V148__overdraft_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V148__overdraft_changes.sql
new file mode 100755
index 0000000..cfe7a41
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V148__overdraft_changes.sql
@@ -0,0 +1,14 @@
+ALTER TABLE `m_savings_product`
+	ADD COLUMN `allow_overdraft` TINYINT(1) NOT NULL DEFAULT '0' AFTER `withdrawal_fee_for_transfer`,
+	ADD COLUMN `overdraft_limit` DECIMAL(19,6) NULL DEFAULT NULL AFTER `allow_overdraft`;
+
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `allow_overdraft` TINYINT(1) NOT NULL DEFAULT '0' AFTER `withdrawal_fee_for_transfer`,
+	ADD COLUMN `overdraft_limit` DECIMAL(19,6) NULL DEFAULT NULL AFTER `allow_overdraft`;
+
+ALTER TABLE `m_savings_account_transaction`
+	ADD COLUMN `overdraft_amount_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `amount`;
+
+ALTER TABLE `m_client`
+	ADD COLUMN `default_savings_product` BIGINT(20) NULL DEFAULT NULL AFTER `closedon_userid`,
+	ADD CONSTRAINT `FK_m_client_m_savings_product` FOREIGN KEY (`default_savings_product`) REFERENCES `m_savings_product` (`id`);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V149__add_created_date_savings_transaction.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V149__add_created_date_savings_transaction.sql
new file mode 100755
index 0000000..7af5f3c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V149__add_created_date_savings_transaction.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `m_savings_account_transaction`
+	ADD COLUMN `created_date` DATETIME NOT NULL AFTER `cumulative_balance_derived`;
+
+update m_savings_account_transaction sat set sat.created_date=sat.transaction_date;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V14__rename_status_id_to_enum.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V14__rename_status_id_to_enum.sql
new file mode 100644
index 0000000..8e43f31
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V14__rename_status_id_to_enum.sql
@@ -0,0 +1,8 @@
+
+ALTER TABLE m_group CHANGE COLUMN `status_id` `status_enum` INT(5) NOT NULL DEFAULT '300'  ;
+
+ALTER TABLE m_client CHANGE COLUMN `status_id` `status_enum` INT(5) NOT NULL DEFAULT '300'  ;
+
+update r_enum_value
+set enum_name = 'status_enum'
+where enum_name = 'status_id';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V150__basic_savings_report.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V150__basic_savings_report.sql
new file mode 100755
index 0000000..0b17ade
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V150__basic_savings_report.sql
@@ -0,0 +1,30 @@
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Savings Transactions', 'Pentaho', NULL, 'Savings', NULL, NULL, 0, 1);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Client Savings Summary', 'Pentaho', NULL, 'Savings', NULL, NULL, 0, 1);
+
+INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `special`, `selectOne`, `selectAll`, `parameter_sql`, `parent_id`) VALUES ( 'selectAccount', 'accountNo', 'Enter Account No', 'text', 'string', 'n/a', NULL, NULL, NULL, NULL, NULL);
+INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `special`, `selectOne`, `selectAll`, `parameter_sql`, `parent_id`) VALUES ('savingsProductIdSelectAll', 'savingsProductId', 'Product', 'select', 'number', '0', NULL, NULL, 'Y', 'select p.id, p.`name`\r\nfrom m_savings_product p\r\norder by 2', NULL);
+
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Savings Transactions'), (select id from stretchy_parameter where parameter_name='startDateSelect'), 'fromDate');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Savings Transactions'), (select id from stretchy_parameter where parameter_name='endDateSelect'), 'toDate');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Savings Transactions'), (select id from stretchy_parameter where parameter_name='selectAccount'), 'accountNo');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Savings Summary'), (select id from stretchy_parameter where parameter_name='startDateSelect'), 'fromDate');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Savings Summary'), (select id from stretchy_parameter where parameter_name='endDateSelect'), 'toDate');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Savings Summary'), (select id from stretchy_parameter where parameter_name='OfficeIdSelectOne'), 'selectOffice');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Savings Summary'), (select id from stretchy_parameter where parameter_name='savingsProductIdSelectAll'), 'selectProduct');
+
+ALTER TABLE `r_enum_value`
+	ADD COLUMN `enum_type` TINYINT(1) NOT NULL AFTER `enum_value`;
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 1, 'deposit', 'deposit', 0);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 2, 'withdrawal', 'withdrawal', 1);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 3, 'Interest Posting', 'Interest Posting', 0);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 4, 'Withdrawal Fee', 'Withdrawal Fee', 1);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 5, 'Annual Fee', 'Annual Fee', 1);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 6, 'Waive Charge', 'Waive Charge', 0);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 7, 'Pay Charge', 'Pay Charge', 1);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 12, 'Initiate Transfer', 'Initiate Transfer', 0);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 13, 'Approve Transfer', 'Approve Transfer', 0);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 14, 'Withdraw Transfer', 'Withdraw Transfer', 0);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 15, 'Reject Transfer', 'Reject Transfer', 0);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 16, 'Written-Off', 'Written-Off', 0);
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 17, 'Overdraft Interest', 'Overdraft Interest', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V151__add_default_savings_account_to_client.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V151__add_default_savings_account_to_client.sql
new file mode 100755
index 0000000..c3e040e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V151__add_default_savings_account_to_client.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_client`
+	ADD COLUMN `default_savings_account` BIGINT(20) NULL DEFAULT NULL AFTER `default_savings_product`,
+	ADD CONSTRAINT `FK_m_client_m_savings_account` FOREIGN KEY (`default_savings_account`) REFERENCES `m_savings_account` (`id`);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATESAVINGSACCOUNT_CLIENT', 'CLIENT', 'UPDATESAVINGSACCOUNT', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V152__added_grace_for_over_due.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V152__added_grace_for_over_due.sql
new file mode 100755
index 0000000..fe94c76
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V152__added_grace_for_over_due.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `grace_on_arrears_ageing` SMALLINT(5) NULL DEFAULT NULL AFTER `max_outstanding_loan_balance`;
+
+ALTER TABLE `m_loan`
+	ADD COLUMN `grace_on_arrears_ageing` SMALLINT(5) NULL DEFAULT NULL AFTER `max_outstanding_loan_balance`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V153__Insert_missed_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V153__Insert_missed_permissions.sql
new file mode 100644
index 0000000..d257259
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V153__Insert_missed_permissions.sql
@@ -0,0 +1,9 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'READ_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'READ_JOURNALENTRY', 'JOURNALENTRY', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'READ_GLACCOUNT', 'GLACCOUNT', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'READ_GLCLOSURE', 'GLCLOSURE', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'READ_HOLIDAY', 'HOLIDAY', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('jobs', 'READ_SCHEDULER', 'SCHEDULER', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'READ_PRODUCTMIX', 'PRODUCTMIX', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'READ_MEETING', 'MEETING', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('jobs', 'EXECUTEJOB_SCHEDULER', 'SCHEDULER', 'EXECUTEJOB', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V154__aging_details.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V154__aging_details.sql
new file mode 100644
index 0000000..1c1a06e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V154__aging_details.sql
@@ -0,0 +1 @@
+UPDATE `stretchy_report` SET `report_sql`='\r\nSELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as "Client Account No.",\r\n 	mc.display_name AS "Client Name",\r\n 	ml.account_no AS "Account Number",\r\n 	ml.principal_amount AS "Loan Amount",\r\n ml.principal_disbursed_derived AS "Original Principal",\r\n ml.interest_charged_derived AS "Original Interest",\r\n ml.principal_repaid_derived AS "Principal Paid",\r\n ml.interest_repaid_derived AS "Interest Paid",\r\n laa.principal_overdue_derived AS "Principal Overdue",\r\n laa.interest_overdue_derived AS "Interest Overdue",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as "Days in Arrears",\r\n\r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS "Weeks In Arrears Band",\r\n\r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n				 \'> 360\'))))) AS "Days in Arrears Band"\r\n\r\n	FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n	        AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n	    INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n	    INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id AND rev.enum_name = \'loan_status_id\'\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n	WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no\r\n' WHERE  `report_name`='Aging Detail';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V155__stretchy_into_pentaho.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V155__stretchy_into_pentaho.sql
new file mode 100644
index 0000000..63865a7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V155__stretchy_into_pentaho.sql
@@ -0,0 +1,718 @@
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report)VALUES ("Active Loans - Details(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Active Loans - Summary(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Active Loans by Disbursal Period(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Active Loans in last installment Summary(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report)VALUES ("Active Loans in last installment(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Active Loans Passed Final Maturity Summary(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Active Loans Passed Final Maturity(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Aging Detail(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Aging Summary (Arrears in Months)(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Aging Summary (Arrears in Weeks)(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Client Listing(Pentaho)", 'Pentaho', 'Client', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Client Loans Listing(Pentaho)", 'Pentaho', 'Client', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Expected Payments By Date - Basic(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Funds Disbursed Between Dates Summary by Office(Pentaho)", 'Pentaho', 'Fund', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Funds Disbursed Between Dates Summary(Pentaho)", 'Pentaho', 'Fund', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Loans Awaiting Disbursal Summary by Month(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Loans Awaiting Disbursal Summary(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Loans Awaiting Disbursal(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Loans Pending Approval(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Obligation Met Loans Details(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Obligation Met Loans Summary(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Portfolio at Risk by Branch(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Portfolio at Risk(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Rescheduled Loans(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("TxnRunningBalances(Pentaho)", 'Pentaho', 'Transaction', '(NULL)', '(NULL)', 1, 1);
+INSERT INTO stretchy_report (report_name, report_type, report_category, report_sql, description, core_report, use_report) VALUES ("Written-Off Loans(Pentaho)", 'Pentaho', 'Loan', '(NULL)', '(NULL)', 1, 1);
+
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name) VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Details(Pentaho)'),(select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),'branch');
+
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name) VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Details(Pentaho)'),(select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'loanOfficer');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name) VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Details(Pentaho)'),(select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'currencyId');
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name) VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Details(Pentaho)'),(select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name) VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Details(Pentaho)'),(select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name) VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Details(Pentaho)'),(select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),'loanPurposeId');
+
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'loanOfficer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans - Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='parTypeSelect'),
+ 'parType');
+
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans by Disbursal Period(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans by Disbursal Period(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'loanOfficer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans by Disbursal Period(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans by Disbursal Period(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans by Disbursal Period(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans by Disbursal Period(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans by Disbursal Period(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateSelect'),
+ 'startDate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans by Disbursal Period(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateSelect'),
+ 'endDate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='parTypeSelect'),
+ 'parType');
+
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans in last installment(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='parTypeSelect'),
+ 'parType');
+
+  INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Active Loans Passed Final Maturity(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Aging Detail(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Aging Summary (Arrears in Months)(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Aging Summary (Arrears in Months)(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Aging Summary (Arrears in Weeks)(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Aging Summary (Arrears in Weeks)(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Client Listing(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'selectOffice');
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Client Loans Listing(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Client Loans Listing(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Client Loans Listing(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Client Loans Listing(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Client Loans Listing(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Client Loans Listing(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Expected Payments By Date - Basic(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Expected Payments By Date - Basic(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Expected Payments By Date - Basic(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateSelect'),
+ 'startDate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Expected Payments By Date - Basic(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateSelect'),
+ 'endDate');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Funds Disbursed Between Dates Summary by Office(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Funds Disbursed Between Dates Summary by Office(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Funds Disbursed Between Dates Summary by Office(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Funds Disbursed Between Dates Summary by Office(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateSelect'),
+ 'startDate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Funds Disbursed Between Dates Summary by Office(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateSelect'),
+ 'endDate');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Funds Disbursed Between Dates Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Funds Disbursed Between Dates Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Funds Disbursed Between Dates Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateSelect'),
+ 'startDate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Funds Disbursed Between Dates Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateSelect'),
+ 'endDate');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary by Month(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary by Month(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary by Month(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary by Month(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary by Month(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary by Month(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+  INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+  INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+  INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Awaiting Disbursal(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Pending Approval(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Pending Approval(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Pending Approval(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Pending Approval(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Pending Approval(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Loans Pending Approval(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Details(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Details(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Details(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Details(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Details(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Details(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Details(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateSelect'),
+ 'startDate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Details(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateSelect'),
+ 'endDate');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Details(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='obligDateTypeSelect'),
+ 'obligDateType');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateSelect'),
+ 'Startdate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateSelect'),
+ 'Enddate');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Obligation Met Loans Summary(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='obligDateTypeSelect'),
+ 'obligDateType');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk by Branch(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk by Branch(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk by Branch(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk by Branch(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk by Branch(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk by Branch(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk by Branch(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='parTypeSelect'),
+ 'parType');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'loanOfficer');
+
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='fundIdSelectAll'),
+ 'fundId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanPurposeIdSelectAll'),
+ 'loanPurposeId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Portfolio at Risk(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='parTypeSelect'),
+ 'parType');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Rescheduled Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Rescheduled Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+  INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Rescheduled Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Rescheduled Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateSelect'),
+ 'startDate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Rescheduled Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateSelect'),
+ 'endDate');
+INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='TxnRunningBalances(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='TxnRunningBalances(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanOfficerIdSelectAll'),
+ 'Loan Officer');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='TxnRunningBalances(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateSelect'),
+ 'startDate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='TxnRunningBalances(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateSelect'),
+ 'endDate');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Written-Off Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'),
+ 'Branch');
+  INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Written-Off Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='currencyIdSelectAll'),
+ 'CurrencyId');
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Written-Off Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='loanProductIdSelectAll'),
+ 'loanProductId');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Written-Off Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateSelect'),
+ 'startDate');
+
+ INSERT INTO stretchy_report_parameter (report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id From stretchy_report sr where sr.report_name='Written-Off Loans(Pentaho)'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateSelect'),
+ 'endDate');
+
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V156__added_loan_saving_txns_pentaho.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V156__added_loan_saving_txns_pentaho.sql
new file mode 100644
index 0000000..591ed4c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V156__added_loan_saving_txns_pentaho.sql
@@ -0,0 +1,9 @@
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Client Saving Transactions', 'Pentaho', NULL, 'Savings', NULL, NULL, 0, 0);
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Saving Transactions'), (select id from stretchy_parameter where parameter_name='startDateSelect'), 'startDate');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Saving Transactions'), (select id from stretchy_parameter where parameter_name='endDateSelect'), 'endDate');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Saving Transactions'), (select id from stretchy_parameter where parameter_name='selectAccount'), 'accountNo');
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Client Loan Account Schedule', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 0);
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Loan Account Schedule'), (select id from stretchy_parameter where parameter_name='startDateSelect'), 'startDate');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Loan Account Schedule'), (select id from stretchy_parameter where parameter_name='endDateSelect'), 'endDate');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select id from stretchy_report where report_name = 'Client Loan Account Schedule'), (select id from stretchy_parameter where parameter_name='selectAccount'), 'accountNo');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V157__overdue_charge_improvements.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V157__overdue_charge_improvements.sql
new file mode 100755
index 0000000..37a2ee5
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V157__overdue_charge_improvements.sql
@@ -0,0 +1,18 @@
+ALTER TABLE `m_charge`
+	ADD COLUMN `fee_frequency` SMALLINT(5) NULL DEFAULT NULL AFTER `max_cap`;
+
+CREATE TABLE `m_loan_overdue_installment_charge` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_charge_id` BIGINT(20) NOT NULL,
+	`loan_schedule_id` BIGINT(20) NOT NULL,
+	`frequency_number` INT(10) NOT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_m_loan_overdue_installment_charge_m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+	CONSTRAINT `FK_m_loan_overdue_installment_charge_m_loan_repayment_schedule` FOREIGN KEY (`loan_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+INSERT INTO `c_configuration` (`name`, `value`, `enabled`) VALUES ('grace-on-penalty-posting', 0, 1);
+
+INSERT INTO m_loan_overdue_installment_charge (`loan_charge_id`, `loan_schedule_id`, `frequency_number`) SELECT mlc.id ,ls.id ,1  from m_loan_charge as mlc inner join m_charge mc on mc.id = mlc.charge_id inner join m_loan_repayment_schedule ls on ls.loan_id = mlc.loan_id and ls.duedate = mlc.due_for_collection_as_of_date where mc.charge_time_enum = 9 and mlc.is_active = 1;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V158__dashboard_and_navigation_queries.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V158__dashboard_and_navigation_queries.sql
new file mode 100644
index 0000000..4c0d04f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V158__dashboard_and_navigation_queries.sql
@@ -0,0 +1,17 @@
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('GroupNamesByStaff', 'Table', '', '', 'Select gr.id as id, gr.display_name as name from m_group gr where gr.level_id=1 and gr.staff_id = ${staffId}', '', 0, 1);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('ClientTrendsByDay', 'Table', '', 'Client', 'SELECT 	COUNT(cl.id) AS count, \n		cl.activation_date AS days\nFROM m_office of \n	LEFT JOIN m_client cl on of.id = cl.office_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n	AND (cl.activation_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 DAY) AND DATE(NOW()- INTERVAL 1 DAY))\nGROUP BY days', 'Retrieves the number of clients joined in last 12 days', 0, 1);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('ClientTrendsByWeek', 'Table', '', 'Client', 'SELECT 	COUNT(cl.id) AS count, \n		WEEK(cl.activation_date) AS Weeks\nFROM m_office of \n	LEFT JOIN m_client cl on of.id = cl.office_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n	AND (cl.activation_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 WEEK) AND DATE(NOW()))\nGROUP BY Weeks', '', 0, 1);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('ClientTrendsByMonth', 'Table', '', 'Client', 'SELECT 	COUNT(cl.id) AS count, \n		MONTHNAME(cl.activation_date) AS Months\nFROM m_office of \n	LEFT JOIN m_client cl on of.id = cl.office_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n	AND (cl.activation_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 MONTH) AND DATE(NOW()))\nGROUP BY Months', '', 0, 1);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('LoanTrendsByDay', 'Table', '', 'Loan', 'SELECT 	COUNT(ln.id) AS lcount, \n		ln.disbursedon_date AS days\nFROM m_office of \n	LEFT JOIN m_client cl on of.id = cl.office_id\n	LEFT JOIN m_loan ln on cl.id = ln.client_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n	AND (ln.disbursedon_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 DAY) AND DATE(NOW()- INTERVAL 1 DAY))\nGROUP BY days', 'Retrieves Number of loans disbursed for last 12 days', 0, 1);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('LoanTrendsByWeek', 'Table', '', 'Loan', 'SELECT 	COUNT(ln.id) AS lcount, \n		WEEK(ln.disbursedon_date) AS Weeks\nFROM m_office of \n	LEFT JOIN m_client cl on of.id = cl.office_id\n	LEFT JOIN m_loan ln on cl.id = ln.client_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n	AND (ln.disbursedon_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 WEEK) AND DATE(NOW()))\nGROUP BY Weeks', '', 0, 1);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('LoanTrendsByMonth', 'Table', '', 'Loan', 'SELECT 	COUNT(ln.id) AS lcount, \n		MONTHNAME(ln.disbursedon_date) AS Months\nFROM m_office of \n	LEFT JOIN m_client cl on of.id = cl.office_id\n	LEFT JOIN m_loan ln on cl.id = ln.client_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n	AND (ln.disbursedon_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 MONTH) AND DATE(NOW()))\nGROUP BY Months', '', 0, 1);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Demand_Vs_Collection', 'Table', '', 'Loan', 'select amount.AmountDue-amount.AmountPaid as AmountDue, amount.AmountPaid as AmountPaid from\n(SELECT \n(IFNULL(SUM(ls.principal_amount),0) - IFNULL(SUM(ls.principal_writtenoff_derived),0)\n + IFNULL(SUM(ls.interest_amount),0) - IFNULL(SUM(ls.interest_writtenoff_derived),0) \n - IFNULL(SUM(ls.interest_waived_derived),0)\n + IFNULL(SUM(ls.fee_charges_amount),0) - IFNULL(SUM(ls.fee_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.fee_charges_waived_derived),0)\n + IFNULL(SUM(ls.penalty_charges_amount),0) - IFNULL(SUM(ls.penalty_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.penalty_charges_waived_derived),0)\n) AS AmountDue, \n\n(IFNULL(SUM(ls.principal_completed_derived),0) - IFNULL(SUM(ls.principal_writtenoff_derived),0) + IFNULL(SUM(ls.interest_completed_derived),0) - IFNULL(SUM(ls.interest_writtenoff_derived),0) \n - IFNULL(SUM(ls.interest_waived_derived),0)\n + IFNULL(SUM(ls.fee_charges_completed_derived),0) - IFNULL(SUM(ls.fee_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.fee_charges_waived_derived),0)\n + IFNULL(SUM(ls.penalty_charges_completed_derived),0) - IFNULL(SUM(ls.penalty_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.penalty_charges_waived_derived),0)\n) AS AmountPaid\nFROM m_office of\nLEFT JOIN m_client cl ON of.id = cl.office_id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nLEFT JOIN m_loan_repayment_schedule ls ON ln.id = ls.loan_id\nWHERE ls.duedate = DATE(NOW()) AND \n (of.hierarchy LIKE CONCAT((\nSELECT ino.hierarchy\nFROM m_office ino\nWHERE ino.id = ${officeId}),"%"))) as amount', 'Demand Vs Collection', 0, 1);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Disbursal_Vs_Awaitingdisbursal', 'Table', '', 'Loan', 'select awaitinddisbursal.amount-disbursedAmount.amount as amountToBeDisburse, disbursedAmount.amount as disbursedAmount from \n(\nSELECT 	COUNT(ln.id) AS noOfLoans, \n			IFNULL(SUM(ln.principal_amount),0) AS amount\nFROM \nm_office of\nLEFT JOIN m_client cl ON cl.office_id = of.id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nWHERE \nln.expected_disbursedon_date = DATE(NOW()) AND \n(ln.loan_status_id=200 OR ln.loan_status_id=300) AND\n of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" )\n) awaitinddisbursal,\n(\nSELECT 	COUNT(ltrxn.id) as count, \n			IFNULL(SUM(ltrxn.amount),0) as amount \nFROM \nm_office of\nLEFT JOIN m_client cl ON cl.office_id = of.id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nLEFT JOIN m_loan_transaction ltrxn ON ln.id = ltrxn.loan_id\nWHERE \nltrxn.transaction_date = DATE(NOW()) AND \nltrxn.transaction_type_enum=1 AND\n of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n) disbursedAmount', 'Disbursal_Vs_Awaitingdisbursal', 0, 1);
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select sr.id from stretchy_report sr where sr.report_name = 'ClientTrendsByDay'), (select sp.id from stretchy_parameter sp where sp.parameter_name = 'OfficeIdSelectOne'), '');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select sr.id from stretchy_report sr where sr.report_name = 'ClientTrendsByWeek'), (select sp.id from stretchy_parameter sp where sp.parameter_name = 'OfficeIdSelectOne'), '');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select sr.id from stretchy_report sr where sr.report_name = 'ClientTrendsByMonth'), (select sp.id from stretchy_parameter sp where sp.parameter_name = 'OfficeIdSelectOne'), '');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select sr.id from stretchy_report sr where sr.report_name = 'LoanTrendsByDay'), (select sp.id from stretchy_parameter sp where sp.parameter_name = 'OfficeIdSelectOne'), '');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select sr.id from stretchy_report sr where sr.report_name = 'LoanTrendsByWeek'), (select sp.id from stretchy_parameter sp where sp.parameter_name = 'OfficeIdSelectOne'), '');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select sr.id from stretchy_report sr where sr.report_name = 'LoanTrendsByMonth'), (select sp.id from stretchy_parameter sp where sp.parameter_name = 'OfficeIdSelectOne'), '');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select sr.id from stretchy_report sr where sr.report_name = 'Demand_Vs_Collection'), (select sp.id from stretchy_parameter sp where sp.parameter_name = 'OfficeIdSelectOne'), '');
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES ((select sr.id from stretchy_report sr where sr.report_name = 'Disbursal_Vs_Awaitingdisbursal'), (select sp.id from stretchy_parameter sp where sp.parameter_name = 'OfficeIdSelectOne'), '');
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V159__add_transaction_id_column_m_portfolio_command_source.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V159__add_transaction_id_column_m_portfolio_command_source.sql
new file mode 100644
index 0000000..728bee5
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V159__add_transaction_id_column_m_portfolio_command_source.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_portfolio_command_source`
+	ADD COLUMN `transaction_id` VARCHAR(100) NULL DEFAULT NULL AFTER `product_id`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V15__center_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V15__center_permissions.sql
new file mode 100644
index 0000000..95482dc
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V15__center_permissions.sql
@@ -0,0 +1,7 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'READ_CENTER', 'CENTER', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_CENTER', 'CENTER', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_CENTER_CHECKER', 'CENTER', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_CENTER', 'CENTER', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_CENTER_CHECKER', 'CENTER', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_CENTER', 'CENTER', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_CENTER_CHECKER', 'CENTER', 'DELETE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V160_2__Allow_nullValue_For_principal_on_lonProduct.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V160_2__Allow_nullValue_For_principal_on_lonProduct.sql
new file mode 100644
index 0000000..e839015
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V160_2__Allow_nullValue_For_principal_on_lonProduct.sql
@@ -0,0 +1 @@
+ALTER TABLE  `m_product_loan` CHANGE  `principal_amount`  `principal_amount` DECIMAL( 19, 6 ) NULL ;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V160__standing_instruction_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V160__standing_instruction_changes.sql
new file mode 100755
index 0000000..b0918a9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V160__standing_instruction_changes.sql
@@ -0,0 +1,125 @@
+CREATE TABLE `m_account_transfer_details` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`from_office_id` BIGINT(20) NOT NULL,
+	`to_office_id` BIGINT(20) NOT NULL,
+	`from_client_id` BIGINT(20) NULL DEFAULT NULL,
+	`to_client_id` BIGINT(20) NULL DEFAULT NULL,
+	`from_savings_account_id` BIGINT(20) NULL DEFAULT NULL,
+	`to_savings_account_id` BIGINT(20) NULL DEFAULT NULL,
+	`from_loan_account_id` BIGINT(20) NULL DEFAULT NULL,
+	`to_loan_account_id` BIGINT(20) NULL DEFAULT NULL,
+	`transfer_type` SMALLINT(2) NULL DEFAULT NULL,
+	`from_savings_transaction_id` BIGINT(20) NULL DEFAULT NULL,
+	`from_loan_transaction_id` BIGINT(20) NULL DEFAULT NULL,
+	`to_savings_transaction_id` BIGINT(20) NULL DEFAULT NULL,
+	`to_loan_transaction_id` BIGINT(20) NULL DEFAULT NULL,
+	`is_reversed` TINYINT(1) NOT NULL,
+	`transaction_date` DATE NOT NULL,
+	`currency_code` VARCHAR(3) NOT NULL,
+	`currency_digits` SMALLINT(5) NOT NULL,
+	`currency_multiplesof` SMALLINT(5) NULL DEFAULT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`description` VARCHAR(200) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_account_transfer_details_from_office` (`from_office_id`),
+	INDEX `FK_m_account_transfer_details_to_office` (`to_office_id`),
+	INDEX `FK_m_account_transfer_details_from_client` (`from_client_id`),
+	INDEX `FK_m_account_transfer_details_to_client` (`to_client_id`),
+	INDEX `FK_m_account_transfer_details_from_savings_account` (`from_savings_account_id`),
+	INDEX `FK_m_account_transfer_details_to_savings_account` (`to_savings_account_id`),
+	INDEX `FK_m_account_transfer_details_from_loan_account` (`from_loan_account_id`),
+	INDEX `FK_m_account_transfer_details_to_loan_account` (`to_loan_account_id`),
+	CONSTRAINT `FK_m_account_transfer_details_from_client` FOREIGN KEY (`from_client_id`) REFERENCES `m_client` (`id`),
+	CONSTRAINT `FK_m_account_transfer_details_from_loan_account` FOREIGN KEY (`from_loan_account_id`) REFERENCES `m_loan` (`id`),
+	CONSTRAINT `FK_m_account_transfer_details_from_office` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+	CONSTRAINT `FK_m_account_transfer_details_from_savings_account` FOREIGN KEY (`from_savings_account_id`) REFERENCES `m_savings_account` (`id`),
+	CONSTRAINT `FK_m_account_transfer_details_to_client` FOREIGN KEY (`to_client_id`) REFERENCES `m_client` (`id`),
+	CONSTRAINT `FK_m_account_transfer_details_to_loan_account` FOREIGN KEY (`to_loan_account_id`) REFERENCES `m_loan` (`id`),
+	CONSTRAINT `FK_m_account_transfer_details_to_office` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`),
+	CONSTRAINT `FK_m_account_transfer_details_to_savings_account` FOREIGN KEY (`to_savings_account_id`) REFERENCES `m_savings_account` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+CREATE TABLE `m_account_transfer_standing_instructions` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`name` VARCHAR(250) NOT NULL,
+	`account_transfer_details_id` BIGINT(20) NOT NULL,
+	`priority` TINYINT(2) NOT NULL,
+	`status` TINYINT(2) NOT NULL,
+	`instruction_type` TINYINT(2) NOT NULL,
+	`amount` DECIMAL(19,6) NULL DEFAULT NULL,
+	`valid_from` DATE NOT NULL,
+	`valid_till` DATE NULL DEFAULT NULL,
+	`recurrence_type` TINYINT(1) NOT NULL,
+	`recurrence_frequency` SMALLINT(5) NULL DEFAULT NULL,
+	`recurrence_interval` SMALLINT(5) NULL DEFAULT NULL,
+	`recurrence_on_day` SMALLINT(2) NULL DEFAULT NULL,
+	`recurrence_on_month` SMALLINT(2) NULL DEFAULT NULL,
+	`last_run_date` DATE NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `name` (`name`),
+	INDEX `FK_m_standing_instructions_account_transfer_details` (`account_transfer_details_id`),
+	CONSTRAINT `FK_m_standing_instructions_account_transfer_details` FOREIGN KEY (`account_transfer_details_id`) REFERENCES `m_account_transfer_details` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+
+CREATE TABLE `m_account_transfer_transaction` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`account_transfer_details_id` BIGINT(20) NOT NULL,
+	`from_savings_transaction_id` BIGINT(20) NULL DEFAULT NULL,
+	`from_loan_transaction_id` BIGINT(20) NULL DEFAULT NULL,
+	`to_savings_transaction_id` BIGINT(20) NULL DEFAULT NULL,
+	`to_loan_transaction_id` BIGINT(20) NULL DEFAULT NULL,
+	`is_reversed` TINYINT(1) NOT NULL,
+	`transaction_date` DATE NOT NULL,
+	`currency_code` VARCHAR(3) NOT NULL,
+	`currency_digits` SMALLINT(5) NOT NULL,
+	`currency_multiplesof` SMALLINT(5) NULL DEFAULT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`description` VARCHAR(200) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_account_transfer_transaction_from_m_savings_transaction` (`from_savings_transaction_id`),
+	INDEX `FK_m_account_transfer_transaction_to_m_savings_transaction` (`to_savings_transaction_id`),
+	INDEX `FK_m_account_transfer_transaction_to_m_loan_transaction` (`to_loan_transaction_id`),
+	INDEX `FK_m_account_transfer_transaction_from_m_loan_transaction` (`from_loan_transaction_id`),
+	INDEX `FK_m_account_transfer_transaction_account_detail` (`account_transfer_details_id`),
+	CONSTRAINT `FK_m_account_transfer_transaction_account_detail` FOREIGN KEY (`account_transfer_details_id`) REFERENCES `m_account_transfer_details` (`id`),
+	CONSTRAINT `FK_m_account_transfer_transaction_from_m_loan_transaction` FOREIGN KEY (`from_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+	CONSTRAINT `FK_m_account_transfer_transaction_from_m_savings_transaction` FOREIGN KEY (`from_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
+	CONSTRAINT `FK_m_account_transfer_transaction_to_m_loan_transaction` FOREIGN KEY (`to_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+	CONSTRAINT `FK_m_account_transfer_transaction_to_m_savings_transaction` FOREIGN KEY (`to_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+
+
+INSERT INTO `m_account_transfer_details` (`from_office_id`, `to_office_id`, `from_client_id`, `to_client_id`, `from_savings_account_id`, `to_savings_account_id`, `from_loan_account_id`, `to_loan_account_id`, `from_savings_transaction_id`, `from_loan_transaction_id`, `to_savings_transaction_id`, `to_loan_transaction_id`, `is_reversed`, `transaction_date`, `currency_code`, `currency_digits`, `currency_multiplesof`, `amount`, `description`) select at.from_office_id,at.to_office_id,at.from_client_id,at.to_client_id,at.from_savings_account_id,at.to_savings_account_id,at.from_loan_account_id,at.to_loan_account_id,at.from_savings_transaction_id,at.from_loan_transaction_id,at.to_savings_transaction_id,at.to_loan_transaction_id,at.is_reversed,at.transaction_date,at.currency_code,at.currency_digits,at.currency_multiplesof,at.amount,at.description from m_savings_account_transfer at;
+
+INSERT INTO `m_account_transfer_transaction` (`account_transfer_details_id`, `from_savings_transaction_id`, `from_loan_transaction_id`, `to_savings_transaction_id`, `to_loan_transaction_id`, `is_reversed`, `transaction_date`, `currency_code`, `currency_digits`, `currency_multiplesof`, `amount`, `description`) select ad.id,ad.from_savings_transaction_id,ad.from_loan_transaction_id,ad.to_savings_transaction_id,ad.to_loan_transaction_id,ad.is_reversed,ad.transaction_date,ad.currency_code,ad.currency_digits,ad.currency_multiplesof,ad.amount,ad.description from m_account_transfer_details ad;
+
+ALTER TABLE `m_account_transfer_details`
+	DROP COLUMN `from_savings_transaction_id`,
+	DROP COLUMN `from_loan_transaction_id`,
+	DROP COLUMN `to_savings_transaction_id`,
+	DROP COLUMN `to_loan_transaction_id`,
+	DROP COLUMN `is_reversed`,
+	DROP COLUMN `transaction_date`,
+	DROP COLUMN `currency_code`,
+	DROP COLUMN `currency_digits`,
+	DROP COLUMN `currency_multiplesof`,
+	DROP COLUMN `amount`,
+	DROP COLUMN `description`;
+
+DROP TABLE `m_savings_account_transfer`;
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('account_transfer', 'READ_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('account_transfer', 'CREATE_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('account_transfer', 'UPDATE_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('account_transfer', 'DELETE_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'DELETE', 0);
+
+
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Execute Standing Instruction', 'Execute Standing Instruction', '0 0 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V161__added_accrual_batch_job.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V161__added_accrual_batch_job.sql
new file mode 100755
index 0000000..06c88d3
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V161__added_accrual_batch_job.sql
@@ -0,0 +1,6 @@
+ALTER TABLE `m_loan_repayment_schedule`
+	ADD COLUMN `accrual_interest_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `interest_waived_derived`,
+	ADD COLUMN `accrual_fee_charges_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `fee_charges_waived_derived`,
+	ADD COLUMN `accrual_penalty_charges_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `penalty_charges_waived_derived`;
+
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Add Accrual Transactions', 'Add Accrual Transactions', '0 1 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V162__overdue_charge_batch_job.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V162__overdue_charge_batch_job.sql
new file mode 100755
index 0000000..49f3177
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V162__overdue_charge_batch_job.sql
@@ -0,0 +1 @@
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Apply penalty to overdue loans', 'Apply penalty to overdue loans', '0 0 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V163__added_npa_for_loans.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V163__added_npa_for_loans.sql
new file mode 100755
index 0000000..cd86d3a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V163__added_npa_for_loans.sql
@@ -0,0 +1,7 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `overdue_days_for_npa` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_arrears_ageing`;
+
+ALTER TABLE `m_loan`
+	ADD COLUMN `is_npa` TINYINT(1) NOT NULL DEFAULT '0' AFTER `grace_on_arrears_ageing`;
+
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`) VALUES ('Update Non Performing Assets', 'Update Non Performing Assets', '0 0 0 1/1 * ? *', now());
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V164__fd_and_rd_deposit_tables.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V164__fd_and_rd_deposit_tables.sql
new file mode 100644
index 0000000..e4ba327
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V164__fd_and_rd_deposit_tables.sql
@@ -0,0 +1,237 @@
+CREATE TABLE IF NOT EXISTS `m_interest_rate_chart` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `from_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE IF NOT EXISTS `m_interest_rate_slab` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `interest_rate_chart_id` bigint(20) NOT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `period_type_enum` smallint(5) NOT NULL DEFAULT '1',
+  `from_period` int(11) NOT NULL DEFAULT '0',
+  `to_period` int(11) DEFAULT NULL,
+  `amount_range_from` decimal(19,6) DEFAULT NULL,
+  `amount_range_to` decimal(19,6) DEFAULT NULL,
+  `annual_interest_rate` decimal(19,6) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKIRS00000000000001` (`interest_rate_chart_id`),
+  CONSTRAINT `FKIRS00000000000001` FOREIGN KEY (`interest_rate_chart_id`) REFERENCES `m_interest_rate_chart` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `m_deposit_product_interest_rate_chart` (
+  `deposit_product_id` bigint(20) NOT NULL,
+  `interest_rate_chart_id` bigint(20) NOT NULL,
+  UNIQUE KEY `deposit_product_id_interest_rate_chart_id` (`deposit_product_id`,`interest_rate_chart_id`),
+  KEY `FKDPIRC00000000000002` (`interest_rate_chart_id`),
+  CONSTRAINT `FKDPIRC00000000000001` FOREIGN KEY (`deposit_product_id`) REFERENCES `m_savings_product` (`id`),
+  CONSTRAINT `FKDPIRC00000000000002` FOREIGN KEY (`interest_rate_chart_id`) REFERENCES `m_interest_rate_chart` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE IF NOT EXISTS `m_deposit_product_term_and_preclosure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_product_id` bigint(20) NOT NULL DEFAULT '0',
+  `min_deposit_term` int(11) DEFAULT NULL,
+  `max_deposit_term` int(11) DEFAULT NULL,
+  `min_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `max_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `in_multiples_of_deposit_term` int(11) DEFAULT NULL,
+  `in_multiples_of_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `interest_free_period_applicable` smallint(5) DEFAULT NULL,
+  `interest_free_from_period` int(11) DEFAULT NULL,
+  `interest_free_to_period` int(11) DEFAULT NULL,
+  `interest_free_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_applicable` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_interest` decimal(19,6) DEFAULT NULL,
+  `pre_closure_penal_interest_on_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKDPTP00000000000001` (`savings_product_id`),
+  CONSTRAINT `FKDPTP00000000000001` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE IF NOT EXISTS `m_deposit_product_recurring_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_product_id` bigint(20) NOT NULL DEFAULT '0',
+  `recurring_deposit_type_enum` smallint(5) DEFAULT NULL,
+  `recurring_deposit_frequency` int(11) DEFAULT NULL,
+  `recurring_deposit_frequency_type_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKDPRD00000000000001` (`savings_product_id`),
+  CONSTRAINT `FKDPRD00000000000001` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `m_savings_account_interest_rate_chart` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `name` varchar(100) DEFAULT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `from_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAIRC00000000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAIRC00000000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `m_savings_account_interest_rate_slab` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_interest_rate_chart_id` bigint(20) NOT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `period_type_enum` smallint(5) NOT NULL DEFAULT '1',
+  `from_period` int(11) NOT NULL DEFAULT '0',
+  `to_period` int(11) DEFAULT NULL,
+  `amount_range_from` decimal(19,6) DEFAULT NULL,
+  `amount_range_to` decimal(19,6) DEFAULT NULL,
+  `annual_interest_rate` decimal(19,6) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAIRS00000000000001` (`savings_account_interest_rate_chart_id`),
+  CONSTRAINT `FKSAIRS00000000000001` FOREIGN KEY (`savings_account_interest_rate_chart_id`) REFERENCES `m_savings_account_interest_rate_chart` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `m_deposit_account_recurring_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL DEFAULT '0',
+  `recurring_deposit_amount` decimal(19,6) DEFAULT NULL,
+  `recurring_deposit_type_enum` smallint(5) DEFAULT NULL,
+  `recurring_deposit_frequency` int(11) DEFAULT NULL,
+  `recurring_deposit_frequency_type_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKDARD00000000000001` (`savings_account_id`),
+  CONSTRAINT `FKDARD00000000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `m_deposit_account_term_and_preclosure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL DEFAULT '0',
+  `min_deposit_term` int(11) DEFAULT NULL,
+  `max_deposit_term` int(11) DEFAULT NULL,
+  `min_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `max_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `in_multiples_of_deposit_term` int(11) DEFAULT NULL,
+  `in_multiples_of_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `interest_free_period_applicable` smallint(5) DEFAULT NULL,
+  `interest_free_from_period` int(11) DEFAULT NULL,
+  `interest_free_to_period` int(11) DEFAULT NULL,
+  `interest_free_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_applicable` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_interest` decimal(19,6) DEFAULT NULL,
+  `pre_closure_penal_interest_on_enum` smallint(5) DEFAULT NULL,
+  `deposit_period` int(11) DEFAULT NULL,
+  `deposit_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `deposit_amount` decimal(19,6) DEFAULT NULL,
+  `maturity_amount` decimal(19,6) DEFAULT NULL,
+  `maturity_date` date DEFAULT NULL,
+  `on_account_closure_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKDATP00000000000001` (`savings_account_id`),
+  CONSTRAINT `FKDATP00000000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+ALTER TABLE `m_savings_product`
+	ADD COLUMN `deposit_type_enum` SMALLINT(5) NOT NULL DEFAULT '100' AFTER `description`;
+
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `deposit_type_enum` SMALLINT(5) NOT NULL DEFAULT '100' AFTER `account_type_enum`;
+
+ALTER TABLE `m_client`
+  ADD COLUMN `gender_cv_id` INT(11) NULL DEFAULT NULL AFTER `mobile_no`,
+  ADD COLUMN `date_of_birth` DATE NULL DEFAULT NULL AFTER `gender_cv_id`;
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_INTERESTRATECHART', 'INTERESTRATECHART', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_INTERESTRATECHART_CHECKER', 'INTERESTRATECHART', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_INTERESTRATECHART', 'INTERESTRATECHART', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_INTERESTRATECHART', 'INTERESTRATECHART', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_INTERESTRATECHART_CHECKER', 'INTERESTRATECHART', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_INTERESTRATECHART_CHECKER', 'INTERESTRATECHART', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_CHARTSLAB', 'CHARTSLAB', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_CHARTSLAB_CHECKER', 'CHARTSLAB', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_CHARTSLAB', 'CHARTSLAB', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_CHARTSLAB', 'CHARTSLAB', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_CHARTSLAB_CHECKER', 'CHARTSLAB', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_CHARTSLAB_CHECKER', 'CHARTSLAB', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_FIXEDDEPOSITPRODUCT_CHECKER', 'FIXEDDEPOSITPRODUCT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_FIXEDDEPOSITPRODUCT_CHECKER', 'FIXEDDEPOSITPRODUCT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_FIXEDDEPOSITPRODUCT_CHECKER', 'FIXEDDEPOSITPRODUCT', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_RECURRINGDEPOSITPRODUCT_CHECKER', 'RECURRINGDEPOSITPRODUCT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_RECURRINGDEPOSITPRODUCT_CHECKER', 'RECURRINGDEPOSITPRODUCT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_RECURRINGDEPOSITPRODUCT_CHECKER', 'RECURRINGDEPOSITPRODUCT', 'DELETE', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'READ_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'DEPOSIT_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'DEPOSIT', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'DEPOSIT_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'DEPOSIT', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'WITHDRAWAL_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'WITHDRAWAL', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'WITHDRAWAL_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'WITHDRAWAL', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'ACTIVATE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'ACTIVATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'ACTIVATE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'ACTIVATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'CALCULATEINTEREST_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'CALCULATEINTEREST', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'CALCULATEINTEREST_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'CALCULATEINTEREST', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'POSTINTEREST_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'POSTINTEREST', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'POSTINTEREST_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'POSTINTEREST', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'APPROVE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'APPROVE', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'REJECT_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'REJECT', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'WITHDRAW_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'WITHDRAW', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'APPROVALUNDO_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'APPROVALUNDO', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'CLOSE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'CLOSE', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'APPROVE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'APPROVE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'REJECT_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'REJECT', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'WITHDRAW_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'WITHDRAW', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'APPROVALUNDO_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'APPROVALUNDO', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'CLOSE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'CLOSE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'UNDOTRANSACTION_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'UNDOTRANSACTION', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'UNDOTRANSACTION_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'UNDOTRANSACTION', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'ADJUSTTRANSACTION_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'ADJUSTTRANSACTION', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'READ_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'DEPOSIT_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'DEPOSIT', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'DEPOSIT_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'DEPOSIT', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'WITHDRAWAL_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAWAL', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'WITHDRAWAL_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAWAL', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'ACTIVATE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'ACTIVATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'ACTIVATE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'ACTIVATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'CALCULATEINTEREST_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'CALCULATEINTEREST', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'CALCULATEINTEREST_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'CALCULATEINTEREST', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'POSTINTEREST_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'POSTINTEREST', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'POSTINTEREST_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'POSTINTEREST', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'APPROVE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'APPROVE', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'REJECT_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'REJECT', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'WITHDRAW_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAW', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'APPROVALUNDO_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'APPROVALUNDO', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'CLOSE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'CLOSE', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'APPROVE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'APPROVE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'REJECT_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'REJECT', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'WITHDRAW_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAW', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'APPROVALUNDO_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'APPROVALUNDO', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'CLOSE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'CLOSE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'UNDOTRANSACTION_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'UNDOTRANSACTION', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'UNDOTRANSACTION_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'UNDOTRANSACTION', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'ADJUSTTRANSACTION_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'ADJUSTTRANSACTION', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_savings', 'PREMATURECLOSE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'PREMATURECLOSE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`)                      VALUES ('transaction_savings', 'PREMATURECLOSE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'PREMATURECLOSE');
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_savings', 'PREMATURECLOSE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'PREMATURECLOSE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`)                      VALUES ('transaction_savings', 'PREMATURECLOSE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'PREMATURECLOSE');
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V165__added_permission_for_disburse_to_saving_account.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V165__added_permission_for_disburse_to_saving_account.sql
new file mode 100755
index 0000000..4eb6cf1
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V165__added_permission_for_disburse_to_saving_account.sql
@@ -0,0 +1 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_loan', 'DISBURSETOSAVINGS_LOAN', 'LOAN', 'DISBURSETOSAVINGS', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V166__added_deposit_amount_to_product_term_and_preclosure.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V166__added_deposit_amount_to_product_term_and_preclosure.sql
new file mode 100644
index 0000000..bb8e210
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V166__added_deposit_amount_to_product_term_and_preclosure.sql
@@ -0,0 +1,7 @@
+ALTER TABLE `m_deposit_product_term_and_preclosure`
+	ADD COLUMN `min_deposit_amount` DECIMAL(19,6) NULL DEFAULT NULL,
+	ADD COLUMN `max_deposit_amount` DECIMAL(19,6) NULL DEFAULT NULL,
+	ADD COLUMN `deposit_amount` DECIMAL(19,6) NULL DEFAULT NULL;
+
+ALTER TABLE  m_deposit_account_term_and_preclosure
+	ADD COLUMN  `expected_firstdepositon_date` DATE NULL DEFAULT NULL;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V167__added_columns_for_writtenOff_loans_recovered.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V167__added_columns_for_writtenOff_loans_recovered.sql
new file mode 100644
index 0000000..a2afaad
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V167__added_columns_for_writtenOff_loans_recovered.sql
@@ -0,0 +1,27 @@
+INSERT INTO `m_permission` (
+`id` ,
+`grouping` ,
+`code` ,
+`entity_name` ,
+`action_name` ,
+`can_maker_checker`
+)
+VALUES (
+NULL ,  'transaction_loan',  'RECOVERYPAYMENT_LOAN',  'LOAN',  'RECOVERYPAYMENT',  '0'
+);
+
+
+Alter table m_loan
+add column total_recovered_derived Decimal(19,6);
+
+
+INSERT INTO `acc_gl_account` (`name`, `hierarchy`, `gl_code`,`account_usage`, `classification_enum`,`description`)
+select 'Loan Recovery (Temp)', '.', '220002-Temp', 1, 4,'Temporary account to track income from Loan recovery'
+FROM m_product_loan WHERE accounting_type != 1
+limit 1;
+
+INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`financial_account_type`)
+select (select max(id) from acc_gl_account where classification_enum=4 and account_usage=1 LIMIT 1), mapping.product_id, mapping.product_type, 12
+from acc_product_mapping mapping
+where mapping.financial_account_type = 4 and mapping.product_type=1
+group by mapping.product_id;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V168__added_transfer_fixed_deposit_interest_to_linked_account.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V168__added_transfer_fixed_deposit_interest_to_linked_account.sql
new file mode 100755
index 0000000..0d412bf
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V168__added_transfer_fixed_deposit_interest_to_linked_account.sql
@@ -0,0 +1,9 @@
+ALTER TABLE `job_run_history`
+	CHANGE COLUMN `error_message` `error_message` TEXT NULL DEFAULT NULL AFTER `status`;
+
+ALTER TABLE `m_deposit_account_term_and_preclosure`
+	ADD COLUMN `transfer_interest_to_linked_account` TINYINT(1) NOT NULL DEFAULT '0' AFTER `expected_firstdepositon_date`;
+
+UPDATE `job` SET `scheduler_group`=1 WHERE  `name`='Post Interest For Savings';
+
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Transfer Interest To Savings', 'Transfer Interest To Savings', '0 2 0 1/1 * ? *', now(), 4, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 1, 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V169__update_dashboard_reports_to_core_reports_use_report_to_false.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V169__update_dashboard_reports_to_core_reports_use_report_to_false.sql
new file mode 100644
index 0000000..d6fbdb6
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V169__update_dashboard_reports_to_core_reports_use_report_to_false.sql
@@ -0,0 +1 @@
+UPDATE stretchy_report SET core_report=1, use_report=0 WHERE report_name in ('ClientTrendsByDay','ClientTrendsByWeek','ClientTrendsByMonth','LoanTrendsByDay','LoanTrendsByWeek','LoanTrendsByMonth','Demand_Vs_Collection','Disbursal_Vs_Awaitingdisbursal');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V16__drop_min_max_column_on_loan_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V16__drop_min_max_column_on_loan_table.sql
new file mode 100644
index 0000000..b41315a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V16__drop_min_max_column_on_loan_table.sql
@@ -0,0 +1,7 @@
+ALTER TABLE `m_loan`
+	DROP COLUMN `min_principal_amount`,
+	DROP COLUMN `max_principal_amount`,
+	DROP COLUMN `min_nominal_interest_rate_per_period`,
+	DROP COLUMN `max_nominal_interest_rate_per_period`,
+	DROP COLUMN `min_number_of_repayments`,
+	DROP COLUMN `max_number_of_repayments`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V170__update_deposit_accounts_maturity_details_job.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V170__update_deposit_accounts_maturity_details_job.sql
new file mode 100644
index 0000000..689d9d5
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V170__update_deposit_accounts_maturity_details_job.sql
@@ -0,0 +1 @@
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Update Deposit Accounts Maturity details', 'Update Deposit Accounts Maturity details', '0 0 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V171__added_mandatory_savings_and_rd_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V171__added_mandatory_savings_and_rd_changes.sql
new file mode 100644
index 0000000..c1ba726
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V171__added_mandatory_savings_and_rd_changes.sql
@@ -0,0 +1,71 @@
+INSERT INTO `c_configuration` (`name`, `value`, `enabled`) VALUES ('age_limit_for_senior_citizen', 65, 1);
+INSERT INTO `c_configuration` (`name`, `value`, `enabled`) VALUES ('age_limit_for_children', 15, 1);
+
+
+ALTER TABLE `m_interest_rate_slab`
+	ADD COLUMN `interest_rate_for_female` DECIMAL(19,6) NULL AFTER `annual_interest_rate`,
+	ADD COLUMN `interest_rate_for_children` DECIMAL(19,6) NULL AFTER `interest_rate_for_female`,
+	ADD COLUMN `interest_rate_for_senior_citizen` DECIMAL(19,6) NULL AFTER `interest_rate_for_children`;
+
+ALTER TABLE `m_savings_account_interest_rate_slab`
+	ADD COLUMN `interest_rate_for_female` DECIMAL(19,6) NULL AFTER `annual_interest_rate`,
+	ADD COLUMN `interest_rate_for_children` DECIMAL(19,6) NULL AFTER `interest_rate_for_female`,
+	ADD COLUMN `interest_rate_for_senior_citizen` DECIMAL(19,6) NULL AFTER `interest_rate_for_children`;
+
+ALTER TABLE `m_deposit_account_recurring_detail`
+	CHANGE COLUMN `recurring_deposit_amount` `mandatory_recommended_deposit_amount` DECIMAL(19,6) NULL DEFAULT NULL,
+	ADD COLUMN `is_mandatory` TINYINT NOT NULL DEFAULT '0',
+	ADD COLUMN `allow_withdrawal` TINYINT NOT NULL DEFAULT '0',
+	ADD COLUMN `adjust_advance_towards_future_payments` TINYINT NOT NULL DEFAULT '1',
+	ADD COLUMN `is_calendar_inherited` TINYINT NOT NULL DEFAULT '0',
+	ADD COLUMN `total_overdue_amount` DECIMAL(19,6) NULL DEFAULT NULL,
+	ADD COLUMN `no_of_overdue_installments` INT(11) NULL DEFAULT NULL,
+	DROP COLUMN `recurring_deposit_type_enum`,
+	DROP COLUMN `recurring_deposit_frequency`,
+	DROP COLUMN `recurring_deposit_frequency_type_enum`;
+
+
+ALTER TABLE `m_deposit_product_term_and_preclosure`
+	DROP COLUMN `interest_free_period_applicable`,
+	DROP COLUMN `interest_free_from_period`,
+	DROP COLUMN `interest_free_to_period`,
+	DROP COLUMN `interest_free_period_frequency_enum`;
+
+ALTER TABLE `m_deposit_product_recurring_detail`
+	ADD COLUMN `is_mandatory` TINYINT(1) NOT NULL DEFAULT '1',
+	ADD COLUMN `allow_withdrawal` TINYINT(1) NOT NULL DEFAULT '0',
+	ADD COLUMN `adjust_advance_towards_future_payments` TINYINT(1) NOT NULL DEFAULT '1',
+	DROP COLUMN `recurring_deposit_type_enum`,
+	DROP COLUMN `recurring_deposit_frequency`,
+	DROP COLUMN `recurring_deposit_frequency_type_enum`;
+
+ALTER TABLE `m_deposit_account_term_and_preclosure`
+	DROP COLUMN `interest_free_period_applicable`,
+	DROP COLUMN `interest_free_from_period`,
+	DROP COLUMN `interest_free_to_period`,
+	DROP COLUMN `interest_free_period_frequency_enum`;
+
+CREATE TABLE `m_mandatory_savings_schedule` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`savings_account_id` BIGINT(20) NOT NULL,
+	`fromdate` DATE NULL DEFAULT NULL,
+	`duedate` DATE NOT NULL,
+	`installment` SMALLINT(5) NOT NULL,
+	`deposit_amount` DECIMAL(19,6) NULL DEFAULT NULL,
+	`deposit_amount_completed_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`total_paid_in_advance_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`total_paid_late_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`completed_derived` BIT(1) NOT NULL,
+	`obligations_met_on_date` DATE NULL DEFAULT NULL,
+	`createdby_id` BIGINT(20) NULL DEFAULT NULL,
+	`created_date` DATETIME NULL DEFAULT NULL,
+	`lastmodified_date` DATETIME NULL DEFAULT NULL,
+	`lastmodifiedby_id` BIGINT(20) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FKMSS0000000001` (`savings_account_id`),
+	CONSTRAINT `FKMSS0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+);
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'READ_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'READ_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'READ', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V172__accounting_changes_for_transfers.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V172__accounting_changes_for_transfers.sql
new file mode 100644
index 0000000..3950e1a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V172__accounting_changes_for_transfers.sql
@@ -0,0 +1,21 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'READ_OFFICEGLACCOUNT', 'OFFICEGLACCOUNT', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'CREATE_OFFICEGLACCOUNT', 'OFFICEGLACCOUNT', 'CREATE', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'DELETE_OFFICEGLACCOUNT', 'OFFICEGLACCOUNT', 'DELETE', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'UPDATE_OFFICEGLACCOUNT', 'OFFICEGLACCOUNT', 'UPDATE', 0);
+
+CREATE TABLE `acc_gl_office_mapping` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`gl_account_id` BIGINT(20) NOT NULL DEFAULT '0',
+	`office_id` BIGINT(20) NOT NULL,
+	`financial_account_type` SMALLINT(5) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_office_mapping_acc_gl_account` (`gl_account_id`),
+	INDEX `FK_office_mapping_office` (`office_id`),
+	CONSTRAINT `FK_office_mapping_acc_gl_account` FOREIGN KEY (`gl_account_id`) REFERENCES `acc_gl_account` (`id`),
+	CONSTRAINT `FK_office_mapping_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V173__ppi.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V173__ppi.sql
new file mode 100644
index 0000000..7c11b7d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V173__ppi.sql
@@ -0,0 +1,103 @@
+ALTER TABLE `m_code_value`
+ADD COLUMN `code_score` INT(11) NULL AFTER `order_position`;
+
+
+CREATE TABLE IF NOT EXISTS `ppi_scores` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `score_from` int(11) NOT NULL,
+  `score_to` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+
+INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(0, 4);
+
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(5, 9);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(10, 14);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(15, 19);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(20, 24);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(25,29 );
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(30, 34);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(35, 39);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(40, 44);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(45, 49);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(50, 54);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(55, 59);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(60,64);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(65, 69);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(70, 74);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(75, 79);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(80,84);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(85, 89);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(90, 94);
+	INSERT INTO `ppi_scores` ( `score_from`, `score_to`)
+VALUES
+	(95, 100);
+
+
+
+CREATE TABLE IF NOT EXISTS `ppi_likelihoods` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) NOT NULL,
+  `name` varchar(250) NOT NULL,
+  PRIMARY KEY (`id`)
+) COLLATE='utf8_general_ci'
+ENGINE=InnoDB ;
+
+
+
+ CREATE TABLE IF NOT EXISTS `ppi_likelihoods_ppi` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `likelihood_id` bigint(20) NOT NULL,
+  `ppi_name` varchar(250) NOT NULL,
+  `enabled` int(11) NOT NULL DEFAULT '100',
+  PRIMARY KEY (`id`)
+) COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+/**PPI permission**/
+INSERT INTO `m_permission` (`id`, `grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES (NULL, 'datatable', 'UPDATE_LIKELIHOOD', 'likelihood', 'UPDATE', '0');
+
+INSERT INTO `m_permission` (`id`, `grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES (NULL, 'survey', 'REGISTER_SURVEY', 'survey', 'CREATE', '0');
+
+/**Registered table category**/
+ALTER TABLE  `x_registered_table` ADD  `category` INT NOT NULL  DEFAULT 100;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V174__remove_interest_accrual.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V174__remove_interest_accrual.sql
new file mode 100644
index 0000000..fb0dc10
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V174__remove_interest_accrual.sql
@@ -0,0 +1,3 @@
+/**Remove all existing apply interest transaction Types**/
+update m_loan_transaction set interest_portion_derived=amount where transaction_type_enum=11;
+update m_loan_transaction set transaction_type_enum=10 where transaction_type_enum=11;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V175__added_incentive_interest_rates.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V175__added_incentive_interest_rates.sql
new file mode 100755
index 0000000..3695515
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V175__added_incentive_interest_rates.sql
@@ -0,0 +1,49 @@
+ALTER TABLE `m_client`
+	ADD COLUMN `client_type_cv_id` INT(11) NULL DEFAULT NULL AFTER `default_savings_account`,
+	ADD COLUMN `client_classification_cv_id` INT(11) NULL DEFAULT NULL AFTER `client_type_cv_id`,
+	ADD CONSTRAINT `FK_m_client_type_m_code_value` FOREIGN KEY (`client_type_cv_id`) REFERENCES `m_code_value` (`id`),
+	ADD CONSTRAINT `FK_m_client_classification_m_code_value` FOREIGN KEY (`client_classification_cv_id`) REFERENCES `m_code_value` (`id`),
+	ADD CONSTRAINT `FK1_m_client_gender_m_code_value` FOREIGN KEY (`gender_cv_id`) REFERENCES `m_code_value` (`id`);
+
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) VALUES ('ClientType', 1);
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) VALUES ('ClientClassification', 1);
+
+
+CREATE TABLE `m_interest_incentives` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`interest_rate_slab_id` BIGINT(20) NOT NULL,
+	`entiry_type` SMALLINT(2) NOT NULL,
+	`attribute_name` SMALLINT(2) NOT NULL,
+	`condition_type` SMALLINT(2) NOT NULL,
+	`attribute_value` VARCHAR(50) NOT NULL,
+	`incentive_type` SMALLINT(2) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_interest_incentives_m_interest_rate_slab` (`interest_rate_slab_id`),
+	CONSTRAINT `FK_m_interest_incentives_m_interest_rate_slab` FOREIGN KEY (`interest_rate_slab_id`) REFERENCES `m_interest_rate_slab` (`id`)
+);
+
+
+CREATE TABLE `m_savings_interest_incentives` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`deposit_account_interest_rate_slab_id` BIGINT(20) NOT NULL,
+	`entiry_type` SMALLINT(2) NOT NULL,
+	`attribute_name` SMALLINT(2) NOT NULL,
+	`condition_type` SMALLINT(2) NOT NULL,
+	`attribute_value` VARCHAR(50) NOT NULL,
+	`incentive_type` SMALLINT(2) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_savings_interest_incentives_m_savings_interest_rate_slab` (`deposit_account_interest_rate_slab_id`),
+	CONSTRAINT `FK_m_savings_interest_incentives_m_savings_interest_rate_slab` FOREIGN KEY (`deposit_account_interest_rate_slab_id`) REFERENCES `m_savings_account_interest_rate_slab` (`id`)
+);
+
+ALTER TABLE `m_interest_rate_slab`
+	DROP COLUMN `interest_rate_for_female`,
+	DROP COLUMN `interest_rate_for_children`,
+	DROP COLUMN `interest_rate_for_senior_citizen`;
+
+ALTER TABLE `m_savings_account_interest_rate_slab`
+	DROP COLUMN `interest_rate_for_female`,
+	DROP COLUMN `interest_rate_for_children`,
+	DROP COLUMN `interest_rate_for_senior_citizen`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V176__updates_to_financial_activity_accounts.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V176__updates_to_financial_activity_accounts.sql
new file mode 100644
index 0000000..4c6dabb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V176__updates_to_financial_activity_accounts.sql
@@ -0,0 +1,15 @@
+ALTER TABLE acc_gl_office_mapping DROP FOREIGN KEY `FK_office_mapping_office`;
+
+ALTER TABLE acc_gl_office_mapping DROP column office_id;
+
+ALTER TABLE `acc_gl_office_mapping`
+	ALTER `financial_account_type` DROP DEFAULT;
+
+ALTER TABLE `acc_gl_office_mapping`
+	CHANGE COLUMN `financial_account_type` `financial_activity_type` SMALLINT(5) NOT NULL;
+
+ALTER TABLE `acc_gl_office_mapping`
+	ADD UNIQUE INDEX `financial_activity_type` (`financial_activity_type`);
+
+
+RENAME TABLE `acc_gl_office_mapping` TO `acc_gl_financial_activity_account`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V177__cleanup_for_client_incentives.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V177__cleanup_for_client_incentives.sql
new file mode 100755
index 0000000..c095178
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V177__cleanup_for_client_incentives.sql
@@ -0,0 +1,2 @@
+DELETE FROM `c_configuration` WHERE  `name`='age_limit_for_senior_citizen';
+DELETE FROM `c_configuration` WHERE  `name`='age_limit_for_children';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V178__updates_to_financial_activity_accounts_pt2.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V178__updates_to_financial_activity_accounts_pt2.sql
new file mode 100644
index 0000000..9587331
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V178__updates_to_financial_activity_accounts_pt2.sql
@@ -0,0 +1,16 @@
+update m_permission set entity_name="FINANCIALACTIVITYACCOUNT" where entity_name="OFFICEGLACCOUNT";
+update m_permission set code="READ_FINANCIALACTIVITYACCOUNT" where code="READ_OFFICEGLACCOUNT";
+update m_permission set code="CREATE_FINANCIALACTIVITYACCOUNT" where code="CREATE_OFFICEGLACCOUNT";
+update m_permission set code="DELETE_FINANCIALACTIVITYACCOUNT" where code="DELETE_OFFICEGLACCOUNT";
+update m_permission set code="UPDATE_FINANCIALACTIVITYACCOUNT" where code="UPDATE_OFFICEGLACCOUNT";
+
+/*Default Account for tracking account transfer*/
+INSERT INTO `acc_gl_account` (`name`, `hierarchy`, `gl_code`,`account_usage`, `classification_enum`,`description`)
+select 'Liability Transfer (Temp)', '.', '220004-Temp', 1, 2,'Temporary Liability account to track Account Transfers'
+FROM m_product_loan WHERE accounting_type != 1
+limit 1;
+
+INSERT INTO `acc_gl_financial_activity_account` (`gl_account_id`,`financial_activity_type`)
+select (select max(id) from acc_gl_account where classification_enum=2 and account_usage=1 LIMIT 1), 200
+FROM m_product_loan WHERE accounting_type != 1
+limit 1;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V179__updates_to_action_names_for_maker_checker_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V179__updates_to_action_names_for_maker_checker_permissions.sql
new file mode 100644
index 0000000..61a3178
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V179__updates_to_action_names_for_maker_checker_permissions.sql
@@ -0,0 +1,3 @@
+UPDATE m_permission
+SET action_name = CONCAT(action_name,'_CHECKER')
+WHERE code LIKE "%_CHECKER";
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V17__update_stretchy_reporting_ddl.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V17__update_stretchy_reporting_ddl.sql
new file mode 100644
index 0000000..9aab88e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V17__update_stretchy_reporting_ddl.sql
@@ -0,0 +1,90 @@
+/*
+Reason for all the tmp tables and renames is had mysql problems with foreign keys doing alter table commands
+*/
+
+CREATE TABLE `stretchy_report_tmp` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+insert into stretchy_report_tmp
+(id, report_name, report_type, report_subtype, report_category,
+report_sql, description, core_report, use_report)
+select report_id, report_name, report_type, report_subtype, report_category,
+report_sql, description, core_report, use_report
+from stretchy_report;
+
+CREATE TABLE `stretchy_parameter_tmp` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+insert into stretchy_parameter_tmp
+(id, parameter_name, parameter_variable, parameter_label, parameter_displayType, parameter_FormatType,
+parameter_default, special, selectOne, selectAll, parameter_sql, parent_id)
+select parameter_id, parameter_name, parameter_variable, parameter_label, parameter_displayType, parameter_FormatType,
+parameter_default, special, selectOne, selectAll, parameter_sql, parent_parameter_id
+from stretchy_parameter;
+
+CREATE TABLE `stretchy_report_parameter_tmp` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `report_id_name_UNIQUE` (`report_id`,`report_parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+insert into stretchy_report_parameter_tmp(report_id, parameter_id, report_parameter_name)
+select report_id, parameter_id, report_parameter_name
+from stretchy_report_parameter;
+
+
+ALTER TABLE `stretchy_parameter_tmp`
+  ADD CONSTRAINT `fk_stretchy_parameter_001`
+  FOREIGN KEY (`parent_id` )
+  REFERENCES `stretchy_parameter_tmp` (`id` )
+  ON DELETE RESTRICT ON UPDATE RESTRICT,
+  ADD INDEX `fk_stretchy_parameter_001_idx` (`parent_id` ASC);
+
+SET foreign_key_checks = 0;
+ALTER TABLE `stretchy_report_parameter_tmp`
+  ADD CONSTRAINT `fk_report_parameter_001` FOREIGN KEY (`report_id` )
+  REFERENCES `stretchy_report_tmp` (`id` ) ON DELETE CASCADE ON UPDATE RESTRICT,
+
+  ADD CONSTRAINT `fk_report_parameter_002` FOREIGN KEY (`parameter_id` )
+  REFERENCES `stretchy_parameter_tmp` (`id` ) ON DELETE RESTRICT ON UPDATE RESTRICT,
+
+  ADD INDEX `fk_report_parameter_001_idx` (`report_id` ASC),
+  ADD INDEX `fk_report_parameter_002_idx` (`parameter_id` ASC) ;
+SET foreign_key_checks = 1;
+
+drop table stretchy_report_parameter;
+drop table stretchy_report;
+drop table stretchy_parameter;
+
+rename table
+stretchy_report_tmp to stretchy_report,
+stretchy_parameter_tmp to stretchy_parameter,
+stretchy_report_parameter_tmp to stretchy_report_parameter;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V180__update_report_schemas_for_disbursed_vs_awaitingdisbursal_and_groupnamesbystaff.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V180__update_report_schemas_for_disbursed_vs_awaitingdisbursal_and_groupnamesbystaff.sql
new file mode 100644
index 0000000..c7ecafe
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V180__update_report_schemas_for_disbursed_vs_awaitingdisbursal_and_groupnamesbystaff.sql
@@ -0,0 +1,2 @@
+UPDATE `stretchy_report` SET `report_sql`='select awaitinddisbursal.amount-disbursedAmount.amount as amountToBeDisburse, disbursedAmount.amount as disbursedAmount from \n(\nSELECT 	COUNT(ln.id) AS noOfLoans, \n			IFNULL(SUM(ln.principal_amount),0) AS amount\nFROM \nm_office of\nLEFT JOIN m_client cl ON cl.office_id = of.id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nWHERE \nln.expected_disbursedon_date = DATE(NOW()) AND \n(ln.loan_status_id=200 OR ln.loan_status_id=300) AND\n of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" )\n) awaitinddisbursal,\n(\nSELECT 	COUNT(ltrxn.id) as count, \n			IFNULL(SUM(ltrxn.amount),0) as amount \nFROM \nm_office of\nLEFT JOIN m_client cl ON cl.office_id = of.id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nLEFT JOIN m_loan_transaction ltrxn ON ln.id = ltrxn.loan_id\nWHERE \nltrxn.transaction_date = DATE(NOW()) AND \nltrxn.is_reversed = 0 AND\nltrxn.transaction_type_enum=1 AND\n of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n) disbursedAmount' WHERE  `report_name`='Disbursal_Vs_Awaitingdisbursal';
+UPDATE `stretchy_report` SET `core_report`=1, `use_report`=0 WHERE `report_name`='GroupNamesByStaff';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V181__standing_instruction_logging.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V181__standing_instruction_logging.sql
new file mode 100755
index 0000000..797ac3d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V181__standing_instruction_logging.sql
@@ -0,0 +1,11 @@
+CREATE TABLE `m_account_transfer_standing_instructions_history` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`standing_instruction_id` BIGINT(20) NOT NULL,
+	`status` VARCHAR(20) NOT NULL,
+	`execution_time` DATETIME NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`error_log` VARCHAR(500) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_account_transfer_standing_instructions_history` (`standing_instruction_id`),
+	CONSTRAINT `FK_m_account_transfer_standing_instructions_m_history` FOREIGN KEY (`standing_instruction_id`) REFERENCES `m_account_transfer_standing_instructions` (`id`)
+);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V182__added_min_required_balance_to_savings_product.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V182__added_min_required_balance_to_savings_product.sql
new file mode 100644
index 0000000..c5be4a7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V182__added_min_required_balance_to_savings_product.sql
@@ -0,0 +1,7 @@
+ALTER TABLE `m_savings_product`
+	ADD COLUMN `min_required_balance` DECIMAL(19,6) NULL AFTER `overdraft_limit`,
+	ADD COLUMN `allow_overdraft_min_balance` TINYINT(1) NOT NULL DEFAULT '0' AFTER `min_required_balance`;
+
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `min_required_balance` DECIMAL(19,6) NULL AFTER `account_balance_derived`,
+	ADD COLUMN `allow_overdraft_min_balance` TINYINT(1) NOT NULL DEFAULT '0' AFTER `min_required_balance`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V183__added_min_balance_for_interest_calculation.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V183__added_min_balance_for_interest_calculation.sql
new file mode 100755
index 0000000..9cb5b46
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V183__added_min_balance_for_interest_calculation.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_savings_product`
+	ADD COLUMN `min_balance_for_interest_calculation` DECIMAL(19,6) NULL DEFAULT NULL AFTER `allow_overdraft_min_balance`;
+
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `min_balance_for_interest_calculation` DECIMAL(19,6) NULL DEFAULT NULL AFTER `allow_overdraft_min_balance`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V184__update_min_required_balance_for_savings_product.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V184__update_min_required_balance_for_savings_product.sql
new file mode 100644
index 0000000..8769b99
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V184__update_min_required_balance_for_savings_product.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_savings_product`
+	CHANGE COLUMN `allow_overdraft_min_balance` `enforce_min_required_balance` TINYINT(1) NOT NULL DEFAULT '0' AFTER `min_required_balance`;
+
+ALTER TABLE `m_savings_account`
+	CHANGE COLUMN `allow_overdraft_min_balance` `enforce_min_required_balance` TINYINT(1) NOT NULL DEFAULT '0' AFTER `min_required_balance`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V185__add_accrual_till_date_for_periodic_accrual.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V185__add_accrual_till_date_for_periodic_accrual.sql
new file mode 100755
index 0000000..d86b74f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V185__add_accrual_till_date_for_periodic_accrual.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan`
+	ADD COLUMN `accrued_till` DATE NULL DEFAULT NULL AFTER `total_recovered_derived`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V186__added_periodic_accrual_job.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V186__added_periodic_accrual_job.sql
new file mode 100755
index 0000000..724123e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V186__added_periodic_accrual_job.sql
@@ -0,0 +1,3 @@
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `scheduler_group`) VALUES ('Add Periodic Accrual Transactions', 'Add Periodic Accrual Transactions', '0 2 0 1/1 * ? *', now(), 4, 3);
+
+UPDATE `job` SET `scheduler_group`=3 WHERE  `name`='Add Accrual Transactions';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V187__added_permission_to_periodic_accrual.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V187__added_permission_to_periodic_accrual.sql
new file mode 100755
index 0000000..c2233a6
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V187__added_permission_to_periodic_accrual.sql
@@ -0,0 +1 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'EXECUTE_PERIODICACCRUALACCOUNTING', 'PERIODICACCRUALACCOUNTING', 'EXECUTE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V188__add_savingscharge_inactivate_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V188__add_savingscharge_inactivate_permissions.sql
new file mode 100644
index 0000000..0800f0f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V188__add_savingscharge_inactivate_permissions.sql
@@ -0,0 +1,5 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'INACTIVATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'INACTIVATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'INACTIVATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'INACTIVATE_CHECKER', 0);
+
+ALTER TABLE `m_savings_account_charge`
+	ADD COLUMN `inactivated_on_date` DATE NULL DEFAULT NULL AFTER `is_active`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V189__m_loan_interest_recalculation_tables.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V189__m_loan_interest_recalculation_tables.sql
new file mode 100644
index 0000000..7a9aedb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V189__m_loan_interest_recalculation_tables.sql
@@ -0,0 +1,31 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `days_in_month_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `overdue_days_for_npa`,
+	ADD COLUMN `days_in_year_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `days_in_month_enum`,
+	ADD COLUMN `interest_recalculation_enabled` TINYINT NOT NULL DEFAULT '0' AFTER `days_in_year_enum`;
+
+ALTER TABLE `m_loan`
+	ADD COLUMN `days_in_month_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `accrued_till`,
+	ADD COLUMN `days_in_year_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `days_in_month_enum`,
+	ADD COLUMN `interest_recalculation_enabled` TINYINT NOT NULL DEFAULT '0' AFTER `days_in_year_enum`;
+
+CREATE TABLE `m_product_loan_recalculation_details` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`product_id` BIGINT(20) NOT NULL,
+	`compound_type_enum` SMALLINT(5) NOT NULL,
+	`reschedule_strategy_enum` SMALLINT(5) NOT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_m_product_loan_m_product_loan_recalculation_details` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`)
+)
+COLLATE='latin1_swedish_ci'
+ENGINE=InnoDB;
+
+CREATE TABLE `m_loan_recalculation_details` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_id` BIGINT(20) NOT NULL,
+	`compound_type_enum` SMALLINT(5) NOT NULL,
+	`reschedule_strategy_enum` SMALLINT(5) NOT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_m_loan_m_loan_recalculation_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+)
+COLLATE='latin1_swedish_ci'
+ENGINE=InnoDB;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V18__update_stretchy_reporting_reportSql.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V18__update_stretchy_reporting_reportSql.sql
new file mode 100644
index 0000000..095ea4b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V18__update_stretchy_reporting_reportSql.sql
@@ -0,0 +1,6 @@
+
+UPDATE `stretchy_parameter` SET `parameter_sql`='select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r sp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r from stretchy_parameter sp\r left join stretchy_parameter spp on spp.id = sp.parent_id\r where sp.special is null\r and exists \r 	(select \'f\' \r 	from stretchy_report sr\r 	join stretchy_report_parameter srp on srp.report_id = sr.id\r 	where sr.report_name in(${reportListing})\r 	and srp.parameter_id = sp.id\r 	)\r order by sp.id' WHERE `id`='1002';
+
+UPDATE `stretchy_parameter` SET `parameter_sql`='select  r.id as report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\nrp.id as parameter_id, rp.report_parameter_name, p.parameter_name\n  from stretchy_report r\n  left join stretchy_report_parameter rp on rp.report_id = r.id \n  left join stretchy_parameter p on p.id = rp.parameter_id\n  where r.use_report is true\n  and exists\n  ( select \'f\'\n  from m_appuser_role ur \n  join m_role r on r.id = ur.role_id\n  join m_role_permission rp on rp.role_id = r.id\n  join m_permission p on p.id = rp.permission_id\n  where ur.appuser_id = ${currentUserId}\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name)) )\n  order by r.report_category, r.report_name, rp.id' WHERE `id`='1001';
+
+UPDATE `stretchy_parameter` SET `parameter_sql`='select  r.id as report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\n  rp.id as parameter_id, rp.report_parameter_name, p.parameter_name\n  from stretchy_report r\n  left join stretchy_report_parameter rp on rp.report_id = r.id\n  left join stretchy_parameter p on p.id = rp.parameter_id\n  where r.report_category = \'${reportCategory}\'\n  and r.use_report is true\n  and exists\n  (select \'f\'\n  from m_appuser_role ur \n  join m_role r on r.id = ur.role_id\n  join m_role_permission rp on rp.role_id = r.id\n  join m_permission p on p.id = rp.permission_id\n  where ur.appuser_id = ${currentUserId}\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name)) )\n  order by r.report_category, r.report_name, rp.id' WHERE `id`='1003';
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V190__add_associategroup_disassociategroup_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V190__add_associategroup_disassociategroup_permissions.sql
new file mode 100644
index 0000000..61edae5
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V190__add_associategroup_disassociategroup_permissions.sql
@@ -0,0 +1,4 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio_center', 'DISASSOCIATEGROUPS_CENTER', 'CENTER', 'DISASSOCIATEGROUPS', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio_center', 'ASSOCIATEGROUPS_CENTER', 'CENTER', 'ASSOCIATEGROUPS', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio_center', 'DISASSOCIATEGROUPS_CENTER_CHECKER', 'CENTER', 'DISASSOCIATEGROUPS_CHECKER', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio_center', 'ASSOCIATEGROUPS_CENTER_CHECKER', 'CENTER', 'ASSOCIATEGROUPS_CHECKER', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V191__update_gl_account_increase_size_of_name_col.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V191__update_gl_account_increase_size_of_name_col.sql
new file mode 100644
index 0000000..e435985
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V191__update_gl_account_increase_size_of_name_col.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `acc_gl_account`
+	CHANGE COLUMN `name` `name` VARCHAR(200) NOT NULL AFTER `id`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V192__interest_recalculate_job.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V192__interest_recalculate_job.sql
new file mode 100755
index 0000000..8616fd2
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V192__interest_recalculate_job.sql
@@ -0,0 +1,7 @@
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `scheduler_group`) VALUES ('Recalculate Interest For Loans', 'Recalculate Interest For Loans', '0 1 0 1/1 * ? *', now(), 4, 3);
+
+UPDATE `job` SET `scheduler_group`=3 WHERE  `name`='Update Non Performing Assets';
+
+UPDATE `job` SET `task_priority`=3 WHERE  `name`='Add Accrual Transactions';
+
+UPDATE `job` SET `task_priority`=2 WHERE  `name`='Add Periodic Accrual Transactions';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V193__added_column_joiningDate_for_staff.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V193__added_column_joiningDate_for_staff.sql
new file mode 100644
index 0000000..cbbbadb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V193__added_column_joiningDate_for_staff.sql
@@ -0,0 +1,2 @@
+alter table m_staff
+add column joining_date date;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V194__added_recalculatedInterestComponent_for_interest_recalculation.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V194__added_recalculatedInterestComponent_for_interest_recalculation.sql
new file mode 100755
index 0000000..07ff298
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V194__added_recalculatedInterestComponent_for_interest_recalculation.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan_repayment_schedule`
+	ADD COLUMN `recalculated_interest_component` TINYINT(1) NOT NULL DEFAULT '0';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V195__moved_rest_frequency_to_product_level.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V195__moved_rest_frequency_to_product_level.sql
new file mode 100755
index 0000000..5bf4136
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V195__moved_rest_frequency_to_product_level.sql
@@ -0,0 +1,9 @@
+ALTER TABLE `m_product_loan_recalculation_details`
+	ADD COLUMN `rest_frequency_type_enum` SMALLINT(1) NOT NULL AFTER `reschedule_strategy_enum`,
+	ADD COLUMN `rest_frequency_interval` SMALLINT(3) NOT NULL DEFAULT '0' AFTER `rest_frequency_type_enum`,
+	ADD COLUMN `rest_freqency_date` DATE NULL DEFAULT NULL AFTER `rest_frequency_interval`;
+
+ALTER TABLE `m_loan_recalculation_details`
+	ADD COLUMN `rest_frequency_type_enum` SMALLINT(1) NOT NULL AFTER `reschedule_strategy_enum`,
+	ADD COLUMN `rest_frequency_interval` SMALLINT(3) NOT NULL DEFAULT '0' AFTER `rest_frequency_type_enum`,
+	ADD COLUMN `rest_freqency_date` DATE NULL DEFAULT NULL AFTER `rest_frequency_interval`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V196__added_loan_running_balance_to_transactions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V196__added_loan_running_balance_to_transactions.sql
new file mode 100755
index 0000000..d271abe
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V196__added_loan_running_balance_to_transactions.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan_transaction`
+	ADD COLUMN `outstanding_loan_balance_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `overpayment_portion_derived`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V197__updated_loan_running_balance_of_transactions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V197__updated_loan_running_balance_of_transactions.sql
new file mode 100755
index 0000000..b7b9417
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V197__updated_loan_running_balance_of_transactions.sql
@@ -0,0 +1,13 @@
+CREATE TABLE `m_loan_transaction_temp` (
+	`id` BIGINT(20) NOT NULL,
+	`loan_id` BIGINT(20) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL DEFAULT '0',
+	`transaction_date` DATE NOT NULL
+);
+
+INSERT INTO m_loan_transaction_temp(`id`,`loan_id`,`transaction_date`,`amount`) select lt.id, lt.loan_id,lt.transaction_date,if(lt.transaction_type_enum = 1 , IFNULL(lt.amount,0),IFNULL(-lt.principal_portion_derived,0)) from m_loan_transaction lt where lt.is_reversed=0;
+
+
+UPDATE m_loan_transaction lt SET lt.outstanding_loan_balance_derived = (select sum(ltt.amount) from m_loan_transaction_temp ltt where ((ltt.transaction_date = lt.transaction_date and ltt.id  <= lt.id) or ltt.transaction_date < lt.transaction_date) and ltt.loan_id = lt.loan_id) where lt.transaction_type_enum != 10 and lt.is_reversed = 0;
+
+DROP TABLE `m_loan_transaction_temp`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V198__loan_rescheduling_tables_and_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V198__loan_rescheduling_tables_and_permissions.sql
new file mode 100644
index 0000000..95ea664
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V198__loan_rescheduling_tables_and_permissions.sql
@@ -0,0 +1,73 @@
+create table m_loan_reschedule_request (
+id bigint(20) primary key auto_increment,
+loan_id bigint(20) not null,
+status_enum smallint(5) not null,
+reschedule_from_installment smallint(5) not null comment 'Rescheduling will start from this installment',
+grace_on_principal smallint(5) null comment 'Number of installments that should be added with 0 principal amount',
+grace_on_interest smallint(5) null comment 'Number of installments that should be added with 0 interest rate',
+reschedule_from_date date not null comment 'Rescheduling will start from the installment with due date similar to this date.',
+extra_terms smallint(5)	comment 'Number of extra terms to be added to the schedule',
+interest_rate decimal(19,6)	null comment 'If provided, the interest rate for the unpaid installments will be recalculated',
+recalculate_interest tinyint(1) null comment 'If set to 1, interest will be recalculated starting from the reschedule period.',
+adjusted_due_date date null comment 'New due date for the first rescheduled installment',
+reschedule_reason_cv_id int(11) null comment 'ID of code value of reason for rescheduling',
+reschedule_reason_comment varchar(500) null comment 'Text provided in addition to the reason code value',
+submitted_on_date date not null,
+submitted_by_user_id bigint(20) not null,
+approved_on_date date null,
+approved_by_user_id bigint(20) null,
+rejected_on_date date null,
+rejected_by_user_id bigint(20) null,
+foreign key (loan_id) references m_loan(id),
+foreign key (reschedule_reason_cv_id) references m_code_value(id),
+foreign key (submitted_by_user_id) references m_appuser(id),
+foreign key (approved_by_user_id) references m_appuser(id),
+foreign key (rejected_by_user_id) references m_appuser(id)
+);
+
+create table m_loan_repayment_schedule_history (
+ id bigint(20) primary key auto_increment,
+ loan_id bigint(20) not null,
+ loan_reschedule_request_id bigint(20) null,
+ fromdate date null,
+ duedate date not null,
+ installment smallint(5) not null,
+ principal_amount decimal(19,6) null,
+ principal_completed_derived decimal(19,6) null,
+ principal_writtenoff_derived decimal(19,6) null,
+ interest_amount decimal(19,6) null,
+ interest_completed_derived decimal(19,6) null,
+ interest_writtenoff_derived decimal(19,6) null,
+ interest_waived_derived decimal(19,6) null,
+ accrual_interest_derived decimal(19,6) null,
+ fee_charges_amount decimal(19,6) null,
+ fee_charges_completed_derived decimal(19,6) null,
+ fee_charges_writtenoff_derived decimal(19,6) null,
+ fee_charges_waived_derived decimal(19,6) null,
+ accrual_fee_charges_derived decimal(19,6) null,
+ penalty_charges_amount decimal(19,6) null,
+ penalty_charges_completed_derived decimal(19,6) null,
+ penalty_charges_writtenoff_derived decimal(19,6) null,
+ penalty_charges_waived_derived decimal(19,6) null,
+ accrual_penalty_charges_derived decimal(19,6) null,
+ total_paid_in_advance_derived decimal(19,6) null,
+ total_paid_late_derived decimal(19,6) null,
+ completed_derived bit(1) not null,
+ obligations_met_on_date date null,
+ createdby_id bigint(20) null,
+ created_date datetime null,
+ lastmodified_date datetime null,
+ lastmodifiedby_id bigint(20) null,
+ foreign key (loan_id) references m_loan(id),
+ foreign key (loan_reschedule_request_id) references m_loan_reschedule_request(id)
+);
+
+alter table m_loan add rescheduledon_userid bigint(20) null after rescheduledon_date;
+
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) values ('loan_reschedule', 'READ_RESCHEDULELOAN', 'RESCHEDULELOAN', 'READ', '0');
+
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) values ('loan_reschedule', 'CREATE_RESCHEDULELOAN', 'RESCHEDULELOAN', 'CREATE', '0');
+
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) values ('loan_reschedule', 'REJECT_RESCHEDULELOAN', 'RESCHEDULELOAN', 'REJECT', '0');
+
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) values ('loan_reschedule', 'APPROVE_RESCHEDULELOAN', 'RESCHEDULELOAN', 'APPROVE', '0');
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V199__removed_extra_columns_from_schedule_history.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V199__removed_extra_columns_from_schedule_history.sql
new file mode 100755
index 0000000..b7470df
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V199__removed_extra_columns_from_schedule_history.sql
@@ -0,0 +1,20 @@
+ALTER TABLE `m_loan_repayment_schedule_history`
+	DROP COLUMN `principal_completed_derived`,
+	DROP COLUMN `principal_writtenoff_derived`,
+	DROP COLUMN `interest_completed_derived`,
+	DROP COLUMN `interest_writtenoff_derived`,
+	DROP COLUMN `interest_waived_derived`,
+	DROP COLUMN `accrual_interest_derived`,
+	DROP COLUMN `fee_charges_completed_derived`,
+	DROP COLUMN `fee_charges_writtenoff_derived`,
+	DROP COLUMN `fee_charges_waived_derived`,
+	DROP COLUMN `accrual_fee_charges_derived`,
+	DROP COLUMN `penalty_charges_completed_derived`,
+	DROP COLUMN `penalty_charges_writtenoff_derived`,
+	DROP COLUMN `penalty_charges_waived_derived`,
+	DROP COLUMN `accrual_penalty_charges_derived`,
+	DROP COLUMN `total_paid_in_advance_derived`,
+	DROP COLUMN `total_paid_late_derived`,
+	DROP COLUMN `completed_derived`,
+	DROP COLUMN `obligations_met_on_date`,
+	ADD COLUMN `version` INT(5) NOT NULL;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V19__report_maintenance_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V19__report_maintenance_permissions.sql
new file mode 100644
index 0000000..6d1cee7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V19__report_maintenance_permissions.sql
@@ -0,0 +1,14 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_REPORT', 'REPORT', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'CREATE_REPORT', 'REPORT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'CREATE_REPORT_CHECKER', 'REPORT', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'UPDATE_REPORT', 'REPORT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'UPDATE_REPORT_CHECKER', 'REPORT', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'DELETE_REPORT', 'REPORT', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'DELETE_REPORT_CHECKER', 'REPORT', 'DELETE', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V1__mifosplatform-core-ddl-latest.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V1__mifosplatform-core-ddl-latest.sql
new file mode 100644
index 0000000..339ef8b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V1__mifosplatform-core-ddl-latest.sql
@@ -0,0 +1,946 @@
+-- drop tables in base-schema
+SET foreign_key_checks = 0;
+
+-- drop accounting subsystem
+DROP TABLE IF EXISTS `acc_gl_account`;
+DROP TABLE IF EXISTS `acc_gl_closure`;
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+DROP TABLE IF EXISTS `acc_product_mapping`;
+
+-- drop portfolio subsystem
+DROP TABLE IF EXISTS `c_configuration`;
+DROP TABLE IF EXISTS `m_appuser`;
+DROP TABLE IF EXISTS `m_appuser_role`;
+DROP TABLE IF EXISTS `m_calendar`;
+DROP TABLE IF EXISTS `m_calendar_instance`;
+DROP TABLE IF EXISTS `m_charge`;
+DROP TABLE IF EXISTS `m_client`;
+DROP TABLE IF EXISTS `m_client_identifier`;
+DROP TABLE IF EXISTS `m_code`;
+DROP TABLE IF EXISTS `m_code_value`;
+DROP TABLE IF EXISTS `m_currency`;
+DROP TABLE IF EXISTS `m_deposit_account`;
+DROP TABLE IF EXISTS `m_deposit_account_transaction`;
+DROP TABLE IF EXISTS `m_document`;
+DROP TABLE IF EXISTS `m_fund`;
+DROP TABLE IF EXISTS `m_group`;
+DROP TABLE IF EXISTS `m_group_level`;
+DROP TABLE IF EXISTS `m_group_client`;
+DROP TABLE IF EXISTS `m_guarantor`;
+DROP TABLE IF EXISTS `m_loan`;
+DROP TABLE IF EXISTS `m_loan_charge`;
+DROP TABLE IF EXISTS `m_loan_arrears_aging`;
+DROP TABLE IF EXISTS `m_loan_collateral`;
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+DROP TABLE IF EXISTS `m_loan_transaction`;
+DROP TABLE IF EXISTS `m_note`;
+DROP TABLE IF EXISTS `m_office`;
+DROP TABLE IF EXISTS `m_office_transaction`;
+DROP TABLE IF EXISTS `m_organisation_currency`;
+DROP TABLE IF EXISTS `m_permission`;
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+DROP TABLE IF EXISTS `m_product_deposit`;
+DROP TABLE IF EXISTS `m_product_loan`;
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+DROP TABLE IF EXISTS `m_role`;
+DROP TABLE IF EXISTS `m_role_permission`;
+DROP TABLE IF EXISTS `m_savings_account`;
+DROP TABLE IF EXISTS `m_savings_account_transaction`;
+DROP TABLE IF EXISTS `m_savings_product`;
+DROP TABLE IF EXISTS `m_staff`;
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+DROP TABLE IF EXISTS `x_registered_table`;
+
+-- drop reporting related tables
+DROP TABLE IF EXISTS `r_enum_value`;
+DROP TABLE IF EXISTS `rpt_sequence`;
+DROP TABLE IF EXISTS `stretchy_parameter`;
+DROP TABLE IF EXISTS `stretchy_report`;
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+
+SET foreign_key_checks = 1;
+
+-- DDL for reference/lookup tables
+CREATE TABLE `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` TINYINT(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(50) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/*not a major table - just intended for database reporting use for enums and values that would be hidden in java*/
+CREATE TABLE `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* used to link MySql tables to Mifos X application tables for additional data needs */
+CREATE TABLE `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `m_calendar` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `title` varchar(50) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `m_calendar_instance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `entity_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ================= end of reference/lookup tables =============
+
+-- DDL for office related tables
+CREATE TABLE `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ============ end of office related tables ==========
+
+-- DDL for admin tables
+CREATE TABLE `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ================ end of user admin tables ===============
+
+-- DDL for organisation wide related concepts
+CREATE TABLE `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `subresource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ============ end of organisation wide related tables ===========
+
+-- DDL client/group related tables
+CREATE TABLE `m_group_level` (
+`id` INT(11) NOT NULL AUTO_INCREMENT,
+`parent_id` INT(11) NULL DEFAULT NULL,
+`super_parent` TINYINT(1) NOT NULL,
+`level_name` VARCHAR(100) NOT NULL,
+`recursable` TINYINT(1) NOT NULL,
+`can_have_clients` TINYINT(1) NOT NULL,
+PRIMARY KEY (`id`),
+INDEX `Parent_levelId_reference` (`parent_id`),
+CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
+)ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `staff_id` BIGINT(20) DEFAULT NULL,
+  `parent_id` BIGINT(20) NULL DEFAULT NULL,
+  `level_Id` INT(11) NOT NULL,
+  `hierarchy` VARCHAR(100) NULL DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`, `level_id`),
+  UNIQUE KEY `external_id` (`external_id`, `level_Id`),
+  KEY `office_id` (`office_id`),
+  KEY `staff_id` (`staff_id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_Id`) REFERENCES `m_group_level` (`id`),
+  CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `image_key` varchar(500) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ==== end of client/group related tables ==========
+
+-- DDL for loan and loan related tables
+CREATE TABLE `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `loanpurpose_cv_id` int(11) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `interest_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `disbursedon_userid` bigint(20) DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `principal_disbursed_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `rescheduledon_date` date DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `writtenoffon_date` date DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loanpurpose_codevalue` (`loanpurpose_cv_id`),
+  KEY `FK_submittedon_userid` (`submittedon_userid`),
+  KEY `FK_approvedon_userid` (`approvedon_userid`),
+  KEY `FK_rejectedon_userid` (`rejectedon_userid`),
+  KEY `FK_withdrawnon_userid` (`withdrawnon_userid`),
+  KEY `FK_disbursedon_userid` (`disbursedon_userid`),
+  KEY `FK_closedon_userid` (`closedon_userid`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_approvedon_userid` FOREIGN KEY (`approvedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_closedon_userid` FOREIGN KEY (`closedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_disbursedon_userid` FOREIGN KEY (`disbursedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loanpurpose_codevalue` FOREIGN KEY (`loanpurpose_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_rejectedon_userid` FOREIGN KEY (`rejectedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_submittedon_userid` FOREIGN KEY (`submittedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_withdrawnon_userid` FOREIGN KEY (`withdrawnon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_arrears_aging` (
+  `loan_id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `principal_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `overdue_since_date_derived` date DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_arrears_aging_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_guarantor` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_id` BIGINT(20) NOT NULL,
+        `client_reln_cv_id` INT(11) DEFAULT NULL,
+	`type_enum` SMALLINT(5) NOT NULL,
+	`entity_id` BIGINT(20) NULL DEFAULT NULL,
+	`firstname` VARCHAR(50) NULL DEFAULT NULL,
+	`lastname` VARCHAR(50) NULL DEFAULT NULL,
+	`dob` DATE NULL DEFAULT NULL,
+	`address_line_1` VARCHAR(500) NULL DEFAULT NULL,
+	`address_line_2` VARCHAR(500) NULL DEFAULT NULL,
+	`city` VARCHAR(50) NULL DEFAULT NULL,
+	`state` VARCHAR(50) NULL DEFAULT NULL,
+	`country` VARCHAR(50) NULL DEFAULT NULL,
+	`zip` VARCHAR(20) NULL DEFAULT NULL,
+	`house_phone_number` VARCHAR(20) NULL DEFAULT NULL,
+	`mobile_number` VARCHAR(20) NULL DEFAULT NULL,
+	`comment` VARCHAR(500) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_guarantor_m_loan` (`loan_id`),
+        CONSTRAINT `FK_m_guarantor_m_code_value` FOREIGN KEY (`client_reln_cv_id`) REFERENCES `m_code_value` (`id`),
+	CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_collateral` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_cv_id` int(11) NOT NULL,
+  `value` DECIMAL(19,6) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_collateral_m_loan` (`loan_id`),
+  KEY `FK_collateral_code_value` (`type_cv_id`),
+  CONSTRAINT `FK_collateral_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_collateral_code_value` FOREIGN KEY (`type_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `is_reversed` TINYINT(1) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- ======== end of loan related tables ==========
+
+CREATE TABLE `m_savings_product` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `interest_period_enum` SMALLINT(5) NOT NULL,
+  `interest_calculation_type_enum` SMALLINT(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` SMALLINT(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sp_unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_savings_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `status_enum` SMALLINT(5) NOT NULL DEFAULT 300,
+  `activation_date` DATE DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `nominal_interest_rate_per_period` decimal(19,6) NOT NULL,
+  `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) NOT NULL,
+  `interest_period_enum` SMALLINT(5) NOT NULL,
+  `interest_calculation_type_enum` SMALLINT(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` SMALLINT(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `lockedin_until_date_derived` DATE DEFAULT NULL,
+  `total_deposits_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_earned_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
+  `account_balance_derived` decimal(19,6) NOT NULL DEFAULT 0,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sa_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `sa_externalid_UNIQUE` (`external_id`),
+  KEY `FKSA00000000000001` (`client_id`),
+  KEY `FKSA00000000000002` (`group_id`),
+  KEY `FKSA00000000000003` (`product_id`),
+  CONSTRAINT `FKSA00000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA00000000000002` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSA00000000000003` FOREIGN KEY (`product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `m_savings_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `running_balance_derived` DECIMAL(19,6) NULL,
+  `balance_number_of_days_derived` INT NULL,
+  `balance_end_date_derived` DATE NULL,
+  `cumulative_balance_derived` DECIMAL(19,6) NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- end of savings account related tables
+
+-- DDL for notes associated with all client/group and financial accounts
+CREATE TABLE `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK_m_note_m_group` (`group_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_note_m_group` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- DDL for accounting sub system related tables
+CREATE TABLE `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_entry` TINYINT(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type_enum` smallint(5) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- =========== end of accounting related tables ==========
+
+-- DDL for reporting related tables
+CREATE TABLE `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `stretchy_parameter` (
+  `parameter_id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_parameter_id` int(11) NULL DEFAULT NULL,
+  PRIMARY KEY (`parameter_id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`),
+  INDEX `fk_stretchy_parameter_0001_idx` (`parent_parameter_id`),
+  CONSTRAINT `fk_stretchy_parameter_0001` FOREIGN KEY (`parent_parameter_id`) REFERENCES `stretchy_parameter` (`parameter_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `stretchy_report` (
+  `report_id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`report_id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `stretchy_report_parameter` (
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`report_id`,`parameter_id`),
+  UNIQUE KEY `report_id_name_UNIQUE` (`report_id`,`report_parameter_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- =========== end of reporting related tables ============
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V200__alter_savings_account_for_start_interest_calculation_date.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V200__alter_savings_account_for_start_interest_calculation_date.sql
new file mode 100644
index 0000000..06e529d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V200__alter_savings_account_for_start_interest_calculation_date.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_savings_account`
+ADD COLUMN `start_interest_calculation_date` DATE NULL DEFAULT NULL AFTER `min_balance_for_interest_calculation`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V201__webhooks.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V201__webhooks.sql
new file mode 100644
index 0000000..0e782d7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V201__webhooks.sql
@@ -0,0 +1,108 @@
+-- -----------------------------------------------------
+-- Table `m_hook_templates`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS `m_hook_templates` (
+  `id` SMALLINT(6) NOT NULL AUTO_INCREMENT,
+  `name` VARCHAR(45) NOT NULL,
+  PRIMARY KEY (`id`))
+ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+
+-- -----------------------------------------------------
+-- Table `m_hook`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS `m_hook` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `template_id` SMALLINT(6) NOT NULL,
+  `is_active` SMALLINT(3) NOT NULL DEFAULT 1,
+  `name` VARCHAR(45) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  INDEX `fk_template_id_idx` (`template_id` ASC),
+  CONSTRAINT `fk_template_id`
+    FOREIGN KEY (`template_id`)
+    REFERENCES `m_hook_templates` (`id`))
+ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+
+-- -----------------------------------------------------
+-- Table `m_hook_schema`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS `m_hook_schema` (
+  `id` SMALLINT(6) NOT NULL AUTO_INCREMENT,
+  `hook_template_id` SMALLINT(6) NOT NULL,
+  `field_type` VARCHAR(45) NOT NULL,
+  `field_name` VARCHAR(100) NOT NULL,
+  `placeholder` VARCHAR(100) DEFAULT NULL,
+  `optional` TINYINT(3) NOT NULL DEFAULT 0,
+  PRIMARY KEY (`id`),
+  INDEX `fk_hook_template_id_idx` (`hook_template_id` ASC),
+  CONSTRAINT `fk_hook_template_id`
+    FOREIGN KEY (`hook_template_id`)
+    REFERENCES `m_hook_templates` (`id`))
+ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+
+-- -----------------------------------------------------
+-- Table `m_hook_registered_events`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS `m_hook_registered_events` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `hook_id` BIGINT(20) NOT NULL,
+  `entity_name` VARCHAR(45) NOT NULL,
+  `action_name` VARCHAR(45) NOT NULL,
+  PRIMARY KEY (`id`),
+  INDEX `fk_hook_id_idx` (`hook_id` ASC),
+  CONSTRAINT `fk_hook_idc`
+    FOREIGN KEY (`hook_id`)
+    REFERENCES `m_hook` (`id`))
+ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+
+-- -----------------------------------------------------
+-- Table `m_hook_configuration`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS `m_hook_configuration` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `hook_id` BIGINT(20) NULL,
+  `field_type` VARCHAR(45) NOT NULL,
+  `field_name` VARCHAR(100) NOT NULL,
+  `field_value` VARCHAR(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  INDEX `fk_hook_id_idx` (`hook_id` ASC),
+  CONSTRAINT `fk_hook_id_cfg`
+    FOREIGN KEY (`hook_id`)
+    REFERENCES `m_hook` (`id`))
+ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'CREATE_HOOK', 'HOOK', 'CREATE', 0);
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'READ_HOOK', 'HOOK', 'READ', 0);
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'UPDATE_HOOK', 'HOOK', 'UPDATE', 0);
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'DELETE_HOOK', 'HOOK', 'DELETE', 0);
+
+insert into m_hook_templates values(1, "Web");
+insert into m_hook_templates values(2, "SMS Bridge");
+
+INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
+VALUES (1, 'string', 'Payload URL', 0);
+INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `placeholder`, `optional`)
+VALUES (1, 'string', 'Content Type', 'json / form', 0);
+INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
+VALUES (2, 'string', 'Payload URL', 0);
+INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
+VALUES (2, 'string', 'SMS Provider', 0);
+INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
+VALUES (2, 'string', 'Phone Number', 0);
+INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
+VALUES (2, 'string', 'SMS Provider Token', 0);
+INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
+VALUES (2, 'string', 'SMS Provider Account Id', 0);
+
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V202__savings_officer_history_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V202__savings_officer_history_table.sql
new file mode 100644
index 0000000..0ffcb82
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V202__savings_officer_history_table.sql
@@ -0,0 +1,20 @@
+CREATE TABLE `m_savings_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `savings_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_savings_officer_assignment_history_0001` (`account_id`),
+  KEY `fk_m_savings_officer_assignment_history_0002` (`savings_officer_id`),
+  CONSTRAINT `fk_m_savings_officer_assignment_history_0001` FOREIGN KEY (`account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `fk_m_savings_officer_assignment_history_0002` FOREIGN KEY (`savings_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+insert into m_permission (grouping,code,entity_name,action_name) values ('portfolio','REMOVESAVINGSOFFICER_SAVINGSACCOUNT','SAVINGSACCOUNT','REMOVESAVINGSOFFICER');
+insert into m_permission (grouping,code,entity_name,action_name) values ('portfolio','UPDATESAVINGSOFFICER_SAVINGSACCOUNT','SAVINGSACCOUNT','UPDATESAVINGSOFFICER');
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V203__added_subbmittedDate_loantransaction.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V203__added_subbmittedDate_loantransaction.sql
new file mode 100644
index 0000000..fca29d8
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V203__added_subbmittedDate_loantransaction.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `m_loan_transaction` ADD `submitted_on_date` DATE NOT NULL;
+
+UPDATE `m_loan_transaction` SET `submitted_on_date`= `transaction_date` ;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V204__insert_script_for_charges_paid_by_for_accruals.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V204__insert_script_for_charges_paid_by_for_accruals.sql
new file mode 100755
index 0000000..39d5e4e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V204__insert_script_for_charges_paid_by_for_accruals.sql
@@ -0,0 +1 @@
+insert into m_loan_charge_paid_by (`loan_transaction_id`,`loan_charge_id`,`amount`) select lt.id, lc.id,  if(lic.amount is null,lc.amount,lic.amount)  from m_loan_transaction lt join m_loan_repayment_schedule rs on rs.loan_id = lt.loan_id and rs.duedate = lt.transaction_date join m_loan_charge lc on lc.loan_id = rs.loan_id and ((lc.due_for_collection_as_of_date > rs.fromdate and lc.due_for_collection_as_of_date <= rs.duedate) or lc.charge_time_enum = 8) and lc.is_active=1 join m_loan loan on loan.id = lt.loan_id join m_product_loan lp on lp.id = loan.product_id and lp.accounting_type =3  left join m_loan_installment_charge lic on lic.loan_charge_id = lc.id and lic.loan_schedule_id = rs.id where  lt.transaction_type_enum = 10 and (lt.fee_charges_portion_derived is not null or lt.penalty_charges_portion_derived is not null) and lt.is_reversed = 0
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V205__fix_for_charge_and_interest_waiver_with_accruals.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V205__fix_for_charge_and_interest_waiver_with_accruals.sql
new file mode 100755
index 0000000..a58cdd2
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V205__fix_for_charge_and_interest_waiver_with_accruals.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_loan_charge_paid_by`
+	ADD COLUMN `installment_number` SMALLINT(5) NULL AFTER `amount`;
+	
+ALTER TABLE `m_loan_transaction`
+	ADD COLUMN `unrecognized_income_portion` DECIMAL(19,6) NULL DEFAULT NULL AFTER `overpayment_portion_derived`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V206__interest_posting_configuration.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V206__interest_posting_configuration.sql
new file mode 100644
index 0000000..7ea69a7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V206__interest_posting_configuration.sql
@@ -0,0 +1,7 @@
+ALTER TABLE c_configuration ADD `description` varchar(300) DEFAULT NULL;
+
+INSERT INTO `c_configuration` (`id`, `name`, `enabled`, `description`) 
+VALUES (NULL, 'savings-interest-posting-current-period-end', '0', "Recommended to be changed only once during start of production. When set as false(default), interest will be posted on the first date of next period. If set as true, interest will be posted on last date of current period. There is no difference in the interest amount posted.");
+
+INSERT INTO `c_configuration` (`id`, `name`, `value`, `enabled`, `description`)
+VALUES (NULL, 'financial-year-beginning-month', '1', '1', "Recommended to be changed only once during start of production. Allowed values 1 - 12 (January - December). Interest posting periods are evaluated based on this configuration.");
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V207__min_max_clients_per_group.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V207__min_max_clients_per_group.sql
new file mode 100644
index 0000000..cd21a11
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V207__min_max_clients_per_group.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `m_group_level`
+	ADD COLUMN `min_clients` INT(11) NOT NULL DEFAULT '0' AFTER `can_have_clients`,
+	ADD COLUMN `max_clients` INT(11) NOT NULL DEFAULT '0' AFTER `min_clients`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V208__min_max_clients_in_group_redux.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V208__min_max_clients_in_group_redux.sql
new file mode 100644
index 0000000..16906cf
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V208__min_max_clients_in_group_redux.sql
@@ -0,0 +1,9 @@
+INSERT INTO `c_configuration` (`id`, `name`,`value`, `enabled`, `description`) 
+VALUES (NULL, 'min-clients-in-group', '5', '0',"Minimum number of Clients that a Group should have");
+
+INSERT INTO `c_configuration` (`id`, `name`, `value`, `enabled`, `description`)
+VALUES (NULL, 'max-clients-in-group', '5', '0', "Maximum number of Clients that a Group can have");
+
+ALTER TABLE `m_group_level`
+	DROP COLUMN `min_clients`,
+	DROP COLUMN `max_clients`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V209__add_all_report_names_in_m_permission_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V209__add_all_report_names_in_m_permission_table.sql
new file mode 100644
index 0000000..605ed2c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V209__add_all_report_names_in_m_permission_table.sql
@@ -0,0 +1,41 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Active Loans - Summary(Pentaho)', 'Active Loans - Summary(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Active Loans by Disbursal Period(Pentaho)', 'Active Loans by Disbursal Period(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Active Loans in last installment Summary(Pentaho)', 'Active Loans in last installment Summary(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Active Loans in last installment(Pentaho)', 'Active Loans in last installment(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Active Loans Passed Final Maturity Summary(Pentaho)', 'Active Loans Passed Final Maturity Summary(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Active Loans Passed Final Maturity(Pentaho)', 'Active Loans Passed Final Maturity(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Aging Detail(Pentaho)', 'Aging Detail(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Aging Summary (Arrears in Months)(Pentaho)', 'Aging Summary (Arrears in Months)(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Aging Summary (Arrears in Weeks)(Pentaho)', 'Aging Summary (Arrears in Weeks)(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Client Listing(Pentaho)', 'Client Listing(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Client Loan Account Schedule', 'Client Loan Account Schedule', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Client Loans Listing(Pentaho)', 'Client Loans Listing(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Client Saving Transactions', 'Client Saving Transactions', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Client Savings Summary', 'Client Savings Summary', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_ClientSummary ', 'ClientSummary ', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_ClientTrendsByDay', 'ClientTrendsByDay', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_ClientTrendsByMonth', 'ClientTrendsByMonth', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_ClientTrendsByWeek', 'ClientTrendsByWeek', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Demand_Vs_Collection', 'Demand_Vs_Collection', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Disbursal_Vs_Awaitingdisbursal', 'Disbursal_Vs_Awaitingdisbursal', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Expected Payments By Date - Basic(Pentaho)', 'Expected Payments By Date - Basic(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Funds Disbursed Between Dates Summary by Office(Pentaho)', 'Funds Disbursed Between Dates Summary by Office(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Funds Disbursed Between Dates Summary(Pentaho)', 'Funds Disbursed Between Dates Summary(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_GroupNamesByStaff', 'GroupNamesByStaff', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_GroupSavingSummary', 'GroupSavingSummary', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_LoanCyclePerProduct', 'LoanCyclePerProduct', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Loans Awaiting Disbursal Summary by Month(Pentaho)', 'Loans Awaiting Disbursal Summary by Month(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Loans Awaiting Disbursal Summary(Pentaho)', 'Loans Awaiting Disbursal Summary(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Loans Awaiting Disbursal(Pentaho)', 'Loans Awaiting Disbursal(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Loans Pending Approval(Pentaho)', 'Loans Pending Approval(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_LoanTrendsByDay', 'LoanTrendsByDay', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_LoanTrendsByMonth', 'LoanTrendsByMonth', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_LoanTrendsByWeek', 'LoanTrendsByWeek', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Obligation Met Loans Details(Pentaho)', 'Obligation Met Loans Details(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Obligation Met Loans Summary(Pentaho)', 'Obligation Met Loans Summary(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Portfolio at Risk by Branch(Pentaho)', 'Portfolio at Risk by Branch(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Portfolio at Risk(Pentaho)', 'Portfolio at Risk(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Rescheduled Loans(Pentaho)', 'Rescheduled Loans(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Savings Transactions', 'Savings Transactions', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_TxnRunningBalances(Pentaho)', 'TxnRunningBalances(Pentaho)', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Written-Off Loans(Pentaho)', 'Written-Off Loans(Pentaho)', 'READ', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V20__report_maint_perms_really_configuration.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V20__report_maint_perms_really_configuration.sql
new file mode 100644
index 0000000..2667e83
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V20__report_maint_perms_really_configuration.sql
@@ -0,0 +1,2 @@
+
+update m_permission set grouping = 'configuration' where entity_name = 'report';
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V210__track_manually_adjusted_transactions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V210__track_manually_adjusted_transactions.sql
new file mode 100644
index 0000000..d6a9101
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V210__track_manually_adjusted_transactions.sql
@@ -0,0 +1,2 @@
+Alter table m_loan_transaction
+Add column manually_adjusted_or_reversed tinyint(1) default 0;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V211__minimum_days_between_disbursal_and_first_repayment.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V211__minimum_days_between_disbursal_and_first_repayment.sql
new file mode 100644
index 0000000..7b89658
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V211__minimum_days_between_disbursal_and_first_repayment.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `min_days_between_disbursal_and_first_repayment` INT(3) NULL AFTER `interest_recalculation_enabled`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V212__add_NthDay_and_DayOfWeek_columns_loan.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V212__add_NthDay_and_DayOfWeek_columns_loan.sql
new file mode 100644
index 0000000..2fa44f7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V212__add_NthDay_and_DayOfWeek_columns_loan.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `m_loan` 
+ ADD COLUMN `repayment_frequency_nth_day_enum` SMALLINT(5) NOT NULL DEFAULT '0' AFTER `repayment_period_frequency_enum`,
+ ADD COLUMN `repayment_frequency_day_of_week_enum` SMALLINT(5) NOT NULL DEFAULT '0' AFTER `repayment_frequency_nth_day_enum`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V213__NthDay_and_DayOfWeek_columns_should_be_nullable.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V213__NthDay_and_DayOfWeek_columns_should_be_nullable.sql
new file mode 100644
index 0000000..d259631
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V213__NthDay_and_DayOfWeek_columns_should_be_nullable.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `m_loan`
+	CHANGE COLUMN `repayment_frequency_nth_day_enum` `repayment_frequency_nth_day_enum` SMALLINT(5) NULL DEFAULT '0' AFTER `repayment_period_frequency_enum`,
+	CHANGE COLUMN `repayment_frequency_day_of_week_enum` `repayment_frequency_day_of_week_enum` SMALLINT(5) NULL DEFAULT '0' AFTER `repayment_frequency_nth_day_enum`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V214__alter_table_add_create_SI_at_disbursement.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V214__alter_table_add_create_SI_at_disbursement.sql
new file mode 100644
index 0000000..c33a946
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V214__alter_table_add_create_SI_at_disbursement.sql
@@ -0,0 +1 @@
+alter table  `m_loan` add  `create_standing_instruction_at_disbursement` tinyint(1) null;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V215__guarantee_on_hold_fund_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V215__guarantee_on_hold_fund_changes.sql
new file mode 100644
index 0000000..d286f34
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V215__guarantee_on_hold_fund_changes.sql
@@ -0,0 +1,18 @@
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `on_hold_funds_derived` DECIMAL(19,6) NULL DEFAULT NULL;
+	
+ALTER TABLE `m_portfolio_account_associations`
+	ADD COLUMN `association_type_enum` SMALLINT(1) NOT NULL DEFAULT '1';
+
+CREATE TABLE `m_product_loan_guarantee_details` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_product_id` BIGINT(20) NOT NULL,
+	`mandatory_guarantee` DECIMAL(19,5) NOT NULL,
+	`minimum_guarantee_from_own_funds` DECIMAL(19,5) NULL,
+	`minimum_guarantee_from_guarantor_funds` DECIMAL(19,5) NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_guarantee_details_loan_product` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+);	
+
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `hold_guarantee_funds` TINYINT(1) NOT NULL DEFAULT '0';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V216__adding_loan_proposed_amount_to_loan.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V216__adding_loan_proposed_amount_to_loan.sql
new file mode 100644
index 0000000..09f24dc
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V216__adding_loan_proposed_amount_to_loan.sql
@@ -0,0 +1,2 @@
+   	ALTER TABLE `m_loan` ADD COLUMN `principal_amount_proposed` DECIMAL(19,6) NOT NULL AFTER `currency_multiplesof`;
+	UPDATE m_loan SET principal_amount_proposed = approved_principal;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V217__client_substatus_and_codevalue_description.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V217__client_substatus_and_codevalue_description.sql
new file mode 100644
index 0000000..8edd88d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V217__client_substatus_and_codevalue_description.sql
@@ -0,0 +1,12 @@
+
+ALTER TABLE `m_client`
+	ADD COLUMN `sub_status` INT(11) NULL DEFAULT NULL AFTER `status_enum`,
+	ADD CONSTRAINT `FK_m_client_substatus_m_code_value` FOREIGN KEY (`sub_status`) REFERENCES `m_code_value` (`id`);
+	
+	
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) VALUES ('ClientSubStatus', 1);
+
+
+ALTER TABLE `m_code_value`
+	ADD COLUMN `code_description` VARCHAR(500) NULL DEFAULT NULL AFTER `code_value`;
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V218__add_user_and_datetime_for_loan_savings_transactions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V218__add_user_and_datetime_for_loan_savings_transactions.sql
new file mode 100644
index 0000000..9790d08
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V218__add_user_and_datetime_for_loan_savings_transactions.sql
@@ -0,0 +1,8 @@
+ALTER TABLE `m_loan_transaction`
+	ADD COLUMN 	(
+		`created_date` DATETIME,
+		`appuser_id` BIGINT(20)
+	);
+
+ALTER TABLE `m_savings_account_transaction`
+	ADD COLUMN `appuser_id` BIGINT(20);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V219__guarantor_on_hold_fund_changes_for_account.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V219__guarantor_on_hold_fund_changes_for_account.sql
new file mode 100755
index 0000000..2cf7560
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V219__guarantor_on_hold_fund_changes_for_account.sql
@@ -0,0 +1,47 @@
+ALTER TABLE `m_portfolio_account_associations`
+	ADD COLUMN `is_active` TINYINT(1) NOT NULL DEFAULT '1';
+	
+ALTER TABLE `m_guarantor`
+	ADD COLUMN `is_active` TINYINT(1) NOT NULL DEFAULT '1';
+	
+CREATE TABLE `m_guarantor_funding_details` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`guarantor_id` BIGINT(20) NOT NULL,
+	`account_associations_id` BIGINT(20) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`amount_released_derived` DECIMAL(19,6) NULL,
+	`amount_remaining_derived` DECIMAL(19,6) NULL,
+	`amount_transfered_derived` DECIMAL(19,6) NULL,
+	`status_enum` SMALLINT(3) NOT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_m_guarantor_fund_details_m_guarantor` FOREIGN KEY (`guarantor_id`) REFERENCES `m_guarantor` (`id`),
+	CONSTRAINT `FK_m_guarantor_fund_details_account_associations_id` FOREIGN KEY (`account_associations_id`) REFERENCES `m_portfolio_account_associations` (`id`)
+);
+
+CREATE TABLE `m_deposit_account_on_hold_transaction` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`savings_account_id` BIGINT(20) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`transaction_type_enum` SMALLINT(1) NOT NULL,
+	`transaction_date` DATE NOT NULL,
+	`is_reversed` TINYINT(1) NOT NULL DEFAULT '0',
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_deposit_on_hold_transaction_m_savings_account` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+);
+
+CREATE TABLE `m_guarantor_transaction` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`guarantor_fund_detail_id` BIGINT(20) NOT NULL,
+	`loan_transaction_id` BIGINT(20) NOT NULL,
+	`deposit_on_hold_transaction_id` BIGINT(20) NOT NULL,
+	`is_reversed` TINYINT(1) NOT NULL DEFAULT '0',
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_guarantor_transaction_m_deposit_account_on_hold_transaction` FOREIGN KEY (`deposit_on_hold_transaction_id`) REFERENCES `m_deposit_account_on_hold_transaction` (`id`),
+	CONSTRAINT `FK_guarantor_transaction_guarantor_fund_detail` FOREIGN KEY (`guarantor_fund_detail_id`) REFERENCES `m_guarantor_funding_details` (`id`),
+	CONSTRAINT `FK_guarantor_transaction_m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+);
+
+ALTER TABLE `m_loan`
+	ADD COLUMN `guarantee_amount_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `interest_recalculation_enabled`,
+	ADD COLUMN `guarantee_outstanding_amount_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `guarantee_amount_derived`;
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V21__activation-permissions-for-clients.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V21__activation-permissions-for-clients.sql
new file mode 100644
index 0000000..98cf78e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V21__activation-permissions-for-clients.sql
@@ -0,0 +1,8 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ACTIVATE_CLIENT', 'CLIENT', 'ACTIVATE', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ACTIVATE_CLIENT_CHECKER', 'CLIENT', 'ACTIVATE', 0);
+
+
+ALTER TABLE `m_client`
+CHANGE COLUMN `external_id` `external_id` VARCHAR(100) NULL DEFAULT NULL AFTER `account_no`,
+CHANGE COLUMN `status_enum` `status_enum` INT(5) NOT NULL DEFAULT '300' AFTER `external_id`,
+CHANGE COLUMN `joined_date` `activation_date` DATE NULL DEFAULT NULL  AFTER `status_enum`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V220__account_number_preferences.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V220__account_number_preferences.sql
new file mode 100644
index 0000000..e874bfb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V220__account_number_preferences.sql
@@ -0,0 +1,22 @@
+CREATE TABLE `c_account_number_format` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`account_type_enum` SMALLINT(1) NOT NULL,
+	`prefix_type_enum` SMALLINT(2) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `account_type_enum` (`account_type_enum`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+;
+
+
+/*permissions*/
+
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'CREATE_ACCOUNTNUMBERFORMAT', 'ACCOUNTNUMBERFORMAT', 'CREATE', 0);
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'READ_ACCOUNTNUMBERFORMAT', 'ACCOUNTNUMBERFORMAT', 'READ', 0);
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'UPDATE_ACCOUNTNUMBERFORMAT', 'ACCOUNTNUMBERFORMAT', 'UPDATE', 0);
+insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('configuration', 'DELETE_ACCOUNTNUMBERFORMAT', 'HOOK', 'DELETE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V221__add_version_for_m_savings_account.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V221__add_version_for_m_savings_account.sql
new file mode 100644
index 0000000..9079cda
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V221__add_version_for_m_savings_account.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `version` INT(15) NOT NULL DEFAULT '1' AFTER `on_hold_funds_derived`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V222__guarantor_on_hold_fund_changes_for_transactions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V222__guarantor_on_hold_fund_changes_for_transactions.sql
new file mode 100755
index 0000000..d394ea1
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V222__guarantor_on_hold_fund_changes_for_transactions.sql
@@ -0,0 +1,14 @@
+ALTER TABLE `m_deposit_account_on_hold_transaction`
+	ADD COLUMN `created_date` DATETIME NOT NULL;
+	
+ALTER TABLE `m_guarantor_transaction`
+	ALTER `loan_transaction_id` DROP DEFAULT;
+	
+ALTER TABLE `m_guarantor_transaction`
+	CHANGE COLUMN `loan_transaction_id` `loan_transaction_id` BIGINT(20) NULL ;
+	
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'RECOVERGUARANTEES_LOAN', 'LOAN', 'RECOVERGUARANTEES', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'RECOVERGUARANTEES_LOAN_CHECKER', 'LOAN', 'RECOVERGUARANTEES_CHECKER', 0);
+ALTER TABLE `m_loan`
+	DROP COLUMN `guarantee_outstanding_amount_derived`;
+	
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V223__add_version_for_m_loan_account.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V223__add_version_for_m_loan_account.sql
new file mode 100644
index 0000000..e795d5e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V223__add_version_for_m_loan_account.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan`
+	ADD COLUMN `version` INT(15) NOT NULL DEFAULT '1' AFTER `create_standing_instruction_at_disbursement`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V224__client_lifecycle_adding_statuses.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V224__client_lifecycle_adding_statuses.sql
new file mode 100644
index 0000000..e52acf6
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V224__client_lifecycle_adding_statuses.sql
@@ -0,0 +1,35 @@
+/**add columns to m_client**/
+ALTER TABLE `m_client`
+	ADD COLUMN `reject_reason_cv_id` INT(11) NULL DEFAULT NULL AFTER `client_classification_cv_id`,
+	ADD COLUMN `rejectedon_date` DATE NULL DEFAULT NULL AFTER `reject_reason_cv_id`,
+	ADD COLUMN `rejectedon_userid` BIGINT(20) NULL DEFAULT NULL AFTER `rejectedon_date`,
+	ADD COLUMN `withdraw_reason_cv_id` INT(11) NULL DEFAULT NULL AFTER `rejectedon_userid`,
+	ADD COLUMN `withdrawn_on_date` DATE NULL DEFAULT NULL AFTER `withdraw_reason_cv_id`,
+	ADD COLUMN `withdraw_on_userid` BIGINT(20) NULL DEFAULT NULL AFTER `withdrawn_on_date`,
+	ADD COLUMN `reactivated_on_date` DATE NULL AFTER `withdraw_on_userid`,
+	ADD COLUMN `reactivated_on_userid` BIGINT(20) NULL AFTER `reactivated_on_date`,
+	ADD CONSTRAINT `FK_m_client_type_mcode_value_reject` FOREIGN KEY (`reject_reason_cv_id`) REFERENCES `m_code_value` (`id`),
+	ADD CONSTRAINT `FK_m_client_type_m_code_value_withdraw` FOREIGN KEY (`withdraw_reason_cv_id`) REFERENCES `m_code_value` (`id`);
+
+
+ALTER TABLE `m_client`
+	ADD COLUMN `updated_by` BIGINT(20) NULL DEFAULT NULL AFTER `closedon_date`,
+	ADD COLUMN `updated_on` DATE NULL DEFAULT NULL AFTER `updated_by`;
+
+
+/**permissions for client reject and withdraw**/
+INSERT INTO `m_permission` ( `grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'REJECT_CLIENT', 'CLIENT', 'REJECT', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'REJECT_CLIENT_CHECKER', 'CLIENT', 'REJECT_CHECKER', 0);
+
+INSERT INTO `m_permission` ( `grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'WITHDRAW_CLIENT', 'CLIENT', 'WITHDRAW', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'WITHDRAW_CLIENT_CHECKER', 'CLIENT', 'WITHDRAW_CHECKER', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'REACTIVATE_CLIENT', 'CLIENT', 'REACTIVATE', 1);
+INSERT INTO `m_permission` ( `grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'REACTIVATE_CLIENT_CHECKER', 'CLIENT', 'REACTIVATE_CHECKER', 0);
+
+
+/**Code for capturing reasons for new life cycle events**/
+INSERT INTO `m_code` ( `code_name`, `is_system_defined`) VALUES ( 'ClientRejectReason', 1);
+INSERT INTO `m_code` ( `code_name`, `is_system_defined`) VALUES ( 'ClientWithdrawReason', 1);
+
+	
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V225__permissions_for_updating_recurring_deposit_amount.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V225__permissions_for_updating_recurring_deposit_amount.sql
new file mode 100644
index 0000000..f6b221e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V225__permissions_for_updating_recurring_deposit_amount.sql
@@ -0,0 +1,3 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'UPDATEDEPOSITAMOUNT_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'UPDATEDEPOSITAMOUNT', 1);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'transaction_savings', 'UPDATEDEPOSITAMOUNT_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'UPDATEDEPOSITAMOUNT', 1);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V226__configuration_for_enforcing_calendars_for_jlg_loans.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V226__configuration_for_enforcing_calendars_for_jlg_loans.sql
new file mode 100644
index 0000000..f1aea73
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V226__configuration_for_enforcing_calendars_for_jlg_loans.sql
@@ -0,0 +1 @@
+INSERT INTO c_configuration (`name`, `description`) VALUES ('meetings-mandatory-for-jlg-loans', 'Enforces all JLG loans to follow a meeting schedule belonging to parent group or Center');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V227__loan-refund-permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V227__loan-refund-permissions.sql
new file mode 100644
index 0000000..174d1a3
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V227__loan-refund-permissions.sql
@@ -0,0 +1,16 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES
+	('transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 0);
+
+	INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES
+	( 'transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 1);
+
+	INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES
+	('transaction_loan', 'REFUNDBYCASH_LOAN', 'LOAN', 'REFUNDBYCASH', 1);
+	
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES
+	('transaction_loan', 'REFUNDBYCASH_LOAN_CHECKER', 'LOAN', 'REFUNDBYCASH', 0);	
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V228__entity_to_entity_access.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V228__entity_to_entity_access.sql
new file mode 100644
index 0000000..b1b3cdf
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V228__entity_to_entity_access.sql
@@ -0,0 +1,45 @@
+-- Code required to store various types of Entity to Entity Access types that Mifos Supports
+insert into m_code (code_name, is_system_defined)
+values ('Entity to Entity Access Types', 1);
+
+-- Three Code Values required to support:
+-- a) Loan Products restricted to specific Offices
+-- b) Savings Products restricted to specific Offices
+-- c) Fees/Charges restricted to specific Offices
+insert into m_code_value (code_id, code_value, order_position)
+values (
+	(select id from m_code where code_name = 'Entity to Entity Access Types'),
+	'Office Access to Loan Products',  0);
+
+insert into m_code_value (code_id, code_value, order_position)
+values (
+	(select id from m_code where code_name = 'Entity to Entity Access Types'),
+	'Office Access to Savings Products',  0);
+	
+insert into m_code_value (code_id, code_value, order_position)
+values (
+	(select id from m_code where code_name = 'Entity to Entity Access Types'),
+	'Office Access to Fees/Charges',  0);
+
+-- Table where the actual restrictions will be stored
+CREATE TABLE `m_entity_to_entity_access` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`entity_type` VARCHAR(50) NOT NULL,
+	`entity_id` BIGINT(20) NOT NULL,
+	`access_type_code_value_id` INT(11) NOT NULL,
+	`second_entity_type` VARCHAR(50) NOT NULL,
+	`second_entity_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `IDX_OFFICE` (`entity_type`,`entity_id`),
+	UNIQUE KEY `id_uniq_m_entity_to_entity_access` (`entity_type`,`entity_id`,`access_type_code_value_id`,`second_entity_type`,`second_entity_id`),
+	CONSTRAINT `FK_access_type_code_m_code_value` FOREIGN KEY (`access_type_code_value_id`) REFERENCES `m_code_value` (`id`)
+);
+
+-- Global Configurations for Entity access restrictions
+insert into c_configuration (name, value, enabled, description)
+	values ('office-specific-products-enabled', 0, 0,
+	'Whether products and fees should be office specific or not? This property should NOT be changed once Mifos is Live.');
+
+insert into c_configuration (name, value, enabled, description)
+	values ('restrict-products-to-user-office', 0, 0,
+	'This should be enabled only if, products & fees are office specific (i.e. office-specific-products-enabled is enabled). This property specifies if the products should be auto-restricted to office of the user who created the proudct? Note: This property should NOT be changed once Mifos is Live.');
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V229__teller_cash_management.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V229__teller_cash_management.sql
new file mode 100644
index 0000000..f47fe20
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V229__teller_cash_management.sql
@@ -0,0 +1,107 @@
+CREATE TABLE `m_tellers` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`office_id` BIGINT(20) NOT NULL,
+	`debit_account_id` BIGINT(20),
+	`credit_account_id` BIGINT(20),
+	`name` VARCHAR(50) NOT NULL,
+	`description` VARCHAR(100),
+	`valid_from` DATE,
+	`valid_to` DATE,
+	`state` SMALLINT(5),
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `m_tellers_name_unq` (`name`),
+	INDEX `IK_m_tellers_m_office` (`office_id`),
+	CONSTRAINT `FK_m_tellers_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+	CONSTRAINT `FK_m_tellers_gl_account_debit_account_id` FOREIGN KEY (`debit_account_id`) REFERENCES `acc_gl_account` (`id`),
+	CONSTRAINT `FK_m_tellers_gl_account_credit_account_id` FOREIGN KEY (`credit_account_id`) REFERENCES `acc_gl_account` (`id`)
+	);
+
+CREATE TABLE `m_cashiers` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`staff_id` BIGINT(20),
+	`teller_id` BIGINT(20),
+	`description` VARCHAR(100),
+	`start_date` DATE,
+	`end_date` DATE,
+	`start_time` varchar(10),
+	`end_time` varchar(10),
+	`full_day` TINYINT,
+	PRIMARY KEY (`id`),
+	INDEX `IK_m_cashiers_m_staff` (`staff_id`),
+	INDEX `IK_m_cashiers_m_teller` (`teller_id`),
+	CONSTRAINT `FK_m_cashiers_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
+	CONSTRAINT `FK_m_cashiers_m_teller` FOREIGN KEY (`teller_id`) REFERENCES `m_tellers` (`id`)
+	); 
+		
+CREATE TABLE `m_cashier_transactions` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`cashier_id` BIGINT(20) NOT NULL,
+	`txn_type` SMALLINT(5)  NOT NULL,
+	`txn_amount` DECIMAL (19,6)  NOT NULL,
+	`txn_date` DATE  NOT NULL,
+	`created_date` DATETIME  NOT NULL,
+	`entity_type` VARCHAR(50),
+	`entity_id` BIGINT(20),
+	`txn_note` VARCHAR(200),
+	PRIMARY KEY (`id`),
+	INDEX `IK_m_teller_transactions_m_cashier` (`cashier_id`),
+	CONSTRAINT `FK_m_teller_transactions_m_cashiers` FOREIGN KEY (`cashier_id`) REFERENCES `m_cashiers` (`id`)
+	);
+
+	
+	INSERT INTO m_permission (
+		grouping, code, entity_name, action_name, can_maker_checker
+	) values (
+		'cash_mgmt', 'CREATE_TELLER', 'TELLER', 'CREATE', 1
+	);
+	INSERT INTO m_permission (
+		grouping, code, entity_name, action_name, can_maker_checker
+	) values (
+		'cash_mgmt', 'UPDATE_TELLER', 'TELLER', 'CREATE', 1
+	);
+	
+	INSERT INTO m_permission (
+		grouping, code, entity_name, action_name, can_maker_checker
+	) values (
+		'cash_mgmt', 'ALLOCATECASHIER_TELLER', 'TELLER', 'ALLOCATE', 1
+	);
+	
+	INSERT INTO m_permission (
+		grouping, code, entity_name, action_name, can_maker_checker
+	) values (
+		'cash_mgmt', 'UPDATECASHIERALLOCATION_TELLER', 'TELLER', 'UPDATECASHIERALLOCATION', 1
+	);
+	
+	INSERT INTO m_permission (
+		grouping, code, entity_name, action_name, can_maker_checker
+	) values (
+		'cash_mgmt', 'DELETECASHIERALLOCATION_TELLER', 'TELLER', 'DELETECASHIERALLOCATION', 1
+	);
+	
+	INSERT INTO m_permission (
+		grouping, code, entity_name, action_name, can_maker_checker
+	) values (
+		'cash_mgmt', 'ALLOCATECASHTOCASHIER_TELLER', 'TELLER', 'ALLOCATECASHTOCASHIER', 1
+	);
+	
+	INSERT INTO m_permission (
+		grouping, code, entity_name, action_name, can_maker_checker
+	) values (
+		'cash_mgmt', 'SETTLECASHFROMCASHIER_TELLER', 'TELLER', 'SETTLECASHFROMCASHIER', 1
+	);
+	
+	INSERT INTO r_enum_value (
+		enum_name, enum_id, enum_message_property, enum_value, enum_type
+	) values (
+		'teller_status', 300, 'Active', 'Active',0
+	);
+	INSERT INTO r_enum_value (
+		enum_name, enum_id, enum_message_property, enum_value, enum_type
+	) values (
+		'teller_status', 400, 'Inactive', 'Inactive',0
+	);
+	INSERT INTO r_enum_value (
+		enum_name, enum_id, enum_message_property, enum_value, enum_type
+	) values (
+		'teller_status', 600, 'Closed', 'Closed',0
+	);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V22__alter-group-for-consistency-add-permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V22__alter-group-for-consistency-add-permissions.sql
new file mode 100644
index 0000000..749fd53
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V22__alter-group-for-consistency-add-permissions.sql
@@ -0,0 +1,19 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ACTIVATE_CENTER', 'CENTER', 'ACTIVATE', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ACTIVATE_CENTER_CHECKER', 'CENTER', 'ACTIVATE', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ACTIVATE_GROUP', 'GROUP', 'ACTIVATE', 1);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ACTIVATE_GROUP_CHECKER', 'GROUP', 'ACTIVATE', 0);
+
+
+ALTER TABLE `m_group` DROP FOREIGN KEY `FK_m_group_level`;
+ALTER TABLE `m_group`
+CHANGE COLUMN `external_id` `external_id` VARCHAR(100) NULL DEFAULT NULL AFTER `id`,
+CHANGE COLUMN `status_enum` `status_enum` INT(5) NOT NULL DEFAULT '300' AFTER `external_id`,
+CHANGE COLUMN `name` `display_name` VARCHAR(100) NOT NULL  AFTER `level_id`,
+CHANGE COLUMN `level_Id` `level_id` INT(11) NOT NULL,
+ADD CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_id`) REFERENCES `m_group_level` (`id`);
+
+
+ALTER TABLE `m_group`
+DROP COLUMN `is_deleted`,
+ADD COLUMN `activation_date` DATE NULL DEFAULT NULL AFTER `status_enum` ;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V230__role_status_and_correspoding_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V230__role_status_and_correspoding_permissions.sql
new file mode 100644
index 0000000..6faddc0
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V230__role_status_and_correspoding_permissions.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_role` ADD COLUMN `is_disabled` TINYINT(1) NOT NULL DEFAULT 0 AFTER `description`;
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('authorisation', 'DISABLE_ROLE', 'ROLE', 'DISABLE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('authorisation', 'DISABLE_ROLE_CHECKER', 'ROLE', 'DISABLE_CHECKER', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('authorisation', 'ENABLE_ROLE', 'ROLE', 'ENABLE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('authorisation', 'ENABLE_ROLE_CHECKER', 'ROLE', 'ENABLE_CHECKER', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V231__m_cashier_transaction_added_currency_code.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V231__m_cashier_transaction_added_currency_code.sql
new file mode 100644
index 0000000..10cbd24
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V231__m_cashier_transaction_added_currency_code.sql
@@ -0,0 +1 @@
+ALTER TABLE `m_cashier_transactions` ADD COLUMN `currency_code` VARCHAR(3) NULL AFTER `txn_note`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V232__insert_center_closure_reason.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V232__insert_center_closure_reason.sql
new file mode 100644
index 0000000..627004f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V232__insert_center_closure_reason.sql
@@ -0,0 +1 @@
+INSERT INTO `m_code` (`code_name`,`is_system_defined`) VALUES('CenterClosureReason',1);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V233__Savings_Transaction_Receipt.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V233__Savings_Transaction_Receipt.sql
new file mode 100644
index 0000000..bd8bf83
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V233__Savings_Transaction_Receipt.sql
@@ -0,0 +1,8 @@
+INSERT INTO stretchy_parameter ( parameter_name, parameter_variable, parameter_label, parameter_displayType, parameter_FormatType, parameter_default, special, selectOne, selectAll, parameter_sql, parent_id) VALUES ('transactionId', 'transactionId', 'transactionId', 'text', 'string', 'n/a', NULL, NULL, NULL, NULL, NULL);
+
+INSERT INTO stretchy_report ( report_name, report_type, report_subtype, report_category, report_sql, description, core_report, use_report) VALUES ( 'Savings Transaction Receipt', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1);
+
+INSERT INTO stretchy_report_parameter ( report_id, parameter_id, report_parameter_name) 
+VALUES ((select sr.id from stretchy_report sr where sr.report_name='Savings Transaction Receipt'),
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='transactionId'), 
+ 'transactionId');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V234__opening_balaces_setup.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V234__opening_balaces_setup.sql
new file mode 100644
index 0000000..a28a2c2
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V234__opening_balaces_setup.sql
@@ -0,0 +1,34 @@
+-- Example: INSERT INTO `acc_gl_account` 
+--	(
+-- `name`, `parent_id`, `hierarchy`, `gl_code`, `disabled`, `manual_journal_entries_allowed`,
+--	`account_usage`, `classification_enum`, `tag_id`, `description`
+--	)
+--	VALUES 
+--	(
+--	'Opening Balances Contra Account', NULL, '.', 'OBCA', 0, 1, 
+--	1, 3, NULL, NULL
+--	);
+
+INSERT INTO `c_configuration` 
+	(
+		`name`, 
+		`value`, 
+		`enabled`
+	)
+	VALUES 
+	(
+		'office-opening-balances-contra-account', 
+		0, -- Or Example: (SELECT id FROM acc_gl_account WHERE gl_code = 'OBCA' ),
+		1);
+
+ALTER TABLE `c_configuration`
+	ADD UNIQUE INDEX `name_UNIQUE` (`name`);
+	
+INSERT INTO `m_permission`
+	(
+		`grouping`, `code`, `entity_name`, `action_name`
+	) 
+	VALUES 
+	(
+		'accounting', 'DEFINEOPENINGBALANCE_JOURNALENTRY', 'JOURNALENTRY', 'DEFINEOPENINGBALANCE'
+	);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V235__add_ugd_template_id_m_hook.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V235__add_ugd_template_id_m_hook.sql
new file mode 100644
index 0000000..293b189
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V235__add_ugd_template_id_m_hook.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_hook` ADD COLUMN `ugd_template_id` bigint(20) NULL;
+ALTER TABLE `m_hook` ADD CONSTRAINT `fk_ugd_template_id` FOREIGN KEY (`ugd_template_id`) REFERENCES `m_template` (`id`);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V236__individual_collection_sheet_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V236__individual_collection_sheet_permissions.sql
new file mode 100755
index 0000000..effe292
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V236__individual_collection_sheet_permissions.sql
@@ -0,0 +1,2 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('collection_sheet', 'READ_COLLECTIONSHEET', 'COLLECTIONSHEET', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('collection_sheet', 'SAVE_COLLECTIONSHEET', 'COLLECTIONSHEET', 'SAVE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V237__add_threshold_config_for_last_instalment.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V237__add_threshold_config_for_last_instalment.sql
new file mode 100755
index 0000000..fbb8499
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V237__add_threshold_config_for_last_instalment.sql
@@ -0,0 +1,8 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `principal_threshold_for_last_instalment` DECIMAL(5,2) NOT NULL DEFAULT '50';
+	
+update m_product_loan pl set pl.principal_threshold_for_last_instalment = 0 where pl.allow_multiple_disbursals = 0;
+
+	
+	
+	
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V238__update_staff_display_name_length.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V238__update_staff_display_name_length.sql
new file mode 100644
index 0000000..66e4502
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V238__update_staff_display_name_length.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `m_staff`
+	ALTER `display_name` DROP DEFAULT;
+ALTER TABLE `m_staff`
+	CHANGE COLUMN `display_name` `display_name` VARCHAR(102) NOT NULL AFTER `lastname`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V239__Loan_Transaction_Receipt.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V239__Loan_Transaction_Receipt.sql
new file mode 100644
index 0000000..00f040d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V239__Loan_Transaction_Receipt.sql
@@ -0,0 +1,2 @@
+INSERT INTO stretchy_report ( report_name, report_type, report_subtype, report_category, report_sql, description, core_report, use_report) VALUES ('Loan Transaction Receipt', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1);
+INSERT INTO stretchy_report_parameter ( report_id, parameter_id, report_parameter_name) VALUES ( (select sr.id from stretchy_report sr where sr.report_name='Loan Transaction Receipt'), (select sp.id from stretchy_parameter sp where sp.parameter_name='transactionId'), 'transactionId');
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V23__remove-enable-disable-configuration-for-client-group-status.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V23__remove-enable-disable-configuration-for-client-group-status.sql
new file mode 100644
index 0000000..71dbe76
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V23__remove-enable-disable-configuration-for-client-group-status.sql
@@ -0,0 +1,4 @@
+DELETE FROM `c_configuration` WHERE `name`='allow-pending-client-status';
+DELETE FROM `c_configuration` WHERE `name`='allow-pending-group-status';
+
+ALTER TABLE `m_client` DROP COLUMN `is_deleted`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V240__arrears_aging_config_for_interest_recalculation.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V240__arrears_aging_config_for_interest_recalculation.sql
new file mode 100644
index 0000000..d1e7bbd
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V240__arrears_aging_config_for_interest_recalculation.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_product_loan_recalculation_details`
+	ADD COLUMN `arrears_based_on_original_schedule` TINYINT(1) NOT NULL DEFAULT '0';
+	
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `account_moves_out_of_npa_only_on_arrears_completion` TINYINT(1) NOT NULL DEFAULT '0';	
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V241__fixed_emi_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V241__fixed_emi_changes.sql
new file mode 100644
index 0000000..b36a04f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V241__fixed_emi_changes.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `can_define_fixed_emi_amount` TINYINT(1) NOT NULL DEFAULT '0',
+	ADD COLUMN `instalment_amount_in_multiples_of` DECIMAL(19,6) NULL DEFAULT NULL;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V242__entitytoentitymappingrelation.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V242__entitytoentitymappingrelation.sql
new file mode 100644
index 0000000..7f4474a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V242__entitytoentitymappingrelation.sql
@@ -0,0 +1,38 @@
+CREATE TABLE `m_entity_relation` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`from_entity_type` INT(10) NOT NULL,
+	`to_entity_type` INT(10) NOT NULL,
+	`code_name` VARCHAR(50) NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `from_entity_type_to_entity_type_code_name` (`from_entity_type`, `to_entity_type`, `code_name`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1
+;
+
+CREATE TABLE `m_entity_to_entity_mapping` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`rel_id` BIGINT(20) NOT NULL DEFAULT '0',
+	`from_id` BIGINT(20) NOT NULL DEFAULT '0',
+	`to_id` BIGINT(20) UNSIGNED NOT NULL,
+	`start_date` DATE NULL DEFAULT NULL,
+	`end_date` DATE NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `rel_id_from_id_to_id` (`rel_id`, `from_id`, `to_id`),
+	CONSTRAINT `FK__rel_id_m_entity_relation_id` FOREIGN KEY (`rel_id`) REFERENCES `m_entity_relation` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1
+;
+
+INSERT INTO `m_entity_relation` (`from_entity_type`, `to_entity_type`, `code_name`) VALUES (1, 2, 'office_access_to_loan_products');
+INSERT INTO `m_entity_relation` (`from_entity_type`, `to_entity_type`, `code_name`) VALUES (1, 3, 'office_access_to_savings_products');
+INSERT INTO `m_entity_relation` (`from_entity_type`, `to_entity_type`, `code_name`) VALUES (1, 4, 'office_access_to_fees/charges');
+INSERT INTO `m_entity_relation` (`from_entity_type`, `to_entity_type`, `code_name`) VALUES (5, 2, 'role_access_to_loan_products');
+INSERT INTO `m_entity_relation` (`from_entity_type`, `to_entity_type`, `code_name`) VALUES (5, 3, 'role_access_to_savings_products');
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('infrastructure', 'CREATE_ENTITYMAPPING', 'ENTITYMAPPING', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('infrastructure', 'UPDATE_ENTITYMAPPING', 'ENTITYMAPPING', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('infrastructure', 'DELETE_ENTITYMAPPING', 'ENTITYMAPPING', 'DELETE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V243__alter_loan_disbursement_details.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V243__alter_loan_disbursement_details.sql
new file mode 100644
index 0000000..a0cdc40
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V243__alter_loan_disbursement_details.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan_disbursement_detail`
+	DROP COLUMN approved_principal;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V244__staff_assignment_history_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V244__staff_assignment_history_table.sql
new file mode 100644
index 0000000..29170d1
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V244__staff_assignment_history_table.sql
@@ -0,0 +1,22 @@
+CREATE TABLE IF NOT EXISTS `m_staff_assignment_history` (
+`id` bigint(20) NOT NULL AUTO_INCREMENT,
+`centre_id` bigint(20) DEFAULT NULL,
+`staff_id` bigint(20) NOT NULL,
+`start_date` date NOT NULL,
+`end_date` date DEFAULT NULL,
+`createdby_id` bigint(20) DEFAULT NULL,
+`created_date` datetime DEFAULT NULL,
+`lastmodified_date` datetime DEFAULT NULL,
+`lastmodifiedby_id` bigint(20) DEFAULT NULL,
+PRIMARY KEY (`id`),
+KEY `FK_m_staff_assignment_history_centre_id_m_group` (`centre_id`),
+KEY `FK_m_staff_assignment_history_m_staff` (`staff_id`),
+CONSTRAINT `FK_m_staff_assignment_history_centre_id_m_group` FOREIGN KEY (`centre_id`) REFERENCES `m_group` (`id`),
+CONSTRAINT `FK_m_staff_assignment_history_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+);
+INSERT INTO stretchy_parameter ( parameter_name, parameter_variable, parameter_label, parameter_displayType, parameter_FormatType, parameter_default, special, selectOne, selectAll, parameter_sql, parent_id) VALUES ('selectCenterId', 'centerId', 'Enter Center Id', 'text', 'string', 'n/a', NULL, NULL, NULL, NULL, NULL);
+INSERT INTO stretchy_report ( report_name, report_type, report_subtype, report_category, report_sql, description, core_report, use_report) VALUES ( 'Staff Assignment History', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1);
+INSERT INTO stretchy_report_parameter ( report_id, parameter_id, report_parameter_name)
+VALUES ((select sr.id from stretchy_report sr where sr.report_name='Staff Assignment History'),
+(select sp.id from stretchy_parameter sp where sp.parameter_name='selectCenterId'),
+'centerId');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V245__open_rd_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V245__open_rd_changes.sql
new file mode 100644
index 0000000..d87c8a3
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V245__open_rd_changes.sql
@@ -0,0 +1,2 @@
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`) VALUES ('Generate Mandatory Savings Schedule', 'Generate Mandatory Savings Schedule', '0 5 0 1/1 * ? *', now());
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V246__drop_group_client_foreign_key_from_m_loan.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V246__drop_group_client_foreign_key_from_m_loan.sql
new file mode 100644
index 0000000..4b9b539
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V246__drop_group_client_foreign_key_from_m_loan.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan`
+	DROP FOREIGN KEY `fk_m_group_client_001`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V247__consistency_wrt_spelling_principalThresholdForLastInstalment.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V247__consistency_wrt_spelling_principalThresholdForLastInstalment.sql
new file mode 100644
index 0000000..8c604b2
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V247__consistency_wrt_spelling_principalThresholdForLastInstalment.sql
@@ -0,0 +1,3 @@
+
+ALTER TABLE `m_product_loan`
+	CHANGE COLUMN `principal_threshold_for_last_instalment` `principal_threshold_for_last_installment` DECIMAL(5,2) NOT NULL DEFAULT '50.00' AFTER `hold_guarantee_funds`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V248__added_password_never_expired_to_User.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V248__added_password_never_expired_to_User.sql
new file mode 100644
index 0000000..4e21019
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V248__added_password_never_expired_to_User.sql
@@ -0,0 +1 @@
+ALTER TABLE  `m_appuser` ADD  `password_never_expires` TINYINT NOT NULL DEFAULT  '0' COMMENT  'define if the password, should be check for validity period or not';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V249__workingdays_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V249__workingdays_permissions.sql
new file mode 100644
index 0000000..9930758
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V249__workingdays_permissions.sql
@@ -0,0 +1,36 @@
+INSERT INTO  `m_permission` (
+`grouping` ,
+`code` ,
+`entity_name` ,
+`action_name` ,
+`can_maker_checker`
+)
+VALUES (
+ 'organisation',  'READ_WORKINGDAYS',  'WORKINGDAYS',  'READ',  '0'
+);
+
+
+INSERT INTO  `m_permission` (
+`grouping` ,
+`code` ,
+`entity_name` ,
+`action_name` ,
+`can_maker_checker`
+)
+VALUES (
+ 'organisation',  'UPDATE_WORKINGDAYS',  'WORKINGDAYS',  'UPDATE',  '0'
+);
+
+INSERT INTO  `m_permission` (
+`grouping` ,
+`code` ,
+`entity_name` ,
+`action_name` ,
+`can_maker_checker`
+)
+VALUES (
+ 'organisation',  'UPDATE_WORKINGDAYS_CHECKER',  'WORKINGDAYS',  'UPDATE_CHECKER',  '0'
+);
+
+
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V24__add-group-client-foreign-key-constraint-in-loan-table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V24__add-group-client-foreign-key-constraint-in-loan-table.sql
new file mode 100644
index 0000000..ed923bc
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V24__add-group-client-foreign-key-constraint-in-loan-table.sql
@@ -0,0 +1,7 @@
+ALTER TABLE m_loan
+ADD CONSTRAINT `fk_m_group_client_001`
+FOREIGN KEY (`group_id` , `client_id` )
+REFERENCES m_group_client (`group_id` , `client_id` )
+ON DELETE NO ACTION
+ON UPDATE NO ACTION
+, ADD INDEX `fk_m_group_client_001_idx` (`group_id` ASC, `client_id` ASC) ;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V250__password_validation_policy.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V250__password_validation_policy.sql
new file mode 100644
index 0000000..c3eeac7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V250__password_validation_policy.sql
@@ -0,0 +1,43 @@
+
+CREATE TABLE IF NOT EXISTS `m_password_validation_policy` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `regex` text NOT NULL,
+  `description` text NOT NULL,
+  `active` tinyint(4) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+
+INSERT INTO `m_password_validation_policy` (
+`id` ,
+`regex` ,
+`description` ,
+`active`
+)
+VALUES (
+NULL ,  '^.{1,50}$',  'Password most be at least 1 character and not more that 50 characters long',  '1'
+);
+
+INSERT INTO `m_password_validation_policy` (
+`id` ,
+`regex` ,
+`description` ,
+`active`
+)
+VALUES (
+NULL ,  '^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\\s).{6,50}$',  'Password must be at least 6 characters, no more than 50 characters long, must include at least one upper case letter, one lower case letter, one numeric digit and no space',  '0'
+);
+
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUE ("authorisation","READ_PASSWORD_PREFERENCES","PASSWORD_PREFERENCES","READ",0);
+
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUE ("authorisation","UPDATE_PASSWORD_PREFERENCES","PASSWORD_PREFERENCES","UPDATE",0);
+
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUE ("authorisation","UPDATE_PASSWORD_PREFERENCES_CHECKER","PASSWORD_PREFERENCES","UPDATE_CHECKER",0);
+
+
+
+
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V251__paymentType_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V251__paymentType_table.sql
new file mode 100644
index 0000000..7d6a498
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V251__paymentType_table.sql
@@ -0,0 +1,46 @@
+CREATE TABLE `m_payment_type` (
+	`id` INT(11) NOT NULL AUTO_INCREMENT,
+	`value` VARCHAR(100) NULL DEFAULT NULL,
+	`description` VARCHAR(500) NULL DEFAULT NULL,
+	`is_cash_payment` TINYINT(1) NULL DEFAULT '0',
+	`order_position` INT(11) NOT NULL DEFAULT '0',
+	PRIMARY KEY (`id`)
+)
+COLLATE='utf8_general_ci';
+
+INSERT INTO m_payment_type (id,value,description,order_position) 
+SELECT id,code_value,code_description,order_position 
+FROM m_code_value 
+where code_id in
+(select mc.id
+from m_code mc where mc.code_name='PaymentType' 
+);
+
+ALTER TABLE `m_payment_detail`
+	DROP FOREIGN KEY `FK_m_payment_detail_m_code_value`;
+
+ALTER TABLE `m_payment_detail`
+	CHANGE COLUMN `payment_type_cv_id` `payment_type_id` INT(11) NULL DEFAULT NULL AFTER `id`;
+	
+ALTER TABLE `m_payment_detail`
+	ADD CONSTRAINT `FK_m_payment_detail_m_payment_type` FOREIGN KEY (`payment_type_id`) REFERENCES `m_payment_type` (`id`);
+	
+ALTER TABLE `acc_product_mapping`
+	DROP FOREIGN KEY `FK_acc_product_mapping_m_code_value`;
+	
+ALTER TABLE `acc_product_mapping`
+	ADD CONSTRAINT `FK_acc_product_mapping_m_payment_type` FOREIGN KEY (`payment_type`) REFERENCES `m_payment_type` (`id`);
+	
+DELETE from m_code_value
+WHERE
+m_code_value.code_id in 
+(SELECT mc.id
+FROM m_code mc where mc.code_name='PaymentType' );
+
+DELETE FROM m_code WHERE code_name="PaymentType";
+	
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_PAYMENTTYPE', 'PAYMENTTYPE', 'CREATE', 0);
+	
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_PAYMENTTYPE', 'PAYMENTTYPE', 'UPDATE', 0);
+	
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_PAYMENTTYPE', 'PAYMENTTYPE', 'DELETE', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V252__bug_fix_teller_cash_management.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V252__bug_fix_teller_cash_management.sql
new file mode 100644
index 0000000..2e62b8a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V252__bug_fix_teller_cash_management.sql
@@ -0,0 +1,13 @@
+UPDATE m_permission  SET
+action_name="UPDATE" 
+WHERE
+code = "UPDATE_TELLER";
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`) VALUES ('cash_mgmt', 'DELETE_TELLER', 'TELLER', 'DELETE');
+
+	
+ALTER TABLE `m_cashier_transactions`
+	DROP FOREIGN KEY `FK_m_teller_transactions_m_cashiers`;
+	
+ALTER TABLE `m_cashier_transactions`
+	ADD CONSTRAINT `FK_m_teller_transactions_m_cashiers` FOREIGN KEY (`cashier_id`) REFERENCES `m_cashiers` (`id`) ON UPDATE CASCADE ON DELETE CASCADE;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V253__product_loan_configurable_attributes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V253__product_loan_configurable_attributes.sql
new file mode 100644
index 0000000..7db8d65
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V253__product_loan_configurable_attributes.sql
@@ -0,0 +1,21 @@
+CREATE TABLE `m_product_loan_configurable_attributes` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` BIGINT NOT NULL,
+  `amortization_method_enum` TINYINT NOT NULL DEFAULT '1',
+  `interest_method_enum` TINYINT NOT NULL DEFAULT '1',
+  `loan_transaction_strategy_id` TINYINT NOT NULL DEFAULT '1',
+  `interest_calculated_in_period_enum` TINYINT NOT NULL DEFAULT '1',
+  `arrearstolerance_amount` TINYINT NOT NULL DEFAULT '1',
+  `repay_every` TINYINT NOT NULL DEFAULT '1',
+  `moratorium` TINYINT NOT NULL DEFAULT '1',
+  `grace_on_arrears_ageing` TINYINT NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `fk_m_product_loan_configurable_attributes_0001` (`loan_product_id`),
+  CONSTRAINT `fk_m_product_loan_configurable_attributes_0001` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+ INSERT into `m_product_loan_configurable_attributes` 
+(loan_product_id,amortization_method_enum,interest_method_enum,loan_transaction_strategy_id,
+interest_calculated_in_period_enum,arrearstolerance_amount,repay_every,moratorium,grace_on_arrears_ageing)
+(select pl.id,'1','1','1','1','1','1','1','1' from `m_product_loan` pl );
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V254__General_Ledger_Report.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V254__General_Ledger_Report.sql
new file mode 100644
index 0000000..8f62059
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V254__General_Ledger_Report.sql
@@ -0,0 +1,24 @@
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('GeneralLedgerReport', 'Pentaho', NULL, 'Accounting', NULL, NULL, 0, 1);
+
+INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `special`, `selectOne`, `selectAll`, `parameter_sql`, `parent_id`) VALUES ('SelectGLAccountNO', 'GLAccountNO', 'GLAccountNO', 'select', 'number', '0', NULL, NULL, NULL, '\r\n\r\n\r\n\r\n\r\n(select id aid,name aname\r\nfrom acc_gl_account)\r\nunion\r\n(select -1,\'ALL\')       \r\norder by 1                                                                                                                                ', NULL);
+
+INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES
+ ((select sr.id from stretchy_report sr where sr.report_name='GeneralLedgerReport'), 
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='SelectGLAccountNO'), 
+  'Account');
+  
+   INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES
+ ((select sr.id from stretchy_report sr where sr.report_name='GeneralLedgerReport'), 
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='startDateselect'),
+  'ondate');
+  
+    INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES
+ ((select sr.id from stretchy_report sr where sr.report_name='GeneralLedgerReport'), 
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='endDateselect'), 
+  'todate');
+  
+   INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES
+ ((select sr.id from stretchy_report sr where sr.report_name='GeneralLedgerReport'), 
+ (select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'), 
+  'office');
+  
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V255__pre_close_interest_period_config.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V255__pre_close_interest_period_config.sql
new file mode 100644
index 0000000..0399c0d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V255__pre_close_interest_period_config.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_product_loan_recalculation_details`
+	ADD COLUMN `pre_close_interest_calculation_strategy` SMALLINT(3) NOT NULL DEFAULT '1';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V256__Update script for General_Ledger_report.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V256__Update script for General_Ledger_report.sql
new file mode 100644
index 0000000..801d4b8
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V256__Update script for General_Ledger_report.sql
@@ -0,0 +1,32 @@
+update stretchy_report_parameter 
+set report_parameter_name='account'
+where report_id = (select stretchy_report.id from stretchy_report where report_name='GeneralLedgerReport')
+and parameter_id=(select p.id from stretchy_parameter p where parameter_name='SelectGLAccountNO');
+
+
+update stretchy_report_parameter
+set report_parameter_name='fromDate'
+where report_id =(select stretchy_report.id from stretchy_report where report_name='GeneralLedgerReport')
+and parameter_id=(select p.id from stretchy_parameter p where parameter_name='startDateSelect');
+
+update stretchy_report_parameter
+set report_parameter_name='toDate'
+where report_id=(select stretchy_report.id from stretchy_report where report_name='GeneralLedgerReport')
+and parameter_id=(select p.id from stretchy_parameter p where parameter_name='endDateSelect');
+
+update stretchy_report_parameter
+set report_parameter_name='branch'
+where report_id=(select stretchy_report.id from stretchy_report where report_name='GeneralLedgerReport')
+and parameter_id=(select p.id from stretchy_parameter p where parameter_name='OfficeIdSelectOne');
+
+INSERT INTO `m_permission` (
+`grouping` ,
+`code` ,
+`entity_name` ,
+`action_name` ,
+`can_maker_checker`
+) VALUES ('report', 'READ_General Ledger Report', 'General Ledger Report', 'READ', 0);
+
+update `stretchy_parameter` 
+set `parameter_sql` = 'select id aid,name aname\r\nfrom acc_gl_account'
+where stretchy_parameter.parameter_name='SelectGLAccountNO';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V257__staff_image_association.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V257__staff_image_association.sql
new file mode 100644
index 0000000..9af582b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V257__staff_image_association.sql
@@ -0,0 +1,16 @@
+ALTER TABLE `m_staff` 
+	ADD COLUMN `image_id` BIGINT(20) NULL,
+	ADD CONSTRAINT `FK_m_staff_m_image` FOREIGN KEY (`image_id`) REFERENCES `m_image` (`id`);
+
+INSERT INTO m_permission (
+grouping ,
+code ,
+entity_name ,
+action_name ,
+can_maker_checker
+) VALUES
+('portfolio', 'READ_STAFFIMAGE', 'STAFFIMAGE', 'READ', '0'),
+('portfolio', 'CREATE_STAFFIMAGE', 'STAFFIMAGE', 'CREATE', '1'),
+('portfolio', 'CREATE_STAFFIMAGE_CHECKER', 'STAFFIMAGE', 'CREATE', '0'),
+('portfolio', 'DELETE_STAFFIMAGE', 'STAFFIMAGE', 'DELETE', '1'),
+('portfolio', 'DELETE_STAFFIMAGE_CHECKER', 'STAFFIMAGE', 'DELETE', '0');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V258__interest_compounding_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V258__interest_compounding_changes.sql
new file mode 100644
index 0000000..8a46dce
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V258__interest_compounding_changes.sql
@@ -0,0 +1,10 @@
+ALTER TABLE `m_product_loan_recalculation_details`
+	ADD COLUMN `compounding_frequency_type_enum` SMALLINT(1) NULL DEFAULT NULL,
+	ADD COLUMN `compounding_frequency_interval` SMALLINT(3) NULL DEFAULT NULL,
+	ADD COLUMN `compounding_freqency_date` DATE NULL DEFAULT NULL;
+	
+	
+ALTER TABLE `m_loan_recalculation_details`
+	ADD COLUMN `compounding_frequency_type_enum` SMALLINT(1) NULL DEFAULT NULL,
+	ADD COLUMN `compounding_frequency_interval` SMALLINT(3) NULL DEFAULT NULL,
+	ADD COLUMN `compounding_freqency_date` DATE NULL DEFAULT NULL;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V259__alter_working_days.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V259__alter_working_days.sql
new file mode 100644
index 0000000..223d527
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V259__alter_working_days.sql
@@ -0,0 +1 @@
+ALTER TABLE `m_working_days` ADD extend_term_daily_repayments TINYINT(1) NULL DEFAULT '0';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V25__update_client_reports_for_status_and_activation_change.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V25__update_client_reports_for_status_and_activation_change.sql
new file mode 100644
index 0000000..1b28722
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V25__update_client_reports_for_status_and_activation_change.sql
@@ -0,0 +1,82 @@
+update stretchy_report
+set report_sql = "select
+concat(repeat("".."",
+   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, '.', '')) - 1))), ounder.`name`) as ""Office/Branch"",
+ c.account_no as ""Client Account No."",
+c.display_name as ""Name"",
+r.enum_message_property as ""Status"",
+c.activation_date as ""Activation"", c.external_id as ""External Id""
+from m_office o
+join m_office ounder on ounder.hierarchy like concat(o.hierarchy, '%')
+and ounder.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_client c on c.office_id = ounder.id
+left join r_enum_value r on r.enum_name = 'status_enum' and r.enum_id = c.status_enum
+where o.id = ${officeId}
+order by ounder.hierarchy, c.account_no"
+
+where report_name = 'Client Listing';
+
+update stretchy_report
+set report_sql = "select
+concat(repeat("".."",
+   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, '.', '')) - 1))), ounder.`name`) as ""Office/Branch"", c.account_no as ""Client Account No."",
+c.display_name as ""Name"",
+r.enum_message_property as ""Client Status"",
+lo.display_name as ""Loan Officer"", l.account_no as ""Loan Account No."", l.external_id as ""External Id"", p.name as Loan, st.enum_message_property as ""Status"",
+f.`name` as Fund, purp.code_value as ""Loan Purpose"",
+ifnull(cur.display_symbol, l.currency_code) as Currency,
+l.principal_amount, l.arrearstolerance_amount as ""Arrears Tolerance Amount"",
+l.number_of_repayments as ""Expected No. Repayments"",
+l.annual_nominal_interest_rate as "" Annual Nominal Interest Rate"",
+l.nominal_interest_rate_per_period as ""Nominal Interest Rate Per Period"",
+ipf.enum_message_property as ""Interest Rate Frequency"",
+im.enum_message_property as ""Interest Method"",
+icp.enum_message_property as ""Interest Calculated in Period"",
+l.term_frequency as ""Term Frequency"",
+tf.enum_message_property as ""Term Frequency Period"",
+l.repay_every as ""Repayment Frequency"",
+rf.enum_message_property as ""Repayment Frequency Period"",
+am.enum_message_property as ""Amortization"",
+l.total_charges_due_at_disbursement_derived as ""Total Charges Due At Disbursement"",
+date(l.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As ""Expected Disbursal"",
+date(l.expected_firstrepaymenton_date) as ""Expected First Repayment"",
+date(l.interest_calculated_from_date) as ""Interest Calculated From"" ,
+date(l.disbursedon_date) as Disbursed,
+date(l.expected_maturedon_date) ""Expected Maturity"",
+date(l.maturedon_date) as ""Matured On"", date(l.closedon_date) as Closed,
+date(l.rejectedon_date) as Rejected, date(l.rescheduledon_date) as Rescheduled,
+date(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) ""Written Off""
+from m_office o
+join m_office ounder on ounder.hierarchy like concat(o.hierarchy, '%')
+and ounder.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_client c on c.office_id = ounder.id
+left join r_enum_value r on r.enum_name = 'status_enum'
+ and r.enum_id = c.status_enum
+left join m_loan l on l.client_id = c.id
+left join m_staff lo on lo.id = l.loan_officer_id
+left join m_product_loan p on p.id = l.product_id
+left join m_fund f on f.id = l.fund_id
+left join r_enum_value st on st.enum_name = ""loan_status_id"" and st.enum_id = l.loan_status_id
+left join r_enum_value ipf on ipf.enum_name = ""interest_period_frequency_enum""
+ and ipf.enum_id = l.interest_period_frequency_enum
+left join r_enum_value im on im.enum_name = ""interest_method_enum""
+ and im.enum_id = l.interest_method_enum
+left join r_enum_value tf on tf.enum_name = ""term_period_frequency_enum""
+ and tf.enum_id = l.term_period_frequency_enum
+left join r_enum_value icp on icp.enum_name = ""interest_calculated_in_period_enum""
+ and icp.enum_id = l.interest_calculated_in_period_enum
+left join r_enum_value rf on rf.enum_name = ""repayment_period_frequency_enum""
+ and rf.enum_id = l.repayment_period_frequency_enum
+left join r_enum_value am on am.enum_name = ""amortization_method_enum""
+ and am.enum_id = l.amortization_method_enum
+left join m_code_value purp on purp.id = l.loanpurpose_cv_id
+left join m_currency cur on cur.code = l.currency_code
+where o.id = ${officeId}
+and (l.currency_code = ""${currencyId}"" or ""-1"" = ""${currencyId}"")
+and (l.product_id = ""${loanProductId}"" or ""-1"" = ""${loanProductId}"")
+and (ifnull(l.loan_officer_id, -10) = ""${loanOfficerId}"" or ""-1"" = ""${loanOfficerId}"")
+and (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})
+and (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})
+order by ounder.hierarchy, 2 , l.id"
+
+where report_name = 'Client Loans Listing';
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V260__alter_password_validation_policy.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V260__alter_password_validation_policy.sql
new file mode 100644
index 0000000..125dd16
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V260__alter_password_validation_policy.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `m_password_validation_policy` ADD COLUMN `key` VARCHAR(255) NOT NULL;
+
+UPDATE `m_password_validation_policy` pvp SET pvp.`key`='simple' where pvp.id='1' ;
+UPDATE `m_password_validation_policy` pvp SET pvp.`key`='secure' where pvp.id='2' ;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V261__Update script for Client_Loan_Account_Schedule_Report.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V261__Update script for Client_Loan_Account_Schedule_Report.sql
new file mode 100644
index 0000000..4fde4d1
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V261__Update script for Client_Loan_Account_Schedule_Report.sql
@@ -0,0 +1,3 @@
+update stretchy_report_parameter scr set scr.report_parameter_name='selectLoan' where scr.report_id=(select sr.id from    
+stretchy_report  sr where report_name='Client Loan Account Schedule')
+and scr.parameter_id=(select sp.id from  stretchy_parameter sp where parameter_label = 'Enter Account No');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V262__accountNumber_for_groups.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V262__accountNumber_for_groups.sql
new file mode 100644
index 0000000..027b7e9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V262__accountNumber_for_groups.sql
@@ -0,0 +1,2 @@
+ALTER TABLE m_group ADD COLUMN `account_no` VARCHAR(20) NOT NULL;
+UPDATE m_group set account_no = lpad(id,9,0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V263__mifos_reports.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V263__mifos_reports.sql
new file mode 100644
index 0000000..e36859c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V263__mifos_reports.sql
@@ -0,0 +1,26 @@
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Active Loan Summary per Branch', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1);
+
+INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `special`, `selectOne`, `selectAll`, `parameter_sql`, `parent_id`) VALUES ('asOnDate', 'asOn', 'As On', 'date', 'date', 'today', NULL, NULL, NULL, NULL, NULL);
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Balance Outstanding', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1);
+
+INSERT INTO `stretchy_report_parameter` (report_id,parameter_id,report_parameter_name) VALUES ((select id from stretchy_report where report_name = 'Balance Outstanding'),(select id from stretchy_parameter where parameter_name='OfficeIdSelectOne'),'branch');
+INSERT INTO `stretchy_report_parameter` (report_id,parameter_id,report_parameter_name) VALUES ((select id from stretchy_report where report_name = 'Balance Outstanding'),(select id from stretchy_parameter where parameter_name='asOnDate'),'ondate');
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Collection Report', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1);
+INSERT INTO `stretchy_report_parameter` (report_id,parameter_id,report_parameter_name) VALUES ((select id from stretchy_report where report_name = 'Collection Report'),(select id from stretchy_parameter where parameter_name='OfficeIdSelectOne'),'branch');
+INSERT INTO `stretchy_report_parameter` (report_id,parameter_id,report_parameter_name) VALUES ((select id from stretchy_report where report_name = 'Collection Report'),(select id from stretchy_parameter where parameter_name='startDateSelect'),'fromDate');
+INSERT INTO `stretchy_report_parameter` (report_id,parameter_id,report_parameter_name) VALUES ((select id from stretchy_report where report_name = 'Collection Report'),(select id from stretchy_parameter where parameter_name='endDateSelect'),'toDate');
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Disbursal Report', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1);
+INSERT INTO `stretchy_report_parameter` (report_id,parameter_id,report_parameter_name) VALUES ((select id from stretchy_report where report_name = 'Disbursal Report'),(select id from stretchy_parameter where parameter_name='OfficeIdSelectOne'),'branch');
+INSERT INTO `stretchy_report_parameter` (report_id,parameter_id,report_parameter_name) VALUES ((select id from stretchy_report where report_name = 'Disbursal Report'),(select id from stretchy_parameter where parameter_name='startDateSelect'),'fromDate');
+INSERT INTO `stretchy_report_parameter` (report_id,parameter_id,report_parameter_name) VALUES ((select id from stretchy_report where report_name = 'Disbursal Report'),(select id from stretchy_parameter where parameter_name='endDateSelect'),'toDate');
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Active Loan Summary per Branch', 'Active Loan Summary per Branch', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Disbursal Report', 'Disbursal Report', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Balance Outstanding', 'Balance Outstanding', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('report', 'READ_Collection Report', 'Collection Report', 'READ', 0);
+
+
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V264__insert_paymenttype_and_report_read_permission.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V264__insert_paymenttype_and_report_read_permission.sql
new file mode 100644
index 0000000..8a93473
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V264__insert_paymenttype_and_report_read_permission.sql
@@ -0,0 +1,2 @@
+INSERT INTO m_permission (grouping,code,entity_name,action_name,can_maker_checker) VALUES ('portfolio','READ_PAYMENTTYPE','PAYMENTTYPE','READ',0);
+INSERT INTO m_permission (grouping,code,entity_name,action_name,can_maker_checker) VALUES ('report','READ_Staff Assignment History','Staff Assignment History(Pentaho)','READ',0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V265__modify_external_service_schema.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V265__modify_external_service_schema.sql
new file mode 100644
index 0000000..c614dfb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V265__modify_external_service_schema.sql
@@ -0,0 +1,48 @@
+
+
+
+alter table c_external_service drop index name;
+
+Rename table c_external_service to c_external_service_properties;
+
+CREATE TABLE `c_external_service` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`name` VARCHAR(100) NULL DEFAULT NULL,	
+	PRIMARY KEY (`id`),
+	UNIQUE KEY `name_UNIQUE` (`name`)
+) 
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+insert into `c_external_service` ( `name`) values( 'S3');
+
+Alter table c_external_service_properties 
+	ADD COLUMN `external_service_id` BIGINT(20) NULL DEFAULT NULL;
+
+update c_external_service_properties set external_service_id = (select id from c_external_service where name = 'S3');
+
+ALTER TABLE `c_external_service_properties`
+    CHANGE COLUMN `external_service_id` `external_service_id` BIGINT(20) NOT NULL;
+
+ALTER TABLE `c_external_service_properties`
+    ADD CONSTRAINT `FK_c_external_service_properties_c_external_service` FOREIGN KEY (`external_service_id`) REFERENCES `c_external_service` (`id`);
+
+
+insert into `c_external_service` ( `name`) values( 'SMTP_Email_Account');
+
+insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('username', 'support@cloudmicrofinance.com', (select id from c_external_service where name = 'SMTP_Email_Account'));
+
+insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('password', 'support80', (select id from c_external_service where name = 'SMTP_Email_Account'));
+
+insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('host', 'smtp.gmail.com', (select id from c_external_service where name = 'SMTP_Email_Account'));
+
+insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('port', '25', (select id from c_external_service where name = 'SMTP_Email_Account'));
+
+insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('useTLS', 'true', (select id from c_external_service where name = 'SMTP_Email_Account'));
+
+insert into m_permission(grouping, code, entity_name, action_name, can_maker_checker) values('externalservices', 'UPDATE_EXTERNALSERVICES', 'EXTERNALSERVICES', 'UPDATE', 0);
+
+
+	
+commit;
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V266__client_fees.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V266__client_fees.sql
new file mode 100644
index 0000000..5fa7254
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V266__client_fees.sql
@@ -0,0 +1,85 @@
+-- add permissions for Client Fees
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'READ_CLIENTCHARGE', 'CLIENTCHARGE', 'READ', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_CLIENTCHARGE', 'CLIENTCHARGE', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_CLIENTCHARGE', 'CLIENTCHARGE', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'WAIVE_CLIENTCHARGE', 'CLIENTCHARGE', 'WAIVE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'PAY_CLIENTCHARGE', 'CLIENTCHARGE', 'PAY', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'INACTIVATE_CLIENTCHARGE', 'CLIENTCHARGE', 'INACTIVATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_CLIENTCHARGE', 'CLIENTCHARGE', 'UPDATE', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'CREATE_CHECKER', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'DELETE_CHECKER', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'WAIVE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'WAIVE_CHECKER', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'PAY_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'PAY_CHECKER', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'INACTIVATE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'INACTIVATE_CHECKER', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'UPDATE_CHECKER', 0);
+
+
+-- new tables
+CREATE TABLE `m_client_transaction` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`client_id` BIGINT(20) NOT NULL,
+	`office_id` BIGINT(20) NOT NULL,
+	`currency_code` VARCHAR(3) NOT NULL,
+	`payment_detail_id` BIGINT(20) NULL DEFAULT NULL,
+	`is_reversed` TINYINT(1) NOT NULL,
+	`external_id` VARCHAR(50) NULL DEFAULT NULL,
+	`transaction_date` DATE NOT NULL,
+	`transaction_type_enum` SMALLINT(5) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`created_date` DATETIME NOT NULL,
+	`appuser_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `external_id` (`external_id`),
+	INDEX `FK_m_client_transaction_m_client` (`client_id`),
+	INDEX `FK_m_client_transaction_m_appuser` (`appuser_id`),
+	CONSTRAINT `FK_m_client_transaction_m_appuser` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`),
+	CONSTRAINT `FK_m_client_transaction_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+;
+
+CREATE TABLE `m_client_charge` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`client_id` BIGINT(20) NOT NULL,
+	`charge_id` BIGINT(20) NOT NULL,
+	`is_penalty` TINYINT(1) NOT NULL,
+	`charge_time_enum` SMALLINT(5) NOT NULL,
+	`charge_due_date` DATE NULL DEFAULT NULL,
+	`charge_calculation_enum` SMALLINT(5) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`amount_paid_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount_waived_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount_writtenoff_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`amount_outstanding_derived` DECIMAL(19,6) NOT NULL,
+	`is_paid_derived` TINYINT(1) NULL DEFAULT NULL,
+	`waived` TINYINT(1) NULL DEFAULT NULL,
+	`is_active` TINYINT(1) NULL DEFAULT NULL,
+	`inactivated_on_date` DATE NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_client_charge_m_client` (`client_id`),
+	INDEX `FK_m_client_charge_m_charge` (`charge_id`),
+	CONSTRAINT `FK_m_client_charge_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+	CONSTRAINT `FK_m_client_charge_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+;
+
+CREATE TABLE `m_client_charge_paid_by` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`client_transaction_id` BIGINT(20) NOT NULL,
+	`client_charge_id` BIGINT(20) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_client_charge_paid_by_m_client_transaction` (`client_transaction_id`),
+	INDEX `FK_m_client_charge_paid_by_m_client_charge` (`client_charge_id`),
+	CONSTRAINT `FK_m_client_charge_paid_by_m_client_charge` FOREIGN KEY (`client_charge_id`) REFERENCES `m_client_charge` (`id`),
+	CONSTRAINT `FK_m_client_charge_paid_by_m_client_transaction` FOREIGN KEY (`client_transaction_id`) REFERENCES `m_client_transaction` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+;
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V267__client_transaction_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V267__client_transaction_permissions.sql
new file mode 100644
index 0000000..1e03340
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V267__client_transaction_permissions.sql
@@ -0,0 +1,3 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_client', 'READTRANSACTION_CLIENT', 'CLIENT', 'READTRANSACTION', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_client', 'UNDOTRANSACTION_CLIENT', 'CLIENT', 'UNDOTRANSACTION', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_client', 'UNDOTRANSACTION_CLIENT_CHECKER', 'CLIENT', 'UNDOTRANSACTION_CHECKER', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V268__update_gmail_password.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V268__update_gmail_password.sql
new file mode 100644
index 0000000..f035052
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V268__update_gmail_password.sql
@@ -0,0 +1 @@
+update c_external_service_properties set value = 'support81' where name = 'password' and external_service_id = (select id from c_external_service where name = 'SMTP_Email_Account');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V269__increased_calendar_title_length .sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V269__increased_calendar_title_length .sql
new file mode 100644
index 0000000..27fe94e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V269__increased_calendar_title_length .sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_calendar`
+	CHANGE COLUMN `title` `title` VARCHAR(70) NOT NULL;
+	
+ALTER TABLE `m_calendar_history`
+	CHANGE COLUMN `title` `title` VARCHAR(70) NOT NULL;	
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V26__add-support-for-withdrawal-fees-on-savings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V26__add-support-for-withdrawal-fees-on-savings.sql
new file mode 100644
index 0000000..6fea52b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V26__add-support-for-withdrawal-fees-on-savings.sql
@@ -0,0 +1,12 @@
+ALTER TABLE `m_savings_product`
+ADD COLUMN `withdrawal_fee_amount` DECIMAL(19,6) NULL DEFAULT NULL AFTER `lockin_period_frequency_enum`,
+ADD COLUMN `withdrawal_fee_type_enum` SMALLINT(5) NULL DEFAULT NULL AFTER `withdrawal_fee_amount`;
+
+
+ALTER TABLE `m_savings_account`
+ADD COLUMN `withdrawal_fee_amount` DECIMAL(19,6) NULL DEFAULT NULL AFTER `lockin_period_frequency_enum`,
+ADD COLUMN `withdrawal_fee_type_enum` SMALLINT(5) NULL DEFAULT NULL AFTER `withdrawal_fee_amount`;
+
+
+ALTER TABLE `m_savings_account`
+ADD COLUMN `total_withdrawal_fees_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `total_withdrawals_derived`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V270__add_rounding_mode_configuration.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V270__add_rounding_mode_configuration.sql
new file mode 100644
index 0000000..b7f919e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V270__add_rounding_mode_configuration.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `c_configuration`
+	ADD COLUMN `is_trap_door` boolean NOT NULL DEFAULT '0' AFTER `enabled`;
+
+insert into c_configuration(name, value, enabled, is_trap_door, description) values('rounding-mode', '6', '1', '1', '0 - UP, 1 - DOWN, 2- CEILING, 3- FLOOR, 4- HALF_UP, 5- HALF_DOWN, 6 - HALF_EVEN');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V271__accounting_for_client_charges.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V271__accounting_for_client_charges.sql
new file mode 100644
index 0000000..2c4071f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V271__accounting_for_client_charges.sql
@@ -0,0 +1,9 @@
+-- Charges have a reference to an Income or Liability account that gets credited when a payment is made
+ALTER TABLE `m_charge`
+	ADD COLUMN `income_or_liability_account_id` BIGINT(20) NULL AFTER `fee_frequency`,
+	ADD CONSTRAINT `FK_m_charge_acc_gl_account` FOREIGN KEY (`income_or_liability_account_id`) REFERENCES `acc_gl_account` (`id`);
+
+--Journal entries also refer to Client transactions
+ALTER TABLE `acc_gl_journal_entry`
+	ADD COLUMN `client_transaction_id` BIGINT(20) NULL DEFAULT NULL AFTER `savings_transaction_id`,
+	ADD CONSTRAINT `FK_acc_gl_journal_entry_m_client_transaction` FOREIGN KEY (`client_transaction_id`) REFERENCES `m_client_transaction` (`id`);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V272__loan_tranche_disbursement_charge.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V272__loan_tranche_disbursement_charge.sql
new file mode 100644
index 0000000..d3a00fa
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V272__loan_tranche_disbursement_charge.sql
@@ -0,0 +1,26 @@
+CREATE TABLE `m_loan_tranche_disbursement_charge` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_charge_id` BIGINT(20) NOT NULL,
+	`disbursement_detail_id` BIGINT(20) NULL,
+	PRIMARY KEY (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1
+;
+
+ALTER TABLE `m_loan_tranche_disbursement_charge`
+	ADD CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+	ADD CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_disbursement_detail` FOREIGN KEY (`disbursement_detail_id`) REFERENCES `m_loan_disbursement_detail` (`id`);
+	
+	
+CREATE TABLE `m_loan_tranche_charges` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_id` BIGINT(20) NOT NULL,
+	`charge_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`id`)
+) ;
+
+ALTER TABLE `m_loan_tranche_charges`
+	ADD CONSTRAINT `FK_m_loan_tranche_charges_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+	ADD CONSTRAINT `FK_m_loan_tranche_charges_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V273__oauth_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V273__oauth_changes.sql
new file mode 100644
index 0000000..6197606
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V273__oauth_changes.sql
@@ -0,0 +1,43 @@
+
+CREATE TABLE `oauth_client_details` (
+  `client_id` varchar(128) NOT NULL,
+  `resource_ids` varchar(256) DEFAULT NULL,
+  `client_secret` varchar(256) DEFAULT NULL,
+  `scope` varchar(256) DEFAULT NULL,
+  `authorized_grant_types` varchar(256) DEFAULT NULL,
+  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
+  `authorities` varchar(256) DEFAULT NULL,
+  `access_token_validity` int(11) DEFAULT NULL,
+  `refresh_token_validity` int(11) DEFAULT NULL,
+  `additional_information` varchar(4096) DEFAULT NULL,
+  `autoapprove` BIT(1) NULL DEFAULT NULL,
+  PRIMARY KEY (`client_id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1;
+
+INSERT INTO `oauth_client_details` (`client_id`, `client_secret`, `scope`, `authorized_grant_types`) VALUES ('community-app', '123', 'all', 'password,refresh_token');
+
+
+CREATE TABLE `oauth_access_token` (
+  `token_id` varchar(256) DEFAULT NULL,
+  `token` blob,
+  `authentication_id` varchar(256) DEFAULT NULL,
+  `user_name` varchar(256) DEFAULT NULL,
+  `client_id` varchar(256) DEFAULT NULL,
+  `authentication` blob,
+  `refresh_token` varchar(256) DEFAULT NULL
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1;
+
+CREATE TABLE `oauth_refresh_token` (
+  `token_id` varchar(256) DEFAULT NULL,
+  `token` blob,
+  `authentication` blob
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+	
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V274__Loan_Reschedule_Code_Value.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V274__Loan_Reschedule_Code_Value.sql
new file mode 100644
index 0000000..2c57cc3
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V274__Loan_Reschedule_Code_Value.sql
@@ -0,0 +1,5 @@
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) VALUES ('LoanRescheduleReason', '1');
+
+
+
+	
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V275__loan_transaction_to_repayment_schedule_mapping.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V275__loan_transaction_to_repayment_schedule_mapping.sql
new file mode 100644
index 0000000..13d7342
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V275__loan_transaction_to_repayment_schedule_mapping.sql
@@ -0,0 +1,15 @@
+CREATE TABLE `m_loan_transaction_repayment_schedule_mapping` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_transaction_id` BIGINT(20) NOT NULL,
+	`loan_repayment_schedule_id` BIGINT(20) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	`principal_portion_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`interest_portion_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`fee_charges_portion_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	`penalty_charges_portion_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_mappings_m_loan_transaction` (`loan_transaction_id`),
+	INDEX `FK_mappings_m_loan_repayment_schedule` (`loan_repayment_schedule_id`),
+	CONSTRAINT `FK_mappings_m_loan_repayment_schedule` FOREIGN KEY (`loan_repayment_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`),
+	CONSTRAINT `FK_mappings_m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V276__loan_recalulated_till_date.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V276__loan_recalulated_till_date.sql
new file mode 100644
index 0000000..21275bf
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V276__loan_recalulated_till_date.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan`
+	ADD COLUMN `interest_recalcualated_on` DATE NULL DEFAULT NULL AFTER `accrued_till`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V277__Loan_Product_Provisioning.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V277__Loan_Product_Provisioning.sql
new file mode 100644
index 0000000..b2b5ab0
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V277__Loan_Product_Provisioning.sql
@@ -0,0 +1,105 @@
+CREATE TABLE `m_provision_category` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`category_name` VARCHAR(100) NOT NULL,
+	`description` VARCHAR(300) NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `category_name` (`category_name`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1
+;
+
+INSERT INTO `m_provision_category` (`category_name`, `description`) VALUES ('STANDARD', 'Punctual Payment without any dues');
+INSERT INTO `m_provision_category` (`category_name`, `description`) VALUES ('SUB-STANDARD', 'Principal and/or Interest overdue by x days');
+INSERT INTO `m_provision_category` (`category_name`, `description`) VALUES ('DOUBTFUL', 'Principal and/or Interest overdue by x days and less than y');
+INSERT INTO `m_provision_category` (`category_name`, `description`) VALUES ('LOSS', 'Principal and/or Interest overdue by y days');
+
+CREATE TABLE `m_provisioning_criteria` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`criteria_name` VARCHAR(200) NOT NULL,
+	`createdby_id` BIGINT(20) NULL DEFAULT NULL,
+	`created_date` DATETIME NULL DEFAULT NULL,
+	`lastmodifiedby_id` BIGINT(20) NULL DEFAULT NULL,
+	`lastmodified_date` DATETIME NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `criteria_name` (`criteria_name`),
+	FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+	FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+);
+
+CREATE TABLE `m_provisioning_criteria_definition` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`criteria_id` BIGINT(20) NOT NULL,
+	`category_id` BIGINT(20) NOT NULL,
+	`min_age` BIGINT(20) NOT NULL,
+	`max_age` BIGINT(20) NOT NULL,
+	`provision_percentage` DECIMAL(5,2) NOT NULL,
+	`liability_account` BIGINT(20),
+	`expense_account` BIGINT(20),
+	PRIMARY KEY (`id`),
+	FOREIGN KEY (`criteria_id`) REFERENCES `m_provisioning_criteria` (`id`),
+	FOREIGN KEY (`category_id`) REFERENCES `m_provision_category` (`id`),
+	FOREIGN KEY (`liability_account`) REFERENCES `acc_gl_account` (`id`),
+	FOREIGN KEY (`expense_account`) REFERENCES `acc_gl_account` (`id`)
+);
+
+CREATE TABLE `m_loanproduct_provisioning_mapping` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`product_id` BIGINT(20) NOT NULL,
+	`criteria_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `product_id` (`product_id`),
+	FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+	FOREIGN KEY (`criteria_id`) REFERENCES `m_provisioning_criteria` (`id`)
+);
+
+CREATE TABLE `m_provisioning_history` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`journal_entry_created` BIT(1) DEFAULT 0,
+	`createdby_id` BIGINT(20) NULL DEFAULT NULL,
+	`created_date` DATE NULL DEFAULT NULL,
+	`lastmodifiedby_id` BIGINT(20) NULL DEFAULT NULL,
+	`lastmodified_date` DATE NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+	FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+);
+
+CREATE TABLE `m_loanproduct_provisioning_entry` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`history_id` BIGINT(20) NOT NULL,
+	`criteria_id` BIGINT(20) NOT NULL,
+	`currency_code` VARCHAR(3) NOT NULL,
+	`office_id` BIGINT(20) NOT NULL,
+	`product_id` BIGINT(20) NOT NULL,
+	`category_id` BIGINT(20) NOT NULL,
+	`overdue_in_days` BIGINT(20) DEFAULT 0,
+	`reseve_amount` DECIMAL(20,6) DEFAULT 0,
+	`liability_account` BIGINT(20) NULL DEFAULT NULL,
+	`expense_account` BIGINT(20) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	FOREIGN KEY (`history_id`) REFERENCES `m_provisioning_history` (`id`),
+	FOREIGN KEY (`criteria_id`) REFERENCES `m_provisioning_criteria` (`id`),
+	FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+	FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+	FOREIGN KEY (`category_id`) REFERENCES `m_provision_category` (`id`),
+	FOREIGN KEY (`liability_account`) REFERENCES `acc_gl_account` (`id`),
+	FOREIGN KEY (`expense_account`) REFERENCES `acc_gl_account` (`id`)
+);
+
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Generate Loan Loss Provisioning', 'Generate Loan Loss Provisioning', '0 0 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0);
+
+INSERT INTO `m_permission`(`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
+('LOAN_PROVISIONING', 'CREATE_PROVISIONCATEGORY', 'PROVISIONCATEGORY', 'CREATE', 0),
+('LOAN_PROVISIONING', 'DELETE_PROVISIONCATEGORY', 'PROVISIONCATEGORY', 'DELETE', 0),
+('LOAN_PROVISIONING', 'CREATE_PROVISIONCRITERIA', 'PROVISIONINGCRITERIA', 'CREATE', 0),
+('LOAN_PROVISIONING', 'UPDATE_PROVISIONCRITERIA', 'PROVISIONINGCRITERIA', 'UPDATE', 0),
+('LOAN_PROVISIONING', 'DELETE_PROVISIONCRITERIA', 'PROVISIONINGCRITERIA', 'DELETE', 0),
+('LOAN_PROVISIONING', 'CREATE_PROVISIONENTRIES', 'PROVISIONINGENTRIES', 'CREATE', 0),
+('LOAN_PROVISIONING', 'CREATE_PROVISIONJOURNALENTRIES', 'PROVISIONINGENTRIES', 'CREATE', 0),
+('LOAN_PROVISIONING', 'RECREATE_PROVISIONENTRIES', 'PROVISIONINGENTRIES', 'RECREATE', 0);
+
+INSERT INTO `m_appuser` ( `is_deleted`, `office_id`, `staff_id`, `username`, `firstname`, `lastname`, `password`, `email`, `firsttime_login_remaining`, `nonexpired`, `nonlocked`, `nonexpired_credentials`, `enabled`, `last_time_password_updated`, `password_never_expires`) VALUES
+	(0, 1, NULL, 'system', 'system', 'system', '5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a', 'demomfi@mifos.org', b'0', b'1', b'1', b'1', b'1', '2014-03-07', 0);
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V278__LoanTransactionProcessingStrategy.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V278__LoanTransactionProcessingStrategy.sql
new file mode 100644
index 0000000..0175abb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V278__LoanTransactionProcessingStrategy.sql
@@ -0,0 +1,22 @@
+UPDATE `ref_loan_transaction_processing_strategy` SET `name`='Penalties, Fees, Interest, Principal order'
+WHERE code='mifos-standard-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `name`='Overdue/Due Fee/Int,Principal' WHERE code='rbi-india-strategy' ; 
+UPDATE `ref_loan_transaction_processing_strategy` SET `name`='HeavensFamily Unique' WHERE code='heavensfamily-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `name`='Creocore Unique' WHERE code='creocore-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `name`='Principal, Interest, Penalties, Fees Order' 
+WHERE code='principal-interest-penalties-fees-order-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `name`='Interest, Principal, Penalties, Fees Order' 
+WHERE code='interest-principal-penalties-fees-order-strategy' ;
+
+ALTER TABLE `ref_loan_transaction_processing_strategy`ADD `sort_order` INT(4) ;
+
+UPDATE `ref_loan_transaction_processing_strategy` SET `sort_order`=1 WHERE code='mifos-standard-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `sort_order`=2 WHERE code='rbi-india-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `sort_order`=3 WHERE code='principal-interest-penalties-fees-order-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `sort_order`=4 WHERE code='interest-principal-penalties-fees-order-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `sort_order`=5 WHERE code='early-repayment-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `sort_order`=6 WHERE code='heavensfamily-strategy' ;
+UPDATE `ref_loan_transaction_processing_strategy` SET `sort_order`=7 WHERE code='creocore-strategy' ;
+
+
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V279__floating_rates.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V279__floating_rates.sql
new file mode 100644
index 0000000..f7d04b8
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V279__floating_rates.sql
@@ -0,0 +1,61 @@
+CREATE TABLE `m_floating_rates` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`name` VARCHAR(200) NOT NULL,
+	`is_base_lending_rate` BIT(1) NOT NULL DEFAULT 0,
+	`is_active` BIT(1) NOT NULL DEFAULT 1,
+	`createdby_id` BIGINT(20) NOT NULL,
+	`created_date` DATETIME NOT NULL,
+	`lastmodifiedby_id` BIGINT(20) NOT NULL,
+	`lastmodified_date` DATETIME NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `unq_name` (`name`)
+);
+
+CREATE TABLE `m_floating_rates_periods` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`floating_rates_id` BIGINT(20) NOT NULL,
+	`from_date` DATETIME NOT NULL,
+	`interest_rate` DECIMAL(19,6) NOT NULL,
+	`is_differential_to_base_lending_rate` BIT(1) NOT NULL DEFAULT 0,
+	`is_active` BIT(1) NOT NULL DEFAULT 1,
+	`createdby_id` BIGINT(20) NOT NULL,
+	`created_date` DATETIME NOT NULL,
+	`lastmodifiedby_id` BIGINT(20) NOT NULL,
+	`lastmodified_date` DATETIME NOT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_mappings_m_floating_rates` FOREIGN KEY (`floating_rates_id`) REFERENCES `m_floating_rates` (`id`)
+);
+
+ALTER TABLE `m_product_loan`
+ADD COLUMN `is_linked_to_floating_interest_rates` BIT(1) NOT NULL DEFAULT 0 AFTER `fund_id`,
+MODIFY COLUMN `nominal_interest_rate_per_period` DECIMAL(19,6) NULL DEFAULT NULL,
+MODIFY COLUMN `interest_period_frequency_enum` SMALLINT(5) NULL DEFAULT NULL,
+MODIFY COLUMN `annual_nominal_interest_rate` DECIMAL(19,6) NULL DEFAULT NULL;
+
+CREATE TABLE `m_product_loan_floating_rates` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_product_id` BIGINT(20) NOT NULL,
+	`floating_rates_id` BIGINT(20) NOT NULL,
+	`interest_rate_differential` DECIMAL(19,6) NOT NULL DEFAULT 0,
+	`min_differential_lending_rate` DECIMAL(19,6) NOT NULL DEFAULT 0,
+	`default_differential_lending_rate` DECIMAL(19,6) NOT NULL DEFAULT 0,
+	`max_differential_lending_rate` DECIMAL(19,6) NOT NULL DEFAULT 0,
+	`is_floating_interest_rate_calculation_allowed` BIT(1) NOT NULL DEFAULT 0,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_mappings_m_product_loan_id` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`),
+	CONSTRAINT `FK_mappings_m_floating_rates_id` FOREIGN KEY (`floating_rates_id`) REFERENCES `m_floating_rates` (`id`)
+);
+
+ALTER TABLE `m_loan`
+ADD COLUMN `is_floating_interest_rate` BIT(1) NULL DEFAULT 0 AFTER `arrearstolerance_amount`,
+ADD COLUMN `interest_rate_differential` DECIMAL(19,6) NULL DEFAULT 0 AFTER `is_floating_interest_rate`,
+MODIFY COLUMN `nominal_interest_rate_per_period` DECIMAL(19,6) NULL DEFAULT NULL,
+MODIFY COLUMN `interest_period_frequency_enum` SMALLINT(5) NULL DEFAULT NULL,
+MODIFY COLUMN `annual_nominal_interest_rate` DECIMAL(19,6) NULL DEFAULT NULL;
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) 
+VALUES ('portfolio', 'READ_FLOATINGRATE', 'FLOATINGRATE', 'READ', 0),
+	('portfolio', 'CREATE_FLOATINGRATE', 'FLOATINGRATE', 'CREATE', 1),
+	('portfolio', 'CREATE_FLOATINGRATE_CHECKER', 'FLOATINGRATE', 'CREATE_CHECKER', 0), 
+	('portfolio', 'UPDATE_FLOATINGRATE', 'FLOATINGRATE', 'UPDATE', 1),
+	('portfolio', 'UPDATE_FLOATINGRATE_CHECKER', 'FLOATINGRATE', 'UPDATE_CHECKER', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V27__add-loan-type-column-to-loan-table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V27__add-loan-type-column-to-loan-table.sql
new file mode 100644
index 0000000..fdc97c8
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V27__add-loan-type-column-to-loan-table.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan`
+	ADD COLUMN `loan_type_enum` SMALLINT(5) NOT NULL AFTER `loan_status_id`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V280__spm_framework_initial_tables.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V280__spm_framework_initial_tables.sql
new file mode 100644
index 0000000..478df21
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V280__spm_framework_initial_tables.sql
@@ -0,0 +1,72 @@
+CREATE TABLE `m_surveys` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`a_key` VARCHAR(32) NOT NULL,
+	`a_name` VARCHAR(255) NOT NULL,
+	`description` VARCHAR(4000) NULL,
+	`country_code` VARCHAR(2) NOT NULL,
+	`valid_from` DATETIME NULL DEFAULT NULL,
+	`valid_to` DATETIME NULL DEFAULT NULL,
+	PRIMARY KEY (`id`)
+);
+
+CREATE TABLE `m_survey_components` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` BIGINT(20) NOT NULL,
+  `a_key` VARCHAR(32) NOT NULL,
+  `a_text` VARCHAR(255) NOT NULL,
+  `description` VARCHAR(4000) NULL,
+  `sequence_no` INT(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`)
+);
+
+CREATE TABLE `m_survey_questions` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` BIGINT(20) NOT NULL,
+  `component_key` VARCHAR(32) NULL,
+  `a_key` VARCHAR(32) NOT NULL,
+  `a_text` VARCHAR(255) NOT NULL,
+  `description` VARCHAR(4000) NULL,
+  `sequence_no` INT(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`)
+);
+
+CREATE TABLE `m_survey_responses` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `question_id` BIGINT(20) NOT NULL,
+  `a_text` VARCHAR(255) NOT NULL,
+  `a_value` INT(4) NOT NULL,
+  `sequence_no` INT(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  FOREIGN KEY (`question_id`) REFERENCES `m_survey_questions` (`id`)
+);
+
+CREATE TABLE `m_survey_lookup_tables` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` BIGINT(20) NOT NULL,
+  `a_key` VARCHAR(255) NOT NULL,
+  `description` INT(4) NULL,
+  `value_from` INT(4) NOT NULL,
+  `value_to` INT(4) NOT NULL,
+  `score` DECIMAL(5, 2) NOT NULL,
+  PRIMARY KEY (`id`),
+  FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`)
+);
+
+CREATE TABLE `m_survey_scorecards` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` BIGINT(20) NOT NULL,
+  `question_id` BIGINT(20) NOT NULL,
+  `response_id` BIGINT(20) NOT NULL,
+  `staff_id` BIGINT(20) NOT NULL,
+  `client_id` BIGINT(20) NOT NULL,
+  `created_on` DATETIME NULL DEFAULT NULL,
+  `a_value` INT(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`),
+  FOREIGN KEY (`question_id`) REFERENCES `m_survey_questions` (`id`),
+  FOREIGN KEY (`response_id`) REFERENCES `m_survey_responses` (`id`),
+  FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
+  FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V281__add_configuration_param_backdate-penalties.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V281__add_configuration_param_backdate-penalties.sql
new file mode 100644
index 0000000..a4fcb540
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V281__add_configuration_param_backdate-penalties.sql
@@ -0,0 +1,4 @@
+INSERT INTO `c_configuration` ( `name`, `value`, `enabled`, `description`)
+VALUES
+  ( 'backdate-penalties-enabled', 0, 1, 'If this parameter is disabled penalties will only be added to instalments due moving forward, any old overdue instalments will not be affected.');
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V282__CustomerSelfService.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V282__CustomerSelfService.sql
new file mode 100644
index 0000000..5b60c0b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V282__CustomerSelfService.sql
@@ -0,0 +1,15 @@
+ALTER TABLE `m_appuser`
+ADD COLUMN `is_self_service_user` BIT(1) NOT NULL DEFAULT 0;
+
+CREATE TABLE `m_selfservice_user_client_mapping` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`appuser_id` BIGINT(20) NOT NULL,
+	`client_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `appuser_id_client_id` (`appuser_id`, `client_id`),
+	CONSTRAINT `m_selfservice_appuser_id` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`),
+	CONSTRAINT `m_selfservice_client_id` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V283__Variable_Installments.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V283__Variable_Installments.sql
new file mode 100644
index 0000000..924ce1d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V283__Variable_Installments.sql
@@ -0,0 +1,26 @@
+ALTER TABLE `m_product_loan`
+ADD COLUMN `allow_variabe_installments` BIT(1) NOT NULL DEFAULT 0 AFTER `is_linked_to_floating_interest_rates` ;
+
+CREATE TABLE `m_product_loan_variable_installment_config` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_product_id` BIGINT(20) NOT NULL,
+	`minimum_gap` INT(4) NOT NULL,
+	`maximum_gap` INT(4) NOT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `FK_mappings_m_variable_product_loan_id` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)	
+);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATESCHEDULEEXCEPTIONS_LOAN', 'LOAN', 'CREATESCHEDULEEXCEPTIONS', 0), ('portfolio', 'CREATESCHEDULEEXCEPTIONS_LOAN_CHECKER', 'LOAN', 'CREATESCHEDULEEXCEPTIONS_CHECKER', 0), ('portfolio', 'DELETESCHEDULEEXCEPTIONS_LOAN', 'LOAN', 'DELETESCHEDULEEXCEPTIONS', 0),('portfolio', 'DELETESCHEDULEEXCEPTIONS_LOAN_CHECKER', 'LOAN', 'DELETESCHEDULEEXCEPTIONS_CHECKER', 0);
+
+
+ALTER TABLE `m_loan_term_variations`
+	ALTER `applicable_from` DROP DEFAULT,
+	ALTER `term_value` DROP DEFAULT;
+ALTER TABLE `m_loan_term_variations`
+	CHANGE COLUMN `applicable_from` `applicable_date` DATE NOT NULL AFTER `term_type`,
+	CHANGE COLUMN `term_value` `decimal_value` DECIMAL(19,6) NULL AFTER `applicable_date`,
+	ADD COLUMN `date_value` DATE NULL AFTER `decimal_value`,
+	ADD COLUMN `is_specific_to_installment` TINYINT NOT NULL DEFAULT '0' AFTER `date_value`,
+	ADD COLUMN `applied_on_loan_status` SMALLINT(5) NOT NULL AFTER `is_specific_to_installment`;
+	
+UPDATE `m_loan_term_variations` SET `applied_on_loan_status`=300;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V284__update_codevalue.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V284__update_codevalue.sql
new file mode 100644
index 0000000..d839ecf
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V284__update_codevalue.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_code_value`
+	ADD COLUMN `is_active` TINYINT(1) NOT NULL DEFAULT '1' AFTER `code_score`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V285__undo_last_tranche_script.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V285__undo_last_tranche_script.sql
new file mode 100644
index 0000000..36ea895
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V285__undo_last_tranche_script.sql
@@ -0,0 +1,3 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_loan', 'DISBURSALLASTUNDO_LOAN', 'LOAN', 'DISBURSALLASTUNDO', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_loan', 'DISBURSALLASTUNDO_LOAN_CHECKER', 'LOAN', 'DISBURSALLASTUNDO_CHECKER', 0);
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V286__partial_period_interest_calcualtion.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V286__partial_period_interest_calcualtion.sql
new file mode 100644
index 0000000..d107b76
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V286__partial_period_interest_calcualtion.sql
@@ -0,0 +1,10 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `allow_partial_period_interest_calcualtion` TINYINT(1) NOT NULL DEFAULT '0' AFTER `interest_calculated_in_period_enum`;
+	
+ALTER TABLE `m_loan`
+	ADD COLUMN `allow_partial_period_interest_calcualtion` TINYINT(1) NOT NULL DEFAULT '0' AFTER `interest_calculated_in_period_enum`;
+	
+UPDATE m_product_loan mpl inner join (select mp.id as productId from m_product_loan mp where mp.interest_calculated_in_period_enum = 1 and  (mp.interest_recalculation_enabled = 1 or mp.allow_multiple_disbursals = 1 or mp.is_linked_to_floating_interest_rates =1 or mp.allow_variabe_installments =1)) x on x.productId = mpl.id SET mpl.allow_partial_period_interest_calcualtion = 1;	
+	
+UPDATE m_loan ml inner join (select loan.id as loanId from m_product_loan mp inner join m_loan loan on loan.product_id = mp.id where mp.allow_partial_period_interest_calcualtion = 1) x on x.loanId = ml.id SET ml.allow_partial_period_interest_calcualtion=1;
+	
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V287__alter_spm_scorecard.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V287__alter_spm_scorecard.sql
new file mode 100644
index 0000000..a5b852b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V287__alter_spm_scorecard.sql
@@ -0,0 +1,19 @@
+SET FOREIGN_KEY_CHECKS = 0;
+DROP TABLE IF EXISTS `m_survey_scorecards`;
+CREATE TABLE `m_survey_scorecards` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` BIGINT(20) NOT NULL,
+  `question_id` BIGINT(20) NOT NULL,
+  `response_id` BIGINT(20) NOT NULL,
+  `user_id` BIGINT(20) NOT NULL,
+  `client_id` BIGINT(20) NOT NULL,
+  `created_on` DATETIME NULL DEFAULT NULL,
+  `a_value` INT(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`),
+  FOREIGN KEY (`question_id`) REFERENCES `m_survey_questions` (`id`),
+  FOREIGN KEY (`response_id`) REFERENCES `m_survey_responses` (`id`),
+  FOREIGN KEY (`user_id`) REFERENCES `m_appusers` (`id`),
+  FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+);
+SET FOREIGN_KEY_CHECKS = 1;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V288__overdraft_interest.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V288__overdraft_interest.sql
new file mode 100644
index 0000000..ca3843f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V288__overdraft_interest.sql
@@ -0,0 +1,8 @@
+ALTER TABLE `m_savings_account`
+ADD COLUMN `nominal_annual_interest_rate_overdraft` DECIMAL(19,6) NULL DEFAULT 0 AFTER `overdraft_limit`,
+ADD COLUMN `total_overdraft_interest_derived` DECIMAL(19,6) NULL DEFAULT 0 AFTER `total_interest_posted_derived`,
+ADD COLUMN `min_overdraft_for_interest_calculation` DECIMAL(19,6) NULL DEFAULT 0 AFTER `nominal_annual_interest_rate_overdraft`;
+
+ALTER TABLE `m_savings_product`
+ADD COLUMN `nominal_annual_interest_rate_overdraft` DECIMAL(19,6) NULL DEFAULT 0 AFTER `overdraft_limit`,
+ADD COLUMN `min_overdraft_for_interest_calculation` DECIMAL(19,6) NULL DEFAULT 0 AFTER `nominal_annual_interest_rate_overdraft`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V289__client_non_person.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V289__client_non_person.sql
new file mode 100644
index 0000000..1393860
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V289__client_non_person.sql
@@ -0,0 +1,22 @@
+
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) values 
+	("Constitution", true);
+	
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) values 
+	("Main Business Line", true);
+	
+ALTER TABLE `m_client` ADD `legal_form_enum` INT(5);
+	
+CREATE TABLE `m_client_non_person` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`client_id` BIGINT(20) NOT NULL UNIQUE,
+	`constitution_cv_id` INT(11) NOT NULL,
+	`incorp_no` varchar(50),
+	`incorp_validity_till` DATETIME,
+	`main_business_line_cv_id` INT(11),
+	`remarks` varchar(150),
+	PRIMARY KEY (`id`),
+	INDEX `FK_client_id` (`client_id`),	
+	CONSTRAINT `FK_client_id` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+);
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V28__accounting-abstractions-and-autoposting.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V28__accounting-abstractions-and-autoposting.sql
new file mode 100755
index 0000000..8861af9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V28__accounting-abstractions-and-autoposting.sql
@@ -0,0 +1,46 @@
+CREATE TABLE `acc_accounting_rule` (
+	`id` BIGINT(20) NOT NULL,
+	`name` VARCHAR(100) NULL DEFAULT NULL,
+	`office_id` BIGINT(20) NULL DEFAULT NULL,
+	`debit_account_id` BIGINT(20) NOT NULL,
+	`credit_account_id` BIGINT(20) NOT NULL,
+	`description` VARCHAR(500) NULL DEFAULT NULL,
+	`system_defined` TINYINT(1) NOT NULL DEFAULT '0',
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `accounting_rule_name_unique` (`name`),
+	INDEX `FK_acc_accounting_rule_acc_gl_account_debit` (`debit_account_id`),
+	INDEX `FK_acc_accounting_rule_acc_gl_account_credit` (`credit_account_id`),
+	INDEX `FK_acc_accounting_rule_m_office` (`office_id`),
+	CONSTRAINT `FK_acc_accounting_rule_acc_gl_account_credit` FOREIGN KEY (`credit_account_id`) REFERENCES `acc_gl_account` (`id`),
+	CONSTRAINT `FK_acc_accounting_rule_acc_gl_account_debit` FOREIGN KEY (`debit_account_id`) REFERENCES `acc_gl_account` (`id`),
+	CONSTRAINT `FK_acc_accounting_rule_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+CREATE TABLE `acc_auto_posting` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`name` VARCHAR(100) NOT NULL,
+	`description` VARCHAR(500) NULL DEFAULT NULL,
+	`office_id` BIGINT(20) NULL DEFAULT NULL,
+	`product_type_enum` SMALLINT(5) NOT NULL,
+	`product_id` BIGINT(20) NULL DEFAULT NULL,
+	`charge_id` BIGINT(20) NULL DEFAULT NULL,
+	`event` INT(11) NOT NULL,
+	`event_attribute` INT(11) NULL DEFAULT NULL,
+	`accounting_rule_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `auto_posting_name_unique` (`name`),
+	INDEX `FK_acc_auto_posting_m_office` (`office_id`),
+	INDEX `FK_acc_auto_posting_acc_accounting_rule` (`accounting_rule_id`),
+	INDEX `FK_acc_auto_posting_m_code` (`event`),
+	INDEX `FK_acc_auto_posting_m_charge` (`charge_id`),
+	INDEX `FK_acc_auto_posting_m_code_value` (`event_attribute`),
+	CONSTRAINT `FK_acc_auto_posting_acc_accounting_rule` FOREIGN KEY (`accounting_rule_id`) REFERENCES `acc_accounting_rule` (`id`),
+	CONSTRAINT `FK_acc_auto_posting_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+	CONSTRAINT `FK_acc_auto_posting_m_code` FOREIGN KEY (`event`) REFERENCES `m_code` (`id`),
+	CONSTRAINT `FK_acc_auto_posting_m_code_value` FOREIGN KEY (`event_attribute`) REFERENCES `m_code_value` (`id`),
+	CONSTRAINT `FK_acc_auto_posting_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V290__shares_dividends_permissions_script.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V290__shares_dividends_permissions_script.sql
new file mode 100644
index 0000000..1bf56e9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V290__shares_dividends_permissions_script.sql
@@ -0,0 +1,5 @@
+INSERT INTO `m_permission`(`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
+('SHAREPRODUCT', 'CREATE_SHAREPRODUCT', 'SHAREPRODUCT', 'CREATE', 0),
+('SHAREPRODUCT', 'UPDATE_SHAREPRODUCT', 'SHAREPRODUCT', 'CREATE', 0),
+('SHAREACCOUNT', 'CREATE_SHAREACCOUNT', 'SHAREACCOUNT', 'CREATE', 0),
+('SHAREACCOUNT', 'UPDATE_SHAREACCOUNT', 'SHAREACCOUNT', 'CREATE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V29__add-support-for-annual-fees-on-savings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V29__add-support-for-annual-fees-on-savings.sql
new file mode 100644
index 0000000..a471920
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V29__add-support-for-annual-fees-on-savings.sql
@@ -0,0 +1,13 @@
+ALTER TABLE `m_savings_product`
+ADD COLUMN `annual_fee_amount` DECIMAL(19,6) NULL DEFAULT NULL AFTER `withdrawal_fee_type_enum`,
+ADD COLUMN `annual_fee_on_month` SMALLINT(5) NULL DEFAULT NULL AFTER `annual_fee_amount`,
+ADD COLUMN `annual_fee_on_day` SMALLINT(5) NULL DEFAULT NULL AFTER `annual_fee_on_month`;
+
+ALTER TABLE `m_savings_account`
+ADD COLUMN `annual_fee_amount` DECIMAL(19,6) NULL DEFAULT NULL AFTER `withdrawal_fee_type_enum`,
+ADD COLUMN `annual_fee_on_month` SMALLINT(5) NULL DEFAULT NULL AFTER `annual_fee_amount`,
+ADD COLUMN `annual_fee_on_day` SMALLINT(5) NULL DEFAULT NULL AFTER `annual_fee_on_month`,
+ADD COLUMN `annual_fee_next_due_date` DATE NULL DEFAULT NULL AFTER `annual_fee_on_day`;
+
+ALTER TABLE `m_savings_account`
+ADD COLUMN `total_annual_fees_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `total_withdrawal_fees_derived`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V2__mifosx-base-reference-data-utf8.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V2__mifosx-base-reference-data-utf8.sql
new file mode 100644
index 0000000..73d4bfe
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V2__mifosx-base-reference-data-utf8.sql
@@ -0,0 +1,321 @@
+-- currency symbols may not apply through command line on windows so use a different client like mysql workbench
+
+INSERT INTO `ref_loan_transaction_processing_strategy`
+(`id`,`code`,`name`)
+VALUES
+(1, 'mifos-standard-strategy', 'Mifos style'),
+(2, 'heavensfamily-strategy', 'Heavensfamily'),
+(3, 'creocore-strategy', 'Creocore'),
+(4, 'rbi-india-strategy', 'RBI (India)');
+
+INSERT INTO `c_configuration`
+(`name`, `enabled`)
+VALUES
+('maker-checker', 0);
+
+INSERT INTO `r_enum_value`
+VALUES
+('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),
+('amortization_method_enum',1,'Equal installments','Equal installments'),
+('interest_calculated_in_period_enum',0,'Daily','Daily'),
+('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),
+('interest_method_enum',0,'Declining Balance','Declining Balance'),
+('interest_method_enum',1,'Flat','Flat'),
+('interest_period_frequency_enum',2,'Per month','Per month'),
+('interest_period_frequency_enum',3,'Per year','Per year'),
+('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),
+('loan_status_id',200,'Approved','Approved'),
+('loan_status_id',300,'Active','Active'),
+('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),
+('loan_status_id',500,'Rejected','Rejected'),
+('loan_status_id',600,'Closed','Closed'),
+('loan_status_id',601,'Written-Off','Written-Off'),
+('loan_status_id',602,'Rescheduled','Rescheduled'),
+('loan_status_id',700,'Overpaid','Overpaid'),
+('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),
+('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),
+('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),
+('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),
+('processing_result_enum',0,'invalid','Invalid'),
+('processing_result_enum',1,'processed','Processed'),
+('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),
+('processing_result_enum',3,'rejected','Rejected'),
+('repayment_period_frequency_enum',0,'Days','Days'),
+('repayment_period_frequency_enum',1,'Weeks','Weeks'),
+('repayment_period_frequency_enum',2,'Months','Months'),
+('term_period_frequency_enum',0,'Days','Days'),
+('term_period_frequency_enum',1,'Weeks','Weeks'),
+('term_period_frequency_enum',2,'Months','Months'),
+('term_period_frequency_enum',3,'Years','Years');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '1', 'Disbursement', 'Disbursement');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '2', 'Repayment', 'Repayment');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '3', 'Contra', 'Contra');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '4', 'Waive Interest', 'Waive Interest');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '5', 'Repayment At Disbursement', 'Repayment At Disbursement');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '6', 'Write-Off', 'Write-Off');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '7', 'Marked for Rescheduling', 'Marked for Rescheduling');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '8', 'Recovery Repayment', 'Recovery Repayment');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '9', 'Waive Charges', 'Waive Charges');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '10', 'Apply Charges', 'Apply Charges');
+
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
+VALUES ('transaction_type_enum', '11', 'Apply Interest', 'Apply Interest');
+
+INSERT INTO `m_currency`
+(`id`,`code`,`decimal_places`,`display_symbol`,`name`, `internationalized_name_code`)
+VALUES
+(1,'AED',2,NULL,'UAE Dirham','currency.AED'),
+(2,'AFN',2,NULL,'Afghanistan Afghani','currency.AFN'),
+(3,'ALL',2,NULL,'Albanian Lek','currency.ALL'),
+(4,'AMD',2,NULL,'Armenian Dram','currency.AMD'),
+(5,'ANG',2,NULL,'Netherlands Antillian Guilder','currency.ANG'),
+(6,'AOA',2,NULL,'Angolan Kwanza','currency.AOA'),
+(7,'ARS',2,'$','Argentine Peso','currency.ARS'),
+(8,'AUD',2,'A$','Australian Dollar','currency.AUD'),
+(9,'AWG',2,NULL,'Aruban Guilder','currency.AWG'),
+(10,'AZM',2,NULL,'Azerbaijanian Manat','currency.AZM'),
+(11,'BAM',2,NULL,'Bosnia and Herzegovina Convertible Marks','currency.BAM'),
+(12,'BBD',2,NULL,'Barbados Dollar','currency.BBD'),
+(13,'BDT',2,NULL,'Bangladesh Taka','currency.BDT'),
+(14,'BGN',2,NULL,'Bulgarian Lev','currency.BGN'),
+(15,'BHD',3,NULL,'Bahraini Dinar','currency.BHD'),
+(16,'BIF',0,NULL,'Burundi Franc','currency.BIF'),
+(17,'BMD',2,NULL,'Bermudian Dollar','currency.BMD'),
+(18,'BND',2,'B$','Brunei Dollar','currency.BND'),
+(19,'BOB',2,'Bs.','Bolivian Boliviano','currency.BOB'),
+(20,'BRL',2,'R$','Brazilian Real','currency.BRL'),
+(21,'BSD',2,NULL,'Bahamian Dollar','currency.BSD'),
+(22,'BTN',2,NULL,'Bhutan Ngultrum','currency.BTN'),
+(23,'BWP',2,NULL,'Botswana Pula','currency.BWP'),
+(24,'BYR',0,NULL,'Belarussian Ruble','currency.BYR'),
+(25,'BZD',2,'BZ$','Belize Dollar','currency.BZD'),
+(26,'CAD',2,NULL,'Canadian Dollar','currency.CAD'),
+(27,'CDF',2,NULL,'Franc Congolais','currency.CDF'),
+(28,'CHF',2,NULL,'Swiss Franc','currency.CHF'),
+(29,'CLP',0,'$','Chilean Peso','currency.CLP'),
+(30,'CNY',2,NULL,'Chinese Yuan Renminbi','currency.CNY'),
+(31,'COP',2,'$','Colombian Peso','currency.COP'),
+(32,'CRC',2,'₡','Costa Rican Colon','currency.CRC'),
+(33,'CSD',2,NULL,'Serbian Dinar','currency.CSD'),
+(34,'CUP',2,'$MN','Cuban Peso','currency.CUP'),
+(35,'CVE',2,NULL,'Cape Verde Escudo','currency.CVE'),
+(36,'CYP',2,NULL,'Cyprus Pound','currency.CYP'),
+(37,'CZK',2,NULL,'Czech Koruna','currency.CZK'),
+(38,'DJF',0,NULL,'Djibouti Franc','currency.DJF'),
+(39,'DKK',2,NULL,'Danish Krone','currency.DKK'),
+(40,'DOP',2,'RD$','Dominican Peso','currency.DOP'),
+(41,'DZD',2,NULL,'Algerian Dinar','currency.DZD'),
+(42,'EEK',2,NULL,'Estonian Kroon','currency.EEK'),
+(43,'EGP',2,NULL,'Egyptian Pound','currency.EGP'),
+(44,'ERN',2,NULL,'Eritrea Nafka','currency.ERN'),
+(45,'ETB',2,NULL,'Ethiopian Birr','currency.ETB'),
+(46,'EUR',2,'€','Euro','currency.EUR'),
+(47,'FJD',2,NULL,'Fiji Dollar','currency.FJD'),
+(48,'FKP',2,NULL,'Falkland Islands Pound','currency.FKP'),
+(49,'GBP',2,NULL,'Pound Sterling','currency.GBP'),
+(50,'GEL',2,NULL,'Georgian Lari','currency.GEL'),
+(51,'GHC',2,'GHc','Ghana Cedi','currency.GHC'),
+(52,'GIP',2,NULL,'Gibraltar Pound','currency.GIP'),
+(53,'GMD',2,NULL,'Gambian Dalasi','currency.GMD'),
+(54,'GNF',0,NULL,'Guinea Franc','currency.GNF'),
+(55,'GTQ',2,'Q','Guatemala Quetzal','currency.GTQ'),
+(56,'GYD',2,NULL,'Guyana Dollar','currency.GYD'),
+(57,'HKD',2,NULL,'Hong Kong Dollar','currency.HKD'),
+(58,'HNL',2,'L','Honduras Lempira','currency.HNL'),
+(59,'HRK',2,NULL,'Croatian Kuna','currency.HRK'),
+(60,'HTG',2,'G','Haiti Gourde','currency.HTG'),
+(61,'HUF',2,NULL,'Hungarian Forint','currency.HUF'),
+(62,'IDR',2,NULL,'Indonesian Rupiah','currency.IDR'),
+(63,'ILS',2,NULL,'New Israeli Shekel','currency.ILS'),
+(64,'INR',2,'₹','Indian Rupee','currency.INR'),
+(65,'IQD',3,NULL,'Iraqi Dinar','currency.IQD'),
+(66,'IRR',2,NULL,'Iranian Rial','currency.IRR'),
+(67,'ISK',0,NULL,'Iceland Krona','currency.ISK'),
+(68,'JMD',2,NULL,'Jamaican Dollar','currency.JMD'),
+(69,'JOD',3,NULL,'Jordanian Dinar','currency.JOD'),
+(70,'JPY',0,NULL,'Japanese Yen','currency.JPY'),
+(71,'KES',2,'KSh','Kenyan Shilling','currency.KES'),
+(72,'KGS',2,NULL,'Kyrgyzstan Som','currency.KGS'),
+(73,'KHR',2,NULL,'Cambodia Riel','currency.KHR'),
+(74,'KMF',0,NULL,'Comoro Franc','currency.KMF'),
+(75,'KPW',2,NULL,'North Korean Won','currency.KPW'),
+(76,'KRW',0,NULL,'Korean Won','currency.KRW'),
+(77,'KWD',3,NULL,'Kuwaiti Dinar','currency.KWD'),
+(78,'KYD',2,NULL,'Cayman Islands Dollar','currency.KYD'),
+(79,'KZT',2,NULL,'Kazakhstan Tenge','currency.KZT'),
+(80,'LAK',2,NULL,'Lao Kip','currency.LAK'),
+(81,'LBP',2,'L£','Lebanese Pound','currency.LBP'),
+(82,'LKR',2,NULL,'Sri Lanka Rupee','currency.LKR'),
+(83,'LRD',2,NULL,'Liberian Dollar','currency.LRD'),
+(84,'LSL',2,NULL,'Lesotho Loti','currency.LSL'),
+(85,'LTL',2,NULL,'Lithuanian Litas','currency.LTL'),
+(86,'LVL',2,NULL,'Latvian Lats','currency.LVL'),
+(87,'LYD',3,NULL,'Libyan Dinar','currency.LYD'),
+(88,'MAD',2,NULL,'Moroccan Dirham','currency.MAD'),
+(89,'MDL',2,NULL,'Moldovan Leu','currency.MDL'),
+(90,'MGA',2,NULL,'Malagasy Ariary','currency.MGA'),
+(91,'MKD',2,NULL,'Macedonian Denar','currency.MKD'),
+(92,'MMK',2,'K','Myanmar Kyat','currency.MMK'),
+(93,'MNT',2,NULL,'Mongolian Tugrik','currency.MNT'),
+(94,'MOP',2,NULL,'Macau Pataca','currency.MOP'),
+(95,'MRO',2,NULL,'Mauritania Ouguiya','currency.MRO'),
+(96,'MTL',2,NULL,'Maltese Lira','currency.MTL'),
+(97,'MUR',2,NULL,'Mauritius Rupee','currency.MUR'),
+(98,'MVR',2,NULL,'Maldives Rufiyaa','currency.MVR'),
+(99,'MWK',2,NULL,'Malawi Kwacha','currency.MWK'),
+(100,'MXN',2,'$','Mexican Peso','currency.MXN'),
+(101,'MYR',2,NULL,'Malaysian Ringgit','currency.MYR'),
+(102,'MZM',2,NULL,'Mozambique Metical','currency.MZM'),
+(103,'NAD',2,NULL,'Namibia Dollar','currency.NAD'),
+(104,'NGN',2,NULL,'Nigerian Naira','currency.NGN'),
+(105,'NIO',2,'C$','Nicaragua Cordoba Oro','currency.NIO'),
+(106,'NOK',2,NULL,'Norwegian Krone','currency.NOK'),
+(107,'NPR',2,NULL,'Nepalese Rupee','currency.NPR'),
+(108,'NZD',2,NULL,'New Zealand Dollar','currency.NZD'),
+(109,'OMR',3,NULL,'Rial Omani','currency.OMR'),
+(110,'PAB',2,'B/.','Panama Balboa','currency.PAB'),
+(111,'PEN',2,'S/.','Peruvian Nuevo Sol','currency.PEN'),
+(112,'PGK',2,NULL,'Papua New Guinea Kina','currency.PGK'),
+(113,'PHP',2,NULL,'Philippine Peso','currency.PHP'),
+(114,'PKR',2,NULL,'Pakistan Rupee','currency.PKR'),
+(115,'PLN',2,NULL,'Polish Zloty','currency.PLN'),
+(116,'PYG',0,'₲','Paraguayan Guarani','currency.PYG'),
+(117,'QAR',2,NULL,'Qatari Rial','currency.QAR'),
+(118,'RON',2,NULL,'Romanian Leu','currency.RON'),
+(119,'RUB',2,NULL,'Russian Ruble','currency.RUB'),
+(120,'RWF',0,NULL,'Rwanda Franc','currency.RWF'),
+(121,'SAR',2,NULL,'Saudi Riyal','currency.SAR'),
+(122,'SBD',2,NULL,'Solomon Islands Dollar','currency.SBD'),
+(123,'SCR',2,NULL,'Seychelles Rupee','currency.SCR'),
+(124,'SDD',2,NULL,'Sudanese Dinar','currency.SDD'),
+(125,'SEK',2,NULL,'Swedish Krona','currency.SEK'),
+(126,'SGD',2,NULL,'Singapore Dollar','currency.SGD'),
+(127,'SHP',2,NULL,'St Helena Pound','currency.SHP'),
+(128,'SIT',2,NULL,'Slovenian Tolar','currency.SIT'),
+(129,'SKK',2,NULL,'Slovak Koruna','currency.SKK'),
+(130,'SLL',2,NULL,'Sierra Leone Leone','currency.SLL'),
+(131,'SOS',2,NULL,'Somali Shilling','currency.SOS'),
+(132,'SRD',2,NULL,'Surinam Dollar','currency.SRD'),
+(133,'STD',2,NULL,'Sao Tome and Principe Dobra','currency.STD'),
+(134,'SVC',2,NULL,'El Salvador Colon','currency.SVC'),
+(135,'SYP',2,NULL,'Syrian Pound','currency.SYP'),
+(136,'SZL',2,NULL,'Swaziland Lilangeni','currency.SZL'),
+(137,'THB',2,NULL,'Thai Baht','currency.THB'),
+(138,'TJS',2,NULL,'Tajik Somoni','currency.TJS'),
+(139,'TMM',2,NULL,'Turkmenistan Manat','currency.TMM'),
+(140,'TND',3,'DT','Tunisian Dinar','currency.TND'),
+(141,'TOP',2,NULL,'Tonga Pa\'anga','currency.TOP'),
+(142,'TRY',2,NULL,'Turkish Lira','currency.TRY'),
+(143,'TTD',2,NULL,'Trinidad and Tobago Dollar','currency.TTD'),
+(144,'TWD',2,NULL,'New Taiwan Dollar','currency.TWD'),
+(145,'TZS',2,NULL,'Tanzanian Shilling','currency.TZS'),
+(146,'UAH',2,NULL,'Ukraine Hryvnia','currency.UAH'),
+(147,'UGX',2,'USh','Uganda Shilling','currency.UGX'),
+(148,'USD',2,'$','US Dollar','currency.USD'),
+(149,'UYU',2,'$U','Peso Uruguayo','currency.UYU'),
+(150,'UZS',2,NULL,'Uzbekistan Sum','currency.UZS'),
+(151,'VEB',2,'Bs.F.','Venezuelan Bolivar','currency.VEB'),
+(152,'VND',2,NULL,'Vietnamese Dong','currency.VND'),
+(153,'VUV',0,NULL,'Vanuatu Vatu','currency.VUV'),
+(154,'WST',2,NULL,'Samoa Tala','currency.WST'),
+(155,'XAF',0,NULL,'CFA Franc BEAC','currency.XAF'),
+(156,'XCD',2,NULL,'East Caribbean Dollar','currency.XCD'),
+(157,'XDR',5,NULL,'SDR (Special Drawing Rights)','currency.XDR'),
+(158,'XOF',0, 'CFA','CFA Franc BCEAO','currency.XOF'),
+(159,'XPF',0,NULL,'CFP Franc','currency.XPF'),
+(160,'YER',2,NULL,'Yemeni Rial','currency.YER'),
+(161,'ZAR',2, 'R','South African Rand','currency.ZAR'),
+(162,'ZMK',2,NULL,'Zambian Kwacha','currency.ZMK'),
+(163,'ZWD',2,NULL,'Zimbabwe Dollar','currency.ZWD');
+-- ======== end of currencies ==
+
+INSERT INTO `m_organisation_currency` (`id`, `code`, `decimal_places`, `name`, `display_symbol`, `internationalized_name_code`)
+VALUES (21,'USD',2,'US Dollar','$','currency.USD');
+
+INSERT INTO `m_office` (`id`, `parent_id`, `hierarchy`, `external_id`, `name`, `opening_date`)
+VALUES
+(1,NULL,'.','1','Head Office','2009-01-01');
+
+INSERT INTO `m_group_level` (`id`, `parent_id`, `super_parent`, `level_name`, `recursable`, `can_have_clients`)
+VALUES (1, NULL, 1, 'Center', 1, 0);
+INSERT INTO `m_group_level` (`id`, `parent_id`, `super_parent`, `level_name`, `recursable`, `can_have_clients`)
+VALUES (2, 1, 0, 'Group', 0, 1);
+
+
+
+-- create single code and code value for client identifiers
+INSERT INTO `m_code`
+(`code_name`, `is_system_defined`)
+VALUES
+('Customer Identifier',1),
+('LoanCollateral',1),
+('LoanPurpose',1),
+('Gender',1),
+('YesNo',1),
+('GuarantorRelationship',1);
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Passport', 1
+from m_code mc
+where mc.`code_name` = "Customer Identifier";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Id', 2
+from m_code mc
+where mc.`code_name` = "Customer Identifier";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Drivers License', 3
+from m_code mc
+where mc.`code_name` = "Customer Identifier";
+
+INSERT INTO `m_code_value`(`code_id`,`code_value`,`order_position`)
+select mc.id, 'Any Other Id Type', 4
+from m_code mc
+where mc.`code_name` = "Customer Identifier";
+
+-- Adding a few Default Guarantor Relationships
+insert into m_code_value (code_id,code_value,order_position)
+	select id,"Spouse",0
+	from m_code
+	where m_code.code_name="GuarantorRelationship";
+
+insert into m_code_value (code_id,code_value,order_position)
+	select id,"Parent",0
+	from m_code
+	where m_code.code_name="GuarantorRelationship";
+
+insert into m_code_value (code_id,code_value,order_position)
+	select id,"Sibling",0
+	from m_code
+	where m_code.code_name="GuarantorRelationship";
+
+insert into m_code_value (code_id,code_value,order_position)
+	select id,"Business Associate",0
+	from m_code
+	where m_code.code_name="GuarantorRelationship";
+
+insert into m_code_value (code_id,code_value,order_position)
+	select id,"Other",0
+	from m_code
+	where m_code.code_name="GuarantorRelationship";
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V30__add-referenceNumber-to-acc_gl_journal_entry.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V30__add-referenceNumber-to-acc_gl_journal_entry.sql
new file mode 100644
index 0000000..2e77fda
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V30__add-referenceNumber-to-acc_gl_journal_entry.sql
@@ -0,0 +1,2 @@
+alter table `acc_gl_journal_entry`
+add column `ref_num` varchar(100) default NULL AFTER `reversed`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V31__drop-autopostings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V31__drop-autopostings.sql
new file mode 100755
index 0000000..f2e3618
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V31__drop-autopostings.sql
@@ -0,0 +1 @@
+drop table `acc_auto_posting`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V32__associate-disassociate-clients-from-group-permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V32__associate-disassociate-clients-from-group-permissions.sql
new file mode 100644
index 0000000..700eb36
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V32__associate-disassociate-clients-from-group-permissions.sql
@@ -0,0 +1,4 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ASSOCIATECLIENTS_GROUP', 'GROUP', 'ASSOCIATECLIENTS', 0);
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DISASSOCIATECLIENTS_GROUP', 'GROUP', 'DISASSOCIATECLIENTS', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V33__drop_unique_check_on_stretchy_report_parameter.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V33__drop_unique_check_on_stretchy_report_parameter.sql
new file mode 100644
index 0000000..af31b43
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V33__drop_unique_check_on_stretchy_report_parameter.sql
@@ -0,0 +1,3 @@
+
+ALTER TABLE stretchy_report_parameter
+  DROP INDEX `report_id_name_UNIQUE` ;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V34__add_unique_check_on_stretchy_report_parameter.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V34__add_unique_check_on_stretchy_report_parameter.sql
new file mode 100644
index 0000000..eec244f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V34__add_unique_check_on_stretchy_report_parameter.sql
@@ -0,0 +1,2 @@
+ALTER TABLE stretchy_report_parameter
+  ADD UNIQUE INDEX `report_parameter_unique` (`report_id` ASC, `parameter_id` ASC) ;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V35__add_hierarchy_column_for_acc_gl_account.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V35__add_hierarchy_column_for_acc_gl_account.sql
new file mode 100644
index 0000000..f84fceb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V35__add_hierarchy_column_for_acc_gl_account.sql
@@ -0,0 +1 @@
+ALTER TABLE `acc_gl_account` ADD COLUMN `hierarchy` varchar(50) AFTER `parent_id`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V36__add_tag_id_column_for_acc_gl_account.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V36__add_tag_id_column_for_acc_gl_account.sql
new file mode 100644
index 0000000..9dadf2f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V36__add_tag_id_column_for_acc_gl_account.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `acc_gl_account`
+ADD COLUMN `tag_id` INT(11) NULL DEFAULT NULL AFTER `classification_enum`,
+ADD INDEX `FKGLACC000000002` (`tag_id`),
+ADD CONSTRAINT `FKGLACC000000002` FOREIGN KEY (`tag_id`) REFERENCES `m_code_value` (`id`);
+INSERT into m_code(`code_name`,`is_system_defined`) values ('AssetAccountTags',1),('LiabilityAccountTags',1),('EquityAccountTags',1),('IncomeAccountTags',1),('ExpenseAccountTags',1);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V37__add-center-group-collection-sheet-permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V37__add-center-group-collection-sheet-permissions.sql
new file mode 100644
index 0000000..141f1fc
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V37__add-center-group-collection-sheet-permissions.sql
@@ -0,0 +1,3 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'SAVECOLLECTIONSHEET_GROUP', 'GROUP', 'SAVECOLLECTIONSHEET', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'SAVECOLLECTIONSHEET_CENTER', 'CENTER', 'SAVECOLLECTIONSHEET', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V38__add-group-summary-details-report.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V38__add-group-summary-details-report.sql
new file mode 100644
index 0000000..467c197
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V38__add-group-summary-details-report.sql
@@ -0,0 +1,70 @@
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('GroupSummaryDetails', 'Table', true, false, 'Utility query for getting group summary details for a group_id',
+
+"/*
+Active Client is a client linked to the 'group' via m_group_client
+and with an active status_enum (not sure what it means if the group is inactive)
+
+Active Borrowers - Borrow may be a client or a 'group'
+
+Currency is an outstanding issue
+
+Couldn't set up group savings for a client
+(as opposed to opening a individual clients savings account)
+*/
+select x.*
+from m_office o,
+m_group g,
+
+(select a.activeClients,
+(b.activeClientLoans + c.activeGroupLoans) as activeLoans,
+b.activeClientLoans, c.activeGroupLoans,
+(b.activeClientBorrowers + c.activeGroupBorrowers) as activeBorrowers,
+b.activeClientBorrowers, c.activeGroupBorrowers, d.*,
+0 as activeSavings, 0 as totalSavingsAmount
+from
+(select count(*) as activeClients
+from m_group topgroup
+join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%')
+join m_group_client gc on gc.group_id = g.id
+join m_client c on c.id = gc.client_id
+where topgroup.id = ${groupId}
+and c.status_enum = 300) a,
+
+(select count(*) as activeClientLoans,
+count(distinct(l.client_id)) as activeClientBorrowers
+from m_group topgroup
+join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%')
+join m_loan l on l.group_id = g.id and l.client_id is not null
+where topgroup.id = ${groupId}
+and l.loan_status_id = 300) b,
+
+(select count(*) as activeGroupLoans,
+count(distinct(l.group_id)) as activeGroupBorrowers
+from m_group topgroup
+join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%')
+join m_loan l on l.group_id = g.id and l.client_id is null
+where topgroup.id = ${groupId}
+and l.loan_status_id = 300) c,
+
+(select ifnull(sum(l.principal_disbursed_derived),0) as totalDisbursedAmount,
+ifnull(sum(l.principal_outstanding_derived),0) as totalLoanOutstandingAmount,
+count(laa.loan_id) as overdueLoans, ifnull(sum(laa.total_overdue_derived), 0) as totalLoanOverdueAmount
+from m_group topgroup
+join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%')
+join m_loan l on l.group_id = g.id
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+where topgroup.id = ${groupId}
+and l.disbursedon_date is not null) d) x
+
+where g.id = ${groupId}
+and o.id = g.office_id
+and o.hierarchy like concat('${currentUserHierarchy}', '%')");
+
+
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_GroupSummaryDetails', 'GroupSummaryDetails', 'READ', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V39__payment-channels-updates.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V39__payment-channels-updates.sql
new file mode 100755
index 0000000..5ca90f9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V39__payment-channels-updates.sql
@@ -0,0 +1,17 @@
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) VALUES
+('PaymentType',1);
+
+/*Update payment detail to add foreign key relationship to user defined Code Value*/
+update m_loan_transaction set payment_detail_id=null;
+ALTER TABLE `m_payment_detail`
+	ALTER `payment_type_enum` DROP DEFAULT;
+ALTER TABLE `m_payment_detail`
+	CHANGE COLUMN `payment_type_enum` `payment_type_cv_id` INT(11) NULL AFTER `id`;
+delete from m_payment_detail;
+ALTER TABLE `m_payment_detail`
+	ADD CONSTRAINT `FK_m_payment_detail_m_code_value` FOREIGN KEY (`payment_type_cv_id`) REFERENCES `m_code_value` (`id`);
+
+/*Map Different Payment Channels to payment Types*/
+ALTER TABLE `acc_product_mapping`
+	ADD COLUMN `payment_type` INT(11) NULL DEFAULT NULL AFTER `product_type`,
+	ADD CONSTRAINT `FK_acc_product_mapping_m_code_value` FOREIGN KEY (`payment_type`) REFERENCES `m_code_value` (`id`);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql
new file mode 100644
index 0000000..589056b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql
@@ -0,0 +1,334 @@
+
+-- ========= roles and permissions =========
+
+/*
+this scripts removes all current m_role_permission and m_permission entries
+and then inserts new m_permission entries and just one m_role_permission entry
+which gives the role (id 1 - super user) an ALL_FUNCTIONS permission
+
+If you had other roles set up with specific permissions you will have to set up their permissions again.
+*/
+
+-- truncate `m_role_permission`;
+-- truncate `m_permission`;
+-- truncate `x_registered_table`;
+
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) VALUES
+('special','ALL_FUNCTIONS',NULL,NULL,0),
+('special','ALL_FUNCTIONS_READ',NULL,NULL,0),
+('special', 'CHECKER_SUPER_USER', NULL, NULL, '0'),
+('special','REPORTING_SUPER_USER',NULL,NULL,0),
+('authorisation','READ_PERMISSION','PERMISSION','READ',0),
+('authorisation','PERMISSIONS_ROLE','ROLE','PERMISSIONS',1),
+('authorisation','CREATE_ROLE','ROLE','CREATE',1),
+('authorisation','CREATE_ROLE_CHECKER','ROLE','CREATE',0),
+('authorisation','READ_ROLE','ROLE','READ',0),
+('authorisation','UPDATE_ROLE','ROLE','UPDATE',1),
+('authorisation','UPDATE_ROLE_CHECKER','ROLE','UPDATE',0),
+('authorisation','DELETE_ROLE','ROLE','DELETE',1),
+('authorisation','DELETE_ROLE_CHECKER','ROLE','DELETE',0),
+('authorisation','CREATE_USER','USER','CREATE',1),
+('authorisation','CREATE_USER_CHECKER','USER','CREATE',0),
+('authorisation','READ_USER','USER','READ',0),
+('authorisation','UPDATE_USER','USER','UPDATE',1),
+('authorisation','UPDATE_USER_CHECKER','USER','UPDATE',0),
+('authorisation','DELETE_USER','USER','DELETE',1),
+('authorisation','DELETE_USER_CHECKER','USER','DELETE',0),
+('configuration','READ_CONFIGURATION','CONFIGURATION','READ',1),
+('configuration','UPDATE_CONFIGURATION','CONFIGURATION','UPDATE',1),
+('configuration','UPDATE_CONFIGURATION_CHECKER','CONFIGURATION','UPDATE',0),
+('configuration','READ_CODE','CODE','READ',0),
+('configuration','CREATE_CODE','CODE','CREATE',1),
+('configuration','CREATE_CODE_CHECKER','CODE','CREATE',0),
+('configuration','UPDATE_CODE','CODE','UPDATE',1),
+('configuration','UPDATE_CODE_CHECKER','CODE','UPDATE',0),
+('configuration','DELETE_CODE','CODE','DELETE',1),
+('configuration','DELETE_CODE_CHECKER','CODE','DELETE',0),
+('configuration', 'READ_CODEVALUE', 'CODEVALUE', 'READ', '0'),
+('configuration', 'CREATE_CODEVALUE', 'CODEVALUE', 'CREATE', '1'),
+('configuration', 'CREATE_CODEVALUE_CHECKER', 'CODEVALUE', 'CREATE', '0'),
+('configuration', 'UPDATE_CODEVALUE', 'CODEVALUE', 'UPDATE', '1'),
+('configuration', 'UPDATE_CODEVALUE_CHECKER', 'CODEVALUE', 'UPDATE', '0'),
+('configuration', 'DELETE_CODEVALUE', 'CODEVALUE', 'DELETE', '1'),
+('configuration', 'DELETE_CODEVALUE_CHECKER', 'CODEVALUE', 'DELETE', '0'),
+('configuration','READ_CURRENCY','CURRENCY','READ',0),
+('configuration','UPDATE_CURRENCY','CURRENCY','UPDATE',1),
+('configuration','UPDATE_CURRENCY_CHECKER','CURRENCY','UPDATE',0),
+('configuration', 'UPDATE_PERMISSION', 'PERMISSION', 'UPDATE', '1'),
+('configuration', 'UPDATE_PERMISSION_CHECKER', 'PERMISSION', 'UPDATE', '0'),
+('configuration', 'READ_DATATABLE', 'DATATABLE', 'READ', '0'),
+('configuration', 'REGISTER_DATATABLE', 'DATATABLE', 'REGISTER', '1'),
+('configuration', 'REGISTER_DATATABLE_CHECKER', 'DATATABLE', 'REGISTER', '0'),
+('configuration', 'DEREGISTER_DATATABLE', 'DATATABLE', 'DEREGISTER', '1'),
+('configuration', 'DEREGISTER_DATATABLE_CHECKER', 'DATATABLE', 'DEREGISTER', '0'),
+('configuration', 'READ_AUDIT', 'AUDIT', 'READ', '0'),
+('configuration', 'CREATE_CALENDAR', 'CALENDAR', 'CREATE', '0'),
+('configuration', 'READ_CALENDAR', 'CALENDAR', 'READ', '0'),
+('configuration', 'UPDATE_CALENDAR', 'CALENDAR', 'UPDATE', '0'),
+('configuration', 'DELETE_CALENDAR', 'CALENDAR', 'DELETE', '0'),
+('configuration', 'CREATE_CALENDAR_CHECKER', 'CALENDAR', 'CREATE', '0'),
+('configuration', 'UPDATE_CALENDAR_CHECKER', 'CALENDAR', 'UPDATE', '0'),
+('configuration', 'DELETE_CALENDAR_CHECKER', 'CALENDAR', 'DELETE', '0'),
+('organisation', 'READ_MAKERCHECKER', 'MAKERCHECKER', 'READ', '0'),
+('organisation', 'READ_CHARGE', 'CHARGE', 'READ', '0'),
+('organisation', 'CREATE_CHARGE', 'CHARGE', 'CREATE', '1'),
+('organisation', 'CREATE_CHARGE_CHECKER', 'CHARGE', 'CREATE', '0'),
+('organisation', 'UPDATE_CHARGE', 'CHARGE', 'UPDATE', '1'),
+('organisation', 'UPDATE_CHARGE_CHECKER', 'CHARGE', 'UPDATE', '0'),
+('organisation', 'DELETE_CHARGE', 'CHARGE', 'DELETE', '1'),
+('organisation', 'DELETE_CHARGE_CHECKER', 'CHARGE', 'DELETE', '0'),
+('organisation', 'READ_FUND', 'FUND', 'READ', '0'),
+('organisation', 'CREATE_FUND', 'FUND', 'CREATE', '1'),
+('organisation', 'CREATE_FUND_CHECKER', 'FUND', 'CREATE', '0'),
+('organisation', 'UPDATE_FUND', 'FUND', 'UPDATE', '1'),
+('organisation', 'UPDATE_FUND_CHECKER', 'FUND', 'UPDATE', '0'),
+('organisation', 'DELETE_FUND', 'FUND', 'DELETE', '1'),
+('organisation', 'DELETE_FUND_CHECKER', 'FUND', 'DELETE', '0'),
+('organisation', 'READ_LOANPRODUCT', 'LOANPRODUCT', 'READ', '0'),
+('organisation', 'CREATE_LOANPRODUCT', 'LOANPRODUCT', 'CREATE', '1'),
+('organisation', 'CREATE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'CREATE', '0'),
+('organisation', 'UPDATE_LOANPRODUCT', 'LOANPRODUCT', 'UPDATE', '1'),
+('organisation', 'UPDATE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'UPDATE', '0'),
+('organisation', 'DELETE_LOANPRODUCT', 'LOANPRODUCT', 'DELETE', '1'),
+('organisation', 'DELETE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'DELETE', '0'),
+('organisation', 'READ_OFFICE', 'OFFICE', 'READ', '0'),
+('organisation', 'CREATE_OFFICE', 'OFFICE', 'CREATE', '1'),
+('organisation', 'CREATE_OFFICE_CHECKER', 'OFFICE', 'CREATE', '0'),
+('organisation', 'UPDATE_OFFICE', 'OFFICE', 'UPDATE', '1'),
+('organisation', 'UPDATE_OFFICE_CHECKER', 'OFFICE', 'UPDATE', '0'),
+('organisation', 'READ_OFFICETRANSACTION', 'OFFICETRANSACTION', 'READ', '0'),
+('organisation', 'DELETE_OFFICE_CHECKER', 'OFFICE', 'DELETE', '0'),
+('organisation', 'CREATE_OFFICETRANSACTION', 'OFFICETRANSACTION', 'CREATE', '1'),
+('organisation', 'CREATE_OFFICETRANSACTION_CHECKER', 'OFFICETRANSACTION', 'CREATE', '0'),
+('organisation', 'DELETE_OFFICETRANSACTION', 'OFFICETRANSACTION', 'DELETE', 1),
+('organisation', 'DELETE_OFFICETRANSACTION_CHECKER', 'OFFICETRANSACTION', 'DELETE', 0),
+('organisation', 'READ_STAFF', 'STAFF', 'READ', '0'),
+('organisation', 'CREATE_STAFF', 'STAFF', 'CREATE', '1'),
+('organisation', 'CREATE_STAFF_CHECKER', 'STAFF', 'CREATE', '0'),
+('organisation', 'UPDATE_STAFF', 'STAFF', 'UPDATE', '1'),
+('organisation', 'UPDATE_STAFF_CHECKER', 'STAFF', 'UPDATE', '0'),
+('organisation', 'DELETE_STAFF', 'STAFF', 'DELETE', '1'),
+('organisation', 'DELETE_STAFF_CHECKER', 'STAFF', 'DELETE', '0'),
+('organisation', 'READ_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'READ', '0'),
+('organisation', 'CREATE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'CREATE', '1'),
+('organisation', 'CREATE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'CREATE', '0'),
+('organisation', 'UPDATE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'UPDATE', '1'),
+('organisation', 'UPDATE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'UPDATE', '0'),
+('organisation', 'DELETE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'DELETE', '1'),
+('organisation', 'DELETE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'DELETE', '0'),
+('portfolio', 'READ_LOAN', 'LOAN', 'READ', '0'),
+('portfolio', 'CREATE_LOAN', 'LOAN', 'CREATE', '1'),
+('portfolio', 'CREATE_LOAN_CHECKER', 'LOAN', 'CREATE', '0'),
+('portfolio', 'UPDATE_LOAN', 'LOAN', 'UPDATE', '1'),
+('portfolio', 'UPDATE_LOAN_CHECKER', 'LOAN', 'UPDATE', '0'),
+('portfolio', 'DELETE_LOAN', 'LOAN', 'DELETE', '1'),
+('portfolio', 'DELETE_LOAN_CHECKER', 'LOAN', 'DELETE', '0'),
+-- ('portfolio', 'CREATEHISTORIC_LOAN', 'LOAN', 'CREATEHISTORIC', '1'),
+-- ('portfolio', 'CREATEHISTORIC_LOAN_CHECKER', 'LOAN', 'CREATEHISTORIC', '0'),
+-- ('portfolio', 'UPDATEHISTORIC_LOAN', 'LOAN', 'UPDATEHISTORIC', '1'),
+-- ('portfolio', 'UPDATEHISTORIC_LOAN_CHECKER', 'LOAN', 'UPDATEHISTORIC', '0'),
+('portfolio', 'READ_CLIENT', 'CLIENT', 'READ', '0'),
+('portfolio', 'CREATE_CLIENT', 'CLIENT', 'CREATE', '1'),
+('portfolio', 'CREATE_CLIENT_CHECKER', 'CLIENT', 'CREATE', '0'),
+('portfolio', 'UPDATE_CLIENT', 'CLIENT', 'UPDATE', '1'),
+('portfolio', 'UPDATE_CLIENT_CHECKER', 'CLIENT', 'UPDATE', '0'),
+('portfolio', 'DELETE_CLIENT', 'CLIENT', 'DELETE', '1'),
+('portfolio', 'DELETE_CLIENT_CHECKER', 'CLIENT', 'DELETE', '0'),
+('portfolio', 'READ_CLIENTIMAGE', 'CLIENTIMAGE', 'READ', '0'),
+('portfolio', 'CREATE_CLIENTIMAGE', 'CLIENTIMAGE', 'CREATE', '1'),
+('portfolio', 'CREATE_CLIENTIMAGE_CHECKER', 'CLIENTIMAGE', 'CREATE', '0'),
+('portfolio', 'DELETE_CLIENTIMAGE', 'CLIENTIMAGE', 'DELETE', '1'),
+('portfolio', 'DELETE_CLIENTIMAGE_CHECKER', 'CLIENTIMAGE', 'DELETE', '0'),
+('portfolio', 'READ_CLIENTNOTE', 'CLIENTNOTE', 'READ', '0'),
+('portfolio', 'CREATE_CLIENTNOTE', 'CLIENTNOTE', 'CREATE', '1'),
+('portfolio', 'CREATE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_CLIENTNOTE', 'CLIENTNOTE', 'UPDATE', '1'),
+('portfolio', 'UPDATE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_CLIENTNOTE', 'CLIENTNOTE', 'DELETE', '1'),
+('portfolio', 'DELETE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'DELETE', '0'),
+('portfolio', 'READ_GROUPNOTE', 'GROUPNOTE', 'READ', '0'),
+('portfolio', 'CREATE_GROUPNOTE', 'GROUPNOTE', 'CREATE', '1'),
+('portfolio', 'UPDATE_GROUPNOTE', 'GROUPNOTE', 'UPDATE', '1'),
+('portfolio', 'DELETE_GROUPNOTE', 'GROUPNOTE', 'DELETE', '1'),
+('portfolio', 'CREATE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'DELETE', '0'),
+('portfolio', 'READ_LOANNOTE', 'LOANNOTE', 'READ', '0'),
+('portfolio', 'CREATE_LOANNOTE', 'LOANNOTE', 'CREATE', '1'),
+('portfolio', 'UPDATE_LOANNOTE', 'LOANNOTE', 'UPDATE', '1'),
+('portfolio', 'DELETE_LOANNOTE', 'LOANNOTE', 'DELETE', '1'),
+('portfolio', 'CREATE_LOANNOTE_CHECKER', 'LOANNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_LOANNOTE_CHECKER', 'LOANNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_LOANNOTE_CHECKER', 'LOANNOTE', 'DELETE', '0'),
+('portfolio', 'READ_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'READ', '0'),
+('portfolio', 'CREATE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'CREATE', '1'),
+('portfolio', 'UPDATE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'UPDATE', '1'),
+('portfolio', 'DELETE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'DELETE', '1'),
+('portfolio', 'CREATE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'DELETE', '0'),
+('portfolio', 'READ_SAVINGNOTE', 'SAVINGNOTE', 'READ', '0'),
+('portfolio', 'CREATE_SAVINGNOTE', 'SAVINGNOTE', 'CREATE', '1'),
+('portfolio', 'UPDATE_SAVINGNOTE', 'SAVINGNOTE', 'UPDATE', '1'),
+('portfolio', 'DELETE_SAVINGNOTE', 'SAVINGNOTE', 'DELETE', '1'),
+('portfolio', 'CREATE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'CREATE', '0'),
+('portfolio', 'UPDATE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'UPDATE', '0'),
+('portfolio', 'DELETE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'DELETE', '0'),
+('portfolio', 'READ_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'READ', '0'),
+('portfolio', 'CREATE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'CREATE', '1'),
+('portfolio', 'CREATE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'CREATE', '0'),
+('portfolio', 'UPDATE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'UPDATE', '1'),
+('portfolio', 'UPDATE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'UPDATE', '0'),
+('portfolio', 'DELETE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'DELETE', '1'),
+('portfolio', 'DELETE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'DELETE', '0'),
+('portfolio', 'READ_DOCUMENT', 'DOCUMENT', 'READ', '0'),
+('portfolio', 'CREATE_DOCUMENT', 'DOCUMENT', 'CREATE', '1'),
+('portfolio', 'CREATE_DOCUMENT_CHECKER', 'DOCUMENT', 'CREATE', '0'),
+('portfolio', 'UPDATE_DOCUMENT', 'DOCUMENT', 'UPDATE', '1'),
+('portfolio', 'UPDATE_DOCUMENT_CHECKER', 'DOCUMENT', 'UPDATE', '0'),
+('portfolio', 'DELETE_DOCUMENT', 'DOCUMENT', 'DELETE', '1'),
+('portfolio', 'DELETE_DOCUMENT_CHECKER', 'DOCUMENT', 'DELETE', '0'),
+('portfolio', 'READ_GROUP', 'GROUP', 'READ', '0'),
+('portfolio', 'CREATE_GROUP', 'GROUP', 'CREATE', '1'),
+('portfolio', 'CREATE_GROUP_CHECKER', 'GROUP', 'CREATE', '0'),
+('portfolio', 'UPDATE_GROUP', 'GROUP', 'UPDATE', '1'),
+('portfolio', 'UPDATE_GROUP_CHECKER', 'GROUP', 'UPDATE', '0'),
+('portfolio', 'DELETE_GROUP', 'GROUP', 'DELETE', '1'),
+('portfolio', 'DELETE_GROUP_CHECKER', 'GROUP', 'DELETE', '0'),
+('portfolio', 'UNASSIGNSTAFF_GROUP', 'GROUP', 'UNASSIGNSTAFF', 1),
+('portfolio', 'UNASSIGNSTAFF_GROUP_CHECKER', 'GROUP', 'UNASSIGNSTAFF', 0),
+('portfolio', 'CREATE_LOANCHARGE', 'LOANCHARGE', 'CREATE', '1'),
+('portfolio', 'CREATE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'CREATE', '0'),
+('portfolio', 'UPDATE_LOANCHARGE', 'LOANCHARGE', 'UPDATE', '1'),
+('portfolio', 'UPDATE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'UPDATE', '0'),
+('portfolio', 'DELETE_LOANCHARGE', 'LOANCHARGE', 'DELETE', '1'),
+('portfolio', 'DELETE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'DELETE', '0'),
+('portfolio', 'WAIVE_LOANCHARGE', 'LOANCHARGE', 'WAIVE', '1'),
+('portfolio', 'WAIVE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'WAIVE', '0'),
+('portfolio', 'READ_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'READ', '0'),
+('portfolio', 'CREATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CREATE', '1'),
+('portfolio', 'CREATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CREATE', '0'),
+('portfolio', 'UPDATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UPDATE', '1'),
+('portfolio', 'UPDATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UPDATE', '0'),
+('portfolio', 'DELETE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DELETE', '1'),
+('portfolio', 'DELETE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DELETE', '0'),
+('portfolio', 'READ_GUARANTOR', 'GUARANTOR', 'READ', 0),
+('portfolio', 'CREATE_GUARANTOR', 'GUARANTOR', 'CREATE', 1),
+('portfolio', 'CREATE_GUARANTOR_CHECKER', 'GUARANTOR', 'CREATE', 0),
+('portfolio', 'UPDATE_GUARANTOR', 'GUARANTOR', 'UPDATE', 1),
+('portfolio', 'UPDATE_GUARANTOR_CHECKER', 'GUARANTOR', 'UPDATE', 0),
+('portfolio', 'DELETE_GUARANTOR', 'GUARANTOR', 'DELETE', 1),
+('portfolio', 'DELETE_GUARANTOR_CHECKER', 'GUARANTOR', 'DELETE', 0),
+('portfolio', 'READ_COLLATERAL', 'COLLATERAL', 'READ', '0'),
+('portfolio', 'CREATE_COLLATERAL', 'COLLATERAL', 'CREATE', '1'),
+('portfolio', 'UPDATE_COLLATERAL', 'COLLATERAL', 'UPDATE', '1'),
+('portfolio', 'DELETE_COLLATERAL', 'COLLATERAL', 'DELETE', '1'),
+('portfolio', 'CREATE_COLLATERAL_CHECKER', 'COLLATERAL', 'CREATE', '0'),
+('portfolio', 'UPDATE_COLLATERAL_CHECKER', 'COLLATERAL', 'UPDATE', '0'),
+('portfolio', 'DELETE_COLLATERAL_CHECKER', 'COLLATERAL', 'DELETE', '0'),
+('transaction_loan', 'APPROVE_LOAN', 'LOAN', 'APPROVE', '1'),
+('transaction_loan', 'APPROVEINPAST_LOAN', 'LOAN', 'APPROVEINPAST', '1'),
+('transaction_loan', 'REJECT_LOAN', 'LOAN', 'REJECT', '1'),
+('transaction_loan', 'REJECTINPAST_LOAN', 'LOAN', 'REJECTINPAST', '1'),
+('transaction_loan', 'WITHDRAW_LOAN', 'LOAN', 'WITHDRAW', '1'),
+('transaction_loan', 'WITHDRAWINPAST_LOAN', 'LOAN', 'WITHDRAWINPAST', '1'),
+('transaction_loan', 'APPROVALUNDO_LOAN', 'LOAN', 'APPROVALUNDO', '1'),
+('transaction_loan', 'DISBURSE_LOAN', 'LOAN', 'DISBURSE', '1'),
+('transaction_loan', 'DISBURSEINPAST_LOAN', 'LOAN', 'DISBURSEINPAST', '1'),
+('transaction_loan', 'DISBURSALUNDO_LOAN', 'LOAN', 'DISBURSALUNDO', '1'),
+('transaction_loan', 'REPAYMENT_LOAN', 'LOAN', 'REPAYMENT', '1'),
+('transaction_loan', 'REPAYMENTINPAST_LOAN', 'LOAN', 'REPAYMENTINPAST', '1'),
+('transaction_loan', 'ADJUST_LOAN', 'LOAN', 'ADJUST', '1'),
+('transaction_loan', 'WAIVEINTERESTPORTION_LOAN', 'LOAN', 'WAIVEINTERESTPORTION', '1'),
+('transaction_loan', 'WRITEOFF_LOAN', 'LOAN', 'WRITEOFF', '1'),
+('transaction_loan', 'CLOSE_LOAN', 'LOAN', 'CLOSE', '1'),
+('transaction_loan', 'CLOSEASRESCHEDULED_LOAN', 'LOAN', 'CLOSEASRESCHEDULED', '1'),
+('transaction_loan', 'UPDATELOANOFFICER_LOAN', 'LOAN', 'UPDATELOANOFFICER', 1),
+('transaction_loan', 'UPDATELOANOFFICER_LOAN_CHECKER', 'LOAN', 'UPDATELOANOFFICER', 0),
+('transaction_loan', 'REMOVELOANOFFICER_LOAN', 'LOAN', 'REMOVELOANOFFICER', 1),
+('transaction_loan', 'REMOVELOANOFFICER_LOAN_CHECKER', 'LOAN', 'REMOVELOANOFFICER', 0),
+('transaction_loan', 'BULKREASSIGN_LOAN', 'LOAN', 'BULKREASSIGN', '1'),
+('transaction_loan', 'BULKREASSIGN_LOAN_CHECKER', 'LOAN', 'BULKREASSIGN', '0'),
+('transaction_loan', 'APPROVE_LOAN_CHECKER', 'LOAN', 'APPROVE', '0'),
+('transaction_loan', 'APPROVEINPAST_LOAN_CHECKER', 'LOAN', 'APPROVEINPAST', '0'),
+('transaction_loan', 'REJECT_LOAN_CHECKER', 'LOAN', 'REJECT', '0'),
+('transaction_loan', 'REJECTINPAST_LOAN_CHECKER', 'LOAN', 'REJECTINPAST', '0'),
+('transaction_loan', 'WITHDRAW_LOAN_CHECKER', 'LOAN', 'WITHDRAW', '0'),
+('transaction_loan', 'WITHDRAWINPAST_LOAN_CHECKER', 'LOAN', 'WITHDRAWINPAST', '0'),
+('transaction_loan', 'APPROVALUNDO_LOAN_CHECKER', 'LOAN', 'APPROVALUNDO', '0'),
+('transaction_loan', 'DISBURSE_LOAN_CHECKER', 'LOAN', 'DISBURSE', '0'),
+('transaction_loan', 'DISBURSEINPAST_LOAN_CHECKER', 'LOAN', 'DISBURSEINPAST', '0'),
+('transaction_loan', 'DISBURSALUNDO_LOAN_CHECKER', 'LOAN', 'DISBURSALUNDO', '0'),
+('transaction_loan', 'REPAYMENT_LOAN_CHECKER', 'LOAN', 'REPAYMENT', '0'),
+('transaction_loan', 'REPAYMENTINPAST_LOAN_CHECKER', 'LOAN', 'REPAYMENTINPAST', '0'),
+('transaction_loan', 'ADJUST_LOAN_CHECKER', 'LOAN', 'ADJUST', '0'),
+('transaction_loan', 'WAIVEINTERESTPORTION_LOAN_CHECKER', 'LOAN', 'WAIVEINTERESTPORTION', '0'),
+('transaction_loan', 'WRITEOFF_LOAN_CHECKER', 'LOAN', 'WRITEOFF', '0'),
+('transaction_loan', 'CLOSE_LOAN_CHECKER', 'LOAN', 'CLOSE', '0'),
+('transaction_loan', 'CLOSEASRESCHEDULED_LOAN_CHECKER', 'LOAN', 'CLOSEASRESCHEDULED', '0'),
+('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DEPOSIT', '1'),
+('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DEPOSIT', '0'),
+('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAWAL', '1'),
+('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAWAL', '0'),
+('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ACTIVATE', '1'),
+('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE', '0'),
+('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '1'),
+('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '0');
+
+-- == accounting related permissions
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
+('accounting', 'CREATE_GLACCOUNT', 'GLACCOUNT', 'CREATE', 1),
+('accounting', 'UPDATE_GLACCOUNT', 'GLACCOUNT', 'UPDATE', 1),
+('accounting', 'DELETE_GLACCOUNT', 'GLACCOUNT', 'DELETE', 1),
+('accounting', 'CREATE_GLCLOSURE', 'GLCLOSURE', 'CREATE', 1),
+('accounting', 'UPDATE_GLCLOSURE', 'GLCLOSURE', 'UPDATE', 1),
+('accounting', 'DELETE_GLCLOSURE', 'GLCLOSURE', 'DELETE', 1),
+('accounting', 'CREATE_JOURNALENTRY', 'JOURNALENTRY', 'CREATE', 1),
+('accounting', 'REVERSE_JOURNALENTRY', 'JOURNALENTRY', 'REVERSE', 1);
+
+
+INSERT INTO `m_role` (`id`, `name`, `description`)
+VALUES
+(1,'Super user','This role provides all application permissions.');
+
+/* role 1 is super user, give it ALL_FUNCTIONS */
+INSERT INTO m_role_permission(role_id, permission_id)
+select 1, id
+from m_permission
+where code = 'ALL_FUNCTIONS';
+
+INSERT INTO `m_appuser` (`id`, `office_id`, `username`, `firstname`, `lastname`, `password`, `email`,
+`firsttime_login_remaining`, `nonexpired`, `nonlocked`, `nonexpired_credentials`, `enabled`)
+VALUES
+(1,1,'mifos','App','Administrator','5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a','demomfi@mifos.org','\0','','','','');
+
+
+INSERT INTO `m_appuser_role` (`appuser_id`, `role_id`) VALUES (1,1);
+
+
+-- Add in permissions for any special datatables added in base reference data
+-- This needs to always happen at end of the script
+
+/* add a create, read, update and delete permission for each registered datatable */
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('CREATE_', r.registered_table_name), r.registered_table_name, 'CREATE'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('READ_', r.registered_table_name), r.registered_table_name, 'READ'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('UPDATE_', r.registered_table_name), r.registered_table_name, 'UPDATE'
+from x_registered_table r;
+
+insert into m_permission(grouping, `code`, entity_name, action_name)
+select 'datatable', concat('DELETE_', r.registered_table_name), r.registered_table_name, 'DELETE'
+from x_registered_table r;
+
+
+/* regardless of inserted permission settings above, no permissions (transactions) are preselected as being part of the maker-checker process
+so, just set the flag to false... the end-user can decide which permissions should be maker-checkerable
+*/
+update m_permission set can_maker_checker = false;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V40__add_permissions_for_accounting_rule.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V40__add_permissions_for_accounting_rule.sql
new file mode 100644
index 0000000..9959cfd
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V40__add_permissions_for_accounting_rule.sql
@@ -0,0 +1 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organistion', 'DELETE_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'DELETE', 0),('organistion', 'CREATE_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'CREATE', 0),('organistion', 'UPDATE_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'UPDATE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V41__group-summary-reports.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V41__group-summary-reports.sql
new file mode 100644
index 0000000..91c17af
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V41__group-summary-reports.sql
@@ -0,0 +1,89 @@
+
+DELETE FROM `stretchy_report` where report_name = 'GroupSummaryDetails';
+DELETE FROM `m_permission` where entity_name = 'GroupSummaryDetails';
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('GroupSummaryCounts', 'Table', true, false, 'Utility query for getting group summary count details for a group_id',
+
+"
+/*
+Active Client is a client linked to the 'group' via m_group_client
+and with an active 'status_enum'.)
+Active Borrowers - Borrower may be a client or a 'group'
+*/
+select x.*
+from m_office o,
+m_group g,
+
+(select a.activeClients,
+(b.activeClientLoans + c.activeGroupLoans) as activeLoans,
+b.activeClientLoans, c.activeGroupLoans,
+(b.activeClientBorrowers + c.activeGroupBorrowers) as activeBorrowers,
+b.activeClientBorrowers, c.activeGroupBorrowers,
+(b.overdueClientLoans +  c.overdueGroupLoans) as overdueLoans,
+b.overdueClientLoans, c.overdueGroupLoans
+from
+(select count(*) as activeClients
+from m_group topgroup
+join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%')
+join m_group_client gc on gc.group_id = g.id
+join m_client c on c.id = gc.client_id
+where topgroup.id = ${groupId}
+and c.status_enum = 300) a,
+
+(select count(*) as activeClientLoans,
+count(distinct(l.client_id)) as activeClientBorrowers,
+ifnull(sum(if(laa.loan_id is not null, 1, 0)), 0) as overdueClientLoans
+from m_group topgroup
+join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%')
+join m_loan l on l.group_id = g.id and l.client_id is not null
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+where topgroup.id = ${groupId}
+and l.loan_status_id = 300) b,
+
+(select count(*) as activeGroupLoans,
+count(distinct(l.group_id)) as activeGroupBorrowers,
+ifnull(sum(if(laa.loan_id is not null, 1, 0)), 0) as overdueGroupLoans
+from m_group topgroup
+join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%')
+join m_loan l on l.group_id = g.id and l.client_id is null
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+where topgroup.id = ${groupId}
+and l.loan_status_id = 300) c
+) x
+
+where g.id = ${groupId}
+and o.id = g.office_id
+and o.hierarchy like concat('${currentUserHierarchy}', '%')
+");
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_GroupSummaryCounts', 'GroupSummaryCounts', 'READ', 0);
+
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('GroupSummaryAmounts', 'Table', true, false, 'Utility query for getting group summary currency amount details for a group_id',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as currency,
+ifnull(sum(l.principal_disbursed_derived),0) as totalDisbursedAmount,
+ifnull(sum(l.principal_outstanding_derived),0) as totalLoanOutstandingAmount,
+count(laa.loan_id) as overdueLoans, ifnull(sum(laa.total_overdue_derived), 0) as totalLoanOverdueAmount
+from m_group topgroup
+join m_office o on o.id = topgroup.office_id and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%')
+join m_loan l on l.group_id = g.id
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+left join m_currency cur on cur.code = l.currency_code
+where topgroup.id = ${groupId}
+and l.disbursedon_date is not null
+group by l.currency_code
+");
+
+
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_GroupSummaryAmounts', 'GroupSummaryAmounts', 'READ', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V42__Add_default_value_for_id_for_acc_accounting_rule.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V42__Add_default_value_for_id_for_acc_accounting_rule.sql
new file mode 100644
index 0000000..d0d9d87
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V42__Add_default_value_for_id_for_acc_accounting_rule.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `acc_accounting_rule`
+	CHANGE COLUMN `id` `id` BIGINT(20) NOT NULL AUTO_INCREMENT FIRST;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V43__accounting-for-savings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V43__accounting-for-savings.sql
new file mode 100755
index 0000000..cf2e1ce
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V43__accounting-for-savings.sql
@@ -0,0 +1,11 @@
+/*add accounting type field to savings product*/
+ALTER TABLE `m_savings_product`
+	ADD COLUMN `accounting_type` SMALLINT(5) NOT NULL AFTER `lockin_period_frequency_enum`;
+
+/*update existing savings products to have "No" accounting*/
+update m_savings_product set accounting_type=1;
+
+/*track payment details for savings transactions*/
+ALTER TABLE `m_savings_account_transaction`
+ADD COLUMN `payment_detail_id` BIGINT(20) NULL DEFAULT NULL AFTER `savings_account_id`,
+ADD CONSTRAINT `FK_m_savings_account_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V44__document-increase-size-of-column-type.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V44__document-increase-size-of-column-type.sql
new file mode 100755
index 0000000..c09286c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V44__document-increase-size-of-column-type.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_document`
+	CHANGE COLUMN `type` `type` VARCHAR(500) NULL DEFAULT NULL AFTER `size`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V45__create_acc_rule_tags_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V45__create_acc_rule_tags_table.sql
new file mode 100644
index 0000000..b17bc2e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V45__create_acc_rule_tags_table.sql
@@ -0,0 +1,11 @@
+create table `acc_rule_tags` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`acc_rule_id` BIGINT(20) NOT NULL,
+	`tag_id` INT(11) NOT NULL,
+	`acc_type_enum` SMALLINT(5) NOT NULL,
+	primary key(`id`),
+	INDEX `FK_acc_accounting_rule_id` (`acc_rule_id`),
+	INDEX `FK_m_code_value_id` (`tag_id`),
+	CONSTRAINT `FK_acc_accounting_rule_id` FOREIGN KEY (`acc_rule_id`) REFERENCES `acc_accounting_rule` (`id`),
+	CONSTRAINT `FK_m_code_value_id` FOREIGN KEY (`tag_id`) REFERENCES `m_code_value` (`id`)
+);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V46__extend_datatables_api.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V46__extend_datatables_api.sql
new file mode 100644
index 0000000..8a90512
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V46__extend_datatables_api.sql
@@ -0,0 +1,9 @@
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+    VALUES
+        ('configuration', 'CREATE_DATATABLE', 'DATATABLE', 'CREATE', 0),
+        ('configuration', 'CREATE_DATATABLE_CHECKER', 'DATATABLE', 'CREATE', 0),
+        ('configuration', 'UPDATE_DATATABLE', 'DATATABLE', 'UPDATE', 0),
+        ('configuration', 'UPDATE_DATATABLE_CHECKER', 'DATATABLE', 'UPDATE', 0),
+        ('configuration', 'DELETE_DATATABLE', 'DATATABLE', 'DELETE', 0),
+        ('configuration', 'DELETE_DATATABLE_CHECKER', 'DATATABLE', 'DELETE', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V47__staff-hierarchy-link-to-users.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V47__staff-hierarchy-link-to-users.sql
new file mode 100644
index 0000000..9a220bd
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V47__staff-hierarchy-link-to-users.sql
@@ -0,0 +1,14 @@
+ALTER TABLE `m_appuser`
+ADD COLUMN `staff_id` BIGINT(20) NULL DEFAULT NULL AFTER `office_id` ;
+
+ALTER TABLE m_appuser
+ADD CONSTRAINT `fk_m_appuser_002`
+FOREIGN KEY (`staff_id`)
+REFERENCES m_staff (`id`)
+ON DELETE NO ACTION
+ON UPDATE NO ACTION
+,ADD INDEX `fk_m_appuser_002x` (`staff_id` ASC);
+
+ALTER TABLE `m_staff`
+ADD COLUMN `organisational_role_enum` SMALLINT NULL DEFAULT NULL AFTER `external_id`,
+ADD COLUMN `organisational_role_parent_staff_id` BIGINT(20) NULL DEFAULT NULL AFTER `organisational_role_enum`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V48__adding-S3-Support.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V48__adding-S3-Support.sql
new file mode 100755
index 0000000..5df104d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V48__adding-S3-Support.sql
@@ -0,0 +1,77 @@
+INSERT INTO c_configuration (name,enabled) VALUES ('amazon-S3',0);
+
+/*New table for storing details of external services used*/
+CREATE TABLE `c_external_service` (
+	`name` VARCHAR(150) NOT NULL,
+	`value` VARCHAR(250) NULL DEFAULT NULL,
+	UNIQUE INDEX `name` (`name`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+INSERT INTO c_external_service (name) VALUES ('s3_bucket_name');
+INSERT INTO c_external_service (name) VALUES ('s3_access_key');
+INSERT INTO c_external_service (name) VALUES ('s3_secret_key');
+
+
+
+/*Image tables stores details of all images*/
+CREATE TABLE IF NOT EXISTS `m_image`(
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`location` varchar(500),
+	`storage_type_enum` SMALLINT(5),
+	PRIMARY KEY (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+/*Client points to image table*/
+ALTER TABLE `m_client`
+	ADD COLUMN `image_id` BIGINT(20) NULL DEFAULT NULL AFTER `display_name`,
+	ADD CONSTRAINT `FK_m_client_m_image` FOREIGN KEY (`image_id`) REFERENCES `m_image` (`id`);
+
+/*Move existing image locations to new table*/
+drop procedure if exists migrate_customer_image_data;
+
+delimiter #
+create procedure migrate_customer_image_data()
+begin
+DECLARE v_counter INT DEFAULT 0;
+DECLARE num_of_clients INT DEFAULT 0;
+DECLARE curr_image INT DEFAULT 0;
+DECLARE prev_image INT DEFAULT 0;
+SELECT COUNT(*) FROM m_client INTO num_of_clients;
+SET @curr_client := 0;
+while v_counter < num_of_clients do
+	SET @s = CONCAT('INSERT INTO m_image(`location`,`storage_type_enum`) select image_key, 1 FROM m_client where image_key is not null LIMIT  ', v_counter , ', ', 1);
+	PREPARE stmt1 FROM @s;
+	EXECUTE stmt1;
+	DEALLOCATE PREPARE stmt1;
+
+	select ifnull(max(`id`),0) from m_image INTO curr_image;
+	SET @z = CONCAT('select id INTO @curr_client FROM m_client where image_key is not null limit  ', v_counter , ', ', 1);
+	PREPARE stmt2 FROM @z;
+	EXECUTE stmt2;
+
+	IF (prev_image != curr_image) THEN UPDATE m_client set image_id = curr_image where id=@curr_client;
+	END IF;
+
+	set prev_image=curr_image;
+	set v_counter=v_counter+1;
+end while;
+delete from m_image where `location` is null;
+end#
+
+delimiter ;
+call migrate_customer_image_data();
+
+/*Now drop the procedure*/
+drop procedure if exists migrate_customer_image_data;
+
+/*Delete image key from client*/
+ALTER TABLE `m_client` DROP COLUMN `image_key`;
+
+
+/*Add storage type for m_document table and update existing documents to file storage*/
+ALTER TABLE m_document ADD COLUMN storage_type_enum SMALLINT(5);
+UPDATE m_document set storage_type_enum=1;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V49__track-loan-charge-payment-transactions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V49__track-loan-charge-payment-transactions.sql
new file mode 100755
index 0000000..3e6a983
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V49__track-loan-charge-payment-transactions.sql
@@ -0,0 +1,13 @@
+CREATE TABLE `m_loan_charge_paid_by` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`loan_transaction_id` BIGINT(20) NOT NULL,
+	`loan_charge_id` BIGINT(20) NOT NULL,
+	`amount` DECIMAL(19,6) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK__m_loan_transaction` (`loan_transaction_id`),
+	INDEX `FK__m_loan_charge` (`loan_charge_id`),
+	CONSTRAINT `FK__m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+	CONSTRAINT `FK__m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V4__mifosx-core-reports-utf8.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V4__mifosx-core-reports-utf8.sql
new file mode 100644
index 0000000..9995005
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V4__mifosx-core-reports-utf8.sql
@@ -0,0 +1,11 @@
+truncate table stretchy_report;
+truncate table stretchy_parameter;
+truncate table stretchy_report_parameter;
+
+INSERT INTO `stretchy_report` VALUES (1,'Client Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n c.account_no as \"Client Account No.\",  \r\nc.display_name as \"Name\",  \n\nc.joined_date as \"Joined\", c.external_id as \"External Id\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand \n\nounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\nwhere o.id = ${officeId}\r\nand c.is_deleted=0\r\n\n\norder by ounder.hierarchy, c.account_no','Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t \n\nhave that client browser/memory impact).',1,1),(2,'Client Loans Listing','Table',NULL,'Client','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client \n\nAccount No.\", \r\nc.display_name as \"Name\", \r\nlo.display_name as \"Loan Officer\", l.account_no as \"Loan Account No.\", l.external_id as \"External Id\", \r\n\n\np.name as Loan, st.enum_message_property as \"Status\",  \r\nf.`name` as Fund, purp.code_value as \"Loan Purpose\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount,\n\n\r\nl.arrearstolerance_amount as \"Arrears Tolerance Amount\",\r\nl.number_of_repayments as \"Expected No. Repayments\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\nl.nominal_interest_rate_per_period as \"Nominal Interest Rate Per Period\",\r\n\r\nipf.enum_message_property as \"Interest Rate Frequency\n\n\",\r\nim.enum_message_property as \"Interest Method\",\r\nicp.enum_message_property as \"Interest Calculated in Period\",\r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.repay_every as \"Repayment Frequency\",\r\nrf.enum_message_property as \"Repayment Frequency Period\",\n\n\r\nam.enum_message_property as \"Amortization\",\r\n\r\nl.total_charges_due_at_disbursement_derived as \"Total Charges Due At Disbursement\",\r\n\r\ndate( \n\nl.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As \"Expected Disbursal\",\r\ndate(l.expected_firstrepaymenton_date) as \n\n\"Expected First Repayment\", date(l.interest_calculated_from_date) as \"Interest Calculated From\" ,\r\ndate(l.disbursedon_date) as Disbursed, date\n\n(l.expected_maturedon_date) \"Expected Maturity\",\r\ndate(l.maturedon_date) as \"Matured On\", date(l.closedon_date) as Closed,\r\ndate(l.rejectedon_date) as \n\nRejected, date(l.rescheduledon_date) as Rescheduled, \r\ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) \"Written Off\"\r\nfrom m_office o \r\njoin \n\nm_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on \n\nc.office_id = ounder.id\r\nleft join m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_product_loan p on p.id = \n\nl.product_id\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join r_enum_value st on st.enum_name = \"loan_status_id\" and st.enum_id = l.loan_status_id\r\nleft join \n\nr_enum_value ipf on ipf.enum_name = \"interest_period_frequency_enum\" and ipf.enum_id = l.interest_period_frequency_enum\r\nleft join r_enum_value im on im.enum_name \n\n= \"interest_method_enum\" and im.enum_id = l.interest_method_enum\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = \n\nl.term_period_frequency_enum\r\nleft join r_enum_value icp on icp.enum_name = \"interest_calculated_in_period_enum\" and icp.enum_id = \n\nl.interest_calculated_in_period_enum\r\nleft join r_enum_value rf on rf.enum_name = \"repayment_period_frequency_enum\" and rf.enum_id = \n\nl.repayment_period_frequency_enum\r\nleft join r_enum_value am on am.enum_name = \"amortization_method_enum\" and am.enum_id = l.amortization_method_enum\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\n\r\nleft \n\njoin m_currency cur on cur.code = l.currency_code\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand \n\n(l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \n\n\"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\norder by ounder.hierarchy, 2 , l.id','Individual Client Report\r\n\r\nPretty \n\nwide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).',1,1),(5,'Loans Awaiting Disbursal','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No\", c.display_name as \"Name\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund, ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\",\r\ndate(l.approvedon_date) \"Approved\",\r\ndatediff(l.expected_disbursedon_date, curdate()) as \"Days to Disbursal\",\r\ndate(l.expected_disbursedon_date) \"Expected Disbursal\",\r\npurp.code_value as \"Loan Purpose\",\r\n lo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, datediff(l.expected_disbursedon_date, curdate()),  c.account_no','Individual Client Report',1,1),(6,'Loans Awaiting Disbursal Summary','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`','Individual Client Report',1,1),(7,'Loans Awaiting Disbursal Summary by Month','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\npl.`name` as \"Product\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as \"Year\", \r\nmonthname(l.expected_disbursedon_date) as \"Month\",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)','Individual Client Report',1,1),(8,'Loans Pending Approval','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client Name\", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  pl.`name` as \"Product\", \r\nl.account_no as \"Loan Account No.\", \r\nl.principal_amount as \"Loan Amount\", \r\nl.term_frequency as \"Term Frequency\",\n\n\r\ntf.enum_message_property as \"Term Frequency Period\",\r\nl.annual_nominal_interest_rate as \" Annual \n\nNominal Interest Rate\", \r\ndatediff(curdate(), l.submittedon_date) \"Days Pending Approval\", \r\npurp.code_value as \"Loan Purpose\",\r\nlo.display_name as \"Loan Officer\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = \"term_period_frequency_enum\" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 100 /*Submitted and awaiting approval */\r\norder by ounder.hierarchy, l.submittedon_date,  l.account_no','Individual Client Report',1,1),(11,'Active Loans - Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. loans_in_arrears_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(if(laa.loan_id is not null,  l.id, null)  )) as loans_in_arrears_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(laa.principal_overdue_derived) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(laa.interest_overdue_derived) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(laa.fee_charges_overdue_derived) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(laa.penalty_charges_overdue_derived) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(12,'Active Loans - Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\npenalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(13,'Obligation Met Loans Details','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No.\", c.display_name as \"Client\",\r\nl.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.total_repayment_derived  as \"Total Repaid\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", \r\ndate(l.closedon_date) as \"Closed\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(14,'Obligation Met Loans Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as \"No. of Clients\",\r\ncount(distinct(l.id)) as \"No. of Loans\",\r\nsum(l.principal_amount) as \"Total Loan Amount\", \r\nsum(l.principal_repaid_derived) as \"Total Principal Repaid\",\r\nsum(l.interest_repaid_derived) as \"Total Interest Repaid\",\r\nsum(l.fee_charges_repaid_derived) as \"Total Fees Repaid\",\r\nsum(l.penalty_charges_repaid_derived) as \"Total Penalties Repaid\",\r\nsum(l.interest_waived_derived) as \"Total Interest Waived\",\r\nsum(l.fee_charges_waived_derived) as \"Total Fees Waived\",\r\nsum(l.penalty_charges_waived_derived) as \"Total Penalties Waived\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n	when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n	when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n	else 1 = 1\r\n	end)\r\nand l.loan_status_id = 600\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code','Individual Client \n\nReport',1,1),(15,'Portfolio at Risk','Table',NULL,'Loan','select x.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from \r\n(select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code) x','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(16,'Portfolio at Risk by Branch','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\",\r\nx.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select  ounder.id as \"branch\", ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\n\r\nsum(l.principal_outstanding_derived) as \"Principal Outstanding\",\r\nsum(laa.principal_overdue_derived) as \"Principal Overdue\",\r\n\r\nsum(l.interest_outstanding_derived) as \"Interest Outstanding\",\r\nsum(laa.interest_overdue_derived) as \"Interest Overdue\",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as \"Fees Outstanding\",\r\nsum(laa.fee_charges_overdue_derived) as \"Fees Overdue\",\r\n\r\nsum(penalty_charges_outstanding_derived) as \"Penalties Outstanding\",\r\nsum(laa.penalty_charges_overdue_derived) as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency','Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)',1,1),(20,'Funds Disbursed Between Dates Summary','Table',NULL,'Fund','select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, \r\nround(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(21,'Funds Disbursed Between Dates Summary by Office','Table',NULL,'Fund','select \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\n \n\nifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\n\n\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c \n\non c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\n\n\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand \n\n(l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, \n\nl.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)',NULL,1,1),(48,'Balance Sheet','Pentaho',NULL,'Accounting',NULL,'Balance Sheet',1,1),(49,'Income Statement','Pentaho',NULL,'Accounting',NULL,'Profit and Loss Statement',1,1),(50,'Trial Balance','Pentaho',NULL,'Accounting',NULL,'Trial Balance Report',1,1),(51,'Written-Off Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Written-Off Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Written-Off Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Written-Off Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Written-Off Penalties\',\r\nn.note AS \'Reason For Write-Off\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 6 /*write-off */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=601\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\") \r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Written Off Loans',1,1),(52,'Aging Detail','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as \"Client Account No.\",\r\n 	mc.display_name AS \"Client Name\",\r\n 	ml.account_no AS \"Account Number\",\r\n 	ml.principal_amount AS \"Loan Amount\",\r\n ml.principal_disbursed_derived AS \"Original Principal\",\r\n ml.interest_charged_derived AS \"Original Interest\",\r\n ml.principal_repaid_derived AS \"Principal Paid\",\r\n ml.interest_repaid_derived AS \"Interest Paid\",\r\n laa.principal_overdue_derived AS \"Principal Overdue\",\r\n laa.interest_overdue_derived AS \"Interest Overdue\",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as \"Days in Arrears\",\r\n\r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n 	IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS \"Weeks In Arrears Band\",\r\n\r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n				 \'> 360\'))))) AS \"Days in Arrears Band\"\r\n\r\n	FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n	        AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n	    INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n	    INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n	WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Loan arrears aging (Weeks)',1,1),(53,'Aging Summary (Arrears in Weeks)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Weeks In Arrears (Up To)\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'1\',2 UNION\r\n		SELECT \'2\',3 UNION\r\n		SELECT \'3\',4 UNION\r\n		SELECT \'4\',5 UNION\r\n		SELECT \'5\',6 UNION\r\n		SELECT \'6\',7 UNION\r\n		SELECT \'7\',8 UNION\r\n		SELECT \'8\',9 UNION\r\n		SELECT \'9\',10 UNION\r\n		SELECT \'10\',11 UNION\r\n		SELECT \'11\',12 UNION\r\n		SELECT \'12\',13 UNION\r\n		SELECT \'12+\',14) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<8, \'1\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<15, \'2\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<22, \'3\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<29, \'4\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<36, \'5\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<43, \'6\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<50, \'7\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<57, \'8\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<64, \'9\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<71, \'10\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<78, \'11\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<85, \'12\',\r\n				 \'12+\'))))))))))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(54,'Rescheduled Loans','Table',NULL,'Loan','SELECT \r\nconcat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as \"Client Account No.\",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as \"Loan Amount\",\r\nifnull(lt.principal_portion_derived, 0) AS \'Rescheduled Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Rescheduled Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Rescheduled Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Rescheduled Penalties\',\r\nn.note AS \'Reason For Rescheduling\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 7 /*marked for rescheduling */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=602\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no','Individual Lending Report. Rescheduled Loans.  The ability to reschedule (or mark that you have rescheduled the loan elsewhere) is a legacy of the older Mifos product.  Needed for migration.',1,1),(55,'Active Loans Passed Final Maturity','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", \r\nc.display_name as \"Client\", l.account_no as \"Loan Account No.\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\ndate(l.expected_maturedon_date) as \"Expected Matured On\",\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(56,'Active Loans Passed Final Maturity Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency',NULL,1,1),(57,'Active Loans in last installment','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(lastInstallment.`hierarchy`) - LENGTH(REPLACE(lastInstallment.`hierarchy`, \'.\', \'\')) - 1))), lastInstallment.branch) as \"Office/Branch\",\r\nlastInstallment.Currency,\r\nlastInstallment.`Loan Officer`, \r\nlastInstallment.`Client Account No`, lastInstallment.`Client`, \r\nlastInstallment.`Loan Account No`, lastInstallment.`Product`, \r\nlastInstallment.`Fund`,  lastInstallment.`Loan Amount`, \r\nlastInstallment.`Annual Nominal Interest Rate`, \r\nlastInstallment.`Disbursed`, lastInstallment.`Expected Matured On` ,\r\n\r\nl.principal_repaid_derived as \"Principal Repaid\",\r\nl.principal_outstanding_derived as \"Principal Outstanding\",\r\nlaa.principal_overdue_derived as \"Principal Overdue\",\r\n\r\nl.interest_repaid_derived as \"Interest Repaid\",\r\nl.interest_outstanding_derived as \"Interest Outstanding\",\r\nlaa.interest_overdue_derived as \"Interest Overdue\",\r\n\r\nl.fee_charges_repaid_derived as \"Fees Repaid\",\r\nl.fee_charges_outstanding_derived  as \"Fees Outstanding\",\r\nlaa.fee_charges_overdue_derived as \"Fees Overdue\",\r\n\r\nl.penalty_charges_repaid_derived as \"Penalties Repaid\",\r\nl.penalty_charges_outstanding_derived as \"Penalties Outstanding\",\r\nlaa.penalty_charges_overdue_derived as \"Penalties Overdue\"\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\norder by lastInstallment.hierarchy, lastInstallment.Currency, lastInstallment.`Client Account No`, lastInstallment.`Loan Account No`','Individual Client \n\nReport',1,1),(58,'Active Loans in last installment Summary','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as \"Office/Branch\", x.currency as Currency,\r\n x.client_count as \"No. of Clients\", x.active_loan_count as \"No. Active Loans\", x. arrears_loan_count as \"No. of Loans in Arrears\",\r\nx.principal as \"Total Loans Disbursed\", x.principal_repaid as \"Principal Repaid\", x.principal_outstanding as \"Principal Outstanding\", x.principal_overdue as \"Principal Overdue\",\r\nx.interest as \"Total Interest\", x.interest_repaid as \"Interest Repaid\", x.interest_outstanding as \"Interest Outstanding\", x.interest_overdue as \"Interest Overdue\",\r\nx.fees as \"Total Fees\", x.fees_repaid as \"Fees Repaid\", x.fees_outstanding as \"Fees Outstanding\", x.fees_overdue as \"Fees Overdue\",\r\nx.penalties as \"Total Penalties\", x.penalties_repaid as \"Penalties Repaid\", x.penalties_outstanding as \"Penalties Outstanding\", x.penalties_overdue as \"Penalties Overdue\",\r\n\r\n	(case\r\n	when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n	when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n	when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n	when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n	else \"invalid PAR Type\"\r\n	end) as \"Portfolio at Risk %\"\r\n from m_office mo\r\njoin \r\n(select lastInstallment.branchId as branchId,\r\nlastInstallment.Currency,\r\ncount(distinct(lastInstallment.clientId)) as client_count, \r\ncount(distinct(lastInstallment.loanId)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id as branchId, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as \"Loan Officer\", c.id as clientId, c.account_no as \"Client Account No\",\r\nc.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  l.principal_amount as \"Loan Amount\", \r\nl.annual_nominal_interest_rate as \"Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed\", date(l.expected_maturedon_date) as \"Expected Matured On\"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\ngroup by lastInstallment.branchId) x on x.branchId = mo.id\r\norder by mo.hierarchy, x.Currency','Individual Client \n\nReport',1,1),(59,'Active Loans by Disbursal Period','Table',NULL,'Loan','select concat(repeat(\"..\",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as \"Office/Branch\",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as \"Client Account No\", c.display_name as \"Client\", l.account_no as \"Loan Account No\", pl.`name` as \"Product\", \r\nf.`name` as Fund,  \r\nl.principal_amount as \"Loan Principal Amount\", \r\nl.annual_nominal_interest_rate as \" Annual Nominal Interest Rate\", \r\ndate(l.disbursedon_date) as \"Disbursed Date\", \r\n\r\nl.total_expected_repayment_derived as \"Total Loan (P+I+F+Pen)\",\r\nl.total_repayment_derived as \"Total Repaid (P+I+F+Pen)\",\r\nlo.display_name as \"Loan Officer\"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\nand (l.product_id = \"${loanProductId}\" or \"-1\" = \"${loanProductId}\")\r\nand (ifnull(l.loan_officer_id, -10) = \"${loanOfficerId}\" or \"-1\" = \"${loanOfficerId}\")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no','Individual Client \n\nReport',1,1),(61,'Aging Summary (Arrears in Months)','Table',NULL,'Loan','SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Days In Arrears\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n	/* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n	(SELECT \'On Schedule\' period_no,1 pid UNION\r\n		SELECT \'0 - 30\',2 UNION\r\n		SELECT \'30 - 60\',3 UNION\r\n		SELECT \'60 - 90\',4 UNION\r\n		SELECT \'90 - 180\',5 UNION\r\n		SELECT \'180 - 360\',6 UNION\r\n		SELECT \'> 360\',7 ) pers,\r\n	(SELECT distinctrow moc.code, moc.name\r\n  	FROM m_office mo2\r\n   	INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n				LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   	INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n   	INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n	INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n	WHERE ml2.loan_status_id=300 /* active */\r\n	AND mo2.id=${officeId}\r\nAND (ml2.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n  	z.currency, z.arrPeriod, \r\n	COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n	SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n	SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n	/*derived table just used to get arrPeriod value (was much slower to\r\n	duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n	(SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<31, \'0 - 30\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<61, \'30 - 60\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<91, \'60 - 90\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<181, \'90 - 180\', \r\n		IF(DATEDIFF(CURDATE(), minOverdueDate)<361, \'180 - 360\', \r\n				 \'> 360\')))))) AS arrPeriod\r\n\r\n	FROM /* get the individual loan details */\r\n		(SELECT ml.id AS loanId, ml.currency_code as currency,\r\n   			ml.principal_disbursed_derived as principal, \r\n			   ml.interest_charged_derived as interest, \r\n   			ml.principal_repaid_derived as prinPaid, \r\n			   ml.interest_repaid_derived intPaid,\r\n\r\n			   laa.principal_overdue_derived as prinOverdue,\r\n			   laa.interest_overdue_derived as intOverdue,\r\n\r\n			   IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n			  \r\n  		FROM m_office mo\r\n   		INNER JOIN m_office ounder ON ounder.hierarchy \r\n				LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n   		INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n   		INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n		   LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n		WHERE ml.loan_status_id=300 /* active */\r\n     		AND mo.id=${officeId}\r\n     AND (ml.currency_code = \"${currencyId}\" or \"-1\" = \"${currencyId}\")\r\n  		GROUP BY ml.id) x\r\n	) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid','Loan amount in arrears by branch',1,1),(91,'Loan Account Schedule','Pentaho',NULL,'Loan',NULL,NULL,1,0),(92,'Branch Expected Cash Flow','Pentaho',NULL,'Loan',NULL,NULL,1,1),(93,'Expected Payments By Date - Basic','Table',NULL,'Loan','SELECT \r\n      ounder.name \'Office\', \r\n      IFNULL(ms.display_name,\'-\') \'Loan Officer\',\r\n	  mc.account_no \'Client Account Number\',\r\n	  mc.display_name \'Name\',\r\n	  mp.name \'Product\',\r\n	  ml.account_no \'Loan Account Number\',\r\n	  mr.duedate \'Due Date\',\r\n	  mr.installment \'Installment\',\r\n	  cu.display_symbol \'Currency\',\r\n	  mr.principal_amount- IFNULL(mr.principal_completed_derived,0) \'Principal Due\',\r\n	  mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0) \'Interest Due\', \r\n	  IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0) \'Fees Due\', \r\n	  IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0) \'Penalty Due\',\r\n      (mr.principal_amount- IFNULL(mr.principal_completed_derived,0)) +\r\n       (mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0)) + \r\n       (IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0)) + \r\n       (IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0)) \'Total Due\', \r\n     mlaa.total_overdue_derived \'Total Overdue\'\r\n										 \r\n FROM m_office mo\r\n  JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\n  \r\n  AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n	\r\n  LEFT JOIN m_client mc ON mc.office_id=ounder.id\r\n  LEFT JOIN m_loan ml ON ml.client_id=mc.id AND ml.loan_status_id=300\r\n  LEFT JOIN m_loan_arrears_aging mlaa ON mlaa.loan_id=ml.id\r\n  LEFT JOIN m_loan_repayment_schedule mr ON mr.loan_id=ml.id AND mr.completed_derived=0\r\n  LEFT JOIN m_product_loan mp ON mp.id=ml.product_id\r\n  LEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\n  LEFT JOIN m_currency cu ON cu.code=ml.currency_code\r\n WHERE mo.id=${officeId}\r\n AND (IFNULL(ml.loan_officer_id, -10) = \"${loanOfficerId}\" OR \"-1\" = \"${loanOfficerId}\")\r\n AND mr.duedate BETWEEN \'${startDate}\' AND \'${endDate}\'\r\n ORDER BY ounder.id,mr.duedate,ml.account_no','Test',1,1),(94,'Expected Payments By Date - Formatted','Pentaho',NULL,'Loan',NULL,NULL,1,1);
+INSERT INTO `stretchy_parameter` VALUES (1,'startDateSelect','startDate','startDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(2,'endDateSelect','endDate','endDate','date','date','today',NULL,NULL,NULL,NULL,NULL),(3,'obligDateTypeSelect','obligDateType','obligDateType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Closed\" as `name` union all\r\nselect 2, \"Disbursal\" ) x\r\norder by x.`id`',NULL),(5,'OfficeIdSelectOne','officeId','Office','select','number','0',NULL,'Y',NULL,'select id, \r\nconcat(substring(\"........................................\", 1, \r\n   \n\n((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat\n\n(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy',NULL),(6,'loanOfficerIdSelectAll','loanOfficerId','Loan Officer','select','number','0',NULL,NULL,'Y','(select lo.id, lo.display_name as `Name` \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\njoin m_staff lo on lo.office_id = ounder.id\r\nwhere lo.is_loan_officer = true\r\nand o.id = ${officeId})\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',5),(10,'currencyIdSelectAll','currencyId','Currency','select','number','0',NULL,NULL,'Y','select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`',NULL),(20,'fundIdSelectAll','fundId','Fund','select','number','0',NULL,NULL,'Y','(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2',NULL),(25,'loanProductIdSelectAll','loanProductId','Product','select','number','0',NULL,NULL,'Y','select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere p.currency_code = \'${currencyId}\'\r\norder by 2',10),(26,'loanPurposeIdSelectAll','loanPurposeId','Loan Purpose','select','number','0',NULL,NULL,'Y','select -10 as id, \'-\' as code_value\r\nunion all\r\nselect * from (select v.id, v.code_value\r\nfrom m_code c\r\njoin m_code_value v on v.code_id = c.id\r\nwhere c.code_name = \"loanPurpose\"\r\norder by v.order_position)  x',NULL),(100,'parTypeSelect','parType','parType','select','number','0',NULL,NULL,NULL,'select * from\r\n(select 1 as id, \"Principal Only\" as `name` union all\r\nselect 2, \"Principal + Interest\" union all\r\nselect 3, \"Principal + Interest + Fees\" union all\r\nselect 4, \"Principal + Interest + Fees + Penalties\") x\r\norder by x.`id`',NULL),(1001,'FullReportList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\r\n  \n\nrp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on rp.report_id = r.report_id\r\n  \n\nleft join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \n\n\r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on rp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where \n\nur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by \n\nr.report_category, r.report_name, rp.parameter_id',NULL),(1002,'FullParameterList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r\nsp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r\nfrom stretchy_parameter sp\r\nleft join stretchy_parameter spp on spp.parameter_id = sp.parent_parameter_id\r\nwhere sp.special is null\r\nand exists \r\n	(select \'f\' \r\n	from stretchy_report sr\r\n	join stretchy_report_parameter srp on srp.report_id = sr.report_id\r\n	where sr.report_name in(${reportListing})\r\n	and srp.parameter_id = sp.parameter_id\r\n	)\r\norder by sp.parameter_id',NULL),(1003,'reportCategoryList',NULL,'n/a','n/a','n/a','n/a','Y',NULL,NULL,'select  r.report_id, r.report_name, r.report_type, r.report_subtype, \n\nr.report_category,\r\n  rp.parameter_id, rp.report_parameter_name, p.parameter_name\r\n  from stretchy_report r\r\n  left join stretchy_report_parameter rp on \n\nrp.report_id = r.report_id\r\n  left join stretchy_parameter p on p.parameter_id = rp.parameter_id\r\n  where r.report_category = \'${reportCategory}\'\r\n  and \n\nr.use_report is true\r\n  and exists\r\n  (\r\n select \'f\'\r\n  from m_appuser_role ur \r\n  join m_role r on r.id = ur.role_id\r\n  join m_role_permission rp on \n\nrp.role_id = r.id\r\n  join m_permission p on p.id = rp.permission_id\r\n  where ur.appuser_id = ${currentUserId}\r\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \n\n\'ALL_FUNCTIONS\') or p.code = concat(\"READ_\", r.report_name))\r\n )\r\n  order by r.report_category, r.report_name, rp.parameter_id',NULL);
+INSERT INTO `stretchy_report_parameter` VALUES (1,5,NULL),(2,5,NULL),(2,6,NULL),(2,10,NULL),(2,20,NULL),(2,25,NULL),(2,26,NULL),(5,5,NULL),(5,6,NULL),(5,10,NULL),(5,20,NULL),(5,25,NULL),(5,26,NULL),(6,5,NULL),(6,6,NULL),(6,10,NULL),(6,20,NULL),(6,25,NULL),(6,26,NULL),(7,5,NULL),(7,6,NULL),(7,10,NULL),(7,20,NULL),(7,25,NULL),(7,26,NULL),(8,5,NULL),(8,6,NULL),(8,10,NULL),(8,25,NULL),(8,26,NULL),(11,5,NULL),(11,6,NULL),(11,10,NULL),(11,20,NULL),(11,25,NULL),(11,26,NULL),(11,100,NULL),(12,5,NULL),(12,6,NULL),(12,10,NULL),(12,20,NULL),(12,25,NULL),(12,26,NULL),(13,1,NULL),(13,2,NULL),(13,3,NULL),(13,5,NULL),(13,6,NULL),(13,10,NULL),(13,20,NULL),(13,25,NULL),(13,26,NULL),(14,1,NULL),(14,2,NULL),(14,3,NULL),(14,5,NULL),(14,6,NULL),(14,10,NULL),(14,20,NULL),(14,25,NULL),(14,26,NULL),(15,5,NULL),(15,6,NULL),(15,10,NULL),(15,20,NULL),(15,25,NULL),(15,26,NULL),(15,100,NULL),(16,5,NULL),(16,6,NULL),(16,10,NULL),(16,20,NULL),(16,25,NULL),(16,26,NULL),(16,100,NULL),(20,1,NULL),(20,2,NULL),(20,10,NULL),(20,20,NULL),(21,1,NULL),(21,2,NULL),(21,5,NULL),(21,10,NULL),(21,20,NULL),(48,5,'branch'),(48,2,'date'),(49,5,'branch'),(49,1,'fromDate'),(49,2,'toDate'),(50,5,'branch'),(50,1,'fromDate'),(50,2,'toDate'),(51,1,NULL),(51,2,NULL),(51,5,NULL),(51,10,NULL),(51,25,NULL),(52,5,NULL),(53,5,NULL),(53,10,NULL),(54,1,NULL),(54,2,NULL),(54,5,NULL),(54,10,NULL),(54,25,NULL),(55,5,NULL),(55,6,NULL),(55,10,NULL),(55,20,NULL),(55,25,NULL),(55,26,NULL),(56,5,NULL),(56,6,NULL),(56,10,NULL),(56,20,NULL),(56,25,NULL),(56,26,NULL),(56,100,NULL),(57,5,NULL),(57,6,NULL),(57,10,NULL),(57,20,NULL),(57,25,NULL),(57,26,NULL),(58,5,NULL),(58,6,NULL),(58,10,NULL),(58,20,NULL),(58,25,NULL),(58,26,NULL),(58,100,NULL),(59,1,NULL),(59,2,NULL),(59,5,NULL),(59,6,NULL),(59,10,NULL),(59,20,NULL),(59,25,NULL),(59,26,NULL),(61,5,NULL),(61,10,NULL),(92,1,'fromDate'),(92,5,'selectOffice'),(92,2,'toDate'),(93,1,NULL),(93,2,NULL),(93,5,NULL),(93,6,NULL),(94,2,'endDate'),(94,6,'loanOfficerId'),(94,5,'officeId'),(94,1,'startDate');
+
+insert into m_permission(grouping, `code`, entity_name, action_name, can_maker_checker)
+select 'report', concat('READ_', r.report_name), r.report_name, 'READ', false
+from stretchy_report r;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V50__add-grace-settings-to-loan-product.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V50__add-grace-settings-to-loan-product.sql
new file mode 100644
index 0000000..96f23e9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V50__add-grace-settings-to-loan-product.sql
@@ -0,0 +1,9 @@
+ALTER TABLE `m_product_loan`
+ADD COLUMN `grace_on_principal_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `max_number_of_repayments`,
+ADD COLUMN `grace_on_interest_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_principal_periods`,
+ADD COLUMN `grace_interest_free_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_interest_periods`;
+
+ALTER TABLE `m_loan`
+ADD COLUMN `grace_on_principal_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `number_of_repayments`,
+ADD COLUMN `grace_on_interest_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_principal_periods`,
+ADD COLUMN `grace_interest_free_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_interest_periods`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V51__track-additional-details-related-to-installment-performance.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V51__track-additional-details-related-to-installment-performance.sql
new file mode 100644
index 0000000..a59e5ec
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V51__track-additional-details-related-to-installment-performance.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `m_loan_repayment_schedule`
+ADD COLUMN `obligations_met_on_date` DATE NULL DEFAULT NULL AFTER `completed_derived`;
+
+ALTER TABLE `m_loan_repayment_schedule`
+CHANGE COLUMN `interest_waived_derived` `interest_waived_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `interest_writtenoff_derived` ;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V52__add_boolean_support_cols_to_acc_accounting_rule.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V52__add_boolean_support_cols_to_acc_accounting_rule.sql
new file mode 100644
index 0000000..f042770
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V52__add_boolean_support_cols_to_acc_accounting_rule.sql
@@ -0,0 +1,6 @@
+ALTER TABLE `acc_accounting_rule`
+ ADD COLUMN `allow_multiple_debits` TINYINT(1) NOT NULL DEFAULT '0' AFTER `debit_account_id`,
+ ADD COLUMN `allow_multiple_credits` TINYINT(1) NOT NULL DEFAULT '0' AFTER `credit_account_id`;
+
+ALTER TABLE acc_rule_tags
+ ADD UNIQUE KEY `UNIQUE_ACCOUNT_RULE_TAGS` (`acc_rule_id`,`tag_id`,`acc_type_enum`);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V53__track-advance-and-late-payments-on-installment.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V53__track-advance-and-late-payments-on-installment.sql
new file mode 100644
index 0000000..f7858d0
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V53__track-advance-and-late-payments-on-installment.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `m_loan_repayment_schedule`
+ADD COLUMN `total_paid_in_advance_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `penalty_charges_waived_derived`,
+ADD COLUMN `total_paid_late_derived` DECIMAL(19,6) NULL DEFAULT NULL  AFTER `total_paid_in_advance_derived`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V54__charge-to-income-account-mappings.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V54__charge-to-income-account-mappings.sql
new file mode 100755
index 0000000..ef63134
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V54__charge-to-income-account-mappings.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `acc_product_mapping`
+	ADD COLUMN `charge_id` BIGINT(20) NULL DEFAULT NULL AFTER `payment_type`,
+	ADD CONSTRAINT `FK_acc_product_mapping_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V55__add-additional-transaction-processing-strategies.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V55__add-additional-transaction-processing-strategies.sql
new file mode 100644
index 0000000..48c10f6
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V55__add-additional-transaction-processing-strategies.sql
@@ -0,0 +1,12 @@
+ALTER TABLE `ref_loan_transaction_processing_strategy`
+DROP COLUMN `lastmodified_date` ,
+DROP COLUMN `lastmodifiedby_id` ,
+DROP COLUMN `created_date` ,
+DROP COLUMN `createdby_id` ;
+
+
+INSERT INTO `ref_loan_transaction_processing_strategy` (`id`, `code`, `name`) VALUES
+(5,'principal-interest-penalties-fees-order-strategy', 'Principal Interest Penalties Fees Order');
+
+INSERT INTO `ref_loan_transaction_processing_strategy` (`id`,`code`, `name`)
+VALUES (6,'interest-principal-penalties-fees-order-strategy', 'Interest Principal Penalties Fees Order');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V56__track-overpaid-amount-on-loans.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V56__track-overpaid-amount-on-loans.sql
new file mode 100644
index 0000000..99a235e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V56__track-overpaid-amount-on-loans.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_loan`
+ADD COLUMN `total_overpaid_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `total_outstanding_derived`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V57__add_default_values_to_debit_and_credit_accounts_acc_accounting_rule.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V57__add_default_values_to_debit_and_credit_accounts_acc_accounting_rule.sql
new file mode 100644
index 0000000..bb52beb
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V57__add_default_values_to_debit_and_credit_accounts_acc_accounting_rule.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `acc_accounting_rule`
+	CHANGE COLUMN `debit_account_id` `debit_account_id` BIGINT(20) NULL DEFAULT NULL AFTER `office_id`,
+	CHANGE COLUMN `credit_account_id` `credit_account_id` BIGINT(20) NULL DEFAULT NULL AFTER `allow_multiple_debits`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V58__create-holiday-tables_changed.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V58__create-holiday-tables_changed.sql
new file mode 100644
index 0000000..35d8326
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V58__create-holiday-tables_changed.sql
@@ -0,0 +1,22 @@
+
+create table `m_holiday`(
+`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+`name` varchar(100) NOT NULL,
+`from_date` DATETIME NOT NULL,
+`to_date` DATETIME NOT NULL,
+`description` varchar(100)  NULL DEFAULT NULL,
+PRIMARY KEY (`id`),
+UNIQUE INDEX `holiday_name` (`name`)
+);
+
+CREATE TABLE `m_holiday_office` (
+	`holiday_id` BIGINT(20) NOT NULL,
+	`office_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`holiday_id`, `office_id`),
+	INDEX `m_holiday_id_ibfk_1` (`holiday_id`),
+	INDEX `m_office_id_ibfk_2` (`office_id`),
+	CONSTRAINT `m_holiday_id_ibfk_1` FOREIGN KEY (`holiday_id`) REFERENCES `m_holiday` (`id`),
+	CONSTRAINT `m_office_id_ibfk_2` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'CREATE_HOLIDAY', 'HOLIDAY', 'CREATE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V59__add_group_roles_schema_and_permissions.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V59__add_group_roles_schema_and_permissions.sql
new file mode 100644
index 0000000..16ad5f0
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V59__add_group_roles_schema_and_permissions.sql
@@ -0,0 +1,22 @@
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) VALUES ('GROUPROLE', 1);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ASSIGNROLE_GROUP', 'GROUP', 'ASSIGNROLE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UNASSIGNROLE_GROUP', 'GROUP', 'UNASSIGNROLE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATEROLE_GROUP', 'GROUP', 'UPDATEROLE', 0);
+
+CREATE TABLE `m_group_roles` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`client_id` BIGINT(20) NULL DEFAULT NULL,
+	`group_id` BIGINT(20) NULL DEFAULT NULL,
+	`role_cv_id` INT(11) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FKGroupRoleClientId` (`client_id`),
+	INDEX `FKGroupRoleGroupId` (`group_id`),
+	INDEX `FK_grouprole_m_codevalue` (`role_cv_id`),
+	UNIQUE INDEX `UNIQUE_GROUP_ROLES` (`client_id`, `group_id`, `role_cv_id`),
+	CONSTRAINT `FKGroupRoleClientId` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+	CONSTRAINT `FKGroupRoleGroupId` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+	CONSTRAINT `FK_grouprole_m_codevalue` FOREIGN KEY (`role_cv_id`) REFERENCES `m_code_value` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V5__update-savings-product-and-account-tables.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V5__update-savings-product-and-account-tables.sql
new file mode 100644
index 0000000..a97eab7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V5__update-savings-product-and-account-tables.sql
@@ -0,0 +1,11 @@
+ALTER TABLE `m_savings_product`
+DROP COLUMN `nominal_interest_rate_period_frequency_enum`,
+CHANGE COLUMN `nominal_interest_rate_per_period` `nominal_annual_interest_rate` DECIMAL(19,6) NOT NULL,
+CHANGE COLUMN `interest_period_enum` `interest_compounding_period_enum` SMALLINT(5) NOT NULL;
+
+
+ALTER TABLE `m_savings_account`
+DROP COLUMN `annual_nominal_interest_rate`,
+DROP COLUMN `nominal_interest_rate_period_frequency_enum`,
+CHANGE COLUMN `nominal_interest_rate_per_period` `nominal_annual_interest_rate` DECIMAL(19,6) NOT NULL,
+CHANGE COLUMN `interest_period_enum` `interest_compounding_period_enum` SMALLINT(5) NOT NULL;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V60__quipo_dashboard_reports.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V60__quipo_dashboard_reports.sql
new file mode 100644
index 0000000..22d79e8
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V60__quipo_dashboard_reports.sql
@@ -0,0 +1,318 @@
+/* Leaving as out-of-box (but non-core reports) for now
+could be removed later if people think too many non-generic
+reports are being added
+used in quipo TEVI dashboard (landing pages) */
+
+DELETE FROM `stretchy_report` where report_name = 'FieldAgentStats';
+DELETE FROM `stretchy_report` where report_name = 'FieldAgentPrograms';
+DELETE FROM `stretchy_report` where report_name = 'ProgramDetails';
+DELETE FROM `stretchy_report` where report_name = 'ChildrenStaffList';
+DELETE FROM `stretchy_report` where report_name = 'CoordinatorStats';
+DELETE FROM `stretchy_report` where report_name = 'BranchManagerStats';
+DELETE FROM `stretchy_report` where report_name = 'ProgramDirectorStats';
+DELETE FROM `stretchy_report` where report_name = 'ProgramStats';
+
+DELETE FROM `m_permission` where entity_name = 'FieldAgentStats';
+DELETE FROM `m_permission` where entity_name = 'FieldAgentPrograms';
+DELETE FROM `m_permission` where entity_name = 'ProgramDetails';
+DELETE FROM `m_permission` where entity_name = 'ChildrenStaffList';
+DELETE FROM `m_permission` where entity_name = 'CoordinatorStats';
+DELETE FROM `m_permission` where entity_name = 'BranchManagerStats';
+DELETE FROM `m_permission` where entity_name = 'ProgramDirectorStats';
+DELETE FROM `m_permission` where entity_name = 'ProgramStats';
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('FieldAgentStats', 'Table', 'Quipo', false, false, 'Field Agent Statistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+'???' as loanOverPaymentAmount,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_staff fa
+join m_office o on o.id = fa.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_group pgm on pgm.staff_id = fa.id
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where fa.id = ${staffId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('FieldAgentPrograms', 'Table', 'Quipo', false, false, 'List of Field Agent Programs',
+
+"
+select pgm.id, pgm.display_name as `name`, sts.enum_message_property as status
+ from m_group pgm
+ join m_office o on o.id = pgm.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+ left join r_enum_value sts on sts.enum_name = 'status_enum' and sts.enum_id = pgm.status_enum
+ where pgm.staff_id = ${staffId}
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('ProgramDetails', 'Table', 'Quipo', false, false, 'List of Loans in a Program',
+
+"
+ select l.id as loanId, l.account_no as loanAccountNo, c.id as clientId, c.account_no as clientAccountNo,
+ pgm.display_name as programName,
+
+(select count(*)
+from m_loan cy
+where cy.group_id = pgm.id and cy.client_id =c.id
+and cy.disbursedon_date <= l.disbursedon_date) as loanCycleNo,
+
+c.display_name as clientDisplayName,
+ ifnull(cur.display_symbol, l.currency_code) as Currency,
+ifnull(l.principal_repaid_derived,0.0) as loanRepaidAmount,
+ifnull(l.principal_outstanding_derived, 0.0) as loanOutstandingAmount,
+ '???' as loanInAdvanceAmount,
+
+ifnull(laa.principal_overdue_derived, 0.0) as loanInArrearsAmount,
+if(ifnull(laa.principal_overdue_derived, 0.00) > 0, 'Yes', 'No') as inDefault,
+
+if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)  as portfolioAtRisk
+
+ from m_group pgm
+ join m_office o on o.id = pgm.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+ join m_loan l on l.group_id = pgm.id and l.client_id is not null
+ left join m_currency cur on cur.code = l.currency_code
+ join m_client c on c.id = l.client_id
+ left join m_loan_arrears_aging laa on laa.loan_id = l.id
+ where pgm.id = ${programId}
+ and l.loan_status_id = 300
+order by c.display_name, l.account_no
+
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('ChildrenStaffList', 'Table', 'Quipo', false, false, 'Get Next Level Down Staff',
+
+"
+ select s.id, s.display_name,
+s.firstname, s.lastname, s.organisational_role_enum,
+s.organisational_role_parent_staff_id,
+sp.display_name as `organisational_role_parent_staff_display_name`
+from m_staff s
+join m_staff sp on s.organisational_role_parent_staff_id = sp.id
+where s.organisational_role_parent_staff_id = ${staffId}
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('CoordinatorStats', 'Table', 'Quipo', false, false, 'Coordinator Statistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+'???' as loanOverPaymentAmount,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_staff coord
+join m_staff fa on fa.organisational_role_parent_staff_id = coord.id
+join m_office o on o.id = fa.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_group pgm on pgm.staff_id = fa.id
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where coord.id = ${staffId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('BranchManagerStats', 'Table', 'Quipo', false, false, 'Branch Manager Statistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+'???' as loanOverPaymentAmount,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_staff bm
+join m_staff coord on coord.organisational_role_parent_staff_id = bm.id
+join m_staff fa on fa.organisational_role_parent_staff_id = coord.id
+join m_office o on o.id = fa.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_group pgm on pgm.staff_id = fa.id
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where bm.id = ${staffId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('ProgramDirectorStats', 'Table', 'Quipo', false, false, 'Program DirectorStatistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+'???' as loanOverPaymentAmount,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_staff pd
+join m_staff bm on bm.organisational_role_parent_staff_id = pd.id
+join m_staff coord on coord.organisational_role_parent_staff_id = bm.id
+join m_staff fa on fa.organisational_role_parent_staff_id = coord.id
+join m_office o on o.id = fa.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_group pgm on pgm.staff_id = fa.id
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where pd.id = ${staffId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('ProgramStats', 'Table', 'Quipo', false, false, 'Program Statistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+'???' as loanOverPaymentAmount,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_group pgm
+join m_office o on o.id = pgm.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where pgm.id = ${programId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_FieldAgentStats', 'FieldAgentStats', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_FieldAgentPrograms', 'FieldAgentPrograms', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_ProgramDetails', 'ProgramDetails', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_ChildrenStaffList', 'ChildrenStaffList', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_CoordinatorStats', 'CoordinatorStats', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_BranchManagerStats', 'BranchManagerStats', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_ProgramDirectorStats', 'ProgramDirectorStats', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_ProgramStats', 'ProgramStats', 'READ', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V61__txn_running_balance_example.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V61__txn_running_balance_example.sql
new file mode 100644
index 0000000..4a13da0
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V61__txn_running_balance_example.sql
@@ -0,0 +1,124 @@
+
+/* Leaving as out-of-box (but non-core report) for now
+could be removed later if people think too many non-generic
+reports are being added
+similar example used in small MFI Elevate Africa */
+
+
+delete from stretchy_report_parameter
+where report_id = (select r.id from stretchy_report r where r.report_name = 'TxnRunningBalances');
+
+DELETE FROM `stretchy_report` where report_name = 'TxnRunningBalances';
+
+DELETE FROM `m_permission` where entity_name = 'TxnRunningBalances';
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('TxnRunningBalances', 'Table', 'Transaction', false, false,
+"Running Balance Txn report for Individual Lending.
+Suitable for small MFI's.  Larger could use it using the branch or other parameters.
+Basically, suck it and see if its quick enough for you out-of-te box or whether it needs performance work in your situation.
+",
+
+"
+select date('${startDate}') as 'Transaction Date', 'Opening Balance' as `Transaction Type`, null as Office,
+	null as 'Loan Officer', null as `Loan Account No`, null as `Loan Product`, null as `Currency`,
+	null as `Client Account No`, null as Client,
+	null as Amount, null as Principal, null as Interest,
+@totalOutstandingPrincipal :=
+ifnull(round(sum(
+	if (txn.transaction_type_enum = 1 /* disbursement */,
+		ifnull(txn.amount,0.00),
+		ifnull(txn.principal_portion_derived,0.00) * -1))
+			,2),0.00)  as 'Outstanding Principal',
+
+@totalInterestIncome :=
+ifnull(round(sum(
+	if (txn.transaction_type_enum in (2,5,8) /* repayment, repayment at disbursal, recovery repayment */,
+		ifnull(txn.interest_portion_derived,0.00),
+		0))
+			,2),0.00) as 'Interest Income',
+
+@totalWriteOff :=
+ifnull(round(sum(
+	if (txn.transaction_type_enum = 6 /* write-off */,
+		ifnull(txn.principal_portion_derived,0.00),
+		0))
+			,2),0.00) as 'Principal Write Off'
+from m_office o
+join m_office ounder on ounder.hierarchy like concat(o.hierarchy, '%')
+                          and ounder.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_client c on c.office_id = ounder.id
+join m_loan l on l.client_id = c.id
+join m_product_loan lp on lp.id = l.product_id
+join m_loan_transaction txn on txn.loan_id = l.id
+left join m_currency cur on cur.code = l.currency_code
+where txn.is_reversed = false
+and txn.transaction_type_enum not in (10,11)
+and o.id = ${officeId}
+and txn.transaction_date < date('${startDate}')
+
+union all
+
+select x.`Transaction Date`, x.`Transaction Type`, x.Office, x.`Loan Officer`, x.`Loan Account No`, x.`Loan Product`, x.`Currency`,
+	x.`Client Account No`, x.Client, x.Amount, x.Principal, x.Interest,
+cast(round(
+	if (x.transaction_type_enum = 1 /* disbursement */,
+		@totalOutstandingPrincipal := @totalOutstandingPrincipal + x.`Amount`,
+		@totalOutstandingPrincipal := @totalOutstandingPrincipal - x.`Principal`)
+			,2) as decimal(19,2)) as 'Outstanding Principal',
+cast(round(
+	if (x.transaction_type_enum in (2,5,8) /* repayment, repayment at disbursal, recovery repayment */,
+		@totalInterestIncome := @totalInterestIncome + x.`Interest`,
+		@totalInterestIncome)
+			,2) as decimal(19,2)) as 'Interest Income',
+cast(round(
+	if (x.transaction_type_enum = 6 /* write-off */,
+		@totalWriteOff := @totalWriteOff + x.`Principal`,
+		@totalWriteOff)
+			,2) as decimal(19,2)) as 'Principal Write Off'
+from
+(select txn.transaction_type_enum, txn.id as txn_id, txn.transaction_date as 'Transaction Date',
+cast(
+	ifnull(re.enum_message_property, concat('Unknown Transaction Type Value: ' , txn.transaction_type_enum))
+	as char) as 'Transaction Type',
+ounder.`name` as Office, lo.display_name as 'Loan Officer',
+l.account_no  as 'Loan Account No', lp.`name` as 'Loan Product',
+ifnull(cur.display_symbol, l.currency_code) as Currency,
+c.account_no as 'Client Account No', c.display_name as 'Client',
+ifnull(txn.amount,0.00) as Amount,
+ifnull(txn.principal_portion_derived,0.00) as Principal,
+ifnull(txn.interest_portion_derived,0.00) as Interest
+from m_office o
+join m_office ounder on ounder.hierarchy like concat(o.hierarchy, '%')
+                          and ounder.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_client c on c.office_id = ounder.id
+join m_loan l on l.client_id = c.id
+left join m_staff lo on lo.id = l.loan_officer_id
+join m_product_loan lp on lp.id = l.product_id
+join m_loan_transaction txn on txn.loan_id = l.id
+left join m_currency cur on cur.code = l.currency_code
+left join r_enum_value re on re.enum_name = 'transaction_type_enum'
+						and re.enum_id = txn.transaction_type_enum
+where txn.is_reversed = false
+and txn.transaction_type_enum not in (10,11)
+and (ifnull(l.loan_officer_id, -10) = '${loanOfficerId}' or '-1' = '${loanOfficerId}')
+and o.id = ${officeId}
+and txn.transaction_date >= date('${startDate}')
+and txn.transaction_date <= date('${endDate}')
+order by txn.transaction_date, txn.id) x
+");
+
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_TxnRunningBalances', 'TxnRunningBalances', 'READ', 0);
+
+
+insert into stretchy_report_parameter (report_id, parameter_id)
+select r.id, p.id
+from stretchy_report r,
+stretchy_parameter p
+where r.report_name = 'TxnRunningBalances'
+and p.parameter_name in ('startDateSelect', 'endDateSelect', 'OfficeIdSelectOne',
+			'loanOfficerIdSelectAll');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V62__add_staff_id_to_m_client_changed.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V62__add_staff_id_to_m_client_changed.sql
new file mode 100644
index 0000000..af90c29
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V62__add_staff_id_to_m_client_changed.sql
@@ -0,0 +1,5 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UNASSIGNSTAFF_CLIENT', 'CLIENT', 'UNASSIGNSTAFF', 0);
+ALTER TABLE m_client
+  ADD COLUMN `staff_id` BIGINT(20) NULL DEFAULT NULL AFTER `office_id`,
+  ADD INDEX `client_staff_id` (`staff_id`),
+  ADD CONSTRAINT `FK_m_client_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V63__add_sync_disbursement_with_meeting_column_to_loan.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V63__add_sync_disbursement_with_meeting_column_to_loan.sql
new file mode 100644
index 0000000..b909e82
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V63__add_sync_disbursement_with_meeting_column_to_loan.sql
@@ -0,0 +1,4 @@
+
+
+ALTER TABLE `m_loan`
+	ADD COLUMN `sync_disbursement_with_meeting` TINYINT(1) NULL AFTER `loan_transaction_strategy_id`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V64__add_permission_for_assign_staff.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V64__add_permission_for_assign_staff.sql
new file mode 100644
index 0000000..ac46c06
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V64__add_permission_for_assign_staff.sql
@@ -0,0 +1 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ASSIGNSTAFF_CLIENT', 'CLIENT', 'ASSIGNSTAFF', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V65__fix_rupee_symbol_issues.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V65__fix_rupee_symbol_issues.sql
new file mode 100755
index 0000000..abc3730
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V65__fix_rupee_symbol_issues.sql
@@ -0,0 +1,5 @@
+/***Work around for issues on non Unicode safe connections**/
+UPDATE `m_currency` SET `display_symbol`=CHAR(226, 130, 185) WHERE  `id`=64;
+
+/***Update organization currencies if applicable***/
+update m_organisation_currency set display_symbol =CHAR(226, 130, 185) where code = "INR";
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V66__client_close_functionality.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V66__client_close_functionality.sql
new file mode 100644
index 0000000..9c54817
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V66__client_close_functionality.sql
@@ -0,0 +1,7 @@
+INSERT INTO `m_code` (`code_name`, `is_system_defined`) VALUES ('ClientClosureReason', 1);
+
+ALTER TABLE `m_client` ADD COLUMN `closure_reason_cv_id` INT(11) NULL DEFAULT NULL,
+ADD COLUMN `closedon_date` DATE NULL DEFAULT NULL,
+ADD CONSTRAINT `FK_m_client_m_code` FOREIGN KEY (`closure_reason_cv_id`) REFERENCES `m_code_value` (`id`);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`) VALUES ('portfolio', 'CLOSE_CLIENT', 'CLIENT', 'CLOSE');
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V67__loans_in_advance_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V67__loans_in_advance_table.sql
new file mode 100644
index 0000000..d6c60d7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V67__loans_in_advance_table.sql
@@ -0,0 +1,12 @@
+/* table contains details of loans that have had overpayments aka payments in advance */
+
+CREATE TABLE `m_loan_paid_in_advance` (
+  `loan_id` bigint(20) NOT NULL,
+  `principal_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_paid_in_advance_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V68__quipo_dashboard_reports_updated.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V68__quipo_dashboard_reports_updated.sql
new file mode 100644
index 0000000..7326448
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V68__quipo_dashboard_reports_updated.sql
@@ -0,0 +1,324 @@
+/* Leaving as out-of-box (but non-core reports) for now
+could be removed later if people think too many non-generic
+reports are being added
+used in quipo TEVI dashboard (landing pages) */
+
+DELETE FROM `stretchy_report` where report_name = 'FieldAgentStats';
+DELETE FROM `stretchy_report` where report_name = 'FieldAgentPrograms';
+DELETE FROM `stretchy_report` where report_name = 'ProgramDetails';
+DELETE FROM `stretchy_report` where report_name = 'ChildrenStaffList';
+DELETE FROM `stretchy_report` where report_name = 'CoordinatorStats';
+DELETE FROM `stretchy_report` where report_name = 'BranchManagerStats';
+DELETE FROM `stretchy_report` where report_name = 'ProgramDirectorStats';
+DELETE FROM `stretchy_report` where report_name = 'ProgramStats';
+
+DELETE FROM `m_permission` where entity_name = 'FieldAgentStats';
+DELETE FROM `m_permission` where entity_name = 'FieldAgentPrograms';
+DELETE FROM `m_permission` where entity_name = 'ProgramDetails';
+DELETE FROM `m_permission` where entity_name = 'ChildrenStaffList';
+DELETE FROM `m_permission` where entity_name = 'CoordinatorStats';
+DELETE FROM `m_permission` where entity_name = 'BranchManagerStats';
+DELETE FROM `m_permission` where entity_name = 'ProgramDirectorStats';
+DELETE FROM `m_permission` where entity_name = 'ProgramStats';
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('FieldAgentStats', 'Table', 'Quipo', false, false, 'Field Agent Statistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_staff fa
+join m_office o on o.id = fa.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_group pgm on pgm.staff_id = fa.id
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where fa.id = ${staffId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('FieldAgentPrograms', 'Table', 'Quipo', false, false, 'List of Field Agent Programs',
+
+"
+select pgm.id, pgm.display_name as `name`, sts.enum_message_property as status
+ from m_group pgm
+ join m_office o on o.id = pgm.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+ left join r_enum_value sts on sts.enum_name = 'status_enum' and sts.enum_id = pgm.status_enum
+ where pgm.staff_id = ${staffId}
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('ProgramDetails', 'Table', 'Quipo', false, false, 'List of Loans in a Program',
+
+"
+ select l.id as loanId, l.account_no as loanAccountNo, c.id as clientId, c.account_no as clientAccountNo,
+ pgm.display_name as programName,
+
+(select count(*)
+from m_loan cy
+where cy.group_id = pgm.id and cy.client_id =c.id
+and cy.disbursedon_date <= l.disbursedon_date) as loanCycleNo,
+
+c.display_name as clientDisplayName,
+ ifnull(cur.display_symbol, l.currency_code) as Currency,
+ifnull(l.principal_repaid_derived,0.0) as loanRepaidAmount,
+ifnull(l.principal_outstanding_derived, 0.0) as loanOutstandingAmount,
+sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,
+
+ifnull(laa.principal_overdue_derived, 0.0) as loanInArrearsAmount,
+if(ifnull(laa.principal_overdue_derived, 0.00) > 0, 'Yes', 'No') as inDefault,
+
+if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)  as portfolioAtRisk
+
+ from m_group pgm
+ join m_office o on o.id = pgm.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+ join m_loan l on l.group_id = pgm.id and l.client_id is not null
+ left join m_currency cur on cur.code = l.currency_code
+ join m_client c on c.id = l.client_id
+ left join m_loan_arrears_aging laa on laa.loan_id = l.id
+ left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id
+ where pgm.id = ${programId}
+ and l.loan_status_id = 300
+order by c.display_name, l.account_no
+
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('ChildrenStaffList', 'Table', 'Quipo', false, false, 'Get Next Level Down Staff',
+
+"
+ select s.id, s.display_name,
+s.firstname, s.lastname, s.organisational_role_enum,
+s.organisational_role_parent_staff_id,
+sp.display_name as `organisational_role_parent_staff_display_name`
+from m_staff s
+join m_staff sp on s.organisational_role_parent_staff_id = sp.id
+where s.organisational_role_parent_staff_id = ${staffId}
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('CoordinatorStats', 'Table', 'Quipo', false, false, 'Coordinator Statistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_staff coord
+join m_staff fa on fa.organisational_role_parent_staff_id = coord.id
+join m_office o on o.id = fa.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_group pgm on pgm.staff_id = fa.id
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where coord.id = ${staffId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('BranchManagerStats', 'Table', 'Quipo', false, false, 'Branch Manager Statistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_staff bm
+join m_staff coord on coord.organisational_role_parent_staff_id = bm.id
+join m_staff fa on fa.organisational_role_parent_staff_id = coord.id
+join m_office o on o.id = fa.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_group pgm on pgm.staff_id = fa.id
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where bm.id = ${staffId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('ProgramDirectorStats', 'Table', 'Quipo', false, false, 'Program DirectorStatistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_staff pd
+join m_staff bm on bm.organisational_role_parent_staff_id = pd.id
+join m_staff coord on coord.organisational_role_parent_staff_id = bm.id
+join m_staff fa on fa.organisational_role_parent_staff_id = coord.id
+join m_office o on o.id = fa.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_group pgm on pgm.staff_id = fa.id
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where pd.id = ${staffId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `core_report`, `use_report`, `description`, `report_sql`)
+VALUES ('ProgramStats', 'Table', 'Quipo', false, false, 'Program Statistics',
+
+"
+select ifnull(cur.display_symbol, l.currency_code) as Currency,
+/*This query will return more than one entry if more than one currency is used */
+count(distinct(c.id)) as activeClients, count(*) as activeLoans,
+sum(l.principal_disbursed_derived) as disbursedAmount,
+sum(l.principal_outstanding_derived) as loanOutstandingAmount,
+round((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,
+sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,
+sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) as portfolioAtRisk,
+
+round((sum(
+	if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,
+
+count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) as clientsInDefault,
+round((count(distinct(
+		if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+			c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,
+(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount
+from m_group pgm
+join m_office o on o.id = pgm.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+join m_loan l on l.group_id = pgm.id and l.client_id is not null
+left join m_currency cur on cur.code = l.currency_code
+left join m_loan_arrears_aging laa on laa.loan_id = l.id
+left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id
+join m_client c on c.id = l.client_id
+where pgm.id = ${programId}
+and l.loan_status_id = 300
+group  by l.currency_code
+");
+
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_FieldAgentStats', 'FieldAgentStats', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_FieldAgentPrograms', 'FieldAgentPrograms', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_ProgramDetails', 'ProgramDetails', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_ChildrenStaffList', 'ChildrenStaffList', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_CoordinatorStats', 'CoordinatorStats', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_BranchManagerStats', 'BranchManagerStats', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_ProgramDirectorStats', 'ProgramDirectorStats', 'READ', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('report', 'READ_ProgramStats', 'ProgramStats', 'READ', 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V69__loans_in_advance_initialise.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V69__loans_in_advance_initialise.sql
new file mode 100644
index 0000000..1341d77
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V69__loans_in_advance_initialise.sql
@@ -0,0 +1,19 @@
+/* initialises m_loan_paid_in_advance table... same sql is run in daily batch job */
+
+truncate m_loan_paid_in_advance;
+
+INSERT INTO m_loan_paid_in_advance(loan_id, principal_in_advance_derived, interest_in_advance_derived,
+fee_charges_in_advance_derived, penalty_charges_in_advance_derived, total_in_advance_derived)
+select ml.id as loanId,SUM(ifnull(mr.principal_completed_derived, 0)) as principal_in_advance_derived,
+SUM(ifnull(mr.interest_completed_derived, 0)) as interest_in_advance_derived,
+SUM(ifnull(mr.fee_charges_completed_derived, 0)) as fee_charges_in_advance_derived,
+SUM(ifnull(mr.penalty_charges_completed_derived, 0)) as penalty_charges_in_advance_derived,
+
+(SUM(ifnull(mr.principal_completed_derived, 0)) + SUM(ifnull(mr.interest_completed_derived, 0)) +
+SUM(ifnull(mr.fee_charges_completed_derived, 0)) + SUM(ifnull(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived
+FROM m_loan ml
+INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id
+WHERE ml.loan_status_id = 300 and mr.duedate >= CURDATE()
+GROUP BY ml.id
+HAVING (SUM(ifnull(mr.principal_completed_derived, 0)) + SUM(ifnull(mr.interest_completed_derived, 0)) +
+SUM(ifnull(mr.fee_charges_completed_derived, 0)) + SUM(ifnull(mr.penalty_charges_completed_derived, 0))) > 0.0
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V6__add_min_max_principal_column_to_loan.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V6__add_min_max_principal_column_to_loan.sql
new file mode 100644
index 0000000..4a5c527
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V6__add_min_max_principal_column_to_loan.sql
@@ -0,0 +1,7 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `min_principal_amount` DECIMAL(19,6) NOT NULL AFTER `principal_amount`,
+	ADD COLUMN `max_principal_amount` DECIMAL(19,6) NOT NULL AFTER `min_principal_amount`;
+
+ALTER TABLE `m_loan`
+	ADD COLUMN `min_principal_amount` DECIMAL(19,6) NOT NULL AFTER `principal_amount`,
+	ADD COLUMN `max_principal_amount` DECIMAL(19,6) NOT NULL AFTER `min_principal_amount`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V70__quipo_program_detail_query_fix.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V70__quipo_program_detail_query_fix.sql
new file mode 100644
index 0000000..dcf2952
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V70__quipo_program_detail_query_fix.sql
@@ -0,0 +1,38 @@
+
+update stretchy_report
+set report_sql =
+"
+ select l.id as loanId, l.account_no as loanAccountNo, c.id as clientId, c.account_no as clientAccountNo,
+ pgm.display_name as programName,
+
+(select count(*)
+from m_loan cy
+where cy.group_id = pgm.id and cy.client_id =c.id
+and cy.disbursedon_date <= l.disbursedon_date) as loanCycleNo,
+
+c.display_name as clientDisplayName,
+ ifnull(cur.display_symbol, l.currency_code) as Currency,
+ifnull(l.principal_repaid_derived,0.0) as loanRepaidAmount,
+ifnull(l.principal_outstanding_derived, 0.0) as loanOutstandingAmount,
+ifnull(lpa.principal_in_advance_derived,0.0) as LoanPaidInAdvance,
+
+ifnull(laa.principal_overdue_derived, 0.0) as loanInArrearsAmount,
+if(ifnull(laa.principal_overdue_derived, 0.00) > 0, 'Yes', 'No') as inDefault,
+
+if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),
+		l.principal_outstanding_derived,0)  as portfolioAtRisk
+
+ from m_group pgm
+ join m_office o on o.id = pgm.office_id
+			and o.hierarchy like concat('${currentUserHierarchy}', '%')
+ join m_loan l on l.group_id = pgm.id and l.client_id is not null
+ left join m_currency cur on cur.code = l.currency_code
+ join m_client c on c.id = l.client_id
+ left join m_loan_arrears_aging laa on laa.loan_id = l.id
+ left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id
+ where pgm.id = ${programId}
+ and l.loan_status_id = 300
+order by c.display_name, l.account_no
+
+"
+where report_name = 'ProgramDetails';
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V71__insert_reschedule_repayment_to_configuration.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V71__insert_reschedule_repayment_to_configuration.sql
new file mode 100644
index 0000000..e902697
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V71__insert_reschedule_repayment_to_configuration.sql
@@ -0,0 +1 @@
+INSERT INTO `c_configuration` (`name`, `enabled`) VALUES ('reschedule-future-repayments', 1);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V72__add_m_loan_counter_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V72__add_m_loan_counter_changes.sql
new file mode 100644
index 0000000..3cd450c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V72__add_m_loan_counter_changes.sql
@@ -0,0 +1,19 @@
+ALTER TABLE m_product_loan
+ ADD COLUMN `include_in_borrower_cycle` TINYINT(1) NOT NULL DEFAULT '0';
+
+CREATE TABLE `m_client_loan_counter` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`client_id` BIGINT(20) NOT NULL,
+	`loan_product_id` BIGINT(20) NOT NULL,
+	`loan_id` BIGINT(20) NOT NULL,
+	`running_count` SMALLINT(4) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_client_id_loan_counter` (`client_id`),
+	INDEX `FK_m_loan_product_loan_counter` (`loan_product_id`),
+	INDEX `FK_m_client_loan_counter` (`loan_id`),
+	CONSTRAINT `FK_m_client_id_loan_counter` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+	CONSTRAINT `FK_m_loan_product_loan_counter` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`),
+	CONSTRAINT `FK_m_client_loan_counter` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V73__add_repayments_rescheduled_to_and_processed_column_to_holiday.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V73__add_repayments_rescheduled_to_and_processed_column_to_holiday.sql
new file mode 100644
index 0000000..a6f57a7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V73__add_repayments_rescheduled_to_and_processed_column_to_holiday.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `m_holiday`
+	ADD COLUMN `repayments_rescheduled_to` DATETIME NOT NULL AFTER `to_date`,
+	ADD COLUMN `processed` TINYINT(1) NOT NULL DEFAULT '0' AFTER `repayments_rescheduled_to`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V74__alter_m_loan_counter_table_add_group.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V74__alter_m_loan_counter_table_add_group.sql
new file mode 100644
index 0000000..a8df34c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V74__alter_m_loan_counter_table_add_group.sql
@@ -0,0 +1,4 @@
+DROP TABLE `m_client_loan_counter`;
+ALTER TABLE `m_loan`
+	ADD COLUMN `loan_counter` SMALLINT NULL DEFAULT NULL AFTER `sync_disbursement_with_meeting`,
+	ADD COLUMN `loan_product_counter` SMALLINT NULL DEFAULT NULL AFTER `loan_counter`;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V75__add_reschedule-repayments-on-holidays_to_configuration.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V75__add_reschedule-repayments-on-holidays_to_configuration.sql
new file mode 100644
index 0000000..1325e36
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V75__add_reschedule-repayments-on-holidays_to_configuration.sql
@@ -0,0 +1 @@
+INSERT INTO `c_configuration` (`name`) VALUES ('reschedule-repayments-on-holidays');
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V76__rename_permission_grouping.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V76__rename_permission_grouping.sql
new file mode 100644
index 0000000..109f954
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V76__rename_permission_grouping.sql
@@ -0,0 +1,12 @@
+/* break-out center and group permissions from porfolio grouping */
+
+update m_permission
+set grouping = "portfolio_center"
+where code like '%center%'
+and grouping like 'portfolio';
+
+
+update m_permission
+set grouping = "portfolio_group"
+where code like '%group%'
+and grouping like 'portfolio';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V77__alter_m_product_loan_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V77__alter_m_product_loan_changes.sql
new file mode 100644
index 0000000..c8a5869
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V77__alter_m_product_loan_changes.sql
@@ -0,0 +1,2 @@
+alter table `m_product_loan`  add column `start_date` DATE NULL DEFAULT NULL ,
+add column `close_date` DATE NULL DEFAULT NULL ;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V78__breakdown_portfolio_grouping.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V78__breakdown_portfolio_grouping.sql
new file mode 100644
index 0000000..f6b4790
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V78__breakdown_portfolio_grouping.sql
@@ -0,0 +1,5 @@
+/* grouping is misspelt but also should be under accounting */
+
+update m_permission
+set grouping = 'accounting'
+where grouping = 'organistion';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V79__schedule_jobs_tables.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V79__schedule_jobs_tables.sql
new file mode 100755
index 0000000..a53af33
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V79__schedule_jobs_tables.sql
@@ -0,0 +1,41 @@
+CREATE TABLE `scheduled_jobs` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`job_name` VARCHAR(50) NOT NULL COLLATE 'latin1_swedish_ci',
+	`job_display_name` VARCHAR(50) NOT NULL COLLATE 'latin1_swedish_ci',
+	`cron_expression` VARCHAR(20) NOT NULL COLLATE 'latin1_swedish_ci',
+	`create_time` DATETIME NOT NULL,
+	`task_priority` SMALLINT(6) NOT NULL DEFAULT '5',
+	`group_name` VARCHAR(50) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
+	`previous_run_start_time` DATETIME NULL DEFAULT NULL,
+	`next_run_time` DATETIME NULL DEFAULT NULL,
+	`trigger_key` VARCHAR(500) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
+	`job_initializing_errorlog` TEXT NULL COLLATE 'latin1_swedish_ci',
+	`is_active` TINYINT(1) NOT NULL DEFAULT '1',
+	PRIMARY KEY (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+CREATE TABLE `scheduled_job_runhistory` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`job_id` BIGINT(20) NOT NULL,
+	`version` BIGINT(20) NOT NULL,
+	`start_time` DATETIME NOT NULL,
+	`end_time` DATETIME NOT NULL,
+	`status` VARCHAR(10) NOT NULL COLLATE 'latin1_swedish_ci',
+	`errormessage` VARCHAR(500) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
+	`triggertype` VARCHAR(25) NOT NULL COLLATE 'latin1_swedish_ci',
+	`errorlog` TEXT NULL COLLATE 'latin1_swedish_ci',
+	PRIMARY KEY (`id`),
+	INDEX `scheduledjobsFK` (`job_id`),
+	CONSTRAINT `scheduledjobsFK` FOREIGN KEY (`job_id`) REFERENCES `scheduled_jobs` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+
+INSERT INTO `scheduled_jobs` (`job_name`, `job_display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `trigger_key`, `job_initializing_errorlog`, `is_active`) VALUES ('Update loan Summary', 'Update loan Summary', '0 0 22 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1);
+INSERT INTO `scheduled_jobs` (`job_name`, `job_display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `trigger_key`, `job_initializing_errorlog`, `is_active`) VALUES ('Update Loan Arrears Ageing', 'Update Loan Arrears Ageing', '0 1 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1);
+INSERT INTO `scheduled_jobs` (`job_name`, `job_display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `trigger_key`, `job_initializing_errorlog`, `is_active`) VALUES ('Update Loan Paid In Advance', 'Update Loan Paid In Advance', '0 5 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1);
+INSERT INTO `scheduled_jobs` (`job_name`, `job_display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `trigger_key`, `job_initializing_errorlog`, `is_active`) VALUES ('Apply Annual Fee For Savings', 'Apply Annual Fee For Savings', '0 20 22 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1);
+INSERT INTO `scheduled_jobs` (`job_name`, `job_display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `trigger_key`, `job_initializing_errorlog`, `is_active`) VALUES ('Apply Holidays To Loans', 'Apply Holidays To Loans', '0 0 12 * * ?', now(), 5, NULL, NULL, NULL, NULL, NULL, 1);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V7__remove_read_makerchecker_permission.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V7__remove_read_makerchecker_permission.sql
new file mode 100644
index 0000000..8a507e4
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V7__remove_read_makerchecker_permission.sql
@@ -0,0 +1,14 @@
+
+/*
+Remove READ_MAKERCHECKER permission as no longer used.
+*/
+
+delete from m_role_permission
+where exists
+(select 'f' from m_permission p
+where p.id = m_role_permission.permission_id and p.code = 'READ_MAKERCHECKER');
+
+
+delete from m_permission
+where code = 'READ_MAKERCHECKER';
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V80__schedule_jobs_tables_updates.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V80__schedule_jobs_tables_updates.sql
new file mode 100644
index 0000000..19dfa51
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V80__schedule_jobs_tables_updates.sql
@@ -0,0 +1,20 @@
+RENAME TABLE `scheduled_jobs` TO `job`;
+RENAME TABLE `scheduled_job_runhistory` TO `job_run_history`;
+
+ALTER TABLE `job`
+	ALTER `job_name` DROP DEFAULT,
+	ALTER `job_display_name` DROP DEFAULT;
+ALTER TABLE `job`
+	CHANGE COLUMN `job_name` `name` VARCHAR(50) NOT NULL,
+	CHANGE COLUMN `job_display_name` `display_name` VARCHAR(50) NOT NULL;
+
+ALTER TABLE `job`
+	CHANGE COLUMN `job_initializing_errorlog` `initializing_errorlog` TEXT NULL;
+
+
+ALTER TABLE `job_run_history`
+	ALTER `triggertype` DROP DEFAULT;
+ALTER TABLE `job_run_history`
+	CHANGE COLUMN `errormessage` `error_message` VARCHAR(500) NULL DEFAULT NULL,
+	CHANGE COLUMN `triggertype` `trigger_type` VARCHAR(25) NOT NULL,
+	CHANGE COLUMN `errorlog` `error_log` TEXT NULL;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V81__savings_related_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V81__savings_related_changes.sql
new file mode 100644
index 0000000..b36b4ae
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V81__savings_related_changes.sql
@@ -0,0 +1,69 @@
+ALTER TABLE `m_note`
+ADD COLUMN `savings_account_id` BIGINT(20) NULL DEFAULT NULL  AFTER `loan_transaction_id`,
+ADD COLUMN `savings_account_transaction_id` BIGINT(20) NULL DEFAULT NULL  AFTER `savings_account_id`;
+
+ALTER TABLE `m_note`   ADD CONSTRAINT `FK_savings_account_id`  FOREIGN KEY (`savings_account_id` )  REFERENCES `m_savings_account` (`id` )  ON DELETE NO ACTION  ON UPDATE NO ACTION, ADD INDEX `FK_savings_account_id` (`savings_account_id` ASC) ;
+
+
+ALTER TABLE `m_savings_account_transaction`
+CHANGE COLUMN `is_reversed` `is_reversed` TINYINT(1) NOT NULL AFTER `transaction_type_enum`,
+CHANGE COLUMN `balance_end_date_derived` `balance_end_date_derived` DATE NULL DEFAULT NULL  AFTER `amount`,
+CHANGE COLUMN `balance_number_of_days_derived` `balance_number_of_days_derived` INT(11) NULL DEFAULT NULL AFTER `balance_end_date_derived`;
+
+ALTER TABLE `m_savings_account`
+ADD COLUMN `field_officer_id` bigint(20) DEFAULT NULL AFTER `product_id`,
+ADD COLUMN `submittedon_date` DATE NOT NULL AFTER `status_enum`,
+ADD COLUMN `submittedon_userid` bigint(20) DEFAULT NULL AFTER `submittedon_date`,
+ADD COLUMN `approvedon_date` DATE DEFAULT NULL AFTER `submittedon_userid`,
+ADD COLUMN `approvedon_userid` bigint(20) DEFAULT NULL AFTER `approvedon_date`,
+ADD COLUMN `rejectedon_date` DATE DEFAULT NULL AFTER `approvedon_userid`,
+ADD COLUMN `rejectedon_userid` bigint(20) DEFAULT NULL AFTER `rejectedon_date`,
+ADD COLUMN `withdrawnon_date` DATE DEFAULT NULL AFTER `rejectedon_userid`,
+ADD COLUMN `withdrawnon_userid` bigint(20) DEFAULT NULL AFTER `withdrawnon_date`;
+
+ALTER TABLE `m_savings_account`
+CHANGE COLUMN `activation_date` `activatedon_date` DATE NULL DEFAULT NULL AFTER `withdrawnon_userid`;
+
+ALTER TABLE `m_savings_account`
+ADD COLUMN `activatedon_userid` bigint(20) DEFAULT NULL AFTER `activatedon_date`,
+ADD COLUMN `closedon_date` DATE DEFAULT NULL AFTER `activatedon_userid`,
+ADD COLUMN `closedon_userid` bigint(20) DEFAULT NULL AFTER `closedon_date`;
+
+UPDATE `m_savings_account`
+SET
+`submittedon_date`=`activatedon_date`,
+`approvedon_date`=`activatedon_date`;
+
+
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
+VALUES
+('transaction_savings', 'APPROVE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVE', 1),
+('transaction_savings', 'REJECT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'REJECT', 1),
+('transaction_savings', 'WITHDRAW_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAW', 1),
+('transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVALUNDO', 1),
+('transaction_savings', 'CLOSE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CLOSE', 1);
+
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
+VALUES
+('transaction_savings', 'APPROVE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVE', 0),
+('transaction_savings', 'REJECT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'REJECT', 0),
+('transaction_savings', 'WITHDRAW_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAW', 0),
+('transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVALUNDO', 0),
+('transaction_savings', 'CLOSE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CLOSE', 0);
+
+
+-- Remove permissions with 'in the past' permissions
+
+DELETE FROM `m_permission` WHERE `id`='210';
+
+DELETE FROM `m_permission` WHERE `id`='212';
+DELETE FROM `m_permission` WHERE `id`='214';
+DELETE FROM `m_permission` WHERE `id`='217';
+DELETE FROM `m_permission` WHERE `id`='220';
+DELETE FROM `m_permission` WHERE `id`='233';
+DELETE FROM `m_permission` WHERE `id`='235';
+DELETE FROM `m_permission` WHERE `id`='237';
+DELETE FROM `m_permission` WHERE `id`='240';
+DELETE FROM `m_permission` WHERE `id`='243';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V82__schedule_jobs_tables_updates_for_running_status.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V82__schedule_jobs_tables_updates_for_running_status.sql
new file mode 100755
index 0000000..e540dca
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V82__schedule_jobs_tables_updates_for_running_status.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `job`
+	ADD COLUMN `currently_running` TINYINT(1) NOT NULL DEFAULT '0' AFTER `is_active`;
+
+ALTER TABLE `job`
+	CHANGE COLUMN `trigger_key` `job_key` VARCHAR(500) NULL DEFAULT NULL;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V83__non-working-days-table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V83__non-working-days-table.sql
new file mode 100644
index 0000000..475a0ee
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V83__non-working-days-table.sql
@@ -0,0 +1,11 @@
+CREATE TABLE `m_working_days` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`recurrence` VARCHAR(100) NULL DEFAULT NULL,
+	`repayment_rescheduling_enum` SMALLINT(5) NULL DEFAULT NULL,
+	PRIMARY KEY (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1;
+
+INSERT INTO `m_working_days` (`recurrence`, `repayment_rescheduling_enum`) VALUES ('FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA', 2);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V84__undo_savings_transaction_permission.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V84__undo_savings_transaction_permission.sql
new file mode 100644
index 0000000..16860a4
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V84__undo_savings_transaction_permission.sql
@@ -0,0 +1,5 @@
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
+VALUES
+('transaction_savings', 'UNDOTRANSACTION_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UNDOTRANSACTION', 1),
+('transaction_savings', 'UNDOTRANSACTION_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UNDOTRANSACTION', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V85__product_mix_related_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V85__product_mix_related_changes.sql
new file mode 100644
index 0000000..704fe22
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V85__product_mix_related_changes.sql
@@ -0,0 +1,19 @@
+ALTER TABLE `m_portfolio_command_source`
+	ADD COLUMN `product_id` BIGINT NULL DEFAULT NULL AFTER `processing_result_enum`;
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_PRODUCTMIX', 'PRODUCTMIX', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_PRODUCTMIX', 'PRODUCTMIX', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_PRODUCTMIX', 'PRODUCTMIX', 'DELETE', 0);
+
+CREATE TABLE `m_product_mix` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`product_id` BIGINT(20) NOT NULL,
+	`restricted_product_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK_m_product_mix_product_id_to_m_product_loan` (`product_id`),
+	INDEX `FK_m_product_mix_restricted_product_id_to_m_product_loan` (`restricted_product_id`),
+	CONSTRAINT `FK_m_product_mix_restricted_product_id_to_m_product_loan` FOREIGN KEY (`restricted_product_id`) REFERENCES `m_product_loan` (`id`),
+	CONSTRAINT `FK_m_product_mix_product_id_to_m_product_loan` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V86__update-working-days.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V86__update-working-days.sql
new file mode 100644
index 0000000..323b00a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V86__update-working-days.sql
@@ -0,0 +1,2 @@
+TRUNCATE `m_working_days`;
+INSERT INTO `m_working_days` (`recurrence`, `repayment_rescheduling_enum`) VALUES ('FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU', 2);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V87__add_permission_for_scheduler.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V87__add_permission_for_scheduler.sql
new file mode 100644
index 0000000..4e7d90f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V87__add_permission_for_scheduler.sql
@@ -0,0 +1 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('jobs', 'UPDATE_SCHEDULER', 'SCHEDULER', 'UPDATE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V88__added_update_constrain_for_scheduler_jobs.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V88__added_update_constrain_for_scheduler_jobs.sql
new file mode 100755
index 0000000..c73bc93
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V88__added_update_constrain_for_scheduler_jobs.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `job`
+	ADD COLUMN `updates_allowed` TINYINT(1) NOT NULL DEFAULT '1' AFTER `currently_running`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V89__added_scheduler_group.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V89__added_scheduler_group.sql
new file mode 100755
index 0000000..95f6f1c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V89__added_scheduler_group.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `job`
+	ADD COLUMN `scheduler_group` SMALLINT(2) NOT NULL DEFAULT '0' AFTER `updates_allowed`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V8__deposit-transaction-permissions-if-they-exist.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V8__deposit-transaction-permissions-if-they-exist.sql
new file mode 100644
index 0000000..db40322
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V8__deposit-transaction-permissions-if-they-exist.sql
@@ -0,0 +1,2 @@
+delete from m_permission where grouping = 'transaction_deposit';
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V90__client_performance_history_reports.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V90__client_performance_history_reports.sql
new file mode 100644
index 0000000..a6fdf5d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V90__client_performance_history_reports.sql
@@ -0,0 +1,2 @@
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('ClientSummary ', 'Table', NULL, NULL, 'SELECT x.* FROM m_client c, m_office o, \n(\n       SELECT a.loanCycle, a.activeLoans, b.lastLoanAmount, d.activeSavings, d.totalSavings FROM \n	(SELECT IFNULL(MAX(l.loan_counter),0) AS loanCycle, COUNT(l.id) AS activeLoans FROM m_loan l WHERE l.loan_status_id=300 AND l.client_id=${clientId}) a, \n	(SELECT count(l.id), IFNULL(l.principal_amount,0) AS \'lastLoanAmount\' FROM m_loan l WHERE l.client_id=${clientId} AND l.disbursedon_date = (SELECT IFNULL(MAX(disbursedon_date),NOW()) FROM m_loan where client_id=${clientId} and loan_status_id=300)) b, \n	(SELECT COUNT(s.id) AS \'activeSavings\', IFNULL(SUM(s.account_balance_derived),0) AS \'totalSavings\' FROM m_savings_account s WHERE s.status_enum=300 AND s.client_id=${clientId}) d\n) x\nWHERE c.id=${clientId} AND o.id = c.office_id AND o.hierarchy LIKE CONCAT(\'${currentUserHierarchy}\', \'%\')', 'Utility query for getting the client summary details', 1, 0);
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('LoanCyclePerProduct', 'Table', NULL, NULL, 'SELECT lp.name AS \'productName\', MAX(l.loan_product_counter) AS \'loanProductCycle\' FROM m_loan l JOIN m_product_loan lp ON l.product_id=lp.id WHERE lp.include_in_borrower_cycle=1 AND l.loan_product_counter IS NOT NULL AND l.client_id=${clientId} GROUP BY l.product_id', 'Utility query for getting the client loan cycle details', 1, 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V91_1__configuration_settings_for_holiday_and_non_workingday.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V91_1__configuration_settings_for_holiday_and_non_workingday.sql
new file mode 100644
index 0000000..0b84ea2
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V91_1__configuration_settings_for_holiday_and_non_workingday.sql
@@ -0,0 +1,2 @@
+INSERT INTO `c_configuration` (`name`, `enabled`) VALUES ('allow-transactions-on-holiday', 0);
+INSERT INTO `c_configuration` (`name`, `enabled`) VALUES ('allow-transactions-on-non_workingday', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V91__apply_annual_fees_permission.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V91__apply_annual_fees_permission.sql
new file mode 100644
index 0000000..c82bc74
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V91__apply_annual_fees_permission.sql
@@ -0,0 +1,5 @@
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
+VALUES
+('transaction_savings', 'APPLYANNUALFEE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPLYANNUALFEE', 1),
+('transaction_savings', 'APPLYANNUALFEE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPLYANNUALFEE', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V92__group_center_assign_staff_permission.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V92__group_center_assign_staff_permission.sql
new file mode 100644
index 0000000..d471067
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V92__group_center_assign_staff_permission.sql
@@ -0,0 +1,2 @@
+INSERT	INTO `m_permission` (grouping,code,`entity_name`,`action_name`,`can_maker_checker`)
+values('portfolio_group','ASSIGNSTAFF_GROUP','GROUP','ASSIGNSTAFF',0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V93__loan_transaction_external_id.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V93__loan_transaction_external_id.sql
new file mode 100644
index 0000000..e5b136c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V93__loan_transaction_external_id.sql
@@ -0,0 +1,3 @@
+ALTER TABLE m_loan_transaction
+ADD COLUMN `external_id` VARCHAR(100) NULL DEFAULT NULL  AFTER `is_reversed`  ,
+ADD UNIQUE INDEX `external_id_UNIQUE` (`external_id` ASC) ;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V94__added_savings_accont type.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V94__added_savings_accont type.sql
new file mode 100755
index 0000000..87985e7
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V94__added_savings_accont type.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `account_type_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `status_enum`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V95__batch_job_postInterest.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V95__batch_job_postInterest.sql
new file mode 100644
index 0000000..58fda9d
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V95__batch_job_postInterest.sql
@@ -0,0 +1 @@
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`) VALUES ('Post Interest For Savings', 'Post Interest For Savings', '0 0 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0);
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V96__savings_accounts_transfers_table.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V96__savings_accounts_transfers_table.sql
new file mode 100644
index 0000000..425b17b
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V96__savings_accounts_transfers_table.sql
@@ -0,0 +1,56 @@
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
+VALUES
+('transaction_savings', 'READ_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'READ', 0),
+('transaction_savings', 'CREATE_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'CREATE', 1),
+('transaction_savings', 'CREATE_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'CREATE', 0);
+
+
+DROP TABLE IF EXISTS `m_savings_account_transfer`;
+
+CREATE TABLE `m_savings_account_transfer` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) NOT NULL,
+  `to_office_id` bigint(20) NOT NULL,
+  `from_client_id` bigint(20) NOT NULL,
+  `to_client_id` bigint(20) NOT NULL,
+  `from_savings_account_id` bigint(20) DEFAULT NULL,
+  `to_savings_account_id` bigint(20) DEFAULT NULL,
+  `from_loan_account_id` bigint(20) DEFAULT NULL,
+  `to_loan_account_id` bigint(20) DEFAULT NULL,
+  `from_savings_transaction_id` bigint(20) DEFAULT NULL,
+  `from_loan_transaction_id` bigint(20) DEFAULT NULL,
+  `to_savings_transaction_id` bigint(20) DEFAULT NULL,
+  `to_loan_transaction_id` bigint(20) DEFAULT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(200) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKTRAN000000001` (`from_office_id`),
+  KEY `FKTRAN000000002` (`from_client_id`),
+  KEY `FKTRAN000000003` (`from_savings_account_id`),
+  KEY `FKTRAN000000004` (`to_office_id`),
+  KEY `FKTRAN000000005` (`to_client_id`),
+  KEY `FKTRAN000000006` (`to_savings_account_id`),
+  KEY `FKTRAN000000007` (`to_loan_account_id`),
+  KEY `FKTRAN000000008` (`from_savings_transaction_id`),
+  KEY `FKTRAN000000009` (`to_savings_transaction_id`),
+  KEY `FKTRAN000000010` (`to_loan_transaction_id`),
+  KEY `FKTRAN000000011` (`from_loan_account_id`),
+  KEY `FKTRAN000000012` (`from_loan_transaction_id`),
+  CONSTRAINT `FKTRAN000000001` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FKTRAN000000002` FOREIGN KEY (`from_client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKTRAN000000003` FOREIGN KEY (`from_savings_account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `FKTRAN000000004` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FKTRAN000000005` FOREIGN KEY (`to_client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKTRAN000000006` FOREIGN KEY (`to_savings_account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `FKTRAN000000007` FOREIGN KEY (`to_loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FKTRAN000000008` FOREIGN KEY (`from_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
+  CONSTRAINT `FKTRAN000000009` FOREIGN KEY (`to_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
+  CONSTRAINT `FKTRAN000000010` FOREIGN KEY (`to_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FKTRAN000000011` FOREIGN KEY (`from_loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FKTRAN000000012` FOREIGN KEY (`from_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V97__add_permission_for_adjust_savings_transaction.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V97__add_permission_for_adjust_savings_transaction.sql
new file mode 100644
index 0000000..906bb14
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V97__add_permission_for_adjust_savings_transaction.sql
@@ -0,0 +1 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_savings', 'ADJUSTTRANSACTION_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ADJUSTTRANSACTION', 0);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V98__added_currency_roundof_for_multipleof.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V98__added_currency_roundof_for_multipleof.sql
new file mode 100644
index 0000000..2c14cf9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V98__added_currency_roundof_for_multipleof.sql
@@ -0,0 +1,12 @@
+ALTER TABLE `m_savings_account`
+	ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
+ALTER TABLE `m_savings_product`
+	ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
+ALTER TABLE `m_loan`
+	ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
+ALTER TABLE `m_currency`
+	ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `decimal_places`;
+ALTER TABLE `m_organisation_currency`
+	ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `decimal_places`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V9__add_min_max_constraint_column_to_loan_loanproduct.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V9__add_min_max_constraint_column_to_loan_loanproduct.sql
new file mode 100644
index 0000000..ae955a2
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V9__add_min_max_constraint_column_to_loan_loanproduct.sql
@@ -0,0 +1,37 @@
+ALTER TABLE `m_product_loan`
+	ADD COLUMN `min_nominal_interest_rate_per_period` DECIMAL(19,6) NOT NULL AFTER `nominal_interest_rate_per_period`,
+	ADD COLUMN `max_nominal_interest_rate_per_period` DECIMAL(19,6) NOT NULL AFTER `min_nominal_interest_rate_per_period`,
+	ADD COLUMN `min_number_of_repayments` SMALLINT(5) NOT NULL AFTER `number_of_repayments`,
+	ADD COLUMN `max_number_of_repayments` SMALLINT(5) NOT NULL AFTER `min_number_of_repayments`;
+
+ALTER TABLE `m_loan`
+	ADD COLUMN `min_nominal_interest_rate_per_period` DECIMAL(19,6) NOT NULL AFTER `nominal_interest_rate_per_period`,
+	ADD COLUMN `max_nominal_interest_rate_per_period` DECIMAL(19,6) NOT NULL AFTER `min_nominal_interest_rate_per_period`,
+	ADD COLUMN `min_number_of_repayments` SMALLINT(5) NOT NULL AFTER `number_of_repayments`,
+	ADD COLUMN `max_number_of_repayments` SMALLINT(5) NOT NULL AFTER `min_number_of_repayments`;
+
+ALTER TABLE `m_loan`
+	ALTER `min_principal_amount` DROP DEFAULT,
+	ALTER `max_principal_amount` DROP DEFAULT;
+ALTER TABLE `m_loan`
+	CHANGE COLUMN `min_principal_amount` `min_principal_amount` DECIMAL(19,6) NULL AFTER `principal_amount`,
+	CHANGE COLUMN `max_principal_amount` `max_principal_amount` DECIMAL(19,6) NULL AFTER `min_principal_amount`,
+	CHANGE COLUMN `min_nominal_interest_rate_per_period` `min_nominal_interest_rate_per_period` DECIMAL(19,6) NULL AFTER `nominal_interest_rate_per_period`,
+	CHANGE COLUMN `max_nominal_interest_rate_per_period` `max_nominal_interest_rate_per_period` DECIMAL(19,6) NULL AFTER `min_nominal_interest_rate_per_period`,
+	CHANGE COLUMN `min_number_of_repayments` `min_number_of_repayments` SMALLINT(5) NULL AFTER `number_of_repayments`,
+	CHANGE COLUMN `max_number_of_repayments` `max_number_of_repayments` SMALLINT(5) NULL AFTER `min_number_of_repayments`;
+
+ALTER TABLE `m_product_loan`
+	ALTER `min_principal_amount` DROP DEFAULT,
+	ALTER `max_principal_amount` DROP DEFAULT,
+	ALTER `min_nominal_interest_rate_per_period` DROP DEFAULT,
+	ALTER `max_nominal_interest_rate_per_period` DROP DEFAULT,
+	ALTER `min_number_of_repayments` DROP DEFAULT,
+	ALTER `max_number_of_repayments` DROP DEFAULT;
+ALTER TABLE `m_product_loan`
+	CHANGE COLUMN `min_principal_amount` `min_principal_amount` DECIMAL(19,6) NULL AFTER `principal_amount`,
+	CHANGE COLUMN `max_principal_amount` `max_principal_amount` DECIMAL(19,6) NULL AFTER `min_principal_amount`,
+	CHANGE COLUMN `min_nominal_interest_rate_per_period` `min_nominal_interest_rate_per_period` DECIMAL(19,6) NULL AFTER `nominal_interest_rate_per_period`,
+	CHANGE COLUMN `max_nominal_interest_rate_per_period` `max_nominal_interest_rate_per_period` DECIMAL(19,6) NULL AFTER `min_nominal_interest_rate_per_period`,
+	CHANGE COLUMN `min_number_of_repayments` `min_number_of_repayments` SMALLINT(5) NULL AFTER `number_of_repayments`,
+	CHANGE COLUMN `max_number_of_repayments` `max_number_of_repayments` SMALLINT(5) NULL AFTER `min_number_of_repayments`;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql b/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql
new file mode 100644
index 0000000..922e3f3
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql
@@ -0,0 +1,89 @@
+-- MySQL dump 10.13  Distrib 5.1.60, for Win32 (ia32)
+--
+-- Host: localhost    Database: mifosplatform-tenants
+-- ------------------------------------------------------
+-- Server version	5.1.60-community
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `tenants`
+--
+
+DROP TABLE IF EXISTS `tenants`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tenants` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `identifier` varchar(100) NOT NULL,
+  `name` varchar(100) NOT NULL,
+  `schema_name` varchar(100) NOT NULL,
+  `timezone_id` varchar(100) NOT NULL,
+  `country_id` int(11) DEFAULT NULL,
+  `joined_date` date DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `schema_server` varchar(100) NOT NULL DEFAULT 'localhost',
+  `schema_server_port` varchar(10) NOT NULL DEFAULT '3306',
+  `schema_username` varchar(100) NOT NULL DEFAULT 'root',
+  `schema_password` varchar(100) NOT NULL DEFAULT 'mysql',
+  `auto_update` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `tenants`
+--
+
+LOCK TABLES `tenants` WRITE;
+/*!40000 ALTER TABLE `tenants` DISABLE KEYS */;
+INSERT INTO `tenants` VALUES (1,'default','Default Demo Tenant','mifostenant-default','Asia/Kolkata',NULL,NULL,NULL,NULL,'localhost','3306','root','mysql',1);
+/*!40000 ALTER TABLE `tenants` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `timezones`
+--
+
+DROP TABLE IF EXISTS `timezones`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `timezones` (
+  `id` int(3) NOT NULL AUTO_INCREMENT,
+  `country_code` varchar(2) NOT NULL,
+  `timezonename` varchar(100) NOT NULL,
+  `comments` varchar(150) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=416 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `timezones`
+--
+
+LOCK TABLES `timezones` WRITE;
+/*!40000 ALTER TABLE `timezones` DISABLE KEYS */;
+INSERT INTO `timezones` VALUES (1,'AD','Europe/Andorra',NULL),(2,'AE','Asia/Dubai',''),(3,'AF','Asia/Kabul',''),(4,'AG','America/Antigua',''),(5,'AI','America/Anguilla',''),(6,'AL','Europe/Tirane',''),(7,'AM','Asia/Yerevan',''),(8,'AO','Africa/Luanda',''),(9,'AQ','Antarctica/McMurdo','McMurdo Station, Ross Island'),(10,'AQ','Antarctica/South_Pole','Amundsen-Scott Station, South Pole'),(11,'AQ','Antarctica/Rothera','Rothera Station, Adelaide Island'),(12,'AQ','Antarctica/Palmer','Palmer Station, Anvers Island'),(13,'AQ','Antarctica/Mawson','Mawson Station, Holme Bay'),(14,'AQ','Antarctica/Davis','Davis Station, Vestfold Hills'),(15,'AQ','Antarctica/Casey','Casey Station, Bailey Peninsula'),(16,'AQ','Antarctica/Vostok','Vostok Station, Lake Vostok'),(17,'AQ','Antarctica/DumontDUrville','Dumont-dUrville Station, Terre Adelie'),(18,'AQ','Antarctica/Syowa','Syowa Station, E Ongul I'),(19,'AQ','Antarctica/Macquarie','Macquarie Island Station, Macquarie Island'),(20,'AR','America/Argentina/Buenos_Aires','Buenos Aires (BA, CF)'),(21,'AR','America/Argentina/Cordoba','most locations (CB, CC, CN, ER, FM, MN, SE, SF)'),(22,'AR','America/Argentina/Salta','(SA, LP, NQ, RN)'),(23,'AR','America/Argentina/Jujuy','Jujuy (JY)'),(24,'AR','America/Argentina/Tucuman','Tucuman (TM)'),(25,'AR','America/Argentina/Catamarca','Catamarca (CT), Chubut (CH)'),(26,'AR','America/Argentina/La_Rioja','La Rioja (LR)'),(27,'AR','America/Argentina/San_Juan','San Juan (SJ)'),(28,'AR','America/Argentina/Mendoza','Mendoza (MZ)'),(29,'AR','America/Argentina/San_Luis','San Luis (SL)'),(30,'AR','America/Argentina/Rio_Gallegos','Santa Cruz (SC)'),(31,'AR','America/Argentina/Ushuaia','Tierra del Fuego (TF)'),(32,'AS','Pacific/Pago_Pago',''),(33,'AT','Europe/Vienna',''),(34,'AU','Australia/Lord_Howe','Lord Howe Island'),(35,'AU','Australia/Hobart','Tasmania - most locations'),(36,'AU','Australia/Currie','Tasmania - King Island'),(37,'AU','Australia/Melbourne','Victoria'),(38,'AU','Australia/Sydney','New South Wales - most locations'),(39,'AU','Australia/Broken_Hill','New South Wales - Yancowinna'),(40,'AU','Australia/Brisbane','Queensland - most locations'),(41,'AU','Australia/Lindeman','Queensland - Holiday Islands'),(42,'AU','Australia/Adelaide','South Australia'),(43,'AU','Australia/Darwin','Northern Territory'),(44,'AU','Australia/Perth','Western Australia - most locations'),(45,'AU','Australia/Eucla','Western Australia - Eucla area'),(46,'AW','America/Aruba',''),(47,'AX','Europe/Mariehamn',''),(48,'AZ','Asia/Baku',''),(49,'BA','Europe/Sarajevo',''),(50,'BB','America/Barbados',''),(51,'BD','Asia/Dhaka',''),(52,'BE','Europe/Brussels',''),(53,'BF','Africa/Ouagadougou',''),(54,'BG','Europe/Sofia',''),(55,'BH','Asia/Bahrain',''),(56,'BI','Africa/Bujumbura',''),(57,'BJ','Africa/Porto-Novo',''),(58,'BL','America/St_Barthelemy',''),(59,'BM','Atlantic/Bermuda',''),(60,'BN','Asia/Brunei',''),(61,'BO','America/La_Paz',''),(62,'BQ','America/Kralendijk',''),(63,'BR','America/Noronha','Atlantic islands'),(64,'BR','America/Belem','Amapa, E Para'),(65,'BR','America/Fortaleza','NE Brazil (MA, PI, CE, RN, PB)'),(66,'BR','America/Recife','Pernambuco'),(67,'BR','America/Araguaina','Tocantins'),(68,'BR','America/Maceio','Alagoas, Sergipe'),(69,'BR','America/Bahia','Bahia'),(70,'BR','America/Sao_Paulo','S & SE Brazil (GO, DF, MG, ES, RJ, SP, PR, SC, RS)'),(71,'BR','America/Campo_Grande','Mato Grosso do Sul'),(72,'BR','America/Cuiaba','Mato Grosso'),(73,'BR','America/Santarem','W Para'),(74,'BR','America/Porto_Velho','Rondonia'),(75,'BR','America/Boa_Vista','Roraima'),(76,'BR','America/Manaus','E Amazonas'),(77,'BR','America/Eirunepe','W Amazonas'),(78,'BR','America/Rio_Branco','Acre'),(79,'BS','America/Nassau',''),(80,'BT','Asia/Thimphu',''),(81,'BW','Africa/Gaborone',''),(82,'BY','Europe/Minsk',''),(83,'BZ','America/Belize',''),(84,'CA','America/St_Johns','Newfoundland Time, including SE Labrador'),(85,'CA','America/Halifax','Atlantic Time - Nova Scotia (most places), PEI'),(86,'CA','America/Glace_Bay','Atlantic Time - Nova Scotia - places that did not observe DST 1966-1971'),(87,'CA','America/Moncton','Atlantic Time - New Brunswick'),(88,'CA','America/Goose_Bay','Atlantic Time - Labrador - most locations'),(89,'CA','America/Blanc-Sablon','Atlantic Standard Time - Quebec - Lower North Shore'),(90,'CA','America/Montreal','Eastern Time - Quebec - most locations'),(91,'CA','America/Toronto','Eastern Time - Ontario - most locations'),(92,'CA','America/Nipigon','Eastern Time - Ontario & Quebec - places that did not observe DST 1967-1973'),(93,'CA','America/Thunder_Bay','Eastern Time - Thunder Bay, Ontario'),(94,'CA','America/Iqaluit','Eastern Time - east Nunavut - most locations'),(95,'CA','America/Pangnirtung','Eastern Time - Pangnirtung, Nunavut'),(96,'CA','America/Resolute','Central Standard Time - Resolute, Nunavut'),(97,'CA','America/Atikokan','Eastern Standard Time - Atikokan, Ontario and Southampton I, Nunavut'),(98,'CA','America/Rankin_Inlet','Central Time - central Nunavut'),(99,'CA','America/Winnipeg','Central Time - Manitoba & west Ontario'),(100,'CA','America/Rainy_River','Central Time - Rainy River & Fort Frances, Ontario'),(101,'CA','America/Regina','Central Standard Time - Saskatchewan - most locations'),(102,'CA','America/Swift_Current','Central Standard Time - Saskatchewan - midwest'),(103,'CA','America/Edmonton','Mountain Time - Alberta, east British Columbia & west Saskatchewan'),(104,'CA','America/Cambridge_Bay','Mountain Time - west Nunavut'),(105,'CA','America/Yellowknife','Mountain Time - central Northwest Territories'),(106,'CA','America/Inuvik','Mountain Time - west Northwest Territories'),(107,'CA','America/Creston','Mountain Standard Time - Creston, British Columbia'),(108,'CA','America/Dawson_Creek','Mountain Standard Time - Dawson Creek & Fort Saint John, British Columbia'),(109,'CA','America/Vancouver','Pacific Time - west British Columbia'),(110,'CA','America/Whitehorse','Pacific Time - south Yukon'),(111,'CA','America/Dawson','Pacific Time - north Yukon'),(112,'CC','Indian/Cocos',''),(113,'CD','Africa/Kinshasa','west Dem. Rep. of Congo'),(114,'CD','Africa/Lubumbashi','east Dem. Rep. of Congo'),(115,'CF','Africa/Bangui',''),(116,'CG','Africa/Brazzaville',''),(117,'CH','Europe/Zurich',''),(118,'CI','Africa/Abidjan',''),(119,'CK','Pacific/Rarotonga',''),(120,'CL','America/Santiago','most locations'),(121,'CL','Pacific/Easter','Easter Island & Sala y Gomez'),(122,'CM','Africa/Douala',''),(123,'CN','Asia/Shanghai','east China - Beijing, Guangdong, Shanghai, etc.'),(124,'CN','Asia/Harbin','Heilongjiang (except Mohe), Jilin'),(125,'CN','Asia/Chongqing','central China - Sichuan, Yunnan, Guangxi, Shaanxi, Guizhou, etc.'),(126,'CN','Asia/Urumqi','most of Tibet & Xinjiang'),(127,'CN','Asia/Kashgar','west Tibet & Xinjiang'),(128,'CO','America/Bogota',''),(129,'CR','America/Costa_Rica',''),(130,'CU','America/Havana',''),(131,'CV','Atlantic/Cape_Verde',''),(132,'CW','America/Curacao',''),(133,'CX','Indian/Christmas',''),(134,'CY','Asia/Nicosia',''),(135,'CZ','Europe/Prague',''),(136,'DE','Europe/Berlin',''),(137,'DJ','Africa/Djibouti',''),(138,'DK','Europe/Copenhagen',''),(139,'DM','America/Dominica',''),(140,'DO','America/Santo_Domingo',''),(141,'DZ','Africa/Algiers',''),(142,'EC','America/Guayaquil','mainland'),(143,'EC','Pacific/Galapagos','Galapagos Islands'),(144,'EE','Europe/Tallinn',''),(145,'EG','Africa/Cairo',''),(146,'EH','Africa/El_Aaiun',''),(147,'ER','Africa/Asmara',''),(148,'ES','Europe/Madrid','mainland'),(149,'ES','Africa/Ceuta','Ceuta & Melilla'),(150,'ES','Atlantic/Canary','Canary Islands'),(151,'ET','Africa/Addis_Ababa',''),(152,'FI','Europe/Helsinki',''),(153,'FJ','Pacific/Fiji',''),(154,'FK','Atlantic/Stanley',''),(155,'FM','Pacific/Chuuk','Chuuk (Truk) and Yap'),(156,'FM','Pacific/Pohnpei','Pohnpei (Ponape)'),(157,'FM','Pacific/Kosrae','Kosrae'),(158,'FO','Atlantic/Faroe',''),(159,'FR','Europe/Paris',''),(160,'GA','Africa/Libreville',''),(161,'GB','Europe/London',''),(162,'GD','America/Grenada',''),(163,'GE','Asia/Tbilisi',''),(164,'GF','America/Cayenne',''),(165,'GG','Europe/Guernsey',''),(166,'GH','Africa/Accra',''),(167,'GI','Europe/Gibraltar',''),(168,'GL','America/Godthab','most locations'),(169,'GL','America/Danmarkshavn','east coast, north of Scoresbysund'),(170,'GL','America/Scoresbysund','Scoresbysund / Ittoqqortoormiit'),(171,'GL','America/Thule','Thule / Pituffik'),(172,'GM','Africa/Banjul',''),(173,'GN','Africa/Conakry',''),(174,'GP','America/Guadeloupe',''),(175,'GQ','Africa/Malabo',''),(176,'GR','Europe/Athens',''),(177,'GS','Atlantic/South_Georgia',''),(178,'GT','America/Guatemala',''),(179,'GU','Pacific/Guam',''),(180,'GW','Africa/Bissau',''),(181,'GY','America/Guyana',''),(182,'HK','Asia/Hong_Kong',''),(183,'HN','America/Tegucigalpa',''),(184,'HR','Europe/Zagreb',''),(185,'HT','America/Port-au-Prince',''),(186,'HU','Europe/Budapest',''),(187,'ID','Asia/Jakarta','Java & Sumatra'),(188,'ID','Asia/Pontianak','west & central Borneo'),(189,'ID','Asia/Makassar','east & south Borneo, Sulawesi (Celebes), Bali, Nusa Tengarra, west Timor'),(190,'ID','Asia/Jayapura','west New Guinea (Irian Jaya) & Malukus (Moluccas)'),(191,'IE','Europe/Dublin',''),(192,'IL','Asia/Jerusalem',''),(193,'IM','Europe/Isle_of_Man',''),(194,'IN','Asia/Kolkata',''),(195,'IO','Indian/Chagos',''),(196,'IQ','Asia/Baghdad',''),(197,'IR','Asia/Tehran',''),(198,'IS','Atlantic/Reykjavik',''),(199,'IT','Europe/Rome',''),(200,'JE','Europe/Jersey',''),(201,'JM','America/Jamaica',''),(202,'JO','Asia/Amman',''),(203,'JP','Asia/Tokyo',''),(204,'KE','Africa/Nairobi',''),(205,'KG','Asia/Bishkek',''),(206,'KH','Asia/Phnom_Penh',''),(207,'KI','Pacific/Tarawa','Gilbert Islands'),(208,'KI','Pacific/Enderbury','Phoenix Islands'),(209,'KI','Pacific/Kiritimati','Line Islands'),(210,'KM','Indian/Comoro',''),(211,'KN','America/St_Kitts',''),(212,'KP','Asia/Pyongyang',''),(213,'KR','Asia/Seoul',''),(214,'KW','Asia/Kuwait',''),(215,'KY','America/Cayman',''),(216,'KZ','Asia/Almaty','most locations'),(217,'KZ','Asia/Qyzylorda','Qyzylorda (Kyzylorda, Kzyl-Orda)'),(218,'KZ','Asia/Aqtobe','Aqtobe (Aktobe)'),(219,'KZ','Asia/Aqtau','Atyrau (Atirau, Guryev), Mangghystau (Mankistau)'),(220,'KZ','Asia/Oral','West Kazakhstan'),(221,'LA','Asia/Vientiane',''),(222,'LB','Asia/Beirut',''),(223,'LC','America/St_Lucia',''),(224,'LI','Europe/Vaduz',''),(225,'LK','Asia/Colombo',''),(226,'LR','Africa/Monrovia',''),(227,'LS','Africa/Maseru',''),(228,'LT','Europe/Vilnius',''),(229,'LU','Europe/Luxembourg',''),(230,'LV','Europe/Riga',''),(231,'LY','Africa/Tripoli',''),(232,'MA','Africa/Casablanca',''),(233,'MC','Europe/Monaco',''),(234,'MD','Europe/Chisinau',''),(235,'ME','Europe/Podgorica',''),(236,'MF','America/Marigot',''),(237,'MG','Indian/Antananarivo',''),(238,'MH','Pacific/Majuro','most locations'),(239,'MH','Pacific/Kwajalein','Kwajalein'),(240,'MK','Europe/Skopje',''),(241,'ML','Africa/Bamako',''),(242,'MM','Asia/Rangoon',''),(243,'MN','Asia/Ulaanbaatar','most locations'),(244,'MN','Asia/Hovd','Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan'),(245,'MN','Asia/Choibalsan','Dornod, Sukhbaatar'),(246,'MO','Asia/Macau',''),(247,'MP','Pacific/Saipan',''),(248,'MQ','America/Martinique',''),(249,'MR','Africa/Nouakchott',''),(250,'MS','America/Montserrat',''),(251,'MT','Europe/Malta',''),(252,'MU','Indian/Mauritius',''),(253,'MV','Indian/Maldives',''),(254,'MW','Africa/Blantyre',''),(255,'MX','America/Mexico_City','Central Time - most locations'),(256,'MX','America/Cancun','Central Time - Quintana Roo'),(257,'MX','America/Merida','Central Time - Campeche, Yucatan'),(258,'MX','America/Monterrey','Mexican Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas away from US border'),(259,'MX','America/Matamoros','US Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas near US border'),(260,'MX','America/Mazatlan','Mountain Time - S Baja, Nayarit, Sinaloa'),(261,'MX','America/Chihuahua','Mexican Mountain Time - Chihuahua away from US border'),(262,'MX','America/Ojinaga','US Mountain Time - Chihuahua near US border'),(263,'MX','America/Hermosillo','Mountain Standard Time - Sonora'),(264,'MX','America/Tijuana','US Pacific Time - Baja California near US border'),(265,'MX','America/Santa_Isabel','Mexican Pacific Time - Baja California away from US border'),(266,'MX','America/Bahia_Banderas','Mexican Central Time - Bahia de Banderas'),(267,'MY','Asia/Kuala_Lumpur','peninsular Malaysia'),(268,'MY','Asia/Kuching','Sabah & Sarawak'),(269,'MZ','Africa/Maputo',''),(270,'NA','Africa/Windhoek',''),(271,'NC','Pacific/Noumea',''),(272,'NE','Africa/Niamey',''),(273,'NF','Pacific/Norfolk',''),(274,'NG','Africa/Lagos',''),(275,'NI','America/Managua',''),(276,'NL','Europe/Amsterdam',''),(277,'NO','Europe/Oslo',''),(278,'NP','Asia/Kathmandu',''),(279,'NR','Pacific/Nauru',''),(280,'NU','Pacific/Niue',''),(281,'NZ','Pacific/Auckland','most locations'),(282,'NZ','Pacific/Chatham','Chatham Islands'),(283,'OM','Asia/Muscat',''),(284,'PA','America/Panama',''),(285,'PE','America/Lima',''),(286,'PF','Pacific/Tahiti','Society Islands'),(287,'PF','Pacific/Marquesas','Marquesas Islands'),(288,'PF','Pacific/Gambier','Gambier Islands'),(289,'PG','Pacific/Port_Moresby',''),(290,'PH','Asia/Manila',''),(291,'PK','Asia/Karachi',''),(292,'PL','Europe/Warsaw',''),(293,'PM','America/Miquelon',''),(294,'PN','Pacific/Pitcairn',''),(295,'PR','America/Puerto_Rico',''),(296,'PS','Asia/Gaza','Gaza Strip'),(297,'PS','Asia/Hebron','West Bank'),(298,'PT','Europe/Lisbon','mainland'),(299,'PT','Atlantic/Madeira','Madeira Islands'),(300,'PT','Atlantic/Azores','Azores'),(301,'PW','Pacific/Palau',''),(302,'PY','America/Asuncion',''),(303,'QA','Asia/Qatar',''),(304,'RE','Indian/Reunion',''),(305,'RO','Europe/Bucharest',''),(306,'RS','Europe/Belgrade',''),(307,'RU','Europe/Kaliningrad','Moscow-01 - Kaliningrad'),(308,'RU','Europe/Moscow','Moscow+00 - west Russia'),(309,'RU','Europe/Volgograd','Moscow+00 - Caspian Sea'),(310,'RU','Europe/Samara','Moscow+00 - Samara, Udmurtia'),(311,'RU','Asia/Yekaterinburg','Moscow+02 - Urals'),(312,'RU','Asia/Omsk','Moscow+03 - west Siberia'),(313,'RU','Asia/Novosibirsk','Moscow+03 - Novosibirsk'),(314,'RU','Asia/Novokuznetsk','Moscow+03 - Novokuznetsk'),(315,'RU','Asia/Krasnoyarsk','Moscow+04 - Yenisei River'),(316,'RU','Asia/Irkutsk','Moscow+05 - Lake Baikal'),(317,'RU','Asia/Yakutsk','Moscow+06 - Lena River'),(318,'RU','Asia/Vladivostok','Moscow+07 - Amur River'),(319,'RU','Asia/Sakhalin','Moscow+07 - Sakhalin Island'),(320,'RU','Asia/Magadan','Moscow+08 - Magadan'),(321,'RU','Asia/Kamchatka','Moscow+08 - Kamchatka'),(322,'RU','Asia/Anadyr','Moscow+08 - Bering Sea'),(323,'RW','Africa/Kigali',''),(324,'SA','Asia/Riyadh',''),(325,'SB','Pacific/Guadalcanal',''),(326,'SC','Indian/Mahe',''),(327,'SD','Africa/Khartoum',''),(328,'SE','Europe/Stockholm',''),(329,'SG','Asia/Singapore',''),(330,'SH','Atlantic/St_Helena',''),(331,'SI','Europe/Ljubljana',''),(332,'SJ','Arctic/Longyearbyen',''),(333,'SK','Europe/Bratislava',''),(334,'SL','Africa/Freetown',''),(335,'SM','Europe/San_Marino',''),(336,'SN','Africa/Dakar',''),(337,'SO','Africa/Mogadishu',''),(338,'SR','America/Paramaribo',''),(339,'SS','Africa/Juba',''),(340,'ST','Africa/Sao_Tome',''),(341,'SV','America/El_Salvador',''),(342,'SX','America/Lower_Princes',''),(343,'SY','Asia/Damascus',''),(344,'SZ','Africa/Mbabane',''),(345,'TC','America/Grand_Turk',''),(346,'TD','Africa/Ndjamena',''),(347,'TF','Indian/Kerguelen',''),(348,'TG','Africa/Lome',''),(349,'TH','Asia/Bangkok',''),(350,'TJ','Asia/Dushanbe',''),(351,'TK','Pacific/Fakaofo',''),(352,'TL','Asia/Dili',''),(353,'TM','Asia/Ashgabat',''),(354,'TN','Africa/Tunis',''),(355,'TO','Pacific/Tongatapu',''),(356,'TR','Europe/Istanbul',''),(357,'TT','America/Port_of_Spain',''),(358,'TV','Pacific/Funafuti',''),(359,'TW','Asia/Taipei',''),(360,'TZ','Africa/Dar_es_Salaam',''),(361,'UA','Europe/Kiev','most locations'),(362,'UA','Europe/Uzhgorod','Ruthenia'),(363,'UA','Europe/Zaporozhye','Zaporozhye, E Lugansk / Zaporizhia, E Luhansk'),(364,'UA','Europe/Simferopol','central Crimea'),(365,'UG','Africa/Kampala',''),(366,'UM','Pacific/Johnston','Johnston Atoll'),(367,'UM','Pacific/Midway','Midway Islands'),(368,'UM','Pacific/Wake','Wake Island'),(369,'US','America/New_York','Eastern Time'),(370,'US','America/Detroit','Eastern Time - Michigan - most locations'),(371,'US','America/Kentucky/Louisville','Eastern Time - Kentucky - Louisville area'),(372,'US','America/Kentucky/Monticello','Eastern Time - Kentucky - Wayne County'),(373,'US','America/Indiana/Indianapolis','Eastern Time - Indiana - most locations'),(374,'US','America/Indiana/Vincennes','Eastern Time - Indiana - Daviess, Dubois, Knox & Martin Counties'),(375,'US','America/Indiana/Winamac','Eastern Time - Indiana - Pulaski County'),(376,'US','America/Indiana/Marengo','Eastern Time - Indiana - Crawford County'),(377,'US','America/Indiana/Petersburg','Eastern Time - Indiana - Pike County'),(378,'US','America/Indiana/Vevay','Eastern Time - Indiana - Switzerland County'),(379,'US','America/Chicago','Central Time'),(380,'US','America/Indiana/Tell_City','Central Time - Indiana - Perry County'),(381,'US','America/Indiana/Knox','Central Time - Indiana - Starke County'),(382,'US','America/Menominee','Central Time - Michigan - Dickinson, Gogebic, Iron & Menominee Counties'),(383,'US','America/North_Dakota/Center','Central Time - North Dakota - Oliver County'),(384,'US','America/North_Dakota/New_Salem','Central Time - North Dakota - Morton County (except Mandan area)'),(385,'US','America/North_Dakota/Beulah','Central Time - North Dakota - Mercer County'),(386,'US','America/Denver','Mountain Time'),(387,'US','America/Boise','Mountain Time - south Idaho & east Oregon'),(388,'US','America/Shiprock','Mountain Time - Navajo'),(389,'US','America/Phoenix','Mountain Standard Time - Arizona'),(390,'US','America/Los_Angeles','Pacific Time'),(391,'US','America/Anchorage','Alaska Time'),(392,'US','America/Juneau','Alaska Time - Alaska panhandle'),(393,'US','America/Sitka','Alaska Time - southeast Alaska panhandle'),(394,'US','America/Yakutat','Alaska Time - Alaska panhandle neck'),(395,'US','America/Nome','Alaska Time - west Alaska'),(396,'US','America/Adak','Aleutian Islands'),(397,'US','America/Metlakatla','Metlakatla Time - Annette Island'),(398,'US','Pacific/Honolulu','Hawaii'),(399,'UY','America/Montevideo',''),(400,'UZ','Asia/Samarkand','west Uzbekistan'),(401,'UZ','Asia/Tashkent','east Uzbekistan'),(402,'VA','Europe/Vatican',''),(403,'VC','America/St_Vincent',''),(404,'VE','America/Caracas',''),(405,'VG','America/Tortola',''),(406,'VI','America/St_Thomas',''),(407,'VN','Asia/Ho_Chi_Minh',''),(408,'VU','Pacific/Efate',''),(409,'WF','Pacific/Wallis',''),(410,'WS','Pacific/Apia',''),(411,'YE','Asia/Aden',''),(412,'YT','Indian/Mayotte',''),(413,'ZA','Africa/Johannesburg',''),(414,'ZM','Africa/Lusaka',''),(415,'ZW','Africa/Harare','');
+/*!40000 ALTER TABLE `timezones` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-04-03 18:11:34
diff --git a/fineract-provider/src/main/resources/sql/migrations/list_db/V2__externalize-connection-properties.sql b/fineract-provider/src/main/resources/sql/migrations/list_db/V2__externalize-connection-properties.sql
new file mode 100644
index 0000000..63129a9
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/list_db/V2__externalize-connection-properties.sql
@@ -0,0 +1,14 @@
+ALTER TABLE `tenants`
+  ADD COLUMN `pool_initial_size` int(5) DEFAULT 5 AFTER `auto_update`,
+  ADD COLUMN `pool_validation_interval` int(11) DEFAULT 30000,
+  ADD COLUMN `pool_remove_abandoned` tinyint(1) DEFAULT 1,
+  ADD COLUMN `pool_remove_abandoned_timeout` int(5) DEFAULT 60,
+  ADD COLUMN `pool_log_abandoned` tinyint(1) DEFAULT 1,
+  ADD COLUMN `pool_abandon_when_percentage_full` int(5) DEFAULT 50,
+  ADD COLUMN `pool_test_on_borrow` tinyint(1) DEFAULT 1,
+  ADD COLUMN `pool_max_active` int(5) DEFAULT 40,
+  ADD COLUMN `pool_min_idle` int(5) DEFAULT 20,
+  ADD COLUMN `pool_max_idle` int(5) DEFAULT 10,
+  ADD COLUMN `pool_suspect_timeout` int(5) DEFAULT 60,
+  ADD COLUMN `pool_time_between_eviction_runs_millis` int(11) DEFAULT 34000,
+  ADD COLUMN `pool_min_evictable_idle_time_millis` int(11) DEFAULT 60000;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/list_db/V3__deadlock-retry-properties.sql b/fineract-provider/src/main/resources/sql/migrations/list_db/V3__deadlock-retry-properties.sql
new file mode 100644
index 0000000..0044f31
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/list_db/V3__deadlock-retry-properties.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `tenants`
+  ADD COLUMN `deadlock_max_retries` int(5) DEFAULT 0,
+  ADD COLUMN `deadlock_max_retry_interval` int(5) DEFAULT 1;
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/list_db/V4__introduced_oltpId_reportId_columns_and_tenants_server_connection_table.sql b/fineract-provider/src/main/resources/sql/migrations/list_db/V4__introduced_oltpId_reportId_columns_and_tenants_server_connection_table.sql
new file mode 100644
index 0000000..efd772e
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/list_db/V4__introduced_oltpId_reportId_columns_and_tenants_server_connection_table.sql
@@ -0,0 +1,62 @@
+RENAME TABLE tenants to temp_tenants;
+
+create table tenant_server_connections(`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`schema_server` VARCHAR(100) NOT NULL DEFAULT 'localhost',
+    `schema_name` VARCHAR(100) NOT NULL,
+	`schema_server_port` VARCHAR(10) NOT NULL DEFAULT '3306',
+	`schema_username` VARCHAR(100) NOT NULL DEFAULT 'root',
+	`schema_password` VARCHAR(100) NOT NULL DEFAULT 'mysql',
+	`auto_update` TINYINT(1) NOT NULL DEFAULT '1',
+	`pool_initial_size` INT(5) NULL DEFAULT '5',
+	`pool_validation_interval` INT(11) NULL DEFAULT '30000',
+	`pool_remove_abandoned` TINYINT(1) NULL DEFAULT '1',
+	`pool_remove_abandoned_timeout` INT(5) NULL DEFAULT '60',
+	`pool_log_abandoned` TINYINT(1) NULL DEFAULT '1',
+	`pool_abandon_when_percentage_full` INT(5) NULL DEFAULT '50',
+	`pool_test_on_borrow` TINYINT(1) NULL DEFAULT '1',
+	`pool_max_active` INT(5) NULL DEFAULT '40',
+	`pool_min_idle` INT(5) NULL DEFAULT '20',
+	`pool_max_idle` INT(5) NULL DEFAULT '10',
+	`pool_suspect_timeout` INT(5) NULL DEFAULT '60',
+	`pool_time_between_eviction_runs_millis` INT(11) NULL DEFAULT '34000',
+	`pool_min_evictable_idle_time_millis` INT(11) NULL DEFAULT '60000',
+	`deadlock_max_retries` INT(5) NULL DEFAULT '0',
+	`deadlock_max_retry_interval` INT(5) NULL DEFAULT '1',
+	PRIMARY KEY (`id`)
+	)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1;
+
+
+INSERT INTO `tenant_server_connections` (`id`,`schema_name`,`schema_server`, `schema_server_port`, `schema_username`, `schema_password`, `auto_update`, `pool_initial_size`, `pool_validation_interval`, `pool_remove_abandoned`, `pool_remove_abandoned_timeout`, `pool_log_abandoned`, `pool_abandon_when_percentage_full`, `pool_test_on_borrow`, `pool_max_active`, `pool_min_idle`, `pool_max_idle`, `pool_suspect_timeout`, `pool_time_between_eviction_runs_millis`, `pool_min_evictable_idle_time_millis`, `deadlock_max_retries`, `deadlock_max_retry_interval`)
+SELECT `id`,`schema_name`,`schema_server`, `schema_server_port`, `schema_username`, `schema_password`, `auto_update`, `pool_initial_size`, `pool_validation_interval`, `pool_remove_abandoned`, `pool_remove_abandoned_timeout`, `pool_log_abandoned`, `pool_abandon_when_percentage_full`, `pool_test_on_borrow`, `pool_max_active`, `pool_min_idle`, `pool_max_idle`, `pool_suspect_timeout`, `pool_time_between_eviction_runs_millis`, `pool_min_evictable_idle_time_millis`, `deadlock_max_retries`, `deadlock_max_retry_interval` from temp_tenants;
+
+	
+CREATE TABLE tenants(
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`identifier` VARCHAR(100) NOT NULL,
+	`name` VARCHAR(100) NOT NULL,
+	`timezone_id` VARCHAR(100) NOT NULL,
+	`country_id` INT(11) NULL DEFAULT NULL,
+	`joined_date` DATE NULL DEFAULT NULL,
+	`created_date` DATETIME NULL DEFAULT NULL,
+	`lastmodified_date` DATETIME NULL DEFAULT NULL,
+	`oltp_id` BIGINT(20) NOT NULL,
+	`report_id` BIGINT(20) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `fk_oltp_id` (`oltp_id`),
+	INDEX `fk_report_id` (`report_id`),
+	CONSTRAINT `fk_oltp_id` FOREIGN KEY (`oltp_id`) REFERENCES `tenant_server_connections` (`id`),
+	CONSTRAINT `fk_report_id` FOREIGN KEY (`report_id`) REFERENCES `tenant_server_connections` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+AUTO_INCREMENT=1;
+
+INSERT INTO tenants(`id`,`identifier`,`name`,`timezone_id`,`country_id`,`joined_date`,`created_date`,`lastmodified_date`,`oltp_id`, `report_id`)
+SELECT  `id`,`identifier`,`name`,`timezone_id`,`country_id`,`joined_date`,`created_date`,`lastmodified_date`,`id`, `id` from temp_tenants ;
+
+
+DROP TABLE temp_tenants;
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/sample_data/barebones_db.sql b/fineract-provider/src/main/resources/sql/migrations/sample_data/barebones_db.sql
new file mode 100644
index 0000000..a0522dd
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/sample_data/barebones_db.sql
@@ -0,0 +1,5717 @@
+-- --------------------------------------------------------
+-- Host:                         127.0.0.1
+-- Server version:               5.5.34 - MySQL Community Server (GPL)
+-- Server OS:                    Win64
+-- HeidiSQL Version:             9.3.0.4984
+-- --------------------------------------------------------
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET NAMES utf8mb4 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+
+-- Dumping structure for table mifostenant-default.acc_accounting_rule
+DROP TABLE IF EXISTS `acc_accounting_rule`;
+CREATE TABLE IF NOT EXISTS `acc_accounting_rule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `debit_account_id` bigint(20) DEFAULT NULL,
+  `allow_multiple_debits` tinyint(1) NOT NULL DEFAULT '0',
+  `credit_account_id` bigint(20) DEFAULT NULL,
+  `allow_multiple_credits` tinyint(1) NOT NULL DEFAULT '0',
+  `description` varchar(500) DEFAULT NULL,
+  `system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `accounting_rule_name_unique` (`name`),
+  KEY `FK_acc_accounting_rule_acc_gl_account_debit` (`debit_account_id`),
+  KEY `FK_acc_accounting_rule_acc_gl_account_credit` (`credit_account_id`),
+  KEY `FK_acc_accounting_rule_m_office` (`office_id`),
+  CONSTRAINT `FK_acc_accounting_rule_acc_gl_account_credit` FOREIGN KEY (`credit_account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_accounting_rule_acc_gl_account_debit` FOREIGN KEY (`debit_account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_accounting_rule_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.acc_accounting_rule: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_accounting_rule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_accounting_rule` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.acc_gl_account
+DROP TABLE IF EXISTS `acc_gl_account`;
+CREATE TABLE IF NOT EXISTS `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(200) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(50) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `tag_id` int(11) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  KEY `FKGLACC000000002` (`tag_id`),
+  CONSTRAINT `FKGLACC000000002` FOREIGN KEY (`tag_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.acc_gl_account: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_gl_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_account` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.acc_gl_closure
+DROP TABLE IF EXISTS `acc_gl_closure`;
+CREATE TABLE IF NOT EXISTS `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.acc_gl_closure: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_gl_closure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_closure` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.acc_gl_financial_activity_account
+DROP TABLE IF EXISTS `acc_gl_financial_activity_account`;
+CREATE TABLE IF NOT EXISTS `acc_gl_financial_activity_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) NOT NULL DEFAULT '0',
+  `financial_activity_type` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `financial_activity_type` (`financial_activity_type`),
+  KEY `FK_office_mapping_acc_gl_account` (`gl_account_id`),
+  CONSTRAINT `FK_office_mapping_acc_gl_account` FOREIGN KEY (`gl_account_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.acc_gl_financial_activity_account: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_gl_financial_activity_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_financial_activity_account` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.acc_gl_journal_entry
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+CREATE TABLE IF NOT EXISTS `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `savings_transaction_id` bigint(20) DEFAULT NULL,
+  `client_transaction_id` bigint(20) DEFAULT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `ref_num` varchar(100) DEFAULT NULL,
+  `manual_entry` tinyint(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type_enum` smallint(5) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  `is_running_balance_calculated` tinyint(4) NOT NULL DEFAULT '0',
+  `office_running_balance` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `organization_running_balance` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `payment_details_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  KEY `FK_acc_gl_journal_entry_m_loan_transaction` (`loan_transaction_id`),
+  KEY `FK_acc_gl_journal_entry_m_savings_account_transaction` (`savings_transaction_id`),
+  KEY `FK_acc_gl_journal_entry_m_payment_detail` (`payment_details_id`),
+  KEY `FK_acc_gl_journal_entry_m_client_transaction` (`client_transaction_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_client_transaction` FOREIGN KEY (`client_transaction_id`) REFERENCES `m_client_transaction` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_payment_detail` FOREIGN KEY (`payment_details_id`) REFERENCES `m_payment_detail` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_savings_account_transaction` FOREIGN KEY (`savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.acc_gl_journal_entry: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_gl_journal_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.acc_product_mapping
+DROP TABLE IF EXISTS `acc_product_mapping`;
+CREATE TABLE IF NOT EXISTS `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `payment_type` int(11) DEFAULT NULL,
+  `charge_id` bigint(20) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_product_mapping_m_charge` (`charge_id`),
+  KEY `FK_acc_product_mapping_m_payment_type` (`payment_type`),
+  CONSTRAINT `FK_acc_product_mapping_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `FK_acc_product_mapping_m_payment_type` FOREIGN KEY (`payment_type`) REFERENCES `m_payment_type` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.acc_product_mapping: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_product_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_product_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.acc_rule_tags
+DROP TABLE IF EXISTS `acc_rule_tags`;
+CREATE TABLE IF NOT EXISTS `acc_rule_tags` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `acc_rule_id` bigint(20) NOT NULL,
+  `tag_id` int(11) NOT NULL,
+  `acc_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UNIQUE_ACCOUNT_RULE_TAGS` (`acc_rule_id`,`tag_id`,`acc_type_enum`),
+  KEY `FK_acc_accounting_rule_id` (`acc_rule_id`),
+  KEY `FK_m_code_value_id` (`tag_id`),
+  CONSTRAINT `FK_acc_accounting_rule_id` FOREIGN KEY (`acc_rule_id`) REFERENCES `acc_accounting_rule` (`id`),
+  CONSTRAINT `FK_m_code_value_id` FOREIGN KEY (`tag_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.acc_rule_tags: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_rule_tags` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_rule_tags` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.c_account_number_format
+DROP TABLE IF EXISTS `c_account_number_format`;
+CREATE TABLE IF NOT EXISTS `c_account_number_format` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_type_enum` smallint(1) NOT NULL,
+  `prefix_type_enum` smallint(2) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_type_enum` (`account_type_enum`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.c_account_number_format: ~0 rows (approximately)
+/*!40000 ALTER TABLE `c_account_number_format` DISABLE KEYS */;
+/*!40000 ALTER TABLE `c_account_number_format` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.c_cache
+DROP TABLE IF EXISTS `c_cache`;
+CREATE TABLE IF NOT EXISTS `c_cache` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `cache_type_enum` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.c_cache: ~1 rows (approximately)
+/*!40000 ALTER TABLE `c_cache` DISABLE KEYS */;
+INSERT INTO `c_cache` (`id`, `cache_type_enum`) VALUES
+	(1, 1);
+/*!40000 ALTER TABLE `c_cache` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.c_configuration
+DROP TABLE IF EXISTS `c_configuration`;
+CREATE TABLE IF NOT EXISTS `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `value` int(11) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  `is_trap_door` tinyint(1) NOT NULL DEFAULT '0',
+  `description` varchar(300) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_UNIQUE` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.c_configuration: ~20 rows (approximately)
+/*!40000 ALTER TABLE `c_configuration` DISABLE KEYS */;
+INSERT INTO `c_configuration` (`id`, `name`, `value`, `enabled`, `is_trap_door`, `description`) VALUES
+	(1, 'maker-checker', NULL, 0, 0, NULL),
+	(4, 'amazon-S3', NULL, 0, 0, NULL),
+	(5, 'reschedule-future-repayments', NULL, 1, 0, NULL),
+	(6, 'reschedule-repayments-on-holidays', NULL, 0, 0, NULL),
+	(7, 'allow-transactions-on-holiday', NULL, 0, 0, NULL),
+	(8, 'allow-transactions-on-non_workingday', NULL, 0, 0, NULL),
+	(9, 'constraint_approach_for_datatables', NULL, 0, 0, NULL),
+	(10, 'penalty-wait-period', 2, 1, 0, NULL),
+	(11, 'force-password-reset-days', 0, 0, 0, NULL),
+	(12, 'grace-on-penalty-posting', 0, 1, 0, NULL),
+	(15, 'savings-interest-posting-current-period-end', NULL, 0, 0, 'Recommended to be changed only once during start of production. When set as false(default), interest will be posted on the first date of next period. If set as true, interest will be posted on last date of current period. There is no difference in the interest amount posted.'),
+	(16, 'financial-year-beginning-month', 1, 1, 0, 'Recommended to be changed only once during start of production. Allowed values 1 - 12 (January - December). Interest posting periods are evaluated based on this configuration.'),
+	(17, 'min-clients-in-group', 5, 0, 0, 'Minimum number of Clients that a Group should have'),
+	(18, 'max-clients-in-group', 5, 0, 0, 'Maximum number of Clients that a Group can have'),
+	(19, 'meetings-mandatory-for-jlg-loans', NULL, 0, 0, 'Enforces all JLG loans to follow a meeting schedule belonging to parent group or Center'),
+	(20, 'office-specific-products-enabled', 0, 0, 0, 'Whether products and fees should be office specific or not? This property should NOT be changed once Mifos is Live.'),
+	(21, 'restrict-products-to-user-office', 0, 0, 0, 'This should be enabled only if, products & fees are office specific (i.e. office-specific-products-enabled is enabled). This property specifies if the products should be auto-restricted to office of the user who created the proudct? Note: This property should NOT be changed once Mifos is Live.'),
+	(22, 'office-opening-balances-contra-account', 0, 1, 0, NULL),
+	(23, 'rounding-mode', 6, 1, 1, '0 - UP, 1 - DOWN, 2- CEILING, 3- FLOOR, 4- HALF_UP, 5- HALF_DOWN, 6 - HALF_EVEN'),
+	(24, 'backdate-penalties-enabled', 0, 1, 0, 'If this parameter is disabled penalties will only be added to instalments due moving forward, any old overdue instalments will not be affected.');
+/*!40000 ALTER TABLE `c_configuration` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.c_external_service
+DROP TABLE IF EXISTS `c_external_service`;
+CREATE TABLE IF NOT EXISTS `c_external_service` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_UNIQUE` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.c_external_service: ~2 rows (approximately)
+/*!40000 ALTER TABLE `c_external_service` DISABLE KEYS */;
+INSERT INTO `c_external_service` (`id`, `name`) VALUES
+	(1, 'S3'),
+	(2, 'SMTP_Email_Account');
+/*!40000 ALTER TABLE `c_external_service` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.c_external_service_properties
+DROP TABLE IF EXISTS `c_external_service_properties`;
+CREATE TABLE IF NOT EXISTS `c_external_service_properties` (
+  `name` varchar(150) NOT NULL,
+  `value` varchar(250) DEFAULT NULL,
+  `external_service_id` bigint(20) NOT NULL,
+  KEY `FK_c_external_service_properties_c_external_service` (`external_service_id`),
+  CONSTRAINT `FK_c_external_service_properties_c_external_service` FOREIGN KEY (`external_service_id`) REFERENCES `c_external_service` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.c_external_service_properties: ~8 rows (approximately)
+/*!40000 ALTER TABLE `c_external_service_properties` DISABLE KEYS */;
+INSERT INTO `c_external_service_properties` (`name`, `value`, `external_service_id`) VALUES
+	('s3_access_key', NULL, 1),
+	('s3_bucket_name', NULL, 1),
+	('s3_secret_key', NULL, 1),
+	('username', 'support@cloudmicrofinance.com', 2),
+	('password', 'support81', 2),
+	('host', 'smtp.gmail.com', 2),
+	('port', '25', 2),
+	('useTLS', 'true', 2);
+/*!40000 ALTER TABLE `c_external_service_properties` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.job
+DROP TABLE IF EXISTS `job`;
+CREATE TABLE IF NOT EXISTS `job` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) NOT NULL,
+  `display_name` varchar(50) NOT NULL,
+  `cron_expression` varchar(20) CHARACTER SET latin1 NOT NULL,
+  `create_time` datetime NOT NULL,
+  `task_priority` smallint(6) NOT NULL DEFAULT '5',
+  `group_name` varchar(50) CHARACTER SET latin1 DEFAULT NULL,
+  `previous_run_start_time` datetime DEFAULT NULL,
+  `next_run_time` datetime DEFAULT NULL,
+  `job_key` varchar(500) DEFAULT NULL,
+  `initializing_errorlog` text,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  `currently_running` tinyint(1) NOT NULL DEFAULT '0',
+  `updates_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `scheduler_group` smallint(2) NOT NULL DEFAULT '0',
+  `is_misfired` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.job: ~19 rows (approximately)
+/*!40000 ALTER TABLE `job` DISABLE KEYS */;
+INSERT INTO `job` (`id`, `name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES
+	(1, 'Update loan Summary', 'Update loan Summary', '0 0 22 1/1 * ? *', '2015-06-03 02:56:57', 5, NULL, NULL, '2016-01-20 22:00:00', 'Update loan SummaryJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(2, 'Update Loan Arrears Ageing', 'Update Loan Arrears Ageing', '0 1 0 1/1 * ? *', '2015-06-03 02:56:57', 5, NULL, NULL, '2016-01-21 00:01:00', 'Update Loan Arrears AgeingJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(3, 'Update Loan Paid In Advance', 'Update Loan Paid In Advance', '0 5 0 1/1 * ? *', '2015-06-03 02:56:57', 5, NULL, NULL, '2016-01-21 00:05:00', 'Update Loan Paid In AdvanceJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(4, 'Apply Annual Fee For Savings', 'Apply Annual Fee For Savings', '0 20 22 1/1 * ? *', '2015-06-03 02:56:57', 5, NULL, NULL, '2016-01-20 22:20:00', 'Apply Annual Fee For SavingsJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(5, 'Apply Holidays To Loans', 'Apply Holidays To Loans', '0 0 12 * * ?', '2015-06-03 02:56:57', 5, NULL, NULL, '2016-01-21 12:00:00', 'Apply Holidays To LoansJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(6, 'Post Interest For Savings', 'Post Interest For Savings', '0 0 0 1/1 * ? *', '2015-06-03 02:56:58', 5, NULL, NULL, '2016-01-21 00:00:00', 'Post Interest For SavingsJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 1, 0),
+	(7, 'Transfer Fee For Loans From Savings', 'Transfer Fee For Loans From Savings', '0 1 0 1/1 * ? *', '2015-06-03 02:57:00', 5, NULL, NULL, '2016-01-21 00:01:00', 'Transfer Fee For Loans From SavingsJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(8, 'Pay Due Savings Charges', 'Pay Due Savings Charges', '0 0 12 * * ?', '2013-09-23 00:00:00', 5, NULL, NULL, '2016-01-21 12:00:00', 'Pay Due Savings ChargesJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(9, 'Update Accounting Running Balances', 'Update Accounting Running Balances', '0 1 0 1/1 * ? *', '2015-06-03 02:57:00', 5, NULL, NULL, '2016-01-21 00:01:00', 'Update Accounting Running BalancesJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(10, 'Execute Standing Instruction', 'Execute Standing Instruction', '0 0 0 1/1 * ? *', '2015-06-03 02:57:04', 5, NULL, NULL, '2016-01-21 00:00:00', 'Execute Standing InstructionJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(11, 'Add Accrual Transactions', 'Add Accrual Transactions', '0 1 0 1/1 * ? *', '2015-06-03 02:57:04', 3, NULL, NULL, '2016-01-21 00:01:00', 'Add Accrual TransactionsJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 3, 0),
+	(12, 'Apply penalty to overdue loans', 'Apply penalty to overdue loans', '0 0 0 1/1 * ? *', '2015-06-03 02:57:04', 5, NULL, NULL, '2016-01-21 00:00:00', 'Apply penalty to overdue loansJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(13, 'Update Non Performing Assets', 'Update Non Performing Assets', '0 0 0 1/1 * ? *', '2015-06-03 02:57:04', 5, NULL, NULL, '2016-01-21 00:00:00', 'Update Non Performing AssetsJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 3, 0),
+	(14, 'Transfer Interest To Savings', 'Transfer Interest To Savings', '0 2 0 1/1 * ? *', '2015-06-03 02:57:05', 4, NULL, NULL, '2016-01-21 00:02:00', 'Transfer Interest To SavingsJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 1, 0),
+	(15, 'Update Deposit Accounts Maturity details', 'Update Deposit Accounts Maturity details', '0 0 0 1/1 * ? *', '2015-06-03 02:57:05', 5, NULL, NULL, '2016-01-21 00:00:00', 'Update Deposit Accounts Maturity detailsJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(16, 'Add Periodic Accrual Transactions', 'Add Periodic Accrual Transactions', '0 2 0 1/1 * ? *', '2015-06-03 02:57:06', 2, NULL, NULL, '2016-01-21 00:02:00', 'Add Periodic Accrual TransactionsJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 3, 0),
+	(17, 'Recalculate Interest For Loans', 'Recalculate Interest For Loans', '0 1 0 1/1 * ? *', '2015-06-03 02:57:07', 4, NULL, NULL, '2016-01-21 00:01:00', 'Recalculate Interest For LoansJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 3, 0),
+	(18, 'Generate Mandatory Savings Schedule', 'Generate Mandatory Savings Schedule', '0 5 0 1/1 * ? *', '2015-06-03 02:57:12', 5, NULL, NULL, '2016-01-21 00:05:00', 'Generate Mandatory Savings ScheduleJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(19, 'Generate Loan Loss Provisioning', 'Generate Loan Loss Provisioning', '0 0 0 1/1 * ? *', '2015-10-20 19:57:53', 5, NULL, NULL, '2016-01-21 00:00:00', 'Generate Loan Loss ProvisioningJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0);
+/*!40000 ALTER TABLE `job` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.job_run_history
+DROP TABLE IF EXISTS `job_run_history`;
+CREATE TABLE IF NOT EXISTS `job_run_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `job_id` bigint(20) NOT NULL,
+  `version` bigint(20) NOT NULL,
+  `start_time` datetime NOT NULL,
+  `end_time` datetime NOT NULL,
+  `status` varchar(10) CHARACTER SET latin1 NOT NULL,
+  `error_message` text,
+  `trigger_type` varchar(25) NOT NULL,
+  `error_log` text,
+  PRIMARY KEY (`id`),
+  KEY `scheduledjobsFK` (`job_id`),
+  CONSTRAINT `scheduledjobsFK` FOREIGN KEY (`job_id`) REFERENCES `job` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.job_run_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `job_run_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `job_run_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.mix_taxonomy
+DROP TABLE IF EXISTS `mix_taxonomy`;
+CREATE TABLE IF NOT EXISTS `mix_taxonomy` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `namespace_id` int(11) DEFAULT NULL,
+  `dimension` varchar(100) DEFAULT NULL,
+  `type` int(11) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `need_mapping` tinyint(1) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.mix_taxonomy: ~48 rows (approximately)
+/*!40000 ALTER TABLE `mix_taxonomy` DISABLE KEYS */;
+INSERT INTO `mix_taxonomy` (`id`, `name`, `namespace_id`, `dimension`, `type`, `description`, `need_mapping`) VALUES
+	(1, 'AdministrativeExpense', 1, NULL, 3, NULL, 1),
+	(2, 'Assets', 3, NULL, 1, 'All outstanding principals due for all outstanding client loans. This includes current, delinquent, and renegotiated loans, but not loans that have been written off. It does not include interest receivable.', 1),
+	(3, 'Assets', 3, 'MaturityDimension:LessThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', 1),
+	(4, 'Assets', 3, 'MaturityDimension:MoreThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', 1),
+	(5, 'CashAndCashEquivalents', 1, NULL, 1, NULL, 1),
+	(6, 'Deposits', 3, NULL, 1, 'The total value of funds placed in an account with an MFI that are payable to a depositor. This item includes any current, checking, or savings accounts that are payable on demand. It also includes time deposits which have a fixed maturity date and compulsory deposits.', 1),
+	(7, 'Deposits', 3, 'DepositProductsDimension:CompulsoryMember', 1, 'The value of deposits that an MFI\'s clients are required to  maintain as a condition of an existing or future loan.', NULL),
+	(8, 'Deposits', 3, 'DepositProductsDimension:VoluntaryMember', 1, 'The value of deposits that an MFI\'s clients are not required to  maintain as a condition of an existing or future loan.', NULL),
+	(9, 'Deposits', 3, 'LocationDimension:RuralMember', 1, 'Located in rural areas. Segmentation based on location.', NULL),
+	(10, 'Deposits', 3, 'LocationDimension:UrbanMember', 1, 'Located in urban areas. Segmentation based on location.', NULL),
+	(11, 'Deposits', 3, 'MaturityDimension:LessThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', NULL),
+	(12, 'Deposits', 3, 'MaturityDimension:MoreThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', NULL),
+	(13, 'EmployeeBenefitsExpense', 1, NULL, 3, NULL, NULL),
+	(14, 'Equity', 1, NULL, 1, NULL, NULL),
+	(15, 'Expense', 1, NULL, 3, NULL, NULL),
+	(16, 'FinancialExpense', 3, NULL, 3, 'All costs All costs incurred in raising funds from third parties, fee expenses from non-financial services, net gains (losses) due to changes in fair value of financial liabilities, impairment losses net of reversals of financial assets other than loan portfolio and net gains (losses) from restatement of financial statements in terms of the measuring unit current at the end of the reporting period.', NULL),
+	(17, 'FinancialRevenueOnLoans', 3, NULL, 2, 'Interest and non-interest income generated by the provision of credit services to the clients. Fees and commissions for late payment are also included.', NULL),
+	(18, 'ImpairmentLossAllowanceGrossLoanPortfolio', 3, NULL, 2, 'An allowance for the risk of losses in the gross loan portfolio due to default .', NULL),
+	(19, 'Liabilities', 1, NULL, 1, NULL, NULL),
+	(20, 'Liabilities', 3, 'MaturityDimension:LessThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', NULL),
+	(21, 'Liabilities', 3, 'MaturityDimension:MoreThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', NULL),
+	(22, 'LoanPortfolioGross', 3, NULL, 2, 'All outstanding principals due for all outstanding client loans. This includes current, delinquent, and renegotiated loans, but not loans that have been written off. It does not include interest receivable.', NULL),
+	(23, 'LoanPortfolioGross', 3, 'CreditProductsDimension:MicroenterpriseMember', 2, 'Loans that finance the production or trade of goods and  services for an individual\'s microenterprise, whether or not the microenterprise is legally registered. Segmentation based on loan product.', NULL),
+	(24, 'LoanPortfolioGross', 3, 'DelinquencyDimension:OneMonthOrMoreMember', 2, 'Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal  past due or renegotiated. Segmentation based on the  principal balance of all loans outstanding that have one or  more installments of principal past due or renegotiated.', NULL),
+	(25, 'LoanPortfolioGross', 3, 'DelinquencyDimension:ThreeMonthsOrMoreMember', 2, 'Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal  past due or renegotiated.? Segmentation based on the  principal balance of all loans outstanding that have one or  more installments of principal past due or renegotiated.', NULL),
+	(26, 'LoanPortfolioGross', 3, 'LocationDimension:RuralMember', 2, 'Located in rural areas. Segmentation based on geographic location.', NULL),
+	(27, 'LoanPortfolioGross', 3, 'LocationDimension:UrbanMember', 2, 'Located in urbal areas. Segmentation based on geographic location.', NULL),
+	(28, 'LoanPortfolioGross', 3, 'MaturityDimension:LessThanOneYearMember', 2, 'Segmentation based on the life of an asset or liability.', NULL),
+	(29, 'LoanPortfolioGross', 3, 'MaturityDimension:MoreThanOneYearMember', 2, 'Segmentation based on the life of an asset or liability.', NULL),
+	(30, 'NetLoanLoss', 3, '', 3, 'Referred to the value of delinquency loans written off net of any principal recovery.', NULL),
+	(31, 'NetLoanLossProvisionExpense', 3, NULL, 3, 'Represent the net value of loan portfolio impairment loss considering any reversal on impairment loss and any recovery on loans written off recognized as a income during the accounting period.', NULL),
+	(32, 'NetOperatingIncome', 3, NULL, 2, 'Total operating revenue less all expenses related to the MFI\'s core financial service operation including total financial expense, impairment loss and operating expense. Donations are excluded.', NULL),
+	(33, 'NetOperatingIncomeNetOfTaxExpense', 3, NULL, 3, 'Net operating income reported incorporating the effect of taxes. Taxes include all domestic and foreign taxes which are based on taxable profits, other taxes related to personnel, financial transactions or value-added taxes are not considered in calculation of this value.', NULL),
+	(34, 'NumberOfActiveBorrowers', 3, NULL, 0, 'The number of individuals who currently have an outstanding loan balance with the MFI or are primarily responsible for repaying any portion of the gross loan portfolio. Individuals who have multiple loans with an MFI should be counted as a single borrower.', NULL),
+	(35, 'NumberOfActiveBorrowers', 3, 'GenderDimension:FemaleMember', 0, 'The number of individuals who currently have an outstanding loan balance with the MFI or are primarily responsible for repaying any portion of the gross loan portfolio. Individuals who have multiple loans with an MFI should be counted as a single borrower.', NULL),
+	(36, 'NumberOfBoardMembers', 3, 'GenderDimension:FemaleMember', 0, 'The number of members that comprise the board of directors at the end of the reporting period who are female.', NULL),
+	(37, 'NumberOfDepositAccounts', 3, NULL, 0, 'The number of individuals who currently have funds on deposit with the MFI on a voluntary basis; i.e., they are not required to maintain the deposit account to access a loan. This number applies only to deposits held by an MFI, not to those deposits held in other institutions by the MFI\'s clients. The number should be based on the number of individuals rather than the number of groups. A single deposit account may represent multiple depositors.', NULL),
+	(38, 'NumberOfDepositors', 3, '', 0, 'The number of deposit accounts, both voluntary and compulsory, opened at the MFI whose balances the institution is liable to repay. The number should be based on the number of individual accounts rather than on the number of groups.', NULL),
+	(39, 'NumberOfEmployees', 3, NULL, 0, 'The number of individuals who are actively employed by an entity. This number includes contract employees or advisors who dedicate a substantial portion of their time to the entity, even if they are not on the entity\'s employees roster.', NULL),
+	(40, 'NumberOfEmployees', 3, 'GenderDimension:FemaleMember', 0, 'The number of individuals who are actively employed by an entity. This number includes contract employees or advisors who dedicate a substantial portion of their time to the entity, even if they are not on the entity\'s employees roster.', NULL),
+	(41, 'NumberOfLoanOfficers', 3, NULL, 0, 'The number of employees whose main activity is to manage a portion of the gross loan portfolio. A loan officer is a staff member of record who is directly responsible for arranging and monitoring client loans.', NULL),
+	(42, 'NumberOfLoanOfficers', 3, 'GenderDimension:FemaleMember', 0, 'The number of employees whose main activity is to manage a portion of the gross loan portfolio. A loan officer is a staff member of record who is directly responsible for arranging and monitoring client loans.', NULL),
+	(43, 'NumberOfManagers', 3, 'GenderDimension:FemaleMember', 0, 'The number of members that comprise the management of the institution who are female.', NULL),
+	(44, 'NumberOfOffices', 3, NULL, 0, 'The number of staffed points of service and administrative sites used to deliver or support the delivery of financial services to microfinance clients.', NULL),
+	(45, 'NumberOfOutstandingLoans', 3, NULL, 0, 'The number of loans in the gross loan portfolio. For MFIs using a group lending methodology, the number of loans should refer to the number of individuals receiving loans as part of a group or as part of a group loan.', NULL),
+	(46, 'OperatingExpense', 3, NULL, 3, 'Includes expenses not related to financial and credit loss impairment, such as personnel expenses, depreciation, amortization and administrative expenses.', NULL),
+	(47, 'OperatingIncome', 3, NULL, 2, 'Includes all financial income and other operating revenue which is generated from non-financial services. Operating income also includes net gains (losses) from holding financial assets (changes on their values during the period and foreign exchange differences). Donations or any revenue not related with an MFI\'s core business of making loans and providing financial services are not considered under this category.', NULL),
+	(48, 'WriteOffsOnGrossLoanPortfolio', 3, NULL, 2, 'The value of loans that have been recognized as uncollectible for accounting purposes. A write-off is an accounting procedure that removes the outstanding balance of the loan from the gross loan portfolio and impairment loss allowance. Thus, the write-off does not affect the net loan portfolio, total assets, or any equity account. If the impairment loss allowance is insufficient to cover the amount written off, the excess amount will result in an additional impairment loss on loans recognised in profit or loss of the period.', NULL);
+/*!40000 ALTER TABLE `mix_taxonomy` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.mix_taxonomy_mapping
+DROP TABLE IF EXISTS `mix_taxonomy_mapping`;
+CREATE TABLE IF NOT EXISTS `mix_taxonomy_mapping` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `identifier` varchar(50) NOT NULL DEFAULT '',
+  `config` varchar(200) DEFAULT NULL,
+  `last_update_date` datetime DEFAULT NULL,
+  `currency` varchar(11) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.mix_taxonomy_mapping: ~1 rows (approximately)
+/*!40000 ALTER TABLE `mix_taxonomy_mapping` DISABLE KEYS */;
+INSERT INTO `mix_taxonomy_mapping` (`id`, `identifier`, `config`, `last_update_date`, `currency`) VALUES
+	(1, 'default', NULL, NULL, '');
+/*!40000 ALTER TABLE `mix_taxonomy_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.mix_xbrl_namespace
+DROP TABLE IF EXISTS `mix_xbrl_namespace`;
+CREATE TABLE IF NOT EXISTS `mix_xbrl_namespace` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `prefix` varchar(20) NOT NULL DEFAULT '',
+  `url` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UNQUE` (`prefix`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.mix_xbrl_namespace: ~7 rows (approximately)
+/*!40000 ALTER TABLE `mix_xbrl_namespace` DISABLE KEYS */;
+INSERT INTO `mix_xbrl_namespace` (`id`, `prefix`, `url`) VALUES
+	(1, 'ifrs', 'http://xbrl.iasb.org/taxonomy/2009-04-01/ifrs'),
+	(2, 'iso4217', 'http://www.xbrl.org/2003/iso4217'),
+	(3, 'mix', 'http://www.themix.org/int/fr/ifrs/basi/YYYY-MM-DD/mx-cor'),
+	(4, 'xbrldi', 'http://xbrl.org/2006/xbrldi'),
+	(5, 'xbrli', 'http://www.xbrl.org/2003/instance'),
+	(6, 'link', 'http://www.xbrl.org/2003/linkbase'),
+	(7, 'dc-all', 'http://www.themix.org/int/fr/ifrs/basi/2010-08-31/dc-all');
+/*!40000 ALTER TABLE `mix_xbrl_namespace` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_account_transfer_details
+DROP TABLE IF EXISTS `m_account_transfer_details`;
+CREATE TABLE IF NOT EXISTS `m_account_transfer_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) NOT NULL,
+  `to_office_id` bigint(20) NOT NULL,
+  `from_client_id` bigint(20) DEFAULT NULL,
+  `to_client_id` bigint(20) DEFAULT NULL,
+  `from_savings_account_id` bigint(20) DEFAULT NULL,
+  `to_savings_account_id` bigint(20) DEFAULT NULL,
+  `from_loan_account_id` bigint(20) DEFAULT NULL,
+  `to_loan_account_id` bigint(20) DEFAULT NULL,
+  `transfer_type` smallint(2) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_account_transfer_details_from_office` (`from_office_id`),
+  KEY `FK_m_account_transfer_details_to_office` (`to_office_id`),
+  KEY `FK_m_account_transfer_details_from_client` (`from_client_id`),
+  KEY `FK_m_account_transfer_details_to_client` (`to_client_id`),
+  KEY `FK_m_account_transfer_details_from_savings_account` (`from_savings_account_id`),
+  KEY `FK_m_account_transfer_details_to_savings_account` (`to_savings_account_id`),
+  KEY `FK_m_account_transfer_details_from_loan_account` (`from_loan_account_id`),
+  KEY `FK_m_account_transfer_details_to_loan_account` (`to_loan_account_id`),
+  CONSTRAINT `FK_m_account_transfer_details_from_client` FOREIGN KEY (`from_client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_from_loan_account` FOREIGN KEY (`from_loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_from_office` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_from_savings_account` FOREIGN KEY (`from_savings_account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_to_client` FOREIGN KEY (`to_client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_to_loan_account` FOREIGN KEY (`to_loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_to_office` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_to_savings_account` FOREIGN KEY (`to_savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_account_transfer_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_account_transfer_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_account_transfer_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_account_transfer_standing_instructions
+DROP TABLE IF EXISTS `m_account_transfer_standing_instructions`;
+CREATE TABLE IF NOT EXISTS `m_account_transfer_standing_instructions` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(250) NOT NULL,
+  `account_transfer_details_id` bigint(20) NOT NULL,
+  `priority` tinyint(2) NOT NULL,
+  `status` tinyint(2) NOT NULL,
+  `instruction_type` tinyint(2) NOT NULL,
+  `amount` decimal(19,6) DEFAULT NULL,
+  `valid_from` date NOT NULL,
+  `valid_till` date DEFAULT NULL,
+  `recurrence_type` tinyint(1) NOT NULL,
+  `recurrence_frequency` smallint(5) DEFAULT NULL,
+  `recurrence_interval` smallint(5) DEFAULT NULL,
+  `recurrence_on_day` smallint(2) DEFAULT NULL,
+  `recurrence_on_month` smallint(2) DEFAULT NULL,
+  `last_run_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`),
+  KEY `FK_m_standing_instructions_account_transfer_details` (`account_transfer_details_id`),
+  CONSTRAINT `FK_m_standing_instructions_account_transfer_details` FOREIGN KEY (`account_transfer_details_id`) REFERENCES `m_account_transfer_details` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_account_transfer_standing_instructions: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_account_transfer_standing_instructions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_account_transfer_standing_instructions` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_account_transfer_standing_instructions_history
+DROP TABLE IF EXISTS `m_account_transfer_standing_instructions_history`;
+CREATE TABLE IF NOT EXISTS `m_account_transfer_standing_instructions_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `standing_instruction_id` bigint(20) NOT NULL,
+  `status` varchar(20) NOT NULL,
+  `execution_time` datetime NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `error_log` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_account_transfer_standing_instructions_history` (`standing_instruction_id`),
+  CONSTRAINT `FK_m_account_transfer_standing_instructions_m_history` FOREIGN KEY (`standing_instruction_id`) REFERENCES `m_account_transfer_standing_instructions` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_account_transfer_standing_instructions_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_account_transfer_standing_instructions_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_account_transfer_standing_instructions_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_account_transfer_transaction
+DROP TABLE IF EXISTS `m_account_transfer_transaction`;
+CREATE TABLE IF NOT EXISTS `m_account_transfer_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_transfer_details_id` bigint(20) NOT NULL,
+  `from_savings_transaction_id` bigint(20) DEFAULT NULL,
+  `from_loan_transaction_id` bigint(20) DEFAULT NULL,
+  `to_savings_transaction_id` bigint(20) DEFAULT NULL,
+  `to_loan_transaction_id` bigint(20) DEFAULT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(200) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_account_transfer_transaction_from_m_savings_transaction` (`from_savings_transaction_id`),
+  KEY `FK_m_account_transfer_transaction_to_m_savings_transaction` (`to_savings_transaction_id`),
+  KEY `FK_m_account_transfer_transaction_to_m_loan_transaction` (`to_loan_transaction_id`),
+  KEY `FK_m_account_transfer_transaction_from_m_loan_transaction` (`from_loan_transaction_id`),
+  KEY `FK_m_account_transfer_transaction_account_detail` (`account_transfer_details_id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_account_detail` FOREIGN KEY (`account_transfer_details_id`) REFERENCES `m_account_transfer_details` (`id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_from_m_loan_transaction` FOREIGN KEY (`from_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_from_m_savings_transaction` FOREIGN KEY (`from_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_to_m_loan_transaction` FOREIGN KEY (`to_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_to_m_savings_transaction` FOREIGN KEY (`to_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_account_transfer_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_account_transfer_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_account_transfer_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_appuser
+DROP TABLE IF EXISTS `m_appuser`;
+CREATE TABLE IF NOT EXISTS `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  `last_time_password_updated` date NOT NULL,
+  `password_never_expires` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'define if the password, should be check for validity period or not',
+  `is_self_service_user` bit(1) NOT NULL DEFAULT b'0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  KEY `fk_m_appuser_002x` (`staff_id`),
+  KEY `last_time_password_updated` (`last_time_password_updated`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `fk_m_appuser_002` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_appuser: ~2 rows (approximately)
+/*!40000 ALTER TABLE `m_appuser` DISABLE KEYS */;
+INSERT INTO `m_appuser` (`id`, `is_deleted`, `office_id`, `staff_id`, `username`, `firstname`, `lastname`, `password`, `email`, `firsttime_login_remaining`, `nonexpired`, `nonlocked`, `nonexpired_credentials`, `enabled`, `last_time_password_updated`, `password_never_expires`, `is_self_service_user`) VALUES
+	(1, 0, 1, NULL, 'mifos', 'App', 'Administrator', '5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a', 'demomfi@mifos.org', b'0', b'1', b'1', b'1', b'1', '2015-06-03', 0, b'0'),
+	(2, 0, 1, NULL, 'system', 'system', 'system', '5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a', 'demomfi@mifos.org', b'0', b'1', b'1', b'1', b'1', '2014-03-07', 0, b'0');
+/*!40000 ALTER TABLE `m_appuser` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_appuser_previous_password
+DROP TABLE IF EXISTS `m_appuser_previous_password`;
+CREATE TABLE IF NOT EXISTS `m_appuser_previous_password` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `user_id` bigint(20) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `removal_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `user_id` (`user_id`),
+  CONSTRAINT `m_appuser_previous_password_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-default.m_appuser_previous_password: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_appuser_previous_password` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_appuser_previous_password` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_appuser_role
+DROP TABLE IF EXISTS `m_appuser_role`;
+CREATE TABLE IF NOT EXISTS `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_appuser_role: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_appuser_role` DISABLE KEYS */;
+INSERT INTO `m_appuser_role` (`appuser_id`, `role_id`) VALUES
+	(1, 1);
+/*!40000 ALTER TABLE `m_appuser_role` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_calendar
+DROP TABLE IF EXISTS `m_calendar`;
+CREATE TABLE IF NOT EXISTS `m_calendar` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `title` varchar(70) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_calendar: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_calendar` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_calendar_history
+DROP TABLE IF EXISTS `m_calendar_history`;
+CREATE TABLE IF NOT EXISTS `m_calendar_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `title` varchar(70) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_history` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_history` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_calendar_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_calendar_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_calendar_instance
+DROP TABLE IF EXISTS `m_calendar_instance`;
+CREATE TABLE IF NOT EXISTS `m_calendar_instance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `entity_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_calendar_instance: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_calendar_instance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar_instance` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_cashiers
+DROP TABLE IF EXISTS `m_cashiers`;
+CREATE TABLE IF NOT EXISTS `m_cashiers` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `teller_id` bigint(20) DEFAULT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `start_date` date DEFAULT NULL,
+  `end_date` date DEFAULT NULL,
+  `start_time` varchar(10) DEFAULT NULL,
+  `end_time` varchar(10) DEFAULT NULL,
+  `full_day` tinyint(4) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `IK_m_cashiers_m_staff` (`staff_id`),
+  KEY `IK_m_cashiers_m_teller` (`teller_id`),
+  CONSTRAINT `FK_m_cashiers_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_m_cashiers_m_teller` FOREIGN KEY (`teller_id`) REFERENCES `m_tellers` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_cashiers: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_cashiers` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_cashiers` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_cashier_transactions
+DROP TABLE IF EXISTS `m_cashier_transactions`;
+CREATE TABLE IF NOT EXISTS `m_cashier_transactions` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `cashier_id` bigint(20) NOT NULL,
+  `txn_type` smallint(5) NOT NULL,
+  `txn_amount` decimal(19,6) NOT NULL,
+  `txn_date` date NOT NULL,
+  `created_date` datetime NOT NULL,
+  `entity_type` varchar(50) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `txn_note` varchar(200) DEFAULT NULL,
+  `currency_code` varchar(3) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `IK_m_teller_transactions_m_cashier` (`cashier_id`),
+  CONSTRAINT `FK_m_teller_transactions_m_cashiers` FOREIGN KEY (`cashier_id`) REFERENCES `m_cashiers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_cashier_transactions: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_cashier_transactions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_cashier_transactions` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_charge
+DROP TABLE IF EXISTS `m_charge`;
+CREATE TABLE IF NOT EXISTS `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `charge_payment_mode_enum` smallint(5) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `fee_on_day` smallint(5) DEFAULT NULL,
+  `fee_interval` smallint(5) DEFAULT NULL,
+  `fee_on_month` smallint(5) DEFAULT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `min_cap` decimal(19,6) DEFAULT NULL,
+  `max_cap` decimal(19,6) DEFAULT NULL,
+  `fee_frequency` smallint(5) DEFAULT NULL,
+  `income_or_liability_account_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`),
+  KEY `FK_m_charge_acc_gl_account` (`income_or_liability_account_id`),
+  CONSTRAINT `FK_m_charge_acc_gl_account` FOREIGN KEY (`income_or_liability_account_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_client
+DROP TABLE IF EXISTS `m_client`;
+CREATE TABLE IF NOT EXISTS `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '300',
+  `sub_status` int(11) DEFAULT NULL,
+  `activation_date` date DEFAULT NULL,
+  `office_joining_date` date DEFAULT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `transfer_to_office_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `mobile_no` varchar(50) DEFAULT NULL,
+  `gender_cv_id` int(11) DEFAULT NULL,
+  `date_of_birth` date DEFAULT NULL,
+  `image_id` bigint(20) DEFAULT NULL,
+  `closure_reason_cv_id` int(11) DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `updated_by` bigint(20) DEFAULT NULL,
+  `updated_on` date DEFAULT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `activatedon_userid` bigint(20) DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `default_savings_product` bigint(20) DEFAULT NULL,
+  `default_savings_account` bigint(20) DEFAULT NULL,
+  `client_type_cv_id` int(11) DEFAULT NULL,
+  `client_classification_cv_id` int(11) DEFAULT NULL,
+  `reject_reason_cv_id` int(11) DEFAULT NULL,
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `withdraw_reason_cv_id` int(11) DEFAULT NULL,
+  `withdrawn_on_date` date DEFAULT NULL,
+  `withdraw_on_userid` bigint(20) DEFAULT NULL,
+  `reactivated_on_date` date DEFAULT NULL,
+  `reactivated_on_userid` bigint(20) DEFAULT NULL,
+  `legal_form_enum` int(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  UNIQUE KEY `mobile_no_UNIQUE` (`mobile_no`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  KEY `FK_m_client_m_image` (`image_id`),
+  KEY `client_staff_id` (`staff_id`),
+  KEY `FK_m_client_m_code` (`closure_reason_cv_id`),
+  KEY `FK_m_client_m_office` (`transfer_to_office_id`),
+  KEY `FK_m_client_m_savings_product` (`default_savings_product`),
+  KEY `FK_m_client_m_savings_account` (`default_savings_account`),
+  KEY `FK_m_client_type_m_code_value` (`client_type_cv_id`),
+  KEY `FK_m_client_classification_m_code_value` (`client_classification_cv_id`),
+  KEY `FK1_m_client_gender_m_code_value` (`gender_cv_id`),
+  KEY `FK_m_client_substatus_m_code_value` (`sub_status`),
+  KEY `FK_m_client_type_mcode_value_reject` (`reject_reason_cv_id`),
+  KEY `FK_m_client_type_m_code_value_withdraw` (`withdraw_reason_cv_id`),
+  CONSTRAINT `FK1_m_client_gender_m_code_value` FOREIGN KEY (`gender_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_client_classification_m_code_value` FOREIGN KEY (`client_classification_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_m_code` FOREIGN KEY (`closure_reason_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_m_image` FOREIGN KEY (`image_id`) REFERENCES `m_image` (`id`),
+  CONSTRAINT `FK_m_client_m_office` FOREIGN KEY (`transfer_to_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_client_m_savings_account` FOREIGN KEY (`default_savings_account`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `FK_m_client_m_savings_product` FOREIGN KEY (`default_savings_product`) REFERENCES `m_savings_product` (`id`),
+  CONSTRAINT `FK_m_client_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_m_client_substatus_m_code_value` FOREIGN KEY (`sub_status`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_type_mcode_value_reject` FOREIGN KEY (`reject_reason_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_type_m_code_value` FOREIGN KEY (`client_type_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_type_m_code_value_withdraw` FOREIGN KEY (`withdraw_reason_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_client: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_client_attendance
+DROP TABLE IF EXISTS `m_client_attendance`;
+CREATE TABLE IF NOT EXISTS `m_client_attendance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL DEFAULT '0',
+  `meeting_id` bigint(20) NOT NULL,
+  `attendance_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_client_meeting_attendance` (`client_id`,`meeting_id`),
+  KEY `FK_m_meeting_m_client_attendance` (`meeting_id`),
+  CONSTRAINT `FK_m_client_m_client_attendance` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_meeting_m_client_attendance` FOREIGN KEY (`meeting_id`) REFERENCES `m_meeting` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_client_attendance: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_attendance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_attendance` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_client_charge
+DROP TABLE IF EXISTS `m_client_charge`;
+CREATE TABLE IF NOT EXISTS `m_client_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_due_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL,
+  `is_paid_derived` tinyint(1) DEFAULT NULL,
+  `waived` tinyint(1) DEFAULT NULL,
+  `is_active` tinyint(1) DEFAULT NULL,
+  `inactivated_on_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_client_charge_m_client` (`client_id`),
+  KEY `FK_m_client_charge_m_charge` (`charge_id`),
+  CONSTRAINT `FK_m_client_charge_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `FK_m_client_charge_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_client_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_client_charge_paid_by
+DROP TABLE IF EXISTS `m_client_charge_paid_by`;
+CREATE TABLE IF NOT EXISTS `m_client_charge_paid_by` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_transaction_id` bigint(20) NOT NULL,
+  `client_charge_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_client_charge_paid_by_m_client_transaction` (`client_transaction_id`),
+  KEY `FK_m_client_charge_paid_by_m_client_charge` (`client_charge_id`),
+  CONSTRAINT `FK_m_client_charge_paid_by_m_client_charge` FOREIGN KEY (`client_charge_id`) REFERENCES `m_client_charge` (`id`),
+  CONSTRAINT `FK_m_client_charge_paid_by_m_client_transaction` FOREIGN KEY (`client_transaction_id`) REFERENCES `m_client_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_client_charge_paid_by: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_charge_paid_by` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_charge_paid_by` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_client_identifier
+DROP TABLE IF EXISTS `m_client_identifier`;
+CREATE TABLE IF NOT EXISTS `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_client_identifier: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_identifier` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_identifier` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_client_non_person
+DROP TABLE IF EXISTS `m_client_non_person`;
+CREATE TABLE IF NOT EXISTS `m_client_non_person` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `constitution_cv_id` int(11) NOT NULL,
+  `incorp_no` varchar(50) DEFAULT NULL,
+  `incorp_validity_till` datetime DEFAULT NULL,
+  `main_business_line_cv_id` int(11) DEFAULT NULL,
+  `remarks` varchar(150) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `client_id` (`client_id`),
+  KEY `FK_client_id` (`client_id`),
+  CONSTRAINT `FK_client_id` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_client_non_person: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_non_person` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_non_person` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_client_transaction
+DROP TABLE IF EXISTS `m_client_transaction`;
+CREATE TABLE IF NOT EXISTS `m_client_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `payment_detail_id` bigint(20) DEFAULT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `external_id` varchar(50) DEFAULT NULL,
+  `transaction_date` date NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `appuser_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FK_m_client_transaction_m_client` (`client_id`),
+  KEY `FK_m_client_transaction_m_appuser` (`appuser_id`),
+  CONSTRAINT `FK_m_client_transaction_m_appuser` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_client_transaction_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_client_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_code
+DROP TABLE IF EXISTS `m_code`;
+CREATE TABLE IF NOT EXISTS `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_code: ~24 rows (approximately)
+/*!40000 ALTER TABLE `m_code` DISABLE KEYS */;
+INSERT INTO `m_code` (`id`, `code_name`, `is_system_defined`) VALUES
+	(1, 'Customer Identifier', 1),
+	(2, 'LoanCollateral', 1),
+	(3, 'LoanPurpose', 1),
+	(4, 'Gender', 1),
+	(5, 'YesNo', 1),
+	(6, 'GuarantorRelationship', 1),
+	(7, 'AssetAccountTags', 1),
+	(8, 'LiabilityAccountTags', 1),
+	(9, 'EquityAccountTags', 1),
+	(10, 'IncomeAccountTags', 1),
+	(11, 'ExpenseAccountTags', 1),
+	(13, 'GROUPROLE', 1),
+	(14, 'ClientClosureReason', 1),
+	(15, 'GroupClosureReason', 1),
+	(16, 'ClientType', 1),
+	(17, 'ClientClassification', 1),
+	(18, 'ClientSubStatus', 1),
+	(19, 'ClientRejectReason', 1),
+	(20, 'ClientWithdrawReason', 1),
+	(21, 'Entity to Entity Access Types', 1),
+	(22, 'CenterClosureReason', 1),
+	(23, 'LoanRescheduleReason', 1),
+	(24, 'Constitution', 1),
+	(25, 'Main Business Line', 1);
+/*!40000 ALTER TABLE `m_code` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_code_value
+DROP TABLE IF EXISTS `m_code_value`;
+CREATE TABLE IF NOT EXISTS `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `code_description` varchar(500) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  `code_score` int(11) DEFAULT NULL,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_code_value: ~12 rows (approximately)
+/*!40000 ALTER TABLE `m_code_value` DISABLE KEYS */;
+INSERT INTO `m_code_value` (`id`, `code_id`, `code_value`, `code_description`, `order_position`, `code_score`, `is_active`) VALUES
+	(1, 1, 'Passport', NULL, 1, NULL, 1),
+	(2, 1, 'Id', NULL, 2, NULL, 1),
+	(3, 1, 'Drivers License', NULL, 3, NULL, 1),
+	(4, 1, 'Any Other Id Type', NULL, 4, NULL, 1),
+	(5, 6, 'Spouse', NULL, 0, NULL, 1),
+	(6, 6, 'Parent', NULL, 0, NULL, 1),
+	(7, 6, 'Sibling', NULL, 0, NULL, 1),
+	(8, 6, 'Business Associate', NULL, 0, NULL, 1),
+	(9, 6, 'Other', NULL, 0, NULL, 1),
+	(10, 21, 'Office Access to Loan Products', NULL, 0, NULL, 1),
+	(11, 21, 'Office Access to Savings Products', NULL, 0, NULL, 1),
+	(12, 21, 'Office Access to Fees/Charges', NULL, 0, NULL, 1);
+/*!40000 ALTER TABLE `m_code_value` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_currency
+DROP TABLE IF EXISTS `m_currency`;
+CREATE TABLE IF NOT EXISTS `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_currency: ~163 rows (approximately)
+/*!40000 ALTER TABLE `m_currency` DISABLE KEYS */;
+INSERT INTO `m_currency` (`id`, `code`, `decimal_places`, `currency_multiplesof`, `display_symbol`, `name`, `internationalized_name_code`) VALUES
+	(1, 'AED', 2, NULL, NULL, 'UAE Dirham', 'currency.AED'),
+	(2, 'AFN', 2, NULL, NULL, 'Afghanistan Afghani', 'currency.AFN'),
+	(3, 'ALL', 2, NULL, NULL, 'Albanian Lek', 'currency.ALL'),
+	(4, 'AMD', 2, NULL, NULL, 'Armenian Dram', 'currency.AMD'),
+	(5, 'ANG', 2, NULL, NULL, 'Netherlands Antillian Guilder', 'currency.ANG'),
+	(6, 'AOA', 2, NULL, NULL, 'Angolan Kwanza', 'currency.AOA'),
+	(7, 'ARS', 2, NULL, '$', 'Argentine Peso', 'currency.ARS'),
+	(8, 'AUD', 2, NULL, 'A$', 'Australian Dollar', 'currency.AUD'),
+	(9, 'AWG', 2, NULL, NULL, 'Aruban Guilder', 'currency.AWG'),
+	(10, 'AZM', 2, NULL, NULL, 'Azerbaijanian Manat', 'currency.AZM'),
+	(11, 'BAM', 2, NULL, NULL, 'Bosnia and Herzegovina Convertible Marks', 'currency.BAM'),
+	(12, 'BBD', 2, NULL, NULL, 'Barbados Dollar', 'currency.BBD'),
+	(13, 'BDT', 2, NULL, NULL, 'Bangladesh Taka', 'currency.BDT'),
+	(14, 'BGN', 2, NULL, NULL, 'Bulgarian Lev', 'currency.BGN'),
+	(15, 'BHD', 3, NULL, NULL, 'Bahraini Dinar', 'currency.BHD'),
+	(16, 'BIF', 0, NULL, NULL, 'Burundi Franc', 'currency.BIF'),
+	(17, 'BMD', 2, NULL, NULL, 'Bermudian Dollar', 'currency.BMD'),
+	(18, 'BND', 2, NULL, 'B$', 'Brunei Dollar', 'currency.BND'),
+	(19, 'BOB', 2, NULL, 'Bs.', 'Bolivian Boliviano', 'currency.BOB'),
+	(20, 'BRL', 2, NULL, 'R$', 'Brazilian Real', 'currency.BRL'),
+	(21, 'BSD', 2, NULL, NULL, 'Bahamian Dollar', 'currency.BSD'),
+	(22, 'BTN', 2, NULL, NULL, 'Bhutan Ngultrum', 'currency.BTN'),
+	(23, 'BWP', 2, NULL, NULL, 'Botswana Pula', 'currency.BWP'),
+	(24, 'BYR', 0, NULL, NULL, 'Belarussian Ruble', 'currency.BYR'),
+	(25, 'BZD', 2, NULL, 'BZ$', 'Belize Dollar', 'currency.BZD'),
+	(26, 'CAD', 2, NULL, NULL, 'Canadian Dollar', 'currency.CAD'),
+	(27, 'CDF', 2, NULL, NULL, 'Franc Congolais', 'currency.CDF'),
+	(28, 'CHF', 2, NULL, NULL, 'Swiss Franc', 'currency.CHF'),
+	(29, 'CLP', 0, NULL, '$', 'Chilean Peso', 'currency.CLP'),
+	(30, 'CNY', 2, NULL, NULL, 'Chinese Yuan Renminbi', 'currency.CNY'),
+	(31, 'COP', 2, NULL, '$', 'Colombian Peso', 'currency.COP'),
+	(32, 'CRC', 2, NULL, '₡', 'Costa Rican Colon', 'currency.CRC'),
+	(33, 'CSD', 2, NULL, NULL, 'Serbian Dinar', 'currency.CSD'),
+	(34, 'CUP', 2, NULL, '$MN', 'Cuban Peso', 'currency.CUP'),
+	(35, 'CVE', 2, NULL, NULL, 'Cape Verde Escudo', 'currency.CVE'),
+	(36, 'CYP', 2, NULL, NULL, 'Cyprus Pound', 'currency.CYP'),
+	(37, 'CZK', 2, NULL, NULL, 'Czech Koruna', 'currency.CZK'),
+	(38, 'DJF', 0, NULL, NULL, 'Djibouti Franc', 'currency.DJF'),
+	(39, 'DKK', 2, NULL, NULL, 'Danish Krone', 'currency.DKK'),
+	(40, 'DOP', 2, NULL, 'RD$', 'Dominican Peso', 'currency.DOP'),
+	(41, 'DZD', 2, NULL, NULL, 'Algerian Dinar', 'currency.DZD'),
+	(42, 'EEK', 2, NULL, NULL, 'Estonian Kroon', 'currency.EEK'),
+	(43, 'EGP', 2, NULL, NULL, 'Egyptian Pound', 'currency.EGP'),
+	(44, 'ERN', 2, NULL, NULL, 'Eritrea Nafka', 'currency.ERN'),
+	(45, 'ETB', 2, NULL, NULL, 'Ethiopian Birr', 'currency.ETB'),
+	(46, 'EUR', 2, NULL, '€', 'Euro', 'currency.EUR'),
+	(47, 'FJD', 2, NULL, NULL, 'Fiji Dollar', 'currency.FJD'),
+	(48, 'FKP', 2, NULL, NULL, 'Falkland Islands Pound', 'currency.FKP'),
+	(49, 'GBP', 2, NULL, NULL, 'Pound Sterling', 'currency.GBP'),
+	(50, 'GEL', 2, NULL, NULL, 'Georgian Lari', 'currency.GEL'),
+	(51, 'GHC', 2, NULL, 'GHc', 'Ghana Cedi', 'currency.GHC'),
+	(52, 'GIP', 2, NULL, NULL, 'Gibraltar Pound', 'currency.GIP'),
+	(53, 'GMD', 2, NULL, NULL, 'Gambian Dalasi', 'currency.GMD'),
+	(54, 'GNF', 0, NULL, NULL, 'Guinea Franc', 'currency.GNF'),
+	(55, 'GTQ', 2, NULL, 'Q', 'Guatemala Quetzal', 'currency.GTQ'),
+	(56, 'GYD', 2, NULL, NULL, 'Guyana Dollar', 'currency.GYD'),
+	(57, 'HKD', 2, NULL, NULL, 'Hong Kong Dollar', 'currency.HKD'),
+	(58, 'HNL', 2, NULL, 'L', 'Honduras Lempira', 'currency.HNL'),
+	(59, 'HRK', 2, NULL, NULL, 'Croatian Kuna', 'currency.HRK'),
+	(60, 'HTG', 2, NULL, 'G', 'Haiti Gourde', 'currency.HTG'),
+	(61, 'HUF', 2, NULL, NULL, 'Hungarian Forint', 'currency.HUF'),
+	(62, 'IDR', 2, NULL, NULL, 'Indonesian Rupiah', 'currency.IDR'),
+	(63, 'ILS', 2, NULL, NULL, 'New Israeli Shekel', 'currency.ILS'),
+	(64, 'INR', 2, NULL, '₹', 'Indian Rupee', 'currency.INR'),
+	(65, 'IQD', 3, NULL, NULL, 'Iraqi Dinar', 'currency.IQD'),
+	(66, 'IRR', 2, NULL, NULL, 'Iranian Rial', 'currency.IRR'),
+	(67, 'ISK', 0, NULL, NULL, 'Iceland Krona', 'currency.ISK'),
+	(68, 'JMD', 2, NULL, NULL, 'Jamaican Dollar', 'currency.JMD'),
+	(69, 'JOD', 3, NULL, NULL, 'Jordanian Dinar', 'currency.JOD'),
+	(70, 'JPY', 0, NULL, NULL, 'Japanese Yen', 'currency.JPY'),
+	(71, 'KES', 2, NULL, 'KSh', 'Kenyan Shilling', 'currency.KES'),
+	(72, 'KGS', 2, NULL, NULL, 'Kyrgyzstan Som', 'currency.KGS'),
+	(73, 'KHR', 2, NULL, NULL, 'Cambodia Riel', 'currency.KHR'),
+	(74, 'KMF', 0, NULL, NULL, 'Comoro Franc', 'currency.KMF'),
+	(75, 'KPW', 2, NULL, NULL, 'North Korean Won', 'currency.KPW'),
+	(76, 'KRW', 0, NULL, NULL, 'Korean Won', 'currency.KRW'),
+	(77, 'KWD', 3, NULL, NULL, 'Kuwaiti Dinar', 'currency.KWD'),
+	(78, 'KYD', 2, NULL, NULL, 'Cayman Islands Dollar', 'currency.KYD'),
+	(79, 'KZT', 2, NULL, NULL, 'Kazakhstan Tenge', 'currency.KZT'),
+	(80, 'LAK', 2, NULL, NULL, 'Lao Kip', 'currency.LAK'),
+	(81, 'LBP', 2, NULL, 'L£', 'Lebanese Pound', 'currency.LBP'),
+	(82, 'LKR', 2, NULL, NULL, 'Sri Lanka Rupee', 'currency.LKR'),
+	(83, 'LRD', 2, NULL, NULL, 'Liberian Dollar', 'currency.LRD'),
+	(84, 'LSL', 2, NULL, NULL, 'Lesotho Loti', 'currency.LSL'),
+	(85, 'LTL', 2, NULL, NULL, 'Lithuanian Litas', 'currency.LTL'),
+	(86, 'LVL', 2, NULL, NULL, 'Latvian Lats', 'currency.LVL'),
+	(87, 'LYD', 3, NULL, NULL, 'Libyan Dinar', 'currency.LYD'),
+	(88, 'MAD', 2, NULL, NULL, 'Moroccan Dirham', 'currency.MAD'),
+	(89, 'MDL', 2, NULL, NULL, 'Moldovan Leu', 'currency.MDL'),
+	(90, 'MGA', 2, NULL, NULL, 'Malagasy Ariary', 'currency.MGA'),
+	(91, 'MKD', 2, NULL, NULL, 'Macedonian Denar', 'currency.MKD'),
+	(92, 'MMK', 2, NULL, 'K', 'Myanmar Kyat', 'currency.MMK'),
+	(93, 'MNT', 2, NULL, NULL, 'Mongolian Tugrik', 'currency.MNT'),
+	(94, 'MOP', 2, NULL, NULL, 'Macau Pataca', 'currency.MOP'),
+	(95, 'MRO', 2, NULL, NULL, 'Mauritania Ouguiya', 'currency.MRO'),
+	(96, 'MTL', 2, NULL, NULL, 'Maltese Lira', 'currency.MTL'),
+	(97, 'MUR', 2, NULL, NULL, 'Mauritius Rupee', 'currency.MUR'),
+	(98, 'MVR', 2, NULL, NULL, 'Maldives Rufiyaa', 'currency.MVR'),
+	(99, 'MWK', 2, NULL, NULL, 'Malawi Kwacha', 'currency.MWK'),
+	(100, 'MXN', 2, NULL, '$', 'Mexican Peso', 'currency.MXN'),
+	(101, 'MYR', 2, NULL, NULL, 'Malaysian Ringgit', 'currency.MYR'),
+	(102, 'MZM', 2, NULL, NULL, 'Mozambique Metical', 'currency.MZM'),
+	(103, 'NAD', 2, NULL, NULL, 'Namibia Dollar', 'currency.NAD'),
+	(104, 'NGN', 2, NULL, NULL, 'Nigerian Naira', 'currency.NGN'),
+	(105, 'NIO', 2, NULL, 'C$', 'Nicaragua Cordoba Oro', 'currency.NIO'),
+	(106, 'NOK', 2, NULL, NULL, 'Norwegian Krone', 'currency.NOK'),
+	(107, 'NPR', 2, NULL, NULL, 'Nepalese Rupee', 'currency.NPR'),
+	(108, 'NZD', 2, NULL, NULL, 'New Zealand Dollar', 'currency.NZD'),
+	(109, 'OMR', 3, NULL, NULL, 'Rial Omani', 'currency.OMR'),
+	(110, 'PAB', 2, NULL, 'B/.', 'Panama Balboa', 'currency.PAB'),
+	(111, 'PEN', 2, NULL, 'S/.', 'Peruvian Nuevo Sol', 'currency.PEN'),
+	(112, 'PGK', 2, NULL, NULL, 'Papua New Guinea Kina', 'currency.PGK'),
+	(113, 'PHP', 2, NULL, NULL, 'Philippine Peso', 'currency.PHP'),
+	(114, 'PKR', 2, NULL, NULL, 'Pakistan Rupee', 'currency.PKR'),
+	(115, 'PLN', 2, NULL, NULL, 'Polish Zloty', 'currency.PLN'),
+	(116, 'PYG', 0, NULL, '₲', 'Paraguayan Guarani', 'currency.PYG'),
+	(117, 'QAR', 2, NULL, NULL, 'Qatari Rial', 'currency.QAR'),
+	(118, 'RON', 2, NULL, NULL, 'Romanian Leu', 'currency.RON'),
+	(119, 'RUB', 2, NULL, NULL, 'Russian Ruble', 'currency.RUB'),
+	(120, 'RWF', 0, NULL, NULL, 'Rwanda Franc', 'currency.RWF'),
+	(121, 'SAR', 2, NULL, NULL, 'Saudi Riyal', 'currency.SAR'),
+	(122, 'SBD', 2, NULL, NULL, 'Solomon Islands Dollar', 'currency.SBD'),
+	(123, 'SCR', 2, NULL, NULL, 'Seychelles Rupee', 'currency.SCR'),
+	(124, 'SDD', 2, NULL, NULL, 'Sudanese Dinar', 'currency.SDD'),
+	(125, 'SEK', 2, NULL, NULL, 'Swedish Krona', 'currency.SEK'),
+	(126, 'SGD', 2, NULL, NULL, 'Singapore Dollar', 'currency.SGD'),
+	(127, 'SHP', 2, NULL, NULL, 'St Helena Pound', 'currency.SHP'),
+	(128, 'SIT', 2, NULL, NULL, 'Slovenian Tolar', 'currency.SIT'),
+	(129, 'SKK', 2, NULL, NULL, 'Slovak Koruna', 'currency.SKK'),
+	(130, 'SLL', 2, NULL, NULL, 'Sierra Leone Leone', 'currency.SLL'),
+	(131, 'SOS', 2, NULL, NULL, 'Somali Shilling', 'currency.SOS'),
+	(132, 'SRD', 2, NULL, NULL, 'Surinam Dollar', 'currency.SRD'),
+	(133, 'STD', 2, NULL, NULL, 'Sao Tome and Principe Dobra', 'currency.STD'),
+	(134, 'SVC', 2, NULL, NULL, 'El Salvador Colon', 'currency.SVC'),
+	(135, 'SYP', 2, NULL, NULL, 'Syrian Pound', 'currency.SYP'),
+	(136, 'SZL', 2, NULL, NULL, 'Swaziland Lilangeni', 'currency.SZL'),
+	(137, 'THB', 2, NULL, NULL, 'Thai Baht', 'currency.THB'),
+	(138, 'TJS', 2, NULL, NULL, 'Tajik Somoni', 'currency.TJS'),
+	(139, 'TMM', 2, NULL, NULL, 'Turkmenistan Manat', 'currency.TMM'),
+	(140, 'TND', 3, NULL, 'DT', 'Tunisian Dinar', 'currency.TND'),
+	(141, 'TOP', 2, NULL, NULL, 'Tonga Pa\'anga', 'currency.TOP'),
+	(142, 'TRY', 2, NULL, NULL, 'Turkish Lira', 'currency.TRY'),
+	(143, 'TTD', 2, NULL, NULL, 'Trinidad and Tobago Dollar', 'currency.TTD'),
+	(144, 'TWD', 2, NULL, NULL, 'New Taiwan Dollar', 'currency.TWD'),
+	(145, 'TZS', 2, NULL, NULL, 'Tanzanian Shilling', 'currency.TZS'),
+	(146, 'UAH', 2, NULL, NULL, 'Ukraine Hryvnia', 'currency.UAH'),
+	(147, 'UGX', 2, NULL, 'USh', 'Uganda Shilling', 'currency.UGX'),
+	(148, 'USD', 2, NULL, '$', 'US Dollar', 'currency.USD'),
+	(149, 'UYU', 2, NULL, '$U', 'Peso Uruguayo', 'currency.UYU'),
+	(150, 'UZS', 2, NULL, NULL, 'Uzbekistan Sum', 'currency.UZS'),
+	(151, 'VEB', 2, NULL, 'Bs.F.', 'Venezuelan Bolivar', 'currency.VEB'),
+	(152, 'VND', 2, NULL, NULL, 'Vietnamese Dong', 'currency.VND'),
+	(153, 'VUV', 0, NULL, NULL, 'Vanuatu Vatu', 'currency.VUV'),
+	(154, 'WST', 2, NULL, NULL, 'Samoa Tala', 'currency.WST'),
+	(155, 'XAF', 0, NULL, NULL, 'CFA Franc BEAC', 'currency.XAF'),
+	(156, 'XCD', 2, NULL, NULL, 'East Caribbean Dollar', 'currency.XCD'),
+	(157, 'XDR', 5, NULL, NULL, 'SDR (Special Drawing Rights)', 'currency.XDR'),
+	(158, 'XOF', 0, NULL, 'CFA', 'CFA Franc BCEAO', 'currency.XOF'),
+	(159, 'XPF', 0, NULL, NULL, 'CFP Franc', 'currency.XPF'),
+	(160, 'YER', 2, NULL, NULL, 'Yemeni Rial', 'currency.YER'),
+	(161, 'ZAR', 2, NULL, 'R', 'South African Rand', 'currency.ZAR'),
+	(162, 'ZMK', 2, NULL, NULL, 'Zambian Kwacha', 'currency.ZMK'),
+	(163, 'ZWD', 2, NULL, NULL, 'Zimbabwe Dollar', 'currency.ZWD');
+/*!40000 ALTER TABLE `m_currency` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_deposit_account_on_hold_transaction
+DROP TABLE IF EXISTS `m_deposit_account_on_hold_transaction`;
+CREATE TABLE IF NOT EXISTS `m_deposit_account_on_hold_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `transaction_type_enum` smallint(1) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `created_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_deposit_on_hold_transaction_m_savings_account` (`savings_account_id`),
+  CONSTRAINT `FK_deposit_on_hold_transaction_m_savings_account` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_deposit_account_on_hold_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_account_on_hold_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_account_on_hold_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_deposit_account_recurring_detail
+DROP TABLE IF EXISTS `m_deposit_account_recurring_detail`;
+CREATE TABLE IF NOT EXISTS `m_deposit_account_recurring_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL DEFAULT '0',
+  `mandatory_recommended_deposit_amount` decimal(19,6) DEFAULT NULL,
+  `is_mandatory` tinyint(4) NOT NULL DEFAULT '0',
+  `allow_withdrawal` tinyint(4) NOT NULL DEFAULT '0',
+  `adjust_advance_towards_future_payments` tinyint(4) NOT NULL DEFAULT '1',
+  `is_calendar_inherited` tinyint(4) NOT NULL DEFAULT '0',
+  `total_overdue_amount` decimal(19,6) DEFAULT NULL,
+  `no_of_overdue_installments` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKDARD00000000000001` (`savings_account_id`),
+  CONSTRAINT `FKDARD00000000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_deposit_account_recurring_detail: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_account_recurring_detail` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_account_recurring_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_deposit_account_term_and_preclosure
+DROP TABLE IF EXISTS `m_deposit_account_term_and_preclosure`;
+CREATE TABLE IF NOT EXISTS `m_deposit_account_term_and_preclosure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL DEFAULT '0',
+  `min_deposit_term` int(11) DEFAULT NULL,
+  `max_deposit_term` int(11) DEFAULT NULL,
+  `min_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `max_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `in_multiples_of_deposit_term` int(11) DEFAULT NULL,
+  `in_multiples_of_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_applicable` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_interest` decimal(19,6) DEFAULT NULL,
+  `pre_closure_penal_interest_on_enum` smallint(5) DEFAULT NULL,
+  `deposit_period` int(11) DEFAULT NULL,
+  `deposit_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `deposit_amount` decimal(19,6) DEFAULT NULL,
+  `maturity_amount` decimal(19,6) DEFAULT NULL,
+  `maturity_date` date DEFAULT NULL,
+  `on_account_closure_enum` smallint(5) DEFAULT NULL,
+  `expected_firstdepositon_date` date DEFAULT NULL,
+  `transfer_interest_to_linked_account` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `FKDATP00000000000001` (`savings_account_id`),
+  CONSTRAINT `FKDATP00000000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_deposit_account_term_and_preclosure: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_account_term_and_preclosure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_account_term_and_preclosure` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_deposit_product_interest_rate_chart
+DROP TABLE IF EXISTS `m_deposit_product_interest_rate_chart`;
+CREATE TABLE IF NOT EXISTS `m_deposit_product_interest_rate_chart` (
+  `deposit_product_id` bigint(20) NOT NULL,
+  `interest_rate_chart_id` bigint(20) NOT NULL,
+  UNIQUE KEY `deposit_product_id_interest_rate_chart_id` (`deposit_product_id`,`interest_rate_chart_id`),
+  KEY `FKDPIRC00000000000002` (`interest_rate_chart_id`),
+  CONSTRAINT `FKDPIRC00000000000001` FOREIGN KEY (`deposit_product_id`) REFERENCES `m_savings_product` (`id`),
+  CONSTRAINT `FKDPIRC00000000000002` FOREIGN KEY (`interest_rate_chart_id`) REFERENCES `m_interest_rate_chart` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_deposit_product_interest_rate_chart: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_product_interest_rate_chart` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_product_interest_rate_chart` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_deposit_product_recurring_detail
+DROP TABLE IF EXISTS `m_deposit_product_recurring_detail`;
+CREATE TABLE IF NOT EXISTS `m_deposit_product_recurring_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_product_id` bigint(20) NOT NULL DEFAULT '0',
+  `is_mandatory` tinyint(1) NOT NULL DEFAULT '1',
+  `allow_withdrawal` tinyint(1) NOT NULL DEFAULT '0',
+  `adjust_advance_towards_future_payments` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `FKDPRD00000000000001` (`savings_product_id`),
+  CONSTRAINT `FKDPRD00000000000001` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_deposit_product_recurring_detail: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_product_recurring_detail` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_product_recurring_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_deposit_product_term_and_preclosure
+DROP TABLE IF EXISTS `m_deposit_product_term_and_preclosure`;
+CREATE TABLE IF NOT EXISTS `m_deposit_product_term_and_preclosure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_product_id` bigint(20) NOT NULL DEFAULT '0',
+  `min_deposit_term` int(11) DEFAULT NULL,
+  `max_deposit_term` int(11) DEFAULT NULL,
+  `min_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `max_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `in_multiples_of_deposit_term` int(11) DEFAULT NULL,
+  `in_multiples_of_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_applicable` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_interest` decimal(19,6) DEFAULT NULL,
+  `pre_closure_penal_interest_on_enum` smallint(5) DEFAULT NULL,
+  `min_deposit_amount` decimal(19,6) DEFAULT NULL,
+  `max_deposit_amount` decimal(19,6) DEFAULT NULL,
+  `deposit_amount` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKDPTP00000000000001` (`savings_product_id`),
+  CONSTRAINT `FKDPTP00000000000001` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_deposit_product_term_and_preclosure: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_product_term_and_preclosure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_product_term_and_preclosure` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_document
+DROP TABLE IF EXISTS `m_document`;
+CREATE TABLE IF NOT EXISTS `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(500) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  `storage_type_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_document: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_document` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_document` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_entity_relation
+DROP TABLE IF EXISTS `m_entity_relation`;
+CREATE TABLE IF NOT EXISTS `m_entity_relation` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_entity_type` int(10) NOT NULL,
+  `to_entity_type` int(10) NOT NULL,
+  `code_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `from_entity_type_to_entity_type_code_name` (`from_entity_type`,`to_entity_type`,`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_entity_relation: ~5 rows (approximately)
+/*!40000 ALTER TABLE `m_entity_relation` DISABLE KEYS */;
+INSERT INTO `m_entity_relation` (`id`, `from_entity_type`, `to_entity_type`, `code_name`) VALUES
+	(1, 1, 2, 'office_access_to_loan_products'),
+	(2, 1, 3, 'office_access_to_savings_products'),
+	(3, 1, 4, 'office_access_to_fees/charges'),
+	(4, 5, 2, 'role_access_to_loan_products'),
+	(5, 5, 3, 'role_access_to_savings_products');
+/*!40000 ALTER TABLE `m_entity_relation` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_entity_to_entity_access
+DROP TABLE IF EXISTS `m_entity_to_entity_access`;
+CREATE TABLE IF NOT EXISTS `m_entity_to_entity_access` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `entity_type` varchar(50) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `access_type_code_value_id` int(11) NOT NULL,
+  `second_entity_type` varchar(50) NOT NULL,
+  `second_entity_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `id_uniq_m_entity_to_entity_access` (`entity_type`,`entity_id`,`access_type_code_value_id`,`second_entity_type`,`second_entity_id`),
+  KEY `IDX_OFFICE` (`entity_type`,`entity_id`),
+  KEY `FK_access_type_code_m_code_value` (`access_type_code_value_id`),
+  CONSTRAINT `FK_access_type_code_m_code_value` FOREIGN KEY (`access_type_code_value_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_entity_to_entity_access: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_entity_to_entity_access` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_entity_to_entity_access` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_entity_to_entity_mapping
+DROP TABLE IF EXISTS `m_entity_to_entity_mapping`;
+CREATE TABLE IF NOT EXISTS `m_entity_to_entity_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `rel_id` bigint(20) NOT NULL DEFAULT '0',
+  `from_id` bigint(20) NOT NULL DEFAULT '0',
+  `to_id` bigint(20) unsigned NOT NULL,
+  `start_date` date DEFAULT NULL,
+  `end_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `rel_id_from_id_to_id` (`rel_id`,`from_id`,`to_id`),
+  CONSTRAINT `FK__rel_id_m_entity_relation_id` FOREIGN KEY (`rel_id`) REFERENCES `m_entity_relation` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_entity_to_entity_mapping: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_entity_to_entity_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_entity_to_entity_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_floating_rates
+DROP TABLE IF EXISTS `m_floating_rates`;
+CREATE TABLE IF NOT EXISTS `m_floating_rates` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(200) NOT NULL,
+  `is_base_lending_rate` bit(1) NOT NULL DEFAULT b'0',
+  `is_active` bit(1) NOT NULL DEFAULT b'1',
+  `createdby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_floating_rates: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_floating_rates` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_floating_rates` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_floating_rates_periods
+DROP TABLE IF EXISTS `m_floating_rates_periods`;
+CREATE TABLE IF NOT EXISTS `m_floating_rates_periods` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `floating_rates_id` bigint(20) NOT NULL,
+  `from_date` datetime NOT NULL,
+  `interest_rate` decimal(19,6) NOT NULL,
+  `is_differential_to_base_lending_rate` bit(1) NOT NULL DEFAULT b'0',
+  `is_active` bit(1) NOT NULL DEFAULT b'1',
+  `createdby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_mappings_m_floating_rates` (`floating_rates_id`),
+  CONSTRAINT `FK_mappings_m_floating_rates` FOREIGN KEY (`floating_rates_id`) REFERENCES `m_floating_rates` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_floating_rates_periods: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_floating_rates_periods` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_floating_rates_periods` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_fund
+DROP TABLE IF EXISTS `m_fund`;
+CREATE TABLE IF NOT EXISTS `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_fund: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_fund` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_fund` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_group
+DROP TABLE IF EXISTS `m_group`;
+CREATE TABLE IF NOT EXISTS `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `external_id` varchar(100) DEFAULT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '300',
+  `activation_date` date DEFAULT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `level_id` int(11) NOT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `closure_reason_cv_id` int(11) DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `activatedon_userid` bigint(20) DEFAULT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `account_no` varchar(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`display_name`,`level_id`),
+  UNIQUE KEY `external_id` (`external_id`,`level_id`),
+  UNIQUE KEY `external_id_UNIQUE` (`external_id`),
+  KEY `office_id` (`office_id`),
+  KEY `staff_id` (`staff_id`),
+  KEY `Parent_Id_reference` (`parent_id`),
+  KEY `FK_m_group_level` (`level_id`),
+  KEY `FK_m_group_m_code` (`closure_reason_cv_id`),
+  CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_id`) REFERENCES `m_group_level` (`id`),
+  CONSTRAINT `FK_m_group_m_code` FOREIGN KEY (`closure_reason_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_group: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_group` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_group_client
+DROP TABLE IF EXISTS `m_group_client`;
+CREATE TABLE IF NOT EXISTS `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_group_client: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_group_client` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_group_level
+DROP TABLE IF EXISTS `m_group_level`;
+CREATE TABLE IF NOT EXISTS `m_group_level` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) DEFAULT NULL,
+  `super_parent` tinyint(1) NOT NULL,
+  `level_name` varchar(100) NOT NULL,
+  `recursable` tinyint(1) NOT NULL,
+  `can_have_clients` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `Parent_levelId_reference` (`parent_id`),
+  CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_group_level: ~2 rows (approximately)
+/*!40000 ALTER TABLE `m_group_level` DISABLE KEYS */;
+INSERT INTO `m_group_level` (`id`, `parent_id`, `super_parent`, `level_name`, `recursable`, `can_have_clients`) VALUES
+	(1, NULL, 1, 'Center', 1, 0),
+	(2, 1, 0, 'Group', 0, 1);
+/*!40000 ALTER TABLE `m_group_level` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_group_roles
+DROP TABLE IF EXISTS `m_group_roles`;
+CREATE TABLE IF NOT EXISTS `m_group_roles` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `role_cv_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UNIQUE_GROUP_ROLES` (`client_id`,`group_id`,`role_cv_id`),
+  KEY `FKGroupRoleClientId` (`client_id`),
+  KEY `FKGroupRoleGroupId` (`group_id`),
+  KEY `FK_grouprole_m_codevalue` (`role_cv_id`),
+  CONSTRAINT `FKGroupRoleClientId` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKGroupRoleGroupId` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_grouprole_m_codevalue` FOREIGN KEY (`role_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_group_roles: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_group_roles` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group_roles` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_guarantor
+DROP TABLE IF EXISTS `m_guarantor`;
+CREATE TABLE IF NOT EXISTS `m_guarantor` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `client_reln_cv_id` int(11) DEFAULT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `dob` date DEFAULT NULL,
+  `address_line_1` varchar(500) DEFAULT NULL,
+  `address_line_2` varchar(500) DEFAULT NULL,
+  `city` varchar(50) DEFAULT NULL,
+  `state` varchar(50) DEFAULT NULL,
+  `country` varchar(50) DEFAULT NULL,
+  `zip` varchar(20) DEFAULT NULL,
+  `house_phone_number` varchar(20) DEFAULT NULL,
+  `mobile_number` varchar(20) DEFAULT NULL,
+  `comment` varchar(500) DEFAULT NULL,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_m_loan` (`loan_id`),
+  KEY `FK_m_guarantor_m_code_value` (`client_reln_cv_id`),
+  CONSTRAINT `FK_m_guarantor_m_code_value` FOREIGN KEY (`client_reln_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_guarantor: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_guarantor` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_guarantor_funding_details
+DROP TABLE IF EXISTS `m_guarantor_funding_details`;
+CREATE TABLE IF NOT EXISTS `m_guarantor_funding_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `guarantor_id` bigint(20) NOT NULL,
+  `account_associations_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_released_derived` decimal(19,6) DEFAULT NULL,
+  `amount_remaining_derived` decimal(19,6) DEFAULT NULL,
+  `amount_transfered_derived` decimal(19,6) DEFAULT NULL,
+  `status_enum` smallint(3) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_fund_details_m_guarantor` (`guarantor_id`),
+  KEY `FK_m_guarantor_fund_details_account_associations_id` (`account_associations_id`),
+  CONSTRAINT `FK_m_guarantor_fund_details_account_associations_id` FOREIGN KEY (`account_associations_id`) REFERENCES `m_portfolio_account_associations` (`id`),
+  CONSTRAINT `FK_m_guarantor_fund_details_m_guarantor` FOREIGN KEY (`guarantor_id`) REFERENCES `m_guarantor` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_guarantor_funding_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_guarantor_funding_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor_funding_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_guarantor_transaction
+DROP TABLE IF EXISTS `m_guarantor_transaction`;
+CREATE TABLE IF NOT EXISTS `m_guarantor_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `guarantor_fund_detail_id` bigint(20) NOT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `deposit_on_hold_transaction_id` bigint(20) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `FK_guarantor_transaction_m_deposit_account_on_hold_transaction` (`deposit_on_hold_transaction_id`),
+  KEY `FK_guarantor_transaction_guarantor_fund_detail` (`guarantor_fund_detail_id`),
+  KEY `FK_guarantor_transaction_m_loan_transaction` (`loan_transaction_id`),
+  CONSTRAINT `FK_guarantor_transaction_guarantor_fund_detail` FOREIGN KEY (`guarantor_fund_detail_id`) REFERENCES `m_guarantor_funding_details` (`id`),
+  CONSTRAINT `FK_guarantor_transaction_m_deposit_account_on_hold_transaction` FOREIGN KEY (`deposit_on_hold_transaction_id`) REFERENCES `m_deposit_account_on_hold_transaction` (`id`),
+  CONSTRAINT `FK_guarantor_transaction_m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_guarantor_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_guarantor_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_holiday
+DROP TABLE IF EXISTS `m_holiday`;
+CREATE TABLE IF NOT EXISTS `m_holiday` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `from_date` datetime NOT NULL,
+  `to_date` datetime NOT NULL,
+  `repayments_rescheduled_to` datetime NOT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '100',
+  `processed` tinyint(1) NOT NULL DEFAULT '0',
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `holiday_name` (`name`,`from_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_holiday: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_holiday` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_holiday` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_holiday_office
+DROP TABLE IF EXISTS `m_holiday_office`;
+CREATE TABLE IF NOT EXISTS `m_holiday_office` (
+  `holiday_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`holiday_id`,`office_id`),
+  KEY `m_holiday_id_ibfk_1` (`holiday_id`),
+  KEY `m_office_id_ibfk_2` (`office_id`),
+  CONSTRAINT `m_holiday_id_ibfk_1` FOREIGN KEY (`holiday_id`) REFERENCES `m_holiday` (`id`),
+  CONSTRAINT `m_office_id_ibfk_2` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_holiday_office: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_holiday_office` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_holiday_office` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_hook
+DROP TABLE IF EXISTS `m_hook`;
+CREATE TABLE IF NOT EXISTS `m_hook` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `template_id` smallint(6) NOT NULL,
+  `is_active` smallint(3) NOT NULL DEFAULT '1',
+  `name` varchar(45) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `ugd_template_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_template_id_idx` (`template_id`),
+  KEY `fk_ugd_template_id` (`ugd_template_id`),
+  CONSTRAINT `fk_template_id` FOREIGN KEY (`template_id`) REFERENCES `m_hook_templates` (`id`),
+  CONSTRAINT `fk_ugd_template_id` FOREIGN KEY (`ugd_template_id`) REFERENCES `m_template` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_hook: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_hook` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_hook` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_hook_configuration
+DROP TABLE IF EXISTS `m_hook_configuration`;
+CREATE TABLE IF NOT EXISTS `m_hook_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `hook_id` bigint(20) DEFAULT NULL,
+  `field_type` varchar(45) NOT NULL,
+  `field_name` varchar(100) NOT NULL,
+  `field_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_hook_id_idx` (`hook_id`),
+  CONSTRAINT `fk_hook_id_cfg` FOREIGN KEY (`hook_id`) REFERENCES `m_hook` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_hook_configuration: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_hook_configuration` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_hook_configuration` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_hook_registered_events
+DROP TABLE IF EXISTS `m_hook_registered_events`;
+CREATE TABLE IF NOT EXISTS `m_hook_registered_events` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `hook_id` bigint(20) NOT NULL,
+  `entity_name` varchar(45) NOT NULL,
+  `action_name` varchar(45) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_hook_id_idx` (`hook_id`),
+  CONSTRAINT `fk_hook_idc` FOREIGN KEY (`hook_id`) REFERENCES `m_hook` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_hook_registered_events: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_hook_registered_events` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_hook_registered_events` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_hook_schema
+DROP TABLE IF EXISTS `m_hook_schema`;
+CREATE TABLE IF NOT EXISTS `m_hook_schema` (
+  `id` smallint(6) NOT NULL AUTO_INCREMENT,
+  `hook_template_id` smallint(6) NOT NULL,
+  `field_type` varchar(45) NOT NULL,
+  `field_name` varchar(100) NOT NULL,
+  `placeholder` varchar(100) DEFAULT NULL,
+  `optional` tinyint(3) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `fk_hook_template_id_idx` (`hook_template_id`),
+  CONSTRAINT `fk_hook_template_id` FOREIGN KEY (`hook_template_id`) REFERENCES `m_hook_templates` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_hook_schema: ~7 rows (approximately)
+/*!40000 ALTER TABLE `m_hook_schema` DISABLE KEYS */;
+INSERT INTO `m_hook_schema` (`id`, `hook_template_id`, `field_type`, `field_name`, `placeholder`, `optional`) VALUES
+	(1, 1, 'string', 'Payload URL', NULL, 0),
+	(2, 1, 'string', 'Content Type', 'json / form', 0),
+	(3, 2, 'string', 'Payload URL', NULL, 0),
+	(4, 2, 'string', 'SMS Provider', NULL, 0),
+	(5, 2, 'string', 'Phone Number', NULL, 0),
+	(6, 2, 'string', 'SMS Provider Token', NULL, 0),
+	(7, 2, 'string', 'SMS Provider Account Id', NULL, 0);
+/*!40000 ALTER TABLE `m_hook_schema` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_hook_templates
+DROP TABLE IF EXISTS `m_hook_templates`;
+CREATE TABLE IF NOT EXISTS `m_hook_templates` (
+  `id` smallint(6) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_hook_templates: ~2 rows (approximately)
+/*!40000 ALTER TABLE `m_hook_templates` DISABLE KEYS */;
+INSERT INTO `m_hook_templates` (`id`, `name`) VALUES
+	(1, 'Web'),
+	(2, 'SMS Bridge');
+/*!40000 ALTER TABLE `m_hook_templates` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_image
+DROP TABLE IF EXISTS `m_image`;
+CREATE TABLE IF NOT EXISTS `m_image` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `location` varchar(500) DEFAULT NULL,
+  `storage_type_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_image: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_image` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_image` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_interest_incentives
+DROP TABLE IF EXISTS `m_interest_incentives`;
+CREATE TABLE IF NOT EXISTS `m_interest_incentives` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `interest_rate_slab_id` bigint(20) NOT NULL,
+  `entiry_type` smallint(2) NOT NULL,
+  `attribute_name` smallint(2) NOT NULL,
+  `condition_type` smallint(2) NOT NULL,
+  `attribute_value` varchar(50) NOT NULL,
+  `incentive_type` smallint(2) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_interest_incentives_m_interest_rate_slab` (`interest_rate_slab_id`),
+  CONSTRAINT `FK_m_interest_incentives_m_interest_rate_slab` FOREIGN KEY (`interest_rate_slab_id`) REFERENCES `m_interest_rate_slab` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_interest_incentives: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_interest_incentives` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_interest_incentives` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_interest_rate_chart
+DROP TABLE IF EXISTS `m_interest_rate_chart`;
+CREATE TABLE IF NOT EXISTS `m_interest_rate_chart` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `from_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_interest_rate_chart: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_interest_rate_chart` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_interest_rate_chart` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_interest_rate_slab
+DROP TABLE IF EXISTS `m_interest_rate_slab`;
+CREATE TABLE IF NOT EXISTS `m_interest_rate_slab` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `interest_rate_chart_id` bigint(20) NOT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `period_type_enum` smallint(5) NOT NULL DEFAULT '1',
+  `from_period` int(11) NOT NULL DEFAULT '0',
+  `to_period` int(11) DEFAULT NULL,
+  `amount_range_from` decimal(19,6) DEFAULT NULL,
+  `amount_range_to` decimal(19,6) DEFAULT NULL,
+  `annual_interest_rate` decimal(19,6) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKIRS00000000000001` (`interest_rate_chart_id`),
+  CONSTRAINT `FKIRS00000000000001` FOREIGN KEY (`interest_rate_chart_id`) REFERENCES `m_interest_rate_chart` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_interest_rate_slab: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_interest_rate_slab` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_interest_rate_slab` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan
+DROP TABLE IF EXISTS `m_loan`;
+CREATE TABLE IF NOT EXISTS `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `loanpurpose_cv_id` int(11) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `loan_type_enum` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `principal_amount_proposed` decimal(19,6) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `approved_principal` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `is_floating_interest_rate` bit(1) DEFAULT b'0',
+  `interest_rate_differential` decimal(19,6) DEFAULT '0.000000',
+  `nominal_interest_rate_per_period` decimal(19,6) DEFAULT NULL,
+  `interest_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) DEFAULT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `allow_partial_period_interest_calcualtion` tinyint(1) NOT NULL DEFAULT '0',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `repayment_frequency_nth_day_enum` smallint(5) DEFAULT '0',
+  `repayment_frequency_day_of_week_enum` smallint(5) DEFAULT '0',
+  `number_of_repayments` smallint(5) NOT NULL,
+  `grace_on_principal_periods` smallint(5) DEFAULT NULL,
+  `grace_on_interest_periods` smallint(5) DEFAULT NULL,
+  `grace_interest_free_periods` smallint(5) DEFAULT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `disbursedon_userid` bigint(20) DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `principal_disbursed_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overpaid_derived` decimal(19,6) DEFAULT NULL,
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `rescheduledon_date` date DEFAULT NULL,
+  `rescheduledon_userid` bigint(20) DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `writtenoffon_date` date DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  `sync_disbursement_with_meeting` tinyint(1) DEFAULT NULL,
+  `loan_counter` smallint(6) DEFAULT NULL,
+  `loan_product_counter` smallint(6) DEFAULT NULL,
+  `fixed_emi_amount` decimal(19,6) DEFAULT NULL,
+  `max_outstanding_loan_balance` decimal(19,6) DEFAULT NULL,
+  `grace_on_arrears_ageing` smallint(5) DEFAULT NULL,
+  `is_npa` tinyint(1) NOT NULL DEFAULT '0',
+  `total_recovered_derived` decimal(19,6) DEFAULT NULL,
+  `accrued_till` date DEFAULT NULL,
+  `interest_recalcualated_on` date DEFAULT NULL,
+  `days_in_month_enum` smallint(5) NOT NULL DEFAULT '1',
+  `days_in_year_enum` smallint(5) NOT NULL DEFAULT '1',
+  `interest_recalculation_enabled` tinyint(4) NOT NULL DEFAULT '0',
+  `guarantee_amount_derived` decimal(19,6) DEFAULT NULL,
+  `create_standing_instruction_at_disbursement` tinyint(1) DEFAULT NULL,
+  `version` int(15) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loanpurpose_codevalue` (`loanpurpose_cv_id`),
+  KEY `FK_submittedon_userid` (`submittedon_userid`),
+  KEY `FK_approvedon_userid` (`approvedon_userid`),
+  KEY `FK_rejectedon_userid` (`rejectedon_userid`),
+  KEY `FK_withdrawnon_userid` (`withdrawnon_userid`),
+  KEY `FK_disbursedon_userid` (`disbursedon_userid`),
+  KEY `FK_closedon_userid` (`closedon_userid`),
+  KEY `fk_m_group_client_001_idx` (`group_id`,`client_id`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_approvedon_userid` FOREIGN KEY (`approvedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_closedon_userid` FOREIGN KEY (`closedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_disbursedon_userid` FOREIGN KEY (`disbursedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_m_loanpurpose_codevalue` FOREIGN KEY (`loanpurpose_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_rejectedon_userid` FOREIGN KEY (`rejectedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_submittedon_userid` FOREIGN KEY (`submittedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_withdrawnon_userid` FOREIGN KEY (`withdrawnon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loanproduct_provisioning_entry
+DROP TABLE IF EXISTS `m_loanproduct_provisioning_entry`;
+CREATE TABLE IF NOT EXISTS `m_loanproduct_provisioning_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `history_id` bigint(20) NOT NULL,
+  `criteria_id` bigint(20) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `product_id` bigint(20) NOT NULL,
+  `category_id` bigint(20) NOT NULL,
+  `overdue_in_days` bigint(20) DEFAULT '0',
+  `reseve_amount` decimal(20,6) DEFAULT '0.000000',
+  `liability_account` bigint(20) DEFAULT NULL,
+  `expense_account` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `history_id` (`history_id`),
+  KEY `criteria_id` (`criteria_id`),
+  KEY `currency_code` (`currency_code`),
+  KEY `office_id` (`office_id`),
+  KEY `product_id` (`product_id`),
+  KEY `category_id` (`category_id`),
+  KEY `liability_account` (`liability_account`),
+  KEY `expense_account` (`expense_account`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_1` FOREIGN KEY (`history_id`) REFERENCES `m_provisioning_history` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_2` FOREIGN KEY (`criteria_id`) REFERENCES `m_provisioning_criteria` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_3` FOREIGN KEY (`currency_code`) REFERENCES `m_currency` (`code`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_4` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_5` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_6` FOREIGN KEY (`category_id`) REFERENCES `m_provision_category` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_7` FOREIGN KEY (`liability_account`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_8` FOREIGN KEY (`expense_account`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loanproduct_provisioning_entry: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loanproduct_provisioning_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loanproduct_provisioning_entry` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loanproduct_provisioning_mapping
+DROP TABLE IF EXISTS `m_loanproduct_provisioning_mapping`;
+CREATE TABLE IF NOT EXISTS `m_loanproduct_provisioning_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `product_id` bigint(20) NOT NULL,
+  `criteria_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `product_id` (`product_id`),
+  KEY `criteria_id` (`criteria_id`),
+  CONSTRAINT `m_loanproduct_provisioning_mapping_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_mapping_ibfk_2` FOREIGN KEY (`criteria_id`) REFERENCES `m_provisioning_criteria` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loanproduct_provisioning_mapping: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loanproduct_provisioning_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loanproduct_provisioning_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_arrears_aging
+DROP TABLE IF EXISTS `m_loan_arrears_aging`;
+CREATE TABLE IF NOT EXISTS `m_loan_arrears_aging` (
+  `loan_id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `principal_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `overdue_since_date_derived` date DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_arrears_aging_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_arrears_aging: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_arrears_aging` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_charge
+DROP TABLE IF EXISTS `m_loan_charge`;
+CREATE TABLE IF NOT EXISTS `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `charge_payment_mode_enum` smallint(5) NOT NULL DEFAULT '0',
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `charge_amount_or_percentage` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  `min_cap` decimal(19,6) DEFAULT NULL,
+  `max_cap` decimal(19,6) DEFAULT NULL,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_charge_paid_by
+DROP TABLE IF EXISTS `m_loan_charge_paid_by`;
+CREATE TABLE IF NOT EXISTS `m_loan_charge_paid_by` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_transaction_id` bigint(20) NOT NULL,
+  `loan_charge_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `installment_number` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK__m_loan_transaction` (`loan_transaction_id`),
+  KEY `FK__m_loan_charge` (`loan_charge_id`),
+  CONSTRAINT `FK__m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+  CONSTRAINT `FK__m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_charge_paid_by: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_charge_paid_by` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_charge_paid_by` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_collateral
+DROP TABLE IF EXISTS `m_loan_collateral`;
+CREATE TABLE IF NOT EXISTS `m_loan_collateral` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_cv_id` int(11) NOT NULL,
+  `value` decimal(19,6) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_collateral_m_loan` (`loan_id`),
+  KEY `FK_collateral_code_value` (`type_cv_id`),
+  CONSTRAINT `FK_collateral_code_value` FOREIGN KEY (`type_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_collateral_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_collateral: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_collateral` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_collateral` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_disbursement_detail
+DROP TABLE IF EXISTS `m_loan_disbursement_detail`;
+CREATE TABLE IF NOT EXISTS `m_loan_disbursement_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `expected_disburse_date` datetime NOT NULL,
+  `disbursedon_date` datetime DEFAULT NULL,
+  `principal` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_loan_disbursement_detail_loan_id` (`loan_id`),
+  CONSTRAINT `FK_loan_disbursement_detail_loan_id` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_disbursement_detail: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_disbursement_detail` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_disbursement_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_installment_charge
+DROP TABLE IF EXISTS `m_loan_installment_charge`;
+CREATE TABLE IF NOT EXISTS `m_loan_installment_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_charge_id` bigint(20) NOT NULL,
+  `loan_schedule_id` bigint(20) NOT NULL,
+  `due_date` date DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  `amount_through_charge_payment` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_loan_charge_id_charge_schedule` (`loan_charge_id`),
+  KEY `FK_loan_schedule_id_charge_schedule` (`loan_schedule_id`),
+  CONSTRAINT `FK_loan_charge_id_charge_schedule` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+  CONSTRAINT `FK_loan_schedule_id_charge_schedule` FOREIGN KEY (`loan_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_installment_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_installment_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_installment_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_officer_assignment_history
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+CREATE TABLE IF NOT EXISTS `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_officer_assignment_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_overdue_installment_charge
+DROP TABLE IF EXISTS `m_loan_overdue_installment_charge`;
+CREATE TABLE IF NOT EXISTS `m_loan_overdue_installment_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_charge_id` bigint(20) NOT NULL,
+  `loan_schedule_id` bigint(20) NOT NULL,
+  `frequency_number` int(10) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_loan_overdue_installment_charge_m_loan_charge` (`loan_charge_id`),
+  KEY `FK_m_loan_overdue_installment_charge_m_loan_repayment_schedule` (`loan_schedule_id`),
+  CONSTRAINT `FK_m_loan_overdue_installment_charge_m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+  CONSTRAINT `FK_m_loan_overdue_installment_charge_m_loan_repayment_schedule` FOREIGN KEY (`loan_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_overdue_installment_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_overdue_installment_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_overdue_installment_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_paid_in_advance
+DROP TABLE IF EXISTS `m_loan_paid_in_advance`;
+CREATE TABLE IF NOT EXISTS `m_loan_paid_in_advance` (
+  `loan_id` bigint(20) NOT NULL,
+  `principal_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_paid_in_advance_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_paid_in_advance: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_paid_in_advance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_paid_in_advance` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_recalculation_details
+DROP TABLE IF EXISTS `m_loan_recalculation_details`;
+CREATE TABLE IF NOT EXISTS `m_loan_recalculation_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `compound_type_enum` smallint(5) NOT NULL,
+  `reschedule_strategy_enum` smallint(5) NOT NULL,
+  `rest_frequency_type_enum` smallint(1) NOT NULL,
+  `rest_frequency_interval` smallint(3) NOT NULL DEFAULT '0',
+  `rest_freqency_date` date DEFAULT NULL,
+  `compounding_frequency_type_enum` smallint(1) DEFAULT NULL,
+  `compounding_frequency_interval` smallint(3) DEFAULT NULL,
+  `compounding_freqency_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_loan_m_loan_recalculation_details` (`loan_id`),
+  CONSTRAINT `FK_m_loan_m_loan_recalculation_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-default.m_loan_recalculation_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_recalculation_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_recalculation_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_repayment_schedule
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+CREATE TABLE IF NOT EXISTS `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  `accrual_interest_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `accrual_fee_charges_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `accrual_penalty_charges_derived` decimal(19,6) DEFAULT NULL,
+  `total_paid_in_advance_derived` decimal(19,6) DEFAULT NULL,
+  `total_paid_late_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `obligations_met_on_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `recalculated_interest_component` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_repayment_schedule: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_repayment_schedule_history
+DROP TABLE IF EXISTS `m_loan_repayment_schedule_history`;
+CREATE TABLE IF NOT EXISTS `m_loan_repayment_schedule_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_reschedule_request_id` bigint(20) DEFAULT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `version` int(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `loan_id` (`loan_id`),
+  KEY `loan_reschedule_request_id` (`loan_reschedule_request_id`),
+  CONSTRAINT `m_loan_repayment_schedule_history_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `m_loan_repayment_schedule_history_ibfk_2` FOREIGN KEY (`loan_reschedule_request_id`) REFERENCES `m_loan_reschedule_request` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_repayment_schedule_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_repayment_schedule_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_reschedule_request
+DROP TABLE IF EXISTS `m_loan_reschedule_request`;
+CREATE TABLE IF NOT EXISTS `m_loan_reschedule_request` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `status_enum` smallint(5) NOT NULL,
+  `reschedule_from_installment` smallint(5) NOT NULL COMMENT 'Rescheduling will start from this installment',
+  `grace_on_principal` smallint(5) DEFAULT NULL COMMENT 'Number of installments that should be added with 0 principal amount',
+  `grace_on_interest` smallint(5) DEFAULT NULL COMMENT 'Number of installments that should be added with 0 interest rate',
+  `reschedule_from_date` date NOT NULL COMMENT 'Rescheduling will start from the installment with due date similar to this date.',
+  `extra_terms` smallint(5) DEFAULT NULL COMMENT 'Number of extra terms to be added to the schedule',
+  `interest_rate` decimal(19,6) DEFAULT NULL COMMENT 'If provided, the interest rate for the unpaid installments will be recalculated',
+  `recalculate_interest` tinyint(1) DEFAULT NULL COMMENT 'If set to 1, interest will be recalculated starting from the reschedule period.',
+  `adjusted_due_date` date DEFAULT NULL COMMENT 'New due date for the first rescheduled installment',
+  `reschedule_reason_cv_id` int(11) DEFAULT NULL COMMENT 'ID of code value of reason for rescheduling',
+  `reschedule_reason_comment` varchar(500) DEFAULT NULL COMMENT 'Text provided in addition to the reason code value',
+  `submitted_on_date` date NOT NULL,
+  `submitted_by_user_id` bigint(20) NOT NULL,
+  `approved_on_date` date DEFAULT NULL,
+  `approved_by_user_id` bigint(20) DEFAULT NULL,
+  `rejected_on_date` date DEFAULT NULL,
+  `rejected_by_user_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `loan_id` (`loan_id`),
+  KEY `reschedule_reason_cv_id` (`reschedule_reason_cv_id`),
+  KEY `submitted_by_user_id` (`submitted_by_user_id`),
+  KEY `approved_by_user_id` (`approved_by_user_id`),
+  KEY `rejected_by_user_id` (`rejected_by_user_id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_2` FOREIGN KEY (`reschedule_reason_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_3` FOREIGN KEY (`submitted_by_user_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_4` FOREIGN KEY (`approved_by_user_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_5` FOREIGN KEY (`rejected_by_user_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_reschedule_request: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_reschedule_request` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_reschedule_request` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_term_variations
+DROP TABLE IF EXISTS `m_loan_term_variations`;
+CREATE TABLE IF NOT EXISTS `m_loan_term_variations` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `term_type` smallint(2) NOT NULL,
+  `applicable_date` date NOT NULL,
+  `decimal_value` decimal(19,6) DEFAULT NULL,
+  `date_value` date DEFAULT NULL,
+  `is_specific_to_installment` tinyint(4) NOT NULL DEFAULT '0',
+  `applied_on_loan_status` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_loan_id_m_loan_id` (`loan_id`),
+  CONSTRAINT `FK_loan_id_m_loan_id` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_term_variations: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_term_variations` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_term_variations` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_tranche_charges
+DROP TABLE IF EXISTS `m_loan_tranche_charges`;
+CREATE TABLE IF NOT EXISTS `m_loan_tranche_charges` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_loan_tranche_charges_m_loan` (`loan_id`),
+  KEY `FK_m_loan_tranche_charges_m_charge` (`charge_id`),
+  CONSTRAINT `FK_m_loan_tranche_charges_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `FK_m_loan_tranche_charges_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_tranche_charges: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_tranche_charges` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_tranche_charges` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_tranche_disbursement_charge
+DROP TABLE IF EXISTS `m_loan_tranche_disbursement_charge`;
+CREATE TABLE IF NOT EXISTS `m_loan_tranche_disbursement_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_charge_id` bigint(20) NOT NULL,
+  `disbursement_detail_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_loan_tranche_disbursement_charge_m_loan_charge` (`loan_charge_id`),
+  KEY `FK_m_loan_tranche_disbursement_charge_m_loan_disbursement_detail` (`disbursement_detail_id`),
+  CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+  CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_disbursement_detail` FOREIGN KEY (`disbursement_detail_id`) REFERENCES `m_loan_disbursement_detail` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_tranche_disbursement_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_tranche_disbursement_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_tranche_disbursement_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_transaction
+DROP TABLE IF EXISTS `m_loan_transaction`;
+CREATE TABLE IF NOT EXISTS `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `payment_detail_id` bigint(20) DEFAULT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `overpayment_portion_derived` decimal(19,6) DEFAULT NULL,
+  `unrecognized_income_portion` decimal(19,6) DEFAULT NULL,
+  `outstanding_loan_balance_derived` decimal(19,6) DEFAULT NULL,
+  `submitted_on_date` date NOT NULL,
+  `manually_adjusted_or_reversed` tinyint(1) DEFAULT '0',
+  `created_date` datetime DEFAULT NULL,
+  `appuser_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `external_id_UNIQUE` (`external_id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  KEY `FK_m_loan_transaction_m_payment_detail` (`payment_detail_id`),
+  KEY `FK_m_loan_transaction_m_office` (`office_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_m_loan_transaction_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_loan_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_loan_transaction_repayment_schedule_mapping
+DROP TABLE IF EXISTS `m_loan_transaction_repayment_schedule_mapping`;
+CREATE TABLE IF NOT EXISTS `m_loan_transaction_repayment_schedule_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_transaction_id` bigint(20) NOT NULL,
+  `loan_repayment_schedule_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_mappings_m_loan_transaction` (`loan_transaction_id`),
+  KEY `FK_mappings_m_loan_repayment_schedule` (`loan_repayment_schedule_id`),
+  CONSTRAINT `FK_mappings_m_loan_repayment_schedule` FOREIGN KEY (`loan_repayment_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`),
+  CONSTRAINT `FK_mappings_m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_loan_transaction_repayment_schedule_mapping: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_transaction_repayment_schedule_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction_repayment_schedule_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_mandatory_savings_schedule
+DROP TABLE IF EXISTS `m_mandatory_savings_schedule`;
+CREATE TABLE IF NOT EXISTS `m_mandatory_savings_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `deposit_amount` decimal(19,6) DEFAULT NULL,
+  `deposit_amount_completed_derived` decimal(19,6) DEFAULT NULL,
+  `total_paid_in_advance_derived` decimal(19,6) DEFAULT NULL,
+  `total_paid_late_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `obligations_met_on_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKMSS0000000001` (`savings_account_id`),
+  CONSTRAINT `FKMSS0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_mandatory_savings_schedule: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_mandatory_savings_schedule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_mandatory_savings_schedule` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_meeting
+DROP TABLE IF EXISTS `m_meeting`;
+CREATE TABLE IF NOT EXISTS `m_meeting` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_instance_id` bigint(20) NOT NULL,
+  `meeting_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_calendar_instance_id_meeting_date` (`calendar_instance_id`,`meeting_date`),
+  CONSTRAINT `FK_m_calendar_instance_m_meeting` FOREIGN KEY (`calendar_instance_id`) REFERENCES `m_calendar_instance` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-default.m_meeting: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_meeting` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_meeting` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_note
+DROP TABLE IF EXISTS `m_note`;
+CREATE TABLE IF NOT EXISTS `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `savings_account_transaction_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK_m_note_m_group` (`group_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  KEY `FK_savings_account_id` (`savings_account_id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_note_m_group` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_savings_account_id` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_note: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_note` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_note` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_office
+DROP TABLE IF EXISTS `m_office`;
+CREATE TABLE IF NOT EXISTS `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_office: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_office` DISABLE KEYS */;
+INSERT INTO `m_office` (`id`, `parent_id`, `hierarchy`, `external_id`, `name`, `opening_date`) VALUES
+	(1, NULL, '.', '1', 'Head Office', '2009-01-01');
+/*!40000 ALTER TABLE `m_office` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_office_transaction
+DROP TABLE IF EXISTS `m_office_transaction`;
+CREATE TABLE IF NOT EXISTS `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_office_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_office_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_office_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_organisation_currency
+DROP TABLE IF EXISTS `m_organisation_currency`;
+CREATE TABLE IF NOT EXISTS `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_organisation_currency: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_organisation_currency` DISABLE KEYS */;
+INSERT INTO `m_organisation_currency` (`id`, `code`, `decimal_places`, `currency_multiplesof`, `name`, `display_symbol`, `internationalized_name_code`) VALUES
+	(21, 'USD', 2, NULL, 'US Dollar', '$', 'currency.USD');
+/*!40000 ALTER TABLE `m_organisation_currency` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_password_validation_policy
+DROP TABLE IF EXISTS `m_password_validation_policy`;
+CREATE TABLE IF NOT EXISTS `m_password_validation_policy` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `regex` text NOT NULL,
+  `description` text NOT NULL,
+  `active` tinyint(4) NOT NULL DEFAULT '0',
+  `key` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_password_validation_policy: ~2 rows (approximately)
+/*!40000 ALTER TABLE `m_password_validation_policy` DISABLE KEYS */;
+INSERT INTO `m_password_validation_policy` (`id`, `regex`, `description`, `active`, `key`) VALUES
+	(1, '^.{1,50}$', 'Password most be at least 1 character and not more that 50 characters long', 1, 'simple'),
+	(2, '^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\\s).{6,50}$', 'Password must be at least 6 characters, no more than 50 characters long, must include at least one upper case letter, one lower case letter, one numeric digit and no space', 0, 'secure');
+/*!40000 ALTER TABLE `m_password_validation_policy` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_payment_detail
+DROP TABLE IF EXISTS `m_payment_detail`;
+CREATE TABLE IF NOT EXISTS `m_payment_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `payment_type_id` int(11) DEFAULT NULL,
+  `account_number` varchar(100) DEFAULT NULL,
+  `check_number` varchar(100) DEFAULT NULL,
+  `receipt_number` varchar(100) DEFAULT NULL,
+  `bank_number` varchar(100) DEFAULT NULL,
+  `routing_code` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_payment_detail_m_payment_type` (`payment_type_id`),
+  CONSTRAINT `FK_m_payment_detail_m_payment_type` FOREIGN KEY (`payment_type_id`) REFERENCES `m_payment_type` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_payment_detail: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_payment_detail` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_payment_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_payment_type
+DROP TABLE IF EXISTS `m_payment_type`;
+CREATE TABLE IF NOT EXISTS `m_payment_type` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `value` varchar(100) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `is_cash_payment` tinyint(1) DEFAULT '0',
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_payment_type: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_payment_type` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_payment_type` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_permission
+DROP TABLE IF EXISTS `m_permission`;
+CREATE TABLE IF NOT EXISTS `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=705 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_permission: ~741 rows (approximately)
+/*!40000 ALTER TABLE `m_permission` DISABLE KEYS */;
+INSERT INTO `m_permission` (`id`, `grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
+	(1, 'special', 'ALL_FUNCTIONS', NULL, NULL, 0),
+	(2, 'special', 'ALL_FUNCTIONS_READ', NULL, NULL, 0),
+	(3, 'special', 'CHECKER_SUPER_USER', NULL, NULL, 0),
+	(4, 'special', 'REPORTING_SUPER_USER', NULL, NULL, 0),
+	(5, 'authorisation', 'READ_PERMISSION', 'PERMISSION', 'READ', 0),
+	(6, 'authorisation', 'PERMISSIONS_ROLE', 'ROLE', 'PERMISSIONS', 0),
+	(7, 'authorisation', 'CREATE_ROLE', 'ROLE', 'CREATE', 0),
+	(8, 'authorisation', 'CREATE_ROLE_CHECKER', 'ROLE', 'CREATE_CHECKER', 0),
+	(9, 'authorisation', 'READ_ROLE', 'ROLE', 'READ', 0),
+	(10, 'authorisation', 'UPDATE_ROLE', 'ROLE', 'UPDATE', 0),
+	(11, 'authorisation', 'UPDATE_ROLE_CHECKER', 'ROLE', 'UPDATE_CHECKER', 0),
+	(12, 'authorisation', 'DELETE_ROLE', 'ROLE', 'DELETE', 0),
+	(13, 'authorisation', 'DELETE_ROLE_CHECKER', 'ROLE', 'DELETE_CHECKER', 0),
+	(14, 'authorisation', 'CREATE_USER', 'USER', 'CREATE', 0),
+	(15, 'authorisation', 'CREATE_USER_CHECKER', 'USER', 'CREATE_CHECKER', 0),
+	(16, 'authorisation', 'READ_USER', 'USER', 'READ', 0),
+	(17, 'authorisation', 'UPDATE_USER', 'USER', 'UPDATE', 0),
+	(18, 'authorisation', 'UPDATE_USER_CHECKER', 'USER', 'UPDATE_CHECKER', 0),
+	(19, 'authorisation', 'DELETE_USER', 'USER', 'DELETE', 0),
+	(20, 'authorisation', 'DELETE_USER_CHECKER', 'USER', 'DELETE_CHECKER', 0),
+	(21, 'configuration', 'READ_CONFIGURATION', 'CONFIGURATION', 'READ', 0),
+	(22, 'configuration', 'UPDATE_CONFIGURATION', 'CONFIGURATION', 'UPDATE', 0),
+	(23, 'configuration', 'UPDATE_CONFIGURATION_CHECKER', 'CONFIGURATION', 'UPDATE_CHECKER', 0),
+	(24, 'configuration', 'READ_CODE', 'CODE', 'READ', 0),
+	(25, 'configuration', 'CREATE_CODE', 'CODE', 'CREATE', 0),
+	(26, 'configuration', 'CREATE_CODE_CHECKER', 'CODE', 'CREATE_CHECKER', 0),
+	(27, 'configuration', 'UPDATE_CODE', 'CODE', 'UPDATE', 0),
+	(28, 'configuration', 'UPDATE_CODE_CHECKER', 'CODE', 'UPDATE_CHECKER', 0),
+	(29, 'configuration', 'DELETE_CODE', 'CODE', 'DELETE', 0),
+	(30, 'configuration', 'DELETE_CODE_CHECKER', 'CODE', 'DELETE_CHECKER', 0),
+	(31, 'configuration', 'READ_CODEVALUE', 'CODEVALUE', 'READ', 0),
+	(32, 'configuration', 'CREATE_CODEVALUE', 'CODEVALUE', 'CREATE', 0),
+	(33, 'configuration', 'CREATE_CODEVALUE_CHECKER', 'CODEVALUE', 'CREATE_CHECKER', 0),
+	(34, 'configuration', 'UPDATE_CODEVALUE', 'CODEVALUE', 'UPDATE', 0),
+	(35, 'configuration', 'UPDATE_CODEVALUE_CHECKER', 'CODEVALUE', 'UPDATE_CHECKER', 0),
+	(36, 'configuration', 'DELETE_CODEVALUE', 'CODEVALUE', 'DELETE', 0),
+	(37, 'configuration', 'DELETE_CODEVALUE_CHECKER', 'CODEVALUE', 'DELETE_CHECKER', 0),
+	(38, 'configuration', 'READ_CURRENCY', 'CURRENCY', 'READ', 0),
+	(39, 'configuration', 'UPDATE_CURRENCY', 'CURRENCY', 'UPDATE', 0),
+	(40, 'configuration', 'UPDATE_CURRENCY_CHECKER', 'CURRENCY', 'UPDATE_CHECKER', 0),
+	(41, 'configuration', 'UPDATE_PERMISSION', 'PERMISSION', 'UPDATE', 0),
+	(42, 'configuration', 'UPDATE_PERMISSION_CHECKER', 'PERMISSION', 'UPDATE_CHECKER', 0),
+	(43, 'configuration', 'READ_DATATABLE', 'DATATABLE', 'READ', 0),
+	(44, 'configuration', 'REGISTER_DATATABLE', 'DATATABLE', 'REGISTER', 0),
+	(45, 'configuration', 'REGISTER_DATATABLE_CHECKER', 'DATATABLE', 'REGISTER_CHECKER', 0),
+	(46, 'configuration', 'DEREGISTER_DATATABLE', 'DATATABLE', 'DEREGISTER', 0),
+	(47, 'configuration', 'DEREGISTER_DATATABLE_CHECKER', 'DATATABLE', 'DEREGISTER_CHECKER', 0),
+	(48, 'configuration', 'READ_AUDIT', 'AUDIT', 'READ', 0),
+	(49, 'configuration', 'CREATE_CALENDAR', 'CALENDAR', 'CREATE', 0),
+	(50, 'configuration', 'READ_CALENDAR', 'CALENDAR', 'READ', 0),
+	(51, 'configuration', 'UPDATE_CALENDAR', 'CALENDAR', 'UPDATE', 0),
+	(52, 'configuration', 'DELETE_CALENDAR', 'CALENDAR', 'DELETE', 0),
+	(53, 'configuration', 'CREATE_CALENDAR_CHECKER', 'CALENDAR', 'CREATE_CHECKER', 0),
+	(54, 'configuration', 'UPDATE_CALENDAR_CHECKER', 'CALENDAR', 'UPDATE_CHECKER', 0),
+	(55, 'configuration', 'DELETE_CALENDAR_CHECKER', 'CALENDAR', 'DELETE_CHECKER', 0),
+	(57, 'organisation', 'READ_CHARGE', 'CHARGE', 'READ', 0),
+	(58, 'organisation', 'CREATE_CHARGE', 'CHARGE', 'CREATE', 0),
+	(59, 'organisation', 'CREATE_CHARGE_CHECKER', 'CHARGE', 'CREATE_CHECKER', 0),
+	(60, 'organisation', 'UPDATE_CHARGE', 'CHARGE', 'UPDATE', 0),
+	(61, 'organisation', 'UPDATE_CHARGE_CHECKER', 'CHARGE', 'UPDATE_CHECKER', 0),
+	(62, 'organisation', 'DELETE_CHARGE', 'CHARGE', 'DELETE', 0),
+	(63, 'organisation', 'DELETE_CHARGE_CHECKER', 'CHARGE', 'DELETE_CHECKER', 0),
+	(64, 'organisation', 'READ_FUND', 'FUND', 'READ', 0),
+	(65, 'organisation', 'CREATE_FUND', 'FUND', 'CREATE', 0),
+	(66, 'organisation', 'CREATE_FUND_CHECKER', 'FUND', 'CREATE_CHECKER', 0),
+	(67, 'organisation', 'UPDATE_FUND', 'FUND', 'UPDATE', 0),
+	(68, 'organisation', 'UPDATE_FUND_CHECKER', 'FUND', 'UPDATE_CHECKER', 0),
+	(69, 'organisation', 'DELETE_FUND', 'FUND', 'DELETE', 0),
+	(70, 'organisation', 'DELETE_FUND_CHECKER', 'FUND', 'DELETE_CHECKER', 0),
+	(71, 'organisation', 'READ_LOANPRODUCT', 'LOANPRODUCT', 'READ', 0),
+	(72, 'organisation', 'CREATE_LOANPRODUCT', 'LOANPRODUCT', 'CREATE', 0),
+	(73, 'organisation', 'CREATE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'CREATE_CHECKER', 0),
+	(74, 'organisation', 'UPDATE_LOANPRODUCT', 'LOANPRODUCT', 'UPDATE', 0),
+	(75, 'organisation', 'UPDATE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'UPDATE_CHECKER', 0),
+	(76, 'organisation', 'DELETE_LOANPRODUCT', 'LOANPRODUCT', 'DELETE', 0),
+	(77, 'organisation', 'DELETE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'DELETE_CHECKER', 0),
+	(78, 'organisation', 'READ_OFFICE', 'OFFICE', 'READ', 0),
+	(79, 'organisation', 'CREATE_OFFICE', 'OFFICE', 'CREATE', 0),
+	(80, 'organisation', 'CREATE_OFFICE_CHECKER', 'OFFICE', 'CREATE_CHECKER', 0),
+	(81, 'organisation', 'UPDATE_OFFICE', 'OFFICE', 'UPDATE', 0),
+	(82, 'organisation', 'UPDATE_OFFICE_CHECKER', 'OFFICE', 'UPDATE_CHECKER', 0),
+	(83, 'organisation', 'READ_OFFICETRANSACTION', 'OFFICETRANSACTION', 'READ', 0),
+	(84, 'organisation', 'DELETE_OFFICE_CHECKER', 'OFFICE', 'DELETE_CHECKER', 0),
+	(85, 'organisation', 'CREATE_OFFICETRANSACTION', 'OFFICETRANSACTION', 'CREATE', 0),
+	(86, 'organisation', 'CREATE_OFFICETRANSACTION_CHECKER', 'OFFICETRANSACTION', 'CREATE_CHECKER', 0),
+	(87, 'organisation', 'DELETE_OFFICETRANSACTION', 'OFFICETRANSACTION', 'DELETE', 0),
+	(88, 'organisation', 'DELETE_OFFICETRANSACTION_CHECKER', 'OFFICETRANSACTION', 'DELETE_CHECKER', 0),
+	(89, 'organisation', 'READ_STAFF', 'STAFF', 'READ', 0),
+	(90, 'organisation', 'CREATE_STAFF', 'STAFF', 'CREATE', 0),
+	(91, 'organisation', 'CREATE_STAFF_CHECKER', 'STAFF', 'CREATE_CHECKER', 0),
+	(92, 'organisation', 'UPDATE_STAFF', 'STAFF', 'UPDATE', 0),
+	(93, 'organisation', 'UPDATE_STAFF_CHECKER', 'STAFF', 'UPDATE_CHECKER', 0),
+	(94, 'organisation', 'DELETE_STAFF', 'STAFF', 'DELETE', 0),
+	(95, 'organisation', 'DELETE_STAFF_CHECKER', 'STAFF', 'DELETE_CHECKER', 0),
+	(96, 'organisation', 'READ_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'READ', 0),
+	(97, 'organisation', 'CREATE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'CREATE', 0),
+	(98, 'organisation', 'CREATE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'CREATE_CHECKER', 0),
+	(99, 'organisation', 'UPDATE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'UPDATE', 0),
+	(100, 'organisation', 'UPDATE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'UPDATE_CHECKER', 0),
+	(101, 'organisation', 'DELETE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'DELETE', 0),
+	(102, 'organisation', 'DELETE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'DELETE_CHECKER', 0),
+	(103, 'portfolio', 'READ_LOAN', 'LOAN', 'READ', 0),
+	(104, 'portfolio', 'CREATE_LOAN', 'LOAN', 'CREATE', 0),
+	(105, 'portfolio', 'CREATE_LOAN_CHECKER', 'LOAN', 'CREATE_CHECKER', 0),
+	(106, 'portfolio', 'UPDATE_LOAN', 'LOAN', 'UPDATE', 0),
+	(107, 'portfolio', 'UPDATE_LOAN_CHECKER', 'LOAN', 'UPDATE_CHECKER', 0),
+	(108, 'portfolio', 'DELETE_LOAN', 'LOAN', 'DELETE', 0),
+	(109, 'portfolio', 'DELETE_LOAN_CHECKER', 'LOAN', 'DELETE_CHECKER', 0),
+	(110, 'portfolio', 'READ_CLIENT', 'CLIENT', 'READ', 0),
+	(111, 'portfolio', 'CREATE_CLIENT', 'CLIENT', 'CREATE', 0),
+	(112, 'portfolio', 'CREATE_CLIENT_CHECKER', 'CLIENT', 'CREATE_CHECKER', 0),
+	(113, 'portfolio', 'UPDATE_CLIENT', 'CLIENT', 'UPDATE', 0),
+	(114, 'portfolio', 'UPDATE_CLIENT_CHECKER', 'CLIENT', 'UPDATE_CHECKER', 0),
+	(115, 'portfolio', 'DELETE_CLIENT', 'CLIENT', 'DELETE', 0),
+	(116, 'portfolio', 'DELETE_CLIENT_CHECKER', 'CLIENT', 'DELETE_CHECKER', 0),
+	(117, 'portfolio', 'READ_CLIENTIMAGE', 'CLIENTIMAGE', 'READ', 0),
+	(118, 'portfolio', 'CREATE_CLIENTIMAGE', 'CLIENTIMAGE', 'CREATE', 0),
+	(119, 'portfolio', 'CREATE_CLIENTIMAGE_CHECKER', 'CLIENTIMAGE', 'CREATE_CHECKER', 0),
+	(120, 'portfolio', 'DELETE_CLIENTIMAGE', 'CLIENTIMAGE', 'DELETE', 0),
+	(121, 'portfolio', 'DELETE_CLIENTIMAGE_CHECKER', 'CLIENTIMAGE', 'DELETE_CHECKER', 0),
+	(122, 'portfolio', 'READ_CLIENTNOTE', 'CLIENTNOTE', 'READ', 0),
+	(123, 'portfolio', 'CREATE_CLIENTNOTE', 'CLIENTNOTE', 'CREATE', 0),
+	(124, 'portfolio', 'CREATE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'CREATE_CHECKER', 0),
+	(125, 'portfolio', 'UPDATE_CLIENTNOTE', 'CLIENTNOTE', 'UPDATE', 0),
+	(126, 'portfolio', 'UPDATE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'UPDATE_CHECKER', 0),
+	(127, 'portfolio', 'DELETE_CLIENTNOTE', 'CLIENTNOTE', 'DELETE', 0),
+	(128, 'portfolio', 'DELETE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'DELETE_CHECKER', 0),
+	(129, 'portfolio_group', 'READ_GROUPNOTE', 'GROUPNOTE', 'READ', 0),
+	(130, 'portfolio_group', 'CREATE_GROUPNOTE', 'GROUPNOTE', 'CREATE', 0),
+	(131, 'portfolio_group', 'UPDATE_GROUPNOTE', 'GROUPNOTE', 'UPDATE', 0),
+	(132, 'portfolio_group', 'DELETE_GROUPNOTE', 'GROUPNOTE', 'DELETE', 0),
+	(133, 'portfolio_group', 'CREATE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'CREATE_CHECKER', 0),
+	(134, 'portfolio_group', 'UPDATE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'UPDATE_CHECKER', 0),
+	(135, 'portfolio_group', 'DELETE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'DELETE_CHECKER', 0),
+	(136, 'portfolio', 'READ_LOANNOTE', 'LOANNOTE', 'READ', 0),
+	(137, 'portfolio', 'CREATE_LOANNOTE', 'LOANNOTE', 'CREATE', 0),
+	(138, 'portfolio', 'UPDATE_LOANNOTE', 'LOANNOTE', 'UPDATE', 0),
+	(139, 'portfolio', 'DELETE_LOANNOTE', 'LOANNOTE', 'DELETE', 0),
+	(140, 'portfolio', 'CREATE_LOANNOTE_CHECKER', 'LOANNOTE', 'CREATE_CHECKER', 0),
+	(141, 'portfolio', 'UPDATE_LOANNOTE_CHECKER', 'LOANNOTE', 'UPDATE_CHECKER', 0),
+	(142, 'portfolio', 'DELETE_LOANNOTE_CHECKER', 'LOANNOTE', 'DELETE_CHECKER', 0),
+	(143, 'portfolio', 'READ_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'READ', 0),
+	(144, 'portfolio', 'CREATE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'CREATE', 0),
+	(145, 'portfolio', 'UPDATE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'UPDATE', 0),
+	(146, 'portfolio', 'DELETE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'DELETE', 0),
+	(147, 'portfolio', 'CREATE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'CREATE_CHECKER', 0),
+	(148, 'portfolio', 'UPDATE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'UPDATE_CHECKER', 0),
+	(149, 'portfolio', 'DELETE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'DELETE_CHECKER', 0),
+	(150, 'portfolio', 'READ_SAVINGNOTE', 'SAVINGNOTE', 'READ', 0),
+	(151, 'portfolio', 'CREATE_SAVINGNOTE', 'SAVINGNOTE', 'CREATE', 0),
+	(152, 'portfolio', 'UPDATE_SAVINGNOTE', 'SAVINGNOTE', 'UPDATE', 0),
+	(153, 'portfolio', 'DELETE_SAVINGNOTE', 'SAVINGNOTE', 'DELETE', 0),
+	(154, 'portfolio', 'CREATE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'CREATE_CHECKER', 0),
+	(155, 'portfolio', 'UPDATE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'UPDATE_CHECKER', 0),
+	(156, 'portfolio', 'DELETE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'DELETE_CHECKER', 0),
+	(157, 'portfolio', 'READ_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'READ', 0),
+	(158, 'portfolio', 'CREATE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'CREATE', 0),
+	(159, 'portfolio', 'CREATE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'CREATE_CHECKER', 0),
+	(160, 'portfolio', 'UPDATE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'UPDATE', 0),
+	(161, 'portfolio', 'UPDATE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'UPDATE_CHECKER', 0),
+	(162, 'portfolio', 'DELETE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'DELETE', 0),
+	(163, 'portfolio', 'DELETE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'DELETE_CHECKER', 0),
+	(164, 'portfolio', 'READ_DOCUMENT', 'DOCUMENT', 'READ', 0),
+	(165, 'portfolio', 'CREATE_DOCUMENT', 'DOCUMENT', 'CREATE', 0),
+	(166, 'portfolio', 'CREATE_DOCUMENT_CHECKER', 'DOCUMENT', 'CREATE_CHECKER', 0),
+	(167, 'portfolio', 'UPDATE_DOCUMENT', 'DOCUMENT', 'UPDATE', 0),
+	(168, 'portfolio', 'UPDATE_DOCUMENT_CHECKER', 'DOCUMENT', 'UPDATE_CHECKER', 0),
+	(169, 'portfolio', 'DELETE_DOCUMENT', 'DOCUMENT', 'DELETE', 0),
+	(170, 'portfolio', 'DELETE_DOCUMENT_CHECKER', 'DOCUMENT', 'DELETE_CHECKER', 0),
+	(171, 'portfolio_group', 'READ_GROUP', 'GROUP', 'READ', 0),
+	(172, 'portfolio_group', 'CREATE_GROUP', 'GROUP', 'CREATE', 0),
+	(173, 'portfolio_group', 'CREATE_GROUP_CHECKER', 'GROUP', 'CREATE_CHECKER', 0),
+	(174, 'portfolio_group', 'UPDATE_GROUP', 'GROUP', 'UPDATE', 0),
+	(175, 'portfolio_group', 'UPDATE_GROUP_CHECKER', 'GROUP', 'UPDATE_CHECKER', 0),
+	(176, 'portfolio_group', 'DELETE_GROUP', 'GROUP', 'DELETE', 0),
+	(177, 'portfolio_group', 'DELETE_GROUP_CHECKER', 'GROUP', 'DELETE_CHECKER', 0),
+	(178, 'portfolio_group', 'UNASSIGNSTAFF_GROUP', 'GROUP', 'UNASSIGNSTAFF', 0),
+	(179, 'portfolio_group', 'UNASSIGNSTAFF_GROUP_CHECKER', 'GROUP', 'UNASSIGNSTAFF_CHECKER', 0),
+	(180, 'portfolio', 'CREATE_LOANCHARGE', 'LOANCHARGE', 'CREATE', 0),
+	(181, 'portfolio', 'CREATE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'CREATE_CHECKER', 0),
+	(182, 'portfolio', 'UPDATE_LOANCHARGE', 'LOANCHARGE', 'UPDATE', 0),
+	(183, 'portfolio', 'UPDATE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'UPDATE_CHECKER', 0),
+	(184, 'portfolio', 'DELETE_LOANCHARGE', 'LOANCHARGE', 'DELETE', 0),
+	(185, 'portfolio', 'DELETE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'DELETE_CHECKER', 0),
+	(186, 'portfolio', 'WAIVE_LOANCHARGE', 'LOANCHARGE', 'WAIVE', 0),
+	(187, 'portfolio', 'WAIVE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'WAIVE_CHECKER', 0),
+	(188, 'portfolio', 'READ_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'READ', 0),
+	(189, 'portfolio', 'CREATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CREATE', 0),
+	(190, 'portfolio', 'CREATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CREATE_CHECKER', 0),
+	(191, 'portfolio', 'UPDATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UPDATE', 0),
+	(192, 'portfolio', 'UPDATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UPDATE_CHECKER', 0),
+	(193, 'portfolio', 'DELETE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DELETE', 0),
+	(194, 'portfolio', 'DELETE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DELETE_CHECKER', 0),
+	(195, 'portfolio', 'READ_GUARANTOR', 'GUARANTOR', 'READ', 0),
+	(196, 'portfolio', 'CREATE_GUARANTOR', 'GUARANTOR', 'CREATE', 0),
+	(197, 'portfolio', 'CREATE_GUARANTOR_CHECKER', 'GUARANTOR', 'CREATE_CHECKER', 0),
+	(198, 'portfolio', 'UPDATE_GUARANTOR', 'GUARANTOR', 'UPDATE', 0),
+	(199, 'portfolio', 'UPDATE_GUARANTOR_CHECKER', 'GUARANTOR', 'UPDATE_CHECKER', 0),
+	(200, 'portfolio', 'DELETE_GUARANTOR', 'GUARANTOR', 'DELETE', 0),
+	(201, 'portfolio', 'DELETE_GUARANTOR_CHECKER', 'GUARANTOR', 'DELETE_CHECKER', 0),
+	(202, 'portfolio', 'READ_COLLATERAL', 'COLLATERAL', 'READ', 0),
+	(203, 'portfolio', 'CREATE_COLLATERAL', 'COLLATERAL', 'CREATE', 0),
+	(204, 'portfolio', 'UPDATE_COLLATERAL', 'COLLATERAL', 'UPDATE', 0),
+	(205, 'portfolio', 'DELETE_COLLATERAL', 'COLLATERAL', 'DELETE', 0),
+	(206, 'portfolio', 'CREATE_COLLATERAL_CHECKER', 'COLLATERAL', 'CREATE_CHECKER', 0),
+	(207, 'portfolio', 'UPDATE_COLLATERAL_CHECKER', 'COLLATERAL', 'UPDATE_CHECKER', 0),
+	(208, 'portfolio', 'DELETE_COLLATERAL_CHECKER', 'COLLATERAL', 'DELETE_CHECKER', 0),
+	(209, 'transaction_loan', 'APPROVE_LOAN', 'LOAN', 'APPROVE', 0),
+	(211, 'transaction_loan', 'REJECT_LOAN', 'LOAN', 'REJECT', 0),
+	(213, 'transaction_loan', 'WITHDRAW_LOAN', 'LOAN', 'WITHDRAW', 0),
+	(215, 'transaction_loan', 'APPROVALUNDO_LOAN', 'LOAN', 'APPROVALUNDO', 0),
+	(216, 'transaction_loan', 'DISBURSE_LOAN', 'LOAN', 'DISBURSE', 0),
+	(218, 'transaction_loan', 'DISBURSALUNDO_LOAN', 'LOAN', 'DISBURSALUNDO', 0),
+	(219, 'transaction_loan', 'REPAYMENT_LOAN', 'LOAN', 'REPAYMENT', 0),
+	(221, 'transaction_loan', 'ADJUST_LOAN', 'LOAN', 'ADJUST', 0),
+	(222, 'transaction_loan', 'WAIVEINTERESTPORTION_LOAN', 'LOAN', 'WAIVEINTERESTPORTION', 0),
+	(223, 'transaction_loan', 'WRITEOFF_LOAN', 'LOAN', 'WRITEOFF', 0),
+	(224, 'transaction_loan', 'CLOSE_LOAN', 'LOAN', 'CLOSE', 0),
+	(225, 'transaction_loan', 'CLOSEASRESCHEDULED_LOAN', 'LOAN', 'CLOSEASRESCHEDULED', 0),
+	(226, 'transaction_loan', 'UPDATELOANOFFICER_LOAN', 'LOAN', 'UPDATELOANOFFICER', 0),
+	(227, 'transaction_loan', 'UPDATELOANOFFICER_LOAN_CHECKER', 'LOAN', 'UPDATELOANOFFICER_CHECKER', 0),
+	(228, 'transaction_loan', 'REMOVELOANOFFICER_LOAN', 'LOAN', 'REMOVELOANOFFICER', 0),
+	(229, 'transaction_loan', 'REMOVELOANOFFICER_LOAN_CHECKER', 'LOAN', 'REMOVELOANOFFICER_CHECKER', 0),
+	(230, 'transaction_loan', 'BULKREASSIGN_LOAN', 'LOAN', 'BULKREASSIGN', 0),
+	(231, 'transaction_loan', 'BULKREASSIGN_LOAN_CHECKER', 'LOAN', 'BULKREASSIGN_CHECKER', 0),
+	(232, 'transaction_loan', 'APPROVE_LOAN_CHECKER', 'LOAN', 'APPROVE_CHECKER', 0),
+	(234, 'transaction_loan', 'REJECT_LOAN_CHECKER', 'LOAN', 'REJECT_CHECKER', 0),
+	(236, 'transaction_loan', 'WITHDRAW_LOAN_CHECKER', 'LOAN', 'WITHDRAW_CHECKER', 0),
+	(238, 'transaction_loan', 'APPROVALUNDO_LOAN_CHECKER', 'LOAN', 'APPROVALUNDO_CHECKER', 0),
+	(239, 'transaction_loan', 'DISBURSE_LOAN_CHECKER', 'LOAN', 'DISBURSE_CHECKER', 0),
+	(241, 'transaction_loan', 'DISBURSALUNDO_LOAN_CHECKER', 'LOAN', 'DISBURSALUNDO_CHECKER', 0),
+	(242, 'transaction_loan', 'REPAYMENT_LOAN_CHECKER', 'LOAN', 'REPAYMENT_CHECKER', 0),
+	(244, 'transaction_loan', 'ADJUST_LOAN_CHECKER', 'LOAN', 'ADJUST_CHECKER', 0),
+	(245, 'transaction_loan', 'WAIVEINTERESTPORTION_LOAN_CHECKER', 'LOAN', 'WAIVEINTERESTPORTION_CHECKER', 0),
+	(246, 'transaction_loan', 'WRITEOFF_LOAN_CHECKER', 'LOAN', 'WRITEOFF_CHECKER', 0),
+	(247, 'transaction_loan', 'CLOSE_LOAN_CHECKER', 'LOAN', 'CLOSE_CHECKER', 0),
+	(248, 'transaction_loan', 'CLOSEASRESCHEDULED_LOAN_CHECKER', 'LOAN', 'CLOSEASRESCHEDULED_CHECKER', 0),
+	(249, 'transaction_savings', 'DEPOSIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DEPOSIT', 0),
+	(250, 'transaction_savings', 'DEPOSIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DEPOSIT_CHECKER', 0),
+	(251, 'transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAWAL', 0),
+	(252, 'transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAWAL_CHECKER', 0),
+	(253, 'transaction_savings', 'ACTIVATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ACTIVATE', 0),
+	(254, 'transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE_CHECKER', 0),
+	(255, 'transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', 0),
+	(256, 'transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CALCULATEINTEREST_CHECKER', 0),
+	(257, 'accounting', 'CREATE_GLACCOUNT', 'GLACCOUNT', 'CREATE', 0),
+	(258, 'accounting', 'UPDATE_GLACCOUNT', 'GLACCOUNT', 'UPDATE', 0),
+	(259, 'accounting', 'DELETE_GLACCOUNT', 'GLACCOUNT', 'DELETE', 0),
+	(260, 'accounting', 'CREATE_GLCLOSURE', 'GLCLOSURE', 'CREATE', 0),
+	(261, 'accounting', 'UPDATE_GLCLOSURE', 'GLCLOSURE', 'UPDATE', 0),
+	(262, 'accounting', 'DELETE_GLCLOSURE', 'GLCLOSURE', 'DELETE', 0),
+	(263, 'accounting', 'CREATE_JOURNALENTRY', 'JOURNALENTRY', 'CREATE', 0),
+	(264, 'accounting', 'REVERSE_JOURNALENTRY', 'JOURNALENTRY', 'REVERSE', 0),
+	(265, 'report', 'READ_Active Loans - Details', 'Active Loans - Details', 'READ', 0),
+	(266, 'report', 'READ_Active Loans - Summary', 'Active Loans - Summary', 'READ', 0),
+	(267, 'report', 'READ_Active Loans by Disbursal Period', 'Active Loans by Disbursal Period', 'READ', 0),
+	(268, 'report', 'READ_Active Loans in last installment', 'Active Loans in last installment', 'READ', 0),
+	(269, 'report', 'READ_Active Loans in last installment Summary', 'Active Loans in last installment Summary', 'READ', 0),
+	(270, 'report', 'READ_Active Loans Passed Final Maturity', 'Active Loans Passed Final Maturity', 'READ', 0),
+	(271, 'report', 'READ_Active Loans Passed Final Maturity Summary', 'Active Loans Passed Final Maturity Summary', 'READ', 0),
+	(272, 'report', 'READ_Aging Detail', 'Aging Detail', 'READ', 0),
+	(273, 'report', 'READ_Aging Summary (Arrears in Months)', 'Aging Summary (Arrears in Months)', 'READ', 0),
+	(274, 'report', 'READ_Aging Summary (Arrears in Weeks)', 'Aging Summary (Arrears in Weeks)', 'READ', 0),
+	(275, 'report', 'READ_Balance Sheet', 'Balance Sheet', 'READ', 0),
+	(276, 'report', 'READ_Branch Expected Cash Flow', 'Branch Expected Cash Flow', 'READ', 0),
+	(277, 'report', 'READ_Client Listing', 'Client Listing', 'READ', 0),
+	(278, 'report', 'READ_Client Loans Listing', 'Client Loans Listing', 'READ', 0),
+	(279, 'report', 'READ_Expected Payments By Date - Basic', 'Expected Payments By Date - Basic', 'READ', 0),
+	(280, 'report', 'READ_Expected Payments By Date - Formatted', 'Expected Payments By Date - Formatted', 'READ', 0),
+	(281, 'report', 'READ_Funds Disbursed Between Dates Summary', 'Funds Disbursed Between Dates Summary', 'READ', 0),
+	(282, 'report', 'READ_Funds Disbursed Between Dates Summary by Office', 'Funds Disbursed Between Dates Summary by Office', 'READ', 0),
+	(283, 'report', 'READ_Income Statement', 'Income Statement', 'READ', 0),
+	(284, 'report', 'READ_Loan Account Schedule', 'Loan Account Schedule', 'READ', 0),
+	(285, 'report', 'READ_Loans Awaiting Disbursal', 'Loans Awaiting Disbursal', 'READ', 0),
+	(286, 'report', 'READ_Loans Awaiting Disbursal Summary', 'Loans Awaiting Disbursal Summary', 'READ', 0),
+	(287, 'report', 'READ_Loans Awaiting Disbursal Summary by Month', 'Loans Awaiting Disbursal Summary by Month', 'READ', 0),
+	(288, 'report', 'READ_Loans Pending Approval', 'Loans Pending Approval', 'READ', 0),
+	(289, 'report', 'READ_Obligation Met Loans Details', 'Obligation Met Loans Details', 'READ', 0),
+	(290, 'report', 'READ_Obligation Met Loans Summary', 'Obligation Met Loans Summary', 'READ', 0),
+	(291, 'report', 'READ_Portfolio at Risk', 'Portfolio at Risk', 'READ', 0),
+	(292, 'report', 'READ_Portfolio at Risk by Branch', 'Portfolio at Risk by Branch', 'READ', 0),
+	(293, 'report', 'READ_Rescheduled Loans', 'Rescheduled Loans', 'READ', 0),
+	(294, 'report', 'READ_Trial Balance', 'Trial Balance', 'READ', 0),
+	(295, 'report', 'READ_Written-Off Loans', 'Written-Off Loans', 'READ', 0),
+	(296, 'transaction_savings', 'POSTINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'POSTINTEREST', 1),
+	(297, 'transaction_savings', 'POSTINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'POSTINTEREST_CHECKER', 0),
+	(298, 'portfolio_center', 'READ_CENTER', 'CENTER', 'READ', 0),
+	(299, 'portfolio_center', 'CREATE_CENTER', 'CENTER', 'CREATE', 0),
+	(300, 'portfolio_center', 'CREATE_CENTER_CHECKER', 'CENTER', 'CREATE_CHECKER', 0),
+	(301, 'portfolio_center', 'UPDATE_CENTER', 'CENTER', 'UPDATE', 0),
+	(302, 'portfolio_center', 'UPDATE_CENTER_CHECKER', 'CENTER', 'UPDATE_CHECKER', 0),
+	(303, 'portfolio_center', 'DELETE_CENTER', 'CENTER', 'DELETE', 0),
+	(304, 'portfolio_center', 'DELETE_CENTER_CHECKER', 'CENTER', 'DELETE_CHECKER', 0),
+	(305, 'configuration', 'READ_REPORT', 'REPORT', 'READ', 0),
+	(306, 'configuration', 'CREATE_REPORT', 'REPORT', 'CREATE', 0),
+	(307, 'configuration', 'CREATE_REPORT_CHECKER', 'REPORT', 'CREATE_CHECKER', 0),
+	(308, 'configuration', 'UPDATE_REPORT', 'REPORT', 'UPDATE', 0),
+	(309, 'configuration', 'UPDATE_REPORT_CHECKER', 'REPORT', 'UPDATE_CHECKER', 0),
+	(310, 'configuration', 'DELETE_REPORT', 'REPORT', 'DELETE', 0),
+	(311, 'configuration', 'DELETE_REPORT_CHECKER', 'REPORT', 'DELETE_CHECKER', 0),
+	(312, 'portfolio', 'ACTIVATE_CLIENT', 'CLIENT', 'ACTIVATE', 1),
+	(313, 'portfolio', 'ACTIVATE_CLIENT_CHECKER', 'CLIENT', 'ACTIVATE_CHECKER', 0),
+	(314, 'portfolio_center', 'ACTIVATE_CENTER', 'CENTER', 'ACTIVATE', 1),
+	(315, 'portfolio_center', 'ACTIVATE_CENTER_CHECKER', 'CENTER', 'ACTIVATE_CHECKER', 0),
+	(316, 'portfolio_group', 'ACTIVATE_GROUP', 'GROUP', 'ACTIVATE', 1),
+	(317, 'portfolio_group', 'ACTIVATE_GROUP_CHECKER', 'GROUP', 'ACTIVATE_CHECKER', 0),
+	(318, 'portfolio_group', 'ASSOCIATECLIENTS_GROUP', 'GROUP', 'ASSOCIATECLIENTS', 0),
+	(319, 'portfolio_group', 'DISASSOCIATECLIENTS_GROUP', 'GROUP', 'DISASSOCIATECLIENTS', 0),
+	(320, 'portfolio_group', 'SAVECOLLECTIONSHEET_GROUP', 'GROUP', 'SAVECOLLECTIONSHEET', 0),
+	(321, 'portfolio_center', 'SAVECOLLECTIONSHEET_CENTER', 'CENTER', 'SAVECOLLECTIONSHEET', 0),
+	(323, 'accounting', 'DELETE_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'DELETE', 0),
+	(324, 'accounting', 'CREATE_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'CREATE', 0),
+	(325, 'accounting', 'UPDATE_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'UPDATE', 0),
+	(326, 'report', 'READ_GroupSummaryCounts', 'GroupSummaryCounts', 'READ', 0),
+	(327, 'report', 'READ_GroupSummaryAmounts', 'GroupSummaryAmounts', 'READ', 0),
+	(328, 'configuration', 'CREATE_DATATABLE', 'DATATABLE', 'CREATE', 0),
+	(329, 'configuration', 'CREATE_DATATABLE_CHECKER', 'DATATABLE', 'CREATE_CHECKER', 0),
+	(330, 'configuration', 'UPDATE_DATATABLE', 'DATATABLE', 'UPDATE', 0),
+	(331, 'configuration', 'UPDATE_DATATABLE_CHECKER', 'DATATABLE', 'UPDATE_CHECKER', 0),
+	(332, 'configuration', 'DELETE_DATATABLE', 'DATATABLE', 'DELETE', 0),
+	(333, 'configuration', 'DELETE_DATATABLE_CHECKER', 'DATATABLE', 'DELETE_CHECKER', 0),
+	(334, 'organisation', 'CREATE_HOLIDAY', 'HOLIDAY', 'CREATE', 0),
+	(335, 'portfolio_group', 'ASSIGNROLE_GROUP', 'GROUP', 'ASSIGNROLE', 0),
+	(336, 'portfolio_group', 'UNASSIGNROLE_GROUP', 'GROUP', 'UNASSIGNROLE', 0),
+	(337, 'portfolio_group', 'UPDATEROLE_GROUP', 'GROUP', 'UPDATEROLE', 0),
+	(346, 'report', 'READ_TxnRunningBalances', 'TxnRunningBalances', 'READ', 0),
+	(347, 'portfolio', 'UNASSIGNSTAFF_CLIENT', 'CLIENT', 'UNASSIGNSTAFF', 0),
+	(348, 'portfolio', 'ASSIGNSTAFF_CLIENT', 'CLIENT', 'ASSIGNSTAFF', 0),
+	(349, 'portfolio', 'CLOSE_CLIENT', 'CLIENT', 'CLOSE', 1),
+	(350, 'report', 'READ_FieldAgentStats', 'FieldAgentStats', 'READ', 0),
+	(351, 'report', 'READ_FieldAgentPrograms', 'FieldAgentPrograms', 'READ', 0),
+	(352, 'report', 'READ_ProgramDetails', 'ProgramDetails', 'READ', 0),
+	(353, 'report', 'READ_ChildrenStaffList', 'ChildrenStaffList', 'READ', 0),
+	(354, 'report', 'READ_CoordinatorStats', 'CoordinatorStats', 'READ', 0),
+	(355, 'report', 'READ_BranchManagerStats', 'BranchManagerStats', 'READ', 0),
+	(356, 'report', 'READ_ProgramDirectorStats', 'ProgramDirectorStats', 'READ', 0),
+	(357, 'report', 'READ_ProgramStats', 'ProgramStats', 'READ', 0),
+	(358, 'transaction_savings', 'APPROVE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVE', 1),
+	(359, 'transaction_savings', 'REJECT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'REJECT', 1),
+	(360, 'transaction_savings', 'WITHDRAW_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAW', 1),
+	(361, 'transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVALUNDO', 1),
+	(362, 'transaction_savings', 'CLOSE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CLOSE', 1),
+	(363, 'transaction_savings', 'APPROVE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVE_CHECKER', 0),
+	(364, 'transaction_savings', 'REJECT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'REJECT_CHECKER', 0),
+	(365, 'transaction_savings', 'WITHDRAW_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAW_CHECKER', 0),
+	(366, 'transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVALUNDO_CHECKER', 0),
+	(367, 'transaction_savings', 'CLOSE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CLOSE_CHECKER', 0),
+	(368, 'transaction_savings', 'UNDOTRANSACTION_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UNDOTRANSACTION', 1),
+	(369, 'transaction_savings', 'UNDOTRANSACTION_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UNDOTRANSACTION_CHECKER', 0),
+	(370, 'portfolio', 'CREATE_PRODUCTMIX', 'PRODUCTMIX', 'CREATE', 0),
+	(371, 'portfolio', 'UPDATE_PRODUCTMIX', 'PRODUCTMIX', 'UPDATE', 0),
+	(372, 'portfolio', 'DELETE_PRODUCTMIX', 'PRODUCTMIX', 'DELETE', 0),
+	(373, 'jobs', 'UPDATE_SCHEDULER', 'SCHEDULER', 'UPDATE', 0),
+	(374, 'transaction_savings', 'APPLYANNUALFEE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPLYANNUALFEE', 1),
+	(375, 'transaction_savings', 'APPLYANNUALFEE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPLYANNUALFEE_CHECKER', 0),
+	(376, 'portfolio_group', 'ASSIGNSTAFF_GROUP', 'GROUP', 'ASSIGNSTAFF', 0),
+	(377, 'transaction_savings', 'READ_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'READ', 0),
+	(378, 'transaction_savings', 'CREATE_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'CREATE', 1),
+	(379, 'transaction_savings', 'CREATE_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'CREATE_CHECKER', 0),
+	(380, 'transaction_savings', 'ADJUSTTRANSACTION_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ADJUSTTRANSACTION', 0),
+	(381, 'portfolio', 'CREATE_MEETING', 'MEETING', 'CREATE', 0),
+	(382, 'portfolio', 'UPDATE_MEETING', 'MEETING', 'UPDATE', 0),
+	(383, 'portfolio', 'DELETE_MEETING', 'MEETING', 'DELETE', 0),
+	(384, 'portfolio', 'SAVEORUPDATEATTENDANCE_MEETING', 'MEETING', 'SAVEORUPDATEATTENDANCE', 0),
+	(385, 'portfolio_group', 'TRANSFERCLIENTS_GROUP', 'GROUP', 'TRANSFERCLIENTS', 0),
+	(386, 'portfolio_group', 'TRANSFERCLIENTS_GROUP_CHECKER', 'GROUP', 'TRANSFERCLIENTS_CHECKER', 0),
+	(389, 'portfolio', 'PROPOSETRANSFER_CLIENT', 'CLIENT', 'PROPOSETRANSFER', 0),
+	(390, 'portfolio', 'PROPOSETRANSFER_CLIENT_CHECKER', 'CLIENT', 'PROPOSETRANSFER_CHECKER', 0),
+	(391, 'portfolio', 'ACCEPTTRANSFER_CLIENT', 'CLIENT', 'ACCEPTTRANSFER', 0),
+	(392, 'portfolio', 'ACCEPTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'ACCEPTTRANSFER_CHECKER', 0),
+	(393, 'portfolio', 'REJECTTRANSFER_CLIENT', 'CLIENT', 'REJECTTRANSFER', 0),
+	(394, 'portfolio', 'REJECTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'REJECTTRANSFER_CHECKER', 0),
+	(395, 'portfolio', 'WITHDRAWTRANSFER_CLIENT', 'CLIENT', 'WITHDRAWTRANSFER', 0),
+	(396, 'portfolio', 'WITHDRAWTRANSFER_CLIENT_CHECKER', 'CLIENT', 'WITHDRAWTRANSFER_CHECKER', 0),
+	(397, 'portfolio', 'CLOSE_GROUP', 'GROUP', 'CLOSE', 1),
+	(398, 'portfolio', 'CLOSE_CENTER', 'CENTER', 'CLOSE', 1),
+	(399, 'xbrlmapping', 'UPDATE_XBRLMAPPING', 'XBRLMAPPING', 'UPDATE', 0),
+	(400, 'configuration', 'READ_CACHE', 'CACHE', 'READ', 0),
+	(401, 'configuration', 'UPDATE_CACHE', 'CACHE', 'UPDATE', 0),
+	(402, 'transaction_loan', 'PAY_LOANCHARGE', 'LOANCHARGE', 'PAY', 0),
+	(403, 'portfolio', 'CREATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'CREATE', 0),
+	(404, 'portfolio', 'CREATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'CREATE_CHECKER', 0),
+	(405, 'portfolio', 'UPDATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'UPDATE', 0),
+	(406, 'portfolio', 'UPDATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'UPDATE_CHECKER', 0),
+	(407, 'portfolio', 'DELETE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'DELETE', 0),
+	(408, 'portfolio', 'DELETE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'DELETE_CHECKER', 0),
+	(409, 'portfolio', 'WAIVE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'WAIVE', 0),
+	(410, 'portfolio', 'WAIVE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'WAIVE_CHECKER', 0),
+	(411, 'portfolio', 'PAY_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'PAY', 0),
+	(412, 'portfolio', 'PAY_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'PAY_CHECKER', 0),
+	(413, 'portfolio', 'PROPOSEANDACCEPTTRANSFER_CLIENT', 'CLIENT', 'PROPOSEANDACCEPTTRANSFER', 0),
+	(414, 'portfolio', 'PROPOSEANDACCEPTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'PROPOSEANDACCEPTTRANSFER_CHECKER', 0),
+	(415, 'organisation', 'DELETE_TEMPLATE', 'TEMPLATE', 'DELETE', 0),
+	(416, 'organisation', 'CREATE_TEMPLATE', 'TEMPLATE', 'CREATE', 0),
+	(417, 'organisation', 'UPDATE_TEMPLATE', 'TEMPLATE', 'UPDATE', 0),
+	(418, 'organisation', 'READ_TEMPLATE', 'TEMPLATE', 'READ', 0),
+	(419, 'accounting', 'UPDATERUNNINGBALANCE_JOURNALENTRY', 'JOURNALENTRY', 'UPDATERUNNINGBALANCE', 0),
+	(420, 'organisation', 'READ_SMS', 'SMS', 'READ', 0),
+	(421, 'organisation', 'CREATE_SMS', 'SMS', 'CREATE', 0),
+	(422, 'organisation', 'CREATE_SMS_CHECKER', 'SMS', 'CREATE_CHECKER', 0),
+	(423, 'organisation', 'UPDATE_SMS', 'SMS', 'UPDATE', 0),
+	(424, 'organisation', 'UPDATE_SMS_CHECKER', 'SMS', 'UPDATE_CHECKER', 0),
+	(425, 'organisation', 'DELETE_SMS', 'SMS', 'DELETE', 0),
+	(426, 'organisation', 'DELETE_SMS_CHECKER', 'SMS', 'DELETE_CHECKER', 0),
+	(427, 'organisation', 'CREATE_HOLIDAY_CHECKER', 'HOLIDAY', 'CREATE_CHECKER', 0),
+	(428, 'organisation', 'ACTIVATE_HOLIDAY', 'HOLIDAY', 'ACTIVATE', 0),
+	(429, 'organisation', 'ACTIVATE_HOLIDAY_CHECKER', 'HOLIDAY', 'ACTIVATE_CHECKER', 0),
+	(430, 'organisation', 'UPDATE_HOLIDAY', 'HOLIDAY', 'UPDATE', 0),
+	(431, 'organisation', 'UPDATE_HOLIDAY_CHECKER', 'HOLIDAY', 'UPDATE_CHECKER', 0),
+	(432, 'organisation', 'DELETE_HOLIDAY', 'HOLIDAY', 'DELETE', 0),
+	(433, 'organisation', 'DELETE_HOLIDAY_CHECKER', 'HOLIDAY', 'DELETE_CHECKER', 0),
+	(434, 'transaction_loan', 'UNDOWRITEOFF_LOAN', 'LOAN', 'UNDOWRITEOFF', 0),
+	(435, 'portfolio', 'READ_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'READ', 0),
+	(436, 'accounting', 'CREATE_JOURNALENTRY_CHECKER', 'JOURNALENTRY', 'CREATE_CHECKER', 0),
+	(437, 'portfolio', 'UPDATE_DISBURSEMENTDETAIL', 'DISBURSEMENTDETAIL', 'UPDATE', 0),
+	(438, 'portfolio', 'UPDATESAVINGSACCOUNT_CLIENT', 'CLIENT', 'UPDATESAVINGSACCOUNT', 0),
+	(439, 'accounting', 'READ_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'READ', 0),
+	(440, 'accounting', 'READ_JOURNALENTRY', 'JOURNALENTRY', 'READ', 0),
+	(441, 'accounting', 'READ_GLACCOUNT', 'GLACCOUNT', 'READ', 0),
+	(442, 'accounting', 'READ_GLCLOSURE', 'GLCLOSURE', 'READ', 0),
+	(443, 'organisation', 'READ_HOLIDAY', 'HOLIDAY', 'READ', 0),
+	(444, 'jobs', 'READ_SCHEDULER', 'SCHEDULER', 'READ', 0),
+	(445, 'portfolio', 'READ_PRODUCTMIX', 'PRODUCTMIX', 'READ', 0),
+	(446, 'portfolio', 'READ_MEETING', 'MEETING', 'READ', 0),
+	(447, 'jobs', 'EXECUTEJOB_SCHEDULER', 'SCHEDULER', 'EXECUTEJOB', 0),
+	(448, 'account_transfer', 'READ_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'READ', 0),
+	(449, 'account_transfer', 'CREATE_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'CREATE', 0),
+	(450, 'account_transfer', 'UPDATE_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'UPDATE', 0),
+	(451, 'account_transfer', 'DELETE_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'DELETE', 0),
+	(452, 'portfolio', 'CREATE_INTERESTRATECHART', 'INTERESTRATECHART', 'CREATE', 0),
+	(453, 'portfolio', 'CREATE_INTERESTRATECHART_CHECKER', 'INTERESTRATECHART', 'CREATE_CHECKER', 0),
+	(454, 'portfolio', 'UPDATE_INTERESTRATECHART', 'INTERESTRATECHART', 'UPDATE', 0),
+	(455, 'portfolio', 'DELETE_INTERESTRATECHART', 'INTERESTRATECHART', 'DELETE', 0),
+	(456, 'portfolio', 'UPDATE_INTERESTRATECHART_CHECKER', 'INTERESTRATECHART', 'UPDATE_CHECKER', 0),
+	(457, 'portfolio', 'DELETE_INTERESTRATECHART_CHECKER', 'INTERESTRATECHART', 'DELETE_CHECKER', 0),
+	(458, 'portfolio', 'CREATE_CHARTSLAB', 'CHARTSLAB', 'CREATE', 0),
+	(459, 'portfolio', 'CREATE_CHARTSLAB_CHECKER', 'CHARTSLAB', 'CREATE_CHECKER', 0),
+	(460, 'portfolio', 'UPDATE_CHARTSLAB', 'CHARTSLAB', 'UPDATE', 0),
+	(461, 'portfolio', 'DELETE_CHARTSLAB', 'CHARTSLAB', 'DELETE', 0),
+	(462, 'portfolio', 'UPDATE_CHARTSLAB_CHECKER', 'CHARTSLAB', 'UPDATE_CHECKER', 0),
+	(463, 'portfolio', 'DELETE_CHARTSLAB_CHECKER', 'CHARTSLAB', 'DELETE_CHECKER', 0),
+	(464, 'portfolio', 'CREATE_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'CREATE', 0),
+	(465, 'portfolio', 'CREATE_FIXEDDEPOSITPRODUCT_CHECKER', 'FIXEDDEPOSITPRODUCT', 'CREATE_CHECKER', 0),
+	(466, 'portfolio', 'UPDATE_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'UPDATE', 0),
+	(467, 'portfolio', 'DELETE_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'DELETE', 0),
+	(468, 'portfolio', 'UPDATE_FIXEDDEPOSITPRODUCT_CHECKER', 'FIXEDDEPOSITPRODUCT', 'UPDATE_CHECKER', 0),
+	(469, 'portfolio', 'DELETE_FIXEDDEPOSITPRODUCT_CHECKER', 'FIXEDDEPOSITPRODUCT', 'DELETE_CHECKER', 0),
+	(470, 'portfolio', 'CREATE_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'CREATE', 0),
+	(471, 'portfolio', 'CREATE_RECURRINGDEPOSITPRODUCT_CHECKER', 'RECURRINGDEPOSITPRODUCT', 'CREATE_CHECKER', 0),
+	(472, 'portfolio', 'UPDATE_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'UPDATE', 0),
+	(473, 'portfolio', 'DELETE_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'DELETE', 0),
+	(474, 'portfolio', 'UPDATE_RECURRINGDEPOSITPRODUCT_CHECKER', 'RECURRINGDEPOSITPRODUCT', 'UPDATE_CHECKER', 0),
+	(475, 'portfolio', 'DELETE_RECURRINGDEPOSITPRODUCT_CHECKER', 'RECURRINGDEPOSITPRODUCT', 'DELETE_CHECKER', 0),
+	(476, 'portfolio', 'READ_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'READ', 0),
+	(477, 'portfolio', 'CREATE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'CREATE', 0),
+	(478, 'portfolio', 'CREATE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'CREATE_CHECKER', 0),
+	(479, 'portfolio', 'UPDATE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'UPDATE', 0),
+	(480, 'portfolio', 'UPDATE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'UPDATE_CHECKER', 0),
+	(481, 'portfolio', 'DELETE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'DELETE', 0),
+	(482, 'portfolio', 'DELETE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'DELETE_CHECKER', 0),
+	(483, 'transaction_savings', 'DEPOSIT_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'DEPOSIT', 0),
+	(484, 'transaction_savings', 'DEPOSIT_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'DEPOSIT_CHECKER', 0),
+	(485, 'transaction_savings', 'WITHDRAWAL_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'WITHDRAWAL', 0),
+	(486, 'transaction_savings', 'WITHDRAWAL_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'WITHDRAWAL_CHECKER', 0),
+	(487, 'transaction_savings', 'ACTIVATE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'ACTIVATE', 0),
+	(488, 'transaction_savings', 'ACTIVATE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'ACTIVATE_CHECKER', 0),
+	(489, 'transaction_savings', 'CALCULATEINTEREST_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'CALCULATEINTEREST', 0),
+	(490, 'transaction_savings', 'CALCULATEINTEREST_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'CALCULATEINTEREST_CHECKER', 0),
+	(491, 'transaction_savings', 'POSTINTEREST_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'POSTINTEREST', 1),
+	(492, 'transaction_savings', 'POSTINTEREST_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'POSTINTEREST_CHECKER', 0),
+	(493, 'transaction_savings', 'APPROVE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'APPROVE', 1),
+	(494, 'transaction_savings', 'REJECT_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'REJECT', 1),
+	(495, 'transaction_savings', 'WITHDRAW_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'WITHDRAW', 1),
+	(496, 'transaction_savings', 'APPROVALUNDO_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'APPROVALUNDO', 1),
+	(497, 'transaction_savings', 'CLOSE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'CLOSE', 1),
+	(498, 'transaction_savings', 'APPROVE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'APPROVE_CHECKER', 0),
+	(499, 'transaction_savings', 'REJECT_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'REJECT_CHECKER', 0),
+	(500, 'transaction_savings', 'WITHDRAW_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'WITHDRAW_CHECKER', 0),
+	(501, 'transaction_savings', 'APPROVALUNDO_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'APPROVALUNDO_CHECKER', 0),
+	(502, 'transaction_savings', 'CLOSE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'CLOSE_CHECKER', 0),
+	(503, 'transaction_savings', 'UNDOTRANSACTION_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'UNDOTRANSACTION', 1),
+	(504, 'transaction_savings', 'UNDOTRANSACTION_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'UNDOTRANSACTION_CHECKER', 0),
+	(505, 'transaction_savings', 'ADJUSTTRANSACTION_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'ADJUSTTRANSACTION', 0),
+	(506, 'portfolio', 'READ_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'READ', 0),
+	(507, 'portfolio', 'CREATE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'CREATE', 0),
+	(508, 'portfolio', 'CREATE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'CREATE_CHECKER', 0),
+	(509, 'portfolio', 'UPDATE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'UPDATE', 0),
+	(510, 'portfolio', 'UPDATE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'UPDATE_CHECKER', 0),
+	(511, 'portfolio', 'DELETE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'DELETE', 0),
+	(512, 'portfolio', 'DELETE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'DELETE_CHECKER', 0),
+	(513, 'transaction_savings', 'DEPOSIT_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'DEPOSIT', 0),
+	(514, 'transaction_savings', 'DEPOSIT_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'DEPOSIT_CHECKER', 0),
+	(515, 'transaction_savings', 'WITHDRAWAL_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAWAL', 0),
+	(516, 'transaction_savings', 'WITHDRAWAL_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAWAL_CHECKER', 0),
+	(517, 'transaction_savings', 'ACTIVATE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'ACTIVATE', 0),
+	(518, 'transaction_savings', 'ACTIVATE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'ACTIVATE_CHECKER', 0),
+	(519, 'transaction_savings', 'CALCULATEINTEREST_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'CALCULATEINTEREST', 0),
+	(520, 'transaction_savings', 'CALCULATEINTEREST_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'CALCULATEINTEREST_CHECKER', 0),
+	(521, 'transaction_savings', 'POSTINTEREST_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'POSTINTEREST', 1),
+	(522, 'transaction_savings', 'POSTINTEREST_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'POSTINTEREST_CHECKER', 0),
+	(523, 'transaction_savings', 'APPROVE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'APPROVE', 1),
+	(524, 'transaction_savings', 'REJECT_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'REJECT', 1),
+	(525, 'transaction_savings', 'WITHDRAW_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAW', 1),
+	(526, 'transaction_savings', 'APPROVALUNDO_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'APPROVALUNDO', 1),
+	(527, 'transaction_savings', 'CLOSE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'CLOSE', 1),
+	(528, 'transaction_savings', 'APPROVE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'APPROVE_CHECKER', 0),
+	(529, 'transaction_savings', 'REJECT_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'REJECT_CHECKER', 0),
+	(530, 'transaction_savings', 'WITHDRAW_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAW_CHECKER', 0),
+	(531, 'transaction_savings', 'APPROVALUNDO_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'APPROVALUNDO_CHECKER', 0),
+	(532, 'transaction_savings', 'CLOSE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'CLOSE_CHECKER', 0),
+	(533, 'transaction_savings', 'UNDOTRANSACTION_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'UNDOTRANSACTION', 1),
+	(534, 'transaction_savings', 'UNDOTRANSACTION_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'UNDOTRANSACTION_CHECKER', 0),
+	(535, 'transaction_savings', 'ADJUSTTRANSACTION_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'ADJUSTTRANSACTION', 0),
+	(536, 'transaction_savings', 'PREMATURECLOSE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'PREMATURECLOSE_CHECKER', 0),
+	(537, 'transaction_savings', 'PREMATURECLOSE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'PREMATURECLOSE', 1),
+	(538, 'transaction_savings', 'PREMATURECLOSE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'PREMATURECLOSE_CHECKER', 0),
+	(539, 'transaction_savings', 'PREMATURECLOSE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'PREMATURECLOSE', 1),
+	(540, 'transaction_loan', 'DISBURSETOSAVINGS_LOAN', 'LOAN', 'DISBURSETOSAVINGS', 0),
+	(541, 'transaction_loan', 'RECOVERYPAYMENT_LOAN', 'LOAN', 'RECOVERYPAYMENT', 0),
+	(542, 'organisation', 'READ_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'READ', 0),
+	(543, 'organisation', 'READ_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'READ', 0),
+	(544, 'accounting', 'READ_FINANCIALACTIVITYACCOUNT', 'FINANCIALACTIVITYACCOUNT', 'READ', 0),
+	(545, 'accounting', 'CREATE_FINANCIALACTIVITYACCOUNT', 'FINANCIALACTIVITYACCOUNT', 'CREATE', 0),
+	(546, 'accounting', 'DELETE_FINANCIALACTIVITYACCOUNT', 'FINANCIALACTIVITYACCOUNT', 'DELETE', 0),
+	(547, 'accounting', 'UPDATE_FINANCIALACTIVITYACCOUNT', 'FINANCIALACTIVITYACCOUNT', 'UPDATE', 0),
+	(548, 'datatable', 'UPDATE_LIKELIHOOD', 'likelihood', 'UPDATE', 0),
+	(549, 'survey', 'REGISTER_SURVEY', 'survey', 'CREATE', 0),
+	(550, 'accounting', 'EXECUTE_PERIODICACCRUALACCOUNTING', 'PERIODICACCRUALACCOUNTING', 'EXECUTE', 0),
+	(551, 'portfolio', 'INACTIVATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'INACTIVATE', 0),
+	(552, 'portfolio', 'INACTIVATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'INACTIVATE_CHECKER', 0),
+	(553, 'portfolio_center', 'DISASSOCIATEGROUPS_CENTER', 'CENTER', 'DISASSOCIATEGROUPS', 0),
+	(554, 'portfolio_center', 'ASSOCIATEGROUPS_CENTER', 'CENTER', 'ASSOCIATEGROUPS', 0),
+	(555, 'portfolio_center', 'DISASSOCIATEGROUPS_CENTER_CHECKER', 'CENTER', 'DISASSOCIATEGROUPS_CHECKER', 0),
+	(556, 'portfolio_center', 'ASSOCIATEGROUPS_CENTER_CHECKER', 'CENTER', 'ASSOCIATEGROUPS_CHECKER', 0),
+	(557, 'loan_reschedule', 'READ_RESCHEDULELOAN', 'RESCHEDULELOAN', 'READ', 0),
+	(558, 'loan_reschedule', 'CREATE_RESCHEDULELOAN', 'RESCHEDULELOAN', 'CREATE', 0),
+	(559, 'loan_reschedule', 'REJECT_RESCHEDULELOAN', 'RESCHEDULELOAN', 'REJECT', 0),
+	(560, 'loan_reschedule', 'APPROVE_RESCHEDULELOAN', 'RESCHEDULELOAN', 'APPROVE', 0),
+	(561, 'configuration', 'CREATE_HOOK', 'HOOK', 'CREATE', 0),
+	(562, 'configuration', 'READ_HOOK', 'HOOK', 'READ', 0),
+	(563, 'configuration', 'UPDATE_HOOK', 'HOOK', 'UPDATE', 0),
+	(564, 'configuration', 'DELETE_HOOK', 'HOOK', 'DELETE', 0),
+	(565, 'portfolio', 'REMOVESAVINGSOFFICER_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'REMOVESAVINGSOFFICER', 1),
+	(566, 'portfolio', 'UPDATESAVINGSOFFICER_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UPDATESAVINGSOFFICER', 1),
+	(567, 'report', 'READ_Active Loans - Summary(Pentaho)', 'Active Loans - Summary(Pentaho)', 'READ', 0),
+	(568, 'report', 'READ_Active Loans by Disbursal Period(Pentaho)', 'Active Loans by Disbursal Period(Pentaho)', 'READ', 0),
+	(569, 'report', 'READ_Active Loans in last installment Summary(Pentaho)', 'Active Loans in last installment Summary(Pentaho)', 'READ', 0),
+	(570, 'report', 'READ_Active Loans in last installment(Pentaho)', 'Active Loans in last installment(Pentaho)', 'READ', 0),
+	(571, 'report', 'READ_Active Loans Passed Final Maturity Summary(Pentaho)', 'Active Loans Passed Final Maturity Summary(Pentaho)', 'READ', 0),
+	(572, 'report', 'READ_Active Loans Passed Final Maturity(Pentaho)', 'Active Loans Passed Final Maturity(Pentaho)', 'READ', 0),
+	(573, 'report', 'READ_Aging Detail(Pentaho)', 'Aging Detail(Pentaho)', 'READ', 0),
+	(574, 'report', 'READ_Aging Summary (Arrears in Months)(Pentaho)', 'Aging Summary (Arrears in Months)(Pentaho)', 'READ', 0),
+	(575, 'report', 'READ_Aging Summary (Arrears in Weeks)(Pentaho)', 'Aging Summary (Arrears in Weeks)(Pentaho)', 'READ', 0),
+	(576, 'report', 'READ_Client Listing(Pentaho)', 'Client Listing(Pentaho)', 'READ', 0),
+	(577, 'report', 'READ_Client Loan Account Schedule', 'Client Loan Account Schedule', 'READ', 0),
+	(578, 'report', 'READ_Client Loans Listing(Pentaho)', 'Client Loans Listing(Pentaho)', 'READ', 0),
+	(579, 'report', 'READ_Client Saving Transactions', 'Client Saving Transactions', 'READ', 0),
+	(580, 'report', 'READ_Client Savings Summary', 'Client Savings Summary', 'READ', 0),
+	(581, 'report', 'READ_ClientSummary ', 'ClientSummary ', 'READ', 0),
+	(582, 'report', 'READ_ClientTrendsByDay', 'ClientTrendsByDay', 'READ', 0),
+	(583, 'report', 'READ_ClientTrendsByMonth', 'ClientTrendsByMonth', 'READ', 0),
+	(584, 'report', 'READ_ClientTrendsByWeek', 'ClientTrendsByWeek', 'READ', 0),
+	(585, 'report', 'READ_Demand_Vs_Collection', 'Demand_Vs_Collection', 'READ', 0),
+	(586, 'report', 'READ_Disbursal_Vs_Awaitingdisbursal', 'Disbursal_Vs_Awaitingdisbursal', 'READ', 0),
+	(587, 'report', 'READ_Expected Payments By Date - Basic(Pentaho)', 'Expected Payments By Date - Basic(Pentaho)', 'READ', 0),
+	(588, 'report', 'READ_Funds Disbursed Between Dates Summary by Office(Pentaho)', 'Funds Disbursed Between Dates Summary by Office(Pentaho)', 'READ', 0),
+	(589, 'report', 'READ_Funds Disbursed Between Dates Summary(Pentaho)', 'Funds Disbursed Between Dates Summary(Pentaho)', 'READ', 0),
+	(590, 'report', 'READ_GroupNamesByStaff', 'GroupNamesByStaff', 'READ', 0),
+	(591, 'report', 'READ_GroupSavingSummary', 'GroupSavingSummary', 'READ', 0),
+	(592, 'report', 'READ_LoanCyclePerProduct', 'LoanCyclePerProduct', 'READ', 0),
+	(593, 'report', 'READ_Loans Awaiting Disbursal Summary by Month(Pentaho)', 'Loans Awaiting Disbursal Summary by Month(Pentaho)', 'READ', 0),
+	(594, 'report', 'READ_Loans Awaiting Disbursal Summary(Pentaho)', 'Loans Awaiting Disbursal Summary(Pentaho)', 'READ', 0),
+	(595, 'report', 'READ_Loans Awaiting Disbursal(Pentaho)', 'Loans Awaiting Disbursal(Pentaho)', 'READ', 0),
+	(596, 'report', 'READ_Loans Pending Approval(Pentaho)', 'Loans Pending Approval(Pentaho)', 'READ', 0),
+	(597, 'report', 'READ_LoanTrendsByDay', 'LoanTrendsByDay', 'READ', 0),
+	(598, 'report', 'READ_LoanTrendsByMonth', 'LoanTrendsByMonth', 'READ', 0),
+	(599, 'report', 'READ_LoanTrendsByWeek', 'LoanTrendsByWeek', 'READ', 0),
+	(600, 'report', 'READ_Obligation Met Loans Details(Pentaho)', 'Obligation Met Loans Details(Pentaho)', 'READ', 0),
+	(601, 'report', 'READ_Obligation Met Loans Summary(Pentaho)', 'Obligation Met Loans Summary(Pentaho)', 'READ', 0),
+	(602, 'report', 'READ_Portfolio at Risk by Branch(Pentaho)', 'Portfolio at Risk by Branch(Pentaho)', 'READ', 0),
+	(603, 'report', 'READ_Portfolio at Risk(Pentaho)', 'Portfolio at Risk(Pentaho)', 'READ', 0),
+	(604, 'report', 'READ_Rescheduled Loans(Pentaho)', 'Rescheduled Loans(Pentaho)', 'READ', 0),
+	(605, 'report', 'READ_Savings Transactions', 'Savings Transactions', 'READ', 0),
+	(606, 'report', 'READ_TxnRunningBalances(Pentaho)', 'TxnRunningBalances(Pentaho)', 'READ', 0),
+	(607, 'report', 'READ_Written-Off Loans(Pentaho)', 'Written-Off Loans(Pentaho)', 'READ', 0),
+	(608, 'configuration', 'CREATE_ACCOUNTNUMBERFORMAT', 'ACCOUNTNUMBERFORMAT', 'CREATE', 0),
+	(609, 'configuration', 'READ_ACCOUNTNUMBERFORMAT', 'ACCOUNTNUMBERFORMAT', 'READ', 0),
+	(610, 'configuration', 'UPDATE_ACCOUNTNUMBERFORMAT', 'ACCOUNTNUMBERFORMAT', 'UPDATE', 0),
+	(611, 'configuration', 'DELETE_ACCOUNTNUMBERFORMAT', 'HOOK', 'DELETE', 0),
+	(612, 'portfolio', 'RECOVERGUARANTEES_LOAN', 'LOAN', 'RECOVERGUARANTEES', 0),
+	(613, 'portfolio', 'RECOVERGUARANTEES_LOAN_CHECKER', 'LOAN', 'RECOVERGUARANTEES_CHECKER', 0),
+	(614, 'portfolio', 'REJECT_CLIENT', 'CLIENT', 'REJECT', 1),
+	(615, 'portfolio', 'REJECT_CLIENT_CHECKER', 'CLIENT', 'REJECT_CHECKER', 0),
+	(616, 'portfolio', 'WITHDRAW_CLIENT', 'CLIENT', 'WITHDRAW', 1),
+	(617, 'portfolio', 'WITHDRAW_CLIENT_CHECKER', 'CLIENT', 'WITHDRAW_CHECKER', 0),
+	(618, 'portfolio', 'REACTIVATE_CLIENT', 'CLIENT', 'REACTIVATE', 1),
+	(619, 'portfolio', 'REACTIVATE_CLIENT_CHECKER', 'CLIENT', 'REACTIVATE_CHECKER', 0),
+	(620, 'transaction_savings', 'UPDATEDEPOSITAMOUNT_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'UPDATEDEPOSITAMOUNT', 1),
+	(621, 'transaction_savings', 'UPDATEDEPOSITAMOUNT_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'UPDATEDEPOSITAMOUNT', 1),
+	(622, 'transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 0),
+	(623, 'transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 1),
+	(624, 'transaction_loan', 'REFUNDBYCASH_LOAN', 'LOAN', 'REFUNDBYCASH', 1),
+	(625, 'transaction_loan', 'REFUNDBYCASH_LOAN_CHECKER', 'LOAN', 'REFUNDBYCASH', 0),
+	(626, 'cash_mgmt', 'CREATE_TELLER', 'TELLER', 'CREATE', 1),
+	(627, 'cash_mgmt', 'UPDATE_TELLER', 'TELLER', 'UPDATE', 1),
+	(628, 'cash_mgmt', 'ALLOCATECASHIER_TELLER', 'TELLER', 'ALLOCATE', 1),
+	(629, 'cash_mgmt', 'UPDATECASHIERALLOCATION_TELLER', 'TELLER', 'UPDATECASHIERALLOCATION', 1),
+	(630, 'cash_mgmt', 'DELETECASHIERALLOCATION_TELLER', 'TELLER', 'DELETECASHIERALLOCATION', 1),
+	(631, 'cash_mgmt', 'ALLOCATECASHTOCASHIER_TELLER', 'TELLER', 'ALLOCATECASHTOCASHIER', 1),
+	(632, 'cash_mgmt', 'SETTLECASHFROMCASHIER_TELLER', 'TELLER', 'SETTLECASHFROMCASHIER', 1),
+	(633, 'authorisation', 'DISABLE_ROLE', 'ROLE', 'DISABLE', 0),
+	(634, 'authorisation', 'DISABLE_ROLE_CHECKER', 'ROLE', 'DISABLE_CHECKER', 0),
+	(635, 'authorisation', 'ENABLE_ROLE', 'ROLE', 'ENABLE', 0),
+	(636, 'authorisation', 'ENABLE_ROLE_CHECKER', 'ROLE', 'ENABLE_CHECKER', 0),
+	(637, 'accounting', 'DEFINEOPENINGBALANCE_JOURNALENTRY', 'JOURNALENTRY', 'DEFINEOPENINGBALANCE', 1),
+	(638, 'collection_sheet', 'READ_COLLECTIONSHEET', 'COLLECTIONSHEET', 'READ', 0),
+	(639, 'collection_sheet', 'SAVE_COLLECTIONSHEET', 'COLLECTIONSHEET', 'SAVE', 0),
+	(640, 'infrastructure', 'CREATE_ENTITYMAPPING', 'ENTITYMAPPING', 'CREATE', 0),
+	(641, 'infrastructure', 'UPDATE_ENTITYMAPPING', 'ENTITYMAPPING', 'UPDATE', 0),
+	(642, 'infrastructure', 'DELETE_ENTITYMAPPING', 'ENTITYMAPPING', 'DELETE', 0),
+	(643, 'organisation', 'READ_WORKINGDAYS', 'WORKINGDAYS', 'READ', 0),
+	(644, 'organisation', 'UPDATE_WORKINGDAYS', 'WORKINGDAYS', 'UPDATE', 0),
+	(645, 'organisation', 'UPDATE_WORKINGDAYS_CHECKER', 'WORKINGDAYS', 'UPDATE_CHECKER', 0),
+	(646, 'authorisation', 'READ_PASSWORD_PREFERENCES', 'PASSWORD_PREFERENCES', 'READ', 0),
+	(647, 'authorisation', 'UPDATE_PASSWORD_PREFERENCES', 'PASSWORD_PREFERENCES', 'UPDATE', 0),
+	(648, 'authorisation', 'UPDATE_PASSWORD_PREFERENCES_CHECKER', 'PASSWORD_PREFERENCES', 'UPDATE_CHECKER', 0),
+	(649, 'portfolio', 'CREATE_PAYMENTTYPE', 'PAYMENTTYPE', 'CREATE', 0),
+	(650, 'portfolio', 'UPDATE_PAYMENTTYPE', 'PAYMENTTYPE', 'UPDATE', 0),
+	(651, 'portfolio', 'DELETE_PAYMENTTYPE', 'PAYMENTTYPE', 'DELETE', 0),
+	(652, 'cash_mgmt', 'DELETE_TELLER', 'TELLER', 'DELETE', 1),
+	(653, 'report', 'READ_General Ledger Report', 'General Ledger Report', 'READ', 0),
+	(654, 'portfolio', 'READ_STAFFIMAGE', 'STAFFIMAGE', 'READ', 0),
+	(655, 'portfolio', 'CREATE_STAFFIMAGE', 'STAFFIMAGE', 'CREATE', 1),
+	(656, 'portfolio', 'CREATE_STAFFIMAGE_CHECKER', 'STAFFIMAGE', 'CREATE', 0),
+	(657, 'portfolio', 'DELETE_STAFFIMAGE', 'STAFFIMAGE', 'DELETE', 1),
+	(658, 'portfolio', 'DELETE_STAFFIMAGE_CHECKER', 'STAFFIMAGE', 'DELETE', 0),
+	(659, 'report', 'READ_Active Loan Summary per Branch', 'Active Loan Summary per Branch', 'READ', 0),
+	(660, 'report', 'READ_Disbursal Report', 'Disbursal Report', 'READ', 0),
+	(661, 'report', 'READ_Balance Outstanding', 'Balance Outstanding', 'READ', 0),
+	(662, 'report', 'READ_Collection Report', 'Collection Report', 'READ', 0),
+	(663, 'portfolio', 'READ_PAYMENTTYPE', 'PAYMENTTYPE', 'READ', 0),
+	(664, 'report', 'READ_Staff Assignment History', 'Staff Assignment History(Pentaho)', 'READ', 0),
+	(665, 'externalservices', 'UPDATE_EXTERNALSERVICES', 'EXTERNALSERVICES', 'UPDATE', 0),
+	(666, 'portfolio', 'READ_CLIENTCHARGE', 'CLIENTCHARGE', 'READ', 0),
+	(667, 'portfolio', 'CREATE_CLIENTCHARGE', 'CLIENTCHARGE', 'CREATE', 0),
+	(668, 'portfolio', 'DELETE_CLIENTCHARGE', 'CLIENTCHARGE', 'DELETE', 0),
+	(669, 'portfolio', 'WAIVE_CLIENTCHARGE', 'CLIENTCHARGE', 'WAIVE', 0),
+	(670, 'portfolio', 'PAY_CLIENTCHARGE', 'CLIENTCHARGE', 'PAY', 0),
+	(671, 'portfolio', 'INACTIVATE_CLIENTCHARGE', 'CLIENTCHARGE', 'INACTIVATE', 0),
+	(672, 'portfolio', 'UPDATE_CLIENTCHARGE', 'CLIENTCHARGE', 'UPDATE', 0),
+	(673, 'portfolio', 'CREATE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'CREATE_CHECKER', 0),
+	(674, 'portfolio', 'DELETE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'DELETE_CHECKER', 0),
+	(675, 'portfolio', 'WAIVE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'WAIVE_CHECKER', 0),
+	(676, 'portfolio', 'PAY_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'PAY_CHECKER', 0),
+	(677, 'portfolio', 'INACTIVATE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'INACTIVATE_CHECKER', 0),
+	(678, 'portfolio', 'UPDATE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'UPDATE_CHECKER', 0),
+	(679, 'transaction_client', 'READTRANSACTION_CLIENT', 'CLIENT', 'READTRANSACTION', 0),
+	(680, 'transaction_client', 'UNDOTRANSACTION_CLIENT', 'CLIENT', 'UNDOTRANSACTION', 0),
+	(681, 'transaction_client', 'UNDOTRANSACTION_CLIENT_CHECKER', 'CLIENT', 'UNDOTRANSACTION_CHECKER', 0),
+	(682, 'LOAN_PROVISIONING', 'CREATE_PROVISIONCATEGORY', 'PROVISIONCATEGORY', 'CREATE', 0),
+	(683, 'LOAN_PROVISIONING', 'DELETE_PROVISIONCATEGORY', 'PROVISIONCATEGORY', 'DELETE', 0),
+	(684, 'LOAN_PROVISIONING', 'CREATE_PROVISIONCRITERIA', 'PROVISIONINGCRITERIA', 'CREATE', 0),
+	(685, 'LOAN_PROVISIONING', 'UPDATE_PROVISIONCRITERIA', 'PROVISIONINGCRITERIA', 'UPDATE', 0),
+	(686, 'LOAN_PROVISIONING', 'DELETE_PROVISIONCRITERIA', 'PROVISIONINGCRITERIA', 'DELETE', 0),
+	(687, 'LOAN_PROVISIONING', 'CREATE_PROVISIONENTRIES', 'PROVISIONINGENTRIES', 'CREATE', 0),
+	(688, 'LOAN_PROVISIONING', 'CREATE_PROVISIONJOURNALENTRIES', 'PROVISIONINGENTRIES', 'CREATE', 0),
+	(689, 'LOAN_PROVISIONING', 'RECREATE_PROVISIONENTRIES', 'PROVISIONINGENTRIES', 'RECREATE', 0),
+	(690, 'portfolio', 'READ_FLOATINGRATE', 'FLOATINGRATE', 'READ', 0),
+	(691, 'portfolio', 'CREATE_FLOATINGRATE', 'FLOATINGRATE', 'CREATE', 1),
+	(692, 'portfolio', 'CREATE_FLOATINGRATE_CHECKER', 'FLOATINGRATE', 'CREATE_CHECKER', 0),
+	(693, 'portfolio', 'UPDATE_FLOATINGRATE', 'FLOATINGRATE', 'UPDATE', 1),
+	(694, 'portfolio', 'UPDATE_FLOATINGRATE_CHECKER', 'FLOATINGRATE', 'UPDATE_CHECKER', 0),
+	(695, 'portfolio', 'CREATESCHEDULEEXCEPTIONS_LOAN', 'LOAN', 'CREATESCHEDULEEXCEPTIONS', 0),
+	(696, 'portfolio', 'CREATESCHEDULEEXCEPTIONS_LOAN_CHECKER', 'LOAN', 'CREATESCHEDULEEXCEPTIONS_CHECKER', 0),
+	(697, 'portfolio', 'DELETESCHEDULEEXCEPTIONS_LOAN', 'LOAN', 'DELETESCHEDULEEXCEPTIONS', 0),
+	(698, 'portfolio', 'DELETESCHEDULEEXCEPTIONS_LOAN_CHECKER', 'LOAN', 'DELETESCHEDULEEXCEPTIONS_CHECKER', 0),
+	(699, 'transaction_loan', 'DISBURSALLASTUNDO_LOAN', 'LOAN', 'DISBURSALLASTUNDO', 0),
+	(700, 'transaction_loan', 'DISBURSALLASTUNDO_LOAN_CHECKER', 'LOAN', 'DISBURSALLASTUNDO_CHECKER', 0),
+	(701, 'SHAREPRODUCT', 'CREATE_SHAREPRODUCT', 'SHAREPRODUCT', 'CREATE', 0),
+	(702, 'SHAREPRODUCT', 'UPDATE_SHAREPRODUCT', 'SHAREPRODUCT', 'CREATE', 0),
+	(703, 'SHAREACCOUNT', 'CREATE_SHAREACCOUNT', 'SHAREACCOUNT', 'CREATE', 0),
+	(704, 'SHAREACCOUNT', 'UPDATE_SHAREACCOUNT', 'SHAREACCOUNT', 'CREATE', 0);
+/*!40000 ALTER TABLE `m_permission` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_portfolio_account_associations
+DROP TABLE IF EXISTS `m_portfolio_account_associations`;
+CREATE TABLE IF NOT EXISTS `m_portfolio_account_associations` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_account_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `linked_loan_account_id` bigint(20) DEFAULT NULL,
+  `linked_savings_account_id` bigint(20) DEFAULT NULL,
+  `association_type_enum` smallint(1) NOT NULL DEFAULT '1',
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `account_association_loan_fk` (`loan_account_id`),
+  KEY `account_association_savings_fk` (`savings_account_id`),
+  KEY `linked_loan_fk` (`linked_loan_account_id`),
+  KEY `linked_savings_fk` (`linked_savings_account_id`),
+  CONSTRAINT `account_association_loan_fk` FOREIGN KEY (`loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `account_association_savings_fk` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `linked_loan_fk` FOREIGN KEY (`linked_loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `linked_savings_fk` FOREIGN KEY (`linked_savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_portfolio_account_associations: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_portfolio_account_associations` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_portfolio_account_associations` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_portfolio_command_source
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+CREATE TABLE IF NOT EXISTS `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `subresource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_portfolio_command_source: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_portfolio_command_source` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_product_loan
+DROP TABLE IF EXISTS `m_product_loan`;
+CREATE TABLE IF NOT EXISTS `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `short_name` varchar(4) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `min_principal_amount` decimal(19,6) DEFAULT NULL,
+  `max_principal_amount` decimal(19,6) DEFAULT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `is_linked_to_floating_interest_rates` bit(1) NOT NULL DEFAULT b'0',
+  `allow_variabe_installments` bit(1) NOT NULL DEFAULT b'0',
+  `nominal_interest_rate_per_period` decimal(19,6) DEFAULT NULL,
+  `min_nominal_interest_rate_per_period` decimal(19,6) DEFAULT NULL,
+  `max_nominal_interest_rate_per_period` decimal(19,6) DEFAULT NULL,
+  `interest_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) DEFAULT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `allow_partial_period_interest_calcualtion` tinyint(1) NOT NULL DEFAULT '0',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `min_number_of_repayments` smallint(5) DEFAULT NULL,
+  `max_number_of_repayments` smallint(5) DEFAULT NULL,
+  `grace_on_principal_periods` smallint(5) DEFAULT NULL,
+  `grace_on_interest_periods` smallint(5) DEFAULT NULL,
+  `grace_interest_free_periods` smallint(5) DEFAULT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `include_in_borrower_cycle` tinyint(1) NOT NULL DEFAULT '0',
+  `use_borrower_cycle` tinyint(1) NOT NULL DEFAULT '0',
+  `start_date` date DEFAULT NULL,
+  `close_date` date DEFAULT NULL,
+  `allow_multiple_disbursals` tinyint(1) NOT NULL DEFAULT '0',
+  `max_disbursals` int(2) DEFAULT NULL,
+  `max_outstanding_loan_balance` decimal(19,6) DEFAULT NULL,
+  `grace_on_arrears_ageing` smallint(5) DEFAULT NULL,
+  `overdue_days_for_npa` smallint(5) DEFAULT NULL,
+  `days_in_month_enum` smallint(5) NOT NULL DEFAULT '1',
+  `days_in_year_enum` smallint(5) NOT NULL DEFAULT '1',
+  `interest_recalculation_enabled` tinyint(4) NOT NULL DEFAULT '0',
+  `min_days_between_disbursal_and_first_repayment` int(3) DEFAULT NULL,
+  `hold_guarantee_funds` tinyint(1) NOT NULL DEFAULT '0',
+  `principal_threshold_for_last_installment` decimal(5,2) NOT NULL DEFAULT '50.00',
+  `account_moves_out_of_npa_only_on_arrears_completion` tinyint(1) NOT NULL DEFAULT '0',
+  `can_define_fixed_emi_amount` tinyint(1) NOT NULL DEFAULT '0',
+  `instalment_amount_in_multiples_of` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`),
+  UNIQUE KEY `unq_short_name` (`short_name`),
+  UNIQUE KEY `external_id_UNIQUE` (`external_id`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_product_loan: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_product_loan_charge
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_product_loan_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_product_loan_configurable_attributes
+DROP TABLE IF EXISTS `m_product_loan_configurable_attributes`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_configurable_attributes` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL,
+  `amortization_method_enum` tinyint(4) NOT NULL DEFAULT '1',
+  `interest_method_enum` tinyint(4) NOT NULL DEFAULT '1',
+  `loan_transaction_strategy_id` tinyint(4) NOT NULL DEFAULT '1',
+  `interest_calculated_in_period_enum` tinyint(4) NOT NULL DEFAULT '1',
+  `arrearstolerance_amount` tinyint(4) NOT NULL DEFAULT '1',
+  `repay_every` tinyint(4) NOT NULL DEFAULT '1',
+  `moratorium` tinyint(4) NOT NULL DEFAULT '1',
+  `grace_on_arrears_ageing` tinyint(4) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `fk_m_product_loan_configurable_attributes_0001` (`loan_product_id`),
+  CONSTRAINT `fk_m_product_loan_configurable_attributes_0001` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_product_loan_configurable_attributes: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_configurable_attributes` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_configurable_attributes` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_product_loan_floating_rates
+DROP TABLE IF EXISTS `m_product_loan_floating_rates`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_floating_rates` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL,
+  `floating_rates_id` bigint(20) NOT NULL,
+  `interest_rate_differential` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `min_differential_lending_rate` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `default_differential_lending_rate` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `max_differential_lending_rate` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_floating_interest_rate_calculation_allowed` bit(1) NOT NULL DEFAULT b'0',
+  PRIMARY KEY (`id`),
+  KEY `FK_mappings_m_product_loan_id` (`loan_product_id`),
+  KEY `FK_mappings_m_floating_rates_id` (`floating_rates_id`),
+  CONSTRAINT `FK_mappings_m_floating_rates_id` FOREIGN KEY (`floating_rates_id`) REFERENCES `m_floating_rates` (`id`),
+  CONSTRAINT `FK_mappings_m_product_loan_id` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_product_loan_floating_rates: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_floating_rates` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_floating_rates` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_product_loan_guarantee_details
+DROP TABLE IF EXISTS `m_product_loan_guarantee_details`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_guarantee_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL,
+  `mandatory_guarantee` decimal(19,5) NOT NULL,
+  `minimum_guarantee_from_own_funds` decimal(19,5) DEFAULT NULL,
+  `minimum_guarantee_from_guarantor_funds` decimal(19,5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_guarantee_details_loan_product` (`loan_product_id`),
+  CONSTRAINT `FK_guarantee_details_loan_product` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_product_loan_guarantee_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_guarantee_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_guarantee_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_product_loan_recalculation_details
+DROP TABLE IF EXISTS `m_product_loan_recalculation_details`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_recalculation_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `product_id` bigint(20) NOT NULL,
+  `compound_type_enum` smallint(5) NOT NULL,
+  `reschedule_strategy_enum` smallint(5) NOT NULL,
+  `rest_frequency_type_enum` smallint(1) NOT NULL,
+  `rest_frequency_interval` smallint(3) NOT NULL DEFAULT '0',
+  `rest_freqency_date` date DEFAULT NULL,
+  `arrears_based_on_original_schedule` tinyint(1) NOT NULL DEFAULT '0',
+  `pre_close_interest_calculation_strategy` smallint(3) NOT NULL DEFAULT '1',
+  `compounding_frequency_type_enum` smallint(1) DEFAULT NULL,
+  `compounding_frequency_interval` smallint(3) DEFAULT NULL,
+  `compounding_freqency_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_product_loan_m_product_loan_recalculation_details` (`product_id`),
+  CONSTRAINT `FK_m_product_loan_m_product_loan_recalculation_details` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-default.m_product_loan_recalculation_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_recalculation_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_recalculation_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_product_loan_variable_installment_config
+DROP TABLE IF EXISTS `m_product_loan_variable_installment_config`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_variable_installment_config` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL,
+  `minimum_gap` int(4) NOT NULL,
+  `maximum_gap` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_mappings_m_variable_product_loan_id` (`loan_product_id`),
+  CONSTRAINT `FK_mappings_m_variable_product_loan_id` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_product_loan_variable_installment_config: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_variable_installment_config` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_variable_installment_config` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_product_loan_variations_borrower_cycle
+DROP TABLE IF EXISTS `m_product_loan_variations_borrower_cycle`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_variations_borrower_cycle` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL DEFAULT '0',
+  `borrower_cycle_number` int(3) NOT NULL DEFAULT '0',
+  `value_condition` int(1) NOT NULL DEFAULT '0',
+  `param_type` int(1) NOT NULL DEFAULT '0',
+  `default_value` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `max_value` decimal(19,6) DEFAULT NULL,
+  `min_value` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `borrower_cycle_loan_product_FK` (`loan_product_id`),
+  CONSTRAINT `borrower_cycle_loan_product_FK` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_product_loan_variations_borrower_cycle: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_variations_borrower_cycle` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_variations_borrower_cycle` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_product_mix
+DROP TABLE IF EXISTS `m_product_mix`;
+CREATE TABLE IF NOT EXISTS `m_product_mix` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `product_id` bigint(20) NOT NULL,
+  `restricted_product_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_product_mix_product_id_to_m_product_loan` (`product_id`),
+  KEY `FK_m_product_mix_restricted_product_id_to_m_product_loan` (`restricted_product_id`),
+  CONSTRAINT `FK_m_product_mix_product_id_to_m_product_loan` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_m_product_mix_restricted_product_id_to_m_product_loan` FOREIGN KEY (`restricted_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_product_mix: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_mix` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_mix` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_provisioning_criteria
+DROP TABLE IF EXISTS `m_provisioning_criteria`;
+CREATE TABLE IF NOT EXISTS `m_provisioning_criteria` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `criteria_name` varchar(200) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `criteria_name` (`criteria_name`),
+  KEY `createdby_id` (`createdby_id`),
+  KEY `lastmodifiedby_id` (`lastmodifiedby_id`),
+  CONSTRAINT `m_provisioning_criteria_ibfk_1` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_provisioning_criteria_ibfk_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_provisioning_criteria: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_provisioning_criteria` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_provisioning_criteria` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_provisioning_criteria_definition
+DROP TABLE IF EXISTS `m_provisioning_criteria_definition`;
+CREATE TABLE IF NOT EXISTS `m_provisioning_criteria_definition` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `criteria_id` bigint(20) NOT NULL,
+  `category_id` bigint(20) NOT NULL,
+  `min_age` bigint(20) NOT NULL,
+  `max_age` bigint(20) NOT NULL,
+  `provision_percentage` decimal(5,2) NOT NULL,
+  `liability_account` bigint(20) DEFAULT NULL,
+  `expense_account` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `criteria_id` (`criteria_id`),
+  KEY `category_id` (`category_id`),
+  KEY `liability_account` (`liability_account`),
+  KEY `expense_account` (`expense_account`),
+  CONSTRAINT `m_provisioning_criteria_definition_ibfk_1` FOREIGN KEY (`criteria_id`) REFERENCES `m_provisioning_criteria` (`id`),
+  CONSTRAINT `m_provisioning_criteria_definition_ibfk_2` FOREIGN KEY (`category_id`) REFERENCES `m_provision_category` (`id`),
+  CONSTRAINT `m_provisioning_criteria_definition_ibfk_3` FOREIGN KEY (`liability_account`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `m_provisioning_criteria_definition_ibfk_4` FOREIGN KEY (`expense_account`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_provisioning_criteria_definition: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_provisioning_criteria_definition` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_provisioning_criteria_definition` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_provisioning_history
+DROP TABLE IF EXISTS `m_provisioning_history`;
+CREATE TABLE IF NOT EXISTS `m_provisioning_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `journal_entry_created` bit(1) DEFAULT b'0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` date DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `createdby_id` (`createdby_id`),
+  KEY `lastmodifiedby_id` (`lastmodifiedby_id`),
+  CONSTRAINT `m_provisioning_history_ibfk_1` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_provisioning_history_ibfk_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_provisioning_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_provisioning_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_provisioning_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_provision_category
+DROP TABLE IF EXISTS `m_provision_category`;
+CREATE TABLE IF NOT EXISTS `m_provision_category` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `category_name` varchar(100) NOT NULL,
+  `description` varchar(300) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `category_name` (`category_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_provision_category: ~4 rows (approximately)
+/*!40000 ALTER TABLE `m_provision_category` DISABLE KEYS */;
+INSERT INTO `m_provision_category` (`id`, `category_name`, `description`) VALUES
+	(1, 'STANDARD', 'Punctual Payment without any dues'),
+	(2, 'SUB-STANDARD', 'Principal and/or Interest overdue by x days'),
+	(3, 'DOUBTFUL', 'Principal and/or Interest overdue by x days and less than y'),
+	(4, 'LOSS', 'Principal and/or Interest overdue by y days');
+/*!40000 ALTER TABLE `m_provision_category` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_role
+DROP TABLE IF EXISTS `m_role`;
+CREATE TABLE IF NOT EXISTS `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `is_disabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_role: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_role` DISABLE KEYS */;
+INSERT INTO `m_role` (`id`, `name`, `description`, `is_disabled`) VALUES
+	(1, 'Super user', 'This role provides all application permissions.', 0);
+/*!40000 ALTER TABLE `m_role` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_role_permission
+DROP TABLE IF EXISTS `m_role_permission`;
+CREATE TABLE IF NOT EXISTS `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_role_permission: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_role_permission` DISABLE KEYS */;
+INSERT INTO `m_role_permission` (`role_id`, `permission_id`) VALUES
+	(1, 1);
+/*!40000 ALTER TABLE `m_role_permission` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_account
+DROP TABLE IF EXISTS `m_savings_account`;
+CREATE TABLE IF NOT EXISTS `m_savings_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `field_officer_id` bigint(20) DEFAULT NULL,
+  `status_enum` smallint(5) NOT NULL DEFAULT '300',
+  `account_type_enum` smallint(5) NOT NULL DEFAULT '1',
+  `deposit_type_enum` smallint(5) NOT NULL DEFAULT '100',
+  `submittedon_date` date NOT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `activatedon_date` date DEFAULT NULL,
+  `activatedon_userid` bigint(20) DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `nominal_annual_interest_rate` decimal(19,6) NOT NULL,
+  `interest_compounding_period_enum` smallint(5) NOT NULL,
+  `interest_posting_period_enum` smallint(5) NOT NULL DEFAULT '4',
+  `interest_calculation_type_enum` smallint(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `withdrawal_fee_for_transfer` tinyint(4) DEFAULT '1',
+  `allow_overdraft` tinyint(1) NOT NULL DEFAULT '0',
+  `overdraft_limit` decimal(19,6) DEFAULT NULL,
+  `nominal_annual_interest_rate_overdraft` decimal(19,6) DEFAULT '0.000000',
+  `min_overdraft_for_interest_calculation` decimal(19,6) DEFAULT '0.000000',
+  `lockedin_until_date_derived` date DEFAULT NULL,
+  `total_deposits_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawal_fees_derived` decimal(19,6) DEFAULT NULL,
+  `total_fees_charge_derived` decimal(19,6) DEFAULT NULL,
+  `total_penalty_charge_derived` decimal(19,6) DEFAULT NULL,
+  `total_annual_fees_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_earned_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
+  `total_overdraft_interest_derived` decimal(19,6) DEFAULT '0.000000',
+  `account_balance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `min_required_balance` decimal(19,6) DEFAULT NULL,
+  `enforce_min_required_balance` tinyint(1) NOT NULL DEFAULT '0',
+  `min_balance_for_interest_calculation` decimal(19,6) DEFAULT NULL,
+  `start_interest_calculation_date` date DEFAULT NULL,
+  `on_hold_funds_derived` decimal(19,6) DEFAULT NULL,
+  `version` int(15) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sa_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `sa_externalid_UNIQUE` (`external_id`),
+  KEY `FKSA00000000000001` (`client_id`),
+  KEY `FKSA00000000000002` (`group_id`),
+  KEY `FKSA00000000000003` (`product_id`),
+  CONSTRAINT `FKSA00000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA00000000000002` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSA00000000000003` FOREIGN KEY (`product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_account: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_account_charge
+DROP TABLE IF EXISTS `m_savings_account_charge`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_due_date` date DEFAULT NULL,
+  `fee_on_month` smallint(5) DEFAULT NULL,
+  `fee_on_day` smallint(5) DEFAULT NULL,
+  `fee_interval` smallint(5) DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  `inactivated_on_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_savings_account_charge_ibfk_2` (`savings_account_id`),
+  CONSTRAINT `m_savings_account_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_savings_account_charge_ibfk_2` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_account_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_account_charge_paid_by
+DROP TABLE IF EXISTS `m_savings_account_charge_paid_by`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_charge_paid_by` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_transaction_id` bigint(20) NOT NULL,
+  `savings_account_charge_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK__m_savings_account_transaction` (`savings_account_transaction_id`),
+  KEY `FK__m_savings_account_charge` (`savings_account_charge_id`),
+  CONSTRAINT `FK__m_savings_account_charge` FOREIGN KEY (`savings_account_charge_id`) REFERENCES `m_savings_account_charge` (`id`),
+  CONSTRAINT `FK__m_savings_account_transaction` FOREIGN KEY (`savings_account_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_account_charge_paid_by: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_charge_paid_by` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_charge_paid_by` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_account_interest_rate_chart
+DROP TABLE IF EXISTS `m_savings_account_interest_rate_chart`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_interest_rate_chart` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `name` varchar(100) DEFAULT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `from_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAIRC00000000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAIRC00000000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_account_interest_rate_chart: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_interest_rate_chart` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_interest_rate_chart` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_account_interest_rate_slab
+DROP TABLE IF EXISTS `m_savings_account_interest_rate_slab`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_interest_rate_slab` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_interest_rate_chart_id` bigint(20) NOT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `period_type_enum` smallint(5) NOT NULL DEFAULT '1',
+  `from_period` int(11) NOT NULL DEFAULT '0',
+  `to_period` int(11) DEFAULT NULL,
+  `amount_range_from` decimal(19,6) DEFAULT NULL,
+  `amount_range_to` decimal(19,6) DEFAULT NULL,
+  `annual_interest_rate` decimal(19,6) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAIRS00000000000001` (`savings_account_interest_rate_chart_id`),
+  CONSTRAINT `FKSAIRS00000000000001` FOREIGN KEY (`savings_account_interest_rate_chart_id`) REFERENCES `m_savings_account_interest_rate_chart` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_account_interest_rate_slab: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_interest_rate_slab` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_interest_rate_slab` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_account_transaction
+DROP TABLE IF EXISTS `m_savings_account_transaction`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `payment_detail_id` bigint(20) DEFAULT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `overdraft_amount_derived` decimal(19,6) DEFAULT NULL,
+  `balance_end_date_derived` date DEFAULT NULL,
+  `balance_number_of_days_derived` int(11) DEFAULT NULL,
+  `running_balance_derived` decimal(19,6) DEFAULT NULL,
+  `cumulative_balance_derived` decimal(19,6) DEFAULT NULL,
+  `created_date` datetime NOT NULL,
+  `appuser_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`savings_account_id`),
+  KEY `FK_m_savings_account_transaction_m_payment_detail` (`payment_detail_id`),
+  KEY `FK_m_savings_account_transaction_m_office` (`office_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `FK_m_savings_account_transaction_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_savings_account_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_account_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_interest_incentives
+DROP TABLE IF EXISTS `m_savings_interest_incentives`;
+CREATE TABLE IF NOT EXISTS `m_savings_interest_incentives` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `deposit_account_interest_rate_slab_id` bigint(20) NOT NULL,
+  `entiry_type` smallint(2) NOT NULL,
+  `attribute_name` smallint(2) NOT NULL,
+  `condition_type` smallint(2) NOT NULL,
+  `attribute_value` varchar(50) NOT NULL,
+  `incentive_type` smallint(2) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_savings_interest_incentives_m_savings_interest_rate_slab` (`deposit_account_interest_rate_slab_id`),
+  CONSTRAINT `FK_m_savings_interest_incentives_m_savings_interest_rate_slab` FOREIGN KEY (`deposit_account_interest_rate_slab_id`) REFERENCES `m_savings_account_interest_rate_slab` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_interest_incentives: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_interest_incentives` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_interest_incentives` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_officer_assignment_history
+DROP TABLE IF EXISTS `m_savings_officer_assignment_history`;
+CREATE TABLE IF NOT EXISTS `m_savings_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `savings_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_savings_officer_assignment_history_0001` (`account_id`),
+  KEY `fk_m_savings_officer_assignment_history_0002` (`savings_officer_id`),
+  CONSTRAINT `fk_m_savings_officer_assignment_history_0001` FOREIGN KEY (`account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `fk_m_savings_officer_assignment_history_0002` FOREIGN KEY (`savings_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_officer_assignment_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_officer_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_officer_assignment_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_product
+DROP TABLE IF EXISTS `m_savings_product`;
+CREATE TABLE IF NOT EXISTS `m_savings_product` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `short_name` varchar(4) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `deposit_type_enum` smallint(5) NOT NULL DEFAULT '100',
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `nominal_annual_interest_rate` decimal(19,6) NOT NULL,
+  `interest_compounding_period_enum` smallint(5) NOT NULL,
+  `interest_posting_period_enum` smallint(5) NOT NULL DEFAULT '4',
+  `interest_calculation_type_enum` smallint(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `withdrawal_fee_amount` decimal(19,6) DEFAULT NULL,
+  `withdrawal_fee_type_enum` smallint(5) DEFAULT NULL,
+  `withdrawal_fee_for_transfer` tinyint(4) DEFAULT '1',
+  `allow_overdraft` tinyint(1) NOT NULL DEFAULT '0',
+  `overdraft_limit` decimal(19,6) DEFAULT NULL,
+  `nominal_annual_interest_rate_overdraft` decimal(19,6) DEFAULT '0.000000',
+  `min_overdraft_for_interest_calculation` decimal(19,6) DEFAULT '0.000000',
+  `min_required_balance` decimal(19,6) DEFAULT NULL,
+  `enforce_min_required_balance` tinyint(1) NOT NULL DEFAULT '0',
+  `min_balance_for_interest_calculation` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sp_unq_name` (`name`),
+  UNIQUE KEY `sp_unq_short_name` (`short_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_product: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_product` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_product` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_savings_product_charge
+DROP TABLE IF EXISTS `m_savings_product_charge`;
+CREATE TABLE IF NOT EXISTS `m_savings_product_charge` (
+  `savings_product_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`savings_product_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_savings_product_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_savings_product_charge_ibfk_2` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_savings_product_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_product_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_product_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_selfservice_user_client_mapping
+DROP TABLE IF EXISTS `m_selfservice_user_client_mapping`;
+CREATE TABLE IF NOT EXISTS `m_selfservice_user_client_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `appuser_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `appuser_id_client_id` (`appuser_id`,`client_id`),
+  KEY `m_selfservice_client_id` (`client_id`),
+  CONSTRAINT `m_selfservice_appuser_id` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_selfservice_client_id` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_selfservice_user_client_mapping: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_selfservice_user_client_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_selfservice_user_client_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_staff
+DROP TABLE IF EXISTS `m_staff`;
+CREATE TABLE IF NOT EXISTS `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(102) NOT NULL,
+  `mobile_no` varchar(50) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `organisational_role_enum` smallint(6) DEFAULT NULL,
+  `organisational_role_parent_staff_id` bigint(20) DEFAULT NULL,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  `joining_date` date DEFAULT NULL,
+  `image_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  UNIQUE KEY `external_id_UNIQUE` (`external_id`),
+  UNIQUE KEY `mobile_no_UNIQUE` (`mobile_no`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  KEY `FK_m_staff_m_image` (`image_id`),
+  CONSTRAINT `FK_m_staff_m_image` FOREIGN KEY (`image_id`) REFERENCES `m_image` (`id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_staff: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_staff` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_staff` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_staff_assignment_history
+DROP TABLE IF EXISTS `m_staff_assignment_history`;
+CREATE TABLE IF NOT EXISTS `m_staff_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `centre_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) NOT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_staff_assignment_history_centre_id_m_group` (`centre_id`),
+  KEY `FK_m_staff_assignment_history_m_staff` (`staff_id`),
+  CONSTRAINT `FK_m_staff_assignment_history_centre_id_m_group` FOREIGN KEY (`centre_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_m_staff_assignment_history_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_staff_assignment_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_staff_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_staff_assignment_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_surveys
+DROP TABLE IF EXISTS `m_surveys`;
+CREATE TABLE IF NOT EXISTS `m_surveys` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `a_key` varchar(32) NOT NULL,
+  `a_name` varchar(255) NOT NULL,
+  `description` varchar(4000) DEFAULT NULL,
+  `country_code` varchar(2) NOT NULL,
+  `valid_from` datetime DEFAULT NULL,
+  `valid_to` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_surveys: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_surveys` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_surveys` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_survey_components
+DROP TABLE IF EXISTS `m_survey_components`;
+CREATE TABLE IF NOT EXISTS `m_survey_components` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` bigint(20) NOT NULL,
+  `a_key` varchar(32) NOT NULL,
+  `a_text` varchar(255) NOT NULL,
+  `description` varchar(4000) DEFAULT NULL,
+  `sequence_no` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `survey_id` (`survey_id`),
+  CONSTRAINT `m_survey_components_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_survey_components: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_components` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_components` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_survey_lookup_tables
+DROP TABLE IF EXISTS `m_survey_lookup_tables`;
+CREATE TABLE IF NOT EXISTS `m_survey_lookup_tables` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` bigint(20) NOT NULL,
+  `a_key` varchar(255) NOT NULL,
+  `description` int(4) DEFAULT NULL,
+  `value_from` int(4) NOT NULL,
+  `value_to` int(4) NOT NULL,
+  `score` decimal(5,2) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `survey_id` (`survey_id`),
+  CONSTRAINT `m_survey_lookup_tables_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_survey_lookup_tables: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_lookup_tables` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_lookup_tables` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_survey_questions
+DROP TABLE IF EXISTS `m_survey_questions`;
+CREATE TABLE IF NOT EXISTS `m_survey_questions` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` bigint(20) NOT NULL,
+  `component_key` varchar(32) DEFAULT NULL,
+  `a_key` varchar(32) NOT NULL,
+  `a_text` varchar(255) NOT NULL,
+  `description` varchar(4000) DEFAULT NULL,
+  `sequence_no` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `survey_id` (`survey_id`),
+  CONSTRAINT `m_survey_questions_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_survey_questions: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_questions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_questions` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_survey_responses
+DROP TABLE IF EXISTS `m_survey_responses`;
+CREATE TABLE IF NOT EXISTS `m_survey_responses` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `question_id` bigint(20) NOT NULL,
+  `a_text` varchar(255) NOT NULL,
+  `a_value` int(4) NOT NULL,
+  `sequence_no` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `question_id` (`question_id`),
+  CONSTRAINT `m_survey_responses_ibfk_1` FOREIGN KEY (`question_id`) REFERENCES `m_survey_questions` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_survey_responses: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_responses` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_responses` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_survey_scorecards
+DROP TABLE IF EXISTS `m_survey_scorecards`;
+CREATE TABLE IF NOT EXISTS `m_survey_scorecards` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` bigint(20) NOT NULL,
+  `question_id` bigint(20) NOT NULL,
+  `response_id` bigint(20) NOT NULL,
+  `user_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  `created_on` datetime DEFAULT NULL,
+  `a_value` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `survey_id` (`survey_id`),
+  KEY `question_id` (`question_id`),
+  KEY `response_id` (`response_id`),
+  KEY `user_id` (`user_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_2` FOREIGN KEY (`question_id`) REFERENCES `m_survey_questions` (`id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_3` FOREIGN KEY (`response_id`) REFERENCES `m_survey_responses` (`id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_4` FOREIGN KEY (`user_id`) REFERENCES `m_appusers` (`id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_5` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_survey_scorecards: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_scorecards` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_scorecards` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_tellers
+DROP TABLE IF EXISTS `m_tellers`;
+CREATE TABLE IF NOT EXISTS `m_tellers` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `debit_account_id` bigint(20) DEFAULT NULL,
+  `credit_account_id` bigint(20) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `valid_from` date DEFAULT NULL,
+  `valid_to` date DEFAULT NULL,
+  `state` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `m_tellers_name_unq` (`name`),
+  KEY `IK_m_tellers_m_office` (`office_id`),
+  KEY `FK_m_tellers_gl_account_debit_account_id` (`debit_account_id`),
+  KEY `FK_m_tellers_gl_account_credit_account_id` (`credit_account_id`),
+  CONSTRAINT `FK_m_tellers_gl_account_credit_account_id` FOREIGN KEY (`credit_account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_m_tellers_gl_account_debit_account_id` FOREIGN KEY (`debit_account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_m_tellers_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_tellers: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_tellers` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_tellers` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_template
+DROP TABLE IF EXISTS `m_template`;
+CREATE TABLE IF NOT EXISTS `m_template` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
+  `text` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
+  `entity` int(11) DEFAULT NULL,
+  `type` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_template: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_template` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_template` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_templatemappers
+DROP TABLE IF EXISTS `m_templatemappers`;
+CREATE TABLE IF NOT EXISTS `m_templatemappers` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `mapperkey` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  `mapperorder` int(11) DEFAULT NULL,
+  `mappervalue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_templatemappers: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_templatemappers` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_templatemappers` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_template_m_templatemappers
+DROP TABLE IF EXISTS `m_template_m_templatemappers`;
+CREATE TABLE IF NOT EXISTS `m_template_m_templatemappers` (
+  `m_template_id` bigint(20) NOT NULL,
+  `mappers_id` bigint(20) NOT NULL,
+  UNIQUE KEY `mappers_id` (`mappers_id`),
+  KEY `mappers_id_2` (`mappers_id`),
+  KEY `m_template_id` (`m_template_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_template_m_templatemappers: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_template_m_templatemappers` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_template_m_templatemappers` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.m_working_days
+DROP TABLE IF EXISTS `m_working_days`;
+CREATE TABLE IF NOT EXISTS `m_working_days` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `recurrence` varchar(100) DEFAULT NULL,
+  `repayment_rescheduling_enum` smallint(5) DEFAULT NULL,
+  `extend_term_daily_repayments` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.m_working_days: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_working_days` DISABLE KEYS */;
+INSERT INTO `m_working_days` (`id`, `recurrence`, `repayment_rescheduling_enum`, `extend_term_daily_repayments`) VALUES
+	(1, 'FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU', 2, 0);
+/*!40000 ALTER TABLE `m_working_days` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.oauth_access_token
+DROP TABLE IF EXISTS `oauth_access_token`;
+CREATE TABLE IF NOT EXISTS `oauth_access_token` (
+  `token_id` varchar(256) DEFAULT NULL,
+  `token` blob,
+  `authentication_id` varchar(256) DEFAULT NULL,
+  `user_name` varchar(256) DEFAULT NULL,
+  `client_id` varchar(256) DEFAULT NULL,
+  `authentication` blob,
+  `refresh_token` varchar(256) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.oauth_access_token: ~0 rows (approximately)
+/*!40000 ALTER TABLE `oauth_access_token` DISABLE KEYS */;
+/*!40000 ALTER TABLE `oauth_access_token` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.oauth_client_details
+DROP TABLE IF EXISTS `oauth_client_details`;
+CREATE TABLE IF NOT EXISTS `oauth_client_details` (
+  `client_id` varchar(128) NOT NULL,
+  `resource_ids` varchar(256) DEFAULT NULL,
+  `client_secret` varchar(256) DEFAULT NULL,
+  `scope` varchar(256) DEFAULT NULL,
+  `authorized_grant_types` varchar(256) DEFAULT NULL,
+  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
+  `authorities` varchar(256) DEFAULT NULL,
+  `access_token_validity` int(11) DEFAULT NULL,
+  `refresh_token_validity` int(11) DEFAULT NULL,
+  `additional_information` varchar(4096) DEFAULT NULL,
+  `autoapprove` bit(1) DEFAULT NULL,
+  PRIMARY KEY (`client_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.oauth_client_details: ~1 rows (approximately)
+/*!40000 ALTER TABLE `oauth_client_details` DISABLE KEYS */;
+INSERT INTO `oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES
+	('community-app', NULL, '123', 'all', 'password,refresh_token', NULL, NULL, NULL, NULL, NULL, NULL);
+/*!40000 ALTER TABLE `oauth_client_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.oauth_refresh_token
+DROP TABLE IF EXISTS `oauth_refresh_token`;
+CREATE TABLE IF NOT EXISTS `oauth_refresh_token` (
+  `token_id` varchar(256) DEFAULT NULL,
+  `token` blob,
+  `authentication` blob
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.oauth_refresh_token: ~0 rows (approximately)
+/*!40000 ALTER TABLE `oauth_refresh_token` DISABLE KEYS */;
+/*!40000 ALTER TABLE `oauth_refresh_token` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.ppi_likelihoods
+DROP TABLE IF EXISTS `ppi_likelihoods`;
+CREATE TABLE IF NOT EXISTS `ppi_likelihoods` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) NOT NULL,
+  `name` varchar(250) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.ppi_likelihoods: ~0 rows (approximately)
+/*!40000 ALTER TABLE `ppi_likelihoods` DISABLE KEYS */;
+/*!40000 ALTER TABLE `ppi_likelihoods` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.ppi_likelihoods_ppi
+DROP TABLE IF EXISTS `ppi_likelihoods_ppi`;
+CREATE TABLE IF NOT EXISTS `ppi_likelihoods_ppi` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `likelihood_id` bigint(20) NOT NULL,
+  `ppi_name` varchar(250) NOT NULL,
+  `enabled` int(11) NOT NULL DEFAULT '100',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.ppi_likelihoods_ppi: ~0 rows (approximately)
+/*!40000 ALTER TABLE `ppi_likelihoods_ppi` DISABLE KEYS */;
+/*!40000 ALTER TABLE `ppi_likelihoods_ppi` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.ppi_scores
+DROP TABLE IF EXISTS `ppi_scores`;
+CREATE TABLE IF NOT EXISTS `ppi_scores` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `score_from` int(11) NOT NULL,
+  `score_to` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.ppi_scores: ~20 rows (approximately)
+/*!40000 ALTER TABLE `ppi_scores` DISABLE KEYS */;
+INSERT INTO `ppi_scores` (`id`, `score_from`, `score_to`) VALUES
+	(1, 0, 4),
+	(2, 5, 9),
+	(3, 10, 14),
+	(4, 15, 19),
+	(5, 20, 24),
+	(6, 25, 29),
+	(7, 30, 34),
+	(8, 35, 39),
+	(9, 40, 44),
+	(10, 45, 49),
+	(11, 50, 54),
+	(12, 55, 59),
+	(13, 60, 64),
+	(14, 65, 69),
+	(15, 70, 74),
+	(16, 75, 79),
+	(17, 80, 84),
+	(18, 85, 89),
+	(19, 90, 94),
+	(20, 95, 100);
+/*!40000 ALTER TABLE `ppi_scores` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.ref_loan_transaction_processing_strategy
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+CREATE TABLE IF NOT EXISTS `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `sort_order` int(4) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.ref_loan_transaction_processing_strategy: ~7 rows (approximately)
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` DISABLE KEYS */;
+INSERT INTO `ref_loan_transaction_processing_strategy` (`id`, `code`, `name`, `sort_order`) VALUES
+	(1, 'mifos-standard-strategy', 'Penalties, Fees, Interest, Principal order', 1),
+	(2, 'heavensfamily-strategy', 'HeavensFamily Unique', 6),
+	(3, 'creocore-strategy', 'Creocore Unique', 7),
+	(4, 'rbi-india-strategy', 'Overdue/Due Fee/Int,Principal', 2),
+	(5, 'principal-interest-penalties-fees-order-strategy', 'Principal, Interest, Penalties, Fees Order', 3),
+	(6, 'interest-principal-penalties-fees-order-strategy', 'Interest, Principal, Penalties, Fees Order', 4),
+	(7, 'early-repayment-strategy', 'Early Repayment Strategy', 5);
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.rpt_sequence
+DROP TABLE IF EXISTS `rpt_sequence`;
+CREATE TABLE IF NOT EXISTS `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.rpt_sequence: ~0 rows (approximately)
+/*!40000 ALTER TABLE `rpt_sequence` DISABLE KEYS */;
+/*!40000 ALTER TABLE `rpt_sequence` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.r_enum_value
+DROP TABLE IF EXISTS `r_enum_value`;
+CREATE TABLE IF NOT EXISTS `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  `enum_type` tinyint(1) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.r_enum_value: ~64 rows (approximately)
+/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES
+	('amortization_method_enum', 0, 'Equal principle payments', 'Equal principle payments', 0),
+	('amortization_method_enum', 1, 'Equal installments', 'Equal installments', 0),
+	('interest_calculated_in_period_enum', 0, 'Daily', 'Daily', 0),
+	('interest_calculated_in_period_enum', 1, 'Same as repayment period', 'Same as repayment period', 0),
+	('interest_method_enum', 0, 'Declining Balance', 'Declining Balance', 0),
+	('interest_method_enum', 1, 'Flat', 'Flat', 0),
+	('interest_period_frequency_enum', 2, 'Per month', 'Per month', 0),
+	('interest_period_frequency_enum', 3, 'Per year', 'Per year', 0),
+	('loan_status_id', 0, 'Invalid', 'Invalid', 0),
+	('loan_status_id', 100, 'Submitted and awaiting approval', 'Submitted and awaiting approval', 0),
+	('loan_status_id', 200, 'Approved', 'Approved', 0),
+	('loan_status_id', 300, 'Active', 'Active', 0),
+	('loan_status_id', 400, 'Withdrawn by client', 'Withdrawn by client', 0),
+	('loan_status_id', 500, 'Rejected', 'Rejected', 0),
+	('loan_status_id', 600, 'Closed', 'Closed', 0),
+	('loan_status_id', 601, 'Written-Off', 'Written-Off', 0),
+	('loan_status_id', 602, 'Rescheduled', 'Rescheduled', 0),
+	('loan_status_id', 700, 'Overpaid', 'Overpaid', 0),
+	('loan_transaction_strategy_id', 1, 'mifos-standard-strategy', 'Mifos style', 0),
+	('loan_transaction_strategy_id', 2, 'heavensfamily-strategy', 'Heavensfamily', 0),
+	('loan_transaction_strategy_id', 3, 'creocore-strategy', 'Creocore', 0),
+	('loan_transaction_strategy_id', 4, 'rbi-india-strategy', 'RBI (India)', 0),
+	('processing_result_enum', 0, 'invalid', 'Invalid', 0),
+	('processing_result_enum', 1, 'processed', 'Processed', 0),
+	('processing_result_enum', 2, 'awaiting.approval', 'Awaiting Approval', 0),
+	('processing_result_enum', 3, 'rejected', 'Rejected', 0),
+	('repayment_period_frequency_enum', 0, 'Days', 'Days', 0),
+	('repayment_period_frequency_enum', 1, 'Weeks', 'Weeks', 0),
+	('repayment_period_frequency_enum', 2, 'Months', 'Months', 0),
+	('savings_transaction_type_enum', 1, 'deposit', 'deposit', 0),
+	('savings_transaction_type_enum', 2, 'withdrawal', 'withdrawal', 1),
+	('savings_transaction_type_enum', 3, 'Interest Posting', 'Interest Posting', 0),
+	('savings_transaction_type_enum', 4, 'Withdrawal Fee', 'Withdrawal Fee', 1),
+	('savings_transaction_type_enum', 5, 'Annual Fee', 'Annual Fee', 1),
+	('savings_transaction_type_enum', 6, 'Waive Charge', 'Waive Charge', 0),
+	('savings_transaction_type_enum', 7, 'Pay Charge', 'Pay Charge', 1),
+	('savings_transaction_type_enum', 12, 'Initiate Transfer', 'Initiate Transfer', 0),
+	('savings_transaction_type_enum', 13, 'Approve Transfer', 'Approve Transfer', 0),
+	('savings_transaction_type_enum', 14, 'Withdraw Transfer', 'Withdraw Transfer', 0),
+	('savings_transaction_type_enum', 15, 'Reject Transfer', 'Reject Transfer', 0),
+	('savings_transaction_type_enum', 16, 'Written-Off', 'Written-Off', 0),
+	('savings_transaction_type_enum', 17, 'Overdraft Interest', 'Overdraft Interest', 0),
+	('status_enum', 0, 'Invalid', 'Invalid', 0),
+	('status_enum', 100, 'Pending', 'Pending', 0),
+	('status_enum', 300, 'Active', 'Active', 0),
+	('status_enum', 600, 'Closed', 'Closed', 0),
+	('teller_status', 300, 'Active', 'Active', 0),
+	('teller_status', 400, 'Inactive', 'Inactive', 0),
+	('teller_status', 600, 'Closed', 'Closed', 0),
+	('term_period_frequency_enum', 0, 'Days', 'Days', 0),
+	('term_period_frequency_enum', 1, 'Weeks', 'Weeks', 0),
+	('term_period_frequency_enum', 2, 'Months', 'Months', 0),
+	('term_period_frequency_enum', 3, 'Years', 'Years', 0),
+	('transaction_type_enum', 1, 'Disbursement', 'Disbursement', 0),
+	('transaction_type_enum', 2, 'Repayment', 'Repayment', 0),
+	('transaction_type_enum', 3, 'Contra', 'Contra', 0),
+	('transaction_type_enum', 4, 'Waive Interest', 'Waive Interest', 0),
+	('transaction_type_enum', 5, 'Repayment At Disbursement', 'Repayment At Disbursement', 0),
+	('transaction_type_enum', 6, 'Write-Off', 'Write-Off', 0),
+	('transaction_type_enum', 7, 'Marked for Rescheduling', 'Marked for Rescheduling', 0),
+	('transaction_type_enum', 8, 'Recovery Repayment', 'Recovery Repayment', 0),
+	('transaction_type_enum', 9, 'Waive Charges', 'Waive Charges', 0),
+	('transaction_type_enum', 10, 'Apply Charges', 'Apply Charges', 0),
+	('transaction_type_enum', 11, 'Apply Interest', 'Apply Interest', 0);
+/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.scheduler_detail
+DROP TABLE IF EXISTS `scheduler_detail`;
+CREATE TABLE IF NOT EXISTS `scheduler_detail` (
+  `id` smallint(2) NOT NULL AUTO_INCREMENT,
+  `is_suspended` tinyint(1) NOT NULL DEFAULT '0',
+  `execute_misfired_jobs` tinyint(1) NOT NULL DEFAULT '1',
+  `reset_scheduler_on_bootup` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.scheduler_detail: ~1 rows (approximately)
+/*!40000 ALTER TABLE `scheduler_detail` DISABLE KEYS */;
+INSERT INTO `scheduler_detail` (`id`, `is_suspended`, `execute_misfired_jobs`, `reset_scheduler_on_bootup`) VALUES
+	(1, 0, 1, 1);
+/*!40000 ALTER TABLE `scheduler_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.schema_version
+DROP TABLE IF EXISTS `schema_version`;
+CREATE TABLE IF NOT EXISTS `schema_version` (
+  `version_rank` int(11) NOT NULL,
+  `installed_rank` int(11) NOT NULL,
+  `version` varchar(50) NOT NULL,
+  `description` varchar(200) NOT NULL,
+  `type` varchar(20) NOT NULL,
+  `script` varchar(1000) NOT NULL,
+  `checksum` int(11) DEFAULT NULL,
+  `installed_by` varchar(100) NOT NULL,
+  `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `execution_time` int(11) NOT NULL,
+  `success` tinyint(1) NOT NULL,
+  PRIMARY KEY (`version`),
+  KEY `schema_version_vr_idx` (`version_rank`),
+  KEY `schema_version_ir_idx` (`installed_rank`),
+  KEY `schema_version_s_idx` (`success`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.schema_version: ~297 rows (approximately)
+/*!40000 ALTER TABLE `schema_version` DISABLE KEYS */;
+INSERT INTO `schema_version` (`version_rank`, `installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES
+	(1, 1, '1', 'mifosplatform-core-ddl-latest', 'SQL', 'V1__mifosplatform-core-ddl-latest.sql', 1800446512, 'root', '2015-06-03 15:26:50', 919, 1),
+	(10, 10, '10', 'interest-posting-fields-for-savings', 'SQL', 'V10__interest-posting-fields-for-savings.sql', 921633032, 'root', '2015-06-03 15:26:51', 300, 1),
+	(100, 100, '100', 'Group saving summary report', 'SQL', 'V100__Group_saving_summary_report.sql', -1635399448, 'root', '2015-06-03 15:26:58', 1, 1),
+	(101, 101, '101', 'add mulitplesof to account transfers table', 'SQL', 'V101__add_mulitplesof_to_account_transfers_table.sql', 693007396, 'root', '2015-06-03 15:26:58', 109, 1),
+	(102, 102, '102', 'client attendance tables', 'SQL', 'V102__client_attendance_tables.sql', 1698025788, 'root', '2015-06-03 15:26:58', 48, 1),
+	(103, 103, '103', 'cluster support for batch jobs', 'SQL', 'V103__cluster_support_for_batch_jobs.sql', -1566474883, 'root', '2015-06-03 15:26:58', 49, 1),
+	(104, 104, '104', 'permissions for transfers', 'SQL', 'V104__permissions_for_transfers.sql', 1104237193, 'root', '2015-06-03 15:26:58', 2, 1),
+	(105, 105, '105', 'track loan transaction against office', 'SQL', 'V105__track_loan_transaction_against_office.sql', 460401960, 'root', '2015-06-03 15:26:59', 225, 1),
+	(106, 106, '106', 'more permissions for transfers', 'SQL', 'V106__more_permissions_for_transfers.sql', -1298572248, 'root', '2015-06-03 15:26:59', 4, 1),
+	(107, 107, '107', 'datatable code mappings', 'SQL', 'V107__datatable_code_mappings.sql', 1534043325, 'root', '2015-06-03 15:26:59', 37, 1),
+	(108, 108, '108', 'client has transfer office', 'SQL', 'V108__client_has_transfer_office.sql', 1659150580, 'root', '2015-06-03 15:26:59', 19, 1),
+	(109, 109, '109', 'account transfer withdrawal fee configuration', 'SQL', 'V109__account_transfer_withdrawal_fee_configuration.sql', 1610197449, 'root', '2015-06-03 15:26:59', 80, 1),
+	(11, 11, '11', 'add-payment-details', 'SQL', 'V11__add-payment-details.sql', 662675601, 'root', '2015-06-03 15:26:52', 88, 1),
+	(110, 110, '110', 'group center close', 'SQL', 'V110__group_center_close.sql', 592532718, 'root', '2015-06-03 15:26:59', 38, 1),
+	(111, 111, '111', 'disable constraint approach for datatables by default', 'SQL', 'V111__disable_constraint_approach_for_datatables_by_default.sql', 2058257907, 'root', '2015-06-03 15:26:59', 1, 1),
+	(112, 112, '111.1', 'set default transfers in suspense account for existing loan products', 'SQL', 'V111_1__set default_transfers_in_suspense_account_for_existing_loan_products.sql', -2022488149, 'root', '2015-06-03 15:26:59', 1, 1),
+	(113, 113, '112', 'mixreport sql support', 'SQL', 'V112__mixreport_sql_support.sql', 1698359442, 'root', '2015-06-03 15:26:59', 84, 1),
+	(114, 114, '113', 'track savings transaction against office', 'SQL', 'V113__track_savings_transaction_against_office.sql', -443279148, 'root', '2015-06-03 15:26:59', 106, 1),
+	(115, 115, '114', 'set default transfers in suspense account for existing savings products - Copy', 'SQL', 'V114__set_default_transfers_in_suspense_account_for_existing_savings_products - Copy.sql', -1403120536, 'root', '2015-06-03 15:26:59', 1, 1),
+	(116, 116, '115', 'permissions for cache api', 'SQL', 'V115__permissions_for_cache_api.sql', -1583242502, 'root', '2015-06-03 15:26:59', 26, 1),
+	(117, 117, '116', 'track currency for journal entries', 'SQL', 'V116__track_currency_for_journal_entries.sql', -1108820305, 'root', '2015-06-03 15:26:59', 127, 1),
+	(118, 118, '117', 'loan charge from savings', 'SQL', 'V117__loan_charge_from_savings.sql', 294078650, 'root', '2015-06-03 15:27:00', 106, 1),
+	(119, 119, '118', 'savings charge', 'SQL', 'V118__savings_charge.sql', 1334878019, 'root', '2015-06-03 15:27:00', 89, 1),
+	(120, 120, '118.1', 'savings charge patch update', 'SQL', 'V118_1__savings_charge_patch_update.sql', 681062374, 'root', '2015-06-03 15:27:00', 42, 1),
+	(121, 121, '118.2', 'product mapping delete duplicate fund source to account mappings', 'SQL', 'V118_2__product_mapping_delete_duplicate_fund_source_to_account_mappings.sql', 843735115, 'root', '2015-06-03 15:27:00', 0, 1),
+	(122, 122, '118.3', 'permissions form propose and accept client transfers', 'SQL', 'V118_3__permissions_form_propose_and_accept_client_transfers.sql', -2021972980, 'root', '2015-06-03 15:27:00', 2, 1),
+	(123, 123, '118.4', 'reset default transfers in suspense account for existing savings products', 'SQL', 'V118_4__reset_default_transfers_in_suspense_account_for_existing_savings_products.sql', -699275732, 'root', '2015-06-03 15:27:00', 1, 1),
+	(124, 124, '118.5', 'batch job entry for pay savings charge', 'SQL', 'V118_5__batch_job_entry_for_pay_savings_charge.sql', 11127915, 'root', '2015-06-03 15:27:00', 1, 1),
+	(125, 125, '118.6', 'defaults for income from penalties for savings product', 'SQL', 'V118_6__defaults_for_income_from_penalties_for savings_product.sql', -1604260872, 'root', '2015-06-03 15:27:00', 2, 1),
+	(126, 126, '118.7', 'move withdrawal annual fee to charges', 'SQL', 'V118_7__move_withdrawal_annual_fee_to_charges.sql', 486907496, 'root', '2015-06-03 15:27:00', 166, 1),
+	(127, 127, '118.8', 'track overpayments seperately in loan transactions', 'SQL', 'V118_8__track_overpayments_seperately_in_loan_transactions.sql', -1506771861, 'root', '2015-06-03 15:27:00', 145, 1),
+	(128, 128, '119', 'add template table', 'SQL', 'V119__add_template_table.sql', -340132249, 'root', '2015-06-03 15:27:00', 45, 1),
+	(12, 12, '12', 'add external id to couple of tables', 'SQL', 'V12__add_external_id_to_couple_of_tables.sql', 1782914953, 'root', '2015-06-03 15:26:52', 214, 1),
+	(129, 129, '120', 'accounting running balance', 'SQL', 'V120__accounting_running_balance.sql', 1553735313, 'root', '2015-06-03 15:27:00', 39, 1),
+	(130, 130, '121', 'accounting running balance for organization', 'SQL', 'V121__accounting_running_balance_for_organization.sql', -725212393, 'root', '2015-06-03 15:27:00', 63, 1),
+	(131, 131, '122', 'recurring fee support for savings', 'SQL', 'V122__recurring_fee_support_for_savings.sql', -1243503882, 'root', '2015-06-03 15:27:01', 77, 1),
+	(132, 132, '123', 'remove payment mode for savings', 'SQL', 'V123__remove_payment_mode_for_savings.sql', -310516979, 'root', '2015-06-03 15:27:01', 52, 1),
+	(133, 133, '124', 'added min max cap for charges', 'SQL', 'V124__added_min_max_cap_for_charges.sql', 1016982354, 'root', '2015-06-03 15:27:01', 71, 1),
+	(134, 134, '125', 'added column for actual fee amount or percentage', 'SQL', 'V125__added_column_for_actual_fee_amount_or_percentage.sql', -1902751935, 'root', '2015-06-03 15:27:01', 42, 1),
+	(135, 135, '126', 'initial database structure for sms outbound', 'SQL', 'V126__initial_database_structure_for_sms_outbound.sql', 1597367272, 'root', '2015-06-03 15:27:01', 28, 1),
+	(136, 136, '127', 'mobile no fields', 'SQL', 'V127__mobile_no_fields.sql', -1478167026, 'root', '2015-06-03 15:27:01', 135, 1),
+	(137, 137, '128', 'added loan installment charge', 'SQL', 'V128__added_loan_installment_charge.sql', -427737636, 'root', '2015-06-03 15:27:01', 24, 1),
+	(138, 138, '129', 'client and group timeline', 'SQL', 'V129__client_and_group_timeline.sql', -354048349, 'root', '2015-06-03 15:27:01', 114, 1),
+	(13, 13, '13', 'add group and client pending configuration', 'SQL', 'V13__add_group_and_client_pending_configuration.sql', 2043924577, 'root', '2015-06-03 15:26:52', 4, 1),
+	(139, 139, '130', 'calendar-history-table', 'SQL', 'V130__calendar-history-table.sql', -1068056425, 'root', '2015-06-03 15:27:01', 22, 1),
+	(140, 140, '131', 'holiday-status-column-and-permissions', 'SQL', 'V131__holiday-status-column-and-permissions.sql', 169147264, 'root', '2015-06-03 15:27:01', 89, 1),
+	(141, 141, '132', 'borrower cycle changes', 'SQL', 'V132__borrower_cycle_changes.sql', -1484124924, 'root', '2015-06-03 15:27:01', 70, 1),
+	(142, 142, '133', 'adding payment detail with journal entry', 'SQL', 'V133__adding_payment_detail_with_journal_entry.sql', 1432448000, 'root', '2015-06-03 15:27:01', 31, 1),
+	(143, 143, '134', 'added column value on c configuration', 'SQL', 'V134__added_column_value_on_c_configuration.sql', -587477224, 'root', '2015-06-03 15:27:01', 25, 1),
+	(144, 144, '134.1', 'submitted date updation for clients', 'SQL', 'V134_1__submitted_date_updation_for_clients.sql', -1714823906, 'root', '2015-06-03 15:27:01', 2, 1),
+	(145, 145, '134.2', 'permissions spelling correction', 'SQL', 'V134_2__permissions_spelling_correction.sql', -1824098129, 'root', '2015-06-03 15:27:02', 1, 1),
+	(146, 146, '135', 'added permission for undo written off', 'SQL', 'V135__added_permission_for_undo_written_off.sql', 426644857, 'root', '2015-06-03 15:27:02', 1, 1),
+	(147, 147, '136.1', 'update script strechy parameter', 'SQL', 'V136_1__update_script_strechy_parameter.sql', 633461657, 'root', '2015-06-03 15:27:02', 1, 1),
+	(148, 148, '137', 'added is active column in m staff', 'SQL', 'V137__added_is_active_column_in_m_staff.sql', 1962782431, 'root', '2015-06-03 15:27:02', 50, 1),
+	(149, 149, '138', 'add short name for m product loan and m savings product', 'SQL', 'V138__add_short_name_for_m_product_loan_and_m_savings_product.sql', 420749751, 'root', '2015-06-03 15:27:02', 265, 1),
+	(150, 150, '139', 'default value for is active updated to true in m staff', 'SQL', 'V139__default_value_for_is_active_updated_to_true_in_m_staff.sql', -633907272, 'root', '2015-06-03 15:27:02', 45, 1),
+	(14, 14, '14', 'rename status id to enum', 'SQL', 'V14__rename_status_id_to_enum.sql', -309404445, 'root', '2015-06-03 15:26:52', 55, 1),
+	(151, 151, '140', 'added loan charge status', 'SQL', 'V140__added_loan_charge_status.sql', 701858626, 'root', '2015-06-03 15:27:02', 65, 1),
+	(152, 152, '140.1', 'added payment detail id in ac gl journal entry', 'SQL', 'V140_1__added_payment_detail_id_in_ac_gl_journal_entry.sql', -2051594288, 'root', '2015-06-03 15:27:02', 33, 1),
+	(153, 153, '141', 'add early repayment strategy', 'SQL', 'V141__add_early_repayment_strategy.sql', 1510094803, 'root', '2015-06-03 15:27:02', 1, 1),
+	(154, 154, '142', 'read savingsaccount charge permission', 'SQL', 'V142__read_savingsaccount_charge_permission.sql', 643820806, 'root', '2015-06-03 15:27:02', 1, 1),
+	(155, 155, '143', 'create journalentry checker permission', 'SQL', 'V143__create_journalentry_checker_permission.sql', 1931469061, 'root', '2015-06-03 15:27:02', 1, 1),
+	(156, 156, '144', 'spelling mistake corrections', 'SQL', 'V144__spelling_mistake_corrections.sql', 196034832, 'root', '2015-06-03 15:27:02', 18, 1),
+	(157, 157, '145', 'add force password reset in c configuration', 'SQL', 'V145__add_force_password_reset_in_c_configuration.sql', 521336058, 'root', '2015-06-03 15:27:02', 164, 1),
+	(158, 158, '146', 'tranche loans', 'SQL', 'V146__tranche_loans.sql', 500763449, 'root', '2015-06-03 15:27:03', 180, 1),
+	(159, 159, '147', 'tranche loans column name changes', 'SQL', 'V147__tranche_loans_column_name_changes.sql', 400468365, 'root', '2015-06-03 15:27:03', 47, 1),
+	(160, 160, '148', 'overdraft changes', 'SQL', 'V148__overdraft_changes.sql', 1529306114, 'root', '2015-06-03 15:27:03', 143, 1),
+	(161, 161, '149', 'add created date savings transaction', 'SQL', 'V149__add_created_date_savings_transaction.sql', -116162300, 'root', '2015-06-03 15:27:03', 43, 1),
+	(15, 15, '15', 'center permissions', 'SQL', 'V15__center_permissions.sql', 2015498904, 'root', '2015-06-03 15:26:52', 3, 1),
+	(162, 162, '150', 'basic savings report', 'SQL', 'V150__basic_savings_report.sql', 1230058085, 'root', '2015-06-03 15:27:03', 53, 1),
+	(163, 163, '151', 'add default savings account to client', 'SQL', 'V151__add_default_savings_account_to_client.sql', 1421080968, 'root', '2015-06-03 15:27:03', 44, 1),
+	(164, 164, '152', 'added grace for over due', 'SQL', 'V152__added_grace_for_over_due.sql', -1487653468, 'root', '2015-06-03 15:27:03', 141, 1),
+	(165, 165, '153', 'Insert missed permissions', 'SQL', 'V153__Insert_missed_permissions.sql', 1384787449, 'root', '2015-06-03 15:27:03', 3, 1),
+	(166, 166, '154', 'aging details', 'SQL', 'V154__aging_details.sql', 1117759702, 'root', '2015-06-03 15:27:03', 1, 1),
+	(167, 167, '155', 'stretchy into pentaho', 'SQL', 'V155__stretchy_into_pentaho.sql', 1146009989, 'root', '2015-06-03 15:27:03', 45, 1),
+	(168, 168, '156', 'added loan saving txns pentaho', 'SQL', 'V156__added_loan_saving_txns_pentaho.sql', 1288577073, 'root', '2015-06-03 15:27:03', 4, 1),
+	(169, 169, '157', 'overdue charge improvements', 'SQL', 'V157__overdue_charge_improvements.sql', -1602717371, 'root', '2015-06-03 15:27:03', 63, 1),
+	(170, 170, '158', 'dashboard and navigation queries', 'SQL', 'V158__dashboard_and_navigation_queries.sql', 555462763, 'root', '2015-06-03 15:27:03', 8, 1),
+	(171, 171, '159', 'add transaction id column m portfolio command source', 'SQL', 'V159__add_transaction_id_column_m_portfolio_command_source.sql', 724056808, 'root', '2015-06-03 15:27:04', 99, 1),
+	(16, 16, '16', 'drop min max column on loan table', 'SQL', 'V16__drop_min_max_column_on_loan_table.sql', 120700748, 'root', '2015-06-03 15:26:52', 83, 1),
+	(172, 172, '160', 'standing instruction changes', 'SQL', 'V160__standing_instruction_changes.sql', -1634447666, 'root', '2015-06-03 15:27:04', 131, 1),
+	(173, 173, '160.2', 'Allow nullValue For principal on lonProduct', 'SQL', 'V160_2__Allow_nullValue_For_principal_on_lonProduct.sql', 844844635, 'root', '2015-06-03 15:27:04', 103, 1),
+	(174, 174, '161', 'added accrual batch job', 'SQL', 'V161__added_accrual_batch_job.sql', -760303345, 'root', '2015-06-03 15:27:04', 68, 1),
+	(175, 175, '162', 'overdue charge batch job', 'SQL', 'V162__overdue_charge_batch_job.sql', -1213828784, 'root', '2015-06-03 15:27:04', 1, 1),
+	(176, 176, '163', 'added npa for loans', 'SQL', 'V163__added_npa_for_loans.sql', 1832640598, 'root', '2015-06-03 15:27:04', 152, 1),
+	(177, 177, '164', 'fd and rd deposit tables', 'SQL', 'V164__fd_and_rd_deposit_tables.sql', -1202481632, 'root', '2015-06-03 15:27:04', 389, 1),
+	(178, 178, '165', 'added permission for disburse to saving account', 'SQL', 'V165__added_permission_for_disburse_to_saving_account.sql', -2109143723, 'root', '2015-06-03 15:27:05', 2, 1),
+	(179, 179, '166', 'added deposit amount to product term and preclosure', 'SQL', 'V166__added_deposit_amount_to_product_term_and_preclosure.sql', -452742507, 'root', '2015-06-03 15:27:05', 81, 1),
+	(180, 180, '167', 'added columns for writtenOff loans recovered', 'SQL', 'V167__added_columns_for_writtenOff_loans_recovered.sql', 384306348, 'root', '2015-06-03 15:27:05', 94, 1),
+	(181, 181, '168', 'added transfer fixed deposit interest to linked account', 'SQL', 'V168__added_transfer_fixed_deposit_interest_to_linked_account.sql', -246915767, 'root', '2015-06-03 15:27:05', 89, 1),
+	(182, 182, '169', 'update dashboard reports to core reports use report to false', 'SQL', 'V169__update_dashboard_reports_to_core_reports_use_report_to_false.sql', 1910199831, 'root', '2015-06-03 15:27:05', 1, 1),
+	(17, 17, '17', 'update stretchy reporting ddl', 'SQL', 'V17__update_stretchy_reporting_ddl.sql', -1374690095, 'root', '2015-06-03 15:26:52', 141, 1),
+	(183, 183, '170', 'update deposit accounts maturity details job', 'SQL', 'V170__update_deposit_accounts_maturity_details_job.sql', 348328732, 'root', '2015-06-03 15:27:05', 2, 1),
+	(184, 184, '171', 'added mandatory savings and rd changes', 'SQL', 'V171__added_mandatory_savings_and_rd_changes.sql', -106401726, 'root', '2015-06-03 15:27:05', 324, 1),
+	(185, 185, '172', 'accounting changes for transfers', 'SQL', 'V172__accounting_changes_for_transfers.sql', 398136509, 'root', '2015-06-03 15:27:05', 58, 1),
+	(186, 186, '173', 'ppi', 'SQL', 'V173__ppi.sql', -512596643, 'root', '2015-06-03 15:27:05', 107, 1),
+	(187, 187, '174', 'remove interest accrual', 'SQL', 'V174__remove_interest_accrual.sql', -353161686, 'root', '2015-06-03 15:27:05', 1, 1),
+	(188, 188, '175', 'added incentive interest rates', 'SQL', 'V175__added_incentive_interest_rates.sql', 749853165, 'root', '2015-06-03 15:27:06', 167, 1),
+	(189, 189, '176', 'updates to financial activity accounts', 'SQL', 'V176__updates_to_financial_activity_accounts.sql', -1274960595, 'root', '2015-06-03 15:27:06', 85, 1),
+	(190, 190, '177', 'cleanup for client incentives', 'SQL', 'V177__cleanup_for_client_incentives.sql', -1838944707, 'root', '2015-06-03 15:27:06', 2, 1),
+	(191, 191, '178', 'updates to financial activity accounts pt2', 'SQL', 'V178__updates_to_financial_activity_accounts_pt2.sql', -658545948, 'root', '2015-06-03 15:27:06', 4, 1),
+	(192, 192, '179', 'updates to action names for maker checker permissions', 'SQL', 'V179__updates_to_action_names_for_maker_checker_permissions.sql', 255160379, 'root', '2015-06-03 15:27:06', 4, 1),
+	(18, 18, '18', 'update stretchy reporting reportSql', 'SQL', 'V18__update_stretchy_reporting_reportSql.sql', 1012533433, 'root', '2015-06-03 15:26:52', 4, 1),
+	(193, 193, '180', 'update report schemas for disbursed vs awaitingdisbursal and groupnamesbystaff', 'SQL', 'V180__update_report_schemas_for_disbursed_vs_awaitingdisbursal_and_groupnamesbystaff.sql', 674107071, 'root', '2015-06-03 15:27:06', 2, 1),
+	(194, 194, '181', 'standing instruction logging', 'SQL', 'V181__standing_instruction_logging.sql', -737719462, 'root', '2015-06-03 15:27:06', 35, 1),
+	(195, 195, '182', 'added min required balance to savings product', 'SQL', 'V182__added_min_required_balance_to_savings_product.sql', -1648679936, 'root', '2015-06-03 15:27:06', 76, 1),
+	(196, 196, '183', 'added min balance for interest calculation', 'SQL', 'V183__added_min_balance_for_interest_calculation.sql', 1902310539, 'root', '2015-06-03 15:27:06', 105, 1),
+	(197, 197, '184', 'update min required balance for savings product', 'SQL', 'V184__update_min_required_balance_for_savings_product.sql', 313972591, 'root', '2015-06-03 15:27:06', 28, 1),
+	(198, 198, '185', 'add accrual till date for periodic accrual', 'SQL', 'V185__add_accrual_till_date_for_periodic_accrual.sql', 1705918516, 'root', '2015-06-03 15:27:06', 144, 1),
+	(199, 199, '186', 'added periodic accrual job', 'SQL', 'V186__added_periodic_accrual_job.sql', 394599380, 'root', '2015-06-03 15:27:06', 1, 1),
+	(200, 200, '187', 'added permission to periodic accrual', 'SQL', 'V187__added_permission_to_periodic_accrual.sql', 1479836850, 'root', '2015-06-03 15:27:06', 2, 1),
+	(201, 201, '188', 'add savingscharge inactivate permissions', 'SQL', 'V188__add_savingscharge_inactivate_permissions.sql', -740798972, 'root', '2015-06-03 15:27:06', 41, 1),
+	(202, 202, '189', 'm loan interest recalculation tables', 'SQL', 'V189__m_loan_interest_recalculation_tables.sql', -873206694, 'root', '2015-06-03 15:27:07', 301, 1),
+	(19, 19, '19', 'report maintenance permissions', 'SQL', 'V19__report_maintenance_permissions.sql', 57066563, 'root', '2015-06-03 15:26:52', 4, 1),
+	(203, 203, '190', 'add associategroup disassociategroup permissions', 'SQL', 'V190__add_associategroup_disassociategroup_permissions.sql', 1517251106, 'root', '2015-06-03 15:27:07', 2, 1),
+	(204, 204, '191', 'update gl account increase size of name col', 'SQL', 'V191__update_gl_account_increase_size_of_name_col.sql', -1113630867, 'root', '2015-06-03 15:27:07', 17, 1),
+	(205, 205, '192', 'interest recalculate job', 'SQL', 'V192__interest_recalculate_job.sql', -110230216, 'root', '2015-06-03 15:27:07', 2, 1),
+	(206, 206, '193', 'added column joiningDate for staff', 'SQL', 'V193__added_column_joiningDate_for_staff.sql', -443555800, 'root', '2015-06-03 15:27:07', 33, 1),
+	(207, 207, '194', 'added recalculatedInterestComponent for interest recalculation', 'SQL', 'V194__added_recalculatedInterestComponent_for_interest_recalculation.sql', 2052912155, 'root', '2015-06-03 15:27:07', 35, 1),
+	(208, 208, '195', 'moved rest frequency to product level', 'SQL', 'V195__moved_rest_frequency_to_product_level.sql', 1459923988, 'root', '2015-06-03 15:27:07', 97, 1),
+	(209, 209, '196', 'added loan running balance to transactions', 'SQL', 'V196__added_loan_running_balance_to_transactions.sql', 342588167, 'root', '2015-06-03 15:27:07', 48, 1),
+	(210, 210, '197', 'updated loan running balance of transactions', 'SQL', 'V197__updated_loan_running_balance_of_transactions.sql', 473184964, 'root', '2015-06-03 15:27:07', 27, 1),
+	(211, 211, '198', 'loan rescheduling tables and permissions', 'SQL', 'V198__loan_rescheduling_tables_and_permissions.sql', 689132282, 'root', '2015-06-03 15:27:07', 128, 1),
+	(212, 212, '199', 'removed extra columns from schedule history', 'SQL', 'V199__removed_extra_columns_from_schedule_history.sql', 1961301885, 'root', '2015-06-03 15:27:07', 105, 1),
+	(2, 2, '2', 'mifosx-base-reference-data-utf8', 'SQL', 'V2__mifosx-base-reference-data-utf8.sql', 2084750372, 'root', '2015-06-03 15:26:50', 30, 1),
+	(20, 20, '20', 'report maint perms really configuration', 'SQL', 'V20__report_maint_perms_really_configuration.sql', -796088526, 'root', '2015-06-03 15:26:52', 1, 1),
+	(213, 213, '200', 'alter savings account for start interest calculation date', 'SQL', 'V200__alter_savings_account_for_start_interest_calculation_date.sql', -2046824671, 'root', '2015-06-03 15:27:07', 68, 1),
+	(214, 214, '201', 'webhooks', 'SQL', 'V201__webhooks.sql', -1852431117, 'root', '2015-06-03 15:27:08', 194, 1),
+	(215, 215, '202', 'savings officer history table', 'SQL', 'V202__savings_officer_history_table.sql', 1515516270, 'root', '2015-06-03 15:27:08', 50, 1),
+	(216, 216, '203', 'added subbmittedDate loantransaction', 'SQL', 'V203__added_subbmittedDate_loantransaction.sql', 762589044, 'root', '2015-06-03 15:27:08', 34, 1),
+	(217, 217, '204', 'insert script for charges paid by for accruals', 'SQL', 'V204__insert_script_for_charges_paid_by_for_accruals.sql', 1126139057, 'root', '2015-06-03 15:27:08', 2, 1),
+	(218, 218, '205', 'fix for charge and interest waiver with accruals', 'SQL', 'V205__fix_for_charge_and_interest_waiver_with_accruals.sql', 1834454603, 'root', '2015-06-03 15:27:08', 91, 1),
+	(219, 219, '206', 'interest posting configuration', 'SQL', 'V206__interest_posting_configuration.sql', 1777902577, 'root', '2015-06-03 15:27:08', 52, 1),
+	(220, 220, '207', 'min max clients per group', 'SQL', 'V207__min_max_clients_per_group.sql', -1776502977, 'root', '2015-06-03 15:27:08', 46, 1),
+	(221, 221, '208', 'min max clients in group redux', 'SQL', 'V208__min_max_clients_in_group_redux.sql', -1881405737, 'root', '2015-06-03 15:27:08', 47, 1),
+	(222, 222, '209', 'add all report names in m permission table', 'SQL', 'V209__add_all_report_names_in_m_permission_table.sql', 1034507855, 'root', '2015-06-03 15:27:08', 9, 1),
+	(21, 21, '21', 'activation-permissions-for-clients', 'SQL', 'V21__activation-permissions-for-clients.sql', 1289685589, 'root', '2015-06-03 15:26:52', 45, 1),
+	(223, 223, '210', 'track manually adjusted transactions', 'SQL', 'V210__track_manually_adjusted_transactions.sql', -1444778976, 'root', '2015-06-03 15:27:08', 47, 1),
+	(224, 224, '211', 'minimum days between disbursal and first repayment', 'SQL', 'V211__minimum_days_between_disbursal_and_first_repayment.sql', 1660532746, 'root', '2015-06-03 15:27:08', 51, 1),
+	(225, 225, '212', 'add NthDay and DayOfWeek columns loan', 'SQL', 'V212__add_NthDay_and_DayOfWeek_columns_loan.sql', -924123306, 'root', '2015-06-03 15:27:08', 100, 1),
+	(226, 226, '213', 'NthDay and DayOfWeek columns should be nullable', 'SQL', 'V213__NthDay_and_DayOfWeek_columns_should_be_nullable.sql', -240730886, 'root', '2015-06-03 15:27:09', 135, 1),
+	(227, 227, '214', 'alter table add create SI at disbursement', 'SQL', 'V214__alter_table_add_create_SI_at_disbursement.sql', -473599398, 'root', '2015-06-03 15:27:09', 272, 1),
+	(228, 228, '215', 'guarantee on hold fund changes', 'SQL', 'V215__guarantee_on_hold_fund_changes.sql', 111795154, 'root', '2015-06-03 15:27:09', 235, 1),
+	(229, 229, '216', 'adding loan proposed amount to loan', 'SQL', 'V216__adding_loan_proposed_amount_to_loan.sql', 2123229215, 'root', '2015-06-03 15:27:09', 109, 1),
+	(230, 230, '217', 'client substatus and codevalue description', 'SQL', 'V217__client_substatus_and_codevalue_description.sql', 1344632615, 'root', '2015-06-03 15:27:09', 88, 1),
+	(231, 231, '218', 'add user and datetime for loan savings transactions', 'SQL', 'V218__add_user_and_datetime_for_loan_savings_transactions.sql', 939524468, 'root', '2015-06-03 15:27:10', 115, 1),
+	(232, 232, '219', 'guarantor on hold fund changes for account', 'SQL', 'V219__guarantor_on_hold_fund_changes_for_account.sql', -380048619, 'root', '2015-06-03 15:27:10', 327, 1),
+	(22, 22, '22', 'alter-group-for-consistency-add-permissions', 'SQL', 'V22__alter-group-for-consistency-add-permissions.sql', 1509095759, 'root', '2015-06-03 15:26:52', 130, 1),
+	(233, 233, '220', 'account number preferences', 'SQL', 'V220__account_number_preferences.sql', 765825838, 'root', '2015-06-03 15:27:10', 22, 1),
+	(234, 234, '221', 'add version for m savings account', 'SQL', 'V221__add_version_for_m_savings_account.sql', -2026329127, 'root', '2015-06-03 15:27:10', 49, 1),
+	(235, 235, '222', 'guarantor on hold fund changes for transactions', 'SQL', 'V222__guarantor_on_hold_fund_changes_for_transactions.sql', -426530719, 'root', '2015-06-03 15:27:10', 193, 1),
+	(236, 236, '223', 'add version for m loan account', 'SQL', 'V223__add_version_for_m_loan_account.sql', -1129379217, 'root', '2015-06-03 15:27:10', 134, 1),
+	(237, 237, '224', 'client lifecycle adding statuses', 'SQL', 'V224__client_lifecycle_adding_statuses.sql', 43152274, 'root', '2015-06-03 15:27:11', 181, 1),
+	(238, 238, '225', 'permissions for updating recurring deposit amount', 'SQL', 'V225__permissions_for_updating_recurring_deposit_amount.sql', -564145896, 'root', '2015-06-03 15:27:11', 1, 1),
+	(239, 239, '226', 'configuration for enforcing calendars for jlg loans', 'SQL', 'V226__configuration_for_enforcing_calendars_for_jlg_loans.sql', -382855919, 'root', '2015-06-03 15:27:11', 1, 1),
+	(240, 240, '227', 'loan-refund-permissions', 'SQL', 'V227__loan-refund-permissions.sql', -511074400, 'root', '2015-06-03 15:27:11', 2, 1),
+	(241, 241, '228', 'entity to entity access', 'SQL', 'V228__entity_to_entity_access.sql', -409076299, 'root', '2015-06-03 15:27:11', 108, 1),
+	(242, 242, '229', 'teller cash management', 'SQL', 'V229__teller_cash_management.sql', 2147103896, 'root', '2015-06-03 15:27:11', 83, 1),
+	(23, 23, '23', 'remove-enable-disable-configuration-for-client-group-status', 'SQL', 'V23__remove-enable-disable-configuration-for-client-group-status.sql', 1496208571, 'root', '2015-06-03 15:26:53', 67, 1),
+	(243, 243, '230', 'role status and correspoding permissions', 'SQL', 'V230__role_status_and_correspoding_permissions.sql', -21174595, 'root', '2015-06-03 15:27:11', 34, 1),
+	(244, 244, '231', 'm cashier transaction added currency code', 'SQL', 'V231__m_cashier_transaction_added_currency_code.sql', -1593672561, 'root', '2015-06-03 15:27:11', 32, 1),
+	(245, 245, '232', 'insert center closure reason', 'SQL', 'V232__insert_center_closure_reason.sql', -2049914418, 'root', '2015-06-03 15:27:11', 1, 1),
+	(246, 246, '233', 'Savings Transaction Receipt', 'SQL', 'V233__Savings_Transaction_Receipt.sql', 1836289382, 'root', '2015-06-03 15:27:11', 2, 1),
+	(247, 247, '234', 'opening balaces setup', 'SQL', 'V234__opening_balaces_setup.sql', 1777198314, 'root', '2015-06-03 15:27:11', 23, 1),
+	(248, 248, '235', 'add ugd template id m hook', 'SQL', 'V235__add_ugd_template_id_m_hook.sql', 1120955673, 'root', '2015-06-03 15:27:11', 81, 1),
+	(249, 249, '236', 'individual collection sheet permissions', 'SQL', 'V236__individual_collection_sheet_permissions.sql', -66130238, 'root', '2015-06-03 15:27:11', 1, 1),
+	(250, 250, '237', 'add threshold config for last instalment', 'SQL', 'V237__add_threshold_config_for_last_instalment.sql', 412873149, 'root', '2015-06-03 15:27:11', 59, 1),
+	(251, 251, '238', 'update staff display name length', 'SQL', 'V238__update_staff_display_name_length.sql', -1003425306, 'root', '2015-06-03 15:27:11', 47, 1),
+	(252, 252, '239', 'Loan Transaction Receipt', 'SQL', 'V239__Loan_Transaction_Receipt.sql', -130819179, 'root', '2015-06-03 15:27:11', 2, 1),
+	(24, 24, '24', 'add-group-client-foreign-key-constraint-in-loan-table', 'SQL', 'V24__add-group-client-foreign-key-constraint-in-loan-table.sql', 1077707078, 'root', '2015-06-03 15:26:53', 141, 1),
+	(253, 253, '240', 'arrears aging config for interest recalculation', 'SQL', 'V240__arrears_aging_config_for_interest_recalculation.sql', 674368034, 'root', '2015-06-03 15:27:12', 129, 1),
+	(254, 254, '241', 'fixed emi changes', 'SQL', 'V241__fixed_emi_changes.sql', 1943069939, 'root', '2015-06-03 15:27:12', 53, 1),
+	(255, 255, '242', 'entitytoentitymappingrelation', 'SQL', 'V242__entitytoentitymappingrelation.sql', -1770973716, 'root', '2015-06-03 15:27:12', 40, 1),
+	(256, 256, '243', 'alter loan disbursement details', 'SQL', 'V243__alter_loan_disbursement_details.sql', 1461060824, 'root', '2015-06-03 15:27:12', 48, 1),
+	(257, 257, '244', 'staff assignment history table', 'SQL', 'V244__staff_assignment_history_table.sql', -427095856, 'root', '2015-06-03 15:27:12', 33, 1),
+	(258, 258, '245', 'open rd changes', 'SQL', 'V245__open_rd_changes.sql', 2142566381, 'root', '2015-06-03 15:27:12', 1, 1),
+	(259, 259, '246', 'drop group client foreign key from m loan', 'SQL', 'V246__drop_group_client_foreign_key_from_m_loan.sql', -1721132405, 'root', '2015-06-03 15:27:12', 35, 1),
+	(260, 260, '247', 'consistency wrt spelling principalThresholdForLastInstalment', 'SQL', 'V247__consistency_wrt_spelling_principalThresholdForLastInstalment.sql', 1371980378, 'root', '2015-06-03 15:27:12', 7, 1),
+	(261, 261, '248', 'added password never expired to User', 'SQL', 'V248__added_password_never_expired_to_User.sql', -1800179163, 'root', '2015-06-03 15:27:12', 43, 1),
+	(262, 262, '249', 'workingdays permissions', 'SQL', 'V249__workingdays_permissions.sql', -1322891155, 'root', '2015-06-03 15:27:12', 1, 1),
+	(25, 25, '25', 'update client reports for status and activation change', 'SQL', 'V25__update_client_reports_for_status_and_activation_change.sql', -6310920, 'root', '2015-06-03 15:26:53', 3, 1),
+	(263, 263, '250', 'password validation policy', 'SQL', 'V250__password_validation_policy.sql', 1197290340, 'root', '2015-06-03 15:27:12', 26, 1),
+	(264, 264, '251', 'paymentType table', 'SQL', 'V251__paymentType_table.sql', -1969329175, 'root', '2015-06-03 15:27:12', 75, 1),
+	(265, 265, '252', 'bug fix teller cash management', 'SQL', 'V252__bug_fix_teller_cash_management.sql', -736743970, 'root', '2015-06-03 15:27:12', 110, 1),
+	(266, 266, '253', 'product loan configurable attributes', 'SQL', 'V253__product_loan_configurable_attributes.sql', 1787268316, 'root', '2015-06-03 15:27:12', 16, 1),
+	(267, 267, '254', 'General Ledger Report', 'SQL', 'V254__General_Ledger_Report.sql', -186920768, 'root', '2015-06-03 15:27:12', 4, 1),
+	(268, 268, '255', 'pre close interest period config', 'SQL', 'V255__pre_close_interest_period_config.sql', 1383225707, 'root', '2015-06-03 15:27:12', 26, 1),
+	(269, 269, '256', 'Update script for General Ledger report', 'SQL', 'V256__Update script for General_Ledger_report.sql', 952686971, 'root', '2015-06-03 15:27:12', 3, 1),
+	(270, 270, '257', 'staff image association', 'SQL', 'V257__staff_image_association.sql', 1740118046, 'root', '2015-06-03 15:27:12', 41, 1),
+	(271, 271, '258', 'interest compounding changes', 'SQL', 'V258__interest_compounding_changes.sql', 1484848861, 'root', '2015-06-03 15:27:13', 84, 1),
+	(272, 272, '259', 'alter working days', 'SQL', 'V259__alter_working_days.sql', 1733733251, 'root', '2015-06-03 15:27:13', 28, 1),
+	(26, 26, '26', 'add-support-for-withdrawal-fees-on-savings', 'SQL', 'V26__add-support-for-withdrawal-fees-on-savings.sql', -755832247, 'root', '2015-06-03 15:26:53', 176, 1),
+	(273, 273, '260', 'alter password validation policy', 'SQL', 'V260__alter_password_validation_policy.sql', -853716637, 'root', '2015-06-03 15:27:13', 28, 1),
+	(274, 274, '261', 'Update script for Client Loan Account Schedule Report', 'SQL', 'V261__Update script for Client_Loan_Account_Schedule_Report.sql', 1873100628, 'root', '2015-09-06 17:49:01', 20, 1),
+	(275, 275, '262', 'accountNumber for groups', 'SQL', 'V262__accountNumber_for_groups.sql', -31083607, 'root', '2015-09-06 17:49:01', 179, 1),
+	(276, 276, '263', 'mifos reports', 'SQL', 'V263__mifos_reports.sql', -1358041795, 'root', '2015-09-06 17:49:01', 27, 1),
+	(277, 277, '264', 'insert paymenttype and report read permission', 'SQL', 'V264__insert_paymenttype_and_report_read_permission.sql', 984979503, 'root', '2015-09-06 17:49:01', 6, 1),
+	(278, 278, '265', 'modify external service schema', 'SQL', 'V265__modify_external_service_schema.sql', 1844344576, 'root', '2015-09-06 17:49:02', 350, 1),
+	(279, 279, '266', 'client fees', 'SQL', 'V266__client_fees.sql', 41332385, 'root', '2015-09-06 17:49:02', 112, 1),
+	(280, 280, '267', 'client transaction permissions', 'SQL', 'V267__client_transaction_permissions.sql', 130000057, 'root', '2015-09-06 17:49:02', 6, 1),
+	(281, 281, '268', 'update gmail password', 'SQL', 'V268__update_gmail_password.sql', 1723317114, 'root', '2015-09-06 17:49:02', 8, 1),
+	(282, 282, '269', 'increased calendar title length ', 'SQL', 'V269__increased_calendar_title_length .sql', 1780890645, 'root', '2015-09-06 17:49:02', 156, 1),
+	(27, 27, '27', 'add-loan-type-column-to-loan-table', 'SQL', 'V27__add-loan-type-column-to-loan-table.sql', -2130377861, 'root', '2015-06-03 15:26:53', 97, 1),
+	(283, 283, '270', 'add rounding mode configuration', 'SQL', 'V270__add_rounding_mode_configuration.sql', 1195237290, 'root', '2015-09-06 17:49:02', 78, 1),
+	(284, 284, '271', 'accounting for client charges', 'SQL', 'V271__accounting_for_client_charges.sql', 1477443700, 'root', '2015-09-06 17:49:03', 184, 1),
+	(285, 285, '272', 'loan tranche disbursement charge', 'SQL', 'V272__loan_tranche_disbursement_charge.sql', 2018052750, 'root', '2015-09-06 17:49:03', 182, 1),
+	(286, 286, '273', 'oauth changes', 'SQL', 'V273__oauth_changes.sql', 1811521678, 'root', '2015-09-09 13:21:37', 120, 1),
+	(287, 287, '274', 'Loan Reschedule Code Value', 'SQL', 'V274__Loan_Reschedule_Code_Value.sql', -1190544276, 'root', '2015-09-15 18:00:14', 63, 1),
+	(288, 288, '275', 'loan transaction to repayment schedule mapping', 'SQL', 'V275__loan_transaction_to_repayment_schedule_mapping.sql', 1971001203, 'root', '2015-09-21 20:04:43', 570, 1),
+	(289, 289, '276', 'loan recalulated till date', 'SQL', 'V276__loan_recalulated_till_date.sql', 631764351, 'root', '2015-10-20 19:57:50', 1743, 1),
+	(290, 290, '277', 'Loan Product Provisioning', 'SQL', 'V277__Loan_Product_Provisioning.sql', -510229006, 'root', '2015-10-20 19:57:53', 2792, 1),
+	(291, 291, '278', 'LoanTransactionProcessingStrategy', 'SQL', 'V278__LoanTransactionProcessingStrategy.sql', -1388446419, 'root', '2015-11-04 17:03:00', 878, 1),
+	(292, 292, '279', 'floating rates', 'SQL', 'V279__floating_rates.sql', 830029264, 'root', '2015-11-18 16:13:08', 620, 1),
+	(28, 28, '28', 'accounting-abstractions-and-autoposting', 'SQL', 'V28__accounting-abstractions-and-autoposting.sql', -626584837, 'root', '2015-06-03 15:26:53', 63, 1),
+	(293, 293, '280', 'spm framework initial tables', 'SQL', 'V280__spm_framework_initial_tables.sql', -1638980235, 'root', '2015-12-02 16:07:43', 509, 1),
+	(294, 294, '281', 'add configuration param backdate-penalties', 'SQL', 'V281__add_configuration_param_backdate-penalties.sql', -45520299, 'root', '2015-12-02 16:07:44', 39, 1),
+	(295, 295, '282', 'CustomerSelfService', 'SQL', 'V282__CustomerSelfService.sql', -51763400, 'root', '2015-12-17 10:17:33', 440, 1),
+	(296, 296, '283', 'Variable Installments', 'SQL', 'V283__Variable_Installments.sql', -1104936867, 'root', '2016-01-12 17:39:15', 2067, 1),
+	(297, 297, '284', 'update codevalue', 'SQL', 'V284__update_codevalue.sql', 442711672, 'root', '2016-01-12 17:39:16', 982, 1),
+	(298, 298, '285', 'undo last tranche script', 'SQL', 'V285__undo_last_tranche_script.sql', 1551040289, 'root', '2016-01-12 17:39:17', 56, 1),
+	(299, 299, '286', 'partial period interest calcualtion', 'SQL', 'V286__partial_period_interest_calcualtion.sql', -1701869481, 'root', '2016-01-12 17:39:19', 1898, 1),
+	(300, 300, '287', 'alter spm scorecard', 'SQL', 'V287__alter_spm_scorecard.sql', 1834026952, 'root', '2016-01-20 18:23:18', 227, 1),
+	(301, 301, '288', 'overdraft interest', 'SQL', 'V288__overdraft_interest.sql', 2003058104, 'root', '2016-01-20 18:23:19', 282, 1),
+	(302, 302, '289', 'client non person', 'SQL', 'V289__client_non_person.sql', 1595576360, 'root', '2016-01-20 18:23:19', 239, 1),
+	(29, 29, '29', 'add-support-for-annual-fees-on-savings', 'SQL', 'V29__add-support-for-annual-fees-on-savings.sql', -1595233842, 'root', '2015-06-03 15:26:53', 157, 1),
+	(303, 303, '290', 'shares dividends permissions script', 'SQL', 'V290__shares_dividends_permissions_script.sql', -1504459497, 'root', '2016-01-20 18:23:19', 47, 1),
+	(3, 3, '3', 'mifosx-permissions-and-authorisation-utf8', 'SQL', 'V3__mifosx-permissions-and-authorisation-utf8.sql', 914436650, 'root', '2015-06-03 15:26:50', 14, 1),
+	(30, 30, '30', 'add-referenceNumber-to-acc gl journal entry', 'SQL', 'V30__add-referenceNumber-to-acc_gl_journal_entry.sql', 255130282, 'root', '2015-06-03 15:26:53', 59, 1),
+	(31, 31, '31', 'drop-autopostings', 'SQL', 'V31__drop-autopostings.sql', -2072166818, 'root', '2015-06-03 15:26:53', 5, 1),
+	(32, 32, '32', 'associate-disassociate-clients-from-group-permissions', 'SQL', 'V32__associate-disassociate-clients-from-group-permissions.sql', -947369256, 'root', '2015-06-03 15:26:53', 2, 1),
+	(33, 33, '33', 'drop unique check on stretchy report parameter', 'SQL', 'V33__drop_unique_check_on_stretchy_report_parameter.sql', -1599579296, 'root', '2015-06-03 15:26:53', 23, 1),
+	(34, 34, '34', 'add unique check on stretchy report parameter', 'SQL', 'V34__add_unique_check_on_stretchy_report_parameter.sql', -1286928230, 'root', '2015-06-03 15:26:53', 22, 1),
+	(35, 35, '35', 'add hierarchy column for acc gl account', 'SQL', 'V35__add_hierarchy_column_for_acc_gl_account.sql', -1387013309, 'root', '2015-06-03 15:26:54', 49, 1),
+	(36, 36, '36', 'add tag id column for acc gl account', 'SQL', 'V36__add_tag_id_column_for_acc_gl_account.sql', 414916166, 'root', '2015-06-03 15:26:54', 26, 1),
+	(37, 37, '37', 'add-center-group-collection-sheet-permissions', 'SQL', 'V37__add-center-group-collection-sheet-permissions.sql', -1268924209, 'root', '2015-06-03 15:26:54', 2, 1),
+	(38, 38, '38', 'add-group-summary-details-report', 'SQL', 'V38__add-group-summary-details-report.sql', -1875404314, 'root', '2015-06-03 15:26:54', 2, 1),
+	(39, 39, '39', 'payment-channels-updates', 'SQL', 'V39__payment-channels-updates.sql', 598090296, 'root', '2015-06-03 15:26:54', 69, 1),
+	(4, 4, '4', 'mifosx-core-reports-utf8', 'SQL', 'V4__mifosx-core-reports-utf8.sql', -1810870969, 'root', '2015-06-03 15:26:50', 54, 1),
+	(40, 40, '40', 'add permissions for accounting rule', 'SQL', 'V40__add_permissions_for_accounting_rule.sql', 1514233058, 'root', '2015-06-03 15:26:54', 2, 1),
+	(41, 41, '41', 'group-summary-reports', 'SQL', 'V41__group-summary-reports.sql', 1425507637, 'root', '2015-06-03 15:26:54', 4, 1),
+	(42, 42, '42', 'Add default value for id for acc accounting rule', 'SQL', 'V42__Add_default_value_for_id_for_acc_accounting_rule.sql', 2059436790, 'root', '2015-06-03 15:26:54', 25, 1),
+	(43, 43, '43', 'accounting-for-savings', 'SQL', 'V43__accounting-for-savings.sql', -1542089062, 'root', '2015-06-03 15:26:54', 180, 1),
+	(44, 44, '44', 'document-increase-size-of-column-type', 'SQL', 'V44__document-increase-size-of-column-type.sql', 1059756585, 'root', '2015-06-03 15:26:54', 57, 1),
+	(45, 45, '45', 'create acc rule tags table', 'SQL', 'V45__create_acc_rule_tags_table.sql', -1290711661, 'root', '2015-06-03 15:26:54', 11, 1),
+	(46, 46, '46', 'extend datatables api', 'SQL', 'V46__extend_datatables_api.sql', 1658150926, 'root', '2015-06-03 15:26:54', 2, 1),
+	(47, 47, '47', 'staff-hierarchy-link-to-users', 'SQL', 'V47__staff-hierarchy-link-to-users.sql', 1571520914, 'root', '2015-06-03 15:26:54', 103, 1),
+	(48, 48, '48', 'adding-S3-Support', 'SQL', 'V48__adding-S3-Support.sql', -767612266, 'root', '2015-06-03 15:26:54', 164, 1),
+	(49, 49, '49', 'track-loan-charge-payment-transactions', 'SQL', 'V49__track-loan-charge-payment-transactions.sql', -1735511516, 'root', '2015-06-03 15:26:54', 24, 1),
+	(5, 5, '5', 'update-savings-product-and-account-tables', 'SQL', 'V5__update-savings-product-and-account-tables.sql', 1349701479, 'root', '2015-06-03 15:26:51', 122, 1),
+	(50, 50, '50', 'add-grace-settings-to-loan-product', 'SQL', 'V50__add-grace-settings-to-loan-product.sql', -1807166173, 'root', '2015-06-03 15:26:55', 140, 1),
+	(51, 51, '51', 'track-additional-details-related-to-installment-performance', 'SQL', 'V51__track-additional-details-related-to-installment-performance.sql', 729891777, 'root', '2015-06-03 15:26:55', 102, 1),
+	(52, 52, '52', 'add boolean support cols to acc accounting rule', 'SQL', 'V52__add_boolean_support_cols_to_acc_accounting_rule.sql', 1853745947, 'root', '2015-06-03 15:26:55', 71, 1),
+	(53, 53, '53', 'track-advance-and-late-payments-on-installment', 'SQL', 'V53__track-advance-and-late-payments-on-installment.sql', 1135041990, 'root', '2015-06-03 15:26:55', 45, 1),
+	(54, 54, '54', 'charge-to-income-account-mappings', 'SQL', 'V54__charge-to-income-account-mappings.sql', -302610090, 'root', '2015-06-03 15:26:55', 25, 1),
+	(55, 55, '55', 'add-additional-transaction-processing-strategies', 'SQL', 'V55__add-additional-transaction-processing-strategies.sql', -1366608716, 'root', '2015-06-03 15:26:55', 58, 1),
+	(56, 56, '56', 'track-overpaid-amount-on-loans', 'SQL', 'V56__track-overpaid-amount-on-loans.sql', -2006962467, 'root', '2015-06-03 15:26:55', 99, 1),
+	(57, 57, '57', 'add default values to debit and credit accounts acc accounting rule', 'SQL', 'V57__add_default_values_to_debit_and_credit_accounts_acc_accounting_rule.sql', 1041459650, 'root', '2015-06-03 15:26:55', 51, 1),
+	(58, 58, '58', 'create-holiday-tables changed', 'SQL', 'V58__create-holiday-tables_changed.sql', -1395337105, 'root', '2015-06-03 15:26:55', 150, 1),
+	(59, 59, '59', 'add group roles schema and permissions', 'SQL', 'V59__add_group_roles_schema_and_permissions.sql', -406383935, 'root', '2015-06-03 15:26:55', 55, 1),
+	(6, 6, '6', 'add min max principal column to loan', 'SQL', 'V6__add_min_max_principal_column_to_loan.sql', -907223871, 'root', '2015-06-03 15:26:51', 140, 1),
+	(60, 60, '60', 'quipo dashboard reports', 'SQL', 'V60__quipo_dashboard_reports.sql', -1618354471, 'root', '2015-06-03 15:26:56', 13, 1),
+	(61, 61, '61', 'txn running balance example', 'SQL', 'V61__txn_running_balance_example.sql', -80025043, 'root', '2015-06-03 15:26:56', 4, 1),
+	(62, 62, '62', 'add staff id to m client changed', 'SQL', 'V62__add_staff_id_to_m_client_changed.sql', 1148218006, 'root', '2015-06-03 15:26:56', 18, 1),
+	(63, 63, '63', 'add sync disbursement with meeting column to loan', 'SQL', 'V63__add_sync_disbursement_with_meeting_column_to_loan.sql', 1201879376, 'root', '2015-06-03 15:26:56', 85, 1),
+	(64, 64, '64', 'add permission for assign staff', 'SQL', 'V64__add_permission_for_assign_staff.sql', -1938102414, 'root', '2015-06-03 15:26:56', 1, 1),
+	(65, 65, '65', 'fix rupee symbol issues', 'SQL', 'V65__fix_rupee_symbol_issues.sql', 1008895069, 'root', '2015-06-03 15:26:56', 1, 1),
+	(66, 66, '66', 'client close functionality', 'SQL', 'V66__client_close_functionality.sql', -142847690, 'root', '2015-06-03 15:26:56', 38, 1),
+	(67, 67, '67', 'loans in advance table', 'SQL', 'V67__loans_in_advance_table.sql', 1665941254, 'root', '2015-06-03 15:26:56', 21, 1),
+	(68, 68, '68', 'quipo dashboard reports updated', 'SQL', 'V68__quipo_dashboard_reports_updated.sql', 1746719914, 'root', '2015-06-03 15:26:56', 14, 1),
+	(69, 69, '69', 'loans in advance initialise', 'SQL', 'V69__loans_in_advance_initialise.sql', 1518847594, 'root', '2015-06-03 15:26:56', 8, 1),
+	(7, 7, '7', 'remove read makerchecker permission', 'SQL', 'V7__remove_read_makerchecker_permission.sql', -569619336, 'root', '2015-06-03 15:26:51', 2, 1),
+	(70, 70, '70', 'quipo program detail query fix', 'SQL', 'V70__quipo_program_detail_query_fix.sql', 1892537189, 'root', '2015-06-03 15:26:56', 1, 1),
+	(71, 71, '71', 'insert reschedule repayment to configuration', 'SQL', 'V71__insert_reschedule_repayment_to_configuration.sql', -1148306529, 'root', '2015-06-03 15:26:56', 1, 1),
+	(72, 72, '72', 'add m loan counter changes', 'SQL', 'V72__add_m_loan_counter_changes.sql', 878990870, 'root', '2015-06-03 15:26:56', 63, 1),
+	(73, 73, '73', 'add repayments rescheduled to and processed column to holiday', 'SQL', 'V73__add_repayments_rescheduled_to_and_processed_column_to_holiday.sql', -503832337, 'root', '2015-06-03 15:26:56', 154, 1),
+	(74, 74, '74', 'alter m loan counter table add group', 'SQL', 'V74__alter_m_loan_counter_table_add_group.sql', -2117284805, 'root', '2015-06-03 15:26:56', 94, 1),
+	(75, 75, '75', 'add reschedule-repayments-on-holidays to configuration', 'SQL', 'V75__add_reschedule-repayments-on-holidays_to_configuration.sql', 1328301697, 'root', '2015-06-03 15:26:56', 1, 1),
+	(76, 76, '76', 'rename permission grouping', 'SQL', 'V76__rename_permission_grouping.sql', 782643717, 'root', '2015-06-03 15:26:56', 3, 1),
+	(77, 77, '77', 'alter m product loan changes', 'SQL', 'V77__alter_m_product_loan_changes.sql', -1168017986, 'root', '2015-06-03 15:26:56', 87, 1),
+	(78, 78, '78', 'breakdown portfolio grouping', 'SQL', 'V78__breakdown_portfolio_grouping.sql', -1151517023, 'root', '2015-06-03 15:26:56', 2, 1),
+	(79, 79, '79', 'schedule jobs tables', 'SQL', 'V79__schedule_jobs_tables.sql', -648184231, 'root', '2015-06-03 15:26:57', 81, 1),
+	(8, 8, '8', 'deposit-transaction-permissions-if-they-exist', 'SQL', 'V8__deposit-transaction-permissions-if-they-exist.sql', 1925296214, 'root', '2015-06-03 15:26:51', 1, 1),
+	(80, 80, '80', 'schedule jobs tables updates', 'SQL', 'V80__schedule_jobs_tables_updates.sql', 427508507, 'root', '2015-06-03 15:26:57', 74, 1),
+	(81, 81, '81', 'savings related changes', 'SQL', 'V81__savings_related_changes.sql', 1538092549, 'root', '2015-06-03 15:26:57', 247, 1),
+	(82, 82, '82', 'schedule jobs tables updates for running status', 'SQL', 'V82__schedule_jobs_tables_updates_for_running_status.sql', -1900600035, 'root', '2015-06-03 15:26:57', 213, 1),
+	(83, 83, '83', 'non-working-days-table', 'SQL', 'V83__non-working-days-table.sql', 7304707, 'root', '2015-06-03 15:26:57', 52, 1),
+	(84, 84, '84', 'undo savings transaction permission', 'SQL', 'V84__undo_savings_transaction_permission.sql', -1712636214, 'root', '2015-06-03 15:26:57', 1, 1),
+	(85, 85, '85', 'product mix related changes', 'SQL', 'V85__product_mix_related_changes.sql', -1558734721, 'root', '2015-06-03 15:26:57', 117, 1),
+	(86, 86, '86', 'update-working-days', 'SQL', 'V86__update-working-days.sql', 1751835641, 'root', '2015-06-03 15:26:57', 6, 1),
+	(87, 87, '87', 'add permission for scheduler', 'SQL', 'V87__add_permission_for_scheduler.sql', -575950289, 'root', '2015-06-03 15:26:57', 1, 1),
+	(88, 88, '88', 'added update constrain for scheduler jobs', 'SQL', 'V88__added_update_constrain_for_scheduler_jobs.sql', -897794717, 'root', '2015-06-03 15:26:57', 27, 1),
+	(89, 89, '89', 'added scheduler group', 'SQL', 'V89__added_scheduler_group.sql', -1570560491, 'root', '2015-06-03 15:26:57', 32, 1),
+	(9, 9, '9', 'add min max constraint column to loan loanproduct', 'SQL', 'V9__add_min_max_constraint_column_to_loan_loanproduct.sql', -709167892, 'root', '2015-06-03 15:26:51', 317, 1),
+	(90, 90, '90', 'client performance history reports', 'SQL', 'V90__client_performance_history_reports.sql', -358569421, 'root', '2015-06-03 15:26:58', 2, 1),
+	(91, 91, '91', 'apply annual fees permission', 'SQL', 'V91__apply_annual_fees_permission.sql', 1152030995, 'root', '2015-06-03 15:26:58', 2, 1),
+	(92, 92, '91.1', 'configuration settings for holiday and non workingday', 'SQL', 'V91_1__configuration_settings_for_holiday_and_non_workingday.sql', 2070643129, 'root', '2015-06-03 15:26:58', 2, 1),
+	(93, 93, '92', 'group center assign staff permission', 'SQL', 'V92__group_center_assign_staff_permission.sql', -1280675809, 'root', '2015-06-03 15:26:58', 2, 1),
+	(94, 94, '93', 'loan transaction external id', 'SQL', 'V93__loan_transaction_external_id.sql', 1766682107, 'root', '2015-06-03 15:26:58', 49, 1),
+	(95, 95, '94', 'added savings accont type', 'SQL', 'V94__added_savings_accont type.sql', -947513684, 'root', '2015-06-03 15:26:58', 68, 1),
+	(96, 96, '95', 'batch job postInterest', 'SQL', 'V95__batch_job_postInterest.sql', 2096051563, 'root', '2015-06-03 15:26:58', 1, 1),
+	(97, 97, '96', 'savings accounts transfers table', 'SQL', 'V96__savings_accounts_transfers_table.sql', 1113386790, 'root', '2015-06-03 15:26:58', 61, 1),
+	(98, 98, '97', 'add permission for adjust savings transaction', 'SQL', 'V97__add_permission_for_adjust_savings_transaction.sql', -2045732265, 'root', '2015-06-03 15:26:58', 2, 1),
+	(99, 99, '98', 'added currency roundof for multipleof', 'SQL', 'V98__added_currency_roundof_for_multipleof.sql', -107928515, 'root', '2015-06-03 15:26:58', 325, 1);
+/*!40000 ALTER TABLE `schema_version` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.sms_messages_outbound
+DROP TABLE IF EXISTS `sms_messages_outbound`;
+CREATE TABLE IF NOT EXISTS `sms_messages_outbound` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '100',
+  `mobile_no` varchar(50) NOT NULL,
+  `message` varchar(1000) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKGROUP000000001` (`group_id`),
+  KEY `FKCLIENT00000001` (`client_id`),
+  KEY `FKSTAFF000000001` (`staff_id`),
+  CONSTRAINT `FKCLIENT00000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKGROUP000000001` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSTAFF000000001` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.sms_messages_outbound: ~0 rows (approximately)
+/*!40000 ALTER TABLE `sms_messages_outbound` DISABLE KEYS */;
+/*!40000 ALTER TABLE `sms_messages_outbound` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.stretchy_parameter
+DROP TABLE IF EXISTS `stretchy_parameter`;
+CREATE TABLE IF NOT EXISTS `stretchy_parameter` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`),
+  KEY `fk_stretchy_parameter_001_idx` (`parent_id`),
+  CONSTRAINT `fk_stretchy_parameter_001` FOREIGN KEY (`parent_id`) REFERENCES `stretchy_parameter` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1010 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.stretchy_parameter: ~19 rows (approximately)
+/*!40000 ALTER TABLE `stretchy_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_parameter` (`id`, `parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `special`, `selectOne`, `selectAll`, `parameter_sql`, `parent_id`) VALUES
+	(1, 'startDateSelect', 'startDate', 'startDate', 'date', 'date', 'today', NULL, NULL, NULL, NULL, NULL),
+	(2, 'endDateSelect', 'endDate', 'endDate', 'date', 'date', 'today', NULL, NULL, NULL, NULL, NULL),
+	(3, 'obligDateTypeSelect', 'obligDateType', 'obligDateType', 'select', 'number', '0', NULL, NULL, NULL, 'select * from\r\n(select 1 as id, "Closed" as `name` union all\r\nselect 2, "Disbursal" ) x\r\norder by x.`id`', NULL),
+	(5, 'OfficeIdSelectOne', 'officeId', 'Office', 'select', 'number', '0', NULL, 'Y', NULL, 'select id, \r\nconcat(substring("........................................", 1, \r\n   \n\n((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat\n\n(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy', NULL),
+	(6, 'loanOfficerIdSelectAll', 'loanOfficerId', 'Loan Officer', 'select', 'number', '0', NULL, NULL, 'Y', '(select lo.id, lo.display_name as `Name` \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\njoin m_staff lo on lo.office_id = ounder.id\r\nwhere lo.is_loan_officer = true\r\nand o.id = ${officeId})\r\nunion all\r\n(select -10, \'-\')\r\norder by 2', 5),
+	(10, 'currencyIdSelectAll', 'currencyId', 'Currency', 'select', 'number', '0', NULL, NULL, 'Y', 'select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`', NULL),
+	(20, 'fundIdSelectAll', 'fundId', 'Fund', 'select', 'number', '0', NULL, NULL, 'Y', '(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2', NULL),
+	(25, 'loanProductIdSelectAll', 'loanProductId', 'Product', 'select', 'number', '0', NULL, NULL, 'Y', 'select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere (p.currency_code = \'${currencyId}\' or \'-1\'= \'${currencyId}\')\r\norder by 2', 10),
+	(26, 'loanPurposeIdSelectAll', 'loanPurposeId', 'Loan Purpose', 'select', 'number', '0', NULL, NULL, 'Y', 'select -10 as id, \'-\' as code_value\r\nunion all\r\nselect * from (select v.id, v.code_value\r\nfrom m_code c\r\njoin m_code_value v on v.code_id = c.id\r\nwhere c.code_name = "loanPurpose"\r\norder by v.order_position)  x', NULL),
+	(100, 'parTypeSelect', 'parType', 'parType', 'select', 'number', '0', NULL, NULL, NULL, 'select * from\r\n(select 1 as id, "Principal Only" as `name` union all\r\nselect 2, "Principal + Interest" union all\r\nselect 3, "Principal + Interest + Fees" union all\r\nselect 4, "Principal + Interest + Fees + Penalties") x\r\norder by x.`id`', NULL),
+	(1001, 'FullReportList', NULL, 'n/a', 'n/a', 'n/a', 'n/a', 'Y', NULL, NULL, 'select  r.id as report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\nrp.id as parameter_id, rp.report_parameter_name, p.parameter_name\n  from stretchy_report r\n  left join stretchy_report_parameter rp on rp.report_id = r.id \n  left join stretchy_parameter p on p.id = rp.parameter_id\n  where r.use_report is true\n  and exists\n  ( select \'f\'\n  from m_appuser_role ur \n  join m_role r on r.id = ur.role_id\n  join m_role_permission rp on rp.role_id = r.id\n  join m_permission p on p.id = rp.permission_id\n  where ur.appuser_id = ${currentUserId}\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat("READ_", r.report_name)) )\n  order by r.report_category, r.report_name, rp.id', NULL),
+	(1002, 'FullParameterList', NULL, 'n/a', 'n/a', 'n/a', 'n/a', 'Y', NULL, NULL, 'select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r sp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r from stretchy_parameter sp\r left join stretchy_parameter spp on spp.id = sp.parent_id\r where sp.special is null\r and exists \r   (select \'f\' \r  from stretchy_report sr\r   join stretchy_report_parameter srp on srp.report_id = sr.id\r   where sr.report_name in(${reportListing})\r   and srp.parameter_id = sp.id\r  )\r order by sp.id', NULL),
+	(1003, 'reportCategoryList', NULL, 'n/a', 'n/a', 'n/a', 'n/a', 'Y', NULL, NULL, 'select  r.id as report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\n  rp.id as parameter_id, rp.report_parameter_name, p.parameter_name\n  from stretchy_report r\n  left join stretchy_report_parameter rp on rp.report_id = r.id\n  left join stretchy_parameter p on p.id = rp.parameter_id\n  where r.report_category = \'${reportCategory}\'\n  and r.use_report is true\n  and exists\n  (select \'f\'\n  from m_appuser_role ur \n  join m_role r on r.id = ur.role_id\n  join m_role_permission rp on rp.role_id = r.id\n  join m_permission p on p.id = rp.permission_id\n  where ur.appuser_id = ${currentUserId}\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat("READ_", r.report_name)) )\n  order by r.report_category, r.report_name, rp.id', NULL),
+	(1004, 'selectAccount', 'accountNo', 'Enter Account No', 'text', 'string', 'n/a', NULL, NULL, NULL, NULL, NULL),
+	(1005, 'savingsProductIdSelectAll', 'savingsProductId', 'Product', 'select', 'number', '0', NULL, NULL, 'Y', 'select p.id, p.`name`\r\nfrom m_savings_product p\r\norder by 2', NULL),
+	(1006, 'transactionId', 'transactionId', 'transactionId', 'text', 'string', 'n/a', NULL, NULL, NULL, NULL, NULL),
+	(1007, 'selectCenterId', 'centerId', 'Enter Center Id', 'text', 'string', 'n/a', NULL, NULL, NULL, NULL, NULL),
+	(1008, 'SelectGLAccountNO', 'GLAccountNO', 'GLAccountNO', 'select', 'number', '0', NULL, NULL, NULL, 'select id aid,name aname\r\nfrom acc_gl_account', NULL),
+	(1009, 'asOnDate', 'asOn', 'As On', 'date', 'date', 'today', NULL, NULL, NULL, NULL, NULL);
+/*!40000 ALTER TABLE `stretchy_parameter` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.stretchy_report
+DROP TABLE IF EXISTS `stretchy_report`;
+CREATE TABLE IF NOT EXISTS `stretchy_report` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=165 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.stretchy_report: ~61 rows (approximately)
+/*!40000 ALTER TABLE `stretchy_report` DISABLE KEYS */;
+INSERT INTO `stretchy_report` (`id`, `report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES
+	(1, 'Client Listing', 'Table', NULL, 'Client', 'select\nconcat(repeat("..",\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\n c.account_no as "Client Account No.",\nc.display_name as "Name",\nr.enum_message_property as "Status",\nc.activation_date as "Activation", c.external_id as "External Id"\nfrom m_office o\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_client c on c.office_id = ounder.id\nleft join r_enum_value r on r.enum_name = \'status_enum\' and r.enum_id = c.status_enum\nwhere o.id = ${officeId}\norder by ounder.hierarchy, c.account_no', 'Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t \n\nhave that client browser/memory impact).', 1, 1),
+	(2, 'Client Loans Listing', 'Table', NULL, 'Client', 'select\nconcat(repeat("..",\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch", c.account_no as "Client Account No.",\nc.display_name as "Name",\nr.enum_message_property as "Client Status",\nlo.display_name as "Loan Officer", l.account_no as "Loan Account No.", l.external_id as "External Id", p.name as Loan, st.enum_message_property as "Status",\nf.`name` as Fund, purp.code_value as "Loan Purpose",\nifnull(cur.display_symbol, l.currency_code) as Currency,\nl.principal_amount, l.arrearstolerance_amount as "Arrears Tolerance Amount",\nl.number_of_repayments as "Expected No. Repayments",\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate",\nl.nominal_interest_rate_per_period as "Nominal Interest Rate Per Period",\nipf.enum_message_property as "Interest Rate Frequency",\nim.enum_message_property as "Interest Method",\nicp.enum_message_property as "Interest Calculated in Period",\nl.term_frequency as "Term Frequency",\ntf.enum_message_property as "Term Frequency Period",\nl.repay_every as "Repayment Frequency",\nrf.enum_message_property as "Repayment Frequency Period",\nam.enum_message_property as "Amortization",\nl.total_charges_due_at_disbursement_derived as "Total Charges Due At Disbursement",\ndate(l.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As "Expected Disbursal",\ndate(l.expected_firstrepaymenton_date) as "Expected First Repayment",\ndate(l.interest_calculated_from_date) as "Interest Calculated From" ,\ndate(l.disbursedon_date) as Disbursed,\ndate(l.expected_maturedon_date) "Expected Maturity",\ndate(l.maturedon_date) as "Matured On", date(l.closedon_date) as Closed,\ndate(l.rejectedon_date) as Rejected, date(l.rescheduledon_date) as Rescheduled,\ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) "Written Off"\nfrom m_office o\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_client c on c.office_id = ounder.id\nleft join r_enum_value r on r.enum_name = \'status_enum\'\n and r.enum_id = c.status_enum\nleft join m_loan l on l.client_id = c.id\nleft join m_staff lo on lo.id = l.loan_officer_id\nleft join m_product_loan p on p.id = l.product_id\nleft join m_fund f on f.id = l.fund_id\nleft join r_enum_value st on st.enum_name = "loan_status_id" and st.enum_id = l.loan_status_id\nleft join r_enum_value ipf on ipf.enum_name = "interest_period_frequency_enum"\n and ipf.enum_id = l.interest_period_frequency_enum\nleft join r_enum_value im on im.enum_name = "interest_method_enum"\n and im.enum_id = l.interest_method_enum\nleft join r_enum_value tf on tf.enum_name = "term_period_frequency_enum"\n and tf.enum_id = l.term_period_frequency_enum\nleft join r_enum_value icp on icp.enum_name = "interest_calculated_in_period_enum"\n and icp.enum_id = l.interest_calculated_in_period_enum\nleft join r_enum_value rf on rf.enum_name = "repayment_period_frequency_enum"\n and rf.enum_id = l.repayment_period_frequency_enum\nleft join r_enum_value am on am.enum_name = "amortization_method_enum"\n and am.enum_id = l.amortization_method_enum\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\nleft join m_currency cur on cur.code = l.currency_code\nwhere o.id = ${officeId}\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\norder by ounder.hierarchy, 2 , l.id', 'Individual Client Report\r\n\r\nPretty \n\nwide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).', 1, 1),
+	(5, 'Loans Awaiting Disbursal', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nc.account_no as "Client Account No", c.display_name as "Name", l.account_no as "Loan Account No.", pl.`name` as "Product", \r\nf.`name` as Fund, ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\nl.term_frequency as "Term Frequency",\n\n\r\ntf.enum_message_property as "Term Frequency Period",\r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate",\r\ndate(l.approvedon_date) "Approved",\r\ndatediff(l.expected_disbursedon_date, curdate()) as "Days to Disbursal",\r\ndate(l.expected_disbursedon_date) "Expected Disbursal",\r\npurp.code_value as "Loan Purpose",\r\n lo.display_name as "Loan Officer"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = "term_period_frequency_enum" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, datediff(l.expected_disbursedon_date, curdate()),  c.account_no', 'Individual Client Report', 1, 1),
+	(6, 'Loans Awaiting Disbursal Summary', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\npl.`name` as "Product", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`', 'Individual Client Report', 1, 1),
+	(7, 'Loans Awaiting Disbursal Summary by Month', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\npl.`name` as "Product", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as "Year", \r\nmonthname(l.expected_disbursedon_date) as "Month",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)', 'Individual Client Report', 1, 1),
+	(8, 'Loans Pending Approval', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nc.account_no as "Client Account No.", c.display_name as "Client Name", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  pl.`name` as "Product", \r\nl.account_no as "Loan Account No.", \r\nl.principal_amount as "Loan Amount", \r\nl.term_frequency as "Term Frequency",\n\n\r\ntf.enum_message_property as "Term Frequency Period",\r\nl.annual_nominal_interest_rate as " Annual \n\nNominal Interest Rate", \r\ndatediff(curdate(), l.submittedon_date) "Days Pending Approval", \r\npurp.code_value as "Loan Purpose",\r\nlo.display_name as "Loan Officer"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = "term_period_frequency_enum" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 100 /*Submitted and awaiting approval */\r\norder by ounder.hierarchy, l.submittedon_date,  l.account_no', 'Individual Client Report', 1, 1),
+	(11, 'Active Loans - Summary', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as "Office/Branch", x.currency as Currency,\r\n x.client_count as "No. of Clients", x.active_loan_count as "No. Active Loans", x. loans_in_arrears_count as "No. of Loans in Arrears",\r\nx.principal as "Total Loans Disbursed", x.principal_repaid as "Principal Repaid", x.principal_outstanding as "Principal Outstanding", x.principal_overdue as "Principal Overdue",\r\nx.interest as "Total Interest", x.interest_repaid as "Interest Repaid", x.interest_outstanding as "Interest Outstanding", x.interest_overdue as "Interest Overdue",\r\nx.fees as "Total Fees", x.fees_repaid as "Fees Repaid", x.fees_outstanding as "Fees Outstanding", x.fees_overdue as "Fees Overdue",\r\nx.penalties as "Total Penalties", x.penalties_repaid as "Penalties Repaid", x.penalties_outstanding as "Penalties Outstanding", x.penalties_overdue as "Penalties Overdue",\r\n\r\n  (case\r\n when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(if(laa.loan_id is not null,  l.id, null)  )) as loans_in_arrears_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(laa.principal_overdue_derived) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(laa.interest_overdue_derived) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(laa.fee_charges_overdue_derived) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(laa.penalty_charges_overdue_derived) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency', NULL, 1, 1),
+	(12, 'Active Loans - Details', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as "Loan Officer", \r\nc.display_name as "Client", l.account_no as "Loan Account No.", pl.`name` as "Product", \r\nf.`name` as Fund,  \r\nl.principal_amount as "Loan Amount", \r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed Date", \r\ndate(l.expected_maturedon_date) as "Expected Matured On",\r\n\r\nl.principal_repaid_derived as "Principal Repaid",\r\nl.principal_outstanding_derived as "Principal Outstanding",\r\nlaa.principal_overdue_derived as "Principal Overdue",\r\n\r\nl.interest_repaid_derived as "Interest Repaid",\r\nl.interest_outstanding_derived as "Interest Outstanding",\r\nlaa.interest_overdue_derived as "Interest Overdue",\r\n\r\nl.fee_charges_repaid_derived as "Fees Repaid",\r\nl.fee_charges_outstanding_derived  as "Fees Outstanding",\r\nlaa.fee_charges_overdue_derived as "Fees Overdue",\r\n\r\nl.penalty_charges_repaid_derived as "Penalties Repaid",\r\nl.penalty_charges_outstanding_derived as "Penalties Outstanding",\r\npenalty_charges_overdue_derived as "Penalties Overdue"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'Individual Client \n\nReport', 1, 1),
+	(13, 'Obligation Met Loans Details', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as "Client Account No.", c.display_name as "Client",\r\nl.account_no as "Loan Account No.", pl.`name` as "Product", \r\nf.`name` as Fund,  \r\nl.principal_amount as "Loan Amount", \r\nl.total_repayment_derived  as "Total Repaid", \r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed", \r\ndate(l.closedon_date) as "Closed",\r\n\r\nl.principal_repaid_derived as "Principal Repaid",\r\nl.interest_repaid_derived as "Interest Repaid",\r\nl.fee_charges_repaid_derived as "Fees Repaid",\r\nl.penalty_charges_repaid_derived as "Penalties Repaid",\r\nlo.display_name as "Loan Officer"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n  when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n  else 1 = 1\r\n  end)\r\nand l.loan_status_id = 600\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'Individual Client \n\nReport', 1, 1),
+	(14, 'Obligation Met Loans Summary', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as "No. of Clients",\r\ncount(distinct(l.id)) as "No. of Loans",\r\nsum(l.principal_amount) as "Total Loan Amount", \r\nsum(l.principal_repaid_derived) as "Total Principal Repaid",\r\nsum(l.interest_repaid_derived) as "Total Interest Repaid",\r\nsum(l.fee_charges_repaid_derived) as "Total Fees Repaid",\r\nsum(l.penalty_charges_repaid_derived) as "Total Penalties Repaid",\r\nsum(l.interest_waived_derived) as "Total Interest Waived",\r\nsum(l.fee_charges_waived_derived) as "Total Fees Waived",\r\nsum(l.penalty_charges_waived_derived) as "Total Penalties Waived"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n  when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n  else 1 = 1\r\n  end)\r\nand l.loan_status_id = 600\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code', 'Individual Client \n\nReport', 1, 1),
+	(15, 'Portfolio at Risk', 'Table', NULL, 'Loan', 'select x.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n  (case\r\n when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from \r\n(select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(l.principal_outstanding_derived) as "Principal Outstanding",\r\nsum(laa.principal_overdue_derived) as "Principal Overdue",\r\n\r\nsum(l.interest_outstanding_derived) as "Interest Outstanding",\r\nsum(laa.interest_overdue_derived) as "Interest Overdue",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as "Fees Outstanding",\r\nsum(laa.fee_charges_overdue_derived) as "Fees Overdue",\r\n\r\nsum(penalty_charges_outstanding_derived) as "Penalties Outstanding",\r\nsum(laa.penalty_charges_overdue_derived) as "Penalties Overdue"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code) x', 'Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)', 1, 1),
+	(16, 'Portfolio at Risk by Branch', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as "Office/Branch",\r\nx.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n (case\r\n when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from m_office mo\r\njoin \r\n(select  ounder.id as "branch", ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\n\r\nsum(l.principal_outstanding_derived) as "Principal Outstanding",\r\nsum(laa.principal_overdue_derived) as "Principal Overdue",\r\n\r\nsum(l.interest_outstanding_derived) as "Interest Outstanding",\r\nsum(laa.interest_overdue_derived) as "Interest Overdue",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as "Fees Outstanding",\r\nsum(laa.fee_charges_overdue_derived) as "Fees Overdue",\r\n\r\nsum(penalty_charges_outstanding_derived) as "Penalties Outstanding",\r\nsum(laa.penalty_charges_overdue_derived) as "Penalties Overdue"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency', 'Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)', 1, 1),
+	(20, 'Funds Disbursed Between Dates Summary', 'Table', NULL, 'Fund', 'select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, \r\nround(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)', NULL, 1, 1),
+	(21, 'Funds Disbursed Between Dates Summary by Office', 'Table', NULL, 'Fund', 'select \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\n \n\nifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\n\n\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c \n\non c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\n\n\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand \n\n(l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, \n\nl.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)', NULL, 1, 1),
+	(48, 'Balance Sheet', 'Pentaho', NULL, 'Accounting', NULL, 'Balance Sheet', 1, 1),
+	(49, 'Income Statement', 'Pentaho', NULL, 'Accounting', NULL, 'Profit and Loss Statement', 1, 1),
+	(50, 'Trial Balance', 'Pentaho', NULL, 'Accounting', NULL, 'Trial Balance Report', 1, 1),
+	(51, 'Written-Off Loans', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as "Client Account No.",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as "Loan Amount",\r\nifnull(lt.principal_portion_derived, 0) AS \'Written-Off Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Written-Off Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Written-Off Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Written-Off Penalties\',\r\nn.note AS \'Reason For Write-Off\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 6 /*write-off */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=601\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = "${currencyId}" or "-1" = "${currencyId}") \r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no', 'Individual Lending Report. Written Off Loans', 1, 1),
+	(52, 'Aging Detail', 'Table', NULL, 'Loan', '\r\nSELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as "Client Account No.",\r\n  mc.display_name AS "Client Name",\r\n   ml.account_no AS "Account Number",\r\n  ml.principal_amount AS "Loan Amount",\r\n ml.principal_disbursed_derived AS "Original Principal",\r\n ml.interest_charged_derived AS "Original Interest",\r\n ml.principal_repaid_derived AS "Principal Paid",\r\n ml.interest_repaid_derived AS "Interest Paid",\r\n laa.principal_overdue_derived AS "Principal Overdue",\r\n laa.interest_overdue_derived AS "Interest Overdue",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as "Days in Arrears",\r\n\r\n  IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n  IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n  IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS "Weeks In Arrears Band",\r\n\r\n   IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n    IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n   IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n   IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n   IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n         \'> 360\'))))) AS "Days in Arrears Band"\r\n\r\n FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n          AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n      INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n      INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id AND rev.enum_name = \'loan_status_id\'\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n  WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no\r\n', 'Loan arrears aging (Weeks)', 1, 1),
+	(53, 'Aging Summary (Arrears in Weeks)', 'Table', NULL, 'Loan', 'SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Weeks In Arrears (Up To)\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n /* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n  (SELECT \'On Schedule\' period_no,1 pid UNION\r\n   SELECT \'1\',2 UNION\r\n    SELECT \'2\',3 UNION\r\n    SELECT \'3\',4 UNION\r\n    SELECT \'4\',5 UNION\r\n    SELECT \'5\',6 UNION\r\n    SELECT \'6\',7 UNION\r\n    SELECT \'7\',8 UNION\r\n    SELECT \'8\',9 UNION\r\n    SELECT \'9\',10 UNION\r\n   SELECT \'10\',11 UNION\r\n    SELECT \'11\',12 UNION\r\n    SELECT \'12\',13 UNION\r\n    SELECT \'12+\',14) pers,\r\n  (SELECT distinctrow moc.code, moc.name\r\n    FROM m_office mo2\r\n     INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n       LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n     INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n  WHERE ml2.loan_status_id=300 /* active */\r\n AND mo2.id=${officeId}\r\nAND (ml2.currency_code = "${currencyId}" or "-1" = "${currencyId}")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n    z.currency, z.arrPeriod, \r\n COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n  SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n  /*derived table just used to get arrPeriod value (was much slower to\r\n  duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n  (SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<8, \'1\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<15, \'2\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<22, \'3\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<29, \'4\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<36, \'5\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<43, \'6\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<50, \'7\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<57, \'8\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<64, \'9\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<71, \'10\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<78, \'11\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<85, \'12\',\r\n         \'12+\'))))))))))))) AS arrPeriod\r\n\r\n  FROM /* get the individual loan details */\r\n    (SELECT ml.id AS loanId, ml.currency_code as currency,\r\n        ml.principal_disbursed_derived as principal, \r\n        ml.interest_charged_derived as interest, \r\n        ml.principal_repaid_derived as prinPaid, \r\n        ml.interest_repaid_derived intPaid,\r\n\r\n         laa.principal_overdue_derived as prinOverdue,\r\n         laa.interest_overdue_derived as intOverdue,\r\n\r\n         IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n        \r\n      FROM m_office mo\r\n      INNER JOIN m_office ounder ON ounder.hierarchy \r\n       LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n      INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n      INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n       LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n    WHERE ml.loan_status_id=300 /* active */\r\n        AND mo.id=${officeId}\r\n     AND (ml.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\n      GROUP BY ml.id) x\r\n ) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid', 'Loan amount in arrears by branch', 1, 1),
+	(54, 'Rescheduled Loans', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as "Client Account No.",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as "Loan Amount",\r\nifnull(lt.principal_portion_derived, 0) AS \'Rescheduled Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Rescheduled Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Rescheduled Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Rescheduled Penalties\',\r\nn.note AS \'Reason For Rescheduling\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 7 /*marked for rescheduling */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=602\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no', 'Individual Lending Report. Rescheduled Loans.  The ability to reschedule (or mark that you have rescheduled the loan elsewhere) is a legacy of the older Mifos product.  Needed for migration.', 1, 1),
+	(55, 'Active Loans Passed Final Maturity', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as "Loan Officer", \r\nc.display_name as "Client", l.account_no as "Loan Account No.", pl.`name` as "Product", \r\nf.`name` as Fund,  \r\nl.principal_amount as "Loan Amount", \r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed Date", \r\ndate(l.expected_maturedon_date) as "Expected Matured On",\r\n\r\nl.principal_repaid_derived as "Principal Repaid",\r\nl.principal_outstanding_derived as "Principal Outstanding",\r\nlaa.principal_overdue_derived as "Principal Overdue",\r\n\r\nl.interest_repaid_derived as "Interest Repaid",\r\nl.interest_outstanding_derived as "Interest Outstanding",\r\nlaa.interest_overdue_derived as "Interest Overdue",\r\n\r\nl.fee_charges_repaid_derived as "Fees Repaid",\r\nl.fee_charges_outstanding_derived  as "Fees Outstanding",\r\nlaa.fee_charges_overdue_derived as "Fees Overdue",\r\n\r\nl.penalty_charges_repaid_derived as "Penalties Repaid",\r\nl.penalty_charges_outstanding_derived as "Penalties Outstanding",\r\nlaa.penalty_charges_overdue_derived as "Penalties Overdue"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'Individual Client \n\nReport', 1, 1),
+	(56, 'Active Loans Passed Final Maturity Summary', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as "Office/Branch", x.currency as Currency,\r\n x.client_count as "No. of Clients", x.active_loan_count as "No. Active Loans", x. arrears_loan_count as "No. of Loans in Arrears",\r\nx.principal as "Total Loans Disbursed", x.principal_repaid as "Principal Repaid", x.principal_outstanding as "Principal Outstanding", x.principal_overdue as "Principal Overdue",\r\nx.interest as "Total Interest", x.interest_repaid as "Interest Repaid", x.interest_outstanding as "Interest Outstanding", x.interest_overdue as "Interest Overdue",\r\nx.fees as "Total Fees", x.fees_repaid as "Fees Repaid", x.fees_outstanding as "Fees Outstanding", x.fees_overdue as "Fees Overdue",\r\nx.penalties as "Total Penalties", x.penalties_repaid as "Penalties Repaid", x.penalties_outstanding as "Penalties Outstanding", x.penalties_overdue as "Penalties Overdue",\r\n\r\n  (case\r\n when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency', NULL, 1, 1),
+	(57, 'Active Loans in last installment', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(lastInstallment.`hierarchy`) - LENGTH(REPLACE(lastInstallment.`hierarchy`, \'.\', \'\')) - 1))), lastInstallment.branch) as "Office/Branch",\r\nlastInstallment.Currency,\r\nlastInstallment.`Loan Officer`, \r\nlastInstallment.`Client Account No`, lastInstallment.`Client`, \r\nlastInstallment.`Loan Account No`, lastInstallment.`Product`, \r\nlastInstallment.`Fund`,  lastInstallment.`Loan Amount`, \r\nlastInstallment.`Annual Nominal Interest Rate`, \r\nlastInstallment.`Disbursed`, lastInstallment.`Expected Matured On` ,\r\n\r\nl.principal_repaid_derived as "Principal Repaid",\r\nl.principal_outstanding_derived as "Principal Outstanding",\r\nlaa.principal_overdue_derived as "Principal Overdue",\r\n\r\nl.interest_repaid_derived as "Interest Repaid",\r\nl.interest_outstanding_derived as "Interest Outstanding",\r\nlaa.interest_overdue_derived as "Interest Overdue",\r\n\r\nl.fee_charges_repaid_derived as "Fees Repaid",\r\nl.fee_charges_outstanding_derived  as "Fees Outstanding",\r\nlaa.fee_charges_overdue_derived as "Fees Overdue",\r\n\r\nl.penalty_charges_repaid_derived as "Penalties Repaid",\r\nl.penalty_charges_outstanding_derived as "Penalties Outstanding",\r\nlaa.penalty_charges_overdue_derived as "Penalties Overdue"\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as "Loan Officer", c.account_no as "Client Account No",\r\nc.display_name as "Client", l.account_no as "Loan Account No", pl.`name` as "Product", \r\nf.`name` as Fund,  l.principal_amount as "Loan Amount", \r\nl.annual_nominal_interest_rate as "Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed", date(l.expected_maturedon_date) as "Expected Matured On"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\norder by lastInstallment.hierarchy, lastInstallment.Currency, lastInstallment.`Client Account No`, lastInstallment.`Loan Account No`', 'Individual Client \n\nReport', 1, 1),
+	(58, 'Active Loans in last installment Summary', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as "Office/Branch", x.currency as Currency,\r\n x.client_count as "No. of Clients", x.active_loan_count as "No. Active Loans", x. arrears_loan_count as "No. of Loans in Arrears",\r\nx.principal as "Total Loans Disbursed", x.principal_repaid as "Principal Repaid", x.principal_outstanding as "Principal Outstanding", x.principal_overdue as "Principal Overdue",\r\nx.interest as "Total Interest", x.interest_repaid as "Interest Repaid", x.interest_outstanding as "Interest Outstanding", x.interest_overdue as "Interest Overdue",\r\nx.fees as "Total Fees", x.fees_repaid as "Fees Repaid", x.fees_outstanding as "Fees Outstanding", x.fees_overdue as "Fees Overdue",\r\nx.penalties as "Total Penalties", x.penalties_repaid as "Penalties Repaid", x.penalties_outstanding as "Penalties Outstanding", x.penalties_overdue as "Penalties Overdue",\r\n\r\n  (case\r\n when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from m_office mo\r\njoin \r\n(select lastInstallment.branchId as branchId,\r\nlastInstallment.Currency,\r\ncount(distinct(lastInstallment.clientId)) as client_count, \r\ncount(distinct(lastInstallment.loanId)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id as branchId, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as "Loan Officer", c.id as clientId, c.account_no as "Client Account No",\r\nc.display_name as "Client", l.account_no as "Loan Account No", pl.`name` as "Product", \r\nf.`name` as Fund,  l.principal_amount as "Loan Amount", \r\nl.annual_nominal_interest_rate as "Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed", date(l.expected_maturedon_date) as "Expected Matured On"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\ngroup by lastInstallment.branchId) x on x.branchId = mo.id\r\norder by mo.hierarchy, x.Currency', 'Individual Client \n\nReport', 1, 1),
+	(59, 'Active Loans by Disbursal Period', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as "Client Account No", c.display_name as "Client", l.account_no as "Loan Account No", pl.`name` as "Product", \r\nf.`name` as Fund,  \r\nl.principal_amount as "Loan Principal Amount", \r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed Date", \r\n\r\nl.total_expected_repayment_derived as "Total Loan (P+I+F+Pen)",\r\nl.total_repayment_derived as "Total Repaid (P+I+F+Pen)",\r\nlo.display_name as "Loan Officer"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'Individual Client \n\nReport', 1, 1),
+	(61, 'Aging Summary (Arrears in Months)', 'Table', NULL, 'Loan', 'SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Days In Arrears\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n /* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n  (SELECT \'On Schedule\' period_no,1 pid UNION\r\n   SELECT \'0 - 30\',2 UNION\r\n   SELECT \'30 - 60\',3 UNION\r\n    SELECT \'60 - 90\',4 UNION\r\n    SELECT \'90 - 180\',5 UNION\r\n   SELECT \'180 - 360\',6 UNION\r\n    SELECT \'> 360\',7 ) pers,\r\n  (SELECT distinctrow moc.code, moc.name\r\n    FROM m_office mo2\r\n     INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n       LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n     INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n  WHERE ml2.loan_status_id=300 /* active */\r\n AND mo2.id=${officeId}\r\nAND (ml2.currency_code = "${currencyId}" or "-1" = "${currencyId}")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n    z.currency, z.arrPeriod, \r\n COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n  SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n  /*derived table just used to get arrPeriod value (was much slower to\r\n  duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n  (SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<31, \'0 - 30\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<61, \'30 - 60\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<91, \'60 - 90\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<181, \'90 - 180\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<361, \'180 - 360\', \r\n        \'> 360\')))))) AS arrPeriod\r\n\r\n FROM /* get the individual loan details */\r\n    (SELECT ml.id AS loanId, ml.currency_code as currency,\r\n        ml.principal_disbursed_derived as principal, \r\n        ml.interest_charged_derived as interest, \r\n        ml.principal_repaid_derived as prinPaid, \r\n        ml.interest_repaid_derived intPaid,\r\n\r\n         laa.principal_overdue_derived as prinOverdue,\r\n         laa.interest_overdue_derived as intOverdue,\r\n\r\n         IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n        \r\n      FROM m_office mo\r\n      INNER JOIN m_office ounder ON ounder.hierarchy \r\n       LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n      INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n      INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n       LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n    WHERE ml.loan_status_id=300 /* active */\r\n        AND mo.id=${officeId}\r\n     AND (ml.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\n      GROUP BY ml.id) x\r\n ) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid', 'Loan amount in arrears by branch', 1, 1),
+	(91, 'Loan Account Schedule', 'Pentaho', NULL, 'Loan', NULL, NULL, 1, 0),
+	(92, 'Branch Expected Cash Flow', 'Pentaho', NULL, 'Loan', NULL, NULL, 1, 1),
+	(93, 'Expected Payments By Date - Basic', 'Table', NULL, 'Loan', 'SELECT \r\n      ounder.name \'Office\', \r\n      IFNULL(ms.display_name,\'-\') \'Loan Officer\',\r\n    mc.account_no \'Client Account Number\',\r\n    mc.display_name \'Name\',\r\n   mp.name \'Product\',\r\n    ml.account_no \'Loan Account Number\',\r\n    mr.duedate \'Due Date\',\r\n    mr.installment \'Installment\',\r\n   cu.display_symbol \'Currency\',\r\n   mr.principal_amount- IFNULL(mr.principal_completed_derived,0) \'Principal Due\',\r\n    mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0) \'Interest Due\', \r\n   IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0) \'Fees Due\', \r\n    IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0) \'Penalty Due\',\r\n      (mr.principal_amount- IFNULL(mr.principal_completed_derived,0)) +\r\n       (mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0)) + \r\n       (IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0)) + \r\n       (IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0)) \'Total Due\', \r\n     mlaa.total_overdue_derived \'Total Overdue\'\r\n                    \r\n FROM m_office mo\r\n  JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\n  \r\n  AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n \r\n  LEFT JOIN m_client mc ON mc.office_id=ounder.id\r\n  LEFT JOIN m_loan ml ON ml.client_id=mc.id AND ml.loan_status_id=300\r\n  LEFT JOIN m_loan_arrears_aging mlaa ON mlaa.loan_id=ml.id\r\n  LEFT JOIN m_loan_repayment_schedule mr ON mr.loan_id=ml.id AND mr.completed_derived=0\r\n  LEFT JOIN m_product_loan mp ON mp.id=ml.product_id\r\n  LEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\n  LEFT JOIN m_currency cu ON cu.code=ml.currency_code\r\n WHERE mo.id=${officeId}\r\n AND (IFNULL(ml.loan_officer_id, -10) = "${loanOfficerId}" OR "-1" = "${loanOfficerId}")\r\n AND mr.duedate BETWEEN \'${startDate}\' AND \'${endDate}\'\r\n ORDER BY ounder.id,mr.duedate,ml.account_no', 'Test', 1, 1),
+	(94, 'Expected Payments By Date - Formatted', 'Pentaho', NULL, 'Loan', NULL, NULL, 1, 1),
+	(96, 'GroupSummaryCounts', 'Table', NULL, NULL, '\n/*\nActive Client is a client linked to the \'group\' via m_group_client\nand with an active \'status_enum\'.)\nActive Borrowers - Borrower may be a client or a \'group\'\n*/\nselect x.*\nfrom m_office o,\nm_group g,\n\n(select a.activeClients,\n(b.activeClientLoans + c.activeGroupLoans) as activeLoans,\nb.activeClientLoans, c.activeGroupLoans,\n(b.activeClientBorrowers + c.activeGroupBorrowers) as activeBorrowers,\nb.activeClientBorrowers, c.activeGroupBorrowers,\n(b.overdueClientLoans +  c.overdueGroupLoans) as overdueLoans,\nb.overdueClientLoans, c.overdueGroupLoans\nfrom\n(select count(*) as activeClients\nfrom m_group topgroup\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_group_client gc on gc.group_id = g.id\njoin m_client c on c.id = gc.client_id\nwhere topgroup.id = ${groupId}\nand c.status_enum = 300) a,\n\n(select count(*) as activeClientLoans,\ncount(distinct(l.client_id)) as activeClientBorrowers,\nifnull(sum(if(laa.loan_id is not null, 1, 0)), 0) as overdueClientLoans\nfrom m_group topgroup\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_loan l on l.group_id = g.id and l.client_id is not null\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nwhere topgroup.id = ${groupId}\nand l.loan_status_id = 300) b,\n\n(select count(*) as activeGroupLoans,\ncount(distinct(l.group_id)) as activeGroupBorrowers,\nifnull(sum(if(laa.loan_id is not null, 1, 0)), 0) as overdueGroupLoans\nfrom m_group topgroup\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_loan l on l.group_id = g.id and l.client_id is null\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nwhere topgroup.id = ${groupId}\nand l.loan_status_id = 300) c\n) x\n\nwhere g.id = ${groupId}\nand o.id = g.office_id\nand o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\n', 'Utility query for getting group summary count details for a group_id', 1, 0),
+	(97, 'GroupSummaryAmounts', 'Table', NULL, NULL, '\nselect ifnull(cur.display_symbol, l.currency_code) as currency,\nifnull(sum(l.principal_disbursed_derived),0) as totalDisbursedAmount,\nifnull(sum(l.principal_outstanding_derived),0) as totalLoanOutstandingAmount,\ncount(laa.loan_id) as overdueLoans, ifnull(sum(laa.total_overdue_derived), 0) as totalLoanOverdueAmount\nfrom m_group topgroup\njoin m_office o on o.id = topgroup.office_id and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_loan l on l.group_id = g.id\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nleft join m_currency cur on cur.code = l.currency_code\nwhere topgroup.id = ${groupId}\nand l.disbursedon_date is not null\ngroup by l.currency_code\n', 'Utility query for getting group summary currency amount details for a group_id', 1, 0),
+	(106, 'TxnRunningBalances', 'Table', NULL, 'Transaction', '\nselect date(\'${startDate}\') as \'Transaction Date\', \'Opening Balance\' as `Transaction Type`, null as Office,\n  null as \'Loan Officer\', null as `Loan Account No`, null as `Loan Product`, null as `Currency`,\n  null as `Client Account No`, null as Client,\n  null as Amount, null as Principal, null as Interest,\n@totalOutstandingPrincipal :=\nifnull(round(sum(\n  if (txn.transaction_type_enum = 1 /* disbursement */,\n   ifnull(txn.amount,0.00),\n    ifnull(txn.principal_portion_derived,0.00) * -1))\n     ,2),0.00)  as \'Outstanding Principal\',\n\n@totalInterestIncome :=\nifnull(round(sum(\n  if (txn.transaction_type_enum in (2,5,8) /* repayment, repayment at disbursal, recovery repayment */,\n   ifnull(txn.interest_portion_derived,0.00),\n    0))\n     ,2),0.00) as \'Interest Income\',\n\n@totalWriteOff :=\nifnull(round(sum(\n if (txn.transaction_type_enum = 6 /* write-off */,\n    ifnull(txn.principal_portion_derived,0.00),\n   0))\n     ,2),0.00) as \'Principal Write Off\'\nfrom m_office o\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\n                          and ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_client c on c.office_id = ounder.id\njoin m_loan l on l.client_id = c.id\njoin m_product_loan lp on lp.id = l.product_id\njoin m_loan_transaction txn on txn.loan_id = l.id\nleft join m_currency cur on cur.code = l.currency_code\nwhere txn.is_reversed = false\nand txn.transaction_type_enum not in (10,11)\nand o.id = ${officeId}\nand txn.transaction_date < date(\'${startDate}\')\n\nunion all\n\nselect x.`Transaction Date`, x.`Transaction Type`, x.Office, x.`Loan Officer`, x.`Loan Account No`, x.`Loan Product`, x.`Currency`,\n x.`Client Account No`, x.Client, x.Amount, x.Principal, x.Interest,\ncast(round(\n  if (x.transaction_type_enum = 1 /* disbursement */,\n   @totalOutstandingPrincipal := @totalOutstandingPrincipal + x.`Amount`,\n    @totalOutstandingPrincipal := @totalOutstandingPrincipal - x.`Principal`)\n     ,2) as decimal(19,2)) as \'Outstanding Principal\',\ncast(round(\n  if (x.transaction_type_enum in (2,5,8) /* repayment, repayment at disbursal, recovery repayment */,\n   @totalInterestIncome := @totalInterestIncome + x.`Interest`,\n    @totalInterestIncome)\n     ,2) as decimal(19,2)) as \'Interest Income\',\ncast(round(\n  if (x.transaction_type_enum = 6 /* write-off */,\n    @totalWriteOff := @totalWriteOff + x.`Principal`,\n   @totalWriteOff)\n     ,2) as decimal(19,2)) as \'Principal Write Off\'\nfrom\n(select txn.transaction_type_enum, txn.id as txn_id, txn.transaction_date as \'Transaction Date\',\ncast(\n ifnull(re.enum_message_property, concat(\'Unknown Transaction Type Value: \' , txn.transaction_type_enum))\n  as char) as \'Transaction Type\',\nounder.`name` as Office, lo.display_name as \'Loan Officer\',\nl.account_no  as \'Loan Account No\', lp.`name` as \'Loan Product\',\nifnull(cur.display_symbol, l.currency_code) as Currency,\nc.account_no as \'Client Account No\', c.display_name as \'Client\',\nifnull(txn.amount,0.00) as Amount,\nifnull(txn.principal_portion_derived,0.00) as Principal,\nifnull(txn.interest_portion_derived,0.00) as Interest\nfrom m_office o\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\n                          and ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_client c on c.office_id = ounder.id\njoin m_loan l on l.client_id = c.id\nleft join m_staff lo on lo.id = l.loan_officer_id\njoin m_product_loan lp on lp.id = l.product_id\njoin m_loan_transaction txn on txn.loan_id = l.id\nleft join m_currency cur on cur.code = l.currency_code\nleft join r_enum_value re on re.enum_name = \'transaction_type_enum\'\n            and re.enum_id = txn.transaction_type_enum\nwhere txn.is_reversed = false\nand txn.transaction_type_enum not in (10,11)\nand (ifnull(l.loan_officer_id, -10) = \'${loanOfficerId}\' or \'-1\' = \'${loanOfficerId}\')\nand o.id = ${officeId}\nand txn.transaction_date >= date(\'${startDate}\')\nand txn.transaction_date <= date(\'${endDate}\')\norder by txn.transaction_date, txn.id) x\n', 'Running Balance Txn report for Individual Lending.\nSuitable for small MFI\'s.  Larger could use it using the branch or other parameters.\nBasically, suck it and see if its quick enough for you out-of-te box or whether it needs performance work in your situation.\n', 0, 0),
+	(107, 'FieldAgentStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_staff fa\njoin m_office o on o.id = fa.office_id\n     and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group pgm on pgm.staff_id = fa.id\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id\njoin m_client c on c.id = l.client_id\nwhere fa.id = ${staffId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Field Agent Statistics', 0, 0),
+	(108, 'FieldAgentPrograms', 'Table', NULL, 'Quipo', '\nselect pgm.id, pgm.display_name as `name`, sts.enum_message_property as status\n from m_group pgm\n join m_office o on o.id = pgm.office_id\n      and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\n left join r_enum_value sts on sts.enum_name = \'status_enum\' and sts.enum_id = pgm.status_enum\n where pgm.staff_id = ${staffId}\n', 'List of Field Agent Programs', 0, 0),
+	(109, 'ProgramDetails', 'Table', NULL, 'Quipo', '\n select l.id as loanId, l.account_no as loanAccountNo, c.id as clientId, c.account_no as clientAccountNo,\n pgm.display_name as programName,\n\n(select count(*)\nfrom m_loan cy\nwhere cy.group_id = pgm.id and cy.client_id =c.id\nand cy.disbursedon_date <= l.disbursedon_date) as loanCycleNo,\n\nc.display_name as clientDisplayName,\n ifnull(cur.display_symbol, l.currency_code) as Currency,\nifnull(l.principal_repaid_derived,0.0) as loanRepaidAmount,\nifnull(l.principal_outstanding_derived, 0.0) as loanOutstandingAmount,\nifnull(lpa.principal_in_advance_derived,0.0) as LoanPaidInAdvance,\n\nifnull(laa.principal_overdue_derived, 0.0) as loanInArrearsAmount,\nif(ifnull(laa.principal_overdue_derived, 0.00) > 0, \'Yes\', \'No\') as inDefault,\n\nif(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)  as portfolioAtRisk\n\n from m_group pgm\n join m_office o on o.id = pgm.office_id\n     and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\n join m_loan l on l.group_id = pgm.id and l.client_id is not null\n left join m_currency cur on cur.code = l.currency_code\n join m_client c on c.id = l.client_id\n left join m_loan_arrears_aging laa on laa.loan_id = l.id\n left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id\n where pgm.id = ${programId}\n and l.loan_status_id = 300\norder by c.display_name, l.account_no\n\n', 'List of Loans in a Program', 0, 0),
+	(110, 'ChildrenStaffList', 'Table', NULL, 'Quipo', '\n select s.id, s.display_name,\ns.firstname, s.lastname, s.organisational_role_enum,\ns.organisational_role_parent_staff_id,\nsp.display_name as `organisational_role_parent_staff_display_name`\nfrom m_staff s\njoin m_staff sp on s.organisational_role_parent_staff_id = sp.id\nwhere s.organisational_role_parent_staff_id = ${staffId}\n', 'Get Next Level Down Staff', 0, 0),
+	(111, 'CoordinatorStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_staff coord\njoin m_staff fa on fa.organisational_role_parent_staff_id = coord.id\njoin m_office o on o.id = fa.office_id\n      and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group pgm on pgm.staff_id = fa.id\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id\njoin m_client c on c.id = l.client_id\nwhere coord.id = ${staffId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Coordinator Statistics', 0, 0),
+	(112, 'BranchManagerStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_staff bm\njoin m_staff coord on coord.organisational_role_parent_staff_id = bm.id\njoin m_staff fa on fa.organisational_role_parent_staff_id = coord.id\njoin m_office o on o.id = fa.office_id\n      and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group pgm on pgm.staff_id = fa.id\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id\njoin m_client c on c.id = l.client_id\nwhere bm.id = ${staffId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Branch Manager Statistics', 0, 0),
+	(113, 'ProgramDirectorStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_staff pd\njoin m_staff bm on bm.organisational_role_parent_staff_id = pd.id\njoin m_staff coord on coord.organisational_role_parent_staff_id = bm.id\njoin m_staff fa on fa.organisational_role_parent_staff_id = coord.id\njoin m_office o on o.id = fa.office_id\n     and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group pgm on pgm.staff_id = fa.id\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id\njoin m_client c on c.id = l.client_id\nwhere pd.id = ${staffId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Program DirectorStatistics', 0, 0),
+	(114, 'ProgramStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_group pgm\njoin m_office o on o.id = pgm.office_id\n     and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id\njoin m_client c on c.id = l.client_id\nwhere pgm.id = ${programId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Program Statistics', 0, 0),
+	(115, 'ClientSummary ', 'Table', NULL, NULL, 'SELECT x.* FROM m_client c, m_office o, \n(\n       SELECT a.loanCycle, a.activeLoans, b.lastLoanAmount, d.activeSavings, d.totalSavings FROM \n  (SELECT IFNULL(MAX(l.loan_counter),0) AS loanCycle, COUNT(l.id) AS activeLoans FROM m_loan l WHERE l.loan_status_id=300 AND l.client_id=${clientId}) a, \n  (SELECT count(l.id), IFNULL(l.principal_amount,0) AS \'lastLoanAmount\' FROM m_loan l WHERE l.client_id=${clientId} AND l.disbursedon_date = (SELECT IFNULL(MAX(disbursedon_date),NOW()) FROM m_loan where client_id=${clientId} and loan_status_id=300)) b, \n (SELECT COUNT(s.id) AS \'activeSavings\', IFNULL(SUM(s.account_balance_derived),0) AS \'totalSavings\' FROM m_savings_account s WHERE s.status_enum=300 AND s.client_id=${clientId}) d\n) x\nWHERE c.id=${clientId} AND o.id = c.office_id AND o.hierarchy LIKE CONCAT(\'${currentUserHierarchy}\', \'%\')', 'Utility query for getting the client summary details', 1, 0),
+	(116, 'LoanCyclePerProduct', 'Table', NULL, NULL, 'SELECT lp.name AS \'productName\', MAX(l.loan_product_counter) AS \'loanProductCycle\' FROM m_loan l JOIN m_product_loan lp ON l.product_id=lp.id WHERE lp.include_in_borrower_cycle=1 AND l.loan_product_counter IS NOT NULL AND l.client_id=${clientId} GROUP BY l.product_id', 'Utility query for getting the client loan cycle details', 1, 0),
+	(117, 'GroupSavingSummary', 'Table', NULL, NULL, 'select ifnull(cur.display_symbol, sa.currency_code) as currency,\ncount(sa.id) as totalSavingAccounts, ifnull(sum(sa.account_balance_derived),0) as totalSavings\nfrom m_group topgroup\njoin m_office o on o.id = topgroup.office_id and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_savings_account sa on sa.group_id = g.id\nleft join m_currency cur on cur.code = sa.currency_code\nwhere topgroup.id = ${groupId}\nand sa.activatedon_date is not null\ngroup by sa.currency_code', 'Utility query for getting group or center saving summary details for a group_id', 1, 0),
+	(118, 'Savings Transactions', 'Pentaho', NULL, 'Savings', NULL, NULL, 0, 1),
+	(119, 'Client Savings Summary', 'Pentaho', NULL, 'Savings', NULL, NULL, 0, 1),
+	(120, 'Active Loans - Details(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(121, 'Active Loans - Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(122, 'Active Loans by Disbursal Period(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(123, 'Active Loans in last installment Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(124, 'Active Loans in last installment(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(125, 'Active Loans Passed Final Maturity Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(126, 'Active Loans Passed Final Maturity(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(127, 'Aging Detail(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(128, 'Aging Summary (Arrears in Months)(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(129, 'Aging Summary (Arrears in Weeks)(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(130, 'Client Listing(Pentaho)', 'Pentaho', NULL, 'Client', '(NULL)', '(NULL)', 1, 1),
+	(131, 'Client Loans Listing(Pentaho)', 'Pentaho', NULL, 'Client', '(NULL)', '(NULL)', 1, 1),
+	(132, 'Expected Payments By Date - Basic(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(133, 'Funds Disbursed Between Dates Summary by Office(Pentaho)', 'Pentaho', NULL, 'Fund', '(NULL)', '(NULL)', 1, 1),
+	(134, 'Funds Disbursed Between Dates Summary(Pentaho)', 'Pentaho', NULL, 'Fund', '(NULL)', '(NULL)', 1, 1),
+	(135, 'Loans Awaiting Disbursal Summary by Month(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(136, 'Loans Awaiting Disbursal Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(137, 'Loans Awaiting Disbursal(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(138, 'Loans Pending Approval(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(139, 'Obligation Met Loans Details(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(140, 'Obligation Met Loans Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(141, 'Portfolio at Risk by Branch(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(142, 'Portfolio at Risk(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(143, 'Rescheduled Loans(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(144, 'TxnRunningBalances(Pentaho)', 'Pentaho', NULL, 'Transaction', '(NULL)', '(NULL)', 1, 1),
+	(145, 'Written-Off Loans(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(146, 'Client Saving Transactions', 'Pentaho', NULL, 'Savings', NULL, NULL, 0, 0),
+	(147, 'Client Loan Account Schedule', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 0),
+	(148, 'GroupNamesByStaff', 'Table', '', '', 'Select gr.id as id, gr.display_name as name from m_group gr where gr.level_id=1 and gr.staff_id = ${staffId}', '', 1, 0),
+	(149, 'ClientTrendsByDay', 'Table', '', 'Client', 'SELECT   COUNT(cl.id) AS count, \n   cl.activation_date AS days\nFROM m_office of \n LEFT JOIN m_client cl on of.id = cl.office_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (cl.activation_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 DAY) AND DATE(NOW()- INTERVAL 1 DAY))\nGROUP BY days', 'Retrieves the number of clients joined in last 12 days', 1, 0),
+	(150, 'ClientTrendsByWeek', 'Table', '', 'Client', 'SELECT  COUNT(cl.id) AS count, \n   WEEK(cl.activation_date) AS Weeks\nFROM m_office of \n  LEFT JOIN m_client cl on of.id = cl.office_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (cl.activation_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 WEEK) AND DATE(NOW()))\nGROUP BY Weeks', '', 1, 0),
+	(151, 'ClientTrendsByMonth', 'Table', '', 'Client', 'SELECT   COUNT(cl.id) AS count, \n   MONTHNAME(cl.activation_date) AS Months\nFROM m_office of \n  LEFT JOIN m_client cl on of.id = cl.office_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (cl.activation_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 MONTH) AND DATE(NOW()))\nGROUP BY Months', '', 1, 0),
+	(152, 'LoanTrendsByDay', 'Table', '', 'Loan', 'SELECT   COUNT(ln.id) AS lcount, \n    ln.disbursedon_date AS days\nFROM m_office of \n  LEFT JOIN m_client cl on of.id = cl.office_id\n LEFT JOIN m_loan ln on cl.id = ln.client_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (ln.disbursedon_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 DAY) AND DATE(NOW()- INTERVAL 1 DAY))\nGROUP BY days', 'Retrieves Number of loans disbursed for last 12 days', 1, 0),
+	(153, 'LoanTrendsByWeek', 'Table', '', 'Loan', 'SELECT  COUNT(ln.id) AS lcount, \n    WEEK(ln.disbursedon_date) AS Weeks\nFROM m_office of \n LEFT JOIN m_client cl on of.id = cl.office_id\n LEFT JOIN m_loan ln on cl.id = ln.client_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (ln.disbursedon_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 WEEK) AND DATE(NOW()))\nGROUP BY Weeks', '', 1, 0),
+	(154, 'LoanTrendsByMonth', 'Table', '', 'Loan', 'SELECT   COUNT(ln.id) AS lcount, \n    MONTHNAME(ln.disbursedon_date) AS Months\nFROM m_office of \n LEFT JOIN m_client cl on of.id = cl.office_id\n LEFT JOIN m_loan ln on cl.id = ln.client_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (ln.disbursedon_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 MONTH) AND DATE(NOW()))\nGROUP BY Months', '', 1, 0),
+	(155, 'Demand_Vs_Collection', 'Table', '', 'Loan', 'select amount.AmountDue-amount.AmountPaid as AmountDue, amount.AmountPaid as AmountPaid from\n(SELECT \n(IFNULL(SUM(ls.principal_amount),0) - IFNULL(SUM(ls.principal_writtenoff_derived),0)\n + IFNULL(SUM(ls.interest_amount),0) - IFNULL(SUM(ls.interest_writtenoff_derived),0) \n - IFNULL(SUM(ls.interest_waived_derived),0)\n + IFNULL(SUM(ls.fee_charges_amount),0) - IFNULL(SUM(ls.fee_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.fee_charges_waived_derived),0)\n + IFNULL(SUM(ls.penalty_charges_amount),0) - IFNULL(SUM(ls.penalty_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.penalty_charges_waived_derived),0)\n) AS AmountDue, \n\n(IFNULL(SUM(ls.principal_completed_derived),0) - IFNULL(SUM(ls.principal_writtenoff_derived),0) + IFNULL(SUM(ls.interest_completed_derived),0) - IFNULL(SUM(ls.interest_writtenoff_derived),0) \n - IFNULL(SUM(ls.interest_waived_derived),0)\n + IFNULL(SUM(ls.fee_charges_completed_derived),0) - IFNULL(SUM(ls.fee_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.fee_charges_waived_derived),0)\n + IFNULL(SUM(ls.penalty_charges_completed_derived),0) - IFNULL(SUM(ls.penalty_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.penalty_charges_waived_derived),0)\n) AS AmountPaid\nFROM m_office of\nLEFT JOIN m_client cl ON of.id = cl.office_id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nLEFT JOIN m_loan_repayment_schedule ls ON ln.id = ls.loan_id\nWHERE ls.duedate = DATE(NOW()) AND \n (of.hierarchy LIKE CONCAT((\nSELECT ino.hierarchy\nFROM m_office ino\nWHERE ino.id = ${officeId}),"%"))) as amount', 'Demand Vs Collection', 1, 0),
+	(156, 'Disbursal_Vs_Awaitingdisbursal', 'Table', '', 'Loan', 'select awaitinddisbursal.amount-disbursedAmount.amount as amountToBeDisburse, disbursedAmount.amount as disbursedAmount from \n(\nSELECT  COUNT(ln.id) AS noOfLoans, \n     IFNULL(SUM(ln.principal_amount),0) AS amount\nFROM \nm_office of\nLEFT JOIN m_client cl ON cl.office_id = of.id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nWHERE \nln.expected_disbursedon_date = DATE(NOW()) AND \n(ln.loan_status_id=200 OR ln.loan_status_id=300) AND\n of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" )\n) awaitinddisbursal,\n(\nSELECT   COUNT(ltrxn.id) as count, \n      IFNULL(SUM(ltrxn.amount),0) as amount \nFROM \nm_office of\nLEFT JOIN m_client cl ON cl.office_id = of.id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nLEFT JOIN m_loan_transaction ltrxn ON ln.id = ltrxn.loan_id\nWHERE \nltrxn.transaction_date = DATE(NOW()) AND \nltrxn.is_reversed = 0 AND\nltrxn.transaction_type_enum=1 AND\n of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n) disbursedAmount', 'Disbursal_Vs_Awaitingdisbursal', 1, 0),
+	(157, 'Savings Transaction Receipt', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1),
+	(158, 'Loan Transaction Receipt', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1),
+	(159, 'Staff Assignment History', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1),
+	(160, 'GeneralLedgerReport', 'Pentaho', NULL, 'Accounting', NULL, NULL, 0, 1),
+	(161, 'Active Loan Summary per Branch', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1),
+	(162, 'Balance Outstanding', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1),
+	(163, 'Collection Report', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1),
+	(164, 'Disbursal Report', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1);
+/*!40000 ALTER TABLE `stretchy_report` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.stretchy_report_parameter
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+CREATE TABLE IF NOT EXISTS `stretchy_report_parameter` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `report_parameter_unique` (`report_id`,`parameter_id`),
+  KEY `fk_report_parameter_001_idx` (`report_id`),
+  KEY `fk_report_parameter_002_idx` (`parameter_id`),
+  CONSTRAINT `fk_report_parameter_001` FOREIGN KEY (`report_id`) REFERENCES `stretchy_report` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
+  CONSTRAINT `fk_report_parameter_002` FOREIGN KEY (`parameter_id`) REFERENCES `stretchy_parameter` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB AUTO_INCREMENT=441 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.stretchy_report_parameter: ~334 rows (approximately)
+/*!40000 ALTER TABLE `stretchy_report_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_report_parameter` (`id`, `report_id`, `parameter_id`, `report_parameter_name`) VALUES
+	(1, 1, 5, NULL),
+	(2, 2, 5, NULL),
+	(3, 2, 6, NULL),
+	(4, 2, 10, NULL),
+	(5, 2, 20, NULL),
+	(6, 2, 25, NULL),
+	(7, 2, 26, NULL),
+	(8, 5, 5, NULL),
+	(9, 5, 6, NULL),
+	(10, 5, 10, NULL),
+	(11, 5, 20, NULL),
+	(12, 5, 25, NULL),
+	(13, 5, 26, NULL),
+	(14, 6, 5, NULL),
+	(15, 6, 6, NULL),
+	(16, 6, 10, NULL),
+	(17, 6, 20, NULL),
+	(18, 6, 25, NULL),
+	(19, 6, 26, NULL),
+	(20, 7, 5, NULL),
+	(21, 7, 6, NULL),
+	(22, 7, 10, NULL),
+	(23, 7, 20, NULL),
+	(24, 7, 25, NULL),
+	(25, 7, 26, NULL),
+	(26, 8, 5, NULL),
+	(27, 8, 6, NULL),
+	(28, 8, 10, NULL),
+	(29, 8, 25, NULL),
+	(30, 8, 26, NULL),
+	(31, 11, 5, NULL),
+	(32, 11, 6, NULL),
+	(33, 11, 10, NULL),
+	(34, 11, 20, NULL),
+	(35, 11, 25, NULL),
+	(36, 11, 26, NULL),
+	(37, 11, 100, NULL),
+	(38, 12, 5, NULL),
+	(39, 12, 6, NULL),
+	(40, 12, 10, NULL),
+	(41, 12, 20, NULL),
+	(42, 12, 25, NULL),
+	(43, 12, 26, NULL),
+	(44, 13, 1, NULL),
+	(45, 13, 2, NULL),
+	(46, 13, 3, NULL),
+	(47, 13, 5, NULL),
+	(48, 13, 6, NULL),
+	(49, 13, 10, NULL),
+	(50, 13, 20, NULL),
+	(51, 13, 25, NULL),
+	(52, 13, 26, NULL),
+	(53, 14, 1, NULL),
+	(54, 14, 2, NULL),
+	(55, 14, 3, NULL),
+	(56, 14, 5, NULL),
+	(57, 14, 6, NULL),
+	(58, 14, 10, NULL),
+	(59, 14, 20, NULL),
+	(60, 14, 25, NULL),
+	(61, 14, 26, NULL),
+	(62, 15, 5, NULL),
+	(63, 15, 6, NULL),
+	(64, 15, 10, NULL),
+	(65, 15, 20, NULL),
+	(66, 15, 25, NULL),
+	(67, 15, 26, NULL),
+	(68, 15, 100, NULL),
+	(69, 16, 5, NULL),
+	(70, 16, 6, NULL),
+	(71, 16, 10, NULL),
+	(72, 16, 20, NULL),
+	(73, 16, 25, NULL),
+	(74, 16, 26, NULL),
+	(75, 16, 100, NULL),
+	(76, 20, 1, NULL),
+	(77, 20, 2, NULL),
+	(78, 20, 10, NULL),
+	(79, 20, 20, NULL),
+	(80, 21, 1, NULL),
+	(81, 21, 2, NULL),
+	(82, 21, 5, NULL),
+	(83, 21, 10, NULL),
+	(84, 21, 20, NULL),
+	(85, 48, 5, 'branch'),
+	(86, 48, 2, 'date'),
+	(87, 49, 5, 'branch'),
+	(88, 49, 1, 'fromDate'),
+	(89, 49, 2, 'toDate'),
+	(90, 50, 5, 'branch'),
+	(91, 50, 1, 'fromDate'),
+	(92, 50, 2, 'toDate'),
+	(93, 51, 1, NULL),
+	(94, 51, 2, NULL),
+	(95, 51, 5, NULL),
+	(96, 51, 10, NULL),
+	(97, 51, 25, NULL),
+	(98, 52, 5, NULL),
+	(99, 53, 5, NULL),
+	(100, 53, 10, NULL),
+	(101, 54, 1, NULL),
+	(102, 54, 2, NULL),
+	(103, 54, 5, NULL),
+	(104, 54, 10, NULL),
+	(105, 54, 25, NULL),
+	(106, 55, 5, NULL),
+	(107, 55, 6, NULL),
+	(108, 55, 10, NULL),
+	(109, 55, 20, NULL),
+	(110, 55, 25, NULL),
+	(111, 55, 26, NULL),
+	(112, 56, 5, NULL),
+	(113, 56, 6, NULL),
+	(114, 56, 10, NULL),
+	(115, 56, 20, NULL),
+	(116, 56, 25, NULL),
+	(117, 56, 26, NULL),
+	(118, 56, 100, NULL),
+	(119, 57, 5, NULL),
+	(120, 57, 6, NULL),
+	(121, 57, 10, NULL),
+	(122, 57, 20, NULL),
+	(123, 57, 25, NULL),
+	(124, 57, 26, NULL),
+	(125, 58, 5, NULL),
+	(126, 58, 6, NULL),
+	(127, 58, 10, NULL),
+	(128, 58, 20, NULL),
+	(129, 58, 25, NULL),
+	(130, 58, 26, NULL),
+	(131, 58, 100, NULL),
+	(132, 59, 1, NULL),
+	(133, 59, 2, NULL),
+	(134, 59, 5, NULL),
+	(135, 59, 6, NULL),
+	(136, 59, 10, NULL),
+	(137, 59, 20, NULL),
+	(138, 59, 25, NULL),
+	(139, 59, 26, NULL),
+	(140, 61, 5, NULL),
+	(141, 61, 10, NULL),
+	(142, 92, 1, 'fromDate'),
+	(143, 92, 5, 'selectOffice'),
+	(144, 92, 2, 'toDate'),
+	(145, 93, 1, NULL),
+	(146, 93, 2, NULL),
+	(147, 93, 5, NULL),
+	(148, 93, 6, NULL),
+	(149, 94, 2, 'endDate'),
+	(150, 94, 6, 'loanOfficerId'),
+	(151, 94, 5, 'officeId'),
+	(152, 94, 1, 'startDate'),
+	(256, 106, 2, NULL),
+	(257, 106, 6, NULL),
+	(258, 106, 5, NULL),
+	(259, 106, 1, NULL),
+	(263, 118, 1, 'fromDate'),
+	(264, 118, 2, 'toDate'),
+	(265, 118, 1004, 'accountNo'),
+	(266, 119, 1, 'fromDate'),
+	(267, 119, 2, 'toDate'),
+	(268, 119, 5, 'selectOffice'),
+	(269, 119, 1005, 'selectProduct'),
+	(270, 120, 5, 'branch'),
+	(271, 120, 6, 'loanOfficer'),
+	(272, 120, 10, 'currencyId'),
+	(273, 120, 20, 'fundId'),
+	(274, 120, 25, 'loanProductId'),
+	(275, 120, 26, 'loanPurposeId'),
+	(276, 121, 5, 'Branch'),
+	(277, 121, 6, 'loanOfficer'),
+	(278, 121, 10, 'CurrencyId'),
+	(279, 121, 20, 'fundId'),
+	(280, 121, 25, 'loanProductId'),
+	(281, 121, 26, 'loanPurposeId'),
+	(282, 121, 100, 'parType'),
+	(283, 122, 5, 'Branch'),
+	(284, 122, 6, 'loanOfficer'),
+	(285, 122, 10, 'CurrencyId'),
+	(286, 122, 20, 'fundId'),
+	(287, 122, 25, 'loanProductId'),
+	(288, 122, 26, 'loanPurposeId'),
+	(289, 122, 1, 'startDate'),
+	(290, 122, 2, 'endDate'),
+	(291, 123, 5, 'Branch'),
+	(292, 123, 6, 'Loan Officer'),
+	(293, 123, 10, 'CurrencyId'),
+	(294, 123, 20, 'fundId'),
+	(295, 123, 25, 'loanProductId'),
+	(296, 123, 26, 'loanPurposeId'),
+	(297, 123, 100, 'parType'),
+	(298, 124, 5, 'Branch'),
+	(299, 124, 6, 'Loan Officer'),
+	(300, 124, 10, 'CurrencyId'),
+	(301, 124, 20, 'fundId'),
+	(302, 124, 25, 'loanProductId'),
+	(303, 124, 26, 'loanPurposeId'),
+	(304, 125, 5, 'Branch'),
+	(305, 125, 6, 'Loan Officer'),
+	(306, 125, 10, 'CurrencyId'),
+	(307, 125, 20, 'fundId'),
+	(308, 125, 25, 'loanProductId'),
+	(309, 125, 26, 'loanPurposeId'),
+	(310, 125, 100, 'parType'),
+	(311, 126, 5, 'Branch'),
+	(312, 126, 6, 'Loan Officer'),
+	(313, 126, 10, 'CurrencyId'),
+	(314, 126, 20, 'fundId'),
+	(315, 126, 25, 'loanProductId'),
+	(316, 126, 26, 'loanPurposeId'),
+	(317, 127, 5, 'Branch'),
+	(318, 128, 5, 'Branch'),
+	(319, 128, 10, 'CurrencyId'),
+	(320, 129, 5, 'Branch'),
+	(321, 129, 10, 'CurrencyId'),
+	(322, 130, 5, 'selectOffice'),
+	(323, 131, 5, 'Branch'),
+	(324, 131, 6, 'Loan Officer'),
+	(325, 131, 10, 'CurrencyId'),
+	(326, 131, 20, 'fundId'),
+	(327, 131, 25, 'loanProductId'),
+	(328, 131, 26, 'loanPurposeId'),
+	(329, 132, 5, 'Branch'),
+	(330, 132, 6, 'Loan Officer'),
+	(331, 132, 1, 'startDate'),
+	(332, 132, 2, 'endDate'),
+	(333, 133, 5, 'Branch'),
+	(334, 133, 10, 'CurrencyId'),
+	(335, 133, 20, 'fundId'),
+	(336, 133, 1, 'startDate'),
+	(337, 133, 2, 'endDate'),
+	(338, 134, 10, 'CurrencyId'),
+	(339, 134, 20, 'fundId'),
+	(340, 134, 1, 'startDate'),
+	(341, 134, 2, 'endDate'),
+	(342, 135, 5, 'Branch'),
+	(343, 135, 6, 'Loan Officer'),
+	(344, 135, 10, 'CurrencyId'),
+	(345, 135, 20, 'fundId'),
+	(346, 135, 25, 'loanProductId'),
+	(347, 135, 26, 'loanPurposeId'),
+	(348, 136, 5, 'Branch'),
+	(349, 136, 6, 'Loan Officer'),
+	(350, 136, 10, 'CurrencyId'),
+	(351, 136, 20, 'fundId'),
+	(352, 136, 25, 'loanProductId'),
+	(353, 136, 26, 'loanPurposeId'),
+	(354, 137, 5, 'Branch'),
+	(355, 137, 6, 'Loan Officer'),
+	(356, 137, 10, 'CurrencyId'),
+	(357, 137, 20, 'fundId'),
+	(358, 137, 25, 'loanProductId'),
+	(359, 137, 26, 'loanPurposeId'),
+	(360, 138, 5, 'Branch'),
+	(361, 138, 6, 'Loan Officer'),
+	(362, 138, 10, 'CurrencyId'),
+	(363, 138, 20, 'fundId'),
+	(364, 138, 25, 'loanProductId'),
+	(365, 138, 26, 'loanPurposeId'),
+	(366, 139, 5, 'Branch'),
+	(367, 139, 6, 'Loan Officer'),
+	(368, 139, 10, 'CurrencyId'),
+	(369, 139, 20, 'fundId'),
+	(370, 139, 25, 'loanProductId'),
+	(371, 139, 26, 'loanPurposeId'),
+	(372, 139, 1, 'startDate'),
+	(373, 139, 2, 'endDate'),
+	(374, 139, 3, 'obligDateType'),
+	(375, 140, 5, 'Branch'),
+	(376, 140, 6, 'Loan Officer'),
+	(377, 140, 10, 'CurrencyId'),
+	(378, 140, 20, 'fundId'),
+	(379, 140, 25, 'loanProductId'),
+	(380, 140, 26, 'loanPurposeId'),
+	(381, 140, 1, 'Startdate'),
+	(382, 140, 2, 'Enddate'),
+	(383, 140, 3, 'obligDateType'),
+	(384, 141, 5, 'Branch'),
+	(385, 141, 6, 'Loan Officer'),
+	(386, 141, 10, 'CurrencyId'),
+	(387, 141, 20, 'fundId'),
+	(388, 141, 25, 'loanProductId'),
+	(389, 141, 26, 'loanPurposeId'),
+	(390, 141, 100, 'parType'),
+	(391, 142, 5, 'Branch'),
+	(392, 142, 6, 'loanOfficer'),
+	(393, 142, 10, 'CurrencyId'),
+	(394, 142, 20, 'fundId'),
+	(395, 142, 25, 'loanProductId'),
+	(396, 142, 26, 'loanPurposeId'),
+	(397, 142, 100, 'parType'),
+	(398, 143, 5, 'Branch'),
+	(399, 143, 10, 'CurrencyId'),
+	(400, 143, 25, 'loanProductId'),
+	(401, 143, 1, 'startDate'),
+	(402, 143, 2, 'endDate'),
+	(403, 144, 5, 'Branch'),
+	(404, 144, 6, 'Loan Officer'),
+	(405, 144, 1, 'startDate'),
+	(406, 144, 2, 'endDate'),
+	(407, 145, 5, 'Branch'),
+	(408, 145, 10, 'CurrencyId'),
+	(409, 145, 25, 'loanProductId'),
+	(410, 145, 1, 'startDate'),
+	(411, 145, 2, 'endDate'),
+	(412, 146, 1, 'startDate'),
+	(413, 146, 2, 'endDate'),
+	(414, 146, 1004, 'accountNo'),
+	(415, 147, 1, 'startDate'),
+	(416, 147, 2, 'endDate'),
+	(417, 147, 1004, 'selectLoan'),
+	(418, 149, 5, ''),
+	(419, 150, 5, ''),
+	(420, 151, 5, ''),
+	(421, 152, 5, ''),
+	(422, 153, 5, ''),
+	(423, 154, 5, ''),
+	(424, 155, 5, ''),
+	(425, 156, 5, ''),
+	(426, 157, 1006, 'transactionId'),
+	(427, 158, 1006, 'transactionId'),
+	(428, 159, 1007, 'centerId'),
+	(429, 160, 1008, 'account'),
+	(430, 160, 1, 'fromDate'),
+	(431, 160, 2, 'toDate'),
+	(432, 160, 5, 'branch'),
+	(433, 162, 5, 'branch'),
+	(434, 162, 1009, 'ondate'),
+	(435, 163, 5, 'branch'),
+	(436, 163, 1, 'fromDate'),
+	(437, 163, 2, 'toDate'),
+	(438, 164, 5, 'branch'),
+	(439, 164, 1, 'fromDate'),
+	(440, 164, 2, 'toDate');
+/*!40000 ALTER TABLE `stretchy_report_parameter` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.x_registered_table
+DROP TABLE IF EXISTS `x_registered_table`;
+CREATE TABLE IF NOT EXISTS `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  `category` int(11) NOT NULL DEFAULT '100',
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.x_registered_table: ~0 rows (approximately)
+/*!40000 ALTER TABLE `x_registered_table` DISABLE KEYS */;
+/*!40000 ALTER TABLE `x_registered_table` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-default.x_table_column_code_mappings
+DROP TABLE IF EXISTS `x_table_column_code_mappings`;
+CREATE TABLE IF NOT EXISTS `x_table_column_code_mappings` (
+  `column_alias_name` varchar(50) NOT NULL,
+  `code_id` int(10) NOT NULL,
+  PRIMARY KEY (`column_alias_name`),
+  KEY `FK_x_code_id` (`code_id`),
+  CONSTRAINT `FK_x_code_id` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-default.x_table_column_code_mappings: ~0 rows (approximately)
+/*!40000 ALTER TABLE `x_table_column_code_mappings` DISABLE KEYS */;
+/*!40000 ALTER TABLE `x_table_column_code_mappings` ENABLE KEYS */;
+/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
+/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
diff --git a/fineract-provider/src/main/resources/sql/migrations/sample_data/load_sample_data.sql b/fineract-provider/src/main/resources/sql/migrations/sample_data/load_sample_data.sql
new file mode 100644
index 0000000..b69acad
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/sample_data/load_sample_data.sql
@@ -0,0 +1,5974 @@
+-- --------------------------------------------------------
+-- Host:                         127.0.0.1
+-- Server version:               5.5.34 - MySQL Community Server (GPL)
+-- Server OS:                    Win64
+-- HeidiSQL Version:             9.3.0.4984
+-- --------------------------------------------------------
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET NAMES utf8mb4 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+
+-- Dumping structure for table mifostenant-reference.acc_accounting_rule
+DROP TABLE IF EXISTS `acc_accounting_rule`;
+CREATE TABLE IF NOT EXISTS `acc_accounting_rule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `debit_account_id` bigint(20) DEFAULT NULL,
+  `allow_multiple_debits` tinyint(1) NOT NULL DEFAULT '0',
+  `credit_account_id` bigint(20) DEFAULT NULL,
+  `allow_multiple_credits` tinyint(1) NOT NULL DEFAULT '0',
+  `description` varchar(500) DEFAULT NULL,
+  `system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `accounting_rule_name_unique` (`name`),
+  KEY `FK_acc_accounting_rule_acc_gl_account_debit` (`debit_account_id`),
+  KEY `FK_acc_accounting_rule_acc_gl_account_credit` (`credit_account_id`),
+  KEY `FK_acc_accounting_rule_m_office` (`office_id`),
+  CONSTRAINT `FK_acc_accounting_rule_acc_gl_account_credit` FOREIGN KEY (`credit_account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_accounting_rule_acc_gl_account_debit` FOREIGN KEY (`debit_account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_accounting_rule_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.acc_accounting_rule: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_accounting_rule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_accounting_rule` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.acc_gl_account
+DROP TABLE IF EXISTS `acc_gl_account`;
+CREATE TABLE IF NOT EXISTS `acc_gl_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(200) NOT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(50) DEFAULT NULL,
+  `gl_code` varchar(45) NOT NULL,
+  `disabled` tinyint(1) NOT NULL DEFAULT '0',
+  `manual_journal_entries_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `account_usage` tinyint(1) NOT NULL DEFAULT '2',
+  `classification_enum` smallint(5) NOT NULL,
+  `tag_id` int(11) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `acc_gl_code` (`gl_code`),
+  KEY `FK_ACC_0000000001` (`parent_id`),
+  KEY `FKGLACC000000002` (`tag_id`),
+  CONSTRAINT `FKGLACC000000002` FOREIGN KEY (`tag_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_ACC_0000000001` FOREIGN KEY (`parent_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.acc_gl_account: ~55 rows (approximately)
+/*!40000 ALTER TABLE `acc_gl_account` DISABLE KEYS */;
+INSERT INTO `acc_gl_account` (`id`, `name`, `parent_id`, `hierarchy`, `gl_code`, `disabled`, `manual_journal_entries_allowed`, `account_usage`, `classification_enum`, `tag_id`, `description`) VALUES
+	(1, 'Deposit Accounts Portfolio', NULL, '.', '10100', 0, 1, 2, 2, NULL, NULL),
+	(2, 'Overpayment Liability', NULL, '.', '10200', 0, 1, 1, 2, NULL, NULL),
+	(3, 'Interest Payable', NULL, '.', '10300', 0, 1, 2, 2, NULL, NULL),
+	(4, 'Pass-Through Collections', NULL, '.', '10400', 0, 1, 2, 2, NULL, NULL),
+	(5, 'External Credits and Borrowings', NULL, '.', '10500', 0, 1, 2, 2, NULL, NULL),
+	(6, 'Accrued Expenses and Provisions', NULL, '.', '10600', 0, 1, 2, 2, NULL, NULL),
+	(7, 'Other Liabilities', NULL, '.', '10700', 0, 1, 2, 2, NULL, NULL),
+	(8, 'Fixed Assets', NULL, '.', '20100', 0, 1, 2, 1, NULL, NULL),
+	(9, 'Investments', NULL, '.', '20200', 0, 1, 2, 1, NULL, NULL),
+	(10, 'Cash', NULL, '.', '20300', 0, 1, 2, 1, NULL, NULL),
+	(11, 'Bank Accounts', NULL, '.', '20400', 0, 1, 2, 1, NULL, NULL),
+	(12, 'Lending Portfolio', NULL, '.', '20500', 0, 1, 2, 1, NULL, NULL),
+	(13, 'Interest and Fees Receivable', NULL, '.', '20600', 0, 1, 2, 1, NULL, NULL),
+	(14, 'Advance Payments', NULL, '.', '20700', 0, 1, 2, 1, NULL, NULL),
+	(15, 'Reserves for Possible Losses', NULL, '.', '20800', 0, 1, 2, 1, NULL, NULL),
+	(16, 'Accrued Income', NULL, '.', '20900', 0, 1, 2, 1, NULL, NULL),
+	(17, 'Suspense Account', NULL, '.', '9999', 0, 1, 1, 1, NULL, NULL),
+	(18, 'Portfolio Earnings', NULL, '.', '30100', 0, 1, 2, 4, NULL, NULL),
+	(19, 'Non Portfolio Income', NULL, '.', '30200', 0, 1, 2, 4, NULL, NULL),
+	(20, 'Portfolio Expenses', NULL, '.', '40100', 0, 1, 2, 5, NULL, NULL),
+	(21, 'General And Administrative Expenses', NULL, '.', '40200', 0, 1, 2, 5, NULL, NULL),
+	(22, 'Sundry Expenses', NULL, '.', '40800', 0, 1, 2, 5, NULL, NULL),
+	(23, 'Repairs and Maintenance', NULL, '.', '40900', 0, 1, 2, 5, NULL, NULL),
+	(24, 'Depreciation and Amortization', NULL, '.', '41000', 0, 1, 2, 5, NULL, NULL),
+	(25, 'Equity and Share Capital', NULL, '.', '50100', 0, 1, 2, 3, NULL, NULL),
+	(26, 'Opening Balances Contra Account', NULL, '.', '9099', 0, 1, 2, 3, NULL, NULL),
+	(27, 'Voluntary Savings', 1, '.1.', '10101', 0, 1, 2, 2, NULL, NULL),
+	(28, 'Mandatory Savings', 1, '.1.', '10102', 0, 1, 2, 2, NULL, NULL),
+	(29, 'Term Deposits', 1, '.1.', '10103', 0, 1, 2, 2, NULL, NULL),
+	(30, 'Recurring Deposits', 1, '.1.', '10104', 0, 1, 1, 2, NULL, NULL),
+	(31, 'Furniture and Fixtures', 8, '.8.', '20101', 0, 1, 1, 1, NULL, NULL),
+	(32, 'Cash In Hand', 10, '.10.', '20301', 0, 1, 1, 1, NULL, NULL),
+	(33, 'Petty Cash', 10, '.10.', '20302', 0, 1, 1, 1, NULL, NULL),
+	(34, 'Loans to Customers', 12, '.12.', '20501', 0, 1, 1, 1, NULL, NULL),
+	(35, 'Current Account Overdrafts', 12, '.12.', '20502', 0, 1, 1, 1, NULL, NULL),
+	(36, 'Fees and Charges', 18, '.18.', '30101', 0, 1, 1, 4, NULL, NULL),
+	(37, 'Penalties', 18, '.18.', '30102', 0, 1, 1, 4, NULL, NULL),
+	(38, 'Interest Received from Borrowers', 18, '.18.', '30103', 0, 1, 1, 4, NULL, NULL),
+	(39, 'Insurance Charges', 18, '.18.', '30104', 0, 1, 1, 4, NULL, NULL),
+	(40, 'Other Operating Income', 18, '.18.', '30105', 0, 1, 1, 4, NULL, NULL),
+	(41, 'Losses Written Off', 20, '.20.', '40101', 0, 1, 1, 5, NULL, NULL),
+	(42, 'Interest Paid To Depositors', 20, '.20.', '40102', 0, 1, 1, 5, NULL, NULL),
+	(43, 'Loan Collection Expenses', 20, '.20.', '40103', 0, 1, 1, 5, NULL, NULL),
+	(44, 'Salaries and Wages', 21, '.21.', '40300', 0, 1, 1, 5, NULL, NULL),
+	(45, 'Professional Expenses', 21, '.21.', '40400', 0, 1, 1, 5, NULL, NULL),
+	(46, 'Travel and Conveyance Expenses', 21, '.21.', '40500', 0, 1, 1, 5, NULL, NULL),
+	(47, 'Training Expenses', 21, '.21.', '40600', 0, 1, 1, 5, NULL, NULL),
+	(48, 'Office Expenses', 21, '.21.', '40700', 0, 1, 2, 5, NULL, NULL),
+	(49, 'Printing and Stationery', 48, '.21.48.', '40701', 0, 1, 1, 5, NULL, NULL),
+	(50, 'Telephone Charges', 48, '.21.48.', '40702', 0, 1, 1, 5, NULL, NULL),
+	(51, 'Electricity Charges', 48, '.21.48.', '40703', 0, 1, 1, 5, NULL, NULL),
+	(52, 'Rent Paid', 48, '.21.48.', '40704', 0, 1, 1, 5, NULL, NULL),
+	(53, 'Internet Charges', 48, '.21.48.', '40705', 0, 1, 1, 5, NULL, NULL),
+	(54, 'Loan Recovery (Temp)', NULL, '.', '220002-Temp', 0, 1, 1, 4, NULL, 'Temporary account to track income from Loan recovery'),
+	(55, 'Liability Transfer (Temp)', NULL, '.', '220004-Temp', 0, 1, 1, 2, NULL, 'Temporary Liability account to track Account Transfers');
+/*!40000 ALTER TABLE `acc_gl_account` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.acc_gl_closure
+DROP TABLE IF EXISTS `acc_gl_closure`;
+CREATE TABLE IF NOT EXISTS `acc_gl_closure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `closing_date` date NOT NULL,
+  `is_deleted` int(20) NOT NULL DEFAULT '0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `comments` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `office_id_closing_date` (`office_id`,`closing_date`),
+  KEY `FK_acc_gl_closure_m_office` (`office_id`),
+  KEY `FK_acc_gl_closure_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_closure_m_appuser_2` (`lastmodifiedby_id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_closure_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.acc_gl_closure: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_gl_closure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_closure` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.acc_gl_financial_activity_account
+DROP TABLE IF EXISTS `acc_gl_financial_activity_account`;
+CREATE TABLE IF NOT EXISTS `acc_gl_financial_activity_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) NOT NULL DEFAULT '0',
+  `financial_activity_type` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `financial_activity_type` (`financial_activity_type`),
+  KEY `FK_office_mapping_acc_gl_account` (`gl_account_id`),
+  CONSTRAINT `FK_office_mapping_acc_gl_account` FOREIGN KEY (`gl_account_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.acc_gl_financial_activity_account: ~1 rows (approximately)
+/*!40000 ALTER TABLE `acc_gl_financial_activity_account` DISABLE KEYS */;
+INSERT INTO `acc_gl_financial_activity_account` (`id`, `gl_account_id`, `financial_activity_type`) VALUES
+	(1, 55, 200);
+/*!40000 ALTER TABLE `acc_gl_financial_activity_account` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.acc_gl_journal_entry
+DROP TABLE IF EXISTS `acc_gl_journal_entry`;
+CREATE TABLE IF NOT EXISTS `acc_gl_journal_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `reversal_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `transaction_id` varchar(50) NOT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `savings_transaction_id` bigint(20) DEFAULT NULL,
+  `client_transaction_id` bigint(20) DEFAULT NULL,
+  `reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `ref_num` varchar(100) DEFAULT NULL,
+  `manual_entry` tinyint(1) NOT NULL DEFAULT '0',
+  `entry_date` date NOT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `entity_type_enum` smallint(5) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  `is_running_balance_calculated` tinyint(4) NOT NULL DEFAULT '0',
+  `office_running_balance` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `organization_running_balance` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `payment_details_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_gl_journal_entry_m_office` (`office_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
+  KEY `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
+  KEY `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
+  KEY `FK_acc_gl_journal_entry_m_loan_transaction` (`loan_transaction_id`),
+  KEY `FK_acc_gl_journal_entry_m_savings_account_transaction` (`savings_transaction_id`),
+  KEY `FK_acc_gl_journal_entry_m_payment_detail` (`payment_details_id`),
+  KEY `FK_acc_gl_journal_entry_m_client_transaction` (`client_transaction_id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_client_transaction` FOREIGN KEY (`client_transaction_id`) REFERENCES `m_client_transaction` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_payment_detail` FOREIGN KEY (`payment_details_id`) REFERENCES `m_payment_detail` (`id`),
+  CONSTRAINT `FK_acc_gl_journal_entry_m_savings_account_transaction` FOREIGN KEY (`savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.acc_gl_journal_entry: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_gl_journal_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_gl_journal_entry` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.acc_product_mapping
+DROP TABLE IF EXISTS `acc_product_mapping`;
+CREATE TABLE IF NOT EXISTS `acc_product_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `gl_account_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `product_type` smallint(5) DEFAULT NULL,
+  `payment_type` int(11) DEFAULT NULL,
+  `charge_id` bigint(20) DEFAULT NULL,
+  `financial_account_type` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_acc_product_mapping_m_code_value` (`payment_type`),
+  KEY `FK_acc_product_mapping_m_charge` (`charge_id`),
+  CONSTRAINT `FK_acc_product_mapping_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `FK_acc_product_mapping_m_payment_type` FOREIGN KEY (`payment_type`) REFERENCES `m_payment_type` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.acc_product_mapping: ~18 rows (approximately)
+/*!40000 ALTER TABLE `acc_product_mapping` DISABLE KEYS */;
+INSERT INTO `acc_product_mapping` (`id`, `gl_account_id`, `product_id`, `product_type`, `payment_type`, `charge_id`, `financial_account_type`) VALUES
+	(1, 32, 1, 1, NULL, NULL, 1),
+	(2, 34, 1, 1, NULL, NULL, 2),
+	(3, 17, 1, 1, NULL, NULL, 10),
+	(4, 38, 1, 1, NULL, NULL, 3),
+	(5, 36, 1, 1, NULL, NULL, 4),
+	(6, 37, 1, 1, NULL, NULL, 5),
+	(7, 41, 1, 1, NULL, NULL, 6),
+	(8, 2, 1, 1, NULL, NULL, 11),
+	(9, 31, 1, 2, NULL, NULL, 1),
+	(10, 32, 1, 2, NULL, NULL, 11),
+	(11, 36, 1, 2, NULL, NULL, 4),
+	(12, 37, 1, 2, NULL, NULL, 5),
+	(13, 38, 1, 2, NULL, NULL, 12),
+	(14, 41, 1, 2, NULL, NULL, 3),
+	(15, 42, 1, 2, NULL, NULL, 13),
+	(16, 30, 1, 2, NULL, NULL, 2),
+	(17, 30, 1, 2, NULL, NULL, 10),
+	(18, 54, 1, 1, NULL, NULL, 12);
+/*!40000 ALTER TABLE `acc_product_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.acc_rule_tags
+DROP TABLE IF EXISTS `acc_rule_tags`;
+CREATE TABLE IF NOT EXISTS `acc_rule_tags` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `acc_rule_id` bigint(20) NOT NULL,
+  `tag_id` int(11) NOT NULL,
+  `acc_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UNIQUE_ACCOUNT_RULE_TAGS` (`acc_rule_id`,`tag_id`,`acc_type_enum`),
+  KEY `FK_acc_accounting_rule_id` (`acc_rule_id`),
+  KEY `FK_m_code_value_id` (`tag_id`),
+  CONSTRAINT `FK_acc_accounting_rule_id` FOREIGN KEY (`acc_rule_id`) REFERENCES `acc_accounting_rule` (`id`),
+  CONSTRAINT `FK_m_code_value_id` FOREIGN KEY (`tag_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-reference.acc_rule_tags: ~0 rows (approximately)
+/*!40000 ALTER TABLE `acc_rule_tags` DISABLE KEYS */;
+/*!40000 ALTER TABLE `acc_rule_tags` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.c_account_number_format
+DROP TABLE IF EXISTS `c_account_number_format`;
+CREATE TABLE IF NOT EXISTS `c_account_number_format` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_type_enum` smallint(1) NOT NULL,
+  `prefix_type_enum` smallint(2) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_type_enum` (`account_type_enum`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.c_account_number_format: ~0 rows (approximately)
+/*!40000 ALTER TABLE `c_account_number_format` DISABLE KEYS */;
+/*!40000 ALTER TABLE `c_account_number_format` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.c_cache
+DROP TABLE IF EXISTS `c_cache`;
+CREATE TABLE IF NOT EXISTS `c_cache` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `cache_type_enum` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.c_cache: ~1 rows (approximately)
+/*!40000 ALTER TABLE `c_cache` DISABLE KEYS */;
+INSERT INTO `c_cache` (`id`, `cache_type_enum`) VALUES
+	(1, 1);
+/*!40000 ALTER TABLE `c_cache` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.c_configuration
+DROP TABLE IF EXISTS `c_configuration`;
+CREATE TABLE IF NOT EXISTS `c_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) DEFAULT NULL,
+  `value` int(11) DEFAULT NULL,
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  `is_trap_door` tinyint(1) NOT NULL DEFAULT '0',
+  `description` varchar(300) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_UNIQUE` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.c_configuration: ~20 rows (approximately)
+/*!40000 ALTER TABLE `c_configuration` DISABLE KEYS */;
+INSERT INTO `c_configuration` (`id`, `name`, `value`, `enabled`, `is_trap_door`, `description`) VALUES
+	(1, 'maker-checker', NULL, 0, 0, NULL),
+	(4, 'amazon-S3', NULL, 0, 0, NULL),
+	(5, 'reschedule-future-repayments', NULL, 1, 0, NULL),
+	(6, 'reschedule-repayments-on-holidays', NULL, 0, 0, NULL),
+	(7, 'allow-transactions-on-holiday', NULL, 0, 0, NULL),
+	(8, 'allow-transactions-on-non_workingday', NULL, 0, 0, NULL),
+	(9, 'constraint_approach_for_datatables', NULL, 0, 0, NULL),
+	(10, 'penalty-wait-period', 2, 1, 0, NULL),
+	(11, 'force-password-reset-days', 0, 0, 0, NULL),
+	(12, 'grace-on-penalty-posting', 0, 1, 0, NULL),
+	(15, 'savings-interest-posting-current-period-end', NULL, 0, 0, 'Recommended to be changed only once during start of production. When set as false(default), interest will be posted on the first date of next period. If set as true, interest will be posted on last date of current period. There is no difference in the interest amount posted.'),
+	(16, 'financial-year-beginning-month', 1, 1, 0, 'Recommended to be changed only once during start of production. Allowed values 1 - 12 (January - December). Interest posting periods are evaluated based on this configuration.'),
+	(17, 'min-clients-in-group', 5, 0, 0, 'Minimum number of Clients that a Group should have'),
+	(18, 'max-clients-in-group', 5, 0, 0, 'Maximum number of Clients that a Group can have'),
+	(19, 'meetings-mandatory-for-jlg-loans', NULL, 0, 0, 'Enforces all JLG loans to follow a meeting schedule belonging to parent group or Center'),
+	(20, 'office-specific-products-enabled', 0, 0, 0, 'Whether products and fees should be office specific or not? This property should NOT be changed once Mifos is Live.'),
+	(21, 'restrict-products-to-user-office', 0, 0, 0, 'This should be enabled only if, products & fees are office specific (i.e. office-specific-products-enabled is enabled). This property specifies if the products should be auto-restricted to office of the user who created the proudct? Note: This property should NOT be changed once Mifos is Live.'),
+	(22, 'office-opening-balances-contra-account', 0, 1, 0, NULL),
+	(23, 'rounding-mode', 6, 1, 1, '0 - UP, 1 - DOWN, 2- CEILING, 3- FLOOR, 4- HALF_UP, 5- HALF_DOWN, 6 - HALF_EVEN'),
+	(24, 'backdate-penalties-enabled', 0, 1, 0, 'If this parameter is disabled penalties will only be added to instalments due moving forward, any old overdue instalments will not be affected.');
+/*!40000 ALTER TABLE `c_configuration` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.c_external_service
+DROP TABLE IF EXISTS `c_external_service`;
+CREATE TABLE IF NOT EXISTS `c_external_service` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_UNIQUE` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.c_external_service: ~2 rows (approximately)
+/*!40000 ALTER TABLE `c_external_service` DISABLE KEYS */;
+INSERT INTO `c_external_service` (`id`, `name`) VALUES
+	(1, 'S3'),
+	(2, 'SMTP_Email_Account');
+/*!40000 ALTER TABLE `c_external_service` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.c_external_service_properties
+DROP TABLE IF EXISTS `c_external_service_properties`;
+CREATE TABLE IF NOT EXISTS `c_external_service_properties` (
+  `name` varchar(150) NOT NULL,
+  `value` varchar(250) DEFAULT NULL,
+  `external_service_id` bigint(20) NOT NULL,
+  KEY `FK_c_external_service_properties_c_external_service` (`external_service_id`),
+  CONSTRAINT `FK_c_external_service_properties_c_external_service` FOREIGN KEY (`external_service_id`) REFERENCES `c_external_service` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.c_external_service_properties: ~8 rows (approximately)
+/*!40000 ALTER TABLE `c_external_service_properties` DISABLE KEYS */;
+INSERT INTO `c_external_service_properties` (`name`, `value`, `external_service_id`) VALUES
+	('s3_access_key', NULL, 1),
+	('s3_bucket_name', NULL, 1),
+	('s3_secret_key', NULL, 1),
+	('username', 'support@cloudmicrofinance.com', 2),
+	('password', 'support81', 2),
+	('host', 'smtp.gmail.com', 2),
+	('port', '25', 2),
+	('useTLS', 'true', 2);
+/*!40000 ALTER TABLE `c_external_service_properties` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.job
+DROP TABLE IF EXISTS `job`;
+CREATE TABLE IF NOT EXISTS `job` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) NOT NULL,
+  `display_name` varchar(50) NOT NULL,
+  `cron_expression` varchar(20) CHARACTER SET latin1 NOT NULL,
+  `create_time` datetime NOT NULL,
+  `task_priority` smallint(6) NOT NULL DEFAULT '5',
+  `group_name` varchar(50) CHARACTER SET latin1 DEFAULT NULL,
+  `previous_run_start_time` datetime DEFAULT NULL,
+  `next_run_time` datetime DEFAULT NULL,
+  `job_key` varchar(500) DEFAULT NULL,
+  `initializing_errorlog` text,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  `currently_running` tinyint(1) NOT NULL DEFAULT '0',
+  `updates_allowed` tinyint(1) NOT NULL DEFAULT '1',
+  `scheduler_group` smallint(2) NOT NULL DEFAULT '0',
+  `is_misfired` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.job: ~19 rows (approximately)
+/*!40000 ALTER TABLE `job` DISABLE KEYS */;
+INSERT INTO `job` (`id`, `name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES
+	(1, 'Update loan Summary', 'Update loan Summary', '0 0 22 1/1 * ? *', '2014-03-07 18:29:14', 5, NULL, '2014-06-11 09:30:00', '2016-01-20 22:00:00', 'Update loan SummaryJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(2, 'Update Loan Arrears Ageing', 'Update Loan Arrears Ageing', '0 1 0 1/1 * ? *', '2014-03-07 18:29:14', 5, NULL, NULL, '2016-01-21 00:01:00', 'Update Loan Arrears AgeingJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(3, 'Update Loan Paid In Advance', 'Update Loan Paid In Advance', '0 5 0 1/1 * ? *', '2014-03-07 18:29:14', 5, NULL, NULL, '2016-01-21 00:05:00', 'Update Loan Paid In AdvanceJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(4, 'Apply Annual Fee For Savings', 'Apply Annual Fee For Savings', '0 20 22 1/1 * ? *', '2014-03-07 18:29:14', 5, NULL, '2014-06-11 09:50:00', '2016-01-20 22:20:00', 'Apply Annual Fee For SavingsJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(5, 'Apply Holidays To Loans', 'Apply Holidays To Loans', '0 0 12 * * ?', '2014-03-07 18:29:14', 5, NULL, '2014-03-24 12:00:04', '2016-01-21 12:00:00', 'Apply Holidays To LoansJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(6, 'Post Interest For Savings', 'Post Interest For Savings', '0 0 0 1/1 * ? *', '2014-03-07 18:29:21', 5, NULL, NULL, '2016-01-21 00:00:00', 'Post Interest For SavingsJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 1, 0),
+	(7, 'Transfer Fee For Loans From Savings', 'Transfer Fee For Loans From Savings', '0 1 0 1/1 * ? *', '2014-03-07 18:29:32', 5, NULL, NULL, '2016-01-21 00:01:00', 'Transfer Fee For Loans From SavingsJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(8, 'Pay Due Savings Charges', 'Pay Due Savings Charges', '0 0 12 * * ?', '2013-09-23 00:00:00', 5, NULL, '2014-03-24 12:00:04', '2016-01-21 12:00:00', 'Pay Due Savings ChargesJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(9, 'Update Accounting Running Balances', 'Update Accounting Running Balances', '0 1 0 1/1 * ? *', '2014-03-07 18:29:37', 5, NULL, NULL, '2016-01-21 00:01:00', 'Update Accounting Running BalancesJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(10, 'Execute Standing Instruction', 'Execute Standing Instruction', '0 0 0 1/1 * ? *', '2014-05-01 16:10:35', 5, NULL, NULL, '2016-01-21 00:00:00', 'Execute Standing InstructionJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(11, 'Add Accrual Transactions', 'Add Accrual Transactions', '0 1 0 1/1 * ? *', '2014-05-01 16:10:36', 3, NULL, NULL, '2016-01-21 00:01:00', 'Add Accrual TransactionsJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 3, 0),
+	(12, 'Apply penalty to overdue loans', 'Apply penalty to overdue loans', '0 0 0 1/1 * ? *', '2014-05-01 16:10:36', 5, NULL, NULL, '2016-01-21 00:00:00', 'Apply penalty to overdue loansJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(13, 'Update Non Performing Assets', 'Update Non Performing Assets', '0 0 0 1/1 * ? *', '2014-05-01 16:10:41', 5, NULL, NULL, '2016-01-21 00:00:00', 'Update Non Performing AssetsJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 3, 0),
+	(14, 'Transfer Interest To Savings', 'Transfer Interest To Savings', '0 2 0 1/1 * ? *', '2014-06-11 09:09:15', 4, NULL, NULL, '2016-01-21 00:02:00', 'Transfer Interest To SavingsJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 1, 0),
+	(15, 'Update Deposit Accounts Maturity details', 'Update Deposit Accounts Maturity details', '0 0 0 1/1 * ? *', '2014-06-11 09:09:15', 5, NULL, NULL, '2016-01-21 00:00:00', 'Update Deposit Accounts Maturity detailsJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(16, 'Add Periodic Accrual Transactions', 'Add Periodic Accrual Transactions', '0 2 0 1/1 * ? *', '2014-10-14 16:19:45', 2, NULL, NULL, '2016-01-21 00:02:00', 'Add Periodic Accrual TransactionsJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 3, 0),
+	(17, 'Recalculate Interest For Loans', 'Recalculate Interest For Loans', '0 1 0 1/1 * ? *', '2014-10-14 16:19:55', 4, NULL, NULL, '2016-01-21 00:01:00', 'Recalculate Interest For LoansJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 3, 0),
+	(18, 'Generate Mandatory Savings Schedule', 'Generate Mandatory Savings Schedule', '0 5 0 1/1 * ? *', '2015-04-16 02:28:43', 5, NULL, NULL, '2016-01-21 00:05:00', 'Generate Mandatory Savings ScheduleJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0),
+	(19, 'Generate Loan Loss Provisioning', 'Generate Loan Loss Provisioning', '0 0 0 1/1 * ? *', '2015-10-20 19:57:58', 5, NULL, NULL, '2016-01-21 00:00:00', 'Generate Loan Loss ProvisioningJobDetail2 _ DEFAULT', NULL, 1, 0, 1, 0, 0);
+/*!40000 ALTER TABLE `job` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.job_run_history
+DROP TABLE IF EXISTS `job_run_history`;
+CREATE TABLE IF NOT EXISTS `job_run_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `job_id` bigint(20) NOT NULL,
+  `version` bigint(20) NOT NULL,
+  `start_time` datetime NOT NULL,
+  `end_time` datetime NOT NULL,
+  `status` varchar(10) CHARACTER SET latin1 NOT NULL,
+  `error_message` text,
+  `trigger_type` varchar(25) NOT NULL,
+  `error_log` text,
+  PRIMARY KEY (`id`),
+  KEY `scheduledjobsFK` (`job_id`),
+  CONSTRAINT `scheduledjobsFK` FOREIGN KEY (`job_id`) REFERENCES `job` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.job_run_history: ~18 rows (approximately)
+/*!40000 ALTER TABLE `job_run_history` DISABLE KEYS */;
+INSERT INTO `job_run_history` (`id`, `job_id`, `version`, `start_time`, `end_time`, `status`, `error_message`, `trigger_type`, `error_log`) VALUES
+	(1, 8, 1, '2014-03-14 12:00:00', '2014-03-14 12:00:01', 'success', NULL, 'cron', NULL),
+	(2, 5, 1, '2014-03-14 12:00:00', '2014-03-14 12:00:01', 'success', NULL, 'cron', NULL),
+	(3, 5, 2, '2014-03-15 12:00:00', '2014-03-15 12:00:11', 'success', NULL, 'cron', NULL),
+	(4, 8, 2, '2014-03-15 12:00:02', '2014-03-15 12:00:11', 'success', NULL, 'cron', NULL),
+	(5, 8, 3, '2014-03-17 12:00:00', '2014-03-17 12:00:02', 'success', NULL, 'cron', NULL),
+	(6, 5, 3, '2014-03-17 12:00:00', '2014-03-17 12:00:02', 'success', NULL, 'cron', NULL),
+	(7, 8, 4, '2014-03-18 12:00:00', '2014-03-18 12:00:03', 'success', NULL, 'cron', NULL),
+	(8, 5, 4, '2014-03-18 12:00:00', '2014-03-18 12:00:02', 'success', NULL, 'cron', NULL),
+	(9, 5, 5, '2014-03-19 12:00:00', '2014-03-19 12:00:02', 'success', NULL, 'cron', NULL),
+	(10, 8, 5, '2014-03-19 12:00:00', '2014-03-19 12:00:02', 'success', NULL, 'cron', NULL),
+	(11, 5, 6, '2014-03-20 12:00:00', '2014-03-20 12:00:02', 'success', NULL, 'cron', NULL),
+	(12, 8, 6, '2014-03-20 12:00:00', '2014-03-20 12:00:02', 'success', NULL, 'cron', NULL),
+	(13, 8, 7, '2014-03-21 12:00:00', '2014-03-21 12:00:01', 'success', NULL, 'cron', NULL),
+	(14, 5, 7, '2014-03-21 12:00:00', '2014-03-21 12:00:01', 'success', NULL, 'cron', NULL),
+	(15, 5, 8, '2014-03-24 12:00:04', '2014-03-24 12:00:12', 'success', NULL, 'cron', NULL),
+	(16, 8, 8, '2014-03-24 12:00:04', '2014-03-24 12:00:12', 'success', NULL, 'cron', NULL),
+	(17, 1, 1, '2014-06-11 09:30:00', '2014-06-11 09:30:01', 'success', NULL, 'cron', NULL),
+	(18, 4, 1, '2014-06-11 09:50:00', '2014-06-11 09:50:01', 'success', NULL, 'cron', NULL);
+/*!40000 ALTER TABLE `job_run_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.mix_taxonomy
+DROP TABLE IF EXISTS `mix_taxonomy`;
+CREATE TABLE IF NOT EXISTS `mix_taxonomy` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `namespace_id` int(11) DEFAULT NULL,
+  `dimension` varchar(100) DEFAULT NULL,
+  `type` int(11) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `need_mapping` tinyint(1) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.mix_taxonomy: ~48 rows (approximately)
+/*!40000 ALTER TABLE `mix_taxonomy` DISABLE KEYS */;
+INSERT INTO `mix_taxonomy` (`id`, `name`, `namespace_id`, `dimension`, `type`, `description`, `need_mapping`) VALUES
+	(1, 'AdministrativeExpense', 1, NULL, 3, NULL, 1),
+	(2, 'Assets', 3, NULL, 1, 'All outstanding principals due for all outstanding client loans. This includes current, delinquent, and renegotiated loans, but not loans that have been written off. It does not include interest receivable.', 1),
+	(3, 'Assets', 3, 'MaturityDimension:LessThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', 1),
+	(4, 'Assets', 3, 'MaturityDimension:MoreThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', 1),
+	(5, 'CashAndCashEquivalents', 1, NULL, 1, NULL, 1),
+	(6, 'Deposits', 3, NULL, 1, 'The total value of funds placed in an account with an MFI that are payable to a depositor. This item includes any current, checking, or savings accounts that are payable on demand. It also includes time deposits which have a fixed maturity date and compulsory deposits.', 1),
+	(7, 'Deposits', 3, 'DepositProductsDimension:CompulsoryMember', 1, 'The value of deposits that an MFI\'s clients are required to  maintain as a condition of an existing or future loan.', NULL),
+	(8, 'Deposits', 3, 'DepositProductsDimension:VoluntaryMember', 1, 'The value of deposits that an MFI\'s clients are not required to  maintain as a condition of an existing or future loan.', NULL),
+	(9, 'Deposits', 3, 'LocationDimension:RuralMember', 1, 'Located in rural areas. Segmentation based on location.', NULL),
+	(10, 'Deposits', 3, 'LocationDimension:UrbanMember', 1, 'Located in urban areas. Segmentation based on location.', NULL),
+	(11, 'Deposits', 3, 'MaturityDimension:LessThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', NULL),
+	(12, 'Deposits', 3, 'MaturityDimension:MoreThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', NULL),
+	(13, 'EmployeeBenefitsExpense', 1, NULL, 3, NULL, NULL),
+	(14, 'Equity', 1, NULL, 1, NULL, NULL),
+	(15, 'Expense', 1, NULL, 3, NULL, NULL),
+	(16, 'FinancialExpense', 3, NULL, 3, 'All costs All costs incurred in raising funds from third parties, fee expenses from non-financial services, net gains (losses) due to changes in fair value of financial liabilities, impairment losses net of reversals of financial assets other than loan portfolio and net gains (losses) from restatement of financial statements in terms of the measuring unit current at the end of the reporting period.', NULL),
+	(17, 'FinancialRevenueOnLoans', 3, NULL, 2, 'Interest and non-interest income generated by the provision of credit services to the clients. Fees and commissions for late payment are also included.', NULL),
+	(18, 'ImpairmentLossAllowanceGrossLoanPortfolio', 3, NULL, 2, 'An allowance for the risk of losses in the gross loan portfolio due to default .', NULL),
+	(19, 'Liabilities', 1, NULL, 1, NULL, NULL),
+	(20, 'Liabilities', 3, 'MaturityDimension:LessThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', NULL),
+	(21, 'Liabilities', 3, 'MaturityDimension:MoreThanOneYearMember', 1, 'Segmentation based on the life of an asset or liability.', NULL),
+	(22, 'LoanPortfolioGross', 3, NULL, 2, 'All outstanding principals due for all outstanding client loans. This includes current, delinquent, and renegotiated loans, but not loans that have been written off. It does not include interest receivable.', NULL),
+	(23, 'LoanPortfolioGross', 3, 'CreditProductsDimension:MicroenterpriseMember', 2, 'Loans that finance the production or trade of goods and  services for an individual\'s microenterprise, whether or not the microenterprise is legally registered. Segmentation based on loan product.', NULL),
+	(24, 'LoanPortfolioGross', 3, 'DelinquencyDimension:OneMonthOrMoreMember', 2, 'Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal  past due or renegotiated. Segmentation based on the  principal balance of all loans outstanding that have one or  more installments of principal past due or renegotiated.', NULL),
+	(25, 'LoanPortfolioGross', 3, 'DelinquencyDimension:ThreeMonthsOrMoreMember', 2, 'Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal  past due or renegotiated.? Segmentation based on the  principal balance of all loans outstanding that have one or  more installments of principal past due or renegotiated.', NULL),
+	(26, 'LoanPortfolioGross', 3, 'LocationDimension:RuralMember', 2, 'Located in rural areas. Segmentation based on geographic location.', NULL),
+	(27, 'LoanPortfolioGross', 3, 'LocationDimension:UrbanMember', 2, 'Located in urbal areas. Segmentation based on geographic location.', NULL),
+	(28, 'LoanPortfolioGross', 3, 'MaturityDimension:LessThanOneYearMember', 2, 'Segmentation based on the life of an asset or liability.', NULL),
+	(29, 'LoanPortfolioGross', 3, 'MaturityDimension:MoreThanOneYearMember', 2, 'Segmentation based on the life of an asset or liability.', NULL),
+	(30, 'NetLoanLoss', 3, '', 3, 'Referred to the value of delinquency loans written off net of any principal recovery.', NULL),
+	(31, 'NetLoanLossProvisionExpense', 3, NULL, 3, 'Represent the net value of loan portfolio impairment loss considering any reversal on impairment loss and any recovery on loans written off recognized as a income during the accounting period.', NULL),
+	(32, 'NetOperatingIncome', 3, NULL, 2, 'Total operating revenue less all expenses related to the MFI\'s core financial service operation including total financial expense, impairment loss and operating expense. Donations are excluded.', NULL),
+	(33, 'NetOperatingIncomeNetOfTaxExpense', 3, NULL, 3, 'Net operating income reported incorporating the effect of taxes. Taxes include all domestic and foreign taxes which are based on taxable profits, other taxes related to personnel, financial transactions or value-added taxes are not considered in calculation of this value.', NULL),
+	(34, 'NumberOfActiveBorrowers', 3, NULL, 0, 'The number of individuals who currently have an outstanding loan balance with the MFI or are primarily responsible for repaying any portion of the gross loan portfolio. Individuals who have multiple loans with an MFI should be counted as a single borrower.', NULL),
+	(35, 'NumberOfActiveBorrowers', 3, 'GenderDimension:FemaleMember', 0, 'The number of individuals who currently have an outstanding loan balance with the MFI or are primarily responsible for repaying any portion of the gross loan portfolio. Individuals who have multiple loans with an MFI should be counted as a single borrower.', NULL),
+	(36, 'NumberOfBoardMembers', 3, 'GenderDimension:FemaleMember', 0, 'The number of members that comprise the board of directors at the end of the reporting period who are female.', NULL),
+	(37, 'NumberOfDepositAccounts', 3, NULL, 0, 'The number of individuals who currently have funds on deposit with the MFI on a voluntary basis; i.e., they are not required to maintain the deposit account to access a loan. This number applies only to deposits held by an MFI, not to those deposits held in other institutions by the MFI\'s clients. The number should be based on the number of individuals rather than the number of groups. A single deposit account may represent multiple depositors.', NULL),
+	(38, 'NumberOfDepositors', 3, '', 0, 'The number of deposit accounts, both voluntary and compulsory, opened at the MFI whose balances the institution is liable to repay. The number should be based on the number of individual accounts rather than on the number of groups.', NULL),
+	(39, 'NumberOfEmployees', 3, NULL, 0, 'The number of individuals who are actively employed by an entity. This number includes contract employees or advisors who dedicate a substantial portion of their time to the entity, even if they are not on the entity\'s employees roster.', NULL),
+	(40, 'NumberOfEmployees', 3, 'GenderDimension:FemaleMember', 0, 'The number of individuals who are actively employed by an entity. This number includes contract employees or advisors who dedicate a substantial portion of their time to the entity, even if they are not on the entity\'s employees roster.', NULL),
+	(41, 'NumberOfLoanOfficers', 3, NULL, 0, 'The number of employees whose main activity is to manage a portion of the gross loan portfolio. A loan officer is a staff member of record who is directly responsible for arranging and monitoring client loans.', NULL),
+	(42, 'NumberOfLoanOfficers', 3, 'GenderDimension:FemaleMember', 0, 'The number of employees whose main activity is to manage a portion of the gross loan portfolio. A loan officer is a staff member of record who is directly responsible for arranging and monitoring client loans.', NULL),
+	(43, 'NumberOfManagers', 3, 'GenderDimension:FemaleMember', 0, 'The number of members that comprise the management of the institution who are female.', NULL),
+	(44, 'NumberOfOffices', 3, NULL, 0, 'The number of staffed points of service and administrative sites used to deliver or support the delivery of financial services to microfinance clients.', NULL),
+	(45, 'NumberOfOutstandingLoans', 3, NULL, 0, 'The number of loans in the gross loan portfolio. For MFIs using a group lending methodology, the number of loans should refer to the number of individuals receiving loans as part of a group or as part of a group loan.', NULL),
+	(46, 'OperatingExpense', 3, NULL, 3, 'Includes expenses not related to financial and credit loss impairment, such as personnel expenses, depreciation, amortization and administrative expenses.', NULL),
+	(47, 'OperatingIncome', 3, NULL, 2, 'Includes all financial income and other operating revenue which is generated from non-financial services. Operating income also includes net gains (losses) from holding financial assets (changes on their values during the period and foreign exchange differences). Donations or any revenue not related with an MFI\'s core business of making loans and providing financial services are not considered under this category.', NULL),
+	(48, 'WriteOffsOnGrossLoanPortfolio', 3, NULL, 2, 'The value of loans that have been recognized as uncollectible for accounting purposes. A write-off is an accounting procedure that removes the outstanding balance of the loan from the gross loan portfolio and impairment loss allowance. Thus, the write-off does not affect the net loan portfolio, total assets, or any equity account. If the impairment loss allowance is insufficient to cover the amount written off, the excess amount will result in an additional impairment loss on loans recognised in profit or loss of the period.', NULL);
+/*!40000 ALTER TABLE `mix_taxonomy` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.mix_taxonomy_mapping
+DROP TABLE IF EXISTS `mix_taxonomy_mapping`;
+CREATE TABLE IF NOT EXISTS `mix_taxonomy_mapping` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `identifier` varchar(50) NOT NULL DEFAULT '',
+  `config` varchar(200) DEFAULT NULL,
+  `last_update_date` datetime DEFAULT NULL,
+  `currency` varchar(11) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.mix_taxonomy_mapping: ~1 rows (approximately)
+/*!40000 ALTER TABLE `mix_taxonomy_mapping` DISABLE KEYS */;
+INSERT INTO `mix_taxonomy_mapping` (`id`, `identifier`, `config`, `last_update_date`, `currency`) VALUES
+	(1, 'default', NULL, NULL, '');
+/*!40000 ALTER TABLE `mix_taxonomy_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.mix_xbrl_namespace
+DROP TABLE IF EXISTS `mix_xbrl_namespace`;
+CREATE TABLE IF NOT EXISTS `mix_xbrl_namespace` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `prefix` varchar(20) NOT NULL DEFAULT '',
+  `url` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UNQUE` (`prefix`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.mix_xbrl_namespace: ~7 rows (approximately)
+/*!40000 ALTER TABLE `mix_xbrl_namespace` DISABLE KEYS */;
+INSERT INTO `mix_xbrl_namespace` (`id`, `prefix`, `url`) VALUES
+	(1, 'ifrs', 'http://xbrl.iasb.org/taxonomy/2009-04-01/ifrs'),
+	(2, 'iso4217', 'http://www.xbrl.org/2003/iso4217'),
+	(3, 'mix', 'http://www.themix.org/int/fr/ifrs/basi/YYYY-MM-DD/mx-cor'),
+	(4, 'xbrldi', 'http://xbrl.org/2006/xbrldi'),
+	(5, 'xbrli', 'http://www.xbrl.org/2003/instance'),
+	(6, 'link', 'http://www.xbrl.org/2003/linkbase'),
+	(7, 'dc-all', 'http://www.themix.org/int/fr/ifrs/basi/2010-08-31/dc-all');
+/*!40000 ALTER TABLE `mix_xbrl_namespace` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_account_transfer_details
+DROP TABLE IF EXISTS `m_account_transfer_details`;
+CREATE TABLE IF NOT EXISTS `m_account_transfer_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) NOT NULL,
+  `to_office_id` bigint(20) NOT NULL,
+  `from_client_id` bigint(20) DEFAULT NULL,
+  `to_client_id` bigint(20) DEFAULT NULL,
+  `from_savings_account_id` bigint(20) DEFAULT NULL,
+  `to_savings_account_id` bigint(20) DEFAULT NULL,
+  `from_loan_account_id` bigint(20) DEFAULT NULL,
+  `to_loan_account_id` bigint(20) DEFAULT NULL,
+  `transfer_type` smallint(2) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_account_transfer_details_from_office` (`from_office_id`),
+  KEY `FK_m_account_transfer_details_to_office` (`to_office_id`),
+  KEY `FK_m_account_transfer_details_from_client` (`from_client_id`),
+  KEY `FK_m_account_transfer_details_to_client` (`to_client_id`),
+  KEY `FK_m_account_transfer_details_from_savings_account` (`from_savings_account_id`),
+  KEY `FK_m_account_transfer_details_to_savings_account` (`to_savings_account_id`),
+  KEY `FK_m_account_transfer_details_from_loan_account` (`from_loan_account_id`),
+  KEY `FK_m_account_transfer_details_to_loan_account` (`to_loan_account_id`),
+  CONSTRAINT `FK_m_account_transfer_details_from_client` FOREIGN KEY (`from_client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_from_loan_account` FOREIGN KEY (`from_loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_from_office` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_from_savings_account` FOREIGN KEY (`from_savings_account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_to_client` FOREIGN KEY (`to_client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_to_loan_account` FOREIGN KEY (`to_loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_to_office` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_account_transfer_details_to_savings_account` FOREIGN KEY (`to_savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_account_transfer_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_account_transfer_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_account_transfer_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_account_transfer_standing_instructions
+DROP TABLE IF EXISTS `m_account_transfer_standing_instructions`;
+CREATE TABLE IF NOT EXISTS `m_account_transfer_standing_instructions` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(250) NOT NULL,
+  `account_transfer_details_id` bigint(20) NOT NULL,
+  `priority` tinyint(2) NOT NULL,
+  `status` tinyint(2) NOT NULL,
+  `instruction_type` tinyint(2) NOT NULL,
+  `amount` decimal(19,6) DEFAULT NULL,
+  `valid_from` date NOT NULL,
+  `valid_till` date DEFAULT NULL,
+  `recurrence_type` tinyint(1) NOT NULL,
+  `recurrence_frequency` smallint(5) DEFAULT NULL,
+  `recurrence_interval` smallint(5) DEFAULT NULL,
+  `recurrence_on_day` smallint(2) DEFAULT NULL,
+  `recurrence_on_month` smallint(2) DEFAULT NULL,
+  `last_run_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`),
+  KEY `FK_m_standing_instructions_account_transfer_details` (`account_transfer_details_id`),
+  CONSTRAINT `FK_m_standing_instructions_account_transfer_details` FOREIGN KEY (`account_transfer_details_id`) REFERENCES `m_account_transfer_details` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_account_transfer_standing_instructions: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_account_transfer_standing_instructions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_account_transfer_standing_instructions` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_account_transfer_standing_instructions_history
+DROP TABLE IF EXISTS `m_account_transfer_standing_instructions_history`;
+CREATE TABLE IF NOT EXISTS `m_account_transfer_standing_instructions_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `standing_instruction_id` bigint(20) NOT NULL,
+  `status` varchar(20) NOT NULL,
+  `execution_time` datetime NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `error_log` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_account_transfer_standing_instructions_history` (`standing_instruction_id`),
+  CONSTRAINT `FK_m_account_transfer_standing_instructions_m_history` FOREIGN KEY (`standing_instruction_id`) REFERENCES `m_account_transfer_standing_instructions` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_account_transfer_standing_instructions_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_account_transfer_standing_instructions_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_account_transfer_standing_instructions_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_account_transfer_transaction
+DROP TABLE IF EXISTS `m_account_transfer_transaction`;
+CREATE TABLE IF NOT EXISTS `m_account_transfer_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_transfer_details_id` bigint(20) NOT NULL,
+  `from_savings_transaction_id` bigint(20) DEFAULT NULL,
+  `from_loan_transaction_id` bigint(20) DEFAULT NULL,
+  `to_savings_transaction_id` bigint(20) DEFAULT NULL,
+  `to_loan_transaction_id` bigint(20) DEFAULT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `description` varchar(200) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_account_transfer_transaction_from_m_savings_transaction` (`from_savings_transaction_id`),
+  KEY `FK_m_account_transfer_transaction_to_m_savings_transaction` (`to_savings_transaction_id`),
+  KEY `FK_m_account_transfer_transaction_to_m_loan_transaction` (`to_loan_transaction_id`),
+  KEY `FK_m_account_transfer_transaction_from_m_loan_transaction` (`from_loan_transaction_id`),
+  KEY `FK_m_account_transfer_transaction_account_detail` (`account_transfer_details_id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_account_detail` FOREIGN KEY (`account_transfer_details_id`) REFERENCES `m_account_transfer_details` (`id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_from_m_loan_transaction` FOREIGN KEY (`from_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_from_m_savings_transaction` FOREIGN KEY (`from_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_to_m_loan_transaction` FOREIGN KEY (`to_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK_m_account_transfer_transaction_to_m_savings_transaction` FOREIGN KEY (`to_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_account_transfer_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_account_transfer_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_account_transfer_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_appuser
+DROP TABLE IF EXISTS `m_appuser`;
+CREATE TABLE IF NOT EXISTS `m_appuser` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `username` varchar(100) NOT NULL,
+  `firstname` varchar(100) NOT NULL,
+  `lastname` varchar(100) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `email` varchar(100) NOT NULL,
+  `firsttime_login_remaining` bit(1) NOT NULL,
+  `nonexpired` bit(1) NOT NULL,
+  `nonlocked` bit(1) NOT NULL,
+  `nonexpired_credentials` bit(1) NOT NULL,
+  `enabled` bit(1) NOT NULL,
+  `last_time_password_updated` date NOT NULL,
+  `password_never_expires` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'define if the password, should be check for validity period or not',
+  `is_self_service_user` bit(1) NOT NULL DEFAULT b'0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username_org` (`username`),
+  KEY `FKB3D587CE0DD567A` (`office_id`),
+  KEY `fk_m_appuser_002x` (`staff_id`),
+  KEY `last_time_password_updated` (`last_time_password_updated`),
+  CONSTRAINT `FKB3D587CE0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `fk_m_appuser_002` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_appuser: ~5 rows (approximately)
+/*!40000 ALTER TABLE `m_appuser` DISABLE KEYS */;
+INSERT INTO `m_appuser` (`id`, `is_deleted`, `office_id`, `staff_id`, `username`, `firstname`, `lastname`, `password`, `email`, `firsttime_login_remaining`, `nonexpired`, `nonlocked`, `nonexpired_credentials`, `enabled`, `last_time_password_updated`, `password_never_expires`, `is_self_service_user`) VALUES
+	(1, 0, 1, NULL, 'mifos', 'App', 'Administrator', '5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a', 'demomfi@mifos.org', b'0', b'1', b'1', b'1', b'1', '2014-03-07', 0, b'0'),
+	(2, 0, 1, NULL, 'adama', 'Adam', 'A', '08a5c6ee6e7361aa153b4c8620d31bcedef514238a69c0f30f05fe460663cbc6', 'adama@123.com', b'0', b'1', b'1', b'1', b'1', '2014-03-07', 0, b'0'),
+	(4, 0, 2, NULL, 'benb', 'Ben', 'B', '19d327dc34e2f7b889b571ad9651355e8cce87493611847c70fabf1fcfa914c8', 'benb@123.com', b'0', b'1', b'1', b'1', b'1', '2014-03-07', 0, b'0'),
+	(5, 0, 3, NULL, 'janej', 'Jane', 'J', 'f37ddcab2b9cfb015d94becdda719361ce13df943058cbf3130db468c288acb9', 'janej@123.com', b'0', b'1', b'1', b'1', b'1', '2014-03-07', 0, b'0'),
+	(6, 0, 1, NULL, 'system', 'system', 'system', '5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a', 'demomfi@mifos.org', b'0', b'1', b'1', b'1', b'1', '2014-03-07', 0, b'0');
+/*!40000 ALTER TABLE `m_appuser` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_appuser_previous_password
+DROP TABLE IF EXISTS `m_appuser_previous_password`;
+CREATE TABLE IF NOT EXISTS `m_appuser_previous_password` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `user_id` bigint(20) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `removal_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `user_id` (`user_id`),
+  CONSTRAINT `m_appuser_previous_password_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-reference.m_appuser_previous_password: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_appuser_previous_password` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_appuser_previous_password` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_appuser_role
+DROP TABLE IF EXISTS `m_appuser_role`;
+CREATE TABLE IF NOT EXISTS `m_appuser_role` (
+  `appuser_id` bigint(20) NOT NULL,
+  `role_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`appuser_id`,`role_id`),
+  KEY `FK7662CE59B4100309` (`appuser_id`),
+  KEY `FK7662CE5915CEC7AB` (`role_id`),
+  CONSTRAINT `FK7662CE5915CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`),
+  CONSTRAINT `FK7662CE59B4100309` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_appuser_role: ~4 rows (approximately)
+/*!40000 ALTER TABLE `m_appuser_role` DISABLE KEYS */;
+INSERT INTO `m_appuser_role` (`appuser_id`, `role_id`) VALUES
+	(1, 1),
+	(2, 1),
+	(4, 1),
+	(5, 1);
+/*!40000 ALTER TABLE `m_appuser_role` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_calendar
+DROP TABLE IF EXISTS `m_calendar`;
+CREATE TABLE IF NOT EXISTS `m_calendar` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `title` varchar(70) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_calendar: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_calendar` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_calendar_history
+DROP TABLE IF EXISTS `m_calendar_history`;
+CREATE TABLE IF NOT EXISTS `m_calendar_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `title` varchar(70) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `location` varchar(50) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `duration` smallint(6) DEFAULT NULL,
+  `calendar_type_enum` smallint(5) NOT NULL,
+  `repeating` tinyint(1) NOT NULL DEFAULT '0',
+  `recurrence` varchar(100) DEFAULT NULL,
+  `remind_by_enum` smallint(5) DEFAULT NULL,
+  `first_reminder` smallint(11) DEFAULT NULL,
+  `second_reminder` smallint(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_history` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_history` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-reference.m_calendar_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_calendar_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_calendar_instance
+DROP TABLE IF EXISTS `m_calendar_instance`;
+CREATE TABLE IF NOT EXISTS `m_calendar_instance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_id` bigint(20) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `entity_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`),
+  CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_calendar_instance: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_calendar_instance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_calendar_instance` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_cashiers
+DROP TABLE IF EXISTS `m_cashiers`;
+CREATE TABLE IF NOT EXISTS `m_cashiers` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `teller_id` bigint(20) DEFAULT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `start_date` date DEFAULT NULL,
+  `end_date` date DEFAULT NULL,
+  `start_time` varchar(10) DEFAULT NULL,
+  `end_time` varchar(10) DEFAULT NULL,
+  `full_day` tinyint(4) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `IK_m_cashiers_m_staff` (`staff_id`),
+  KEY `IK_m_cashiers_m_teller` (`teller_id`),
+  CONSTRAINT `FK_m_cashiers_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_m_cashiers_m_teller` FOREIGN KEY (`teller_id`) REFERENCES `m_tellers` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_cashiers: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_cashiers` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_cashiers` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_cashier_transactions
+DROP TABLE IF EXISTS `m_cashier_transactions`;
+CREATE TABLE IF NOT EXISTS `m_cashier_transactions` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `cashier_id` bigint(20) NOT NULL,
+  `txn_type` smallint(5) NOT NULL,
+  `txn_amount` decimal(19,6) NOT NULL,
+  `txn_date` date NOT NULL,
+  `created_date` datetime NOT NULL,
+  `entity_type` varchar(50) DEFAULT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `txn_note` varchar(200) DEFAULT NULL,
+  `currency_code` varchar(3) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `IK_m_teller_transactions_m_cashier` (`cashier_id`),
+  CONSTRAINT `FK_m_teller_transactions_m_cashiers` FOREIGN KEY (`cashier_id`) REFERENCES `m_cashiers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_cashier_transactions: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_cashier_transactions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_cashier_transactions` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_charge
+DROP TABLE IF EXISTS `m_charge`;
+CREATE TABLE IF NOT EXISTS `m_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `charge_applies_to_enum` smallint(5) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `charge_payment_mode_enum` smallint(5) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `fee_on_day` smallint(5) DEFAULT NULL,
+  `fee_interval` smallint(5) DEFAULT NULL,
+  `fee_on_month` smallint(5) DEFAULT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL,
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
+  `min_cap` decimal(19,6) DEFAULT NULL,
+  `max_cap` decimal(19,6) DEFAULT NULL,
+  `fee_frequency` smallint(5) DEFAULT NULL,
+  `income_or_liability_account_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`),
+  KEY `FK_m_charge_acc_gl_account` (`income_or_liability_account_id`),
+  CONSTRAINT `FK_m_charge_acc_gl_account` FOREIGN KEY (`income_or_liability_account_id`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_charge: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_charge` DISABLE KEYS */;
+INSERT INTO `m_charge` (`id`, `name`, `currency_code`, `charge_applies_to_enum`, `charge_time_enum`, `charge_calculation_enum`, `charge_payment_mode_enum`, `amount`, `fee_on_day`, `fee_interval`, `fee_on_month`, `is_penalty`, `is_active`, `is_deleted`, `min_cap`, `max_cap`, `fee_frequency`, `income_or_liability_account_id`) VALUES
+	(1, 'Processing Fee', 'USD', 1, 1, 1, 0, 500.000000, NULL, NULL, NULL, 0, 1, 0, NULL, NULL, NULL, NULL);
+/*!40000 ALTER TABLE `m_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_client
+DROP TABLE IF EXISTS `m_client`;
+CREATE TABLE IF NOT EXISTS `m_client` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '300',
+  `sub_status` int(11) DEFAULT NULL,
+  `activation_date` date DEFAULT NULL,
+  `office_joining_date` date DEFAULT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `transfer_to_office_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `middlename` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `fullname` varchar(100) DEFAULT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `mobile_no` varchar(50) DEFAULT NULL,
+  `gender_cv_id` int(11) DEFAULT NULL,
+  `date_of_birth` date DEFAULT NULL,
+  `image_id` bigint(20) DEFAULT NULL,
+  `closure_reason_cv_id` int(11) DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `updated_by` bigint(20) DEFAULT NULL,
+  `updated_on` date DEFAULT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `activatedon_userid` bigint(20) DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `default_savings_product` bigint(20) DEFAULT NULL,
+  `default_savings_account` bigint(20) DEFAULT NULL,
+  `client_type_cv_id` int(11) DEFAULT NULL,
+  `client_classification_cv_id` int(11) DEFAULT NULL,
+  `reject_reason_cv_id` int(11) DEFAULT NULL,
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `withdraw_reason_cv_id` int(11) DEFAULT NULL,
+  `withdrawn_on_date` date DEFAULT NULL,
+  `withdraw_on_userid` bigint(20) DEFAULT NULL,
+  `reactivated_on_date` date DEFAULT NULL,
+  `reactivated_on_userid` bigint(20) DEFAULT NULL,
+  `legal_form_enum` int(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `external_id` (`external_id`),
+  UNIQUE KEY `mobile_no_UNIQUE` (`mobile_no`),
+  KEY `FKCE00CAB3E0DD567A` (`office_id`),
+  KEY `FK_m_client_m_image` (`image_id`),
+  KEY `client_staff_id` (`staff_id`),
+  KEY `FK_m_client_m_code` (`closure_reason_cv_id`),
+  KEY `FK_m_client_m_office` (`transfer_to_office_id`),
+  KEY `FK_m_client_m_savings_product` (`default_savings_product`),
+  KEY `FK_m_client_m_savings_account` (`default_savings_account`),
+  KEY `FK_m_client_type_m_code_value` (`client_type_cv_id`),
+  KEY `FK_m_client_classification_m_code_value` (`client_classification_cv_id`),
+  KEY `FK1_m_client_gender_m_code_value` (`gender_cv_id`),
+  KEY `FK_m_client_substatus_m_code_value` (`sub_status`),
+  KEY `FK_m_client_type_mcode_value_reject` (`reject_reason_cv_id`),
+  KEY `FK_m_client_type_m_code_value_withdraw` (`withdraw_reason_cv_id`),
+  CONSTRAINT `FK1_m_client_gender_m_code_value` FOREIGN KEY (`gender_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FKCE00CAB3E0DD567A` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_client_classification_m_code_value` FOREIGN KEY (`client_classification_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_m_code` FOREIGN KEY (`closure_reason_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_m_image` FOREIGN KEY (`image_id`) REFERENCES `m_image` (`id`),
+  CONSTRAINT `FK_m_client_m_office` FOREIGN KEY (`transfer_to_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_client_m_savings_account` FOREIGN KEY (`default_savings_account`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `FK_m_client_m_savings_product` FOREIGN KEY (`default_savings_product`) REFERENCES `m_savings_product` (`id`),
+  CONSTRAINT `FK_m_client_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_m_client_substatus_m_code_value` FOREIGN KEY (`sub_status`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_type_mcode_value_reject` FOREIGN KEY (`reject_reason_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_type_m_code_value` FOREIGN KEY (`client_type_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_client_type_m_code_value_withdraw` FOREIGN KEY (`withdraw_reason_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_client: ~7 rows (approximately)
+/*!40000 ALTER TABLE `m_client` DISABLE KEYS */;
+INSERT INTO `m_client` (`id`, `account_no`, `external_id`, `status_enum`, `sub_status`, `activation_date`, `office_joining_date`, `office_id`, `transfer_to_office_id`, `staff_id`, `firstname`, `middlename`, `lastname`, `fullname`, `display_name`, `mobile_no`, `gender_cv_id`, `date_of_birth`, `image_id`, `closure_reason_cv_id`, `closedon_date`, `updated_by`, `updated_on`, `submittedon_date`, `submittedon_userid`, `activatedon_userid`, `closedon_userid`, `default_savings_product`, `default_savings_account`, `client_type_cv_id`, `client_classification_cv_id`, `reject_reason_cv_id`, `rejectedon_date`, `rejectedon_userid`, `withdraw_reason_cv_id`, `withdrawn_on_date`, `withdraw_on_userid`, `reactivated_on_date`, `reactivated_on_userid`, `legal_form_enum`) VALUES
+	(1, '000000001', NULL, 300, NULL, '2014-03-07', '2014-03-07', 1, NULL, 1, 'Smith', NULL, 'R', NULL, 'Smith R', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2010-01-01', 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
+	(2, '000000002', NULL, 300, NULL, '2010-01-04', '2010-01-04', 2, NULL, 2, 'Johnson', NULL, 'D', NULL, 'Johnson D', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2010-01-04', 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
+	(4, '000000004', NULL, 300, NULL, '2010-01-04', '2010-01-04', 2, NULL, 2, 'Williams', NULL, 'G', NULL, 'Williams G', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2010-01-04', 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
+	(5, '000000005', NULL, 300, NULL, '2010-01-04', '2010-01-04', 2, NULL, 2, 'Harris', NULL, 'P', NULL, 'Harris P', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2010-01-04', 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
+	(6, '000000006', NULL, 300, NULL, '2010-01-04', '2010-01-04', 2, NULL, 2, 'Allen', NULL, 'E', NULL, 'Allen E', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2010-01-04', 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
+	(7, '000000007', NULL, 300, NULL, '2010-01-04', '2010-01-04', 2, NULL, 2, 'Scott', NULL, 'C', NULL, 'Scott C', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2010-01-04', 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
+	(8, '000000008', NULL, 300, NULL, '2010-01-04', '2010-01-04', 2, NULL, 2, 'Robinson', NULL, 'R', NULL, 'Robinson R', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2010-01-04', 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+/*!40000 ALTER TABLE `m_client` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_client_attendance
+DROP TABLE IF EXISTS `m_client_attendance`;
+CREATE TABLE IF NOT EXISTS `m_client_attendance` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL DEFAULT '0',
+  `meeting_id` bigint(20) NOT NULL,
+  `attendance_type_enum` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_client_meeting_attendance` (`client_id`,`meeting_id`),
+  KEY `FK_m_meeting_m_client_attendance` (`meeting_id`),
+  CONSTRAINT `FK_m_client_m_client_attendance` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_meeting_m_client_attendance` FOREIGN KEY (`meeting_id`) REFERENCES `m_meeting` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_client_attendance: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_attendance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_attendance` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_client_charge
+DROP TABLE IF EXISTS `m_client_charge`;
+CREATE TABLE IF NOT EXISTS `m_client_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL,
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_due_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL,
+  `is_paid_derived` tinyint(1) DEFAULT NULL,
+  `waived` tinyint(1) DEFAULT NULL,
+  `is_active` tinyint(1) DEFAULT NULL,
+  `inactivated_on_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_client_charge_m_client` (`client_id`),
+  KEY `FK_m_client_charge_m_charge` (`charge_id`),
+  CONSTRAINT `FK_m_client_charge_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `FK_m_client_charge_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_client_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_client_charge_paid_by
+DROP TABLE IF EXISTS `m_client_charge_paid_by`;
+CREATE TABLE IF NOT EXISTS `m_client_charge_paid_by` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_transaction_id` bigint(20) NOT NULL,
+  `client_charge_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_client_charge_paid_by_m_client_transaction` (`client_transaction_id`),
+  KEY `FK_m_client_charge_paid_by_m_client_charge` (`client_charge_id`),
+  CONSTRAINT `FK_m_client_charge_paid_by_m_client_charge` FOREIGN KEY (`client_charge_id`) REFERENCES `m_client_charge` (`id`),
+  CONSTRAINT `FK_m_client_charge_paid_by_m_client_transaction` FOREIGN KEY (`client_transaction_id`) REFERENCES `m_client_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_client_charge_paid_by: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_charge_paid_by` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_charge_paid_by` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_client_identifier
+DROP TABLE IF EXISTS `m_client_identifier`;
+CREATE TABLE IF NOT EXISTS `m_client_identifier` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `document_type_id` int(11) NOT NULL,
+  `document_key` varchar(50) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_identifier_key` (`document_type_id`,`document_key`),
+  UNIQUE KEY `unique_client_identifier` (`client_id`,`document_type_id`),
+  KEY `FK_m_client_document_m_client` (`client_id`),
+  KEY `FK_m_client_document_m_code_value` (`document_type_id`),
+  CONSTRAINT `FK_m_client_document_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK_m_client_document_m_code_value` FOREIGN KEY (`document_type_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_client_identifier: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_identifier` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_identifier` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_client_non_person
+DROP TABLE IF EXISTS `m_client_non_person`;
+CREATE TABLE IF NOT EXISTS `m_client_non_person` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `constitution_cv_id` int(11) NOT NULL,
+  `incorp_no` varchar(50) DEFAULT NULL,
+  `incorp_validity_till` datetime DEFAULT NULL,
+  `main_business_line_cv_id` int(11) DEFAULT NULL,
+  `remarks` varchar(150) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `client_id` (`client_id`),
+  KEY `FK_client_id` (`client_id`),
+  CONSTRAINT `FK_client_id` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_client_non_person: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_non_person` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_non_person` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_client_transaction
+DROP TABLE IF EXISTS `m_client_transaction`;
+CREATE TABLE IF NOT EXISTS `m_client_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `payment_detail_id` bigint(20) DEFAULT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `external_id` varchar(50) DEFAULT NULL,
+  `transaction_date` date NOT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `appuser_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `external_id` (`external_id`),
+  KEY `FK_m_client_transaction_m_client` (`client_id`),
+  KEY `FK_m_client_transaction_m_appuser` (`appuser_id`),
+  CONSTRAINT `FK_m_client_transaction_m_appuser` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_client_transaction_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_client_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_client_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_client_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_code
+DROP TABLE IF EXISTS `m_code`;
+CREATE TABLE IF NOT EXISTS `m_code` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_name` varchar(100) DEFAULT NULL,
+  `is_system_defined` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_name` (`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_code: ~24 rows (approximately)
+/*!40000 ALTER TABLE `m_code` DISABLE KEYS */;
+INSERT INTO `m_code` (`id`, `code_name`, `is_system_defined`) VALUES
+	(1, 'Customer Identifier', 1),
+	(2, 'LoanCollateral', 1),
+	(3, 'LoanPurpose', 1),
+	(4, 'Gender', 1),
+	(5, 'YesNo', 1),
+	(6, 'GuarantorRelationship', 1),
+	(7, 'AssetAccountTags', 1),
+	(8, 'LiabilityAccountTags', 1),
+	(9, 'EquityAccountTags', 1),
+	(10, 'IncomeAccountTags', 1),
+	(11, 'ExpenseAccountTags', 1),
+	(13, 'GROUPROLE', 1),
+	(14, 'ClientClosureReason', 1),
+	(15, 'GroupClosureReason', 1),
+	(16, 'ClientType', 1),
+	(17, 'ClientClassification', 1),
+	(18, 'ClientSubStatus', 1),
+	(19, 'ClientRejectReason', 1),
+	(20, 'ClientWithdrawReason', 1),
+	(21, 'Entity to Entity Access Types', 1),
+	(22, 'CenterClosureReason', 1),
+	(23, 'LoanRescheduleReason', 1),
+	(24, 'Constitution', 1),
+	(25, 'Main Business Line', 1);
+/*!40000 ALTER TABLE `m_code` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_code_value
+DROP TABLE IF EXISTS `m_code_value`;
+CREATE TABLE IF NOT EXISTS `m_code_value` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code_id` int(11) NOT NULL,
+  `code_value` varchar(100) DEFAULT NULL,
+  `code_description` varchar(500) DEFAULT NULL,
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  `code_score` int(11) DEFAULT NULL,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code_value` (`code_id`,`code_value`),
+  KEY `FKCFCEA42640BE071Z` (`code_id`),
+  CONSTRAINT `FKCFCEA42640BE071Z` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_code_value: ~26 rows (approximately)
+/*!40000 ALTER TABLE `m_code_value` DISABLE KEYS */;
+INSERT INTO `m_code_value` (`id`, `code_id`, `code_value`, `code_description`, `order_position`, `code_score`, `is_active`) VALUES
+	(1, 1, 'Passport Id', NULL, 1, NULL, 1),
+	(2, 1, 'Government Id', NULL, 2, NULL, 1),
+	(3, 1, 'Drivers License', NULL, 3, NULL, 1),
+	(4, 1, 'Any Other Id Type', NULL, 4, NULL, 1),
+	(5, 6, 'Spouse', NULL, 0, NULL, 1),
+	(6, 6, 'Parent', NULL, 0, NULL, 1),
+	(7, 6, 'Sibling', NULL, 0, NULL, 1),
+	(8, 6, 'Business Associate', NULL, 0, NULL, 1),
+	(9, 6, 'Other', NULL, 0, NULL, 1),
+	(10, 3, 'Cattle Rearing', NULL, 104, NULL, 1),
+	(11, 3, 'Others', NULL, 105, NULL, 1),
+	(12, 3, 'Tailoring Shop', NULL, 101, NULL, 1),
+	(13, 3, 'Small Provisions Store', NULL, 102, NULL, 1),
+	(14, 3, 'Agriculture', NULL, 103, NULL, 1),
+	(15, 14, 'Blacklisted', NULL, 1, NULL, 1),
+	(16, 14, 'Deceased', NULL, 2, NULL, 1),
+	(17, 14, 'Transferred', NULL, 3, NULL, 1),
+	(18, 14, 'Left', NULL, 4, NULL, 1),
+	(19, 14, 'others', NULL, 5, NULL, 1),
+	(20, 1, 'Voter ID', NULL, 6, NULL, 1),
+	(21, 1, 'Ration Card', NULL, 5, NULL, 1),
+	(22, 4, 'Male', NULL, 1, NULL, 1),
+	(24, 4, 'Female', NULL, 2, NULL, 1),
+	(25, 21, 'Office Access to Loan Products', NULL, 0, NULL, 1),
+	(26, 21, 'Office Access to Savings Products', NULL, 0, NULL, 1),
+	(27, 21, 'Office Access to Fees/Charges', NULL, 0, NULL, 1);
+/*!40000 ALTER TABLE `m_code_value` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_currency
+DROP TABLE IF EXISTS `m_currency`;
+CREATE TABLE IF NOT EXISTS `m_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_currency: ~163 rows (approximately)
+/*!40000 ALTER TABLE `m_currency` DISABLE KEYS */;
+INSERT INTO `m_currency` (`id`, `code`, `decimal_places`, `currency_multiplesof`, `display_symbol`, `name`, `internationalized_name_code`) VALUES
+	(1, 'AED', 2, NULL, NULL, 'UAE Dirham', 'currency.AED'),
+	(2, 'AFN', 2, NULL, NULL, 'Afghanistan Afghani', 'currency.AFN'),
+	(3, 'ALL', 2, NULL, NULL, 'Albanian Lek', 'currency.ALL'),
+	(4, 'AMD', 2, NULL, NULL, 'Armenian Dram', 'currency.AMD'),
+	(5, 'ANG', 2, NULL, NULL, 'Netherlands Antillian Guilder', 'currency.ANG'),
+	(6, 'AOA', 2, NULL, NULL, 'Angolan Kwanza', 'currency.AOA'),
+	(7, 'ARS', 2, NULL, '$', 'Argentine Peso', 'currency.ARS'),
+	(8, 'AUD', 2, NULL, 'A$', 'Australian Dollar', 'currency.AUD'),
+	(9, 'AWG', 2, NULL, NULL, 'Aruban Guilder', 'currency.AWG'),
+	(10, 'AZM', 2, NULL, NULL, 'Azerbaijanian Manat', 'currency.AZM'),
+	(11, 'BAM', 2, NULL, NULL, 'Bosnia and Herzegovina Convertible Marks', 'currency.BAM'),
+	(12, 'BBD', 2, NULL, NULL, 'Barbados Dollar', 'currency.BBD'),
+	(13, 'BDT', 2, NULL, NULL, 'Bangladesh Taka', 'currency.BDT'),
+	(14, 'BGN', 2, NULL, NULL, 'Bulgarian Lev', 'currency.BGN'),
+	(15, 'BHD', 3, NULL, NULL, 'Bahraini Dinar', 'currency.BHD'),
+	(16, 'BIF', 0, NULL, NULL, 'Burundi Franc', 'currency.BIF'),
+	(17, 'BMD', 2, NULL, NULL, 'Bermudian Dollar', 'currency.BMD'),
+	(18, 'BND', 2, NULL, 'B$', 'Brunei Dollar', 'currency.BND'),
+	(19, 'BOB', 2, NULL, 'Bs.', 'Bolivian Boliviano', 'currency.BOB'),
+	(20, 'BRL', 2, NULL, 'R$', 'Brazilian Real', 'currency.BRL'),
+	(21, 'BSD', 2, NULL, NULL, 'Bahamian Dollar', 'currency.BSD'),
+	(22, 'BTN', 2, NULL, NULL, 'Bhutan Ngultrum', 'currency.BTN'),
+	(23, 'BWP', 2, NULL, NULL, 'Botswana Pula', 'currency.BWP'),
+	(24, 'BYR', 0, NULL, NULL, 'Belarussian Ruble', 'currency.BYR'),
+	(25, 'BZD', 2, NULL, 'BZ$', 'Belize Dollar', 'currency.BZD'),
+	(26, 'CAD', 2, NULL, NULL, 'Canadian Dollar', 'currency.CAD'),
+	(27, 'CDF', 2, NULL, NULL, 'Franc Congolais', 'currency.CDF'),
+	(28, 'CHF', 2, NULL, NULL, 'Swiss Franc', 'currency.CHF'),
+	(29, 'CLP', 0, NULL, '$', 'Chilean Peso', 'currency.CLP'),
+	(30, 'CNY', 2, NULL, NULL, 'Chinese Yuan Renminbi', 'currency.CNY'),
+	(31, 'COP', 2, NULL, '$', 'Colombian Peso', 'currency.COP'),
+	(32, 'CRC', 2, NULL, '?', 'Costa Rican Colon', 'currency.CRC'),
+	(33, 'CSD', 2, NULL, NULL, 'Serbian Dinar', 'currency.CSD'),
+	(34, 'CUP', 2, NULL, '$MN', 'Cuban Peso', 'currency.CUP'),
+	(35, 'CVE', 2, NULL, NULL, 'Cape Verde Escudo', 'currency.CVE'),
+	(36, 'CYP', 2, NULL, NULL, 'Cyprus Pound', 'currency.CYP'),
+	(37, 'CZK', 2, NULL, NULL, 'Czech Koruna', 'currency.CZK'),
+	(38, 'DJF', 0, NULL, NULL, 'Djibouti Franc', 'currency.DJF'),
+	(39, 'DKK', 2, NULL, NULL, 'Danish Krone', 'currency.DKK'),
+	(40, 'DOP', 2, NULL, 'RD$', 'Dominican Peso', 'currency.DOP'),
+	(41, 'DZD', 2, NULL, NULL, 'Algerian Dinar', 'currency.DZD'),
+	(42, 'EEK', 2, NULL, NULL, 'Estonian Kroon', 'currency.EEK'),
+	(43, 'EGP', 2, NULL, NULL, 'Egyptian Pound', 'currency.EGP'),
+	(44, 'ERN', 2, NULL, NULL, 'Eritrea Nafka', 'currency.ERN'),
+	(45, 'ETB', 2, NULL, NULL, 'Ethiopian Birr', 'currency.ETB'),
+	(46, 'EUR', 2, NULL, '€', 'Euro', 'currency.EUR'),
+	(47, 'FJD', 2, NULL, NULL, 'Fiji Dollar', 'currency.FJD'),
+	(48, 'FKP', 2, NULL, NULL, 'Falkland Islands Pound', 'currency.FKP'),
+	(49, 'GBP', 2, NULL, NULL, 'Pound Sterling', 'currency.GBP'),
+	(50, 'GEL', 2, NULL, NULL, 'Georgian Lari', 'currency.GEL'),
+	(51, 'GHC', 2, NULL, 'GHc', 'Ghana Cedi', 'currency.GHC'),
+	(52, 'GIP', 2, NULL, NULL, 'Gibraltar Pound', 'currency.GIP'),
+	(53, 'GMD', 2, NULL, NULL, 'Gambian Dalasi', 'currency.GMD'),
+	(54, 'GNF', 0, NULL, NULL, 'Guinea Franc', 'currency.GNF'),
+	(55, 'GTQ', 2, NULL, 'Q', 'Guatemala Quetzal', 'currency.GTQ'),
+	(56, 'GYD', 2, NULL, NULL, 'Guyana Dollar', 'currency.GYD'),
+	(57, 'HKD', 2, NULL, NULL, 'Hong Kong Dollar', 'currency.HKD'),
+	(58, 'HNL', 2, NULL, 'L', 'Honduras Lempira', 'currency.HNL'),
+	(59, 'HRK', 2, NULL, NULL, 'Croatian Kuna', 'currency.HRK'),
+	(60, 'HTG', 2, NULL, 'G', 'Haiti Gourde', 'currency.HTG'),
+	(61, 'HUF', 2, NULL, NULL, 'Hungarian Forint', 'currency.HUF'),
+	(62, 'IDR', 2, NULL, NULL, 'Indonesian Rupiah', 'currency.IDR'),
+	(63, 'ILS', 2, NULL, NULL, 'New Israeli Shekel', 'currency.ILS'),
+	(64, 'INR', 2, NULL, '₹', 'Indian Rupee', 'currency.INR'),
+	(65, 'IQD', 3, NULL, NULL, 'Iraqi Dinar', 'currency.IQD'),
+	(66, 'IRR', 2, NULL, NULL, 'Iranian Rial', 'currency.IRR'),
+	(67, 'ISK', 0, NULL, NULL, 'Iceland Krona', 'currency.ISK'),
+	(68, 'JMD', 2, NULL, NULL, 'Jamaican Dollar', 'currency.JMD'),
+	(69, 'JOD', 3, NULL, NULL, 'Jordanian Dinar', 'currency.JOD'),
+	(70, 'JPY', 0, NULL, NULL, 'Japanese Yen', 'currency.JPY'),
+	(71, 'KES', 2, NULL, 'KSh', 'Kenyan Shilling', 'currency.KES'),
+	(72, 'KGS', 2, NULL, NULL, 'Kyrgyzstan Som', 'currency.KGS'),
+	(73, 'KHR', 2, NULL, NULL, 'Cambodia Riel', 'currency.KHR'),
+	(74, 'KMF', 0, NULL, NULL, 'Comoro Franc', 'currency.KMF'),
+	(75, 'KPW', 2, NULL, NULL, 'North Korean Won', 'currency.KPW'),
+	(76, 'KRW', 0, NULL, NULL, 'Korean Won', 'currency.KRW'),
+	(77, 'KWD', 3, NULL, NULL, 'Kuwaiti Dinar', 'currency.KWD'),
+	(78, 'KYD', 2, NULL, NULL, 'Cayman Islands Dollar', 'currency.KYD'),
+	(79, 'KZT', 2, NULL, NULL, 'Kazakhstan Tenge', 'currency.KZT'),
+	(80, 'LAK', 2, NULL, NULL, 'Lao Kip', 'currency.LAK'),
+	(81, 'LBP', 2, NULL, 'L£', 'Lebanese Pound', 'currency.LBP'),
+	(82, 'LKR', 2, NULL, NULL, 'Sri Lanka Rupee', 'currency.LKR'),
+	(83, 'LRD', 2, NULL, NULL, 'Liberian Dollar', 'currency.LRD'),
+	(84, 'LSL', 2, NULL, NULL, 'Lesotho Loti', 'currency.LSL'),
+	(85, 'LTL', 2, NULL, NULL, 'Lithuanian Litas', 'currency.LTL'),
+	(86, 'LVL', 2, NULL, NULL, 'Latvian Lats', 'currency.LVL'),
+	(87, 'LYD', 3, NULL, NULL, 'Libyan Dinar', 'currency.LYD'),
+	(88, 'MAD', 2, NULL, NULL, 'Moroccan Dirham', 'currency.MAD'),
+	(89, 'MDL', 2, NULL, NULL, 'Moldovan Leu', 'currency.MDL'),
+	(90, 'MGA', 2, NULL, NULL, 'Malagasy Ariary', 'currency.MGA'),
+	(91, 'MKD', 2, NULL, NULL, 'Macedonian Denar', 'currency.MKD'),
+	(92, 'MMK', 2, NULL, 'K', 'Myanmar Kyat', 'currency.MMK'),
+	(93, 'MNT', 2, NULL, NULL, 'Mongolian Tugrik', 'currency.MNT'),
+	(94, 'MOP', 2, NULL, NULL, 'Macau Pataca', 'currency.MOP'),
+	(95, 'MRO', 2, NULL, NULL, 'Mauritania Ouguiya', 'currency.MRO'),
+	(96, 'MTL', 2, NULL, NULL, 'Maltese Lira', 'currency.MTL'),
+	(97, 'MUR', 2, NULL, NULL, 'Mauritius Rupee', 'currency.MUR'),
+	(98, 'MVR', 2, NULL, NULL, 'Maldives Rufiyaa', 'currency.MVR'),
+	(99, 'MWK', 2, NULL, NULL, 'Malawi Kwacha', 'currency.MWK'),
+	(100, 'MXN', 2, NULL, '$', 'Mexican Peso', 'currency.MXN'),
+	(101, 'MYR', 2, NULL, NULL, 'Malaysian Ringgit', 'currency.MYR'),
+	(102, 'MZM', 2, NULL, NULL, 'Mozambique Metical', 'currency.MZM'),
+	(103, 'NAD', 2, NULL, NULL, 'Namibia Dollar', 'currency.NAD'),
+	(104, 'NGN', 2, NULL, NULL, 'Nigerian Naira', 'currency.NGN'),
+	(105, 'NIO', 2, NULL, 'C$', 'Nicaragua Cordoba Oro', 'currency.NIO'),
+	(106, 'NOK', 2, NULL, NULL, 'Norwegian Krone', 'currency.NOK'),
+	(107, 'NPR', 2, NULL, NULL, 'Nepalese Rupee', 'currency.NPR'),
+	(108, 'NZD', 2, NULL, NULL, 'New Zealand Dollar', 'currency.NZD'),
+	(109, 'OMR', 3, NULL, NULL, 'Rial Omani', 'currency.OMR'),
+	(110, 'PAB', 2, NULL, 'B/.', 'Panama Balboa', 'currency.PAB'),
+	(111, 'PEN', 2, NULL, 'S/.', 'Peruvian Nuevo Sol', 'currency.PEN'),
+	(112, 'PGK', 2, NULL, NULL, 'Papua New Guinea Kina', 'currency.PGK'),
+	(113, 'PHP', 2, NULL, NULL, 'Philippine Peso', 'currency.PHP'),
+	(114, 'PKR', 2, NULL, NULL, 'Pakistan Rupee', 'currency.PKR'),
+	(115, 'PLN', 2, NULL, NULL, 'Polish Zloty', 'currency.PLN'),
+	(116, 'PYG', 0, NULL, '?', 'Paraguayan Guarani', 'currency.PYG'),
+	(117, 'QAR', 2, NULL, NULL, 'Qatari Rial', 'currency.QAR'),
+	(118, 'RON', 2, NULL, NULL, 'Romanian Leu', 'currency.RON'),
+	(119, 'RUB', 2, NULL, NULL, 'Russian Ruble', 'currency.RUB'),
+	(120, 'RWF', 0, NULL, NULL, 'Rwanda Franc', 'currency.RWF'),
+	(121, 'SAR', 2, NULL, NULL, 'Saudi Riyal', 'currency.SAR'),
+	(122, 'SBD', 2, NULL, NULL, 'Solomon Islands Dollar', 'currency.SBD'),
+	(123, 'SCR', 2, NULL, NULL, 'Seychelles Rupee', 'currency.SCR'),
+	(124, 'SDD', 2, NULL, NULL, 'Sudanese Dinar', 'currency.SDD'),
+	(125, 'SEK', 2, NULL, NULL, 'Swedish Krona', 'currency.SEK'),
+	(126, 'SGD', 2, NULL, NULL, 'Singapore Dollar', 'currency.SGD'),
+	(127, 'SHP', 2, NULL, NULL, 'St Helena Pound', 'currency.SHP'),
+	(128, 'SIT', 2, NULL, NULL, 'Slovenian Tolar', 'currency.SIT'),
+	(129, 'SKK', 2, NULL, NULL, 'Slovak Koruna', 'currency.SKK'),
+	(130, 'SLL', 2, NULL, NULL, 'Sierra Leone Leone', 'currency.SLL'),
+	(131, 'SOS', 2, NULL, NULL, 'Somali Shilling', 'currency.SOS'),
+	(132, 'SRD', 2, NULL, NULL, 'Surinam Dollar', 'currency.SRD'),
+	(133, 'STD', 2, NULL, NULL, 'Sao Tome and Principe Dobra', 'currency.STD'),
+	(134, 'SVC', 2, NULL, NULL, 'El Salvador Colon', 'currency.SVC'),
+	(135, 'SYP', 2, NULL, NULL, 'Syrian Pound', 'currency.SYP'),
+	(136, 'SZL', 2, NULL, NULL, 'Swaziland Lilangeni', 'currency.SZL'),
+	(137, 'THB', 2, NULL, NULL, 'Thai Baht', 'currency.THB'),
+	(138, 'TJS', 2, NULL, NULL, 'Tajik Somoni', 'currency.TJS'),
+	(139, 'TMM', 2, NULL, NULL, 'Turkmenistan Manat', 'currency.TMM'),
+	(140, 'TND', 3, NULL, 'DT', 'Tunisian Dinar', 'currency.TND'),
+	(141, 'TOP', 2, NULL, NULL, 'Tonga Pa\'anga', 'currency.TOP'),
+	(142, 'TRY', 2, NULL, NULL, 'Turkish Lira', 'currency.TRY'),
+	(143, 'TTD', 2, NULL, NULL, 'Trinidad and Tobago Dollar', 'currency.TTD'),
+	(144, 'TWD', 2, NULL, NULL, 'New Taiwan Dollar', 'currency.TWD'),
+	(145, 'TZS', 2, NULL, NULL, 'Tanzanian Shilling', 'currency.TZS'),
+	(146, 'UAH', 2, NULL, NULL, 'Ukraine Hryvnia', 'currency.UAH'),
+	(147, 'UGX', 2, NULL, 'USh', 'Uganda Shilling', 'currency.UGX'),
+	(148, 'USD', 2, NULL, '$', 'US Dollar', 'currency.USD'),
+	(149, 'UYU', 2, NULL, '$U', 'Peso Uruguayo', 'currency.UYU'),
+	(150, 'UZS', 2, NULL, NULL, 'Uzbekistan Sum', 'currency.UZS'),
+	(151, 'VEB', 2, NULL, 'Bs.F.', 'Venezuelan Bolivar', 'currency.VEB'),
+	(152, 'VND', 2, NULL, NULL, 'Vietnamese Dong', 'currency.VND'),
+	(153, 'VUV', 0, NULL, NULL, 'Vanuatu Vatu', 'currency.VUV'),
+	(154, 'WST', 2, NULL, NULL, 'Samoa Tala', 'currency.WST'),
+	(155, 'XAF', 0, NULL, NULL, 'CFA Franc BEAC', 'currency.XAF'),
+	(156, 'XCD', 2, NULL, NULL, 'East Caribbean Dollar', 'currency.XCD'),
+	(157, 'XDR', 5, NULL, NULL, 'SDR (Special Drawing Rights)', 'currency.XDR'),
+	(158, 'XOF', 0, NULL, 'CFA', 'CFA Franc BCEAO', 'currency.XOF'),
+	(159, 'XPF', 0, NULL, NULL, 'CFP Franc', 'currency.XPF'),
+	(160, 'YER', 2, NULL, NULL, 'Yemeni Rial', 'currency.YER'),
+	(161, 'ZAR', 2, NULL, 'R', 'South African Rand', 'currency.ZAR'),
+	(162, 'ZMK', 2, NULL, NULL, 'Zambian Kwacha', 'currency.ZMK'),
+	(163, 'ZWD', 2, NULL, NULL, 'Zimbabwe Dollar', 'currency.ZWD');
+/*!40000 ALTER TABLE `m_currency` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_deposit_account_on_hold_transaction
+DROP TABLE IF EXISTS `m_deposit_account_on_hold_transaction`;
+CREATE TABLE IF NOT EXISTS `m_deposit_account_on_hold_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `transaction_type_enum` smallint(1) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL DEFAULT '0',
+  `created_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_deposit_on_hold_transaction_m_savings_account` (`savings_account_id`),
+  CONSTRAINT `FK_deposit_on_hold_transaction_m_savings_account` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_deposit_account_on_hold_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_account_on_hold_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_account_on_hold_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_deposit_account_recurring_detail
+DROP TABLE IF EXISTS `m_deposit_account_recurring_detail`;
+CREATE TABLE IF NOT EXISTS `m_deposit_account_recurring_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL DEFAULT '0',
+  `mandatory_recommended_deposit_amount` decimal(19,6) DEFAULT NULL,
+  `is_mandatory` tinyint(4) NOT NULL DEFAULT '0',
+  `allow_withdrawal` tinyint(4) NOT NULL DEFAULT '0',
+  `adjust_advance_towards_future_payments` tinyint(4) NOT NULL DEFAULT '1',
+  `is_calendar_inherited` tinyint(4) NOT NULL DEFAULT '0',
+  `total_overdue_amount` decimal(19,6) DEFAULT NULL,
+  `no_of_overdue_installments` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKDARD00000000000001` (`savings_account_id`),
+  CONSTRAINT `FKDARD00000000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_deposit_account_recurring_detail: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_account_recurring_detail` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_account_recurring_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_deposit_account_term_and_preclosure
+DROP TABLE IF EXISTS `m_deposit_account_term_and_preclosure`;
+CREATE TABLE IF NOT EXISTS `m_deposit_account_term_and_preclosure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL DEFAULT '0',
+  `min_deposit_term` int(11) DEFAULT NULL,
+  `max_deposit_term` int(11) DEFAULT NULL,
+  `min_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `max_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `in_multiples_of_deposit_term` int(11) DEFAULT NULL,
+  `in_multiples_of_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_applicable` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_interest` decimal(19,6) DEFAULT NULL,
+  `pre_closure_penal_interest_on_enum` smallint(5) DEFAULT NULL,
+  `deposit_period` int(11) DEFAULT NULL,
+  `deposit_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `deposit_amount` decimal(19,6) DEFAULT NULL,
+  `maturity_amount` decimal(19,6) DEFAULT NULL,
+  `maturity_date` date DEFAULT NULL,
+  `on_account_closure_enum` smallint(5) DEFAULT NULL,
+  `expected_firstdepositon_date` date DEFAULT NULL,
+  `transfer_interest_to_linked_account` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `FKDATP00000000000001` (`savings_account_id`),
+  CONSTRAINT `FKDATP00000000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_deposit_account_term_and_preclosure: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_account_term_and_preclosure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_account_term_and_preclosure` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_deposit_product_interest_rate_chart
+DROP TABLE IF EXISTS `m_deposit_product_interest_rate_chart`;
+CREATE TABLE IF NOT EXISTS `m_deposit_product_interest_rate_chart` (
+  `deposit_product_id` bigint(20) NOT NULL,
+  `interest_rate_chart_id` bigint(20) NOT NULL,
+  UNIQUE KEY `deposit_product_id_interest_rate_chart_id` (`deposit_product_id`,`interest_rate_chart_id`),
+  KEY `FKDPIRC00000000000002` (`interest_rate_chart_id`),
+  CONSTRAINT `FKDPIRC00000000000001` FOREIGN KEY (`deposit_product_id`) REFERENCES `m_savings_product` (`id`),
+  CONSTRAINT `FKDPIRC00000000000002` FOREIGN KEY (`interest_rate_chart_id`) REFERENCES `m_interest_rate_chart` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_deposit_product_interest_rate_chart: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_product_interest_rate_chart` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_product_interest_rate_chart` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_deposit_product_recurring_detail
+DROP TABLE IF EXISTS `m_deposit_product_recurring_detail`;
+CREATE TABLE IF NOT EXISTS `m_deposit_product_recurring_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_product_id` bigint(20) NOT NULL DEFAULT '0',
+  `is_mandatory` tinyint(1) NOT NULL DEFAULT '1',
+  `allow_withdrawal` tinyint(1) NOT NULL DEFAULT '0',
+  `adjust_advance_towards_future_payments` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `FKDPRD00000000000001` (`savings_product_id`),
+  CONSTRAINT `FKDPRD00000000000001` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_deposit_product_recurring_detail: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_product_recurring_detail` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_product_recurring_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_deposit_product_term_and_preclosure
+DROP TABLE IF EXISTS `m_deposit_product_term_and_preclosure`;
+CREATE TABLE IF NOT EXISTS `m_deposit_product_term_and_preclosure` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_product_id` bigint(20) NOT NULL DEFAULT '0',
+  `min_deposit_term` int(11) DEFAULT NULL,
+  `max_deposit_term` int(11) DEFAULT NULL,
+  `min_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `max_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `in_multiples_of_deposit_term` int(11) DEFAULT NULL,
+  `in_multiples_of_deposit_term_type_enum` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_applicable` smallint(5) DEFAULT NULL,
+  `pre_closure_penal_interest` decimal(19,6) DEFAULT NULL,
+  `pre_closure_penal_interest_on_enum` smallint(5) DEFAULT NULL,
+  `min_deposit_amount` decimal(19,6) DEFAULT NULL,
+  `max_deposit_amount` decimal(19,6) DEFAULT NULL,
+  `deposit_amount` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKDPTP00000000000001` (`savings_product_id`),
+  CONSTRAINT `FKDPTP00000000000001` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_deposit_product_term_and_preclosure: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_deposit_product_term_and_preclosure` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_deposit_product_term_and_preclosure` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_document
+DROP TABLE IF EXISTS `m_document`;
+CREATE TABLE IF NOT EXISTS `m_document` (
+  `id` int(20) NOT NULL AUTO_INCREMENT,
+  `parent_entity_type` varchar(50) NOT NULL,
+  `parent_entity_id` int(20) NOT NULL DEFAULT '0',
+  `name` varchar(250) NOT NULL,
+  `file_name` varchar(250) NOT NULL,
+  `size` int(20) DEFAULT '0',
+  `type` varchar(500) DEFAULT NULL,
+  `description` varchar(1000) DEFAULT NULL,
+  `location` varchar(500) NOT NULL DEFAULT '0',
+  `storage_type_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_document: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_document` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_document` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_entity_relation
+DROP TABLE IF EXISTS `m_entity_relation`;
+CREATE TABLE IF NOT EXISTS `m_entity_relation` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_entity_type` int(10) NOT NULL,
+  `to_entity_type` int(10) NOT NULL,
+  `code_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `from_entity_type_to_entity_type_code_name` (`from_entity_type`,`to_entity_type`,`code_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_entity_relation: ~5 rows (approximately)
+/*!40000 ALTER TABLE `m_entity_relation` DISABLE KEYS */;
+INSERT INTO `m_entity_relation` (`id`, `from_entity_type`, `to_entity_type`, `code_name`) VALUES
+	(1, 1, 2, 'office_access_to_loan_products'),
+	(2, 1, 3, 'office_access_to_savings_products'),
+	(3, 1, 4, 'office_access_to_fees/charges'),
+	(4, 5, 2, 'role_access_to_loan_products'),
+	(5, 5, 3, 'role_access_to_savings_products');
+/*!40000 ALTER TABLE `m_entity_relation` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_entity_to_entity_access
+DROP TABLE IF EXISTS `m_entity_to_entity_access`;
+CREATE TABLE IF NOT EXISTS `m_entity_to_entity_access` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `entity_type` varchar(50) NOT NULL,
+  `entity_id` bigint(20) NOT NULL,
+  `access_type_code_value_id` int(11) NOT NULL,
+  `second_entity_type` varchar(50) NOT NULL,
+  `second_entity_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `id_uniq_m_entity_to_entity_access` (`entity_type`,`entity_id`,`access_type_code_value_id`,`second_entity_type`,`second_entity_id`),
+  KEY `IDX_OFFICE` (`entity_type`,`entity_id`),
+  KEY `FK_access_type_code_m_code_value` (`access_type_code_value_id`),
+  CONSTRAINT `FK_access_type_code_m_code_value` FOREIGN KEY (`access_type_code_value_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_entity_to_entity_access: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_entity_to_entity_access` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_entity_to_entity_access` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_entity_to_entity_mapping
+DROP TABLE IF EXISTS `m_entity_to_entity_mapping`;
+CREATE TABLE IF NOT EXISTS `m_entity_to_entity_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `rel_id` bigint(20) NOT NULL DEFAULT '0',
+  `from_id` bigint(20) NOT NULL DEFAULT '0',
+  `to_id` bigint(20) unsigned NOT NULL,
+  `start_date` date DEFAULT NULL,
+  `end_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `rel_id_from_id_to_id` (`rel_id`,`from_id`,`to_id`),
+  CONSTRAINT `FK__rel_id_m_entity_relation_id` FOREIGN KEY (`rel_id`) REFERENCES `m_entity_relation` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_entity_to_entity_mapping: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_entity_to_entity_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_entity_to_entity_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_floating_rates
+DROP TABLE IF EXISTS `m_floating_rates`;
+CREATE TABLE IF NOT EXISTS `m_floating_rates` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(200) NOT NULL,
+  `is_base_lending_rate` bit(1) NOT NULL DEFAULT b'0',
+  `is_active` bit(1) NOT NULL DEFAULT b'1',
+  `createdby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_floating_rates: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_floating_rates` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_floating_rates` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_floating_rates_periods
+DROP TABLE IF EXISTS `m_floating_rates_periods`;
+CREATE TABLE IF NOT EXISTS `m_floating_rates_periods` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `floating_rates_id` bigint(20) NOT NULL,
+  `from_date` datetime NOT NULL,
+  `interest_rate` decimal(19,6) NOT NULL,
+  `is_differential_to_base_lending_rate` bit(1) NOT NULL DEFAULT b'0',
+  `is_active` bit(1) NOT NULL DEFAULT b'1',
+  `createdby_id` bigint(20) NOT NULL,
+  `created_date` datetime NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `lastmodified_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_mappings_m_floating_rates` (`floating_rates_id`),
+  CONSTRAINT `FK_mappings_m_floating_rates` FOREIGN KEY (`floating_rates_id`) REFERENCES `m_floating_rates` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_floating_rates_periods: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_floating_rates_periods` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_floating_rates_periods` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_fund
+DROP TABLE IF EXISTS `m_fund`;
+CREATE TABLE IF NOT EXISTS `m_fund` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `fund_name_org` (`name`),
+  UNIQUE KEY `fund_externalid_org` (`external_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_fund: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_fund` DISABLE KEYS */;
+INSERT INTO `m_fund` (`id`, `name`, `external_id`) VALUES
+	(1, 'Loan from Central Bank', NULL);
+/*!40000 ALTER TABLE `m_fund` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_group
+DROP TABLE IF EXISTS `m_group`;
+CREATE TABLE IF NOT EXISTS `m_group` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `external_id` varchar(100) DEFAULT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '300',
+  `activation_date` date DEFAULT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `level_id` int(11) NOT NULL,
+  `display_name` varchar(100) NOT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `closure_reason_cv_id` int(11) DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `activatedon_userid` bigint(20) DEFAULT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `account_no` varchar(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`display_name`,`level_id`),
+  UNIQUE KEY `external_id` (`external_id`,`level_id`),
+  UNIQUE KEY `external_id_UNIQUE` (`external_id`),
+  KEY `office_id` (`office_id`),
+  KEY `staff_id` (`staff_id`),
+  KEY `Parent_Id_reference` (`parent_id`),
+  KEY `FK_m_group_level` (`level_id`),
+  KEY `FK_m_group_m_code` (`closure_reason_cv_id`),
+  CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_id`) REFERENCES `m_group_level` (`id`),
+  CONSTRAINT `FK_m_group_m_code` FOREIGN KEY (`closure_reason_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_group: ~4 rows (approximately)
+/*!40000 ALTER TABLE `m_group` DISABLE KEYS */;
+INSERT INTO `m_group` (`id`, `external_id`, `status_enum`, `activation_date`, `office_id`, `staff_id`, `parent_id`, `level_id`, `display_name`, `hierarchy`, `closure_reason_cv_id`, `closedon_date`, `activatedon_userid`, `submittedon_date`, `submittedon_userid`, `closedon_userid`, `account_no`) VALUES
+	(1, NULL, 300, '2010-01-02', 2, 2, NULL, 1, 'Santa Cruz', '.1.', NULL, NULL, 1, '2010-01-02', 1, NULL, '000000001'),
+	(2, NULL, 300, '2010-01-03', 2, 2, NULL, 1, 'Santa Maria', '.2.', NULL, NULL, 1, '2010-01-03', 1, NULL, '000000002'),
+	(3, NULL, 300, '2010-01-04', 2, 2, 2, 2, 'Santa Maria Group 1', '.2.3.', NULL, NULL, 1, '2010-01-04', 1, NULL, '000000003'),
+	(4, NULL, 300, '2010-01-04', 2, 2, 2, 2, 'Santa Maria Group 2', '.2.4.', NULL, NULL, 1, '2010-01-04', 1, NULL, '000000004');
+/*!40000 ALTER TABLE `m_group` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_group_client
+DROP TABLE IF EXISTS `m_group_client`;
+CREATE TABLE IF NOT EXISTS `m_group_client` (
+  `group_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`group_id`,`client_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_group_client_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `m_group_client_ibfk_2` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_group_client: ~6 rows (approximately)
+/*!40000 ALTER TABLE `m_group_client` DISABLE KEYS */;
+INSERT INTO `m_group_client` (`group_id`, `client_id`) VALUES
+	(3, 2),
+	(3, 4),
+	(3, 5),
+	(4, 6),
+	(4, 7),
+	(4, 8);
+/*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_group_level
+DROP TABLE IF EXISTS `m_group_level`;
+CREATE TABLE IF NOT EXISTS `m_group_level` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) DEFAULT NULL,
+  `super_parent` tinyint(1) NOT NULL,
+  `level_name` varchar(100) NOT NULL,
+  `recursable` tinyint(1) NOT NULL,
+  `can_have_clients` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `Parent_levelId_reference` (`parent_id`),
+  CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_group_level: ~2 rows (approximately)
+/*!40000 ALTER TABLE `m_group_level` DISABLE KEYS */;
+INSERT INTO `m_group_level` (`id`, `parent_id`, `super_parent`, `level_name`, `recursable`, `can_have_clients`) VALUES
+	(1, NULL, 1, 'Center', 1, 0),
+	(2, 1, 0, 'Group', 0, 1);
+/*!40000 ALTER TABLE `m_group_level` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_group_roles
+DROP TABLE IF EXISTS `m_group_roles`;
+CREATE TABLE IF NOT EXISTS `m_group_roles` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `role_cv_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UNIQUE_GROUP_ROLES` (`client_id`,`group_id`,`role_cv_id`),
+  KEY `FKGroupRoleClientId` (`client_id`),
+  KEY `FKGroupRoleGroupId` (`group_id`),
+  KEY `FK_grouprole_m_codevalue` (`role_cv_id`),
+  CONSTRAINT `FKGroupRoleClientId` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKGroupRoleGroupId` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_grouprole_m_codevalue` FOREIGN KEY (`role_cv_id`) REFERENCES `m_code_value` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_group_roles: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_group_roles` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_group_roles` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_guarantor
+DROP TABLE IF EXISTS `m_guarantor`;
+CREATE TABLE IF NOT EXISTS `m_guarantor` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `client_reln_cv_id` int(11) DEFAULT NULL,
+  `type_enum` smallint(5) NOT NULL,
+  `entity_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `dob` date DEFAULT NULL,
+  `address_line_1` varchar(500) DEFAULT NULL,
+  `address_line_2` varchar(500) DEFAULT NULL,
+  `city` varchar(50) DEFAULT NULL,
+  `state` varchar(50) DEFAULT NULL,
+  `country` varchar(50) DEFAULT NULL,
+  `zip` varchar(20) DEFAULT NULL,
+  `house_phone_number` varchar(20) DEFAULT NULL,
+  `mobile_number` varchar(20) DEFAULT NULL,
+  `comment` varchar(500) DEFAULT NULL,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_m_loan` (`loan_id`),
+  KEY `FK_m_guarantor_m_code_value` (`client_reln_cv_id`),
+  CONSTRAINT `FK_m_guarantor_m_code_value` FOREIGN KEY (`client_reln_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_m_guarantor_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_guarantor: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_guarantor` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_guarantor_funding_details
+DROP TABLE IF EXISTS `m_guarantor_funding_details`;
+CREATE TABLE IF NOT EXISTS `m_guarantor_funding_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `guarantor_id` bigint(20) NOT NULL,
+  `account_associations_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_released_derived` decimal(19,6) DEFAULT NULL,
+  `amount_remaining_derived` decimal(19,6) DEFAULT NULL,
+  `amount_transfered_derived` decimal(19,6) DEFAULT NULL,
+  `status_enum` smallint(3) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_guarantor_fund_details_m_guarantor` (`guarantor_id`),
+  KEY `FK_m_guarantor_fund_details_account_associations_id` (`account_associations_id`),
+  CONSTRAINT `FK_m_guarantor_fund_details_account_associations_id` FOREIGN KEY (`account_associations_id`) REFERENCES `m_portfolio_account_associations` (`id`),
+  CONSTRAINT `FK_m_guarantor_fund_details_m_guarantor` FOREIGN KEY (`guarantor_id`) REFERENCES `m_guarantor` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_guarantor_funding_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_guarantor_funding_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor_funding_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_guarantor_transaction
+DROP TABLE IF EXISTS `m_guarantor_transaction`;
+CREATE TABLE IF NOT EXISTS `m_guarantor_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `guarantor_fund_detail_id` bigint(20) NOT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `deposit_on_hold_transaction_id` bigint(20) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `FK_guarantor_transaction_m_deposit_account_on_hold_transaction` (`deposit_on_hold_transaction_id`),
+  KEY `FK_guarantor_transaction_guarantor_fund_detail` (`guarantor_fund_detail_id`),
+  KEY `FK_guarantor_transaction_m_loan_transaction` (`loan_transaction_id`),
+  CONSTRAINT `FK_guarantor_transaction_guarantor_fund_detail` FOREIGN KEY (`guarantor_fund_detail_id`) REFERENCES `m_guarantor_funding_details` (`id`),
+  CONSTRAINT `FK_guarantor_transaction_m_deposit_account_on_hold_transaction` FOREIGN KEY (`deposit_on_hold_transaction_id`) REFERENCES `m_deposit_account_on_hold_transaction` (`id`),
+  CONSTRAINT `FK_guarantor_transaction_m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_guarantor_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_guarantor_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_guarantor_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_holiday
+DROP TABLE IF EXISTS `m_holiday`;
+CREATE TABLE IF NOT EXISTS `m_holiday` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `from_date` datetime NOT NULL,
+  `to_date` datetime NOT NULL,
+  `repayments_rescheduled_to` datetime NOT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '100',
+  `processed` tinyint(1) NOT NULL DEFAULT '0',
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `holiday_name` (`name`,`from_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-reference.m_holiday: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_holiday` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_holiday` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_holiday_office
+DROP TABLE IF EXISTS `m_holiday_office`;
+CREATE TABLE IF NOT EXISTS `m_holiday_office` (
+  `holiday_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`holiday_id`,`office_id`),
+  KEY `m_holiday_id_ibfk_1` (`holiday_id`),
+  KEY `m_office_id_ibfk_2` (`office_id`),
+  CONSTRAINT `m_holiday_id_ibfk_1` FOREIGN KEY (`holiday_id`) REFERENCES `m_holiday` (`id`),
+  CONSTRAINT `m_office_id_ibfk_2` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-reference.m_holiday_office: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_holiday_office` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_holiday_office` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_hook
+DROP TABLE IF EXISTS `m_hook`;
+CREATE TABLE IF NOT EXISTS `m_hook` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `template_id` smallint(6) NOT NULL,
+  `is_active` smallint(3) NOT NULL DEFAULT '1',
+  `name` varchar(45) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `ugd_template_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_template_id_idx` (`template_id`),
+  KEY `fk_ugd_template_id` (`ugd_template_id`),
+  CONSTRAINT `fk_template_id` FOREIGN KEY (`template_id`) REFERENCES `m_hook_templates` (`id`),
+  CONSTRAINT `fk_ugd_template_id` FOREIGN KEY (`ugd_template_id`) REFERENCES `m_template` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_hook: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_hook` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_hook` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_hook_configuration
+DROP TABLE IF EXISTS `m_hook_configuration`;
+CREATE TABLE IF NOT EXISTS `m_hook_configuration` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `hook_id` bigint(20) DEFAULT NULL,
+  `field_type` varchar(45) NOT NULL,
+  `field_name` varchar(100) NOT NULL,
+  `field_value` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_hook_id_idx` (`hook_id`),
+  CONSTRAINT `fk_hook_id_cfg` FOREIGN KEY (`hook_id`) REFERENCES `m_hook` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_hook_configuration: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_hook_configuration` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_hook_configuration` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_hook_registered_events
+DROP TABLE IF EXISTS `m_hook_registered_events`;
+CREATE TABLE IF NOT EXISTS `m_hook_registered_events` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `hook_id` bigint(20) NOT NULL,
+  `entity_name` varchar(45) NOT NULL,
+  `action_name` varchar(45) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_hook_id_idx` (`hook_id`),
+  CONSTRAINT `fk_hook_idc` FOREIGN KEY (`hook_id`) REFERENCES `m_hook` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_hook_registered_events: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_hook_registered_events` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_hook_registered_events` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_hook_schema
+DROP TABLE IF EXISTS `m_hook_schema`;
+CREATE TABLE IF NOT EXISTS `m_hook_schema` (
+  `id` smallint(6) NOT NULL AUTO_INCREMENT,
+  `hook_template_id` smallint(6) NOT NULL,
+  `field_type` varchar(45) NOT NULL,
+  `field_name` varchar(100) NOT NULL,
+  `placeholder` varchar(100) DEFAULT NULL,
+  `optional` tinyint(3) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `fk_hook_template_id_idx` (`hook_template_id`),
+  CONSTRAINT `fk_hook_template_id` FOREIGN KEY (`hook_template_id`) REFERENCES `m_hook_templates` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_hook_schema: ~7 rows (approximately)
+/*!40000 ALTER TABLE `m_hook_schema` DISABLE KEYS */;
+INSERT INTO `m_hook_schema` (`id`, `hook_template_id`, `field_type`, `field_name`, `placeholder`, `optional`) VALUES
+	(1, 1, 'string', 'Payload URL', NULL, 0),
+	(2, 1, 'string', 'Content Type', 'json / form', 0),
+	(3, 2, 'string', 'Payload URL', NULL, 0),
+	(4, 2, 'string', 'SMS Provider', NULL, 0),
+	(5, 2, 'string', 'Phone Number', NULL, 0),
+	(6, 2, 'string', 'SMS Provider Token', NULL, 0),
+	(7, 2, 'string', 'SMS Provider Account Id', NULL, 0);
+/*!40000 ALTER TABLE `m_hook_schema` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_hook_templates
+DROP TABLE IF EXISTS `m_hook_templates`;
+CREATE TABLE IF NOT EXISTS `m_hook_templates` (
+  `id` smallint(6) NOT NULL AUTO_INCREMENT,
+  `name` varchar(45) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_hook_templates: ~2 rows (approximately)
+/*!40000 ALTER TABLE `m_hook_templates` DISABLE KEYS */;
+INSERT INTO `m_hook_templates` (`id`, `name`) VALUES
+	(1, 'Web'),
+	(2, 'SMS Bridge');
+/*!40000 ALTER TABLE `m_hook_templates` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_image
+DROP TABLE IF EXISTS `m_image`;
+CREATE TABLE IF NOT EXISTS `m_image` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `location` varchar(500) DEFAULT NULL,
+  `storage_type_enum` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_image: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_image` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_image` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_interest_incentives
+DROP TABLE IF EXISTS `m_interest_incentives`;
+CREATE TABLE IF NOT EXISTS `m_interest_incentives` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `interest_rate_slab_id` bigint(20) NOT NULL,
+  `entiry_type` smallint(2) NOT NULL,
+  `attribute_name` smallint(2) NOT NULL,
+  `condition_type` smallint(2) NOT NULL,
+  `attribute_value` varchar(50) NOT NULL,
+  `incentive_type` smallint(2) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_interest_incentives_m_interest_rate_slab` (`interest_rate_slab_id`),
+  CONSTRAINT `FK_m_interest_incentives_m_interest_rate_slab` FOREIGN KEY (`interest_rate_slab_id`) REFERENCES `m_interest_rate_slab` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_interest_incentives: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_interest_incentives` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_interest_incentives` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_interest_rate_chart
+DROP TABLE IF EXISTS `m_interest_rate_chart`;
+CREATE TABLE IF NOT EXISTS `m_interest_rate_chart` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `from_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_interest_rate_chart: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_interest_rate_chart` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_interest_rate_chart` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_interest_rate_slab
+DROP TABLE IF EXISTS `m_interest_rate_slab`;
+CREATE TABLE IF NOT EXISTS `m_interest_rate_slab` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `interest_rate_chart_id` bigint(20) NOT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `period_type_enum` smallint(5) NOT NULL DEFAULT '1',
+  `from_period` int(11) NOT NULL DEFAULT '0',
+  `to_period` int(11) DEFAULT NULL,
+  `amount_range_from` decimal(19,6) DEFAULT NULL,
+  `amount_range_to` decimal(19,6) DEFAULT NULL,
+  `annual_interest_rate` decimal(19,6) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKIRS00000000000001` (`interest_rate_chart_id`),
+  CONSTRAINT `FKIRS00000000000001` FOREIGN KEY (`interest_rate_chart_id`) REFERENCES `m_interest_rate_chart` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_interest_rate_slab: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_interest_rate_slab` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_interest_rate_slab` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan
+DROP TABLE IF EXISTS `m_loan`;
+CREATE TABLE IF NOT EXISTS `m_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `loanpurpose_cv_id` int(11) DEFAULT NULL,
+  `loan_status_id` smallint(5) NOT NULL,
+  `loan_type_enum` smallint(5) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `principal_amount_proposed` decimal(19,6) NOT NULL,
+  `principal_amount` decimal(19,6) NOT NULL,
+  `approved_principal` decimal(19,6) NOT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `is_floating_interest_rate` bit(1) DEFAULT b'0',
+  `interest_rate_differential` decimal(19,6) DEFAULT '0.000000',
+  `nominal_interest_rate_per_period` decimal(19,6) DEFAULT NULL,
+  `interest_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) DEFAULT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `allow_partial_period_interest_calcualtion` tinyint(1) NOT NULL DEFAULT '0',
+  `term_frequency` smallint(5) NOT NULL DEFAULT '0',
+  `term_period_frequency_enum` smallint(5) NOT NULL DEFAULT '2',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `repayment_frequency_nth_day_enum` smallint(5) DEFAULT '0',
+  `repayment_frequency_day_of_week_enum` smallint(5) DEFAULT '0',
+  `number_of_repayments` smallint(5) NOT NULL,
+  `grace_on_principal_periods` smallint(5) DEFAULT NULL,
+  `grace_on_interest_periods` smallint(5) DEFAULT NULL,
+  `grace_interest_free_periods` smallint(5) DEFAULT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `submittedon_date` date DEFAULT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `expected_disbursedon_date` date DEFAULT NULL,
+  `expected_firstrepaymenton_date` date DEFAULT NULL,
+  `interest_calculated_from_date` date DEFAULT NULL,
+  `disbursedon_date` date DEFAULT NULL,
+  `disbursedon_userid` bigint(20) DEFAULT NULL,
+  `expected_maturedon_date` date DEFAULT NULL,
+  `maturedon_date` date DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `total_charges_due_at_disbursement_derived` decimal(19,6) DEFAULT NULL,
+  `principal_disbursed_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `principal_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_charged_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_repaid_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_repayment_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_expected_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_costofloan_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_waived_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_writtenoff_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overpaid_derived` decimal(19,6) DEFAULT NULL,
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `rescheduledon_date` date DEFAULT NULL,
+  `rescheduledon_userid` bigint(20) DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `writtenoffon_date` date DEFAULT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  `sync_disbursement_with_meeting` tinyint(1) DEFAULT NULL,
+  `loan_counter` smallint(6) DEFAULT NULL,
+  `loan_product_counter` smallint(6) DEFAULT NULL,
+  `fixed_emi_amount` decimal(19,6) DEFAULT NULL,
+  `max_outstanding_loan_balance` decimal(19,6) DEFAULT NULL,
+  `grace_on_arrears_ageing` smallint(5) DEFAULT NULL,
+  `is_npa` tinyint(1) NOT NULL DEFAULT '0',
+  `total_recovered_derived` decimal(19,6) DEFAULT NULL,
+  `accrued_till` date DEFAULT NULL,
+  `interest_recalcualated_on` date DEFAULT NULL,
+  `days_in_month_enum` smallint(5) NOT NULL DEFAULT '1',
+  `days_in_year_enum` smallint(5) NOT NULL DEFAULT '1',
+  `interest_recalculation_enabled` tinyint(4) NOT NULL DEFAULT '0',
+  `guarantee_amount_derived` decimal(19,6) DEFAULT NULL,
+  `create_standing_instruction_at_disbursement` tinyint(1) DEFAULT NULL,
+  `version` int(15) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `loan_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `loan_externalid_UNIQUE` (`external_id`),
+  KEY `FKB6F935D87179A0CB` (`client_id`),
+  KEY `FKB6F935D8C8D4B434` (`product_id`),
+  KEY `FK7C885877240145` (`fund_id`),
+  KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
+  KEY `FK_m_loan_m_staff` (`loan_officer_id`),
+  KEY `group_id` (`group_id`),
+  KEY `FK_m_loanpurpose_codevalue` (`loanpurpose_cv_id`),
+  KEY `FK_submittedon_userid` (`submittedon_userid`),
+  KEY `FK_approvedon_userid` (`approvedon_userid`),
+  KEY `FK_rejectedon_userid` (`rejectedon_userid`),
+  KEY `FK_withdrawnon_userid` (`withdrawnon_userid`),
+  KEY `FK_disbursedon_userid` (`disbursedon_userid`),
+  KEY `FK_closedon_userid` (`closedon_userid`),
+  KEY `fk_m_group_client_001_idx` (`group_id`,`client_id`),
+  CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FKB6F935D87179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKB6F935D8C8D4B434` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_approvedon_userid` FOREIGN KEY (`approvedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_closedon_userid` FOREIGN KEY (`closedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_disbursedon_userid` FOREIGN KEY (`disbursedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`),
+  CONSTRAINT `FK_m_loan_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `FK_m_loanpurpose_codevalue` FOREIGN KEY (`loanpurpose_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_rejectedon_userid` FOREIGN KEY (`rejectedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_submittedon_userid` FOREIGN KEY (`submittedon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_withdrawnon_userid` FOREIGN KEY (`withdrawnon_userid`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_loan` DISABLE KEYS */;
+INSERT INTO `m_loan` (`id`, `account_no`, `external_id`, `client_id`, `group_id`, `product_id`, `fund_id`, `loan_officer_id`, `loanpurpose_cv_id`, `loan_status_id`, `loan_type_enum`, `currency_code`, `currency_digits`, `currency_multiplesof`, `principal_amount_proposed`, `principal_amount`, `approved_principal`, `arrearstolerance_amount`, `is_floating_interest_rate`, `interest_rate_differential`, `nominal_interest_rate_per_period`, `interest_period_frequency_enum`, `annual_nominal_interest_rate`, `interest_method_enum`, `interest_calculated_in_period_enum`, `allow_partial_period_interest_calcualtion`, `term_frequency`, `term_period_frequency_enum`, `repay_every`, `repayment_period_frequency_enum`, `repayment_frequency_nth_day_enum`, `repayment_frequency_day_of_week_enum`, `number_of_repayments`, `grace_on_principal_periods`, `grace_on_interest_periods`, `grace_interest_free_periods`, `amortization_method_enum`, `submittedon_date`, `submittedon_userid`, `approvedon_date`, `approvedon_userid`, `expected_disbursedon_date`, `expected_firstrepaymenton_date`, `interest_calculated_from_date`, `disbursedon_date`, `disbursedon_userid`, `expected_maturedon_date`, `maturedon_date`, `closedon_date`, `closedon_userid`, `total_charges_due_at_disbursement_derived`, `principal_disbursed_derived`, `principal_repaid_derived`, `principal_writtenoff_derived`, `principal_outstanding_derived`, `interest_charged_derived`, `interest_repaid_derived`, `interest_waived_derived`, `interest_writtenoff_derived`, `interest_outstanding_derived`, `fee_charges_charged_derived`, `fee_charges_repaid_derived`, `fee_charges_waived_derived`, `fee_charges_writtenoff_derived`, `fee_charges_outstanding_derived`, `penalty_charges_charged_derived`, `penalty_charges_repaid_derived`, `penalty_charges_waived_derived`, `penalty_charges_writtenoff_derived`, `penalty_charges_outstanding_derived`, `total_expected_repayment_derived`, `total_repayment_derived`, `total_expected_costofloan_derived`, `total_costofloan_derived`, `total_waived_derived`, `total_writtenoff_derived`, `total_outstanding_derived`, `total_overpaid_derived`, `rejectedon_date`, `rejectedon_userid`, `rescheduledon_date`, `rescheduledon_userid`, `withdrawnon_date`, `withdrawnon_userid`, `writtenoffon_date`, `loan_transaction_strategy_id`, `sync_disbursement_with_meeting`, `loan_counter`, `loan_product_counter`, `fixed_emi_amount`, `max_outstanding_loan_balance`, `grace_on_arrears_ageing`, `is_npa`, `total_recovered_derived`, `accrued_till`, `interest_recalcualated_on`, `days_in_month_enum`, `days_in_year_enum`, `interest_recalculation_enabled`, `guarantee_amount_derived`, `create_standing_instruction_at_disbursement`, `version`) VALUES
+	(1, '000000001', NULL, 8, NULL, 1, 1, NULL, NULL, 200, 1, 'USD', 2, 0, 10000.000000, 10000.000000, 10000.000000, NULL, b'0', 0.000000, 26.000000, 3, 26.000000, 1, 1, 0, 25, 1, 1, 1, 0, 0, 25, NULL, NULL, NULL, 1, '2014-06-02', 1, '2014-06-11', 1, '2014-06-16', NULL, NULL, NULL, NULL, '2014-12-08', '2014-12-08', NULL, NULL, 500.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, 1, 1, 0, NULL, NULL, 1);
+/*!40000 ALTER TABLE `m_loan` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loanproduct_provisioning_entry
+DROP TABLE IF EXISTS `m_loanproduct_provisioning_entry`;
+CREATE TABLE IF NOT EXISTS `m_loanproduct_provisioning_entry` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `history_id` bigint(20) NOT NULL,
+  `criteria_id` bigint(20) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `product_id` bigint(20) NOT NULL,
+  `category_id` bigint(20) NOT NULL,
+  `overdue_in_days` bigint(20) DEFAULT '0',
+  `reseve_amount` decimal(20,6) DEFAULT '0.000000',
+  `liability_account` bigint(20) DEFAULT NULL,
+  `expense_account` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `history_id` (`history_id`),
+  KEY `criteria_id` (`criteria_id`),
+  KEY `currency_code` (`currency_code`),
+  KEY `office_id` (`office_id`),
+  KEY `product_id` (`product_id`),
+  KEY `category_id` (`category_id`),
+  KEY `liability_account` (`liability_account`),
+  KEY `expense_account` (`expense_account`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_1` FOREIGN KEY (`history_id`) REFERENCES `m_provisioning_history` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_2` FOREIGN KEY (`criteria_id`) REFERENCES `m_provisioning_criteria` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_3` FOREIGN KEY (`currency_code`) REFERENCES `m_currency` (`code`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_4` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_5` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_6` FOREIGN KEY (`category_id`) REFERENCES `m_provision_category` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_7` FOREIGN KEY (`liability_account`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_entry_ibfk_8` FOREIGN KEY (`expense_account`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loanproduct_provisioning_entry: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loanproduct_provisioning_entry` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loanproduct_provisioning_entry` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loanproduct_provisioning_mapping
+DROP TABLE IF EXISTS `m_loanproduct_provisioning_mapping`;
+CREATE TABLE IF NOT EXISTS `m_loanproduct_provisioning_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `product_id` bigint(20) NOT NULL,
+  `criteria_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `product_id` (`product_id`),
+  KEY `criteria_id` (`criteria_id`),
+  CONSTRAINT `m_loanproduct_provisioning_mapping_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `m_loanproduct_provisioning_mapping_ibfk_2` FOREIGN KEY (`criteria_id`) REFERENCES `m_provisioning_criteria` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loanproduct_provisioning_mapping: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loanproduct_provisioning_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loanproduct_provisioning_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_arrears_aging
+DROP TABLE IF EXISTS `m_loan_arrears_aging`;
+CREATE TABLE IF NOT EXISTS `m_loan_arrears_aging` (
+  `loan_id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `principal_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_overdue_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `overdue_since_date_derived` date DEFAULT NULL,
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_arrears_aging_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_arrears_aging: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_arrears_aging` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_arrears_aging` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_charge
+DROP TABLE IF EXISTS `m_loan_charge`;
+CREATE TABLE IF NOT EXISTS `m_loan_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `due_for_collection_as_of_date` date DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `charge_payment_mode_enum` smallint(5) NOT NULL DEFAULT '0',
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `charge_amount_or_percentage` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  `min_cap` decimal(19,6) DEFAULT NULL,
+  `max_cap` decimal(19,6) DEFAULT NULL,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_loan_charge_ibfk_2` (`loan_id`),
+  CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_charge: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_charge` DISABLE KEYS */;
+INSERT INTO `m_loan_charge` (`id`, `loan_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `due_for_collection_as_of_date`, `charge_calculation_enum`, `charge_payment_mode_enum`, `calculation_percentage`, `calculation_on_amount`, `charge_amount_or_percentage`, `amount`, `amount_paid_derived`, `amount_waived_derived`, `amount_writtenoff_derived`, `amount_outstanding_derived`, `is_paid_derived`, `waived`, `min_cap`, `max_cap`, `is_active`) VALUES
+	(1, 1, 1, 0, 1, NULL, 1, 0, NULL, NULL, 500.000000, 500.000000, NULL, NULL, NULL, 500.000000, 0, 0, NULL, NULL, 1);
+/*!40000 ALTER TABLE `m_loan_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_charge_paid_by
+DROP TABLE IF EXISTS `m_loan_charge_paid_by`;
+CREATE TABLE IF NOT EXISTS `m_loan_charge_paid_by` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_transaction_id` bigint(20) NOT NULL,
+  `loan_charge_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `installment_number` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK__m_loan_transaction` (`loan_transaction_id`),
+  KEY `FK__m_loan_charge` (`loan_charge_id`),
+  CONSTRAINT `FK__m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+  CONSTRAINT `FK__m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_charge_paid_by: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_charge_paid_by` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_charge_paid_by` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_collateral
+DROP TABLE IF EXISTS `m_loan_collateral`;
+CREATE TABLE IF NOT EXISTS `m_loan_collateral` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `type_cv_id` int(11) NOT NULL,
+  `value` decimal(19,6) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_collateral_m_loan` (`loan_id`),
+  KEY `FK_collateral_code_value` (`type_cv_id`),
+  CONSTRAINT `FK_collateral_code_value` FOREIGN KEY (`type_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `FK_collateral_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_collateral: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_collateral` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_collateral` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_disbursement_detail
+DROP TABLE IF EXISTS `m_loan_disbursement_detail`;
+CREATE TABLE IF NOT EXISTS `m_loan_disbursement_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `expected_disburse_date` datetime NOT NULL,
+  `disbursedon_date` datetime DEFAULT NULL,
+  `principal` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_loan_disbursement_detail_loan_id` (`loan_id`),
+  CONSTRAINT `FK_loan_disbursement_detail_loan_id` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_disbursement_detail: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_disbursement_detail` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_disbursement_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_installment_charge
+DROP TABLE IF EXISTS `m_loan_installment_charge`;
+CREATE TABLE IF NOT EXISTS `m_loan_installment_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_charge_id` bigint(20) NOT NULL,
+  `loan_schedule_id` bigint(20) NOT NULL,
+  `due_date` date DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  `amount_through_charge_payment` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_loan_charge_id_charge_schedule` (`loan_charge_id`),
+  KEY `FK_loan_schedule_id_charge_schedule` (`loan_schedule_id`),
+  CONSTRAINT `FK_loan_charge_id_charge_schedule` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+  CONSTRAINT `FK_loan_schedule_id_charge_schedule` FOREIGN KEY (`loan_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_installment_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_installment_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_installment_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_officer_assignment_history
+DROP TABLE IF EXISTS `m_loan_officer_assignment_history`;
+CREATE TABLE IF NOT EXISTS `m_loan_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_loan_officer_assignment_history_0001` (`loan_id`),
+  KEY `fk_m_loan_officer_assignment_history_0002` (`loan_officer_id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0001` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `fk_m_loan_officer_assignment_history_0002` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_officer_assignment_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_officer_assignment_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_overdue_installment_charge
+DROP TABLE IF EXISTS `m_loan_overdue_installment_charge`;
+CREATE TABLE IF NOT EXISTS `m_loan_overdue_installment_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_charge_id` bigint(20) NOT NULL,
+  `loan_schedule_id` bigint(20) NOT NULL,
+  `frequency_number` int(10) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_loan_overdue_installment_charge_m_loan_charge` (`loan_charge_id`),
+  KEY `FK_m_loan_overdue_installment_charge_m_loan_repayment_schedule` (`loan_schedule_id`),
+  CONSTRAINT `FK_m_loan_overdue_installment_charge_m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+  CONSTRAINT `FK_m_loan_overdue_installment_charge_m_loan_repayment_schedule` FOREIGN KEY (`loan_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_overdue_installment_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_overdue_installment_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_overdue_installment_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_paid_in_advance
+DROP TABLE IF EXISTS `m_loan_paid_in_advance`;
+CREATE TABLE IF NOT EXISTS `m_loan_paid_in_advance` (
+  `loan_id` bigint(20) NOT NULL,
+  `principal_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `interest_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `fee_charges_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `penalty_charges_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `total_in_advance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  PRIMARY KEY (`loan_id`),
+  CONSTRAINT `m_loan_paid_in_advance_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_paid_in_advance: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_paid_in_advance` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_paid_in_advance` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_recalculation_details
+DROP TABLE IF EXISTS `m_loan_recalculation_details`;
+CREATE TABLE IF NOT EXISTS `m_loan_recalculation_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `compound_type_enum` smallint(5) NOT NULL,
+  `reschedule_strategy_enum` smallint(5) NOT NULL,
+  `rest_frequency_type_enum` smallint(1) NOT NULL,
+  `rest_frequency_interval` smallint(3) NOT NULL DEFAULT '0',
+  `rest_freqency_date` date DEFAULT NULL,
+  `compounding_frequency_type_enum` smallint(1) DEFAULT NULL,
+  `compounding_frequency_interval` smallint(3) DEFAULT NULL,
+  `compounding_freqency_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_loan_m_loan_recalculation_details` (`loan_id`),
+  CONSTRAINT `FK_m_loan_m_loan_recalculation_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-reference.m_loan_recalculation_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_recalculation_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_recalculation_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_repayment_schedule
+DROP TABLE IF EXISTS `m_loan_repayment_schedule`;
+CREATE TABLE IF NOT EXISTS `m_loan_repayment_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `principal_completed_derived` decimal(19,6) DEFAULT NULL,
+  `principal_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `interest_completed_derived` decimal(19,6) DEFAULT NULL,
+  `interest_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `interest_waived_derived` decimal(19,6) DEFAULT NULL,
+  `accrual_interest_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `accrual_fee_charges_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_completed_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_waived_derived` decimal(19,6) DEFAULT NULL,
+  `accrual_penalty_charges_derived` decimal(19,6) DEFAULT NULL,
+  `total_paid_in_advance_derived` decimal(19,6) DEFAULT NULL,
+  `total_paid_late_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `obligations_met_on_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `recalculated_interest_component` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `FK488B92AA40BE0710` (`loan_id`),
+  CONSTRAINT `FK488B92AA40BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_repayment_schedule: ~25 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` DISABLE KEYS */;
+INSERT INTO `m_loan_repayment_schedule` (`id`, `loan_id`, `fromdate`, `duedate`, `installment`, `principal_amount`, `principal_completed_derived`, `principal_writtenoff_derived`, `interest_amount`, `interest_completed_derived`, `interest_writtenoff_derived`, `interest_waived_derived`, `accrual_interest_derived`, `fee_charges_amount`, `fee_charges_completed_derived`, `fee_charges_writtenoff_derived`, `fee_charges_waived_derived`, `accrual_fee_charges_derived`, `penalty_charges_amount`, `penalty_charges_completed_derived`, `penalty_charges_writtenoff_derived`, `penalty_charges_waived_derived`, `accrual_penalty_charges_derived`, `total_paid_in_advance_derived`, `total_paid_late_derived`, `completed_derived`, `obligations_met_on_date`, `createdby_id`, `created_date`, `lastmodified_date`, `lastmodifiedby_id`, `recalculated_interest_component`) VALUES
+	(1, 1, '2014-06-16', '2014-06-23', 1, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(2, 1, '2014-06-23', '2014-06-30', 2, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(3, 1, '2014-06-30', '2014-07-07', 3, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(4, 1, '2014-07-07', '2014-07-14', 4, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(5, 1, '2014-07-14', '2014-07-21', 5, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(6, 1, '2014-07-21', '2014-07-28', 6, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(7, 1, '2014-07-28', '2014-08-04', 7, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(8, 1, '2014-08-04', '2014-08-11', 8, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(9, 1, '2014-08-11', '2014-08-18', 9, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(10, 1, '2014-08-18', '2014-08-25', 10, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(11, 1, '2014-08-25', '2014-09-01', 11, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(12, 1, '2014-09-01', '2014-09-08', 12, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(13, 1, '2014-09-08', '2014-09-15', 13, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(14, 1, '2014-09-15', '2014-09-22', 14, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(15, 1, '2014-09-22', '2014-09-29', 15, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(16, 1, '2014-09-29', '2014-10-06', 16, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(17, 1, '2014-10-06', '2014-10-13', 17, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(18, 1, '2014-10-13', '2014-10-20', 18, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(19, 1, '2014-10-20', '2014-10-27', 19, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(20, 1, '2014-10-27', '2014-11-03', 20, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(21, 1, '2014-11-03', '2014-11-10', 21, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(22, 1, '2014-11-10', '2014-11-17', 22, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(23, 1, '2014-11-17', '2014-11-24', 23, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(24, 1, '2014-11-24', '2014-12-01', 24, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0),
+	(25, 1, '2014-12-01', '2014-12-08', 25, 400.000000, NULL, NULL, 50.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, b'0', NULL, 1, '2014-06-11 09:17:45', '2014-06-11 09:17:45', 1, 0);
+/*!40000 ALTER TABLE `m_loan_repayment_schedule` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_repayment_schedule_history
+DROP TABLE IF EXISTS `m_loan_repayment_schedule_history`;
+CREATE TABLE IF NOT EXISTS `m_loan_repayment_schedule_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `loan_reschedule_request_id` bigint(20) DEFAULT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `interest_amount` decimal(19,6) DEFAULT NULL,
+  `fee_charges_amount` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_amount` decimal(19,6) DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `version` int(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `loan_id` (`loan_id`),
+  KEY `loan_reschedule_request_id` (`loan_reschedule_request_id`),
+  CONSTRAINT `m_loan_repayment_schedule_history_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `m_loan_repayment_schedule_history_ibfk_2` FOREIGN KEY (`loan_reschedule_request_id`) REFERENCES `m_loan_reschedule_request` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_repayment_schedule_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_repayment_schedule_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_repayment_schedule_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_reschedule_request
+DROP TABLE IF EXISTS `m_loan_reschedule_request`;
+CREATE TABLE IF NOT EXISTS `m_loan_reschedule_request` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `status_enum` smallint(5) NOT NULL,
+  `reschedule_from_installment` smallint(5) NOT NULL COMMENT 'Rescheduling will start from this installment',
+  `grace_on_principal` smallint(5) DEFAULT NULL COMMENT 'Number of installments that should be added with 0 principal amount',
+  `grace_on_interest` smallint(5) DEFAULT NULL COMMENT 'Number of installments that should be added with 0 interest rate',
+  `reschedule_from_date` date NOT NULL COMMENT 'Rescheduling will start from the installment with due date similar to this date.',
+  `extra_terms` smallint(5) DEFAULT NULL COMMENT 'Number of extra terms to be added to the schedule',
+  `interest_rate` decimal(19,6) DEFAULT NULL COMMENT 'If provided, the interest rate for the unpaid installments will be recalculated',
+  `recalculate_interest` tinyint(1) DEFAULT NULL COMMENT 'If set to 1, interest will be recalculated starting from the reschedule period.',
+  `adjusted_due_date` date DEFAULT NULL COMMENT 'New due date for the first rescheduled installment',
+  `reschedule_reason_cv_id` int(11) DEFAULT NULL COMMENT 'ID of code value of reason for rescheduling',
+  `reschedule_reason_comment` varchar(500) DEFAULT NULL COMMENT 'Text provided in addition to the reason code value',
+  `submitted_on_date` date NOT NULL,
+  `submitted_by_user_id` bigint(20) NOT NULL,
+  `approved_on_date` date DEFAULT NULL,
+  `approved_by_user_id` bigint(20) DEFAULT NULL,
+  `rejected_on_date` date DEFAULT NULL,
+  `rejected_by_user_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `loan_id` (`loan_id`),
+  KEY `reschedule_reason_cv_id` (`reschedule_reason_cv_id`),
+  KEY `submitted_by_user_id` (`submitted_by_user_id`),
+  KEY `approved_by_user_id` (`approved_by_user_id`),
+  KEY `rejected_by_user_id` (`rejected_by_user_id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_1` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_2` FOREIGN KEY (`reschedule_reason_cv_id`) REFERENCES `m_code_value` (`id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_3` FOREIGN KEY (`submitted_by_user_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_4` FOREIGN KEY (`approved_by_user_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_loan_reschedule_request_ibfk_5` FOREIGN KEY (`rejected_by_user_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_reschedule_request: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_reschedule_request` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_reschedule_request` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_term_variations
+DROP TABLE IF EXISTS `m_loan_term_variations`;
+CREATE TABLE IF NOT EXISTS `m_loan_term_variations` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `term_type` smallint(2) NOT NULL,
+  `applicable_date` date NOT NULL,
+  `decimal_value` decimal(19,6) DEFAULT NULL,
+  `date_value` date DEFAULT NULL,
+  `is_specific_to_installment` tinyint(4) NOT NULL DEFAULT '0',
+  `applied_on_loan_status` smallint(5) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_loan_id_m_loan_id` (`loan_id`),
+  CONSTRAINT `FK_loan_id_m_loan_id` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_term_variations: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_term_variations` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_term_variations` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_tranche_charges
+DROP TABLE IF EXISTS `m_loan_tranche_charges`;
+CREATE TABLE IF NOT EXISTS `m_loan_tranche_charges` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_loan_tranche_charges_m_loan` (`loan_id`),
+  KEY `FK_m_loan_tranche_charges_m_charge` (`charge_id`),
+  CONSTRAINT `FK_m_loan_tranche_charges_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `FK_m_loan_tranche_charges_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_tranche_charges: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_tranche_charges` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_tranche_charges` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_tranche_disbursement_charge
+DROP TABLE IF EXISTS `m_loan_tranche_disbursement_charge`;
+CREATE TABLE IF NOT EXISTS `m_loan_tranche_disbursement_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_charge_id` bigint(20) NOT NULL,
+  `disbursement_detail_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_loan_tranche_disbursement_charge_m_loan_charge` (`loan_charge_id`),
+  KEY `FK_m_loan_tranche_disbursement_charge_m_loan_disbursement_detail` (`disbursement_detail_id`),
+  CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
+  CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_disbursement_detail` FOREIGN KEY (`disbursement_detail_id`) REFERENCES `m_loan_disbursement_detail` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_tranche_disbursement_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_tranche_disbursement_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_tranche_disbursement_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_transaction
+DROP TABLE IF EXISTS `m_loan_transaction`;
+CREATE TABLE IF NOT EXISTS `m_loan_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `payment_detail_id` bigint(20) DEFAULT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `overpayment_portion_derived` decimal(19,6) DEFAULT NULL,
+  `unrecognized_income_portion` decimal(19,6) DEFAULT NULL,
+  `outstanding_loan_balance_derived` decimal(19,6) DEFAULT NULL,
+  `submitted_on_date` date NOT NULL,
+  `manually_adjusted_or_reversed` tinyint(1) DEFAULT '0',
+  `created_date` datetime DEFAULT NULL,
+  `appuser_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `external_id_UNIQUE` (`external_id`),
+  KEY `FKCFCEA42640BE0710` (`loan_id`),
+  KEY `FK_m_loan_transaction_m_payment_detail` (`payment_detail_id`),
+  KEY `FK_m_loan_transaction_m_office` (`office_id`),
+  CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_m_loan_transaction_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_loan_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_loan_transaction_repayment_schedule_mapping
+DROP TABLE IF EXISTS `m_loan_transaction_repayment_schedule_mapping`;
+CREATE TABLE IF NOT EXISTS `m_loan_transaction_repayment_schedule_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_transaction_id` bigint(20) NOT NULL,
+  `loan_repayment_schedule_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `principal_portion_derived` decimal(19,6) DEFAULT NULL,
+  `interest_portion_derived` decimal(19,6) DEFAULT NULL,
+  `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_mappings_m_loan_transaction` (`loan_transaction_id`),
+  KEY `FK_mappings_m_loan_repayment_schedule` (`loan_repayment_schedule_id`),
+  CONSTRAINT `FK_mappings_m_loan_repayment_schedule` FOREIGN KEY (`loan_repayment_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`),
+  CONSTRAINT `FK_mappings_m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_loan_transaction_repayment_schedule_mapping: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_loan_transaction_repayment_schedule_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_loan_transaction_repayment_schedule_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_mandatory_savings_schedule
+DROP TABLE IF EXISTS `m_mandatory_savings_schedule`;
+CREATE TABLE IF NOT EXISTS `m_mandatory_savings_schedule` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `fromdate` date DEFAULT NULL,
+  `duedate` date NOT NULL,
+  `installment` smallint(5) NOT NULL,
+  `deposit_amount` decimal(19,6) DEFAULT NULL,
+  `deposit_amount_completed_derived` decimal(19,6) DEFAULT NULL,
+  `total_paid_in_advance_derived` decimal(19,6) DEFAULT NULL,
+  `total_paid_late_derived` decimal(19,6) DEFAULT NULL,
+  `completed_derived` bit(1) NOT NULL,
+  `obligations_met_on_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKMSS0000000001` (`savings_account_id`),
+  CONSTRAINT `FKMSS0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_mandatory_savings_schedule: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_mandatory_savings_schedule` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_mandatory_savings_schedule` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_meeting
+DROP TABLE IF EXISTS `m_meeting`;
+CREATE TABLE IF NOT EXISTS `m_meeting` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `calendar_instance_id` bigint(20) NOT NULL,
+  `meeting_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_calendar_instance_id_meeting_date` (`calendar_instance_id`,`meeting_date`),
+  CONSTRAINT `FK_m_calendar_instance_m_meeting` FOREIGN KEY (`calendar_instance_id`) REFERENCES `m_calendar_instance` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-reference.m_meeting: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_meeting` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_meeting` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_note
+DROP TABLE IF EXISTS `m_note`;
+CREATE TABLE IF NOT EXISTS `m_note` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `loan_transaction_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `savings_account_transaction_id` bigint(20) DEFAULT NULL,
+  `note_type_enum` smallint(5) NOT NULL,
+  `note` varchar(1000) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7C9708924D26803` (`loan_transaction_id`),
+  KEY `FK7C97089541F0A56` (`createdby_id`),
+  KEY `FK7C970897179A0CB` (`client_id`),
+  KEY `FK_m_note_m_group` (`group_id`),
+  KEY `FK7C970898F889C3F` (`lastmodifiedby_id`),
+  KEY `FK7C9708940BE0710` (`loan_id`),
+  KEY `FK_savings_account_id` (`savings_account_id`),
+  CONSTRAINT `FK7C9708924D26803` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+  CONSTRAINT `FK7C9708940BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK7C97089541F0A56` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK7C970897179A0CB` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FK7C970898F889C3F` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_note_m_group` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_savings_account_id` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_note: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_note` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_note` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_office
+DROP TABLE IF EXISTS `m_office`;
+CREATE TABLE IF NOT EXISTS `m_office` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT NULL,
+  `hierarchy` varchar(100) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `opening_date` date NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_org` (`name`),
+  UNIQUE KEY `externalid_org` (`external_id`),
+  KEY `FK2291C477E2551DCC` (`parent_id`),
+  CONSTRAINT `FK2291C477E2551DCC` FOREIGN KEY (`parent_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_office: ~3 rows (approximately)
+/*!40000 ALTER TABLE `m_office` DISABLE KEYS */;
+INSERT INTO `m_office` (`id`, `parent_id`, `hierarchy`, `external_id`, `name`, `opening_date`) VALUES
+	(1, NULL, '.', '1', 'Head Office', '2009-01-01'),
+	(2, 1, '.2.', NULL, 'Manila', '2010-01-01'),
+	(3, 1, '.3.', NULL, 'Pasay', '2010-02-08');
+/*!40000 ALTER TABLE `m_office` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_office_transaction
+DROP TABLE IF EXISTS `m_office_transaction`;
+CREATE TABLE IF NOT EXISTS `m_office_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `from_office_id` bigint(20) DEFAULT NULL,
+  `to_office_id` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` int(11) NOT NULL,
+  `transaction_amount` decimal(19,6) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK1E37728B93C6C1B6` (`to_office_id`),
+  KEY `FK1E37728B783C5C25` (`from_office_id`),
+  CONSTRAINT `FK1E37728B783C5C25` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK1E37728B93C6C1B6` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_office_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_office_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_office_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_organisation_currency
+DROP TABLE IF EXISTS `m_organisation_currency`;
+CREATE TABLE IF NOT EXISTS `m_organisation_currency` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(3) NOT NULL,
+  `decimal_places` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `display_symbol` varchar(10) DEFAULT NULL,
+  `internationalized_name_code` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_organisation_currency: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_organisation_currency` DISABLE KEYS */;
+INSERT INTO `m_organisation_currency` (`id`, `code`, `decimal_places`, `currency_multiplesof`, `name`, `display_symbol`, `internationalized_name_code`) VALUES
+	(21, 'USD', 2, NULL, 'US Dollar', '$', 'currency.USD');
+/*!40000 ALTER TABLE `m_organisation_currency` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_password_validation_policy
+DROP TABLE IF EXISTS `m_password_validation_policy`;
+CREATE TABLE IF NOT EXISTS `m_password_validation_policy` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `regex` text NOT NULL,
+  `description` text NOT NULL,
+  `active` tinyint(4) NOT NULL DEFAULT '0',
+  `key` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_password_validation_policy: ~2 rows (approximately)
+/*!40000 ALTER TABLE `m_password_validation_policy` DISABLE KEYS */;
+INSERT INTO `m_password_validation_policy` (`id`, `regex`, `description`, `active`, `key`) VALUES
+	(1, '^.{1,50}$', 'Password most be at least 1 character and not more that 50 characters long', 1, 'simple'),
+	(2, '^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\\s).{6,50}$', 'Password must be at least 6 characters, no more than 50 characters long, must include at least one upper case letter, one lower case letter, one numeric digit and no space', 0, 'secure');
+/*!40000 ALTER TABLE `m_password_validation_policy` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_payment_detail
+DROP TABLE IF EXISTS `m_payment_detail`;
+CREATE TABLE IF NOT EXISTS `m_payment_detail` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `payment_type_id` int(11) DEFAULT NULL,
+  `account_number` varchar(100) DEFAULT NULL,
+  `check_number` varchar(100) DEFAULT NULL,
+  `receipt_number` varchar(100) DEFAULT NULL,
+  `bank_number` varchar(100) DEFAULT NULL,
+  `routing_code` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_payment_detail_m_code_value` (`payment_type_id`),
+  CONSTRAINT `FK_m_payment_detail_m_payment_type` FOREIGN KEY (`payment_type_id`) REFERENCES `m_payment_type` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_payment_detail: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_payment_detail` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_payment_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_payment_type
+DROP TABLE IF EXISTS `m_payment_type`;
+CREATE TABLE IF NOT EXISTS `m_payment_type` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `value` varchar(100) DEFAULT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `is_cash_payment` tinyint(1) DEFAULT '0',
+  `order_position` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_payment_type: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_payment_type` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_payment_type` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_permission
+DROP TABLE IF EXISTS `m_permission`;
+CREATE TABLE IF NOT EXISTS `m_permission` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `grouping` varchar(45) DEFAULT NULL,
+  `code` varchar(100) NOT NULL,
+  `entity_name` varchar(100) DEFAULT NULL,
+  `action_name` varchar(100) DEFAULT NULL,
+  `can_maker_checker` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=705 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_permission: ~781 rows (approximately)
+/*!40000 ALTER TABLE `m_permission` DISABLE KEYS */;
+INSERT INTO `m_permission` (`id`, `grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
+	(1, 'special', 'ALL_FUNCTIONS', NULL, NULL, 0),
+	(2, 'special', 'ALL_FUNCTIONS_READ', NULL, NULL, 0),
+	(3, 'special', 'CHECKER_SUPER_USER', NULL, NULL, 0),
+	(4, 'special', 'REPORTING_SUPER_USER', NULL, NULL, 0),
+	(5, 'authorisation', 'READ_PERMISSION', 'PERMISSION', 'READ', 0),
+	(6, 'authorisation', 'PERMISSIONS_ROLE', 'ROLE', 'PERMISSIONS', 0),
+	(7, 'authorisation', 'CREATE_ROLE', 'ROLE', 'CREATE', 0),
+	(8, 'authorisation', 'CREATE_ROLE_CHECKER', 'ROLE', 'CREATE_CHECKER', 0),
+	(9, 'authorisation', 'READ_ROLE', 'ROLE', 'READ', 0),
+	(10, 'authorisation', 'UPDATE_ROLE', 'ROLE', 'UPDATE', 0),
+	(11, 'authorisation', 'UPDATE_ROLE_CHECKER', 'ROLE', 'UPDATE_CHECKER', 0),
+	(12, 'authorisation', 'DELETE_ROLE', 'ROLE', 'DELETE', 0),
+	(13, 'authorisation', 'DELETE_ROLE_CHECKER', 'ROLE', 'DELETE_CHECKER', 0),
+	(14, 'authorisation', 'CREATE_USER', 'USER', 'CREATE', 0),
+	(15, 'authorisation', 'CREATE_USER_CHECKER', 'USER', 'CREATE_CHECKER', 0),
+	(16, 'authorisation', 'READ_USER', 'USER', 'READ', 0),
+	(17, 'authorisation', 'UPDATE_USER', 'USER', 'UPDATE', 0),
+	(18, 'authorisation', 'UPDATE_USER_CHECKER', 'USER', 'UPDATE_CHECKER', 0),
+	(19, 'authorisation', 'DELETE_USER', 'USER', 'DELETE', 0),
+	(20, 'authorisation', 'DELETE_USER_CHECKER', 'USER', 'DELETE_CHECKER', 0),
+	(21, 'configuration', 'READ_CONFIGURATION', 'CONFIGURATION', 'READ', 0),
+	(22, 'configuration', 'UPDATE_CONFIGURATION', 'CONFIGURATION', 'UPDATE', 0),
+	(23, 'configuration', 'UPDATE_CONFIGURATION_CHECKER', 'CONFIGURATION', 'UPDATE_CHECKER', 0),
+	(24, 'configuration', 'READ_CODE', 'CODE', 'READ', 0),
+	(25, 'configuration', 'CREATE_CODE', 'CODE', 'CREATE', 0),
+	(26, 'configuration', 'CREATE_CODE_CHECKER', 'CODE', 'CREATE_CHECKER', 0),
+	(27, 'configuration', 'UPDATE_CODE', 'CODE', 'UPDATE', 0),
+	(28, 'configuration', 'UPDATE_CODE_CHECKER', 'CODE', 'UPDATE_CHECKER', 0),
+	(29, 'configuration', 'DELETE_CODE', 'CODE', 'DELETE', 0),
+	(30, 'configuration', 'DELETE_CODE_CHECKER', 'CODE', 'DELETE_CHECKER', 0),
+	(31, 'configuration', 'READ_CODEVALUE', 'CODEVALUE', 'READ', 0),
+	(32, 'configuration', 'CREATE_CODEVALUE', 'CODEVALUE', 'CREATE', 0),
+	(33, 'configuration', 'CREATE_CODEVALUE_CHECKER', 'CODEVALUE', 'CREATE_CHECKER', 0),
+	(34, 'configuration', 'UPDATE_CODEVALUE', 'CODEVALUE', 'UPDATE', 0),
+	(35, 'configuration', 'UPDATE_CODEVALUE_CHECKER', 'CODEVALUE', 'UPDATE_CHECKER', 0),
+	(36, 'configuration', 'DELETE_CODEVALUE', 'CODEVALUE', 'DELETE', 0),
+	(37, 'configuration', 'DELETE_CODEVALUE_CHECKER', 'CODEVALUE', 'DELETE_CHECKER', 0),
+	(38, 'configuration', 'READ_CURRENCY', 'CURRENCY', 'READ', 0),
+	(39, 'configuration', 'UPDATE_CURRENCY', 'CURRENCY', 'UPDATE', 0),
+	(40, 'configuration', 'UPDATE_CURRENCY_CHECKER', 'CURRENCY', 'UPDATE_CHECKER', 0),
+	(41, 'configuration', 'UPDATE_PERMISSION', 'PERMISSION', 'UPDATE', 0),
+	(42, 'configuration', 'UPDATE_PERMISSION_CHECKER', 'PERMISSION', 'UPDATE_CHECKER', 0),
+	(43, 'configuration', 'READ_DATATABLE', 'DATATABLE', 'READ', 0),
+	(44, 'configuration', 'REGISTER_DATATABLE', 'DATATABLE', 'REGISTER', 0),
+	(45, 'configuration', 'REGISTER_DATATABLE_CHECKER', 'DATATABLE', 'REGISTER_CHECKER', 0),
+	(46, 'configuration', 'DEREGISTER_DATATABLE', 'DATATABLE', 'DEREGISTER', 0),
+	(47, 'configuration', 'DEREGISTER_DATATABLE_CHECKER', 'DATATABLE', 'DEREGISTER_CHECKER', 0),
+	(48, 'configuration', 'READ_AUDIT', 'AUDIT', 'READ', 0),
+	(49, 'configuration', 'CREATE_CALENDAR', 'CALENDAR', 'CREATE', 0),
+	(50, 'configuration', 'READ_CALENDAR', 'CALENDAR', 'READ', 0),
+	(51, 'configuration', 'UPDATE_CALENDAR', 'CALENDAR', 'UPDATE', 0),
+	(52, 'configuration', 'DELETE_CALENDAR', 'CALENDAR', 'DELETE', 0),
+	(53, 'configuration', 'CREATE_CALENDAR_CHECKER', 'CALENDAR', 'CREATE_CHECKER', 0),
+	(54, 'configuration', 'UPDATE_CALENDAR_CHECKER', 'CALENDAR', 'UPDATE_CHECKER', 0),
+	(55, 'configuration', 'DELETE_CALENDAR_CHECKER', 'CALENDAR', 'DELETE_CHECKER', 0),
+	(57, 'organisation', 'READ_CHARGE', 'CHARGE', 'READ', 0),
+	(58, 'organisation', 'CREATE_CHARGE', 'CHARGE', 'CREATE', 0),
+	(59, 'organisation', 'CREATE_CHARGE_CHECKER', 'CHARGE', 'CREATE_CHECKER', 0),
+	(60, 'organisation', 'UPDATE_CHARGE', 'CHARGE', 'UPDATE', 0),
+	(61, 'organisation', 'UPDATE_CHARGE_CHECKER', 'CHARGE', 'UPDATE_CHECKER', 0),
+	(62, 'organisation', 'DELETE_CHARGE', 'CHARGE', 'DELETE', 0),
+	(63, 'organisation', 'DELETE_CHARGE_CHECKER', 'CHARGE', 'DELETE_CHECKER', 0),
+	(64, 'organisation', 'READ_FUND', 'FUND', 'READ', 0),
+	(65, 'organisation', 'CREATE_FUND', 'FUND', 'CREATE', 0),
+	(66, 'organisation', 'CREATE_FUND_CHECKER', 'FUND', 'CREATE_CHECKER', 0),
+	(67, 'organisation', 'UPDATE_FUND', 'FUND', 'UPDATE', 0),
+	(68, 'organisation', 'UPDATE_FUND_CHECKER', 'FUND', 'UPDATE_CHECKER', 0),
+	(69, 'organisation', 'DELETE_FUND', 'FUND', 'DELETE', 0),
+	(70, 'organisation', 'DELETE_FUND_CHECKER', 'FUND', 'DELETE_CHECKER', 0),
+	(71, 'organisation', 'READ_LOANPRODUCT', 'LOANPRODUCT', 'READ', 0),
+	(72, 'organisation', 'CREATE_LOANPRODUCT', 'LOANPRODUCT', 'CREATE', 0),
+	(73, 'organisation', 'CREATE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'CREATE_CHECKER', 0),
+	(74, 'organisation', 'UPDATE_LOANPRODUCT', 'LOANPRODUCT', 'UPDATE', 0),
+	(75, 'organisation', 'UPDATE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'UPDATE_CHECKER', 0),
+	(76, 'organisation', 'DELETE_LOANPRODUCT', 'LOANPRODUCT', 'DELETE', 0),
+	(77, 'organisation', 'DELETE_LOANPRODUCT_CHECKER', 'LOANPRODUCT', 'DELETE_CHECKER', 0),
+	(78, 'organisation', 'READ_OFFICE', 'OFFICE', 'READ', 0),
+	(79, 'organisation', 'CREATE_OFFICE', 'OFFICE', 'CREATE', 0),
+	(80, 'organisation', 'CREATE_OFFICE_CHECKER', 'OFFICE', 'CREATE_CHECKER', 0),
+	(81, 'organisation', 'UPDATE_OFFICE', 'OFFICE', 'UPDATE', 0),
+	(82, 'organisation', 'UPDATE_OFFICE_CHECKER', 'OFFICE', 'UPDATE_CHECKER', 0),
+	(83, 'organisation', 'READ_OFFICETRANSACTION', 'OFFICETRANSACTION', 'READ', 0),
+	(84, 'organisation', 'DELETE_OFFICE_CHECKER', 'OFFICE', 'DELETE_CHECKER', 0),
+	(85, 'organisation', 'CREATE_OFFICETRANSACTION', 'OFFICETRANSACTION', 'CREATE', 0),
+	(86, 'organisation', 'CREATE_OFFICETRANSACTION_CHECKER', 'OFFICETRANSACTION', 'CREATE_CHECKER', 0),
+	(87, 'organisation', 'DELETE_OFFICETRANSACTION', 'OFFICETRANSACTION', 'DELETE', 0),
+	(88, 'organisation', 'DELETE_OFFICETRANSACTION_CHECKER', 'OFFICETRANSACTION', 'DELETE_CHECKER', 0),
+	(89, 'organisation', 'READ_STAFF', 'STAFF', 'READ', 0),
+	(90, 'organisation', 'CREATE_STAFF', 'STAFF', 'CREATE', 0),
+	(91, 'organisation', 'CREATE_STAFF_CHECKER', 'STAFF', 'CREATE_CHECKER', 0),
+	(92, 'organisation', 'UPDATE_STAFF', 'STAFF', 'UPDATE', 0),
+	(93, 'organisation', 'UPDATE_STAFF_CHECKER', 'STAFF', 'UPDATE_CHECKER', 0),
+	(94, 'organisation', 'DELETE_STAFF', 'STAFF', 'DELETE', 0),
+	(95, 'organisation', 'DELETE_STAFF_CHECKER', 'STAFF', 'DELETE_CHECKER', 0),
+	(96, 'organisation', 'READ_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'READ', 0),
+	(97, 'organisation', 'CREATE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'CREATE', 0),
+	(98, 'organisation', 'CREATE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'CREATE_CHECKER', 0),
+	(99, 'organisation', 'UPDATE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'UPDATE', 0),
+	(100, 'organisation', 'UPDATE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'UPDATE_CHECKER', 0),
+	(101, 'organisation', 'DELETE_SAVINGSPRODUCT', 'SAVINGSPRODUCT', 'DELETE', 0),
+	(102, 'organisation', 'DELETE_SAVINGSPRODUCT_CHECKER', 'SAVINGSPRODUCT', 'DELETE_CHECKER', 0),
+	(103, 'portfolio', 'READ_LOAN', 'LOAN', 'READ', 0),
+	(104, 'portfolio', 'CREATE_LOAN', 'LOAN', 'CREATE', 0),
+	(105, 'portfolio', 'CREATE_LOAN_CHECKER', 'LOAN', 'CREATE_CHECKER', 0),
+	(106, 'portfolio', 'UPDATE_LOAN', 'LOAN', 'UPDATE', 0),
+	(107, 'portfolio', 'UPDATE_LOAN_CHECKER', 'LOAN', 'UPDATE_CHECKER', 0),
+	(108, 'portfolio', 'DELETE_LOAN', 'LOAN', 'DELETE', 0),
+	(109, 'portfolio', 'DELETE_LOAN_CHECKER', 'LOAN', 'DELETE_CHECKER', 0),
+	(110, 'portfolio', 'READ_CLIENT', 'CLIENT', 'READ', 0),
+	(111, 'portfolio', 'CREATE_CLIENT', 'CLIENT', 'CREATE', 0),
+	(112, 'portfolio', 'CREATE_CLIENT_CHECKER', 'CLIENT', 'CREATE_CHECKER', 0),
+	(113, 'portfolio', 'UPDATE_CLIENT', 'CLIENT', 'UPDATE', 0),
+	(114, 'portfolio', 'UPDATE_CLIENT_CHECKER', 'CLIENT', 'UPDATE_CHECKER', 0),
+	(115, 'portfolio', 'DELETE_CLIENT', 'CLIENT', 'DELETE', 0),
+	(116, 'portfolio', 'DELETE_CLIENT_CHECKER', 'CLIENT', 'DELETE_CHECKER', 0),
+	(117, 'portfolio', 'READ_CLIENTIMAGE', 'CLIENTIMAGE', 'READ', 0),
+	(118, 'portfolio', 'CREATE_CLIENTIMAGE', 'CLIENTIMAGE', 'CREATE', 0),
+	(119, 'portfolio', 'CREATE_CLIENTIMAGE_CHECKER', 'CLIENTIMAGE', 'CREATE_CHECKER', 0),
+	(120, 'portfolio', 'DELETE_CLIENTIMAGE', 'CLIENTIMAGE', 'DELETE', 0),
+	(121, 'portfolio', 'DELETE_CLIENTIMAGE_CHECKER', 'CLIENTIMAGE', 'DELETE_CHECKER', 0),
+	(122, 'portfolio', 'READ_CLIENTNOTE', 'CLIENTNOTE', 'READ', 0),
+	(123, 'portfolio', 'CREATE_CLIENTNOTE', 'CLIENTNOTE', 'CREATE', 0),
+	(124, 'portfolio', 'CREATE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'CREATE_CHECKER', 0),
+	(125, 'portfolio', 'UPDATE_CLIENTNOTE', 'CLIENTNOTE', 'UPDATE', 0),
+	(126, 'portfolio', 'UPDATE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'UPDATE_CHECKER', 0),
+	(127, 'portfolio', 'DELETE_CLIENTNOTE', 'CLIENTNOTE', 'DELETE', 0),
+	(128, 'portfolio', 'DELETE_CLIENTNOTE_CHECKER', 'CLIENTNOTE', 'DELETE_CHECKER', 0),
+	(129, 'portfolio_group', 'READ_GROUPNOTE', 'GROUPNOTE', 'READ', 0),
+	(130, 'portfolio_group', 'CREATE_GROUPNOTE', 'GROUPNOTE', 'CREATE', 0),
+	(131, 'portfolio_group', 'UPDATE_GROUPNOTE', 'GROUPNOTE', 'UPDATE', 0),
+	(132, 'portfolio_group', 'DELETE_GROUPNOTE', 'GROUPNOTE', 'DELETE', 0),
+	(133, 'portfolio_group', 'CREATE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'CREATE_CHECKER', 0),
+	(134, 'portfolio_group', 'UPDATE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'UPDATE_CHECKER', 0),
+	(135, 'portfolio_group', 'DELETE_GROUPNOTE_CHECKER', 'GROUPNOTE', 'DELETE_CHECKER', 0),
+	(136, 'portfolio', 'READ_LOANNOTE', 'LOANNOTE', 'READ', 0),
+	(137, 'portfolio', 'CREATE_LOANNOTE', 'LOANNOTE', 'CREATE', 0),
+	(138, 'portfolio', 'UPDATE_LOANNOTE', 'LOANNOTE', 'UPDATE', 0),
+	(139, 'portfolio', 'DELETE_LOANNOTE', 'LOANNOTE', 'DELETE', 0),
+	(140, 'portfolio', 'CREATE_LOANNOTE_CHECKER', 'LOANNOTE', 'CREATE_CHECKER', 0),
+	(141, 'portfolio', 'UPDATE_LOANNOTE_CHECKER', 'LOANNOTE', 'UPDATE_CHECKER', 0),
+	(142, 'portfolio', 'DELETE_LOANNOTE_CHECKER', 'LOANNOTE', 'DELETE_CHECKER', 0),
+	(143, 'portfolio', 'READ_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'READ', 0),
+	(144, 'portfolio', 'CREATE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'CREATE', 0),
+	(145, 'portfolio', 'UPDATE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'UPDATE', 0),
+	(146, 'portfolio', 'DELETE_LOANTRANSACTIONNOTE', 'LOANTRANSACTIONNOTE', 'DELETE', 0),
+	(147, 'portfolio', 'CREATE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'CREATE_CHECKER', 0),
+	(148, 'portfolio', 'UPDATE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'UPDATE_CHECKER', 0),
+	(149, 'portfolio', 'DELETE_LOANTRANSACTIONNOTE_CHECKER', 'LOANTRANSACTIONNOTE', 'DELETE_CHECKER', 0),
+	(150, 'portfolio', 'READ_SAVINGNOTE', 'SAVINGNOTE', 'READ', 0),
+	(151, 'portfolio', 'CREATE_SAVINGNOTE', 'SAVINGNOTE', 'CREATE', 0),
+	(152, 'portfolio', 'UPDATE_SAVINGNOTE', 'SAVINGNOTE', 'UPDATE', 0),
+	(153, 'portfolio', 'DELETE_SAVINGNOTE', 'SAVINGNOTE', 'DELETE', 0),
+	(154, 'portfolio', 'CREATE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'CREATE_CHECKER', 0),
+	(155, 'portfolio', 'UPDATE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'UPDATE_CHECKER', 0),
+	(156, 'portfolio', 'DELETE_SAVINGNOTE_CHECKER', 'SAVINGNOTE', 'DELETE_CHECKER', 0),
+	(157, 'portfolio', 'READ_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'READ', 0),
+	(158, 'portfolio', 'CREATE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'CREATE', 0),
+	(159, 'portfolio', 'CREATE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'CREATE_CHECKER', 0),
+	(160, 'portfolio', 'UPDATE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'UPDATE', 0),
+	(161, 'portfolio', 'UPDATE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'UPDATE_CHECKER', 0),
+	(162, 'portfolio', 'DELETE_CLIENTIDENTIFIER', 'CLIENTIDENTIFIER', 'DELETE', 0),
+	(163, 'portfolio', 'DELETE_CLIENTIDENTIFIER_CHECKER', 'CLIENTIDENTIFIER', 'DELETE_CHECKER', 0),
+	(164, 'portfolio', 'READ_DOCUMENT', 'DOCUMENT', 'READ', 0),
+	(165, 'portfolio', 'CREATE_DOCUMENT', 'DOCUMENT', 'CREATE', 0),
+	(166, 'portfolio', 'CREATE_DOCUMENT_CHECKER', 'DOCUMENT', 'CREATE_CHECKER', 0),
+	(167, 'portfolio', 'UPDATE_DOCUMENT', 'DOCUMENT', 'UPDATE', 0),
+	(168, 'portfolio', 'UPDATE_DOCUMENT_CHECKER', 'DOCUMENT', 'UPDATE_CHECKER', 0),
+	(169, 'portfolio', 'DELETE_DOCUMENT', 'DOCUMENT', 'DELETE', 0),
+	(170, 'portfolio', 'DELETE_DOCUMENT_CHECKER', 'DOCUMENT', 'DELETE_CHECKER', 0),
+	(171, 'portfolio_group', 'READ_GROUP', 'GROUP', 'READ', 0),
+	(172, 'portfolio_group', 'CREATE_GROUP', 'GROUP', 'CREATE', 0),
+	(173, 'portfolio_group', 'CREATE_GROUP_CHECKER', 'GROUP', 'CREATE_CHECKER', 0),
+	(174, 'portfolio_group', 'UPDATE_GROUP', 'GROUP', 'UPDATE', 0),
+	(175, 'portfolio_group', 'UPDATE_GROUP_CHECKER', 'GROUP', 'UPDATE_CHECKER', 0),
+	(176, 'portfolio_group', 'DELETE_GROUP', 'GROUP', 'DELETE', 0),
+	(177, 'portfolio_group', 'DELETE_GROUP_CHECKER', 'GROUP', 'DELETE_CHECKER', 0),
+	(178, 'portfolio_group', 'UNASSIGNSTAFF_GROUP', 'GROUP', 'UNASSIGNSTAFF', 0),
+	(179, 'portfolio_group', 'UNASSIGNSTAFF_GROUP_CHECKER', 'GROUP', 'UNASSIGNSTAFF_CHECKER', 0),
+	(180, 'portfolio', 'CREATE_LOANCHARGE', 'LOANCHARGE', 'CREATE', 0),
+	(181, 'portfolio', 'CREATE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'CREATE_CHECKER', 0),
+	(182, 'portfolio', 'UPDATE_LOANCHARGE', 'LOANCHARGE', 'UPDATE', 0),
+	(183, 'portfolio', 'UPDATE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'UPDATE_CHECKER', 0),
+	(184, 'portfolio', 'DELETE_LOANCHARGE', 'LOANCHARGE', 'DELETE', 0),
+	(185, 'portfolio', 'DELETE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'DELETE_CHECKER', 0),
+	(186, 'portfolio', 'WAIVE_LOANCHARGE', 'LOANCHARGE', 'WAIVE', 0),
+	(187, 'portfolio', 'WAIVE_LOANCHARGE_CHECKER', 'LOANCHARGE', 'WAIVE_CHECKER', 0),
+	(188, 'portfolio', 'READ_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'READ', 0),
+	(189, 'portfolio', 'CREATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CREATE', 0),
+	(190, 'portfolio', 'CREATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CREATE_CHECKER', 0),
+	(191, 'portfolio', 'UPDATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UPDATE', 0),
+	(192, 'portfolio', 'UPDATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UPDATE_CHECKER', 0),
+	(193, 'portfolio', 'DELETE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DELETE', 0),
+	(194, 'portfolio', 'DELETE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DELETE_CHECKER', 0),
+	(195, 'portfolio', 'READ_GUARANTOR', 'GUARANTOR', 'READ', 0),
+	(196, 'portfolio', 'CREATE_GUARANTOR', 'GUARANTOR', 'CREATE', 0),
+	(197, 'portfolio', 'CREATE_GUARANTOR_CHECKER', 'GUARANTOR', 'CREATE_CHECKER', 0),
+	(198, 'portfolio', 'UPDATE_GUARANTOR', 'GUARANTOR', 'UPDATE', 0),
+	(199, 'portfolio', 'UPDATE_GUARANTOR_CHECKER', 'GUARANTOR', 'UPDATE_CHECKER', 0),
+	(200, 'portfolio', 'DELETE_GUARANTOR', 'GUARANTOR', 'DELETE', 0),
+	(201, 'portfolio', 'DELETE_GUARANTOR_CHECKER', 'GUARANTOR', 'DELETE_CHECKER', 0),
+	(202, 'portfolio', 'READ_COLLATERAL', 'COLLATERAL', 'READ', 0),
+	(203, 'portfolio', 'CREATE_COLLATERAL', 'COLLATERAL', 'CREATE', 0),
+	(204, 'portfolio', 'UPDATE_COLLATERAL', 'COLLATERAL', 'UPDATE', 0),
+	(205, 'portfolio', 'DELETE_COLLATERAL', 'COLLATERAL', 'DELETE', 0),
+	(206, 'portfolio', 'CREATE_COLLATERAL_CHECKER', 'COLLATERAL', 'CREATE_CHECKER', 0),
+	(207, 'portfolio', 'UPDATE_COLLATERAL_CHECKER', 'COLLATERAL', 'UPDATE_CHECKER', 0),
+	(208, 'portfolio', 'DELETE_COLLATERAL_CHECKER', 'COLLATERAL', 'DELETE_CHECKER', 0),
+	(209, 'transaction_loan', 'APPROVE_LOAN', 'LOAN', 'APPROVE', 0),
+	(211, 'transaction_loan', 'REJECT_LOAN', 'LOAN', 'REJECT', 0),
+	(213, 'transaction_loan', 'WITHDRAW_LOAN', 'LOAN', 'WITHDRAW', 0),
+	(215, 'transaction_loan', 'APPROVALUNDO_LOAN', 'LOAN', 'APPROVALUNDO', 0),
+	(216, 'transaction_loan', 'DISBURSE_LOAN', 'LOAN', 'DISBURSE', 0),
+	(218, 'transaction_loan', 'DISBURSALUNDO_LOAN', 'LOAN', 'DISBURSALUNDO', 0),
+	(219, 'transaction_loan', 'REPAYMENT_LOAN', 'LOAN', 'REPAYMENT', 0),
+	(221, 'transaction_loan', 'ADJUST_LOAN', 'LOAN', 'ADJUST', 0),
+	(222, 'transaction_loan', 'WAIVEINTERESTPORTION_LOAN', 'LOAN', 'WAIVEINTERESTPORTION', 0),
+	(223, 'transaction_loan', 'WRITEOFF_LOAN', 'LOAN', 'WRITEOFF', 0),
+	(224, 'transaction_loan', 'CLOSE_LOAN', 'LOAN', 'CLOSE', 0),
+	(225, 'transaction_loan', 'CLOSEASRESCHEDULED_LOAN', 'LOAN', 'CLOSEASRESCHEDULED', 0),
+	(226, 'transaction_loan', 'UPDATELOANOFFICER_LOAN', 'LOAN', 'UPDATELOANOFFICER', 0),
+	(227, 'transaction_loan', 'UPDATELOANOFFICER_LOAN_CHECKER', 'LOAN', 'UPDATELOANOFFICER_CHECKER', 0),
+	(228, 'transaction_loan', 'REMOVELOANOFFICER_LOAN', 'LOAN', 'REMOVELOANOFFICER', 0),
+	(229, 'transaction_loan', 'REMOVELOANOFFICER_LOAN_CHECKER', 'LOAN', 'REMOVELOANOFFICER_CHECKER', 0),
+	(230, 'transaction_loan', 'BULKREASSIGN_LOAN', 'LOAN', 'BULKREASSIGN', 0),
+	(231, 'transaction_loan', 'BULKREASSIGN_LOAN_CHECKER', 'LOAN', 'BULKREASSIGN_CHECKER', 0),
+	(232, 'transaction_loan', 'APPROVE_LOAN_CHECKER', 'LOAN', 'APPROVE_CHECKER', 0),
+	(234, 'transaction_loan', 'REJECT_LOAN_CHECKER', 'LOAN', 'REJECT_CHECKER', 0),
+	(236, 'transaction_loan', 'WITHDRAW_LOAN_CHECKER', 'LOAN', 'WITHDRAW_CHECKER', 0),
+	(238, 'transaction_loan', 'APPROVALUNDO_LOAN_CHECKER', 'LOAN', 'APPROVALUNDO_CHECKER', 0),
+	(239, 'transaction_loan', 'DISBURSE_LOAN_CHECKER', 'LOAN', 'DISBURSE_CHECKER', 0),
+	(241, 'transaction_loan', 'DISBURSALUNDO_LOAN_CHECKER', 'LOAN', 'DISBURSALUNDO_CHECKER', 0),
+	(242, 'transaction_loan', 'REPAYMENT_LOAN_CHECKER', 'LOAN', 'REPAYMENT_CHECKER', 0),
+	(244, 'transaction_loan', 'ADJUST_LOAN_CHECKER', 'LOAN', 'ADJUST_CHECKER', 0),
+	(245, 'transaction_loan', 'WAIVEINTERESTPORTION_LOAN_CHECKER', 'LOAN', 'WAIVEINTERESTPORTION_CHECKER', 0),
+	(246, 'transaction_loan', 'WRITEOFF_LOAN_CHECKER', 'LOAN', 'WRITEOFF_CHECKER', 0),
+	(247, 'transaction_loan', 'CLOSE_LOAN_CHECKER', 'LOAN', 'CLOSE_CHECKER', 0),
+	(248, 'transaction_loan', 'CLOSEASRESCHEDULED_LOAN_CHECKER', 'LOAN', 'CLOSEASRESCHEDULED_CHECKER', 0),
+	(249, 'transaction_savings', 'DEPOSIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DEPOSIT', 0),
+	(250, 'transaction_savings', 'DEPOSIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DEPOSIT_CHECKER', 0),
+	(251, 'transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAWAL', 0),
+	(252, 'transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAWAL_CHECKER', 0),
+	(253, 'transaction_savings', 'ACTIVATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ACTIVATE', 0),
+	(254, 'transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE_CHECKER', 0),
+	(255, 'transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', 0),
+	(256, 'transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CALCULATEINTEREST_CHECKER', 0),
+	(257, 'accounting', 'CREATE_GLACCOUNT', 'GLACCOUNT', 'CREATE', 0),
+	(258, 'accounting', 'UPDATE_GLACCOUNT', 'GLACCOUNT', 'UPDATE', 0),
+	(259, 'accounting', 'DELETE_GLACCOUNT', 'GLACCOUNT', 'DELETE', 0),
+	(260, 'accounting', 'CREATE_GLCLOSURE', 'GLCLOSURE', 'CREATE', 0),
+	(261, 'accounting', 'UPDATE_GLCLOSURE', 'GLCLOSURE', 'UPDATE', 0),
+	(262, 'accounting', 'DELETE_GLCLOSURE', 'GLCLOSURE', 'DELETE', 0),
+	(263, 'accounting', 'CREATE_JOURNALENTRY', 'JOURNALENTRY', 'CREATE', 0),
+	(264, 'accounting', 'REVERSE_JOURNALENTRY', 'JOURNALENTRY', 'REVERSE', 0),
+	(265, 'report', 'READ_Active Loans - Details', 'Active Loans - Details', 'READ', 0),
+	(266, 'report', 'READ_Active Loans - Summary', 'Active Loans - Summary', 'READ', 0),
+	(267, 'report', 'READ_Active Loans by Disbursal Period', 'Active Loans by Disbursal Period', 'READ', 0),
+	(268, 'report', 'READ_Active Loans in last installment', 'Active Loans in last installment', 'READ', 0),
+	(269, 'report', 'READ_Active Loans in last installment Summary', 'Active Loans in last installment Summary', 'READ', 0),
+	(270, 'report', 'READ_Active Loans Passed Final Maturity', 'Active Loans Passed Final Maturity', 'READ', 0),
+	(271, 'report', 'READ_Active Loans Passed Final Maturity Summary', 'Active Loans Passed Final Maturity Summary', 'READ', 0),
+	(272, 'report', 'READ_Aging Detail', 'Aging Detail', 'READ', 0),
+	(273, 'report', 'READ_Aging Summary (Arrears in Months)', 'Aging Summary (Arrears in Months)', 'READ', 0),
+	(274, 'report', 'READ_Aging Summary (Arrears in Weeks)', 'Aging Summary (Arrears in Weeks)', 'READ', 0),
+	(275, 'report', 'READ_Balance Sheet', 'Balance Sheet', 'READ', 0),
+	(276, 'report', 'READ_Branch Expected Cash Flow', 'Branch Expected Cash Flow', 'READ', 0),
+	(277, 'report', 'READ_Client Listing', 'Client Listing', 'READ', 0),
+	(278, 'report', 'READ_Client Loans Listing', 'Client Loans Listing', 'READ', 0),
+	(279, 'report', 'READ_Expected Payments By Date - Basic', 'Expected Payments By Date - Basic', 'READ', 0),
+	(280, 'report', 'READ_Expected Payments By Date - Formatted', 'Expected Payments By Date - Formatted', 'READ', 0),
+	(281, 'report', 'READ_Funds Disbursed Between Dates Summary', 'Funds Disbursed Between Dates Summary', 'READ', 0),
+	(282, 'report', 'READ_Funds Disbursed Between Dates Summary by Office', 'Funds Disbursed Between Dates Summary by Office', 'READ', 0),
+	(283, 'report', 'READ_Income Statement', 'Income Statement', 'READ', 0),
+	(284, 'report', 'READ_Loan Account Schedule', 'Loan Account Schedule', 'READ', 0),
+	(285, 'report', 'READ_Loans Awaiting Disbursal', 'Loans Awaiting Disbursal', 'READ', 0),
+	(286, 'report', 'READ_Loans Awaiting Disbursal Summary', 'Loans Awaiting Disbursal Summary', 'READ', 0),
+	(287, 'report', 'READ_Loans Awaiting Disbursal Summary by Month', 'Loans Awaiting Disbursal Summary by Month', 'READ', 0),
+	(288, 'report', 'READ_Loans Pending Approval', 'Loans Pending Approval', 'READ', 0),
+	(289, 'report', 'READ_Obligation Met Loans Details', 'Obligation Met Loans Details', 'READ', 0),
+	(290, 'report', 'READ_Obligation Met Loans Summary', 'Obligation Met Loans Summary', 'READ', 0),
+	(291, 'report', 'READ_Portfolio at Risk', 'Portfolio at Risk', 'READ', 0),
+	(292, 'report', 'READ_Portfolio at Risk by Branch', 'Portfolio at Risk by Branch', 'READ', 0),
+	(293, 'report', 'READ_Rescheduled Loans', 'Rescheduled Loans', 'READ', 0),
+	(294, 'report', 'READ_Trial Balance', 'Trial Balance', 'READ', 0),
+	(295, 'report', 'READ_Written-Off Loans', 'Written-Off Loans', 'READ', 0),
+	(296, 'transaction_savings', 'POSTINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'POSTINTEREST', 1),
+	(297, 'transaction_savings', 'POSTINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'POSTINTEREST_CHECKER', 0),
+	(298, 'portfolio_center', 'READ_CENTER', 'CENTER', 'READ', 0),
+	(299, 'portfolio_center', 'CREATE_CENTER', 'CENTER', 'CREATE', 0),
+	(300, 'portfolio_center', 'CREATE_CENTER_CHECKER', 'CENTER', 'CREATE_CHECKER', 0),
+	(301, 'portfolio_center', 'UPDATE_CENTER', 'CENTER', 'UPDATE', 0),
+	(302, 'portfolio_center', 'UPDATE_CENTER_CHECKER', 'CENTER', 'UPDATE_CHECKER', 0),
+	(303, 'portfolio_center', 'DELETE_CENTER', 'CENTER', 'DELETE', 0),
+	(304, 'portfolio_center', 'DELETE_CENTER_CHECKER', 'CENTER', 'DELETE_CHECKER', 0),
+	(305, 'configuration', 'READ_REPORT', 'REPORT', 'READ', 0),
+	(306, 'configuration', 'CREATE_REPORT', 'REPORT', 'CREATE', 0),
+	(307, 'configuration', 'CREATE_REPORT_CHECKER', 'REPORT', 'CREATE_CHECKER', 0),
+	(308, 'configuration', 'UPDATE_REPORT', 'REPORT', 'UPDATE', 0),
+	(309, 'configuration', 'UPDATE_REPORT_CHECKER', 'REPORT', 'UPDATE_CHECKER', 0),
+	(310, 'configuration', 'DELETE_REPORT', 'REPORT', 'DELETE', 0),
+	(311, 'configuration', 'DELETE_REPORT_CHECKER', 'REPORT', 'DELETE_CHECKER', 0),
+	(312, 'portfolio', 'ACTIVATE_CLIENT', 'CLIENT', 'ACTIVATE', 1),
+	(313, 'portfolio', 'ACTIVATE_CLIENT_CHECKER', 'CLIENT', 'ACTIVATE_CHECKER', 0),
+	(314, 'portfolio_center', 'ACTIVATE_CENTER', 'CENTER', 'ACTIVATE', 1),
+	(315, 'portfolio_center', 'ACTIVATE_CENTER_CHECKER', 'CENTER', 'ACTIVATE_CHECKER', 0),
+	(316, 'portfolio_group', 'ACTIVATE_GROUP', 'GROUP', 'ACTIVATE', 1),
+	(317, 'portfolio_group', 'ACTIVATE_GROUP_CHECKER', 'GROUP', 'ACTIVATE_CHECKER', 0),
+	(318, 'portfolio_group', 'ASSOCIATECLIENTS_GROUP', 'GROUP', 'ASSOCIATECLIENTS', 0),
+	(319, 'portfolio_group', 'DISASSOCIATECLIENTS_GROUP', 'GROUP', 'DISASSOCIATECLIENTS', 0),
+	(320, 'portfolio_group', 'SAVECOLLECTIONSHEET_GROUP', 'GROUP', 'SAVECOLLECTIONSHEET', 0),
+	(321, 'portfolio_center', 'SAVECOLLECTIONSHEET_CENTER', 'CENTER', 'SAVECOLLECTIONSHEET', 0),
+	(323, 'accounting', 'DELETE_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'DELETE', 0),
+	(324, 'accounting', 'CREATE_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'CREATE', 0),
+	(325, 'accounting', 'UPDATE_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'UPDATE', 0),
+	(326, 'report', 'READ_GroupSummaryCounts', 'GroupSummaryCounts', 'READ', 0),
+	(327, 'report', 'READ_GroupSummaryAmounts', 'GroupSummaryAmounts', 'READ', 0),
+	(328, 'configuration', 'CREATE_DATATABLE', 'DATATABLE', 'CREATE', 0),
+	(329, 'configuration', 'CREATE_DATATABLE_CHECKER', 'DATATABLE', 'CREATE_CHECKER', 0),
+	(330, 'configuration', 'UPDATE_DATATABLE', 'DATATABLE', 'UPDATE', 0),
+	(331, 'configuration', 'UPDATE_DATATABLE_CHECKER', 'DATATABLE', 'UPDATE_CHECKER', 0),
+	(332, 'configuration', 'DELETE_DATATABLE', 'DATATABLE', 'DELETE', 0),
+	(333, 'configuration', 'DELETE_DATATABLE_CHECKER', 'DATATABLE', 'DELETE_CHECKER', 0),
+	(334, 'organisation', 'CREATE_HOLIDAY', 'HOLIDAY', 'CREATE', 0),
+	(335, 'portfolio_group', 'ASSIGNROLE_GROUP', 'GROUP', 'ASSIGNROLE', 0),
+	(336, 'portfolio_group', 'UNASSIGNROLE_GROUP', 'GROUP', 'UNASSIGNROLE', 0),
+	(337, 'portfolio_group', 'UPDATEROLE_GROUP', 'GROUP', 'UPDATEROLE', 0),
+	(346, 'report', 'READ_TxnRunningBalances', 'TxnRunningBalances', 'READ', 0),
+	(347, 'portfolio', 'UNASSIGNSTAFF_CLIENT', 'CLIENT', 'UNASSIGNSTAFF', 0),
+	(348, 'portfolio', 'ASSIGNSTAFF_CLIENT', 'CLIENT', 'ASSIGNSTAFF', 0),
+	(349, 'portfolio', 'CLOSE_CLIENT', 'CLIENT', 'CLOSE', 1),
+	(350, 'report', 'READ_FieldAgentStats', 'FieldAgentStats', 'READ', 0),
+	(351, 'report', 'READ_FieldAgentPrograms', 'FieldAgentPrograms', 'READ', 0),
+	(352, 'report', 'READ_ProgramDetails', 'ProgramDetails', 'READ', 0),
+	(353, 'report', 'READ_ChildrenStaffList', 'ChildrenStaffList', 'READ', 0),
+	(354, 'report', 'READ_CoordinatorStats', 'CoordinatorStats', 'READ', 0),
+	(355, 'report', 'READ_BranchManagerStats', 'BranchManagerStats', 'READ', 0),
+	(356, 'report', 'READ_ProgramDirectorStats', 'ProgramDirectorStats', 'READ', 0),
+	(357, 'report', 'READ_ProgramStats', 'ProgramStats', 'READ', 0),
+	(358, 'transaction_savings', 'APPROVE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVE', 1),
+	(359, 'transaction_savings', 'REJECT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'REJECT', 1),
+	(360, 'transaction_savings', 'WITHDRAW_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAW', 1),
+	(361, 'transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVALUNDO', 1),
+	(362, 'transaction_savings', 'CLOSE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CLOSE', 1),
+	(363, 'transaction_savings', 'APPROVE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVE_CHECKER', 0),
+	(364, 'transaction_savings', 'REJECT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'REJECT_CHECKER', 0),
+	(365, 'transaction_savings', 'WITHDRAW_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAW_CHECKER', 0),
+	(366, 'transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVALUNDO_CHECKER', 0),
+	(367, 'transaction_savings', 'CLOSE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CLOSE_CHECKER', 0),
+	(368, 'transaction_savings', 'UNDOTRANSACTION_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UNDOTRANSACTION', 1),
+	(369, 'transaction_savings', 'UNDOTRANSACTION_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UNDOTRANSACTION_CHECKER', 0),
+	(370, 'portfolio', 'CREATE_PRODUCTMIX', 'PRODUCTMIX', 'CREATE', 0),
+	(371, 'portfolio', 'UPDATE_PRODUCTMIX', 'PRODUCTMIX', 'UPDATE', 0),
+	(372, 'portfolio', 'DELETE_PRODUCTMIX', 'PRODUCTMIX', 'DELETE', 0),
+	(373, 'jobs', 'UPDATE_SCHEDULER', 'SCHEDULER', 'UPDATE', 0),
+	(374, 'transaction_savings', 'APPLYANNUALFEE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPLYANNUALFEE', 1),
+	(375, 'transaction_savings', 'APPLYANNUALFEE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPLYANNUALFEE_CHECKER', 0),
+	(376, 'portfolio_group', 'ASSIGNSTAFF_GROUP', 'GROUP', 'ASSIGNSTAFF', 0),
+	(377, 'transaction_savings', 'READ_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'READ', 0),
+	(378, 'transaction_savings', 'CREATE_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'CREATE', 1),
+	(379, 'transaction_savings', 'CREATE_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'CREATE_CHECKER', 0),
+	(380, 'transaction_savings', 'ADJUSTTRANSACTION_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ADJUSTTRANSACTION', 0),
+	(381, 'portfolio', 'CREATE_MEETING', 'MEETING', 'CREATE', 0),
+	(382, 'portfolio', 'UPDATE_MEETING', 'MEETING', 'UPDATE', 0),
+	(383, 'portfolio', 'DELETE_MEETING', 'MEETING', 'DELETE', 0),
+	(384, 'portfolio', 'SAVEORUPDATEATTENDANCE_MEETING', 'MEETING', 'SAVEORUPDATEATTENDANCE', 0),
+	(385, 'portfolio_group', 'TRANSFERCLIENTS_GROUP', 'GROUP', 'TRANSFERCLIENTS', 0),
+	(386, 'portfolio_group', 'TRANSFERCLIENTS_GROUP_CHECKER', 'GROUP', 'TRANSFERCLIENTS_CHECKER', 0),
+	(389, 'portfolio', 'PROPOSETRANSFER_CLIENT', 'CLIENT', 'PROPOSETRANSFER', 0),
+	(390, 'portfolio', 'PROPOSETRANSFER_CLIENT_CHECKER', 'CLIENT', 'PROPOSETRANSFER_CHECKER', 0),
+	(391, 'portfolio', 'ACCEPTTRANSFER_CLIENT', 'CLIENT', 'ACCEPTTRANSFER', 0),
+	(392, 'portfolio', 'ACCEPTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'ACCEPTTRANSFER_CHECKER', 0),
+	(393, 'portfolio', 'REJECTTRANSFER_CLIENT', 'CLIENT', 'REJECTTRANSFER', 0),
+	(394, 'portfolio', 'REJECTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'REJECTTRANSFER_CHECKER', 0),
+	(395, 'portfolio', 'WITHDRAWTRANSFER_CLIENT', 'CLIENT', 'WITHDRAWTRANSFER', 0),
+	(396, 'portfolio', 'WITHDRAWTRANSFER_CLIENT_CHECKER', 'CLIENT', 'WITHDRAWTRANSFER_CHECKER', 0),
+	(397, 'portfolio', 'CLOSE_GROUP', 'GROUP', 'CLOSE', 1),
+	(398, 'portfolio', 'CLOSE_CENTER', 'CENTER', 'CLOSE', 1),
+	(399, 'xbrlmapping', 'UPDATE_XBRLMAPPING', 'XBRLMAPPING', 'UPDATE', 0),
+	(400, 'configuration', 'READ_CACHE', 'CACHE', 'READ', 0),
+	(401, 'configuration', 'UPDATE_CACHE', 'CACHE', 'UPDATE', 0),
+	(402, 'transaction_loan', 'PAY_LOANCHARGE', 'LOANCHARGE', 'PAY', 0),
+	(403, 'portfolio', 'CREATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'CREATE', 0),
+	(404, 'portfolio', 'CREATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'CREATE_CHECKER', 0),
+	(405, 'portfolio', 'UPDATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'UPDATE', 0),
+	(406, 'portfolio', 'UPDATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'UPDATE_CHECKER', 0),
+	(407, 'portfolio', 'DELETE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'DELETE', 0),
+	(408, 'portfolio', 'DELETE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'DELETE_CHECKER', 0),
+	(409, 'portfolio', 'WAIVE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'WAIVE', 0),
+	(410, 'portfolio', 'WAIVE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'WAIVE_CHECKER', 0),
+	(411, 'portfolio', 'PAY_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'PAY', 0),
+	(412, 'portfolio', 'PAY_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'PAY_CHECKER', 0),
+	(413, 'portfolio', 'PROPOSEANDACCEPTTRANSFER_CLIENT', 'CLIENT', 'PROPOSEANDACCEPTTRANSFER', 0),
+	(414, 'portfolio', 'PROPOSEANDACCEPTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'PROPOSEANDACCEPTTRANSFER_CHECKER', 0),
+	(415, 'organisation', 'DELETE_TEMPLATE', 'TEMPLATE', 'DELETE', 0),
+	(416, 'organisation', 'CREATE_TEMPLATE', 'TEMPLATE', 'CREATE', 0),
+	(417, 'organisation', 'UPDATE_TEMPLATE', 'TEMPLATE', 'UPDATE', 0),
+	(418, 'organisation', 'READ_TEMPLATE', 'TEMPLATE', 'READ', 0),
+	(419, 'accounting', 'UPDATERUNNINGBALANCE_JOURNALENTRY', 'JOURNALENTRY', 'UPDATERUNNINGBALANCE', 0),
+	(420, 'organisation', 'READ_SMS', 'SMS', 'READ', 0),
+	(421, 'organisation', 'CREATE_SMS', 'SMS', 'CREATE', 0),
+	(422, 'organisation', 'CREATE_SMS_CHECKER', 'SMS', 'CREATE_CHECKER', 0),
+	(423, 'organisation', 'UPDATE_SMS', 'SMS', 'UPDATE', 0),
+	(424, 'organisation', 'UPDATE_SMS_CHECKER', 'SMS', 'UPDATE_CHECKER', 0),
+	(425, 'organisation', 'DELETE_SMS', 'SMS', 'DELETE', 0),
+	(426, 'organisation', 'DELETE_SMS_CHECKER', 'SMS', 'DELETE_CHECKER', 0),
+	(427, 'organisation', 'CREATE_HOLIDAY_CHECKER', 'HOLIDAY', 'CREATE_CHECKER', 0),
+	(428, 'organisation', 'ACTIVATE_HOLIDAY', 'HOLIDAY', 'ACTIVATE', 0),
+	(429, 'organisation', 'ACTIVATE_HOLIDAY_CHECKER', 'HOLIDAY', 'ACTIVATE_CHECKER', 0),
+	(430, 'organisation', 'UPDATE_HOLIDAY', 'HOLIDAY', 'UPDATE', 0),
+	(431, 'organisation', 'UPDATE_HOLIDAY_CHECKER', 'HOLIDAY', 'UPDATE_CHECKER', 0),
+	(432, 'organisation', 'DELETE_HOLIDAY', 'HOLIDAY', 'DELETE', 0),
+	(433, 'organisation', 'DELETE_HOLIDAY_CHECKER', 'HOLIDAY', 'DELETE_CHECKER', 0),
+	(434, 'transaction_loan', 'UNDOWRITEOFF_LOAN', 'LOAN', 'UNDOWRITEOFF', 0),
+	(435, 'portfolio', 'READ_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'READ', 0),
+	(436, 'accounting', 'CREATE_JOURNALENTRY_CHECKER', 'JOURNALENTRY', 'CREATE_CHECKER', 0),
+	(437, 'portfolio', 'UPDATE_DISBURSEMENTDETAIL', 'DISBURSEMENTDETAIL', 'UPDATE', 0),
+	(438, 'portfolio', 'UPDATESAVINGSACCOUNT_CLIENT', 'CLIENT', 'UPDATESAVINGSACCOUNT', 0),
+	(439, 'accounting', 'READ_ACCOUNTINGRULE', 'ACCOUNTINGRULE', 'READ', 0),
+	(440, 'accounting', 'READ_JOURNALENTRY', 'JOURNALENTRY', 'READ', 0),
+	(441, 'accounting', 'READ_GLACCOUNT', 'GLACCOUNT', 'READ', 0),
+	(442, 'accounting', 'READ_GLCLOSURE', 'GLCLOSURE', 'READ', 0),
+	(443, 'organisation', 'READ_HOLIDAY', 'HOLIDAY', 'READ', 0),
+	(444, 'jobs', 'READ_SCHEDULER', 'SCHEDULER', 'READ', 0),
+	(445, 'portfolio', 'READ_PRODUCTMIX', 'PRODUCTMIX', 'READ', 0),
+	(446, 'portfolio', 'READ_MEETING', 'MEETING', 'READ', 0),
+	(447, 'jobs', 'EXECUTEJOB_SCHEDULER', 'SCHEDULER', 'EXECUTEJOB', 0),
+	(448, 'account_transfer', 'READ_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'READ', 0),
+	(449, 'account_transfer', 'CREATE_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'CREATE', 0),
+	(450, 'account_transfer', 'UPDATE_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'UPDATE', 0),
+	(451, 'account_transfer', 'DELETE_STANDINGINSTRUCTION ', 'STANDINGINSTRUCTION ', 'DELETE', 0),
+	(452, 'portfolio', 'CREATE_INTERESTRATECHART', 'INTERESTRATECHART', 'CREATE', 0),
+	(453, 'portfolio', 'CREATE_INTERESTRATECHART_CHECKER', 'INTERESTRATECHART', 'CREATE_CHECKER', 0),
+	(454, 'portfolio', 'UPDATE_INTERESTRATECHART', 'INTERESTRATECHART', 'UPDATE', 0),
+	(455, 'portfolio', 'DELETE_INTERESTRATECHART', 'INTERESTRATECHART', 'DELETE', 0),
+	(456, 'portfolio', 'UPDATE_INTERESTRATECHART_CHECKER', 'INTERESTRATECHART', 'UPDATE_CHECKER', 0),
+	(457, 'portfolio', 'DELETE_INTERESTRATECHART_CHECKER', 'INTERESTRATECHART', 'DELETE_CHECKER', 0),
+	(458, 'portfolio', 'CREATE_CHARTSLAB', 'CHARTSLAB', 'CREATE', 0),
+	(459, 'portfolio', 'CREATE_CHARTSLAB_CHECKER', 'CHARTSLAB', 'CREATE_CHECKER', 0),
+	(460, 'portfolio', 'UPDATE_CHARTSLAB', 'CHARTSLAB', 'UPDATE', 0),
+	(461, 'portfolio', 'DELETE_CHARTSLAB', 'CHARTSLAB', 'DELETE', 0),
+	(462, 'portfolio', 'UPDATE_CHARTSLAB_CHECKER', 'CHARTSLAB', 'UPDATE_CHECKER', 0),
+	(463, 'portfolio', 'DELETE_CHARTSLAB_CHECKER', 'CHARTSLAB', 'DELETE_CHECKER', 0),
+	(464, 'portfolio', 'CREATE_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'CREATE', 0),
+	(465, 'portfolio', 'CREATE_FIXEDDEPOSITPRODUCT_CHECKER', 'FIXEDDEPOSITPRODUCT', 'CREATE_CHECKER', 0),
+	(466, 'portfolio', 'UPDATE_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'UPDATE', 0),
+	(467, 'portfolio', 'DELETE_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'DELETE', 0),
+	(468, 'portfolio', 'UPDATE_FIXEDDEPOSITPRODUCT_CHECKER', 'FIXEDDEPOSITPRODUCT', 'UPDATE_CHECKER', 0),
+	(469, 'portfolio', 'DELETE_FIXEDDEPOSITPRODUCT_CHECKER', 'FIXEDDEPOSITPRODUCT', 'DELETE_CHECKER', 0),
+	(470, 'portfolio', 'CREATE_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'CREATE', 0),
+	(471, 'portfolio', 'CREATE_RECURRINGDEPOSITPRODUCT_CHECKER', 'RECURRINGDEPOSITPRODUCT', 'CREATE_CHECKER', 0),
+	(472, 'portfolio', 'UPDATE_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'UPDATE', 0),
+	(473, 'portfolio', 'DELETE_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'DELETE', 0),
+	(474, 'portfolio', 'UPDATE_RECURRINGDEPOSITPRODUCT_CHECKER', 'RECURRINGDEPOSITPRODUCT', 'UPDATE_CHECKER', 0),
+	(475, 'portfolio', 'DELETE_RECURRINGDEPOSITPRODUCT_CHECKER', 'RECURRINGDEPOSITPRODUCT', 'DELETE_CHECKER', 0),
+	(476, 'portfolio', 'READ_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'READ', 0),
+	(477, 'portfolio', 'CREATE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'CREATE', 0),
+	(478, 'portfolio', 'CREATE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'CREATE_CHECKER', 0),
+	(479, 'portfolio', 'UPDATE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'UPDATE', 0),
+	(480, 'portfolio', 'UPDATE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'UPDATE_CHECKER', 0),
+	(481, 'portfolio', 'DELETE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'DELETE', 0),
+	(482, 'portfolio', 'DELETE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'DELETE_CHECKER', 0),
+	(483, 'transaction_savings', 'DEPOSIT_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'DEPOSIT', 0),
+	(484, 'transaction_savings', 'DEPOSIT_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'DEPOSIT_CHECKER', 0),
+	(485, 'transaction_savings', 'WITHDRAWAL_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'WITHDRAWAL', 0),
+	(486, 'transaction_savings', 'WITHDRAWAL_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'WITHDRAWAL_CHECKER', 0),
+	(487, 'transaction_savings', 'ACTIVATE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'ACTIVATE', 0),
+	(488, 'transaction_savings', 'ACTIVATE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'ACTIVATE_CHECKER', 0),
+	(489, 'transaction_savings', 'CALCULATEINTEREST_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'CALCULATEINTEREST', 0),
+	(490, 'transaction_savings', 'CALCULATEINTEREST_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'CALCULATEINTEREST_CHECKER', 0),
+	(491, 'transaction_savings', 'POSTINTEREST_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'POSTINTEREST', 1),
+	(492, 'transaction_savings', 'POSTINTEREST_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'POSTINTEREST_CHECKER', 0),
+	(493, 'transaction_savings', 'APPROVE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'APPROVE', 1),
+	(494, 'transaction_savings', 'REJECT_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'REJECT', 1),
+	(495, 'transaction_savings', 'WITHDRAW_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'WITHDRAW', 1),
+	(496, 'transaction_savings', 'APPROVALUNDO_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'APPROVALUNDO', 1),
+	(497, 'transaction_savings', 'CLOSE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'CLOSE', 1),
+	(498, 'transaction_savings', 'APPROVE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'APPROVE_CHECKER', 0),
+	(499, 'transaction_savings', 'REJECT_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'REJECT_CHECKER', 0),
+	(500, 'transaction_savings', 'WITHDRAW_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'WITHDRAW_CHECKER', 0),
+	(501, 'transaction_savings', 'APPROVALUNDO_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'APPROVALUNDO_CHECKER', 0),
+	(502, 'transaction_savings', 'CLOSE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'CLOSE_CHECKER', 0),
+	(503, 'transaction_savings', 'UNDOTRANSACTION_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'UNDOTRANSACTION', 1),
+	(504, 'transaction_savings', 'UNDOTRANSACTION_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'UNDOTRANSACTION_CHECKER', 0),
+	(505, 'transaction_savings', 'ADJUSTTRANSACTION_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'ADJUSTTRANSACTION', 0),
+	(506, 'portfolio', 'READ_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'READ', 0),
+	(507, 'portfolio', 'CREATE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'CREATE', 0),
+	(508, 'portfolio', 'CREATE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'CREATE_CHECKER', 0),
+	(509, 'portfolio', 'UPDATE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'UPDATE', 0),
+	(510, 'portfolio', 'UPDATE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'UPDATE_CHECKER', 0),
+	(511, 'portfolio', 'DELETE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'DELETE', 0),
+	(512, 'portfolio', 'DELETE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'DELETE_CHECKER', 0),
+	(513, 'transaction_savings', 'DEPOSIT_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'DEPOSIT', 0),
+	(514, 'transaction_savings', 'DEPOSIT_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'DEPOSIT_CHECKER', 0),
+	(515, 'transaction_savings', 'WITHDRAWAL_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAWAL', 0),
+	(516, 'transaction_savings', 'WITHDRAWAL_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAWAL_CHECKER', 0),
+	(517, 'transaction_savings', 'ACTIVATE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'ACTIVATE', 0),
+	(518, 'transaction_savings', 'ACTIVATE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'ACTIVATE_CHECKER', 0),
+	(519, 'transaction_savings', 'CALCULATEINTEREST_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'CALCULATEINTEREST', 0),
+	(520, 'transaction_savings', 'CALCULATEINTEREST_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'CALCULATEINTEREST_CHECKER', 0),
+	(521, 'transaction_savings', 'POSTINTEREST_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'POSTINTEREST', 1),
+	(522, 'transaction_savings', 'POSTINTEREST_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'POSTINTEREST_CHECKER', 0),
+	(523, 'transaction_savings', 'APPROVE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'APPROVE', 1),
+	(524, 'transaction_savings', 'REJECT_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'REJECT', 1),
+	(525, 'transaction_savings', 'WITHDRAW_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAW', 1),
+	(526, 'transaction_savings', 'APPROVALUNDO_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'APPROVALUNDO', 1),
+	(527, 'transaction_savings', 'CLOSE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'CLOSE', 1),
+	(528, 'transaction_savings', 'APPROVE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'APPROVE_CHECKER', 0),
+	(529, 'transaction_savings', 'REJECT_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'REJECT_CHECKER', 0),
+	(530, 'transaction_savings', 'WITHDRAW_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'WITHDRAW_CHECKER', 0),
+	(531, 'transaction_savings', 'APPROVALUNDO_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'APPROVALUNDO_CHECKER', 0),
+	(532, 'transaction_savings', 'CLOSE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'CLOSE_CHECKER', 0),
+	(533, 'transaction_savings', 'UNDOTRANSACTION_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'UNDOTRANSACTION', 1),
+	(534, 'transaction_savings', 'UNDOTRANSACTION_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'UNDOTRANSACTION_CHECKER', 0),
+	(535, 'transaction_savings', 'ADJUSTTRANSACTION_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'ADJUSTTRANSACTION', 0),
+	(536, 'transaction_savings', 'PREMATURECLOSE_FIXEDDEPOSITACCOUNT_CHECKER', 'FIXEDDEPOSITACCOUNT', 'PREMATURECLOSE_CHECKER', 0),
+	(537, 'transaction_savings', 'PREMATURECLOSE_FIXEDDEPOSITACCOUNT', 'FIXEDDEPOSITACCOUNT', 'PREMATURECLOSE', 1),
+	(538, 'transaction_savings', 'PREMATURECLOSE_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'PREMATURECLOSE_CHECKER', 0),
+	(539, 'transaction_savings', 'PREMATURECLOSE_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'PREMATURECLOSE', 1),
+	(540, 'transaction_loan', 'DISBURSETOSAVINGS_LOAN', 'LOAN', 'DISBURSETOSAVINGS', 0),
+	(541, 'transaction_loan', 'RECOVERYPAYMENT_LOAN', 'LOAN', 'RECOVERYPAYMENT', 0),
+	(542, 'organisation', 'READ_RECURRINGDEPOSITPRODUCT', 'RECURRINGDEPOSITPRODUCT', 'READ', 0),
+	(543, 'organisation', 'READ_FIXEDDEPOSITPRODUCT', 'FIXEDDEPOSITPRODUCT', 'READ', 0),
+	(544, 'accounting', 'READ_FINANCIALACTIVITYACCOUNT', 'FINANCIALACTIVITYACCOUNT', 'READ', 0),
+	(545, 'accounting', 'CREATE_FINANCIALACTIVITYACCOUNT', 'FINANCIALACTIVITYACCOUNT', 'CREATE', 0),
+	(546, 'accounting', 'DELETE_FINANCIALACTIVITYACCOUNT', 'FINANCIALACTIVITYACCOUNT', 'DELETE', 0),
+	(547, 'accounting', 'UPDATE_FINANCIALACTIVITYACCOUNT', 'FINANCIALACTIVITYACCOUNT', 'UPDATE', 0),
+	(548, 'datatable', 'UPDATE_LIKELIHOOD', 'likelihood', 'UPDATE', 0),
+	(549, 'survey', 'REGISTER_SURVEY', 'survey', 'CREATE', 0),
+	(550, 'accounting', 'EXECUTE_PERIODICACCRUALACCOUNTING', 'PERIODICACCRUALACCOUNTING', 'EXECUTE', 0),
+	(551, 'portfolio', 'INACTIVATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'INACTIVATE', 0),
+	(552, 'portfolio', 'INACTIVATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'INACTIVATE_CHECKER', 0),
+	(553, 'portfolio_center', 'DISASSOCIATEGROUPS_CENTER', 'CENTER', 'DISASSOCIATEGROUPS', 0),
+	(554, 'portfolio_center', 'ASSOCIATEGROUPS_CENTER', 'CENTER', 'ASSOCIATEGROUPS', 0),
+	(555, 'portfolio_center', 'DISASSOCIATEGROUPS_CENTER_CHECKER', 'CENTER', 'DISASSOCIATEGROUPS_CHECKER', 0),
+	(556, 'portfolio_center', 'ASSOCIATEGROUPS_CENTER_CHECKER', 'CENTER', 'ASSOCIATEGROUPS_CHECKER', 0),
+	(557, 'loan_reschedule', 'READ_RESCHEDULELOAN', 'RESCHEDULELOAN', 'READ', 0),
+	(558, 'loan_reschedule', 'CREATE_RESCHEDULELOAN', 'RESCHEDULELOAN', 'CREATE', 0),
+	(559, 'loan_reschedule', 'REJECT_RESCHEDULELOAN', 'RESCHEDULELOAN', 'REJECT', 0),
+	(560, 'loan_reschedule', 'APPROVE_RESCHEDULELOAN', 'RESCHEDULELOAN', 'APPROVE', 0),
+	(561, 'configuration', 'CREATE_HOOK', 'HOOK', 'CREATE', 0),
+	(562, 'configuration', 'READ_HOOK', 'HOOK', 'READ', 0),
+	(563, 'configuration', 'UPDATE_HOOK', 'HOOK', 'UPDATE', 0),
+	(564, 'configuration', 'DELETE_HOOK', 'HOOK', 'DELETE', 0),
+	(565, 'portfolio', 'REMOVESAVINGSOFFICER_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'REMOVESAVINGSOFFICER', 1),
+	(566, 'portfolio', 'UPDATESAVINGSOFFICER_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UPDATESAVINGSOFFICER', 1),
+	(567, 'report', 'READ_Active Loans - Summary(Pentaho)', 'Active Loans - Summary(Pentaho)', 'READ', 0),
+	(568, 'report', 'READ_Active Loans by Disbursal Period(Pentaho)', 'Active Loans by Disbursal Period(Pentaho)', 'READ', 0),
+	(569, 'report', 'READ_Active Loans in last installment Summary(Pentaho)', 'Active Loans in last installment Summary(Pentaho)', 'READ', 0),
+	(570, 'report', 'READ_Active Loans in last installment(Pentaho)', 'Active Loans in last installment(Pentaho)', 'READ', 0),
+	(571, 'report', 'READ_Active Loans Passed Final Maturity Summary(Pentaho)', 'Active Loans Passed Final Maturity Summary(Pentaho)', 'READ', 0),
+	(572, 'report', 'READ_Active Loans Passed Final Maturity(Pentaho)', 'Active Loans Passed Final Maturity(Pentaho)', 'READ', 0),
+	(573, 'report', 'READ_Aging Detail(Pentaho)', 'Aging Detail(Pentaho)', 'READ', 0),
+	(574, 'report', 'READ_Aging Summary (Arrears in Months)(Pentaho)', 'Aging Summary (Arrears in Months)(Pentaho)', 'READ', 0),
+	(575, 'report', 'READ_Aging Summary (Arrears in Weeks)(Pentaho)', 'Aging Summary (Arrears in Weeks)(Pentaho)', 'READ', 0),
+	(576, 'report', 'READ_Client Listing(Pentaho)', 'Client Listing(Pentaho)', 'READ', 0),
+	(577, 'report', 'READ_Client Loan Account Schedule', 'Client Loan Account Schedule', 'READ', 0),
+	(578, 'report', 'READ_Client Loans Listing(Pentaho)', 'Client Loans Listing(Pentaho)', 'READ', 0),
+	(579, 'report', 'READ_Client Saving Transactions', 'Client Saving Transactions', 'READ', 0),
+	(580, 'report', 'READ_Client Savings Summary', 'Client Savings Summary', 'READ', 0),
+	(581, 'report', 'READ_ClientSummary ', 'ClientSummary ', 'READ', 0),
+	(582, 'report', 'READ_ClientTrendsByDay', 'ClientTrendsByDay', 'READ', 0),
+	(583, 'report', 'READ_ClientTrendsByMonth', 'ClientTrendsByMonth', 'READ', 0),
+	(584, 'report', 'READ_ClientTrendsByWeek', 'ClientTrendsByWeek', 'READ', 0),
+	(585, 'report', 'READ_Demand_Vs_Collection', 'Demand_Vs_Collection', 'READ', 0),
+	(586, 'report', 'READ_Disbursal_Vs_Awaitingdisbursal', 'Disbursal_Vs_Awaitingdisbursal', 'READ', 0),
+	(587, 'report', 'READ_Expected Payments By Date - Basic(Pentaho)', 'Expected Payments By Date - Basic(Pentaho)', 'READ', 0),
+	(588, 'report', 'READ_Funds Disbursed Between Dates Summary by Office(Pentaho)', 'Funds Disbursed Between Dates Summary by Office(Pentaho)', 'READ', 0),
+	(589, 'report', 'READ_Funds Disbursed Between Dates Summary(Pentaho)', 'Funds Disbursed Between Dates Summary(Pentaho)', 'READ', 0),
+	(590, 'report', 'READ_GroupNamesByStaff', 'GroupNamesByStaff', 'READ', 0),
+	(591, 'report', 'READ_GroupSavingSummary', 'GroupSavingSummary', 'READ', 0),
+	(592, 'report', 'READ_LoanCyclePerProduct', 'LoanCyclePerProduct', 'READ', 0),
+	(593, 'report', 'READ_Loans Awaiting Disbursal Summary by Month(Pentaho)', 'Loans Awaiting Disbursal Summary by Month(Pentaho)', 'READ', 0),
+	(594, 'report', 'READ_Loans Awaiting Disbursal Summary(Pentaho)', 'Loans Awaiting Disbursal Summary(Pentaho)', 'READ', 0),
+	(595, 'report', 'READ_Loans Awaiting Disbursal(Pentaho)', 'Loans Awaiting Disbursal(Pentaho)', 'READ', 0),
+	(596, 'report', 'READ_Loans Pending Approval(Pentaho)', 'Loans Pending Approval(Pentaho)', 'READ', 0),
+	(597, 'report', 'READ_LoanTrendsByDay', 'LoanTrendsByDay', 'READ', 0),
+	(598, 'report', 'READ_LoanTrendsByMonth', 'LoanTrendsByMonth', 'READ', 0),
+	(599, 'report', 'READ_LoanTrendsByWeek', 'LoanTrendsByWeek', 'READ', 0),
+	(600, 'report', 'READ_Obligation Met Loans Details(Pentaho)', 'Obligation Met Loans Details(Pentaho)', 'READ', 0),
+	(601, 'report', 'READ_Obligation Met Loans Summary(Pentaho)', 'Obligation Met Loans Summary(Pentaho)', 'READ', 0),
+	(602, 'report', 'READ_Portfolio at Risk by Branch(Pentaho)', 'Portfolio at Risk by Branch(Pentaho)', 'READ', 0),
+	(603, 'report', 'READ_Portfolio at Risk(Pentaho)', 'Portfolio at Risk(Pentaho)', 'READ', 0),
+	(604, 'report', 'READ_Rescheduled Loans(Pentaho)', 'Rescheduled Loans(Pentaho)', 'READ', 0),
+	(605, 'report', 'READ_Savings Transactions', 'Savings Transactions', 'READ', 0),
+	(606, 'report', 'READ_TxnRunningBalances(Pentaho)', 'TxnRunningBalances(Pentaho)', 'READ', 0),
+	(607, 'report', 'READ_Written-Off Loans(Pentaho)', 'Written-Off Loans(Pentaho)', 'READ', 0),
+	(608, 'configuration', 'CREATE_ACCOUNTNUMBERFORMAT', 'ACCOUNTNUMBERFORMAT', 'CREATE', 0),
+	(609, 'configuration', 'READ_ACCOUNTNUMBERFORMAT', 'ACCOUNTNUMBERFORMAT', 'READ', 0),
+	(610, 'configuration', 'UPDATE_ACCOUNTNUMBERFORMAT', 'ACCOUNTNUMBERFORMAT', 'UPDATE', 0),
+	(611, 'configuration', 'DELETE_ACCOUNTNUMBERFORMAT', 'HOOK', 'DELETE', 0),
+	(612, 'portfolio', 'RECOVERGUARANTEES_LOAN', 'LOAN', 'RECOVERGUARANTEES', 0),
+	(613, 'portfolio', 'RECOVERGUARANTEES_LOAN_CHECKER', 'LOAN', 'RECOVERGUARANTEES_CHECKER', 0),
+	(614, 'portfolio', 'REJECT_CLIENT', 'CLIENT', 'REJECT', 1),
+	(615, 'portfolio', 'REJECT_CLIENT_CHECKER', 'CLIENT', 'REJECT_CHECKER', 0),
+	(616, 'portfolio', 'WITHDRAW_CLIENT', 'CLIENT', 'WITHDRAW', 1),
+	(617, 'portfolio', 'WITHDRAW_CLIENT_CHECKER', 'CLIENT', 'WITHDRAW_CHECKER', 0),
+	(618, 'portfolio', 'REACTIVATE_CLIENT', 'CLIENT', 'REACTIVATE', 1),
+	(619, 'portfolio', 'REACTIVATE_CLIENT_CHECKER', 'CLIENT', 'REACTIVATE_CHECKER', 0),
+	(620, 'transaction_savings', 'UPDATEDEPOSITAMOUNT_RECURRINGDEPOSITACCOUNT', 'RECURRINGDEPOSITACCOUNT', 'UPDATEDEPOSITAMOUNT', 1),
+	(621, 'transaction_savings', 'UPDATEDEPOSITAMOUNT_RECURRINGDEPOSITACCOUNT_CHECKER', 'RECURRINGDEPOSITACCOUNT', 'UPDATEDEPOSITAMOUNT', 1),
+	(622, 'transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 0),
+	(623, 'transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 1),
+	(624, 'transaction_loan', 'REFUNDBYCASH_LOAN', 'LOAN', 'REFUNDBYCASH', 1),
+	(625, 'transaction_loan', 'REFUNDBYCASH_LOAN_CHECKER', 'LOAN', 'REFUNDBYCASH', 0),
+	(626, 'cash_mgmt', 'CREATE_TELLER', 'TELLER', 'CREATE', 1),
+	(627, 'cash_mgmt', 'UPDATE_TELLER', 'TELLER', 'UPDATE', 1),
+	(628, 'cash_mgmt', 'ALLOCATECASHIER_TELLER', 'TELLER', 'ALLOCATE', 1),
+	(629, 'cash_mgmt', 'UPDATECASHIERALLOCATION_TELLER', 'TELLER', 'UPDATECASHIERALLOCATION', 1),
+	(630, 'cash_mgmt', 'DELETECASHIERALLOCATION_TELLER', 'TELLER', 'DELETECASHIERALLOCATION', 1),
+	(631, 'cash_mgmt', 'ALLOCATECASHTOCASHIER_TELLER', 'TELLER', 'ALLOCATECASHTOCASHIER', 1),
+	(632, 'cash_mgmt', 'SETTLECASHFROMCASHIER_TELLER', 'TELLER', 'SETTLECASHFROMCASHIER', 1),
+	(633, 'authorisation', 'DISABLE_ROLE', 'ROLE', 'DISABLE', 0),
+	(634, 'authorisation', 'DISABLE_ROLE_CHECKER', 'ROLE', 'DISABLE_CHECKER', 0),
+	(635, 'authorisation', 'ENABLE_ROLE', 'ROLE', 'ENABLE', 0),
+	(636, 'authorisation', 'ENABLE_ROLE_CHECKER', 'ROLE', 'ENABLE_CHECKER', 0),
+	(637, 'accounting', 'DEFINEOPENINGBALANCE_JOURNALENTRY', 'JOURNALENTRY', 'DEFINEOPENINGBALANCE', 1),
+	(638, 'collection_sheet', 'READ_COLLECTIONSHEET', 'COLLECTIONSHEET', 'READ', 0),
+	(639, 'collection_sheet', 'SAVE_COLLECTIONSHEET', 'COLLECTIONSHEET', 'SAVE', 0),
+	(640, 'infrastructure', 'CREATE_ENTITYMAPPING', 'ENTITYMAPPING', 'CREATE', 0),
+	(641, 'infrastructure', 'UPDATE_ENTITYMAPPING', 'ENTITYMAPPING', 'UPDATE', 0),
+	(642, 'infrastructure', 'DELETE_ENTITYMAPPING', 'ENTITYMAPPING', 'DELETE', 0),
+	(643, 'organisation', 'READ_WORKINGDAYS', 'WORKINGDAYS', 'READ', 0),
+	(644, 'organisation', 'UPDATE_WORKINGDAYS', 'WORKINGDAYS', 'UPDATE', 0),
+	(645, 'organisation', 'UPDATE_WORKINGDAYS_CHECKER', 'WORKINGDAYS', 'UPDATE_CHECKER', 0),
+	(646, 'authorisation', 'READ_PASSWORD_PREFERENCES', 'PASSWORD_PREFERENCES', 'READ', 0),
+	(647, 'authorisation', 'UPDATE_PASSWORD_PREFERENCES', 'PASSWORD_PREFERENCES', 'UPDATE', 0),
+	(648, 'authorisation', 'UPDATE_PASSWORD_PREFERENCES_CHECKER', 'PASSWORD_PREFERENCES', 'UPDATE_CHECKER', 0),
+	(649, 'portfolio', 'CREATE_PAYMENTTYPE', 'PAYMENTTYPE', 'CREATE', 0),
+	(650, 'portfolio', 'UPDATE_PAYMENTTYPE', 'PAYMENTTYPE', 'UPDATE', 0),
+	(651, 'portfolio', 'DELETE_PAYMENTTYPE', 'PAYMENTTYPE', 'DELETE', 0),
+	(652, 'cash_mgmt', 'DELETE_TELLER', 'TELLER', 'DELETE', 1),
+	(653, 'report', 'READ_General Ledger Report', 'General Ledger Report', 'READ', 0),
+	(654, 'portfolio', 'READ_STAFFIMAGE', 'STAFFIMAGE', 'READ', 0),
+	(655, 'portfolio', 'CREATE_STAFFIMAGE', 'STAFFIMAGE', 'CREATE', 1),
+	(656, 'portfolio', 'CREATE_STAFFIMAGE_CHECKER', 'STAFFIMAGE', 'CREATE', 0),
+	(657, 'portfolio', 'DELETE_STAFFIMAGE', 'STAFFIMAGE', 'DELETE', 1),
+	(658, 'portfolio', 'DELETE_STAFFIMAGE_CHECKER', 'STAFFIMAGE', 'DELETE', 0),
+	(659, 'report', 'READ_Active Loan Summary per Branch', 'Active Loan Summary per Branch', 'READ', 0),
+	(660, 'report', 'READ_Disbursal Report', 'Disbursal Report', 'READ', 0),
+	(661, 'report', 'READ_Balance Outstanding', 'Balance Outstanding', 'READ', 0),
+	(662, 'report', 'READ_Collection Report', 'Collection Report', 'READ', 0),
+	(663, 'portfolio', 'READ_PAYMENTTYPE', 'PAYMENTTYPE', 'READ', 0),
+	(664, 'report', 'READ_Staff Assignment History', 'Staff Assignment History(Pentaho)', 'READ', 0),
+	(665, 'externalservices', 'UPDATE_EXTERNALSERVICES', 'EXTERNALSERVICES', 'UPDATE', 0),
+	(666, 'portfolio', 'READ_CLIENTCHARGE', 'CLIENTCHARGE', 'READ', 0),
+	(667, 'portfolio', 'CREATE_CLIENTCHARGE', 'CLIENTCHARGE', 'CREATE', 0),
+	(668, 'portfolio', 'DELETE_CLIENTCHARGE', 'CLIENTCHARGE', 'DELETE', 0),
+	(669, 'portfolio', 'WAIVE_CLIENTCHARGE', 'CLIENTCHARGE', 'WAIVE', 0),
+	(670, 'portfolio', 'PAY_CLIENTCHARGE', 'CLIENTCHARGE', 'PAY', 0),
+	(671, 'portfolio', 'INACTIVATE_CLIENTCHARGE', 'CLIENTCHARGE', 'INACTIVATE', 0),
+	(672, 'portfolio', 'UPDATE_CLIENTCHARGE', 'CLIENTCHARGE', 'UPDATE', 0),
+	(673, 'portfolio', 'CREATE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'CREATE_CHECKER', 0),
+	(674, 'portfolio', 'DELETE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'DELETE_CHECKER', 0),
+	(675, 'portfolio', 'WAIVE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'WAIVE_CHECKER', 0),
+	(676, 'portfolio', 'PAY_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'PAY_CHECKER', 0),
+	(677, 'portfolio', 'INACTIVATE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'INACTIVATE_CHECKER', 0),
+	(678, 'portfolio', 'UPDATE_CLIENTCHARGE_CHECKER', 'CLIENTCHARGE', 'UPDATE_CHECKER', 0),
+	(679, 'transaction_client', 'READTRANSACTION_CLIENT', 'CLIENT', 'READTRANSACTION', 0),
+	(680, 'transaction_client', 'UNDOTRANSACTION_CLIENT', 'CLIENT', 'UNDOTRANSACTION', 0),
+	(681, 'transaction_client', 'UNDOTRANSACTION_CLIENT_CHECKER', 'CLIENT', 'UNDOTRANSACTION_CHECKER', 0),
+	(682, 'LOAN_PROVISIONING', 'CREATE_PROVISIONCATEGORY', 'PROVISIONCATEGORY', 'CREATE', 0),
+	(683, 'LOAN_PROVISIONING', 'DELETE_PROVISIONCATEGORY', 'PROVISIONCATEGORY', 'DELETE', 0),
+	(684, 'LOAN_PROVISIONING', 'CREATE_PROVISIONCRITERIA', 'PROVISIONINGCRITERIA', 'CREATE', 0),
+	(685, 'LOAN_PROVISIONING', 'UPDATE_PROVISIONCRITERIA', 'PROVISIONINGCRITERIA', 'UPDATE', 0),
+	(686, 'LOAN_PROVISIONING', 'DELETE_PROVISIONCRITERIA', 'PROVISIONINGCRITERIA', 'DELETE', 0),
+	(687, 'LOAN_PROVISIONING', 'CREATE_PROVISIONENTRIES', 'PROVISIONINGENTRIES', 'CREATE', 0),
+	(688, 'LOAN_PROVISIONING', 'CREATE_PROVISIONJOURNALENTRIES', 'PROVISIONINGENTRIES', 'CREATE', 0),
+	(689, 'LOAN_PROVISIONING', 'RECREATE_PROVISIONENTRIES', 'PROVISIONINGENTRIES', 'RECREATE', 0),
+	(690, 'portfolio', 'READ_FLOATINGRATE', 'FLOATINGRATE', 'READ', 0),
+	(691, 'portfolio', 'CREATE_FLOATINGRATE', 'FLOATINGRATE', 'CREATE', 1),
+	(692, 'portfolio', 'CREATE_FLOATINGRATE_CHECKER', 'FLOATINGRATE', 'CREATE_CHECKER', 0),
+	(693, 'portfolio', 'UPDATE_FLOATINGRATE', 'FLOATINGRATE', 'UPDATE', 1),
+	(694, 'portfolio', 'UPDATE_FLOATINGRATE_CHECKER', 'FLOATINGRATE', 'UPDATE_CHECKER', 0),
+	(695, 'portfolio', 'CREATESCHEDULEEXCEPTIONS_LOAN', 'LOAN', 'CREATESCHEDULEEXCEPTIONS', 0),
+	(696, 'portfolio', 'CREATESCHEDULEEXCEPTIONS_LOAN_CHECKER', 'LOAN', 'CREATESCHEDULEEXCEPTIONS_CHECKER', 0),
+	(697, 'portfolio', 'DELETESCHEDULEEXCEPTIONS_LOAN', 'LOAN', 'DELETESCHEDULEEXCEPTIONS', 0),
+	(698, 'portfolio', 'DELETESCHEDULEEXCEPTIONS_LOAN_CHECKER', 'LOAN', 'DELETESCHEDULEEXCEPTIONS_CHECKER', 0),
+	(699, 'transaction_loan', 'DISBURSALLASTUNDO_LOAN', 'LOAN', 'DISBURSALLASTUNDO', 0),
+	(700, 'transaction_loan', 'DISBURSALLASTUNDO_LOAN_CHECKER', 'LOAN', 'DISBURSALLASTUNDO_CHECKER', 0),
+	(701, 'SHAREPRODUCT', 'CREATE_SHAREPRODUCT', 'SHAREPRODUCT', 'CREATE', 0),
+	(702, 'SHAREPRODUCT', 'UPDATE_SHAREPRODUCT', 'SHAREPRODUCT', 'CREATE', 0),
+	(703, 'SHAREACCOUNT', 'CREATE_SHAREACCOUNT', 'SHAREACCOUNT', 'CREATE', 0),
+	(704, 'SHAREACCOUNT', 'UPDATE_SHAREACCOUNT', 'SHAREACCOUNT', 'CREATE', 0);
+/*!40000 ALTER TABLE `m_permission` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_portfolio_account_associations
+DROP TABLE IF EXISTS `m_portfolio_account_associations`;
+CREATE TABLE IF NOT EXISTS `m_portfolio_account_associations` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_account_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `linked_loan_account_id` bigint(20) DEFAULT NULL,
+  `linked_savings_account_id` bigint(20) DEFAULT NULL,
+  `association_type_enum` smallint(1) NOT NULL DEFAULT '1',
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `account_association_loan_fk` (`loan_account_id`),
+  KEY `account_association_savings_fk` (`savings_account_id`),
+  KEY `linked_loan_fk` (`linked_loan_account_id`),
+  KEY `linked_savings_fk` (`linked_savings_account_id`),
+  CONSTRAINT `account_association_loan_fk` FOREIGN KEY (`loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `account_association_savings_fk` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `linked_loan_fk` FOREIGN KEY (`linked_loan_account_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `linked_savings_fk` FOREIGN KEY (`linked_savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_portfolio_account_associations: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_portfolio_account_associations` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_portfolio_account_associations` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_portfolio_command_source
+DROP TABLE IF EXISTS `m_portfolio_command_source`;
+CREATE TABLE IF NOT EXISTS `m_portfolio_command_source` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_name` varchar(50) NOT NULL,
+  `entity_name` varchar(50) NOT NULL,
+  `office_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `loan_id` bigint(20) DEFAULT NULL,
+  `savings_account_id` bigint(20) DEFAULT NULL,
+  `api_get_url` varchar(100) NOT NULL,
+  `resource_id` bigint(20) DEFAULT NULL,
+  `subresource_id` bigint(20) DEFAULT NULL,
+  `command_as_json` text NOT NULL,
+  `maker_id` bigint(20) NOT NULL,
+  `made_on_date` datetime NOT NULL,
+  `checker_id` bigint(20) DEFAULT NULL,
+  `checked_on_date` datetime DEFAULT NULL,
+  `processing_result_enum` smallint(5) NOT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `transaction_id` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_maker_m_appuser` (`maker_id`),
+  KEY `FK_m_checker_m_appuser` (`checker_id`),
+  KEY `action_name` (`action_name`),
+  KEY `entity_name` (`entity_name`,`resource_id`),
+  KEY `made_on_date` (`made_on_date`),
+  KEY `checked_on_date` (`checked_on_date`),
+  KEY `processing_result_enum` (`processing_result_enum`),
+  KEY `office_id` (`office_id`),
+  KEY `group_id` (`office_id`),
+  KEY `client_id` (`office_id`),
+  KEY `loan_id` (`office_id`),
+  CONSTRAINT `FK_m_checker_m_appuser` FOREIGN KEY (`checker_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_m_maker_m_appuser` FOREIGN KEY (`maker_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_portfolio_command_source: ~71 rows (approximately)
+/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
+INSERT INTO `m_portfolio_command_source` (`id`, `action_name`, `entity_name`, `office_id`, `group_id`, `client_id`, `loan_id`, `savings_account_id`, `api_get_url`, `resource_id`, `subresource_id`, `command_as_json`, `maker_id`, `made_on_date`, `checker_id`, `checked_on_date`, `processing_result_enum`, `product_id`, `transaction_id`) VALUES
+	(1, 'CREATE', 'STAFF', 1, NULL, NULL, NULL, NULL, '/staff/template', 1, NULL, '{"isLoanOfficer":true,"officeId":1,"firstname":"Aliya","lastname":"A"}', 1, '2014-03-07 19:10:05', NULL, NULL, 1, NULL, NULL),
+	(2, 'CREATE', 'USER', 1, NULL, NULL, NULL, NULL, '/users/template', 2, NULL, '{"sendPasswordToEmail":true,"officeId":1,"username":"adama","firstname":"Adam","lastname":"A","email":"adama@123.com","roles":["1"]}', 1, '2014-03-07 19:19:31', NULL, NULL, 1, NULL, NULL),
+	(3, 'CREATE', 'CLIENT', 1, NULL, 1, NULL, NULL, '/clients/template', 1, NULL, '{"officeId":1,"staffId":1,"firstname":"Smith","lastname":"R","active":true,"locale":"en","dateFormat":"dd MMMM yyyy","activationDate":"07 March 2014","submittedOnDate":"01 January 2010","savingsProductId":null}', 1, '2014-03-07 19:23:36', NULL, NULL, 1, NULL, NULL),
+	(4, 'CREATE', 'OFFICE', 2, NULL, NULL, NULL, NULL, '/offices/template', 2, NULL, '{"parentId":1,"name":"Manila","locale":"en","dateFormat":"dd MMMM yyyy","openingDate":"01 January 2010"}', 1, '2014-03-07 19:24:51', NULL, NULL, 1, NULL, NULL),
+	(5, 'CREATE', 'USER', 2, NULL, NULL, NULL, NULL, '/users/template', 4, NULL, '{"sendPasswordToEmail":true,"officeId":2,"username":"benb","firstname":"Ben","lastname":"B","email":"benb@123.com","roles":["1"]}', 1, '2014-03-07 19:26:26', NULL, NULL, 1, NULL, NULL),
+	(6, 'CREATE', 'STAFF', 2, NULL, NULL, NULL, NULL, '/staff/template', 2, NULL, '{"isLoanOfficer":true,"officeId":2,"firstname":"Mary","lastname":"M"}', 1, '2014-03-07 19:27:47', NULL, NULL, 1, NULL, NULL),
+	(7, 'CREATE', 'CENTER', 2, 1, NULL, NULL, NULL, '/centers/template', 1, NULL, '{"officeId":2,"staffId":2,"active":true,"name":"Jimmy","activationDate":"07 March 2014","submittedOnDate":"02 January 2010","locale":"en","dateFormat":"dd MMMM yyyy"}', 1, '2014-03-07 19:32:33', NULL, NULL, 1, NULL, NULL),
+	(8, 'UPDATE', 'CENTER', 2, 1, NULL, NULL, NULL, '/centers/1', 1, NULL, '{"activationDate":"02 January 2010","dateFormat":"dd MMMM yyyy","locale":"en"}', 1, '2014-03-07 19:32:55', NULL, NULL, 1, NULL, NULL),
+	(9, 'CREATE', 'CENTER', 2, 2, NULL, NULL, NULL, '/centers/template', 2, NULL, '{"officeId":2,"name":"Global Trade Finance","active":true,"activationDate":"07 March 2014","submittedOnDate":"03 January 2010","locale":"en","dateFormat":"dd MMMM yyyy"}', 1, '2014-03-07 19:41:08', NULL, NULL, 1, NULL, NULL),
+	(10, 'UPDATE', 'CENTER', 2, 2, NULL, NULL, NULL, '/centers/2', 2, NULL, '{"staffId":2,"activationDate":"03 January 2010","dateFormat":"dd MMMM yyyy","locale":"en"}', 1, '2014-03-07 19:41:40', NULL, NULL, 1, NULL, NULL),
+	(11, 'CREATE', 'GROUP', 2, 3, NULL, NULL, NULL, '/groups/template', 3, NULL, '{"clientMembers":[],"staffId":"2","name":"Nirvana","active":false,"submittedOnDate":"04 January 2010","dateFormat":"dd MMMM yyyy","locale":"en","centerId":"2","officeId":"2"}', 1, '2014-03-07 19:44:24', NULL, NULL, 1, NULL, NULL),
+	(12, 'ACTIVATE', 'GROUP', 2, 3, NULL, NULL, NULL, '/groups/3?command=activate', 3, NULL, '{"activationDate":"04 January 2010","locale":"en","dateFormat":"dd MMMM yyyy"}', 1, '2014-03-07 19:45:06', NULL, NULL, 1, NULL, NULL),
+	(13, 'CREATE', 'GROUP', 2, 4, NULL, NULL, NULL, '/groups/template', 4, NULL, '{"clientMembers":[],"staffId":"2","name":"Oasis","active":true,"activationDate":"04 January 2010","submittedOnDate":"04 January 2010","dateFormat":"dd MMMM yyyy","locale":"en","centerId":"2","officeId":"2"}', 1, '2014-03-07 19:51:14', NULL, NULL, 1, NULL, NULL),
+	(14, 'CREATE', 'CLIENT', 2, 3, 2, NULL, NULL, '/clients/template', 2, NULL, '{"staffId":"2","firstname":"Johnson","lastname":"D","active":true,"activationDate":"04 January 2010","dateFormat":"dd MMMM yyyy","locale":"en","groupId":"3","officeId":"2"}', 1, '2014-03-07 19:56:25', NULL, NULL, 1, NULL, NULL),
+	(15, 'CREATE', 'CLIENT', 2, 3, 3, NULL, NULL, '/clients/template', 3, NULL, '{"staffId":"2","firstname":"Williams","lastname":"G","active":false,"activationDate":"04 January 2010","dateFormat":"dd MMMM yyyy","locale":"en","groupId":"3","officeId":"2"}', 1, '2014-03-07 19:57:18', NULL, NULL, 1, NULL, NULL),
+	(16, 'DELETE', 'CLIENT', 2, NULL, 3, NULL, NULL, '/clients/3', 3, NULL, '{}', 1, '2014-03-07 19:59:46', NULL, NULL, 1, NULL, NULL),
+	(17, 'CREATE', 'CLIENT', 2, 3, 4, NULL, NULL, '/clients/template', 4, NULL, '{"staffId":"2","firstname":"Williams","lastname":"G","active":true,"activationDate":"04 January 2010","dateFormat":"dd MMMM yyyy","locale":"en","groupId":"3","officeId":"2"}', 1, '2014-03-07 20:00:26', NULL, NULL, 1, NULL, NULL),
+	(18, 'CREATE', 'CLIENT', 2, 3, 5, NULL, NULL, '/clients/template', 5, NULL, '{"staffId":"2","firstname":"Harris","lastname":"P","active":true,"activationDate":"04 January 2010","dateFormat":"dd MMMM yyyy","locale":"en","groupId":"3","officeId":"2"}', 1, '2014-03-07 20:01:07', NULL, NULL, 1, NULL, NULL),
+	(19, 'CREATE', 'CLIENT', 2, 4, 6, NULL, NULL, '/clients/template', 6, NULL, '{"staffId":"2","firstname":"Allen","lastname":"E","active":true,"activationDate":"04 January 2010","dateFormat":"dd MMMM yyyy","locale":"en","groupId":"4","officeId":"2"}', 1, '2014-03-07 20:02:37', NULL, NULL, 1, NULL, NULL),
+	(20, 'CREATE', 'CLIENT', 2, 4, 7, NULL, NULL, '/clients/template', 7, NULL, '{"staffId":"2","firstname":"Allen","lastname":"E","active":true,"activationDate":"04 January 2010","dateFormat":"dd MMMM yyyy","locale":"en","groupId":"4","officeId":"2"}', 1, '2014-03-07 20:02:37', NULL, NULL, 1, NULL, NULL),
+	(21, 'UPDATE', 'CLIENT', 2, NULL, 7, NULL, NULL, '/clients/7', 7, NULL, '{"firstname":"Scott","lastname":"C"}', 1, '2014-03-07 20:04:17', NULL, NULL, 1, NULL, NULL),
+	(22, 'CREATE', 'CLIENT', 2, 4, 8, NULL, NULL, '/clients/template', 8, NULL, '{"staffId":"2","firstname":"Robinson","lastname":"R","active":true,"activationDate":"04 January 2010","dateFormat":"dd MMMM yyyy","locale":"en","groupId":"4","officeId":"2"}', 1, '2014-03-07 20:05:31', NULL, NULL, 1, NULL, NULL),
+	(23, 'CREATE', 'OFFICE', 3, NULL, NULL, NULL, NULL, '/offices/template', 3, NULL, '{"parentId":1,"name":"Pasay","locale":"en","dateFormat":"dd MMMM yyyy","openingDate":"08 February 2010"}', 1, '2014-03-07 20:06:22', NULL, NULL, 1, NULL, NULL),
+	(24, 'CREATE', 'USER', 1, NULL, NULL, NULL, NULL, '/users/template', 5, NULL, '{"sendPasswordToEmail":true,"officeId":1,"username":"janej","firstname":"Jane","lastname":"J","email":"janej@123.com","roles":["1"]}', 1, '2014-03-07 20:07:48', NULL, NULL, 1, NULL, NULL),
+	(25, 'CREATE', 'STAFF', 3, NULL, NULL, NULL, NULL, '/staff/template', 3, NULL, '{"isLoanOfficer":true,"officeId":3,"firstname":"John","lastname":"K"}', 1, '2014-03-07 20:08:28', NULL, NULL, 1, NULL, NULL),
+	(26, 'CREATE', 'FUND', NULL, NULL, NULL, NULL, NULL, '/funds/template', 1, NULL, '{"name":"Loan from Central Bank"}', 1, '2014-03-10 10:11:50', NULL, NULL, 1, NULL, NULL),
+	(27, 'CREATE', 'LOANPRODUCT', NULL, NULL, NULL, NULL, NULL, '/loanproducts/template', 1, NULL, '{"currencyCode":"USD","includeInBorrowerCycle":"false","useBorrowerCycle":"false","digitsAfterDecimal":"2","inMultiplesOf":"0","repaymentFrequencyType":1,"interestRateFrequencyType":3,"amortizationType":1,"interestType":1,"interestCalculationPeriodType":1,"transactionProcessingStrategyId":1,"principalVariationsForBorrowerCycle":[],"interestRateVariationsForBorrowerCycle":[],"numberOfRepaymentVariationsForBorrowerCycle":[],"multiDisburseLoan":false,"fundSourceAccountId":31,"loanPortfolioAccountId":32,"transfersInSuspenseAccountId":33,"interestOnLoanAccountId":36,"incomeFromFeeAccountId":37,"incomeFromPenaltyAccountId":38,"writeOffAccountId":41,"overpaymentLiabilityAccountId":30,"accountingRule":"2","name":"Income Generating Loan","shortName":"IGL","fundId":1,"minPrincipal":"10000","principal":"10000","maxPrincipal":"10000","minNumberOfRepayments":"25","numberOfRepayments":"25","maxNumberOfRepayments":"25","repaymentEvery":"1","minInterestRatePerPeriod":"26","interestRatePerPeriod":"26","maxInterestRatePerPeriod":"26","paymentChannelToFundSourceMappings":[],"feeToIncomeAccountMappings":[],"penaltyToIncomeAccountMappings":[],"charges":[],"dateFormat":"dd MMMM yyyy","locale":"en","startDate":"01 January 2010"}', 1, '2014-03-10 10:16:39', NULL, NULL, 1, NULL, NULL),
+	(28, 'CREATE', 'SAVINGSPRODUCT', NULL, NULL, NULL, NULL, NULL, '/savingsproducts/template', 1, NULL, '{"currencyCode":"USD","digitsAfterDecimal":2,"interestCompoundingPeriodType":1,"interestPostingPeriodType":4,"interestCalculationType":1,"interestCalculationDaysInYearType":365,"savingsReferenceAccountId":31,"overdraftPortfolioControlId":32,"savingsControlAccountId":30,"incomeFromFeeAccountId":36,"incomeFromPenaltyAccountId":37,"incomeFromInterestId":38,"interestOnSavingsAccountId":41,"writeOffAccountId":42,"accountingRule":"2","name":"Voluntary savings","shortName":"VS","description":"Save money","inMultiplesOf":"0","nominalAnnualInterestRate":"9.5","minRequiredOpeningBalance":"1000","lockinPeriodFrequency":"1","lockinPeriodFrequencyType":1,"withdrawalFeeForTransfers":"false","paymentChannelToFundSourceMappings":[],"feeToIncomeAccountMappings":[],"penaltyToIncomeAccountMappings":[],"charges":[],"locale":"en","transfersInSuspenseAccountId":30}', 1, '2014-03-10 10:21:16', NULL, NULL, 1, NULL, NULL),
+	(29, 'UPDATE', 'SAVINGSPRODUCT', NULL, NULL, NULL, NULL, NULL, '/savingsproducts/1', 1, NULL, '{"shortName":"VS","penaltyToIncomeAccountMappings":"[]","paymentChannelToFundSourceMappings":"[]","feeToIncomeAccountMappings":"[]"}', 1, '2014-03-10 10:21:51', NULL, NULL, 1, NULL, NULL),
+	(30, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/3/codevalues/template', 10, NULL, '{"name":"Cattle Rearing","position":"104"}', 1, '2014-03-10 10:27:02', NULL, NULL, 1, NULL, NULL),
+	(31, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/3/codevalues/template', 11, NULL, '{"name":"Others","position":"105"}', 1, '2014-03-10 10:27:17', NULL, NULL, 1, NULL, NULL),
+	(32, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/3/codevalues/template', 12, NULL, '{"name":"Tailoring Shop","position":"101"}', 1, '2014-03-10 10:27:34', NULL, NULL, 1, NULL, NULL),
+	(33, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/3/codevalues/template', 13, NULL, '{"name":"Small Provisions Store","position":"102"}', 1, '2014-03-10 10:27:49', NULL, NULL, 1, NULL, NULL),
+	(34, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/3/codevalues/template', 14, NULL, '{"name":"Agriculture","position":"105"}', 1, '2014-03-10 10:28:02', NULL, NULL, 1, NULL, NULL),
+	(35, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/3/codevalues/14', 14, NULL, '{"position":103}', 1, '2014-03-10 10:28:13', NULL, NULL, 1, NULL, NULL),
+	(36, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/template', 15, NULL, '{"name":"Blacklisted","position":"201"}', 1, '2014-03-10 10:31:50', NULL, NULL, 1, NULL, NULL),
+	(37, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/template', 16, NULL, '{"name":"Deceased","position":"202"}', 1, '2014-03-10 10:34:54', NULL, NULL, 1, NULL, NULL),
+	(38, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/template', 17, NULL, '{"name":"Transferred","position":"203"}', 1, '2014-03-10 10:35:10', NULL, NULL, 1, NULL, NULL),
+	(39, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/template', 18, NULL, '{"name":"Left","position":"204"}', 1, '2014-03-10 10:35:23', NULL, NULL, 1, NULL, NULL),
+	(40, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/template', 19, NULL, '{"position":"205","name":"others"}', 1, '2014-03-10 10:35:33', NULL, NULL, 1, NULL, NULL),
+	(41, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/1/codevalues/2', 2, NULL, '{"name":"Government Id"}', 1, '2014-03-10 10:37:37', NULL, NULL, 1, NULL, NULL),
+	(42, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/1/codevalues/1', 1, NULL, '{"name":"Passport Id"}', 1, '2014-03-10 10:38:09', NULL, NULL, 1, NULL, NULL),
+	(43, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/1/codevalues/template', 20, NULL, '{"name":"Voter ID","position":"7"}', 1, '2014-03-10 10:38:50', NULL, NULL, 1, NULL, NULL),
+	(44, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/1/codevalues/template', 21, NULL, '{"name":"Ration Card","position":"6"}', 1, '2014-03-10 10:39:08', NULL, NULL, 1, NULL, NULL),
+	(45, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/1/codevalues/21', 21, NULL, '{"position":5}', 1, '2014-03-10 10:39:20', NULL, NULL, 1, NULL, NULL),
+	(46, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/1/codevalues/20', 20, NULL, '{"position":6}', 1, '2014-03-10 10:39:24', NULL, NULL, 1, NULL, NULL),
+	(47, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/19', 19, NULL, '{"position":5}', 1, '2014-03-10 10:39:53', NULL, NULL, 1, NULL, NULL),
+	(48, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/18', 18, NULL, '{"position":4}', 1, '2014-03-10 10:39:53', NULL, NULL, 1, NULL, NULL),
+	(49, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/17', 17, NULL, '{"position":3}', 1, '2014-03-10 10:39:53', NULL, NULL, 1, NULL, NULL),
+	(50, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/16', 16, NULL, '{"position":2}', 1, '2014-03-10 10:39:54', NULL, NULL, 1, NULL, NULL),
+	(51, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/14/codevalues/15', 15, NULL, '{"position":1}', 1, '2014-03-10 10:39:54', NULL, NULL, 1, NULL, NULL),
+	(52, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/4/codevalues/template', 22, NULL, '{"name":"Male","position":"1"}', 1, '2014-03-10 10:46:36', NULL, NULL, 1, NULL, NULL),
+	(53, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/4/codevalues/22', 22, NULL, '{"position":2}', 1, '2014-03-10 10:46:49', NULL, NULL, 1, NULL, NULL),
+	(54, 'UPDATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/4/codevalues/22', 22, NULL, '{"position":1}', 1, '2014-03-10 10:47:02', NULL, NULL, 1, NULL, NULL),
+	(55, 'CREATE', 'CODEVALUE', NULL, NULL, NULL, NULL, NULL, '/codes/4/codevalues/template', 24, NULL, '{"name":"Female","position":"2"}', 1, '2014-03-10 10:47:20', NULL, NULL, 1, NULL, NULL),
+	(56, 'CREATE', 'CHARGE', NULL, NULL, NULL, NULL, NULL, '/charges/template', 1, NULL, '{"chargeAppliesTo":1,"name":"Processing Fee","currencyCode":"USD","chargeTimeType":1,"chargeCalculationType":1,"chargePaymentMode":0,"amount":"500","active":true,"locale":"en","monthDayFormat":"dd MMM"}', 1, '2014-03-10 10:50:24', NULL, NULL, 1, NULL, NULL),
+	(57, 'UPDATE', 'LOANPRODUCT', NULL, NULL, NULL, NULL, NULL, '/loanproducts/1', 1, NULL, '{"inMultiplesOf":0,"charges":"[{\\"id\\":1}]","penaltyToIncomeAccountMappings":"[]","paymentChannelToFundSourceMappings":"[]","feeToIncomeAccountMappings":"[]"}', 1, '2014-03-10 10:59:18', NULL, NULL, 1, NULL, NULL),
+	(58, 'UPDATE', 'USER', 3, NULL, NULL, NULL, NULL, '/users/5', 5, NULL, '{"officeId":3}', 1, '2014-03-14 16:22:56', NULL, NULL, 1, NULL, NULL),
+	(59, 'UPDATE', 'CENTER', 2, 2, NULL, NULL, NULL, '/centers/2', 2, NULL, '{"name":"Santa Maria"}', 1, '2014-03-14 16:50:20', NULL, NULL, 1, NULL, NULL),
+	(60, 'UPDATE', 'CENTER', 2, 1, NULL, NULL, NULL, '/centers/1', 1, NULL, '{"name":"Santa Cruz"}', 1, '2014-03-14 16:51:55', NULL, NULL, 1, NULL, NULL),
+	(61, 'UPDATE', 'GROUP', 2, 3, NULL, NULL, NULL, '/groups/3', 3, NULL, '{"name":"Santa Maria Group 1"}', 1, '2014-03-14 16:57:00', NULL, NULL, 1, NULL, NULL),
+	(62, 'UPDATE', 'GROUP', 2, 4, NULL, NULL, NULL, '/groups/4', 4, NULL, '{"name":"Santa Maria Group 2"}', 1, '2014-03-14 16:57:27', NULL, NULL, 1, NULL, NULL),
+	(63, 'UPDATE', 'LOANPRODUCT', NULL, NULL, NULL, NULL, NULL, '/loanproducts/1', 1, NULL, '{"inMultiplesOf":0,"penaltyToIncomeAccountMappings":"[]","incomeFromFeeAccountId":36,"fundSourceAccountId":32,"paymentChannelToFundSourceMappings":"[]","interestOnLoanAccountId":38,"transfersInSuspenseAccountId":31,"loanPortfolioAccountId":34,"incomeFromPenaltyAccountId":37,"feeToIncomeAccountMappings":"[]"}', 1, '2014-05-01 16:25:00', NULL, NULL, 1, NULL, NULL),
+	(64, 'UPDATE', 'GLACCOUNT', NULL, NULL, NULL, NULL, NULL, '/glaccounts/17', 17, NULL, '{"usage":1}', 1, '2014-05-01 16:25:26', NULL, NULL, 1, NULL, NULL),
+	(65, 'UPDATE', 'GLACCOUNT', NULL, NULL, NULL, NULL, NULL, '/glaccounts/2', 2, NULL, '{"usage":1}', 1, '2014-05-01 16:25:56', NULL, NULL, 1, NULL, NULL),
+	(66, 'UPDATE', 'LOANPRODUCT', NULL, NULL, NULL, NULL, NULL, '/loanproducts/1', 1, NULL, '{"inMultiplesOf":0,"penaltyToIncomeAccountMappings":"[]","paymentChannelToFundSourceMappings":"[]","feeToIncomeAccountMappings":"[]"}', 1, '2014-05-01 16:26:06', NULL, NULL, 1, NULL, NULL),
+	(67, 'UPDATE', 'LOANPRODUCT', NULL, NULL, NULL, NULL, NULL, '/loanproducts/1', 1, NULL, '{"inMultiplesOf":0,"overpaymentLiabilityAccountId":2,"penaltyToIncomeAccountMappings":"[]","paymentChannelToFundSourceMappings":"[]","transfersInSuspenseAccountId":17,"feeToIncomeAccountMappings":"[]"}', 1, '2014-05-01 16:26:37', NULL, NULL, 1, NULL, NULL),
+	(68, 'CREATE', 'LOAN', 2, NULL, 8, 1, NULL, '/loans', 1, NULL, '{"clientId":"8","productId":1,"disbursementData":[],"fundId":1,"principal":10000,"loanTermFrequency":25,"loanTermFrequencyType":1,"numberOfRepayments":25,"repaymentEvery":1,"repaymentFrequencyType":1,"interestRatePerPeriod":26,"amortizationType":1,"interestType":1,"interestCalculationPeriodType":1,"transactionProcessingStrategyId":1,"locale":"en","dateFormat":"dd MMMM yyyy","loanType":"individual","expectedDisbursementDate":"16 June 2014","submittedOnDate":"02 June 2014","charges":[{"chargeId":1,"amount":500}]}', 1, '2014-06-11 09:17:45', NULL, NULL, 1, NULL, NULL),
+	(69, 'APPROVE', 'LOAN', 2, NULL, 8, 1, NULL, '/loans/1', 1, NULL, '{"status":{"id":200,"code":"loanStatusType.approved","value":"Approved","pendingApproval":false,"waitingForDisbursal":true,"active":false,"closedObligationsMet":false,"closedWrittenOff":false,"closedRescheduled":false,"closed":false,"overpaid":false},"locale":"en","dateFormat":"dd MMMM yyyy","approvedOnDate":"11 June 2014"}', 1, '2014-06-11 09:18:16', NULL, NULL, 1, NULL, NULL),
+	(70, 'CREATE', 'FINANCIALACTIVITYACCOUNT', NULL, NULL, NULL, NULL, NULL, '/organizationglaccounts/template', 2, NULL, '{"financialActivityId":100,"glAccountId":33}', 1, '2014-06-16 16:54:20', NULL, NULL, 1, NULL, NULL),
+	(71, 'UPDATE', 'FINANCIALACTIVITYACCOUNT', NULL, NULL, NULL, NULL, NULL, '/organizationglaccounts/2', 2, NULL, '{"glAccountId":32}', 1, '2014-06-16 16:57:17', NULL, NULL, 1, NULL, NULL),
+	(72, 'DELETE', 'FINANCIALACTIVITYACCOUNT', NULL, NULL, NULL, NULL, NULL, '/organizationglaccounts/2', 2, NULL, '{}', 1, '2014-06-16 17:11:12', NULL, NULL, 1, NULL, NULL);
+/*!40000 ALTER TABLE `m_portfolio_command_source` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_product_loan
+DROP TABLE IF EXISTS `m_product_loan`;
+CREATE TABLE IF NOT EXISTS `m_product_loan` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `short_name` varchar(4) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `principal_amount` decimal(19,6) DEFAULT NULL,
+  `min_principal_amount` decimal(19,6) DEFAULT NULL,
+  `max_principal_amount` decimal(19,6) DEFAULT NULL,
+  `arrearstolerance_amount` decimal(19,6) DEFAULT NULL,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) DEFAULT NULL,
+  `fund_id` bigint(20) DEFAULT NULL,
+  `is_linked_to_floating_interest_rates` bit(1) NOT NULL DEFAULT b'0',
+  `allow_variabe_installments` bit(1) NOT NULL DEFAULT b'0',
+  `nominal_interest_rate_per_period` decimal(19,6) DEFAULT NULL,
+  `min_nominal_interest_rate_per_period` decimal(19,6) DEFAULT NULL,
+  `max_nominal_interest_rate_per_period` decimal(19,6) DEFAULT NULL,
+  `interest_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `annual_nominal_interest_rate` decimal(19,6) DEFAULT NULL,
+  `interest_method_enum` smallint(5) NOT NULL,
+  `interest_calculated_in_period_enum` smallint(5) NOT NULL DEFAULT '1',
+  `allow_partial_period_interest_calcualtion` tinyint(1) NOT NULL DEFAULT '0',
+  `repay_every` smallint(5) NOT NULL,
+  `repayment_period_frequency_enum` smallint(5) NOT NULL,
+  `number_of_repayments` smallint(5) NOT NULL,
+  `min_number_of_repayments` smallint(5) DEFAULT NULL,
+  `max_number_of_repayments` smallint(5) DEFAULT NULL,
+  `grace_on_principal_periods` smallint(5) DEFAULT NULL,
+  `grace_on_interest_periods` smallint(5) DEFAULT NULL,
+  `grace_interest_free_periods` smallint(5) DEFAULT NULL,
+  `amortization_method_enum` smallint(5) NOT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `include_in_borrower_cycle` tinyint(1) NOT NULL DEFAULT '0',
+  `use_borrower_cycle` tinyint(1) NOT NULL DEFAULT '0',
+  `start_date` date DEFAULT NULL,
+  `close_date` date DEFAULT NULL,
+  `allow_multiple_disbursals` tinyint(1) NOT NULL DEFAULT '0',
+  `max_disbursals` int(2) DEFAULT NULL,
+  `max_outstanding_loan_balance` decimal(19,6) DEFAULT NULL,
+  `grace_on_arrears_ageing` smallint(5) DEFAULT NULL,
+  `overdue_days_for_npa` smallint(5) DEFAULT NULL,
+  `days_in_month_enum` smallint(5) NOT NULL DEFAULT '1',
+  `days_in_year_enum` smallint(5) NOT NULL DEFAULT '1',
+  `interest_recalculation_enabled` tinyint(4) NOT NULL DEFAULT '0',
+  `min_days_between_disbursal_and_first_repayment` int(3) DEFAULT NULL,
+  `hold_guarantee_funds` tinyint(1) NOT NULL DEFAULT '0',
+  `principal_threshold_for_last_installment` decimal(5,2) NOT NULL DEFAULT '50.00',
+  `account_moves_out_of_npa_only_on_arrears_completion` tinyint(1) NOT NULL DEFAULT '0',
+  `can_define_fixed_emi_amount` tinyint(1) NOT NULL DEFAULT '0',
+  `instalment_amount_in_multiples_of` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`),
+  UNIQUE KEY `unq_short_name` (`short_name`),
+  UNIQUE KEY `external_id_UNIQUE` (`external_id`),
+  KEY `FKA6A8A7D77240145` (`fund_id`),
+  KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
+  CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `m_fund` (`id`),
+  CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_product_loan: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan` DISABLE KEYS */;
+INSERT INTO `m_product_loan` (`id`, `short_name`, `currency_code`, `currency_digits`, `currency_multiplesof`, `principal_amount`, `min_principal_amount`, `max_principal_amount`, `arrearstolerance_amount`, `name`, `description`, `fund_id`, `is_linked_to_floating_interest_rates`, `allow_variabe_installments`, `nominal_interest_rate_per_period`, `min_nominal_interest_rate_per_period`, `max_nominal_interest_rate_per_period`, `interest_period_frequency_enum`, `annual_nominal_interest_rate`, `interest_method_enum`, `interest_calculated_in_period_enum`, `allow_partial_period_interest_calcualtion`, `repay_every`, `repayment_period_frequency_enum`, `number_of_repayments`, `min_number_of_repayments`, `max_number_of_repayments`, `grace_on_principal_periods`, `grace_on_interest_periods`, `grace_interest_free_periods`, `amortization_method_enum`, `accounting_type`, `loan_transaction_strategy_id`, `external_id`, `include_in_borrower_cycle`, `use_borrower_cycle`, `start_date`, `close_date`, `allow_multiple_disbursals`, `max_disbursals`, `max_outstanding_loan_balance`, `grace_on_arrears_ageing`, `overdue_days_for_npa`, `days_in_month_enum`, `days_in_year_enum`, `interest_recalculation_enabled`, `min_days_between_disbursal_and_first_repayment`, `hold_guarantee_funds`, `principal_threshold_for_last_installment`, `account_moves_out_of_npa_only_on_arrears_completion`, `can_define_fixed_emi_amount`, `instalment_amount_in_multiples_of`) VALUES
+	(1, 'IGL', 'USD', 2, 0, 10000.000000, 10000.000000, 10000.000000, NULL, 'Income Generating Loan', NULL, 1, b'0', b'0', 26.000000, 26.000000, 26.000000, 3, 26.000000, 1, 1, 0, 1, 1, 25, 25, 25, NULL, NULL, NULL, 1, 2, 1, NULL, 0, 0, '2010-01-01', NULL, 0, NULL, NULL, NULL, NULL, 1, 1, 0, NULL, 0, 0.00, 0, 0, NULL);
+/*!40000 ALTER TABLE `m_product_loan` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_product_loan_charge
+DROP TABLE IF EXISTS `m_product_loan_charge`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_charge` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_product_loan_charge_ibfk_2` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_product_loan_charge: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_charge` DISABLE KEYS */;
+INSERT INTO `m_product_loan_charge` (`product_loan_id`, `charge_id`) VALUES
+	(1, 1);
+/*!40000 ALTER TABLE `m_product_loan_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_product_loan_configurable_attributes
+DROP TABLE IF EXISTS `m_product_loan_configurable_attributes`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_configurable_attributes` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL,
+  `amortization_method_enum` tinyint(4) NOT NULL DEFAULT '1',
+  `interest_method_enum` tinyint(4) NOT NULL DEFAULT '1',
+  `loan_transaction_strategy_id` tinyint(4) NOT NULL DEFAULT '1',
+  `interest_calculated_in_period_enum` tinyint(4) NOT NULL DEFAULT '1',
+  `arrearstolerance_amount` tinyint(4) NOT NULL DEFAULT '1',
+  `repay_every` tinyint(4) NOT NULL DEFAULT '1',
+  `moratorium` tinyint(4) NOT NULL DEFAULT '1',
+  `grace_on_arrears_ageing` tinyint(4) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `fk_m_product_loan_configurable_attributes_0001` (`loan_product_id`),
+  CONSTRAINT `fk_m_product_loan_configurable_attributes_0001` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_product_loan_configurable_attributes: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_configurable_attributes` DISABLE KEYS */;
+INSERT INTO `m_product_loan_configurable_attributes` (`id`, `loan_product_id`, `amortization_method_enum`, `interest_method_enum`, `loan_transaction_strategy_id`, `interest_calculated_in_period_enum`, `arrearstolerance_amount`, `repay_every`, `moratorium`, `grace_on_arrears_ageing`) VALUES
+	(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+/*!40000 ALTER TABLE `m_product_loan_configurable_attributes` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_product_loan_floating_rates
+DROP TABLE IF EXISTS `m_product_loan_floating_rates`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_floating_rates` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL,
+  `floating_rates_id` bigint(20) NOT NULL,
+  `interest_rate_differential` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `min_differential_lending_rate` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `default_differential_lending_rate` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `max_differential_lending_rate` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_floating_interest_rate_calculation_allowed` bit(1) NOT NULL DEFAULT b'0',
+  PRIMARY KEY (`id`),
+  KEY `FK_mappings_m_product_loan_id` (`loan_product_id`),
+  KEY `FK_mappings_m_floating_rates_id` (`floating_rates_id`),
+  CONSTRAINT `FK_mappings_m_floating_rates_id` FOREIGN KEY (`floating_rates_id`) REFERENCES `m_floating_rates` (`id`),
+  CONSTRAINT `FK_mappings_m_product_loan_id` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_product_loan_floating_rates: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_floating_rates` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_floating_rates` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_product_loan_guarantee_details
+DROP TABLE IF EXISTS `m_product_loan_guarantee_details`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_guarantee_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL,
+  `mandatory_guarantee` decimal(19,5) NOT NULL,
+  `minimum_guarantee_from_own_funds` decimal(19,5) DEFAULT NULL,
+  `minimum_guarantee_from_guarantor_funds` decimal(19,5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_guarantee_details_loan_product` (`loan_product_id`),
+  CONSTRAINT `FK_guarantee_details_loan_product` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_product_loan_guarantee_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_guarantee_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_guarantee_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_product_loan_recalculation_details
+DROP TABLE IF EXISTS `m_product_loan_recalculation_details`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_recalculation_details` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `product_id` bigint(20) NOT NULL,
+  `compound_type_enum` smallint(5) NOT NULL,
+  `reschedule_strategy_enum` smallint(5) NOT NULL,
+  `rest_frequency_type_enum` smallint(1) NOT NULL,
+  `rest_frequency_interval` smallint(3) NOT NULL DEFAULT '0',
+  `rest_freqency_date` date DEFAULT NULL,
+  `arrears_based_on_original_schedule` tinyint(1) NOT NULL DEFAULT '0',
+  `pre_close_interest_calculation_strategy` smallint(3) NOT NULL DEFAULT '1',
+  `compounding_frequency_type_enum` smallint(1) DEFAULT NULL,
+  `compounding_frequency_interval` smallint(3) DEFAULT NULL,
+  `compounding_freqency_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_product_loan_m_product_loan_recalculation_details` (`product_id`),
+  CONSTRAINT `FK_m_product_loan_m_product_loan_recalculation_details` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-reference.m_product_loan_recalculation_details: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_recalculation_details` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_recalculation_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_product_loan_variable_installment_config
+DROP TABLE IF EXISTS `m_product_loan_variable_installment_config`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_variable_installment_config` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL,
+  `minimum_gap` int(4) NOT NULL,
+  `maximum_gap` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_mappings_m_variable_product_loan_id` (`loan_product_id`),
+  CONSTRAINT `FK_mappings_m_variable_product_loan_id` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_product_loan_variable_installment_config: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_variable_installment_config` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_variable_installment_config` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_product_loan_variations_borrower_cycle
+DROP TABLE IF EXISTS `m_product_loan_variations_borrower_cycle`;
+CREATE TABLE IF NOT EXISTS `m_product_loan_variations_borrower_cycle` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `loan_product_id` bigint(20) NOT NULL DEFAULT '0',
+  `borrower_cycle_number` int(3) NOT NULL DEFAULT '0',
+  `value_condition` int(1) NOT NULL DEFAULT '0',
+  `param_type` int(1) NOT NULL DEFAULT '0',
+  `default_value` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `max_value` decimal(19,6) DEFAULT NULL,
+  `min_value` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `borrower_cycle_loan_product_FK` (`loan_product_id`),
+  CONSTRAINT `borrower_cycle_loan_product_FK` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_product_loan_variations_borrower_cycle: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_loan_variations_borrower_cycle` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_loan_variations_borrower_cycle` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_product_mix
+DROP TABLE IF EXISTS `m_product_mix`;
+CREATE TABLE IF NOT EXISTS `m_product_mix` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `product_id` bigint(20) NOT NULL,
+  `restricted_product_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_product_mix_product_id_to_m_product_loan` (`product_id`),
+  KEY `FK_m_product_mix_restricted_product_id_to_m_product_loan` (`restricted_product_id`),
+  CONSTRAINT `FK_m_product_mix_product_id_to_m_product_loan` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_m_product_mix_restricted_product_id_to_m_product_loan` FOREIGN KEY (`restricted_product_id`) REFERENCES `m_product_loan` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_product_mix: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_product_mix` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_product_mix` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_provisioning_criteria
+DROP TABLE IF EXISTS `m_provisioning_criteria`;
+CREATE TABLE IF NOT EXISTS `m_provisioning_criteria` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `criteria_name` varchar(200) NOT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `criteria_name` (`criteria_name`),
+  KEY `createdby_id` (`createdby_id`),
+  KEY `lastmodifiedby_id` (`lastmodifiedby_id`),
+  CONSTRAINT `m_provisioning_criteria_ibfk_1` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_provisioning_criteria_ibfk_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_provisioning_criteria: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_provisioning_criteria` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_provisioning_criteria` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_provisioning_criteria_definition
+DROP TABLE IF EXISTS `m_provisioning_criteria_definition`;
+CREATE TABLE IF NOT EXISTS `m_provisioning_criteria_definition` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `criteria_id` bigint(20) NOT NULL,
+  `category_id` bigint(20) NOT NULL,
+  `min_age` bigint(20) NOT NULL,
+  `max_age` bigint(20) NOT NULL,
+  `provision_percentage` decimal(5,2) NOT NULL,
+  `liability_account` bigint(20) DEFAULT NULL,
+  `expense_account` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `criteria_id` (`criteria_id`),
+  KEY `category_id` (`category_id`),
+  KEY `liability_account` (`liability_account`),
+  KEY `expense_account` (`expense_account`),
+  CONSTRAINT `m_provisioning_criteria_definition_ibfk_1` FOREIGN KEY (`criteria_id`) REFERENCES `m_provisioning_criteria` (`id`),
+  CONSTRAINT `m_provisioning_criteria_definition_ibfk_2` FOREIGN KEY (`category_id`) REFERENCES `m_provision_category` (`id`),
+  CONSTRAINT `m_provisioning_criteria_definition_ibfk_3` FOREIGN KEY (`liability_account`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `m_provisioning_criteria_definition_ibfk_4` FOREIGN KEY (`expense_account`) REFERENCES `acc_gl_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_provisioning_criteria_definition: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_provisioning_criteria_definition` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_provisioning_criteria_definition` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_provisioning_history
+DROP TABLE IF EXISTS `m_provisioning_history`;
+CREATE TABLE IF NOT EXISTS `m_provisioning_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `journal_entry_created` bit(1) DEFAULT b'0',
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` date DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  `lastmodified_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `createdby_id` (`createdby_id`),
+  KEY `lastmodifiedby_id` (`lastmodifiedby_id`),
+  CONSTRAINT `m_provisioning_history_ibfk_1` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_provisioning_history_ibfk_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_provisioning_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_provisioning_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_provisioning_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_provision_category
+DROP TABLE IF EXISTS `m_provision_category`;
+CREATE TABLE IF NOT EXISTS `m_provision_category` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `category_name` varchar(100) NOT NULL,
+  `description` varchar(300) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `category_name` (`category_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_provision_category: ~4 rows (approximately)
+/*!40000 ALTER TABLE `m_provision_category` DISABLE KEYS */;
+INSERT INTO `m_provision_category` (`id`, `category_name`, `description`) VALUES
+	(1, 'STANDARD', 'Punctual Payment without any dues'),
+	(2, 'SUB-STANDARD', 'Principal and/or Interest overdue by x days'),
+	(3, 'DOUBTFUL', 'Principal and/or Interest overdue by x days and less than y'),
+	(4, 'LOSS', 'Principal and/or Interest overdue by y days');
+/*!40000 ALTER TABLE `m_provision_category` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_role
+DROP TABLE IF EXISTS `m_role`;
+CREATE TABLE IF NOT EXISTS `m_role` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `is_disabled` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_role: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_role` DISABLE KEYS */;
+INSERT INTO `m_role` (`id`, `name`, `description`, `is_disabled`) VALUES
+	(1, 'Super user', 'This role provides all application permissions.', 0);
+/*!40000 ALTER TABLE `m_role` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_role_permission
+DROP TABLE IF EXISTS `m_role_permission`;
+CREATE TABLE IF NOT EXISTS `m_role_permission` (
+  `role_id` bigint(20) NOT NULL,
+  `permission_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`),
+  KEY `FK8DEDB04815CEC7AB` (`role_id`),
+  KEY `FK8DEDB048103B544B` (`permission_id`),
+  CONSTRAINT `FK8DEDB048103B544B` FOREIGN KEY (`permission_id`) REFERENCES `m_permission` (`id`),
+  CONSTRAINT `FK8DEDB04815CEC7AB` FOREIGN KEY (`role_id`) REFERENCES `m_role` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_role_permission: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_role_permission` DISABLE KEYS */;
+INSERT INTO `m_role_permission` (`role_id`, `permission_id`) VALUES
+	(1, 1);
+/*!40000 ALTER TABLE `m_role_permission` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_account
+DROP TABLE IF EXISTS `m_savings_account`;
+CREATE TABLE IF NOT EXISTS `m_savings_account` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_no` varchar(20) NOT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `group_id` bigint(20) DEFAULT NULL,
+  `product_id` bigint(20) DEFAULT NULL,
+  `field_officer_id` bigint(20) DEFAULT NULL,
+  `status_enum` smallint(5) NOT NULL DEFAULT '300',
+  `account_type_enum` smallint(5) NOT NULL DEFAULT '1',
+  `deposit_type_enum` smallint(5) NOT NULL DEFAULT '100',
+  `submittedon_date` date NOT NULL,
+  `submittedon_userid` bigint(20) DEFAULT NULL,
+  `approvedon_date` date DEFAULT NULL,
+  `approvedon_userid` bigint(20) DEFAULT NULL,
+  `rejectedon_date` date DEFAULT NULL,
+  `rejectedon_userid` bigint(20) DEFAULT NULL,
+  `withdrawnon_date` date DEFAULT NULL,
+  `withdrawnon_userid` bigint(20) DEFAULT NULL,
+  `activatedon_date` date DEFAULT NULL,
+  `activatedon_userid` bigint(20) DEFAULT NULL,
+  `closedon_date` date DEFAULT NULL,
+  `closedon_userid` bigint(20) DEFAULT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `nominal_annual_interest_rate` decimal(19,6) NOT NULL,
+  `interest_compounding_period_enum` smallint(5) NOT NULL,
+  `interest_posting_period_enum` smallint(5) NOT NULL DEFAULT '4',
+  `interest_calculation_type_enum` smallint(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `withdrawal_fee_for_transfer` tinyint(4) DEFAULT '1',
+  `allow_overdraft` tinyint(1) NOT NULL DEFAULT '0',
+  `overdraft_limit` decimal(19,6) DEFAULT NULL,
+  `nominal_annual_interest_rate_overdraft` decimal(19,6) DEFAULT '0.000000',
+  `min_overdraft_for_interest_calculation` decimal(19,6) DEFAULT '0.000000',
+  `lockedin_until_date_derived` date DEFAULT NULL,
+  `total_deposits_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
+  `total_withdrawal_fees_derived` decimal(19,6) DEFAULT NULL,
+  `total_fees_charge_derived` decimal(19,6) DEFAULT NULL,
+  `total_penalty_charge_derived` decimal(19,6) DEFAULT NULL,
+  `total_annual_fees_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_earned_derived` decimal(19,6) DEFAULT NULL,
+  `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
+  `total_overdraft_interest_derived` decimal(19,6) DEFAULT '0.000000',
+  `account_balance_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `min_required_balance` decimal(19,6) DEFAULT NULL,
+  `enforce_min_required_balance` tinyint(1) NOT NULL DEFAULT '0',
+  `min_balance_for_interest_calculation` decimal(19,6) DEFAULT NULL,
+  `start_interest_calculation_date` date DEFAULT NULL,
+  `on_hold_funds_derived` decimal(19,6) DEFAULT NULL,
+  `version` int(15) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sa_account_no_UNIQUE` (`account_no`),
+  UNIQUE KEY `sa_externalid_UNIQUE` (`external_id`),
+  KEY `FKSA00000000000001` (`client_id`),
+  KEY `FKSA00000000000002` (`group_id`),
+  KEY `FKSA00000000000003` (`product_id`),
+  CONSTRAINT `FKSA00000000000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKSA00000000000002` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSA00000000000003` FOREIGN KEY (`product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_account: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_account_charge
+DROP TABLE IF EXISTS `m_savings_account_charge`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_charge` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  `is_penalty` tinyint(1) NOT NULL DEFAULT '0',
+  `charge_time_enum` smallint(5) NOT NULL,
+  `charge_due_date` date DEFAULT NULL,
+  `fee_on_month` smallint(5) DEFAULT NULL,
+  `fee_on_day` smallint(5) DEFAULT NULL,
+  `fee_interval` smallint(5) DEFAULT NULL,
+  `charge_calculation_enum` smallint(5) NOT NULL,
+  `calculation_percentage` decimal(19,6) DEFAULT NULL,
+  `calculation_on_amount` decimal(19,6) DEFAULT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `amount_paid_derived` decimal(19,6) DEFAULT NULL,
+  `amount_waived_derived` decimal(19,6) DEFAULT NULL,
+  `amount_writtenoff_derived` decimal(19,6) DEFAULT NULL,
+  `amount_outstanding_derived` decimal(19,6) NOT NULL DEFAULT '0.000000',
+  `is_paid_derived` tinyint(1) NOT NULL DEFAULT '0',
+  `waived` tinyint(1) NOT NULL DEFAULT '0',
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  `inactivated_on_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `charge_id` (`charge_id`),
+  KEY `m_savings_account_charge_ibfk_2` (`savings_account_id`),
+  CONSTRAINT `m_savings_account_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_savings_account_charge_ibfk_2` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_account_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_account_charge_paid_by
+DROP TABLE IF EXISTS `m_savings_account_charge_paid_by`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_charge_paid_by` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_transaction_id` bigint(20) NOT NULL,
+  `savings_account_charge_id` bigint(20) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK__m_savings_account_transaction` (`savings_account_transaction_id`),
+  KEY `FK__m_savings_account_charge` (`savings_account_charge_id`),
+  CONSTRAINT `FK__m_savings_account_charge` FOREIGN KEY (`savings_account_charge_id`) REFERENCES `m_savings_account_charge` (`id`),
+  CONSTRAINT `FK__m_savings_account_transaction` FOREIGN KEY (`savings_account_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_account_charge_paid_by: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_charge_paid_by` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_charge_paid_by` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_account_interest_rate_chart
+DROP TABLE IF EXISTS `m_savings_account_interest_rate_chart`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_interest_rate_chart` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `name` varchar(100) DEFAULT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `from_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAIRC00000000000001` (`savings_account_id`),
+  CONSTRAINT `FKSAIRC00000000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_account_interest_rate_chart: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_interest_rate_chart` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_interest_rate_chart` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_account_interest_rate_slab
+DROP TABLE IF EXISTS `m_savings_account_interest_rate_slab`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_interest_rate_slab` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_interest_rate_chart_id` bigint(20) NOT NULL,
+  `description` varchar(200) DEFAULT NULL,
+  `period_type_enum` smallint(5) NOT NULL DEFAULT '1',
+  `from_period` int(11) NOT NULL DEFAULT '0',
+  `to_period` int(11) DEFAULT NULL,
+  `amount_range_from` decimal(19,6) DEFAULT NULL,
+  `amount_range_to` decimal(19,6) DEFAULT NULL,
+  `annual_interest_rate` decimal(19,6) NOT NULL,
+  `currency_code` varchar(3) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAIRS00000000000001` (`savings_account_interest_rate_chart_id`),
+  CONSTRAINT `FKSAIRS00000000000001` FOREIGN KEY (`savings_account_interest_rate_chart_id`) REFERENCES `m_savings_account_interest_rate_chart` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_account_interest_rate_slab: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_interest_rate_slab` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_interest_rate_slab` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_account_transaction
+DROP TABLE IF EXISTS `m_savings_account_transaction`;
+CREATE TABLE IF NOT EXISTS `m_savings_account_transaction` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `savings_account_id` bigint(20) NOT NULL,
+  `office_id` bigint(20) NOT NULL,
+  `payment_detail_id` bigint(20) DEFAULT NULL,
+  `transaction_type_enum` smallint(5) NOT NULL,
+  `is_reversed` tinyint(1) NOT NULL,
+  `transaction_date` date NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  `overdraft_amount_derived` decimal(19,6) DEFAULT NULL,
+  `balance_end_date_derived` date DEFAULT NULL,
+  `balance_number_of_days_derived` int(11) DEFAULT NULL,
+  `running_balance_derived` decimal(19,6) DEFAULT NULL,
+  `cumulative_balance_derived` decimal(19,6) DEFAULT NULL,
+  `created_date` datetime NOT NULL,
+  `appuser_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKSAT0000000001` (`savings_account_id`),
+  KEY `FK_m_savings_account_transaction_m_payment_detail` (`payment_detail_id`),
+  KEY `FK_m_savings_account_transaction_m_office` (`office_id`),
+  CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `FK_m_savings_account_transaction_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
+  CONSTRAINT `FK_m_savings_account_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_account_transaction: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_account_transaction` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_account_transaction` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_interest_incentives
+DROP TABLE IF EXISTS `m_savings_interest_incentives`;
+CREATE TABLE IF NOT EXISTS `m_savings_interest_incentives` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `deposit_account_interest_rate_slab_id` bigint(20) NOT NULL,
+  `entiry_type` smallint(2) NOT NULL,
+  `attribute_name` smallint(2) NOT NULL,
+  `condition_type` smallint(2) NOT NULL,
+  `attribute_value` varchar(50) NOT NULL,
+  `incentive_type` smallint(2) NOT NULL,
+  `amount` decimal(19,6) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_savings_interest_incentives_m_savings_interest_rate_slab` (`deposit_account_interest_rate_slab_id`),
+  CONSTRAINT `FK_m_savings_interest_incentives_m_savings_interest_rate_slab` FOREIGN KEY (`deposit_account_interest_rate_slab_id`) REFERENCES `m_savings_account_interest_rate_slab` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_interest_incentives: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_interest_incentives` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_interest_incentives` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_officer_assignment_history
+DROP TABLE IF EXISTS `m_savings_officer_assignment_history`;
+CREATE TABLE IF NOT EXISTS `m_savings_officer_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `account_id` bigint(20) NOT NULL,
+  `savings_officer_id` bigint(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_m_savings_officer_assignment_history_0001` (`account_id`),
+  KEY `fk_m_savings_officer_assignment_history_0002` (`savings_officer_id`),
+  CONSTRAINT `fk_m_savings_officer_assignment_history_0001` FOREIGN KEY (`account_id`) REFERENCES `m_savings_account` (`id`),
+  CONSTRAINT `fk_m_savings_officer_assignment_history_0002` FOREIGN KEY (`savings_officer_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_officer_assignment_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_officer_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_officer_assignment_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_product
+DROP TABLE IF EXISTS `m_savings_product`;
+CREATE TABLE IF NOT EXISTS `m_savings_product` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL,
+  `short_name` varchar(4) NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `deposit_type_enum` smallint(5) NOT NULL DEFAULT '100',
+  `currency_code` varchar(3) NOT NULL,
+  `currency_digits` smallint(5) NOT NULL,
+  `currency_multiplesof` smallint(5) DEFAULT NULL,
+  `nominal_annual_interest_rate` decimal(19,6) NOT NULL,
+  `interest_compounding_period_enum` smallint(5) NOT NULL,
+  `interest_posting_period_enum` smallint(5) NOT NULL DEFAULT '4',
+  `interest_calculation_type_enum` smallint(5) NOT NULL,
+  `interest_calculation_days_in_year_type_enum` smallint(5) NOT NULL,
+  `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
+  `lockin_period_frequency_enum` smallint(5) DEFAULT NULL,
+  `accounting_type` smallint(5) NOT NULL,
+  `withdrawal_fee_amount` decimal(19,6) DEFAULT NULL,
+  `withdrawal_fee_type_enum` smallint(5) DEFAULT NULL,
+  `withdrawal_fee_for_transfer` tinyint(4) DEFAULT '1',
+  `allow_overdraft` tinyint(1) NOT NULL DEFAULT '0',
+  `overdraft_limit` decimal(19,6) DEFAULT NULL,
+  `nominal_annual_interest_rate_overdraft` decimal(19,6) DEFAULT '0.000000',
+  `min_overdraft_for_interest_calculation` decimal(19,6) DEFAULT '0.000000',
+  `min_required_balance` decimal(19,6) DEFAULT NULL,
+  `enforce_min_required_balance` tinyint(1) NOT NULL DEFAULT '0',
+  `min_balance_for_interest_calculation` decimal(19,6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `sp_unq_name` (`name`),
+  UNIQUE KEY `sp_unq_short_name` (`short_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_product: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_product` DISABLE KEYS */;
+INSERT INTO `m_savings_product` (`id`, `name`, `short_name`, `description`, `deposit_type_enum`, `currency_code`, `currency_digits`, `currency_multiplesof`, `nominal_annual_interest_rate`, `interest_compounding_period_enum`, `interest_posting_period_enum`, `interest_calculation_type_enum`, `interest_calculation_days_in_year_type_enum`, `min_required_opening_balance`, `lockin_period_frequency`, `lockin_period_frequency_enum`, `accounting_type`, `withdrawal_fee_amount`, `withdrawal_fee_type_enum`, `withdrawal_fee_for_transfer`, `allow_overdraft`, `overdraft_limit`, `nominal_annual_interest_rate_overdraft`, `min_overdraft_for_interest_calculation`, `min_required_balance`, `enforce_min_required_balance`, `min_balance_for_interest_calculation`) VALUES
+	(1, 'Voluntary savings', 'VS', 'Save money', 100, 'USD', 2, 0, 9.500000, 1, 4, 1, 365, 1000.000000, 1.000000, 1, 2, NULL, NULL, 0, 0, NULL, 0.000000, 0.000000, NULL, 0, NULL);
+/*!40000 ALTER TABLE `m_savings_product` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_savings_product_charge
+DROP TABLE IF EXISTS `m_savings_product_charge`;
+CREATE TABLE IF NOT EXISTS `m_savings_product_charge` (
+  `savings_product_id` bigint(20) NOT NULL,
+  `charge_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`savings_product_id`,`charge_id`),
+  KEY `charge_id` (`charge_id`),
+  CONSTRAINT `m_savings_product_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+  CONSTRAINT `m_savings_product_charge_ibfk_2` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_savings_product_charge: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_savings_product_charge` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_savings_product_charge` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_selfservice_user_client_mapping
+DROP TABLE IF EXISTS `m_selfservice_user_client_mapping`;
+CREATE TABLE IF NOT EXISTS `m_selfservice_user_client_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `appuser_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `appuser_id_client_id` (`appuser_id`,`client_id`),
+  KEY `m_selfservice_client_id` (`client_id`),
+  CONSTRAINT `m_selfservice_appuser_id` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `m_selfservice_client_id` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_selfservice_user_client_mapping: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_selfservice_user_client_mapping` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_selfservice_user_client_mapping` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_staff
+DROP TABLE IF EXISTS `m_staff`;
+CREATE TABLE IF NOT EXISTS `m_staff` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `is_loan_officer` tinyint(1) NOT NULL DEFAULT '0',
+  `office_id` bigint(20) DEFAULT NULL,
+  `firstname` varchar(50) DEFAULT NULL,
+  `lastname` varchar(50) DEFAULT NULL,
+  `display_name` varchar(102) NOT NULL,
+  `mobile_no` varchar(50) DEFAULT NULL,
+  `external_id` varchar(100) DEFAULT NULL,
+  `organisational_role_enum` smallint(6) DEFAULT NULL,
+  `organisational_role_parent_staff_id` bigint(20) DEFAULT NULL,
+  `is_active` tinyint(1) NOT NULL DEFAULT '1',
+  `joining_date` date DEFAULT NULL,
+  `image_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `display_name` (`display_name`),
+  UNIQUE KEY `external_id_UNIQUE` (`external_id`),
+  UNIQUE KEY `mobile_no_UNIQUE` (`mobile_no`),
+  KEY `FK_m_staff_m_office` (`office_id`),
+  KEY `FK_m_staff_m_image` (`image_id`),
+  CONSTRAINT `FK_m_staff_m_image` FOREIGN KEY (`image_id`) REFERENCES `m_image` (`id`),
+  CONSTRAINT `FK_m_staff_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_staff: ~3 rows (approximately)
+/*!40000 ALTER TABLE `m_staff` DISABLE KEYS */;
+INSERT INTO `m_staff` (`id`, `is_loan_officer`, `office_id`, `firstname`, `lastname`, `display_name`, `mobile_no`, `external_id`, `organisational_role_enum`, `organisational_role_parent_staff_id`, `is_active`, `joining_date`, `image_id`) VALUES
+	(1, 1, 1, 'Aliya', 'A', 'A, Aliya', NULL, NULL, NULL, NULL, 1, NULL, NULL),
+	(2, 1, 2, 'Mary', 'M', 'M, Mary', NULL, NULL, NULL, NULL, 1, NULL, NULL),
+	(3, 1, 3, 'John', 'K', 'K, John', NULL, NULL, NULL, NULL, 1, NULL, NULL);
+/*!40000 ALTER TABLE `m_staff` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_staff_assignment_history
+DROP TABLE IF EXISTS `m_staff_assignment_history`;
+CREATE TABLE IF NOT EXISTS `m_staff_assignment_history` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `centre_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) NOT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date DEFAULT NULL,
+  `createdby_id` bigint(20) DEFAULT NULL,
+  `created_date` datetime DEFAULT NULL,
+  `lastmodified_date` datetime DEFAULT NULL,
+  `lastmodifiedby_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_m_staff_assignment_history_centre_id_m_group` (`centre_id`),
+  KEY `FK_m_staff_assignment_history_m_staff` (`staff_id`),
+  CONSTRAINT `FK_m_staff_assignment_history_centre_id_m_group` FOREIGN KEY (`centre_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FK_m_staff_assignment_history_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_staff_assignment_history: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_staff_assignment_history` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_staff_assignment_history` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_surveys
+DROP TABLE IF EXISTS `m_surveys`;
+CREATE TABLE IF NOT EXISTS `m_surveys` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `a_key` varchar(32) NOT NULL,
+  `a_name` varchar(255) NOT NULL,
+  `description` varchar(4000) DEFAULT NULL,
+  `country_code` varchar(2) NOT NULL,
+  `valid_from` datetime DEFAULT NULL,
+  `valid_to` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_surveys: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_surveys` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_surveys` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_survey_components
+DROP TABLE IF EXISTS `m_survey_components`;
+CREATE TABLE IF NOT EXISTS `m_survey_components` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` bigint(20) NOT NULL,
+  `a_key` varchar(32) NOT NULL,
+  `a_text` varchar(255) NOT NULL,
+  `description` varchar(4000) DEFAULT NULL,
+  `sequence_no` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `survey_id` (`survey_id`),
+  CONSTRAINT `m_survey_components_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_survey_components: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_components` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_components` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_survey_lookup_tables
+DROP TABLE IF EXISTS `m_survey_lookup_tables`;
+CREATE TABLE IF NOT EXISTS `m_survey_lookup_tables` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` bigint(20) NOT NULL,
+  `a_key` varchar(255) NOT NULL,
+  `description` int(4) DEFAULT NULL,
+  `value_from` int(4) NOT NULL,
+  `value_to` int(4) NOT NULL,
+  `score` decimal(5,2) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `survey_id` (`survey_id`),
+  CONSTRAINT `m_survey_lookup_tables_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_survey_lookup_tables: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_lookup_tables` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_lookup_tables` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_survey_questions
+DROP TABLE IF EXISTS `m_survey_questions`;
+CREATE TABLE IF NOT EXISTS `m_survey_questions` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` bigint(20) NOT NULL,
+  `component_key` varchar(32) DEFAULT NULL,
+  `a_key` varchar(32) NOT NULL,
+  `a_text` varchar(255) NOT NULL,
+  `description` varchar(4000) DEFAULT NULL,
+  `sequence_no` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `survey_id` (`survey_id`),
+  CONSTRAINT `m_survey_questions_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_survey_questions: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_questions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_questions` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_survey_responses
+DROP TABLE IF EXISTS `m_survey_responses`;
+CREATE TABLE IF NOT EXISTS `m_survey_responses` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `question_id` bigint(20) NOT NULL,
+  `a_text` varchar(255) NOT NULL,
+  `a_value` int(4) NOT NULL,
+  `sequence_no` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `question_id` (`question_id`),
+  CONSTRAINT `m_survey_responses_ibfk_1` FOREIGN KEY (`question_id`) REFERENCES `m_survey_questions` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_survey_responses: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_responses` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_responses` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_survey_scorecards
+DROP TABLE IF EXISTS `m_survey_scorecards`;
+CREATE TABLE IF NOT EXISTS `m_survey_scorecards` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `survey_id` bigint(20) NOT NULL,
+  `question_id` bigint(20) NOT NULL,
+  `response_id` bigint(20) NOT NULL,
+  `user_id` bigint(20) NOT NULL,
+  `client_id` bigint(20) NOT NULL,
+  `created_on` datetime DEFAULT NULL,
+  `a_value` int(4) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `survey_id` (`survey_id`),
+  KEY `question_id` (`question_id`),
+  KEY `response_id` (`response_id`),
+  KEY `user_id` (`user_id`),
+  KEY `client_id` (`client_id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `m_surveys` (`id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_2` FOREIGN KEY (`question_id`) REFERENCES `m_survey_questions` (`id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_3` FOREIGN KEY (`response_id`) REFERENCES `m_survey_responses` (`id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_4` FOREIGN KEY (`user_id`) REFERENCES `m_appusers` (`id`),
+  CONSTRAINT `m_survey_scorecards_ibfk_5` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_survey_scorecards: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_survey_scorecards` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_survey_scorecards` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_tellers
+DROP TABLE IF EXISTS `m_tellers`;
+CREATE TABLE IF NOT EXISTS `m_tellers` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `office_id` bigint(20) NOT NULL,
+  `debit_account_id` bigint(20) DEFAULT NULL,
+  `credit_account_id` bigint(20) DEFAULT NULL,
+  `name` varchar(50) NOT NULL,
+  `description` varchar(100) DEFAULT NULL,
+  `valid_from` date DEFAULT NULL,
+  `valid_to` date DEFAULT NULL,
+  `state` smallint(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `m_tellers_name_unq` (`name`),
+  KEY `IK_m_tellers_m_office` (`office_id`),
+  KEY `FK_m_tellers_gl_account_debit_account_id` (`debit_account_id`),
+  KEY `FK_m_tellers_gl_account_credit_account_id` (`credit_account_id`),
+  CONSTRAINT `FK_m_tellers_gl_account_credit_account_id` FOREIGN KEY (`credit_account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_m_tellers_gl_account_debit_account_id` FOREIGN KEY (`debit_account_id`) REFERENCES `acc_gl_account` (`id`),
+  CONSTRAINT `FK_m_tellers_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_tellers: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_tellers` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_tellers` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_template
+DROP TABLE IF EXISTS `m_template`;
+CREATE TABLE IF NOT EXISTS `m_template` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
+  `text` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
+  `entity` int(11) DEFAULT NULL,
+  `type` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unq_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_template: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_template` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_template` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_templatemappers
+DROP TABLE IF EXISTS `m_templatemappers`;
+CREATE TABLE IF NOT EXISTS `m_templatemappers` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `mapperkey` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  `mapperorder` int(11) DEFAULT NULL,
+  `mappervalue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_templatemappers: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_templatemappers` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_templatemappers` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_template_m_templatemappers
+DROP TABLE IF EXISTS `m_template_m_templatemappers`;
+CREATE TABLE IF NOT EXISTS `m_template_m_templatemappers` (
+  `m_template_id` bigint(20) NOT NULL,
+  `mappers_id` bigint(20) NOT NULL,
+  UNIQUE KEY `mappers_id` (`mappers_id`),
+  KEY `mappers_id_2` (`mappers_id`),
+  KEY `m_template_id` (`m_template_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_template_m_templatemappers: ~0 rows (approximately)
+/*!40000 ALTER TABLE `m_template_m_templatemappers` DISABLE KEYS */;
+/*!40000 ALTER TABLE `m_template_m_templatemappers` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.m_working_days
+DROP TABLE IF EXISTS `m_working_days`;
+CREATE TABLE IF NOT EXISTS `m_working_days` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `recurrence` varchar(100) DEFAULT NULL,
+  `repayment_rescheduling_enum` smallint(5) DEFAULT NULL,
+  `extend_term_daily_repayments` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.m_working_days: ~1 rows (approximately)
+/*!40000 ALTER TABLE `m_working_days` DISABLE KEYS */;
+INSERT INTO `m_working_days` (`id`, `recurrence`, `repayment_rescheduling_enum`, `extend_term_daily_repayments`) VALUES
+	(1, 'FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU', 2, 0);
+/*!40000 ALTER TABLE `m_working_days` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.oauth_access_token
+DROP TABLE IF EXISTS `oauth_access_token`;
+CREATE TABLE IF NOT EXISTS `oauth_access_token` (
+  `token_id` varchar(256) DEFAULT NULL,
+  `token` blob,
+  `authentication_id` varchar(256) DEFAULT NULL,
+  `user_name` varchar(256) DEFAULT NULL,
+  `client_id` varchar(256) DEFAULT NULL,
+  `authentication` blob,
+  `refresh_token` varchar(256) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.oauth_access_token: ~0 rows (approximately)
+/*!40000 ALTER TABLE `oauth_access_token` DISABLE KEYS */;
+/*!40000 ALTER TABLE `oauth_access_token` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.oauth_client_details
+DROP TABLE IF EXISTS `oauth_client_details`;
+CREATE TABLE IF NOT EXISTS `oauth_client_details` (
+  `client_id` varchar(128) NOT NULL,
+  `resource_ids` varchar(256) DEFAULT NULL,
+  `client_secret` varchar(256) DEFAULT NULL,
+  `scope` varchar(256) DEFAULT NULL,
+  `authorized_grant_types` varchar(256) DEFAULT NULL,
+  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
+  `authorities` varchar(256) DEFAULT NULL,
+  `access_token_validity` int(11) DEFAULT NULL,
+  `refresh_token_validity` int(11) DEFAULT NULL,
+  `additional_information` varchar(4096) DEFAULT NULL,
+  `autoapprove` bit(1) DEFAULT NULL,
+  PRIMARY KEY (`client_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.oauth_client_details: ~1 rows (approximately)
+/*!40000 ALTER TABLE `oauth_client_details` DISABLE KEYS */;
+INSERT INTO `oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES
+	('community-app', NULL, '123', 'all', 'password,refresh_token', NULL, NULL, NULL, NULL, NULL, NULL);
+/*!40000 ALTER TABLE `oauth_client_details` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.oauth_refresh_token
+DROP TABLE IF EXISTS `oauth_refresh_token`;
+CREATE TABLE IF NOT EXISTS `oauth_refresh_token` (
+  `token_id` varchar(256) DEFAULT NULL,
+  `token` blob,
+  `authentication` blob
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.oauth_refresh_token: ~0 rows (approximately)
+/*!40000 ALTER TABLE `oauth_refresh_token` DISABLE KEYS */;
+/*!40000 ALTER TABLE `oauth_refresh_token` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.ppi_likelihoods
+DROP TABLE IF EXISTS `ppi_likelihoods`;
+CREATE TABLE IF NOT EXISTS `ppi_likelihoods` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) NOT NULL,
+  `name` varchar(250) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.ppi_likelihoods: ~0 rows (approximately)
+/*!40000 ALTER TABLE `ppi_likelihoods` DISABLE KEYS */;
+/*!40000 ALTER TABLE `ppi_likelihoods` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.ppi_likelihoods_ppi
+DROP TABLE IF EXISTS `ppi_likelihoods_ppi`;
+CREATE TABLE IF NOT EXISTS `ppi_likelihoods_ppi` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `likelihood_id` bigint(20) NOT NULL,
+  `ppi_name` varchar(250) NOT NULL,
+  `enabled` int(11) NOT NULL DEFAULT '100',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.ppi_likelihoods_ppi: ~0 rows (approximately)
+/*!40000 ALTER TABLE `ppi_likelihoods_ppi` DISABLE KEYS */;
+/*!40000 ALTER TABLE `ppi_likelihoods_ppi` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.ppi_scores
+DROP TABLE IF EXISTS `ppi_scores`;
+CREATE TABLE IF NOT EXISTS `ppi_scores` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `score_from` int(11) NOT NULL,
+  `score_to` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.ppi_scores: ~20 rows (approximately)
+/*!40000 ALTER TABLE `ppi_scores` DISABLE KEYS */;
+INSERT INTO `ppi_scores` (`id`, `score_from`, `score_to`) VALUES
+	(1, 0, 4),
+	(2, 5, 9),
+	(3, 10, 14),
+	(4, 15, 19),
+	(5, 20, 24),
+	(6, 25, 29),
+	(7, 30, 34),
+	(8, 35, 39),
+	(9, 40, 44),
+	(10, 45, 49),
+	(11, 50, 54),
+	(12, 55, 59),
+	(13, 60, 64),
+	(14, 65, 69),
+	(15, 70, 74),
+	(16, 75, 79),
+	(17, 80, 84),
+	(18, 85, 89),
+	(19, 90, 94),
+	(20, 95, 100);
+/*!40000 ALTER TABLE `ppi_scores` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.ref_loan_transaction_processing_strategy
+DROP TABLE IF EXISTS `ref_loan_transaction_processing_strategy`;
+CREATE TABLE IF NOT EXISTS `ref_loan_transaction_processing_strategy` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `code` varchar(100) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `sort_order` int(4) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `ltp_strategy_code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.ref_loan_transaction_processing_strategy: ~7 rows (approximately)
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` DISABLE KEYS */;
+INSERT INTO `ref_loan_transaction_processing_strategy` (`id`, `code`, `name`, `sort_order`) VALUES
+	(1, 'mifos-standard-strategy', 'Penalties, Fees, Interest, Principal order', 1),
+	(2, 'heavensfamily-strategy', 'HeavensFamily Unique', 6),
+	(3, 'creocore-strategy', 'Creocore Unique', 7),
+	(4, 'rbi-india-strategy', 'Overdue/Due Fee/Int,Principal', 2),
+	(5, 'principal-interest-penalties-fees-order-strategy', 'Principal, Interest, Penalties, Fees Order', 3),
+	(6, 'interest-principal-penalties-fees-order-strategy', 'Interest, Principal, Penalties, Fees Order', 4),
+	(7, 'early-repayment-strategy', 'Early Repayment Strategy', 5);
+/*!40000 ALTER TABLE `ref_loan_transaction_processing_strategy` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.rpt_sequence
+DROP TABLE IF EXISTS `rpt_sequence`;
+CREATE TABLE IF NOT EXISTS `rpt_sequence` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.rpt_sequence: ~0 rows (approximately)
+/*!40000 ALTER TABLE `rpt_sequence` DISABLE KEYS */;
+/*!40000 ALTER TABLE `rpt_sequence` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.r_enum_value
+DROP TABLE IF EXISTS `r_enum_value`;
+CREATE TABLE IF NOT EXISTS `r_enum_value` (
+  `enum_name` varchar(100) NOT NULL,
+  `enum_id` int(11) NOT NULL,
+  `enum_message_property` varchar(100) NOT NULL,
+  `enum_value` varchar(100) NOT NULL,
+  `enum_type` tinyint(1) NOT NULL,
+  PRIMARY KEY (`enum_name`,`enum_id`),
+  UNIQUE KEY `enum_message_property` (`enum_name`,`enum_message_property`),
+  UNIQUE KEY `enum_value` (`enum_name`,`enum_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.r_enum_value: ~64 rows (approximately)
+/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
+INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES
+	('amortization_method_enum', 0, 'Equal principle payments', 'Equal principle payments', 0),
+	('amortization_method_enum', 1, 'Equal installments', 'Equal installments', 0),
+	('interest_calculated_in_period_enum', 0, 'Daily', 'Daily', 0),
+	('interest_calculated_in_period_enum', 1, 'Same as repayment period', 'Same as repayment period', 0),
+	('interest_method_enum', 0, 'Declining Balance', 'Declining Balance', 0),
+	('interest_method_enum', 1, 'Flat', 'Flat', 0),
+	('interest_period_frequency_enum', 2, 'Per month', 'Per month', 0),
+	('interest_period_frequency_enum', 3, 'Per year', 'Per year', 0),
+	('loan_status_id', 0, 'Invalid', 'Invalid', 0),
+	('loan_status_id', 100, 'Submitted and awaiting approval', 'Submitted and awaiting approval', 0),
+	('loan_status_id', 200, 'Approved', 'Approved', 0),
+	('loan_status_id', 300, 'Active', 'Active', 0),
+	('loan_status_id', 400, 'Withdrawn by client', 'Withdrawn by client', 0),
+	('loan_status_id', 500, 'Rejected', 'Rejected', 0),
+	('loan_status_id', 600, 'Closed', 'Closed', 0),
+	('loan_status_id', 601, 'Written-Off', 'Written-Off', 0),
+	('loan_status_id', 602, 'Rescheduled', 'Rescheduled', 0),
+	('loan_status_id', 700, 'Overpaid', 'Overpaid', 0),
+	('loan_transaction_strategy_id', 1, 'mifos-standard-strategy', 'Mifos style', 0),
+	('loan_transaction_strategy_id', 2, 'heavensfamily-strategy', 'Heavensfamily', 0),
+	('loan_transaction_strategy_id', 3, 'creocore-strategy', 'Creocore', 0),
+	('loan_transaction_strategy_id', 4, 'rbi-india-strategy', 'RBI (India)', 0),
+	('processing_result_enum', 0, 'invalid', 'Invalid', 0),
+	('processing_result_enum', 1, 'processed', 'Processed', 0),
+	('processing_result_enum', 2, 'awaiting.approval', 'Awaiting Approval', 0),
+	('processing_result_enum', 3, 'rejected', 'Rejected', 0),
+	('repayment_period_frequency_enum', 0, 'Days', 'Days', 0),
+	('repayment_period_frequency_enum', 1, 'Weeks', 'Weeks', 0),
+	('repayment_period_frequency_enum', 2, 'Months', 'Months', 0),
+	('savings_transaction_type_enum', 1, 'deposit', 'deposit', 0),
+	('savings_transaction_type_enum', 2, 'withdrawal', 'withdrawal', 1),
+	('savings_transaction_type_enum', 3, 'Interest Posting', 'Interest Posting', 0),
+	('savings_transaction_type_enum', 4, 'Withdrawal Fee', 'Withdrawal Fee', 1),
+	('savings_transaction_type_enum', 5, 'Annual Fee', 'Annual Fee', 1),
+	('savings_transaction_type_enum', 6, 'Waive Charge', 'Waive Charge', 0),
+	('savings_transaction_type_enum', 7, 'Pay Charge', 'Pay Charge', 1),
+	('savings_transaction_type_enum', 12, 'Initiate Transfer', 'Initiate Transfer', 0),
+	('savings_transaction_type_enum', 13, 'Approve Transfer', 'Approve Transfer', 0),
+	('savings_transaction_type_enum', 14, 'Withdraw Transfer', 'Withdraw Transfer', 0),
+	('savings_transaction_type_enum', 15, 'Reject Transfer', 'Reject Transfer', 0),
+	('savings_transaction_type_enum', 16, 'Written-Off', 'Written-Off', 0),
+	('savings_transaction_type_enum', 17, 'Overdraft Interest', 'Overdraft Interest', 0),
+	('status_enum', 0, 'Invalid', 'Invalid', 0),
+	('status_enum', 100, 'Pending', 'Pending', 0),
+	('status_enum', 300, 'Active', 'Active', 0),
+	('status_enum', 600, 'Closed', 'Closed', 0),
+	('teller_status', 300, 'Active', 'Active', 0),
+	('teller_status', 400, 'Inactive', 'Inactive', 0),
+	('teller_status', 600, 'Closed', 'Closed', 0),
+	('term_period_frequency_enum', 0, 'Days', 'Days', 0),
+	('term_period_frequency_enum', 1, 'Weeks', 'Weeks', 0),
+	('term_period_frequency_enum', 2, 'Months', 'Months', 0),
+	('term_period_frequency_enum', 3, 'Years', 'Years', 0),
+	('transaction_type_enum', 1, 'Disbursement', 'Disbursement', 0),
+	('transaction_type_enum', 2, 'Repayment', 'Repayment', 0),
+	('transaction_type_enum', 3, 'Contra', 'Contra', 0),
+	('transaction_type_enum', 4, 'Waive Interest', 'Waive Interest', 0),
+	('transaction_type_enum', 5, 'Repayment At Disbursement', 'Repayment At Disbursement', 0),
+	('transaction_type_enum', 6, 'Write-Off', 'Write-Off', 0),
+	('transaction_type_enum', 7, 'Marked for Rescheduling', 'Marked for Rescheduling', 0),
+	('transaction_type_enum', 8, 'Recovery Repayment', 'Recovery Repayment', 0),
+	('transaction_type_enum', 9, 'Waive Charges', 'Waive Charges', 0),
+	('transaction_type_enum', 10, 'Apply Charges', 'Apply Charges', 0),
+	('transaction_type_enum', 11, 'Apply Interest', 'Apply Interest', 0);
+/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.scheduler_detail
+DROP TABLE IF EXISTS `scheduler_detail`;
+CREATE TABLE IF NOT EXISTS `scheduler_detail` (
+  `id` smallint(2) NOT NULL AUTO_INCREMENT,
+  `is_suspended` tinyint(1) NOT NULL DEFAULT '0',
+  `execute_misfired_jobs` tinyint(1) NOT NULL DEFAULT '1',
+  `reset_scheduler_on_bootup` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.scheduler_detail: ~1 rows (approximately)
+/*!40000 ALTER TABLE `scheduler_detail` DISABLE KEYS */;
+INSERT INTO `scheduler_detail` (`id`, `is_suspended`, `execute_misfired_jobs`, `reset_scheduler_on_bootup`) VALUES
+	(1, 0, 1, 1);
+/*!40000 ALTER TABLE `scheduler_detail` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.schema_version
+DROP TABLE IF EXISTS `schema_version`;
+CREATE TABLE IF NOT EXISTS `schema_version` (
+  `version_rank` int(11) NOT NULL,
+  `installed_rank` int(11) NOT NULL,
+  `version` varchar(50) NOT NULL,
+  `description` varchar(200) NOT NULL,
+  `type` varchar(20) NOT NULL,
+  `script` varchar(1000) NOT NULL,
+  `checksum` int(11) DEFAULT NULL,
+  `installed_by` varchar(100) NOT NULL,
+  `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `execution_time` int(11) NOT NULL,
+  `success` tinyint(1) NOT NULL,
+  PRIMARY KEY (`version`),
+  KEY `schema_version_vr_idx` (`version_rank`),
+  KEY `schema_version_ir_idx` (`installed_rank`),
+  KEY `schema_version_s_idx` (`success`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+-- Dumping data for table mifostenant-reference.schema_version: ~293 rows (approximately)
+/*!40000 ALTER TABLE `schema_version` DISABLE KEYS */;
+INSERT INTO `schema_version` (`version_rank`, `installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES
+	(1, 1, '1', 'mifosplatform-core-ddl-latest', 'SQL', 'V1__mifosplatform-core-ddl-latest.sql', -1957145051, 'root', '2014-03-08 02:28:38', 10710, 1),
+	(10, 10, '10', 'interest-posting-fields-for-savings', 'SQL', 'V10__interest-posting-fields-for-savings.sql', -1133853485, 'root', '2014-03-08 02:28:43', 1339, 1),
+	(100, 100, '100', 'Group saving summary report', 'SQL', 'V100__Group_saving_summary_report.sql', -1635399448, 'root', '2014-03-08 02:29:23', 26, 1),
+	(101, 101, '101', 'add mulitplesof to account transfers table', 'SQL', 'V101__add_mulitplesof_to_account_transfers_table.sql', -1162976022, 'root', '2014-03-08 02:29:24', 271, 1),
+	(102, 102, '102', 'client attendance tables', 'SQL', 'V102__client_attendance_tables.sql', -45448019, 'root', '2014-03-08 02:29:24', 359, 1),
+	(103, 103, '103', 'cluster support for batch jobs', 'SQL', 'V103__cluster_support_for_batch_jobs.sql', -781879007, 'root', '2014-03-08 02:29:25', 489, 1),
+	(104, 104, '104', 'permissions for transfers', 'SQL', 'V104__permissions_for_transfers.sql', 653895919, 'root', '2014-03-08 02:29:25', 51, 1),
+	(105, 105, '105', 'track loan transaction against office', 'SQL', 'V105__track_loan_transaction_against_office.sql', 785650440, 'root', '2014-03-08 02:29:26', 1260, 1),
+	(106, 106, '106', 'more permissions for transfers', 'SQL', 'V106__more_permissions_for_transfers.sql', -1132691133, 'root', '2014-03-08 02:29:27', 63, 1),
+	(107, 107, '107', 'datatable code mappings', 'SQL', 'V107__datatable_code_mappings.sql', 630737271, 'root', '2014-03-08 02:29:27', 185, 1),
+	(108, 108, '108', 'client has transfer office', 'SQL', 'V108__client_has_transfer_office.sql', -1748734810, 'root', '2014-03-08 02:29:27', 356, 1),
+	(109, 109, '109', 'account transfer withdrawal fee configuration', 'SQL', 'V109__account_transfer_withdrawal_fee_configuration.sql', -754569033, 'root', '2014-03-08 02:29:28', 524, 1),
+	(11, 11, '11', 'add-payment-details', 'SQL', 'V11__add-payment-details.sql', 391380768, 'root', '2014-03-08 02:28:44', 404, 1),
+	(110, 110, '110', 'group center close', 'SQL', 'V110__group_center_close.sql', -1261775365, 'root', '2014-03-08 02:29:28', 326, 1),
+	(111, 111, '111', 'disable constraint approach for datatables by default', 'SQL', 'V111__disable_constraint_approach_for_datatables_by_default.sql', 2058257907, 'root', '2014-03-08 02:29:29', 28, 1),
+	(112, 112, '111.1', 'set default transfers in suspense account for existing loan products', 'SQL', 'V111_1__set default_transfers_in_suspense_account_for_existing_loan_products.sql', 1907173791, 'root', '2014-03-08 02:29:29', 5, 1),
+	(113, 113, '112', 'mixreport sql support', 'SQL', 'V112__mixreport_sql_support.sql', 1254859560, 'root', '2014-03-08 02:29:29', 655, 1),
+	(114, 114, '113', 'track savings transaction against office', 'SQL', 'V113__track_savings_transaction_against_office.sql', -1390529632, 'root', '2014-03-08 02:29:30', 799, 1),
+	(115, 115, '114', 'set default transfers in suspense account for existing savings products - Copy', 'SQL', 'V114__set_default_transfers_in_suspense_account_for_existing_savings_products - Copy.sql', 1518369372, 'root', '2014-03-08 02:29:30', 3, 1),
+	(116, 116, '115', 'permissions for cache api', 'SQL', 'V115__permissions_for_cache_api.sql', 156437687, 'root', '2014-03-08 02:29:31', 147, 1),
+	(117, 117, '116', 'track currency for journal entries', 'SQL', 'V116__track_currency_for_journal_entries.sql', 1109139399, 'root', '2014-03-08 02:29:31', 613, 1),
+	(118, 118, '117', 'loan charge from savings', 'SQL', 'V117__loan_charge_from_savings.sql', 13633826, 'root', '2014-03-08 02:29:32', 942, 1),
+	(119, 119, '118', 'savings charge', 'SQL', 'V118__savings_charge.sql', 899101813, 'root', '2014-03-08 02:29:33', 522, 1),
+	(120, 120, '118.1', 'savings charge patch update', 'SQL', 'V118_1__savings_charge_patch_update.sql', 426133125, 'root', '2014-03-08 02:29:34', 304, 1),
+	(121, 121, '118.2', 'product mapping delete duplicate fund source to account mappings', 'SQL', 'V118_2__product_mapping_delete_duplicate_fund_source_to_account_mappings.sql', 139468093, 'root', '2014-03-08 02:29:34', 3, 1),
+	(122, 122, '118.3', 'permissions form propose and accept client transfers', 'SQL', 'V118_3__permissions_form_propose_and_accept_client_transfers.sql', 602708322, 'root', '2014-03-08 02:29:34', 26, 1),
+	(123, 123, '118.4', 'reset default transfers in suspense account for existing savings products', 'SQL', 'V118_4__reset_default_transfers_in_suspense_account_for_existing_savings_products.sql', 1246865828, 'root', '2014-03-08 02:29:34', 7, 1),
+	(124, 124, '118.5', 'batch job entry for pay savings charge', 'SQL', 'V118_5__batch_job_entry_for_pay_savings_charge.sql', -1477017272, 'root', '2014-03-08 02:29:34', 34, 1),
+	(125, 125, '118.6', 'defaults for income from penalties for savings product', 'SQL', 'V118_6__defaults_for_income_from_penalties_for savings_product.sql', 255024118, 'root', '2014-03-08 02:29:34', 3, 1),
+	(126, 126, '118.7', 'move withdrawal annual fee to charges', 'SQL', 'V118_7__move_withdrawal_annual_fee_to_charges.sql', 480656720, 'root', '2014-03-08 02:29:35', 1079, 1),
+	(127, 127, '118.8', 'track overpayments seperately in loan transactions', 'SQL', 'V118_8__track_overpayments_seperately_in_loan_transactions.sql', 1684107411, 'root', '2014-03-08 02:29:36', 342, 1),
+	(128, 128, '119', 'add template table', 'SQL', 'V119__add_template_table.sql', 1524629249, 'root', '2014-03-08 02:29:36', 388, 1),
+	(12, 12, '12', 'add external id to couple of tables', 'SQL', 'V12__add_external_id_to_couple_of_tables.sql', 371833586, 'root', '2014-03-08 02:28:45', 1332, 1),
+	(129, 129, '120', 'accounting running balance', 'SQL', 'V120__accounting_running_balance.sql', 746738547, 'root', '2014-03-08 02:29:37', 619, 1),
+	(130, 130, '121', 'accounting running balance for organization', 'SQL', 'V121__accounting_running_balance_for_organization.sql', 1907762382, 'root', '2014-03-08 02:29:38', 488, 1),
+	(131, 131, '122', 'recurring fee support for savings', 'SQL', 'V122__recurring_fee_support_for_savings.sql', -1538770236, 'root', '2014-03-08 02:29:38', 527, 1),
+	(132, 132, '123', 'remove payment mode for savings', 'SQL', 'V123__remove_payment_mode_for_savings.sql', 1909778922, 'root', '2014-03-08 02:29:39', 186, 1),
+	(133, 133, '124', 'added min max cap for charges', 'SQL', 'V124__added_min_max_cap_for_charges.sql', -1996899270, 'root', '2014-03-08 02:29:39', 472, 1),
+	(134, 134, '125', 'added column for actual fee amount or percentage', 'SQL', 'V125__added_column_for_actual_fee_amount_or_percentage.sql', -87760502, 'root', '2014-03-08 02:29:40', 230, 1),
+	(135, 135, '126', 'initial database structure for sms outbound', 'SQL', 'V126__initial_database_structure_for_sms_outbound.sql', -586195149, 'root', '2014-03-08 02:29:40', 252, 1),
+	(136, 136, '127', 'mobile no fields', 'SQL', 'V127__mobile_no_fields.sql', -659228285, 'root', '2014-03-08 02:29:40', 417, 1),
+	(137, 137, '128', 'added loan installment charge', 'SQL', 'V128__added_loan_installment_charge.sql', -1983585024, 'root', '2014-03-08 02:29:41', 145, 1),
+	(138, 138, '129', 'client and group timeline', 'SQL', 'V129__client_and_group_timeline.sql', -1671377251, 'root', '2014-03-08 02:29:42', 840, 1),
+	(13, 13, '13', 'add group and client pending configuration', 'SQL', 'V13__add_group_and_client_pending_configuration.sql', 145878397, 'root', '2014-03-08 02:28:46', 58, 1),
+	(139, 139, '130', 'calendar-history-table', 'SQL', 'V130__calendar-history-table.sql', -475045678, 'root', '2014-03-08 02:29:42', 180, 1),
+	(140, 140, '131', 'holiday-status-column-and-permissions', 'SQL', 'V131__holiday-status-column-and-permissions.sql', -1387001593, 'root', '2014-03-08 02:29:43', 862, 1),
+	(141, 141, '132', 'borrower cycle changes', 'SQL', 'V132__borrower_cycle_changes.sql', -355052428, 'root', '2014-03-08 02:29:44', 579, 1),
+	(142, 142, '133', 'adding payment detail with journal entry', 'SQL', 'V133__adding_payment_detail_with_journal_entry.sql', 1975659943, 'root', '2014-03-08 02:29:44', 399, 1),
+	(143, 143, '134', 'added column value on c configuration', 'SQL', 'V134__added_column_value_on_c_configuration.sql', -30626232, 'root', '2014-03-08 02:29:45', 495, 1),
+	(144, 144, '134.1', 'submitted date updation for clients', 'SQL', 'V134_1__submitted_date_updation_for_clients.sql', 184599342, 'root', '2014-03-08 02:29:45', 7, 1),
+	(145, 145, '134.2', 'permissions spelling correction', 'SQL', 'V134_2__permissions_spelling_correction.sql', 996960341, 'root', '2014-03-08 02:29:45', 73, 1),
+	(146, 146, '135', 'added permission for undo written off', 'SQL', 'V135__added_permission_for_undo_written_off.sql', 1414936537, 'root', '2014-03-08 02:29:45', 50, 1),
+	(147, 147, '136.1', 'update script strechy parameter', 'SQL', 'V136_1__update_script_strechy_parameter.sql', 633461657, 'root', '2014-03-08 02:29:46', 56, 1),
+	(148, 148, '137', 'added is active column in m staff', 'SQL', 'V137__added_is_active_column_in_m_staff.sql', 1962782431, 'root', '2014-03-08 02:29:46', 494, 1),
+	(149, 149, '138', 'add short name for m product loan and m savings product', 'SQL', 'V138__add_short_name_for_m_product_loan_and_m_savings_product.sql', -1526828084, 'root', '2014-03-08 02:29:51', 4619, 1),
+	(150, 150, '139', 'default value for is active updated to true in m staff', 'SQL', 'V139__default_value_for_is_active_updated_to_true_in_m_staff.sql', 844329308, 'root', '2014-03-08 02:29:52', 569, 1),
+	(14, 14, '14', 'rename status id to enum', 'SQL', 'V14__rename_status_id_to_enum.sql', 1958382098, 'root', '2014-03-08 02:28:46', 532, 1),
+	(151, 151, '140', 'added loan charge status', 'SQL', 'V140__added_loan_charge_status.sql', 1209971905, 'root', '2014-03-08 02:29:53', 657, 1),
+	(152, 152, '140.1', 'added payment detail id in ac gl journal entry', 'SQL', 'V140_1__added_payment_detail_id_in_ac_gl_journal_entry.sql', -214253481, 'root', '2014-03-08 02:29:53', 664, 1),
+	(153, 153, '141', 'add early repayment strategy', 'SQL', 'V141__add_early_repayment_strategy.sql', 401969634, 'root', '2014-03-08 02:29:54', 56, 1),
+	(154, 154, '142', 'read savingsaccount charge permission', 'SQL', 'V142__read_savingsaccount_charge_permission.sql', -1798083956, 'root', '2014-03-08 02:29:54', 64, 1),
+	(155, 155, '143', 'create journalentry checker permission', 'SQL', 'V143__create_journalentry_checker_permission.sql', 227507002, 'root', '2014-03-08 02:29:54', 57, 1),
+	(156, 156, '144', 'spelling mistake corrections', 'SQL', 'V144__spelling_mistake_corrections.sql', -778391100, 'root', '2014-03-08 02:29:55', 547, 1),
+	(157, 157, '145', 'add force password reset in c configuration', 'SQL', 'V145__add_force_password_reset_in_c_configuration.sql', -662441756, 'root', '2014-03-08 02:29:57', 2286, 1),
+	(158, 158, '146', 'tranche loans', 'SQL', 'V146__tranche_loans.sql', 989126672, 'root', '2014-03-08 02:30:02', 4290, 1),
+	(159, 159, '147', 'tranche loans column name changes', 'SQL', 'V147__tranche_loans_column_name_changes.sql', -533159256, 'root', '2014-03-08 02:30:03', 812, 1),
+	(160, 160, '148', 'overdraft changes', 'SQL', 'V148__overdraft_changes.sql', -1470217992, 'root', '2014-03-08 02:30:07', 3837, 1),
+	(161, 161, '149', 'add created date savings transaction', 'SQL', 'V149__add_created_date_savings_transaction.sql', 137884095, 'root', '2014-03-08 02:30:08', 772, 1),
+	(15, 15, '15', 'center permissions', 'SQL', 'V15__center_permissions.sql', 1124247014, 'root', '2014-03-08 02:28:46', 28, 1),
+	(162, 162, '150', 'basic savings report', 'SQL', 'V150__basic_savings_report.sql', -1500021911, 'root', '2014-03-08 02:30:09', 1191, 1),
+	(163, 163, '151', 'add default savings account to client', 'SQL', 'V151__add_default_savings_account_to_client.sql', -2012873976, 'root', '2014-04-03 03:47:49', 105, 1),
+	(164, 164, '152', 'added grace for over due', 'SQL', 'V152__added_grace_for_over_due.sql', 1917777205, 'root', '2014-04-03 03:47:49', 170, 1),
+	(165, 165, '153', 'Insert missed permissions', 'SQL', 'V153__Insert_missed_permissions.sql', -1693091937, 'root', '2014-04-03 03:47:49', 13, 1),
+	(166, 166, '154', 'aging details', 'SQL', 'V154__aging_details.sql', 1117759702, 'root', '2014-04-03 03:47:49', 14, 1),
+	(167, 167, '155', 'stretchy into pentaho', 'SQL', 'V155__stretchy_into_pentaho.sql', -1836158085, 'root', '2014-04-03 03:47:49', 137, 1),
+	(168, 168, '156', 'added loan saving txns pentaho', 'SQL', 'V156__added_loan_saving_txns_pentaho.sql', 1942570756, 'root', '2014-04-03 03:47:49', 6, 1),
+	(169, 169, '157', 'overdue charge improvements', 'SQL', 'V157__overdue_charge_improvements.sql', -1267720651, 'root', '2014-04-03 03:47:50', 106, 1),
+	(170, 170, '158', 'dashboard and navigation queries', 'SQL', 'V158__dashboard_and_navigation_queries.sql', -265915721, 'root', '2014-04-03 03:47:50', 14, 1),
+	(171, 171, '159', 'add transaction id column m portfolio command source', 'SQL', 'V159__add_transaction_id_column_m_portfolio_command_source.sql', -1834626647, 'root', '2014-05-01 23:10:29', 2615, 1),
+	(16, 16, '16', 'drop min max column on loan table', 'SQL', 'V16__drop_min_max_column_on_loan_table.sql', -1497882087, 'root', '2014-03-08 02:28:47', 275, 1),
+	(172, 172, '160', 'standing instruction changes', 'SQL', 'V160__standing_instruction_changes.sql', -2130923257, 'root', '2014-05-01 23:10:35', 5658, 1),
+	(173, 191, '160.2', 'Allow nullValue For principal on lonProduct', 'SQL', 'V160_2__Allow_nullValue_For_principal_on_lonProduct.sql', 844844635, 'root', '2014-07-11 06:57:59', 1699, 1),
+	(174, 173, '161', 'added accrual batch job', 'SQL', 'V161__added_accrual_batch_job.sql', -1558441026, 'root', '2014-05-01 23:10:36', 813, 1),
+	(175, 174, '162', 'overdue charge batch job', 'SQL', 'V162__overdue_charge_batch_job.sql', -1213828784, 'root', '2014-05-01 23:10:36', 15, 1),
+	(176, 175, '163', 'added npa for loans', 'SQL', 'V163__added_npa_for_loans.sql', -381581272, 'root', '2014-05-01 23:10:41', 4713, 1),
+	(177, 176, '164', 'fd and rd deposit tables', 'SQL', 'V164__fd_and_rd_deposit_tables.sql', -489803231, 'root', '2014-05-01 23:10:48', 7168, 1),
+	(178, 177, '165', 'added permission for disburse to saving account', 'SQL', 'V165__added_permission_for_disburse_to_saving_account.sql', -2109143723, 'root', '2014-05-01 23:10:48', 100, 1),
+	(179, 178, '166', 'added deposit amount to product term and preclosure', 'SQL', 'V166__added_deposit_amount_to_product_term_and_preclosure.sql', -2068527520, 'root', '2014-05-01 23:10:50', 1411, 1),
+	(180, 179, '167', 'added columns for writtenOff loans recovered', 'SQL', 'V167__added_columns_for_writtenOff_loans_recovered.sql', -901133645, 'root', '2014-06-11 21:39:12', 3242, 1),
+	(181, 180, '168', 'added transfer fixed deposit interest to linked account', 'SQL', 'V168__added_transfer_fixed_deposit_interest_to_linked_account.sql', 1513454871, 'root', '2014-06-11 21:39:15', 2239, 1),
+	(182, 181, '169', 'update dashboard reports to core reports use report to false', 'SQL', 'V169__update_dashboard_reports_to_core_reports_use_report_to_false.sql', 1910199831, 'root', '2014-06-11 21:39:15', 39, 1),
+	(17, 17, '17', 'update stretchy reporting ddl', 'SQL', 'V17__update_stretchy_reporting_ddl.sql', 2040068410, 'root', '2014-03-08 02:28:48', 1519, 1),
+	(183, 182, '170', 'update deposit accounts maturity details job', 'SQL', 'V170__update_deposit_accounts_maturity_details_job.sql', 348328732, 'root', '2014-06-11 21:39:15', 31, 1),
+	(184, 183, '171', 'added mandatory savings and rd changes', 'SQL', 'V171__added_mandatory_savings_and_rd_changes.sql', -219494664, 'root', '2014-06-11 21:39:21', 5645, 1),
+	(185, 184, '172', 'accounting changes for transfers', 'SQL', 'V172__accounting_changes_for_transfers.sql', 1989818135, 'root', '2014-06-11 21:39:22', 1052, 1),
+	(186, 185, '173', 'ppi', 'SQL', 'V173__ppi.sql', -2061337506, 'root', '2014-06-11 21:39:25', 3126, 1),
+	(187, 186, '174', 'remove interest accrual', 'SQL', 'V174__remove_interest_accrual.sql', 1343795196, 'root', '2014-06-11 21:39:25', 3, 1),
+	(188, 187, '175', 'added incentive interest rates', 'SQL', 'V175__added_incentive_interest_rates.sql', 1609110836, 'root', '2014-06-11 21:39:33', 7507, 1),
+	(189, 188, '176', 'updates to financial activity accounts', 'SQL', 'V176__updates_to_financial_activity_accounts.sql', 242225588, 'root', '2014-06-17 05:16:55', 2330, 1),
+	(190, 189, '177', 'cleanup for client incentives', 'SQL', 'V177__cleanup_for_client_incentives.sql', -2131344758, 'root', '2014-06-17 05:16:56', 36, 1),
+	(191, 190, '178', 'updates to financial activity accounts pt2', 'SQL', 'V178__updates_to_financial_activity_accounts_pt2.sql', -417659005, 'root', '2014-06-17 05:16:56', 40, 1),
+	(192, 192, '179', 'updates to action names for maker checker permissions', 'SQL', 'V179__updates_to_action_names_for_maker_checker_permissions.sql', -239637884, 'root', '2014-07-11 06:57:59', 163, 1),
+	(18, 18, '18', 'update stretchy reporting reportSql', 'SQL', 'V18__update_stretchy_reporting_reportSql.sql', -170206095, 'root', '2014-03-08 02:28:48', 29, 1),
+	(193, 193, '180', 'update report schemas for disbursed vs awaitingdisbursal and groupnamesbystaff', 'SQL', 'V180__update_report_schemas_for_disbursed_vs_awaitingdisbursal_and_groupnamesbystaff.sql', -478172694, 'root', '2014-07-11 06:57:59', 43, 1),
+	(194, 194, '181', 'standing instruction logging', 'SQL', 'V181__standing_instruction_logging.sql', -259580241, 'root', '2014-07-11 06:58:00', 848, 1),
+	(195, 195, '182', 'added min required balance to savings product', 'SQL', 'V182__added_min_required_balance_to_savings_product.sql', -2083442779, 'root', '2014-07-11 06:58:03', 2500, 1),
+	(196, 196, '183', 'added min balance for interest calculation', 'SQL', 'V183__added_min_balance_for_interest_calculation.sql', -1892956044, 'root', '2014-07-11 06:58:05', 2157, 1),
+	(197, 197, '184', 'update min required balance for savings product', 'SQL', 'V184__update_min_required_balance_for_savings_product.sql', -978631870, 'root', '2014-07-11 06:58:06', 618, 1),
+	(198, 198, '185', 'add accrual till date for periodic accrual', 'SQL', 'V185__add_accrual_till_date_for_periodic_accrual.sql', 1925372415, 'root', '2014-10-15 04:49:45', 3845, 1),
+	(199, 199, '186', 'added periodic accrual job', 'SQL', 'V186__added_periodic_accrual_job.sql', 292417488, 'root', '2014-10-15 04:49:45', 71, 1),
+	(200, 200, '187', 'added permission to periodic accrual', 'SQL', 'V187__added_permission_to_periodic_accrual.sql', 1479836850, 'root', '2014-10-15 04:49:45', 24, 1),
+	(201, 201, '188', 'add savingscharge inactivate permissions', 'SQL', 'V188__add_savingscharge_inactivate_permissions.sql', 2095096043, 'root', '2014-10-15 04:49:46', 945, 1),
+	(202, 202, '189', 'm loan interest recalculation tables', 'SQL', 'V189__m_loan_interest_recalculation_tables.sql', -61157169, 'root', '2014-10-15 04:49:53', 6949, 1),
+	(19, 19, '19', 'report maintenance permissions', 'SQL', 'V19__report_maintenance_permissions.sql', -1528956905, 'root', '2014-03-08 02:28:49', 26, 1),
+	(203, 203, '190', 'add associategroup disassociategroup permissions', 'SQL', 'V190__add_associategroup_disassociategroup_permissions.sql', 296284732, 'root', '2014-10-15 04:49:53', 30, 1),
+	(204, 204, '191', 'update gl account increase size of name col', 'SQL', 'V191__update_gl_account_increase_size_of_name_col.sql', -247079901, 'root', '2014-10-15 04:49:55', 1552, 1),
+	(205, 205, '192', 'interest recalculate job', 'SQL', 'V192__interest_recalculate_job.sql', 589462859, 'root', '2014-10-15 04:49:55', 43, 1),
+	(206, 206, '193', 'added column joiningDate for staff', 'SQL', 'V193__added_column_joiningDate_for_staff.sql', 586097114, 'root', '2014-10-15 04:49:56', 1100, 1),
+	(207, 207, '194', 'added recalculatedInterestComponent for interest recalculation', 'SQL', 'V194__added_recalculatedInterestComponent_for_interest_recalculation.sql', 1691292674, 'root', '2014-10-15 04:49:57', 815, 1),
+	(208, 208, '195', 'moved rest frequency to product level', 'SQL', 'V195__moved_rest_frequency_to_product_level.sql', 1697161943, 'root', '2014-10-15 04:49:59', 1551, 1),
+	(209, 209, '196', 'added loan running balance to transactions', 'SQL', 'V196__added_loan_running_balance_to_transactions.sql', 320541466, 'root', '2014-10-15 04:50:00', 1091, 1),
+	(210, 210, '197', 'updated loan running balance of transactions', 'SQL', 'V197__updated_loan_running_balance_of_transactions.sql', 514142390, 'root', '2014-10-15 04:50:00', 360, 1),
+	(211, 211, '198', 'loan rescheduling tables and permissions', 'SQL', 'V198__loan_rescheduling_tables_and_permissions.sql', 320951317, 'root', '2014-10-15 04:50:05', 4569, 1),
+	(212, 212, '199', 'removed extra columns from schedule history', 'SQL', 'V199__removed_extra_columns_from_schedule_history.sql', -885550667, 'root', '2014-10-15 04:50:06', 695, 1),
+	(2, 2, '2', 'mifosx-base-reference-data-utf8', 'SQL', 'V2__mifosx-base-reference-data-utf8.sql', 1316484475, 'root', '2014-03-08 02:28:38', 106, 1),
+	(20, 20, '20', 'report maint perms really configuration', 'SQL', 'V20__report_maint_perms_really_configuration.sql', -402845015, 'root', '2014-03-08 02:28:49', 31, 1),
+	(213, 213, '200', 'alter savings account for start interest calculation date', 'SQL', 'V200__alter_savings_account_for_start_interest_calculation_date.sql', 338554725, 'root', '2014-10-15 04:50:07', 1303, 1),
+	(214, 214, '201', 'webhooks', 'SQL', 'V201__webhooks.sql', 1446707293, 'root', '2014-10-15 04:50:10', 3093, 1),
+	(215, 215, '202', 'savings officer history table', 'SQL', 'V202__savings_officer_history_table.sql', 445151847, 'root', '2014-10-15 04:50:11', 672, 1),
+	(216, 216, '203', 'added subbmittedDate loantransaction', 'SQL', 'V203__added_subbmittedDate_loantransaction.sql', 481117136, 'root', '2014-10-15 04:50:13', 1698, 1),
+	(217, 217, '204', 'insert script for charges paid by for accruals', 'SQL', 'V204__insert_script_for_charges_paid_by_for_accruals.sql', 1126139057, 'root', '2014-10-15 04:50:13', 4, 1),
+	(218, 218, '205', 'fix for charge and interest waiver with accruals', 'SQL', 'V205__fix_for_charge_and_interest_waiver_with_accruals.sql', 620191357, 'root', '2014-11-02 18:54:39', 2022, 1),
+	(219, 219, '206', 'interest posting configuration', 'SQL', 'V206__interest_posting_configuration.sql', -1024393207, 'root', '2014-11-02 18:54:40', 948, 1),
+	(220, 220, '207', 'min max clients per group', 'SQL', 'V207__min_max_clients_per_group.sql', -2069742774, 'root', '2014-12-23 22:35:48', 923, 1),
+	(221, 221, '208', 'min max clients in group redux', 'SQL', 'V208__min_max_clients_in_group_redux.sql', 1253715309, 'root', '2014-12-23 22:35:49', 764, 1),
+	(222, 222, '209', 'add all report names in m permission table', 'SQL', 'V209__add_all_report_names_in_m_permission_table.sql', -801564857, 'root', '2014-12-23 22:35:49', 177, 1),
+	(21, 21, '21', 'activation-permissions-for-clients', 'SQL', 'V21__activation-permissions-for-clients.sql', -569932376, 'root', '2014-03-08 02:28:49', 328, 1),
+	(223, 223, '210', 'track manually adjusted transactions', 'SQL', 'V210__track_manually_adjusted_transactions.sql', 228516334, 'root', '2014-12-23 22:35:51', 1242, 1),
+	(224, 224, '211', 'minimum days between disbursal and first repayment', 'SQL', 'V211__minimum_days_between_disbursal_and_first_repayment.sql', 888628428, 'root', '2014-12-23 22:35:53', 1831, 1),
+	(225, 225, '212', 'add NthDay and DayOfWeek columns loan', 'SQL', 'V212__add_NthDay_and_DayOfWeek_columns_loan.sql', 1949824258, 'root', '2014-12-23 22:35:56', 3064, 1),
+	(226, 226, '213', 'NthDay and DayOfWeek columns should be nullable', 'SQL', 'V213__NthDay_and_DayOfWeek_columns_should_be_nullable.sql', 1399365002, 'root', '2014-12-23 22:36:00', 4338, 1),
+	(227, 227, '214', 'alter table add create SI at disbursement', 'SQL', 'V214__alter_table_add_create_SI_at_disbursement.sql', -154407476, 'root', '2014-12-23 22:36:03', 3021, 1),
+	(228, 228, '215', 'guarantee on hold fund changes', 'SQL', 'V215__guarantee_on_hold_fund_changes.sql', 303524926, 'root', '2014-12-23 22:36:08', 5060, 1),
+	(229, 229, '216', 'adding loan proposed amount to loan', 'SQL', 'V216__adding_loan_proposed_amount_to_loan.sql', 348182179, 'root', '2014-12-23 22:36:13', 4172, 1),
+	(230, 230, '217', 'client substatus and codevalue description', 'SQL', 'V217__client_substatus_and_codevalue_description.sql', 1679310398, 'root', '2014-12-23 22:36:20', 7286, 1),
+	(231, 231, '218', 'add user and datetime for loan savings transactions', 'SQL', 'V218__add_user_and_datetime_for_loan_savings_transactions.sql', -1058697092, 'root', '2014-12-23 22:36:23', 2416, 1),
+	(232, 232, '219', 'guarantor on hold fund changes for account', 'SQL', 'V219__guarantor_on_hold_fund_changes_for_account.sql', 1081934247, 'root', '2014-12-23 22:36:34', 10673, 1),
+	(22, 22, '22', 'alter-group-for-consistency-add-permissions', 'SQL', 'V22__alter-group-for-consistency-add-permissions.sql', 578271556, 'root', '2014-03-08 02:28:50', 841, 1),
+	(233, 233, '220', 'account number preferences', 'SQL', 'V220__account_number_preferences.sql', -502873750, 'root', '2014-12-23 22:36:35', 1295, 1),
+	(234, 234, '221', 'add version for m savings account', 'SQL', 'V221__add_version_for_m_savings_account.sql', 863858591, 'root', '2014-12-23 22:36:38', 2521, 1),
+	(235, 235, '222', 'guarantor on hold fund changes for transactions', 'SQL', 'V222__guarantor_on_hold_fund_changes_for_transactions.sql', -1709074177, 'root', '2014-12-23 22:36:43', 5200, 1),
+	(236, 236, '223', 'add version for m loan account', 'SQL', 'V223__add_version_for_m_loan_account.sql', -973851712, 'root', '2014-12-23 22:36:49', 5675, 1),
+	(237, 237, '224', 'client lifecycle adding statuses', 'SQL', 'V224__client_lifecycle_adding_statuses.sql', -289697454, 'root', '2014-12-23 22:37:01', 11533, 1),
+	(238, 238, '225', 'permissions for updating recurring deposit amount', 'SQL', 'V225__permissions_for_updating_recurring_deposit_amount.sql', -1367144205, 'root', '2014-12-23 22:37:01', 41, 1),
+	(239, 239, '226', 'configuration for enforcing calendars for jlg loans', 'SQL', 'V226__configuration_for_enforcing_calendars_for_jlg_loans.sql', -382855919, 'root', '2014-12-23 22:37:01', 31, 1),
+	(240, 240, '227', 'loan-refund-permissions', 'SQL', 'V227__loan-refund-permissions.sql', 2141152676, 'root', '2014-12-23 22:37:01', 242, 1),
+	(241, 241, '228', 'entity to entity access', 'SQL', 'V228__entity_to_entity_access.sql', -1244421071, 'root', '2014-12-23 22:37:03', 1255, 1),
+	(242, 242, '229', 'teller cash management', 'SQL', 'V229__teller_cash_management.sql', 753724982, 'root', '2014-12-23 22:37:05', 2441, 1),
+	(23, 23, '23', 'remove-enable-disable-configuration-for-client-group-status', 'SQL', 'V23__remove-enable-disable-configuration-for-client-group-status.sql', -832390233, 'root', '2014-03-08 02:28:50', 295, 1),
+	(243, 243, '230', 'role status and correspoding permissions', 'SQL', 'V230__role_status_and_correspoding_permissions.sql', -21174595, 'root', '2015-04-16 14:58:42', 131, 1),
+	(244, 244, '231', 'm cashier transaction added currency code', 'SQL', 'V231__m_cashier_transaction_added_currency_code.sql', -1593672561, 'root', '2015-04-16 14:58:42', 33, 1),
+	(245, 245, '232', 'insert center closure reason', 'SQL', 'V232__insert_center_closure_reason.sql', -2049914418, 'root', '2015-04-16 14:58:42', 2, 1),
+	(246, 246, '233', 'Savings Transaction Receipt', 'SQL', 'V233__Savings_Transaction_Receipt.sql', 1836289382, 'root', '2015-04-16 14:58:42', 16, 1),
+	(247, 247, '234', 'opening balaces setup', 'SQL', 'V234__opening_balaces_setup.sql', 1777198314, 'root', '2015-04-16 14:58:42', 30, 1),
+	(248, 248, '235', 'add ugd template id m hook', 'SQL', 'V235__add_ugd_template_id_m_hook.sql', 1120955673, 'root', '2015-04-16 14:58:42', 73, 1),
+	(249, 249, '236', 'individual collection sheet permissions', 'SQL', 'V236__individual_collection_sheet_permissions.sql', -66130238, 'root', '2015-04-16 14:58:42', 2, 1),
+	(250, 250, '237', 'add threshold config for last instalment', 'SQL', 'V237__add_threshold_config_for_last_instalment.sql', 412873149, 'root', '2015-04-16 14:58:42', 53, 1),
+	(251, 251, '238', 'update staff display name length', 'SQL', 'V238__update_staff_display_name_length.sql', -1003425306, 'root', '2015-04-16 14:58:42', 57, 1),
+	(252, 252, '239', 'Loan Transaction Receipt', 'SQL', 'V239__Loan_Transaction_Receipt.sql', -130819179, 'root', '2015-04-16 14:58:42', 3, 1),
+	(24, 24, '24', 'add-group-client-foreign-key-constraint-in-loan-table', 'SQL', 'V24__add-group-client-foreign-key-constraint-in-loan-table.sql', -621897624, 'root', '2014-03-08 02:28:51', 318, 1),
+	(253, 253, '240', 'arrears aging config for interest recalculation', 'SQL', 'V240__arrears_aging_config_for_interest_recalculation.sql', 674368034, 'root', '2015-04-16 14:58:42', 114, 1),
+	(254, 254, '241', 'fixed emi changes', 'SQL', 'V241__fixed_emi_changes.sql', 1943069939, 'root', '2015-04-16 14:58:42', 47, 1),
+	(255, 255, '242', 'entitytoentitymappingrelation', 'SQL', 'V242__entitytoentitymappingrelation.sql', -1770973716, 'root', '2015-04-16 14:58:42', 59, 1),
+	(256, 256, '243', 'alter loan disbursement details', 'SQL', 'V243__alter_loan_disbursement_details.sql', 1461060824, 'root', '2015-04-16 14:58:42', 31, 1),
+	(257, 257, '244', 'staff assignment history table', 'SQL', 'V244__staff_assignment_history_table.sql', -427095856, 'root', '2015-04-16 14:58:43', 30, 1),
+	(258, 258, '245', 'open rd changes', 'SQL', 'V245__open_rd_changes.sql', 2142566381, 'root', '2015-04-16 14:58:43', 2, 1),
+	(259, 259, '246', 'drop group client foreign key from m loan', 'SQL', 'V246__drop_group_client_foreign_key_from_m_loan.sql', -1721132405, 'root', '2015-04-16 14:58:43', 11, 1),
+	(260, 260, '247', 'consistency wrt spelling principalThresholdForLastInstalment', 'SQL', 'V247__consistency_wrt_spelling_principalThresholdForLastInstalment.sql', 1371980378, 'root', '2015-04-16 14:58:43', 9, 1),
+	(261, 261, '248', 'added password never expired to User', 'SQL', 'V248__added_password_never_expired_to_User.sql', -1800179163, 'root', '2015-04-16 14:58:43', 37, 1),
+	(262, 262, '249', 'workingdays permissions', 'SQL', 'V249__workingdays_permissions.sql', -1322891155, 'root', '2015-04-16 14:58:43', 3, 1),
+	(25, 25, '25', 'update client reports for status and activation change', 'SQL', 'V25__update_client_reports_for_status_and_activation_change.sql', -1426943124, 'root', '2014-03-08 02:28:51', 30, 1),
+	(263, 263, '250', 'password validation policy', 'SQL', 'V250__password_validation_policy.sql', 1197290340, 'root', '2015-04-16 14:58:43', 38, 1),
+	(264, 264, '251', 'paymentType table', 'SQL', 'V251__paymentType_table.sql', -1969329175, 'root', '2015-04-16 14:58:43', 117, 1),
+	(265, 265, '252', 'bug fix teller cash management', 'SQL', 'V252__bug_fix_teller_cash_management.sql', -736743970, 'root', '2015-04-16 14:58:43', 167, 1),
+	(266, 266, '253', 'product loan configurable attributes', 'SQL', 'V253__product_loan_configurable_attributes.sql', 1787268316, 'root', '2015-04-16 14:58:43', 15, 1),
+	(267, 267, '254', 'General Ledger Report', 'SQL', 'V254__General_Ledger_Report.sql', -186920768, 'root', '2015-04-16 14:58:43', 6, 1),
+	(268, 268, '255', 'pre close interest period config', 'SQL', 'V255__pre_close_interest_period_config.sql', 1383225707, 'root', '2015-04-16 14:58:43', 23, 1),
+	(269, 269, '256', 'Update script for General Ledger report', 'SQL', 'V256__Update script for General_Ledger_report.sql', 1918702942, 'root', '2015-04-16 14:58:43', 4, 1),
+	(270, 270, '257', 'staff image association', 'SQL', 'V257__staff_image_association.sql', 1740118046, 'root', '2015-04-16 14:58:43', 37, 1),
+	(271, 271, '258', 'interest compounding changes', 'SQL', 'V258__interest_compounding_changes.sql', 1484848861, 'root', '2015-05-19 04:50:26', 223, 1),
+	(272, 272, '259', 'alter working days', 'SQL', 'V259__alter_working_days.sql', 1733733251, 'root', '2015-05-19 04:50:26', 33, 1),
+	(26, 26, '26', 'add-support-for-withdrawal-fees-on-savings', 'SQL', 'V26__add-support-for-withdrawal-fees-on-savings.sql', -1955461568, 'root', '2014-03-08 02:28:52', 884, 1),
+	(273, 273, '260', 'alter password validation policy', 'SQL', 'V260__alter_password_validation_policy.sql', -853716637, 'root', '2015-05-19 04:50:26', 32, 1),
+	(274, 274, '261', 'Update script for Client Loan Account Schedule Report', 'SQL', 'V261__Update script for Client_Loan_Account_Schedule_Report.sql', 1873100628, 'root', '2015-09-06 17:49:04', 14, 1),
+	(275, 275, '262', 'accountNumber for groups', 'SQL', 'V262__accountNumber_for_groups.sql', -31083607, 'root', '2015-09-06 17:49:04', 123, 1),
+	(276, 276, '263', 'mifos reports', 'SQL', 'V263__mifos_reports.sql', -1358041795, 'root', '2015-09-06 17:49:04', 12, 1),
+	(277, 277, '264', 'insert paymenttype and report read permission', 'SQL', 'V264__insert_paymenttype_and_report_read_permission.sql', 984979503, 'root', '2015-09-06 17:49:04', 6, 1),
+	(278, 278, '265', 'modify external service schema', 'SQL', 'V265__modify_external_service_schema.sql', 1844344576, 'root', '2015-09-06 17:49:05', 290, 1),
+	(279, 279, '266', 'client fees', 'SQL', 'V266__client_fees.sql', 41332385, 'root', '2015-09-06 17:49:05', 111, 1),
+	(280, 280, '267', 'client transaction permissions', 'SQL', 'V267__client_transaction_permissions.sql', 130000057, 'root', '2015-09-06 17:49:05', 6, 1),
+	(281, 281, '268', 'update gmail password', 'SQL', 'V268__update_gmail_password.sql', 1723317114, 'root', '2015-09-06 17:49:05', 5, 1),
+	(282, 282, '269', 'increased calendar title length ', 'SQL', 'V269__increased_calendar_title_length .sql', 1780890645, 'root', '2015-09-06 17:49:05', 167, 1),
+	(27, 27, '27', 'add-loan-type-column-to-loan-table', 'SQL', 'V27__add-loan-type-column-to-loan-table.sql', -746287938, 'root', '2014-03-08 02:28:52', 344, 1),
+	(283, 283, '270', 'add rounding mode configuration', 'SQL', 'V270__add_rounding_mode_configuration.sql', 1195237290, 'root', '2015-09-06 17:49:05', 67, 1),
+	(284, 284, '271', 'accounting for client charges', 'SQL', 'V271__accounting_for_client_charges.sql', 1477443700, 'root', '2015-09-06 17:49:06', 204, 1),
+	(285, 285, '272', 'loan tranche disbursement charge', 'SQL', 'V272__loan_tranche_disbursement_charge.sql', 2018052750, 'root', '2015-09-06 17:49:06', 178, 1),
+	(286, 286, '273', 'oauth changes', 'SQL', 'V273__oauth_changes.sql', 1811521678, 'root', '2015-09-09 13:21:38', 98, 1),
+	(287, 287, '274', 'Loan Reschedule Code Value', 'SQL', 'V274__Loan_Reschedule_Code_Value.sql', -1190544276, 'root', '2015-09-15 18:00:15', 32, 1),
+	(288, 288, '275', 'loan transaction to repayment schedule mapping', 'SQL', 'V275__loan_transaction_to_repayment_schedule_mapping.sql', 1971001203, 'root', '2015-09-21 20:04:43', 471, 1),
+	(289, 289, '276', 'loan recalulated till date', 'SQL', 'V276__loan_recalulated_till_date.sql', 631764351, 'root', '2015-10-20 19:57:56', 1672, 1),
+	(290, 290, '277', 'Loan Product Provisioning', 'SQL', 'V277__Loan_Product_Provisioning.sql', -510229006, 'root', '2015-10-20 19:57:58', 2295, 1),
+	(291, 291, '278', 'LoanTransactionProcessingStrategy', 'SQL', 'V278__LoanTransactionProcessingStrategy.sql', -1388446419, 'root', '2015-11-04 17:03:01', 877, 1),
+	(292, 292, '279', 'floating rates', 'SQL', 'V279__floating_rates.sql', 830029264, 'root', '2015-11-18 16:13:09', 645, 1),
+	(28, 28, '28', 'accounting-abstractions-and-autoposting', 'SQL', 'V28__accounting-abstractions-and-autoposting.sql', -966431980, 'root', '2014-03-08 02:28:53', 556, 1),
+	(293, 293, '280', 'spm framework initial tables', 'SQL', 'V280__spm_framework_initial_tables.sql', -1638980235, 'root', '2015-12-02 16:07:45', 630, 1),
+	(294, 294, '281', 'add configuration param backdate-penalties', 'SQL', 'V281__add_configuration_param_backdate-penalties.sql', -45520299, 'root', '2015-12-02 16:07:45', 45, 1),
+	(295, 295, '282', 'CustomerSelfService', 'SQL', 'V282__CustomerSelfService.sql', -51763400, 'root', '2015-12-17 10:17:34', 255, 1),
+	(296, 296, '283', 'Variable Installments', 'SQL', 'V283__Variable_Installments.sql', -1104936867, 'root', '2016-01-12 17:39:21', 1966, 1),
+	(297, 297, '284', 'update codevalue', 'SQL', 'V284__update_codevalue.sql', 442711672, 'root', '2016-01-12 17:39:22', 457, 1),
+	(298, 298, '285', 'undo last tranche script', 'SQL', 'V285__undo_last_tranche_script.sql', 1551040289, 'root', '2016-01-12 17:39:22', 29, 1),
+	(299, 299, '286', 'partial period interest calcualtion', 'SQL', 'V286__partial_period_interest_calcualtion.sql', -1701869481, 'root', '2016-01-12 17:39:23', 1590, 1),
+	(300, 300, '287', 'alter spm scorecard', 'SQL', 'V287__alter_spm_scorecard.sql', 1834026952, 'root', '2016-01-20 18:23:20', 194, 1),
+	(301, 301, '288', 'overdraft interest', 'SQL', 'V288__overdraft_interest.sql', 2003058104, 'root', '2016-01-20 18:23:20', 307, 1),
+	(302, 302, '289', 'client non person', 'SQL', 'V289__client_non_person.sql', 1595576360, 'root', '2016-01-20 18:23:20', 277, 1),
+	(29, 29, '29', 'add-support-for-annual-fees-on-savings', 'SQL', 'V29__add-support-for-annual-fees-on-savings.sql', 992227725, 'root', '2014-03-08 02:28:55', 1556, 1),
+	(303, 303, '290', 'shares dividends permissions script', 'SQL', 'V290__shares_dividends_permissions_script.sql', -1504459497, 'root', '2016-01-20 18:23:21', 39, 1),
+	(3, 3, '3', 'mifosx-permissions-and-authorisation-utf8', 'SQL', 'V3__mifosx-permissions-and-authorisation-utf8.sql', 1922951887, 'root', '2014-03-08 02:28:38', 110, 1),
+	(30, 30, '30', 'add-referenceNumber-to-acc gl journal entry', 'SQL', 'V30__add-referenceNumber-to-acc_gl_journal_entry.sql', 2079970797, 'root', '2014-03-08 02:28:55', 327, 1),
+	(31, 31, '31', 'drop-autopostings', 'SQL', 'V31__drop-autopostings.sql', 630501407, 'root', '2014-03-08 02:28:55', 39, 1),
+	(32, 32, '32', 'associate-disassociate-clients-from-group-permissions', 'SQL', 'V32__associate-disassociate-clients-from-group-permissions.sql', 765311507, 'root', '2014-03-08 02:28:55', 29, 1),
+	(33, 33, '33', 'drop unique check on stretchy report parameter', 'SQL', 'V33__drop_unique_check_on_stretchy_report_parameter.sql', -716768190, 'root', '2014-03-08 02:28:56', 253, 1),
+	(34, 34, '34', 'add unique check on stretchy report parameter', 'SQL', 'V34__add_unique_check_on_stretchy_report_parameter.sql', -1989718961, 'root', '2014-03-08 02:28:56', 254, 1),
+	(35, 35, '35', 'add hierarchy column for acc gl account', 'SQL', 'V35__add_hierarchy_column_for_acc_gl_account.sql', -1387013309, 'root', '2014-03-08 02:28:57', 300, 1),
+	(36, 36, '36', 'add tag id column for acc gl account', 'SQL', 'V36__add_tag_id_column_for_acc_gl_account.sql', -620418591, 'root', '2014-03-08 02:28:57', 404, 1),
+	(37, 37, '37', 'add-center-group-collection-sheet-permissions', 'SQL', 'V37__add-center-group-collection-sheet-permissions.sql', -1157429270, 'root', '2014-03-08 02:28:57', 32, 1),
+	(38, 38, '38', 'add-group-summary-details-report', 'SQL', 'V38__add-group-summary-details-report.sql', -1018394665, 'root', '2014-03-08 02:28:57', 36, 1),
+	(39, 39, '39', 'payment-channels-updates', 'SQL', 'V39__payment-channels-updates.sql', -1005512239, 'root', '2014-03-08 02:28:58', 1172, 1),
+	(4, 4, '4', 'mifosx-core-reports-utf8', 'SQL', 'V4__mifosx-core-reports-utf8.sql', -934709187, 'root', '2014-03-08 02:28:39', 287, 1),
+	(40, 40, '40', 'add permissions for accounting rule', 'SQL', 'V40__add_permissions_for_accounting_rule.sql', 1514233058, 'root', '2014-03-08 02:28:59', 37, 1),
+	(41, 41, '41', 'group-summary-reports', 'SQL', 'V41__group-summary-reports.sql', 263779795, 'root', '2014-03-08 02:28:59', 229, 1),
+	(42, 42, '42', 'Add default value for id for acc accounting rule', 'SQL', 'V42__Add_default_value_for_id_for_acc_accounting_rule.sql', 1068680120, 'root', '2014-03-08 02:28:59', 242, 1),
+	(43, 43, '43', 'accounting-for-savings', 'SQL', 'V43__accounting-for-savings.sql', 1965510021, 'root', '2014-03-08 02:29:00', 567, 1),
+	(44, 44, '44', 'document-increase-size-of-column-type', 'SQL', 'V44__document-increase-size-of-column-type.sql', 1264142829, 'root', '2014-03-08 02:29:00', 271, 1),
+	(45, 45, '45', 'create acc rule tags table', 'SQL', 'V45__create_acc_rule_tags_table.sql', -307868244, 'root', '2014-03-08 02:29:01', 189, 1),
+	(46, 46, '46', 'extend datatables api', 'SQL', 'V46__extend_datatables_api.sql', 297544230, 'root', '2014-03-08 02:29:01', 38, 1),
+	(47, 47, '47', 'staff-hierarchy-link-to-users', 'SQL', 'V47__staff-hierarchy-link-to-users.sql', 480254198, 'root', '2014-03-08 02:29:02', 868, 1),
+	(48, 48, '48', 'adding-S3-Support', 'SQL', 'V48__adding-S3-Support.sql', -280798781, 'root', '2014-03-08 02:29:03', 1276, 1),
+	(49, 49, '49', 'track-loan-charge-payment-transactions', 'SQL', 'V49__track-loan-charge-payment-transactions.sql', 170618680, 'root', '2014-03-08 02:29:03', 176, 1),
+	(5, 5, '5', 'update-savings-product-and-account-tables', 'SQL', 'V5__update-savings-product-and-account-tables.sql', 1171300485, 'root', '2014-03-08 02:28:39', 636, 1),
+	(50, 50, '50', 'add-grace-settings-to-loan-product', 'SQL', 'V50__add-grace-settings-to-loan-product.sql', 188244658, 'root', '2014-03-08 02:29:05', 926, 1),
+	(51, 51, '51', 'track-additional-details-related-to-installment-performance', 'SQL', 'V51__track-additional-details-related-to-installment-performance.sql', 2012793946, 'root', '2014-03-08 02:29:05', 602, 1),
+	(52, 52, '52', 'add boolean support cols to acc accounting rule', 'SQL', 'V52__add_boolean_support_cols_to_acc_accounting_rule.sql', 961668575, 'root', '2014-03-08 02:29:06', 501, 1),
+	(53, 53, '53', 'track-advance-and-late-payments-on-installment', 'SQL', 'V53__track-advance-and-late-payments-on-installment.sql', -230737076, 'root', '2014-03-08 02:29:06', 212, 1),
+	(54, 54, '54', 'charge-to-income-account-mappings', 'SQL', 'V54__charge-to-income-account-mappings.sql', 2064168495, 'root', '2014-03-08 02:29:07', 303, 1),
+	(55, 55, '55', 'add-additional-transaction-processing-strategies', 'SQL', 'V55__add-additional-transaction-processing-strategies.sql', 1186305896, 'root', '2014-03-08 02:29:07', 352, 1),
+	(56, 56, '56', 'track-overpaid-amount-on-loans', 'SQL', 'V56__track-overpaid-amount-on-loans.sql', 1455634018, 'root', '2014-03-08 02:29:07', 239, 1),
+	(57, 57, '57', 'add default values to debit and credit accounts acc accounting rule', 'SQL', 'V57__add_default_values_to_debit_and_credit_accounts_acc_accounting_rule.sql', 1936034654, 'root', '2014-03-08 02:29:08', 272, 1),
+	(58, 58, '58', 'create-holiday-tables changed', 'SQL', 'V58__create-holiday-tables_changed.sql', 878594707, 'root', '2014-03-08 02:29:08', 374, 1),
+	(59, 59, '59', 'add group roles schema and permissions', 'SQL', 'V59__add_group_roles_schema_and_permissions.sql', 2139634800, 'root', '2014-03-08 02:29:09', 259, 1),
+	(6, 6, '6', 'add min max principal column to loan', 'SQL', 'V6__add_min_max_principal_column_to_loan.sql', 21414779, 'root', '2014-03-08 02:28:40', 648, 1),
+	(60, 60, '60', 'quipo dashboard reports', 'SQL', 'V60__quipo_dashboard_reports.sql', -1414014218, 'root', '2014-03-08 02:29:09', 67, 1),
+	(61, 61, '61', 'txn running balance example', 'SQL', 'V61__txn_running_balance_example.sql', -1186179870, 'root', '2014-03-08 02:29:09', 43, 1),
+	(62, 62, '62', 'add staff id to m client changed', 'SQL', 'V62__add_staff_id_to_m_client_changed.sql', -903717279, 'root', '2014-03-08 02:29:09', 289, 1),
+	(63, 63, '63', 'add sync disbursement with meeting column to loan', 'SQL', 'V63__add_sync_disbursement_with_meeting_column_to_loan.sql', 1706011840, 'root', '2014-03-08 02:29:10', 298, 1),
+	(64, 64, '64', 'add permission for assign staff', 'SQL', 'V64__add_permission_for_assign_staff.sql', -1938102414, 'root', '2014-03-08 02:29:10', 36, 1),
+	(65, 65, '65', 'fix rupee symbol issues', 'SQL', 'V65__fix_rupee_symbol_issues.sql', 581612224, 'root', '2014-03-08 02:29:10', 33, 1),
+	(66, 66, '66', 'client close functionality', 'SQL', 'V66__client_close_functionality.sql', 225242657, 'root', '2014-03-08 02:29:10', 357, 1),
+	(67, 67, '67', 'loans in advance table', 'SQL', 'V67__loans_in_advance_table.sql', -2001051496, 'root', '2014-03-08 02:29:11', 126, 1),
+	(68, 68, '68', 'quipo dashboard reports updated', 'SQL', 'V68__quipo_dashboard_reports_updated.sql', -1241469930, 'root', '2014-03-08 02:29:11', 74, 1),
+	(69, 69, '69', 'loans in advance initialise', 'SQL', 'V69__loans_in_advance_initialise.sql', -1961764720, 'root', '2014-03-08 02:29:11', 44, 1),
+	(7, 7, '7', 'remove read makerchecker permission', 'SQL', 'V7__remove_read_makerchecker_permission.sql', -335430825, 'root', '2014-03-08 02:28:40', 37, 1),
+	(70, 70, '70', 'quipo program detail query fix', 'SQL', 'V70__quipo_program_detail_query_fix.sql', 961289260, 'root', '2014-03-08 02:29:11', 37, 1),
+	(71, 71, '71', 'insert reschedule repayment to configuration', 'SQL', 'V71__insert_reschedule_repayment_to_configuration.sql', -1148306529, 'root', '2014-03-08 02:29:11', 29, 1),
+	(72, 72, '72', 'add m loan counter changes', 'SQL', 'V72__add_m_loan_counter_changes.sql', 201544058, 'root', '2014-03-08 02:29:12', 487, 1),
+	(73, 73, '73', 'add repayments rescheduled to and processed column to holiday', 'SQL', 'V73__add_repayments_rescheduled_to_and_processed_column_to_holiday.sql', -1946338033, 'root', '2014-03-08 02:29:12', 265, 1),
+	(74, 74, '74', 'alter m loan counter table add group', 'SQL', 'V74__alter_m_loan_counter_table_add_group.sql', -889985683, 'root', '2014-03-08 02:29:13', 322, 1),
+	(75, 75, '75', 'add reschedule-repayments-on-holidays to configuration', 'SQL', 'V75__add_reschedule-repayments-on-holidays_to_configuration.sql', 1328301697, 'root', '2014-03-08 02:29:13', 24, 1),
+	(76, 76, '76', 'rename permission grouping', 'SQL', 'V76__rename_permission_grouping.sql', 1717580945, 'root', '2014-03-08 02:29:13', 28, 1),
+	(77, 77, '77', 'alter m product loan changes', 'SQL', 'V77__alter_m_product_loan_changes.sql', 677013677, 'root', '2014-03-08 02:29:13', 283, 1),
+	(78, 78, '78', 'breakdown portfolio grouping', 'SQL', 'V78__breakdown_portfolio_grouping.sql', -1385954232, 'root', '2014-03-08 02:29:13', 28, 1),
+	(79, 79, '79', 'schedule jobs tables', 'SQL', 'V79__schedule_jobs_tables.sql', 339707179, 'root', '2014-03-08 02:29:14', 435, 1),
+	(8, 8, '8', 'deposit-transaction-permissions-if-they-exist', 'SQL', 'V8__deposit-transaction-permissions-if-they-exist.sql', -1507997551, 'root', '2014-03-08 02:28:40', 6, 1),
+	(80, 80, '80', 'schedule jobs tables updates', 'SQL', 'V80__schedule_jobs_tables_updates.sql', -152869205, 'root', '2014-03-08 02:29:15', 1507, 1),
+	(81, 81, '81', 'savings related changes', 'SQL', 'V81__savings_related_changes.sql', 285284658, 'root', '2014-03-08 02:29:17', 1593, 1),
+	(82, 82, '82', 'schedule jobs tables updates for running status', 'SQL', 'V82__schedule_jobs_tables_updates_for_running_status.sql', -1029370098, 'root', '2014-03-08 02:29:18', 621, 1),
+	(83, 83, '83', 'non-working-days-table', 'SQL', 'V83__non-working-days-table.sql', -1092480574, 'root', '2014-03-08 02:29:18', 138, 1),
+	(84, 84, '84', 'undo savings transaction permission', 'SQL', 'V84__undo_savings_transaction_permission.sql', 1857641857, 'root', '2014-03-08 02:29:18', 31, 1),
+	(85, 85, '85', 'product mix related changes', 'SQL', 'V85__product_mix_related_changes.sql', -740767169, 'root', '2014-03-08 02:29:19', 484, 1),
+	(86, 86, '86', 'update-working-days', 'SQL', 'V86__update-working-days.sql', 1266232028, 'root', '2014-03-08 02:29:19', 45, 1),
+	(87, 87, '87', 'add permission for scheduler', 'SQL', 'V87__add_permission_for_scheduler.sql', -575950289, 'root', '2014-03-08 02:29:19', 27, 1),
+	(88, 88, '88', 'added update constrain for scheduler jobs', 'SQL', 'V88__added_update_constrain_for_scheduler_jobs.sql', 1579070736, 'root', '2014-03-08 02:29:20', 380, 1),
+	(89, 89, '89', 'added scheduler group', 'SQL', 'V89__added_scheduler_group.sql', -1538207332, 'root', '2014-03-08 02:29:20', 245, 1),
+	(9, 9, '9', 'add min max constraint column to loan loanproduct', 'SQL', 'V9__add_min_max_constraint_column_to_loan_loanproduct.sql', -2103326932, 'root', '2014-03-08 02:28:42', 1503, 1),
+	(90, 90, '90', 'client performance history reports', 'SQL', 'V90__client_performance_history_reports.sql', 35589718, 'root', '2014-03-08 02:29:20', 51, 1),
+	(91, 91, '91', 'apply annual fees permission', 'SQL', 'V91__apply_annual_fees_permission.sql', 440351308, 'root', '2014-03-08 02:29:20', 34, 1),
+	(92, 92, '91.1', 'configuration settings for holiday and non workingday', 'SQL', 'V91_1__configuration_settings_for_holiday_and_non_workingday.sql', -429561096, 'root', '2014-03-08 02:29:20', 25, 1),
+	(93, 93, '92', 'group center assign staff permission', 'SQL', 'V92__group_center_assign_staff_permission.sql', -1557846330, 'root', '2014-03-08 02:29:20', 21, 1),
+	(94, 94, '93', 'loan transaction external id', 'SQL', 'V93__loan_transaction_external_id.sql', 987684239, 'root', '2014-03-08 02:29:21', 252, 1),
+	(95, 95, '94', 'added savings accont type', 'SQL', 'V94__added_savings_accont type.sql', 623078091, 'root', '2014-03-08 02:29:21', 199, 1),
+	(96, 96, '95', 'batch job postInterest', 'SQL', 'V95__batch_job_postInterest.sql', -1484077135, 'root', '2014-03-08 02:29:21', 25, 1),
+	(97, 97, '96', 'savings accounts transfers table', 'SQL', 'V96__savings_accounts_transfers_table.sql', -1447275289, 'root', '2014-03-08 02:29:22', 370, 1),
+	(98, 98, '97', 'add permission for adjust savings transaction', 'SQL', 'V97__add_permission_for_adjust_savings_transaction.sql', -2045732265, 'root', '2014-03-08 02:29:22', 20, 1),
+	(99, 99, '98', 'added currency roundof for multipleof', 'SQL', 'V98__added_currency_roundof_for_multipleof.sql', -131804848, 'root', '2014-03-08 02:29:23', 1440, 1);
+/*!40000 ALTER TABLE `schema_version` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.sms_messages_outbound
+DROP TABLE IF EXISTS `sms_messages_outbound`;
+CREATE TABLE IF NOT EXISTS `sms_messages_outbound` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '100',
+  `mobile_no` varchar(50) NOT NULL,
+  `message` varchar(1000) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FKGROUP000000001` (`group_id`),
+  KEY `FKCLIENT00000001` (`client_id`),
+  KEY `FKSTAFF000000001` (`staff_id`),
+  CONSTRAINT `FKCLIENT00000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `FKGROUP000000001` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `FKSTAFF000000001` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.sms_messages_outbound: ~0 rows (approximately)
+/*!40000 ALTER TABLE `sms_messages_outbound` DISABLE KEYS */;
+/*!40000 ALTER TABLE `sms_messages_outbound` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.stretchy_parameter
+DROP TABLE IF EXISTS `stretchy_parameter`;
+CREATE TABLE IF NOT EXISTS `stretchy_parameter` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parameter_name` varchar(45) NOT NULL,
+  `parameter_variable` varchar(45) DEFAULT NULL,
+  `parameter_label` varchar(45) NOT NULL,
+  `parameter_displayType` varchar(45) NOT NULL,
+  `parameter_FormatType` varchar(10) NOT NULL,
+  `parameter_default` varchar(45) NOT NULL,
+  `special` varchar(1) DEFAULT NULL,
+  `selectOne` varchar(1) DEFAULT NULL,
+  `selectAll` varchar(1) DEFAULT NULL,
+  `parameter_sql` text,
+  `parent_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_UNIQUE` (`parameter_name`),
+  KEY `fk_stretchy_parameter_001_idx` (`parent_id`),
+  CONSTRAINT `fk_stretchy_parameter_001` FOREIGN KEY (`parent_id`) REFERENCES `stretchy_parameter` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1010 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.stretchy_parameter: ~19 rows (approximately)
+/*!40000 ALTER TABLE `stretchy_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_parameter` (`id`, `parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `special`, `selectOne`, `selectAll`, `parameter_sql`, `parent_id`) VALUES
+	(1, 'startDateSelect', 'startDate', 'startDate', 'date', 'date', 'today', NULL, NULL, NULL, NULL, NULL),
+	(2, 'endDateSelect', 'endDate', 'endDate', 'date', 'date', 'today', NULL, NULL, NULL, NULL, NULL),
+	(3, 'obligDateTypeSelect', 'obligDateType', 'obligDateType', 'select', 'number', '0', NULL, NULL, NULL, 'select * from\r\n(select 1 as id, "Closed" as `name` union all\r\nselect 2, "Disbursal" ) x\r\norder by x.`id`', NULL),
+	(5, 'OfficeIdSelectOne', 'officeId', 'Office', 'select', 'number', '0', NULL, 'Y', NULL, 'select id, \r\nconcat(substring("........................................", 1, \r\n   \n\n((LENGTH(`hierarchy`) - LENGTH(REPLACE(`hierarchy`, \'.\', \'\')) - 1) * 4)), \r\n   `name`) as tc\r\nfrom m_office\r\nwhere hierarchy like concat\n\n(\'${currentUserHierarchy}\', \'%\')\r\norder by hierarchy', NULL),
+	(6, 'loanOfficerIdSelectAll', 'loanOfficerId', 'Loan Officer', 'select', 'number', '0', NULL, NULL, 'Y', '(select lo.id, lo.display_name as `Name` \r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\njoin m_staff lo on lo.office_id = ounder.id\r\nwhere lo.is_loan_officer = true\r\nand o.id = ${officeId})\r\nunion all\r\n(select -10, \'-\')\r\norder by 2', 5),
+	(10, 'currencyIdSelectAll', 'currencyId', 'Currency', 'select', 'number', '0', NULL, NULL, 'Y', 'select `code`, `name`\r\nfrom m_organisation_currency\r\norder by `code`', NULL),
+	(20, 'fundIdSelectAll', 'fundId', 'Fund', 'select', 'number', '0', NULL, NULL, 'Y', '(select id, `name`\r\nfrom m_fund)\r\nunion all\r\n(select -10, \'-\')\r\norder by 2', NULL),
+	(25, 'loanProductIdSelectAll', 'loanProductId', 'Product', 'select', 'number', '0', NULL, NULL, 'Y', 'select p.id, p.`name`\r\nfrom m_product_loan p\r\nwhere (p.currency_code = \'${currencyId}\' or \'-1\'= \'${currencyId}\')\r\norder by 2', 10),
+	(26, 'loanPurposeIdSelectAll', 'loanPurposeId', 'Loan Purpose', 'select', 'number', '0', NULL, NULL, 'Y', 'select -10 as id, \'-\' as code_value\r\nunion all\r\nselect * from (select v.id, v.code_value\r\nfrom m_code c\r\njoin m_code_value v on v.code_id = c.id\r\nwhere c.code_name = "loanPurpose"\r\norder by v.order_position)  x', NULL),
+	(100, 'parTypeSelect', 'parType', 'parType', 'select', 'number', '0', NULL, NULL, NULL, 'select * from\r\n(select 1 as id, "Principal Only" as `name` union all\r\nselect 2, "Principal + Interest" union all\r\nselect 3, "Principal + Interest + Fees" union all\r\nselect 4, "Principal + Interest + Fees + Penalties") x\r\norder by x.`id`', NULL),
+	(1001, 'FullReportList', NULL, 'n/a', 'n/a', 'n/a', 'n/a', 'Y', NULL, NULL, 'select  r.id as report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\nrp.id as parameter_id, rp.report_parameter_name, p.parameter_name\n  from stretchy_report r\n  left join stretchy_report_parameter rp on rp.report_id = r.id \n  left join stretchy_parameter p on p.id = rp.parameter_id\n  where r.use_report is true\n  and exists\n  ( select \'f\'\n  from m_appuser_role ur \n  join m_role r on r.id = ur.role_id\n  join m_role_permission rp on rp.role_id = r.id\n  join m_permission p on p.id = rp.permission_id\n  where ur.appuser_id = ${currentUserId}\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat("READ_", r.report_name)) )\n  order by r.report_category, r.report_name, rp.id', NULL),
+	(1002, 'FullParameterList', NULL, 'n/a', 'n/a', 'n/a', 'n/a', 'Y', NULL, NULL, 'select sp.parameter_name, sp.parameter_variable, sp.parameter_label, sp.parameter_displayType, \r sp.parameter_FormatType, sp.parameter_default, sp.selectOne,  sp.selectAll, spp.parameter_name as parentParameterName\r from stretchy_parameter sp\r left join stretchy_parameter spp on spp.id = sp.parent_id\r where sp.special is null\r and exists \r   (select \'f\' \r  from stretchy_report sr\r   join stretchy_report_parameter srp on srp.report_id = sr.id\r   where sr.report_name in(${reportListing})\r   and srp.parameter_id = sp.id\r  )\r order by sp.id', NULL),
+	(1003, 'reportCategoryList', NULL, 'n/a', 'n/a', 'n/a', 'n/a', 'Y', NULL, NULL, 'select  r.id as report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,\n  rp.id as parameter_id, rp.report_parameter_name, p.parameter_name\n  from stretchy_report r\n  left join stretchy_report_parameter rp on rp.report_id = r.id\n  left join stretchy_parameter p on p.id = rp.parameter_id\n  where r.report_category = \'${reportCategory}\'\n  and r.use_report is true\n  and exists\n  (select \'f\'\n  from m_appuser_role ur \n  join m_role r on r.id = ur.role_id\n  join m_role_permission rp on rp.role_id = r.id\n  join m_permission p on p.id = rp.permission_id\n  where ur.appuser_id = ${currentUserId}\n  and (p.code in (\'ALL_FUNCTIONS_READ\', \'ALL_FUNCTIONS\') or p.code = concat("READ_", r.report_name)) )\n  order by r.report_category, r.report_name, rp.id', NULL),
+	(1004, 'selectAccount', 'accountNo', 'Enter Account No', 'text', 'string', 'n/a', NULL, NULL, NULL, NULL, NULL),
+	(1005, 'savingsProductIdSelectAll', 'savingsProductId', 'Product', 'select', 'number', '0', NULL, NULL, 'Y', 'select p.id, p.`name`\r\nfrom m_savings_product p\r\norder by 2', NULL),
+	(1006, 'transactionId', 'transactionId', 'transactionId', 'text', 'string', 'n/a', NULL, NULL, NULL, NULL, NULL),
+	(1007, 'selectCenterId', 'centerId', 'Enter Center Id', 'text', 'string', 'n/a', NULL, NULL, NULL, NULL, NULL),
+	(1008, 'SelectGLAccountNO', 'GLAccountNO', 'GLAccountNO', 'select', 'number', '0', NULL, NULL, NULL, 'select id aid,name aname\r\nfrom acc_gl_account', NULL),
+	(1009, 'asOnDate', 'asOn', 'As On', 'date', 'date', 'today', NULL, NULL, NULL, NULL, NULL);
+/*!40000 ALTER TABLE `stretchy_parameter` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.stretchy_report
+DROP TABLE IF EXISTS `stretchy_report`;
+CREATE TABLE IF NOT EXISTS `stretchy_report` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_name` varchar(100) NOT NULL,
+  `report_type` varchar(20) NOT NULL,
+  `report_subtype` varchar(20) DEFAULT NULL,
+  `report_category` varchar(45) DEFAULT NULL,
+  `report_sql` text,
+  `description` text,
+  `core_report` tinyint(1) DEFAULT '0',
+  `use_report` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `report_name_UNIQUE` (`report_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=165 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.stretchy_report: ~125 rows (approximately)
+/*!40000 ALTER TABLE `stretchy_report` DISABLE KEYS */;
+INSERT INTO `stretchy_report` (`id`, `report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES
+	(1, 'Client Listing', 'Table', NULL, 'Client', 'select \nconcat(repeat("..",   \n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\n c.account_no as "Client Account No.",  \nc.display_name as "Name",  \nr.enum_message_property as "Status",\nc.activation_date as "Activation", c.external_id as "External Id"\nfrom m_office o \njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_client c on c.office_id = ounder.id\nleft join r_enum_value r on r.enum_name = \'status_enum\' and r.enum_id = c.status_enum\nwhere o.id = ${officeId}\norder by ounder.hierarchy, c.account_no', 'Individual Client Report\r\n\r\nLists the small number of defined fields on the client table.  Would expect to copy this \n\nreport and add any \'one to one\' additional data for specific tenant needs.\r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for \n\nlarger ones.  Depending on how many columns are displayed, there is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t \n\nhave that client browser/memory impact).', 1, 1),
+	(2, 'Client Loans Listing', 'Table', NULL, 'Client', 'select \nconcat(repeat("..",   \n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch", c.account_no as "Client Account No.", \nc.display_name as "Name",\nr.enum_message_property as "Client Status",\nlo.display_name as "Loan Officer", l.account_no as "Loan Account No.", l.external_id as "External Id", p.name as Loan, st.enum_message_property as "Status",  \nf.`name` as Fund, purp.code_value as "Loan Purpose",\nifnull(cur.display_symbol, l.currency_code) as Currency,  \nl.principal_amount, l.arrearstolerance_amount as "Arrears Tolerance Amount",\nl.number_of_repayments as "Expected No. Repayments", \nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate", \nl.nominal_interest_rate_per_period as "Nominal Interest Rate Per Period",\nipf.enum_message_property as "Interest Rate Frequency",\nim.enum_message_property as "Interest Method",\nicp.enum_message_property as "Interest Calculated in Period",\nl.term_frequency as "Term Frequency",\ntf.enum_message_property as "Term Frequency Period",\nl.repay_every as "Repayment Frequency",\nrf.enum_message_property as "Repayment Frequency Period",\nam.enum_message_property as "Amortization",\nl.total_charges_due_at_disbursement_derived as "Total Charges Due At Disbursement",\ndate(l.submittedon_date) as Submitted, date(l.approvedon_date) Approved, l.expected_disbursedon_date As "Expected Disbursal",\ndate(l.expected_firstrepaymenton_date) as "Expected First Repayment", \ndate(l.interest_calculated_from_date) as "Interest Calculated From" ,\ndate(l.disbursedon_date) as Disbursed, \ndate(l.expected_maturedon_date) "Expected Maturity",\ndate(l.maturedon_date) as "Matured On", date(l.closedon_date) as Closed,\ndate(l.rejectedon_date) as Rejected, date(l.rescheduledon_date) as Rescheduled, \ndate(l.withdrawnon_date) as Withdrawn, date(l.writtenoffon_date) "Written Off"\nfrom m_office o \njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_client c on c.office_id = ounder.id\nleft join r_enum_value r on r.enum_name = \'status_enum\' \n and r.enum_id = c.status_enum\nleft join m_loan l on l.client_id = c.id\nleft join m_staff lo on lo.id = l.loan_officer_id\nleft join m_product_loan p on p.id = l.product_id\nleft join m_fund f on f.id = l.fund_id\nleft join r_enum_value st on st.enum_name = "loan_status_id" and st.enum_id = l.loan_status_id\nleft join r_enum_value ipf on ipf.enum_name = "interest_period_frequency_enum" \n and ipf.enum_id = l.interest_period_frequency_enum\nleft join r_enum_value im on im.enum_name = "interest_method_enum" \n and im.enum_id = l.interest_method_enum\nleft join r_enum_value tf on tf.enum_name = "term_period_frequency_enum" \n and tf.enum_id = l.term_period_frequency_enum\nleft join r_enum_value icp on icp.enum_name = "interest_calculated_in_period_enum" \n and icp.enum_id = l.interest_calculated_in_period_enum\nleft join r_enum_value rf on rf.enum_name = "repayment_period_frequency_enum" \n and rf.enum_id = l.repayment_period_frequency_enum\nleft join r_enum_value am on am.enum_name = "amortization_method_enum" \n and am.enum_id = l.amortization_method_enum\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\nleft join m_currency cur on cur.code = l.currency_code\nwhere o.id = ${officeId}\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\norder by ounder.hierarchy, 2 , l.id', 'Individual Client Report\r\n\r\nPretty \n\nwide report that lists the basic details of client loans.  \r\n\r\nCan be run for any size MFI but you\'d expect it only to be run within a branch for larger ones.  \n\nThere is probably is a limit of about 20/50k clients returned for html display (export to excel doesn\'t have that client browser/memory impact).', 1, 1),
+	(5, 'Loans Awaiting Disbursal', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nc.account_no as "Client Account No", c.display_name as "Name", l.account_no as "Loan Account No.", pl.`name` as "Product", \r\nf.`name` as Fund, ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nl.principal_amount as Principal,  \r\nl.term_frequency as "Term Frequency",\n\n\r\ntf.enum_message_property as "Term Frequency Period",\r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate",\r\ndate(l.approvedon_date) "Approved",\r\ndatediff(l.expected_disbursedon_date, curdate()) as "Days to Disbursal",\r\ndate(l.expected_disbursedon_date) "Expected Disbursal",\r\npurp.code_value as "Loan Purpose",\r\n lo.display_name as "Loan Officer"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = "term_period_frequency_enum" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\norder by ounder.hierarchy, datediff(l.expected_disbursedon_date, curdate()),  c.account_no', 'Individual Client Report', 1, 1),
+	(6, 'Loans Awaiting Disbursal Summary', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\npl.`name` as "Product", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  f.`name` as Fund,\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`\r\norder by ounder.hierarchy, pl.`name`, l.currency_code,  f.`name`', 'Individual Client Report', 1, 1),
+	(7, 'Loans Awaiting Disbursal Summary by Month', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\npl.`name` as "Product", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nyear(l.expected_disbursedon_date) as "Year", \r\nmonthname(l.expected_disbursedon_date) as "Month",\r\nsum(l.principal_amount) as Principal\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 200\r\ngroup by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)\r\norder by ounder.hierarchy, pl.`name`, l.currency_code, year(l.expected_disbursedon_date), month(l.expected_disbursedon_date)', 'Individual Client Report', 1, 1),
+	(8, 'Loans Pending Approval', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nc.account_no as "Client Account No.", c.display_name as "Client Name", \r\nifnull(cur.display_symbol, l.currency_code) as Currency,  pl.`name` as "Product", \r\nl.account_no as "Loan Account No.", \r\nl.principal_amount as "Loan Amount", \r\nl.term_frequency as "Term Frequency",\n\n\r\ntf.enum_message_property as "Term Frequency Period",\r\nl.annual_nominal_interest_rate as " Annual \n\nNominal Interest Rate", \r\ndatediff(curdate(), l.submittedon_date) "Days Pending Approval", \r\npurp.code_value as "Loan Purpose",\r\nlo.display_name as "Loan Officer"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join r_enum_value tf on tf.enum_name = "term_period_frequency_enum" and tf.enum_id = l.term_period_frequency_enum\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 100 /*Submitted and awaiting approval */\r\norder by ounder.hierarchy, l.submittedon_date,  l.account_no', 'Individual Client Report', 1, 1),
+	(11, 'Active Loans - Summary', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as "Office/Branch", x.currency as Currency,\r\n x.client_count as "No. of Clients", x.active_loan_count as "No. Active Loans", x. loans_in_arrears_count as "No. of Loans in Arrears",\r\nx.principal as "Total Loans Disbursed", x.principal_repaid as "Principal Repaid", x.principal_outstanding as "Principal Outstanding", x.principal_overdue as "Principal Overdue",\r\nx.interest as "Total Interest", x.interest_repaid as "Interest Repaid", x.interest_outstanding as "Interest Outstanding", x.interest_overdue as "Interest Overdue",\r\nx.fees as "Total Fees", x.fees_repaid as "Fees Repaid", x.fees_outstanding as "Fees Outstanding", x.fees_overdue as "Fees Overdue",\r\nx.penalties as "Total Penalties", x.penalties_repaid as "Penalties Repaid", x.penalties_outstanding as "Penalties Outstanding", x.penalties_overdue as "Penalties Overdue",\r\n\r\n  (case\r\n when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(if(laa.loan_id is not null,  l.id, null)  )) as loans_in_arrears_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(laa.principal_overdue_derived) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(laa.interest_overdue_derived) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(laa.fee_charges_overdue_derived) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(laa.penalty_charges_overdue_derived) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency', NULL, 1, 1),
+	(12, 'Active Loans - Details', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as "Loan Officer", \r\nc.display_name as "Client", l.account_no as "Loan Account No.", pl.`name` as "Product", \r\nf.`name` as Fund,  \r\nl.principal_amount as "Loan Amount", \r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed Date", \r\ndate(l.expected_maturedon_date) as "Expected Matured On",\r\n\r\nl.principal_repaid_derived as "Principal Repaid",\r\nl.principal_outstanding_derived as "Principal Outstanding",\r\nlaa.principal_overdue_derived as "Principal Overdue",\r\n\r\nl.interest_repaid_derived as "Interest Repaid",\r\nl.interest_outstanding_derived as "Interest Outstanding",\r\nlaa.interest_overdue_derived as "Interest Overdue",\r\n\r\nl.fee_charges_repaid_derived as "Fees Repaid",\r\nl.fee_charges_outstanding_derived  as "Fees Outstanding",\r\nlaa.fee_charges_overdue_derived as "Fees Overdue",\r\n\r\nl.penalty_charges_repaid_derived as "Penalties Repaid",\r\nl.penalty_charges_outstanding_derived as "Penalties Outstanding",\r\npenalty_charges_overdue_derived as "Penalties Overdue"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'Individual Client \n\nReport', 1, 1),
+	(13, 'Obligation Met Loans Details', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as "Client Account No.", c.display_name as "Client",\r\nl.account_no as "Loan Account No.", pl.`name` as "Product", \r\nf.`name` as Fund,  \r\nl.principal_amount as "Loan Amount", \r\nl.total_repayment_derived  as "Total Repaid", \r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed", \r\ndate(l.closedon_date) as "Closed",\r\n\r\nl.principal_repaid_derived as "Principal Repaid",\r\nl.interest_repaid_derived as "Interest Repaid",\r\nl.fee_charges_repaid_derived as "Fees Repaid",\r\nl.penalty_charges_repaid_derived as "Penalties Repaid",\r\nlo.display_name as "Loan Officer"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n  when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n  else 1 = 1\r\n  end)\r\nand l.loan_status_id = 600\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'Individual Client \n\nReport', 1, 1),
+	(14, 'Obligation Met Loans Summary', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\ncount(distinct(c.id)) as "No. of Clients",\r\ncount(distinct(l.id)) as "No. of Loans",\r\nsum(l.principal_amount) as "Total Loan Amount", \r\nsum(l.principal_repaid_derived) as "Total Principal Repaid",\r\nsum(l.interest_repaid_derived) as "Total Interest Repaid",\r\nsum(l.fee_charges_repaid_derived) as "Total Fees Repaid",\r\nsum(l.penalty_charges_repaid_derived) as "Total Penalties Repaid",\r\nsum(l.interest_waived_derived) as "Total Interest Waived",\r\nsum(l.fee_charges_waived_derived) as "Total Fees Waived",\r\nsum(l.penalty_charges_waived_derived) as "Total Penalties Waived"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand (case\r\n  when ${obligDateType} = 1 then\r\n    l.closedon_date between \'${startDate}\' and \'${endDate}\'\r\n when ${obligDateType} = 2 then\r\n    l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\n  else 1 = 1\r\n  end)\r\nand l.loan_status_id = 600\r\ngroup by ounder.hierarchy, l.currency_code\r\norder by ounder.hierarchy, l.currency_code', 'Individual Client \n\nReport', 1, 1),
+	(15, 'Portfolio at Risk', 'Table', NULL, 'Loan', 'select x.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n  (case\r\n when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from \r\n(select  ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\nsum(l.principal_outstanding_derived) as "Principal Outstanding",\r\nsum(laa.principal_overdue_derived) as "Principal Overdue",\r\n\r\nsum(l.interest_outstanding_derived) as "Interest Outstanding",\r\nsum(laa.interest_overdue_derived) as "Interest Overdue",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as "Fees Outstanding",\r\nsum(laa.fee_charges_overdue_derived) as "Fees Overdue",\r\n\r\nsum(penalty_charges_outstanding_derived) as "Penalties Outstanding",\r\nsum(laa.penalty_charges_overdue_derived) as "Penalties Overdue"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by l.currency_code\r\norder by l.currency_code) x', 'Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)', 1, 1),
+	(16, 'Portfolio at Risk by Branch', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as "Office/Branch",\r\nx.Currency, x.`Principal Outstanding`, x.`Principal Overdue`, x.`Interest Outstanding`, x.`Interest Overdue`, \r\nx.`Fees Outstanding`, x.`Fees Overdue`, x.`Penalties Outstanding`, x.`Penalties Overdue`,\r\n\r\n (case\r\n when ${parType} = 1 then\r\n    cast(round((x.`Principal Overdue` * 100) / x.`Principal Outstanding`, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding`), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding`), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.`Principal Overdue` + x.`Interest Overdue` + x.`Fees Overdue` + x.`Penalties Overdue`) * 100) / (x.`Principal Outstanding` + x.`Interest Outstanding` + x.`Fees Outstanding` + x.`Penalties Overdue`), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from m_office mo\r\njoin \r\n(select  ounder.id as "branch", ifnull(cur.display_symbol, l.currency_code) as Currency,  \r\n\r\nsum(l.principal_outstanding_derived) as "Principal Outstanding",\r\nsum(laa.principal_overdue_derived) as "Principal Overdue",\r\n\r\nsum(l.interest_outstanding_derived) as "Interest Outstanding",\r\nsum(laa.interest_overdue_derived) as "Interest Overdue",\r\n\r\nsum(l.fee_charges_outstanding_derived)  as "Fees Outstanding",\r\nsum(laa.fee_charges_overdue_derived) as "Fees Overdue",\r\n\r\nsum(penalty_charges_outstanding_derived) as "Penalties Outstanding",\r\nsum(laa.penalty_charges_overdue_derived) as "Penalties Overdue"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin  m_loan l on l.client_id = c.id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_code_value purp on purp.id = l.loanpurpose_cv_id\r\nleft join m_product_loan p on p.id = l.product_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency', 'Covers all loans.\r\n\r\nFor larger MFIs … we should add some derived fields on loan (or a 1:1 loan related table like mifos 2.x does)\r\nPrinciple, Interest, Fees, Penalties Outstanding and Overdue (possibly waived and written off too)', 1, 1),
+	(20, 'Funds Disbursed Between Dates Summary', 'Table', NULL, 'Fund', 'select ifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, \r\nround(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office ounder \r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\ngroup by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)\r\norder by ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)', NULL, 1, 1),
+	(21, 'Funds Disbursed Between Dates Summary by Office', 'Table', NULL, 'Fund', 'select \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\n \n\nifnull(f.`name`, \'-\') as Fund,  ifnull(cur.display_symbol, l.currency_code) as Currency, round(sum(l.principal_amount), 4) as disbursed_amount\r\nfrom m_office o\r\n\n\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c \n\non c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_currency cur on cur.`code` = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\n\n\nwhere disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand o.id = ${officeId}\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand \n\n(l.currency_code = \'${currencyId}\' or \'-1\' = \'${currencyId}\')\r\ngroup by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, \n\nl.currency_code)\r\norder by ounder.`name`,  ifnull(f.`name`, \'-\') , ifnull(cur.display_symbol, l.currency_code)', NULL, 1, 1),
+	(48, 'Balance Sheet', 'Pentaho', NULL, 'Accounting', NULL, 'Balance Sheet', 1, 1),
+	(49, 'Income Statement', 'Pentaho', NULL, 'Accounting', NULL, 'Profit and Loss Statement', 1, 1),
+	(50, 'Trial Balance', 'Pentaho', NULL, 'Accounting', NULL, 'Trial Balance Report', 1, 1),
+	(51, 'Written-Off Loans', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as "Client Account No.",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as "Loan Amount",\r\nifnull(lt.principal_portion_derived, 0) AS \'Written-Off Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Written-Off Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Written-Off Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Written-Off Penalties\',\r\nn.note AS \'Reason For Write-Off\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 6 /*write-off */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=601\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = "${currencyId}" or "-1" = "${currencyId}") \r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no', 'Individual Lending Report. Written Off Loans', 1, 1),
+	(52, 'Aging Detail', 'Table', NULL, 'Loan', '\r\nSELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nmc.account_no as "Client Account No.",\r\n  mc.display_name AS "Client Name",\r\n   ml.account_no AS "Account Number",\r\n  ml.principal_amount AS "Loan Amount",\r\n ml.principal_disbursed_derived AS "Original Principal",\r\n ml.interest_charged_derived AS "Original Interest",\r\n ml.principal_repaid_derived AS "Principal Paid",\r\n ml.interest_repaid_derived AS "Interest Paid",\r\n laa.principal_overdue_derived AS "Principal Overdue",\r\n laa.interest_overdue_derived AS "Interest Overdue",\r\nDATEDIFF(CURDATE(), laa.overdue_since_date_derived) as "Days in Arrears",\r\n\r\n  IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<7, \'<1\', \r\n  IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<8, \' 1\', \r\n  IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<15,  \'2\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<22, \' 3\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<29, \' 4\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<36, \' 5\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<43, \' 6\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<50, \' 7\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<57, \' 8\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<64, \' 9\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<71, \'10\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<78, \'11\', \r\n   IF(DATEDIFF(CURDATE(), laa.overdue_since_date_derived)<85, \'12\', \'12+\')))))))))))) )AS "Weeks In Arrears Band",\r\n\r\n   IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<31, \'0 - 30\', \r\n    IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<61, \'30 - 60\', \r\n   IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<91, \'60 - 90\', \r\n   IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<181, \'90 - 180\', \r\n   IF(DATEDIFF(CURDATE(),  laa.overdue_since_date_derived)<361, \'180 - 360\', \r\n         \'> 360\'))))) AS "Days in Arrears Band"\r\n\r\n FROM m_office mo \r\n    JOIN m_office ounder ON ounder.hierarchy like concat(mo.hierarchy, \'%\')\r\n          AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n      INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n      INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id AND rev.enum_name = \'loan_status_id\'\r\n    INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\n    left join m_currency cur on cur.code = ml.currency_code\r\n  WHERE ml.loan_status_id=300\r\n    AND mo.id=${officeId}\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no\r\n', 'Loan arrears aging (Weeks)', 1, 1),
+	(53, 'Aging Summary (Arrears in Weeks)', 'Table', NULL, 'Loan', 'SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Weeks In Arrears (Up To)\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n /* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n  (SELECT \'On Schedule\' period_no,1 pid UNION\r\n   SELECT \'1\',2 UNION\r\n    SELECT \'2\',3 UNION\r\n    SELECT \'3\',4 UNION\r\n    SELECT \'4\',5 UNION\r\n    SELECT \'5\',6 UNION\r\n    SELECT \'6\',7 UNION\r\n    SELECT \'7\',8 UNION\r\n    SELECT \'8\',9 UNION\r\n    SELECT \'9\',10 UNION\r\n   SELECT \'10\',11 UNION\r\n    SELECT \'11\',12 UNION\r\n    SELECT \'12\',13 UNION\r\n    SELECT \'12+\',14) pers,\r\n  (SELECT distinctrow moc.code, moc.name\r\n    FROM m_office mo2\r\n     INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n       LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n     INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n  WHERE ml2.loan_status_id=300 /* active */\r\n AND mo2.id=${officeId}\r\nAND (ml2.currency_code = "${currencyId}" or "-1" = "${currencyId}")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n    z.currency, z.arrPeriod, \r\n COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n  SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n  /*derived table just used to get arrPeriod value (was much slower to\r\n  duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n  (SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<8, \'1\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<15, \'2\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<22, \'3\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<29, \'4\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<36, \'5\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<43, \'6\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<50, \'7\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<57, \'8\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<64, \'9\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<71, \'10\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<78, \'11\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<85, \'12\',\r\n         \'12+\'))))))))))))) AS arrPeriod\r\n\r\n  FROM /* get the individual loan details */\r\n    (SELECT ml.id AS loanId, ml.currency_code as currency,\r\n        ml.principal_disbursed_derived as principal, \r\n        ml.interest_charged_derived as interest, \r\n        ml.principal_repaid_derived as prinPaid, \r\n        ml.interest_repaid_derived intPaid,\r\n\r\n         laa.principal_overdue_derived as prinOverdue,\r\n         laa.interest_overdue_derived as intOverdue,\r\n\r\n         IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n        \r\n      FROM m_office mo\r\n      INNER JOIN m_office ounder ON ounder.hierarchy \r\n       LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n      INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n      INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n       LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n    WHERE ml.loan_status_id=300 /* active */\r\n        AND mo.id=${officeId}\r\n     AND (ml.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\n      GROUP BY ml.id) x\r\n ) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid', 'Loan amount in arrears by branch', 1, 1),
+	(54, 'Rescheduled Loans', 'Table', NULL, 'Loan', 'SELECT \r\nconcat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, ml.currency_code) as Currency,  \r\nc.account_no as "Client Account No.",\r\nc.display_name AS \'Client Name\',\r\nml.account_no AS \'Loan Account No.\',\r\nmpl.name AS \'Product Name\',\r\nml.disbursedon_date AS \'Disbursed Date\',\r\nlt.transaction_date AS \'Written Off date\',\r\nml.principal_amount as "Loan Amount",\r\nifnull(lt.principal_portion_derived, 0) AS \'Rescheduled Principal\',\r\nifnull(lt.interest_portion_derived, 0) AS \'Rescheduled Interest\',\r\nifnull(lt.fee_charges_portion_derived,0) AS \'Rescheduled Fees\',\r\nifnull(lt.penalty_charges_portion_derived,0) AS \'Rescheduled Penalties\',\r\nn.note AS \'Reason For Rescheduling\',\r\nIFNULL(ms.display_name,\'-\') AS \'Loan Officer Name\'\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan ml ON ml.client_id = c.id\r\nJOIN m_product_loan mpl ON mpl.id=ml.product_id\r\nLEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\nJOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nLEFT JOIN m_note n ON n.loan_transaction_id = lt.id\r\nLEFT JOIN m_currency cur on cur.code = ml.currency_code\r\nWHERE lt.transaction_type_enum = 7 /*marked for rescheduling */\r\nAND lt.is_reversed is false \r\nAND ml.loan_status_id=602\r\nAND o.id=${officeId}\r\nAND (mpl.id=${loanProductId} OR ${loanProductId}=-1)\r\nAND lt.transaction_date BETWEEN \'${startDate}\' AND \'${endDate}\'\r\nAND (ml.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nORDER BY ounder.hierarchy, ifnull(cur.display_symbol, ml.currency_code), ml.account_no', 'Individual Lending Report. Rescheduled Loans.  The ability to reschedule (or mark that you have rescheduled the loan elsewhere) is a legacy of the older Mifos product.  Needed for migration.', 1, 1),
+	(55, 'Active Loans Passed Final Maturity', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as "Loan Officer", \r\nc.display_name as "Client", l.account_no as "Loan Account No.", pl.`name` as "Product", \r\nf.`name` as Fund,  \r\nl.principal_amount as "Loan Amount", \r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed Date", \r\ndate(l.expected_maturedon_date) as "Expected Matured On",\r\n\r\nl.principal_repaid_derived as "Principal Repaid",\r\nl.principal_outstanding_derived as "Principal Outstanding",\r\nlaa.principal_overdue_derived as "Principal Overdue",\r\n\r\nl.interest_repaid_derived as "Interest Repaid",\r\nl.interest_outstanding_derived as "Interest Outstanding",\r\nlaa.interest_overdue_derived as "Interest Overdue",\r\n\r\nl.fee_charges_repaid_derived as "Fees Repaid",\r\nl.fee_charges_outstanding_derived  as "Fees Outstanding",\r\nlaa.fee_charges_overdue_derived as "Fees Overdue",\r\n\r\nl.penalty_charges_repaid_derived as "Penalties Repaid",\r\nl.penalty_charges_outstanding_derived as "Penalties Outstanding",\r\nlaa.penalty_charges_overdue_derived as "Penalties Overdue"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'Individual Client \n\nReport', 1, 1),
+	(56, 'Active Loans Passed Final Maturity Summary', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as "Office/Branch", x.currency as Currency,\r\n x.client_count as "No. of Clients", x.active_loan_count as "No. Active Loans", x. arrears_loan_count as "No. of Loans in Arrears",\r\nx.principal as "Total Loans Disbursed", x.principal_repaid as "Principal Repaid", x.principal_outstanding as "Principal Outstanding", x.principal_overdue as "Principal Overdue",\r\nx.interest as "Total Interest", x.interest_repaid as "Interest Repaid", x.interest_outstanding as "Interest Outstanding", x.interest_overdue as "Interest Overdue",\r\nx.fees as "Total Fees", x.fees_repaid as "Fees Repaid", x.fees_outstanding as "Fees Outstanding", x.fees_overdue as "Fees Overdue",\r\nx.penalties as "Total Penalties", x.penalties_repaid as "Penalties Repaid", x.penalties_outstanding as "Penalties Outstanding", x.penalties_overdue as "Penalties Overdue",\r\n\r\n  (case\r\n when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from m_office mo\r\njoin \r\n(select ounder.id as branch,\r\nifnull(cur.display_symbol, l.currency_code) as currency,\r\ncount(distinct(c.id)) as client_count, \r\ncount(distinct(l.id)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\n\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand l.expected_maturedon_date < curdate()\r\ngroup by ounder.id, l.currency_code) x on x.branch = mo.id\r\norder by mo.hierarchy, x.Currency', NULL, 1, 1),
+	(57, 'Active Loans in last installment', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(lastInstallment.`hierarchy`) - LENGTH(REPLACE(lastInstallment.`hierarchy`, \'.\', \'\')) - 1))), lastInstallment.branch) as "Office/Branch",\r\nlastInstallment.Currency,\r\nlastInstallment.`Loan Officer`, \r\nlastInstallment.`Client Account No`, lastInstallment.`Client`, \r\nlastInstallment.`Loan Account No`, lastInstallment.`Product`, \r\nlastInstallment.`Fund`,  lastInstallment.`Loan Amount`, \r\nlastInstallment.`Annual Nominal Interest Rate`, \r\nlastInstallment.`Disbursed`, lastInstallment.`Expected Matured On` ,\r\n\r\nl.principal_repaid_derived as "Principal Repaid",\r\nl.principal_outstanding_derived as "Principal Outstanding",\r\nlaa.principal_overdue_derived as "Principal Overdue",\r\n\r\nl.interest_repaid_derived as "Interest Repaid",\r\nl.interest_outstanding_derived as "Interest Outstanding",\r\nlaa.interest_overdue_derived as "Interest Overdue",\r\n\r\nl.fee_charges_repaid_derived as "Fees Repaid",\r\nl.fee_charges_outstanding_derived  as "Fees Outstanding",\r\nlaa.fee_charges_overdue_derived as "Fees Overdue",\r\n\r\nl.penalty_charges_repaid_derived as "Penalties Repaid",\r\nl.penalty_charges_outstanding_derived as "Penalties Outstanding",\r\nlaa.penalty_charges_overdue_derived as "Penalties Overdue"\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as "Loan Officer", c.account_no as "Client Account No",\r\nc.display_name as "Client", l.account_no as "Loan Account No", pl.`name` as "Product", \r\nf.`name` as Fund,  l.principal_amount as "Loan Amount", \r\nl.annual_nominal_interest_rate as "Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed", date(l.expected_maturedon_date) as "Expected Matured On"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\norder by lastInstallment.hierarchy, lastInstallment.Currency, lastInstallment.`Client Account No`, lastInstallment.`Loan Account No`', 'Individual Client \n\nReport', 1, 1),
+	(58, 'Active Loans in last installment Summary', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(mo.`hierarchy`) - LENGTH(REPLACE(mo.`hierarchy`, \'.\', \'\')) - 1))), mo.`name`) as "Office/Branch", x.currency as Currency,\r\n x.client_count as "No. of Clients", x.active_loan_count as "No. Active Loans", x. arrears_loan_count as "No. of Loans in Arrears",\r\nx.principal as "Total Loans Disbursed", x.principal_repaid as "Principal Repaid", x.principal_outstanding as "Principal Outstanding", x.principal_overdue as "Principal Overdue",\r\nx.interest as "Total Interest", x.interest_repaid as "Interest Repaid", x.interest_outstanding as "Interest Outstanding", x.interest_overdue as "Interest Overdue",\r\nx.fees as "Total Fees", x.fees_repaid as "Fees Repaid", x.fees_outstanding as "Fees Outstanding", x.fees_overdue as "Fees Overdue",\r\nx.penalties as "Total Penalties", x.penalties_repaid as "Penalties Repaid", x.penalties_outstanding as "Penalties Outstanding", x.penalties_overdue as "Penalties Overdue",\r\n\r\n  (case\r\n when ${parType} = 1 then\r\n    cast(round((x.principal_overdue * 100) / x.principal_outstanding, 2) as char)\r\n when ${parType} = 2 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding), 2) as char)\r\n when ${parType} = 3 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding), 2) as char)\r\n when ${parType} = 4 then\r\n    cast(round(((x.principal_overdue + x.interest_overdue + x.fees_overdue + x.penalties_overdue) * 100) / (x.principal_outstanding + x.interest_outstanding + x.fees_outstanding + x.penalties_overdue), 2) as char)\r\n else "invalid PAR Type"\r\n end) as "Portfolio at Risk %"\r\n from m_office mo\r\njoin \r\n(select lastInstallment.branchId as branchId,\r\nlastInstallment.Currency,\r\ncount(distinct(lastInstallment.clientId)) as client_count, \r\ncount(distinct(lastInstallment.loanId)) as  active_loan_count,\r\ncount(distinct(laa.loan_id)  ) as arrears_loan_count,\r\n\r\nsum(l.principal_disbursed_derived) as principal,\r\nsum(l.principal_repaid_derived) as principal_repaid,\r\nsum(l.principal_outstanding_derived) as principal_outstanding,\r\nsum(ifnull(laa.principal_overdue_derived,0)) as principal_overdue,\r\n\r\nsum(l.interest_charged_derived) as interest,\r\nsum(l.interest_repaid_derived) as interest_repaid,\r\nsum(l.interest_outstanding_derived) as interest_outstanding,\r\nsum(ifnull(laa.interest_overdue_derived,0)) as interest_overdue,\r\n\r\nsum(l.fee_charges_charged_derived) as fees,\r\nsum(l.fee_charges_repaid_derived) as fees_repaid,\r\nsum(l.fee_charges_outstanding_derived)  as fees_outstanding,\r\nsum(ifnull(laa.fee_charges_overdue_derived,0)) as fees_overdue,\r\n\r\nsum(l.penalty_charges_charged_derived) as penalties,\r\nsum(l.penalty_charges_repaid_derived) as penalties_repaid,\r\nsum(l.penalty_charges_outstanding_derived) as penalties_outstanding,\r\nsum(ifnull(laa.penalty_charges_overdue_derived,0)) as penalties_overdue\r\n\r\nfrom \r\n(select l.id as loanId, l.number_of_repayments, min(r.installment), \r\nounder.id as branchId, ounder.hierarchy, ounder.`name` as branch, \r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nlo.display_name as "Loan Officer", c.id as clientId, c.account_no as "Client Account No",\r\nc.display_name as "Client", l.account_no as "Loan Account No", pl.`name` as "Product", \r\nf.`name` as Fund,  l.principal_amount as "Loan Amount", \r\nl.annual_nominal_interest_rate as "Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed", date(l.expected_maturedon_date) as "Expected Matured On"\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_repayment_schedule r on r.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.loan_status_id = 300\r\nand r.completed_derived is false\r\nand r.duedate >= curdate()\r\ngroup by l.id\r\nhaving l.number_of_repayments = min(r.installment)) lastInstallment\r\njoin m_loan l on l.id = lastInstallment.loanId\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\ngroup by lastInstallment.branchId) x on x.branchId = mo.id\r\norder by mo.hierarchy, x.Currency', 'Individual Client \n\nReport', 1, 1),
+	(59, 'Active Loans by Disbursal Period', 'Table', NULL, 'Loan', 'select concat(repeat("..",   \r\n   ((LENGTH(ounder.`hierarchy`) - LENGTH(REPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) as "Office/Branch",\r\nifnull(cur.display_symbol, l.currency_code) as Currency,\r\nc.account_no as "Client Account No", c.display_name as "Client", l.account_no as "Loan Account No", pl.`name` as "Product", \r\nf.`name` as Fund,  \r\nl.principal_amount as "Loan Principal Amount", \r\nl.annual_nominal_interest_rate as " Annual Nominal Interest Rate", \r\ndate(l.disbursedon_date) as "Disbursed Date", \r\n\r\nl.total_expected_repayment_derived as "Total Loan (P+I+F+Pen)",\r\nl.total_repayment_derived as "Total Repaid (P+I+F+Pen)",\r\nlo.display_name as "Loan Officer"\r\n\r\nfrom m_office o \r\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\r\nand ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\r\njoin m_client c on c.office_id = ounder.id\r\njoin m_loan l on l.client_id = c.id\r\njoin m_product_loan pl on pl.id = l.product_id\r\nleft join m_staff lo on lo.id = l.loan_officer_id\r\nleft join m_currency cur on cur.code = l.currency_code\r\nleft join m_fund f on f.id = l.fund_id\r\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\r\nwhere o.id = ${officeId}\r\nand (l.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\nand (l.product_id = "${loanProductId}" or "-1" = "${loanProductId}")\r\nand (ifnull(l.loan_officer_id, -10) = "${loanOfficerId}" or "-1" = "${loanOfficerId}")\r\nand (ifnull(l.fund_id, -10) = ${fundId} or -1 = ${fundId})\r\nand (ifnull(l.loanpurpose_cv_id, -10) = ${loanPurposeId} or -1 = ${loanPurposeId})\r\nand l.disbursedon_date between \'${startDate}\' and \'${endDate}\'\r\nand l.loan_status_id = 300\r\ngroup by l.id\r\norder by ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'Individual Client \n\nReport', 1, 1),
+	(61, 'Aging Summary (Arrears in Months)', 'Table', NULL, 'Loan', 'SELECT \r\n  IFNULL(periods.currencyName, periods.currency) as currency, \r\n  periods.period_no \'Days In Arrears\', \r\n  IFNULL(ars.loanId, 0) \'No Of Loans\', \r\n  IFNULL(ars.principal,0.0) \'Original Principal\', \r\n  IFNULL(ars.interest,0.0) \'Original Interest\', \r\n  IFNULL(ars.prinPaid,0.0) \'Principal Paid\', \r\n  IFNULL(ars.intPaid,0.0) \'Interest Paid\', \r\n  IFNULL(ars.prinOverdue,0.0) \'Principal Overdue\', \r\n  IFNULL(ars.intOverdue,0.0)\'Interest Overdue\'\r\nFROM \r\n /* full table of aging periods/currencies used combo to ensure each line represented */\r\n  (SELECT curs.code as currency, curs.name as currencyName, pers.* from\r\n  (SELECT \'On Schedule\' period_no,1 pid UNION\r\n   SELECT \'0 - 30\',2 UNION\r\n   SELECT \'30 - 60\',3 UNION\r\n    SELECT \'60 - 90\',4 UNION\r\n    SELECT \'90 - 180\',5 UNION\r\n   SELECT \'180 - 360\',6 UNION\r\n    SELECT \'> 360\',7 ) pers,\r\n  (SELECT distinctrow moc.code, moc.name\r\n    FROM m_office mo2\r\n     INNER JOIN m_office ounder2 ON ounder2.hierarchy \r\n       LIKE CONCAT(mo2.hierarchy, \'%\')\r\nAND ounder2.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n    INNER JOIN m_client mc2 ON mc2.office_id=ounder2.id\r\n     INNER JOIN m_loan ml2 ON ml2.client_id = mc2.id\r\n INNER JOIN m_organisation_currency moc ON moc.code = ml2.currency_code\r\n  WHERE ml2.loan_status_id=300 /* active */\r\n AND mo2.id=${officeId}\r\nAND (ml2.currency_code = "${currencyId}" or "-1" = "${currencyId}")) curs) periods\r\n\r\n\r\nLEFT JOIN /* table of aging periods per currency with gaps if no applicable loans */\r\n(SELECT \r\n    z.currency, z.arrPeriod, \r\n COUNT(z.loanId) as loanId, SUM(z.principal) as principal, SUM(z.interest) as interest, \r\n SUM(z.prinPaid) as prinPaid, SUM(z.intPaid) as intPaid, \r\n  SUM(z.prinOverdue) as prinOverdue, SUM(z.intOverdue) as intOverdue\r\nFROM\r\n  /*derived table just used to get arrPeriod value (was much slower to\r\n  duplicate calc of minOverdueDate in inner query)\r\nmight not be now with derived fields but didn’t check */\r\n  (SELECT x.loanId, x.currency, x.principal, x.interest, x.prinPaid, x.intPaid, x.prinOverdue, x.intOverdue,\r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<1, \'On Schedule\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<31, \'0 - 30\', \r\n   IF(DATEDIFF(CURDATE(), minOverdueDate)<61, \'30 - 60\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<91, \'60 - 90\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<181, \'90 - 180\', \r\n    IF(DATEDIFF(CURDATE(), minOverdueDate)<361, \'180 - 360\', \r\n        \'> 360\')))))) AS arrPeriod\r\n\r\n FROM /* get the individual loan details */\r\n    (SELECT ml.id AS loanId, ml.currency_code as currency,\r\n        ml.principal_disbursed_derived as principal, \r\n        ml.interest_charged_derived as interest, \r\n        ml.principal_repaid_derived as prinPaid, \r\n        ml.interest_repaid_derived intPaid,\r\n\r\n         laa.principal_overdue_derived as prinOverdue,\r\n         laa.interest_overdue_derived as intOverdue,\r\n\r\n         IFNULL(laa.overdue_since_date_derived, curdate()) as minOverdueDate\r\n        \r\n      FROM m_office mo\r\n      INNER JOIN m_office ounder ON ounder.hierarchy \r\n       LIKE CONCAT(mo.hierarchy, \'%\')\r\nAND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n      INNER JOIN m_client mc ON mc.office_id=ounder.id\r\n      INNER JOIN m_loan ml ON ml.client_id = mc.id\r\n       LEFT JOIN m_loan_arrears_aging laa on laa.loan_id = ml.id\r\n    WHERE ml.loan_status_id=300 /* active */\r\n        AND mo.id=${officeId}\r\n     AND (ml.currency_code = "${currencyId}" or "-1" = "${currencyId}")\r\n      GROUP BY ml.id) x\r\n ) z \r\nGROUP BY z.currency, z.arrPeriod ) ars ON ars.arrPeriod=periods.period_no and ars.currency = periods.currency\r\nORDER BY periods.currency, periods.pid', 'Loan amount in arrears by branch', 1, 1),
+	(91, 'Loan Account Schedule', 'Pentaho', NULL, 'Loan', NULL, NULL, 1, 0),
+	(92, 'Branch Expected Cash Flow', 'Pentaho', NULL, 'Loan', NULL, NULL, 1, 1),
+	(93, 'Expected Payments By Date - Basic', 'Table', NULL, 'Loan', 'SELECT \r\n      ounder.name \'Office\', \r\n      IFNULL(ms.display_name,\'-\') \'Loan Officer\',\r\n    mc.account_no \'Client Account Number\',\r\n    mc.display_name \'Name\',\r\n   mp.name \'Product\',\r\n    ml.account_no \'Loan Account Number\',\r\n    mr.duedate \'Due Date\',\r\n    mr.installment \'Installment\',\r\n   cu.display_symbol \'Currency\',\r\n   mr.principal_amount- IFNULL(mr.principal_completed_derived,0) \'Principal Due\',\r\n    mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0) \'Interest Due\', \r\n   IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0) \'Fees Due\', \r\n    IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0) \'Penalty Due\',\r\n      (mr.principal_amount- IFNULL(mr.principal_completed_derived,0)) +\r\n       (mr.interest_amount- IFNULL(IFNULL(mr.interest_completed_derived,mr.interest_waived_derived),0)) + \r\n       (IFNULL(mr.fee_charges_amount,0)- IFNULL(IFNULL(mr.fee_charges_completed_derived,mr.fee_charges_waived_derived),0)) + \r\n       (IFNULL(mr.penalty_charges_amount,0)- IFNULL(IFNULL(mr.penalty_charges_completed_derived,mr.penalty_charges_waived_derived),0)) \'Total Due\', \r\n     mlaa.total_overdue_derived \'Total Overdue\'\r\n                    \r\n FROM m_office mo\r\n  JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\n  \r\n  AND ounder.hierarchy like CONCAT(\'${currentUserHierarchy}\', \'%\')\r\n \r\n  LEFT JOIN m_client mc ON mc.office_id=ounder.id\r\n  LEFT JOIN m_loan ml ON ml.client_id=mc.id AND ml.loan_status_id=300\r\n  LEFT JOIN m_loan_arrears_aging mlaa ON mlaa.loan_id=ml.id\r\n  LEFT JOIN m_loan_repayment_schedule mr ON mr.loan_id=ml.id AND mr.completed_derived=0\r\n  LEFT JOIN m_product_loan mp ON mp.id=ml.product_id\r\n  LEFT JOIN m_staff ms ON ms.id=ml.loan_officer_id\r\n  LEFT JOIN m_currency cu ON cu.code=ml.currency_code\r\n WHERE mo.id=${officeId}\r\n AND (IFNULL(ml.loan_officer_id, -10) = "${loanOfficerId}" OR "-1" = "${loanOfficerId}")\r\n AND mr.duedate BETWEEN \'${startDate}\' AND \'${endDate}\'\r\n ORDER BY ounder.id,mr.duedate,ml.account_no', 'Test', 1, 1),
+	(94, 'Expected Payments By Date - Formatted', 'Pentaho', NULL, 'Loan', NULL, NULL, 1, 1),
+	(96, 'GroupSummaryCounts', 'Table', NULL, NULL, '\n/*\nActive Client is a client linked to the \'group\' via m_group_client \nand with an active \'status_enum\'.)\nActive Borrowers - Borrower may be a client or a \'group\'\n*/\nselect x.*\nfrom m_office o,\nm_group g,\n\n(select a.activeClients, \n(b.activeClientLoans + c.activeGroupLoans) as activeLoans, \nb.activeClientLoans, c.activeGroupLoans,\n(b.activeClientBorrowers + c.activeGroupBorrowers) as activeBorrowers,\nb.activeClientBorrowers, c.activeGroupBorrowers,\n(b.overdueClientLoans +  c.overdueGroupLoans) as overdueLoans,\nb.overdueClientLoans, c.overdueGroupLoans\nfrom\n(select count(*) as activeClients\nfrom m_group topgroup\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_group_client gc on gc.group_id = g.id\njoin m_client c on c.id = gc.client_id\nwhere topgroup.id = ${groupId} \nand c.status_enum = 300) a,\n\n(select count(*) as activeClientLoans, \ncount(distinct(l.client_id)) as activeClientBorrowers,\nifnull(sum(if(laa.loan_id is not null, 1, 0)), 0) as overdueClientLoans\nfrom m_group topgroup\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_loan l on l.group_id = g.id and l.client_id is not null\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nwhere topgroup.id = ${groupId} \nand l.loan_status_id = 300) b,\n\n(select count(*) as activeGroupLoans, \ncount(distinct(l.group_id)) as activeGroupBorrowers,\nifnull(sum(if(laa.loan_id is not null, 1, 0)), 0) as overdueGroupLoans\nfrom m_group topgroup\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_loan l on l.group_id = g.id and l.client_id is null\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nwhere topgroup.id = ${groupId} \nand l.loan_status_id = 300) c\n) x\n\nwhere g.id = ${groupId}\nand o.id = g.office_id\nand o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\n', 'Utility query for getting group summary count details for a group_id', 1, 0),
+	(97, 'GroupSummaryAmounts', 'Table', NULL, NULL, '\nselect ifnull(cur.display_symbol, l.currency_code) as currency,\nifnull(sum(l.principal_disbursed_derived),0) as totalDisbursedAmount,\nifnull(sum(l.principal_outstanding_derived),0) as totalLoanOutstandingAmount,\ncount(laa.loan_id) as overdueLoans, ifnull(sum(laa.total_overdue_derived), 0) as totalLoanOverdueAmount\nfrom m_group topgroup\njoin m_office o on o.id = topgroup.office_id and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_loan l on l.group_id = g.id\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id\nleft join m_currency cur on cur.code = l.currency_code\nwhere topgroup.id = ${groupId}\nand l.disbursedon_date is not null\ngroup by l.currency_code\n', 'Utility query for getting group summary currency amount details for a group_id', 1, 0),
+	(106, 'TxnRunningBalances', 'Table', NULL, 'Transaction', '\nselect date(\'${startDate}\') as \'Transaction Date\', \'Opening Balance\' as `Transaction Type`, null as Office,\n  null as \'Loan Officer\', null as `Loan Account No`, null as `Loan Product`, null as `Currency`, \n null as `Client Account No`, null as Client, \n null as Amount, null as Principal, null as Interest,\n@totalOutstandingPrincipal :=       \nifnull(round(sum(\n if (txn.transaction_type_enum = 1 /* disbursement */,\n   ifnull(txn.amount,0.00), \n   ifnull(txn.principal_portion_derived,0.00) * -1)) \n      ,2),0.00)  as \'Outstanding Principal\',\n\n@totalInterestIncome := \nifnull(round(sum(\n if (txn.transaction_type_enum in (2,5,8) /* repayment, repayment at disbursal, recovery repayment */,\n   ifnull(txn.interest_portion_derived,0.00), \n   0))\n     ,2),0.00) as \'Interest Income\',\n\n@totalWriteOff :=\nifnull(round(sum(\n if (txn.transaction_type_enum = 6 /* write-off */,\n    ifnull(txn.principal_portion_derived,0.00), \n    0)) \n      ,2),0.00) as \'Principal Write Off\'\nfrom m_office o\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\n                          and ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_client c on c.office_id = ounder.id\njoin m_loan l on l.client_id = c.id\njoin m_product_loan lp on lp.id = l.product_id\njoin m_loan_transaction txn on txn.loan_id = l.id\nleft join m_currency cur on cur.code = l.currency_code\nwhere txn.is_reversed = false  \nand txn.transaction_type_enum not in (10,11)\nand o.id = ${officeId}\nand txn.transaction_date < date(\'${startDate}\')\n\nunion all\n\nselect x.`Transaction Date`, x.`Transaction Type`, x.Office, x.`Loan Officer`, x.`Loan Account No`, x.`Loan Product`, x.`Currency`, \n  x.`Client Account No`, x.Client, x.Amount, x.Principal, x.Interest,\ncast(round( \n if (x.transaction_type_enum = 1 /* disbursement */,\n   @totalOutstandingPrincipal := @totalOutstandingPrincipal + x.`Amount`, \n   @totalOutstandingPrincipal := @totalOutstandingPrincipal - x.`Principal`) \n      ,2) as decimal(19,2)) as \'Outstanding Principal\',\ncast(round(\n  if (x.transaction_type_enum in (2,5,8) /* repayment, repayment at disbursal, recovery repayment */,\n   @totalInterestIncome := @totalInterestIncome + x.`Interest`, \n   @totalInterestIncome) \n      ,2) as decimal(19,2)) as \'Interest Income\',\ncast(round(\n  if (x.transaction_type_enum = 6 /* write-off */,\n    @totalWriteOff := @totalWriteOff + x.`Principal`, \n    @totalWriteOff) \n      ,2) as decimal(19,2)) as \'Principal Write Off\'\nfrom\n(select txn.transaction_type_enum, txn.id as txn_id, txn.transaction_date as \'Transaction Date\', \ncast(\n  ifnull(re.enum_message_property, concat(\'Unknown Transaction Type Value: \' , txn.transaction_type_enum)) \n as char) as \'Transaction Type\',\nounder.`name` as Office, lo.display_name as \'Loan Officer\',\nl.account_no  as \'Loan Account No\', lp.`name` as \'Loan Product\', \nifnull(cur.display_symbol, l.currency_code) as Currency,\nc.account_no as \'Client Account No\', c.display_name as \'Client\',\nifnull(txn.amount,0.00) as Amount,\nifnull(txn.principal_portion_derived,0.00) as Principal,\nifnull(txn.interest_portion_derived,0.00) as Interest\nfrom m_office o\njoin m_office ounder on ounder.hierarchy like concat(o.hierarchy, \'%\')\n                          and ounder.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_client c on c.office_id = ounder.id\njoin m_loan l on l.client_id = c.id\nleft join m_staff lo on lo.id = l.loan_officer_id\njoin m_product_loan lp on lp.id = l.product_id\njoin m_loan_transaction txn on txn.loan_id = l.id\nleft join m_currency cur on cur.code = l.currency_code\nleft join r_enum_value re on re.enum_name = \'transaction_type_enum\'\n           and re.enum_id = txn.transaction_type_enum\nwhere txn.is_reversed = false  \nand txn.transaction_type_enum not in (10,11)\nand (ifnull(l.loan_officer_id, -10) = \'${loanOfficerId}\' or \'-1\' = \'${loanOfficerId}\')\nand o.id = ${officeId}\nand txn.transaction_date >= date(\'${startDate}\')\nand txn.transaction_date <= date(\'${endDate}\')\norder by txn.transaction_date, txn.id) x\n', 'Running Balance Txn report for Individual Lending.\nSuitable for small MFI\'s.  Larger could use it using the branch or other parameters.\nBasically, suck it and see if its quick enough for you out-of-te box or whether it needs performance work in your situation.\n', 0, 0),
+	(107, 'FieldAgentStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_staff fa\njoin m_office o on o.id = fa.office_id\n     and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group pgm on pgm.staff_id = fa.id\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id \nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id \njoin m_client c on c.id = l.client_id\nwhere fa.id = ${staffId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Field Agent Statistics', 0, 0),
+	(108, 'FieldAgentPrograms', 'Table', NULL, 'Quipo', ' \nselect pgm.id, pgm.display_name as `name`, sts.enum_message_property as status\n from m_group pgm \n join m_office o on o.id = pgm.office_id\n      and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\n left join r_enum_value sts on sts.enum_name = \'status_enum\' and sts.enum_id = pgm.status_enum\n where pgm.staff_id = ${staffId} \n', 'List of Field Agent Programs', 0, 0),
+	(109, 'ProgramDetails', 'Table', NULL, 'Quipo', '\n select l.id as loanId, l.account_no as loanAccountNo, c.id as clientId, c.account_no as clientAccountNo,\n pgm.display_name as programName, \n\n(select count(*)\nfrom m_loan cy\nwhere cy.group_id = pgm.id and cy.client_id =c.id\nand cy.disbursedon_date <= l.disbursedon_date) as loanCycleNo,\n\nc.display_name as clientDisplayName,\n ifnull(cur.display_symbol, l.currency_code) as Currency,\nifnull(l.principal_repaid_derived,0.0) as loanRepaidAmount,\nifnull(l.principal_outstanding_derived, 0.0) as loanOutstandingAmount,\nifnull(lpa.principal_in_advance_derived,0.0) as LoanPaidInAdvance,\n\nifnull(laa.principal_overdue_derived, 0.0) as loanInArrearsAmount, \nif(ifnull(laa.principal_overdue_derived, 0.00) > 0, \'Yes\', \'No\') as inDefault,\n\nif(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)  as portfolioAtRisk\n\n from m_group pgm\n join m_office o on o.id = pgm.office_id\n       and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\n join m_loan l on l.group_id = pgm.id and l.client_id is not null\n left join m_currency cur on cur.code = l.currency_code\n join m_client c on c.id = l.client_id\n left join m_loan_arrears_aging laa on laa.loan_id = l.id\n left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id \n where pgm.id = ${programId}\n and l.loan_status_id = 300\norder by c.display_name, l.account_no\n \n', 'List of Loans in a Program', 0, 0),
+	(110, 'ChildrenStaffList', 'Table', NULL, 'Quipo', '\n select s.id, s.display_name, \ns.firstname, s.lastname, s.organisational_role_enum, \ns.organisational_role_parent_staff_id, \nsp.display_name as `organisational_role_parent_staff_display_name` \nfrom m_staff s \njoin m_staff sp on s.organisational_role_parent_staff_id = sp.id \nwhere s.organisational_role_parent_staff_id = ${staffId}\n', 'Get Next Level Down Staff', 0, 0),
+	(111, 'CoordinatorStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_staff coord\njoin m_staff fa on fa.organisational_role_parent_staff_id = coord.id\njoin m_office o on o.id = fa.office_id\n      and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group pgm on pgm.staff_id = fa.id\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id \nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id \njoin m_client c on c.id = l.client_id\nwhere coord.id = ${staffId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Coordinator Statistics', 0, 0),
+	(112, 'BranchManagerStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_staff bm\njoin m_staff coord on coord.organisational_role_parent_staff_id = bm.id\njoin m_staff fa on fa.organisational_role_parent_staff_id = coord.id\njoin m_office o on o.id = fa.office_id\n      and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group pgm on pgm.staff_id = fa.id\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id \nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id \njoin m_client c on c.id = l.client_id\nwhere bm.id = ${staffId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Branch Manager Statistics', 0, 0),
+	(113, 'ProgramDirectorStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_staff pd\njoin m_staff bm on bm.organisational_role_parent_staff_id = pd.id\njoin m_staff coord on coord.organisational_role_parent_staff_id = bm.id\njoin m_staff fa on fa.organisational_role_parent_staff_id = coord.id\njoin m_office o on o.id = fa.office_id\n     and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group pgm on pgm.staff_id = fa.id\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id \nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id \njoin m_client c on c.id = l.client_id\nwhere pd.id = ${staffId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Program DirectorStatistics', 0, 0),
+	(114, 'ProgramStats', 'Table', NULL, 'Quipo', '\nselect ifnull(cur.display_symbol, l.currency_code) as Currency,\n/*This query will return more than one entry if more than one currency is used */\ncount(distinct(c.id)) as activeClients, count(*) as activeLoans,\nsum(l.principal_disbursed_derived) as disbursedAmount,\nsum(l.principal_outstanding_derived) as loanOutstandingAmount,\nround((sum(l.principal_outstanding_derived) * 100) /  sum(l.principal_disbursed_derived),2) as loanOutstandingPC,\nsum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance,\nsum(\n if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) as portfolioAtRisk,\n\nround((sum(\n  if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n    l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC,\n\ncount(distinct(\n    if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) as clientsInDefault,\nround((count(distinct(\n   if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()),\n      c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC,\n(sum(l.principal_disbursed_derived) / count(*))  as averageLoanAmount\nfrom m_group pgm\njoin m_office o on o.id = pgm.office_id\n     and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_loan l on l.group_id = pgm.id and l.client_id is not null\nleft join m_currency cur on cur.code = l.currency_code\nleft join m_loan_arrears_aging laa on laa.loan_id = l.id \nleft join m_loan_paid_in_advance lpa on lpa.loan_id = l.id \njoin m_client c on c.id = l.client_id\nwhere pgm.id = ${programId}\nand l.loan_status_id = 300\ngroup  by l.currency_code\n', 'Program Statistics', 0, 0),
+	(115, 'ClientSummary ', 'Table', NULL, NULL, 'SELECT x.* FROM m_client c, m_office o, \n(\n       SELECT a.loanCycle, a.activeLoans, b.lastLoanAmount, d.activeSavings, d.totalSavings FROM \n  (SELECT IFNULL(MAX(l.loan_counter),0) AS loanCycle, COUNT(l.id) AS activeLoans FROM m_loan l WHERE l.loan_status_id=300 AND l.client_id=${clientId}) a, \n  (SELECT count(l.id), IFNULL(l.principal_amount,0) AS \'lastLoanAmount\' FROM m_loan l WHERE l.client_id=${clientId} AND l.disbursedon_date = (SELECT IFNULL(MAX(disbursedon_date),NOW()) FROM m_loan where client_id=${clientId} and loan_status_id=300)) b, \n (SELECT COUNT(s.id) AS \'activeSavings\', IFNULL(SUM(s.account_balance_derived),0) AS \'totalSavings\' FROM m_savings_account s WHERE s.status_enum=300 AND s.client_id=${clientId}) d\n) x\nWHERE c.id=${clientId} AND o.id = c.office_id AND o.hierarchy LIKE CONCAT(\'${currentUserHierarchy}\', \'%\')', 'Utility query for getting the client summary details', 1, 0),
+	(116, 'LoanCyclePerProduct', 'Table', NULL, NULL, 'SELECT lp.name AS \'productName\', MAX(l.loan_product_counter) AS \'loanProductCycle\' FROM m_loan l JOIN m_product_loan lp ON l.product_id=lp.id WHERE lp.include_in_borrower_cycle=1 AND l.loan_product_counter IS NOT NULL AND l.client_id=${clientId} GROUP BY l.product_id', 'Utility query for getting the client loan cycle details', 1, 0),
+	(117, 'GroupSavingSummary', 'Table', NULL, NULL, 'select ifnull(cur.display_symbol, sa.currency_code) as currency,\ncount(sa.id) as totalSavingAccounts, ifnull(sum(sa.account_balance_derived),0) as totalSavings\nfrom m_group topgroup\njoin m_office o on o.id = topgroup.office_id and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_savings_account sa on sa.group_id = g.id\nleft join m_currency cur on cur.code = sa.currency_code\nwhere topgroup.id = ${groupId}\nand sa.activatedon_date is not null\ngroup by sa.currency_code', 'Utility query for getting group or center saving summary details for a group_id', 1, 0),
+	(118, 'Savings Transactions', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1),
+	(119, 'Client Savings Summary', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1),
+	(120, 'Active Loans - Details(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(121, 'Active Loans - Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(122, 'Active Loans by Disbursal Period(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(123, 'Active Loans in last installment Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(124, 'Active Loans in last installment(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(125, 'Active Loans Passed Final Maturity Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(126, 'Active Loans Passed Final Maturity(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(127, 'Aging Detail(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(128, 'Aging Summary (Arrears in Months)(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(129, 'Aging Summary (Arrears in Weeks)(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(130, 'Client Listing(Pentaho)', 'Pentaho', NULL, 'Client', '(NULL)', '(NULL)', 1, 1),
+	(131, 'Client Loans Listing(Pentaho)', 'Pentaho', NULL, 'Client', '(NULL)', '(NULL)', 1, 1),
+	(132, 'Expected Payments By Date - Basic(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(133, 'Funds Disbursed Between Dates Summary by Office(Pentaho)', 'Pentaho', NULL, 'Fund', '(NULL)', '(NULL)', 1, 1),
+	(134, 'Funds Disbursed Between Dates Summary(Pentaho)', 'Pentaho', NULL, 'Fund', '(NULL)', '(NULL)', 1, 1),
+	(135, 'Loans Awaiting Disbursal Summary by Month(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(136, 'Loans Awaiting Disbursal Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(137, 'Loans Awaiting Disbursal(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(138, 'Loans Pending Approval(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(139, 'Obligation Met Loans Details(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(140, 'Obligation Met Loans Summary(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(141, 'Portfolio at Risk by Branch(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(142, 'Portfolio at Risk(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(143, 'Rescheduled Loans(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(144, 'TxnRunningBalances(Pentaho)', 'Pentaho', NULL, 'Transaction', '(NULL)', '(NULL)', 1, 1),
+	(145, 'Written-Off Loans(Pentaho)', 'Pentaho', NULL, 'Loan', '(NULL)', '(NULL)', 1, 1),
+	(146, 'Client Saving Transactions', 'Pentaho', NULL, 'Savings', NULL, NULL, 0, 0),
+	(147, 'Client Loan Account Schedule', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 0),
+	(148, 'GroupNamesByStaff', 'Table', '', '', 'Select gr.id as id, gr.display_name as name from m_group gr where gr.level_id=1 and gr.staff_id = ${staffId}', '', 1, 0),
+	(149, 'ClientTrendsByDay', 'Table', '', 'Client', 'SELECT   COUNT(cl.id) AS count, \n   cl.activation_date AS days\nFROM m_office of \n LEFT JOIN m_client cl on of.id = cl.office_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (cl.activation_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 DAY) AND DATE(NOW()- INTERVAL 1 DAY))\nGROUP BY days', 'Retrieves the number of clients joined in last 12 days', 1, 0),
+	(150, 'ClientTrendsByWeek', 'Table', '', 'Client', 'SELECT  COUNT(cl.id) AS count, \n   WEEK(cl.activation_date) AS Weeks\nFROM m_office of \n  LEFT JOIN m_client cl on of.id = cl.office_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (cl.activation_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 WEEK) AND DATE(NOW()))\nGROUP BY Weeks', '', 1, 0),
+	(151, 'ClientTrendsByMonth', 'Table', '', 'Client', 'SELECT   COUNT(cl.id) AS count, \n   MONTHNAME(cl.activation_date) AS Months\nFROM m_office of \n  LEFT JOIN m_client cl on of.id = cl.office_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (cl.activation_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 MONTH) AND DATE(NOW()))\nGROUP BY Months', '', 1, 0),
+	(152, 'LoanTrendsByDay', 'Table', '', 'Loan', 'SELECT   COUNT(ln.id) AS lcount, \n    ln.disbursedon_date AS days\nFROM m_office of \n  LEFT JOIN m_client cl on of.id = cl.office_id\n LEFT JOIN m_loan ln on cl.id = ln.client_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (ln.disbursedon_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 DAY) AND DATE(NOW()- INTERVAL 1 DAY))\nGROUP BY days', 'Retrieves Number of loans disbursed for last 12 days', 1, 0),
+	(153, 'LoanTrendsByWeek', 'Table', '', 'Loan', 'SELECT  COUNT(ln.id) AS lcount, \n    WEEK(ln.disbursedon_date) AS Weeks\nFROM m_office of \n LEFT JOIN m_client cl on of.id = cl.office_id\n LEFT JOIN m_loan ln on cl.id = ln.client_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (ln.disbursedon_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 WEEK) AND DATE(NOW()))\nGROUP BY Weeks', '', 1, 0),
+	(154, 'LoanTrendsByMonth', 'Table', '', 'Loan', 'SELECT   COUNT(ln.id) AS lcount, \n    MONTHNAME(ln.disbursedon_date) AS Months\nFROM m_office of \n LEFT JOIN m_client cl on of.id = cl.office_id\n LEFT JOIN m_loan ln on cl.id = ln.client_id\nWHERE of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n  AND (ln.disbursedon_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 MONTH) AND DATE(NOW()))\nGROUP BY Months', '', 1, 0),
+	(155, 'Demand_Vs_Collection', 'Table', '', 'Loan', 'select amount.AmountDue-amount.AmountPaid as AmountDue, amount.AmountPaid as AmountPaid from\n(SELECT \n(IFNULL(SUM(ls.principal_amount),0) - IFNULL(SUM(ls.principal_writtenoff_derived),0)\n + IFNULL(SUM(ls.interest_amount),0) - IFNULL(SUM(ls.interest_writtenoff_derived),0) \n - IFNULL(SUM(ls.interest_waived_derived),0)\n + IFNULL(SUM(ls.fee_charges_amount),0) - IFNULL(SUM(ls.fee_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.fee_charges_waived_derived),0)\n + IFNULL(SUM(ls.penalty_charges_amount),0) - IFNULL(SUM(ls.penalty_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.penalty_charges_waived_derived),0)\n) AS AmountDue, \n\n(IFNULL(SUM(ls.principal_completed_derived),0) - IFNULL(SUM(ls.principal_writtenoff_derived),0) + IFNULL(SUM(ls.interest_completed_derived),0) - IFNULL(SUM(ls.interest_writtenoff_derived),0) \n - IFNULL(SUM(ls.interest_waived_derived),0)\n + IFNULL(SUM(ls.fee_charges_completed_derived),0) - IFNULL(SUM(ls.fee_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.fee_charges_waived_derived),0)\n + IFNULL(SUM(ls.penalty_charges_completed_derived),0) - IFNULL(SUM(ls.penalty_charges_writtenoff_derived),0) \n - IFNULL(SUM(ls.penalty_charges_waived_derived),0)\n) AS AmountPaid\nFROM m_office of\nLEFT JOIN m_client cl ON of.id = cl.office_id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nLEFT JOIN m_loan_repayment_schedule ls ON ln.id = ls.loan_id\nWHERE ls.duedate = DATE(NOW()) AND \n (of.hierarchy LIKE CONCAT((\nSELECT ino.hierarchy\nFROM m_office ino\nWHERE ino.id = ${officeId}),"%"))) as amount', 'Demand Vs Collection', 1, 0),
+	(156, 'Disbursal_Vs_Awaitingdisbursal', 'Table', '', 'Loan', 'select awaitinddisbursal.amount-disbursedAmount.amount as amountToBeDisburse, disbursedAmount.amount as disbursedAmount from \n(\nSELECT  COUNT(ln.id) AS noOfLoans, \n     IFNULL(SUM(ln.principal_amount),0) AS amount\nFROM \nm_office of\nLEFT JOIN m_client cl ON cl.office_id = of.id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nWHERE \nln.expected_disbursedon_date = DATE(NOW()) AND \n(ln.loan_status_id=200 OR ln.loan_status_id=300) AND\n of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" )\n) awaitinddisbursal,\n(\nSELECT   COUNT(ltrxn.id) as count, \n      IFNULL(SUM(ltrxn.amount),0) as amount \nFROM \nm_office of\nLEFT JOIN m_client cl ON cl.office_id = of.id\nLEFT JOIN m_loan ln ON cl.id = ln.client_id\nLEFT JOIN m_loan_transaction ltrxn ON ln.id = ltrxn.loan_id\nWHERE \nltrxn.transaction_date = DATE(NOW()) AND \nltrxn.is_reversed = 0 AND\nltrxn.transaction_type_enum=1 AND\n of.hierarchy like concat((select ino.hierarchy from m_office ino where ino.id = ${officeId}),"%" ) \n) disbursedAmount', 'Disbursal_Vs_Awaitingdisbursal', 1, 0),
+	(157, 'Savings Transaction Receipt', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1),
+	(158, 'Loan Transaction Receipt', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1),
+	(159, 'Staff Assignment History', 'Pentaho', NULL, NULL, NULL, NULL, 0, 1),
+	(160, 'GeneralLedgerReport', 'Pentaho', NULL, 'Accounting', NULL, NULL, 0, 1),
+	(161, 'Active Loan Summary per Branch', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1),
+	(162, 'Balance Outstanding', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1),
+	(163, 'Collection Report', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1),
+	(164, 'Disbursal Report', 'Pentaho', NULL, 'Loans', NULL, NULL, 0, 1);
+/*!40000 ALTER TABLE `stretchy_report` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.stretchy_report_parameter
+DROP TABLE IF EXISTS `stretchy_report_parameter`;
+CREATE TABLE IF NOT EXISTS `stretchy_report_parameter` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `report_id` int(11) NOT NULL,
+  `parameter_id` int(11) NOT NULL,
+  `report_parameter_name` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `report_parameter_unique` (`report_id`,`parameter_id`),
+  KEY `fk_report_parameter_001_idx` (`report_id`),
+  KEY `fk_report_parameter_002_idx` (`parameter_id`),
+  CONSTRAINT `fk_report_parameter_001` FOREIGN KEY (`report_id`) REFERENCES `stretchy_report` (`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_report_parameter_002` FOREIGN KEY (`parameter_id`) REFERENCES `stretchy_parameter` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=441 DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.stretchy_report_parameter: ~334 rows (approximately)
+/*!40000 ALTER TABLE `stretchy_report_parameter` DISABLE KEYS */;
+INSERT INTO `stretchy_report_parameter` (`id`, `report_id`, `parameter_id`, `report_parameter_name`) VALUES
+	(1, 1, 5, NULL),
+	(2, 2, 5, NULL),
+	(3, 2, 6, NULL),
+	(4, 2, 10, NULL),
+	(5, 2, 20, NULL),
+	(6, 2, 25, NULL),
+	(7, 2, 26, NULL),
+	(8, 5, 5, NULL),
+	(9, 5, 6, NULL),
+	(10, 5, 10, NULL),
+	(11, 5, 20, NULL),
+	(12, 5, 25, NULL),
+	(13, 5, 26, NULL),
+	(14, 6, 5, NULL),
+	(15, 6, 6, NULL),
+	(16, 6, 10, NULL),
+	(17, 6, 20, NULL),
+	(18, 6, 25, NULL),
+	(19, 6, 26, NULL),
+	(20, 7, 5, NULL),
+	(21, 7, 6, NULL),
+	(22, 7, 10, NULL),
+	(23, 7, 20, NULL),
+	(24, 7, 25, NULL),
+	(25, 7, 26, NULL),
+	(26, 8, 5, NULL),
+	(27, 8, 6, NULL),
+	(28, 8, 10, NULL),
+	(29, 8, 25, NULL),
+	(30, 8, 26, NULL),
+	(31, 11, 5, NULL),
+	(32, 11, 6, NULL),
+	(33, 11, 10, NULL),
+	(34, 11, 20, NULL),
+	(35, 11, 25, NULL),
+	(36, 11, 26, NULL),
+	(37, 11, 100, NULL),
+	(38, 12, 5, NULL),
+	(39, 12, 6, NULL),
+	(40, 12, 10, NULL),
+	(41, 12, 20, NULL),
+	(42, 12, 25, NULL),
+	(43, 12, 26, NULL),
+	(44, 13, 1, NULL),
+	(45, 13, 2, NULL),
+	(46, 13, 3, NULL),
+	(47, 13, 5, NULL),
+	(48, 13, 6, NULL),
+	(49, 13, 10, NULL),
+	(50, 13, 20, NULL),
+	(51, 13, 25, NULL),
+	(52, 13, 26, NULL),
+	(53, 14, 1, NULL),
+	(54, 14, 2, NULL),
+	(55, 14, 3, NULL),
+	(56, 14, 5, NULL),
+	(57, 14, 6, NULL),
+	(58, 14, 10, NULL),
+	(59, 14, 20, NULL),
+	(60, 14, 25, NULL),
+	(61, 14, 26, NULL),
+	(62, 15, 5, NULL),
+	(63, 15, 6, NULL),
+	(64, 15, 10, NULL),
+	(65, 15, 20, NULL),
+	(66, 15, 25, NULL),
+	(67, 15, 26, NULL),
+	(68, 15, 100, NULL),
+	(69, 16, 5, NULL),
+	(70, 16, 6, NULL),
+	(71, 16, 10, NULL),
+	(72, 16, 20, NULL),
+	(73, 16, 25, NULL),
+	(74, 16, 26, NULL),
+	(75, 16, 100, NULL),
+	(76, 20, 1, NULL),
+	(77, 20, 2, NULL),
+	(78, 20, 10, NULL),
+	(79, 20, 20, NULL),
+	(80, 21, 1, NULL),
+	(81, 21, 2, NULL),
+	(82, 21, 5, NULL),
+	(83, 21, 10, NULL),
+	(84, 21, 20, NULL),
+	(85, 48, 5, 'branch'),
+	(86, 48, 2, 'date'),
+	(87, 49, 5, 'branch'),
+	(88, 49, 1, 'fromDate'),
+	(89, 49, 2, 'toDate'),
+	(90, 50, 5, 'branch'),
+	(91, 50, 1, 'fromDate'),
+	(92, 50, 2, 'toDate'),
+	(93, 51, 1, NULL),
+	(94, 51, 2, NULL),
+	(95, 51, 5, NULL),
+	(96, 51, 10, NULL),
+	(97, 51, 25, NULL),
+	(98, 52, 5, NULL),
+	(99, 53, 5, NULL),
+	(100, 53, 10, NULL),
+	(101, 54, 1, NULL),
+	(102, 54, 2, NULL),
+	(103, 54, 5, NULL),
+	(104, 54, 10, NULL),
+	(105, 54, 25, NULL),
+	(106, 55, 5, NULL),
+	(107, 55, 6, NULL),
+	(108, 55, 10, NULL),
+	(109, 55, 20, NULL),
+	(110, 55, 25, NULL),
+	(111, 55, 26, NULL),
+	(112, 56, 5, NULL),
+	(113, 56, 6, NULL),
+	(114, 56, 10, NULL),
+	(115, 56, 20, NULL),
+	(116, 56, 25, NULL),
+	(117, 56, 26, NULL),
+	(118, 56, 100, NULL),
+	(119, 57, 5, NULL),
+	(120, 57, 6, NULL),
+	(121, 57, 10, NULL),
+	(122, 57, 20, NULL),
+	(123, 57, 25, NULL),
+	(124, 57, 26, NULL),
+	(125, 58, 5, NULL),
+	(126, 58, 6, NULL),
+	(127, 58, 10, NULL),
+	(128, 58, 20, NULL),
+	(129, 58, 25, NULL),
+	(130, 58, 26, NULL),
+	(131, 58, 100, NULL),
+	(132, 59, 1, NULL),
+	(133, 59, 2, NULL),
+	(134, 59, 5, NULL),
+	(135, 59, 6, NULL),
+	(136, 59, 10, NULL),
+	(137, 59, 20, NULL),
+	(138, 59, 25, NULL),
+	(139, 59, 26, NULL),
+	(140, 61, 5, NULL),
+	(141, 61, 10, NULL),
+	(142, 92, 1, 'fromDate'),
+	(143, 92, 5, 'selectOffice'),
+	(144, 92, 2, 'toDate'),
+	(145, 93, 1, NULL),
+	(146, 93, 2, NULL),
+	(147, 93, 5, NULL),
+	(148, 93, 6, NULL),
+	(149, 94, 2, 'endDate'),
+	(150, 94, 6, 'loanOfficerId'),
+	(151, 94, 5, 'officeId'),
+	(152, 94, 1, 'startDate'),
+	(256, 106, 2, NULL),
+	(257, 106, 6, NULL),
+	(258, 106, 5, NULL),
+	(259, 106, 1, NULL),
+	(263, 118, 1, 'fromDate'),
+	(264, 118, 2, 'toDate'),
+	(265, 118, 1004, 'accountNo'),
+	(266, 119, 1, 'fromDate'),
+	(267, 119, 2, 'toDate'),
+	(268, 119, 5, 'selectOffice'),
+	(269, 119, 1005, 'selectProduct'),
+	(270, 120, 5, 'branch'),
+	(271, 120, 6, 'loanOfficer'),
+	(272, 120, 10, 'currencyId'),
+	(273, 120, 20, 'fundId'),
+	(274, 120, 25, 'loanProductId'),
+	(275, 120, 26, 'loanPurposeId'),
+	(276, 121, 5, 'Branch'),
+	(277, 121, 6, 'loanOfficer'),
+	(278, 121, 10, 'CurrencyId'),
+	(279, 121, 20, 'fundId'),
+	(280, 121, 25, 'loanProductId'),
+	(281, 121, 26, 'loanPurposeId'),
+	(282, 121, 100, 'parType'),
+	(283, 122, 5, 'Branch'),
+	(284, 122, 6, 'loanOfficer'),
+	(285, 122, 10, 'CurrencyId'),
+	(286, 122, 20, 'fundId'),
+	(287, 122, 25, 'loanProductId'),
+	(288, 122, 26, 'loanPurposeId'),
+	(289, 122, 1, 'startDate'),
+	(290, 122, 2, 'endDate'),
+	(291, 123, 5, 'Branch'),
+	(292, 123, 6, 'Loan Officer'),
+	(293, 123, 10, 'CurrencyId'),
+	(294, 123, 20, 'fundId'),
+	(295, 123, 25, 'loanProductId'),
+	(296, 123, 26, 'loanPurposeId'),
+	(297, 123, 100, 'parType'),
+	(298, 124, 5, 'Branch'),
+	(299, 124, 6, 'Loan Officer'),
+	(300, 124, 10, 'CurrencyId'),
+	(301, 124, 20, 'fundId'),
+	(302, 124, 25, 'loanProductId'),
+	(303, 124, 26, 'loanPurposeId'),
+	(304, 125, 5, 'Branch'),
+	(305, 125, 6, 'Loan Officer'),
+	(306, 125, 10, 'CurrencyId'),
+	(307, 125, 20, 'fundId'),
+	(308, 125, 25, 'loanProductId'),
+	(309, 125, 26, 'loanPurposeId'),
+	(310, 125, 100, 'parType'),
+	(311, 126, 5, 'Branch'),
+	(312, 126, 6, 'Loan Officer'),
+	(313, 126, 10, 'CurrencyId'),
+	(314, 126, 20, 'fundId'),
+	(315, 126, 25, 'loanProductId'),
+	(316, 126, 26, 'loanPurposeId'),
+	(317, 127, 5, 'Branch'),
+	(318, 128, 5, 'Branch'),
+	(319, 128, 10, 'CurrencyId'),
+	(320, 129, 5, 'Branch'),
+	(321, 129, 10, 'CurrencyId'),
+	(322, 130, 5, 'selectOffice'),
+	(323, 131, 5, 'Branch'),
+	(324, 131, 6, 'Loan Officer'),
+	(325, 131, 10, 'CurrencyId'),
+	(326, 131, 20, 'fundId'),
+	(327, 131, 25, 'loanProductId'),
+	(328, 131, 26, 'loanPurposeId'),
+	(329, 132, 5, 'Branch'),
+	(330, 132, 6, 'Loan Officer'),
+	(331, 132, 1, 'startDate'),
+	(332, 132, 2, 'endDate'),
+	(333, 133, 5, 'Branch'),
+	(334, 133, 10, 'CurrencyId'),
+	(335, 133, 20, 'fundId'),
+	(336, 133, 1, 'startDate'),
+	(337, 133, 2, 'endDate'),
+	(338, 134, 10, 'CurrencyId'),
+	(339, 134, 20, 'fundId'),
+	(340, 134, 1, 'startDate'),
+	(341, 134, 2, 'endDate'),
+	(342, 135, 5, 'Branch'),
+	(343, 135, 6, 'Loan Officer'),
+	(344, 135, 10, 'CurrencyId'),
+	(345, 135, 20, 'fundId'),
+	(346, 135, 25, 'loanProductId'),
+	(347, 135, 26, 'loanPurposeId'),
+	(348, 136, 5, 'Branch'),
+	(349, 136, 6, 'Loan Officer'),
+	(350, 136, 10, 'CurrencyId'),
+	(351, 136, 20, 'fundId'),
+	(352, 136, 25, 'loanProductId'),
+	(353, 136, 26, 'loanPurposeId'),
+	(354, 137, 5, 'Branch'),
+	(355, 137, 6, 'Loan Officer'),
+	(356, 137, 10, 'CurrencyId'),
+	(357, 137, 20, 'fundId'),
+	(358, 137, 25, 'loanProductId'),
+	(359, 137, 26, 'loanPurposeId'),
+	(360, 138, 5, 'Branch'),
+	(361, 138, 6, 'Loan Officer'),
+	(362, 138, 10, 'CurrencyId'),
+	(363, 138, 20, 'fundId'),
+	(364, 138, 25, 'loanProductId'),
+	(365, 138, 26, 'loanPurposeId'),
+	(366, 139, 5, 'Branch'),
+	(367, 139, 6, 'Loan Officer'),
+	(368, 139, 10, 'CurrencyId'),
+	(369, 139, 20, 'fundId'),
+	(370, 139, 25, 'loanProductId'),
+	(371, 139, 26, 'loanPurposeId'),
+	(372, 139, 1, 'startDate'),
+	(373, 139, 2, 'endDate'),
+	(374, 139, 3, 'obligDateType'),
+	(375, 140, 5, 'Branch'),
+	(376, 140, 6, 'Loan Officer'),
+	(377, 140, 10, 'CurrencyId'),
+	(378, 140, 20, 'fundId'),
+	(379, 140, 25, 'loanProductId'),
+	(380, 140, 26, 'loanPurposeId'),
+	(381, 140, 1, 'Startdate'),
+	(382, 140, 2, 'Enddate'),
+	(383, 140, 3, 'obligDateType'),
+	(384, 141, 5, 'Branch'),
+	(385, 141, 6, 'Loan Officer'),
+	(386, 141, 10, 'CurrencyId'),
+	(387, 141, 20, 'fundId'),
+	(388, 141, 25, 'loanProductId'),
+	(389, 141, 26, 'loanPurposeId'),
+	(390, 141, 100, 'parType'),
+	(391, 142, 5, 'Branch'),
+	(392, 142, 6, 'loanOfficer'),
+	(393, 142, 10, 'CurrencyId'),
+	(394, 142, 20, 'fundId'),
+	(395, 142, 25, 'loanProductId'),
+	(396, 142, 26, 'loanPurposeId'),
+	(397, 142, 100, 'parType'),
+	(398, 143, 5, 'Branch'),
+	(399, 143, 10, 'CurrencyId'),
+	(400, 143, 25, 'loanProductId'),
+	(401, 143, 1, 'startDate'),
+	(402, 143, 2, 'endDate'),
+	(403, 144, 5, 'Branch'),
+	(404, 144, 6, 'Loan Officer'),
+	(405, 144, 1, 'startDate'),
+	(406, 144, 2, 'endDate'),
+	(407, 145, 5, 'Branch'),
+	(408, 145, 10, 'CurrencyId'),
+	(409, 145, 25, 'loanProductId'),
+	(410, 145, 1, 'startDate'),
+	(411, 145, 2, 'endDate'),
+	(412, 146, 1, 'startDate'),
+	(413, 146, 2, 'endDate'),
+	(414, 146, 1004, 'accountNo'),
+	(415, 147, 1, 'startDate'),
+	(416, 147, 2, 'endDate'),
+	(417, 147, 1004, 'selectLoan'),
+	(418, 149, 5, ''),
+	(419, 150, 5, ''),
+	(420, 151, 5, ''),
+	(421, 152, 5, ''),
+	(422, 153, 5, ''),
+	(423, 154, 5, ''),
+	(424, 155, 5, ''),
+	(425, 156, 5, ''),
+	(426, 157, 1006, 'transactionId'),
+	(427, 158, 1006, 'transactionId'),
+	(428, 159, 1007, 'centerId'),
+	(429, 160, 1008, 'account'),
+	(430, 160, 1, 'fromDate'),
+	(431, 160, 2, 'toDate'),
+	(432, 160, 5, 'branch'),
+	(433, 162, 5, 'branch'),
+	(434, 162, 1009, 'ondate'),
+	(435, 163, 5, 'branch'),
+	(436, 163, 1, 'fromDate'),
+	(437, 163, 2, 'toDate'),
+	(438, 164, 5, 'branch'),
+	(439, 164, 1, 'fromDate'),
+	(440, 164, 2, 'toDate');
+/*!40000 ALTER TABLE `stretchy_report_parameter` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.x_registered_table
+DROP TABLE IF EXISTS `x_registered_table`;
+CREATE TABLE IF NOT EXISTS `x_registered_table` (
+  `registered_table_name` varchar(50) NOT NULL,
+  `application_table_name` varchar(50) NOT NULL,
+  `category` int(11) NOT NULL DEFAULT '100',
+  PRIMARY KEY (`registered_table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.x_registered_table: ~0 rows (approximately)
+/*!40000 ALTER TABLE `x_registered_table` DISABLE KEYS */;
+/*!40000 ALTER TABLE `x_registered_table` ENABLE KEYS */;
+
+
+-- Dumping structure for table mifostenant-reference.x_table_column_code_mappings
+DROP TABLE IF EXISTS `x_table_column_code_mappings`;
+CREATE TABLE IF NOT EXISTS `x_table_column_code_mappings` (
+  `column_alias_name` varchar(50) NOT NULL,
+  `code_id` int(10) NOT NULL,
+  PRIMARY KEY (`column_alias_name`),
+  KEY `FK_x_code_id` (`code_id`),
+  CONSTRAINT `FK_x_code_id` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Dumping data for table mifostenant-reference.x_table_column_code_mappings: ~0 rows (approximately)
+/*!40000 ALTER TABLE `x_table_column_code_mappings` DISABLE KEYS */;
+/*!40000 ALTER TABLE `x_table_column_code_mappings` ENABLE KEYS */;
+/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
+/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
diff --git a/fineract-provider/src/main/webapp/META-INF/context.xml b/fineract-provider/src/main/webapp/META-INF/context.xml
new file mode 100644
index 0000000..ab9567a
--- /dev/null
+++ b/fineract-provider/src/main/webapp/META-INF/context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public 
+	License, v. 2.0. If a copy of the MPL was not distributed with this file, 
+	You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<Context>
+	<ResourceLink name="jdbc/mifosplatform-tenants" global="jdbc/mifosplatform-tenants"
+		type="javax.sql.DataSource" />
+</Context>
\ No newline at end of file
diff --git a/fineract-provider/src/main/webapp/WEB-INF/web.xml b/fineract-provider/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..37cc323
--- /dev/null
+++ b/fineract-provider/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+	id="fineract-platform-provider" version="3.0">
+
+	<display-name>Fineract Platform</display-name>
+	<description>A provider of microfinance services which applications can
+		consume once authenticated and authorized.</description>
+
+
+    <!--  =========================================================================
+            web.xml is no longer used since the switch to Spring Boot.
+
+            org.apache.fineract.infrastructure.core.boot.war.WebApplicationInitializerConfiguration
+            and org.apache.fineract.infrastructure.core.boot.WebXmlConfiguration take its place now.
+          =========================================================================
+      -->
+
+
+
+</web-app>
\ No newline at end of file
diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/BatchBuilder.java b/fineract-provider/src/test/java/org/apache/fineract/batch/BatchBuilder.java
new file mode 100644
index 0000000..650dfe7
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/batch/BatchBuilder.java
@@ -0,0 +1,67 @@
+/**
+ * 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.batch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.service.BatchApiService;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Runs a unit test for BatchApiResource by mocking the BatchRequest and
+ * BatchApiService objects.
+ * 
+ * @author Rishabh Shukla
+ */
+public class BatchBuilder {
+
+    // Verify a non-empty response by BatchApiResource.
+    @Test
+    public void batchApiTest() {
+
+        // Mock a BatchRequest objects
+        final BatchRequest batchTest = Mockito.mock(BatchRequest.class);
+
+        // Mock a BatchApiService object
+        final BatchApiService serviceTest = Mockito.mock(BatchApiService.class);
+
+        // Mock a UriInfo object
+        final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+
+        final List<BatchRequest> requestList = new ArrayList<>();
+        requestList.add(batchTest);
+
+        // Call the BatchApiService using mocked objects
+        final List<BatchResponse> result = serviceTest.handleBatchRequestsWithoutEnclosingTransaction(requestList, uriInfo);
+
+        // Verifies whether handleBatchRequests() function of BatchApiService
+        // was called
+        Mockito.verify(serviceTest).handleBatchRequestsWithoutEnclosingTransaction(requestList, uriInfo);
+
+        // Verifies a non-empty response by the BatchApiResource
+        Assert.assertNotNull(result);
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderTest.java b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderTest.java
new file mode 100644
index 0000000..811845b
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderTest.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.commands.provider;
+
+import org.apache.fineract.commands.exception.UnsupportedCommandException;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.commands.provider.CommandHandlerProvider;
+import org.apache.fineract.infrastructure.configuration.spring.TestsWithoutDatabaseAndNoJobsConfiguration;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+@ActiveProfiles("basicauth")
+@ContextConfiguration(classes = TestsWithoutDatabaseAndNoJobsConfiguration.class)
+public class CommandHandlerProviderTest {
+
+    @Autowired
+    private CommandHandlerProvider commandHandlerProvider;
+
+    public CommandHandlerProviderTest() {
+        super();
+    }
+
+    @Test
+    public void shouldRegisterHandler() {
+        try {
+            final Long testCommandId = 815L;
+
+            final NewCommandSourceHandler registeredHandler = this.commandHandlerProvider.getHandler("HUMAN", "UPDATE");
+
+            final CommandProcessingResult result =
+                    registeredHandler.processCommand(
+                            JsonCommand.fromExistingCommand(testCommandId, null, null, null, null, null, null, null, null));
+            Assert.assertEquals(testCommandId, result.commandId());
+        } catch (UnsupportedCommandException ucex) {
+            Assert.fail();
+        }
+    }
+
+    @Test(expected = UnsupportedCommandException.class)
+    public void shouldThrowUnsupportedCommandException() {
+        this.commandHandlerProvider.getHandler("WHATEVER", "DOSOMETHING");
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/provider/ValidCommandHandler.java b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/ValidCommandHandler.java
new file mode 100644
index 0000000..9fc2c7e
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/ValidCommandHandler.java
@@ -0,0 +1,39 @@
+/**
+ * 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.commands.provider;
+
+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.stereotype.Component;
+
+@Component
+@CommandType(entity = "HUMAN", action = "UPDATE")
+public class ValidCommandHandler implements NewCommandSourceHandler {
+
+    public ValidCommandHandler() {
+        super();
+    }
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return CommandProcessingResult.commandOnlyResult(command.commandId());
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/common/RestAssuredFixture.java b/fineract-provider/src/test/java/org/apache/fineract/common/RestAssuredFixture.java
new file mode 100644
index 0000000..92f1e7a
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/common/RestAssuredFixture.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.common;
+
+import com.google.common.base.Preconditions;
+import com.jayway.restassured.RestAssured;
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * REST integration tests helper. Intended to encapsulate the current
+ * RestAssured-based implementation as private. May be REST integration tests
+ * should instead be written using e.g. Spring's TestRestTemplate or Square's
+ * Retrofit (already used in the Mifos X Android client).
+ */
+public class RestAssuredFixture {
+
+	private final int httpPort;
+
+	private ResponseSpecification responseSpec;
+	private RequestSpecification requestSpec;
+
+	public RestAssuredFixture(int httpPort) {
+		super();
+		this.httpPort = httpPort;
+		Utils.initializeRESTAssured();
+		RestAssured.port = httpPort;
+		this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+		this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+		this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+	}
+
+	public <T> T httpGet(String apiPath, String jsonAttributeToGetBack) {
+		return Utils.performServerGet(this.requestSpec, this.responseSpec, getApiPath(apiPath), jsonAttributeToGetBack);
+	}
+
+	public <T> T httpGet(String apiPath) {
+		return httpGet(apiPath, "");
+	}
+
+	private String getApiPath(String apiPath) {
+        Preconditions.checkArgument(apiPath.startsWith("/"), "trailingApiUrl must start with slash: " + apiPath);
+        return "/fineract-provider/api/v1" + apiPath + "?tenantIdentifier=default";
+	}
+
+	protected String getApiUrl(String apiPath) {
+		return "http://localhost:" + httpPort + getApiPath(apiPath);
+    }
+
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/common/Utils.java b/fineract-provider/src/test/java/org/apache/fineract/common/Utils.java
new file mode 100644
index 0000000..60fa476
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/common/Utils.java
@@ -0,0 +1,134 @@
+/**
+ * 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.common;
+
+import static com.jayway.restassured.RestAssured.given;
+import static com.jayway.restassured.path.json.JsonPath.from;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.Random;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.conn.HttpHostConnectException;
+
+import com.jayway.restassured.RestAssured;
+import com.jayway.restassured.path.json.JsonPath;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Util for RestAssured tests. This class here in src/test is copy/pasted :(
+ * from and identical to the one in src/integrationTest; please keep it in sync.
+ * The cunning plan is that, now that we have Spring Boot + MariaDB4j,
+ * eventually do completely away with src/integrationTest and have only
+ * src/test.. can you help? ;)
+ */
+@SuppressWarnings("unchecked")
+public class Utils {
+
+    public static final String TENANT_IDENTIFIER = "tenantIdentifier=default";
+
+    private static final String LOGIN_URL = "/fineract-provider/api/v1/authentication?username=mifos&password=password&" + TENANT_IDENTIFIER;
+
+    public static void initializeRESTAssured() {
+        RestAssured.baseURI = "https://localhost";
+        RestAssured.port = 8443;
+        RestAssured.keystore("src/main/resources/keystore.jks", "openmf");
+    }
+
+    public static String loginIntoServerAndGetBase64EncodedAuthenticationKey() {
+        try {
+            System.out.println("-----------------------------------LOGIN-----------------------------------------");
+            final String json = RestAssured.post(LOGIN_URL).asString();
+            assertThat("Failed to login into fineract platform", StringUtils.isBlank(json), is(false));
+            return JsonPath.with(json).get("base64EncodedAuthenticationKey");
+        } catch (final Exception e) {
+            if (e instanceof HttpHostConnectException) {
+                final HttpHostConnectException hh = (HttpHostConnectException) e;
+                fail("Failed to connect to fineract platform:" + hh.getMessage());
+            }
+
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String getURL, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
+        return (T) from(json).get(jsonAttributeToGetBack);
+    }
+
+    public static <T> T performServerPost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String postURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).log().ifError().when().post(postURL)
+                .andReturn().asString();
+        if (jsonAttributeToGetBack == null) { return (T) json; }
+        return (T) from(json).get(jsonAttributeToGetBack);
+    }
+
+    public static <T> T performServerPut(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String putURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).log().ifError().when().put(putURL)
+                .andReturn().asString();
+        return (T) from(json).get(jsonAttributeToGetBack);
+    }
+
+    public static <T> T performServerDelete(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String deleteURL, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().delete(deleteURL).andReturn()
+                .asString();
+        return (T) from(json).get(jsonAttributeToGetBack);
+    }
+
+    public static String convertDateToURLFormat(String dateToBeConvert) {
+        SimpleDateFormat oldFormat = new SimpleDateFormat("dd MMMMMM yyyy", Locale.US);
+        SimpleDateFormat newFormat = new SimpleDateFormat("yyyy-MM-dd");
+        String reformattedStr = "";
+        try {
+            reformattedStr = newFormat.format(oldFormat.parse(dateToBeConvert));
+        } catch (final ParseException e) {
+            e.printStackTrace();
+        }
+        return reformattedStr;
+    }
+
+    public static String randomStringGenerator(final String prefix, final int len, final String sourceSetString) {
+        final int lengthOfSource = sourceSetString.length();
+        final Random rnd = new Random();
+        final StringBuilder sb = new StringBuilder(len);
+        for (int i = 0; i < len; i++) {
+            sb.append((sourceSetString).charAt(rnd.nextInt(lengthOfSource)));
+        }
+        return (prefix + (sb.toString()));
+    }
+
+    public static String randomStringGenerator(final String prefix, final int len) {
+        return randomStringGenerator(prefix, len, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    }
+
+    public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
+        return randomStringGenerator(prefix, lenOfRandomSuffix);
+    }
+
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/SpringConfigurationTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/SpringConfigurationTest.java
new file mode 100644
index 0000000..eeebc01
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/SpringConfigurationTest.java
@@ -0,0 +1,57 @@
+/**
+ * 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.configuration.spring;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+/**
+ * This integration test ensure that the "production" Spring XML configuration
+ * files (appContext.xml & Co.) are valid. It does not need any database for
+ * that.
+ *
+ * Note: For a simple test like this, contrary to
+ * AbstractSpringBootWithMariaDB4jIntegrationTest, there is no need to use
+ * Boot's SpringApplicationConfiguration here, instead the simpler classic Sring
+ * core ContextConfiguration is sufficient (as long as the
+ * TestsWithoutDatabaseAndNoJobsConfiguration used extends
+ * AbstractApplicationConfiguration).
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+@ActiveProfiles("basicauth")
+@ContextConfiguration(classes = TestsWithoutDatabaseAndNoJobsConfiguration.class)
+public class SpringConfigurationTest {
+
+    /**
+     * This tests if "Aal izz well" (i.e. if we can start-up with the [almost]
+     * "production" Spring XML configuration) by doing nothing - it doesn't have
+     * to, as the SpringJUnit4ClassRunner with this @ContextConfiguration will
+     * automatically fail if e.g. there is any invalid Spring XML, an invalid
+     * bean definition somewhere or anything like that.
+     * 
+     * @see https://www.google.ch/search?q=Aal+izz+well
+     */
+    @Test
+    public void testSpringXMLConfiguration() {}
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/TestsWithoutDatabaseAndNoJobsConfiguration.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/TestsWithoutDatabaseAndNoJobsConfiguration.java
new file mode 100644
index 0000000..4b70943
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/TestsWithoutDatabaseAndNoJobsConfiguration.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.infrastructure.configuration.spring;
+
+import javax.sql.DataSource;
+
+import org.apache.fineract.infrastructure.core.boot.AbstractApplicationConfiguration;
+import org.apache.fineract.infrastructure.core.service.TenantDatabaseUpgradeService;
+import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Spring @Configuration which does not require a running database. It also does
+ * not load any job configuration (as they are in the DB), thus nor starts any
+ * background jobs. For some integration tests, this may be perfectly sufficient
+ * (and faster to run such tests).
+ */
+public class TestsWithoutDatabaseAndNoJobsConfiguration extends AbstractApplicationConfiguration {
+
+    /**
+     * Override TenantDatabaseUpgradeService binding, because the real one has a @PostConstruct
+     * upgradeAllTenants() which accesses the database on start-up.
+     */
+    @Bean
+    public TenantDatabaseUpgradeService tenantDatabaseUpgradeService() {
+        return new TenantDatabaseUpgradeService(null, null, null) {
+            @Override
+            public void upgradeAllTenants() {
+                // NOOP
+            }
+        };
+    }
+
+    /**
+     * Override JobRegisterService binding, because the real
+     * JobRegisterServiceImpl has a @PostConstruct loadAllJobs() which accesses
+     * the database on start-up.
+     */
+    @Bean
+    public JobRegisterService jobRegisterServiceImpl() {
+        JobRegisterService mockJobRegisterService = Mockito.mock(JobRegisterService.class);
+        return mockJobRegisterService;
+    }
+
+    /**
+     * DataSource with Mockito RETURNS_MOCKS black magic.
+     */
+    @Bean
+    public DataSource tenantDataSourceJndi() {
+        DataSource mockDataSource = Mockito.mock(DataSource.class, Mockito.RETURNS_MOCKS);
+        return mockDataSource;
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/boot/tests/AbstractSpringBootWithMariaDB4jIntegrationTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/boot/tests/AbstractSpringBootWithMariaDB4jIntegrationTest.java
new file mode 100644
index 0000000..d538186
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/boot/tests/AbstractSpringBootWithMariaDB4jIntegrationTest.java
@@ -0,0 +1,22 @@
+package org.apache.fineract.infrastructure.core.boot.tests;
+
+import org.apache.fineract.ServerWithMariaDB4jApplication;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.IntegrationTest;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = ServerWithMariaDB4jApplication.Configuration.class)
+@WebAppConfiguration
+@ActiveProfiles("basicauth")
+@IntegrationTest({ "server.port=0", "management.port=0", "mariaDB4j.port=0", "mariaDB4j.dataDir=null" })
+public abstract class AbstractSpringBootWithMariaDB4jIntegrationTest {
+
+	// do NOT put any helper methods here!
+	// it's much better to use composition instead of inheritance
+	// so write a test util ("fixture") and use it as a field in your test
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/boot/tests/EmbeddedTomcatWithSSLConfigurationTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/boot/tests/EmbeddedTomcatWithSSLConfigurationTest.java
new file mode 100644
index 0000000..b1eea9e
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/boot/tests/EmbeddedTomcatWithSSLConfigurationTest.java
@@ -0,0 +1,38 @@
+package org.apache.fineract.infrastructure.core.boot.tests;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.fineract.infrastructure.core.boot.EmbeddedTomcatWithSSLConfiguration;
+import org.junit.Test;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+public class EmbeddedTomcatWithSSLConfigurationTest {
+
+    @Test
+    public void testGetFileWithFileResource() throws IOException {
+        // Test class probably isn't in a JAR
+        checkClassResource(getClass());
+    }
+
+    @Test
+    public void testGetFileWithClasspathResource() throws IOException {
+        // Spring Resource class probably is in a JAR
+        File f1 = checkClassResource(Resource.class);
+        f1.delete();
+        checkClassResource(Resource.class);
+    }
+
+    protected File checkClassResource(Class<?> clazz) throws IOException {
+        String testClasspathResourcePath = clazz.getCanonicalName().replace('.', '/') + ".class";
+        Resource r = new ClassPathResource(testClasspathResourcePath);
+        File f = new EmbeddedTomcatWithSSLConfiguration().getFile(r);
+        assertTrue(f.exists());
+        f = new EmbeddedTomcatWithSSLConfiguration().getFile(r);
+        assertTrue(f.exists());
+        return f;
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/boot/tests/SpringBootServerLoginTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/boot/tests/SpringBootServerLoginTest.java
new file mode 100644
index 0000000..dc70ffb
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/boot/tests/SpringBootServerLoginTest.java
@@ -0,0 +1,31 @@
+package org.apache.fineract.infrastructure.core.boot.tests;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.ServerApplication;
+import org.apache.fineract.common.RestAssuredFixture;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * This is an integration test for the Spring Boot launch stuff.
+ * 
+ * @see ServerApplication
+ */
+public class SpringBootServerLoginTest extends AbstractSpringBootWithMariaDB4jIntegrationTest {
+
+    protected RestAssuredFixture util;
+
+    @Test
+    @Ignore("Failing on Cloubees")
+    public void hasPlatformStarted() {
+        util = new RestAssuredFixture(8443);
+        List<Map<String, String>> response = util.httpGet("/users");
+        assertThat(response.get(0).get("username"), is("mifos"));
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/test/java/org/apache/fineract/mix/report/service/ReadTaxonomyMappingServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/mix/report/service/ReadTaxonomyMappingServiceImplTest.java
new file mode 100644
index 0000000..963725d
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/mix/report/service/ReadTaxonomyMappingServiceImplTest.java
@@ -0,0 +1,52 @@
+/**
+ * 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.mix.report.service;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.mix.service.XBRLResultServiceImpl;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ReadTaxonomyMappingServiceImplTest {
+
+    private XBRLResultServiceImpl readService;
+
+    @Before
+    public void setUp() throws Exception {
+        final RoutingDataSource dataSource = Mockito.mock(RoutingDataSource.class);
+        this.readService = new XBRLResultServiceImpl(dataSource, null, null);
+
+    }
+
+    @Test
+    public void shouldCorrectlyGetGLCode() {
+        final ArrayList<String> result = this.readService.getGLCodes("{12000}+{11000}");
+        assertEquals("12000", result.get(0));
+        assertEquals("11000", result.get(1));
+    }
+
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/mix/report/service/XBRLBuilderTest.java b/fineract-provider/src/test/java/org/apache/fineract/mix/report/service/XBRLBuilderTest.java
new file mode 100644
index 0000000..e7f7d80
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/mix/report/service/XBRLBuilderTest.java
@@ -0,0 +1,95 @@
+/**
+ * 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.mix.report.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.util.HashMap;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.fineract.mix.data.MixTaxonomyData;
+import org.apache.fineract.mix.data.NamespaceData;
+import org.apache.fineract.mix.service.NamespaceReadPlatformServiceImpl;
+import org.apache.fineract.mix.service.XBRLBuilder;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class XBRLBuilderTest {
+
+    @Mock
+    private NamespaceReadPlatformServiceImpl readNamespaceService;
+    @InjectMocks
+    private final XBRLBuilder xbrlBuilder = new XBRLBuilder();
+
+    @Before
+    public void setUp() throws Exception {
+
+        this.readNamespaceService = Mockito.mock(NamespaceReadPlatformServiceImpl.class);
+        when(this.readNamespaceService.retrieveNamespaceByPrefix(Matchers.anyString())).thenReturn(
+                new NamespaceData(1l, "mockedprefix", "mockedurl"));
+
+    }
+
+    @SuppressWarnings("null")
+    @Test
+    public void shouldCorrectlyBuildMap() {
+
+        final HashMap<MixTaxonomyData, BigDecimal> map = new HashMap<MixTaxonomyData, BigDecimal>();
+        final MixTaxonomyData data1 = Mockito.mock(MixTaxonomyData.class);
+        when(data1.getName()).thenReturn("Assets");
+        map.put(data1, new BigDecimal(10000));
+        final String result = this.xbrlBuilder.build(map, Date.valueOf("2005-11-11"), Date.valueOf("2013-07-17"), "USD");
+        System.out.println(result);
+        NodeList nodes = null;
+        try {
+            nodes = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(result.getBytes()))
+                    .getElementsByTagName("Assets");
+        } catch (final SAXException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (final IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (final ParserConfigurationException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        assertNotNull(nodes);
+        assertNotNull(nodes.item(0));
+        assertEquals("Assets", nodes.item(0).getNodeName());
+    }
+
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/AdhikarLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/AdhikarLoanRepaymentScheduleTransactionProcessorTest.java
new file mode 100644
index 0000000..b5a224a
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/AdhikarLoanRepaymentScheduleTransactionProcessorTest.java
@@ -0,0 +1,226 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount;
+
+import java.lang.reflect.Field;
+import java.math.RoundingMode;
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@SuppressWarnings("unused")
+@RunWith(MockitoJUnitRunner.class)
+public class AdhikarLoanRepaymentScheduleTransactionProcessorTest {
+
+    // class under test
+    private RBILoanRepaymentScheduleTransactionProcessor processor;
+
+    //
+    private final LocalDate july2nd = new LocalDate(2012, 7, 2);
+    private final MonetaryCurrency usDollars = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
+    private List<LoanRepaymentScheduleInstallment> installments;
+
+    @Before
+    public void setUpForEachTestCase() throws Exception {
+
+        Field field = MoneyHelper.class.getDeclaredField("roundingMode");
+        field.setAccessible(true);
+        field.set(null, RoundingMode.HALF_EVEN);
+        this.installments = LoanScheduleTestDataHelper.createSimpleLoanSchedule(this.july2nd, this.usDollars);
+
+        this.processor = new RBILoanRepaymentScheduleTransactionProcessor();
+    }
+
+    /**
+     * Scenario 1: Given no overdue installments, current interest due is paid
+     * before principal.
+     */
+    @Test
+    public void givenNoOverdueInstallmentsOnTimeRepaymentPaysOffInterestDueFirst() {
+
+        // // setup
+        // Money repaymentAmount = new
+        // MoneyBuilder().with(usDollars).with("100.00").build();
+        // LoanTransaction onTimePartialRepayment = new
+        // LoanTransactionBuilder().repayment().with(july2nd).with(repaymentAmount).build();
+        //
+        // // execute test
+        // processor.handleTransaction(onTimePartialRepayment, usDollars,
+        // installments);
+        //
+        // // verification of derived fields on installments
+        // LoanRepaymentScheduleInstallment firstInstallment =
+        // this.installments.get(0);
+        // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"),
+        // interestOf("100.00"), interestWaivedOf("0.00"), firstInstallment,
+        // usDollars);
+        //
+        // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"),
+        // interestOf("100.00"), onTimePartialRepayment, usDollars);
+    }
+
+    // /**
+    // * Scenario 2: Given overdue interest exists on overdue installment, a
+    // late
+    // * repayment results in overdue interest and current interest being paid
+    // * before principal component for overdue or current installments.
+    // */
+    // @Test
+    // public void
+    // givenOverdueInterestOnOverdueInstallmentsLateRepaymentPaysOffOverdueInterestAndCurrentInterestDue()
+    // {
+    //
+    // // setup
+    // Money repaymentAmount = new
+    // MoneyBuilder().with(usDollars).with("300.00").build();
+    // LoanTransaction lateRepayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd.plusMonths(1)).with(repaymentAmount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(lateRepayment, usDollars, installments);
+    //
+    // // verification of derived fields on installments
+    // LoanRepaymentScheduleInstallment firstInstallment =
+    // this.installments.get(0);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"),
+    // interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment,
+    // usDollars);
+    //
+    // LoanRepaymentScheduleInstallment secondInstallment =
+    // this.installments.get(1);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"),
+    // interestOf("100.00"), interestWaivedOf("0.00"), secondInstallment,
+    // usDollars);
+    //
+    // LoanRepaymentScheduleInstallment thirdInstallment =
+    // this.installments.get(2);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"),
+    // interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment,
+    // usDollars);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"),
+    // interestOf("300.00"), lateRepayment, usDollars);
+    // }
+    //
+    // /**
+    // * Scenario 3: Given an overdue installment with overdue interest and
+    // principal, a late
+    // * repayment results in overdue interest and current interest being paid
+    // * before principal component for overdue or current installments.
+    // */
+    // @Test
+    // public void
+    // givenAFullyOverdueInstallmentALateRepaymentShouldPayOffOverdueInterestAndCurrentInterestDueFirstThenOverduePrincipal()
+    // {
+    //
+    // // setup
+    // Money repaymentAmount = new
+    // MoneyBuilder().with(usDollars).with("900.00").build();
+    // LoanTransaction lateRepayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd.plusMonths(1)).with(repaymentAmount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(lateRepayment, usDollars, installments);
+    //
+    // // verification of derived fields on installments
+    // LoanRepaymentScheduleInstallment firstInstallment =
+    // this.installments.get(0);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("500.00"),
+    // interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment,
+    // usDollars);
+    //
+    // LoanRepaymentScheduleInstallment secondInstallment =
+    // this.installments.get(1);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"),
+    // interestOf("200.00"), interestWaivedOf("0.00"), secondInstallment,
+    // usDollars);
+    //
+    // LoanRepaymentScheduleInstallment thirdInstallment =
+    // this.installments.get(2);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"),
+    // interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment,
+    // usDollars);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("500.00"),
+    // interestOf("400.00"), lateRepayment, usDollars);
+    // }
+    //
+    // private void assertThatLoanTransactionHasDerivedFieldsOf(
+    // final String principal,
+    // final String interest,
+    // final LoanTransaction transaction, final MonetaryCurrency currency) {
+    //
+    // Money expectedPrincipalPortion = new
+    // MoneyBuilder().with(currency).with(principal).build();
+    // assertThat("Principal portion of transaction not as expected: ",
+    // transaction.getPrincipalPortion(currency).toString(),
+    // is(expectedPrincipalPortion.toString()));
+    //
+    // Money expectedInterestPortion = new
+    // MoneyBuilder().with(currency).with(interest).build();
+    // assertThat("Interest portion of transaction not as expected: ",
+    // transaction.getInterestPortion(currency).toString(),
+    // is(expectedInterestPortion.toString()));
+    // }
+    //
+    // private void assertThatLoanInstallmentHasDerivedFieldsOf(
+    // final String principal,
+    // final String interest,
+    // final String interestWaived,
+    // final LoanRepaymentScheduleInstallment installment, final
+    // MonetaryCurrency currency) {
+    //
+    // Money expectedPrincipalCompleted = new
+    // MoneyBuilder().with(currency).with(principal).build();
+    // assertThat("principal not as expected",
+    // installment.getPrincipalCompleted(currency).toString(),
+    // is(expectedPrincipalCompleted.toString()));
+    //
+    // Money expectedInterestCompleted = new
+    // MoneyBuilder().with(currency).with(interest).build();
+    // assertThat("interest completed not as expected: ",installment.getInterestCompleted(currency).toString(),
+    // is(expectedInterestCompleted.toString()));
+    //
+    // Money expectedInterestWaived = new
+    // MoneyBuilder().with(currency).with(interestWaived).build();
+    // assertThat("interest waived not as expected: ",
+    // installment.getInterestWaived(currency).toString(),
+    // is(expectedInterestWaived.toString()));
+    // }
+    //
+    // private String principalOf(final String value) {
+    // return value;
+    // }
+    //
+    // private String interestOf(final String value) {
+    // return value;
+    // }
+    //
+    // private String interestWaivedOf(final String value) {
+    // return value;
+    // }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest.java
new file mode 100644
index 0000000..418ac89
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest.java
@@ -0,0 +1,630 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount;
+
+import java.lang.reflect.Field;
+import java.math.RoundingMode;
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor;
+import org.joda.time.LocalDate;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@SuppressWarnings("unused")
+@RunWith(MockitoJUnitRunner.class)
+public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest {
+
+    // class under test
+    private HeavensFamilyLoanRepaymentScheduleTransactionProcessor processor;
+
+    //
+    private final LocalDate july2nd = new LocalDate(2012, 7, 2);
+    private final MonetaryCurrency usDollars = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
+    private List<LoanRepaymentScheduleInstallment> installments;
+
+    @Before
+    public void setUpForEachTestCase() throws Exception {
+
+        Field field = MoneyHelper.class.getDeclaredField("roundingMode");
+        field.setAccessible(true);
+        field.set(null, RoundingMode.HALF_EVEN);
+        this.installments = LoanScheduleTestDataHelper.createSimpleLoanSchedule(this.july2nd, this.usDollars);
+
+        this.processor = new HeavensFamilyLoanRepaymentScheduleTransactionProcessor();
+    }
+
+    /**
+     * Scenario 1: Single transaction which is less than the interest component
+     * of the first installment.
+     * 
+     * Expectation: - First installment shows interest completed equal to that
+     * of transactions, zero principal completed. (payment order interest,
+     * principal) - transaction has interest portion equal to transaction
+     * amount, and zero principal portion
+     */
+    @Test
+    public void givenSingleOnTimeLoanTransactionShouldPayoffInterestComponentFirst() {
+
+        // // setup
+        // LoanRepaymentScheduleInstallment singleInstallment =
+        // this.installments.get(0);
+        //
+        // Money oneHundredDollars = new
+        // MoneyBuilder().with(usDollars).with("100.00").build();
+        // LoanTransaction onTimePartialPayment = new
+        // LoanTransactionBuilder().repayment().with(july2nd).with(oneHundredDollars).build();
+        //
+        // // execute test
+        // processor.handleTransaction(onTimePartialPayment, usDollars,
+        // installments);
+        //
+        // // verification of derived fields on installments
+        // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("0.00",
+        // "100.00", singleInstallment);
+        // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("0.00",
+        // "100.00", onTimePartialPayment);
+    }
+
+    // /**
+    // * Scenario 2: Two transactions which is less than the interest component
+    // of the first installment.
+    // *
+    // * Expectation:
+    // * - First installment shows interest completed equal to that of
+    // transactions, zero principal completed. (payment order interest,
+    // principal)
+    // * - transactions has interest portion equal to transaction amount, and
+    // zero principal portion
+    // */
+    // @Test
+    // public void
+    // givenMultipleOnTimeLoanTransactionShouldPayoffInterestComponentFirst() {
+    //
+    // // setup
+    // LoanRepaymentScheduleInstallment singleInstallment =
+    // this.installments.get(0);
+    //
+    // Money oneHundredDollars = new
+    // MoneyBuilder().with(usDollars).with("100.00").build();
+    // LoanTransaction onTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(oneHundredDollars).build();
+    //
+    // Money fiftyDollars = new
+    // MoneyBuilder().with(usDollars).with("50.00").build();
+    // LoanTransaction secondOnTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(fiftyDollars).build();
+    //
+    //
+    // // execute test
+    // processor.handleTransaction(onTimePartialPayment, usDollars,
+    // installments);
+    // processor.handleTransaction(secondOnTimePartialPayment, usDollars,
+    // installments);
+    //
+    // // verification of derived fields on installments
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("0.00",
+    // "150.00", singleInstallment);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("0.00",
+    // "100.00", onTimePartialPayment);
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("0.00",
+    // "50.00", secondOnTimePartialPayment);
+    // }
+    //
+    // /**
+    // * Scenario 3: Single transaction which is greater than the interest
+    // component of the first installment but less than the total due for
+    // installment.
+    // *
+    // * Expectation:
+    // * - First installment shows interest completed equal to that interest
+    // due, remainder is against principal completed. (payment order interest,
+    // principal)
+    // * - transaction has interest portion equal to interest expected and
+    // principal portion of remainder.
+    // */
+    // @Test
+    // public void
+    // givenSingleOnTimeLoanTransactionShouldPayoffInterestThenPrincipalComponents()
+    // {
+    //
+    // // setup
+    // LoanRepaymentScheduleInstallment singleInstallment =
+    // this.installments.get(0);
+    //
+    // Money amount = new MoneyBuilder().with(usDollars).with("300.00").build();
+    // LoanTransaction onTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(onTimePartialPayment, usDollars,
+    // installments);
+    //
+    // // verification of derived fields on installments
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("100.00",
+    // "200.00", singleInstallment);
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("100.00",
+    // "200.00", onTimePartialPayment);
+    // }
+    //
+    // /**
+    // * Scenario 4: Single transaction which is greater than the interest
+    // component of the first installment but less than the total due for
+    // installment.
+    // *
+    // * Expectation:
+    // * - First installment shows interest completed equal to that interest
+    // due, remainder is against principal completed. (payment order interest,
+    // principal)
+    // * - transaction has interest portion equal to interest expected and
+    // principal portion of remainder.
+    // */
+    // @Test
+    // public void
+    // givenMultipleOnTimeLoanTransactionShouldPayoffInterestThenPrincipalComponents()
+    // {
+    //
+    // // setup
+    // LoanRepaymentScheduleInstallment singleInstallment =
+    // this.installments.get(0);
+    //
+    // Money amount = new MoneyBuilder().with(usDollars).with("300.00").build();
+    // LoanTransaction onTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
+    //
+    // Money secondTransactionAmount = new
+    // MoneyBuilder().with(usDollars).with("50.00").build();
+    // LoanTransaction secondOnTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(secondTransactionAmount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(onTimePartialPayment, usDollars,
+    // installments);
+    // processor.handleTransaction(secondOnTimePartialPayment, usDollars,
+    // installments);
+    //
+    // // verification of derived fields on installments
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("150.00",
+    // "200.00", singleInstallment);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("100.00",
+    // "200.00", onTimePartialPayment);
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("50.00",
+    // "0.00", secondOnTimePartialPayment);
+    // }
+    //
+    // /**
+    // * Scenario 5:Single transaction which is equal to the total due for
+    // installment.
+    // *
+    // * Expectation:
+    // * - First installment shows interest completed and principal completed
+    // equal to their due amounts. (payment order interest, principal)
+    // * - transaction has interest portion and principal portions
+    // */
+    // @Test
+    // public void
+    // givenSingleFullOnTimeLoanTransactionShouldPayoffInterestThenPrincipalComponentInFull()
+    // {
+    //
+    // // setup
+    // LoanRepaymentScheduleInstallment singleInstallment =
+    // this.installments.get(0);
+    //
+    // Money amount = new
+    // MoneyBuilder().with(usDollars).with("1200.00").build();
+    // LoanTransaction onTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(onTimePartialPayment, usDollars,
+    // installments);
+    //
+    // // verification of derived fields on installments
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00",
+    // "200.00", singleInstallment);
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("1000.00",
+    // "200.00", onTimePartialPayment);
+    // }
+    //
+    // /**
+    // * Scenario 6: Multiple transactions which together equal to the total due
+    // for installment.
+    // *
+    // * Expectation:
+    // * - First installment shows interest completed and principal completed
+    // equal to their due amounts. (payment order interest, principal)
+    // * - transaction has interest portion and principal portions
+    // */
+    // @Test
+    // public void
+    // givenMuliptleOnTimeLoanTransactionsThatInFullShouldPayoffInterestThenPrincipalComponentInFull()
+    // {
+    //
+    // // setup
+    // LoanRepaymentScheduleInstallment singleInstallment =
+    // this.installments.get(0);
+    //
+    // Money amount = new MoneyBuilder().with(usDollars).with("600.00").build();
+    // LoanTransaction onTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
+    //
+    // Money secondTransactionAmount = new
+    // MoneyBuilder().with(usDollars).with("600.00").build();
+    // LoanTransaction secondOnTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(secondTransactionAmount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(onTimePartialPayment, usDollars,
+    // installments);
+    // processor.handleTransaction(secondOnTimePartialPayment, usDollars,
+    // installments);
+    //
+    // // verification of derived fields on installments
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00",
+    // "200.00", singleInstallment);
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("400.00",
+    // "200.00", onTimePartialPayment);
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("600.00",
+    // "0.00", secondOnTimePartialPayment);
+    // }
+    //
+    // /**
+    // * Scenario 7: Single transaction which is greater than total due for
+    // first installment.
+    // *
+    // * Expectation:
+    // * - First installment shows interest completed and principal completed
+    // equal to their due amounts. (payment order interest, principal)
+    // * - Second installment shows principal completed equal to the remaining
+    // amount
+    // * - transaction has interest portion and principal portions, the
+    // principal portion is the sum of principal completed for first and second
+    // installments
+    // */
+    // @Test
+    // public void
+    // givenSingleOnTimeLoanTransactionThatOverpaysInstallmentShouldPayoffInterestThenPrincipalComponentInFullOfFirstInstallmentWithRemainingOverpaymentAgainstPrincipalOfNextInstallment()
+    // {
+    //
+    // // setup
+    // Money amount = new
+    // MoneyBuilder().with(usDollars).with("1300.00").build();
+    // LoanTransaction onTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(onTimePartialPayment, usDollars,
+    // installments);
+    //
+    // // verification of derived fields on installments
+    // LoanRepaymentScheduleInstallment firstInstallment =
+    // this.installments.get(0);
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00",
+    // "200.00", firstInstallment);
+    //
+    // LoanRepaymentScheduleInstallment secondInstallment =
+    // this.installments.get(1);
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("100.00",
+    // "0.00", secondInstallment);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("1100.00",
+    // "200.00", onTimePartialPayment);
+    // }
+    //
+    // /**
+    // * Scenario 8: Multiple transactions which together are greater than total
+    // due for first installment.
+    // *
+    // * Expectation:
+    // * - First installment shows interest completed and principal completed
+    // equal to their due amounts. (payment order interest, principal)
+    // * - Second installment shows principal completed equal to the remaining
+    // amount
+    // */
+    // @Test
+    // public void
+    // givenMultipleOnTimeLoanTransactionThatOverpaysInstallmentShouldPayoffInterestThenPrincipalComponentInFullOfFirstInstallmentWithRemainingOverpaymentAgainstPrincipalOfNextInstallment()
+    // {
+    //
+    // // setup
+    // Money amount = new MoneyBuilder().with(usDollars).with("600.00").build();
+    // LoanTransaction onTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
+    //
+    // Money secondTransactionAmount = new
+    // MoneyBuilder().with(usDollars).with("700.00").build();
+    // LoanTransaction secondOnTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(secondTransactionAmount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(onTimePartialPayment, usDollars,
+    // installments);
+    // processor.handleTransaction(secondOnTimePartialPayment, usDollars,
+    // installments);
+    //
+    // // verification of derived fields on installments
+    // LoanRepaymentScheduleInstallment singleInstallment =
+    // this.installments.get(0);
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00",
+    // "200.00", singleInstallment);
+    //
+    // LoanRepaymentScheduleInstallment secondInstallment =
+    // this.installments.get(1);
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("100.00",
+    // "0.00", secondInstallment);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("400.00",
+    // "200.00", onTimePartialPayment);
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("700.00",
+    // "0.00", secondOnTimePartialPayment);
+    // }
+    //
+    // /**
+    // * Scenario 9: Single transaction which overpays first installment and
+    // second installment with outstanding amount been account as principal in
+    // third installment.
+    // *
+    // * Where an installments principal is paid for fully in advance - the
+    // interest component is waived.
+    // */
+    // @Test
+    // public void
+    // givenOverPaymentShouldPayoffInterestThenPrincipalComponentsOfFirstInstallmentWithRemainingOverpaymentAgainstPrincipalOfAllSubsequentInstallments()
+    // {
+    // // setup
+    // Money amount = new
+    // MoneyBuilder().with(usDollars).with("3000.00").build();
+    // LoanTransaction onTimePartialPayment = new
+    // LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(onTimePartialPayment, usDollars,
+    // installments);
+    //
+    // // verification of derived fields on installments
+    // LoanRepaymentScheduleInstallment firstInstallment =
+    // this.installments.get(0);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"),
+    // interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment,
+    // usDollars);
+    //
+    // LoanRepaymentScheduleInstallment secondInstallment =
+    // this.installments.get(1);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"),
+    // interestOf("0.00"), interestWaivedOf("200.00"), secondInstallment,
+    // usDollars);
+    //
+    // LoanRepaymentScheduleInstallment thirdInstallment =
+    // this.installments.get(2);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("800.00"),
+    // interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment,
+    // usDollars);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("2800.00"),
+    // interestOf("200.00"), onTimePartialPayment, usDollars);
+    // }
+    //
+    // /**
+    // * Scenario 10: Single transaction which is a repayment in advance
+    // */
+    // @Test
+    // public void
+    // givenRepaymentOfInstallmentInAdvanceShouldPayoffOnlyPrincipalComponent()
+    // {
+    // // setup
+    // installments =
+    // LoanScheduleTestDataHelper.createSimpleLoanScheduleWithFirstInstallmentFullyPaid(july2nd,
+    // usDollars);
+    // LoanRepaymentScheduleInstallment firstInstallment =
+    // this.installments.get(0);
+    // LocalDate paymentInAdvanceDate =
+    // firstInstallment.getDueDate().minusDays(1);
+    //
+    // Money amount = new MoneyBuilder().with(usDollars).with("900.00").build();
+    // LoanTransaction paymentInAdvance = new
+    // LoanTransactionBuilder().repayment().with(paymentInAdvanceDate).with(amount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(paymentInAdvance, usDollars, installments);
+    //
+    // // verification of derived fields on installments
+    // LoanRepaymentScheduleInstallment secondInstallment =
+    // this.installments.get(1);
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("900.00",
+    // "0.00", secondInstallment);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("900.00",
+    // "0.00", paymentInAdvance);
+    // }
+    //
+    // /**
+    // * Scenario 11: Single transaction which is a repayment many days early
+    // but still does not qualify for 'in advance'.
+    // */
+    // @Test
+    // public void
+    // givenRepaymentOfInstallmentBeforeDueDateButNotInAdvanceShouldPayoffInterestAndPrincipalComponents()
+    // {
+    // // setup
+    // installments =
+    // LoanScheduleTestDataHelper.createSimpleLoanScheduleWithFirstInstallmentFullyPaid(july2nd,
+    // usDollars);
+    // LoanRepaymentScheduleInstallment firstInstallment =
+    // this.installments.get(0);
+    // LocalDate paymentBeforeDueDateButNotInAdvanceDate =
+    // firstInstallment.getDueDate().plusDays(1);
+    //
+    // Money amount = new MoneyBuilder().with(usDollars).with("900.00").build();
+    // LoanTransaction paymentBeforeDueDateButNotInAdvance = new
+    // LoanTransactionBuilder().repayment().with(paymentBeforeDueDateButNotInAdvanceDate).with(amount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(paymentBeforeDueDateButNotInAdvance,
+    // usDollars, installments);
+    //
+    // // verification of derived fields on installments
+    // LoanRepaymentScheduleInstallment secondInstallment =
+    // this.installments.get(1);
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("700.00",
+    // "200.00", secondInstallment);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("700.00",
+    // "200.00", paymentBeforeDueDateButNotInAdvance);
+    // }
+    //
+    //
+    // /**
+    // * Scenario 12: Single transaction which is a repayment many days early
+    // but still does not qualify for 'in advance'.
+    // */
+    // @Test
+    // public void
+    // givenLateRepaymentOfInstallmentShouldPayoffInterestAndPrincipalComponentsOfOverdueInstallmentsWithNoWaiveOfInterest()
+    // {
+    // // setup
+    // installments =
+    // LoanScheduleTestDataHelper.createSimpleLoanSchedule(july2nd, usDollars);
+    // LoanRepaymentScheduleInstallment firstInstallment =
+    // this.installments.get(0);
+    // LocalDate latePaymentDate = firstInstallment.getDueDate().plusMonths(5);
+    //
+    // Money amount = new
+    // MoneyBuilder().with(usDollars).with("3600.00").build();
+    // LoanTransaction lateRepaymentTransaction = new
+    // LoanTransactionBuilder().repayment().with(latePaymentDate).with(amount).build();
+    //
+    // // execute test
+    // processor.handleTransaction(lateRepaymentTransaction, usDollars,
+    // installments);
+    //
+    // // verification of derived fields on installments
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"),
+    // interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment,
+    // usDollars);
+    //
+    // LoanRepaymentScheduleInstallment secondInstallment =
+    // this.installments.get(1);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"),
+    // interestOf("200.00"), interestWaivedOf("0.00"), secondInstallment,
+    // usDollars);
+    //
+    // LoanRepaymentScheduleInstallment thirdInstallment =
+    // this.installments.get(2);
+    // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"),
+    // interestOf("200.00"), interestWaivedOf("0.00"), thirdInstallment,
+    // usDollars);
+    //
+    // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("3000.00"),
+    // interestOf("600.00"), lateRepaymentTransaction, usDollars);
+    // }
+    //
+    //
+    // private void
+    // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf(
+    // final String principal, final String interest, final LoanTransaction
+    // transaction) {
+    //
+    // Money expectedInterestPortion = new
+    // MoneyBuilder().with(usDollars).with(interest).build();
+    // assertThat(transaction.getInterestPortion(usDollars).toString(),
+    // is(expectedInterestPortion.toString()));
+    //
+    // Money expectedPrincipalPortion = new
+    // MoneyBuilder().with(usDollars).with(principal).build();
+    // assertThat(transaction.getPrincipalPortion(usDollars).toString(),
+    // is(expectedPrincipalPortion.toString()));
+    // }
+    //
+    // private void
+    // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf(final
+    // String principal, final String interest,
+    // final LoanRepaymentScheduleInstallment installment) {
+    // Money expectedInterestCompleted = new
+    // MoneyBuilder().with(usDollars).with(interest).build();
+    // assertThat(installment.getInterestCompleted(usDollars).toString(),
+    // is(expectedInterestCompleted.toString()));
+    //
+    // Money expectedPrincipalCompleted = new
+    // MoneyBuilder().with(usDollars).with(principal).build();
+    // assertThat(installment.getPrincipalCompleted(usDollars).toString(),
+    // is(expectedPrincipalCompleted.toString()));
+    // }
+    //
+    // private void assertThatLoanTransactionHasDerivedFieldsOf(
+    // final String principal,
+    // final String interest,
+    // final LoanTransaction transaction, final MonetaryCurrency currency) {
+    //
+    // Money expectedPrincipalPortion = new
+    // MoneyBuilder().with(currency).with(principal).build();
+    // assertThat("Principal portion of transaction not as expected: ",
+    // transaction.getPrincipalPortion(currency).toString(),
+    // is(expectedPrincipalPortion.toString()));
+    //
+    // Money expectedInterestPortion = new
+    // MoneyBuilder().with(currency).with(interest).build();
+    // assertThat("Interest portion of transaction not as expected: ",
+    // transaction.getInterestPortion(currency).toString(),
+    // is(expectedInterestPortion.toString()));
+    // }
+    //
+    // private void assertThatLoanInstallmentHasDerivedFieldsOf(
+    // final String principal,
+    // final String interest,
+    // final String interestWaived,
+    // final LoanRepaymentScheduleInstallment installment, final
+    // MonetaryCurrency currency) {
+    //
+    // Money expectedPrincipalCompleted = new
+    // MoneyBuilder().with(currency).with(principal).build();
+    // assertThat("principal not as expected",
+    // installment.getPrincipalCompleted(currency).toString(),
+    // is(expectedPrincipalCompleted.toString()));
+    //
+    // Money expectedInterestCompleted = new
+    // MoneyBuilder().with(currency).with(interest).build();
+    // assertThat("interest completed not as expected: ",installment.getInterestCompleted(currency).toString(),
+    // is(expectedInterestCompleted.toString()));
+    //
+    // Money expectedInterestWaived = new
+    // MoneyBuilder().with(currency).with(interestWaived).build();
+    // assertThat("interest waived not as expected: ",
+    // installment.getInterestWaived(currency).toString(),
+    // is(expectedInterestWaived.toString()));
+    // }
+    //
+    // private String principalOf(final String value) {
+    // return value;
+    // }
+    //
+    // private String interestOf(final String value) {
+    // return value;
+    // }
+    //
+    // private String interestWaivedOf(final String value) {
+    // return value;
+    // }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java
new file mode 100644
index 0000000..3e635aa
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
+import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+
+/**
+ * This class is used to keep in one place configurations for setting up
+ * {@link LoanProductRelatedDetail} object used in {@link LoanScheduleGenerator}
+ * 's
+ */
+public class LoanProductRelatedDetailTestHelper {
+
+    public static LoanProductRelatedDetail createSettingsForEqualPrincipalAmortizationQuarterly() {
+
+        final MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
+        final BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
+
+        // 2% per month, 24% per year
+        final BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
+        final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS;
+        final BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24"));
+
+        final InterestMethod interestMethod = InterestMethod.DECLINING_BALANCE;
+        final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
+        final Integer repayEvery = Integer.valueOf(3);
+        final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS;
+
+        final Integer defaultNumberOfRepayments = Integer.valueOf(4);
+        final AmortizationMethod amortizationMethod = AmortizationMethod.EQUAL_PRINCIPAL;
+
+        final BigDecimal inArrearsTolerance = BigDecimal.ZERO;
+
+        return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
+                defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
+                defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
+    }
+
+    public static LoanProductRelatedDetail createSettingsForEqualInstallmentAmortizationQuarterly() {
+        final MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
+        final BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
+
+        // 2% per month, 24% per year
+        final BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
+        final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS;
+        final BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24"));
+
+        final InterestMethod interestMethod = InterestMethod.DECLINING_BALANCE;
+        final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
+        final Integer repayEvery = Integer.valueOf(3);
+        final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS;
+
+        final Integer defaultNumberOfRepayments = Integer.valueOf(4);
+        final AmortizationMethod amortizationMethod = AmortizationMethod.EQUAL_INSTALLMENTS;
+
+        final BigDecimal inArrearsTolerance = BigDecimal.ZERO;
+
+        return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
+                defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
+                defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
+    }
+
+    public static LoanProductRelatedDetail createSettingsForFlatQuarterly(final AmortizationMethod amortizationMethod) {
+
+        final MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
+        final BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
+
+        // 2% per month, 24% per year
+        final BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
+        final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS;
+        final BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24"));
+
+        final InterestMethod interestMethod = InterestMethod.FLAT;
+        final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
+        final Integer repayEvery = Integer.valueOf(3);
+        final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS;
+
+        final Integer defaultNumberOfRepayments = Integer.valueOf(4);
+
+        final BigDecimal inArrearsTolerance = BigDecimal.ZERO;
+
+        return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
+                defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
+                defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
+    }
+
+    public static LoanProductRelatedDetail createSettingsForIrregularFlatEveryFourMonths() {
+
+        final MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("KSH").withDigitsAfterDecimal(0).build();
+        final BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("15000"));
+
+        // 2% per month, 24% per year
+        final BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
+        final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS;
+        final BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24"));
+
+        final InterestMethod interestMethod = InterestMethod.FLAT;
+        final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
+        final Integer repayEvery = Integer.valueOf(3);
+        final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS;
+
+        final Integer defaultNumberOfRepayments = Integer.valueOf(2);
+
+        final BigDecimal inArrearsTolerance = BigDecimal.ZERO;
+
+        final AmortizationMethod amortizationMethod = AmortizationMethod.EQUAL_PRINCIPAL;
+
+        return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
+                defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
+                defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
+    }
+
+    private static LoanProductRelatedDetail createLoanProductRelatedDetail(final MonetaryCurrency currency,
+            final BigDecimal defaultPrincipal, final BigDecimal defaultNominalInterestRatePerPeriod,
+            final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal defaultAnnualNominalInterestRate,
+            final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod,
+            final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments,
+            final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance) {
+
+        final Integer graceOnPrincipalPayment = Integer.valueOf(0);
+        final Integer graceOnInterestPayment = Integer.valueOf(0);
+        final Integer graceOnInterestCharged = Integer.valueOf(0);
+        final Integer graceOnArrearsAgeing = Integer.valueOf(0);
+
+        final Integer daysInMonthType = DaysInMonthType.ACTUAL.getValue();
+        final Integer daysInYearType = DaysInYearType.ACTUAL.getValue();
+        final boolean isInterestRecalculationEnabled = false;
+        final boolean considerPartialPeriodInterest = false;
+
+        return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
+                defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, considerPartialPeriodInterest, repayEvery,
+                repaymentFrequencyType, defaultNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
+                amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java
new file mode 100644
index 0000000..c8a1bba
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.joda.time.LocalDate;
+
+public class LoanRepaymentScheduleInstallmentBuilder {
+
+    private final Loan loan = null;
+    private Integer installmentNumber = Integer.valueOf(1);
+    private final LocalDate fromDate = LocalDate.now();
+    private LocalDate dueDate = LocalDate.now();
+    private final LocalDate latestTransactionDate = LocalDate.now();
+    private MonetaryCurrency currencyDetail = new MonetaryCurrencyBuilder().build();
+    private Money principal = new MoneyBuilder().build();
+    private Money interest = new MoneyBuilder().build();
+    private final Money feeCharges = new MoneyBuilder().build();
+    private final Money penaltyCharges = new MoneyBuilder().build();
+    private boolean completed = false;
+    private boolean recalculatedInterestComponent = false;
+
+    public LoanRepaymentScheduleInstallmentBuilder(final MonetaryCurrency currencyDetail) {
+        this.currencyDetail = currencyDetail;
+        this.principal = new MoneyBuilder().with(currencyDetail).build();
+        this.interest = new MoneyBuilder().with(currencyDetail).build();
+    }
+
+    public LoanRepaymentScheduleInstallment build() {
+        final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this.loan, this.installmentNumber,
+                this.fromDate, this.dueDate, this.principal.getAmount(), this.interest.getAmount(), this.feeCharges.getAmount(),
+                this.penaltyCharges.getAmount(), this.recalculatedInterestComponent);
+        if (this.completed) {
+            installment.payPrincipalComponent(this.latestTransactionDate, this.principal);
+            installment.payInterestComponent(this.latestTransactionDate, this.interest);
+        }
+        return installment;
+    }
+
+    public LoanRepaymentScheduleInstallmentBuilder withPrincipal(final String withPrincipal) {
+        this.principal = new MoneyBuilder().with(this.currencyDetail).with(withPrincipal).build();
+        return this;
+    }
+
+    public LoanRepaymentScheduleInstallmentBuilder withInterest(final String withInterest) {
+        this.interest = new MoneyBuilder().with(this.currencyDetail).with(withInterest).build();
+        return this;
+    }
+
+    public LoanRepaymentScheduleInstallmentBuilder withDueDate(final LocalDate withDueDate) {
+        this.dueDate = withDueDate;
+        return this;
+    }
+
+    public LoanRepaymentScheduleInstallmentBuilder completed() {
+        this.completed = true;
+        return this;
+    }
+
+    public LoanRepaymentScheduleInstallmentBuilder withInstallmentNumber(final int withInstallmentNumber) {
+        this.installmentNumber = withInstallmentNumber;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanScheduleTestDataHelper.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanScheduleTestDataHelper.java
new file mode 100644
index 0000000..9041188
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanScheduleTestDataHelper.java
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.joda.time.LocalDate;
+
+/**
+ * Helper class for creating loan schedule data suitable for testing.
+ */
+public class LoanScheduleTestDataHelper {
+
+    /**
+     * Creates brand new three installment loan:
+     * 
+     * For example: with firstDueDate = 02 July 2011
+     * 
+     * Date Principal Interest Interest Waived
+     * ==================================
+     * ================================================ 02 July 2011 1,000 200 0
+     * 02 August 2011 1,000 200 0 02 September 2011 1,000 200 0
+     */
+    public static List<LoanRepaymentScheduleInstallment> createSimpleLoanSchedule(final LocalDate firstDueDate,
+            final MonetaryCurrency currency) {
+        final LoanRepaymentScheduleInstallment firstInstallment = new LoanRepaymentScheduleInstallmentBuilder(currency)
+                .withInstallmentNumber(1).withDueDate(firstDueDate).withPrincipal("1000.00").withInterest("200.00").build();
+
+        final LoanRepaymentScheduleInstallment secondInstallment = new LoanRepaymentScheduleInstallmentBuilder(currency)
+                .withInstallmentNumber(2).withDueDate(firstDueDate.plusMonths(1)).withPrincipal("1000.00").withInterest("200.00").build();
+
+        final LoanRepaymentScheduleInstallment thirdInstallment = new LoanRepaymentScheduleInstallmentBuilder(currency)
+                .withInstallmentNumber(3).withDueDate(firstDueDate.plusMonths(2)).withPrincipal("1000.00").withInterest("200.00").build();
+
+        return Arrays.asList(firstInstallment, secondInstallment, thirdInstallment);
+    }
+
+    /**
+     * Creates three installment loan with first installment fully completed:
+     * 
+     * For example: with firstDueDate = 02 July 2011
+     * 
+     * Date Principal Interest Interest Waived Completed
+     * ========================
+     * ==================================================
+     * ====================================== 02 July 2011 1,000 200 0 true
+     * (principal paid, interest paid) 02 August 2011 1,000 200 0 false 02
+     * September 2011 1,000 200 0 false
+     */
+    public static List<LoanRepaymentScheduleInstallment> createSimpleLoanScheduleWithFirstInstallmentFullyPaid(
+            final LocalDate firstDueDate, final MonetaryCurrency currency) {
+
+        final LoanRepaymentScheduleInstallment firstInstallment = new LoanRepaymentScheduleInstallmentBuilder(currency)
+                .withInstallmentNumber(1).withDueDate(firstDueDate).withPrincipal("1000.00").withInterest("200.00").completed().build();
+
+        final LoanRepaymentScheduleInstallment secondInstallment = new LoanRepaymentScheduleInstallmentBuilder(currency)
+                .withInstallmentNumber(2).withDueDate(firstDueDate.plusMonths(1)).withPrincipal("1000.00").withInterest("200.00").build();
+
+        final LoanRepaymentScheduleInstallment thirdInstallment = new LoanRepaymentScheduleInstallmentBuilder(currency)
+                .withInstallmentNumber(3).withDueDate(firstDueDate.plusMonths(2)).withPrincipal("1000.00").withInterest("200.00").build();
+
+        return Arrays.asList(firstInstallment, secondInstallment, thirdInstallment);
+    }
+
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanTransactionBuilder.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanTransactionBuilder.java
new file mode 100644
index 0000000..a8baf7a
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanTransactionBuilder.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount;
+
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.joda.time.LocalDate;
+
+public class LoanTransactionBuilder {
+
+    @SuppressWarnings("unused")
+    private Money transactionAmount = new MoneyBuilder().build();
+    @SuppressWarnings("unused")
+    private LocalDate transactionDate = LocalDate.now();
+    @SuppressWarnings("unused")
+    private boolean repayment = false;
+
+    /**
+     * public LoanTransaction build() {
+     * 
+     * LoanTransaction transaction = null;
+     * 
+     * if (repayment) { transaction =
+     * LoanTransaction.repayment(transactionAmount, transactionDate); }
+     * 
+     * return transaction; }
+     **/
+
+    public LoanTransactionBuilder with(final Money newAmount) {
+        this.transactionAmount = newAmount;
+        return this;
+    }
+
+    public LoanTransactionBuilder with(final LocalDate withTransactionDate) {
+        this.transactionDate = withTransactionDate;
+        return this;
+    }
+
+    public LoanTransactionBuilder repayment() {
+        this.repayment = true;
+        return this;
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/MonetaryCurrencyBuilder.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/MonetaryCurrencyBuilder.java
new file mode 100644
index 0000000..3effadb
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/MonetaryCurrencyBuilder.java
@@ -0,0 +1,42 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+
+public class MonetaryCurrencyBuilder {
+
+    private String code = "XOF";
+    private int digitsAfterDecimal = 0;
+    private final Integer inMultiplesOf = null;
+
+    public MonetaryCurrency build() {
+        return new MonetaryCurrency(this.code, this.digitsAfterDecimal, this.inMultiplesOf);
+    }
+
+    public MonetaryCurrencyBuilder withCode(final String withCode) {
+        this.code = withCode;
+        return this;
+    }
+
+    public MonetaryCurrencyBuilder withDigitsAfterDecimal(final int withDigitsAfterDecimal) {
+        this.digitsAfterDecimal = withDigitsAfterDecimal;
+        return this;
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/MoneyBuilder.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/MoneyBuilder.java
new file mode 100644
index 0000000..31dbe4d
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/MoneyBuilder.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount;
+
+import java.math.BigDecimal;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+public class MoneyBuilder {
+
+    private MonetaryCurrency currencyDetail = new MonetaryCurrencyBuilder().build();
+    private BigDecimal newAmount = BigDecimal.ZERO;
+
+    public Money build() {
+        return Money.of(this.currencyDetail, this.newAmount);
+    }
+
+    public MoneyBuilder with(final MonetaryCurrency withDetail) {
+        this.currencyDetail = withDetail;
+        return this;
+    }
+
+    public MoneyBuilder with(final String withAmount) {
+        this.newAmount = BigDecimal.valueOf(Double.valueOf(withAmount));
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/test/java/org/apache/fineract/template/TemplateMergeServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/template/TemplateMergeServiceTest.java
new file mode 100644
index 0000000..9b22153
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/template/TemplateMergeServiceTest.java
@@ -0,0 +1,134 @@
+/**
+ * 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.template;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.math.RoundingMode;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.loanaccount.LoanScheduleTestDataHelper;
+import org.apache.fineract.portfolio.loanaccount.MonetaryCurrencyBuilder;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.template.domain.Template;
+import org.apache.fineract.template.domain.TemplateMapper;
+import org.apache.fineract.template.service.TemplateMergeService;
+import org.joda.time.LocalDate;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+public class TemplateMergeServiceTest {
+
+    private TemplateMergeService tms = new TemplateMergeService();
+    
+    @Before
+    public void setUpForEachTestCase() throws Exception {
+
+        Field field = MoneyHelper.class.getDeclaredField("roundingMode");
+        field.setAccessible(true);
+        field.set(null, RoundingMode.HALF_EVEN);
+    }
+
+    
+
+    @Test
+    public void compileHelloTemplate() throws Exception {
+        String templateText = "Hello Test for Template {{file.name}}!";
+
+        File file = new File("hello");
+        Map<String, Object> scopes = new HashMap<>();
+        scopes.put("file", file);
+
+        String output = compileTemplateText(templateText, scopes);
+        assertEquals("Hello Test for Template hello!", output);
+    }
+
+    @Test
+    public void compileLoanSummary() throws IOException {
+        LocalDate july2nd = new LocalDate(2012, 7, 2);
+        MonetaryCurrency usDollars = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
+        List<LoanRepaymentScheduleInstallment> installments = LoanScheduleTestDataHelper.createSimpleLoanSchedule(july2nd, usDollars);
+
+        Map<String, Object> scopes = new HashMap<>();
+        scopes.put("installments", installments);
+
+        String templateText = Resources.toString(Resources.getResource("template.mustache"), Charsets.UTF_8);
+        String expectedOutput = Resources.toString(Resources.getResource("template-expected.html"), Charsets.UTF_8);
+
+        String output = compileTemplateText(templateText, scopes);
+        // System.out.println(output);
+        assertEquals(expectedOutput, output);
+    }
+
+    @Test
+    public void arrayUsingLoop() throws Exception {
+        String templateText = "Hello Test for Template{{#data.name}} {{.}}{{/data.name}}!";
+        String jsonData = "{\"name\": [ \"Michael\", \"Terence\" ] }";
+        String expectedOutput = "Hello Test for Template Michael Terence!";
+
+        Map<String, Object> scopes = new HashMap<>();
+        scopes.put("data", createMapFromJSON(jsonData));
+
+        String output = compileTemplateText(templateText, scopes);
+        assertEquals(expectedOutput, output);
+    }
+
+    @Test
+    public void arrayUsingIndex() throws Exception {
+        String templateText = "Hello Test for Template {{data.name#1}} & {{data.name#0}}!";
+        String jsonData = "{\"name\": [ \"Michael\", \"Terence\" ] }";
+        String expectedOutput = "Hello Test for Template Terence & Michael!";
+
+        Map<String, Object> scopes = new HashMap<>();
+        scopes.put("data", createMapFromJSON(jsonData));
+
+        String output = compileTemplateText(templateText, scopes);
+        assertEquals(expectedOutput, output);
+    }
+
+    protected String compileTemplateText(String templateText, Map<String, Object> scope) throws MalformedURLException, IOException {
+        List<TemplateMapper> mappers = new ArrayList<>();
+        Template template = new Template("TemplateName", templateText, null, null, mappers);
+        return tms.compile(template, scope);
+    }
+    
+    protected Map<String, Object> createMapFromJSON(String jsonText) {
+        Gson gson = new Gson();
+        Type ssMap = new TypeToken<Map<String, Object>>(){}.getType();
+        JsonElement json = new JsonParser().parse(jsonText);
+        return gson.fromJson(json, ssMap);
+    }
+}
diff --git a/fineract-provider/src/test/resources/META-INF/context.xml b/fineract-provider/src/test/resources/META-INF/context.xml
new file mode 100644
index 0000000..e418870
--- /dev/null
+++ b/fineract-provider/src/test/resources/META-INF/context.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    This Source Code Form is subject to the terms of the Mozilla Public
+    License, v. 2.0. If a copy of the MPL was not distributed with this file,
+    You can obtain one at http://mozilla.org/MPL/2.0/.
+
+-->
+<Context>
+	<Resource type="javax.sql.DataSource" name="jdbc/mifosplatform-tenants"
+		driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/mifosplatform-tenants"
+		username="root" password="mysql" validationQuery="select 1" maxActive="10"
+		maxIdle="4" />
+</Context>
\ No newline at end of file
diff --git a/fineract-provider/src/test/resources/template-expected.html b/fineract-provider/src/test/resources/template-expected.html
new file mode 100644
index 0000000..a008dae
--- /dev/null
+++ b/fineract-provider/src/test/resources/template-expected.html
@@ -0,0 +1,152 @@
+<h1><span style="font-size:20px"><strong><span style="color:#0000CD">Sample Loan Summary &amp; Repayment Schedule&nbsp;</span></strong></span></h1>
+
+<h2><span style="font-size:16px"><span style="color:#0000CD">TABLE 1. LOAN SUMMARY</span>&nbsp;</span></h2>
+
+<table align="left" border="1" cellpadding="3" cellspacing="0" style="width:650px">
+    <tbody>
+        <tr>
+            <td>Loan size and currency</td>
+            <td>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</td>
+            <td>Total no. of installments&nbsp;</td>
+            <td>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Interest rate&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>Installment&nbsp;frequency</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Commission fee</td>
+            <td>&nbsp;</td>
+            <td>Disbursement date&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Other fees</td>
+            <td>&nbsp;</td>
+            <td>Mandatory savings</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Purpose of loan</td>
+            <td>&nbsp;</td>
+            <td>Maturity date&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Grace period (if applies)</td>
+            <td>&nbsp;</td>
+            <td>Type of guarantee</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Penalty for late payment</td>
+            <td>&nbsp;</td>
+            <td>Maturity of the loan (in months)&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Penalty for pre-payment</td>
+            <td>&nbsp;</td>
+            <td>Total cost of loan (principle + int.+ fees)&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+    </tbody>
+</table>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<h2><span style="font-size:16px"><span style="color:#0000CD">TABLE 2. REPAYMENT SCHEDULE&nbsp;</span></span></h2>
+
+<table border="1" cellpadding="3" cellspacing="0" style="width:600px">
+    <tbody>
+        <tr>
+            <td>Date</td>
+            <td>Disbursement&nbsp;Amount</td>
+            <td>
+            <p>Principle Repayment</p>
+            </td>
+            <td>&nbsp;Interest&nbsp;Repayment</td>
+            <td>Commission&nbsp;Payment</td>
+            <td>Fees</td>
+            <td>Tax Payment</td>
+            <td>Total&nbsp;Repayment</td>
+            <td>Total Balance&nbsp;Outstanding</td>
+        </tr>
+            <tr>
+                <td>2012-07-02</td>
+                <td>1</td>
+                <td>NO fromDate AS IT CHANGES</td>
+                <td></td>
+                <td>&nbsp;</td>
+                <td></td>
+                <td>&nbsp;</td>
+                <td>&nbsp;</td>
+                <td>&nbsp;</td>
+            </tr>
+            <tr>
+                <td>2012-08-02</td>
+                <td>2</td>
+                <td>NO fromDate AS IT CHANGES</td>
+                <td></td>
+                <td>&nbsp;</td>
+                <td></td>
+                <td>&nbsp;</td>
+                <td>&nbsp;</td>
+                <td>&nbsp;</td>
+            </tr>
+            <tr>
+                <td>2012-09-02</td>
+                <td>3</td>
+                <td>NO fromDate AS IT CHANGES</td>
+                <td></td>
+                <td>&nbsp;</td>
+                <td></td>
+                <td>&nbsp;</td>
+                <td>&nbsp;</td>
+                <td>&nbsp;</td>
+            </tr>
+        <tr>
+            <td>Total</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+    </tbody>
+</table>
+
+<p>&nbsp;</p>
+
+<p>Client&nbsp;Signature&nbsp;___________________________________________________________________&nbsp;</p>
+
+<p>Credit Officer Signature _____________________________________________________________&nbsp;</p>
+
+<p>Branch&nbsp;Director&nbsp;Signature&nbsp;___________________________________________________________&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p><span style="font-size:12px"><strong>Please contact the<span style="color:#FF0000"> Macro</span><span style="color:#0000CD">Dreams</span> Customer Service Line if you have questions or complaints about our products,&nbsp;</strong></span></p>
+
+<p><span style="font-size:12px"><strong>services, or staff: 555-555-5555. Open Monday to Saturday 7:00-20:00. All calls are free.&nbsp;</strong></span></p>
diff --git a/fineract-provider/src/test/resources/template.mustache b/fineract-provider/src/test/resources/template.mustache
new file mode 100644
index 0000000..959b61f
--- /dev/null
+++ b/fineract-provider/src/test/resources/template.mustache
@@ -0,0 +1,132 @@
+<h1><span style="font-size:20px"><strong><span style="color:#0000CD">Sample Loan Summary &amp; Repayment Schedule&nbsp;</span></strong></span></h1>
+
+<h2><span style="font-size:16px"><span style="color:#0000CD">TABLE 1. LOAN SUMMARY</span>&nbsp;</span></h2>
+
+<table align="left" border="1" cellpadding="3" cellspacing="0" style="width:650px">
+    <tbody>
+        <tr>
+            <td>Loan size and currency</td>
+            <td>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</td>
+            <td>Total no. of installments&nbsp;</td>
+            <td>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Interest rate&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>Installment&nbsp;frequency</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Commission fee</td>
+            <td>&nbsp;</td>
+            <td>Disbursement date&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Other fees</td>
+            <td>&nbsp;</td>
+            <td>Mandatory savings</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Purpose of loan</td>
+            <td>&nbsp;</td>
+            <td>Maturity date&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Grace period (if applies)</td>
+            <td>&nbsp;</td>
+            <td>Type of guarantee</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Penalty for late payment</td>
+            <td>&nbsp;</td>
+            <td>Maturity of the loan (in months)&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+        <tr>
+            <td>Penalty for pre-payment</td>
+            <td>&nbsp;</td>
+            <td>Total cost of loan (principle + int.+ fees)&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+    </tbody>
+</table>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<h2><span style="font-size:16px"><span style="color:#0000CD">TABLE 2. REPAYMENT SCHEDULE&nbsp;</span></span></h2>
+
+<table border="1" cellpadding="3" cellspacing="0" style="width:600px">
+    <tbody>
+        <tr>
+            <td>Date</td>
+            <td>Disbursement&nbsp;Amount</td>
+            <td>
+            <p>Principle Repayment</p>
+            </td>
+            <td>&nbsp;Interest&nbsp;Repayment</td>
+            <td>Commission&nbsp;Payment</td>
+            <td>Fees</td>
+            <td>Tax Payment</td>
+            <td>Total&nbsp;Repayment</td>
+            <td>Total Balance&nbsp;Outstanding</td>
+        </tr>
+        {{#installments}}
+            <tr>
+                <td>{{dueDate}}</td>
+                <td>{{installmentNumber}}</td>
+                <td>NO fromDate AS IT CHANGES</td>
+                <td>{{principal}}</td>
+                <td>&nbsp;</td>
+                <td>{{feeChargesCharged}}</td>
+                <td>&nbsp;</td>
+                <td>&nbsp;</td>
+                <td>&nbsp;</td>
+            </tr>
+        {{/installments}}
+        <tr>
+            <td>Total</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+        </tr>
+    </tbody>
+</table>
+
+<p>&nbsp;</p>
+
+<p>Client&nbsp;Signature&nbsp;___________________________________________________________________&nbsp;</p>
+
+<p>Credit Officer Signature _____________________________________________________________&nbsp;</p>
+
+<p>Branch&nbsp;Director&nbsp;Signature&nbsp;___________________________________________________________&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p>&nbsp;</p>
+
+<p><span style="font-size:12px"><strong>Please contact the<span style="color:#FF0000"> Macro</span><span style="color:#0000CD">Dreams</span> Customer Service Line if you have questions or complaints about our products,&nbsp;</strong></span></p>
+
+<p><span style="font-size:12px"><strong>services, or staff: 555-555-5555. Open Monday to Saturday 7:00-20:00. All calls are free.&nbsp;</strong></span></p>
diff --git a/release.sh b/release.sh
new file mode 100755
index 0000000..b1f021c
--- /dev/null
+++ b/release.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+VERSION=$1
+if [ -z $VERSION ]; then
+  echo "Usage: $0 version [releasedDate]"
+  exit 1;
+fi
+
+RELDATE=$2
+if [ -z $RELDATE ]; then
+  RELDATE=$(date '+%d/%b/%y')
+fi
+
+APPDIR=apps/community-app
+"$APPDIR/release.sh" "$VERSION" "$RELDATE"
+echo "releaseVersion=$VERSION.RELEASE" > fineract-provider/gradle.properties
+
diff --git a/travis_build.sh b/travis_build.sh
new file mode 100755
index 0000000..222870a
--- /dev/null
+++ b/travis_build.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+cd fineract-provider
+# NOTE: The --info, while quite a bit more verbose, is VERY useful to understand failures on Travis,
+# where you do not have access to any files like build/reports/tests/index.html, only the Console. 
+# @see http://mrhaki.blogspot.ch/2013/05/gradle-goodness-show-more-information.html
+# @see http://forums.gradle.org/gradle/topics/whats_new_in_gradle_1_1_test_logging for alternative 
+./gradlew --info clean licenseMain licenseTest licenseIntegrationTest test